or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

bigquery-client.mdclickhouse-client.mdcore-types.mddatabricks-client.mdfactory-functions.mdindex.mdpostgres-client.mdredshift-client.mdsnowflake-client.mdsql-builders.mdssh-tunnel.mdtrino-client.md
tile.json

clickhouse-client.mddocs/

Clickhouse Client

Clickhouse column-oriented OLAP database client with named parameter support, HTTP/HTTPS protocol, streaming queries, and timezone session configuration.

⚠️ CRITICAL: NOT EXPORTED FROM PACKAGE

ClickhouseWarehouseClient, ClickhouseSqlBuilder, and ClickhouseTypes are NOT exported from the @lightdash/warehouses package index. They can ONLY be accessed via factory functions:

  • Use warehouseClientFromCredentials({ type: WarehouseTypes.CLICKHOUSE, ... }) to create a client
  • Use warehouseSqlBuilderFromType(SupportedDbtAdapter.CLICKHOUSE) to create a SQL builder

Direct imports like import { ClickhouseWarehouseClient } from '@lightdash/warehouses' will fail at runtime.

Capabilities

ClickhouseWarehouseClient Class

Clickhouse warehouse client implementation with HTTP protocol and named parameter queries.

/**
 * Clickhouse warehouse client with HTTP/HTTPS protocol and named parameters
 *
 * IMPORTANT: Clickhouse uses database as the primary namespace (not catalog+schema).
 * In this implementation, the 'schema' credential field maps to Clickhouse's 'database'.
 * Named parameters use {paramName: Type} syntax in SQL queries.
 */
class ClickhouseWarehouseClient implements WarehouseClient {
  /** Clickhouse client instance (from '@clickhouse/client' package) */
  client: ClickHouseClient;

  /**
   * Initialize Clickhouse client with credentials
   * Uses HTTP or HTTPS protocol based on secure flag
   * @param credentials - Clickhouse credentials with host, port, database, and auth
   */
  constructor(credentials: CreateClickhouseCredentials);

  /**
   * Test the database connection
   * Executes a simple query (SELECT 1) to verify connectivity and credentials
   * @returns Promise that resolves if connection is successful
   * @throws {WarehouseConnectionError} If connection fails
   */
  test(): Promise<void>;

  /**
   * Stream query results with named parameters and timezone support
   * Uses JSONCompactEachRowWithNamesAndTypes format for efficient streaming
   * @param sql - SQL query string with optional named parameters {paramName: Type}
   * @param streamCallback - Callback invoked for each batch of results
   * @param options - Query execution options with named parameters, tags, and timezone
   * @returns Promise that resolves when streaming completes
   * @note Clickhouse REQUIRES named parameters format: {paramName: Type} in SQL
   * @note Example: SELECT * FROM users WHERE id = {userId: UInt32}
   */
  streamQuery(
    sql: string,
    streamCallback: (data: WarehouseResults) => void,
    options: {
      queryParams?: Record<string, AnyType>;
      tags?: Record<string, string>;
      timezone?: string;
    }
  ): Promise<void>;

  /**
   * Execute query and return all results
   * Inherited from WarehouseBaseClient
   * @param sql - SQL query string to execute
   * @param tags - Optional tags for query tracking
   * @param timezone - Optional timezone for query execution
   * @param values - NOT SUPPORTED for Clickhouse (use queryParams instead)
   * @param queryParams - Named parameters for query (format: {paramName: value})
   * @returns Promise resolving to query results with fields and rows
   * @deprecated Use streamQuery() instead to avoid loading all results into memory
   */
  runQuery(
    sql: string,
    tags?: Record<string, string>,
    timezone?: string,
    values?: AnyType[],
    queryParams?: Record<string, AnyType>
  ): Promise<WarehouseResults>;

  /**
   * Get catalog metadata for specific tables
   * Queries system.columns table for column metadata
   * @param requests - Array of table identifiers (database in schema field, table)
   * @returns Promise resolving to nested catalog structure
   * @note In Clickhouse, 'schema' parameter maps to 'database'
   */
  getCatalog(
    requests: Array<{ database: string; schema?: string; table: string }>
  ): Promise<WarehouseCatalog>;

