drizzle-cube
Version:
Drizzle ORM-first semantic layer with Cube.js compatibility. Type-safe analytics and dashboards with SQL injection protection.
1,694 lines (1,693 loc) • 461 kB
JavaScript
const V = Symbol.for("drizzle:entityKind");
function d(T, E) {
if (!T || typeof T != "object")
return !1;
if (T instanceof E)
return !0;
if (!Object.prototype.hasOwnProperty.call(E, V))
throw new Error(
`Class "${E.name ?? "<unknown>"}" doesn't look like a Drizzle entity. If this is incorrect and the class is provided by Drizzle, please report this as a bug.`
);
let e = Object.getPrototypeOf(T).constructor;
if (e)
for (; e; ) {
if (V in e && e[V] === E[V])
return !0;
e = Object.getPrototypeOf(e);
}
return !1;
}
class RE {
constructor(E, e) {
this.table = E, this.config = e, this.name = e.name, this.keyAsName = e.keyAsName, this.notNull = e.notNull, this.default = e.default, this.defaultFn = e.defaultFn, this.onUpdateFn = e.onUpdateFn, this.hasDefault = e.hasDefault, this.primary = e.primaryKey, this.isUnique = e.isUnique, this.uniqueName = e.uniqueName, this.uniqueType = e.uniqueType, this.dataType = e.dataType, this.columnType = e.columnType, this.generated = e.generated, this.generatedIdentity = e.generatedIdentity;
}
static [V] = "Column";
name;
keyAsName;
primary;
notNull;
default;
defaultFn;
onUpdateFn;
hasDefault;
isUnique;
uniqueName;
uniqueType;
dataType;
columnType;
enumValues = void 0;
generated = void 0;
generatedIdentity = void 0;
config;
mapFromDriverValue(E) {
return E;
}
mapToDriverValue(E) {
return E;
}
// ** @internal */
shouldDisableInsert() {
return this.config.generated !== void 0 && this.config.generated.type !== "byDefault";
}
}
const iE = Symbol.for("drizzle:Name"), TT = Symbol.for("drizzle:isPgEnum");
function ie(T) {
return !!T && typeof T == "function" && TT in T && T[TT] === !0;
}
class xT {
static [V] = "Subquery";
constructor(E, e, R, A = !1, S = []) {
this._ = {
brand: "Subquery",
sql: E,
selectedFields: e,
alias: R,
isWith: A,
usedTables: S
};
}
// getSQL(): SQL<unknown> {
// return new SQL([this]);
// }
}
const ae = {
startActiveSpan(T, E) {
return E();
}
}, TE = Symbol.for("drizzle:ViewBaseConfig"), aE = Symbol.for("drizzle:Schema"), eT = Symbol.for("drizzle:Columns"), RT = Symbol.for("drizzle:ExtraConfigColumns"), oE = Symbol.for("drizzle:OriginalName"), DE = Symbol.for("drizzle:BaseName"), sE = Symbol.for("drizzle:IsAlias"), AT = Symbol.for("drizzle:ExtraConfigBuilder"), oe = Symbol.for("drizzle:IsDrizzleTable");
class W {
static [V] = "Table";
/** @internal */
static Symbol = {
Name: iE,
Schema: aE,
OriginalName: oE,
Columns: eT,
ExtraConfigColumns: RT,
BaseName: DE,
IsAlias: sE,
ExtraConfigBuilder: AT
};
/**
* @internal
* Can be changed if the table is aliased.
*/
[iE];
/**
* @internal
* Used to store the original name of the table, before any aliasing.
*/
[oE];
/** @internal */
[aE];
/** @internal */
[eT];
/** @internal */
[RT];
/**
* @internal
* Used to store the table name before the transformation via the `tableCreator` functions.
*/
[DE];
/** @internal */
[sE] = !1;
/** @internal */
[oe] = !0;
/** @internal */
[AT] = void 0;
constructor(E, e, R) {
this[iE] = this[oE] = E, this[aE] = e, this[DE] = R;
}
}
function vT(T) {
return T != null && typeof T.getSQL == "function";
}
function De(T) {
const E = { sql: "", params: [] };
for (const e of T)
E.sql += e.sql, E.params.push(...e.params), e.typings?.length && (E.typings || (E.typings = []), E.typings.push(...e.typings));
return E;
}
class f {
static [V] = "StringChunk";
value;
constructor(E) {
this.value = Array.isArray(E) ? E : [E];
}
getSQL() {
return new H([this]);
}
}
class H {
constructor(E) {
this.queryChunks = E;
for (const e of E)
if (d(e, W)) {
const R = e[W.Symbol.Schema];
this.usedTables.push(
R === void 0 ? e[W.Symbol.Name] : R + "." + e[W.Symbol.Name]
);
}
}
static [V] = "SQL";
/** @internal */
decoder = QT;
shouldInlineParams = !1;
/** @internal */
usedTables = [];
append(E) {
return this.queryChunks.push(...E.queryChunks), this;
}
toQuery(E) {
return ae.startActiveSpan("drizzle.buildSQL", (e) => {
const R = this.buildQueryFromSourceParams(this.queryChunks, E);
return e?.setAttributes({
"drizzle.query.text": R.sql,
"drizzle.query.params": JSON.stringify(R.params)
}), R;
});
}
buildQueryFromSourceParams(E, e) {
const R = Object.assign({}, e, {
inlineParams: e.inlineParams || this.shouldInlineParams,
paramStartIndex: e.paramStartIndex || { value: 0 }
}), {
casing: A,
escapeName: S,
escapeParam: I,
prepareTyping: t,
inlineParams: s,
paramStartIndex: O
} = R;
return De(E.map((N) => {
if (d(N, f))
return { sql: N.value.join(""), params: [] };
if (d(N, ZE))
return { sql: S(N.value), params: [] };
if (N === void 0)
return { sql: "", params: [] };
if (Array.isArray(N)) {
const C = [new f("(")];
for (const [L, n] of N.entries())
C.push(n), L < N.length - 1 && C.push(new f(", "));
return C.push(new f(")")), this.buildQueryFromSourceParams(C, R);
}
if (d(N, H))
return this.buildQueryFromSourceParams(N.queryChunks, {
...R,
inlineParams: s || N.shouldInlineParams
});
if (d(N, W)) {
const C = N[W.Symbol.Schema], L = N[W.Symbol.Name];
return {
sql: C === void 0 || N[sE] ? S(L) : S(C) + "." + S(L),
params: []
};
}
if (d(N, RE)) {
const C = A.getColumnCasing(N);
if (e.invokeSource === "indexes")
return { sql: S(C), params: [] };
const L = N.table[W.Symbol.Schema];
return {
sql: N.table[sE] || L === void 0 ? S(N.table[W.Symbol.Name]) + "." + S(C) : S(L) + "." + S(N.table[W.Symbol.Name]) + "." + S(C),
params: []
};
}
if (d(N, qT)) {
const C = N[TE].schema, L = N[TE].name;
return {
sql: C === void 0 || N[TE].isAlias ? S(L) : S(C) + "." + S(L),
params: []
};
}
if (d(N, CE)) {
if (d(N.value, rE))
return { sql: I(O.value++, N), params: [N], typings: ["none"] };
const C = N.value === null ? null : N.encoder.mapToDriverValue(N.value);
if (d(C, H))
return this.buildQueryFromSourceParams([C], R);
if (s)
return { sql: this.mapInlineParam(C, R), params: [] };
let L = ["none"];
return t && (L = [t(N.encoder)]), { sql: I(O.value++, C), params: [C], typings: L };
}
return d(N, rE) ? { sql: I(O.value++, N), params: [N], typings: ["none"] } : d(N, H.Aliased) && N.fieldAlias !== void 0 ? { sql: S(N.fieldAlias), params: [] } : d(N, xT) ? N._.isWith ? { sql: S(N._.alias), params: [] } : this.buildQueryFromSourceParams([
new f("("),
N._.sql,
new f(") "),
new ZE(N._.alias)
], R) : ie(N) ? N.schema ? { sql: S(N.schema) + "." + S(N.enumName), params: [] } : { sql: S(N.enumName), params: [] } : vT(N) ? N.shouldOmitSQLParens?.() ? this.buildQueryFromSourceParams([N.getSQL()], R) : this.buildQueryFromSourceParams([
new f("("),
N.getSQL(),
new f(")")
], R) : s ? { sql: this.mapInlineParam(N, R), params: [] } : { sql: I(O.value++, N), params: [N], typings: ["none"] };
}));
}
mapInlineParam(E, { escapeString: e }) {
if (E === null)
return "null";
if (typeof E == "number" || typeof E == "boolean")
return E.toString();
if (typeof E == "string")
return e(E);
if (typeof E == "object") {
const R = E.toString();
return e(R === "[object Object]" ? JSON.stringify(E) : R);
}
throw new Error("Unexpected param value: " + E);
}
getSQL() {
return this;
}
as(E) {
return E === void 0 ? this : new H.Aliased(this, E);
}
mapWith(E) {
return this.decoder = typeof E == "function" ? { mapFromDriverValue: E } : E, this;
}
inlineParams() {
return this.shouldInlineParams = !0, this;
}
/**
* This method is used to conditionally include a part of the query.
*
* @param condition - Condition to check
* @returns itself if the condition is `true`, otherwise `undefined`
*/
if(E) {
return E ? this : void 0;
}
}
class ZE {
constructor(E) {
this.value = E;
}
static [V] = "Name";
brand;
getSQL() {
return new H([this]);
}
}
function Pe(T) {
return typeof T == "object" && T !== null && "mapToDriverValue" in T && typeof T.mapToDriverValue == "function";
}
const QT = {
mapFromDriverValue: (T) => T
}, ZT = {
mapToDriverValue: (T) => T
};
({
...QT,
...ZT
});
class CE {
/**
* @param value - Parameter value
* @param encoder - Encoder to convert the value to a driver parameter
*/
constructor(E, e = ZT) {
this.value = E, this.encoder = e;
}
static [V] = "Param";
brand;
getSQL() {
return new H([this]);
}
}
function r(T, ...E) {
const e = [];
(E.length > 0 || T.length > 0 && T[0] !== "") && e.push(new f(T[0]));
for (const [R, A] of E.entries())
e.push(A, new f(T[R + 1]));
return new H(e);
}
((T) => {
function E() {
return new H([]);
}
T.empty = E;
function e(s) {
return new H(s);
}
T.fromList = e;
function R(s) {
return new H([new f(s)]);
}
T.raw = R;
function A(s, O) {
const N = [];
for (const [C, L] of s.entries())
C > 0 && O !== void 0 && N.push(O), N.push(L);
return new H(N);
}
T.join = A;
function S(s) {
return new ZE(s);
}
T.identifier = S;
function I(s) {
return new rE(s);
}
T.placeholder = I;
function t(s, O) {
return new CE(s, O);
}
T.param = t;
})(r || (r = {}));
((T) => {
class E {
constructor(R, A) {
this.sql = R, this.fieldAlias = A;
}
static [V] = "SQL.Aliased";
/** @internal */
isSelectionField = !1;
getSQL() {
return this.sql;
}
/** @internal */
clone() {
return new E(this.sql, this.fieldAlias);
}
}
T.Aliased = E;
})(H || (H = {}));
class rE {
constructor(E) {
this.name = E;
}
static [V] = "Placeholder";
getSQL() {
return new H([this]);
}
}
const Me = Symbol.for("drizzle:IsDrizzleView");
class qT {
static [V] = "View";
/** @internal */
[TE];
/** @internal */
[Me] = !0;
constructor({ name: E, schema: e, selectedFields: R, query: A }) {
this[TE] = {
name: E,
originalName: E,
schema: e,
selectedFields: R,
query: A,
isExisting: !A,
isAlias: !1
};
}
getSQL() {
return new H([this]);
}
}
RE.prototype.getSQL = function() {
return new H([this]);
};
W.prototype.getSQL = function() {
return new H([this]);
};
xT.prototype.getSQL = function() {
return new H([this]);
};
function g(T, E) {
return Pe(E) && !vT(T) && !d(T, CE) && !d(T, rE) && !d(T, RE) && !d(T, W) && !d(T, qT) ? new CE(T, E) : T;
}
const k = (T, E) => r`${T} = ${g(E, T)}`, ST = (T, E) => r`${T} <> ${g(E, T)}`;
function h(...T) {
const E = T.filter(
(e) => e !== void 0
);
if (E.length !== 0)
return E.length === 1 ? new H(E) : new H([
new f("("),
r.join(E, new f(" and ")),
new f(")")
]);
}
function PE(...T) {
const E = T.filter(
(e) => e !== void 0
);
if (E.length !== 0)
return E.length === 1 ? new H(E) : new H([
new f("("),
r.join(E, new f(" or ")),
new f(")")
]);
}
const ME = (T, E) => r`${T} > ${g(E, T)}`, Q = (T, E) => r`${T} >= ${g(E, T)}`, UE = (T, E) => r`${T} < ${g(E, T)}`, Z = (T, E) => r`${T} <= ${g(E, T)}`;
function lE(T, E) {
return Array.isArray(E) ? E.length === 0 ? r`false` : r`${T} in ${E.map((e) => g(e, T))}` : r`${T} in ${g(E, T)}`;
}
function IT(T, E) {
return Array.isArray(E) ? E.length === 0 ? r`true` : r`${T} not in ${E.map((e) => g(e, T))}` : r`${T} not in ${g(E, T)}`;
}
function NT(T) {
return r`${T} is null`;
}
function OT(T) {
return r`${T} is not null`;
}
function tT(T) {
return r`${T} asc`;
}
function Ue(T) {
return r`${T} desc`;
}
function cE(T) {
return r`count(${T || r.raw("*")})`.mapWith(Number);
}
function le(T) {
return r`count(distinct ${T})`.mapWith(Number);
}
function y(T) {
return r`sum(${T})`.mapWith(String);
}
function AE(T) {
return r`max(${T})`.mapWith(d(T, RE) ? T : String);
}
function SE(T) {
return r`min(${T})`.mapWith(d(T, RE) ? T : String);
}
class jE {
/**
* Default implementation returns template unchanged
* Override in specific adapters for database-specific preprocessing
*/
preprocessCalculatedTemplate(E) {
return E;
}
/**
* Helper method to build pattern for string matching
* Can be overridden by specific adapters if needed
*/
buildPattern(E, e) {
switch (E) {
case "contains":
case "notContains":
return `%${e}%`;
case "startsWith":
return `${e}%`;
case "endsWith":
return `%${e}`;
default:
return e;
}
}
}
class ce extends jE {
getEngineType() {
return "postgres";
}
/**
* Build PostgreSQL time dimension using DATE_TRUNC function
* Extracted from executor.ts:649-670 and multi-cube-builder.ts:306-320
*/
buildTimeDimension(E, e) {
switch (E) {
case "year":
return r`DATE_TRUNC('year', ${e}::timestamp)`;
case "quarter":
return r`DATE_TRUNC('quarter', ${e}::timestamp)`;
case "month":
return r`DATE_TRUNC('month', ${e}::timestamp)`;
case "week":
return r`DATE_TRUNC('week', ${e}::timestamp)`;
case "day":
return r`DATE_TRUNC('day', ${e}::timestamp)::timestamp`;
case "hour":
return r`DATE_TRUNC('hour', ${e}::timestamp)`;
case "minute":
return r`DATE_TRUNC('minute', ${e}::timestamp)`;
case "second":
return r`DATE_TRUNC('second', ${e}::timestamp)`;
default:
return e;
}
}
/**
* Build PostgreSQL string matching conditions using ILIKE (case-insensitive)
* Extracted from executor.ts:807-813 and multi-cube-builder.ts:468-474
*/
buildStringCondition(E, e, R) {
switch (e) {
case "contains":
return r`${E} ILIKE ${`%${R}%`}`;
case "notContains":
return r`${E} NOT ILIKE ${`%${R}%`}`;
case "startsWith":
return r`${E} ILIKE ${`${R}%`}`;
case "endsWith":
return r`${E} ILIKE ${`%${R}`}`;
case "like":
return r`${E} LIKE ${R}`;
case "notLike":
return r`${E} NOT LIKE ${R}`;
case "ilike":
return r`${E} ILIKE ${R}`;
case "regex":
return r`${E} ~* ${R}`;
case "notRegex":
return r`${E} !~* ${R}`;
default:
throw new Error(`Unsupported string operator: ${e}`);
}
}
/**
* Build PostgreSQL type casting using :: syntax
* Extracted from various locations where ::timestamp was used
*/
castToType(E, e) {
switch (e) {
case "timestamp":
return r`${E}::timestamp`;
case "decimal":
return r`${E}::decimal`;
case "integer":
return r`${E}::integer`;
default:
throw new Error(`Unsupported cast type: ${e}`);
}
}
/**
* Build PostgreSQL AVG aggregation with COALESCE for NULL handling
* PostgreSQL AVG returns NULL for empty sets, so we use COALESCE for consistent behavior
* Extracted from multi-cube-builder.ts:284
*/
buildAvg(E) {
return r`COALESCE(AVG(${E}), 0)`;
}
/**
* Build PostgreSQL CASE WHEN conditional expression
*/
buildCaseWhen(E, e) {
const R = E.map((A) => r`WHEN ${A.when} THEN ${A.then}`).reduce((A, S) => r`${A} ${S}`);
return e !== void 0 ? r`CASE ${R} ELSE ${e} END` : r`CASE ${R} END`;
}
/**
* Build PostgreSQL boolean literal
* PostgreSQL uses TRUE/FALSE keywords
*/
buildBooleanLiteral(E) {
return E ? r`TRUE` : r`FALSE`;
}
/**
* Convert filter values - PostgreSQL uses native types
* No conversion needed for PostgreSQL
*/
convertFilterValue(E) {
return E;
}
/**
* Prepare date value for PostgreSQL
* PostgreSQL accepts Date objects directly
*/
prepareDateValue(E) {
return E;
}
/**
* PostgreSQL stores timestamps as native timestamp types
*/
isTimestampInteger() {
return !1;
}
/**
* PostgreSQL time dimensions already return proper values
* No conversion needed
*/
convertTimeDimensionResult(E) {
return E;
}
}
class jT extends jE {
getEngineType() {
return "mysql";
}
/**
* Build MySQL time dimension using DATE_FORMAT function
* MySQL equivalent to PostgreSQL's DATE_TRUNC
*/
buildTimeDimension(E, e) {
const R = {
year: "%Y-01-01 00:00:00",
quarter: "%Y-%q-01 00:00:00",
// %q gives quarter (1,2,3,4), but we need to map this properly
month: "%Y-%m-01 00:00:00",
week: "%Y-%u-01 00:00:00",
// %u gives week of year
day: "%Y-%m-%d 00:00:00",
hour: "%Y-%m-%d %H:00:00",
minute: "%Y-%m-%d %H:%i:00",
second: "%Y-%m-%d %H:%i:%s"
};
switch (E) {
case "quarter":
return r`DATE_ADD(MAKEDATE(YEAR(${e}), 1), INTERVAL (QUARTER(${e}) - 1) * 3 MONTH)`;
case "week":
return r`DATE_SUB(${e}, INTERVAL WEEKDAY(${e}) DAY)`;
default:
const A = R[E];
return A ? r`STR_TO_DATE(DATE_FORMAT(${e}, ${A}), '%Y-%m-%d %H:%i:%s')` : e;
}
}
/**
* Build MySQL string matching conditions using LIKE
* MySQL LIKE is case-insensitive by default (depending on collation)
* For guaranteed case-insensitive matching, we use LOWER() functions
*/
buildStringCondition(E, e, R) {
switch (e) {
case "contains":
return r`LOWER(${E}) LIKE ${`%${R.toLowerCase()}%`}`;
case "notContains":
return r`LOWER(${E}) NOT LIKE ${`%${R.toLowerCase()}%`}`;
case "startsWith":
return r`LOWER(${E}) LIKE ${`${R.toLowerCase()}%`}`;
case "endsWith":
return r`LOWER(${E}) LIKE ${`%${R.toLowerCase()}`}`;
case "like":
return r`${E} LIKE ${R}`;
case "notLike":
return r`${E} NOT LIKE ${R}`;
case "ilike":
return r`LOWER(${E}) LIKE ${R.toLowerCase()}`;
case "regex":
return r`${E} REGEXP ${R}`;
case "notRegex":
return r`${E} NOT REGEXP ${R}`;
default:
throw new Error(`Unsupported string operator: ${e}`);
}
}
/**
* Build MySQL type casting using CAST() function
* MySQL equivalent to PostgreSQL's :: casting syntax
*/
castToType(E, e) {
switch (e) {
case "timestamp":
return r`CAST(${E} AS DATETIME)`;
case "decimal":
return r`CAST(${E} AS DECIMAL(10,2))`;
case "integer":
return r`CAST(${E} AS SIGNED INTEGER)`;
default:
throw new Error(`Unsupported cast type: ${e}`);
}
}
/**
* Build MySQL AVG aggregation with IFNULL for NULL handling
* MySQL AVG returns NULL for empty sets, using IFNULL for consistency
*/
buildAvg(E) {
return r`IFNULL(AVG(${E}), 0)`;
}
/**
* Build MySQL CASE WHEN conditional expression
*/
buildCaseWhen(E, e) {
const R = E.map((A) => r`WHEN ${A.when} THEN ${A.then}`).reduce((A, S) => r`${A} ${S}`);
return e !== void 0 ? r`CASE ${R} ELSE ${e} END` : r`CASE ${R} END`;
}
/**
* Build MySQL boolean literal
* MySQL uses TRUE/FALSE keywords (equivalent to 1/0)
*/
buildBooleanLiteral(E) {
return E ? r`TRUE` : r`FALSE`;
}
/**
* Convert filter values - MySQL uses native types
* No conversion needed for MySQL
*/
convertFilterValue(E) {
return E;
}
/**
* Prepare date value for MySQL
* MySQL accepts Date objects directly
*/
prepareDateValue(E) {
return E;
}
/**
* MySQL stores timestamps as native timestamp types
*/
isTimestampInteger() {
return !1;
}
/**
* MySQL time dimensions already return proper values
* No conversion needed
*/
convertTimeDimensionResult(E) {
return E;
}
}
class ue extends jE {
getEngineType() {
return "sqlite";
}
/**
* Build SQLite time dimension using date/datetime functions with modifiers
* For integer timestamp columns (milliseconds), first convert to datetime
* SQLite doesn't have DATE_TRUNC like PostgreSQL, so we use strftime and date modifiers
* Returns datetime strings for consistency with other databases
*/
buildTimeDimension(E, e) {
switch (E) {
case "year":
return r`datetime(${e}, 'unixepoch', 'start of year')`;
case "quarter":
const R = r`datetime(${e}, 'unixepoch')`;
return r`datetime(${R}, 'start of year',
'+' || (((CAST(strftime('%m', ${R}) AS INTEGER) - 1) / 3) * 3) || ' months')`;
case "month":
return r`datetime(${e}, 'unixepoch', 'start of month')`;
case "week":
return r`date(datetime(${e}, 'unixepoch'), 'weekday 1', '-6 days')`;
case "day":
return r`datetime(${e}, 'unixepoch', 'start of day')`;
case "hour":
const A = r`datetime(${e}, 'unixepoch')`;
return r`datetime(strftime('%Y-%m-%d %H:00:00', ${A}))`;
case "minute":
const S = r`datetime(${e}, 'unixepoch')`;
return r`datetime(strftime('%Y-%m-%d %H:%M:00', ${S}))`;
case "second":
const I = r`datetime(${e}, 'unixepoch')`;
return r`datetime(strftime('%Y-%m-%d %H:%M:%S', ${I}))`;
default:
return r`datetime(${e}, 'unixepoch')`;
}
}
/**
* Build SQLite string matching conditions using LOWER() + LIKE for case-insensitive matching
* SQLite LIKE is case-insensitive by default, but LOWER() ensures consistency
*/
buildStringCondition(E, e, R) {
switch (e) {
case "contains":
return r`LOWER(${E}) LIKE ${`%${R.toLowerCase()}%`}`;
case "notContains":
return r`LOWER(${E}) NOT LIKE ${`%${R.toLowerCase()}%`}`;
case "startsWith":
return r`LOWER(${E}) LIKE ${`${R.toLowerCase()}%`}`;
case "endsWith":
return r`LOWER(${E}) LIKE ${`%${R.toLowerCase()}`}`;
case "like":
return r`${E} LIKE ${R}`;
case "notLike":
return r`${E} NOT LIKE ${R}`;
case "ilike":
return r`LOWER(${E}) LIKE ${R.toLowerCase()}`;
case "regex":
return r`${E} GLOB ${R}`;
case "notRegex":
return r`${E} NOT GLOB ${R}`;
default:
throw new Error(`Unsupported string operator: ${e}`);
}
}
/**
* Build SQLite type casting using CAST() function
* SQLite has dynamic typing but supports CAST for consistency
*/
castToType(E, e) {
switch (e) {
case "timestamp":
return r`datetime(${E} / 1000, 'unixepoch')`;
case "decimal":
return r`CAST(${E} AS REAL)`;
case "integer":
return r`CAST(${E} AS INTEGER)`;
default:
throw new Error(`Unsupported cast type: ${e}`);
}
}
/**
* Build SQLite AVG aggregation with IFNULL for NULL handling
* SQLite AVG returns NULL for empty sets, using IFNULL for consistency
*/
buildAvg(E) {
return r`IFNULL(AVG(${E}), 0)`;
}
/**
* Build SQLite CASE WHEN conditional expression
*/
buildCaseWhen(E, e) {
const R = E.map((A) => A.then && typeof A.then == "object" && (A.then.queryChunks || A.then._ || A.then.sql) ? r`WHEN ${A.when} THEN ${r.raw("(")}${A.then}${r.raw(")")}` : r`WHEN ${A.when} THEN ${A.then}`).reduce((A, S) => r`${A} ${S}`);
return e !== void 0 ? e && typeof e == "object" && (e.queryChunks || e._ || e.sql) ? r`CASE ${R} ELSE ${r.raw("(")}${e}${r.raw(")")} END` : r`CASE ${R} ELSE ${e} END` : r`CASE ${R} END`;
}
/**
* Build SQLite boolean literal
* SQLite uses 1/0 for true/false
*/
buildBooleanLiteral(E) {
return E ? r`1` : r`0`;
}
/**
* Preprocess calculated measure templates for SQLite-specific handling
*
* SQLite performs integer division by default (5/2 = 2 instead of 2.5).
* This method wraps division operands with CAST to REAL to ensure float division.
*
* Pattern matched: {measure1} / {measure2} or {measure1} / NULLIF({measure2}, 0)
* Transforms to: CAST({measure1} AS REAL) / ...
*
* @param calculatedSql - Template string with {member} references
* @returns Preprocessed template with CAST for division operations
*/
preprocessCalculatedTemplate(E) {
const e = /(\{[^}]+\})\s*\/\s*/g;
return E.replace(e, (R, A) => `${A.replace(/\{([^}]+)\}/, "CAST({$1} AS REAL)")} / `);
}
/**
* Convert filter values to SQLite-compatible types
* SQLite doesn't support boolean types - convert boolean to integer (1/0)
* Convert Date objects to milliseconds for integer timestamp columns
*/
convertFilterValue(E) {
return typeof E == "boolean" ? E ? 1 : 0 : E instanceof Date ? E.getTime() : Array.isArray(E) ? E.map((e) => this.convertFilterValue(e)) : E;
}
/**
* Prepare date value for SQLite integer timestamp storage
* Convert Date objects to milliseconds (Unix timestamp * 1000)
*/
prepareDateValue(E) {
if (!(E instanceof Date)) {
if (typeof E == "number") return E;
if (typeof E == "string") return new Date(E).getTime();
throw new Error(`prepareDateValue expects a Date object, got ${typeof E}`);
}
return E.getTime();
}
/**
* SQLite stores timestamps as integers (milliseconds)
*/
isTimestampInteger() {
return !0;
}
/**
* Convert SQLite time dimension results back to Date objects
* SQLite time dimensions return datetime strings, but clients expect Date objects
*/
convertTimeDimensionResult(E) {
return E;
}
}
class Ge extends jT {
getEngineType() {
return "singlestore";
}
// SingleStore inherits most MySQL functionality
// Override methods here only if SingleStore-specific behavior is needed
// Note: SingleStore has some known limitations:
// - ORDER BY and LIMIT cannot be chained together in some contexts
// - Nested selects with aggregation functions are not supported
// - Serial column type only assures uniqueness (tests may need ORDER BY)
// These limitations are typically handled at the query building level
// rather than in the adapter, but can be addressed here if needed
}
function me(T) {
switch (T) {
case "postgres":
return new ce();
case "mysql":
return new jT();
case "sqlite":
return new ue();
case "singlestore":
return new Ge();
default:
throw new Error(`Unsupported database engine: ${T}`);
}
}
class kE {
constructor(E, e, R) {
this.db = E, this.schema = e;
const A = R || this.getEngineType();
this.databaseAdapter = me(A);
}
databaseAdapter;
}
class He extends kE {
async execute(E, e) {
if (E && typeof E == "object" && typeof E.execute == "function") {
const A = await E.execute();
return Array.isArray(A) ? A.map((S) => this.convertNumericFields(S, e)) : A;
}
if (!this.db.execute)
throw new Error("PostgreSQL database instance must have an execute method");
const R = await this.db.execute(E);
return Array.isArray(R) ? R.map((A) => this.convertNumericFields(A, e)) : R;
}
/**
* Convert numeric string fields to numbers (only for measure fields)
*/
convertNumericFields(E, e) {
if (!E || typeof E != "object") return E;
const R = {};
for (const [A, S] of Object.entries(E))
e && e.includes(A) ? R[A] = this.coerceToNumber(S) : R[A] = S;
return R;
}
/**
* Coerce a value to a number if it represents a numeric type
*/
coerceToNumber(E) {
if (E == null || typeof E == "number") return E;
if (typeof E == "bigint") return Number(E);
if (E && typeof E == "object") {
if (typeof E.toString == "function") {
const e = E.toString();
if (/^-?\d+(\.\d+)?$/.test(e))
return e.includes(".") ? parseFloat(e) : parseInt(e, 10);
}
if (E.constructor?.name === "Numeric" || E.constructor?.name === "Decimal" || "digits" in E || "sign" in E) {
const e = E.toString();
return parseFloat(e);
}
return E;
}
if (typeof E == "string") {
if (/^-?\d+(\.\d+)?$/.test(E))
return E.includes(".") ? parseFloat(E) : parseInt(E, 10);
if (!isNaN(parseFloat(E)) && isFinite(parseFloat(E)))
return parseFloat(E);
}
return E;
}
getEngineType() {
return "postgres";
}
}
function sT(T, E) {
return new He(T, E, "postgres");
}
class kT extends kE {
async execute(E, e) {
if (E && typeof E == "object" && typeof E.execute == "function") {
const A = await E.execute();
return Array.isArray(A) ? A.map((S) => this.convertNumericFields(S, e)) : A;
}
if (!this.db.execute)
throw new Error("MySQL database instance must have an execute method");
const R = await this.db.execute(E);
return Array.isArray(R) ? R.map((A) => this.convertNumericFields(A, e)) : R;
}
/**
* Convert numeric string fields to numbers (measure fields + numeric dimensions)
*/
convertNumericFields(E, e) {
if (!E || typeof E != "object") return E;
const R = {};
for (const [A, S] of Object.entries(E))
e && e.includes(A) ? R[A] = this.coerceToNumber(S) : R[A] = S;
return R;
}
/**
* Coerce a value to a number if it represents a numeric type
*/
coerceToNumber(E) {
if (E == null || typeof E == "number") return E;
if (typeof E == "string") {
if (/^-?\d+(\.\d+)?$/.test(E))
return E.includes(".") ? parseFloat(E) : parseInt(E, 10);
if (!isNaN(parseFloat(E)) && isFinite(parseFloat(E)))
return parseFloat(E);
}
return E;
}
getEngineType() {
return "mysql";
}
}
function de(T, E) {
return new kT(T, E, "mysql");
}
class Be extends kE {
async execute(E, e) {
if (E && typeof E == "object" && typeof E.execute == "function") {
const R = await E.execute();
return Array.isArray(R) ? R.map((A) => this.convertNumericFields(A, e)) : R;
}
try {
if (this.db.all) {
const R = this.db.all(E);
return Array.isArray(R) ? R.map((A) => this.convertNumericFields(A, e)) : R;
} else {
if (this.db.run)
return this.db.run(E);
throw new Error("SQLite database instance must have an all() or run() method");
}
} catch (R) {
throw new Error(`SQLite execution failed: ${R instanceof Error ? R.message : "Unknown error"}`);
}
}
/**
* Convert numeric string fields to numbers (only for measure fields)
*/
convertNumericFields(E, e) {
if (!E || typeof E != "object") return E;
const R = {};
for (const [A, S] of Object.entries(E))
e && e.includes(A) ? R[A] = this.coerceToNumber(S) : R[A] = S;
return R;
}
/**
* Coerce a value to a number if it represents a numeric type
*/
coerceToNumber(E) {
if (E == null || typeof E == "number") return E;
if (typeof E == "string") {
if (/^-?\d+(\.\d+)?$/.test(E))
return E.includes(".") ? parseFloat(E) : parseInt(E, 10);
if (!isNaN(parseFloat(E)) && isFinite(parseFloat(E)))
return parseFloat(E);
}
return E;
}
getEngineType() {
return "sqlite";
}
}
function CT(T, E) {
return new Be(T, E, "sqlite");
}
class pe extends kT {
getEngineType() {
return "singlestore";
}
// SingleStore-specific optimizations can be added here if needed
// For now, we inherit all behavior from MySQLExecutor since
// SingleStore is largely MySQL-compatible
}
function Fe(T, E) {
return new pe(T, E);
}
function rT(T, E, e) {
if (e)
switch (e) {
case "postgres":
return sT(T, E);
case "mysql":
return de(T, E);
case "sqlite":
return CT(T, E);
case "singlestore":
return Fe(T, E);
}
if (T.all && T.run)
return CT(T, E);
if (T.execute)
return sT(T, E);
throw new Error("Unable to determine database engine type. Please specify engineType parameter.");
}
function LT(T) {
return typeof T == "function" ? T() : T;
}
function zT(T, E) {
if (E) return E;
switch (T) {
case "belongsTo":
return "inner";
// FK should exist
case "hasOne":
return "left";
// Parent may have no child
case "hasMany":
return "left";
// Parent may have no children
case "belongsToMany":
return "left";
// Many-to-many through junction table
default:
return "left";
}
}
function Ye(T) {
return T && typeof T == "object" ? r`${r`${T}`}` : T;
}
function w(T, E) {
const e = typeof T == "function" ? T(E) : T;
return Ye(e);
}
function fe(T, E) {
if (T.relationship !== "belongsToMany" || !T.through)
throw new Error("expandBelongsToManyJoin can only be called on belongsToMany relationships with through configuration");
const { table: e, sourceKey: R, targetKey: A, securitySql: S } = T.through, I = [];
for (const N of R) {
const C = N.as || k;
I.push(C(N.source, N.target));
}
const t = [];
for (const N of A) {
const C = N.as || k;
t.push(C(N.source, N.target));
}
let s;
if (S) {
const N = S(E);
s = Array.isArray(N) ? N : [N];
}
const O = zT("belongsToMany", T.sqlJoinType);
return {
junctionJoins: [
{
joinType: O,
table: e,
condition: h(...I)
},
{
joinType: O,
table: e,
// This will be replaced with target cube table in query planner
condition: h(...t)
}
],
junctionSecurityConditions: s
};
}
class J {
dependencyGraph;
cubes;
constructor(E) {
this.cubes = E instanceof Map ? E : /* @__PURE__ */ new Map([[E.name, E]]), this.dependencyGraph = /* @__PURE__ */ new Map();
}
/**
* Extract {member} references from calculatedSql template
* Supports both {measure} and {Cube.measure} syntax
*
* @param calculatedSql - Template string with {member} references
* @returns Array of dependency information
*/
extractDependencies(E) {
const e = /\{([^}]+)\}/g, R = E.matchAll(e), A = [];
for (const S of R) {
const I = S[1].trim();
if (I.includes(".")) {
const [t, s] = I.split(".");
A.push({
measureName: I,
cubeName: t.trim(),
fieldName: s.trim()
});
} else
A.push({
measureName: I,
cubeName: null,
fieldName: I
});
}
return A;
}
/**
* Build dependency graph for all calculated measures in a cube
*
* @param cube - The cube containing measures
*/
buildGraph(E) {
for (const [e, R] of Object.entries(E.measures))
if (R.type === "calculated" && R.calculatedSql) {
const A = `${E.name}.${e}`, S = this.extractDependencies(R.calculatedSql), I = /* @__PURE__ */ new Set();
for (const t of S) {
const O = `${t.cubeName || E.name}.${t.fieldName}`;
I.add(O);
}
this.dependencyGraph.set(A, {
id: A,
dependencies: I,
inDegree: 0
});
}
this.calculateInDegrees();
}
/**
* Build dependency graph for multiple cubes
*
* @param cubes - Map of cubes to analyze
*/
buildGraphForMultipleCubes(E) {
for (const e of E.values())
this.buildGraph(e);
}
/**
* Calculate in-degree for each node (number of measures depending on it)
*/
calculateInDegrees() {
for (const E of this.dependencyGraph.values())
E.inDegree = 0;
for (const E of this.dependencyGraph.values())
for (const e of E.dependencies) {
const R = this.dependencyGraph.get(e);
R && R.inDegree++;
}
}
/**
* Perform topological sort using Kahn's algorithm
* Returns measures in dependency order (dependencies first)
*
* @param measureNames - List of measure names to sort
* @returns Sorted array of measure names
* @throws Error if circular dependency detected
*/
topologicalSort(E) {
const e = /* @__PURE__ */ new Map(), R = [], A = [];
for (const S of E) {
const I = this.dependencyGraph.get(S);
I && e.set(S, {
id: I.id,
dependencies: new Set(I.dependencies),
inDegree: 0
// Will recalculate below
});
}
for (const S of e.values()) {
let I = 0;
for (const t of S.dependencies)
e.has(t) && I++;
S.inDegree = I;
}
for (const [S, I] of e)
I.inDegree === 0 && R.push(S);
for (; R.length > 0; ) {
const S = R.shift();
A.push(S);
for (const [I, t] of e)
t.dependencies.has(S) && (t.inDegree--, t.inDegree === 0 && R.push(I));
}
if (A.length < e.size) {
const S = this.detectCycle();
throw new Error(
`Circular dependency detected in calculated measures: ${S ? S.join(" -> ") : "unknown cycle"}`
);
}
return A;
}
/**
* Detect circular dependencies using DFS
* Returns the cycle path if found, null otherwise
*
* @returns Array representing the cycle, or null
*/
detectCycle() {
const E = /* @__PURE__ */ new Set(), e = /* @__PURE__ */ new Set(), R = [];
for (const A of this.dependencyGraph.keys())
if (!E.has(A)) {
const S = this.dfs(A, E, e, R);
if (S)
return S;
}
return null;
}
/**
* DFS helper for cycle detection
*/
dfs(E, e, R, A) {
e.add(E), R.add(E), A.push(E);
const S = this.dependencyGraph.get(E);
if (!S)
return A.pop(), R.delete(E), null;
for (const I of S.dependencies)
if (e.has(I)) {
if (R.has(I)) {
const t = A.indexOf(I);
return [...A.slice(t), I];
}
} else {
const t = this.dfs(I, e, R, A);
if (t)
return t;
}
return A.pop(), R.delete(E), null;
}
/**
* Get all dependencies for a specific measure (direct and transitive)
*
* @param measureName - Full measure name (e.g., "Cube.measure")
* @returns Set of all dependency measure names
*/
getAllDependencies(E) {
const e = /* @__PURE__ */ new Set(), R = /* @__PURE__ */ new Set(), A = (S) => {
if (R.has(S)) return;
R.add(S);
const I = this.dependencyGraph.get(S);
if (I)
for (const t of I.dependencies)
e.add(t), A(t);
};
return A(E), e;
}
/**
* Validate that all dependencies exist
*
* @param cube - The cube to validate
* @throws Error if dependencies are missing
*/
validateDependencies(E) {
for (const [e, R] of Object.entries(E.measures))
if (R.type === "calculated" && R.calculatedSql) {
const A = this.extractDependencies(R.calculatedSql);
for (const S of A) {
const I = S.cubeName || E.name, t = this.cubes.get(I);
if (!t)
throw new Error(
`Calculated measure '${E.name}.${e}' references unknown cube '${I}'`
);
if (!t.measures[S.fieldName])
throw new Error(
`Calculated measure '${E.name}.${e}' references unknown measure '${S.measureName}'`
);
if (I === E.name && S.fieldName === e)
throw new Error(
`Calculated measure '${E.name}.${e}' cannot reference itself`
);
}
}
}
/**
* Auto-populate dependencies array for calculated measures
* Updates the measure objects with detected dependencies
*
* @param cube - The cube to update
*/
populateDependencies(E) {
for (const [, e] of Object.entries(E.measures))
if (e.type === "calculated" && e.calculatedSql && !e.dependencies) {
const R = this.extractDependencies(e.calculatedSql);
e.dependencies = R.map((A) => A.measureName);
}
}
/**
* Check if a measure is a calculated measure
*
* @param measure - The measure to check
* @returns True if the measure is calculated
*/
static isCalculatedMeasure(E) {
return E.type === "calculated" && !!E.calculatedSql;
}
}
function he(T, E) {
const { cube: e, allCubes: R, resolvedMeasures: A } = E, S = zE(T), I = /* @__PURE__ */ new Map();
for (const C of S) {
const { originalRef: L, cubeName: n, fieldName: i } = C, o = n || e.name;
if (!R.get(o))
throw new Error(
`Cannot substitute {${L}}: cube '${o}' not found`
);
const P = `${o}.${i}`, l = A.get(P);
if (!l)
throw new Error(
`Cannot substitute {${L}}: measure '${P}' not resolved yet. Ensure measures are resolved in dependency order.`
);
const M = l(), G = r`${M}`;
I.set(L, G);
}
const t = [], s = [];
let O = 0;
for (const C of S) {
const L = `{${C.originalRef}}`, n = T.indexOf(L, O);
if (n >= 0) {
t.push(T.substring(O, n));
const i = I.get(C.originalRef);
i && s.push(i), O = n + L.length;
}
}
if (t.push(T.substring(O)), s.length === 0)
return r.raw(T);
const N = [];
for (let C = 0; C < t.length; C++)
t[C] && N.push(new f(t[C])), C < s.length && N.push(s[C]);
return r.join(N, r.raw(""));
}
function zE(T) {
const E = /\{([^}]+)\}/g, e = T.matchAll(E), R = [];
for (const A of e) {
const S = A[1].trim();
if (S.includes(".")) {
const [I, t] = S.split(".").map((s) => s.trim());
R.push({
originalRef: S,
cubeName: I,
fieldName: t
});
} else
R.push({
originalRef: S,
cubeName: null,
fieldName: S
});
}
return R;
}
function Ve(T) {
const E = [];
let e = 0;
for (let I = 0; I < T.length; I++)
if (T[I] === "{")
e++;
else if (T[I] === "}" && (e--, e < 0)) {
E.push(`Unmatched closing brace at position ${I}`);
break;
}
e > 0 && E.push("Unmatched opening brace in template"), /\{\s*\}/.test(T) && E.push("Empty member reference {} found in template"), /\{[^}]*\{/.test(T) && E.push("Nested braces are not allowed in member references");
const S = zE(T);
for (const I of S) {
const t = I.cubeName ? `${I.cubeName}.${I.fieldName}` : I.fieldName;
/^[a-zA-Z_][a-zA-Z0-9_.]*$/.test(t) || E.push(
`Invalid member reference {${I.originalRef}}: must start with letter or underscore, and contain only letters, numbers, underscores, and dots`
), t.split(".").length > 2 && E.push(
`Invalid member reference {${I.originalRef}}: only one dot allowed (Cube.measure format)`
);
}
return {
isValid: E.length === 0,
errors: E
};
}
function uE(T, E) {
const e = zE(T), R = /* @__PURE__ */ new Set();
for (const A of e) {
const I = `${A.cubeName || E}.${A.fieldName}`;
R.add(I);
}
return Array.from(R);
}
class We {
constructor(E) {
this.databaseAdapter = E;
}
/**
* Build resolvedMeasures map for a set of measures
* This centralizes the logic for building both regular and calculated measures
* in dependency order, avoiding duplication across main queries and CTEs
*
* @param measureNames - Array of measure names to resolve (e.g., ["Employees.count", "Employees.activePercentage"])
* @param cubeMap - Map of all cubes involved in the query
* @param context - Query context with database and security context
* @param customMeasureBuilder - Optional function to override how individual measures are built
* @returns Map of measure names to SQL builder functions
*/
buildResolvedMeasures(E, e, R, A) {
const S = /* @__PURE__ */ new Map(), I = [], t = [], s = new Set(E), O = new J(e);
for (const N of e.values())
O.buildGraph(N);
for (const N of E) {
const [C, L] = N.split("."), n = e.get(C);
if (n && n.measures && n.measures[L]) {
const i = n.measures[L];
J.isCalculatedMeasure(i) ? (t.push(N), uE(i.calculatedSql, C).forEach((P) => s.add(P)), O.getAllDependencies(N).forEach((P) => {
const [l, M] = P.split("."), G = e.get(l);
if (G && G.measures[M]) {
const m = G.measures[M];
J.isCalculatedMeasure(m) && uE(m.calculatedSql, l).forEach((b) => s.add(b));
}
})) : I.push(N);
}
}
for (const N of s) {
const [C, L] = N.split("."), n = e.get(C);
if (n && n.measures && n.measures[L]) {
const i = n.measures[L];
J.isCalculatedMeasure(i) ? t.includes(N) || t.push(N) : I.includes(N) || I.push(N);
}
}
for (const N of I) {
const [C, L] = N.split("."), n = e.get(C), i = n.measures[L];
if (A) {
const o = A(N, i, n);
S.set(N, () => o);
} else
S.set(N, () => this.buildMeasureExpression(i, R));
}
if (t.length > 0) {
const N = O.topologicalSort(t);
for (const C of N) {
const [L, n] = C.split("."), i = e.get(L), o = i.measures[n];
S.set(C, () => this.buildCalculatedMeasure(
o,
i,
e,
S,
R
));
}
}
return S;
}
/**
* Build dynamic selections for measures, dimensions, and time dimensions
* Works for both single and multi-cube queries
* Handles calculated measures with dependency resolution
*/
buildSelections(E, e, R) {
const A = {}, S = E instanceof Map ? E : /* @__PURE__ */ new Map([[E.name, E]]);
if (e.dimensions)
for (const I of e.dimensions) {
const [t, s] = I.split("."), O = S.get(t);
if (O && O.dimensions && O.dimensions[s]) {
const N = O.dimensions[s], C = w(N.sql, R);
A[I] = r`${C}`.as(I);
}
}
if (e.measures) {
const I = this.buildResolvedMeasures(
e.measures,
S,
R
);
for (const t of e.measures) {
const s = I.get(t);
if (s) {
const O = s();
A[t] = r`${O}`.as(t);
}
}
}
if (e.timeDimensions)
for (const I of e.timeDimensions) {
const [t, s] = I.dimension.split("."), O = S.get(t);
if (O && O.dimensions && O.dimensions[s]) {
const N = O.dimensions[s], C = this.buildTimeDimensionExpression(
N.sql,
I.granularity,
R
);
A[I.dimension] = r`${C}`.as(I.dimension);
}
}
return Object.keys(A).length === 0 && (A.count = cE()), A;
}
/**
* Build calculated measure expression by substituting {member} references
* with resolved SQL expressions
*/
buildCalculatedMeasure(E, e, R, A, S) {
if (!E.calculatedSql)
throw new Error(
`Calculated measure '${e.name}.${E.name}' missing calculatedSql property`
);
const I = this.databaseAdapter.preprocessCalculatedTemplate(E.calculatedSql);
return he(I, {
cube: e,
allCubes: R,
resolvedMeasures: A
});
}
/**
* Build resolved measures map for a calculated measure from CTE columns
* This handles re-aggregating pre-aggregated CTE columns for calculated measures
*
* IMPORTANT: For calculated measures in CTEs, we cannot sum/avg pre-computed ratios.
* We must recalculate from the base measures that were pre-aggregated in the CTE.
*
* @param measure - The calculated measure to build
* @param cube - The cube containing this measure
* @param cteInfo - CTE metadata (alias, measures, cube reference)
* @param allCubes - Map of all cubes in the query
* @param context - Query context
* @returns SQL expression for the calculated measure using CTE column references
*/
buildCTECalculatedMeasure(E, e, R, A, S) {
if (!E.calculatedSql)
throw new Error(
`Calculated measure '${e.name}.${E.name || "unknown"}' missing calculatedSql property`
);
const I = /* @__PURE__ */ new Map(), t = uE(E.calculatedSql, e.name);
for (const s of t) {
const [O, N] = s.split("."), C = A.get(O);
if (C && C.measures[N]) {
const L = C.measures[N];
if (R.measures.includes(s)) {
const n = r`${r.identifier(R.cteAlias)}.${r.identifier(N)}`;
let i;
switch (L.type) {
case "count":
case "countDistinct":
case "sum":
i = y(n);
break;
case "avg":
i = this.databaseAdapter.buildAvg(n);
break;
case "min":
i = SE(n);
break;
case "max":
i = AE(n);
break;
case "number":
i = y(n);
break;
default:
i = y(n);
}
I.set(s, () => i);
}
}
}
return this.buildCalculatedMeasure(
E,
e,
A,
I,
S
);
}
/**
* Build measure expression for HAVING clause, handling CTE references correctly
*/
buildHavingMeasureExpression(E, e, R, A, S) {
if (S && S.preAggregationCTEs) {
const I = S.preAggregationCTEs.find((t) => t.cube.name === E);
if (I && I.measures.includes(`${E}.${e}`))
if (R.type === "calculated" && R.calculatedSql) {
const t = S.primaryCube.name === E ? S.primaryCube : S.joinCubes?.find((O) => O.cube.name === E)?.cube;
if (!t)
throw new Error(`Cube ${E} not found in query plan`);
const s = /* @__PURE__ */ new Map([[S.primaryCube.name, S.primaryCube]]);
if (S.joinCubes)
for (const O of S.joinCubes)
s.set(O.cube.name, O.cube);
return this.buildCTECalculatedMeasure(
R,
t,
I,
s,
A
);
} else {
const t = r`${r.identifier(I.cteAlias)}.${r.identifier(e)}`;
switch (R.type) {
case "count":
case "countDistinct":
case "sum":
return y(t);
case "avg":
return this.databaseAdapter.buildAvg(t);