Change endpoint from persons to people

This commit is contained in:
xfarrow
2025-03-23 21:00:08 +01:00
parent 4ae263662c
commit d005193f63
7158 changed files with 700476 additions and 735 deletions

View File

@ -0,0 +1,115 @@
const { EventEmitter } = require('events');
const toArray = require('lodash/toArray');
const assign = require('lodash/assign');
const { addQueryContext } = require('../util/helpers');
const saveAsyncStack = require('../util/save-async-stack');
const {
augmentWithBuilderInterface,
} = require('../builder-interface-augmenter');
// Constructor for the builder instance, typically called from
// `knex.builder`, accepting the current `knex` instance,
// and pulling out the `client` and `grammar` from the current
// knex instance.
class SchemaBuilder extends EventEmitter {
constructor(client) {
super();
this.client = client;
this._sequence = [];
if (client.config) {
this._debug = client.config.debug;
saveAsyncStack(this, 4);
}
}
withSchema(schemaName) {
this._schema = schemaName;
return this;
}
toString() {
return this.toQuery();
}
toSQL() {
return this.client.schemaCompiler(this).toSQL();
}
async generateDdlCommands() {
return await this.client.schemaCompiler(this).generateDdlCommands();
}
}
// Each of the schema builder methods just add to the
// "_sequence" array for consistency.
[
'createTable',
'createTableIfNotExists',
'createTableLike',
'createView',
'createViewOrReplace',
'createMaterializedView',
'refreshMaterializedView',
'dropView',
'dropViewIfExists',
'dropMaterializedView',
'dropMaterializedViewIfExists',
'createSchema',
'createSchemaIfNotExists',
'dropSchema',
'dropSchemaIfExists',
'createExtension',
'createExtensionIfNotExists',
'dropExtension',
'dropExtensionIfExists',
'table',
'alterTable',
'view',
'alterView',
'hasTable',
'hasColumn',
'dropTable',
'renameTable',
'renameView',
'dropTableIfExists',
'raw',
].forEach(function (method) {
SchemaBuilder.prototype[method] = function () {
if (method === 'createTableIfNotExists') {
this.client.logger.warn(
[
'Use async .hasTable to check if table exists and then use plain .createTable. Since ',
'.createTableIfNotExists actually just generates plain "CREATE TABLE IF NOT EXIST..." ',
'query it will not work correctly if there are any alter table queries generated for ',
'columns afterwards. To not break old migrations this function is left untouched for now',
', but it should not be used when writing new code and it is removed from documentation.',
].join('')
);
}
if (method === 'table') method = 'alterTable';
if (method === 'view') method = 'alterView';
this._sequence.push({
method,
args: toArray(arguments),
});
return this;
};
});
SchemaBuilder.extend = (methodName, fn) => {
if (
Object.prototype.hasOwnProperty.call(SchemaBuilder.prototype, methodName)
) {
throw new Error(
`Can't extend SchemaBuilder with existing method ('${methodName}').`
);
}
assign(SchemaBuilder.prototype, { [methodName]: fn });
};
augmentWithBuilderInterface(SchemaBuilder);
addQueryContext(SchemaBuilder);
module.exports = SchemaBuilder;

View File

@ -0,0 +1,146 @@
const extend = require('lodash/extend');
const assign = require('lodash/assign');
const toArray = require('lodash/toArray');
const { addQueryContext } = require('../util/helpers');
// The chainable interface off the original "column" method.
class ColumnBuilder {
constructor(client, tableBuilder, type, args) {
this.client = client;
this._method = 'add';
this._single = {};
this._modifiers = {};
this._statements = [];
this._type = columnAlias[type] || type;
this._args = args;
this._tableBuilder = tableBuilder;
// If we're altering the table, extend the object
// with the available "alter" methods.
if (tableBuilder._method === 'alter') {
extend(this, AlterMethods);
}
}
// Specify that the current column "references" a column,
// which may be tableName.column or just "column"
references(value) {
return this._tableBuilder.foreign
.call(this._tableBuilder, this._args[0], undefined, this)
._columnBuilder(this)
.references(value);
}
}
// All of the modifier methods that can be used to modify the current query.
const modifiers = [
'default',
'defaultsTo',
'defaultTo',
'unsigned',
'nullable',
'first',
'after',
'comment',
'collate',
'check',
'checkPositive',
'checkNegative',
'checkIn',
'checkNotIn',
'checkBetween',
'checkLength',
'checkRegex',
];
// Aliases for convenience.
const aliasMethod = {
default: 'defaultTo',
defaultsTo: 'defaultTo',
};
// If we call any of the modifiers (index or otherwise) on the chainable, we pretend
// as though we're calling `table.method(column)` directly.
modifiers.forEach(function (method) {
const key = aliasMethod[method] || method;
ColumnBuilder.prototype[method] = function () {
this._modifiers[key] = toArray(arguments);
return this;
};
});
addQueryContext(ColumnBuilder);
ColumnBuilder.prototype.notNull = ColumnBuilder.prototype.notNullable =
function notNullable() {
return this.nullable(false);
};
['index', 'primary', 'unique'].forEach(function (method) {
ColumnBuilder.prototype[method] = function () {
if (this._type.toLowerCase().indexOf('increments') === -1) {
this._tableBuilder[method].apply(
this._tableBuilder,
[this._args[0]].concat(toArray(arguments))
);
}
return this;
};
});
ColumnBuilder.extend = (methodName, fn) => {
if (
Object.prototype.hasOwnProperty.call(ColumnBuilder.prototype, methodName)
) {
throw new Error(
`Can't extend ColumnBuilder with existing method ('${methodName}').`
);
}
assign(ColumnBuilder.prototype, { [methodName]: fn });
};
const AlterMethods = {};
// Specify that the column is to be dropped. This takes precedence
// over all other rules for the column.
AlterMethods.drop = function () {
this._single.drop = true;
return this;
};
// Specify the "type" that we're looking to set the
// Knex takes no responsibility for any data-loss that may
// occur when changing data types.
AlterMethods.alterType = function (type) {
this._statements.push({
grouping: 'alterType',
value: type,
});
return this;
};
// Set column method to alter (default is add).
AlterMethods.alter = function ({
alterNullable = true,
alterType = true,
} = {}) {
this._method = 'alter';
this.alterNullable = alterNullable;
this.alterType = alterType;
return this;
};
// Alias a few methods for clarity when processing.
const columnAlias = {
float: 'floating',
enum: 'enu',
boolean: 'bool',
string: 'varchar',
bigint: 'bigInteger',
};
module.exports = ColumnBuilder;