  /**
   * Get all tables in the configured database
   * Queries system.tables table
   * @returns Promise resolving to array of table metadata
   * @note WarehouseTables is { database: string; schema: string; table: string; partitionColumn?: PartitionColumn }[]
   */
  getAllTables(): Promise<WarehouseTables>;

  /**
   * Get tables in specific database
   * @param schema - Optional database name (uses credentials schema/database if omitted)
   * @param tags - Optional tags for query tracking
   * @returns Promise resolving to catalog with tables in database
   * @note In Clickhouse, 'schema' parameter maps to 'database'
   */
  getTables(
    schema?: string,
    tags?: Record<string, string>
  ): Promise<WarehouseCatalog>;

  /**
   * Get field metadata for specific table
   * Queries system.columns for column definitions
   * @param tableName - Name of the table
   * @param schema - Optional database name (uses credentials schema/database if omitted)
   * @param database - Always empty string for Clickhouse (uses schema parameter)
   * @param tags - Optional tags for query tracking
   * @returns Promise resolving to catalog with table's column definitions
   * @note In Clickhouse, 'schema' parameter maps to 'database'
   */
  getFields(
    tableName: string,
    schema?: string,
    database?: string,
    tags?: Record<string, string>
  ): Promise<WarehouseCatalog>;

  /**
   * Parse warehouse errors into WarehouseQueryError
   * Uses inherited base implementation from WarehouseBaseClient
   * @param error - Error object from Clickhouse
   * @returns WarehouseQueryError with enhanced error message
   */
  parseError(error: Error): Error;

  /**
   * Execute query asynchronously and optionally stream results
   * Inherited from WarehouseBaseClient
   * @param args - Async query execution arguments
   * @param resultsStreamCallback - Optional callback for streaming results
   * @returns Promise resolving to async query execution details
   * @note Clickhouse implementation uses streamQuery() internally (no native async support)
   */
  executeAsyncQuery(
    args: WarehouseExecuteAsyncQueryArgs,
    resultsStreamCallback?: (rows: WarehouseResults['rows'], fields: WarehouseResults['fields']) => void
  ): Promise<WarehouseExecuteAsyncQuery>;
}

Usage Examples:

import { warehouseClientFromCredentials } from '@lightdash/warehouses';
import { WarehouseTypes } from '@lightdash/common';

// Create Clickhouse client (only via factory function - not directly exported)
const client = warehouseClientFromCredentials({
  type: WarehouseTypes.CLICKHOUSE,
  host: 'clickhouse.example.com',
  port: 8123,
  user: 'analyst',
  password: 'secure-password',
  schema: 'analytics', // Maps to Clickhouse 'database'
  secure: true, // Use HTTPS
  timeoutSeconds: 30,
  startOfWeek: null,
});

// Test connection
await client.test();

// Stream query with named parameters and timezone
await client.streamQuery(
  `
    SELECT
      customer_id,
      SUM(order_amount) as total_spent,
      COUNT(*) as order_count
    FROM orders
    WHERE order_date >= {startDate: Date}
      AND customer_region = {region: String}
    GROUP BY customer_id
    ORDER BY total_spent DESC
    LIMIT 100
  `,
  (data) => {
    console.log('Received batch:', data.rows.length, 'rows');
    data.rows.forEach(row => {
      console.log(`Customer ${row.customer_id}: $${row.total_spent} (${row.order_count} orders)`);
    });
  },
  {
    queryParams: {
      startDate: '2023-01-01',
      region: 'US-WEST',
    },
    timezone: 'America/Los_Angeles',
    tags: { source: 'lightdash', report: 'top_customers' },
  }
);

// Get table metadata
const tables = await client.getAllTables();
console.log('Tables:', tables.map(t => t.table));

// Get column definitions
const schema = await client.getFields('orders', 'analytics');
console.log('Columns:', Object.keys(schema['']['analytics']['orders']));

ClickhouseSqlBuilder Class

SQL builder for Clickhouse-specific SQL syntax.

/**
 * SQL builder for Clickhouse with quantile/median aggregations
 * Handles single-quote escaping and SQL comment removal
 */
class ClickhouseSqlBuilder implements WarehouseSqlBuilder {
  /** Warehouse type identifier */
  readonly type: WarehouseTypes.CLICKHOUSE;

