531 lines
27 KiB
TypeScript
Vendored
531 lines
27 KiB
TypeScript
Vendored
import { describe, expect, it } from "vitest";
|
|
import type { ConnectionConfig } from "@/types/database";
|
|
import { databaseObjectTreeNodeSchema } from "@/lib/database/databaseFeatureSupport";
|
|
import {
|
|
GAUSSDB_M_JDBC_DRIVER_CLASS,
|
|
connectionDatabaseMetadataSchema,
|
|
connectionObjectTreeNodeSchema,
|
|
connectionObjectTreeQuerySchema,
|
|
connectionQueryExecutionSchema,
|
|
connectionShouldDiscoverJdbcSchemas,
|
|
connectionShouldLoadIdentifierQuote,
|
|
connectionUsesConnectionRootSchemaMode,
|
|
connectionUsesDatabaseObjectTreeMode,
|
|
effectiveDatabaseTypeForConnection,
|
|
gaussdbConnectionMode,
|
|
gaussdbCountQueryDop,
|
|
gaussdbCountQueryDopHint,
|
|
gaussdbIdentifierQuoteOverride,
|
|
gaussdbIdentifierQuoteStyle,
|
|
gaussdbTargetServerType,
|
|
inferJdbcDialect,
|
|
metadataSchemaForConnection,
|
|
objectListSchemaForConnection,
|
|
setGaussdbConnectionMode,
|
|
setGaussdbCountQueryDop,
|
|
setGaussdbIdentifierQuoteStyle,
|
|
setGaussdbTargetServerType,
|
|
supportsGaussdbIdentifierQuoteStyle,
|
|
transferDatabaseTypeForConnection,
|
|
} from "@/lib/database/jdbcDialect";
|
|
import { supportsTransfer } from "@/lib/database/databaseFeatureSupport";
|
|
import { transferObjectKindsForDatabase } from "@/lib/database/transferObjectKinds";
|
|
|
|
describe("jdbc dialect inference", () => {
|
|
it("detects InterSystems IRIS and Caché JDBC connections", () => {
|
|
expect(
|
|
inferJdbcDialect({
|
|
db_type: "jdbc",
|
|
connection_string: "jdbc:IRIS://localhost:1972/USER",
|
|
}),
|
|
).toBe("iris");
|
|
expect(
|
|
inferJdbcDialect({
|
|
db_type: "jdbc",
|
|
connection_string: "jdbc:Cache://localhost:1972/USER",
|
|
}),
|
|
).toBe("iris");
|
|
expect(
|
|
inferJdbcDialect({
|
|
db_type: "jdbc",
|
|
jdbc_driver_class: "com.intersystems.jdbc.IRISDriver",
|
|
}),
|
|
).toBe("iris");
|
|
expect(
|
|
inferJdbcDialect({
|
|
db_type: "jdbc",
|
|
jdbc_driver_paths: ["/drivers/intersystems-jdbc-3.10.5.jar"],
|
|
}),
|
|
).toBe("iris");
|
|
// Legacy Caché connections pick the CacheDB.jar from the driver store; the
|
|
// jar file name and driver label are the only Intersystems markers there.
|
|
expect(
|
|
inferJdbcDialect({
|
|
db_type: "jdbc",
|
|
jdbc_driver_paths: ["/drivers/CacheDB.jar"],
|
|
}),
|
|
).toBe("iris");
|
|
expect(
|
|
inferJdbcDialect({
|
|
db_type: "jdbc",
|
|
driver_label: "CacheDB",
|
|
}),
|
|
).toBe("iris");
|
|
expect(inferJdbcDialect({ db_type: "jdbc", driver_profile: "cache" })).toBe("iris");
|
|
});
|
|
|
|
it("uses IRIS table preview dialect for generic JDBC IRIS connections", () => {
|
|
expect(
|
|
effectiveDatabaseTypeForConnection({
|
|
db_type: "jdbc",
|
|
connection_string: "jdbc:IRIS://localhost:1972/USER",
|
|
}),
|
|
).toBe("iris");
|
|
});
|
|
|
|
it("uses JDBC driver profiles when inferring dialect", () => {
|
|
expect(
|
|
inferJdbcDialect({
|
|
db_type: "jdbc",
|
|
driver_profile: "sqlserver",
|
|
}),
|
|
).toBe("sqlserver");
|
|
});
|
|
|
|
it("detects TDengine JDBC connections and keeps the selected database in the object tree", () => {
|
|
const connection = {
|
|
db_type: "jdbc" as const,
|
|
connection_string: "jdbc:TAOS-RS://tdengine.example:6041/",
|
|
jdbc_driver_class: "com.taosdata.jdbc.rs.RestfulDriver",
|
|
};
|
|
|
|
expect(inferJdbcDialect(connection)).toBe("tdengine");
|
|
expect(effectiveDatabaseTypeForConnection(connection)).toBe("tdengine");
|
|
expect(connectionUsesDatabaseObjectTreeMode(connection)).toBe(false);
|
|
expect(connectionObjectTreeQuerySchema(connection, "dbx_test")).toBe("dbx_test");
|
|
});
|
|
|
|
it("keeps Phoenix as generic JDBC while preserving its schema tree", () => {
|
|
const connection = { db_type: "jdbc" as const, driver_profile: "phoenix" };
|
|
|
|
expect(inferJdbcDialect(connection)).toBe("jdbc");
|
|
expect(effectiveDatabaseTypeForConnection(connection)).toBe("jdbc");
|
|
expect(connectionUsesDatabaseObjectTreeMode(connection)).toBe(false);
|
|
expect(connectionObjectTreeQuerySchema(connection, "default", "DEMO")).toBe("DEMO");
|
|
expect(connectionObjectTreeNodeSchema(connection, "default", "DEMO")).toBe("DEMO");
|
|
expect(connectionShouldLoadIdentifierQuote(connection)).toBe(true);
|
|
});
|
|
|
|
it.each([
|
|
["jdbc:oracle:thin:@//localhost:1521/XE", "oracle"],
|
|
["jdbc:dm://localhost:5236/DAMENG", "dameng"],
|
|
] as const)("uses connection-root schemas for %s", (connectionString, dialect) => {
|
|
const connection = {
|
|
db_type: "jdbc" as const,
|
|
connection_string: connectionString,
|
|
};
|
|
|
|
expect(inferJdbcDialect(connection)).toBe(dialect);
|
|
expect(connectionUsesConnectionRootSchemaMode(connection)).toBe(true);
|
|
expect(connectionUsesDatabaseObjectTreeMode(connection)).toBe(false);
|
|
expect(connectionObjectTreeQuerySchema(connection, "DBX_TEST", "DBX_TEST")).toBe("DBX_TEST");
|
|
expect(connectionObjectTreeNodeSchema(connection, "DBX_TEST", "DBX_TEST")).toBe("DBX_TEST");
|
|
});
|
|
|
|
it("detects GaussDB-compatible JDBC connections as schema-aware", () => {
|
|
const gaussdbConnection = {
|
|
db_type: "jdbc" as const,
|
|
connection_string: "jdbc:gaussdb://localhost:8000/testdb",
|
|
jdbc_driver_class: "com.huawei.gaussdb.jdbc.Driver",
|
|
};
|
|
const opengaussConnection = {
|
|
db_type: "jdbc" as const,
|
|
connection_string: "jdbc:opengauss://localhost:5432/postgres",
|
|
jdbc_driver_class: "org.opengauss.Driver",
|
|
};
|
|
|
|
expect(inferJdbcDialect(gaussdbConnection)).toBe("gaussdb");
|
|
expect(connectionUsesDatabaseObjectTreeMode(gaussdbConnection)).toBe(false);
|
|
expect(inferJdbcDialect(opengaussConnection)).toBe("opengauss");
|
|
expect(connectionUsesDatabaseObjectTreeMode(opengaussConnection)).toBe(false);
|
|
});
|
|
|
|
it("loads driver-reported identifier quotes for compatible JDBC connections", () => {
|
|
expect(connectionShouldLoadIdentifierQuote({ db_type: "jdbc", jdbc_driver_paths: ["/drivers/gaussdb.jar"] })).toBe(true);
|
|
expect(connectionShouldLoadIdentifierQuote({ db_type: "jdbc", jdbc_driver_class: "org.opengauss.Driver" })).toBe(true);
|
|
expect(connectionShouldLoadIdentifierQuote({ db_type: "jdbc", jdbc_driver_class: "org.postgresql.Driver" })).toBe(true);
|
|
expect(connectionShouldLoadIdentifierQuote({ db_type: "kingbase" })).toBe(true);
|
|
expect(connectionShouldLoadIdentifierQuote({ db_type: "gaussdb" })).toBe(true);
|
|
expect(connectionShouldLoadIdentifierQuote({ db_type: "gbase", driver_profile: "gbase8s" })).toBe(true);
|
|
expect(connectionShouldLoadIdentifierQuote({ db_type: "gbase", driver_profile: "gbase8a" })).toBe(false);
|
|
// Cloud Spanner reports a backtick for GoogleSQL and a double quote for the
|
|
// PostgreSQL dialect; both are only known from the connection.
|
|
expect(connectionShouldLoadIdentifierQuote({ db_type: "spanner" })).toBe(true);
|
|
expect(connectionShouldLoadIdentifierQuote({ db_type: "bigquery" })).toBe(false);
|
|
expect(
|
|
connectionShouldLoadIdentifierQuote({
|
|
db_type: "jdbc",
|
|
jdbc_driver_class: "org.postgresql.Driver",
|
|
external_config: { gaussdbIdentifierQuoteStyle: "backtick" },
|
|
}),
|
|
).toBe(false);
|
|
});
|
|
|
|
it("falls back to a flat table tree when GBase 8s reports no schemas", () => {
|
|
expect(connectionShouldDiscoverJdbcSchemas({ db_type: "gbase", driver_profile: "gbase8s" })).toBe(true);
|
|
expect(connectionShouldDiscoverJdbcSchemas({ db_type: "gbase", driver_profile: "gbase8a" })).toBe(false);
|
|
});
|
|
|
|
it("recognizes GaussDB reached through PostgreSQL-compatible JDBC drivers", () => {
|
|
const connection = {
|
|
db_type: "jdbc" as const,
|
|
connection_string: "jdbc:postgresql://localhost:5432/postgres",
|
|
jdbc_driver_class: "org.postgresql.Driver",
|
|
database_info: { productName: "GaussDB Kernel", driverName: "PostgreSQL JDBC Driver" },
|
|
};
|
|
|
|
expect(inferJdbcDialect(connection)).toBe("gaussdb");
|
|
expect(effectiveDatabaseTypeForConnection(connection)).toBe("gaussdb");
|
|
expect(supportsGaussdbIdentifierQuoteStyle(connection)).toBe(true);
|
|
});
|
|
|
|
it("supports persisted GaussDB identifier quote overrides", () => {
|
|
const native = { db_type: "gaussdb" as const, external_config: undefined as unknown };
|
|
const jdbc = { db_type: "jdbc" as const, jdbc_driver_paths: ["/drivers/gaussdb.jar"], external_config: { retained: true } as unknown };
|
|
|
|
expect(supportsGaussdbIdentifierQuoteStyle(native)).toBe(true);
|
|
expect(gaussdbIdentifierQuoteStyle(native)).toBe("auto");
|
|
expect(gaussdbIdentifierQuoteOverride(native)).toBeUndefined();
|
|
|
|
setGaussdbIdentifierQuoteStyle(native, "backtick");
|
|
expect(gaussdbIdentifierQuoteStyle(native)).toBe("backtick");
|
|
expect(gaussdbIdentifierQuoteOverride(native)).toBe("`");
|
|
|
|
setGaussdbIdentifierQuoteStyle(jdbc, "double");
|
|
expect(jdbc.external_config).toEqual({ retained: true, gaussdbIdentifierQuoteStyle: "double" });
|
|
expect(gaussdbIdentifierQuoteOverride(jdbc)).toBe('"');
|
|
expect(connectionShouldLoadIdentifierQuote(jdbc)).toBe(false);
|
|
|
|
setGaussdbIdentifierQuoteStyle(jdbc, "auto");
|
|
expect(jdbc.external_config).toEqual({ retained: true });
|
|
expect(connectionShouldLoadIdentifierQuote(jdbc)).toBe(true);
|
|
});
|
|
|
|
it("detects Dameng JDBC connections", () => {
|
|
const damengConnection = {
|
|
db_type: "jdbc" as const,
|
|
connection_string: "jdbc:dm://localhost:5236/DAMENG",
|
|
jdbc_driver_class: "dm.jdbc.driver.DmDriver",
|
|
};
|
|
|
|
expect(inferJdbcDialect(damengConnection)).toBe("dameng");
|
|
});
|
|
|
|
it("detects GBase JDBC connections for transfer dialect selection", () => {
|
|
const gbaseConnection = {
|
|
db_type: "jdbc" as const,
|
|
connection_string: "jdbc:gbase://localhost:5258/dbx_test",
|
|
jdbc_driver_class: "cn.gbase.Driver",
|
|
};
|
|
|
|
expect(inferJdbcDialect(gbaseConnection)).toBe("gbase");
|
|
expect(effectiveDatabaseTypeForConnection(gbaseConnection)).toBe("gbase");
|
|
|
|
// GBase 8s is Informix-based and must stay on generic jdbc (no MySQL-family transfer dialect).
|
|
const gbase8sByUrl = {
|
|
db_type: "jdbc" as const,
|
|
connection_string: "jdbc:gbasedbt-sqli://localhost:9088/dbx_test:INFORMIXSERVER=ol_gbasedbt",
|
|
jdbc_driver_class: "com.gbasedbt.jdbc.Driver",
|
|
};
|
|
const gbase8sByProfile = {
|
|
db_type: "jdbc" as const,
|
|
driver_profile: "gbase8s",
|
|
};
|
|
const gbase8sProfileWithLegacyGbaseUrl = {
|
|
db_type: "jdbc" as const,
|
|
driver_profile: "gbase8s",
|
|
connection_string: "jdbc:gbase://localhost:5258/dbx_test",
|
|
jdbc_driver_class: "cn.gbase.Driver",
|
|
};
|
|
expect(inferJdbcDialect(gbase8sByUrl)).toBe("informix");
|
|
expect(effectiveDatabaseTypeForConnection(gbase8sByUrl)).toBe("informix");
|
|
expect(inferJdbcDialect(gbase8sByProfile)).toBeUndefined();
|
|
expect(effectiveDatabaseTypeForConnection(gbase8sByProfile)).toBe("jdbc");
|
|
expect(inferJdbcDialect(gbase8sProfileWithLegacyGbaseUrl)).toBeUndefined();
|
|
expect(effectiveDatabaseTypeForConnection(gbase8sProfileWithLegacyGbaseUrl)).toBe("jdbc");
|
|
});
|
|
|
|
it("keeps doris-family mysql connections on the mysql transfer path", () => {
|
|
// Doris/SelectDB/StarRocks connections are saved as db_type=mysql with a
|
|
// doris-family driver_profile. The SQL dialect stays doris/starrocks
|
|
// (3-part catalog names), but transfer admission and object kinds must ride
|
|
// the raw mysql db_type: the standalone doris/starrocks manifest entries are
|
|
// not transfer-capable, so the effective type would hide these connections
|
|
// from the transfer dialog and drop their non-table object kinds.
|
|
for (const driver_profile of ["starrocks", "doris", "selectdb"]) {
|
|
const connection = { db_type: "mysql" as const, driver_profile };
|
|
const transferType = transferDatabaseTypeForConnection(connection);
|
|
|
|
expect(transferType).toBe("mysql");
|
|
expect(supportsTransfer(transferType)).toBe(true);
|
|
expect(transferObjectKindsForDatabase(transferType)).toContain("VIEW");
|
|
}
|
|
});
|
|
|
|
it("resolves transfer types for non-doris-family connections through the effective type", () => {
|
|
expect(transferDatabaseTypeForConnection(undefined)).toBeUndefined();
|
|
expect(transferDatabaseTypeForConnection({ db_type: "mysql" })).toBe("mysql");
|
|
expect(transferDatabaseTypeForConnection({ db_type: "postgres" })).toBe("postgres");
|
|
expect(transferDatabaseTypeForConnection({ db_type: "gbase" })).toBe("mysql");
|
|
expect(transferDatabaseTypeForConnection({ db_type: "jdbc", connection_string: "jdbc:gbase://localhost:5258/dbx_test" })).toBe("gbase");
|
|
// A generic-JDBC Doris URL keeps its raw db_type (never admitted), matching
|
|
// the pre-effective-type behavior for unknown jdbc connections.
|
|
expect(transferDatabaseTypeForConnection({ db_type: "jdbc", connection_string: "jdbc:doris://localhost:9030/dbx_test" })).toBe("jdbc");
|
|
});
|
|
|
|
it("uses Hive tree and execution semantics for Inceptor JDBC metadata", () => {
|
|
const connection = {
|
|
db_type: "jdbc" as const,
|
|
connection_string: "jdbc:hive2://inceptor.example.com:10000/default",
|
|
database_info: { productName: "Apache Hive", driverName: "Inceptor JDBC 8.37.3" },
|
|
};
|
|
|
|
expect(inferJdbcDialect(connection)).toBe("hive");
|
|
expect(effectiveDatabaseTypeForConnection(connection)).toBe("hive");
|
|
expect(connectionUsesDatabaseObjectTreeMode(connection)).toBe(false);
|
|
expect(connectionObjectTreeNodeSchema(connection, "CS")).toBe("CS");
|
|
expect(connectionQueryExecutionSchema(connection, "CS", undefined, false)).toBe("CS");
|
|
});
|
|
|
|
it.each([
|
|
{ driver_profile: "inceptor" },
|
|
{ driver_label: "Inceptor JDBC 8.37.3" },
|
|
{ jdbc_driver_class: "io.transwarp.inceptor.jdbc.InceptorDriver" },
|
|
{ jdbc_driver_paths: ["C:\\drivers\\InceptorJDBC.jar"] },
|
|
{ database_info: { driverName: "Inceptor JDBC 8.37.3" } },
|
|
{ database_info: { serverComment: "Transwarp Inceptor Server" } },
|
|
])("detects Inceptor from explicit JDBC identity %#", (identity) => {
|
|
expect(inferJdbcDialect({ db_type: "jdbc", ...identity })).toBe("hive");
|
|
});
|
|
|
|
it("recognizes Apache Hive metadata without changing generic hive2 or MySQL inference", () => {
|
|
expect(inferJdbcDialect({ db_type: "jdbc", database_info: { productName: "Apache Hive" } })).toBe("hive");
|
|
expect(inferJdbcDialect({ db_type: "jdbc", driver_label: "Kyuubi JDBC", connection_string: "jdbc:hive2://kyuubi.example.com/default" })).toBe("mysql");
|
|
expect(inferJdbcDialect({ db_type: "jdbc", connection_string: "jdbc:hive2://hiveserver.example.com/default" })).toBe("mysql");
|
|
expect(inferJdbcDialect({ db_type: "jdbc", connection_string: "jdbc:mysql://mysql.example.com/app" })).toBe("mysql");
|
|
expect(inferJdbcDialect({ db_type: "jdbc", jdbc_driver_paths: ["/drivers/mysql-connector-j-8.0.33.jar"] })).toBe("mysql");
|
|
});
|
|
|
|
it("prefers explicit Kyuubi identity over Apache Hive product metadata", () => {
|
|
expect(inferJdbcDialect({ db_type: "jdbc", driver_label: "Kyuubi JDBC", database_info: { productName: "Apache Hive" } })).toBe("mysql");
|
|
});
|
|
});
|
|
|
|
describe("GaussDB connection mode", () => {
|
|
it("keeps native connections compatible and configures M mode for the vendor JDBC driver", () => {
|
|
const connection = { db_type: "gaussdb", driver_profile: "gaussdb", driver_label: "GaussDB" } as ConnectionConfig;
|
|
|
|
expect(gaussdbConnectionMode(connection)).toBe("native");
|
|
setGaussdbConnectionMode(connection, "m-jdbc");
|
|
expect(connection.driver_profile).toBe("gaussdb-m");
|
|
expect(connection.jdbc_driver_class).toBe(GAUSSDB_M_JDBC_DRIVER_CLASS);
|
|
expect(gaussdbIdentifierQuoteOverride(connection)).toBeUndefined();
|
|
|
|
setGaussdbConnectionMode(connection, "native");
|
|
expect(connection.driver_profile).toBe("gaussdb");
|
|
expect(connection.jdbc_driver_class).toBeUndefined();
|
|
});
|
|
|
|
it("uses the driver default and preserves targetServerType from legacy URL fields", () => {
|
|
const connection = { db_type: "gaussdb", driver_profile: "gaussdb-m" } as ConnectionConfig;
|
|
|
|
expect(gaussdbTargetServerType(connection)).toBe("any");
|
|
|
|
connection.url_params = "currentSchema=app&targetServerType=slave";
|
|
expect(gaussdbTargetServerType(connection)).toBe("slave");
|
|
|
|
connection.url_params = undefined;
|
|
connection.connection_string = "jdbc:gaussdb://db.internal:8000/app?targetServerType=MASTER&ssl=true";
|
|
expect(gaussdbTargetServerType(connection)).toBe("master");
|
|
|
|
setGaussdbTargetServerType(connection, "any");
|
|
expect(gaussdbTargetServerType(connection)).toBe("any");
|
|
expect(connection.external_config).toEqual({ gaussdbTargetServerType: "any" });
|
|
});
|
|
|
|
it("keeps count query parallelism disabled until explicitly configured", () => {
|
|
const connection = { db_type: "gaussdb", external_config: { retained: true } } as ConnectionConfig;
|
|
|
|
expect(gaussdbCountQueryDop(connection)).toBe(1);
|
|
expect(gaussdbCountQueryDopHint(connection)).toBeUndefined();
|
|
|
|
setGaussdbCountQueryDop(connection, 8);
|
|
expect(connection.external_config).toEqual({ retained: true, gaussdbCountQueryDop: 8 });
|
|
expect(gaussdbCountQueryDopHint(connection)).toBe("/*+ set(query_dop 8) */");
|
|
|
|
connection.external_config = { retained: true, gaussdbCountQueryDop: 32 };
|
|
expect(gaussdbCountQueryDop(connection)).toBe(1);
|
|
expect(gaussdbCountQueryDopHint(connection)).toBeUndefined();
|
|
|
|
setGaussdbCountQueryDop(connection, 1);
|
|
expect(connection.external_config).toEqual({ retained: true });
|
|
});
|
|
});
|
|
|
|
describe("query execution schema", () => {
|
|
it.each(["spark", "hive"] as const)("uses the selected database as the %s execution schema", (dbType) => {
|
|
expect(connectionQueryExecutionSchema({ db_type: dbType }, "ai_test", undefined, false)).toBe("ai_test");
|
|
});
|
|
|
|
it.each(["postgres", "gaussdb", "opengauss"] as const)("prefers an explicit schema for %s", (dbType) => {
|
|
expect(connectionQueryExecutionSchema({ db_type: dbType }, "app", "reporting", false)).toBe("reporting");
|
|
});
|
|
|
|
it("prefers an explicit schema for Kingbase query execution", () => {
|
|
expect(connectionQueryExecutionSchema({ db_type: "kingbase" }, "qinzhou", "sdy_smartsite", false)).toBe("sdy_smartsite");
|
|
});
|
|
|
|
it("does not send a schema for MySQL database context", () => {
|
|
expect(connectionQueryExecutionSchema({ db_type: "mysql" }, "app", undefined, false)).toBeUndefined();
|
|
});
|
|
|
|
it("does not change data-tab execution context", () => {
|
|
expect(connectionQueryExecutionSchema({ db_type: "spark" }, "ai_test", undefined, true)).toBeUndefined();
|
|
});
|
|
|
|
it("keeps generic JDBC Databend schema fallback", () => {
|
|
expect(connectionQueryExecutionSchema({ db_type: "jdbc", connection_string: "jdbc:databend://localhost:8000/default" }, "analytics", undefined, false)).toBe("analytics");
|
|
});
|
|
});
|
|
|
|
describe("object tree node schema", () => {
|
|
it("ignores database-shaped schema metadata for MySQL tables", () => {
|
|
expect(connectionObjectTreeNodeSchema({ db_type: "mysql" }, "app", "app")).toBeUndefined();
|
|
});
|
|
|
|
it("uses the SQLite database alias to qualify attached tables", () => {
|
|
expect(connectionObjectTreeNodeSchema({ db_type: "sqlite" }, "analytics")).toBe("analytics");
|
|
});
|
|
|
|
it("keeps unqualified Informix metadata on the login owner", () => {
|
|
expect(connectionObjectTreeQuerySchema({ db_type: "informix" }, "prulife")).toBe("");
|
|
expect(connectionObjectTreeNodeSchema({ db_type: "informix" }, "prulife")).toBeUndefined();
|
|
});
|
|
|
|
it.each([
|
|
{ db_type: "jdbc" as const, connection_string: "jdbc:informix-sqli://localhost:9088/prulife" },
|
|
{ db_type: "gbase" as const, driver_profile: "gbase8s" },
|
|
])("keeps compatible Informix metadata on the login owner", (connection) => {
|
|
expect(connectionObjectTreeQuerySchema(connection, "prulife")).toBe("");
|
|
expect(connectionObjectTreeNodeSchema(connection, "prulife")).toBeUndefined();
|
|
});
|
|
|
|
it("preserves explicit Informix owners", () => {
|
|
expect(connectionObjectTreeQuerySchema({ db_type: "informix" }, "prulife", "xtdpcky")).toBe("xtdpcky");
|
|
expect(connectionObjectTreeNodeSchema({ db_type: "informix" }, "prulife", "xtdpcky")).toBe("xtdpcky");
|
|
});
|
|
|
|
it("never sends the Cloud Spanner resource path as a metadata schema", () => {
|
|
const connection = { db_type: "spanner" as const };
|
|
const resourcePath = "projects/p/instances/i/databases/db";
|
|
|
|
// GoogleSQL's default schema is the empty string, which must survive the
|
|
// `schema || database` default: sending the resource path matches no objects.
|
|
expect(connectionObjectTreeQuerySchema(connection, resourcePath, "")).toBe("");
|
|
expect(connectionObjectTreeQuerySchema(connection, resourcePath)).toBe("");
|
|
expect(metadataSchemaForConnection(connection, resourcePath, "")).toBe("");
|
|
// PostgreSQL-dialect databases and named schemas pass through unchanged.
|
|
expect(connectionObjectTreeQuerySchema(connection, resourcePath, "public")).toBe("public");
|
|
expect(connectionObjectTreeQuerySchema(connection, resourcePath, "analytics")).toBe("analytics");
|
|
expect(metadataSchemaForConnection(connection, resourcePath, "analytics")).toBe("analytics");
|
|
// The tree node resolves to GoogleSQL's blank default schema rather than to the resource
|
|
// path, so the path can never reach qualifiedTableName as a qualifier. It is the empty
|
|
// string and not undefined because Spanner does have a schema level: "" is the literal
|
|
// name of the GoogleSQL user schema.
|
|
expect(connectionObjectTreeNodeSchema(connection, resourcePath)).toBe("");
|
|
expect(connectionObjectTreeNodeSchema(connection, resourcePath, "")).toBe("");
|
|
expect(connectionObjectTreeNodeSchema(connection, resourcePath, "sales")).toBe("sales");
|
|
expect(connectionObjectTreeNodeSchema(connection, resourcePath, resourcePath)).toBe("");
|
|
// Named schemas (Spanner 2024+) reach the tree unchanged now that Spanner is schema-aware.
|
|
expect(connectionObjectTreeQuerySchema(connection, resourcePath, "sales")).toBe("sales");
|
|
expect(databaseObjectTreeNodeSchema("spanner", resourcePath, "sales")).toBe("sales");
|
|
expect(databaseObjectTreeNodeSchema("spanner", resourcePath, "")).toBe("");
|
|
expect(databaseObjectTreeNodeSchema("spanner", resourcePath)).toBe("");
|
|
// Callers that collapsed `node.schema || node.database` before calling in (the sidebar
|
|
// SQL template and DDL paths) hand over the resource path as the schema.
|
|
expect(connectionObjectTreeQuerySchema(connection, resourcePath, resourcePath)).toBe("");
|
|
expect(metadataSchemaForConnection(connection, resourcePath, resourcePath)).toBe("");
|
|
});
|
|
|
|
it("never uses the database name as the schema when a schema-tree engine has no schema yet", () => {
|
|
// Query tabs can reach locate/metadata paths before a schema is picked (a
|
|
// toolbar "new query" tab, or an external .sql file reopened from disk).
|
|
// PostgreSQL and its relatives keep tables under a separate schema level, so
|
|
// `schema || database` sends "dbx_test" as the schema and matches nothing —
|
|
// locate in the sidebar silently does nothing (issue #7648). The blank schema
|
|
// lets the backend resolve the session default instead.
|
|
for (const dbType of ["postgres", "gaussdb", "opengauss", "kingbase", "redshift", "duckdb"] as const) {
|
|
expect(connectionObjectTreeQuerySchema({ db_type: dbType }, "dbx_test")).toBe("");
|
|
expect(connectionObjectTreeNodeSchema({ db_type: dbType }, "dbx_test")).toBeUndefined();
|
|
expect(metadataSchemaForConnection({ db_type: dbType }, "dbx_test")).toBe("");
|
|
// An explicit schema still wins, and the database name is a legitimate
|
|
// schema name when the user really did select it.
|
|
expect(connectionObjectTreeQuerySchema({ db_type: dbType }, "dbx_test", "public")).toBe("public");
|
|
expect(connectionObjectTreeNodeSchema({ db_type: dbType }, "dbx_test", "public")).toBe("public");
|
|
}
|
|
// SQL Server keeps its own dbo default for metadata lookups.
|
|
expect(connectionObjectTreeQuerySchema({ db_type: "sqlserver" }, "dbx_test")).toBe("");
|
|
expect(connectionObjectTreeNodeSchema({ db_type: "sqlserver" }, "dbx_test")).toBeUndefined();
|
|
expect(metadataSchemaForConnection({ db_type: "sqlserver" }, "dbx_test")).toBe("dbo");
|
|
});
|
|
|
|
it("keeps the database-as-schema fallback for engines whose database is the schema", () => {
|
|
// Oracle, Dameng and the Hive family address objects as schema.table with no
|
|
// separate schema level in the tree, so the database name is the schema there
|
|
// and must survive the fix above.
|
|
expect(connectionObjectTreeNodeSchema({ db_type: "oracle" }, "DBX_TEST")).toBe("DBX_TEST");
|
|
expect(connectionObjectTreeQuerySchema({ db_type: "oracle" }, "DBX_TEST")).toBe("DBX_TEST");
|
|
expect(connectionObjectTreeNodeSchema({ db_type: "dameng" }, "DBX_TEST")).toBe("DBX_TEST");
|
|
expect(connectionObjectTreeNodeSchema({ db_type: "hive" }, "CS")).toBe("CS");
|
|
expect(connectionObjectTreeNodeSchema({ db_type: "spark" }, "CS")).toBe("CS");
|
|
// Flat engines keep returning no tree schema at all.
|
|
expect(connectionObjectTreeNodeSchema({ db_type: "mysql" }, "shop")).toBeUndefined();
|
|
});
|
|
|
|
it("keeps the database-as-schema fallback for flat engines and blanks it for Cloud Spanner", () => {
|
|
// Editor completion paths send the database name as the metadata schema when a
|
|
// connection has no schema level; only Spanner must send a blank schema instead.
|
|
expect(connectionDatabaseMetadataSchema({ db_type: "mysql" }, "shop")).toBe("shop");
|
|
expect(connectionDatabaseMetadataSchema({ db_type: "hbase" }, "shop")).toBe("shop");
|
|
expect(connectionDatabaseMetadataSchema({ db_type: "mysql" }, "shop", "other")).toBe("other");
|
|
expect(connectionDatabaseMetadataSchema({ db_type: "spanner" }, "projects/p/instances/i/databases/db")).toBe("");
|
|
expect(connectionDatabaseMetadataSchema({ db_type: "spanner" }, "projects/p/instances/i/databases/db", "")).toBe("");
|
|
expect(connectionDatabaseMetadataSchema({ db_type: "spanner" }, "projects/p/instances/i/databases/db", "public")).toBe("public");
|
|
});
|
|
});
|
|
|
|
describe("object list schema", () => {
|
|
it("falls back to the uppercased Dameng connection username when no schema is selected", () => {
|
|
// Dameng's object SQL filters on a fixed WHERE o.OWNER = ?, so a blank schema
|
|
// matches nothing and the object tab renders empty (#8301).
|
|
expect(objectListSchemaForConnection({ db_type: "dameng", username: "sales_app" })).toBe("SALES_APP");
|
|
});
|
|
|
|
it("applies the Dameng fallback to generic jdbc:dm connections", () => {
|
|
expect(objectListSchemaForConnection({ db_type: "jdbc", connection_string: "jdbc:dm://localhost:5236", username: "sysdba" })).toBe("SYSDBA");
|
|
});
|
|
|
|
it("keeps an explicitly selected schema for Dameng", () => {
|
|
expect(objectListSchemaForConnection({ db_type: "dameng", username: "sales_app" }, "OTHER_SCHEMA")).toBe("OTHER_SCHEMA");
|
|
});
|
|
|
|
it("returns a blank schema for Dameng when the connection has no usable username", () => {
|
|
expect(objectListSchemaForConnection({ db_type: "dameng" })).toBe("");
|
|
expect(objectListSchemaForConnection({ db_type: "dameng", username: " " })).toBe("");
|
|
});
|
|
|
|
it.each(["oracle", "oceanbase-oracle", "postgres", "mysql"] as const)("does not fall back to the username for %s", (dbType) => {
|
|
expect(objectListSchemaForConnection({ db_type: dbType, username: "app_user" })).toBe("");
|
|
});
|
|
});
|