UNPKG

drizzle-cube

Version:

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

1,645 lines 435 kB
const h = Symbol.for("drizzle:entityKind"); function H(T, E) { if (!T || typeof T != "object") return !1; if (T instanceof E) return !0; if (!Object.prototype.hasOwnProperty.call(E, h)) 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 R = Object.getPrototypeOf(T).constructor; if (R) for (; R; ) { if (h in R && R[h] === E[h]) return !0; R = Object.getPrototypeOf(R); } return !1; } class TE { constructor(E, R) { this.table = E, this.config = R, this.name = R.name, this.keyAsName = R.keyAsName, this.notNull = R.notNull, this.default = R.default, this.defaultFn = R.defaultFn, this.onUpdateFn = R.onUpdateFn, this.hasDefault = R.hasDefault, this.primary = R.primaryKey, this.isUnique = R.isUnique, this.uniqueName = R.uniqueName, this.uniqueType = R.uniqueType, this.dataType = R.dataType, this.columnType = R.columnType, this.generated = R.generated, this.generatedIdentity = R.generatedIdentity; } static [h] = "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 _E = Symbol.for("drizzle:Name"), kE = Symbol.for("drizzle:isPgEnum"); function _R(T) { return !!T && typeof T == "function" && kE in T && T[kE] === !0; } class gT { static [h] = "Subquery"; constructor(E, R, A, e = !1, S = []) { this._ = { brand: "Subquery", sql: E, selectedFields: R, alias: A, isWith: e, usedTables: S }; } // getSQL(): SQL<unknown> { // return new SQL([this]); // } } const rR = { startActiveSpan(T, E) { return E(); } }, z = Symbol.for("drizzle:ViewBaseConfig"), rE = Symbol.for("drizzle:Schema"), zE = Symbol.for("drizzle:Columns"), ET = Symbol.for("drizzle:ExtraConfigColumns"), nE = Symbol.for("drizzle:OriginalName"), iE = Symbol.for("drizzle:BaseName"), IE = Symbol.for("drizzle:IsAlias"), TT = Symbol.for("drizzle:ExtraConfigBuilder"), nR = Symbol.for("drizzle:IsDrizzleTable"); class W { static [h] = "Table"; /** @internal */ static Symbol = { Name: _E, Schema: rE, OriginalName: nE, Columns: zE, ExtraConfigColumns: ET, BaseName: iE, IsAlias: IE, ExtraConfigBuilder: TT }; /** * @internal * Can be changed if the table is aliased. */ [_E]; /** * @internal * Used to store the original name of the table, before any aliasing. */ [nE]; /** @internal */ [rE]; /** @internal */ [zE]; /** @internal */ [ET]; /** * @internal * Used to store the table name before the transformation via the `tableCreator` functions. */ [iE]; /** @internal */ [IE] = !1; /** @internal */ [nR] = !0; /** @internal */ [TT] = void 0; constructor(E, R, A) { this[_E] = this[nE] = E, this[rE] = R, this[iE] = A; } } function wT(T) { return T != null && typeof T.getSQL == "function"; } function iR(T) { const E = { sql: "", params: [] }; for (const R of T) E.sql += R.sql, E.params.push(...R.params), R.typings?.length && (E.typings || (E.typings = []), E.typings.push(...R.typings)); return E; } class d { static [h] = "StringChunk"; value; constructor(E) { this.value = Array.isArray(E) ? E : [E]; } getSQL() { return new u([this]); } } class u { constructor(E) { this.queryChunks = E; for (const R of E) if (H(R, W)) { const A = R[W.Symbol.Schema]; this.usedTables.push( A === void 0 ? R[W.Symbol.Name] : A + "." + R[W.Symbol.Name] ); } } static [h] = "SQL"; /** @internal */ decoder = JT; shouldInlineParams = !1; /** @internal */ usedTables = []; append(E) { return this.queryChunks.push(...E.queryChunks), this; } toQuery(E) { return rR.startActiveSpan("drizzle.buildSQL", (R) => { const A = this.buildQueryFromSourceParams(this.queryChunks, E); return R?.setAttributes({ "drizzle.query.text": A.sql, "drizzle.query.params": JSON.stringify(A.params) }), A; }); } buildQueryFromSourceParams(E, R) { const A = Object.assign({}, R, { inlineParams: R.inlineParams || this.shouldInlineParams, paramStartIndex: R.paramStartIndex || { value: 0 } }), { casing: e, escapeName: S, escapeParam: N, prepareTyping: t, inlineParams: r, paramStartIndex: I } = A; return iR(E.map((O) => { if (H(O, d)) return { sql: O.value.join(""), params: [] }; if (H(O, JE)) return { sql: S(O.value), params: [] }; if (O === void 0) return { sql: "", params: [] }; if (Array.isArray(O)) { const s = [new d("(")]; for (const [L, a] of O.entries()) s.push(a), L < O.length - 1 && s.push(new d(", ")); return s.push(new d(")")), this.buildQueryFromSourceParams(s, A); } if (H(O, u)) return this.buildQueryFromSourceParams(O.queryChunks, { ...A, inlineParams: r || O.shouldInlineParams }); if (H(O, W)) { const s = O[W.Symbol.Schema], L = O[W.Symbol.Name]; return { sql: s === void 0 || O[IE] ? S(L) : S(s) + "." + S(L), params: [] }; } if (H(O, TE)) { const s = e.getColumnCasing(O); if (R.invokeSource === "indexes") return { sql: S(s), params: [] }; const L = O.table[W.Symbol.Schema]; return { sql: O.table[IE] || L === void 0 ? S(O.table[W.Symbol.Name]) + "." + S(s) : S(L) + "." + S(O.table[W.Symbol.Name]) + "." + S(s), params: [] }; } if (H(O, vT)) { const s = O[z].schema, L = O[z].name; return { sql: s === void 0 || O[z].isAlias ? S(L) : S(s) + "." + S(L), params: [] }; } if (H(O, OE)) { if (H(O.value, NE)) return { sql: N(I.value++, O), params: [O], typings: ["none"] }; const s = O.value === null ? null : O.encoder.mapToDriverValue(O.value); if (H(s, u)) return this.buildQueryFromSourceParams([s], A); if (r) return { sql: this.mapInlineParam(s, A), params: [] }; let L = ["none"]; return t && (L = [t(O.encoder)]), { sql: N(I.value++, s), params: [s], typings: L }; } return H(O, NE) ? { sql: N(I.value++, O), params: [O], typings: ["none"] } : H(O, u.Aliased) && O.fieldAlias !== void 0 ? { sql: S(O.fieldAlias), params: [] } : H(O, gT) ? O._.isWith ? { sql: S(O._.alias), params: [] } : this.buildQueryFromSourceParams([ new d("("), O._.sql, new d(") "), new JE(O._.alias) ], A) : _R(O) ? O.schema ? { sql: S(O.schema) + "." + S(O.enumName), params: [] } : { sql: S(O.enumName), params: [] } : wT(O) ? O.shouldOmitSQLParens?.() ? this.buildQueryFromSourceParams([O.getSQL()], A) : this.buildQueryFromSourceParams([ new d("("), O.getSQL(), new d(")") ], A) : r ? { sql: this.mapInlineParam(O, A), params: [] } : { sql: N(I.value++, O), params: [O], typings: ["none"] }; })); } mapInlineParam(E, { escapeString: R }) { if (E === null) return "null"; if (typeof E == "number" || typeof E == "boolean") return E.toString(); if (typeof E == "string") return R(E); if (typeof E == "object") { const A = E.toString(); return R(A === "[object Object]" ? JSON.stringify(E) : A); } throw new Error("Unexpected param value: " + E); } getSQL() { return this; } as(E) { return E === void 0 ? this : new u.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 JE { constructor(E) { this.value = E; } static [h] = "Name"; brand; getSQL() { return new u([this]); } } function DR(T) { return typeof T == "object" && T !== null && "mapToDriverValue" in T && typeof T.mapToDriverValue == "function"; } const JT = { mapFromDriverValue: (T) => T }, xT = { mapToDriverValue: (T) => T }; ({ ...JT, ...xT }); class OE { /** * @param value - Parameter value * @param encoder - Encoder to convert the value to a driver parameter */ constructor(E, R = xT) { this.value = E, this.encoder = R; } static [h] = "Param"; brand; getSQL() { return new u([this]); } } function C(T, ...E) { const R = []; (E.length > 0 || T.length > 0 && T[0] !== "") && R.push(new d(T[0])); for (const [A, e] of E.entries()) R.push(e, new d(T[A + 1])); return new u(R); } ((T) => { function E() { return new u([]); } T.empty = E; function R(r) { return new u(r); } T.fromList = R; function A(r) { return new u([new d(r)]); } T.raw = A; function e(r, I) { const O = []; for (const [s, L] of r.entries()) s > 0 && I !== void 0 && O.push(I), O.push(L); return new u(O); } T.join = e; function S(r) { return new JE(r); } T.identifier = S; function N(r) { return new NE(r); } T.placeholder = N; function t(r, I) { return new OE(r, I); } T.param = t; })(C || (C = {})); ((T) => { class E { constructor(A, e) { this.sql = A, this.fieldAlias = e; } static [h] = "SQL.Aliased"; /** @internal */ isSelectionField = !1; getSQL() { return this.sql; } /** @internal */ clone() { return new E(this.sql, this.fieldAlias); } } T.Aliased = E; })(u || (u = {})); class NE { constructor(E) { this.name = E; } static [h] = "Placeholder"; getSQL() { return new u([this]); } } const aR = Symbol.for("drizzle:IsDrizzleView"); class vT { static [h] = "View"; /** @internal */ [z]; /** @internal */ [aR] = !0; constructor({ name: E, schema: R, selectedFields: A, query: e }) { this[z] = { name: E, originalName: E, schema: R, selectedFields: A, query: e, isExisting: !e, isAlias: !1 }; } getSQL() { return new u([this]); } } TE.prototype.getSQL = function() { return new u([this]); }; W.prototype.getSQL = function() { return new u([this]); }; gT.prototype.getSQL = function() { return new u([this]); }; function b(T, E) { return DR(E) && !wT(T) && !H(T, OE) && !H(T, NE) && !H(T, TE) && !H(T, W) && !H(T, vT) ? new OE(T, E) : T; } const sE = (T, E) => C`${T} = ${b(E, T)}`, RT = (T, E) => C`${T} <> ${b(E, T)}`; function f(...T) { const E = T.filter( (R) => R !== void 0 ); if (E.length !== 0) return E.length === 1 ? new u(E) : new u([ new d("("), C.join(E, new d(" and ")), new d(")") ]); } function DE(...T) { const E = T.filter( (R) => R !== void 0 ); if (E.length !== 0) return E.length === 1 ? new u(E) : new u([ new d("("), C.join(E, new d(" or ")), new d(")") ]); } const aE = (T, E) => C`${T} > ${b(E, T)}`, v = (T, E) => C`${T} >= ${b(E, T)}`, oE = (T, E) => C`${T} < ${b(E, T)}`, Q = (T, E) => C`${T} <= ${b(E, T)}`; function PE(T, E) { return Array.isArray(E) ? E.length === 0 ? C`false` : C`${T} in ${E.map((R) => b(R, T))}` : C`${T} in ${b(E, T)}`; } function AT(T, E) { return Array.isArray(E) ? E.length === 0 ? C`true` : C`${T} not in ${E.map((R) => b(R, T))}` : C`${T} not in ${b(E, T)}`; } function eT(T) { return C`${T} is null`; } function ST(T) { return C`${T} is not null`; } function IT(T) { return C`${T} asc`; } function oR(T) { return C`${T} desc`; } function ME(T) { return C`count(${T || C.raw("*")})`.mapWith(Number); } function PR(T) { return C`count(distinct ${T})`.mapWith(Number); } function g(T) { return C`sum(${T})`.mapWith(String); } function xE(T) { return C`max(${T})`.mapWith(H(T, TE) ? T : String); } function vE(T) { return C`min(${T})`.mapWith(H(T, TE) ? T : String); } class ZE { /** * 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 MR extends ZE { 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 C`DATE_TRUNC('year', ${R}::timestamp)`; case "quarter": return C`DATE_TRUNC('quarter', ${R}::timestamp)`; case "month": return C`DATE_TRUNC('month', ${R}::timestamp)`; case "week": return C`DATE_TRUNC('week', ${R}::timestamp)`; case "day": return C`DATE_TRUNC('day', ${R}::timestamp)::timestamp`; case "hour": return C`DATE_TRUNC('hour', ${R}::timestamp)`; case "minute": return C`DATE_TRUNC('minute', ${R}::timestamp)`; case "second": return C`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) { switch (R) { case "contains": return C`${E} ILIKE ${`%${A}%`}`; case "notContains": return C`${E} NOT ILIKE ${`%${A}%`}`; case "startsWith": return C`${E} ILIKE ${`${A}%`}`; case "endsWith": return C`${E} ILIKE ${`%${A}`}`; case "like": return C`${E} LIKE ${A}`; case "notLike": return C`${E} NOT LIKE ${A}`; case "ilike": return C`${E} ILIKE ${A}`; case "regex": return C`${E} ~* ${A}`; case "notRegex": return C`${E} !~* ${A}`; 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 C`${E}::timestamp`; case "decimal": return C`${E}::decimal`; case "integer": return C`${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 C`COALESCE(AVG(${E}), 0)`; } /** * Build PostgreSQL CASE WHEN conditional expression */ buildCaseWhen(E, R) { const A = E.map((e) => C`WHEN ${e.when} THEN ${e.then}`).reduce((e, S) => C`${e} ${S}`); return R !== void 0 ? C`CASE ${A} ELSE ${R} END` : C`CASE ${A} END`; } /** * Build PostgreSQL boolean literal * PostgreSQL uses TRUE/FALSE keywords */ buildBooleanLiteral(E) { return E ? C`TRUE` : C`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 QT extends ZE { 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 C`DATE_ADD(MAKEDATE(YEAR(${R}), 1), INTERVAL (QUARTER(${R}) - 1) * 3 MONTH)`; case "week": return C`DATE_SUB(${R}, INTERVAL WEEKDAY(${R}) DAY)`; default: const e = A[E]; return e ? C`STR_TO_DATE(DATE_FORMAT(${R}, ${e}), '%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) { switch (R) { case "contains": return C`LOWER(${E}) LIKE ${`%${A.toLowerCase()}%`}`; case "notContains": return C`LOWER(${E}) NOT LIKE ${`%${A.toLowerCase()}%`}`; case "startsWith": return C`LOWER(${E}) LIKE ${`${A.toLowerCase()}%`}`; case "endsWith": return C`LOWER(${E}) LIKE ${`%${A.toLowerCase()}`}`; case "like": return C`${E} LIKE ${A}`; case "notLike": return C`${E} NOT LIKE ${A}`; case "ilike": return C`LOWER(${E}) LIKE ${A.toLowerCase()}`; case "regex": return C`${E} REGEXP ${A}`; case "notRegex": return C`${E} NOT REGEXP ${A}`; 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 C`CAST(${E} AS DATETIME)`; case "decimal": return C`CAST(${E} AS DECIMAL(10,2))`; case "integer": return C`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 C`IFNULL(AVG(${E}), 0)`; } /** * Build MySQL CASE WHEN conditional expression */ buildCaseWhen(E, R) { const A = E.map((e) => C`WHEN ${e.when} THEN ${e.then}`).reduce((e, S) => C`${e} ${S}`); return R !== void 0 ? C`CASE ${A} ELSE ${R} END` : C`CASE ${A} END`; } /** * Build MySQL boolean literal * MySQL uses TRUE/FALSE keywords (equivalent to 1/0) */ buildBooleanLiteral(E) { return E ? C`TRUE` : C`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 UR extends ZE { 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 C`datetime(${R}, 'unixepoch', 'start of year')`; case "quarter": const A = C`datetime(${R}, 'unixepoch')`; return C`datetime(${A}, 'start of year', '+' || (((CAST(strftime('%m', ${A}) AS INTEGER) - 1) / 3) * 3) || ' months')`; case "month": return C`datetime(${R}, 'unixepoch', 'start of month')`; case "week": return C`date(datetime(${R}, 'unixepoch'), 'weekday 1', '-6 days')`; case "day": return C`datetime(${R}, 'unixepoch', 'start of day')`; case "hour": const e = C`datetime(${R}, 'unixepoch')`; return C`datetime(strftime('%Y-%m-%d %H:00:00', ${e}))`; case "minute": const S = C`datetime(${R}, 'unixepoch')`; return C`datetime(strftime('%Y-%m-%d %H:%M:00', ${S}))`; case "second": const N = C`datetime(${R}, 'unixepoch')`; return C`datetime(strftime('%Y-%m-%d %H:%M:%S', ${N}))`; default: return C`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) { switch (R) { case "contains": return C`LOWER(${E}) LIKE ${`%${A.toLowerCase()}%`}`; case "notContains": return C`LOWER(${E}) NOT LIKE ${`%${A.toLowerCase()}%`}`; case "startsWith": return C`LOWER(${E}) LIKE ${`${A.toLowerCase()}%`}`; case "endsWith": return C`LOWER(${E}) LIKE ${`%${A.toLowerCase()}`}`; case "like": return C`${E} LIKE ${A}`; case "notLike": return C`${E} NOT LIKE ${A}`; case "ilike": return C`LOWER(${E}) LIKE ${A.toLowerCase()}`; case "regex": return C`${E} GLOB ${A}`; case "notRegex": return C`${E} NOT GLOB ${A}`; 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 C`datetime(${E} / 1000, 'unixepoch')`; case "decimal": return C`CAST(${E} AS REAL)`; case "integer": return C`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 C`IFNULL(AVG(${E}), 0)`; } /** * Build SQLite CASE WHEN conditional expression */ buildCaseWhen(E, R) { const A = E.map((e) => e.then && typeof e.then == "object" && (e.then.queryChunks || e.then._ || e.then.sql) ? C`WHEN ${e.when} THEN ${C.raw("(")}${e.then}${C.raw(")")}` : C`WHEN ${e.when} THEN ${e.then}`).reduce((e, S) => C`${e} ${S}`); return R !== void 0 ? R && typeof R == "object" && (R.queryChunks || R._ || R.sql) ? C`CASE ${A} ELSE ${C.raw("(")}${R}${C.raw(")")} END` : C`CASE ${A} ELSE ${R} END` : C`CASE ${A} END`; } /** * Build SQLite boolean literal * SQLite uses 1/0 for true/false */ buildBooleanLiteral(E) { return E ? C`1` : C`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; } } class GR extends QT { 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 lR(T) { switch (T) { case "postgres": return new MR(); case "mysql": return new QT(); case "sqlite": return new UR(); case "singlestore": return new GR(); default: throw new Error(`Unsupported database engine: ${T}`); } } class qE { constructor(E, R, A) { this.db = E, this.schema = R; const e = A || this.getEngineType(); this.databaseAdapter = lR(e); } databaseAdapter; } class cR extends qE { async execute(E, R) { if (E && typeof E == "object" && typeof E.execute == "function") { const e = await E.execute(); return Array.isArray(e) ? e.map((S) => this.convertNumericFields(S, R)) : e; } 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((e) => this.convertNumericFields(e, 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 [e, S] of Object.entries(E)) R && R.includes(e) ? A[e] = this.coerceToNumber(S) : A[e] = S; 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 == "bigint") return Number(E); if (E && typeof E == "object") { if (typeof E.toString == "function") { const R = E.toString(); if (/^-?\d+(\.\d+)?$/.test(R)) return R.includes(".") ? parseFloat(R) : parseInt(R, 10); } if (E.constructor?.name === "Numeric" || E.constructor?.name === "Decimal" || "digits" in E || "sign" in E) { const R = E.toString(); return parseFloat(R); } 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 OT(T, E) { return new cR(T, E, "postgres"); } class ZT extends qE { async execute(E, R) { if (E && typeof E == "object" && typeof E.execute == "function") { const e = await E.execute(); return Array.isArray(e) ? e.map((S) => this.convertNumericFields(S, R)) : e; } 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((e) => this.convertNumericFields(e, 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 [e, S] of Object.entries(E)) R && R.includes(e) ? A[e] = this.coerceToNumber(S) : A[e] = S; 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 uR(T, E) { return new ZT(T, E, "mysql"); } class HR extends qE { async execute(E, R) { if (E && typeof E == "object" && typeof E.execute == "function") { const A = await E.execute(); return Array.isArray(A) ? A.map((e) => this.convertNumericFields(e, R)) : A; } try { if (this.db.all) { const A = this.db.all(E); return Array.isArray(A) ? A.map((e) => this.convertNumericFields(e, 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 [e, S] of Object.entries(E)) R && R.includes(e) ? A[e] = this.coerceToNumber(S) : A[e] = S; 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"; } } function NT(T, E) { return new HR(T, E, "sqlite"); } class BR extends ZT { 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 mR(T, E) { return new BR(T, E); } function sT(T, E, R) { if (R) switch (R) { case "postgres": return OT(T, E); case "mysql": return uR(T, E); case "sqlite": return NT(T, E); case "singlestore": return mR(T, E); } if (T.all && T.run) return NT(T, E); if (T.execute) return OT(T, E); throw new Error("Unable to determine database engine type. Please specify engineType parameter."); } function tT(T) { return typeof T == "function" ? T() : T; } function FR(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 default: return "left"; } } function $(T, E) { return typeof T == "function" ? T(E) : T; } class YR { 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 e = {}, S = E instanceof Map ? E : /* @__PURE__ */ new Map([[E.name, E]]); if (R.dimensions) for (const N of R.dimensions) { const [t, r] = N.split("."), I = S.get(t); if (I && I.dimensions && I.dimensions[r]) { const O = I.dimensions[r], s = $(O.sql, A); e[N] = C`${s}`.as(N); } } if (R.measures) for (const N of R.measures) { const [t, r] = N.split("."), I = S.get(t); if (I && I.measures && I.measures[r]) { const O = I.measures[r], s = this.buildMeasureExpression(O, A); e[N] = C`${s}`.as(N); } } if (R.timeDimensions) for (const N of R.timeDimensions) { const [t, r] = N.dimension.split("."), I = S.get(t); if (I && I.dimensions && I.dimensions[r]) { const O = I.dimensions[r], s = this.buildTimeDimensionExpression( O.sql, N.granularity, A ); e[N.dimension] = C`${s}`.as(N.dimension); } } return Object.keys(e).length === 0 && (e.count = ME()), e; } /** * Build measure expression for HAVING clause, handling CTE references correctly */ buildHavingMeasureExpression(E, R, A, e, S) { if (S && S.preAggregationCTEs) { const N = S.preAggregationCTEs.find((t) => t.cube.name === E); if (N && N.measures.includes(`${E}.${R}`)) { const t = C`${C.identifier(N.cteAlias)}.${C.identifier(R)}`; switch (A.type) { case "count": case "countDistinct": case "sum": return g(t); case "avg": return this.databaseAdapter.buildAvg(t); case "min": return vE(t); case "max": return xE(t); case "number": return g(t); default: return g(t); } } } return this.buildMeasureExpression(A, e); } /** * Build measure expression with aggregation and filters */ buildMeasureExpression(E, R) { let A = $(E.sql, R); if (E.filters && E.filters.length > 0) { const e = E.filters.map((S) => S(R)).filter(Boolean); if (e.length > 0) { const S = e.length === 1 ? e[0] : f(...e); A = this.databaseAdapter.buildCaseWhen([ { when: S, then: A } ]); } } switch (E.type) { case "count": return ME(A); case "countDistinct": return PR(A); case "sum": return g(A); case "avg": return this.databaseAdapter.buildAvg(A); case "min": return vE(A); case "max": return xE(A); case "number": return A; default: return ME(A); } } /** * Build time dimension expression with granularity using database adapter */ buildTimeDimensionExpression(E, R, A) { const e = $(E, A); return R ? this.databaseAdapter.buildTimeDimension(R, e) : e instanceof u ? e : C`${e}`; } /** * Build WHERE conditions from semantic query filters (dimensions only) * Works for both single and multi-cube queries */ buildWhereConditions(E, R, A, e) { const S = [], N = E instanceof Map ? E : /* @__PURE__ */ new Map([[E.name, E]]); if (R.filters && R.filters.length > 0) for (const t of R.filters) { const r = this.processFilter(t, N, A, "where", e); r && S.push(r); } if (R.timeDimensions) for (const t of R.timeDimensions) { const [r, I] = t.dimension.split("."), O = N.get(r); if (O && O.dimensions[I] && t.dateRange) { if (e?.preAggregationCTEs && e.preAggregationCTEs.some((n) => n.cube.name === r)) continue; const s = O.dimensions[I], L = $(s.sql, A), a = this.buildDateRangeCondition(L, t.dateRange); a && S.push(a); } } return S; } /** * Build HAVING conditions from semantic query filters (measures only) * Works for both single and multi-cube queries */ buildHavingConditions(E, R, A, e) { const S = [], N = E instanceof Map ? E : /* @__PURE__ */ new Map([[E.name, E]]); if (R.filters && R.filters.length > 0) for (const t of R.filters) { const r = this.processFilter(t, N, A, "having", e); r && S.push(r); } return S; } /** * Process a single filter (basic or logical) * @param filterType - 'where' for dimension filters, 'having' for measure filters */ processFilter(E, R, A, e, S) { if ("and" in E || "or" in E) { const a = E; if (a.and) { const o = a.and.map((n) => this.processFilter(n, R, A, e, S)).filter((n) => n !== null); return o.length > 0 ? f(...o) : null; } if (a.or) { const o = a.or.map((n) => this.processFilter(n, R, A, e, S)).filter((n) => n !== null); return o.length > 0 ? DE(...o) : null; } } const N = E, [t, r] = N.member.split("."), I = R.get(t); if (!I) return null; const O = I.dimensions[r], s = I.measures[r], L = O || s; if (!L) return null; if (e === "where" && O) { if (S?.preAggregationCTEs && S.preAggregationCTEs.some((n) => n.cube.name === t)) return null; const a = $(O.sql, A); return this.buildFilterCondition(a, N.operator, N.values, L); } else { if (e === "where" && s) return null; if (e === "having" && s) { const a = this.buildHavingMeasureExpression(t, r, s, A, S); return this.buildFilterCondition(a, N.operator, N.values, L); } } return null; } /** * Build filter condition using Drizzle operators */ buildFilterCondition(E, R, A, e) { if (!A || A.length === 0) return R === "equals" ? this.databaseAdapter.buildBooleanLiteral(!1) : null; const S = A.filter((t) => !(t == null || t === "" || typeof t == "string" && t.includes("\0"))).map(this.databaseAdapter.convertFilterValue); if (S.length === 0 && !["set", "notSet"].includes(R)) return R === "equals" ? this.databaseAdapter.buildBooleanLiteral(!1) : null; const N = S[0]; switch (R) { case "equals": if (S.length > 1) { if (e?.type === "time") { const t = S.map((r) => this.normalizeDate(r) || r); return PE(E, t); } return PE(E, S); } else if (S.length === 1) { const t = e?.type === "time" && this.normalizeDate(N) || N; return sE(E, t); } return this.databaseAdapter.buildBooleanLiteral(!1); case "notEquals": return S.length > 1 ? AT(E, S) : S.length === 1 ? RT(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 aE(E, N); case "gte": return v(E, N); case "lt": return oE(E, N); case "lte": return Q(E, N); case "set": return ST(E); case "notSet": return eT(E); case "inDateRange": if (S.length >= 2) { const t = this.normalizeDate(S[0]), r = this.normalizeDate(S[1]); if (t && r) return f( v(E, t), Q(E, r) ); } return null; case "beforeDate": { const t = this.normalizeDate(N); return t ? oE(E, t) : null; } case "afterDate": { const t = this.normalizeDate(N); return t ? aE(E, t) : null; } case "between": return S.length >= 2 ? f( v(E, S[0]), Q(E, S[1]) ) : null; case "notBetween": return S.length >= 2 ? DE( oE(E, S[0]), aE(E, S[1]) ) : null; case "in": return S.length > 0 ? PE(E, S) : null; case "notIn": return S.length > 0 ? AT(E, S) : null; case "like": return this.databaseAdapter.buildStringCondition(E, "like", N); case "notLike": return this.databaseAdapter.buildStringCondition(E, "notLike", N); case "ilike": return this.databaseAdapter.buildStringCondition(E, "ilike", N); case "regex": return this.databaseAdapter.buildStringCondition(E, "regex", N); case "notRegex": return this.databaseAdapter.buildStringCondition(E, "notRegex", N); case "isEmpty": return DE( eT(E), sE(E, "") ); case "isNotEmpty": return f( ST(E), RT(E, "") ); 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]), e = this.normalizeDate(R[1]); return !A || !e ? null : f( v(E, A), Q(E, e) ); } if (typeof R == "string") { const A = this.parseRelativeDateRange(R); if (A) return f( v(E, A.start), Q(E, A.end) ); const e = this.normalizeDate(R); if (!e) return null; const S = new Date(e); S.setUTCHours(0, 0, 0, 0); const N = new Date(e); return N.setUTCHours(23, 59, 59, 999), f( v(E, S), Q(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(), e = R.getUTCFullYear(), S = R.getUTCMonth(), N = R.getUTCDate(), t = R.getUTCDay(); if (A === "today") { const s = new Date(R); s.setUTCHours(0, 0, 0, 0); const L = new Date(R); return L.setUTCHours(23, 59, 59, 999), { start: s, end: L }; } if (A === "yesterday") { const s = new Date(R); s.setUTCDate(N - 1), s.setUTCHours(0, 0, 0, 0); const L = new Date(R); return L.setUTCDate(N - 1), L.setUTCHours(23, 59, 59, 999), { start: s, end: L }; } if (A === "this week") { const s = t === 0 ? -6 : 1 - t, L = new Date(R); L.setUTCDate(N + s), L.setUTCHours(0, 0, 0, 0); const a = new Date(L); return a.setUTCDate(L.getUTCDate() + 6), a.setUTCHours(23, 59, 59, 999), { start: L, end: a }; } if (A === "this month") { const s = new Date(Date.UTC(e, S, 1, 0, 0, 0, 0)), L = new Date(Date.UTC(e, S + 1, 0, 23, 59, 59, 999)); return { start: s, end: L }; } if (A === "this quarter") { const s = Math.floor(S / 3), L = new Date(Date.UTC(e, s * 3, 1, 0, 0, 0, 0)), a = new Date(Date.UTC(e, s * 3 + 3, 0, 23, 59, 59, 999)); return { start: L, end: a }; } if (A === "this year") { const s = new Date(Date.UTC(e, 0, 1, 0, 0, 0, 0)), L = new Date(Date.UTC(e, 11, 31, 23, 59, 59, 999)); return { start: s, end: L }; } const r = A.match(/^last\s+(\d+)\s+days?$/); if (r) { const s = parseInt(r[1], 10), L = new Date(R); L.setUTCDate(N - s + 1), L.setUTCHours(0, 0, 0, 0); const a = new Date(R); return a.setUTCHours(23, 59, 59, 999), { start: L, end: a }; } if (A === "last week") { const s = t === 0 ? -13 : -6 - t, L = new Date(R); L.setUTCDate(N + s), L.setUTCHours(0, 0, 0, 0); const a = new Date(L); return a.setUTCDate(L.getUTCDate() + 6), a.setUTCHours(23, 59, 59, 999), { start: L, end: a }; } if (A === "last month") { const s = new Date(Date.UTC(e, S - 1, 1, 0, 0, 0, 0)), L = new Date(Date.UTC(e, S, 0, 23, 59, 59, 999)); return { start: s, end: L }; } if (A === "last quarter") { const s = Math.floor(S / 3), L = s === 0 ? 3 : s - 1, a = s === 0 ? e - 1 : e, o = new Date(Date.UTC(a, L * 3, 1, 0, 0, 0, 0)), n = new Date(Date.UTC(a, L * 3 + 3, 0, 23, 59, 59, 999)); return { start: o, end: n }; } if (A === "last year") { const s = new Date(Date.UTC(e - 1, 0, 1, 0, 0, 0, 0)), L = new Date(Date.UTC(e - 1, 11, 31, 23, 59, 59, 999)); return { start: s, end: L }; } if (A === "last 12 months") { const s = new Date(Date.UTC(e, S - 11, 1, 0, 0, 0, 0)), L = new Date(R); return L.setUTCHours(23, 59, 59, 999), { start: s, end: L }; } const I = A.match(/^last\s+(\d+)\s+months?$/); if (I) { const s = parseInt(I[1], 10), L = new Date(Date.UTC(e, S - s + 1, 1, 0, 0, 0, 0)), a = new Date(R); return a.setUTCHours(23, 59, 59, 999), { start: L, end: a }; } const O = A.match(/^last\s+(\d+)\s+years?$/); if (O) { const s = parseInt(O[1], 10), L = new Date(Date.UTC(e - s, 0, 1, 0, 0, 0, 0)), a = new Date(R); return a.setUTCHours(23, 59, 59, 999), { start: L, end: a }; } 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, e = new Date(A); return isNaN(e.getTime()) ? null : e; } 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, e) { const S = []; if (!(R.measures && R.measures.length > 0)) return []; const t = E instanceof Map ? E : /* @__PURE__ */ new Map([[E.name, E]]); if (R.dimensions) for (const r of R.dimensions) { const [I, O] = r.split("."), s = t.get(I); if (s && s.dimensions && s.dimensions[O]) if (e?.preAggregationCTEs?.some((a) => a.cube.name === I)) { const a = e.preAggregationCTEs.find((n) => n.cube.name === I), o = a.joinKeys.find((n) => n.targetColumn === O); if (o && o.sourceColumnObj) S.push(o.sourceColumnObj); else { const n = C`${C.identifier(a.cteAlias)}.${C.identifier(O)}`; S.push(n); } } else { const a = s.dimensions[O], o = $(a.sql, A); S.push(o); } } if (R.timeDimensions) for (const r of R.timeDimensions) { const [I, O] = r.dimension.split("."), s = t.get(I); if (s && s.dimensions && s.dimensions[O]) if (e?.preAggregationCTEs?.some((a) => a.cube.name === I)) { const a = e.preAggregationCTEs.find((n) => n.cube.name === I), o = a.joinKeys.find((n) => n.targetColumn === O); if (o && o.sourceColumnObj) { const n = this.buildTimeDimensionExpression( o.sourceColumnObj, r.granularity, A ); S.push(n); } else { const n = C`${C.identifier(a.cteAlias)}.${C.identifier(O)}`; S.push(n); } } else { const a = s.dimensions[O], o = this.buildTimeDimensionExpression( a.sql, r.granularity, A ); S.push(o); } } return S; } /** * Build ORDER BY clause with automatic time dimension sorting */ buildOrderBy(E, R) { const A = [], e = R || [ ...E.measures || [], ...E.dimensions || [], ...E.timeDimensions?.map((S) => S.dimension) || [] ]; if (E.order && Object.keys(E.order).length > 0) for (const [S, N] of Object.entries(E.order)) { if (!e.includes(S)) throw new Error(`Cannot order by '${S}': field is not selected in the query`); const t = N === "desc" ? oR(C.identifier(S)) : IT(C.identifier(S)); A.push(t); } if (E.timeDimensions && E.timeDimensions.length > 0) { const S = new Set(Object.keys(E.order || {})), N = [...E.timeDimensions].sort( (t, r) => t.dimension.localeCompare(r.dimension) ); for (const t of N) S.has(t.dimension) || A.push(IT(C.identifier(t.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 = [], e = E instanceof Map ? E : /* @__PURE__ */ new Map([[E.name, E]]); if (R.measures && A.push(...R.measures), R.dimensions) for (const S of R.dimensions) { const [N, t] = S.split("."), r = e.get(N); if (r) { const I = r.dimensions[t]; I && I.type === "number" && A.push(S); } } 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 e = E; if (A !== void 0) { if (A < 0) throw new Error("Limit must be non-negative"); e = e.limit(A); } if (R.offset !== void 0) { if (R.offset < 0) throw new Error("Offset must be non-negative"); e = e.offset(R.offset); } return e; } } class pR { /** * 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 [e] = A.split("."); R.add(e); } if (E.dimens