Fatal error: 'try!' expression unexpectedly raised an error: Unexpected null value for column `"to"`
stefansaasen opened this issue · comments
The following error occurs using the schema reader (specifically columnDefinitions(table:)
) for a particular table (see below for the tables/foreign key definition that triggers this):
SQLite/Query.swift:1216: Fatal error: 'try!' expression unexpectedly raised an error: Unexpected null value for column `"to"`
SQLite.swift/Sources/SQLite/Typed/Query.swift
Line 1216 in 7a2e3cd
This is due to the fact that the foreignKeys
function in the SchemaReader
assumes that the to
colum returned by running PRAGMA foreign_key_list("table name")
contains a valid primary key column and is not null:
SQLite.swift/Sources/SQLite/Schema/SchemaReader.swift
Lines 94 to 107 in 7a2e3cd
The error occurs in line 100
: row[ForeignKeyListTable.toColumn]
.
That is not necessarily true though.
How to reproduce
Minimal example (see below for the necessary Package.swift
definition):
import SQLite
do {
let db = try Connection()
let sql = """
CREATE TABLE artist(
artistid INTEGER PRIMARY KEY,
artistname TEXT
);
CREATE TABLE track(
trackid INTEGER,
trackname TEXT,
trackartist INTEGER REFERENCES artist
);
CREATE INDEX trackindex ON track(trackartist);
"""
try db.execute(sql)
let schemaInfoArtist = try db.schema.columnDefinitions(table: "track")
print("schemaInfoArtist: \(schemaInfoArtist)")
} catch {
print("error: \(error)")
}
The table definition is taken from the last example on https://www.sqlite.org/foreignkeys.html#fk_indexes (Section 3 "Required and Suggested Database Indexes").
The result of the PRAGMA
query is the following table:
sqlite> PRAGMA foreign_key_list('track');
id|seq|table|from|to|on_update|on_delete|match
0|0|artist|trackartist||NO ACTION|NO ACTION|NONE
Here the to
column is empty.
The SQLite source for the foreign key contains the following bits (see https://github.com/sqlite/sqlite/blob/master/src/sqliteInt.h#L2500):
struct FKey {
...
struct sColMap { /* Mapping of columns in pFrom to columns in zTo */
....
char *zCol; /* Name of column in zTo. If NULL use PRIMARY KEY */
} aCol[1];
};
So it seems valid to omit the primary key column and SQLite will automatically use the primary key column of the referenced table. On the flip side that means, to
can in fact be null. In this example, the referenced column would be artist. artistid
(the primary key of the artist
table).
Build Information
SQLite.swift version: 0.14.1
(also tested on master
at 1b1eba0)
Xcode version: 14.3
macOS: 13.3.1
SQLite.swift is integrated via the SPM:
import PackageDescription
let package = Package(
name: "to-column-null",
dependencies: [
.package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.14.1")
],
targets: [
// Targets are the basic building blocks of a package, defining a module or a test suite.
// Targets can depend on other targets in this package and products from dependencies.
.executableTarget(
name: "to-column-null",
dependencies: [
.product(name: "SQLite", package: "SQLite.swift")
],
path: "Sources"),
]
)
Thanks for reporting this. Can you take a look at #1210 to see if this fixes your problem?
Thanks for looking into this! That does in fact solve the problem and matches the information returned by e.g. PRAGMA foreign_key_list('track');
.
Should this be documented somehow? E.g. as the caller I need to work out what the primary key of the referenced table is (depending on the use case).
There's a code comment // when null, use primary key
, maybe surface this?
Yes, good point, I'll add some documentation to the ForeignKey
struct
.