Skip to content

Commit 4b1eaf8

Browse files
Fix SQLite typing
Invalid types were declared in SQLite dialect, causing cast errors, and having led to an undue hack disabling casting for some types.
1 parent 25bc518 commit 4b1eaf8

File tree

2 files changed

+34
-33
lines changed

2 files changed

+34
-33
lines changed

src/NHibernate/Dialect/SQLiteDialect.cs

Lines changed: 30 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
using System;
21
using System.Collections.Generic;
32
using System.Data;
43
using System.Data.Common;
@@ -38,32 +37,46 @@ public SQLiteDialect()
3837

3938
protected virtual void RegisterColumnTypes()
4039
{
40+
// SQLite really has only five types, and a very lax typing system, see https://www.sqlite.org/datatype3.html
41+
// Please do not map (again) fancy types that do not actually exist in SQLite, as this is kind of supported by
42+
// SQLite but creates bugs in convert operations.
4143
RegisterColumnType(DbType.Binary, "BLOB");
42-
RegisterColumnType(DbType.Byte, "TINYINT");
43-
RegisterColumnType(DbType.Int16, "SMALLINT");
44-
RegisterColumnType(DbType.Int32, "INT");
45-
RegisterColumnType(DbType.Int64, "BIGINT");
44+
RegisterColumnType(DbType.Byte, "INTEGER");
45+
RegisterColumnType(DbType.Int16, "INTEGER");
46+
RegisterColumnType(DbType.Int32, "INTEGER");
47+
RegisterColumnType(DbType.Int64, "INTEGER");
4648
RegisterColumnType(DbType.SByte, "INTEGER");
4749
RegisterColumnType(DbType.UInt16, "INTEGER");
4850
RegisterColumnType(DbType.UInt32, "INTEGER");
4951
RegisterColumnType(DbType.UInt64, "INTEGER");
52+
53+
// NUMERIC and REAL are almost the same, they are binary floating point numbers. There is only a slight difference
54+
// for values without a floating part. They will be represented as integers with numeric, but still as floating
55+
// values with real. The side-effect of this is numeric being able of storing exactly bigger integers than real.
5056
RegisterColumnType(DbType.Currency, "NUMERIC");
5157
RegisterColumnType(DbType.Decimal, "NUMERIC");
52-
RegisterColumnType(DbType.Double, "DOUBLE");
53-
RegisterColumnType(DbType.Single, "DOUBLE");
58+
RegisterColumnType(DbType.Double, "REAL");
59+
RegisterColumnType(DbType.Single, "REAL");
5460
RegisterColumnType(DbType.VarNumeric, "NUMERIC");
61+
5562
RegisterColumnType(DbType.AnsiString, "TEXT");
5663
RegisterColumnType(DbType.String, "TEXT");
5764
RegisterColumnType(DbType.AnsiStringFixedLength, "TEXT");
5865
RegisterColumnType(DbType.StringFixedLength, "TEXT");
5966

60-
RegisterColumnType(DbType.Date, "DATE");
61-
RegisterColumnType(DbType.DateTime, "DATETIME");
62-
RegisterColumnType(DbType.Time, "TIME");
63-
RegisterColumnType(DbType.Boolean, "BOOL");
64-
// UNIQUEIDENTIFIER is not a SQLite type, but SQLite does not care much, see
65-
// https://www.sqlite.org/datatype3.html
66-
RegisterColumnType(DbType.Guid, "UNIQUEIDENTIFIER");
67+
// https://www.sqlite.org/datatype3.html#boolean_datatype
68+
RegisterColumnType(DbType.Boolean, "INTEGER");
69+
70+
// See https://www.sqlite.org/datatype3.html#date_and_time_datatype, we have three choices for date and time
71+
// The one causing the less issues in case of an explicit cast is text. Beware, System.Data.SQLite has an
72+
// internal use only "DATETIME" type. Using it causes it to directly convert the text stored into SQLite to
73+
// a .Net DateTime, but also causes columns in SQLite to have numeric affinity and convert to destroy the
74+
// value. As said in their chm documentation, this "DATETIME" type is for System.Data.SQLite internal use only.
75+
RegisterColumnType(DbType.Date, "TEXT");
76+
RegisterColumnType(DbType.DateTime, "TEXT");
77+
RegisterColumnType(DbType.Time, "TEXT");
78+
79+
RegisterColumnType(DbType.Guid, _binaryGuid ? "BLOB" : "TEXT");
6780
}
6881

