UNPKG

sequelize-ibmi

Version:

Multi dialect ORM for Node.JS

914 lines (764 loc) 30.3 kB
'use strict'; const Utils = require('../../utils'); const util = require('util'); //const Transaction = require('../../transaction'); const _ = require('lodash'); const AbstractQueryGenerator = require('../abstract/query-generator'); const DataTypes = require('../../data-types'); const Model = require('../../model'); const SqlString = require('../../sql-string'); const Op = require('../../operators'); const typeWithoutDefault = new Set(['BLOB']); class IBMiQueryGenerator extends AbstractQueryGenerator { // Version queries versionQuery() { return "SELECT CONCAT(OS_VERSION, CONCAT('.', OS_RELEASE)) AS VERSION FROM SYSIBMADM.ENV_SYS_INFO"; } // Schema queries createSchema(schema) { return `CREATE SCHEMA "${schema}"`; } dropSchema(schema) { // return `DROP SCHEMA "${schema}"`; return `BEGIN IF EXISTS (SELECT * FROM SYSIBM.SQLSCHEMAS WHERE TABLE_SCHEM = '${schema}') THEN SET TRANSACTION ISOLATION LEVEL NO COMMIT; DROP SCHEMA "${schema}"; COMMIT; END IF; END`; } showSchemasQuery(options) { let skippedSchemas = ''; if (options.skip) { for (let i = 0; i < options.skip.length; i++) { skippedSchemas += ` AND SCHEMA_NAME != '${options.skip[i]}'`; } } return `SELECT DISTINCT SCHEMA_NAME AS "schema_name" FROM QSYS2.SYSSCHEMAAUTH WHERE GRANTEE = CURRENT USER${skippedSchemas}`; } // Table queries createTableQuery(tableName, attributes, options) { const primaryKeys = []; const foreignKeys = {}; const attrStr = []; for (const attr in attributes) { if (!Object.prototype.hasOwnProperty.call(attributes, attr)) continue; const dataType = attributes[attr]; if (dataType.includes('PRIMARY KEY')) { primaryKeys.push(attr); attrStr.push(`${this.quoteIdentifier(attr)} ${dataType.replace('PRIMARY KEY', '')}`); } else { attrStr.push(`${this.quoteIdentifier(attr)} ${dataType}`); } } let attributesClause = attrStr.join(', '); const pkString = primaryKeys.map(pk => this.quoteIdentifier(pk)).join(', '); if (options.uniqueKeys) { _.each(options.uniqueKeys, (columns, indexName) => { // if primary keys === unique keys, then skip adding new constraint const uniqueIsPrimary = columns.fields.length === primaryKeys.length && columns.fields.sort().every((value, index) => { return value === primaryKeys.sort()[index]; }); if (uniqueIsPrimary) { return true; } if (columns.customIndex) { if (typeof indexName !== 'string') { indexName = `uniq_${tableName}_${columns.fields.join('_')}`; } attributesClause += `, CONSTRAINT ${this.quoteIdentifier(indexName)} UNIQUE (${columns.fields.map(field => this.quoteIdentifier(field)).join(', ')})`; } }); } if (pkString.length > 0) { attributesClause += `, PRIMARY KEY (${pkString})`; } for (const fkey in foreignKeys) { if (Object.prototype.hasOwnProperty.call(foreignKeys, fkey)) { attributesClause += `, FOREIGN KEY (${this.quoteIdentifier(fkey)}) ${foreignKeys[fkey]}`; } } let tableObject = {}; if (typeof tableName === 'string') { tableObject.table = tableName; } else { tableObject = tableName; } return `BEGIN DECLARE CONTINUE HANDLER FOR SQLSTATE VALUE '42710' BEGIN END; CREATE TABLE ${tableName.schema ? `"${tableObject.schema}".` : '' }"${tableObject.table ? tableObject.table : tableObject.tableName}" (${attributesClause}); END`; // return `BEGIN IF NOT EXISTS (SELECT * FROM SYSIBM.SQLTABLES WHERE TABLE_SCHEM = '${schema}' AND TABLE_NAME = '${table}') THEN CREATE TABLE "${schema}"."${table}" (${attributesClause}); COMMIT; END IF; END`; // return `BEGIN IF NOT EXISTS (SELECT * FROM SYSIBM.SQLTABLES WHERE TABLE_SCHEM = '${tableName.schema}' AND TABLE_NAME = '${tableName.table}') THEN CREATE TABLE ${this.quoteTable(tableName)} (${attributesClause}); COMMIT; END IF; END`; } dropTableQuery(tableName, options) { let table = tableName; let schema = undefined; if (typeof table === 'object') { schema = table.schema || undefined; table = table.table; } else if (options.schema) { schema = options.schema; } return `DROP TABLE IF EXISTS ${schema ? `"${schema}".` : ''}"${table}"`; } describeTableQuery(tableName, schema) { const sql = `SELECT QSYS2.SYSCOLUMNS.*, QSYS2.SYSCST.CONSTRAINT_NAME, QSYS2.SYSCST.CONSTRAINT_TYPE FROM QSYS2.SYSCOLUMNS LEFT OUTER JOIN QSYS2.SYSCSTCOL ON QSYS2.SYSCOLUMNS.TABLE_SCHEMA = QSYS2.SYSCSTCOL.TABLE_SCHEMA AND QSYS2.SYSCOLUMNS.TABLE_NAME = QSYS2.SYSCSTCOL.TABLE_NAME AND QSYS2.SYSCOLUMNS.COLUMN_NAME = QSYS2.SYSCSTCOL.COLUMN_NAME LEFT JOIN QSYS2.SYSCST ON QSYS2.SYSCSTCOL.CONSTRAINT_NAME = QSYS2.SYSCST.CONSTRAINT_NAME WHERE QSYS2.SYSCOLUMNS.TABLE_SCHEMA = ${schema ? `'${schema}'` : 'CURRENT SCHEMA'} AND QSYS2.SYSCOLUMNS.TABLE_NAME = '${tableName}'`; // const sql = // `select // * // froms // SYSIBM.COLUMNS // where // TABLE_SCHEMA = ${schema ? `${schema}` : 'CURRENT SCHEMA'} // and // TABLE_NAME = '${tableName}'`; return sql; } showTablesQuery(schema) { return `SELECT TABLE_NAME FROM SYSIBM.SQLTABLES WHERE TABLE_TYPE = 'TABLE'${schema ? ` AND TABLE_SCHEM = '${schema}'` : ''}`; } addColumnQuery(table, key, dataType) { dataType.field = key; const definition = this.attributeToSQL(dataType, { context: 'addColumn', tableName: table, foreignKey: key }); return `ALTER TABLE ${this.quoteTable(table)} ADD ${this.quoteIdentifier(key)} ${definition}`; } removeColumnQuery(tableName, attributeName) { return `ALTER TABLE ${this.quoteTable(tableName)} DROP COLUMN ${this.quoteIdentifier(attributeName)}`; } changeColumnQuery(tableName, attributes) { const attrString = []; const constraintString = []; for (const attributeName in attributes) { let definition = attributes[attributeName]; if (definition.includes('REFERENCES')) { const attrName = this.quoteIdentifier(attributeName); definition = definition.replace(/.+?(?=REFERENCES)/, ''); const foreignKey = this.quoteIdentifier(`${attributeName}`); constraintString.push(`${foreignKey} FOREIGN KEY (${attrName}) ${definition}`); } else { attrString.push(`"${attributeName}" SET DATA TYPE ${definition}`); } } let finalQuery = ''; if (attrString.length) { finalQuery += `ALTER COLUMN ${attrString.join(', ')}`; finalQuery += constraintString.length ? ' ' : ''; } if (constraintString.length) { finalQuery += `ADD CONSTRAINT ${constraintString.join(', ')}`; } return `ALTER TABLE ${this.quoteTable(tableName)} ${finalQuery};`; } renameTableQuery(before, after) { return `RENAME TABLE ${this.quoteTable(before)} TO ${this.quoteTable(after)};`; } renameColumnQuery(tableName, attrBefore, attributes) { const attrString = []; for (const attrName in attributes) { const definition = attributes[attrName]; attrString.push(`\`${attrBefore}\` \`${attrName}\` ${definition}`); } return `ALTER TABLE ${this.quoteTable(tableName)} RENAME COLUMN ${attrString.join(', ')};`; } handleSequelizeMethod(smth, tableName, factory, options, prepend) { if (smth instanceof Utils.Json) { // Parse nested object if (smth.conditions) { const conditions = this.parseConditionObject(smth.conditions).map(condition => `${this.quoteIdentifier(condition.path[0])}->>'$.${_.tail(condition.path).join('.')}' = '${condition.value}'` ); return conditions.join(' and '); } if (smth.path) { let str; // Allow specifying conditions using the sqlite json functions if (this._checkValidJsonStatement(smth.path)) { str = smth.path; } else { // Also support json dot notation let path = smth.path; let startWithDot = true; // Convert .number. to [number]. path = path.replace(/\.(\d+)\./g, '[$1].'); // Convert .number$ to [number] path = path.replace(/\.(\d+)$/, '[$1]'); path = path.split('.'); let columnName = path.shift(); const match = columnName.match(/\[\d+\]$/); // If columnName ends with [\d+] if (match !== null) { path.unshift(columnName.substr(match.index)); columnName = columnName.substr(0, match.index); startWithDot = false; } str = `${this.quoteIdentifier(columnName)}->>'$${startWithDot ? '.' : ''}${path.join('.')}'`; } if (smth.value) { str += util.format(' = %s', this.escape(smth.value)); } return str; } } else if (smth instanceof Utils.Cast) { if (/timestamp/i.test(smth.type)) { smth.type = 'timestamp'; } else if (smth.json && /boolean/i.test(smth.type)) { // true or false cannot be casted as booleans within a JSON structure smth.type = 'char'; } else if (/double precision/i.test(smth.type) || /boolean/i.test(smth.type) || /integer/i.test(smth.type)) { smth.type = 'integer'; } else if (/text/i.test(smth.type)) { smth.type = 'char'; } } return super.handleSequelizeMethod(smth, tableName, factory, options, prepend); } _whereParseSingleValueObject(key, field, prop, value, options) { if (prop === Op.not) { if (Array.isArray(value)) { prop = Op.notIn; } else if (value !== null && value !== true && value !== false) { prop = Op.ne; } } let comparator = this.OperatorMap[prop] || this.OperatorMap[Op.eq]; switch (prop) { case Op.in: case Op.notIn: if (value instanceof Utils.Literal) { return this._joinKeyValue(key, value.val, comparator, options.prefix); } if (value.length) { return this._joinKeyValue(key, `(${value.map(item => this.escape(item, field, { where: true })).join(', ')})`, comparator, options.prefix); } if (comparator === this.OperatorMap[Op.in]) { return this._joinKeyValue(key, '(NULL)', comparator, options.prefix); } return ''; case Op.any: case Op.all: comparator = `${this.OperatorMap[Op.eq]} ${comparator}`; if (value[Op.values]) { return this._joinKeyValue(key, `(VALUES ${value[Op.values].map(item => `(${this.escape(item)})`).join(', ')})`, comparator, options.prefix); } return this._joinKeyValue(key, `(${this.escape(value, field)})`, comparator, options.prefix); case Op.between: case Op.notBetween: return this._joinKeyValue(key, `${this.escape(value[0], field)} AND ${this.escape(value[1], field)}`, comparator, options.prefix); case Op.raw: throw new Error('The `$raw` where property is no longer supported. Use `sequelize.literal` instead.'); case Op.col: comparator = this.OperatorMap[Op.eq]; value = value.split('.'); if (value.length > 2) { value = [ // join the tables by -> to match out internal namings value.slice(0, -1).join('->'), value[value.length - 1] ]; } return this._joinKeyValue(key, value.map(identifier => this.quoteIdentifier(identifier)).join('.'), comparator, options.prefix); case Op.startsWith: case Op.endsWith: case Op.substring: comparator = this.OperatorMap[Op.like]; if (value instanceof Utils.Literal) { value = value.val; } let pattern = `${value}%`; if (prop === Op.endsWith) pattern = `%${value}`; if (prop === Op.substring) pattern = `%${value}%`; return this._joinKeyValue(key, this.escape(pattern), comparator, options.prefix); } const escapeOptions = { acceptStrings: comparator.includes(this.OperatorMap[Op.like]) }; if (_.isPlainObject(value)) { if (value[Op.col]) { return this._joinKeyValue(key, this.whereItemQuery(null, value), comparator, options.prefix); } if (value[Op.any]) { escapeOptions.isList = true; return this._joinKeyValue(key, `(${this.escape(value[Op.any], field, escapeOptions)})`, `${comparator} ${this.OperatorMap[Op.any]}`, options.prefix); } if (value[Op.all]) { escapeOptions.isList = true; return this._joinKeyValue(key, `(${this.escape(value[Op.all], field, escapeOptions)})`, `${comparator} ${this.OperatorMap[Op.all]}`, options.prefix); } } if (value === null && comparator === this.OperatorMap[Op.eq]) { return this._joinKeyValue(key, this.escape(value, field, escapeOptions), this.OperatorMap[Op.is], options.prefix); } if (value === null && comparator === this.OperatorMap[Op.ne]) { return this._joinKeyValue(key, this.escape(value, field, escapeOptions), this.OperatorMap[Op.not], options.prefix); } return this._joinKeyValue(key, this.escape(value, field, escapeOptions), comparator, options.prefix); } escape(value, field, options) { options = options || {}; if (value !== null && value !== undefined) { if (value instanceof Utils.SequelizeMethod) { return this.handleSequelizeMethod(value); } if (field && field.type) { this.validate(value, field, options); if (field.type.stringify) { // Users shouldn't have to worry about these args - just give them a function that takes a single arg if (field.type._binary) { field.type.escape = false; } const simpleEscape = escVal => SqlString.escape(escVal, this.options.timezone, this.dialect); value = field.type.stringify(value, { escape: simpleEscape, field, timezone: this.options.timezone, operation: options.operation }); if (field.type.escape === false) { // The data-type already did the required escaping return value; } } } } const format = value === null && options.where === true ? true : false; // const format = false; return SqlString.escape(value, this.options.timezone, this.dialect, format); } /* Returns an add index query. Parameters: - tableName -> Name of an existing table, possibly with schema. - options: - type: UNIQUE|FULLTEXT|SPATIAL - name: The name of the index. Default is <table>_<attr1>_<attr2> - fields: An array of attributes as string or as hash. If the attribute is a hash, it must have the following content: - name: The name of the attribute/column - length: An integer. Optional - order: 'ASC' or 'DESC'. Optional - parser - using - operator - concurrently: Pass CONCURRENT so other operations run while the index is created - rawTablename, the name of the table, without schema. Used to create the name of the index @private */ addIndexQuery(tableName, attributes, options, rawTablename) { options = options || {}; if (!Array.isArray(attributes)) { options = attributes; attributes = undefined; } else { options.fields = attributes; } options.prefix = options.prefix || rawTablename || tableName; if (options.prefix && typeof options.prefix === 'string') { options.prefix = options.prefix.replace(/\./g, '_'); options.prefix = options.prefix.replace(/("|')/g, ''); } const fieldsSql = options.fields.map(field => { if (typeof field === 'string') { return this.quoteIdentifier(field); } if (field instanceof Utils.SequelizeMethod) { return this.handleSequelizeMethod(field); } let result = ''; if (field.attribute) { field.name = field.attribute; } if (!field.name) { throw new Error(`The following index field has no name: ${util.inspect(field)}`); } result += this.quoteIdentifier(field.name); if (this._dialect.supports.index.length && field.length) { result += `(${field.length})`; } return result; }); if (!options.name) { // Mostly for cases where addIndex is called directly by the user without an options object (for example in migrations) // All calls that go through sequelize should already have a name options = Utils.nameIndex(options, options.prefix); } options = Model._conformIndex(options); if (!this._dialect.supports.index.type) { delete options.type; } if (options.where) { options.where = this.whereQuery(options.where); } if (typeof tableName === 'string') { tableName = this.quoteIdentifiers(tableName); } else { tableName = this.quoteTable(tableName); } // Although the function is 'addIndex', and the values are passed through // the 'indexes' key of a table, Db2 for i doesn't allow REFERENCES to // work against a UNIQUE INDEX, only a UNIQUE constraint. if (options.unique) { // return `ALTER TABLE ${tableName} ADD CONSTRAINT ${this.quoteIdentifiers(options.name)} UNIQUE (${fieldsSql.join(', ')}${options.operator ? ` ${options.operator}` : ''})`; return `BEGIN DECLARE CONTINUE HANDLER FOR SQLSTATE VALUE '42891' BEGIN END; ALTER TABLE ${tableName} ADD CONSTRAINT ${this.quoteIdentifiers(options.name)} UNIQUE (${fieldsSql.join(', ')}${options.operator ? ` ${options.operator}` : ''}); END`; } return `CREATE ${options.unique ? 'UNIQUE' : ''} INDEX ${this.quoteIdentifiers(options.name)} ON ${tableName} (${fieldsSql.join(', ')}${options.operator ? ` ${options.operator}` : ''})`; } _toJSONValue(value) { // true/false are stored as strings in mysql if (typeof value === 'boolean') { return value.toString(); } // null is stored as a string in mysql if (value === null) { return 'null'; } return value; } upsertQuery(tableName, insertValues, updateValues, where, model, options) { const aliasTable = `temp_${this.quoteTable(tableName)}`; let query = `MERGE INTO ${this.quoteTable(tableName)} `; const usingClause = `USING ( SELECT * FROM (${this.quoteTable(tableName)} VALUES(42) ) AS ${aliasTable}("id") ON (${aliasTable}."id" = ${this.quoteTable(tableName)}."id")`; query += usingClause; query += ` WHEN MATCHED THEN ${this.updateQuery(tableName, tableName, where, options, updateValues)} WHEN NOT MATCHED THEN ${this.insertQuery(tableName, insertValues, model, options).sql}`; // where = this.getWhereConditions(where, null, model, options); // if (where) { // query += `ON ${where}`; // } return query; } insertQuery(table, valueHash, modelAttributes, options) { // remove the final semi-colon const query = super.insertQuery(table, valueHash, modelAttributes, options); if (query.query[query.query.length - 1] === ';') { query.query = query.query.slice(0, -1); query.query = `SELECT * FROM FINAL TABLE (${query.query})`; } return query; } selectQuery(tableName, options, model) { // remove the final semi-colon let query = super.selectQuery(tableName, options, model); if (query[query.length - 1] === ';') { query = query.slice(0, -1); } return query; } bulkInsertQuery(tableName, fieldValueHashes, options, fieldMappedAttributes) { // remove the final semi-colon let query = super.bulkInsertQuery(tableName, fieldValueHashes, options, fieldMappedAttributes); if (query[query.length - 1] === ';') { query = query.slice(0, -1); query = `SELECT * FROM FINAL TABLE (${query})`; } return query; } truncateTableQuery(tableName) { return `TRUNCATE TABLE ${this.quoteTable(tableName)} IMMEDIATE`; } deleteQuery(tableName, where, options = {}, model) { let limit = ''; if (options.offset || options.limit) { limit = this.addLimitAndOffset(options); } let query = `DELETE FROM ${this.quoteTable(tableName)}`; where = this.getWhereConditions(where, null, model, options); if (where) { query += ` WHERE ${where}`; } return query + limit; } /** * Returns an SQL fragment for adding result constraints. * * @param {object} options An object with selectQuery options. * @returns {string} The generated sql query. * @private */ addLimitAndOffset(options) { let fragment = ''; const offset = options.offset; const limit = options.limit; if (offset) { if (typeof offset === 'number' && Number.isInteger(offset)) { fragment += ` OFFSET ${offset} ROWS`; } else { console.warn('"offset" must be an integer. Offset is not added'); } } if (limit) { if (typeof limit === 'number' && Number.isInteger(limit)) { fragment += ` FETCH FIRST ${limit} ROWS ONLY`; } else { console.warn('"limit" must be an integer. Limit is not added'); } } return fragment; } // Indexes and constraints showIndexesQuery(tableName) { let table; let schema; if (typeof tableName === 'string') { table = tableName; } else { table = tableName.tableName || tableName.table; schema = tableName.schema; } const sql = `select QSYS2.SYSCSTCOL.CONSTRAINT_NAME as NAME, QSYS2.SYSCSTCOL.COLUMN_NAME, QSYS2.SYSCST.CONSTRAINT_TYPE, QSYS2.SYSCST.TABLE_SCHEMA, QSYS2.SYSCST.TABLE_NAME from QSYS2.SYSCSTCOL left outer join QSYS2.SYSCST on QSYS2.SYSCSTCOL.TABLE_SCHEMA = QSYS2.SYSCST.TABLE_SCHEMA and QSYS2.SYSCSTCOL.TABLE_NAME = QSYS2.SYSCST.TABLE_NAME and QSYS2.SYSCSTCOL.CONSTRAINT_NAME = QSYS2.SYSCST.CONSTRAINT_NAME where QSYS2.SYSCSTCOL.TABLE_SCHEMA = ${schema ? `'${schema}'` : 'CURRENT SCHEMA'} and QSYS2.SYSCSTCOL.TABLE_NAME = '${table}' union select QSYS2.SYSKEYS.INDEX_NAME AS NAME, QSYS2.SYSKEYS.COLUMN_NAME, CAST('INDEX' AS VARCHAR(11)), QSYS2.SYSINDEXES.TABLE_SCHEMA, QSYS2.SYSINDEXES.TABLE_NAME from QSYS2.SYSKEYS left outer join QSYS2.SYSINDEXES on QSYS2.SYSKEYS.INDEX_NAME = QSYS2.SYSINDEXES.INDEX_NAME where QSYS2.SYSINDEXES.TABLE_SCHEMA = ${schema ? `'${schema}'` : 'CURRENT SCHEMA'} and QSYS2.SYSINDEXES.TABLE_NAME = '${table}'`; return sql; } showConstraintsQuery(table, constraintName) { const tableName = table.tableName || table; const schemaName = table.schema; let sql = [ 'SELECT CONSTRAINT_NAME AS "constraintName",', 'CONSTRAINT_SCHEMA AS "constraintSchema",', 'CONSTRAINT_TYPE AS "constraintType",', 'TABLE_NAME AS "tableName",', 'TABLE_SCHEMA AS "tableSchema"', 'from QSYS2.SYSCST', `WHERE table_name='${tableName}'` ].join(' '); if (constraintName) { sql += ` AND CONSTRAINT_NAME = '${constraintName}'`; } if (schemaName) { sql += ` AND TABLE_SCHEMA = '${schemaName}'`; } return sql; } removeIndexQuery(tableName, indexNameOrAttributes) { let indexName = indexNameOrAttributes; if (typeof indexName !== 'string') { indexName = Utils.underscore(`${tableName}_${indexNameOrAttributes.join('_')}`); } return `BEGIN IF EXISTS (SELECT * FROM QSYS2.SYSINDEXES WHERE INDEX_NAME = '${indexName}') THEN DROP INDEX "${indexName}"; COMMIT; END IF; END`; } bindParam(bind) { return value => { bind.push(value); return '?'; }; } attributeToSQL(attribute, options) { if (!_.isPlainObject(attribute)) { attribute = { type: attribute }; } const attributeString = attribute.type.toString({ escape: this.escape.bind(this) }); let template = attributeString; if (attribute.type instanceof DataTypes.ENUM) { if (attribute.type.values && !attribute.values) attribute.values = attribute.type.values; // enums are a special case template = attribute.type.toSql(); template += ` ${options.context === 'changeColumn' ? 'ADD' : ''} CHECK (${this.quoteIdentifier(attribute.field)} IN(${attribute.values.map(value => { return this.escape(value); }).join(', ') }))`; } else { template = attribute.type.toString(options); } if (attribute.allowNull === false) { template += ' NOT NULL'; } else if (attribute.allowNull === true && (options && options.context === 'changeColumn')) { template += ' DROP NOT NULL'; } if (attribute.autoIncrement) { template += ' GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1)'; } // BLOB cannot have a default value if (!typeWithoutDefault.has(attributeString) && attribute.type._binary !== true && Utils.defaultValueSchemable(attribute.defaultValue)) { if (attribute.defaultValue === true) { attribute.defaultValue = 1; } else if (attribute.defaultValue === false) { attribute.defaultValue = 0; } template += ` DEFAULT ${this.escape(attribute.defaultValue)}`; } if (attribute.unique === true && !attribute.primaryKey) { template += ' UNIQUE'; } if (attribute.primaryKey) { template += ' PRIMARY KEY'; } // Db2 for i comments are a mess // if (attribute.comment) { // template += ` ${options.context === 'changeColumn' ? 'ADD ' : ''}COMMENT ${this.escape(attribute.comment)}`; // } if (attribute.first) { template += ' FIRST'; } if (attribute.after) { template += ` AFTER ${this.quoteIdentifier(attribute.after)}`; } if (attribute.references) { if (options && options.context === 'addColumn' && options.foreignKey) { const attrName = this.quoteIdentifier(options.foreignKey); const fkName = this.quoteIdentifier(`${options.tableName}_${attrName}_foreign_idx`); template += ` ADD CONSTRAINT ${fkName} FOREIGN KEY (${attrName})`; } template += ` REFERENCES ${this.quoteTable(attribute.references.model)}`; if (attribute.references.key) { template += ` (${this.quoteIdentifier(attribute.references.key)})`; } else { template += ` (${this.quoteIdentifier('id')})`; } if (attribute.onDelete) { template += ` ON DELETE ${attribute.onDelete.toUpperCase()}`; } if (attribute.onUpdate && attribute.onUpdate.toUpperCase() !== 'CASCADE') { template += ` ON UPDATE ${attribute.onUpdate.toUpperCase()}`; } } return template; } attributesToSQL(attributes, options) { const result = {}; for (const key in attributes) { const attribute = attributes[key]; attribute.field = attribute.field || key; result[attribute.field || key] = this.attributeToSQL(attribute, options); } return result; } /** * Generates an SQL query that returns all foreign keys of a table. * * @param {object} table The table. * @param {string} schemaName The name of the schema. * @returns {string} The generated sql query. * @private */ getForeignKeysQuery(table, schemaName) { const quotedSchemaName = schemaName ? wrapSingleQuote(schemaName) : ''; const quotedTableName = wrapSingleQuote(table.tableName || table); const sql = [ 'SELECT FK_NAME AS "constraintName",', 'PKTABLE_CAT AS "referencedTableCatalog",', 'PKTABLE_SCHEM AS "referencedTableSchema",', 'PKTABLE_NAME AS "referencedTableName",', 'PKCOLUMN_NAME AS "referencedColumnName",', 'FKTABLE_CAT AS "tableCatalog",', 'FKTABLE_SCHEM AS "tableSchema",', 'FKTABLE_NAME AS "tableName",', 'FKTABLE_SCHEM AS "tableSchema",', 'FKCOLUMN_NAME AS "columnName"', 'FROM SYSIBM.SQLFOREIGNKEYS', `WHERE FKTABLE_SCHEM = ${quotedSchemaName}`, `AND FKTABLE_NAME = ${quotedTableName}` ].join(' '); return sql; } /** * Generates an SQL query that returns the foreign key constraint of a given column. * * @param {object} table The table. * @param {string} columnName The name of the column. * @returns {string} The generated sql query. * @private */ getForeignKeyQuery(table, columnName) { const quotedSchemaName = table.schema ? wrapSingleQuote(table.schema) : ''; const quotedTableName = wrapSingleQuote(table.tableName || table); const quotedColumnName = wrapSingleQuote(columnName); const sql = [ 'SELECT FK_NAME AS "constraintName",', 'PKTABLE_CAT AS "referencedTableCatalog",', 'PKTABLE_SCHEM AS "referencedTableSchema",', 'PKTABLE_NAME AS "referencedTableName",', 'PKCOLUMN_NAME AS "referencedColumnName",', 'FKTABLE_CAT AS "tableCatalog",', 'FKTABLE_SCHEM AS "tableSchema",', 'FKTABLE_NAME AS "tableName",', 'FKTABLE_SCHEM AS "tableSchema",', 'FKCOLUMN_NAME AS "columnName"', 'FROM SYSIBM.SQLFOREIGNKEYS', `WHERE FKTABLE_SCHEM = ${quotedSchemaName}`, `AND FKTABLE_NAME = ${quotedTableName}`, `AND FKCOLUMN_NAME = ${quotedColumnName}` ].join(' '); return sql; } /** * Generates an SQL query that removes a foreign key from a table. * * @param {string} tableName The name of the table. * @param {string} foreignKey The name of the foreign key constraint. * @returns {string} The generated sql query. * @private */ dropForeignKeyQuery(tableName, foreignKey) { return `ALTER TABLE ${this.quoteTable(tableName)} DROP FOREIGN KEY ${this.quoteIdentifier(foreignKey)};`; } booleanValue(value) { if (value) { return 1; } return 0; } } // private methods function wrapSingleQuote(identifier) { return Utils.addTicks(identifier, '\''); } exports.QueryGenerator = IBMiQueryGenerator;