UNPKG

drizzle-cube

Version:

Drizzle ORM-first semantic layer with Cube.js compatibility. Type-safe analytics and dashboards with SQL injection protection.

1,523 lines 613 kB
import { sql as i, eq as W, and as f, SQL as Nt, gte as q, lte as j, arrayContained as Ot, arrayOverlaps as It, arrayContains as at, isNotNull as TE, ne as sE, or as se, isNull as nE, notInArray as RE, inArray as le, lt as ue, gt as ce, StringChunk as ot, sum as y, max as ne, min as Re, count as ve, countDistinct as Ct, desc as _t, asc as AE } from "drizzle-orm"; class qe { /** * 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; } } /** * Parse ISO 8601 duration into components * Supports P[n]Y[n]M[n]DT[n]H[n]M[n]S format * @param duration - ISO 8601 duration string (e.g., "P7D", "PT1H30M", "P1DT2H") * @returns Parsed duration components */ parseISODuration(e) { const E = { years: 0, months: 0, days: 0, hours: 0, minutes: 0, seconds: 0 }, t = /^P(?:(\d+)Y)?(?:(\d+)M)?(?:(\d+)D)?(?:T(?:(\d+)H)?(?:(\d+)M)?(?:(\d+(?:\.\d+)?)S)?)?$/, T = e.match(t); if (!T) throw new Error(`Invalid ISO 8601 duration format: ${e}`); return E.years = parseInt(T[1] || "0", 10), E.months = parseInt(T[2] || "0", 10), E.days = parseInt(T[3] || "0", 10), E.hours = parseInt(T[4] || "0", 10), E.minutes = parseInt(T[5] || "0", 10), E.seconds = parseFloat(T[6] || "0"), E; } /** * Convert ISO 8601 duration to total seconds * Note: Months and years are approximated (30 days/month, 365 days/year) * @param duration - ISO 8601 duration string * @returns Total seconds */ durationToSeconds(e) { const E = this.parseISODuration(e); return E.years * 365 * 24 * 60 * 60 + E.months * 30 * 24 * 60 * 60 + E.days * 24 * 60 * 60 + E.hours * 60 * 60 + E.minutes * 60 + E.seconds; } } class Lt extends qe { getEngineType() { return "postgres"; } /** * PostgreSQL supports LATERAL joins since version 9.3 */ supportsLateralJoins() { return !0; } // ============================================ // Funnel Analysis Methods // ============================================ /** * Build PostgreSQL INTERVAL from ISO 8601 duration * PostgreSQL supports INTERVAL literal syntax: INTERVAL '7 days' */ buildIntervalFromISO(e) { const E = this.parseISODuration(e), t = []; E.years && t.push(`${E.years} years`), E.months && t.push(`${E.months} months`), E.days && t.push(`${E.days} days`), E.hours && t.push(`${E.hours} hours`), E.minutes && t.push(`${E.minutes} minutes`), E.seconds && t.push(`${E.seconds} seconds`); const T = t.join(" ") || "0 seconds"; return i`INTERVAL '${i.raw(T)}'`; } /** * Build PostgreSQL time difference in seconds using EXTRACT(EPOCH FROM ...) * Returns (end - start) as seconds */ buildTimeDifferenceSeconds(e, E) { return i`EXTRACT(EPOCH FROM (${e} - ${E}))`; } /** * Build PostgreSQL timestamp + interval expression */ buildDateAddInterval(e, E) { const t = this.buildIntervalFromISO(E); return i`(${e} + ${t})`; } /** * Build PostgreSQL conditional aggregation using FILTER clause * PostgreSQL supports the standard SQL FILTER clause for efficient conditional aggregation * Example: AVG(time_diff) FILTER (WHERE step_1_time IS NOT NULL) */ buildConditionalAggregation(e, E, t) { const T = e.toUpperCase(); return e === "count" && !E ? i`COUNT(*) FILTER (WHERE ${t})` : i`${i.raw(T)}(${E}) FILTER (WHERE ${t})`; } /** * 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 i`DATE_TRUNC('year', ${E}::timestamp)`; case "quarter": return i`DATE_TRUNC('quarter', ${E}::timestamp)`; case "month": return i`DATE_TRUNC('month', ${E}::timestamp)`; case "week": return i`DATE_TRUNC('week', ${E}::timestamp)`; case "day": return i`DATE_TRUNC('day', ${E}::timestamp)::timestamp`; case "hour": return i`DATE_TRUNC('hour', ${E}::timestamp)`; case "minute": return i`DATE_TRUNC('minute', ${E}::timestamp)`; case "second": return i`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, t) { switch (E) { case "contains": return i`${e} ILIKE ${`%${t}%`}`; case "notContains": return i`${e} NOT ILIKE ${`%${t}%`}`; case "startsWith": return i`${e} ILIKE ${`${t}%`}`; case "endsWith": return i`${e} ILIKE ${`%${t}`}`; case "like": return i`${e} LIKE ${t}`; case "notLike": return i`${e} NOT LIKE ${t}`; case "ilike": return i`${e} ILIKE ${t}`; case "regex": return i`${e} ~* ${t}`; case "notRegex": return i`${e} !~* ${t}`; 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 i`${e}::timestamp`; case "decimal": return i`${e}::decimal`; case "integer": return i`${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 i`COALESCE(AVG(${e}), 0)`; } /** * Build PostgreSQL CASE WHEN conditional expression */ buildCaseWhen(e, E) { const t = e.map((T) => i`WHEN ${T.when} THEN ${T.then}`).reduce((T, n) => i`${T} ${n}`); return E !== void 0 ? i`CASE ${t} ELSE ${E} END` : i`CASE ${t} END`; } /** * Build PostgreSQL boolean literal * PostgreSQL uses TRUE/FALSE keywords */ buildBooleanLiteral(e) { return e ? i`TRUE` : i`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; } // ============================================ // Statistical & Window Function Methods // ============================================ /** * PostgreSQL has full support for statistical and window functions */ getCapabilities() { return { supportsStddev: !0, supportsVariance: !0, supportsPercentile: !0, supportsWindowFunctions: !0, supportsFrameClause: !0, supportsLateralJoins: !0 }; } /** * Build PostgreSQL STDDEV aggregation * Uses STDDEV_POP for population, STDDEV_SAMP for sample */ buildStddev(e, E = !1) { const t = E ? "STDDEV_SAMP" : "STDDEV_POP"; return i`COALESCE(${i.raw(t)}(${e}), 0)`; } /** * Build PostgreSQL VARIANCE aggregation * Uses VAR_POP for population, VAR_SAMP for sample */ buildVariance(e, E = !1) { const t = E ? "VAR_SAMP" : "VAR_POP"; return i`COALESCE(${i.raw(t)}(${e}), 0)`; } /** * Build PostgreSQL PERCENTILE_CONT aggregation * Uses ordered-set aggregate function */ buildPercentile(e, E) { const t = E / 100; return i`PERCENTILE_CONT(${t}) WITHIN GROUP (ORDER BY ${e})`; } /** * Build PostgreSQL window function expression * PostgreSQL has full window function support */ buildWindowFunction(e, E, t, T, n) { const R = t && t.length > 0 ? i`PARTITION BY ${i.join(t, i`, `)}` : i``, A = T && T.length > 0 ? i`ORDER BY ${i.join(T.map( (I) => I.direction === "desc" ? i`${I.field} DESC` : i`${I.field} ASC` ), i`, `)}` : i``; let S = i``; if (n?.frame) { const { type: I, start: a, end: o } = n.frame, l = I.toUpperCase(), C = a === "unbounded" ? "UNBOUNDED PRECEDING" : typeof a == "number" ? `${a} PRECEDING` : "CURRENT ROW", u = o === "unbounded" ? "UNBOUNDED FOLLOWING" : o === "current" ? "CURRENT ROW" : typeof o == "number" ? `${o} FOLLOWING` : "CURRENT ROW"; S = i`${i.raw(l)} BETWEEN ${i.raw(C)} AND ${i.raw(u)}`; } const r = []; t && t.length > 0 && r.push(R), T && T.length > 0 && r.push(A), n?.frame && r.push(S); const N = r.length > 0 ? i.join(r, i` `) : i``, O = i`OVER (${N})`; switch (e) { case "lag": return i`LAG(${E}, ${n?.offset ?? 1}${n?.defaultValue !== void 0 ? i`, ${n.defaultValue}` : i``}) ${O}`; case "lead": return i`LEAD(${E}, ${n?.offset ?? 1}${n?.defaultValue !== void 0 ? i`, ${n.defaultValue}` : i``}) ${O}`; case "rank": return i`RANK() ${O}`; case "denseRank": return i`DENSE_RANK() ${O}`; case "rowNumber": return i`ROW_NUMBER() ${O}`; case "ntile": return i`NTILE(${n?.nTile ?? 4}) ${O}`; case "firstValue": return i`FIRST_VALUE(${E}) ${O}`; case "lastValue": return i`LAST_VALUE(${E}) ${O}`; case "movingAvg": return i`AVG(${E}) ${O}`; case "movingSum": return i`SUM(${E}) ${O}`; default: throw new Error(`Unsupported window function: ${e}`); } } } class xE extends qe { getEngineType() { return "mysql"; } /** * MySQL supports LATERAL joins since version 8.0.14 */ supportsLateralJoins() { return !0; } // ============================================ // Funnel Analysis Methods // ============================================ /** * Build MySQL INTERVAL from ISO 8601 duration * MySQL uses DATE_ADD with INTERVAL syntax but intervals must be added separately * For simplicity, we convert to seconds for consistent handling */ buildIntervalFromISO(e) { const E = this.parseISODuration(e), t = []; E.years && t.push(`${E.years} YEAR`), E.months && t.push(`${E.months} MONTH`), E.days && t.push(`${E.days} DAY`), E.hours && t.push(`${E.hours} HOUR`), E.minutes && t.push(`${E.minutes} MINUTE`), E.seconds && t.push(`${E.seconds} SECOND`); const T = this.durationToSeconds(e); return i`${T}`; } /** * Build MySQL time difference in seconds using TIMESTAMPDIFF * Returns (end - start) as seconds */ buildTimeDifferenceSeconds(e, E) { return i`TIMESTAMPDIFF(SECOND, ${E}, ${e})`; } /** * Build MySQL timestamp + interval expression * Uses DATE_ADD function */ buildDateAddInterval(e, E) { const t = this.parseISODuration(E); let T = e; return t.years && (T = i`DATE_ADD(${T}, INTERVAL ${t.years} YEAR)`), t.months && (T = i`DATE_ADD(${T}, INTERVAL ${t.months} MONTH)`), t.days && (T = i`DATE_ADD(${T}, INTERVAL ${t.days} DAY)`), t.hours && (T = i`DATE_ADD(${T}, INTERVAL ${t.hours} HOUR)`), t.minutes && (T = i`DATE_ADD(${T}, INTERVAL ${t.minutes} MINUTE)`), t.seconds && (T = i`DATE_ADD(${T}, INTERVAL ${t.seconds} SECOND)`), T; } /** * Build MySQL conditional aggregation using CASE WHEN * MySQL doesn't support FILTER clause, so we use CASE WHEN pattern * Example: AVG(CASE WHEN step_1_time IS NOT NULL THEN time_diff END) */ buildConditionalAggregation(e, E, t) { const T = e.toUpperCase(); return e === "count" && !E ? i`COUNT(CASE WHEN ${t} THEN 1 END)` : i`${i.raw(T)}(CASE WHEN ${t} THEN ${E} END)`; } /** * Build MySQL time dimension using DATE_FORMAT function * MySQL equivalent to PostgreSQL's DATE_TRUNC */ buildTimeDimension(e, E) { const t = { 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 i`DATE_ADD(MAKEDATE(YEAR(${E}), 1), INTERVAL (QUARTER(${E}) - 1) * 3 MONTH)`; case "week": return i`DATE_SUB(${E}, INTERVAL WEEKDAY(${E}) DAY)`; default: { const T = t[e]; return T ? i`STR_TO_DATE(DATE_FORMAT(${E}, ${T}), '%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, t) { switch (E) { case "contains": return i`LOWER(${e}) LIKE ${`%${t.toLowerCase()}%`}`; case "notContains": return i`LOWER(${e}) NOT LIKE ${`%${t.toLowerCase()}%`}`; case "startsWith": return i`LOWER(${e}) LIKE ${`${t.toLowerCase()}%`}`; case "endsWith": return i`LOWER(${e}) LIKE ${`%${t.toLowerCase()}`}`; case "like": return i`${e} LIKE ${t}`; case "notLike": return i`${e} NOT LIKE ${t}`; case "ilike": return i`LOWER(${e}) LIKE ${t.toLowerCase()}`; case "regex": return i`${e} REGEXP ${t}`; case "notRegex": return i`${e} NOT REGEXP ${t}`; 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 i`CAST(${e} AS DATETIME)`; case "decimal": return i`CAST(${e} AS DECIMAL(10,2))`; case "integer": return i`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 i`IFNULL(AVG(${e}), 0)`; } /** * Build MySQL CASE WHEN conditional expression */ buildCaseWhen(e, E) { const t = e.map((T) => i`WHEN ${T.when} THEN ${T.then}`).reduce((T, n) => i`${T} ${n}`); return E !== void 0 ? i`CASE ${t} ELSE ${E} END` : i`CASE ${t} END`; } /** * Build MySQL boolean literal * MySQL uses TRUE/FALSE keywords (equivalent to 1/0) */ buildBooleanLiteral(e) { return e ? i`TRUE` : i`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; } // ============================================ // Statistical & Window Function Methods // ============================================ /** * MySQL 8.0+ has support for statistical and window functions * but not PERCENTILE_CONT */ getCapabilities() { return { supportsStddev: !0, supportsVariance: !0, supportsPercentile: !1, // MySQL doesn't have PERCENTILE_CONT supportsWindowFunctions: !0, supportsFrameClause: !0, supportsLateralJoins: !0 // MySQL 8.0.14+ }; } /** * Build MySQL STDDEV aggregation * Uses STDDEV_POP for population, STDDEV_SAMP for sample */ buildStddev(e, E = !1) { const t = E ? "STDDEV_SAMP" : "STDDEV_POP"; return i`IFNULL(${i.raw(t)}(${e}), 0)`; } /** * Build MySQL VARIANCE aggregation * Uses VAR_POP for population, VAR_SAMP for sample */ buildVariance(e, E = !1) { const t = E ? "VAR_SAMP" : "VAR_POP"; return i`IFNULL(${i.raw(t)}(${e}), 0)`; } /** * MySQL does not support PERCENTILE_CONT * Returns null for graceful degradation */ buildPercentile(e, E) { return null; } /** * Build MySQL window function expression * MySQL 8.0+ has full window function support */ buildWindowFunction(e, E, t, T, n) { const R = t && t.length > 0 ? i`PARTITION BY ${i.join(t, i`, `)}` : i``, A = T && T.length > 0 ? i`ORDER BY ${i.join(T.map( (I) => I.direction === "desc" ? i`${I.field} DESC` : i`${I.field} ASC` ), i`, `)}` : i``; let S = i``; if (n?.frame) { const { type: I, start: a, end: o } = n.frame, l = I.toUpperCase(), C = a === "unbounded" ? "UNBOUNDED PRECEDING" : typeof a == "number" ? `${a} PRECEDING` : "CURRENT ROW", u = o === "unbounded" ? "UNBOUNDED FOLLOWING" : o === "current" ? "CURRENT ROW" : typeof o == "number" ? `${o} FOLLOWING` : "CURRENT ROW"; S = i`${i.raw(l)} BETWEEN ${i.raw(C)} AND ${i.raw(u)}`; } const r = []; t && t.length > 0 && r.push(R), T && T.length > 0 && r.push(A), n?.frame && r.push(S); const N = r.length > 0 ? i.join(r, i` `) : i``, O = i`OVER (${N})`; switch (e) { case "lag": return i`LAG(${E}, ${n?.offset ?? 1}${n?.defaultValue !== void 0 ? i`, ${n.defaultValue}` : i``}) ${O}`; case "lead": return i`LEAD(${E}, ${n?.offset ?? 1}${n?.defaultValue !== void 0 ? i`, ${n.defaultValue}` : i``}) ${O}`; case "rank": return i`RANK() ${O}`; case "denseRank": return i`DENSE_RANK() ${O}`; case "rowNumber": return i`ROW_NUMBER() ${O}`; case "ntile": return i`NTILE(${n?.nTile ?? 4}) ${O}`; case "firstValue": return i`FIRST_VALUE(${E}) ${O}`; case "lastValue": return i`LAST_VALUE(${E}) ${O}`; case "movingAvg": return i`AVG(${E}) ${O}`; case "movingSum": return i`SUM(${E}) ${O}`; default: throw new Error(`Unsupported window function: ${e}`); } } } class lt extends qe { getEngineType() { return "sqlite"; } /** * SQLite does not support LATERAL joins * Flow queries require LATERAL for efficient execution and are not supported on SQLite */ supportsLateralJoins() { return !1; } // ============================================ // Funnel Analysis Methods // ============================================ /** * Build SQLite INTERVAL from ISO 8601 duration * SQLite doesn't have native interval types, so we convert to seconds * for arithmetic operations on Unix timestamps */ buildIntervalFromISO(e) { const E = this.durationToSeconds(e); return i`${E}`; } /** * Build SQLite time difference in seconds * SQLite timestamps are stored as Unix seconds, so simple subtraction works * Returns (end - start) as seconds */ buildTimeDifferenceSeconds(e, E) { return i`(${e} - ${E})`; } /** * Build SQLite timestamp + interval expression * Since SQLite stores timestamps as Unix seconds, just add the seconds */ buildDateAddInterval(e, E) { const t = this.durationToSeconds(E); return i`(${e} + ${t})`; } /** * Build SQLite conditional aggregation using CASE WHEN * SQLite doesn't support FILTER clause, so we use CASE WHEN pattern * Example: AVG(CASE WHEN step_1_time IS NOT NULL THEN time_diff END) */ buildConditionalAggregation(e, E, t) { const T = e.toUpperCase(); return e === "count" && !E ? i`COUNT(CASE WHEN ${t} THEN 1 END)` : i`${i.raw(T)}(CASE WHEN ${t} THEN ${E} END)`; } /** * 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 i`datetime(${E}, 'unixepoch', 'start of year')`; case "quarter": { const t = i`datetime(${E}, 'unixepoch')`; return i`datetime(${t}, 'start of year', '+' || (((CAST(strftime('%m', ${t}) AS INTEGER) - 1) / 3) * 3) || ' months')`; } case "month": return i`datetime(${E}, 'unixepoch', 'start of month')`; case "week": return i`date(datetime(${E}, 'unixepoch'), 'weekday 1', '-6 days')`; case "day": return i`datetime(${E}, 'unixepoch', 'start of day')`; case "hour": { const t = i`datetime(${E}, 'unixepoch')`; return i`datetime(strftime('%Y-%m-%d %H:00:00', ${t}))`; } case "minute": { const t = i`datetime(${E}, 'unixepoch')`; return i`datetime(strftime('%Y-%m-%d %H:%M:00', ${t}))`; } case "second": { const t = i`datetime(${E}, 'unixepoch')`; return i`datetime(strftime('%Y-%m-%d %H:%M:%S', ${t}))`; } default: return i`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, t) { switch (E) { case "contains": return i`LOWER(${e}) LIKE ${`%${t.toLowerCase()}%`}`; case "notContains": return i`LOWER(${e}) NOT LIKE ${`%${t.toLowerCase()}%`}`; case "startsWith": return i`LOWER(${e}) LIKE ${`${t.toLowerCase()}%`}`; case "endsWith": return i`LOWER(${e}) LIKE ${`%${t.toLowerCase()}`}`; case "like": return i`${e} LIKE ${t}`; case "notLike": return i`${e} NOT LIKE ${t}`; case "ilike": return i`LOWER(${e}) LIKE ${t.toLowerCase()}`; case "regex": return i`${e} GLOB ${t}`; case "notRegex": return i`${e} NOT GLOB ${t}`; 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 i`datetime(${e} / 1000, 'unixepoch')`; case "decimal": return i`CAST(${e} AS REAL)`; case "integer": return i`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 i`IFNULL(AVG(${e}), 0)`; } /** * Build SQLite CASE WHEN conditional expression */ buildCaseWhen(e, E) { const t = e.map((T) => T.then && typeof T.then == "object" && (T.then.queryChunks || T.then._ || T.then.sql) ? i`WHEN ${T.when} THEN ${i.raw("(")}${T.then}${i.raw(")")}` : i`WHEN ${T.when} THEN ${T.then}`).reduce((T, n) => i`${T} ${n}`); return E !== void 0 ? E && typeof E == "object" && (E.queryChunks || E._ || E.sql) ? i`CASE ${t} ELSE ${i.raw("(")}${E}${i.raw(")")} END` : i`CASE ${t} ELSE ${E} END` : i`CASE ${t} END`; } /** * Build SQLite boolean literal * SQLite uses 1/0 for true/false */ buildBooleanLiteral(e) { return e ? i`1` : i`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, (t, T) => `${T.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; } // ============================================ // Statistical & Window Function Methods // ============================================ /** * SQLite has limited statistical support (no native STDDEV/VARIANCE/PERCENTILE) * but supports window functions since SQLite 3.25 */ getCapabilities() { return { supportsStddev: !1, // Requires extension supportsVariance: !1, // Requires extension supportsPercentile: !1, // Requires extension supportsWindowFunctions: !0, // SQLite 3.25+ supportsFrameClause: !0, supportsLateralJoins: !1 // SQLite does not support LATERAL }; } /** * SQLite does not have native STDDEV * Returns null for graceful degradation */ buildStddev(e, E = !1) { return null; } /** * SQLite does not have native VARIANCE * Returns null for graceful degradation */ buildVariance(e, E = !1) { return null; } /** * SQLite does not have native PERCENTILE * Returns null for graceful degradation */ buildPercentile(e, E) { return null; } /** * Build SQLite window function expression * SQLite 3.25+ supports window functions */ buildWindowFunction(e, E, t, T, n) { const R = t && t.length > 0 ? i`PARTITION BY ${i.join(t, i`, `)}` : i``, A = T && T.length > 0 ? i`ORDER BY ${i.join(T.map( (I) => I.direction === "desc" ? i`${I.field} DESC` : i`${I.field} ASC` ), i`, `)}` : i``; let S = i``; if (n?.frame) { const { type: I, start: a, end: o } = n.frame, l = I.toUpperCase(), C = a === "unbounded" ? "UNBOUNDED PRECEDING" : typeof a == "number" ? `${a} PRECEDING` : "CURRENT ROW", u = o === "unbounded" ? "UNBOUNDED FOLLOWING" : o === "current" ? "CURRENT ROW" : typeof o == "number" ? `${o} FOLLOWING` : "CURRENT ROW"; S = i`${i.raw(l)} BETWEEN ${i.raw(C)} AND ${i.raw(u)}`; } const r = []; t && t.length > 0 && r.push(R), T && T.length > 0 && r.push(A), n?.frame && r.push(S); const N = r.length > 0 ? i.join(r, i` `) : i``, O = i`OVER (${N})`; switch (e) { case "lag": return i`LAG(${E}, ${n?.offset ?? 1}${n?.defaultValue !== void 0 ? i`, ${n.defaultValue}` : i``}) ${O}`; case "lead": return i`LEAD(${E}, ${n?.offset ?? 1}${n?.defaultValue !== void 0 ? i`, ${n.defaultValue}` : i``}) ${O}`; case "rank": return i`RANK() ${O}`; case "denseRank": return i`DENSE_RANK() ${O}`; case "rowNumber": return i`ROW_NUMBER() ${O}`; case "ntile": return i`NTILE(${n?.nTile ?? 4}) ${O}`; case "firstValue": return i`FIRST_VALUE(${E}) ${O}`; case "lastValue": return i`LAST_VALUE(${E}) ${O}`; case "movingAvg": return i`AVG(${E}) ${O}`; case "movingSum": return i`SUM(${E}) ${O}`; default: throw new Error(`Unsupported window function: ${e}`); } } } class ut extends xE { 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 ct(s) { switch (s) { case "postgres": return new Lt(); case "mysql": return new xE(); case "sqlite": return new lt(); case "singlestore": return new ut(); default: throw new Error(`Unsupported database engine: ${s}`); } } class je { constructor(e, E, t) { this.db = e, this.schema = E; const T = t || this.getEngineType(); this.databaseAdapter = ct(T); } databaseAdapter; } function Dt(s, e) { const E = [], t = []; let T = !1, n, R, A; const S = []; for (const N of s) { const O = N.match(/Planning Time:\s*([\d.]+)\s*ms/i); if (O) { n = parseFloat(O[1]); continue; } const I = N.match(/Execution Time:\s*([\d.]+)\s*ms/i); if (I) { R = parseFloat(I[1]); continue; } const a = Pt(N); if (a) { a.type.includes("Seq Scan") && (T = !0), a.index && t.push(a.index), E.length === 0 && a.estimatedCost !== void 0 && (A = a.estimatedCost); const o = N.search(/\S/); for (; S.length > 0 && S[S.length - 1].indent >= o; ) S.pop(); if (S.length === 0) E.push(a); else { const l = S[S.length - 1].op; l.children || (l.children = []), l.children.push(a); } S.push({ indent: o, op: a }); } } const r = { database: "postgres", planningTime: n, executionTime: R, totalCost: A, hasSequentialScans: T, usedIndexes: [...new Set(t)] // Deduplicate }; return { operations: E, summary: r, raw: s.join(` `), sql: e }; } function Pt(s) { const e = s.replace(/^[\s->]+/, "").trim(); if (!e) return null; const E = e.match( /^([A-Za-z][A-Za-z0-9 ]+?)(?:\s+using\s+(\S+))?(?:\s+on\s+(\S+))?(?:\s+\w+)?(?:\s+\(cost=([\d.]+)\.\.([\d.]+)\s+rows=(\d+)(?:\s+width=\d+)?\))?(?:\s+\(actual time=([\d.]+)\.\.([\d.]+)\s+rows=(\d+)\s+loops=(\d+)\))?/i ); if (!E) return e.match(/^Filter:\s*(.+)$/i) || e.match(/^(Hash Cond|Join Filter|Index Cond):\s*(.+)$/i), null; const t = E[1].trim(), T = E[2] || void 0, n = E[3] || void 0, R = E[5] ? parseFloat(E[5]) : void 0, A = E[6] ? parseInt(E[6], 10) : void 0, S = E[9] ? parseInt(E[9], 10) : void 0, r = { type: t, table: n, index: T, estimatedRows: A, estimatedCost: R // Use end cost as the operation cost }; S !== void 0 && (r.actualRows = S); const N = s.match(/Filter:\s*(.+?)(?:\)|$)/i); return N && (r.filter = N[1].trim()), r; } class dt extends je { async execute(e, E) { if (e && typeof e == "object" && typeof e.execute == "function") { const T = await e.execute(); return Array.isArray(T) ? T.map((n) => this.convertNumericFields(n, E)) : T; } if (!this.db.execute) throw new Error("PostgreSQL database instance must have an execute method"); const t = await this.db.execute(e); return Array.isArray(t) ? t.map((T) => this.convertNumericFields(T, E)) : t; } /** * Convert numeric string fields to numbers (only for measure fields) */ convertNumericFields(e, E) { if (!e || typeof e != "object") return e; const t = {}; for (const [T, n] of Object.entries(e)) E && E.includes(T) ? t[T] = this.coerceToNumber(n) : t[T] = n; return t; } /** * 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"; } /** * Execute EXPLAIN on a SQL query to get the execution plan */ async explainQuery(e, E, t) { const T = t?.analyze ? "EXPLAIN ANALYZE" : "EXPLAIN"; if (!this.db.execute) throw new Error("PostgreSQL database instance must have an execute method"); const n = await this.db.execute( i`${i.raw(T)} ${i.raw(e.replace(/\$(\d+)/g, (A, S) => { const r = parseInt(S, 10) - 1, N = E[r]; return N === null ? "NULL" : typeof N == "number" ? String(N) : typeof N == "boolean" ? N ? "TRUE" : "FALSE" : N instanceof Date ? `'${N.toISOString()}'` : `'${String(N).replace(/'/g, "''")}'`; }))}` ), R = []; if (Array.isArray(n)) { for (const A of n) if (A && typeof A == "object") { const S = A["QUERY PLAN"] || A["query plan"] || A.queryplan; typeof S == "string" && R.push(S); } } return Dt(R, { sql: e, params: E }); } /** * Get existing indexes for the specified tables */ async getTableIndexes(e) { if (!e || e.length === 0) return []; if (!this.db.execute) throw new Error("PostgreSQL database instance must have an execute method"); try { const E = e.map((T) => `'${T.toLowerCase()}'`).join(","), t = await this.db.execute(i` SELECT t.relname as table_name, i.relname as index_name, array_to_string(array_agg(a.attname ORDER BY k.n), ',') as columns, ix.indisunique as is_unique, ix.indisprimary as is_primary FROM pg_index ix JOIN pg_class t ON t.oid = ix.indrelid JOIN pg_class i ON i.oid = ix.indexrelid JOIN pg_namespace n ON n.oid = t.relnamespace JOIN LATERAL unnest(ix.indkey) WITH ORDINALITY AS k(attnum, n) ON true JOIN pg_attribute a ON a.attrelid = t.oid AND a.attnum = k.attnum WHERE n.nspname = 'public' AND t.relname IN (${i.raw(E)}) GROUP BY t.relname, i.relname, ix.indisunique, ix.indisprimary ORDER BY t.relname, i.relname `); return Array.isArray(t) ? t.map((T) => ({ table_name: T.table_name, index_name: T.index_name, columns: T.columns.split(","), is_unique: T.is_unique, is_primary: T.is_primary })) : []; } catch (E) { return console.warn("Failed to get table indexes:", E), []; } } } function rE(s, e) { return new dt(s, e, "postgres"); } function Mt(s, e) { const E = e?.toLowerCase() || ""; switch (s.toLowerCase()) { case "all": return "Seq Scan"; // Full table scan case "index": return E.includes("using index") ? "Index Only Scan" : "Index Scan"; case "range": return "Index Range Scan"; case "ref": case "eq_ref": return "Index Lookup"; case "const": case "system": return "Const Lookup"; case "null": return "No Table"; default: return `MySQL ${s}`; } } function Ut(s, e) { const E = [], t = []; let T = !1, n = 0; for (const S of s) { const r = Mt(S.type, S.Extra); S.type.toLowerCase() === "all" && (T = !0), S.key && t.push(S.key); const N = { type: r, table: S.table || void 0, index: S.key || void 0, estimatedRows: S.rows, // MySQL doesn't have direct cost, estimate from rows estimatedCost: S.rows }; if (S.Extra) { const O = []; S.Extra.includes("Using where") && O.push("WHERE filter applied"), S.Extra.includes("Using filesort") && O.push("Filesort required"), S.Extra.includes("Using temporary") && O.push("Temporary table required"), S.Extra.includes("Using join buffer") && O.push("Join buffer used"), O.length > 0 && (N.details = O.join("; ")), N.filter = S.Extra; } E.push(N), n += S.rows; } const R = { database: "mysql", planningTime: void 0, // MySQL doesn't report planning time in standard EXPLAIN executionTime: void 0, // Only available with EXPLAIN ANALYZE in MySQL 8.0.18+ totalCost: n, hasSequentialScans: T, usedIndexes: [...new Set(t)] }, A = [ "id select_type table type possible_keys key rows Extra", ...s.map( (S) => `${S.id} ${S.select_type} ${S.table || "NULL"} ${S.type} ${S.possible_keys || "NULL"} ${S.key || "NULL"} ${S.rows} ${S.Extra || ""}` ) ]; return { operations: E, summary: R, raw: A.join(` `), sql: e }; } class JE extends je { async execute(e, E) { if (e && typeof e == "object" && typeof e.execute == "function") { const T = await e.execute(); return Array.isArray(T) ? T.map((n) => this.convertNumericFields(n, E)) : T; } if (!this.db.execute) throw new Error("MySQL database instance must have an execute method"); const t = await this.db.execute(e); return Array.isArray(t) ? t.map((T) => this.convertNumericFields(T, E)) : t; } /** * Convert numeric string fields to numbers (measure fields + numeric dimensions) */ convertNumericFields(e, E) { if (!e || typeof e != "object") return e; const t = {}; for (const [T, n] of Object.entries(e)) E && E.includes(T) ? t[T] = this.coerceToNumber(n) : t[T] = n; return t; } /** * 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"; } /** * Execute EXPLAIN on a SQL query to get the execution plan */ async explainQuery(e, E, t) { let T = e, n = 0; T = T.replace(/\?/g, () => { const r = E[n++]; return r === null ? "NULL" : typeof r == "number" ? String(r) : typeof r == "boolean" ? r ? "1" : "0" : r instanceof Date ? `'${r.toISOString().slice(0, 19).replace("T", " ")}'` : `'${String(r).replace(/'/g, "''")}'`; }); const R = t?.analyze ? "EXPLAIN ANALYZE" : "EXPLAIN"; if (!this.db.execute) throw new Error("MySQL database instance must have an execute method"); const A = await this.db.execute( i.raw(`${R} ${T}`) ), S = []; if (Array.isArray(A)) for (const r of A) r && typeof r == "object" && S.push({ id: r.id || 1, select_type: r.select_type || "SIMPLE", table: r.table || null, partitions: r.partitions || null, type: r.type || "ALL", possible_keys: r.possible_keys || null, key: r.key || null, key_len: r.key_len || null, ref: r.ref || null, rows: Number(r.rows) || 0, filtered: Number(r.filtered) || 100, Extra: r.Extra || null }); return Ut(S, { sql: e, params: E }); } /** * Get existing indexes for the specified tables */ async getTableIndexes(e) { if (!e || e.length === 0) return []; if (!this.db.execute) throw new Error("MySQL database instance must have an execute method"); try { const E = e.map((T) => `'${T.toLowerCase()}'`).join(","), t = await this.db.execute(i` SELECT TABLE_NAME as table_name, INDEX_NAME as index_name, GROUP_CONCAT(COLUMN_NAME ORDER BY SEQ_IN_INDEX) as columns, CASE WHEN NON_UNIQUE = 0 THEN TRUE ELSE FALSE END as is_unique, CASE WHEN INDEX_NAME = 'PRIMARY' THEN TRUE ELSE FALSE END as is_primary FROM INFORMATION_SCHEMA.STATISTICS WHERE TABLE_SCHEMA = DATABASE() AND LOWER(TABLE_NAME) IN (${i.raw(E)}) GROUP BY TABLE_NAME, INDEX_NAME, NON_UNIQUE ORDER BY TABLE_NAME, INDEX_NAME `); return Array.isArray(t) ? t.map((T) => ({ table_name: T.table_name, index_name: T.index_name, columns: T.columns.split(","), is_unique: !!T.is_unique, is_primary: !!T.is_primary })) : []; } catch (E) { return console.warn("Failed to get table indexes:", E), []; } } } function mt(s, e) { return new JE(s, e, "mysql"); } function pt(s) { const e = s.toLowerCase(), E = s.match(/^SCAN\s+(\S+)/i); if (E) return { type: "Seq Scan", table: E[1] }; const t = s.match( /^SEARCH\s+(\S+)\s+USING\s+(?:COVERING\s+)?INDEX\s+(\S+)(?:\s+\((.+)\))?/i ); if (t) return { type: "Index Scan", table: t[1], index: t[2], filter: t[3] }; const T = s.match( /^SEARCH\s+(\S+)\s+USING\s+INTEGER\s+PRIMARY\s+KEY\s+\((.+)\)/i ); if (T) return { type: "Primary Key Lookup", table: T[1], filter: T[2] }; const n = s.match(/^SEARCH\s+(\S+)/i); return n ? { type: "Search", table: n[1] } : e.includes("temp b-tree") ? e.includes("order by") ? { type: "Sort" } : e.includes("group by") ? { type: "Group" } : e.includes("distinct") ? { type: "Distinct" } : { type: "Temp B-Tree" } : e.includes("compound") ? { type: "Compound Query" } : e.includes("subquery") ? { type: "Subquery" } : e.includes("co-routine") ? { type: "Coroutine" } : { type: s }; } function ft(s, e) { const E = [], t = []; let T = !1; const n = /* @__PURE__ */ new Map(); for (const S of s) { const r = pt(S.detail); r.type === "Seq Scan" && (T = !0), r.index && t.push(r.index); const N = { type: r.type, table: r.table, index: r.index, filter: r.filter, details: S.detail // Keep original detail for reference }; if (n.set(S.id, N), S.parent === 0) E.push(N); else { const O = n.get(S.parent); O ? (O.children || (O.children = []), O.children.push(N)) : E.push(N); } } const R = { database: "sqlite", planningTime: void 0, // SQLite doesn't report timing executionTime: void 0, totalCost: void 0, // SQLite doesn't report costs hasSequentialScans: T, usedIndexes: [...new Set(t)] }, A = [ "id parent detail", ...s.map((S) => `${S.id} ${S.parent} ${S.detail}`) ]; return { operations: E, summary: R, raw: A.join(` `), sql: e }; } class ht extends je { async execute(e, E) { if (e && typeof e == "object" && typeof e.execute == "function") { const t = await e.execute(); return Array.isArray(t) ? t.map((T) => this.convertNumericFields(T, E)) : t; } try { if (this.db.all) { const t = this.db.all(e); return Array.isArray(t) ? t.map((T) => this.convertNumericFields(T, E)) : t; } else { if (this.db.run) return this.db.run(e); throw new Error("SQLite database instance must have an all() or run() method"); } } catch (t) { throw new Error(`SQLite execution failed: ${t instanceof Error ? t.message : "Unknown error"}`); } } /** * Convert numeric string fields to numbers (only for measure fields) */ convertNumericFields(e, E) { if (!e || typeof e != "object") return e; const t = {}; for (const [T, n] of Object.entries(e)) E && E.includes(T) ? t[T] = this.coerceToNumber(n) : t[T] = n; return t; } /** * 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"; } /** * Execute EXPLAIN QUERY PLAN on a SQL query to get the execution plan * Note: SQLite doesn't support EXPLAIN ANALYZE */ async explainQuery(e, E, t) { let T = e, n = 0; T = T.replace(/\?/g, () => { const r = E[n++]; return r === null ? "NULL" : typeof r == "number" ? String(r) : typeof r == "boolean" ? r ? "1" : "0" : r instanceof Date ? `'${r.toISOString()}'` : `'${String(r).replace(/'/g, "''")}'`; }); const R = `EXPLAIN QUERY PLAN ${T}`; let A = []; if (this.db.all) A = this.db.all(i.raw(R)); else throw new Error("SQLite database instance must have an all() method for EXPLAIN"); const S = []; if (Array.isArray(A)) for (const r of A) r && typeof r == "object" && S.push({ id: Number(r.id) || 0, parent: Number(r.parent) || 0, notused: Number(r.notused) || 0, detail: String(r.detail || "") }); return ft(S, { sql: e, params: E }); } /** * Get existing indexes for the specified tables */ async getTableIndexes(e) { if (!e || e.length === 0) return []; if (!this.db.all) throw new Error("SQLite database instance must have an all() method"); try { const E = []; for (const t of e) { const T = this.db.all( i.raw(`SELECT name, "unique", origin FROM pragma_index_list('${t.toLowerCase()}')`) ); if (Array.isArray(T)) for (const n of T) { const R = n.name, A = !!n.unique, S = n.origin, r = this.db.all( i.raw(`SELECT name FROM pragma_index_info('${R}') ORDER BY seqno`) ), N = []; if (Array.isArray(r)) for (const O of r) { const I = O.name; typeof I == "string" && N.push(I); } E.push({ table_name: t.toLowerCase(), index_name: R, columns: N, is_unique: A, is_primary: S === "pk" }); } } return E; } catch (E) { return console.warn("Failed to get table indexes:", E), []; } } } function iE(s, e) { return new ht(s, e, "sqlite"); } class Gt extends JE { 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 Bt(s, e) { return new Gt(s, e); } function SE(s, e, E) { if (E) switch (E) { case "postgres": return rE(s, e); case "mysql": return mt(s, e); case "sqlite": return iE(s, e); case "singlestore": return Bt(s, e); } if (s.all && s.run) return iE(s, e); if (s.execute) return rE(s, e); throw new Error("Unable to determine database engine type. Please specify engineType parameter."); } function Oe(s) { return typeof s == "function" ? s() : s; } function xe(s, e) { if (e) return e; switch (s) { 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 Je(s) { return s && typeof s == "object" ? i`${i`${s}`}` : s; } function h(s, e) { const E = typeof s == "function" ? s(e) : s; return Je(E); } function JA(s, e, E) { return { ...s, cubes: e, currentCube: E }; } function QA(s, e) { return { name: s, ...e }; } function Ft(s, e) { if (s.relationship !== "belongsToMany" || !s.through) throw new Error("expandBelongsToManyJoin can only be called on belongsToMany relationships with through configuration"); const { table: E, sourceKey: t, targetKey: T, securitySql: n } = s.through, R = []; for (const N of t) { const O = N.as || W; R.push(O(N.source, N.target)); } const A = []; for (const N of T) { const O = N.as || W; A.push(O(N.source, N.target)); } let S; if (n) { const N = n(e); S = Array.isArray(N) ? N : [N]; } const r = xe("belongsToMany", s.sqlJoinType); return { junctionJoins: [ { joinType: r, table: E, condition: f(...R) }, { joinType: r, table: E, // This will be replaced with target cube table in query planner condition: f(...A) } ], junctionSecurityConditions: S }; } function Ie(s) { if ("and" in s) return `and:[${s.and.map(Ie).sort().join(",")}]`; if ("or" in s) return `or