UNPKG

waterline-postgresql

Version:
573 lines (459 loc) 18.9 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); var _slicedToArray = (function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i['return']) _i['return'](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError('Invalid attempt to destructure non-iterable instance'); } }; })(); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) arr2[i] = arr[i]; return arr2; } else { return Array.from(arr); } } var _lodash = require('lodash'); var _lodash2 = _interopRequireDefault(_lodash); var _adapter = require('./adapter'); var _adapter2 = _interopRequireDefault(_adapter); var _waterlineSequelSequelLibCriteriaProcessor = require('waterline-sequel/sequel/lib/criteriaProcessor'); var _waterlineSequelSequelLibCriteriaProcessor2 = _interopRequireDefault(_waterlineSequelSequelLibCriteriaProcessor); var _spatial = require('./spatial'); var _spatial2 = _interopRequireDefault(_spatial); var _procedures = require('./procedures'); var _procedures2 = _interopRequireDefault(_procedures); var _knex = require('knex'); var _knex2 = _interopRequireDefault(_knex); var Util = { PG_MAX_INT: 2147483647, initializeConnection: function initializeConnection(cxn) { return _adapter2['default'].getVersion(cxn).then(function (version) { cxn.version = Util.validateVersion(version); return _procedures2['default'].describeAll(cxn); }).then(function (procedures) { cxn.storedProcedures = procedures; }); }, getTransaction: function getTransaction(txn, query) { if (Util.isTransaction(txn)) { return txn; } else { return query; } }, isTransaction: function isTransaction(txn) { return txn && _lodash2['default'].isFunction(txn.commit); }, /** * Apply a primary key constraint to a table * * @param table - a knex table object * @param definition - a waterline attribute definition */ applyPrimaryKeyConstraints: function applyPrimaryKeyConstraints(table, definition) { var primaryKeys = _lodash2['default'].keys(_lodash2['default'].pickBy(definition, function (attribute) { return attribute.primaryKey; })); if (!primaryKeys.length) return; return table.primary(primaryKeys); }, applyCompositeUniqueConstraints: function applyCompositeUniqueConstraints(table, definition) { _lodash2['default'].each(definition, function (attribute, name) { var uniqueDef = attribute.unique || {}; if (attribute.primaryKey) return; if (_lodash2['default'].isEmpty(uniqueDef)) return; if (!_lodash2['default'].isArray(uniqueDef.composite)) return; var uniqueKeys = _lodash2['default'].uniq([name].concat(_toConsumableArray(uniqueDef.composite))); table.unique(uniqueKeys); }); }, applyEnumConstraints: function applyEnumConstraints(table, definition) { _lodash2['default'].each(definition, function (attribute, name) { if (_lodash2['default'].isArray(attribute['enum'])) { table.enu(name, attribute['enum']); } }); }, applyTableConstraints: function applyTableConstraints(table, definition) { return Promise.all([Util.applyPrimaryKeyConstraints(table, definition), Util.applyCompositeUniqueConstraints(table, definition)]); }, //Util.applyEnumConstraints(table, definition) applyColumnConstraints: function applyColumnConstraints(column, definition) { if (_lodash2['default'].isString(definition)) { return; } return _lodash2['default'].map(definition, function (value, key) { if (key == 'defaultsTo' && definition.autoIncrement && value == 'AUTO_INCREMENT') { return; } return Util.applyParticularColumnConstraint(column, key, value, definition); }); }, /** * Apply value constraints to a particular column */ applyParticularColumnConstraint: function applyParticularColumnConstraint(column, constraintName, value, definition) { if (!value) return; switch (constraintName) { case 'index': return column.index(_lodash2['default'].get(value, 'indexName'), _lodash2['default'].get(value, 'indexType')); /** * Acceptable forms: * attr: { unique: true } * attr: { * unique: { * unique: true, // or false * composite: [ 'otherAttr' ] * } * } */ case 'unique': if ((value === true || _lodash2['default'].get(value, 'unique') === true) && !definition.primaryKey) { column.unique(); } return; case 'notNull': return column.notNullable(); case 'defaultsTo': if (_lodash2['default'].isArray(value) && definition.type == 'array') { return column.defaultTo('{' + value.join(',') + '}'); } if (!_lodash2['default'].isFunction(value)) { return column.defaultTo(value); } /* * TODO case 'comment': return table.comment(attr.comment || attr.description) */ case 'primaryKey': case 'autoIncrement': if (definition.dbType == 'uuid') { return column.defaultTo(_knex2['default'].raw('uuid_generate_v4()')); } } }, /** * Create a column for Knex from a Waterline attribute definition */ toKnexColumn: function toKnexColumn(table, _name, attrDefinition, wlModel, schema) { var attr = _lodash2['default'].isObject(attrDefinition) ? attrDefinition : { type: attrDefinition }; var type = attr.autoIncrement ? 'serial' : attr.type; var name = attr.columnName || _name; if (_lodash2['default'].includes(wlModel.meta.uuids, _name) && !wlModel.meta.junctionTable) { wlModel._attributes[_name].type = 'uuid'; wlModel.definition[_name].type = 'uuid'; wlModel._cast._types[_name] = 'uuid'; type = 'uuid'; } if (attrDefinition.foreignKey && attrDefinition.model) { var refModel = schema[attrDefinition.model]; try { var fpk = _adapter2['default'].getPrimaryKey({ collections: schema }, attrDefinition.model); if (_lodash2['default'].includes(refModel.meta.uuids, fpk) && !refModel.meta.junctionTable) { type = 'uuid'; } } catch (e) {} } // set key types for m2m if (attrDefinition.foreignKey && attrDefinition.references && attrDefinition.on) { try { type = schema[attrDefinition.references].attributes[attrDefinition.on].type; } catch (e) {} } /** * Perform a special check for ENUM. ENUM is both a type and a constraint. * * table.enu(col, values) * Adds a enum column, (aliased to enu, as enum is a reserved word in javascript). */ if (_lodash2['default'].isArray(attr['enum'])) { return table.enu(name, attr['enum']); } switch (attr.dbType || type.toLowerCase()) { /** * table.text(name, [textType]) * Adds a text column, with optional textType for MySql text datatype preference. * textType may be mediumtext or longtext, otherwise defaults to text. */ case 'string': case 'text': case 'mediumtext': case 'longtext': return table.text(name, type); /** * table.string(name, [length]) * Adds a string column, with optional length defaulting to 255. */ case 'character varying': return table.string(name, attr.length); case 'serial': case 'smallserial': return table.specificType(name, 'serial'); case 'bigserial': return table.specificType(name, 'bigserial'); /** * table.boolean(name) * Adds a boolean column. */ case 'boolean': return table.boolean(name); /** * table.integer(name) * Adds an integer column. */ case 'int': case 'integer': case 'smallint': return table.integer(name); /** * table.bigInteger(name) * In MySQL or PostgreSQL, adds a bigint column, otherwise adds a normal integer. * Note that bigint data is returned as a string in queries because JavaScript may * be unable to parse them without loss of precision. */ case 'bigint': case 'biginteger': return table.bigInteger(name); /** * table.float(column, [precision], [scale]) * Adds a float column, with optional precision and scale. */ case 'real': case 'float': return table.float(name, attr.precision, attr.scale); case 'double': return table.float(name, 15, attr.scale); /** * table.decimal(column, [precision], [scale]) * Adds a decimal column, with optional precision and scale. */ case 'decimal': return table.decimal(name, attr.precision, attr.scale); /** * table.time(name) * Adds a time column. */ case 'time': return table.time(name); /** * table.date(name) * Adds a date column. */ case 'date': return table.date(name); /** * table.timestamp(name, [standard]) * Adds a timestamp column, defaults to timestamptz in PostgreSQL, * unless true is passed as the second argument. * * Note that the method for defaulting to the current datetime varies from one * database to another. For example: PostreSQL requires .defaultTo(knex.raw('now()')), * but SQLite3 requires .defaultTo(knex.raw("date('now')")). */ case 'datestamp': case 'datetime': return table.timestamp(name, attr.standard); case 'array': return table.specificType(name, 'text ARRAY'); /** * table.json(name, [jsonb]) * Adds a json column, using the built-in json type in postgresql, * defaulting to a text column in older versions of postgresql or in unsupported databases. * jsonb can be used by passing true as the second argument. */ case 'json': case 'jsonb': return table.jsonb(name); case 'binary': return table.binary(name); /** * table.uuid(name) * Adds a uuid column - this uses the built-in uuid type in postgresql, * and falling back to a char(36) in other databases. */ case 'uuid': return table.uuid(name); default: return table.specificType(name, attr.dbType || type); } }, /** * Convert a parameterized waterline query into a knex-compatible query string */ toKnexRawQuery: function toKnexRawQuery(sql) { var wlSqlOptions = _adapter2['default'].wlSqlOptions; sql = (sql || '').replace(/\$\d+/g, '?'); if (_lodash2['default'].get(wlSqlOptions, 'wlNext.caseSensitive')) { sql = sql.replace(/LOWER\(("\w+"."\w+")\)/ig, '$1'); } return sql; }, /** * Cast values to the correct type */ castValues: function castValues(values) { return _lodash2['default'].map(values, function (value) { if (_lodash2['default'].isString(value) && value[0] === '[') { var arr = JSON.parse(value); if (_lodash2['default'].isArray(arr)) { return arr; } } return value; }); }, castResultRows: function castResultRows(rows, schema) { if (_lodash2['default'].isPlainObject(rows)) { return Util.castResultValues(rows, schema); } else { return _lodash2['default'].map(rows, function (row) { return Util.castResultValues(row, schema); }); } }, castResultValues: function castResultValues(values, schema) { return _lodash2['default'].mapValues(values, function (value, attr) { var definition = schema.definition[attr]; if (!definition) return value; if (_spatial2['default'].isSpatialColumn(definition)) { try { return JSON.parse(value); } catch (e) { return null; } } if (_lodash2['default'].isArray(value)) { return _lodash2['default'].map(value, function (item) { try { return JSON.parse(item); } catch (e) { return item; } }); } return value; }); }, sanitize: function sanitize(data, schema, cxn) { if (_lodash2['default'].isArray(data)) { return _lodash2['default'].map(data, function (record) { return Util.sanitizeRecord(record, schema, cxn); }); } else { return Util.sanitizeRecord(data, schema, cxn); } }, sanitizeRecord: function sanitizeRecord(data, schema, cxn) { _lodash2['default'].each(data, function (value, attr) { var definition = schema.definition[attr]; // remove unrecognized fields (according to schema) from data if (!definition) { delete data[attr]; return; } // remove any autoIncrement fields from data if (!definition || definition.autoIncrement) { delete data[attr]; } if (_spatial2['default'].isSpatialColumn(definition)) { data[attr] = _spatial2['default'].fromGeojson(data[attr], definition, cxn); } }); return data; }, /** * Construct a knex query that joins one or more tables for populate() */ buildKnexJoinQuery: function buildKnexJoinQuery(cxn, tableName, options) { var schema = cxn.collections[tableName]; var pk = _adapter2['default'].getPrimaryKey(cxn, tableName); var query = cxn.knex.select(tableName + '.*').select(_spatial2['default'].buildSpatialSelect(schema.definition, tableName, cxn)).select(cxn.knex.raw(Util.buildSelectAggregationColumns(cxn, options))).from(tableName).where(Util.buildWhereClause(cxn, tableName, options)).groupBy(tableName + '.' + pk).orderByRaw(Util.buildOrderByClause(tableName, options)).limit(options.limit || Util.PG_MAX_INT).offset(options.skip || 0); Util.buildKnexJoins(cxn, options, query); return query; }, addSelectColumns: function addSelectColumns(columns, query) { var _query$split = query.split('FROM'); var _query$split2 = _slicedToArray(_query$split, 2); var oldSelectClause = _query$split2[0]; var fromClause = _query$split2[1]; var newSelectClause = [oldSelectClause.split(',')].concat(_toConsumableArray(columns)).join(','); return newSelectClause + ' FROM ' + fromClause; }, buildKnexJoins: function buildKnexJoins(cxn, _ref, query) { var joins = _ref.joins; _lodash2['default'].each(joins, function (join) { var parentAlias = Util.getParentAlias(join); var alias = Util.getSubqueryAlias(join); var subquery = Util.buildKnexJoinSubquery(cxn, join); query.leftJoin(cxn.knex.raw('(' + subquery + ') as "' + alias + '"'), alias + '.' + join.childKey, parentAlias + '.' + join.parentKey); }); }, buildKnexJoinSubquery: function buildKnexJoinSubquery(cxn, _ref2) { var criteria = _ref2.criteria; var child = _ref2.child; var schema = cxn.collections[child]; return cxn.knex.select('*').select(_spatial2['default'].buildSpatialSelect(schema.definition, child, cxn)).from(child).where(Util.buildWhereClause(cxn, child, criteria)); }, buildOrderByClause: function buildOrderByClause(tableName, _ref3) { var sort = _ref3.sort; if (_lodash2['default'].isEmpty(sort)) { return '1'; } var queryTokens = _lodash2['default'].map(sort, function (_direction, field) { var direction = _direction === 1 ? '' : 'desc'; return '"' + tableName + '"."' + field + '" ' + direction; }); return queryTokens.join(', '); }, buildWhereClause: function buildWhereClause(cxn, tableName, options) { var parser = new _waterlineSequelSequelLibCriteriaProcessor2['default'](tableName, cxn.schema, _adapter2['default'].wlSqlOptions); var _parser$read = parser.read(_lodash2['default'].omit(options, ['sort', 'limit', 'groupBy', 'skip'])); var query = _parser$read.query; var values = _parser$read.values; return cxn.knex.raw(Util.toKnexRawQuery(query), Util.castValues(values)); }, getJoinAlias: function getJoinAlias(_ref4) { var alias = _ref4.alias; var parentKey = _ref4.parentKey; var removeParentKey = _ref4.removeParentKey; if (alias != parentKey && removeParentKey === true) { return parentKey; } else { return alias; } }, getParentAlias: function getParentAlias(join) { if (join.junctionTable) { return Util.getJoinAlias(join) + join.parent; } else { return join.parent; } }, getSubqueryAlias: function getSubqueryAlias(join) { return Util.getJoinAlias(join) + join.child; }, buildSelectAggregationColumns: function buildSelectAggregationColumns(cxn, _ref5) { var joins = _ref5.joins; return _lodash2['default'].map(_lodash2['default'].reject(joins, { select: false }), function (join) { var criteria = join.criteria || {}; var subqueryAlias = Util.getSubqueryAlias(join); var asColumn = Util.getJoinAlias(join); var orderBy = Util.buildOrderByClause(subqueryAlias, criteria); var start = (criteria.skip || 0) + 1; var end = (criteria.limit || Util.PG_MAX_INT - start) + start - 1; if (!criteria.skip && !criteria.limit) { return 'json_agg("' + subqueryAlias + '".* order by ' + orderBy + ') as "' + asColumn + '"'; } return 'array_to_json((array_agg("' + subqueryAlias + '".* order by ' + orderBy + '))[' + start + ':' + end + ']) as "' + asColumn + '"'; }); }, /** * Parse and validate a Postgres "select version()" result */ validateVersion: function validateVersion(_ref6) { var _ref62 = _slicedToArray(_ref6, 3); var major = _ref62[0]; var minor = _ref62[1]; var patch = _ref62[2]; if (major < 9 || major === 9 && minor < 4) { throw new Error('\n PostgreSQL ' + major + '.' + minor + '.' + patch + ' detected. This adapter requires PostgreSQL 9.4 or higher.\n Please either:\n 1. Upgrade your Postgres server to at least 9.4.0 -or-\n 2. Use the sails-postgresql adapter instead: https://www.npmjs.com/package/sails-postgresql\n '); } return parseFloat(major + '.' + minor); } }; exports['default'] = Util; module.exports = exports['default'];