UNPKG

@abaplint/core

Version:
323 lines • 15.6 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Select = void 0; const Expressions = require("../../2_statements/expressions"); const basic_1 = require("../../types/basic"); const inline_data_1 = require("./inline_data"); const target_1 = require("./target"); const sql_from_1 = require("./sql_from"); const sql_for_all_entries_1 = require("./sql_for_all_entries"); const _scope_type_1 = require("../_scope_type"); const sql_source_1 = require("./sql_source"); const sql_compare_1 = require("./sql_compare"); const sql_order_by_1 = require("./sql_order_by"); const dynamic_1 = require("./dynamic"); const _reference_1 = require("../_reference"); const _syntax_input_1 = require("../_syntax_input"); const isSimple = /^\w+$/; class Select { static runSyntax(node, input, skipImplicitInto = false) { var _a; const token = node.getFirstToken(); const from = node.findDirectExpression(Expressions.SQLFrom); const dbSources = from ? sql_from_1.SQLFrom.runSyntax(from, input) : []; if (from === undefined) { const message = `Missing FROM`; input.issues.push((0, _syntax_input_1.syntaxIssue)(input, node.getFirstToken(), message)); return; } const fields = this.findFields(node, input); if (fields.length === 0 && node.findDirectExpression(Expressions.SQLFieldListLoop) === undefined && node.findDirectExpression(Expressions.SQLAggregation) === undefined) { const message = `SELECT: fields missing`; input.issues.push((0, _syntax_input_1.syntaxIssue)(input, node.getFirstToken(), message)); return; } const isSingle = node.getChildren()[1].concatTokens().toUpperCase() === "SINGLE" || node.get() instanceof Expressions.SelectLoop; this.checkFields(fields, dbSources, input, node); this.handleInto(node, input, fields, dbSources, isSingle); const fae = node.findDirectExpression(Expressions.SQLForAllEntries); if (fae) { input.scope.push(_scope_type_1.ScopeType.OpenSQL, "SELECT", token.getStart(), input.filename); sql_for_all_entries_1.SQLForAllEntries.runSyntax(fae, input); } for (const t of node.findAllExpressions(Expressions.Target)) { target_1.Target.runSyntax(t, input); } // check implicit into, the target field is implict equal to the table name if (skipImplicitInto === false && node.findDirectExpression(Expressions.SQLIntoTable) === undefined && node.findDirectExpression(Expressions.SQLIntoList) === undefined && node.findDirectExpression(Expressions.SQLIntoStructure) === undefined) { const fields = (_a = node.findFirstExpression(Expressions.SQLAggregation)) === null || _a === void 0 ? void 0 : _a.concatTokens(); const c = new RegExp(/^count\(\s*\*\s*\)$/, "i"); if (fields === undefined || c.test(fields) === false) { const nameToken = from === null || from === void 0 ? void 0 : from.findDirectExpression(Expressions.SQLFromSource); if (nameToken) { const found = input.scope.findVariable(nameToken.concatTokens()); if (found) { input.scope.addReference(nameToken.getFirstToken(), found, _reference_1.ReferenceType.DataWriteReference, input.filename); } else { const message = `Target variable ${nameToken.concatTokens()} not found in scope`; input.issues.push((0, _syntax_input_1.syntaxIssue)(input, node.getFirstToken(), message)); return; } } } } // OFFSET for (const s of node.findDirectExpressions(Expressions.SQLSource)) { sql_source_1.SQLSource.runSyntax(s, input); } for (const up of node.findDirectExpressions(Expressions.SQLUpTo)) { for (const s of up.findDirectExpressions(Expressions.SQLSource)) { sql_source_1.SQLSource.runSyntax(s, input); } } for (const fae of node.findDirectExpressions(Expressions.SQLForAllEntries)) { for (const s of fae.findDirectExpressions(Expressions.SQLSource)) { sql_source_1.SQLSource.runSyntax(s, input); } } for (const s of node.findAllExpressions(Expressions.SQLCompare)) { sql_compare_1.SQLCompare.runSyntax(s, input, dbSources); } for (const s of node.findDirectExpressions(Expressions.SQLOrderBy)) { sql_order_by_1.SQLOrderBy.runSyntax(s, input); } if (this.isStrictMode(node)) { this.strictModeChecks(node, input); } if (input.scope.getType() === _scope_type_1.ScopeType.OpenSQL) { input.scope.pop(node.getLastToken().getEnd()); } } // there are multiple rules, but gotta start somewhere static isStrictMode(node) { const into = node.findDirectExpressionsMulti([Expressions.SQLIntoList, Expressions.SQLIntoStructure, Expressions.SQLIntoTable])[0]; const where = node.findDirectExpression(Expressions.SQLCond); // INTO is after WHERE if (into && where && into.getFirstToken().getStart().isAfter(where.getFirstToken().getStart())) { return true; } // FIELDS is used if (node.findFirstExpression(Expressions.SQLFields)) { return true; } // any field is escaped with @ for (const source of node.findAllExpressions(Expressions.SQLSource)) { if (source.getFirstToken().getStr() === "@") { return true; } } // comma used in FROM const fieldList = node.findFirstExpression(Expressions.SQLFieldList); if (fieldList && fieldList.findDirectTokenByText(",")) { return true; } return false; } static strictModeChecks(node, input) { const sources = node.findAllExpressions(Expressions.SQLSource); for (const source of sources) { const first = source.getFirstChild(); if ((first === null || first === void 0 ? void 0 : first.get()) instanceof Expressions.SQLAliasField) { continue; } else if ((first === null || first === void 0 ? void 0 : first.getFirstToken().getStr()) === "@") { continue; } else if ((first === null || first === void 0 ? void 0 : first.getChildren()[0].get()) instanceof Expressions.Constant) { continue; } const message = `SELECT: "${source.concatTokens()}" must be escaped with @ in strict mode`; input.issues.push((0, _syntax_input_1.syntaxIssue)(input, node.getFirstToken(), message)); } } static handleInto(node, input, fields, dbSources, _isSingle) { const intoTable = node.findDirectExpression(Expressions.SQLIntoTable); if (intoTable) { const inline = intoTable.findFirstExpression(Expressions.InlineData); if (inline) { inline_data_1.InlineData.runSyntax(inline, input, this.buildTableType(fields, dbSources, input.scope)); } } const intoStructure = node.findDirectExpression(Expressions.SQLIntoStructure); if (intoStructure) { const inlineList = intoStructure.findAllExpressions(Expressions.InlineData); if (inlineList.length === 1) { inline_data_1.InlineData.runSyntax(inlineList[0], input, this.buildStructureType(fields, dbSources, input.scope)); } else { for (const inline of inlineList) { // todo, for now these are voided inline_data_1.InlineData.runSyntax(inline, input, basic_1.VoidType.get("SELECT_todo1")); } } } const intoList = node.findDirectExpression(Expressions.SQLIntoList); if (intoList) { const isDynamic = fields.length === 1 && fields[0].expression.findDirectExpression(Expressions.Dynamic) !== undefined; const targets = intoList.findDirectExpressions(Expressions.SQLTarget); if (targets.length !== fields.length && isDynamic !== true) { const message = `number of fields selected vs list does not match`; input.issues.push((0, _syntax_input_1.syntaxIssue)(input, node.getFirstToken(), message)); return; } for (let i = 0; i < targets.length; i++) { const target = targets[i]; const field = fields[i]; const inline = target.findFirstExpression(Expressions.InlineData); if (inline) { if (isDynamic) { const message = `dynamic field list, inlining not possible`; input.issues.push((0, _syntax_input_1.syntaxIssue)(input, node.getFirstToken(), message)); return; } let type = basic_1.VoidType.get("SELECT_todo2"); if (isSimple.test(field.code)) { for (const dbSource of dbSources) { if (dbSource === undefined) { continue; } const dbType = dbSource.parseType(input.scope.getRegistry()); if (dbType instanceof basic_1.StructureType) { const found = dbType.getComponentByName(field.code); if (found) { type = found; break; } } } } inline_data_1.InlineData.runSyntax(inline, input, type); } } } } static checkFields(fields, dbSources, input, node) { if (dbSources.length > 1) { return; } const first = dbSources[0]; if (first === undefined) { // then its voided return; } const type = first.parseType(input.scope.getRegistry()); if (type instanceof basic_1.VoidType || type instanceof basic_1.UnknownType) { return; } if (!(type instanceof basic_1.StructureType)) { const message = "checkFields, expected structure, " + type.constructor.name; input.issues.push((0, _syntax_input_1.syntaxIssue)(input, node.getFirstToken(), message)); return; } for (const field of fields) { if (field.code === "*") { continue; } if (isSimple.test(field.code) && type.getComponentByName(field.code) === undefined) { const message = `checkFields, field ${field.code} not found`; input.issues.push((0, _syntax_input_1.syntaxIssue)(input, node.getFirstToken(), message)); return; } } } static buildStructureType(fields, dbSources, scope) { var _a, _b, _c; if (fields.length === 1 && dbSources.length === 1) { const dbType = (_a = dbSources[0]) === null || _a === void 0 ? void 0 : _a.parseType(scope.getRegistry()); if (dbType === undefined) { const name = ((_b = dbSources[0]) === null || _b === void 0 ? void 0 : _b.getName()) || "buildStructureTypeError"; if (scope.getRegistry().inErrorNamespace(name) === true) { return new basic_1.UnknownType("Select, " + name + " not found"); } else { return basic_1.VoidType.get((_c = dbSources[0]) === null || _c === void 0 ? void 0 : _c.getName()); } } if (fields[0].code === "*") { return dbType; } else { if (dbType instanceof basic_1.StructureType) { const field = dbType.getComponentByName(fields[0].code); if (field) { return field; } else { // todo: aggregated/calculated values return basic_1.VoidType.get("SELECT_todo11"); } } else { return basic_1.VoidType.get("SELECT_todo10"); } } } else { return basic_1.VoidType.get("SELECT_todo9"); } } static buildTableType(fields, dbSources, scope) { if (dbSources.length !== 1) { return basic_1.VoidType.get("SELECT_todo3"); } if (dbSources[0] === undefined) { // then its a voided table return basic_1.VoidType.get("SELECT_todo4"); } const dbType = dbSources[0].parseType(scope.getRegistry()); if (!(dbType instanceof basic_1.StructureType)) { return basic_1.VoidType.get("SELECT_todo5"); } if (fields.length === 1 && fields[0].code === "*") { return new basic_1.TableType(dbType, { withHeader: false, keyType: basic_1.TableKeyType.default }, undefined); } const allFieldsSimple = fields.every(f => isSimple.test(f.code)); if (allFieldsSimple === true) { const components = []; for (const field of fields) { const type = dbType.getComponentByName(field.code); if (type === undefined) { return basic_1.VoidType.get("SELECT_todo6"); } components.push({ name: field.code, type }); } return new basic_1.TableType(new basic_1.StructureType(components), { withHeader: false, keyType: basic_1.TableKeyType.default }, undefined); } return basic_1.VoidType.get("SELECT_todo7"); } static findFields(node, input) { var _a, _b; let expr = undefined; const ret = []; if (node.get() instanceof Expressions.SelectLoop) { expr = node.findFirstExpression(Expressions.SQLFieldListLoop); } else { expr = node.findFirstExpression(Expressions.SQLFieldList); } if (((_a = expr === null || expr === void 0 ? void 0 : expr.getFirstChild()) === null || _a === void 0 ? void 0 : _a.get()) instanceof Expressions.Dynamic) { dynamic_1.Dynamic.runSyntax(expr.getFirstChild(), input); } for (const field of (expr === null || expr === void 0 ? void 0 : expr.findDirectExpressionsMulti([Expressions.SQLField, Expressions.SQLFieldName])) || []) { let code = field.concatTokens().toUpperCase(); const as = ((_b = field.findDirectExpression(Expressions.SQLAsName)) === null || _b === void 0 ? void 0 : _b.concatTokens()) || ""; if (as !== "") { code = code.replace(" AS " + as, ""); } ret.push({ code, as, expression: field }); } if (ret.length === 0 && expr) { ret.push({ code: expr.concatTokens(), as: "", expression: expr }); } return ret; } } exports.Select = Select; //# sourceMappingURL=select.js.map