jl-db-comp 0.1.0__py3-none-any.whl → 0.1.2__py3-none-any.whl
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.
- jl_db_comp/_version.py +1 -1
- jl_db_comp/connections.py +173 -0
- jl_db_comp/routes.py +353 -7
- jl_db_comp/tests/test_routes.py +1 -1
- {jl_db_comp/labextension → jl_db_comp-0.1.2.data/data/share/jupyter/labextensions/jl_db_comp}/package.json +3 -2
- {jl_db_comp-0.1.0.data → jl_db_comp-0.1.2.data}/data/share/jupyter/labextensions/jl_db_comp/schemas/jl_db_comp/package.json.orig +2 -1
- {jl_db_comp-0.1.0.data → jl_db_comp-0.1.2.data}/data/share/jupyter/labextensions/jl_db_comp/schemas/jl_db_comp/plugin.json +4 -4
- jl_db_comp-0.1.2.data/data/share/jupyter/labextensions/jl_db_comp/static/171.d366980651e0db8d978c.js +1 -0
- jl_db_comp-0.1.2.data/data/share/jupyter/labextensions/jl_db_comp/static/728.6552504d5b9b27551bc5.js +1 -0
- jl_db_comp-0.1.2.data/data/share/jupyter/labextensions/jl_db_comp/static/remoteEntry.2f5032a1d7560953515d.js +1 -0
- jl_db_comp-0.1.2.data/data/share/jupyter/labextensions/jl_db_comp/static/third-party-licenses.json +16 -0
- {jl_db_comp-0.1.0.dist-info → jl_db_comp-0.1.2.dist-info}/METADATA +103 -1
- jl_db_comp-0.1.2.dist-info/RECORD +20 -0
- jl_db_comp/labextension/build_log.json +0 -728
- jl_db_comp/labextension/schemas/jl_db_comp/package.json.orig +0 -214
- jl_db_comp/labextension/schemas/jl_db_comp/plugin.json +0 -27
- jl_db_comp/labextension/static/lib_index_js.a0969ed73da70f2cc451.js +0 -561
- jl_db_comp/labextension/static/lib_index_js.a0969ed73da70f2cc451.js.map +0 -1
- jl_db_comp/labextension/static/remoteEntry.5763ae02737e035e938c.js +0 -560
- jl_db_comp/labextension/static/remoteEntry.5763ae02737e035e938c.js.map +0 -1
- jl_db_comp/labextension/static/style.js +0 -4
- jl_db_comp/labextension/static/style_index_js.5364c7419a6b9db5d727.js +0 -508
- jl_db_comp/labextension/static/style_index_js.5364c7419a6b9db5d727.js.map +0 -1
- jl_db_comp-0.1.0.data/data/share/jupyter/labextensions/jl_db_comp/build_log.json +0 -728
- jl_db_comp-0.1.0.data/data/share/jupyter/labextensions/jl_db_comp/package.json +0 -219
- jl_db_comp-0.1.0.data/data/share/jupyter/labextensions/jl_db_comp/static/lib_index_js.a0969ed73da70f2cc451.js +0 -561
- jl_db_comp-0.1.0.data/data/share/jupyter/labextensions/jl_db_comp/static/lib_index_js.a0969ed73da70f2cc451.js.map +0 -1
- jl_db_comp-0.1.0.data/data/share/jupyter/labextensions/jl_db_comp/static/remoteEntry.5763ae02737e035e938c.js +0 -560
- jl_db_comp-0.1.0.data/data/share/jupyter/labextensions/jl_db_comp/static/remoteEntry.5763ae02737e035e938c.js.map +0 -1
- jl_db_comp-0.1.0.data/data/share/jupyter/labextensions/jl_db_comp/static/style_index_js.5364c7419a6b9db5d727.js +0 -508
- jl_db_comp-0.1.0.data/data/share/jupyter/labextensions/jl_db_comp/static/style_index_js.5364c7419a6b9db5d727.js.map +0 -1
- jl_db_comp-0.1.0.dist-info/RECORD +0 -33
- {jl_db_comp-0.1.0.data → jl_db_comp-0.1.2.data}/data/etc/jupyter/jupyter_server_config.d/jl_db_comp.json +0 -0
- {jl_db_comp-0.1.0.data → jl_db_comp-0.1.2.data}/data/share/jupyter/labextensions/jl_db_comp/install.json +0 -0
- {jl_db_comp-0.1.0.data → jl_db_comp-0.1.2.data}/data/share/jupyter/labextensions/jl_db_comp/static/style.js +0 -0
- {jl_db_comp-0.1.0.dist-info → jl_db_comp-0.1.2.dist-info}/WHEEL +0 -0
- {jl_db_comp-0.1.0.dist-info → jl_db_comp-0.1.2.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,561 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
(self["webpackChunkjl_db_comp"] = self["webpackChunkjl_db_comp"] || []).push([["lib_index_js"],{
|
|
3
|
-
|
|
4
|
-
/***/ "./lib/api.js"
|
|
5
|
-
/*!********************!*\
|
|
6
|
-
!*** ./lib/api.js ***!
|
|
7
|
-
\********************/
|
|
8
|
-
(__unused_webpack_module, __webpack_exports__, __webpack_require__) {
|
|
9
|
-
|
|
10
|
-
__webpack_require__.r(__webpack_exports__);
|
|
11
|
-
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
|
|
12
|
-
/* harmony export */ fetchPostgresCompletions: () => (/* binding */ fetchPostgresCompletions)
|
|
13
|
-
/* harmony export */ });
|
|
14
|
-
/* harmony import */ var _jupyterlab_services__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @jupyterlab/services */ "webpack/sharing/consume/default/@jupyterlab/services");
|
|
15
|
-
/* harmony import */ var _jupyterlab_services__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_jupyterlab_services__WEBPACK_IMPORTED_MODULE_0__);
|
|
16
|
-
/* harmony import */ var _request__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./request */ "./lib/request.js");
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Fetch PostgreSQL table and column completions from the server.
|
|
21
|
-
*
|
|
22
|
-
* @param dbUrl - PostgreSQL connection string (optional if using env var)
|
|
23
|
-
* @param prefix - Optional prefix to filter completions
|
|
24
|
-
* @param schema - Database schema name (default: 'public')
|
|
25
|
-
* @param tableName - Optional table name to filter columns (only returns columns from this table)
|
|
26
|
-
* @param schemaOrTable - Ambiguous identifier that could be either a schema or table name (backend will determine)
|
|
27
|
-
* @param jsonbColumn - Optional JSONB column name to extract keys from
|
|
28
|
-
* @param jsonbPath - Optional JSONB path for nested key extraction
|
|
29
|
-
* @returns Array of completion items
|
|
30
|
-
*/
|
|
31
|
-
async function fetchPostgresCompletions(dbUrl, prefix = '', schema = 'public', tableName, schemaOrTable, jsonbColumn, jsonbPath) {
|
|
32
|
-
try {
|
|
33
|
-
const params = new URLSearchParams();
|
|
34
|
-
if (dbUrl) {
|
|
35
|
-
params.append('db_url', encodeURIComponent(dbUrl));
|
|
36
|
-
}
|
|
37
|
-
if (prefix) {
|
|
38
|
-
params.append('prefix', prefix);
|
|
39
|
-
}
|
|
40
|
-
params.append('schema', schema);
|
|
41
|
-
if (tableName) {
|
|
42
|
-
params.append('table', tableName);
|
|
43
|
-
}
|
|
44
|
-
if (schemaOrTable) {
|
|
45
|
-
params.append('schema_or_table', schemaOrTable);
|
|
46
|
-
}
|
|
47
|
-
if (jsonbColumn) {
|
|
48
|
-
params.append('jsonb_column', jsonbColumn);
|
|
49
|
-
if (jsonbPath && jsonbPath.length > 0) {
|
|
50
|
-
params.append('jsonb_path', JSON.stringify(jsonbPath));
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
const endpoint = `completions?${params.toString()}`;
|
|
54
|
-
const response = await (0,_request__WEBPACK_IMPORTED_MODULE_1__.requestAPI)(endpoint, {
|
|
55
|
-
method: 'GET'
|
|
56
|
-
});
|
|
57
|
-
if (response.status === 'error') {
|
|
58
|
-
console.error('PostgreSQL completion error:', response.message);
|
|
59
|
-
return [];
|
|
60
|
-
}
|
|
61
|
-
// If JSONB keys requested, return only those
|
|
62
|
-
if (jsonbColumn && response.jsonbKeys) {
|
|
63
|
-
return response.jsonbKeys;
|
|
64
|
-
}
|
|
65
|
-
// Return appropriate results based on context
|
|
66
|
-
if (tableName || schemaOrTable) {
|
|
67
|
-
// If we have table context, prefer columns
|
|
68
|
-
return response.columns.length > 0 ? response.columns : response.tables;
|
|
69
|
-
}
|
|
70
|
-
return [...response.tables, ...response.columns];
|
|
71
|
-
}
|
|
72
|
-
catch (err) {
|
|
73
|
-
if (err instanceof _jupyterlab_services__WEBPACK_IMPORTED_MODULE_0__.ServerConnection.ResponseError) {
|
|
74
|
-
const status = err.response.status;
|
|
75
|
-
let detail = err.message;
|
|
76
|
-
if (typeof detail === 'string' &&
|
|
77
|
-
(detail.includes('<!DOCTYPE') || detail.includes('<html'))) {
|
|
78
|
-
detail = `HTML error page (${detail.substring(0, 100)}...)`;
|
|
79
|
-
}
|
|
80
|
-
console.error(`PostgreSQL completions API failed (${status}): ${detail}`);
|
|
81
|
-
}
|
|
82
|
-
else {
|
|
83
|
-
const msg = err instanceof Error ? err.message : 'Unknown error';
|
|
84
|
-
console.error(`PostgreSQL completions API failed: ${msg}`);
|
|
85
|
-
}
|
|
86
|
-
return [];
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
/***/ },
|
|
92
|
-
|
|
93
|
-
/***/ "./lib/index.js"
|
|
94
|
-
/*!**********************!*\
|
|
95
|
-
!*** ./lib/index.js ***!
|
|
96
|
-
\**********************/
|
|
97
|
-
(__unused_webpack_module, __webpack_exports__, __webpack_require__) {
|
|
98
|
-
|
|
99
|
-
__webpack_require__.r(__webpack_exports__);
|
|
100
|
-
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
|
|
101
|
-
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
|
|
102
|
-
/* harmony export */ });
|
|
103
|
-
/* harmony import */ var _jupyterlab_completer__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @jupyterlab/completer */ "webpack/sharing/consume/default/@jupyterlab/completer");
|
|
104
|
-
/* harmony import */ var _jupyterlab_completer__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_jupyterlab_completer__WEBPACK_IMPORTED_MODULE_0__);
|
|
105
|
-
/* harmony import */ var _jupyterlab_settingregistry__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @jupyterlab/settingregistry */ "webpack/sharing/consume/default/@jupyterlab/settingregistry");
|
|
106
|
-
/* harmony import */ var _jupyterlab_settingregistry__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(_jupyterlab_settingregistry__WEBPACK_IMPORTED_MODULE_1__);
|
|
107
|
-
/* harmony import */ var _provider__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./provider */ "./lib/provider.js");
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* Plugin ID constant.
|
|
113
|
-
*/
|
|
114
|
-
const PLUGIN_ID = 'jl_db_comp:plugin';
|
|
115
|
-
/**
|
|
116
|
-
* Initialization data for the jl_db_comp extension.
|
|
117
|
-
*
|
|
118
|
-
* This plugin provides PostgreSQL table and column name completions
|
|
119
|
-
* in JupyterLab notebooks and editors when typing SQL queries.
|
|
120
|
-
*/
|
|
121
|
-
const plugin = {
|
|
122
|
-
id: PLUGIN_ID,
|
|
123
|
-
description: 'A JupyterLab extension to complete db queries in jupyterlab notebooks',
|
|
124
|
-
autoStart: true,
|
|
125
|
-
requires: [_jupyterlab_completer__WEBPACK_IMPORTED_MODULE_0__.ICompletionProviderManager],
|
|
126
|
-
optional: [_jupyterlab_settingregistry__WEBPACK_IMPORTED_MODULE_1__.ISettingRegistry],
|
|
127
|
-
activate: (app, completionManager, settingRegistry) => {
|
|
128
|
-
let provider;
|
|
129
|
-
if (settingRegistry) {
|
|
130
|
-
settingRegistry
|
|
131
|
-
.load(PLUGIN_ID)
|
|
132
|
-
.then(settings => {
|
|
133
|
-
provider = new _provider__WEBPACK_IMPORTED_MODULE_2__.PostgresCompletionProvider(settings);
|
|
134
|
-
completionManager.registerProvider(provider);
|
|
135
|
-
console.log('JupyterLab extension jl_db_comp is activated!');
|
|
136
|
-
})
|
|
137
|
-
.catch(reason => {
|
|
138
|
-
console.error('Failed to load settings for jl_db_comp:', reason);
|
|
139
|
-
provider = new _provider__WEBPACK_IMPORTED_MODULE_2__.PostgresCompletionProvider();
|
|
140
|
-
completionManager.registerProvider(provider);
|
|
141
|
-
console.log('JupyterLab extension jl_db_comp is activated!');
|
|
142
|
-
});
|
|
143
|
-
}
|
|
144
|
-
else {
|
|
145
|
-
provider = new _provider__WEBPACK_IMPORTED_MODULE_2__.PostgresCompletionProvider();
|
|
146
|
-
completionManager.registerProvider(provider);
|
|
147
|
-
console.log('JupyterLab extension jl_db_comp is activated!');
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
};
|
|
151
|
-
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (plugin);
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
/***/ },
|
|
155
|
-
|
|
156
|
-
/***/ "./lib/provider.js"
|
|
157
|
-
/*!*************************!*\
|
|
158
|
-
!*** ./lib/provider.js ***!
|
|
159
|
-
\*************************/
|
|
160
|
-
(__unused_webpack_module, __webpack_exports__, __webpack_require__) {
|
|
161
|
-
|
|
162
|
-
__webpack_require__.r(__webpack_exports__);
|
|
163
|
-
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
|
|
164
|
-
/* harmony export */ PostgresCompletionProvider: () => (/* binding */ PostgresCompletionProvider)
|
|
165
|
-
/* harmony export */ });
|
|
166
|
-
/* harmony import */ var _api__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./api */ "./lib/api.js");
|
|
167
|
-
|
|
168
|
-
/**
|
|
169
|
-
* PostgreSQL completion provider for JupyterLab.
|
|
170
|
-
*
|
|
171
|
-
* Provides table and column name completions from PostgreSQL databases
|
|
172
|
-
* when editing SQL-like code in notebooks and editors.
|
|
173
|
-
*/
|
|
174
|
-
class PostgresCompletionProvider {
|
|
175
|
-
/**
|
|
176
|
-
* Create a new PostgresCompletionProvider.
|
|
177
|
-
*
|
|
178
|
-
* @param settings - Optional settings registry to load database configuration
|
|
179
|
-
*/
|
|
180
|
-
constructor(settings) {
|
|
181
|
-
this.identifier = 'jl_db_comp:postgres-completer';
|
|
182
|
-
this.renderer = null;
|
|
183
|
-
this._cache = new Map();
|
|
184
|
-
this._cacheTTL = 5 * 60 * 1000; // 5 minutes in milliseconds
|
|
185
|
-
this._settings = null;
|
|
186
|
-
this._dbUrl = '';
|
|
187
|
-
this._schema = 'public';
|
|
188
|
-
this._enabled = true;
|
|
189
|
-
/**
|
|
190
|
-
* SQL keywords that trigger completion.
|
|
191
|
-
*/
|
|
192
|
-
this._sqlKeywords = [
|
|
193
|
-
'select',
|
|
194
|
-
'from',
|
|
195
|
-
'join',
|
|
196
|
-
'where',
|
|
197
|
-
'insert',
|
|
198
|
-
'update',
|
|
199
|
-
'delete',
|
|
200
|
-
'inner',
|
|
201
|
-
'left',
|
|
202
|
-
'right',
|
|
203
|
-
'outer',
|
|
204
|
-
'on',
|
|
205
|
-
'group',
|
|
206
|
-
'order',
|
|
207
|
-
'by',
|
|
208
|
-
'having',
|
|
209
|
-
'into',
|
|
210
|
-
'values',
|
|
211
|
-
'set'
|
|
212
|
-
];
|
|
213
|
-
if (settings) {
|
|
214
|
-
this._settings = settings;
|
|
215
|
-
this._loadSettings();
|
|
216
|
-
settings.changed.connect(() => {
|
|
217
|
-
this._loadSettings();
|
|
218
|
-
});
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
/**
|
|
222
|
-
* Load database configuration from settings.
|
|
223
|
-
*/
|
|
224
|
-
_loadSettings() {
|
|
225
|
-
if (!this._settings) {
|
|
226
|
-
return;
|
|
227
|
-
}
|
|
228
|
-
this._dbUrl = this._settings.get('databaseUrl').composite;
|
|
229
|
-
this._schema = this._settings.get('schema').composite;
|
|
230
|
-
this._enabled = this._settings.get('enabled').composite;
|
|
231
|
-
}
|
|
232
|
-
/**
|
|
233
|
-
* Determine if completions should be shown in the current context.
|
|
234
|
-
*
|
|
235
|
-
* Checks for SQL keywords or context that suggests SQL code.
|
|
236
|
-
*/
|
|
237
|
-
async isApplicable(context) {
|
|
238
|
-
if (!this._enabled) {
|
|
239
|
-
return false;
|
|
240
|
-
}
|
|
241
|
-
// Get editor content from context
|
|
242
|
-
const editor = context.editor;
|
|
243
|
-
if (!editor) {
|
|
244
|
-
return false;
|
|
245
|
-
}
|
|
246
|
-
const text = editor.model.sharedModel.getSource();
|
|
247
|
-
if (!text) {
|
|
248
|
-
return false;
|
|
249
|
-
}
|
|
250
|
-
const textLower = text.toLowerCase();
|
|
251
|
-
// Check if any SQL keyword is present
|
|
252
|
-
return this._sqlKeywords.some(keyword => textLower.includes(keyword));
|
|
253
|
-
}
|
|
254
|
-
/**
|
|
255
|
-
* Fetch completion items for the current context.
|
|
256
|
-
*
|
|
257
|
-
* Uses caching to minimize database calls.
|
|
258
|
-
*/
|
|
259
|
-
async fetch(request, context) {
|
|
260
|
-
var _a;
|
|
261
|
-
if (!this._enabled) {
|
|
262
|
-
return { start: request.offset, end: request.offset, items: [] };
|
|
263
|
-
}
|
|
264
|
-
const { text, offset } = request;
|
|
265
|
-
// Extract context: schema, table, and prefix
|
|
266
|
-
const extracted = this._extractContext(text, offset);
|
|
267
|
-
// Create cache key that includes full context
|
|
268
|
-
let cacheKey;
|
|
269
|
-
if (extracted.jsonbColumn) {
|
|
270
|
-
// JSONB key completion: table.column->path
|
|
271
|
-
const pathStr = ((_a = extracted.jsonbPath) === null || _a === void 0 ? void 0 : _a.join('.')) || '';
|
|
272
|
-
const tablePrefix = extracted.schemaOrTable
|
|
273
|
-
? `${extracted.schemaOrTable}.`
|
|
274
|
-
: '';
|
|
275
|
-
cacheKey =
|
|
276
|
-
`${tablePrefix}${extracted.jsonbColumn}->${pathStr}.${extracted.prefix}`.toLowerCase();
|
|
277
|
-
}
|
|
278
|
-
else if (extracted.schema && extracted.tableName) {
|
|
279
|
-
// schema.table.prefix
|
|
280
|
-
cacheKey =
|
|
281
|
-
`${extracted.schema}.${extracted.tableName}.${extracted.prefix}`.toLowerCase();
|
|
282
|
-
}
|
|
283
|
-
else if (extracted.schemaOrTable) {
|
|
284
|
-
// schema.prefix OR table.prefix (ambiguous)
|
|
285
|
-
cacheKey = `${extracted.schemaOrTable}.${extracted.prefix}`.toLowerCase();
|
|
286
|
-
}
|
|
287
|
-
else {
|
|
288
|
-
// just prefix
|
|
289
|
-
cacheKey = extracted.prefix.toLowerCase();
|
|
290
|
-
}
|
|
291
|
-
// Check cache first
|
|
292
|
-
const cached = this._getCached(cacheKey);
|
|
293
|
-
if (cached) {
|
|
294
|
-
return this._formatReply(cached, request.offset, extracted.prefix);
|
|
295
|
-
}
|
|
296
|
-
// Fetch from database
|
|
297
|
-
try {
|
|
298
|
-
const items = await (0,_api__WEBPACK_IMPORTED_MODULE_0__.fetchPostgresCompletions)(this._dbUrl || undefined, extracted.prefix, extracted.schema || this._schema, extracted.tableName, extracted.schemaOrTable, extracted.jsonbColumn, extracted.jsonbPath);
|
|
299
|
-
// Cache the results
|
|
300
|
-
this._cache.set(cacheKey, {
|
|
301
|
-
items,
|
|
302
|
-
timestamp: Date.now()
|
|
303
|
-
});
|
|
304
|
-
return this._formatReply(items, request.offset, extracted.prefix);
|
|
305
|
-
}
|
|
306
|
-
catch (error) {
|
|
307
|
-
console.error('Failed to fetch PostgreSQL completions:', error);
|
|
308
|
-
return { start: request.offset, end: request.offset, items: [] };
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
/**
|
|
312
|
-
* Extract context from the text: prefix being typed, optional table name, optional schema, and JSONB context.
|
|
313
|
-
*
|
|
314
|
-
* Detects patterns like:
|
|
315
|
-
* - "schema.table.col" → { schema: "schema", tableName: "table", prefix: "col" }
|
|
316
|
-
* - "schema.table." → { schema: "schema", tableName: "table", prefix: "" }
|
|
317
|
-
* - "schema.tab" → { schemaOrTable: "schema", prefix: "tab" }
|
|
318
|
-
* - "schema." → { schemaOrTable: "schema", prefix: "" }
|
|
319
|
-
* - "table.col" → { schemaOrTable: "table", prefix: "col" }
|
|
320
|
-
* - "table." → { schemaOrTable: "table", prefix: "" }
|
|
321
|
-
* - "prefix" → { prefix: "prefix" }
|
|
322
|
-
* - "column_name->" → { jsonbColumn: "column_name", jsonbPath: [], prefix: "" }
|
|
323
|
-
* - "column_name->>'key1'->" → { jsonbColumn: "column_name", jsonbPath: ["key1"], prefix: "" }
|
|
324
|
-
* - "table.column_name->>'key'->" → { schemaOrTable: "table", jsonbColumn: "column_name", jsonbPath: ["key"], prefix: "" }
|
|
325
|
-
*
|
|
326
|
-
* Note: For single-dot patterns (schema. or table.), the backend will determine
|
|
327
|
-
* whether it's a schema (list tables) or table (list columns) by checking the database.
|
|
328
|
-
*/
|
|
329
|
-
_extractContext(text, offset) {
|
|
330
|
-
const beforeCursor = text.substring(0, offset);
|
|
331
|
-
// JSONB pattern: Detect -> or ->> operators
|
|
332
|
-
// Examples: metadata-> or content -> or patients.metadata->>'key'->
|
|
333
|
-
if (beforeCursor.includes('->')) {
|
|
334
|
-
// Much simpler approach: find the last -> or ->> and work backwards
|
|
335
|
-
// Look for: word characters, optional dot+word, then ->, then anything
|
|
336
|
-
// Pattern: (word.)?word -> rest
|
|
337
|
-
const simpleMatch = beforeCursor.match(/([\w]+\.)?([\w]+)\s*->\s*(.*)$/);
|
|
338
|
-
if (simpleMatch) {
|
|
339
|
-
const tableOrSchema = simpleMatch[1]
|
|
340
|
-
? simpleMatch[1].slice(0, -1)
|
|
341
|
-
: undefined; // Remove trailing dot
|
|
342
|
-
const columnName = simpleMatch[2];
|
|
343
|
-
const afterOperator = simpleMatch[3];
|
|
344
|
-
// Parse the path after the first ->
|
|
345
|
-
// Example: "'key1'->>'key2'->" or "key1" or ""
|
|
346
|
-
const jsonbPath = [];
|
|
347
|
-
const pathRegex = /['"]?([\w]+)['"]?\s*->/g;
|
|
348
|
-
let pathMatch;
|
|
349
|
-
while ((pathMatch = pathRegex.exec(afterOperator)) !== null) {
|
|
350
|
-
jsonbPath.push(pathMatch[1]);
|
|
351
|
-
}
|
|
352
|
-
// Get the current prefix (what's being typed after the last ->)
|
|
353
|
-
// Remove any keys that are part of the path
|
|
354
|
-
const lastArrowIndex = afterOperator.lastIndexOf('->');
|
|
355
|
-
let currentPrefix = '';
|
|
356
|
-
if (lastArrowIndex >= 0) {
|
|
357
|
-
currentPrefix = afterOperator
|
|
358
|
-
.substring(lastArrowIndex + 2)
|
|
359
|
-
.trim()
|
|
360
|
-
.replace(/['"]/g, '');
|
|
361
|
-
}
|
|
362
|
-
else {
|
|
363
|
-
// No nested path, just get whatever is after the ->
|
|
364
|
-
currentPrefix = afterOperator.trim().replace(/['"]/g, '');
|
|
365
|
-
}
|
|
366
|
-
return {
|
|
367
|
-
schemaOrTable: tableOrSchema,
|
|
368
|
-
jsonbColumn: columnName,
|
|
369
|
-
jsonbPath,
|
|
370
|
-
prefix: currentPrefix
|
|
371
|
-
};
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
// Three-part pattern: schema.table.column
|
|
375
|
-
const threePartMatch = beforeCursor.match(/([\w]+)\.([\w]+)\.([\w]*)$/);
|
|
376
|
-
if (threePartMatch) {
|
|
377
|
-
return {
|
|
378
|
-
schema: threePartMatch[1],
|
|
379
|
-
tableName: threePartMatch[2],
|
|
380
|
-
prefix: threePartMatch[3]
|
|
381
|
-
};
|
|
382
|
-
}
|
|
383
|
-
// Two-part pattern: could be schema.table OR table.column
|
|
384
|
-
// Backend will determine which by checking if first part is a schema
|
|
385
|
-
const twoPartMatch = beforeCursor.match(/([\w]+)\.([\w]*)$/);
|
|
386
|
-
if (twoPartMatch) {
|
|
387
|
-
return {
|
|
388
|
-
schemaOrTable: twoPartMatch[1],
|
|
389
|
-
prefix: twoPartMatch[2]
|
|
390
|
-
};
|
|
391
|
-
}
|
|
392
|
-
// Single word: could be a table name OR a column name
|
|
393
|
-
// Check if there's a FROM clause in the query to determine context
|
|
394
|
-
const wordMatch = beforeCursor.match(/[\w]+$/);
|
|
395
|
-
const prefix = wordMatch ? wordMatch[0] : '';
|
|
396
|
-
// Look for FROM clause in the entire text (before or after cursor)
|
|
397
|
-
// Match patterns like: FROM table, FROM schema.table, FROM table AS alias
|
|
398
|
-
const fullText = text.toLowerCase();
|
|
399
|
-
const fromMatch = fullText.match(/\bfrom\s+([\w]+\.)?[\w]+/);
|
|
400
|
-
if (fromMatch) {
|
|
401
|
-
// Extract the table name (with optional schema)
|
|
402
|
-
const fromClause = fromMatch[0];
|
|
403
|
-
const tableMatch = fromClause.match(/\bfrom\s+(?:([\w]+)\.)?([\w]+)/);
|
|
404
|
-
if (tableMatch) {
|
|
405
|
-
const schema = tableMatch[1];
|
|
406
|
-
const table = tableMatch[2];
|
|
407
|
-
// If we have a schema, return schema.table pattern
|
|
408
|
-
if (schema) {
|
|
409
|
-
return {
|
|
410
|
-
schema,
|
|
411
|
-
tableName: table,
|
|
412
|
-
prefix
|
|
413
|
-
};
|
|
414
|
-
}
|
|
415
|
-
// Otherwise, return table as schemaOrTable (backend will check if it's a table)
|
|
416
|
-
return {
|
|
417
|
-
schemaOrTable: table,
|
|
418
|
-
prefix
|
|
419
|
-
};
|
|
420
|
-
}
|
|
421
|
-
}
|
|
422
|
-
// No FROM clause found, just return prefix (will suggest tables)
|
|
423
|
-
return {
|
|
424
|
-
prefix
|
|
425
|
-
};
|
|
426
|
-
}
|
|
427
|
-
/**
|
|
428
|
-
* Get cached completion items if still valid.
|
|
429
|
-
*/
|
|
430
|
-
_getCached(prefix) {
|
|
431
|
-
const key = prefix.toLowerCase();
|
|
432
|
-
const entry = this._cache.get(key);
|
|
433
|
-
if (!entry) {
|
|
434
|
-
return null;
|
|
435
|
-
}
|
|
436
|
-
const age = Date.now() - entry.timestamp;
|
|
437
|
-
if (age > this._cacheTTL) {
|
|
438
|
-
this._cache.delete(key);
|
|
439
|
-
return null;
|
|
440
|
-
}
|
|
441
|
-
return entry.items;
|
|
442
|
-
}
|
|
443
|
-
/**
|
|
444
|
-
* Format completion items into the reply format expected by JupyterLab.
|
|
445
|
-
*/
|
|
446
|
-
_formatReply(items, offset, prefix) {
|
|
447
|
-
const start = offset - prefix.length;
|
|
448
|
-
const end = offset;
|
|
449
|
-
const formattedItems = items.map(item => {
|
|
450
|
-
let label = item.name;
|
|
451
|
-
let insertText = item.name;
|
|
452
|
-
// Add quotes around JSONB keys
|
|
453
|
-
if (item.type === 'jsonb_key') {
|
|
454
|
-
insertText = `'${item.name}'`;
|
|
455
|
-
}
|
|
456
|
-
// Add table context for columns
|
|
457
|
-
if (item.type === 'column' && item.table) {
|
|
458
|
-
label = `${item.name} (${item.table})`;
|
|
459
|
-
}
|
|
460
|
-
// Add type-specific icon
|
|
461
|
-
let typeIcon = '📊'; // Default for columns
|
|
462
|
-
let sortText = item.name; // Default sort order
|
|
463
|
-
if (item.type === 'table') {
|
|
464
|
-
typeIcon = '📋';
|
|
465
|
-
}
|
|
466
|
-
else if (item.type === 'view') {
|
|
467
|
-
typeIcon = '👁️';
|
|
468
|
-
}
|
|
469
|
-
else if (item.type === 'jsonb_key') {
|
|
470
|
-
typeIcon = '🔑';
|
|
471
|
-
// Use 0000 prefix to sort JSONB keys to the top (numbers sort before letters)
|
|
472
|
-
sortText = `0000${item.name}`;
|
|
473
|
-
}
|
|
474
|
-
// Build documentation
|
|
475
|
-
let documentation;
|
|
476
|
-
if (item.type === 'column' && item.dataType && item.table) {
|
|
477
|
-
documentation = `${item.table}.${item.name}: ${item.dataType}`;
|
|
478
|
-
}
|
|
479
|
-
else if (item.type === 'jsonb_key' && item.keyPath) {
|
|
480
|
-
documentation = `JSONB key: ${item.keyPath.join(' -> ')}`;
|
|
481
|
-
}
|
|
482
|
-
return {
|
|
483
|
-
label: `${typeIcon} ${label}`,
|
|
484
|
-
insertText,
|
|
485
|
-
sortText,
|
|
486
|
-
type: item.type,
|
|
487
|
-
documentation
|
|
488
|
-
};
|
|
489
|
-
});
|
|
490
|
-
return {
|
|
491
|
-
start,
|
|
492
|
-
end,
|
|
493
|
-
items: formattedItems
|
|
494
|
-
};
|
|
495
|
-
}
|
|
496
|
-
/**
|
|
497
|
-
* Clear the completion cache.
|
|
498
|
-
*/
|
|
499
|
-
clearCache() {
|
|
500
|
-
this._cache.clear();
|
|
501
|
-
}
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
/***/ },
|
|
506
|
-
|
|
507
|
-
/***/ "./lib/request.js"
|
|
508
|
-
/*!************************!*\
|
|
509
|
-
!*** ./lib/request.js ***!
|
|
510
|
-
\************************/
|
|
511
|
-
(__unused_webpack_module, __webpack_exports__, __webpack_require__) {
|
|
512
|
-
|
|
513
|
-
__webpack_require__.r(__webpack_exports__);
|
|
514
|
-
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
|
|
515
|
-
/* harmony export */ requestAPI: () => (/* binding */ requestAPI)
|
|
516
|
-
/* harmony export */ });
|
|
517
|
-
/* harmony import */ var _jupyterlab_coreutils__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @jupyterlab/coreutils */ "webpack/sharing/consume/default/@jupyterlab/coreutils");
|
|
518
|
-
/* harmony import */ var _jupyterlab_coreutils__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_jupyterlab_coreutils__WEBPACK_IMPORTED_MODULE_0__);
|
|
519
|
-
/* harmony import */ var _jupyterlab_services__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @jupyterlab/services */ "webpack/sharing/consume/default/@jupyterlab/services");
|
|
520
|
-
/* harmony import */ var _jupyterlab_services__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(_jupyterlab_services__WEBPACK_IMPORTED_MODULE_1__);
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
/**
|
|
524
|
-
* Call the server extension
|
|
525
|
-
*
|
|
526
|
-
* @param endPoint API REST end point for the extension
|
|
527
|
-
* @param init Initial values for the request
|
|
528
|
-
* @returns The response body interpreted as JSON
|
|
529
|
-
*/
|
|
530
|
-
async function requestAPI(endPoint = '', init = {}) {
|
|
531
|
-
// Make request to Jupyter API
|
|
532
|
-
const settings = _jupyterlab_services__WEBPACK_IMPORTED_MODULE_1__.ServerConnection.makeSettings();
|
|
533
|
-
const requestUrl = _jupyterlab_coreutils__WEBPACK_IMPORTED_MODULE_0__.URLExt.join(settings.baseUrl, 'jl-db-comp', // our server extension's API namespace
|
|
534
|
-
endPoint);
|
|
535
|
-
let response;
|
|
536
|
-
try {
|
|
537
|
-
response = await _jupyterlab_services__WEBPACK_IMPORTED_MODULE_1__.ServerConnection.makeRequest(requestUrl, init, settings);
|
|
538
|
-
}
|
|
539
|
-
catch (error) {
|
|
540
|
-
throw new _jupyterlab_services__WEBPACK_IMPORTED_MODULE_1__.ServerConnection.NetworkError(error);
|
|
541
|
-
}
|
|
542
|
-
let data = await response.text();
|
|
543
|
-
if (data.length > 0) {
|
|
544
|
-
try {
|
|
545
|
-
data = JSON.parse(data);
|
|
546
|
-
}
|
|
547
|
-
catch (error) {
|
|
548
|
-
console.log('Not a JSON response body.', response);
|
|
549
|
-
}
|
|
550
|
-
}
|
|
551
|
-
if (!response.ok) {
|
|
552
|
-
throw new _jupyterlab_services__WEBPACK_IMPORTED_MODULE_1__.ServerConnection.ResponseError(response, data.message || data);
|
|
553
|
-
}
|
|
554
|
-
return data;
|
|
555
|
-
}
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
/***/ }
|
|
559
|
-
|
|
560
|
-
}]);
|
|
561
|
-
//# sourceMappingURL=lib_index_js.a0969ed73da70f2cc451.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"lib_index_js.a0969ed73da70f2cc451.js","mappings":"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","sources":["webpack://jl_db_comp/./src/api.ts","webpack://jl_db_comp/./src/index.ts","webpack://jl_db_comp/./src/provider.ts","webpack://jl_db_comp/./src/request.ts"],"sourcesContent":["import { ServerConnection } from '@jupyterlab/services';\nimport { requestAPI } from './request';\n\n/**\n * Database completion item representing a table, column, or JSONB key.\n */\nexport interface ICompletionItem {\n name: string;\n type: 'table' | 'column' | 'view' | 'jsonb_key';\n table?: string;\n dataType?: string;\n keyPath?: string[]; // For JSONB keys, the path to this key\n}\n\n/**\n * Response from the PostgreSQL completions API endpoint.\n */\nexport interface ICompletionsResponse {\n status: 'success' | 'error';\n tables: ICompletionItem[];\n columns: ICompletionItem[];\n jsonbKeys?: ICompletionItem[]; // JSONB keys from actual table data\n message?: string;\n}\n\n/**\n * Fetch PostgreSQL table and column completions from the server.\n *\n * @param dbUrl - PostgreSQL connection string (optional if using env var)\n * @param prefix - Optional prefix to filter completions\n * @param schema - Database schema name (default: 'public')\n * @param tableName - Optional table name to filter columns (only returns columns from this table)\n * @param schemaOrTable - Ambiguous identifier that could be either a schema or table name (backend will determine)\n * @param jsonbColumn - Optional JSONB column name to extract keys from\n * @param jsonbPath - Optional JSONB path for nested key extraction\n * @returns Array of completion items\n */\nexport async function fetchPostgresCompletions(\n dbUrl?: string,\n prefix = '',\n schema = 'public',\n tableName?: string,\n schemaOrTable?: string,\n jsonbColumn?: string,\n jsonbPath?: string[]\n): Promise<ICompletionItem[]> {\n try {\n const params = new URLSearchParams();\n if (dbUrl) {\n params.append('db_url', encodeURIComponent(dbUrl));\n }\n if (prefix) {\n params.append('prefix', prefix);\n }\n params.append('schema', schema);\n if (tableName) {\n params.append('table', tableName);\n }\n if (schemaOrTable) {\n params.append('schema_or_table', schemaOrTable);\n }\n if (jsonbColumn) {\n params.append('jsonb_column', jsonbColumn);\n if (jsonbPath && jsonbPath.length > 0) {\n params.append('jsonb_path', JSON.stringify(jsonbPath));\n }\n }\n\n const endpoint = `completions?${params.toString()}`;\n const response = await requestAPI<ICompletionsResponse>(endpoint, {\n method: 'GET'\n });\n\n if (response.status === 'error') {\n console.error('PostgreSQL completion error:', response.message);\n return [];\n }\n\n // If JSONB keys requested, return only those\n if (jsonbColumn && response.jsonbKeys) {\n return response.jsonbKeys;\n }\n\n // Return appropriate results based on context\n if (tableName || schemaOrTable) {\n // If we have table context, prefer columns\n return response.columns.length > 0 ? response.columns : response.tables;\n }\n\n return [...response.tables, ...response.columns];\n } catch (err) {\n if (err instanceof ServerConnection.ResponseError) {\n const status = err.response.status;\n let detail = err.message;\n\n if (\n typeof detail === 'string' &&\n (detail.includes('<!DOCTYPE') || detail.includes('<html'))\n ) {\n detail = `HTML error page (${detail.substring(0, 100)}...)`;\n }\n\n console.error(`PostgreSQL completions API failed (${status}): ${detail}`);\n } else {\n const msg = err instanceof Error ? err.message : 'Unknown error';\n console.error(`PostgreSQL completions API failed: ${msg}`);\n }\n\n return [];\n }\n}\n","import {\n JupyterFrontEnd,\n JupyterFrontEndPlugin\n} from '@jupyterlab/application';\n\nimport { ICompletionProviderManager } from '@jupyterlab/completer';\nimport { ISettingRegistry } from '@jupyterlab/settingregistry';\n\nimport { PostgresCompletionProvider } from './provider';\n\n/**\n * Plugin ID constant.\n */\nconst PLUGIN_ID = 'jl_db_comp:plugin';\n\n/**\n * Initialization data for the jl_db_comp extension.\n *\n * This plugin provides PostgreSQL table and column name completions\n * in JupyterLab notebooks and editors when typing SQL queries.\n */\nconst plugin: JupyterFrontEndPlugin<void> = {\n id: PLUGIN_ID,\n description:\n 'A JupyterLab extension to complete db queries in jupyterlab notebooks',\n autoStart: true,\n requires: [ICompletionProviderManager],\n optional: [ISettingRegistry],\n activate: (\n app: JupyterFrontEnd,\n completionManager: ICompletionProviderManager,\n settingRegistry: ISettingRegistry | null\n ) => {\n let provider: PostgresCompletionProvider;\n\n if (settingRegistry) {\n settingRegistry\n .load(PLUGIN_ID)\n .then(settings => {\n provider = new PostgresCompletionProvider(settings);\n completionManager.registerProvider(provider);\n console.log('JupyterLab extension jl_db_comp is activated!');\n })\n .catch(reason => {\n console.error('Failed to load settings for jl_db_comp:', reason);\n provider = new PostgresCompletionProvider();\n completionManager.registerProvider(provider);\n console.log('JupyterLab extension jl_db_comp is activated!');\n });\n } else {\n provider = new PostgresCompletionProvider();\n completionManager.registerProvider(provider);\n console.log('JupyterLab extension jl_db_comp is activated!');\n }\n }\n};\n\nexport default plugin;\n","import {\n CompletionHandler,\n ICompletionContext,\n ICompletionProvider\n} from '@jupyterlab/completer';\nimport { ISettingRegistry } from '@jupyterlab/settingregistry';\nimport { fetchPostgresCompletions, ICompletionItem } from './api';\n\n/**\n * Cache entry for PostgreSQL completions.\n */\ninterface ICacheEntry {\n items: ICompletionItem[];\n timestamp: number;\n}\n\n/**\n * PostgreSQL completion provider for JupyterLab.\n *\n * Provides table and column name completions from PostgreSQL databases\n * when editing SQL-like code in notebooks and editors.\n */\nexport class PostgresCompletionProvider implements ICompletionProvider {\n readonly identifier = 'jl_db_comp:postgres-completer';\n readonly renderer = null;\n\n private _cache = new Map<string, ICacheEntry>();\n private _cacheTTL = 5 * 60 * 1000; // 5 minutes in milliseconds\n private _settings: ISettingRegistry.ISettings | null = null;\n private _dbUrl = '';\n private _schema = 'public';\n private _enabled = true;\n\n /**\n * SQL keywords that trigger completion.\n */\n private readonly _sqlKeywords = [\n 'select',\n 'from',\n 'join',\n 'where',\n 'insert',\n 'update',\n 'delete',\n 'inner',\n 'left',\n 'right',\n 'outer',\n 'on',\n 'group',\n 'order',\n 'by',\n 'having',\n 'into',\n 'values',\n 'set'\n ];\n\n /**\n * Create a new PostgresCompletionProvider.\n *\n * @param settings - Optional settings registry to load database configuration\n */\n constructor(settings?: ISettingRegistry.ISettings | null) {\n if (settings) {\n this._settings = settings;\n this._loadSettings();\n\n settings.changed.connect(() => {\n this._loadSettings();\n });\n }\n }\n\n /**\n * Load database configuration from settings.\n */\n private _loadSettings(): void {\n if (!this._settings) {\n return;\n }\n\n this._dbUrl = this._settings.get('databaseUrl').composite as string;\n this._schema = this._settings.get('schema').composite as string;\n this._enabled = this._settings.get('enabled').composite as boolean;\n }\n\n /**\n * Determine if completions should be shown in the current context.\n *\n * Checks for SQL keywords or context that suggests SQL code.\n */\n async isApplicable(context: ICompletionContext): Promise<boolean> {\n if (!this._enabled) {\n return false;\n }\n\n // Get editor content from context\n const editor = context.editor;\n if (!editor) {\n return false;\n }\n\n const text = editor.model.sharedModel.getSource();\n if (!text) {\n return false;\n }\n\n const textLower = text.toLowerCase();\n\n // Check if any SQL keyword is present\n return this._sqlKeywords.some(keyword => textLower.includes(keyword));\n }\n\n /**\n * Fetch completion items for the current context.\n *\n * Uses caching to minimize database calls.\n */\n async fetch(\n request: CompletionHandler.IRequest,\n context: ICompletionContext\n ): Promise<CompletionHandler.ICompletionItemsReply> {\n if (!this._enabled) {\n return { start: request.offset, end: request.offset, items: [] };\n }\n\n const { text, offset } = request;\n\n // Extract context: schema, table, and prefix\n const extracted = this._extractContext(text, offset);\n\n // Create cache key that includes full context\n let cacheKey: string;\n if (extracted.jsonbColumn) {\n // JSONB key completion: table.column->path\n const pathStr = extracted.jsonbPath?.join('.') || '';\n const tablePrefix = extracted.schemaOrTable\n ? `${extracted.schemaOrTable}.`\n : '';\n cacheKey =\n `${tablePrefix}${extracted.jsonbColumn}->${pathStr}.${extracted.prefix}`.toLowerCase();\n } else if (extracted.schema && extracted.tableName) {\n // schema.table.prefix\n cacheKey =\n `${extracted.schema}.${extracted.tableName}.${extracted.prefix}`.toLowerCase();\n } else if (extracted.schemaOrTable) {\n // schema.prefix OR table.prefix (ambiguous)\n cacheKey = `${extracted.schemaOrTable}.${extracted.prefix}`.toLowerCase();\n } else {\n // just prefix\n cacheKey = extracted.prefix.toLowerCase();\n }\n\n // Check cache first\n const cached = this._getCached(cacheKey);\n if (cached) {\n return this._formatReply(cached, request.offset, extracted.prefix);\n }\n\n // Fetch from database\n try {\n const items = await fetchPostgresCompletions(\n this._dbUrl || undefined,\n extracted.prefix,\n extracted.schema || this._schema,\n extracted.tableName,\n extracted.schemaOrTable,\n extracted.jsonbColumn,\n extracted.jsonbPath\n );\n\n // Cache the results\n this._cache.set(cacheKey, {\n items,\n timestamp: Date.now()\n });\n\n return this._formatReply(items, request.offset, extracted.prefix);\n } catch (error) {\n console.error('Failed to fetch PostgreSQL completions:', error);\n return { start: request.offset, end: request.offset, items: [] };\n }\n }\n\n /**\n * Extract context from the text: prefix being typed, optional table name, optional schema, and JSONB context.\n *\n * Detects patterns like:\n * - \"schema.table.col\" → { schema: \"schema\", tableName: \"table\", prefix: \"col\" }\n * - \"schema.table.\" → { schema: \"schema\", tableName: \"table\", prefix: \"\" }\n * - \"schema.tab\" → { schemaOrTable: \"schema\", prefix: \"tab\" }\n * - \"schema.\" → { schemaOrTable: \"schema\", prefix: \"\" }\n * - \"table.col\" → { schemaOrTable: \"table\", prefix: \"col\" }\n * - \"table.\" → { schemaOrTable: \"table\", prefix: \"\" }\n * - \"prefix\" → { prefix: \"prefix\" }\n * - \"column_name->\" → { jsonbColumn: \"column_name\", jsonbPath: [], prefix: \"\" }\n * - \"column_name->>'key1'->\" → { jsonbColumn: \"column_name\", jsonbPath: [\"key1\"], prefix: \"\" }\n * - \"table.column_name->>'key'->\" → { schemaOrTable: \"table\", jsonbColumn: \"column_name\", jsonbPath: [\"key\"], prefix: \"\" }\n *\n * Note: For single-dot patterns (schema. or table.), the backend will determine\n * whether it's a schema (list tables) or table (list columns) by checking the database.\n */\n private _extractContext(\n text: string,\n offset: number\n ): {\n prefix: string;\n tableName?: string;\n schema?: string;\n schemaOrTable?: string;\n jsonbColumn?: string;\n jsonbPath?: string[];\n } {\n const beforeCursor = text.substring(0, offset);\n\n // JSONB pattern: Detect -> or ->> operators\n // Examples: metadata-> or content -> or patients.metadata->>'key'->\n if (beforeCursor.includes('->')) {\n // Much simpler approach: find the last -> or ->> and work backwards\n // Look for: word characters, optional dot+word, then ->, then anything\n // Pattern: (word.)?word -> rest\n const simpleMatch = beforeCursor.match(/([\\w]+\\.)?([\\w]+)\\s*->\\s*(.*)$/);\n\n if (simpleMatch) {\n const tableOrSchema = simpleMatch[1]\n ? simpleMatch[1].slice(0, -1)\n : undefined; // Remove trailing dot\n const columnName = simpleMatch[2];\n const afterOperator = simpleMatch[3];\n\n // Parse the path after the first ->\n // Example: \"'key1'->>'key2'->\" or \"key1\" or \"\"\n const jsonbPath: string[] = [];\n const pathRegex = /['\"]?([\\w]+)['\"]?\\s*->/g;\n let pathMatch;\n while ((pathMatch = pathRegex.exec(afterOperator)) !== null) {\n jsonbPath.push(pathMatch[1]);\n }\n\n // Get the current prefix (what's being typed after the last ->)\n // Remove any keys that are part of the path\n const lastArrowIndex = afterOperator.lastIndexOf('->');\n let currentPrefix = '';\n if (lastArrowIndex >= 0) {\n currentPrefix = afterOperator\n .substring(lastArrowIndex + 2)\n .trim()\n .replace(/['\"]/g, '');\n } else {\n // No nested path, just get whatever is after the ->\n currentPrefix = afterOperator.trim().replace(/['\"]/g, '');\n }\n\n return {\n schemaOrTable: tableOrSchema,\n jsonbColumn: columnName,\n jsonbPath,\n prefix: currentPrefix\n };\n }\n }\n\n // Three-part pattern: schema.table.column\n const threePartMatch = beforeCursor.match(/([\\w]+)\\.([\\w]+)\\.([\\w]*)$/);\n if (threePartMatch) {\n return {\n schema: threePartMatch[1],\n tableName: threePartMatch[2],\n prefix: threePartMatch[3]\n };\n }\n\n // Two-part pattern: could be schema.table OR table.column\n // Backend will determine which by checking if first part is a schema\n const twoPartMatch = beforeCursor.match(/([\\w]+)\\.([\\w]*)$/);\n if (twoPartMatch) {\n return {\n schemaOrTable: twoPartMatch[1],\n prefix: twoPartMatch[2]\n };\n }\n\n // Single word: could be a table name OR a column name\n // Check if there's a FROM clause in the query to determine context\n const wordMatch = beforeCursor.match(/[\\w]+$/);\n const prefix = wordMatch ? wordMatch[0] : '';\n\n // Look for FROM clause in the entire text (before or after cursor)\n // Match patterns like: FROM table, FROM schema.table, FROM table AS alias\n const fullText = text.toLowerCase();\n const fromMatch = fullText.match(/\\bfrom\\s+([\\w]+\\.)?[\\w]+/);\n\n if (fromMatch) {\n // Extract the table name (with optional schema)\n const fromClause = fromMatch[0];\n const tableMatch = fromClause.match(/\\bfrom\\s+(?:([\\w]+)\\.)?([\\w]+)/);\n\n if (tableMatch) {\n const schema = tableMatch[1];\n const table = tableMatch[2];\n\n // If we have a schema, return schema.table pattern\n if (schema) {\n return {\n schema,\n tableName: table,\n prefix\n };\n }\n\n // Otherwise, return table as schemaOrTable (backend will check if it's a table)\n return {\n schemaOrTable: table,\n prefix\n };\n }\n }\n\n // No FROM clause found, just return prefix (will suggest tables)\n return {\n prefix\n };\n }\n\n /**\n * Get cached completion items if still valid.\n */\n private _getCached(prefix: string): ICompletionItem[] | null {\n const key = prefix.toLowerCase();\n const entry = this._cache.get(key);\n\n if (!entry) {\n return null;\n }\n\n const age = Date.now() - entry.timestamp;\n if (age > this._cacheTTL) {\n this._cache.delete(key);\n return null;\n }\n\n return entry.items;\n }\n\n /**\n * Format completion items into the reply format expected by JupyterLab.\n */\n private _formatReply(\n items: ICompletionItem[],\n offset: number,\n prefix: string\n ): CompletionHandler.ICompletionItemsReply {\n const start = offset - prefix.length;\n const end = offset;\n\n const formattedItems = items.map(item => {\n let label = item.name;\n let insertText = item.name;\n\n // Add quotes around JSONB keys\n if (item.type === 'jsonb_key') {\n insertText = `'${item.name}'`;\n }\n\n // Add table context for columns\n if (item.type === 'column' && item.table) {\n label = `${item.name} (${item.table})`;\n }\n\n // Add type-specific icon\n let typeIcon = '📊'; // Default for columns\n let sortText = item.name; // Default sort order\n\n if (item.type === 'table') {\n typeIcon = '📋';\n } else if (item.type === 'view') {\n typeIcon = '👁️';\n } else if (item.type === 'jsonb_key') {\n typeIcon = '🔑';\n // Use 0000 prefix to sort JSONB keys to the top (numbers sort before letters)\n sortText = `0000${item.name}`;\n }\n\n // Build documentation\n let documentation: string | undefined;\n if (item.type === 'column' && item.dataType && item.table) {\n documentation = `${item.table}.${item.name}: ${item.dataType}`;\n } else if (item.type === 'jsonb_key' && item.keyPath) {\n documentation = `JSONB key: ${item.keyPath.join(' -> ')}`;\n }\n\n return {\n label: `${typeIcon} ${label}`,\n insertText,\n sortText,\n type: item.type,\n documentation\n };\n });\n\n return {\n start,\n end,\n items: formattedItems\n };\n }\n\n /**\n * Clear the completion cache.\n */\n clearCache(): void {\n this._cache.clear();\n }\n}\n","import { URLExt } from '@jupyterlab/coreutils';\n\nimport { ServerConnection } from '@jupyterlab/services';\n\n/**\n * Call the server extension\n *\n * @param endPoint API REST end point for the extension\n * @param init Initial values for the request\n * @returns The response body interpreted as JSON\n */\nexport async function requestAPI<T>(\n endPoint = '',\n init: RequestInit = {}\n): Promise<T> {\n // Make request to Jupyter API\n const settings = ServerConnection.makeSettings();\n const requestUrl = URLExt.join(\n settings.baseUrl,\n 'jl-db-comp', // our server extension's API namespace\n endPoint\n );\n\n let response: Response;\n try {\n response = await ServerConnection.makeRequest(requestUrl, init, settings);\n } catch (error) {\n throw new ServerConnection.NetworkError(error as any);\n }\n\n let data: any = await response.text();\n\n if (data.length > 0) {\n try {\n data = JSON.parse(data);\n } catch (error) {\n console.log('Not a JSON response body.', response);\n }\n }\n\n if (!response.ok) {\n throw new ServerConnection.ResponseError(response, data.message || data);\n }\n\n return data;\n}\n"],"names":[],"ignoreList":[],"sourceRoot":""}
|