Skip to content
Open
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
2 changes: 1 addition & 1 deletion Examples/CaseStudies/DynamicQuery.swift
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ struct DynamicQueryDemo: SwiftUICaseStudy {
func fetch(_ db: Database) throws -> Value {
let search =
Fact
.where { $0.body.contains(query) }
.where { $0.body.like("%\(query)%") }
.order { $0.id.desc() }
return try Value(
facts: search.fetchAll(db),
Expand Down
2 changes: 2 additions & 0 deletions Examples/Examples.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -1100,6 +1100,8 @@
isa = XCLocalSwiftPackageReference;
relativePath = ..;
traits = (
LazyInitializableByDefault,
StrictDecoding,
);
};
/* End XCLocalSwiftPackageReference section */
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Examples/Reminders/Schema.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ nonisolated struct Reminder: Hashable, Identifiable {
var notes = ""
var position = 0
var priority: Priority?
@Column(lazyInitializable: false)
var remindersListID: RemindersList.ID
var status: Status = .incomplete
var title = ""
Expand Down
2 changes: 1 addition & 1 deletion Examples/Reminders/SearchReminders.swift
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ class SearchRemindersModel {
let existingTags = searchTokens.compactMap { $0.kind == .tag ? $0.rawValue : nil }
try await $tags.load(
Tag
.where { $0.title.hasPrefix(searchText.dropFirst()) && !$0.title.in(existingTags) }
.where { $0.title.like("\(searchText.dropFirst())%") && !$0.title.in(existingTags) }
.order(by: \.title)
)
} else {
Expand Down
2 changes: 1 addition & 1 deletion Examples/SyncUps/Dependencies/SpeechClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ nonisolated struct SpeechClient: DependencyKey {
requestAuthorization: { .authorized },
startTask: {
AsyncThrowingStream { continuation in
Task { @MainActor in
_ = Task { @MainActor in
isRecording.setValue(true)
var finalText = """
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor \
Expand Down
2 changes: 1 addition & 1 deletion Examples/SyncUps/SyncUpDetail.swift
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ final class SyncUpDetailModel: HashableObject {
withErrorReporting {
try database.write { db in
let ids = indices.map { meetings[$0].id }
try Meeting.where { ids.contains($0.id) }.delete().execute(db)
try Meeting.where { $0.id.in(ids) }.delete().execute(db)
}
}
}
Expand Down
26 changes: 13 additions & 13 deletions Package.resolved

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 17 additions & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,17 @@ let package = Package(
),
],
traits: [
.trait(
name: "LazyInitializableByDefault",
description: "Optionalize draft properties that have no default."
),
.trait(
name: "StrictDecoding",
description: """
Throw an error, rather than coerce, when decoding a column whose storage type does not \
match the expected type.
"""
),
.trait(
name: "CasePaths",
description: "Introduce support for enum tables."
Expand All @@ -47,8 +58,13 @@ let package = Package(
.package(url: "https://github.com/pointfreeco/swift-snapshot-testing", from: "1.18.4"),
.package(
url: "https://github.com/pointfreeco/swift-structured-queries",
from: "0.32.0",
branch: "strict-decoding",
// from: "0.32.0",
traits: [
.trait(
name: "LazyInitializableByDefault",
condition: .when(traits: ["LazyInitializableByDefault"])
),
.trait(name: "CasePaths", condition: .when(traits: ["CasePaths"])),
.trait(name: "Tagged", condition: .when(traits: ["Tagged"])),
]
Expand Down
15 changes: 12 additions & 3 deletions Sources/SQLiteData/StructuredQueries+GRDB/CustomFunctions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,16 @@ extension Database {
Unmanaged.passRetained(ScalarDatabaseFunctionDefinition(function)).toOpaque(),
{ context, argumentCount, arguments in
do {
var decoder = SQLiteFunctionDecoder(argumentCount: argumentCount, arguments: arguments)
try Unmanaged<ScalarDatabaseFunctionDefinition>
let function = Unmanaged<ScalarDatabaseFunctionDefinition>
.fromOpaque(sqlite3_user_data(context))
.takeUnretainedValue()
.function
var decoder = SQLiteFunctionDecoder(
name: function.name,
argumentCount: argumentCount,
arguments: arguments
)
try function
.invoke(&decoder)
.result(db: context)
} catch {
Expand Down Expand Up @@ -53,8 +58,12 @@ extension Database {
body,
nil,
{ context, argumentCount, arguments in
var decoder = SQLiteFunctionDecoder(argumentCount: argumentCount, arguments: arguments)
let function = AggregateDatabaseFunctionContext[context].takeUnretainedValue()
var decoder = SQLiteFunctionDecoder(
name: function.iterator.body.name,
argumentCount: argumentCount,
arguments: arguments
)
do {
try function.iterator.step(&decoder)
} catch {
Expand Down
19 changes: 19 additions & 0 deletions Sources/SQLiteData/StructuredQueries+GRDB/Decoding.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import GRDBSQLite

#if !StrictDecoding
import ConcurrencyExtras

let reportedTypeMismatches = LockIsolated<Set<String>>([])
#endif

@usableFromInline
func storageClassName(_ type: Int32) -> String {
switch type {
case SQLITE_BLOB: "BLOB"
case SQLITE_FLOAT: "REAL"
case SQLITE_INTEGER: "INTEGER"
case SQLITE_TEXT: "TEXT"
case SQLITE_NULL: "NULL"
default: "unknown storage class \(type)"
}
}
49 changes: 35 additions & 14 deletions Sources/SQLiteData/StructuredQueries+GRDB/QueryCursor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,24 +30,51 @@ public class QueryCursor<Element>: DatabaseCursor {
struct DecodingError: Error, CustomStringConvertible {
let columnIndex: Int
let columnName: String
let reason: String
let sql: String

@usableFromInline
init(columnIndex: Int, columnName: String, sql: String) {
init(columnIndex: Int, columnName: String, reason: String, sql: String) {
self.columnIndex = columnIndex
self.columnName = columnName
self.reason = reason
self.sql = sql
}

@usableFromInline
var description: String {
"""
Expected column \(columnIndex) (\(columnName.debugDescription)) to not be NULL: …
Expected column \(columnIndex) (\(columnName.debugDescription)) \(reason): ...

\(sql)
"""
}
}

@usableFromInline
func missingRequiredColumnError() -> DecodingError {
let columnIndex = Int(decoder.currentIndex) - 1
return DecodingError(
columnIndex: columnIndex,
columnName: _statement.columnNames[columnIndex],
reason: "to not be NULL",
sql: _statement.sql
)
}

@usableFromInline
func typeMismatchError(_ columnType: Any.Type) -> DecodingError {
let columnIndex = Int(decoder.currentIndex)
let storageClass = storageClassName(
sqlite3_column_type(_statement.sqliteStatement, Int32(columnIndex))
)
return DecodingError(
columnIndex: columnIndex,
columnName: _statement.columnNames[columnIndex],
reason: "to decode \(columnType), but found \(storageClass)",
sql: _statement.sql
)
}
}

@usableFromInline
Expand All @@ -68,12 +95,9 @@ final class QueryValueCursor<QueryValue: QueryRepresentable>: QueryCursor<QueryV
decoder.next()
return element
} catch QueryDecodingError.missingRequiredColumn {
let columnIndex = Int(decoder.currentIndex) - 1
throw DecodingError(
columnIndex: columnIndex,
columnName: _statement.columnNames[columnIndex],
sql: _statement.sql
)
throw missingRequiredColumnError()
} catch QueryDecodingError.typeMismatch(let columnType) {
throw typeMismatchError(columnType)
}
}
}
Expand All @@ -99,12 +123,9 @@ final class QueryPackCursor<
decoder.next()
return element
} catch QueryDecodingError.missingRequiredColumn {
let columnIndex = Int(decoder.currentIndex) - 1
throw DecodingError(
columnIndex: columnIndex,
columnName: _statement.columnNames[columnIndex],
sql: _statement.sql
)
throw missingRequiredColumnError()
} catch QueryDecodingError.typeMismatch(let columnType) {
throw typeMismatchError(columnType)
}
}
}
Expand Down
Loading