View File

@ -0,0 +1,307 @@
// Column Compiler
// Used for designating column definitions
// during the table "create" / "alter" statements.
// -------
const helpers = require('./internal/helpers');
const groupBy = require('lodash/groupBy');
const first = require('lodash/first');
const has = require('lodash/has');
const tail = require('lodash/tail');
const { toNumber } = require('../util/helpers');
const { formatDefault } = require('../formatter/formatterUtils');
const { operator: operator_ } = require('../formatter/wrappingFormatter');
class ColumnCompiler {
constructor(client, tableCompiler, columnBuilder) {
this.client = client;
this.tableCompiler = tableCompiler;
this.columnBuilder = columnBuilder;
this._commonBuilder = this.columnBuilder;
this.args = columnBuilder._args;
this.type = columnBuilder._type.toLowerCase();
this.grouped = groupBy(columnBuilder._statements, 'grouping');
this.modified = columnBuilder._modifiers;
this.isIncrements = this.type.indexOf('increments') !== -1;
this.formatter = client.formatter(columnBuilder);
this.bindings = [];
this.formatter.bindings = this.bindings;
this.bindingsHolder = this;
this.sequence = [];
this.modifiers = [];
this.checksCount = 0;
}
_addCheckModifiers() {
this.modifiers.push(
'check',
'checkPositive',
'checkNegative',
'checkIn',
'checkNotIn',
'checkBetween',
'checkLength',
'checkRegex'
);
}
defaults(label) {
if (Object.prototype.hasOwnProperty.call(this._defaultMap, label)) {
return this._defaultMap[label].bind(this)();
} else {
throw new Error(
`There is no default for the specified identifier ${label}`
);
}
}
// To convert to sql, we first go through and build the
// column as it would be in the insert statement
toSQL() {
this.pushQuery(this.compileColumn());
if (this.sequence.additional) {
this.sequence = this.sequence.concat(this.sequence.additional);
}
return this.sequence;
}
// Compiles a column.
compileColumn() {
return (
this.formatter.wrap(this.getColumnName()) +
' ' +
this.getColumnType() +
this.getModifiers()
);
}
// Assumes the autoincrementing key is named `id` if not otherwise specified.
getColumnName() {
const value = first(this.args);
return value || this.defaults('columnName');
}
getColumnType() {
// Column type is cached so side effects (such as in pg native enums) are only run once
if (!this._columnType) {
const type = this[this.type];
this._columnType =
typeof type === 'function' ? type.apply(this, tail(this.args)) : type;
}
return this._columnType;
}
getModifiers() {
const modifiers = [];
for (let i = 0, l = this.modifiers.length; i < l; i++) {
const modifier = this.modifiers[i];
//Cannot allow 'nullable' modifiers on increments types
if (!this.isIncrements || (this.isIncrements && modifier === 'comment')) {
if (has(this.modified, modifier)) {
const val = this[modifier].apply(this, this.modified[modifier]);
if (val) modifiers.push(val);
}
}
}
return modifiers.length > 0 ? ` ${modifiers.join(' ')}` : '';
}
// Types
// ------
varchar(length) {
return `varchar(${toNumber(length, 255)})`;
}
floating(precision, scale) {
return `float(${toNumber(precision, 8)}, ${toNumber(scale, 2)})`;
}
decimal(precision, scale) {
if (precision === null) {
throw new Error(
'Specifying no precision on decimal columns is not supported for that SQL dialect.'
);
}
return `decimal(${toNumber(precision, 8)}, ${toNumber(scale, 2)})`;
}
// Used to support custom types
specifictype(type) {
return type;
}
// Modifiers
// -------
nullable(nullable) {
return nullable === false ? 'not null' : 'null';
}
notNullable() {
return this.nullable(false);
}
defaultTo(value) {
return `default ${formatDefault(value, this.type, this.client)}`;
}
increments(options = { primaryKey: true }) {
return (
'integer not null' +
(this.tableCompiler._canBeAddPrimaryKey(options) ? ' primary key' : '') +
' autoincrement'
);
}
bigincrements(options = { primaryKey: true }) {
return this.increments(options);
}
_pushAlterCheckQuery(checkPredicate, constraintName) {
let checkName = constraintName;
if (!checkName) {
this.checksCount++;
checkName =
this.tableCompiler.tableNameRaw +
'_' +
this.getColumnName() +
'_' +
this.checksCount;
}
this.pushAdditional(function () {
this.pushQuery(
`alter table ${this.tableCompiler.tableName()} add constraint ${checkName} check(${checkPredicate})`
);
});
}
_checkConstraintName(constraintName) {
return constraintName ? `constraint ${constraintName} ` : '';
}
_check(checkPredicate, constraintName) {
if (this.columnBuilder._method === 'alter') {
this._pushAlterCheckQuery(checkPredicate, constraintName);
return '';
}
return `${this._checkConstraintName(
constraintName
)}check (${checkPredicate})`;
}
checkPositive(constraintName) {
return this._check(
`${this.formatter.wrap(this.getColumnName())} ${operator_(
'>',
this.columnBuilder,
this.bindingsHolder
)} 0`,
constraintName
);
}
checkNegative(constraintName) {
return this._check(
`${this.formatter.wrap(this.getColumnName())} ${operator_(
'<',
this.columnBuilder,
this.bindingsHolder
)} 0`,
constraintName
);
}
_checkIn(values, constraintName, not) {
return this._check(
`${this.formatter.wrap(this.getColumnName())} ${
not ? 'not ' : ''
}in (${values.map((v) => this.client._escapeBinding(v)).join(',')})`,
constraintName
);
}
checkIn(values, constraintName) {
return this._checkIn(values, constraintName);
}
checkNotIn(values, constraintName) {
return this._checkIn(values, constraintName, true);
}
checkBetween(intervals, constraintName) {
if (
intervals.length === 2 &&
!Array.isArray(intervals[0]) &&
!Array.isArray(intervals[1])
) {
intervals = [intervals];
}
const intervalChecks = intervals
.map((interval) => {
return `${this.formatter.wrap(
this.getColumnName()
)} between ${this.client._escapeBinding(
interval[0]
)} and ${this.client._escapeBinding(interval[1])}`;
})
.join(' or ');
return this._check(intervalChecks, constraintName);
}
checkLength(operator, length, constraintName) {
return this._check(
`length(${this.formatter.wrap(this.getColumnName())}) ${operator_(
operator,
this.columnBuilder,
this.bindingsHolder
)} ${toNumber(length)}`,
constraintName
);
}
}
ColumnCompiler.prototype.binary = 'blob';
ColumnCompiler.prototype.bool = 'boolean';
ColumnCompiler.prototype.date = 'date';
ColumnCompiler.prototype.datetime = 'datetime';
ColumnCompiler.prototype.time = 'time';
ColumnCompiler.prototype.timestamp = 'timestamp';
ColumnCompiler.prototype.geometry = 'geometry';
ColumnCompiler.prototype.geography = 'geography';
ColumnCompiler.prototype.point = 'point';
ColumnCompiler.prototype.enu = 'varchar';
ColumnCompiler.prototype.bit = ColumnCompiler.prototype.json = 'text';
ColumnCompiler.prototype.uuid = ({
useBinaryUuid = false,
primaryKey = false,
} = {}) => (useBinaryUuid ? 'binary(16)' : 'char(36)');
ColumnCompiler.prototype.integer =
ColumnCompiler.prototype.smallint =
ColumnCompiler.prototype.mediumint =
'integer';
ColumnCompiler.prototype.biginteger = 'bigint';
ColumnCompiler.prototype.text = 'text';
ColumnCompiler.prototype.tinyint = 'tinyint';
ColumnCompiler.prototype.pushQuery = helpers.pushQuery;
ColumnCompiler.prototype.pushAdditional = helpers.pushAdditional;
ColumnCompiler.prototype.unshiftQuery = helpers.unshiftQuery;
ColumnCompiler.prototype._defaultMap = {
columnName: function () {
if (!this.isIncrements) {
throw new Error(
`You did not specify a column name for the ${this.type} column.`
);
}
return 'id';
},
};
module.exports = ColumnCompiler;

