Skip to content

Commit 026c5c2

Browse files
committed
Introduce support for backup
1 parent 36e01b0 commit 026c5c2

File tree

3 files changed

+230
-0
lines changed

3 files changed

+230
-0
lines changed

SQLite.xcodeproj/project.pbxproj

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,13 @@
77
objects = {
88

99
/* Begin PBXBuildFile section */
10+
02A43A9822738CF100FEC494 /* Backup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02A43A9722738CF100FEC494 /* Backup.swift */; };
11+
02A43A9922738CF100FEC494 /* Backup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02A43A9722738CF100FEC494 /* Backup.swift */; };
12+
02A43A9A22738CF100FEC494 /* Backup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02A43A9722738CF100FEC494 /* Backup.swift */; };
13+
02A43A9B22738CF100FEC494 /* Backup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02A43A9722738CF100FEC494 /* Backup.swift */; };
14+
02A43A9D22738E2900FEC494 /* BackupTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02A43A9C22738E2900FEC494 /* BackupTests.swift */; };
15+
02A43A9E22738E2900FEC494 /* BackupTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02A43A9C22738E2900FEC494 /* BackupTests.swift */; };
16+
02A43A9F22738E2900FEC494 /* BackupTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02A43A9C22738E2900FEC494 /* BackupTests.swift */; };
1017
03A65E641C6BB0F60062603F /* SQLite.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 03A65E5A1C6BB0F50062603F /* SQLite.framework */; };
1118
03A65E721C6BB2D30062603F /* SQLite.h in Headers */ = {isa = PBXBuildFile; fileRef = EE247AD61C3F04ED00AE3E12 /* SQLite.h */; settings = {ATTRIBUTES = (Public, ); }; };
1219
03A65E731C6BB2D80062603F /* Foundation.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF71C3F06E900AE3E12 /* Foundation.swift */; };
@@ -211,6 +218,8 @@
211218
/* End PBXContainerItemProxy section */
212219

