@iarayan/ch-orm
Version:
A Developer-First ClickHouse ORM with Powerful CLI Tools
294 lines • 11 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.MigrationRunner = void 0;
const Schema_1 = require("./Schema");
const MigrationRecord_1 = require("./models/MigrationRecord");
/**
* MigrationRunner class for managing migration execution
* Handles applying and rolling back migrations
*/
class MigrationRunner {
/**
* Create a new MigrationRunner instance
* @param connection - ClickHouse connection
*/
constructor(connection) {
/**
* Name of the migrations table
*/
this.migrationsTable = "migrations";
/**
* Array of migrations to be applied or rolled back
*/
this.migrations = [];
this.connection = connection;
this.schema = new Schema_1.Schema(connection);
// Set the connection for the model
MigrationRecord_1.MigrationRecord.setConnection(connection);
}
/**
* Set the name of the migrations table
* @param tableName - Migrations table name
* @returns MigrationRunner instance for chaining
*/
setMigrationsTable(tableName) {
this.migrationsTable = tableName;
return this;
}
/**
* Add a migration to the runner
* @param migration - Migration instance
* @returns MigrationRunner instance for chaining
*/
add(migration) {
this.migrations.push(migration);
return this;
}
/**
* Add multiple migrations to the runner
* @param migrations - Array of migration instances
* @returns MigrationRunner instance for chaining
*/
addMultiple(migrations) {
this.migrations = [...this.migrations, ...migrations];
return this;
}
/**
* Set the migrations for the runner
* @param migrations - Array of migration instances
* @returns MigrationRunner instance for chaining
*/
setMigrations(migrations) {
this.migrations = migrations;
return this;
}
/**
* Ensure the migrations table exists
* @returns Promise that resolves when the migrations table is created (if needed)
*/
async ensureMigrationsTable() {
try {
// First check if the table exists using the Schema class
const tableExists = await this.schema.hasTable(this.migrationsTable);
if (!tableExists) {
// If it doesn't exist, create it
await this.createMigrationsTable();
}
}
catch (error) {
// If there's an error, try to create the table
await this.createMigrationsTable();
}
}
/**
* Get all migration records from the database
* @returns Promise that resolves to array of migration records
*/
async getMigrationRecords() {
await this.ensureMigrationsTable();
// Use the MigrationRecord model to get all records
const records = await MigrationRecord_1.MigrationRecord.query()
.orderBy("created_at", "ASC")
.get();
return records ? records : [];
}
/**
* Get the last batch number from the migration records
* @returns Promise that resolves to the last batch number or 0 if no migrations have been run
*/
async getLastBatchNumber() {
var _a;
// Use the MigrationRecord model to get the max batch number
const result = await MigrationRecord_1.MigrationRecord.query().max("batch");
if (!result || !result.length) {
return 0;
}
const maxBatch = (_a = result[0]) === null || _a === void 0 ? void 0 : _a["max(batch)"];
return maxBatch || 0;
}
/**
* Save a migration record to the database
* @param migration - Migration instance
* @param batch - Batch number
* @param executionTime - Execution time in seconds
* @returns Promise that resolves when the record is saved
*/
async saveMigrationRecord(migration, batch, executionTime) {
// Create a new MigrationRecord instance
await MigrationRecord_1.MigrationRecord.createAndSave({
name: migration.getName(),
batch,
execution_time: executionTime,
created_at: new Date(),
});
}
/**
* Delete a migration record from the database
* @param migration - Migration instance
* @returns Promise that resolves when the record is deleted
*/
async deleteMigrationRecord(migration) {
// Use the MigrationRecord model to delete the record
await MigrationRecord_1.MigrationRecord.query().where("name", migration.getName()).delete();
}
/**
* Run pending migrations
* @returns Promise that resolves to number of migrations applied
*/
async run() {
await this.ensureMigrationsTable();
// Get existing migration records
const existingRecords = await this.getMigrationRecords();
const existingNames = existingRecords.map((record) => record.name);
// Get pending migrations
const pendingMigrations = this.migrations.filter((migration) => !existingNames.includes(migration.getName()));
if (pendingMigrations.length === 0) {
return 0;
}
// Get the next batch number
const nextBatch = (await this.getLastBatchNumber()) + 1;
// Apply each pending migration
let appliedCount = 0;
for (const migration of pendingMigrations) {
const startTime = process.hrtime();
// Apply the migration
await migration.apply();
// Calculate execution time
const [seconds, nanoseconds] = process.hrtime(startTime);
const executionTime = seconds + nanoseconds / 1e9;
// Save the migration record
await this.saveMigrationRecord(migration, nextBatch, executionTime);
// Update the applied count
appliedCount++;
}
return appliedCount;
}
/**
* Rollback the last batch of migrations
* @returns Promise that resolves to number of migrations rolled back
*/
async rollback() {
await this.ensureMigrationsTable();
// Get the last batch number
const lastBatch = await this.getLastBatchNumber();
if (lastBatch === 0) {
return 0;
}
// Get records from the last batch using the model
const recordsToRollback = await MigrationRecord_1.MigrationRecord.query()
.where("batch", lastBatch)
.orderBy("created_at", "DESC")
.get();
if (recordsToRollback.length === 0) {
return 0;
}
// Find the corresponding migration instances
const migrationsToRollback = recordsToRollback
.map((record) => this.migrations.find((m) => m.getName() === record.name))
.filter((m) => m !== undefined);
// Set these migrations as applied
migrationsToRollback.forEach((migration) => migration.setApplied(true));
// Rollback each migration
let rolledBackCount = 0;
for (const migration of migrationsToRollback) {
// Rollback the migration
await migration.revert();
// Delete the migration record
await this.deleteMigrationRecord(migration);
// Update the rolled back count
rolledBackCount++;
}
return rolledBackCount;
}
/**
* Reset the database by rolling back all migrations
* @returns Promise that resolves to number of migrations rolled back
*/
async reset() {
await this.ensureMigrationsTable();
// Get all migration records
const recordsToRollback = await MigrationRecord_1.MigrationRecord.query()
.orderBy("created_at", "DESC")
.get();
if (!recordsToRollback || recordsToRollback.length === 0) {
return 0;
}
// Find the corresponding migration instances
const migrationsToRollback = recordsToRollback
.map((record) => this.migrations.find((m) => m.getName() === record.name))
.filter((m) => m !== undefined);
// Set these migrations as applied
migrationsToRollback.forEach((migration) => migration.setApplied(true));
// Rollback each migration
let rolledBackCount = 0;
for (const migration of migrationsToRollback) {
// Rollback the migration
await migration.revert();
// Delete the migration record
await this.deleteMigrationRecord(migration);
// Update the rolled back count
rolledBackCount++;
}
return rolledBackCount;
}
/**
* Refresh the database by rolling back all migrations and then running them again
* @returns Promise that resolves to number of migrations applied
*/
async refresh() {
// Reset the database
await this.reset();
// Run all migrations
return this.run();
}
/**
* Get array of pending migrations
* @returns Promise that resolves to array of pending migration names
*/
async getPendingMigrations() {
const existingRecords = await this.getMigrationRecords();
const existingNames = existingRecords.map((record) => record.name);
return this.migrations
.filter((migration) => !existingNames.includes(migration.getName()))
.map((migration) => migration.getName());
}
/**
* Get array of all migration names
* @returns Array of all migration names
*/
getMigrationNames() {
return this.migrations.map((migration) => migration.getName());
}
/**
* Get the underlying connection instance
* @returns Connection instance
*/
getConnection() {
return this.connection;
}
/**
* Create the migrations table if it doesn't exist
* This is used to track which migrations have been applied
* @returns Promise that resolves when the table is created
*/
async createMigrationsTable() {
// Check if the table exists first
const tableExists = await this.schema.hasTable(this.migrationsTable);
if (!tableExists) {
// Create the migrations table
await this.schema.create(this.migrationsTable, (table) => {
// Define columns matching the MigrationRecord model
table.string("name");
table.int32("batch");
table.float64("execution_time", { nullable: true });
table.dateTime("created_at");
// Set the engine to ReplacingMergeTree
table.replacingMergeTree();
// In ClickHouse, the ORDER BY key is effectively the primary key
table.orderBy("name");
});
}
}
}
exports.MigrationRunner = MigrationRunner;
//# sourceMappingURL=MigrationRunner.js.map