View File

@ -0,0 +1,187 @@
const {
pushQuery,
pushAdditional,
unshiftQuery,
} = require('./internal/helpers');
// The "SchemaCompiler" takes all of the query statements which have been
// gathered in the "SchemaBuilder" and turns them into an array of
// properly formatted / bound query strings.
class SchemaCompiler {
constructor(client, builder) {
this.builder = builder;
this._commonBuilder = this.builder;
this.client = client;
this.schema = builder._schema;
this.bindings = [];
this.bindingsHolder = this;
this.formatter = client.formatter(builder);
this.formatter.bindings = this.bindings;
this.sequence = [];
}
createSchema() {
throwOnlyPGError('createSchema');
}
createSchemaIfNotExists() {
throwOnlyPGError('createSchemaIfNotExists');
}
dropSchema() {
throwOnlyPGError('dropSchema');
}
dropSchemaIfExists() {
throwOnlyPGError('dropSchemaIfExists');
}
dropTable(tableName) {
this.pushQuery(
this.dropTablePrefix +
this.formatter.wrap(prefixedTableName(this.schema, tableName))
);
}
dropTableIfExists(tableName) {
this.pushQuery(
this.dropTablePrefix +
'if exists ' +
this.formatter.wrap(prefixedTableName(this.schema, tableName))
);
}
dropView(viewName) {
this._dropView(viewName, false, false);
}
dropViewIfExists(viewName) {
this._dropView(viewName, true, false);
}
dropMaterializedView(viewName) {
throw new Error('materialized views are not supported by this dialect.');
}
dropMaterializedViewIfExists(viewName) {
throw new Error('materialized views are not supported by this dialect.');
}
renameView(from, to) {
throw new Error(
'rename view is not supported by this dialect (instead drop then create another view).'
);
}
refreshMaterializedView() {
throw new Error('materialized views are not supported by this dialect.');
}
_dropView(viewName, ifExists, materialized) {
this.pushQuery(
(materialized ? this.dropMaterializedViewPrefix : this.dropViewPrefix) +
(ifExists ? 'if exists ' : '') +
this.formatter.wrap(prefixedTableName(this.schema, viewName))
);
}
raw(sql, bindings) {
this.sequence.push(this.client.raw(sql, bindings).toSQL());
}
toSQL() {
const sequence = this.builder._sequence;
for (let i = 0, l = sequence.length; i < l; i++) {
const query = sequence[i];
this[query.method].apply(this, query.args);
}
return this.sequence;
}
async generateDdlCommands() {
const generatedCommands = this.toSQL();
return {
pre: [],
sql: Array.isArray(generatedCommands)
? generatedCommands
: [generatedCommands],
check: null,
post: [],
};
}
}
SchemaCompiler.prototype.dropTablePrefix = 'drop table ';
SchemaCompiler.prototype.dropViewPrefix = 'drop view ';
SchemaCompiler.prototype.dropMaterializedViewPrefix = 'drop materialized view ';
SchemaCompiler.prototype.alterViewPrefix = 'alter view ';
SchemaCompiler.prototype.alterTable = buildTable('alter');
SchemaCompiler.prototype.createTable = buildTable('create');
SchemaCompiler.prototype.createTableIfNotExists = buildTable('createIfNot');
SchemaCompiler.prototype.createTableLike = buildTable('createLike');
SchemaCompiler.prototype.createView = buildView('create');
SchemaCompiler.prototype.createViewOrReplace = buildView('createOrReplace');
SchemaCompiler.prototype.createMaterializedView = buildView(
'createMaterializedView'
);
SchemaCompiler.prototype.alterView = buildView('alter');
SchemaCompiler.prototype.pushQuery = pushQuery;
SchemaCompiler.prototype.pushAdditional = pushAdditional;
SchemaCompiler.prototype.unshiftQuery = unshiftQuery;
function build(builder) {
// pass queryContext down to tableBuilder but do not overwrite it if already set
const queryContext = this.builder.queryContext();
if (queryContext !== undefined && builder.queryContext() === undefined) {
builder.queryContext(queryContext);
}
builder.setSchema(this.schema);
const sql = builder.toSQL();
for (let i = 0, l = sql.length; i < l; i++) {
this.sequence.push(sql[i]);
}
}
function buildTable(type) {
if (type === 'createLike') {
return function (tableName, tableNameLike, fn) {
const builder = this.client.tableBuilder(
type,
tableName,
tableNameLike,
fn
);
build.call(this, builder);
};
} else {
return function (tableName, fn) {
const builder = this.client.tableBuilder(type, tableName, null, fn);
build.call(this, builder);
};
}
}
function buildView(type) {
return function (viewName, fn) {
const builder = this.client.viewBuilder(type, viewName, fn);
build.call(this, builder);
};
}
function prefixedTableName(prefix, table) {
return prefix ? `${prefix}.${table}` : table;
}
function throwOnlyPGError(operationName) {
throw new Error(
`${operationName} is not supported for this dialect (only PostgreSQL supports it currently).`
);
}
module.exports = SchemaCompiler;

