fix(oracle): preserve DATE time in display and export
This commit is contained in:
17 files changed
+504
-58
No files matched your search
@@ -4024,6 +4024,11 @@ function tableColumnForGridColumn(columnIndex: number): ColumnInfo | undefined {
|
||||
return props.tableMeta?.columns.find((column) => column.name.toLowerCase() === columnName.toLowerCase());
|
||||
}
|
||||
|
||||
function resultColumnInfoForGridColumn(columnIndex: number): Pick<ColumnInfo, "data_type"> | undefined {
|
||||
const dataType = props.result.column_types?.[columnIndex]?.trim();
|
||||
return dataType ? { data_type: dataType } : undefined;
|
||||
}
|
||||
|
||||
function temporalEditorKindForColumn(columnIndex: number): TemporalCellEditorKind | undefined {
|
||||
return temporalCellEditorKind(tableColumnForGridColumn(columnIndex)?.data_type, props.databaseType);
|
||||
}
|
||||
@@ -5351,7 +5356,8 @@ function formatCell(value: CellValue, columnIndex?: number): string {
|
||||
const formatter = columnIndex === undefined ? undefined : resolvedColumnFormatters.value[columnIndex];
|
||||
const columnName = columnIndex === undefined ? undefined : props.result.columns[columnIndex];
|
||||
const columnInfo = columnIndex === undefined ? undefined : tableColumnForGridColumn(columnIndex);
|
||||
const arrayDisplay = formatter ? undefined : dataGridCellDisplayText({ value, databaseType: props.databaseType, columnInfo });
|
||||
const displayColumnInfo = columnInfo ?? (columnIndex === undefined ? undefined : resultColumnInfoForGridColumn(columnIndex));
|
||||
const arrayDisplay = formatter ? undefined : dataGridCellDisplayText({ value, databaseType: props.databaseType, columnInfo: displayColumnInfo });
|
||||
if (arrayDisplay !== undefined) return arrayDisplay;
|
||||
const binaryDisplay = formatter ? null : binaryCellDisplayText(value, columnInfo?.data_type ?? (columnName ? columnTypeMap.value.get(columnName) : undefined));
|
||||
if (binaryDisplay) return binaryDisplay;
|
||||
@@ -5392,7 +5398,13 @@ function draftCellPlaceholder(item: RowItem | undefined, columnIndex: number): s
|
||||
return item?.isDraft && item.data[columnIndex] === null ? t("grid.quickEntryDraftPlaceholder") : null;
|
||||
}
|
||||
|
||||
watch(() => [props.result.columns.join("\u0000"), columnFormatterSignatures.value.join("\u0000")], clearCellFormatCache);
|
||||
function columnTypeCacheSignature(): string {
|
||||
const resultTypes = props.result.column_types?.join("\u0000") ?? "";
|
||||
const tableTypes = props.tableMeta?.columns?.map((column) => `${column.name}:${column.data_type}`).join("\u0000") ?? "";
|
||||
return `${resultTypes}\u0001${tableTypes}`;
|
||||
}
|
||||
|
||||
watch(() => [props.result.columns.join("\u0000"), columnFormatterSignatures.value.join("\u0000"), columnTypeCacheSignature()], clearCellFormatCache);
|
||||
|
||||
function quoteIdent(name: string): string {
|
||||
return quoteTableIdentifier(props.databaseType, name);
|
||||
|
||||
@@ -237,6 +237,7 @@ export function useDataGridExport(options: UseDataGridExportOptions) {
|
||||
tableName: tableMeta.value?.tableName ?? null,
|
||||
copyInsertTargetLabel: copyInsertTargetLabel?.value ?? null,
|
||||
columns: columns.value,
|
||||
columnTypes: columnTypes.value ?? null,
|
||||
sourceColumns: sourceColumns.value ?? null,
|
||||
excludePrimaryKeys,
|
||||
insertMode,
|
||||
@@ -311,6 +312,7 @@ export function useDataGridExport(options: UseDataGridExportOptions) {
|
||||
databaseType: databaseType.value,
|
||||
tableMeta: tableMeta.value,
|
||||
columns: columns.value,
|
||||
columnTypes: columnTypes.value,
|
||||
sourceColumns: sourceColumns.value,
|
||||
rows: rows.map((item) => item.data),
|
||||
excludePrimaryKeys,
|
||||
@@ -1075,9 +1077,10 @@ export function useDataGridExport(options: UseDataGridExportOptions) {
|
||||
} {
|
||||
const exportColumns = tableMeta.value ? effectiveColumns(sourceColumns.value, result.columns) : result.columns;
|
||||
const columnIndexes = exportColumns.map((column, index) => ({ column, index })).filter((item): item is { column: string; index: number } => !!item.column);
|
||||
const exportColumnTypes = columnTypes.value?.length === result.columns.length ? columnTypes.value : undefined;
|
||||
return {
|
||||
columns: columnIndexes.map((item) => item.column),
|
||||
columnTypes: tableMeta.value ? columnIndexes.map((item) => columnTypes.value?.[item.index]) : undefined,
|
||||
columnTypes: exportColumnTypes ? columnIndexes.map((item) => exportColumnTypes[item.index]) : undefined,
|
||||
rows: result.rows.map((row) => columnIndexes.map((item) => row[item.index] ?? null)),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { dataGridCellDisplayText } from "@/lib/dataGrid/dataGridCellCoercion";
|
||||
|
||||
describe("dataGridCellDisplayText", () => {
|
||||
it("formats Oracle DATE values without RFC3339 separators", () => {
|
||||
expect(
|
||||
dataGridCellDisplayText({
|
||||
value: "2022-08-25T09:58:43Z",
|
||||
databaseType: "oracle",
|
||||
columnInfo: { data_type: "DATE" },
|
||||
}),
|
||||
).toBe("2022-08-25 09:58:43");
|
||||
});
|
||||
|
||||
it("formats midnight Oracle DATE values as a date", () => {
|
||||
expect(
|
||||
dataGridCellDisplayText({
|
||||
value: "2022-08-25T00:00:00Z",
|
||||
databaseType: "oracle",
|
||||
columnInfo: { data_type: "DATE" },
|
||||
}),
|
||||
).toBe("2022-08-25");
|
||||
});
|
||||
|
||||
it("does not format non-date Oracle strings", () => {
|
||||
expect(
|
||||
dataGridCellDisplayText({
|
||||
value: "2022-08-25T09:58:43Z",
|
||||
databaseType: "oracle",
|
||||
columnInfo: { data_type: "VARCHAR2(64)" },
|
||||
}),
|
||||
).toBeUndefined();
|
||||
});
|
||||
});
|
||||
@@ -45,6 +45,9 @@ export function dataGridCellDisplayText(options: { value: GridCellValue; databas
|
||||
if (Array.isArray(options.value) && options.databaseType === "postgres" && isPostgresArrayColumn(options.columnInfo, options.value)) {
|
||||
return formatPostgresArrayText(options.value);
|
||||
}
|
||||
if (typeof options.value === "string" && isOracleDateColumn(options.databaseType, options.columnInfo)) {
|
||||
return formatOracleDateDisplayText(options.value);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@@ -113,6 +116,39 @@ function normalizeDataType(dataType: string | undefined): string {
|
||||
return (dataType ?? "").trim().toLowerCase();
|
||||
}
|
||||
|
||||
function isOracleDateColumn(databaseType: DatabaseType | undefined, columnInfo: Pick<ColumnInfo, "data_type"> | undefined): boolean {
|
||||
if (databaseType !== "oracle" && databaseType !== "oceanbase-oracle") return false;
|
||||
const base = normalizeDataType(columnInfo?.data_type).split(/[()\s\t\n]/)[0] ?? "";
|
||||
return base === "date";
|
||||
}
|
||||
|
||||
function formatOracleDateDisplayText(value: string): string | undefined {
|
||||
const parts = parseOracleDateLikeText(value);
|
||||
if (!parts) return undefined;
|
||||
if (parts.time === "00:00:00" && !parts.fraction) return parts.date;
|
||||
return `${parts.date} ${parts.time}${parts.fraction ?? ""}`;
|
||||
}
|
||||
|
||||
function parseOracleDateLikeText(value: string): { date: string; time: string; fraction?: string } | undefined {
|
||||
if (!/^\d{4}-\d{2}-\d{2}/.test(value)) return undefined;
|
||||
const date = value.slice(0, 10);
|
||||
if (value.length === 10) return { date, time: "00:00:00" };
|
||||
const separator = value[10];
|
||||
if (separator !== "T" && separator !== " ") return undefined;
|
||||
if (!/^\d{2}:\d{2}:\d{2}/.test(value.slice(11))) return undefined;
|
||||
const time = value.slice(11, 19);
|
||||
let rest = value.slice(19);
|
||||
let fraction: string | undefined;
|
||||
if (rest.startsWith(".")) {
|
||||
const match = rest.match(/^(\.\d{1,9})(.*)$/);
|
||||
if (!match) return undefined;
|
||||
fraction = match[1];
|
||||
rest = match[2];
|
||||
}
|
||||
if (rest && !/^z$/i.test(rest) && !/^[+-]\d{2}:\d{2}$/.test(rest)) return undefined;
|
||||
return { date, time, fraction };
|
||||
}
|
||||
|
||||
function isExactDecimalDataType(dataType: string): boolean {
|
||||
return /\b(?:decimal|numeric|number|dec|money|smallmoney|bigdecimal|bignumeric|big_numeric|fixed)\b/.test(dataType);
|
||||
}
|
||||
|
||||
@@ -45,6 +45,7 @@ export interface DataGridCopyInsertStatementOptions {
|
||||
databaseType?: DatabaseType;
|
||||
tableMeta?: DataGridTableMeta;
|
||||
columns: string[];
|
||||
columnTypes?: Array<string | null | undefined>;
|
||||
sourceColumns?: Array<string | undefined>;
|
||||
rows: GridCellValue[][];
|
||||
excludePrimaryKeys?: boolean;
|
||||
|
||||
@@ -156,6 +156,7 @@ describe("queryStore database open state", () => {
|
||||
vi.unstubAllGlobals();
|
||||
const storage = installLocalStorage();
|
||||
storage.set("dbx-editor-settings", JSON.stringify({ openTabsRestoreMode: "none" }));
|
||||
storage.set("dbx-app-state:open_tabs", JSON.stringify({ tabs: JSON.parse(persistedTabs), activeTabId: "tab-1" }));
|
||||
storage.set(OPEN_TABS_STORAGE_KEY, persistedTabs);
|
||||
storage.set(ACTIVE_TAB_STORAGE_KEY, "tab-1");
|
||||
setActivePinia(createPinia());
|
||||
@@ -170,6 +171,7 @@ describe("queryStore database open state", () => {
|
||||
expect(store.activeTabId).toBeNull();
|
||||
expect(storage.get(OPEN_TABS_STORAGE_KEY)).toBeUndefined();
|
||||
expect(storage.get(ACTIVE_TAB_STORAGE_KEY)).toBeUndefined();
|
||||
expect(JSON.parse(storage.get("dbx-app-state:open_tabs") ?? "{}")).toEqual({ tabs: [], activeTabId: null });
|
||||
});
|
||||
|
||||
it("restores only pinned tabs when launch restore mode is pinned", async () => {
|
||||
|
||||
@@ -681,6 +681,12 @@ export const useQueryStore = defineStore("query", () => {
|
||||
if (saved?.tabs && Array.isArray(saved.tabs)) {
|
||||
const restored = restoreSavedTabsFromPayload(saved);
|
||||
applyRestoredOpenTabs(restored);
|
||||
if (useSettingsStore().editorSettings.openTabsRestoreMode === "none") {
|
||||
// Restore is explicitly disabled, so stale saved payloads should not
|
||||
// reappear if the user later changes the setting.
|
||||
clearLegacySavedTabs();
|
||||
await saveTabs(tabs.value, activeTabId.value).catch(() => undefined);
|
||||
}
|
||||
isOpenTabsLoaded.value = true;
|
||||
return;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user