rawsql-ts
Version:
[beta]High-performance SQL parser and AST analyzer written in TypeScript. Provides fast parsing and advanced transformation capabilities.
272 lines • 11.5 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
exports.ScopeResolver = void 0;
const SelectQuery_1 = require("../models/SelectQuery");
const Clause_1 = require("../models/Clause");
const CTECollector_1 = require("../transformers/CTECollector");
const TextPositionUtils_1 = require("./TextPositionUtils");
/**
* Resolves scope information at cursor positions for SQL IntelliSense
*
* Provides comprehensive scope analysis including table availability, CTE resolution,
* and column visibility for intelligent code completion suggestions.
*
* @example
* ```typescript
* const sql = `
* WITH users AS (SELECT id, name FROM accounts)
* SELECT u.name FROM users u
* LEFT JOIN orders o ON u.id = o.user_id
* WHERE u.|
* `;
* const scope = ScopeResolver.resolveAt(sql, { line: 4, column: 12 });
*
* console.log(scope.availableTables); // [{ name: 'users', alias: 'u' }, { name: 'orders', alias: 'o' }]
* console.log(scope.availableCTEs); // [{ name: 'users', columns: ['id', 'name'] }]
* ```
*/
class ScopeResolver {
/**
* Resolve scope information at the specified cursor position
*
* @param sql - SQL text to analyze
* @param cursorPosition - Character position of cursor (0-based)
* @returns Complete scope information
*/
static resolve(sql, cursorPosition) {
// Simplified for suggestion-only focus - return basic scope information
// Complex SQL parsing removed to avoid issues with incomplete SQL syntax
return this.createEmptyScope();
}
/**
* Resolve scope information at line/column position
*
* @param sql - SQL text to analyze
* @param position - Line and column position (1-based)
* @returns Complete scope information
*/
static resolveAt(sql, position) {
const charOffset = TextPositionUtils_1.TextPositionUtils.lineColumnToCharOffset(sql, position);
if (charOffset === -1) {
return this.createEmptyScope();
}
return this.resolve(sql, charOffset);
}
/**
* Get available columns for a specific table or alias
*
* @param sql - SQL text containing the query
* @param cursorPosition - Cursor position for scope resolution
* @param tableOrAlias - Table name or alias to get columns for
* @returns Array of available columns for the specified table
*/
static getColumnsForTable(sql, cursorPosition, tableOrAlias) {
const scope = this.resolve(sql, cursorPosition);
// Find matching table
const table = scope.availableTables.find(t => t.name === tableOrAlias || t.alias === tableOrAlias);
if (!table) {
return [];
}
// Return columns for this table
return scope.visibleColumns.filter(col => col.tableName === table.name ||
(table.alias && col.tableAlias === table.alias));
}
static analyzeScopeFromQuery(query) {
const scope = {
availableTables: [],
availableCTEs: [],
subqueryLevel: 0,
visibleColumns: [],
currentQuery: query,
parentQueries: []
};
if (query instanceof SelectQuery_1.SimpleSelectQuery) {
// Collect CTEs
scope.availableCTEs = this.collectCTEs(query);
// Collect tables from FROM and JOINs
scope.availableTables = this.collectTablesFromQuery(query);
// Collect visible columns
scope.visibleColumns = this.collectVisibleColumns(scope.availableTables, scope.availableCTEs);
}
else if (query instanceof SelectQuery_1.BinarySelectQuery) {
// For UNION queries, analyze both sides
const leftScope = this.analyzeScopeFromQuery(query.left);
const rightScope = this.analyzeScopeFromQuery(query.right);
// Merge scopes (tables from both sides available)
scope.availableTables = [...leftScope.availableTables, ...rightScope.availableTables];
scope.availableCTEs = [...leftScope.availableCTEs, ...rightScope.availableCTEs];
scope.visibleColumns = [...leftScope.visibleColumns, ...rightScope.visibleColumns];
}
return scope;
}
static collectCTEs(query) {
const ctes = [];
if (query.withClause) {
const cteCollector = new CTECollector_1.CTECollector();
const collectedCTEs = cteCollector.collect(query);
for (const cte of collectedCTEs) {
ctes.push({
name: cte.getSourceAliasName(),
query: cte.query,
columns: this.extractCTEColumns(cte.query),
materialized: cte.materialized || false
});
}
}
return ctes;
}
static collectTablesFromQuery(query) {
const tables = [];
// Collect from FROM clause
if (query.fromClause) {
const fromTables = this.extractTablesFromFromClause(query.fromClause);
tables.push(...fromTables);
}
return tables;
}
static extractTablesFromFromClause(fromClause) {
var _a, _b, _c, _d;
const tables = [];
// Extract main source table
if (fromClause.source.datasource instanceof Clause_1.TableSource) {
const table = {
name: this.extractTableName(fromClause.source.datasource.qualifiedName),
alias: (_a = fromClause.source.aliasExpression) === null || _a === void 0 ? void 0 : _a.table.name,
schema: this.extractSchemaName(fromClause.source.datasource.qualifiedName),
fullName: this.getQualifiedNameString(fromClause.source.datasource.qualifiedName),
sourceType: 'table'
};
tables.push(table);
}
else if (fromClause.source.datasource instanceof Clause_1.SubQuerySource) {
const table = {
name: ((_b = fromClause.source.aliasExpression) === null || _b === void 0 ? void 0 : _b.table.name) || 'subquery',
alias: (_c = fromClause.source.aliasExpression) === null || _c === void 0 ? void 0 : _c.table.name,
fullName: ((_d = fromClause.source.aliasExpression) === null || _d === void 0 ? void 0 : _d.table.name) || 'subquery',
sourceType: 'subquery',
originalQuery: fromClause.source.datasource.query
};
tables.push(table);
}
// Collect from JOINs
if (fromClause.joins) {
for (const join of fromClause.joins) {
const joinTables = this.extractTablesFromJoin(join);
tables.push(...joinTables);
}
}
return tables;
}
static extractTablesFromJoin(join) {
var _a, _b, _c, _d;
const tables = [];
if (join.source.datasource instanceof Clause_1.TableSource) {
const table = {
name: this.extractTableName(join.source.datasource.qualifiedName),
alias: (_a = join.source.aliasExpression) === null || _a === void 0 ? void 0 : _a.table.name,
schema: this.extractSchemaName(join.source.datasource.qualifiedName),
fullName: this.getQualifiedNameString(join.source.datasource.qualifiedName),
sourceType: 'table'
};
tables.push(table);
}
else if (join.source.datasource instanceof Clause_1.SubQuerySource) {
const table = {
name: ((_b = join.source.aliasExpression) === null || _b === void 0 ? void 0 : _b.table.name) || 'subquery',
alias: (_c = join.source.aliasExpression) === null || _c === void 0 ? void 0 : _c.table.name,
fullName: ((_d = join.source.aliasExpression) === null || _d === void 0 ? void 0 : _d.table.name) || 'subquery',
sourceType: 'subquery',
originalQuery: join.source.datasource.query
};
tables.push(table);
}
return tables;
}
static getQualifiedNameString(qualifiedName) {
// Use the existing method from QualifiedName to get the string representation
return qualifiedName.toString();
}
static extractTableName(qualifiedName) {
const fullName = this.getQualifiedNameString(qualifiedName);
const parts = fullName.split('.');
return parts[parts.length - 1]; // Last part is table name
}
static extractSchemaName(qualifiedName) {
const fullName = this.getQualifiedNameString(qualifiedName);
const parts = fullName.split('.');
return parts.length > 1 ? parts[parts.length - 2] : undefined;
}
static extractCTEColumns(query) {
// Try to extract column names from CTE SELECT clause
try {
if (query instanceof SelectQuery_1.SimpleSelectQuery && query.selectClause) {
const columns = [];
for (const item of query.selectClause.items) {
// Use alias if available, otherwise try to extract from expression
if (item.identifier) {
columns.push(item.identifier.name);
}
else {
// Try to extract column name from expression
const columnName = this.extractColumnNameFromExpression(item.value);
if (columnName) {
columns.push(columnName);
}
}
}
return columns;
}
}
catch (error) {
// If extraction fails, return undefined
}
return undefined;
}
static extractColumnNameFromExpression(expression) {
// Simple extraction - can be enhanced based on ValueComponent types
if (expression && typeof expression === 'object' && 'value' in expression) {
return expression.value;
}
return undefined;
}
static collectVisibleColumns(tables, ctes) {
const columns = [];
// Add columns from CTEs
for (const cte of ctes) {
if (cte.columns) {
for (const columnName of cte.columns) {
columns.push({
name: columnName,
tableName: cte.name,
fullReference: `${cte.name}.${columnName}`
});
}
}
}
// For regular tables, we would need schema information to determine columns
// This is a placeholder - in practice, this would integrate with database metadata
for (const table of tables) {
if (table.sourceType === 'table') {
// Placeholder - would query database schema
columns.push({
name: '*',
tableName: table.name,
tableAlias: table.alias,
fullReference: `${table.alias || table.name}.*`
});
}
}
return columns;
}
static createEmptyScope() {
return {
availableTables: [],
availableCTEs: [],
subqueryLevel: 0,
visibleColumns: [],
parentQueries: []
};
}
}
exports.ScopeResolver = ScopeResolver;
//# sourceMappingURL=ScopeResolver.js.map
;