• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

knex / knex / 3980064122

pending completion
3980064122

push

github

GitHub
Additional lint checks before publishing (#5459)

4186 of 4984 branches covered (83.99%)

Branch coverage included in aggregate %.

11 of 11 new or added lines in 7 files covered. (100.0%)

19749 of 20922 relevant lines covered (94.39%)

1379.83 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

95.89
/lib/schema/tablebuilder.js
1
// TableBuilder
2

3
// Takes the function passed to the "createTable" or "table/editTable"
4
// functions and calls it with the "TableBuilder" as both the context and
5
// the first argument. Inside this function we can specify what happens to the
6
// method, pushing everything we want to do onto the "allStatements" array,
7
// which is then compiled into sql.
8
// ------
9
const each = require('lodash/each');
63✔
10
const extend = require('lodash/extend');
63✔
11
const assign = require('lodash/assign');
63✔
12
const toArray = require('lodash/toArray');
63✔
13
const helpers = require('../util/helpers');
63✔
14
const { isString, isFunction, isObject } = require('../util/is');
63✔
15

16
class TableBuilder {
17
  constructor(client, method, tableName, tableNameLike, fn) {
18
    this.client = client;
3,213✔
19
    this._fn = fn;
3,213✔
20
    this._method = method;
3,213✔
21
    this._schemaName = undefined;
3,213✔
22
    this._tableName = tableName;
3,213✔
23
    this._tableNameLike = tableNameLike;
3,213✔
24
    this._statements = [];
3,213✔
25
    this._single = {};
3,213✔
26

27
    if (!tableNameLike && !isFunction(this._fn)) {
3,213✔
28
      throw new TypeError(
14✔
29
        'A callback function must be supplied to calls against `.createTable` ' +
30
          'and `.table`'
31
      );
32
    }
33
  }
34

35
  setSchema(schemaName) {
36
    this._schemaName = schemaName;
3,199✔
37
  }
38

39
  // Convert the current tableBuilder object "toSQL"
40
  // giving us additional methods if we're altering
41
  // rather than creating the table.
42
  toSQL() {
43
    if (this._method === 'alter') {
3,199✔
44
      extend(this, AlterMethods);
1,209✔
45
    }
46
    // With 'create table ... like' callback function is useless.
47
    if (this._fn) {
3,199✔
48
      this._fn.call(this, this);
3,180✔
49
    }
50
    return this.client.tableCompiler(this).toSQL();
3,173✔
51
  }
52

53
  // The "timestamps" call is really just sets the `created_at` and `updated_at` columns.
54

55
  timestamps(useTimestamps, defaultToNow, useCamelCase) {
56
    if (isObject(useTimestamps)) {
130✔
57
      ({ useTimestamps, defaultToNow, useCamelCase } = useTimestamps);
13✔
58
    }
59
    const method = useTimestamps === true ? 'timestamp' : 'datetime';
130✔
60
    const createdAt = this[method](useCamelCase ? 'createdAt' : 'created_at');
130✔
61
    const updatedAt = this[method](useCamelCase ? 'updatedAt' : 'updated_at');
130✔
62

63
    if (defaultToNow === true) {
130✔
64
      const now = this.client.raw('CURRENT_TIMESTAMP');
15✔
65
      createdAt.notNullable().defaultTo(now);
15✔
66
      updatedAt.notNullable().defaultTo(now);
15✔
67
    }
68
  }
69

70
  // Set the comment value for a table, they're only allowed to be called
71
  // once per table.
72
  comment(value) {
73
    if (typeof value !== 'string') {
41✔
74
      throw new TypeError('Table comment must be string');
12✔
75
    }
76
    this._single.comment = value;
29✔
77
  }
78

79
  // Set a foreign key on the table, calling
80
  // `table.foreign('column_name').references('column').on('table').onDelete()...
81
  // Also called from the ColumnBuilder context when chaining.
82
  foreign(column, keyName) {
83
    const foreignData = { column: column, keyName: keyName };
197✔
84
    this._statements.push({
197✔
85
      grouping: 'alterTable',
86
      method: 'foreign',
87
      args: [foreignData],
88
    });
89
    let returnObj = {
197✔
90
      references(tableColumn) {
91
        let pieces;
92
        if (isString(tableColumn)) {
197✔
93
          pieces = tableColumn.split('.');
191✔
94
        }
95
        if (!pieces || pieces.length === 1) {
197✔
96
          foreignData.references = pieces ? pieces[0] : tableColumn;
122✔
97
          return {
122✔
98
            on(tableName) {
99
              if (typeof tableName !== 'string') {
122✔
100
                throw new TypeError(
21✔
101
                  `Expected tableName to be a string, got: ${typeof tableName}`
102
                );
103
              }
104
              foreignData.inTable = tableName;
101✔
105
              return returnObj;
101✔
106
            },
107
            inTable() {
108
              return this.on.apply(this, arguments);
94✔
109
            },
110
          };
111
        }
112
        foreignData.inTable = pieces[0];
75✔
113
        foreignData.references = pieces[1];
75✔
114
        return returnObj;
75✔
115
      },
116
      withKeyName(keyName) {
117
        foreignData.keyName = keyName;
48✔
118
        return returnObj;
48✔
119
      },
120
      onUpdate(statement) {
121
        foreignData.onUpdate = statement;
12✔
122
        return returnObj;
12✔
123
      },
124
      onDelete(statement) {
125
        foreignData.onDelete = statement;
13✔
126
        return returnObj;
13✔
127
      },
128
      deferrable: (type) => {
129
        const unSupported = [
14✔
130
          'mysql',
131
          'mssql',
132
          'redshift',
133
          'mysql2',
134
          'oracledb',
135
        ];
136
        if (unSupported.indexOf(this.client.dialect) !== -1) {
14✔
137
          throw new Error(`${this.client.dialect} does not support deferrable`);
3✔
138
        }
139
        foreignData.deferrable = type;
11✔
140
        return returnObj;
11✔
141
      },
142
      _columnBuilder(builder) {
143
        extend(builder, returnObj);
121✔
144
        returnObj = builder;
121✔
145
        return builder;
121✔
146
      },
147
    };
148
    return returnObj;
197✔
149
  }
150

151
  check(checkPredicate, bindings, constraintName) {
152
    this._statements.push({
28✔
153
      grouping: 'checks',
154
      args: [checkPredicate, bindings, constraintName],
155
    });
156
    return this;
28✔
157
  }
158
}
159

160
[
63✔
161
  // Each of the index methods can be called individually, with the
162
  // column name to be used, e.g. table.unique('column').
163
  'index',
164
  'primary',
165
  'unique',
166

167
  // Key specific
168
  'dropPrimary',
169
  'dropUnique',
170
  'dropIndex',
171
  'dropForeign',
172
].forEach((method) => {
173
  TableBuilder.prototype[method] = function () {
441✔
174
    this._statements.push({
958✔
175
      grouping: 'alterTable',
176
      method,
177
      args: toArray(arguments),
178
    });
179
    return this;
958✔
180
  };
181
});
182

183
// Warn for dialect-specific table methods, since that's the
184
// only time these are supported.
185
const specialMethods = {
63✔
186
  mysql: ['engine', 'charset', 'collate'],
187
  postgresql: ['inherits'],
188
};
189
each(specialMethods, function (methods, dialect) {
63✔
190
  methods.forEach(function (method) {
126✔
191
    TableBuilder.prototype[method] = function (value) {
252✔
192
      if (this.client.dialect !== dialect) {
61✔
193
        throw new Error(
4✔
194
          `Knex only supports ${method} statement with ${dialect}.`
195
        );
196
      }
197
      if (this._method === 'alter') {
57!
198
        throw new Error(
×
199
          `Knex does not support altering the ${method} outside of create ` +
200
            `table, please use knex.raw statement.`
201
        );
202
      }
203
      this._single[method] = value;
57✔
204
    };
205
  });
206
});
207

208
helpers.addQueryContext(TableBuilder);
63✔
209

210
// Each of the column types that we can add, we create a new ColumnBuilder
211
// instance and push it onto the statements array.
212
const columnTypes = [
63✔
213
  // Numeric
214
  'tinyint',
215
  'smallint',
216
  'mediumint',
217
  'int',
218
  'bigint',
219
  'decimal',
220
  'float',
221
  'double',
222
  'real',
223
  'bit',
224
  'boolean',
225
  'serial',
226

227
  // Date / Time
228
  'date',
229
  'datetime',
230
  'timestamp',
231
  'time',
232
  'year',
233

234
  // Geometry
235
  'geometry',
236
  'geography',
237
  'point',
238

239
  // String
240
  'char',
241
  'varchar',
242
  'tinytext',
243
  'tinyText',
244
  'text',
245
  'mediumtext',
246
  'mediumText',
247
  'longtext',
248
  'longText',
249
  'binary',
250
  'varbinary',
251
  'tinyblob',
252
  'tinyBlob',
253
  'mediumblob',
254
  'mediumBlob',
255
  'blob',
256
  'longblob',
257
  'longBlob',
258
  'enum',
259
  'set',
260

261
  // Increments, Aliases, and Additional
262
  'bool',
263
  'dateTime',
264
  'increments',
265
  'bigincrements',
266
  'bigIncrements',
267
  'integer',
268
  'biginteger',
269
  'bigInteger',
270
  'string',
271
  'json',
272
  'jsonb',
273
  'uuid',
274
  'enu',
275
  'specificType',
276
];
277

278
// For each of the column methods, create a new "ColumnBuilder" interface,
279
// push it onto the "allStatements" stack, and then return the interface,
280
// with which we can add indexes, etc.
281
columnTypes.forEach((type) => {
63✔
282
  TableBuilder.prototype[type] = function () {
3,402✔
283
    const args = toArray(arguments);
6,698✔
284
    const builder = this.client.columnBuilder(this, type, args);
6,698✔
285
    this._statements.push({
6,698✔
286
      grouping: 'columns',
287
      builder,
288
    });
289
    return builder;
6,698✔
290
  };
291
});
292

293
const AlterMethods = {
63✔
294
  // Renames the current column `from` the current
295
  // TODO: this.column(from).rename(to)
296
  renameColumn(from, to) {
297
    this._statements.push({
50✔
298
      grouping: 'alterTable',
299
      method: 'renameColumn',
300
      args: [from, to],
301
    });
302
    return this;
50✔
303
  },
304

305
  dropTimestamps() {
306
    // arguments[0] = useCamelCase
307
    return this.dropColumns(
7✔
308
      arguments[0] === true
7!
309
        ? ['createdAt', 'updatedAt']
310
        : ['created_at', 'updated_at']
311
    );
312
  },
313

314
  setNullable(column) {
315
    this._statements.push({
12✔
316
      grouping: 'alterTable',
317
      method: 'setNullable',
318
      args: [column],
319
    });
320

321
    return this;
12✔
322
  },
323

324
  check(checkPredicate, bindings, constraintName) {
325
    this._statements.push({
×
326
      grouping: 'alterTable',
327
      method: 'check',
328
      args: [checkPredicate, bindings, constraintName],
329
    });
330
  },
331

332
  dropChecks() {
333
    this._statements.push({
14✔
334
      grouping: 'alterTable',
335
      method: 'dropChecks',
336
      args: toArray(arguments),
337
    });
338
  },
339

340
  dropNullable(column) {
341
    this._statements.push({
12✔
342
      grouping: 'alterTable',
343
      method: 'dropNullable',
344
      args: [column],
345
    });
346

347
    return this;
12✔
348
  },
349

350
  // TODO: changeType
351
};
352

353
// Drop a column from the current table.
354
// TODO: Enable this.column(columnName).drop();
355
AlterMethods.dropColumn = AlterMethods.dropColumns = function () {
63✔
356
  this._statements.push({
203✔
357
    grouping: 'alterTable',
358
    method: 'dropColumn',
359
    args: toArray(arguments),
360
  });
361
  return this;
203✔
362
};
363

364
TableBuilder.extend = (methodName, fn) => {
63✔
365
  if (
1!
366
    Object.prototype.hasOwnProperty.call(TableBuilder.prototype, methodName)
367
  ) {
368
    throw new Error(
×
369
      `Can't extend TableBuilder with existing method ('${methodName}').`
370
    );
371
  }
372

373
  assign(TableBuilder.prototype, { [methodName]: fn });
1✔
374
};
375

376
module.exports = TableBuilder;
63✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc