@indiekitai/pg-complete 0.1.0

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/dist/index.js ADDED
@@ -0,0 +1,1219 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ BUILTIN_FUNCTIONS: () => BUILTIN_FUNCTIONS,
24
+ KEYWORDS: () => KEYWORDS,
25
+ KEYWORD_TREE: () => KEYWORD_TREE,
26
+ PgCompleter: () => PgCompleter,
27
+ extractTables: () => extractTables,
28
+ suggestType: () => suggestType
29
+ });
30
+ module.exports = __toCommonJS(index_exports);
31
+
32
+ // src/completer.ts
33
+ var import_pg = require("pg");
34
+
35
+ // src/keywords.ts
36
+ var KEYWORDS = [
37
+ // DML
38
+ "SELECT",
39
+ "FROM",
40
+ "WHERE",
41
+ "AND",
42
+ "OR",
43
+ "NOT",
44
+ "IN",
45
+ "EXISTS",
46
+ "BETWEEN",
47
+ "LIKE",
48
+ "ILIKE",
49
+ "IS",
50
+ "NULL",
51
+ "TRUE",
52
+ "FALSE",
53
+ "AS",
54
+ "ON",
55
+ "USING",
56
+ "JOIN",
57
+ "INNER",
58
+ "LEFT",
59
+ "RIGHT",
60
+ "FULL",
61
+ "OUTER",
62
+ "CROSS",
63
+ "NATURAL",
64
+ "ORDER",
65
+ "BY",
66
+ "ASC",
67
+ "DESC",
68
+ "NULLS",
69
+ "FIRST",
70
+ "LAST",
71
+ "GROUP",
72
+ "HAVING",
73
+ "LIMIT",
74
+ "OFFSET",
75
+ "UNION",
76
+ "ALL",
77
+ "INTERSECT",
78
+ "EXCEPT",
79
+ "DISTINCT",
80
+ "INSERT",
81
+ "INTO",
82
+ "VALUES",
83
+ "DEFAULT",
84
+ "RETURNING",
85
+ "UPDATE",
86
+ "SET",
87
+ "DELETE",
88
+ "WITH",
89
+ "RECURSIVE",
90
+ "CASE",
91
+ "WHEN",
92
+ "THEN",
93
+ "ELSE",
94
+ "END",
95
+ "CAST",
96
+ "COALESCE",
97
+ "NULLIF",
98
+ "GREATEST",
99
+ "LEAST",
100
+ // DDL
101
+ "CREATE",
102
+ "ALTER",
103
+ "DROP",
104
+ "TRUNCATE",
105
+ "TABLE",
106
+ "VIEW",
107
+ "INDEX",
108
+ "SEQUENCE",
109
+ "SCHEMA",
110
+ "DATABASE",
111
+ "FUNCTION",
112
+ "PROCEDURE",
113
+ "TRIGGER",
114
+ "TYPE",
115
+ "EXTENSION",
116
+ "MATERIALIZED",
117
+ "TEMPORARY",
118
+ "TEMP",
119
+ "UNLOGGED",
120
+ "IF",
121
+ "CASCADE",
122
+ "RESTRICT",
123
+ "ADD",
124
+ "COLUMN",
125
+ "RENAME",
126
+ "TO",
127
+ "PRIMARY",
128
+ "KEY",
129
+ "FOREIGN",
130
+ "REFERENCES",
131
+ "UNIQUE",
132
+ "CHECK",
133
+ "CONSTRAINT",
134
+ "NOT NULL",
135
+ // Data types
136
+ "INTEGER",
137
+ "INT",
138
+ "BIGINT",
139
+ "SMALLINT",
140
+ "SERIAL",
141
+ "BIGSERIAL",
142
+ "REAL",
143
+ "DOUBLE",
144
+ "PRECISION",
145
+ "NUMERIC",
146
+ "DECIMAL",
147
+ "TEXT",
148
+ "VARCHAR",
149
+ "CHAR",
150
+ "CHARACTER",
151
+ "VARYING",
152
+ "BOOLEAN",
153
+ "BOOL",
154
+ "DATE",
155
+ "TIME",
156
+ "TIMESTAMP",
157
+ "TIMESTAMPTZ",
158
+ "INTERVAL",
159
+ "JSON",
160
+ "JSONB",
161
+ "UUID",
162
+ "BYTEA",
163
+ "ARRAY",
164
+ // Transaction
165
+ "BEGIN",
166
+ "COMMIT",
167
+ "ROLLBACK",
168
+ "SAVEPOINT",
169
+ // Other
170
+ "GRANT",
171
+ "REVOKE",
172
+ "EXPLAIN",
173
+ "ANALYZE",
174
+ "VERBOSE",
175
+ "COPY",
176
+ "VACUUM",
177
+ "REINDEX",
178
+ "CLUSTER",
179
+ "LATERAL",
180
+ "WINDOW",
181
+ "OVER",
182
+ "PARTITION",
183
+ "ROWS",
184
+ "RANGE",
185
+ "FETCH",
186
+ "NEXT",
187
+ "PRIOR",
188
+ "ABSOLUTE",
189
+ "RELATIVE",
190
+ "FOR",
191
+ "SHARE",
192
+ "NOWAIT",
193
+ "SKIP",
194
+ "LOCKED",
195
+ "ANY",
196
+ "SOME"
197
+ ];
198
+ var KEYWORD_TREE = {
199
+ "SELECT": ["DISTINCT", "ALL", "*"],
200
+ "FROM": [],
201
+ "WHERE": [],
202
+ "ORDER": ["BY"],
203
+ "GROUP": ["BY"],
204
+ "INSERT": ["INTO"],
205
+ "UPDATE": ["SET"],
206
+ "DELETE": ["FROM"],
207
+ "CREATE": [
208
+ "TABLE",
209
+ "VIEW",
210
+ "INDEX",
211
+ "SCHEMA",
212
+ "DATABASE",
213
+ "FUNCTION",
214
+ "PROCEDURE",
215
+ "TRIGGER",
216
+ "TYPE",
217
+ "EXTENSION",
218
+ "MATERIALIZED",
219
+ "TEMPORARY",
220
+ "TEMP",
221
+ "UNLOGGED",
222
+ "UNIQUE",
223
+ "OR"
224
+ ],
225
+ "ALTER": [
226
+ "TABLE",
227
+ "VIEW",
228
+ "INDEX",
229
+ "SCHEMA",
230
+ "DATABASE",
231
+ "FUNCTION",
232
+ "PROCEDURE",
233
+ "TRIGGER",
234
+ "TYPE",
235
+ "EXTENSION",
236
+ "SEQUENCE"
237
+ ],
238
+ "DROP": [
239
+ "TABLE",
240
+ "VIEW",
241
+ "INDEX",
242
+ "SCHEMA",
243
+ "DATABASE",
244
+ "FUNCTION",
245
+ "PROCEDURE",
246
+ "TRIGGER",
247
+ "TYPE",
248
+ "EXTENSION",
249
+ "SEQUENCE",
250
+ "IF"
251
+ ],
252
+ "JOIN": [],
253
+ "INNER": ["JOIN"],
254
+ "LEFT": ["JOIN", "OUTER"],
255
+ "RIGHT": ["JOIN", "OUTER"],
256
+ "FULL": ["JOIN", "OUTER"],
257
+ "CROSS": ["JOIN"],
258
+ "NATURAL": ["JOIN", "LEFT", "RIGHT", "FULL", "INNER"],
259
+ "OUTER": ["JOIN"],
260
+ "IF": ["EXISTS", "NOT"],
261
+ "NOT": ["NULL", "EXISTS", "IN", "BETWEEN", "LIKE", "ILIKE"],
262
+ "IS": ["NULL", "NOT", "TRUE", "FALSE", "DISTINCT"],
263
+ "OR": ["REPLACE"],
264
+ "UNION": ["ALL"],
265
+ "LIMIT": [],
266
+ "OFFSET": [],
267
+ "SET": [],
268
+ "VALUES": [],
269
+ "ON": [],
270
+ "RETURNING": [],
271
+ "INTO": [],
272
+ "GRANT": ["ALL", "SELECT", "INSERT", "UPDATE", "DELETE", "USAGE"],
273
+ "REVOKE": ["ALL", "SELECT", "INSERT", "UPDATE", "DELETE", "USAGE"],
274
+ "WITH": ["RECURSIVE"]
275
+ };
276
+ var BUILTIN_FUNCTIONS = [
277
+ // Aggregate
278
+ "avg",
279
+ "count",
280
+ "max",
281
+ "min",
282
+ "sum",
283
+ "array_agg",
284
+ "string_agg",
285
+ "bool_and",
286
+ "bool_or",
287
+ "json_agg",
288
+ "jsonb_agg",
289
+ "json_object_agg",
290
+ "jsonb_object_agg",
291
+ // String
292
+ "length",
293
+ "lower",
294
+ "upper",
295
+ "trim",
296
+ "ltrim",
297
+ "rtrim",
298
+ "substring",
299
+ "replace",
300
+ "concat",
301
+ "concat_ws",
302
+ "left",
303
+ "right",
304
+ "repeat",
305
+ "reverse",
306
+ "split_part",
307
+ "starts_with",
308
+ "format",
309
+ "regexp_replace",
310
+ "regexp_matches",
311
+ // Numeric
312
+ "abs",
313
+ "ceil",
314
+ "floor",
315
+ "round",
316
+ "trunc",
317
+ "mod",
318
+ "power",
319
+ "sqrt",
320
+ "random",
321
+ "generate_series",
322
+ // Date/Time
323
+ "now",
324
+ "current_date",
325
+ "current_time",
326
+ "current_timestamp",
327
+ "date_trunc",
328
+ "date_part",
329
+ "extract",
330
+ "age",
331
+ "to_char",
332
+ "to_date",
333
+ "to_timestamp",
334
+ "to_number",
335
+ "make_date",
336
+ "make_time",
337
+ "make_timestamp",
338
+ // JSON
339
+ "json_build_object",
340
+ "jsonb_build_object",
341
+ "json_build_array",
342
+ "jsonb_build_array",
343
+ "json_extract_path",
344
+ "jsonb_extract_path",
345
+ "json_extract_path_text",
346
+ "jsonb_extract_path_text",
347
+ "json_array_length",
348
+ "jsonb_array_length",
349
+ "json_each",
350
+ "jsonb_each",
351
+ "json_each_text",
352
+ "jsonb_each_text",
353
+ "json_typeof",
354
+ "jsonb_typeof",
355
+ "row_to_json",
356
+ "to_json",
357
+ "to_jsonb",
358
+ // Array
359
+ "array_length",
360
+ "array_dims",
361
+ "array_lower",
362
+ "array_upper",
363
+ "array_append",
364
+ "array_prepend",
365
+ "array_cat",
366
+ "array_remove",
367
+ "array_position",
368
+ "array_positions",
369
+ "unnest",
370
+ // Type casting
371
+ "cast",
372
+ // Window
373
+ "row_number",
374
+ "rank",
375
+ "dense_rank",
376
+ "percent_rank",
377
+ "cume_dist",
378
+ "ntile",
379
+ "lag",
380
+ "lead",
381
+ "first_value",
382
+ "last_value",
383
+ "nth_value",
384
+ // System
385
+ "current_database",
386
+ "current_schema",
387
+ "current_user",
388
+ "session_user",
389
+ "pg_typeof",
390
+ "version",
391
+ // Conditional
392
+ "coalesce",
393
+ "nullif",
394
+ "greatest",
395
+ "least"
396
+ ];
397
+
398
+ // src/parser.ts
399
+ function suggestType(fullText, textBeforeCursor) {
400
+ const trimmed = textBeforeCursor.trimEnd();
401
+ const wordMatch = textBeforeCursor.match(/([A-Za-z0-9_."$]+)$/);
402
+ const wordBeforeCursor = wordMatch ? wordMatch[1] : "";
403
+ const textForAnalysis = wordBeforeCursor ? textBeforeCursor.slice(0, -wordBeforeCursor.length) : textBeforeCursor;
404
+ const dotMatch = wordBeforeCursor.match(/^([A-Za-z_][A-Za-z0-9_]*)\.(.*)$/);
405
+ const tableRefs = extractTables(fullText);
406
+ const lastKeyword = findLastKeyword(textForAnalysis);
407
+ const lastKw = lastKeyword?.toUpperCase() || "";
408
+ if (dotMatch) {
409
+ const parent = dotMatch[1];
410
+ const matchingTables = tableRefs.filter(
411
+ (t) => t.alias === parent || t.name === parent || t.schema && `${t.schema}.${t.name}` === parent
412
+ );
413
+ const suggestions = [];
414
+ if (matchingTables.length > 0) {
415
+ suggestions.push({ type: "column", tableRefs: matchingTables, qualifiable: false, parent });
416
+ }
417
+ suggestions.push(
418
+ { type: "table", schema: parent },
419
+ { type: "view", schema: parent },
420
+ { type: "function", schema: parent }
421
+ );
422
+ return suggestions;
423
+ }
424
+ if (!lastKw || lastKw === "") {
425
+ return [{ type: "keyword" }];
426
+ }
427
+ if (lastKw === "SELECT" || lastKw === "WHERE" || lastKw === "HAVING" || lastKw === "ORDER BY" || lastKw === "DISTINCT") {
428
+ return [
429
+ { type: "column", tableRefs, qualifiable: tableRefs.length > 1 },
430
+ { type: "function", schema: null },
431
+ { type: "keyword", lastToken: lastKw }
432
+ ];
433
+ }
434
+ if (lastKw === "FROM" || lastKw === "COPY" || lastKw === "UPDATE" || lastKw === "INTO" || lastKw === "INSERT INTO" || lastKw === "TRUNCATE") {
435
+ const isFrom = lastKw === "FROM";
436
+ const suggestions = [{ type: "schema" }];
437
+ if (isFrom) {
438
+ suggestions.push({ type: "from_clause", schema: null, tableRefs });
439
+ } else {
440
+ suggestions.push({ type: "table", schema: null });
441
+ suggestions.push({ type: "view", schema: null });
442
+ }
443
+ return suggestions;
444
+ }
445
+ if (lastKw === "JOIN" || lastKw.endsWith(" JOIN")) {
446
+ const suggestions = [
447
+ { type: "schema" },
448
+ { type: "from_clause", schema: null, tableRefs }
449
+ ];
450
+ if (tableRefs.length > 0) {
451
+ suggestions.push({ type: "join", tableRefs, schema: null });
452
+ }
453
+ return suggestions;
454
+ }
455
+ if (lastKw === "ON") {
456
+ const aliases = tableRefs.map((t) => t.alias || t.name);
457
+ const suggestions = [
458
+ { type: "alias", aliases }
459
+ ];
460
+ if (tableRefs.length >= 2) {
461
+ suggestions.push({ type: "join_condition", tableRefs });
462
+ }
463
+ return suggestions;
464
+ }
465
+ if (lastKw === "SET") {
466
+ return [{ type: "column", tableRefs, qualifiable: false }];
467
+ }
468
+ if (lastKw === "TABLE" || lastKw === "VIEW") {
469
+ return [{ type: "schema" }, { type: lastKw === "TABLE" ? "table" : "view", schema: null }];
470
+ }
471
+ if (lastKw === "FUNCTION") {
472
+ return [{ type: "schema" }, { type: "function", schema: null }];
473
+ }
474
+ if (lastKw === "SCHEMA") {
475
+ return [{ type: "schema" }];
476
+ }
477
+ if (lastKw === "DATABASE") {
478
+ return [{ type: "database" }];
479
+ }
480
+ if (lastKw === "TYPE" || lastKw === "::") {
481
+ return [
482
+ { type: "datatype", schema: null },
483
+ { type: "table", schema: null },
484
+ { type: "schema" }
485
+ ];
486
+ }
487
+ if (["CREATE", "ALTER", "DROP"].includes(lastKw)) {
488
+ return [{ type: "keyword", lastToken: lastKw }];
489
+ }
490
+ if (lastKw === "AND" || lastKw === "OR" || lastKw === "," || lastKw === "=") {
491
+ const prevKw = findPrevSignificantKeyword(textForAnalysis);
492
+ if (prevKw) {
493
+ return suggestType(fullText, textForAnalysis);
494
+ }
495
+ }
496
+ if (lastKw === "AS") {
497
+ return [];
498
+ }
499
+ if (lastKw === "COLUMN") {
500
+ return [{ type: "column", tableRefs, qualifiable: false }];
501
+ }
502
+ return [{ type: "keyword", lastToken: lastKw }];
503
+ }
504
+ function extractTables(sql) {
505
+ const tables = [];
506
+ const normalized = sql.replace(/\s+/g, " ").trim();
507
+ const tableContextRegex = /\b(?:FROM|JOIN|UPDATE|INTO|TABLE)\s+/gi;
508
+ let match;
509
+ while ((match = tableContextRegex.exec(normalized)) !== null) {
510
+ const afterKeyword = normalized.slice(match.index + match[0].length);
511
+ const refs = parseTableList(afterKeyword);
512
+ tables.push(...refs);
513
+ }
514
+ return dedup(tables);
515
+ }
516
+ function parseTableList(text) {
517
+ const tables = [];
518
+ const parts = splitOnCommas(text);
519
+ for (const part of parts) {
520
+ const ref = parseOneTableRef(part.trim());
521
+ if (ref) tables.push(ref);
522
+ }
523
+ return tables;
524
+ }
525
+ function splitOnCommas(text) {
526
+ const parts = [];
527
+ let depth = 0;
528
+ let current = "";
529
+ for (const ch of text) {
530
+ if (ch === "(") depth++;
531
+ else if (ch === ")") depth--;
532
+ if (ch === "," && depth === 0) {
533
+ parts.push(current);
534
+ current = "";
535
+ } else if (depth < 0) {
536
+ parts.push(current);
537
+ break;
538
+ } else if (/[;]/.test(ch) && depth === 0) {
539
+ parts.push(current);
540
+ break;
541
+ } else {
542
+ current += ch;
543
+ }
544
+ }
545
+ if (current.trim()) parts.push(current);
546
+ return parts;
547
+ }
548
+ function parseOneTableRef(text) {
549
+ const stopKeywords = /\b(?:WHERE|SET|ORDER|GROUP|HAVING|LIMIT|OFFSET|UNION|INTERSECT|EXCEPT|ON|USING|RETURNING|VALUES|LEFT|RIGHT|INNER|OUTER|FULL|CROSS|NATURAL|JOIN)\b/i;
550
+ const stopMatch = text.match(stopKeywords);
551
+ if (stopMatch) {
552
+ text = text.slice(0, stopMatch.index).trim();
553
+ }
554
+ if (!text) return null;
555
+ const m = text.match(
556
+ /^(?:"([^"]+)"|([A-Za-z_][A-Za-z0-9_$]*))(?:\.(?:"([^"]+)"|([A-Za-z_][A-Za-z0-9_$]*)))?(?:\s+(?:AS\s+)?(?:"([^"]+)"|([A-Za-z_][A-Za-z0-9_$]*)))?/i
557
+ );
558
+ if (!m) return null;
559
+ const part1 = m[1] || m[2];
560
+ const part2 = m[3] || m[4];
561
+ const alias = m[5] || m[6];
562
+ const aliasUpper = alias?.toUpperCase();
563
+ const keywordAlias = aliasUpper && /^(WHERE|SET|ORDER|GROUP|HAVING|LIMIT|OFFSET|ON|USING|JOIN|LEFT|RIGHT|INNER|OUTER|FULL|CROSS|NATURAL|AS|AND|OR|NOT|RETURNING|VALUES|INTO|FROM|SELECT|INSERT|UPDATE|DELETE|CREATE|ALTER|DROP)$/.test(aliasUpper);
564
+ if (part2) {
565
+ return {
566
+ schema: part1.toLowerCase(),
567
+ name: part2.toLowerCase(),
568
+ alias: keywordAlias ? null : alias?.toLowerCase() || null,
569
+ isFunction: false
570
+ };
571
+ }
572
+ return {
573
+ schema: null,
574
+ name: part1.toLowerCase(),
575
+ alias: keywordAlias ? null : alias?.toLowerCase() || null,
576
+ isFunction: false
577
+ };
578
+ }
579
+ function dedup(tables) {
580
+ const seen = /* @__PURE__ */ new Set();
581
+ return tables.filter((t) => {
582
+ const key = `${t.schema || ""}.${t.name}.${t.alias || ""}`;
583
+ if (seen.has(key)) return false;
584
+ seen.add(key);
585
+ return true;
586
+ });
587
+ }
588
+ function findLastKeyword(text) {
589
+ const trimmed = text.trimEnd();
590
+ const compoundMatch = trimmed.match(/\b(ORDER\s+BY|GROUP\s+BY|INSERT\s+INTO|NOT\s+IN|LEFT\s+(?:OUTER\s+)?JOIN|RIGHT\s+(?:OUTER\s+)?JOIN|FULL\s+(?:OUTER\s+)?JOIN|INNER\s+JOIN|CROSS\s+JOIN|NATURAL\s+JOIN|DISTINCT\s+ON)\s*$/i);
591
+ if (compoundMatch) {
592
+ return compoundMatch[1].replace(/\s+/g, " ").toUpperCase();
593
+ }
594
+ const simpleMatch = trimmed.match(/\b([A-Za-z]+)\s*$/);
595
+ if (simpleMatch) {
596
+ const kw = simpleMatch[1].toUpperCase();
597
+ const knownKeywords = /* @__PURE__ */ new Set([
598
+ "SELECT",
599
+ "FROM",
600
+ "WHERE",
601
+ "JOIN",
602
+ "ON",
603
+ "AND",
604
+ "OR",
605
+ "SET",
606
+ "INTO",
607
+ "UPDATE",
608
+ "INSERT",
609
+ "DELETE",
610
+ "CREATE",
611
+ "ALTER",
612
+ "DROP",
613
+ "TABLE",
614
+ "VIEW",
615
+ "FUNCTION",
616
+ "SCHEMA",
617
+ "DATABASE",
618
+ "AS",
619
+ "HAVING",
620
+ "ORDER",
621
+ "GROUP",
622
+ "DISTINCT",
623
+ "LIMIT",
624
+ "OFFSET",
625
+ "RETURNING",
626
+ "TRUNCATE",
627
+ "COPY",
628
+ "TYPE",
629
+ "COLUMN",
630
+ "VALUES"
631
+ ]);
632
+ if (knownKeywords.has(kw)) return kw;
633
+ }
634
+ if (trimmed.endsWith(",")) return ",";
635
+ if (trimmed.endsWith("=")) return "=";
636
+ if (trimmed.endsWith("::")) return "::";
637
+ return null;
638
+ }
639
+ function findPrevSignificantKeyword(text) {
640
+ const withoutLast = text.replace(/\S+\s*$/, "").trim();
641
+ return findLastKeyword(withoutLast);
642
+ }
643
+ function tableRef(t) {
644
+ return t.alias || t.name;
645
+ }
646
+
647
+ // src/metadata.ts
648
+ async function fetchMetadata(pool) {
649
+ const meta = {
650
+ tables: {},
651
+ views: {},
652
+ functions: {},
653
+ datatypes: {}
654
+ };
655
+ const [schemas, tables, views, tableCols, viewCols, functions, fks, datatypes] = await Promise.all([
656
+ fetchSchemas(pool),
657
+ fetchTables(pool),
658
+ fetchViews(pool),
659
+ fetchColumns(pool, ["r", "p", "f"]),
660
+ fetchColumns(pool, ["v", "m"]),
661
+ fetchFunctions(pool),
662
+ fetchForeignKeys(pool),
663
+ fetchDatatypes(pool)
664
+ ]);
665
+ for (const schema of schemas) {
666
+ meta.tables[schema] = meta.tables[schema] || {};
667
+ meta.views[schema] = meta.views[schema] || {};
668
+ meta.functions[schema] = meta.functions[schema] || {};
669
+ meta.datatypes[schema] = meta.datatypes[schema] || {};
670
+ }
671
+ for (const { schema, name } of tables) {
672
+ if (!meta.tables[schema]) meta.tables[schema] = {};
673
+ meta.tables[schema][name] = [];
674
+ }
675
+ for (const { schema, name } of views) {
676
+ if (!meta.views[schema]) meta.views[schema] = {};
677
+ meta.views[schema][name] = [];
678
+ }
679
+ for (const col of tableCols) {
680
+ const tbl = meta.tables[col.schema]?.[col.table];
681
+ if (tbl) {
682
+ tbl.push({
683
+ name: col.name,
684
+ datatype: col.datatype,
685
+ hasDefault: col.hasDefault,
686
+ default_: col.default_,
687
+ foreignKeys: []
688
+ });
689
+ }
690
+ }
691
+ for (const col of viewCols) {
692
+ const v = meta.views[col.schema]?.[col.view];
693
+ if (v) {
694
+ v.push({
695
+ name: col.name,
696
+ datatype: col.datatype,
697
+ hasDefault: col.hasDefault,
698
+ default_: col.default_,
699
+ foreignKeys: []
700
+ });
701
+ }
702
+ }
703
+ for (const f of functions) {
704
+ if (!meta.functions[f.schemaName]) meta.functions[f.schemaName] = {};
705
+ if (!meta.functions[f.schemaName][f.funcName]) {
706
+ meta.functions[f.schemaName][f.funcName] = [];
707
+ }
708
+ meta.functions[f.schemaName][f.funcName].push(f);
709
+ }
710
+ for (const fk of fks) {
711
+ const childCols = meta.tables[fk.childSchema]?.[fk.childTable];
712
+ if (childCols) {
713
+ const col = childCols.find((c) => c.name === fk.childColumn);
714
+ if (col) col.foreignKeys.push(fk);
715
+ }
716
+ const parentCols = meta.tables[fk.parentSchema]?.[fk.parentTable];
717
+ if (parentCols) {
718
+ const col = parentCols.find((c) => c.name === fk.parentColumn);
719
+ if (col) col.foreignKeys.push(fk);
720
+ }
721
+ }
722
+ for (const dt of datatypes) {
723
+ if (!meta.datatypes[dt.schema]) meta.datatypes[dt.schema] = {};
724
+ meta.datatypes[dt.schema][dt.name] = null;
725
+ }
726
+ return meta;
727
+ }
728
+ async function fetchSchemas(pool) {
729
+ const { rows } = await pool.query(
730
+ `SELECT nspname FROM pg_catalog.pg_namespace ORDER BY 1`
731
+ );
732
+ return rows.map((r) => r.nspname);
733
+ }
734
+ async function fetchTables(pool) {
735
+ const { rows } = await pool.query(
736
+ `SELECT n.nspname AS schema, c.relname AS name
737
+ FROM pg_catalog.pg_class c
738
+ LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
739
+ WHERE c.relkind = ANY($1)
740
+ ORDER BY 1, 2`,
741
+ [["r", "p", "f"]]
742
+ );
743
+ return rows.map((r) => ({ schema: r.schema, name: r.name }));
744
+ }
745
+ async function fetchViews(pool) {
746
+ const { rows } = await pool.query(
747
+ `SELECT n.nspname AS schema, c.relname AS name
748
+ FROM pg_catalog.pg_class c
749
+ LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
750
+ WHERE c.relkind = ANY($1)
751
+ ORDER BY 1, 2`,
752
+ [["v", "m"]]
753
+ );
754
+ return rows.map((r) => ({ schema: r.schema, name: r.name }));
755
+ }
756
+ async function fetchColumns(pool, kinds) {
757
+ const { rows } = await pool.query(
758
+ `SELECT nsp.nspname AS schema,
759
+ cls.relname AS table,
760
+ att.attname AS name,
761
+ att.atttypid::regtype::text AS datatype,
762
+ att.atthasdef AS "hasDefault",
763
+ pg_catalog.pg_get_expr(def.adbin, def.adrelid, true) AS "default_"
764
+ FROM pg_catalog.pg_attribute att
765
+ INNER JOIN pg_catalog.pg_class cls ON att.attrelid = cls.oid
766
+ INNER JOIN pg_catalog.pg_namespace nsp ON cls.relnamespace = nsp.oid
767
+ LEFT OUTER JOIN pg_attrdef def ON def.adrelid = att.attrelid AND def.adnum = att.attnum
768
+ WHERE cls.relkind = ANY($1)
769
+ AND NOT att.attisdropped
770
+ AND att.attnum > 0
771
+ ORDER BY 1, 2, att.attnum`,
772
+ [kinds]
773
+ );
774
+ return rows.map((r) => ({
775
+ schema: r.schema,
776
+ table: r.table,
777
+ view: r.table,
778
+ name: r.name,
779
+ datatype: r.datatype,
780
+ hasDefault: r.hasDefault,
781
+ default_: r.default_
782
+ }));
783
+ }
784
+ async function fetchFunctions(pool) {
785
+ const { rows } = await pool.query(
786
+ `SELECT n.nspname AS "schemaName",
787
+ p.proname AS "funcName",
788
+ p.proargnames AS "argNames",
789
+ COALESCE(proallargtypes::regtype[], proargtypes::regtype[])::text[] AS "argTypes",
790
+ p.proargmodes AS "argModes",
791
+ prorettype::regtype::text AS "returnType",
792
+ p.prokind = 'a' AS "isAggregate",
793
+ p.prokind = 'w' AS "isWindow",
794
+ p.proretset AS "isSetReturning",
795
+ d.deptype = 'e' AS "isExtension"
796
+ FROM pg_catalog.pg_proc p
797
+ INNER JOIN pg_catalog.pg_namespace n ON n.oid = p.pronamespace
798
+ LEFT JOIN pg_depend d ON d.objid = p.oid AND d.deptype = 'e'
799
+ WHERE p.prorettype::regtype != 'trigger'::regtype
800
+ ORDER BY 1, 2`
801
+ );
802
+ return rows.map((r) => ({
803
+ ...r,
804
+ isPublic: r.schemaName === "public",
805
+ isExtension: !!r.isExtension
806
+ }));
807
+ }
808
+ async function fetchForeignKeys(pool) {
809
+ const { rows } = await pool.query(
810
+ `SELECT s_p.nspname AS "parentSchema",
811
+ t_p.relname AS "parentTable",
812
+ unnest((
813
+ SELECT array_agg(attname ORDER BY i)
814
+ FROM (SELECT unnest(confkey) AS attnum, generate_subscripts(confkey, 1) AS i) x
815
+ JOIN pg_catalog.pg_attribute c USING(attnum)
816
+ WHERE c.attrelid = fk.confrelid
817
+ )) AS "parentColumn",
818
+ s_c.nspname AS "childSchema",
819
+ t_c.relname AS "childTable",
820
+ unnest((
821
+ SELECT array_agg(attname ORDER BY i)
822
+ FROM (SELECT unnest(conkey) AS attnum, generate_subscripts(conkey, 1) AS i) x
823
+ JOIN pg_catalog.pg_attribute c USING(attnum)
824
+ WHERE c.attrelid = fk.conrelid
825
+ )) AS "childColumn"
826
+ FROM pg_catalog.pg_constraint fk
827
+ JOIN pg_catalog.pg_class t_p ON t_p.oid = fk.confrelid
828
+ JOIN pg_catalog.pg_namespace s_p ON s_p.oid = t_p.relnamespace
829
+ JOIN pg_catalog.pg_class t_c ON t_c.oid = fk.conrelid
830
+ JOIN pg_catalog.pg_namespace s_c ON s_c.oid = t_c.relnamespace
831
+ WHERE fk.contype = 'f'`
832
+ );
833
+ return rows;
834
+ }
835
+ async function fetchDatatypes(pool) {
836
+ const { rows } = await pool.query(
837
+ `SELECT n.nspname AS schema, t.typname AS name
838
+ FROM pg_catalog.pg_type t
839
+ INNER JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace
840
+ WHERE (t.typrelid = 0 OR (SELECT c.relkind = 'c' FROM pg_catalog.pg_class c WHERE c.oid = t.typrelid))
841
+ AND NOT EXISTS (SELECT 1 FROM pg_catalog.pg_type el WHERE el.oid = t.typelem AND el.typarray = t.oid)
842
+ AND n.nspname <> 'pg_catalog'
843
+ AND n.nspname <> 'information_schema'
844
+ ORDER BY 1, 2`
845
+ );
846
+ return rows;
847
+ }
848
+ async function fetchSearchPath(pool) {
849
+ const { rows } = await pool.query(
850
+ `SELECT * FROM unnest(current_schemas(true))`
851
+ );
852
+ return rows.map((r) => r.unnest);
853
+ }
854
+
855
+ // src/completer.ts
856
+ var PgCompleter = class {
857
+ pool = null;
858
+ metadata;
859
+ searchPath;
860
+ qualifyColumns;
861
+ connectionString;
862
+ constructor(connectionStringOrOpts) {
863
+ const opts = typeof connectionStringOrOpts === "string" ? { connectionString: connectionStringOrOpts } : connectionStringOrOpts || {};
864
+ this.connectionString = opts.connectionString;
865
+ this.metadata = opts.metadata || { tables: {}, views: {}, functions: {}, datatypes: {} };
866
+ this.searchPath = opts.searchPath || ["pg_catalog", "public"];
867
+ this.qualifyColumns = opts.qualifyColumns || "if_more_than_one_table";
868
+ }
869
+ /** Connect to the database and load schema metadata */
870
+ async refresh() {
871
+ if (!this.connectionString) {
872
+ throw new Error("No connection string provided");
873
+ }
874
+ this.pool = new import_pg.Pool({ connectionString: this.connectionString, max: 2 });
875
+ try {
876
+ const [meta, sp] = await Promise.all([
877
+ fetchMetadata(this.pool),
878
+ fetchSearchPath(this.pool)
879
+ ]);
880
+ this.metadata = meta;
881
+ this.searchPath = sp;
882
+ } finally {
883
+ await this.pool.end();
884
+ this.pool = null;
885
+ }
886
+ }
887
+ /** Close any open connections */
888
+ async close() {
889
+ if (this.pool) {
890
+ await this.pool.end();
891
+ this.pool = null;
892
+ }
893
+ }
894
+ /** Set metadata directly (for testing or pre-loaded scenarios) */
895
+ setMetadata(meta, searchPath) {
896
+ this.metadata = meta;
897
+ if (searchPath) this.searchPath = searchPath;
898
+ }
899
+ /**
900
+ * Get completions for the given SQL text.
901
+ * @param text - The full SQL text typed so far
902
+ * @param cursorPos - Cursor position (defaults to end of text)
903
+ * @returns Array of completion suggestions
904
+ */
905
+ complete(text, cursorPos) {
906
+ const pos = cursorPos ?? text.length;
907
+ const textBeforeCursor = text.slice(0, pos);
908
+ const fullText = text;
909
+ const suggestions = suggestType(fullText, textBeforeCursor);
910
+ const completions = [];
911
+ for (const suggestion of suggestions) {
912
+ switch (suggestion.type) {
913
+ case "keyword":
914
+ completions.push(...this.getKeywordCompletions(suggestion.lastToken));
915
+ break;
916
+ case "table":
917
+ completions.push(...this.getTableCompletions(suggestion.schema));
918
+ break;
919
+ case "view":
920
+ completions.push(...this.getViewCompletions(suggestion.schema));
921
+ break;
922
+ case "column":
923
+ completions.push(...this.getColumnCompletions(
924
+ suggestion.tableRefs,
925
+ suggestion.qualifiable,
926
+ suggestion.parent
927
+ ));
928
+ break;
929
+ case "function":
930
+ completions.push(...this.getFunctionCompletions(suggestion.schema));
931
+ break;
932
+ case "schema":
933
+ completions.push(...this.getSchemaCompletions());
934
+ break;
935
+ case "from_clause":
936
+ completions.push(...this.getTableCompletions(suggestion.schema));
937
+ completions.push(...this.getViewCompletions(suggestion.schema));
938
+ completions.push(...this.getFunctionCompletions(suggestion.schema));
939
+ break;
940
+ case "join":
941
+ completions.push(...this.getJoinCompletions(suggestion.tableRefs, suggestion.schema));
942
+ break;
943
+ case "join_condition":
944
+ completions.push(...this.getJoinConditionCompletions(suggestion.tableRefs, suggestion.parent));
945
+ break;
946
+ case "alias":
947
+ completions.push(...suggestion.aliases.map((a) => ({
948
+ text: a,
949
+ type: "alias"
950
+ })));
951
+ break;
952
+ case "database":
953
+ break;
954
+ case "datatype":
955
+ completions.push(...this.getDatatypeCompletions(suggestion.schema));
956
+ break;
957
+ }
958
+ }
959
+ const wordMatch = textBeforeCursor.match(/([A-Za-z0-9_."$]+)$/);
960
+ const word = wordMatch ? wordMatch[1] : "";
961
+ const dotIdx = word.lastIndexOf(".");
962
+ const filterWord = dotIdx >= 0 ? word.slice(dotIdx + 1) : word;
963
+ if (!filterWord) return this.dedup(completions);
964
+ const lower = filterWord.toLowerCase().replace(/^"/, "");
965
+ const filtered = completions.filter((c) => {
966
+ const ct = c.text.toLowerCase();
967
+ return ct.startsWith(lower) || fuzzyMatch(lower, ct);
968
+ });
969
+ filtered.sort((a, b) => {
970
+ const aExact = a.text.toLowerCase().startsWith(lower) ? 0 : 1;
971
+ const bExact = b.text.toLowerCase().startsWith(lower) ? 0 : 1;
972
+ if (aExact !== bExact) return aExact - bExact;
973
+ return a.text.localeCompare(b.text);
974
+ });
975
+ return this.dedup(filtered);
976
+ }
977
+ dedup(completions) {
978
+ const seen = /* @__PURE__ */ new Set();
979
+ return completions.filter((c) => {
980
+ const key = `${c.type}:${c.text}`;
981
+ if (seen.has(key)) return false;
982
+ seen.add(key);
983
+ return true;
984
+ });
985
+ }
986
+ getKeywordCompletions(lastToken) {
987
+ let kws;
988
+ if (lastToken && KEYWORD_TREE[lastToken]) {
989
+ kws = KEYWORD_TREE[lastToken];
990
+ if (kws.length === 0) kws = KEYWORDS;
991
+ } else {
992
+ kws = KEYWORDS;
993
+ }
994
+ return kws.map((k) => ({ text: k, type: "keyword" }));
995
+ }
996
+ getSchemaCompletions() {
997
+ const schemas = /* @__PURE__ */ new Set();
998
+ for (const kind of ["tables", "views", "functions", "datatypes"]) {
999
+ for (const s of Object.keys(this.metadata[kind])) {
1000
+ if (!s.startsWith("pg_")) schemas.add(s);
1001
+ }
1002
+ }
1003
+ return [...schemas].map((s) => ({ text: s, type: "schema" }));
1004
+ }
1005
+ getTableCompletions(schema) {
1006
+ const results = [];
1007
+ const schemas = schema ? [schema] : this.getVisibleSchemas();
1008
+ for (const s of schemas) {
1009
+ const tables = this.metadata.tables[s];
1010
+ if (!tables) continue;
1011
+ for (const name of Object.keys(tables)) {
1012
+ if (!schema && name.startsWith("pg_")) continue;
1013
+ const prefix = !schema && !this.searchPath.includes(s) ? `${s}.` : "";
1014
+ results.push({ text: prefix + name, type: "table" });
1015
+ }
1016
+ }
1017
+ return results;
1018
+ }
1019
+ getViewCompletions(schema) {
1020
+ const results = [];
1021
+ const schemas = schema ? [schema] : this.getVisibleSchemas();
1022
+ for (const s of schemas) {
1023
+ const views = this.metadata.views[s];
1024
+ if (!views) continue;
1025
+ for (const name of Object.keys(views)) {
1026
+ if (!schema && name.startsWith("pg_")) continue;
1027
+ const prefix = !schema && !this.searchPath.includes(s) ? `${s}.` : "";
1028
+ results.push({ text: prefix + name, type: "view" });
1029
+ }
1030
+ }
1031
+ return results;
1032
+ }
1033
+ getColumnCompletions(tableRefs, qualifiable, parent) {
1034
+ const results = [];
1035
+ const doQualify = qualifiable && {
1036
+ always: true,
1037
+ never: false,
1038
+ if_more_than_one_table: tableRefs.length > 1
1039
+ }[this.qualifyColumns];
1040
+ for (const tref of tableRefs) {
1041
+ const cols = this.findColumns(tref);
1042
+ const ref = tableRef(tref);
1043
+ for (const col of cols) {
1044
+ const text = doQualify ? `${ref}.${col.name}` : col.name;
1045
+ results.push({
1046
+ text,
1047
+ type: "column",
1048
+ meta: col.datatype
1049
+ });
1050
+ }
1051
+ }
1052
+ return results;
1053
+ }
1054
+ getFunctionCompletions(schema) {
1055
+ const results = [];
1056
+ const schemas = schema ? [schema] : this.getVisibleSchemas();
1057
+ for (const s of schemas) {
1058
+ const funcs = this.metadata.functions[s];
1059
+ if (!funcs) continue;
1060
+ for (const name of Object.keys(funcs)) {
1061
+ const prefix = schema || !this.searchPath.includes(s) ? `${s}.` : "";
1062
+ results.push({ text: prefix + name, type: "function" });
1063
+ }
1064
+ }
1065
+ if (!schema) {
1066
+ for (const f of BUILTIN_FUNCTIONS) {
1067
+ results.push({ text: f, type: "function" });
1068
+ }
1069
+ }
1070
+ return results;
1071
+ }
1072
+ getJoinCompletions(tableRefs, schema) {
1073
+ const results = [];
1074
+ for (const tref of tableRefs) {
1075
+ const cols = this.findColumns(tref);
1076
+ for (const col of cols) {
1077
+ for (const fk of col.foreignKeys) {
1078
+ const isChild = col.name === fk.childColumn && this.matchesTable(tref, fk.childSchema, fk.childTable);
1079
+ const otherSchema = isChild ? fk.parentSchema : fk.childSchema;
1080
+ const otherTable = isChild ? fk.parentTable : fk.childTable;
1081
+ const otherCol = isChild ? fk.parentColumn : fk.childColumn;
1082
+ const thisCol = isChild ? fk.childColumn : fk.parentColumn;
1083
+ if (schema && otherSchema !== schema) continue;
1084
+ const ref = tableRef(tref);
1085
+ const joinText = `${otherTable} ON ${otherTable}.${otherCol} = ${ref}.${thisCol}`;
1086
+ results.push({ text: joinText, type: "join", meta: "fk join" });
1087
+ }
1088
+ }
1089
+ }
1090
+ return results;
1091
+ }
1092
+ getJoinConditionCompletions(tableRefs, parent) {
1093
+ const results = [];
1094
+ if (tableRefs.length < 2) return results;
1095
+ const lastRef = tableRefs[tableRefs.length - 1];
1096
+ const lastCols = this.findColumns(lastRef);
1097
+ const lastRefName = tableRef(lastRef);
1098
+ for (const col of lastCols) {
1099
+ for (const fk of col.foreignKeys) {
1100
+ for (const otherRef of tableRefs.slice(0, -1)) {
1101
+ const otherRefName = tableRef(otherRef);
1102
+ const isChild = col.name === fk.childColumn && this.matchesTable(lastRef, fk.childSchema, fk.childTable);
1103
+ if (isChild) {
1104
+ if (this.matchesTable(otherRef, fk.parentSchema, fk.parentTable)) {
1105
+ results.push({
1106
+ text: `${lastRefName}.${fk.childColumn} = ${otherRefName}.${fk.parentColumn}`,
1107
+ type: "join",
1108
+ meta: "fk join"
1109
+ });
1110
+ }
1111
+ } else {
1112
+ if (this.matchesTable(otherRef, fk.childSchema, fk.childTable)) {
1113
+ results.push({
1114
+ text: `${lastRefName}.${fk.parentColumn} = ${otherRefName}.${fk.childColumn}`,
1115
+ type: "join",
1116
+ meta: "fk join"
1117
+ });
1118
+ }
1119
+ }
1120
+ }
1121
+ }
1122
+ }
1123
+ for (const col of lastCols) {
1124
+ for (const otherRef of tableRefs.slice(0, -1)) {
1125
+ const otherCols = this.findColumns(otherRef);
1126
+ const match = otherCols.find((c) => c.name === col.name);
1127
+ if (match) {
1128
+ const otherRefName = tableRef(otherRef);
1129
+ const text = `${lastRefName}.${col.name} = ${otherRefName}.${col.name}`;
1130
+ if (!results.some((r) => r.text === text)) {
1131
+ results.push({ text, type: "join", meta: "name join" });
1132
+ }
1133
+ }
1134
+ }
1135
+ }
1136
+ return results;
1137
+ }
1138
+ getDatatypeCompletions(schema) {
1139
+ const results = [];
1140
+ const schemas = schema ? [schema] : this.getVisibleSchemas();
1141
+ for (const s of schemas) {
1142
+ const types = this.metadata.datatypes[s];
1143
+ if (!types) continue;
1144
+ for (const name of Object.keys(types)) {
1145
+ results.push({ text: name, type: "datatype" });
1146
+ }
1147
+ }
1148
+ if (!schema) {
1149
+ for (const t of [
1150
+ "integer",
1151
+ "bigint",
1152
+ "smallint",
1153
+ "serial",
1154
+ "bigserial",
1155
+ "real",
1156
+ "double precision",
1157
+ "numeric",
1158
+ "decimal",
1159
+ "text",
1160
+ "varchar",
1161
+ "char",
1162
+ "boolean",
1163
+ "bool",
1164
+ "date",
1165
+ "time",
1166
+ "timestamp",
1167
+ "timestamptz",
1168
+ "interval",
1169
+ "json",
1170
+ "jsonb",
1171
+ "uuid",
1172
+ "bytea",
1173
+ "xml"
1174
+ ]) {
1175
+ results.push({ text: t, type: "datatype" });
1176
+ }
1177
+ }
1178
+ return results;
1179
+ }
1180
+ /** Find columns for a table reference, searching through schemas */
1181
+ findColumns(tref) {
1182
+ const schemas = tref.schema ? [tref.schema] : this.searchPath;
1183
+ for (const s of schemas) {
1184
+ const cols = this.metadata.tables[s]?.[tref.name] || this.metadata.views[s]?.[tref.name];
1185
+ if (cols) return cols;
1186
+ }
1187
+ return [];
1188
+ }
1189
+ matchesTable(tref, schema, table) {
1190
+ if (tref.schema) return tref.schema === schema && tref.name === table;
1191
+ return tref.name === table && this.searchPath.includes(schema);
1192
+ }
1193
+ getVisibleSchemas() {
1194
+ const all = new Set(this.searchPath);
1195
+ for (const kind of ["tables", "views", "functions", "datatypes"]) {
1196
+ for (const s of Object.keys(this.metadata[kind])) {
1197
+ all.add(s);
1198
+ }
1199
+ }
1200
+ return [...all];
1201
+ }
1202
+ };
1203
+ function fuzzyMatch(pattern, text) {
1204
+ let pi = 0;
1205
+ for (let ti = 0; ti < text.length && pi < pattern.length; ti++) {
1206
+ if (text[ti] === pattern[pi]) pi++;
1207
+ }
1208
+ return pi === pattern.length;
1209
+ }
1210
+ // Annotate the CommonJS export names for ESM import in node:
1211
+ 0 && (module.exports = {
1212
+ BUILTIN_FUNCTIONS,
1213
+ KEYWORDS,
1214
+ KEYWORD_TREE,
1215
+ PgCompleter,
1216
+ extractTables,
1217
+ suggestType
1218
+ });
1219
+ //# sourceMappingURL=index.js.map