@514labs/moose-lib 0.6.527 → 0.6.528

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/dist/browserCompatible.d.mts +2 -2
  2. package/dist/browserCompatible.d.ts +2 -2
  3. package/dist/browserCompatible.js +393 -2
  4. package/dist/browserCompatible.js.map +1 -1
  5. package/dist/browserCompatible.mjs +389 -2
  6. package/dist/browserCompatible.mjs.map +1 -1
  7. package/dist/compilerPlugin.js +2 -1
  8. package/dist/compilerPlugin.js.map +1 -1
  9. package/dist/compilerPlugin.mjs +2 -1
  10. package/dist/compilerPlugin.mjs.map +1 -1
  11. package/dist/dmv2/index.d.mts +2 -2
  12. package/dist/dmv2/index.d.ts +2 -2
  13. package/dist/dmv2/index.js +393 -2
  14. package/dist/dmv2/index.js.map +1 -1
  15. package/dist/dmv2/index.mjs +389 -2
  16. package/dist/dmv2/index.mjs.map +1 -1
  17. package/dist/{index-k_kpRxE3.d.mts → index-BTIlwBBZ.d.mts} +13 -2
  18. package/dist/{index-7uxZbwmY.d.ts → index-w7pvlv3c.d.ts} +13 -2
  19. package/dist/index.d.mts +4 -4
  20. package/dist/index.d.ts +4 -4
  21. package/dist/index.js +394 -2
  22. package/dist/index.js.map +1 -1
  23. package/dist/index.mjs +390 -2
  24. package/dist/index.mjs.map +1 -1
  25. package/dist/moose-runner.js +36 -3
  26. package/dist/moose-runner.js.map +1 -1
  27. package/dist/moose-runner.mjs +36 -3
  28. package/dist/moose-runner.mjs.map +1 -1
  29. package/dist/{view-BCWJcLF6.d.mts → query-client-6YrlC3Df.d.mts} +420 -80
  30. package/dist/{view-BCWJcLF6.d.ts → query-client-6YrlC3Df.d.ts} +420 -80
  31. package/dist/testing/index.d.mts +1 -1
  32. package/dist/testing/index.d.ts +1 -1
  33. package/dist/testing/index.js +10 -1
  34. package/dist/testing/index.js.map +1 -1
  35. package/dist/testing/index.mjs +10 -1
  36. package/dist/testing/index.mjs.map +1 -1
  37. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/testing/index.ts","../../src/sqlHelpers.ts","../../src/testing/shared.ts","../../src/testing/clickhouse-diagnostics.ts","../../src/testing/test-reporter.ts"],"sourcesContent":["export {\n percentile,\n explain,\n profileQuery,\n resolveProfiles,\n profileBenchmark,\n tableStats,\n} from \"./clickhouse-diagnostics\";\nexport type {\n ExplainResult,\n ProfileResult,\n BenchmarkResult,\n TableStats,\n} from \"./clickhouse-diagnostics\";\n\nexport { createTestReporter } from \"./test-reporter\";\nexport type {\n TestReport,\n TestReportTarget,\n TestReporterOptions,\n} from \"./test-reporter\";\n","// source https://github.com/blakeembrey/sql-template-tag/blob/main/src/index.ts\nimport { Column } from \"./dataModels/dataModelTypes\";\nimport { OlapTable, View } from \"./dmv2\";\nimport { AggregationFunction } from \"./dataModels/typeConvert\";\n\n/**\n * Quote a ClickHouse identifier with backticks if not already quoted.\n * Backticks allow special characters (e.g., hyphens) in identifiers.\n */\nexport const quoteIdentifier = (name: string): string => {\n return name.startsWith(\"`\") && name.endsWith(\"`\") ? name : `\\`${name}\\``;\n};\n\nconst isTable = (\n value: RawValue | Column | OlapTable<any> | View,\n): value is OlapTable<any> =>\n typeof value === \"object\" &&\n value !== null &&\n \"kind\" in value &&\n value.kind === \"OlapTable\";\n\nconst isView = (\n value: RawValue | Column | OlapTable<any> | View,\n): value is View =>\n typeof value === \"object\" &&\n value !== null &&\n \"kind\" in value &&\n value.kind === \"View\";\n\nexport type IdentifierBrandedString = string & {\n readonly __identifier_brand?: unique symbol;\n};\nexport type NonIdentifierBrandedString = string & {\n readonly __identifier_brand?: unique symbol;\n};\n\n/**\n * Values supported by SQL engine.\n */\nexport type Value =\n | NonIdentifierBrandedString\n | number\n | boolean\n | Date\n | [string, string];\n\n/**\n * Supported value or SQL instance.\n */\nexport type RawValue = Value | Sql;\n\nconst isColumn = (\n value: RawValue | Column | OlapTable<any> | View,\n): value is Column =>\n typeof value === \"object\" &&\n value !== null &&\n !(\"kind\" in value) &&\n \"name\" in value &&\n \"annotations\" in value;\n\n/**\n * Sql template tag interface with attached helper methods.\n */\nexport interface SqlTemplateTag {\n /**\n * @deprecated Use `sql.statement` for full SQL statements or `sql.fragment` for SQL fragments.\n */\n (\n strings: readonly string[],\n ...values: readonly (RawValue | Column | OlapTable<any> | View)[]\n ): Sql;\n\n /**\n * Template literal tag for complete SQL statements (e.g. SELECT, INSERT, CREATE).\n * Produces a Sql instance with `isFragment = false`.\n */\n statement(\n strings: readonly string[],\n ...values: readonly (RawValue | Column | OlapTable<any> | View)[]\n ): Sql;\n\n /**\n * Template literal tag for SQL fragments (e.g. expressions, conditions, partial clauses).\n * Produces a Sql instance with `isFragment = true`.\n */\n fragment(\n strings: readonly string[],\n ...values: readonly (RawValue | Column | OlapTable<any> | View)[]\n ): Sql;\n\n /**\n * Join an array of Sql fragments with a separator.\n * @param fragments - Array of Sql fragments to join\n * @param separator - Optional separator string (defaults to \", \")\n */\n join(fragments: Sql[], separator?: string): Sql;\n\n /**\n * Create raw SQL from a string without parameterization.\n * WARNING: SQL injection risk if used with untrusted input.\n */\n raw(text: string): Sql;\n}\n\nfunction sqlImpl(\n strings: readonly string[],\n ...values: readonly (RawValue | Column | OlapTable<any> | View)[]\n): Sql {\n return new Sql(strings, values);\n}\n\nexport const sql: SqlTemplateTag = sqlImpl as SqlTemplateTag;\n\nsql.statement = function (\n strings: readonly string[],\n ...values: readonly (RawValue | Column | OlapTable<any> | View)[]\n): Sql {\n return new Sql(strings, values, false);\n};\n\nsql.fragment = function (\n strings: readonly string[],\n ...values: readonly (RawValue | Column | OlapTable<any> | View)[]\n): Sql {\n return new Sql(strings, values, true);\n};\n\nconst instanceofSql = (\n value: RawValue | Column | OlapTable<any> | View,\n): value is Sql =>\n typeof value === \"object\" && \"values\" in value && \"strings\" in value;\n\n/**\n * A SQL instance can be nested within each other to build SQL strings.\n */\nexport class Sql {\n readonly values: Value[];\n readonly strings: string[];\n readonly isFragment: boolean | undefined;\n\n constructor(\n rawStrings: readonly string[],\n rawValues: readonly (RawValue | Column | OlapTable<any> | View | Sql)[],\n isFragment?: boolean,\n ) {\n if (rawStrings.length - 1 !== rawValues.length) {\n if (rawStrings.length === 0) {\n throw new TypeError(\"Expected at least 1 string\");\n }\n\n throw new TypeError(\n `Expected ${rawStrings.length} strings to have ${\n rawStrings.length - 1\n } values`,\n );\n }\n\n const valuesLength = rawValues.reduce<number>(\n (len: number, value: RawValue | Column | OlapTable<any> | View | Sql) =>\n len +\n (instanceofSql(value) ? value.values.length\n : isColumn(value) || isTable(value) || isView(value) ? 0\n : 1),\n 0,\n );\n\n this.values = new Array(valuesLength);\n this.strings = new Array(valuesLength + 1);\n this.isFragment = isFragment;\n\n this.strings[0] = rawStrings[0];\n\n // Iterate over raw values, strings, and children. The value is always\n // positioned between two strings, e.g. `index + 1`.\n let i = 0,\n pos = 0;\n while (i < rawValues.length) {\n const child = rawValues[i++];\n const rawString = rawStrings[i];\n\n // Check for nested `sql` queries.\n if (instanceofSql(child)) {\n // Append child prefix text to current string.\n this.strings[pos] += child.strings[0];\n\n let childIndex = 0;\n while (childIndex < child.values.length) {\n this.values[pos++] = child.values[childIndex++];\n this.strings[pos] = child.strings[childIndex];\n }\n\n // Append raw string to current string.\n this.strings[pos] += rawString;\n } else if (isColumn(child)) {\n const aggregationFunction = child.annotations.find(\n ([k, _]) => k === \"aggregationFunction\",\n );\n if (aggregationFunction !== undefined) {\n const funcName = (aggregationFunction[1] as AggregationFunction)\n .functionName;\n const parenIdx = funcName.indexOf(\"(\");\n const mergedName =\n parenIdx !== -1 ?\n `${funcName.slice(0, parenIdx)}Merge${funcName.slice(parenIdx)}`\n : `${funcName}Merge`;\n this.strings[pos] += `${mergedName}(\\`${child.name}\\`)`;\n } else {\n this.strings[pos] += `\\`${child.name}\\``;\n }\n this.strings[pos] += rawString;\n } else if (isTable(child)) {\n const deployedName = child.generateTableName();\n if (child.config.database) {\n this.strings[pos] +=\n `\\`${child.config.database}\\`.\\`${deployedName}\\``;\n } else {\n this.strings[pos] += `\\`${deployedName}\\``;\n }\n this.strings[pos] += rawString;\n } else if (isView(child)) {\n if (child.database) {\n this.strings[pos] += `\\`${child.database}\\`.\\`${child.name}\\``;\n } else {\n this.strings[pos] += `\\`${child.name}\\``;\n }\n this.strings[pos] += rawString;\n } else {\n this.values[pos++] = child;\n this.strings[pos] = rawString;\n }\n }\n }\n\n /**\n * Append another Sql fragment, returning a new Sql instance.\n */\n append(other: Sql): Sql {\n return new Sql(\n [...this.strings, \"\"],\n [...this.values, other],\n this.isFragment,\n );\n }\n}\n\nsql.join = function (fragments: Sql[], separator?: string): Sql {\n if (fragments.length === 0) return new Sql([\"\"], [], true);\n if (fragments.length === 1) {\n const frag = fragments[0];\n return new Sql(frag.strings, frag.values, true);\n }\n const sep = separator ?? \", \";\n const normalized = sep.includes(\" \") ? sep : ` ${sep} `;\n const strings = [\"\", ...Array(fragments.length - 1).fill(normalized), \"\"];\n return new Sql(strings, fragments, true);\n};\n\nsql.raw = function (text: string): Sql {\n return new Sql([text], [], true);\n};\n\nexport const toStaticQuery = (sql: Sql): string => {\n const [query, params] = toQuery(sql);\n if (Object.keys(params).length !== 0) {\n throw new Error(\n \"Dynamic SQL is not allowed in the select statement in view creation.\",\n );\n }\n return query;\n};\n\nexport const toQuery = (sql: Sql): [string, { [pN: string]: any }] => {\n const parameterizedStubs = sql.values.map((v, i) =>\n createClickhouseParameter(i, v),\n );\n\n const query = sql.strings\n .map((s, i) =>\n s != \"\" ? `${s}${emptyIfUndefined(parameterizedStubs[i])}` : \"\",\n )\n .join(\"\");\n\n const query_params = sql.values.reduce(\n (acc: Record<string, unknown>, v, i) => ({\n ...acc,\n [`p${i}`]: getValueFromParameter(v),\n }),\n {},\n );\n return [query, query_params];\n};\n\n/**\n * Build a display-only SQL string with values inlined for logging/debugging.\n * Does not alter execution behavior; use toQuery for actual execution.\n */\nexport const toQueryPreview = (sql: Sql): string => {\n try {\n const formatValue = (v: Value): string => {\n // Unwrap identifiers: [\"Identifier\", name]\n if (Array.isArray(v)) {\n const [type, val] = v as unknown as [string, any];\n if (type === \"Identifier\") {\n // Quote identifiers with backticks like other helpers\n return `\\`${String(val)}\\``;\n }\n // Fallback for unexpected arrays\n return `[${(v as unknown as any[]).map((x) => formatValue(x as Value)).join(\", \")}]`;\n }\n if (v === null || v === undefined) return \"NULL\";\n if (typeof v === \"string\") return `'${v.replace(/'/g, \"''\")}'`;\n if (typeof v === \"number\") return String(v);\n if (typeof v === \"boolean\") return v ? \"true\" : \"false\";\n if (v instanceof Date)\n return `'${v.toISOString().replace(\"T\", \" \").slice(0, 19)}'`;\n try {\n return JSON.stringify(v as unknown as any);\n } catch {\n return String(v);\n }\n };\n\n let out = sql.strings[0] ?? \"\";\n for (let i = 0; i < sql.values.length; i++) {\n const val = getValueFromParameter(sql.values[i] as any);\n out += formatValue(val as Value);\n out += sql.strings[i + 1] ?? \"\";\n }\n return out.replace(/\\s+/g, \" \").trim();\n } catch (error) {\n console.log(`toQueryPreview error: ${error}`);\n return \"/* query preview unavailable */\";\n }\n};\n\nexport const getValueFromParameter = (value: any) => {\n if (Array.isArray(value)) {\n const [type, val] = value;\n if (type === \"Identifier\") return val;\n }\n return value;\n};\nexport function createClickhouseParameter(\n parameterIndex: number,\n value: Value,\n) {\n // ClickHouse use {name:type} be a placeholder, so if we only use number string as name e.g: {1:Unit8}\n // it will face issue when converting to the query params => {1: value1}, because the key is value not string type, so here add prefix \"p\" to avoid this issue.\n return `{p${parameterIndex}:${mapToClickHouseType(value)}}`;\n}\n\n/**\n * Convert the JS type (source is JSON format by API query parameter) to the corresponding ClickHouse type for generating named placeholder of parameterized query.\n * Only support to convert number to Int or Float, boolean to Bool, string to String, other types will convert to String.\n * If exist complex type e.g: object, Array, null, undefined, Date, Record.. etc, just convert to string type by ClickHouse function in SQL.\n * ClickHouse support converting string to other types function.\n * Please see Each section of the https://clickhouse.com/docs/en/sql-reference/functions and https://clickhouse.com/docs/en/sql-reference/functions/type-conversion-functions\n * @param value\n * @returns 'Float', 'Int', 'Bool', 'String'\n */\nexport const mapToClickHouseType = (value: Value) => {\n if (typeof value === \"number\") {\n // infer the float or int according to exist remainder or not\n return Number.isInteger(value) ? \"Int\" : \"Float\";\n }\n // When define column type or query result with parameterized query, The Bool or Boolean type both supported.\n // But the column type of query result only return Bool, so we only support Bool type for safety.\n if (typeof value === \"boolean\") return \"Bool\";\n if (value instanceof Date) return \"DateTime\";\n if (Array.isArray(value)) {\n const [type, _] = value;\n return type;\n }\n return \"String\";\n};\nfunction emptyIfUndefined(value: string | undefined): string {\n return value === undefined ? \"\" : value;\n}\n","/** Validate that a `runs` parameter is a positive integer. */\nexport function validateRuns(runs: number): void {\n if (!Number.isInteger(runs) || runs < 1) {\n throw new Error(`runs must be a positive integer, got ${runs}`);\n }\n}\n","/**\n * ClickHouse diagnostic helpers for benchmarking and profiling queries.\n *\n * Provides utilities to:\n * - EXPLAIN a query and extract index/granule stats\n * - Profile query execution via system.query_log\n * - Benchmark a query over N runs with p50/p95 percentiles\n * - Get row count, part count, and disk size for a table\n */\n\nimport { type Sql, sql, toQueryPreview, quoteIdentifier } from \"../sqlHelpers\";\nimport { type QueryClient } from \"../consumption-apis/query-client\";\nimport { validateRuns } from \"./shared\";\n\n// ---------------------------------------------------------------------------\n// Shared types\n// ---------------------------------------------------------------------------\n\nexport interface BenchmarkResult {\n readonly profiles: readonly ProfileResult[];\n readonly p50: number;\n readonly p95: number;\n}\n\n// ---------------------------------------------------------------------------\n// Shared utilities\n// ---------------------------------------------------------------------------\n\nexport function percentile(values: number[], p: number): number {\n if (values.length === 0) return 0;\n const sorted = [...values].sort((a, b) => a - b);\n const index = Math.min(\n sorted.length - 1,\n Math.ceil((p / 100) * sorted.length) - 1,\n );\n return sorted[Math.max(0, index)];\n}\n\n// ---------------------------------------------------------------------------\n// Explain\n// ---------------------------------------------------------------------------\n\nexport interface ExplainResult {\n readonly indexCondition: string;\n readonly selectedGranules: number;\n readonly totalGranules: number;\n readonly granuleSkipPct: number;\n readonly rawPlan: string;\n}\n\n/**\n * Run EXPLAIN indexes=1 on a query and extract index condition and granule stats.\n *\n * Note: The query is rendered via `toQueryPreview` and re-wrapped with `sql.raw`.\n * This bakes parameterized values into the EXPLAIN string as literals, which\n * matches ClickHouse EXPLAIN behavior (it does not support parameterized queries).\n */\nexport async function explain(\n queryClient: QueryClient,\n query: Sql,\n): Promise<ExplainResult> {\n const sqlString = toQueryPreview(query);\n const explainSql = sql.raw(`EXPLAIN indexes = 1 ${sqlString}`);\n const result = await queryClient.execute(explainSql);\n const rows = (await result.json()) as {\n explain?: string;\n EXPLAIN?: string;\n }[];\n\n const lines = rows.map((r) => r.explain ?? r.EXPLAIN ?? \"\");\n const full = lines.join(\"\\n\");\n\n const conditionMatch = full.match(/Condition:\\s*(.+)/);\n const granulesMatch = full.match(/Granules:\\s*(\\d+)\\/(\\d+)/);\n\n const selected = granulesMatch ? parseInt(granulesMatch[1], 10) : 0;\n const total = granulesMatch ? parseInt(granulesMatch[2], 10) : 0;\n const skipPct = total > 0 ? ((total - selected) / total) * 100 : 0;\n\n return {\n indexCondition: conditionMatch?.[1]?.trim() ?? \"unknown\",\n selectedGranules: selected,\n totalGranules: total,\n granuleSkipPct: Math.round(skipPct),\n rawPlan: full,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Profile (batched)\n// ---------------------------------------------------------------------------\n\nexport interface ProfileResult {\n readonly queryId: string;\n readonly readRows: number;\n readonly readBytes: number;\n readonly memoryUsage: number;\n readonly durationMs: number;\n readonly resultRows: number;\n readonly diskReadUs: number;\n readonly osReadBytes: number;\n}\n\n/**\n * Execute a query and return its query_id for later profile resolution.\n * Does NOT flush logs or read system.query_log — call resolveProfiles() after all runs.\n */\nexport async function profileQuery(\n queryClient: QueryClient,\n query: Sql,\n): Promise<string> {\n const result = await queryClient.execute(query);\n await result.json();\n return result.query_id;\n}\n\n/**\n * Flush logs once and batch-resolve all query_ids from system.query_log.\n * Call this after all profileQuery() calls are complete.\n *\n * Query IDs are parameterized via the `sql` template tag to prevent injection.\n */\nexport async function resolveProfiles(\n queryClient: QueryClient,\n queryIds: string[],\n): Promise<ProfileResult[]> {\n if (queryIds.length === 0) return [];\n\n await queryClient.command(sql.raw(\"SYSTEM FLUSH LOGS\"));\n\n const idFragments = queryIds.map((id) => sql`${id}`);\n const logQuery = sql`SELECT\n query_id, read_rows, read_bytes, memory_usage, query_duration_ms, result_rows,\n ProfileEvents['DiskReadElapsedMicroseconds'] as disk_read_us,\n ProfileEvents['OSReadChars'] as os_read_bytes\n FROM system.query_log\n WHERE query_id IN (${sql.join(idFragments)}) AND type = 'QueryFinish'\n ORDER BY event_time ASC`;\n\n const logResult = await queryClient.execute(logQuery);\n const logRows = (await logResult.json()) as {\n query_id: string;\n read_rows?: string;\n read_bytes?: string;\n memory_usage?: string;\n query_duration_ms?: string;\n result_rows?: string;\n disk_read_us?: string;\n os_read_bytes?: string;\n }[];\n\n const byId = new Map(logRows.map((r) => [r.query_id, r]));\n\n return queryIds.map((id) => {\n const entry = byId.get(id);\n if (!entry) {\n throw new Error(\n `No query_log entry for query_id=${id}. Check log_queries setting.`,\n );\n }\n return {\n queryId: id,\n readRows: Number(entry.read_rows ?? 0),\n readBytes: Number(entry.read_bytes ?? 0),\n memoryUsage: Number(entry.memory_usage ?? 0),\n durationMs: Number(entry.query_duration_ms ?? 0),\n resultRows: Number(entry.result_rows ?? 0),\n diskReadUs: Number(entry.disk_read_us ?? 0),\n osReadBytes: Number(entry.os_read_bytes ?? 0),\n };\n });\n}\n\n/**\n * Run a query N times, then batch-resolve all profiles in one flush + one lookup.\n * Returns profiles with p50/p95 of server-side duration.\n */\nexport async function profileBenchmark(\n queryClient: QueryClient,\n query: Sql,\n runs: number,\n): Promise<BenchmarkResult> {\n validateRuns(runs);\n\n const queryIds: string[] = [];\n for (let i = 0; i < runs; i++) {\n queryIds.push(await profileQuery(queryClient, query));\n }\n\n const profiles = await resolveProfiles(queryClient, queryIds);\n const durations = profiles.map((p) => p.durationMs);\n\n return {\n profiles,\n p50: percentile(durations, 50),\n p95: percentile(durations, 95),\n };\n}\n\n// ---------------------------------------------------------------------------\n// Table stats\n// ---------------------------------------------------------------------------\n\n// Matches \"table\" or \"database.table\" — no trailing/leading/consecutive dots\nconst TABLE_NAME_RE = /^[a-zA-Z_][a-zA-Z0-9_]*(?:\\.[a-zA-Z_][a-zA-Z0-9_]*)?$/;\n\nexport interface TableStats {\n readonly table: string;\n readonly rows: number;\n readonly parts: number;\n readonly diskSize: string;\n}\n\nexport async function tableStats(\n queryClient: QueryClient,\n table: string,\n): Promise<TableStats> {\n if (!TABLE_NAME_RE.test(table)) {\n throw new Error(`Invalid table name: ${table}`);\n }\n\n // Split on dot to handle database.table — quote each part separately\n const quoted = table\n .split(\".\")\n .map((part) => quoteIdentifier(part))\n .join(\".\");\n // Extract database and bare table name for system.parts lookup\n const parts = table.split(\".\");\n const bareTable = parts.length > 1 ? parts[parts.length - 1] : table;\n const bareDatabase = parts.length > 1 ? parts[0] : null;\n\n const [countRows, partsRows] = await Promise.all([\n queryClient\n .execute(sql.raw(`SELECT count() as rows FROM ${quoted}`))\n .then((r) => r.json() as Promise<{ rows: string }[]>),\n queryClient\n .execute(\n bareDatabase ?\n sql`SELECT count() as parts, formatReadableSize(sum(bytes_on_disk)) as disk_size\n FROM system.parts\n WHERE active = 1 AND table = ${bareTable} AND database = ${bareDatabase}`\n : sql`SELECT count() as parts, formatReadableSize(sum(bytes_on_disk)) as disk_size\n FROM system.parts\n WHERE active = 1 AND table = ${bareTable}`,\n )\n .then((r) => r.json() as Promise<{ parts: string; disk_size: string }[]>),\n ]);\n\n return {\n table,\n rows: Number(countRows[0]?.rows ?? 0),\n parts: Number(partsRows[0]?.parts ?? 0),\n diskSize: partsRows[0]?.disk_size ?? \"unknown\",\n };\n}\n","/**\n * Test report accumulator — collects results from test cases\n * and writes a timestamped JSON detail file.\n */\n\nimport { writeFile, mkdir } from \"node:fs/promises\";\nimport { join } from \"node:path\";\n\nexport interface TestReportTarget {\n readonly host: string;\n readonly database: string;\n}\n\nexport interface TestReport {\n readonly timestamp: string;\n readonly target: TestReportTarget;\n tests: Record<string, unknown>;\n}\n\nexport interface TestReporterOptions {\n /** Prefix for the output filename (e.g. \"benchmark-details\") */\n prefix: string;\n /** Directory to write report files into */\n outputDir: string;\n /** ClickHouse target info. If omitted, reads from MOOSE_CLICKHOUSE_CONFIG__* env vars. */\n target?: TestReportTarget;\n}\n\nexport function createTestReporter(options: TestReporterOptions): {\n results: TestReport;\n flush: () => Promise<string>;\n} {\n const target = options.target ?? {\n host: process.env.MOOSE_CLICKHOUSE_CONFIG__HOST ?? \"localhost\",\n database: process.env.MOOSE_CLICKHOUSE_CONFIG__DB_NAME ?? \"local\",\n };\n\n const results: TestReport = {\n timestamp: new Date().toISOString(),\n target,\n tests: {},\n };\n\n const flush = async (): Promise<string> => {\n await mkdir(options.outputDir, { recursive: true });\n const filename = `${options.prefix}-${new Date().toISOString().replace(/[:.]/g, \"-\")}.json`;\n const filepath = join(options.outputDir, filename);\n await writeFile(filepath, JSON.stringify(results, null, 2));\n return filepath;\n };\n\n return { results, flush };\n}\n"],"mappings":"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","names":["sql"]}