View File

@ -0,0 +1,55 @@
const tail = require('lodash/tail');
const { isString } = require('../../util/is');
// Push a new query onto the compiled "sequence" stack,
// creating a new formatter, returning the compiler.
function pushQuery(query) {
if (!query) return;
if (isString(query)) {
query = { sql: query };
}
if (!query.bindings) {
query.bindings = this.bindingsHolder.bindings;
}
this.sequence.push(query);
this.formatter = this.client.formatter(this._commonBuilder);
this.bindings = [];
this.formatter.bindings = this.bindings;
}
// Used in cases where we need to push some additional column specific statements.
function pushAdditional(fn) {
const child = new this.constructor(
this.client,
this.tableCompiler,
this.columnBuilder
);
fn.call(child, tail(arguments));
this.sequence.additional = (this.sequence.additional || []).concat(
child.sequence
);
}
// Unshift a new query onto the compiled "sequence" stack,
// creating a new formatter, returning the compiler.
function unshiftQuery(query) {
if (!query) return;
if (isString(query)) {
query = { sql: query };
}
if (!query.bindings) {
query.bindings = this.bindingsHolder.bindings;
}
this.sequence.unshift(query);
this.formatter = this.client.formatter(this._commonBuilder);
this.bindings = [];
this.formatter.bindings = this.bindings;
}
module.exports = {
pushAdditional,
pushQuery,
unshiftQuery,
};

View File

