Skip to content

Commit 933b982

Browse files
authored
support alter table syntax (joe-re#124)
* parse for alter table add column * parse for alter table drop column * parse for alter table modify column * alter table keyword completion * complete table alias only for SELECT clause * ALTER COLUMN completion
1 parent af9f5a9 commit 933b982

File tree

13 files changed

+2052
-681
lines changed

13 files changed

+2052
-681
lines changed

packages/server/src/complete/Identifier.ts

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,22 +10,26 @@ export const ICONS = {
1010
UTILITY: CompletionItemKind.Event,
1111
}
1212

13+
type OnClause = 'FROM' | 'ALTER TABLE' | 'OTHERS'
1314
export class Identifier {
1415
lastToken: string
1516
identifier: string
1617
detail: string
1718
kind: CompletionItemKind
19+
onClause: OnClause
1820

1921
constructor(
2022
lastToken: string,
2123
identifier: string,
2224
detail: string,
23-
kind: CompletionItemKind
25+
kind: CompletionItemKind,
26+
onClause?: OnClause
2427
) {
2528
this.lastToken = lastToken
2629
this.identifier = identifier
27-
this.detail = detail || ''
30+
this.detail = detail ?? ''
2831
this.kind = kind
32+
this.onClause = onClause ?? 'OTHERS'
2933
}
3034

3135
matchesLastToken(): boolean {
@@ -49,7 +53,7 @@ export class Identifier {
4953
if (i > 0) {
5054
tableName = label.substring(i + 1)
5155
}
52-
tableAlias = makeTableAlias(tableName)
56+
tableAlias = this.onClause === 'FROM' ? makeTableAlias(tableName) : ''
5357
kindName = 'table'
5458
} else {
5559
kindName = 'column'
@@ -61,8 +65,12 @@ export class Identifier {
6165
kind: this.kind,
6266
}
6367

64-
if (this.kind == ICONS.TABLE) {
65-
item.insertText = `${label} AS ${tableAlias}`
68+
if (this.kind === ICONS.TABLE) {
69+
if (tableAlias) {
70+
item.insertText = `${label} AS ${tableAlias}`
71+
} else {
72+
item.insertText = label
73+
}
6674
}
6775
return item
6876
}

packages/server/src/complete/candidates/createColumnCandidates.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ export function createCandidatesForColumnsOfAnyTable(
1717
lastToken,
1818
column.columnName,
1919
column.description,
20-
ICONS.TABLE
20+
ICONS.TABLE,
21+
'FROM'
2122
)
2223
})
2324
.filter((item) => item.matchesLastToken())

packages/server/src/complete/candidates/createJoinCandidates.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ export function createJoinCondidates(
1818
const result: CompletionItem[] = []
1919
const fromTable = getNearestFromTableFromPos(ast.from?.tables || [], pos)
2020
if (fromTable && fromTable.type === 'table') {
21-
result.push(...createTableCandidates(tables, token))
21+
result.push(...createTableCandidates(tables, token, true))
2222
result.push(toCompletionItemForKeyword('INNER JOIN'))
2323
result.push(toCompletionItemForKeyword('LEFT JOIN'))
2424
result.push(toCompletionItemForKeyword('ON'))

packages/server/src/complete/candidates/createKeywordCandidatesFromExpectedLiterals.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,12 @@ export function createKeywordCandidatesFromExpectedLiterals(
4040
return 'RIGHT JOIN'
4141
case 'INNER':
4242
return 'INNER JOIN'
43+
case 'ALTER':
44+
if (nodes.find((v) => v.text === 'ADD')) {
45+
// if 'ADD' is includes on candidates, it should be for "ALTER COLUMN"
46+
return 'ALTER COLUMN'
47+
}
48+
return 'ALTER TABLE'
4349
default:
4450
return v
4551
}

packages/server/src/complete/candidates/createTableCandidates.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,21 @@ function allTableNameCombinations(table: Table): string[] {
1717
return names
1818
}
1919

20-
export function createTableCandidates(tables: Table[], lastToken: string) {
20+
export function createTableCandidates(
21+
tables: Table[],
22+
lastToken: string,
23+
onFromClause?: boolean
24+
) {
2125
return tables
2226
.flatMap((table) => allTableNameCombinations(table))
2327
.map((aTableNameVariant) => {
24-
return new Identifier(lastToken, aTableNameVariant, '', ICONS.TABLE)
28+
return new Identifier(
29+
lastToken,
30+
aTableNameVariant,
31+
'',
32+
ICONS.TABLE,
33+
onFromClause ? 'FROM' : 'OTHERS'
34+
)
2535
})
2636
.filter((item) => item.matchesLastToken())
2737
.map((item) => item.toCompletionItem())

packages/server/src/complete/complete.ts

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -170,10 +170,12 @@ class Completer {
170170
this.candidates.push(item)
171171
}
172172

173-
addCandidatesForTables(tables: Table[]) {
174-
createTableCandidates(tables, this.lastToken).forEach((item) => {
175-
this.addCandidate(item)
176-
})
173+
addCandidatesForTables(tables: Table[], onFromClause: boolean) {
174+
createTableCandidates(tables, this.lastToken, onFromClause).forEach(
175+
(item) => {
176+
this.addCandidate(item)
177+
}
178+
)
177179
}
178180

179181
addCandidatesForColumnsOfAnyTable(tables: Table[]) {
@@ -229,7 +231,7 @@ class Completer {
229231
) || []
230232
this.addCandidatesForExpectedLiterals(expectedLiteralNodes)
231233
this.addCandidatesForFunctions()
232-
this.addCandidatesForTables(this.schema.tables)
234+
this.addCandidatesForTables(this.schema.tables, false)
233235
}
234236

235237
addCandidatesForSelectQuery(e: ParseError, fromNodes: FromTableNode[]) {
@@ -244,7 +246,7 @@ class Completer {
244246
this.addCandidatesForFunctions()
245247
this.addCandidatesForScopedColumns(fromNodes, schemaAndSubqueries)
246248
this.addCandidatesForAliases(fromNodes)
247-
this.addCandidatesForTables(schemaAndSubqueries)
249+
this.addCandidatesForTables(schemaAndSubqueries, true)
248250
if (logger.isDebugEnabled())
249251
logger.debug(
250252
`candidates for error returns: ${JSON.stringify(this.candidates)}`
@@ -267,7 +269,7 @@ class Completer {
267269

268270
addCandidatesForParsedDeleteStatement(ast: DeleteStatement) {
269271
if (isPosInLocation(ast.table.location, this.pos)) {
270-
this.addCandidatesForTables(this.schema.tables)
272+
this.addCandidatesForTables(this.schema.tables, false)
271273
} else if (
272274
ast.where &&
273275
isPosInLocation(ast.where.expression.location, this.pos)
@@ -304,7 +306,7 @@ class Completer {
304306
// Column is not scoped to a table/alias yet
305307
// Could be an alias, a talbe or a function
306308
this.addCandidatesForAliases(fromNodes)
307-
this.addCandidatesForTables(schemaAndSubqueries)
309+
this.addCandidatesForTables(schemaAndSubqueries, true)
308310
this.addCandidatesForFunctions()
309311
}
310312
}

packages/server/test/complete.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,7 @@ describe('From clause', () => {
220220
)
221221
expect(result.candidates.length).toEqual(1)
222222
expect(result.candidates[0].label).toEqual('TABLE1')
223+
expect(result.candidates[0].insertText).toEqual('TABLE1 AS TAB')
223224
})
224225

225226
test('from clause: complete TableName:multi lines', () => {

packages/server/test/complete/Identifier.test.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,5 +33,21 @@ describe('Identifier', () => {
3333
const completion = item.toCompletionItem()
3434
expect(completion.label).toEqual('column1.subcolumn2')
3535
})
36+
37+
describe('ICONS.TABLE', () => {
38+
test('Add alias if it is onFromClause', () => {
39+
const item = new Identifier('T', 'TABLE1', '', ICONS.TABLE, 'FROM')
40+
const completion = item.toCompletionItem()
41+
expect(completion.label).toEqual('TABLE1')
42+
expect(completion.insertText).toEqual('TABLE1 AS TAB')
43+
})
44+
45+
test("Doesn't add alias if it isn't onFromClause", () => {
46+
const item = new Identifier('T', 'TABLE1', '', ICONS.TABLE, 'OTHERS')
47+
const completion = item.toCompletionItem()
48+
expect(completion.label).toEqual('TABLE1')
49+
expect(completion.insertText).toEqual('TABLE1')
50+
})
51+
})
3652
})
3753
})
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import { complete } from '../../src/complete'
2+
3+
const SIMPLE_SCHEMA = {
4+
tables: [
5+
{
6+
catalog: null,
7+
database: null,
8+
tableName: 'TABLE1',
9+
columns: [
10+
{ columnName: 'COLUMN1', description: '' },
11+
{ columnName: 'COLUMN2', description: '' },
12+
],
13+
},
14+
],
15+
functions: [
16+
{
17+
name: 'array_concat()',
18+
description: 'desc1',
19+
},
20+
{
21+
name: 'array_contains()',
22+
description: 'desc2',
23+
},
24+
],
25+
}
26+
27+
describe('complete ALTER TABLE statement', () => {
28+
describe('keyword completion', () => {
29+
it("completes 'ALTER TABLE' keyword", () => {
30+
const sql = 'A'
31+
const result = complete(sql, { line: 0, column: sql.length })
32+
expect(result.candidates.length).toEqual(1)
33+
expect(result.candidates[0].label).toEqual('ALTER TABLE')
34+
})
35+
36+
it("completes 'ALTER COLUMN' keyword", () => {
37+
const sql = 'ALTER TABLE Customers ALTE'
38+
const result = complete(sql, { line: 0, column: sql.length })
39+
expect(result.candidates.length).toEqual(1)
40+
expect(result.candidates[0].label).toEqual('ALTER COLUMN')
41+
})
42+
})
43+
44+
describe('Table name completion', () => {
45+
it('completes table name', () => {
46+
const sql = 'ALTER TABLE T'
47+
const result = complete(
48+
sql,
49+
{ line: 0, column: sql.length },
50+
SIMPLE_SCHEMA
51+
)
52+
expect(result.candidates.length).toEqual(1)
53+
expect(result.candidates[0].label).toEqual('TABLE1')
54+
expect(result.candidates[0].insertText).toEqual('TABLE1')
55+
})
56+
})
57+
58+
describe('Column name completion', () => {
59+
// TODO: Deal with column completion
60+
it.skip('completes column name', () => {
61+
const sql = 'ALTER TABLE TABLE1 MODIFY C'
62+
const result = complete(
63+
sql,
64+
{ line: 0, column: sql.length },
65+
SIMPLE_SCHEMA
66+
)
67+
expect(result.candidates.length).toEqual(2)
68+
expect(result.candidates[0].label).toEqual('COLUMN1')
69+
expect(result.candidates[0].insertText).toEqual('COLUMN1')
70+
})
71+
})
72+
})

0 commit comments

Comments
 (0)