1
+ {"version":3,"sources":["../../src/testing/index.ts","../../src/sqlHelpers.ts","../../src/testing/shared.ts","../../src/testing/clickhouse-diagnostics.ts","../../src/testing/test-reporter.ts"],"sourcesContent":["export {\n percentile,\n explain,\n profileQuery,\n resolveProfiles,\n profileBenchmark,\n tableStats,\n} from \"./clickhouse-diagnostics\";\nexport type {\n ExplainResult,\n ProfileResult,\n BenchmarkResult,\n TableStats,\n} from \"./clickhouse-diagnostics\";\n\nexport { createTestReporter } from \"./test-reporter\";\nexport type {\n TestReport,\n TestReportTarget,\n TestReporterOptions,\n} from \"./test-reporter\";\n","// source https://github.com/blakeembrey/sql-template-tag/blob/main/src/index.ts\nimport { Column } from \"./dataModels/dataModelTypes\";\nimport { OlapTable, View } from \"./dmv2\";\nimport { OlapDictionary } from \"./dmv2/sdk/olapDictionary\";\n\nimport { AggregationFunction } from \"./dataModels/typeConvert\";\n\n/**\n * Quote a ClickHouse identifier with backticks if not already quoted.\n * Backticks allow special characters (e.g., hyphens) in identifiers.\n */\nexport const quoteIdentifier = (name: string): string => {\n return name.startsWith(\"`\") && name.endsWith(\"`\") ? name : `\\`${name}\\``;\n};\n\nconst isTable = (\n value: RawValue | Column | OlapTable<any> | View | OlapDictionary<any>,\n): value is OlapTable<any> =>\n typeof value === \"object\" &&\n value !== null &&\n \"kind\" in value &&\n value.kind === \"OlapTable\";\n\nconst isView = (\n value: RawValue | Column | OlapTable<any> | View | OlapDictionary<any>,\n): value is View =>\n typeof value === \"object\" &&\n value !== null &&\n \"kind\" in value &&\n value.kind === \"View\";\n\nconst isDictionary = (\n value: RawValue | Column | OlapTable<any> | View | OlapDictionary<any>,\n): value is OlapDictionary<any> =>\n typeof value === \"object\" &&\n value !== null &&\n \"kind\" in value &&\n (value as any).kind === \"OlapDictionary\";\n\nexport type IdentifierBrandedString = string & {\n readonly __identifier_brand?: unique symbol;\n};\nexport type NonIdentifierBrandedString = string & {\n readonly __identifier_brand?: unique symbol;\n};\n\n/**\n * Values supported by SQL engine.\n */\nexport type Value =\n | NonIdentifierBrandedString\n | number\n | boolean\n | Date\n | [string, string];\n\n/**\n * Supported value or SQL instance.\n */\nexport type RawValue = Value | Sql;\n\nconst isColumn = (\n value: RawValue | Column | OlapTable<any> | View | OlapDictionary<any>,\n): value is Column =>\n typeof value === \"object\" &&\n value !== null &&\n !(\"kind\" in value) &&\n \"name\" in value &&\n \"annotations\" in value;\n\n/**\n * Sql template tag interface with attached helper methods.\n */\nexport interface SqlTemplateTag {\n /**\n * @deprecated Use `sql.statement` for full SQL statements or `sql.fragment` for SQL fragments.\n */\n (\n strings: readonly string[],\n ...values: readonly (\n | RawValue\n | Column\n | OlapTable<any>\n | View\n | OlapDictionary<any>\n )[]\n ): Sql;\n\n /**\n * Template literal tag for complete SQL statements (e.g. SELECT, INSERT, CREATE).\n * Produces a Sql instance with `isFragment = false`.\n */\n statement(\n strings: readonly string[],\n ...values: readonly (\n | RawValue\n | Column\n | OlapTable<any>\n | View\n | OlapDictionary<any>\n )[]\n ): Sql;\n\n /**\n * Template literal tag for SQL fragments (e.g. expressions, conditions, partial clauses).\n * Produces a Sql instance with `isFragment = true`.\n */\n fragment(\n strings: readonly string[],\n ...values: readonly (\n | RawValue\n | Column\n | OlapTable<any>\n | View\n | OlapDictionary<any>\n )[]\n ): Sql;\n\n /**\n * Join an array of Sql fragments with a separator.\n * @param fragments - Array of Sql fragments to join\n * @param separator - Optional separator string (defaults to \", \")\n */\n join(fragments: Sql[], separator?: string): Sql;\n\n /**\n * Create raw SQL from a string without parameterization.\n * WARNING: SQL injection risk if used with untrusted input.\n */\n raw(text: string): Sql;\n}\n\nfunction sqlImpl(\n strings: readonly string[],\n ...values: readonly (\n | RawValue\n | Column\n | OlapTable<any>\n | View\n | OlapDictionary<any>\n )[]\n): Sql {\n return new Sql(strings, values);\n}\n\nexport const sql: SqlTemplateTag = sqlImpl as SqlTemplateTag;\n\nsql.statement = function (\n strings: readonly string[],\n ...values: readonly (\n | RawValue\n | Column\n | OlapTable<any>\n | View\n | OlapDictionary<any>\n )[]\n): Sql {\n return new Sql(strings, values, false);\n};\n\nsql.fragment = function (\n strings: readonly string[],\n ...values: readonly (\n | RawValue\n | Column\n | OlapTable<any>\n | View\n | OlapDictionary<any>\n )[]\n): Sql {\n return new Sql(strings, values, true);\n};\n\nconst instanceofSql = (\n value: RawValue | Column | OlapTable<any> | View | OlapDictionary<any>,\n): value is Sql =>\n typeof value === \"object\" && \"values\" in value && \"strings\" in value;\n\n/**\n * A SQL instance can be nested within each other to build SQL strings.\n */\nexport class Sql {\n readonly values: Value[];\n readonly strings: string[];\n readonly isFragment: boolean | undefined;\n\n constructor(\n rawStrings: readonly string[],\n rawValues: readonly (\n | RawValue\n | Column\n | OlapTable<any>\n | View\n | OlapDictionary<any>\n | Sql\n )[],\n isFragment?: boolean,\n ) {\n if (rawStrings.length - 1 !== rawValues.length) {\n if (rawStrings.length === 0) {\n throw new TypeError(\"Expected at least 1 string\");\n }\n\n throw new TypeError(\n `Expected ${rawStrings.length} strings to have ${\n rawStrings.length - 1\n } values`,\n );\n }\n\n const valuesLength = rawValues.reduce<number>(\n (\n len: number,\n value:\n | RawValue\n | Column\n | OlapTable<any>\n | View\n | OlapDictionary<any>\n | Sql,\n ) =>\n len +\n (instanceofSql(value) ? value.values.length\n : (\n isColumn(value) ||\n isTable(value) ||\n isView(value) ||\n isDictionary(value)\n ) ?\n 0\n : 1),\n 0,\n );\n\n this.values = new Array(valuesLength);\n this.strings = new Array(valuesLength + 1);\n this.isFragment = isFragment;\n\n this.strings[0] = rawStrings[0];\n\n // Iterate over raw values, strings, and children. The value is always\n // positioned between two strings, e.g. `index + 1`.\n let i = 0,\n pos = 0;\n while (i < rawValues.length) {\n const child = rawValues[i++];\n const rawString = rawStrings[i];\n\n // Check for nested `sql` queries.\n if (instanceofSql(child)) {\n // Append child prefix text to current string.\n this.strings[pos] += child.strings[0];\n\n let childIndex = 0;\n while (childIndex < child.values.length) {\n this.values[pos++] = child.values[childIndex++];\n this.strings[pos] = child.strings[childIndex];\n }\n\n // Append raw string to current string.\n this.strings[pos] += rawString;\n } else if (isColumn(child)) {\n const aggregationFunction = child.annotations.find(\n ([k, _]) => k === \"aggregationFunction\",\n );\n if (aggregationFunction !== undefined) {\n const funcName = (aggregationFunction[1] as AggregationFunction)\n .functionName;\n const parenIdx = funcName.indexOf(\"(\");\n const mergedName =\n parenIdx !== -1 ?\n `${funcName.slice(0, parenIdx)}Merge${funcName.slice(parenIdx)}`\n : `${funcName}Merge`;\n this.strings[pos] += `${mergedName}(\\`${child.name}\\`)`;\n } else {\n this.strings[pos] += `\\`${child.name}\\``;\n }\n this.strings[pos] += rawString;\n } else if (isTable(child)) {\n const deployedName = child.generateTableName();\n if (child.config.database) {\n this.strings[pos] +=\n `\\`${child.config.database}\\`.\\`${deployedName}\\``;\n } else {\n this.strings[pos] += `\\`${deployedName}\\``;\n }\n this.strings[pos] += rawString;\n } else if (isView(child)) {\n if (child.database) {\n this.strings[pos] += `\\`${child.database}\\`.\\`${child.name}\\``;\n } else {\n this.strings[pos] += `\\`${child.name}\\``;\n }\n this.strings[pos] += rawString;\n } else if (isDictionary(child)) {\n // Dictionaries render as single-quoted string literals for use with dictGet():\n // sql`dictGet(${ProductDict}, 'attr', id)` → dictGet('db.dict_name', 'attr', id)\n // Note: dictionaries are NOT queryable via FROM — ClickHouse will reject such queries.\n if (/\\b(?:FROM|JOIN)\\s*$/i.test(this.strings[pos])) {\n console.warn(\n `OlapDictionary '${child.getQualifiedName()}' interpolated after FROM/JOIN in sql tag. ` +\n `Dictionaries render as string literals (e.g. 'db.dict_name') for use with dictGet(), ` +\n `not as table identifiers. ClickHouse dictionaries cannot be queried directly with FROM/JOIN.`,\n );\n }\n this.strings[pos] +=\n `'${child.getQualifiedName().replace(/'/g, \"''\")}'`;\n this.strings[pos] += rawString;\n } else {\n this.values[pos++] = child;\n this.strings[pos] = rawString;\n }\n }\n }\n\n /**\n * Append another Sql fragment, returning a new Sql instance.\n */\n append(other: Sql): Sql {\n return new Sql(\n [...this.strings, \"\"],\n [...this.values, other],\n this.isFragment,\n );\n }\n}\n\nsql.join = function (fragments: Sql[], separator?: string): Sql {\n if (fragments.length === 0) return new Sql([\"\"], [], true);\n if (fragments.length === 1) {\n const frag = fragments[0];\n return new Sql(frag.strings, frag.values, true);\n }\n const sep = separator ?? \", \";\n const normalized = sep.includes(\" \") ? sep : ` ${sep} `;\n const strings = [\"\", ...Array(fragments.length - 1).fill(normalized), \"\"];\n return new Sql(strings, fragments, true);\n};\n\nsql.raw = function (text: string): Sql {\n return new Sql([text], [], true);\n};\n\nexport const toStaticQuery = (sql: Sql): string => {\n const [query, params] = toQuery(sql);\n if (Object.keys(params).length !== 0) {\n throw new Error(\n \"Dynamic SQL is not allowed in the select statement in view creation.\",\n );\n }\n return query;\n};\n\nexport const toQuery = (sql: Sql): [string, { [pN: string]: any }] => {\n const parameterizedStubs = sql.values.map((v, i) =>\n createClickhouseParameter(i, v),\n );\n\n const query = sql.strings\n .map((s, i) =>\n s != \"\" ? `${s}${emptyIfUndefined(parameterizedStubs[i])}` : \"\",\n )\n .join(\"\");\n\n const query_params = sql.values.reduce(\n (acc: Record<string, unknown>, v, i) => ({\n ...acc,\n [`p${i}`]: getValueFromParameter(v),\n }),\n {},\n );\n return [query, query_params];\n};\n\n/**\n * Build a display-only SQL string with values inlined for logging/debugging.\n * Does not alter execution behavior; use toQuery for actual execution.\n */\nexport const toQueryPreview = (sql: Sql): string => {\n try {\n const formatValue = (v: Value): string => {\n // Unwrap identifiers: [\"Identifier\", name]\n if (Array.isArray(v)) {\n const [type, val] = v as unknown as [string, any];\n if (type === \"Identifier\") {\n // Quote identifiers with backticks like other helpers\n return `\\`${String(val)}\\``;\n }\n // Fallback for unexpected arrays\n return `[${(v as unknown as any[]).map((x) => formatValue(x as Value)).join(\", \")}]`;\n }\n if (v === null || v === undefined) return \"NULL\";\n if (typeof v === \"string\") return `'${v.replace(/'/g, \"''\")}'`;\n if (typeof v === \"number\") return String(v);\n if (typeof v === \"boolean\") return v ? \"true\" : \"false\";\n if (v instanceof Date)\n return `'${v.toISOString().replace(\"T\", \" \").slice(0, 19)}'`;\n try {\n return JSON.stringify(v as unknown as any);\n } catch {\n return String(v);\n }\n };\n\n let out = sql.strings[0] ?? \"\";\n for (let i = 0; i < sql.values.length; i++) {\n const val = getValueFromParameter(sql.values[i] as any);\n out += formatValue(val as Value);\n out += sql.strings[i + 1] ?? \"\";\n }\n return out.replace(/\\s+/g, \" \").trim();\n } catch (error) {\n console.log(`toQueryPreview error: ${error}`);\n return \"/* query preview unavailable */\";\n }\n};\n\nexport const getValueFromParameter = (value: any) => {\n if (Array.isArray(value)) {\n const [type, val] = value;\n if (type === \"Identifier\") return val;\n }\n return value;\n};\nexport function createClickhouseParameter(\n parameterIndex: number,\n value: Value,\n) {\n // ClickHouse use {name:type} be a placeholder, so if we only use number string as name e.g: {1:Unit8}\n // it will face issue when converting to the query params => {1: value1}, because the key is value not string type, so here add prefix \"p\" to avoid this issue.\n return `{p${parameterIndex}:${mapToClickHouseType(value)}}`;\n}\n\n/**\n * Convert the JS type (source is JSON format by API query parameter) to the corresponding ClickHouse type for generating named placeholder of parameterized query.\n * Only support to convert number to Int or Float, boolean to Bool, string to String, other types will convert to String.\n * If exist complex type e.g: object, Array, null, undefined, Date, Record.. etc, just convert to string type by ClickHouse function in SQL.\n * ClickHouse support converting string to other types function.\n * Please see Each section of the https://clickhouse.com/docs/en/sql-reference/functions and https://clickhouse.com/docs/en/sql-reference/functions/type-conversion-functions\n * @param value\n * @returns 'Float', 'Int', 'Bool', 'String'\n */\nexport const mapToClickHouseType = (value: Value) => {\n if (typeof value === \"number\") {\n // infer the float or int according to exist remainder or not\n return Number.isInteger(value) ? \"Int\" : \"Float\";\n }\n // When define column type or query result with parameterized query, The Bool or Boolean type both supported.\n // But the column type of query result only return Bool, so we only support Bool type for safety.\n if (typeof value === \"boolean\") return \"Bool\";\n if (value instanceof Date) return \"DateTime\";\n if (Array.isArray(value)) {\n const [type, _] = value;\n return type;\n }\n return \"String\";\n};\nfunction emptyIfUndefined(value: string | undefined): string {\n return value === undefined ? \"\" : value;\n}\n","/** Validate that a `runs` parameter is a positive integer. */\nexport function validateRuns(runs: number): void {\n if (!Number.isInteger(runs) || runs < 1) {\n throw new Error(`runs must be a positive integer, got ${runs}`);\n }\n}\n","/**\n * ClickHouse diagnostic helpers for benchmarking and profiling queries.\n *\n * Provides utilities to:\n * - EXPLAIN a query and extract index/granule stats\n * - Profile query execution via system.query_log\n * - Benchmark a query over N runs with p50/p95 percentiles\n * - Get row count, part count, and disk size for a table\n */\n\nimport { type Sql, sql, toQueryPreview, quoteIdentifier } from \"../sqlHelpers\";\nimport { type QueryClient } from \"../consumption-apis/query-client\";\nimport { validateRuns } from \"./shared\";\n\n// ---------------------------------------------------------------------------\n// Shared types\n// ---------------------------------------------------------------------------\n\nexport interface BenchmarkResult {\n readonly profiles: readonly ProfileResult[];\n readonly p50: number;\n readonly p95: number;\n}\n\n// ---------------------------------------------------------------------------\n// Shared utilities\n// ---------------------------------------------------------------------------\n\nexport function percentile(values: number[], p: number): number {\n if (values.length === 0) return 0;\n const sorted = [...values].sort((a, b) => a - b);\n const index = Math.min(\n sorted.length - 1,\n Math.ceil((p / 100) * sorted.length) - 1,\n );\n return sorted[Math.max(0, index)];\n}\n\n// ---------------------------------------------------------------------------\n// Explain\n// ---------------------------------------------------------------------------\n\nexport interface ExplainResult {\n readonly indexCondition: string;\n readonly selectedGranules: number;\n readonly totalGranules: number;\n readonly granuleSkipPct: number;\n readonly rawPlan: string;\n}\n\n/**\n * Run EXPLAIN indexes=1 on a query and extract index condition and granule stats.\n *\n * Note: The query is rendered via `toQueryPreview` and re-wrapped with `sql.raw`.\n * This bakes parameterized values into the EXPLAIN string as literals, which\n * matches ClickHouse EXPLAIN behavior (it does not support parameterized queries).\n */\nexport async function explain(\n queryClient: QueryClient,\n query: Sql,\n): Promise<ExplainResult> {\n const sqlString = toQueryPreview(query);\n const explainSql = sql.raw(`EXPLAIN indexes = 1 ${sqlString}`);\n const result = await queryClient.execute(explainSql);\n const rows = (await result.json()) as {\n explain?: string;\n EXPLAIN?: string;\n }[];\n\n const lines = rows.map((r) => r.explain ?? r.EXPLAIN ?? \"\");\n const full = lines.join(\"\\n\");\n\n const conditionMatch = full.match(/Condition:\\s*(.+)/);\n const granulesMatch = full.match(/Granules:\\s*(\\d+)\\/(\\d+)/);\n\n const selected = granulesMatch ? parseInt(granulesMatch[1], 10) : 0;\n const total = granulesMatch ? parseInt(granulesMatch[2], 10) : 0;\n const skipPct = total > 0 ? ((total - selected) / total) * 100 : 0;\n\n return {\n indexCondition: conditionMatch?.[1]?.trim() ?? \"unknown\",\n selectedGranules: selected,\n totalGranules: total,\n granuleSkipPct: Math.round(skipPct),\n rawPlan: full,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Profile (batched)\n// ---------------------------------------------------------------------------\n\nexport interface ProfileResult {\n readonly queryId: string;\n readonly readRows: number;\n readonly readBytes: number;\n readonly memoryUsage: number;\n readonly durationMs: number;\n readonly resultRows: number;\n readonly diskReadUs: number;\n readonly osReadBytes: number;\n}\n\n/**\n * Execute a query and return its query_id for later profile resolution.\n * Does NOT flush logs or read system.query_log — call resolveProfiles() after all runs.\n */\nexport async function profileQuery(\n queryClient: QueryClient,\n query: Sql,\n): Promise<string> {\n const result = await queryClient.execute(query);\n await result.json();\n return result.query_id;\n}\n\n/**\n * Flush logs once and batch-resolve all query_ids from system.query_log.\n * Call this after all profileQuery() calls are complete.\n *\n * Query IDs are parameterized via the `sql` template tag to prevent injection.\n */\nexport async function resolveProfiles(\n queryClient: QueryClient,\n queryIds: string[],\n): Promise<ProfileResult[]> {\n if (queryIds.length === 0) return [];\n\n await queryClient.command(sql.raw(\"SYSTEM FLUSH LOGS\"));\n\n const idFragments = queryIds.map((id) => sql`${id}`);\n const logQuery = sql`SELECT\n query_id, read_rows, read_bytes, memory_usage, query_duration_ms, result_rows,\n ProfileEvents['DiskReadElapsedMicroseconds'] as disk_read_us,\n ProfileEvents['OSReadChars'] as os_read_bytes\n FROM system.query_log\n WHERE query_id IN (${sql.join(idFragments)}) AND type = 'QueryFinish'\n ORDER BY event_time ASC`;\n\n const logResult = await queryClient.execute(logQuery);\n const logRows = (await logResult.json()) as {\n query_id: string;\n read_rows?: string;\n read_bytes?: string;\n memory_usage?: string;\n query_duration_ms?: string;\n result_rows?: string;\n disk_read_us?: string;\n os_read_bytes?: string;\n }[];\n\n const byId = new Map(logRows.map((r) => [r.query_id, r]));\n\n return queryIds.map((id) => {\n const entry = byId.get(id);\n if (!entry) {\n throw new Error(\n `No query_log entry for query_id=${id}. Check log_queries setting.`,\n );\n }\n return {\n queryId: id,\n readRows: Number(entry.read_rows ?? 0),\n readBytes: Number(entry.read_bytes ?? 0),\n memoryUsage: Number(entry.memory_usage ?? 0),\n durationMs: Number(entry.query_duration_ms ?? 0),\n resultRows: Number(entry.result_rows ?? 0),\n diskReadUs: Number(entry.disk_read_us ?? 0),\n osReadBytes: Number(entry.os_read_bytes ?? 0),\n };\n });\n}\n\n/**\n * Run a query N times, then batch-resolve all profiles in one flush + one lookup.\n * Returns profiles with p50/p95 of server-side duration.\n */\nexport async function profileBenchmark(\n queryClient: QueryClient,\n query: Sql,\n runs: number,\n): Promise<BenchmarkResult> {\n validateRuns(runs);\n\n const queryIds: string[] = [];\n for (let i = 0; i < runs; i++) {\n queryIds.push(await profileQuery(queryClient, query));\n }\n\n const profiles = await resolveProfiles(queryClient, queryIds);\n const durations = profiles.map((p) => p.durationMs);\n\n return {\n profiles,\n p50: percentile(durations, 50),\n p95: percentile(durations, 95),\n };\n}\n\n// ---------------------------------------------------------------------------\n// Table stats\n// ---------------------------------------------------------------------------\n\n// Matches \"table\" or \"database.table\" — no trailing/leading/consecutive dots\nconst TABLE_NAME_RE = /^[a-zA-Z_][a-zA-Z0-9_]*(?:\\.[a-zA-Z_][a-zA-Z0-9_]*)?$/;\n\nexport interface TableStats {\n readonly table: string;\n readonly rows: number;\n readonly parts: number;\n readonly diskSize: string;\n}\n\nexport async function tableStats(\n queryClient: QueryClient,\n table: string,\n): Promise<TableStats> {\n if (!TABLE_NAME_RE.test(table)) {\n throw new Error(`Invalid table name: ${table}`);\n }\n\n // Split on dot to handle database.table — quote each part separately\n const quoted = table\n .split(\".\")\n .map((part) => quoteIdentifier(part))\n .join(\".\");\n // Extract database and bare table name for system.parts lookup\n const parts = table.split(\".\");\n const bareTable = parts.length > 1 ? parts[parts.length - 1] : table;\n const bareDatabase = parts.length > 1 ? parts[0] : null;\n\n const [countRows, partsRows] = await Promise.all([\n queryClient\n .execute(sql.raw(`SELECT count() as rows FROM ${quoted}`))\n .then((r) => r.json() as Promise<{ rows: string }[]>),\n queryClient\n .execute(\n bareDatabase ?\n sql`SELECT count() as parts, formatReadableSize(sum(bytes_on_disk)) as disk_size\n FROM system.parts\n WHERE active = 1 AND table = ${bareTable} AND database = ${bareDatabase}`\n : sql`SELECT count() as parts, formatReadableSize(sum(bytes_on_disk)) as disk_size\n FROM system.parts\n WHERE active = 1 AND table = ${bareTable}`,\n )\n .then((r) => r.json() as Promise<{ parts: string; disk_size: string }[]>),\n ]);\n\n return {\n table,\n rows: Number(countRows[0]?.rows ?? 0),\n parts: Number(partsRows[0]?.parts ?? 0),\n diskSize: partsRows[0]?.disk_size ?? \"unknown\",\n };\n}\n","/**\n * Test report accumulator — collects results from test cases\n * and writes a timestamped JSON detail file.\n */\n\nimport { writeFile, mkdir } from \"node:fs/promises\";\nimport { join } from \"node:path\";\n\nexport interface TestReportTarget {\n readonly host: string;\n readonly database: string;\n}\n\nexport interface TestReport {\n readonly timestamp: string;\n readonly target: TestReportTarget;\n tests: Record<string, unknown>;\n}\n\nexport interface TestReporterOptions {\n /** Prefix for the output filename (e.g. \"benchmark-details\") */\n prefix: string;\n /** Directory to write report files into */\n outputDir: string;\n /** ClickHouse target info. If omitted, reads from MOOSE_CLICKHOUSE_CONFIG__* env vars. */\n target?: TestReportTarget;\n}\n\nexport function createTestReporter(options: TestReporterOptions): {\n results: TestReport;\n flush: () => Promise<string>;\n} {\n const target = options.target ?? {\n host: process.env.MOOSE_CLICKHOUSE_CONFIG__HOST ?? \"localhost\",\n database: process.env.MOOSE_CLICKHOUSE_CONFIG__DB_NAME ?? \"local\",\n };\n\n const results: TestReport = {\n timestamp: new Date().toISOString(),\n target,\n tests: {},\n };\n\n const flush = async (): Promise<string> => {\n await mkdir(options.outputDir, { recursive: true });\n const filename = `${options.prefix}-${new Date().toISOString().replace(/[:.]/g, \"-\")}.json`;\n const filepath = join(options.outputDir, filename);\n await writeFile(filepath, JSON.stringify(results, null, 2));\n return filepath;\n };\n\n return { results, flush };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACWO,IAAM,kBAAkB,CAAC,SAAyB;AACvD,SAAO,KAAK,WAAW,GAAG,KAAK,KAAK,SAAS,GAAG,IAAI,OAAO,KAAK,IAAI;AACtE;AAEA,IAAM,UAAU,CACd,UAEA,OAAO,UAAU,YACjB,UAAU,QACV,UAAU,SACV,MAAM,SAAS;AAEjB,IAAM,SAAS,CACb,UAEA,OAAO,UAAU,YACjB,UAAU,QACV,UAAU,SACV,MAAM,SAAS;AAEjB,IAAM,eAAe,CACnB,UAEA,OAAO,UAAU,YACjB,UAAU,QACV,UAAU,SACT,MAAc,SAAS;AAwB1B,IAAM,WAAW,CACf,UAEA,OAAO,UAAU,YACjB,UAAU,QACV,EAAE,UAAU,UACZ,UAAU,SACV,iBAAiB;AAgEnB,SAAS,QACP,YACG,QAOE;AACL,SAAO,IAAI,IAAI,SAAS,MAAM;AAChC;AAEO,IAAM,MAAsB;AAEnC,IAAI,YAAY,SACd,YACG,QAOE;AACL,SAAO,IAAI,IAAI,SAAS,QAAQ,KAAK;AACvC;AAEA,IAAI,WAAW,SACb,YACG,QAOE;AACL,SAAO,IAAI,IAAI,SAAS,QAAQ,IAAI;AACtC;AAEA,IAAM,gBAAgB,CACpB,UAEA,OAAO,UAAU,YAAY,YAAY,SAAS,aAAa;AAK1D,IAAM,MAAN,MAAM,KAAI;AAAA,EACN;AAAA,EACA;AAAA,EACA;AAAA,EAET,YACE,YACA,WAQA,YACA;AACA,QAAI,WAAW,SAAS,MAAM,UAAU,QAAQ;AAC9C,UAAI,WAAW,WAAW,GAAG;AAC3B,cAAM,IAAI,UAAU,4BAA4B;AAAA,MAClD;AAEA,YAAM,IAAI;AAAA,QACR,YAAY,WAAW,MAAM,oBAC3B,WAAW,SAAS,CACtB;AAAA,MACF;AAAA,IACF;AAEA,UAAM,eAAe,UAAU;AAAA,MAC7B,CACE,KACA,UAQA,OACC,cAAc,KAAK,IAAI,MAAM,OAAO,SAEnC,SAAS,KAAK,KACd,QAAQ,KAAK,KACb,OAAO,KAAK,KACZ,aAAa,KAAK,IAElB,IACA;AAAA,MACJ;AAAA,IACF;AAEA,SAAK,SAAS,IAAI,MAAM,YAAY;AACpC,SAAK,UAAU,IAAI,MAAM,eAAe,CAAC;AACzC,SAAK,aAAa;AAElB,SAAK,QAAQ,CAAC,IAAI,WAAW,CAAC;AAI9B,QAAI,IAAI,GACN,MAAM;AACR,WAAO,IAAI,UAAU,QAAQ;AAC3B,YAAM,QAAQ,UAAU,GAAG;AAC3B,YAAM,YAAY,WAAW,CAAC;AAG9B,UAAI,cAAc,KAAK,GAAG;AAExB,aAAK,QAAQ,GAAG,KAAK,MAAM,QAAQ,CAAC;AAEpC,YAAI,aAAa;AACjB,eAAO,aAAa,MAAM,OAAO,QAAQ;AACvC,eAAK,OAAO,KAAK,IAAI,MAAM,OAAO,YAAY;AAC9C,eAAK,QAAQ,GAAG,IAAI,MAAM,QAAQ,UAAU;AAAA,QAC9C;AAGA,aAAK,QAAQ,GAAG,KAAK;AAAA,MACvB,WAAW,SAAS,KAAK,GAAG;AAC1B,cAAM,sBAAsB,MAAM,YAAY;AAAA,UAC5C,CAAC,CAAC,GAAG,CAAC,MAAM,MAAM;AAAA,QACpB;AACA,YAAI,wBAAwB,QAAW;AACrC,gBAAM,WAAY,oBAAoB,CAAC,EACpC;AACH,gBAAM,WAAW,SAAS,QAAQ,GAAG;AACrC,gBAAM,aACJ,aAAa,KACX,GAAG,SAAS,MAAM,GAAG,QAAQ,CAAC,QAAQ,SAAS,MAAM,QAAQ,CAAC,KAC9D,GAAG,QAAQ;AACf,eAAK,QAAQ,GAAG,KAAK,GAAG,UAAU,MAAM,MAAM,IAAI;AAAA,QACpD,OAAO;AACL,eAAK,QAAQ,GAAG,KAAK,KAAK,MAAM,IAAI;AAAA,QACtC;AACA,aAAK,QAAQ,GAAG,KAAK;AAAA,MACvB,WAAW,QAAQ,KAAK,GAAG;AACzB,cAAM,eAAe,MAAM,kBAAkB;AAC7C,YAAI,MAAM,OAAO,UAAU;AACzB,eAAK,QAAQ,GAAG,KACd,KAAK,MAAM,OAAO,QAAQ,QAAQ,YAAY;AAAA,QAClD,OAAO;AACL,eAAK,QAAQ,GAAG,KAAK,KAAK,YAAY;AAAA,QACxC;AACA,aAAK,QAAQ,GAAG,KAAK;AAAA,MACvB,WAAW,OAAO,KAAK,GAAG;AACxB,YAAI,MAAM,UAAU;AAClB,eAAK,QAAQ,GAAG,KAAK,KAAK,MAAM,QAAQ,QAAQ,MAAM,IAAI;AAAA,QAC5D,OAAO;AACL,eAAK,QAAQ,GAAG,KAAK,KAAK,MAAM,IAAI;AAAA,QACtC;AACA,aAAK,QAAQ,GAAG,KAAK;AAAA,MACvB,WAAW,aAAa,KAAK,GAAG;AAI9B,YAAI,uBAAuB,KAAK,KAAK,QAAQ,GAAG,CAAC,GAAG;AAClD,kBAAQ;AAAA,YACN,mBAAmB,MAAM,iBAAiB,CAAC;AAAA,UAG7C;AAAA,QACF;AACA,aAAK,QAAQ,GAAG,KACd,IAAI,MAAM,iBAAiB,EAAE,QAAQ,MAAM,IAAI,CAAC;AAClD,aAAK,QAAQ,GAAG,KAAK;AAAA,MACvB,OAAO;AACL,aAAK,OAAO,KAAK,IAAI;AACrB,aAAK,QAAQ,GAAG,IAAI;AAAA,MACtB;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,OAAO,OAAiB;AACtB,WAAO,IAAI;AAAA,MACT,CAAC,GAAG,KAAK,SAAS,EAAE;AAAA,MACpB,CAAC,GAAG,KAAK,QAAQ,KAAK;AAAA,MACtB,KAAK;AAAA,IACP;AAAA,EACF;AACF;AAEA,IAAI,OAAO,SAAU,WAAkB,WAAyB;AAC9D,MAAI,UAAU,WAAW,EAAG,QAAO,IAAI,IAAI,CAAC,EAAE,GAAG,CAAC,GAAG,IAAI;AACzD,MAAI,UAAU,WAAW,GAAG;AAC1B,UAAM,OAAO,UAAU,CAAC;AACxB,WAAO,IAAI,IAAI,KAAK,SAAS,KAAK,QAAQ,IAAI;AAAA,EAChD;AACA,QAAM,MAAM,aAAa;AACzB,QAAM,aAAa,IAAI,SAAS,GAAG,IAAI,MAAM,IAAI,GAAG;AACpD,QAAM,UAAU,CAAC,IAAI,GAAG,MAAM,UAAU,SAAS,CAAC,EAAE,KAAK,UAAU,GAAG,EAAE;AACxE,SAAO,IAAI,IAAI,SAAS,WAAW,IAAI;AACzC;AAEA,IAAI,MAAM,SAAU,MAAmB;AACrC,SAAO,IAAI,IAAI,CAAC,IAAI,GAAG,CAAC,GAAG,IAAI;AACjC;AAqCO,IAAM,iBAAiB,CAACA,SAAqB;AAClD,MAAI;AACF,UAAM,cAAc,CAAC,MAAqB;AAExC,UAAI,MAAM,QAAQ,CAAC,GAAG;AACpB,cAAM,CAAC,MAAM,GAAG,IAAI;AACpB,YAAI,SAAS,cAAc;AAEzB,iBAAO,KAAK,OAAO,GAAG,CAAC;AAAA,QACzB;AAEA,eAAO,IAAK,EAAuB,IAAI,CAAC,MAAM,YAAY,CAAU,CAAC,EAAE,KAAK,IAAI,CAAC;AAAA,MACnF;AACA,UAAI,MAAM,QAAQ,MAAM,OAAW,QAAO;AAC1C,UAAI,OAAO,MAAM,SAAU,QAAO,IAAI,EAAE,QAAQ,MAAM,IAAI,CAAC;AAC3D,UAAI,OAAO,MAAM,SAAU,QAAO,OAAO,CAAC;AAC1C,UAAI,OAAO,MAAM,UAAW,QAAO,IAAI,SAAS;AAChD,UAAI,aAAa;AACf,eAAO,IAAI,EAAE,YAAY,EAAE,QAAQ,KAAK,GAAG,EAAE,MAAM,GAAG,EAAE,CAAC;AAC3D,UAAI;AACF,eAAO,KAAK,UAAU,CAAmB;AAAA,MAC3C,QAAQ;AACN,eAAO,OAAO,CAAC;AAAA,MACjB;AAAA,IACF;AAEA,QAAI,MAAMA,KAAI,QAAQ,CAAC,KAAK;AAC5B,aAAS,IAAI,GAAG,IAAIA,KAAI,OAAO,QAAQ,KAAK;AAC1C,YAAM,MAAM,sBAAsBA,KAAI,OAAO,CAAC,CAAQ;AACtD,aAAO,YAAY,GAAY;AAC/B,aAAOA,KAAI,QAAQ,IAAI,CAAC,KAAK;AAAA,IAC/B;AACA,WAAO,IAAI,QAAQ,QAAQ,GAAG,EAAE,KAAK;AAAA,EACvC,SAAS,OAAO;AACd,YAAQ,IAAI,yBAAyB,KAAK,EAAE;AAC5C,WAAO;AAAA,EACT;AACF;AAEO,IAAM,wBAAwB,CAAC,UAAe;AACnD,MAAI,MAAM,QAAQ,KAAK,GAAG;AACxB,UAAM,CAAC,MAAM,GAAG,IAAI;AACpB,QAAI,SAAS,aAAc,QAAO;AAAA,EACpC;AACA,SAAO;AACT;;;ACtaO,SAAS,aAAa,MAAoB;AAC/C,MAAI,CAAC,OAAO,UAAU,IAAI,KAAK,OAAO,GAAG;AACvC,UAAM,IAAI,MAAM,wCAAwC,IAAI,EAAE;AAAA,EAChE;AACF;;;ACuBO,SAAS,WAAW,QAAkB,GAAmB;AAC9D,MAAI,OAAO,WAAW,EAAG,QAAO;AAChC,QAAM,SAAS,CAAC,GAAG,MAAM,EAAE,KAAK,CAAC,GAAG,MAAM,IAAI,CAAC;AAC/C,QAAM,QAAQ,KAAK;AAAA,IACjB,OAAO,SAAS;AAAA,IAChB,KAAK,KAAM,IAAI,MAAO,OAAO,MAAM,IAAI;AAAA,EACzC;AACA,SAAO,OAAO,KAAK,IAAI,GAAG,KAAK,CAAC;AAClC;AAqBA,eAAsB,QACpB,aACA,OACwB;AACxB,QAAM,YAAY,eAAe,KAAK;AACtC,QAAM,aAAa,IAAI,IAAI,uBAAuB,SAAS,EAAE;AAC7D,QAAM,SAAS,MAAM,YAAY,QAAQ,UAAU;AACnD,QAAM,OAAQ,MAAM,OAAO,KAAK;AAKhC,QAAM,QAAQ,KAAK,IAAI,CAAC,MAAM,EAAE,WAAW,EAAE,WAAW,EAAE;AAC1D,QAAM,OAAO,MAAM,KAAK,IAAI;AAE5B,QAAM,iBAAiB,KAAK,MAAM,mBAAmB;AACrD,QAAM,gBAAgB,KAAK,MAAM,0BAA0B;AAE3D,QAAM,WAAW,gBAAgB,SAAS,cAAc,CAAC,GAAG,EAAE,IAAI;AAClE,QAAM,QAAQ,gBAAgB,SAAS,cAAc,CAAC,GAAG,EAAE,IAAI;AAC/D,QAAM,UAAU,QAAQ,KAAM,QAAQ,YAAY,QAAS,MAAM;AAEjE,SAAO;AAAA,IACL,gBAAgB,iBAAiB,CAAC,GAAG,KAAK,KAAK;AAAA,IAC/C,kBAAkB;AAAA,IAClB,eAAe;AAAA,IACf,gBAAgB,KAAK,MAAM,OAAO;AAAA,IAClC,SAAS;AAAA,EACX;AACF;AAqBA,eAAsB,aACpB,aACA,OACiB;AACjB,QAAM,SAAS,MAAM,YAAY,QAAQ,KAAK;AAC9C,QAAM,OAAO,KAAK;AAClB,SAAO,OAAO;AAChB;AAQA,eAAsB,gBACpB,aACA,UAC0B;AAC1B,MAAI,SAAS,WAAW,EAAG,QAAO,CAAC;AAEnC,QAAM,YAAY,QAAQ,IAAI,IAAI,mBAAmB,CAAC;AAEtD,QAAM,cAAc,SAAS,IAAI,CAAC,OAAO,MAAM,EAAE,EAAE;AACnD,QAAM,WAAW;AAAA;AAAA;AAAA;AAAA;AAAA,uBAKI,IAAI,KAAK,WAAW,CAAC;AAAA;AAG1C,QAAM,YAAY,MAAM,YAAY,QAAQ,QAAQ;AACpD,QAAM,UAAW,MAAM,UAAU,KAAK;AAWtC,QAAM,OAAO,IAAI,IAAI,QAAQ,IAAI,CAAC,MAAM,CAAC,EAAE,UAAU,CAAC,CAAC,CAAC;AAExD,SAAO,SAAS,IAAI,CAAC,OAAO;AAC1B,UAAM,QAAQ,KAAK,IAAI,EAAE;AACzB,QAAI,CAAC,OAAO;AACV,YAAM,IAAI;AAAA,QACR,mCAAmC,EAAE;AAAA,MACvC;AAAA,IACF;AACA,WAAO;AAAA,MACL,SAAS;AAAA,MACT,UAAU,OAAO,MAAM,aAAa,CAAC;AAAA,MACrC,WAAW,OAAO,MAAM,cAAc,CAAC;AAAA,MACvC,aAAa,OAAO,MAAM,gBAAgB,CAAC;AAAA,MAC3C,YAAY,OAAO,MAAM,qBAAqB,CAAC;AAAA,MAC/C,YAAY,OAAO,MAAM,eAAe,CAAC;AAAA,MACzC,YAAY,OAAO,MAAM,gBAAgB,CAAC;AAAA,MAC1C,aAAa,OAAO,MAAM,iBAAiB,CAAC;AAAA,IAC9C;AAAA,EACF,CAAC;AACH;AAMA,eAAsB,iBACpB,aACA,OACA,MAC0B;AAC1B,eAAa,IAAI;AAEjB,QAAM,WAAqB,CAAC;AAC5B,WAAS,IAAI,GAAG,IAAI,MAAM,KAAK;AAC7B,aAAS,KAAK,MAAM,aAAa,aAAa,KAAK,CAAC;AAAA,EACtD;AAEA,QAAM,WAAW,MAAM,gBAAgB,aAAa,QAAQ;AAC5D,QAAM,YAAY,SAAS,IAAI,CAAC,MAAM,EAAE,UAAU;AAElD,SAAO;AAAA,IACL;AAAA,IACA,KAAK,WAAW,WAAW,EAAE;AAAA,IAC7B,KAAK,WAAW,WAAW,EAAE;AAAA,EAC/B;AACF;AAOA,IAAM,gBAAgB;AAStB,eAAsB,WACpB,aACA,OACqB;AACrB,MAAI,CAAC,cAAc,KAAK,KAAK,GAAG;AAC9B,UAAM,IAAI,MAAM,uBAAuB,KAAK,EAAE;AAAA,EAChD;AAGA,QAAM,SAAS,MACZ,MAAM,GAAG,EACT,IAAI,CAAC,SAAS,gBAAgB,IAAI,CAAC,EACnC,KAAK,GAAG;AAEX,QAAM,QAAQ,MAAM,MAAM,GAAG;AAC7B,QAAM,YAAY,MAAM,SAAS,IAAI,MAAM,MAAM,SAAS,CAAC,IAAI;AAC/D,QAAM,eAAe,MAAM,SAAS,IAAI,MAAM,CAAC,IAAI;AAEnD,QAAM,CAAC,WAAW,SAAS,IAAI,MAAM,QAAQ,IAAI;AAAA,IAC/C,YACG,QAAQ,IAAI,IAAI,+BAA+B,MAAM,EAAE,CAAC,EACxD,KAAK,CAAC,MAAM,EAAE,KAAK,CAAgC;AAAA,IACtD,YACG;AAAA,MACC,eACE;AAAA;AAAA,0CAEgC,SAAS,mBAAmB,YAAY,KACxE;AAAA;AAAA,0CAEgC,SAAS;AAAA,IAC7C,EACC,KAAK,CAAC,MAAM,EAAE,KAAK,CAAoD;AAAA,EAC5E,CAAC;AAED,SAAO;AAAA,IACL;AAAA,IACA,MAAM,OAAO,UAAU,CAAC,GAAG,QAAQ,CAAC;AAAA,IACpC,OAAO,OAAO,UAAU,CAAC,GAAG,SAAS,CAAC;AAAA,IACtC,UAAU,UAAU,CAAC,GAAG,aAAa;AAAA,EACvC;AACF;;;ACzPA,sBAAiC;AACjC,uBAAqB;AAsBd,SAAS,mBAAmB,SAGjC;AACA,QAAM,SAAS,QAAQ,UAAU;AAAA,IAC/B,MAAM,QAAQ,IAAI,iCAAiC;AAAA,IACnD,UAAU,QAAQ,IAAI,oCAAoC;AAAA,EAC5D;AAEA,QAAM,UAAsB;AAAA,IAC1B,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,IAClC;AAAA,IACA,OAAO,CAAC;AAAA,EACV;AAEA,QAAM,QAAQ,YAA6B;AACzC,cAAM,uBAAM,QAAQ,WAAW,EAAE,WAAW,KAAK,CAAC;AAClD,UAAM,WAAW,GAAG,QAAQ,MAAM,KAAI,oBAAI,KAAK,GAAE,YAAY,EAAE,QAAQ,SAAS,GAAG,CAAC;AACpF,UAAM,eAAW,uBAAK,QAAQ,WAAW,QAAQ;AACjD,cAAM,2BAAU,UAAU,KAAK,UAAU,SAAS,MAAM,CAAC,CAAC;AAC1D,WAAO;AAAA,EACT;AAEA,SAAO,EAAE,SAAS,MAAM;AAC1B;","names":["sql"]}
@@ -4,6 +4,7 @@ var quoteIdentifier = (name) => {
4
4
  };
5
5
  var isTable = (value) => typeof value === "object" && value !== null && "kind" in value && value.kind === "OlapTable";
6
6
  var isView = (value) => typeof value === "object" && value !== null && "kind" in value && value.kind === "View";
7
+ var isDictionary = (value) => typeof value === "object" && value !== null && "kind" in value && value.kind === "OlapDictionary";
7
8
  var isColumn = (value) => typeof value === "object" && value !== null && !("kind" in value) && "name" in value && "annotations" in value;
8
9
  function sqlImpl(strings, ...values) {
9
10
  return new Sql(strings, values);
@@ -30,7 +31,7 @@ var Sql = class _Sql {
30
31
  );
31
32
  }
32
33
  const valuesLength = rawValues.reduce(
33
- (len, value) => len + (instanceofSql(value) ? value.values.length : isColumn(value) || isTable(value) || isView(value) ? 0 : 1),
34
+ (len, value) => len + (instanceofSql(value) ? value.values.length : isColumn(value) || isTable(value) || isView(value) || isDictionary(value) ? 0 : 1),
34
35
  0
35
36
  );
36
37
  this.values = new Array(valuesLength);
@@ -77,6 +78,14 @@ var Sql = class _Sql {
77
78
  this.strings[pos] += `\`${child.name}\``;
78
79
  }
79
80
  this.strings[pos] += rawString;
81
+ } else if (isDictionary(child)) {
82
+ if (/\b(?:FROM|JOIN)\s*$/i.test(this.strings[pos])) {
83
+ console.warn(
84
+ `OlapDictionary '${child.getQualifiedName()}' interpolated after FROM/JOIN in sql tag. Dictionaries render as string literals (e.g. 'db.dict_name') for use with dictGet(), not as table identifiers. ClickHouse dictionaries cannot be queried directly with FROM/JOIN.`
85
+ );
86
+ }
87
+ this.strings[pos] += `'${child.getQualifiedName().replace(/'/g, "''")}'`;
88
+ this.strings[pos] += rawString;
80
89
  } else {
81
90
  this.values[pos++] = child;
82
91
  this.strings[pos] = rawString;
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/sqlHelpers.ts","../../src/testing/shared.ts","../../src/testing/clickhouse-diagnostics.ts","../../src/testing/test-reporter.ts"],"sourcesContent":["// source https://github.com/blakeembrey/sql-template-tag/blob/main/src/index.ts\nimport { Column } from \"./dataModels/dataModelTypes\";\nimport { OlapTable, View } from \"./dmv2\";\nimport { AggregationFunction } from \"./dataModels/typeConvert\";\n\n/**\n * Quote a ClickHouse identifier with backticks if not already quoted.\n * Backticks allow special characters (e.g., hyphens) in identifiers.\n */\nexport const quoteIdentifier = (name: string): string => {\n return name.startsWith(\"`\") && name.endsWith(\"`\") ? name : `\\`${name}\\``;\n};\n\nconst isTable = (\n value: RawValue | Column | OlapTable<any> | View,\n): value is OlapTable<any> =>\n typeof value === \"object\" &&\n value !== null &&\n \"kind\" in value &&\n value.kind === \"OlapTable\";\n\nconst isView = (\n value: RawValue | Column | OlapTable<any> | View,\n): value is View =>\n typeof value === \"object\" &&\n value !== null &&\n \"kind\" in value &&\n value.kind === \"View\";\n\nexport type IdentifierBrandedString = string & {\n readonly __identifier_brand?: unique symbol;\n};\nexport type NonIdentifierBrandedString = string & {\n readonly __identifier_brand?: unique symbol;\n};\n\n/**\n * Values supported by SQL engine.\n */\nexport type Value =\n | NonIdentifierBrandedString\n | number\n | boolean\n | Date\n | [string, string];\n\n/**\n * Supported value or SQL instance.\n */\nexport type RawValue = Value | Sql;\n\nconst isColumn = (\n value: RawValue | Column | OlapTable<any> | View,\n): value is Column =>\n typeof value === \"object\" &&\n value !== null &&\n !(\"kind\" in value) &&\n \"name\" in value &&\n \"annotations\" in value;\n\n/**\n * Sql template tag interface with attached helper methods.\n */\nexport interface SqlTemplateTag {\n /**\n * @deprecated Use `sql.statement` for full SQL statements or `sql.fragment` for SQL fragments.\n */\n (\n strings: readonly string[],\n ...values: readonly (RawValue | Column | OlapTable<any> | View)[]\n ): Sql;\n\n /**\n * Template literal tag for complete SQL statements (e.g. SELECT, INSERT, CREATE).\n * Produces a Sql instance with `isFragment = false`.\n */\n statement(\n strings: readonly string[],\n ...values: readonly (RawValue | Column | OlapTable<any> | View)[]\n ): Sql;\n\n /**\n * Template literal tag for SQL fragments (e.g. expressions, conditions, partial clauses).\n * Produces a Sql instance with `isFragment = true`.\n */\n fragment(\n strings: readonly string[],\n ...values: readonly (RawValue | Column | OlapTable<any> | View)[]\n ): Sql;\n\n /**\n * Join an array of Sql fragments with a separator.\n * @param fragments - Array of Sql fragments to join\n * @param separator - Optional separator string (defaults to \", \")\n */\n join(fragments: Sql[], separator?: string): Sql;\n\n /**\n * Create raw SQL from a string without parameterization.\n * WARNING: SQL injection risk if used with untrusted input.\n */\n raw(text: string): Sql;\n}\n\nfunction sqlImpl(\n strings: readonly string[],\n ...values: readonly (RawValue | Column | OlapTable<any> | View)[]\n): Sql {\n return new Sql(strings, values);\n}\n\nexport const sql: SqlTemplateTag = sqlImpl as SqlTemplateTag;\n\nsql.statement = function (\n strings: readonly string[],\n ...values: readonly (RawValue | Column | OlapTable<any> | View)[]\n): Sql {\n return new Sql(strings, values, false);\n};\n\nsql.fragment = function (\n strings: readonly string[],\n ...values: readonly (RawValue | Column | OlapTable<any> | View)[]\n): Sql {\n return new Sql(strings, values, true);\n};\n\nconst instanceofSql = (\n value: RawValue | Column | OlapTable<any> | View,\n): value is Sql =>\n typeof value === \"object\" && \"values\" in value && \"strings\" in value;\n\n/**\n * A SQL instance can be nested within each other to build SQL strings.\n */\nexport class Sql {\n readonly values: Value[];\n readonly strings: string[];\n readonly isFragment: boolean | undefined;\n\n constructor(\n rawStrings: readonly string[],\n rawValues: readonly (RawValue | Column | OlapTable<any> | View | Sql)[],\n isFragment?: boolean,\n ) {\n if (rawStrings.length - 1 !== rawValues.length) {\n if (rawStrings.length === 0) {\n throw new TypeError(\"Expected at least 1 string\");\n }\n\n throw new TypeError(\n `Expected ${rawStrings.length} strings to have ${\n rawStrings.length - 1\n } values`,\n );\n }\n\n const valuesLength = rawValues.reduce<number>(\n (len: number, value: RawValue | Column | OlapTable<any> | View | Sql) =>\n len +\n (instanceofSql(value) ? value.values.length\n : isColumn(value) || isTable(value) || isView(value) ? 0\n : 1),\n 0,\n );\n\n this.values = new Array(valuesLength);\n this.strings = new Array(valuesLength + 1);\n this.isFragment = isFragment;\n\n this.strings[0] = rawStrings[0];\n\n // Iterate over raw values, strings, and children. The value is always\n // positioned between two strings, e.g. `index + 1`.\n let i = 0,\n pos = 0;\n while (i < rawValues.length) {\n const child = rawValues[i++];\n const rawString = rawStrings[i];\n\n // Check for nested `sql` queries.\n if (instanceofSql(child)) {\n // Append child prefix text to current string.\n this.strings[pos] += child.strings[0];\n\n let childIndex = 0;\n while (childIndex < child.values.length) {\n this.values[pos++] = child.values[childIndex++];\n this.strings[pos] = child.strings[childIndex];\n }\n\n // Append raw string to current string.\n this.strings[pos] += rawString;\n } else if (isColumn(child)) {\n const aggregationFunction = child.annotations.find(\n ([k, _]) => k === \"aggregationFunction\",\n );\n if (aggregationFunction !== undefined) {\n const funcName = (aggregationFunction[1] as AggregationFunction)\n .functionName;\n const parenIdx = funcName.indexOf(\"(\");\n const mergedName =\n parenIdx !== -1 ?\n `${funcName.slice(0, parenIdx)}Merge${funcName.slice(parenIdx)}`\n : `${funcName}Merge`;\n this.strings[pos] += `${mergedName}(\\`${child.name}\\`)`;\n } else {\n this.strings[pos] += `\\`${child.name}\\``;\n }\n this.strings[pos] += rawString;\n } else if (isTable(child)) {\n const deployedName = child.generateTableName();\n if (child.config.database) {\n this.strings[pos] +=\n `\\`${child.config.database}\\`.\\`${deployedName}\\``;\n } else {\n this.strings[pos] += `\\`${deployedName}\\``;\n }\n this.strings[pos] += rawString;\n } else if (isView(child)) {\n if (child.database) {\n this.strings[pos] += `\\`${child.database}\\`.\\`${child.name}\\``;\n } else {\n this.strings[pos] += `\\`${child.name}\\``;\n }\n this.strings[pos] += rawString;\n } else {\n this.values[pos++] = child;\n this.strings[pos] = rawString;\n }\n }\n }\n\n /**\n * Append another Sql fragment, returning a new Sql instance.\n */\n append(other: Sql): Sql {\n return new Sql(\n [...this.strings, \"\"],\n [...this.values, other],\n this.isFragment,\n );\n }\n}\n\nsql.join = function (fragments: Sql[], separator?: string): Sql {\n if (fragments.length === 0) return new Sql([\"\"], [], true);\n if (fragments.length === 1) {\n const frag = fragments[0];\n return new Sql(frag.strings, frag.values, true);\n }\n const sep = separator ?? \", \";\n const normalized = sep.includes(\" \") ? sep : ` ${sep} `;\n const strings = [\"\", ...Array(fragments.length - 1).fill(normalized), \"\"];\n return new Sql(strings, fragments, true);\n};\n\nsql.raw = function (text: string): Sql {\n return new Sql([text], [], true);\n};\n\nexport const toStaticQuery = (sql: Sql): string => {\n const [query, params] = toQuery(sql);\n if (Object.keys(params).length !== 0) {\n throw new Error(\n \"Dynamic SQL is not allowed in the select statement in view creation.\",\n );\n }\n return query;\n};\n\nexport const toQuery = (sql: Sql): [string, { [pN: string]: any }] => {\n const parameterizedStubs = sql.values.map((v, i) =>\n createClickhouseParameter(i, v),\n );\n\n const query = sql.strings\n .map((s, i) =>\n s != \"\" ? `${s}${emptyIfUndefined(parameterizedStubs[i])}` : \"\",\n )\n .join(\"\");\n\n const query_params = sql.values.reduce(\n (acc: Record<string, unknown>, v, i) => ({\n ...acc,\n [`p${i}`]: getValueFromParameter(v),\n }),\n {},\n );\n return [query, query_params];\n};\n\n/**\n * Build a display-only SQL string with values inlined for logging/debugging.\n * Does not alter execution behavior; use toQuery for actual execution.\n */\nexport const toQueryPreview = (sql: Sql): string => {\n try {\n const formatValue = (v: Value): string => {\n // Unwrap identifiers: [\"Identifier\", name]\n if (Array.isArray(v)) {\n const [type, val] = v as unknown as [string, any];\n if (type === \"Identifier\") {\n // Quote identifiers with backticks like other helpers\n return `\\`${String(val)}\\``;\n }\n // Fallback for unexpected arrays\n return `[${(v as unknown as any[]).map((x) => formatValue(x as Value)).join(\", \")}]`;\n }\n if (v === null || v === undefined) return \"NULL\";\n if (typeof v === \"string\") return `'${v.replace(/'/g, \"''\")}'`;\n if (typeof v === \"number\") return String(v);\n if (typeof v === \"boolean\") return v ? \"true\" : \"false\";\n if (v instanceof Date)\n return `'${v.toISOString().replace(\"T\", \" \").slice(0, 19)}'`;\n try {\n return JSON.stringify(v as unknown as any);\n } catch {\n return String(v);\n }\n };\n\n let out = sql.strings[0] ?? \"\";\n for (let i = 0; i < sql.values.length; i++) {\n const val = getValueFromParameter(sql.values[i] as any);\n out += formatValue(val as Value);\n out += sql.strings[i + 1] ?? \"\";\n }\n return out.replace(/\\s+/g, \" \").trim();\n } catch (error) {\n console.log(`toQueryPreview error: ${error}`);\n return \"/* query preview unavailable */\";\n }\n};\n\nexport const getValueFromParameter = (value: any) => {\n if (Array.isArray(value)) {\n const [type, val] = value;\n if (type === \"Identifier\") return val;\n }\n return value;\n};\nexport function createClickhouseParameter(\n parameterIndex: number,\n value: Value,\n) {\n // ClickHouse use {name:type} be a placeholder, so if we only use number string as name e.g: {1:Unit8}\n // it will face issue when converting to the query params => {1: value1}, because the key is value not string type, so here add prefix \"p\" to avoid this issue.\n return `{p${parameterIndex}:${mapToClickHouseType(value)}}`;\n}\n\n/**\n * Convert the JS type (source is JSON format by API query parameter) to the corresponding ClickHouse type for generating named placeholder of parameterized query.\n * Only support to convert number to Int or Float, boolean to Bool, string to String, other types will convert to String.\n * If exist complex type e.g: object, Array, null, undefined, Date, Record.. etc, just convert to string type by ClickHouse function in SQL.\n * ClickHouse support converting string to other types function.\n * Please see Each section of the https://clickhouse.com/docs/en/sql-reference/functions and https://clickhouse.com/docs/en/sql-reference/functions/type-conversion-functions\n * @param value\n * @returns 'Float', 'Int', 'Bool', 'String'\n */\nexport const mapToClickHouseType = (value: Value) => {\n if (typeof value === \"number\") {\n // infer the float or int according to exist remainder or not\n return Number.isInteger(value) ? \"Int\" : \"Float\";\n }\n // When define column type or query result with parameterized query, The Bool or Boolean type both supported.\n // But the column type of query result only return Bool, so we only support Bool type for safety.\n if (typeof value === \"boolean\") return \"Bool\";\n if (value instanceof Date) return \"DateTime\";\n if (Array.isArray(value)) {\n const [type, _] = value;\n return type;\n }\n return \"String\";\n};\nfunction emptyIfUndefined(value: string | undefined): string {\n return value === undefined ? \"\" : value;\n}\n","/** Validate that a `runs` parameter is a positive integer. */\nexport function validateRuns(runs: number): void {\n if (!Number.isInteger(runs) || runs < 1) {\n throw new Error(`runs must be a positive integer, got ${runs}`);\n }\n}\n","/**\n * ClickHouse diagnostic helpers for benchmarking and profiling queries.\n *\n * Provides utilities to:\n * - EXPLAIN a query and extract index/granule stats\n * - Profile query execution via system.query_log\n * - Benchmark a query over N runs with p50/p95 percentiles\n * - Get row count, part count, and disk size for a table\n */\n\nimport { type Sql, sql, toQueryPreview, quoteIdentifier } from \"../sqlHelpers\";\nimport { type QueryClient } from \"../consumption-apis/query-client\";\nimport { validateRuns } from \"./shared\";\n\n// ---------------------------------------------------------------------------\n// Shared types\n// ---------------------------------------------------------------------------\n\nexport interface BenchmarkResult {\n readonly profiles: readonly ProfileResult[];\n readonly p50: number;\n readonly p95: number;\n}\n\n// ---------------------------------------------------------------------------\n// Shared utilities\n// ---------------------------------------------------------------------------\n\nexport function percentile(values: number[], p: number): number {\n if (values.length === 0) return 0;\n const sorted = [...values].sort((a, b) => a - b);\n const index = Math.min(\n sorted.length - 1,\n Math.ceil((p / 100) * sorted.length) - 1,\n );\n return sorted[Math.max(0, index)];\n}\n\n// ---------------------------------------------------------------------------\n// Explain\n// ---------------------------------------------------------------------------\n\nexport interface ExplainResult {\n readonly indexCondition: string;\n readonly selectedGranules: number;\n readonly totalGranules: number;\n readonly granuleSkipPct: number;\n readonly rawPlan: string;\n}\n\n/**\n * Run EXPLAIN indexes=1 on a query and extract index condition and granule stats.\n *\n * Note: The query is rendered via `toQueryPreview` and re-wrapped with `sql.raw`.\n * This bakes parameterized values into the EXPLAIN string as literals, which\n * matches ClickHouse EXPLAIN behavior (it does not support parameterized queries).\n */\nexport async function explain(\n queryClient: QueryClient,\n query: Sql,\n): Promise<ExplainResult> {\n const sqlString = toQueryPreview(query);\n const explainSql = sql.raw(`EXPLAIN indexes = 1 ${sqlString}`);\n const result = await queryClient.execute(explainSql);\n const rows = (await result.json()) as {\n explain?: string;\n EXPLAIN?: string;\n }[];\n\n const lines = rows.map((r) => r.explain ?? r.EXPLAIN ?? \"\");\n const full = lines.join(\"\\n\");\n\n const conditionMatch = full.match(/Condition:\\s*(.+)/);\n const granulesMatch = full.match(/Granules:\\s*(\\d+)\\/(\\d+)/);\n\n const selected = granulesMatch ? parseInt(granulesMatch[1], 10) : 0;\n const total = granulesMatch ? parseInt(granulesMatch[2], 10) : 0;\n const skipPct = total > 0 ? ((total - selected) / total) * 100 : 0;\n\n return {\n indexCondition: conditionMatch?.[1]?.trim() ?? \"unknown\",\n selectedGranules: selected,\n totalGranules: total,\n granuleSkipPct: Math.round(skipPct),\n rawPlan: full,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Profile (batched)\n// ---------------------------------------------------------------------------\n\nexport interface ProfileResult {\n readonly queryId: string;\n readonly readRows: number;\n readonly readBytes: number;\n readonly memoryUsage: number;\n readonly durationMs: number;\n readonly resultRows: number;\n readonly diskReadUs: number;\n readonly osReadBytes: number;\n}\n\n/**\n * Execute a query and return its query_id for later profile resolution.\n * Does NOT flush logs or read system.query_log — call resolveProfiles() after all runs.\n */\nexport async function profileQuery(\n queryClient: QueryClient,\n query: Sql,\n): Promise<string> {\n const result = await queryClient.execute(query);\n await result.json();\n return result.query_id;\n}\n\n/**\n * Flush logs once and batch-resolve all query_ids from system.query_log.\n * Call this after all profileQuery() calls are complete.\n *\n * Query IDs are parameterized via the `sql` template tag to prevent injection.\n */\nexport async function resolveProfiles(\n queryClient: QueryClient,\n queryIds: string[],\n): Promise<ProfileResult[]> {\n if (queryIds.length === 0) return [];\n\n await queryClient.command(sql.raw(\"SYSTEM FLUSH LOGS\"));\n\n const idFragments = queryIds.map((id) => sql`${id}`);\n const logQuery = sql`SELECT\n query_id, read_rows, read_bytes, memory_usage, query_duration_ms, result_rows,\n ProfileEvents['DiskReadElapsedMicroseconds'] as disk_read_us,\n ProfileEvents['OSReadChars'] as os_read_bytes\n FROM system.query_log\n WHERE query_id IN (${sql.join(idFragments)}) AND type = 'QueryFinish'\n ORDER BY event_time ASC`;\n\n const logResult = await queryClient.execute(logQuery);\n const logRows = (await logResult.json()) as {\n query_id: string;\n read_rows?: string;\n read_bytes?: string;\n memory_usage?: string;\n query_duration_ms?: string;\n result_rows?: string;\n disk_read_us?: string;\n os_read_bytes?: string;\n }[];\n\n const byId = new Map(logRows.map((r) => [r.query_id, r]));\n\n return queryIds.map((id) => {\n const entry = byId.get(id);\n if (!entry) {\n throw new Error(\n `No query_log entry for query_id=${id}. Check log_queries setting.`,\n );\n }\n return {\n queryId: id,\n readRows: Number(entry.read_rows ?? 0),\n readBytes: Number(entry.read_bytes ?? 0),\n memoryUsage: Number(entry.memory_usage ?? 0),\n durationMs: Number(entry.query_duration_ms ?? 0),\n resultRows: Number(entry.result_rows ?? 0),\n diskReadUs: Number(entry.disk_read_us ?? 0),\n osReadBytes: Number(entry.os_read_bytes ?? 0),\n };\n });\n}\n\n/**\n * Run a query N times, then batch-resolve all profiles in one flush + one lookup.\n * Returns profiles with p50/p95 of server-side duration.\n */\nexport async function profileBenchmark(\n queryClient: QueryClient,\n query: Sql,\n runs: number,\n): Promise<BenchmarkResult> {\n validateRuns(runs);\n\n const queryIds: string[] = [];\n for (let i = 0; i < runs; i++) {\n queryIds.push(await profileQuery(queryClient, query));\n }\n\n const profiles = await resolveProfiles(queryClient, queryIds);\n const durations = profiles.map((p) => p.durationMs);\n\n return {\n profiles,\n p50: percentile(durations, 50),\n p95: percentile(durations, 95),\n };\n}\n\n// ---------------------------------------------------------------------------\n// Table stats\n// ---------------------------------------------------------------------------\n\n// Matches \"table\" or \"database.table\" — no trailing/leading/consecutive dots\nconst TABLE_NAME_RE = /^[a-zA-Z_][a-zA-Z0-9_]*(?:\\.[a-zA-Z_][a-zA-Z0-9_]*)?$/;\n\nexport interface TableStats {\n readonly table: string;\n readonly rows: number;\n readonly parts: number;\n readonly diskSize: string;\n}\n\nexport async function tableStats(\n queryClient: QueryClient,\n table: string,\n): Promise<TableStats> {\n if (!TABLE_NAME_RE.test(table)) {\n throw new Error(`Invalid table name: ${table}`);\n }\n\n // Split on dot to handle database.table — quote each part separately\n const quoted = table\n .split(\".\")\n .map((part) => quoteIdentifier(part))\n .join(\".\");\n // Extract database and bare table name for system.parts lookup\n const parts = table.split(\".\");\n const bareTable = parts.length > 1 ? parts[parts.length - 1] : table;\n const bareDatabase = parts.length > 1 ? parts[0] : null;\n\n const [countRows, partsRows] = await Promise.all([\n queryClient\n .execute(sql.raw(`SELECT count() as rows FROM ${quoted}`))\n .then((r) => r.json() as Promise<{ rows: string }[]>),\n queryClient\n .execute(\n bareDatabase ?\n sql`SELECT count() as parts, formatReadableSize(sum(bytes_on_disk)) as disk_size\n FROM system.parts\n WHERE active = 1 AND table = ${bareTable} AND database = ${bareDatabase}`\n : sql`SELECT count() as parts, formatReadableSize(sum(bytes_on_disk)) as disk_size\n FROM system.parts\n WHERE active = 1 AND table = ${bareTable}`,\n )\n .then((r) => r.json() as Promise<{ parts: string; disk_size: string }[]>),\n ]);\n\n return {\n table,\n rows: Number(countRows[0]?.rows ?? 0),\n parts: Number(partsRows[0]?.parts ?? 0),\n diskSize: partsRows[0]?.disk_size ?? \"unknown\",\n };\n}\n","/**\n * Test report accumulator — collects results from test cases\n * and writes a timestamped JSON detail file.\n */\n\nimport { writeFile, mkdir } from \"node:fs/promises\";\nimport { join } from \"node:path\";\n\nexport interface TestReportTarget {\n readonly host: string;\n readonly database: string;\n}\n\nexport interface TestReport {\n readonly timestamp: string;\n readonly target: TestReportTarget;\n tests: Record<string, unknown>;\n}\n\nexport interface TestReporterOptions {\n /** Prefix for the output filename (e.g. \"benchmark-details\") */\n prefix: string;\n /** Directory to write report files into */\n outputDir: string;\n /** ClickHouse target info. If omitted, reads from MOOSE_CLICKHOUSE_CONFIG__* env vars. */\n target?: TestReportTarget;\n}\n\nexport function createTestReporter(options: TestReporterOptions): {\n results: TestReport;\n flush: () => Promise<string>;\n} {\n const target = options.target ?? {\n host: process.env.MOOSE_CLICKHOUSE_CONFIG__HOST ?? \"localhost\",\n database: process.env.MOOSE_CLICKHOUSE_CONFIG__DB_NAME ?? \"local\",\n };\n\n const results: TestReport = {\n timestamp: new Date().toISOString(),\n target,\n tests: {},\n };\n\n const flush = async (): Promise<string> => {\n await mkdir(options.outputDir, { recursive: true });\n const filename = `${options.prefix}-${new Date().toISOString().replace(/[:.]/g, \"-\")}.json`;\n const filepath = join(options.outputDir, filename);\n await writeFile(filepath, JSON.stringify(results, null, 2));\n return filepath;\n };\n\n return { results, flush };\n}\n"],"mappings":"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","names":["sql"]}
1
+ {"version":3,"sources":["../../src/sqlHelpers.ts","../../src/testing/shared.ts","../../src/testing/clickhouse-diagnostics.ts","../../src/testing/test-reporter.ts"],"sourcesContent":["// source https://github.com/blakeembrey/sql-template-tag/blob/main/src/index.ts\nimport { Column } from \"./dataModels/dataModelTypes\";\nimport { OlapTable, View } from \"./dmv2\";\nimport { OlapDictionary } from \"./dmv2/sdk/olapDictionary\";\n\nimport { AggregationFunction } from \"./dataModels/typeConvert\";\n\n/**\n * Quote a ClickHouse identifier with backticks if not already quoted.\n * Backticks allow special characters (e.g., hyphens) in identifiers.\n */\nexport const quoteIdentifier = (name: string): string => {\n return name.startsWith(\"`\") && name.endsWith(\"`\") ? name : `\\`${name}\\``;\n};\n\nconst isTable = (\n value: RawValue | Column | OlapTable<any> | View | OlapDictionary<any>,\n): value is OlapTable<any> =>\n typeof value === \"object\" &&\n value !== null &&\n \"kind\" in value &&\n value.kind === \"OlapTable\";\n\nconst isView = (\n value: RawValue | Column | OlapTable<any> | View | OlapDictionary<any>,\n): value is View =>\n typeof value === \"object\" &&\n value !== null &&\n \"kind\" in value &&\n value.kind === \"View\";\n\nconst isDictionary = (\n value: RawValue | Column | OlapTable<any> | View | OlapDictionary<any>,\n): value is OlapDictionary<any> =>\n typeof value === \"object\" &&\n value !== null &&\n \"kind\" in value &&\n (value as any).kind === \"OlapDictionary\";\n\nexport type IdentifierBrandedString = string & {\n readonly __identifier_brand?: unique symbol;\n};\nexport type NonIdentifierBrandedString = string & {\n readonly __identifier_brand?: unique symbol;\n};\n\n/**\n * Values supported by SQL engine.\n */\nexport type Value =\n | NonIdentifierBrandedString\n | number\n | boolean\n | Date\n | [string, string];\n\n/**\n * Supported value or SQL instance.\n */\nexport type RawValue = Value | Sql;\n\nconst isColumn = (\n value: RawValue | Column | OlapTable<any> | View | OlapDictionary<any>,\n): value is Column =>\n typeof value === \"object\" &&\n value !== null &&\n !(\"kind\" in value) &&\n \"name\" in value &&\n \"annotations\" in value;\n\n/**\n * Sql template tag interface with attached helper methods.\n */\nexport interface SqlTemplateTag {\n /**\n * @deprecated Use `sql.statement` for full SQL statements or `sql.fragment` for SQL fragments.\n */\n (\n strings: readonly string[],\n ...values: readonly (\n | RawValue\n | Column\n | OlapTable<any>\n | View\n | OlapDictionary<any>\n )[]\n ): Sql;\n\n /**\n * Template literal tag for complete SQL statements (e.g. SELECT, INSERT, CREATE).\n * Produces a Sql instance with `isFragment = false`.\n */\n statement(\n strings: readonly string[],\n ...values: readonly (\n | RawValue\n | Column\n | OlapTable<any>\n | View\n | OlapDictionary<any>\n )[]\n ): Sql;\n\n /**\n * Template literal tag for SQL fragments (e.g. expressions, conditions, partial clauses).\n * Produces a Sql instance with `isFragment = true`.\n */\n fragment(\n strings: readonly string[],\n ...values: readonly (\n | RawValue\n | Column\n | OlapTable<any>\n | View\n | OlapDictionary<any>\n )[]\n ): Sql;\n\n /**\n * Join an array of Sql fragments with a separator.\n * @param fragments - Array of Sql fragments to join\n * @param separator - Optional separator string (defaults to \", \")\n */\n join(fragments: Sql[], separator?: string): Sql;\n\n /**\n * Create raw SQL from a string without parameterization.\n * WARNING: SQL injection risk if used with untrusted input.\n */\n raw(text: string): Sql;\n}\n\nfunction sqlImpl(\n strings: readonly string[],\n ...values: readonly (\n | RawValue\n | Column\n | OlapTable<any>\n | View\n | OlapDictionary<any>\n )[]\n): Sql {\n return new Sql(strings, values);\n}\n\nexport const sql: SqlTemplateTag = sqlImpl as SqlTemplateTag;\n\nsql.statement = function (\n strings: readonly string[],\n ...values: readonly (\n | RawValue\n | Column\n | OlapTable<any>\n | View\n | OlapDictionary<any>\n )[]\n): Sql {\n return new Sql(strings, values, false);\n};\n\nsql.fragment = function (\n strings: readonly string[],\n ...values: readonly (\n | RawValue\n | Column\n | OlapTable<any>\n | View\n | OlapDictionary<any>\n )[]\n): Sql {\n return new Sql(strings, values, true);\n};\n\nconst instanceofSql = (\n value: RawValue | Column | OlapTable<any> | View | OlapDictionary<any>,\n): value is Sql =>\n typeof value === \"object\" && \"values\" in value && \"strings\" in value;\n\n/**\n * A SQL instance can be nested within each other to build SQL strings.\n */\nexport class Sql {\n readonly values: Value[];\n readonly strings: string[];\n readonly isFragment: boolean | undefined;\n\n constructor(\n rawStrings: readonly string[],\n rawValues: readonly (\n | RawValue\n | Column\n | OlapTable<any>\n | View\n | OlapDictionary<any>\n | Sql\n )[],\n isFragment?: boolean,\n ) {\n if (rawStrings.length - 1 !== rawValues.length) {\n if (rawStrings.length === 0) {\n throw new TypeError(\"Expected at least 1 string\");\n }\n\n throw new TypeError(\n `Expected ${rawStrings.length} strings to have ${\n rawStrings.length - 1\n } values`,\n );\n }\n\n const valuesLength = rawValues.reduce<number>(\n (\n len: number,\n value:\n | RawValue\n | Column\n | OlapTable<any>\n | View\n | OlapDictionary<any>\n | Sql,\n ) =>\n len +\n (instanceofSql(value) ? value.values.length\n : (\n isColumn(value) ||\n isTable(value) ||\n isView(value) ||\n isDictionary(value)\n ) ?\n 0\n : 1),\n 0,\n );\n\n this.values = new Array(valuesLength);\n this.strings = new Array(valuesLength + 1);\n this.isFragment = isFragment;\n\n this.strings[0] = rawStrings[0];\n\n // Iterate over raw values, strings, and children. The value is always\n // positioned between two strings, e.g. `index + 1`.\n let i = 0,\n pos = 0;\n while (i < rawValues.length) {\n const child = rawValues[i++];\n const rawString = rawStrings[i];\n\n // Check for nested `sql` queries.\n if (instanceofSql(child)) {\n // Append child prefix text to current string.\n this.strings[pos] += child.strings[0];\n\n let childIndex = 0;\n while (childIndex < child.values.length) {\n this.values[pos++] = child.values[childIndex++];\n this.strings[pos] = child.strings[childIndex];\n }\n\n // Append raw string to current string.\n this.strings[pos] += rawString;\n } else if (isColumn(child)) {\n const aggregationFunction = child.annotations.find(\n ([k, _]) => k === \"aggregationFunction\",\n );\n if (aggregationFunction !== undefined) {\n const funcName = (aggregationFunction[1] as AggregationFunction)\n .functionName;\n const parenIdx = funcName.indexOf(\"(\");\n const mergedName =\n parenIdx !== -1 ?\n `${funcName.slice(0, parenIdx)}Merge${funcName.slice(parenIdx)}`\n : `${funcName}Merge`;\n this.strings[pos] += `${mergedName}(\\`${child.name}\\`)`;\n } else {\n this.strings[pos] += `\\`${child.name}\\``;\n }\n this.strings[pos] += rawString;\n } else if (isTable(child)) {\n const deployedName = child.generateTableName();\n if (child.config.database) {\n this.strings[pos] +=\n `\\`${child.config.database}\\`.\\`${deployedName}\\``;\n } else {\n this.strings[pos] += `\\`${deployedName}\\``;\n }\n this.strings[pos] += rawString;\n } else if (isView(child)) {\n if (child.database) {\n this.strings[pos] += `\\`${child.database}\\`.\\`${child.name}\\``;\n } else {\n this.strings[pos] += `\\`${child.name}\\``;\n }\n this.strings[pos] += rawString;\n } else if (isDictionary(child)) {\n // Dictionaries render as single-quoted string literals for use with dictGet():\n // sql`dictGet(${ProductDict}, 'attr', id)` → dictGet('db.dict_name', 'attr', id)\n // Note: dictionaries are NOT queryable via FROM — ClickHouse will reject such queries.\n if (/\\b(?:FROM|JOIN)\\s*$/i.test(this.strings[pos])) {\n console.warn(\n `OlapDictionary '${child.getQualifiedName()}' interpolated after FROM/JOIN in sql tag. ` +\n `Dictionaries render as string literals (e.g. 'db.dict_name') for use with dictGet(), ` +\n `not as table identifiers. ClickHouse dictionaries cannot be queried directly with FROM/JOIN.`,\n );\n }\n this.strings[pos] +=\n `'${child.getQualifiedName().replace(/'/g, \"''\")}'`;\n this.strings[pos] += rawString;\n } else {\n this.values[pos++] = child;\n this.strings[pos] = rawString;\n }\n }\n }\n\n /**\n * Append another Sql fragment, returning a new Sql instance.\n */\n append(other: Sql): Sql {\n return new Sql(\n [...this.strings, \"\"],\n [...this.values, other],\n this.isFragment,\n );\n }\n}\n\nsql.join = function (fragments: Sql[], separator?: string): Sql {\n if (fragments.length === 0) return new Sql([\"\"], [], true);\n if (fragments.length === 1) {\n const frag = fragments[0];\n return new Sql(frag.strings, frag.values, true);\n }\n const sep = separator ?? \", \";\n const normalized = sep.includes(\" \") ? sep : ` ${sep} `;\n const strings = [\"\", ...Array(fragments.length - 1).fill(normalized), \"\"];\n return new Sql(strings, fragments, true);\n};\n\nsql.raw = function (text: string): Sql {\n return new Sql([text], [], true);\n};\n\nexport const toStaticQuery = (sql: Sql): string => {\n const [query, params] = toQuery(sql);\n if (Object.keys(params).length !== 0) {\n throw new Error(\n \"Dynamic SQL is not allowed in the select statement in view creation.\",\n );\n }\n return query;\n};\n\nexport const toQuery = (sql: Sql): [string, { [pN: string]: any }] => {\n const parameterizedStubs = sql.values.map((v, i) =>\n createClickhouseParameter(i, v),\n );\n\n const query = sql.strings\n .map((s, i) =>\n s != \"\" ? `${s}${emptyIfUndefined(parameterizedStubs[i])}` : \"\",\n )\n .join(\"\");\n\n const query_params = sql.values.reduce(\n (acc: Record<string, unknown>, v, i) => ({\n ...acc,\n [`p${i}`]: getValueFromParameter(v),\n }),\n {},\n );\n return [query, query_params];\n};\n\n/**\n * Build a display-only SQL string with values inlined for logging/debugging.\n * Does not alter execution behavior; use toQuery for actual execution.\n */\nexport const toQueryPreview = (sql: Sql): string => {\n try {\n const formatValue = (v: Value): string => {\n // Unwrap identifiers: [\"Identifier\", name]\n if (Array.isArray(v)) {\n const [type, val] = v as unknown as [string, any];\n if (type === \"Identifier\") {\n // Quote identifiers with backticks like other helpers\n return `\\`${String(val)}\\``;\n }\n // Fallback for unexpected arrays\n return `[${(v as unknown as any[]).map((x) => formatValue(x as Value)).join(\", \")}]`;\n }\n if (v === null || v === undefined) return \"NULL\";\n if (typeof v === \"string\") return `'${v.replace(/'/g, \"''\")}'`;\n if (typeof v === \"number\") return String(v);\n if (typeof v === \"boolean\") return v ? \"true\" : \"false\";\n if (v instanceof Date)\n return `'${v.toISOString().replace(\"T\", \" \").slice(0, 19)}'`;\n try {\n return JSON.stringify(v as unknown as any);\n } catch {\n return String(v);\n }\n };\n\n let out = sql.strings[0] ?? \"\";\n for (let i = 0; i < sql.values.length; i++) {\n const val = getValueFromParameter(sql.values[i] as any);\n out += formatValue(val as Value);\n out += sql.strings[i + 1] ?? \"\";\n }\n return out.replace(/\\s+/g, \" \").trim();\n } catch (error) {\n console.log(`toQueryPreview error: ${error}`);\n return \"/* query preview unavailable */\";\n }\n};\n\nexport const getValueFromParameter = (value: any) => {\n if (Array.isArray(value)) {\n const [type, val] = value;\n if (type === \"Identifier\") return val;\n }\n return value;\n};\nexport function createClickhouseParameter(\n parameterIndex: number,\n value: Value,\n) {\n // ClickHouse use {name:type} be a placeholder, so if we only use number string as name e.g: {1:Unit8}\n // it will face issue when converting to the query params => {1: value1}, because the key is value not string type, so here add prefix \"p\" to avoid this issue.\n return `{p${parameterIndex}:${mapToClickHouseType(value)}}`;\n}\n\n/**\n * Convert the JS type (source is JSON format by API query parameter) to the corresponding ClickHouse type for generating named placeholder of parameterized query.\n * Only support to convert number to Int or Float, boolean to Bool, string to String, other types will convert to String.\n * If exist complex type e.g: object, Array, null, undefined, Date, Record.. etc, just convert to string type by ClickHouse function in SQL.\n * ClickHouse support converting string to other types function.\n * Please see Each section of the https://clickhouse.com/docs/en/sql-reference/functions and https://clickhouse.com/docs/en/sql-reference/functions/type-conversion-functions\n * @param value\n * @returns 'Float', 'Int', 'Bool', 'String'\n */\nexport const mapToClickHouseType = (value: Value) => {\n if (typeof value === \"number\") {\n // infer the float or int according to exist remainder or not\n return Number.isInteger(value) ? \"Int\" : \"Float\";\n }\n // When define column type or query result with parameterized query, The Bool or Boolean type both supported.\n // But the column type of query result only return Bool, so we only support Bool type for safety.\n if (typeof value === \"boolean\") return \"Bool\";\n if (value instanceof Date) return \"DateTime\";\n if (Array.isArray(value)) {\n const [type, _] = value;\n return type;\n }\n return \"String\";\n};\nfunction emptyIfUndefined(value: string | undefined): string {\n return value === undefined ? \"\" : value;\n}\n","/** Validate that a `runs` parameter is a positive integer. */\nexport function validateRuns(runs: number): void {\n if (!Number.isInteger(runs) || runs < 1) {\n throw new Error(`runs must be a positive integer, got ${runs}`);\n }\n}\n","/**\n * ClickHouse diagnostic helpers for benchmarking and profiling queries.\n *\n * Provides utilities to:\n * - EXPLAIN a query and extract index/granule stats\n * - Profile query execution via system.query_log\n * - Benchmark a query over N runs with p50/p95 percentiles\n * - Get row count, part count, and disk size for a table\n */\n\nimport { type Sql, sql, toQueryPreview, quoteIdentifier } from \"../sqlHelpers\";\nimport { type QueryClient } from \"../consumption-apis/query-client\";\nimport { validateRuns } from \"./shared\";\n\n// ---------------------------------------------------------------------------\n// Shared types\n// ---------------------------------------------------------------------------\n\nexport interface BenchmarkResult {\n readonly profiles: readonly ProfileResult[];\n readonly p50: number;\n readonly p95: number;\n}\n\n// ---------------------------------------------------------------------------\n// Shared utilities\n// ---------------------------------------------------------------------------\n\nexport function percentile(values: number[], p: number): number {\n if (values.length === 0) return 0;\n const sorted = [...values].sort((a, b) => a - b);\n const index = Math.min(\n sorted.length - 1,\n Math.ceil((p / 100) * sorted.length) - 1,\n );\n return sorted[Math.max(0, index)];\n}\n\n// ---------------------------------------------------------------------------\n// Explain\n// ---------------------------------------------------------------------------\n\nexport interface ExplainResult {\n readonly indexCondition: string;\n readonly selectedGranules: number;\n readonly totalGranules: number;\n readonly granuleSkipPct: number;\n readonly rawPlan: string;\n}\n\n/**\n * Run EXPLAIN indexes=1 on a query and extract index condition and granule stats.\n *\n * Note: The query is rendered via `toQueryPreview` and re-wrapped with `sql.raw`.\n * This bakes parameterized values into the EXPLAIN string as literals, which\n * matches ClickHouse EXPLAIN behavior (it does not support parameterized queries).\n */\nexport async function explain(\n queryClient: QueryClient,\n query: Sql,\n): Promise<ExplainResult> {\n const sqlString = toQueryPreview(query);\n const explainSql = sql.raw(`EXPLAIN indexes = 1 ${sqlString}`);\n const result = await queryClient.execute(explainSql);\n const rows = (await result.json()) as {\n explain?: string;\n EXPLAIN?: string;\n }[];\n\n const lines = rows.map((r) => r.explain ?? r.EXPLAIN ?? \"\");\n const full = lines.join(\"\\n\");\n\n const conditionMatch = full.match(/Condition:\\s*(.+)/);\n const granulesMatch = full.match(/Granules:\\s*(\\d+)\\/(\\d+)/);\n\n const selected = granulesMatch ? parseInt(granulesMatch[1], 10) : 0;\n const total = granulesMatch ? parseInt(granulesMatch[2], 10) : 0;\n const skipPct = total > 0 ? ((total - selected) / total) * 100 : 0;\n\n return {\n indexCondition: conditionMatch?.[1]?.trim() ?? \"unknown\",\n selectedGranules: selected,\n totalGranules: total,\n granuleSkipPct: Math.round(skipPct),\n rawPlan: full,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Profile (batched)\n// ---------------------------------------------------------------------------\n\nexport interface ProfileResult {\n readonly queryId: string;\n readonly readRows: number;\n readonly readBytes: number;\n readonly memoryUsage: number;\n readonly durationMs: number;\n readonly resultRows: number;\n readonly diskReadUs: number;\n readonly osReadBytes: number;\n}\n\n/**\n * Execute a query and return its query_id for later profile resolution.\n * Does NOT flush logs or read system.query_log — call resolveProfiles() after all runs.\n */\nexport async function profileQuery(\n queryClient: QueryClient,\n query: Sql,\n): Promise<string> {\n const result = await queryClient.execute(query);\n await result.json();\n return result.query_id;\n}\n\n/**\n * Flush logs once and batch-resolve all query_ids from system.query_log.\n * Call this after all profileQuery() calls are complete.\n *\n * Query IDs are parameterized via the `sql` template tag to prevent injection.\n */\nexport async function resolveProfiles(\n queryClient: QueryClient,\n queryIds: string[],\n): Promise<ProfileResult[]> {\n if (queryIds.length === 0) return [];\n\n await queryClient.command(sql.raw(\"SYSTEM FLUSH LOGS\"));\n\n const idFragments = queryIds.map((id) => sql`${id}`);\n const logQuery = sql`SELECT\n query_id, read_rows, read_bytes, memory_usage, query_duration_ms, result_rows,\n ProfileEvents['DiskReadElapsedMicroseconds'] as disk_read_us,\n ProfileEvents['OSReadChars'] as os_read_bytes\n FROM system.query_log\n WHERE query_id IN (${sql.join(idFragments)}) AND type = 'QueryFinish'\n ORDER BY event_time ASC`;\n\n const logResult = await queryClient.execute(logQuery);\n const logRows = (await logResult.json()) as {\n query_id: string;\n read_rows?: string;\n read_bytes?: string;\n memory_usage?: string;\n query_duration_ms?: string;\n result_rows?: string;\n disk_read_us?: string;\n os_read_bytes?: string;\n }[];\n\n const byId = new Map(logRows.map((r) => [r.query_id, r]));\n\n return queryIds.map((id) => {\n const entry = byId.get(id);\n if (!entry) {\n throw new Error(\n `No query_log entry for query_id=${id}. Check log_queries setting.`,\n );\n }\n return {\n queryId: id,\n readRows: Number(entry.read_rows ?? 0),\n readBytes: Number(entry.read_bytes ?? 0),\n memoryUsage: Number(entry.memory_usage ?? 0),\n durationMs: Number(entry.query_duration_ms ?? 0),\n resultRows: Number(entry.result_rows ?? 0),\n diskReadUs: Number(entry.disk_read_us ?? 0),\n osReadBytes: Number(entry.os_read_bytes ?? 0),\n };\n });\n}\n\n/**\n * Run a query N times, then batch-resolve all profiles in one flush + one lookup.\n * Returns profiles with p50/p95 of server-side duration.\n */\nexport async function profileBenchmark(\n queryClient: QueryClient,\n query: Sql,\n runs: number,\n): Promise<BenchmarkResult> {\n validateRuns(runs);\n\n const queryIds: string[] = [];\n for (let i = 0; i < runs; i++) {\n queryIds.push(await profileQuery(queryClient, query));\n }\n\n const profiles = await resolveProfiles(queryClient, queryIds);\n const durations = profiles.map((p) => p.durationMs);\n\n return {\n profiles,\n p50: percentile(durations, 50),\n p95: percentile(durations, 95),\n };\n}\n\n// ---------------------------------------------------------------------------\n// Table stats\n// ---------------------------------------------------------------------------\n\n// Matches \"table\" or \"database.table\" — no trailing/leading/consecutive dots\nconst TABLE_NAME_RE = /^[a-zA-Z_][a-zA-Z0-9_]*(?:\\.[a-zA-Z_][a-zA-Z0-9_]*)?$/;\n\nexport interface TableStats {\n readonly table: string;\n readonly rows: number;\n readonly parts: number;\n readonly diskSize: string;\n}\n\nexport async function tableStats(\n queryClient: QueryClient,\n table: string,\n): Promise<TableStats> {\n if (!TABLE_NAME_RE.test(table)) {\n throw new Error(`Invalid table name: ${table}`);\n }\n\n // Split on dot to handle database.table — quote each part separately\n const quoted = table\n .split(\".\")\n .map((part) => quoteIdentifier(part))\n .join(\".\");\n // Extract database and bare table name for system.parts lookup\n const parts = table.split(\".\");\n const bareTable = parts.length > 1 ? parts[parts.length - 1] : table;\n const bareDatabase = parts.length > 1 ? parts[0] : null;\n\n const [countRows, partsRows] = await Promise.all([\n queryClient\n .execute(sql.raw(`SELECT count() as rows FROM ${quoted}`))\n .then((r) => r.json() as Promise<{ rows: string }[]>),\n queryClient\n .execute(\n bareDatabase ?\n sql`SELECT count() as parts, formatReadableSize(sum(bytes_on_disk)) as disk_size\n FROM system.parts\n WHERE active = 1 AND table = ${bareTable} AND database = ${bareDatabase}`\n : sql`SELECT count() as parts, formatReadableSize(sum(bytes_on_disk)) as disk_size\n FROM system.parts\n WHERE active = 1 AND table = ${bareTable}`,\n )\n .then((r) => r.json() as Promise<{ parts: string; disk_size: string }[]>),\n ]);\n\n return {\n table,\n rows: Number(countRows[0]?.rows ?? 0),\n parts: Number(partsRows[0]?.parts ?? 0),\n diskSize: partsRows[0]?.disk_size ?? \"unknown\",\n };\n}\n","/**\n * Test report accumulator — collects results from test cases\n * and writes a timestamped JSON detail file.\n */\n\nimport { writeFile, mkdir } from \"node:fs/promises\";\nimport { join } from \"node:path\";\n\nexport interface TestReportTarget {\n readonly host: string;\n readonly database: string;\n}\n\nexport interface TestReport {\n readonly timestamp: string;\n readonly target: TestReportTarget;\n tests: Record<string, unknown>;\n}\n\nexport interface TestReporterOptions {\n /** Prefix for the output filename (e.g. \"benchmark-details\") */\n prefix: string;\n /** Directory to write report files into */\n outputDir: string;\n /** ClickHouse target info. If omitted, reads from MOOSE_CLICKHOUSE_CONFIG__* env vars. */\n target?: TestReportTarget;\n}\n\nexport function createTestReporter(options: TestReporterOptions): {\n results: TestReport;\n flush: () => Promise<string>;\n} {\n const target = options.target ?? {\n host: process.env.MOOSE_CLICKHOUSE_CONFIG__HOST ?? \"localhost\",\n database: process.env.MOOSE_CLICKHOUSE_CONFIG__DB_NAME ?? \"local\",\n };\n\n const results: TestReport = {\n timestamp: new Date().toISOString(),\n target,\n tests: {},\n };\n\n const flush = async (): Promise<string> => {\n await mkdir(options.outputDir, { recursive: true });\n const filename = `${options.prefix}-${new Date().toISOString().replace(/[:.]/g, \"-\")}.json`;\n const filepath = join(options.outputDir, filename);\n await writeFile(filepath, JSON.stringify(results, null, 2));\n return filepath;\n };\n\n return { results, flush };\n}\n"],"mappings":"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","names":["sql"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@514labs/moose-lib",
3
- "version": "0.6.527",
3
+ "version": "0.6.528",
4
4
  "engines": {
5
5
  "node": ">=20 <25"
6
6
  },