UNPKG

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
"use strict"; 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