UNPKG

ddl-manager

Version:

store postgres procedures and triggers in files

242 lines 8.89 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.CacheLinter = void 0; const psql_lang_1 = require("psql-lang"); const assert_1 = require("assert"); class CacheLinter { constructor(cursor, cache, select = cache.row.cache, cacheForItem = new psql_lang_1.FromTable({ row: { table: cache.row.for, as: cache.row.as } })) { this.cursor = cursor; this.cache = cache; this.select = select; this.cacheForItem = cacheForItem; } static lint(cursor, cache) { const linter = new CacheLinter(cursor, cache); linter.lint(); } lint() { this.validateCte(); this.validateUnion(); this.validateGroupBy(); this.validateFrom(); this.validateJoins(); this.validateSubQueries(); this.validateColumns(); this.validateColumnsLinks(); this.validateWhere(); this.validateStringAggregations(); this.validateOrderByAndLimit(); } validateCte() { const cte = this.select.row.with; if (cte) { this.throwError("CTE (with queries) are not supported", cte); } } validateUnion() { const union = this.select.row.union; if (union) { this.throwError("UNION are not supported", union.select); } } validateGroupBy() { const groupBy = this.select.row.groupBy; if (groupBy === null || groupBy === void 0 ? void 0 : groupBy.length) { this.throwError("GROUP BY are not supported", groupBy[0]); } } validateSubQueries() { const subQuery = this.select.filterChildrenByInstance(psql_lang_1.Select)[0]; if (subQuery) { this.throwError("SUB QUERIES are not supported", subQuery); } } validateColumns() { var _a, _b; const columns = (_a = this.select.row.select) !== null && _a !== void 0 ? _a : []; if (!columns.length) { this.throwError("required select any columns or expressions", this.select); } const columnsMap = {}; for (const columnNode of columns) { const columnAlias = (_b = columnNode.row.as) === null || _b === void 0 ? void 0 : _b.toValue(); if (!columnAlias) { this.throwError("required alias for every cache column", columnNode); } if (columnAlias in columnsMap) { this.throwError("duplicated cache column", columnNode.row.as); } columnsMap[columnAlias] = true; } } validateColumnsLinks() { extract(this.select, psql_lang_1.ColumnReference) .forEach(this.validateColumnLink.bind(this)); } validateColumnLink(columnLink) { const isStar = (columnLink.row.allColumns && columnLink.row.column.length === 0); if (isStar) { return; } const sourceFromItem = columnLink.findDeclaration(); const missingSource = (!sourceFromItem && !columnLink.isDependentOn(this.cacheForItem)); if (missingSource) { if (columnLink.row.column.length === 1) { const fromItems = this.select.filterChildrenByInstance(psql_lang_1.AbstractFromItem); if (fromItems.length != 1) { this.throwError(`implicit table reference: ${columnLink}`, columnLink); } return; } this.throwError(`source for column ${columnLink} not found`, columnLink); } } validateStringAggregations() { this.select.filterChildrenByInstance(psql_lang_1.FunctionCall) .forEach(this.validateStringAgg.bind(this)); } validateStringAgg(funcCall) { const name = String(funcCall.row.call); if (name != "string_agg") { return; } const args = funcCall.row.arguments || []; if (args.length === 1) { this.throwError("required delimiter for string_agg", funcCall); } } validateFrom() { if (this.select.row.from.length > 1) { this.throwError("multiple FROM are not supported", this.select.row.from[1]); } const allFromItems = extract(this.select, psql_lang_1.AbstractFromItem); for (const fromItem of allFromItems) { if (!(fromItem instanceof psql_lang_1.FromTable)) { this.throwError("supported only from table", fromItem); } } } validateJoins() { this.select.filterChildrenByInstance(psql_lang_1.Join) .forEach(this.validateJoin.bind(this)); } validateJoin(join) { if (!("on" in join.row)) { this.throwError("required ON condition for join", join); } } validateWhere() { const { from, where } = this.select.row; if (!where || !(from[0] instanceof psql_lang_1.FromTable)) { return; } extract(where, psql_lang_1.EqualAny) .forEach(this.validateWhereAny.bind(this)); extract(where, psql_lang_1.BinaryOperator) .forEach(this.validateWhereArrayIntersection.bind(this)); } validateWhereAny(node) { const isWrongSide = (!this.containsFromTable(node.row.operand) // for_table.column = ... && !(node.row.equalAny instanceof psql_lang_1.Select) && this.containsFromTable(node.row.equalAny) // .. =any( from_table.column ) ); if (isWrongSide) { this.throwError([ "your condition is slow SeqScan, condition should be:", `${node.row.equalAny} && ARRAY[ ${node.row.operand} ]` ].join("\n"), node); } } validateWhereArrayIntersection(node) { if (node.row.operator !== "&&") { return; } const arrayLiteral = getArray(node.row.left, node.row.right); if (!arrayLiteral) { return; } const operand = getOther(node.row.left, node.row.right, arrayLiteral); const isWrongSide = (this.containsFromTable(arrayLiteral) && // ... && ARRAY[ from_table.column ] !this.containsFromTable(operand) // for_table.column && ... ); if (isWrongSide) { this.throwError([ "your condition is slow SeqScan, condition should be:", `${arrayLiteral.row.array} = any(${operand})` ].join("\n"), node); } } validateOrderByAndLimit() { const orderBy = this.select.row.orderBy; const limit = this.select.row.limit; if (!(orderBy === null || orderBy === void 0 ? void 0 : orderBy.length)) { if (limit) { this.throwError("required ORDER BY", limit); } return; } const fromItems = this.select.row.from; if (fromItems.length === 0) { this.throwError("required FROM ITEM", orderBy[0]); } const join = extract(this.select, psql_lang_1.Join)[0]; if (join) { this.throwError("joins is not supported for order by/limit 1 trigger", join); } if (!limit) { this.throwError("required LIMIT 1", orderBy[0]); } if (String(limit) !== "1") { this.throwError("supported only limit 1", limit); } } containsFromTable(operand) { const fromItem = this.getFromTable(); const schemaName = fromItem.row.table.row.schema; const tableName = fromItem.row.table.row.name; const tableAlias = fromItem.row.as; return extract(operand, psql_lang_1.ColumnReference).some(column => { const path = column.row.column; if (path.length === 0 || path.length === 1) { return true; } if (path.length === 2) { return path[0].equal(tableAlias || tableName); } return (schemaName && path[0].equal(schemaName) && path[1].equal(tableName)); }); } getFromTable() { const fromItem = this.select.row.from[0]; assert_1.strict.ok(fromItem instanceof psql_lang_1.FromTable, "required from table"); return fromItem; } throwError(error, atNode) { this.cursor.throwError(error, atNode); } } exports.CacheLinter = CacheLinter; function extract(node, NodeClass) { if (node instanceof NodeClass) { return [node]; } return node.filterChildrenByInstance(NodeClass); } function getArray(...nodes) { return nodes.find(node => node instanceof psql_lang_1.ArrayLiteral); } function getOther(left, right, other) { if (left === other) { return right; } return left; } //# sourceMappingURL=CacheLinter.js.map