feat(editor): configure SQL variable syntax
This commit is contained in:
1 parent
54632f4c16
commit
1a309b6b63
18 files changed
+450
-24
No files matched your search
@@ -23,6 +23,8 @@ interface ParameterOccurrence extends SqlParameterDescriptor {
|
||||
|
||||
export interface SqlParameterOptions {
|
||||
databaseType?: DatabaseType;
|
||||
// Which placeholder syntaxes are recognized. Undefined enables all of them.
|
||||
enabledSyntaxes?: readonly SqlParameterSyntax[];
|
||||
}
|
||||
|
||||
const PARAMETER_NAME_RE = /^[\p{L}_][\p{L}\p{N}_]*$/u;
|
||||
@@ -78,6 +80,8 @@ function findSqlParameterOccurrences(sql: string, options?: SqlParameterOptions)
|
||||
const occurrences: ParameterOccurrence[] = [];
|
||||
const nativeSqlServerParameters = collectNativeSqlServerParameters(sql);
|
||||
const supportsNamedParameters = options?.databaseType !== "saphana";
|
||||
const enabledSyntaxes = options?.enabledSyntaxes ? new Set(options.enabledSyntaxes) : null;
|
||||
const isSyntaxEnabled = (syntax: SqlParameterSyntax) => !enabledSyntaxes || enabledSyntaxes.has(syntax);
|
||||
let i = 0;
|
||||
let dollarQuoteEnd = "";
|
||||
let positionalIndex = 0;
|
||||
@@ -110,14 +114,14 @@ function findSqlParameterOccurrences(sql: string, options?: SqlParameterOptions)
|
||||
i = skipBlockComment(sql, i + 2);
|
||||
continue;
|
||||
}
|
||||
if (ch === "?") {
|
||||
if (ch === "?" && isSyntaxEnabled("positional")) {
|
||||
positionalIndex += 1;
|
||||
const key = `?${positionalIndex}`;
|
||||
occurrences.push({ key, name: key, syntax: "positional", token: "?", start: i, end: i + 1 });
|
||||
i += 1;
|
||||
continue;
|
||||
}
|
||||
if (ch === ":" && supportsNamedParameters) {
|
||||
if (ch === ":" && supportsNamedParameters && isSyntaxEnabled("named")) {
|
||||
const name = readParameterName(sql, i + 1);
|
||||
if (name && sql[i - 1] !== ":" && sql[i + 1] !== "=") {
|
||||
occurrences.push({
|
||||
@@ -132,7 +136,7 @@ function findSqlParameterOccurrences(sql: string, options?: SqlParameterOptions)
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (ch === "$" && next === "{") {
|
||||
if (ch === "$" && next === "{" && isSyntaxEnabled("shell")) {
|
||||
const end = sql.indexOf("}", i + 2);
|
||||
if (end !== -1) {
|
||||
const name = sql.slice(i + 2, end).trim();
|
||||
@@ -143,7 +147,7 @@ function findSqlParameterOccurrences(sql: string, options?: SqlParameterOptions)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (ch === "#" && next === "{") {
|
||||
if (ch === "#" && next === "{" && isSyntaxEnabled("mybatis")) {
|
||||
const end = sql.indexOf("}", i + 2);
|
||||
if (end !== -1) {
|
||||
const name = sql.slice(i + 2, end).trim();
|
||||
@@ -158,7 +162,7 @@ function findSqlParameterOccurrences(sql: string, options?: SqlParameterOptions)
|
||||
i = skipLine(sql, i + 1);
|
||||
continue;
|
||||
}
|
||||
if (ch === "@") {
|
||||
if (ch === "@" && isSyntaxEnabled("sqlserver")) {
|
||||
const name = readParameterName(sql, i + 1);
|
||||
if (name && next !== "@" && sql[i - 1] !== "@" && !nativeSqlServerParameters.declared.has(name.toLowerCase()) && !nativeSqlServerParameters.ignoredStarts.has(i)) {
|
||||
occurrences.push({
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
// Per-database-type configuration for SQL variable/placeholder substitution.
|
||||
//
|
||||
// DBX runs two client-side substitution systems before sending SQL to a backend:
|
||||
// the placeholder parameter dialog (`sqlParameters.ts`, five syntaxes) and the
|
||||
// `@set name = value;` expansion (`sqlVariables.ts`). This module lets users opt
|
||||
// out of individual syntaxes per database type. Every toggle defaults to `true`,
|
||||
// so an empty/absent config reproduces the historical "always substitute" behaviour.
|
||||
//
|
||||
// Storage is sparse: only syntaxes explicitly turned off (`false`) are persisted,
|
||||
// keyed by database type. Anything not stored resolves to enabled.
|
||||
|
||||
import type { DatabaseType } from "@/types/database";
|
||||
import type { SqlParameterSyntax } from "@/lib/sql/sqlParameters";
|
||||
import { manifestDatabaseTypes } from "@/lib/database/databaseDriverManifest";
|
||||
|
||||
export interface SqlVariableSyntaxToggles {
|
||||
positional: boolean; // ?
|
||||
named: boolean; // :name
|
||||
shell: boolean; // ${name}
|
||||
mybatis: boolean; // #{name}
|
||||
sqlserver: boolean; // @name
|
||||
atSet: boolean; // @set name = value; (expandSqlVariables)
|
||||
}
|
||||
|
||||
export const DEFAULT_SQL_VARIABLE_SYNTAX_TOGGLES: SqlVariableSyntaxToggles = {
|
||||
positional: true,
|
||||
named: true,
|
||||
shell: true,
|
||||
mybatis: true,
|
||||
sqlserver: true,
|
||||
atSet: true,
|
||||
};
|
||||
|
||||
// Fixed order for iterating the toggles in the settings UI.
|
||||
export const SQL_VARIABLE_SYNTAX_KEYS = ["positional", "named", "shell", "mybatis", "sqlserver", "atSet"] as const satisfies readonly (keyof SqlVariableSyntaxToggles)[];
|
||||
|
||||
// Display tokens (code symbols, not translated) shown next to each toggle.
|
||||
export const SQL_VARIABLE_SYNTAX_TOKENS: Record<keyof SqlVariableSyntaxToggles, string> = {
|
||||
positional: "?",
|
||||
named: ":name",
|
||||
shell: "${name}",
|
||||
mybatis: "#{name}",
|
||||
sqlserver: "@name",
|
||||
atSet: "@set …;",
|
||||
};
|
||||
|
||||
// The first five toggles map one-to-one onto placeholder parameter syntaxes.
|
||||
const PARAMETER_SYNTAX_KEYS = ["positional", "named", "shell", "mybatis", "sqlserver"] as const satisfies readonly SqlParameterSyntax[];
|
||||
|
||||
export type SqlVariableSyntaxOverrides = Partial<Record<DatabaseType, Partial<SqlVariableSyntaxToggles>>>;
|
||||
|
||||
// Database types offered in the settings selector — every connectable type.
|
||||
export const SQL_VARIABLE_SYNTAX_DATABASE_TYPES: DatabaseType[] = manifestDatabaseTypes();
|
||||
|
||||
/**
|
||||
* Resolve the effective toggles for a database type. Any syntax not explicitly
|
||||
* disabled in `overrides` is enabled. A pure function safe to call on every
|
||||
* execution.
|
||||
*/
|
||||
export function resolveSqlVariableSyntaxToggles(overrides: SqlVariableSyntaxOverrides | undefined, dbType: DatabaseType | undefined): SqlVariableSyntaxToggles {
|
||||
const partial = dbType ? overrides?.[dbType] : undefined;
|
||||
return {
|
||||
positional: partial?.positional ?? true,
|
||||
named: partial?.named ?? true,
|
||||
shell: partial?.shell ?? true,
|
||||
mybatis: partial?.mybatis ?? true,
|
||||
sqlserver: partial?.sqlserver ?? true,
|
||||
atSet: partial?.atSet ?? true,
|
||||
};
|
||||
}
|
||||
|
||||
/** Derive the enabled placeholder parameter syntaxes (excludes `atSet`). */
|
||||
export function enabledSqlParameterSyntaxes(toggles: SqlVariableSyntaxToggles): SqlParameterSyntax[] {
|
||||
return PARAMETER_SYNTAX_KEYS.filter((key) => toggles[key]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize persisted overrides into a sparse structure: drop non-object entries
|
||||
* and only keep syntaxes explicitly set to `false`. Absence resolves to enabled.
|
||||
*/
|
||||
export function normalizeSqlVariableSyntaxOverrides(value: unknown): SqlVariableSyntaxOverrides {
|
||||
if (!value || typeof value !== "object" || Array.isArray(value)) return {};
|
||||
const result: SqlVariableSyntaxOverrides = {};
|
||||
for (const [dbType, raw] of Object.entries(value as Record<string, unknown>)) {
|
||||
if (!raw || typeof raw !== "object" || Array.isArray(raw)) continue;
|
||||
const partial: Partial<SqlVariableSyntaxToggles> = {};
|
||||
for (const key of SQL_VARIABLE_SYNTAX_KEYS) {
|
||||
if ((raw as Record<string, unknown>)[key] === false) partial[key] = false;
|
||||
}
|
||||
if (Object.keys(partial).length > 0) result[dbType as DatabaseType] = partial;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
Reference in new issue
Block a user