semanticdb-v2 2.0.0-alpha.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/README.md +448 -0
- package/dist/cache/expiry.d.ts +5 -0
- package/dist/cache/expiry.js +1 -0
- package/dist/cache/identity.d.ts +3 -0
- package/dist/cache/identity.js +1 -0
- package/dist/cache/index.d.ts +3 -0
- package/dist/cache/redis-query-cache.d.ts +37 -0
- package/dist/cache/redis-query-cache.js +1 -0
- package/dist/cache/serialization.d.ts +3 -0
- package/dist/cache/serialization.js +1 -0
- package/dist/cache/types.d.ts +27 -0
- package/dist/calendar/constants.d.ts +7 -0
- package/dist/calendar/constants.js +1 -0
- package/dist/calendar/index.d.ts +6 -0
- package/dist/calendar/provider-relation-store.d.ts +9 -0
- package/dist/calendar/provider-relation-store.js +1 -0
- package/dist/calendar/resolver.d.ts +10 -0
- package/dist/calendar/resolver.js +1 -0
- package/dist/calendar/system-schemas.d.ts +3 -0
- package/dist/calendar/system-schemas.js +1 -0
- package/dist/calendar/types.d.ts +31 -0
- package/dist/compatibility/index.d.ts +2 -0
- package/dist/compatibility/logicform.d.ts +13 -0
- package/dist/compatibility/logicform.js +1 -0
- package/dist/compiler/compile-cte.d.ts +12 -0
- package/dist/compiler/compile-cte.js +1 -0
- package/dist/compiler/compile-plan.d.ts +5 -0
- package/dist/compiler/compile-plan.js +1 -0
- package/dist/compiler/index.d.ts +4 -0
- package/dist/compiler/shorten-sql.d.ts +2 -0
- package/dist/compiler/shorten-sql.js +2 -0
- package/dist/compiler/types.d.ts +5 -0
- package/dist/custom-functions/custom-functions.d.ts +6 -0
- package/dist/custom-functions/custom-functions.js +1 -0
- package/dist/custom-functions/index.d.ts +3 -0
- package/dist/custom-functions/operator-adapter.d.ts +12 -0
- package/dist/custom-functions/operator-adapter.js +1 -0
- package/dist/custom-functions/types.d.ts +62 -0
- package/dist/errors.d.ts +5 -0
- package/dist/errors.js +1 -0
- package/dist/executor/compatibility.d.ts +6 -0
- package/dist/executor/compatibility.js +1 -0
- package/dist/executor/components.d.ts +9 -0
- package/dist/executor/components.js +1 -0
- package/dist/executor/connection-registry.d.ts +12 -0
- package/dist/executor/connection-registry.js +1 -0
- package/dist/executor/evaluate.d.ts +4 -0
- package/dist/executor/evaluate.js +1 -0
- package/dist/executor/execute-query.d.ts +13 -0
- package/dist/executor/execute-query.js +1 -0
- package/dist/executor/execute.d.ts +4 -0
- package/dist/executor/execute.js +1 -0
- package/dist/executor/fetch-entities.d.ts +26 -0
- package/dist/executor/fetch-entities.js +1 -0
- package/dist/executor/index.d.ts +3 -0
- package/dist/executor/result-hydrator/entity-loader.d.ts +6 -0
- package/dist/executor/result-hydrator/entity-loader.js +1 -0
- package/dist/executor/result-hydrator/format-values.d.ts +4 -0
- package/dist/executor/result-hydrator/format-values.js +1 -0
- package/dist/executor/result-hydrator/hydrate-result.d.ts +4 -0
- package/dist/executor/result-hydrator/hydrate-result.js +1 -0
- package/dist/executor/result-hydrator/index.d.ts +2 -0
- package/dist/executor/result-hydrator/missing-entity.d.ts +3 -0
- package/dist/executor/result-hydrator/missing-entity.js +1 -0
- package/dist/executor/result-hydrator/populate-entities.d.ts +3 -0
- package/dist/executor/result-hydrator/populate-entities.js +1 -0
- package/dist/executor/result-hydrator/types.d.ts +14 -0
- package/dist/executor/row-identity.d.ts +4 -0
- package/dist/executor/row-identity.js +1 -0
- package/dist/executor/types.d.ts +118 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.js +1 -0
- package/dist/logicform-types.d.ts +125 -0
- package/dist/logicform-types.js +1 -0
- package/dist/normalizer/core.d.ts +19 -0
- package/dist/normalizer/core.js +1 -0
- package/dist/normalizer/default-pred-name.d.ts +6 -0
- package/dist/normalizer/default-pred-name.js +1 -0
- package/dist/normalizer/entity-id.d.ts +4 -0
- package/dist/normalizer/entity-id.js +1 -0
- package/dist/normalizer/groupby.d.ts +4 -0
- package/dist/normalizer/groupby.js +1 -0
- package/dist/normalizer/index.d.ts +3 -0
- package/dist/normalizer/normalize-logicform.d.ts +4 -0
- package/dist/normalizer/normalize-logicform.js +1 -0
- package/dist/normalizer/pagination.d.ts +2 -0
- package/dist/normalizer/pagination.js +1 -0
- package/dist/normalizer/pred-query-name.d.ts +1 -0
- package/dist/normalizer/pred-query-name.js +1 -0
- package/dist/normalizer/preds.d.ts +4 -0
- package/dist/normalizer/preds.js +1 -0
- package/dist/normalizer/query.d.ts +5 -0
- package/dist/normalizer/query.js +1 -0
- package/dist/normalizer/version.d.ts +3 -0
- package/dist/normalizer/version.js +1 -0
- package/dist/operators/builtins/abs.d.ts +2 -0
- package/dist/operators/builtins/abs.js +1 -0
- package/dist/operators/builtins/accumulate.d.ts +2 -0
- package/dist/operators/builtins/accumulate.js +1 -0
- package/dist/operators/builtins/add.d.ts +2 -0
- package/dist/operators/builtins/add.js +1 -0
- package/dist/operators/builtins/arithmetic.d.ts +3 -0
- package/dist/operators/builtins/arithmetic.js +1 -0
- package/dist/operators/builtins/average-per-period.d.ts +2 -0
- package/dist/operators/builtins/average-per-period.js +1 -0
- package/dist/operators/builtins/avg.d.ts +2 -0
- package/dist/operators/builtins/avg.js +1 -0
- package/dist/operators/builtins/case.d.ts +2 -0
- package/dist/operators/builtins/case.js +1 -0
- package/dist/operators/builtins/concat.d.ts +2 -0
- package/dist/operators/builtins/concat.js +1 -0
- package/dist/operators/builtins/count-distinct.d.ts +2 -0
- package/dist/operators/builtins/count-distinct.js +1 -0
- package/dist/operators/builtins/count.d.ts +2 -0
- package/dist/operators/builtins/count.js +1 -0
- package/dist/operators/builtins/date-bucket.d.ts +2 -0
- package/dist/operators/builtins/date-bucket.js +1 -0
- package/dist/operators/builtins/divide.d.ts +2 -0
- package/dist/operators/builtins/divide.js +1 -0
- package/dist/operators/builtins/index.d.ts +2 -0
- package/dist/operators/builtins/index.js +1 -0
- package/dist/operators/builtins/max.d.ts +2 -0
- package/dist/operators/builtins/max.js +1 -0
- package/dist/operators/builtins/min.d.ts +2 -0
- package/dist/operators/builtins/min.js +1 -0
- package/dist/operators/builtins/multiply.d.ts +2 -0
- package/dist/operators/builtins/multiply.js +1 -0
- package/dist/operators/builtins/newly.d.ts +2 -0
- package/dist/operators/builtins/newly.js +1 -0
- package/dist/operators/builtins/period-boundary.d.ts +2 -0
- package/dist/operators/builtins/period-boundary.js +1 -0
- package/dist/operators/builtins/period-difference.d.ts +2 -0
- package/dist/operators/builtins/period-difference.js +1 -0
- package/dist/operators/builtins/period-growth.d.ts +2 -0
- package/dist/operators/builtins/period-growth.js +1 -0
- package/dist/operators/builtins/period-name.d.ts +1 -0
- package/dist/operators/builtins/period-name.js +1 -0
- package/dist/operators/builtins/period-shift.d.ts +6 -0
- package/dist/operators/builtins/period-shift.js +1 -0
- package/dist/operators/builtins/rank.d.ts +2 -0
- package/dist/operators/builtins/rank.js +1 -0
- package/dist/operators/builtins/row-number.d.ts +2 -0
- package/dist/operators/builtins/row-number.js +1 -0
- package/dist/operators/builtins/share-of-total.d.ts +2 -0
- package/dist/operators/builtins/share-of-total.js +1 -0
- package/dist/operators/builtins/sql.d.ts +3 -0
- package/dist/operators/builtins/sql.js +1 -0
- package/dist/operators/builtins/substring.d.ts +2 -0
- package/dist/operators/builtins/substring.js +1 -0
- package/dist/operators/builtins/subtract.d.ts +2 -0
- package/dist/operators/builtins/subtract.js +1 -0
- package/dist/operators/builtins/sum.d.ts +2 -0
- package/dist/operators/builtins/sum.js +1 -0
- package/dist/operators/builtins/time-range.d.ts +13 -0
- package/dist/operators/builtins/time-range.js +1 -0
- package/dist/operators/groupby.d.ts +3 -0
- package/dist/operators/groupby.js +1 -0
- package/dist/operators/index.d.ts +7 -0
- package/dist/operators/index.js +1 -0
- package/dist/operators/registry.d.ts +10 -0
- package/dist/operators/registry.js +1 -0
- package/dist/operators/sql-expression.d.ts +1 -0
- package/dist/operators/sql-expression.js +1 -0
- package/dist/operators/types.d.ts +277 -0
- package/dist/permissions/index.d.ts +3 -0
- package/dist/permissions/permissions.d.ts +10 -0
- package/dist/permissions/permissions.js +1 -0
- package/dist/permissions/types.d.ts +12 -0
- package/dist/physical/clickhouse-provider.d.ts +44 -0
- package/dist/physical/clickhouse-provider.js +1 -0
- package/dist/physical/connection.d.ts +32 -0
- package/dist/physical/connection.js +1 -0
- package/dist/physical/display-query.d.ts +4 -0
- package/dist/physical/display-query.js +1 -0
- package/dist/physical/doris-provider.d.ts +9 -0
- package/dist/physical/doris-provider.js +1 -0
- package/dist/physical/index.d.ts +18 -0
- package/dist/physical/mysql-compatible-sql.d.ts +6 -0
- package/dist/physical/mysql-compatible-sql.js +1 -0
- package/dist/physical/mysql-protocol-provider.d.ts +34 -0
- package/dist/physical/mysql-protocol-provider.js +1 -0
- package/dist/physical/mysql-provider.d.ts +8 -0
- package/dist/physical/mysql-provider.js +1 -0
- package/dist/physical/oracle-provider.d.ts +53 -0
- package/dist/physical/oracle-provider.js +1 -0
- package/dist/physical/postgresql-provider.d.ts +37 -0
- package/dist/physical/postgresql-provider.js +1 -0
- package/dist/physical/provider.d.ts +40 -0
- package/dist/physical/snowflake-provider.d.ts +45 -0
- package/dist/physical/snowflake-provider.js +1 -0
- package/dist/physical/starrocks-provider.d.ts +9 -0
- package/dist/physical/starrocks-provider.js +1 -0
- package/dist/planner/build-lineage.d.ts +5 -0
- package/dist/planner/build-lineage.js +1 -0
- package/dist/planner/create-logical-plan.d.ts +15 -0
- package/dist/planner/create-logical-plan.js +1 -0
- package/dist/planner/index.d.ts +7 -0
- package/dist/planner/select-schema-mapping.d.ts +14 -0
- package/dist/planner/select-schema-mapping.js +1 -0
- package/dist/planner/semantics/index.d.ts +5 -0
- package/dist/planner/semantics/lifecycle/classification.d.ts +7 -0
- package/dist/planner/semantics/lifecycle/classification.js +1 -0
- package/dist/planner/semantics/lifecycle/index.d.ts +3 -0
- package/dist/planner/semantics/lifecycle/query-policy.d.ts +3 -0
- package/dist/planner/semantics/lifecycle/query-policy.js +1 -0
- package/dist/planner/semantics/lifecycle/rule.d.ts +2 -0
- package/dist/planner/semantics/lifecycle/rule.js +1 -0
- package/dist/planner/semantics/observation/classification.d.ts +3 -0
- package/dist/planner/semantics/observation/classification.js +1 -0
- package/dist/planner/semantics/observation/index.d.ts +4 -0
- package/dist/planner/semantics/observation/operator-policy.d.ts +11 -0
- package/dist/planner/semantics/observation/operator-policy.js +1 -0
- package/dist/planner/semantics/observation/rule.d.ts +2 -0
- package/dist/planner/semantics/observation/rule.js +1 -0
- package/dist/planner/semantics/observation/types.d.ts +3 -0
- package/dist/planner/semantics/registry-defaults.d.ts +2 -0
- package/dist/planner/semantics/registry-defaults.js +1 -0
- package/dist/planner/semantics/registry.d.ts +32 -0
- package/dist/planner/semantics/registry.js +1 -0
- package/dist/planner/types.d.ts +202 -0
- package/dist/planner/types.js +1 -0
- package/dist/plugins/hierarchy/compatibility.d.ts +2 -0
- package/dist/plugins/hierarchy/compatibility.js +1 -0
- package/dist/plugins/hierarchy/entity-identity.d.ts +2 -0
- package/dist/plugins/hierarchy/entity-identity.js +1 -0
- package/dist/plugins/hierarchy/index.d.ts +3 -0
- package/dist/plugins/hierarchy/index.js +1 -0
- package/dist/plugins/hierarchy/operator.d.ts +2 -0
- package/dist/plugins/hierarchy/operator.js +1 -0
- package/dist/plugins/hierarchy/resolve-schema.d.ts +2 -0
- package/dist/plugins/hierarchy/resolve-schema.js +1 -0
- package/dist/plugins/hierarchy/types.d.ts +28 -0
- package/dist/plugins/index.d.ts +5 -0
- package/dist/plugins/index.js +1 -0
- package/dist/plugins/types.d.ts +20 -0
- package/dist/schema/entity-identity.d.ts +5 -0
- package/dist/schema/entity-identity.js +1 -0
- package/dist/schema/index.d.ts +4 -0
- package/dist/schema/resolve-property-path.d.ts +15 -0
- package/dist/schema/resolve-property-path.js +1 -0
- package/dist/schema/schema-registry.d.ts +4 -0
- package/dist/schema/schema-registry.js +1 -0
- package/dist/schema-mappings/freshness.d.ts +7 -0
- package/dist/schema-mappings/freshness.js +1 -0
- package/dist/schema-mappings/index.d.ts +4 -0
- package/dist/schema-mappings/registry.d.ts +9 -0
- package/dist/schema-mappings/registry.js +1 -0
- package/dist/schema-mappings/types.d.ts +46 -0
- package/dist/semantic-types/builtins/boolean.d.ts +2 -0
- package/dist/semantic-types/builtins/boolean.js +1 -0
- package/dist/semantic-types/builtins/category.d.ts +2 -0
- package/dist/semantic-types/builtins/category.js +1 -0
- package/dist/semantic-types/builtins/currency.d.ts +2 -0
- package/dist/semantic-types/builtins/currency.js +1 -0
- package/dist/semantic-types/builtins/date.d.ts +2 -0
- package/dist/semantic-types/builtins/date.js +1 -0
- package/dist/semantic-types/builtins/duration.d.ts +2 -0
- package/dist/semantic-types/builtins/duration.js +1 -0
- package/dist/semantic-types/builtins/end-date.d.ts +2 -0
- package/dist/semantic-types/builtins/end-date.js +1 -0
- package/dist/semantic-types/builtins/entity.d.ts +2 -0
- package/dist/semantic-types/builtins/entity.js +1 -0
- package/dist/semantic-types/builtins/file.d.ts +2 -0
- package/dist/semantic-types/builtins/file.js +1 -0
- package/dist/semantic-types/builtins/human-name.d.ts +2 -0
- package/dist/semantic-types/builtins/human-name.js +1 -0
- package/dist/semantic-types/builtins/id.d.ts +2 -0
- package/dist/semantic-types/builtins/id.js +1 -0
- package/dist/semantic-types/builtins/image.d.ts +2 -0
- package/dist/semantic-types/builtins/image.js +1 -0
- package/dist/semantic-types/builtins/index.d.ts +2 -0
- package/dist/semantic-types/builtins/index.js +1 -0
- package/dist/semantic-types/builtins/int.d.ts +2 -0
- package/dist/semantic-types/builtins/int.js +1 -0
- package/dist/semantic-types/builtins/name.d.ts +2 -0
- package/dist/semantic-types/builtins/name.js +1 -0
- package/dist/semantic-types/builtins/number.d.ts +2 -0
- package/dist/semantic-types/builtins/number.js +1 -0
- package/dist/semantic-types/builtins/object.d.ts +3 -0
- package/dist/semantic-types/builtins/object.js +1 -0
- package/dist/semantic-types/builtins/order.d.ts +2 -0
- package/dist/semantic-types/builtins/order.js +1 -0
- package/dist/semantic-types/builtins/percentage.d.ts +2 -0
- package/dist/semantic-types/builtins/percentage.js +1 -0
- package/dist/semantic-types/builtins/start-date.d.ts +2 -0
- package/dist/semantic-types/builtins/start-date.js +1 -0
- package/dist/semantic-types/builtins/string.d.ts +2 -0
- package/dist/semantic-types/builtins/string.js +1 -0
- package/dist/semantic-types/builtins/timestamp.d.ts +2 -0
- package/dist/semantic-types/builtins/timestamp.js +1 -0
- package/dist/semantic-types/index.d.ts +9 -0
- package/dist/semantic-types/index.js +1 -0
- package/dist/semantic-types/primal-types/date.d.ts +2 -0
- package/dist/semantic-types/primal-types/date.js +1 -0
- package/dist/semantic-types/primal-types/index.d.ts +2 -0
- package/dist/semantic-types/primal-types/index.js +1 -0
- package/dist/semantic-types/registry.d.ts +14 -0
- package/dist/semantic-types/registry.js +1 -0
- package/dist/semantic-types/resolve-schema.d.ts +4 -0
- package/dist/semantic-types/resolve-schema.js +1 -0
- package/dist/semantic-types/types.d.ts +90 -0
- package/dist/semantic-types/validate-resolved-schema.d.ts +6 -0
- package/dist/semantic-types/validate-resolved-schema.js +1 -0
- package/dist/utils/clone.d.ts +1 -0
- package/dist/utils/clone.js +1 -0
- package/package.json +62 -0
package/README.md
ADDED
|
@@ -0,0 +1,448 @@
|
|
|
1
|
+
# SemanticDB
|
|
2
|
+
|
|
3
|
+
SemanticDB translates its own LogicForm into SQL and executes it on a single core compute layer.
|
|
4
|
+
Callers provide schemas, schema mappings, operators, and database connection information.
|
|
5
|
+
SemanticDB creates the matching provider and owns its connection lifecycle.
|
|
6
|
+
|
|
7
|
+
## Public API
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
import { Logicform } from 'semanticdb-v2';
|
|
11
|
+
import { SchemaMappingRegistry } from 'semanticdb-v2';
|
|
12
|
+
|
|
13
|
+
const schemaMappings = new SchemaMappingRegistry([
|
|
14
|
+
{
|
|
15
|
+
schemaId: 'sales',
|
|
16
|
+
connectionId: 'analytics',
|
|
17
|
+
source: 'analytics.sales_detail',
|
|
18
|
+
freshness: { mode: 'ttl', ttlSeconds: 300 },
|
|
19
|
+
},
|
|
20
|
+
]);
|
|
21
|
+
|
|
22
|
+
const response = await Logicform.execute(
|
|
23
|
+
{
|
|
24
|
+
version: '2.0',
|
|
25
|
+
schema: 'sales',
|
|
26
|
+
groupby: [{
|
|
27
|
+
operator: '$dateBucket',
|
|
28
|
+
pred: 'order_date',
|
|
29
|
+
args: { granularity: 'month' },
|
|
30
|
+
}],
|
|
31
|
+
preds: [{ operator: '$sum', pred: 'amount', name: 'revenue' }],
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
connections: [{
|
|
35
|
+
id: 'analytics',
|
|
36
|
+
kind: 'clickhouse',
|
|
37
|
+
url: 'http://localhost:8123',
|
|
38
|
+
username: 'default',
|
|
39
|
+
password: process.env.CLICKHOUSE_PASSWORD,
|
|
40
|
+
database: 'analytics',
|
|
41
|
+
}],
|
|
42
|
+
schemas,
|
|
43
|
+
schemaMappings,
|
|
44
|
+
operators,
|
|
45
|
+
onProgress(event) {
|
|
46
|
+
console.log(event.stage, event);
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
);
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Every executable LogicForm, including LogicForms used as subqueries, must declare `version`. This
|
|
53
|
+
SDK accepts semantic versions greater than or equal to `2.0`; missing, malformed, or older versions
|
|
54
|
+
fail with `UNSUPPORTED_LOGICFORM_VERSION` before Schema resolution or database access. A relation
|
|
55
|
+
query shaped as `{ schema, query }` is part of its parent LogicForm and inherits the parent's version.
|
|
56
|
+
|
|
57
|
+
PredItems without an explicit `name` receive a human-readable name during normalization. Generated
|
|
58
|
+
names default to Chinese; pass `locale: 'en-US'` in `ExecuteOptions` for the built-in English
|
|
59
|
+
names. Every operator owns its localized default names and receives the requested locale directly:
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
operators.register({
|
|
63
|
+
name: '$weightedAverage',
|
|
64
|
+
defaultName: ({ operandName, locale }) => locale.toLowerCase().startsWith('en')
|
|
65
|
+
? `Weighted average of ${operandName}`
|
|
66
|
+
: `${operandName}加权平均值`,
|
|
67
|
+
kind: 'aggregate',
|
|
68
|
+
toSQL: ({ column }) => `weighted_avg(${column})`,
|
|
69
|
+
});
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
The same locale applies to `preds`, `groupby`, nested expressions, custom-function components,
|
|
73
|
+
and entity-population queries. A PredItem's normalized `query` is included in its
|
|
74
|
+
generated name, for example `门店为上海的销售额合计`. Operator-specific `args` also participate
|
|
75
|
+
through each operator's `defaultName()`; period operators distinguish同比、环比 and other offsets.
|
|
76
|
+
An explicitly authored `name` is preserved unchanged.
|
|
77
|
+
|
|
78
|
+
`entity_id` identifies one entity by `hierarchy.property` on hierarchy Schemas and by the unique
|
|
79
|
+
`type: 'ID'` property on other Schemas. Normalization adds that identity filter unless the query
|
|
80
|
+
already contains one, in which case the explicit query wins. The legacy value `placeholder` is
|
|
81
|
+
preserved as entity context but never becomes a SQL filter. This also applies to relation queries
|
|
82
|
+
shaped as `{ schema, query, entity_id }`. `representation` is presentation metadata for frontend
|
|
83
|
+
consumers and does not change normalization, planning, or execution. See
|
|
84
|
+
[Hierarchy plugin](docs/hierarchy-plugin.md) for Schema configuration, `$hierarchyLevel`, legacy
|
|
85
|
+
compatibility, and its plugin boundary.
|
|
86
|
+
|
|
87
|
+
Compound capabilities can be supplied by external packages through `ExecuteOptions.plugins`.
|
|
88
|
+
Plugins may own Schema metadata, legacy conversion, `entity_id` selection, and Operators while
|
|
89
|
+
remaining scoped to one execution. See [Register an external plugin](docs/register-plugin.md).
|
|
90
|
+
|
|
91
|
+
Parent-child Schemas may declare `parentProperty`. `$recursive` filters a property to a root node and
|
|
92
|
+
all descendants; object properties infer the tree Schema from `ref`. MySQL, Doris, StarRocks, and
|
|
93
|
+
PostgreSQL compile this as a parameterized recursive CTE.
|
|
94
|
+
|
|
95
|
+
Top-level projections may set `schema` to read metrics from another registered Schema. SemanticDB
|
|
96
|
+
splits those projections into independent provider queries, aligns shared query/groupby dimensions,
|
|
97
|
+
and merges rows by the group keys before applying `having`, `sort`, `limitBy`, and pagination.
|
|
98
|
+
|
|
99
|
+
The progress stream emits `started`, `validated`, `normalized`, `planned`, `lineage`,
|
|
100
|
+
`compiled`, `executing`, `hydrating`, `result`, and `completed`. Every event includes a cumulative
|
|
101
|
+
`partialResult`; for example, `compiled` includes `sqls`, while `result` includes `sqls` and
|
|
102
|
+
`result`. Failures emit `failed` before rejection and preserve the partial result accumulated so
|
|
103
|
+
far.
|
|
104
|
+
|
|
105
|
+
Each `sqls` item keeps the executable statement and its parameters together, plus a complete
|
|
106
|
+
SQL string for diagnostics:
|
|
107
|
+
|
|
108
|
+
```ts
|
|
109
|
+
interface ExecutedSql {
|
|
110
|
+
type: 'semantic-resolution' | 'computation';
|
|
111
|
+
sql: string;
|
|
112
|
+
parameters: readonly unknown[];
|
|
113
|
+
displaySql: string;
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
`semantic-resolution` identifies supporting SQL used to resolve semantic input or output, such as
|
|
118
|
+
latest-data-date lookup and object entity population. `computation` identifies SQL that calculates
|
|
119
|
+
the requested LogicForm result. `sqls` includes every physical SQL involved in the execution; an
|
|
120
|
+
entry remains visible when its result is served by the query cache.
|
|
121
|
+
|
|
122
|
+
Providers execute only parameterized `sql + parameters`; SemanticDB never executes `displaySql`.
|
|
123
|
+
Because `displaySql` contains literal values and may expose sensitive data, use it only in trusted
|
|
124
|
+
debugging and observability channels.
|
|
125
|
+
|
|
126
|
+
### Permissions
|
|
127
|
+
|
|
128
|
+
Pass table, row, and column permissions together through `ExecuteOptions.permissions`:
|
|
129
|
+
|
|
130
|
+
```ts
|
|
131
|
+
import type { Permissions } from 'semanticdb-v2';
|
|
132
|
+
|
|
133
|
+
const permissions = {
|
|
134
|
+
orders: {
|
|
135
|
+
rowPermissions: {
|
|
136
|
+
订单状态: { $ne: '已删除' },
|
|
137
|
+
所属部门: {
|
|
138
|
+
schema: 'departments',
|
|
139
|
+
query: {
|
|
140
|
+
部门名称: { $in: ['华东销售部', '华南销售部'] },
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
},
|
|
144
|
+
columnPermissions: {
|
|
145
|
+
whitelist: ['ID', '订单状态', '销售额', '所属部门', 'createdAt'],
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
departments: {
|
|
149
|
+
rowPermissions: {
|
|
150
|
+
部门名称: { $in: ['华东销售部', '华南销售部'] },
|
|
151
|
+
},
|
|
152
|
+
columnPermissions: {
|
|
153
|
+
whitelist: ['ID', '部门名称'],
|
|
154
|
+
},
|
|
155
|
+
},
|
|
156
|
+
} satisfies Permissions;
|
|
157
|
+
|
|
158
|
+
await Logicform.execute(logicform, {
|
|
159
|
+
schemas,
|
|
160
|
+
schemaMappings,
|
|
161
|
+
connection,
|
|
162
|
+
permissions,
|
|
163
|
+
});
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
When `permissions` is omitted, execution remains unrestricted. When it is present, its Schema keys
|
|
167
|
+
are the table allowlist. `rowPermissions` is always combined with the caller query using `AND`,
|
|
168
|
+
including internal latest-data-date queries, related-table joins, and object population queries.
|
|
169
|
+
`columnPermissions.whitelist` accepts property names or IDs and restricts caller projections,
|
|
170
|
+
filters, grouping, operator inputs, and relation paths. A row policy may still use a non-whitelisted
|
|
171
|
+
column as an internal filter. If an ID is not whitelisted, SemanticDB generates the executor-owned
|
|
172
|
+
result-row `_id` instead of exposing the physical ID as that identity.
|
|
173
|
+
|
|
174
|
+
### Custom composite metrics
|
|
175
|
+
|
|
176
|
+
Pass legacy-compatible `genComponents`, `mergeComponents`, and `genMergeSQL` definitions through
|
|
177
|
+
`ExecuteOptions.customFunctions`. JavaScript merge supports async callbacks; SQL merge compiles
|
|
178
|
+
components into CTEs. `Logicform.genComponents()` expands the same definitions into an indicator
|
|
179
|
+
tree without accessing a database. Cross-Schema components independently pass table, row, and
|
|
180
|
+
column permissions. See [Custom composite metrics](docs/custom-functions.md) for the complete
|
|
181
|
+
contract, merge-mode selection, and trusted function-string behavior.
|
|
182
|
+
|
|
183
|
+
The root entry point accepts a `connections` array of `DatabaseConnection` items. Every item has a unique `id`; set `kind` to `mysql`,
|
|
184
|
+
`doris`, `starrocks`, `clickhouse`, `snowflake`, `oracle`, or `postgresql`, then add that driver's
|
|
185
|
+
native connection fields directly on the item. Each Schema Mapping selects an item through
|
|
186
|
+
`connectionId`. SemanticDB lazily creates and reuses the matching internal provider. Call
|
|
187
|
+
`await Logicform.close()` during application shutdown to close every connection pool and session.
|
|
188
|
+
|
|
189
|
+
### PostgreSQL connection
|
|
190
|
+
|
|
191
|
+
PostgreSQL uses the official `pg` pool and PostgreSQL-native `$1`, `$2` parameters:
|
|
192
|
+
|
|
193
|
+
```ts
|
|
194
|
+
await Logicform.execute(logicform, {
|
|
195
|
+
schemas,
|
|
196
|
+
schemaMappings,
|
|
197
|
+
connections: [{
|
|
198
|
+
id: 'primary',
|
|
199
|
+
kind: 'postgresql',
|
|
200
|
+
connectionString: process.env.DATABASE_URL,
|
|
201
|
+
}],
|
|
202
|
+
});
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
### Doris and StarRocks connections
|
|
206
|
+
|
|
207
|
+
Doris and StarRocks expose MySQL-compatible protocols, so both use `mysql2` connection fields:
|
|
208
|
+
|
|
209
|
+
```ts
|
|
210
|
+
const doris = { id: 'doris-main', kind: 'doris', host, port, user, password, database } as const;
|
|
211
|
+
const starrocks = { id: 'starrocks-main', kind: 'starrocks', host, port, user, password, database } as const;
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
They are independent providers with distinct `kind` values. Their currently compatible identifier,
|
|
215
|
+
pagination, date, aggregate, string, regex, parameter, and display-SQL behavior comes from a shared
|
|
216
|
+
MySQL-compatible SQL helper. Database-specific differences should be implemented by overriding the
|
|
217
|
+
corresponding internal dialect capability.
|
|
218
|
+
|
|
219
|
+
### Snowflake connection
|
|
220
|
+
|
|
221
|
+
Snowflake connection fields are passed to the official `snowflake-sdk`:
|
|
222
|
+
|
|
223
|
+
```ts
|
|
224
|
+
await Logicform.execute(logicform, {
|
|
225
|
+
schemas,
|
|
226
|
+
schemaMappings,
|
|
227
|
+
connections: [{
|
|
228
|
+
id: 'warehouse',
|
|
229
|
+
kind: 'snowflake',
|
|
230
|
+
account: process.env.SNOWFLAKE_ACCOUNT,
|
|
231
|
+
username: process.env.SNOWFLAKE_USERNAME,
|
|
232
|
+
password: process.env.SNOWFLAKE_PASSWORD,
|
|
233
|
+
warehouse: process.env.SNOWFLAKE_WAREHOUSE,
|
|
234
|
+
database: process.env.SNOWFLAKE_DATABASE,
|
|
235
|
+
schema: process.env.SNOWFLAKE_SCHEMA,
|
|
236
|
+
role: process.env.SNOWFLAKE_ROLE,
|
|
237
|
+
}],
|
|
238
|
+
});
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
### Oracle connection
|
|
242
|
+
|
|
243
|
+
Oracle uses the official `oracledb` driver in Thin mode and does not require Oracle Client
|
|
244
|
+
libraries:
|
|
245
|
+
|
|
246
|
+
```ts
|
|
247
|
+
await Logicform.execute(logicform, {
|
|
248
|
+
schemas,
|
|
249
|
+
schemaMappings,
|
|
250
|
+
connections: [{
|
|
251
|
+
id: 'oracle-main',
|
|
252
|
+
kind: 'oracle',
|
|
253
|
+
user: process.env.ORACLE_USER,
|
|
254
|
+
password: process.env.ORACLE_PASSWORD,
|
|
255
|
+
connectString: process.env.ORACLE_CONNECT_STRING,
|
|
256
|
+
}],
|
|
257
|
+
});
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
### Semantic types
|
|
261
|
+
|
|
262
|
+
`Schema.properties[].type` is an extensible semantic type. SemanticDB supplies the built-in
|
|
263
|
+
types and derives each property's `primal_type` while resolving the Schema. A supplied
|
|
264
|
+
`primal_type` must match the registered type. `isArray` describes the value container and
|
|
265
|
+
defaults to `false`; for example, tags are represented as `type: 'category', isArray: true`.
|
|
266
|
+
|
|
267
|
+
```ts
|
|
268
|
+
import { createSemanticTypeRegistry, Logicform } from 'semanticdb-v2';
|
|
269
|
+
|
|
270
|
+
const semanticTypes = createSemanticTypeRegistry();
|
|
271
|
+
semanticTypes.register({
|
|
272
|
+
name: 'email',
|
|
273
|
+
primalType: 'string',
|
|
274
|
+
validateProperty(property) {
|
|
275
|
+
if (property.isArray) throw new Error('email must be scalar');
|
|
276
|
+
},
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
await Logicform.execute(logicform, { schemas, schemaMappings, connection, semanticTypes });
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
Registries are instance-local. Applications can reuse one registry across executions without
|
|
283
|
+
introducing global mutable registration.
|
|
284
|
+
|
|
285
|
+
See [Register a custom SemanticType](docs/register-semantic-type.md) for the complete external
|
|
286
|
+
registration API, validation lifecycle, and Schema example.
|
|
287
|
+
|
|
288
|
+
### SQL-derived properties
|
|
289
|
+
|
|
290
|
+
A Property can map to a trusted, provider-specific SQL expression instead of one physical column:
|
|
291
|
+
|
|
292
|
+
```ts
|
|
293
|
+
{
|
|
294
|
+
id: 'net_amount',
|
|
295
|
+
name: 'Net amount',
|
|
296
|
+
type: 'currency',
|
|
297
|
+
udf: { sql: 'COALESCE(amount, 0) - COALESCE(discount, 0)' },
|
|
298
|
+
}
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
`udf.sql` is used consistently for projection, filtering, grouping, operator input, and relation
|
|
302
|
+
keys. In an event Schema, `$TS` expands to the quoted physical timestamp column. The expression is
|
|
303
|
+
trusted model metadata, must match the active provider dialect, and cannot contain statements,
|
|
304
|
+
comments, or parameter placeholders. Property UDFs do not support the legacy `function` or
|
|
305
|
+
`dependencies` fields; application-side computed values belong outside this query SDK.
|
|
306
|
+
|
|
307
|
+
### Query cache
|
|
308
|
+
|
|
309
|
+
All compiled provider queries, including the internal latest-data-date query, can share one
|
|
310
|
+
caller-owned query cache. Redis is published from `semanticdb-v2/cache/redis`; cache identity
|
|
311
|
+
includes namespace, provider, SQL, and parameters. Physical expiration belongs to each Schema
|
|
312
|
+
mapping's `freshness` policy. See [Query cache and Redis](docs/query-cache.md).
|
|
313
|
+
|
|
314
|
+
When Redis is shared with other applications, pass an application-specific key prefix:
|
|
315
|
+
|
|
316
|
+
```ts
|
|
317
|
+
import { createRedisQueryCache } from 'semanticdb-v2/cache/redis';
|
|
318
|
+
|
|
319
|
+
const cache = await createRedisQueryCache({
|
|
320
|
+
url: process.env.REDIS_URL,
|
|
321
|
+
keyPrefix: process.env.REDIS_KEY_PREFIX,
|
|
322
|
+
});
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
Operators convert SQL expressions and declare the semantic type of their single output column. See
|
|
326
|
+
[LogicForm operators](docs/operators.md) for the built-in syntax and composition rules, and
|
|
327
|
+
[Register an operator](docs/register-operator.md) for the extension contract.
|
|
328
|
+
|
|
329
|
+
Schema and Property metadata use only `id`; neither defines `sid`. See the
|
|
330
|
+
[breaking-change migration checklist](docs/breaking-change-schema-property-id.md) before
|
|
331
|
+
updating an upstream Schema producer or downstream consumer.
|
|
332
|
+
|
|
333
|
+
## Architecture
|
|
334
|
+
|
|
335
|
+
The execution pipeline is split into explicit layers:
|
|
336
|
+
|
|
337
|
+
```text
|
|
338
|
+
LogicForm
|
|
339
|
+
-> normalizer validates and canonicalizes the request
|
|
340
|
+
-> planner builds a provider-independent LogicalPlan and lineage
|
|
341
|
+
-> compiler renders the LogicalPlan through the provider contract
|
|
342
|
+
-> physical executes SQL through a concrete database provider
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
`executor` is the application layer that coordinates the pipeline and publishes progress
|
|
346
|
+
events. `semantic-types` validates and resolves Schema property types before normalization;
|
|
347
|
+
`schema` owns Schema lookup and logical field mapping, while `schema-mappings` owns the
|
|
348
|
+
physical source bindings. Each layer owns its types, while the provider contract and concrete
|
|
349
|
+
database adapters live in `physical`. Physical adapters do not own normalization,
|
|
350
|
+
planning, or compilation rules.
|
|
351
|
+
|
|
352
|
+
After physical execution, the executor's Result Hydrator runs SemanticType result hooks and
|
|
353
|
+
batch-populates directly selected object properties. See [Result Hydrator](docs/result-hydrator.md).
|
|
354
|
+
|
|
355
|
+
Each layer exposes an `index.ts` boundary. Cross-layer imports should use that boundary rather
|
|
356
|
+
than reach into another layer's implementation files.
|
|
357
|
+
|
|
358
|
+
See [LogicForm to SQL pipeline](docs/logicform-to-sql-pipeline.md) for the responsibility and
|
|
359
|
+
input/output contract of every stage.
|
|
360
|
+
|
|
361
|
+
The [legacy test migration report](docs/legacy-test-migration.md) records how the old project's
|
|
362
|
+
test suite maps to this rewrite, including migrated behavior and explicit non-migration reasons.
|
|
363
|
+
|
|
364
|
+
Scalar object properties can be traversed with paths such as `门店_城市`. The planner resolves
|
|
365
|
+
these paths into provider-independent LEFT JOIN plans. See
|
|
366
|
+
[Object relations and LEFT JOIN](docs/object-relations.md).
|
|
367
|
+
|
|
368
|
+
## MySQL test configuration
|
|
369
|
+
|
|
370
|
+
The test suite includes a real MySQL execution test. Configure it through the project `.env`:
|
|
371
|
+
|
|
372
|
+
```bash
|
|
373
|
+
cp .env.example .env
|
|
374
|
+
npm test
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
The supported variables are `TEST_MYSQL_HOST`, `TEST_MYSQL_PORT`, `TEST_MYSQL_USER`,
|
|
378
|
+
`TEST_MYSQL_PASSWORD`, and `TEST_MYSQL_DATABASE`. The test creates an isolated table, verifies
|
|
379
|
+
the returned aggregates, and removes the table afterward. If `TEST_MYSQL_DATABASE` is omitted,
|
|
380
|
+
it creates and reuses the isolated `semanticdb_v2_integration` database.
|
|
381
|
+
|
|
382
|
+
## PostgreSQL test configuration
|
|
383
|
+
|
|
384
|
+
Set `TEST_POSTGRESQL_URL` in `.env` to enable the real PostgreSQL integration test. It creates an
|
|
385
|
+
isolated table in the configured database, verifies parameterized execution and LogicForm
|
|
386
|
+
aggregation, and removes the table afterward.
|
|
387
|
+
|
|
388
|
+
## Redis test configuration
|
|
389
|
+
|
|
390
|
+
The test suite also includes a real Redis integration test. Configure the connection in `.env`:
|
|
391
|
+
|
|
392
|
+
```bash
|
|
393
|
+
REDIS_URL=redis://localhost:6379/0
|
|
394
|
+
REDIS_KEY_PREFIX=semanticdb-v2
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
`REDIS_KEY_PREFIX` is the application namespace and should be unique when Redis is shared. The
|
|
398
|
+
test appends its own process ID and UUID, verifies real expiry and source-index behavior, and
|
|
399
|
+
removes its keys afterward. It is skipped only when `REDIS_URL` is absent; a configured but
|
|
400
|
+
unreachable Redis server fails the test.
|
|
401
|
+
|
|
402
|
+
## Snowflake test configuration
|
|
403
|
+
|
|
404
|
+
The ordinary test suite includes a real Snowflake provider and LogicForm execution test. Uncomment
|
|
405
|
+
and fill the `TEST_SNOWFLAKE_*` variables in `.env`; `ACCOUNT`, `USERNAME`, `PASSWORD`, `WAREHOUSE`,
|
|
406
|
+
and `DATABASE` are required, while `SCHEMA` and `ROLE` are optional. The test only reads
|
|
407
|
+
`INFORMATION_SCHEMA.TABLES`. It is skipped when the required configuration is absent and fails when
|
|
408
|
+
configured credentials cannot connect.
|
|
409
|
+
|
|
410
|
+
## Oracle test configuration
|
|
411
|
+
|
|
412
|
+
The ordinary test suite includes real Oracle provider and LogicForm execution tests. Uncomment
|
|
413
|
+
and fill `TEST_ORACLE_USER`, `TEST_ORACLE_PASSWORD`, and `TEST_ORACLE_CONNECT_STRING` in `.env`.
|
|
414
|
+
Thin mode is the default. To test Thick mode, set `TEST_ORACLE_MODE=thick` and optionally set
|
|
415
|
+
`TEST_ORACLE_CLIENT_LIB_DIR` and `TEST_ORACLE_CONFIG_DIR`. The tests query Oracle's `DUAL` table;
|
|
416
|
+
they are skipped when required connection values are absent and fail when configured Oracle
|
|
417
|
+
credentials, client libraries, or connectivity are invalid.
|
|
418
|
+
|
|
419
|
+
## Debugging the pipeline
|
|
420
|
+
|
|
421
|
+
[`debug/debug.ts`](debug/debug.ts) prepares an isolated MySQL table and executes one complete
|
|
422
|
+
LogicForm pipeline against it. It uses the same `TEST_MYSQL_*` variables from `.env` as the
|
|
423
|
+
integration tests.
|
|
424
|
+
|
|
425
|
+
In Trae or VS Code, open **Run and Debug**, select **Debug SemanticDB pipeline**, set a breakpoint
|
|
426
|
+
on the `Logicform.execute()` call or inside `src/`, and start debugging. The same entry point can
|
|
427
|
+
be run without the IDE:
|
|
428
|
+
|
|
429
|
+
```bash
|
|
430
|
+
npm run debug
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
## Current scope
|
|
434
|
+
|
|
435
|
+
- ClickHouse, MySQL, Oracle, and Snowflake core compute providers
|
|
436
|
+
- Projections, conditional aggregates, and `$sum`, `$count`, `$countDistinct`, `$avg`, `$min`, `$max`
|
|
437
|
+
- Structured arithmetic, share-of-total, period comparison, period-boundary, date-bucket,
|
|
438
|
+
cumulative period, trusted SQL expression, string, case, and window operators
|
|
439
|
+
- Declarative operators
|
|
440
|
+
- Grouping with hour/day/week/month/quarter/year time buckets
|
|
441
|
+
- `$eq`, `$ne`, `$gt`, `$gte`, `$lt`, `$lte`, `$in`, `$nin`, `$exists`, `$contains`, `$regex`
|
|
442
|
+
- `$and` and `$or`, HAVING, sorting, row-count and percentage pagination, and per-group `limitBy`
|
|
443
|
+
- Query-level and output-column lineage
|
|
444
|
+
- Multi-level scalar and array object relation paths, array groupby, and batched entity population
|
|
445
|
+
- Nested `from` LogicForms compiled as parameterized derived tables
|
|
446
|
+
|
|
447
|
+
Exports and metadata persistence are intentionally deferred. Directly selected array-object
|
|
448
|
+
properties are populated without expanding the root result rows.
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { SourceFreshnessPolicy } from '../schema-mappings/index';
|
|
2
|
+
export declare const nextDailyExpiration: (policy: Extract<SourceFreshnessPolicy, {
|
|
3
|
+
readonly mode: "daily";
|
|
4
|
+
}>, now: Date) => Date;
|
|
5
|
+
export declare const resolveQueryExpiration: (policies: readonly SourceFreshnessPolicy[], now: Date) => Date | undefined;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{parseDailyExpireAt as s}from"../schema-mappings/freshness.js";const i=(n,o)=>{const r=new Intl.DateTimeFormat("en-US",{timeZone:o,year:"numeric",month:"2-digit",day:"2-digit",hour:"2-digit",minute:"2-digit",second:"2-digit",hourCycle:"h23"}).formatToParts(n),e=a=>Number(r.find(t=>t.type===a)?.value);return{year:e("year"),month:e("month"),day:e("day"),hour:e("hour"),minute:e("minute"),second:e("second")}},m=(n,o)=>{const r=Date.UTC(n.year,n.month-1,n.day,n.hour,n.minute,n.second);let e=r;for(let a=0;a<3;a+=1){const t=i(new Date(e),o),u=Date.UTC(t.year,t.month-1,t.day,t.hour,t.minute,t.second);e+=r-u}return new Date(e)},d=(n,o)=>{const r=s(n.expireAt);if(!r)throw new Error(`Invalid daily expiration time: ${n.expireAt}`);if(!n.timeZone){const t=new Date(o);return t.setHours(r.hour,r.minute,r.second,0),t.getTime()<=o.getTime()&&t.setDate(t.getDate()+1),t}const e=i(o,n.timeZone);let a=m({...e,...r},n.timeZone);if(a.getTime()<=o.getTime()){const t=new Date(Date.UTC(e.year,e.month-1,e.day+1));a=m({year:t.getUTCFullYear(),month:t.getUTCMonth()+1,day:t.getUTCDate(),...r},n.timeZone)}return a},h=(n,o)=>{const r=n.flatMap(e=>e.mode==="manual"?[]:e.mode==="ttl"?[new Date(o.getTime()+e.ttlSeconds*1e3)]:[d(e,o)]);return r.length===0?void 0:new Date(Math.min(...r.map(e=>e.getTime())))};export{d as nextDailyExpiration,h as resolveQueryExpiration};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{createHash as s}from"node:crypto";const n=e=>e===void 0?{type:"undefined"}:typeof e=="bigint"?{type:"bigint",value:e.toString()}:typeof e=="number"&&!Number.isFinite(e)?{type:"number",value:String(e)}:e instanceof Date?{type:"date",value:e.toISOString()}:Array.isArray(e)?e.map(n):e&&typeof e=="object"?Object.fromEntries(Object.entries(e).sort(([r],[t])=>r.localeCompare(t)).map(([r,t])=>[r,n(t)])):e,i=e=>s("sha256").update(JSON.stringify(n(e))).digest("hex"),o=e=>i({namespace:e.namespace,provider:e.provider,sql:e.sql,parameters:e.parameters}),p=(e,r)=>i({namespace:e,source:r});export{o as queryCacheIdentityHash,p as sourceCacheIndexHash};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { QueryCache, QueryCacheIdentity, QueryCacheWriteOptions } from './types';
|
|
2
|
+
import type { DataRow } from '../physical/index';
|
|
3
|
+
export interface RedisMultiLike {
|
|
4
|
+
set(key: string, value: string, options?: {
|
|
5
|
+
readonly PXAT: number;
|
|
6
|
+
}): this;
|
|
7
|
+
zRemRangeByScore(key: string, min: number, max: number): this;
|
|
8
|
+
zAdd(key: string, entry: {
|
|
9
|
+
readonly score: number;
|
|
10
|
+
readonly value: string;
|
|
11
|
+
}): this;
|
|
12
|
+
exec(): Promise<unknown>;
|
|
13
|
+
}
|
|
14
|
+
export interface RedisQueryCacheClient {
|
|
15
|
+
get(key: string): Promise<string | null>;
|
|
16
|
+
multi(): RedisMultiLike;
|
|
17
|
+
zRange(key: string, start: number, stop: number): Promise<string[]>;
|
|
18
|
+
del(keys: string | string[]): Promise<number>;
|
|
19
|
+
close(): Promise<unknown>;
|
|
20
|
+
}
|
|
21
|
+
export interface RedisQueryCacheOptions {
|
|
22
|
+
readonly url?: string;
|
|
23
|
+
readonly keyPrefix?: string;
|
|
24
|
+
}
|
|
25
|
+
/** Redis implementation of the provider-independent QueryCache contract. */
|
|
26
|
+
export declare class RedisQueryCache implements QueryCache {
|
|
27
|
+
#private;
|
|
28
|
+
constructor(client: RedisQueryCacheClient, options?: {
|
|
29
|
+
readonly keyPrefix?: string;
|
|
30
|
+
readonly ownsClient?: boolean;
|
|
31
|
+
});
|
|
32
|
+
get(identity: QueryCacheIdentity): Promise<readonly DataRow[] | undefined>;
|
|
33
|
+
set(identity: QueryCacheIdentity, rows: readonly DataRow[], options: QueryCacheWriteOptions): Promise<void>;
|
|
34
|
+
invalidateSource(namespace: string, source: string): Promise<void>;
|
|
35
|
+
close(): Promise<void>;
|
|
36
|
+
}
|
|
37
|
+
export declare const createRedisQueryCache: (options?: RedisQueryCacheOptions) => Promise<RedisQueryCache>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{createClient as y}from"redis";import{queryCacheIdentityHash as d,sourceCacheIndexHash as f}from"./identity.js";import{deserializeQueryRows as w,serializeQueryRows as x}from"./serialization.js";const m=Number.MAX_SAFE_INTEGER;class u{#e;#t;#s;constructor(e,t={}){this.#e=e,this.#t=t.keyPrefix??"semanticdb:cache",this.#s=t.ownsClient??!1}#r(e){return`${this.#t}:query:${d(e)}`}#i(e,t){return`${this.#t}:source:${f(e,t)}`}async get(e){const t=await this.#e.get(this.#r(e));if(t!==null)return w(t)}async set(e,t,i){const s=this.#r(e),a=x(t),c=i.expiresAt?.getTime(),l=c??m,n=this.#e.multi();c===void 0?n.set(s,a):n.set(s,a,{PXAT:c});for(const h of new Set(i.sources)){const o=this.#i(e.namespace,h);n.zRemRangeByScore(o,0,Date.now()),n.zAdd(o,{score:l,value:s})}await n.exec()}async invalidateSource(e,t){const i=this.#i(e,t),s=await this.#e.zRange(i,0,-1);s.length>0&&await this.#e.del(s),await this.#e.del(i)}async close(){this.#s&&await this.#e.close()}}const C=async(r={})=>{const e=y(r.url?{url:r.url}:{});return e.on("error",()=>{}),await e.connect(),new u(e,{...r.keyPrefix?{keyPrefix:r.keyPrefix}:{},ownsClient:!0})};export{u as RedisQueryCache,C as createRedisQueryCache};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const n=e=>e===void 0?{type:"undefined"}:typeof e=="bigint"?{type:"bigint",value:e.toString()}:typeof e=="number"&&!Number.isFinite(e)?{type:"number",value:String(e)}:e instanceof Date?{type:"date",value:e.toISOString()}:Array.isArray(e)?{type:"array",value:e.map(n)}:e&&typeof e=="object"?{type:"object",value:Object.entries(e).map(([r,t])=>[r,n(t)])}:e,i=e=>{if(e===null||typeof e!="object")return e;switch(e.type){case"undefined":return;case"bigint":return BigInt(e.value);case"number":return Number(e.value);case"date":return new Date(e.value);case"array":return e.value.map(i);case"object":return Object.fromEntries(e.value.map(([r,t])=>[r,i(t)]))}},s=e=>JSON.stringify(n(e)),o=e=>{const r=i(JSON.parse(e));if(!(!Array.isArray(r)||r.some(t=>!t||typeof t!="object"||Array.isArray(t))))return r};export{o as deserializeQueryRows,s as serializeQueryRows};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { CoreComputeProvider, DataRow, SqlParameters } from '../physical/index';
|
|
2
|
+
export interface QueryCacheIdentity {
|
|
3
|
+
readonly namespace: string;
|
|
4
|
+
readonly provider: CoreComputeProvider['kind'];
|
|
5
|
+
readonly sql: string;
|
|
6
|
+
readonly parameters: SqlParameters;
|
|
7
|
+
}
|
|
8
|
+
export interface QueryCacheWriteOptions {
|
|
9
|
+
readonly sources: readonly string[];
|
|
10
|
+
/** Omitted for manually invalidated entries. */
|
|
11
|
+
readonly expiresAt?: Date;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Cross-execution cache for compiled provider queries. Implementations must
|
|
15
|
+
* treat SQL parameters and the data-source namespace as part of the identity.
|
|
16
|
+
*/
|
|
17
|
+
export interface QueryCache {
|
|
18
|
+
get(identity: QueryCacheIdentity): Promise<readonly DataRow[] | undefined>;
|
|
19
|
+
set(identity: QueryCacheIdentity, rows: readonly DataRow[], options: QueryCacheWriteOptions): Promise<void>;
|
|
20
|
+
invalidateSource(namespace: string, source: string): Promise<void>;
|
|
21
|
+
close(): Promise<void>;
|
|
22
|
+
}
|
|
23
|
+
export interface QueryCacheExecutionOptions {
|
|
24
|
+
readonly store: QueryCache;
|
|
25
|
+
/** Isolates tenants and physical data sources that can produce identical SQL. */
|
|
26
|
+
readonly namespace: string;
|
|
27
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/** Fixed internal physical table names. These are intentionally not configurable yet. */
|
|
2
|
+
export declare const CALENDAR_DATES_TABLE = "semanticdb_calendar_dates";
|
|
3
|
+
export declare const CALENDAR_RELATIONS_TABLE = "semanticdb_calendar_relations";
|
|
4
|
+
export declare const CALENDAR_SYSTEM_SCHEMA_IDS: {
|
|
5
|
+
readonly dates: "__semanticdb_calendar_dates";
|
|
6
|
+
readonly relations: "__semanticdb_calendar_relations";
|
|
7
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const _="semanticdb_calendar_dates",a="semanticdb_calendar_relations",e={dates:"__semanticdb_calendar_dates",relations:"__semanticdb_calendar_relations"};export{_ as CALENDAR_DATES_TABLE,a as CALENDAR_RELATIONS_TABLE,e as CALENDAR_SYSTEM_SCHEMA_IDS};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { CALENDAR_DATES_TABLE, CALENDAR_RELATIONS_TABLE, CALENDAR_SYSTEM_SCHEMA_IDS, } from './constants';
|
|
2
|
+
export { resolveCalendarPeriod, resolveCalendarPeriodFromStore, selectCalendarDates, } from './resolver';
|
|
3
|
+
export type { CalendarRelationStore } from './resolver';
|
|
4
|
+
export { ProviderCalendarRelationStore } from './provider-relation-store';
|
|
5
|
+
export { calendarSystemSchemas } from './system-schemas';
|
|
6
|
+
export type { CalendarAlignment, CalendarComparison, CalendarDateRow, CalendarRelation, CalendarRelationRow, ResolvedCalendarPeriod, } from './types';
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { CoreComputeProvider } from '../physical/index';
|
|
2
|
+
import type { CalendarRelationStore } from './resolver';
|
|
3
|
+
import type { CalendarComparison, CalendarRelationRow } from './types';
|
|
4
|
+
/** Loads calendar mappings from the fixed internal relations table. */
|
|
5
|
+
export declare class ProviderCalendarRelationStore implements CalendarRelationStore {
|
|
6
|
+
private readonly provider;
|
|
7
|
+
constructor(provider: CoreComputeProvider);
|
|
8
|
+
loadRelations(currentDateIds: readonly (string | number)[], comparison: CalendarComparison): Promise<readonly CalendarRelationRow[]>;
|
|
9
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{CALENDAR_RELATIONS_TABLE as s}from"./constants.js";const g=["source_date_id","target_date_id","calendar","grain","relation","relation_key"];class _{provider;constructor(t){this.provider=t}async loadRelations(t,i){if(t.length===0)return[];const n=[...t,i.calendar,i.grain,i.relation];i.key!==void 0&&n.push(i.key);const l=t.map((e,d)=>this.provider.parameter(d,e)),a=e=>this.provider.parameter(e,n[e]),r=e=>this.provider.quoteIdentifier(e),o=[`${r("source_date_id")} IN (${l.join(", ")})`,`${r("calendar")} = ${a(t.length)}`,`${r("grain")} = ${a(t.length+1)}`,`${r("relation")} = ${a(t.length+2)}`];return i.key!==void 0&&o.push(`${r("relation_key")} = ${a(t.length+3)}`),(await this.provider.execute({sql:`SELECT ${g.map(r).join(", ")} FROM ${r(s)} WHERE ${o.join(" AND ")}`,parameters:n})).map(e=>({sourceDateId:e.source_date_id,targetDateId:e.target_date_id,calendar:String(e.calendar),grain:String(e.grain),relation:String(e.relation),...typeof e.relation_key=="string"||typeof e.relation_key=="number"?{key:String(e.relation_key)}:{}}))}}export{_ as ProviderCalendarRelationStore};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { CalendarComparison, CalendarDateRow, CalendarRelationRow, ResolvedCalendarPeriod } from './types';
|
|
2
|
+
export interface CalendarRelationStore {
|
|
3
|
+
loadRelations(currentDateIds: readonly (string | number)[], comparison: CalendarComparison): Promise<readonly CalendarRelationRow[]>;
|
|
4
|
+
}
|
|
5
|
+
/** Resolve current natural-day IDs through an explicit calendar relation. */
|
|
6
|
+
export declare const resolveCalendarPeriod: (currentDateIds: readonly (string | number)[], comparison: CalendarComparison, relations: readonly CalendarRelationRow[]) => ResolvedCalendarPeriod;
|
|
7
|
+
/** Async resolver boundary used by the executor; storage remains provider-owned. */
|
|
8
|
+
export declare const resolveCalendarPeriodFromStore: (currentDateIds: readonly (string | number)[], comparison: CalendarComparison, store: CalendarRelationStore) => Promise<ResolvedCalendarPeriod>;
|
|
9
|
+
/** Select natural days by calendar coordinates before resolving a relation. */
|
|
10
|
+
export declare const selectCalendarDates: (rows: readonly CalendarDateRow[], predicate: Partial<Pick<CalendarDateRow, "calendar" | "grain" | "periodKey" | "relativeDay">>) => readonly (string | number)[];
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const l=n=>`${typeof n}:${String(n)}`,p=(n,t)=>{const r=typeof n=="number"?n:Number(n),o=typeof t=="number"?t:Number(t);return Number.isFinite(r)&&Number.isFinite(o)?r-o:String(n).localeCompare(String(t))},m=(n,t,r)=>{const o=r.filter(e=>e.calendar===t.calendar&&e.grain===t.grain&&e.relation===t.relation&&(t.key===void 0||e.key===t.key)),d=new Map(o.map(e=>[l(e.sourceDateId),e])),b=n.flatMap(e=>{const a=d.get(l(e))?.targetDateId;return a==null?[]:[a]}),f=[...new Map(b.map(e=>[l(e),e])).values()].sort(p),s=[];for(const e of f){const a=s[s.length-1];if(a&&typeof a.end=="number"&&typeof e=="number"&&e===a.end+1)a.end=e;else if(a&&String(a.end).match(/^\d{8}$/)&&String(e).match(/^\d{8}$/)){const i=Number(a.end),c=Number(e),u=new Date(Date.UTC(Math.floor(i/1e4),Math.floor(i/100)%100-1,i%100));u.setUTCDate(u.getUTCDate()+1);const g=new Date(Date.UTC(Math.floor(c/1e4),Math.floor(c/100)%100-1,c%100));if(u.getTime()===g.getTime()){a.end=e;continue}s.push({start:e,end:e})}else s.push({start:e,end:e})}return{dateIds:f,ranges:s}},y=async(n,t,r)=>m(n,t,await r.loadRelations(n,t)),D=(n,t)=>n.filter(r=>Object.entries(t).every(([o,d])=>r[o]===d)).map(r=>r.dateId);export{m as resolveCalendarPeriod,y as resolveCalendarPeriodFromStore,D as selectCalendarDates};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{CALENDAR_RELATIONS_TABLE as t,CALENDAR_SYSTEM_SCHEMA_IDS as e,CALENDAR_DATES_TABLE as i}from"./constants.js";const r={[e.dates]:{id:e.dates,name:e.dates,type:"entity",source:i,properties:[{id:"date_id",name:"date_id",type:"id",primal_type:"string"},{id:"calendar",name:"calendar",type:"string",primal_type:"string"},{id:"grain",name:"grain",type:"string",primal_type:"string"},{id:"period_key",name:"period_key",type:"string",primal_type:"string"},{id:"relative_day",name:"relative_day",type:"int",primal_type:"number"}]},[e.relations]:{id:e.relations,name:e.relations,type:"entity",source:t,properties:[{id:"source_date_id",name:"source_date_id",type:"string",primal_type:"string"},{id:"target_date_id",name:"target_date_id",type:"string",primal_type:"string"},{id:"calendar",name:"calendar",type:"string",primal_type:"string"},{id:"grain",name:"grain",type:"string",primal_type:"string"},{id:"relation",name:"relation",type:"string",primal_type:"string"},{id:"relation_key",name:"relation_key",type:"string",primal_type:"string"}]}};export{r as calendarSystemSchemas};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export type CalendarRelation = 'previous_period' | 'next_period' | 'previous_occurrence' | 'next_occurrence';
|
|
2
|
+
export type CalendarAlignment = 'same_period' | 'same_elapsed_position' | 'same_relative_day';
|
|
3
|
+
export interface CalendarComparison {
|
|
4
|
+
readonly calendar: string;
|
|
5
|
+
readonly grain: string;
|
|
6
|
+
readonly relation: CalendarRelation;
|
|
7
|
+
readonly alignment?: CalendarAlignment;
|
|
8
|
+
readonly key?: string;
|
|
9
|
+
}
|
|
10
|
+
export interface CalendarDateRow {
|
|
11
|
+
readonly dateId: string | number;
|
|
12
|
+
readonly calendar: string;
|
|
13
|
+
readonly grain: string;
|
|
14
|
+
readonly periodKey?: string | number;
|
|
15
|
+
readonly relativeDay?: number;
|
|
16
|
+
}
|
|
17
|
+
export interface CalendarRelationRow {
|
|
18
|
+
readonly sourceDateId: string | number;
|
|
19
|
+
readonly targetDateId: string | number | null;
|
|
20
|
+
readonly calendar: string;
|
|
21
|
+
readonly grain: string;
|
|
22
|
+
readonly relation: CalendarRelation;
|
|
23
|
+
readonly key?: string;
|
|
24
|
+
}
|
|
25
|
+
export interface ResolvedCalendarPeriod {
|
|
26
|
+
readonly dateIds: readonly (string | number)[];
|
|
27
|
+
readonly ranges: readonly {
|
|
28
|
+
readonly start: string | number;
|
|
29
|
+
readonly end: string | number;
|
|
30
|
+
}[];
|
|
31
|
+
}
|