typeorm之MysqlQueryRunner

xiaoxiao2021-02-28  24

src/driver/mysql/MysqlQueryRunner.ts //Mysql的查询运行器,在一个单独的数据库连接中执行数据库操作 //这里的数据库连接指的是底层连接,而不是typeorm的Connection export class MysqlQueryRunner implements QueryRunner { //驱动对象 driver: MysqlDriver; //typeorm连接对象 connection: Connection; //孤立的实体管理器,只对当前查询运行器有效 manager: EntityManager; //指明这个查询运行器的底层连接是否已经被释放 //释放之后不能再执行数据库操作 isReleased = false; //是否执行事务 isTransactionActive = false; //存储临时用户数据,可以在订阅者中访问 data = {}; //用来执行操作的底层数据库连接 protected databaseConnection: any; //第一次获取底层数据库连接的Promise protected databaseConnectionPromise: Promise<any>; //内存sql模式,sql存储在内存中,只存储DDL protected sqlMemoryMode: boolean = false; //内存sql模式开启,存储sql的数组,up为创建语句,down为移除语句 protected sqlsInMemory: (string|{ up: string, down: string })[] = []; //在主从复制模式下指明运行模式,非主从复制模式被忽略 protected mode: "master"|"slave"; //使用驱动实例、模式构造 constructor(driver: MysqlDriver, mode: "master"|"slave" = "master") { this.driver = driver; this.connection = driver.connection; this.mode = mode; } //连接方法,从驱动实例获取底层数据库连接 connect(): Promise<any> { //底层数据库连接存在,直接返回 if (this.databaseConnection) return Promise.resolve(this.databaseConnection); //承诺存在,直接返回 if (this.databaseConnectionPromise) return this.databaseConnectionPromise; //从模式且主从复制开启 if (this.mode === "slave" && this.driver.isReplicated) { //使用驱动获取从服务器连接 //注意:箭头函数继承父作用域this,在then里面设置了获取到的连接 this.databaseConnectionPromise = this.driver.obtainSlaveConnection().then(connection => { this.databaseConnection = connection; return this.databaseConnection; }); } else { //使用驱动获取主服务器连接,即当主从复制不开启也是获取主服务器连接 this.databaseConnectionPromise = this.driver.obtainMasterConnection().then(connection => { this.databaseConnection = connection; return this.databaseConnection; }); } return this.databaseConnectionPromise; } //释放底层数据库连接,释放之后不再能执行查询 release(): Promise<void> { this.isReleased = true; if (this.databaseConnection) //底层连接执行释放 this.databaseConnection.release(); return Promise.resolve(); } //开启事物 async startTransaction(): Promise<void> { //只能开启一次 if (this.isTransactionActive) throw new TransactionAlreadyStartedError(); this.isTransactionActive = true; //mysql的开启事物语句 await this.query("START TRANSACTION"); } //提交事务 async commitTransaction(): Promise<void> { if (!this.isTransactionActive) throw new TransactionNotStartedError(); //mysql的提交事务语句 await this.query("COMMIT"); this.isTransactionActive = false; } //回滚事务 async rollbackTransaction(): Promise<void> { if (!this.isTransactionActive) throw new TransactionNotStartedError(); //mysql的回滚事务语句 await this.query("ROLLBACK"); this.isTransactionActive = false; } //执行mysql的原始sql语句,可以执行参数数组 //一般在query中使用?表示参数,参数为字符串数组,按照下标设置参数 //其他的insert、update、delete等方法都调用了这个方法 //这个方法应该是调用底层数据库连接的唯一方法 query(query: string, parameters?: any[]): Promise<any> { //已释放不能执行 if (this.isReleased) throw new QueryRunnerAlreadyReleasedError(); return new Promise(async (ok, fail) => { try { //执行connect方法,保证底层连接存在,只有第一次执行会获取到底层连接 const databaseConnection = await this.connect(); //使用Connection的logger记录日志 this.driver.connection.logger.logQuery(query, parameters, this); //操作开始时间 const queryStartTime = +new Date(); //直接使用底层数据库连接执行,即mysql这个npm包中的连接对象 databaseConnection.query(query, parameters, (err: any, result: any) => { //如果查询时间超时,记录慢查询日志,mysql本身有慢查询功能,这里是另外的实现 const maxQueryExecutionTime = this.driver.connection.options.maxQueryExecutionTime; const queryEndTime = +new Date(); const queryExecutionTime = queryEndTime - queryStartTime; if (maxQueryExecutionTime && queryExecutionTime > maxQueryExecutionTime) this.driver.connection.logger.logQuerySlow(queryExecutionTime, query, parameters, this); //出现错误记录错误日志 if (err) { this.driver.connection.logger.logQueryError(err, query, parameters, this); return fail(new QueryFailedError(query, parameters, err)); } //返回查询结果,返回的是mysql包里面的packet类型 ok(result); }); } catch (err) { fail(err); } }); } //执行查询,返回原始数据流 //这个数据流类型为Query,继承了可读流,注意这个可读流的数据事件为packet而不是data //可读流写成字符串,还是packet数据 stream(query: string, parameters?: any[], onEnd?: Function, onError?: Function): Promise<ReadStream> { if (this.isReleased) throw new QueryRunnerAlreadyReleasedError(); return new Promise(async (ok, fail) => { try { const databaseConnection = await this.connect(); this.driver.connection.logger.logQuery(query, parameters, this); //不提供回调,则返回查询数据流 const stream = databaseConnection.query(query, parameters); if (onEnd) stream.on("end", onEnd); if (onError) stream.on("error", onError); ok(stream); } catch (err) { fail(err); } }); } //执行插入语句 //tablePath:表名、实体名 //keyValues:键值对 async insert(tablePath: string, keyValues: ObjectLiteral): Promise<InsertResult> { //获取所有键 const keys = Object.keys(keyValues); //使用''包裹,用逗号连接,'需要转义 //获取列名 const columns = keys.map(key => `\`${key}\``).join(", "); //获取插入值的占位符? const values = keys.map(key => "?").join(","); //获取值数组 const parameters = keys.map(key => keyValues[key]); //自动生成列元数据,如自动生成主键 const generatedColumns = this.connection.hasMetadata(tablePath) ? this.connection.getMetadata(tablePath).generatedColumns : []; //生成预编译语句,包含表名、列名列表、值占位符 const sql = `INSERT INTO \`${this.escapeTablePath(tablePath)}\`(${columns}) VALUES (${values})`; //执行查询 const result = await this.query(sql, parameters); //遍历所有自动生成列,获取生成键值对,reduce初始值为空对象 const generatedMap = generatedColumns.reduce((map, generatedColumn) => { //如果是主键且执行结果有插入id则返回id,否则返回输出参数对应值 const value = generatedColumn.isPrimary && result.insertId ? result.insertId : keyValues[generatedColumn.databaseName]; if (!value) return map; //合并map,map为一个键值对象 return OrmUtils.mergeDeep(map, generatedColumn.createValueMap(value)); }, {} as ObjectLiteral); //注意返回的为一个对象 return { //插入结果 result: result, //自动生成的键值对 generatedMap: Object.keys(generatedMap).length > 0 ? generatedMap : undefined }; } //执行更新操作 //tablePath:表路径,包含数据库名 //valuesMap:要更新的价值对 //conditions:更新条件键值对 async update(tablePath: string, valuesMap: ObjectLiteral, conditions: ObjectLiteral): Promise<void> { //获取拼接后的更新值的键值对字符串,包含了值占位符? const updateValues = this.parametrize(valuesMap).join(", "); //获取条件键值对字符串,包含了值占位符? const conditionString = this.parametrize(conditions).join(" AND "); //拼接sql const sql = `UPDATE \`${this.escapeTablePath(tablePath)}\` SET ${updateValues} ${conditionString ? (" WHERE " + conditionString) : ""}`; //获取条件值数组 const conditionParams = Object.keys(conditions).map(key => conditions[key]); //获取更新值数组 const updateParams = Object.keys(valuesMap).map(key => valuesMap[key]); //拼接所有的值,按照占位符顺序,先更新值,再条件值 const allParameters = updateParams.concat(conditionParams); //执行操作 await this.query(sql, allParameters); } //执行删除操作 //tablePath:表路径,包含数据库名 //conditions:删除条件 async delete(tablePath: string, conditions: ObjectLiteral|string, maybeParameters?: any[]): Promise<void> { //获取包含占位符?的条件字符串,以AND连接 const conditionString = typeof conditions === "string" ? conditions : this.parametrize(conditions).join(" AND "); //获取值数组,对应占位符?,除了在对象中直接执行键值对,也可以在第三个参数中执行值数组 const parameters = conditions instanceof Object ? Object.keys(conditions).map(key => (conditions as ObjectLiteral)[key]) : maybeParameters; //拼接sql const sql = `DELETE FROM \`${this.escapeTablePath(tablePath)}\` WHERE ${conditionString}`; //执行sql await this.query(sql, parameters); } //在封闭表中插入,即只插入自动生成对额封闭表,而实体表的插入已经完成,封闭表只管理子孙关系 //tablePath:表路径,包含数据库名 //newEntityId:插入实体id //parentId:所属父实体id //hasLevel:实体是否包含level,即TreeLevelColumn装饰器装饰的列 //返回一个级别值 async insertIntoClosureTable(tablePath: string, newEntityId: any, parentId: any, hasLevel: boolean): Promise<number> { // 包含级别列名时 if (hasLevel) { await this.query( //向封闭表中插入祖先、子孙、级别三个值 `INSERT INTO \`${this.escapeTablePath(tablePath)}\`(\`ancestor\`, \`descendant\`, \`level\`) ` + //插入值为查询结果,第一个查询为查询父实体的所有祖先,其level+1 `SELECT \`ancestor\`, ${newEntityId}, \`level\` + 1 FROM \`${this.escapeTablePath(tablePath)}\` WHERE \`descendant\` = ${parentId} ` + //不去重的合并查询结果,这个好像是新生成实体自身的关系 `UNION ALL SELECT ${newEntityId}, ${newEntityId}, 1` ); } else { //不包含级别列时 await this.query( `INSERT INTO \`${this.escapeTablePath(tablePath)}\`(\`ancestor\`, \`descendant\`) ` + `SELECT \`ancestor\`, ${newEntityId} FROM \`${this.escapeTablePath(tablePath)}\` WHERE \`descendant\` = ${parentId} ` + `UNION ALL SELECT ${newEntityId}, ${newEntityId}` ); } //如果包含级别列 if (hasLevel) { //查询子孙为父实体的最高级别 const results: ObjectLiteral[] = await this.query(`SELECT MAX(\`level\`) as \`level\` FROM \`${this.escapeTablePath(tablePath)}\` WHERE \`descendant\` = ${parentId}`); //返回级别+1 return results && results[0] && results[0]["level"] ? parseInt(results[0]["level"]) + 1 : 1; } else { return -1; } } //加载给定表名的结构数据,指定表名,为getTables方法的特殊情况 async getTable(tableName: string): Promise<Table|undefined> { const tables = await this.getTables([tableName]); return tables.length > 0 ? tables[0] : undefined; } //加载给定表名数组的结构数据,返回Table数组 async getTables(tablePaths: string[]): Promise<Table[]> { //内存模式不支持 if (this.sqlMemoryMode) throw new Error(`Loading table is not supported in sql memory mode`); if (!tablePaths || !tablePaths.length) return []; //获取单纯的表名数组,不包括数据库名 const tableNames = tablePaths.map(tablePath => { return tablePath.indexOf(".") === -1 ? tablePath : tablePath.split(".")[1]; }); //获取数据库名数组 const dbNames = tablePaths //过滤出带数据库名的路径 .filter(tablePath => tablePath.indexOf(".") !== -1) //返回数据库名数组 .map(tablePath => tablePath.split(".")[0]); //如果在获取到的数据库名数组中没有发现驱动默认的数据库名,push if (this.driver.database && !dbNames.find(dbName => dbName === this.driver.database)) dbNames.push(this.driver.database); //数据库名拼接字符串 const databaseNamesString = dbNames.map(dbName => `'${dbName}'`).join(", "); //表名拼接字符串 const tableNamesString = tableNames.map(tableName => `'${tableName}'`).join(", "); //查询表 const tablesSql = `SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA IN (${databaseNamesString}) AND TABLE_NAME IN (${tableNamesString})`; // todo(dima): fix, remove IN, apply AND and OR like in Mssql //查询列 const columnsSql = `SELECT * FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA IN (${databaseNamesString})`; //查询索引 const indicesSql = `SELECT * FROM INFORMATION_SCHEMA.STATISTICS WHERE TABLE_SCHEMA IN (${databaseNamesString}) AND INDEX_NAME != 'PRIMARY' ORDER BY SEQ_IN_INDEX`; //查询外键 const foreignKeysSql = `SELECT * FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE WHERE TABLE_SCHEMA IN (${databaseNamesString}) AND REFERENCED_COLUMN_NAME IS NOT NULL`; //在一个Promise中执行所有查询 const [dbTables, dbColumns, dbIndices, dbForeignKeys]: ObjectLiteral[][] = await Promise.all([ //注意Promise.all参数为Promsie对象数组,如果对异步函数使用await,则返回的不是Promise,所以这个不能使用Promise this.query(tablesSql), this.query(columnsSql), this.query(indicesSql), this.query(foreignKeysSql) ]); if (!dbTables.length) return []; //mariadb数据库 const isMariaDb = this.driver.options.type === "mariadb"; //创建Table类对象 return Promise.all(dbTables.map(async dbTable => { //创建对象 const table = new Table(dbTable["TABLE_NAME"]); //设置数据库名 table.database = dbTable["TABLE_SCHEMA"]; //主键列数组 const primaryKeys: ObjectLiteral[] = await this.query(`SHOW INDEX FROM \`${dbTable["TABLE_SCHEMA"]}\`.\`${dbTable["TABLE_NAME"]}\` WHERE Key_name = 'PRIMARY'`); //设置Table的列数组 table.columns = dbColumns //过滤出指定表的列 .filter(dbColumn => dbColumn["TABLE_NAME"] === table.name) //遍历返回TableColumn对象数组 .map(dbColumn => { //创建列对象 const tableColumn = new TableColumn(); //设置列名 tableColumn.name = dbColumn["COLUMN_NAME"]; //设置小写列类型 const columnType = dbColumn["COLUMN_TYPE"].toLowerCase(); //获取类型中长度括号 const endIndex = columnType.indexOf("("); //设置列类型不包括括号 tableColumn.type = endIndex !== -1 ? columnType.substring(0, endIndex) : columnType; //列默认值不存在 if (dbColumn["COLUMN_DEFAULT"] === null || dbColumn["COLUMN_DEFAULT"] === undefined || (isMariaDb && dbColumn["COLUMN_DEFAULT"] === "NULL")) { tableColumn.default = undefined; } else { //设置列默认值 tableColumn.default = dbColumn["COLUMN_DEFAULT"]; } //是否可为空 tableColumn.isNullable = dbColumn["IS_NULLABLE"] === "YES"; //是否为主键 tableColumn.isPrimary = dbColumn["COLUMN_KEY"].indexOf("PRI") !== -1; //是否唯一 tableColumn.isUnique = dbColumn["COLUMN_KEY"].indexOf("UNI") !== -1; //是否是自动生成列,mysql只支持auto_increment自动生成 tableColumn.isGenerated = dbColumn["EXTRA"].indexOf("auto_increment") !== -1; //列注解 tableColumn.comment = dbColumn["COLUMN_COMMENT"]; //精度 tableColumn.precision = dbColumn["NUMERIC_PRECISION"]; //标度 tableColumn.scale = dbColumn["NUMERIC_SCALE"]; //列级别字符集 tableColumn.charset = dbColumn["CHARACTER_SET_NAME"]; //校对名称 tableColumn.collation = dbColumn["COLLATION_NAME"]; //整型列,包括各种int、year if (tableColumn.type === "int" || tableColumn.type === "tinyint" || tableColumn.type === "smallint" || tableColumn.type === "mediumint" || tableColumn.type === "bigint" || tableColumn.type === "year") { //设置列长度 const length = columnType.substring(columnType.indexOf("(") + 1, columnType.indexOf(")")); tableColumn.length = length ? length.toString() : ""; } else { //非整型列 tableColumn.length = dbColumn["CHARACTER_MAXIMUM_LENGTH"] ? dbColumn["CHARACTER_MAXIMUM_LENGTH"].toString() : ""; } //枚举类型,mysql原生支持枚举类型 if (tableColumn.type === "enum") { //获取原始类型 const colType = dbColumn["COLUMN_TYPE"]; //获取枚举值字符串数组 const items = colType.substring(colType.indexOf("(") + 1, colType.indexOf(")")).split(","); //去头去尾 tableColumn.enum = (items as string[]).map(item => { return item.substring(1, item.length - 1); }); } //日期类型,注意:日期不是设置长度,而是精度 if (tableColumn.type === "datetime" || tableColumn.type === "time" || tableColumn.type === "timestamp") { //设置日期精度 tableColumn.precision = dbColumn["DATETIME_PRECISION"]; } return tableColumn; }); //设置主键列对象数组 table.primaryKeys = primaryKeys.map(primaryKey => { return new TablePrimaryKey(primaryKey["Key_name"], primaryKey["Column_name"]); }); //设置外键列对象数组 table.foreignKeys = dbForeignKeys //先过滤出指定表名的数组 .filter(dbForeignKey => dbForeignKey["TABLE_NAME"] === table.name) //创建外键对象,指定约束名称 .map(dbForeignKey => new TableForeignKey(dbForeignKey["CONSTRAINT_NAME"], [], [], "", "")); // todo: fix missing params //设置索引数组 table.indices = dbIndices //先过滤出指定表名,且未包含在外键、主键中的索引 .filter(dbIndex => { return dbIndex["TABLE_NAME"] === table.name && (!table.foreignKeys.find(foreignKey => foreignKey.name === dbIndex["INDEX_NAME"])) && (!table.primaryKeys.find(primaryKey => primaryKey.name === dbIndex["INDEX_NAME"])); }) //获取索引名称数组 .map(dbIndex => dbIndex["INDEX_NAME"]) //过滤出唯一值,这个方法可以记住 .filter((value, index, self) => self.indexOf(value) === index) //遍历索引名称 .map(dbIndexName => { //过滤出指定名称索引,因为上面进行了去重 const currentDbIndices = dbIndices.filter(dbIndex => dbIndex["TABLE_NAME"] === table.name && dbIndex["INDEX_NAME"] === dbIndexName); //获取列名数组 const columnNames = currentDbIndices.map(dbIndex => dbIndex["COLUMN_NAME"]); // 当只有一个指定名称索引,且为unique索引 if (currentDbIndices.length === 1 && currentDbIndices[0]["NON_UNIQUE"] === 0) { //查找出列名等于索引名且列名等于索引中指定列名的列 const column = table.columns.find(column => column.name === currentDbIndices[0]["INDEX_NAME"] && column.name === currentDbIndices[0]["COLUMN_NAME"]); if (column) { //设置列的unique属性,这种情况不创建新的TableIndex对象 column.isUnique = true; return; } } //返回TableIndex对象,指定表名、索引名、列名数组、是否为unique索引 return new TableIndex(dbTable["TABLE_NAME"], dbIndexName, columnNames, currentDbIndices[0]["NON_UNIQUE"] === 0); }) //移除空元素?,这个方法也要记一下 .filter(index => !!index) as TableIndex[]; // remove empty returns return table; })); } //检查指定名称数据库是否存在 async hasDatabase(database: string): Promise<boolean> { //mysql系统表查询 const result = await this.query(`SELECT * FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = '${database}'`); return result.length ? true : false; } //指定名称表是否存在 async hasTable(tableOrPath: Table|string): Promise<boolean> { //解析表路径,获取对象包含了数据库名、表名 const parsedTablePath = this.parseTablePath(tableOrPath); const sql = `SELECT * FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = '${parsedTablePath.database}' AND TABLE_NAME = '${parsedTablePath.tableName}'`; const result = await this.query(sql); return result.length ? true : false; } //表是否包含指定名称列 async hasColumn(tableOrPath: Table|string, column: TableColumn|string): Promise<boolean> { //解析表路径 const parsedTablePath = this.parseTablePath(tableOrPath); //获取列名 const columnName = column instanceof TableColumn ? column.name : column; //查询语句 const sql = `SELECT * FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = '${parsedTablePath.database}' AND TABLE_NAME = '${parsedTablePath.tableName}' AND COLUMN_NAME = '${columnName}'`; const result = await this.query(sql); return result.length ? true : false; } //创建数据库,如果不存在 createDatabase(database: string): Promise<void[]> { return this.query(`CREATE DATABASE IF NOT EXISTS ${database}`); // todo(dima): IT SHOULD NOT EXECUTE "IF NOT EXIST" if user already has a database (privileges issue) } //创建schema,mysql不支持 createSchema(schemas: string[]): Promise<void[]> { return Promise.resolve([]); } //创建一个新的表,参数为Table对象 async createTable(table: Table): Promise<void> { //拼接列定义字符串数组 const columnDefinitions = table.columns.map(column => this.buildCreateColumnSql(column, false)).join(", "); //建表sql,包含列定义,没有闭合括号 let sql = `CREATE TABLE \`${this.escapeTablePath(table)}\` (${columnDefinitions}`; //获取非自动生成主键列,自动生成主键列已经包含在列定义中了 const primaryKeyColumns = table.columns.filter(column => column.isPrimary && !column.isGenerated); if (primaryKeyColumns.length > 0) //拼接主键定义、引擎 sql += `, PRIMARY KEY(${primaryKeyColumns.map(column => `\`${column.name}\``).join(", ")})`; sql += `) ENGINE=${table.engine || "InnoDB"}`; //恢复sql,不知道什么作用 const revertSql = `DROP TABLE \`${this.escapeTablePath(table)}\``; return this.schemaQuery(sql, revertSql); } //删除指定表 async dropTable(tableOrPath: Table|string): Promise<void> { const sql = `DROP TABLE \`${this.escapeTablePath(tableOrPath)}\``; return this.query(sql); } //在指定表中添加列,这个也是DDL语句,可以被存储在内存中 async addColumn(tableOrPath: Table|string, column: TableColumn): Promise<void> { const sql = `ALTER TABLE \`${this.escapeTablePath(tableOrPath)}\` ADD ${this.buildCreateColumnSql(column, false)}`; const revertSql = `ALTER TABLE \`${this.escapeTablePath(tableOrPath)}\` DROP \`${column.name}\``; return this.schemaQuery(sql, revertSql); } //添加多个列 async addColumns(tableOrName: Table|string, columns: TableColumn[]): Promise<void> { const queries = columns.map(column => this.addColumn(tableOrName as any, column)); await Promise.all(queries); } //重命名指定表中指定列 async renameColumn(tableOrName: Table|string, oldTableColumnOrName: TableColumn|string, newTableColumnOrName: TableColumn|string): Promise<void> { let table: Table|undefined = undefined; //获取Table对象 if (tableOrName instanceof Table) { table = tableOrName; } else { table = await this.getTable(tableOrName); // todo: throw exception, this wont work because of sql memory enabled. remove support by table name if (!table) throw new Error(`Table ${tableOrName} was not found.`); } //获取更新前的TableColumn对象 let oldColumn: TableColumn|undefined = undefined; if (oldTableColumnOrName instanceof TableColumn) { oldColumn = oldTableColumnOrName; } else { oldColumn = table.columns.find(column => column.name === oldTableColumnOrName); } if (!oldColumn) throw new Error(`Column "${oldTableColumnOrName}" was not found in the "${tableOrName}" table.`); let newColumn: TableColumn|undefined = undefined; if (newTableColumnOrName instanceof TableColumn) { newColumn = newTableColumnOrName; } else { //如果只是指定名称,则克隆更新前列对象,设置新的列名 newColumn = oldColumn.clone(); newColumn.name = newTableColumnOrName; } //执行更改方法 return this.changeColumn(table, oldColumn, newColumn); } //更改表的指定列 async changeColumn(tableOrName: Table|string, oldTableColumnOrName: TableColumn|string, newColumn: TableColumn): Promise<void> { //有病吧,两个方法都来一遍? let table: Table|undefined = undefined; if (tableOrName instanceof Table) { table = tableOrName; } else { table = await this.getTable(tableOrName); } if (!table) throw new Error(`Table ${tableOrName} was not found.`); let oldColumn: TableColumn|undefined = undefined; if (oldTableColumnOrName instanceof TableColumn) { oldColumn = oldTableColumnOrName; } else { oldColumn = table.columns.find(column => column.name === oldTableColumnOrName); } if (!oldColumn) throw new Error(`Column "${oldTableColumnOrName}" was not found in the "${tableOrName}" table.`); //如果新列不是unique if (newColumn.isUnique === false && oldColumn.isUnique === true) //删除索引即可,注意这里bureturn,后面还要执行 await this.query(`ALTER TABLE \`${this.escapeTablePath(table)}\` DROP INDEX \`${oldColumn.name}\``); //使用alter table ... change ...语句 const sql = `ALTER TABLE \`${this.escapeTablePath(table)}\` CHANGE \`${oldColumn.name}\` ${this.buildCreateColumnSql(newColumn, oldColumn.isPrimary)}`; const revertSql = `ALTER TABLE \`${this.escapeTablePath(table)}\` CHANGE \`${oldColumn.name}\` ${this.buildCreateColumnSql(oldColumn, oldColumn.isPrimary)}`; //执行DDL语句 return this.schemaQuery(sql, revertSql); } //一次改变多个列 async changeColumns(table: Table, changedColumns: { newColumn: TableColumn, oldColumn: TableColumn }[]): Promise<void> { //注意这里遍历执行async方法没有使用foreach,而是是使用map,Promise.all const updatePromises = changedColumns.map(async changedColumn => { return this.changeColumn(table, changedColumn.oldColumn, changedColumn.newColumn); }); await Promise.all(updatePromises); } //删除表中指定列 async dropColumn(table: Table, column: TableColumn): Promise<void> { const sql = `ALTER TABLE \`${this.escapeTablePath(table)}\` DROP \`${column.name}\``; const revertSql = `ALTER TABLE \`${this.escapeTablePath(table)}\` ADD ${this.buildCreateColumnSql(column, false)}`; return this.schemaQuery(sql, revertSql); } //删除多个列 async dropColumns(table: Table, columns: TableColumn[]): Promise<void> { const dropPromises = columns.map(column => this.dropColumn(table, column)); await Promise.all(dropPromises); } //更新表的主键,这一步在什么情况下执行?,创建表时已经包含主键了 async updatePrimaryKeys(table: Table): Promise<void> { //如果表没有自动生成列 if (!table.hasGeneratedColumn) //只删除主键列 await this.query(`ALTER TABLE \`${this.escapeTablePath(table)}\` DROP PRIMARY KEY`); //获取非自动生成主键列 const primaryColumnNames = table.columns .filter(column => column.isPrimary && !column.isGenerated) .map(column => "`" + column.name + "`"); //添加主键列 if (primaryColumnNames.length > 0) { const sql = `ALTER TABLE \`${this.escapeTablePath(table)}\` ADD PRIMARY KEY (${primaryColumnNames.join(", ")})`; const revertSql = `ALTER TABLE \`${this.escapeTablePath(table)}\` DROP PRIMARY KEY`; return this.schemaQuery(sql, revertSql); } } //在指定表中创建外键列 async createForeignKey(tableOrPath: Table|string, foreignKey: TableForeignKey): Promise<void> { //拼接外键列名 const columnNames = foreignKey.columnNames.map(column => "`" + column + "`").join(", "); //拼接引用列名 const referencedColumnNames = foreignKey.referencedColumnNames.map(column => "`" + column + "`").join(","); //添加外键约束 let sql = `ALTER TABLE \`${this.escapeTablePath(tableOrPath)}\` ADD CONSTRAINT \`${foreignKey.name}\` ` + `FOREIGN KEY (${columnNames}) ` + //引用表名 `REFERENCES \`${foreignKey.referencedTableName}\`(${referencedColumnNames})`; //如果包含onDelete属性,拼接它 //on delete 可以决定当删除主表时如何处理从表记录 //"RESTRICT":如果从表引用了要删除的主表,则不允许删除 //"CASCADE":删除所有引用的从表记录 //"SET NULL":在从表中将外键列设为null,外键列为not null时会出问题 //"DEFAULT if (foreignKey.onDelete) sql += " ON DELETE " + foreignKey.onDelete; const revertSql = `ALTER TABLE \`${this.escapeTablePath(tableOrPath)}\` DROP FOREIGN KEY \`${foreignKey.name}\``; //执行DDL语句 return this.schemaQuery(sql, revertSql); } //创建多个外键列 async createForeignKeys(tableOrName: Table|string, foreignKeys: TableForeignKey[]): Promise<void> { const promises = foreignKeys.map(foreignKey => this.createForeignKey(tableOrName as any, foreignKey)); await Promise.all(promises); } //删除指定外键,只删除约束,不删除列 async dropForeignKey(tableOrPath: Table|string, foreignKey: TableForeignKey): Promise<void> { //删除外键约束 const sql = `ALTER TABLE \`${this.escapeTablePath(tableOrPath)}\` DROP FOREIGN KEY \`${foreignKey.name}\``; //外键列名 const columnNames = foreignKey.columnNames.map(column => "`" + column + "`").join(", "); //引用列名 const referencedColumnNames = foreignKey.referencedColumnNames.map(column => "`" + column + "`").join(","); //恢复sql let revertSql = `ALTER TABLE \`${this.escapeTablePath(tableOrPath)}\` ADD CONSTRAINT \`${foreignKey.name}\` ` + `FOREIGN KEY (${columnNames}) ` + `REFERENCES \`${foreignKey.referencedTableName}\`(${referencedColumnNames})`; if (foreignKey.onDelete) revertSql += " ON DELETE " + foreignKey.onDelete; return this.schemaQuery(sql, revertSql); } //删除多个外键列 async dropForeignKeys(tableOrName: Table|string, foreignKeys: TableForeignKey[]): Promise<void> { const promises = foreignKeys.map(foreignKey => this.dropForeignKey(tableOrName as any, foreignKey)); await Promise.all(promises); } //创建索引 async createIndex(table: Table|string, index: TableIndex): Promise<void> { //拼接索引包含的列名 const columns = index.columnNames.map(columnName => "`" + columnName + "`").join(", "); //索引可以设置是否为unique const sql = `CREATE ${index.isUnique ? "UNIQUE " : ""}INDEX \`${index.name}\` ON \`${this.escapeTablePath(table)}\`(${columns})`; //恢复sql const revertSql = `ALTER TABLE \`${this.escapeTablePath(table)}\` DROP INDEX \`${index.name}\``; //执行DDL await this.schemaQuery(sql, revertSql); } //删除索引 async dropIndex(tableOrPath: Table|string, index: TableIndex|string): Promise<void> { //索引名称 const indexName = index instanceof TableIndex ? index.name : index; const sql = `ALTER TABLE \`${this.escapeTablePath(tableOrPath)}\` DROP INDEX \`${indexName}\``; //这个用法有些不统一!毕竟schemaQuery里面有个缓存过程 if (index instanceof TableIndex) { const columns = index.columnNames.map(columnName => "`" + columnName + "`").join(", "); const revertSql = `CREATE ${index.isUnique ? "UNIQUE " : ""}INDEX \`${index.name}\` ON \`${this.escapeTablePath(tableOrPath)}\`(${columns})`; await this.schemaQuery(sql, revertSql); } else { await this.query(sql); } } //截断表 async truncate(tableOrPath: Table|string): Promise<void> { await this.query(`TRUNCATE TABLE \`${this.escapeTablePath(tableOrPath)}\``); } //从当前连接数据库中移除所有的表,这个方法根第一个参数有关系吗? async clearDatabase(tables?: string[], database?: string): Promise<void> { //开启事务 await this.startTransaction(); try { //关闭外键检查sql const disableForeignKeysCheckQuery = `SET FOREIGN_KEY_CHECKS = 0;`; //直接从数据库中查询出删除表的sql语句 const dropTablesQuery = `SELECT concat('DROP TABLE IF EXISTS \`', table_schema, '\`.\`', table_name, '\`;') AS query FROM information_schema.tables WHERE table_schema = '${database}'`; //启用外键检查sql const enableForeignKeysCheckQuery = `SET FOREIGN_KEY_CHECKS = 1;`; //关闭外键检查 await this.query(disableForeignKeysCheckQuery); //获取删除sql结果数组 const dropQueries: ObjectLiteral[] = await this.query(dropTablesQuery); //执行删除表sql await Promise.all(dropQueries.map(query => this.query(query["query"]))); //关闭外键检查 await this.query(enableForeignKeysCheckQuery); //提交事务 await this.commitTransaction(); } catch (error) { try { //出错,回滚 await this.rollbackTransaction(); } catch (rollbackError) { } throw error; } } //启用内存sql存储,一般只存储DDL语句 enableSqlMemory(): void { this.sqlMemoryMode = true; } //禁用内存sql存储 disableSqlMemory(): void { this.sqlsInMemory = []; this.sqlMemoryMode = false; } //获取内存存储sql getMemorySql(): (string|{ up: string, down: string })[] { return this.sqlsInMemory; } //执行结构sql,即DDL //upQuery:创建sql //downQuery:移除sql protected async schemaQuery(upQuery: string, downQuery: string): Promise<void> { //内存sql模式开启,则只存储sql,不执行,什么时候执行? if (this.sqlMemoryMode === true) { this.sqlsInMemory.push({ up: upQuery, down: downQuery }); return Promise.resolve() as Promise<any>; } //执行创建sql,没看出这个downQuery什么作用 await this.query(upQuery); } //解析表路径,返回个对象,包含数据库名、表名 protected parseTablePath(tableOrPath: Table|string) { if (tableOrPath instanceof Table) { return { database: tableOrPath.database || this.driver.database, tableName: tableOrPath.name }; } else { return { database: tableOrPath.indexOf(".") !== -1 ? tableOrPath.split(".")[0] : this.driver.database, tableName: tableOrPath.indexOf(".") !== -1 ? tableOrPath.split(".")[1] : tableOrPath }; } } //转义表路径,直接获取数据库名.表名 protected escapeTablePath(tableOrPath: Table|string): string { if (tableOrPath instanceof Table) return tableOrPath.database ? `${tableOrPath.database}\`.\`${tableOrPath.name}` : `${tableOrPath.name}`; return tableOrPath.split(".").map(i => `${i}`).join("\`.\`"); } //参数化给定对象的属性名,返回数组,字符串为key=?,包含占位符 protected parametrize(objectLiteral: ObjectLiteral): string[] { //获取一个数组,包含值占位符,在update中使用,也可以在where中使用 return Object.keys(objectLiteral).map(key => `\`${key}\`=?`); } //构建创建列的sql,第二个参数为是否跳过主键 protected buildCreateColumnSql(column: TableColumn, skipPrimary: boolean) { //列名+完整类型 let c = "`" + column.name + "` " + this.connection.driver.createFullType(column); //枚举值 if (column.enum) c += "(" + column.enum.map(value => "'" + value + "'").join(", ") + ")"; //列字符集 if (column.charset) c += " CHARACTER SET " + column.charset; //列校对 if (column.collation) c += " COLLATE " + column.collation; //not null if (column.isNullable !== true) c += " NOT NULL"; //unique if (column.isUnique === true) c += " UNIQUE"; //自动生成主键列,非自动生成主键在最后设置 if (column.isGenerated && column.isPrimary && !skipPrimary) c += " PRIMARY KEY"; //increment if (column.isGenerated === true && column.generationStrategy === "increment") // don't use skipPrimary here since updates can update already exist primary without auto inc. c += " AUTO_INCREMENT"; //列注解 if (column.comment) c += " COMMENT '" + column.comment + "'"; //列默认值 if (column.default !== undefined && column.default !== null) c += " DEFAULT " + column.default; return c; } }

 

QueryRunner为typeorm中执行数据库操作的最底层对象,所有的其他对象EntityManager、Repository、QueryBuilder都在内部使用了QueryRunner

QueryRunner内部调用了底层连接,如nodejs的mysql包中的连接对象,来执行操作,核心方法为query,其他所有方法DML、DDL都是通过query方法执行

QueryRunner中封装了很多DDL语句,如数据库、表、列、索引、外键等的创建、更改、删除操作

 

 

转载请注明原文地址: https://www.6miu.com/read-2630588.html

最新回复(0)