diff --git a/example/monaco_editor/.sqllsrc.json b/example/monaco_editor/.sqllsrc.json index 0ce72c88..546479a2 100644 --- a/example/monaco_editor/.sqllsrc.json +++ b/example/monaco_editor/.sqllsrc.json @@ -1,8 +1,5 @@ { - "adapter": "postgres", - "host": "postgres", - "port": 5432, - "user": "sqlls", - "password": "sqlls", - "database": "postgres_db" -} \ No newline at end of file + "name": "pyspark-conf", + "adapter": "json", + "filename": "testing.schema.json" +} diff --git a/example/monaco_editor/testing.schema.json b/example/monaco_editor/testing.schema.json new file mode 100644 index 00000000..d9670ebe --- /dev/null +++ b/example/monaco_editor/testing.schema.json @@ -0,0 +1,348 @@ +{ + "functions": [], + "tables": [ + { + "columns": [ + { + "columnName": "id", + "description": "integer", + "metadata": {}, + "type": "integer" + }, + { + "columnName": "name", + "description": "string", + "metadata": { + "description": "new description" + }, + "type": "string" + }, + { + "columnName": "age", + "description": "integer", + "metadata": {}, + "type": "integer" + }, + { + "columnName": "books", + "description": "array", + "metadata": {}, + "type": "array" + }, + { + "columnName": "struct_col", + "description": "struct", + "metadata": {}, + "type": "struct" + }, + { + "columnName": "map_col", + "description": "map", + "metadata": {}, + "type": "map" + } + ], + "database": "school", + "tableName": "student" + }, + { + "database": "school", + "tableName": "nested_table", + "columns": [ + { + "columnName": "col1", + "description": "the desc. text" + }, + { + "columnName": "struct_col", + "description": "the desc. text" + } + ] + }, + { + "database": "tales", + "tableName": "tbl1", + "columns": [ + { + "columnName": "one", + "description": "one(Type: varchar(10), Null: No, Default: null)" + }, + { + "columnName": "two", + "description": "two(Type: smallint, Null: No, Default: null)" + } + ] + }, + { + "database": "tales", + "tableName": "contacts", + "columns": [ + { + "columnName": "contact_id", + "description": "contact_id(Type: INTEGER, Null: No, Default: null)" + }, + { + "columnName": "first_name", + "description": "first_name(Type: TEXT, Null: Yes, Default: null)" + }, + { + "columnName": "last_name", + "description": "last_name(Type: TEXT, Null: Yes, Default: null)" + }, + { + "columnName": "email", + "description": "email(Type: TEXT, Null: Yes, Default: null)" + }, + { + "columnName": "phone", + "description": "phone(Type: TEXT, Null: Yes, Default: null)" + } + ] + }, + { + "database": null, + "tableName": "unqualified_table", + "columns": [ + { + "columnName": "group_id", + "description": "group_id(Type: INTEGER, Null: No, Default: null)" + }, + { + "columnName": "name", + "description": "name(Type: TEXT, Null: Yes, Default: null)" + } + ] + }, + { + "database": "tales", + "catalog": "db1", + "tableName": "extra_qualified_table", + "columns": [ + { + "columnName": "contact_id", + "description": "contact_id(Type: INTEGER, Null: No, Default: null)" + }, + { + "columnName": "group_id", + "description": "group_id(Type: INTEGER, Null: No, Default: null)" + } + ] + }, + { + "database": "tales", + "tableName": "COMPANY", + "columns": [ + { + "columnName": "ID", + "description": "ID(Type: INT, Null: Yes, Default: null)" + }, + { + "columnName": "NAME", + "description": "NAME(Type: TEXT, Null: Yes, Default: null)" + }, + { + "columnName": "AGE", + "description": "AGE(Type: INT, Null: Yes, Default: null)" + }, + { + "columnName": "ADDRESS", + "description": "ADDRESS(Type: CHAR(50), Null: No, Default: null)" + }, + { + "columnName": "SALARY", + "description": "SALARY(Type: REAL, Null: No, Default: null)" + } + ] + }, + { + "database": "tales", + "tableName": "employees", + "columns": [ + { + "columnName": "job_id", + "description": "" + }, + { + "columnName": "employee_id", + "description": "" + }, + { + "columnName": "manager_id", + "description": "" + }, + { + "columnName": "department_id", + "description": "" + }, + { + "columnName": "first_name", + "description": "" + }, + { + "columnName": "last_name", + "description": "" + }, + { + "columnName": "email", + "description": "" + }, + { + "columnName": "phone_number", + "description": "" + }, + { + "columnName": "hire_date", + "description": "" + }, + { + "columnName": "salary", + "description": "" + }, + { + "columnName": "commision_pct", + "description": "" + } + ] + }, + { + "database": "tales", + "tableName": "jobs", + "columns": [ + { + "columnName": "job_id", + "description": "" + }, + { + "columnName": "job_title", + "description": "" + }, + { + "columnName": "min_salary", + "description": "" + }, + { + "columnName": "max_salary", + "description": "" + }, + { + "columnName": "created_at", + "description": "" + }, + { + "columnName": "updated_at", + "description": "" + } + ] + }, + { + "database": "tales", + "tableName": "job_history", + "columns": [ + { + "columnName": "employee_id", + "description": "" + }, + { + "columnName": "start_date", + "description": "" + }, + { + "columnName": "end_date", + "description": "" + }, + { + "columnName": "job_id", + "description": "" + }, + { + "columnName": "department_id", + "description": "" + } + ] + }, + { + "database": "tales", + "tableName": "departments", + "columns": [ + { + "columnName": "department_id", + "description": "" + }, + { + "columnName": "department_name", + "description": "" + }, + { + "columnName": "manager_id", + "description": "" + }, + { + "columnName": "location_id", + "description": "" + } + ] + }, + { + "database": "tales", + "tableName": "locations", + "columns": [ + { + "columnName": "location_id", + "description": "" + }, + { + "columnName": "street_address", + "description": "" + }, + { + "columnName": "postal_code", + "description": "" + }, + { + "columnName": "city", + "description": "" + }, + { + "columnName": "state_province", + "description": "" + }, + { + "columnName": "country_id", + "description": "" + } + ] + }, + { + "database": "tales", + "tableName": "countries", + "columns": [ + { + "columnName": "country_id", + "description": "" + }, + { + "columnName": "country_name", + "description": "" + }, + { + "columnName": "region_id", + "description": "" + } + ] + }, + { + "database": "tales", + "tableName": "regions", + "columns": [ + { + "columnName": "region_id", + "description": "" + }, + { + "columnName": "region_name", + "description": "" + } + ] + } + ] +} diff --git a/packages/server/src/complete/Identifier.ts b/packages/server/src/complete/Identifier.ts index 2716aa5b..43273c7d 100644 --- a/packages/server/src/complete/Identifier.ts +++ b/packages/server/src/complete/Identifier.ts @@ -1,5 +1,4 @@ import { CompletionItem, CompletionItemKind } from 'vscode-languageserver-types' -import { makeTableAlias } from './StringUtils' export const ICONS = { KEYWORD: CompletionItemKind.Text, @@ -46,14 +45,12 @@ export class Identifier { const idx = this.lastToken.lastIndexOf('.') const label = this.identifier.substring(idx + 1) let kindName: string - let tableAlias = '' if (this.kind === ICONS.TABLE) { let tableName = label const i = tableName.lastIndexOf('.') if (i > 0) { tableName = label.substring(i + 1) } - tableAlias = this.onClause === 'FROM' ? makeTableAlias(tableName) : '' kindName = 'table' } else { kindName = 'column' @@ -66,11 +63,7 @@ export class Identifier { } if (this.kind === ICONS.TABLE) { - if (tableAlias) { - item.insertText = `${label} AS ${tableAlias}` - } else { - item.insertText = label - } + item.insertText = label } return item } diff --git a/packages/server/src/complete/candidates/createAliasCandidates.ts b/packages/server/src/complete/candidates/createAliasCandidates.ts index 9f1d9895..9d085553 100644 --- a/packages/server/src/complete/candidates/createAliasCandidates.ts +++ b/packages/server/src/complete/candidates/createAliasCandidates.ts @@ -7,7 +7,9 @@ export function createAliasCandidates( token: string ): CompletionItem[] { return fromNodes - .map((fromNode) => fromNode.as) + .map( + (fromNode) => fromNode.as ?? ('table' in fromNode ? fromNode.table : null) + ) .filter((aliasName) => aliasName && aliasName.startsWith(token)) .map((aliasName) => toCompletionItemForAlias(aliasName || '')) } diff --git a/packages/server/src/complete/candidates/createColumnCandidates.ts b/packages/server/src/complete/candidates/createColumnCandidates.ts index 7b87e4b9..63dd34e4 100644 --- a/packages/server/src/complete/candidates/createColumnCandidates.ts +++ b/packages/server/src/complete/candidates/createColumnCandidates.ts @@ -49,3 +49,41 @@ export function createCandidatesForScopedColumns( .filter((item) => item.matchesLastToken()) .map((item) => item.toCompletionItem()) } + +export function createCandidatesForUnscopedColumns( + fromNodes: FromTableNode[], + tables: Table[], + lastToken: string +): CompletionItem[] { + return tables + .flatMap((table) => { + if (fromNodes.length === 0) { + return table.columns.map((col) => { + return new Identifier( + lastToken, + makeColumnName('', col.columnName), + col.description, + ICONS.COLUMN + ) + }) + } + return fromNodes + .filter((fromNode) => isTableMatch(fromNode, table)) + .map(getAliasFromFromTableNode) + .flatMap((alias) => { + return table.columns.map((col) => { + return new Identifier( + lastToken, + makeColumnName( + alias === table.tableName ? '' : alias, + col.columnName + ), + col.description, + ICONS.COLUMN + ) + }) + }) + }) + .filter((item) => item.matchesLastToken()) + .map((item) => item.toCompletionItem()) +} diff --git a/packages/server/src/complete/candidates/createJoinCandidates.ts b/packages/server/src/complete/candidates/createJoinCandidates.ts index f3117bb3..b69cd8b9 100644 --- a/packages/server/src/complete/candidates/createJoinCandidates.ts +++ b/packages/server/src/complete/candidates/createJoinCandidates.ts @@ -4,7 +4,7 @@ import { getNearestFromTableFromPos } from '../AstUtils' import { Table } from '../../database_libs/AbstractClient' import { toCompletionItemForKeyword } from '../CompletionItemUtils' import { Pos } from '../complete' -import { createTableCandidates } from './createTableCandidates' +import { createCatalogDatabaseAndTableCandidates } from './createTableCandidates' export function createJoinCondidates( ast: SelectStatement, @@ -18,7 +18,7 @@ export function createJoinCondidates( const result: CompletionItem[] = [] const fromTable = getNearestFromTableFromPos(ast.from?.tables || [], pos) if (fromTable && fromTable.type === 'table') { - result.push(...createTableCandidates(tables, token, true)) + result.push(...createCatalogDatabaseAndTableCandidates(tables, token, true)) result.push(toCompletionItemForKeyword('INNER JOIN')) result.push(toCompletionItemForKeyword('LEFT JOIN')) result.push(toCompletionItemForKeyword('ON')) diff --git a/packages/server/src/complete/candidates/createTableCandidates.ts b/packages/server/src/complete/candidates/createTableCandidates.ts index f5429ef2..88d33242 100644 --- a/packages/server/src/complete/candidates/createTableCandidates.ts +++ b/packages/server/src/complete/candidates/createTableCandidates.ts @@ -9,25 +9,57 @@ import { ICONS } from '../CompletionItemUtils' * @param table * @returns */ -function allTableNameCombinations(table: Table): string[] { - const names = [table.tableName] - if (table.database) names.push(table.database + '.' + table.tableName) - if (table.catalog) - names.push(table.catalog + '.' + table.database + '.' + table.tableName) - return names +function getFullyQualifiedTableName(table: Table): string { + if (table.catalog && table.database) { + return table.catalog + '.' + table.database + '.' + table.tableName + } + if (table.database) { + return table.database + '.' + table.tableName + } + return table.tableName } -export function createTableCandidates( +export function createCatalogDatabaseAndTableCandidates( tables: Table[], lastToken: string, onFromClause?: boolean ) { - return tables - .flatMap((table) => allTableNameCombinations(table)) - .map((aTableNameVariant) => { + const qualificationLevel = lastToken.split('.').length - 1 + + const qualifiedEntities = tables.flatMap((table) => { + let qualificationNeeded = 0 + if (table.catalog) { + qualificationNeeded++ + } + if (table.database) { + qualificationNeeded++ + } + const qualificationLevelNeeded = qualificationNeeded - qualificationLevel + switch (qualificationLevelNeeded) { + case 0: + return [getFullyQualifiedTableName(table)] + case 1: + if (table.catalog && table.database) { + return [table.catalog + '.' + table.database] + } + if (table.database) { + return [table.database] + } + break + case 2: + if (table.catalog) { + return [table.catalog] + } + break + } + return [] + }) + + return qualifiedEntities + .map((databaseEntity) => { return new Identifier( lastToken, - aTableNameVariant, + databaseEntity, '', ICONS.TABLE, onFromClause ? 'FROM' : 'OTHERS' diff --git a/packages/server/src/complete/complete.ts b/packages/server/src/complete/complete.ts index 0160ed44..3bec93db 100644 --- a/packages/server/src/complete/complete.ts +++ b/packages/server/src/complete/complete.ts @@ -11,7 +11,6 @@ import { AST, AlterTableStatement, } from '@joe-re/sql-parser' -import log4js from 'log4js' import { CompletionItem } from 'vscode-languageserver-types' import { Schema, Table } from '../database_libs/AbstractClient' import { getRidOfAfterPosString } from './StringUtils' @@ -24,11 +23,12 @@ import { getNearestFromTableFromPos, } from './AstUtils' import { createBasicKeywordCandidates } from './candidates/createBasicKeywordCandidates' -import { createTableCandidates } from './candidates/createTableCandidates' +import { createCatalogDatabaseAndTableCandidates } from './candidates/createTableCandidates' import { createJoinCondidates } from './candidates/createJoinCandidates' import { createCandidatesForColumnsOfAnyTable, createCandidatesForScopedColumns, + createCandidatesForUnscopedColumns, } from './candidates/createColumnCandidates' import { createAliasCandidates } from './candidates/createAliasCandidates' import { createSelectAllColumnsCandidates } from './candidates/createSelectAllColumnsCandidates' @@ -39,7 +39,15 @@ import { ICONS, toCompletionItemForKeyword } from './CompletionItemUtils' export type Pos = { line: number; column: number } -const logger = log4js.getLogger() +// stubbing logger to make the lib work in browser +const logger = { + isDebugEnabled: function () { + return false + }, + debug: function (_: unknown) { + return undefined + }, +} function getFromNodesFromClause(sql: string): FromClauseParserResult | null { try { @@ -172,11 +180,13 @@ class Completer { } addCandidatesForTables(tables: Table[], onFromClause: boolean) { - createTableCandidates(tables, this.lastToken, onFromClause).forEach( - (item) => { - this.addCandidate(item) - } - ) + createCatalogDatabaseAndTableCandidates( + tables, + this.lastToken, + onFromClause + ).forEach((item) => { + this.addCandidate(item) + }) } addCandidatesForColumnsOfAnyTable(tables: Table[]) { @@ -316,9 +326,9 @@ class Completer { this.addCandidatesForScopedColumns(fromNodes, schemaAndSubqueries) } else { // Column is not scoped to a table/alias yet - // Could be an alias, a talbe or a function + // Could be an alias or an unscoped column + this.addCandidatesForUnscopedColumns(fromNodes, schemaAndSubqueries) this.addCandidatesForAliases(fromNodes) - this.addCandidatesForTables(schemaAndSubqueries, true) this.addCandidatesForFunctions() } } @@ -385,6 +395,16 @@ class Completer { console.timeEnd('addCandidatesForScopedColumns') } + addCandidatesForUnscopedColumns(fromNodes: FromTableNode[], tables: Table[]) { + createCandidatesForUnscopedColumns( + fromNodes, + tables, + this.lastToken + ).forEach((v) => { + this.addCandidate(v) + }) + } + addCandidatesForAliases(fromNodes: FromTableNode[]) { createAliasCandidates(fromNodes, this.lastToken).forEach((v) => { this.addCandidate(v) diff --git a/packages/server/src/createServer.ts b/packages/server/src/createServer.ts index 81783952..b758e094 100644 --- a/packages/server/src/createServer.ts +++ b/packages/server/src/createServer.ts @@ -189,7 +189,7 @@ export function createServerWithConnection( } else if (rootPath) { SettingStore.getInstance().setSettingFromFile( `${process.env.HOME}/.config/sql-language-server/.sqllsrc.json`, - `${rootPath}/.sqllsrc.json`, + `.sqllsrc.json`, rootPath || '' ) } diff --git a/packages/server/test/complete.test.ts b/packages/server/test/complete.test.ts index fd3eadb5..637089b1 100644 --- a/packages/server/test/complete.test.ts +++ b/packages/server/test/complete.test.ts @@ -65,7 +65,7 @@ describe('keyword completion', () => { test("complete 'WHERE' keyword multi-line", () => { const result = complete( ` - SELECT * + SELECT * FROM FOO AS foo W `, @@ -220,7 +220,7 @@ describe('From clause', () => { ) expect(result.candidates.length).toEqual(1) expect(result.candidates[0].label).toEqual('TABLE1') - expect(result.candidates[0].insertText).toEqual('TABLE1 AS TAB') + expect(result.candidates[0].insertText).toEqual('TABLE1') }) test('from clause: complete TableName:multi lines', () => { @@ -277,8 +277,12 @@ describe('Where clause', () => { { line: 0, column: 46 }, SIMPLE_SCHEMA ) - expect(result.candidates.length).toEqual(1) - expect(result.candidates[0].label).toEqual('tab') + const expected = [ + expect.objectContaining({ label: 'tab' }), + expect.objectContaining({ label: 'tab.COLUMN1' }), + expect.objectContaining({ label: 'tab.COLUMN2' }), + ] + expect(result.candidates).toEqual(expect.arrayContaining(expected)) }) }) @@ -343,13 +347,15 @@ describe('cursor on dot', () => { expect(result.candidates[0].label).toEqual('COLUMN2') }) - test('not complete when ', () => { + test('suggest columns from table in the FROM clause', () => { const result = complete( - 'SELECT FROM TABLE1', + 'SELECT C FROM TABLE1', { line: 0, column: 8 }, SIMPLE_SCHEMA ) - expect(result.candidates.length).toEqual(13) // TODO what are they? + expect(result.candidates.length).toEqual(2) + expect(result.candidates[0].label).toEqual('COLUMN1') + expect(result.candidates[1].label).toEqual('COLUMN2') }) }) @@ -390,7 +396,7 @@ describe('JOIN', () => { test('from clause: INNER JOIN', () => { const result = complete( ` - SELECT + SELECT * FROM TABLE1 AS a INN`, @@ -415,9 +421,7 @@ describe('Fully qualified table names', () => { SIMPLE_NESTED_SCHEMA ) expect(result.candidates.length).toEqual(1) - const expected = [ - expect.objectContaining({ label: 'catalog3.schema3.table3' }), - ] + const expected = [expect.objectContaining({ label: 'catalog3' })] expect(result.candidates).toEqual(expect.arrayContaining(expected)) }) test('complete schema name', () => { @@ -427,7 +431,7 @@ describe('Fully qualified table names', () => { SIMPLE_NESTED_SCHEMA ) expect(result.candidates.length).toEqual(1) - const expected = [expect.objectContaining({ label: 'schema3.table3' })] + const expected = [expect.objectContaining({ label: 'schema3' })] expect(result.candidates).toEqual(expect.arrayContaining(expected)) }) test('complete table name', () => { @@ -450,18 +454,14 @@ describe('Fully qualified table names', () => { const expected = [expect.objectContaining({ label: 'table3' })] expect(result.candidates).toEqual(expect.arrayContaining(expected)) }) - test('complete table name by using fully qualified table name', () => { + + test('not complete table name when not qualified', () => { const result = complete( 'SELECT * FROM tabl', { line: 0, column: 18 }, SIMPLE_NESTED_SCHEMA ) - expect(result.candidates.length).toEqual(2) - const expected = [ - expect.objectContaining({ label: 'table2' }), - expect.objectContaining({ label: 'table3' }), - ] - expect(result.candidates).toEqual(expect.arrayContaining(expected)) + expect(result.candidates.length).toEqual(0) }) test('complete alias when table', () => { const result = complete( @@ -469,7 +469,6 @@ describe('Fully qualified table names', () => { { line: 0, column: 8 }, SIMPLE_NESTED_SCHEMA ) - expect(result.candidates.length).toEqual(1) const expected = [expect.objectContaining({ label: 'ali' })] expect(result.candidates).toEqual(expect.arrayContaining(expected)) }) @@ -479,7 +478,6 @@ describe('Fully qualified table names', () => { { line: 0, column: 8 }, SIMPLE_NESTED_SCHEMA ) - expect(result.candidates.length).toEqual(1) const expected = [expect.objectContaining({ label: 'ali' })] expect(result.candidates).toEqual(expect.arrayContaining(expected)) }) @@ -489,7 +487,6 @@ describe('Fully qualified table names', () => { { line: 0, column: 8 }, SIMPLE_NESTED_SCHEMA ) - expect(result.candidates.length).toEqual(1) const expected = [expect.objectContaining({ label: 'ali' })] expect(result.candidates).toEqual(expect.arrayContaining(expected)) }) @@ -499,7 +496,6 @@ describe('Fully qualified table names', () => { { line: 0, column: 11 }, SIMPLE_NESTED_SCHEMA ) - expect(result.candidates.length).toEqual(1) const expected = [expect.objectContaining({ label: 'abc' })] expect(result.candidates).toEqual(expect.arrayContaining(expected)) }) @@ -510,11 +506,8 @@ describe('Fully qualified table names', () => { { line: 0, column: 17 }, SIMPLE_NESTED_SCHEMA ) - expect(result.candidates.length).toEqual(2) - const expected = [ - expect.objectContaining({ label: 'schema2.table2' }), - expect.objectContaining({ label: 'schema3.table3' }), - ] + expect(result.candidates.length).toEqual(1) + const expected = [expect.objectContaining({ label: 'schema2' })] expect(result.candidates).toEqual(expect.arrayContaining(expected)) }) test('complete table name', () => { @@ -533,7 +526,6 @@ describe('Fully qualified table names', () => { { line: 0, column: 8 }, SIMPLE_NESTED_SCHEMA ) - expect(result.candidates.length).toEqual(1) const expected = [expect.objectContaining({ label: 'ali' })] expect(result.candidates).toEqual(expect.arrayContaining(expected)) }) @@ -543,18 +535,16 @@ describe('Fully qualified table names', () => { { line: 0, column: 8 }, SIMPLE_NESTED_SCHEMA ) - expect(result.candidates.length).toEqual(1) const expected = [expect.objectContaining({ label: 'ali' })] expect(result.candidates).toEqual(expect.arrayContaining(expected)) }) - test('complete alias when catalog.schema.table', () => { + test('complete aliased+column when schema.table', () => { const result = complete( 'SELECT a FROM schema2.table2 AS ali', { line: 0, column: 8 }, SIMPLE_NESTED_SCHEMA ) - expect(result.candidates.length).toEqual(1) - const expected = [expect.objectContaining({ label: 'ali' })] + const expected = [expect.objectContaining({ label: 'ali.abc' })] expect(result.candidates).toEqual(expect.arrayContaining(expected)) }) test('complete aliased column name', () => { diff --git a/packages/server/test/complete/Identifier.test.ts b/packages/server/test/complete/Identifier.test.ts index 3b192eac..67dc8787 100644 --- a/packages/server/test/complete/Identifier.test.ts +++ b/packages/server/test/complete/Identifier.test.ts @@ -35,13 +35,6 @@ describe('Identifier', () => { }) describe('ICONS.TABLE', () => { - test('Add alias if it is onFromClause', () => { - const item = new Identifier('T', 'TABLE1', '', ICONS.TABLE, 'FROM') - const completion = item.toCompletionItem() - expect(completion.label).toEqual('TABLE1') - expect(completion.insertText).toEqual('TABLE1 AS TAB') - }) - test("Doesn't add alias if it isn't onFromClause", () => { const item = new Identifier('T', 'TABLE1', '', ICONS.TABLE, 'OTHERS') const completion = item.toCompletionItem() diff --git a/packages/server/test/complete/complete_column.test.ts b/packages/server/test/complete/complete_column.test.ts index 9781f9fd..59d84e18 100644 --- a/packages/server/test/complete/complete_column.test.ts +++ b/packages/server/test/complete/complete_column.test.ts @@ -31,7 +31,11 @@ describe('ColumnName completion', () => { { line: 0, column: 10 }, SIMPLE_SCHEMA ) - expect(result.candidates.length).toEqual(0) + const expected = [ + expect.objectContaining({ label: 'COLUMN1' }), + expect.objectContaining({ label: 'COLUMN2' }), + ] + expect(result.candidates).toEqual(expect.arrayContaining(expected)) }) test('complete ColumnName', () => { diff --git a/packages/server/test/complete/complete_table.test.ts b/packages/server/test/complete/complete_table.test.ts index cc1234b6..80bb2395 100644 --- a/packages/server/test/complete/complete_table.test.ts +++ b/packages/server/test/complete/complete_table.test.ts @@ -63,8 +63,10 @@ describe('TableName completion', () => { { line: 0, column: 9 }, SIMPLE_SCHEMA ) - expect(result.candidates.length).toEqual(1) - expect(result.candidates[0].label).toEqual('tab') + expect(result.candidates.length).toEqual(3) + expect(result.candidates).toEqual( + expect.arrayContaining([expect.objectContaining({ label: 'tab' })]) + ) }) test('complete SELECT star', () => { const result = complete( @@ -102,7 +104,6 @@ describe('TableName completion', () => { { line: 0, column: 7 }, SIMPLE_SCHEMA ) - expect(result.candidates.length).toEqual(13) const expected = [ expect.objectContaining({ label: 'Select all columns from TABLE1',