@ -0,0 +1,376 @@
// TableBuilder
// Takes the function passed to the "createTable" or "table/editTable"
// functions and calls it with the "TableBuilder" as both the context and
// the first argument. Inside this function we can specify what happens to the
// method, pushing everything we want to do onto the "allStatements" array,
// which is then compiled into sql.
// ------
const each = require('lodash/each');
const extend = require('lodash/extend');
const assign = require('lodash/assign');
const toArray = require('lodash/toArray');
const helpers = require('../util/helpers');
const { isString, isFunction, isObject } = require('../util/is');
class TableBuilder {
constructor(client, method, tableName, tableNameLike, fn) {
this.client = client;
this._fn = fn;
this._method = method;
this._schemaName = undefined;
this._tableName = tableName;
this._tableNameLike = tableNameLike;
this._statements = [];
this._single = {};
if (!tableNameLike && !isFunction(this._fn)) {
throw new TypeError(
'A callback function must be supplied to calls against `.createTable` ' +
'and `.table`'
);
}
}
setSchema(schemaName) {
this._schemaName = schemaName;
}
// Convert the current tableBuilder object "toSQL"
// giving us additional methods if we're altering
// rather than creating the table.
toSQL() {
if (this._method === 'alter') {
extend(this, AlterMethods);
}
// With 'create table ... like' callback function is useless.
if (this._fn) {
this._fn.call(this, this);
}
return this.client.tableCompiler(this).toSQL();
}
// The "timestamps" call is really just sets the `created_at` and `updated_at` columns.
timestamps(useTimestamps, defaultToNow, useCamelCase) {
if (isObject(useTimestamps)) {
({ useTimestamps, defaultToNow, useCamelCase } = useTimestamps);
}
const method = useTimestamps === true ? 'timestamp' : 'datetime';
const createdAt = this[method](useCamelCase ? 'createdAt' : 'created_at');
const updatedAt = this[method](useCamelCase ? 'updatedAt' : 'updated_at');
if (defaultToNow === true) {
const now = this.client.raw('CURRENT_TIMESTAMP');
createdAt.notNullable().defaultTo(now);
updatedAt.notNullable().defaultTo(now);
}
}
// Set the comment value for a table, they're only allowed to be called
// once per table.
comment(value) {
if (typeof value !== 'string') {
throw new TypeError('Table comment must be string');
}
this._single.comment = value;
}
// Set a foreign key on the table, calling
// `table.foreign('column_name').references('column').on('table').onDelete()...
// Also called from the ColumnBuilder context when chaining.
foreign(column, keyName) {
const foreignData = { column: column, keyName: keyName };
this._statements.push({
grouping: 'alterTable',
method: 'foreign',
args: [foreignData],
});
let returnObj = {
references(tableColumn) {
let pieces;
if (isString(tableColumn)) {
pieces = tableColumn.split('.');
}
if (!pieces || pieces.length === 1) {
foreignData.references = pieces ? pieces[0] : tableColumn;
return {
on(tableName) {
if (typeof tableName !== 'string') {
throw new TypeError(
`Expected tableName to be a string, got: ${typeof tableName}`
);
}
foreignData.inTable = tableName;
return returnObj;
},
inTable() {
return this.on.apply(this, arguments);
},
};
}
foreignData.inTable = pieces[0];
foreignData.references = pieces[1];
return returnObj;
},
withKeyName(keyName) {
foreignData.keyName = keyName;
return returnObj;
},
onUpdate(statement) {
foreignData.onUpdate = statement;
return returnObj;
},
onDelete(statement) {
foreignData.onDelete = statement;
return returnObj;
},
deferrable: (type) => {
const unSupported = [
'mysql',
'mssql',
'redshift',
'mysql2',
'oracledb',
];
if (unSupported.indexOf(this.client.dialect) !== -1) {
throw new Error(`${this.client.dialect} does not support deferrable`);
}
foreignData.deferrable = type;
return returnObj;
},
_columnBuilder(builder) {
extend(builder, returnObj);
returnObj = builder;
return builder;
},
};
return returnObj;
}
check(checkPredicate, bindings, constraintName) {
this._statements.push({
grouping: 'checks',
args: [checkPredicate, bindings, constraintName],
});
return this;
}
}
[
// Each of the index methods can be called individually, with the
// column name to be used, e.g. table.unique('column').
'index',
'primary',
'unique',
// Key specific
'dropPrimary',
'dropUnique',
'dropIndex',
'dropForeign',
].forEach((method) => {
TableBuilder.prototype[method] = function () {
this._statements.push({
grouping: 'alterTable',
method,
args: toArray(arguments),
});
return this;
};
});
// Warn for dialect-specific table methods, since that's the
// only time these are supported.
const specialMethods = {
mysql: ['engine', 'charset', 'collate'],
postgresql: ['inherits'],
};
each(specialMethods, function (methods, dialect) {
methods.forEach(function (method) {
TableBuilder.prototype[method] = function (value) {
if (this.client.dialect !== dialect) {
throw new Error(
`Knex only supports ${method} statement with ${dialect}.`
);
}
if (this._method === 'alter') {
throw new Error(
`Knex does not support altering the ${method} outside of create ` +
`table, please use knex.raw statement.`
);
}
this._single[method] = value;
};
});
});
helpers.addQueryContext(TableBuilder);
// Each of the column types that we can add, we create a new ColumnBuilder
// instance and push it onto the statements array.
const columnTypes = [
// Numeric
'tinyint',
'smallint',
'mediumint',
'int',
'bigint',
'decimal',
'float',
'double',
'real',
'bit',
'boolean',
'serial',
// Date / Time
'date',
'datetime',
'timestamp',
'time',
'year',
// Geometry
'geometry',
'geography',
'point',
// String
'char',
'varchar',
'tinytext',
'tinyText',
'text',
'mediumtext',
'mediumText',
'longtext',
'longText',
'binary',
'varbinary',
'tinyblob',
'tinyBlob',
'mediumblob',
'mediumBlob',
'blob',
'longblob',
'longBlob',
'enum',
'set',
// Increments, Aliases, and Additional
'bool',
'dateTime',
'increments',
'bigincrements',
'bigIncrements',
'integer',
'biginteger',
'bigInteger',
'string',
'json',
'jsonb',
'uuid',
'enu',
'specificType',
];
// For each of the column methods, create a new "ColumnBuilder" interface,
// push it onto the "allStatements" stack, and then return the interface,
// with which we can add indexes, etc.
columnTypes.forEach((type) => {
TableBuilder.prototype[type] = function () {
const args = toArray(arguments);
const builder = this.client.columnBuilder(this, type, args);
this._statements.push({
grouping: 'columns',
builder,
});
return builder;
};
});
const AlterMethods = {
// Renames the current column `from` the current
// TODO: this.column(from).rename(to)
renameColumn(from, to) {
this._statements.push({
grouping: 'alterTable',
method: 'renameColumn',
args: [from, to],
});
return this;
},
dropTimestamps() {
// arguments[0] = useCamelCase
return this.dropColumns(
arguments[0] === true
? ['createdAt', 'updatedAt']
: ['created_at', 'updated_at']
);
},
setNullable(column) {
this._statements.push({
grouping: 'alterTable',
method: 'setNullable',
args: [column],
});
return this;
},
check(checkPredicate, bindings, constraintName) {
this._statements.push({
grouping: 'alterTable',
method: 'check',
args: [checkPredicate, bindings, constraintName],
});
},
dropChecks() {
this._statements.push({
grouping: 'alterTable',
method: 'dropChecks',
args: toArray(arguments),
});
},
dropNullable(column) {
this._statements.push({
grouping: 'alterTable',
method: 'dropNullable',
args: [column],
});
return this;
},
// TODO: changeType
};
// Drop a column from the current table.
// TODO: Enable this.column(columnName).drop();
AlterMethods.dropColumn = AlterMethods.dropColumns = function () {
this._statements.push({
grouping: 'alterTable',
method: 'dropColumn',
args: toArray(arguments),
});
return this;
};
TableBuilder.extend = (methodName, fn) => {
if (
Object.prototype.hasOwnProperty.call(TableBuilder.prototype, methodName)
) {
throw new Error(
`Can't extend TableBuilder with existing method ('${methodName}').`
);
}
assign(TableBuilder.prototype, { [methodName]: fn });
};
module.exports = TableBuilder;

