ts-sql
Version:
An SQL builder in Typescript. This project is heavily inspired by [XQL](/extjs/xql). A big shout out to @exjs and @kobalicek for this amazing project.
191 lines • 7.84 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const jsymbol_1 = require("jsymbol");
const ast = require("./astsql");
const queryVisitor_1 = require("./queryVisitor");
var SymbolState;
(function (SymbolState) {
SymbolState[SymbolState["Resolved"] = 0] = "Resolved";
SymbolState[SymbolState["Unresolved"] = 1] = "Unresolved";
})(SymbolState = exports.SymbolState || (exports.SymbolState = {}));
class SqlAstSymbol {
constructor(identifier, type) {
this.identifier = identifier;
this.type = type;
this.state = SymbolState.Unresolved;
}
toString() {
return this.identifier;
}
}
exports.SqlAstSymbol = SqlAstSymbol;
class ValidationContext {
constructor(currentNode) {
this.currentNode = currentNode;
this.symbolTable = new jsymbol_1.SymbolTable();
this.collectedSymbols = [];
}
}
exports.ValidationContext = ValidationContext;
class QueryValidator extends queryVisitor_1.QueryVisitor {
constructor(dialect, query, contextBuilder = ValidationContext) {
super(dialect, query, contextBuilder);
this.visitSelectStatement = (context, node) => {
context.symbolTable.enterScope();
this.visitGenericNode(context, node.query);
for (let sym of context.symbolTable) {
context.collectedSymbols.push(sym);
}
context.symbolTable.exitScope();
return context;
};
this.visitQueryExpression = (context, node) => {
if (node.from) {
this.visitNode(context, node.from);
}
if (node.elements && node.elements.length) {
for (let se of node.elements) {
this.visitNode(context, se);
}
}
if (node.orderBy) {
this.visitNode(context, node.orderBy);
}
if (node.limit) {
this.visitNode(context, node.limit);
}
return context;
};
this.visitTableSpec = (context, node) => {
node.tableName = this.addResolvedSymbol(context, node.tableName.toString(), ast.SqlSymbolType.Table);
if (node.partitions && node.partitions.length) {
for (let index = 0; index < node.partitions.length; index++) {
let symbol = this.addResolvedSymbol(context, node.partitions[index].toString(), ast.SqlSymbolType.Field);
symbol.parent = node.tableName;
node.partitions[index] = symbol;
}
}
return context;
};
this.visitAllColumns = (context, node) => {
node.table = this.lookupOrAddTable(context, node.table.toString());
return context;
};
this.visitColumnName = (context, node) => {
let parent = node.table ? this.lookupOrAddTable(context, node.table.toString()) : undefined;
node.table = parent;
let fieldSymbol = this.lookupOrAddSymbol(context, node.name.toString(), ast.SqlSymbolType.Field);
fieldSymbol.parent = parent;
fieldSymbol.state = parent ? parent.state : SymbolState.Unresolved;
node.name = fieldSymbol;
return context;
};
this.visitSimpleFunctionCall = (context, node) => {
node.name = this.addResolvedSymbol(context, node.name.toString(), ast.SqlSymbolType.Function);
if (node.args) {
for (let fe of node.args) {
if (this.shouldVisitNode(fe)) {
context = this.visitNode(context, fe);
}
}
}
return context;
};
this.visitAssignedTerm = (context, node) => {
context = this.visitNode(context, node.value);
if (node.variable) {
node.variable = this.addResolvedSymbol(context, node.variable.toString(), ast.SqlSymbolType.Variable);
}
return context;
};
this.visitAssignedExpressionAtom = (context, node) => {
context = this.visitNode(context, node.expression);
if (node.variable) {
node.variable = this.addResolvedSymbol(context, node.variable.toString(), ast.SqlSymbolType.Variable);
}
return context;
};
this.visitVariable = (context, node) => {
node.name = this.addResolvedSymbol(context, node.name.toString(), ast.SqlSymbolType.Variable);
return context;
};
this.visitSelectIntoFieldsExpression = (context, node) => {
for (let index = 0; index < node.fields.length; index++) {
node.fields[index] = this.addResolvedSymbol(context, node.fields[index].toString(), ast.SqlSymbolType.Variable);
}
return context;
};
this.visitAliasedTerm = (context, node) => {
if (!node.alias) {
return this.visitGenericNode(context, node);
}
context = this.visitNode(context, node.term);
let aliasSymbol = this.addResolvedSymbol(context, node.alias.toString(), ast.SqlSymbolType.Alias);
let nodeType = ast.SqlAstNode.getNodeType(node.term);
switch (nodeType) {
case "ColumnName":
aliasSymbol.parent = (node.term.name);
break;
case "TableSpec":
aliasSymbol.parent = (node.term.tableName);
break;
default:
break;
}
if (aliasSymbol.parent) {
aliasSymbol.state = aliasSymbol.parent.state;
}
node.alias = aliasSymbol;
return context;
};
}
validate() {
let context = this.visit();
for (let sym of context.symbolTable) {
context.collectedSymbols.push(sym);
}
return context.collectedSymbols;
}
addResolvedSymbol(context, name, type) {
let symbol = new SqlAstSymbol(name, type);
symbol.state = SymbolState.Resolved;
context.symbolTable.add(symbol);
return symbol;
}
lookupOrAddTable(context, name) {
let symbols = context.symbolTable.lookup(name);
if (symbols) {
for (let index = 0; index < symbols.length; index++) {
if (symbols[index].type === ast.SqlSymbolType.Alias
|| symbols[index].type === ast.SqlSymbolType.Table) {
return symbols[index];
}
}
}
let symbol = new SqlAstSymbol(name, ast.SqlSymbolType.Table);
symbol.state = SymbolState.Unresolved;
context.symbolTable.add(symbol);
return symbol;
}
lookupOrAddSymbol(context, name, type) {
let symbols = context.symbolTable.lookup(name, type);
if (symbols) {
if (symbols.length > 1) {
throw Error("Found more than one symbol of the same type in the current scope");
}
return symbols[0];
}
let symbol = new SqlAstSymbol(name, type);
symbol.state = SymbolState.Unresolved;
context.symbolTable.add(symbol);
return symbol;
}
}
exports.QueryValidator = QueryValidator;
class MySQLQueryValidator extends QueryValidator {
constructor(query) {
super("MySQL", query, ValidationContext);
}
}
exports.MySQLQueryValidator = MySQLQueryValidator;
//# sourceMappingURL=queryValidator.js.map