Skip to content

Commit fdba310

Browse files
committed
Merge branch 'release-1.0.0'
2 parents 7dd4850 + e0da22a commit fdba310

32 files changed

+4357
-73
lines changed

.travis.yml

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
language: objective-c
22

33
os: osx
4-
osx_image: xcode10.1
4+
osx_image: xcode10.2
55

66
xcode_project: Restructure.xcodeproj
77

@@ -11,22 +11,22 @@ xcode_scheme:
1111
- Restructure tvOS
1212

1313
xcode_sdk:
14-
- iphonesimulator12.1
14+
- iphonesimulator12.2
1515
- macosx10.14
16-
- appletvsimulator12.1
16+
- appletvsimulator12.2
1717

1818
matrix:
1919
exclude:
2020
- xcode_scheme: Restructure iOS
2121
xcode_sdk: macosx10.14
2222
- xcode_scheme: Restructure iOS
23-
xcode_sdk: appletvsimulator12.1
23+
xcode_sdk: appletvsimulator12.2
2424
- xcode_scheme: Restructure macOS
25-
xcode_sdk: iphonesimulator12.1
25+
xcode_sdk: iphonesimulator12.2
2626
- xcode_scheme: Restructure macOS
27-
xcode_sdk: appletvsimulator12.1
27+
xcode_sdk: appletvsimulator12.2
2828
- xcode_scheme: Restructure tvOS
29-
xcode_sdk: iphonesimulator12.1
29+
xcode_sdk: iphonesimulator12.2
3030
- xcode_scheme: Restructure tvOS
3131
xcode_sdk: macosx10.14
3232

CHANGELOG.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8-
## [Unreleased]
8+
## 1.0.0 - 2019-06-20
99
### Added
1010
- Created the primary `Restructure` object for maintaining SQLite databases.
11+
- Created the `Statement` object for working with prepared SQLite statements.
12+
- Created the `Row` object for working with resultant rows from a `Statement`.
13+
- Created the `RowDecoder` and `StatementEncoder` for using Swift's `Decodable` protocols.

LICENSE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
Copyright (c) 2018 Stephen H. Gerstacker
1+
Copyright (c) 2019 Stephen H. Gerstacker
22

33
Permission is hereby granted, free of charge, to any person obtaining a copy
44
of this software and associated documentation files (the "Software"), to deal

README.md

Lines changed: 223 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,225 @@
11
# Restructure
22

3-
This is the future home of a complete rewrite of [Structure](https://github.com/stack/Structure). Restructure... get it?
3+
[![Build Status](https://travis-ci.org/stack/Restructure.svg?branch=develop)](https://travis-ci.org/stack/Restructure)
4+
![Swift 5.0](https://img.shields.io/badge/Swift-5.0-orange.svg)
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+
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
//
2+
// RestructureInitializationTests.swift
3+
// Restructure
4+
//
5+
// Created by Stephen H. Gerstacker on 11/4/18.
6+
// Copyright @ 2019 Stephen H. Gerstacker. All rights reserved.
7+
//
8+
9+
import XCTest
10+
@testable import Restructure
11+
12+
class RestructureInitializationTests: XCTestCase {
13+
14+
var restructure: Restructure? = nil
15+
var tempPath: String = ""
16+
17+
override func setUp() {
18+
tempPath = testPath(description: "Initialization Tests")
19+
}
20+
21+
override func tearDown() {
22+
if let restructure = restructure {
23+
restructure.close()
24+
self.restructure = nil
25+
}
26+
27+
let manager = FileManager.default
28+
29+
if manager.fileExists(atPath: tempPath) {
30+
try! manager.removeItem(atPath: tempPath)
31+
}
32+
}
33+
34+
func testCreateInMemoryWorks() {
35+
XCTAssertNoThrow(restructure = try Restructure())
36+
XCTAssertNotNil(restructure)
37+
}
38+
39+
func testCreateFileWorks() {
40+
XCTAssertNoThrow(restructure = try Restructure(path: tempPath))
41+
XCTAssertNotNil(restructure)
42+
}
43+
44+
func testCreateExistingFileWorks() {
45+
// Build the first time
46+
restructure = try! Restructure(path: tempPath)
47+
restructure!.close()
48+
restructure = nil
49+
50+
// Attempt to run again
51+
XCTAssertNoThrow(restructure = try Restructure(path: tempPath))
52+
XCTAssertNotNil(restructure)
53+
54+
XCTAssertNotNil(restructure)
55+
}
56+
57+
func testClosingTemporaryDeletedFile() {
58+
// Ensure the file doesn't exist
59+
XCTAssertFalse(FileManager.default.fileExists(atPath: tempPath))
60+
61+
// Build the structure
62+
restructure = try! Restructure(path: tempPath)
63+
restructure!.isTemporary = true
64+
65+
// Ensure the file does exist
66+
XCTAssertTrue(FileManager.default.fileExists(atPath: tempPath))
67+
68+
// Close and clean up
69+
restructure!.close()
70+
71+
// Ensure the file doesn't exist
72+
XCTAssertFalse(FileManager.default.fileExists(atPath: tempPath))
73+
}
74+
75+
}

0 commit comments

Comments
 (0)