diff --git a/Sources/SQLKit/Builders/Implementations/SQLCreateTriggerBuilder.swift b/Sources/SQLKit/Builders/Implementations/SQLCreateTriggerBuilder.swift index c81d539d..bc948370 100644 --- a/Sources/SQLKit/Builders/Implementations/SQLCreateTriggerBuilder.swift +++ b/Sources/SQLKit/Builders/Implementations/SQLCreateTriggerBuilder.swift @@ -158,6 +158,22 @@ public final class SQLCreateTriggerBuilder: SQLQueryBuilder { self.createTrigger.orderTriggerName = otherTriggerName return self } + + + @inlinable + @discardableResult + public func orReplace(orReplace: Bool = true) -> Self { + self.createTrigger.orReplace = orReplace + return self + } + + + @inlinable + @discardableResult + public func ifNotExists(ifNotExists: Bool = true) -> Self { + self.createTrigger.ifNotExists = ifNotExists + return self + } } extension SQLDatabase { diff --git a/Sources/SQLKit/Database/SQLDialect.swift b/Sources/SQLKit/Database/SQLDialect.swift index 41d7b030..6857ea66 100644 --- a/Sources/SQLKit/Database/SQLDialect.swift +++ b/Sources/SQLKit/Database/SQLDialect.swift @@ -368,6 +368,16 @@ public struct SQLTriggerSyntax: Sendable { public static var conditionRequiresParentheses: Self { .init(rawValue: 1 << 9) } + + /// Indicates support for `OR REPLACE` syntax. + public static var supportsOrReplace: Self { + .init(rawValue: 1 << 10) + } + + /// Indicates support for an `IF NOT EXISTS` syntax. + public static var supportsIfNotExists: Self { + .init(rawValue: 1 << 11) + } } /// Describes specific feature support for `CREATE TRIGGER` syntax. diff --git a/Sources/SQLKit/Expressions/Queries/SQLCreateTrigger.swift b/Sources/SQLKit/Expressions/Queries/SQLCreateTrigger.swift index b97b2711..eaed6ce4 100644 --- a/Sources/SQLKit/Expressions/Queries/SQLCreateTrigger.swift +++ b/Sources/SQLKit/Expressions/Queries/SQLCreateTrigger.swift @@ -36,6 +36,12 @@ public struct SQLCreateTrigger: SQLExpression { /// `true` if the new trigger will be a constraint trigger, if supported. public var isConstraint: Bool + + /// Include `OR REPLACE` to the trigger syntax. + public var orReplace: Bool + + /// Whether to include `IF NOT EXISTS` syntax. + public var ifNotExists: Bool /// The ordering of the trigger's execution relative to the triggering event. /// @@ -122,6 +128,8 @@ public struct SQLCreateTrigger: SQLExpression { self.when = when self.event = event self.isConstraint = false + self.orReplace = false + self.ifNotExists = false } /// Create a new trigger creation query. @@ -162,10 +170,23 @@ public struct SQLCreateTrigger: SQLExpression { if syntax.contains(.supportsConstraints), self.isConstraint { $0.append("CONSTRAINT") } + + if self.orReplace, syntax.contains(.supportsOrReplace) { + $0.append("OR REPLACE") + } + if let definer = self.definer, syntax.contains(.supportsDefiner) { $0.append("DEFINER =", definer) } - $0.append("TRIGGER", self.name) + + $0.append("TRIGGER") + + if self.ifNotExists, syntax.contains(.supportsIfNotExists) { + $0.append("IF NOT EXISTS") + } + + $0.append(self.name) + $0.append(self.when, self.event) if let columns = self.columns, !columns.isEmpty, syntax.contains(.supportsUpdateColumns) { $0.append("OF", SQLList(columns)) diff --git a/Tests/SQLKitTests/SQLCreateDropTriggerTests.swift b/Tests/SQLKitTests/SQLCreateDropTriggerTests.swift index c65f58ab..40d18917 100644 --- a/Tests/SQLKitTests/SQLCreateDropTriggerTests.swift +++ b/Tests/SQLKitTests/SQLCreateDropTriggerTests.swift @@ -33,7 +33,7 @@ final class SQLCreateDropTriggerTests: XCTestCase { } func testMySqlTriggerCreates() { - self.db._dialect.triggerSyntax = .init(create: [.supportsBody, .supportsOrder, .supportsDefiner, .requiresForEachRow]) + self.db._dialect.triggerSyntax = .init(create: [.supportsBody, .supportsOrder, .supportsDefiner, .requiresForEachRow, .supportsIfNotExists]) let builder = self.db.create(trigger: "foo", table: "planet", when: .before, event: .insert) .body(self.body.map { SQLRaw($0) }) @@ -44,20 +44,30 @@ final class SQLCreateDropTriggerTests: XCTestCase { of: builder, is: "CREATE DEFINER = 'foo@bar' TRIGGER ``foo`` BEFORE INSERT ON ``planet`` FOR EACH ROW PRECEDES ``other`` BEGIN \(self.body.joined(separator: " ")) END;" ) + XCTAssertSerialization( + of: self.db.create(trigger: "foo", table: "planet", when: .before, event: .insert) + .ifNotExists(), + is: "CREATE TRIGGER IF NOT EXISTS ``foo`` BEFORE INSERT ON ``planet`` FOR EACH ROW" + ) } func testSqliteTriggerCreates() { - self.db._dialect.triggerSyntax = .init(create: [.supportsBody, .supportsCondition]) + self.db._dialect.triggerSyntax = .init(create: [.supportsBody, .supportsCondition, .supportsIfNotExists]) XCTAssertSerialization( of: self.db.create(trigger: "foo", table: "planet", when: .before, event: .insert) .body(self.body.map { SQLRaw($0) }) .condition("\(ident: "foo") = \(ident: "bar")" as SQLQueryString), is: "CREATE TRIGGER ``foo`` BEFORE INSERT ON ``planet`` WHEN ``foo`` = ``bar`` BEGIN \(self.body.joined(separator: " ")) END;" ) + XCTAssertSerialization( + of: self.db.create(trigger: "foo", table: "planet", when: .before, event: .insert) + .ifNotExists(), + is: "CREATE TRIGGER IF NOT EXISTS ``foo`` BEFORE INSERT ON ``planet``" + ) } func testPostgreSqlTriggerCreates() { - self.db._dialect.triggerSyntax = .init(create: [.supportsForEach, .postgreSQLChecks, .supportsCondition, .conditionRequiresParentheses, .supportsConstraints]) + self.db._dialect.triggerSyntax = .init(create: [.supportsForEach, .postgreSQLChecks, .supportsCondition, .conditionRequiresParentheses, .supportsConstraints, .supportsOrReplace]) XCTAssertSerialization( of: self.db.create(trigger: "foo", table: "planet", when: .after, event: .insert) .each(.row) @@ -80,6 +90,10 @@ final class SQLCreateDropTriggerTests: XCTestCase { .procedure("qwer"), is: "CREATE TRIGGER ``foo`` INSTEAD OF UPDATE ON ``planet`` FOR EACH ROW EXECUTE PROCEDURE ``qwer``" ) + XCTAssertSerialization( + of: self.db.create(trigger: "foo", table: "planet", when: .before, event: .insert) + .orReplace(), + is: "CREATE OR REPLACE TRIGGER ``foo`` BEFORE INSERT ON ``planet``") } func testPostgreSqlTriggerCreateWithColumns() {