|
1 | 1 | # Restructure |
2 | 2 |
|
3 | | -This is the future home of a complete rewrite of [Structure](https://github.com/stack/Structure). Restructure... get it? |
| 3 | +[](https://travis-ci.org/stack/Restructure) |
| 4 | + |
| 5 | + |
| 6 | +Restructure is a wrapper library for [SQLite](https://sqlite.org/index.html) for |
| 7 | +iOS, macOS, and tvOS. It's fairly opinionated, as in, it does exactly what I |
| 8 | +want it to do. Feel free to use it, fork it, or do what you would like with it. |
| 9 | + |
| 10 | +## Installation |
| 11 | + |
| 12 | +Adding this repository as a git submodule is the only way to use the library. |
| 13 | + |
| 14 | +1. Add this repository as a [git submodule](https://git-scm.com/book/en/v2/Git-Tools-Submodules). |
| 15 | +2. Check out the tag or branch you wish to use. |
| 16 | +3. Add the `Restructure.xcodeproj` project to your existing project. |
| 17 | +4. Add the appropriate framework to your Linked Frameworks and Libraries. |
| 18 | + |
| 19 | +In the future, when the dust has settled from WWDC '19, support for Swift |
| 20 | +Package Manager will be added. |
| 21 | + |
| 22 | +## Usage |
| 23 | + |
| 24 | +### Opening A Database |
| 25 | + |
| 26 | +A database can be opened with either a file path or run completely in memory. |
| 27 | + |
| 28 | +```swift |
| 29 | +// File backed database |
| 30 | +let restructure = try Restructure(path: "/path/to/data.db") |
| 31 | + |
| 32 | +// Memory backed database |
| 33 | +let restructure = try Restructure() |
| 34 | + |
| 35 | +// Closing the database |
| 36 | +restructure.close() |
| 37 | +``` |
| 38 | + |
| 39 | +### Standard SQLite |
| 40 | + |
| 41 | +Restructure supports the standard mechanisms of SQLite. |
| 42 | + |
| 43 | +```swift |
| 44 | +// Execute a statement |
| 45 | +try restructure.execute(query: "CREATE TABLE foo (name TEXT, age INTEGER)") |
| 46 | + |
| 47 | +// Insert data |
| 48 | +let insertStatement = try restructure.prepare(query: "INSERT INTO foo (name, age) VALUES (:name, :age)") |
| 49 | +insertStatement.bind(value: "Bar", for: "name") |
| 50 | +insertStatement.bind(value: 42, for: "age") |
| 51 | +try insertStatement.perform() |
| 52 | + |
| 53 | +// Update data |
| 54 | +let updateStatement = try restructure.prepare(query: "UPDATE foo SET age = :age WHERE name = :name") |
| 55 | +updateStatement.bind(value: 43, for: "age") |
| 56 | +updateStatement.bind(value: "Bar", for: "name") |
| 57 | +try updateStatement.perform() |
| 58 | + |
| 59 | +// Reuse a statement |
| 60 | +updateStatement.reset() |
| 61 | +updateStatement.bind(value: 44, for: "age") |
| 62 | +updateStatement.bind(value: "Bar", for: "name") |
| 63 | +try updateStatement.perform() |
| 64 | + |
| 65 | +// Fetch Data |
| 66 | +let selectStatement = try restructure.prepare(query: "SELECT name, age FROM foo") |
| 67 | + |
| 68 | +if case let row(row) = selectStatement.step() { |
| 69 | + let name: String = row["name"] |
| 70 | + let age: Int = row["age"] |
| 71 | +} |
| 72 | +``` |
| 73 | + |
| 74 | +Note: Statements finalize themselves. |
| 75 | + |
| 76 | +Data conversions are handled by the framework. When binding data, it is bound |
| 77 | +using the closest datatype available to SQLite. When extracting values from a |
| 78 | +row, the data is converted to the explicit type of the variable. Variable types |
| 79 | +must be defined to extract the data. SQLite is then used to perform any [data |
| 80 | +type conversion](https://www.sqlite.org/datatype3.html). |
| 81 | + |
| 82 | +Restructure currently supports the following data types: |
| 83 | + |
| 84 | +* Bool |
| 85 | +* Int |
| 86 | +* Int8 |
| 87 | +* Int16 |
| 88 | +* Int32 |
| 89 | +* Int64 |
| 90 | +* UInt |
| 91 | +* UInt8 |
| 92 | +* UInt16 |
| 93 | +* UInt32 |
| 94 | +* Float |
| 95 | +* Double |
| 96 | +* Data |
| 97 | +* Date |
| 98 | +* String |
| 99 | +* Array |
| 100 | + |
| 101 | + |
| 102 | +### Statements Are Sequences |
| 103 | + |
| 104 | +To help with fetching data, all statements are `Sequence` types and can be |
| 105 | +iterated over. The iterator returns a row for every successful `step` that would |
| 106 | +have been performed. |
| 107 | + |
| 108 | +```swift |
| 109 | +let statement = try restructure.prepare(query: "SELECT name, age FROM foo") |
| 110 | + |
| 111 | +for row in statement { |
| 112 | + let name: String = row["name"] |
| 113 | + let age: Int = row["age"] |
| 114 | +} |
| 115 | +``` |
| 116 | + |
| 117 | +### Complex Data Types |
| 118 | + |
| 119 | +Restructure supports storing arrays of data. This is done by encoding the data |
| 120 | +and storing it like a normal value. Encoding can either be done with binary |
| 121 | +plists or JSON. |
| 122 | + |
| 123 | + |
| 124 | +```swift |
| 125 | +// Make all arrays in Restructure binary plists |
| 126 | +restructure.arrayStrategy = .bplist |
| 127 | + |
| 128 | +// Make a specific statement use JSON |
| 129 | +statement.arrayStrategy = .json |
| 130 | + |
| 131 | +// Get and fetch an array of Integers |
| 132 | +statement.bind(value:[1,2,3], for: "values") |
| 133 | +let values: [Int] = row["values"] |
| 134 | +``` |
| 135 | + |
| 136 | +Dates can be stored in the formats supported by SQLite. Typically this means: |
| 137 | + |
| 138 | +* Integers for UNIX epoch times in seconds. |
| 139 | +* Real for Julian days since January 1, 4713 BC. |
| 140 | +* Text for ISO 8601 dates. |
| 141 | + |
| 142 | +```swift |
| 143 | +// Make all dates in Restructure julian |
| 144 | +restructure.arrayStrategy = .real |
| 145 | + |
| 146 | +// Make a specific statement use epoch |
| 147 | +statement.arrayStrategy = .integer |
| 148 | + |
| 149 | +// Get and fetch a date |
| 150 | +statement.bind(value: Date(), for: "date") |
| 151 | +let date: Date = row["date"] |
| 152 | +``` |
| 153 | + |
| 154 | +### Statements Are `Encodable` |
| 155 | + |
| 156 | +You can prepare a statement with the `StatementEncoder` and `Encodable` data: |
| 157 | + |
| 158 | +```swift |
| 159 | +struct Foo: Encodable { |
| 160 | + let a: Int64? |
| 161 | + let b: String |
| 162 | + let c: Double |
| 163 | + let d: Int |
| 164 | + let e: Data |
| 165 | +} |
| 166 | + |
| 167 | +let foo = Foo(a: nil, b: "1", c: 2.0, d: 3, e: Data(bytes: [0x4, 0x5, 0x6], count: 3)) |
| 168 | + |
| 169 | +let statement = try! restructure.prepare(query: "INSERT INTO foo (b, c, d, e) VALUES (:b, :c, :d, :e)") |
| 170 | +let encoder = StatementEncoder() |
| 171 | +try encoder.encode(foo, to: statement) |
| 172 | +``` |
| 173 | + |
| 174 | +### Rows are `Decodable` |
| 175 | + |
| 176 | +You can extract data from a row with a `RowDecoder` and `Decodable` data: |
| 177 | + |
| 178 | +```swift |
| 179 | +struct Foo: Encodable { |
| 180 | + let a: Int64? |
| 181 | + let b: String |
| 182 | + let c: Double |
| 183 | + let d: Int |
| 184 | + let e: Data |
| 185 | +} |
| 186 | + |
| 187 | +let statement = try! restructure.prepare(query: "SELECT a, b, c, d, e FROM foo LIMIT 1") |
| 188 | +let decoder = RowDecoder() |
| 189 | + |
| 190 | +for row in statement } |
| 191 | + let foo = try! decoder.decode(Foo.self, from: row) |
| 192 | +} |
| 193 | +``` |
| 194 | + |
| 195 | +### Migrations |
| 196 | + |
| 197 | +The Restructure object has a `userVersion` property to track the version of a |
| 198 | +database. This can be used for any purpose, but is best used for migrations. |
| 199 | + |
| 200 | +```swift |
| 201 | +// Run an initial migration |
| 202 | +try restructure.migrate(version: 1) { |
| 203 | + // Execute statements here |
| 204 | +} |
| 205 | + |
| 206 | +// Run another migration |
| 207 | +try restructure.migrate(version: 2) { |
| 208 | + // Execute more statements here |
| 209 | +} |
| 210 | +``` |
| 211 | + |
| 212 | +After each run of `migrate`, the `userVersion` value is incremented. Subsequent |
| 213 | +runs of migrations are ignored for versions that have already been run. |
| 214 | + |
| 215 | +## Caveats |
| 216 | + |
| 217 | +Restructure makes no guarantees about thread safety. It is as safe as the |
| 218 | +underlying SQLite library. |
| 219 | + |
| 220 | +The `Codable` support only supports single objects. Hierarchies of data are not |
| 221 | +supported. |
| 222 | + |
| 223 | +`UInt64` is not supported as a data type, as SQLite only supports signed 64-bit |
| 224 | +integers. |
| 225 | + |
0 commit comments