View File

@ -0,0 +1,439 @@
/* eslint max-len:0 */
// Table Compiler
// -------
const {
pushAdditional,
pushQuery,
unshiftQuery,
} = require('./internal/helpers');
const helpers = require('../util/helpers');
const groupBy = require('lodash/groupBy');
const indexOf = require('lodash/indexOf');
const isEmpty = require('lodash/isEmpty');
const tail = require('lodash/tail');
const { normalizeArr } = require('../util/helpers');
class TableCompiler {
constructor(client, tableBuilder) {
this.client = client;
this.tableBuilder = tableBuilder;
this._commonBuilder = this.tableBuilder;
this.method = tableBuilder._method;
this.schemaNameRaw = tableBuilder._schemaName;
this.tableNameRaw = tableBuilder._tableName;
this.tableNameLikeRaw = tableBuilder._tableNameLike;
this.single = tableBuilder._single;
this.grouped = groupBy(tableBuilder._statements, 'grouping');
this.formatter = client.formatter(tableBuilder);
this.bindings = [];
this.formatter.bindings = this.bindings;
this.bindingsHolder = this;
this.sequence = [];
this._formatting = client.config && client.config.formatting;
this.checksCount = 0;
}
// Convert the tableCompiler toSQL
toSQL() {
this[this.method]();
return this.sequence;
}
// Column Compilation
// -------
// If this is a table "creation", we need to first run through all
// of the columns to build them into a single string,
// and then run through anything else and push it to the query sequence.
create(ifNot, like) {
const columnBuilders = this.getColumns();
const columns = columnBuilders.map((col) => col.toSQL());
const columnTypes = this.getColumnTypes(columns);
if (this.createAlterTableMethods) {
this.alterTableForCreate(columnTypes);
}
this.createQuery(columnTypes, ifNot, like);
this.columnQueries(columns);
delete this.single.comment;
this.alterTable();
}
// Only create the table if it doesn't exist.
createIfNot() {
this.create(true);
}
createLike() {
this.create(false, true);
}
createLikeIfNot() {
this.create(true, true);
}
// If we're altering the table, we need to one-by-one
// go through and handle each of the queries associated
// with altering the table's schema.
alter() {
const addColBuilders = this.getColumns();
const addColumns = addColBuilders.map((col) => col.toSQL());
const alterColBuilders = this.getColumns('alter');
const alterColumns = alterColBuilders.map((col) => col.toSQL());
const addColumnTypes = this.getColumnTypes(addColumns);
const alterColumnTypes = this.getColumnTypes(alterColumns);
this.addColumns(addColumnTypes);
this.alterColumns(alterColumnTypes, alterColBuilders);
this.columnQueries(addColumns);
this.columnQueries(alterColumns);
this.alterTable();
}
foreign(foreignData) {
if (foreignData.inTable && foreignData.references) {
const keyName = foreignData.keyName
? this.formatter.wrap(foreignData.keyName)
: this._indexCommand('foreign', this.tableNameRaw, foreignData.column);
const column = this.formatter.columnize(foreignData.column);
const references = this.formatter.columnize(foreignData.references);
const inTable = this.formatter.wrap(foreignData.inTable);
const onUpdate = foreignData.onUpdate
? (this.lowerCase ? ' on update ' : ' ON UPDATE ') +
foreignData.onUpdate
: '';
const onDelete = foreignData.onDelete
? (this.lowerCase ? ' on delete ' : ' ON DELETE ') +
foreignData.onDelete
: '';
const deferrable = foreignData.deferrable
? this.lowerCase
? ` deferrable initially ${foreignData.deferrable.toLowerCase()} `
: ` DEFERRABLE INITIALLY ${foreignData.deferrable.toUpperCase()} `
: '';
if (this.lowerCase) {
this.pushQuery(
(!this.forCreate ? `alter table ${this.tableName()} add ` : '') +
'constraint ' +
keyName +
' ' +
'foreign key (' +
column +
') references ' +
inTable +
' (' +
references +
')' +
onUpdate +
onDelete +
deferrable
);
} else {
this.pushQuery(
(!this.forCreate ? `ALTER TABLE ${this.tableName()} ADD ` : '') +
'CONSTRAINT ' +
keyName +
' ' +
'FOREIGN KEY (' +
column +
') REFERENCES ' +
inTable +
' (' +
references +
')' +
onUpdate +
onDelete +
deferrable
);
}
}
}
// Get all of the column sql & bindings individually for building the table queries.
getColumnTypes(columns) {
return columns.reduce(
function (memo, columnSQL) {
const column = columnSQL[0];
memo.sql.push(column.sql);
memo.bindings.concat(column.bindings);
return memo;
},
{ sql: [], bindings: [] }
);
}
// Adds all of the additional queries from the "column"
columnQueries(columns) {
const queries = columns.reduce(function (memo, columnSQL) {
const column = tail(columnSQL);
if (!isEmpty(column)) return memo.concat(column);
return memo;
}, []);
for (const q of queries) {
this.pushQuery(q);
}
}
// All of the columns to "add" for the query
addColumns(columns, prefix) {
prefix = prefix || this.addColumnsPrefix;
if (columns.sql.length > 0) {
const columnSql = columns.sql.map((column) => {
return prefix + column;
});
this.pushQuery({
sql:
(this.lowerCase ? 'alter table ' : 'ALTER TABLE ') +
this.tableName() +
' ' +
columnSql.join(', '),
bindings: columns.bindings,
});
}
}
alterColumns(columns, colBuilders) {
if (columns.sql.length > 0) {
this.addColumns(columns, this.alterColumnsPrefix, colBuilders);
}
}
// Compile the columns as needed for the current create or alter table
getColumns(method) {
const columns = this.grouped.columns || [];
method = method || 'add';
const queryContext = this.tableBuilder.queryContext();
return columns
.filter((column) => column.builder._method === method)
.map((column) => {
// pass queryContext down to columnBuilder but do not overwrite it if already set
if (
queryContext !== undefined &&
column.builder.queryContext() === undefined
) {
column.builder.queryContext(queryContext);
}
return this.client.columnCompiler(this, column.builder);
});
}
tableName() {
const name = this.schemaNameRaw
? `${this.schemaNameRaw}.${this.tableNameRaw}`
: this.tableNameRaw;
return this.formatter.wrap(name);
}
tableNameLike() {
const name = this.schemaNameRaw
? `${this.schemaNameRaw}.${this.tableNameLikeRaw}`
: this.tableNameLikeRaw;
return this.formatter.wrap(name);
}
// Generate all of the alter column statements necessary for the query.
alterTable() {
const alterTable = this.grouped.alterTable || [];
for (let i = 0, l = alterTable.length; i < l; i++) {
const statement = alterTable[i];
if (this[statement.method]) {
this[statement.method].apply(this, statement.args);
} else {
this.client.logger.error(`Debug: ${statement.method} does not exist`);
}
}
for (const item in this.single) {
if (typeof this[item] === 'function') this[item](this.single[item]);
}
}
alterTableForCreate(columnTypes) {
this.forCreate = true;
const savedSequence = this.sequence;
const alterTable = this.grouped.alterTable || [];
this.grouped.alterTable = [];
for (let i = 0, l = alterTable.length; i < l; i++) {
const statement = alterTable[i];
if (indexOf(this.createAlterTableMethods, statement.method) < 0) {
this.grouped.alterTable.push(statement);
continue;
}
if (this[statement.method]) {
this.sequence = [];
this[statement.method].apply(this, statement.args);
columnTypes.sql.push(this.sequence[0].sql);
} else {
this.client.logger.error(`Debug: ${statement.method} does not exist`);
}
}
this.sequence = savedSequence;
this.forCreate = false;
}
// Drop the index on the current table.
dropIndex(value) {
this.pushQuery(`drop index${value}`);
}
dropUnique() {
throw new Error('Method implemented in the dialect driver');
}
dropForeign() {
throw new Error('Method implemented in the dialect driver');
}
dropColumn() {
const columns = helpers.normalizeArr.apply(null, arguments);
const drops = (Array.isArray(columns) ? columns : [columns]).map(
(column) => {
return this.dropColumnPrefix + this.formatter.wrap(column);
}
);
this.pushQuery(
(this.lowerCase ? 'alter table ' : 'ALTER TABLE ') +
this.tableName() +
' ' +
drops.join(', ')
);
}
//Default implementation of setNullable. Overwrite on dialect-specific tablecompiler when needed
//(See postgres/mssql for reference)
_setNullableState(column, nullable) {
const tableName = this.tableName();
const columnName = this.formatter.columnize(column);
const alterColumnPrefix = this.alterColumnsPrefix;
return this.pushQuery({
sql: 'SELECT 1',
output: () => {
return this.client
.queryBuilder()
.from(this.tableNameRaw)
.columnInfo(column)
.then((columnInfo) => {
if (isEmpty(columnInfo)) {
throw new Error(
`.setNullable: Column ${columnName} does not exist in table ${tableName}.`
);
}
const nullableType = nullable ? 'null' : 'not null';
const columnType =
columnInfo.type +
(columnInfo.maxLength ? `(${columnInfo.maxLength})` : '');
const defaultValue =
columnInfo.defaultValue !== null &&
columnInfo.defaultValue !== void 0
? `default '${columnInfo.defaultValue}'`
: '';
const sql = `alter table ${tableName} ${alterColumnPrefix} ${columnName} ${columnType} ${nullableType} ${defaultValue}`;
return this.client.raw(sql);
});
},
});
}
setNullable(column) {
return this._setNullableState(column, true);
}
dropNullable(column) {
return this._setNullableState(column, false);
}
dropChecks(checkConstraintNames) {
if (checkConstraintNames === undefined) return '';
checkConstraintNames = normalizeArr(checkConstraintNames);
const tableName = this.tableName();
const sql = `alter table ${tableName} ${checkConstraintNames
.map((constraint) => `drop constraint ${constraint}`)
.join(', ')}`;
this.pushQuery(sql);
}
check(checkPredicate, bindings, constraintName) {
const tableName = this.tableName();
let checkConstraint = constraintName;
if (!checkConstraint) {
this.checksCount++;
checkConstraint = tableName + '_' + this.checksCount;
}
const sql = `alter table ${tableName} add constraint ${checkConstraint} check(${checkPredicate})`;
this.pushQuery(sql);
}
_addChecks() {
if (this.grouped.checks) {
return (
', ' +
this.grouped.checks
.map((c) => {
return `${
c.args[2] ? 'constraint ' + c.args[2] + ' ' : ''
}check (${this.client.raw(c.args[0], c.args[1])})`;
})
.join(', ')
);
}
return '';
}
// If no name was specified for this index, we will create one using a basic
// convention of the table name, followed by the columns, followed by an
// index type, such as primary or index, which makes the index unique.
_indexCommand(type, tableName, columns) {
if (!Array.isArray(columns)) columns = columns ? [columns] : [];
const table = tableName.replace(/\.|-/g, '_');
const indexName = (
table +
'_' +
columns.join('_') +
'_' +
type
).toLowerCase();
return this.formatter.wrap(indexName);
}
_getPrimaryKeys() {
return (this.grouped.alterTable || [])
.filter((a) => a.method === 'primary')
.flatMap((a) => a.args)
.flat();
}
_canBeAddPrimaryKey(options) {
return options.primaryKey && this._getPrimaryKeys().length === 0;
}
_getIncrementsColumnNames() {
return this.grouped.columns
.filter((c) => c.builder._type === 'increments')
.map((c) => c.builder._args[0]);
}
_getBigIncrementsColumnNames() {
return this.grouped.columns
.filter((c) => c.builder._type === 'bigincrements')
.map((c) => c.builder._args[0]);
}
}
TableCompiler.prototype.pushQuery = pushQuery;
TableCompiler.prototype.pushAdditional = pushAdditional;
TableCompiler.prototype.unshiftQuery = unshiftQuery;
TableCompiler.prototype.lowerCase = true;
TableCompiler.prototype.createAlterTableMethods = null;
TableCompiler.prototype.addColumnsPrefix = 'add column ';
TableCompiler.prototype.alterColumnsPrefix = 'alter column ';
TableCompiler.prototype.modifyColumnPrefix = 'modify column ';
TableCompiler.prototype.dropColumnPrefix = 'drop column ';
module.exports = TableCompiler;

