From 14de7cf4500251b649b4829e3c5905bd831f02f4 Mon Sep 17 00:00:00 2001 From: Simon Kempendorf Date: Mon, 4 Mar 2019 12:28:44 +0100 Subject: [PATCH 01/15] Fix bug on filter for empty group --- Sources/Fluent/QueryBuilder/QueryBuilder+Filter.swift | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Sources/Fluent/QueryBuilder/QueryBuilder+Filter.swift b/Sources/Fluent/QueryBuilder/QueryBuilder+Filter.swift index 1fb344c5..8665dbd2 100644 --- a/Sources/Fluent/QueryBuilder/QueryBuilder+Filter.swift +++ b/Sources/Fluent/QueryBuilder/QueryBuilder+Filter.swift @@ -122,8 +122,11 @@ extension QueryBuilder { let sub = query query = main - // apply the sub-filters as a group - Database.queryFilterApply(Database.queryFilterGroup(relation, Database.queryFilters(for: sub)), to: &query) + // apply the sub-filters as a group, if there are any + let queryFilters = Database.queryFilters(for: sub) + if !queryFilters.isEmpty { + Database.queryFilterApply(Database.queryFilterGroup(relation, queryFilters), to: &query) + } return self } } From 94f4190803bd47a90534f140ee14efc2edba51e4 Mon Sep 17 00:00:00 2001 From: Simon Kempendorf Date: Tue, 5 Mar 2019 22:16:44 +0100 Subject: [PATCH 02/15] Get CI passing by using correct branch --- circle.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/circle.yml b/circle.yml index a5f3186a..4dc4c510 100644 --- a/circle.yml +++ b/circle.yml @@ -39,7 +39,7 @@ jobs: steps: - run: name: Clone Fluent PostgreSQL - command: git clone -b master https://github.com/vapor/fluent-postgresql.git + command: git clone -b 1 https://github.com/vapor/fluent-postgresql.git working_directory: ~/ - run: name: Switch Fluent PostgreSQL to this Fluent revision From 41c94f3437c4c6b3c9da0e0346e7348023f8c593 Mon Sep 17 00:00:00 2001 From: Daniel Alm Date: Fri, 22 Mar 2019 14:38:27 +0100 Subject: [PATCH 03/15] Add support for copying query builders. --- .../Fluent/QueryBuilder/QueryBuilder+Copy.swift | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 Sources/Fluent/QueryBuilder/QueryBuilder+Copy.swift diff --git a/Sources/Fluent/QueryBuilder/QueryBuilder+Copy.swift b/Sources/Fluent/QueryBuilder/QueryBuilder+Copy.swift new file mode 100644 index 00000000..e0cf5936 --- /dev/null +++ b/Sources/Fluent/QueryBuilder/QueryBuilder+Copy.swift @@ -0,0 +1,16 @@ +extension QueryBuilder { + // MARK: Copy + + /// Creates a copy of the current query builder. Useful for constructing two similar, but not identical, queries. + /// + /// Example: two queries sorted by different criteria. + /// + /// let baseQuery = try User.query(on: conn).filter(\.name == "foo") + /// let sortedByID = baseQuery.copy().sort(\.id, .ascending) + /// let sortedByName = baseQuery.copy().sort(\.name, .ascending) + /// + /// - returns: A copy of the current query builder that can be manipulated independently of the original. + public func copy() -> Self { + return .init(query: self.query, on: self.connection, resultTransformer: self.resultTransformer) + } +} From 6c4e5bc525b6bb4a9f9c9dcd1914538043cad845 Mon Sep 17 00:00:00 2001 From: Tanner Nelson Date: Wed, 10 Apr 2019 14:28:30 -0400 Subject: [PATCH 04/15] fix ci --- circle.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/circle.yml b/circle.yml index 4dc4c510..f5f107be 100644 --- a/circle.yml +++ b/circle.yml @@ -61,7 +61,7 @@ jobs: steps: - run: name: Clone Fluent MySQL - command: git clone -b master https://github.com/vapor/fluent-mysql.git + command: git clone -b 3 https://github.com/vapor/fluent-mysql.git working_directory: ~/ - run: name: Switch Fluent MySQL to this Fluent revision From d71e49f78443593e83c8c71e6a03b8fb7ea0b522 Mon Sep 17 00:00:00 2001 From: Jerry Carter Date: Thu, 2 May 2019 18:53:35 -0400 Subject: [PATCH 05/15] Update Model+CRUD.swift Added 'withSoftDelete:true' to delete so that soft deleted rows can be removed. --- Sources/Fluent/Model/Model+CRUD.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Fluent/Model/Model+CRUD.swift b/Sources/Fluent/Model/Model+CRUD.swift index 840d0244..dc0957ef 100644 --- a/Sources/Fluent/Model/Model+CRUD.swift +++ b/Sources/Fluent/Model/Model+CRUD.swift @@ -49,7 +49,7 @@ extension Model where Database: QuerySupporting { /// - conn: Database connection to use. /// - returns: Future that will be completed when the delete is done. public func delete(force: Bool = false, on conn: DatabaseConnectable) -> Future { - return Self.query(on: conn).delete(self, force: force) + return Self.query(on: conn, withSoftDeleted: true).delete(self, force: force) } /// Restores a soft deleted model. From fe952d49033c4be9902dc38bf710a84c20c66b21 Mon Sep 17 00:00:00 2001 From: Jerry Carter Date: Thu, 2 May 2019 18:54:13 -0400 Subject: [PATCH 06/15] Update BenchmarkSoftDeletable.swift Added unit test for deleting a previously soft deleted row. --- .../Benchmarks/BenchmarkSoftDeletable.swift | 46 ++++++++++++++----- 1 file changed, 34 insertions(+), 12 deletions(-) diff --git a/Sources/FluentBenchmark/Benchmarks/BenchmarkSoftDeletable.swift b/Sources/FluentBenchmark/Benchmarks/BenchmarkSoftDeletable.swift index 7fae7d39..8be6bb65 100644 --- a/Sources/FluentBenchmark/Benchmarks/BenchmarkSoftDeletable.swift +++ b/Sources/FluentBenchmark/Benchmarks/BenchmarkSoftDeletable.swift @@ -4,38 +4,60 @@ extension Benchmarker where Database: QuerySupporting & TransactionSupporting { /// The actual benchmark. fileprivate func _benchmark(on conn: Database.Connection) throws { start("Soft delete") - // create - var bar = Bar(baz: 1) + var bar1 = Bar(baz: 1) + var bar2 = Bar(baz: 2) + // Initially empty if try test(Bar.query(on: conn).count()) != 0 { fail("count should have been 0") } - bar = try test(bar.save(on: conn)) + // Create two rows + bar1 = try test(bar1.save(on: conn)) + bar2 = try test(bar2.save(on: conn)) + if try test(Bar.query(on: conn).count()) != 2 { + fail("count should have been 2") + } + if try test(Bar.query(on: conn, withSoftDeleted: true).count()) != 2 { + fail("count should have been 2") + } + + // Soft delete the first + try test(bar1.delete(on: conn)) if try test(Bar.query(on: conn).count()) != 1 { fail("count should have been 1") } - if try test(Bar.query(on: conn, withSoftDeleted: true).count()) != 1 { - fail("count should have been 1") + if try test(Bar.query(on: conn, withSoftDeleted: true).count()) != 2 { + fail("count should have been 2") } - try test(bar.delete(on: conn)) + // Soft delete the second + try test(bar2.delete(on: conn)) if try test(Bar.query(on: conn).count()) != 0 { fail("count should have been 0") } - if try test(Bar.query(on: conn, withSoftDeleted: true).count()) != 1 { - fail("count should have been 1") + if try test(Bar.query(on: conn, withSoftDeleted: true).count()) != 2 { + fail("count should have been 2") } - - bar = try test(bar.restore(on: conn)) + + // Restore the first + bar1 = try test(bar1.restore(on: conn)) if try test(Bar.query(on: conn).count()) != 1 { fail("count should have been 1") } + if try test(Bar.query(on: conn, withSoftDeleted: true).count()) != 2 { + fail("count should have been 2") + } + + // Delete both + try test(bar1.delete(force: true, on: conn)) + if try test(Bar.query(on: conn).count()) != 0 { + fail("count should have been 0") + } if try test(Bar.query(on: conn, withSoftDeleted: true).count()) != 1 { fail("count should have been 1") } - - try test(bar.delete(force: true, on: conn)) + try test(bar2.delete(force: true, on: conn)) if try test(Bar.query(on: conn).count()) != 0 { fail("count should have been 0") } From 514cf3d991ac7ab485465f0504d6038a4dfea12d Mon Sep 17 00:00:00 2001 From: Jerry Carter Date: Fri, 3 May 2019 10:18:31 -0400 Subject: [PATCH 07/15] Update BenchmarkSoftDeletable.swift Added a test of the behavior of the updatedAt and deletedAt timestamps under soft delete. --- .../Benchmarks/BenchmarkSoftDeletable.swift | 72 ++++++++++++++++++- 1 file changed, 70 insertions(+), 2 deletions(-) diff --git a/Sources/FluentBenchmark/Benchmarks/BenchmarkSoftDeletable.swift b/Sources/FluentBenchmark/Benchmarks/BenchmarkSoftDeletable.swift index 8be6bb65..b2c1035d 100644 --- a/Sources/FluentBenchmark/Benchmarks/BenchmarkSoftDeletable.swift +++ b/Sources/FluentBenchmark/Benchmarks/BenchmarkSoftDeletable.swift @@ -66,10 +66,80 @@ extension Benchmarker where Database: QuerySupporting & TransactionSupporting { } } + /// Verify that updatedAt and deletedAt are changed appropriately for soft delete. + fileprivate func _benchmarkTimestamps(on conn: Database.Connection) throws { + start("Soft delete timestamps") + + // Initially empty + if try test(Bar.query(on: conn).count()) != 0 { + fail("count should have been 0") + } + + // Upon creation, updatedAt should be set and deletedAt should be nil + var bar = Bar(baz: 16) + bar = try test(bar.save(on: conn)) + if bar.deletedAt != nil { + fail("deletedAt should not be set") + } + guard let updatedAtAfterCreate = bar.updatedAt else { + fail("updatedAt was not set") + return + } + + // Ensure that there is a substantial difference for checking update timestamp. + Thread.sleep(forTimeInterval: 0.05) + + // After soft delete, updatedAt should have changed and deletedAt should now be set + try test(bar.delete(on: conn)) + guard let deletedAt = bar.deletedAt else { + fail("deletedAt was not set") + return + } + if !deletedAt.isWithin(seconds: 0.1, of: Date()) { + fail("timestamps should be current") + } + if deletedAt <= updatedAtAfterCreate { + fail("deletedAt value is invalid") + } + guard let updatedAtAfterDelete = bar.updatedAt else { + fail("updatedAt was cleared after soft delete") + return + } + if updatedAtAfterDelete <= updatedAtAfterCreate { + fail("updatedAt was not changed after soft delete") + } + + // Ensure that there is a substantial difference for checking update timestamp. + Thread.sleep(forTimeInterval: 0.05) + + // After reset, updatedAt should have changed and deletedAt should now be nil + bar = try test(bar.restore(on: conn)) + if bar.deletedAt != nil { + fail("deletedAt was not cleared after restore") + } + if bar.updatedAt == nil { + fail("updateAt was cleared after restore") + } + guard let updatedAtAfterRestore = bar.updatedAt else { + fail("updatedAt was cleared after restore") + return + } + if updatedAtAfterRestore <= updatedAtAfterDelete { + fail("updatedAt was not changed after restore") + } + + // Clean up + try test(Bar.query(on: conn, withSoftDeleted: true).delete(force: true)) + if try test(Bar.query(on: conn).count()) != 0 { + fail("count should be 0 at the end of the test") + } + } + /// Benchmark fluent transactions. public func benchmarkSoftDeletable() throws { let conn = try test(pool.requestConnection()) try self._benchmark(on: conn) + try self._benchmarkTimestamps(on: conn) pool.releaseConnection(conn) } } @@ -87,5 +157,3 @@ extension Benchmarker where Database: QuerySupporting & TransactionSupporting & pool.releaseConnection(conn) } } - - From 754ea918a8686653a75944fb3c7a4f84851bf54b Mon Sep 17 00:00:00 2001 From: Jerry Carter Date: Fri, 3 May 2019 10:46:54 -0400 Subject: [PATCH 08/15] Update Model+CRUD.swift Changed delete query based on value of 'force'. --- Sources/Fluent/Model/Model+CRUD.swift | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Sources/Fluent/Model/Model+CRUD.swift b/Sources/Fluent/Model/Model+CRUD.swift index dc0957ef..c638aaa4 100644 --- a/Sources/Fluent/Model/Model+CRUD.swift +++ b/Sources/Fluent/Model/Model+CRUD.swift @@ -49,7 +49,11 @@ extension Model where Database: QuerySupporting { /// - conn: Database connection to use. /// - returns: Future that will be completed when the delete is done. public func delete(force: Bool = false, on conn: DatabaseConnectable) -> Future { - return Self.query(on: conn, withSoftDeleted: true).delete(self, force: force) + if force { + return Self.query(on: conn, withSoftDeleted: true).delete(self, force: true) + } else { + return Self.query(on: conn).delete(self, force: false) + } } /// Restores a soft deleted model. From 3851c118304c023027cabda4390e0cba4c529798 Mon Sep 17 00:00:00 2001 From: Jerry Carter Date: Fri, 3 May 2019 10:53:49 -0400 Subject: [PATCH 09/15] Update BenchmarkSoftDeletable.swift Added second test to withSchema --- Sources/FluentBenchmark/Benchmarks/BenchmarkSoftDeletable.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Sources/FluentBenchmark/Benchmarks/BenchmarkSoftDeletable.swift b/Sources/FluentBenchmark/Benchmarks/BenchmarkSoftDeletable.swift index b2c1035d..80af8627 100644 --- a/Sources/FluentBenchmark/Benchmarks/BenchmarkSoftDeletable.swift +++ b/Sources/FluentBenchmark/Benchmarks/BenchmarkSoftDeletable.swift @@ -154,6 +154,7 @@ extension Benchmarker where Database: QuerySupporting & TransactionSupporting & try? test(Bar.revert(on: conn)) } try self._benchmark(on: conn) + try self._benchmarkTimestamps(on: conn) pool.releaseConnection(conn) } } From 7a261a212bf6b9f9210de937cce364ce33ad69f8 Mon Sep 17 00:00:00 2001 From: Jerry Carter Date: Fri, 3 May 2019 10:59:06 -0400 Subject: [PATCH 10/15] Update BenchmarkSoftDeletable.swift Restructured to parallel BenchmarkTimestampable.swift. --- .../Benchmarks/BenchmarkSoftDeletable.swift | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/Sources/FluentBenchmark/Benchmarks/BenchmarkSoftDeletable.swift b/Sources/FluentBenchmark/Benchmarks/BenchmarkSoftDeletable.swift index 80af8627..25b5523a 100644 --- a/Sources/FluentBenchmark/Benchmarks/BenchmarkSoftDeletable.swift +++ b/Sources/FluentBenchmark/Benchmarks/BenchmarkSoftDeletable.swift @@ -2,7 +2,7 @@ import Fluent extension Benchmarker where Database: QuerySupporting & TransactionSupporting { /// The actual benchmark. - fileprivate func _benchmark(on conn: Database.Connection) throws { + fileprivate func _benchmarkBasicFunctionality(on conn: Database.Connection) throws { start("Soft delete") var bar1 = Bar(baz: 1) var bar2 = Bar(baz: 2) @@ -134,12 +134,17 @@ extension Benchmarker where Database: QuerySupporting & TransactionSupporting { fail("count should be 0 at the end of the test") } } + + /// The actual benchmark. + fileprivate func _benchmark(on conn: Database.Connection) throws { + try _benchmarkBasicFunctionality(on: conn) + try _benchmarkTimestamps(on: conn) + } /// Benchmark fluent transactions. public func benchmarkSoftDeletable() throws { let conn = try test(pool.requestConnection()) try self._benchmark(on: conn) - try self._benchmarkTimestamps(on: conn) pool.releaseConnection(conn) } } @@ -154,7 +159,6 @@ extension Benchmarker where Database: QuerySupporting & TransactionSupporting & try? test(Bar.revert(on: conn)) } try self._benchmark(on: conn) - try self._benchmarkTimestamps(on: conn) pool.releaseConnection(conn) } } From d92d4cb95ae3530a8736a37c7d88904974d995ff Mon Sep 17 00:00:00 2001 From: Jerry Carter Date: Fri, 3 May 2019 11:28:18 -0400 Subject: [PATCH 11/15] Update BenchmarkSoftDeletable.swift Local only --- Sources/FluentBenchmark/Benchmarks/BenchmarkSoftDeletable.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Sources/FluentBenchmark/Benchmarks/BenchmarkSoftDeletable.swift b/Sources/FluentBenchmark/Benchmarks/BenchmarkSoftDeletable.swift index 25b5523a..a5051fa4 100644 --- a/Sources/FluentBenchmark/Benchmarks/BenchmarkSoftDeletable.swift +++ b/Sources/FluentBenchmark/Benchmarks/BenchmarkSoftDeletable.swift @@ -4,6 +4,7 @@ extension Benchmarker where Database: QuerySupporting & TransactionSupporting { /// The actual benchmark. fileprivate func _benchmarkBasicFunctionality(on conn: Database.Connection) throws { start("Soft delete") + fail("JERRY WAS HERE") var bar1 = Bar(baz: 1) var bar2 = Bar(baz: 2) @@ -69,6 +70,7 @@ extension Benchmarker where Database: QuerySupporting & TransactionSupporting { /// Verify that updatedAt and deletedAt are changed appropriately for soft delete. fileprivate func _benchmarkTimestamps(on conn: Database.Connection) throws { start("Soft delete timestamps") + fail("JERRY WAS HERE") // Initially empty if try test(Bar.query(on: conn).count()) != 0 { From 2c4340f86757d78a03ee54d843bb11c3b255fdf4 Mon Sep 17 00:00:00 2001 From: Jerry Carter Date: Fri, 3 May 2019 11:45:12 -0400 Subject: [PATCH 12/15] Revert "Update BenchmarkSoftDeletable.swift" This reverts commit d92d4cb95ae3530a8736a37c7d88904974d995ff. --- Sources/FluentBenchmark/Benchmarks/BenchmarkSoftDeletable.swift | 2 -- 1 file changed, 2 deletions(-) diff --git a/Sources/FluentBenchmark/Benchmarks/BenchmarkSoftDeletable.swift b/Sources/FluentBenchmark/Benchmarks/BenchmarkSoftDeletable.swift index a5051fa4..25b5523a 100644 --- a/Sources/FluentBenchmark/Benchmarks/BenchmarkSoftDeletable.swift +++ b/Sources/FluentBenchmark/Benchmarks/BenchmarkSoftDeletable.swift @@ -4,7 +4,6 @@ extension Benchmarker where Database: QuerySupporting & TransactionSupporting { /// The actual benchmark. fileprivate func _benchmarkBasicFunctionality(on conn: Database.Connection) throws { start("Soft delete") - fail("JERRY WAS HERE") var bar1 = Bar(baz: 1) var bar2 = Bar(baz: 2) @@ -70,7 +69,6 @@ extension Benchmarker where Database: QuerySupporting & TransactionSupporting { /// Verify that updatedAt and deletedAt are changed appropriately for soft delete. fileprivate func _benchmarkTimestamps(on conn: Database.Connection) throws { start("Soft delete timestamps") - fail("JERRY WAS HERE") // Initially empty if try test(Bar.query(on: conn).count()) != 0 { From 78b9865adff1facd84f6d34e29c9aaab9c509658 Mon Sep 17 00:00:00 2001 From: Jerry Carter Date: Fri, 3 May 2019 13:26:29 -0400 Subject: [PATCH 13/15] Update BenchmarkSoftDeletable.swift Need to read the row after soft delete to fetch the deletedAt setting. --- .../FluentBenchmark/Benchmarks/BenchmarkSoftDeletable.swift | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Sources/FluentBenchmark/Benchmarks/BenchmarkSoftDeletable.swift b/Sources/FluentBenchmark/Benchmarks/BenchmarkSoftDeletable.swift index 25b5523a..d886c349 100644 --- a/Sources/FluentBenchmark/Benchmarks/BenchmarkSoftDeletable.swift +++ b/Sources/FluentBenchmark/Benchmarks/BenchmarkSoftDeletable.swift @@ -91,6 +91,11 @@ extension Benchmarker where Database: QuerySupporting & TransactionSupporting { // After soft delete, updatedAt should have changed and deletedAt should now be set try test(bar.delete(on: conn)) + guard let deletedBar = try test(Bar.query(on: conn).first()) else { + fail("hard delete when soft was expected") + return + } + bar = deletedBar guard let deletedAt = bar.deletedAt else { fail("deletedAt was not set") return From 8f8c84f4200618402142d247c37da0d56158779f Mon Sep 17 00:00:00 2001 From: Jerry Carter Date: Fri, 3 May 2019 15:08:39 -0400 Subject: [PATCH 14/15] Update BenchmarkSoftDeletable.swift Oops. Must have withSoftDeleted set to true. --- Sources/FluentBenchmark/Benchmarks/BenchmarkSoftDeletable.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/FluentBenchmark/Benchmarks/BenchmarkSoftDeletable.swift b/Sources/FluentBenchmark/Benchmarks/BenchmarkSoftDeletable.swift index d886c349..88162b3b 100644 --- a/Sources/FluentBenchmark/Benchmarks/BenchmarkSoftDeletable.swift +++ b/Sources/FluentBenchmark/Benchmarks/BenchmarkSoftDeletable.swift @@ -91,7 +91,7 @@ extension Benchmarker where Database: QuerySupporting & TransactionSupporting { // After soft delete, updatedAt should have changed and deletedAt should now be set try test(bar.delete(on: conn)) - guard let deletedBar = try test(Bar.query(on: conn).first()) else { + guard let deletedBar = try test(Bar.query(on: conn, withSoftDeleted: true).first()) else { fail("hard delete when soft was expected") return } From a4724cf6137a58e11bf227a00a066df455440a02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zsolt=20V=C3=A1radi?= Date: Tue, 7 May 2019 22:23:01 +0200 Subject: [PATCH 15/15] Fix fluent-sqlite test on CircleCI --- circle.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/circle.yml b/circle.yml index f5f107be..c9ac4e46 100644 --- a/circle.yml +++ b/circle.yml @@ -77,7 +77,7 @@ jobs: steps: - run: name: Clone Fluent SQLite - command: git clone -b master https://github.com/vapor/fluent-sqlite.git + command: git clone -b 3 https://github.com/vapor/fluent-sqlite.git working_directory: ~/ - run: name: Switch Fluent SQLite to this Fluent revision