@coldsmirk/inkstone-sql 0.32.0 → 0.34.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 +2 -2
- package/dist/{assist-BM4bNDhw.d.ts → assist-Cr2idunB.d.ts} +8 -4
- package/dist/index.d.ts +28 -14
- package/dist/index.js +198 -23
- package/dist/monaco.d.ts +1 -1
- package/dist/react.d.ts +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -31,7 +31,7 @@ const runnable = runnableStatement(text, from, to, "postgres");
|
|
|
31
31
|
if (runnable.kind === "one") {
|
|
32
32
|
// Markers are read off the BLANKED form — `select '?'` binds nothing — and under the kind's
|
|
33
33
|
// own binding rules: `markers.refusal` mirrors the engine scanner's own faults (mixed forms,
|
|
34
|
-
// `??` on mysql/dm, a hand-written `$1` beside a minted marker), so the editor can say so
|
|
34
|
+
// `??` on mysql/dm/clickhouse, a hand-written `$1` beside a minted marker), so the editor can say so
|
|
35
35
|
// before the round trip does.
|
|
36
36
|
const markers = statementMarkers(runnable.blanked, "postgres");
|
|
37
37
|
}
|
|
@@ -41,7 +41,7 @@ Everything the intelligence answers goes through one lexical reader taken under
|
|
|
41
41
|
|
|
42
42
|
## The mirror invariant
|
|
43
43
|
|
|
44
|
-
`noise.ts` (which runs of a statement are strings / comments / quoted names, per kind) and the marker grammar mirror [**polyglot's engine scanner**](https://github.com/coldsmirk/polyglot) (`
|
|
44
|
+
`noise.ts` (which runs of a statement are strings / comments / quoted names, per kind) and the marker grammar mirror [**polyglot's engine scanner**](https://github.com/coldsmirk/polyglot) (`sql/Placeholders`, over its profiles in `kinds/Dialect`) flag for flag, each kind under the two lexicons the sidecar reads it by: the database's own says where a statement ends and what is prose, and the one the kind's driver finds a `?` by says where the markers are — SQL Server nests a block comment, but Microsoft's driver ends one at its first close and binds a `?` past it; ClickHouse's driver reads a `?` as the conditional operator `a ? b : c` where a lone `:` follows it before any other `?`, and binds nothing through that `:` (`conditional.ts`). The scanner decides what actually binds; a reader that diverged would prompt for a parameter the engine refuses, or send a statement short of one it binds. **A scanner change requires a matching release of this package**, and the per-kind cases in `reader.test.ts` are the invariant's guard.
|
|
45
45
|
|
|
46
46
|
## Structured refusals
|
|
47
47
|
|
|
@@ -14,8 +14,11 @@
|
|
|
14
14
|
* Doris (`doris`, StarRocks and SelectDB with it) speaks MySQL's protocol and grammar and reads
|
|
15
15
|
* here as the MySQL idiom throughout, but its own parser admits no identifier past ASCII bare —
|
|
16
16
|
* `患者` must be backticked where MySQL takes it as written — so it earned a kind for its spelling.
|
|
17
|
+
* ClickHouse (`clickhouse`) is a lexicon of its own on the engine — backslash escapes and backtick
|
|
18
|
+
* names as MySQL's, but `--` a comment without its space, `?` its own bind marker — and names its
|
|
19
|
+
* functions by their case (`toDate`, never `TODATE`).
|
|
17
20
|
*/
|
|
18
|
-
type SqlKind = "postgres" | "mysql" | "oracle" | "sqlserver" | "dm" | "kingbase" | "doris";
|
|
21
|
+
type SqlKind = "postgres" | "mysql" | "oracle" | "sqlserver" | "dm" | "kingbase" | "doris" | "clickhouse";
|
|
19
22
|
/**
|
|
20
23
|
* One table or view as the catalog reports it. Names carry the vendor's own casing — that casing
|
|
21
24
|
* is information, and everything downstream preserves it.
|
|
@@ -170,9 +173,10 @@ type Runnable = {
|
|
|
170
173
|
*/
|
|
171
174
|
text: string;
|
|
172
175
|
/**
|
|
173
|
-
* The
|
|
174
|
-
*
|
|
175
|
-
*
|
|
176
|
+
* The statement as the engine's own scanner reads it when it is sent — under the lexicon the
|
|
177
|
+
* kind's driver finds a `?` by, from the statement's own start — with everything the scanner
|
|
178
|
+
* skips whole (literals, comments and quoted names) overwritten by spaces, character for
|
|
179
|
+
* character, so offsets still line up with `text`. A marker scan must read this: in
|
|
176
180
|
* `select '?'` the `?` is prose, and a marker scan of the raw text would ask the operator for
|
|
177
181
|
* a value the engine then refuses as an extra argument.
|
|
178
182
|
*/
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { C as CatalogTable, S as CatalogFunction, _ as readCall, a as createSqlAssist, b as runnableStatement, c as SqlSource, d as CallRead, f as IdentifierRead, g as TableRef, h as StatementRead, i as SqlSignatureHelp, l as createSqlSchema, m as StatementContext, n as SqlCompletionCandidate, o as SqlCatalog, p as Runnable, r as SqlHoverCard, s as SqlSchema, t as SqlAssist, u as qualifiedName, v as readIdentifier, w as SqlKind, x as CatalogColumn, y as readStatement } from "./assist-
|
|
1
|
+
import { C as CatalogTable, S as CatalogFunction, _ as readCall, a as createSqlAssist, b as runnableStatement, c as SqlSource, d as CallRead, f as IdentifierRead, g as TableRef, h as StatementRead, i as SqlSignatureHelp, l as createSqlSchema, m as StatementContext, n as SqlCompletionCandidate, o as SqlCatalog, p as Runnable, r as SqlHoverCard, s as SqlSchema, t as SqlAssist, u as qualifiedName, v as readIdentifier, w as SqlKind, x as CatalogColumn, y as readStatement } from "./assist-Cr2idunB.js";
|
|
2
2
|
//#region src/functions.d.ts
|
|
3
3
|
/**
|
|
4
4
|
* The function vocabulary this bench speaks: the curated floor this project wrote
|
|
@@ -78,11 +78,17 @@ interface SqlFunction {
|
|
|
78
78
|
* manual: the words an integration engineer writes at this console, weighted toward reading — a
|
|
79
79
|
* keyword or function missing here costs one suggestion, so the lists stay honest rather than
|
|
80
80
|
* exhaustive. Everything is lowercase, the casing the bench itself writes (the tree's
|
|
81
|
-
* `select * from`, the editor placeholder); completion re-cases it to follow the operator's hand
|
|
81
|
+
* `select * from`, the editor placeholder); completion re-cases it to follow the operator's hand —
|
|
82
|
+
* but for the functions of a kind that names them by their case, spelled as the vendor spells them.
|
|
82
83
|
*/
|
|
83
84
|
interface SqlDialect {
|
|
84
85
|
keywords: string[];
|
|
85
86
|
functions: SqlFunction[];
|
|
87
|
+
/**
|
|
88
|
+
* The kind names a function by its case (ClickHouse's `toDate`, never `TODATE`): its curated
|
|
89
|
+
* functions are written as listed, whatever the operator's hand.
|
|
90
|
+
*/
|
|
91
|
+
functionsByCase?: boolean;
|
|
86
92
|
}
|
|
87
93
|
export declare const SQL_DIALECTS: Record<SqlKind, SqlDialect>;
|
|
88
94
|
/**
|
|
@@ -133,11 +139,11 @@ export declare function joinGuesses(kind: SqlKind, target: SqlSource, others: Sq
|
|
|
133
139
|
* Placeholder detection for what a run asks the operator for.
|
|
134
140
|
*
|
|
135
141
|
* This is **not** validation and must never become it: the engine-side scanner (the polyglot
|
|
136
|
-
* sidecar's `
|
|
137
|
-
* implementation here would drift from it the first time a dialect gained a quoting
|
|
138
|
-
* this decides is whether the run has values to ask for, which form the parameter
|
|
139
|
-
* offers, and — where the statement is one the scanner refuses outright — the refusal, as
|
|
140
|
-
* code, so the editor can say so before the round trip does.
|
|
142
|
+
* sidecar's `sql/Placeholders`) is the authority on what binds, it is lexical and per-dialect,
|
|
143
|
+
* and a second implementation here would drift from it the first time a dialect gained a quoting
|
|
144
|
+
* form. All this decides is whether the run has values to ask for, which form the parameter
|
|
145
|
+
* dialog offers, and — where the statement is one the scanner refuses outright — the refusal, as
|
|
146
|
+
* a code, so the editor can say so before the round trip does.
|
|
141
147
|
*
|
|
142
148
|
* **Callers pass the statement's blanked form** (`Runnable.blanked`), never its raw text. A
|
|
143
149
|
* disagreement with the engine's scanner is not cosmetic in either direction — it answers both
|
|
@@ -146,8 +152,10 @@ export declare function joinGuesses(kind: SqlKind, target: SqlSource, others: Sq
|
|
|
146
152
|
* has no placeholder in it. Blanking is the reader's job because the reader already owns what is
|
|
147
153
|
* prose and what is SQL for this page, and it takes that decision under the data source's own
|
|
148
154
|
* lexical profile (`noise.ts`), mode for mode with the scanner — MySQL's `#` comments and
|
|
149
|
-
* backslash escapes, Postgres's `$tag$` bodies, Oracle's `q'…'`, every kind's quoted names
|
|
150
|
-
*
|
|
155
|
+
* backslash escapes, Postgres's `$tag$` bodies, Oracle's `q'…'`, every kind's quoted names,
|
|
156
|
+
* SQL Server's block comments as its driver ends them, and ClickHouse's conditional operator as
|
|
157
|
+
* its driver crosses it — so what reaches this file is exactly the text the engine would mint
|
|
158
|
+
* markers from.
|
|
151
159
|
*/
|
|
152
160
|
/**
|
|
153
161
|
* The binding form a statement's markers suggest.
|
|
@@ -159,8 +167,8 @@ type MarkerForm = "none" | "positional" | "named";
|
|
|
159
167
|
*
|
|
160
168
|
* - `"mixed-forms"` — the statement writes both `?` and `:name`; the scanner binds neither.
|
|
161
169
|
* - `"question-escape-unsupported"` — `??` on a kind whose native form is itself `?` (MySQL,
|
|
162
|
-
*
|
|
163
|
-
*
|
|
170
|
+
* 达梦, ClickHouse): the escape has no rendering that survives, and none is needed — MySQL and
|
|
171
|
+
* 达梦 have no `?` operator, and ClickHouse's one, the conditional `a ? b : c`, is read as one.
|
|
164
172
|
* - `"native-marker-collision"` — a marker hand-written in the kind's own bind form (`$1`,
|
|
165
173
|
* `:1`, `@p1`) beside a `?`/`:name` the scanner would mint: both would bind the same slot.
|
|
166
174
|
*/
|
|
@@ -191,6 +199,11 @@ interface StatementMarkers {
|
|
|
191
199
|
export declare function statementMarkers(statement: string, kind: SqlKind): StatementMarkers;
|
|
192
200
|
//#endregion
|
|
193
201
|
//#region src/noise.d.ts
|
|
202
|
+
/**
|
|
203
|
+
* Which lexicon a text is read under: the database's own — where a statement ends, what is prose
|
|
204
|
+
* — or the one the kind's driver finds a `?` by, which the sidecar mints its markers under.
|
|
205
|
+
*/
|
|
206
|
+
type NoiseLexicon = "database" | "driver";
|
|
194
207
|
/**
|
|
195
208
|
* A run the reader must not look inside. `sealed` says the closing delimiter was found: a caret
|
|
196
209
|
* right after a sealed run is back in the statement, while a caret at the end of an unterminated
|
|
@@ -223,9 +236,10 @@ interface BlankedSql {
|
|
|
223
236
|
/**
|
|
224
237
|
* The statement with its noise blanked out, char for char, so every offset survives — in the two
|
|
225
238
|
* renderings the reader needs (see [BlankedSql]). One pass, mirroring the sidecar scanner's own
|
|
226
|
-
* loop over its mutually exclusive lexical modes
|
|
239
|
+
* loop over its mutually exclusive lexical modes, under the database's own lexicon unless the
|
|
240
|
+
* driver's is asked for.
|
|
227
241
|
*/
|
|
228
|
-
export declare function blankNoise(text: string, kind: SqlKind): BlankedSql;
|
|
242
|
+
export declare function blankNoise(text: string, kind: SqlKind, lexicon?: NoiseLexicon): BlankedSql;
|
|
229
243
|
/**
|
|
230
244
|
* Whether the caret sits inside a string or a comment, where the bench has nothing to say. A
|
|
231
245
|
* quoted name is not one: it is a name the operator is writing, and the reader is about names.
|
|
@@ -373,4 +387,4 @@ export declare function spellQualifiedName(kind: SqlKind, table: {
|
|
|
373
387
|
name: string;
|
|
374
388
|
}): string;
|
|
375
389
|
//#endregion
|
|
376
|
-
export { type BenchFunction, type BindRefusal, type BlankedSql, type Bound, type BoundParams, type CallRead, type CatalogColumn, type CatalogFunction, type CatalogTable, type IdentifierRead, type JoinGuess, type JoinReason, type MarkerForm, type MarkerRefusal, type NoiseSpan, type ParamType, type ParamValue, type Runnable, type SqlAssist, type SqlCatalog, type SqlCompletionCandidate, type SqlDialect, type SqlFunction, type SqlFunctions, type SqlHoverCard, type SqlKind, type SqlScalar, type SqlSchema, type SqlSignatureHelp, type SqlSource, type StatementContext, type StatementMarkers, type StatementOrigin, type StatementRead, type TableRef, createSqlAssist, createSqlSchema, qualifiedName, readCall, readIdentifier, readStatement, runnableStatement };
|
|
390
|
+
export { type BenchFunction, type BindRefusal, type BlankedSql, type Bound, type BoundParams, type CallRead, type CatalogColumn, type CatalogFunction, type CatalogTable, type IdentifierRead, type JoinGuess, type JoinReason, type MarkerForm, type MarkerRefusal, type NoiseLexicon, type NoiseSpan, type ParamType, type ParamValue, type Runnable, type SqlAssist, type SqlCatalog, type SqlCompletionCandidate, type SqlDialect, type SqlFunction, type SqlFunctions, type SqlHoverCard, type SqlKind, type SqlScalar, type SqlSchema, type SqlSignatureHelp, type SqlSource, type StatementContext, type StatementMarkers, type StatementOrigin, type StatementRead, type TableRef, createSqlAssist, createSqlSchema, qualifiedName, readCall, readIdentifier, readStatement, runnableStatement };
|
package/dist/index.js
CHANGED
|
@@ -181,6 +181,63 @@ const MYSQL = dialect([
|
|
|
181
181
|
fn("group_concat", "group_concat(expr)", "Aggregate: joins each row's value into one string; takes `separator '…'`."),
|
|
182
182
|
fn("json_extract", "json_extract(json, path)", "Reads a JSON path, as in `'$.id'`.")
|
|
183
183
|
]);
|
|
184
|
+
const CLICKHOUSE$1 = {
|
|
185
|
+
...dialect([
|
|
186
|
+
"final",
|
|
187
|
+
"prewhere",
|
|
188
|
+
"sample",
|
|
189
|
+
"array",
|
|
190
|
+
"global",
|
|
191
|
+
"any",
|
|
192
|
+
"asof",
|
|
193
|
+
"semi",
|
|
194
|
+
"anti",
|
|
195
|
+
"limit",
|
|
196
|
+
"offset",
|
|
197
|
+
"settings",
|
|
198
|
+
"format",
|
|
199
|
+
"totals",
|
|
200
|
+
"rollup",
|
|
201
|
+
"cube",
|
|
202
|
+
"ilike",
|
|
203
|
+
"interval",
|
|
204
|
+
"true",
|
|
205
|
+
"false"
|
|
206
|
+
], [
|
|
207
|
+
fn("now", "now(timezone)", "Current date and time, in the server's time zone unless one is named."),
|
|
208
|
+
fn("today", "today()", "Current date."),
|
|
209
|
+
fn("toDate", "toDate(value)", "Converts to a Date."),
|
|
210
|
+
fn("toDateTime", "toDateTime(value, timezone)", "Converts to a DateTime, in a time zone where one is named."),
|
|
211
|
+
fn("toString", "toString(value)", "Converts to a String."),
|
|
212
|
+
fn("toInt64", "toInt64(value)", "Converts to a 64-bit integer."),
|
|
213
|
+
fn("toFloat64", "toFloat64(value)", "Converts to a 64-bit float."),
|
|
214
|
+
fn("toStartOfDay", "toStartOfDay(time)", "Rounds a time down to the start of its day."),
|
|
215
|
+
fn("toStartOfHour", "toStartOfHour(time)", "Rounds a time down to the start of its hour."),
|
|
216
|
+
fn("toStartOfMonth", "toStartOfMonth(date)", "Rounds a date down to the first day of its month."),
|
|
217
|
+
fn("formatDateTime", "formatDateTime(time, format)", "Formats a time through a format string, as in `'%Y-%m-%d'`."),
|
|
218
|
+
fn("dateDiff", "dateDiff(unit, start, end)", "Unit boundaries crossed between two times, as in `dateDiff('day', a, b)`."),
|
|
219
|
+
fn("if", "if(condition, then, else)", "Ternary choice."),
|
|
220
|
+
fn("multiIf", "multiIf(condition, then, …, else)", "The value of the first condition that holds, or the last argument."),
|
|
221
|
+
fn("ifNull", "ifNull(value, fallback)", "The second argument when the first is NULL."),
|
|
222
|
+
fn("concat", "concat(value, …)", "Joins the arguments as strings."),
|
|
223
|
+
fn("length", "length(value)", "Bytes in a string, elements in an array; `lengthUTF8` counts characters."),
|
|
224
|
+
fn("substring", "substring(text, offset, length)", "`length` bytes from byte `offset`, counting from 1; `substringUTF8` counts characters."),
|
|
225
|
+
fn("position", "position(text, search)", "Byte position of a substring, counting from 1, or 0 when it is absent."),
|
|
226
|
+
fn("replaceAll", "replaceAll(text, search, replacement)", "Replaces every occurrence of a substring."),
|
|
227
|
+
fn("splitByChar", "splitByChar(separator, text)", "Splits a string on a one-character separator into an array."),
|
|
228
|
+
fn("JSONExtractString", "JSONExtractString(json, key, …)", "A string read out of JSON by key or index, empty where there is none."),
|
|
229
|
+
fn("has", "has(array, value)", "Whether the array holds the value."),
|
|
230
|
+
fn("arrayJoin", "arrayJoin(array)", "One row per element of the array."),
|
|
231
|
+
fn("countIf", "countIf(condition)", "Aggregate: rows for which the condition holds."),
|
|
232
|
+
fn("sumIf", "sumIf(value, condition)", "Aggregate: the sum over the rows for which the condition holds."),
|
|
233
|
+
fn("uniq", "uniq(expr)", "Aggregate: the distinct values, counted approximately."),
|
|
234
|
+
fn("uniqExact", "uniqExact(expr)", "Aggregate: the distinct values, counted exactly."),
|
|
235
|
+
fn("groupArray", "groupArray(expr)", "Aggregate: collects each row's value into an array."),
|
|
236
|
+
fn("argMax", "argMax(value, by)", "Aggregate: `value` from the row where `by` is largest."),
|
|
237
|
+
fn("any", "any(expr)", "Aggregate: one value of the group, whichever is met first.")
|
|
238
|
+
]),
|
|
239
|
+
functionsByCase: true
|
|
240
|
+
};
|
|
184
241
|
const SQL_DIALECTS = {
|
|
185
242
|
postgres: POSTGRES$1,
|
|
186
243
|
kingbase: POSTGRES$1,
|
|
@@ -222,7 +279,8 @@ const SQL_DIALECTS = {
|
|
|
222
279
|
fn("rtrim", "rtrim(text)", "Strips trailing whitespace."),
|
|
223
280
|
fn("string_agg", "string_agg(expr, delimiter)", "Aggregate: joins each row's value with a delimiter.")
|
|
224
281
|
]),
|
|
225
|
-
dm: ORACLE$1
|
|
282
|
+
dm: ORACLE$1,
|
|
283
|
+
clickhouse: CLICKHOUSE$1
|
|
226
284
|
};
|
|
227
285
|
function signatureParameters(signature) {
|
|
228
286
|
const open = signature.indexOf("(");
|
|
@@ -323,6 +381,13 @@ const TSQL_RESERVED = reservedWords(`
|
|
|
323
381
|
tran transaction trigger truncate try_convert tsequal union unique unpivot update updatetext
|
|
324
382
|
use user values varying view waitfor when where while with within writetext
|
|
325
383
|
`);
|
|
384
|
+
const CLICKHOUSE_RESERVED = reservedWords(`
|
|
385
|
+
all and anti any array as asc asof between by case cast collate cross cube desc distinct else
|
|
386
|
+
end except exists false fill final first format from full global group having ilike in inner
|
|
387
|
+
insert interpolate intersect interval into is join last left like limit not null nulls offset
|
|
388
|
+
on or order outer paste prewhere qualify right rollup sample select semi settings step table
|
|
389
|
+
then totals true union using values when where window with
|
|
390
|
+
`);
|
|
326
391
|
const ORACLE = {
|
|
327
392
|
open: "\"",
|
|
328
393
|
close: "\"",
|
|
@@ -357,10 +422,18 @@ const SPELLINGS = {
|
|
|
357
422
|
regular: /^[a-z_@#][\w@#$]*$/i,
|
|
358
423
|
reserved: TSQL_RESERVED
|
|
359
424
|
},
|
|
360
|
-
dm: ORACLE
|
|
425
|
+
dm: ORACLE,
|
|
426
|
+
clickhouse: {
|
|
427
|
+
open: "`",
|
|
428
|
+
close: "`",
|
|
429
|
+
backslash: true,
|
|
430
|
+
regular: /^[a-z_]\w*$/i,
|
|
431
|
+
reserved: CLICKHOUSE_RESERVED
|
|
432
|
+
}
|
|
361
433
|
};
|
|
362
434
|
function delimited(spelling, name) {
|
|
363
|
-
|
|
435
|
+
const escaped = spelling.backslash === true ? name.replaceAll("\\", () => "\\\\") : name;
|
|
436
|
+
return `${spelling.open}${escaped.replaceAll(spelling.close, () => spelling.close + spelling.close)}${spelling.close}`;
|
|
364
437
|
}
|
|
365
438
|
function spellIdentifier(kind, name) {
|
|
366
439
|
const spelling = SPELLINGS[kind];
|
|
@@ -421,6 +494,67 @@ function joinGuesses(kind, target, others) {
|
|
|
421
494
|
return [...found.values()];
|
|
422
495
|
}
|
|
423
496
|
//#endregion
|
|
497
|
+
//#region src/conditional.ts
|
|
498
|
+
function conditionalEnd(text, at) {
|
|
499
|
+
let index = at + 2;
|
|
500
|
+
while (index >= 0 && index < text.length) {
|
|
501
|
+
const ch = text[index];
|
|
502
|
+
if (ch === "?" || ch === ":") return ch === ":" && index + 1 < text.length && text[index + 1] !== ":" && text[index - 1] !== ":" ? index + 1 : -1;
|
|
503
|
+
index = skipped(text, index);
|
|
504
|
+
}
|
|
505
|
+
return -1;
|
|
506
|
+
}
|
|
507
|
+
function skipped(text, at) {
|
|
508
|
+
const ch = text[at];
|
|
509
|
+
if (ch === "'" || ch === "\"" || ch === "`") return quoted(text, at);
|
|
510
|
+
if (ch === "(" || ch === "[" || ch === "{") return group(text, at);
|
|
511
|
+
if (ch === "-" && text[at + 1] === "-") return lineEnd(text, at + 2);
|
|
512
|
+
if (ch === "/" && text[at + 1] === "*") return blockEnd(text, at + 2);
|
|
513
|
+
return at + 1;
|
|
514
|
+
}
|
|
515
|
+
function quoted(text, open) {
|
|
516
|
+
const quote = text[open];
|
|
517
|
+
for (let index = open + 1; index < text.length; index += 1) {
|
|
518
|
+
const ch = text[index];
|
|
519
|
+
if (ch === "\\") index += 1;
|
|
520
|
+
else if (ch === quote) {
|
|
521
|
+
if (text[index + 1] !== quote) return index + 1;
|
|
522
|
+
index += 1;
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
return -1;
|
|
526
|
+
}
|
|
527
|
+
const CLOSERS = {
|
|
528
|
+
"(": ")",
|
|
529
|
+
"[": "]",
|
|
530
|
+
"{": "}"
|
|
531
|
+
};
|
|
532
|
+
function group(text, open) {
|
|
533
|
+
const close = CLOSERS[text[open]];
|
|
534
|
+
let index = open + 1;
|
|
535
|
+
while (index >= 0 && index < text.length) {
|
|
536
|
+
if (text[index] === close) return index + 1;
|
|
537
|
+
index = skipped(text, index);
|
|
538
|
+
}
|
|
539
|
+
return -1;
|
|
540
|
+
}
|
|
541
|
+
function lineEnd(text, from) {
|
|
542
|
+
const end = text.indexOf("\n", from);
|
|
543
|
+
return end > from ? end + 1 : text.length;
|
|
544
|
+
}
|
|
545
|
+
function blockEnd(text, from) {
|
|
546
|
+
let depth = 1;
|
|
547
|
+
for (let index = from; index + 1 < text.length; index += 1) if (text[index] === "/" && text[index + 1] === "*") {
|
|
548
|
+
depth += 1;
|
|
549
|
+
index += 1;
|
|
550
|
+
} else if (text[index] === "*" && text[index + 1] === "/") {
|
|
551
|
+
depth -= 1;
|
|
552
|
+
index += 1;
|
|
553
|
+
if (depth === 0) return index + 1;
|
|
554
|
+
}
|
|
555
|
+
return -1;
|
|
556
|
+
}
|
|
557
|
+
//#endregion
|
|
424
558
|
//#region src/noise.ts
|
|
425
559
|
const BASE = {
|
|
426
560
|
backslashEscapes: false,
|
|
@@ -432,7 +566,22 @@ const BASE = {
|
|
|
432
566
|
dollarQuoting: false,
|
|
433
567
|
escapeStrings: false,
|
|
434
568
|
nestedBlockComments: false,
|
|
435
|
-
alternativeQuoting: false
|
|
569
|
+
alternativeQuoting: false,
|
|
570
|
+
backslashInBackticks: false,
|
|
571
|
+
conditionalOperator: false
|
|
572
|
+
};
|
|
573
|
+
const SQLSERVER = {
|
|
574
|
+
...BASE,
|
|
575
|
+
bracketIdentifiers: true,
|
|
576
|
+
nestedBlockComments: true
|
|
577
|
+
};
|
|
578
|
+
const CLICKHOUSE = {
|
|
579
|
+
...BASE,
|
|
580
|
+
backslashEscapes: true,
|
|
581
|
+
hashLineComments: true,
|
|
582
|
+
backtickIdentifiers: true,
|
|
583
|
+
nestedBlockComments: true,
|
|
584
|
+
backslashInBackticks: true
|
|
436
585
|
};
|
|
437
586
|
const NOISE_PROFILES = {
|
|
438
587
|
postgres: {
|
|
@@ -461,16 +610,24 @@ const NOISE_PROFILES = {
|
|
|
461
610
|
...BASE,
|
|
462
611
|
alternativeQuoting: true
|
|
463
612
|
},
|
|
464
|
-
sqlserver:
|
|
465
|
-
...BASE,
|
|
466
|
-
bracketIdentifiers: true
|
|
467
|
-
},
|
|
613
|
+
sqlserver: SQLSERVER,
|
|
468
614
|
dm: BASE,
|
|
469
615
|
kingbase: {
|
|
470
616
|
...BASE,
|
|
471
617
|
escapeStrings: true,
|
|
472
618
|
nestedBlockComments: true,
|
|
473
619
|
dollarQuoting: true
|
|
620
|
+
},
|
|
621
|
+
clickhouse: CLICKHOUSE
|
|
622
|
+
};
|
|
623
|
+
const DRIVER_PROFILES = {
|
|
624
|
+
sqlserver: {
|
|
625
|
+
...SQLSERVER,
|
|
626
|
+
nestedBlockComments: false
|
|
627
|
+
},
|
|
628
|
+
clickhouse: {
|
|
629
|
+
...CLICKHOUSE,
|
|
630
|
+
conditionalOperator: true
|
|
474
631
|
}
|
|
475
632
|
};
|
|
476
633
|
const WORD_START = /[a-z_\u{0080}-\u{FFFF}]/iu;
|
|
@@ -575,10 +732,14 @@ function opensLineComment(text, from, profile) {
|
|
|
575
732
|
const after = text[from + 2];
|
|
576
733
|
return !profile.lineCommentNeedsSpace || after === void 0 || after <= " ";
|
|
577
734
|
}
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
735
|
+
const lastBlanked = {
|
|
736
|
+
database: null,
|
|
737
|
+
driver: null
|
|
738
|
+
};
|
|
739
|
+
function blankNoise(text, kind, lexicon = "database") {
|
|
740
|
+
const last = lastBlanked[lexicon];
|
|
741
|
+
if (last !== null && last.text === text && last.kind === kind) return last.result;
|
|
742
|
+
const profile = lexicon === "driver" ? DRIVER_PROFILES[kind] ?? NOISE_PROFILES[kind] : NOISE_PROFILES[kind];
|
|
582
743
|
const withNames = text.split("");
|
|
583
744
|
const blanked = text.split("");
|
|
584
745
|
const spans = [];
|
|
@@ -600,11 +761,18 @@ function blankNoise(text, kind) {
|
|
|
600
761
|
const ch = text[at];
|
|
601
762
|
if (ch === "'") skip(closeDelimited(text, at + 1, "'", profile.backslashEscapes), false);
|
|
602
763
|
else if (ch === "\"") skip(closeDelimited(text, at + 1, "\"", profile.backslashEscapes), !profile.doubleQuotedStrings);
|
|
603
|
-
else if (ch === "`" && profile.backtickIdentifiers) skip(closeDelimited(text, at + 1, "`",
|
|
764
|
+
else if (ch === "`" && profile.backtickIdentifiers) skip(closeDelimited(text, at + 1, "`", profile.backslashInBackticks), true);
|
|
604
765
|
else if (ch === "[" && profile.bracketIdentifiers) skip(closeBracket(text, at + 1), true);
|
|
605
766
|
else if (opensLineComment(text, at, profile)) skip(closeLine(text, at), false);
|
|
606
767
|
else if (ch === "/" && text[at + 1] === "*") skip(closeBlock(text, at + 2, profile.nestedBlockComments), false);
|
|
607
|
-
else if (ch === "
|
|
768
|
+
else if (ch === "?" && profile.conditionalOperator) {
|
|
769
|
+
const end = conditionalEnd(text, at);
|
|
770
|
+
if (end < 0) at += text[at + 1] === "?" ? 2 : 1;
|
|
771
|
+
else skip({
|
|
772
|
+
end,
|
|
773
|
+
sealed: true
|
|
774
|
+
}, false);
|
|
775
|
+
} else if (ch === "$" && profile.dollarQuoting) {
|
|
608
776
|
const quoted = closeDollarQuoted(text, at);
|
|
609
777
|
if (quoted === null) at += 1;
|
|
610
778
|
else skip(quoted, false);
|
|
@@ -622,7 +790,7 @@ function blankNoise(text, kind) {
|
|
|
622
790
|
blanked: blanked.join(""),
|
|
623
791
|
spans
|
|
624
792
|
};
|
|
625
|
-
lastBlanked = {
|
|
793
|
+
lastBlanked[lexicon] = {
|
|
626
794
|
text,
|
|
627
795
|
kind,
|
|
628
796
|
result
|
|
@@ -918,10 +1086,11 @@ function runnableStatement(text, from, to, kind) {
|
|
|
918
1086
|
if (spans.length === 0) return { kind: "none" };
|
|
919
1087
|
if (selected && spans.length > 1) return { kind: "several" };
|
|
920
1088
|
const span = spans[0];
|
|
1089
|
+
const statement = text.slice(span.from, span.to);
|
|
921
1090
|
return {
|
|
922
1091
|
kind: "one",
|
|
923
|
-
text:
|
|
924
|
-
blanked:
|
|
1092
|
+
text: statement,
|
|
1093
|
+
blanked: blankNoise(statement, kind, "driver").blanked,
|
|
925
1094
|
from: span.from,
|
|
926
1095
|
to: span.to,
|
|
927
1096
|
alone: statementSpans(text, blanked, 0, text.length).length <= 1
|
|
@@ -1023,7 +1192,7 @@ function expansionCandidate(kind, sources, replacesStar) {
|
|
|
1023
1192
|
};
|
|
1024
1193
|
}
|
|
1025
1194
|
function createSqlCompletion(kind, schema, functions) {
|
|
1026
|
-
const { keywords } = SQL_DIALECTS[kind];
|
|
1195
|
+
const { keywords, functionsByCase = false } = SQL_DIALECTS[kind];
|
|
1027
1196
|
const spelled = /* @__PURE__ */ new Map();
|
|
1028
1197
|
async function wordCandidates(typed) {
|
|
1029
1198
|
const upper = typed !== "" && typed === typed.toUpperCase() && typed !== typed.toLowerCase();
|
|
@@ -1039,7 +1208,7 @@ function createSqlCompletion(kind, schema, functions) {
|
|
|
1039
1208
|
sortGroup: RANK_KEYWORD
|
|
1040
1209
|
};
|
|
1041
1210
|
}), ...known.map((entry) => {
|
|
1042
|
-
const name = entry.origin === "curated" ? spell(entry.name) : entry.name;
|
|
1211
|
+
const name = entry.origin === "curated" && !functionsByCase ? spell(entry.name) : entry.name;
|
|
1043
1212
|
const written = entry.origin === "curated" ? name : spellCallable(kind, name);
|
|
1044
1213
|
const call = entry.qualifier === "" ? written : `${spellIdentifier(kind, entry.qualifier)}.${written}`;
|
|
1045
1214
|
return {
|
|
@@ -1161,7 +1330,8 @@ const DIALECTS = {
|
|
|
1161
1330
|
oracle: "plsql",
|
|
1162
1331
|
dm: "plsql",
|
|
1163
1332
|
sqlserver: "transactsql",
|
|
1164
|
-
kingbase: "postgresql"
|
|
1333
|
+
kingbase: "postgresql",
|
|
1334
|
+
clickhouse: "clickhouse"
|
|
1165
1335
|
};
|
|
1166
1336
|
const PLACEHOLDERS = String.raw`\?|:[A-Za-z_]\w*`;
|
|
1167
1337
|
function sentinelFor(text) {
|
|
@@ -1426,8 +1596,12 @@ function createSqlAssist(kind, catalog) {
|
|
|
1426
1596
|
}
|
|
1427
1597
|
//#endregion
|
|
1428
1598
|
//#region src/markers.ts
|
|
1429
|
-
const SCAN = /[A-Z_\u0080-\uFFFF][\w$\u0080-\uFFFF]*|(?<qq>\?\?)|::|(?<q>\?)
|
|
1430
|
-
const QUESTION_IS_NATIVE = /* @__PURE__ */ new Set([
|
|
1599
|
+
const SCAN = /[A-Z_\u0080-\uFFFF][\w$\u0080-\uFFFF]*|(?<qq>\?\?)|::|(?<q>\?)|(?<!\.):(?<name>[A-Z_]\w*)|(?<colonDigits>:\d+)|(?<dollarDigits>\$\d+)|(?<atDigits>@P\d+)/gi;
|
|
1600
|
+
const QUESTION_IS_NATIVE = /* @__PURE__ */ new Set([
|
|
1601
|
+
"mysql",
|
|
1602
|
+
"dm",
|
|
1603
|
+
"clickhouse"
|
|
1604
|
+
]);
|
|
1431
1605
|
const NATIVE_GROUP = {
|
|
1432
1606
|
postgres: "dollarDigits",
|
|
1433
1607
|
mysql: null,
|
|
@@ -1435,7 +1609,8 @@ const NATIVE_GROUP = {
|
|
|
1435
1609
|
oracle: "colonDigits",
|
|
1436
1610
|
sqlserver: "atDigits",
|
|
1437
1611
|
dm: null,
|
|
1438
|
-
kingbase: "dollarDigits"
|
|
1612
|
+
kingbase: "dollarDigits",
|
|
1613
|
+
clickhouse: null
|
|
1439
1614
|
};
|
|
1440
1615
|
function statementMarkers(statement, kind) {
|
|
1441
1616
|
const names = [];
|
package/dist/monaco.d.ts
CHANGED
package/dist/react.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as SqlAssist } from "./assist-
|
|
1
|
+
import { t as SqlAssist } from "./assist-Cr2idunB.js";
|
|
2
2
|
import { ConsoleCaret, ConsoleEditorHandle, ConsoleEditorProps } from "@coldsmirk/inkstone-react/monaco";
|
|
3
3
|
import { ReactNode } from "react";
|
|
4
4
|
//#region src/react.d.ts
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@coldsmirk/inkstone-sql",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.34.0",
|
|
4
4
|
"description": "Schema-aware SQL editing intelligence for Monaco: lexical statement reading under per-dialect noise profiles, catalog-driven completion/hover/signature help, unified-placeholder awareness, formatting, and a drop-in React SQL editor.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"sql",
|
|
@@ -48,12 +48,12 @@
|
|
|
48
48
|
"devDependencies": {
|
|
49
49
|
"monaco-editor": "^0.57.0",
|
|
50
50
|
"react": "^19.3.0",
|
|
51
|
-
"@coldsmirk/inkstone-react": "^0.
|
|
51
|
+
"@coldsmirk/inkstone-react": "^0.34.0"
|
|
52
52
|
},
|
|
53
53
|
"peerDependencies": {
|
|
54
54
|
"monaco-editor": "^0.57.0",
|
|
55
55
|
"react": ">=19",
|
|
56
|
-
"@coldsmirk/inkstone-react": "^0.
|
|
56
|
+
"@coldsmirk/inkstone-react": "^0.34.0"
|
|
57
57
|
},
|
|
58
58
|
"peerDependenciesMeta": {
|
|
59
59
|
"@coldsmirk/inkstone-react": {
|