View File

@ -0,0 +1,92 @@
const helpers = require('../util/helpers');
const extend = require('lodash/extend');
const assign = require('lodash/assign');
class ViewBuilder {
constructor(client, method, viewName, fn) {
this.client = client;
this._method = method;
this._schemaName = undefined;
this._columns = undefined;
this._fn = fn;
this._viewName = viewName;
this._statements = [];
this._single = {};
}
setSchema(schemaName) {
this._schemaName = schemaName;
}
columns(columns) {
this._columns = columns;
}
as(selectQuery) {
this._selectQuery = selectQuery;
}
checkOption() {
throw new Error(
'check option definition is not supported by this dialect.'
);
}
localCheckOption() {
throw new Error(
'check option definition is not supported by this dialect.'
);
}
cascadedCheckOption() {
throw new Error(
'check option definition is not supported by this dialect.'
);
}
toSQL() {
if (this._method === 'alter') {
extend(this, AlterMethods);
}
this._fn.call(this, this);
return this.client.viewCompiler(this).toSQL();
}
}
const AlterMethods = {
column(column) {
const self = this;
return {
rename: function (newName) {
self._statements.push({
grouping: 'alterView',
method: 'renameColumn',
args: [column, newName],
});
return this;
},
defaultTo: function (defaultValue) {
self._statements.push({
grouping: 'alterView',
method: 'defaultTo',
args: [column, defaultValue],
});
return this;
},
};
},
};
helpers.addQueryContext(ViewBuilder);
ViewBuilder.extend = (methodName, fn) => {
if (Object.prototype.hasOwnProperty.call(ViewBuilder.prototype, methodName)) {
throw new Error(
`Can't extend ViewBuilder with existing method ('${methodName}').`
);
}
assign(ViewBuilder.prototype, { [methodName]: fn });
};
module.exports = ViewBuilder;

