fix: sql代码提示修复:支持跨schema提示

This commit is contained in:
刘宗洋
2023-11-08 17:36:52 +08:00
parent eddda41291
commit abad0ed481

View File

@@ -93,6 +93,52 @@ export class DbInst {
return tables;
}
async loadTableSuggestions(dbName: string, range: any, reload?: boolean) {
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,
insertText: tableName + ' ',
range,
sortText: 300 + index + '',
});
});
return { suggestions };
}
/** 加载列信息提示 */
async loadTableColumnSuggestions(db: string, tableName: string, range: any) {
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, 否则选中时备注等会被遮挡
insertText: fieldName, // create_time
range,
sortText: 100 + index + '', // 使用表字段声明顺序排序,排序需为字符串类型
});
});
return { suggestions };
}
/**
* 获取表的所有列信息
* @param table 表名
@@ -482,7 +528,7 @@ export type FieldsMeta = {
* @param db 库名
* @param dbs 该库所有库名
*/
export function registerDbCompletionItemProvider(language: string, dbId: number, db: string, dbs: [] = []) {
export function registerDbCompletionItemProvider(language: string, dbId: number, db: string, dbs: any[] = []) {
registerCompletionItemProvider(language, {
triggerCharacters: ['.', ' '],
provideCompletionItems: async (model: editor.ITextModel, position: Position): Promise<languages.CompletionList | null | undefined> => {
@@ -518,29 +564,6 @@ export function registerDbCompletionItemProvider(language: string, dbId: number,
let lastToken = tokens[tokens.length - 1].toLowerCase();
const secondToken = (tokens.length > 2 && tokens[tokens.length - 2].toLowerCase()) || '';
// console.log("光标前文本:=>" + textBeforePointerMulti)
// console.log("最后输入的:=>" + lastToken)
let suggestions: languages.CompletionItem[] = [];
let alias = '';
if (lastToken.indexOf('.') > -1 || secondToken.indexOf('.') > -1) {
// 如果是.触发代码提示,则进行【 库.表名联想 】 或 【 表别名.表字段联想 】
alias = lastToken.substring(0, lastToken.lastIndexOf('.'));
if (lastToken.trim().startsWith('.')) {
alias = secondToken;
}
// 如果字符串粘连起了如:'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;
}
}
}
// 获取光标所在行之前的所有文本内容
const textBeforeCursor = model.getValueInRange({
startLineNumber: 1,
@@ -579,39 +602,62 @@ export function registerDbCompletionItemProvider(language: string, dbId: number,
sqlStatement = textBeforeCursor + textAfterCursor;
}
const tableName = getTableName4SqlCtx(sqlStatement, alias);
// 提出到表名,则将表对应的字段也添加进提示建议
if (tableName) {
let dbHits = await dbInst.loadDbHints(db);
let columns = dbHits[tableName];
columns?.forEach((a: string, index: any) => {
// 字段数据格式 字段名 字段注释, 如: create_time [datetime][创建时间]
const nameAndComment = a.split(' ');
const fieldName = nameAndComment[0];
let suggestions: languages.CompletionItem[] = [];
// 库名提示
if (dbs && dbs.length > 0) {
dbs.forEach((a: any) => {
suggestions.push({
label: {
label: a,
description: 'column',
description: 'schema',
},
kind: monaco.languages.CompletionItemKind.Property,
detail: '', // 不显示detail, 否则选中时备注等会被遮挡
insertText: fieldName, // create_time
kind: monaco.languages.CompletionItemKind.Folder,
insertText: a,
range,
sortText: 100 + index + '', // 使用表字段声明顺序排序,排序需为字符串类型
});
});
}
// 若存在字段提示,并且有别名,则提示字段即可,不完善后续的表名以及函数等
if (suggestions.length > 0 && alias) {
return {
suggestions: suggestions,
};
let alias = '';
if (lastToken.indexOf('.') > -1 || secondToken.indexOf('.') > -1) {
// 如果是.触发代码提示,则进行【 库.表名联想 】 或 【 表别名.表字段联想 】
alias = lastToken.substring(0, lastToken.lastIndexOf('.'));
if (lastToken.trim().startsWith('.')) {
alias = secondToken;
}
// 如果字符串粘连起了如:'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;
}
}
// 如果是【库.表名联想】.前的字符串是库名
if (dbs.indexOf(alias) >= 0) {
return await dbInst.loadTableSuggestions(alias, range);
}
// 表下列名联想 .前的字符串是表名或表别名
const sqlInfo = getTableName4SqlCtx(sqlStatement, alias, db);
// 提出到表名,则将表对应的字段也添加进提示建议
if (sqlInfo) {
return await dbInst.loadTableColumnSuggestions(sqlInfo.db, sqlInfo.tableName, range);
}
}
const tables = await dbInst.loadTables(db);
// 空格触发也会提示字段信息
const sqlInfo = getTableName4SqlCtx(sqlStatement, alias, db);
if (sqlInfo) {
const columnSuggestions = await dbInst.loadTableColumnSuggestions(sqlInfo.db, sqlInfo.tableName, range);
suggestions.push(...columnSuggestions.suggestions);
}
// 表名联想
// 当前库的表名联想
const tables = await dbInst.loadTables(db);
tables.forEach((tableMeta: any, index: any) => {
const { tableName, tableComment } = tableMeta;
suggestions.push({
@@ -695,21 +741,6 @@ export function registerDbCompletionItemProvider(language: string, dbId: number,
});
});
// 库名提示
if (dbs && dbs.length > 0) {
dbs.forEach((a: any) => {
suggestions.push({
label: {
label: a,
description: 'schema',
},
kind: monaco.languages.CompletionItemKind.Folder,
insertText: a,
range,
});
});
}
// 默认提示
return {
suggestions: suggestions,
@@ -718,31 +749,33 @@ export function registerDbCompletionItemProvider(language: string, dbId: number,
});
}
function getTableName4SqlCtx(sql: string, alias: string = '') {
function getTableName4SqlCtx(sql: string, alias: string = '', defaultDb: string): { tableName: string; tableAlias: string; db: string } | undefined {
// 去除多余的换行、空格和制表符
sql = sql.replace(/[\r\n\s\t]+/g, ' ');
// 提取所有可能的表名和别名
const regex = /(?:(?:FROM|JOIN|UPDATE)\s+(\S+)\s+(?:AS\s+)?(\S+))/gi;
const regex = /(?:FROM|JOIN|UPDATE)\s+(\S+)\s+(?:AS\s+)?(\S+)/gi;
let matches;
const tables = [];
// 使用正则表达式匹配所有的表和别名
while ((matches = regex.exec(sql)) !== null) {
const tableName = matches[1].replace(/[`"]/g, '');
let tableName = matches[1].replace(/[`"]/g, '');
let db = defaultDb;
if (tableName.indexOf('.') >= 0) {
let info = tableName.split('.');
db = info[0];
tableName = info[1];
}
const tableAlias = matches[2] ? matches[2].replace(/[`"]/g, '') : tableName;
tables.push({ tableName, tableAlias });
tables.push({ tableName, tableAlias, db });
}
// console.log('sql....', sql);
// console.log('alias....', alias);
// console.log('parset tables...', tables);
if (alias) {
// 如果指定了别名参数,则返回对应的表名
const table = tables.find((t) => t.tableAlias === alias);
return table ? table.tableName : '';
return tables.find((t) => t.tableAlias === alias);
} else {
// 如果未指定别名参数,则返回第一个表名
return tables.length > 0 ? tables[0].tableName : '';
return tables.length > 0 ? tables[0] : undefined;
}
}