Skip to content

Commit a4c1d1d

Browse files
authored
Merge pull request #1148 from stephencelis/schemachanger-part-2
schemachanger-part-2
2 parents 2181f48 + 020ec7a commit a4c1d1d

20 files changed

+966
-367
lines changed

.swiftlint.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ disabled_rules: # rule identifiers to exclude from running
33
- operator_whitespace
44
- large_tuple
55
- closure_parameter_position
6+
- inclusive_language # sqlite_master etc.
67
included: # paths to include during linting. `--path` is ignored if present. takes precendence over `excluded`.
78
- Sources
89
- Tests

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
0.14.0 (tbd), [diff][diff-0.14.0]
22
========================================
33

4+
* Support more complex schema changes and queries ([#1073][], [#1146][] [#1148][])
45
* Support `ATTACH`/`DETACH` ([#30][], [#1142][])
56
* Support `WITH` clause ([#1139][])
67
* Add `Value` conformance for `NSURL` ([#1110][], [#1141][])
@@ -160,6 +161,7 @@
160161
[#866]: https://github.com/stephencelis/SQLite.swift/pull/866
161162
[#881]: https://github.com/stephencelis/SQLite.swift/pull/881
162163
[#919]: https://github.com/stephencelis/SQLite.swift/pull/919
164+
[#1073]: https://github.com/stephencelis/SQLite.swift/issues/1073
163165
[#1075]: https://github.com/stephencelis/SQLite.swift/pull/1075
164166
[#1077]: https://github.com/stephencelis/SQLite.swift/issues/1077
165167
[#1094]: https://github.com/stephencelis/SQLite.swift/pull/1094
@@ -185,3 +187,5 @@
185187
[#1141]: https://github.com/stephencelis/SQLite.swift/pull/1141
186188
[#1142]: https://github.com/stephencelis/SQLite.swift/pull/1142
187189
[#1144]: https://github.com/stephencelis/SQLite.swift/pull/1144
190+
[#1146]: https://github.com/stephencelis/SQLite.swift/pull/1146
191+
[#1148]: https://github.com/stephencelis/SQLite.swift/pull/1148

Documentation/Index.md

Lines changed: 78 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1372,6 +1372,37 @@ try db.transaction {
13721372

13731373
> _Note:_ Transactions run in a serial queue.
13741374

1375+
## Querying the Schema
1376+
1377+
We can obtain generic information about objects in the current schema with a `SchemaReader`:
1378+
1379+
```swift
1380+
let schema = db.schema
1381+
```
1382+
1383+
To query the data:
1384+
1385+
```swift
1386+
let indexes = try schema.objectDefinitions(type: .index)
1387+
let tables = try schema.objectDefinitions(type: .table)
1388+
let triggers = try schema.objectDefinitions(type: .trigger)
1389+
```
1390+
1391+
### Indexes and Columns
1392+
1393+
Specialized methods are available to get more detailed information:
1394+
1395+
```swift
1396+
let indexes = try schema.indexDefinitions("users")
1397+
let columns = try schema.columnDefinitions("users")
1398+
1399+
for index in indexes {
1400+
print("\(index.name) columns:\(index.columns))")
1401+
}
1402+
for column in columns {
1403+
print("\(column.name) pk:\(column.primaryKey) nullable: \(column.nullable)")
1404+
}
1405+
```
13751406

13761407
## Altering the Schema
13771408

@@ -1455,11 +1486,56 @@ tables](#creating-a-table).
14551486

14561487
### Renaming Columns
14571488

1458-
Added in SQLite 3.25.0, not exposed yet. [#1073](https://github.com/stephencelis/SQLite.swift/issues/1073)
1489+
We can rename columns with the help of the `SchemaChanger` class:
1490+
1491+
```swift
1492+
let schemaChanger = SchemaChanger(connection: db)
1493+
try schemaChanger.alter(table: "users") { table in
1494+
table.rename(column: "old_name", to: "new_name")
1495+
}
1496+
```
14591497

14601498
### Dropping Columns
14611499

1462-
Added in SQLite 3.35.0, not exposed yet. [#1073](https://github.com/stephencelis/SQLite.swift/issues/1073)
1500+
```swift
1501+
let schemaChanger = SchemaChanger(connection: db)
1502+
try schemaChanger.alter(table: "users") { table in
1503+
table.drop(column: "email")
1504+
}
1505+
```
1506+
1507+
These operations will work with all versions of SQLite and use modern SQL
1508+
operations such as `DROP COLUMN` when available.
1509+
1510+
### Adding Columns (SchemaChanger)
1511+
1512+
The `SchemaChanger` provides an alternative API to add new columns:
1513+
1514+
```swift
1515+
let newColumn = ColumnDefinition(
1516+
name: "new_text_column",
1517+
type: .TEXT,
1518+
nullable: true,
1519+
defaultValue: .stringLiteral("foo")
1520+
)
1521+
1522+
let schemaChanger = SchemaChanger(connection: db)
1523+
1524+
try schemaChanger.alter(table: "users") { table in
1525+
table.add(newColumn)
1526+
}
1527+
```
1528+
1529+
### Renaming/dropping Tables (SchemaChanger)
1530+
1531+
The `SchemaChanger` provides an alternative API to rename and drop tables:
1532+
1533+
```swift
1534+
let schemaChanger = SchemaChanger(connection: db)
1535+
1536+
try schemaChanger.rename(table: "users", to: "users_new")
1537+
try schemaChanger.drop(table: "emails")
1538+
```
14631539

14641540
### Indexes
14651541

@@ -1516,7 +1592,6 @@ try db.run(users.dropIndex(email, ifExists: true))
15161592
// DROP INDEX IF EXISTS "index_users_on_email"
15171593
```
15181594

1519-
15201595
### Dropping Tables
15211596

15221597
We can build
@@ -1536,7 +1611,6 @@ try db.run(users.drop(ifExists: true))
15361611
// DROP TABLE IF EXISTS "users"
15371612
```
15381613

1539-
15401614
### Migrations and Schema Versioning
15411615

15421616
You can use the convenience property on `Connection` to query and set the

Makefile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ build:
1818

1919
lint:
2020
swiftlint --strict
21+
lint-fix:
22+
swiftlint lint fix
2123

2224
test:
2325
ifdef XCPRETTY

SQLite.playground/Contents.swift

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,3 +101,31 @@ db.createAggregation("customConcat",
101101
result: { $0 })
102102
let result = try db.prepare("SELECT customConcat(email) FROM users").scalar() as! String
103103
print(result)
104+
105+
/// schema queries
106+
let schema = db.schema
107+
let objects = try schema.objectDefinitions()
108+
print(objects)
109+
110+
let columns = try schema.columnDefinitions(table: "users")
111+
print(columns)
112+
113+
/// schema alteration
114+
115+
let schemaChanger = SchemaChanger(connection: db)
116+
try schemaChanger.alter(table: "users") { table in
117+
table.add(ColumnDefinition(name: "age", type: .INTEGER))
118+
table.rename(column: "email", to: "electronic_mail")
119+
table.drop(column: "name")
120+
}
121+
122+
let changedColumns = try schema.columnDefinitions(table: "users")
123+
print(changedColumns)
124+
125+
let age = Expression<Int?>("age")
126+
let electronicMail = Expression<String>("electronic_mail")
127+
128+
let newRowid = try db.run(users.insert(
129+
electronicMail <- "[email protected]",
130+
age <- 33
131+
))

SQLite.xcodeproj/project.pbxproj

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,18 @@
199199
997DF2AF287FC06D00F8DF95 /* Query+with.swift in Sources */ = {isa = PBXBuildFile; fileRef = 997DF2AD287FC06D00F8DF95 /* Query+with.swift */; };
200200
997DF2B0287FC06D00F8DF95 /* Query+with.swift in Sources */ = {isa = PBXBuildFile; fileRef = 997DF2AD287FC06D00F8DF95 /* Query+with.swift */; };
201201
997DF2B1287FC06D00F8DF95 /* Query+with.swift in Sources */ = {isa = PBXBuildFile; fileRef = 997DF2AD287FC06D00F8DF95 /* Query+with.swift */; };
202+
DB58B21128FB864300F8EEA4 /* SchemaReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB58B21028FB864300F8EEA4 /* SchemaReader.swift */; };
203+
DB58B21228FB864300F8EEA4 /* SchemaReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB58B21028FB864300F8EEA4 /* SchemaReader.swift */; };
204+
DB58B21328FB864300F8EEA4 /* SchemaReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB58B21028FB864300F8EEA4 /* SchemaReader.swift */; };
205+
DB58B21428FB864300F8EEA4 /* SchemaReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB58B21028FB864300F8EEA4 /* SchemaReader.swift */; };
206+
DB58B21628FC7C4600F8EEA4 /* SQLiteFeature.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB58B21528FC7C4600F8EEA4 /* SQLiteFeature.swift */; };
207+
DB58B21728FC7C4600F8EEA4 /* SQLiteFeature.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB58B21528FC7C4600F8EEA4 /* SQLiteFeature.swift */; };
208+
DB58B21828FC7C4600F8EEA4 /* SQLiteFeature.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB58B21528FC7C4600F8EEA4 /* SQLiteFeature.swift */; };
209+
DB58B21928FC7C4600F8EEA4 /* SQLiteFeature.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB58B21528FC7C4600F8EEA4 /* SQLiteFeature.swift */; };
210+
DB7C5DA628D7C9B6006395CF /* SQLiteVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB7C5DA528D7C9B6006395CF /* SQLiteVersion.swift */; };
211+
DB7C5DA728D7C9B6006395CF /* SQLiteVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB7C5DA528D7C9B6006395CF /* SQLiteVersion.swift */; };
212+
DB7C5DA828D7C9B6006395CF /* SQLiteVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB7C5DA528D7C9B6006395CF /* SQLiteVersion.swift */; };
213+
DB7C5DA928D7C9B6006395CF /* SQLiteVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB7C5DA528D7C9B6006395CF /* SQLiteVersion.swift */; };
202214
EE247AD71C3F04ED00AE3E12 /* SQLite.h in Headers */ = {isa = PBXBuildFile; fileRef = EE247AD61C3F04ED00AE3E12 /* SQLite.h */; settings = {ATTRIBUTES = (Public, ); }; };
203215
EE247ADE1C3F04ED00AE3E12 /* SQLite.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EE247AD31C3F04ED00AE3E12 /* SQLite.framework */; };
204216
EE247B031C3F06E900AE3E12 /* Blob.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AEE1C3F06E900AE3E12 /* Blob.swift */; };
@@ -325,6 +337,9 @@
325337
49EB68C31F7B3CB400D89D40 /* Coding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Coding.swift; sourceTree = "<group>"; };
326338
997DF2AD287FC06D00F8DF95 /* Query+with.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Query+with.swift"; sourceTree = "<group>"; };
327339
A121AC451CA35C79005A31D1 /* SQLite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SQLite.framework; sourceTree = BUILT_PRODUCTS_DIR; };
340+
DB58B21028FB864300F8EEA4 /* SchemaReader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SchemaReader.swift; sourceTree = "<group>"; };
341+
DB58B21528FC7C4600F8EEA4 /* SQLiteFeature.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SQLiteFeature.swift; sourceTree = "<group>"; };
342+
DB7C5DA528D7C9B6006395CF /* SQLiteVersion.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SQLiteVersion.swift; sourceTree = "<group>"; };
328343
EE247AD31C3F04ED00AE3E12 /* SQLite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SQLite.framework; sourceTree = BUILT_PRODUCTS_DIR; };
329344
EE247AD61C3F04ED00AE3E12 /* SQLite.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SQLite.h; sourceTree = "<group>"; };
330345
EE247AD81C3F04ED00AE3E12 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
@@ -424,6 +439,7 @@
424439
19A1792D261C689FC988A90A /* Schema */ = {
425440
isa = PBXGroup;
426441
children = (
442+
DB58B21028FB864300F8EEA4 /* SchemaReader.swift */,
427443
19A171B262DDE8718513CFDA /* SchemaChanger.swift */,
428444
19A17268AE67B746B96AC125 /* SchemaDefinitions.swift */,
429445
19A170A97B51DC5EE365F3C5 /* Connection+Schema.swift */,
@@ -569,6 +585,8 @@
569585
19A175A9CB446640AE6F2200 /* Connection+Aggregation.swift */,
570586
3DF7B78728842972005DD8CA /* Connection+Attach.swift */,
571587
3DF7B790288449BA005DD8CA /* URIQueryParameter.swift */,
588+
DB58B21528FC7C4600F8EEA4 /* SQLiteFeature.swift */,
589+
DB7C5DA528D7C9B6006395CF /* SQLiteVersion.swift */,
572590
19A17F285B767BFACD96714B /* Connection+Pragmas.swift */,
573591
);
574592
path = Core;
@@ -954,13 +972,16 @@
954972
02A43A9A22738CF100FEC494 /* Backup.swift in Sources */,
955973
19A17FF4A10B44D3937C8CAC /* Errors.swift in Sources */,
956974
19A1737286A74F3CF7412906 /* DateAndTimeFunctions.swift in Sources */,
975+
DB7C5DA828D7C9B6006395CF /* SQLiteVersion.swift in Sources */,
957976
19A17073552293CA063BEA66 /* Result.swift in Sources */,
958977
997DF2B0287FC06D00F8DF95 /* Query+with.swift in Sources */,
959978
19A179B59450FE7C4811AB8A /* Connection+Aggregation.swift in Sources */,
979+
DB58B21828FC7C4600F8EEA4 /* SQLiteFeature.swift in Sources */,
960980
19A17FC07731779C1B8506FA /* SchemaChanger.swift in Sources */,
961981
19A1740EACD47904AA24B8DC /* SchemaDefinitions.swift in Sources */,
962982
19A1750EF4A5F92954A451FF /* Connection+Schema.swift in Sources */,
963983
19A17986405D9A875698408F /* Connection+Pragmas.swift in Sources */,
984+
DB58B21328FB864300F8EEA4 /* SchemaReader.swift in Sources */,
964985
);
965986
runOnlyForDeploymentPostprocessing = 0;
966987
};
@@ -1011,9 +1032,12 @@
10111032
997DF2B1287FC06D00F8DF95 /* Query+with.swift in Sources */,
10121033
3D67B3F71DB246D700A4F4C6 /* Foundation.swift in Sources */,
10131034
3D67B3F81DB246D700A4F4C6 /* Helpers.swift in Sources */,
1035+
DB58B21428FB864300F8EEA4 /* SchemaReader.swift in Sources */,
10141036
3D67B3E91DB246D100A4F4C6 /* Statement.swift in Sources */,
1037+
DB7C5DA928D7C9B6006395CF /* SQLiteVersion.swift in Sources */,
10151038
3D67B3EA1DB246D100A4F4C6 /* Value.swift in Sources */,
10161039
3D67B3EB1DB246D100A4F4C6 /* FTS4.swift in Sources */,
1040+
DB58B21928FC7C4600F8EEA4 /* SQLiteFeature.swift in Sources */,
10171041
3D67B3EC1DB246D100A4F4C6 /* RTree.swift in Sources */,
10181042
3D67B3ED1DB246D100A4F4C6 /* FTS5.swift in Sources */,
10191043
3D67B3EE1DB246D100A4F4C6 /* AggregateFunctions.swift in Sources */,
@@ -1069,13 +1093,16 @@
10691093
02A43A9822738CF100FEC494 /* Backup.swift in Sources */,
10701094
19A1792C0520D4E83C2EB075 /* Errors.swift in Sources */,
10711095
19A17E29278A12BC4F542506 /* DateAndTimeFunctions.swift in Sources */,
1096+
DB7C5DA628D7C9B6006395CF /* SQLiteVersion.swift in Sources */,
10721097
19A173EFEF0B3BD0B3ED406C /* Result.swift in Sources */,
10731098
997DF2AE287FC06D00F8DF95 /* Query+with.swift in Sources */,
10741099
19A176376CB6A94759F7980A /* Connection+Aggregation.swift in Sources */,
1100+
DB58B21628FC7C4600F8EEA4 /* SQLiteFeature.swift in Sources */,
10751101
19A1773A335CAB9D0AE14E8E /* SchemaChanger.swift in Sources */,
10761102
19A17BA13FD35F058787B7D3 /* SchemaDefinitions.swift in Sources */,
10771103
19A174506543905D71BF0518 /* Connection+Schema.swift in Sources */,
10781104
19A17018F250343BD0F9F4B0 /* Connection+Pragmas.swift in Sources */,
1105+
DB58B21128FB864300F8EEA4 /* SchemaReader.swift in Sources */,
10791106
);
10801107
runOnlyForDeploymentPostprocessing = 0;
10811108
};
@@ -1146,13 +1173,16 @@
11461173
02A43A9922738CF100FEC494 /* Backup.swift in Sources */,
11471174
19A17490543609FCED53CACC /* Errors.swift in Sources */,
11481175
19A17152E32A9585831E3FE0 /* DateAndTimeFunctions.swift in Sources */,
1176+
DB7C5DA728D7C9B6006395CF /* SQLiteVersion.swift in Sources */,
11491177
19A17F1B3F0A3C96B5ED6D64 /* Result.swift in Sources */,
11501178
997DF2AF287FC06D00F8DF95 /* Query+with.swift in Sources */,
11511179
19A170ACC97B19730FB7BA4D /* Connection+Aggregation.swift in Sources */,
1180+
DB58B21728FC7C4600F8EEA4 /* SQLiteFeature.swift in Sources */,
11521181
19A177290558991BCC60E4E3 /* SchemaChanger.swift in Sources */,
11531182
19A17B0DF1DDB6BBC9C95D64 /* SchemaDefinitions.swift in Sources */,
11541183
19A17F0BF02896E1664F4090 /* Connection+Schema.swift in Sources */,
11551184
19A1760CE25615CA015E2E5F /* Connection+Pragmas.swift in Sources */,
1185+
DB58B21228FB864300F8EEA4 /* SchemaReader.swift in Sources */,
11561186
);
11571187
runOnlyForDeploymentPostprocessing = 0;
11581188
};

Sources/SQLite/Core/Connection+Pragmas.swift

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import Foundation
22

33
public typealias UserVersion = Int32
4-
public typealias SQLiteVersion = (Int, Int, Int)
54

65
public extension Connection {
76
/// The user version of the database.
@@ -21,9 +20,9 @@ public extension Connection {
2120
guard let version = (try? scalar("SELECT sqlite_version()")) as? String,
2221
let splits = .some(version.split(separator: ".", maxSplits: 3)), splits.count == 3,
2322
let major = Int(splits[0]), let minor = Int(splits[1]), let point = Int(splits[2]) else {
24-
return (0, 0, 0)
23+
return .zero
2524
}
26-
return (major, minor, point)
25+
return .init(major: major, minor: minor, point: point)
2726
}
2827

2928
// Changing the foreign_keys setting affects the execution of all statements prepared using the database
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import Foundation
2+
3+
enum SQLiteFeature {
4+
case partialIntegrityCheck // PRAGMA integrity_check(table)
5+
case sqliteSchemaTable // sqlite_master => sqlite_schema
6+
case renameColumn // ALTER TABLE ... RENAME COLUMN
7+
case dropColumn // ALTER TABLE ... DROP COLUMN
8+
9+
func isSupported(by version: SQLiteVersion) -> Bool {
10+
switch self {
11+
case .partialIntegrityCheck, .sqliteSchemaTable:
12+
return version >= .init(major: 3, minor: 33)
13+
case .renameColumn:
14+
return version >= .init(major: 3, minor: 25)
15+
case .dropColumn:
16+
return version >= .init(major: 3, minor: 35)
17+
}
18+
}
19+
}
20+
21+
extension Connection {
22+
func supports(_ feature: SQLiteFeature) -> Bool {
23+
feature.isSupported(by: sqliteVersion)
24+
}
25+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import Foundation
2+
3+
public struct SQLiteVersion: Comparable, CustomStringConvertible {
4+
public let major: Int
5+
public let minor: Int
6+
public var point: Int = 0
7+
8+
public var description: String {
9+
"SQLite \(major).\(minor).\(point)"
10+
}
11+
12+
public static func <(lhs: SQLiteVersion, rhs: SQLiteVersion) -> Bool {
13+
lhs.tuple < rhs.tuple
14+
}
15+
16+
public static func ==(lhs: SQLiteVersion, rhs: SQLiteVersion) -> Bool {
17+
lhs.tuple == rhs.tuple
18+
}
19+
20+
static var zero: SQLiteVersion = .init(major: 0, minor: 0)
21+
private var tuple: (Int, Int, Int) { (major, minor, point) }
22+
}

0 commit comments

Comments
 (0)