  /**
   * Get the DBT adapter type for Clickhouse
   * @returns SupportedDbtAdapter.CLICKHOUSE
   */
  getAdapterType(): SupportedDbtAdapter;

  /**
   * Get the character used for string quoting
   * @returns Single quote (')
   */
  getStringQuoteChar(): string;

  /**
   * Get the character used for escaping string quotes
   * @returns Single quote (') - Clickhouse uses quote doubling
   */
  getEscapeStringQuoteChar(): string;

  /**
   * Get the character used for field/column quoting
   * @returns Double quote (") - inherited from base
   */
  getFieldQuoteChar(): string;

  /**
   * Get the floating point type for Clickhouse
   * @returns 'FLOAT' - inherited from base
   */
  getFloatingType(): string;

  /**
   * Get the start of week day
   * @returns WeekDay value or null/undefined
   */
  getStartOfWeek(): WeekDay | null | undefined;

  /**
   * Generate Clickhouse-specific metric SQL
   * Handles percentile and median aggregations
   * @param sql - Base SQL expression (column or calculation)
   * @param metric - Metric definition with type and parameters
   * @returns SQL string for the metric aggregation
   * @note Uses quantile(fraction)(expr) for percentiles
   * @note Uses median(expr) for median
   * @note Falls back to base implementation for other metric types
   */
  getMetricSql(sql: string, metric: Metric): string;

  /**
   * Concatenate strings using Clickhouse concat function
   * @param args - Variable number of string expressions
   * @returns SQL concat expression - inherited from base
   */
  concatString(...args: string[]): string;

  /**
   * Escape string value for safe SQL interpolation
   * Doubles single quotes, escapes backslashes, removes SQL comments and null bytes
   * @param value - String value to escape
   * @returns Escaped string safe for SQL
   */
  escapeString(value: string): string;
}

Usage Examples:

import { warehouseSqlBuilderFromType } from '@lightdash/warehouses';
import { SupportedDbtAdapter, MetricType, FieldType } from '@lightdash/common';

// Create Clickhouse SQL builder (only via factory - ClickhouseSqlBuilder not exported)
const builder = warehouseSqlBuilderFromType(SupportedDbtAdapter.CLICKHOUSE);

// Generate percentile SQL (90th percentile)
const p90Sql = builder.getMetricSql('response_time', {
  fieldType: FieldType.METRIC,
  type: MetricType.PERCENTILE,
  percentile: 90,
  sql: 'response_time',
  name: 'p90_response_time',
  label: 'P90 Response Time',
  table: 'requests',
  tableLabel: 'Requests',
  hidden: false,
});
// Result: quantile(0.9)(response_time)

// Generate median SQL
const medianSql = builder.getMetricSql('revenue', {
  fieldType: FieldType.METRIC,
  type: MetricType.MEDIAN,
  sql: 'revenue',
  name: 'median_revenue',
  label: 'Median Revenue',
  table: 'orders',
  tableLabel: 'Orders',
  hidden: false,
});
// Result: median(revenue)

// Escape string values
const escaped = builder.escapeString("O'Reilly");
// Result: O''Reilly

// Get field quote character
const fieldQuote = builder.getFieldQuoteChar();
// Result: "

Clickhouse Credentials Type

Clickhouse warehouse connection credentials.

/**
 * Clickhouse warehouse credentials
 * Note: CreateClickhouseCredentials is re-exported from @lightdash/common
 */
interface CreateClickhouseCredentials {
  /** Warehouse type discriminator */
  type: WarehouseTypes.CLICKHOUSE;
  /** Clickhouse server hostname */
  host: string;
  /** Clickhouse server port (default: 8123 for HTTP, 8443 for HTTPS) */
  port: number;
  /** Username for authentication */
  user: string;
  /** Password for authentication */
  password: string;
  /** Database name (referred to as 'schema' in this implementation) */
  schema: string;
  /** Use HTTPS instead of HTTP (optional, default: false) */
  secure?: boolean;
  /** Request timeout in seconds (optional, default: 30) */
  timeoutSeconds?: number;
  /** Week start day configuration (optional) */
  startOfWeek?: WeekDay | null;
  /** Require user credentials flag (optional) */
  requireUserCredentials?: boolean;
}

ClickhouseTypes Enum

Clickhouse data type mappings.

