@chkit/core 0.1.2-beta.5 → 0.2.0-beta.11

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.
Files changed (93) hide show
  1. package/README.md +26 -6
  2. package/dist/canonical.d.ts.map +1 -1
  3. package/dist/canonical.js +63 -7
  4. package/dist/canonical.js.map +1 -1
  5. package/dist/column-default.d.ts +62 -0
  6. package/dist/column-default.d.ts.map +1 -0
  7. package/dist/column-default.js +212 -0
  8. package/dist/column-default.js.map +1 -0
  9. package/dist/conflict-markers.d.ts +3 -0
  10. package/dist/conflict-markers.d.ts.map +1 -0
  11. package/dist/conflict-markers.js +9 -0
  12. package/dist/conflict-markers.js.map +1 -0
  13. package/dist/identifier.d.ts +22 -0
  14. package/dist/identifier.d.ts.map +1 -0
  15. package/dist/identifier.js +69 -0
  16. package/dist/identifier.js.map +1 -0
  17. package/dist/index.d.ts +10 -3
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.js +10 -3
  20. package/dist/index.js.map +1 -1
  21. package/dist/insert-columns.d.ts +13 -0
  22. package/dist/insert-columns.d.ts.map +1 -0
  23. package/dist/insert-columns.js +36 -0
  24. package/dist/insert-columns.js.map +1 -0
  25. package/dist/kafka.d.ts +9 -0
  26. package/dist/kafka.d.ts.map +1 -0
  27. package/dist/kafka.js +64 -0
  28. package/dist/kafka.js.map +1 -0
  29. package/dist/key-clause.d.ts +6 -1
  30. package/dist/key-clause.d.ts.map +1 -1
  31. package/dist/key-clause.js +12 -13
  32. package/dist/key-clause.js.map +1 -1
  33. package/dist/model-types.d.ts +123 -7
  34. package/dist/model-types.d.ts.map +1 -1
  35. package/dist/model.d.ts +4 -2
  36. package/dist/model.d.ts.map +1 -1
  37. package/dist/model.js +13 -3
  38. package/dist/model.js.map +1 -1
  39. package/dist/object-dependencies.d.ts +18 -0
  40. package/dist/object-dependencies.d.ts.map +1 -0
  41. package/dist/object-dependencies.js +351 -0
  42. package/dist/object-dependencies.js.map +1 -0
  43. package/dist/on-cluster.d.ts.map +1 -1
  44. package/dist/on-cluster.js +18 -9
  45. package/dist/on-cluster.js.map +1 -1
  46. package/dist/planner.d.ts.map +1 -1
  47. package/dist/planner.js +144 -81
  48. package/dist/planner.js.map +1 -1
  49. package/dist/projection.d.ts.map +1 -1
  50. package/dist/projection.js +5 -40
  51. package/dist/projection.js.map +1 -1
  52. package/dist/schema-loader.d.ts +2 -0
  53. package/dist/schema-loader.d.ts.map +1 -1
  54. package/dist/schema-loader.js +72 -5
  55. package/dist/schema-loader.js.map +1 -1
  56. package/dist/sql-lexer.d.ts +29 -0
  57. package/dist/sql-lexer.d.ts.map +1 -0
  58. package/dist/sql-lexer.js +220 -0
  59. package/dist/sql-lexer.js.map +1 -0
  60. package/dist/sql-normalizer.d.ts +48 -0
  61. package/dist/sql-normalizer.d.ts.map +1 -1
  62. package/dist/sql-normalizer.js +118 -1
  63. package/dist/sql-normalizer.js.map +1 -1
  64. package/dist/sql-scan.d.ts +20 -0
  65. package/dist/sql-scan.d.ts.map +1 -0
  66. package/dist/sql-scan.js +82 -0
  67. package/dist/sql-scan.js.map +1 -0
  68. package/dist/sql-splitter.d.ts.map +1 -1
  69. package/dist/sql-splitter.js +52 -5
  70. package/dist/sql-splitter.js.map +1 -1
  71. package/dist/sql.d.ts +9 -1
  72. package/dist/sql.d.ts.map +1 -1
  73. package/dist/sql.js +95 -36
  74. package/dist/sql.js.map +1 -1
  75. package/dist/text-index-sql.d.ts +6 -0
  76. package/dist/text-index-sql.d.ts.map +1 -0
  77. package/dist/text-index-sql.js +200 -0
  78. package/dist/text-index-sql.js.map +1 -0
  79. package/dist/text-index.d.ts +10 -0
  80. package/dist/text-index.d.ts.map +1 -0
  81. package/dist/text-index.js +121 -0
  82. package/dist/text-index.js.map +1 -0
  83. package/dist/ts-import.d.ts.map +1 -1
  84. package/dist/ts-import.js +23 -3
  85. package/dist/ts-import.js.map +1 -1
  86. package/dist/validate.d.ts.map +1 -1
  87. package/dist/validate.js +350 -3
  88. package/dist/validate.js.map +1 -1
  89. package/package.json +1 -1
  90. package/dist/utils.d.ts +0 -2
  91. package/dist/utils.d.ts.map +0 -1
  92. package/dist/utils.js +0 -2
  93. package/dist/utils.js.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"text-index-sql.js","sourceRoot":"","sources":["../src/text-index-sql.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ import type { TextSkipIndex } from './model-types.js';
2
+ export { normalizeTextIndexSQL } from './text-index-sql.js';
3
+ export declare const TEXT_INDEX_GRANULARITY = 100000000;
4
+ type Params = Omit<TextSkipIndex, 'name' | 'type' | 'expression' | 'granularity'>;
5
+ export declare function renderTextIndexType(index: Params): string;
6
+ /** Fail explicitly on unknown or malformed parameters rather than losing them on pull. */
7
+ export declare function parseTextIndexParams(args: string): Params;
8
+ export declare function canonicalizeTextIndex(index: TextSkipIndex): TextSkipIndex;
9
+ export declare function textIndexFingerprint(index: TextSkipIndex): string;
10
+ //# sourceMappingURL=text-index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"text-index.d.ts","sourceRoot":"","sources":["../src/text-index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAA;AASrD,OAAO,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAA;AAC3D,eAAO,MAAM,sBAAsB,YAAc,CAAA;AAEjD,KAAK,MAAM,GAAG,IAAI,CAAC,aAAa,EAAE,MAAM,GAAG,MAAM,GAAG,YAAY,GAAG,aAAa,CAAC,CAAA;AAYjF,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CA4BzD;AAED,0FAA0F;AAC1F,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAoCzD;AAED,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,aAAa,GAAG,aAAa,CAUzE;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,aAAa,GAAG,MAAM,CAajE"}
@@ -0,0 +1,121 @@
1
+ import { formatTextSQL, normalizeTextIndexSQL, textExpressionFingerprint, textSQLTokens, textSQLFingerprint, } from './text-index-sql.js';
2
+ export { normalizeTextIndexSQL } from './text-index-sql.js';
3
+ export const TEXT_INDEX_GRANULARITY = 100_000_000;
4
+ const PARAMETERS = [
5
+ ['tokenizer', 'tokenizer', 'sql'],
6
+ ['preprocessor', 'preprocessor', 'sql'],
7
+ ['postprocessor', 'postprocessor', 'sql'],
8
+ ['supportPhraseSearch', 'support_phrase_search', 'boolean'],
9
+ ['dictionaryBlockSize', 'dictionary_block_size', 'number'],
10
+ ['dictionaryBlockFrontcodingCompression', 'dictionary_block_frontcoding_compression', 'boolean'],
11
+ ['postingListBlockSize', 'posting_list_block_size', 'number'],
12
+ ['postingListCodec', 'posting_list_codec', 'codec'],
13
+ ];
14
+ export function renderTextIndexType(index) {
15
+ if (typeof index.tokenizer !== 'string' || !textSQLTokens(index.tokenizer).length) {
16
+ throw new Error('A non-empty tokenizer is required');
17
+ }
18
+ const parts = [];
19
+ for (const [field, key, kind] of PARAMETERS) {
20
+ const value = index[field];
21
+ if (value === undefined)
22
+ continue;
23
+ let sql;
24
+ if (kind === 'sql') {
25
+ if (typeof value !== 'string' || !textSQLTokens(value).length)
26
+ throw new Error(`${field} must be non-empty SQL`);
27
+ sql = normalizeTextIndexSQL(value);
28
+ }
29
+ else if (kind === 'boolean') {
30
+ if (typeof value !== 'boolean')
31
+ throw new Error(`${field} must be a boolean`);
32
+ sql = value ? '1' : '0';
33
+ }
34
+ else if (kind === 'number') {
35
+ if (typeof value !== 'number' || !Number.isSafeInteger(value) || value <= 0)
36
+ throw new Error(`${field} must be a positive safe integer`);
37
+ sql = String(value);
38
+ }
39
+ else {
40
+ if (value !== 'none' && value !== 'bitpacking')
41
+ throw new Error(`${field} must be none or bitpacking`);
42
+ sql = `'${value}'`;
43
+ }
44
+ parts.push(`${key} = ${sql}`);
45
+ }
46
+ return `text(${parts.join(', ')})`;
47
+ }
48
+ /** Fail explicitly on unknown or malformed parameters rather than losing them on pull. */
49
+ export function parseTextIndexParams(args) {
50
+ const tokens = textSQLTokens(args);
51
+ let group = [];
52
+ const groups = [group];
53
+ let depth = 0;
54
+ for (const token of tokens) {
55
+ if (['(', '[', '{'].includes(token))
56
+ depth++;
57
+ if ([')', ']', '}'].includes(token))
58
+ depth--;
59
+ if (token === ',' && depth === 0) {
60
+ group = [];
61
+ groups.push(group);
62
+ }
63
+ else
64
+ group.push(token);
65
+ }
66
+ const params = {};
67
+ for (const group of groups) {
68
+ const [key, equals, ...value] = group;
69
+ const parameter = PARAMETERS.find((item) => item[1] === key);
70
+ if (!parameter || equals !== '=' || value.length === 0)
71
+ throw new Error(`Invalid or unsupported text index parameter: ${formatTextSQL(group)}`);
72
+ const [field, , kind] = parameter;
73
+ if (field in params)
74
+ throw new Error(`Duplicate text index parameter: ${key}`);
75
+ const raw = formatTextSQL(value);
76
+ if (kind === 'boolean') {
77
+ if (!/^(0|1|true|false)$/i.test(raw))
78
+ throw new Error(`Invalid boolean for ${key}: ${raw}`);
79
+ params[field] = raw === '1' || raw.toLowerCase() === 'true';
80
+ }
81
+ else if (kind === 'number') {
82
+ if (!/^\d+$/.test(raw))
83
+ throw new Error(`Invalid integer for ${key}: ${raw}`);
84
+ params[field] = Number(raw);
85
+ }
86
+ else if (kind === 'codec') {
87
+ params[field] = raw.slice(1, -1);
88
+ if (raw !== "'none'" && raw !== "'bitpacking'")
89
+ throw new Error(`Invalid codec: ${raw}`);
90
+ }
91
+ else
92
+ params[field] = raw;
93
+ }
94
+ const result = params;
95
+ renderTextIndexType(result);
96
+ return result;
97
+ }
98
+ export function canonicalizeTextIndex(index) {
99
+ // Parse/render fixes key order as well as SQL formatting for migration snapshots.
100
+ const type = renderTextIndexType(index);
101
+ return {
102
+ name: index.name,
103
+ expression: textExpressionFingerprint(index.expression),
104
+ type: 'text',
105
+ granularity: TEXT_INDEX_GRANULARITY,
106
+ ...parseTextIndexParams(type.slice(5, -1)),
107
+ };
108
+ }
109
+ export function textIndexFingerprint(index) {
110
+ const canonical = canonicalizeTextIndex(index);
111
+ return JSON.stringify({
112
+ ...canonical,
113
+ expression: textSQLFingerprint(canonical.expression),
114
+ tokenizer: textSQLFingerprint(canonical.tokenizer),
115
+ preprocessor: canonical.preprocessor === undefined ? undefined : textSQLFingerprint(canonical.preprocessor),
116
+ postprocessor: canonical.postprocessor === undefined
117
+ ? undefined
118
+ : textSQLFingerprint(canonical.postprocessor),
119
+ });
120
+ }
121
+ //# sourceMappingURL=text-index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"text-index.js","sourceRoot":"","sources":["../src/text-index.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"ts-import.d.ts","sourceRoot":"","sources":["../src/ts-import.ts"],"names":[],"mappings":"AAEA,KAAK,eAAe,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;AAiB9C;;;;;;;GAOG;AACH,wBAAsB,gBAAgB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC,CAMrF"}
1
+ {"version":3,"file":"ts-import.d.ts","sourceRoot":"","sources":["../src/ts-import.ts"],"names":[],"mappings":"AAGA,KAAK,eAAe,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;AAyB9C;;;;;;;GAOG;AACH,wBAAsB,gBAAgB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC,CAIrF"}
package/dist/ts-import.js CHANGED
@@ -1,5 +1,13 @@
1
+ import { realpath } from 'node:fs/promises';
1
2
  import { pathToFileURL } from 'node:url';
2
3
  let cachedNodeImporter = null;
4
+ // Bun never settles a second import() of a file that failed to parse, even after
5
+ // the file is fixed, and a second import() of a module whose evaluation threw
6
+ // resolves to the half-evaluated module. Each failure is kept and thrown again,
7
+ // so importing the same file twice in one process fails the same way. Bun knows
8
+ // a module by its real path, so the import and the kept failure use it too: a
9
+ // path through a symlink (such as /tmp on macOS) is the same file.
10
+ const failedBunImports = new Map();
3
11
  function isBun() {
4
12
  return typeof globalThis.Bun !== 'undefined';
5
13
  }
@@ -20,10 +28,22 @@ async function getNodeImporter() {
20
28
  * modules load the same way under either runtime.
21
29
  */
22
30
  export async function importModuleFile(absolutePath) {
23
- if (isBun()) {
24
- return (await import(pathToFileURL(absolutePath).href));
25
- }
31
+ if (isBun())
32
+ return importWithBun(absolutePath);
26
33
  const nodeImport = await getNodeImporter();
27
34
  return nodeImport(absolutePath);
28
35
  }
36
+ async function importWithBun(absolutePath) {
37
+ // A path that does not resolve is imported as given, so the import reports it.
38
+ const href = pathToFileURL(await realpath(absolutePath).catch(() => absolutePath)).href;
39
+ if (failedBunImports.has(href))
40
+ throw failedBunImports.get(href);
41
+ try {
42
+ return (await import(href));
43
+ }
44
+ catch (error) {
45
+ failedBunImports.set(href, error);
46
+ throw error;
47
+ }
48
+ }
29
49
  //# sourceMappingURL=ts-import.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ts-import.js","sourceRoot":"","sources":["../src/ts-import.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AAKxC,IAAI,kBAAkB,GAA0B,IAAI,CAAA;AAEpD,SAAS,KAAK;IACZ,OAAO,OAAQ,UAAgC,CAAC,GAAG,KAAK,WAAW,CAAA;AACrE,CAAC;AAED,KAAK,UAAU,eAAe;IAC5B,IAAI,kBAAkB;QAAE,OAAO,kBAAkB,CAAA;IACjD,MAAM,EAAE,UAAU,EAAE,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,CAAA;IAC3C,MAAM,IAAI,GAAG,UAAU,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;IACxC,kBAAkB,GAAG,CAAC,EAAE,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAA6B,CAAA;IACxE,OAAO,kBAAkB,CAAA;AAC3B,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,YAAoB;IACzD,IAAI,KAAK,EAAE,EAAE,CAAC;QACZ,OAAO,CAAC,MAAM,MAAM,CAAC,aAAa,CAAC,YAAY,CAAC,CAAC,IAAI,CAAC,CAAoB,CAAA;IAC5E,CAAC;IACD,MAAM,UAAU,GAAG,MAAM,eAAe,EAAE,CAAA;IAC1C,OAAO,UAAU,CAAC,YAAY,CAAC,CAAA;AACjC,CAAC"}
1
+ {"version":3,"file":"ts-import.js","sourceRoot":"","sources":["../src/ts-import.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAA;AAC3C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AAKxC,IAAI,kBAAkB,GAA0B,IAAI,CAAA;AAEpD,iFAAiF;AACjF,8EAA8E;AAC9E,gFAAgF;AAChF,gFAAgF;AAChF,8EAA8E;AAC9E,mEAAmE;AACnE,MAAM,gBAAgB,GAAG,IAAI,GAAG,EAAmB,CAAA;AAEnD,SAAS,KAAK;IACZ,OAAO,OAAQ,UAAgC,CAAC,GAAG,KAAK,WAAW,CAAA;AACrE,CAAC;AAED,KAAK,UAAU,eAAe;IAC5B,IAAI,kBAAkB;QAAE,OAAO,kBAAkB,CAAA;IACjD,MAAM,EAAE,UAAU,EAAE,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,CAAA;IAC3C,MAAM,IAAI,GAAG,UAAU,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;IACxC,kBAAkB,GAAG,CAAC,EAAE,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAA6B,CAAA;IACxE,OAAO,kBAAkB,CAAA;AAC3B,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,YAAoB;IACzD,IAAI,KAAK,EAAE;QAAE,OAAO,aAAa,CAAC,YAAY,CAAC,CAAA;IAC/C,MAAM,UAAU,GAAG,MAAM,eAAe,EAAE,CAAA;IAC1C,OAAO,UAAU,CAAC,YAAY,CAAC,CAAA;AACjC,CAAC;AAED,KAAK,UAAU,aAAa,CAAC,YAAoB;IAC/C,+EAA+E;IAC/E,MAAM,IAAI,GAAG,aAAa,CAAC,MAAM,QAAQ,CAAC,YAAY,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,YAAY,CAAC,CAAC,CAAC,IAAI,CAAA;IACvF,IAAI,gBAAgB,CAAC,GAAG,CAAC,IAAI,CAAC;QAAE,MAAM,gBAAgB,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;IAChE,IAAI,CAAC;QACH,OAAO,CAAC,MAAM,MAAM,CAAC,IAAI,CAAC,CAAoB,CAAA;IAChD,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,gBAAgB,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,CAAC,CAAA;QACjC,MAAM,KAAK,CAAA;IACb,CAAC;AACH,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../src/validate.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAIV,gBAAgB,EAEhB,eAAe,EAEhB,MAAM,YAAY,CAAA;AA0OnB,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,gBAAgB,EAAE,GAAG,eAAe,EAAE,CAwBtF;AAED,wBAAgB,sBAAsB,CAAC,WAAW,EAAE,gBAAgB,EAAE,GAAG,IAAI,CAG5E"}
1
+ {"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../src/validate.ts"],"names":[],"mappings":"AAsBA,OAAO,KAAK,EAMV,gBAAgB,EAEhB,eAAe,EAEhB,MAAM,YAAY,CAAA;AAiqBnB,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,gBAAgB,EAAE,GAAG,eAAe,EAAE,CA2BtF;AAED,wBAAgB,sBAAsB,CAAC,WAAW,EAAE,gBAAgB,EAAE,GAAG,IAAI,CAG5E"}
package/dist/validate.js CHANGED
@@ -1,7 +1,15 @@
1
+ import { renderTextIndexType } from './text-index.js';
1
2
  import { definitionKey } from './canonical.js';
2
3
  import { canonicalizeCodec, isGeneralCodec, isRawCodec } from './codec.js';
3
- import { isPlainColumnReference, normalizeKeyColumns } from './key-clause.js';
4
+ import { LEGACY_EXPRESSION_PREFIX, columnTypeAcceptsStringLiteral, isSQLExpression, parseColumnDefault, readsUnparsableEphemeralLiteralAsNull, renderDefault, startsWithFunctionCall, storesUnparsableLiteralAsNull, trimExpression, } from './column-default.js';
5
+ import { describeInvalidIdentifier } from './identifier.js';
6
+ import { isPlainColumnReference, normalizeKeyColumns, splitTopLevelComma } from './key-clause.js';
4
7
  import { isIndexProjection, normalizeProjectionIndex } from './projection.js';
8
+ import { stripWrappingParens } from './sql-scan.js';
9
+ import { isKafkaEngine } from './kafka.js';
10
+ import { tokenizeSQL } from './sql-lexer.js';
11
+ import { normalizeSQLFragment } from './sql-normalizer.js';
12
+ import { textSQLTokens } from './text-index-sql.js';
5
13
  import { ChxValidationError as ValidationError } from './model.js';
6
14
  function pushValidationIssue(issues, def, code, message) {
7
15
  issues.push({
@@ -38,7 +46,174 @@ function validateColumnCodec(def, column, issues) {
38
46
  pushValidationIssue(issues, def, 'codec_chain_must_end_with_general', `Table ${def.database}.${def.name} column "${column.name}" codec chain must end with a general codec (NONE, LZ4, LZ4HC, ZSTD, T64, GCD, ALP)`);
39
47
  }
40
48
  }
49
+ const COLUMN_DEFAULT_KINDS = new Set(['DEFAULT', 'MATERIALIZED', 'ALIAS', 'EPHEMERAL']);
50
+ // Runs on raw definitions (toCreateSQL) and canonical ones (planDiff), where
51
+ // `{ expression }` is already the `fn:` string; parseColumnDefault reads both.
52
+ function validateColumnDefault(def, column, issues) {
53
+ const subject = `Table ${def.database}.${def.name} column "${column.name}"`;
54
+ const kind = column.defaultKind ?? 'DEFAULT';
55
+ const value = column.default;
56
+ if (value === undefined) {
57
+ if (kind === 'MATERIALIZED' || kind === 'ALIAS') {
58
+ pushValidationIssue(issues, def, 'column_expression_required', `${subject} is ${kind} and requires a non-empty expression. Set default: { expression: "<sql>" }.`);
59
+ }
60
+ return;
61
+ }
62
+ if (value === null)
63
+ return;
64
+ if (typeof value === 'object' && !isSQLExpression(value)) {
65
+ pushValidationIssue(issues, def, 'column_default_invalid', `${subject} has an unsupported default value. Use a string, number, or boolean literal, or { expression: "<sql>" } for a SQL expression.`);
66
+ return;
67
+ }
68
+ const parsed = parseColumnDefault(value);
69
+ if (parsed.kind === 'expression') {
70
+ validateDefaultExpression(def, subject, kind, parsed.sql, issues);
71
+ return;
72
+ }
73
+ if (typeof parsed.value !== 'string')
74
+ return;
75
+ // A string renders as a quoted literal for every kind. MATERIALIZED and ALIAS
76
+ // compute their value, so there it is almost always SQL written without
77
+ // { expression }; a constant string is spelled { expression: "'text'" }.
78
+ if (kind === 'MATERIALIZED' || kind === 'ALIAS') {
79
+ pushValidationIssue(issues, def, 'column_expression_requires_fn', describePlainStringExpression(subject, kind, parsed.value));
80
+ return;
81
+ }
82
+ // DEFAULT and EPHEMERAL keep a string as a literal. On a column that cannot
83
+ // hold a string, one that starts with a function call is SQL written without
84
+ // { expression }: ClickHouse rejects the literal, or reads it as NULL (#234).
85
+ if ((kind !== 'DEFAULT' && kind !== 'EPHEMERAL') || !startsWithFunctionCall(parsed.value))
86
+ return;
87
+ const type = typeof column.type === 'string' ? column.type.trim() : '';
88
+ if (columnTypeAcceptsStringLiteral(type))
89
+ return;
90
+ pushValidationIssue(issues, def, 'column_default_looks_like_expression', describeQuotedFunctionDefault(subject, kind, column.nullable ? `Nullable(${type})` : type, parsed.value));
91
+ }
92
+ function validateDefaultExpression(def, subject, kind, sql, issues) {
93
+ const rendered = renderDefault({ expression: sql });
94
+ if (rendered === '') {
95
+ const removable = kind !== 'MATERIALIZED' && kind !== 'ALIAS';
96
+ pushValidationIssue(issues, def, 'column_expression_required', `${subject} has an empty default expression. Put the SQL in default: { expression: "<sql>" }${removable ? ', or remove default' : ''}.`);
97
+ return;
98
+ }
99
+ // The expression is rendered mid-statement, so an open string, quoted
100
+ // identifier or block comment would swallow the SQL after it, the next
101
+ // statements of the migration file included.
102
+ const tokens = tokenizeSQL(sql);
103
+ const unterminated = tokens.find((token) => !token.terminated);
104
+ if (unterminated !== undefined) {
105
+ pushValidationIssue(issues, def, 'column_default_invalid', `${subject} has default expression ${JSON.stringify(sql)} with an unterminated ${describeOpenToken(unterminated)}, which would swallow the rest of the generated SQL. Close it or remove it.`);
106
+ }
107
+ // ClickHouse reads `#` as a comment only before a space or `!`; any other
108
+ // `#` is a syntax error. Report it here instead of at migrate: at the end of
109
+ // an expression it used to turn the ` COMMENT` or ` CODEC` rendered after it
110
+ // into a comment, and the statement applied without them.
111
+ if (tokens.some(isStrayHash)) {
112
+ pushValidationIssue(issues, def, 'column_default_invalid', `${subject} has default expression ${JSON.stringify(sql)} with a # that starts no comment, which ClickHouse rejects. Remove it, or put a space after it to start a comment: "# note".`);
113
+ }
114
+ // The prefix only marks a plain string as SQL. Inside an expression it is
115
+ // text: `{ expression: 'fn:now()' }` would render `DEFAULT fn:now()`.
116
+ if (keepsLegacyPrefix(rendered)) {
117
+ const unprefixed = rendered.slice(LEGACY_EXPRESSION_PREFIX.length).trim();
118
+ pushValidationIssue(issues, def, 'column_default_invalid', `${subject} has default expression ${JSON.stringify(rendered)}, which keeps the legacy fn: prefix and would render ${kind} ${rendered}, a syntax error. Remove the prefix: default: { expression: ${JSON.stringify(unprefixed)} }.`);
119
+ }
120
+ }
121
+ // Whether validateDefaultExpression accepts `sql`, so that a message can
122
+ // suggest it as the fix.
123
+ function passesExpressionChecks(sql) {
124
+ const rendered = renderDefault({ expression: sql });
125
+ return (rendered !== '' &&
126
+ !keepsLegacyPrefix(rendered) &&
127
+ tokenizeSQL(sql).every((token) => token.terminated && !isStrayHash(token)));
128
+ }
129
+ // `fn::String` is not a leftover prefix: it casts a column named fn.
130
+ function keepsLegacyPrefix(sql) {
131
+ return sql.startsWith(LEGACY_EXPRESSION_PREFIX) && sql[LEGACY_EXPRESSION_PREFIX.length] !== ':';
132
+ }
133
+ function isStrayHash(token) {
134
+ return token.kind === 'punctuation' && token.text === '#';
135
+ }
136
+ // The lexer leaves only strings, quoted identifiers and block comments open.
137
+ function describeOpenToken(token) {
138
+ if (token.kind === 'block_comment')
139
+ return 'block comment';
140
+ if (token.kind === 'quoted_identifier')
141
+ return 'quoted identifier';
142
+ return 'string literal';
143
+ }
144
+ function describePlainStringExpression(subject, kind, value) {
145
+ const literal = renderDefault(value);
146
+ const fix = suggestedExpression(value);
147
+ const asSQL = fix === undefined
148
+ ? 'default: { expression: "<sql>" } for a SQL expression'
149
+ : `default: { expression: ${JSON.stringify(fix.expression)} } to render ${kind} ${fix.rendered} (legacy spelling: ${JSON.stringify(`${LEGACY_EXPRESSION_PREFIX}${fix.expression}`)})`;
150
+ return `${subject} is ${kind} with plain string default ${JSON.stringify(value)}, which renders as the quoted literal ${literal} instead of SQL. Use ${asSQL}, or default: { expression: ${JSON.stringify(literal)} } for a constant string.`;
151
+ }
152
+ // `effectiveType` is the type as rendered, with `nullable: true` applied. The
153
+ // quoted-text option is for a type chkit misreads, such as a string type it
154
+ // does not know: on the types it recognizes, that literal is what fails.
155
+ function describeQuotedFunctionDefault(subject, kind, effectiveType, value) {
156
+ const literal = renderDefault(value);
157
+ const fix = suggestedExpression(value);
158
+ const readsNull = kind === 'EPHEMERAL'
159
+ ? readsUnparsableEphemeralLiteralAsNull(effectiveType)
160
+ : storesUnparsableLiteralAsNull(effectiveType);
161
+ const outcome = readsNull
162
+ ? `which ClickHouse accepts for type ${effectiveType} but ${kind === 'EPHEMERAL' ? 'reads' : 'stores'} as NULL`
163
+ : `which ClickHouse rejects for type ${effectiveType}`;
164
+ const asSQL = fix === undefined
165
+ ? 'Use default: { expression: "<sql>" } for a SQL expression.'
166
+ : `Use default: { expression: ${JSON.stringify(fix.expression)} } to render ${kind} ${fix.rendered}.`;
167
+ return `${subject} has default ${JSON.stringify(value)}, a plain string that looks like a SQL function call. Plain strings render as quoted literals (${kind} ${literal}), ${outcome}. ${asSQL} If chkit misjudged the type and the column should ${kind === 'EPHEMERAL' ? 'hold' : 'store'} this text, use default: { expression: ${JSON.stringify(literal)} }.`;
168
+ }
169
+ /**
170
+ * The plain string as the `{ expression }` a message suggests, with the SQL it
171
+ * renders on one line, or undefined when validation would reject that
172
+ * expression too (no SQL, a leftover `fn:` prefix, an unterminated token or a
173
+ * stray `#`).
174
+ */
175
+ function suggestedExpression(value) {
176
+ const expression = trimExpression(value);
177
+ if (!passesExpressionChecks(expression))
178
+ return undefined;
179
+ const rendered = tokenizeSQL(renderDefault({ expression }))
180
+ .map((token) => (token.kind === 'whitespace' ? ' ' : token.text))
181
+ .join('');
182
+ return { expression, rendered };
183
+ }
41
184
  function validateTableDefinition(def, issues) {
185
+ const kafka = isKafkaEngine(def.engine);
186
+ if (kafka) {
187
+ for (const field of ['primaryKey', 'orderBy', 'uniqueKey', 'partitionBy', 'ttl', 'indexes', 'projections']) {
188
+ const value = def[field];
189
+ if (Array.isArray(value) ? value.length > 0 : Boolean(value)) {
190
+ pushValidationIssue(issues, def, 'kafka_unsupported_clause', `Kafka table ${def.database}.${def.name} does not support ${field}. Put storage clauses on the destination table.`);
191
+ }
192
+ }
193
+ for (const column of def.columns) {
194
+ if (column.default !== undefined || (column.defaultKind ?? 'DEFAULT') !== 'DEFAULT') {
195
+ pushValidationIssue(issues, def, 'kafka_column_default', `Kafka table ${def.database}.${def.name} column "${column.name}" cannot have a DEFAULT, MATERIALIZED, ALIAS, or EPHEMERAL definition. Compute values in the materialized view.`);
196
+ }
197
+ }
198
+ if (/^Kafka\s*(?:\(\s*\))?$/i.test(def.engine.trim())) {
199
+ for (const key of ['kafka_broker_list', 'kafka_topic_list', 'kafka_group_name', 'kafka_format']) {
200
+ if (typeof def.settings?.[key] !== 'string' || !String(def.settings[key]).trim()) {
201
+ pushValidationIssue(issues, def, 'kafka_missing_setting', `Kafka table ${def.database}.${def.name} requires a nonempty ${key} string (or engine arguments / a named collection).`);
202
+ }
203
+ }
204
+ }
205
+ for (const [key, value] of Object.entries(def.settings ?? {})) {
206
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key) || (typeof value === 'number' && !Number.isFinite(value))) {
207
+ pushValidationIssue(issues, def, 'kafka_invalid_setting', `Kafka table ${def.database}.${def.name} has an invalid setting key or value: ${key}.`);
208
+ }
209
+ if (key === 'kafka_auto_offset_reset') {
210
+ pushValidationIssue(issues, def, 'kafka_invalid_setting', 'kafka_auto_offset_reset is not a Kafka table setting on standard ClickHouse. Configure auto_offset_reset in the server Kafka configuration.');
211
+ }
212
+ if (/password|secret|token/i.test(key) && value === '[HIDDEN]') {
213
+ pushValidationIssue(issues, def, 'kafka_invalid_setting', `Kafka setting ${key} was redacted by ClickHouse. Restore the credential or use server-side configuration before generating migrations.`);
214
+ }
215
+ }
216
+ }
42
217
  const columnSeen = new Set();
43
218
  const columnSet = new Set();
44
219
  for (const column of def.columns) {
@@ -48,7 +223,17 @@ function validateTableDefinition(def, issues) {
48
223
  }
49
224
  columnSeen.add(column.name);
50
225
  columnSet.add(column.name);
226
+ if (column.defaultKind !== undefined && !COLUMN_DEFAULT_KINDS.has(column.defaultKind)) {
227
+ pushValidationIssue(issues, def, 'column_default_kind_invalid', `Invalid defaultKind on column "${column.name}"`);
228
+ }
229
+ // Kafka already rejects every default and column kind (kafka_column_default),
230
+ // which also covers the codec of an ALIAS or EPHEMERAL column; one error per
231
+ // mistake.
232
+ if (!kafka)
233
+ validateUnstoredColumnCodec(def, column, issues);
51
234
  validateColumnCodec(def, column, issues);
235
+ if (!kafka)
236
+ validateColumnDefault(def, column, issues);
52
237
  }
53
238
  const indexSeen = new Set();
54
239
  for (const index of def.indexes ?? []) {
@@ -57,6 +242,14 @@ function validateTableDefinition(def, issues) {
57
242
  continue;
58
243
  }
59
244
  indexSeen.add(index.name);
245
+ if (index.type === 'text') {
246
+ try {
247
+ renderTextIndexType(index);
248
+ }
249
+ catch (error) {
250
+ pushValidationIssue(issues, def, 'text_index_invalid_parameters', `Text index "${index.name}": ${error instanceof Error ? error.message : String(error)}`);
251
+ }
252
+ }
60
253
  }
61
254
  const projectionSeen = new Set();
62
255
  for (const projection of def.projections ?? []) {
@@ -75,16 +268,86 @@ function validateTableDefinition(def, issues) {
75
268
  pushValidationIssue(issues, def, 'projection_empty_index', `Table ${def.database}.${def.name} projection "${projection.name}" has an empty index expression`);
76
269
  }
77
270
  }
78
- for (const column of normalizeKeyColumns(def.primaryKey)) {
271
+ for (const column of normalizeKeyColumns(def.primaryKey, columnSet)) {
79
272
  if (isPlainColumnReference(column) && !columnSet.has(column)) {
80
273
  pushValidationIssue(issues, def, 'primary_key_missing_column', `Table ${def.database}.${def.name} primaryKey references missing column "${column}"`);
81
274
  }
82
275
  }
83
- for (const column of normalizeKeyColumns(def.orderBy)) {
276
+ for (const column of normalizeKeyColumns(def.orderBy, columnSet)) {
84
277
  if (isPlainColumnReference(column) && !columnSet.has(column)) {
85
278
  pushValidationIssue(issues, def, 'order_by_missing_column', `Table ${def.database}.${def.name} orderBy references missing column "${column}"`);
86
279
  }
87
280
  }
281
+ validateUnstoredColumnReferences(def, columnSet, issues);
282
+ }
283
+ function validateUnstoredColumnCodec(def, column, issues) {
284
+ const kind = column.defaultKind;
285
+ // A bare `EPHEMERAL CODEC(...)` parses the codec as the default expression.
286
+ // After an EPHEMERAL default or comment ClickHouse accepts the codec.
287
+ const bareEphemeral = kind === 'EPHEMERAL' && column.default === undefined && !column.comment;
288
+ if (column.codec && (kind === 'ALIAS' || bareEphemeral)) {
289
+ pushValidationIssue(issues, def, 'column_kind_codec_unsupported', `Column "${column.name}" is ${kind} and cannot have a codec; ClickHouse stores no data for it.`);
290
+ }
291
+ }
292
+ /**
293
+ * Flags ALIAS and EPHEMERAL columns named directly where ClickHouse needs a
294
+ * stored column. References inside larger expressions (`toStartOfDay(day)`,
295
+ * TTL) are left for ClickHouse to report at migrate time.
296
+ */
297
+ function validateUnstoredColumnReferences(def, columnSet, issues) {
298
+ const unstored = new Map(def.columns.flatMap((column) => column.defaultKind === 'ALIAS' || column.defaultKind === 'EPHEMERAL' ? [[column.name, column.defaultKind]] : []));
299
+ if (unstored.size === 0)
300
+ return;
301
+ const check = (field, parts, kinds = ['ALIAS', 'EPHEMERAL']) => {
302
+ for (const part of parts) {
303
+ const name = unquoteColumnName(part);
304
+ const kind = unstored.get(name);
305
+ if (kind && kinds.includes(kind)) {
306
+ pushValidationIssue(issues, def, 'column_kind_not_stored', `Table ${def.database}.${def.name} ${field} references ${kind} column "${name}", which ClickHouse does not store; use a MATERIALIZED column instead.`);
307
+ }
308
+ }
309
+ };
310
+ check('orderBy', normalizeKeyColumns(def.orderBy, columnSet));
311
+ check('primaryKey', normalizeKeyColumns(def.primaryKey, columnSet));
312
+ // Fragments are read without their comments, as canonicalization stores them
313
+ // (#232), so raw definitions (toCreateSQL) and canonical ones (planDiff)
314
+ // check the same fragment text.
315
+ check('partitionBy', splitTopLevelComma(stripWrappingParens(normalizeSQLFragment(def.partitionBy ?? ''))));
316
+ check('engine', engineArguments(def.engine));
317
+ // ClickHouse indexes an ALIAS column by its expression, but not an EPHEMERAL one.
318
+ for (const index of def.indexes ?? []) {
319
+ check(`index "${index.name}"`, [normalizeSQLFragment(index.expression)], ['EPHEMERAL']);
320
+ }
321
+ for (const projection of def.projections ?? []) {
322
+ let tokens;
323
+ try {
324
+ tokens = textSQLTokens(normalizeSQLFragment(isIndexProjection(projection) ? projection.index : projection.query));
325
+ }
326
+ catch {
327
+ continue;
328
+ }
329
+ // A token followed by `(` is a function that merely shares the column's name,
330
+ // and one after AS names an output alias.
331
+ const names = tokens.map(unquoteColumnName);
332
+ const read = new Set(names.filter((name, i) => unstored.get(name) === 'EPHEMERAL' && tokens[i + 1] !== '(' && tokens[i - 1]?.toUpperCase() !== 'AS'));
333
+ for (const name of read) {
334
+ pushValidationIssue(issues, def, 'column_ephemeral_in_projection', `Table ${def.database}.${def.name} projection "${projection.name}" reads EPHEMERAL column "${name}", which ClickHouse does not store, so the table is rejected or every INSERT fails. Use a MATERIALIZED column instead.`);
335
+ }
336
+ }
337
+ }
338
+ /** Bare engine arguments, with one tuple level flattened: `SummingMergeTree((a, b))`. */
339
+ function engineArguments(engine) {
340
+ // Only MergeTree-family parameters name columns; e.g. Distributed takes a cluster name.
341
+ const args = /^\s*\w*MergeTree\s*\(([\s\S]*)\)\s*$/.exec(engine)?.[1] ?? '';
342
+ return splitTopLevelComma(args)
343
+ .flatMap((arg) => splitTopLevelComma(stripWrappingParens(arg)))
344
+ .filter((arg) => !arg.startsWith("'"));
345
+ }
346
+ /** A column name with its backtick or double-quote identifier quotes removed. */
347
+ function unquoteColumnName(token) {
348
+ const trimmed = token.trim();
349
+ const quote = trimmed.charAt(0);
350
+ return trimmed.length > 1 && (quote === '`' || quote === '"') && trimmed.endsWith(quote) ? trimmed.slice(1, -1) : trimmed;
88
351
  }
89
352
  const INTERVAL_PATTERN = /^\s*\d+\s+(SECOND|MINUTE|HOUR|DAY|WEEK|MONTH|YEAR)(\s+\d+\s+(SECOND|MINUTE|HOUR|DAY|WEEK|MONTH|YEAR))*\s*$/i;
90
353
  const REPLICATED_ENGINE_PATTERN = /^(Shared|Replicated)/;
@@ -123,6 +386,86 @@ function validateMaterializedViewDefinition(def, issues, definitions) {
123
386
  }
124
387
  }
125
388
  }
389
+ function validateDictionaryDefinition(def, issues) {
390
+ const attributeSeen = new Set();
391
+ const attributeSet = new Set();
392
+ for (const attribute of def.attributes) {
393
+ if (attributeSeen.has(attribute.name)) {
394
+ pushValidationIssue(issues, def, 'duplicate_column_name', `Dictionary ${def.database}.${def.name} has duplicate attribute name "${attribute.name}"`);
395
+ continue;
396
+ }
397
+ attributeSeen.add(attribute.name);
398
+ attributeSet.add(attribute.name);
399
+ if (attribute.default !== undefined && attribute.expression !== undefined) {
400
+ pushValidationIssue(issues, def, 'dictionary_attribute_default_expression_exclusive', `Dictionary ${def.database}.${def.name} attribute "${attribute.name}" sets both "default" and "expression"; choose one`);
401
+ }
402
+ if (attribute.bidirectional && !attribute.hierarchical) {
403
+ pushValidationIssue(issues, def, 'dictionary_bidirectional_requires_hierarchical', `Dictionary ${def.database}.${def.name} attribute "${attribute.name}" sets "bidirectional" without "hierarchical"; bidirectional only applies to hierarchical attributes`);
404
+ }
405
+ }
406
+ if (def.primaryKey.length === 0) {
407
+ pushValidationIssue(issues, def, 'dictionary_missing_primary_key', `Dictionary ${def.database}.${def.name} requires a non-empty primaryKey`);
408
+ }
409
+ else {
410
+ for (const column of def.primaryKey) {
411
+ if (!attributeSet.has(column)) {
412
+ pushValidationIssue(issues, def, 'dictionary_primary_key_missing_attribute', `Dictionary ${def.database}.${def.name} primaryKey references missing attribute "${column}"`);
413
+ }
414
+ }
415
+ }
416
+ if (def.source.trim().length === 0) {
417
+ pushValidationIssue(issues, def, 'dictionary_missing_source', `Dictionary ${def.database}.${def.name} requires a non-empty "source"`);
418
+ }
419
+ if (def.layout.trim().length === 0) {
420
+ pushValidationIssue(issues, def, 'dictionary_missing_layout', `Dictionary ${def.database}.${def.name} requires a non-empty "layout"`);
421
+ }
422
+ if (def.lifetime.trim().length === 0) {
423
+ pushValidationIssue(issues, def, 'dictionary_missing_lifetime', `Dictionary ${def.database}.${def.name} requires a non-empty "lifetime"`);
424
+ }
425
+ if (def.range) {
426
+ for (const column of [def.range.min, def.range.max]) {
427
+ if (!attributeSet.has(column)) {
428
+ pushValidationIssue(issues, def, 'dictionary_range_missing_attribute', `Dictionary ${def.database}.${def.name} range references missing attribute "${column}"`);
429
+ }
430
+ }
431
+ }
432
+ }
433
+ function collectIdentifiers(def) {
434
+ const identifiers = [
435
+ { label: 'database name', name: def.database },
436
+ { label: 'name', name: def.name },
437
+ ];
438
+ if (def.kind === 'table') {
439
+ for (const column of def.columns)
440
+ identifiers.push({ label: 'column name', name: column.name });
441
+ for (const index of def.indexes ?? [])
442
+ identifiers.push({ label: 'index name', name: index.name });
443
+ for (const projection of def.projections ?? []) {
444
+ identifiers.push({ label: 'projection name', name: projection.name });
445
+ }
446
+ }
447
+ else if (def.kind === 'dictionary') {
448
+ for (const attribute of def.attributes) {
449
+ identifiers.push({ label: 'attribute name', name: attribute.name });
450
+ }
451
+ }
452
+ else if (def.kind === 'materialized_view') {
453
+ identifiers.push({ label: 'target database name', name: def.to.database });
454
+ identifiers.push({ label: 'target name', name: def.to.name });
455
+ }
456
+ return identifiers;
457
+ }
458
+ // Names are quoted and escaped when rendered, so any character is safe except
459
+ // those ClickHouse cannot store in a name. Catching them here gives a clear
460
+ // error instead of a server-side failure (e.g. names from a CDC source catalog).
461
+ function validateIdentifiers(def, issues) {
462
+ for (const { label, name } of collectIdentifiers(def)) {
463
+ const problem = describeInvalidIdentifier(name);
464
+ if (problem) {
465
+ pushValidationIssue(issues, def, 'invalid_identifier', `${def.kind} ${JSON.stringify(def.database)}.${JSON.stringify(def.name)} ${label} ${JSON.stringify(name)} ${problem}`);
466
+ }
467
+ }
468
+ }
126
469
  export function validateDefinitions(definitions) {
127
470
  const issues = [];
128
471
  const objectKeys = new Set();
@@ -133,12 +476,16 @@ export function validateDefinitions(definitions) {
133
476
  continue;
134
477
  }
135
478
  objectKeys.add(key);
479
+ validateIdentifiers(def, issues);
136
480
  if (def.kind === 'table') {
137
481
  validateTableDefinition(def, issues);
138
482
  }
139
483
  else if (def.kind === 'materialized_view') {
140
484
  validateMaterializedViewDefinition(def, issues, definitions);
141
485
  }
486
+ else if (def.kind === 'dictionary') {
487
+ validateDictionaryDefinition(def, issues);
488
+ }
142
489
  }
143
490
  return issues;
144
491
  }