feat(editor): dialect-aware SQL quick actions with per-database templates
This commit is contained in:
1 parent
455117b8c5
commit
65b820e6a9
30 files changed
+1541
-150
No files matched your search
@@ -0,0 +1,64 @@
|
||||
import type { DatabaseType } from "@/types/database";
|
||||
|
||||
/** Default row limit for built-in `sel` snippets. */
|
||||
export const DEFAULT_SELECT_ROW_LIMIT = 100;
|
||||
|
||||
/** Default row limit for SQL quick-action "select first N" kind. */
|
||||
export const DEFAULT_SQL_SHORTCUT_SELECT_LIMIT = 10;
|
||||
|
||||
export type SelectLimitStyle = "limit" | "top" | "first" | "fetch-first" | "rows" | "rownum";
|
||||
|
||||
const SELECT_LIMIT_STYLE_BY_DATABASE: Partial<Record<DatabaseType, SelectLimitStyle>> = {
|
||||
oracle: "rownum",
|
||||
"oceanbase-oracle": "rownum",
|
||||
oscar: "rownum",
|
||||
dameng: "rownum",
|
||||
db2: "fetch-first",
|
||||
sqlserver: "top",
|
||||
access: "top",
|
||||
iris: "top",
|
||||
teradata: "top",
|
||||
informix: "first",
|
||||
firebird: "rows",
|
||||
// Unknown JDBC dialects fall through to LIMIT (most common / safest default).
|
||||
// Known JDBC profiles are converted to their effective database type before
|
||||
// reaching the editor.
|
||||
};
|
||||
|
||||
export function selectLimitStyleForDatabase(databaseType?: DatabaseType): SelectLimitStyle {
|
||||
return databaseType ? (SELECT_LIMIT_STYLE_BY_DATABASE[databaseType] ?? "limit") : "limit";
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a dialect-aware `SELECT * FROM <table>` with a row limit.
|
||||
* `table` is inserted as-is (caller supplies quoting / `${table}` token).
|
||||
*/
|
||||
export function buildSelectStarWithLimitSql(table: string, limit: number, databaseType?: DatabaseType): string {
|
||||
const safeLimit = Number.isFinite(limit) && limit > 0 ? Math.floor(limit) : DEFAULT_SELECT_ROW_LIMIT;
|
||||
const style = selectLimitStyleForDatabase(databaseType);
|
||||
switch (style) {
|
||||
case "top":
|
||||
return `SELECT TOP ${safeLimit} *\nFROM ${table}`;
|
||||
case "first":
|
||||
return `SELECT FIRST ${safeLimit} *\nFROM ${table}`;
|
||||
case "fetch-first":
|
||||
return `SELECT *\nFROM ${table}\nFETCH FIRST ${safeLimit} ROWS ONLY`;
|
||||
case "rows":
|
||||
return `SELECT *\nFROM ${table}\nROWS ${safeLimit}`;
|
||||
case "rownum":
|
||||
return `SELECT *\nFROM ${table}\nWHERE ROWNUM <= ${safeLimit}`;
|
||||
case "limit":
|
||||
return `SELECT *\nFROM ${table}\nLIMIT ${safeLimit}`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Snippet bodies keep a trailing semicolon for insert-at-cursor UX.
|
||||
* An unknown JDBC driver may expose any SQL dialect, so the snippet stays
|
||||
* unbounded instead of guessing LIMIT; quick actions remain bounded via
|
||||
* `buildSelectStarWithLimitSql` regardless.
|
||||
*/
|
||||
export function buildSelectSnippetBody(databaseType?: DatabaseType, limit = DEFAULT_SELECT_ROW_LIMIT): string {
|
||||
if (databaseType === "jdbc") return "SELECT *\nFROM table;";
|
||||
return `${buildSelectStarWithLimitSql("table", limit, databaseType)};`;
|
||||
}
|
||||
@@ -1,23 +1,164 @@
|
||||
import { canonicalShortcutKey } from "@/lib/editor/shortcutDisplay";
|
||||
import { formatShortcut, SHORTCUT_DEFINITIONS, type ShortcutSettings } from "@/lib/editor/shortcutRegistry";
|
||||
import type { SqlShortcutAction } from "@/types/database";
|
||||
import { buildSelectStarWithLimitSql, DEFAULT_SQL_SHORTCUT_SELECT_LIMIT } from "@/lib/sql/sqlDialectSelectLimit";
|
||||
import type { DatabaseType, SqlShortcutAction, SqlShortcutKind } from "@/types/database";
|
||||
import { DATABASE_TYPES } from "@/types/generated/databaseTypes";
|
||||
|
||||
export const SQL_SHORTCUT_TABLE_TOKEN = "${table}";
|
||||
|
||||
export const BUILTIN_SQL_SHORTCUT_SELECT_LIMIT_ID = "builtin-select-limit";
|
||||
export const BUILTIN_SQL_SHORTCUT_COUNT_ID = "builtin-count";
|
||||
|
||||
const DATABASE_TYPE_SET = new Set<string>(DATABASE_TYPES);
|
||||
|
||||
/** Built-in quick actions shipped with empty / missing settings. */
|
||||
export const DEFAULT_SQL_SHORTCUTS: SqlShortcutAction[] = [
|
||||
{
|
||||
id: BUILTIN_SQL_SHORTCUT_SELECT_LIMIT_ID,
|
||||
label: "Select first N rows",
|
||||
shortcut: "Mod+Shift+1",
|
||||
kind: "select-limit",
|
||||
limit: DEFAULT_SQL_SHORTCUT_SELECT_LIMIT,
|
||||
sql: buildSelectStarWithLimitSql(SQL_SHORTCUT_TABLE_TOKEN, DEFAULT_SQL_SHORTCUT_SELECT_LIMIT),
|
||||
enabled: true,
|
||||
},
|
||||
{
|
||||
id: BUILTIN_SQL_SHORTCUT_COUNT_ID,
|
||||
label: "Count rows",
|
||||
shortcut: "Mod+Shift+2",
|
||||
sql: `SELECT COUNT(*) AS row_count FROM ${SQL_SHORTCUT_TABLE_TOKEN}`,
|
||||
enabled: true,
|
||||
},
|
||||
];
|
||||
|
||||
export function isBuiltinSqlShortcut(id: string): boolean {
|
||||
return id === BUILTIN_SQL_SHORTCUT_SELECT_LIMIT_ID || id === BUILTIN_SQL_SHORTCUT_COUNT_ID;
|
||||
}
|
||||
|
||||
/** Append any missing built-in ids without overwriting user-edited entries. */
|
||||
export function mergeDefaultSqlShortcuts(actions: readonly SqlShortcutAction[]): SqlShortcutAction[] {
|
||||
const ids = new Set(actions.map((action) => action.id));
|
||||
const missing = DEFAULT_SQL_SHORTCUTS.filter((action) => !ids.has(action.id)).map((action) => ({ ...action }));
|
||||
return missing.length > 0 ? [...actions, ...missing] : [...actions];
|
||||
}
|
||||
|
||||
export function resolveSqlShortcutTemplate(template: string, selectedTable: string): string {
|
||||
return template.replace(/\$\{table\}/g, () => selectedTable.trim());
|
||||
}
|
||||
|
||||
export function normalizeSqlShortcutLimit(value: unknown, fallback = DEFAULT_SQL_SHORTCUT_SELECT_LIMIT): number {
|
||||
const n = typeof value === "number" ? value : typeof value === "string" ? Number(value) : NaN;
|
||||
if (!Number.isFinite(n) || n < 1) return fallback;
|
||||
return Math.min(Math.floor(n), 100_000);
|
||||
}
|
||||
|
||||
export function normalizeSqlShortcutKind(value: unknown): SqlShortcutKind {
|
||||
return value === "select-limit" ? "select-limit" : "template";
|
||||
}
|
||||
|
||||
export function normalizeSqlShortcutDatabaseTypes(value: unknown): DatabaseType[] | undefined {
|
||||
if (!Array.isArray(value) || value.length === 0) return undefined;
|
||||
const valid: DatabaseType[] = [];
|
||||
const seen = new Set<string>();
|
||||
for (const item of value) {
|
||||
if (typeof item !== "string" || !DATABASE_TYPE_SET.has(item) || seen.has(item)) continue;
|
||||
seen.add(item);
|
||||
valid.push(item as DatabaseType);
|
||||
}
|
||||
return valid.length > 0 ? valid : undefined;
|
||||
}
|
||||
|
||||
export function normalizeSqlShortcutSqlByDatabaseType(value: unknown): Partial<Record<DatabaseType, string>> | undefined {
|
||||
if (!value || typeof value !== "object" || Array.isArray(value)) return undefined;
|
||||
const result: Partial<Record<DatabaseType, string>> = {};
|
||||
for (const [key, sql] of Object.entries(value as Record<string, unknown>)) {
|
||||
if (!DATABASE_TYPE_SET.has(key) || typeof sql !== "string") continue;
|
||||
result[key as DatabaseType] = sql;
|
||||
}
|
||||
return Object.keys(result).length > 0 ? result : undefined;
|
||||
}
|
||||
|
||||
/** When overrides exist without an explicit scope, treat override keys as the database scope. */
|
||||
export function deriveSqlShortcutDatabaseTypes(databaseTypes: DatabaseType[] | undefined, sqlByDatabaseType: Partial<Record<DatabaseType, string>> | undefined): DatabaseType[] | undefined {
|
||||
if (databaseTypes && databaseTypes.length > 0) return databaseTypes;
|
||||
if (!sqlByDatabaseType) return undefined;
|
||||
const derived = Object.keys(sqlByDatabaseType).filter((key): key is DatabaseType => DATABASE_TYPE_SET.has(key));
|
||||
return derived.length > 0 ? derived : undefined;
|
||||
}
|
||||
|
||||
export function resolveSqlShortcutBody(action: Pick<SqlShortcutAction, "sql" | "sqlByDatabaseType">, databaseType?: DatabaseType): string {
|
||||
if (databaseType && action.sqlByDatabaseType?.[databaseType] != null && action.sqlByDatabaseType[databaseType] !== "") {
|
||||
return action.sqlByDatabaseType[databaseType]!;
|
||||
}
|
||||
return action.sql;
|
||||
}
|
||||
|
||||
export function canonicalSqlShortcutSql(action: Pick<SqlShortcutAction, "kind" | "limit" | "sql">): string {
|
||||
if (normalizeSqlShortcutKind(action.kind) !== "select-limit") return action.sql;
|
||||
const limit = normalizeSqlShortcutLimit(action.limit);
|
||||
return buildSelectStarWithLimitSql(SQL_SHORTCUT_TABLE_TOKEN, limit);
|
||||
}
|
||||
|
||||
export function enabledSqlShortcutActions(actions: readonly SqlShortcutAction[]): SqlShortcutAction[] {
|
||||
return actions.filter((action) => action.enabled !== false && action.shortcut.trim().length > 0);
|
||||
}
|
||||
|
||||
function shortcutsUseSameKeys(first: string, second: string, platform = globalThis.navigator?.platform || ""): boolean {
|
||||
export function sqlShortcutAppliesToDatabase(action: Pick<SqlShortcutAction, "databaseTypes">, databaseType?: DatabaseType): boolean {
|
||||
const types = action.databaseTypes;
|
||||
if (!types || types.length === 0) return true;
|
||||
if (!databaseType) return false;
|
||||
return types.includes(databaseType);
|
||||
}
|
||||
|
||||
export function sqlShortcutDatabaseScopesOverlap(first?: readonly DatabaseType[], second?: readonly DatabaseType[]): boolean {
|
||||
const firstAll = !first || first.length === 0;
|
||||
const secondAll = !second || second.length === 0;
|
||||
if (firstAll || secondAll) return true;
|
||||
const secondSet = new Set(second);
|
||||
return first.some((dbType) => secondSet.has(dbType));
|
||||
}
|
||||
|
||||
export function shortcutsUseSameKeys(first: string, second: string, platform = globalThis.navigator?.platform || ""): boolean {
|
||||
if (!first || !second) return false;
|
||||
if (canonicalShortcutKey(first) === canonicalShortcutKey(second)) return true;
|
||||
return formatShortcut(first, platform).toLowerCase() === formatShortcut(second, platform).toLowerCase();
|
||||
}
|
||||
|
||||
export function uniqueSqlShortcutBindings(actions: readonly SqlShortcutAction[], platform = globalThis.navigator?.platform || ""): string[] {
|
||||
const bindings: string[] = [];
|
||||
for (const action of enabledSqlShortcutActions(actions)) {
|
||||
const shortcut = action.shortcut.trim();
|
||||
if (bindings.some((existing) => shortcutsUseSameKeys(existing, shortcut, platform))) continue;
|
||||
bindings.push(shortcut);
|
||||
}
|
||||
return bindings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefer a scoped action over an all-databases action when both match the same key.
|
||||
*/
|
||||
export function resolveSqlShortcutForDatabase(actions: readonly SqlShortcutAction[], shortcut: string, databaseType?: DatabaseType, platform = globalThis.navigator?.platform || ""): SqlShortcutAction | undefined {
|
||||
const matches = enabledSqlShortcutActions(actions).filter((action) => shortcutsUseSameKeys(action.shortcut, shortcut, platform) && sqlShortcutAppliesToDatabase(action, databaseType));
|
||||
if (matches.length === 0) return undefined;
|
||||
matches.sort((a, b) => (b.databaseTypes?.length ?? 0) - (a.databaseTypes?.length ?? 0));
|
||||
return matches[0];
|
||||
}
|
||||
|
||||
export function buildSqlShortcutExecutionSql(action: SqlShortcutAction, selectedTable: string, databaseType?: DatabaseType): string {
|
||||
const table = selectedTable.trim();
|
||||
if (normalizeSqlShortcutKind(action.kind) === "select-limit") {
|
||||
return buildSelectStarWithLimitSql(table, normalizeSqlShortcutLimit(action.limit), databaseType);
|
||||
}
|
||||
return resolveSqlShortcutTemplate(resolveSqlShortcutBody(action, databaseType), table);
|
||||
}
|
||||
|
||||
export function sqlShortcutDisplaySql(action: SqlShortcutAction, databaseType?: DatabaseType): string {
|
||||
if (normalizeSqlShortcutKind(action.kind) === "select-limit") {
|
||||
return buildSelectStarWithLimitSql(SQL_SHORTCUT_TABLE_TOKEN, normalizeSqlShortcutLimit(action.limit), databaseType);
|
||||
}
|
||||
return resolveSqlShortcutBody(action, databaseType);
|
||||
}
|
||||
|
||||
export function findSqlShortcutConflicts(actions: readonly SqlShortcutAction[], fixedShortcuts: ShortcutSettings, platform = globalThis.navigator?.platform || ""): string[] {
|
||||
const conflicts = new Set<string>();
|
||||
const editorFixedShortcuts = SHORTCUT_DEFINITIONS.filter((item) => item.scope === "editor" || item.scope === "global");
|
||||
@@ -25,7 +166,7 @@ export function findSqlShortcutConflicts(actions: readonly SqlShortcutAction[],
|
||||
for (const action of actions) {
|
||||
if (action.enabled === false || !action.shortcut.trim()) continue;
|
||||
|
||||
const duplicate = actions.find((other) => other.id !== action.id && other.enabled !== false && other.shortcut.trim() && shortcutsUseSameKeys(other.shortcut, action.shortcut, platform));
|
||||
const duplicate = actions.find((other) => other.id !== action.id && other.enabled !== false && other.shortcut.trim() && shortcutsUseSameKeys(other.shortcut, action.shortcut, platform) && sqlShortcutDatabaseScopesOverlap(action.databaseTypes, other.databaseTypes));
|
||||
if (duplicate) {
|
||||
conflicts.add(action.id);
|
||||
conflicts.add(duplicate.id);
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
import type { DatabaseType } from "@/types/database";
|
||||
|
||||
/** In-dialog state for custom SQL shortcut bodies (fallback + per-DB overrides). */
|
||||
export type SqlShortcutFormBodies = {
|
||||
/** Default fallback template persisted as payload.sql */
|
||||
sql: string;
|
||||
/** Textarea content for the current editing context */
|
||||
body: string;
|
||||
databaseTypes: DatabaseType[];
|
||||
sqlByDatabaseType: Partial<Record<DatabaseType, string>>;
|
||||
editingDatabaseType: DatabaseType | "";
|
||||
};
|
||||
|
||||
/** Stage the current textarea into the override map (scoped) or default sql (all-databases). */
|
||||
export function flushSqlShortcutEditingBody(form: SqlShortcutFormBodies): SqlShortcutFormBodies {
|
||||
if (!form.editingDatabaseType) {
|
||||
return { ...form, sql: form.body, sqlByDatabaseType: { ...form.sqlByDatabaseType } };
|
||||
}
|
||||
return {
|
||||
...form,
|
||||
sqlByDatabaseType: { ...form.sqlByDatabaseType, [form.editingDatabaseType]: form.body },
|
||||
};
|
||||
}
|
||||
|
||||
/** Flush current body, then load textarea for another selected database. */
|
||||
export function switchSqlShortcutEditingDatabaseType(form: SqlShortcutFormBodies, next: DatabaseType): SqlShortcutFormBodies {
|
||||
const flushed = flushSqlShortcutEditingBody(form);
|
||||
return {
|
||||
...flushed,
|
||||
editingDatabaseType: next,
|
||||
body: flushed.sqlByDatabaseType[next] ?? flushed.sql,
|
||||
};
|
||||
}
|
||||
|
||||
export function toggleSqlShortcutFormDatabaseType(form: SqlShortcutFormBodies, dbType: DatabaseType): SqlShortcutFormBodies {
|
||||
const flushed = flushSqlShortcutEditingBody(form);
|
||||
const removing = flushed.databaseTypes.includes(dbType);
|
||||
const nextTypes = removing ? flushed.databaseTypes.filter((item) => item !== dbType) : [...flushed.databaseTypes, dbType];
|
||||
const sqlByDatabaseType = { ...flushed.sqlByDatabaseType };
|
||||
|
||||
if (removing) {
|
||||
delete sqlByDatabaseType[dbType];
|
||||
}
|
||||
|
||||
if (nextTypes.length === 0) {
|
||||
// Last scoped DB removed → same as "all databases": keep visible text as the only sql.
|
||||
const body = flushed.editingDatabaseType === dbType || !flushed.editingDatabaseType ? flushed.body : (sqlByDatabaseType[flushed.editingDatabaseType] ?? flushed.body);
|
||||
return {
|
||||
...flushed,
|
||||
databaseTypes: [],
|
||||
sqlByDatabaseType: {},
|
||||
editingDatabaseType: "",
|
||||
sql: body,
|
||||
body,
|
||||
};
|
||||
}
|
||||
|
||||
let editingDatabaseType = flushed.editingDatabaseType;
|
||||
if (!editingDatabaseType || !nextTypes.includes(editingDatabaseType)) {
|
||||
editingDatabaseType = nextTypes[0]!;
|
||||
}
|
||||
|
||||
return {
|
||||
...flushed,
|
||||
databaseTypes: nextTypes,
|
||||
sqlByDatabaseType,
|
||||
editingDatabaseType,
|
||||
body: sqlByDatabaseType[editingDatabaseType] ?? flushed.sql,
|
||||
sql: flushed.sql,
|
||||
};
|
||||
}
|
||||
|
||||
/** Switch back to all-databases: wipe overrides, keep currently visible text as sql. */
|
||||
export function clearSqlShortcutFormDatabaseTypes(form: SqlShortcutFormBodies): SqlShortcutFormBodies {
|
||||
const flushed = flushSqlShortcutEditingBody(form);
|
||||
const body = flushed.editingDatabaseType && flushed.sqlByDatabaseType[flushed.editingDatabaseType] != null ? flushed.sqlByDatabaseType[flushed.editingDatabaseType]! : flushed.body;
|
||||
return {
|
||||
...flushed,
|
||||
databaseTypes: [],
|
||||
sqlByDatabaseType: {},
|
||||
editingDatabaseType: "",
|
||||
sql: body,
|
||||
body,
|
||||
};
|
||||
}
|
||||
|
||||
/** Copy current textarea to every selected DB override and the default sql. */
|
||||
export function applySqlShortcutBodyToAllSelected(form: SqlShortcutFormBodies): SqlShortcutFormBodies {
|
||||
if (form.databaseTypes.length === 0) {
|
||||
return { ...form, sql: form.body, sqlByDatabaseType: {} };
|
||||
}
|
||||
const sqlByDatabaseType: Partial<Record<DatabaseType, string>> = {};
|
||||
for (const dbType of form.databaseTypes) {
|
||||
sqlByDatabaseType[dbType] = form.body;
|
||||
}
|
||||
return {
|
||||
...form,
|
||||
sql: form.body,
|
||||
sqlByDatabaseType,
|
||||
};
|
||||
}
|
||||
|
||||
/** Persistable bodies: default sql + overrides only for still-selected databases. */
|
||||
export function buildSqlShortcutBodiesForSave(form: SqlShortcutFormBodies): {
|
||||
sql: string;
|
||||
databaseTypes?: DatabaseType[];
|
||||
sqlByDatabaseType?: Partial<Record<DatabaseType, string>>;
|
||||
} {
|
||||
const flushed = flushSqlShortcutEditingBody(form);
|
||||
if (flushed.databaseTypes.length === 0) {
|
||||
return { sql: flushed.body };
|
||||
}
|
||||
const sqlByDatabaseType: Partial<Record<DatabaseType, string>> = {};
|
||||
for (const dbType of flushed.databaseTypes) {
|
||||
const override = flushed.sqlByDatabaseType[dbType];
|
||||
if (override != null) sqlByDatabaseType[dbType] = override;
|
||||
}
|
||||
return {
|
||||
sql: flushed.sql,
|
||||
databaseTypes: [...flushed.databaseTypes],
|
||||
...(Object.keys(sqlByDatabaseType).length > 0 ? { sqlByDatabaseType } : {}),
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import { extractQualifiedIdentifierAt, isSqlKeyword } from "@/lib/sql/sqlNavigation";
|
||||
|
||||
export type SqlShortcutTableSelection = {
|
||||
from: number;
|
||||
to: number;
|
||||
empty: boolean;
|
||||
/** Cursor / selection head used when selection is empty. */
|
||||
head: number;
|
||||
};
|
||||
|
||||
function identifierAtCursor(doc: string, pos: number): { start: number; end: number } | null {
|
||||
let located = extractQualifiedIdentifierAt(doc, pos);
|
||||
// CodeMirror can report the boundary immediately after the token.
|
||||
if (!located && pos > 0) located = extractQualifiedIdentifierAt(doc, pos - 1);
|
||||
if (!located || located.parts.length === 0) return null;
|
||||
const last = located.parts[located.parts.length - 1]!;
|
||||
if (!last.quoted && isSqlKeyword(last.value)) return null;
|
||||
return { start: located.start, end: located.end };
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the `${table}` token for SQL quick actions:
|
||||
* non-empty selection wins; otherwise use the qualified identifier under the cursor
|
||||
* (preserving original quotes and dots from the document).
|
||||
*/
|
||||
export function resolveSqlShortcutTableToken(doc: string, selection: SqlShortcutTableSelection): string | null {
|
||||
if (!selection.empty) {
|
||||
const selected = doc.slice(selection.from, selection.to).trim();
|
||||
return selected.length > 0 ? selected : null;
|
||||
}
|
||||
const located = identifierAtCursor(doc, selection.head);
|
||||
if (!located) return null;
|
||||
const token = doc.slice(located.start, located.end).trim();
|
||||
return token.length > 0 ? token : null;
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
import { buildSelectSnippetBody, DEFAULT_SELECT_ROW_LIMIT } from "@/lib/sql/sqlDialectSelectLimit";
|
||||
import type { DatabaseType, SqlSnippet } from "@/types/database";
|
||||
|
||||
const DEFAULT_SELECT_ROW_LIMIT = 100;
|
||||
|
||||
/**
|
||||
* Built-in SQL snippets shared by database types.
|
||||
*
|
||||
@@ -129,48 +128,9 @@ interface BuiltinSqlSnippetRule {
|
||||
buildBody: BuiltinSqlSnippetBodyBuilder;
|
||||
}
|
||||
|
||||
type SelectSnippetLimitStyle = "limit" | "top" | "first" | "fetch-first" | "rows" | "rownum" | "unbounded";
|
||||
|
||||
const SELECT_SNIPPET_LIMIT_STYLE_BY_DATABASE: Partial<Record<DatabaseType, SelectSnippetLimitStyle>> = {
|
||||
oracle: "rownum",
|
||||
"oceanbase-oracle": "rownum",
|
||||
oscar: "rownum",
|
||||
dameng: "rownum",
|
||||
db2: "fetch-first",
|
||||
sqlserver: "top",
|
||||
access: "top",
|
||||
iris: "top",
|
||||
teradata: "top",
|
||||
informix: "first",
|
||||
firebird: "rows",
|
||||
// An unknown JDBC driver may expose any SQL dialect. Known JDBC profiles are
|
||||
// converted to their effective database type before reaching the editor.
|
||||
jdbc: "unbounded",
|
||||
};
|
||||
|
||||
const PARENTHESIZED_ADD_COLUMN_DATABASES = new Set<DatabaseType>(["oracle", "oceanbase-oracle", "yashandb", "xugu", "dameng", "iris", "informix"]);
|
||||
const ADD_COLUMN_WITHOUT_COLUMN_KEYWORD_DATABASES = new Set<DatabaseType>(["sqlserver", "kingbase", "cassandra", "teradata"]);
|
||||
|
||||
function buildSelectSnippetBody(databaseType?: DatabaseType): string {
|
||||
const style = databaseType ? (SELECT_SNIPPET_LIMIT_STYLE_BY_DATABASE[databaseType] ?? "limit") : "limit";
|
||||
switch (style) {
|
||||
case "top":
|
||||
return `SELECT TOP ${DEFAULT_SELECT_ROW_LIMIT} *\nFROM table;`;
|
||||
case "first":
|
||||
return `SELECT FIRST ${DEFAULT_SELECT_ROW_LIMIT} *\nFROM table;`;
|
||||
case "fetch-first":
|
||||
return `SELECT *\nFROM table\nFETCH FIRST ${DEFAULT_SELECT_ROW_LIMIT} ROWS ONLY;`;
|
||||
case "rows":
|
||||
return `SELECT *\nFROM table\nROWS ${DEFAULT_SELECT_ROW_LIMIT};`;
|
||||
case "rownum":
|
||||
return `SELECT *\nFROM table\nWHERE ROWNUM <= ${DEFAULT_SELECT_ROW_LIMIT};`;
|
||||
case "unbounded":
|
||||
return "SELECT *\nFROM table;";
|
||||
case "limit":
|
||||
return `SELECT *\nFROM table\nLIMIT ${DEFAULT_SELECT_ROW_LIMIT};`;
|
||||
}
|
||||
}
|
||||
|
||||
function buildUpdateSnippetBody(databaseType?: DatabaseType): string {
|
||||
if (databaseType === "clickhouse") {
|
||||
return "ALTER TABLE table\nUPDATE column = value\nWHERE condition;";
|
||||
|
||||
Reference in new issue
Block a user