2023-02-13 21:11:16 +08:00
|
|
|
|
/* eslint-disable no-unused-vars */
|
|
|
|
|
|
import { dbApi } from './api';
|
2023-06-29 00:40:42 +08:00
|
|
|
|
import { getTextWidth } from '@/common/utils/string';
|
2023-11-12 20:14:44 +08:00
|
|
|
|
import SqlExecBox from './component/sqleditor/SqlExecBox';
|
2023-09-19 23:00:32 +08:00
|
|
|
|
import * as monaco from 'monaco-editor/esm/vs/editor/editor.api';
|
|
|
|
|
|
import { editor, languages, Position } from 'monaco-editor';
|
|
|
|
|
|
|
|
|
|
|
|
import { registerCompletionItemProvider } from '@/components/monaco/completionItemProvider';
|
2024-01-11 12:35:44 +08:00
|
|
|
|
import { DbDialect, EditorCompletionItem, getDbDialect } from './dialect';
|
2024-01-29 04:20:23 +00:00
|
|
|
|
import { type RemovableRef, useLocalStorage } from '@vueuse/core';
|
2024-05-16 17:26:32 +08:00
|
|
|
|
import { DbGetDbNamesMode } from './enums';
|
2024-01-29 04:20:23 +00:00
|
|
|
|
|
|
|
|
|
|
const hintsStorage: RemovableRef<Map<string, any>> = useLocalStorage('db-table-hints', new Map());
|
|
|
|
|
|
const tableStorage: RemovableRef<Map<string, any>> = useLocalStorage('db-tables', new Map());
|
2023-09-19 23:00:32 +08:00
|
|
|
|
|
2023-02-13 21:11:16 +08:00
|
|
|
|
const dbInstCache: Map<number, DbInst> = new Map();
|
|
|
|
|
|
|
|
|
|
|
|
export class DbInst {
|
2023-02-15 21:28:01 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 标签路径
|
|
|
|
|
|
*/
|
2023-07-06 20:59:22 +08:00
|
|
|
|
tagPath: string;
|
2023-02-15 21:28:01 +08:00
|
|
|
|
|
2023-02-13 21:11:16 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 实例id
|
|
|
|
|
|
*/
|
2023-07-06 20:59:22 +08:00
|
|
|
|
id: number;
|
2023-02-13 21:11:16 +08:00
|
|
|
|
|
2023-11-13 17:41:03 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* ip:port
|
|
|
|
|
|
*/
|
|
|
|
|
|
host: string;
|
|
|
|
|
|
|
2023-02-15 21:28:01 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 实例名
|
|
|
|
|
|
*/
|
2023-07-06 20:59:22 +08:00
|
|
|
|
name: string;
|
2023-02-15 21:28:01 +08:00
|
|
|
|
|
2023-02-13 21:11:16 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 数据库类型, mysql postgres
|
|
|
|
|
|
*/
|
2023-07-06 20:59:22 +08:00
|
|
|
|
type: string;
|
2023-02-13 21:11:16 +08:00
|
|
|
|
|
2024-02-29 22:12:50 +08:00
|
|
|
|
/**
|
2024-05-08 21:04:25 +08:00
|
|
|
|
* 流程定义,若存在则需要审批执行
|
2024-02-29 22:12:50 +08:00
|
|
|
|
*/
|
2024-05-08 21:04:25 +08:00
|
|
|
|
flowProcdef: any;
|
2024-02-29 22:12:50 +08:00
|
|
|
|
|
2023-02-13 21:11:16 +08:00
|
|
|
|
/**
|
2023-11-12 20:14:44 +08:00
|
|
|
|
* dbName -> db
|
2023-02-13 21:11:16 +08:00
|
|
|
|
*/
|
2023-07-06 20:59:22 +08:00
|
|
|
|
dbs: Map<string, Db> = new Map();
|
2023-02-13 21:11:16 +08:00
|
|
|
|
|
2023-11-12 20:14:44 +08:00
|
|
|
|
/** 数据库,多个用空格隔开 */
|
2023-12-12 17:53:24 +08:00
|
|
|
|
databases: string[];
|
2023-06-13 15:57:08 +08:00
|
|
|
|
|
2023-02-13 21:11:16 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 默认查询分页数量
|
|
|
|
|
|
*/
|
2023-11-20 12:21:27 +08:00
|
|
|
|
static DefaultLimit = 25;
|
2023-02-13 21:11:16 +08:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 获取指定数据库实例,若不存在则新建并缓存
|
|
|
|
|
|
* @param dbName 数据库名
|
|
|
|
|
|
* @returns db实例
|
|
|
|
|
|
*/
|
|
|
|
|
|
getDb(dbName: string) {
|
|
|
|
|
|
if (!dbName) {
|
2023-07-06 20:59:22 +08:00
|
|
|
|
throw new Error('dbName不能为空');
|
2023-02-13 21:11:16 +08:00
|
|
|
|
}
|
2024-01-29 04:20:23 +00:00
|
|
|
|
let key = `${this.id}_${dbName}`;
|
|
|
|
|
|
let db = this.dbs.get(key);
|
2023-02-13 21:11:16 +08:00
|
|
|
|
if (db) {
|
|
|
|
|
|
return db;
|
|
|
|
|
|
}
|
|
|
|
|
|
console.info(`new db -> dbId: ${this.id}, dbName: ${dbName}`);
|
|
|
|
|
|
db = new Db();
|
|
|
|
|
|
db.name = dbName;
|
2024-01-29 04:20:23 +00:00
|
|
|
|
this.dbs.set(key, db);
|
2023-02-13 21:11:16 +08:00
|
|
|
|
return db;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-01-31 20:41:41 +08:00
|
|
|
|
// 获取数据库实例方言
|
|
|
|
|
|
getDialect(): DbDialect {
|
|
|
|
|
|
return getDbDialect(this.type);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-02-13 21:11:16 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 加载数据库表信息
|
|
|
|
|
|
* @param dbName 数据库名
|
2023-03-28 15:22:05 +08:00
|
|
|
|
* @param reload 是否重新请求接口获取数据
|
2023-02-13 21:11:16 +08:00
|
|
|
|
* @returns 表信息
|
|
|
|
|
|
*/
|
2023-03-28 15:22:05 +08:00
|
|
|
|
async loadTables(dbName: string, reload?: boolean) {
|
2023-02-13 21:11:16 +08:00
|
|
|
|
const db = this.getDb(dbName);
|
2024-01-29 04:20:23 +00:00
|
|
|
|
let key = this.dbTablesKey(dbName);
|
|
|
|
|
|
let tables = tableStorage.value.get(key);
|
|
|
|
|
|
// 优先从 table 缓存中获取
|
2023-03-28 15:22:05 +08:00
|
|
|
|
if (!reload && tables) {
|
2024-01-29 04:20:23 +00:00
|
|
|
|
db.tables = tables;
|
2023-02-13 21:11:16 +08:00
|
|
|
|
return tables;
|
|
|
|
|
|
}
|
2023-04-05 22:41:53 +08:00
|
|
|
|
// 重置列信息缓存与表提示信息
|
|
|
|
|
|
db.columnsMap?.clear();
|
2023-02-13 21:11:16 +08:00
|
|
|
|
console.log(`load tables -> dbName: ${dbName}`);
|
2023-11-02 12:46:21 +08:00
|
|
|
|
tables = await dbApi.tableInfos.request({ id: this.id, db: dbName });
|
2024-01-29 04:20:23 +00:00
|
|
|
|
tableStorage.value.set(key, tables);
|
2023-02-13 21:11:16 +08:00
|
|
|
|
db.tables = tables;
|
2024-01-29 04:20:23 +00:00
|
|
|
|
|
|
|
|
|
|
// 异步加载表提示信息
|
|
|
|
|
|
this.loadDbHints(dbName, true).then(() => {});
|
2023-02-13 21:11:16 +08:00
|
|
|
|
return tables;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-01-08 11:24:37 +08:00
|
|
|
|
async loadTableSuggestions(dbDialect: DbDialect, dbName: string, range: any, reload?: boolean) {
|
2023-11-08 17:36:52 +08:00
|
|
|
|
const tables = await this.loadTables(dbName, reload);
|
|
|
|
|
|
// 表名联想
|
|
|
|
|
|
let suggestions: languages.CompletionItem[] = [];
|
|
|
|
|
|
tables?.forEach((tableMeta: any, index: any) => {
|
|
|
|
|
|
const { tableName, tableComment } = tableMeta;
|
|
|
|
|
|
suggestions.push({
|
|
|
|
|
|
label: {
|
|
|
|
|
|
label: tableName + ' - ' + tableComment,
|
|
|
|
|
|
description: 'table',
|
|
|
|
|
|
},
|
|
|
|
|
|
kind: monaco.languages.CompletionItemKind.File,
|
|
|
|
|
|
detail: tableComment,
|
2024-01-11 12:35:44 +08:00
|
|
|
|
insertText: dbDialect.quoteIdentifier(tableName) + ' ',
|
2023-11-08 17:36:52 +08:00
|
|
|
|
range,
|
|
|
|
|
|
sortText: 300 + index + '',
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
return { suggestions };
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** 加载列信息提示 */
|
2024-01-11 12:35:44 +08:00
|
|
|
|
async loadTableColumnSuggestions(dbDialect: DbDialect, db: string, tableName: string, range: any) {
|
2023-11-08 17:36:52 +08:00
|
|
|
|
let dbHits = await this.loadDbHints(db);
|
|
|
|
|
|
let columns = dbHits[tableName];
|
|
|
|
|
|
let suggestions: languages.CompletionItem[] = [];
|
|
|
|
|
|
columns?.forEach((a: string, index: any) => {
|
|
|
|
|
|
// 字段数据格式 字段名 字段注释, 如: create_time [datetime][创建时间]
|
|
|
|
|
|
const nameAndComment = a.split(' ');
|
|
|
|
|
|
const fieldName = nameAndComment[0];
|
|
|
|
|
|
suggestions.push({
|
|
|
|
|
|
label: {
|
|
|
|
|
|
label: a,
|
|
|
|
|
|
description: 'column',
|
|
|
|
|
|
},
|
|
|
|
|
|
kind: monaco.languages.CompletionItemKind.Property,
|
|
|
|
|
|
detail: '', // 不显示detail, 否则选中时备注等会被遮挡
|
2024-01-11 12:35:44 +08:00
|
|
|
|
insertText: dbDialect.quoteIdentifier(fieldName) + ' ', // create_time
|
2023-11-08 17:36:52 +08:00
|
|
|
|
range,
|
|
|
|
|
|
sortText: 100 + index + '', // 使用表字段声明顺序排序,排序需为字符串类型
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
return { suggestions };
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-02-13 21:11:16 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 获取表的所有列信息
|
2024-01-05 05:31:32 +00:00
|
|
|
|
* @param dbName 数据库名
|
2023-02-13 21:11:16 +08:00
|
|
|
|
* @param table 表名
|
|
|
|
|
|
*/
|
|
|
|
|
|
async loadColumns(dbName: string, table: string) {
|
|
|
|
|
|
const db = this.getDb(dbName);
|
|
|
|
|
|
// 优先从 table map中获取
|
|
|
|
|
|
let columns = db.getColumns(table);
|
2024-03-01 04:03:03 +00:00
|
|
|
|
if (columns && columns.length > 0) {
|
2023-02-13 21:11:16 +08:00
|
|
|
|
return columns;
|
|
|
|
|
|
}
|
|
|
|
|
|
console.log(`load columns -> dbName: ${dbName}, table: ${table}`);
|
|
|
|
|
|
columns = await dbApi.columnMetadata.request({
|
|
|
|
|
|
id: this.id,
|
|
|
|
|
|
db: dbName,
|
|
|
|
|
|
tableName: table,
|
|
|
|
|
|
});
|
2024-03-21 17:15:52 +08:00
|
|
|
|
|
|
|
|
|
|
DbInst.initColumns(columns);
|
|
|
|
|
|
|
2023-02-13 21:11:16 +08:00
|
|
|
|
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);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-01-29 04:20:23 +00:00
|
|
|
|
dbTableHintsKey(dbName: string) {
|
|
|
|
|
|
return `db-table-hints_${this.id}_${dbName}`;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
dbTablesKey(dbName: string) {
|
|
|
|
|
|
return `db-tables_${this.id}_${dbName}`;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-02-13 21:11:16 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 获取库信息提示
|
|
|
|
|
|
*/
|
2024-01-29 04:20:23 +00:00
|
|
|
|
async loadDbHints(dbName: string, reload?: boolean) {
|
2023-02-13 21:11:16 +08:00
|
|
|
|
const db = this.getDb(dbName);
|
2024-01-29 04:20:23 +00:00
|
|
|
|
let key = this.dbTableHintsKey(dbName);
|
|
|
|
|
|
let hints = hintsStorage.value.get(key);
|
|
|
|
|
|
if (!reload && hints) {
|
|
|
|
|
|
db.tableHints = hints;
|
|
|
|
|
|
return hints;
|
2023-02-13 21:11:16 +08:00
|
|
|
|
}
|
|
|
|
|
|
console.log(`load db-hits -> dbName: ${dbName}`);
|
2024-01-29 04:20:23 +00:00
|
|
|
|
hints = await dbApi.hintTables.request({ id: this.id, db: db.name });
|
|
|
|
|
|
db.tableHints = hints;
|
|
|
|
|
|
hintsStorage.value.set(key, hints);
|
|
|
|
|
|
return hints;
|
2023-02-13 21:11:16 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2023-07-06 20:59:22 +08:00
|
|
|
|
* 执行sql
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param sql sql
|
|
|
|
|
|
* @param remark 执行备注
|
|
|
|
|
|
*/
|
2023-12-11 01:00:09 +08:00
|
|
|
|
async runSql(dbName: string, sql: string, remark: string = '') {
|
2023-02-13 21:11:16 +08:00
|
|
|
|
return await dbApi.sqlExec.request({
|
|
|
|
|
|
id: this.id,
|
|
|
|
|
|
db: dbName,
|
|
|
|
|
|
sql: sql.trim(),
|
|
|
|
|
|
remark,
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-12-11 01:00:09 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 执行sql(可取消的)
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param sql sql
|
|
|
|
|
|
* @param remark 执行备注
|
|
|
|
|
|
*/
|
|
|
|
|
|
execSql(dbName: string, sql: string, remark: string = '') {
|
|
|
|
|
|
let dbId = this.id;
|
|
|
|
|
|
return dbApi.sqlExec.useApi({
|
|
|
|
|
|
id: dbId,
|
|
|
|
|
|
db: dbName,
|
|
|
|
|
|
sql: sql.trim(),
|
|
|
|
|
|
remark,
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-09-11 22:59:13 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 获取count sql
|
|
|
|
|
|
* @param table 表名
|
|
|
|
|
|
* @param condition 条件
|
|
|
|
|
|
* @returns count sql
|
|
|
|
|
|
*/
|
|
|
|
|
|
getDefaultCountSql = (table: string, condition?: string) => {
|
2024-01-15 11:55:59 +00:00
|
|
|
|
return `SELECT COUNT(*) count FROM ${this.wrapName(table)} ${condition ? 'WHERE ' + condition : ''}`;
|
2023-09-11 22:59:13 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
2023-02-13 21:11:16 +08:00
|
|
|
|
// 获取指定表的默认查询sql
|
2024-01-29 04:20:23 +00:00
|
|
|
|
getDefaultSelectSql(db: string, table: string, condition: string, orderBy: string, pageNum: number, limit: number = DbInst.DefaultLimit) {
|
2024-03-01 04:03:03 +00:00
|
|
|
|
return this.getDialect().getDefaultSelectSql(db, table, condition, orderBy, pageNum, limit);
|
2023-02-13 21:11:16 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 生成指定数据的insert语句
|
|
|
|
|
|
* @param dbName 数据库名
|
|
|
|
|
|
* @param table 表名
|
|
|
|
|
|
* @param datas 要生成的数据
|
2024-03-01 04:03:03 +00:00
|
|
|
|
* @param dbDialect db方言
|
|
|
|
|
|
* @param skipNull 是否跳过空字段
|
2023-02-13 21:11:16 +08:00
|
|
|
|
*/
|
2024-01-31 20:41:41 +08:00
|
|
|
|
async genInsertSql(dbName: string, table: string, datas: any[], skipNull = false) {
|
2023-02-13 21:11:16 +08:00
|
|
|
|
if (!datas) {
|
|
|
|
|
|
return '';
|
|
|
|
|
|
}
|
2024-03-01 04:03:03 +00:00
|
|
|
|
let schema = '';
|
|
|
|
|
|
let arr = dbName.split('/');
|
|
|
|
|
|
if (arr.length == 1) {
|
|
|
|
|
|
schema = this.wrapName(dbName) + '.';
|
|
|
|
|
|
} else if (arr.length == 2) {
|
|
|
|
|
|
schema = this.wrapName(arr[1]) + '.';
|
|
|
|
|
|
}
|
2023-11-17 13:31:28 +08:00
|
|
|
|
|
2024-03-01 04:03:03 +00:00
|
|
|
|
let dbDialect = this.getDialect();
|
2023-11-17 13:31:28 +08:00
|
|
|
|
const columns = await this.loadColumns(dbName, table);
|
2023-02-13 21:11:16 +08:00
|
|
|
|
const sqls = [];
|
|
|
|
|
|
for (let data of datas) {
|
|
|
|
|
|
let colNames = [];
|
|
|
|
|
|
let values = [];
|
|
|
|
|
|
for (let column of columns) {
|
|
|
|
|
|
const colName = column.columnName;
|
2024-01-31 20:41:41 +08:00
|
|
|
|
if (skipNull && data[colName] == null) {
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
2023-09-11 22:59:13 +08:00
|
|
|
|
colNames.push(this.wrapName(colName));
|
2024-03-01 04:03:03 +00:00
|
|
|
|
values.push(dbDialect.wrapValue(column.dataType, data[colName]));
|
2023-02-13 21:11:16 +08:00
|
|
|
|
}
|
2024-03-01 04:03:03 +00:00
|
|
|
|
sqls.push(`INSERT INTO ${schema}${this.wrapName(table)} (${colNames.join(', ')}) VALUES(${values.join(', ')})`);
|
2023-02-13 21:11:16 +08:00
|
|
|
|
}
|
2023-07-06 20:59:22 +08:00
|
|
|
|
return sqls.join(';\n') + ';';
|
2023-02-13 21:11:16 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-01-31 20:41:41 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 生成根据主键更新语句
|
|
|
|
|
|
* @param dbName 数据库名
|
|
|
|
|
|
* @param table 表名
|
|
|
|
|
|
* @param columnValue 要更新的列以及对应的值 field->columnName; value->columnValue
|
|
|
|
|
|
* @param rowData 表的一行完整数据(需要获取主键信息)
|
|
|
|
|
|
*/
|
|
|
|
|
|
async genUpdateSql(dbName: string, table: string, columnValue: {}, rowData: {}) {
|
|
|
|
|
|
let schema = '';
|
|
|
|
|
|
let dbArr = dbName.split('/');
|
|
|
|
|
|
if (dbArr.length == 2) {
|
|
|
|
|
|
schema = this.wrapName(dbArr[1]) + '.';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
let sql = `UPDATE ${schema}${this.wrapName(table)} SET `;
|
|
|
|
|
|
// 主键列信息
|
|
|
|
|
|
const primaryKey = await this.loadTableColumn(dbName, table);
|
2024-03-21 03:35:18 +00:00
|
|
|
|
let primaryKeyType = primaryKey.dataType;
|
2024-01-31 20:41:41 +08:00
|
|
|
|
let primaryKeyName = primaryKey.columnName;
|
|
|
|
|
|
let primaryKeyValue = rowData[primaryKeyName];
|
|
|
|
|
|
const dialect = this.getDialect();
|
|
|
|
|
|
for (let k of Object.keys(columnValue)) {
|
|
|
|
|
|
const v = columnValue[k];
|
|
|
|
|
|
// 更新字段列信息
|
|
|
|
|
|
const updateColumn = await this.loadTableColumn(dbName, table, k);
|
2024-03-21 03:35:18 +00:00
|
|
|
|
sql += ` ${this.wrapName(k)} = ${dialect.wrapValue(updateColumn.dataType, v)},`;
|
2024-01-31 20:41:41 +08:00
|
|
|
|
}
|
|
|
|
|
|
sql = sql.substring(0, sql.length - 1);
|
2024-03-01 04:03:03 +00:00
|
|
|
|
|
|
|
|
|
|
return sql + ` WHERE ${this.wrapName(primaryKeyName)} = ${this.getDialect().wrapValue(primaryKeyType, primaryKeyValue)} ;`;
|
2024-01-31 20:41:41 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2023-02-13 21:11:16 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 生成根据主键删除的sql语句
|
2024-03-01 04:03:03 +00:00
|
|
|
|
* @param db 数据库名
|
2023-02-13 21:11:16 +08:00
|
|
|
|
* @param table 表名
|
|
|
|
|
|
* @param datas 要删除的记录
|
|
|
|
|
|
*/
|
2023-11-17 13:31:28 +08:00
|
|
|
|
async genDeleteByPrimaryKeysSql(db: string, table: string, datas: any[]) {
|
|
|
|
|
|
const primaryKey = await this.loadTableColumn(db, table);
|
2023-02-13 21:11:16 +08:00
|
|
|
|
const primaryKeyColumnName = primaryKey.columnName;
|
2024-03-21 03:35:18 +00:00
|
|
|
|
const ids = datas.map((d: any) => `${this.getDialect().wrapValue(primaryKey.dataType, d[primaryKeyColumnName])}`).join(',');
|
2023-09-11 22:59:13 +08:00
|
|
|
|
return `DELETE FROM ${this.wrapName(table)} WHERE ${this.wrapName(primaryKeyColumnName)} IN (${ids})`;
|
2023-02-13 21:11:16 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2023-06-13 15:57:08 +08:00
|
|
|
|
/*
|
2023-07-06 20:59:22 +08:00
|
|
|
|
* 弹框提示是否执行sql
|
|
|
|
|
|
*/
|
2023-02-13 21:11:16 +08:00
|
|
|
|
promptExeSql = (db: string, sql: string, cancelFunc: any = null, successFunc: any = null) => {
|
|
|
|
|
|
SqlExecBox({
|
2023-07-06 20:59:22 +08:00
|
|
|
|
sql,
|
|
|
|
|
|
dbId: this.id,
|
|
|
|
|
|
db,
|
2024-01-31 20:41:41 +08:00
|
|
|
|
dbType: this.getDialect().getInfo().formatSqlDialect,
|
2023-02-13 21:11:16 +08:00
|
|
|
|
runSuccessCallback: successFunc,
|
|
|
|
|
|
cancelCallback: cancelFunc,
|
2024-05-08 21:04:25 +08:00
|
|
|
|
flowProcdef: this.flowProcdef,
|
2023-02-13 21:11:16 +08:00
|
|
|
|
});
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2023-09-11 22:59:13 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 包裹数据库表名、字段名等,避免使用关键字为字段名或表名时报错
|
2024-03-01 04:03:03 +00:00
|
|
|
|
* @param name 表名、字段名、schema名
|
|
|
|
|
|
* @returns 包裹后的字符串
|
2023-09-11 22:59:13 +08:00
|
|
|
|
*/
|
|
|
|
|
|
wrapName = (name: string) => {
|
2024-01-31 20:41:41 +08:00
|
|
|
|
return this.getDialect().quoteIdentifier(name);
|
2023-09-11 22:59:13 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
2023-02-15 21:28:01 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 获取或新建dbInst,如果缓存中不存在则新建,否则直接返回
|
|
|
|
|
|
* @param inst 数据库实例,后端返回的列表接口中的信息
|
|
|
|
|
|
* @returns DbInst
|
|
|
|
|
|
*/
|
|
|
|
|
|
static getOrNewInst(inst: any) {
|
|
|
|
|
|
if (!inst) {
|
2023-07-06 20:59:22 +08:00
|
|
|
|
throw new Error('inst不能为空');
|
2023-02-15 21:28:01 +08:00
|
|
|
|
}
|
|
|
|
|
|
let dbInst = dbInstCache.get(inst.id);
|
|
|
|
|
|
if (dbInst) {
|
2024-05-08 21:04:25 +08:00
|
|
|
|
// 更新可能更改的流程定义
|
|
|
|
|
|
if (inst.flowProcdef !== undefined) {
|
|
|
|
|
|
dbInst.flowProcdef = inst.flowProcdef;
|
|
|
|
|
|
dbInstCache.set(dbInst.id, dbInst);
|
|
|
|
|
|
}
|
2023-02-15 21:28:01 +08:00
|
|
|
|
return dbInst;
|
|
|
|
|
|
}
|
|
|
|
|
|
console.info(`new dbInst: ${inst.id}, tagPath: ${inst.tagPath}`);
|
|
|
|
|
|
dbInst = new DbInst();
|
|
|
|
|
|
dbInst.tagPath = inst.tagPath;
|
|
|
|
|
|
dbInst.id = inst.id;
|
2023-11-13 17:41:03 +08:00
|
|
|
|
dbInst.host = inst.host;
|
2023-02-15 21:28:01 +08:00
|
|
|
|
dbInst.name = inst.name;
|
|
|
|
|
|
dbInst.type = inst.type;
|
2023-06-13 15:57:08 +08:00
|
|
|
|
dbInst.databases = inst.databases;
|
2024-05-08 21:04:25 +08:00
|
|
|
|
dbInst.flowProcdef = inst.flowProcdef;
|
2023-02-15 21:28:01 +08:00
|
|
|
|
|
|
|
|
|
|
dbInstCache.set(dbInst.id, dbInst);
|
|
|
|
|
|
return dbInst;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-02-13 21:11:16 +08:00
|
|
|
|
/**
|
2023-07-06 20:59:22 +08:00
|
|
|
|
* 获取数据库实例id,若不存在,则新建一个并缓存
|
|
|
|
|
|
* @param dbId 数据库实例id
|
|
|
|
|
|
* @param dbType 第一次获取时为必传项,即第一次创建时
|
|
|
|
|
|
* @returns 数据库实例
|
|
|
|
|
|
*/
|
2023-02-15 21:28:01 +08:00
|
|
|
|
static getInst(dbId?: number): DbInst {
|
|
|
|
|
|
if (!dbId) {
|
|
|
|
|
|
throw new Error('dbId不能为空');
|
|
|
|
|
|
}
|
2023-02-13 21:11:16 +08:00
|
|
|
|
let dbInst = dbInstCache.get(dbId);
|
|
|
|
|
|
if (dbInst) {
|
|
|
|
|
|
return dbInst;
|
|
|
|
|
|
}
|
2023-02-15 21:28:01 +08:00
|
|
|
|
throw new Error('dbInst不存在! 请在合适调用点使用DbInst.newInst()新建该实例');
|
2023-02-13 21:11:16 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 清空所有实例缓存信息
|
|
|
|
|
|
*/
|
|
|
|
|
|
static clearAll() {
|
|
|
|
|
|
dbInstCache.clear();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-04-13 20:11:22 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 判断字段类型是否为数字类型
|
|
|
|
|
|
* @param columnType 字段类型
|
2023-06-13 15:57:08 +08:00
|
|
|
|
* @returns
|
2023-04-13 20:11:22 +08:00
|
|
|
|
*/
|
|
|
|
|
|
static isNumber(columnType: string) {
|
2024-03-15 09:01:51 +00:00
|
|
|
|
return columnType && columnType.match(/int|double|float|number|decimal|byte|bit/gi);
|
2023-07-06 20:59:22 +08:00
|
|
|
|
}
|
2023-04-13 20:11:22 +08:00
|
|
|
|
|
2023-02-13 21:11:16 +08:00
|
|
|
|
/**
|
2023-06-13 15:57:08 +08:00
|
|
|
|
*
|
2023-02-13 21:11:16 +08:00
|
|
|
|
* @param str 字符串
|
|
|
|
|
|
* @param tableData 表数据
|
|
|
|
|
|
* @param flag 标志
|
|
|
|
|
|
* @returns 列宽度
|
|
|
|
|
|
*/
|
2023-06-29 11:49:14 +08:00
|
|
|
|
static flexColumnWidth = (prop: any, tableData: any) => {
|
|
|
|
|
|
if (!prop || !prop.length || prop.length === 0 || prop === undefined) {
|
2023-02-13 21:11:16 +08:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
2023-06-29 11:49:14 +08:00
|
|
|
|
|
2024-05-31 12:12:40 +08:00
|
|
|
|
// 获取列名称的长度 加上排序图标长度、abc为字段类型简称占位符、排序图标等
|
|
|
|
|
|
const columnWidth: number = getTextWidth(prop + 'abc') + 10;
|
2023-06-29 11:49:14 +08:00
|
|
|
|
// prop为该列的字段名(传字符串);tableData为该表格的数据源(传变量);
|
|
|
|
|
|
if (!tableData || !tableData.length || tableData.length === 0 || tableData === undefined) {
|
|
|
|
|
|
return columnWidth;
|
2023-02-13 21:11:16 +08:00
|
|
|
|
}
|
2023-06-29 11:49:14 +08:00
|
|
|
|
|
|
|
|
|
|
// 获取该列中最长的数据(内容)
|
2023-07-06 20:59:22 +08:00
|
|
|
|
let maxWidthText = '';
|
2023-06-29 00:40:42 +08:00
|
|
|
|
// 获取该列中最长的数据(内容)
|
|
|
|
|
|
for (let i = 0; i < tableData.length; i++) {
|
2023-07-06 20:59:22 +08:00
|
|
|
|
let nowValue = tableData[i][prop];
|
2023-06-29 11:49:14 +08:00
|
|
|
|
if (!nowValue) {
|
2023-06-29 00:40:42 +08:00
|
|
|
|
continue;
|
2023-02-13 21:11:16 +08:00
|
|
|
|
}
|
2023-06-29 11:49:14 +08:00
|
|
|
|
// 转为字符串比较长度
|
2023-07-06 20:59:22 +08:00
|
|
|
|
let nowText = nowValue + '';
|
2023-06-29 11:49:14 +08:00
|
|
|
|
if (nowText.length > maxWidthText.length) {
|
|
|
|
|
|
maxWidthText = nowText;
|
2023-02-13 21:11:16 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2024-05-31 12:12:40 +08:00
|
|
|
|
const contentWidth: number = getTextWidth(maxWidthText) + 3;
|
2023-02-13 21:11:16 +08:00
|
|
|
|
const flexWidth: number = contentWidth > columnWidth ? contentWidth : columnWidth;
|
2023-06-29 00:40:42 +08:00
|
|
|
|
return flexWidth > 500 ? 500 : flexWidth;
|
2023-02-13 21:11:16 +08:00
|
|
|
|
};
|
2024-03-21 17:15:52 +08:00
|
|
|
|
|
|
|
|
|
|
// 初始化所有列信息,完善需要显示的列类型,包含长度等,如varchar(20)
|
|
|
|
|
|
static initColumns(columns: any[]) {
|
2024-03-26 09:05:28 +00:00
|
|
|
|
if (!columns) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2024-03-21 17:15:52 +08:00
|
|
|
|
for (let col of columns) {
|
|
|
|
|
|
if (col.charMaxLength > 0) {
|
2024-05-10 19:59:49 +08:00
|
|
|
|
col.columnType = `${col.dataType}(${col.charMaxLength})`;
|
2024-03-21 17:15:52 +08:00
|
|
|
|
col.showLength = col.charMaxLength;
|
|
|
|
|
|
col.showScale = null;
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (col.numPrecision > 0) {
|
|
|
|
|
|
if (col.numScale > 0) {
|
2024-05-10 19:59:49 +08:00
|
|
|
|
col.columnType = `${col.dataType}(${col.numPrecision},${col.numScale})`;
|
2024-03-21 17:15:52 +08:00
|
|
|
|
col.showScale = col.numScale;
|
|
|
|
|
|
} else {
|
2024-05-10 19:59:49 +08:00
|
|
|
|
col.columnType = `${col.dataType}(${col.numPrecision})`;
|
2024-03-21 17:15:52 +08:00
|
|
|
|
col.showScale = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
col.showLength = col.numPrecision;
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-05-10 19:59:49 +08:00
|
|
|
|
col.columnType = col.dataType;
|
2024-03-21 17:15:52 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2024-05-16 17:26:32 +08:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 根据数据库配置信息获取对应的库名列表
|
|
|
|
|
|
* @param db db配置信息
|
|
|
|
|
|
* @returns 库名列表
|
|
|
|
|
|
*/
|
|
|
|
|
|
static async getDbNames(db: any) {
|
|
|
|
|
|
if (db.getDatabaseMode == DbGetDbNamesMode.Assign.value) {
|
|
|
|
|
|
return db.database.split(' ');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return await dbApi.getDbNamesByAc.request({ authCertName: db.authCertName });
|
|
|
|
|
|
}
|
2023-02-13 21:11:16 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 数据库实例信息
|
|
|
|
|
|
*/
|
|
|
|
|
|
class Db {
|
2023-07-06 20:59:22 +08:00
|
|
|
|
name: string; // 库名
|
|
|
|
|
|
tables: []; // 数据库实例表信息
|
|
|
|
|
|
columnsMap: Map<string, any> = new Map(); // table -> columns
|
|
|
|
|
|
tableHints: any = null; // 提示词
|
2023-02-13 21:11:16 +08:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 获取指定表列信息(前提需要dbInst.loadColumns)
|
|
|
|
|
|
* @param table 表名
|
|
|
|
|
|
*/
|
|
|
|
|
|
getColumns(table: string) {
|
|
|
|
|
|
return this.columnsMap.get(table);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2023-07-06 20:59:22 +08:00
|
|
|
|
* 获取指定表中的指定列名信息,若列名为空则默认返回主键
|
|
|
|
|
|
* @param table 表名
|
|
|
|
|
|
* @param columnName 列名
|
|
|
|
|
|
*/
|
2023-02-13 21:11:16 +08:00
|
|
|
|
getColumn(table: string, columnName: string = '') {
|
|
|
|
|
|
const cols = this.getColumns(table);
|
|
|
|
|
|
if (!columnName) {
|
2024-01-29 04:20:23 +00:00
|
|
|
|
const col = cols.find((c: any) => c.isPrimaryKey);
|
2023-02-13 21:11:16 +08:00
|
|
|
|
return col || cols[0];
|
|
|
|
|
|
}
|
|
|
|
|
|
return cols.find((c: any) => c.columnName == columnName);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export enum TabType {
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 表数据
|
|
|
|
|
|
*/
|
|
|
|
|
|
TableData,
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 查询框
|
|
|
|
|
|
*/
|
|
|
|
|
|
Query,
|
2023-11-12 20:14:44 +08:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 表操作
|
|
|
|
|
|
*/
|
|
|
|
|
|
TablesOp,
|
2023-02-13 21:11:16 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export class TabInfo {
|
2023-11-12 20:14:44 +08:00
|
|
|
|
label: string;
|
|
|
|
|
|
|
2023-02-13 21:11:16 +08:00
|
|
|
|
/**
|
2023-11-12 20:14:44 +08:00
|
|
|
|
* tab唯一key。与name都一致
|
2023-02-13 21:11:16 +08:00
|
|
|
|
*/
|
2023-07-06 20:59:22 +08:00
|
|
|
|
key: string;
|
2023-02-13 21:11:16 +08:00
|
|
|
|
|
2023-02-18 23:02:14 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 菜单树节点key
|
|
|
|
|
|
*/
|
2023-07-06 20:59:22 +08:00
|
|
|
|
treeNodeKey: string;
|
2023-02-18 23:02:14 +08:00
|
|
|
|
|
2023-02-13 21:11:16 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 数据库实例id
|
|
|
|
|
|
*/
|
2023-07-06 20:59:22 +08:00
|
|
|
|
dbId: number;
|
2023-02-13 21:11:16 +08:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 库名
|
|
|
|
|
|
*/
|
2023-07-06 20:59:22 +08:00
|
|
|
|
db: string = '';
|
2023-02-13 21:11:16 +08:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* tab 类型
|
|
|
|
|
|
*/
|
2023-07-06 20:59:22 +08:00
|
|
|
|
type: TabType;
|
2023-02-13 21:11:16 +08:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* tab需要的其他信息
|
|
|
|
|
|
*/
|
2023-07-06 20:59:22 +08:00
|
|
|
|
params: any;
|
2023-02-13 21:11:16 +08:00
|
|
|
|
|
2024-05-31 12:12:40 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 组件ref
|
|
|
|
|
|
*/
|
|
|
|
|
|
componentRef: any;
|
|
|
|
|
|
|
2023-02-13 21:11:16 +08:00
|
|
|
|
getNowDbInst() {
|
2023-02-15 21:28:01 +08:00
|
|
|
|
return DbInst.getInst(this.dbId);
|
2023-02-13 21:11:16 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
getNowDb() {
|
|
|
|
|
|
return this.getNowDbInst().getDb(this.db);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-12-12 12:41:44 +08:00
|
|
|
|
function registerCompletions(
|
|
|
|
|
|
completions: EditorCompletionItem[],
|
|
|
|
|
|
suggestions: languages.CompletionItem[],
|
|
|
|
|
|
kind: monaco.languages.CompletionItemKind,
|
|
|
|
|
|
range: any
|
|
|
|
|
|
) {
|
|
|
|
|
|
// mysql关键字
|
|
|
|
|
|
completions.forEach((item: EditorCompletionItem) => {
|
|
|
|
|
|
let { label, insertText, description } = item;
|
|
|
|
|
|
suggestions.push({
|
|
|
|
|
|
label: { label, description },
|
|
|
|
|
|
kind,
|
|
|
|
|
|
insertText: insertText || label,
|
|
|
|
|
|
range,
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-09-19 23:00:32 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 注册数据库表、字段等信息提示
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param dbId 数据库id
|
|
|
|
|
|
* @param db 库名
|
|
|
|
|
|
* @param dbs 该库所有库名
|
2023-12-12 12:41:44 +08:00
|
|
|
|
* @param dbType 数据库类型
|
2023-09-19 23:00:32 +08:00
|
|
|
|
*/
|
2023-12-12 12:41:44 +08:00
|
|
|
|
export function registerDbCompletionItemProvider(dbId: number, db: string, dbs: any[] = [], dbType: string) {
|
|
|
|
|
|
let dbDialect = getDbDialect(dbType);
|
|
|
|
|
|
let dbDialectInfo = dbDialect.getInfo();
|
|
|
|
|
|
let { keywords, operators, functions, variables } = dbDialectInfo.editorCompletions;
|
|
|
|
|
|
registerCompletionItemProvider('sql', {
|
2023-09-19 23:00:32 +08:00
|
|
|
|
triggerCharacters: ['.', ' '],
|
|
|
|
|
|
provideCompletionItems: async (model: editor.ITextModel, position: Position): Promise<languages.CompletionList | null | undefined> => {
|
|
|
|
|
|
let word = model.getWordUntilPosition(position);
|
|
|
|
|
|
const dbInst = DbInst.getInst(dbId);
|
|
|
|
|
|
const { lineNumber, column } = position;
|
|
|
|
|
|
const { startColumn, endColumn } = word;
|
|
|
|
|
|
|
|
|
|
|
|
// 当前行文本
|
|
|
|
|
|
let lineContent = model.getLineContent(lineNumber);
|
|
|
|
|
|
// 注释行不需要代码提示
|
|
|
|
|
|
if (lineContent.startsWith('--')) {
|
|
|
|
|
|
return { suggestions: [] };
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
let range = {
|
|
|
|
|
|
startLineNumber: lineNumber,
|
|
|
|
|
|
endLineNumber: lineNumber,
|
|
|
|
|
|
startColumn,
|
|
|
|
|
|
endColumn,
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 光标前文本
|
|
|
|
|
|
const textBeforePointer = model.getValueInRange({
|
|
|
|
|
|
startLineNumber: lineNumber,
|
|
|
|
|
|
startColumn: 0,
|
|
|
|
|
|
endLineNumber: lineNumber,
|
|
|
|
|
|
endColumn: column,
|
|
|
|
|
|
});
|
2023-09-20 20:42:23 +08:00
|
|
|
|
// // const nextTokens = textAfterPointer.trim().split(/\s+/)
|
|
|
|
|
|
// // const nextToken = nextTokens[0].toLowerCase()
|
|
|
|
|
|
const tokens = textBeforePointer.trim().split(/\s+/);
|
|
|
|
|
|
let lastToken = tokens[tokens.length - 1].toLowerCase();
|
|
|
|
|
|
const secondToken = (tokens.length > 2 && tokens[tokens.length - 2].toLowerCase()) || '';
|
|
|
|
|
|
|
|
|
|
|
|
// 获取光标所在行之前的所有文本内容
|
|
|
|
|
|
const textBeforeCursor = model.getValueInRange({
|
2023-09-19 23:00:32 +08:00
|
|
|
|
startLineNumber: 1,
|
|
|
|
|
|
startColumn: 0,
|
|
|
|
|
|
endLineNumber: lineNumber,
|
|
|
|
|
|
endColumn: column,
|
|
|
|
|
|
});
|
2023-09-20 20:42:23 +08:00
|
|
|
|
|
|
|
|
|
|
// 获取光标所在行之后的所有文本内容
|
|
|
|
|
|
const textAfterCursor = model.getValueInRange({
|
2023-09-19 23:00:32 +08:00
|
|
|
|
startLineNumber: lineNumber,
|
|
|
|
|
|
startColumn: column,
|
|
|
|
|
|
endLineNumber: model.getLineCount(),
|
|
|
|
|
|
endColumn: model.getLineMaxColumn(model.getLineCount()),
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2023-09-20 20:42:23 +08:00
|
|
|
|
// 检测光标前后文本中的分号位置,确定完整 SQL 语句的范围
|
|
|
|
|
|
const start = textBeforeCursor.lastIndexOf(';');
|
|
|
|
|
|
const end = textAfterCursor.indexOf(';');
|
2023-09-19 23:00:32 +08:00
|
|
|
|
|
2023-09-20 20:42:23 +08:00
|
|
|
|
let sqlStatement = '';
|
|
|
|
|
|
// 如果光标前后都有分号,则取二者之间的文本作为完整 SQL 语句
|
|
|
|
|
|
if (start !== -1 && end !== -1) {
|
|
|
|
|
|
sqlStatement = textBeforeCursor.substring(start + 1) + textAfterCursor.substring(0, end);
|
|
|
|
|
|
}
|
|
|
|
|
|
// 如果只有光标前面有分号,则取分号后的文本作为完整 SQL 语句
|
|
|
|
|
|
else if (start !== -1) {
|
|
|
|
|
|
sqlStatement = textBeforeCursor.substring(start + 1) + textAfterCursor;
|
|
|
|
|
|
}
|
|
|
|
|
|
// 如果只有光标后面有分号,则取分号前的文本作为完整 SQL 语句
|
|
|
|
|
|
else if (end !== -1) {
|
|
|
|
|
|
sqlStatement = textBeforeCursor + textAfterCursor.substring(0, end);
|
|
|
|
|
|
}
|
|
|
|
|
|
// 如果光标前后都没有分号,则取整个文本作为完整 SQL 语句
|
|
|
|
|
|
else {
|
|
|
|
|
|
sqlStatement = textBeforeCursor + textAfterCursor;
|
|
|
|
|
|
}
|
2023-09-19 23:00:32 +08:00
|
|
|
|
|
2023-11-08 17:36:52 +08:00
|
|
|
|
let suggestions: languages.CompletionItem[] = [];
|
|
|
|
|
|
|
|
|
|
|
|
// 库名提示
|
|
|
|
|
|
if (dbs && dbs.length > 0) {
|
|
|
|
|
|
dbs.forEach((a: any) => {
|
2023-09-19 23:00:32 +08:00
|
|
|
|
suggestions.push({
|
|
|
|
|
|
label: {
|
|
|
|
|
|
label: a,
|
2023-11-08 17:36:52 +08:00
|
|
|
|
description: 'schema',
|
2023-09-19 23:00:32 +08:00
|
|
|
|
},
|
2023-11-08 17:36:52 +08:00
|
|
|
|
kind: monaco.languages.CompletionItemKind.Folder,
|
2024-01-11 12:35:44 +08:00
|
|
|
|
insertText: dbDialect.quoteIdentifier(a),
|
2023-09-19 23:00:32 +08:00
|
|
|
|
range,
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
2023-11-08 17:36:52 +08:00
|
|
|
|
}
|
2023-09-19 23:00:32 +08:00
|
|
|
|
|
2023-11-08 17:36:52 +08:00
|
|
|
|
let alias = '';
|
|
|
|
|
|
if (lastToken.indexOf('.') > -1 || secondToken.indexOf('.') > -1) {
|
|
|
|
|
|
// 如果是.触发代码提示,则进行【 库.表名联想 】 或 【 表别名.表字段联想 】
|
|
|
|
|
|
alias = lastToken.substring(0, lastToken.lastIndexOf('.'));
|
|
|
|
|
|
if (lastToken.trim().startsWith('.')) {
|
|
|
|
|
|
alias = secondToken;
|
|
|
|
|
|
}
|
2023-12-12 17:53:24 +08:00
|
|
|
|
if (!alias && secondToken.indexOf('.') > -1) {
|
|
|
|
|
|
alias = secondToken.substring(secondToken.indexOf('.') + 1);
|
|
|
|
|
|
}
|
2023-11-08 17:36:52 +08:00
|
|
|
|
|
|
|
|
|
|
// 如果字符串粘连起了如:'a.creator,a.',需要重新取出别名
|
|
|
|
|
|
let aliasArr = lastToken.split(',');
|
|
|
|
|
|
if (aliasArr.length > 1) {
|
|
|
|
|
|
lastToken = aliasArr[aliasArr.length - 1];
|
|
|
|
|
|
alias = lastToken.substring(0, lastToken.lastIndexOf('.'));
|
|
|
|
|
|
if (lastToken.trim().startsWith('.')) {
|
|
|
|
|
|
alias = secondToken;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 如果是【库.表名联想】.前的字符串是库名
|
2023-12-12 17:53:24 +08:00
|
|
|
|
if (dbs?.filter((a) => alias === a?.toLowerCase()).length > 0) {
|
|
|
|
|
|
let dbName = alias;
|
|
|
|
|
|
if (db.indexOf('/') > 0) {
|
|
|
|
|
|
dbName = db.substring(0, db.indexOf('/') + 1) + alias;
|
|
|
|
|
|
}
|
2024-01-08 11:24:37 +08:00
|
|
|
|
return await dbInst.loadTableSuggestions(dbDialect, dbName, range);
|
2023-11-08 17:36:52 +08:00
|
|
|
|
}
|
|
|
|
|
|
// 表下列名联想 .前的字符串是表名或表别名
|
|
|
|
|
|
const sqlInfo = getTableName4SqlCtx(sqlStatement, alias, db);
|
|
|
|
|
|
// 提出到表名,则将表对应的字段也添加进提示建议
|
|
|
|
|
|
if (sqlInfo) {
|
2024-01-08 11:24:37 +08:00
|
|
|
|
return await dbInst.loadTableColumnSuggestions(dbDialect, sqlInfo.db, sqlInfo.tableName, range);
|
2023-09-19 23:00:32 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-11-08 17:36:52 +08:00
|
|
|
|
// 空格触发也会提示字段信息
|
|
|
|
|
|
const sqlInfo = getTableName4SqlCtx(sqlStatement, alias, db);
|
|
|
|
|
|
if (sqlInfo) {
|
2024-01-08 11:24:37 +08:00
|
|
|
|
const columnSuggestions = await dbInst.loadTableColumnSuggestions(dbDialect, sqlInfo.db, sqlInfo.tableName, range);
|
2023-11-08 17:36:52 +08:00
|
|
|
|
suggestions.push(...columnSuggestions.suggestions);
|
|
|
|
|
|
}
|
2023-09-20 20:42:23 +08:00
|
|
|
|
|
2023-11-08 17:36:52 +08:00
|
|
|
|
// 当前库的表名联想
|
|
|
|
|
|
const tables = await dbInst.loadTables(db);
|
2023-09-19 23:00:32 +08:00
|
|
|
|
tables.forEach((tableMeta: any, index: any) => {
|
|
|
|
|
|
const { tableName, tableComment } = tableMeta;
|
|
|
|
|
|
suggestions.push({
|
|
|
|
|
|
label: {
|
|
|
|
|
|
label: tableName + ' - ' + tableComment,
|
|
|
|
|
|
description: 'table',
|
|
|
|
|
|
},
|
|
|
|
|
|
kind: monaco.languages.CompletionItemKind.File,
|
|
|
|
|
|
detail: tableComment,
|
2024-01-11 12:35:44 +08:00
|
|
|
|
insertText: dbDialect.quoteIdentifier(tableName) + ' ',
|
2023-09-19 23:00:32 +08:00
|
|
|
|
range,
|
|
|
|
|
|
sortText: 300 + index + '',
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2023-12-12 12:41:44 +08:00
|
|
|
|
registerCompletions(keywords, suggestions, monaco.languages.CompletionItemKind.Keyword, range);
|
|
|
|
|
|
registerCompletions(operators, suggestions, monaco.languages.CompletionItemKind.Operator, range);
|
|
|
|
|
|
registerCompletions(functions, suggestions, monaco.languages.CompletionItemKind.Function, range);
|
|
|
|
|
|
registerCompletions(variables, suggestions, monaco.languages.CompletionItemKind.Variable, range);
|
2023-09-19 23:00:32 +08:00
|
|
|
|
|
|
|
|
|
|
// 默认提示
|
|
|
|
|
|
return {
|
|
|
|
|
|
suggestions: suggestions,
|
|
|
|
|
|
};
|
|
|
|
|
|
},
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-11-08 17:36:52 +08:00
|
|
|
|
function getTableName4SqlCtx(sql: string, alias: string = '', defaultDb: string): { tableName: string; tableAlias: string; db: string } | undefined {
|
2023-09-20 20:42:23 +08:00
|
|
|
|
// 去除多余的换行、空格和制表符
|
|
|
|
|
|
sql = sql.replace(/[\r\n\s\t]+/g, ' ');
|
2023-09-19 23:00:32 +08:00
|
|
|
|
|
2023-09-20 20:42:23 +08:00
|
|
|
|
// 提取所有可能的表名和别名
|
2023-11-08 17:36:52 +08:00
|
|
|
|
const regex = /(?:FROM|JOIN|UPDATE)\s+(\S+)\s+(?:AS\s+)?(\S+)/gi;
|
2023-09-20 20:42:23 +08:00
|
|
|
|
let matches;
|
|
|
|
|
|
const tables = [];
|
|
|
|
|
|
|
|
|
|
|
|
// 使用正则表达式匹配所有的表和别名
|
|
|
|
|
|
while ((matches = regex.exec(sql)) !== null) {
|
2023-11-08 17:36:52 +08:00
|
|
|
|
let tableName = matches[1].replace(/[`"]/g, '');
|
|
|
|
|
|
let db = defaultDb;
|
|
|
|
|
|
if (tableName.indexOf('.') >= 0) {
|
|
|
|
|
|
let info = tableName.split('.');
|
|
|
|
|
|
db = info[0];
|
2023-12-12 17:53:24 +08:00
|
|
|
|
if (defaultDb.indexOf('/') > 0) {
|
|
|
|
|
|
db = defaultDb.substring(0, defaultDb.indexOf('/') + 1) + db;
|
|
|
|
|
|
}
|
2023-11-08 17:36:52 +08:00
|
|
|
|
tableName = info[1];
|
|
|
|
|
|
}
|
2023-09-20 20:42:23 +08:00
|
|
|
|
const tableAlias = matches[2] ? matches[2].replace(/[`"]/g, '') : tableName;
|
2023-11-08 17:36:52 +08:00
|
|
|
|
tables.push({ tableName, tableAlias, db });
|
2023-09-19 23:00:32 +08:00
|
|
|
|
}
|
2023-09-20 20:42:23 +08:00
|
|
|
|
|
|
|
|
|
|
if (alias) {
|
|
|
|
|
|
// 如果指定了别名参数,则返回对应的表名
|
2023-11-08 17:36:52 +08:00
|
|
|
|
return tables.find((t) => t.tableAlias === alias);
|
2023-09-20 20:42:23 +08:00
|
|
|
|
} else {
|
|
|
|
|
|
// 如果未指定别名参数,则返回第一个表名
|
2023-11-08 17:36:52 +08:00
|
|
|
|
return tables.length > 0 ? tables[0] : undefined;
|
2023-09-20 20:42:23 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|