mirror of
https://github.com/t8y2/dbx.git
synced 2026-10-19 16:16:45 +00:00
fix(sidebar): scope trigger refresh and support firebird trigger actions
This commit is contained in:
@@ -118,7 +118,7 @@ import { isXuguSyntheticTreeNode } from "@/lib/sidebar/xuguPublicSynonyms";
|
||||
import { buildXuguSchedulerJobSql, type XuguSchedulerJobAction } from "@/lib/database/xuguSchedulerJobSql";
|
||||
import { canViewDatabaseObjectDependencies, databaseDependencyProviderFor } from "@/lib/database/databaseObjectDependencies";
|
||||
import { elasticsearchClearIndexPreview, isElasticsearchClearConfirmed, isElasticsearchIndexPattern } from "@/lib/sidebar/elasticsearchIndexActions";
|
||||
import { mysqlObjectTemplateForGroup } from "@/lib/sidebar/mysqlObjectTemplates";
|
||||
import { sidebarObjectTemplateForGroup } from "@/lib/sidebar/databaseObjectTemplates";
|
||||
import { buildTableDeleteTemplate, buildTableInsertTemplate, buildTableSelectTemplate, buildTableUpdateTemplate, insertTemplatePrimaryKeyConflicts } from "@/lib/table/tableSqlTemplates";
|
||||
import SqlTemplateInsertRowsDialog from "@/components/sidebar/SqlTemplateInsertRowsDialog.vue";
|
||||
import { joinExportedDdls } from "@/lib/export/ddlExport";
|
||||
@@ -964,7 +964,7 @@ async function toggle(requestId = beginNavigationRequest()) {
|
||||
// Keep the click path aligned with every object-group definition. In
|
||||
// particular, schema-level trigger/type groups have no tableName, so they
|
||||
// must use the generic object loader rather than the table-trigger loader.
|
||||
const databaseObjectGroup = !!objectTypesForGroupNode(node.type);
|
||||
const databaseObjectGroup = !!objectTypesForGroupNode(node.type) && !node.tableName;
|
||||
const needsConfiguredTablePageDrain = node.type === "group-tables" && connectionStore.getConfig(node.connectionId || "")?.sidebar_auto_load_all_tables === true && hasTableTreeLoadMore(node.children ?? []);
|
||||
if (databaseObjectGroup && connectionStore.canUseLoadedTreeNodeToggle(node) && !needsConfiguredTablePageDrain) {
|
||||
node.isExpanded = !node.isExpanded;
|
||||
@@ -5688,10 +5688,10 @@ async function confirmDeleteHBaseTable() {
|
||||
}
|
||||
}
|
||||
|
||||
function createMysqlObjectTemplate() {
|
||||
function createSidebarObjectTemplate() {
|
||||
const node = activeNode.value;
|
||||
if (!node.connectionId || !node.database) return;
|
||||
const template = mysqlObjectTemplateForGroup(connectionStore.getConfig(node.connectionId), node);
|
||||
const template = sidebarObjectTemplateForGroup(connectionStore.getConfig(node.connectionId), node);
|
||||
if (!template) return;
|
||||
connectionStore.activeConnectionId = node.connectionId;
|
||||
const tabId = queryStore.createTab(node.connectionId, node.database, t(template.titleKey), "query", node.schema, undefined, node.catalog);
|
||||
@@ -7752,11 +7752,11 @@ function buildObjectGroupSidebarMenu(context: SidebarMenuFactoryContext): boolea
|
||||
appendTableVGroupContainerItems(node, items);
|
||||
// 9. Group Labels (group-columns, group-tables, etc.)
|
||||
if (isGroupLabel(node)) {
|
||||
const mysqlObjectTemplate = node.connectionId ? mysqlObjectTemplateForGroup(connectionStore.getConfig(node.connectionId), node) : null;
|
||||
const objectTemplate = node.connectionId ? sidebarObjectTemplateForGroup(connectionStore.getConfig(node.connectionId), node) : null;
|
||||
const hasMongoCreateIndexAction = node.type === "group-indexes" && canCreateMongoIndex.value;
|
||||
const hasMongoDropAllIndexesAction = node.type === "group-indexes" && canDropAllMongoIndexes.value;
|
||||
const canCreateGroupView = node.type === "group-views" && !!node.connectionId && !!node.database && !["neo4j", "nebula"].includes(currentDatabaseType() || "");
|
||||
const hasGroupAction = (node.type === "group-tables" && canCreateTable.value) || canCreateGroupView || !!mysqlObjectTemplate || hasMongoCreateIndexAction || hasMongoDropAllIndexesAction;
|
||||
const hasGroupAction = (node.type === "group-tables" && canCreateTable.value) || canCreateGroupView || !!objectTemplate || hasMongoCreateIndexAction || hasMongoDropAllIndexesAction;
|
||||
const canLoadAllObjectGroup = node.type === "group-tables" || node.type === "group-dolt-system-tables" || node.type === "group-views" || node.type === "group-materialized-views";
|
||||
if (node.type === "group-tables" && canCreateTable.value) {
|
||||
items.push({ label: t("contextMenu.createTable"), action: createTable, icon: Plus });
|
||||
@@ -7773,8 +7773,8 @@ function buildObjectGroupSidebarMenu(context: SidebarMenuFactoryContext): boolea
|
||||
if (node.type === "group-events" && node.connectionId && node.database) {
|
||||
items.push({ label: t("contextMenu.createEvent"), action: openMysqlEventCreateEditor, icon: Plus });
|
||||
}
|
||||
if (mysqlObjectTemplate) {
|
||||
items.push({ label: t(mysqlObjectTemplate.titleKey), action: createMysqlObjectTemplate, icon: Plus });
|
||||
if (objectTemplate) {
|
||||
items.push({ label: t(objectTemplate.titleKey), action: createSidebarObjectTemplate, icon: Plus });
|
||||
}
|
||||
if (hasMongoCreateIndexAction) {
|
||||
items.push({ label: t("contextMenu.createMongoIndex"), action: openCreateMongoIndexDialog, icon: Plus });
|
||||
|
||||
@@ -85,6 +85,27 @@ describe("jdbc dialect inference", () => {
|
||||
).toBe("iris");
|
||||
});
|
||||
|
||||
it("detects Firebird JDBC connections", () => {
|
||||
expect(
|
||||
inferJdbcDialect({
|
||||
db_type: "jdbc",
|
||||
connection_string: "jdbc:firebirdsql://localhost:3050/data/test.fdb",
|
||||
}),
|
||||
).toBe("firebird");
|
||||
expect(
|
||||
inferJdbcDialect({
|
||||
db_type: "jdbc",
|
||||
jdbc_driver_class: "org.firebirdsql.jdbc.FBDriver",
|
||||
}),
|
||||
).toBe("firebird");
|
||||
expect(
|
||||
inferJdbcDialect({
|
||||
db_type: "jdbc",
|
||||
driver_profile: "firebird",
|
||||
}),
|
||||
).toBe("firebird");
|
||||
});
|
||||
|
||||
it("uses JDBC driver profiles when inferring dialect", () => {
|
||||
expect(
|
||||
inferJdbcDialect({
|
||||
|
||||
@@ -17,6 +17,7 @@ describe("triggerToggleSql", () => {
|
||||
expect(supportsTriggerToggle("gaussdb")).toBe(true);
|
||||
expect(supportsTriggerToggle("kwdb")).toBe(true);
|
||||
expect(supportsTriggerToggle("xugu")).toBe(true);
|
||||
expect(supportsTriggerToggle("firebird")).toBe(true);
|
||||
});
|
||||
|
||||
it("returns false for unsupported database types or undefined", () => {
|
||||
@@ -147,6 +148,25 @@ describe("triggerToggleSql", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("generates Firebird trigger enable/disable SQL", () => {
|
||||
expect(
|
||||
buildTriggerToggleSql({
|
||||
databaseType: "firebird",
|
||||
tableName: "orders",
|
||||
triggerName: "trg_orders_audit",
|
||||
enabled: true,
|
||||
}),
|
||||
).toBe('ALTER TRIGGER "trg_orders_audit" ACTIVE;');
|
||||
|
||||
expect(
|
||||
buildTriggerToggleSql({
|
||||
databaseType: "firebird",
|
||||
triggerName: 'trg_"orders"_audit',
|
||||
enabled: false,
|
||||
}),
|
||||
).toBe('ALTER TRIGGER "trg_""orders""_audit" INACTIVE;');
|
||||
});
|
||||
|
||||
it("returns null for PostgreSQL when tableName is missing", () => {
|
||||
expect(
|
||||
buildTriggerToggleSql({
|
||||
|
||||
@@ -129,6 +129,36 @@ describe("sidebar object-group routing", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("refreshes table trigger groups using listTriggers instead of loading database objects", async () => {
|
||||
const listObjects = vi.fn<() => Promise<ObjectInfo[]>>().mockResolvedValue([]);
|
||||
const listTriggers = vi.fn<() => Promise<TriggerInfo[]>>().mockResolvedValue([
|
||||
{
|
||||
name: "trg_orders_audit",
|
||||
timing: "AFTER",
|
||||
event: "UPDATE",
|
||||
},
|
||||
]);
|
||||
const { connection, store } = await createStore({ listObjects, listTriggers });
|
||||
const tableTriggerGroup: TreeNode = {
|
||||
...objectGroup("group-triggers", `${connection.id}:app:app:orders:__triggers`),
|
||||
tableName: "orders",
|
||||
};
|
||||
store.treeNodes = [{ id: connection.id, label: connection.name, type: "connection", connectionId: connection.id, children: [tableTriggerGroup] }];
|
||||
const storedTriggerGroup = store.treeNodes[0].children![0];
|
||||
|
||||
await store.refreshTreeNode(storedTriggerGroup);
|
||||
|
||||
expect(listTriggers).toHaveBeenCalledWith(connection.id, "app", "app", "orders", undefined);
|
||||
expect(listObjects).not.toHaveBeenCalled();
|
||||
expect(storedTriggerGroup.children).toHaveLength(1);
|
||||
expect(storedTriggerGroup.children![0]).toMatchObject({
|
||||
label: "trg_orders_audit (AFTER UPDATE)",
|
||||
objectName: "trg_orders_audit",
|
||||
tableName: "orders",
|
||||
type: "trigger",
|
||||
});
|
||||
});
|
||||
|
||||
it("preserves SQL Server table trigger status metadata across refreshes", async () => {
|
||||
const listObjects = vi.fn<() => Promise<ObjectInfo[]>>().mockResolvedValue([]);
|
||||
const listTriggers = vi.fn<() => Promise<TriggerInfo[]>>();
|
||||
|
||||
@@ -43,6 +43,7 @@ const JDBC_DIALECT_MATCHERS: Array<{ type: DatabaseType; patterns: RegExp[] }> =
|
||||
{ type: "sqlite", patterns: [/jdbc:sqlite:/i, /sqlite/i] },
|
||||
{ type: "db2", patterns: [/jdbc:db2:/i, /\bdb2\b/i] },
|
||||
{ type: "informix", patterns: [/jdbc:informix/i, /informix/i] },
|
||||
{ type: "firebird", patterns: [/jdbc:firebird(?:sql)?:/i, /firebird/i, /org\.firebirdsql\./i] },
|
||||
// CacheDB.jar (legacy Caché driver) carries none of the intersystems URL or
|
||||
// class-name markers, so match the jar file name / driver label directly.
|
||||
{ type: "iris", patterns: [/jdbc:(?:iris|cache):/i, /com\.intersystems\.jdbc\.(?:IRIS|Cache)Driver/i, /intersystems-jdbc/i, /\bcache(?:db)?\b/i] },
|
||||
|
||||
@@ -8,7 +8,7 @@ export interface TriggerToggleSqlOptions {
|
||||
enabled: boolean;
|
||||
}
|
||||
|
||||
const SUPPORTED_TRIGGER_TOGGLE_DATABASES = new Set<DatabaseType>(["sqlserver", "oracle", "oceanbase-oracle", "dameng", "postgres", "kingbase", "opengauss", "vastbase", "highgo", "uxdb", "gaussdb", "kwdb", "xugu"]);
|
||||
const SUPPORTED_TRIGGER_TOGGLE_DATABASES = new Set<DatabaseType>(["sqlserver", "oracle", "oceanbase-oracle", "dameng", "postgres", "kingbase", "opengauss", "vastbase", "highgo", "uxdb", "gaussdb", "kwdb", "xugu", "firebird"]);
|
||||
|
||||
export function supportsTriggerToggle(databaseType?: DatabaseType): boolean {
|
||||
if (!databaseType) return false;
|
||||
@@ -64,6 +64,11 @@ export function buildTriggerToggleSql(options: TriggerToggleSqlOptions): string
|
||||
return `ALTER TABLE ${quotedTable} ${stateKeyword} TRIGGER ${quotedTrigger};`;
|
||||
}
|
||||
|
||||
case "firebird": {
|
||||
const quotedTrigger = quoteDouble(cleanTrigger);
|
||||
return `ALTER TRIGGER ${quotedTrigger} ${enabled ? "ACTIVE" : "INACTIVE"};`;
|
||||
}
|
||||
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
import type { TreeNode } from "@/types/database";
|
||||
import type { JdbcDialectConnection } from "@/lib/database/jdbcDialect";
|
||||
import { mysqlObjectTemplateForGroup, type MysqlObjectTemplate } from "@/lib/sidebar/mysqlObjectTemplates";
|
||||
import { firebirdObjectTemplateForGroup, type FirebirdObjectTemplate } from "@/lib/sidebar/firebirdObjectTemplates";
|
||||
|
||||
export type SidebarObjectTemplate = MysqlObjectTemplate | FirebirdObjectTemplate;
|
||||
|
||||
export function sidebarObjectTemplateForGroup(connection: JdbcDialectConnection | undefined, node: Pick<TreeNode, "type" | "database" | "tableName">): SidebarObjectTemplate | null {
|
||||
return mysqlObjectTemplateForGroup(connection, node) || firebirdObjectTemplateForGroup(connection, node);
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
import type { TreeNode, TreeNodeType } from "@/types/database";
|
||||
import { effectiveDatabaseTypeForConnection, type JdbcDialectConnection } from "@/lib/database/jdbcDialect";
|
||||
|
||||
export type FirebirdObjectTemplateKind = "procedure" | "function" | "trigger";
|
||||
|
||||
export interface FirebirdObjectTemplate {
|
||||
kind: FirebirdObjectTemplateKind;
|
||||
titleKey: "contextMenu.createProcedure" | "contextMenu.createFunction" | "contextMenu.createTrigger";
|
||||
sql: string;
|
||||
}
|
||||
|
||||
type FirebirdTemplateConnection = JdbcDialectConnection;
|
||||
type FirebirdTemplateNode = Pick<TreeNode, "type" | "database" | "tableName">;
|
||||
|
||||
const templateKindByGroup: Partial<Record<TreeNodeType, FirebirdObjectTemplateKind>> = {
|
||||
"group-procedures": "procedure",
|
||||
"group-functions": "function",
|
||||
"group-triggers": "trigger",
|
||||
};
|
||||
|
||||
export function supportsFirebirdObjectTemplates(connection?: FirebirdTemplateConnection): boolean {
|
||||
return effectiveDatabaseTypeForConnection(connection) === "firebird";
|
||||
}
|
||||
|
||||
export function firebirdObjectTemplateForGroup(connection: FirebirdTemplateConnection | undefined, node: FirebirdTemplateNode): FirebirdObjectTemplate | null {
|
||||
if (!supportsFirebirdObjectTemplates(connection) || !node.database) return null;
|
||||
const kind = templateKindByGroup[node.type];
|
||||
if (!kind) return null;
|
||||
return buildFirebirdObjectTemplate(kind, node.tableName);
|
||||
}
|
||||
|
||||
export function buildFirebirdObjectTemplate(kind: FirebirdObjectTemplateKind, tableName?: string): FirebirdObjectTemplate {
|
||||
if (kind === "procedure") {
|
||||
const name = "new_procedure";
|
||||
return {
|
||||
kind,
|
||||
titleKey: "contextMenu.createProcedure",
|
||||
sql: `CREATE OR ALTER PROCEDURE ${quoteFirebirdIdentifier(name)}
|
||||
AS
|
||||
BEGIN
|
||||
/* Procedure body */
|
||||
END;`,
|
||||
};
|
||||
}
|
||||
|
||||
if (kind === "function") {
|
||||
const name = "new_function";
|
||||
return {
|
||||
kind,
|
||||
titleKey: "contextMenu.createFunction",
|
||||
sql: `CREATE OR ALTER FUNCTION ${quoteFirebirdIdentifier(name)}()
|
||||
RETURNS INTEGER
|
||||
AS
|
||||
BEGIN
|
||||
RETURN 0;
|
||||
END;`,
|
||||
};
|
||||
}
|
||||
|
||||
const name = "new_trigger";
|
||||
if (tableName) {
|
||||
return {
|
||||
kind,
|
||||
titleKey: "contextMenu.createTrigger",
|
||||
sql: `CREATE OR ALTER TRIGGER ${quoteFirebirdIdentifier(name)} FOR ${quoteFirebirdIdentifier(tableName)}
|
||||
ACTIVE BEFORE INSERT POSITION 0
|
||||
AS
|
||||
BEGIN
|
||||
/* Trigger body */
|
||||
END;`,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
kind,
|
||||
titleKey: "contextMenu.createTrigger",
|
||||
sql: `CREATE OR ALTER TRIGGER ${quoteFirebirdIdentifier(name)}
|
||||
ACTIVE ON CONNECT POSITION 0
|
||||
AS
|
||||
BEGIN
|
||||
/* Trigger body */
|
||||
END;`,
|
||||
};
|
||||
}
|
||||
|
||||
export function quoteFirebirdIdentifier(identifier: string): string {
|
||||
return `"${identifier.replaceAll('"', '""')}"`;
|
||||
}
|
||||
@@ -8,7 +8,7 @@ export interface MysqlObjectTemplate {
|
||||
sql: string;
|
||||
}
|
||||
|
||||
type MysqlTemplateConnection = Pick<ConnectionConfig, "db_type" | "driver_profile">;
|
||||
type MysqlTemplateConnection = Partial<Pick<ConnectionConfig, "db_type" | "driver_profile">>;
|
||||
type MysqlTemplateNode = Pick<TreeNode, "type" | "database" | "tableName">;
|
||||
|
||||
const templateKindByGroup: Partial<Record<TreeNodeType, MysqlObjectTemplateKind>> = {
|
||||
|
||||
@@ -897,6 +897,7 @@ export function buildObjectGroupPlaceholderNodes({
|
||||
}
|
||||
|
||||
export function objectGroupRefreshParentId(node: TreeNode): string | null {
|
||||
if (node.tableName) return null;
|
||||
if (!objectGroupNodeTypes.has(node.type)) return null;
|
||||
const suffixStart = node.id.lastIndexOf(":__");
|
||||
if (suffixStart < 0) return null;
|
||||
|
||||
@@ -8069,7 +8069,7 @@ export const useConnectionStore = defineStore("connection", () => {
|
||||
}
|
||||
invalidateCompletionCachesForNode(node);
|
||||
invalidateMetadataCachesForNode(node, options);
|
||||
if (objectTypesForGroupNode(node.type)) {
|
||||
if (objectTypesForGroupNode(node.type) && !node.tableName) {
|
||||
clearLoadedChildrenCache(node.id, { deletePersisted: false });
|
||||
await loadObjectGroupChildren(node, { force: true });
|
||||
await invalidateSidebarTableSearchIndexesForTreeRefresh(node);
|
||||
@@ -8145,7 +8145,7 @@ export const useConnectionStore = defineStore("connection", () => {
|
||||
|
||||
async function refreshTreeNodeForTableNameFilter(node: TreeNode, scopeKey: string, revision: number) {
|
||||
invalidateMetadataCachesForNode(node);
|
||||
if (objectTypesForGroupNode(node.type)) {
|
||||
if (objectTypesForGroupNode(node.type) && !node.tableName) {
|
||||
clearLoadedChildrenCache(node.id);
|
||||
await loadObjectGroupChildren(node, {
|
||||
force: true,
|
||||
@@ -10124,7 +10124,7 @@ export const useConnectionStore = defineStore("connection", () => {
|
||||
if (node.children) await refreshNodes(node.children);
|
||||
continue;
|
||||
}
|
||||
if (objectTypesForGroupNode(node.type)) {
|
||||
if (objectTypesForGroupNode(node.type) && !node.tableName) {
|
||||
if (node.connectionId && connectedIds.value.has(node.connectionId)) {
|
||||
clearLoadedChildrenCache(node.id);
|
||||
if (node.isExpanded) {
|
||||
|
||||
+118
@@ -0,0 +1,118 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { test } from "vitest";
|
||||
import { buildFirebirdObjectTemplate, firebirdObjectTemplateForGroup, quoteFirebirdIdentifier, supportsFirebirdObjectTemplates } from "../../apps/desktop/src/lib/sidebar/firebirdObjectTemplates.ts";
|
||||
import { sidebarObjectTemplateForGroup } from "../../apps/desktop/src/lib/sidebar/databaseObjectTemplates.ts";
|
||||
import { executableStatementRanges } from "../../apps/desktop/src/lib/sql/sqlStatementRanges.ts";
|
||||
import type { ConnectionConfig, TreeNode } from "../../apps/desktop/src/types/database.ts";
|
||||
|
||||
function connection(dbType: ConnectionConfig["db_type"], driverProfile?: string): ConnectionConfig {
|
||||
return {
|
||||
id: "conn-1",
|
||||
name: "Test",
|
||||
db_type: dbType,
|
||||
driver_profile: driverProfile,
|
||||
host: "127.0.0.1",
|
||||
port: 3050,
|
||||
username: "sysdba",
|
||||
password: "",
|
||||
};
|
||||
}
|
||||
|
||||
function group(type: TreeNode["type"], overrides: Partial<TreeNode> = {}): TreeNode {
|
||||
return {
|
||||
id: `conn-1:app:${type}`,
|
||||
label: type,
|
||||
type,
|
||||
connectionId: "conn-1",
|
||||
database: "app",
|
||||
children: [],
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
test("Firebird object templates are supported for Firebird connections", () => {
|
||||
assert.equal(supportsFirebirdObjectTemplates(connection("firebird")), true);
|
||||
assert.equal(supportsFirebirdObjectTemplates(connection("jdbc", "firebird")), true);
|
||||
assert.equal(supportsFirebirdObjectTemplates({ db_type: "jdbc", connection_string: "jdbc:firebirdsql://localhost:3050/test.fdb" }), true);
|
||||
assert.equal(supportsFirebirdObjectTemplates({ db_type: "jdbc", jdbc_driver_class: "org.firebirdsql.jdbc.FBDriver" }), true);
|
||||
assert.equal(supportsFirebirdObjectTemplates(connection("mysql")), false);
|
||||
assert.equal(supportsFirebirdObjectTemplates(connection("postgres")), false);
|
||||
});
|
||||
|
||||
test("only supported Firebird object groups expose creation templates", () => {
|
||||
const fb = connection("firebird");
|
||||
assert.equal(firebirdObjectTemplateForGroup(fb, group("group-procedures"))?.kind, "procedure");
|
||||
assert.equal(firebirdObjectTemplateForGroup(fb, group("group-functions"))?.kind, "function");
|
||||
assert.equal(firebirdObjectTemplateForGroup(fb, group("group-triggers"))?.kind, "trigger");
|
||||
assert.equal(firebirdObjectTemplateForGroup(fb, group("group-views")), null);
|
||||
assert.equal(firebirdObjectTemplateForGroup(fb, group("group-functions", { database: "" })), null);
|
||||
});
|
||||
|
||||
test("procedure and function templates use Firebird CREATE OR ALTER syntax", () => {
|
||||
assert.equal(
|
||||
buildFirebirdObjectTemplate("procedure").sql,
|
||||
`CREATE OR ALTER PROCEDURE "new_procedure"
|
||||
AS
|
||||
BEGIN
|
||||
/* Procedure body */
|
||||
END;`,
|
||||
);
|
||||
assert.equal(
|
||||
buildFirebirdObjectTemplate("function").sql,
|
||||
`CREATE OR ALTER FUNCTION "new_function"()
|
||||
RETURNS INTEGER
|
||||
AS
|
||||
BEGIN
|
||||
RETURN 0;
|
||||
END;`,
|
||||
);
|
||||
});
|
||||
|
||||
test("DBX parses every Firebird template as one executable statement", () => {
|
||||
for (const kind of ["procedure", "function"] as const) {
|
||||
const ranges = executableStatementRanges(buildFirebirdObjectTemplate(kind).sql, "firebird");
|
||||
assert.equal(ranges.length, 1, kind);
|
||||
assert.match(ranges[0].sql, new RegExp(`^CREATE OR ALTER ${kind.toUpperCase()}`));
|
||||
}
|
||||
const tableTriggerRanges = executableStatementRanges(buildFirebirdObjectTemplate("trigger", "A_DETAIL").sql, "firebird");
|
||||
assert.equal(tableTriggerRanges.length, 1, "table trigger");
|
||||
assert.match(tableTriggerRanges[0].sql, /^CREATE OR ALTER TRIGGER/);
|
||||
|
||||
const dbTriggerRanges = executableStatementRanges(buildFirebirdObjectTemplate("trigger").sql, "firebird");
|
||||
assert.equal(dbTriggerRanges.length, 1, "database trigger");
|
||||
assert.match(dbTriggerRanges[0].sql, /^CREATE OR ALTER TRIGGER/);
|
||||
});
|
||||
|
||||
test("trigger templates use table context when available and ON CONNECT for database triggers", () => {
|
||||
const tableTrigger = buildFirebirdObjectTemplate("trigger", 'order"items');
|
||||
assert.match(tableTrigger.sql, /FOR "order""items"/);
|
||||
assert.match(tableTrigger.sql, /ACTIVE BEFORE INSERT POSITION 0/);
|
||||
|
||||
const dbTrigger = buildFirebirdObjectTemplate("trigger");
|
||||
assert.match(dbTrigger.sql, /ACTIVE ON CONNECT POSITION 0/);
|
||||
|
||||
const groupTemplate = firebirdObjectTemplateForGroup(connection("firebird"), group("group-triggers", { tableName: "A_DETAIL" }));
|
||||
assert.equal(groupTemplate?.kind, "trigger");
|
||||
assert.match(groupTemplate?.sql || "", /FOR "A_DETAIL"/);
|
||||
});
|
||||
|
||||
test("quoteFirebirdIdentifier properly escapes quotes", () => {
|
||||
assert.equal(quoteFirebirdIdentifier("table_name"), '"table_name"');
|
||||
assert.equal(quoteFirebirdIdentifier('my"table"name'), '"my""table""name"');
|
||||
});
|
||||
|
||||
test("sidebarObjectTemplateForGroup routes both MySQL and Firebird templates", () => {
|
||||
const mysql = connection("mysql", "mysql");
|
||||
const fb = connection("firebird");
|
||||
const pg = connection("postgres");
|
||||
|
||||
const mysqlTpl = sidebarObjectTemplateForGroup(mysql, group("group-triggers", { tableName: "orders" }));
|
||||
assert.equal(mysqlTpl?.kind, "trigger");
|
||||
assert.match(mysqlTpl?.sql || "", /DELIMITER/);
|
||||
|
||||
const fbTpl = sidebarObjectTemplateForGroup(fb, group("group-triggers", { tableName: "ORDERS" }));
|
||||
assert.equal(fbTpl?.kind, "trigger");
|
||||
assert.match(fbTpl?.sql || "", /CREATE OR ALTER TRIGGER "new_trigger" FOR "ORDERS"/);
|
||||
|
||||
assert.equal(sidebarObjectTemplateForGroup(pg, group("group-triggers", { tableName: "orders" })), null);
|
||||
});
|
||||
Reference in New Issue
Block a user