You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Hmmm, from some brief research, it sounds like sqlite doesn't really follow IEEE semantics at all, and hence doesn't really have a concept of NaN. A bigger issue here is that we aren't checking the return code on the sqlite_step when loading each row into the database, which could have at least issued a warning in this case.
For a Vector{Float64} column like this, we're currently "typing" it in sqlite as REAL NOT NULL, and sqlite returns a "constraint violation" when trying to insert NaN. If I take off the NOT NULL constraint, it successfully inserts as NULL, so the values are returned as missing.
Given all that, I don't see a clear solution for the original issue here; but I do plan on doing the return code check and warning when a row fails to insert. I'd love any ideas/research other people have here.
The problem is that the Tables.Schema interprets a vector with NaN and Float64 as a Vector{Float64}, which it actually is. However, this than results in a column of REAL NOT NULL. If I create the schema myself as Union{Float64,Missing} I can get it to store the NaNs as NULL.
A possible solution is to insert/update IEEE-754 special values, +/- Infty and NaNs, as 8-byte blobs. In Sqlite every value can have a different SQL type than the one declared for the table column. So a column declared as REAL can be Blobs in some rows. When selecting back from REAL columns, then of course the type needs to be checked for each row, sqlite3_column_type(). If it is 8 octets long blob, then convert to Float64 (and if it is NULL convert to missing).
If SQLite doesn't support it, most likely the solution will be tied to this Julia driver. This is like deciding how to write a Char to a JSON, that only supports Strings.
In this situation, I usually opt to not support the data type at all, and throw an error if the user tries to insert the unsupported data type to the database. In this case, a NaN value. The user will then be forced to solve the issue in the application level, deciding on how to encode the data.
Well, it is bit different than unsupported data type, unless you are suggesting to not support Float64 at all. In order to throw an error, every single Float64 value getting inserted would need to be checked for Infty or NaN values (which is of course also needed for my suggested solution).
On insert Sqlite just replaces NaNs and Infty silently with NULLs, there is no error code from sqlite3_step or so. The unpleasant consequence is that for Float64 you don't always get out what has been put into the database. The suggested solutions fixes this, albeit only within the particular driver.
Addendum: If the column is declared as REAL NOT NULL, an error is of course indicated. But the problem still exists if Union{Float64, Missing} is to be inserted.
Yes, every value should be checked for NaN or Inf. Or, equivalently, one could define a SQLiteNumber datatype that exactly matches the driver's definition and create encode/decode values to/from Float. Like the Oracle database does with OCINumber.
If the datatype does not match exactly, there's already some conversion happening before sending the value to the database. Usually the driver's C code is full of these per-value checks, and thank God this is Julia because we can do the same before hitting the C code and get the same performance. I guess this check won't hurt performance a lot, given this is most likely an IO bound operation.
Yeah, I agree that the best solution here is like what @felipenoris suggests: SQLite.jl could define a SQLFloat{T} type that properly respected IEEE-754 semantics. If someone feels like making a PR + docs, I'd appreciate it.
The issue cannot be completely solved without changes in Sqlite which will not happen. Regarding a driver like Sqlite.jl, the options are:
Accept how Sqlite is doing things. The columns corresponding to Julia Float64 should simply be declared REAL (not REAL NOT NULL). If a Float64 array has NaNs and/or Inftys, and it is inserted and selected back, it will have Sqlite NULLs in place of these. This simply maps to Union{Float64,Missing}. Alternatively Hipp's suggested sqlite3_column_double_v2 could be implemented in Julia, e.g. by constructing a type (SQLfloat?) that has value NaN for anything that is not REAL or INTEGER in Sqlite.
Disallow insertion of NaN and Inftys into SQlite, raising an exception if attempted. It could be handled via a type SQLfloat.
Workaround via Sqlite BLOBs. It can ensure that what goes into Sqlite also comes out even for NaNs and Inftys, but only within conforming drivers. A Julia type SQLfloat can be a vehicle to implement this for Julia.
So which option, what exactly should a type SQLfloat do?
Edit: I just saw that Inftys are handled normally in Sqlite, only NaNs get converted to NULL and would need special treatment in SQLite.jl
I checked this with registered SQLite.jl 1.9.0 and current master 997c150 on Julia 1.9.4 and 1.13.1, using native SQLite 3.53.4. The silent row loss is fixed: inserting NaN into a REAL NOT NULL column now raises SQLiteException and rolls back all inserts from that load! call. That error checking was added in #263, included in v1.3.0.
SQLite itself still converts a bound NaN into SQL NULL; its native implementation makes that explicit. With a nullable Union{Float64,Missing} input, load! preserves the row count and returns NaN as missing. Positive and negative infinity both remain REAL values and round-trip as Inf and -Inf in the tested version.
The 18-check reproduction covers the failed-batch rollback, a subsequent successful insert, reopening the same database, nullable row counts and values, and the native typeof(?) results. A representation that keeps NaN distinct from missing is still a separate feature, so I am leaving this open for that remaining request. Applications that need that distinction should choose an explicit encoding rather than relying on SQLite REAL.
AI disclosure: This work was prepared with assistance from OpenAI Codex.
Activity
quinnj commentedon Jun 29, 2020
Hmmm, from some brief research, it sounds like sqlite doesn't really follow IEEE semantics at all, and hence doesn't really have a concept of
NaN. A bigger issue here is that we aren't checking the return code on thesqlite_stepwhen loading each row into the database, which could have at least issued a warning in this case.For a
Vector{Float64}column like this, we're currently "typing" it in sqlite asREAL NOT NULL, and sqlite returns a "constraint violation" when trying to insertNaN. If I take off theNOT NULLconstraint, it successfully inserts asNULL, so the values are returned asmissing.Given all that, I don't see a clear solution for the original issue here; but I do plan on doing the return code check and warning when a row fails to insert. I'd love any ideas/research other people have here.
ValentinKaisermayer commentedon Jun 30, 2020
The problem is that the
Tables.Schemainterprets a vector withNaNandFloat64as aVector{Float64}, which it actually is. However, this than results in a column ofREAL NOT NULL. If I create the schema myself asUnion{Float64,Missing}I can get it to store theNaNsasNULL.stephancb commentedon Oct 27, 2020
A possible solution is to insert/update IEEE-754 special values, +/- Infty and NaNs, as 8-byte blobs. In Sqlite every value can have a different SQL type than the one declared for the table column. So a column declared as
REALcan be Blobs in some rows. When selecting back fromREALcolumns, then of course the type needs to be checked for each row,sqlite3_column_type(). If it is 8 octets long blob, then convert toFloat64(and if it isNULLconvert tomissing).felipenoris commentedon Oct 27, 2020
If SQLite doesn't support it, most likely the solution will be tied to this Julia driver. This is like deciding how to write a Char to a JSON, that only supports Strings.
In this situation, I usually opt to not support the data type at all, and throw an error if the user tries to insert the unsupported data type to the database. In this case, a NaN value. The user will then be forced to solve the issue in the application level, deciding on how to encode the data.
stephancb commentedon Oct 27, 2020
Well, it is bit different than unsupported data type, unless you are suggesting to not support
Float64at all. In order to throw an error, every singleFloat64value getting inserted would need to be checked for Infty or NaN values (which is of course also needed for my suggested solution).On insert Sqlite just replaces NaNs and Infty silently with NULLs, there is no error code from
sqlite3_stepor so. The unpleasant consequence is that forFloat64you don't always get out what has been put into the database. The suggested solutions fixes this, albeit only within the particular driver.Addendum: If the column is declared as
REAL NOT NULL, an error is of course indicated. But the problem still exists ifUnion{Float64, Missing}is to be inserted.felipenoris commentedon Oct 29, 2020
Yes, every value should be checked for NaN or Inf. Or, equivalently, one could define a
SQLiteNumberdatatype that exactly matches the driver's definition and create encode/decode values to/from Float. Like the Oracle database does withOCINumber.If the datatype does not match exactly, there's already some conversion happening before sending the value to the database. Usually the driver's C code is full of these per-value checks, and thank God this is Julia because we can do the same before hitting the C code and get the same performance. I guess this check won't hurt performance a lot, given this is most likely an IO bound operation.
quinnj commentedon Oct 30, 2020
Yeah, I agree that the best solution here is like what @felipenoris suggests: SQLite.jl could define a
SQLFloat{T}type that properly respected IEEE-754 semantics. If someone feels like making a PR + docs, I'd appreciate it.For reference, I found this old sqlite user mailing list thread where the core devs basically said they didn't really care about supporting IEEE 😞 http://sqlite.1065341.n5.nabble.com/Handling-of-IEEE-754-nan-and-inf-in-SQLite-td27874.html
stephancb commentedon Oct 30, 2020
Pse see also my question at http://sqlite.1065341.n5.nabble.com/NaN-in-0-0-out-td19086.html and Dr. Hipp's reply.
The issue cannot be completely solved without changes in Sqlite which will not happen. Regarding a driver like Sqlite.jl, the options are:
Accept how Sqlite is doing things. The columns corresponding to Julia
Float64should simply be declaredREAL(notREAL NOT NULL). If aFloat64array has NaNs and/or Inftys, and it is inserted and selected back, it will have Sqlite NULLs in place of these. This simply maps toUnion{Float64,Missing}. Alternatively Hipp's suggestedsqlite3_column_double_v2could be implemented in Julia, e.g. by constructing a type (SQLfloat?) that has value NaN for anything that is notREALorINTEGERin Sqlite.Disallow insertion of NaN and Inftys into SQlite, raising an exception if attempted. It could be handled via a type
SQLfloat.Workaround via Sqlite BLOBs. It can ensure that what goes into Sqlite also comes out even for NaNs and Inftys, but only within conforming drivers. A Julia type
SQLfloatcan be a vehicle to implement this for Julia.So which option, what exactly should a type SQLfloat do?
Edit: I just saw that Inftys are handled normally in Sqlite, only NaNs get converted to NULL and would need special treatment in SQLite.jl
quinnj commentedon Oct 4, 2026
I checked this with registered SQLite.jl 1.9.0 and current master
997c150on Julia 1.9.4 and 1.13.1, using native SQLite 3.53.4. The silent row loss is fixed: inserting NaN into aREAL NOT NULLcolumn now raisesSQLiteExceptionand rolls back all inserts from thatload!call. That error checking was added in #263, included in v1.3.0.SQLite itself still converts a bound NaN into SQL NULL; its native implementation makes that explicit. With a nullable
Union{Float64,Missing}input,load!preserves the row count and returns NaN asmissing. Positive and negative infinity both remain REAL values and round-trip asInfand-Infin the tested version.The 18-check reproduction covers the failed-batch rollback, a subsequent successful insert, reopening the same database, nullable row counts and values, and the native
typeof(?)results. A representation that keeps NaN distinct frommissingis still a separate feature, so I am leaving this open for that remaining request. Applications that need that distinction should choose an explicit encoding rather than relying on SQLite REAL.AI disclosure: This work was prepared with assistance from OpenAI Codex.