From fc89792f67dcd1f2cba275b2cb99f4d65a300c36 Mon Sep 17 00:00:00 2001 From: vmasdani Date: Mon, 10 May 2021 01:04:36 +0700 Subject: [PATCH 1/4] add experimentalAutoMigrate, WIP check if field exists --- lib/database.ts | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/lib/database.ts b/lib/database.ts index 48e884e..6f78d28 100644 --- a/lib/database.ts +++ b/lib/database.ts @@ -211,6 +211,40 @@ export class Database { } } + /** Automatic migrate: Create the given models in the current database, and then sync all fields in the table. + * Does not delete fields. + * + * await db.experimentalAutoMigrate(); + */ + async experimentalAutoMigrate() { + const dialect = this.getDialect() as BuiltInDatabaseDialect; + + console.log("[experimentalAutoMigrate] dialect:", dialect); + + for (const model of this._models) { + await model.createTable(); + + if (dialect === "mongo") { + throw ("Auto-migration only works on SQL."); + } + + console.log(`[experimentalAutoMigrate] syncing table ${model.table}`); + console.log(`[experimentalAutoMigrate] fields:`); + + for (const key in model.fields) { + console.log(key, ":", model.fields[key]); + + const checkDesc = new QueryBuilder().select(key) + .table( + model.table, + ).get(); + + console.log(checkDesc.toDescription()); + console.log(checkDesc.queryForSchema(model)); + } + } + } + /** Associate all the required information for a model to connect to a database. * * await db.link([Flight, Airport]); From 93bec1d56b60a882d77090b4e21b0735e83d330a Mon Sep 17 00:00:00 2001 From: vmasdani Date: Mon, 10 May 2021 04:42:56 +0700 Subject: [PATCH 2/4] create table only without fields, some files modified --- lib/database.ts | 2 +- lib/model.ts | 25 +++++++++++++++++++++++++ lib/query-builder.ts | 16 ++++++++++++++++ 3 files changed, 42 insertions(+), 1 deletion(-) diff --git a/lib/database.ts b/lib/database.ts index 6f78d28..e658cd8 100644 --- a/lib/database.ts +++ b/lib/database.ts @@ -222,7 +222,7 @@ export class Database { console.log("[experimentalAutoMigrate] dialect:", dialect); for (const model of this._models) { - await model.createTable(); + await model.createTableOnlyTable(); if (dialect === "mongo") { throw ("Auto-migration only works on SQL."); diff --git a/lib/model.ts b/lib/model.ts index 0ee72ed..51041f8 100644 --- a/lib/model.ts +++ b/lib/model.ts @@ -174,6 +174,31 @@ export class Model { this._isCreatedInDatabase = true; } + /** Create a model in the database. Only the table not the fields. + * Should not be called from a child model. */ + static async createTableOnlyTable() { + if (this._isCreatedInDatabase) { + throw new Error("This model has already been initialized."); + } + + const createQuery = this._options.queryBuilder + .queryForSchema(this) + .table(this.table) + .createTableOnlyTable( + { + withTimestamps: this.timestamps, + ifNotExists: true, + }, + ) + .toDescription(); + + await this._options.database.query(createQuery); + + this._isCreatedInDatabase = true; + } + + + /** Manually find the primary field by going through the schema fields. */ private static _findPrimaryField(): FieldOptions { const field = Object.entries(this.fields).find( diff --git a/lib/query-builder.ts b/lib/query-builder.ts index cd9e0e6..59d926b 100644 --- a/lib/query-builder.ts +++ b/lib/query-builder.ts @@ -117,6 +117,22 @@ export class QueryBuilder { return this; } + createTableOnlyTable( + { + withTimestamps, + ifNotExists, + }: { + withTimestamps: boolean; + ifNotExists: boolean; + }, + ) { + this._query.type = "create"; + this._query.ifExists = ifNotExists ? false : true; + this._query.fields = {}; + this._query.timestamps = withTimestamps; + return this; + } + dropIfExists() { this._query.type = "drop"; this._query.ifExists = true; From 287a975371ea87185594940ebf884768183041e0 Mon Sep 17 00:00:00 2001 From: vmasdani Date: Mon, 10 May 2021 23:13:16 +0700 Subject: [PATCH 3/4] prototype auto migrate finish. Need to research dex --- lib/database.ts | 23 ++++++++--------------- lib/model.ts | 40 ++++++++++++++++++++++++++++++++++++++++ lib/query-builder.ts | 26 ++++++++++++++++++++++++++ 3 files changed, 74 insertions(+), 15 deletions(-) diff --git a/lib/database.ts b/lib/database.ts index e658cd8..44e292e 100644 --- a/lib/database.ts +++ b/lib/database.ts @@ -222,26 +222,19 @@ export class Database { console.log("[experimentalAutoMigrate] dialect:", dialect); for (const model of this._models) { - await model.createTableOnlyTable(); + try { + console.log('[experimentalAutoMigrate] migrating table', model.table); + await model.createTableOnlyTable(); + } catch (e) { + console.log('[experimentalAutoMigrate]', e); + } + if (dialect === "mongo") { throw ("Auto-migration only works on SQL."); } - console.log(`[experimentalAutoMigrate] syncing table ${model.table}`); - console.log(`[experimentalAutoMigrate] fields:`); - - for (const key in model.fields) { - console.log(key, ":", model.fields[key]); - - const checkDesc = new QueryBuilder().select(key) - .table( - model.table, - ).get(); - - console.log(checkDesc.toDescription()); - console.log(checkDesc.queryForSchema(model)); - } + await model.autoMigrate(); } } diff --git a/lib/model.ts b/lib/model.ts index 51041f8..f9c3fe3 100644 --- a/lib/model.ts +++ b/lib/model.ts @@ -197,6 +197,46 @@ export class Model { this._isCreatedInDatabase = true; } + /** Auto-migrate */ + static async autoMigrate() { + console.log(`[experimentalAutoMigrate] syncing table ${this.table}`); + console.log(`[experimentalAutoMigrate] fields:`); + + for (const key in this.fields) { + console.log('[autoMigrate]', key, ":", this.fields[key], ':type', typeof this.fields[key]); + + const checkDesc = this._queryBuilder.select(key) + .table( + this.table, + ).get(); + + try { + await this._options.database.query(checkDesc.toDescription()); + } catch (e) { + console.log(`[autoMigrate] ${this.table}:${key}:${this.fields[key]} error ${e}`); + console.log(`[autoMigrate] column not found. Creating ${key}:${this.fields[key]}`); + + try { + // await this._options.database.query(`alter table ${this.table} add column ${key} ${this.fields[key]}`); + const alterQuery = this._queryBuilder + .removeSelect() + .alterTable(this.table) + .addColumn(key) + .columnType(this.fields[key].toString()); + + console.log(`[autoMigrate] TODO: query builder alter table, add column ${this.table}:${key}:${this.fields[key]}`); + // console.log('[autoMigrate]', alterQuery); + console.log('\n') + } catch (e) { + console.log('[autoMigrate] failed altering', e); + } + + } + + } + } + + /** Manually find the primary field by going through the schema fields. */ diff --git a/lib/query-builder.ts b/lib/query-builder.ts index 59d926b..eb923fa 100644 --- a/lib/query-builder.ts +++ b/lib/query-builder.ts @@ -7,6 +7,7 @@ export type Operator = ">" | ">=" | "<" | "<=" | "=" | "like"; export type OrderDirection = "desc" | "asc"; export type QueryType = | "create" + | "alter table" | "drop" | "truncate" | "select" @@ -60,6 +61,8 @@ export type QueryDescription = { fieldsDefaults?: ModelDefaults; timestamps?: boolean; values?: Values | Values[]; + addColumn?: string; + columnType?: string; }; export type QueryResult = {}; @@ -89,6 +92,24 @@ export class QueryBuilder { return this; } + alterTable(table: string) { + this._query.type = "alter table"; + this._query.table = table; + return this; + } + + addColumn(columnName: string) { + this._query.addColumn = columnName; + return this; + } + + columnType(columnType: string) { + this._query.columnType = columnType; + return this; + } + + + get() { this._query.type = "select"; return this; @@ -149,6 +170,11 @@ export class QueryBuilder { return this; } + removeSelect() { + this._query.select = undefined; + return this; + } + create(values: Values[]) { this._query.type = "insert"; this._query.values = values; From 88de82f8f504e0275ce3a2efdadfbaff0281ae72 Mon Sep 17 00:00:00 2001 From: vmasdani Date: Thu, 20 May 2021 22:31:56 +0700 Subject: [PATCH 4/4] autoaddcolumn update dex usage --- lib/model.ts | 233 +++++++++++++++--------------- lib/query-builder.ts | 55 ++----- lib/translators/sql-translator.ts | 128 ++++++++++------ 3 files changed, 222 insertions(+), 194 deletions(-) diff --git a/lib/model.ts b/lib/model.ts index f9c3fe3..132faa0 100644 --- a/lib/model.ts +++ b/lib/model.ts @@ -63,7 +63,7 @@ export type ModelEventListeners = { /** Model that can be used with a `Database`. */ export class Model { - [attribute: string]: FieldValue | Function + [attribute: string]: FieldValue | Function; /** Table name as it should be saved in the database. */ static table = ""; @@ -119,7 +119,7 @@ export class Model { this._fieldMatching = this._database._computeModelFieldMatchings( this.name, this.fields, - this.timestamps, + this.timestamps ); this._currentQuery = this._queryBuilder.queryForSchema(this); @@ -165,7 +165,7 @@ export class Model { { withTimestamps: this.timestamps, ifNotExists: true, - }, + } ) .toDescription(); @@ -184,12 +184,10 @@ export class Model { const createQuery = this._options.queryBuilder .queryForSchema(this) .table(this.table) - .createTableOnlyTable( - { - withTimestamps: this.timestamps, - ifNotExists: true, - }, - ) + .createTableOnlyTable({ + withTimestamps: this.timestamps, + ifNotExists: true, + }) .toDescription(); await this._options.database.query(createQuery); @@ -198,51 +196,59 @@ export class Model { } /** Auto-migrate */ - static async autoMigrate() { + static async autoMigrate() { console.log(`[experimentalAutoMigrate] syncing table ${this.table}`); - console.log(`[experimentalAutoMigrate] fields:`); + console.log(`[experimentalAutoMigrate] fields:`, this.fields); for (const key in this.fields) { - console.log('[autoMigrate]', key, ":", this.fields[key], ':type', typeof this.fields[key]); + console.log( + "[autoMigrate]", + key, + ":", + this.fields[key], + ":type", + typeof this.fields[key] + ); - const checkDesc = this._queryBuilder.select(key) - .table( - this.table, - ).get(); + const checkDesc = this._queryBuilder.select(key).table(this.table).get(); + + try { + await this._options.database.query(checkDesc.toDescription()); + } catch (e) { + console.log( + `[autoMigrate] ${this.table}:${key}:${this.fields[key]} error ${e}` + ); + console.log( + `[autoMigrate] column not found. Creating ${key}:${this.fields[key]}` + ); try { - await this._options.database.query(checkDesc.toDescription()); + // await this._options.database.query(`alter table ${this.table} add column ${key} ${this.fields[key]}`); + const alterQuery = this._queryBuilder + .removeSelect() + .alterTable(this.table) + .addColumn(this.formatFieldToDatabase(key) as string) + .columnType(this.fields[key].toString()) + .toDescription(); + + await this._options.database.query(alterQuery); + + console.log( + `[autoMigrate] TODO: query builder alter table, add column ${this.table}:${key}:${this.fields[key]}` + ); + console.log('[autoMigrate]', alterQuery); + console.log("\n"); } catch (e) { - console.log(`[autoMigrate] ${this.table}:${key}:${this.fields[key]} error ${e}`); - console.log(`[autoMigrate] column not found. Creating ${key}:${this.fields[key]}`); - - try { - // await this._options.database.query(`alter table ${this.table} add column ${key} ${this.fields[key]}`); - const alterQuery = this._queryBuilder - .removeSelect() - .alterTable(this.table) - .addColumn(key) - .columnType(this.fields[key].toString()); - - console.log(`[autoMigrate] TODO: query builder alter table, add column ${this.table}:${key}:${this.fields[key]}`); - // console.log('[autoMigrate]', alterQuery); - console.log('\n') - } catch (e) { - console.log('[autoMigrate] failed altering', e); - } - + console.log("[autoMigrate] failed altering", e); } - + } } } - - - /** Manually find the primary field by going through the schema fields. */ private static _findPrimaryField(): FieldOptions { const field = Object.entries(this.fields).find( - ([_, fieldType]) => typeof fieldType === "object" && fieldType.primaryKey, + ([_, fieldType]) => typeof fieldType === "object" && fieldType.primaryKey ); return { @@ -304,7 +310,7 @@ export class Model { private static _formatField( fieldMatching: FieldMatchingTable, field: string | { [fieldName: string]: any }, - defaultCase?: (field: string) => string, + defaultCase?: (field: string) => string ): string | { [fieldName: string]: any } { if (typeof field !== "string") { return Object.entries(field).reduce((prev: any, [fieldName, value]) => { @@ -358,7 +364,7 @@ export class Model { static on( this: T, eventType: ModelEventType, - callback: ModelEventListener, + callback: ModelEventListener ) { if (!(eventType in this._listeners)) { this._listeners[eventType] = []; @@ -376,24 +382,24 @@ export class Model { static addEventListener( this: T, eventType: ModelEventType, - callback: ModelEventListener, + callback: ModelEventListener ) { return this.on(eventType, callback); } static removeEventListener( eventType: ModelEventType, - callback: ModelEventListener, + callback: ModelEventListener ) { if (!(eventType in this._listeners)) { throw new Error( - `There is no event listener for ${eventType}. You might be trying to remove a listener that you haven't added with Model.on('${eventType}', ...).`, + `There is no event listener for ${eventType}. You might be trying to remove a listener that you haven't added with Model.on('${eventType}', ...).` ); } - this._listeners[eventType] = this._listeners[eventType]!.filter(( - listener, - ) => listener !== callback); + this._listeners[eventType] = this._listeners[eventType]!.filter( + (listener) => listener !== callback + ); return this; } @@ -401,7 +407,7 @@ export class Model { /** Run event listeners given a query type and results. */ private static _runEventListeners( queryType: QueryType, - instances?: Model | Model[], + instances?: Model | Model[] ) { // -ing => present, -ed => past const isPastEvent = !!instances; @@ -457,7 +463,7 @@ export class Model { static field(field: string, nameAs: string): FieldAlias; static field(field: string, nameAs?: string): string | FieldAlias { const fullField = this.formatFieldToDatabase( - `${this.table}.${field}`, + `${this.table}.${field}` ) as string; if (nameAs) { @@ -470,7 +476,7 @@ export class Model { /** Run the current query. */ static get() { return this._runQuery( - this._currentQuery.table(this.table).get().toDescription(), + this._currentQuery.table(this.table).get().toDescription() ); } @@ -495,7 +501,7 @@ export class Model { ...fields: (string | FieldAlias)[] ) { this._currentQuery.select( - ...fields.map((field) => this.formatFieldToDatabase(field)), + ...fields.map((field) => this.formatFieldToDatabase(field)) ); return this; } @@ -512,11 +518,14 @@ export class Model { const insertions = Array.isArray(values) ? values : [values]; const results = await this._runQuery( - this._currentQuery.table(this.table).create( - insertions.map((field) => - this.formatFieldToDatabase(this._wrapValuesWithDefaults(field)) - ) as Values[], - ).toDescription(), + this._currentQuery + .table(this.table) + .create( + insertions.map((field) => + this.formatFieldToDatabase(this._wrapValuesWithDefaults(field)) + ) as Values[] + ) + .toDescription() ); if (!Array.isArray(values) && Array.isArray(results)) { @@ -538,9 +547,9 @@ export class Model { .table(this.table) .find( this.getComputedPrimaryKey(), - Array.isArray(idOrIds) ? idOrIds : [idOrIds], + Array.isArray(idOrIds) ? idOrIds : [idOrIds] ) - .toDescription(), + .toDescription() ); return Array.isArray(idOrIds) ? results : (results as Model[])[0]; @@ -557,22 +566,20 @@ export class Model { static orderBy( this: T, fieldOrFields: string | OrderByClauses, - orderDirection: OrderDirection = "asc", + orderDirection: OrderDirection = "asc" ) { if (typeof fieldOrFields === "string") { this._currentQuery.orderBy( this.formatFieldToDatabase(fieldOrFields) as string, - orderDirection, + orderDirection ); } else { - for ( - const [field, orderDirectionField] of Object.entries( - fieldOrFields, - ) - ) { + for (const [field, orderDirectionField] of Object.entries( + fieldOrFields + )) { this._currentQuery.orderBy( this.formatFieldToDatabase(field) as string, - orderDirectionField, + orderDirectionField ); } } @@ -648,35 +655,37 @@ export class Model { static where( this: T, field: string, - fieldValue: FieldValue, + fieldValue: FieldValue ): T; static where( this: T, field: string, operator: Operator, - fieldValue: FieldValue, + fieldValue: FieldValue ): T; static where(this: T, fields: Values): T; static where( this: T, fieldOrFields: string | Values, operatorOrFieldValue?: Operator | FieldValue, - fieldValue?: FieldValue, + fieldValue?: FieldValue ) { if (typeof fieldOrFields === "string") { - const whereOperator: Operator = typeof fieldValue !== "undefined" - ? (operatorOrFieldValue as Operator) - : "="; + const whereOperator: Operator = + typeof fieldValue !== "undefined" + ? (operatorOrFieldValue as Operator) + : "="; - const whereValue: FieldValue = typeof fieldValue !== "undefined" - ? fieldValue - : (operatorOrFieldValue as FieldValue); + const whereValue: FieldValue = + typeof fieldValue !== "undefined" + ? fieldValue + : (operatorOrFieldValue as FieldValue); if (whereValue !== undefined) { this._currentQuery.where( this.formatFieldToDatabase(fieldOrFields) as string, whereOperator, - whereValue, + whereValue ); } } else { @@ -693,7 +702,7 @@ export class Model { this._currentQuery.where( this.formatFieldToDatabase(field) as string, "=", - value, + value ); } } @@ -711,15 +720,13 @@ export class Model { let fieldsToUpdate: Values = {}; if (this.timestamps) { - fieldsToUpdate[ - this.formatFieldToDatabase("updated_at") as string - ] = new Date(); + fieldsToUpdate[this.formatFieldToDatabase("updated_at") as string] = + new Date(); } if (typeof fieldOrFields === "string") { - fieldsToUpdate[ - this.formatFieldToDatabase(fieldOrFields) as string - ] = fieldValue!; + fieldsToUpdate[this.formatFieldToDatabase(fieldOrFields) as string] = + fieldValue!; } else { fieldsToUpdate = { ...fieldsToUpdate, @@ -733,7 +740,7 @@ export class Model { this._currentQuery .table(this.table) .update(fieldsToUpdate) - .toDescription(), + .toDescription() ) as Promise; } @@ -747,7 +754,7 @@ export class Model { .table(this.table) .where(this.getComputedPrimaryKey(), "=", id) .delete() - .toDescription(), + .toDescription() ); } @@ -757,7 +764,7 @@ export class Model { */ static delete() { return this._runQuery( - this._currentQuery.table(this.table).delete().toDescription(), + this._currentQuery.table(this.table).delete().toDescription() ); } @@ -776,12 +783,12 @@ export class Model { this: T, joinTable: ModelSchema, originField: string, - targetField: string, + targetField: string ) { this._currentQuery.join( joinTable.table, joinTable.formatFieldToDatabase(originField) as string, - this.formatFieldToDatabase(targetField) as string, + this.formatFieldToDatabase(targetField) as string ); return this; } @@ -801,12 +808,12 @@ export class Model { this: T, joinTable: ModelSchema, originField: string, - targetField: string, + targetField: string ) { this._currentQuery.leftOuterJoin( joinTable.table, joinTable.formatFieldToDatabase(originField) as string, - this.formatFieldToDatabase(targetField) as string, + this.formatFieldToDatabase(targetField) as string ); return this; } @@ -826,12 +833,12 @@ export class Model { this: T, joinTable: ModelSchema, originField: string, - targetField: string, + targetField: string ) { this._currentQuery.leftJoin( joinTable.table, joinTable.formatFieldToDatabase(originField) as string, - this.formatFieldToDatabase(targetField) as string, + this.formatFieldToDatabase(targetField) as string ); return this; } @@ -847,7 +854,7 @@ export class Model { this._currentQuery .table(this.table) .count(this.formatFieldToDatabase(field) as string) - .toDescription(), + .toDescription() ); return Number((value as AggregationResult[])[0].count); @@ -862,7 +869,7 @@ export class Model { this._currentQuery .table(this.table) .min(this.formatFieldToDatabase(field) as string) - .toDescription(), + .toDescription() ); return Number((value as AggregationResult[])[0].min); @@ -877,7 +884,7 @@ export class Model { this._currentQuery .table(this.table) .max(this.formatFieldToDatabase(field) as string) - .toDescription(), + .toDescription() ); return Number((value as AggregationResult[])[0].max); @@ -892,7 +899,7 @@ export class Model { this._currentQuery .table(this.table) .sum(this.formatFieldToDatabase(field) as string) - .toDescription(), + .toDescription() ); return Number((value as AggregationResult[])[0].sum); @@ -909,7 +916,7 @@ export class Model { this._currentQuery .table(this.table) .avg(this.formatFieldToDatabase(field) as string) - .toDescription(), + .toDescription() ); return Number((value as AggregationResult[])[0].avg); @@ -927,18 +934,18 @@ export class Model { */ static hasMany( this: T, - model: ModelSchema, + model: ModelSchema ): Promise { const currentWhereValue = this._findCurrentQueryWhereClause(); if (model.name in this.pivot) { const pivot = this.pivot[model.name]; const pivotField = this.formatFieldToDatabase( - pivot._pivotsFields[this.name], + pivot._pivotsFields[this.name] ) as string; const pivotOtherModel = pivot._pivotsModels[model.name]; const pivotOtherModelField = pivotOtherModel.formatFieldToDatabase( - pivot._pivotsFields[model.name], + pivot._pivotsFields[model.name] ) as string; return pivot @@ -946,7 +953,7 @@ export class Model { .join( pivotOtherModel, pivotOtherModel.field(pivotOtherModel.getComputedPrimaryKey()), - pivot.field(pivotOtherModelField), + pivot.field(pivotOtherModelField) ) .get(); } @@ -965,11 +972,13 @@ export class Model { const currentModelFKName = this._findModelForeignKeyField(this, model); const currentModelValue = await this.where( this.getComputedPrimaryKey(), - currentWhereValue, + currentWhereValue ).first(); - const currentModelFKValue = - currentModelValue[currentModelFKName] as FieldValue; - return model.where(model.getComputedPrimaryKey(), currentModelFKValue) + const currentModelFKValue = currentModelValue[ + currentModelFKName + ] as FieldValue; + return model + .where(model.getComputedPrimaryKey(), currentModelFKValue) .first(); } @@ -988,7 +997,7 @@ export class Model { if (!where) { throw new Error( - "The current query does not have any where clause for this model primary key.", + "The current query does not have any where clause for this model primary key." ); } @@ -998,10 +1007,10 @@ export class Model { /** Look for a `fieldName: Relationships.belongsTo(forModel)` field for a given `model`. */ private static _findModelForeignKeyField( model: ModelSchema, - forModel: ModelSchema = this, + forModel: ModelSchema = this ): string { const modelFK: [string, FieldType] | undefined = Object.entries( - model.fields, + model.fields ).find(([, type]) => { return typeof type === "object" ? type.relationship?.model === forModel @@ -1063,9 +1072,7 @@ export class Model { } } - await model.where(modelPK, this._getCurrentPrimaryKey()).update( - values, - ); + await model.where(modelPK, this._getCurrentPrimaryKey()).update(values); return this; } @@ -1080,7 +1087,7 @@ export class Model { if (PKCurrentValue === undefined) { throw new Error( - "This instance does not have a value for its primary key. It cannot be deleted.", + "This instance does not have a value for its primary key. It cannot be deleted." ); } diff --git a/lib/query-builder.ts b/lib/query-builder.ts index eb923fa..a00ecb8 100644 --- a/lib/query-builder.ts +++ b/lib/query-builder.ts @@ -108,8 +108,6 @@ export class QueryBuilder { return this; } - - get() { this._query.type = "select"; return this; @@ -128,7 +126,7 @@ export class QueryBuilder { }: { withTimestamps: boolean; ifNotExists: boolean; - }, + } ) { this._query.type = "create"; this._query.ifExists = ifNotExists ? false : true; @@ -138,15 +136,13 @@ export class QueryBuilder { return this; } - createTableOnlyTable( - { - withTimestamps, - ifNotExists, - }: { - withTimestamps: boolean; - ifNotExists: boolean; - }, - ) { + createTableOnlyTable({ + withTimestamps, + ifNotExists, + }: { + withTimestamps: boolean; + ifNotExists: boolean; + }) { this._query.type = "create"; this._query.ifExists = ifNotExists ? false : true; this._query.fields = {}; @@ -171,7 +167,7 @@ export class QueryBuilder { } removeSelect() { - this._query.select = undefined; + delete this._query.select; return this; } @@ -190,10 +186,7 @@ export class QueryBuilder { return this; } - orderBy( - field: string, - orderDirection: OrderDirection, - ) { + orderBy(field: string, orderDirection: OrderDirection) { if (!this._query.orderBy) { this._query.orderBy = {}; } @@ -216,11 +209,7 @@ export class QueryBuilder { return this; } - where( - field: string, - operator: Operator, - value: FieldValue, - ) { + where(field: string, operator: Operator, value: FieldValue) { if (!this._query.wheres) { this._query.wheres = []; } @@ -231,8 +220,8 @@ export class QueryBuilder { value, }; - const existingWhereForFieldIndex = this._query.wheres.findIndex((where) => - where.field === field + const existingWhereForFieldIndex = this._query.wheres.findIndex( + (where) => where.field === field ); if (existingWhereForFieldIndex === -1) { @@ -255,11 +244,7 @@ export class QueryBuilder { return this; } - join( - joinTable: string, - originField: string, - targetField: string, - ) { + join(joinTable: string, originField: string, targetField: string) { if (!this._query.joins) { this._query.joins = []; } @@ -273,11 +258,7 @@ export class QueryBuilder { return this; } - leftOuterJoin( - joinTable: string, - originField: string, - targetField: string, - ) { + leftOuterJoin(joinTable: string, originField: string, targetField: string) { if (!this._query.leftOuterJoins) { this._query.leftOuterJoins = []; } @@ -291,11 +272,7 @@ export class QueryBuilder { return this; } - leftJoin( - joinTable: string, - originField: string, - targetField: string, - ) { + leftJoin(joinTable: string, originField: string, targetField: string) { if (!this._query.leftJoins) { this._query.leftJoins = []; } diff --git a/lib/translators/sql-translator.ts b/lib/translators/sql-translator.ts index 9b2e67a..685c3d8 100644 --- a/lib/translators/sql-translator.ts +++ b/lib/translators/sql-translator.ts @@ -23,20 +23,52 @@ export class SQLTranslator implements Translator { } translateToQuery(query: QueryDescription): Query { - let queryBuilder = new (SQLQueryBuilder as any)( - { + let queryBuilder = new (SQLQueryBuilder as any)({ + client: this._dialect, + useNullAsDefault: this._dialect === "sqlite3", + log: { + // NOTE(eveningkid): It shows a message whenever `createTableIfNotExists` + // is used. Knex deprecated it as part of its library but in our case, + // it actually makes sense. As this warning message should be ignored, + // we override the `log.warn` method so it doesn't show up. + warn() {}, + }, + }); + + // Alter table for auto-migration + if (query.table && query.type === "alter table") { + // [autoMigrate] don't use queryBuilder but just use schema.alterTAble + const alterTableQuery = (SQLQueryBuilder as any)({ client: this._dialect, useNullAsDefault: this._dialect === "sqlite3", - log: { - // NOTE(eveningkid): It shows a message whenever `createTableIfNotExists` - // is used. Knex deprecated it as part of its library but in our case, - // it actually makes sense. As this warning message should be ignored, - // we override the `log.warn` method so it doesn't show up. - warn() { - }, - }, - }, - ); + }).schema.alterTable(query.table, (table: any) => { + // [TODO] [FATAL] find a way to match dex's column type to query's column type (query.columnType) + // For example, do I have to do this? + // switch (query.columnType) { + // case "text": + // table.text(query.addColumn); + // break; + + // case "string": + // table.string(query.addColumn); + // break; + + // case "integer": + // table.integer(query.addColumn); + // break; + // default: + // break; + // } + + table.text(query.addColumn); + }); + + console.log( + `[autoMigrate] QUERY DETECTED AS ALTER TABLE: ${query.addColumn} ${query.columnType} ${this._dialect}` + ); + + return alterTableQuery.toString(); + } if (query.table && query.type !== "drop" && query.type !== "create") { queryBuilder = queryBuilder.table(query.table); @@ -51,7 +83,7 @@ export class SQLTranslator implements Translator { if (query.whereIn) { queryBuilder = queryBuilder.whereIn( query.whereIn.field, - query.whereIn.possibleValues, + query.whereIn.possibleValues ); } @@ -60,7 +92,7 @@ export class SQLTranslator implements Translator { Object.entries(query.orderBy).map(([field, orderDirection]) => ({ column: field, order: orderDirection, - })), + })) ); } @@ -81,7 +113,7 @@ export class SQLTranslator implements Translator { queryBuilder = queryBuilder.where( where.field, where.operator, - where.value, + where.value ); }); } @@ -92,7 +124,7 @@ export class SQLTranslator implements Translator { join.joinTable, join.originField, "=", - join.targetField, + join.targetField ); }); } @@ -102,7 +134,7 @@ export class SQLTranslator implements Translator { queryBuilder = queryBuilder.leftOuterJoin( join.joinTable, join.originField, - join.targetField, + join.targetField ); }); } @@ -112,12 +144,27 @@ export class SQLTranslator implements Translator { queryBuilder = queryBuilder.leftJoin( join.joinTable, join.originField, - join.targetField, + join.targetField ); }); } switch (query.type) { + case "alter table": + console.log( + `[autoMigrate] QUERY DETECTED AS ALTER TABLE: ${query.addColumn} ${query.columnType}` + ); + console.log( + "[autoMigrate]", + queryBuilder?.toString() + // ?.schema + // queryBuilder?.createTable + // ?.alterTable(query.table, (table: any) => {}) + // ?.toString() + ); + + break; + case "drop": const dropTableHelper = query.ifExists ? "dropTableIfExists" @@ -133,7 +180,7 @@ export class SQLTranslator implements Translator { case "create": if (!query.fields) { throw new Error( - "Fields were not provided for creating a new instance.", + "Fields were not provided for creating a new instance." ); } @@ -146,31 +193,26 @@ export class SQLTranslator implements Translator { (table: any) => { const fieldDefaults = query.fieldsDefaults ?? {}; - for ( - const [field, fieldType] of Object.entries(query.fields!) - ) { - addFieldToSchema( - table, - { - name: field, - type: fieldType, - defaultValue: fieldDefaults[field], - }, - ); + for (const [field, fieldType] of Object.entries(query.fields!)) { + addFieldToSchema(table, { + name: field, + type: fieldType, + defaultValue: fieldDefaults[field], + }); } if (query.timestamps) { // Adds `createdAt` and `updatedAt` fields table.timestamps(null, true); } - }, + } ); break; case "insert": if (!query.values) { throw new Error( - "Trying to make an insert query, but no values were provided.", + "Trying to make an insert query, but no values were provided." ); } @@ -180,7 +222,7 @@ export class SQLTranslator implements Translator { case "update": if (!query.values) { throw new Error( - "Trying to make an update query, but no values were provided.", + "Trying to make an update query, but no values were provided." ); } @@ -193,31 +235,31 @@ export class SQLTranslator implements Translator { case "count": queryBuilder = queryBuilder.count( - query.aggregatorField ? query.aggregatorField : "*", + query.aggregatorField ? query.aggregatorField : "*" ); break; case "avg": queryBuilder = queryBuilder.avg( - query.aggregatorField ? query.aggregatorField : "*", + query.aggregatorField ? query.aggregatorField : "*" ); break; case "min": queryBuilder = queryBuilder.min( - query.aggregatorField ? query.aggregatorField : "*", + query.aggregatorField ? query.aggregatorField : "*" ); break; case "max": queryBuilder = queryBuilder.max( - query.aggregatorField ? query.aggregatorField : "*", + query.aggregatorField ? query.aggregatorField : "*" ); break; case "sum": queryBuilder = queryBuilder.sum( - query.aggregatorField ? query.aggregatorField : "*", + query.aggregatorField ? query.aggregatorField : "*" ); break; } @@ -226,15 +268,17 @@ export class SQLTranslator implements Translator { } formatFieldNameToDatabase( - fieldName: string | FieldAlias, + fieldName: string | FieldAlias ): string | FieldAlias { if (typeof fieldName === "string") { const dotIndex = fieldName.indexOf("."); // Table.fieldName if (dotIndex !== -1) { - return fieldName.slice(0, dotIndex + 1) + - snakeCase(fieldName.slice(dotIndex + 1)); + return ( + fieldName.slice(0, dotIndex + 1) + + snakeCase(fieldName.slice(dotIndex + 1)) + ); } return snakeCase(fieldName); @@ -244,7 +288,7 @@ export class SQLTranslator implements Translator { prev[alias] = this.formatFieldNameToDatabase(fullName); return prev; }, - {}, + {} ); } }