mirror of
https://github.com/t8y2/dbx.git
synced 2026-10-19 16:16:45 +00:00
feat(data-generate): support OceanBase Oracle INSERT ALL batches
This commit is contained in:
@@ -154,6 +154,42 @@ describe("data generation background sessions", () => {
|
||||
expect(session.results[0]?.cancelled).toBe(true);
|
||||
});
|
||||
|
||||
it("counts only the first 100 OceanBase rows when the second INSERT ALL statement fails", async () => {
|
||||
mocks.executeMultiWithProgress.mockResolvedValueOnce([{ columns: [], rows: [], affected_rows: 100, execution_time_ms: 1 } as QueryResult, { columns: ["Error"], rows: [["duplicate key"]], affected_rows: 0, execution_time_ms: 1, execution_error: true } as QueryResult]);
|
||||
const session = startDataGenerateSession(
|
||||
{
|
||||
connectionId: "ob-connection",
|
||||
database: "oracletest",
|
||||
prefillSchema: "APP",
|
||||
databaseType: "oceanbase-oracle",
|
||||
label: "APP.Items",
|
||||
targets: [
|
||||
{
|
||||
tableName: "Items",
|
||||
schema: "APP",
|
||||
database: "oracletest",
|
||||
targetRowCount: 101,
|
||||
isSample: false,
|
||||
columns: ["ID"],
|
||||
rows: [],
|
||||
sql: "",
|
||||
statements: [],
|
||||
resolvedColumns: [{ columnName: "ID", dataType: "NUMBER", rowCount: 101, generatorKey: "sequence", generatorParams: { startValue: 1, increment: 1 } }],
|
||||
},
|
||||
],
|
||||
options: { continueOnError: false, truncate: false, useTransaction: false, extendedInsert: true, timeoutSecs: 0, batchRows: 101 },
|
||||
},
|
||||
{ invalidateMetadataCache: vi.fn() },
|
||||
);
|
||||
await vi.waitFor(() => expect(session.status).toBe("completed"));
|
||||
expect(mocks.executeMultiWithProgress).toHaveBeenCalledOnce();
|
||||
expect(mocks.executeMultiWithProgress.mock.calls[0]![2].match(/INSERT ALL/g)).toHaveLength(2);
|
||||
expect(session.results[0]).toMatchObject({ table: "Items", total: 101, ok: 100, err: 1, error: "duplicate key" });
|
||||
const task = useExportTracker().tasks.value.find((candidate) => candidate.exportId === session.id)!;
|
||||
expect(task.status).toBe("Error");
|
||||
expect(task.rowsExported).toBe(100);
|
||||
});
|
||||
|
||||
it("retains per-table failures in the resumable result", async () => {
|
||||
mocks.executeMultiWithProgress.mockResolvedValueOnce([
|
||||
{
|
||||
|
||||
@@ -2016,7 +2016,7 @@ export function formatGeneratedRowValues(config: TableGenerateConfig, databaseTy
|
||||
* on backends that report failures inside the result array instead of throwing.
|
||||
*/
|
||||
export function generateInsertBatches(databaseType: DatabaseType | undefined, state: TableGenerateChunkState, valueRows: string[], forceSingleRow = false): { statements: string[]; rowsPerStatement: number[] } {
|
||||
if (databaseType === "oracle") {
|
||||
if (databaseType === "oracle" || databaseType === "oceanbase-oracle") {
|
||||
const statements = buildOracleInsertStatements(state.targetTable, state.columnList, valueRows);
|
||||
return { statements, rowsPerStatement: oracleRowsPerStatement(valueRows, statements) };
|
||||
}
|
||||
|
||||
+58
-6
@@ -1,7 +1,7 @@
|
||||
import { strict as assert } from "node:assert";
|
||||
import { test } from "vitest";
|
||||
import { BackendErrorException } from "../../apps/desktop/src/lib/backend/errorUtils.ts";
|
||||
import { createTableGenerateState, generateInsertBatches, type TableGenerateConfig } from "../../apps/desktop/src/lib/dataGrid/dataGenerate.ts";
|
||||
import { createTableGenerateState, formatGeneratedRowValues, generateInsertBatches, generateTableData, supportsGeneratedMultiRowValues, type TableGenerateConfig } from "../../apps/desktop/src/lib/dataGrid/dataGenerate.ts";
|
||||
import { errorMessage, isQueryCanceledError, summarizeBatchResults } from "../../apps/desktop/src/lib/dataGrid/generateInsertAccounting.ts";
|
||||
import type { QueryResult } from "../../apps/desktop/src/types/database.ts";
|
||||
|
||||
@@ -62,6 +62,61 @@ test("generateInsertBatches mirrors Oracle INSERT ALL chunking", () => {
|
||||
assert.deepEqual(single.rowsPerStatement, [1]);
|
||||
});
|
||||
|
||||
test("OceanBase Oracle generation batches 0/1/2/100/101 rows with matching statement accounting", () => {
|
||||
for (const [count, expectedCounts] of [
|
||||
[0, []],
|
||||
[1, [1]],
|
||||
[2, [2]],
|
||||
[100, [100]],
|
||||
[101, [100, 1]],
|
||||
] as const) {
|
||||
const config = makeConfig(count);
|
||||
const generated = generateTableData(config, "oceanbase-oracle");
|
||||
const generationState = createTableGenerateState(config, "oceanbase-oracle");
|
||||
const formattedRows = generated.rows.map((row) => formatGeneratedRowValues(config, "oceanbase-oracle", generationState, row));
|
||||
const batches = generateInsertBatches("oceanbase-oracle", generationState, formattedRows);
|
||||
assert.equal(generated.rows.length, count);
|
||||
assert.deepEqual(generated.statements, batches.statements);
|
||||
assert.deepEqual(batches.rowsPerStatement, expectedCounts);
|
||||
assert.equal(generated.statements.length, expectedCounts.length);
|
||||
for (const [index, sql] of generated.statements.entries()) {
|
||||
if (count === 1) assert.match(sql, /^INSERT INTO /);
|
||||
else {
|
||||
assert.match(sql, /^INSERT ALL\n/);
|
||||
assert.match(sql, /SELECT 1 FROM DUAL;$/);
|
||||
assert.equal(sql.match(/\n INTO /g)?.length, expectedCounts[index]);
|
||||
}
|
||||
assert.doesNotMatch(sql, /VALUES\s*\([^;]+\),\s*\(/s);
|
||||
}
|
||||
}
|
||||
assert.equal(supportsGeneratedMultiRowValues("oceanbase-oracle"), false);
|
||||
});
|
||||
|
||||
test("OceanBase Oracle generation preserves quoted names and typed temporal and null values", () => {
|
||||
const generated = generateTableData(
|
||||
{
|
||||
tableName: "Items",
|
||||
schema: "APP",
|
||||
database: "oracletest",
|
||||
rowCount: 2,
|
||||
columns: [
|
||||
{ columnName: "ID", dataType: "NUMBER", rowCount: 2, generatorKey: "sequence", generatorParams: { startValue: 1, increment: 1 } },
|
||||
{ columnName: "Name", dataType: "VARCHAR2(30)", rowCount: 2, generatorKey: "enum", generatorParams: { values: "O'Brien" } },
|
||||
{ columnName: "CREATED", dataType: "DATE", rowCount: 2, generatorKey: "enum", generatorParams: { values: "2026-10-09" } },
|
||||
{ columnName: "STAMP", dataType: "TIMESTAMP(6)", rowCount: 2, generatorKey: "enum", generatorParams: { values: "2026-10-09 12:34:56" } },
|
||||
{ columnName: "EMPTY", dataType: "VARCHAR2(30)", rowCount: 2, generatorKey: "enum", generatorParams: { values: "unused", includeNull: true, nullPercent: 100 } },
|
||||
],
|
||||
},
|
||||
"oceanbase-oracle",
|
||||
);
|
||||
assert.equal(generated.statements.length, 1);
|
||||
assert.match(generated.sql, /INTO "APP"\."Items" \("ID", "Name", "CREATED", "STAMP", "EMPTY"\)/);
|
||||
assert.match(generated.sql, /'O''Brien'/);
|
||||
assert.match(generated.sql, /TO_DATE\('2026-10-09', 'YYYY-MM-DD'\)/);
|
||||
assert.match(generated.sql, /TO_TIMESTAMP\('2026-10-09 12:34:56', 'YYYY-MM-DD HH24:MI:SS'\)/);
|
||||
assert.match(generated.sql, /, NULL\)/);
|
||||
});
|
||||
|
||||
test("generateInsertBatches returns nothing for an empty batch", () => {
|
||||
const batches = generateInsertBatches("mysql", state("mysql", 0), []);
|
||||
assert.deepEqual(batches.statements, []);
|
||||
@@ -78,10 +133,7 @@ test("summarizeBatchResults counts every row when all statements succeed", () =>
|
||||
test("summarizeBatchResults ignores failed statements that do not throw", () => {
|
||||
// Sequential executor and the MySQL batch route resolve with the failure
|
||||
// recorded in the result array instead of rejecting.
|
||||
const outcome = summarizeBatchResults(
|
||||
[result({ affected: 2 }), result({ executionError: true, error: { detail: "Duplicate entry" } }), result({ affected: 1 })],
|
||||
[2, 5, 1],
|
||||
);
|
||||
const outcome = summarizeBatchResults([result({ affected: 2 }), result({ executionError: true, error: { detail: "Duplicate entry" } }), result({ affected: 1 })], [2, 5, 1]);
|
||||
assert.equal(outcome.insertedRows, 3);
|
||||
assert.equal(outcome.failed, true);
|
||||
assert.equal(outcome.error, "Duplicate entry");
|
||||
@@ -127,7 +179,7 @@ test("isQueryCanceledError recognizes the backend cancellation message", () => {
|
||||
});
|
||||
|
||||
test("isQueryCanceledError does not treat SQL failures as cancellations", () => {
|
||||
assert.equal(isQueryCanceledError(new Error("relation \"users\" does not exist")), false);
|
||||
assert.equal(isQueryCanceledError(new Error('relation "users" does not exist')), false);
|
||||
assert.equal(isQueryCanceledError(new Error("Deadlock found when trying to get lock")), false);
|
||||
assert.equal(isQueryCanceledError(new BackendErrorException({ message: "Syntax error" })), false);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user