drizzle-cube
Version:
Drizzle ORM-first semantic layer with Cube.js compatibility. Type-safe analytics and dashboards with SQL injection protection.
1,539 lines • 419 kB
JavaScript
var XT = Object.defineProperty;
var bT = (T, E, R) => E in T ? XT(T, E, { enumerable: !0, configurable: !0, writable: !0, value: R }) : T[E] = R;
var f = (T, E, R) => bT(T, typeof E != "symbol" ? E + "" : E, R);
import { sql as n, count as SE, sum as y, max as uE, min as FE, and as d, countDistinct as KT, SQL as yT, or as $T, gt as fE, lt as WE, gte as Q, lte as Z, isNull as gT, isNotNull as JT, notInArray as xT, ne as wT, inArray as XE, eq as mE, desc as vT, asc as bE } from "drizzle-orm";
class pE {
/**
* Helper method to build pattern for string matching
* Can be overridden by specific adapters if needed
*/
buildPattern(E, R) {
switch (E) {
case "contains":
case "notContains":
return `%${R}%`;
case "startsWith":
return `${R}%`;
case "endsWith":
return `%${R}`;
default:
return R;
}
}
}
class QT extends pE {
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, R) {
switch (E) {
case "year":
return n`DATE_TRUNC('year', ${R}::timestamp)`;
case "quarter":
return n`DATE_TRUNC('quarter', ${R}::timestamp)`;
case "month":
return n`DATE_TRUNC('month', ${R}::timestamp)`;
case "week":
return n`DATE_TRUNC('week', ${R}::timestamp)`;
case "day":
return n`DATE_TRUNC('day', ${R}::timestamp)::timestamp`;
case "hour":
return n`DATE_TRUNC('hour', ${R}::timestamp)`;
case "minute":
return n`DATE_TRUNC('minute', ${R}::timestamp)`;
case "second":
return n`DATE_TRUNC('second', ${R}::timestamp)`;
default:
return R;
}
}
/**
* Build PostgreSQL string matching conditions using ILIKE (case-insensitive)
* Extracted from executor.ts:807-813 and multi-cube-builder.ts:468-474
*/
buildStringCondition(E, R, A) {
const S = this.buildPattern(R, A);
switch (R) {
case "contains":
return n`${E} ILIKE ${S}`;
case "notContains":
return n`${E} NOT ILIKE ${S}`;
case "startsWith":
return n`${E} ILIKE ${S}`;
case "endsWith":
return n`${E} ILIKE ${S}`;
default:
throw new Error(`Unsupported string operator: ${R}`);
}
}
/**
* Build PostgreSQL type casting using :: syntax
* Extracted from various locations where ::timestamp was used
*/
castToType(E, R) {
switch (R) {
case "timestamp":
return n`${E}::timestamp`;
case "decimal":
return n`${E}::decimal`;
case "integer":
return n`${E}::integer`;
default:
throw new Error(`Unsupported cast type: ${R}`);
}
}
/**
* 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 n`COALESCE(AVG(${E}), 0)`;
}
/**
* Build PostgreSQL CASE WHEN conditional expression
*/
buildCaseWhen(E, R) {
const A = E.map((S) => n`WHEN ${S.when} THEN ${S.then}`).reduce((S, I) => n`${S} ${I}`);
return R !== void 0 ? n`CASE ${A} ELSE ${R} END` : n`CASE ${A} END`;
}
/**
* Build PostgreSQL boolean literal
* PostgreSQL uses TRUE/FALSE keywords
*/
buildBooleanLiteral(E) {
return E ? n`TRUE` : n`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 ZT extends pE {
getEngineType() {
return "mysql";
}
/**
* Build MySQL time dimension using DATE_FORMAT function
* MySQL equivalent to PostgreSQL's DATE_TRUNC
*/
buildTimeDimension(E, R) {
const A = {
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 n`DATE_ADD(MAKEDATE(YEAR(${R}), 1), INTERVAL (QUARTER(${R}) - 1) * 3 MONTH)`;
case "week":
return n`DATE_SUB(${R}, INTERVAL WEEKDAY(${R}) DAY)`;
default:
const S = A[E];
return S ? n`STR_TO_DATE(DATE_FORMAT(${R}, ${S}), '%Y-%m-%d %H:%i:%s')` : R;
}
}
/**
* 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, R, A) {
const S = this.buildPattern(R, A.toLowerCase());
switch (R) {
case "contains":
return n`LOWER(${E}) LIKE ${S}`;
case "notContains":
return n`LOWER(${E}) NOT LIKE ${S}`;
case "startsWith":
return n`LOWER(${E}) LIKE ${S}`;
case "endsWith":
return n`LOWER(${E}) LIKE ${S}`;
default:
throw new Error(`Unsupported string operator: ${R}`);
}
}
/**
* Build MySQL type casting using CAST() function
* MySQL equivalent to PostgreSQL's :: casting syntax
*/
castToType(E, R) {
switch (R) {
case "timestamp":
return n`CAST(${E} AS DATETIME)`;
case "decimal":
return n`CAST(${E} AS DECIMAL(10,2))`;
case "integer":
return n`CAST(${E} AS SIGNED INTEGER)`;
default:
throw new Error(`Unsupported cast type: ${R}`);
}
}
/**
* Build MySQL AVG aggregation with IFNULL for NULL handling
* MySQL AVG returns NULL for empty sets, using IFNULL for consistency
*/
buildAvg(E) {
return n`IFNULL(AVG(${E}), 0)`;
}
/**
* Build MySQL CASE WHEN conditional expression
*/
buildCaseWhen(E, R) {
const A = E.map((S) => n`WHEN ${S.when} THEN ${S.then}`).reduce((S, I) => n`${S} ${I}`);
return R !== void 0 ? n`CASE ${A} ELSE ${R} END` : n`CASE ${A} END`;
}
/**
* Build MySQL boolean literal
* MySQL uses TRUE/FALSE keywords (equivalent to 1/0)
*/
buildBooleanLiteral(E) {
return E ? n`TRUE` : n`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 qT extends pE {
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, R) {
switch (E) {
case "year":
return n`datetime(${R}, 'unixepoch', 'start of year')`;
case "quarter":
const A = n`datetime(${R}, 'unixepoch')`;
return n`datetime(${A}, 'start of year',
'+' || (((CAST(strftime('%m', ${A}) AS INTEGER) - 1) / 3) * 3) || ' months')`;
case "month":
return n`datetime(${R}, 'unixepoch', 'start of month')`;
case "week":
return n`date(datetime(${R}, 'unixepoch'), 'weekday 1', '-6 days')`;
case "day":
return n`datetime(${R}, 'unixepoch', 'start of day')`;
case "hour":
const S = n`datetime(${R}, 'unixepoch')`;
return n`datetime(strftime('%Y-%m-%d %H:00:00', ${S}))`;
case "minute":
const I = n`datetime(${R}, 'unixepoch')`;
return n`datetime(strftime('%Y-%m-%d %H:%M:00', ${I}))`;
case "second":
const N = n`datetime(${R}, 'unixepoch')`;
return n`datetime(strftime('%Y-%m-%d %H:%M:%S', ${N}))`;
default:
return n`datetime(${R}, '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, R, A) {
const S = this.buildPattern(R, A.toLowerCase());
switch (R) {
case "contains":
return n`LOWER(${E}) LIKE ${S}`;
case "notContains":
return n`LOWER(${E}) NOT LIKE ${S}`;
case "startsWith":
return n`LOWER(${E}) LIKE ${S}`;
case "endsWith":
return n`LOWER(${E}) LIKE ${S}`;
default:
throw new Error(`Unsupported string operator: ${R}`);
}
}
/**
* Build SQLite type casting using CAST() function
* SQLite has dynamic typing but supports CAST for consistency
*/
castToType(E, R) {
switch (R) {
case "timestamp":
return n`datetime(${E} / 1000, 'unixepoch')`;
case "decimal":
return n`CAST(${E} AS REAL)`;
case "integer":
return n`CAST(${E} AS INTEGER)`;
default:
throw new Error(`Unsupported cast type: ${R}`);
}
}
/**
* Build SQLite AVG aggregation with IFNULL for NULL handling
* SQLite AVG returns NULL for empty sets, using IFNULL for consistency
*/
buildAvg(E) {
return n`IFNULL(AVG(${E}), 0)`;
}
/**
* Build SQLite CASE WHEN conditional expression
*/
buildCaseWhen(E, R) {
const A = E.map((S) => S.then && typeof S.then == "object" && (S.then.queryChunks || S.then._ || S.then.sql) ? n`WHEN ${S.when} THEN ${n.raw("(")}${S.then}${n.raw(")")}` : n`WHEN ${S.when} THEN ${S.then}`).reduce((S, I) => n`${S} ${I}`);
return R !== void 0 ? R && typeof R == "object" && (R.queryChunks || R._ || R.sql) ? n`CASE ${A} ELSE ${n.raw("(")}${R}${n.raw(")")} END` : n`CASE ${A} ELSE ${R} END` : n`CASE ${A} END`;
}
/**
* Build SQLite boolean literal
* SQLite uses 1/0 for true/false
*/
buildBooleanLiteral(E) {
return E ? n`1` : n`0`;
}
/**
* 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((R) => this.convertFilterValue(R)) : 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;
}
}
function kT(T) {
switch (T) {
case "postgres":
return new QT();
case "mysql":
return new ZT();
case "sqlite":
return new qT();
default:
throw new Error(`Unsupported database engine: ${T}`);
}
}
function uO(T, E) {
return {
...E,
name: E.name
};
}
class dE {
constructor(E, R, A) {
f(this, "databaseAdapter");
this.db = E, this.schema = R;
const S = A || this.getEngineType();
this.databaseAdapter = kT(S);
}
}
class jT extends dE {
async execute(E, R) {
if (E && typeof E == "object") {
if (typeof E.execute == "function") {
const S = await E.execute();
return Array.isArray(S) ? S.map((I) => this.convertNumericFields(I, R)) : S;
}
if (this.db && typeof this.db.execute == "function")
try {
const S = await this.db.execute(E);
return Array.isArray(S) ? S.map((I) => this.convertNumericFields(I, R)) : S;
} catch (S) {
if (typeof E.getSQL == "function") {
const I = E.getSQL(), N = await this.db.execute(I);
return Array.isArray(N) ? N.map((C) => this.convertNumericFields(C, R)) : N;
}
throw S;
}
}
if (!this.db.execute)
throw new Error("PostgreSQL database instance must have an execute method");
const A = await this.db.execute(E);
return Array.isArray(A) ? A.map((S) => this.convertNumericFields(S, R)) : A;
}
/**
* Convert numeric string fields to numbers (only for measure fields)
*/
convertNumericFields(E, R) {
if (!E || typeof E != "object") return E;
const A = {};
for (const [S, I] of Object.entries(E))
R && R.includes(S) ? A[S] = this.coerceToNumber(I) : A[S] = I;
return A;
}
/**
* Coerce a value to a number if it represents a numeric type
*/
coerceToNumber(E) {
var R, A;
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 S = E.toString();
if (/^-?\d+(\.\d+)?$/.test(S))
return S.includes(".") ? parseFloat(S) : parseInt(S, 10);
}
if (((R = E.constructor) == null ? void 0 : R.name) === "Numeric" || ((A = E.constructor) == null ? void 0 : A.name) === "Decimal" || "digits" in E || "sign" in E) {
const S = E.toString();
return parseFloat(S);
}
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";
}
}
class zT extends dE {
async execute(E, R) {
if (E && typeof E == "object" && typeof E.execute == "function") {
const A = await E.execute();
return Array.isArray(A) ? A.map((S) => this.convertNumericFields(S, R)) : A;
}
try {
if (this.db.all) {
const A = this.db.all(E);
return Array.isArray(A) ? A.map((S) => this.convertNumericFields(S, R)) : A;
} else {
if (this.db.run)
return this.db.run(E);
throw new Error("SQLite database instance must have an all() or run() method");
}
} catch (A) {
throw new Error(`SQLite execution failed: ${A instanceof Error ? A.message : "Unknown error"}`);
}
}
/**
* Convert numeric string fields to numbers (only for measure fields)
*/
convertNumericFields(E, R) {
if (!E || typeof E != "object") return E;
const A = {};
for (const [S, I] of Object.entries(E))
R && R.includes(S) ? A[S] = this.coerceToNumber(I) : A[S] = I;
return A;
}
/**
* 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";
}
}
class ER extends dE {
async execute(E, R) {
if (E && typeof E == "object" && typeof E.execute == "function") {
const S = await E.execute();
return Array.isArray(S) ? S.map((I) => this.convertNumericFields(I, R)) : S;
}
if (!this.db.execute)
throw new Error("MySQL database instance must have an execute method");
const A = await this.db.execute(E);
return Array.isArray(A) ? A.map((S) => this.convertNumericFields(S, R)) : A;
}
/**
* Convert numeric string fields to numbers (measure fields + numeric dimensions)
*/
convertNumericFields(E, R) {
if (!E || typeof E != "object") return E;
const A = {};
for (const [S, I] of Object.entries(E))
R && R.includes(S) ? A[S] = this.coerceToNumber(I) : A[S] = I;
return A;
}
/**
* 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 KE(T, E) {
return new jT(T, E, "postgres");
}
function yE(T, E) {
return new zT(T, E, "sqlite");
}
function TR(T, E) {
return new ER(T, E, "mysql");
}
function $E(T, E, R) {
if (R)
switch (R) {
case "postgres":
return KE(T, E);
case "mysql":
return TR(T, E);
case "sqlite":
return yE(T, E);
}
if (T.all && T.run)
return yE(T, E);
if (T.execute)
return KE(T, E);
throw new Error("Unable to determine database engine type. Please specify engineType parameter.");
}
function FO(T, E) {
return {
name: T,
...E
};
}
function gE(T) {
return typeof T == "function" ? T() : T;
}
function RR(T, E) {
if (E) return E;
switch (T) {
case "belongsTo":
return "inner";
case "hasOne":
return "left";
case "hasMany":
return "left";
default:
return "left";
}
}
function K(T, E) {
return typeof T == "function" ? T(E) : T;
}
function YO(T, E, R) {
return {
...T,
cubes: E,
currentCube: R
};
}
class AR {
constructor(E) {
this.databaseAdapter = E;
}
/**
* Build dynamic selections for measures, dimensions, and time dimensions
* Works for both single and multi-cube queries
*/
buildSelections(E, R, A) {
const S = {}, I = E instanceof Map ? E : /* @__PURE__ */ new Map([[E.name, E]]);
if (R.dimensions)
for (const N of R.dimensions) {
const [C, r] = N.split("."), O = I.get(C);
if (O && O.dimensions && O.dimensions[r]) {
const t = O.dimensions[r], e = K(t.sql, A);
S[N] = n`${e}`.as(N);
}
}
if (R.measures)
for (const N of R.measures) {
const [C, r] = N.split("."), O = I.get(C);
if (O && O.measures && O.measures[r]) {
const t = O.measures[r], e = this.buildMeasureExpression(t, A);
S[N] = n`${e}`.as(N);
}
}
if (R.timeDimensions)
for (const N of R.timeDimensions) {
const [C, r] = N.dimension.split("."), O = I.get(C);
if (O && O.dimensions && O.dimensions[r]) {
const t = O.dimensions[r], e = this.buildTimeDimensionExpression(
t.sql,
N.granularity,
A
);
S[N.dimension] = n`${e}`.as(N.dimension);
}
}
return Object.keys(S).length === 0 && (S.count = SE()), S;
}
/**
* Build measure expression for HAVING clause, handling CTE references correctly
*/
buildHavingMeasureExpression(E, R, A, S, I) {
if (I && I.preAggregationCTEs) {
const N = I.preAggregationCTEs.find((C) => C.cube.name === E);
if (N && N.measures.includes(`${E}.${R}`)) {
const C = n`${n.identifier(N.cteAlias)}.${n.identifier(R)}`;
switch (A.type) {
case "count":
case "countDistinct":
case "sum":
return y(C);
case "avg":
return this.databaseAdapter.buildAvg(C);
case "min":
return FE(C);
case "max":
return uE(C);
case "number":
return y(C);
default:
return y(C);
}
}
}
return this.buildMeasureExpression(A, S);
}
/**
* Build measure expression with aggregation and filters
*/
buildMeasureExpression(E, R) {
let A = K(E.sql, R);
if (E.filters && E.filters.length > 0) {
const S = E.filters.map((I) => I(R)).filter(Boolean);
if (S.length > 0) {
const I = S.length === 1 ? S[0] : d(...S);
A = this.databaseAdapter.buildCaseWhen([
{ when: I, then: A }
]);
}
}
switch (E.type) {
case "count":
return SE(A);
case "countDistinct":
return KT(A);
case "sum":
return y(A);
case "avg":
return this.databaseAdapter.buildAvg(A);
case "min":
return FE(A);
case "max":
return uE(A);
case "number":
return A;
default:
return SE(A);
}
}
/**
* Build time dimension expression with granularity using database adapter
*/
buildTimeDimensionExpression(E, R, A) {
const S = K(E, A);
return R ? this.databaseAdapter.buildTimeDimension(R, S) : S instanceof yT ? S : n`${S}`;
}
/**
* Build WHERE conditions from semantic query filters (dimensions only)
* Works for both single and multi-cube queries
*/
buildWhereConditions(E, R, A, S) {
const I = [], N = E instanceof Map ? E : /* @__PURE__ */ new Map([[E.name, E]]);
if (R.filters && R.filters.length > 0)
for (const C of R.filters) {
const r = this.processFilter(C, N, A, "where", S);
r && I.push(r);
}
if (R.timeDimensions)
for (const C of R.timeDimensions) {
const [r, O] = C.dimension.split("."), t = N.get(r);
if (t && t.dimensions[O] && C.dateRange) {
if (S != null && S.preAggregationCTEs && S.preAggregationCTEs.some((M) => M.cube.name === r))
continue;
const e = t.dimensions[O], s = K(e.sql, A), i = this.buildDateRangeCondition(s, C.dateRange);
i && I.push(i);
}
}
return I;
}
/**
* Build HAVING conditions from semantic query filters (measures only)
* Works for both single and multi-cube queries
*/
buildHavingConditions(E, R, A, S) {
const I = [], N = E instanceof Map ? E : /* @__PURE__ */ new Map([[E.name, E]]);
if (R.filters && R.filters.length > 0)
for (const C of R.filters) {
const r = this.processFilter(C, N, A, "having", S);
r && I.push(r);
}
return I;
}
/**
* Process a single filter (basic or logical)
* @param filterType - 'where' for dimension filters, 'having' for measure filters
*/
processFilter(E, R, A, S, I) {
if ("and" in E || "or" in E) {
const i = E;
if (i.and) {
const a = i.and.map((M) => this.processFilter(M, R, A, S, I)).filter((M) => M !== null);
return a.length > 0 ? d(...a) : null;
}
if (i.or) {
const a = i.or.map((M) => this.processFilter(M, R, A, S, I)).filter((M) => M !== null);
return a.length > 0 ? $T(...a) : null;
}
}
const N = E, [C, r] = N.member.split("."), O = R.get(C);
if (!O) return null;
const t = O.dimensions[r], e = O.measures[r], s = t || e;
if (!s) return null;
if (S === "where" && t) {
if (I != null && I.preAggregationCTEs && I.preAggregationCTEs.some((M) => M.cube.name === C))
return null;
const i = K(t.sql, A);
return this.buildFilterCondition(i, N.operator, N.values, s);
} else {
if (S === "where" && e)
return null;
if (S === "having" && e) {
const i = this.buildHavingMeasureExpression(C, r, e, A, I);
return this.buildFilterCondition(i, N.operator, N.values, s);
}
}
return null;
}
/**
* Build filter condition using Drizzle operators
*/
buildFilterCondition(E, R, A, S) {
if (!A || A.length === 0)
return R === "equals" ? this.databaseAdapter.buildBooleanLiteral(!1) : null;
const I = A.filter((C) => !(C == null || C === "" || typeof C == "string" && C.includes("\0"))).map(this.databaseAdapter.convertFilterValue);
if (I.length === 0 && !["set", "notSet"].includes(R))
return R === "equals" ? this.databaseAdapter.buildBooleanLiteral(!1) : null;
const N = I[0];
switch (R) {
case "equals":
if (I.length > 1) {
if ((S == null ? void 0 : S.type) === "time") {
const C = I.map((r) => this.normalizeDate(r) || r);
return XE(E, C);
}
return XE(E, I);
} else if (I.length === 1) {
const C = (S == null ? void 0 : S.type) === "time" && this.normalizeDate(N) || N;
return mE(E, C);
}
return this.databaseAdapter.buildBooleanLiteral(!1);
case "notEquals":
return I.length > 1 ? xT(E, I) : I.length === 1 ? wT(E, N) : null;
case "contains":
return this.databaseAdapter.buildStringCondition(E, "contains", N);
case "notContains":
return this.databaseAdapter.buildStringCondition(E, "notContains", N);
case "startsWith":
return this.databaseAdapter.buildStringCondition(E, "startsWith", N);
case "endsWith":
return this.databaseAdapter.buildStringCondition(E, "endsWith", N);
case "gt":
return fE(E, N);
case "gte":
return Q(E, N);
case "lt":
return WE(E, N);
case "lte":
return Z(E, N);
case "set":
return JT(E);
case "notSet":
return gT(E);
case "inDateRange":
if (I.length >= 2) {
const C = this.normalizeDate(I[0]), r = this.normalizeDate(I[1]);
if (C && r)
return d(
Q(E, C),
Z(E, r)
);
}
return null;
case "beforeDate": {
const C = this.normalizeDate(N);
return C ? WE(E, C) : null;
}
case "afterDate": {
const C = this.normalizeDate(N);
return C ? fE(E, C) : null;
}
default:
return null;
}
}
/**
* Build date range condition for time dimensions
*/
buildDateRangeCondition(E, R) {
if (!R) return null;
if (Array.isArray(R) && R.length >= 2) {
const A = this.normalizeDate(R[0]), S = this.normalizeDate(R[1]);
return !A || !S ? null : d(
Q(E, A),
Z(E, S)
);
}
if (typeof R == "string") {
const A = this.parseRelativeDateRange(R);
if (A)
return d(
Q(E, A.start),
Z(E, A.end)
);
const S = this.normalizeDate(R);
if (!S) return null;
const I = new Date(S);
I.setUTCHours(0, 0, 0, 0);
const N = new Date(S);
return N.setUTCHours(23, 59, 59, 999), d(
Q(E, I),
Z(E, N)
);
}
return null;
}
/**
* Parse relative date range expressions like "today", "yesterday", "last 7 days", "this month", etc.
* Handles all 14 DATE_RANGE_OPTIONS from the client
*/
parseRelativeDateRange(E) {
const R = /* @__PURE__ */ new Date(), A = E.toLowerCase().trim(), S = R.getUTCFullYear(), I = R.getUTCMonth(), N = R.getUTCDate(), C = R.getUTCDay();
if (A === "today") {
const e = new Date(R);
e.setUTCHours(0, 0, 0, 0);
const s = new Date(R);
return s.setUTCHours(23, 59, 59, 999), { start: e, end: s };
}
if (A === "yesterday") {
const e = new Date(R);
e.setUTCDate(N - 1), e.setUTCHours(0, 0, 0, 0);
const s = new Date(R);
return s.setUTCDate(N - 1), s.setUTCHours(23, 59, 59, 999), { start: e, end: s };
}
if (A === "this week") {
const e = C === 0 ? -6 : 1 - C, s = new Date(R);
s.setUTCDate(N + e), s.setUTCHours(0, 0, 0, 0);
const i = new Date(s);
return i.setUTCDate(s.getUTCDate() + 6), i.setUTCHours(23, 59, 59, 999), { start: s, end: i };
}
if (A === "this month") {
const e = new Date(Date.UTC(S, I, 1, 0, 0, 0, 0)), s = new Date(Date.UTC(S, I + 1, 0, 23, 59, 59, 999));
return { start: e, end: s };
}
if (A === "this quarter") {
const e = Math.floor(I / 3), s = new Date(Date.UTC(S, e * 3, 1, 0, 0, 0, 0)), i = new Date(Date.UTC(S, e * 3 + 3, 0, 23, 59, 59, 999));
return { start: s, end: i };
}
if (A === "this year") {
const e = new Date(Date.UTC(S, 0, 1, 0, 0, 0, 0)), s = new Date(Date.UTC(S, 11, 31, 23, 59, 59, 999));
return { start: e, end: s };
}
const r = A.match(/^last\s+(\d+)\s+days?$/);
if (r) {
const e = parseInt(r[1], 10), s = new Date(R);
s.setUTCDate(N - e + 1), s.setUTCHours(0, 0, 0, 0);
const i = new Date(R);
return i.setUTCHours(23, 59, 59, 999), { start: s, end: i };
}
if (A === "last week") {
const e = C === 0 ? -13 : -6 - C, s = new Date(R);
s.setUTCDate(N + e), s.setUTCHours(0, 0, 0, 0);
const i = new Date(s);
return i.setUTCDate(s.getUTCDate() + 6), i.setUTCHours(23, 59, 59, 999), { start: s, end: i };
}
if (A === "last month") {
const e = new Date(Date.UTC(S, I - 1, 1, 0, 0, 0, 0)), s = new Date(Date.UTC(S, I, 0, 23, 59, 59, 999));
return { start: e, end: s };
}
if (A === "last quarter") {
const e = Math.floor(I / 3), s = e === 0 ? 3 : e - 1, i = e === 0 ? S - 1 : S, a = new Date(Date.UTC(i, s * 3, 1, 0, 0, 0, 0)), M = new Date(Date.UTC(i, s * 3 + 3, 0, 23, 59, 59, 999));
return { start: a, end: M };
}
if (A === "last year") {
const e = new Date(Date.UTC(S - 1, 0, 1, 0, 0, 0, 0)), s = new Date(Date.UTC(S - 1, 11, 31, 23, 59, 59, 999));
return { start: e, end: s };
}
if (A === "last 12 months") {
const e = new Date(Date.UTC(S, I - 11, 1, 0, 0, 0, 0)), s = new Date(R);
return s.setUTCHours(23, 59, 59, 999), { start: e, end: s };
}
const O = A.match(/^last\s+(\d+)\s+months?$/);
if (O) {
const e = parseInt(O[1], 10), s = new Date(Date.UTC(S, I - e + 1, 1, 0, 0, 0, 0)), i = new Date(R);
return i.setUTCHours(23, 59, 59, 999), { start: s, end: i };
}
const t = A.match(/^last\s+(\d+)\s+years?$/);
if (t) {
const e = parseInt(t[1], 10), s = new Date(Date.UTC(S - e, 0, 1, 0, 0, 0, 0)), i = new Date(R);
return i.setUTCHours(23, 59, 59, 999), { start: s, end: i };
}
return null;
}
/**
* Normalize date values to handle strings, numbers, and Date objects
* Always returns a JavaScript Date object or null
* Database-agnostic - just ensures we have a valid Date
*/
normalizeDate(E) {
if (!E) return null;
if (E instanceof Date)
return isNaN(E.getTime()) ? null : E;
if (typeof E == "number") {
const A = E < 1e10 ? E * 1e3 : E, S = new Date(A);
return isNaN(S.getTime()) ? null : S;
}
if (typeof E == "string") {
const A = new Date(E);
return isNaN(A.getTime()) ? null : A;
}
const R = new Date(E);
return isNaN(R.getTime()) ? null : R;
}
/**
* Build GROUP BY fields from dimensions and time dimensions
* Works for both single and multi-cube queries
*/
buildGroupByFields(E, R, A, S) {
var r, O;
const I = [];
if (!(R.measures && R.measures.length > 0))
return [];
const C = E instanceof Map ? E : /* @__PURE__ */ new Map([[E.name, E]]);
if (R.dimensions)
for (const t of R.dimensions) {
const [e, s] = t.split("."), i = C.get(e);
if (i && i.dimensions && i.dimensions[s])
if ((r = S == null ? void 0 : S.preAggregationCTEs) == null ? void 0 : r.some((M) => M.cube.name === e)) {
const M = S.preAggregationCTEs.find((G) => G.cube.name === e), o = M.joinKeys.find((G) => G.targetColumn === s);
if (o && o.sourceColumnObj)
I.push(o.sourceColumnObj);
else {
const G = n`${n.identifier(M.cteAlias)}.${n.identifier(s)}`;
I.push(G);
}
} else {
const M = i.dimensions[s], o = K(M.sql, A);
I.push(o);
}
}
if (R.timeDimensions)
for (const t of R.timeDimensions) {
const [e, s] = t.dimension.split("."), i = C.get(e);
if (i && i.dimensions && i.dimensions[s])
if ((O = S == null ? void 0 : S.preAggregationCTEs) == null ? void 0 : O.some((M) => M.cube.name === e)) {
const M = S.preAggregationCTEs.find((G) => G.cube.name === e), o = M.joinKeys.find((G) => G.targetColumn === s);
if (o && o.sourceColumnObj) {
const G = this.buildTimeDimensionExpression(
o.sourceColumnObj,
t.granularity,
A
);
I.push(G);
} else {
const G = n`${n.identifier(M.cteAlias)}.${n.identifier(s)}`;
I.push(G);
}
} else {
const M = i.dimensions[s], o = this.buildTimeDimensionExpression(
M.sql,
t.granularity,
A
);
I.push(o);
}
}
return I;
}
/**
* Build ORDER BY clause with automatic time dimension sorting
*/
buildOrderBy(E, R) {
var I;
const A = [], S = R || [
...E.measures || [],
...E.dimensions || [],
...((I = E.timeDimensions) == null ? void 0 : I.map((N) => N.dimension)) || []
];
if (E.order && Object.keys(E.order).length > 0)
for (const [N, C] of Object.entries(E.order)) {
if (!S.includes(N))
throw new Error(`Cannot order by '${N}': field is not selected in the query`);
const r = C === "desc" ? vT(n.identifier(N)) : bE(n.identifier(N));
A.push(r);
}
if (E.timeDimensions && E.timeDimensions.length > 0) {
const N = new Set(Object.keys(E.order || {})), C = [...E.timeDimensions].sort(
(r, O) => r.dimension.localeCompare(O.dimension)
);
for (const r of C)
N.has(r.dimension) || A.push(bE(n.identifier(r.dimension)));
}
return A;
}
/**
* Collect numeric field names (measures + numeric dimensions) for type conversion
* Works for both single and multi-cube queries
*/
collectNumericFields(E, R) {
const A = [], S = E instanceof Map ? E : /* @__PURE__ */ new Map([[E.name, E]]);
if (R.measures && A.push(...R.measures), R.dimensions)
for (const I of R.dimensions) {
const [N, C] = I.split("."), r = S.get(N);
if (r) {
const O = r.dimensions[C];
O && O.type === "number" && A.push(I);
}
}
return A;
}
/**
* Apply LIMIT and OFFSET to a query with validation
* If offset is provided without limit, add a reasonable default limit
*/
applyLimitAndOffset(E, R) {
let A = R.limit;
R.offset !== void 0 && R.offset > 0 && A === void 0 && (A = 50);
let S = E;
if (A !== void 0) {
if (A < 0)
throw new Error("Limit must be non-negative");
S = S.limit(A);
}
if (R.offset !== void 0) {
if (R.offset < 0)
throw new Error("Offset must be non-negative");
S = S.offset(R.offset);
}
return S;
}
}
class SR {
/**
* Analyze a semantic query to determine which cubes are involved
*/
analyzeCubeUsage(E) {
const R = /* @__PURE__ */ new Set();
if (E.measures)
for (const A of E.measures) {
const [S] = A.split(".");
R.add(S);
}
if (E.dimensions)
for (const A of E.dimensions) {
const [S] = A.split(".");
R.add(S);
}
if (E.timeDimensions)
for (const A of E.timeDimensions) {
const [S] = A.dimension.split(".");
R.add(S);
}
if (E.filters)
for (const A of E.filters)
this.extractCubeNamesFromFilter(A, R);
return R;
}
/**
* Recursively extract cube names from filters (handles logical filters)
*/
extractCubeNamesFromFilter(E, R) {
if ("and" in E || "or" in E) {
const A = E.and || E.or || [];
for (const S of A)
this.extractCubeNamesFromFilter(S, R);
return;
}
if ("member" in E) {
const [A] = E.member.split(".");
A && R.add(A);
}
}
/**
* Extract measures referenced in filters (for CTE inclusion)
*/
extractMeasuresFromFilters(E, R) {
const A = [];
if (!E.filters)
return A;
for (const S of E.filters)
this.extractMeasuresFromFilter(S, R, A);
return A;
}
/**
* Recursively extract measures from filters for a specific cube
*/
extractMeasuresFromFilter(E, R, A) {
if ("and" in E || "or" in E) {
const S = E.and || E.or || [];
for (const I of S)
this.extractMeasuresFromFilter(I, R, A);
return;
}
if ("member" in E) {
const S = E.member, [I] = S.split(".");
I === R && A.push(S);
}
}
/**
* Create a unified query plan that works for both single and multi-cube queries
*/
createQueryPlan(E, R, A) {
const S = this.analyzeCubeUsage(R), I = Array.from(S);
if (I.length === 0)
throw new Error("No cubes found in query");
const N = this.choosePrimaryCube(I, R, E), C = E.get(N);
if (!C)
throw new Error(`Primary cube '${N}' not found`);
if (I.length === 1)
return {
primaryCube: C,
joinCubes: [],
// Empty for single cube
selections: {},
// Will be built by QueryBuilder
whereConditions: [],
// Will be built by QueryBuilder
groupByFields: []
// Will be built by QueryBuilder
};
const r = this.buildJoinPlan(E, C, I, A), O = this.planPreAggregationCTEs(E, C, r, R);
return {
primaryCube: C,
joinCubes: r,
selections: {},
// Will be built by QueryBuilder
whereConditions: [],
// Will be built by QueryBuilder
groupByFields: [],
// Will be built by QueryBuilder
preAggregationCTEs: O
};
}
/**
* Choose the primary cube based on query analysis
* Uses a consistent strategy to avoid measure order dependencies
*/
choosePrimaryCube(E, R, A) {
if (R.dimensions && R.dimensions.length > 0 && A) {
const S = R.dimensions.map((N) => N.split(".")[0]), I = /* @__PURE__ */ new Map();
for (const N of S)
I.set(N, (I.get(N) || 0) + 1);
if (I.size > 0) {
const N = Math.max(...I.values()), C = [...I.entries()].filter(([r, O]) => O === N).map(([r, O]) => r).sort();
for (const r of C)
if (this.canReachAllCubes(r, E, A))
return r;
}
}
if (A) {
const S = /* @__PURE__ */ new Map();
for (const I of E)
if (this.canReachAllCubes(I, E, A)) {
const N = A.get(I), C = N != null && N.joins ? Object.keys(N.joins).length : 0;
S.set(I, C);
}
if (S.size > 0) {
const I = Math.max(...S.values());
return [...S.entries()].filter(([C, r]) => r === I).map(([C, r]) => C).sort()[0];
}
}
return [...E].sort()[0];
}
/**
* Check if a cube can reach all other cubes in the list via joins
*/
canReachAllCubes(E, R, A) {
const S = R.filter((I) => I !== E);
for (const I of S) {
const N = this.findJoinPath(A, E, I, /* @__PURE__ */ new Set());
if (!N || N.length === 0)
return !1;
}
return !0;
}
/**
* Build join plan for multi-cube query
* Supports both direct joins and transitive joins through intermediate cubes
*/
buildJoinPlan(E, R, A, S) {
const I = [], N = /* @__PURE__ */ new Set([R.name]), C = A.filter((r) => r !== R.name);
for (const r of C) {
if (N.has(r))
continue;
const O = this.findJoinPath(E, R.name, r, N);
if (!O || O.length === 0)
throw new Error(`No join path found from '${R.name}' to '${r}'`);
for (const { fromCube: t, toCube: e, joinDef: s } of O) {
if (N.has(e))
continue;
const i = E.get(e);
if (!i)
throw new Error(`Cube '${e}' not found`);
const a = this.buildJoinCondition(
s,
null,
// No source alias needed - use the actual column
null,
// No target alias needed - use the actual column
S
), M = RR(s.relationship, s.sqlJoinType);
I.push({
cube: i,
alias: `${e.toLowerCase()}_cube`,
joinType: M,
joinCondition: a
}), N.add(e);
}
}
return I;
}
/**
* Build join condition from new array-based join definition
*/
buildJoinCondition(E, R, A, S) {
const I = [];
for (const N of E.on) {
const C = R ? n`${n.identifier(R)}.${n.identifier(N.source.name)}` : N.source, r = A ? n`${n.identifier(A)}.${n.identifier(N.target.name)}` : N.target, O = N.as || mE;
I.push(O(C, r));
}
return d(...I);
}
/**
* Find join path from source cube to target cube
* Returns array of join steps to reach target
*/
findJoinPath(E, R, A, S) {
if (R === A)
return [];
const I = [
{ cube: R, path: [] }
], N = /* @__PURE__ */ new Set([R, ...S]);
for (; I.length > 0; ) {
const { cube: C, path: r } = I.shift(), O = E.get(C);
if (O != null && O.joins)
for (const [t, e] of Object.entries(O.joins)) {
const i = gE(e.targetCube).name;
if (N.has(i))
continue;
const a = [...r, {
fromCube: C,
toCube: i,
joinDef: e
}];
if (i === A)
return a;
N.add(i), I.push({ cube: i, path: a });
}
}
return null;
}
/**
* Plan pre-aggregation CTEs for hasMany relationships to prevent fan-out
*/
planPreAggregationCTEs(E, R, A, S) {
const I = [];
if (!S.measures || S.measures.length === 0)
return I;
for (const N of A) {
const C = this.findHasManyJoinDef(R, N.cube.name);
if (!C)
continue;
const r = S.measures ? S.measures.filter(
(s) => s.startsWith(N.cube.name + ".")
) : [], O = this.extractMeasuresFromFilters(S, N.cube.name), t = [.../* @__PURE__ */ new Set([...r, ...O])];
if (t.length === 0)
continue;
const e = C.on.map((s) => ({
sourceColumn: s.source.name,
targetColumn: s.target.name,
sourceColumnObj: s.source,
targetColumnObj: s.target
}));
I.push({
cube: N.cube,
alias: N.alias,
cteAlias: `${N.cube.name.toLowerCase()}_agg`,
joinKeys: e,
measures: t
});
}
return I;
}
/**
* Find hasMany join definition from primary cube to target cube
*/
findHasManyJoinDef(E, R) {
if (!E.joins)
return null;
for (const [A, S] of Object.entries(E.joins))
if (gE(S.targetCube).name === R && S.relationship === "hasMany")
return S;
return null;
}
}
class IE {
constructor(E) {
f(this, "queryBuilder");
f(this, "queryPlanner");
f(this, "databaseAdapter");
if (this.dbExecutor = E, this.databaseAdapter = E.databaseAdapter, !this.databaseAdapter)
throw new Error("DatabaseExecutor must have a databaseAdapter property");
this.queryBuilder = new AR(this.databaseAdapter), this.queryPlanner = new SR();
}
/**
* Unified query execution method that handles both single and multi-cube queries
*/
async execute(E, R, A) {
try {
const S = hT(E, R);
if (!S.isValid)
throw new Error(`Query validation failed: ${S.errors.join(", ")}`);
const I = {
db: this.dbExecutor.db,
schema: this.dbExecutor.schema,
securityContext: A
}, N = this.queryPlanner.createQueryPlan(E, R, I), C = this.buildUnifiedQuery(N, R, I), r = this.queryBuilder.collectNumericFields(E, R), O = await this.dbExecutor.execute(C, r), t = Array.isArray(O) ? O.map((s) => {
const i = { ...s };
if (R.timeDimensions) {
for (const a of R.timeDimensions)
if (a.dimension in i) {
let M = i[a.dimension];
if (typeof M == "string" && M.match(/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}/)) {
const o = M.replace(" ", "T"), G = !o.endsWith("Z") && !o.includes("+") ? o + "Z" : o;
M = new Date(G);
}
M = this.databaseAdapter.convertTimeDimensionResult(M), i[a.dimension] = M;
}
}
return i;
}) : [O], e = this.generateAnnotations(N, R);
return {
data: t,
annotation: e
};
} catch (S) {
throw new Error(`Query execution failed: ${S instanceof Error ? S.message : "Unknown error"}`);
}
}
/**
* Legacy interface for single cube queries
*/
async executeQuery(E, R, A) {
const S = /* @__PURE__ */ new Map();
return S.set(E.name, E), this.execute(S, R, A);
}
/**
* Build pre-aggregation CTE for hasMany relationships
*/
buildPreAggregationCTE(E, R, A, S) {
var M;
const I = E.cube, N = I.sql(A), C = {};
for (const o of E.joinKeys)
if (o.targetColumnObj) {
C[o.targetColumn] = o.targetColumnObj;
for (const [G, P] of Object.entries(I.dimensions || {}))
P.sql === o.targetColumnObj && G !== o.targetColumn && (C[G] = n`${o.targetColumnObj}`.as(G));
}
for (const o of E.measures) {
const [, G] = o.split(".");
if (I.measures && I.measures[G]) {
const P = I.measures[G], H = this.queryBuilder.buildMeasureExpression(P, A);
C[G] = n`${H}`.as(G);
}
}
const r = I.name;
if (R.dimensions)
for (const o of R.dimensions) {
const [G, P] = o.split(".");
if (G === r && I.dimensions && I.dimensions[P]) {
const H = I.dimensions[P], l = this.queryBuilder.buildMeasureExpression({ sql: H.sql, type: "number" }, A);
C[P] = n`${l}`.as(P);
}
}
if (R.timeDimensions)
for (const o of R.timeDimensions) {
const [G, P] = o.dimension.split(".");
if (G === r && I.dimensions && I.dimensions[P]) {
const H = I.dimensions[P], l = this.queryBuilder.buildTimeDimensionExpression(H.sql, o.granularity, A);
C[P] = n`${l}`.as(P);
}
}
if (Object.keys(C).length === 0)
return null;
let O = A.db.select(C).from(N.from);
const t = S ? {
...S,
preAggregationCTEs: (M = S.preAggregationCTEs) == null ? void 0 : M.filter((o) => o.cube.name !== I.name)
} : void 0, e = this.queryBuilder.buildWhereConditions(I, R, A, t), s = [];
if (R.timeDimensions)
for (const o of R.timeDimensions) {
const [G, P] = o.dimension.split(".");
if (G === r && I.dimensions && I.dimensions[P] && o.dateRange) {
const H = I.dimensions[P], l = this.queryBuilder.buildMeasureExpression({ sql: H.sql, type: "number" }, A), B = this.queryBuilder.buildDateRangeCondition(l, o.dateRange);
B && s.push(B);
}
}
if (R.filters) {
for (const o of R.filters)
if (!("and" in o) && !("or" in o) && "member" in o && "operator" in o) {
const G = o, [P, H] = G.member.split(".");
if (P === r && I.dimensions && I.dimensions[H]) {
const l = I.dimensions[H];
if (G.operator === "inDateRange") {
const B = this.queryBuilder.buildMeasureExpression({ sql: l.sql, type: "number" }, A), u = this.queryBuilder.buildDateRangeCondition(B, G.values);
u && s.push(u);
}
}
}
}
const i = [];
if (N.where && i.push(N.where), i.push(...e, ...s), i.length > 0) {
const o = i.length === 1 ? i[0] : d(...i);
O = O.where(o);
}
const a = [];
for (const o of E.joinKeys)
o.targetColumnObj && a.push(o.targetColumnObj);
if (R.dimensions)
for (const o of R.dimensions) {
const [G, P] = o.split(".");
if (G === r && I.dimensions && I.dimensions[P]) {
const H = I.dimensions[P], l = K(H.sql, A);
a.push(l);
}
}
if (R.timeDimensions)
for (const o of R.timeDimensions) {
const [G, P] = o.dimension.split(".");
if (G === r && I.dimensions && I.dimensions[P]) {
const H = I.dimensions[P], l = this.queryBuilder.buildTimeDimensionExpression(H.sql, o.granularity, A);
a.push(l);
}
}
return a.length > 0 && (O = O.groupBy(...a)), A.db.$with(E.cteAlias).as(O);
}
// Removed unused getActualJoinTargetColumn method
/**
* Build join condition for CTE
*/
buildCTEJoinCondition(E, R, A) {
var N;
const S = (N = A.preAggregationCTEs) == null ? void 0 : N.find((C) => C.cube.name === E.cube.name);
if (!S)
throw new Error(`CTE info not found for cube ${E.cube.name}`);
const I = [];
for (const C of S.joinKeys) {
const r = C.sourceColumnObj || n.identifier(C.sourceColumn), O = n`${n.identifier(R)}.${n.identifier(C.targetColumn)}`;
I.push(mE(r, O));
}
return I.length === 1 ? I[0] : d(...I);
}
/**
* Build unified query that works for both single and multi-cube queries
*/
buildUnifiedQuery(E, R, A) {
var M, o, G;
const S = [], I = /* @__PURE__ */ new Map();
if (E.preAggregationCTEs && E.preAggregationCTEs.length > 0)
for (const P of E.preAggregationCTEs) {
const H = this.buildPreAggregationCTE(P, R, A, E);
H && (S.push(H), I.set(P.cube.name, P.cteAlias));
}
const N = E.primaryCube.sql(A), r = { ...this.queryBuilder.buildSelections(
E.joinCubes.length > 0 ? this.getCubesFromPlan(E) : E.primaryCub