@hypequery/datasets 0.16.0 → 0.17.1
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/dist/agent-catalog.d.ts.map +1 -1
- package/dist/api.type-test.js +45 -1
- package/dist/catalog.d.ts +6 -0
- package/dist/catalog.d.ts.map +1 -1
- package/dist/catalog.js +12 -0
- package/dist/dataset-query.d.ts +2 -0
- package/dist/dataset-query.d.ts.map +1 -1
- package/dist/dataset-query.js +18 -21
- package/dist/dataset.d.ts +2 -2
- package/dist/dataset.d.ts.map +1 -1
- package/dist/dataset.js +4 -2
- package/dist/executor.d.ts.map +1 -1
- package/dist/executor.js +10 -4
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/measure.d.ts +4 -1
- package/dist/measure.d.ts.map +1 -1
- package/dist/measure.js +12 -0
- package/dist/portable-executor.d.ts +4 -5
- package/dist/portable-executor.d.ts.map +1 -1
- package/dist/portable-executor.js +2 -20
- package/dist/protocol-adapter.d.ts +2 -2
- package/dist/protocol-adapter.js +2 -2
- package/dist/protocol-deployment-adapter.d.ts +8 -0
- package/dist/protocol-deployment-adapter.d.ts.map +1 -0
- package/dist/protocol-deployment-adapter.js +31 -0
- package/dist/protocol-rehydrate.d.ts +4 -2
- package/dist/protocol-rehydrate.d.ts.map +1 -1
- package/dist/protocol-rehydrate.js +44 -12
- package/dist/semantic-planner.d.ts.map +1 -1
- package/dist/semantic-planner.js +3 -0
- package/dist/semantic-query-schema.d.ts.map +1 -1
- package/dist/semantic-query-schema.js +40 -16
- package/dist/types.d.ts +46 -7
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/agent-catalog-projection.d.ts +6 -2
- package/dist/utils/agent-catalog-projection.d.ts.map +1 -1
- package/dist/utils/agent-catalog-projection.js +2 -2
- package/dist/utils/dataset-definition-validation.d.ts +2 -2
- package/dist/utils/dataset-definition-validation.d.ts.map +1 -1
- package/dist/utils/dataset-definition-validation.js +13 -1
- package/dist/utils/dataset-derived-query.d.ts +12 -0
- package/dist/utils/dataset-derived-query.d.ts.map +1 -0
- package/dist/utils/dataset-derived-query.js +112 -0
- package/dist/utils/dataset-measures.d.ts +10 -0
- package/dist/utils/dataset-measures.d.ts.map +1 -0
- package/dist/utils/dataset-measures.js +24 -0
- package/dist/utils/dataset-normalization.d.ts +3 -3
- package/dist/utils/dataset-normalization.d.ts.map +1 -1
- package/dist/utils/dataset-query-result.d.ts +10 -0
- package/dist/utils/dataset-query-result.d.ts.map +1 -0
- package/dist/utils/dataset-query-result.js +20 -0
- package/dist/utils/dataset-query-validation.js +2 -2
- package/dist/utils/derived-measure-validation.d.ts +3 -0
- package/dist/utils/derived-measure-validation.d.ts.map +1 -0
- package/dist/utils/derived-measure-validation.js +138 -0
- package/dist/utils/filtered-aggregation-sql.d.ts.map +1 -1
- package/dist/utils/filtered-aggregation-sql.js +4 -1
- package/dist/utils/protocol-metric-expressions.d.ts +2 -1
- package/dist/utils/protocol-metric-expressions.d.ts.map +1 -1
- package/dist/utils/protocol-metric-expressions.js +4 -0
- package/dist/utils/relationship-fields.d.ts.map +1 -1
- package/dist/utils/relationship-fields.js +6 -2
- package/dist/utils/relationship-validation.d.ts.map +1 -1
- package/dist/utils/relationship-validation.js +12 -1
- package/dist/utils/semantic-metadata-validation.d.ts +2 -2
- package/dist/utils/semantic-metadata-validation.d.ts.map +1 -1
- package/package.json +3 -3
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { quoteSQLIdentifier } from '../sql-utils.js';
|
|
2
|
+
import { validateDatasetQueryInput } from './dataset-query-validation.js';
|
|
3
|
+
import { overfetchLimit } from './pagination.js';
|
|
4
|
+
import { toDatasetQueryResult } from './dataset-query-result.js';
|
|
5
|
+
export function hasSelectedDerivedMeasure(ds, query) {
|
|
6
|
+
return (query.measures ?? []).some(name => Object.hasOwn(ds.derivedMeasures, name));
|
|
7
|
+
}
|
|
8
|
+
function expressionSql(expression, uses) {
|
|
9
|
+
switch (expression.kind) {
|
|
10
|
+
case 'ref': {
|
|
11
|
+
if (!Object.hasOwn(uses, expression.name)) {
|
|
12
|
+
throw new Error(`Formula references undeclared input "${expression.name}".`);
|
|
13
|
+
}
|
|
14
|
+
const measureName = uses[expression.name];
|
|
15
|
+
return quoteSQLIdentifier(measureName);
|
|
16
|
+
}
|
|
17
|
+
case 'literal': {
|
|
18
|
+
if (expression.value === null)
|
|
19
|
+
return 'NULL';
|
|
20
|
+
if (typeof expression.value === 'number') {
|
|
21
|
+
if (!Number.isFinite(expression.value))
|
|
22
|
+
throw new Error('Formula contains a non-finite number.');
|
|
23
|
+
return String(expression.value);
|
|
24
|
+
}
|
|
25
|
+
if (typeof expression.value === 'boolean')
|
|
26
|
+
return expression.value ? 'true' : 'false';
|
|
27
|
+
return `'${expression.value.replace(/\\/g, '\\\\').replace(/'/g, "''")}'`;
|
|
28
|
+
}
|
|
29
|
+
case 'binary': {
|
|
30
|
+
const operators = { add: '+', subtract: '-', multiply: '*', divide: '/' };
|
|
31
|
+
const operator = operators[expression.operator];
|
|
32
|
+
if (!operator)
|
|
33
|
+
throw new Error('Formula contains an unsupported binary operator.');
|
|
34
|
+
return `(${expressionSql(expression.left, uses)} ${operator} ${expressionSql(expression.right, uses)})`;
|
|
35
|
+
}
|
|
36
|
+
case 'function': {
|
|
37
|
+
const args = expression.args.map(arg => expressionSql(arg, uses));
|
|
38
|
+
switch (expression.name) {
|
|
39
|
+
case 'nullIfZero': return `NULLIF(${args[0]}, 0)`;
|
|
40
|
+
case 'coalesce': return `COALESCE(${args.join(', ')})`;
|
|
41
|
+
case 'round': return `ROUND(${args.join(', ')})`;
|
|
42
|
+
case 'floor': return `FLOOR(${args[0]})`;
|
|
43
|
+
case 'ceil': return `CEIL(${args[0]})`;
|
|
44
|
+
default: throw new Error('Formula contains an unsupported function.');
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function derivedProjection(name, definition) {
|
|
50
|
+
const aliases = Object.fromEntries(Object.keys(definition.uses).map(alias => [alias, alias]));
|
|
51
|
+
return `${expressionSql(definition.formula(aliases).expression, definition.uses)} AS ${quoteSQLIdentifier(name)}`;
|
|
52
|
+
}
|
|
53
|
+
export function buildDerivedDatasetSql(ds, query, options, buildBaseQuery) {
|
|
54
|
+
const validation = validateDatasetQueryInput(ds, query, options.context);
|
|
55
|
+
if (!validation.valid) {
|
|
56
|
+
throw new Error(`Invalid dataset query: ${validation.errors.join('; ')}`);
|
|
57
|
+
}
|
|
58
|
+
const selected = query.measures ?? Object.keys(ds.measures);
|
|
59
|
+
const baseMeasures = new Set(selected.filter(name => Object.hasOwn(ds.measures, name)));
|
|
60
|
+
for (const name of selected) {
|
|
61
|
+
const derived = Object.hasOwn(ds.derivedMeasures, name) ? ds.derivedMeasures[name] : undefined;
|
|
62
|
+
if (derived)
|
|
63
|
+
Object.values(derived.uses).forEach(base => baseMeasures.add(base));
|
|
64
|
+
}
|
|
65
|
+
const inner = buildBaseQuery(ds, {
|
|
66
|
+
...query,
|
|
67
|
+
measures: [...baseMeasures],
|
|
68
|
+
orderBy: undefined,
|
|
69
|
+
limit: undefined,
|
|
70
|
+
offset: undefined,
|
|
71
|
+
}, { ...options, executionLimit: undefined, skipDefaultOrderBy: true });
|
|
72
|
+
const { sql: innerSql, parameters } = inner.toSQLWithParams();
|
|
73
|
+
const projections = [];
|
|
74
|
+
if (query.by)
|
|
75
|
+
projections.push(quoteSQLIdentifier('period'));
|
|
76
|
+
for (const dimension of query.dimensions ?? [])
|
|
77
|
+
projections.push(quoteSQLIdentifier(dimension));
|
|
78
|
+
for (const name of selected) {
|
|
79
|
+
const derived = Object.hasOwn(ds.derivedMeasures, name) ? ds.derivedMeasures[name] : undefined;
|
|
80
|
+
projections.push(derived
|
|
81
|
+
? derivedProjection(name, derived)
|
|
82
|
+
: quoteSQLIdentifier(name));
|
|
83
|
+
}
|
|
84
|
+
let sql = `WITH base AS (${innerSql}) SELECT ${projections.join(', ')} FROM base`;
|
|
85
|
+
if (query.orderBy?.length) {
|
|
86
|
+
sql += ` ORDER BY ${query.orderBy.map(order => (`${quoteSQLIdentifier(order.field)} ${order.direction === 'asc' ? 'ASC' : 'DESC'}`)).join(', ')}`;
|
|
87
|
+
}
|
|
88
|
+
else if (query.by) {
|
|
89
|
+
sql += ` ORDER BY ${quoteSQLIdentifier('period')} ASC`;
|
|
90
|
+
}
|
|
91
|
+
const limit = options.executionLimit ?? query.limit;
|
|
92
|
+
if (limit !== undefined)
|
|
93
|
+
sql += ` LIMIT ${limit}`;
|
|
94
|
+
if (query.offset !== undefined)
|
|
95
|
+
sql += ` OFFSET ${query.offset}`;
|
|
96
|
+
return { sql, parameters };
|
|
97
|
+
}
|
|
98
|
+
export async function runDerivedDatasetQuery(ds, query, options, buildBaseQuery) {
|
|
99
|
+
const start = Date.now();
|
|
100
|
+
const { sql, parameters } = buildDerivedDatasetSql(ds, query, {
|
|
101
|
+
...options,
|
|
102
|
+
executionLimit: overfetchLimit(query.limit),
|
|
103
|
+
}, buildBaseQuery);
|
|
104
|
+
const rows = await options.builderFactory.rawQuery(sql, parameters, { abortSignal: options.context?.abortSignal });
|
|
105
|
+
return toDatasetQueryResult(rows, {
|
|
106
|
+
dataset: ds,
|
|
107
|
+
query,
|
|
108
|
+
sql,
|
|
109
|
+
timingMs: Date.now() - start,
|
|
110
|
+
context: options.context,
|
|
111
|
+
});
|
|
112
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { BaseMeasures, DatasetMeasureDefinition, DerivedMeasureDefinition, DerivedMeasures, MeasureDefinition } from '../types.js';
|
|
2
|
+
export declare function isDerivedMeasure(definition: DatasetMeasureDefinition): definition is DerivedMeasureDefinition;
|
|
3
|
+
export declare function isBaseMeasure(definition: DatasetMeasureDefinition): definition is MeasureDefinition;
|
|
4
|
+
export declare function baseMeasureNames(measures: Record<string, DatasetMeasureDefinition>): string[];
|
|
5
|
+
export declare function splitDatasetMeasures<TMeasures extends Record<string, DatasetMeasureDefinition>>(measures: TMeasures | undefined): {
|
|
6
|
+
base: BaseMeasures<TMeasures>;
|
|
7
|
+
derived: DerivedMeasures<TMeasures>;
|
|
8
|
+
};
|
|
9
|
+
export declare function getDerivedMeasure(measures: Record<string, DatasetMeasureDefinition>, name: string): DerivedMeasureDefinition | undefined;
|
|
10
|
+
//# sourceMappingURL=dataset-measures.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dataset-measures.d.ts","sourceRoot":"","sources":["../../src/utils/dataset-measures.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,YAAY,EACZ,wBAAwB,EACxB,wBAAwB,EACxB,eAAe,EACf,iBAAiB,EAClB,MAAM,aAAa,CAAC;AAErB,wBAAgB,gBAAgB,CAC9B,UAAU,EAAE,wBAAwB,GACnC,UAAU,IAAI,wBAAwB,CAExC;AAED,wBAAgB,aAAa,CAC3B,UAAU,EAAE,wBAAwB,GACnC,UAAU,IAAI,iBAAiB,CAEjC;AAED,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,wBAAwB,CAAC,GAAG,MAAM,EAAE,CAI7F;AAED,wBAAgB,oBAAoB,CAAC,SAAS,SAAS,MAAM,CAAC,MAAM,EAAE,wBAAwB,CAAC,EAC7F,QAAQ,EAAE,SAAS,GAAG,SAAS,GAC9B;IAAE,IAAI,EAAE,YAAY,CAAC,SAAS,CAAC,CAAC;IAAC,OAAO,EAAE,eAAe,CAAC,SAAS,CAAC,CAAA;CAAE,CAMxE;AAED,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,wBAAwB,CAAC,EAClD,IAAI,EAAE,MAAM,GACX,wBAAwB,GAAG,SAAS,CAItC"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export function isDerivedMeasure(definition) {
|
|
2
|
+
return definition.__type === 'derived_measure_definition';
|
|
3
|
+
}
|
|
4
|
+
export function isBaseMeasure(definition) {
|
|
5
|
+
return definition.__type === 'measure_definition';
|
|
6
|
+
}
|
|
7
|
+
export function baseMeasureNames(measures) {
|
|
8
|
+
return Object.entries(measures)
|
|
9
|
+
.filter(([, definition]) => isBaseMeasure(definition))
|
|
10
|
+
.map(([name]) => name);
|
|
11
|
+
}
|
|
12
|
+
export function splitDatasetMeasures(measures) {
|
|
13
|
+
const entries = Object.entries(measures ?? {});
|
|
14
|
+
return {
|
|
15
|
+
base: Object.fromEntries(entries.filter(([, definition]) => isBaseMeasure(definition))),
|
|
16
|
+
derived: Object.fromEntries(entries.filter(([, definition]) => isDerivedMeasure(definition))),
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
export function getDerivedMeasure(measures, name) {
|
|
20
|
+
if (!Object.hasOwn(measures, name))
|
|
21
|
+
return undefined;
|
|
22
|
+
const definition = measures[name];
|
|
23
|
+
return isDerivedMeasure(definition) ? definition : undefined;
|
|
24
|
+
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import type { AggregationSpec, DatasetConfig, DimensionDefinition, MeasureDefinition, RelationshipDefinition, SemanticFiltersDefinition } from '../types.js';
|
|
2
|
-
type AnyMeasures = Record<string,
|
|
1
|
+
import type { AggregationSpec, DatasetConfig, DatasetMeasureDefinition, DimensionDefinition, MeasureDefinition, RelationshipDefinition, SemanticFiltersDefinition } from '../types.js';
|
|
2
|
+
type AnyMeasures = Record<string, DatasetMeasureDefinition>;
|
|
3
3
|
type AnyRelationships = Record<string, RelationshipDefinition>;
|
|
4
4
|
export declare function normalizeDimensions<TDimensions extends Record<string, DimensionDefinition>>(config: DatasetConfig<TDimensions, AnyMeasures, AnyRelationships>): TDimensions;
|
|
5
5
|
export declare function normalizeFilters(dimensions: Record<string, DimensionDefinition>, filters?: SemanticFiltersDefinition): SemanticFiltersDefinition;
|
|
6
|
-
export declare function normalizeMeasures<TMeasures extends Record<string,
|
|
6
|
+
export declare function normalizeMeasures<TMeasures extends Record<string, DatasetMeasureDefinition>>(measures: TMeasures | undefined): TMeasures;
|
|
7
7
|
export declare function normalizeRelationships<TRelationships extends Record<string, RelationshipDefinition>>(relationships: TRelationships | undefined, source: string): TRelationships;
|
|
8
8
|
export declare function measureToAggregationSpec(measureName: string, definition: MeasureDefinition): AggregationSpec;
|
|
9
9
|
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dataset-normalization.d.ts","sourceRoot":"","sources":["../../src/utils/dataset-normalization.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,eAAe,EACf,aAAa,EACb,mBAAmB,EACnB,iBAAiB,EACjB,sBAAsB,EACtB,yBAAyB,EAC1B,MAAM,aAAa,CAAC;AAErB,KAAK,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,
|
|
1
|
+
{"version":3,"file":"dataset-normalization.d.ts","sourceRoot":"","sources":["../../src/utils/dataset-normalization.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,eAAe,EACf,aAAa,EACb,wBAAwB,EACxB,mBAAmB,EACnB,iBAAiB,EACjB,sBAAsB,EACtB,yBAAyB,EAC1B,MAAM,aAAa,CAAC;AAErB,KAAK,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,wBAAwB,CAAC,CAAC;AAC5D,KAAK,gBAAgB,GAAG,MAAM,CAAC,MAAM,EAAE,sBAAsB,CAAC,CAAC;AAE/D,wBAAgB,mBAAmB,CAAC,WAAW,SAAS,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,EACzF,MAAM,EAAE,aAAa,CAAC,WAAW,EAAE,WAAW,EAAE,gBAAgB,CAAC,GAChE,WAAW,CAEb;AAED,wBAAgB,gBAAgB,CAC9B,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,EAC/C,OAAO,CAAC,EAAE,yBAAyB,GAClC,yBAAyB,CAgB3B;AAED,wBAAgB,iBAAiB,CAAC,SAAS,SAAS,MAAM,CAAC,MAAM,EAAE,wBAAwB,CAAC,EAC1F,QAAQ,EAAE,SAAS,GAAG,SAAS,GAC9B,SAAS,CAEX;AAED,wBAAgB,sBAAsB,CAAC,cAAc,SAAS,MAAM,CAAC,MAAM,EAAE,sBAAsB,CAAC,EAClG,aAAa,EAAE,cAAc,GAAG,SAAS,EACzC,MAAM,EAAE,MAAM,GACb,cAAc,CAgBhB;AAED,wBAAgB,wBAAwB,CACtC,WAAW,EAAE,MAAM,EACnB,UAAU,EAAE,iBAAiB,GAC5B,eAAe,CAcjB"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { AnyDatasetInstance, DatasetQuery, DatasetQueryResult, ExecutionContext } from '../types.js';
|
|
2
|
+
/** Apply the same result contract to builder-backed and derived dataset queries. */
|
|
3
|
+
export declare function toDatasetQueryResult(rows: Record<string, unknown>[], options: {
|
|
4
|
+
dataset: AnyDatasetInstance;
|
|
5
|
+
query: DatasetQuery;
|
|
6
|
+
sql: string;
|
|
7
|
+
timingMs: number;
|
|
8
|
+
context?: ExecutionContext;
|
|
9
|
+
}): DatasetQueryResult;
|
|
10
|
+
//# sourceMappingURL=dataset-query-result.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dataset-query-result.d.ts","sourceRoot":"","sources":["../../src/utils/dataset-query-result.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,kBAAkB,EAClB,YAAY,EACZ,kBAAkB,EAClB,gBAAgB,EACjB,MAAM,aAAa,CAAC;AAKrB,oFAAoF;AACpF,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,EAC/B,OAAO,EAAE;IACP,OAAO,EAAE,kBAAkB,CAAC;IAC5B,KAAK,EAAE,YAAY,CAAC;IACpB,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,gBAAgB,CAAC;CAC5B,GACA,kBAAkB,CAgBpB"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { applyPagination } from './pagination.js';
|
|
2
|
+
import { serializeSemanticMeasureValues } from './semantic-result-serialization.js';
|
|
3
|
+
import { getRuntimeTenantId } from './tenant-runtime.js';
|
|
4
|
+
/** Apply the same result contract to builder-backed and derived dataset queries. */
|
|
5
|
+
export function toDatasetQueryResult(rows, options) {
|
|
6
|
+
const { dataset, query, sql, timingMs, context } = options;
|
|
7
|
+
const selectedMeasures = query.measures ?? Object.keys(dataset.measures);
|
|
8
|
+
const { data, pagination } = applyPagination(rows, query.limit, query.offset);
|
|
9
|
+
const serializedData = serializeSemanticMeasureValues(data, selectedMeasures);
|
|
10
|
+
return {
|
|
11
|
+
data: serializedData,
|
|
12
|
+
meta: {
|
|
13
|
+
sql,
|
|
14
|
+
timingMs,
|
|
15
|
+
tenant: getRuntimeTenantId(context),
|
|
16
|
+
rowCount: serializedData.length,
|
|
17
|
+
pagination,
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
}
|
|
@@ -6,9 +6,9 @@ import { validateQualifiedFilter, validateRelationshipTenantRuntime, } from './r
|
|
|
6
6
|
export function validateDatasetQueryInput(ds, query, context) {
|
|
7
7
|
const errors = [];
|
|
8
8
|
const dimensionNames = Object.keys(ds.dimensions);
|
|
9
|
-
const measureNames = Object.keys(ds.measures);
|
|
9
|
+
const measureNames = [...Object.keys(ds.measures), ...Object.keys(ds.derivedMeasures ?? {})];
|
|
10
10
|
const selectedDimensions = query.dimensions ?? [];
|
|
11
|
-
const selectedMeasures = query.measures ??
|
|
11
|
+
const selectedMeasures = query.measures ?? Object.keys(ds.measures);
|
|
12
12
|
const filterNames = Object.keys(ds.filters);
|
|
13
13
|
const orderableFields = new Set([
|
|
14
14
|
...selectedDimensions,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"derived-measure-validation.d.ts","sourceRoot":"","sources":["../../src/utils/derived-measure-validation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,wBAAwB,EAIzB,MAAM,aAAa,CAAC;AAmHrB,wBAAgB,uBAAuB,CACrC,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,wBAAwB,CAAC,GACjD,IAAI,CAqDN"}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { isSafeSQLIdentifier } from '../sql-utils.js';
|
|
2
|
+
import { isBaseMeasure, isDerivedMeasure } from './dataset-measures.js';
|
|
3
|
+
function fail(datasetName, measureName, detail) {
|
|
4
|
+
throw new Error(`Invalid dataset "${datasetName}": derived measure "${measureName}" ${detail}`);
|
|
5
|
+
}
|
|
6
|
+
const BINARY_OPERATORS = new Set(['add', 'subtract', 'multiply', 'divide']);
|
|
7
|
+
const FUNCTION_ARITIES = {
|
|
8
|
+
nullIfZero: [1],
|
|
9
|
+
coalesce: [2],
|
|
10
|
+
round: [2],
|
|
11
|
+
floor: [1],
|
|
12
|
+
ceil: [1],
|
|
13
|
+
};
|
|
14
|
+
function collectReferences(datasetName, measureName, expression, references, budget, depth = 0) {
|
|
15
|
+
budget.nodes += 1;
|
|
16
|
+
if (depth > 16 || budget.nodes > 256) {
|
|
17
|
+
fail(datasetName, measureName, 'formula exceeds the expression limits.');
|
|
18
|
+
}
|
|
19
|
+
if (typeof expression !== 'object' || expression === null) {
|
|
20
|
+
fail(datasetName, measureName, 'formula contains an invalid expression.');
|
|
21
|
+
}
|
|
22
|
+
switch (expression.kind) {
|
|
23
|
+
case 'ref':
|
|
24
|
+
if (typeof expression.name !== 'string' || !isSafeSQLIdentifier(expression.name)) {
|
|
25
|
+
fail(datasetName, measureName, 'formula contains an invalid input reference.');
|
|
26
|
+
}
|
|
27
|
+
references.add(expression.name);
|
|
28
|
+
return;
|
|
29
|
+
case 'literal':
|
|
30
|
+
if (expression.value !== null
|
|
31
|
+
&& typeof expression.value !== 'string'
|
|
32
|
+
&& typeof expression.value !== 'boolean'
|
|
33
|
+
&& (typeof expression.value !== 'number' || !Number.isFinite(expression.value))) {
|
|
34
|
+
fail(datasetName, measureName, 'formula contains an invalid literal.');
|
|
35
|
+
}
|
|
36
|
+
return;
|
|
37
|
+
case 'binary':
|
|
38
|
+
if (!BINARY_OPERATORS.has(expression.operator)) {
|
|
39
|
+
fail(datasetName, measureName, 'formula contains an unsupported binary operator.');
|
|
40
|
+
}
|
|
41
|
+
collectReferences(datasetName, measureName, expression.left, references, budget, depth + 1);
|
|
42
|
+
collectReferences(datasetName, measureName, expression.right, references, budget, depth + 1);
|
|
43
|
+
return;
|
|
44
|
+
case 'function':
|
|
45
|
+
if (!Array.isArray(expression.args)
|
|
46
|
+
|| !FUNCTION_ARITIES[expression.name]?.includes(expression.args.length)) {
|
|
47
|
+
fail(datasetName, measureName, 'formula contains an unsupported function or arity.');
|
|
48
|
+
}
|
|
49
|
+
expression.args.forEach(argument => collectReferences(datasetName, measureName, argument, references, budget, depth + 1));
|
|
50
|
+
return;
|
|
51
|
+
default:
|
|
52
|
+
fail(datasetName, measureName, 'formula contains an unsupported expression kind.');
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
function evaluateFormula(datasetName, measureName, definition) {
|
|
56
|
+
const aliases = Object.keys(definition.uses);
|
|
57
|
+
let formula;
|
|
58
|
+
try {
|
|
59
|
+
formula = definition.formula(Object.fromEntries(aliases.map(alias => [alias, alias])));
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
fail(datasetName, measureName, `formula threw: ${error instanceof Error ? error.message : String(error)}.`);
|
|
63
|
+
}
|
|
64
|
+
if (typeof formula !== 'object'
|
|
65
|
+
|| formula === null
|
|
66
|
+
|| formula.__type !== 'formula_expr'
|
|
67
|
+
|| typeof formula.toSQL !== 'function'
|
|
68
|
+
|| typeof formula.expression !== 'object'
|
|
69
|
+
|| formula.expression === null) {
|
|
70
|
+
fail(datasetName, measureName, 'formula must return a FormulaExpr.');
|
|
71
|
+
}
|
|
72
|
+
return formula;
|
|
73
|
+
}
|
|
74
|
+
function assertAcyclic(datasetName, derivedMeasures) {
|
|
75
|
+
const visiting = new Set();
|
|
76
|
+
const visited = new Set();
|
|
77
|
+
const visit = (name) => {
|
|
78
|
+
if (visiting.has(name))
|
|
79
|
+
fail(datasetName, name, 'contains a dependency cycle.');
|
|
80
|
+
if (visited.has(name))
|
|
81
|
+
return;
|
|
82
|
+
if (!Object.hasOwn(derivedMeasures, name))
|
|
83
|
+
return;
|
|
84
|
+
const definition = derivedMeasures[name];
|
|
85
|
+
visiting.add(name);
|
|
86
|
+
Object.values(definition.uses).forEach(dependency => visit(dependency));
|
|
87
|
+
visiting.delete(name);
|
|
88
|
+
visited.add(name);
|
|
89
|
+
};
|
|
90
|
+
Object.keys(derivedMeasures).forEach(visit);
|
|
91
|
+
}
|
|
92
|
+
export function validateDerivedMeasures(datasetName, measures) {
|
|
93
|
+
const baseMeasures = Object.fromEntries(Object.entries(measures).filter(([, definition]) => definition && isBaseMeasure(definition)));
|
|
94
|
+
const derivedMeasures = Object.fromEntries(Object.entries(measures).filter(([, definition]) => definition && isDerivedMeasure(definition)));
|
|
95
|
+
for (const [measureName, definition] of Object.entries(derivedMeasures)) {
|
|
96
|
+
if (!isSafeSQLIdentifier(measureName)) {
|
|
97
|
+
fail(datasetName, measureName, 'name is not a safe identifier.');
|
|
98
|
+
}
|
|
99
|
+
if (typeof definition !== 'object' || definition === null || definition.__type !== 'derived_measure_definition') {
|
|
100
|
+
fail(datasetName, measureName, 'must be created with measure.derived().');
|
|
101
|
+
}
|
|
102
|
+
if (typeof definition.uses !== 'object' || definition.uses === null || Array.isArray(definition.uses)) {
|
|
103
|
+
fail(datasetName, measureName, 'must declare an input map.');
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
assertAcyclic(datasetName, derivedMeasures);
|
|
107
|
+
for (const [measureName, definition] of Object.entries(derivedMeasures)) {
|
|
108
|
+
const uses = Object.entries(definition.uses);
|
|
109
|
+
if (uses.length === 0)
|
|
110
|
+
fail(datasetName, measureName, 'must reference at least one base measure.');
|
|
111
|
+
if (typeof definition.formula !== 'function')
|
|
112
|
+
fail(datasetName, measureName, 'must define a formula function.');
|
|
113
|
+
for (const [alias, dependencyName] of uses) {
|
|
114
|
+
if (!isSafeSQLIdentifier(alias))
|
|
115
|
+
fail(datasetName, measureName, `has invalid input alias "${alias}".`);
|
|
116
|
+
if (typeof dependencyName !== 'string' || dependencyName.includes('.')) {
|
|
117
|
+
fail(datasetName, measureName, `references cross-dataset measure "${String(dependencyName)}".`);
|
|
118
|
+
}
|
|
119
|
+
if (Object.hasOwn(derivedMeasures, dependencyName)) {
|
|
120
|
+
fail(datasetName, measureName, `references derived measure "${dependencyName}"; v1 inputs must be base measures.`);
|
|
121
|
+
}
|
|
122
|
+
if (!Object.hasOwn(baseMeasures, dependencyName))
|
|
123
|
+
fail(datasetName, measureName, `references missing measure "${dependencyName}".`);
|
|
124
|
+
}
|
|
125
|
+
const references = new Set();
|
|
126
|
+
collectReferences(datasetName, measureName, evaluateFormula(datasetName, measureName, definition).expression, references, { nodes: 0 });
|
|
127
|
+
for (const reference of references) {
|
|
128
|
+
if (!Object.hasOwn(definition.uses, reference)) {
|
|
129
|
+
fail(datasetName, measureName, `formula references undeclared input alias "${reference}".`);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
for (const [alias] of uses) {
|
|
133
|
+
if (!references.has(alias)) {
|
|
134
|
+
fail(datasetName, measureName, `declares unused input alias "${alias}".`);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"filtered-aggregation-sql.d.ts","sourceRoot":"","sources":["../../src/utils/filtered-aggregation-sql.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,kBAAkB,EAAgB,MAAM,aAAa,CAAC;AAErF,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,gCAAgC,CAAC;AAEjF,KAAK,YAAY,GAAG,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"filtered-aggregation-sql.d.ts","sourceRoot":"","sources":["../../src/utils/filtered-aggregation-sql.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,kBAAkB,EAAgB,MAAM,aAAa,CAAC;AAErF,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,gCAAgC,CAAC;AAEjF,KAAK,YAAY,GAAG,kBAAkB,CAAC;AAqEvC,wBAAgB,kCAAkC,CAChD,EAAE,EAAE,YAAY,EAChB,IAAI,EAAE,eAAe,EACrB,WAAW,EAAE,MAAM,EACnB,OAAO,CAAC,EAAE,0BAA0B,GACnC,MAAM,CA+BR"}
|
|
@@ -4,7 +4,10 @@ function renderMeasureFilterLiteral(value) {
|
|
|
4
4
|
return 'NULL';
|
|
5
5
|
}
|
|
6
6
|
if (typeof value === 'string') {
|
|
7
|
-
|
|
7
|
+
// ClickHouse also treats backslash as a string escape. Doubling quotes
|
|
8
|
+
// without escaping backslashes lets a value ending in \\ before a quote
|
|
9
|
+
// break out of the literal and change the predicate.
|
|
10
|
+
return `'${value.replace(/\\/g, '\\\\').replace(/'/g, "''")}'`;
|
|
8
11
|
}
|
|
9
12
|
if (typeof value === 'number') {
|
|
10
13
|
if (!Number.isFinite(value)) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type ProtocolExpression, type ProtocolMetricDerivation } from '@hypequery/protocol';
|
|
2
|
-
import type { AggregationSpec, DerivedMetricSpec, MetricFilter } from '../types.js';
|
|
2
|
+
import type { AggregationSpec, DerivedMeasureDefinition, DerivedMetricSpec, MetricFilter } from '../types.js';
|
|
3
3
|
export declare function filterExpression(filter: MetricFilter): ProtocolExpression;
|
|
4
|
+
export declare function derivedMeasureExpression(definition: DerivedMeasureDefinition): ProtocolExpression;
|
|
4
5
|
export declare function metricExpression(spec: AggregationSpec | DerivedMetricSpec): ProtocolExpression;
|
|
5
6
|
/**
|
|
6
7
|
* The formula in the shape it was authored in, beside the inlined form.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"protocol-metric-expressions.d.ts","sourceRoot":"","sources":["../../src/utils/protocol-metric-expressions.ts"],"names":[],"mappings":"AAAA,OAAO,EAEgB,KAAK,kBAAkB,EAAE,KAAK,wBAAwB,EAC5E,MAAM,qBAAqB,CAAC;AAE7B,OAAO,KAAK,EAAE,eAAe,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"protocol-metric-expressions.d.ts","sourceRoot":"","sources":["../../src/utils/protocol-metric-expressions.ts"],"names":[],"mappings":"AAAA,OAAO,EAEgB,KAAK,kBAAkB,EAAE,KAAK,wBAAwB,EAC5E,MAAM,qBAAqB,CAAC;AAE7B,OAAO,KAAK,EAAE,eAAe,EAAE,wBAAwB,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAsC9G,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,YAAY,GAAG,kBAAkB,CAgBzE;AA+CD,wBAAgB,wBAAwB,CAAC,UAAU,EAAE,wBAAwB,GAAG,kBAAkB,CAGjG;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,eAAe,GAAG,iBAAiB,GAAG,kBAAkB,CAQ9F;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,iBAAiB,GAAG,wBAAwB,CASlF"}
|
|
@@ -85,6 +85,10 @@ function semanticExpression(expression, references = {}) {
|
|
|
85
85
|
};
|
|
86
86
|
}
|
|
87
87
|
}
|
|
88
|
+
export function derivedMeasureExpression(definition) {
|
|
89
|
+
const aliases = Object.fromEntries(Object.keys(definition.uses).map(alias => [alias, alias]));
|
|
90
|
+
return semanticExpression(definition.formula(aliases).expression);
|
|
91
|
+
}
|
|
88
92
|
export function metricExpression(spec) {
|
|
89
93
|
if (spec.__type === 'aggregation_spec')
|
|
90
94
|
return aggregationExpression(spec);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"relationship-fields.d.ts","sourceRoot":"","sources":["../../src/utils/relationship-fields.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EACV,kBAAkB,EAClB,mBAAmB,EACnB,sBAAsB,EACvB,MAAM,aAAa,CAAC;AAErB;;;;;;GAMG;AACH,wBAAgB,+BAA+B,CAC7C,IAAI,EAAE,MAAM,EACZ,YAAY,EAAE,sBAAsB,GACnC,MAAM,EAAE,CAEV;AAED;;;;;GAKG;AACH,wBAAgB,+BAA+B,CAC7C,IAAI,EAAE,MAAM,EACZ,YAAY,EAAE,sBAAsB,GACnC,MAAM,EAAE,CAEV;AAgBD,MAAM,WAAW,oBAAoB;IACnC,2DAA2D;IAC3D,YAAY,EAAE,MAAM,CAAC;IACrB,6EAA6E;IAC7E,KAAK,EAAE,MAAM,CAAC;CACf;AAED,yEAAyE;AACzE,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEtD;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,oBAAoB,GAAG,IAAI,CAM7E;AAED,MAAM,WAAW,sBAAsB;IACrC,8EAA8E;IAC9E,gBAAgB,EAAE,MAAM,CAAC;IACzB,YAAY,EAAE,sBAAsB,CAAC;IACrC,4CAA4C;IAC5C,MAAM,EAAE,kBAAkB,CAAC;IAC3B,+DAA+D;IAC/D,mBAAmB,EAAE,MAAM,CAAC;IAC5B,eAAe,EAAE,mBAAmB,CAAC;IACrC,oDAAoD;IACpD,YAAY,EAAE,MAAM,CAAC;IACrB,yEAAyE;IACzE,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,MAAM,wBAAwB,GAChC;IAAE,QAAQ,EAAE,sBAAsB,CAAC;IAAC,KAAK,CAAC,EAAE,SAAS,CAAA;CAAE,GACvD;IAAE,QAAQ,CAAC,EAAE,SAAS,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAE5C;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CACnC,EAAE,EAAE,kBAAkB,EACtB,IAAI,EAAE,MAAM,GACX,wBAAwB,GAAG,IAAI,
|
|
1
|
+
{"version":3,"file":"relationship-fields.d.ts","sourceRoot":"","sources":["../../src/utils/relationship-fields.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EACV,kBAAkB,EAClB,mBAAmB,EACnB,sBAAsB,EACvB,MAAM,aAAa,CAAC;AAErB;;;;;;GAMG;AACH,wBAAgB,+BAA+B,CAC7C,IAAI,EAAE,MAAM,EACZ,YAAY,EAAE,sBAAsB,GACnC,MAAM,EAAE,CAEV;AAED;;;;;GAKG;AACH,wBAAgB,+BAA+B,CAC7C,IAAI,EAAE,MAAM,EACZ,YAAY,EAAE,sBAAsB,GACnC,MAAM,EAAE,CAEV;AAgBD,MAAM,WAAW,oBAAoB;IACnC,2DAA2D;IAC3D,YAAY,EAAE,MAAM,CAAC;IACrB,6EAA6E;IAC7E,KAAK,EAAE,MAAM,CAAC;CACf;AAED,yEAAyE;AACzE,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEtD;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,oBAAoB,GAAG,IAAI,CAM7E;AAED,MAAM,WAAW,sBAAsB;IACrC,8EAA8E;IAC9E,gBAAgB,EAAE,MAAM,CAAC;IACzB,YAAY,EAAE,sBAAsB,CAAC;IACrC,4CAA4C;IAC5C,MAAM,EAAE,kBAAkB,CAAC;IAC3B,+DAA+D;IAC/D,mBAAmB,EAAE,MAAM,CAAC;IAC5B,eAAe,EAAE,mBAAmB,CAAC;IACrC,oDAAoD;IACpD,YAAY,EAAE,MAAM,CAAC;IACrB,yEAAyE;IACzE,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,MAAM,wBAAwB,GAChC;IAAE,QAAQ,EAAE,sBAAsB,CAAC;IAAC,KAAK,CAAC,EAAE,SAAS,CAAA;CAAE,GACvD;IAAE,QAAQ,CAAC,EAAE,SAAS,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAE5C;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CACnC,EAAE,EAAE,kBAAkB,EACtB,IAAI,EAAE,MAAM,GACX,wBAAwB,GAAG,IAAI,CA2DjC"}
|
|
@@ -64,7 +64,9 @@ export function resolveQualifiedField(ds, name) {
|
|
|
64
64
|
return null;
|
|
65
65
|
}
|
|
66
66
|
const { relationship: relationshipName, field } = parsed;
|
|
67
|
-
const relationship = ds.relationships
|
|
67
|
+
const relationship = Object.hasOwn(ds.relationships, relationshipName)
|
|
68
|
+
? ds.relationships[relationshipName]
|
|
69
|
+
: undefined;
|
|
68
70
|
if (!relationship) {
|
|
69
71
|
const available = Object.keys(ds.relationships);
|
|
70
72
|
return {
|
|
@@ -84,7 +86,9 @@ export function resolveQualifiedField(ds, name) {
|
|
|
84
86
|
};
|
|
85
87
|
}
|
|
86
88
|
const target = relationship.target();
|
|
87
|
-
const targetDimension = target.dimensions
|
|
89
|
+
const targetDimension = Object.hasOwn(target.dimensions, field)
|
|
90
|
+
? target.dimensions[field]
|
|
91
|
+
: undefined;
|
|
88
92
|
if (!targetDimension) {
|
|
89
93
|
const available = Object.keys(target.dimensions);
|
|
90
94
|
return {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"relationship-validation.d.ts","sourceRoot":"","sources":["../../src/utils/relationship-validation.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EACV,kBAAkB,EAClB,YAAY,EACZ,gBAAgB,EAChB,YAAY,EACZ,WAAW,EACZ,MAAM,aAAa,CAAC;AAWrB,KAAK,iBAAiB,GAAG,IAAI,CAC3B,YAAY,GAAG,WAAW,EAC1B,YAAY,GAAG,SAAS,GAAG,SAAS,CACrC,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,iCAAiC,CAC/C,EAAE,EAAE,kBAAkB,EACtB,KAAK,EAAE,iBAAiB,EACxB,OAAO,CAAC,EAAE,gBAAgB,GACzB,MAAM,GAAG,SAAS,CAmBpB;AAED;;;;;;;GAOG;AACH,wBAAgB,uBAAuB,CACrC,EAAE,EAAE,kBAAkB,EACtB,MAAM,EAAE,YAAY,EACpB,OAAO,CAAC,EAAE,gBAAgB,GACzB,MAAM,GAAG,IAAI,
|
|
1
|
+
{"version":3,"file":"relationship-validation.d.ts","sourceRoot":"","sources":["../../src/utils/relationship-validation.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EACV,kBAAkB,EAClB,YAAY,EACZ,gBAAgB,EAChB,YAAY,EACZ,WAAW,EACZ,MAAM,aAAa,CAAC;AAWrB,KAAK,iBAAiB,GAAG,IAAI,CAC3B,YAAY,GAAG,WAAW,EAC1B,YAAY,GAAG,SAAS,GAAG,SAAS,CACrC,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,iCAAiC,CAC/C,EAAE,EAAE,kBAAkB,EACtB,KAAK,EAAE,iBAAiB,EACxB,OAAO,CAAC,EAAE,gBAAgB,GACzB,MAAM,GAAG,SAAS,CAmBpB;AAED;;;;;;;GAOG;AACH,wBAAgB,uBAAuB,CACrC,EAAE,EAAE,kBAAkB,EACtB,MAAM,EAAE,YAAY,EACpB,OAAO,CAAC,EAAE,gBAAgB,GACzB,MAAM,GAAG,IAAI,CA4Bf"}
|
|
@@ -43,9 +43,20 @@ export function validateQualifiedFilter(ds, filter, context) {
|
|
|
43
43
|
if (!resolution.resolved) {
|
|
44
44
|
return resolution.error;
|
|
45
45
|
}
|
|
46
|
-
const { target, targetColumn, targetDimension } = resolution.resolved;
|
|
46
|
+
const { target, targetColumn, targetDimension, targetDimensionName } = resolution.resolved;
|
|
47
47
|
if (getRuntimeTenantPredicate(context) && target.tenantKey && targetColumn === target.tenantKey) {
|
|
48
48
|
return `Cannot filter on tenant field "${filter.field}" when runtime tenancy enforcement is active.`;
|
|
49
49
|
}
|
|
50
|
+
// A relationship does not turn every target dimension into an exposed
|
|
51
|
+
// filter. Apply the target's explicit or generated filter allowlist and
|
|
52
|
+
// operator policy. An explicit filter can override filterable: false.
|
|
53
|
+
const definition = target.filters[targetDimensionName];
|
|
54
|
+
if (!definition
|
|
55
|
+
|| definition.field !== targetDimensionName) {
|
|
56
|
+
return `Filter "${filter.field}" is not exposed by target dataset "${target.name}".`;
|
|
57
|
+
}
|
|
58
|
+
if (definition.operators && !definition.operators.includes(filter.operator)) {
|
|
59
|
+
return `Filter "${filter.field}" does not allow operator "${filter.operator}".`;
|
|
60
|
+
}
|
|
50
61
|
return validateFilterValue(filter, targetDimension.fieldType);
|
|
51
62
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { DatasetConfig,
|
|
1
|
+
import type { DatasetConfig, DatasetMeasureDefinition, DimensionDefinition, RelationshipDefinition, SemanticMetadata } from '../types.js';
|
|
2
2
|
export declare function validateSemanticMetadata(datasetName: string, location: string, metadata: SemanticMetadata): void;
|
|
3
|
-
export declare function validateDatasetAgentMetadata<TDimensions extends Record<string, DimensionDefinition>, TMeasures extends Record<string,
|
|
3
|
+
export declare function validateDatasetAgentMetadata<TDimensions extends Record<string, DimensionDefinition>, TMeasures extends Record<string, DatasetMeasureDefinition>, TRelationships extends Record<string, RelationshipDefinition>>(datasetName: string, config: DatasetConfig<TDimensions, TMeasures, TRelationships>): void;
|
|
4
4
|
//# sourceMappingURL=semantic-metadata-validation.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"semantic-metadata-validation.d.ts","sourceRoot":"","sources":["../../src/utils/semantic-metadata-validation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,aAAa,EACb,mBAAmB,EACnB,
|
|
1
|
+
{"version":3,"file":"semantic-metadata-validation.d.ts","sourceRoot":"","sources":["../../src/utils/semantic-metadata-validation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,aAAa,EACb,wBAAwB,EACxB,mBAAmB,EACnB,sBAAsB,EACtB,gBAAgB,EACjB,MAAM,aAAa,CAAC;AAoBrB,wBAAgB,wBAAwB,CACtC,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,gBAAgB,GACzB,IAAI,CA8BN;AAED,wBAAgB,4BAA4B,CAC1C,WAAW,SAAS,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,EACvD,SAAS,SAAS,MAAM,CAAC,MAAM,EAAE,wBAAwB,CAAC,EAC1D,cAAc,SAAS,MAAM,CAAC,MAAM,EAAE,sBAAsB,CAAC,EAE7D,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,aAAa,CAAC,WAAW,EAAE,SAAS,EAAE,cAAc,CAAC,GAC5D,IAAI,CAuDN"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hypequery/datasets",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.1",
|
|
4
4
|
"description": "Code-first TypeScript semantic layer for ClickHouse datasets, metrics, multi-tenancy, and AI agents",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"semantic-layer",
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"dist"
|
|
43
43
|
],
|
|
44
44
|
"dependencies": {
|
|
45
|
-
"@hypequery/protocol": "^0.
|
|
45
|
+
"@hypequery/protocol": "^0.14.0",
|
|
46
46
|
"@noble/hashes": "^1.8.0",
|
|
47
47
|
"zod": "^3.22.4",
|
|
48
48
|
"zod-to-json-schema": "^3.23.5"
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
"typescript": "^5.7.3",
|
|
61
61
|
"@vitest/coverage-v8": "^3.2.6",
|
|
62
62
|
"vitest": "^3.2.6",
|
|
63
|
-
"@hypequery/protocol-conformance": "^0.11.
|
|
63
|
+
"@hypequery/protocol-conformance": "^0.11.2"
|
|
64
64
|
},
|
|
65
65
|
"repository": {
|
|
66
66
|
"type": "git",
|