UNPKG

knex

Version:

A batteries-included SQL query & schema builder for Postgres, MySQL and SQLite3 and the Browser

342 lines (272 loc) 9.68 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = void 0; var _lodash = require("lodash"); var _inherits = _interopRequireDefault(require("inherits")); var _client = _interopRequireDefault(require("../../client")); var _bluebird = _interopRequireDefault(require("bluebird")); var _compiler = _interopRequireDefault(require("./query/compiler")); var _columncompiler = _interopRequireDefault(require("./schema/columncompiler")); var _tablecompiler = _interopRequireDefault(require("./schema/tablecompiler")); var _compiler2 = _interopRequireDefault(require("./schema/compiler")); var _string = require("../../query/string"); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } // PostgreSQL // ------- function Client_PG(config) { _client.default.apply(this, arguments); if (config.returning) { this.defaultReturning = config.returning; } if (config.searchPath) { this.searchPath = config.searchPath; } } (0, _inherits.default)(Client_PG, _client.default); (0, _lodash.assign)(Client_PG.prototype, { queryCompiler() { return new _compiler.default(this, ...arguments); }, columnCompiler() { return new _columncompiler.default(this, ...arguments); }, schemaCompiler() { return new _compiler2.default(this, ...arguments); }, tableCompiler() { return new _tablecompiler.default(this, ...arguments); }, dialect: 'postgresql', driverName: 'pg', _driver() { return require('pg'); }, _escapeBinding: (0, _string.makeEscape)({ escapeArray(val, esc) { return esc(arrayString(val, esc)); }, escapeString(str) { let hasBackslash = false; let escaped = "'"; for (let i = 0; i < str.length; i++) { const c = str[i]; if (c === "'") { escaped += c + c; } else if (c === '\\') { escaped += c + c; hasBackslash = true; } else { escaped += c; } } escaped += "'"; if (hasBackslash === true) { escaped = 'E' + escaped; } return escaped; }, escapeObject(val, prepareValue, timezone, seen = []) { if (val && typeof val.toPostgres === 'function') { seen = seen || []; if (seen.indexOf(val) !== -1) { throw new Error(`circular reference detected while preparing "${val}" for query`); } seen.push(val); return prepareValue(val.toPostgres(prepareValue), seen); } return JSON.stringify(val); } }), wrapIdentifierImpl(value) { if (value === '*') return value; let arrayAccessor = ''; const arrayAccessorMatch = value.match(/(.*?)(\[[0-9]+\])/); if (arrayAccessorMatch) { value = arrayAccessorMatch[1]; arrayAccessor = arrayAccessorMatch[2]; } return `"${value.replace(/"/g, '""')}"${arrayAccessor}`; }, // Get a raw connection, called by the `pool` whenever a new // connection needs to be added to the pool. acquireRawConnection() { const client = this; return new _bluebird.default(function (resolver, rejecter) { const connection = new client.driver.Client(client.connectionSettings); connection.connect(function (err, connection) { if (err) { return rejecter(err); } connection.on('error', err => { connection.__knex__disposed = err; }); connection.on('end', err => { connection.__knex__disposed = err || 'Connection ended unexpectedly'; }); if (!client.version) { return client.checkVersion(connection).then(function (version) { client.version = version; resolver(connection); }); } resolver(connection); }); }).tap(function setSearchPath(connection) { return client.setSchemaSearchPath(connection); }); }, // Used to explicitly close a connection, called internally by the pool // when a connection times out or the pool is shutdown. destroyRawConnection(connection) { return _bluebird.default.fromCallback(connection.end.bind(connection)); }, // In PostgreSQL, we need to do a version check to do some feature // checking on the database. checkVersion(connection) { return new _bluebird.default(function (resolver, rejecter) { connection.query('select version();', function (err, resp) { if (err) return rejecter(err); resolver(/^PostgreSQL (.*?)( |$)/.exec(resp.rows[0].version)[1]); }); }); }, // Position the bindings for the query. The escape sequence for question mark // is \? (e.g. knex.raw("\\?") since javascript requires '\' to be escaped too...) positionBindings(sql) { let questionCount = 0; return sql.replace(/(\\*)(\?)/g, function (match, escapes) { if (escapes.length % 2) { return '?'; } else { questionCount++; return `$${questionCount}`; } }); }, setSchemaSearchPath(connection, searchPath) { let path = searchPath || this.searchPath; if (!path) return _bluebird.default.resolve(true); if (!(0, _lodash.isArray)(path) && !(0, _lodash.isString)(path)) { throw new TypeError(`knex: Expected searchPath to be Array/String, got: ${typeof path}`); } if ((0, _lodash.isString)(path)) { if ((0, _lodash.includes)(path, ',')) { const parts = path.split(','); const arraySyntax = `[${(0, _lodash.map)(parts, searchPath => `'${searchPath}'`).join(', ')}]`; this.logger.warn(`Detected comma in searchPath "${path}".` + `If you are trying to specify multiple schemas, use Array syntax: ${arraySyntax}`); } path = [path]; } path = (0, _lodash.map)(path, schemaName => `"${schemaName}"`).join(','); return new _bluebird.default(function (resolver, rejecter) { connection.query(`set search_path to ${path}`, function (err) { if (err) return rejecter(err); resolver(true); }); }); }, _stream(connection, obj, stream, options) { const PGQueryStream = process.browser ? undefined : require('pg-query-stream'); const sql = obj.sql; return new _bluebird.default(function (resolver, rejecter) { const queryStream = connection.query(new PGQueryStream(sql, obj.bindings, options)); queryStream.on('error', function (error) { rejecter(error); stream.emit('error', error); }); // 'end' IS propagated by .pipe, by default stream.on('end', resolver); queryStream.pipe(stream); }); }, // Runs the query on the specified connection, providing the bindings // and any other necessary prep work. _query(connection, obj) { let queryConfig = { text: obj.sql, values: obj.bindings || [] }; if (obj.options) { queryConfig = (0, _lodash.extend)(queryConfig, obj.options); } return new _bluebird.default(function (resolver, rejecter) { connection.query(queryConfig, function (err, response) { if (err) return rejecter(err); obj.response = response; resolver(obj); }); }); }, // Ensures the response is returned in the same format as other clients. processResponse(obj, runner) { const resp = obj.response; if (obj.output) return obj.output.call(runner, resp); if (obj.method === 'raw') return resp; const returning = obj.returning; if (resp.command === 'SELECT') { if (obj.method === 'first') return resp.rows[0]; if (obj.method === 'pluck') return (0, _lodash.map)(resp.rows, obj.pluck); return resp.rows; } if (returning) { const returns = []; for (let i = 0, l = resp.rows.length; i < l; i++) { const row = resp.rows[i]; if (returning === '*' || Array.isArray(returning)) { returns[i] = row; } else { // Pluck the only column in the row. returns[i] = row[Object.keys(row)[0]]; } } return returns; } if (resp.command === 'UPDATE' || resp.command === 'DELETE') { return resp.rowCount; } return resp; }, canCancelQuery: true, cancelQuery(connectionToKill) { const acquiringConn = this.acquireConnection(); // Error out if we can't acquire connection in time. // Purposely not putting timeout on `pg_terminate_backend` execution because erroring // early there would release the `connectionToKill` back to the pool with // a `KILL QUERY` command yet to finish. return acquiringConn.then(conn => { return this._wrappedCancelQueryCall(conn, connectionToKill).finally(() => { // NOT returning this promise because we want to release the connection // in a non-blocking fashion this.releaseConnection(conn); }); }); }, _wrappedCancelQueryCall(conn, connectionToKill) { return this.query(conn, { method: 'raw', sql: 'SELECT pg_terminate_backend(?);', bindings: [connectionToKill.processID], options: {} }); } }); function arrayString(arr, esc) { let result = '{'; for (let i = 0; i < arr.length; i++) { if (i > 0) result += ','; const val = arr[i]; if (val === null || typeof val === 'undefined') { result += 'NULL'; } else if (Array.isArray(val)) { result += arrayString(val, esc); } else if (typeof val === 'number') { result += val; } else { result += JSON.stringify(typeof val === 'string' ? val : esc(val)); } } return result + '}'; } var _default = Client_PG; exports.default = _default; module.exports = exports.default;