Skip to content
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

Handle JSON better #107

Merged
merged 9 commits into from
Jan 18, 2024
Merged
Show file tree
Hide file tree
Changes from 5 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
4 changes: 2 additions & 2 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ let package = Package(
.library(name: "SQLiteKit", targets: ["SQLiteKit"]),
],
dependencies: [
.package(url: "https://github.com/vapor/sqlite-nio.git", from: "1.6.0"),
.package(url: "https://github.com/vapor/sqlite-nio.git", from: "1.8.4"),
.package(url: "https://github.com/vapor/sql-kit.git", from: "3.28.0"),
.package(url: "https://github.com/vapor/async-kit.git", from: "1.14.0"),
.package(url: "https://github.com/vapor/async-kit.git", from: "1.19.0"),
],
targets: [
.target(name: "SQLiteKit", dependencies: [
Expand Down
44 changes: 44 additions & 0 deletions [email protected]
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// swift-tools-version:5.9
import PackageDescription

let swiftSettings: [SwiftSetting] = [
.enableUpcomingFeature("ExistentialAny"),
.enableExperimentalFeature("StrictConcurrency=complete"),
]

let package = Package(
name: "sqlite-kit",
platforms: [
.macOS(.v10_15),
.iOS(.v13),
.watchOS(.v6),
.tvOS(.v13),
],
products: [
.library(name: "SQLiteKit", targets: ["SQLiteKit"]),
],
dependencies: [
.package(url: "https://github.com/vapor/sqlite-nio.git", from: "1.8.4"),
.package(url: "https://github.com/vapor/sql-kit.git", from: "3.28.0"),
.package(url: "https://github.com/vapor/async-kit.git", from: "1.19.0"),
],
targets: [
.target(
name: "SQLiteKit",
dependencies: [
.product(name: "AsyncKit", package: "async-kit"),
.product(name: "SQLiteNIO", package: "sqlite-nio"),
.product(name: "SQLKit", package: "sql-kit"),
],
swiftSettings: swiftSettings
),
.testTarget(
name: "SQLiteKitTests",
dependencies: [
.product(name: "SQLKitBenchmark", package: "sql-kit"),
.target(name: "SQLiteKit"),
],
swiftSettings: swiftSettings
),
]
)
24 changes: 14 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
<p align="center">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/1130717/268050157-c372aab8-fe28-4f08-a4bb-270673253a80.png">
<source media="(prefers-color-scheme: light)" srcset="https://user-images.githubusercontent.com/1130717/268050207-3275e0a5-9c59-40e0-b854-4ee0af3ebc5c.png">
<img src="https://user-images.githubusercontent.com/1130717/268050207-3275e0a5-9c59-40e0-b854-4ee0af3ebc5c.png" height="96" alt="SQLiteKit">
</picture>
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://github.com/vapor/sqlite-kit/assets/1130717/2d99c0b9-35e5-4f04-bb6c-8d59ff6e78c6">
<source media="(prefers-color-scheme: light)" srcset="https://github.com/vapor/sqlite-kit/assets/1130717/cb36f94d-3ac3-4fba-b801-fe19eac7dbc2">
<img src="https://github.com/vapor/sqlite-kit/assets/1130717/cb36f94d-3ac3-4fba-b801-fe19eac7dbc2" height="96" alt="SQLiteKit">
</picture>
<br>
<br>
<a name=""><img src="https://img.shields.io/badge/sswg-incubating-green.svg" alt="SSWG Incubation"></a>
<a href="https://api.vapor.codes/sqlitekit/documentation/sqlitekit/"><img src="https://img.shields.io/badge/read_the-docs-2196f3.svg" alt="Documentation"></a>
<a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-brightgreen.svg" alt="MIT License"></a>
<a href="https://github.com/vapor/sqlite-kit/actions/workflows/test.yml"><img src="https://github.com/vapor/sqlite-kit/actions/workflows/test.yml/badge.svg" alt="Continuous Integration"></a>
<a href="https://swift.org"><img src="https://img.shields.io/badge/swift-5.7-brightgreen.svg" alt="Swift 5.7"></a>
<a href="https://docs.vapor.codes/4.0/"><img src="https://design.vapor.codes/images/readthedocs.svg" alt="Documentation"></a>
<a href="https://discord.gg/vapor"><img src="https://design.vapor.codes/images/discordchat.svg" alt="Team Chat"></a>
<a href="LICENSE"><img src="https://design.vapor.codes/images/mitlicense.svg" alt="MIT License"></a>
<a href="https://github.com/vapor/sqlite-kit/actions/workflows/test.yml"><img src="https://img.shields.io/github/actions/workflow/status/vapor/sqlite-kit/test.yml?event=push&style=plastic&logo=github&label=test&logoColor=%23ccc" alt="Continuous Integration"></a>
<a href="https://codecov.io/github/vapor/sqlite-kit"><img src="https://img.shields.io/codecov/c/github/vapor/sqlite-kit?style=plastic&logo=codecov&label=Codecov"></a>
<a href="https://swift.org"><img src="https://design.vapor.codes/images/swift57up.svg" alt="Swift 5.7+"></a>
<a href="https://www.swift.org/sswg/incubation-process.html"><img src="https://design.vapor.codes/images/sswg-graduated-white.svg" alt="SSWG Incubation Level: Graduated"></a>
</p>

<br>
1 change: 0 additions & 1 deletion Sources/SQLiteKit/Docs.docc/images/article.svg

This file was deleted.

58 changes: 0 additions & 58 deletions Sources/SQLiteKit/Docs.docc/images/vapor-sqlite-logo.svg

This file was deleted.

22 changes: 22 additions & 0 deletions Sources/SQLiteKit/Docs.docc/images/vapor-sqlitekit-logo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
61 changes: 18 additions & 43 deletions Sources/SQLiteKit/Docs.docc/theme-settings.json
Original file line number Diff line number Diff line change
@@ -1,46 +1,21 @@
{
"theme": {
"aside": {
"border-radius": "6px",
"border-style": "double",
"border-width": "3px"
},
"border-radius": "0",
"button": {
"border-radius": "16px",
"border-width": "1px",
"border-style": "solid"
},
"code": {
"border-radius": "16px",
"border-width": "1px",
"border-style": "solid"
},
"color": {
"fill": {
"dark": "rgb(0, 0, 0)",
"light": "rgb(255, 255, 255)"
},
"sqlite-teal": "hsl(215, 45%, 58%)",
"documentation-intro-fill": "radial-gradient(circle at top, var(--color-documentation-intro-accent) 30%, #000 100%)",
"documentation-intro-accent": "var(--color-sqlite-teal)",
"documentation-intro-accent-outer": {
"dark": "rgb(255, 255, 255)",
"light": "rgb(0, 0, 0)"
},
"documentation-intro-accent-inner": {
"dark": "rgb(0, 0, 0)",
"light": "rgb(255, 255, 255)"
}
},
"icons": {
"technology": "/sqlitekit/images/vapor-sqlite-logo.svg",
"article": "/sqlitekit/images/article.svg"
}
"theme": {
"aside": { "border-radius": "6px", "border-style": "double", "border-width": "3px" },
"border-radius": "0",
"button": { "border-radius": "16px", "border-width": "1px", "border-style": "solid" },
"code": { "border-radius": "16px", "border-width": "1px", "border-style": "solid" },
"color": {
"sqlite": "hsl(215, 45%, 58%)",
"documentation-intro-fill": "radial-gradient(circle at top, var(--color-sqlite) 30%, #000 100%)",
"documentation-intro-accent": "var(--color-sqlite)",
"logo-base": { "dark": "#fff", "light": "#000" },
"logo-shape": { "dark": "#000", "light": "#fff" },
"fill": { "dark": "#000", "light": "#fff" }
},
"features": {
"quickNavigation": {
"enable": true
}
}
"icons": { "technology": "/sqlitekit/images/vapor-sqlitekit-logo.svg" }
},
"features": {
"quickNavigation": { "enable": true },
"i18n": { "enable": true }
}
}
2 changes: 1 addition & 1 deletion Sources/SQLiteKit/SQLiteConnection+SQLKit.swift
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ internal struct _SQLiteDatabaseVersion: SQLDatabaseReportedVersion {
}

private struct _SQLiteSQLDatabase: SQLDatabase {
let database: SQLiteDatabase
let database: any SQLiteDatabase
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI they're dropping the ExistentialAny upcoming feature. I personally liked it. Not sure what exactly are their plans.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Citation or it didn't happen 😜 What an absurd thing to do...


var eventLoop: any EventLoop {
self.database.eventLoop
Expand Down
35 changes: 15 additions & 20 deletions Sources/SQLiteKit/SQLiteDataDecoder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,21 @@ public struct SQLiteDataDecoder {
}
return value as! T
} else {
return try T.init(from: _Decoder(data: data))
do {
return try T.init(from: _Decoder(data: data))
} catch is SentinelError {
let fdata: Data
switch data {
case .blob(let buf): fdata = .init(buf.readableBytesView)
case .text(let str): fdata = .init(str.utf8)
default: fdata = .init()
}
return try JSONDecoder().decode(T.self, from: fdata)
}
}
}

private struct SentinelError: Swift.Error {}

private final class _Decoder: Decoder {
var codingPath: [any CodingKey] = []
Expand All @@ -26,21 +38,11 @@ public struct SQLiteDataDecoder {
init(data: SQLiteData) { self.data = data }

func unkeyedContainer() throws -> any UnkeyedDecodingContainer {
try self.jsonDecoder().unkeyedContainer()
throw SentinelError()
}

func container<Key: CodingKey>(keyedBy: Key.Type) throws -> KeyedDecodingContainer<Key> {
try self.jsonDecoder().container(keyedBy: Key.self)
}

func jsonDecoder() throws -> any Decoder {
let data: Data
switch self.data {
case .blob(let buffer): data = Data(buffer.readableBytesView)
case .text(let string): data = Data(string.utf8)
default: data = .init()
}
return try JSONDecoder().decode(DecoderUnwrapper.self, from: data).decoder
throw SentinelError()
}

func singleValueContainer() throws -> any SingleValueDecodingContainer { _SingleValueDecoder(self) }
Expand All @@ -58,10 +60,3 @@ public struct SQLiteDataDecoder {
}
}
}

private struct DecoderUnwrapper: Decodable {
let decoder: Decoder
init(from decoder: Decoder) {
self.decoder = decoder
}
}
23 changes: 9 additions & 14 deletions Sources/SQLiteKit/SQLiteDataEncoder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,15 @@ public struct SQLiteDataEncoder {
case .data(let data):
return data
case .unkeyed, .keyed:
let json = try JSONEncoder().encode(AnyEncodable(value))
var buffer = ByteBufferAllocator().buffer(capacity: json.count)
buffer.writeBytes(json)
return SQLiteData.blob(buffer)
// Starting with SQLite 3.45.0 (2024-01-15), sending textual JSON as a blob will cause inexplicable
// errors due to the data being interpreted as JSONB (arguably not the best behavior for SQLite's API,
// but not technically a compatibility break). As there is no good way to get at the underlying SQLite
// version from the data encoder, and extending `SQLiteData` would make a rather epic mess, we now just
// always send JSON as text instead. This is technically what we should have been doing all along
// anyway, meaning this change is a bugfix. Good thing, too - otherwise we'd be stuck trying to retain
// bug-for-bug compatibility, starting with reverse-engineering SQLite's JSONB format (which is not the
// same as PostgreSQL's, of course).
return SQLiteData.text(.init(decoding: try JSONEncoder().encode(value), as: UTF8.self))
}
}
}
Expand Down Expand Up @@ -83,13 +88,3 @@ public struct SQLiteDataEncoder {
}
}
}

private struct AnyEncodable: Encodable {
let encodable: Encodable
init(_ encodable: Encodable) {
self.encodable = encodable
}
func encode(to encoder: Encoder) throws {
try self.encodable.encode(to: encoder)
}
}
Loading