@coldsmirk/inkstone-sql 0.34.2 → 0.35.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 CHANGED
@@ -41,7 +41,18 @@ 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) (`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.
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. A statement that defines, changes or drops an object, or grants on one (its first word `CREATE`, `ALTER`, `DROP`, `COMMENT`, `RENAME`, `TRUNCATE`, `GRANT` or `REVOKE`), asks for no values: the scanner sends one as written, so a trigger's `:new` is the trigger's row and never a marker (`Placeholders.definesObjects`).
45
+
46
+ ## Where a statement ends
47
+
48
+ `runnableStatement` parts a script as the kind's own command-line client parts it (`separators.ts`), since that is the tool a pasted script was written for:
49
+
50
+ - **Oracle and 达梦** (SQL*Plus, DIsql): a line holding only `/` ends whatever stands above it; an anonymous block (`DECLARE` / `BEGIN`) or a stored PL/SQL unit (`CREATE [OR REPLACE] PROCEDURE | FUNCTION | PACKAGE | TRIGGER | TYPE | LIBRARY`, 达梦's `CLASS`) runs past its inner `;`s to the `;` after the `END` that closes it, read by its blocks' nesting, and keeps that `;`, which PL/SQL requires.
51
+ - **MySQL** (`mysql`): `DELIMITER` lines name what ends a statement until the next, and a routine (`CREATE … PROCEDURE | FUNCTION | TRIGGER | EVENT`) runs past the `;`s of its compound statements.
52
+ - **SQL Server** (sqlcmd): a line holding only `GO` ends a batch, and a `PROCEDURE`, `FUNCTION` or `TRIGGER` created or altered runs to its batch's end, as T-SQL reads a routine's body.
53
+ - **PostgreSQL and Kingbase** (psql): a `CREATE [OR REPLACE] FUNCTION | PROCEDURE` runs past the `;`s of a `BEGIN ATOMIC … END` body, counted as psql counts them.
54
+
55
+ Everything else ends at its `;`, which is left off. Completion, hover and signature help read the statement of a routine's body the caret is in; `separators.test.ts` is the guard.
45
56
 
46
57
  ## Structured refusals
47
58
 
@@ -169,7 +169,9 @@ declare function readStatement(text: string, cursor: number, kind: SqlKind): Sta
169
169
  type Runnable = {
170
170
  kind: "one";
171
171
  /**
172
- * The statement itself, without the whitespace around it or the `;` that ended it.
172
+ * The statement itself, without the whitespace around it or the separator that ended it:
173
+ * its `;`, a `/` or `GO` line, a `DELIMITER`'s own. A PL/SQL unit keeps its closing `;`,
174
+ * which is PL/SQL's own syntax (`separators.ts`).
173
175
  */
174
176
  text: string;
175
177
  /**
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-Cr2idunB.js";
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-lygAVE0T.js";
2
2
  //#region src/functions.d.ts
3
3
  /**
4
4
  * The function vocabulary this bench speaks: the curated floor this project wrote
package/dist/index.js CHANGED
@@ -431,18 +431,18 @@ const SPELLINGS = {
431
431
  reserved: CLICKHOUSE_RESERVED
432
432
  }
433
433
  };
434
- function delimited(spelling, name) {
434
+ function delimited$1(spelling, name) {
435
435
  const escaped = spelling.backslash === true ? name.replaceAll("\\", () => "\\\\") : name;
436
436
  return `${spelling.open}${escaped.replaceAll(spelling.close, () => spelling.close + spelling.close)}${spelling.close}`;
437
437
  }
438
438
  function spellIdentifier(kind, name) {
439
439
  const spelling = SPELLINGS[kind];
440
440
  if (spelling.regular.test(name) && !spelling.reserved.has(name.toLowerCase())) return name;
441
- return delimited(spelling, name);
441
+ return delimited$1(spelling, name);
442
442
  }
443
443
  function spellCallable(kind, name) {
444
444
  const spelling = SPELLINGS[kind];
445
- return spelling.regular.test(name) ? name : delimited(spelling, name);
445
+ return spelling.regular.test(name) ? name : delimited$1(spelling, name);
446
446
  }
447
447
  function spellQualifier(kind, source) {
448
448
  return source.aliasWritten === "" ? spellIdentifier(kind, source.table.name) : source.aliasWritten;
@@ -801,17 +801,354 @@ function insideNoise(spans, cursor) {
801
801
  return spans.some((span) => !span.name && cursor > span.start && (span.sealed ? cursor < span.end : cursor <= span.end));
802
802
  }
803
803
  //#endregion
804
+ //#region src/separators.ts
805
+ const TOKEN = /(?<word>[a-z_\u{0080}-\u{FFFF}][\w$#\u{0080}-\u{FFFF}]*)|\d[\w.]*|\S/giu;
806
+ const SLASH_LINE = /^\s*\/\s*$/;
807
+ const GO_LINE = /^\s*go(?:\s+\d+)?\s*$/i;
808
+ const DELIMITER_LINE = /^\s*delimiter\s+(?<delimiter>\S+)\s*$/i;
809
+ const PLSQL_MODIFIERS = /* @__PURE__ */ new Set([
810
+ "OR",
811
+ "REPLACE",
812
+ "EDITIONABLE",
813
+ "NONEDITIONABLE"
814
+ ]);
815
+ const DECLARES = /* @__PURE__ */ new Set([
816
+ ";",
817
+ "IS",
818
+ "AS",
819
+ "DECLARE",
820
+ "CREATE",
821
+ "REPLACE",
822
+ "EDITIONABLE",
823
+ "NONEDITIONABLE",
824
+ "MEMBER",
825
+ "STATIC",
826
+ "CONSTRUCTOR",
827
+ "MAP",
828
+ "ORDER",
829
+ "FINAL",
830
+ "INSTANTIABLE",
831
+ "OVERRIDING"
832
+ ]);
833
+ const HEADS = /* @__PURE__ */ new Set([
834
+ "CREATE",
835
+ "REPLACE",
836
+ "EDITIONABLE",
837
+ "NONEDITIONABLE"
838
+ ]);
839
+ const CALL_SPECS = /* @__PURE__ */ new Set([
840
+ "LANGUAGE",
841
+ "EXTERNAL",
842
+ "MLE"
843
+ ]);
844
+ const MYSQL_ROUTINES = /* @__PURE__ */ new Set([
845
+ "PROCEDURE",
846
+ "FUNCTION",
847
+ "TRIGGER",
848
+ "EVENT"
849
+ ]);
850
+ const MYSQL_STATEMENT_STARTS = /* @__PURE__ */ new Set([
851
+ ";",
852
+ "BEGIN",
853
+ "THEN",
854
+ "ELSE",
855
+ "DO",
856
+ "LOOP",
857
+ "REPEAT",
858
+ ":",
859
+ "ROW"
860
+ ]);
861
+ const TSQL_ROUTINES = /* @__PURE__ */ new Set([
862
+ "PROCEDURE",
863
+ "PROC",
864
+ "FUNCTION",
865
+ "TRIGGER"
866
+ ]);
867
+ function plsqlUnit(units) {
868
+ return ([first, ...rest]) => {
869
+ if (first === "DECLARE" || first === "BEGIN") return "plsql";
870
+ if (first !== "CREATE") return "plain";
871
+ const made = rest.find((word) => !PLSQL_MODIFIERS.has(word));
872
+ return made === void 0 ? null : units.has(made) ? "plsql" : "plain";
873
+ };
874
+ }
875
+ const MYSQL_DEFINERS = /* @__PURE__ */ new Set([
876
+ "DEFINER",
877
+ "AGGREGATE",
878
+ "=",
879
+ "@",
880
+ "(",
881
+ ")"
882
+ ]);
883
+ function mysqlUnit([first, ...rest]) {
884
+ if (first !== "CREATE") return "plain";
885
+ for (const [at, token] of rest.entries()) {
886
+ if (MYSQL_ROUTINES.has(token)) return "compound";
887
+ const before = at === 0 ? first : rest[at - 1];
888
+ if (!(MYSQL_DEFINERS.has(token) || before === "=" || before === "@")) return "plain";
889
+ }
890
+ return null;
891
+ }
892
+ function tsqlUnit([first, ...rest]) {
893
+ if (first !== "CREATE" && first !== "ALTER") return "plain";
894
+ const made = rest.find((word) => word !== "OR" && word !== "ALTER");
895
+ return made === void 0 ? null : TSQL_ROUTINES.has(made) ? "batch" : "plain";
896
+ }
897
+ function psqlUnit([first, ...rest]) {
898
+ if (first !== "CREATE") return "plain";
899
+ if (rest[0] === "OR" && rest.length < 3) return null;
900
+ const made = rest[0] === "OR" ? rest[1] === "REPLACE" ? rest[2] : "" : rest[0];
901
+ return made === void 0 ? null : made === "FUNCTION" || made === "PROCEDURE" ? "atomic" : "plain";
902
+ }
903
+ const UNIT_OF = {
904
+ oracle: plsqlUnit(/* @__PURE__ */ new Set([
905
+ "PROCEDURE",
906
+ "FUNCTION",
907
+ "PACKAGE",
908
+ "TRIGGER",
909
+ "TYPE",
910
+ "LIBRARY"
911
+ ])),
912
+ dm: plsqlUnit(/* @__PURE__ */ new Set([
913
+ "PROCEDURE",
914
+ "FUNCTION",
915
+ "PACKAGE",
916
+ "TRIGGER",
917
+ "TYPE",
918
+ "CLASS"
919
+ ])),
920
+ mysql: mysqlUnit,
921
+ sqlserver: tsqlUnit,
922
+ postgres: psqlUnit,
923
+ kingbase: psqlUnit,
924
+ doris: () => "plain",
925
+ clickhouse: () => "plain"
926
+ };
927
+ function parenthesized(reading, token) {
928
+ if (token === "(") reading.parens += 1;
929
+ else if (token === ")") reading.parens = Math.max(0, reading.parens - 1);
930
+ return token === "(" || token === ")" || reading.parens > 0;
931
+ }
932
+ function closes(reading, token, closers) {
933
+ const { closing } = reading;
934
+ reading.closing = false;
935
+ return closing && closers.has(token);
936
+ }
937
+ const PLSQL_CLOSERS = /* @__PURE__ */ new Set([
938
+ "IF",
939
+ "LOOP",
940
+ "CASE"
941
+ ]);
942
+ function declares(reading) {
943
+ reading.header ||= DECLARES.has(reading.previous);
944
+ }
945
+ function heads(reading) {
946
+ reading.header ||= HEADS.has(reading.previous);
947
+ }
948
+ function opens(reading) {
949
+ if (!reading.header) return;
950
+ reading.header = false;
951
+ reading.opened = true;
952
+ reading.frames.push("declare");
953
+ }
954
+ function times(reading) {
955
+ reading.header ||= reading.frames.at(-1) === "compound";
956
+ }
957
+ function controls({ frames }) {
958
+ frames.push("control");
959
+ }
960
+ const PLSQL_WORDS = /* @__PURE__ */ new Map([
961
+ ["DECLARE", ({ frames }) => {
962
+ frames.push("declare");
963
+ }],
964
+ ["BEGIN", ({ frames }) => {
965
+ if (frames.at(-1) === "declare") frames[frames.length - 1] = "body";
966
+ else frames.push("body");
967
+ }],
968
+ ["IF", controls],
969
+ ["LOOP", controls],
970
+ ["CASE", controls],
971
+ ["END", (reading) => {
972
+ reading.frames.pop();
973
+ reading.closing = true;
974
+ }],
975
+ ["IS", opens],
976
+ ["AS", opens],
977
+ ["PROCEDURE", declares],
978
+ ["FUNCTION", declares],
979
+ ["PACKAGE", heads],
980
+ ["CLASS", heads],
981
+ ["BODY", (reading) => {
982
+ reading.header ||= reading.previous === "TYPE";
983
+ }],
984
+ ["TRIGGER", (reading) => {
985
+ if (reading.previous === "COMPOUND") reading.frames.push("compound");
986
+ }],
987
+ ["BEFORE", times],
988
+ ["AFTER", times],
989
+ ["INSTEAD", times]
990
+ ]);
991
+ function plsql(reading, token) {
992
+ if (parenthesized(reading, token)) return false;
993
+ if (token === ";") {
994
+ reading.header = false;
995
+ reading.closing = false;
996
+ return reading.frames.length === 0;
997
+ }
998
+ if (reading.opened) {
999
+ reading.opened = false;
1000
+ if (CALL_SPECS.has(token)) reading.frames.pop();
1001
+ }
1002
+ if (!closes(reading, token, PLSQL_CLOSERS)) PLSQL_WORDS.get(token)?.(reading);
1003
+ return false;
1004
+ }
1005
+ const MYSQL_CLOSERS = /* @__PURE__ */ new Set([
1006
+ "IF",
1007
+ "CASE",
1008
+ "LOOP",
1009
+ "WHILE",
1010
+ "REPEAT"
1011
+ ]);
1012
+ function compound(reading, token) {
1013
+ if (parenthesized(reading, token)) return false;
1014
+ if (token === ";") {
1015
+ reading.closing = false;
1016
+ return reading.frames.length === 0;
1017
+ }
1018
+ if (closes(reading, token, MYSQL_CLOSERS)) return false;
1019
+ if (token === "BEGIN" || token === "CASE" || token === "LOOP") reading.frames.push("body");
1020
+ else if ((token === "IF" || token === "WHILE" || token === "REPEAT") && MYSQL_STATEMENT_STARTS.has(reading.previous)) reading.frames.push("control");
1021
+ else if (token === "END") {
1022
+ reading.frames.pop();
1023
+ reading.closing = true;
1024
+ }
1025
+ return false;
1026
+ }
1027
+ function atomic(reading, token) {
1028
+ if (parenthesized(reading, token)) return false;
1029
+ if (token === "BEGIN" || token === "CASE" && reading.depth >= 1) reading.depth += 1;
1030
+ else if (token === "END" && reading.depth > 0) reading.depth -= 1;
1031
+ return token === ";" && reading.depth === 0;
1032
+ }
1033
+ const ENDS = {
1034
+ plain: (_, token) => token === ";",
1035
+ plsql,
1036
+ compound,
1037
+ atomic,
1038
+ batch: () => false
1039
+ };
1040
+ function reading() {
1041
+ return {
1042
+ head: [],
1043
+ unit: null,
1044
+ frames: [],
1045
+ parens: 0,
1046
+ previous: "",
1047
+ closing: false,
1048
+ header: false,
1049
+ opened: false,
1050
+ depth: 0
1051
+ };
1052
+ }
1053
+ function token(script, value, at) {
1054
+ if (script.current === null && value === ";") {
1055
+ script.separators.push({
1056
+ from: at,
1057
+ to: at + 1
1058
+ });
1059
+ return;
1060
+ }
1061
+ const current = script.current ?? reading();
1062
+ script.current = current;
1063
+ if (current.unit === null && value !== ";") {
1064
+ current.head.push(value);
1065
+ current.unit = UNIT_OF[script.kind](current.head);
1066
+ }
1067
+ const unit = current.unit ?? "plain";
1068
+ const ends = ENDS[unit](current, value);
1069
+ current.previous = value;
1070
+ if (ends) {
1071
+ script.separators.push(unit === "plsql" ? {
1072
+ from: at + 1,
1073
+ to: at + 1
1074
+ } : {
1075
+ from: at,
1076
+ to: at + 1
1077
+ });
1078
+ script.current = null;
1079
+ }
1080
+ }
1081
+ function delimited(script, line, lineStart) {
1082
+ const { delimiter } = script;
1083
+ let after = 0;
1084
+ for (let at = line.indexOf(delimiter); at !== -1; at = line.indexOf(delimiter, after)) {
1085
+ script.separators.push({
1086
+ from: lineStart + at,
1087
+ to: lineStart + at + delimiter.length
1088
+ });
1089
+ script.current = null;
1090
+ after = at + delimiter.length;
1091
+ }
1092
+ if (line.slice(after).trim() !== "") script.current ??= reading();
1093
+ }
1094
+ function statementSeparators(text, blanked, kind) {
1095
+ const script = {
1096
+ kind,
1097
+ separators: [],
1098
+ current: null,
1099
+ delimiter: ";"
1100
+ };
1101
+ const lineEnds = kind === "oracle" || kind === "dm" ? SLASH_LINE : kind === "sqlserver" ? GO_LINE : null;
1102
+ for (let lineStart = 0; lineStart <= blanked.length;) {
1103
+ const newline = blanked.indexOf("\n", lineStart);
1104
+ const lineEnd = newline === -1 ? blanked.length : newline;
1105
+ const line = blanked.slice(lineStart, lineEnd);
1106
+ const directive = kind === "mysql" && script.current === null && DELIMITER_LINE.test(line);
1107
+ if (lineEnds?.test(line) === true) {
1108
+ script.separators.push({
1109
+ from: lineStart,
1110
+ to: lineEnd
1111
+ });
1112
+ script.current = null;
1113
+ } else if (directive) {
1114
+ script.delimiter = DELIMITER_LINE.exec(text.slice(lineStart, lineEnd))?.groups?.delimiter ?? ";";
1115
+ script.separators.push({
1116
+ from: lineStart,
1117
+ to: lineEnd
1118
+ });
1119
+ } else if (script.delimiter === ";") for (const match of line.matchAll(TOKEN)) token(script, (match.groups.word ?? match[0]).toUpperCase(), lineStart + match.index);
1120
+ else delimited(script, line, lineStart);
1121
+ lineStart = lineEnd + 1;
1122
+ }
1123
+ return script.separators;
1124
+ }
1125
+ //#endregion
804
1126
  //#region src/reader.ts
805
1127
  const TOKENS = /[\w$\u{0080}-\u{FFFF}]+|"(?:[^"]|"")*"|`(?:[^`]|``)*`|\[(?:[^\]]|\]\])*\]|\S/gu;
