@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/README.md +114 -0
- package/dist/cli.js +1269 -0
- package/dist/index.d.mts +181 -0
- package/dist/index.d.ts +181 -0
- package/dist/index.js +1219 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +1187 -0
- package/dist/index.mjs.map +1 -0
- package/dist/mcp.js +1303 -0
- package/package.json +41 -0
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
|