UNPKG

@iarayan/ch-orm

Version:

A Developer-First ClickHouse ORM with Powerful CLI Tools

951 lines 29.4 kB
import { DataTypes, TableEngines } from "../constants/Types"; /** * ColumnBuilder class for fluent column definition */ export class ColumnBuilder { /** * Create a new ColumnBuilder instance * @param name - Column name * @param type - Column type * @param blueprint - Reference to the Blueprint instance */ constructor(name, type, blueprint) { /** * Flag to track if the column was registered */ this.registered = false; this.column = { name, type, }; this.blueprint = blueprint; // Register the column automatically when created // This ensures columns are registered even if no chained methods are called this.blueprint.registerColumn(this.column); this.registered = true; } /** * Register the column with the blueprint if not already registered * Used internally to ensure column is registered when methods are called */ ensureRegistered() { if (!this.registered) { this.blueprint.registerColumn(this.column); this.registered = true; } } /** * Set column name * @param name - Column name * @returns ColumnBuilder instance for chaining */ name(name) { this.column.name = name; return this; } /** * Make column nullable * @param isNullable - Optional flag to set nullable status (defaults to true) * @returns ColumnBuilder instance for chaining */ nullable(isNullable = true) { this.column.nullable = isNullable; return this; } /** * Set default value for column * @param expression - Default value expression * @returns ColumnBuilder instance for chaining */ default(expression) { this.column.default = expression; return this; } /** * Set comment for column * @param text - Comment text * @returns ColumnBuilder instance for chaining */ comment(text) { this.column.comment = text; return this; } /** * Set codec for column compression * @param codecExpression - Codec expression * @returns ColumnBuilder instance for chaining */ codec(codecExpression) { this.column.codec = codecExpression; return this; } /** * Set TTL expression for column * @param expression - TTL expression * @returns ColumnBuilder instance for chaining */ ttl(expression) { this.column.ttl = expression; return this; } /** * Get the column definition * @returns Column definition */ getDefinition() { return this.column; } /** * Register the column with the blueprint and return the blueprint * for continuing the chain on the blueprint * @returns Blueprint instance * @deprecated No longer needed as registration happens automatically */ register() { return this.blueprint; } // Proxy methods from Blueprint to allow direct chaining /** * Proxy for Blueprint.mergeTree() */ mergeTree() { return this.blueprint.mergeTree(); } /** * Proxy for Blueprint.orderBy() */ orderBy(columns) { return this.blueprint.orderBy(columns); } /** * Proxy for Blueprint.partitionBy() */ partitionBy(partitionKey) { return this.blueprint.partitionBy(partitionKey); } /** * Proxy for Blueprint.tableSettings() */ tableSettings(settings) { return this.blueprint.tableSettings(settings); } /** * Proxy for Blueprint.comment() */ tableComment(comment) { return this.blueprint.comment(comment); } /** * Proxy for Blueprint.sampleBy() */ sampleBy(expression) { return this.blueprint.sampleBy(expression); } /** * Proxy for Blueprint.ttl() (table-level TTL) */ tableTtl(expression) { return this.blueprint.ttl(expression); } /** * Proxy for Blueprint.replacingMergeTree() */ replacingMergeTree(version) { return this.blueprint.replacingMergeTree(version); } /** * Proxy for Blueprint.summingMergeTree() */ summingMergeTree(...columns) { return this.blueprint.summingMergeTree(...columns); } /** * Proxy for Blueprint.aggregatingMergeTree() */ aggregatingMergeTree() { return this.blueprint.aggregatingMergeTree(); } /** * Proxy for Blueprint.collapsingMergeTree() */ collapsingMergeTree(signColumn) { return this.blueprint.collapsingMergeTree(signColumn); } /** * Proxy for Blueprint.versionedCollapsingMergeTree() */ versionedCollapsingMergeTree(signColumn, versionColumn) { return this.blueprint.versionedCollapsingMergeTree(signColumn, versionColumn); } /** * Proxy for Blueprint.dropColumn() */ dropColumn(name) { return this.blueprint.dropColumn(name); } } /** * Blueprint class for defining table schema * Used in migrations to define table structure in a fluent manner */ export class Blueprint { /** * Create a new Blueprint instance * @param table - Table name */ constructor(table) { /** * Collection of column definitions */ this.columns = []; /** * Collection of index definitions */ this.indices = []; /** * Table engine to use */ this.engine = TableEngines.MERGE_TREE; /** * Engine parameters */ this.engineParams = []; /** * Order by expressions (PRIMARY KEY) */ this.orderByExpressions = []; /** * Partition by expression */ this.partitionByExpression = null; /** * Sampling expression */ this.samplingExpression = null; /** * Table TTL expression */ this.ttlExpression = null; /** * Table settings */ this.settings = {}; /** * Comment for the table */ this.tableComment = null; /** * Flag for if table is temporary */ this.isTemporary = false; /** * Flag for if table should be created if it doesn't exist */ this.ifNotExists = true; /** * Track column modifications for ALTER TABLE */ this.columnModifications = { add: [], modify: [], drop: [], }; /** * Flag to indicate if we're in an ALTER TABLE context */ this.isAltering = false; this.table = table; } /** * Register a column definition generated by a ColumnBuilder * @param column - Column definition to register */ registerColumn(column) { // If we're in an ALTER TABLE context, track the addition if (this.isAltering) { this.columnModifications.add.push(column); } else { this.columns.push(column); } } /** * Private method to create a ColumnBuilder for fluent API * @param name - Column name * @param type - Column type * @param options - Column options (for compatibility with existing code) * @returns ColumnBuilder instance */ createColumn(name, type, options = {}) { const builder = new ColumnBuilder(name, type, this); // Apply any options passed (for backward compatibility) if (options.nullable !== undefined) builder.nullable(options.nullable); if (options.default !== undefined) builder.default(options.default); if (options.comment !== undefined) builder.comment(options.comment); if (options.codec !== undefined) builder.codec(options.codec); if (options.ttl !== undefined) builder.ttl(options.ttl); return builder; } /** * Add a String column * @param name - Column name * @param options - Column options (for backward compatibility) * @returns ColumnBuilder instance for chaining */ string(name, options = {}) { return this.createColumn(name, DataTypes.STRING, options); } /** * Add an Int32 column * @param name - Column name * @param options - Column options (for backward compatibility) * @returns ColumnBuilder instance for chaining */ int32(name, options = {}) { return this.createColumn(name, DataTypes.INT32, options); } /** * Add an Int8 column * @param name - Column name * @param options - Column options (for backward compatibility) * @returns ColumnBuilder instance for chaining */ int8(name, options = {}) { return this.createColumn(name, DataTypes.INT8, options); } /** * Add an UInt8 column * @param name - Column name * @param options - Column options (for backward compatibility) * @returns ColumnBuilder instance for chaining */ uint8(name, options = {}) { return this.createColumn(name, DataTypes.UINT8, options); } /** * Add an Int16 column * @param name - Column name * @param options - Column options (for backward compatibility) * @returns ColumnBuilder instance for chaining */ int16(name, options = {}) { return this.createColumn(name, DataTypes.INT16, options); } /** * Add an UInt16 column * @param name - Column name * @param options - Column options (for backward compatibility) * @returns ColumnBuilder instance for chaining */ uint16(name, options = {}) { return this.createColumn(name, DataTypes.UINT16, options); } /** * Add an UInt32 column * @param name - Column name * @param options - Column options (for backward compatibility) * @returns ColumnBuilder instance for chaining */ uint32(name, options = {}) { return this.createColumn(name, DataTypes.UINT32, options); } /** * Add an Int64 column * @param name - Column name * @param options - Column options (for backward compatibility) * @returns ColumnBuilder instance for chaining */ int64(name, options = {}) { return this.createColumn(name, DataTypes.INT64, options); } /** * Add an UInt64 column * @param name - Column name * @param options - Column options (for backward compatibility) * @returns ColumnBuilder instance for chaining */ uint64(name, options = {}) { return this.createColumn(name, DataTypes.UINT64, options); } /** * Add a Float32 column * @param name - Column name * @param options - Column options (for backward compatibility) * @returns ColumnBuilder instance for chaining */ float32(name, options = {}) { return this.createColumn(name, DataTypes.FLOAT32, options); } /** * Add a Float64 column * @param name - Column name * @param options - Column options (for backward compatibility) * @returns ColumnBuilder instance for chaining */ float64(name, options = {}) { return this.createColumn(name, DataTypes.FLOAT64, options); } /** * Add a Decimal column * @param name - Column name * @param precision - Precision (total digits) * @param scale - Scale (decimal places) * @param options - Column options (for backward compatibility) * @returns ColumnBuilder instance for chaining */ decimal(name, precision = 10, scale = 0, options = {}) { return this.createColumn(name, `${DataTypes.DECIMAL}(${precision}, ${scale})`, options); } /** * Add a FixedString column * @param name - Column name * @param length - String length * @param options - Column options (for backward compatibility) * @returns ColumnBuilder instance for chaining */ fixedString(name, length, options = {}) { return this.createColumn(name, `${DataTypes.FIXED_STRING}(${length})`, options); } /** * Add a UUID column * @param name - Column name * @param options - Column options (for backward compatibility) * @returns ColumnBuilder instance for chaining */ uuid(name, options = {}) { return this.createColumn(name, DataTypes.UUID, options); } /** * Add a Date column * @param name - Column name * @param options - Column options (for backward compatibility) * @returns ColumnBuilder instance for chaining */ date(name, options = {}) { return this.createColumn(name, DataTypes.DATE, options); } /** * Add a Date32 column * @param name - Column name * @param options - Column options (for backward compatibility) * @returns ColumnBuilder instance for chaining */ date32(name, options = {}) { return this.createColumn(name, DataTypes.DATE32, options); } /** * Add a DateTime column * @param name - Column name * @param options - Column options (for backward compatibility) * @returns ColumnBuilder instance for chaining */ dateTime(name, options = {}) { return this.createColumn(name, DataTypes.DATETIME, options); } /** * Add a DateTime64 column * @param name - Column name * @param precision - Precision (0-9) * @param timezone - Timezone name * @param options - Column options (for backward compatibility) * @returns ColumnBuilder instance for chaining */ dateTime64(name, precision = 3, timezone, options = {}) { let type = `${DataTypes.DATETIME64}(${precision}`; if (timezone) { type += `, '${timezone}'`; } type += ")"; return this.createColumn(name, type, options); } /** * Add a Boolean column (implemented as UInt8) * @param name - Column name * @param options - Column options (for backward compatibility) * @returns ColumnBuilder instance for chaining */ boolean(name, options = {}) { return this.createColumn(name, DataTypes.BOOLEAN, options); } /** * Add an Array column * @param name - Column name * @param subtype - Array element type * @param options - Column options (for backward compatibility) * @returns ColumnBuilder instance for chaining */ array(name, subtype, options = {}) { return this.createColumn(name, `${DataTypes.ARRAY}(${subtype})`, options); } /** * Add a Nullable column * @param name - Column name * @param subtype - Base type * @param options - Column options (for backward compatibility) * @returns ColumnBuilder instance for chaining */ nullable(name, subtype, options = {}) { return this.createColumn(name, `${DataTypes.NULLABLE}(${subtype})`, { ...options, nullable: true, }); } /** * Add a LowCardinality column * @param name - Column name * @param subtype - Base type * @param options - Column options (for backward compatibility) * @returns ColumnBuilder instance for chaining */ lowCardinality(name, subtype, options = {}) { return this.createColumn(name, `${DataTypes.LOW_CARDINALITY}(${subtype})`, options); } /** * Add a Map column * @param name - Column name * @param keyType - Key type * @param valueType - Value type * @param options - Column options (for backward compatibility) * @returns ColumnBuilder instance for chaining */ map(name, keyType, valueType, options = {}) { return this.createColumn(name, `${DataTypes.MAP}(${keyType}, ${valueType})`, options); } /** * Add an IPv4 column * @param name - Column name * @param options - Column options (for backward compatibility) * @returns ColumnBuilder instance for chaining */ ipv4(name, options = {}) { return this.createColumn(name, DataTypes.IPV4, options); } /** * Add an IPv6 column * @param name - Column name * @param options - Column options (for backward compatibility) * @returns ColumnBuilder instance for chaining */ ipv6(name, options = {}) { return this.createColumn(name, DataTypes.IPV6, options); } /** * Add a JSON column * @param name - Column name * @param options - Column options (for backward compatibility) * @returns ColumnBuilder instance for chaining */ json(name, options = {}) { return this.createColumn(name, DataTypes.JSON, options); } /** * Add an index to the table * @param name - Index name * @param expression - Index expression * @param type - Index type * @param granularity - Index granularity * @returns Blueprint instance for chaining */ index(name, expression, type = "minmax", granularity = 1) { this.indices.push({ name, expression, type, granularity, }); return this; } /** * Set the engine for the table * @param engine - Table engine * @param params - Engine parameters * @returns Blueprint instance for chaining */ setEngine(engine, ...params) { this.engine = engine; this.engineParams = params; return this; } /** * Set the MergeTree engine * @returns Blueprint instance for chaining */ mergeTree() { return this.setEngine(TableEngines.MERGE_TREE); } /** * Set the ReplacingMergeTree engine * @param version - Version column name (optional) * @returns Blueprint instance for chaining */ replacingMergeTree(version) { return this.setEngine(TableEngines.REPLACING_MERGE_TREE, ...(version ? [version] : [])); } /** * Set the SummingMergeTree engine * @param columns - Columns to sum (optional) * @returns Blueprint instance for chaining */ summingMergeTree(...columns) { return this.setEngine(TableEngines.SUMMING_MERGE_TREE, ...(columns.length ? [columns.join(", ")] : [])); } /** * Set the AggregatingMergeTree engine * @returns Blueprint instance for chaining */ aggregatingMergeTree() { return this.setEngine(TableEngines.AGGREGATING_MERGE_TREE); } /** * Set the CollapsingMergeTree engine * @param signColumn - Sign column name * @returns Blueprint instance for chaining */ collapsingMergeTree(signColumn) { return this.setEngine(TableEngines.COLLAPSING_MERGE_TREE, signColumn); } /** * Set the VersionedCollapsingMergeTree engine * @param signColumn - Sign column name * @param versionColumn - Version column name * @returns Blueprint instance for chaining */ versionedCollapsingMergeTree(signColumn, versionColumn) { return this.setEngine(TableEngines.VERSIONED_COLLAPSING_MERGE_TREE, signColumn, versionColumn); } /** * Set the order by expression (PRIMARY KEY) * @param columns - Column expressions * @returns Blueprint instance for chaining */ orderBy(columns) { if (Array.isArray(columns)) { this.orderByExpressions = columns; } else { this.orderByExpressions = [columns]; } return this; } /** * Set the partition by expression * @param partitionKey - Partition key expression * @returns Blueprint instance for chaining */ partitionBy(partitionKey) { if (Array.isArray(partitionKey)) { this.partitionByExpression = partitionKey.join(", "); } else { this.partitionByExpression = partitionKey; } return this; } /** * Set the sampling expression * @param expression - Sampling expression * @returns Blueprint instance for chaining */ sampleBy(expression) { this.samplingExpression = expression; return this; } /** * Set the TTL expression * @param expression - TTL expression * @returns Blueprint instance for chaining */ ttl(expression) { this.ttlExpression = expression; return this; } /** * Set table settings * @param settings - Table settings * @returns Blueprint instance for chaining */ tableSettings(settings) { this.settings = { ...this.settings, ...settings }; return this; } /** * Set a comment for the table * @param comment - Table comment * @returns Blueprint instance for chaining */ comment(comment) { this.tableComment = comment; return this; } /** * Mark the table as temporary * @returns Blueprint instance for chaining */ temporary() { this.isTemporary = true; return this; } /** * Set if not exists flag * @param value - If not exists flag value * @returns Blueprint instance for chaining */ setIfNotExists(value) { this.ifNotExists = value; return this; } /** * Build the SQL for creating the table * @returns SQL query for creating the table */ toSql() { // Start the create table statement let sql = "CREATE"; // Add temporary flag if needed if (this.isTemporary) { sql += " TEMPORARY"; } sql += " TABLE"; // Add if not exists flag if needed if (this.ifNotExists) { sql += " IF NOT EXISTS"; } sql += ` ${this.table} (\n`; // Add columns const columnDefinitions = this.columns .map((column) => { let def = ` ${column.name} ${column.type}`; // Add default expression if provided if (column.default) { def += ` DEFAULT ${column.default}`; } // Add comment if provided if (column.comment) { def += ` COMMENT '${column.comment.replace(/'/g, "''")}'`; } // Add codec if provided if (column.codec) { def += ` CODEC(${column.codec})`; } // Add TTL if provided if (column.ttl) { def += ` TTL ${column.ttl}`; } return def; }) .join(",\n"); sql += columnDefinitions; // Add indices if any if (this.indices.length > 0) { const indexDefinitions = this.indices .map((idx) => { return ` INDEX ${idx.name} ${idx.expression} TYPE ${idx.type} GRANULARITY ${idx.granularity}`; }) .join(",\n"); sql += ",\n" + indexDefinitions; } sql += "\n)"; // Add engine sql += ` ENGINE = ${this.engine}`; // Add engine parameters if any if (this.engineParams.length > 0) { sql += `(${this.engineParams.join(", ")})`; } // Add ORDER BY clause if provided if (this.orderByExpressions.length > 0) { sql += `\nORDER BY (${this.orderByExpressions.join(", ")})`; } // Add PARTITION BY clause if provided if (this.partitionByExpression) { sql += `\nPARTITION BY ${this.partitionByExpression}`; } // Add SAMPLE BY clause if provided if (this.samplingExpression) { sql += `\nSAMPLE BY ${this.samplingExpression}`; } // Add TTL clause if provided if (this.ttlExpression) { sql += `\nTTL ${this.ttlExpression}`; } // Add SETTINGS if provided if (Object.keys(this.settings).length > 0) { const settingsStrings = Object.entries(this.settings) .map(([key, value]) => { return `${key} = ${value}`; }) .join(", "); sql += `\nSETTINGS ${settingsStrings}`; } // Add comment if provided if (this.tableComment) { sql += `\nCOMMENT '${this.tableComment.replace(/'/g, "''")}'`; } sql += ";"; return sql; } /** * Build the SQL for dropping the table * @param ifExists - Add IF EXISTS clause * @returns SQL query for dropping the table */ toDropSql(ifExists = true) { let sql = "DROP TABLE"; if (ifExists) { sql += " IF EXISTS"; } sql += ` ${this.table};`; return sql; } /** * Get the table name * @returns Table name */ getTableName() { return this.table; } /** * Get columns * @returns Column definitions */ getColumns() { return [...this.columns]; } /** * Generate SQL for ALTER TABLE operations * @returns SQL string for ALTER TABLE */ toAlterSql() { const parts = []; const tableName = this.getTableName(); // Add columns for (const column of this.columnModifications.add) { parts.push(`ADD COLUMN ${this.formatColumnDefinition(column)}`); } // Modify columns for (const column of this.columnModifications.modify) { parts.push(`MODIFY COLUMN ${this.formatColumnDefinition(column)}`); } // Drop columns for (const column of this.columnModifications.drop) { parts.push(`DROP COLUMN ${column}`); } if (parts.length === 0) { return ""; } return `ALTER TABLE ${tableName} ${parts.join(", ")}`; } /** * Format a column definition for SQL * @param column - Column definition * @returns Formatted column definition */ formatColumnDefinition(column) { let definition = `${column.name} ${column.type}`; // Only add NULL/NOT NULL constraints when not in ALTER TABLE context if (!this.isAltering) { if (column.nullable) { definition += " NULL"; } else { definition += " NOT NULL"; } } if (column.default) { definition += ` DEFAULT ${column.default}`; } if (column.comment) { definition += ` COMMENT '${column.comment}'`; } if (column.codec) { definition += ` CODEC(${column.codec})`; } if (column.ttl) { definition += ` TTL ${column.ttl}`; } return definition; } /** * Drop a column * @param name - Column name to drop * @returns Blueprint instance for chaining */ dropColumn(name) { this.columnModifications.drop.push(name); return this; } /** * Set the ALTER TABLE context * @param value - Whether we're in an ALTER TABLE context * @returns Blueprint instance for chaining */ setAltering(value) { this.isAltering = value; return this; } /** * Add a column to the table * @param name - Column name * @param type - Column type * @param options - Column options * @returns Blueprint instance for chaining * @deprecated Use the fluent interface instead */ addColumnDefinition(name, type, options = {}) { const column = { name, type, ...options, }; // If we're in an ALTER TABLE context, track the addition if (this.isAltering) { this.columnModifications.add.push(column); } else { this.columns.push(column); } return this; } /** * Modify an existing column * @param name - Column name * @param type - New column type * @param options - New column options * @returns Blueprint instance for chaining */ modifyColumnDefinition(name, type, options = {}) { this.columnModifications.modify.push({ name, type, ...options, }); return this; } /** * Base method to handle column definitions for both new and existing columns * @param name - Column name * @param type - Column type * @param options - Column options * @returns Blueprint instance for chaining * @deprecated Use the fluent interface instead */ handleColumnDefinition(name, type, options = {}) { if (this.isAltering) { // In ALTER TABLE context, we need to determine if this is a new column or modifying existing const isModify = this.columnModifications.modify.some((col) => col.name === name) || this.columnModifications.add.some((col) => col.name === name); if (isModify) { return this.modifyColumnDefinition(name, type, options); } else { return this.addColumnDefinition(name, type, options); } } return this.addColumnDefinition(name, type, options); } } //# sourceMappingURL=Blueprint.js.map