213220
/* Begin PBXFileReference section */
221+
02A43A9722738CF100FEC494 /* Backup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Backup.swift; sourceTree = "<group>"; };
222+
02A43A9C22738E2900FEC494 /* BackupTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackupTests.swift; sourceTree = "<group>"; };
214223
03A65E5A1C6BB0F50062603F /* SQLite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SQLite.framework; sourceTree = BUILT_PRODUCTS_DIR; };
215224
03A65E631C6BB0F60062603F /* SQLiteTests tvOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "SQLiteTests tvOS.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
216225
03A65E961C6BB3210062603F /* libsqlite3.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libsqlite3.tbd; path = Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS.sdk/usr/lib/libsqlite3.tbd; sourceTree = DEVELOPER_DIR; };
@@ -398,6 +407,7 @@
398407
19A17E2695737FAB5D6086E3 /* fixtures */,
399408
EE247B1A1C3F137700AE3E12 /* AggregateFunctionsTests.swift */,
400409
EE247B1B1C3F137700AE3E12 /* BlobTests.swift */,
410+
02A43A9C22738E2900FEC494 /* BackupTests.swift */,
401411
EE247B1D1C3F137700AE3E12 /* ConnectionTests.swift */,
402412
EE247B1E1C3F137700AE3E12 /* CoreFunctionsTests.swift */,
403413
EE247B1F1C3F137700AE3E12 /* CustomFunctionsTests.swift */,
@@ -434,6 +444,7 @@
434444
EE247AF21C3F06E900AE3E12 /* Statement.swift */,
435445
EE247AF31C3F06E900AE3E12 /* Value.swift */,
436446
19A1710E73A46D5AC721CDA9 /* Errors.swift */,
447+
02A43A9722738CF100FEC494 /* Backup.swift */,
437448
);
438449
path = Core;
439450
sourceTree = "<group>";
@@ -817,6 +828,7 @@
817828
03A65E7E1C6BB2FB0062603F /* AggregateFunctions.swift in Sources */,
818829
19A17EC0D68BA8C03288ADF7 /* FTS5.swift in Sources */,
819830
19A179E76EA6207669B60C1B /* Cipher.swift in Sources */,
831+
02A43A9A22738CF100FEC494 /* Backup.swift in Sources */,
820832
19A17FF4A10B44D3937C8CAC /* Errors.swift in Sources */,
821833
19A1737286A74F3CF7412906 /* DateAndTimeFunctions.swift in Sources */,
822834
);
@@ -839,6 +851,7 @@
839851
03A65E911C6BB3030062603F /* SchemaTests.swift in Sources */,
840852
03A65E8D1C6BB3030062603F /* FTS4Tests.swift in Sources */,
841853
03A65E8C1C6BB3030062603F /* ExpressionTests.swift in Sources */,
854+
02A43A9F22738E2900FEC494 /* BackupTests.swift in Sources */,
842855
03A65E8E1C6BB3030062603F /* OperatorsTests.swift in Sources */,
843856
03A65E951C6BB3030062603F /* TestHelpers.swift in Sources */,
844857
19A17254FBA7894891F7297B /* FTS5Tests.swift in Sources */,
@@ -875,6 +888,7 @@
875888
3D67B3E71DB246BA00A4F4C6 /* Blob.swift in Sources */,
876889
3D67B3E81DB246BA00A4F4C6 /* Connection.swift in Sources */,
877890
19A179CCF9671E345E5A9811 /* Cipher.swift in Sources */,
891+
02A43A9B22738CF100FEC494 /* Backup.swift in Sources */,
878892
19A17DC282E36C4F41AA440B /* Errors.swift in Sources */,
879893
19A173668D948AD4DF1F5352 /* DateAndTimeFunctions.swift in Sources */,
880894
);
@@ -905,6 +919,7 @@
905919
EE247B0D1C3F06E900AE3E12 /* AggregateFunctions.swift in Sources */,
906920
19A1717B10CC941ACB5533D6 /* FTS5.swift in Sources */,
907921
19A171F12AB8B07F2FD7201A /* Cipher.swift in Sources */,
922+
02A43A9822738CF100FEC494 /* Backup.swift in Sources */,
908923
19A1792C0520D4E83C2EB075 /* Errors.swift in Sources */,
909924
19A17E29278A12BC4F542506 /* DateAndTimeFunctions.swift in Sources */,
910925
);
@@ -927,6 +942,7 @@
927942
EE247B351C3F142E00AE3E12 /* ValueTests.swift in Sources */,
928943
EE247B2F1C3F141E00AE3E12 /* QueryTests.swift in Sources */,
929944
EE247B221C3F137700AE3E12 /* AggregateFunctionsTests.swift in Sources */,
945+
02A43A9D22738E2900FEC494 /* BackupTests.swift in Sources */,
930946
EE247B2E1C3F141E00AE3E12 /* OperatorsTests.swift in Sources */,
931947
EE247B251C3F137700AE3E12 /* ConnectionTests.swift in Sources */,
932948
19A171E6FA242F72A308C594 /* FTS5Tests.swift in Sources */,
@@ -963,6 +979,7 @@
963979
EE247B6D1C3F3FEC00AE3E12 /* AggregateFunctions.swift in Sources */,
964980
19A1750CEE9B05267995CF3D /* FTS5.swift in Sources */,
965981
19A17835FD5886FDC5A3228F /* Cipher.swift in Sources */,
982+
02A43A9922738CF100FEC494 /* Backup.swift in Sources */,
966983
19A17490543609FCED53CACC /* Errors.swift in Sources */,
967984
19A17152E32A9585831E3FE0 /* DateAndTimeFunctions.swift in Sources */,
968985
);
@@ -985,6 +1002,7 @@
9851002
EE247B551C3F3FC700AE3E12 /* ConnectionTests.swift in Sources */,
9861003
EE247B611C3F3FC700AE3E12 /* TestHelpers.swift in Sources */,
9871004
EE247B581C3F3FC700AE3E12 /* ExpressionTests.swift in Sources */,
1005+
02A43A9E22738E2900FEC494 /* BackupTests.swift in Sources */,
9881006
EE247B5E1C3F3FC700AE3E12 /* SetterTests.swift in Sources */,
9891007
EE247B5B1C3F3FC700AE3E12 /* QueryTests.swift in Sources */,
9901008
19A174D78559CD30679BCCCB /* FTS5Tests.swift in Sources */,

