fix(postgres): quote data grid condition completions
This commit is contained in:
parent
ff4eac3782
commit
4e926cf22a
|
|
@ -205,6 +205,7 @@ import { reserveDataGridHeaderLine } from "@/lib/dataGrid/dataGridHeaderLayout";
|
|||
import { supportsTableStructureEditing } from "@/lib/database/databaseCapabilities";
|
||||
import { rememberDataGridConditionHistory } from "@/lib/dataGrid/dataGridConditionHistory";
|
||||
import { effectiveDatabaseTypeForConnection } from "@/lib/database/jdbcDialect";
|
||||
import { dataGridConditionColumnOptions } from "@/lib/dataGrid/dataGridConditionCompletion";
|
||||
import { isMacOS } from "@/lib/backend/platform";
|
||||
import { appendDebugLog, isDebugLoggingEnabled } from "@/lib/backend/debugLog";
|
||||
import { formatShortcut } from "@/lib/editor/shortcutRegistry";
|
||||
|
|
@ -567,6 +568,7 @@ const { searchText, deferredSearchText: deferredClientSearchText, overlayVisible
|
|||
|
||||
const orderByInput = ref(props.initialOrderByInput ?? "");
|
||||
const whereFilterInput = ref(props.initialWhereInput ?? "");
|
||||
const conditionColumns = computed(() => dataGridConditionColumnOptions(props.tableMeta?.columns ?? props.result.columns, resolvedDatabaseType.value));
|
||||
const conditionHistoryScope = computed(() => ({
|
||||
connectionId: props.connectionId,
|
||||
database: props.database,
|
||||
|
|
@ -7482,7 +7484,7 @@ const gridContextMenuItems = computed<ContextMenuItem[]>(() => {
|
|||
v-model:order-by-input="orderByInput"
|
||||
v-model:filter-builder-open="filterBuilderOpen"
|
||||
:columns="props.tableMeta?.columns.map((column) => column.name) ?? props.result.columns"
|
||||
:condition-columns="props.tableMeta?.columns ?? props.result.columns"
|
||||
:condition-columns="conditionColumns"
|
||||
:history-scope="conditionHistoryScope"
|
||||
:can-use-where-search="canUseWhereSearch"
|
||||
:compact="compactDataGridToolbar"
|
||||
|
|
|
|||
|
|
@ -70,6 +70,39 @@ describe("useDataGridConditionEditor", () => {
|
|||
expect(editor.suggestions.value).toEqual([{ value: "customer_id = 1", kind: "history" }]);
|
||||
});
|
||||
|
||||
it.each(["where", "orderBy"] as const)("displays raw PostgreSQL column names but inserts their quoted %s text", async (kind) => {
|
||||
const value = ref("");
|
||||
const editor = useDataGridConditionEditor({
|
||||
kind,
|
||||
value,
|
||||
columns: [{ name: "OrderId", insertText: '"OrderId"', comment: "Mixed-case identifier" }],
|
||||
historyScope: {},
|
||||
});
|
||||
|
||||
value.value = kind === "where" ? "status = Order" : "created_at DESC, Order";
|
||||
await nextTick();
|
||||
await vi.waitFor(() => expect(editor.suggestions.value).toEqual([{ value: "OrderId", insertText: '"OrderId"', kind: "column", comment: "Mixed-case identifier" }]));
|
||||
|
||||
expect(editor.accept()).toBe(true);
|
||||
expect(value.value).toBe(kind === "where" ? 'status = "OrderId"' : 'created_at DESC, "OrderId"');
|
||||
});
|
||||
|
||||
it("restores quoted history verbatim instead of quoting it again", () => {
|
||||
const scope = { connectionId: "connection", database: "db", tableName: "orders" };
|
||||
rememberDataGridConditionHistory("orderBy", scope, '"OrderId" DESC');
|
||||
const value = ref("");
|
||||
const editor = useDataGridConditionEditor({
|
||||
kind: "orderBy",
|
||||
value,
|
||||
columns: [{ name: "OrderId", insertText: '"OrderId"' }],
|
||||
historyScope: scope,
|
||||
});
|
||||
|
||||
editor.openHistory();
|
||||
expect(editor.accept(0)).toBe(true);
|
||||
expect(value.value).toBe('"OrderId" DESC');
|
||||
});
|
||||
|
||||
it.each(["where", "orderBy"] as const)("normalizes %s comments from different metadata providers", async (kind) => {
|
||||
const value = ref("");
|
||||
const editor = useDataGridConditionEditor({
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ export type DataGridConditionSuggestionKind = "column" | "history";
|
|||
|
||||
export interface DataGridConditionColumnSuggestion {
|
||||
name: string;
|
||||
insertText?: string;
|
||||
comment?: string | null;
|
||||
}
|
||||
|
||||
|
|
@ -12,6 +13,7 @@ export type DataGridConditionColumnOption = string | DataGridConditionColumnSugg
|
|||
|
||||
export interface DataGridConditionSuggestion {
|
||||
value: string;
|
||||
insertText?: string;
|
||||
kind: DataGridConditionSuggestionKind;
|
||||
comment?: string;
|
||||
}
|
||||
|
|
@ -97,7 +99,8 @@ export function useDataGridConditionEditor(options: UseDataGridConditionEditorOp
|
|||
if (!normalizedValue.startsWith(normalizedToken) || normalizedValue === normalizedToken || seen.has(value)) continue;
|
||||
seen.add(value);
|
||||
const comment = normalizedColumnComment(column);
|
||||
suggestions.push({ value, kind: "column", ...(comment ? { comment } : {}) });
|
||||
const insertText = typeof column === "string" ? value : column.insertText;
|
||||
suggestions.push({ value, kind: "column", ...(insertText !== undefined && insertText !== value ? { insertText } : {}), ...(comment ? { comment } : {}) });
|
||||
}
|
||||
return suggestions;
|
||||
}
|
||||
|
|
@ -177,7 +180,8 @@ export function useDataGridConditionEditor(options: UseDataGridConditionEditorOp
|
|||
const suggestion = suggestions.value[index];
|
||||
if (!suggestion) return false;
|
||||
suppressNextValueSuggestion = true;
|
||||
options.value.value = suggestion.kind === "history" ? suggestion.value : replaceActiveToken(options.kind, options.value.value, suggestion.value);
|
||||
// History is already executable SQL; only fresh column suggestions apply dialect quoting.
|
||||
options.value.value = suggestion.kind === "history" ? suggestion.value : replaceActiveToken(options.kind, options.value.value, suggestion.insertText ?? suggestion.value);
|
||||
dismiss();
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,20 @@
|
|||
import { describe, expect, it } from "vitest";
|
||||
import { dataGridConditionColumnOptions } from "@/lib/dataGrid/dataGridConditionCompletion";
|
||||
|
||||
describe("dataGridConditionColumnOptions", () => {
|
||||
it("reuses PostgreSQL completion quoting while preserving display metadata", () => {
|
||||
expect(dataGridConditionColumnOptions([{ name: "OrderId", comment: "Mixed case" }, { name: "order", comment: null }, { name: "article", comment: "Safe identifier" }, { name: 'has"quote' }], "postgres")).toEqual([
|
||||
{ name: "OrderId", comment: "Mixed case", insertText: '"OrderId"' },
|
||||
{ name: "order", comment: null, insertText: '"order"' },
|
||||
{ name: "article", comment: "Safe identifier", insertText: "article" },
|
||||
{ name: 'has"quote', insertText: '"has""quote"' },
|
||||
]);
|
||||
});
|
||||
|
||||
it.each(["mysql", "sqlserver", "oracle"] as const)("keeps existing %s condition insertions unchanged", (databaseType) => {
|
||||
expect(dataGridConditionColumnOptions(["OrderId", "order"], databaseType)).toEqual([
|
||||
{ name: "OrderId", insertText: "OrderId" },
|
||||
{ name: "order", insertText: "order" },
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
import type { DataGridConditionColumnOption } from "@/composables/useDataGridConditionEditor";
|
||||
import { codeMirrorSqlDialect } from "@/lib/database/jdbcDialect";
|
||||
import { quoteSqlIdentifier } from "@/lib/sql/sqlCompletion";
|
||||
import type { DatabaseType } from "@/types/database";
|
||||
|
||||
export function dataGridConditionColumnOptions(columns: readonly DataGridConditionColumnOption[], databaseType?: DatabaseType): DataGridConditionColumnOption[] {
|
||||
const dialect = codeMirrorSqlDialect(databaseType);
|
||||
return columns.map((column) => {
|
||||
const name = typeof column === "string" ? column : column.name;
|
||||
const insertText = quoteSqlIdentifier(name, dialect);
|
||||
const comment = typeof column === "string" ? undefined : column.comment;
|
||||
return { name, insertText, ...(comment !== undefined ? { comment } : {}) };
|
||||
});
|
||||
}
|
||||
|
|
@ -2777,7 +2777,7 @@ function unquoteIdentifier(value: string): string {
|
|||
return value;
|
||||
}
|
||||
|
||||
function quoteSqlIdentifier(identifier: string, dialect?: "mysql" | "postgres" | "sqlserver"): string {
|
||||
export function quoteSqlIdentifier(identifier: string, dialect?: "mysql" | "postgres" | "sqlserver"): string {
|
||||
if (dialect !== "postgres" || !requiresPostgresIdentifierQuote(identifier)) return identifier;
|
||||
return `"${identifier.replaceAll('"', '""')}"`;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue