123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905 |
- 'use strict';
- const Utils = require('../../utils');
- const util = require('util');
- const DataTypes = require('../../data-types');
- const AbstractQueryGenerator = require('../abstract/query-generator');
- const semver = require('semver');
- const _ = require('lodash');
- class PostgresQueryGenerator extends AbstractQueryGenerator {
- setSearchPath(searchPath) {
- return `SET search_path to ${searchPath};`;
- }
- createDatabaseQuery(databaseName, options) {
- options = {
- encoding: null,
- collate: null,
- ...options
- };
- const values = {
- database: this.quoteTable(databaseName),
- encoding: options.encoding ? ` ENCODING = ${this.escape(options.encoding)}` : '',
- collation: options.collate ? ` LC_COLLATE = ${this.escape(options.collate)}` : '',
- ctype: options.ctype ? ` LC_CTYPE = ${this.escape(options.ctype)}` : '',
- template: options.template ? ` TEMPLATE = ${this.escape(options.template)}` : ''
- };
- return `CREATE DATABASE ${values.database}${values.encoding}${values.collation}${values.ctype}${values.template};`;
- }
- dropDatabaseQuery(databaseName) {
- return `DROP DATABASE IF EXISTS ${this.quoteTable(databaseName)};`;
- }
- createSchema(schema) {
- const databaseVersion = _.get(this, 'sequelize.options.databaseVersion', 0);
- if (databaseVersion && semver.gte(databaseVersion, '9.2.0')) {
- return `CREATE SCHEMA IF NOT EXISTS ${schema};`;
- }
- return `CREATE SCHEMA ${schema};`;
- }
- dropSchema(schema) {
- return `DROP SCHEMA IF EXISTS ${schema} CASCADE;`;
- }
- showSchemasQuery() {
- return "SELECT schema_name FROM information_schema.schemata WHERE schema_name <> 'information_schema' AND schema_name != 'public' AND schema_name !~ E'^pg_';";
- }
- versionQuery() {
- return 'SHOW SERVER_VERSION';
- }
- createTableQuery(tableName, attributes, options) {
- options = { ...options };
- //Postgres 9.0 does not support CREATE TABLE IF NOT EXISTS, 9.1 and above do
- const databaseVersion = _.get(this, 'sequelize.options.databaseVersion', 0);
- const attrStr = [];
- let comments = '';
- let columnComments = '';
- const quotedTable = this.quoteTable(tableName);
- if (options.comment && typeof options.comment === 'string') {
- comments += `; COMMENT ON TABLE ${quotedTable} IS ${this.escape(options.comment)}`;
- }
- for (const attr in attributes) {
- const quotedAttr = this.quoteIdentifier(attr);
- const i = attributes[attr].indexOf('COMMENT ');
- if (i !== -1) {
- // Move comment to a separate query
- const escapedCommentText = this.escape(attributes[attr].substring(i + 8));
- columnComments += `; COMMENT ON COLUMN ${quotedTable}.${quotedAttr} IS ${escapedCommentText}`;
- attributes[attr] = attributes[attr].substring(0, i);
- }
- const dataType = this.dataTypeMapping(tableName, attr, attributes[attr]);
- attrStr.push(`${quotedAttr} ${dataType}`);
- }
- let attributesClause = attrStr.join(', ');
- if (options.uniqueKeys) {
- _.each(options.uniqueKeys, columns => {
- if (columns.customIndex) {
- attributesClause += `, UNIQUE (${columns.fields.map(field => this.quoteIdentifier(field)).join(', ')})`;
- }
- });
- }
- const pks = _.reduce(attributes, (acc, attribute, key) => {
- if (attribute.includes('PRIMARY KEY')) {
- acc.push(this.quoteIdentifier(key));
- }
- return acc;
- }, []).join(',');
- if (pks.length > 0) {
- attributesClause += `, PRIMARY KEY (${pks})`;
- }
- return `CREATE TABLE ${databaseVersion === 0 || semver.gte(databaseVersion, '9.1.0') ? 'IF NOT EXISTS ' : ''}${quotedTable} (${attributesClause})${comments}${columnComments};`;
- }
- dropTableQuery(tableName, options) {
- options = options || {};
- return `DROP TABLE IF EXISTS ${this.quoteTable(tableName)}${options.cascade ? ' CASCADE' : ''};`;
- }
- showTablesQuery() {
- return "SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' AND table_type LIKE '%TABLE' AND table_name != 'spatial_ref_sys';";
- }
- describeTableQuery(tableName, schema) {
- if (!schema) schema = 'public';
- return 'SELECT ' +
- 'pk.constraint_type as "Constraint",' +
- 'c.column_name as "Field", ' +
- 'c.column_default as "Default",' +
- 'c.is_nullable as "Null", ' +
- '(CASE WHEN c.udt_name = \'hstore\' THEN c.udt_name ELSE c.data_type END) || (CASE WHEN c.character_maximum_length IS NOT NULL THEN \'(\' || c.character_maximum_length || \')\' ELSE \'\' END) as "Type", ' +
- '(SELECT array_agg(e.enumlabel) FROM pg_catalog.pg_type t JOIN pg_catalog.pg_enum e ON t.oid=e.enumtypid WHERE t.typname=c.udt_name) AS "special", ' +
- '(SELECT pgd.description FROM pg_catalog.pg_statio_all_tables AS st INNER JOIN pg_catalog.pg_description pgd on (pgd.objoid=st.relid) WHERE c.ordinal_position=pgd.objsubid AND c.table_name=st.relname) AS "Comment" ' +
- 'FROM information_schema.columns c ' +
- 'LEFT JOIN (SELECT tc.table_schema, tc.table_name, ' +
- 'cu.column_name, tc.constraint_type ' +
- 'FROM information_schema.TABLE_CONSTRAINTS tc ' +
- 'JOIN information_schema.KEY_COLUMN_USAGE cu ' +
- 'ON tc.table_schema=cu.table_schema and tc.table_name=cu.table_name ' +
- 'and tc.constraint_name=cu.constraint_name ' +
- 'and tc.constraint_type=\'PRIMARY KEY\') pk ' +
- 'ON pk.table_schema=c.table_schema ' +
- 'AND pk.table_name=c.table_name ' +
- 'AND pk.column_name=c.column_name ' +
- `WHERE c.table_name = ${this.escape(tableName)} AND c.table_schema = ${this.escape(schema)} `;
- }
- /**
- * Check whether the statmement is json function or simple path
- *
- * @param {string} stmt The statement to validate
- * @returns {boolean} true if the given statement is json function
- * @throws {Error} throw if the statement looks like json function but has invalid token
- */
- _checkValidJsonStatement(stmt) {
- if (typeof stmt !== 'string') {
- return false;
- }
- // https://www.postgresql.org/docs/current/static/functions-json.html
- const jsonFunctionRegex = /^\s*((?:[a-z]+_){0,2}jsonb?(?:_[a-z]+){0,2})\([^)]*\)/i;
- const jsonOperatorRegex = /^\s*(->>?|#>>?|@>|<@|\?[|&]?|\|{2}|#-)/i;
- const tokenCaptureRegex = /^\s*((?:([`"'])(?:(?!\2).|\2{2})*\2)|[\w\d\s]+|[().,;+-])/i;
- let currentIndex = 0;
- let openingBrackets = 0;
- let closingBrackets = 0;
- let hasJsonFunction = false;
- let hasInvalidToken = false;
- while (currentIndex < stmt.length) {
- const string = stmt.substr(currentIndex);
- const functionMatches = jsonFunctionRegex.exec(string);
- if (functionMatches) {
- currentIndex += functionMatches[0].indexOf('(');
- hasJsonFunction = true;
- continue;
- }
- const operatorMatches = jsonOperatorRegex.exec(string);
- if (operatorMatches) {
- currentIndex += operatorMatches[0].length;
- hasJsonFunction = true;
- continue;
- }
- const tokenMatches = tokenCaptureRegex.exec(string);
- if (tokenMatches) {
- const capturedToken = tokenMatches[1];
- if (capturedToken === '(') {
- openingBrackets++;
- } else if (capturedToken === ')') {
- closingBrackets++;
- } else if (capturedToken === ';') {
- hasInvalidToken = true;
- break;
- }
- currentIndex += tokenMatches[0].length;
- continue;
- }
- break;
- }
- // Check invalid json statement
- hasInvalidToken |= openingBrackets !== closingBrackets;
- if (hasJsonFunction && hasInvalidToken) {
- throw new Error(`Invalid json statement: ${stmt}`);
- }
- // return true if the statement has valid json function
- return hasJsonFunction;
- }
- 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.jsonPathExtractionQuery(condition.path[0], _.tail(condition.path))} = '${condition.value}'`
- );
- return conditions.join(' AND ');
- }
- if (smth.path) {
- let str;
- // Allow specifying conditions using the postgres json syntax
- if (this._checkValidJsonStatement(smth.path)) {
- str = smth.path;
- } else {
- // Also support json property accessors
- const paths = _.toPath(smth.path);
- const column = paths.shift();
- str = this.jsonPathExtractionQuery(column, paths);
- }
- if (smth.value) {
- str += util.format(' = %s', this.escape(smth.value));
- }
- return str;
- }
- }
- return super.handleSequelizeMethod.call(this, smth, tableName, factory, options, prepend);
- }
- addColumnQuery(table, key, attribute) {
- const dbDataType = this.attributeToSQL(attribute, { context: 'addColumn', table, key });
- const dataType = attribute.type || attribute;
- const definition = this.dataTypeMapping(table, key, dbDataType);
- const quotedKey = this.quoteIdentifier(key);
- const quotedTable = this.quoteTable(this.extractTableDetails(table));
- let query = `ALTER TABLE ${quotedTable} ADD COLUMN ${quotedKey} ${definition};`;
- if (dataType instanceof DataTypes.ENUM) {
- query = this.pgEnum(table, key, dataType) + query;
- } else if (dataType.type && dataType.type instanceof DataTypes.ENUM) {
- query = this.pgEnum(table, key, dataType.type) + query;
- }
- return query;
- }
- removeColumnQuery(tableName, attributeName) {
- const quotedTableName = this.quoteTable(this.extractTableDetails(tableName));
- const quotedAttributeName = this.quoteIdentifier(attributeName);
- return `ALTER TABLE ${quotedTableName} DROP COLUMN ${quotedAttributeName};`;
- }
- changeColumnQuery(tableName, attributes) {
- const query = subQuery => `ALTER TABLE ${this.quoteTable(tableName)} ALTER COLUMN ${subQuery};`;
- const sql = [];
- for (const attributeName in attributes) {
- let definition = this.dataTypeMapping(tableName, attributeName, attributes[attributeName]);
- let attrSql = '';
- if (definition.includes('NOT NULL')) {
- attrSql += query(`${this.quoteIdentifier(attributeName)} SET NOT NULL`);
- definition = definition.replace('NOT NULL', '').trim();
- } else if (!definition.includes('REFERENCES')) {
- attrSql += query(`${this.quoteIdentifier(attributeName)} DROP NOT NULL`);
- }
- if (definition.includes('DEFAULT')) {
- attrSql += query(`${this.quoteIdentifier(attributeName)} SET DEFAULT ${definition.match(/DEFAULT ([^;]+)/)[1]}`);
- definition = definition.replace(/(DEFAULT[^;]+)/, '').trim();
- } else if (!definition.includes('REFERENCES')) {
- attrSql += query(`${this.quoteIdentifier(attributeName)} DROP DEFAULT`);
- }
- if (attributes[attributeName].startsWith('ENUM(')) {
- attrSql += this.pgEnum(tableName, attributeName, attributes[attributeName]);
- definition = definition.replace(/^ENUM\(.+\)/, this.pgEnumName(tableName, attributeName, { schema: false }));
- definition += ` USING (${this.quoteIdentifier(attributeName)}::${this.pgEnumName(tableName, attributeName)})`;
- }
- if (definition.match(/UNIQUE;*$/)) {
- definition = definition.replace(/UNIQUE;*$/, '');
- attrSql += query(`ADD UNIQUE (${this.quoteIdentifier(attributeName)})`).replace('ALTER COLUMN', '');
- }
- if (definition.includes('REFERENCES')) {
- definition = definition.replace(/.+?(?=REFERENCES)/, '');
- attrSql += query(`ADD FOREIGN KEY (${this.quoteIdentifier(attributeName)}) ${definition}`).replace('ALTER COLUMN', '');
- } else {
- attrSql += query(`${this.quoteIdentifier(attributeName)} TYPE ${definition}`);
- }
- sql.push(attrSql);
- }
- return sql.join('');
- }
- renameColumnQuery(tableName, attrBefore, attributes) {
- const attrString = [];
- for (const attributeName in attributes) {
- attrString.push(`${this.quoteIdentifier(attrBefore)} TO ${this.quoteIdentifier(attributeName)}`);
- }
- return `ALTER TABLE ${this.quoteTable(tableName)} RENAME COLUMN ${attrString.join(', ')};`;
- }
- fn(fnName, tableName, parameters, body, returns, language) {
- fnName = fnName || 'testfunc';
- language = language || 'plpgsql';
- returns = returns ? `RETURNS ${returns}` : '';
- parameters = parameters || '';
- return `CREATE OR REPLACE FUNCTION pg_temp.${fnName}(${parameters}) ${returns} AS $func$ BEGIN ${body} END; $func$ LANGUAGE ${language}; SELECT * FROM pg_temp.${fnName}();`;
- }
- truncateTableQuery(tableName, options = {}) {
- return [
- `TRUNCATE ${this.quoteTable(tableName)}`,
- options.restartIdentity ? ' RESTART IDENTITY' : '',
- options.cascade ? ' CASCADE' : ''
- ].join('');
- }
- deleteQuery(tableName, where, options = {}, model) {
- const table = this.quoteTable(tableName);
- let whereClause = this.getWhereConditions(where, null, model, options);
- const limit = options.limit ? ` LIMIT ${this.escape(options.limit)}` : '';
- let primaryKeys = '';
- let primaryKeysSelection = '';
- if (whereClause) {
- whereClause = ` WHERE ${whereClause}`;
- }
- if (options.limit) {
- if (!model) {
- throw new Error('Cannot LIMIT delete without a model.');
- }
- const pks = Object.values(model.primaryKeys).map(pk => this.quoteIdentifier(pk.field)).join(',');
- primaryKeys = model.primaryKeyAttributes.length > 1 ? `(${pks})` : pks;
- primaryKeysSelection = pks;
- return `DELETE FROM ${table} WHERE ${primaryKeys} IN (SELECT ${primaryKeysSelection} FROM ${table}${whereClause}${limit})`;
- }
- return `DELETE FROM ${table}${whereClause}`;
- }
- showIndexesQuery(tableName) {
- let schemaJoin = '';
- let schemaWhere = '';
- if (typeof tableName !== 'string') {
- schemaJoin = ', pg_namespace s';
- schemaWhere = ` AND s.oid = t.relnamespace AND s.nspname = '${tableName.schema}'`;
- tableName = tableName.tableName;
- }
- // This is ARCANE!
- return 'SELECT i.relname AS name, ix.indisprimary AS primary, ix.indisunique AS unique, ix.indkey AS indkey, ' +
- 'array_agg(a.attnum) as column_indexes, array_agg(a.attname) AS column_names, pg_get_indexdef(ix.indexrelid) ' +
- `AS definition FROM pg_class t, pg_class i, pg_index ix, pg_attribute a${schemaJoin} ` +
- 'WHERE t.oid = ix.indrelid AND i.oid = ix.indexrelid AND a.attrelid = t.oid AND ' +
- `t.relkind = 'r' and t.relname = '${tableName}'${schemaWhere} ` +
- 'GROUP BY i.relname, ix.indexrelid, ix.indisprimary, ix.indisunique, ix.indkey ORDER BY i.relname;';
- }
- showConstraintsQuery(tableName) {
- //Postgres converts camelCased alias to lowercase unless quoted
- return [
- 'SELECT constraint_catalog AS "constraintCatalog",',
- 'constraint_schema AS "constraintSchema",',
- 'constraint_name AS "constraintName",',
- 'table_catalog AS "tableCatalog",',
- 'table_schema AS "tableSchema",',
- 'table_name AS "tableName",',
- 'constraint_type AS "constraintType",',
- 'is_deferrable AS "isDeferrable",',
- 'initially_deferred AS "initiallyDeferred"',
- 'from INFORMATION_SCHEMA.table_constraints',
- `WHERE table_name='${tableName}';`
- ].join(' ');
- }
- removeIndexQuery(tableName, indexNameOrAttributes) {
- let indexName = indexNameOrAttributes;
- if (typeof indexName !== 'string') {
- indexName = Utils.underscore(`${tableName}_${indexNameOrAttributes.join('_')}`);
- }
- return `DROP INDEX IF EXISTS ${this.quoteIdentifiers(indexName)}`;
- }
- addLimitAndOffset(options) {
- let fragment = '';
- /* eslint-disable */
- if (options.limit != null) {
- fragment += ' LIMIT ' + this.escape(options.limit);
- }
- if (options.offset != null) {
- fragment += ' OFFSET ' + this.escape(options.offset);
- }
- /* eslint-enable */
- return fragment;
- }
- attributeToSQL(attribute, options) {
- if (!_.isPlainObject(attribute)) {
- attribute = {
- type: attribute
- };
- }
- let type;
- if (
- attribute.type instanceof DataTypes.ENUM ||
- attribute.type instanceof DataTypes.ARRAY && attribute.type.type instanceof DataTypes.ENUM
- ) {
- const enumType = attribute.type.type || attribute.type;
- let values = attribute.values;
- if (enumType.values && !attribute.values) {
- values = enumType.values;
- }
- if (Array.isArray(values) && values.length > 0) {
- type = `ENUM(${values.map(value => this.escape(value)).join(', ')})`;
- if (attribute.type instanceof DataTypes.ARRAY) {
- type += '[]';
- }
- } else {
- throw new Error("Values for ENUM haven't been defined.");
- }
- }
- if (!type) {
- type = attribute.type;
- }
- let sql = type.toString();
- if (Object.prototype.hasOwnProperty.call(attribute, 'allowNull') && !attribute.allowNull) {
- sql += ' NOT NULL';
- }
- if (attribute.autoIncrement) {
- if (attribute.autoIncrementIdentity) {
- sql += ' GENERATED BY DEFAULT AS IDENTITY';
- } else {
- sql += ' SERIAL';
- }
- }
- if (Utils.defaultValueSchemable(attribute.defaultValue)) {
- sql += ` DEFAULT ${this.escape(attribute.defaultValue, attribute)}`;
- }
- if (attribute.unique === true) {
- sql += ' UNIQUE';
- }
- if (attribute.primaryKey) {
- sql += ' PRIMARY KEY';
- }
- if (attribute.references) {
- let referencesTable = this.quoteTable(attribute.references.model);
- let schema;
- if (options.schema) {
- schema = options.schema;
- } else if (
- (!attribute.references.model || typeof attribute.references.model == 'string')
- && options.table
- && options.table.schema
- ) {
- schema = options.table.schema;
- }
- if (schema) {
- referencesTable = this.quoteTable(this.addSchema({
- tableName: referencesTable,
- _schema: schema
- }));
- }
- let referencesKey;
- if (attribute.references.key) {
- referencesKey = this.quoteIdentifiers(attribute.references.key);
- } else {
- referencesKey = this.quoteIdentifier('id');
- }
- sql += ` REFERENCES ${referencesTable} (${referencesKey})`;
- if (attribute.onDelete) {
- sql += ` ON DELETE ${attribute.onDelete.toUpperCase()}`;
- }
- if (attribute.onUpdate) {
- sql += ` ON UPDATE ${attribute.onUpdate.toUpperCase()}`;
- }
- if (attribute.references.deferrable) {
- sql += ` ${attribute.references.deferrable.toString(this)}`;
- }
- }
- if (attribute.comment && typeof attribute.comment === 'string') {
- if (options && (options.context === 'addColumn' || options.context === 'changeColumn')) {
- const quotedAttr = this.quoteIdentifier(options.key);
- const escapedCommentText = this.escape(attribute.comment);
- sql += `; COMMENT ON COLUMN ${this.quoteTable(options.table)}.${quotedAttr} IS ${escapedCommentText}`;
- } else {
- // for createTable event which does it's own parsing
- // TODO: centralize creation of comment statements here
- sql += ` COMMENT ${attribute.comment}`;
- }
- }
- return sql;
- }
- deferConstraintsQuery(options) {
- return options.deferrable.toString(this);
- }
- setConstraintQuery(columns, type) {
- let columnFragment = 'ALL';
- if (columns) {
- columnFragment = columns.map(column => this.quoteIdentifier(column)).join(', ');
- }
- return `SET CONSTRAINTS ${columnFragment} ${type}`;
- }
- setDeferredQuery(columns) {
- return this.setConstraintQuery(columns, 'DEFERRED');
- }
- setImmediateQuery(columns) {
- return this.setConstraintQuery(columns, 'IMMEDIATE');
- }
- attributesToSQL(attributes, options) {
- const result = {};
- for (const key in attributes) {
- const attribute = attributes[key];
- result[attribute.field || key] = this.attributeToSQL(attribute, { key, ...options });
- }
- return result;
- }
- createTrigger(tableName, triggerName, eventType, fireOnSpec, functionName, functionParams, optionsArray) {
- const decodedEventType = this.decodeTriggerEventType(eventType);
- const eventSpec = this.expandTriggerEventSpec(fireOnSpec);
- const expandedOptions = this.expandOptions(optionsArray);
- const paramList = this._expandFunctionParamList(functionParams);
- return `CREATE ${this.triggerEventTypeIsConstraint(eventType)}TRIGGER ${this.quoteIdentifier(triggerName)} ${decodedEventType} ${
- eventSpec} ON ${this.quoteTable(tableName)}${expandedOptions ? ` ${expandedOptions}` : ''} EXECUTE PROCEDURE ${functionName}(${paramList});`;
- }
- dropTrigger(tableName, triggerName) {
- return `DROP TRIGGER ${this.quoteIdentifier(triggerName)} ON ${this.quoteTable(tableName)} RESTRICT;`;
- }
- renameTrigger(tableName, oldTriggerName, newTriggerName) {
- return `ALTER TRIGGER ${this.quoteIdentifier(oldTriggerName)} ON ${this.quoteTable(tableName)} RENAME TO ${this.quoteIdentifier(newTriggerName)};`;
- }
- createFunction(functionName, params, returnType, language, body, optionsArray, options) {
- if (!functionName || !returnType || !language || !body) throw new Error('createFunction missing some parameters. Did you pass functionName, returnType, language and body?');
- const paramList = this._expandFunctionParamList(params);
- const variableList = options && options.variables ? this._expandFunctionVariableList(options.variables) : '';
- const expandedOptionsArray = this.expandOptions(optionsArray);
- const statement = options && options.force ? 'CREATE OR REPLACE FUNCTION' : 'CREATE FUNCTION';
- return `${statement} ${functionName}(${paramList}) RETURNS ${returnType} AS $func$ ${variableList} BEGIN ${body} END; $func$ language '${language}'${expandedOptionsArray};`;
- }
- dropFunction(functionName, params) {
- if (!functionName) throw new Error('requires functionName');
- // RESTRICT is (currently, as of 9.2) default but we'll be explicit
- const paramList = this._expandFunctionParamList(params);
- return `DROP FUNCTION ${functionName}(${paramList}) RESTRICT;`;
- }
- renameFunction(oldFunctionName, params, newFunctionName) {
- const paramList = this._expandFunctionParamList(params);
- return `ALTER FUNCTION ${oldFunctionName}(${paramList}) RENAME TO ${newFunctionName};`;
- }
- pgEscapeAndQuote(val) {
- return this.quoteIdentifier(Utils.removeTicks(this.escape(val), "'"));
- }
- _expandFunctionParamList(params) {
- if (params === undefined || !Array.isArray(params)) {
- throw new Error('_expandFunctionParamList: function parameters array required, including an empty one for no arguments');
- }
- const paramList = [];
- params.forEach(curParam => {
- const paramDef = [];
- if (curParam.type) {
- if (curParam.direction) { paramDef.push(curParam.direction); }
- if (curParam.name) { paramDef.push(curParam.name); }
- paramDef.push(curParam.type);
- } else {
- throw new Error('function or trigger used with a parameter without any type');
- }
- const joined = paramDef.join(' ');
- if (joined) paramList.push(joined);
- });
- return paramList.join(', ');
- }
- _expandFunctionVariableList(variables) {
- if (!Array.isArray(variables)) {
- throw new Error('_expandFunctionVariableList: function variables must be an array');
- }
- const variableDefinitions = [];
- variables.forEach(variable => {
- if (!variable.name || !variable.type) {
- throw new Error('function variable must have a name and type');
- }
- let variableDefinition = `DECLARE ${variable.name} ${variable.type}`;
- if (variable.default) {
- variableDefinition += ` := ${variable.default}`;
- }
- variableDefinition += ';';
- variableDefinitions.push(variableDefinition);
- });
- return variableDefinitions.join(' ');
- }
- expandOptions(options) {
- return options === undefined || _.isEmpty(options) ?
- '' : options.join(' ');
- }
- decodeTriggerEventType(eventSpecifier) {
- const EVENT_DECODER = {
- 'after': 'AFTER',
- 'before': 'BEFORE',
- 'instead_of': 'INSTEAD OF',
- 'after_constraint': 'AFTER'
- };
- if (!EVENT_DECODER[eventSpecifier]) {
- throw new Error(`Invalid trigger event specified: ${eventSpecifier}`);
- }
- return EVENT_DECODER[eventSpecifier];
- }
- triggerEventTypeIsConstraint(eventSpecifier) {
- return eventSpecifier === 'after_constraint' ? 'CONSTRAINT ' : '';
- }
- expandTriggerEventSpec(fireOnSpec) {
- if (_.isEmpty(fireOnSpec)) {
- throw new Error('no table change events specified to trigger on');
- }
- return _.map(fireOnSpec, (fireValue, fireKey) => {
- const EVENT_MAP = {
- 'insert': 'INSERT',
- 'update': 'UPDATE',
- 'delete': 'DELETE',
- 'truncate': 'TRUNCATE'
- };
- if (!EVENT_MAP[fireValue]) {
- throw new Error(`parseTriggerEventSpec: undefined trigger event ${fireKey}`);
- }
- let eventSpec = EVENT_MAP[fireValue];
- if (eventSpec === 'UPDATE') {
- if (Array.isArray(fireValue) && fireValue.length > 0) {
- eventSpec += ` OF ${fireValue.join(', ')}`;
- }
- }
- return eventSpec;
- }).join(' OR ');
- }
- pgEnumName(tableName, attr, options) {
- options = options || {};
- const tableDetails = this.extractTableDetails(tableName, options);
- let enumName = Utils.addTicks(Utils.generateEnumName(tableDetails.tableName, attr), '"');
- // pgListEnums requires the enum name only, without the schema
- if (options.schema !== false && tableDetails.schema) {
- enumName = this.quoteIdentifier(tableDetails.schema) + tableDetails.delimiter + enumName;
- }
- return enumName;
- }
- pgListEnums(tableName, attrName, options) {
- let enumName = '';
- const tableDetails = this.extractTableDetails(tableName, options);
- if (tableDetails.tableName && attrName) {
- enumName = ` AND t.typname=${this.pgEnumName(tableDetails.tableName, attrName, { schema: false }).replace(/"/g, "'")}`;
- }
- return 'SELECT t.typname enum_name, array_agg(e.enumlabel ORDER BY enumsortorder) enum_value FROM pg_type t ' +
- 'JOIN pg_enum e ON t.oid = e.enumtypid ' +
- 'JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace ' +
- `WHERE n.nspname = '${tableDetails.schema}'${enumName} GROUP BY 1`;
- }
- pgEnum(tableName, attr, dataType, options) {
- const enumName = this.pgEnumName(tableName, attr, options);
- let values;
- if (dataType.values) {
- values = `ENUM(${dataType.values.map(value => this.escape(value)).join(', ')})`;
- } else {
- values = dataType.toString().match(/^ENUM\(.+\)/)[0];
- }
- let sql = `CREATE TYPE ${enumName} AS ${values};`;
- if (!!options && options.force === true) {
- sql = this.pgEnumDrop(tableName, attr) + sql;
- }
- return sql;
- }
- pgEnumAdd(tableName, attr, value, options) {
- const enumName = this.pgEnumName(tableName, attr);
- let sql = `ALTER TYPE ${enumName} ADD VALUE `;
- if (semver.gte(this.sequelize.options.databaseVersion, '9.3.0')) {
- sql += 'IF NOT EXISTS ';
- }
- sql += this.escape(value);
- if (options.before) {
- sql += ` BEFORE ${this.escape(options.before)}`;
- } else if (options.after) {
- sql += ` AFTER ${this.escape(options.after)}`;
- }
- return sql;
- }
- pgEnumDrop(tableName, attr, enumName) {
- enumName = enumName || this.pgEnumName(tableName, attr);
- return `DROP TYPE IF EXISTS ${enumName}; `;
- }
- fromArray(text) {
- text = text.replace(/^{/, '').replace(/}$/, '');
- let matches = text.match(/("(?:\\.|[^"\\\\])*"|[^,]*)(?:\s*,\s*|\s*$)/ig);
- if (matches.length < 1) {
- return [];
- }
- matches = matches.map(m => m.replace(/",$/, '').replace(/,$/, '').replace(/(^"|"$)/g, ''));
- return matches.slice(0, -1);
- }
- dataTypeMapping(tableName, attr, dataType) {
- if (dataType.includes('PRIMARY KEY')) {
- dataType = dataType.replace('PRIMARY KEY', '');
- }
- if (dataType.includes('SERIAL')) {
- if (dataType.includes('BIGINT')) {
- dataType = dataType.replace('SERIAL', 'BIGSERIAL');
- dataType = dataType.replace('BIGINT', '');
- } else if (dataType.includes('SMALLINT')) {
- dataType = dataType.replace('SERIAL', 'SMALLSERIAL');
- dataType = dataType.replace('SMALLINT', '');
- } else {
- dataType = dataType.replace('INTEGER', '');
- }
- dataType = dataType.replace('NOT NULL', '');
- }
- if (dataType.startsWith('ENUM(')) {
- dataType = dataType.replace(/^ENUM\(.+\)/, this.pgEnumName(tableName, attr));
- }
- return dataType;
- }
- /**
- * Generates an SQL query that returns all foreign keys of a table.
- *
- * @param {string} tableName The name of the table.
- * @returns {string} The generated sql query.
- * @private
- */
- getForeignKeysQuery(tableName) {
- return 'SELECT conname as constraint_name, pg_catalog.pg_get_constraintdef(r.oid, true) as condef FROM pg_catalog.pg_constraint r ' +
- `WHERE r.conrelid = (SELECT oid FROM pg_class WHERE relname = '${tableName}' LIMIT 1) AND r.contype = 'f' ORDER BY 1;`;
- }
- /**
- * Generate common SQL prefix for getForeignKeyReferencesQuery.
- *
- * @returns {string}
- */
- _getForeignKeyReferencesQueryPrefix() {
- return 'SELECT ' +
- 'DISTINCT tc.constraint_name as constraint_name, ' +
- 'tc.constraint_schema as constraint_schema, ' +
- 'tc.constraint_catalog as constraint_catalog, ' +
- 'tc.table_name as table_name,' +
- 'tc.table_schema as table_schema,' +
- 'tc.table_catalog as table_catalog,' +
- 'kcu.column_name as column_name,' +
- 'ccu.table_schema AS referenced_table_schema,' +
- 'ccu.table_catalog AS referenced_table_catalog,' +
- 'ccu.table_name AS referenced_table_name,' +
- 'ccu.column_name AS referenced_column_name ' +
- 'FROM information_schema.table_constraints AS tc ' +
- 'JOIN information_schema.key_column_usage AS kcu ' +
- 'ON tc.constraint_name = kcu.constraint_name ' +
- 'JOIN information_schema.constraint_column_usage AS ccu ' +
- 'ON ccu.constraint_name = tc.constraint_name ';
- }
- /**
- * Generates an SQL query that returns all foreign keys details of a table.
- *
- * As for getForeignKeysQuery is not compatible with getForeignKeyReferencesQuery, so add a new function.
- *
- * @param {string} tableName
- * @param {string} catalogName
- * @param {string} schemaName
- */
- getForeignKeyReferencesQuery(tableName, catalogName, schemaName) {
- return `${this._getForeignKeyReferencesQueryPrefix()
- }WHERE constraint_type = 'FOREIGN KEY' AND tc.table_name = '${tableName}'${
- catalogName ? ` AND tc.table_catalog = '${catalogName}'` : ''
- }${schemaName ? ` AND tc.table_schema = '${schemaName}'` : ''}`;
- }
- getForeignKeyReferenceQuery(table, columnName) {
- const tableName = table.tableName || table;
- const schema = table.schema;
- return `${this._getForeignKeyReferencesQueryPrefix()
- }WHERE constraint_type = 'FOREIGN KEY' AND tc.table_name='${tableName}' AND kcu.column_name = '${columnName}'${
- schema ? ` AND tc.table_schema = '${schema}'` : ''}`;
- }
- /**
- * 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 CONSTRAINT ${this.quoteIdentifier(foreignKey)};`;
- }
- }
- module.exports = PostgresQueryGenerator;
|