View File

@ -0,0 +1,138 @@
/* eslint max-len:0 */
// View Compiler
// -------
const { pushQuery } = require('./internal/helpers');
const groupBy = require('lodash/groupBy');
const { columnize: columnize_ } = require('../formatter/wrappingFormatter');
class ViewCompiler {
constructor(client, viewBuilder) {
this.client = client;
this.viewBuilder = viewBuilder;
this._commonBuilder = this.viewBuilder;
this.method = viewBuilder._method;
this.schemaNameRaw = viewBuilder._schemaName;
this.viewNameRaw = viewBuilder._viewName;
this.single = viewBuilder._single;
this.selectQuery = viewBuilder._selectQuery;
this.columns = viewBuilder._columns;
this.grouped = groupBy(viewBuilder._statements, 'grouping');
this.formatter = client.formatter(viewBuilder);
this.bindings = [];
this.formatter.bindings = this.bindings;
this.bindingsHolder = this;
this.sequence = [];
}
// Convert the tableCompiler toSQL
toSQL() {
this[this.method]();
return this.sequence;
}
// Column Compilation
// -------
create() {
this.createQuery(this.columns, this.selectQuery);
}
createOrReplace() {
throw new Error('replace views is not supported by this dialect.');
}
createMaterializedView() {
throw new Error('materialized views are not supported by this dialect.');
}
createQuery(columns, selectQuery, materialized, replace) {
const createStatement =
'create ' +
(materialized ? 'materialized ' : '') +
(replace ? 'or replace ' : '') +
'view ';
const columnList = columns
? ' (' +
columnize_(
columns,
this.viewBuilder,
this.client,
this.bindingsHolder
) +
')'
: '';
let sql = createStatement + this.viewName() + columnList;
sql += ' as ';
sql += selectQuery.toString();
switch (this.single.checkOption) {
case 'default_option':
sql += ' with check option';
break;
case 'local':
sql += ' with local check option';
break;
case 'cascaded':
sql += ' with cascaded check option';
break;
default:
break;
}
this.pushQuery({
sql,
});
}
renameView(from, to) {
throw new Error(
'rename view is not supported by this dialect (instead drop, then create another view).'
);
}
refreshMaterializedView() {
throw new Error('materialized views are not supported by this dialect.');
}
alter() {
this.alterView();
}
alterView() {
const alterView = this.grouped.alterView || [];
for (let i = 0, l = alterView.length; i < l; i++) {
const statement = alterView[i];
if (this[statement.method]) {
this[statement.method].apply(this, statement.args);
} else {
this.client.logger.error(`Debug: ${statement.method} does not exist`);
}
}
for (const item in this.single) {
if (typeof this[item] === 'function') this[item](this.single[item]);
}
}
renameColumn(from, to) {
throw new Error('rename column of views is not supported by this dialect.');
}
defaultTo(column, defaultValue) {
throw new Error(
'change default values of views is not supported by this dialect.'
);
}
viewName() {
const name = this.schemaNameRaw
? `${this.schemaNameRaw}.${this.viewNameRaw}`
: this.viewNameRaw;
return this.formatter.wrap(name);
}
}
ViewCompiler.prototype.pushQuery = pushQuery;
module.exports = ViewCompiler;