@uptimizr/db 0.8.2 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/env.d.ts +57 -2
- package/dist/env.d.ts.map +1 -1
- package/dist/env.js +31 -2
- package/dist/env.js.map +1 -1
- package/dist/index.d.ts +6 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -1
- package/dist/query/aggregations.d.ts +4 -4
- package/dist/query/aggregations.d.ts.map +1 -1
- package/dist/query/aggregations.js +200 -143
- package/dist/query/aggregations.js.map +1 -1
- package/dist/query/clickhouseDialect.d.ts.map +1 -1
- package/dist/query/clickhouseDialect.js +7 -2
- package/dist/query/clickhouseDialect.js.map +1 -1
- package/dist/query/dialect.d.ts +55 -11
- package/dist/query/dialect.d.ts.map +1 -1
- package/dist/query/dialect.js +6 -4
- package/dist/query/dialect.js.map +1 -1
- package/dist/query/duckdbDialect.d.ts.map +1 -1
- package/dist/query/duckdbDialect.js +7 -2
- package/dist/query/duckdbDialect.js.map +1 -1
- package/dist/query/index.d.ts +3 -0
- package/dist/query/index.d.ts.map +1 -1
- package/dist/query/index.js +6 -0
- package/dist/query/index.js.map +1 -1
- package/dist/query/mssqlDialect.d.ts +99 -0
- package/dist/query/mssqlDialect.d.ts.map +1 -0
- package/dist/query/mssqlDialect.js +398 -0
- package/dist/query/mssqlDialect.js.map +1 -0
- package/dist/query/postgresDialect.d.ts +48 -0
- package/dist/query/postgresDialect.d.ts.map +1 -0
- package/dist/query/postgresDialect.js +167 -0
- package/dist/query/postgresDialect.js.map +1 -0
- package/dist/query/relational.d.ts +69 -0
- package/dist/query/relational.d.ts.map +1 -0
- package/dist/query/relational.js +94 -0
- package/dist/query/relational.js.map +1 -0
- package/package.json +2 -2
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Microsoft SQL Server (T-SQL) dialect for the query layer (ADR 0020, #85).
|
|
3
|
+
*
|
|
4
|
+
* SQL Server is the optional single-tenant *relational* store for self-hosters
|
|
5
|
+
* standardized on SQL Server / Azure SQL: a multi-writer server, so several
|
|
6
|
+
* collector instances can share one database. Like the Postgres dialect it is a
|
|
7
|
+
* *re-home + dialect emitter* — every aggregation stays authored once in
|
|
8
|
+
* `aggregations.ts` — but it is the heaviest relational port, because T-SQL
|
|
9
|
+
* lacks three things the shared SQL takes for granted:
|
|
10
|
+
*
|
|
11
|
+
* 1. **No array type.** Vector columns (`position`, `direction`, `hit_point`,
|
|
12
|
+
* `ray_origin`, `ray_direction`, `screen`, `rotation`, `scale`) are stored
|
|
13
|
+
* as JSON number arrays (`'[x,y,z]'`, `nvarchar`). Every element access goes
|
|
14
|
+
* through {@link mssqlVectorElement} (`JSON_VALUE(col, '$[i]')` +
|
|
15
|
+
* `TRY_CONVERT(float)`), including the shared SQL's inline `position[1]`
|
|
16
|
+
* indexing, which {@link toTsql} rewrites — this is the single place where
|
|
17
|
+
* the JSON-vector representation is known. `vectorNorm` and `arrayLength`
|
|
18
|
+
* are built over the same representation.
|
|
19
|
+
* 2. **No `ASOF JOIN`.** `asofJoin` renders the shared nearest-row emulation
|
|
20
|
+
* (`renderNearestRowJoin`, `relational.ts`) as `CROSS APPLY` / `OUTER APPLY
|
|
21
|
+
* (SELECT TOP 1 … ORDER BY ts DESC)`.
|
|
22
|
+
* 3. **No aggregate percentile.** T-SQL's `PERCENTILE_CONT` is a *window*
|
|
23
|
+
* function only, so it cannot appear in a `GROUP BY` select list next to
|
|
24
|
+
* `avg`/`count`. `quantile` therefore packs the group's values with
|
|
25
|
+
* `STRING_AGG` — a real aggregate — and a small T-SQL scalar function
|
|
26
|
+
* (`dbo.uptimizr_quantile`, created by the `@uptimizr/db-mssql` migrations)
|
|
27
|
+
* sorts them and computes the type-7 linear interpolation DuckDB's
|
|
28
|
+
* `quantile_cont` / Postgres' `percentile_cont` use. Exact, O(n log n) per
|
|
29
|
+
* group; documented as the store's performance trade-off.
|
|
30
|
+
*
|
|
31
|
+
* Two further T-SQL syntax gaps are closed by {@link toTsql} rather than by the
|
|
32
|
+
* `Dialect` members, because the shared SQL cannot express them portably:
|
|
33
|
+
* `LIMIT n` (→ `OFFSET 0 ROWS FETCH NEXT n ROWS ONLY`) and `GROUP BY <select
|
|
34
|
+
* alias>`, which T-SQL rejects (`Invalid column name`) and is replaced by the
|
|
35
|
+
* alias's defining expression. `toTsql` is applied once, at execution time, by
|
|
36
|
+
* `runMssqlQuery` in `@uptimizr/db-mssql`.
|
|
37
|
+
*
|
|
38
|
+
* Other notes:
|
|
39
|
+
* - `epochMs` / `timeBucketMs` return `float` (exact for epoch milliseconds,
|
|
40
|
+
* which are far below 2^53). `bigint` would come back as a *string* from the
|
|
41
|
+
* TDS driver and `avg(bigint)` truncates in T-SQL; `float` avoids both.
|
|
42
|
+
* - Integer `avg` truncates in T-SQL, so `avgIf` / `avgMerge` / `quantile`
|
|
43
|
+
* cast their input to `float`; the promoted numeric columns are `float`
|
|
44
|
+
* already.
|
|
45
|
+
* - String columns use the binary collation {@link MSSQL_BIN_COLLATION}, so
|
|
46
|
+
* equality, `GROUP BY` and ordering are case-sensitive and code-point ordered
|
|
47
|
+
* exactly like DuckDB / Postgres; JSON extractions carry the same collation.
|
|
48
|
+
* - Parameters are emitted as named `$name` tokens (cast in place for `f64`,
|
|
49
|
+
* `timestamp`, `date`) and rewritten to positional `@p1…@pn` by the shared
|
|
50
|
+
* `toPositionalParams`; timestamp params are bound as ISO-8601 `T`-separated
|
|
51
|
+
* naive-UTC strings (language/`DATEFORMAT`-independent) against
|
|
52
|
+
* `datetime2(3)` columns.
|
|
53
|
+
* - Requires SQL Server 2022 (16.x) / Azure SQL: `GREATEST` / `LEAST` (2022),
|
|
54
|
+
* `STRING_AGG` / `STRING_SPLIT` / `JSON_VALUE` / `DATEDIFF_BIG` (2016+).
|
|
55
|
+
*/
|
|
56
|
+
import type { Dialect } from "./dialect.js";
|
|
57
|
+
import { type NearestRowJoinTokens } from "./relational.js";
|
|
58
|
+
/**
|
|
59
|
+
* Format an epoch-millisecond timestamp as an ISO-8601 naive-UTC
|
|
60
|
+
* `YYYY-MM-DDTHH:MM:SS.mmm` string for binding to a `datetime2(3)` column /
|
|
61
|
+
* parameter. The `T` separator makes the literal language- and
|
|
62
|
+
* `SET DATEFORMAT`-independent; the wall-clock value is the same one the DuckDB
|
|
63
|
+
* / ClickHouse / Postgres stores bind.
|
|
64
|
+
*/
|
|
65
|
+
export declare function toMssqlTimestamp(epochMs: number): string;
|
|
66
|
+
/** Join keywords for the SQL Server flavour of the nearest-row ASOF emulation. */
|
|
67
|
+
export declare const MSSQL_NEAREST_ROW_JOIN: NearestRowJoinTokens;
|
|
68
|
+
/**
|
|
69
|
+
* Binary (case-sensitive, code-point ordered) collation applied to every string
|
|
70
|
+
* column and JSON extraction, so string semantics match DuckDB / Postgres
|
|
71
|
+
* instead of SQL Server's case-insensitive default.
|
|
72
|
+
*/
|
|
73
|
+
export declare const MSSQL_BIN_COLLATION = "Latin1_General_100_BIN2";
|
|
74
|
+
/** Schema-qualified name of the quantile helper function (see module doc). */
|
|
75
|
+
export declare const MSSQL_QUANTILE_FUNCTION = "dbo.uptimizr_quantile";
|
|
76
|
+
/**
|
|
77
|
+
* The `index0`-th (0-based) element of a JSON number array as `float`, or NULL
|
|
78
|
+
* when the array is shorter / the value is not numeric. The one place that
|
|
79
|
+
* knows vectors are JSON on SQL Server (see module doc).
|
|
80
|
+
*/
|
|
81
|
+
export declare function mssqlVectorElement(expr: string, index0: number): string;
|
|
82
|
+
export declare const mssqlDialect: Dialect;
|
|
83
|
+
/**
|
|
84
|
+
* Rewrite a query rendered with {@link mssqlDialect} into T-SQL. Three
|
|
85
|
+
* constructs of the shared SQL are not T-SQL and cannot be expressed through a
|
|
86
|
+
* `Dialect` member without touching every aggregation:
|
|
87
|
+
*
|
|
88
|
+
* - `col[n]` (1-based vector indexing) → {@link mssqlVectorElement}`(col, n-1)`.
|
|
89
|
+
* - `LIMIT n` → `OFFSET 0 ROWS FETCH NEXT n ROWS ONLY` (T-SQL requires an
|
|
90
|
+
* `ORDER BY`, which every `LIMIT` in the shared SQL has).
|
|
91
|
+
* - `GROUP BY <alias>` → the alias's select-list expression (T-SQL: "Invalid
|
|
92
|
+
* column name"). Both DuckDB and Postgres accept output-column aliases in
|
|
93
|
+
* `GROUP BY`; T-SQL only accepts input columns and expressions.
|
|
94
|
+
*
|
|
95
|
+
* Plus the spelling difference `atan2` → `ATN2`. Pure and idempotent; applied
|
|
96
|
+
* once per query by `runMssqlQuery`.
|
|
97
|
+
*/
|
|
98
|
+
export declare function toTsql(sql: string): string;
|
|
99
|
+
//# sourceMappingURL=mssqlDialect.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mssqlDialect.d.ts","sourceRoot":"","sources":["../../src/query/mssqlDialect.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsDG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAa,MAAM,cAAc,CAAC;AACvD,OAAO,EAAwB,KAAK,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAElF;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAOxD;AAED,kFAAkF;AAClF,eAAO,MAAM,sBAAsB,EAAE,oBAMpC,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,4BAA4B,CAAC;AAE7D,8EAA8E;AAC9E,eAAO,MAAM,uBAAuB,0BAA0B,CAAC;AAK/D;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAEvE;AA0BD,eAAO,MAAM,YAAY,EAAE,OA4F1B,CAAC;AAgBF;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAO1C"}
|
|
@@ -0,0 +1,398 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Microsoft SQL Server (T-SQL) dialect for the query layer (ADR 0020, #85).
|
|
3
|
+
*
|
|
4
|
+
* SQL Server is the optional single-tenant *relational* store for self-hosters
|
|
5
|
+
* standardized on SQL Server / Azure SQL: a multi-writer server, so several
|
|
6
|
+
* collector instances can share one database. Like the Postgres dialect it is a
|
|
7
|
+
* *re-home + dialect emitter* — every aggregation stays authored once in
|
|
8
|
+
* `aggregations.ts` — but it is the heaviest relational port, because T-SQL
|
|
9
|
+
* lacks three things the shared SQL takes for granted:
|
|
10
|
+
*
|
|
11
|
+
* 1. **No array type.** Vector columns (`position`, `direction`, `hit_point`,
|
|
12
|
+
* `ray_origin`, `ray_direction`, `screen`, `rotation`, `scale`) are stored
|
|
13
|
+
* as JSON number arrays (`'[x,y,z]'`, `nvarchar`). Every element access goes
|
|
14
|
+
* through {@link mssqlVectorElement} (`JSON_VALUE(col, '$[i]')` +
|
|
15
|
+
* `TRY_CONVERT(float)`), including the shared SQL's inline `position[1]`
|
|
16
|
+
* indexing, which {@link toTsql} rewrites — this is the single place where
|
|
17
|
+
* the JSON-vector representation is known. `vectorNorm` and `arrayLength`
|
|
18
|
+
* are built over the same representation.
|
|
19
|
+
* 2. **No `ASOF JOIN`.** `asofJoin` renders the shared nearest-row emulation
|
|
20
|
+
* (`renderNearestRowJoin`, `relational.ts`) as `CROSS APPLY` / `OUTER APPLY
|
|
21
|
+
* (SELECT TOP 1 … ORDER BY ts DESC)`.
|
|
22
|
+
* 3. **No aggregate percentile.** T-SQL's `PERCENTILE_CONT` is a *window*
|
|
23
|
+
* function only, so it cannot appear in a `GROUP BY` select list next to
|
|
24
|
+
* `avg`/`count`. `quantile` therefore packs the group's values with
|
|
25
|
+
* `STRING_AGG` — a real aggregate — and a small T-SQL scalar function
|
|
26
|
+
* (`dbo.uptimizr_quantile`, created by the `@uptimizr/db-mssql` migrations)
|
|
27
|
+
* sorts them and computes the type-7 linear interpolation DuckDB's
|
|
28
|
+
* `quantile_cont` / Postgres' `percentile_cont` use. Exact, O(n log n) per
|
|
29
|
+
* group; documented as the store's performance trade-off.
|
|
30
|
+
*
|
|
31
|
+
* Two further T-SQL syntax gaps are closed by {@link toTsql} rather than by the
|
|
32
|
+
* `Dialect` members, because the shared SQL cannot express them portably:
|
|
33
|
+
* `LIMIT n` (→ `OFFSET 0 ROWS FETCH NEXT n ROWS ONLY`) and `GROUP BY <select
|
|
34
|
+
* alias>`, which T-SQL rejects (`Invalid column name`) and is replaced by the
|
|
35
|
+
* alias's defining expression. `toTsql` is applied once, at execution time, by
|
|
36
|
+
* `runMssqlQuery` in `@uptimizr/db-mssql`.
|
|
37
|
+
*
|
|
38
|
+
* Other notes:
|
|
39
|
+
* - `epochMs` / `timeBucketMs` return `float` (exact for epoch milliseconds,
|
|
40
|
+
* which are far below 2^53). `bigint` would come back as a *string* from the
|
|
41
|
+
* TDS driver and `avg(bigint)` truncates in T-SQL; `float` avoids both.
|
|
42
|
+
* - Integer `avg` truncates in T-SQL, so `avgIf` / `avgMerge` / `quantile`
|
|
43
|
+
* cast their input to `float`; the promoted numeric columns are `float`
|
|
44
|
+
* already.
|
|
45
|
+
* - String columns use the binary collation {@link MSSQL_BIN_COLLATION}, so
|
|
46
|
+
* equality, `GROUP BY` and ordering are case-sensitive and code-point ordered
|
|
47
|
+
* exactly like DuckDB / Postgres; JSON extractions carry the same collation.
|
|
48
|
+
* - Parameters are emitted as named `$name` tokens (cast in place for `f64`,
|
|
49
|
+
* `timestamp`, `date`) and rewritten to positional `@p1…@pn` by the shared
|
|
50
|
+
* `toPositionalParams`; timestamp params are bound as ISO-8601 `T`-separated
|
|
51
|
+
* naive-UTC strings (language/`DATEFORMAT`-independent) against
|
|
52
|
+
* `datetime2(3)` columns.
|
|
53
|
+
* - Requires SQL Server 2022 (16.x) / Azure SQL: `GREATEST` / `LEAST` (2022),
|
|
54
|
+
* `STRING_AGG` / `STRING_SPLIT` / `JSON_VALUE` / `DATEDIFF_BIG` (2016+).
|
|
55
|
+
*/
|
|
56
|
+
import { renderNearestRowJoin } from "./relational.js";
|
|
57
|
+
/**
|
|
58
|
+
* Format an epoch-millisecond timestamp as an ISO-8601 naive-UTC
|
|
59
|
+
* `YYYY-MM-DDTHH:MM:SS.mmm` string for binding to a `datetime2(3)` column /
|
|
60
|
+
* parameter. The `T` separator makes the literal language- and
|
|
61
|
+
* `SET DATEFORMAT`-independent; the wall-clock value is the same one the DuckDB
|
|
62
|
+
* / ClickHouse / Postgres stores bind.
|
|
63
|
+
*/
|
|
64
|
+
export function toMssqlTimestamp(epochMs) {
|
|
65
|
+
const d = new Date(epochMs);
|
|
66
|
+
const p = (n, w = 2) => String(n).padStart(w, "0");
|
|
67
|
+
return (`${d.getUTCFullYear()}-${p(d.getUTCMonth() + 1)}-${p(d.getUTCDate())}T` +
|
|
68
|
+
`${p(d.getUTCHours())}:${p(d.getUTCMinutes())}:${p(d.getUTCSeconds())}.${p(d.getUTCMilliseconds(), 3)}`);
|
|
69
|
+
}
|
|
70
|
+
/** Join keywords for the SQL Server flavour of the nearest-row ASOF emulation. */
|
|
71
|
+
export const MSSQL_NEAREST_ROW_JOIN = {
|
|
72
|
+
inner: "CROSS APPLY",
|
|
73
|
+
left: "OUTER APPLY",
|
|
74
|
+
onTrue: "",
|
|
75
|
+
selectPrefix: "SELECT TOP 1",
|
|
76
|
+
selectSuffix: "",
|
|
77
|
+
};
|
|
78
|
+
/**
|
|
79
|
+
* Binary (case-sensitive, code-point ordered) collation applied to every string
|
|
80
|
+
* column and JSON extraction, so string semantics match DuckDB / Postgres
|
|
81
|
+
* instead of SQL Server's case-insensitive default.
|
|
82
|
+
*/
|
|
83
|
+
export const MSSQL_BIN_COLLATION = "Latin1_General_100_BIN2";
|
|
84
|
+
/** Schema-qualified name of the quantile helper function (see module doc). */
|
|
85
|
+
export const MSSQL_QUANTILE_FUNCTION = "dbo.uptimizr_quantile";
|
|
86
|
+
/** The Unix epoch as a `datetime2(3)` literal (ISO-8601, language-neutral). */
|
|
87
|
+
const MSSQL_EPOCH = "CAST('1970-01-01T00:00:00' AS datetime2(3))";
|
|
88
|
+
/**
|
|
89
|
+
* The `index0`-th (0-based) element of a JSON number array as `float`, or NULL
|
|
90
|
+
* when the array is shorter / the value is not numeric. The one place that
|
|
91
|
+
* knows vectors are JSON on SQL Server (see module doc).
|
|
92
|
+
*/
|
|
93
|
+
export function mssqlVectorElement(expr, index0) {
|
|
94
|
+
return `TRY_CONVERT(float, JSON_VALUE(${expr}, '$[${index0}]'))`;
|
|
95
|
+
}
|
|
96
|
+
/** Render a trusted compile-time key path as a SQL Server JSON path. */
|
|
97
|
+
function jsonPath(path) {
|
|
98
|
+
// Numeric components index a JSON array (0-based); the others are quoted
|
|
99
|
+
// object keys so any key spelling is safe.
|
|
100
|
+
return `$${path.map((k) => (/^\d+$/.test(k) ? `[${k}]` : `."${k}"`)).join("")}`;
|
|
101
|
+
}
|
|
102
|
+
/** `JSON_VALUE(column, '$.path')` — the value at `path` as text (NULL when absent). */
|
|
103
|
+
function jsonAt(column, path) {
|
|
104
|
+
return `JSON_VALUE(${column}, '${jsonPath(path)}')`;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* The group's non-NULL values of `expr` packed into one comma-separated
|
|
108
|
+
* `varchar(max)` of round-trippable floats (`CONVERT` style 3 = 17 significant
|
|
109
|
+
* digits), consumed (and sorted) by `dbo.uptimizr_quantile`. Deliberately not
|
|
110
|
+
* `WITHIN GROUP (ORDER BY …)`: T-SQL rejects two ordered aggregates with
|
|
111
|
+
* different orderings in one scope, and the shared SQL routinely computes
|
|
112
|
+
* quantiles of several expressions side by side.
|
|
113
|
+
*/
|
|
114
|
+
function packedValues(expr) {
|
|
115
|
+
return `STRING_AGG(CONVERT(varchar(max), CONVERT(varchar(30), CAST((${expr}) AS float), 3)), ',')`;
|
|
116
|
+
}
|
|
117
|
+
export const mssqlDialect = {
|
|
118
|
+
name: "mssql",
|
|
119
|
+
placeholder(name, type) {
|
|
120
|
+
// The TDS driver infers `int`/`float`/`nvarchar` from the JS value; cast in
|
|
121
|
+
// place where the inferred type could differ from the logical one (an
|
|
122
|
+
// integral `f64` would bind as `int`) or where a string must become a date.
|
|
123
|
+
switch (type) {
|
|
124
|
+
case "f64":
|
|
125
|
+
return `CAST($${name} AS float)`;
|
|
126
|
+
case "timestamp":
|
|
127
|
+
return `CAST($${name} AS datetime2(3))`;
|
|
128
|
+
case "date":
|
|
129
|
+
return `CAST($${name} AS date)`;
|
|
130
|
+
default:
|
|
131
|
+
return `$${name}`;
|
|
132
|
+
}
|
|
133
|
+
},
|
|
134
|
+
timestampValue(epochMs) {
|
|
135
|
+
return toMssqlTimestamp(epochMs);
|
|
136
|
+
},
|
|
137
|
+
quantile(expr, q) {
|
|
138
|
+
return `${MSSQL_QUANTILE_FUNCTION}(${packedValues(expr)}, ${q})`;
|
|
139
|
+
},
|
|
140
|
+
vectorNorm(expr) {
|
|
141
|
+
// No arrays: sum the squares of the JSON elements. All callers guard with
|
|
142
|
+
// `arrayLength(expr) = 3`; up to four components are covered (quaternions)
|
|
143
|
+
// and absent components contribute 0.
|
|
144
|
+
const terms = [0, 1, 2, 3].map((i) => `power(coalesce(${mssqlVectorElement(expr, i)}, 0), 2)`);
|
|
145
|
+
return `sqrt(${terms.join(" + ")})`;
|
|
146
|
+
},
|
|
147
|
+
arrayLength(expr) {
|
|
148
|
+
// Vectors are flat JSON number arrays written by the store itself, so the
|
|
149
|
+
// element count is the comma count + 1 (0 for `[]`). Pure scalar text
|
|
150
|
+
// arithmetic — usable in WHERE, SELECT and GROUP BY alike (no subquery).
|
|
151
|
+
return `(CASE WHEN ${expr} IS NULL OR ${expr} = N'[]' THEN 0 ELSE LEN(${expr}) - LEN(REPLACE(${expr}, ',', '')) + 1 END)`;
|
|
152
|
+
},
|
|
153
|
+
avgIf(value, cond) {
|
|
154
|
+
return `avg(CASE WHEN ${cond} THEN CAST((${value}) AS float) END)`;
|
|
155
|
+
},
|
|
156
|
+
anyValue(expr) {
|
|
157
|
+
// `min` is a valid "any" (callers use it on values constant within the
|
|
158
|
+
// group) and ignores NULLs like DuckDB's `any_value`.
|
|
159
|
+
return `min(${expr})`;
|
|
160
|
+
},
|
|
161
|
+
timeBucketMs(tsExpr, intervalPlaceholder) {
|
|
162
|
+
// Float epoch-ms floored to the interval grid (same arithmetic as DuckDB /
|
|
163
|
+
// ClickHouse / Postgres); the interval param is an `int`, the division is
|
|
164
|
+
// float so it can never truncate.
|
|
165
|
+
const bucket = `(${intervalPlaceholder} * 1000)`;
|
|
166
|
+
return `(floor(${this.epochMs(tsExpr)} / ${bucket}) * ${bucket})`;
|
|
167
|
+
},
|
|
168
|
+
epochMs(tsExpr) {
|
|
169
|
+
// `datetime2(3)` holds wall-clock UTC; DATEDIFF_BIG from the epoch is exact
|
|
170
|
+
// milliseconds, returned as float (see module doc).
|
|
171
|
+
return `CAST(DATEDIFF_BIG(millisecond, ${MSSQL_EPOCH}, ${tsExpr}) AS float)`;
|
|
172
|
+
},
|
|
173
|
+
toDate(expr) {
|
|
174
|
+
return `CAST(${expr} AS date)`;
|
|
175
|
+
},
|
|
176
|
+
toText(expr) {
|
|
177
|
+
// Style 121 renders dates as `YYYY-MM-DD` and datetime2 as
|
|
178
|
+
// `YYYY-MM-DD HH:MM:SS.fff` (ISO, language-neutral); the shared SQL only
|
|
179
|
+
// applies it to date-typed rollup columns.
|
|
180
|
+
return `CONVERT(nvarchar(30), ${expr}, 121)`;
|
|
181
|
+
},
|
|
182
|
+
jsonText(column, ...path) {
|
|
183
|
+
return `${jsonAt(column, path)} COLLATE ${MSSQL_BIN_COLLATION}`;
|
|
184
|
+
},
|
|
185
|
+
jsonInt(column, ...path) {
|
|
186
|
+
// `TRY_CONVERT(bigint)` yields NULL for absent / non-integer text exactly
|
|
187
|
+
// like DuckDB's `TRY_CAST(... AS BIGINT)`; widened to float so it never
|
|
188
|
+
// reaches the driver as a bigint string nor truncates under `avg`.
|
|
189
|
+
return `CAST(TRY_CONVERT(bigint, ${jsonAt(column, path)}) AS float)`;
|
|
190
|
+
},
|
|
191
|
+
jsonFloat(column, ...path) {
|
|
192
|
+
return `TRY_CONVERT(float, ${jsonAt(column, path)})`;
|
|
193
|
+
},
|
|
194
|
+
// The daily rollups are query-time views (pre-grouped by day), so each read
|
|
195
|
+
// GROUP BY sees exactly one source row per group and the "merge" of a single
|
|
196
|
+
// precomputed value is a plain pass-through aggregate — as on DuckDB.
|
|
197
|
+
countMerge(stateExpr) {
|
|
198
|
+
return `sum(${stateExpr})`;
|
|
199
|
+
},
|
|
200
|
+
avgMerge(stateExpr) {
|
|
201
|
+
return `avg(CAST((${stateExpr}) AS float))`;
|
|
202
|
+
},
|
|
203
|
+
quantileMerge(stateExpr, q) {
|
|
204
|
+
return `${MSSQL_QUANTILE_FUNCTION}(${packedValues(stateExpr)}, ${q})`;
|
|
205
|
+
},
|
|
206
|
+
asofJoin(spec) {
|
|
207
|
+
return renderNearestRowJoin(spec, MSSQL_NEAREST_ROW_JOIN);
|
|
208
|
+
},
|
|
209
|
+
};
|
|
210
|
+
// ---------------------------------------------------------------------------
|
|
211
|
+
// T-SQL adaptation of the shared SQL (see module doc)
|
|
212
|
+
// ---------------------------------------------------------------------------
|
|
213
|
+
/**
|
|
214
|
+
* Inline 1-based array indexing in the shared SQL: `position[1]`,
|
|
215
|
+
* `m.direction[3]`, … Never matches the dialect's own JSON paths (`'$[0]'`,
|
|
216
|
+
* `'$."uv"[0]'`), because those have no identifier directly before the `[`.
|
|
217
|
+
*/
|
|
218
|
+
const VECTOR_INDEX = /(?<![\w.$"])((?:[A-Za-z_]\w*\.)?[A-Za-z_]\w*)\[(\d+)\]/g;
|
|
219
|
+
/** `LIMIT <param|literal>` — always the last clause of a block in the shared SQL. */
|
|
220
|
+
const LIMIT_CLAUSE = /\bLIMIT\s+(\$[A-Za-z_]\w*|\d+)\b/g;
|
|
221
|
+
/**
|
|
222
|
+
* Rewrite a query rendered with {@link mssqlDialect} into T-SQL. Three
|
|
223
|
+
* constructs of the shared SQL are not T-SQL and cannot be expressed through a
|
|
224
|
+
* `Dialect` member without touching every aggregation:
|
|
225
|
+
*
|
|
226
|
+
* - `col[n]` (1-based vector indexing) → {@link mssqlVectorElement}`(col, n-1)`.
|
|
227
|
+
* - `LIMIT n` → `OFFSET 0 ROWS FETCH NEXT n ROWS ONLY` (T-SQL requires an
|
|
228
|
+
* `ORDER BY`, which every `LIMIT` in the shared SQL has).
|
|
229
|
+
* - `GROUP BY <alias>` → the alias's select-list expression (T-SQL: "Invalid
|
|
230
|
+
* column name"). Both DuckDB and Postgres accept output-column aliases in
|
|
231
|
+
* `GROUP BY`; T-SQL only accepts input columns and expressions.
|
|
232
|
+
*
|
|
233
|
+
* Plus the spelling difference `atan2` → `ATN2`. Pure and idempotent; applied
|
|
234
|
+
* once per query by `runMssqlQuery`.
|
|
235
|
+
*/
|
|
236
|
+
export function toTsql(sql) {
|
|
237
|
+
return rewriteGroupByAliases(sql)
|
|
238
|
+
.replace(VECTOR_INDEX, (_m, expr, index) => mssqlVectorElement(expr, Number(index) - 1))
|
|
239
|
+
.replace(/\batan2\s*\(/gi, "ATN2(")
|
|
240
|
+
.replace(LIMIT_CLAUSE, "OFFSET 0 ROWS FETCH NEXT $1 ROWS ONLY");
|
|
241
|
+
}
|
|
242
|
+
/** Keywords that end a `GROUP BY` list within the same block. */
|
|
243
|
+
const GROUP_TERMINATORS = /^(HAVING|ORDER\s+BY|LIMIT|OFFSET|UNION|EXCEPT|INTERSECT|WINDOW)\b/i;
|
|
244
|
+
/**
|
|
245
|
+
* One pass over the SQL text: paren depth, string literals skipped, and the
|
|
246
|
+
* clause keywords that matter for block detection. `WITHIN GROUP (` is not a
|
|
247
|
+
* `GROUP BY`; keywords inside `OVER (…)` / subqueries sit at a deeper depth.
|
|
248
|
+
*/
|
|
249
|
+
function scanClauses(sql) {
|
|
250
|
+
const clauses = [];
|
|
251
|
+
let depth = 0;
|
|
252
|
+
for (let i = 0; i < sql.length;) {
|
|
253
|
+
const ch = sql[i];
|
|
254
|
+
if (ch === "'") {
|
|
255
|
+
// Skip a string literal ('' is an escaped quote).
|
|
256
|
+
let j = i + 1;
|
|
257
|
+
while (j < sql.length) {
|
|
258
|
+
if (sql[j] === "'") {
|
|
259
|
+
if (sql[j + 1] === "'")
|
|
260
|
+
j += 2;
|
|
261
|
+
else
|
|
262
|
+
break;
|
|
263
|
+
}
|
|
264
|
+
else
|
|
265
|
+
j++;
|
|
266
|
+
}
|
|
267
|
+
i = j + 1;
|
|
268
|
+
continue;
|
|
269
|
+
}
|
|
270
|
+
if (ch === "(") {
|
|
271
|
+
depth++;
|
|
272
|
+
i++;
|
|
273
|
+
continue;
|
|
274
|
+
}
|
|
275
|
+
if (ch === ")") {
|
|
276
|
+
clauses.push({ kind: "close", depth, start: i, end: i + 1 });
|
|
277
|
+
depth--;
|
|
278
|
+
i++;
|
|
279
|
+
continue;
|
|
280
|
+
}
|
|
281
|
+
if (/[A-Za-z_]/.test(ch) && !/[\w.$]/.test(sql[i - 1] ?? " ")) {
|
|
282
|
+
const rest = sql.slice(i);
|
|
283
|
+
const word = /^[A-Za-z_]\w*/.exec(rest)[0];
|
|
284
|
+
const upper = word.toUpperCase();
|
|
285
|
+
let match;
|
|
286
|
+
if (upper === "SELECT")
|
|
287
|
+
match = { kind: "select", length: word.length };
|
|
288
|
+
else if (upper === "FROM")
|
|
289
|
+
match = { kind: "from", length: word.length };
|
|
290
|
+
else if (upper === "GROUP") {
|
|
291
|
+
const by = /^GROUP\s+BY\b/i.exec(rest);
|
|
292
|
+
if (by)
|
|
293
|
+
match = { kind: "groupBy", length: by[0].length };
|
|
294
|
+
}
|
|
295
|
+
else {
|
|
296
|
+
const term = GROUP_TERMINATORS.exec(rest);
|
|
297
|
+
if (term)
|
|
298
|
+
match = { kind: "terminator", length: term[0].length };
|
|
299
|
+
}
|
|
300
|
+
if (match)
|
|
301
|
+
clauses.push({ kind: match.kind, depth, start: i, end: i + match.length });
|
|
302
|
+
i += word.length;
|
|
303
|
+
continue;
|
|
304
|
+
}
|
|
305
|
+
i++;
|
|
306
|
+
}
|
|
307
|
+
return clauses;
|
|
308
|
+
}
|
|
309
|
+
/** Split on commas that sit outside parentheses and string literals. */
|
|
310
|
+
function splitTopLevel(text) {
|
|
311
|
+
const parts = [];
|
|
312
|
+
let depth = 0;
|
|
313
|
+
let inString = false;
|
|
314
|
+
let start = 0;
|
|
315
|
+
for (let i = 0; i < text.length; i++) {
|
|
316
|
+
const ch = text[i];
|
|
317
|
+
if (inString) {
|
|
318
|
+
if (ch === "'") {
|
|
319
|
+
if (text[i + 1] === "'")
|
|
320
|
+
i++;
|
|
321
|
+
else
|
|
322
|
+
inString = false;
|
|
323
|
+
}
|
|
324
|
+
continue;
|
|
325
|
+
}
|
|
326
|
+
if (ch === "'")
|
|
327
|
+
inString = true;
|
|
328
|
+
else if (ch === "(")
|
|
329
|
+
depth++;
|
|
330
|
+
else if (ch === ")")
|
|
331
|
+
depth--;
|
|
332
|
+
else if (ch === "," && depth === 0) {
|
|
333
|
+
parts.push(text.slice(start, i));
|
|
334
|
+
start = i + 1;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
parts.push(text.slice(start));
|
|
338
|
+
return parts;
|
|
339
|
+
}
|
|
340
|
+
const ALIASED_ITEM = /^([\s\S]*\S)\s+AS\s+([A-Za-z_]\w*)\s*$/i;
|
|
341
|
+
const BARE_IDENTIFIER = /^\s*([A-Za-z_]\w*)\s*$/;
|
|
342
|
+
/**
|
|
343
|
+
* Replace output-column aliases in every `GROUP BY` list with the expression
|
|
344
|
+
* that defines them in the owning `SELECT` list (nearest preceding `SELECT` at
|
|
345
|
+
* the same paren depth; its list ends at the first `FROM` at that depth).
|
|
346
|
+
*/
|
|
347
|
+
function rewriteGroupByAliases(sql) {
|
|
348
|
+
const clauses = scanClauses(sql);
|
|
349
|
+
let out = sql;
|
|
350
|
+
// Walk backwards so earlier offsets stay valid after each replacement.
|
|
351
|
+
for (let g = clauses.length - 1; g >= 0; g--) {
|
|
352
|
+
const group = clauses[g];
|
|
353
|
+
if (group.kind !== "groupBy")
|
|
354
|
+
continue;
|
|
355
|
+
let selectIdx = -1;
|
|
356
|
+
for (let s = g - 1; s >= 0; s--) {
|
|
357
|
+
const c = clauses[s];
|
|
358
|
+
if (c.kind === "select" && c.depth === group.depth) {
|
|
359
|
+
selectIdx = s;
|
|
360
|
+
break;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
if (selectIdx < 0)
|
|
364
|
+
continue;
|
|
365
|
+
const from = clauses
|
|
366
|
+
.slice(selectIdx + 1, g)
|
|
367
|
+
.find((c) => c.kind === "from" && c.depth === group.depth);
|
|
368
|
+
if (!from)
|
|
369
|
+
continue;
|
|
370
|
+
const selectList = out
|
|
371
|
+
.slice(clauses[selectIdx].end, from.start)
|
|
372
|
+
.replace(/^\s*(DISTINCT|TOP\s+\d+)\b/i, "");
|
|
373
|
+
const aliases = new Map();
|
|
374
|
+
for (const item of splitTopLevel(selectList)) {
|
|
375
|
+
const m = ALIASED_ITEM.exec(item.trim());
|
|
376
|
+
if (m && m[1].trim() !== m[2])
|
|
377
|
+
aliases.set(m[2], m[1].trim());
|
|
378
|
+
}
|
|
379
|
+
if (aliases.size === 0)
|
|
380
|
+
continue;
|
|
381
|
+
const terminator = clauses
|
|
382
|
+
.slice(g + 1)
|
|
383
|
+
.find((c) => (c.kind === "terminator" && c.depth === group.depth) ||
|
|
384
|
+
(c.kind === "close" && c.depth === group.depth));
|
|
385
|
+
const listEnd = terminator ? terminator.start : out.length;
|
|
386
|
+
const list = out.slice(group.end, listEnd);
|
|
387
|
+
const rewritten = splitTopLevel(list)
|
|
388
|
+
.map((item) => {
|
|
389
|
+
const id = BARE_IDENTIFIER.exec(item);
|
|
390
|
+
const expr = id ? aliases.get(id[1]) : undefined;
|
|
391
|
+
return expr ? item.replace(id[1], expr) : item;
|
|
392
|
+
})
|
|
393
|
+
.join(",");
|
|
394
|
+
out = out.slice(0, group.end) + rewritten + out.slice(listEnd);
|
|
395
|
+
}
|
|
396
|
+
return out;
|
|
397
|
+
}
|
|
398
|
+
//# sourceMappingURL=mssqlDialect.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mssqlDialect.js","sourceRoot":"","sources":["../../src/query/mssqlDialect.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PostgreSQL dialect for the query layer (ADR 0020, #84).
|
|
3
|
+
*
|
|
4
|
+
* Postgres is the optional single-tenant *relational* store: a multi-writer
|
|
5
|
+
* server many self-hosters already operate, for shops that "only want Postgres"
|
|
6
|
+
* and have outgrown DuckDB's single read-write process. This dialect renders the
|
|
7
|
+
* engine-specific fragments of each shared aggregation (`aggregations.ts`) to
|
|
8
|
+
* Postgres SQL, so the bulk of every query stays shared with DuckDB/ClickHouse.
|
|
9
|
+
*
|
|
10
|
+
* Like the other dialects it is *single-tenant* — no `org_id`, no tenant
|
|
11
|
+
* isolation — which keeps it relocatable across the open-core boundary.
|
|
12
|
+
*
|
|
13
|
+
* Binding model: parameters are emitted as named `$name::type` placeholders
|
|
14
|
+
* (every logical {@link ParamType} carries an explicit cast so Postgres never has
|
|
15
|
+
* to infer a parameter type from context) and rewritten to positional `$1…$n`
|
|
16
|
+
* by `toPositionalParams` in the `@uptimizr/db-postgres` runner. Timestamp params
|
|
17
|
+
* are bound as naive-UTC strings (see {@link toPostgresTimestamp}) against
|
|
18
|
+
* `timestamp` (without time zone) columns, so ordering, bucketing, and `::date`
|
|
19
|
+
* truncation are wall-clock-UTC exactly as on DuckDB and ClickHouse, independent
|
|
20
|
+
* of the session `TimeZone`.
|
|
21
|
+
*
|
|
22
|
+
* Row-store fit gaps (issue #84) and how they are closed here:
|
|
23
|
+
* - **No `ASOF JOIN`.** `asofJoin` renders the shared nearest-row emulation
|
|
24
|
+
* (`renderNearestRowJoin`, `relational.ts`) as `JOIN LATERAL (… ORDER BY ts
|
|
25
|
+
* DESC LIMIT 1) ON TRUE`.
|
|
26
|
+
* - **No MergeTree rollups.** The daily rollups are plain views that recompute
|
|
27
|
+
* at query time (see the `@uptimizr/db-postgres` migrations), so the `-Merge`
|
|
28
|
+
* combinators reduce to pass-through aggregates, as on DuckDB.
|
|
29
|
+
* - **Arrays are 1-indexed** — the same convention DuckDB and ClickHouse use, so
|
|
30
|
+
* `position[1]` in the shared SQL needs no translation; `arrayLength` maps to
|
|
31
|
+
* `cardinality` and `vectorNorm` unnests the array.
|
|
32
|
+
* - **JSON** lives in a `jsonb` column; extraction is `#>>` with a text[] path,
|
|
33
|
+
* and the numeric extractors regex-guard the cast (Postgres has no `TRY_CAST`)
|
|
34
|
+
* so an absent / non-numeric key yields NULL exactly like DuckDB's `TRY_CAST`.
|
|
35
|
+
*/
|
|
36
|
+
import type { Dialect } from "./dialect.js";
|
|
37
|
+
import { type NearestRowJoinTokens } from "./relational.js";
|
|
38
|
+
/**
|
|
39
|
+
* Format an epoch-millisecond timestamp as a naive-UTC `YYYY-MM-DD HH:MM:SS.mmm`
|
|
40
|
+
* string for binding to a Postgres `timestamp` (without time zone) column/param.
|
|
41
|
+
* Mirrors the DuckDB / ClickHouse literal format so all stores order and bucket
|
|
42
|
+
* time identically.
|
|
43
|
+
*/
|
|
44
|
+
export declare function toPostgresTimestamp(epochMs: number): string;
|
|
45
|
+
/** Join keywords for the Postgres flavour of the nearest-row ASOF emulation. */
|
|
46
|
+
export declare const POSTGRES_NEAREST_ROW_JOIN: NearestRowJoinTokens;
|
|
47
|
+
export declare const postgresDialect: Dialect;
|
|
48
|
+
//# sourceMappingURL=postgresDialect.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"postgresDialect.d.ts","sourceRoot":"","sources":["../../src/query/postgresDialect.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAa,MAAM,cAAc,CAAC;AACvD,OAAO,EAAwB,KAAK,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAkBlF;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAO3D;AAED,gFAAgF;AAChF,eAAO,MAAM,yBAAyB,EAAE,oBAMvC,CAAC;AAsBF,eAAO,MAAM,eAAe,EAAE,OAiF7B,CAAC"}
|