/**
 * Clickhouse native data types
 * Note: This enum is NOT directly exported from @lightdash/warehouses
 * Only accessible when using ClickhouseWarehouseClient via factory function
 */
enum ClickhouseTypes {
  UINT8 = 'UInt8',
  UINT16 = 'UInt16',
  UINT32 = 'UInt32',
  UINT64 = 'UInt64',
  INT8 = 'Int8',
  INT16 = 'Int16',
  INT32 = 'Int32',
  INT64 = 'Int64',
  FLOAT32 = 'Float32',
  FLOAT64 = 'Float64',
  DECIMAL = 'Decimal',
  DECIMAL32 = 'Decimal32',
  DECIMAL64 = 'Decimal64',
  DECIMAL128 = 'Decimal128',
  DECIMAL256 = 'Decimal256',
  BOOL = 'Bool',
  STRING = 'String',
  FIXEDSTRING = 'FixedString',
  UUID = 'UUID',
  DATE = 'Date',
  DATE32 = 'Date32',
  DATETIME = 'DateTime',
  DATETIME64 = 'DateTime64',
  ENUM8 = 'Enum8',
  ENUM16 = 'Enum16',
  ARRAY = 'Array',
  TUPLE = 'Tuple',
  MAP = 'Map',
  NULLABLE = 'Nullable',
  LOWCARDINALITY = 'LowCardinality',
  IPV4 = 'IPv4',
  IPV6 = 'IPv6',
}

Type Mappings to DimensionType:

Clickhouse TypeDimensionType
BOOLBOOLEAN
UINT8, UINT16, UINT32, UINT64, INT8, INT16, INT32, INT64NUMBER
FLOAT32, FLOAT64NUMBER
DECIMAL, DECIMAL32, DECIMAL64, DECIMAL128, DECIMAL256NUMBER
DATE, DATE32DATE
DATETIME, DATETIME64TIMESTAMP
STRING, FIXEDSTRING, UUID, ENUM8, ENUM16STRING
ARRAY, TUPLE, MAP, IPV4, IPV6STRING
Nullable(T)Same as T (wrapper removed)
LowCardinality(T)Same as T (wrapper removed)

Authentication

Clickhouse uses HTTP Basic Authentication:

{
  type: WarehouseTypes.CLICKHOUSE,
  host: 'clickhouse.example.com',
  port: 8123, // HTTP: 8123, HTTPS: 8443 (default Clickhouse ports)
  user: 'username',
  password: 'password',
  schema: 'default', // Clickhouse database name
  secure: true, // Use HTTPS (recommended for production)
  timeoutSeconds: 30, // Request timeout (default: 30 seconds)
}

Named Parameters

Clickhouse requires named parameters with type annotations in SQL queries:

// Correct named parameter syntax
const query = `
  SELECT * FROM users
  WHERE user_id = {userId: UInt32}
    AND email = {email: String}
    AND created_at >= {startDate: Date}
`;

await client.streamQuery(query, callback, {
  queryParams: {
    userId: 12345,
    email: 'user@example.com',
    startDate: '2023-01-01',
  },
});

Supported Parameter Types (from ClickhouseTypes enum):

Numeric Types:

  • Unsigned Integers: UInt8, UInt16, UInt32, UInt64
  • Signed Integers: Int8, Int16, Int32, Int64
  • Floating Point: Float32, Float64
  • Decimals: Decimal, Decimal32, Decimal64, Decimal128, Decimal256

String Types:

  • String - Variable-length string
  • FixedString - Fixed-length string

Date/Time Types:

  • Date - Calendar date (YYYY-MM-DD)
  • Date32 - Extended date range
  • DateTime - Date and time with second precision
  • DateTime64 - Date and time with subsecond precision

Boolean:

  • Bool - Boolean value

Network Types:

  • IPv4 - IPv4 address
  • IPv6 - IPv6 address

Other Types:

  • UUID - Universally unique identifier
  • Enum8, Enum16 - Enumeration types
  • Array - Array of elements (e.g., Array(Int32))
  • Tuple - Fixed set of elements
  • Map - Key-value pairs

Type Wrappers:

  • Nullable(T) - Allows NULL values for type T
  • LowCardinality(T) - Optimized storage for low-cardinality data

Important:

  • Positional parameters ($1, $2) are NOT supported
  • Named parameters MUST include type annotation in SQL
  • Parameter values are passed via queryParams option

