Skip to content

Commit

Permalink
Raise a fatal error when the users filters a database view by primary…
Browse files Browse the repository at this point in the history
… key

Fixes #1648
  • Loading branch information
groue committed Oct 12, 2024
1 parent d6b50b2 commit 2c49a69
Show file tree
Hide file tree
Showing 3 changed files with 37 additions and 26 deletions.
30 changes: 30 additions & 0 deletions GRDB/Core/Database+Schema.swift
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,36 @@ extension Database {
throw DatabaseError.noSuchTable(tableName)
}

/// Returns the name of the single-column primary key.
///
/// A fatal error is raised if the primary key has several columns, or
/// if `tableName` is the name of a database view.
func filteringPrimaryKeyColumn(_ tableName: String) throws -> String {
do {
let primaryKey = try primaryKey(tableName)
GRDBPrecondition(
primaryKey.columns.count == 1,
"Filtering by primary key requires a single-column primary key in the table '\(tableName)'")
return primaryKey.columns[0]
} catch let error as DatabaseError {
// Maybe the user tries to filter a view by primary key,
// as in <https://github.com/groue/GRDB.swift/issues/1648>.
// In this case, raise a fatalError because this is a
// programmer error which is very likely to be detected
// during development.
if case .SQLITE_ERROR = error.resultCode,
(try? viewExists(tableName)) == true
{
fatalError("""
Filtering by primary key is not available on the database view '\(tableName)'. \
Use `filter(Column("...") == value)` instead.
""")
} else {
throw error
}
}
}

/// Returns nil if table does not exist
private func primaryKey(_ table: TableIdentifier) throws -> PrimaryKeyInfo? {
SchedulingWatchdog.preconditionValidQueue(self)
Expand Down
26 changes: 5 additions & 21 deletions GRDB/QueryInterface/Request/RequestProtocols.swift
Original file line number Diff line number Diff line change
Expand Up @@ -458,11 +458,7 @@ extension TableRequest where Self: FilteredRequest, Self: TypedRequest {
}

return filterWhenConnected(keys: { [databaseTableName] db in
let primaryKey = try db.primaryKey(databaseTableName)
GRDBPrecondition(
primaryKey.columns.count == 1,
"Requesting by key requires a single-column primary key in the table \(databaseTableName)")
let column = primaryKey.columns[0]
let column = try db.filteringPrimaryKeyColumn(databaseTableName)
let strategy = recordType.databaseDataEncodingStrategy(for: column)
let expressions = try datas.map { try strategy.encode($0).sqlExpression }
return expressions
Expand All @@ -475,11 +471,7 @@ extension TableRequest where Self: FilteredRequest, Self: TypedRequest {
}

return filterWhenConnected(keys: { [databaseTableName] db in
let primaryKey = try db.primaryKey(databaseTableName)
GRDBPrecondition(
primaryKey.columns.count == 1,
"Requesting by key requires a single-column primary key in the table \(databaseTableName)")
let column = primaryKey.columns[0]
let column = try db.filteringPrimaryKeyColumn(databaseTableName)
let strategy = recordType.databaseDateEncodingStrategy(for: column)
let expressions = dates.map { strategy.encode($0).sqlExpression }
return expressions
Expand All @@ -492,11 +484,7 @@ extension TableRequest where Self: FilteredRequest, Self: TypedRequest {
}

return filterWhenConnected(keys: { [databaseTableName] db in
let primaryKey = try db.primaryKey(databaseTableName)
GRDBPrecondition(
primaryKey.columns.count == 1,
"Requesting by key requires a single-column primary key in the table \(databaseTableName)")
let column = primaryKey.columns[0]
let column = try db.filteringPrimaryKeyColumn(databaseTableName)
let strategy = recordType.databaseUUIDEncodingStrategy(for: column)
let expressions = uuids.map { strategy.encode($0).sqlExpression }
return expressions
Expand Down Expand Up @@ -524,12 +512,8 @@ extension TableRequest where Self: FilteredRequest, Self: TypedRequest {
// Don't bother removing NULLs. We'd lose CPU cycles, and this does not
// change the SQLite results anyway.
let expressions = try keys(db)

let primaryKey = try db.primaryKey(databaseTableName)
GRDBPrecondition(
primaryKey.columns.count == 1,
"Requesting by key requires a single-column primary key in the table \(databaseTableName)")
return SQLCollection.array(expressions).contains(Column(primaryKey.columns[0]).sqlExpression)
let column = try db.filteringPrimaryKeyColumn(databaseTableName)
return SQLCollection.array(expressions).contains(Column(column).sqlExpression)
}
}

Expand Down
7 changes: 2 additions & 5 deletions GRDB/Record/TableRecord.swift
Original file line number Diff line number Diff line change
Expand Up @@ -744,13 +744,10 @@ extension TableRecord {
/// any error that prevented the `RecordError` from being constructed.
public static func recordNotFound(_ db: Database, key: some DatabaseValueConvertible) -> any Error {
do {
let primaryKey = try db.primaryKey(databaseTableName)
GRDBPrecondition(
primaryKey.columns.count == 1,
"Requesting by key requires a single-column primary key in the table \(databaseTableName)")
let column = try db.filteringPrimaryKeyColumn(databaseTableName)
return RecordError.recordNotFound(
databaseTableName: databaseTableName,
key: [primaryKey.columns[0]: key.databaseValue])
key: [column: key.databaseValue])
} catch {
return error
}
Expand Down

0 comments on commit 2c49a69

Please sign in to comment.