Fix autocolumn detection on schema import.

This commit is contained in:
Sam Rose 2024-08-02 11:17:38 +01:00
parent 40e886b34d
commit f23f479eb9
No known key found for this signature in database
3 changed files with 108 additions and 4 deletions

View File

@ -864,7 +864,7 @@ describe.each([
}) })
!isInternal && !isInternal &&
it.only("can update a row on an external table with a primary key", async () => { it("can update a row on an external table with a primary key", async () => {
const tableName = uuid.v4().substring(0, 10) const tableName = uuid.v4().substring(0, 10)
await client!.schema.createTable(tableName, table => { await client!.schema.createTable(tableName, table => {
table.increments("id").primary() table.increments("id").primary()

View File

@ -104,11 +104,34 @@ export interface OracleColumnsResponse {
SEARCH_CONDITION: null | string SEARCH_CONDITION: null | string
} }
export enum TriggeringEvent {
INSERT = "INSERT",
DELETE = "DELETE",
UPDATE = "UPDATE",
LOGON = "LOGON",
LOGOFF = "LOGOFF",
STARTUP = "STARTUP",
SHUTDOWN = "SHUTDOWN",
SERVERERROR = "SERVERERROR",
SCHEMA = "SCHEMA",
ALTER = "ALTER",
DROP = "DROP",
}
export enum TriggerType {
BEFORE_EACH_ROW = "BEFORE EACH ROW",
AFTER_EACH_ROW = "AFTER EACH ROW",
BEFORE_STATEMENT = "BEFORE STATEMENT",
AFTER_STATEMENT = "AFTER STATEMENT",
INSTEAD_OF = "INSTEAD OF",
COMPOUND = "COMPOUND",
}
export interface OracleTriggersResponse { export interface OracleTriggersResponse {
TABLE_NAME: string TABLE_NAME: string
TRIGGER_NAME: string TRIGGER_NAME: string
TRIGGER_TYPE: string TRIGGER_TYPE: TriggerType
TRIGGERING_EVENT: string TRIGGERING_EVENT: TriggeringEvent
TRIGGER_BODY: string TRIGGER_BODY: string
} }

View File

@ -36,6 +36,8 @@ import {
OracleColumn, OracleColumn,
OracleColumnsResponse, OracleColumnsResponse,
OracleTriggersResponse, OracleTriggersResponse,
TriggeringEvent,
TriggerType,
} from "./base/types" } from "./base/types"
import { sql } from "@budibase/backend-core" import { sql } from "@budibase/backend-core"
@ -146,7 +148,15 @@ class OracleIntegration extends Sql implements DatasourcePlus {
` `
private static readonly TRIGGERS_SQL = ` private static readonly TRIGGERS_SQL = `
SELECT table_name, trigger_name, trigger_type, triggering_event, trigger_body FROM all_triggers WHERE status = 'ENABLED'; SELECT
table_name,
trigger_name,
trigger_type,
triggering_event,
trigger_body
FROM
all_triggers
WHERE status = 'ENABLED'
` `
constructor(config: OracleConfig) { constructor(config: OracleConfig) {
@ -221,6 +231,75 @@ class OracleIntegration extends Sql implements DatasourcePlus {
return oracleTables return oracleTables
} }
private getTriggersFor(
tableName: string,
triggersResponse: Result<OracleTriggersResponse>,
opts?: { event?: TriggeringEvent; type?: TriggerType }
): OracleTriggersResponse[] {
const triggers: OracleTriggersResponse[] = []
for (const trigger of triggersResponse.rows || []) {
if (trigger.TABLE_NAME !== tableName) {
continue
}
if (opts?.event && opts.event !== trigger.TRIGGERING_EVENT) {
continue
}
if (opts?.type && opts.type !== trigger.TRIGGER_TYPE) {
continue
}
triggers.push(trigger)
}
return triggers
}
private markAutoIncrementColumns(
triggersResponse: Result<OracleTriggersResponse>,
tables: Record<string, Table>
) {
for (const table of Object.values(tables)) {
const triggers = this.getTriggersFor(table.name, triggersResponse, {
type: TriggerType.BEFORE_EACH_ROW,
event: TriggeringEvent.INSERT,
})
// This is the trigger body Knex generates for an auto increment column
// called "id" on a table called "foo":
//
// declare checking number := 1;
// begin if (:new. "id" is null) then while checking >= 1 loop
// select
// "foo_seq".nextval into :new. "id"
// from
// dual;
// select
// count("id") into checking
// from
// "foo"
// where
// "id" = :new. "id";
// end loop;
// end if;
// end;
for (const [columnName, schema] of Object.entries(table.schema)) {
const autoIncrementTriggers = triggers.filter(
trigger =>
// This is a bit heuristic, but I think it's the best we can do with
// the information we have. We're looking for triggers that run
// before each row is inserted, and that have a body that contains a
// call to a function that generates a new value for the column. We
// also check that the column name is in the trigger body, to make
// sure we're not picking up triggers that don't affect the column.
trigger.TRIGGER_BODY.includes(`"${columnName}"`) &&
trigger.TRIGGER_BODY.includes(`.nextval`)
)
if (autoIncrementTriggers.length > 0) {
schema.autocolumn = true
}
}
}
}
private static isSupportedColumn(column: OracleColumn) { private static isSupportedColumn(column: OracleColumn) {
return !UNSUPPORTED_TYPES.includes(column.type) return !UNSUPPORTED_TYPES.includes(column.type)
} }
@ -331,6 +410,8 @@ class OracleIntegration extends Sql implements DatasourcePlus {
}) })
}) })
this.markAutoIncrementColumns(triggersResponse, tables)
let externalTables = finaliseExternalTables(tables, entities) let externalTables = finaliseExternalTables(tables, entities)
let errors = checkExternalTables(externalTables) let errors = checkExternalTables(externalTables)
return { tables: externalTables, errors } return { tables: externalTables, errors }