Sources/SQLite/Core/Backup.swift

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
//
2+
// SQLite.swift
3+
// https://github.com/stephencelis/SQLite.swift
4+
// Copyright © 2014-2015 Stephen Celis.
5+
//
6+
// Permission is hereby granted, free of charge, to any person obtaining a copy
7+
// of this software and associated documentation files (the "Software"), to deal
8+
// in the Software without restriction, including without limitation the rights
9+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
// copies of the Software, and to permit persons to whom the Software is
11+
// furnished to do so, subject to the following conditions:
12+
//
13+
// The above copyright notice and this permission notice shall be included in
14+
// all copies or substantial portions of the Software.
15+
//
16+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22+
// THE SOFTWARE.
23+
//
24+
25+
import Foundation
26+
import Dispatch
27+
#if SQLITE_SWIFT_STANDALONE
28+
import sqlite3
29+
#elseif SQLITE_SWIFT_SQLCIPHER
30+
import SQLCipher
31+
#elseif os(Linux)
32+
import CSQLite
33+
#else
34+
import SQLite3
35+
#endif
36+
37+
/// An object representing database backup.
38+
///
39+
/// See: <https://www.sqlite.org/backup.html>
40+
public final class Backup {
41+
42+
/// The name of the database to backup
43+
public enum DatabaseName {
44+
45+
/// The main database
46+
case main
47+
48+
/// The temporary database
49+
case temp
50+
51+
/// A database added to the connection with ATTACH statement
52+
case attached(name: String)
53+
54+
var name: String {
55+
switch self {
56+
case .main:
57+
return "main"
58+
case .temp:
59+
return "temp"
60+
case .attached(let name):
61+
return name
62+
}
63+
}
64+
}
65+
66+
/// Number of pages to copy while performing a backup step
67+
public enum Pages {
68+
69+
/// Indicates all remaining pages should be copied
70+
case all
71+
72+
/// Indicates the maximal number of pages to be copied in single step
73+
case limited(number: Int32)
74+
75+
var number: Int32 {
76+
switch self {
77+
case .all:
78+
return -1
79+
case .limited(let number):
80+
return number
81+
}
82+
}
83+
}
84+
85+
/// Total number of pages to copy
86+
///
87+
/// See: <https://www.sqlite.org/c3ref/backup_finish.html#sqlite3backuppagecount>
88+
public var pageCount: Int32 {
89+
return handle.map { sqlite3_backup_pagecount($0) } ?? 0
90+
}
91+
92+
/// Number of remaining pages to copy.
93+
///
94+
/// See: <https://www.sqlite.org/c3ref/backup_finish.html#sqlite3backupremaining>
95+
public var remainingPages: Int32 {
96+
return handle.map { sqlite3_backup_remaining($0) } ?? 0
97+
}
98+
99+
private let targetConnection: Connection
100+
private let sourceConnection: Connection
101+
102+
private var handle: OpaquePointer?
103+
104+
/// Initializes a new SQLite backup.
105+
///
106+
/// - Parameters:
107+
///
108+
/// - targetConnection: The connection to the database to save backup into.
109+
///
110+
/// - targetConnection: The name of the database to save backup into.
111+
///
112+
/// Default: `.main`.
113+
///
114+
/// - sourceConnection: The connection to the database to backup.
115+
///
116+
/// - targetConnection: The name of the database to backup.
117+
///
118+
/// Default: `.main`.
119+
///
120+
/// - Returns: A new database backup.
121+
///
122+
/// See: <https://www.sqlite.org/c3ref/backup_finish.html#sqlite3backupinit>
123+
public init(targetConnection: Connection,
124+
targetName: DatabaseName = .main,
125+
sourceConnection: Connection,
126+
sourceName: DatabaseName = .main) throws {
127+
128+
self.targetConnection = targetConnection
129+
self.sourceConnection = sourceConnection
130+
131+
self.handle = sqlite3_backup_init(targetConnection.handle,
132+
targetName.name.withCString { $0 },
133+
sourceConnection.handle,
134+
sourceName.name.withCString { $0 })
135+
136+
if self.handle == nil, let error = Result(errorCode: sqlite3_errcode(targetConnection.handle), connection: targetConnection) {
137+
throw error
138+
}
139+
}
140+
141+
/// Performs a backup step.
142+
///
143+
/// - Parameter pagesToCopy: The maximal number of pages to copy in one step
144+
///
145+
/// - Throws: `Result.Error` if step fails.
146+
//
147+
/// See: <https://www.sqlite.org/c3ref/backup_finish.html#sqlite3backupstep>
148+
public func step(pagesToCopy pages: Pages = .all) throws {
149+
let status = sqlite3_backup_step(handle, pages.number)
150+
151+
guard status != SQLITE_DONE else {
152+
finish()
153+
return
154+
}
155+
156+
if let error = Result(errorCode: status, connection: targetConnection) {
157+
throw error
158+
}
159+
}
160+
161+
/// Finalizes backup.
162+
///
163+
/// See: <https://www.sqlite.org/c3ref/backup_finish.html#sqlite3backupfinish>
164+
public func finish() {
165+
guard let handle = self.handle else {
166+
return
167+
}
168+
169+
sqlite3_backup_finish(handle)
170+
self.handle = nil
171+
}
172+
173+
deinit {
174+
finish()
175+
}
176+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import XCTest
2+
import Foundation
3+
import Dispatch
4+
@testable import SQLite
5+
6+
#if SQLITE_SWIFT_STANDALONE
7+
import sqlite3
8+
#elseif SQLITE_SWIFT_SQLCIPHER
9+
import SQLCipher
10+
#elseif os(Linux)
11+
import CSQLite
12+
#else
13+
import SQLite3
14+
#endif
15+
16+
class BackupTests : SQLiteTestCase {
17+
override func setUp() {
18+
super.setUp()
19+
20+
CreateUsersTable()
21+
}
22+
23+
func test_backup_copies_database() throws {
24+
let source = db!
25+
let target = try Connection()
26+
27+
try InsertUsers("alice", "betsy")
28+
29+
let backup = try Backup(targetConnection: target, sourceConnection: source)
30+
try backup.step()
31+
32+
let users = try target.prepare("SELECT email FROM users ORDER BY email")
33+
XCTAssertEqual(users.map { $0[0] as? String }, ["alice@example.com", "betsy@example.com"])
34+
}
35+
}
36+

0 commit comments

Comments
 (0)