Skip to content

schemachanger-part-2 #1148

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
Oct 18, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .swiftlint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ disabled_rules: # rule identifiers to exclude from running
- operator_whitespace
- large_tuple
- closure_parameter_position
- inclusive_language # sqlite_master etc.
included: # paths to include during linting. `--path` is ignored if present. takes precendence over `excluded`.
- Sources
- Tests
Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
0.14.0 (tbd), [diff][diff-0.14.0]
========================================

* Support more complex schema changes and queries ([#1073][], [#1146][] [#1148][])
* Support `ATTACH`/`DETACH` ([#30][], [#1142][])
* Support `WITH` clause ([#1139][])
* Add `Value` conformance for `NSURL` ([#1110][], [#1141][])
Expand Down Expand Up @@ -160,6 +161,7 @@
[#866]: https://github.com/stephencelis/SQLite.swift/pull/866
[#881]: https://github.com/stephencelis/SQLite.swift/pull/881
[#919]: https://github.com/stephencelis/SQLite.swift/pull/919
[#1073]: https://github.com/stephencelis/SQLite.swift/issues/1073
[#1075]: https://github.com/stephencelis/SQLite.swift/pull/1075
[#1077]: https://github.com/stephencelis/SQLite.swift/issues/1077
[#1094]: https://github.com/stephencelis/SQLite.swift/pull/1094
Expand All @@ -185,3 +187,5 @@
[#1141]: https://github.com/stephencelis/SQLite.swift/pull/1141
[#1142]: https://github.com/stephencelis/SQLite.swift/pull/1142
[#1144]: https://github.com/stephencelis/SQLite.swift/pull/1144
[#1146]: https://github.com/stephencelis/SQLite.swift/pull/1146
[#1148]: https://github.com/stephencelis/SQLite.swift/pull/1148
82 changes: 78 additions & 4 deletions Documentation/Index.md
Original file line number Diff line number Diff line change
Expand Up @@ -1371,6 +1371,37 @@ try db.transaction {

> _Note:_ Transactions run in a serial queue.

## Querying the Schema

We can obtain generic information about objects in the current schema with a `SchemaReader`:

```swift
let schema = db.schema
```

To query the data:

```swift
let indexes = try schema.objectDefinitions(type: .index)
let tables = try schema.objectDefinitions(type: .table)
let triggers = try schema.objectDefinitions(type: .trigger)
```

### Indexes and Columns

Specialized methods are available to get more detailed information:

```swift
let indexes = try schema.indexDefinitions("users")
let columns = try schema.columnDefinitions("users")

for index in indexes {
print("\(index.name) columns:\(index.columns))")
}
for column in columns {
print("\(column.name) pk:\(column.primaryKey) nullable: \(column.nullable)")
}
```

## Altering the Schema

Expand Down Expand Up @@ -1454,11 +1485,56 @@ tables](#creating-a-table).

### Renaming Columns

Added in SQLite 3.25.0, not exposed yet. [#1073](https://github.com/stephencelis/SQLite.swift/issues/1073)
We can rename columns with the help of the `SchemaChanger` class:

```swift
let schemaChanger = SchemaChanger(connection: db)
try schemaChanger.alter(table: "users") { table in
table.rename(column: "old_name", to: "new_name")
}
```

### Dropping Columns

Added in SQLite 3.35.0, not exposed yet. [#1073](https://github.com/stephencelis/SQLite.swift/issues/1073)
```swift
let schemaChanger = SchemaChanger(connection: db)
try schemaChanger.alter(table: "users") { table in
table.drop(column: "email")
}
```

These operations will work with all versions of SQLite and use modern SQL
operations such as `DROP COLUMN` when available.

### Adding Columns (SchemaChanger)

The `SchemaChanger` provides an alternative API to add new columns:

```swift
let newColumn = ColumnDefinition(
name: "new_text_column",
type: .TEXT,
nullable: true,
defaultValue: .stringLiteral("foo")
)

let schemaChanger = SchemaChanger(connection: db)

try schemaChanger.alter(table: "users") { table in
table.add(newColumn)
}
```

### Renaming/dropping Tables (SchemaChanger)

The `SchemaChanger` provides an alternative API to rename and drop tables:

```swift
let schemaChanger = SchemaChanger(connection: db)

try schemaChanger.rename(table: "users", to: "users_new")
try schemaChanger.drop(table: "emails")
```

### Indexes

Expand Down Expand Up @@ -1515,7 +1591,6 @@ try db.run(users.dropIndex(email, ifExists: true))
// DROP INDEX IF EXISTS "index_users_on_email"
```


### Dropping Tables

We can build
Expand All @@ -1535,7 +1610,6 @@ try db.run(users.drop(ifExists: true))
// DROP TABLE IF EXISTS "users"
```


### Migrations and Schema Versioning

You can use the convenience property on `Connection` to query and set the
Expand Down
2 changes: 2 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ build:

lint:
swiftlint --strict
lint-fix:
swiftlint lint fix

test:
ifdef XCPRETTY
Expand Down
30 changes: 29 additions & 1 deletion SQLite.playground/Contents.swift
Original file line number Diff line number Diff line change
Expand Up @@ -99,5 +99,33 @@ db.createAggregation("customConcat",
initialValue: "users:",
reduce: reduce,
result: { $0 })
let result = db.prepare("SELECT customConcat(email) FROM users").scalar() as! String
let result = try db.prepare("SELECT customConcat(email) FROM users").scalar() as! String
print(result)

/// schema queries
let schema = db.schema
let objects = try schema.objectDefinitions()
print(objects)

let columns = try schema.columnDefinitions(table: "users")
print(columns)

/// schema alteration

let schemaChanger = SchemaChanger(connection: db)
try schemaChanger.alter(table: "users") { table in
table.add(ColumnDefinition(name: "age", type: .INTEGER))
table.rename(column: "email", to: "electronic_mail")
table.drop(column: "name")
}

let changedColumns = try schema.columnDefinitions(table: "users")
print(changedColumns)

let age = Expression<Int?>("age")
let electronicMail = Expression<String>("electronic_mail")

let newRowid = try db.run(users.insert(
electronicMail <- "[email protected]",
age <- 33
))
30 changes: 30 additions & 0 deletions SQLite.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,18 @@
997DF2AF287FC06D00F8DF95 /* Query+with.swift in Sources */ = {isa = PBXBuildFile; fileRef = 997DF2AD287FC06D00F8DF95 /* Query+with.swift */; };
997DF2B0287FC06D00F8DF95 /* Query+with.swift in Sources */ = {isa = PBXBuildFile; fileRef = 997DF2AD287FC06D00F8DF95 /* Query+with.swift */; };
997DF2B1287FC06D00F8DF95 /* Query+with.swift in Sources */ = {isa = PBXBuildFile; fileRef = 997DF2AD287FC06D00F8DF95 /* Query+with.swift */; };
DB58B21128FB864300F8EEA4 /* SchemaReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB58B21028FB864300F8EEA4 /* SchemaReader.swift */; };
DB58B21228FB864300F8EEA4 /* SchemaReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB58B21028FB864300F8EEA4 /* SchemaReader.swift */; };
DB58B21328FB864300F8EEA4 /* SchemaReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB58B21028FB864300F8EEA4 /* SchemaReader.swift */; };
DB58B21428FB864300F8EEA4 /* SchemaReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB58B21028FB864300F8EEA4 /* SchemaReader.swift */; };
DB58B21628FC7C4600F8EEA4 /* SQLiteFeature.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB58B21528FC7C4600F8EEA4 /* SQLiteFeature.swift */; };
DB58B21728FC7C4600F8EEA4 /* SQLiteFeature.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB58B21528FC7C4600F8EEA4 /* SQLiteFeature.swift */; };
DB58B21828FC7C4600F8EEA4 /* SQLiteFeature.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB58B21528FC7C4600F8EEA4 /* SQLiteFeature.swift */; };
DB58B21928FC7C4600F8EEA4 /* SQLiteFeature.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB58B21528FC7C4600F8EEA4 /* SQLiteFeature.swift */; };
DB7C5DA628D7C9B6006395CF /* SQLiteVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB7C5DA528D7C9B6006395CF /* SQLiteVersion.swift */; };
DB7C5DA728D7C9B6006395CF /* SQLiteVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB7C5DA528D7C9B6006395CF /* SQLiteVersion.swift */; };
DB7C5DA828D7C9B6006395CF /* SQLiteVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB7C5DA528D7C9B6006395CF /* SQLiteVersion.swift */; };
DB7C5DA928D7C9B6006395CF /* SQLiteVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB7C5DA528D7C9B6006395CF /* SQLiteVersion.swift */; };
EE247AD71C3F04ED00AE3E12 /* SQLite.h in Headers */ = {isa = PBXBuildFile; fileRef = EE247AD61C3F04ED00AE3E12 /* SQLite.h */; settings = {ATTRIBUTES = (Public, ); }; };
EE247ADE1C3F04ED00AE3E12 /* SQLite.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EE247AD31C3F04ED00AE3E12 /* SQLite.framework */; };
EE247B031C3F06E900AE3E12 /* Blob.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AEE1C3F06E900AE3E12 /* Blob.swift */; };
Expand Down Expand Up @@ -325,6 +337,9 @@
49EB68C31F7B3CB400D89D40 /* Coding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Coding.swift; sourceTree = "<group>"; };
997DF2AD287FC06D00F8DF95 /* Query+with.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Query+with.swift"; sourceTree = "<group>"; };
A121AC451CA35C79005A31D1 /* SQLite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SQLite.framework; sourceTree = BUILT_PRODUCTS_DIR; };
DB58B21028FB864300F8EEA4 /* SchemaReader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SchemaReader.swift; sourceTree = "<group>"; };
DB58B21528FC7C4600F8EEA4 /* SQLiteFeature.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SQLiteFeature.swift; sourceTree = "<group>"; };
DB7C5DA528D7C9B6006395CF /* SQLiteVersion.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SQLiteVersion.swift; sourceTree = "<group>"; };
EE247AD31C3F04ED00AE3E12 /* SQLite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SQLite.framework; sourceTree = BUILT_PRODUCTS_DIR; };
EE247AD61C3F04ED00AE3E12 /* SQLite.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SQLite.h; sourceTree = "<group>"; };
EE247AD81C3F04ED00AE3E12 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
Expand Down Expand Up @@ -424,6 +439,7 @@
19A1792D261C689FC988A90A /* Schema */ = {
isa = PBXGroup;
children = (
DB58B21028FB864300F8EEA4 /* SchemaReader.swift */,
19A171B262DDE8718513CFDA /* SchemaChanger.swift */,
19A17268AE67B746B96AC125 /* SchemaDefinitions.swift */,
19A170A97B51DC5EE365F3C5 /* Connection+Schema.swift */,
Expand Down Expand Up @@ -569,6 +585,8 @@
19A175A9CB446640AE6F2200 /* Connection+Aggregation.swift */,
3DF7B78728842972005DD8CA /* Connection+Attach.swift */,
3DF7B790288449BA005DD8CA /* URIQueryParameter.swift */,
DB58B21528FC7C4600F8EEA4 /* SQLiteFeature.swift */,
DB7C5DA528D7C9B6006395CF /* SQLiteVersion.swift */,
19A17F285B767BFACD96714B /* Connection+Pragmas.swift */,
);
path = Core;
Expand Down Expand Up @@ -954,13 +972,16 @@
02A43A9A22738CF100FEC494 /* Backup.swift in Sources */,
19A17FF4A10B44D3937C8CAC /* Errors.swift in Sources */,
19A1737286A74F3CF7412906 /* DateAndTimeFunctions.swift in Sources */,
DB7C5DA828D7C9B6006395CF /* SQLiteVersion.swift in Sources */,
19A17073552293CA063BEA66 /* Result.swift in Sources */,
997DF2B0287FC06D00F8DF95 /* Query+with.swift in Sources */,
19A179B59450FE7C4811AB8A /* Connection+Aggregation.swift in Sources */,
DB58B21828FC7C4600F8EEA4 /* SQLiteFeature.swift in Sources */,
19A17FC07731779C1B8506FA /* SchemaChanger.swift in Sources */,
19A1740EACD47904AA24B8DC /* SchemaDefinitions.swift in Sources */,
19A1750EF4A5F92954A451FF /* Connection+Schema.swift in Sources */,
19A17986405D9A875698408F /* Connection+Pragmas.swift in Sources */,
DB58B21328FB864300F8EEA4 /* SchemaReader.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down Expand Up @@ -1011,9 +1032,12 @@
997DF2B1287FC06D00F8DF95 /* Query+with.swift in Sources */,
3D67B3F71DB246D700A4F4C6 /* Foundation.swift in Sources */,
3D67B3F81DB246D700A4F4C6 /* Helpers.swift in Sources */,
DB58B21428FB864300F8EEA4 /* SchemaReader.swift in Sources */,
3D67B3E91DB246D100A4F4C6 /* Statement.swift in Sources */,
DB7C5DA928D7C9B6006395CF /* SQLiteVersion.swift in Sources */,
3D67B3EA1DB246D100A4F4C6 /* Value.swift in Sources */,
3D67B3EB1DB246D100A4F4C6 /* FTS4.swift in Sources */,
DB58B21928FC7C4600F8EEA4 /* SQLiteFeature.swift in Sources */,
3D67B3EC1DB246D100A4F4C6 /* RTree.swift in Sources */,
3D67B3ED1DB246D100A4F4C6 /* FTS5.swift in Sources */,
3D67B3EE1DB246D100A4F4C6 /* AggregateFunctions.swift in Sources */,
Expand Down Expand Up @@ -1069,13 +1093,16 @@
02A43A9822738CF100FEC494 /* Backup.swift in Sources */,
19A1792C0520D4E83C2EB075 /* Errors.swift in Sources */,
19A17E29278A12BC4F542506 /* DateAndTimeFunctions.swift in Sources */,
DB7C5DA628D7C9B6006395CF /* SQLiteVersion.swift in Sources */,
19A173EFEF0B3BD0B3ED406C /* Result.swift in Sources */,
997DF2AE287FC06D00F8DF95 /* Query+with.swift in Sources */,
19A176376CB6A94759F7980A /* Connection+Aggregation.swift in Sources */,
DB58B21628FC7C4600F8EEA4 /* SQLiteFeature.swift in Sources */,
19A1773A335CAB9D0AE14E8E /* SchemaChanger.swift in Sources */,
19A17BA13FD35F058787B7D3 /* SchemaDefinitions.swift in Sources */,
19A174506543905D71BF0518 /* Connection+Schema.swift in Sources */,
19A17018F250343BD0F9F4B0 /* Connection+Pragmas.swift in Sources */,
DB58B21128FB864300F8EEA4 /* SchemaReader.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down Expand Up @@ -1146,13 +1173,16 @@
02A43A9922738CF100FEC494 /* Backup.swift in Sources */,
19A17490543609FCED53CACC /* Errors.swift in Sources */,
19A17152E32A9585831E3FE0 /* DateAndTimeFunctions.swift in Sources */,
DB7C5DA728D7C9B6006395CF /* SQLiteVersion.swift in Sources */,
19A17F1B3F0A3C96B5ED6D64 /* Result.swift in Sources */,
997DF2AF287FC06D00F8DF95 /* Query+with.swift in Sources */,
19A170ACC97B19730FB7BA4D /* Connection+Aggregation.swift in Sources */,
DB58B21728FC7C4600F8EEA4 /* SQLiteFeature.swift in Sources */,
19A177290558991BCC60E4E3 /* SchemaChanger.swift in Sources */,
19A17B0DF1DDB6BBC9C95D64 /* SchemaDefinitions.swift in Sources */,
19A17F0BF02896E1664F4090 /* Connection+Schema.swift in Sources */,
19A1760CE25615CA015E2E5F /* Connection+Pragmas.swift in Sources */,
DB58B21228FB864300F8EEA4 /* SchemaReader.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down
5 changes: 2 additions & 3 deletions Sources/SQLite/Core/Connection+Pragmas.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import Foundation

public typealias UserVersion = Int32
public typealias SQLiteVersion = (Int, Int, Int)

public extension Connection {
/// The user version of the database.
Expand All @@ -21,9 +20,9 @@ public extension Connection {
guard let version = (try? scalar("SELECT sqlite_version()")) as? String,
let splits = .some(version.split(separator: ".", maxSplits: 3)), splits.count == 3,
let major = Int(splits[0]), let minor = Int(splits[1]), let point = Int(splits[2]) else {
return (0, 0, 0)
return .zero
}
return (major, minor, point)
return .init(major: major, minor: minor, point: point)
}

// Changing the foreign_keys setting affects the execution of all statements prepared using the database
Expand Down
25 changes: 25 additions & 0 deletions Sources/SQLite/Core/SQLiteFeature.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import Foundation

enum SQLiteFeature {
case partialIntegrityCheck // PRAGMA integrity_check(table)
case sqliteSchemaTable // sqlite_master => sqlite_schema
case renameColumn // ALTER TABLE ... RENAME COLUMN
case dropColumn // ALTER TABLE ... DROP COLUMN

func isSupported(by version: SQLiteVersion) -> Bool {
switch self {
case .partialIntegrityCheck, .sqliteSchemaTable:
return version >= .init(major: 3, minor: 33)
case .renameColumn:
return version >= .init(major: 3, minor: 25)
case .dropColumn:
return version >= .init(major: 3, minor: 35)
}
}
}

extension Connection {
func supports(_ feature: SQLiteFeature) -> Bool {
feature.isSupported(by: sqliteVersion)
}
}
22 changes: 22 additions & 0 deletions Sources/SQLite/Core/SQLiteVersion.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import Foundation

public struct SQLiteVersion: Comparable, CustomStringConvertible {
public let major: Int
public let minor: Int
public var point: Int = 0

public var description: String {
"SQLite \(major).\(minor).\(point)"
}

public static func <(lhs: SQLiteVersion, rhs: SQLiteVersion) -> Bool {
lhs.tuple < rhs.tuple
}

public static func ==(lhs: SQLiteVersion, rhs: SQLiteVersion) -> Bool {
lhs.tuple == rhs.tuple
}

static var zero: SQLiteVersion = .init(major: 0, minor: 0)
private var tuple: (Int, Int, Int) { (major, minor, point) }
}
Loading