diff --git a/packages/datalib/src/ChangeSet.ts b/packages/datalib/src/ChangeSet.ts index e7c92b670..ff1610cd3 100644 --- a/packages/datalib/src/ChangeSet.ts +++ b/packages/datalib/src/ChangeSet.ts @@ -20,6 +20,7 @@ export interface ChangeSetItem { document?: any; condition?: { [column: string]: string }; fields?: { [column: string]: string }; + insertIfNotExistsFields?: { [column: string]: string }; } export interface ChangeSetItemFields { @@ -284,6 +285,9 @@ function changeSetInsertToSql( targetTable, commandType: 'insert', fields, + insertWhereNotExistsCondition: item.insertIfNotExistsFields + ? compileSimpleChangeSetCondition(item.insertIfNotExistsFields) + : null, }, autoInc ? { @@ -332,6 +336,36 @@ export function extractChangeSetCondition(item: ChangeSetItem, alias?: string): }; } +function compileSimpleChangeSetCondition(fields: { [column: string]: string }): Condition { + function getColumnCondition(columnName: string): Condition { + const value = fields[columnName]; + const expr: Expression = { + exprType: 'column', + columnName, + }; + if (value == null) { + return { + conditionType: 'isNull', + expr, + }; + } else { + return { + conditionType: 'binary', + operator: '=', + left: expr, + right: { + exprType: 'value', + value, + }, + }; + } + } + return { + conditionType: 'and', + conditions: _.keys(fields).map(columnName => getColumnCondition(columnName)), + }; +} + function changeSetUpdateToSql(item: ChangeSetItem, dbinfo: DatabaseInfo = null): Update { const table = dbinfo?.tables?.find(x => x.schemaName == item.schemaName && x.pureName == item.pureName); diff --git a/packages/sqltree/src/dumpSqlCommand.ts b/packages/sqltree/src/dumpSqlCommand.ts index 9a7ea6fe5..0a1bda7a3 100644 --- a/packages/sqltree/src/dumpSqlCommand.ts +++ b/packages/sqltree/src/dumpSqlCommand.ts @@ -92,13 +92,28 @@ export function dumpSqlDelete(dmp: SqlDumper, cmd: Delete) { } export function dumpSqlInsert(dmp: SqlDumper, cmd: Insert) { - dmp.put( - '^insert ^into %f (%,i) ^values (', - cmd.targetTable, - cmd.fields.map(x => x.targetColumn) - ); - dmp.putCollection(',', cmd.fields, x => dumpSqlExpression(dmp, x)); - dmp.put(')'); + if (cmd.insertWhereNotExistsCondition) { + dmp.put( + '^insert ^into %f (%,i) ^select ', + cmd.targetTable, + cmd.fields.map(x => x.targetColumn) + ); + dmp.putCollection(',', cmd.fields, x => dumpSqlExpression(dmp, x)); + if (dmp.dialect.requireFromDual) { + dmp.put(' ^from ^dual '); + } + dmp.put(' ^where ^not ^exists (^select * ^from %f ^where ', cmd.targetTable); + dumpSqlCondition(dmp, cmd.insertWhereNotExistsCondition); + dmp.put(')'); + } else { + dmp.put( + '^insert ^into %f (%,i) ^values (', + cmd.targetTable, + cmd.fields.map(x => x.targetColumn) + ); + dmp.putCollection(',', cmd.fields, x => dumpSqlExpression(dmp, x)); + dmp.put(')'); + } } export function dumpSqlCommand(dmp: SqlDumper, cmd: Command) { diff --git a/packages/sqltree/src/types.ts b/packages/sqltree/src/types.ts index df9802cee..cda154a7f 100644 --- a/packages/sqltree/src/types.ts +++ b/packages/sqltree/src/types.ts @@ -38,6 +38,7 @@ export interface Insert { commandType: 'insert'; fields: UpdateField[]; targetTable: NamedObjectInfo; + insertWhereNotExistsCondition?: Condition; } export interface AllowIdentityInsert { diff --git a/packages/types/dialect.d.ts b/packages/types/dialect.d.ts index 47642df6e..0db6a110f 100644 --- a/packages/types/dialect.d.ts +++ b/packages/types/dialect.d.ts @@ -34,6 +34,7 @@ export interface SqlDialect { dropCheck?: boolean; dropReferencesWhenDropTable?: boolean; + requireFromDual?: boolean; predefinedDataTypes: string[]; diff --git a/plugins/dbgate-plugin-oracle/src/frontend/drivers.js b/plugins/dbgate-plugin-oracle/src/frontend/drivers.js index 69608365a..25b32793a 100644 --- a/plugins/dbgate-plugin-oracle/src/frontend/drivers.js +++ b/plugins/dbgate-plugin-oracle/src/frontend/drivers.js @@ -35,6 +35,7 @@ const dialect = { dropCheck: true, dropReferencesWhenDropTable: true, + requireFromDual: true, predefinedDataTypes: [ 'VARCHAR2',