< Summary

Information
Class: SQLite.EnumCache
Assembly: SQLite.Tests
File(s): /home/runner/work/sqlite-net/sqlite-net/src/SQLite.cs
Tag: 172_8459418968
Line coverage
100%
Covered lines: 13
Uncovered lines: 0
Coverable lines: 13
Total lines: 4992
Line coverage: 100%
Branch coverage
100%
Covered branches: 2
Total branches: 2
Branch coverage: 100%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity NPath complexity Sequence coverage
.cctor()0%110100%
GetInfo()0%110100%
GetInfo(...)100%332100%

File(s)

/home/runner/work/sqlite-net/sqlite-net/src/SQLite.cs

#LineLine coverage
 1//
 2// Copyright (c) 2009-2024 Krueger Systems, Inc.
 3//
 4// Permission is hereby granted, free of charge, to any person obtaining a copy
 5// of this software and associated documentation files (the "Software"), to deal
 6// in the Software without restriction, including without limitation the rights
 7// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 8// copies of the Software, and to permit persons to whom the Software is
 9// furnished to do so, subject to the following conditions:
 10//
 11// The above copyright notice and this permission notice shall be included in
 12// all copies or substantial portions of the Software.
 13//
 14// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 15// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 16// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 17// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 18// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 19// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 20// THE SOFTWARE.
 21//
 22#if WINDOWS_PHONE && !USE_WP8_NATIVE_SQLITE
 23#define USE_CSHARP_SQLITE
 24#endif
 25
 26using System;
 27using System.Collections;
 28using System.Diagnostics;
 29#if !USE_SQLITEPCL_RAW
 30using System.Runtime.InteropServices;
 31#endif
 32using System.Collections.Generic;
 33using System.Reflection;
 34using System.Linq;
 35using System.Linq.Expressions;
 36using System.Text;
 37using System.Threading;
 38
 39#if USE_CSHARP_SQLITE
 40using Sqlite3 = Community.CsharpSqlite.Sqlite3;
 41using Sqlite3DatabaseHandle = Community.CsharpSqlite.Sqlite3.sqlite3;
 42using Sqlite3Statement = Community.CsharpSqlite.Sqlite3.Vdbe;
 43#elif USE_WP8_NATIVE_SQLITE
 44using Sqlite3 = Sqlite.Sqlite3;
 45using Sqlite3DatabaseHandle = Sqlite.Database;
 46using Sqlite3Statement = Sqlite.Statement;
 47#elif USE_SQLITEPCL_RAW
 48using Sqlite3DatabaseHandle = SQLitePCL.sqlite3;
 49using Sqlite3BackupHandle = SQLitePCL.sqlite3_backup;
 50using Sqlite3Statement = SQLitePCL.sqlite3_stmt;
 51using Sqlite3 = SQLitePCL.raw;
 52#else
 53using Sqlite3DatabaseHandle = System.IntPtr;
 54using Sqlite3BackupHandle = System.IntPtr;
 55using Sqlite3Statement = System.IntPtr;
 56#endif
 57
 58#pragma warning disable 1591 // XML Doc Comments
 59
 60namespace SQLite
 61{
 62  public class SQLiteException : Exception
 63  {
 64    public SQLite3.Result Result { get; private set; }
 65
 66    protected SQLiteException (SQLite3.Result r, string message) : base (message)
 67    {
 68      Result = r;
 69    }
 70
 71    public static SQLiteException New (SQLite3.Result r, string message)
 72    {
 73      return new SQLiteException (r, message);
 74    }
 75  }
 76
 77  public class NotNullConstraintViolationException : SQLiteException
 78  {
 79    public IEnumerable<TableMapping.Column> Columns { get; protected set; }
 80
 81    protected NotNullConstraintViolationException (SQLite3.Result r, string message)
 82      : this (r, message, null, null)
 83    {
 84
 85    }
 86
 87    protected NotNullConstraintViolationException (SQLite3.Result r, string message, TableMapping mapping, object obj)
 88      : base (r, message)
 89    {
 90      if (mapping != null && obj != null) {
 91        this.Columns = from c in mapping.Columns
 92                 where c.IsNullable == false && c.GetValue (obj) == null
 93                 select c;
 94      }
 95    }
 96
 97    public static new NotNullConstraintViolationException New (SQLite3.Result r, string message)
 98    {
 99      return new NotNullConstraintViolationException (r, message);
 100    }
 101
 102    public static NotNullConstraintViolationException New (SQLite3.Result r, string message, TableMapping mapping, objec
 103    {
 104      return new NotNullConstraintViolationException (r, message, mapping, obj);
 105    }
 106
 107    public static NotNullConstraintViolationException New (SQLiteException exception, TableMapping mapping, object obj)
 108    {
 109      return new NotNullConstraintViolationException (exception.Result, exception.Message, mapping, obj);
 110    }
 111  }
 112
 113  [Flags]
 114  public enum SQLiteOpenFlags
 115  {
 116    ReadOnly = 1, ReadWrite = 2, Create = 4,
 117    Uri = 0x40, Memory = 0x80,
 118    NoMutex = 0x8000, FullMutex = 0x10000,
 119    SharedCache = 0x20000, PrivateCache = 0x40000,
 120    ProtectionComplete = 0x00100000,
 121    ProtectionCompleteUnlessOpen = 0x00200000,
 122    ProtectionCompleteUntilFirstUserAuthentication = 0x00300000,
 123    ProtectionNone = 0x00400000
 124  }
 125
 126  [Flags]
 127  public enum CreateFlags
 128  {
 129    /// <summary>
 130    /// Use the default creation options
 131    /// </summary>
 132    None = 0x000,
 133    /// <summary>
 134    /// Create a primary key index for a property called 'Id' (case-insensitive).
 135    /// This avoids the need for the [PrimaryKey] attribute.
 136    /// </summary>
 137    ImplicitPK = 0x001,
 138    /// <summary>
 139    /// Create indices for properties ending in 'Id' (case-insensitive).
 140    /// </summary>
 141    ImplicitIndex = 0x002,
 142    /// <summary>
 143    /// Create a primary key for a property called 'Id' and
 144    /// create an indices for properties ending in 'Id' (case-insensitive).
 145    /// </summary>
 146    AllImplicit = 0x003,
 147    /// <summary>
 148    /// Force the primary key property to be auto incrementing.
 149    /// This avoids the need for the [AutoIncrement] attribute.
 150    /// The primary key property on the class should have type int or long.
 151    /// </summary>
 152    AutoIncPK = 0x004,
 153    /// <summary>
 154    /// Create virtual table using FTS3
 155    /// </summary>
 156    FullTextSearch3 = 0x100,
 157    /// <summary>
 158    /// Create virtual table using FTS4
 159    /// </summary>
 160    FullTextSearch4 = 0x200
 161  }
 162
 163  public interface ISQLiteConnection : IDisposable
 164  {
 165    Sqlite3DatabaseHandle Handle { get; }
 166    string DatabasePath { get; }
 167    int LibVersionNumber { get; }
 168    bool TimeExecution { get; set; }
 169    bool Trace { get; set; }
 170    Action<string> Tracer { get; set; }
 171    bool StoreDateTimeAsTicks { get; }
 172    bool StoreTimeSpanAsTicks { get; }
 173    string DateTimeStringFormat { get; }
 174    TimeSpan BusyTimeout { get; set; }
 175    IEnumerable<TableMapping> TableMappings { get; }
 176    bool IsInTransaction { get; }
 177
 178    event EventHandler<NotifyTableChangedEventArgs> TableChanged;
 179
 180    void Backup (string destinationDatabasePath, string databaseName = "main");
 181    void BeginTransaction ();
 182    void Close ();
 183    void Commit ();
 184    SQLiteCommand CreateCommand (string cmdText, params object[] ps);
 185    SQLiteCommand CreateCommand (string cmdText, Dictionary<string, object> args);
 186    int CreateIndex (string indexName, string tableName, string[] columnNames, bool unique = false);
 187    int CreateIndex (string indexName, string tableName, string columnName, bool unique = false);
 188    int CreateIndex (string tableName, string columnName, bool unique = false);
 189    int CreateIndex (string tableName, string[] columnNames, bool unique = false);
 190    int CreateIndex<T> (Expression<Func<T, object>> property, bool unique = false);
 191    CreateTableResult CreateTable<T> (CreateFlags createFlags = CreateFlags.None);
 192    CreateTableResult CreateTable (Type ty, CreateFlags createFlags = CreateFlags.None);
 193    CreateTablesResult CreateTables<T, T2> (CreateFlags createFlags = CreateFlags.None)
 194      where T : new()
 195      where T2 : new();
 196    CreateTablesResult CreateTables<T, T2, T3> (CreateFlags createFlags = CreateFlags.None)
 197      where T : new()
 198      where T2 : new()
 199      where T3 : new();
 200    CreateTablesResult CreateTables<T, T2, T3, T4> (CreateFlags createFlags = CreateFlags.None)
 201      where T : new()
 202      where T2 : new()
 203      where T3 : new()
 204      where T4 : new();
 205    CreateTablesResult CreateTables<T, T2, T3, T4, T5> (CreateFlags createFlags = CreateFlags.None)
 206      where T : new()
 207      where T2 : new()
 208      where T3 : new()
 209      where T4 : new()
 210      where T5 : new();
 211    CreateTablesResult CreateTables (CreateFlags createFlags = CreateFlags.None, params Type[] types);
 212    IEnumerable<T> DeferredQuery<T> (string query, params object[] args) where T : new();
 213    IEnumerable<object> DeferredQuery (TableMapping map, string query, params object[] args);
 214    int Delete (object objectToDelete);
 215    int Delete<T> (object primaryKey);
 216    int Delete (object primaryKey, TableMapping map);
 217    int DeleteAll<T> ();
 218    int DeleteAll (TableMapping map);
 219    int DropTable<T> ();
 220    int DropTable (TableMapping map);
 221    void EnableLoadExtension (bool enabled);
 222    void EnableWriteAheadLogging ();
 223    int Execute (string query, params object[] args);
 224    T ExecuteScalar<T> (string query, params object[] args);
 225    T Find<T> (object pk) where T : new();
 226    object Find (object pk, TableMapping map);
 227    T Find<T> (Expression<Func<T, bool>> predicate) where T : new();
 228    T FindWithQuery<T> (string query, params object[] args) where T : new();
 229    object FindWithQuery (TableMapping map, string query, params object[] args);
 230    T Get<T> (object pk) where T : new();
 231    object Get (object pk, TableMapping map);
 232    T Get<T> (Expression<Func<T, bool>> predicate) where T : new();
 233    TableMapping GetMapping (Type type, CreateFlags createFlags = CreateFlags.None);
 234    TableMapping GetMapping<T> (CreateFlags createFlags = CreateFlags.None);
 235    List<SQLiteConnection.ColumnInfo> GetTableInfo (string tableName);
 236    int Insert (object obj);
 237    int Insert (object obj, Type objType);
 238    int Insert (object obj, string extra);
 239    int Insert (object obj, string extra, Type objType);
 240    int InsertAll (IEnumerable objects, bool runInTransaction = true);
 241    int InsertAll (IEnumerable objects, string extra, bool runInTransaction = true);
 242    int InsertAll (IEnumerable objects, Type objType, bool runInTransaction = true);
 243    int InsertOrReplace (object obj);
 244    int InsertOrReplace (object obj, Type objType);
 245    List<T> Query<T> (string query, params object[] args) where T : new();
 246    List<object> Query (TableMapping map, string query, params object[] args);
 247    List<T> QueryScalars<T> (string query, params object[] args);
 248    void ReKey (string key);
 249    void ReKey (byte[] key);
 250    void Release (string savepoint);
 251    void Rollback ();
 252    void RollbackTo (string savepoint);
 253    void RunInTransaction (Action action);
 254    string SaveTransactionPoint ();
 255    TableQuery<T> Table<T> () where T : new();
 256    int Update (object obj);
 257    int Update (object obj, Type objType);
 258    int UpdateAll (IEnumerable objects, bool runInTransaction = true);
 259  }
 260
 261  /// <summary>
 262  /// An open connection to a SQLite database.
 263  /// </summary>
 264  [Preserve (AllMembers = true)]
 265  public partial class SQLiteConnection : ISQLiteConnection
 266  {
 267    private bool _open;
 268    private TimeSpan _busyTimeout;
 269    readonly static Dictionary<string, TableMapping> _mappings = new Dictionary<string, TableMapping> ();
 270    private System.Diagnostics.Stopwatch _sw;
 271    private long _elapsedMilliseconds = 0;
 272
 273    private int _transactionDepth = 0;
 274    private Random _rand = new Random ();
 275
 276    public Sqlite3DatabaseHandle Handle { get; private set; }
 277    static readonly Sqlite3DatabaseHandle NullHandle = default (Sqlite3DatabaseHandle);
 278    static readonly Sqlite3BackupHandle NullBackupHandle = default (Sqlite3BackupHandle);
 279
 280    /// <summary>
 281    /// Gets the database path used by this connection.
 282    /// </summary>
 283    public string DatabasePath { get; private set; }
 284
 285    /// <summary>
 286    /// Gets the SQLite library version number. 3007014 would be v3.7.14
 287    /// </summary>
 288    public int LibVersionNumber { get; private set; }
 289
 290    /// <summary>
 291    /// Whether Trace lines should be written that show the execution time of queries.
 292    /// </summary>
 293    public bool TimeExecution { get; set; }
 294
 295    /// <summary>
 296    /// Whether to write queries to <see cref="Tracer"/> during execution.
 297    /// </summary>
 298    public bool Trace { get; set; }
 299
 300    /// <summary>
 301    /// The delegate responsible for writing trace lines.
 302    /// </summary>
 303    /// <value>The tracer.</value>
 304    public Action<string> Tracer { get; set; }
 305
 306    /// <summary>
 307    /// Whether to store DateTime properties as ticks (true) or strings (false).
 308    /// </summary>
 309    public bool StoreDateTimeAsTicks { get; private set; }
 310
 311    /// <summary>
 312    /// Whether to store TimeSpan properties as ticks (true) or strings (false).
 313    /// </summary>
 314    public bool StoreTimeSpanAsTicks { get; private set; }
 315
 316    /// <summary>
 317    /// The format to use when storing DateTime properties as strings. Ignored if StoreDateTimeAsTicks is true.
 318    /// </summary>
 319    /// <value>The date time string format.</value>
 320    public string DateTimeStringFormat { get; private set; }
 321
 322    /// <summary>
 323    /// The DateTimeStyles value to use when parsing a DateTime property string.
 324    /// </summary>
 325    /// <value>The date time style.</value>
 326    internal System.Globalization.DateTimeStyles DateTimeStyle { get; private set; }
 327
 328#if USE_SQLITEPCL_RAW && !NO_SQLITEPCL_RAW_BATTERIES
 329    static SQLiteConnection ()
 330    {
 331      SQLitePCL.Batteries_V2.Init ();
 332    }
 333#endif
 334
 335    /// <summary>
 336    /// Constructs a new SQLiteConnection and opens a SQLite database specified by databasePath.
 337    /// </summary>
 338    /// <param name="databasePath">
 339    /// Specifies the path to the database file.
 340    /// </param>
 341    /// <param name="storeDateTimeAsTicks">
 342    /// Specifies whether to store DateTime properties as ticks (true) or strings (false). You
 343    /// absolutely do want to store them as Ticks in all new projects. The value of false is
 344    /// only here for backwards compatibility. There is a *significant* speed advantage, with no
 345    /// down sides, when setting storeDateTimeAsTicks = true.
 346    /// If you use DateTimeOffset properties, it will be always stored as ticks regardingless
 347    /// the storeDateTimeAsTicks parameter.
 348    /// </param>
 349    public SQLiteConnection (string databasePath, bool storeDateTimeAsTicks = true)
 350      : this (new SQLiteConnectionString (databasePath, SQLiteOpenFlags.ReadWrite | SQLiteOpenFlags.Create, storeDateTim
 351    {
 352    }
 353
 354    /// <summary>
 355    /// Constructs a new SQLiteConnection and opens a SQLite database specified by databasePath.
 356    /// </summary>
 357    /// <param name="databasePath">
 358    /// Specifies the path to the database file.
 359    /// </param>
 360    /// <param name="openFlags">
 361    /// Flags controlling how the connection should be opened.
 362    /// </param>
 363    /// <param name="storeDateTimeAsTicks">
 364    /// Specifies whether to store DateTime properties as ticks (true) or strings (false). You
 365    /// absolutely do want to store them as Ticks in all new projects. The value of false is
 366    /// only here for backwards compatibility. There is a *significant* speed advantage, with no
 367    /// down sides, when setting storeDateTimeAsTicks = true.
 368    /// If you use DateTimeOffset properties, it will be always stored as ticks regardingless
 369    /// the storeDateTimeAsTicks parameter.
 370    /// </param>
 371    public SQLiteConnection (string databasePath, SQLiteOpenFlags openFlags, bool storeDateTimeAsTicks = true)
 372      : this (new SQLiteConnectionString (databasePath, openFlags, storeDateTimeAsTicks))
 373    {
 374    }
 375
 376    /// <summary>
 377    /// Constructs a new SQLiteConnection and opens a SQLite database specified by databasePath.
 378    /// </summary>
 379    /// <param name="connectionString">
 380    /// Details on how to find and open the database.
 381    /// </param>
 382    public SQLiteConnection (SQLiteConnectionString connectionString)
 383    {
 384      if (connectionString == null)
 385        throw new ArgumentNullException (nameof (connectionString));
 386      if (connectionString.DatabasePath == null)
 387        throw new InvalidOperationException ("DatabasePath must be specified");
 388
 389      DatabasePath = connectionString.DatabasePath;
 390
 391      LibVersionNumber = SQLite3.LibVersionNumber ();
 392
 393#if NETFX_CORE
 394      SQLite3.SetDirectory(/*temp directory type*/2, Windows.Storage.ApplicationData.Current.TemporaryFolder.Path);
 395#endif
 396
 397      Sqlite3DatabaseHandle handle;
 398
 399#if SILVERLIGHT || USE_CSHARP_SQLITE || USE_SQLITEPCL_RAW
 400      var r = SQLite3.Open (connectionString.DatabasePath, out handle, (int)connectionString.OpenFlags, connectionString
 401#else
 402      // open using the byte[]
 403      // in the case where the path may include Unicode
 404      // force open to using UTF-8 using sqlite3_open_v2
 405      var databasePathAsBytes = GetNullTerminatedUtf8 (connectionString.DatabasePath);
 406      var r = SQLite3.Open (databasePathAsBytes, out handle, (int)connectionString.OpenFlags, connectionString.VfsName);
 407#endif
 408
 409      Handle = handle;
 410      if (r != SQLite3.Result.OK) {
 411        throw SQLiteException.New (r, String.Format ("Could not open database file: {0} ({1})", DatabasePath, r));
 412      }
 413      _open = true;
 414
 415      StoreDateTimeAsTicks = connectionString.StoreDateTimeAsTicks;
 416      StoreTimeSpanAsTicks = connectionString.StoreTimeSpanAsTicks;
 417      DateTimeStringFormat = connectionString.DateTimeStringFormat;
 418      DateTimeStyle = connectionString.DateTimeStyle;
 419
 420      BusyTimeout = TimeSpan.FromSeconds (1.0);
 421      Tracer = line => Debug.WriteLine (line);
 422
 423      connectionString.PreKeyAction?.Invoke (this);
 424      if (connectionString.Key is string stringKey) {
 425        SetKey (stringKey);
 426      }
 427      else if (connectionString.Key is byte[] bytesKey) {
 428        SetKey (bytesKey);
 429      }
 430      else if (connectionString.Key != null) {
 431        throw new InvalidOperationException ("Encryption keys must be strings or byte arrays");
 432      }
 433      connectionString.PostKeyAction?.Invoke (this);
 434    }
 435
 436    /// <summary>
 437    /// Enables the write ahead logging. WAL is significantly faster in most scenarios
 438    /// by providing better concurrency and better disk IO performance than the normal
 439    /// journal mode. You only need to call this function once in the lifetime of the database.
 440    /// </summary>
 441    public void EnableWriteAheadLogging ()
 442    {
 443      ExecuteScalar<string> ("PRAGMA journal_mode=WAL");
 444    }
 445
 446    /// <summary>
 447    /// Convert an input string to a quoted SQL string that can be safely used in queries.
 448    /// </summary>
 449    /// <returns>The quoted string.</returns>
 450    /// <param name="unsafeString">The unsafe string to quote.</param>
 451    static string Quote (string unsafeString)
 452    {
 453      // TODO: Doesn't call sqlite3_mprintf("%Q", u) because we're waiting on https://github.com/ericsink/SQLitePCL.raw/
 454      if (unsafeString == null)
 455        return "NULL";
 456      var safe = unsafeString.Replace ("'", "''");
 457      return "'" + safe + "'";
 458    }
 459
 460    /// <summary>
 461    /// Sets the key used to encrypt/decrypt the database with "pragma key = ...".
 462    /// This must be the first thing you call before doing anything else with this connection
 463    /// if your database is encrypted.
 464    /// This only has an effect if you are using the SQLCipher nuget package.
 465    /// </summary>
 466    /// <param name="key">Encryption key plain text that is converted to the real encryption key using PBKDF2 key deriva
 467    void SetKey (string key)
 468    {
 469      if (key == null)
 470        throw new ArgumentNullException (nameof (key));
 471      var q = Quote (key);
 472      ExecuteScalar<string> ("pragma key = " + q);
 473    }
 474
 475    /// <summary>
 476    /// Sets the key used to encrypt/decrypt the database.
 477    /// This must be the first thing you call before doing anything else with this connection
 478    /// if your database is encrypted.
 479    /// This only has an effect if you are using the SQLCipher nuget package.
 480    /// </summary>
 481    /// <param name="key">256-bit (32 byte) encryption key data</param>
 482    void SetKey (byte[] key)
 483    {
 484      if (key == null)
 485        throw new ArgumentNullException (nameof (key));
 486      if (key.Length != 32 && key.Length != 48)
 487        throw new ArgumentException ("Key must be 32 bytes (256-bit) or 48 bytes (384-bit)", nameof (key));
 488      var s = String.Join ("", key.Select (x => x.ToString ("X2")));
 489      ExecuteScalar<string> ("pragma key = \"x'" + s + "'\"");
 490    }
 491
 492    /// <summary>
 493    /// Change the encryption key for a SQLCipher database with "pragma rekey = ...".
 494    /// </summary>
 495    /// <param name="key">Encryption key plain text that is converted to the real encryption key using PBKDF2 key deriva
 496    public void ReKey (string key)
 497    {
 498      if (key == null)
 499        throw new ArgumentNullException(nameof(key));
 500      var q = Quote(key);
 501      ExecuteScalar<string>("pragma rekey = " + q);
 502    }
 503
 504    /// <summary>
 505    /// Change the encryption key for a SQLCipher database.
 506    /// </summary>
 507    /// <param name="key">256-bit (32 byte) or 384-bit (48 bytes) encryption key data</param>
 508    public void ReKey (byte[] key)
 509    {
 510      if (key == null)
 511        throw new ArgumentNullException(nameof(key));
 512      if (key.Length != 32 && key.Length != 48)
 513        throw new ArgumentException ("Key must be 32 bytes (256-bit) or 48 bytes (384-bit)", nameof (key));
 514      var s = String.Join("", key.Select(x => x.ToString("X2")));
 515      ExecuteScalar<string>("pragma rekey = \"x'" + s + "'\"");
 516    }
 517
 518    /// <summary>
 519    /// Enable or disable extension loading.
 520    /// </summary>
 521    public void EnableLoadExtension (bool enabled)
 522    {
 523      SQLite3.Result r = SQLite3.EnableLoadExtension (Handle, enabled ? 1 : 0);
 524      if (r != SQLite3.Result.OK) {
 525        string msg = SQLite3.GetErrmsg (Handle);
 526        throw SQLiteException.New (r, msg);
 527      }
 528    }
 529
 530#if !USE_SQLITEPCL_RAW
 531    static byte[] GetNullTerminatedUtf8 (string s)
 532    {
 533      var utf8Length = System.Text.Encoding.UTF8.GetByteCount (s);
 534      var bytes = new byte [utf8Length + 1];
 535      utf8Length = System.Text.Encoding.UTF8.GetBytes(s, 0, s.Length, bytes, 0);
 536      return bytes;
 537    }
 538#endif
 539
 540    /// <summary>
 541    /// Sets a busy handler to sleep the specified amount of time when a table is locked.
 542    /// The handler will sleep multiple times until a total time of <see cref="BusyTimeout"/> has accumulated.
 543    /// </summary>
 544    public TimeSpan BusyTimeout {
 545      get { return _busyTimeout; }
 546      set {
 547        _busyTimeout = value;
 548        if (Handle != NullHandle) {
 549          SQLite3.BusyTimeout (Handle, (int)_busyTimeout.TotalMilliseconds);
 550        }
 551      }
 552    }
 553
 554    /// <summary>
 555    /// Returns the mappings from types to tables that the connection
 556    /// currently understands.
 557    /// </summary>
 558    public IEnumerable<TableMapping> TableMappings {
 559      get {
 560        lock (_mappings) {
 561          return new List<TableMapping> (_mappings.Values);
 562        }
 563      }
 564    }
 565
 566    /// <summary>
 567    /// Retrieves the mapping that is automatically generated for the given type.
 568    /// </summary>
 569    /// <param name="type">
 570    /// The type whose mapping to the database is returned.
 571    /// </param>
 572    /// <param name="createFlags">
 573    /// Optional flags allowing implicit PK and indexes based on naming conventions
 574    /// </param>
 575    /// <returns>
 576    /// The mapping represents the schema of the columns of the database and contains
 577    /// methods to set and get properties of objects.
 578    /// </returns>
 579    public TableMapping GetMapping (Type type, CreateFlags createFlags = CreateFlags.None)
 580    {
 581      TableMapping map;
 582      var key = type.FullName;
 583      lock (_mappings) {
 584        if (_mappings.TryGetValue (key, out map)) {
 585          if (createFlags != CreateFlags.None && createFlags != map.CreateFlags) {
 586            map = new TableMapping (type, createFlags);
 587            _mappings[key] = map;
 588          }
 589        }
 590        else {
 591          map = new TableMapping (type, createFlags);
 592          _mappings.Add (key, map);
 593        }
 594      }
 595      return map;
 596    }
 597
 598    /// <summary>
 599    /// Retrieves the mapping that is automatically generated for the given type.
 600    /// </summary>
 601    /// <param name="createFlags">
 602    /// Optional flags allowing implicit PK and indexes based on naming conventions
 603    /// </param>
 604    /// <returns>
 605    /// The mapping represents the schema of the columns of the database and contains
 606    /// methods to set and get properties of objects.
 607    /// </returns>
 608    public TableMapping GetMapping<T> (CreateFlags createFlags = CreateFlags.None)
 609    {
 610      return GetMapping (typeof (T), createFlags);
 611    }
 612
 613    private struct IndexedColumn
 614    {
 615      public int Order;
 616      public string ColumnName;
 617    }
 618
 619    private struct IndexInfo
 620    {
 621      public string IndexName;
 622      public string TableName;
 623      public bool Unique;
 624      public List<IndexedColumn> Columns;
 625    }
 626
 627    /// <summary>
 628    /// Executes a "drop table" on the database.  This is non-recoverable.
 629    /// </summary>
 630    public int DropTable<T> ()
 631    {
 632      return DropTable (GetMapping (typeof (T)));
 633    }
 634
 635    /// <summary>
 636    /// Executes a "drop table" on the database.  This is non-recoverable.
 637    /// </summary>
 638    /// <param name="map">
 639    /// The TableMapping used to identify the table.
 640    /// </param>
 641    public int DropTable (TableMapping map)
 642    {
 643      var query = string.Format ("drop table if exists \"{0}\"", map.TableName);
 644      return Execute (query);
 645    }
 646
 647    /// <summary>
 648    /// Executes a "create table if not exists" on the database. It also
 649    /// creates any specified indexes on the columns of the table. It uses
 650    /// a schema automatically generated from the specified type. You can
 651    /// later access this schema by calling GetMapping.
 652    /// </summary>
 653    /// <returns>
 654    /// Whether the table was created or migrated.
 655    /// </returns>
 656    public CreateTableResult CreateTable<T> (CreateFlags createFlags = CreateFlags.None)
 657    {
 658      return CreateTable (typeof (T), createFlags);
 659    }
 660
 661    /// <summary>
 662    /// Executes a "create table if not exists" on the database. It also
 663    /// creates any specified indexes on the columns of the table. It uses
 664    /// a schema automatically generated from the specified type. You can
 665    /// later access this schema by calling GetMapping.
 666    /// </summary>
 667    /// <param name="ty">Type to reflect to a database table.</param>
 668    /// <param name="createFlags">Optional flags allowing implicit PK and indexes based on naming conventions.</param>
 669    /// <returns>
 670    /// Whether the table was created or migrated.
 671    /// </returns>
 672    public CreateTableResult CreateTable (Type ty, CreateFlags createFlags = CreateFlags.None)
 673    {
 674      var map = GetMapping (ty, createFlags);
 675
 676      // Present a nice error if no columns specified
 677      if (map.Columns.Length == 0) {
 678        throw new Exception (string.Format ("Cannot create a table without columns (does '{0}' have public properties?)"
 679      }
 680
 681      // Check if the table exists
 682      var result = CreateTableResult.Created;
 683      var existingCols = GetTableInfo (map.TableName);
 684
 685      // Create or migrate it
 686      if (existingCols.Count == 0) {
 687
 688        // Facilitate virtual tables a.k.a. full-text search.
 689        bool fts3 = (createFlags & CreateFlags.FullTextSearch3) != 0;
 690        bool fts4 = (createFlags & CreateFlags.FullTextSearch4) != 0;
 691        bool fts = fts3 || fts4;
 692        var @virtual = fts ? "virtual " : string.Empty;
 693        var @using = fts3 ? "using fts3 " : fts4 ? "using fts4 " : string.Empty;
 694
 695        // Build query.
 696        var query = "create " + @virtual + "table if not exists \"" + map.TableName + "\" " + @using + "(\n";
 697        var decls = map.Columns.Select (p => Orm.SqlDecl (p, StoreDateTimeAsTicks, StoreTimeSpanAsTicks));
 698        var decl = string.Join (",\n", decls.ToArray ());
 699        query += decl;
 700        query += ")";
 701        if (map.WithoutRowId) {
 702          query += " without rowid";
 703        }
 704
 705        Execute (query);
 706      }
 707      else {
 708        result = CreateTableResult.Migrated;
 709        MigrateTable (map, existingCols);
 710      }
 711
 712      var indexes = new Dictionary<string, IndexInfo> ();
 713      foreach (var c in map.Columns) {
 714        foreach (var i in c.Indices) {
 715          var iname = i.Name ?? map.TableName + "_" + c.Name;
 716          IndexInfo iinfo;
 717          if (!indexes.TryGetValue (iname, out iinfo)) {
 718            iinfo = new IndexInfo {
 719              IndexName = iname,
 720              TableName = map.TableName,
 721              Unique = i.Unique,
 722              Columns = new List<IndexedColumn> ()
 723            };
 724            indexes.Add (iname, iinfo);
 725          }
 726
 727          if (i.Unique != iinfo.Unique)
 728            throw new Exception ("All the columns in an index must have the same value for their Unique property");
 729
 730          iinfo.Columns.Add (new IndexedColumn {
 731            Order = i.Order,
 732            ColumnName = c.Name
 733          });
 734        }
 735      }
 736
 737      foreach (var indexName in indexes.Keys) {
 738        var index = indexes[indexName];
 739        var columns = index.Columns.OrderBy (i => i.Order).Select (i => i.ColumnName).ToArray ();
 740        CreateIndex (indexName, index.TableName, columns, index.Unique);
 741      }
 742
 743      return result;
 744    }
 745
 746    /// <summary>
 747    /// Executes a "create table if not exists" on the database for each type. It also
 748    /// creates any specified indexes on the columns of the table. It uses
 749    /// a schema automatically generated from the specified type. You can
 750    /// later access this schema by calling GetMapping.
 751    /// </summary>
 752    /// <returns>
 753    /// Whether the table was created or migrated for each type.
 754    /// </returns>
 755    public CreateTablesResult CreateTables<T, T2> (CreateFlags createFlags = CreateFlags.None)
 756      where T : new()
 757      where T2 : new()
 758    {
 759      return CreateTables (createFlags, typeof (T), typeof (T2));
 760    }
 761
 762    /// <summary>
 763    /// Executes a "create table if not exists" on the database for each type. It also
 764    /// creates any specified indexes on the columns of the table. It uses
 765    /// a schema automatically generated from the specified type. You can
 766    /// later access this schema by calling GetMapping.
 767    /// </summary>
 768    /// <returns>
 769    /// Whether the table was created or migrated for each type.
 770    /// </returns>
 771    public CreateTablesResult CreateTables<T, T2, T3> (CreateFlags createFlags = CreateFlags.None)
 772      where T : new()
 773      where T2 : new()
 774      where T3 : new()
 775    {
 776      return CreateTables (createFlags, typeof (T), typeof (T2), typeof (T3));
 777    }
 778
 779    /// <summary>
 780    /// Executes a "create table if not exists" on the database for each type. It also
 781    /// creates any specified indexes on the columns of the table. It uses
 782    /// a schema automatically generated from the specified type. You can
 783    /// later access this schema by calling GetMapping.
 784    /// </summary>
 785    /// <returns>
 786    /// Whether the table was created or migrated for each type.
 787    /// </returns>
 788    public CreateTablesResult CreateTables<T, T2, T3, T4> (CreateFlags createFlags = CreateFlags.None)
 789      where T : new()
 790      where T2 : new()
 791      where T3 : new()
 792      where T4 : new()
 793    {
 794      return CreateTables (createFlags, typeof (T), typeof (T2), typeof (T3), typeof (T4));
 795    }
 796
 797    /// <summary>
 798    /// Executes a "create table if not exists" on the database for each type. It also
 799    /// creates any specified indexes on the columns of the table. It uses
 800    /// a schema automatically generated from the specified type. You can
 801    /// later access this schema by calling GetMapping.
 802    /// </summary>
 803    /// <returns>
 804    /// Whether the table was created or migrated for each type.
 805    /// </returns>
 806    public CreateTablesResult CreateTables<T, T2, T3, T4, T5> (CreateFlags createFlags = CreateFlags.None)
 807      where T : new()
 808      where T2 : new()
 809      where T3 : new()
 810      where T4 : new()
 811      where T5 : new()
 812    {
 813      return CreateTables (createFlags, typeof (T), typeof (T2), typeof (T3), typeof (T4), typeof (T5));
 814    }
 815
 816    /// <summary>
 817    /// Executes a "create table if not exists" on the database for each type. It also
 818    /// creates any specified indexes on the columns of the table. It uses
 819    /// a schema automatically generated from the specified type. You can
 820    /// later access this schema by calling GetMapping.
 821    /// </summary>
 822    /// <returns>
 823    /// Whether the table was created or migrated for each type.
 824    /// </returns>
 825    public CreateTablesResult CreateTables (CreateFlags createFlags = CreateFlags.None, params Type[] types)
 826    {
 827      var result = new CreateTablesResult ();
 828      foreach (Type type in types) {
 829        var aResult = CreateTable (type, createFlags);
 830        result.Results[type] = aResult;
 831      }
 832      return result;
 833    }
 834
 835    /// <summary>
 836    /// Creates an index for the specified table and columns.
 837    /// </summary>
 838    /// <param name="indexName">Name of the index to create</param>
 839    /// <param name="tableName">Name of the database table</param>
 840    /// <param name="columnNames">An array of column names to index</param>
 841    /// <param name="unique">Whether the index should be unique</param>
 842    /// <returns>Zero on success.</returns>
 843    public int CreateIndex (string indexName, string tableName, string[] columnNames, bool unique = false)
 844    {
 845      const string sqlFormat = "create {2} index if not exists \"{3}\" on \"{0}\"(\"{1}\")";
 846      var sql = String.Format (sqlFormat, tableName, string.Join ("\", \"", columnNames), unique ? "unique" : "", indexN
 847      return Execute (sql);
 848    }
 849
 850    /// <summary>
 851    /// Creates an index for the specified table and column.
 852    /// </summary>
 853    /// <param name="indexName">Name of the index to create</param>
 854    /// <param name="tableName">Name of the database table</param>
 855    /// <param name="columnName">Name of the column to index</param>
 856    /// <param name="unique">Whether the index should be unique</param>
 857    /// <returns>Zero on success.</returns>
 858    public int CreateIndex (string indexName, string tableName, string columnName, bool unique = false)
 859    {
 860      return CreateIndex (indexName, tableName, new string[] { columnName }, unique);
 861    }
 862
 863    /// <summary>
 864    /// Creates an index for the specified table and column.
 865    /// </summary>
 866    /// <param name="tableName">Name of the database table</param>
 867    /// <param name="columnName">Name of the column to index</param>
 868    /// <param name="unique">Whether the index should be unique</param>
 869    /// <returns>Zero on success.</returns>
 870    public int CreateIndex (string tableName, string columnName, bool unique = false)
 871    {
 872      return CreateIndex (tableName + "_" + columnName, tableName, columnName, unique);
 873    }
 874
 875    /// <summary>
 876    /// Creates an index for the specified table and columns.
 877    /// </summary>
 878    /// <param name="tableName">Name of the database table</param>
 879    /// <param name="columnNames">An array of column names to index</param>
 880    /// <param name="unique">Whether the index should be unique</param>
 881    /// <returns>Zero on success.</returns>
 882    public int CreateIndex (string tableName, string[] columnNames, bool unique = false)
 883    {
 884      return CreateIndex (tableName + "_" + string.Join ("_", columnNames), tableName, columnNames, unique);
 885    }
 886
 887    /// <summary>
 888    /// Creates an index for the specified object property.
 889    /// e.g. CreateIndex&lt;Client&gt;(c => c.Name);
 890    /// </summary>
 891    /// <typeparam name="T">Type to reflect to a database table.</typeparam>
 892    /// <param name="property">Property to index</param>
 893    /// <param name="unique">Whether the index should be unique</param>
 894    /// <returns>Zero on success.</returns>
 895    public int CreateIndex<T> (Expression<Func<T, object>> property, bool unique = false)
 896    {
 897      MemberExpression mx;
 898      if (property.Body.NodeType == ExpressionType.Convert) {
 899        mx = ((UnaryExpression)property.Body).Operand as MemberExpression;
 900      }
 901      else {
 902        mx = (property.Body as MemberExpression);
 903      }
 904      var propertyInfo = mx.Member as PropertyInfo;
 905      if (propertyInfo == null) {
 906        throw new ArgumentException ("The lambda expression 'property' should point to a valid Property");
 907      }
 908
 909      var propName = propertyInfo.Name;
 910
 911      var map = GetMapping<T> ();
 912      var colName = map.FindColumnWithPropertyName (propName).Name;
 913
 914      return CreateIndex (map.TableName, colName, unique);
 915    }
 916
 917    [Preserve (AllMembers = true)]
 918    public class ColumnInfo
 919    {
 920      //      public int cid { get; set; }
 921
 922      [Column ("name")]
 923      public string Name { get; set; }
 924
 925      //      [Column ("type")]
 926      //      public string ColumnType { get; set; }
 927
 928      public int notnull { get; set; }
 929
 930      //      public string dflt_value { get; set; }
 931
 932      //      public int pk { get; set; }
 933
 934      public override string ToString ()
 935      {
 936        return Name;
 937      }
 938    }
 939
 940    /// <summary>
 941    /// Query the built-in sqlite table_info table for a specific tables columns.
 942    /// </summary>
 943    /// <returns>The columns contains in the table.</returns>
 944    /// <param name="tableName">Table name.</param>
 945    public List<ColumnInfo> GetTableInfo (string tableName)
 946    {
 947      var query = "pragma table_info(\"" + tableName + "\")";
 948      return Query<ColumnInfo> (query);
 949    }
 950
 951    void MigrateTable (TableMapping map, List<ColumnInfo> existingCols)
 952    {
 953      var toBeAdded = new List<TableMapping.Column> ();
 954
 955      foreach (var p in map.Columns) {
 956        var found = false;
 957        foreach (var c in existingCols) {
 958          found = (string.Compare (p.Name, c.Name, StringComparison.OrdinalIgnoreCase) == 0);
 959          if (found)
 960            break;
 961        }
 962        if (!found) {
 963          toBeAdded.Add (p);
 964        }
 965      }
 966
 967      foreach (var p in toBeAdded) {
 968        var addCol = "alter table \"" + map.TableName + "\" add column " + Orm.SqlDecl (p, StoreDateTimeAsTicks, StoreTi
 969        Execute (addCol);
 970      }
 971    }
 972
 973    /// <summary>
 974    /// Creates a new SQLiteCommand. Can be overridden to provide a sub-class.
 975    /// </summary>
 976    /// <seealso cref="SQLiteCommand.OnInstanceCreated"/>
 977    protected virtual SQLiteCommand NewCommand ()
 978    {
 979      return new SQLiteCommand (this);
 980    }
 981
 982    /// <summary>
 983    /// Creates a new SQLiteCommand given the command text with arguments. Place a '?'
 984    /// in the command text for each of the arguments.
 985    /// </summary>
 986    /// <param name="cmdText">
 987    /// The fully escaped SQL.
 988    /// </param>
 989    /// <param name="ps">
 990    /// Arguments to substitute for the occurences of '?' in the command text.
 991    /// </param>
 992    /// <returns>
 993    /// A <see cref="SQLiteCommand"/>
 994    /// </returns>
 995    public SQLiteCommand CreateCommand (string cmdText, params object[] ps)
 996    {
 997      if (!_open)
 998        throw SQLiteException.New (SQLite3.Result.Error, "Cannot create commands from unopened database");
 999
 1000      var cmd = NewCommand ();
 1001      cmd.CommandText = cmdText;
 1002      foreach (var o in ps) {
 1003        cmd.Bind (o);
 1004      }
 1005      return cmd;
 1006    }
 1007
 1008    /// <summary>
 1009    /// Creates a new SQLiteCommand given the command text with named arguments. Place a "[@:$]VVV"
 1010    /// in the command text for each of the arguments. VVV represents an alphanumeric identifier.
 1011    /// For example, @name :name and $name can all be used in the query.
 1012    /// </summary>
 1013    /// <param name="cmdText">
 1014    /// The fully escaped SQL.
 1015    /// </param>
 1016    /// <param name="args">
 1017    /// Arguments to substitute for the occurences of "[@:$]VVV" in the command text.
 1018    /// </param>
 1019    /// <returns>
 1020    /// A <see cref="SQLiteCommand" />
 1021    /// </returns>
 1022    public SQLiteCommand CreateCommand (string cmdText, Dictionary<string, object> args)
 1023    {
 1024      if (!_open)
 1025        throw SQLiteException.New (SQLite3.Result.Error, "Cannot create commands from unopened database");
 1026
 1027      SQLiteCommand cmd = NewCommand ();
 1028      cmd.CommandText = cmdText;
 1029      foreach (var kv in args) {
 1030        cmd.Bind (kv.Key, kv.Value);
 1031      }
 1032      return cmd;
 1033    }
 1034
 1035    /// <summary>
 1036    /// Creates a SQLiteCommand given the command text (SQL) with arguments. Place a '?'
 1037    /// in the command text for each of the arguments and then executes that command.
 1038    /// Use this method instead of Query when you don't expect rows back. Such cases include
 1039    /// INSERTs, UPDATEs, and DELETEs.
 1040    /// You can set the Trace or TimeExecution properties of the connection
 1041    /// to profile execution.
 1042    /// </summary>
 1043    /// <param name="query">
 1044    /// The fully escaped SQL.
 1045    /// </param>
 1046    /// <param name="args">
 1047    /// Arguments to substitute for the occurences of '?' in the query.
 1048    /// </param>
 1049    /// <returns>
 1050    /// The number of rows modified in the database as a result of this execution.
 1051    /// </returns>
 1052    public int Execute (string query, params object[] args)
 1053    {
 1054      var cmd = CreateCommand (query, args);
 1055
 1056      if (TimeExecution) {
 1057        if (_sw == null) {
 1058          _sw = new Stopwatch ();
 1059        }
 1060        _sw.Reset ();
 1061        _sw.Start ();
 1062      }
 1063
 1064      var r = cmd.ExecuteNonQuery ();
 1065
 1066      if (TimeExecution) {
 1067        _sw.Stop ();
 1068        _elapsedMilliseconds += _sw.ElapsedMilliseconds;
 1069        Tracer?.Invoke (string.Format ("Finished in {0} ms ({1:0.0} s total)", _sw.ElapsedMilliseconds, _elapsedMillisec
 1070      }
 1071
 1072      return r;
 1073    }
 1074
 1075    /// <summary>
 1076    /// Creates a SQLiteCommand given the command text (SQL) with arguments. Place a '?'
 1077    /// in the command text for each of the arguments and then executes that command.
 1078    /// Use this method when return primitive values.
 1079    /// You can set the Trace or TimeExecution properties of the connection
 1080    /// to profile execution.
 1081    /// </summary>
 1082    /// <param name="query">
 1083    /// The fully escaped SQL.
 1084    /// </param>
 1085    /// <param name="args">
 1086    /// Arguments to substitute for the occurences of '?' in the query.
 1087    /// </param>
 1088    /// <returns>
 1089    /// The number of rows modified in the database as a result of this execution.
 1090    /// </returns>
 1091    public T ExecuteScalar<T> (string query, params object[] args)
 1092    {
 1093      var cmd = CreateCommand (query, args);
 1094
 1095      if (TimeExecution) {
 1096        if (_sw == null) {
 1097          _sw = new Stopwatch ();
 1098        }
 1099        _sw.Reset ();
 1100        _sw.Start ();
 1101      }
 1102
 1103      var r = cmd.ExecuteScalar<T> ();
 1104
 1105      if (TimeExecution) {
 1106        _sw.Stop ();
 1107        _elapsedMilliseconds += _sw.ElapsedMilliseconds;
 1108        Tracer?.Invoke (string.Format ("Finished in {0} ms ({1:0.0} s total)", _sw.ElapsedMilliseconds, _elapsedMillisec
 1109      }
 1110
 1111      return r;
 1112    }
 1113
 1114    /// <summary>
 1115    /// Creates a SQLiteCommand given the command text (SQL) with arguments. Place a '?'
 1116    /// in the command text for each of the arguments and then executes that command.
 1117    /// It returns each row of the result using the mapping automatically generated for
 1118    /// the given type.
 1119    /// </summary>
 1120    /// <param name="query">
 1121    /// The fully escaped SQL.
 1122    /// </param>
 1123    /// <param name="args">
 1124    /// Arguments to substitute for the occurences of '?' in the query.
 1125    /// </param>
 1126    /// <returns>
 1127    /// An enumerable with one result for each row returned by the query.
 1128    /// </returns>
 1129    public List<T> Query<T> (string query, params object[] args) where T : new()
 1130    {
 1131      var cmd = CreateCommand (query, args);
 1132      return cmd.ExecuteQuery<T> ();
 1133    }
 1134
 1135    /// <summary>
 1136    /// Creates a SQLiteCommand given the command text (SQL) with arguments. Place a '?'
 1137    /// in the command text for each of the arguments and then executes that command.
 1138    /// It returns the first column of each row of the result.
 1139    /// </summary>
 1140    /// <param name="query">
 1141    /// The fully escaped SQL.
 1142    /// </param>
 1143    /// <param name="args">
 1144    /// Arguments to substitute for the occurences of '?' in the query.
 1145    /// </param>
 1146    /// <returns>
 1147    /// An enumerable with one result for the first column of each row returned by the query.
 1148    /// </returns>
 1149    public List<T> QueryScalars<T> (string query, params object[] args)
 1150    {
 1151      var cmd = CreateCommand (query, args);
 1152      return cmd.ExecuteQueryScalars<T> ().ToList ();
 1153    }
 1154
 1155    /// <summary>
 1156    /// Creates a SQLiteCommand given the command text (SQL) with arguments. Place a '?'
 1157    /// in the command text for each of the arguments and then executes that command.
 1158    /// It returns each row of the result using the mapping automatically generated for
 1159    /// the given type.
 1160    /// </summary>
 1161    /// <param name="query">
 1162    /// The fully escaped SQL.
 1163    /// </param>
 1164    /// <param name="args">
 1165    /// Arguments to substitute for the occurences of '?' in the query.
 1166    /// </param>
 1167    /// <returns>
 1168    /// An enumerable with one result for each row returned by the query.
 1169    /// The enumerator (retrieved by calling GetEnumerator() on the result of this method)
 1170    /// will call sqlite3_step on each call to MoveNext, so the database
 1171    /// connection must remain open for the lifetime of the enumerator.
 1172    /// </returns>
 1173    public IEnumerable<T> DeferredQuery<T> (string query, params object[] args) where T : new()
 1174    {
 1175      var cmd = CreateCommand (query, args);
 1176      return cmd.ExecuteDeferredQuery<T> ();
 1177    }
 1178
 1179    /// <summary>
 1180    /// Creates a SQLiteCommand given the command text (SQL) with arguments. Place a '?'
 1181    /// in the command text for each of the arguments and then executes that command.
 1182    /// It returns each row of the result using the specified mapping. This function is
 1183    /// only used by libraries in order to query the database via introspection. It is
 1184    /// normally not used.
 1185    /// </summary>
 1186    /// <param name="map">
 1187    /// A <see cref="TableMapping"/> to use to convert the resulting rows
 1188    /// into objects.
 1189    /// </param>
 1190    /// <param name="query">
 1191    /// The fully escaped SQL.
 1192    /// </param>
 1193    /// <param name="args">
 1194    /// Arguments to substitute for the occurences of '?' in the query.
 1195    /// </param>
 1196    /// <returns>
 1197    /// An enumerable with one result for each row returned by the query.
 1198    /// </returns>
 1199    public List<object> Query (TableMapping map, string query, params object[] args)
 1200    {
 1201      var cmd = CreateCommand (query, args);
 1202      return cmd.ExecuteQuery<object> (map);
 1203    }
 1204
 1205    /// <summary>
 1206    /// Creates a SQLiteCommand given the command text (SQL) with arguments. Place a '?'
 1207    /// in the command text for each of the arguments and then executes that command.
 1208    /// It returns each row of the result using the specified mapping. This function is
 1209    /// only used by libraries in order to query the database via introspection. It is
 1210    /// normally not used.
 1211    /// </summary>
 1212    /// <param name="map">
 1213    /// A <see cref="TableMapping"/> to use to convert the resulting rows
 1214    /// into objects.
 1215    /// </param>
 1216    /// <param name="query">
 1217    /// The fully escaped SQL.
 1218    /// </param>
 1219    /// <param name="args">
 1220    /// Arguments to substitute for the occurences of '?' in the query.
 1221    /// </param>
 1222    /// <returns>
 1223    /// An enumerable with one result for each row returned by the query.
 1224    /// The enumerator (retrieved by calling GetEnumerator() on the result of this method)
 1225    /// will call sqlite3_step on each call to MoveNext, so the database
 1226    /// connection must remain open for the lifetime of the enumerator.
 1227    /// </returns>
 1228    public IEnumerable<object> DeferredQuery (TableMapping map, string query, params object[] args)
 1229    {
 1230      var cmd = CreateCommand (query, args);
 1231      return cmd.ExecuteDeferredQuery<object> (map);
 1232    }
 1233
 1234    /// <summary>
 1235    /// Returns a queryable interface to the table represented by the given type.
 1236    /// </summary>
 1237    /// <returns>
 1238    /// A queryable object that is able to translate Where, OrderBy, and Take
 1239    /// queries into native SQL.
 1240    /// </returns>
 1241    public TableQuery<T> Table<T> () where T : new()
 1242    {
 1243      return new TableQuery<T> (this);
 1244    }
 1245
 1246    /// <summary>
 1247    /// Attempts to retrieve an object with the given primary key from the table
 1248    /// associated with the specified type. Use of this method requires that
 1249    /// the given type have a designated PrimaryKey (using the PrimaryKeyAttribute).
 1250    /// </summary>
 1251    /// <param name="pk">
 1252    /// The primary key.
 1253    /// </param>
 1254    /// <returns>
 1255    /// The object with the given primary key. Throws a not found exception
 1256    /// if the object is not found.
 1257    /// </returns>
 1258    public T Get<T> (object pk) where T : new()
 1259    {
 1260      var map = GetMapping (typeof (T));
 1261      return Query<T> (map.GetByPrimaryKeySql, pk).First ();
 1262    }
 1263
 1264    /// <summary>
 1265    /// Attempts to retrieve an object with the given primary key from the table
 1266    /// associated with the specified type. Use of this method requires that
 1267    /// the given type have a designated PrimaryKey (using the PrimaryKeyAttribute).
 1268    /// </summary>
 1269    /// <param name="pk">
 1270    /// The primary key.
 1271    /// </param>
 1272    /// <param name="map">
 1273    /// The TableMapping used to identify the table.
 1274    /// </param>
 1275    /// <returns>
 1276    /// The object with the given primary key. Throws a not found exception
 1277    /// if the object is not found.
 1278    /// </returns>
 1279    public object Get (object pk, TableMapping map)
 1280    {
 1281      return Query (map, map.GetByPrimaryKeySql, pk).First ();
 1282    }
 1283
 1284    /// <summary>
 1285    /// Attempts to retrieve the first object that matches the predicate from the table
 1286    /// associated with the specified type.
 1287    /// </summary>
 1288    /// <param name="predicate">
 1289    /// A predicate for which object to find.
 1290    /// </param>
 1291    /// <returns>
 1292    /// The object that matches the given predicate. Throws a not found exception
 1293    /// if the object is not found.
 1294    /// </returns>
 1295    public T Get<T> (Expression<Func<T, bool>> predicate) where T : new()
 1296    {
 1297      return Table<T> ().Where (predicate).First ();
 1298    }
 1299
 1300    /// <summary>
 1301    /// Attempts to retrieve an object with the given primary key from the table
 1302    /// associated with the specified type. Use of this method requires that
 1303    /// the given type have a designated PrimaryKey (using the PrimaryKeyAttribute).
 1304    /// </summary>
 1305    /// <param name="pk">
 1306    /// The primary key.
 1307    /// </param>
 1308    /// <returns>
 1309    /// The object with the given primary key or null
 1310    /// if the object is not found.
 1311    /// </returns>
 1312    public T Find<T> (object pk) where T : new()
 1313    {
 1314      var map = GetMapping (typeof (T));
 1315      return Query<T> (map.GetByPrimaryKeySql, pk).FirstOrDefault ();
 1316    }
 1317
 1318    /// <summary>
 1319    /// Attempts to retrieve an object with the given primary key from the table
 1320    /// associated with the specified type. Use of this method requires that
 1321    /// the given type have a designated PrimaryKey (using the PrimaryKeyAttribute).
 1322    /// </summary>
 1323    /// <param name="pk">
 1324    /// The primary key.
 1325    /// </param>
 1326    /// <param name="map">
 1327    /// The TableMapping used to identify the table.
 1328    /// </param>
 1329    /// <returns>
 1330    /// The object with the given primary key or null
 1331    /// if the object is not found.
 1332    /// </returns>
 1333    public object Find (object pk, TableMapping map)
 1334    {
 1335      return Query (map, map.GetByPrimaryKeySql, pk).FirstOrDefault ();
 1336    }
 1337
 1338    /// <summary>
 1339    /// Attempts to retrieve the first object that matches the predicate from the table
 1340    /// associated with the specified type.
 1341    /// </summary>
 1342    /// <param name="predicate">
 1343    /// A predicate for which object to find.
 1344    /// </param>
 1345    /// <returns>
 1346    /// The object that matches the given predicate or null
 1347    /// if the object is not found.
 1348    /// </returns>
 1349    public T Find<T> (Expression<Func<T, bool>> predicate) where T : new()
 1350    {
 1351      return Table<T> ().Where (predicate).FirstOrDefault ();
 1352    }
 1353
 1354    /// <summary>
 1355    /// Attempts to retrieve the first object that matches the query from the table
 1356    /// associated with the specified type.
 1357    /// </summary>
 1358    /// <param name="query">
 1359    /// The fully escaped SQL.
 1360    /// </param>
 1361    /// <param name="args">
 1362    /// Arguments to substitute for the occurences of '?' in the query.
 1363    /// </param>
 1364    /// <returns>
 1365    /// The object that matches the given predicate or null
 1366    /// if the object is not found.
 1367    /// </returns>
 1368    public T FindWithQuery<T> (string query, params object[] args) where T : new()
 1369    {
 1370      return Query<T> (query, args).FirstOrDefault ();
 1371    }
 1372
 1373    /// <summary>
 1374    /// Attempts to retrieve the first object that matches the query from the table
 1375    /// associated with the specified type.
 1376    /// </summary>
 1377    /// <param name="map">
 1378    /// The TableMapping used to identify the table.
 1379    /// </param>
 1380    /// <param name="query">
 1381    /// The fully escaped SQL.
 1382    /// </param>
 1383    /// <param name="args">
 1384    /// Arguments to substitute for the occurences of '?' in the query.
 1385    /// </param>
 1386    /// <returns>
 1387    /// The object that matches the given predicate or null
 1388    /// if the object is not found.
 1389    /// </returns>
 1390    public object FindWithQuery (TableMapping map, string query, params object[] args)
 1391    {
 1392      return Query (map, query, args).FirstOrDefault ();
 1393    }
 1394
 1395    /// <summary>
 1396    /// Whether <see cref="BeginTransaction"/> has been called and the database is waiting for a <see cref="Commit"/>.
 1397    /// </summary>
 1398    public bool IsInTransaction {
 1399      get { return _transactionDepth > 0; }
 1400    }
 1401
 1402    /// <summary>
 1403    /// Begins a new transaction. Call <see cref="Commit"/> to end the transaction.
 1404    /// </summary>
 1405    /// <example cref="System.InvalidOperationException">Throws if a transaction has already begun.</example>
 1406    public void BeginTransaction ()
 1407    {
 1408      // The BEGIN command only works if the transaction stack is empty,
 1409      //    or in other words if there are no pending transactions.
 1410      // If the transaction stack is not empty when the BEGIN command is invoked,
 1411      //    then the command fails with an error.
 1412      // Rather than crash with an error, we will just ignore calls to BeginTransaction
 1413      //    that would result in an error.
 1414      if (Interlocked.CompareExchange (ref _transactionDepth, 1, 0) == 0) {
 1415        try {
 1416          Execute ("begin transaction");
 1417        }
 1418        catch (Exception ex) {
 1419          var sqlExp = ex as SQLiteException;
 1420          if (sqlExp != null) {
 1421            // It is recommended that applications respond to the errors listed below
 1422            //    by explicitly issuing a ROLLBACK command.
 1423            // TODO: This rollback failsafe should be localized to all throw sites.
 1424            switch (sqlExp.Result) {
 1425              case SQLite3.Result.IOError:
 1426              case SQLite3.Result.Full:
 1427              case SQLite3.Result.Busy:
 1428              case SQLite3.Result.NoMem:
 1429              case SQLite3.Result.Interrupt:
 1430                RollbackTo (null, true);
 1431                break;
 1432            }
 1433          }
 1434          else {
 1435            // Call decrement and not VolatileWrite in case we've already
 1436            //    created a transaction point in SaveTransactionPoint since the catch.
 1437            Interlocked.Decrement (ref _transactionDepth);
 1438          }
 1439
 1440          throw;
 1441        }
 1442      }
 1443      else {
 1444        // Calling BeginTransaction on an already open transaction is invalid
 1445        throw new InvalidOperationException ("Cannot begin a transaction while already in a transaction.");
 1446      }
 1447    }
 1448
 1449    /// <summary>
 1450    /// Creates a savepoint in the database at the current point in the transaction timeline.
 1451    /// Begins a new transaction if one is not in progress.
 1452    ///
 1453    /// Call <see cref="RollbackTo(string)"/> to undo transactions since the returned savepoint.
 1454    /// Call <see cref="Release"/> to commit transactions after the savepoint returned here.
 1455    /// Call <see cref="Commit"/> to end the transaction, committing all changes.
 1456    /// </summary>
 1457    /// <returns>A string naming the savepoint.</returns>
 1458    public string SaveTransactionPoint ()
 1459    {
 1460      int depth = Interlocked.Increment (ref _transactionDepth) - 1;
 1461      string retVal = "S" + _rand.Next (short.MaxValue) + "D" + depth;
 1462
 1463      try {
 1464        Execute ("savepoint " + retVal);
 1465      }
 1466      catch (Exception ex) {
 1467        var sqlExp = ex as SQLiteException;
 1468        if (sqlExp != null) {
 1469          // It is recommended that applications respond to the errors listed below
 1470          //    by explicitly issuing a ROLLBACK command.
 1471          // TODO: This rollback failsafe should be localized to all throw sites.
 1472          switch (sqlExp.Result) {
 1473            case SQLite3.Result.IOError:
 1474            case SQLite3.Result.Full:
 1475            case SQLite3.Result.Busy:
 1476            case SQLite3.Result.NoMem:
 1477            case SQLite3.Result.Interrupt:
 1478              RollbackTo (null, true);
 1479              break;
 1480          }
 1481        }
 1482        else {
 1483          Interlocked.Decrement (ref _transactionDepth);
 1484        }
 1485
 1486        throw;
 1487      }
 1488
 1489      return retVal;
 1490    }
 1491
 1492    /// <summary>
 1493    /// Rolls back the transaction that was begun by <see cref="BeginTransaction"/> or <see cref="SaveTransactionPoint"/
 1494    /// </summary>
 1495    public void Rollback ()
 1496    {
 1497      RollbackTo (null, false);
 1498    }
 1499
 1500    /// <summary>
 1501    /// Rolls back the savepoint created by <see cref="BeginTransaction"/> or SaveTransactionPoint.
 1502    /// </summary>
 1503    /// <param name="savepoint">The name of the savepoint to roll back to, as returned by <see cref="SaveTransactionPoin
 1504    public void RollbackTo (string savepoint)
 1505    {
 1506      RollbackTo (savepoint, false);
 1507    }
 1508
 1509    /// <summary>
 1510    /// Rolls back the transaction that was begun by <see cref="BeginTransaction"/>.
 1511    /// </summary>
 1512    /// <param name="savepoint">The name of the savepoint to roll back to, as returned by <see cref="SaveTransactionPoin
 1513    /// <param name="noThrow">true to avoid throwing exceptions, false otherwise</param>
 1514    void RollbackTo (string savepoint, bool noThrow)
 1515    {
 1516      // Rolling back without a TO clause rolls backs all transactions
 1517      //    and leaves the transaction stack empty.
 1518      try {
 1519        if (String.IsNullOrEmpty (savepoint)) {
 1520          if (Interlocked.Exchange (ref _transactionDepth, 0) > 0) {
 1521            Execute ("rollback");
 1522          }
 1523        }
 1524        else {
 1525          DoSavePointExecute (savepoint, "rollback to ");
 1526        }
 1527      }
 1528      catch (SQLiteException) {
 1529        if (!noThrow)
 1530          throw;
 1531
 1532      }
 1533      // No need to rollback if there are no transactions open.
 1534    }
 1535
 1536    /// <summary>
 1537    /// Releases a savepoint returned from <see cref="SaveTransactionPoint"/>.  Releasing a savepoint
 1538    ///    makes changes since that savepoint permanent if the savepoint began the transaction,
 1539    ///    or otherwise the changes are permanent pending a call to <see cref="Commit"/>.
 1540    ///
 1541    /// The RELEASE command is like a COMMIT for a SAVEPOINT.
 1542    /// </summary>
 1543    /// <param name="savepoint">The name of the savepoint to release.  The string should be the result of a call to <see
 1544    public void Release (string savepoint)
 1545    {
 1546      try {
 1547        DoSavePointExecute (savepoint, "release ");
 1548      }
 1549      catch (SQLiteException ex) {
 1550        if (ex.Result == SQLite3.Result.Busy) {
 1551          // Force a rollback since most people don't know this function can fail
 1552          // Don't call Rollback() since the _transactionDepth is 0 and it won't try
 1553          // Calling rollback makes our _transactionDepth variable correct.
 1554          // Writes to the database only happen at depth=0, so this failure will only happen then.
 1555          try {
 1556            Execute ("rollback");
 1557          }
 1558          catch {
 1559            // rollback can fail in all sorts of wonderful version-dependent ways. Let's just hope for the best
 1560          }
 1561        }
 1562        throw;
 1563      }
 1564    }
 1565
 1566    void DoSavePointExecute (string savepoint, string cmd)
 1567    {
 1568      // Validate the savepoint
 1569      int firstLen = savepoint.IndexOf ('D');
 1570      if (firstLen >= 2 && savepoint.Length > firstLen + 1) {
 1571        int depth;
 1572        if (Int32.TryParse (savepoint.Substring (firstLen + 1), out depth)) {
 1573          // TODO: Mild race here, but inescapable without locking almost everywhere.
 1574          if (0 <= depth && depth < _transactionDepth) {
 1575#if NETFX_CORE || USE_SQLITEPCL_RAW || NETCORE
 1576            Volatile.Write (ref _transactionDepth, depth);
 1577#elif SILVERLIGHT
 1578            _transactionDepth = depth;
 1579#else
 1580                        Thread.VolatileWrite (ref _transactionDepth, depth);
 1581#endif
 1582            Execute (cmd + savepoint);
 1583            return;
 1584          }
 1585        }
 1586      }
 1587
 1588      throw new ArgumentException ("savePoint is not valid, and should be the result of a call to SaveTransactionPoint."
 1589    }
 1590
 1591    /// <summary>
 1592    /// Commits the transaction that was begun by <see cref="BeginTransaction"/>.
 1593    /// </summary>
 1594    public void Commit ()
 1595    {
 1596      if (Interlocked.Exchange (ref _transactionDepth, 0) != 0) {
 1597        try {
 1598          Execute ("commit");
 1599        }
 1600        catch {
 1601          // Force a rollback since most people don't know this function can fail
 1602          // Don't call Rollback() since the _transactionDepth is 0 and it won't try
 1603          // Calling rollback makes our _transactionDepth variable correct.
 1604          try {
 1605            Execute ("rollback");
 1606          }
 1607          catch {
 1608            // rollback can fail in all sorts of wonderful version-dependent ways. Let's just hope for the best
 1609          }
 1610          throw;
 1611        }
 1612      }
 1613      // Do nothing on a commit with no open transaction
 1614    }
 1615
 1616    /// <summary>
 1617    /// Executes <paramref name="action"/> within a (possibly nested) transaction by wrapping it in a SAVEPOINT. If an
 1618    /// exception occurs the whole transaction is rolled back, not just the current savepoint. The exception
 1619    /// is rethrown.
 1620    /// </summary>
 1621    /// <param name="action">
 1622    /// The <see cref="Action"/> to perform within a transaction. <paramref name="action"/> can contain any number
 1623    /// of operations on the connection but should never call <see cref="BeginTransaction"/> or
 1624    /// <see cref="Commit"/>.
 1625    /// </param>
 1626    public void RunInTransaction (Action action)
 1627    {
 1628      try {
 1629        var savePoint = SaveTransactionPoint ();
 1630        action ();
 1631        Release (savePoint);
 1632      }
 1633      catch (Exception) {
 1634        Rollback ();
 1635        throw;
 1636      }
 1637    }
 1638
 1639    /// <summary>
 1640    /// Inserts all specified objects.
 1641    /// </summary>
 1642    /// <param name="objects">
 1643    /// An <see cref="IEnumerable"/> of the objects to insert.
 1644    /// <param name="runInTransaction"/>
 1645    /// A boolean indicating if the inserts should be wrapped in a transaction.
 1646    /// </param>
 1647    /// <returns>
 1648    /// The number of rows added to the table.
 1649    /// </returns>
 1650    public int InsertAll (System.Collections.IEnumerable objects, bool runInTransaction = true)
 1651    {
 1652      var c = 0;
 1653      if (runInTransaction) {
 1654        RunInTransaction (() => {
 1655          foreach (var r in objects) {
 1656            c += Insert (r);
 1657          }
 1658        });
 1659      }
 1660      else {
 1661        foreach (var r in objects) {
 1662          c += Insert (r);
 1663        }
 1664      }
 1665      return c;
 1666    }
 1667
 1668    /// <summary>
 1669    /// Inserts all specified objects.
 1670    /// </summary>
 1671    /// <param name="objects">
 1672    /// An <see cref="IEnumerable"/> of the objects to insert.
 1673    /// </param>
 1674    /// <param name="extra">
 1675    /// Literal SQL code that gets placed into the command. INSERT {extra} INTO ...
 1676    /// </param>
 1677    /// <param name="runInTransaction">
 1678    /// A boolean indicating if the inserts should be wrapped in a transaction.
 1679    /// </param>
 1680    /// <returns>
 1681    /// The number of rows added to the table.
 1682    /// </returns>
 1683    public int InsertAll (System.Collections.IEnumerable objects, string extra, bool runInTransaction = true)
 1684    {
 1685      var c = 0;
 1686      if (runInTransaction) {
 1687        RunInTransaction (() => {
 1688          foreach (var r in objects) {
 1689            c += Insert (r, extra);
 1690          }
 1691        });
 1692      }
 1693      else {
 1694        foreach (var r in objects) {
 1695          c += Insert (r, extra);
 1696        }
 1697      }
 1698      return c;
 1699    }
 1700
 1701    /// <summary>
 1702    /// Inserts all specified objects.
 1703    /// </summary>
 1704    /// <param name="objects">
 1705    /// An <see cref="IEnumerable"/> of the objects to insert.
 1706    /// </param>
 1707    /// <param name="objType">
 1708    /// The type of object to insert.
 1709    /// </param>
 1710    /// <param name="runInTransaction">
 1711    /// A boolean indicating if the inserts should be wrapped in a transaction.
 1712    /// </param>
 1713    /// <returns>
 1714    /// The number of rows added to the table.
 1715    /// </returns>
 1716    public int InsertAll (System.Collections.IEnumerable objects, Type objType, bool runInTransaction = true)
 1717    {
 1718      var c = 0;
 1719      if (runInTransaction) {
 1720        RunInTransaction (() => {
 1721          foreach (var r in objects) {
 1722            c += Insert (r, objType);
 1723          }
 1724        });
 1725      }
 1726      else {
 1727        foreach (var r in objects) {
 1728          c += Insert (r, objType);
 1729        }
 1730      }
 1731      return c;
 1732    }
 1733
 1734    /// <summary>
 1735    /// Inserts the given object (and updates its
 1736    /// auto incremented primary key if it has one).
 1737    /// The return value is the number of rows added to the table.
 1738    /// </summary>
 1739    /// <param name="obj">
 1740    /// The object to insert.
 1741    /// </param>
 1742    /// <returns>
 1743    /// The number of rows added to the table.
 1744    /// </returns>
 1745    public int Insert (object obj)
 1746    {
 1747      if (obj == null) {
 1748        return 0;
 1749      }
 1750      return Insert (obj, "", Orm.GetType (obj));
 1751    }
 1752
 1753    /// <summary>
 1754    /// Inserts the given object (and updates its
 1755    /// auto incremented primary key if it has one).
 1756    /// The return value is the number of rows added to the table.
 1757    /// If a UNIQUE constraint violation occurs with
 1758    /// some pre-existing object, this function deletes
 1759    /// the old object.
 1760    /// </summary>
 1761    /// <param name="obj">
 1762    /// The object to insert.
 1763    /// </param>
 1764    /// <returns>
 1765    /// The number of rows modified.
 1766    /// </returns>
 1767    public int InsertOrReplace (object obj)
 1768    {
 1769      if (obj == null) {
 1770        return 0;
 1771      }
 1772      return Insert (obj, "OR REPLACE", Orm.GetType (obj));
 1773    }
 1774
 1775    /// <summary>
 1776    /// Inserts the given object (and updates its
 1777    /// auto incremented primary key if it has one).
 1778    /// The return value is the number of rows added to the table.
 1779    /// </summary>
 1780    /// <param name="obj">
 1781    /// The object to insert.
 1782    /// </param>
 1783    /// <param name="objType">
 1784    /// The type of object to insert.
 1785    /// </param>
 1786    /// <returns>
 1787    /// The number of rows added to the table.
 1788    /// </returns>
 1789    public int Insert (object obj, Type objType)
 1790    {
 1791      return Insert (obj, "", objType);
 1792    }
 1793
 1794    /// <summary>
 1795    /// Inserts the given object (and updates its
 1796    /// auto incremented primary key if it has one).
 1797    /// The return value is the number of rows added to the table.
 1798    /// If a UNIQUE constraint violation occurs with
 1799    /// some pre-existing object, this function deletes
 1800    /// the old object.
 1801    /// </summary>
 1802    /// <param name="obj">
 1803    /// The object to insert.
 1804    /// </param>
 1805    /// <param name="objType">
 1806    /// The type of object to insert.
 1807    /// </param>
 1808    /// <returns>
 1809    /// The number of rows modified.
 1810    /// </returns>
 1811    public int InsertOrReplace (object obj, Type objType)
 1812    {
 1813      return Insert (obj, "OR REPLACE", objType);
 1814    }
 1815
 1816    /// <summary>
 1817    /// Inserts the given object (and updates its
 1818    /// auto incremented primary key if it has one).
 1819    /// The return value is the number of rows added to the table.
 1820    /// </summary>
 1821    /// <param name="obj">
 1822    /// The object to insert.
 1823    /// </param>
 1824    /// <param name="extra">
 1825    /// Literal SQL code that gets placed into the command. INSERT {extra} INTO ...
 1826    /// </param>
 1827    /// <returns>
 1828    /// The number of rows added to the table.
 1829    /// </returns>
 1830    public int Insert (object obj, string extra)
 1831    {
 1832      if (obj == null) {
 1833        return 0;
 1834      }
 1835      return Insert (obj, extra, Orm.GetType (obj));
 1836    }
 1837
 1838    /// <summary>
 1839    /// Inserts the given object (and updates its
 1840    /// auto incremented primary key if it has one).
 1841    /// The return value is the number of rows added to the table.
 1842    /// </summary>
 1843    /// <param name="obj">
 1844    /// The object to insert.
 1845    /// </param>
 1846    /// <param name="extra">
 1847    /// Literal SQL code that gets placed into the command. INSERT {extra} INTO ...
 1848    /// </param>
 1849    /// <param name="objType">
 1850    /// The type of object to insert.
 1851    /// </param>
 1852    /// <returns>
 1853    /// The number of rows added to the table.
 1854    /// </returns>
 1855    public int Insert (object obj, string extra, Type objType)
 1856    {
 1857      if (obj == null || objType == null) {
 1858        return 0;
 1859      }
 1860
 1861      var map = GetMapping (objType);
 1862
 1863      if (map.PK != null && map.PK.IsAutoGuid) {
 1864        if (map.PK.GetValue (obj).Equals (Guid.Empty)) {
 1865          map.PK.SetValue (obj, Guid.NewGuid ());
 1866        }
 1867      }
 1868
 1869      var replacing = string.Compare (extra, "OR REPLACE", StringComparison.OrdinalIgnoreCase) == 0;
 1870
 1871      var cols = replacing ? map.InsertOrReplaceColumns : map.InsertColumns;
 1872      var vals = new object[cols.Length];
 1873      for (var i = 0; i < vals.Length; i++) {
 1874        vals[i] = cols[i].GetValue (obj);
 1875      }
 1876
 1877      var insertCmd = GetInsertCommand (map, extra);
 1878      int count;
 1879
 1880      lock (insertCmd) {
 1881        // We lock here to protect the prepared statement returned via GetInsertCommand.
 1882        // A SQLite prepared statement can be bound for only one operation at a time.
 1883        try {
 1884          count = insertCmd.ExecuteNonQuery (vals);
 1885        }
 1886        catch (SQLiteException ex) {
 1887          if (SQLite3.ExtendedErrCode (this.Handle) == SQLite3.ExtendedResult.ConstraintNotNull) {
 1888            throw NotNullConstraintViolationException.New (ex.Result, ex.Message, map, obj);
 1889          }
 1890          throw;
 1891        }
 1892
 1893        if (map.HasAutoIncPK) {
 1894          var id = SQLite3.LastInsertRowid (Handle);
 1895          map.SetAutoIncPK (obj, id);
 1896        }
 1897      }
 1898      if (count > 0)
 1899        OnTableChanged (map, NotifyTableChangedAction.Insert);
 1900
 1901      return count;
 1902    }
 1903
 1904    readonly Dictionary<Tuple<string, string>, PreparedSqlLiteInsertCommand> _insertCommandMap = new Dictionary<Tuple<st
 1905
 1906    PreparedSqlLiteInsertCommand GetInsertCommand (TableMapping map, string extra)
 1907    {
 1908      PreparedSqlLiteInsertCommand prepCmd;
 1909
 1910      var key = Tuple.Create (map.MappedType.FullName, extra);
 1911
 1912      lock (_insertCommandMap) {
 1913        if (_insertCommandMap.TryGetValue (key, out prepCmd)) {
 1914          return prepCmd;
 1915        }
 1916      }
 1917
 1918      prepCmd = CreateInsertCommand (map, extra);
 1919
 1920      lock (_insertCommandMap) {
 1921        if (_insertCommandMap.TryGetValue (key, out var existing)) {
 1922          prepCmd.Dispose ();
 1923          return existing;
 1924        }
 1925
 1926        _insertCommandMap.Add (key, prepCmd);
 1927      }
 1928
 1929      return prepCmd;
 1930    }
 1931
 1932    PreparedSqlLiteInsertCommand CreateInsertCommand (TableMapping map, string extra)
 1933    {
 1934      var cols = map.InsertColumns;
 1935      string insertSql;
 1936      if (cols.Length == 0 && map.Columns.Length == 1 && map.Columns[0].IsAutoInc) {
 1937        insertSql = string.Format ("insert {1} into \"{0}\" default values", map.TableName, extra);
 1938      }
 1939      else {
 1940        var replacing = string.Compare (extra, "OR REPLACE", StringComparison.OrdinalIgnoreCase) == 0;
 1941
 1942        if (replacing) {
 1943          cols = map.InsertOrReplaceColumns;
 1944        }
 1945
 1946        insertSql = string.Format ("insert {3} into \"{0}\"({1}) values ({2})", map.TableName,
 1947                   string.Join (",", (from c in cols
 1948                            select "\"" + c.Name + "\"").ToArray ()),
 1949                   string.Join (",", (from c in cols
 1950                            select "?").ToArray ()), extra);
 1951
 1952      }
 1953
 1954      var insertCommand = new PreparedSqlLiteInsertCommand (this, insertSql);
 1955      return insertCommand;
 1956    }
 1957
 1958    /// <summary>
 1959    /// Updates all of the columns of a table using the specified object
 1960    /// except for its primary key.
 1961    /// The object is required to have a primary key.
 1962    /// </summary>
 1963    /// <param name="obj">
 1964    /// The object to update. It must have a primary key designated using the PrimaryKeyAttribute.
 1965    /// </param>
 1966    /// <returns>
 1967    /// The number of rows updated.
 1968    /// </returns>
 1969    public int Update (object obj)
 1970    {
 1971      if (obj == null) {
 1972        return 0;
 1973      }
 1974      return Update (obj, Orm.GetType (obj));
 1975    }
 1976
 1977    /// <summary>
 1978    /// Updates all of the columns of a table using the specified object
 1979    /// except for its primary key.
 1980    /// The object is required to have a primary key.
 1981    /// </summary>
 1982    /// <param name="obj">
 1983    /// The object to update. It must have a primary key designated using the PrimaryKeyAttribute.
 1984    /// </param>
 1985    /// <param name="objType">
 1986    /// The type of object to insert.
 1987    /// </param>
 1988    /// <returns>
 1989    /// The number of rows updated.
 1990    /// </returns>
 1991    public int Update (object obj, Type objType)
 1992    {
 1993      int rowsAffected = 0;
 1994      if (obj == null || objType == null) {
 1995        return 0;
 1996      }
 1997
 1998      var map = GetMapping (objType);
 1999
 2000      var pk = map.PK;
 2001
 2002      if (pk == null) {
 2003        throw new NotSupportedException ("Cannot update " + map.TableName + ": it has no PK");
 2004      }
 2005
 2006      var cols = from p in map.Columns
 2007             where p != pk
 2008             select p;
 2009      var vals = from c in cols
 2010             select c.GetValue (obj);
 2011      var ps = new List<object> (vals);
 2012      if (ps.Count == 0) {
 2013        // There is a PK but no accompanying data,
 2014        // so reset the PK to make the UPDATE work.
 2015        cols = map.Columns;
 2016        vals = from c in cols
 2017             select c.GetValue (obj);
 2018        ps = new List<object> (vals);
 2019      }
 2020      ps.Add (pk.GetValue (obj));
 2021      var q = string.Format ("update \"{0}\" set {1} where \"{2}\" = ? ", map.TableName, string.Join (",", (from c in co
 2022                                                          select "\"" + c.Name + "\" = ? ").ToArray ()), pk.Name);
 2023
 2024      try {
 2025        rowsAffected = Execute (q, ps.ToArray ());
 2026      }
 2027      catch (SQLiteException ex) {
 2028
 2029        if (ex.Result == SQLite3.Result.Constraint && SQLite3.ExtendedErrCode (this.Handle) == SQLite3.ExtendedResult.Co
 2030          throw NotNullConstraintViolationException.New (ex, map, obj);
 2031        }
 2032
 2033        throw;
 2034      }
 2035
 2036      if (rowsAffected > 0)
 2037        OnTableChanged (map, NotifyTableChangedAction.Update);
 2038
 2039      return rowsAffected;
 2040    }
 2041
 2042    /// <summary>
 2043    /// Updates all specified objects.
 2044    /// </summary>
 2045    /// <param name="objects">
 2046    /// An <see cref="IEnumerable"/> of the objects to insert.
 2047    /// </param>
 2048    /// <param name="runInTransaction">
 2049    /// A boolean indicating if the inserts should be wrapped in a transaction
 2050    /// </param>
 2051    /// <returns>
 2052    /// The number of rows modified.
 2053    /// </returns>
 2054    public int UpdateAll (System.Collections.IEnumerable objects, bool runInTransaction = true)
 2055    {
 2056      var c = 0;
 2057      if (runInTransaction) {
 2058        RunInTransaction (() => {
 2059          foreach (var r in objects) {
 2060            c += Update (r);
 2061          }
 2062        });
 2063      }
 2064      else {
 2065        foreach (var r in objects) {
 2066          c += Update (r);
 2067        }
 2068      }
 2069      return c;
 2070    }
 2071
 2072    /// <summary>
 2073    /// Deletes the given object from the database using its primary key.
 2074    /// </summary>
 2075    /// <param name="objectToDelete">
 2076    /// The object to delete. It must have a primary key designated using the PrimaryKeyAttribute.
 2077    /// </param>
 2078    /// <returns>
 2079    /// The number of rows deleted.
 2080    /// </returns>
 2081    public int Delete (object objectToDelete)
 2082    {
 2083      var map = GetMapping (Orm.GetType (objectToDelete));
 2084      var pk = map.PK;
 2085      if (pk == null) {
 2086        throw new NotSupportedException ("Cannot delete " + map.TableName + ": it has no PK");
 2087      }
 2088      var q = string.Format ("delete from \"{0}\" where \"{1}\" = ?", map.TableName, pk.Name);
 2089      var count = Execute (q, pk.GetValue (objectToDelete));
 2090      if (count > 0)
 2091        OnTableChanged (map, NotifyTableChangedAction.Delete);
 2092      return count;
 2093    }
 2094
 2095    /// <summary>
 2096    /// Deletes the object with the specified primary key.
 2097    /// </summary>
 2098    /// <param name="primaryKey">
 2099    /// The primary key of the object to delete.
 2100    /// </param>
 2101    /// <returns>
 2102    /// The number of objects deleted.
 2103    /// </returns>
 2104    /// <typeparam name='T'>
 2105    /// The type of object.
 2106    /// </typeparam>
 2107    public int Delete<T> (object primaryKey)
 2108    {
 2109      return Delete (primaryKey, GetMapping (typeof (T)));
 2110    }
 2111
 2112    /// <summary>
 2113    /// Deletes the object with the specified primary key.
 2114    /// </summary>
 2115    /// <param name="primaryKey">
 2116    /// The primary key of the object to delete.
 2117    /// </param>
 2118    /// <param name="map">
 2119    /// The TableMapping used to identify the table.
 2120    /// </param>
 2121    /// <returns>
 2122    /// The number of objects deleted.
 2123    /// </returns>
 2124    public int Delete (object primaryKey, TableMapping map)
 2125    {
 2126      var pk = map.PK;
 2127      if (pk == null) {
 2128        throw new NotSupportedException ("Cannot delete " + map.TableName + ": it has no PK");
 2129      }
 2130      var q = string.Format ("delete from \"{0}\" where \"{1}\" = ?", map.TableName, pk.Name);
 2131      var count = Execute (q, primaryKey);
 2132      if (count > 0)
 2133        OnTableChanged (map, NotifyTableChangedAction.Delete);
 2134      return count;
 2135    }
 2136
 2137    /// <summary>
 2138    /// Deletes all the objects from the specified table.
 2139    /// WARNING WARNING: Let me repeat. It deletes ALL the objects from the
 2140    /// specified table. Do you really want to do that?
 2141    /// </summary>
 2142    /// <returns>
 2143    /// The number of objects deleted.
 2144    /// </returns>
 2145    /// <typeparam name='T'>
 2146    /// The type of objects to delete.
 2147    /// </typeparam>
 2148    public int DeleteAll<T> ()
 2149    {
 2150      var map = GetMapping (typeof (T));
 2151      return DeleteAll (map);
 2152    }
 2153
 2154    /// <summary>
 2155    /// Deletes all the objects from the specified table.
 2156    /// WARNING WARNING: Let me repeat. It deletes ALL the objects from the
 2157    /// specified table. Do you really want to do that?
 2158    /// </summary>
 2159    /// <param name="map">
 2160    /// The TableMapping used to identify the table.
 2161    /// </param>
 2162    /// <returns>
 2163    /// The number of objects deleted.
 2164    /// </returns>
 2165    public int DeleteAll (TableMapping map)
 2166    {
 2167      var query = string.Format ("delete from \"{0}\"", map.TableName);
 2168      var count = Execute (query);
 2169      if (count > 0)
 2170        OnTableChanged (map, NotifyTableChangedAction.Delete);
 2171      return count;
 2172    }
 2173
 2174    /// <summary>
 2175    /// Backup the entire database to the specified path.
 2176    /// </summary>
 2177    /// <param name="destinationDatabasePath">Path to backup file.</param>
 2178    /// <param name="databaseName">The name of the database to backup (usually "main").</param>
 2179    public void Backup (string destinationDatabasePath, string databaseName = "main")
 2180    {
 2181      // Open the destination
 2182      var r = SQLite3.Open (destinationDatabasePath, out var destHandle);
 2183      if (r != SQLite3.Result.OK) {
 2184        throw SQLiteException.New (r, "Failed to open destination database");
 2185      }
 2186
 2187      // Init the backup
 2188      var backup = SQLite3.BackupInit (destHandle, databaseName, Handle, databaseName);
 2189      if (backup == NullBackupHandle) {
 2190        SQLite3.Close (destHandle);
 2191        throw new Exception ("Failed to create backup");
 2192      }
 2193
 2194      // Perform it
 2195      SQLite3.BackupStep (backup, -1);
 2196      SQLite3.BackupFinish (backup);
 2197
 2198      // Check for errors
 2199      r = SQLite3.GetResult (destHandle);
 2200      string msg = "";
 2201      if (r != SQLite3.Result.OK) {
 2202        msg = SQLite3.GetErrmsg (destHandle);
 2203      }
 2204
 2205      // Close everything and report errors
 2206      SQLite3.Close (destHandle);
 2207      if (r != SQLite3.Result.OK) {
 2208        throw SQLiteException.New (r, msg);
 2209      }
 2210    }
 2211
 2212    ~SQLiteConnection ()
 2213    {
 2214      Dispose (false);
 2215    }
 2216
 2217    public void Dispose ()
 2218    {
 2219      Dispose (true);
 2220      GC.SuppressFinalize (this);
 2221    }
 2222
 2223    public void Close ()
 2224    {
 2225      Dispose (true);
 2226    }
 2227
 2228    protected virtual void Dispose (bool disposing)
 2229    {
 2230      var useClose2 = LibVersionNumber >= 3007014;
 2231
 2232      if (_open && Handle != NullHandle) {
 2233        try {
 2234          if (disposing) {
 2235            lock (_insertCommandMap) {
 2236              foreach (var sqlInsertCommand in _insertCommandMap.Values) {
 2237                sqlInsertCommand.Dispose ();
 2238              }
 2239              _insertCommandMap.Clear ();
 2240            }
 2241
 2242            var r = useClose2 ? SQLite3.Close2 (Handle) : SQLite3.Close (Handle);
 2243            if (r != SQLite3.Result.OK) {
 2244              string msg = SQLite3.GetErrmsg (Handle);
 2245              throw SQLiteException.New (r, msg);
 2246            }
 2247          }
 2248          else {
 2249            var r = useClose2 ? SQLite3.Close2 (Handle) : SQLite3.Close (Handle);
 2250          }
 2251        }
 2252        finally {
 2253          Handle = NullHandle;
 2254          _open = false;
 2255        }
 2256      }
 2257    }
 2258
 2259    void OnTableChanged (TableMapping table, NotifyTableChangedAction action)
 2260    {
 2261      var ev = TableChanged;
 2262      if (ev != null)
 2263        ev (this, new NotifyTableChangedEventArgs (table, action));
 2264    }
 2265
 2266    public event EventHandler<NotifyTableChangedEventArgs> TableChanged;
 2267  }
 2268
 2269  public class NotifyTableChangedEventArgs : EventArgs
 2270  {
 2271    public TableMapping Table { get; private set; }
 2272    public NotifyTableChangedAction Action { get; private set; }
 2273
 2274    public NotifyTableChangedEventArgs (TableMapping table, NotifyTableChangedAction action)
 2275    {
 2276      Table = table;
 2277      Action = action;
 2278    }
 2279  }
 2280
 2281  public enum NotifyTableChangedAction
 2282  {
 2283    Insert,
 2284    Update,
 2285    Delete,
 2286  }
 2287
 2288  /// <summary>
 2289  /// Represents a parsed connection string.
 2290  /// </summary>
 2291  public class SQLiteConnectionString
 2292  {
 2293    const string DateTimeSqliteDefaultFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'fff";
 2294
 2295    public string UniqueKey { get; }
 2296    public string DatabasePath { get; }
 2297    public bool StoreDateTimeAsTicks { get; }
 2298    public bool StoreTimeSpanAsTicks { get; }
 2299    public string DateTimeStringFormat { get; }
 2300    public System.Globalization.DateTimeStyles DateTimeStyle { get; }
 2301    public object Key { get; }
 2302    public SQLiteOpenFlags OpenFlags { get; }
 2303    public Action<SQLiteConnection> PreKeyAction { get; }
 2304    public Action<SQLiteConnection> PostKeyAction { get; }
 2305    public string VfsName { get; }
 2306
 2307#if NETFX_CORE
 2308    static readonly string MetroStyleDataPath = Windows.Storage.ApplicationData.Current.LocalFolder.Path;
 2309
 2310        public static readonly string[] InMemoryDbPaths = new[]
 2311        {
 2312            ":memory:",
 2313            "file::memory:"
 2314        };
 2315
 2316        public static bool IsInMemoryPath(string databasePath)
 2317        {
 2318            return InMemoryDbPaths.Any(i => i.Equals(databasePath, StringComparison.OrdinalIgnoreCase));
 2319        }
 2320
 2321#endif
 2322
 2323    /// <summary>
 2324    /// Constructs a new SQLiteConnectionString with all the data needed to open an SQLiteConnection.
 2325    /// </summary>
 2326    /// <param name="databasePath">
 2327    /// Specifies the path to the database file.
 2328    /// </param>
 2329    /// <param name="storeDateTimeAsTicks">
 2330    /// Specifies whether to store DateTime properties as ticks (true) or strings (false). You
 2331    /// absolutely do want to store them as Ticks in all new projects. The value of false is
 2332    /// only here for backwards compatibility. There is a *significant* speed advantage, with no
 2333    /// down sides, when setting storeDateTimeAsTicks = true.
 2334    /// If you use DateTimeOffset properties, it will be always stored as ticks regardingless
 2335    /// the storeDateTimeAsTicks parameter.
 2336    /// </param>
 2337    public SQLiteConnectionString (string databasePath, bool storeDateTimeAsTicks = true)
 2338      : this (databasePath, SQLiteOpenFlags.Create | SQLiteOpenFlags.ReadWrite, storeDateTimeAsTicks)
 2339    {
 2340    }
 2341
 2342    /// <summary>
 2343    /// Constructs a new SQLiteConnectionString with all the data needed to open an SQLiteConnection.
 2344    /// </summary>
 2345    /// <param name="databasePath">
 2346    /// Specifies the path to the database file.
 2347    /// </param>
 2348    /// <param name="storeDateTimeAsTicks">
 2349    /// Specifies whether to store DateTime properties as ticks (true) or strings (false). You
 2350    /// absolutely do want to store them as Ticks in all new projects. The value of false is
 2351    /// only here for backwards compatibility. There is a *significant* speed advantage, with no
 2352    /// down sides, when setting storeDateTimeAsTicks = true.
 2353    /// If you use DateTimeOffset properties, it will be always stored as ticks regardingless
 2354    /// the storeDateTimeAsTicks parameter.
 2355    /// </param>
 2356    /// <param name="key">
 2357    /// Specifies the encryption key to use on the database. Should be a string or a byte[].
 2358    /// </param>
 2359    /// <param name="preKeyAction">
 2360    /// Executes prior to setting key for SQLCipher databases
 2361    /// </param>
 2362    /// <param name="postKeyAction">
 2363    /// Executes after setting key for SQLCipher databases
 2364    /// </param>
 2365    /// <param name="vfsName">
 2366    /// Specifies the Virtual File System to use on the database.
 2367    /// </param>
 2368    public SQLiteConnectionString (string databasePath, bool storeDateTimeAsTicks, object key = null, Action<SQLiteConne
 2369      : this (databasePath, SQLiteOpenFlags.Create | SQLiteOpenFlags.ReadWrite, storeDateTimeAsTicks, key, preKeyAction,
 2370    {
 2371    }
 2372
 2373    /// <summary>
 2374    /// Constructs a new SQLiteConnectionString with all the data needed to open an SQLiteConnection.
 2375    /// </summary>
 2376    /// <param name="databasePath">
 2377    /// Specifies the path to the database file.
 2378    /// </param>
 2379    /// <param name="openFlags">
 2380    /// Flags controlling how the connection should be opened.
 2381    /// </param>
 2382    /// <param name="storeDateTimeAsTicks">
 2383    /// Specifies whether to store DateTime properties as ticks (true) or strings (false). You
 2384    /// absolutely do want to store them as Ticks in all new projects. The value of false is
 2385    /// only here for backwards compatibility. There is a *significant* speed advantage, with no
 2386    /// down sides, when setting storeDateTimeAsTicks = true.
 2387    /// If you use DateTimeOffset properties, it will be always stored as ticks regardingless
 2388    /// the storeDateTimeAsTicks parameter.
 2389    /// </param>
 2390    /// <param name="key">
 2391    /// Specifies the encryption key to use on the database. Should be a string or a byte[].
 2392    /// </param>
 2393    /// <param name="preKeyAction">
 2394    /// Executes prior to setting key for SQLCipher databases
 2395    /// </param>
 2396    /// <param name="postKeyAction">
 2397    /// Executes after setting key for SQLCipher databases
 2398    /// </param>
 2399    /// <param name="vfsName">
 2400    /// Specifies the Virtual File System to use on the database.
 2401    /// </param>
 2402    /// <param name="dateTimeStringFormat">
 2403    /// Specifies the format to use when storing DateTime properties as strings.
 2404    /// </param>
 2405    /// <param name="storeTimeSpanAsTicks">
 2406    /// Specifies whether to store TimeSpan properties as ticks (true) or strings (false). You
 2407    /// absolutely do want to store them as Ticks in all new projects. The value of false is
 2408    /// only here for backwards compatibility. There is a *significant* speed advantage, with no
 2409    /// down sides, when setting storeTimeSpanAsTicks = true.
 2410    /// </param>
 2411    public SQLiteConnectionString (string databasePath, SQLiteOpenFlags openFlags, bool storeDateTimeAsTicks, object key
 2412    {
 2413      if (key != null && !((key is byte[]) || (key is string)))
 2414        throw new ArgumentException ("Encryption keys must be strings or byte arrays", nameof (key));
 2415
 2416      UniqueKey = string.Format ("{0}_{1:X8}", databasePath, (uint)openFlags);
 2417      StoreDateTimeAsTicks = storeDateTimeAsTicks;
 2418      StoreTimeSpanAsTicks = storeTimeSpanAsTicks;
 2419      DateTimeStringFormat = dateTimeStringFormat;
 2420      DateTimeStyle = "o".Equals (DateTimeStringFormat, StringComparison.OrdinalIgnoreCase) || "r".Equals (DateTimeStrin
 2421      Key = key;
 2422      PreKeyAction = preKeyAction;
 2423      PostKeyAction = postKeyAction;
 2424      OpenFlags = openFlags;
 2425      VfsName = vfsName;
 2426
 2427#if NETFX_CORE
 2428      DatabasePath = IsInMemoryPath(databasePath)
 2429                ? databasePath
 2430                : System.IO.Path.Combine(MetroStyleDataPath, databasePath);
 2431
 2432#else
 2433      DatabasePath = databasePath;
 2434#endif
 2435    }
 2436  }
 2437
 2438  [AttributeUsage (AttributeTargets.Class)]
 2439  public class TableAttribute : Attribute
 2440  {
 2441    public string Name { get; set; }
 2442
 2443    /// <summary>
 2444    /// Flag whether to create the table without rowid (see https://sqlite.org/withoutrowid.html)
 2445    ///
 2446    /// The default is <c>false</c> so that sqlite adds an implicit <c>rowid</c> to every table created.
 2447    /// </summary>
 2448    public bool WithoutRowId { get; set; }
 2449
 2450    public TableAttribute (string name)
 2451    {
 2452      Name = name;
 2453    }
 2454  }
 2455
 2456  [AttributeUsage (AttributeTargets.Property)]
 2457  public class ColumnAttribute : Attribute
 2458  {
 2459    public string Name { get; set; }
 2460
 2461    public ColumnAttribute (string name)
 2462    {
 2463      Name = name;
 2464    }
 2465  }
 2466
 2467  [AttributeUsage (AttributeTargets.Property)]
 2468  public class PrimaryKeyAttribute : Attribute
 2469  {
 2470  }
 2471
 2472  [AttributeUsage (AttributeTargets.Property)]
 2473  public class AutoIncrementAttribute : Attribute
 2474  {
 2475  }
 2476
 2477  [AttributeUsage (AttributeTargets.Property, AllowMultiple = true)]
 2478  public class IndexedAttribute : Attribute
 2479  {
 2480    public string Name { get; set; }
 2481    public int Order { get; set; }
 2482    public virtual bool Unique { get; set; }
 2483
 2484    public IndexedAttribute ()
 2485    {
 2486    }
 2487
 2488    public IndexedAttribute (string name, int order)
 2489    {
 2490      Name = name;
 2491      Order = order;
 2492    }
 2493  }
 2494
 2495  [AttributeUsage (AttributeTargets.Property)]
 2496  public class IgnoreAttribute : Attribute
 2497  {
 2498  }
 2499
 2500  [AttributeUsage (AttributeTargets.Property)]
 2501  public class UniqueAttribute : IndexedAttribute
 2502  {
 2503    public override bool Unique {
 2504      get { return true; }
 2505      set { /* throw?  */ }
 2506    }
 2507  }
 2508
 2509  [AttributeUsage (AttributeTargets.Property)]
 2510  public class MaxLengthAttribute : Attribute
 2511  {
 2512    public int Value { get; private set; }
 2513
 2514    public MaxLengthAttribute (int length)
 2515    {
 2516      Value = length;
 2517    }
 2518  }
 2519
 2520  public sealed class PreserveAttribute : System.Attribute
 2521  {
 2522    public bool AllMembers;
 2523    public bool Conditional;
 2524  }
 2525
 2526  /// <summary>
 2527  /// Select the collating sequence to use on a column.
 2528  /// "BINARY", "NOCASE", and "RTRIM" are supported.
 2529  /// "BINARY" is the default.
 2530  /// </summary>
 2531  [AttributeUsage (AttributeTargets.Property)]
 2532  public class CollationAttribute : Attribute
 2533  {
 2534    public string Value { get; private set; }
 2535
 2536    public CollationAttribute (string collation)
 2537    {
 2538      Value = collation;
 2539    }
 2540  }
 2541
 2542  [AttributeUsage (AttributeTargets.Property)]
 2543  public class NotNullAttribute : Attribute
 2544  {
 2545  }
 2546
 2547  [AttributeUsage (AttributeTargets.Enum)]
 2548  public class StoreAsTextAttribute : Attribute
 2549  {
 2550  }
 2551
 2552  public class TableMapping
 2553  {
 2554    public Type MappedType { get; private set; }
 2555
 2556    public string TableName { get; private set; }
 2557
 2558    public bool WithoutRowId { get; private set; }
 2559
 2560    public Column[] Columns { get; private set; }
 2561
 2562    public Column PK { get; private set; }
 2563
 2564    public string GetByPrimaryKeySql { get; private set; }
 2565
 2566    public CreateFlags CreateFlags { get; private set; }
 2567
 2568    internal MapMethod Method { get; private set; } = MapMethod.ByName;
 2569
 2570    readonly Column _autoPk;
 2571    readonly Column[] _insertColumns;
 2572    readonly Column[] _insertOrReplaceColumns;
 2573
 2574    public TableMapping (Type type, CreateFlags createFlags = CreateFlags.None)
 2575    {
 2576      MappedType = type;
 2577      CreateFlags = createFlags;
 2578
 2579      var typeInfo = type.GetTypeInfo ();
 2580#if ENABLE_IL2CPP
 2581      var tableAttr = typeInfo.GetCustomAttribute<TableAttribute> ();
 2582#else
 2583      var tableAttr =
 2584        typeInfo.CustomAttributes
 2585            .Where (x => x.AttributeType == typeof (TableAttribute))
 2586            .Select (x => (TableAttribute)Orm.InflateAttribute (x))
 2587            .FirstOrDefault ();
 2588#endif
 2589
 2590      TableName = (tableAttr != null && !string.IsNullOrEmpty (tableAttr.Name)) ? tableAttr.Name : MappedType.Name;
 2591      WithoutRowId = tableAttr != null ? tableAttr.WithoutRowId : false;
 2592
 2593      var members = GetPublicMembers(type);
 2594      var cols = new List<Column>(members.Count);
 2595      foreach(var m in members)
 2596      {
 2597        var ignore = m.IsDefined(typeof(IgnoreAttribute), true);
 2598        if(!ignore)
 2599          cols.Add(new Column(m, createFlags));
 2600      }
 2601      Columns = cols.ToArray ();
 2602      foreach (var c in Columns) {
 2603        if (c.IsAutoInc && c.IsPK) {
 2604          _autoPk = c;
 2605        }
 2606        if (c.IsPK) {
 2607          PK = c;
 2608        }
 2609      }
 2610
 2611      HasAutoIncPK = _autoPk != null;
 2612
 2613      if (PK != null) {
 2614        GetByPrimaryKeySql = string.Format ("select * from \"{0}\" where \"{1}\" = ?", TableName, PK.Name);
 2615      }
 2616      else {
 2617        // People should not be calling Get/Find without a PK
 2618        GetByPrimaryKeySql = string.Format ("select * from \"{0}\" limit 1", TableName);
 2619      }
 2620
 2621      _insertColumns = Columns.Where (c => !c.IsAutoInc).ToArray ();
 2622      _insertOrReplaceColumns = Columns.ToArray ();
 2623    }
 2624
 2625    private IReadOnlyCollection<MemberInfo> GetPublicMembers(Type type)
 2626    {
 2627      if(type.Name.StartsWith("ValueTuple`"))
 2628        return GetFieldsFromValueTuple(type);
 2629
 2630      var members = new List<MemberInfo>();
 2631      var memberNames = new HashSet<string>();
 2632      var newMembers = new List<MemberInfo>();
 2633      do
 2634      {
 2635        var ti = type.GetTypeInfo();
 2636        newMembers.Clear();
 2637
 2638        newMembers.AddRange(
 2639          from p in ti.DeclaredProperties
 2640          where !memberNames.Contains(p.Name) &&
 2641            p.CanRead && p.CanWrite &&
 2642            p.GetMethod != null && p.SetMethod != null &&
 2643            p.GetMethod.IsPublic && p.SetMethod.IsPublic &&
 2644            !p.GetMethod.IsStatic && !p.SetMethod.IsStatic
 2645          select p);
 2646
 2647        members.AddRange(newMembers);
 2648        foreach(var m in newMembers)
 2649          memberNames.Add(m.Name);
 2650
 2651        type = ti.BaseType;
 2652      }
 2653      while(type != typeof(object));
 2654
 2655      return members;
 2656    }
 2657
 2658    private IReadOnlyCollection<MemberInfo> GetFieldsFromValueTuple(Type type)
 2659    {
 2660      Method = MapMethod.ByPosition;
 2661      var fields = type.GetFields();
 2662
 2663      // https://docs.microsoft.com/en-us/dotnet/api/system.valuetuple-8.rest
 2664      if(fields.Length >= 8)
 2665        throw new NotSupportedException("ValueTuple with more than 7 members not supported due to nesting; see https://d
 2666
 2667      return fields;
 2668    }
 2669
 2670    public bool HasAutoIncPK { get; private set; }
 2671
 2672    public void SetAutoIncPK (object obj, long id)
 2673    {
 2674      if (_autoPk != null) {
 2675        _autoPk.SetValue (obj, Convert.ChangeType (id, _autoPk.ColumnType, null));
 2676      }
 2677    }
 2678
 2679    public Column[] InsertColumns {
 2680      get {
 2681        return _insertColumns;
 2682      }
 2683    }
 2684
 2685    public Column[] InsertOrReplaceColumns {
 2686      get {
 2687        return _insertOrReplaceColumns;
 2688      }
 2689    }
 2690
 2691    public Column FindColumnWithPropertyName (string propertyName)
 2692    {
 2693      var exact = Columns.FirstOrDefault (c => c.PropertyName == propertyName);
 2694      return exact;
 2695    }
 2696
 2697    public Column FindColumn (string columnName)
 2698    {
 2699      if(Method != MapMethod.ByName)
 2700        throw new InvalidOperationException($"This {nameof(TableMapping)} is not mapped by name, but {Method}.");
 2701
 2702      var exact = Columns.FirstOrDefault (c => c.Name.ToLower () == columnName.ToLower ());
 2703      return exact;
 2704    }
 2705
 2706    public class Column
 2707    {
 2708      MemberInfo _member;
 2709
 2710      public string Name { get; private set; }
 2711
 2712      public PropertyInfo PropertyInfo => _member as PropertyInfo;
 2713
 2714      public string PropertyName { get { return _member.Name; } }
 2715
 2716      public Type ColumnType { get; private set; }
 2717
 2718      public string Collation { get; private set; }
 2719
 2720      public bool IsAutoInc { get; private set; }
 2721      public bool IsAutoGuid { get; private set; }
 2722
 2723      public bool IsPK { get; private set; }
 2724
 2725      public IEnumerable<IndexedAttribute> Indices { get; set; }
 2726
 2727      public bool IsNullable { get; private set; }
 2728
 2729      public int? MaxStringLength { get; private set; }
 2730
 2731      public bool StoreAsText { get; private set; }
 2732
 2733      public Column (MemberInfo member, CreateFlags createFlags = CreateFlags.None)
 2734      {
 2735        _member = member;
 2736        var memberType = GetMemberType(member);
 2737
 2738        var colAttr = member.CustomAttributes.FirstOrDefault (x => x.AttributeType == typeof (ColumnAttribute));
 2739#if ENABLE_IL2CPP
 2740                var ca = member.GetCustomAttribute(typeof(ColumnAttribute)) as ColumnAttribute;
 2741        Name = ca == null ? member.Name : ca.Name;
 2742#else
 2743        Name = (colAttr != null && colAttr.ConstructorArguments.Count > 0) ?
 2744            colAttr.ConstructorArguments[0].Value?.ToString () :
 2745            member.Name;
 2746#endif
 2747        //If this type is Nullable<T> then Nullable.GetUnderlyingType returns the T, otherwise it returns null, so get t
 2748        ColumnType = Nullable.GetUnderlyingType (memberType) ?? memberType;
 2749        Collation = Orm.Collation (member);
 2750
 2751        IsPK = Orm.IsPK (member) ||
 2752          (((createFlags & CreateFlags.ImplicitPK) == CreateFlags.ImplicitPK) &&
 2753             string.Compare (member.Name, Orm.ImplicitPkName, StringComparison.OrdinalIgnoreCase) == 0);
 2754
 2755        var isAuto = Orm.IsAutoInc (member) || (IsPK && ((createFlags & CreateFlags.AutoIncPK) == CreateFlags.AutoIncPK)
 2756        IsAutoGuid = isAuto && ColumnType == typeof (Guid);
 2757        IsAutoInc = isAuto && !IsAutoGuid;
 2758
 2759        Indices = Orm.GetIndices (member);
 2760        if (!Indices.Any ()
 2761          && !IsPK
 2762          && ((createFlags & CreateFlags.ImplicitIndex) == CreateFlags.ImplicitIndex)
 2763          && Name.EndsWith (Orm.ImplicitIndexSuffix, StringComparison.OrdinalIgnoreCase)
 2764          ) {
 2765          Indices = new IndexedAttribute[] { new IndexedAttribute () };
 2766        }
 2767        IsNullable = !(IsPK || Orm.IsMarkedNotNull (member));
 2768        MaxStringLength = Orm.MaxStringLength (member);
 2769
 2770        StoreAsText = memberType.GetTypeInfo ().CustomAttributes.Any (x => x.AttributeType == typeof (StoreAsTextAttribu
 2771      }
 2772
 2773      public Column (PropertyInfo member, CreateFlags createFlags = CreateFlags.None)
 2774        : this((MemberInfo)member, createFlags)
 2775      { }
 2776
 2777      public void SetValue (object obj, object val)
 2778      {
 2779        if(_member is PropertyInfo propy)
 2780        {
 2781          if (val != null && ColumnType.GetTypeInfo ().IsEnum)
 2782            propy.SetValue (obj, Enum.ToObject (ColumnType, val));
 2783          else
 2784            propy.SetValue (obj, val);
 2785        }
 2786        else if(_member is FieldInfo field)
 2787        {
 2788          if (val != null && ColumnType.GetTypeInfo ().IsEnum)
 2789            field.SetValue (obj, Enum.ToObject (ColumnType, val));
 2790          else
 2791            field.SetValue (obj, val);
 2792        }
 2793        else
 2794          throw new InvalidProgramException("unreachable condition");
 2795      }
 2796
 2797      public object GetValue (object obj)
 2798      {
 2799        if(_member is PropertyInfo propy)
 2800          return propy.GetValue(obj);
 2801        else if(_member is FieldInfo field)
 2802          return field.GetValue(obj);
 2803        else
 2804          throw new InvalidProgramException("unreachable condition");
 2805      }
 2806
 2807      private static Type GetMemberType(MemberInfo m)
 2808      {
 2809        switch(m.MemberType)
 2810        {
 2811          case MemberTypes.Property: return ((PropertyInfo)m).PropertyType;
 2812          case MemberTypes.Field: return ((FieldInfo)m).FieldType;
 2813          default: throw new InvalidProgramException($"{nameof(TableMapping)} supports properties or fields only.");
 2814        }
 2815      }
 2816    }
 2817
 2818    internal enum MapMethod
 2819    {
 2820      ByName,
 2821      ByPosition
 2822    }
 2823  }
 2824
 2825  class EnumCacheInfo
 2826  {
 2827    public EnumCacheInfo (Type type)
 2828    {
 2829      var typeInfo = type.GetTypeInfo ();
 2830
 2831      IsEnum = typeInfo.IsEnum;
 2832
 2833      if (IsEnum) {
 2834        StoreAsText = typeInfo.CustomAttributes.Any (x => x.AttributeType == typeof (StoreAsTextAttribute));
 2835
 2836        if (StoreAsText) {
 2837          EnumValues = new Dictionary<int, string> ();
 2838          foreach (object e in Enum.GetValues (type)) {
 2839            EnumValues[Convert.ToInt32 (e)] = e.ToString ();
 2840          }
 2841        }
 2842      }
 2843    }
 2844
 2845    public bool IsEnum { get; private set; }
 2846
 2847    public bool StoreAsText { get; private set; }
 2848
 2849    public Dictionary<int, string> EnumValues { get; private set; }
 2850  }
 2851
 2852  static class EnumCache
 2853  {
 12854    static readonly Dictionary<Type, EnumCacheInfo> Cache = new Dictionary<Type, EnumCacheInfo> ();
 2855
 2856    public static EnumCacheInfo GetInfo<T> ()
 62857    {
 62858      return GetInfo (typeof (T));
 62859    }
 2860
 2861    public static EnumCacheInfo GetInfo (Type type)
 252862    {
 502863      lock (Cache) {
 252864        EnumCacheInfo info = null;
 392865        if (!Cache.TryGetValue (type, out info)) {
 142866          info = new EnumCacheInfo (type);
 142867          Cache[type] = info;
 142868        }
 2869
 252870        return info;
 2871      }
 252872    }
 2873  }
 2874
 2875  public static class Orm
 2876  {
 2877    public const int DefaultMaxStringLength = 140;
 2878    public const string ImplicitPkName = "Id";
 2879    public const string ImplicitIndexSuffix = "Id";
 2880
 2881    public static Type GetType (object obj)
 2882    {
 2883      if (obj == null)
 2884        return typeof (object);
 2885      var rt = obj as IReflectableType;
 2886      if (rt != null)
 2887        return rt.GetTypeInfo ().AsType ();
 2888      return obj.GetType ();
 2889    }
 2890
 2891    public static string SqlDecl (TableMapping.Column p, bool storeDateTimeAsTicks, bool storeTimeSpanAsTicks)
 2892    {
 2893      string decl = "\"" + p.Name + "\" " + SqlType (p, storeDateTimeAsTicks, storeTimeSpanAsTicks) + " ";
 2894
 2895      if (p.IsPK) {
 2896        decl += "primary key ";
 2897      }
 2898      if (p.IsAutoInc) {
 2899        decl += "autoincrement ";
 2900      }
 2901      if (!p.IsNullable) {
 2902        decl += "not null ";
 2903      }
 2904      if (!string.IsNullOrEmpty (p.Collation)) {
 2905        decl += "collate " + p.Collation + " ";
 2906      }
 2907
 2908      return decl;
 2909    }
 2910
 2911    public static string SqlType (TableMapping.Column p, bool storeDateTimeAsTicks, bool storeTimeSpanAsTicks)
 2912    {
 2913      var clrType = p.ColumnType;
 2914      if (clrType == typeof (Boolean) || clrType == typeof (Byte) || clrType == typeof (UInt16) || clrType == typeof (SB
 2915        return "integer";
 2916      }
 2917      else if (clrType == typeof (Single) || clrType == typeof (Double) || clrType == typeof (Decimal)) {
 2918        return "float";
 2919      }
 2920      else if (clrType == typeof (String) || clrType == typeof (StringBuilder) || clrType == typeof (Uri) || clrType == 
 2921        int? len = p.MaxStringLength;
 2922
 2923        if (len.HasValue)
 2924          return "varchar(" + len.Value + ")";
 2925
 2926        return "varchar";
 2927      }
 2928      else if (clrType == typeof (TimeSpan)) {
 2929        return storeTimeSpanAsTicks ? "bigint" : "time";
 2930      }
 2931      else if (clrType == typeof (DateTime)) {
 2932        return storeDateTimeAsTicks ? "bigint" : "datetime";
 2933      }
 2934      else if (clrType == typeof (DateTimeOffset)) {
 2935        return "bigint";
 2936      }
 2937      else if (clrType.GetTypeInfo ().IsEnum) {
 2938        if (p.StoreAsText)
 2939          return "varchar";
 2940        else
 2941          return "integer";
 2942      }
 2943      else if (clrType == typeof (byte[])) {
 2944        return "blob";
 2945      }
 2946      else if (clrType == typeof (Guid)) {
 2947        return "varchar(36)";
 2948      }
 2949      else {
 2950        throw new NotSupportedException ("Don't know about " + clrType);
 2951      }
 2952    }
 2953
 2954    public static bool IsPK (MemberInfo p)
 2955    {
 2956      return p.CustomAttributes.Any (x => x.AttributeType == typeof (PrimaryKeyAttribute));
 2957    }
 2958
 2959    public static string Collation (MemberInfo p)
 2960    {
 2961#if ENABLE_IL2CPP
 2962      return (p.GetCustomAttribute<CollationAttribute> ()?.Value) ?? "";
 2963#else
 2964      return
 2965        (p.CustomAttributes
 2966         .Where (x => typeof (CollationAttribute) == x.AttributeType)
 2967         .Select (x => {
 2968           var args = x.ConstructorArguments;
 2969           return args.Count > 0 ? ((args[0].Value as string) ?? "") : "";
 2970         })
 2971         .FirstOrDefault ()) ?? "";
 2972#endif
 2973    }
 2974
 2975    public static bool IsAutoInc (MemberInfo p)
 2976    {
 2977      return p.CustomAttributes.Any (x => x.AttributeType == typeof (AutoIncrementAttribute));
 2978    }
 2979
 2980    public static FieldInfo GetField (TypeInfo t, string name)
 2981    {
 2982      var f = t.GetDeclaredField (name);
 2983      if (f != null)
 2984        return f;
 2985      return GetField (t.BaseType.GetTypeInfo (), name);
 2986    }
 2987
 2988    public static PropertyInfo GetProperty (TypeInfo t, string name)
 2989    {
 2990      var f = t.GetDeclaredProperty (name);
 2991      if (f != null)
 2992        return f;
 2993      return GetProperty (t.BaseType.GetTypeInfo (), name);
 2994    }
 2995
 2996    public static object InflateAttribute (CustomAttributeData x)
 2997    {
 2998      var atype = x.AttributeType;
 2999      var typeInfo = atype.GetTypeInfo ();
 3000#if ENABLE_IL2CPP
 3001      var r = Activator.CreateInstance (x.AttributeType);
 3002#else
 3003      var args = x.ConstructorArguments.Select (a => a.Value).ToArray ();
 3004      var r = Activator.CreateInstance (x.AttributeType, args);
 3005      foreach (var arg in x.NamedArguments) {
 3006        if (arg.IsField) {
 3007          GetField (typeInfo, arg.MemberName).SetValue (r, arg.TypedValue.Value);
 3008        }
 3009        else {
 3010          GetProperty (typeInfo, arg.MemberName).SetValue (r, arg.TypedValue.Value);
 3011        }
 3012      }
 3013#endif
 3014      return r;
 3015    }
 3016
 3017    public static IEnumerable<IndexedAttribute> GetIndices (MemberInfo p)
 3018    {
 3019#if ENABLE_IL2CPP
 3020      return p.GetCustomAttributes<IndexedAttribute> ();
 3021#else
 3022      var indexedInfo = typeof (IndexedAttribute).GetTypeInfo ();
 3023      return
 3024        p.CustomAttributes
 3025         .Where (x => indexedInfo.IsAssignableFrom (x.AttributeType.GetTypeInfo ()))
 3026         .Select (x => (IndexedAttribute)InflateAttribute (x));
 3027#endif
 3028    }
 3029
 3030    public static int? MaxStringLength (MemberInfo p)
 3031    {
 3032#if ENABLE_IL2CPP
 3033      return p.GetCustomAttribute<MaxLengthAttribute> ()?.Value;
 3034#else
 3035      var attr = p.CustomAttributes.FirstOrDefault (x => x.AttributeType == typeof (MaxLengthAttribute));
 3036      if (attr != null) {
 3037        var attrv = (MaxLengthAttribute)InflateAttribute (attr);
 3038        return attrv.Value;
 3039      }
 3040      return null;
 3041#endif
 3042    }
 3043
 3044    public static int? MaxStringLength (PropertyInfo p) => MaxStringLength((MemberInfo)p);
 3045
 3046    public static bool IsMarkedNotNull (MemberInfo p)
 3047    {
 3048      return p.CustomAttributes.Any (x => x.AttributeType == typeof (NotNullAttribute));
 3049    }
 3050  }
 3051
 3052  public partial class SQLiteCommand
 3053  {
 3054    SQLiteConnection _conn;
 3055    private List<Binding> _bindings;
 3056
 3057    public string CommandText { get; set; }
 3058
 3059    public SQLiteCommand (SQLiteConnection conn)
 3060    {
 3061      _conn = conn;
 3062      _bindings = new List<Binding> ();
 3063      CommandText = "";
 3064    }
 3065
 3066    public int ExecuteNonQuery ()
 3067    {
 3068      if (_conn.Trace) {
 3069        _conn.Tracer?.Invoke ("Executing: " + this);
 3070      }
 3071
 3072      var r = SQLite3.Result.OK;
 3073      var stmt = Prepare ();
 3074      r = SQLite3.Step (stmt);
 3075      Finalize (stmt);
 3076      if (r == SQLite3.Result.Done) {
 3077        int rowsAffected = SQLite3.Changes (_conn.Handle);
 3078        return rowsAffected;
 3079      }
 3080      else if (r == SQLite3.Result.Error) {
 3081        string msg = SQLite3.GetErrmsg (_conn.Handle);
 3082        throw SQLiteException.New (r, msg);
 3083      }
 3084      else if (r == SQLite3.Result.Constraint) {
 3085        if (SQLite3.ExtendedErrCode (_conn.Handle) == SQLite3.ExtendedResult.ConstraintNotNull) {
 3086          throw NotNullConstraintViolationException.New (r, SQLite3.GetErrmsg (_conn.Handle));
 3087        }
 3088      }
 3089
 3090      throw SQLiteException.New (r, SQLite3.GetErrmsg (_conn.Handle));
 3091    }
 3092
 3093    public IEnumerable<T> ExecuteDeferredQuery<T> ()
 3094    {
 3095      return ExecuteDeferredQuery<T> (_conn.GetMapping (typeof (T)));
 3096    }
 3097
 3098    public List<T> ExecuteQuery<T> ()
 3099    {
 3100      return ExecuteDeferredQuery<T> (_conn.GetMapping (typeof (T))).ToList ();
 3101    }
 3102
 3103    public List<T> ExecuteQuery<T> (TableMapping map)
 3104    {
 3105      return ExecuteDeferredQuery<T> (map).ToList ();
 3106    }
 3107
 3108    /// <summary>
 3109    /// Invoked every time an instance is loaded from the database.
 3110    /// </summary>
 3111    /// <param name='obj'>
 3112    /// The newly created object.
 3113    /// </param>
 3114    /// <remarks>
 3115    /// This can be overridden in combination with the <see cref="SQLiteConnection.NewCommand"/>
 3116    /// method to hook into the life-cycle of objects.
 3117    /// </remarks>
 3118    protected virtual void OnInstanceCreated (object obj)
 3119    {
 3120      // Can be overridden.
 3121    }
 3122
 3123    public IEnumerable<T> ExecuteDeferredQuery<T> (TableMapping map)
 3124    {
 3125      if (_conn.Trace) {
 3126        _conn.Tracer?.Invoke ("Executing Query: " + this);
 3127      }
 3128
 3129      var stmt = Prepare ();
 3130      try {
 3131        var cols = new TableMapping.Column[SQLite3.ColumnCount (stmt)];
 3132        var fastColumnSetters = new Action<object, Sqlite3Statement, int>[SQLite3.ColumnCount (stmt)];
 3133
 3134        if (map.Method == TableMapping.MapMethod.ByPosition)
 3135        {
 3136          Array.Copy(map.Columns, cols, Math.Min(cols.Length, map.Columns.Length));
 3137        }
 3138        else if (map.Method == TableMapping.MapMethod.ByName) {
 3139          MethodInfo getSetter = null;
 3140          if (typeof(T) != map.MappedType) {
 3141            getSetter = typeof(FastColumnSetter)
 3142              .GetMethod (nameof(FastColumnSetter.GetFastSetter),
 3143                BindingFlags.NonPublic | BindingFlags.Static).MakeGenericMethod (map.MappedType);
 3144          }
 3145
 3146          for (int i = 0; i < cols.Length; i++) {
 3147            var name = SQLite3.ColumnName16 (stmt, i);
 3148            cols[i] = map.FindColumn (name);
 3149            if (cols[i] != null)
 3150              if (getSetter != null) {
 3151                fastColumnSetters[i] = (Action<object, Sqlite3Statement, int>)getSetter.Invoke(null, new object[]{ _conn
 3152              }
 3153              else {
 3154                fastColumnSetters[i] = FastColumnSetter.GetFastSetter<T>(_conn, cols[i]);
 3155              }
 3156          }
 3157        }
 3158
 3159        while (SQLite3.Step (stmt) == SQLite3.Result.Row) {
 3160          var obj = Activator.CreateInstance (map.MappedType);
 3161          for (int i = 0; i < cols.Length; i++) {
 3162            if (cols[i] == null)
 3163              continue;
 3164
 3165            if (fastColumnSetters[i] != null) {
 3166              fastColumnSetters[i].Invoke (obj, stmt, i);
 3167            }
 3168            else {
 3169              var colType = SQLite3.ColumnType (stmt, i);
 3170              var val = ReadCol (stmt, i, colType, cols[i].ColumnType);
 3171              cols[i].SetValue (obj, val);
 3172            }
 3173          }
 3174          OnInstanceCreated (obj);
 3175          yield return (T)obj;
 3176        }
 3177      }
 3178      finally {
 3179        SQLite3.Finalize (stmt);
 3180      }
 3181    }
 3182
 3183    public T ExecuteScalar<T> ()
 3184    {
 3185      if (_conn.Trace) {
 3186        _conn.Tracer?.Invoke ("Executing Query: " + this);
 3187      }
 3188
 3189      T val = default (T);
 3190
 3191      var stmt = Prepare ();
 3192
 3193      try {
 3194        var r = SQLite3.Step (stmt);
 3195        if (r == SQLite3.Result.Row) {
 3196          var colType = SQLite3.ColumnType (stmt, 0);
 3197          var colval = ReadCol (stmt, 0, colType, typeof (T));
 3198          if (colval != null) {
 3199            val = (T)colval;
 3200          }
 3201        }
 3202        else if (r == SQLite3.Result.Done) {
 3203        }
 3204        else {
 3205          throw SQLiteException.New (r, SQLite3.GetErrmsg (_conn.Handle));
 3206        }
 3207      }
 3208      finally {
 3209        Finalize (stmt);
 3210      }
 3211
 3212      return val;
 3213    }
 3214
 3215    public IEnumerable<T> ExecuteQueryScalars<T> ()
 3216    {
 3217      if (_conn.Trace) {
 3218        _conn.Tracer?.Invoke ("Executing Query: " + this);
 3219      }
 3220      var stmt = Prepare ();
 3221      try {
 3222        if (SQLite3.ColumnCount (stmt) < 1) {
 3223          throw new InvalidOperationException ("QueryScalars should return at least one column");
 3224        }
 3225        while (SQLite3.Step (stmt) == SQLite3.Result.Row) {
 3226          var colType = SQLite3.ColumnType (stmt, 0);
 3227          var val = ReadCol (stmt, 0, colType, typeof (T));
 3228          if (val == null) {
 3229            yield return default (T);
 3230          }
 3231          else {
 3232            yield return (T)val;
 3233          }
 3234        }
 3235      }
 3236      finally {
 3237        Finalize (stmt);
 3238      }
 3239    }
 3240
 3241    public void Bind (string name, object val)
 3242    {
 3243      _bindings.Add (new Binding {
 3244        Name = name,
 3245        Value = val
 3246      });
 3247    }
 3248
 3249    public void Bind (object val)
 3250    {
 3251      Bind (null, val);
 3252    }
 3253
 3254    public override string ToString ()
 3255    {
 3256      var parts = new string[1 + _bindings.Count];
 3257      parts[0] = CommandText;
 3258      var i = 1;
 3259      foreach (var b in _bindings) {
 3260        parts[i] = string.Format ("  {0}: {1}", i - 1, b.Value);
 3261        i++;
 3262      }
 3263      return string.Join (Environment.NewLine, parts);
 3264    }
 3265
 3266    Sqlite3Statement Prepare ()
 3267    {
 3268      var stmt = SQLite3.Prepare2 (_conn.Handle, CommandText);
 3269      BindAll (stmt);
 3270      return stmt;
 3271    }
 3272
 3273    void Finalize (Sqlite3Statement stmt)
 3274    {
 3275      SQLite3.Finalize (stmt);
 3276    }
 3277
 3278    void BindAll (Sqlite3Statement stmt)
 3279    {
 3280      int nextIdx = 1;
 3281      foreach (var b in _bindings) {
 3282        if (b.Name != null) {
 3283          b.Index = SQLite3.BindParameterIndex (stmt, b.Name);
 3284        }
 3285        else {
 3286          b.Index = nextIdx++;
 3287        }
 3288
 3289        BindParameter (stmt, b.Index, b.Value, _conn.StoreDateTimeAsTicks, _conn.DateTimeStringFormat, _conn.StoreTimeSp
 3290      }
 3291    }
 3292
 3293    static IntPtr NegativePointer = new IntPtr (-1);
 3294
 3295    internal static void BindParameter (Sqlite3Statement stmt, int index, object value, bool storeDateTimeAsTicks, strin
 3296    {
 3297      if (value == null) {
 3298        SQLite3.BindNull (stmt, index);
 3299      }
 3300      else {
 3301        if (value is Int32) {
 3302          SQLite3.BindInt (stmt, index, (int)value);
 3303        }
 3304        else if (value is String) {
 3305          SQLite3.BindText (stmt, index, (string)value, -1, NegativePointer);
 3306        }
 3307        else if (value is Byte || value is UInt16 || value is SByte || value is Int16) {
 3308          SQLite3.BindInt (stmt, index, Convert.ToInt32 (value));
 3309        }
 3310        else if (value is Boolean) {
 3311          SQLite3.BindInt (stmt, index, (bool)value ? 1 : 0);
 3312        }
 3313        else if (value is UInt32 || value is Int64 || value is UInt64) {
 3314          SQLite3.BindInt64 (stmt, index, Convert.ToInt64 (value));
 3315        }
 3316        else if (value is Single || value is Double || value is Decimal) {
 3317          SQLite3.BindDouble (stmt, index, Convert.ToDouble (value));
 3318        }
 3319        else if (value is TimeSpan) {
 3320          if (storeTimeSpanAsTicks) {
 3321            SQLite3.BindInt64 (stmt, index, ((TimeSpan)value).Ticks);
 3322          }
 3323          else {
 3324            SQLite3.BindText (stmt, index, ((TimeSpan)value).ToString (), -1, NegativePointer);
 3325          }
 3326        }
 3327        else if (value is DateTime) {
 3328          if (storeDateTimeAsTicks) {
 3329            SQLite3.BindInt64 (stmt, index, ((DateTime)value).Ticks);
 3330          }
 3331          else {
 3332            SQLite3.BindText (stmt, index, ((DateTime)value).ToString (dateTimeStringFormat, System.Globalization.Cultur
 3333          }
 3334        }
 3335        else if (value is DateTimeOffset) {
 3336          SQLite3.BindInt64 (stmt, index, ((DateTimeOffset)value).UtcTicks);
 3337        }
 3338        else if (value is byte[]) {
 3339          SQLite3.BindBlob (stmt, index, (byte[])value, ((byte[])value).Length, NegativePointer);
 3340        }
 3341        else if (value is Guid) {
 3342          SQLite3.BindText (stmt, index, ((Guid)value).ToString (), 72, NegativePointer);
 3343        }
 3344        else if (value is Uri) {
 3345          SQLite3.BindText (stmt, index, ((Uri)value).ToString (), -1, NegativePointer);
 3346        }
 3347        else if (value is StringBuilder) {
 3348          SQLite3.BindText (stmt, index, ((StringBuilder)value).ToString (), -1, NegativePointer);
 3349        }
 3350        else if (value is UriBuilder) {
 3351          SQLite3.BindText (stmt, index, ((UriBuilder)value).ToString (), -1, NegativePointer);
 3352        }
 3353        else {
 3354          // Now we could possibly get an enum, retrieve cached info
 3355          var valueType = value.GetType ();
 3356          var enumInfo = EnumCache.GetInfo (valueType);
 3357          if (enumInfo.IsEnum) {
 3358            var enumIntValue = Convert.ToInt32 (value);
 3359            if (enumInfo.StoreAsText)
 3360              SQLite3.BindText (stmt, index, enumInfo.EnumValues[enumIntValue], -1, NegativePointer);
 3361            else
 3362              SQLite3.BindInt (stmt, index, enumIntValue);
 3363          }
 3364          else {
 3365            throw new NotSupportedException ("Cannot store type: " + Orm.GetType (value));
 3366          }
 3367        }
 3368      }
 3369    }
 3370
 3371    class Binding
 3372    {
 3373      public string Name { get; set; }
 3374
 3375      public object Value { get; set; }
 3376
 3377      public int Index { get; set; }
 3378    }
 3379
 3380    object ReadCol (Sqlite3Statement stmt, int index, SQLite3.ColType type, Type clrType)
 3381    {
 3382      if (type == SQLite3.ColType.Null) {
 3383        return null;
 3384      }
 3385      else {
 3386        var clrTypeInfo = clrType.GetTypeInfo ();
 3387        if (clrTypeInfo.IsGenericType && clrTypeInfo.GetGenericTypeDefinition () == typeof (Nullable<>)) {
 3388          clrType = clrTypeInfo.GenericTypeArguments[0];
 3389          clrTypeInfo = clrType.GetTypeInfo ();
 3390        }
 3391
 3392        if (clrType == typeof (String)) {
 3393          return SQLite3.ColumnString (stmt, index);
 3394        }
 3395        else if (clrType == typeof (Int32)) {
 3396          return (int)SQLite3.ColumnInt (stmt, index);
 3397        }
 3398        else if (clrType == typeof (Boolean)) {
 3399          return SQLite3.ColumnInt (stmt, index) == 1;
 3400        }
 3401        else if (clrType == typeof (double)) {
 3402          return SQLite3.ColumnDouble (stmt, index);
 3403        }
 3404        else if (clrType == typeof (float)) {
 3405          return (float)SQLite3.ColumnDouble (stmt, index);
 3406        }
 3407        else if (clrType == typeof (TimeSpan)) {
 3408          if (_conn.StoreTimeSpanAsTicks) {
 3409            return new TimeSpan (SQLite3.ColumnInt64 (stmt, index));
 3410          }
 3411          else {
 3412            var text = SQLite3.ColumnString (stmt, index);
 3413            TimeSpan resultTime;
 3414            if (!TimeSpan.TryParseExact (text, "c", System.Globalization.CultureInfo.InvariantCulture, System.Globalizat
 3415              resultTime = TimeSpan.Parse (text);
 3416            }
 3417            return resultTime;
 3418          }
 3419        }
 3420        else if (clrType == typeof (DateTime)) {
 3421          if (_conn.StoreDateTimeAsTicks) {
 3422            return new DateTime (SQLite3.ColumnInt64 (stmt, index));
 3423          }
 3424          else {
 3425            var text = SQLite3.ColumnString (stmt, index);
 3426            DateTime resultDate;
 3427            if (!DateTime.TryParseExact (text, _conn.DateTimeStringFormat, System.Globalization.CultureInfo.InvariantCul
 3428              resultDate = DateTime.Parse (text);
 3429            }
 3430            return resultDate;
 3431          }
 3432        }
 3433        else if (clrType == typeof (DateTimeOffset)) {
 3434          return new DateTimeOffset (SQLite3.ColumnInt64 (stmt, index), TimeSpan.Zero);
 3435        }
 3436        else if (clrTypeInfo.IsEnum) {
 3437          if (type == SQLite3.ColType.Text) {
 3438            var value = SQLite3.ColumnString (stmt, index);
 3439            return Enum.Parse (clrType, value.ToString (), true);
 3440          }
 3441          else
 3442            return SQLite3.ColumnInt (stmt, index);
 3443        }
 3444        else if (clrType == typeof (Int64)) {
 3445          return SQLite3.ColumnInt64 (stmt, index);
 3446        }
 3447        else if (clrType == typeof (UInt64)) {
 3448          return (ulong)SQLite3.ColumnInt64 (stmt, index);
 3449        }
 3450        else if (clrType == typeof (UInt32)) {
 3451          return (uint)SQLite3.ColumnInt64 (stmt, index);
 3452        }
 3453        else if (clrType == typeof (decimal)) {
 3454          return (decimal)SQLite3.ColumnDouble (stmt, index);
 3455        }
 3456        else if (clrType == typeof (Byte)) {
 3457          return (byte)SQLite3.ColumnInt (stmt, index);
 3458        }
 3459        else if (clrType == typeof (UInt16)) {
 3460          return (ushort)SQLite3.ColumnInt (stmt, index);
 3461        }
 3462        else if (clrType == typeof (Int16)) {
 3463          return (short)SQLite3.ColumnInt (stmt, index);
 3464        }
 3465        else if (clrType == typeof (sbyte)) {
 3466          return (sbyte)SQLite3.ColumnInt (stmt, index);
 3467        }
 3468        else if (clrType == typeof (byte[])) {
 3469          return SQLite3.ColumnByteArray (stmt, index);
 3470        }
 3471        else if (clrType == typeof (Guid)) {
 3472          var text = SQLite3.ColumnString (stmt, index);
 3473          return new Guid (text);
 3474        }
 3475        else if (clrType == typeof (Uri)) {
 3476          var text = SQLite3.ColumnString (stmt, index);
 3477          return new Uri (text);
 3478        }
 3479        else if (clrType == typeof (StringBuilder)) {
 3480          var text = SQLite3.ColumnString (stmt, index);
 3481          return new StringBuilder (text);
 3482        }
 3483        else if (clrType == typeof (UriBuilder)) {
 3484          var text = SQLite3.ColumnString (stmt, index);
 3485          return new UriBuilder (text);
 3486        }
 3487        else {
 3488          throw new NotSupportedException ("Don't know how to read " + clrType);
 3489        }
 3490      }
 3491    }
 3492  }
 3493
 3494  internal class FastColumnSetter
 3495  {
 3496    /// <summary>
 3497    /// Creates a delegate that can be used to quickly set object members from query columns.
 3498    ///
 3499    /// Note that this frontloads the slow reflection-based type checking for columns to only happen once at the beginni
 3500    /// and then afterwards each row of the query can invoke the delegate returned by this function to get much better p
 3501    /// </summary>
 3502    /// <typeparam name="T">The type of the destination object that the query will read into</typeparam>
 3503    /// <param name="conn">The active connection.  Note that this is primarily needed in order to read preferences regar
 3504    /// <param name="column">The table mapping used to map the statement column to a member of the destination object ty
 3505    /// <returns>
 3506    /// A delegate for fast-setting of object members from statement columns.
 3507    ///
 3508    /// If no fast setter is available for the requested column (enums in particular cause headache), then this function
 3509    /// </returns>
 3510    internal static Action<object, Sqlite3Statement, int> GetFastSetter<T> (SQLiteConnection conn, TableMapping.Column c
 3511    {
 3512      Action<object, Sqlite3Statement, int> fastSetter = null;
 3513
 3514      Type clrType = column.PropertyInfo.PropertyType;
 3515
 3516      var clrTypeInfo = clrType.GetTypeInfo ();
 3517      if (clrTypeInfo.IsGenericType && clrTypeInfo.GetGenericTypeDefinition () == typeof (Nullable<>)) {
 3518        clrType = clrTypeInfo.GenericTypeArguments[0];
 3519        clrTypeInfo = clrType.GetTypeInfo ();
 3520      }
 3521
 3522      if (clrType == typeof (String)) {
 3523        fastSetter = CreateTypedSetterDelegate<T, string> (column, (stmt, index) => {
 3524          return SQLite3.ColumnString (stmt, index);
 3525        });
 3526      }
 3527      else if (clrType == typeof (Int32)) {
 3528        fastSetter = CreateNullableTypedSetterDelegate<T, int> (column, (stmt, index)=>{
 3529          return SQLite3.ColumnInt (stmt, index);
 3530        });
 3531      }
 3532      else if (clrType == typeof (Boolean)) {
 3533        fastSetter = CreateNullableTypedSetterDelegate<T, bool> (column, (stmt, index) => {
 3534          return SQLite3.ColumnInt (stmt, index) == 1;
 3535        });
 3536      }
 3537      else if (clrType == typeof (double)) {
 3538        fastSetter = CreateNullableTypedSetterDelegate<T, double> (column, (stmt, index) => {
 3539          return SQLite3.ColumnDouble (stmt, index);
 3540        });
 3541      }
 3542      else if (clrType == typeof (float)) {
 3543        fastSetter = CreateNullableTypedSetterDelegate<T, float> (column, (stmt, index) => {
 3544          return (float) SQLite3.ColumnDouble (stmt, index);
 3545        });
 3546      }
 3547      else if (clrType == typeof (TimeSpan)) {
 3548        if (conn.StoreTimeSpanAsTicks) {
 3549          fastSetter = CreateNullableTypedSetterDelegate<T, TimeSpan> (column, (stmt, index) => {
 3550            return new TimeSpan (SQLite3.ColumnInt64 (stmt, index));
 3551          });
 3552        }
 3553        else {
 3554          fastSetter = CreateNullableTypedSetterDelegate<T, TimeSpan> (column, (stmt, index) => {
 3555            var text = SQLite3.ColumnString (stmt, index);
 3556            TimeSpan resultTime;
 3557            if (!TimeSpan.TryParseExact (text, "c", System.Globalization.CultureInfo.InvariantCulture, System.Globalizat
 3558              resultTime = TimeSpan.Parse (text);
 3559            }
 3560            return resultTime;
 3561          });
 3562        }
 3563      }
 3564      else if (clrType == typeof (DateTime)) {
 3565        if (conn.StoreDateTimeAsTicks) {
 3566          fastSetter = CreateNullableTypedSetterDelegate<T, DateTime> (column, (stmt, index) => {
 3567            return new DateTime (SQLite3.ColumnInt64 (stmt, index));
 3568          });
 3569        }
 3570        else {
 3571          fastSetter = CreateNullableTypedSetterDelegate<T, DateTime> (column, (stmt, index) => {
 3572            var text = SQLite3.ColumnString (stmt, index);
 3573            DateTime resultDate;
 3574            if (!DateTime.TryParseExact (text, conn.DateTimeStringFormat, System.Globalization.CultureInfo.InvariantCult
 3575              resultDate = DateTime.Parse (text);
 3576            }
 3577            return resultDate;
 3578          });
 3579        }
 3580      }
 3581      else if (clrType == typeof (DateTimeOffset)) {
 3582        fastSetter = CreateNullableTypedSetterDelegate<T, DateTimeOffset> (column, (stmt, index) => {
 3583          return new DateTimeOffset (SQLite3.ColumnInt64 (stmt, index), TimeSpan.Zero);
 3584        });
 3585      }
 3586      else if (clrTypeInfo.IsEnum) {
 3587        // NOTE: Not sure of a good way (if any?) to do a strongly-typed fast setter like this for enumerated types -- f
 3588      }
 3589      else if (clrType == typeof (Int64)) {
 3590        fastSetter = CreateNullableTypedSetterDelegate<T, Int64> (column, (stmt, index) => {
 3591          return SQLite3.ColumnInt64 (stmt, index);
 3592        });
 3593      }
 3594      else if (clrType == typeof(UInt64))
 3595      {
 3596        fastSetter = CreateNullableTypedSetterDelegate<T, UInt64>(column, (stmt, index) => {
 3597          return (ulong)SQLite3.ColumnInt64(stmt, index);
 3598        });
 3599      }
 3600      else if (clrType == typeof (UInt32)) {
 3601        fastSetter = CreateNullableTypedSetterDelegate<T, UInt32> (column, (stmt, index) => {
 3602          return (uint)SQLite3.ColumnInt64 (stmt, index);
 3603        });
 3604      }
 3605      else if (clrType == typeof (decimal)) {
 3606        fastSetter = CreateNullableTypedSetterDelegate<T, decimal> (column, (stmt, index) => {
 3607          return (decimal)SQLite3.ColumnDouble (stmt, index);
 3608        });
 3609      }
 3610      else if (clrType == typeof (Byte)) {
 3611        fastSetter = CreateNullableTypedSetterDelegate<T, Byte> (column, (stmt, index) => {
 3612          return (byte)SQLite3.ColumnInt (stmt, index);
 3613        });
 3614      }
 3615      else if (clrType == typeof (UInt16)) {
 3616        fastSetter = CreateNullableTypedSetterDelegate<T, UInt16> (column, (stmt, index) => {
 3617          return (ushort)SQLite3.ColumnInt (stmt, index);
 3618        });
 3619      }
 3620      else if (clrType == typeof (Int16)) {
 3621        fastSetter = CreateNullableTypedSetterDelegate<T, Int16> (column, (stmt, index) => {
 3622          return (short)SQLite3.ColumnInt (stmt, index);
 3623        });
 3624      }
 3625      else if (clrType == typeof (sbyte)) {
 3626        fastSetter = CreateNullableTypedSetterDelegate<T, sbyte> (column, (stmt, index) => {
 3627          return (sbyte)SQLite3.ColumnInt (stmt, index);
 3628        });
 3629      }
 3630      else if (clrType == typeof (byte[])) {
 3631        fastSetter = CreateTypedSetterDelegate<T, byte[]> (column, (stmt, index) => {
 3632          return SQLite3.ColumnByteArray (stmt, index);
 3633        });
 3634      }
 3635      else if (clrType == typeof (Guid)) {
 3636        fastSetter = CreateNullableTypedSetterDelegate<T, Guid> (column, (stmt, index) => {
 3637          var text = SQLite3.ColumnString (stmt, index);
 3638          return new Guid (text);
 3639        });
 3640      }
 3641      else if (clrType == typeof (Uri)) {
 3642        fastSetter = CreateTypedSetterDelegate<T, Uri> (column, (stmt, index) => {
 3643          var text = SQLite3.ColumnString (stmt, index);
 3644          return new Uri (text);
 3645        });
 3646      }
 3647      else if (clrType == typeof (StringBuilder)) {
 3648        fastSetter = CreateTypedSetterDelegate<T, StringBuilder> (column, (stmt, index) => {
 3649          var text = SQLite3.ColumnString (stmt, index);
 3650          return new StringBuilder (text);
 3651        });
 3652      }
 3653      else if (clrType == typeof (UriBuilder)) {
 3654        fastSetter = CreateTypedSetterDelegate<T, UriBuilder> (column, (stmt, index) => {
 3655          var text = SQLite3.ColumnString (stmt, index);
 3656          return new UriBuilder (text);
 3657        });
 3658      }
 3659      else {
 3660        // NOTE: Will fall back to the slow setter method in the event that we are unable to create a fast setter delega
 3661      }
 3662      return fastSetter;
 3663    }
 3664
 3665    /// <summary>
 3666    /// This creates a strongly typed delegate that will permit fast setting of column values given a Sqlite3Statement a
 3667    ///
 3668    /// Note that this is identical to CreateTypedSetterDelegate(), but has an extra check to see if it should create a 
 3669    /// </summary>
 3670    /// <typeparam name="ObjectType">The type of the object whose member column is being set</typeparam>
 3671    /// <typeparam name="ColumnMemberType">The CLR type of the member in the object which corresponds to the given SQLit
 3672    /// <param name="column">The column mapping that identifies the target member of the destination object</param>
 3673    /// <param name="getColumnValue">A lambda that can be used to retrieve the column value at query-time</param>
 3674    /// <returns>A strongly-typed delegate</returns>
 3675    private static Action<object, Sqlite3Statement, int> CreateNullableTypedSetterDelegate<ObjectType, ColumnMemberType>
 3676    {
 3677      var clrTypeInfo = column.PropertyInfo.PropertyType.GetTypeInfo();
 3678      bool isNullable = false;
 3679
 3680      if (clrTypeInfo.IsGenericType && clrTypeInfo.GetGenericTypeDefinition () == typeof (Nullable<>)) {
 3681        isNullable = true;
 3682      }
 3683
 3684      if (isNullable) {
 3685        var setProperty = (Action<ObjectType, ColumnMemberType?>)Delegate.CreateDelegate (
 3686            typeof (Action<ObjectType, ColumnMemberType?>), null,
 3687            column.PropertyInfo.GetSetMethod ());
 3688
 3689        return (o, stmt, i) => {
 3690          var colType = SQLite3.ColumnType (stmt, i);
 3691          if (colType != SQLite3.ColType.Null)
 3692            setProperty.Invoke ((ObjectType)o, getColumnValue.Invoke (stmt, i));
 3693        };
 3694      }
 3695
 3696      return CreateTypedSetterDelegate<ObjectType, ColumnMemberType> (column, getColumnValue);
 3697    }
 3698
 3699    /// <summary>
 3700    /// This creates a strongly typed delegate that will permit fast setting of column values given a Sqlite3Statement a
 3701    /// </summary>
 3702    /// <typeparam name="ObjectType">The type of the object whose member column is being set</typeparam>
 3703    /// <typeparam name="ColumnMemberType">The CLR type of the member in the object which corresponds to the given SQLit
 3704    /// <param name="column">The column mapping that identifies the target member of the destination object</param>
 3705    /// <param name="getColumnValue">A lambda that can be used to retrieve the column value at query-time</param>
 3706    /// <returns>A strongly-typed delegate</returns>
 3707    private static Action<object, Sqlite3Statement, int> CreateTypedSetterDelegate<ObjectType, ColumnMemberType> (TableM
 3708    {
 3709      var setProperty = (Action<ObjectType, ColumnMemberType>)Delegate.CreateDelegate (
 3710          typeof (Action<ObjectType, ColumnMemberType>), null,
 3711          column.PropertyInfo.GetSetMethod ());
 3712
 3713      return (o, stmt, i) => {
 3714        var colType = SQLite3.ColumnType (stmt, i);
 3715        if (colType != SQLite3.ColType.Null)
 3716          setProperty.Invoke ((ObjectType)o, getColumnValue.Invoke (stmt, i));
 3717      };
 3718    }
 3719  }
 3720
 3721  /// <summary>
 3722  /// Since the insert never changed, we only need to prepare once.
 3723  /// </summary>
 3724  class PreparedSqlLiteInsertCommand : IDisposable
 3725  {
 3726    bool Initialized;
 3727
 3728    SQLiteConnection Connection;
 3729
 3730    string CommandText;
 3731
 3732    Sqlite3Statement Statement;
 3733    static readonly Sqlite3Statement NullStatement = default (Sqlite3Statement);
 3734
 3735    public PreparedSqlLiteInsertCommand (SQLiteConnection conn, string commandText)
 3736    {
 3737      Connection = conn;
 3738      CommandText = commandText;
 3739    }
 3740
 3741    public int ExecuteNonQuery (object[] source)
 3742    {
 3743      if (Initialized && Statement == NullStatement) {
 3744        throw new ObjectDisposedException (nameof (PreparedSqlLiteInsertCommand));
 3745      }
 3746
 3747      if (Connection.Trace) {
 3748        Connection.Tracer?.Invoke ("Executing: " + CommandText);
 3749      }
 3750
 3751      var r = SQLite3.Result.OK;
 3752
 3753      if (!Initialized) {
 3754        Statement = SQLite3.Prepare2 (Connection.Handle, CommandText);
 3755        Initialized = true;
 3756      }
 3757
 3758      //bind the values.
 3759      if (source != null) {
 3760        for (int i = 0; i < source.Length; i++) {
 3761          SQLiteCommand.BindParameter (Statement, i + 1, source[i], Connection.StoreDateTimeAsTicks, Connection.DateTime
 3762        }
 3763      }
 3764      r = SQLite3.Step (Statement);
 3765
 3766      if (r == SQLite3.Result.Done) {
 3767        int rowsAffected = SQLite3.Changes (Connection.Handle);
 3768        SQLite3.Reset (Statement);
 3769        return rowsAffected;
 3770      }
 3771      else if (r == SQLite3.Result.Error) {
 3772        string msg = SQLite3.GetErrmsg (Connection.Handle);
 3773        SQLite3.Reset (Statement);
 3774        throw SQLiteException.New (r, msg);
 3775      }
 3776      else if (r == SQLite3.Result.Constraint && SQLite3.ExtendedErrCode (Connection.Handle) == SQLite3.ExtendedResult.C
 3777        SQLite3.Reset (Statement);
 3778        throw NotNullConstraintViolationException.New (r, SQLite3.GetErrmsg (Connection.Handle));
 3779      }
 3780      else {
 3781        SQLite3.Reset (Statement);
 3782        throw SQLiteException.New (r, SQLite3.GetErrmsg (Connection.Handle));
 3783      }
 3784    }
 3785
 3786    public void Dispose ()
 3787    {
 3788      Dispose (true);
 3789      GC.SuppressFinalize (this);
 3790    }
 3791
 3792    void Dispose (bool disposing)
 3793    {
 3794      var s = Statement;
 3795      Statement = NullStatement;
 3796      Connection = null;
 3797      if (s != NullStatement) {
 3798        SQLite3.Finalize (s);
 3799      }
 3800    }
 3801
 3802    ~PreparedSqlLiteInsertCommand ()
 3803    {
 3804      Dispose (false);
 3805    }
 3806  }
 3807
 3808  public enum CreateTableResult
 3809  {
 3810    Created,
 3811    Migrated,
 3812  }
 3813
 3814  public class CreateTablesResult
 3815  {
 3816    public Dictionary<Type, CreateTableResult> Results { get; private set; }
 3817
 3818    public CreateTablesResult ()
 3819    {
 3820      Results = new Dictionary<Type, CreateTableResult> ();
 3821    }
 3822  }
 3823
 3824  public abstract class BaseTableQuery
 3825  {
 3826    protected class Ordering
 3827    {
 3828      public string ColumnName { get; set; }
 3829      public bool Ascending { get; set; }
 3830    }
 3831  }
 3832
 3833  public class TableQuery<T> : BaseTableQuery, IEnumerable<T>
 3834  {
 3835    public SQLiteConnection Connection { get; private set; }
 3836
 3837    public TableMapping Table { get; private set; }
 3838
 3839    Expression _where;
 3840    List<Ordering> _orderBys;
 3841    int? _limit;
 3842    int? _offset;
 3843
 3844    BaseTableQuery _joinInner;
 3845    Expression _joinInnerKeySelector;
 3846    BaseTableQuery _joinOuter;
 3847    Expression _joinOuterKeySelector;
 3848    Expression _joinSelector;
 3849
 3850    Expression _selector;
 3851
 3852    TableQuery (SQLiteConnection conn, TableMapping table)
 3853    {
 3854      Connection = conn;
 3855      Table = table;
 3856    }
 3857
 3858    public TableQuery (SQLiteConnection conn)
 3859    {
 3860      Connection = conn;
 3861      Table = Connection.GetMapping (typeof (T));
 3862    }
 3863
 3864    public TableQuery<U> Clone<U> ()
 3865    {
 3866      var q = new TableQuery<U> (Connection, Table);
 3867      q._where = _where;
 3868      q._deferred = _deferred;
 3869      if (_orderBys != null) {
 3870        q._orderBys = new List<Ordering> (_orderBys);
 3871      }
 3872      q._limit = _limit;
 3873      q._offset = _offset;
 3874      q._joinInner = _joinInner;
 3875      q._joinInnerKeySelector = _joinInnerKeySelector;
 3876      q._joinOuter = _joinOuter;
 3877      q._joinOuterKeySelector = _joinOuterKeySelector;
 3878      q._joinSelector = _joinSelector;
 3879      q._selector = _selector;
 3880      return q;
 3881    }
 3882
 3883    /// <summary>
 3884    /// Filters the query based on a predicate.
 3885    /// </summary>
 3886    public TableQuery<T> Where (Expression<Func<T, bool>> predExpr)
 3887    {
 3888      if (predExpr.NodeType == ExpressionType.Lambda) {
 3889        var lambda = (LambdaExpression)predExpr;
 3890        var pred = lambda.Body;
 3891        var q = Clone<T> ();
 3892        q.AddWhere (pred);
 3893        return q;
 3894      }
 3895      else {
 3896        throw new NotSupportedException ("Must be a predicate");
 3897      }
 3898    }
 3899
 3900    /// <summary>
 3901    /// Delete all the rows that match this query.
 3902    /// </summary>
 3903    public int Delete ()
 3904    {
 3905      return Delete (null);
 3906    }
 3907
 3908    /// <summary>
 3909    /// Delete all the rows that match this query and the given predicate.
 3910    /// </summary>
 3911    public int Delete (Expression<Func<T, bool>> predExpr)
 3912    {
 3913      if (_limit.HasValue || _offset.HasValue)
 3914        throw new InvalidOperationException ("Cannot delete with limits or offsets");
 3915
 3916      if (_where == null && predExpr == null)
 3917        throw new InvalidOperationException ("No condition specified");
 3918
 3919      var pred = _where;
 3920
 3921      if (predExpr != null && predExpr.NodeType == ExpressionType.Lambda) {
 3922        var lambda = (LambdaExpression)predExpr;
 3923        pred = pred != null ? Expression.AndAlso (pred, lambda.Body) : lambda.Body;
 3924      }
 3925
 3926      var args = new List<object> ();
 3927      var cmdText = "delete from \"" + Table.TableName + "\"";
 3928      var w = CompileExpr (pred, args);
 3929      cmdText += " where " + w.CommandText;
 3930
 3931      var command = Connection.CreateCommand (cmdText, args.ToArray ());
 3932
 3933      int result = command.ExecuteNonQuery ();
 3934      return result;
 3935    }
 3936
 3937    /// <summary>
 3938    /// Yields a given number of elements from the query and then skips the remainder.
 3939    /// </summary>
 3940    public TableQuery<T> Take (int n)
 3941    {
 3942      var q = Clone<T> ();
 3943      q._limit = n;
 3944      return q;
 3945    }
 3946
 3947    /// <summary>
 3948    /// Skips a given number of elements from the query and then yields the remainder.
 3949    /// </summary>
 3950    public TableQuery<T> Skip (int n)
 3951    {
 3952      var q = Clone<T> ();
 3953      q._offset = n;
 3954      return q;
 3955    }
 3956
 3957    /// <summary>
 3958    /// Returns the element at a given index
 3959    /// </summary>
 3960    public T ElementAt (int index)
 3961    {
 3962      return Skip (index).Take (1).First ();
 3963    }
 3964
 3965    bool _deferred;
 3966    public TableQuery<T> Deferred ()
 3967    {
 3968      var q = Clone<T> ();
 3969      q._deferred = true;
 3970      return q;
 3971    }
 3972
 3973    /// <summary>
 3974    /// Order the query results according to a key.
 3975    /// </summary>
 3976    public TableQuery<T> OrderBy<U> (Expression<Func<T, U>> orderExpr)
 3977    {
 3978      return AddOrderBy<U> (orderExpr, true);
 3979    }
 3980
 3981    /// <summary>
 3982    /// Order the query results according to a key.
 3983    /// </summary>
 3984    public TableQuery<T> OrderByDescending<U> (Expression<Func<T, U>> orderExpr)
 3985    {
 3986      return AddOrderBy<U> (orderExpr, false);
 3987    }
 3988
 3989    /// <summary>
 3990    /// Order the query results according to a key.
 3991    /// </summary>
 3992    public TableQuery<T> ThenBy<U> (Expression<Func<T, U>> orderExpr)
 3993    {
 3994      return AddOrderBy<U> (orderExpr, true);
 3995    }
 3996
 3997    /// <summary>
 3998    /// Order the query results according to a key.
 3999    /// </summary>
 4000    public TableQuery<T> ThenByDescending<U> (Expression<Func<T, U>> orderExpr)
 4001    {
 4002      return AddOrderBy<U> (orderExpr, false);
 4003    }
 4004
 4005    TableQuery<T> AddOrderBy<U> (Expression<Func<T, U>> orderExpr, bool asc)
 4006    {
 4007      if (orderExpr.NodeType == ExpressionType.Lambda) {
 4008        var lambda = (LambdaExpression)orderExpr;
 4009
 4010        MemberExpression mem = null;
 4011
 4012        var unary = lambda.Body as UnaryExpression;
 4013        if (unary != null && unary.NodeType == ExpressionType.Convert) {
 4014          mem = unary.Operand as MemberExpression;
 4015        }
 4016        else {
 4017          mem = lambda.Body as MemberExpression;
 4018        }
 4019
 4020        if (mem != null && (mem.Expression.NodeType == ExpressionType.Parameter)) {
 4021          var q = Clone<T> ();
 4022          if (q._orderBys == null) {
 4023            q._orderBys = new List<Ordering> ();
 4024          }
 4025          q._orderBys.Add (new Ordering {
 4026            ColumnName = Table.FindColumnWithPropertyName (mem.Member.Name).Name,
 4027            Ascending = asc
 4028          });
 4029          return q;
 4030        }
 4031        else {
 4032          throw new NotSupportedException ("Order By does not support: " + orderExpr);
 4033        }
 4034      }
 4035      else {
 4036        throw new NotSupportedException ("Must be a predicate");
 4037      }
 4038    }
 4039
 4040    private void AddWhere (Expression pred)
 4041    {
 4042      if (_where == null) {
 4043        _where = pred;
 4044      }
 4045      else {
 4046        _where = Expression.AndAlso (_where, pred);
 4047      }
 4048    }
 4049
 4050    ///// <summary>
 4051    ///// Performs an inner join of two queries based on matching keys extracted from the elements.
 4052    ///// </summary>
 4053    //public TableQuery<TResult> Join<TInner, TKey, TResult> (
 4054    //  TableQuery<TInner> inner,
 4055    //  Expression<Func<T, TKey>> outerKeySelector,
 4056    //  Expression<Func<TInner, TKey>> innerKeySelector,
 4057    //  Expression<Func<T, TInner, TResult>> resultSelector)
 4058    //{
 4059    //  var q = new TableQuery<TResult> (Connection, Connection.GetMapping (typeof (TResult))) {
 4060    //    _joinOuter = this,
 4061    //    _joinOuterKeySelector = outerKeySelector,
 4062    //    _joinInner = inner,
 4063    //    _joinInnerKeySelector = innerKeySelector,
 4064    //    _joinSelector = resultSelector,
 4065    //  };
 4066    //  return q;
 4067    //}
 4068
 4069    // Not needed until Joins are supported
 4070    // Keeping this commented out forces the default Linq to objects processor to run
 4071    //public TableQuery<TResult> Select<TResult> (Expression<Func<T, TResult>> selector)
 4072    //{
 4073    //  var q = Clone<TResult> ();
 4074    //  q._selector = selector;
 4075    //  return q;
 4076    //}
 4077
 4078    private SQLiteCommand GenerateCommand (string selectionList)
 4079    {
 4080      if (_joinInner != null && _joinOuter != null) {
 4081        throw new NotSupportedException ("Joins are not supported.");
 4082      }
 4083      else {
 4084        var cmdText = "select " + selectionList + " from \"" + Table.TableName + "\"";
 4085        var args = new List<object> ();
 4086        if (_where != null) {
 4087          var w = CompileExpr (_where, args);
 4088          cmdText += " where " + w.CommandText;
 4089        }
 4090        if ((_orderBys != null) && (_orderBys.Count > 0)) {
 4091          var t = string.Join (", ", _orderBys.Select (o => "\"" + o.ColumnName + "\"" + (o.Ascending ? "" : " desc")).T
 4092          cmdText += " order by " + t;
 4093        }
 4094        if (_limit.HasValue) {
 4095          cmdText += " limit " + _limit.Value;
 4096        }
 4097        if (_offset.HasValue) {
 4098          if (!_limit.HasValue) {
 4099            cmdText += " limit -1 ";
 4100          }
 4101          cmdText += " offset " + _offset.Value;
 4102        }
 4103        return Connection.CreateCommand (cmdText, args.ToArray ());
 4104      }
 4105    }
 4106
 4107    class CompileResult
 4108    {
 4109      public string CommandText { get; set; }
 4110
 4111      public object Value { get; set; }
 4112    }
 4113
 4114    private CompileResult CompileExpr (Expression expr, List<object> queryArgs)
 4115    {
 4116      if (expr == null) {
 4117        throw new NotSupportedException ("Expression is NULL");
 4118      }
 4119      else if (expr is BinaryExpression) {
 4120        var bin = (BinaryExpression)expr;
 4121
 4122        // VB turns 'x=="foo"' into 'CompareString(x,"foo",true/false)==0', so we need to unwrap it
 4123        // http://blogs.msdn.com/b/vbteam/archive/2007/09/18/vb-expression-trees-string-comparisons.aspx
 4124        if (bin.Left.NodeType == ExpressionType.Call) {
 4125          var call = (MethodCallExpression)bin.Left;
 4126          if (call.Method.DeclaringType.FullName == "Microsoft.VisualBasic.CompilerServices.Operators"
 4127            && call.Method.Name == "CompareString")
 4128            bin = Expression.MakeBinary (bin.NodeType, call.Arguments[0], call.Arguments[1]);
 4129        }
 4130
 4131
 4132        var leftr = CompileExpr (bin.Left, queryArgs);
 4133        var rightr = CompileExpr (bin.Right, queryArgs);
 4134
 4135        //If either side is a parameter and is null, then handle the other side specially (for "is null"/"is not null")
 4136        string text;
 4137        if (leftr.CommandText == "?" && leftr.Value == null)
 4138          text = CompileNullBinaryExpression (bin, rightr);
 4139        else if (rightr.CommandText == "?" && rightr.Value == null)
 4140          text = CompileNullBinaryExpression (bin, leftr);
 4141        else
 4142          text = "(" + leftr.CommandText + " " + GetSqlName (bin) + " " + rightr.CommandText + ")";
 4143        return new CompileResult { CommandText = text };
 4144      }
 4145      else if (expr.NodeType == ExpressionType.Not) {
 4146        var operandExpr = ((UnaryExpression)expr).Operand;
 4147        var opr = CompileExpr (operandExpr, queryArgs);
 4148        object val = opr.Value;
 4149        if (val is bool)
 4150          val = !((bool)val);
 4151        return new CompileResult {
 4152          CommandText = "NOT(" + opr.CommandText + ")",
 4153          Value = val
 4154        };
 4155      }
 4156      else if (expr.NodeType == ExpressionType.Call) {
 4157
 4158        var call = (MethodCallExpression)expr;
 4159        var args = new CompileResult[call.Arguments.Count];
 4160        var obj = call.Object != null ? CompileExpr (call.Object, queryArgs) : null;
 4161
 4162        for (var i = 0; i < args.Length; i++) {
 4163          args[i] = CompileExpr (call.Arguments[i], queryArgs);
 4164        }
 4165
 4166        var sqlCall = "";
 4167
 4168        if (call.Method.Name == "Like" && args.Length == 2) {
 4169          sqlCall = "(" + args[0].CommandText + " like " + args[1].CommandText + ")";
 4170        }
 4171        else if (call.Method.Name == "Contains" && args.Length == 2) {
 4172          sqlCall = "(" + args[1].CommandText + " in " + args[0].CommandText + ")";
 4173        }
 4174        else if (call.Method.Name == "Contains" && args.Length == 1) {
 4175          if (call.Object != null && call.Object.Type == typeof (string)) {
 4176            sqlCall = "( instr(" + obj.CommandText + "," + args[0].CommandText + ") >0 )";
 4177          }
 4178          else {
 4179            sqlCall = "(" + args[0].CommandText + " in " + obj.CommandText + ")";
 4180          }
 4181        }
 4182        else if (call.Method.Name == "StartsWith" && args.Length >= 1) {
 4183          var startsWithCmpOp = StringComparison.CurrentCulture;
 4184          if (args.Length == 2) {
 4185            startsWithCmpOp = (StringComparison)args[1].Value;
 4186          }
 4187          switch (startsWithCmpOp) {
 4188            case StringComparison.Ordinal:
 4189            case StringComparison.CurrentCulture:
 4190              sqlCall = "( substr(" + obj.CommandText + ", 1, " + args[0].Value.ToString ().Length + ") =  " + args[0].C
 4191              break;
 4192            case StringComparison.OrdinalIgnoreCase:
 4193            case StringComparison.CurrentCultureIgnoreCase:
 4194              sqlCall = "(" + obj.CommandText + " like (" + args[0].CommandText + " || '%'))";
 4195              break;
 4196          }
 4197
 4198        }
 4199        else if (call.Method.Name == "EndsWith" && args.Length >= 1) {
 4200          var endsWithCmpOp = StringComparison.CurrentCulture;
 4201          if (args.Length == 2) {
 4202            endsWithCmpOp = (StringComparison)args[1].Value;
 4203          }
 4204          switch (endsWithCmpOp) {
 4205            case StringComparison.Ordinal:
 4206            case StringComparison.CurrentCulture:
 4207              sqlCall = "( substr(" + obj.CommandText + ", length(" + obj.CommandText + ") - " + args[0].Value.ToString 
 4208              break;
 4209            case StringComparison.OrdinalIgnoreCase:
 4210            case StringComparison.CurrentCultureIgnoreCase:
 4211              sqlCall = "(" + obj.CommandText + " like ('%' || " + args[0].CommandText + "))";
 4212              break;
 4213          }
 4214        }
 4215        else if (call.Method.Name == "Equals" && args.Length == 1) {
 4216          sqlCall = "(" + obj.CommandText + " = (" + args[0].CommandText + "))";
 4217        }
 4218        else if (call.Method.Name == "ToLower") {
 4219          sqlCall = "(lower(" + obj.CommandText + "))";
 4220        }
 4221        else if (call.Method.Name == "ToUpper") {
 4222          sqlCall = "(upper(" + obj.CommandText + "))";
 4223        }
 4224        else if (call.Method.Name == "Replace" && args.Length == 2) {
 4225          sqlCall = "(replace(" + obj.CommandText + "," + args[0].CommandText + "," + args[1].CommandText + "))";
 4226        }
 4227        else if (call.Method.Name == "IsNullOrEmpty" && args.Length == 1) {
 4228          sqlCall = "(" + args[0].CommandText + " is null or" + args[0].CommandText + " ='' )";
 4229        }
 4230        else {
 4231          sqlCall = call.Method.Name.ToLower () + "(" + string.Join (",", args.Select (a => a.CommandText).ToArray ()) +
 4232        }
 4233        return new CompileResult { CommandText = sqlCall };
 4234
 4235      }
 4236      else if (expr.NodeType == ExpressionType.Constant) {
 4237        var c = (ConstantExpression)expr;
 4238        queryArgs.Add (c.Value);
 4239        return new CompileResult {
 4240          CommandText = "?",
 4241          Value = c.Value
 4242        };
 4243      }
 4244      else if (expr.NodeType == ExpressionType.Convert) {
 4245        var u = (UnaryExpression)expr;
 4246        var ty = u.Type;
 4247        var valr = CompileExpr (u.Operand, queryArgs);
 4248        return new CompileResult {
 4249          CommandText = valr.CommandText,
 4250          Value = valr.Value != null ? ConvertTo (valr.Value, ty) : null
 4251        };
 4252      }
 4253      else if (expr.NodeType == ExpressionType.MemberAccess) {
 4254        var mem = (MemberExpression)expr;
 4255
 4256        var paramExpr = mem.Expression as ParameterExpression;
 4257        if (paramExpr == null) {
 4258          var convert = mem.Expression as UnaryExpression;
 4259          if (convert != null && convert.NodeType == ExpressionType.Convert) {
 4260            paramExpr = convert.Operand as ParameterExpression;
 4261          }
 4262        }
 4263
 4264        if (paramExpr != null) {
 4265          //
 4266          // This is a column of our table, output just the column name
 4267          // Need to translate it if that column name is mapped
 4268          //
 4269          var columnName = Table.FindColumnWithPropertyName (mem.Member.Name).Name;
 4270          return new CompileResult { CommandText = "\"" + columnName + "\"" };
 4271        }
 4272        else {
 4273          object obj = null;
 4274          if (mem.Expression != null) {
 4275            var r = CompileExpr (mem.Expression, queryArgs);
 4276            if (r.Value == null) {
 4277              throw new NotSupportedException ("Member access failed to compile expression");
 4278            }
 4279            if (r.CommandText == "?") {
 4280              queryArgs.RemoveAt (queryArgs.Count - 1);
 4281            }
 4282            obj = r.Value;
 4283          }
 4284
 4285          //
 4286          // Get the member value
 4287          //
 4288          object val = null;
 4289
 4290          if (mem.Member is PropertyInfo) {
 4291            var m = (PropertyInfo)mem.Member;
 4292            val = m.GetValue (obj, null);
 4293          }
 4294          else if (mem.Member is FieldInfo) {
 4295            var m = (FieldInfo)mem.Member;
 4296            val = m.GetValue (obj);
 4297          }
 4298          else {
 4299            throw new NotSupportedException ("MemberExpr: " + mem.Member.GetType ());
 4300          }
 4301
 4302          //
 4303          // Work special magic for enumerables
 4304          //
 4305          if (val != null && val is System.Collections.IEnumerable && !(val is string) && !(val is System.Collections.Ge
 4306            var sb = new System.Text.StringBuilder ();
 4307            sb.Append ("(");
 4308            var head = "";
 4309            foreach (var a in (System.Collections.IEnumerable)val) {
 4310              queryArgs.Add (a);
 4311              sb.Append (head);
 4312              sb.Append ("?");
 4313              head = ",";
 4314            }
 4315            sb.Append (")");
 4316            return new CompileResult {
 4317              CommandText = sb.ToString (),
 4318              Value = val
 4319            };
 4320          }
 4321          else {
 4322            queryArgs.Add (val);
 4323            return new CompileResult {
 4324              CommandText = "?",
 4325              Value = val
 4326            };
 4327          }
 4328        }
 4329      }
 4330      throw new NotSupportedException ("Cannot compile: " + expr.NodeType.ToString ());
 4331    }
 4332
 4333    static object ConvertTo (object obj, Type t)
 4334    {
 4335      Type nut = Nullable.GetUnderlyingType (t);
 4336
 4337      if (nut != null) {
 4338        if (obj == null)
 4339          return null;
 4340        return Convert.ChangeType (obj, nut);
 4341      }
 4342      else {
 4343        return Convert.ChangeType (obj, t);
 4344      }
 4345    }
 4346
 4347    /// <summary>
 4348    /// Compiles a BinaryExpression where one of the parameters is null.
 4349    /// </summary>
 4350    /// <param name="expression">The expression to compile</param>
 4351    /// <param name="parameter">The non-null parameter</param>
 4352    private string CompileNullBinaryExpression (BinaryExpression expression, CompileResult parameter)
 4353    {
 4354      if (expression.NodeType == ExpressionType.Equal)
 4355        return "(" + parameter.CommandText + " is ?)";
 4356      else if (expression.NodeType == ExpressionType.NotEqual)
 4357        return "(" + parameter.CommandText + " is not ?)";
 4358      else if (expression.NodeType == ExpressionType.GreaterThan
 4359        || expression.NodeType == ExpressionType.GreaterThanOrEqual
 4360        || expression.NodeType == ExpressionType.LessThan
 4361        || expression.NodeType == ExpressionType.LessThanOrEqual)
 4362        return "(" + parameter.CommandText + " < ?)"; // always false
 4363      else
 4364        throw new NotSupportedException ("Cannot compile Null-BinaryExpression with type " + expression.NodeType.ToStrin
 4365    }
 4366
 4367    string GetSqlName (Expression expr)
 4368    {
 4369      var n = expr.NodeType;
 4370      if (n == ExpressionType.GreaterThan)
 4371        return ">";
 4372      else if (n == ExpressionType.GreaterThanOrEqual) {
 4373        return ">=";
 4374      }
 4375      else if (n == ExpressionType.LessThan) {
 4376        return "<";
 4377      }
 4378      else if (n == ExpressionType.LessThanOrEqual) {
 4379        return "<=";
 4380      }
 4381      else if (n == ExpressionType.And) {
 4382        return "&";
 4383      }
 4384      else if (n == ExpressionType.AndAlso) {
 4385        return "and";
 4386      }
 4387      else if (n == ExpressionType.Or) {
 4388        return "|";
 4389      }
 4390      else if (n == ExpressionType.OrElse) {
 4391        return "or";
 4392      }
 4393      else if (n == ExpressionType.Equal) {
 4394        return "=";
 4395      }
 4396      else if (n == ExpressionType.NotEqual) {
 4397        return "!=";
 4398      }
 4399      else {
 4400        throw new NotSupportedException ("Cannot get SQL for: " + n);
 4401      }
 4402    }
 4403
 4404    /// <summary>
 4405    /// Execute SELECT COUNT(*) on the query
 4406    /// </summary>
 4407    public int Count ()
 4408    {
 4409      return GenerateCommand ("count(*)").ExecuteScalar<int> ();
 4410    }
 4411
 4412    /// <summary>
 4413    /// Execute SELECT COUNT(*) on the query with an additional WHERE clause.
 4414    /// </summary>
 4415    public int Count (Expression<Func<T, bool>> predExpr)
 4416    {
 4417      return Where (predExpr).Count ();
 4418    }
 4419
 4420    public IEnumerator<T> GetEnumerator ()
 4421    {
 4422      if (!_deferred)
 4423        return GenerateCommand ("*").ExecuteQuery<T> ().GetEnumerator ();
 4424
 4425      return GenerateCommand ("*").ExecuteDeferredQuery<T> ().GetEnumerator ();
 4426    }
 4427
 4428    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator ()
 4429    {
 4430      return GetEnumerator ();
 4431    }
 4432
 4433    /// <summary>
 4434    /// Queries the database and returns the results as a List.
 4435    /// </summary>
 4436    public List<T> ToList ()
 4437    {
 4438      return GenerateCommand ("*").ExecuteQuery<T> ();
 4439    }
 4440
 4441    /// <summary>
 4442    /// Queries the database and returns the results as an array.
 4443    /// </summary>
 4444    public T[] ToArray ()
 4445    {
 4446      return GenerateCommand ("*").ExecuteQuery<T> ().ToArray ();
 4447    }
 4448
 4449    /// <summary>
 4450    /// Returns the first element of this query.
 4451    /// </summary>
 4452    public T First ()
 4453    {
 4454      var query = Take (1);
 4455      return query.ToList ().First ();
 4456    }
 4457
 4458    /// <summary>
 4459    /// Returns the first element of this query, or null if no element is found.
 4460    /// </summary>
 4461    public T FirstOrDefault ()
 4462    {
 4463      var query = Take (1);
 4464      return query.ToList ().FirstOrDefault ();
 4465    }
 4466
 4467    /// <summary>
 4468    /// Returns the first element of this query that matches the predicate.
 4469    /// </summary>
 4470    public T First (Expression<Func<T, bool>> predExpr)
 4471    {
 4472      return Where (predExpr).First ();
 4473    }
 4474
 4475    /// <summary>
 4476    /// Returns the first element of this query that matches the predicate, or null
 4477    /// if no element is found.
 4478    /// </summary>
 4479    public T FirstOrDefault (Expression<Func<T, bool>> predExpr)
 4480    {
 4481      return Where (predExpr).FirstOrDefault ();
 4482    }
 4483  }
 4484
 4485  public static class SQLite3
 4486  {
 4487    public enum Result : int
 4488    {
 4489      OK = 0,
 4490      Error = 1,
 4491      Internal = 2,
 4492      Perm = 3,
 4493      Abort = 4,
 4494      Busy = 5,
 4495      Locked = 6,
 4496      NoMem = 7,
 4497      ReadOnly = 8,
 4498      Interrupt = 9,
 4499      IOError = 10,
 4500      Corrupt = 11,
 4501      NotFound = 12,
 4502      Full = 13,
 4503      CannotOpen = 14,
 4504      LockErr = 15,
 4505      Empty = 16,
 4506      SchemaChngd = 17,
 4507      TooBig = 18,
 4508      Constraint = 19,
 4509      Mismatch = 20,
 4510      Misuse = 21,
 4511      NotImplementedLFS = 22,
 4512      AccessDenied = 23,
 4513      Format = 24,
 4514      Range = 25,
 4515      NonDBFile = 26,
 4516      Notice = 27,
 4517      Warning = 28,
 4518      Row = 100,
 4519      Done = 101
 4520    }
 4521
 4522    public enum ExtendedResult : int
 4523    {
 4524      IOErrorRead = (Result.IOError | (1 << 8)),
 4525      IOErrorShortRead = (Result.IOError | (2 << 8)),
 4526      IOErrorWrite = (Result.IOError | (3 << 8)),
 4527      IOErrorFsync = (Result.IOError | (4 << 8)),
 4528      IOErrorDirFSync = (Result.IOError | (5 << 8)),
 4529      IOErrorTruncate = (Result.IOError | (6 << 8)),
 4530      IOErrorFStat = (Result.IOError | (7 << 8)),
 4531      IOErrorUnlock = (Result.IOError | (8 << 8)),
 4532      IOErrorRdlock = (Result.IOError | (9 << 8)),
 4533      IOErrorDelete = (Result.IOError | (10 << 8)),
 4534      IOErrorBlocked = (Result.IOError | (11 << 8)),
 4535      IOErrorNoMem = (Result.IOError | (12 << 8)),
 4536      IOErrorAccess = (Result.IOError | (13 << 8)),
 4537      IOErrorCheckReservedLock = (Result.IOError | (14 << 8)),
 4538      IOErrorLock = (Result.IOError | (15 << 8)),
 4539      IOErrorClose = (Result.IOError | (16 << 8)),
 4540      IOErrorDirClose = (Result.IOError | (17 << 8)),
 4541      IOErrorSHMOpen = (Result.IOError | (18 << 8)),
 4542      IOErrorSHMSize = (Result.IOError | (19 << 8)),
 4543      IOErrorSHMLock = (Result.IOError | (20 << 8)),
 4544      IOErrorSHMMap = (Result.IOError | (21 << 8)),
 4545      IOErrorSeek = (Result.IOError | (22 << 8)),
 4546      IOErrorDeleteNoEnt = (Result.IOError | (23 << 8)),
 4547      IOErrorMMap = (Result.IOError | (24 << 8)),
 4548      LockedSharedcache = (Result.Locked | (1 << 8)),
 4549      BusyRecovery = (Result.Busy | (1 << 8)),
 4550      CannottOpenNoTempDir = (Result.CannotOpen | (1 << 8)),
 4551      CannotOpenIsDir = (Result.CannotOpen | (2 << 8)),
 4552      CannotOpenFullPath = (Result.CannotOpen | (3 << 8)),
 4553      CorruptVTab = (Result.Corrupt | (1 << 8)),
 4554      ReadonlyRecovery = (Result.ReadOnly | (1 << 8)),
 4555      ReadonlyCannotLock = (Result.ReadOnly | (2 << 8)),
 4556      ReadonlyRollback = (Result.ReadOnly | (3 << 8)),
 4557      AbortRollback = (Result.Abort | (2 << 8)),
 4558      ConstraintCheck = (Result.Constraint | (1 << 8)),
 4559      ConstraintCommitHook = (Result.Constraint | (2 << 8)),
 4560      ConstraintForeignKey = (Result.Constraint | (3 << 8)),
 4561      ConstraintFunction = (Result.Constraint | (4 << 8)),
 4562      ConstraintNotNull = (Result.Constraint | (5 << 8)),
 4563      ConstraintPrimaryKey = (Result.Constraint | (6 << 8)),
 4564      ConstraintTrigger = (Result.Constraint | (7 << 8)),
 4565      ConstraintUnique = (Result.Constraint | (8 << 8)),
 4566      ConstraintVTab = (Result.Constraint | (9 << 8)),
 4567      NoticeRecoverWAL = (Result.Notice | (1 << 8)),
 4568      NoticeRecoverRollback = (Result.Notice | (2 << 8))
 4569    }
 4570
 4571
 4572    public enum ConfigOption : int
 4573    {
 4574      SingleThread = 1,
 4575      MultiThread = 2,
 4576      Serialized = 3
 4577    }
 4578
 4579    const string LibraryPath = "sqlite3";
 4580
 4581#if !USE_CSHARP_SQLITE && !USE_WP8_NATIVE_SQLITE && !USE_SQLITEPCL_RAW
 4582    [DllImport(LibraryPath, EntryPoint = "sqlite3_threadsafe", CallingConvention=CallingConvention.Cdecl)]
 4583    public static extern int Threadsafe ();
 4584
 4585    [DllImport(LibraryPath, EntryPoint = "sqlite3_open", CallingConvention=CallingConvention.Cdecl)]
 4586    public static extern Result Open ([MarshalAs(UnmanagedType.LPStr)] string filename, out IntPtr db);
 4587
 4588    [DllImport(LibraryPath, EntryPoint = "sqlite3_open_v2", CallingConvention=CallingConvention.Cdecl)]
 4589    public static extern Result Open ([MarshalAs(UnmanagedType.LPStr)] string filename, out IntPtr db, int flags, [Marsh
 4590
 4591    [DllImport(LibraryPath, EntryPoint = "sqlite3_open_v2", CallingConvention = CallingConvention.Cdecl)]
 4592    public static extern Result Open(byte[] filename, out IntPtr db, int flags, [MarshalAs (UnmanagedType.LPStr)] string
 4593
 4594    [DllImport(LibraryPath, EntryPoint = "sqlite3_open16", CallingConvention = CallingConvention.Cdecl)]
 4595    public static extern Result Open16([MarshalAs(UnmanagedType.LPWStr)] string filename, out IntPtr db);
 4596
 4597    [DllImport(LibraryPath, EntryPoint = "sqlite3_enable_load_extension", CallingConvention=CallingConvention.Cdecl)]
 4598    public static extern Result EnableLoadExtension (IntPtr db, int onoff);
 4599
 4600    [DllImport(LibraryPath, EntryPoint = "sqlite3_close", CallingConvention=CallingConvention.Cdecl)]
 4601    public static extern Result Close (IntPtr db);
 4602
 4603    [DllImport(LibraryPath, EntryPoint = "sqlite3_close_v2", CallingConvention = CallingConvention.Cdecl)]
 4604    public static extern Result Close2(IntPtr db);
 4605
 4606    [DllImport(LibraryPath, EntryPoint = "sqlite3_initialize", CallingConvention=CallingConvention.Cdecl)]
 4607    public static extern Result Initialize();
 4608
 4609    [DllImport(LibraryPath, EntryPoint = "sqlite3_shutdown", CallingConvention=CallingConvention.Cdecl)]
 4610    public static extern Result Shutdown();
 4611
 4612    [DllImport(LibraryPath, EntryPoint = "sqlite3_config", CallingConvention=CallingConvention.Cdecl)]
 4613    public static extern Result Config (ConfigOption option);
 4614
 4615    [DllImport(LibraryPath, EntryPoint = "sqlite3_win32_set_directory", CallingConvention=CallingConvention.Cdecl, CharS
 4616    public static extern int SetDirectory (uint directoryType, string directoryPath);
 4617
 4618    [DllImport(LibraryPath, EntryPoint = "sqlite3_busy_timeout", CallingConvention=CallingConvention.Cdecl)]
 4619    public static extern Result BusyTimeout (IntPtr db, int milliseconds);
 4620
 4621    [DllImport(LibraryPath, EntryPoint = "sqlite3_changes", CallingConvention=CallingConvention.Cdecl)]
 4622    public static extern int Changes (IntPtr db);
 4623
 4624    [DllImport(LibraryPath, EntryPoint = "sqlite3_prepare_v2", CallingConvention=CallingConvention.Cdecl)]
 4625    public static extern Result Prepare2 (IntPtr db, [MarshalAs(UnmanagedType.LPStr)] string sql, int numBytes, out IntP
 4626
 4627#if NETFX_CORE
 4628    [DllImport (LibraryPath, EntryPoint = "sqlite3_prepare_v2", CallingConvention = CallingConvention.Cdecl)]
 4629    public static extern Result Prepare2 (IntPtr db, byte[] queryBytes, int numBytes, out IntPtr stmt, IntPtr pzTail);
 4630#endif
 4631
 4632    public static IntPtr Prepare2 (IntPtr db, string query)
 4633    {
 4634      IntPtr stmt;
 4635#if NETFX_CORE
 4636            byte[] queryBytes = System.Text.UTF8Encoding.UTF8.GetBytes (query);
 4637            var r = Prepare2 (db, queryBytes, queryBytes.Length, out stmt, IntPtr.Zero);
 4638#else
 4639            var r = Prepare2 (db, query, System.Text.UTF8Encoding.UTF8.GetByteCount (query), out stmt, IntPtr.Zero);
 4640#endif
 4641      if (r != Result.OK) {
 4642        throw SQLiteException.New (r, GetErrmsg (db));
 4643      }
 4644      return stmt;
 4645    }
 4646
 4647    [DllImport(LibraryPath, EntryPoint = "sqlite3_step", CallingConvention=CallingConvention.Cdecl)]
 4648    public static extern Result Step (IntPtr stmt);
 4649
 4650    [DllImport(LibraryPath, EntryPoint = "sqlite3_reset", CallingConvention=CallingConvention.Cdecl)]
 4651    public static extern Result Reset (IntPtr stmt);
 4652
 4653    [DllImport(LibraryPath, EntryPoint = "sqlite3_finalize", CallingConvention=CallingConvention.Cdecl)]
 4654    public static extern Result Finalize (IntPtr stmt);
 4655
 4656    [DllImport(LibraryPath, EntryPoint = "sqlite3_last_insert_rowid", CallingConvention=CallingConvention.Cdecl)]
 4657    public static extern long LastInsertRowid (IntPtr db);
 4658
 4659    [DllImport(LibraryPath, EntryPoint = "sqlite3_errmsg16", CallingConvention=CallingConvention.Cdecl)]
 4660    public static extern IntPtr Errmsg (IntPtr db);
 4661
 4662    public static string GetErrmsg (IntPtr db)
 4663    {
 4664      return Marshal.PtrToStringUni (Errmsg (db));
 4665    }
 4666
 4667    [DllImport(LibraryPath, EntryPoint = "sqlite3_bind_parameter_index", CallingConvention=CallingConvention.Cdecl)]
 4668    public static extern int BindParameterIndex (IntPtr stmt, [MarshalAs(UnmanagedType.LPStr)] string name);
 4669
 4670    [DllImport(LibraryPath, EntryPoint = "sqlite3_bind_null", CallingConvention=CallingConvention.Cdecl)]
 4671    public static extern int BindNull (IntPtr stmt, int index);
 4672
 4673    [DllImport(LibraryPath, EntryPoint = "sqlite3_bind_int", CallingConvention=CallingConvention.Cdecl)]
 4674    public static extern int BindInt (IntPtr stmt, int index, int val);
 4675
 4676    [DllImport(LibraryPath, EntryPoint = "sqlite3_bind_int64", CallingConvention=CallingConvention.Cdecl)]
 4677    public static extern int BindInt64 (IntPtr stmt, int index, long val);
 4678
 4679    [DllImport(LibraryPath, EntryPoint = "sqlite3_bind_double", CallingConvention=CallingConvention.Cdecl)]
 4680    public static extern int BindDouble (IntPtr stmt, int index, double val);
 4681
 4682    [DllImport(LibraryPath, EntryPoint = "sqlite3_bind_text16", CallingConvention=CallingConvention.Cdecl, CharSet = Cha
 4683    public static extern int BindText (IntPtr stmt, int index, [MarshalAs(UnmanagedType.LPWStr)] string val, int n, IntP
 4684
 4685    [DllImport(LibraryPath, EntryPoint = "sqlite3_bind_blob", CallingConvention=CallingConvention.Cdecl)]
 4686    public static extern int BindBlob (IntPtr stmt, int index, byte[] val, int n, IntPtr free);
 4687
 4688    [DllImport(LibraryPath, EntryPoint = "sqlite3_column_count", CallingConvention=CallingConvention.Cdecl)]
 4689    public static extern int ColumnCount (IntPtr stmt);
 4690
 4691    [DllImport(LibraryPath, EntryPoint = "sqlite3_column_name", CallingConvention=CallingConvention.Cdecl)]
 4692    public static extern IntPtr ColumnName (IntPtr stmt, int index);
 4693
 4694    [DllImport(LibraryPath, EntryPoint = "sqlite3_column_name16", CallingConvention=CallingConvention.Cdecl)]
 4695    static extern IntPtr ColumnName16Internal (IntPtr stmt, int index);
 4696    public static string ColumnName16(IntPtr stmt, int index)
 4697    {
 4698      return Marshal.PtrToStringUni(ColumnName16Internal(stmt, index));
 4699    }
 4700
 4701    [DllImport(LibraryPath, EntryPoint = "sqlite3_column_type", CallingConvention=CallingConvention.Cdecl)]
 4702    public static extern ColType ColumnType (IntPtr stmt, int index);
 4703
 4704    [DllImport(LibraryPath, EntryPoint = "sqlite3_column_int", CallingConvention=CallingConvention.Cdecl)]
 4705    public static extern int ColumnInt (IntPtr stmt, int index);
 4706
 4707    [DllImport(LibraryPath, EntryPoint = "sqlite3_column_int64", CallingConvention=CallingConvention.Cdecl)]
 4708    public static extern long ColumnInt64 (IntPtr stmt, int index);
 4709
 4710    [DllImport(LibraryPath, EntryPoint = "sqlite3_column_double", CallingConvention=CallingConvention.Cdecl)]
 4711    public static extern double ColumnDouble (IntPtr stmt, int index);
 4712
 4713    [DllImport(LibraryPath, EntryPoint = "sqlite3_column_text", CallingConvention=CallingConvention.Cdecl)]
 4714    public static extern IntPtr ColumnText (IntPtr stmt, int index);
 4715
 4716    [DllImport(LibraryPath, EntryPoint = "sqlite3_column_text16", CallingConvention=CallingConvention.Cdecl)]
 4717    public static extern IntPtr ColumnText16 (IntPtr stmt, int index);
 4718
 4719    [DllImport(LibraryPath, EntryPoint = "sqlite3_column_blob", CallingConvention=CallingConvention.Cdecl)]
 4720    public static extern IntPtr ColumnBlob (IntPtr stmt, int index);
 4721
 4722    [DllImport(LibraryPath, EntryPoint = "sqlite3_column_bytes", CallingConvention=CallingConvention.Cdecl)]
 4723    public static extern int ColumnBytes (IntPtr stmt, int index);
 4724
 4725    public static string ColumnString (IntPtr stmt, int index)
 4726    {
 4727      return Marshal.PtrToStringUni (SQLite3.ColumnText16 (stmt, index));
 4728    }
 4729
 4730    public static byte[] ColumnByteArray (IntPtr stmt, int index)
 4731    {
 4732      int length = ColumnBytes (stmt, index);
 4733      var result = new byte[length];
 4734      if (length > 0)
 4735        Marshal.Copy (ColumnBlob (stmt, index), result, 0, length);
 4736      return result;
 4737    }
 4738
 4739    [DllImport (LibraryPath, EntryPoint = "sqlite3_errcode", CallingConvention = CallingConvention.Cdecl)]
 4740    public static extern Result GetResult (Sqlite3DatabaseHandle db);
 4741
 4742    [DllImport (LibraryPath, EntryPoint = "sqlite3_extended_errcode", CallingConvention = CallingConvention.Cdecl)]
 4743    public static extern ExtendedResult ExtendedErrCode (IntPtr db);
 4744
 4745    [DllImport (LibraryPath, EntryPoint = "sqlite3_libversion_number", CallingConvention = CallingConvention.Cdecl)]
 4746    public static extern int LibVersionNumber ();
 4747
 4748    [DllImport (LibraryPath, EntryPoint = "sqlite3_backup_init", CallingConvention = CallingConvention.Cdecl)]
 4749    public static extern Sqlite3BackupHandle BackupInit (Sqlite3DatabaseHandle destDb, [MarshalAs (UnmanagedType.LPStr)]
 4750
 4751    [DllImport (LibraryPath, EntryPoint = "sqlite3_backup_step", CallingConvention = CallingConvention.Cdecl)]
 4752    public static extern Result BackupStep (Sqlite3BackupHandle backup, int numPages);
 4753
 4754    [DllImport (LibraryPath, EntryPoint = "sqlite3_backup_finish", CallingConvention = CallingConvention.Cdecl)]
 4755    public static extern Result BackupFinish (Sqlite3BackupHandle backup);
 4756#else
 4757    public static Result Open (string filename, out Sqlite3DatabaseHandle db)
 4758    {
 4759      return (Result)Sqlite3.sqlite3_open (filename, out db);
 4760    }
 4761
 4762    public static Result Open (string filename, out Sqlite3DatabaseHandle db, int flags, string vfsName)
 4763    {
 4764#if USE_WP8_NATIVE_SQLITE
 4765      return (Result)Sqlite3.sqlite3_open_v2(filename, out db, flags, vfsName ?? "");
 4766#else
 4767      return (Result)Sqlite3.sqlite3_open_v2 (filename, out db, flags, vfsName);
 4768#endif
 4769    }
 4770
 4771    public static Result Close (Sqlite3DatabaseHandle db)
 4772    {
 4773      return (Result)Sqlite3.sqlite3_close (db);
 4774    }
 4775
 4776    public static Result Close2 (Sqlite3DatabaseHandle db)
 4777    {
 4778      return (Result)Sqlite3.sqlite3_close_v2 (db);
 4779    }
 4780
 4781    public static Result BusyTimeout (Sqlite3DatabaseHandle db, int milliseconds)
 4782    {
 4783      return (Result)Sqlite3.sqlite3_busy_timeout (db, milliseconds);
 4784    }
 4785
 4786    public static int Changes (Sqlite3DatabaseHandle db)
 4787    {
 4788      return Sqlite3.sqlite3_changes (db);
 4789    }
 4790
 4791    public static Sqlite3Statement Prepare2 (Sqlite3DatabaseHandle db, string query)
 4792    {
 4793      Sqlite3Statement stmt = default (Sqlite3Statement);
 4794#if USE_WP8_NATIVE_SQLITE || USE_SQLITEPCL_RAW
 4795      var r = Sqlite3.sqlite3_prepare_v2 (db, query, out stmt);
 4796#else
 4797      stmt = new Sqlite3Statement();
 4798      var r = Sqlite3.sqlite3_prepare_v2(db, query, -1, ref stmt, 0);
 4799#endif
 4800      if (r != 0) {
 4801        throw SQLiteException.New ((Result)r, GetErrmsg (db));
 4802      }
 4803      return stmt;
 4804    }
 4805
 4806    public static Result Step (Sqlite3Statement stmt)
 4807    {
 4808      return (Result)Sqlite3.sqlite3_step (stmt);
 4809    }
 4810
 4811    public static Result Reset (Sqlite3Statement stmt)
 4812    {
 4813      return (Result)Sqlite3.sqlite3_reset (stmt);
 4814    }
 4815
 4816    public static Result Finalize (Sqlite3Statement stmt)
 4817    {
 4818      return (Result)Sqlite3.sqlite3_finalize (stmt);
 4819    }
 4820
 4821    public static long LastInsertRowid (Sqlite3DatabaseHandle db)
 4822    {
 4823      return Sqlite3.sqlite3_last_insert_rowid (db);
 4824    }
 4825
 4826    public static string GetErrmsg (Sqlite3DatabaseHandle db)
 4827    {
 4828      return Sqlite3.sqlite3_errmsg (db).utf8_to_string ();
 4829    }
 4830
 4831    public static int BindParameterIndex (Sqlite3Statement stmt, string name)
 4832    {
 4833      return Sqlite3.sqlite3_bind_parameter_index (stmt, name);
 4834    }
 4835
 4836    public static int BindNull (Sqlite3Statement stmt, int index)
 4837    {
 4838      return Sqlite3.sqlite3_bind_null (stmt, index);
 4839    }
 4840
 4841    public static int BindInt (Sqlite3Statement stmt, int index, int val)
 4842    {
 4843      return Sqlite3.sqlite3_bind_int (stmt, index, val);
 4844    }
 4845
 4846    public static int BindInt64 (Sqlite3Statement stmt, int index, long val)
 4847    {
 4848      return Sqlite3.sqlite3_bind_int64 (stmt, index, val);
 4849    }
 4850
 4851    public static int BindDouble (Sqlite3Statement stmt, int index, double val)
 4852    {
 4853      return Sqlite3.sqlite3_bind_double (stmt, index, val);
 4854    }
 4855
 4856    public static int BindText (Sqlite3Statement stmt, int index, string val, int n, IntPtr free)
 4857    {
 4858#if USE_WP8_NATIVE_SQLITE
 4859      return Sqlite3.sqlite3_bind_text(stmt, index, val, n);
 4860#elif USE_SQLITEPCL_RAW
 4861      return Sqlite3.sqlite3_bind_text (stmt, index, val);
 4862#else
 4863      return Sqlite3.sqlite3_bind_text(stmt, index, val, n, null);
 4864#endif
 4865    }
 4866
 4867    public static int BindBlob (Sqlite3Statement stmt, int index, byte[] val, int n, IntPtr free)
 4868    {
 4869#if USE_WP8_NATIVE_SQLITE
 4870      return Sqlite3.sqlite3_bind_blob(stmt, index, val, n);
 4871#elif USE_SQLITEPCL_RAW
 4872      return Sqlite3.sqlite3_bind_blob (stmt, index, val);
 4873#else
 4874      return Sqlite3.sqlite3_bind_blob(stmt, index, val, n, null);
 4875#endif
 4876    }
 4877
 4878    public static int ColumnCount (Sqlite3Statement stmt)
 4879    {
 4880      return Sqlite3.sqlite3_column_count (stmt);
 4881    }
 4882
 4883    public static string ColumnName (Sqlite3Statement stmt, int index)
 4884    {
 4885      return Sqlite3.sqlite3_column_name (stmt, index).utf8_to_string ();
 4886    }
 4887
 4888    public static string ColumnName16 (Sqlite3Statement stmt, int index)
 4889    {
 4890      return Sqlite3.sqlite3_column_name (stmt, index).utf8_to_string ();
 4891    }
 4892
 4893    public static ColType ColumnType (Sqlite3Statement stmt, int index)
 4894    {
 4895      return (ColType)Sqlite3.sqlite3_column_type (stmt, index);
 4896    }
 4897
 4898    public static int ColumnInt (Sqlite3Statement stmt, int index)
 4899    {
 4900      return Sqlite3.sqlite3_column_int (stmt, index);
 4901    }
 4902
 4903    public static long ColumnInt64 (Sqlite3Statement stmt, int index)
 4904    {
 4905      return Sqlite3.sqlite3_column_int64 (stmt, index);
 4906    }
 4907
 4908    public static double ColumnDouble (Sqlite3Statement stmt, int index)
 4909    {
 4910      return Sqlite3.sqlite3_column_double (stmt, index);
 4911    }
 4912
 4913    public static string ColumnText (Sqlite3Statement stmt, int index)
 4914    {
 4915      return Sqlite3.sqlite3_column_text (stmt, index).utf8_to_string ();
 4916    }
 4917
 4918    public static string ColumnText16 (Sqlite3Statement stmt, int index)
 4919    {
 4920      return Sqlite3.sqlite3_column_text (stmt, index).utf8_to_string ();
 4921    }
 4922
 4923    public static byte[] ColumnBlob (Sqlite3Statement stmt, int index)
 4924    {
 4925      return Sqlite3.sqlite3_column_blob (stmt, index).ToArray ();
 4926    }
 4927
 4928    public static int ColumnBytes (Sqlite3Statement stmt, int index)
 4929    {
 4930      return Sqlite3.sqlite3_column_bytes (stmt, index);
 4931    }
 4932
 4933    public static string ColumnString (Sqlite3Statement stmt, int index)
 4934    {
 4935      return Sqlite3.sqlite3_column_text (stmt, index).utf8_to_string ();
 4936    }
 4937
 4938    public static byte[] ColumnByteArray (Sqlite3Statement stmt, int index)
 4939    {
 4940      int length = ColumnBytes (stmt, index);
 4941      if (length > 0) {
 4942        return ColumnBlob (stmt, index);
 4943      }
 4944      return new byte[0];
 4945    }
 4946
 4947    public static Result EnableLoadExtension (Sqlite3DatabaseHandle db, int onoff)
 4948    {
 4949      return (Result)Sqlite3.sqlite3_enable_load_extension (db, onoff);
 4950    }
 4951
 4952    public static int LibVersionNumber ()
 4953    {
 4954      return Sqlite3.sqlite3_libversion_number ();
 4955    }
 4956
 4957    public static Result GetResult (Sqlite3DatabaseHandle db)
 4958    {
 4959      return (Result)Sqlite3.sqlite3_errcode (db);
 4960    }
 4961
 4962    public static ExtendedResult ExtendedErrCode (Sqlite3DatabaseHandle db)
 4963    {
 4964      return (ExtendedResult)Sqlite3.sqlite3_extended_errcode (db);
 4965    }
 4966
 4967    public static Sqlite3BackupHandle BackupInit (Sqlite3DatabaseHandle destDb, string destName, Sqlite3DatabaseHandle s
 4968    {
 4969      return Sqlite3.sqlite3_backup_init (destDb, destName, sourceDb, sourceName);
 4970    }
 4971
 4972    public static Result BackupStep (Sqlite3BackupHandle backup, int numPages)
 4973    {
 4974      return (Result)Sqlite3.sqlite3_backup_step (backup, numPages);
 4975    }
 4976
 4977    public static Result BackupFinish (Sqlite3BackupHandle backup)
 4978    {
 4979      return (Result)Sqlite3.sqlite3_backup_finish (backup);
 4980    }
 4981#endif
 4982
 4983    public enum ColType : int
 4984    {
 4985      Integer = 1,
 4986      Float = 2,
 4987      Text = 3,
 4988      Blob = 4,
 4989      Null = 5
 4990    }
 4991  }
 4992}