import { commonCustomKeywords, DataType, DbDialect, DialectInfo, DuplicateStrategy, EditorCompletion, EditorCompletionItem, IndexDefinition, RowDefinition, sqlColumnType, } from './index'; import { DbInst } from '@/views/ops/db/db'; import { language as sqlLanguage } from 'monaco-editor/esm/vs/basic-languages/sql/sql.js'; export { SqliteDialect }; // 参考官方文档:https://www.sqlite.org/datatype3.html const SQLITE_TYPE_LIST: sqlColumnType[] = [ // INTEGER { udtName: 'int', dataType: 'int', desc: '', space: '', range: '' }, { udtName: 'integer', dataType: 'integer', desc: '', space: '', range: '' }, { udtName: 'tinyint', dataType: 'tinyint', desc: '', space: '', range: '' }, { udtName: 'smallint', dataType: 'smallint', desc: '', space: '', range: '' }, { udtName: 'mediumint', dataType: 'mediumint', desc: '', space: '', range: '' }, { udtName: 'bigint', dataType: 'bigint', desc: '', space: '', range: '' }, { udtName: 'unsigned big int', dataType: 'unsigned big int', desc: '', space: '', range: '' }, { udtName: 'int2', dataType: 'int2', desc: '', space: '', range: '' }, { udtName: 'int8', dataType: 'int8', desc: '', space: '', range: '' }, // TEXT { udtName: 'character', dataType: 'character', desc: '', space: '', range: '' }, { udtName: 'varchar', dataType: 'varchar', desc: '', space: '', range: '' }, { udtName: 'varying character', dataType: 'varying character', desc: '', space: '', range: '' }, { udtName: 'nchar', dataType: 'nchar', desc: '', space: '', range: '' }, { udtName: 'native character', dataType: 'native character', desc: '', space: '', range: '' }, { udtName: 'nvarchar', dataType: 'nvarchar', desc: '', space: '', range: '' }, { udtName: 'text', dataType: 'text', desc: '', space: '', range: '' }, { udtName: 'clob', dataType: 'clob', desc: '', space: '', range: '' }, // blob { udtName: 'blob', dataType: 'blob', desc: '', space: '', range: '' }, { udtName: 'no datatype specified', dataType: 'no datatype specified', desc: '', space: '', range: '' }, // REAL { udtName: 'real', dataType: 'real', desc: '', space: '', range: '' }, { udtName: 'double', dataType: 'double', desc: '', space: '', range: '' }, { udtName: 'double precision', dataType: 'double precision', desc: '', space: '', range: '' }, { udtName: 'float', dataType: 'float', desc: '', space: '', range: '' }, // NUMERIC { udtName: 'numeric', dataType: 'numeric', desc: '', space: '', range: '' }, { udtName: 'decimal', dataType: 'decimal', desc: '', space: '', range: '' }, { udtName: 'boolean', dataType: 'boolean', desc: '', space: '', range: '' }, { udtName: 'date', dataType: 'date', desc: '', space: '', range: '' }, { udtName: 'datetime', dataType: 'datetime', desc: '', space: '', range: '' }, ]; const addCustomKeywords = ['PRAGMA', 'database_list', 'sqlite_master']; // 参考官方文档:https://www.sqlite.org/lang_corefunc.html const functions: EditorCompletionItem[] = [ // 字符函数 { label: 'abs', insertText: 'abs(X)', description: '返回给定数值的绝对值' }, { label: 'changes', insertText: 'changes()', description: '返回最近增删改影响的行数' }, { label: 'coalesce', insertText: 'coalesce(X,Y,...)', description: '返回第一个不为空的值' }, { label: 'hex', insertText: 'hex(X)', description: '返回给定字符的hex值' }, { label: 'ifnull', insertText: 'ifnull(X,Y)', description: '返回第一个不为空的值' }, { label: 'iif', insertText: 'iif(X,Y,Z)', description: '如果x为真则返回y,否则返回z' }, { label: 'instr', insertText: 'instr(X,Y)', description: '返回字符y在x的第n个位置' }, { label: 'length', insertText: 'length(X)', description: '返回给定字符的长度' }, { label: 'load_extension', insertText: 'load_extension(X[,Y])', description: '加载扩展块' }, { label: 'lower', insertText: 'lower(X)', description: '返回小写字符' }, { label: 'ltrim', insertText: 'ltrim(X[,Y])', description: '左trim' }, { label: 'nullif', insertText: 'nullif(X,Y)', description: '比较两值相等则返回null,否则返回第一个值' }, { label: 'printf', insertText: "printf('%s',...)", description: '字符串格式化拼接,如%s %d' }, { label: 'quote', insertText: 'quote(X)', description: '把字符串用引号包起来' }, { label: 'random', insertText: 'random()', description: '生成随机数' }, { label: 'randomblob', insertText: 'randomblob(N)', description: '生成一个包含N个随机字节的BLOB' }, { label: 'replace', insertText: 'replace(X,Y,Z)', description: '替换字符串' }, { label: 'round', insertText: 'round(X[,Y])', description: '将数值四舍五入到指定的小数位数' }, { label: 'rtrim', insertText: 'rtrim(X[,Y])', description: '右trim' }, { label: 'sign', insertText: 'sign(X)', description: '返回数字符号 1正 -1负 0零 null' }, { label: 'soundex', insertText: 'soundex(X)', description: '返回字符串X的soundex编码字符串' }, { label: 'sqlite_compileoption_get', insertText: 'sqlite_compileoption_get(N)', description: '获取指定编译选项的值' }, { label: 'sqlite_compileoption_used', insertText: 'sqlite_compileoption_used(X)', description: '检查SQLite编译时是否使用了指定的编译选项', }, { label: 'sqlite_source_id', insertText: 'sqlite_source_id()', description: '获取sqlite源代码标识符' }, { label: 'sqlite_version', insertText: 'sqlite_version()', description: '获取sqlite版本' }, { label: 'substr', insertText: 'substr(X,Y[,Z])', description: '截取字符串' }, { label: 'substring', insertText: 'substring(X,Y[,Z])', description: '截取字符串' }, { label: 'trim', insertText: 'trim(X[,Y])', description: '去除给定字符串前后的字符,默认空格' }, { label: 'typeof', insertText: 'typeof(X)', description: '返回X的基本类型:null,integer,real,text,blob' }, { label: 'unicode', insertText: 'unicode(X)', description: '返回与字符串X的第一个字符相对应的数字unicode代码点' }, { label: 'unlikely', insertText: 'unlikely(X)', description: '返回大写字符' }, { label: 'upper', insertText: 'upper(X)', description: '返回由0x00的N个字节组成的BLOB' }, { label: 'zeroblob', insertText: 'zeroblob(N)', description: '返回分组中的平均值' }, { label: 'avg', insertText: 'avg(X)', description: '返回总条数' }, { label: 'count', insertText: 'count(*)', description: '返回分组中用给定非空字符串连接的值' }, { label: 'group_concat', insertText: 'group_concat(X[,Y])', description: '返回分组中最大值' }, { label: 'max', insertText: 'max(X)', description: '返回分组中最小值' }, { label: 'min', insertText: 'min(X)', description: '返回分组中非空值的总和。' }, { label: 'sum', insertText: 'sum(X)', description: '返回分组中非空值的总和。' }, { label: 'total', insertText: 'total(X)', description: '返回YYYY-MM-DD格式的字符串' }, { label: 'date', insertText: 'date(time-value[, modifier, ...])', description: '返回HH:MM:SS格式的字符串' }, { label: 'time', insertText: 'time(time-value[, modifier, ...])', description: '将日期和时间字符串转换为特定的日期和时间格式', }, { label: 'datetime', insertText: 'datetime(time-value[, modifier, ...])', description: '计算日期和时间的儒略日数' }, { label: 'julianday', insertText: 'julianday(time-value[, modifier, ...])', description: '将日期和时间格式化为指定的字符串', }, ]; let sqliteDialectInfo: DialectInfo; class SqliteDialect implements DbDialect { getInfo(): DialectInfo { if (sqliteDialectInfo) { return sqliteDialectInfo; } let { keywords, operators, builtinVariables } = sqlLanguage; let editorCompletions: EditorCompletion = { keywords: keywords .filter((a: string) => addCustomKeywords.indexOf(a) === -1) .map((a: string): EditorCompletionItem => ({ label: a, description: 'keyword' })) .concat(commonCustomKeywords.map((a): EditorCompletionItem => ({ label: a, description: 'keyword' }))) .concat(addCustomKeywords.map((a): EditorCompletionItem => ({ label: a, description: 'keyword' }))), operators: operators.map((a: string): EditorCompletionItem => ({ label: a, description: 'operator' })), functions, variables: builtinVariables.map((a: string): EditorCompletionItem => ({ label: a, description: 'var' })), }; sqliteDialectInfo = { name: 'Sqlite3', icon: 'icon db/sqlite', defaultPort: 0, formatSqlDialect: 'sql', columnTypes: SQLITE_TYPE_LIST.sort((a, b) => a.udtName.localeCompare(b.udtName)), editorCompletions, }; return sqliteDialectInfo; } getDefaultSelectSql(db: string, table: string, condition: string, orderBy: string, pageNum: number, limit: number) { return `SELECT * FROM ${this.quoteIdentifier(table)} ${condition ? 'WHERE ' + condition : ''} ${orderBy ? orderBy : ''} ${this.getPageSql(pageNum, limit)};`; } getPageSql(pageNum: number, limit: number) { return ` LIMIT ${(pageNum - 1) * limit}, ${limit}`; } getDefaultRows(): RowDefinition[] { return [ { name: 'id', type: 'integer', length: '', numScale: '', value: '', notNull: true, pri: true, auto_increment: true, remark: '主键ID', }, { name: 'creator_id', type: 'bigint', length: '20', numScale: '', value: '', notNull: true, pri: false, auto_increment: false, remark: '创建人id', }, { name: 'creator', type: 'varchar', length: '100', numScale: '', value: '', notNull: true, pri: false, auto_increment: false, remark: '创建人姓名', }, { name: 'create_time', type: 'datetime', length: '', numScale: '', value: 'CURRENT_TIMESTAMP', notNull: true, pri: false, auto_increment: false, remark: '创建时间', }, { name: 'updator_id', type: 'bigint', length: '20', numScale: '', value: '', notNull: true, pri: false, auto_increment: false, remark: '修改人id', }, { name: 'updator', type: 'varchar', length: '100', numScale: '', value: '', notNull: true, pri: false, auto_increment: false, remark: '修改姓名', }, { name: 'update_time', type: 'datetime', length: '', numScale: '', value: 'CURRENT_TIMESTAMP', notNull: true, pri: false, auto_increment: false, remark: '修改时间', }, ]; } getDefaultIndex(): IndexDefinition { return { indexName: '', columnNames: [], unique: false, indexType: 'BTREE', indexComment: '', }; } quoteIdentifier = (name: string) => { return `\"${name}\"`; }; genColumnBasicSql(cl: any): string { let val = cl.value ? (cl.value === 'CURRENT_TIMESTAMP' ? cl.value : `'${cl.value}'`) : ''; let defVal = val ? `DEFAULT ${val}` : ''; let length = cl.length ? `(${cl.length})` : ''; let nullAble = cl.notNull ? 'NOT NULL' : 'NULL'; if (cl.pri) { return ` ${this.quoteIdentifier(cl.name)} ${cl.type}${length} PRIMARY KEY ${cl.auto_increment ? 'AUTOINCREMENT' : ''} ${nullAble} `; } return ` ${this.quoteIdentifier(cl.name)} ${cl.type}${length} ${nullAble} ${defVal} `; } getCreateTableSql(data: any): string { // 创建表结构 let fields: string[] = []; data.fields.res.forEach((item: any) => { item.name && fields.push(this.genColumnBasicSql(item)); }); return `CREATE TABLE ${this.quoteIdentifier(data.db)}.${this.quoteIdentifier(data.tableName)} ( ${fields.join(',')} )`; } getCreateIndexSql(data: any): string { // 创建索引 let sql = [] as string[]; data.indexs.res.forEach((a: any) => { sql.push( `CREATE ${a.unique ? 'UNIQUE' : ''} INDEX ${this.quoteIdentifier(data.db)} . ${this.quoteIdentifier(a.indexName)} ON "${data.tableName}" ( ${a.columnNames.join(',')} )` ); }); return sql.join(';'); } getModifyColumnSql( tableData: any, tableName: string, changeData: { del: RowDefinition[]; add: RowDefinition[]; upd: RowDefinition[]; } ): string { // sqlite修改表结构需要先删除再创建 // 1.删除旧表索引 DROP INDEX "main"."aa"; let sql = [] as string[]; tableData.indexs.res.forEach((a: any) => { a.indexName && sql.push(`DROP INDEX ${this.quoteIdentifier(tableData.db)}.${this.quoteIdentifier(a.indexName)}`); }); // 2.重命名表,备份旧表 ALTER TABLE "main"."t_sys_resource" RENAME TO "_t_sys_resource_old_20240118162712"; new Date().getTime() let oldTableName = `_${tableName}_old_${new Date().getTime()}`; sql.push(`ALTER TABLE ${this.quoteIdentifier(tableData.db)}.${this.quoteIdentifier(tableName)} RENAME TO ${this.quoteIdentifier(oldTableName)}`); // 3.创建新表 sql.push(this.getCreateTableSql(tableData)); // 4.复制数据 INSERT INTO "库名"."新表名" (${insertFields}) SELECT ${queryFields} FROM "库名"."旧表名"; // 查询的字段数据类型和数量应与插入的字段一致 // 判断哪些字段需要查询旧表,哪些字段需要插入新表 // 解析changeData,统计需要查询旧表的字段,统计需要插入新表的字段 let delFields = changeData.del.map((a) => a.name); let addFields = changeData.add.map((a) => a.name); let queryFields = [] as string[]; let insertFields = [] as string[]; tableData.fields.res.forEach((a: any) => { // 新增、删除的字段不需要查询旧表,不需要插入新表 if (addFields.includes(a.name) || delFields.includes(a.name)) { return; } // 修改的字段需要查询和插入,判断是否修改了字段名,如果修改了字段名,需要查询旧表原名,插入新表新名 // 其余未删除、未修改的字段,需要查询旧表,插入新表 queryFields.push(this.quoteIdentifier(a.name === a.oldName ? a.name : a.oldName)); insertFields.push(this.quoteIdentifier(a.name)); }); // 生成sql sql.push( `INSERT INTO ${this.quoteIdentifier(tableData.db)}.${this.quoteIdentifier(tableName)} (${insertFields.join(',')}) SELECT ${queryFields.join(',')} FROM ${this.quoteIdentifier(tableData.db)}.${this.quoteIdentifier(oldTableName)}` ); // 5.创建索引 tableData.indexs.res.forEach((a: any) => { a.indexName && sql.push( `CREATE ${a.unique ? 'UNIQUE' : ''} INDEX ${this.quoteIdentifier(tableData.db)} . ${this.quoteIdentifier(a.indexName)} ON "${tableName}" ( ${a.columnNames.join(',')} )` ); }); return sql.join(';') + ';'; } getModifyIndexSql(tableData: any, tableName: string, changeData: { del: any[]; add: any[]; upd: any[] }): string { // sqlite创建索引需要先删除再创建 // CREATE INDEX "main"."aa1" ON "t_sys_resource" ( "ui_path" ); let sql = [] as string[]; if (changeData.del.length > 0) { changeData.del.forEach((a) => { sql.push(`DROP INDEX ${this.quoteIdentifier(a.indexName)}`); }); } let indexData = [] as any[]; if (changeData.add.length > 0) { indexData = indexData.concat(changeData.add); } if (changeData.upd.length > 0) { indexData = indexData.concat(changeData.upd); } if (indexData.length > 0) { indexData.forEach((a) => { sql.push(`CREATE ${a.unique ? 'UNIQUE' : ''} INDEX ${this.quoteIdentifier(a.indexName)} ON ${tableName} ( ${a.columnNames.join(',')} )`); }); } return sql.join(';'); } getModifyTableInfoSql(tableData: any): string { let schemaArr = tableData.db.split('/'); let schema = schemaArr.length > 1 ? schemaArr[schemaArr.length - 1] : schemaArr[0]; // sqlite没有表注释 let sql = ''; if (tableData.tableName != tableData.oldTableName) { let dbTable = `${this.quoteIdentifier(schema)}.${this.quoteIdentifier(tableData.oldTableName)}`; sql += `ALTER TABLE ${dbTable} RENAME TO ${this.quoteIdentifier(tableData.tableName)}`; } return sql; } getDataType(columnType: string): DataType { if (DbInst.isNumber(columnType)) { return DataType.Number; } // 日期时间类型 if (/datetime|timestamp/gi.test(columnType)) { return DataType.DateTime; } // 日期类型 if (/date/gi.test(columnType)) { return DataType.Date; } // 时间类型 if (/time/gi.test(columnType)) { return DataType.Time; } return DataType.String; } wrapValue(columnType: string, value: any): any { if (value == null) { return 'NULL'; } if (DbInst.isNumber(columnType)) { return value; } return `'${value}'`; } getBatchInsertPreviewSql(tableName: string, fieldArr: string[], duplicateStrategy: DuplicateStrategy): string { let placeholder = '?'.repeat(fieldArr.length).split('').join(','); let prefix = 'insert into'; if (duplicateStrategy === DuplicateStrategy.IGNORE) { prefix = 'insert or ignore into'; } else if (duplicateStrategy === DuplicateStrategy.REPLACE) { prefix = 'insert or replace into'; } return `${prefix} ${this.quoteIdentifier(tableName)}(${fieldArr.join(',')}) values (${placeholder});`; } }