feat(editor): toggle SQL keyword case
This commit is contained in:
1 parent
440e675a0e
commit
93a6a65707
12 files changed
+178
-33
No files matched your search
@@ -1051,6 +1051,8 @@ export interface SqlCompletionItem {
|
||||
boost: number;
|
||||
}
|
||||
|
||||
export type SqlKeywordCase = "preserve" | "upper" | "lower";
|
||||
|
||||
export interface SqlCompletionReferencedTable {
|
||||
name: string;
|
||||
schema?: string;
|
||||
@@ -1117,6 +1119,7 @@ export interface SqlCompletionProviderInput {
|
||||
snippets?: SqlSnippet[];
|
||||
dialect?: "mysql" | "postgres" | "sqlserver";
|
||||
databaseType?: DatabaseType;
|
||||
keywordCase?: SqlKeywordCase;
|
||||
}
|
||||
|
||||
export function buildSqlCompletionItems(
|
||||
@@ -1131,6 +1134,7 @@ export function buildSqlCompletionItems(
|
||||
translations?: SqlCompletionTranslations;
|
||||
dialect?: "mysql" | "postgres" | "sqlserver";
|
||||
databaseType?: DatabaseType;
|
||||
keywordCase?: SqlKeywordCase;
|
||||
},
|
||||
): SqlCompletionItem[] {
|
||||
const context = getSqlCompletionContext(sql, cursor);
|
||||
@@ -1165,7 +1169,7 @@ class SqlCompletionProvider {
|
||||
|
||||
if (!context.exclusiveTableSuggestions && !context.exclusiveColumnSuggestions && !context.exclusiveRoutineSuggestions) {
|
||||
const snippets = this.databaseType === "manticoresearch" ? [...(this.input.snippets ?? DEFAULT_SQL_SNIPPETS), ...MANTICORESEARCH_SQL_SNIPPETS] : (this.input.snippets ?? DEFAULT_SQL_SNIPPETS);
|
||||
this.items.push(...buildSnippetItems(context.prefix, snippets));
|
||||
this.items.push(...buildSnippetItems(context.prefix, snippets, this.input.keywordCase));
|
||||
this.items.push(...buildFunctionSnippetItems(context.prefix, getFunctionDescriptions(this.t), this.databaseType));
|
||||
}
|
||||
|
||||
@@ -1174,12 +1178,13 @@ class SqlCompletionProvider {
|
||||
...buildSnippetItems(
|
||||
context.prefix,
|
||||
MANTICORESEARCH_SQL_SNIPPETS.filter((snippet) => snippet.id === "builtin-manticore-call-pq"),
|
||||
this.input.keywordCase,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (context.preferredKeywords.length > 0) {
|
||||
this.items.push(...buildPreferredKeywordItems(context.prefix, context.preferredKeywords));
|
||||
this.items.push(...buildPreferredKeywordItems(context.prefix, context.preferredKeywords, this.input.keywordCase));
|
||||
}
|
||||
|
||||
if (!context.exclusiveTableSuggestions && !context.exclusiveColumnSuggestions && !context.exclusiveRoutineSuggestions && context.prioritizeSelectAliases) {
|
||||
@@ -1191,11 +1196,11 @@ class SqlCompletionProvider {
|
||||
}
|
||||
|
||||
if (!context.exclusiveTableSuggestions && !context.exclusiveColumnSuggestions && !context.exclusiveRoutineSuggestions && context.suggestJoinConditions) {
|
||||
this.items.push(...buildJoinConditionItems(context, this.input.columnsByTable, this.input.foreignKeysByTable, this.dialect));
|
||||
this.items.push(...buildJoinConditionItems(context, this.input.columnsByTable, this.input.foreignKeysByTable, this.dialect, this.input.keywordCase));
|
||||
}
|
||||
|
||||
if (context.suggestKeywords && !context.exclusiveRoutineSuggestions) {
|
||||
this.items.push(...buildKeywordItems(context.prefix, context, this.databaseType));
|
||||
this.items.push(...buildKeywordItems(context.prefix, context, this.databaseType, this.input.keywordCase));
|
||||
}
|
||||
|
||||
if (!context.exclusiveTableSuggestions && context.suggestColumns) {
|
||||
@@ -1222,7 +1227,7 @@ class SqlCompletionProvider {
|
||||
}
|
||||
|
||||
if (context.comparisonLeftColumn && context.suggestKeywords) {
|
||||
this.items.push(...buildComparisonValueItems(context, this.input.columnsByTable, this.t));
|
||||
this.items.push(...buildComparisonValueItems(context, this.input.columnsByTable, this.t, this.input.keywordCase));
|
||||
}
|
||||
|
||||
if (context.onStar) {
|
||||
@@ -2378,11 +2383,30 @@ function buildOracleTableFunctionItems(prefix: string): SqlCompletionItem[] {
|
||||
}));
|
||||
}
|
||||
|
||||
function buildPreferredKeywordItems(prefix: string, keywords: string[]): SqlCompletionItem[] {
|
||||
function applySqlKeywordCase(value: string, keywordCase?: SqlKeywordCase): string {
|
||||
if (keywordCase === "lower") return value.toLowerCase();
|
||||
return value.toUpperCase();
|
||||
}
|
||||
|
||||
function keywordJoiner(keywordCase?: SqlKeywordCase): string {
|
||||
return keywordCase === "lower" ? " and " : " AND ";
|
||||
}
|
||||
|
||||
function shouldFormatBuiltinSnippet(snippet: SqlSnippet): boolean {
|
||||
return snippet.id.startsWith("builtin-");
|
||||
}
|
||||
|
||||
function applyBuiltinSnippetKeywordCase(snippet: SqlSnippet, keywordCase?: SqlKeywordCase): string {
|
||||
if (!shouldFormatBuiltinSnippet(snippet)) return snippet.body;
|
||||
if (keywordCase === "lower") return snippet.body.toLowerCase();
|
||||
return snippet.body;
|
||||
}
|
||||
|
||||
function buildPreferredKeywordItems(prefix: string, keywords: string[], keywordCase?: SqlKeywordCase): SqlCompletionItem[] {
|
||||
return keywords
|
||||
.filter((keyword) => matchesPrefix(keyword, prefix))
|
||||
.map((keyword, index) => ({
|
||||
label: keyword,
|
||||
label: applySqlKeywordCase(keyword, keywordCase),
|
||||
type: "keyword" as const,
|
||||
boost: computeBoost(keyword, prefix) + 6200 - index,
|
||||
}));
|
||||
@@ -2409,7 +2433,7 @@ function buildStarExpansionItem(columnsByTable: Map<string, SqlCompletionColumn[
|
||||
};
|
||||
}
|
||||
|
||||
function buildComparisonValueItems(context: SqlCompletionContext, columnsByTable: Map<string, SqlCompletionColumn[]>, t?: SqlCompletionTranslations): SqlCompletionItem[] {
|
||||
function buildComparisonValueItems(context: SqlCompletionContext, columnsByTable: Map<string, SqlCompletionColumn[]>, t?: SqlCompletionTranslations, keywordCase?: SqlKeywordCase): SqlCompletionItem[] {
|
||||
const colName = context.comparisonLeftColumn!;
|
||||
const parts = colName.split(".");
|
||||
const unqualified = parts.length > 1 ? parts[parts.length - 1]! : colName;
|
||||
@@ -2446,19 +2470,19 @@ function buildComparisonValueItems(context: SqlCompletionContext, columnsByTable
|
||||
|
||||
// NULL check — always useful
|
||||
items.push({
|
||||
label: "NULL",
|
||||
label: applySqlKeywordCase("NULL", keywordCase),
|
||||
type: "keyword" as const,
|
||||
detail: t?.nullValue ?? "NULL value",
|
||||
boost: 1300,
|
||||
});
|
||||
items.push({
|
||||
label: "IS NULL",
|
||||
label: applySqlKeywordCase("IS NULL", keywordCase),
|
||||
type: "keyword" as const,
|
||||
detail: t?.isNull ?? "Checks whether the value is NULL",
|
||||
boost: 1250,
|
||||
});
|
||||
items.push({
|
||||
label: "IS NOT NULL",
|
||||
label: applySqlKeywordCase("IS NOT NULL", keywordCase),
|
||||
type: "keyword" as const,
|
||||
detail: t?.isNotNull ?? "Checks whether the value is not NULL",
|
||||
boost: 1200,
|
||||
@@ -2715,7 +2739,7 @@ function buildColumnInfo(column: SqlCompletionColumn): string | undefined {
|
||||
return parts.length > 1 ? parts.join("\n") : undefined;
|
||||
}
|
||||
|
||||
function buildJoinConditionItems(context: SqlCompletionContext, columnsByTable: Map<string, SqlCompletionColumn[]>, foreignKeysByTable?: Map<string, SqlCompletionForeignKey[]>, dialect?: "mysql" | "postgres" | "sqlserver"): SqlCompletionItem[] {
|
||||
function buildJoinConditionItems(context: SqlCompletionContext, columnsByTable: Map<string, SqlCompletionColumn[]>, foreignKeysByTable?: Map<string, SqlCompletionForeignKey[]>, dialect?: "mysql" | "postgres" | "sqlserver", keywordCase?: SqlKeywordCase): SqlCompletionItem[] {
|
||||
const refs = context.referencedTables;
|
||||
if (refs.length < 2) return [];
|
||||
|
||||
@@ -2726,7 +2750,7 @@ function buildJoinConditionItems(context: SqlCompletionContext, columnsByTable:
|
||||
for (const previous of previousRefs) {
|
||||
const previousColumns = columnsForReferencedTable(previous, columnsByTable);
|
||||
const latestColumns = columnsForReferencedTable(latest, columnsByTable);
|
||||
items.push(...buildForeignKeyJoinConditionItemsForPair(previous, latest, foreignKeysByTable, context.prefix, dialect), ...buildJoinConditionItemsForPair(previous, previousColumns, latest, latestColumns, context.prefix, dialect));
|
||||
items.push(...buildForeignKeyJoinConditionItemsForPair(previous, latest, foreignKeysByTable, context.prefix, dialect, keywordCase), ...buildJoinConditionItemsForPair(previous, previousColumns, latest, latestColumns, context.prefix, dialect, keywordCase));
|
||||
}
|
||||
|
||||
return items;
|
||||
@@ -2751,21 +2775,25 @@ function foreignKeysForReferencedTable(table: SqlCompletionReferencedTable, fore
|
||||
return [];
|
||||
}
|
||||
|
||||
function buildForeignKeyJoinConditionItemsForPair(left: SqlCompletionReferencedTable, right: SqlCompletionReferencedTable, foreignKeysByTable?: Map<string, SqlCompletionForeignKey[]>, prefix = "", dialect?: "mysql" | "postgres" | "sqlserver"): SqlCompletionItem[] {
|
||||
function buildForeignKeyJoinConditionItemsForPair(left: SqlCompletionReferencedTable, right: SqlCompletionReferencedTable, foreignKeysByTable?: Map<string, SqlCompletionForeignKey[]>, prefix = "", dialect?: "mysql" | "postgres" | "sqlserver", keywordCase?: SqlKeywordCase): SqlCompletionItem[] {
|
||||
if (!foreignKeysByTable) return [];
|
||||
return [...buildDirectionalForeignKeyJoinConditionItems(left, right, foreignKeysForReferencedTable(left, foreignKeysByTable), prefix, dialect), ...buildDirectionalForeignKeyJoinConditionItems(right, left, foreignKeysForReferencedTable(right, foreignKeysByTable), prefix, dialect)];
|
||||
return [
|
||||
...buildDirectionalForeignKeyJoinConditionItems(left, right, foreignKeysForReferencedTable(left, foreignKeysByTable), prefix, dialect, keywordCase),
|
||||
...buildDirectionalForeignKeyJoinConditionItems(right, left, foreignKeysForReferencedTable(right, foreignKeysByTable), prefix, dialect, keywordCase),
|
||||
];
|
||||
}
|
||||
|
||||
function buildDirectionalForeignKeyJoinConditionItems(owner: SqlCompletionReferencedTable, referenced: SqlCompletionReferencedTable, foreignKeys: SqlCompletionForeignKey[], prefix: string, dialect?: "mysql" | "postgres" | "sqlserver"): SqlCompletionItem[] {
|
||||
function buildDirectionalForeignKeyJoinConditionItems(owner: SqlCompletionReferencedTable, referenced: SqlCompletionReferencedTable, foreignKeys: SqlCompletionForeignKey[], prefix: string, dialect?: "mysql" | "postgres" | "sqlserver", keywordCase?: SqlKeywordCase): SqlCompletionItem[] {
|
||||
const matchingForeignKeys = foreignKeys.filter((foreignKey) => referencedTableMatchesName(referenced, foreignKey.ref_table, foreignKey.ref_schema));
|
||||
const groups = groupForeignKeysByConstraint(matchingForeignKeys);
|
||||
const items: SqlCompletionItem[] = [];
|
||||
|
||||
for (const group of groups) {
|
||||
const parts = group.map((foreignKey) => buildJoinConditionPart(owner, foreignKey.column, referenced, foreignKey.ref_column, dialect));
|
||||
const label = parts.map((part) => part.label).join(" AND ");
|
||||
const joiner = keywordJoiner(keywordCase);
|
||||
const label = parts.map((part) => part.label).join(joiner);
|
||||
if (!label || (prefix && !matchesPrefix(label, prefix))) continue;
|
||||
const apply = parts.map((part) => part.apply).join(" AND ");
|
||||
const apply = parts.map((part) => part.apply).join(joiner);
|
||||
items.push({
|
||||
label,
|
||||
type: "snippet",
|
||||
@@ -2819,7 +2847,7 @@ function normalizeIdentifierPart(name: string): string {
|
||||
return name.replace(/^["`[]|["`\]]$/g, "").toLowerCase();
|
||||
}
|
||||
|
||||
function buildJoinConditionItemsForPair(left: SqlCompletionReferencedTable, leftColumns: SqlCompletionColumn[], right: SqlCompletionReferencedTable, rightColumns: SqlCompletionColumn[], prefix: string, dialect?: "mysql" | "postgres" | "sqlserver"): SqlCompletionItem[] {
|
||||
function buildJoinConditionItemsForPair(left: SqlCompletionReferencedTable, leftColumns: SqlCompletionColumn[], right: SqlCompletionReferencedTable, rightColumns: SqlCompletionColumn[], prefix: string, dialect?: "mysql" | "postgres" | "sqlserver", keywordCase?: SqlKeywordCase): SqlCompletionItem[] {
|
||||
const items: SqlCompletionItem[] = [];
|
||||
const leftRef = left.alias || left.name;
|
||||
const rightRef = right.alias || right.name;
|
||||
@@ -2887,7 +2915,7 @@ function buildJoinConditionItemsForPair(left: SqlCompletionReferencedTable, left
|
||||
if (rightName !== "id" && rightName.endsWith("_id")) addPair(leftId, rightColumn, 1650);
|
||||
}
|
||||
|
||||
items.push(...buildCompositeHeuristicJoinConditionItems(left, leftColumns, right, leftByName, rightByName, prefix, dialect));
|
||||
items.push(...buildCompositeHeuristicJoinConditionItems(left, leftColumns, right, leftByName, rightByName, prefix, dialect, keywordCase));
|
||||
|
||||
return items;
|
||||
}
|
||||
@@ -2911,6 +2939,7 @@ function buildCompositeHeuristicJoinConditionItems(
|
||||
rightByName: Map<string, SqlCompletionColumn[]>,
|
||||
prefix: string,
|
||||
dialect?: "mysql" | "postgres" | "sqlserver",
|
||||
keywordCase?: SqlKeywordCase,
|
||||
): SqlCompletionItem[] {
|
||||
const leftId = leftByName.get("id")?.[0];
|
||||
const rightId = rightByName.get("id")?.[0];
|
||||
@@ -2953,13 +2982,14 @@ function buildCompositeHeuristicJoinConditionItems(
|
||||
} else {
|
||||
parts.push(buildHeuristicJoinConditionPart(leftRef, leftApplyRef, candidate.childFk, rightRef, rightApplyRef, candidate.parentId, dialect));
|
||||
}
|
||||
const label = parts.map((part) => part.label).join(" AND ");
|
||||
const joiner = keywordJoiner(keywordCase);
|
||||
const label = parts.map((part) => part.label).join(joiner);
|
||||
if (prefix && !matchesPrefix(label, prefix)) continue;
|
||||
items.push({
|
||||
label,
|
||||
type: "snippet",
|
||||
detail: "Likely composite JOIN condition",
|
||||
apply: parts.map((part) => part.apply).join(" AND "),
|
||||
apply: parts.map((part) => part.apply).join(joiner),
|
||||
boost: 2400 + parts.length,
|
||||
});
|
||||
}
|
||||
@@ -3035,7 +3065,7 @@ export function buildSnippetItemsForTest(prefix: string, snippets: SqlSnippet[])
|
||||
return buildSnippetItems(prefix, snippets);
|
||||
}
|
||||
|
||||
function buildSnippetItems(prefix: string, snippets: SqlSnippet[]): SqlCompletionItem[] {
|
||||
function buildSnippetItems(prefix: string, snippets: SqlSnippet[], keywordCase?: SqlKeywordCase): SqlCompletionItem[] {
|
||||
if (!prefix) return [];
|
||||
return snippets
|
||||
.filter((snippet) => {
|
||||
@@ -3051,11 +3081,12 @@ function buildSnippetItems(prefix: string, snippets: SqlSnippet[]): SqlCompletio
|
||||
// they are likely typing the actual keyword — reduce the base boost so
|
||||
// the real keyword can rank higher.
|
||||
const baseBoost = matchesByPrefix ? 4000 : 0;
|
||||
const body = applyBuiltinSnippetKeywordCase(snippet, keywordCase);
|
||||
return {
|
||||
label: snippet.label,
|
||||
type: "snippet" as const,
|
||||
detail: snippet.body,
|
||||
apply: snippet.body,
|
||||
detail: body,
|
||||
apply: body,
|
||||
boost: Math.max(boostByPrefix, boostByLabel) + baseBoost,
|
||||
};
|
||||
});
|
||||
@@ -3178,7 +3209,7 @@ function isOracleLikeDatabase(databaseType?: DatabaseType): boolean {
|
||||
return databaseType === "oracle" || databaseType === "oceanbase-oracle";
|
||||
}
|
||||
|
||||
function buildKeywordItems(prefix: string, context: SqlCompletionContext, databaseType?: DatabaseType): SqlCompletionItem[] {
|
||||
function buildKeywordItems(prefix: string, context: SqlCompletionContext, databaseType?: DatabaseType, keywordCase?: SqlKeywordCase): SqlCompletionItem[] {
|
||||
const isDml = context.statementKind === "select" || context.statementKind === "insert" || context.statementKind === "update" || context.statementKind === "delete";
|
||||
const showDdl = !isDml || context.suggestTables;
|
||||
|
||||
@@ -3195,7 +3226,7 @@ function buildKeywordItems(prefix: string, context: SqlCompletionContext, databa
|
||||
const base = computeBoost(keyword, prefix);
|
||||
const freqBoost = HIGH_FREQUENCY_KEYWORDS.has(keyword) ? 100 : 0;
|
||||
return {
|
||||
label: keyword,
|
||||
label: applySqlKeywordCase(keyword, keywordCase),
|
||||
type: "keyword" as const,
|
||||
boost: base + freqBoost,
|
||||
};
|
||||
|
||||
Reference in new issue
Block a user