@hypequery/datasets 0.13.5 → 0.14.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 +10 -0
- package/dist/agent-catalog.d.ts +97 -0
- package/dist/agent-catalog.d.ts.map +1 -0
- package/dist/agent-catalog.js +79 -0
- package/dist/api.type-test.js +19 -1
- package/dist/catalog.d.ts +17 -6
- package/dist/catalog.d.ts.map +1 -1
- package/dist/catalog.js +18 -1
- package/dist/contract.d.ts +11 -7
- package/dist/contract.d.ts.map +1 -1
- package/dist/contract.js +22 -1
- package/dist/dataset-query.d.ts.map +1 -1
- package/dist/dataset-query.js +9 -3
- package/dist/dataset.d.ts.map +1 -1
- package/dist/dataset.js +19 -2
- package/dist/executor.d.ts +6 -0
- package/dist/executor.d.ts.map +1 -1
- package/dist/executor.js +83 -24
- package/dist/field.d.ts.map +1 -1
- package/dist/field.js +2 -0
- package/dist/in-memory-backend.d.ts.map +1 -1
- package/dist/in-memory-backend.js +2 -17
- package/dist/index.d.ts +10 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -0
- package/dist/measure.d.ts.map +1 -1
- package/dist/measure.js +4 -0
- package/dist/protocol-adapter.d.ts.map +1 -1
- package/dist/protocol-adapter.js +21 -0
- package/dist/protocol-rehydrate.d.ts +46 -0
- package/dist/protocol-rehydrate.d.ts.map +1 -0
- package/dist/protocol-rehydrate.js +218 -0
- package/dist/publishing/features/dataset-publication.d.ts +10 -0
- package/dist/publishing/features/dataset-publication.d.ts.map +1 -0
- package/dist/publishing/features/dataset-publication.js +34 -0
- package/dist/publishing/node.d.ts +16 -0
- package/dist/publishing/node.d.ts.map +1 -0
- package/dist/publishing/node.js +35 -0
- package/dist/publishing/publisher.d.ts +17 -0
- package/dist/publishing/publisher.d.ts.map +1 -0
- package/dist/publishing/publisher.js +32 -0
- package/dist/publishing/types.d.ts +31 -0
- package/dist/publishing/types.d.ts.map +1 -0
- package/dist/publishing/types.js +1 -0
- package/dist/query-builder-protocol.d.ts +6 -2
- package/dist/query-builder-protocol.d.ts.map +1 -1
- package/dist/semantic-plan.d.ts +3 -1
- package/dist/semantic-plan.d.ts.map +1 -1
- package/dist/semantic-planner.d.ts.map +1 -1
- package/dist/semantic-planner.js +4 -0
- package/dist/semantic-query-schema.d.ts +48 -0
- package/dist/semantic-query-schema.d.ts.map +1 -0
- package/dist/semantic-query-schema.js +218 -0
- package/dist/sql-utils.d.ts +36 -0
- package/dist/sql-utils.d.ts.map +1 -1
- package/dist/sql-utils.js +76 -0
- package/dist/tools.d.ts +5 -0
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +25 -267
- package/dist/types.d.ts +102 -12
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/agent-catalog-projection.d.ts +78 -0
- package/dist/utils/agent-catalog-projection.d.ts.map +1 -0
- package/dist/utils/agent-catalog-projection.js +179 -0
- package/dist/utils/dataset-cache-policy.d.ts +34 -0
- package/dist/utils/dataset-cache-policy.d.ts.map +1 -0
- package/dist/utils/dataset-cache-policy.js +50 -0
- package/dist/utils/dataset-contract.d.ts +2 -2
- package/dist/utils/dataset-contract.d.ts.map +1 -1
- package/dist/utils/dataset-contract.js +3 -1
- package/dist/utils/dataset-definition-validation.d.ts +35 -0
- package/dist/utils/dataset-definition-validation.d.ts.map +1 -0
- package/dist/utils/dataset-definition-validation.js +215 -0
- package/dist/utils/dataset-metric-ref.d.ts +2 -2
- package/dist/utils/dataset-metric-ref.d.ts.map +1 -1
- package/dist/utils/dataset-metric-ref.js +5 -3
- package/dist/utils/metric-alias.d.ts +4 -0
- package/dist/utils/metric-alias.d.ts.map +1 -0
- package/dist/utils/metric-alias.js +28 -0
- package/dist/utils/protocol-metric-capabilities.d.ts +7 -0
- package/dist/utils/protocol-metric-capabilities.d.ts.map +1 -0
- package/dist/utils/protocol-metric-capabilities.js +57 -0
- package/dist/utils/protocol-rehydrate-filters.d.ts +10 -0
- package/dist/utils/protocol-rehydrate-filters.d.ts.map +1 -0
- package/dist/utils/protocol-rehydrate-filters.js +36 -0
- package/dist/utils/protocol-semantic-metadata.d.ts +5 -0
- package/dist/utils/protocol-semantic-metadata.d.ts.map +1 -0
- package/dist/utils/protocol-semantic-metadata.js +5 -0
- package/dist/utils/published-name.d.ts +3 -0
- package/dist/utils/published-name.d.ts.map +1 -0
- package/dist/utils/published-name.js +7 -0
- package/dist/utils/published-relationships.d.ts +34 -0
- package/dist/utils/published-relationships.d.ts.map +1 -0
- package/dist/utils/published-relationships.js +35 -0
- package/dist/utils/relationship-fields.d.ts +7 -0
- package/dist/utils/relationship-fields.d.ts.map +1 -1
- package/dist/utils/relationship-fields.js +13 -1
- package/dist/utils/result-limits.d.ts +34 -0
- package/dist/utils/result-limits.d.ts.map +1 -0
- package/dist/utils/result-limits.js +40 -0
- package/dist/utils/semantic-metadata-validation.d.ts +4 -0
- package/dist/utils/semantic-metadata-validation.d.ts.map +1 -0
- package/dist/utils/semantic-metadata-validation.js +103 -0
- package/dist/utils/semantic-metadata.d.ts +4 -0
- package/dist/utils/semantic-metadata.d.ts.map +1 -0
- package/dist/utils/semantic-metadata.js +13 -0
- package/dist/utils/semantic-result-serialization.d.ts +9 -0
- package/dist/utils/semantic-result-serialization.d.ts.map +1 -0
- package/dist/utils/semantic-result-serialization.js +26 -0
- package/dist/utils/semantic-tool.d.ts +5 -0
- package/dist/utils/semantic-tool.d.ts.map +1 -0
- package/dist/utils/semantic-tool.js +29 -0
- package/package.json +5 -4
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import { sha256 } from '@noble/hashes/sha2';
|
|
2
|
+
import { bytesToHex } from '@noble/hashes/utils';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
import { zodToJsonSchema } from 'zod-to-json-schema';
|
|
5
|
+
import { getDatasetCatalog, getGroupableRelationshipFields, getQueryableRelationshipFields, } from './catalog.js';
|
|
6
|
+
import { SEMANTIC_FILTER_OPERATORS } from './constants.js';
|
|
7
|
+
import { compareStrings, stableStringify, uniqueSorted } from './utils/canonical-json.js';
|
|
8
|
+
export const DEFAULT_SEMANTIC_QUERY_SCHEMA_LIMITS = Object.freeze({
|
|
9
|
+
maxResultSize: 10_000,
|
|
10
|
+
maxOffset: 10_000,
|
|
11
|
+
maxDimensions: 50,
|
|
12
|
+
maxMeasures: 50,
|
|
13
|
+
maxFilters: 100,
|
|
14
|
+
maxOrderBy: 50,
|
|
15
|
+
});
|
|
16
|
+
function isDatasetCatalog(source) {
|
|
17
|
+
return 'requiresTenant' in source && 'supportedGrains' in source && 'orderableFields' in source;
|
|
18
|
+
}
|
|
19
|
+
function resolveCatalog(source) {
|
|
20
|
+
return isDatasetCatalog(source)
|
|
21
|
+
? source
|
|
22
|
+
: getDatasetCatalog(source);
|
|
23
|
+
}
|
|
24
|
+
function lowerLimit(...values) {
|
|
25
|
+
const finite = values.filter((value) => value !== undefined);
|
|
26
|
+
return finite.length > 0 ? Math.min(...finite) : undefined;
|
|
27
|
+
}
|
|
28
|
+
function fieldEnum(values) {
|
|
29
|
+
const unique = uniqueSorted(values);
|
|
30
|
+
return unique.length > 0
|
|
31
|
+
? z.enum(unique)
|
|
32
|
+
: z.never();
|
|
33
|
+
}
|
|
34
|
+
function boundedArray(item, maximum) {
|
|
35
|
+
const array = z.array(item);
|
|
36
|
+
return (maximum === undefined ? array : array.max(maximum)).optional();
|
|
37
|
+
}
|
|
38
|
+
function filterSchema(catalog, fields) {
|
|
39
|
+
const filterValue = z.unknown().refine(value => value !== undefined, 'Required');
|
|
40
|
+
const relationshipFields = new Set(getQueryableRelationshipFields(catalog));
|
|
41
|
+
const variants = uniqueSorted(fields).map((field) => z.object({
|
|
42
|
+
field: z.literal(field),
|
|
43
|
+
operator: relationshipFields.has(field)
|
|
44
|
+
? z.enum(SEMANTIC_FILTER_OPERATORS)
|
|
45
|
+
: fieldEnum(catalog.filters[field]?.operators ?? [...SEMANTIC_FILTER_OPERATORS]),
|
|
46
|
+
value: filterValue,
|
|
47
|
+
}).strict());
|
|
48
|
+
if (variants.length === 0) {
|
|
49
|
+
return z.object({
|
|
50
|
+
field: z.never(),
|
|
51
|
+
operator: z.enum(SEMANTIC_FILTER_OPERATORS),
|
|
52
|
+
value: filterValue,
|
|
53
|
+
}).strict();
|
|
54
|
+
}
|
|
55
|
+
if (variants.length === 1)
|
|
56
|
+
return variants[0];
|
|
57
|
+
return z.union(variants);
|
|
58
|
+
}
|
|
59
|
+
function queryShape(catalog, metricName, options, metric, localRelationshipFields = []) {
|
|
60
|
+
const limits = { ...DEFAULT_SEMANTIC_QUERY_SCHEMA_LIMITS, ...options };
|
|
61
|
+
const relationshipFields = getQueryableRelationshipFields(catalog);
|
|
62
|
+
// `groupable: false` declares a dimension that exists to back a measure, not
|
|
63
|
+
// to be selected. The agent-safe catalog already hides those, so the
|
|
64
|
+
// generated schema must refuse them too — otherwise a dataset advertises one
|
|
65
|
+
// set of dimensions and accepts another. This holds across a relationship
|
|
66
|
+
// hop: a non-groupable target dimension stays filterable but is not a
|
|
67
|
+
// grouping key, so the two lists are tracked separately.
|
|
68
|
+
const groupableRelationshipFields = new Set(getGroupableRelationshipFields(catalog));
|
|
69
|
+
const isGroupable = (name) => (catalog.dimensions[name] !== undefined
|
|
70
|
+
? catalog.dimensions[name].groupable !== false
|
|
71
|
+
: groupableRelationshipFields.has(name));
|
|
72
|
+
const dimensions = metric
|
|
73
|
+
? uniqueSorted([
|
|
74
|
+
...metric.dimensions.filter(isGroupable),
|
|
75
|
+
...localRelationshipFields.filter(isGroupable),
|
|
76
|
+
])
|
|
77
|
+
: uniqueSorted([
|
|
78
|
+
...Object.keys(catalog.dimensions).filter(isGroupable),
|
|
79
|
+
...relationshipFields.filter(isGroupable),
|
|
80
|
+
]);
|
|
81
|
+
const declaredFilters = Object.keys(catalog.filters);
|
|
82
|
+
const filterFields = metric
|
|
83
|
+
? uniqueSorted([...metric.filters, ...localRelationshipFields])
|
|
84
|
+
: metricName
|
|
85
|
+
? uniqueSorted([
|
|
86
|
+
...(declaredFilters.length > 0 ? declaredFilters : Object.keys(catalog.dimensions)),
|
|
87
|
+
...relationshipFields,
|
|
88
|
+
])
|
|
89
|
+
: uniqueSorted([...declaredFilters, ...relationshipFields]);
|
|
90
|
+
const grains = metric
|
|
91
|
+
? metric.grain ? [metric.grain] : metric.grains
|
|
92
|
+
: catalog.supportedGrains;
|
|
93
|
+
const orderable = metricName
|
|
94
|
+
? uniqueSorted([...dimensions, metricName, ...(grains.length > 0 ? ['period'] : [])])
|
|
95
|
+
: uniqueSorted([
|
|
96
|
+
...dimensions,
|
|
97
|
+
...Object.keys(catalog.measures),
|
|
98
|
+
...(catalog.supportedGrains.length > 0 ? ['period'] : []),
|
|
99
|
+
]);
|
|
100
|
+
const maxResultSize = options.enforceResultLimit === false
|
|
101
|
+
? undefined
|
|
102
|
+
: lowerLimit(catalog.limits?.maxResultSize, limits.maxResultSize);
|
|
103
|
+
const defaultResultSize = options.defaultResultSize === undefined
|
|
104
|
+
? undefined
|
|
105
|
+
: lowerLimit(options.defaultResultSize, maxResultSize);
|
|
106
|
+
const grainField = options.grainField ?? 'by';
|
|
107
|
+
const limitSchema = maxResultSize === undefined
|
|
108
|
+
? z.number().int().positive()
|
|
109
|
+
: z.number().int().positive().max(maxResultSize);
|
|
110
|
+
return {
|
|
111
|
+
dimensions: boundedArray(fieldEnum(dimensions), lowerLimit(catalog.limits?.maxDimensions, limits.maxDimensions)),
|
|
112
|
+
...(metricName ? {} : {
|
|
113
|
+
measures: boundedArray(fieldEnum(Object.keys(catalog.measures)), lowerLimit(catalog.limits?.maxMeasures, limits.maxMeasures)),
|
|
114
|
+
}),
|
|
115
|
+
filters: boundedArray(filterSchema(catalog, filterFields), lowerLimit(catalog.limits?.maxFilters, limits.maxFilters)),
|
|
116
|
+
orderBy: boundedArray(z.object({
|
|
117
|
+
field: fieldEnum(orderable),
|
|
118
|
+
direction: z.enum(['asc', 'desc']),
|
|
119
|
+
}).strict(), limits.maxOrderBy),
|
|
120
|
+
limit: defaultResultSize === undefined
|
|
121
|
+
? limitSchema.optional()
|
|
122
|
+
: limitSchema.default(defaultResultSize),
|
|
123
|
+
offset: (limits.maxOffset === undefined
|
|
124
|
+
? z.number().int().nonnegative()
|
|
125
|
+
: z.number().int().nonnegative().max(limits.maxOffset)).optional(),
|
|
126
|
+
[grainField]: fieldEnum(grains).optional(),
|
|
127
|
+
...(options.includeMeta ? { includeMeta: z.boolean().optional() } : {}),
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
export function buildDatasetInputSchema(dataset, options = {}) {
|
|
131
|
+
const schema = z.object(queryShape(resolveCatalog(dataset), undefined, options)).strict();
|
|
132
|
+
if (options.requireSelection === false)
|
|
133
|
+
return schema;
|
|
134
|
+
return schema.refine(input => (input.dimensions?.length ?? 0) > 0 || (input.measures?.length ?? 0) > 0, { message: 'At least one dimension or measure must be specified' });
|
|
135
|
+
}
|
|
136
|
+
export function buildMetricInputSchema(dataset, metricName, options = {}, metricContract) {
|
|
137
|
+
const catalog = resolveCatalog(dataset);
|
|
138
|
+
const metric = metricContract ?? catalog.metrics[metricName];
|
|
139
|
+
const localRelationshipFields = isDatasetCatalog(dataset)
|
|
140
|
+
? []
|
|
141
|
+
: getQueryableRelationshipFields(catalog);
|
|
142
|
+
return z.object(queryShape(catalog, metricName, options, metric, localRelationshipFields)).strict();
|
|
143
|
+
}
|
|
144
|
+
function unionSchemas(schemas, emptyShape) {
|
|
145
|
+
if (schemas.length === 0)
|
|
146
|
+
return z.object(emptyShape).strict();
|
|
147
|
+
if (schemas.length === 1)
|
|
148
|
+
return schemas[0];
|
|
149
|
+
return z.union(schemas);
|
|
150
|
+
}
|
|
151
|
+
function withSelectors(schema, selectors, requireSelection) {
|
|
152
|
+
const shape = (schema instanceof z.ZodEffects ? schema.innerType() : schema).shape;
|
|
153
|
+
const selected = z.object({ ...selectors, ...shape }).strict();
|
|
154
|
+
if (!requireSelection)
|
|
155
|
+
return selected;
|
|
156
|
+
return selected.refine(input => (input.dimensions?.length ?? 0) > 0 || (input.measures?.length ?? 0) > 0, { message: 'At least one dimension or measure must be specified' });
|
|
157
|
+
}
|
|
158
|
+
export function toSemanticJsonSchema(schema, options = {}) {
|
|
159
|
+
const converted = zodToJsonSchema(schema, { target: 'jsonSchema7', $refStrategy: 'none' });
|
|
160
|
+
const { $schema: _schema, ...jsonSchema } = converted;
|
|
161
|
+
const result = jsonSchema;
|
|
162
|
+
const objectSchema = result.anyOf && !result.type ? { ...result, type: 'object' } : result;
|
|
163
|
+
return options.requireSelection ? addSelectionRequirement(objectSchema) : objectSchema;
|
|
164
|
+
}
|
|
165
|
+
function addSelectionRequirement(schema) {
|
|
166
|
+
return {
|
|
167
|
+
...schema,
|
|
168
|
+
allOf: [
|
|
169
|
+
...(schema.allOf ?? []),
|
|
170
|
+
{
|
|
171
|
+
anyOf: [
|
|
172
|
+
{
|
|
173
|
+
type: 'object',
|
|
174
|
+
properties: { dimensions: { type: 'array', minItems: 1 } },
|
|
175
|
+
required: ['dimensions'],
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
type: 'object',
|
|
179
|
+
properties: { measures: { type: 'array', minItems: 1 } },
|
|
180
|
+
required: ['measures'],
|
|
181
|
+
},
|
|
182
|
+
],
|
|
183
|
+
},
|
|
184
|
+
],
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
export function buildCanonicalSemanticQuerySchemas(datasets, options = {}) {
|
|
188
|
+
const datasetSchemas = [];
|
|
189
|
+
const metricSchemas = [];
|
|
190
|
+
for (const [datasetName, dataset] of Object.entries(datasets)
|
|
191
|
+
.sort(([left], [right]) => compareStrings(left, right))) {
|
|
192
|
+
datasetSchemas.push(withSelectors(buildDatasetInputSchema(dataset, { ...options, requireSelection: false }), { dataset: z.literal(datasetName) }, options.requireSelection !== false));
|
|
193
|
+
const catalog = resolveCatalog(dataset);
|
|
194
|
+
for (const metricName of Object.keys(catalog.metrics).sort(compareStrings)) {
|
|
195
|
+
metricSchemas.push(withSelectors(buildMetricInputSchema(dataset, metricName, options, catalog.metrics[metricName]), { dataset: z.literal(datasetName), metric: z.literal(metricName) }, false));
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
const queryDataset = unionSchemas(datasetSchemas, { dataset: z.never() });
|
|
199
|
+
const queryMetric = unionSchemas(metricSchemas, {
|
|
200
|
+
dataset: z.never(),
|
|
201
|
+
metric: z.never(),
|
|
202
|
+
});
|
|
203
|
+
const queryDatasetJsonSchema = toSemanticJsonSchema(queryDataset, {
|
|
204
|
+
requireSelection: options.requireSelection !== false,
|
|
205
|
+
});
|
|
206
|
+
const queryMetricJsonSchema = toSemanticJsonSchema(queryMetric);
|
|
207
|
+
const manifestHash = bytesToHex(sha256(new TextEncoder().encode(stableStringify({
|
|
208
|
+
query_dataset: queryDatasetJsonSchema,
|
|
209
|
+
query_metric: queryMetricJsonSchema,
|
|
210
|
+
}))));
|
|
211
|
+
return Object.freeze({
|
|
212
|
+
queryDataset,
|
|
213
|
+
queryMetric,
|
|
214
|
+
queryDatasetJsonSchema,
|
|
215
|
+
queryMetricJsonSchema,
|
|
216
|
+
manifestHash,
|
|
217
|
+
});
|
|
218
|
+
}
|
package/dist/sql-utils.d.ts
CHANGED
|
@@ -25,4 +25,40 @@ export declare function validateSQLIdentifier(identifier: string, context: strin
|
|
|
25
25
|
* @returns Quoted identifier
|
|
26
26
|
*/
|
|
27
27
|
export declare function quoteSQLIdentifier(identifier: string): string;
|
|
28
|
+
export interface StripSqlLiteralsOptions {
|
|
29
|
+
/**
|
|
30
|
+
* Keep the text inside quoted identifiers (`` `col` ``, `"col"`), blanking only
|
|
31
|
+
* the delimiters. String literals are blanked either way.
|
|
32
|
+
*
|
|
33
|
+
* The two callers need different views of the same expression. Finding a
|
|
34
|
+
* statement terminator means finding one that is *syntax*, and a `;` inside
|
|
35
|
+
* `` `odd;name` `` is not — so that check blanks quoted identifiers too.
|
|
36
|
+
* Deciding whether an expression references a column is the opposite: a quoted
|
|
37
|
+
* identifier is exactly such a reference, so that check needs its text.
|
|
38
|
+
*/
|
|
39
|
+
readonly keepQuotedIdentifiers?: boolean;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Blanks out quoted spans in a SQL expression, preserving length and structure.
|
|
43
|
+
*
|
|
44
|
+
* Lexical checks that look for statement terminators or comment openers have to
|
|
45
|
+
* run on code rather than on data: `concat(name, '; ')` and
|
|
46
|
+
* `replaceAll(note, '--', '')` are ordinary expressions whose literals happen to
|
|
47
|
+
* contain those characters. Scanning the raw source text rejects them.
|
|
48
|
+
*
|
|
49
|
+
* Both ClickHouse escape forms are handled: a doubled quote (`'it''s'`) and a
|
|
50
|
+
* backslash escape (`'it\'s'`).
|
|
51
|
+
*
|
|
52
|
+
* @throws Error when a quote is never closed — an unterminated literal is
|
|
53
|
+
* malformed on its own, and treating the remainder as data would let an open
|
|
54
|
+
* quote hide anything after it.
|
|
55
|
+
*/
|
|
56
|
+
export declare function stripSqlLiterals(sql: string, options?: StripSqlLiteralsOptions): string;
|
|
57
|
+
/**
|
|
58
|
+
* Escapes a string for literal use inside a regular expression.
|
|
59
|
+
*
|
|
60
|
+
* Without this, a value carrying regex syntax either throws at `RegExp`
|
|
61
|
+
* construction or silently matches something other than itself.
|
|
62
|
+
*/
|
|
63
|
+
export declare function escapeRegExp(value: string): string;
|
|
28
64
|
//# sourceMappingURL=sql-utils.d.ts.map
|
package/dist/sql-utils.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sql-utils.d.ts","sourceRoot":"","sources":["../src/sql-utils.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAE/D;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAO/E;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAI7D"}
|
|
1
|
+
{"version":3,"file":"sql-utils.d.ts","sourceRoot":"","sources":["../src/sql-utils.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAE/D;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAO/E;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAI7D;AAQD,MAAM,WAAW,uBAAuB;IACtC;;;;;;;;;OASG;IACH,QAAQ,CAAC,qBAAqB,CAAC,EAAE,OAAO,CAAC;CAC1C;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,uBAAuB,GAAG,MAAM,CAoDvF;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAElD"}
|
package/dist/sql-utils.js
CHANGED
|
@@ -36,3 +36,79 @@ export function quoteSQLIdentifier(identifier) {
|
|
|
36
36
|
const escaped = identifier.replace(/`/g, '``');
|
|
37
37
|
return `\`${escaped}\``;
|
|
38
38
|
}
|
|
39
|
+
/** Quote characters that open a literal or a quoted identifier in ClickHouse. */
|
|
40
|
+
const SQL_QUOTES = new Set(["'", '"', '`']);
|
|
41
|
+
/** Quotes that delimit an identifier rather than a string literal. */
|
|
42
|
+
const IDENTIFIER_QUOTES = new Set(['"', '`']);
|
|
43
|
+
/**
|
|
44
|
+
* Blanks out quoted spans in a SQL expression, preserving length and structure.
|
|
45
|
+
*
|
|
46
|
+
* Lexical checks that look for statement terminators or comment openers have to
|
|
47
|
+
* run on code rather than on data: `concat(name, '; ')` and
|
|
48
|
+
* `replaceAll(note, '--', '')` are ordinary expressions whose literals happen to
|
|
49
|
+
* contain those characters. Scanning the raw source text rejects them.
|
|
50
|
+
*
|
|
51
|
+
* Both ClickHouse escape forms are handled: a doubled quote (`'it''s'`) and a
|
|
52
|
+
* backslash escape (`'it\'s'`).
|
|
53
|
+
*
|
|
54
|
+
* @throws Error when a quote is never closed — an unterminated literal is
|
|
55
|
+
* malformed on its own, and treating the remainder as data would let an open
|
|
56
|
+
* quote hide anything after it.
|
|
57
|
+
*/
|
|
58
|
+
export function stripSqlLiterals(sql, options) {
|
|
59
|
+
let output = '';
|
|
60
|
+
let index = 0;
|
|
61
|
+
while (index < sql.length) {
|
|
62
|
+
const char = sql[index];
|
|
63
|
+
if (!SQL_QUOTES.has(char)) {
|
|
64
|
+
output += char;
|
|
65
|
+
index += 1;
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
const quote = char;
|
|
69
|
+
let cursor = index + 1;
|
|
70
|
+
let closed = false;
|
|
71
|
+
while (cursor < sql.length) {
|
|
72
|
+
const current = sql[cursor];
|
|
73
|
+
if (current === '\\') {
|
|
74
|
+
// Backslash escapes the next character, whatever it is.
|
|
75
|
+
cursor += 2;
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
if (current === quote) {
|
|
79
|
+
if (sql[cursor + 1] === quote) {
|
|
80
|
+
// A doubled quote is an escaped quote, not the end of the span.
|
|
81
|
+
cursor += 2;
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
closed = true;
|
|
85
|
+
cursor += 1;
|
|
86
|
+
break;
|
|
87
|
+
}
|
|
88
|
+
cursor += 1;
|
|
89
|
+
}
|
|
90
|
+
if (!closed) {
|
|
91
|
+
throw new Error(`Unterminated ${quote} literal in SQL expression.`);
|
|
92
|
+
}
|
|
93
|
+
if (options?.keepQuotedIdentifiers && IDENTIFIER_QUOTES.has(quote)) {
|
|
94
|
+
// Blank only the delimiters, so the identifier stays readable as the
|
|
95
|
+
// column reference it is. Length is preserved either way.
|
|
96
|
+
output += ` ${sql.slice(index + 1, cursor - 1)} `;
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
// Replace the whole span, quotes included, with spaces so offsets survive.
|
|
100
|
+
output += ' '.repeat(cursor - index);
|
|
101
|
+
}
|
|
102
|
+
index = cursor;
|
|
103
|
+
}
|
|
104
|
+
return output;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Escapes a string for literal use inside a regular expression.
|
|
108
|
+
*
|
|
109
|
+
* Without this, a value carrying regex syntax either throws at `RegExp`
|
|
110
|
+
* construction or silently matches something other than itself.
|
|
111
|
+
*/
|
|
112
|
+
export function escapeRegExp(value) {
|
|
113
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
114
|
+
}
|
package/dist/tools.d.ts
CHANGED
|
@@ -10,6 +10,11 @@ export type JsonSchema = {
|
|
|
10
10
|
minimum?: number;
|
|
11
11
|
maximum?: number;
|
|
12
12
|
additionalProperties?: boolean;
|
|
13
|
+
minItems?: number;
|
|
14
|
+
maxItems?: number;
|
|
15
|
+
anyOf?: JsonSchema[];
|
|
16
|
+
allOf?: JsonSchema[];
|
|
17
|
+
const?: string;
|
|
13
18
|
};
|
|
14
19
|
export interface SemanticToolDefinition<TInput = Record<string, unknown>, TResult = unknown> {
|
|
15
20
|
name: string;
|
package/dist/tools.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,kBAAkB,EAClB,YAAY,EACZ,gBAAgB,EAChB,YAAY,EACZ,WAAW,
|
|
1
|
+
{"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"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"}
|