6982
protected virtual void RegisterFunctions()
@@ -98,8 +111,6 @@ protected virtual void RegisterFunctions()
98111

99112
RegisterFunction("iif", new SQLFunctionTemplate(null, "case when ?1 then ?2 else ?3 end"));
100113

101-
RegisterFunction("cast", new SQLiteCastFunction());
102-
103114
RegisterFunction("round", new StandardSQLFunction("round"));
104115

105116
// SQLite has no built-in support of bitwise xor, but can emulate it.
@@ -112,7 +123,7 @@ protected virtual void RegisterFunctions()
112123
if (_binaryGuid)
113124
RegisterFunction("strguid", new SQLFunctionTemplate(NHibernateUtil.String, "substr(hex(?1), 7, 2) || substr(hex(?1), 5, 2) || substr(hex(?1), 3, 2) || substr(hex(?1), 1, 2) || '-' || substr(hex(?1), 11, 2) || substr(hex(?1), 9, 2) || '-' || substr(hex(?1), 15, 2) || substr(hex(?1), 13, 2) || '-' || substr(hex(?1), 17, 4) || '-' || substr(hex(?1), 21) "));
114125
else
115-
RegisterFunction("strguid", new SQLFunctionTemplate(NHibernateUtil.String, "cast(?1 as char)"));
126+
RegisterFunction("strguid", new SQLFunctionTemplate(NHibernateUtil.String, "cast(?1 as text)"));
116127

117128
// SQLite random function yields a long, ranging form MinValue to MaxValue. (-9223372036854775808 to
118129
// 9223372036854775807). HQL random requires a float from 0 inclusive to 1 exclusive, so we divide by
@@ -131,7 +142,8 @@ public override void Configure(IDictionary<string, string> settings)
131142

132143
ConfigureBinaryGuid(settings);
133144

134-
// Re-register functions depending on settings.
145+
// Re-register functions and types depending on settings.
146+
RegisterColumnTypes();
135147
RegisterFunctions();
136148
}
137149

@@ -484,17 +496,5 @@ public override bool SupportsForeignKeyConstraintInAlterTable
484496
// Said to be unlimited. http://sqlite.1065341.n5.nabble.com/Max-limits-on-the-following-td37859.html
485497
/// <inheritdoc />
486498
public override int MaxAliasLength => 128;
487-
488-
[Serializable]
489-
protected class SQLiteCastFunction : CastFunction
490-
{
491-
protected override bool CastingIsRequired(string sqlType)
492-
{
493-
// SQLite doesn't support casting to datetime types. It assumes you want an integer and destroys the date string.
494-
if (StringHelper.ContainsCaseInsensitive(sqlType, "date") || StringHelper.ContainsCaseInsensitive(sqlType, "time"))
495-
return false;
496-
return true;
497-
}
498-
}
499499
}
500500
}

src/NHibernate/Type/TimeAsTimeSpanType.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,11 @@ public override object Get(DbDataReader rs, int index, ISessionImplementor sessi
4343
try
4444
{
4545
var value = rs[index];
46-
if(value is TimeSpan time) //For those dialects where DbType.Time means TimeSpan.
46+
if (value is TimeSpan time) //For those dialects where DbType.Time means TimeSpan.
4747
return time;
48-
49-
return ((DateTime)value).TimeOfDay;
48+
49+
var dbValue = Convert.ToDateTime(value);
50+
return dbValue.TimeOfDay;
5051
}
5152
catch (Exception ex)
5253
{

0 commit comments

Comments
 (0)