806
1128
  const TOKENS_TSQL = /[\w$#\u{0080}-\u{FFFF}]+|"(?:[^"]|"")*"|`(?:[^`]|``)*`|\[(?:[^\]]|\]\])*\]|\S/gu;
807
1129
  const WORD_KIND = /^[\w$"`[\u{0080}-\u{FFFF}]/u;
808
1130
  const WORD_KIND_TSQL = /^[\w$#"`[\u{0080}-\u{FFFF}]/u;
809
- function statementBounds(blanked, cursor) {
810
- const start = cursor === 0 ? 0 : blanked.lastIndexOf(";", cursor - 1) + 1;
811
- const close = blanked.indexOf(";", cursor);
1131
+ function unitBounds(separators, cursor, length) {
1132
+ let start = 0;
1133
+ for (const separator of separators) {
1134
+ if (separator.to > cursor) return {
1135
+ start,
1136
+ end: separator.from
1137
+ };
1138
+ start = separator.to;
1139
+ }
812
1140
  return {
813
1141
  start,
814
- end: close === -1 ? blanked.length : close
1142
+ end: length
1143
+ };
1144
+ }
1145
+ function statementBounds(blanked, separators, cursor) {
1146
+ const unit = unitBounds(separators, cursor, blanked.length);
1147
+ const before = cursor === 0 ? -1 : blanked.lastIndexOf(";", cursor - 1);
1148
+ const close = blanked.indexOf(";", cursor);
1149
+ return {
1150
+ start: Math.max(unit.start, before + 1),
1151
+ end: close === -1 ? unit.end : Math.min(unit.end, close)
815
1152
  };
816
1153
  }
817
1154
  function tokenize(blanked, bounds, kind) {
@@ -843,7 +1180,8 @@ function word(token) {
843
1180
  function scan(text, cursor, kind) {
844
1181
  const noise = blankNoise(text, kind);
845
1182
  if (insideNoise(noise.spans, cursor)) return null;
846
- return tokenize(noise.withNames, statementBounds(noise.blanked, cursor), kind);
1183
+ const separators = statementSeparators(text, noise.blanked, kind);
1184
+ return tokenize(noise.withNames, statementBounds(noise.blanked, separators, cursor), kind);
847
1185
  }
848
1186
  const TABLE_ANCHORS = /* @__PURE__ */ new Set([
849
1187
  "from",
@@ -1067,22 +1405,28 @@ function trimmedSpan(text, blanked, from, to) {
1067
1405
  to: end
1068
1406
  };
1069
1407
  }
1070
- function statementSpans(text, blanked, from, to) {
1408
+ function statementSpans(text, blanked, separators, from, to) {
1071
1409
  const spans = [];
1072
1410
  let start = from;
1073
- for (let at = from; at <= to; at += 1) {
1074
- if (!(at === to || blanked[at] === ";")) continue;
1075
- const span = trimmedSpan(text, blanked, start, at);
1411
+ const piece = (end) => {
1412
+ const span = trimmedSpan(text, blanked, start, Math.max(start, end));
1076
1413
  if (span !== null) spans.push(span);
1077
- start = at + 1;
1414
+ };
1415
+ for (const separator of separators) {
1416
+ if (separator.to <= from) continue;
1417
+ if (separator.from >= to) break;
1418
+ piece(separator.from);
1419
+ start = Math.min(Math.max(start, separator.to), to);
1078
1420
  }
1421
+ piece(to);
1079
1422
  return spans;
1080
1423
  }
1081
1424
  function runnableStatement(text, from, to, kind) {
1082
1425
  const { blanked } = blankNoise(text, kind);
1426
+ const separators = statementSeparators(text, blanked, kind);
1083
1427
  const selected = from < to;
1084
- const around = statementBounds(blanked, from);
1085
- const spans = selected ? statementSpans(text, blanked, from, to) : statementSpans(text, blanked, around.start, around.end);
1428
+ const around = unitBounds(separators, from, text.length);
1429
+ const spans = selected ? statementSpans(text, blanked, separators, from, to) : statementSpans(text, blanked, separators, around.start, around.end);
1086
1430
  if (spans.length === 0) return { kind: "none" };
1087
1431
  if (selected && spans.length > 1) return { kind: "several" };
1088
1432
  const span = spans[0];
@@ -1093,7 +1437,7 @@ function runnableStatement(text, from, to, kind) {
1093
1437
  blanked: blankNoise(statement, kind, "driver").blanked,
1094
1438
  from: span.from,
1095
1439
  to: span.to,
1096
- alone: statementSpans(text, blanked, 0, text.length).length <= 1
1440
+ alone: statementSpans(text, blanked, separators, 0, text.length).length <= 1
1097
1441
  };
1098
1442
  }
1099
1443
  function readIdentifier(text, offset, kind) {
@@ -1612,7 +1956,28 @@ const NATIVE_GROUP = {
1612
1956
  kingbase: "dollarDigits",
1613
1957
  clickhouse: null
1614
1958
  };
1959
+ const DEFINING = /* @__PURE__ */ new Set([
1960
+ "CREATE",
1961
+ "ALTER",
1962
+ "DROP",
1963
+ "COMMENT",
1964
+ "RENAME",
1965
+ "TRUNCATE",
1966
+ "GRANT",
1967
+ "REVOKE"
1968
+ ]);
1969
+ const FIRST_WORD = /^\s*(?<word>[a-z_\u{0080}-\u{FFFF}][\w$\u{0080}-\u{FFFF}]*)/iu;
1970
+ function definesObjects(statement) {
1971
+ const word = FIRST_WORD.exec(statement)?.groups?.word;
1972
+ return word !== void 0 && DEFINING.has(word.toUpperCase());
1973
+ }
1615
1974
  function statementMarkers(statement, kind) {
1975
+ if (definesObjects(statement)) return {
1976
+ form: "none",
1977
+ names: [],
1978
+ count: 0,
1979
+ refusal: null
1980
+ };
1616
1981
  const names = [];
1617
1982
  const nativeGroup = NATIVE_GROUP[kind];
1618
1983
  let count = 0;
package/dist/monaco.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { t as SqlAssist } from "./assist-Cr2idunB.js";
1
+ import { t as SqlAssist } from "./assist-lygAVE0T.js";
2
2
  import * as monacoEditor from "monaco-editor";
3
3
  import { IDisposable } from "monaco-editor";
4
4
  //#region src/monaco.d.ts
package/dist/react.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { t as SqlAssist } from "./assist-Cr2idunB.js";
1
+ import { t as SqlAssist } from "./assist-lygAVE0T.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.34.2",
3
+ "version": "0.35.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.34.2"
51
+ "@coldsmirk/inkstone-react": "^0.35.0"
52
52
  },
53
53
  "peerDependencies": {
54
54
  "monaco-editor": "^0.57.0",
55
55
  "react": ">=19",
56
- "@coldsmirk/inkstone-react": "^0.34.2"
56
+ "@coldsmirk/inkstone-react": "^0.35.0"
57
57
  },
58
58
  "peerDependenciesMeta": {
59
59
  "@coldsmirk/inkstone-react": {