Schema/Database Naming

Clickhouse uses a simplified naming scheme compared to other warehouses:

// Clickhouse structure:
// database.table (no separate catalog)

// In this implementation:
// - credentials.schema maps to Clickhouse 'database'
// - database parameter is ignored (always empty string)
// - schema parameter in methods maps to Clickhouse 'database'

// Example:
await client.getFields(
  'orders',      // table name
  'analytics',   // schema (maps to Clickhouse database)
  '',            // database (ignored)
);

Streaming Format

Clickhouse client uses JSONCompactEachRowWithNamesAndTypes format for efficient streaming:

  1. First row contains column names
  2. Second row contains column types
  3. Subsequent rows contain data values as arrays
  4. Client converts arrays to objects using column names

This format minimizes JSON parsing overhead for large result sets.

Common Pitfalls

1. Direct Import Not Supported

// ❌ WRONG - ClickhouseWarehouseClient is NOT exported from index.ts
import { ClickhouseWarehouseClient } from '@lightdash/warehouses';

// ✅ CORRECT - Use factory function
import { warehouseClientFromCredentials } from '@lightdash/warehouses';
import { WarehouseTypes } from '@lightdash/common';

const client = warehouseClientFromCredentials({
  type: WarehouseTypes.CLICKHOUSE,
  // ... credentials
});

2. Named Parameter Syntax

// ❌ WRONG - Missing type annotation
const query = `SELECT * FROM users WHERE id = {userId}`;

// ❌ WRONG - Positional parameters not supported
const query = `SELECT * FROM users WHERE id = $1`;

// ✅ CORRECT - Named parameter with type
const query = `SELECT * FROM users WHERE id = {userId: UInt32}`;

3. Schema vs Database

// ❌ WRONG - Treating schema as namespace
const client = warehouseClientFromCredentials({
  type: WarehouseTypes.CLICKHOUSE,
  schema: 'public', // This maps to Clickhouse database 'public'
});

// ✅ CORRECT - Use actual Clickhouse database name
const client = warehouseClientFromCredentials({
  type: WarehouseTypes.CLICKHOUSE,
  schema: 'analytics', // Clickhouse database name
});

4. Secure Connection

// ❌ WRONG - Insecure connection in production
const client = warehouseClientFromCredentials({
  type: WarehouseTypes.CLICKHOUSE,
  secure: false, // Uses HTTP
  port: 8123,
  // ...
});

// ✅ CORRECT - Use HTTPS in production
const client = warehouseClientFromCredentials({
  type: WarehouseTypes.CLICKHOUSE,
  secure: true, // Uses HTTPS
  port: 8443, // Default HTTPS port for Clickhouse
  // ...
});

Type Wrappers

Clickhouse type wrappers (Nullable, LowCardinality) are automatically handled:

// Clickhouse column type: Nullable(String)
// Mapped to DimensionType: STRING

// Clickhouse column type: LowCardinality(String)
// Mapped to DimensionType: STRING

// Clickhouse column type: Nullable(LowCardinality(String))
// Mapped to DimensionType: STRING

Clickhouse-Specific Features

1. System Tables

Clickhouse provides rich metadata through system tables:

// Get table information
const tables = await client.runQuery(`
  SELECT database, name, engine, total_rows, total_bytes
  FROM system.tables
  WHERE database = {db: String}
`, {}, undefined, undefined, { db: 'analytics' });

// Get column information
const columns = await client.runQuery(`
  SELECT database, table, name, type, position
  FROM system.columns
  WHERE database = {db: String}
    AND table = {tbl: String}
  ORDER BY position
`, {}, undefined, undefined, { db: 'analytics', tbl: 'orders' });

2. Timezone Configuration

Clickhouse supports session-level timezone settings:

await client.streamQuery(
  'SELECT now() as current_time, toDateTime(order_timestamp) as order_time FROM orders',
  (data) => console.log(data.rows),
  {
    timezone: 'America/New_York', // Results converted to this timezone
  }
);

3. Query Performance

Clickhouse is optimized for OLAP workloads:

  • Use streaming for large result sets
  • Consider using LIMIT for exploratory queries
  • Use appropriate table engines (MergeTree for most cases)
  • Leverage Clickhouse's columnar storage for aggregations