@abaplint/core
Version:
abaplint - Core API
323 lines • 15.6 kB
JavaScript
"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