diff --git a/packages/server/src/complete/complete.ts b/packages/server/src/complete/complete.ts index f73d4e52..20de6338 100644 --- a/packages/server/src/complete/complete.ts +++ b/packages/server/src/complete/complete.ts @@ -255,20 +255,13 @@ class Completer { this.addCandidatesForExpectedLiterals(expectedLiteralNodes) this.addCandidatesForFunctions() - const { addedSome: addedSomeScopedColumnCandidates } = - this.addCandidatesForScopedColumns(fromNodes, schemaAndSubqueries) - if (!addedSomeScopedColumnCandidates) { - this.addCandidatesForUnscopedColumns(fromNodes, schemaAndSubqueries) - } - - this.addCandidatesForAliases(fromNodes) - + // Detect FROM clause context BEFORE adding column suggestions const fromNodesContainingCursor = fromNodes.filter((tableNode) => isPosInLocation(tableNode.location, this.pos) ) const isCursorInsideFromClause = fromNodesContainingCursor.length > 0 - // Check if cursor is right after FROM keyword (no table typed yet) + // Check if cursor is right after FROM keyword or typing a table name const afterFromClause = parsedFromClause.after?.trim().toUpperCase() || '' const isCursorAfterFromKeyword = afterFromClause === 'FROM' || @@ -277,7 +270,21 @@ class Completer { afterFromClause ) - if (isCursorInsideFromClause || isCursorAfterFromKeyword) { + const isTypingTableName = + isCursorInsideFromClause || isCursorAfterFromKeyword + + if (!isTypingTableName) { + const { addedSome: addedSomeScopedColumnCandidates } = + this.addCandidatesForScopedColumns(fromNodes, schemaAndSubqueries) + + if (!addedSomeScopedColumnCandidates) { + this.addCandidatesForUnscopedColumns(fromNodes, schemaAndSubqueries) + } + } + + this.addCandidatesForAliases(fromNodes) + + if (isTypingTableName) { // add table candidates if the cursor is inside a FROM clause, JOIN clause, // or right after FROM/JOIN keyword waiting for a table name this.addCandidatesForTables(schemaAndSubqueries, true) diff --git a/packages/server/test/complete/complete_table.test.ts b/packages/server/test/complete/complete_table.test.ts index 80bb2395..dd2a05b1 100644 --- a/packages/server/test/complete/complete_table.test.ts +++ b/packages/server/test/complete/complete_table.test.ts @@ -112,4 +112,41 @@ describe('TableName completion', () => { ] expect(result.candidates).toEqual(expect.arrayContaining(expected)) }) + + test('complete table name after FROM keyword with partial input', () => { + const schema = { + tables: [ + { + catalog: null, + database: null, + tableName: 'notes', + columns: [{ columnName: 'id', description: '' }], + }, + { + catalog: null, + database: null, + tableName: 'users', + columns: [{ columnName: 'name', description: '' }], + }, + ], + functions: [], + } + + const result = complete( + 'SELECT * FROM not', + { line: 0, column: 17 }, + schema + ) + + // Should suggest table names + const labels = result.candidates.map((c) => c.label) + expect(labels).toContain('notes') + + // Should NOT include any column suggestions (even if qualified) + expect(labels).not.toEqual(expect.arrayContaining(['id', 'name'])) + + // All candidates should be tables (CompletionItemKind.Constant = 21) + const TABLE_KIND = 21 + expect(result.candidates.every((c) => c.kind === TABLE_KIND)).toBe(true) + }) })