firefly-compiler 0.6.24 → 0.6.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lsp/Sql.ff DELETED
@@ -1,1395 +0,0 @@
1
- import Token from ff:compiler
2
- import Syntax from ff:compiler
3
- import Pg from ff:postgresql
4
- import Environment from ff:compiler
5
- import Sql
6
- import SqlStatement
7
-
8
- data SqlTokenKind {
9
- SqlReserved
10
- SqlUnreserved
11
- SqlIdentifier
12
- SqlNumber
13
- SqlString
14
- SqlOperator
15
- SqlPunctuation
16
- SqlComment
17
- SqlPlaceholder
18
- }
19
-
20
- data SqlToken(
21
- file: String
22
- code: String
23
- codeLower: String
24
- kind: SqlTokenKind
25
- startLine: Int
26
- startLineOffset: Int
27
- startOffset: Int
28
- stopLine: Int
29
- stopLineOffset: Int
30
- stopOffset: Int
31
- )
32
-
33
- extend token: SqlToken {
34
-
35
- at(): Location {
36
- Location(token.file, token.startLine, (token.startOffset - token.startLineOffset) + 1)
37
- }
38
-
39
- end(): Location {
40
- Location(token.file, token.stopLine, (token.stopOffset - token.stopLineOffset) + 1)
41
- }
42
-
43
- raw(): String {
44
- token.code.slice(token.startOffset, token.stopOffset)
45
- }
46
-
47
- is(kind: SqlTokenKind): Bool {
48
- token.kind == kind
49
- }
50
-
51
- is2(kind1: SqlTokenKind, kind2: SqlTokenKind): Bool {
52
- token.kind == kind1 || token.kind == kind2
53
- }
54
-
55
- is3(kind1: SqlTokenKind, kind2: SqlTokenKind, kind3: SqlTokenKind): Bool {
56
- token.kind == kind1 || token.kind == kind2 || token.kind == kind3
57
- }
58
-
59
- rawIs(value: String): Bool {
60
- token.stopOffset - token.startOffset == value.size() &&
61
- token.codeLower.startsWith(value, token.startOffset)
62
- }
63
- }
64
-
65
- tokenize(sql: String): List[SqlToken] {
66
- let sqlLower = sql.lower()
67
- let tokens = Array.new()
68
- mutable offset = 0
69
- mutable line = 1
70
- mutable lineOffset = 0
71
- let length = sql.size()
72
- function emitToken(startOffset: Int, kind: SqlTokenKind) {
73
- tokens.push(SqlToken(".", sql, sqlLower, kind, line, lineOffset, startOffset, line, lineOffset, offset))
74
- }
75
- while {offset < length} {
76
- let char = sql.grab(offset)
77
- let startOffset = offset
78
- if(char == '\n') {
79
- offset += 1
80
- line += 1
81
- lineOffset = offset
82
- } elseIf {char == '-' && offset + 1 < length && sql.grab(offset + 1) == '-'} {
83
- offset += 2
84
- while {offset < length && sql.grab(offset) != '\n'} {
85
- offset += 1
86
- }
87
- emitToken(startOffset, SqlComment)
88
- } elseIf {char == '/' && offset + 1 < length && sql.grab(offset + 1) == '*'} {
89
- offset += 2
90
- while {offset < length && !(sql.grab(offset) == '*' && offset + 1 < length && sql.grab(offset + 1) == '/')} {
91
- if(sql.grab(offset) == '\n') {
92
- line += 1
93
- lineOffset = offset + 1
94
- }
95
- offset += 1
96
- }
97
- offset += 2
98
- emitToken(startOffset, SqlComment)
99
- } elseIf {char == '\''} {
100
- offset += 1
101
- while {offset < length && sql.grab(offset) != '\''} {
102
- if(sql.grab(offset) == '\\' && offset + 1 < length) {
103
- offset += 2
104
- } else {
105
- if(sql.grab(offset) == '\n') {
106
- line += 1
107
- lineOffset = offset + 1
108
- }
109
- offset += 1
110
- }
111
- }
112
- offset += 1
113
- emitToken(startOffset, SqlString)
114
- } elseIf {char == '$'} {
115
- offset += 1
116
- while {offset < length && sql.grab(offset).isAsciiLetterOrDigit()} {
117
- offset += 1
118
- }
119
- emitToken(startOffset, SqlPlaceholder)
120
- } elseIf {char.isAsciiDigit()} {
121
- while {offset < length && sql.grab(offset).isAsciiDigit()} {
122
- offset += 1
123
- }
124
- if(offset < length && sql.grab(offset) == '.') {
125
- offset += 1
126
- while {offset < length && sql.grab(offset).isAsciiDigit()} {
127
- offset += 1
128
- }
129
- }
130
- emitToken(startOffset, SqlNumber)
131
- } elseIf {char.isAsciiLetter() || char == '_' || char == '"'} {
132
- let isQuoted = char == '"'
133
- if(isQuoted) {
134
- offset += 1
135
- }
136
- while {offset < length && (
137
- sql.grab(offset).isAsciiLetterOrDigit() || sql.grab(offset) == '_' ||
138
- (isQuoted && sql.grab(offset) != '"')
139
- )} {
140
- offset += 1
141
- }
142
- if(isQuoted && offset < length && sql.grab(offset) == '"') {
143
- offset += 1
144
- }
145
- let word = sqlLower.slice(startOffset, offset)
146
- if(!isQuoted && sqlReserved.contains(word) && (tokens.isEmpty() || !tokens.grabLast().rawIs("as"))) {
147
- emitToken(startOffset, SqlReserved)
148
- } elseIf {
149
- !isQuoted && sqlUnreserved.contains(word) && (tokens.isEmpty() || !tokens.grabLast().rawIs("as"))
150
- } {
151
- emitToken(startOffset, SqlUnreserved)
152
- } else {
153
- emitToken(startOffset, SqlIdentifier)
154
- }
155
- } elseIf {char == '(' || char == ')' || char == ',' || char == ';' || char == '.'} {
156
- offset += 1
157
- emitToken(startOffset, SqlPunctuation)
158
- } elseIf {char == ':' && offset + 1 < length && sql.grab(offset + 1) == ':'} {
159
- offset += 2
160
- emitToken(startOffset, SqlPunctuation)
161
- } else {
162
- mutable operator = False
163
- mutable c = char
164
- while {(
165
- c == '+' || c == '-' || c == '*' || c == '/' || c == '<' || c == '>' || c == '=' ||
166
- c == '~' || c == '!' || c == '@' || c == '#' || c == '%' || c == '^' || c == '&' ||
167
- c == '|' || c == '`' || c == '?'
168
- ) && !(
169
- c == '-' && offset + 1 < length && sql.grab(offset + 1) == '-'
170
- ) && !(
171
- c == '/' && offset + 1 < length && sql.grab(offset + 1) == '*'
172
- )} {
173
- operator = True
174
- offset += 1
175
- c = if(offset < length) {sql.grab(offset)} else {' '}
176
- }
177
- if(operator) {
178
- emitToken(startOffset, SqlOperator)
179
- } else {
180
- offset += 1
181
- }
182
- }
183
- }
184
- tokens.drain()
185
- }
186
-
187
- class SqlScope(
188
- mutable targetTable: Option[Pair[Option[String], String]]
189
- symbols: Array[SqlSymbol]
190
- )
191
-
192
- data SqlSymbol {
193
- SqlQuery(alias: String, columns: List[String])
194
- SqlTable(alias: Option[String], schema: Option[String], table: String)
195
- }
196
-
197
- data SqlSimpleView(schema: Option[String], table: String, columns: List[String])
198
-
199
- data SqlCompletionScope(
200
- targetTable: Option[Pair[Option[String], String]]
201
- expectingColumnNames: Option[Set[String]] // Seen set
202
- expectingColumnSetters: Option[Set[String]] // Seen set
203
- expectingJoin: Bool
204
- expectingTable: Bool
205
- avoidTables: Bool
206
- inQuotes: Bool
207
- beforeDot: Option[String]
208
- symbols: List[SqlSymbol]
209
- columns: List[String]
210
- )
211
-
212
- completionScope(tokens: List[SqlToken], line: Int, column: Int): SqlCompletionScope {
213
- let scopes = Array.new[SqlScope]()
214
- mutable foundScopes = None
215
- mutable offset = 0
216
- mutable targetTable = None
217
- mutable expectingColumnNames = None
218
- mutable expectingColumnSetters = None
219
- mutable expectingJoin = False
220
- mutable expectingTable = False
221
- mutable inQuotes = False
222
- mutable avoidTables = False
223
- mutable beforeDot = None
224
- function rawIs(o: Int, raw: String): Bool {
225
- o < tokens.size() && tokens.grab(o).rawIs(raw)
226
- }
227
- function is(o: Int, kind: SqlTokenKind): Bool {
228
- o < tokens.size() && tokens.grab(o).is(kind)
229
- }
230
- function selectEnd(o: Int): Bool {
231
- rawIs(o, "from") || rawIs(o, "join") ||
232
- rawIs(o, "where") || rawIs(o, "group") ||
233
- rawIs(o, "having") || rawIs(o, "window") ||
234
- rawIs(o, "union") || rawIs(o, "intersect") || rawIs(o, "except") ||
235
- rawIs(o, "order") || rawIs(o, "limit") || rawIs(o, "offset") ||
236
- rawIs(o, "fetch") || rawIs(o, "for") ||
237
- rawIs(o, "returning") || rawIs(o, ")") ||
238
- (rawIs(o, "when") && (rawIs(o + 1, "matched") || rawIs(o + 2, "matched")))
239
- }
240
- function foundCursor(
241
- expectTable: Bool
242
- expectColumnNames: Option[Set[String]]
243
- expectColumnSetters: Option[Set[String]]
244
- ): Unit {
245
- targetTable = scopes.last().flatMap {_.targetTable}
246
- expectingColumnNames = expectColumnNames
247
- expectingColumnSetters = expectColumnSetters
248
- expectingJoin = offset >= 3 && (rawIs(offset - 1, "join") || (
249
- rawIs(offset - 3, "join") && (is(offset - 2, SqlIdentifier) || is(offset - 2, SqlUnreserved)) &&
250
- rawIs(offset - 1, ".")
251
- ))
252
- inQuotes = offset < tokens.size() && tokens.grab(offset).raw().startsWith("\"")
253
- expectingTable = expectTable
254
- beforeDot = if(offset < tokens.size() && offset >= 1 && rawIs(offset, ".") && (
255
- is(offset - 1, SqlIdentifier) || is(offset - 1, SqlUnreserved)
256
- )) {
257
- let token = tokens.grab(offset)
258
- if(token.stopLine == line && token.stopOffset - token.stopLineOffset >= column - 1) {
259
- tokens.grab(offset - 1).raw()
260
- }
261
- } elseIf {offset >= 2 && rawIs(offset - 1, ".") && (
262
- is(offset - 2, SqlIdentifier) || is(offset - 2, SqlUnreserved)
263
- )} {Some(tokens.grab(offset - 2).raw())} else {
264
- None
265
- }.map {fromRaw(_)}
266
- avoidTables = offset - 1 >= 0 && (
267
- is(offset - 1, SqlIdentifier) || is(offset - 1, SqlUnreserved) || rawIs(offset - 1, ")")
268
- )
269
- foundScopes = Some(scopes.toList())
270
- }
271
- function checkCursor(
272
- expectTable: Bool,
273
- expectColumnNames: Option[Set[String]] = None
274
- expectColumnSetters: Option[Set[String]] = None
275
- ): Unit {
276
- if(!foundScopes.isEmpty()) {} else:
277
- if(offset >= tokens.size()) {foundCursor(expectTable, expectColumnNames, expectColumnSetters)} else:
278
- let token = tokens.grab(offset)
279
- let grace = if(token.is(SqlPunctuation)) {0} else {1}
280
- if(token.stopLine > line || (
281
- token.stopLine == line && token.stopOffset - token.stopLineOffset >= column - grace
282
- )) {foundCursor(expectTable, expectColumnNames, expectColumnSetters)}
283
- }
284
- function parseScope(): List[Pair[String, Bool]] {
285
- let scopeColumns = Array.new()
286
- function parseColumns() {
287
- mutable simple = True
288
- while {offset < tokens.size() && !selectEnd(offset)} {
289
- checkCursor(False)
290
- if(rawIs(offset, "(")) {
291
- offset += 1
292
- parseScope()
293
- if(rawIs(offset, ")")) {offset += 1}
294
- simple = False
295
- } elseIf {
296
- (is(offset, SqlIdentifier) || is(offset, SqlUnreserved)) &&
297
- (rawIs(offset + 1, ",") || selectEnd(offset + 1))
298
- } {
299
- let alias = tokens.grab(offset)
300
- scopeColumns.push(Pair(fromRaw(alias.raw()), simple))
301
- simple = True
302
- offset += 1
303
- if(rawIs(offset, ",")) {
304
- checkCursor(False)
305
- offset += 1
306
- }
307
- } else {
308
- simple = False
309
- offset += 1
310
- }
311
- }
312
- }
313
- function parseColumnNames(): Set[String] {
314
- mutable seen = Set.new()
315
- checkCursor(False, expectColumnNames = Some(seen))
316
- while {offset < tokens.size() && !rawIs(offset, ")")} {
317
- if(is(offset, SqlIdentifier) || is(offset, SqlUnreserved)) {
318
- offset += 1
319
- checkCursor(False, expectColumnNames = Some(seen))
320
- seen = seen.add(fromRaw(tokens.grab(offset - 1).raw()))
321
- }
322
- while {offset < tokens.size() && !rawIs(offset, ")") && !rawIs(offset, ",")} {
323
- offset += 1
324
- checkCursor(False)
325
- }
326
- if(rawIs(offset, ",")) {
327
- offset += 1
328
- }
329
- checkCursor(False, expectColumnNames = Some(seen))
330
- }
331
- seen
332
- }
333
- function parseColumnSetters() {
334
- mutable seen = Set.new()
335
- mutable beforeEquals = True
336
- while {offset < tokens.size() && !selectEnd(offset)} {
337
- checkCursor(False, expectColumnSetters = if(beforeEquals) {seen})
338
- if(rawIs(offset, "(")) {
339
- offset += 1
340
- if(beforeEquals) {
341
- seen = seen.addAll(parseColumnNames())
342
- } else {
343
- parseScope()
344
- }
345
- if(rawIs(offset, ")")) {offset += 1}
346
- } elseIf {rawIs(offset, ",")} {
347
- offset += 1
348
- beforeEquals = True
349
- } elseIf {rawIs(offset, "=")} {
350
- offset += 1
351
- beforeEquals = False
352
- } elseIf {beforeEquals && (is(offset, SqlIdentifier) || is(offset, SqlUnreserved))} {
353
- offset += 1
354
- checkCursor(False, expectColumnSetters = if(beforeEquals) {seen})
355
- seen = seen.add(fromRaw(tokens.grab(offset - 1).raw()))
356
- beforeEquals = False
357
- } else {
358
- offset += 1
359
- }
360
- }
361
- checkCursor(False, expectColumnSetters = if(beforeEquals) {seen})
362
- }
363
- let scope = SqlScope(None, Array.new())
364
- scopes.push(scope)
365
- if(rawIs(offset, "explain")) {offset += 1}
366
- if(rawIs(offset, "analyze") || rawIs(offset, "analyse")) {offset += 1}
367
- while {rawIs(offset, "with") || rawIs(offset, ",")} {
368
- offset += 1
369
- checkCursor(False)
370
- if(is(offset, SqlIdentifier) || is(offset, SqlUnreserved)):
371
- let alias = tokens.grab(offset)
372
- offset += 1
373
- if(rawIs(offset, "recursive")) {
374
- offset += 1
375
- if(rawIs(offset, "(")) {
376
- offset += 1
377
- parseScope()
378
- if(rawIs(offset, ")")) {offset += 1}
379
- }
380
- }
381
- if(rawIs(offset, "as")) {offset += 1}
382
- let columns = if(rawIs(offset, "(")) {
383
- offset += 1
384
- let columns = parseScope()
385
- if(rawIs(offset, ")")) {offset += 1}
386
- columns
387
- } else {[]}
388
- scope.symbols.push(SqlQuery(fromRaw(alias.raw()), columns.map {_.first}))
389
- }
390
- checkCursor(False)
391
- mutable mergeUsing = rawIs(offset, "merge")
392
- if(rawIs(offset, "select")) {
393
- offset += 1
394
- parseColumns()
395
- }
396
- while {
397
- offset < tokens.size() && !rawIs(offset, ")") && !rawIs(offset, "returning") &&
398
- !rawIs(offset, "union") && !rawIs(offset, "intersect") && !rawIs(offset, "except")
399
- } {
400
- checkCursor(False)
401
- if(rawIs(offset, "(")) {
402
- offset += 1
403
- parseScope()
404
- if(rawIs(offset, ")")) {offset += 1}
405
- } elseIf {!scope.targetTable.isEmpty() && rawIs(offset, "insert") && rawIs(offset + 1, "(")} {
406
- offset += 2
407
- parseColumnNames()
408
- if(rawIs(offset, ")")) {
409
- offset += 1
410
- }
411
- } elseIf {!scope.targetTable.isEmpty() && rawIs(offset, "update") && rawIs(offset + 1, "set")} {
412
- offset += 2
413
- parseColumnSetters()
414
- } elseIf {
415
- rawIs(offset, "from") || rawIs(offset, "join") ||
416
- rawIs(offset, "update") || rawIs(offset, "into") ||
417
- rawIs(offset, "only") || rawIs(offset, "table") ||
418
- (mergeUsing && rawIs(offset, "using"))
419
- } {
420
- if(rawIs(offset, "using")) {mergeUsing = False}
421
- let isFrom = rawIs(offset, "from")
422
- let requireAs = rawIs(offset, "update") || rawIs(offset, "into") || rawIs(offset, "only")
423
- doWhile {
424
- offset += 1
425
- if(rawIs(offset, "lateral")) {offset += 1}
426
- checkCursor(True)
427
- if(rawIs(offset, "(")) {
428
- offset += 1
429
- let columns = parseScope()
430
- if(rawIs(offset, ")")) {offset += 1}
431
- if(rawIs(offset, "as")) {offset += 1}
432
- if(is(offset, SqlIdentifier) || is(offset, SqlUnreserved)) {
433
- let alias = tokens.grab(offset)
434
- scope.symbols.push(SqlQuery(fromRaw(alias.raw()), columns.map {_.first}))
435
- }
436
- } elseIf {is(offset, SqlIdentifier) || is(offset, SqlUnreserved)} {
437
- let isTarget = offset - 1 >= 0 && (
438
- rawIs(offset - 1, "into") || rawIs(offset - 1, "update") || rawIs(offset - 1, "only")
439
- )
440
- let schema = if(rawIs(offset + 1, ".")) {
441
- let schema = tokens.grab(offset)
442
- offset += 2
443
- schema
444
- }
445
- checkCursor(True)
446
- if(is(offset, SqlIdentifier) || is(offset, SqlUnreserved)) {
447
- let relation = tokens.grab(offset)
448
- offset += 1
449
- let allowAlias = if(rawIs(offset, "as")) {offset += 1; True} else {!requireAs}
450
- checkCursor(True)
451
- let alias = if(allowAlias && (is(offset, SqlIdentifier) || is(offset, SqlUnreserved))) {
452
- let alias = tokens.grab(offset)
453
- offset += 1
454
- alias
455
- }
456
- if(isTarget) {
457
- scope.targetTable = Some(Pair(schema.map {fromRaw(_.raw())}, fromRaw(relation.raw())))
458
- }
459
- scope.symbols.push(SqlTable(
460
- alias.map {fromRaw(_.raw())}, schema.map {fromRaw(_.raw())}, fromRaw(relation.raw())
461
- ))
462
- }
463
- if(isTarget && rawIs(offset, "(")) {
464
- offset += 1
465
- parseColumnNames()
466
- if(rawIs(offset, ")")) {
467
- offset += 1
468
- }
469
- } elseIf {isTarget && rawIs(offset, "set")} {
470
- offset += 1
471
- parseColumnSetters()
472
- }
473
- }
474
- isFrom && rawIs(offset, ",")
475
- }
476
- } else {
477
- offset += 1
478
- }
479
- }
480
- checkCursor(False)
481
- if(rawIs(offset, "returning")) {
482
- offset += 1
483
- parseColumns()
484
- }
485
- checkCursor(False)
486
- scopes.pop()
487
- while {rawIs(offset, "union") || rawIs(offset, "intersect") || rawIs(offset, "except")} {
488
- offset += 1
489
- checkCursor(False)
490
- parseScope()
491
- }
492
- scopeColumns.drain()
493
- }
494
- let columns = parseScope().filter {_.second}.map {_.first}
495
- let symbols = foundScopes.toList().flatten().flatMap {_.symbols.toList()}
496
- SqlCompletionScope(
497
- targetTable
498
- expectingColumnNames
499
- expectingColumnSetters
500
- expectingJoin
501
- expectingTable
502
- avoidTables
503
- inQuotes
504
- beforeDot
505
- symbols
506
- columns
507
- )
508
- }
509
-
510
- data SqlData(
511
- searchPathSchemas: List[String]
512
- relations: List[SqlRelation]
513
- foreignKeys: List[SqlForeignKey]
514
- functions: List[SqlFunction]
515
- )
516
-
517
- data SqlRelation(
518
- kind: String
519
- schema: String
520
- name: String
521
- columns: List[SqlColumn]
522
- simpleView: Option[SqlSimpleView]
523
- )
524
-
525
- data SqlColumn(
526
- name: String
527
- notNull: Bool
528
- primaryKey: Bool
529
- type: String
530
- default: Option[String]
531
- description: Option[String]
532
- )
533
-
534
- data SqlFunction(
535
- kind: String
536
- schema: String
537
- name: String
538
- argumentNames: List[String]
539
- argumentTypes: List[String]
540
- returnType: String
541
- description: Option[String]
542
- )
543
-
544
- data SqlForeignKey(
545
- schema: String
546
- name: String
547
- foreignSchema: String
548
- foreignName: String
549
- columns: List[Pair[String, String]]
550
- )
551
-
552
- extend self: SqlForeignKey {
553
- swap(): SqlForeignKey {
554
- SqlForeignKey(self.foreignSchema, self.foreignName, self.schema, self.name, self.columns.map {_.swap()})
555
- }
556
- }
557
-
558
- extend self: SqlRelation {
559
- tableLike(): Bool {
560
- self.kind == "r" || self.kind == "v" || self.kind == "m" || self.kind == "f" || self.kind == "p"
561
- }
562
- kindName(): String {
563
- self.kind.{
564
- | "r" => "table"
565
- | "v" => "view"
566
- | "m" => "materialized view"
567
- | "f" => "foreign table"
568
- | "p" => "partitioned table"
569
- | "i" => "index"
570
- | "I" => "partitioned index"
571
- | "S" => "sequence"
572
- | "t" => "toast table"
573
- | "c" => "composite type"
574
- | _ => "other"
575
- }
576
- }
577
- }
578
-
579
- load(nodeSystem: NodeSystem, pool: PgPool): SqlData {
580
- pool.transaction {connection =>
581
- let s = loadSearchPath(connection)
582
- let rs = loadRelations(connection)
583
- let fs = loadFunctions(connection)
584
- let ks = loadForeignKeys(connection)
585
- SqlData(s, rs, ks, fs)
586
- }
587
- }
588
-
589
- loadSearchPath(connection: PgConnection): List[String] {
590
- connection.statement("""
591
- select pg_catalog.current_schemas(true) search_path_schemas
592
- """).map {row =>
593
- row.getStringArray("search_path_schemas").grab()
594
- }.grabFirst()
595
- }
596
-
597
- loadForeignKeys(connection: PgConnection): List[SqlForeignKey] {
598
- connection.statement("""
599
- select
600
- n1.nspname schema_name,
601
- cl1.relname table_name,
602
- array_agg(a1.attname) column_names,
603
- n2.nspname foreign_schema_name,
604
- cl2.relname foreign_table_name,
605
- array_agg(a2.attname) foreign_column_names
606
- from pg_constraint c
607
- join pg_attribute a1 on a1.attrelid = c.conrelid and a1.attnum = any(c.conkey)
608
- join pg_attribute a2 on a2.attrelid = c.confrelid and a2.attnum = any(c.confkey)
609
- join pg_class cl1 on cl1.oid = c.conrelid
610
- join pg_class cl2 on cl2.oid = c.confrelid
611
- join pg_namespace n1 on n1.oid = cl1.relnamespace
612
- join pg_namespace n2 on n2.oid = cl2.relnamespace
613
- where c.contype = 'f'
614
- group by n1.nspname, cl1.relname, n2.nspname, cl2.relname, c.conname
615
- """).map {row =>
616
- let schema = row.getString("schema_name").grab()
617
- let table = row.getString("table_name").grab()
618
- let foreignSchema = row.getString("foreign_schema_name").grab()
619
- let foreignTable = row.getString("foreign_table_name").grab()
620
- let columns = row.getStringArray("column_names").else {[]}
621
- let foreignColumns = row.getStringArray("foreign_column_names").else {[]}
622
- SqlForeignKey(
623
- schema = unquote(schema)
624
- name = unquote(table)
625
- foreignSchema = unquote(foreignSchema)
626
- foreignName = unquote(foreignTable)
627
- columns = columns.map {unquote(_)}.zip(foreignColumns.map {unquote(_)})
628
- )
629
- }
630
- }
631
-
632
- loadFunctions(connection: PgConnection): List[SqlFunction] {
633
- connection.statement("""
634
- select
635
- p.prokind kind,
636
- n.nspname schema_name,
637
- p.proname function_name,
638
- p.proargnames argument_names,
639
- (
640
- select pg_catalog.array_agg(pg_catalog.format_type(t, null))
641
- from pg_catalog.unnest(p.proargtypes) t
642
- ) argument_types,
643
- pg_catalog.format_type(p.prorettype, null) return_type,
644
- d.description function_description
645
- from pg_catalog.pg_proc p
646
- join pg_catalog.pg_namespace n on n.oid = p.pronamespace
647
- left join pg_catalog.pg_description d on d.objoid = p.oid and d.classoid = 'pg_proc'::regclass
648
- order by n.nspname, p.proname
649
- """).map {row =>
650
- SqlFunction(
651
- kind = row.getString("kind").grab().{
652
- | "p" => "procedure"
653
- | "a" => "aggregate"
654
- | "w" => "window"
655
- | _ => "function"
656
- }
657
- schema = row.getString("schema_name").grab()
658
- name = row.getString("function_name").grab()
659
- argumentNames = row.getStringArray("argument_names").else {[]}.map {unquote(_)}
660
- argumentTypes = row.getStringArray("argument_types").else {[]}.map {unquote(shortenType(_))}
661
- returnType = unquote(shortenType(row.getString("return_type").grab()))
662
- description = row.getString("function_description")
663
- )
664
- }
665
- }
666
-
667
- loadRelations(connection: PgConnection): List[SqlRelation] {
668
- let results = connection.statement("""
669
- select
670
- c.relkind kind,
671
- n.nspname schema_name,
672
- c.relname relation_name,
673
- a.attname column_name,
674
- a.attnotnull not_null,
675
- pg_catalog.format_type(a.atttypid, a.atttypmod) data_type,
676
- pg_catalog.col_description(c.oid, a.attnum) column_description,
677
- exists (
678
- select 1 from pg_catalog.pg_index i
679
- where i.indrelid = a.attrelid and i.indisprimary and a.attnum = any(i.indkey)
680
- ) primary_key,
681
- pg_catalog.pg_get_expr(d.adbin, d.adrelid) default_value,
682
- case when c.relkind = 'v' and a.attnum = 1 then pg_catalog.pg_get_viewdef(c.oid) end view_definition
683
- from pg_catalog.pg_class c
684
- join pg_catalog.pg_namespace n on n.oid = c.relnamespace
685
- left join pg_catalog.pg_attribute a on a.attrelid = c.oid and a.attnum > 0 and not a.attisdropped
686
- left join pg_catalog.pg_attrdef d on d.adrelid = a.attrelid and d.adnum = a.attnum and c.relkind = 'r'
687
- order by n.nspname, c.relname, a.attnum
688
- """).map {row =>
689
- let schemaName = unquote(row.getString("schema_name").grab())
690
- let tableName = unquote(row.getString("relation_name").grab())
691
- let simpleView = row.getString("view_definition").flatMap {d =>
692
- let tokens = tokenize(d)
693
- let scope = completionScope(tokens, 1, 1)
694
- scope.symbols.{
695
- | [SqlTable(_, schema, table)] => Some(SqlSimpleView(schema, table, scope.columns))
696
- | _ => None
697
- }
698
- }
699
- SqlRelation(
700
- kind = row.getString("kind").grab()
701
- schema = schemaName
702
- name = tableName
703
- columns = row.getString("column_name").toList().map {unquote(_)}.map {columnName =>
704
- let type = unquote(shortenType(row.getString("data_type").grab()))
705
- let default = row.getString("default_value").map {d =>
706
- tokenize(d).{
707
- | [a, b, c] {a.is(SqlString) && b.rawIs("::") && c.rawIs("text")} =>
708
- a.raw()
709
- | _ =>
710
- d
711
- }
712
- }
713
- let serialType = if(!default.any {_.endsWith("::regclass)")}) {type} else {
714
- if(!default.map {_.replace("\"", "")}.any {d =>
715
- d == "nextval('" + tableName + "_" + columnName + "_seq'::regclass)" ||
716
- d == "nextval('" + schemaName + "." + tableName + "_" + columnName + "_seq'::regclass)"
717
- }) {type} else:
718
- if(type == "bigint") {"bigserial"} else:
719
- if(type == "integer") {"serial"} else:
720
- type
721
- }
722
- SqlColumn(
723
- name = columnName
724
- notNull = row.getBool("not_null").grab()
725
- primaryKey = row.getBool("primary_key").grab()
726
- type = serialType
727
- default = default.filter {_ => type == serialType}
728
- description = row.getString("column_description")
729
- )
730
- }
731
- simpleView = simpleView
732
- )
733
- }
734
- let groups = results.map {r => Pair(Pair(r.schema, r.name), r)}.group().toList()
735
- let relations = groups.map {| Pair(_, rs) =>
736
- rs.grabFirst().SqlRelation(
737
- columns = rs.flatMap {_.columns}.sortBy {c => Pair(if(c.primaryKey) {1} else {2}, c.name)}
738
- )
739
- }.sortBy {r => Pair(r.schema, r.name)}
740
- let tables = relations.filter {_.kind == "r"}.map {r => Pair(Pair(r.schema, r.name), r)}.toMap()
741
- relations.map {r =>
742
- r.simpleView.flatMap {v =>
743
- v.schema.flatMap {tables.get(Pair(_, v.table))}.map {t =>
744
- let simpleColumns = v.columns.toSet()
745
- r.SqlRelation(columns = r.columns.map {c =>
746
- if(!simpleColumns.contains(c.name)) {c} else {
747
- t.columns.find {c2 => c2.name == c.name}.else {c}
748
- }
749
- })
750
- }
751
- }.else {r}
752
- }
753
- }
754
-
755
- data ExplainedStatement(
756
- explainStatement: String
757
- deltas: List[Pair[Int, Int]]
758
- usedParameters: List[Pair[String, Int]]
759
- )
760
-
761
- buildExplainStatement(statement: String, parameters: Option[List[SqlParameter]]): Option[ExplainedStatement] {
762
- let tokens = tokenize(statement).filter {_.kind != SqlComment}
763
- let placeholders = tokens.filter {token =>
764
- token.kind.{
765
- | SqlPlaceholder => True
766
- | _ => False
767
- }
768
- }
769
- let replacements = placeholders.map {p => Pair(p.raw(), p)}.group().toList().pairs().flatMap {
770
- | Pair(index, Pair(raw, tokens)) => tokens.map {token => Pair(token.startOffset, Pair(index, raw))}
771
- }
772
- let prefix = "EXPLAIN (GENERIC_PLAN) "
773
- mutable fixedStatement = prefix
774
- let deltas = [Pair(0, -prefix.size())].toArray()
775
- mutable position = 0
776
- let usedParameters = replacements.map {| Pair(offset, Pair(index, raw)) =>
777
- Pair(raw.dropFirst(), offset)
778
- }
779
- let types = parameters.toList().flatten().map {p => Pair(p.name, p.sqlType)}.toMap()
780
- replacements.each {| Pair(offset, Pair(index, raw)) =>
781
- let simplePlaceholder = "$" + (index + 1)
782
- let placeholder = types.get(raw.dropFirst()).map {sqlType =>
783
- "(" + simplePlaceholder + "::" + sqlType + ")"
784
- }.else {simplePlaceholder}
785
- let part = statement.slice(position, offset)
786
- fixedStatement += part + placeholder
787
- position += part.size() + raw.size()
788
- deltas.push(Pair(fixedStatement.size() - placeholder.size(), raw.size() - placeholder.size()))
789
- }
790
- fixedStatement += statement.slice(position, statement.size())
791
- let noInbetweenSemicolon = tokens.indexWhere {_.rawIs(";")}.all {_ >= tokens.size() - 1}
792
- if(noInbetweenSemicolon && tokens.first().any {t =>
793
- t.rawIs("with") || t.rawIs("select") || t.rawIs("values") ||
794
- t.rawIs("insert") || t.rawIs("update") || t.rawIs("delete") || t.rawIs("merge")
795
- }):
796
- ExplainedStatement(fixedStatement, deltas.drain(), usedParameters)
797
- }
798
-
799
- shortenType(fullType: String): String {
800
- fullType
801
- .replace("timestamp with time zone", "timestamptz")
802
- .replace("timestamp without time zone", "timestamp")
803
- .replace("time without time zone", "time")
804
- .replace("double precision", "float")
805
- }
806
-
807
- quote(identifier: String, force: Bool = False): String {
808
- if(!force && identifier.first().any {_.isAsciiLower()} && identifier.all {c =>
809
- c.isAsciiLower() || c.isAsciiDigit() || c == '_'
810
- }) {identifier} else {
811
- "\"" + identifier.replace("\"", "\"\"") + "\""
812
- }
813
- }
814
-
815
- unquote(identifier: String): String {
816
- if(identifier.startsWith("\"")) {
817
- identifier.dropFirst().dropLast().replace("\"\"", "\"")
818
- } else {
819
- identifier
820
- }
821
- }
822
-
823
- fromRaw(identifier: String): String {
824
- if(identifier.startsWith("\"")) {
825
- identifier.dropFirst().dropLast().replace("\"\"", "\"")
826
- } else {
827
- identifier.lower()
828
- }
829
- }
830
-
831
- // Source: https://github.com/postgres/postgres/blob/master/src/include/parser/kwlist.h
832
- sqlReserved: Set[String] = [
833
- "all"
834
- "analyse"
835
- "analyze"
836
- "and"
837
- "any"
838
- "array"
839
- "as"
840
- "asc"
841
- "asymmetric"
842
- "authorization"
843
- "between"
844
- "bigint"
845
- "binary"
846
- "bit"
847
- "boolean"
848
- "both"
849
- "case"
850
- "cast"
851
- "char"
852
- "character"
853
- "check"
854
- "coalesce"
855
- "collate"
856
- "collation"
857
- "column"
858
- "concurrently"
859
- "constraint"
860
- "create"
861
- "cross"
862
- "current_catalog"
863
- "current_date"
864
- "current_role"
865
- "current_schema"
866
- "current_time"
867
- "current_timestamp"
868
- "current_user"
869
- "dec"
870
- "decimal"
871
- "default"
872
- "deferrable"
873
- "desc"
874
- "distinct"
875
- "do"
876
- "else"
877
- "end"
878
- "except"
879
- "exists"
880
- "extract"
881
- "false"
882
- "fetch"
883
- "float"
884
- "for"
885
- "foreign"
886
- "freeze"
887
- "from"
888
- "full"
889
- "grant"
890
- "graph_table"
891
- "greatest"
892
- "group"
893
- "grouping"
894
- "having"
895
- "ilike"
896
- "in"
897
- "initially"
898
- "inner"
899
- "inout"
900
- "int"
901
- "integer"
902
- "intersect"
903
- "interval"
904
- "into"
905
- "is"
906
- "isnull"
907
- "join"
908
- "json"
909
- "json_array"
910
- "json_arrayagg"
911
- "json_exists"
912
- "json_object"
913
- "json_objectagg"
914
- "json_query"
915
- "json_scalar"
916
- "json_serialize"
917
- "json_table"
918
- "json_value"
919
- "lateral"
920
- "leading"
921
- "least"
922
- "left"
923
- "like"
924
- "limit"
925
- "localtime"
926
- "localtimestamp"
927
- "merge_action"
928
- "national"
929
- "natural"
930
- "nchar"
931
- "none"
932
- "normalize"
933
- "not"
934
- "notnull"
935
- "null"
936
- "nullif"
937
- "numeric"
938
- "offset"
939
- "on"
940
- "only"
941
- "or"
942
- "order"
943
- "out"
944
- "outer"
945
- "overlaps"
946
- "overlay"
947
- "placing"
948
- "position"
949
- "precision"
950
- "primary"
951
- "real"
952
- "references"
953
- "returning"
954
- "right"
955
- "row"
956
- "select"
957
- "session_user"
958
- "setof"
959
- "similar"
960
- "smallint"
961
- "some"
962
- "substring"
963
- "symmetric"
964
- "system_user"
965
- "table"
966
- "tablesample"
967
- "then"
968
- "time"
969
- "timestamp"
970
- "to"
971
- "trailing"
972
- "treat"
973
- "trim"
974
- "true"
975
- "union"
976
- "unique"
977
- "user"
978
- "using"
979
- "values"
980
- "varchar"
981
- "variadic"
982
- "verbose"
983
- "when"
984
- "where"
985
- "window"
986
- "with"
987
- "xmlattributes"
988
- "xmlconcat"
989
- "xmlelement"
990
- "xmlexists"
991
- "xmlforest"
992
- "xmlnamespaces"
993
- "xmlparse"
994
- "xmlpi"
995
- "xmlroot"
996
- "xmlserialize"
997
- "xmltable"
998
- ].toSet()
999
-
1000
- sqlUnreserved: Set[String] = [
1001
- "abort"
1002
- "absent"
1003
- "absolute"
1004
- "access"
1005
- "action"
1006
- "add"
1007
- "admin"
1008
- "after"
1009
- "aggregate"
1010
- "also"
1011
- "alter"
1012
- "always"
1013
- "asensitive"
1014
- "assertion"
1015
- "assignment"
1016
- "at"
1017
- "atomic"
1018
- "attach"
1019
- "attribute"
1020
- "backward"
1021
- "before"
1022
- "begin"
1023
- "breadth"
1024
- "by"
1025
- "cache"
1026
- "call"
1027
- "called"
1028
- "cascade"
1029
- "cascaded"
1030
- "catalog"
1031
- "chain"
1032
- "characteristics"
1033
- "checkpoint"
1034
- "class"
1035
- "close"
1036
- "cluster"
1037
- "columns"
1038
- "comment"
1039
- "comments"
1040
- "commit"
1041
- "committed"
1042
- "compression"
1043
- "conditional"
1044
- "configuration"
1045
- "conflict"
1046
- "connection"
1047
- "constraints"
1048
- "content"
1049
- "continue"
1050
- "conversion"
1051
- "copy"
1052
- "cost"
1053
- "csv"
1054
- "cube"
1055
- "current"
1056
- "cursor"
1057
- "cycle"
1058
- "data"
1059
- "database"
1060
- "day"
1061
- "deallocate"
1062
- "declare"
1063
- "defaults"
1064
- "deferred"
1065
- "definer"
1066
- "delete"
1067
- "delimiter"
1068
- "delimiters"
1069
- "depends"
1070
- "depth"
1071
- "destination"
1072
- "detach"
1073
- "dictionary"
1074
- "disable"
1075
- "discard"
1076
- "document"
1077
- "domain"
1078
- "double"
1079
- "drop"
1080
- "each"
1081
- "edge"
1082
- "empty"
1083
- "enable"
1084
- "encoding"
1085
- "encrypted"
1086
- "enforced"
1087
- "enum"
1088
- "error"
1089
- "escape"
1090
- "event"
1091
- "exclude"
1092
- "excluding"
1093
- "exclusive"
1094
- "execute"
1095
- "explain"
1096
- "expression"
1097
- "extension"
1098
- "external"
1099
- "family"
1100
- "filter"
1101
- "finalize"
1102
- "first"
1103
- "following"
1104
- "force"
1105
- "format"
1106
- "forward"
1107
- "function"
1108
- "functions"
1109
- "generated"
1110
- "global"
1111
- "granted"
1112
- "graph"
1113
- "groups"
1114
- "handler"
1115
- "header"
1116
- "hold"
1117
- "hour"
1118
- "identity"
1119
- "if"
1120
- "ignore"
1121
- "immediate"
1122
- "immutable"
1123
- "implicit"
1124
- "import"
1125
- "include"
1126
- "including"
1127
- "increment"
1128
- "indent"
1129
- "index"
1130
- "indexes"
1131
- "inherit"
1132
- "inherits"
1133
- "inline"
1134
- "input"
1135
- "insensitive"
1136
- "insert"
1137
- "instead"
1138
- "invoker"
1139
- "isolation"
1140
- "keep"
1141
- "key"
1142
- "keys"
1143
- "label"
1144
- "language"
1145
- "large"
1146
- "last"
1147
- "leakproof"
1148
- "level"
1149
- "listen"
1150
- "load"
1151
- "local"
1152
- "location"
1153
- "lock"
1154
- "locked"
1155
- "logged"
1156
- "lsn"
1157
- "mapping"
1158
- "match"
1159
- "matched"
1160
- "materialized"
1161
- "maxvalue"
1162
- "merge"
1163
- "method"
1164
- "minute"
1165
- "minvalue"
1166
- "mode"
1167
- "month"
1168
- "move"
1169
- "name"
1170
- "names"
1171
- "nested"
1172
- "new"
1173
- "next"
1174
- "nfc"
1175
- "nfd"
1176
- "nfkc"
1177
- "nfkd"
1178
- "no"
1179
- "node"
1180
- "normalized"
1181
- "nothing"
1182
- "notify"
1183
- "nowait"
1184
- "nulls"
1185
- "object"
1186
- "objects"
1187
- "of"
1188
- "off"
1189
- "oids"
1190
- "old"
1191
- "omit"
1192
- "operator"
1193
- "option"
1194
- "options"
1195
- "ordinality"
1196
- "others"
1197
- "over"
1198
- "overriding"
1199
- "owned"
1200
- "owner"
1201
- "parallel"
1202
- "parameter"
1203
- "parser"
1204
- "partial"
1205
- "partition"
1206
- "partitions"
1207
- "passing"
1208
- "password"
1209
- "path"
1210
- "period"
1211
- "plan"
1212
- "plans"
1213
- "policy"
1214
- "portion"
1215
- "preceding"
1216
- "prepare"
1217
- "prepared"
1218
- "preserve"
1219
- "prior"
1220
- "privileges"
1221
- "procedural"
1222
- "procedure"
1223
- "procedures"
1224
- "program"
1225
- "properties"
1226
- "property"
1227
- "publication"
1228
- "quote"
1229
- "quotes"
1230
- "range"
1231
- "read"
1232
- "reassign"
1233
- "recursive"
1234
- "ref"
1235
- "referencing"
1236
- "refresh"
1237
- "reindex"
1238
- "relationship"
1239
- "relative"
1240
- "release"
1241
- "rename"
1242
- "repack"
1243
- "repeatable"
1244
- "replace"
1245
- "replica"
1246
- "reset"
1247
- "respect"
1248
- "restart"
1249
- "restrict"
1250
- "return"
1251
- "returns"
1252
- "revoke"
1253
- "role"
1254
- "rollback"
1255
- "rollup"
1256
- "routine"
1257
- "routines"
1258
- "rows"
1259
- "rule"
1260
- "savepoint"
1261
- "scalar"
1262
- "schema"
1263
- "schemas"
1264
- "scroll"
1265
- "search"
1266
- "second"
1267
- "security"
1268
- "sequence"
1269
- "sequences"
1270
- "serializable"
1271
- "server"
1272
- "session"
1273
- "set"
1274
- "sets"
1275
- "share"
1276
- "show"
1277
- "simple"
1278
- "skip"
1279
- "snapshot"
1280
- "source"
1281
- "split"
1282
- "sql"
1283
- "stable"
1284
- "standalone"
1285
- "start"
1286
- "statement"
1287
- "statistics"
1288
- "stdin"
1289
- "stdout"
1290
- "storage"
1291
- "stored"
1292
- "strict"
1293
- "string"
1294
- "strip"
1295
- "subscription"
1296
- "support"
1297
- "sysid"
1298
- "system"
1299
- "tables"
1300
- "tablespace"
1301
- "target"
1302
- "temp"
1303
- "template"
1304
- "temporary"
1305
- "text"
1306
- "ties"
1307
- "transaction"
1308
- "transform"
1309
- "trigger"
1310
- "truncate"
1311
- "trusted"
1312
- "type"
1313
- "types"
1314
- "uescape"
1315
- "unbounded"
1316
- "uncommitted"
1317
- "unconditional"
1318
- "unencrypted"
1319
- "unknown"
1320
- "unlisten"
1321
- "unlogged"
1322
- "until"
1323
- "update"
1324
- "vacuum"
1325
- "valid"
1326
- "validate"
1327
- "validator"
1328
- "value"
1329
- "varying"
1330
- "version"
1331
- "vertex"
1332
- "view"
1333
- "views"
1334
- "virtual"
1335
- "volatile"
1336
- "wait"
1337
- "whitespace"
1338
- "within"
1339
- "without"
1340
- "work"
1341
- "wrapper"
1342
- "write"
1343
- "xml"
1344
- "year"
1345
- "yes"
1346
- "zone"
1347
- ].toSet()
1348
-
1349
- nodeMain(system: NodeSystem) {
1350
- function showToken(token: SqlToken): String {
1351
- "Token(" + Show.show(token.kind) + ", \"" + token.raw() + "\", at: " +
1352
- token.at().line + ":" + token.at().column + ")"
1353
- }
1354
- function showCompletionScope(sql: String, find: String) {
1355
- system.writeLine("")
1356
- let foundIndex = sql.indexOf(find).grab()
1357
- let scope = completionScope(tokenize(sql), 1, 1 + foundIndex)
1358
- system.writeLine(sql)
1359
- system.writeLine(
1360
- " ".repeat(foundIndex) + "^ " +
1361
- if(!scope.targetTable.isEmpty()) {
1362
- let table = scope.targetTable.grab()
1363
- table.first.map {_ + "."}.else {""} + table.second +
1364
- if(!scope.expectingColumnNames.isEmpty()) {
1365
- " (" + scope.expectingColumnNames.grab().toList().join(", ") + ")"
1366
- } elseIf {!scope.expectingColumnSetters.isEmpty()} {
1367
- " SET " + scope.expectingColumnSetters.grab().toList().join(", ")
1368
- } else {""}
1369
- } else {
1370
- Show.show(scope.expectingTable) + " " + Show.show(scope.beforeDot)
1371
- }
1372
- )
1373
- scope.symbols.each {symbol =>
1374
- system.writeLine(Show.show(symbol))
1375
- }
1376
- }
1377
- let sql = "WITH c as (select x, y z from p, q.w) SELECT *, f.g(h) i FROM s.t1 a WHERE ((SELECT * FROM t2 b WHERE a.id = b.id))"
1378
- system.writeLine("")
1379
- system.writeLine("Tokenize:")
1380
- tokenize(sql).each {token =>
1381
- system.writeLine(showToken(token))
1382
- }
1383
- showCompletionScope(sql, "x")
1384
- showCompletionScope(sql, "*")
1385
- showCompletionScope(sql, "id")
1386
- showCompletionScope(sql, "t1 a")
1387
- showCompletionScope(sql, "1 a")
1388
- showCompletionScope(sql, " a WHERE")
1389
- showCompletionScope("insert into foo (a[1], b.x, c, d)", "c,")
1390
- showCompletionScope("insert into foo ()", ")")
1391
- showCompletionScope("update foo set --", "--")
1392
- showCompletionScope("update foo set x = 1, (y, z) = row (2, 3), w =", "w =")
1393
- showCompletionScope("update foo set answers = 1, --", "--")
1394
- showCompletionScope("merge into foo using (values (1)) as s(id) on id when matched then update set x =", "x =")
1395
- }