mirror of
https://github.com/t8y2/dbx.git
synced 2026-10-19 16:16:45 +00:00
feat(gaussdb): enable manual transaction management
This commit is contained in:
+16
@@ -156,6 +156,22 @@ describe("EditorToolbar commit/rollback visibility", () => {
|
||||
host.remove();
|
||||
});
|
||||
|
||||
it("hides Commit/Rollback for a clean GaussDB sticky manual session", async () => {
|
||||
const host = await mountToolbar({ dbType: "gaussdb", stickyProvenReadOnlyState: true, txnPossiblyDirty: false, txnSessionId: "txn-1" });
|
||||
|
||||
expect(host.querySelector(commitSelector)).toBeNull();
|
||||
expect(host.querySelector(rollbackSelector)).toBeNull();
|
||||
host.remove();
|
||||
});
|
||||
|
||||
it("shows Commit/Rollback once the GaussDB sticky session is dirty", async () => {
|
||||
const host = await mountToolbar({ dbType: "gaussdb", stickyProvenReadOnlyState: true, txnPossiblyDirty: true, txnSessionId: "txn-1" });
|
||||
|
||||
expect(host.querySelector(commitSelector)).not.toBeNull();
|
||||
expect(host.querySelector(rollbackSelector)).not.toBeNull();
|
||||
host.remove();
|
||||
});
|
||||
|
||||
it("keeps the legacy always-visible rule for non-sticky dialects with a session", async () => {
|
||||
const host = await mountToolbar({ dbType: "jdbc", stickyProvenReadOnlyState: false, txnPossiblyDirty: false, txnSessionId: "txn-1" });
|
||||
|
||||
|
||||
@@ -176,6 +176,8 @@ describe("database and schema qualifiers", () => {
|
||||
describe("supportsTransaction", () => {
|
||||
it("returns true for supported database types", () => {
|
||||
expect(supportsTransaction("postgres")).toBe(true);
|
||||
expect(supportsTransaction("gaussdb")).toBe(true);
|
||||
expect(supportsTransaction("opengauss")).toBe(true);
|
||||
expect(supportsTransaction("mysql")).toBe(true);
|
||||
expect(supportsTransaction("oracle")).toBe(true);
|
||||
expect(supportsTransaction("jdbc")).toBe(true);
|
||||
@@ -215,6 +217,8 @@ describe("defaultAutoCommitForDbType", () => {
|
||||
it("honors the configured default transaction mode", () => {
|
||||
expect(defaultAutoCommitForDbType("mysql", "manual")).toBe(false);
|
||||
expect(defaultAutoCommitForDbType("postgres", "manual")).toBe(false);
|
||||
expect(defaultAutoCommitForDbType("gaussdb", "manual")).toBe(false);
|
||||
expect(defaultAutoCommitForDbType("opengauss", "manual")).toBe(false);
|
||||
expect(defaultAutoCommitForDbType("sqlserver", "manual")).toBe(false);
|
||||
expect(defaultAutoCommitForDbType("sqlserver", "auto")).toBe(true);
|
||||
expect(defaultAutoCommitForDbType("oracle", "manual")).toBe(false);
|
||||
@@ -249,11 +253,13 @@ describe("usesOracleStickyTransactionState", () => {
|
||||
});
|
||||
|
||||
describe("usesProvenReadOnlyStickyTransactionState", () => {
|
||||
it("enables the sticky toolbar for the Oracle family, MySQL and PostgreSQL", () => {
|
||||
it("enables the sticky toolbar for the Oracle family, MySQL and the PostgreSQL family", () => {
|
||||
expect(usesProvenReadOnlyStickyTransactionState("oracle")).toBe(true);
|
||||
expect(usesProvenReadOnlyStickyTransactionState("oceanbase-oracle")).toBe(true);
|
||||
expect(usesProvenReadOnlyStickyTransactionState("mysql")).toBe(true);
|
||||
expect(usesProvenReadOnlyStickyTransactionState("postgres")).toBe(true);
|
||||
expect(usesProvenReadOnlyStickyTransactionState("gaussdb")).toBe(true);
|
||||
expect(usesProvenReadOnlyStickyTransactionState("opengauss")).toBe(true);
|
||||
});
|
||||
|
||||
it("keeps databases without manual-transaction support on the legacy toolbar", () => {
|
||||
@@ -267,7 +273,7 @@ describe("usesProvenReadOnlyStickyTransactionState", () => {
|
||||
it("only enables sticky state where manual mode is reachable", () => {
|
||||
// The sticky UX gates commit/rollback in manual mode; a member without
|
||||
// transaction support could never reach it. Guards against drift.
|
||||
for (const dbType of ["oracle", "oceanbase-oracle", "mysql", "postgres"]) {
|
||||
for (const dbType of ["oracle", "oceanbase-oracle", "mysql", "postgres", "gaussdb", "opengauss"]) {
|
||||
expect(supportsTransaction(dbType)).toBe(true);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -281,7 +281,7 @@ export function usesPostgresLikeStructureCopy(dbType?: DatabaseType): boolean {
|
||||
return !!dbType && PG_LIKE_STRUCTURE_TYPES.has(dbType);
|
||||
}
|
||||
|
||||
const TRANSACTION_SUPPORTED_TYPES: readonly string[] = ["postgres", "mysql", "oracle", "jdbc", "oceanbase-oracle", "dameng", "sqlserver"];
|
||||
const TRANSACTION_SUPPORTED_TYPES: readonly string[] = ["postgres", "gaussdb", "opengauss", "mysql", "oracle", "jdbc", "oceanbase-oracle", "dameng", "sqlserver"];
|
||||
|
||||
/** Oracle-family databases, kept ONLY for the Oracle-specific ALTER SESSION SET
|
||||
* CURRENT_SCHEMA compensation in queryStore. Do not use for toolbar/dirty-bit
|
||||
@@ -294,9 +294,10 @@ const ORACLE_STICKY_TRANSACTION_TYPES: ReadonlySet<string> = new Set(["oracle",
|
||||
* (crates/dbx-core/src/query/mod.rs + sql_risk.rs `prove_read_only_for_database`).
|
||||
* Every member must also be in TRANSACTION_SUPPORTED_TYPES above: a database
|
||||
* cannot reach manual mode (and this UX) without explicit transaction control
|
||||
* (#9018). Family members like doris/kingbase join only when their transaction
|
||||
* support lands. */
|
||||
const PROVEN_READ_ONLY_STICKY_TYPES: ReadonlySet<string> = new Set(["oracle", "oceanbase-oracle", "mysql", "postgres"]);
|
||||
* (#9018). The PostgreSQL family (PostgreSQL, GaussDB, openGauss) and MySQL use
|
||||
* the strict `sql_risk` proof; Oracle keeps its lexical classifier. Family
|
||||
* members like doris/kingbase join only when their transaction support lands. */
|
||||
const PROVEN_READ_ONLY_STICKY_TYPES: ReadonlySet<string> = new Set(["oracle", "oceanbase-oracle", "mysql", "postgres", "gaussdb", "opengauss"]);
|
||||
|
||||
/**
|
||||
* Returns true if the given database type supports explicit transaction control
|
||||
|
||||
@@ -99,6 +99,22 @@ describe("queryStore default transaction mode", () => {
|
||||
expect(store.tabs.find((item) => item.id === tabId)!.autoCommit).toBe(false);
|
||||
});
|
||||
|
||||
it("creates a GaussDB query tab in manual transaction mode when configured", async () => {
|
||||
editorSettings.defaultTransactionMode = "manual";
|
||||
mocks.getConnectionConfig.mockReturnValue({
|
||||
id: "gaussdb-1",
|
||||
name: "GaussDB",
|
||||
db_type: "gaussdb",
|
||||
database: "postgres",
|
||||
});
|
||||
|
||||
const { useQueryStore } = await import("@/stores/queryStore");
|
||||
const store = useQueryStore();
|
||||
const tabId = store.createTab("gaussdb-1", "postgres", "Query", "query", "public");
|
||||
|
||||
expect(store.tabs.find((item) => item.id === tabId)!.autoCommit).toBe(false);
|
||||
});
|
||||
|
||||
it("creates a query tab in auto-commit mode by default", async () => {
|
||||
const { useQueryStore } = await import("@/stores/queryStore");
|
||||
const store = useQueryStore();
|
||||
|
||||
+4
@@ -31,6 +31,8 @@ vi.mock("@/lib/backend/api", () => ({
|
||||
vi.mock("@/stores/connectionStore", () => ({
|
||||
useConnectionStore: () => ({
|
||||
ensureConnected: vi.fn().mockResolvedValue(undefined),
|
||||
ensureDatabaseCompatibilityMode: vi.fn().mockResolvedValue(undefined),
|
||||
databaseCompatibilityMode: vi.fn().mockReturnValue(undefined),
|
||||
getConfig: mocks.getConnectionConfig,
|
||||
recordConnectionLostError: vi.fn(),
|
||||
}),
|
||||
@@ -524,6 +526,8 @@ describe("queryStore Oracle/OceanBase manual-transaction sticky state (behavior
|
||||
describe.each([
|
||||
{ label: "MySQL", configId: "mysql-1", database: "test", dbType: "mysql", txnId: "txn-mysql" },
|
||||
{ label: "PostgreSQL", configId: "pg-1", database: "postgres", dbType: "postgres", txnId: "txn-pg" },
|
||||
{ label: "GaussDB", configId: "gaussdb-1", database: "postgres", dbType: "gaussdb", txnId: "txn-gaussdb" },
|
||||
{ label: "openGauss", configId: "opengauss-1", database: "postgres", dbType: "opengauss", txnId: "txn-opengauss" },
|
||||
])("queryStore $label proven-read-only sticky state", ({ configId, database, dbType, txnId }) => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
|
||||
@@ -6063,11 +6063,12 @@ fn mysql_error_is_syntax_error(error: &mysql_async::Error) -> bool {
|
||||
}
|
||||
|
||||
/// Compute per-execution-statement proven-read-only markers for the sticky
|
||||
/// manual-transaction UX (#7122 Oracle, #9018 MySQL/PostgreSQL). The user-facing
|
||||
/// classification SQL is split with the same dialect-aware splitter as the
|
||||
/// execution SQL and paired by count/position; any mismatch is fail-closed (no
|
||||
/// markers). Oracle/OceanBase-Oracle keep the lexical classifier, MySQL and
|
||||
/// PostgreSQL use the strict `sql_risk` proof; every other dialect is unproven.
|
||||
/// manual-transaction UX (#7122 Oracle, #9018 MySQL/PostgreSQL, ... GaussDB).
|
||||
/// The user-facing classification SQL is split with the same dialect-aware
|
||||
/// splitter as the execution SQL and paired by count/position; any mismatch is
|
||||
/// fail-closed (no markers). Oracle/OceanBase-Oracle keep the lexical
|
||||
/// classifier, the PostgreSQL family (MySQL is separate) use the strict
|
||||
/// `sql_risk` proof; every other dialect is unproven.
|
||||
fn classify_manual_transaction_statements(
|
||||
database_type: Option<DatabaseType>,
|
||||
execution_statement_count: usize,
|
||||
@@ -6076,7 +6077,12 @@ fn classify_manual_transaction_statements(
|
||||
let Some(database_type) = database_type.filter(|database_type| {
|
||||
matches!(
|
||||
database_type,
|
||||
DatabaseType::Oracle | DatabaseType::OceanbaseOracle | DatabaseType::Mysql | DatabaseType::Postgres
|
||||
DatabaseType::Oracle
|
||||
| DatabaseType::OceanbaseOracle
|
||||
| DatabaseType::Mysql
|
||||
| DatabaseType::Postgres
|
||||
| DatabaseType::Gaussdb
|
||||
| DatabaseType::OpenGauss
|
||||
)
|
||||
}) else {
|
||||
return Vec::new();
|
||||
@@ -6092,7 +6098,7 @@ fn classify_manual_transaction_statements(
|
||||
user_statements
|
||||
.iter()
|
||||
.map(|statement| match database_type {
|
||||
DatabaseType::Mysql | DatabaseType::Postgres => {
|
||||
DatabaseType::Mysql | DatabaseType::Postgres | DatabaseType::Gaussdb | DatabaseType::OpenGauss => {
|
||||
crate::sql_risk::prove_read_only_for_database(statement, database_type)
|
||||
== crate::sql_risk::ReadProof::ProvenReadOnly
|
||||
}
|
||||
@@ -6447,7 +6453,14 @@ pub async fn execute_in_manual_transaction_with_options(
|
||||
);
|
||||
if matches!(
|
||||
db_type,
|
||||
Some(DatabaseType::Oracle | DatabaseType::OceanbaseOracle | DatabaseType::Mysql | DatabaseType::Postgres)
|
||||
Some(
|
||||
DatabaseType::Oracle
|
||||
| DatabaseType::OceanbaseOracle
|
||||
| DatabaseType::Mysql
|
||||
| DatabaseType::Postgres
|
||||
| DatabaseType::Gaussdb
|
||||
| DatabaseType::OpenGauss
|
||||
)
|
||||
) {
|
||||
result = result.with_manual_transaction_no_statement();
|
||||
}
|
||||
@@ -12095,6 +12108,15 @@ for line in sys.stdin:
|
||||
classify_manual_transaction_statements(Some(DatabaseType::Postgres), 1, Some("SELECT * FROM users")),
|
||||
vec![true]
|
||||
);
|
||||
// GaussDB/openGauss share the PostgreSQL-family proof.
|
||||
assert_eq!(
|
||||
classify_manual_transaction_statements(Some(DatabaseType::Gaussdb), 1, Some("SELECT * FROM users")),
|
||||
vec![true]
|
||||
);
|
||||
assert_eq!(
|
||||
classify_manual_transaction_statements(Some(DatabaseType::OpenGauss), 1, Some("UPDATE users SET x = 1")),
|
||||
vec![false]
|
||||
);
|
||||
// Mixed script: every statement is classified individually.
|
||||
assert_eq!(
|
||||
classify_manual_transaction_statements(Some(DatabaseType::Mysql), 2, Some("SELECT 1; DELETE FROM t")),
|
||||
|
||||
@@ -1133,14 +1133,17 @@ const POSTGRES_PROOF_SAFE_FUNCTIONS: &[&str] = &[
|
||||
];
|
||||
|
||||
/// Prove that a single SQL statement is, by DBX's strict heuristic, an
|
||||
/// ordinary read. Only MySQL/PostgreSQL take part in this proof; every other
|
||||
/// database type is Unproven so its manual-transaction toolbar keeps the
|
||||
/// legacy behavior. Oracle keeps its own lexical classifier
|
||||
/// (`is_oracle_proven_read_only_statement`) — do not reroute it here.
|
||||
/// ordinary read. Only MySQL and the PostgreSQL family (PostgreSQL, GaussDB,
|
||||
/// openGauss) take part in this proof; every other database type is Unproven so
|
||||
/// its manual-transaction toolbar keeps the legacy behavior. Oracle keeps its
|
||||
/// own lexical classifier (`is_oracle_proven_read_only_statement`) — do not
|
||||
/// reroute it here.
|
||||
pub fn prove_read_only_for_database(sql: &str, database_type: DatabaseType) -> ReadProof {
|
||||
match database_type {
|
||||
DatabaseType::Mysql => prove_read_only_statement(sql, "mysql", MYSQL_PROOF_SAFE_FUNCTIONS),
|
||||
DatabaseType::Postgres => prove_read_only_statement(sql, "postgres", POSTGRES_PROOF_SAFE_FUNCTIONS),
|
||||
DatabaseType::Postgres | DatabaseType::Gaussdb | DatabaseType::OpenGauss => {
|
||||
prove_read_only_statement(sql, "postgres", POSTGRES_PROOF_SAFE_FUNCTIONS)
|
||||
}
|
||||
_ => ReadProof::Unproven,
|
||||
}
|
||||
}
|
||||
@@ -1374,6 +1377,24 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn proof_maps_gaussdb_family_to_postgres_proof() {
|
||||
// GaussDB/openGauss are PostgreSQL-family engines: they share the PG
|
||||
// proof so their sticky manual-transaction toolbar behaves like PG.
|
||||
for sql in ["SELECT 1", "SELECT * FROM users", "EXPLAIN SELECT * FROM t"] {
|
||||
assert_eq!(
|
||||
prove_read_only_for_database(sql, DatabaseType::Gaussdb),
|
||||
ReadProof::ProvenReadOnly,
|
||||
"expected proven for Gaussdb: {sql}"
|
||||
);
|
||||
}
|
||||
assert_eq!(
|
||||
prove_read_only_for_database("UPDATE users SET x = 1", DatabaseType::OpenGauss),
|
||||
ReadProof::Unproven
|
||||
);
|
||||
assert_eq!(prove_read_only_for_database("SELECT nextval('seq')", DatabaseType::Gaussdb), ReadProof::Unproven);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn proof_only_applies_to_enabled_dialects() {
|
||||
// Oracle keeps its own lexical classifier; the gate in query.rs must
|
||||
|
||||
Reference in New Issue
Block a user