/* eslint-disable no-unused-vars */ import { dbApi } from './api'; import { getTextWidth } from '@/common/utils/string'; import SqlExecBox from './component/SqlExecBox'; const dbInstCache: Map = new Map(); export class DbInst { /** * 标签路径 */ tagPath: string; /** * 实例id */ id: number; /** * 实例名 */ name: string; /** * 数据库类型, mysql postgres */ type: string; /** * schema -> db */ dbs: Map = new Map(); /** 数据库schema,多个用空格隔开 */ databases: string; /** * 默认查询分页数量 */ static DefaultLimit = 20; /** * 获取指定数据库实例,若不存在则新建并缓存 * @param dbName 数据库名 * @returns db实例 */ getDb(dbName: string) { if (!dbName) { throw new Error('dbName不能为空'); } let db = this.dbs.get(dbName); if (db) { return db; } console.info(`new db -> dbId: ${this.id}, dbName: ${dbName}`); db = new Db(); db.name = dbName; this.dbs.set(dbName, db); return db; } /** * 加载数据库表信息 * @param dbName 数据库名 * @param reload 是否重新请求接口获取数据 * @returns 表信息 */ async loadTables(dbName: string, reload?: boolean) { const db = this.getDb(dbName); // 优先从 table map中获取 let tables = db.tables; if (!reload && tables) { return tables; } // 重置列信息缓存与表提示信息 db.columnsMap?.clear(); db.tableHints = null; console.log(`load tables -> dbName: ${dbName}`); tables = await dbApi.tableMetadata.request({ id: this.id, db: dbName }); db.tables = tables; return tables; } /** * 获取表的所有列信息 * @param table 表名 */ async loadColumns(dbName: string, table: string) { const db = this.getDb(dbName); // 优先从 table map中获取 let columns = db.getColumns(table); if (columns) { return columns; } console.log(`load columns -> dbName: ${dbName}, table: ${table}`); columns = await dbApi.columnMetadata.request({ id: this.id, db: dbName, tableName: table, }); db.columnsMap.set(table, columns); return columns; } /** * 获取指定表的指定信息 * @param table 表名 */ async loadTableColumn(dbName: string, table: string, columnName?: string) { // 确保该表的列信息都已加载 await this.loadColumns(dbName, table); return this.getDb(dbName).getColumn(table, columnName); } /** * 获取库信息提示 */ async loadDbHints(dbName: string) { const db = this.getDb(dbName); if (db.tableHints) { return db.tableHints; } console.log(`load db-hits -> dbName: ${dbName}`); const hits = await dbApi.hintTables.request({ id: this.id, db: db.name }); db.tableHints = hits; return hits; } /** * 执行sql * * @param sql sql * @param remark 执行备注 */ async runSql(dbName: string, sql: string, remark: string = '') { return await dbApi.sqlExec.request({ id: this.id, db: dbName, sql: sql.trim(), remark, }); } /** * 获取count sql * @param table 表名 * @param condition 条件 * @returns count sql */ getDefaultCountSql = (table: string, condition?: string) => { return `SELECT COUNT(*) count FROM ${this.wrapName(table)} ${condition ? 'WHERE ' + condition : ''} limit 1`; }; // 获取指定表的默认查询sql getDefaultSelectSql(table: string, condition: string, orderBy: string, pageNum: number, limit: number = DbInst.DefaultLimit) { const baseSql = `SELECT * FROM ${this.wrapName(table)} ${condition ? 'WHERE ' + condition : ''} ${orderBy ? orderBy : ''}`; if (this.type == 'mysql') { return `${baseSql} LIMIT ${(pageNum - 1) * limit}, ${limit};`; } if (this.type == 'postgres') { return `${baseSql} OFFSET ${(pageNum - 1) * limit} LIMIT ${limit};`; } return baseSql; } /** * 生成指定数据的insert语句 * @param dbName 数据库名 * @param table 表名 * @param datas 要生成的数据 */ genInsertSql(dbName: string, table: string, datas: any[]): string { if (!datas) { return ''; } const columns = this.getDb(dbName).getColumns(table); const sqls = []; for (let data of datas) { let colNames = []; let values = []; for (let column of columns) { const colName = column.columnName; colNames.push(this.wrapName(colName)); values.push(DbInst.wrapValueByType(data[colName])); } sqls.push(`INSERT INTO ${this.wrapName(table)} (${colNames.join(', ')}) VALUES(${values.join(', ')})`); } return sqls.join(';\n') + ';'; } /** * 生成根据主键删除的sql语句 * @param table 表名 * @param datas 要删除的记录 */ genDeleteByPrimaryKeysSql(db: string, table: string, datas: any[]) { const primaryKey = this.getDb(db).getColumn(table); const primaryKeyColumnName = primaryKey.columnName; const ids = datas.map((d: any) => `${DbInst.wrapColumnValue(primaryKey.columnType, d[primaryKeyColumnName])}`).join(','); return `DELETE FROM ${this.wrapName(table)} WHERE ${this.wrapName(primaryKeyColumnName)} IN (${ids})`; } /* * 弹框提示是否执行sql */ promptExeSql = (db: string, sql: string, cancelFunc: any = null, successFunc: any = null) => { SqlExecBox({ sql, dbId: this.id, db, runSuccessCallback: successFunc, cancelCallback: cancelFunc, }); }; /** * 包裹数据库表名、字段名等,避免使用关键字为字段名或表名时报错 * @param table * @param condition * @returns */ wrapName = (name: string) => { if (this.type == 'mysql') { return `\`${name}\``; } if (this.type == 'postgres') { return `"${name}"`; } return name; }; /** * 获取或新建dbInst,如果缓存中不存在则新建,否则直接返回 * @param inst 数据库实例,后端返回的列表接口中的信息 * @returns DbInst */ static getOrNewInst(inst: any) { if (!inst) { throw new Error('inst不能为空'); } let dbInst = dbInstCache.get(inst.id); if (dbInst) { return dbInst; } console.info(`new dbInst: ${inst.id}, tagPath: ${inst.tagPath}`); dbInst = new DbInst(); dbInst.tagPath = inst.tagPath; dbInst.id = inst.id; dbInst.name = inst.name; dbInst.type = inst.type; dbInst.databases = inst.databases; dbInstCache.set(dbInst.id, dbInst); return dbInst; } /** * 获取数据库实例id,若不存在,则新建一个并缓存 * @param dbId 数据库实例id * @param dbType 第一次获取时为必传项,即第一次创建时 * @returns 数据库实例 */ static getInst(dbId?: number): DbInst { if (!dbId) { throw new Error('dbId不能为空'); } let dbInst = dbInstCache.get(dbId); if (dbInst) { return dbInst; } throw new Error('dbInst不存在! 请在合适调用点使用DbInst.newInst()新建该实例'); } /** * 清空所有实例缓存信息 */ static clearAll() { dbInstCache.clear(); } /** * 根据返回值包装值,若值为字符串类型则添加'' * @param val 值 * @returns 包装后的值 */ static wrapValueByType = (val: any) => { if (val == null) { return 'NULL'; } if (typeof val == 'number') { return val; } return `'${val}'`; }; /** * 根据字段类型包装字段值,如为字符串等则添加‘’,数字类型则直接返回即可 */ static wrapColumnValue(columnType: string, value: any) { if (this.isNumber(columnType)) { return value; } return `'${value}'`; } /** * 判断字段类型是否为数字类型 * @param columnType 字段类型 * @returns */ static isNumber(columnType: string) { return columnType.match(/int|double|float|nubmer|decimal|byte|bit/gi); } /** * * @param str 字符串 * @param tableData 表数据 * @param flag 标志 * @returns 列宽度 */ static flexColumnWidth = (prop: any, tableData: any) => { if (!prop || !prop.length || prop.length === 0 || prop === undefined) { return; } // 获取列名称的长度 加上排序图标长度 const columnWidth: number = getTextWidth(prop) + 40; // prop为该列的字段名(传字符串);tableData为该表格的数据源(传变量); if (!tableData || !tableData.length || tableData.length === 0 || tableData === undefined) { return columnWidth; } // 获取该列中最长的数据(内容) let maxWidthText = ''; let maxWidthValue; // 获取该列中最长的数据(内容) for (let i = 0; i < tableData.length; i++) { let nowValue = tableData[i][prop]; if (!nowValue) { continue; } // 转为字符串比较长度 let nowText = nowValue + ''; if (nowText.length > maxWidthText.length) { maxWidthText = nowText; maxWidthValue = nowValue; } } const contentWidth: number = getTextWidth(maxWidthText) + 15; const flexWidth: number = contentWidth > columnWidth ? contentWidth : columnWidth; return flexWidth > 500 ? 500 : flexWidth; }; } /** * 数据库实例信息 */ class Db { name: string; // 库名 tables: []; // 数据库实例表信息 columnsMap: Map = new Map(); // table -> columns tableHints: any = null; // 提示词 /** * 获取指定表列信息(前提需要dbInst.loadColumns) * @param table 表名 */ getColumns(table: string) { return this.columnsMap.get(table); } /** * 获取指定表中的指定列名信息,若列名为空则默认返回主键 * @param table 表名 * @param columnName 列名 */ getColumn(table: string, columnName: string = '') { const cols = this.getColumns(table); if (!columnName) { const col = cols.find((c: any) => c.columnKey == 'PRI'); return col || cols[0]; } return cols.find((c: any) => c.columnName == columnName); } } export enum TabType { /** * 表数据 */ TableData, /** * 查询框 */ Query, } export class TabInfo { /** * tab唯一key。与label、name都一致 */ key: string; /** * 菜单树节点key */ treeNodeKey: string; /** * 数据库实例id */ dbId: number; /** * 库名 */ db: string = ''; /** * tab 类型 */ type: TabType; /** * tab需要的其他信息 */ params: any; getNowDbInst() { return DbInst.getInst(this.dbId); } getNowDb() { return this.getNowDbInst().getDb(this.db); } } /** 修改表字段所需数据 */ export type UpdateFieldsMeta = { // 主键值 primaryKey: string; // 主键名 primaryKeyName: string; // 主键类型 primaryKeyType: string; // 新值 fields: FieldsMeta[]; }; export type FieldsMeta = { // 字段所在div div: HTMLElement; // 字段名 fieldName: string; // 字段所在的表格行数据 row: any; // 字段类型 fieldType: string; // 原值 oldValue: string; // 新值 newValue: string; };