@iarayan/ch-orm
Version:
A Developer-First ClickHouse ORM with Powerful CLI Tools
956 lines • 29.9 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.Blueprint = exports.ColumnBuilder = void 0;
const Types_1 = require("../constants/Types");
/**
* ColumnBuilder class for fluent column definition
*/
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);
}
}
exports.ColumnBuilder = ColumnBuilder;
/**
* Blueprint class for defining table schema
* Used in migrations to define table structure in a fluent manner
*/
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 = Types_1.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, Types_1.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, Types_1.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, Types_1.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, Types_1.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, Types_1.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, Types_1.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, Types_1.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, Types_1.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, Types_1.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, Types_1.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, Types_1.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, `${Types_1.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, `${Types_1.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, Types_1.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, Types_1.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, Types_1.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, Types_1.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 = `${Types_1.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, Types_1.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, `${Types_1.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, `${Types_1.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, `${Types_1.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, `${Types_1.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, Types_1.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, Types_1.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, Types_1.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(Types_1.TableEngines.MERGE_TREE);
}
/**
* Set the ReplacingMergeTree engine
* @param version - Version column name (optional)
* @returns Blueprint instance for chaining
*/
replacingMergeTree(version) {
return this.setEngine(Types_1.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(Types_1.TableEngines.SUMMING_MERGE_TREE, ...(columns.length ? [columns.join(", ")] : []));
}
/**
* Set the AggregatingMergeTree engine
* @returns Blueprint instance for chaining
*/
aggregatingMergeTree() {
return this.setEngine(Types_1.TableEngines.AGGREGATING_MERGE_TREE);
}
/**
* Set the CollapsingMergeTree engine
* @param signColumn - Sign column name
* @returns Blueprint instance for chaining
*/
collapsingMergeTree(signColumn) {
return this.setEngine(Types_1.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(Types_1.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);
}
}
exports.Blueprint = Blueprint;
//# sourceMappingURL=Blueprint.js.map