Skip to content

Commit 140374a

Browse files
committed
implement batch insert, insertMany()
1 parent 70fb7af commit 140374a

File tree

3 files changed

+98
-1
lines changed

3 files changed

+98
-1
lines changed

Sources/SQLite/Typed/Coding.swift

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,13 +38,36 @@ extension QueryType {
3838
///
3939
/// - otherSetters: Any other setters to include in the insert
4040
///
41-
/// - Returns: An `INSERT` statement fort the encodable object
41+
/// - Returns: An `INSERT` statement for the encodable object
4242
public func insert(_ encodable: Encodable, userInfo: [CodingUserInfoKey:Any] = [:], otherSetters: [Setter] = []) throws -> Insert {
4343
let encoder = SQLiteEncoder(userInfo: userInfo)
4444
try encodable.encode(to: encoder)
4545
return self.insert(encoder.setters + otherSetters)
4646
}
4747

48+
/// Creates a batch `INSERT` statement by encoding the array of given objects
49+
/// This method converts any custom nested types to JSON data and does not handle any sort
50+
/// of object relationships. If you want to support relationships between objects you will
51+
/// have to provide your own Encodable implementations that encode the correct ids.
52+
///
53+
/// - Parameters:
54+
///
55+
/// - encodables: Encodable objects to insert
56+
///
57+
/// - userInfo: User info to be passed to encoder
58+
///
59+
/// - otherSetters: Any other setters to include in the inserts, per row/object.
60+
///
61+
/// - Returns: An `INSERT` statement for the encodable objects
62+
public func insertMany(_ encodables: [Encodable], userInfo: [CodingUserInfoKey:Any] = [:], otherSetters: [Setter] = []) throws -> Insert {
63+
let combinedSetters = try encodables.map { encodable -> [Setter] in
64+
let encoder = SQLiteEncoder(userInfo: userInfo)
65+
try encodable.encode(to: encoder)
66+
return encoder.setters + otherSetters
67+
}
68+
return self.insertMany(combinedSetters)
69+
}
70+
4871
/// Creates an `UPDATE` statement by encoding the given object
4972
/// This method converts any custom nested types to JSON data and does not handle any sort
5073
/// of object relationships. If you want to support relationships between objects you will

Sources/SQLite/Typed/Query.swift

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -631,6 +631,18 @@ extension QueryType {
631631
return insert(onConflict, values)
632632
}
633633

634+
public func insertMany( _ values: [[Setter]]) -> Insert {
635+
return insertMany(nil, values)
636+
}
637+
638+
public func insertMany(or onConflict: OnConflict, _ values: [[Setter]]) -> Insert {
639+
return insertMany(onConflict, values)
640+
}
641+
642+
public func insertMany(or onConflict: OnConflict, _ values: [Setter]...) -> Insert {
643+
return insertMany(onConflict, values)
644+
}
645+
634646
fileprivate func insert(_ or: OnConflict?, _ values: [Setter]) -> Insert {
635647
let insert = values.reduce((columns: [Expressible](), values: [Expressible]())) { insert, setter in
636648
(insert.columns + [setter.column], insert.values + [setter.value])
@@ -650,6 +662,29 @@ extension QueryType {
650662
return Insert(" ".join(clauses.compactMap { $0 }).expression)
651663
}
652664

665+
fileprivate func insertMany(_ or: OnConflict?, _ values: [[Setter]]) -> Insert {
666+
guard values.count > 0 else {
667+
return insert()
668+
}
669+
let insertRows = values.map { rowValues in
670+
rowValues.reduce((columns: [Expressible](), values: [Expressible]())) { insert, setter in
671+
(insert.columns + [setter.column], insert.values + [setter.value])
672+
}
673+
}
674+
675+
let clauses: [Expressible?] = [
676+
Expression<Void>(literal: "INSERT"),
677+
or.map { Expression<Void>(literal: "OR \($0.rawValue)") },
678+
Expression<Void>(literal: "INTO"),
679+
tableName(),
680+
"".wrap(insertRows[0].columns) as Expression<Void>,
681+
Expression<Void>(literal: "VALUES"),
682+
", ".join(insertRows.map(\.values).map({ "".wrap($0) as Expression<Void> })),
683+
whereClause
684+
]
685+
return Insert(" ".join(clauses.compactMap { $0 }).expression)
686+
}
687+
653688
/// Runs an `INSERT` statement against the query with `DEFAULT VALUES`.
654689
public func insert() -> Insert {
655690
return Insert(" ".join([
@@ -1010,6 +1045,8 @@ extension Connection {
10101045
/// - SeeAlso: `QueryType.insert(value:_:)`
10111046
/// - SeeAlso: `QueryType.insert(values:)`
10121047
/// - SeeAlso: `QueryType.insert(or:_:)`
1048+
/// - SeeAlso: `QueryType.insertMany(values:)`
1049+
/// - SeeAlso: `QueryType.insertMany(or:_:)`
10131050
/// - SeeAlso: `QueryType.insert()`
10141051
///
10151052
/// - Parameter query: An insert query.

Tests/SQLiteTests/QueryTests.swift

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,26 @@ class QueryTests : XCTestCase {
247247
)
248248
}
249249

250+
func test_insert_many_compilesInsertManyExpression() {
251+
AssertSQL(
252+
"INSERT INTO \"users\" (\"email\", \"age\") VALUES ('[email protected]', 30), ('[email protected]', 32), ('[email protected]', 83)",
253+
users.insertMany([[email <- "[email protected]", age <- 30], [email <- "[email protected]", age <- 32], [email <- "[email protected]", age <- 83]])
254+
)
255+
}
256+
func test_insert_many_compilesInsertManyNoneExpression() {
257+
AssertSQL(
258+
"INSERT INTO \"users\" DEFAULT VALUES",
259+
users.insertMany([])
260+
)
261+
}
262+
263+
func test_insert_many_withOnConflict_compilesInsertManyOrOnConflictExpression() {
264+
AssertSQL(
265+
"INSERT OR REPLACE INTO \"users\" (\"email\", \"age\") VALUES ('[email protected]', 30), ('[email protected]', 32), ('[email protected]', 83)",
266+
users.insertMany(or: .replace, [[email <- "[email protected]", age <- 30], [email <- "[email protected]", age <- 32], [email <- "[email protected]", age <- 83]])
267+
)
268+
}
269+
250270
func test_insert_encodable() throws {
251271
let emails = Table("emails")
252272
let value = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, optional: nil, sub: nil)
@@ -270,6 +290,18 @@ class QueryTests : XCTestCase {
270290
)
271291
}
272292

293+
func test_insert_many_encodable() throws {
294+
let emails = Table("emails")
295+
let value1 = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, optional: nil, sub: nil)
296+
let value2 = TestCodable(int: 2, string: "3", bool: true, float: 3, double: 5, optional: nil, sub: nil)
297+
let value3 = TestCodable(int: 3, string: "4", bool: true, float: 3, double: 6, optional: nil, sub: nil)
298+
let insert = try emails.insertMany([value1, value2, value3])
299+
AssertSQL(
300+
"INSERT INTO \"emails\" (\"int\", \"string\", \"bool\", \"float\", \"double\") VALUES (1, '2', 1, 3.0, 4.0), (2, '3', 1, 3.0, 5.0), (3, '4', 1, 3.0, 6.0)",
301+
insert
302+
)
303+
}
304+
273305
func test_update_compilesUpdateExpression() {
274306
AssertSQL(
275307
"UPDATE \"users\" SET \"age\" = 30, \"admin\" = 1 WHERE (\"id\" = 1)",
@@ -483,6 +515,11 @@ class QueryIntegrationTests : SQLiteTestCase {
483515
XCTAssertEqual(1, id)
484516
}
485517

518+
func test_insert_many() {
519+
let id = try! db.run(users.insertMany([[email <- "[email protected]"], [email <- "[email protected]"]]))
520+
XCTAssertEqual(2, id)
521+
}
522+
486523
func test_update() {
487524
let changes = try! db.run(users.update(email <- "[email protected]"))
488525
XCTAssertEqual(0, changes)

0 commit comments

Comments
 (0)