@514labs/moose-lib 0.6.514 → 0.6.515
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.
- package/dist/browserCompatible.d.mts +2 -2
- package/dist/browserCompatible.d.ts +2 -2
- package/dist/browserCompatible.js +71 -57
- package/dist/browserCompatible.js.map +1 -1
- package/dist/browserCompatible.mjs +71 -57
- package/dist/browserCompatible.mjs.map +1 -1
- package/dist/dmv2/index.d.mts +2 -2
- package/dist/dmv2/index.d.ts +2 -2
- package/dist/dmv2/index.js +67 -53
- package/dist/dmv2/index.js.map +1 -1
- package/dist/dmv2/index.mjs +67 -53
- package/dist/dmv2/index.mjs.map +1 -1
- package/dist/{index-BCTJXoD8.d.mts → index-7uxZbwmY.d.ts} +3 -2
- package/dist/{index-BOIsE8xN.d.ts → index-k_kpRxE3.d.mts} +3 -2
- package/dist/index.d.mts +4 -4
- package/dist/index.d.ts +4 -4
- package/dist/index.js +257 -245
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +257 -245
- package/dist/index.mjs.map +1 -1
- package/dist/moose-runner.js +30 -11
- package/dist/moose-runner.js.map +1 -1
- package/dist/moose-runner.mjs +30 -11
- package/dist/moose-runner.mjs.map +1 -1
- package/dist/testing/index.d.mts +1 -1
- package/dist/testing/index.d.ts +1 -1
- package/dist/testing/index.js +5 -1
- package/dist/testing/index.js.map +1 -1
- package/dist/testing/index.mjs +5 -1
- package/dist/testing/index.mjs.map +1 -1
- package/dist/{view-CJKyTLjp.d.mts → view-BCWJcLF6.d.mts} +23 -4
- package/dist/{view-CJKyTLjp.d.ts → view-BCWJcLF6.d.ts} +23 -4
- package/package.json +1 -1
package/dist/testing/index.d.mts
CHANGED
package/dist/testing/index.d.ts
CHANGED
package/dist/testing/index.js
CHANGED
|
@@ -103,7 +103,11 @@ var Sql = class _Sql {
|
|
|
103
103
|
}
|
|
104
104
|
this.strings[pos] += rawString;
|
|
105
105
|
} else if (isView(child)) {
|
|
106
|
-
|
|
106
|
+
if (child.database) {
|
|
107
|
+
this.strings[pos] += `\`${child.database}\`.\`${child.name}\``;
|
|
108
|
+
} else {
|
|
109
|
+
this.strings[pos] += `\`${child.name}\``;
|
|
110
|
+
}
|
|
107
111
|
this.strings[pos] += rawString;
|
|
108
112
|
} else {
|
|
109
113
|
this.values[pos++] = child;
|
|
@@ -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\";\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,\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 this.strings[pos] += `\\`${child.name}\\``;\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 { 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":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACSO,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;AAwBjB,IAAM,WAAW,CACf,UAEA,OAAO,UAAU,YACjB,UAAU,QACV,EAAE,UAAU,UACZ,UAAU,SACV,iBAAiB;AA8CnB,SAAS,QACP,YACG,QACE;AACL,SAAO,IAAI,IAAI,SAAS,MAAM;AAChC;AAEO,IAAM,MAAsB;AAEnC,IAAI,YAAY,SACd,YACG,QACE;AACL,SAAO,IAAI,IAAI,SAAS,QAAQ,KAAK;AACvC;AAEA,IAAI,WAAW,SACb,YACG,QACE;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,WACA,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,CAAC,KAAa,UACZ,OACC,cAAc,KAAK,IAAI,MAAM,OAAO,SACnC,SAAS,KAAK,KAAK,QAAQ,KAAK,KAAK,OAAO,KAAK,IAAI,IACrD;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,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;;;ACpVO,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"]}
|
package/dist/testing/index.mjs
CHANGED
|
@@ -71,7 +71,11 @@ var Sql = class _Sql {
|
|
|
71
71
|
}
|
|
72
72
|
this.strings[pos] += rawString;
|
|
73
73
|
} else if (isView(child)) {
|
|
74
|
-
|
|
74
|
+
if (child.database) {
|
|
75
|
+
this.strings[pos] += `\`${child.database}\`.\`${child.name}\``;
|
|
76
|
+
} else {
|
|
77
|
+
this.strings[pos] += `\`${child.name}\``;
|
|
78
|
+
}
|
|
75
79
|
this.strings[pos] += rawString;
|
|
76
80
|
} else {
|
|
77
81
|
this.values[pos++] = child;
|
|
@@ -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\";\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,\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 this.strings[pos] += `\\`${child.name}\\``;\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 { 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"]}
|
|
@@ -1364,6 +1364,21 @@ declare class QueryClient {
|
|
|
1364
1364
|
command(sql: Sql): Promise<CommandResult>;
|
|
1365
1365
|
}
|
|
1366
1366
|
|
|
1367
|
+
/**
|
|
1368
|
+
* Configuration options for creating a View.
|
|
1369
|
+
*/
|
|
1370
|
+
interface ViewConfig {
|
|
1371
|
+
/** The SQL SELECT statement or Sql object defining the view's logic. */
|
|
1372
|
+
selectStatement: string | Sql;
|
|
1373
|
+
/** Source tables/views the SELECT reads from. Used for dependency tracking during migrations. */
|
|
1374
|
+
baseTables: (OlapTable<any> | View)[];
|
|
1375
|
+
/** Optional database where the view is created. When set, the view is created as `database`.`name` in ClickHouse. */
|
|
1376
|
+
database?: string;
|
|
1377
|
+
/** Optional metadata for the view (e.g., description, source file). */
|
|
1378
|
+
metadata?: {
|
|
1379
|
+
[key: string]: any;
|
|
1380
|
+
};
|
|
1381
|
+
}
|
|
1367
1382
|
/**
|
|
1368
1383
|
* Represents a database View, defined by a SQL SELECT statement based on one or more base tables or other views.
|
|
1369
1384
|
* Emits structured data for the Moose infrastructure system.
|
|
@@ -1373,6 +1388,8 @@ declare class View {
|
|
|
1373
1388
|
readonly kind = "View";
|
|
1374
1389
|
/** The name of the view */
|
|
1375
1390
|
name: string;
|
|
1391
|
+
/** Optional database where the view is created. When set, the view is created as `database`.`name` in ClickHouse. */
|
|
1392
|
+
database?: string;
|
|
1376
1393
|
/** The SELECT SQL statement that defines the view */
|
|
1377
1394
|
selectSql: string;
|
|
1378
1395
|
/** Names of source tables/views that the SELECT reads from */
|
|
@@ -1384,13 +1401,15 @@ declare class View {
|
|
|
1384
1401
|
/**
|
|
1385
1402
|
* Creates a new View instance.
|
|
1386
1403
|
* @param name The name of the view to be created.
|
|
1387
|
-
* @param
|
|
1388
|
-
|
|
1389
|
-
|
|
1404
|
+
* @param config Configuration for the view: select statement, base tables, optional database, and optional metadata.
|
|
1405
|
+
*/
|
|
1406
|
+
constructor(name: string, config: ViewConfig);
|
|
1407
|
+
/**
|
|
1408
|
+
* @deprecated Use the config-object overload: `new View(name, { selectStatement, baseTables, metadata? })`.
|
|
1390
1409
|
*/
|
|
1391
1410
|
constructor(name: string, selectStatement: string | Sql, baseTables: (OlapTable<any> | View)[], metadata?: {
|
|
1392
1411
|
[key: string]: any;
|
|
1393
1412
|
});
|
|
1394
1413
|
}
|
|
1395
1414
|
|
|
1396
|
-
export { QueryClient as $, type Decimal as A, type Insertable as B, ClickHouseEngines as C, type DateTime as D, quoteIdentifier as E, type FixedString as F, type IdentifierBrandedString as G, type Value as H, type Int8 as I, type SqlTemplateTag as J, sql as K, LifeCycle as L, Sql as M, type NonIdentifierBrandedString as N, OlapTable as O, toStaticQuery as P, toQuery as Q, type RawValue as R, type S3QueueTableSettings as S, type TableConstraint as T, type UInt8 as U, View as V, type WithDefault as W, toQueryPreview as X, getValueFromParameter as Y, createClickhouseParameter as Z, mapToClickHouseType as _, type OlapConfig as a, TypedBase as a0, type Column as a1, type RowPolicyOptions as a2, type TypiaValidators as a3, type
|
|
1415
|
+
export { QueryClient as $, type Decimal as A, type Insertable as B, ClickHouseEngines as C, type DateTime as D, quoteIdentifier as E, type FixedString as F, type IdentifierBrandedString as G, type Value as H, type Int8 as I, type SqlTemplateTag as J, sql as K, LifeCycle as L, Sql as M, type NonIdentifierBrandedString as N, OlapTable as O, toStaticQuery as P, toQuery as Q, type RawValue as R, type S3QueueTableSettings as S, type TableConstraint as T, type UInt8 as U, View as V, type WithDefault as W, toQueryPreview as X, getValueFromParameter as Y, createClickhouseParameter as Z, mapToClickHouseType as _, type OlapConfig as a, TypedBase as a0, type Column as a1, type RowPolicyOptions as a2, type TypiaValidators as a3, type ViewConfig as a4, type DataType as a5, type ClickHousePoint as a6, type ClickHouseRing as a7, type ClickHouseLineString as a8, type ClickHouseMultiLineString as a9, type ClickHousePolygon as aa, type ClickHouseMultiPolygon as ab, type ClickHousePrecision as b, type ClickHouseDecimal as c, type ClickHouseByteSize as d, type ClickHouseFixedStringSize as e, type ClickHouseFloat as f, type ClickHouseInt as g, type ClickHouseJson as h, type LowCardinality as i, type ClickHouseNamedTuple as j, type ClickHouseDefault as k, type ClickHouseTTL as l, type ClickHouseMaterialized as m, type ClickHouseAlias as n, type ClickHouseCodec as o, type DateTime64 as p, type DateTimeString as q, type DateTime64String as r, type Float32 as s, type Float64 as t, type Int16 as u, type Int32 as v, type Int64 as w, type UInt16 as x, type UInt32 as y, type UInt64 as z };
|
|
@@ -1364,6 +1364,21 @@ declare class QueryClient {
|
|
|
1364
1364
|
command(sql: Sql): Promise<CommandResult>;
|
|
1365
1365
|
}
|
|
1366
1366
|
|
|
1367
|
+
/**
|
|
1368
|
+
* Configuration options for creating a View.
|
|
1369
|
+
*/
|
|
1370
|
+
interface ViewConfig {
|
|
1371
|
+
/** The SQL SELECT statement or Sql object defining the view's logic. */
|
|
1372
|
+
selectStatement: string | Sql;
|
|
1373
|
+
/** Source tables/views the SELECT reads from. Used for dependency tracking during migrations. */
|
|
1374
|
+
baseTables: (OlapTable<any> | View)[];
|
|
1375
|
+
/** Optional database where the view is created. When set, the view is created as `database`.`name` in ClickHouse. */
|
|
1376
|
+
database?: string;
|
|
1377
|
+
/** Optional metadata for the view (e.g., description, source file). */
|
|
1378
|
+
metadata?: {
|
|
1379
|
+
[key: string]: any;
|
|
1380
|
+
};
|
|
1381
|
+
}
|
|
1367
1382
|
/**
|
|
1368
1383
|
* Represents a database View, defined by a SQL SELECT statement based on one or more base tables or other views.
|
|
1369
1384
|
* Emits structured data for the Moose infrastructure system.
|
|
@@ -1373,6 +1388,8 @@ declare class View {
|
|
|
1373
1388
|
readonly kind = "View";
|
|
1374
1389
|
/** The name of the view */
|
|
1375
1390
|
name: string;
|
|
1391
|
+
/** Optional database where the view is created. When set, the view is created as `database`.`name` in ClickHouse. */
|
|
1392
|
+
database?: string;
|
|
1376
1393
|
/** The SELECT SQL statement that defines the view */
|
|
1377
1394
|
selectSql: string;
|
|
1378
1395
|
/** Names of source tables/views that the SELECT reads from */
|
|
@@ -1384,13 +1401,15 @@ declare class View {
|
|
|
1384
1401
|
/**
|
|
1385
1402
|
* Creates a new View instance.
|
|
1386
1403
|
* @param name The name of the view to be created.
|
|
1387
|
-
* @param
|
|
1388
|
-
|
|
1389
|
-
|
|
1404
|
+
* @param config Configuration for the view: select statement, base tables, optional database, and optional metadata.
|
|
1405
|
+
*/
|
|
1406
|
+
constructor(name: string, config: ViewConfig);
|
|
1407
|
+
/**
|
|
1408
|
+
* @deprecated Use the config-object overload: `new View(name, { selectStatement, baseTables, metadata? })`.
|
|
1390
1409
|
*/
|
|
1391
1410
|
constructor(name: string, selectStatement: string | Sql, baseTables: (OlapTable<any> | View)[], metadata?: {
|
|
1392
1411
|
[key: string]: any;
|
|
1393
1412
|
});
|
|
1394
1413
|
}
|
|
1395
1414
|
|
|
1396
|
-
export { QueryClient as $, type Decimal as A, type Insertable as B, ClickHouseEngines as C, type DateTime as D, quoteIdentifier as E, type FixedString as F, type IdentifierBrandedString as G, type Value as H, type Int8 as I, type SqlTemplateTag as J, sql as K, LifeCycle as L, Sql as M, type NonIdentifierBrandedString as N, OlapTable as O, toStaticQuery as P, toQuery as Q, type RawValue as R, type S3QueueTableSettings as S, type TableConstraint as T, type UInt8 as U, View as V, type WithDefault as W, toQueryPreview as X, getValueFromParameter as Y, createClickhouseParameter as Z, mapToClickHouseType as _, type OlapConfig as a, TypedBase as a0, type Column as a1, type RowPolicyOptions as a2, type TypiaValidators as a3, type
|
|
1415
|
+
export { QueryClient as $, type Decimal as A, type Insertable as B, ClickHouseEngines as C, type DateTime as D, quoteIdentifier as E, type FixedString as F, type IdentifierBrandedString as G, type Value as H, type Int8 as I, type SqlTemplateTag as J, sql as K, LifeCycle as L, Sql as M, type NonIdentifierBrandedString as N, OlapTable as O, toStaticQuery as P, toQuery as Q, type RawValue as R, type S3QueueTableSettings as S, type TableConstraint as T, type UInt8 as U, View as V, type WithDefault as W, toQueryPreview as X, getValueFromParameter as Y, createClickhouseParameter as Z, mapToClickHouseType as _, type OlapConfig as a, TypedBase as a0, type Column as a1, type RowPolicyOptions as a2, type TypiaValidators as a3, type ViewConfig as a4, type DataType as a5, type ClickHousePoint as a6, type ClickHouseRing as a7, type ClickHouseLineString as a8, type ClickHouseMultiLineString as a9, type ClickHousePolygon as aa, type ClickHouseMultiPolygon as ab, type ClickHousePrecision as b, type ClickHouseDecimal as c, type ClickHouseByteSize as d, type ClickHouseFixedStringSize as e, type ClickHouseFloat as f, type ClickHouseInt as g, type ClickHouseJson as h, type LowCardinality as i, type ClickHouseNamedTuple as j, type ClickHouseDefault as k, type ClickHouseTTL as l, type ClickHouseMaterialized as m, type ClickHouseAlias as n, type ClickHouseCodec as o, type DateTime64 as p, type DateTimeString as q, type DateTime64String as r, type Float32 as s, type Float64 as t, type Int16 as u, type Int32 as v, type Int64 as w, type UInt16 as x, type UInt32 as y, type UInt64 as z };
|