Repository navigation
feat(editor): complete columns of every CTE shape and ORDER BY aliases - #3713
Conversation
# Conflicts: # apple/Packages/TableProCore/Sources/TableProAutocomplete/SQLContextAnalyzer.swift # apple/Packages/TableProCore/Tests/TableProAutocompleteTests/SQLCompletionProviderCatalogTests.swift # apple/TablePro/Core/Autocomplete/CompletionEngine.swift
J2TeamNNL
left a comment
There was a problem hiding this comment.
Review findings, mostly on completion scoping. Items 2 and 3 are worth checking first.
| continue | ||
| if nextCTE < ctes.count, ctes[nextCTE].open == index { | ||
| let cte = ctes[nextCTE] | ||
| found.append((DerivedTable(alias: cte.name, projection: cte.projection, isCommonTableExpression: true), depth)) |
There was a problem hiding this comment.
visibleTables returns every CTE of the WITH list, including the CTE whose own body holds the cursor and CTEs defined after it. In WITH orders AS (SELECT id FROM orders WHERE |) SELECT ... the inner orders is the real table, but mergeDerivedTables swaps it for the CTE projection, so completion offers only id. collect appends every CTE to found unconditionally, so the in-scope rule applies to projections but not to visibleTables.
| default: | ||
| return nil | ||
| } | ||
| if stream.isWord(index, "ONLY") { index += 1 } |
There was a problem hiding this comment.
modifiedTable treats the first word after UPDATE as the table. UPDATE OR REPLACE users SET | (SQLite), UPDATE LOW_PRIORITY t SET and UPDATE TOP (n) t SET resolve the target to OR, LOW_PRIORITY or TOP. .set now prefers context.targetTable over tableReferences.first, so no columns are offered. This is a regression. path() rejects IGNORE through nonAliasWords but not these.
| } | ||
| for cteName in cteNames where present.insert(cteName.lowercased()).inserted { | ||
| references.append(TableReference(tableName: cteName, alias: nil)) | ||
| references.append(TableReference( |
There was a problem hiding this comment.
Every visible CTE is appended to tableReferences with its columns, even when the query never reads it. WITH a AS (SELECT x FROM t), b AS (SELECT y FROM u) SELECT | FROM a now offers y from b. Only derived tables used in FROM or JOIN should add unqualified columns. Unreferenced CTEs should stay resolvable for b. and FROM completion only.
| ]) | ||
|
|
||
| // Innermost first, so a nearer definition shadows an outer one of the same name. | ||
| func visibleTables(in stream: SQLTokenStream, cursor: Int) -> [DerivedTable] { |
There was a problem hiding this comment.
The whole statement is re-tokenized and every definition's projection is recomputed on each completion request, and several projections are computed twice (collect calls withList, mainStatement calls it again per inner body, definition and collect repeat one level down). This runs on every keystroke. Cache projections per call or project only what the cursor scope uses.
| } | ||
|
|
||
| // CTEs were bound to their own scope by the parser, so what is left to expand is a table the catalog knows. | ||
| private func derivedColumns(of reference: TableReference) async -> [String] { |
There was a problem hiding this comment.
derivedColumns(of:) awaits the catalog sequentially per table, and columnItems(for:) calls it once per reference. The same tables are queried again in allColumnsInScope. N derived tables using * mean N sequential awaits plus repeated fetches on every SELECT-list or WHERE completion. Use a TaskGroup or batch the lookups and reuse the results.
| } | ||
|
|
||
| // The open parentheses around `cursor`, outermost first. | ||
| func enclosingParens(before cursor: Int) -> [Int] { |
There was a problem hiding this comment.
The statement-body check in targetTable accepts a column-list paren whose first column is named like a body keyword. INSERT INTO t (values, |, (table, | or (update, | makes isStatementBody true for the column list, targetTable finds no statement and returns nil, and the result falls back to tableReferences.first. Check the token before the paren so INSERT column lists are excluded.
| let isCommonTableExpression: Bool | ||
|
|
||
| // What the statement text names, before the catalog expands a `*`. | ||
| var columns: [String] { |
There was a problem hiding this comment.
DerivedTable.columns has no production caller. The analyzer and provider use projection, so this property only exists for tests and can drift from projection.columns(expanding:). Delete it and have tests call projection.columns { _ in nil }.
| while i < end { | ||
| if let skipped = skipNonCode(in: ns, at: i, limit: end) { | ||
| i = skipped | ||
| // MARK: - Definitions |
There was a problem hiding this comment.
About 58 new // lines restate what the code does, for example // MARK: - Definitions and the one above nextFromItem. AGENTS.md says code comments say why, never what. Remove the ones that only describe the code beneath them.
|
Fixes for the review findings, in J2TeamNNL@559b114
Checks: the autocomplete sources and tests compile with Xcode 26.3 in a scratch build, and all 441 tests pass. The app target was not built and SwiftLint was not run. To take it: |
Summary
AS MATERIALIZEDandAS NOT MATERIALIZEDbodies,INSERT/UPDATE/DELETE/MERGE ... RETURNING,VALUESwith a column list,TABLE tand nestedWITHbodies.*andt.*in a CTE or derived table, andRETURNING *, expand to the table's columns from the schema cache.RETURNING *adds theUPDATE ... FROMandDELETE ... USINGtables, and aUSINGjoin lists its join columns once, as PostgreSQL 17 returns them.WITH c (a, b) AS,AS x(a, b)) renames by position and keeps the rest. Implicit aliases (count(*) total) andLATERALderived tables resolve.WITHIN GROUP.SETand anINSERTcolumn list read the table the statement writes, not the first table found in the statement:UPDATE t SET .. FROM u,INSERT INTO t (..) SELECT .. FROM s, a CTE body,UPDATE o SET .. FROM orders o.DerivedTableParserreads the analyzer's lexed code view as tokens, so a dollar quote, a backslash escape or a comment no longer breaks paren matching. CTE names come from it instead of two regexes, and a CTE binds only where it is in scope.LATERALis no longer read as a table, which fetched columns for a table named LATERAL on each keystroke.Test plan
TableProAutocompleteTests(swift test):DerivedTableParserTestsper shape,SQLContextAnalyzerStatementShapeTests, provider cases against a stub catalog.Docs: TableProApp/docs#71
Fixes #3608