Skip to content

Commit 4d95ef3

Browse files
authored
Merge pull request #902 from cherron-aptera/FastColumnSetter
Performance Optimization: Fast Column Setters
2 parents 351363f + e57c345 commit 4d95ef3

File tree

1 file changed

+233
-3
lines changed

1 file changed

+233
-3
lines changed

src/SQLite.cs

+233-3
Original file line numberDiff line numberDiff line change
@@ -2931,20 +2931,29 @@ public IEnumerable<T> ExecuteDeferredQuery<T> (TableMapping map)
29312931
var stmt = Prepare ();
29322932
try {
29332933
var cols = new TableMapping.Column[SQLite3.ColumnCount (stmt)];
2934+
var fastColumnSetters = new Action<T, Sqlite3Statement, int>[SQLite3.ColumnCount (stmt)];
29342935

29352936
for (int i = 0; i < cols.Length; i++) {
29362937
var name = SQLite3.ColumnName16 (stmt, i);
29372938
cols[i] = map.FindColumn (name);
2939+
if (cols[i] != null)
2940+
fastColumnSetters[i] = FastColumnSetter.GetFastSetter<T> (_conn, cols[i]);
29382941
}
29392942

29402943
while (SQLite3.Step (stmt) == SQLite3.Result.Row) {
29412944
var obj = Activator.CreateInstance (map.MappedType);
29422945
for (int i = 0; i < cols.Length; i++) {
29432946
if (cols[i] == null)
29442947
continue;
2945-
var colType = SQLite3.ColumnType (stmt, i);
2946-
var val = ReadCol (stmt, i, colType, cols[i].ColumnType);
2947-
cols[i].SetValue (obj, val);
2948+
2949+
if (fastColumnSetters[i] != null) {
2950+
fastColumnSetters[i].Invoke ((T)obj, stmt, i);
2951+
}
2952+
else {
2953+
var colType = SQLite3.ColumnType (stmt, i);
2954+
var val = ReadCol (stmt, i, colType, cols[i].ColumnType);
2955+
cols[i].SetValue (obj, val);
2956+
}
29482957
}
29492958
OnInstanceCreated (obj);
29502959
yield return (T)obj;
@@ -3263,6 +3272,227 @@ object ReadCol (Sqlite3Statement stmt, int index, SQLite3.ColType type, Type clr
32633272
}
32643273
}
32653274

3275+
internal class FastColumnSetter
3276+
{
3277+
/// <summary>
3278+
/// Creates a delegate that can be used to quickly set object members from query columns.
3279+
///
3280+
/// Note that this frontloads the slow reflection-based type checking for columns to only happen once at the beginning of a query,
3281+
/// and then afterwards each row of the query can invoke the delegate returned by this function to get much better performance (up to 10x speed boost, depending on query size and platform).
3282+
/// </summary>
3283+
/// <typeparam name="T">The type of the destination object that the query will read into</typeparam>
3284+
/// <param name="conn">The active connection. Note that this is primarily needed in order to read preferences regarding how certain data types (such as TimeSpan / DateTime) should be encoded in the database.</param>
3285+
/// <param name="column">The table mapping used to map the statement column to a member of the destination object type</param>
3286+
/// <returns>
3287+
/// A delegate for fast-setting of object members from statement columns.
3288+
///
3289+
/// If no fast setter is available for the requested column (enums in particular cause headache), then this function returns null.
3290+
/// </returns>
3291+
internal static Action<T, Sqlite3Statement, int> GetFastSetter<T> (SQLiteConnection conn, TableMapping.Column column)
3292+
{
3293+
Action<T, Sqlite3Statement, int> fastSetter = null;
3294+
3295+
Type clrType = column.PropertyInfo.PropertyType;
3296+
3297+
var clrTypeInfo = clrType.GetTypeInfo ();
3298+
if (clrTypeInfo.IsGenericType && clrTypeInfo.GetGenericTypeDefinition () == typeof (Nullable<>)) {
3299+
clrType = clrTypeInfo.GenericTypeArguments[0];
3300+
clrTypeInfo = clrType.GetTypeInfo ();
3301+
}
3302+
3303+
if (clrType == typeof (String)) {
3304+
fastSetter = CreateTypedSetterDelegate<T, string> (column, (stmt, index) => {
3305+
return SQLite3.ColumnString (stmt, index);
3306+
});
3307+
}
3308+
else if (clrType == typeof (Int32)) {
3309+
fastSetter = CreateNullableTypedSetterDelegate<T, int> (column, (stmt, index)=>{
3310+
return SQLite3.ColumnInt (stmt, index);
3311+
});
3312+
}
3313+
else if (clrType == typeof (Boolean)) {
3314+
fastSetter = CreateNullableTypedSetterDelegate<T, bool> (column, (stmt, index) => {
3315+
return SQLite3.ColumnInt (stmt, index) == 1;
3316+
});
3317+
}
3318+
else if (clrType == typeof (double)) {
3319+
fastSetter = CreateNullableTypedSetterDelegate<T, double> (column, (stmt, index) => {
3320+
return SQLite3.ColumnDouble (stmt, index);
3321+
});
3322+
}
3323+
else if (clrType == typeof (float)) {
3324+
fastSetter = CreateNullableTypedSetterDelegate<T, float> (column, (stmt, index) => {
3325+
return (float) SQLite3.ColumnDouble (stmt, index);
3326+
});
3327+
}
3328+
else if (clrType == typeof (TimeSpan)) {
3329+
if (conn.StoreTimeSpanAsTicks) {
3330+
fastSetter = CreateNullableTypedSetterDelegate<T, TimeSpan> (column, (stmt, index) => {
3331+
return new TimeSpan (SQLite3.ColumnInt64 (stmt, index));
3332+
});
3333+
}
3334+
else {
3335+
fastSetter = CreateNullableTypedSetterDelegate<T, TimeSpan> (column, (stmt, index) => {
3336+
var text = SQLite3.ColumnString (stmt, index);
3337+
TimeSpan resultTime;
3338+
if (!TimeSpan.TryParseExact (text, "c", System.Globalization.CultureInfo.InvariantCulture, System.Globalization.TimeSpanStyles.None, out resultTime)) {
3339+
resultTime = TimeSpan.Parse (text);
3340+
}
3341+
return resultTime;
3342+
});
3343+
}
3344+
}
3345+
else if (clrType == typeof (DateTime)) {
3346+
if (conn.StoreDateTimeAsTicks) {
3347+
fastSetter = CreateNullableTypedSetterDelegate<T, DateTime> (column, (stmt, index) => {
3348+
return new DateTime (SQLite3.ColumnInt64 (stmt, index));
3349+
});
3350+
}
3351+
else {
3352+
fastSetter = CreateNullableTypedSetterDelegate<T, DateTime> (column, (stmt, index) => {
3353+
var text = SQLite3.ColumnString (stmt, index);
3354+
DateTime resultDate;
3355+
if (!DateTime.TryParseExact (text, conn.DateTimeStringFormat, System.Globalization.CultureInfo.InvariantCulture, conn.DateTimeStyle, out resultDate)) {
3356+
resultDate = DateTime.Parse (text);
3357+
}
3358+
return resultDate;
3359+
});
3360+
}
3361+
}
3362+
else if (clrType == typeof (DateTimeOffset)) {
3363+
fastSetter = CreateNullableTypedSetterDelegate<T, DateTimeOffset> (column, (stmt, index) => {
3364+
return new DateTimeOffset (SQLite3.ColumnInt64 (stmt, index), TimeSpan.Zero);
3365+
});
3366+
}
3367+
else if (clrTypeInfo.IsEnum) {
3368+
// NOTE: Not sure of a good way (if any?) to do a strongly-typed fast setter like this for enumerated types -- for now, return null and column sets will revert back to the safe (but slow) Reflection-based method of column prop.Set()
3369+
}
3370+
else if (clrType == typeof (Int64)) {
3371+
fastSetter = CreateNullableTypedSetterDelegate<T, Int64> (column, (stmt, index) => {
3372+
return SQLite3.ColumnInt64 (stmt, index);
3373+
});
3374+
}
3375+
else if (clrType == typeof (UInt32)) {
3376+
fastSetter = CreateNullableTypedSetterDelegate<T, UInt32> (column, (stmt, index) => {
3377+
return (uint)SQLite3.ColumnInt64 (stmt, index);
3378+
});
3379+
}
3380+
else if (clrType == typeof (decimal)) {
3381+
fastSetter = CreateNullableTypedSetterDelegate<T, decimal> (column, (stmt, index) => {
3382+
return (decimal)SQLite3.ColumnDouble (stmt, index);
3383+
});
3384+
}
3385+
else if (clrType == typeof (Byte)) {
3386+
fastSetter = CreateNullableTypedSetterDelegate<T, Byte> (column, (stmt, index) => {
3387+
return (byte)SQLite3.ColumnInt (stmt, index);
3388+
});
3389+
}
3390+
else if (clrType == typeof (UInt16)) {
3391+
fastSetter = CreateNullableTypedSetterDelegate<T, UInt16> (column, (stmt, index) => {
3392+
return (ushort)SQLite3.ColumnInt (stmt, index);
3393+
});
3394+
}
3395+
else if (clrType == typeof (Int16)) {
3396+
fastSetter = CreateNullableTypedSetterDelegate<T, Int16> (column, (stmt, index) => {
3397+
return (short)SQLite3.ColumnInt (stmt, index);
3398+
});
3399+
}
3400+
else if (clrType == typeof (sbyte)) {
3401+
fastSetter = CreateNullableTypedSetterDelegate<T, sbyte> (column, (stmt, index) => {
3402+
return (sbyte)SQLite3.ColumnInt (stmt, index);
3403+
});
3404+
}
3405+
else if (clrType == typeof (byte[])) {
3406+
fastSetter = CreateTypedSetterDelegate<T, byte[]> (column, (stmt, index) => {
3407+
return SQLite3.ColumnByteArray (stmt, index);
3408+
});
3409+
}
3410+
else if (clrType == typeof (Guid)) {
3411+
fastSetter = CreateNullableTypedSetterDelegate<T, Guid> (column, (stmt, index) => {
3412+
var text = SQLite3.ColumnString (stmt, index);
3413+
return new Guid (text);
3414+
});
3415+
}
3416+
else if (clrType == typeof (Uri)) {
3417+
fastSetter = CreateTypedSetterDelegate<T, Uri> (column, (stmt, index) => {
3418+
var text = SQLite3.ColumnString (stmt, index);
3419+
return new Uri (text);
3420+
});
3421+
}
3422+
else if (clrType == typeof (StringBuilder)) {
3423+
fastSetter = CreateTypedSetterDelegate<T, StringBuilder> (column, (stmt, index) => {
3424+
var text = SQLite3.ColumnString (stmt, index);
3425+
return new StringBuilder (text);
3426+
});
3427+
}
3428+
else if (clrType == typeof (UriBuilder)) {
3429+
fastSetter = CreateTypedSetterDelegate<T, UriBuilder> (column, (stmt, index) => {
3430+
var text = SQLite3.ColumnString (stmt, index);
3431+
return new UriBuilder (text);
3432+
});
3433+
}
3434+
else {
3435+
// NOTE: Will fall back to the slow setter method in the event that we are unable to create a fast setter delegate for a particular column type
3436+
}
3437+
return fastSetter;
3438+
}
3439+
3440+
/// <summary>
3441+
/// This creates a strongly typed delegate that will permit fast setting of column values given a Sqlite3Statement and a column index.
3442+
///
3443+
/// Note that this is identical to CreateTypedSetterDelegate(), but has an extra check to see if it should create a nullable version of the delegate.
3444+
/// </summary>
3445+
/// <typeparam name="ObjectType">The type of the object whose member column is being set</typeparam>
3446+
/// <typeparam name="ColumnMemberType">The CLR type of the member in the object which corresponds to the given SQLite columnn</typeparam>
3447+
/// <param name="column">The column mapping that identifies the target member of the destination object</param>
3448+
/// <param name="getColumnValue">A lambda that can be used to retrieve the column value at query-time</param>
3449+
/// <returns>A strongly-typed delegate</returns>
3450+
private static Action<ObjectType, Sqlite3Statement, int> CreateNullableTypedSetterDelegate<ObjectType, ColumnMemberType> (TableMapping.Column column, Func<Sqlite3Statement, int, ColumnMemberType> getColumnValue) where ColumnMemberType : struct
3451+
{
3452+
var clrTypeInfo = column.PropertyInfo.PropertyType.GetTypeInfo();
3453+
bool isNullable = false;
3454+
3455+
if (clrTypeInfo.IsGenericType && clrTypeInfo.GetGenericTypeDefinition () == typeof (Nullable<>)) {
3456+
isNullable = true;
3457+
}
3458+
3459+
if (isNullable) {
3460+
var setProperty = (Action<ObjectType, ColumnMemberType?>)Delegate.CreateDelegate (
3461+
typeof (Action<ObjectType, ColumnMemberType?>), null,
3462+
column.PropertyInfo.GetSetMethod ());
3463+
3464+
return (o, stmt, i) => {
3465+
var colType = SQLite3.ColumnType (stmt, i);
3466+
if (colType != SQLite3.ColType.Null)
3467+
setProperty.Invoke (o, getColumnValue.Invoke (stmt, i));
3468+
};
3469+
}
3470+
3471+
return CreateTypedSetterDelegate<ObjectType, ColumnMemberType> (column, getColumnValue);
3472+
}
3473+
3474+
/// <summary>
3475+
/// This creates a strongly typed delegate that will permit fast setting of column values given a Sqlite3Statement and a column index.
3476+
/// </summary>
3477+
/// <typeparam name="ObjectType">The type of the object whose member column is being set</typeparam>
3478+
/// <typeparam name="ColumnMemberType">The CLR type of the member in the object which corresponds to the given SQLite columnn</typeparam>
3479+
/// <param name="column">The column mapping that identifies the target member of the destination object</param>
3480+
/// <param name="getColumnValue">A lambda that can be used to retrieve the column value at query-time</param>
3481+
/// <returns>A strongly-typed delegate</returns>
3482+
private static Action<ObjectType, Sqlite3Statement, int> CreateTypedSetterDelegate<ObjectType, ColumnMemberType> (TableMapping.Column column, Func<Sqlite3Statement, int, ColumnMemberType> getColumnValue)
3483+
{
3484+
var setProperty = (Action<ObjectType, ColumnMemberType>)Delegate.CreateDelegate (
3485+
typeof (Action<ObjectType, ColumnMemberType>), null,
3486+
column.PropertyInfo.GetSetMethod ());
3487+
3488+
return (o, stmt, i) => {
3489+
var colType = SQLite3.ColumnType (stmt, i);
3490+
if (colType != SQLite3.ColType.Null)
3491+
setProperty.Invoke (o, getColumnValue.Invoke (stmt, i));
3492+
};
3493+
}
3494+
}
3495+
32663496
/// <summary>
32673497
/// Since the insert never changed, we only need to prepare once.
32683498
/// </summary>

0 commit comments

Comments
 (0)