fix(xugu): enable safe row editing without primary keys
This commit is contained in:
1 parent
39b2d2a6ac
commit
3e75be87eb
17 files changed
+236
-50
No files matched your search
@@ -112,7 +112,7 @@ import {
|
||||
isClickHouseExistingRowReadonlyColumn,
|
||||
isHiddenGridColumn,
|
||||
isTdengineExistingRowReadonlyColumn,
|
||||
usesSyntheticRowIdKey,
|
||||
shouldIncludeSyntheticRowId,
|
||||
} from "@/lib/table/tableEditing";
|
||||
import { buildDataGridColumnDistinctValuesSql, buildDataGridConditionalUpdateSql, buildDataGridContextFilterCondition, buildDataGridCountSql, buildHiveTablePropertiesSql, type DataGridContextFilterMode } from "@/lib/dataGrid/dataGridSql";
|
||||
import {
|
||||
@@ -4349,7 +4349,7 @@ async function refreshSavedRows(request: { dirtyRows: ReadonlyMap<number, Readon
|
||||
...tableDataLargeValuePreviewOptions(resolvedDatabaseType.value, tableMeta.columns, tableMeta.primaryKeys, pageSize.value),
|
||||
whereInput: identityConditions.join(" OR "),
|
||||
limit: planResult.plan.sourceIndexes.length + 1,
|
||||
includeRowId: usesSyntheticRowIdKey(resolvedDatabaseType.value, tableMeta.primaryKeys, tableMeta.tableType),
|
||||
includeRowId: shouldIncludeSyntheticRowId(resolvedDatabaseType.value, tableMeta.primaryKeys, tableMeta.tableType),
|
||||
});
|
||||
const refreshed = await api.executeQuery(connectionId, props.executionDatabase ?? props.database ?? "", sql, tableMeta.schema ?? props.schema, undefined, {
|
||||
maxRows: planResult.plan.sourceIndexes.length + 1,
|
||||
@@ -5542,7 +5542,7 @@ async function hydrateVisibleLargeValuePreviews(generation: number) {
|
||||
whereInput: predicates.map((predicate) => `(${predicate})`).join(" OR "),
|
||||
limit: requests.size,
|
||||
offset: 0,
|
||||
includeRowId: usesSyntheticRowIdKey(resolvedDatabaseType.value, tableMeta.primaryKeys, tableMeta.tableType),
|
||||
includeRowId: shouldIncludeSyntheticRowId(resolvedDatabaseType.value, tableMeta.primaryKeys, tableMeta.tableType),
|
||||
});
|
||||
if (!visibleLargeValuePreviewActive || generation !== visibleLargeValuePreviewRequestedGeneration || props.result !== sourceResult) return;
|
||||
const connection = connectionStore.getConfig(props.connectionId);
|
||||
@@ -5709,7 +5709,7 @@ async function fetchLargeValueRequestChunk(columnIndex: number, requests: LargeV
|
||||
whereInput: predicates.map((predicate) => `(${predicate})`).join(" OR "),
|
||||
limit: requests.length,
|
||||
offset: 0,
|
||||
includeRowId: usesSyntheticRowIdKey(resolvedDatabaseType.value, tableMeta.primaryKeys, tableMeta.tableType),
|
||||
includeRowId: shouldIncludeSyntheticRowId(resolvedDatabaseType.value, tableMeta.primaryKeys, tableMeta.tableType),
|
||||
});
|
||||
const connection = props.connectionId ? connectionStore.getConfig(props.connectionId) : undefined;
|
||||
const results = await api.executeMulti(props.connectionId!, props.executionDatabase ?? props.database ?? "", sql, undefined, uuid(), {
|
||||
@@ -7120,7 +7120,7 @@ async function applyOrderBySearch() {
|
||||
injectDefaultTimeSeriesWhere: true,
|
||||
limit: pageSize.value,
|
||||
whereInput: currentWhereInput(),
|
||||
includeRowId: usesSyntheticRowIdKey(resolvedDatabaseType.value, tableMeta.primaryKeys, tableMeta.tableType),
|
||||
includeRowId: shouldIncludeSyntheticRowId(resolvedDatabaseType.value, tableMeta.primaryKeys, tableMeta.tableType),
|
||||
});
|
||||
markConditionInputsApplied();
|
||||
await props.onExecuteSql(sql);
|
||||
@@ -7159,7 +7159,7 @@ async function applyWhereFilter() {
|
||||
limit: pageSize.value,
|
||||
injectDefaultTimeSeriesWhere: true,
|
||||
whereInput,
|
||||
includeRowId: usesSyntheticRowIdKey(resolvedDatabaseType.value, tableMeta.primaryKeys, tableMeta.tableType),
|
||||
includeRowId: shouldIncludeSyntheticRowId(resolvedDatabaseType.value, tableMeta.primaryKeys, tableMeta.tableType),
|
||||
});
|
||||
markConditionInputsApplied();
|
||||
await props.onExecuteSql(sql);
|
||||
|
||||
+2
-2
@@ -18,12 +18,12 @@ describe("DataGrid large-value reload SQL", () => {
|
||||
// Keyless Oracle tables address rows via the hidden __DBX_ROWID alias; the
|
||||
// reload must opt into the ROWIDTOCHAR inline view so the generated SQL
|
||||
// never references __DBX_ROWID as a base-table column (ORA-00904).
|
||||
expect(fetchChunkSource).toContain("includeRowId: usesSyntheticRowIdKey(resolvedDatabaseType.value, tableMeta.primaryKeys, tableMeta.tableType)");
|
||||
expect(fetchChunkSource).toContain("includeRowId: shouldIncludeSyntheticRowId(resolvedDatabaseType.value, tableMeta.primaryKeys, tableMeta.tableType)");
|
||||
});
|
||||
|
||||
it("keeps the visible-preview hydration consistent with the synthetic key", () => {
|
||||
const hydrateSource = functionSource("hydrateVisibleLargeValuePreviews", "runVisibleLargeValuePreviewHydration");
|
||||
|
||||
expect(hydrateSource).toContain("includeRowId: usesSyntheticRowIdKey(resolvedDatabaseType.value, tableMeta.primaryKeys, tableMeta.tableType)");
|
||||
expect(hydrateSource).toContain("includeRowId: shouldIncludeSyntheticRowId(resolvedDatabaseType.value, tableMeta.primaryKeys, tableMeta.tableType)");
|
||||
});
|
||||
});
|
||||
@@ -118,7 +118,10 @@ vi.mock("@/lib/backend/debugLog", () => ({ appendDebugLog: vi.fn(), isDebugLoggi
|
||||
// dataTabOpenPolicy 使用真实实现,覆盖设置开关对应的复用范围
|
||||
vi.mock("@/lib/sidebar/treeNodeContext", () => ({ hasTreeNodeDatabaseContext: () => true }));
|
||||
vi.mock("@/lib/table/tableSelectSql", () => ({ buildTableSelectSql: mocks.buildTableSelectSql }));
|
||||
vi.mock("@/lib/table/tableEditing", () => ({ usesSyntheticRowIdKey: () => false }));
|
||||
vi.mock("@/lib/table/tableEditing", () => ({
|
||||
usesSyntheticRowIdKey: () => false,
|
||||
shouldIncludeSyntheticRowId: () => false,
|
||||
}));
|
||||
vi.mock("@/lib/table/tableOpenPageLimit", () => ({ tableOpenPageLimit: () => 100 }));
|
||||
vi.mock("@/lib/tabs/dataTabActivation", () => ({ canActivateExistingDataTableTab: () => false }));
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import { buildTableSelectSql, quoteTableDataIdentifier } from "@/lib/table/table
|
||||
import { tableOpenPageLimit } from "@/lib/table/tableOpenPageLimit";
|
||||
import { tableDataLargeValuePreviewOptions } from "@/lib/dataGrid/dataGridLargeValues";
|
||||
import { elasticsearchCursorPageJumpRequestCount } from "@/lib/dataGrid/dataGridPagination";
|
||||
import { usesSyntheticRowIdKey } from "@/lib/table/tableEditing";
|
||||
import { shouldIncludeSyntheticRowId } from "@/lib/table/tableEditing";
|
||||
import { tableMetaForDataTab } from "@/lib/table/tableDataTabMeta";
|
||||
import * as api from "@/lib/backend/api";
|
||||
import type { QueryTab } from "@/types/database";
|
||||
@@ -87,7 +87,7 @@ export function useDataGridActions(activeTab: ComputedRef<QueryTab | undefined>)
|
||||
const effectiveDbType = effectiveDatabaseTypeForConnection(config);
|
||||
const tableMeta = tableMetaForDataTab(tab);
|
||||
const primaryKeys = tab.tableMeta ? tab.tableMeta.primaryKeys : (tableMeta?.primaryKeys ?? []);
|
||||
const useRowId = usesSyntheticRowIdKey(effectiveDbType, primaryKeys, tableMeta?.tableType);
|
||||
const useRowId = shouldIncludeSyntheticRowId(effectiveDbType, primaryKeys, tableMeta?.tableType);
|
||||
// 列投影只信任真实元数据列:tableMetaForDataTab 的 fallback 列来自查询
|
||||
// 结果(可能是失败结果的 ["Error"]),进入 SQL 会生成非法投影;
|
||||
// 真实列缺失时省略 columns 让 builder 生成 SELECT *
|
||||
|
||||
@@ -5,7 +5,7 @@ import { canApplyDataTabMetadata, canReuseActiveMongoTab, type DataTabReuseMode
|
||||
import { isNoSnapshotErrorResult, isQueryExecutionErrorResult } from "@/lib/query/queryResultError";
|
||||
import { buildTableSelectSql } from "@/lib/table/tableSelectSql";
|
||||
import { tableDataLargeValuePreviewOptions } from "@/lib/dataGrid/dataGridLargeValues";
|
||||
import { editableRowIdentifierColumns, usesSyntheticRowIdKey } from "@/lib/table/tableEditing";
|
||||
import { editableRowIdentifierColumns, shouldIncludeSyntheticRowId } from "@/lib/table/tableEditing";
|
||||
import { tableOpenPageLimit } from "@/lib/table/tableOpenPageLimit";
|
||||
import { uuid } from "@/lib/common/utils";
|
||||
import { beginDataTabNavigation, endDataTabNavigation, isCurrentDataTabNavigation } from "@/lib/tabs/dataTabNavigationGeneration";
|
||||
@@ -272,7 +272,7 @@ async function openTableTarget(target: NavigationTarget, options: { tableInfoTab
|
||||
// 异步窗口内 tab 可能已被复用为其他目标:旧请求的元数据不得落地、
|
||||
// 不得解除新目标的 pending
|
||||
if (!isCurrentTarget() || !isCurrentGeneration()) return;
|
||||
const useRowId = usesSyntheticRowIdKey(effectiveDbType, primaryKeys, targetTableType);
|
||||
const useRowId = shouldIncludeSyntheticRowId(effectiveDbType, primaryKeys, targetTableType);
|
||||
queryStore.setTableMeta(tabId, {
|
||||
schema: tableSchema,
|
||||
catalog: target.catalog,
|
||||
|
||||
@@ -11,7 +11,7 @@ import { canApplyDataTabMetadata, dataTabMetadataNeedsRefresh, findExistingDataT
|
||||
import type { SidebarDataOpenRequest } from "@/lib/sidebar/sidebarDataOpenCoordinator";
|
||||
import { hasTreeNodeDatabaseContext } from "@/lib/sidebar/treeNodeContext";
|
||||
import { buildTableSelectSql } from "@/lib/table/tableSelectSql";
|
||||
import { usesSyntheticRowIdKey } from "@/lib/table/tableEditing";
|
||||
import { shouldIncludeSyntheticRowId } from "@/lib/table/tableEditing";
|
||||
import { tableOpenPageLimit } from "@/lib/table/tableOpenPageLimit";
|
||||
import { tableDataLargeValuePreviewOptions } from "@/lib/dataGrid/dataGridLargeValues";
|
||||
import { canActivateExistingDataTableTab } from "@/lib/tabs/dataTabActivation";
|
||||
@@ -339,7 +339,7 @@ export function useSidebarDataOpenRuntime() {
|
||||
const loadedTableMeta = cachedTableMeta ?? queryStore.tabs.find((item) => item.id === tabId)?.tableMeta;
|
||||
const columns = loadedTableMeta?.columns ?? [];
|
||||
const primaryKeys = loadedTableMeta?.primaryKeys ?? [];
|
||||
const includeRowId = usesSyntheticRowIdKey(effectiveDbType, primaryKeys, tableType);
|
||||
const includeRowId = shouldIncludeSyntheticRowId(effectiveDbType, primaryKeys, tableType);
|
||||
const sql = await buildTableSelectSql({
|
||||
databaseType: effectiveDbType,
|
||||
driverProfile: config?.driver_profile,
|
||||
|
||||
@@ -64,6 +64,21 @@ describe("editable query hidden primary keys", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("supports Xugu's unqualified ROWID expression for keyless base tables", () => {
|
||||
expect(
|
||||
buildQueryWithHiddenPrimaryKeys({
|
||||
sql: "SELECT * FROM APP.USERS t WHERE t.ACTIVE = 1",
|
||||
databaseType: "xugu",
|
||||
primaryKeys: ["__DBX_ROWID"],
|
||||
existingResultNames: ["ID", "NAME"],
|
||||
sourceExpressions: { __DBX_ROWID: "ROWID" },
|
||||
}),
|
||||
).toEqual({
|
||||
sql: 'SELECT *, ROWID AS "__DBX_PK_0" FROM APP.USERS t WHERE t.ACTIVE = 1',
|
||||
projections: [{ sourceName: "__DBX_ROWID", alias: "__DBX_PK_0" }],
|
||||
});
|
||||
});
|
||||
|
||||
it("preserves an Oracle FOR UPDATE clause when appending a hidden row key", () => {
|
||||
expect(
|
||||
buildQueryWithHiddenPrimaryKeys({
|
||||
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
DBX_NEO4J_ELEMENT_ID_COLUMN,
|
||||
DBX_ROWID_COLUMN,
|
||||
DBX_TDENGINE_TBNAME_COLUMN,
|
||||
canInsertTableRows,
|
||||
canDeleteExistingTdengineRows,
|
||||
canEditExistingTableRows,
|
||||
canUseKeylessRowPredicate,
|
||||
@@ -13,6 +14,7 @@ import {
|
||||
isTdengineExistingRowReadonlyColumn,
|
||||
isTableDataEditable,
|
||||
supportsDataGridTransaction,
|
||||
shouldIncludeSyntheticRowId,
|
||||
usesSyntheticRowIdKey,
|
||||
} from "@/lib/table/tableEditing";
|
||||
import type { ColumnInfo, IndexInfo } from "@/types/database";
|
||||
@@ -46,6 +48,29 @@ describe("tableEditing", () => {
|
||||
expect(editablePrimaryKeys("oceanbase-oracle", [column("ID", true), column("NAME")], "TABLE")).toEqual(["ID"]);
|
||||
});
|
||||
|
||||
it("uses Xugu ROWID for ordinary, partitioned, and temporary tables but not views", () => {
|
||||
const columns = [column("ID"), column("VALUE")];
|
||||
expect(editablePrimaryKeys("xugu", columns, "TABLE")).toEqual([DBX_ROWID_COLUMN]);
|
||||
expect(editablePrimaryKeys("xugu", columns, "PARTITIONED TABLE")).toEqual([DBX_ROWID_COLUMN]);
|
||||
expect(editablePrimaryKeys("xugu", columns, "TEMPORARY TABLE")).toEqual([DBX_ROWID_COLUMN]);
|
||||
expect(editablePrimaryKeys("xugu", columns, "VIEW")).toEqual([]);
|
||||
expect(usesSyntheticRowIdKey("xugu", [DBX_ROWID_COLUMN], "TABLE")).toBe(true);
|
||||
expect(usesSyntheticRowIdKey("xugu", [DBX_ROWID_COLUMN], "PARTITIONED TABLE")).toBe(true);
|
||||
expect(usesSyntheticRowIdKey("xugu", [DBX_ROWID_COLUMN], "TEMPORARY TABLE")).toBe(true);
|
||||
expect(usesSyntheticRowIdKey("xugu", [DBX_ROWID_COLUMN], "VIEW")).toBe(false);
|
||||
expect(isTableDataEditable("xugu", [DBX_ROWID_COLUMN], "TABLE")).toBe(true);
|
||||
expect(isTableDataEditable("xugu", [DBX_ROWID_COLUMN], "VIEW")).toBe(false);
|
||||
expect(canInsertTableRows("xugu")).toBe(true);
|
||||
});
|
||||
|
||||
it("includes Xugu ROWID while cold table metadata has no declared primary keys", () => {
|
||||
expect(shouldIncludeSyntheticRowId("xugu", [], "TABLE")).toBe(true);
|
||||
expect(shouldIncludeSyntheticRowId("xugu", [], "PARTITIONED TABLE")).toBe(true);
|
||||
expect(shouldIncludeSyntheticRowId("xugu", [], "TEMPORARY TABLE")).toBe(true);
|
||||
expect(shouldIncludeSyntheticRowId("xugu", [], "VIEW")).toBe(false);
|
||||
expect(shouldIncludeSyntheticRowId("oracle", [], "TABLE")).toBe(false);
|
||||
});
|
||||
|
||||
it("treats view data tabs as readonly", () => {
|
||||
expect(isTableDataEditable("oracle", [DBX_ROWID_COLUMN], "VIEW")).toBe(false);
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { DatabaseType } from "@/types/database";
|
||||
import { isSchemaAware, usesTreeSchemaMode } from "@/lib/database/databaseFeatureSupport";
|
||||
|
||||
export type SyntheticEditKey = "oracle-rowid" | "neo4j-element-id";
|
||||
export type SyntheticEditKey = "oracle-rowid" | "xugu-rowid" | "neo4j-element-id";
|
||||
|
||||
export interface TableDataCapability {
|
||||
insert: boolean;
|
||||
@@ -158,6 +158,20 @@ const DATABASE_CAPABILITY_OVERRIDES: Partial<Record<DatabaseType, Partial<Databa
|
||||
oracle: {
|
||||
syntheticKey: "oracle-rowid",
|
||||
},
|
||||
xugu: {
|
||||
// Xugu exposes a stable ROWID pseudo-column for base, partitioned and
|
||||
// temporary tables. Keep this capability scoped to Xugu instead of
|
||||
// broadening Oracle-compatible behavior for other drivers.
|
||||
syntheticKey: "xugu-rowid",
|
||||
tableData: {
|
||||
insert: true,
|
||||
updateRequiresPrimaryKey: false,
|
||||
deleteRequiresPrimaryKey: false,
|
||||
keylessRowPredicate: true,
|
||||
requiresTransactionalTableForExistingRows: false,
|
||||
transaction: true,
|
||||
},
|
||||
},
|
||||
"oceanbase-oracle": {
|
||||
syntheticKey: "oracle-rowid",
|
||||
},
|
||||
|
||||
@@ -19,7 +19,7 @@ export function editablePrimaryKeys(databaseType: DatabaseType | undefined, colu
|
||||
if (isViewTableType(tableType)) return primaryKeys;
|
||||
if (databaseType === "tdengine" && primaryKeys.length > 0 && isTdengineStableTableType(tableType)) return [DBX_TDENGINE_TBNAME_COLUMN, ...primaryKeys];
|
||||
const syntheticKey = getDatabaseCapability(databaseType).syntheticKey;
|
||||
if (syntheticKey === "oracle-rowid" && primaryKeys.length === 0) return [DBX_ROWID_COLUMN];
|
||||
if ((syntheticKey === "oracle-rowid" || syntheticKey === "xugu-rowid") && primaryKeys.length === 0) return [DBX_ROWID_COLUMN];
|
||||
if (syntheticKey === "neo4j-element-id" && primaryKeys.length === 0) return [DBX_NEO4J_ELEMENT_ID_COLUMN];
|
||||
return primaryKeys;
|
||||
}
|
||||
@@ -93,12 +93,23 @@ export function hiveTablePropertiesIndicateTransactional(result: { rows: readonl
|
||||
export function usesSyntheticRowIdKey(databaseType: DatabaseType | undefined, primaryKeys: string[], tableType?: string): boolean {
|
||||
if (isViewTableType(tableType)) return false;
|
||||
const syntheticKey = getDatabaseCapability(databaseType).syntheticKey;
|
||||
return primaryKeys.length === 1 && ((syntheticKey === "oracle-rowid" && primaryKeys[0].toUpperCase() === DBX_ROWID_COLUMN) || (syntheticKey === "neo4j-element-id" && primaryKeys[0] === DBX_NEO4J_ELEMENT_ID_COLUMN));
|
||||
return primaryKeys.length === 1 && (((syntheticKey === "oracle-rowid" || syntheticKey === "xugu-rowid") && primaryKeys[0].toUpperCase() === DBX_ROWID_COLUMN) || (syntheticKey === "neo4j-element-id" && primaryKeys[0] === DBX_NEO4J_ELEMENT_ID_COLUMN));
|
||||
}
|
||||
|
||||
/**
|
||||
* Table-data tabs may start before metadata has populated declared primary keys.
|
||||
* Xugu base/partitioned/temp tables can still be addressed safely with ROWID,
|
||||
* so request the hidden projection during that cold-cache window as well.
|
||||
*/
|
||||
export function shouldIncludeSyntheticRowId(databaseType: DatabaseType | undefined, primaryKeys: string[], tableType?: string): boolean {
|
||||
if (isViewTableType(tableType)) return false;
|
||||
if (usesSyntheticRowIdKey(databaseType, primaryKeys, tableType)) return true;
|
||||
return databaseType === "xugu" && primaryKeys.length === 0;
|
||||
}
|
||||
|
||||
export function isHiddenGridColumn(databaseType: DatabaseType | undefined, column: string, primaryKeys: string[], tableType?: string): boolean {
|
||||
if (databaseType === "neo4j" && column === DBX_NEO4J_ELEMENT_ID_COLUMN) return true;
|
||||
return usesSyntheticRowIdKey(databaseType, primaryKeys, tableType) && column.toUpperCase() === DBX_ROWID_COLUMN;
|
||||
return shouldIncludeSyntheticRowId(databaseType, primaryKeys, tableType) && column.toUpperCase() === DBX_ROWID_COLUMN;
|
||||
}
|
||||
|
||||
export function isTdengineExistingRowReadonlyColumn(databaseType: DatabaseType | undefined, column: string, columns: ColumnInfo[]): boolean {
|
||||
|
||||
@@ -36,7 +36,7 @@ import { nextRedisCommandDb } from "@/lib/redis/redisCommandSession";
|
||||
import { isRedisMutatingCommand } from "@/lib/redis/redisCommandTable";
|
||||
import { usesAgentCursorForQuery } from "@/lib/database/databaseDriverManifest";
|
||||
import { defaultAutoCommitForDbType, supportsClearableQuerySchema, supportsTransaction } from "@/lib/database/databaseFeatureSupport";
|
||||
import { canInsertTableRows, canUseKeylessRowPredicate, DBX_ROWID_COLUMN, editablePrimaryKeys, usesSyntheticRowIdKey } from "@/lib/table/tableEditing";
|
||||
import { canInsertTableRows, canUseKeylessRowPredicate, DBX_ROWID_COLUMN, editablePrimaryKeys, shouldIncludeSyntheticRowId, usesSyntheticRowIdKey } from "@/lib/table/tableEditing";
|
||||
import { TABLE_DATA_EXPORT_PAGE_SIZE } from "@/lib/table/tableDataExport";
|
||||
import { tableMetaForDataTab } from "@/lib/table/tableDataTabMeta";
|
||||
import { isDataTabMetadataLifecycleStale } from "@/lib/sidebar/dataTabOpenPolicy";
|
||||
@@ -105,7 +105,7 @@ const groupedDisplayMetadataLimiter = new MetadataTaskLimiter(GROUPED_DISPLAY_ME
|
||||
console.debug("[DBX][metadata-load:grouped-display-limiter]", event);
|
||||
});
|
||||
const UPPERCASE_FOLDED_METADATA_TYPES = new Set<string>([...ORACLE_LIKE_METADATA_TYPES, "saphana"]);
|
||||
const HIDDEN_QUERY_KEY_DATABASE_TYPES = new Set<DatabaseType>(["mysql", "postgres", "sqlserver", "oracle"]);
|
||||
const HIDDEN_QUERY_KEY_DATABASE_TYPES = new Set<DatabaseType>(["mysql", "postgres", "sqlserver", "oracle", "xugu"]);
|
||||
const QUERY_RESULT_EXPORT_UNSUPPORTED_ERROR = "Streaming export is unsupported for this query. Simplify it or use a supported driver.";
|
||||
const BACKGROUND_CLIENT_SESSION_SUFFIXES = ["count", "explain", "export"] as const;
|
||||
const CANCEL_QUERY_TIMEOUT_MS = 10_000;
|
||||
@@ -3902,7 +3902,7 @@ export const useQueryStore = defineStore("query", () => {
|
||||
primaryKeys,
|
||||
...tableDataLargeValuePreviewOptions(effectiveDbType, tableMeta.columns, primaryKeys, limit),
|
||||
includeDatabaseName: settingsStore.editorSettings.generateSqlIncludeDatabaseName,
|
||||
includeRowId: usesSyntheticRowIdKey(effectiveDbType, primaryKeys, tableMeta.tableType),
|
||||
includeRowId: shouldIncludeSyntheticRowId(effectiveDbType, primaryKeys, tableMeta.tableType),
|
||||
whereInput: tab.whereInput,
|
||||
injectDefaultTimeSeriesWhere: true,
|
||||
orderBy,
|
||||
@@ -4813,7 +4813,7 @@ export const useQueryStore = defineStore("query", () => {
|
||||
return matches.length === 1 && matches[0]?.type === "table";
|
||||
}
|
||||
|
||||
async function resolveOracleRowIdSafety(tab: QueryTab, loaded: LoadedEditableSource): Promise<boolean> {
|
||||
async function resolveOracleRowIdSafety(tab: QueryTab, loaded: LoadedEditableSource, databaseType: DatabaseType): Promise<boolean> {
|
||||
if (oracleRowIdIsSafeForQuery(tab, loaded)) return true;
|
||||
if (loaded.tableMeta.tableType?.trim()) return false;
|
||||
|
||||
@@ -4830,7 +4830,7 @@ export const useQueryStore = defineStore("query", () => {
|
||||
database: loaded.tableMeta.database ?? tab.database,
|
||||
schema: loaded.tableMeta.schema,
|
||||
tableName: loaded.tableMeta.tableName,
|
||||
databaseType: "oracle",
|
||||
databaseType,
|
||||
driverProfile: connection?.driver_profile || connection?.db_type,
|
||||
catalog: loaded.tableMeta.catalog,
|
||||
},
|
||||
@@ -4858,7 +4858,7 @@ export const useQueryStore = defineStore("query", () => {
|
||||
databaseType,
|
||||
primaryKeys: missingPrimaryKeys,
|
||||
existingResultNames: metadataAnalysis.selectStar ? loaded.tableMeta.columns.map((column) => column.name) : metadataAnalysis.columns.map((column) => column.resultName),
|
||||
sourceExpressions: databaseType === "oracle" && missingPrimaryKeys.includes(DBX_ROWID_COLUMN) ? { [DBX_ROWID_COLUMN]: "ROWIDTOCHAR(ROWID)" } : undefined,
|
||||
sourceExpressions: missingPrimaryKeys.includes(DBX_ROWID_COLUMN) && (databaseType === "oracle" || databaseType === "xugu") ? { [DBX_ROWID_COLUMN]: databaseType === "oracle" ? "ROWIDTOCHAR(ROWID)" : "ROWID" } : undefined,
|
||||
});
|
||||
if (!rewritten) return unchanged;
|
||||
queryExecutionLog("info", "hidden-primary-keys", {
|
||||
@@ -4885,13 +4885,14 @@ export const useQueryStore = defineStore("query", () => {
|
||||
const hasDirectSourceProjection = analysis.columns.some((column) => Boolean(column.sourceName) && (!column.sourceKey || column.sourceKey === source.key));
|
||||
if (!wholeSourceProjected && !hasDirectSourceProjection) return unchanged;
|
||||
// Whole-source projections already include declared primary keys. Only
|
||||
// Oracle needs preflight metadata here to add ROWID for a keyless table.
|
||||
if (databaseType !== "oracle" && wholeSourceProjected) return unchanged;
|
||||
// Oracle and Xugu need preflight metadata here to add their synthetic
|
||||
// row key for a keyless base table.
|
||||
if (databaseType !== "oracle" && databaseType !== "xugu" && wholeSourceProjected) return unchanged;
|
||||
|
||||
const target = resolveEditableSourceMetadataTarget(tab, analysis, source, conn, databaseType, executionDatabase);
|
||||
const cached = getCachedTableMetadata(target.request);
|
||||
let loaded = cached ? loadedEditableSourceFromMetadata(target, cached.metadata) : undefined;
|
||||
if (!cached && databaseType === "oracle") {
|
||||
if (!cached && (databaseType === "oracle" || databaseType === "xugu")) {
|
||||
// Oracle column discovery can be slow. A star projection over a table
|
||||
// with a declared primary key already returns the complete row identity,
|
||||
// so SQL can start while the full metadata needed for editing loads.
|
||||
@@ -4922,12 +4923,12 @@ export const useQueryStore = defineStore("query", () => {
|
||||
if (loaded.tableMeta.tableType?.toUpperCase().includes("VIEW")) return unchanged;
|
||||
const columnPrimaryKeys = loaded.tableMeta.columns.filter((column) => column.is_primary_key).map((column) => column.name);
|
||||
const primaryKeys = databaseType === "oracle" ? loaded.tableMeta.primaryKeys : editablePrimaryKeys(databaseType, loaded.tableMeta.columns, loaded.tableMeta.tableType);
|
||||
const syntheticOracleRowId = databaseType === "oracle" && usesSyntheticRowIdKey(databaseType, primaryKeys, loaded.tableMeta.tableType);
|
||||
// Oracle base tables without a natural identifier use the same ROWID
|
||||
// identity as table-data tabs. Confirm the object is a base table because
|
||||
// selecting ROWID from a view can fail with ORA-01445.
|
||||
if (syntheticOracleRowId && !(await resolveOracleRowIdSafety(tab, loaded))) return unchanged;
|
||||
const declaredPrimaryKeys = databaseType === "oracle" && !syntheticOracleRowId ? primaryKeys : columnPrimaryKeys;
|
||||
const syntheticRowId = (databaseType === "oracle" || databaseType === "xugu") && usesSyntheticRowIdKey(databaseType, primaryKeys, loaded.tableMeta.tableType);
|
||||
// Base tables without a natural identifier use the same ROWID identity
|
||||
// as table-data tabs (Oracle and Xugu). Confirm the object is a base
|
||||
// table because selecting ROWID from a view can fail with ORA-01445.
|
||||
if (syntheticRowId && !(await resolveOracleRowIdSafety(tab, loaded, databaseType))) return unchanged;
|
||||
const declaredPrimaryKeys = databaseType === "oracle" && !syntheticRowId ? primaryKeys : columnPrimaryKeys;
|
||||
return buildHiddenPrimaryKeyPreparation(tab, sql, databaseType, loaded, primaryKeys, declaredPrimaryKeys, traceId, elapsed);
|
||||
} catch (error) {
|
||||
// Metadata enrichment is optional. Query execution must retain its prior
|
||||
|
||||
Reference in new issue
Block a user