< Summary

Information
Class: SQLite.EnumCacheInfo
Assembly: SQLite.Tests
File(s): /home/runner/work/sqlite-net/sqlite-net/src/SQLite.cs
Tag: 197_13668160376
Line coverage
100%
Covered lines: 14
Uncovered lines: 0
Coverable lines: 14
Total lines: 5445
Line coverage: 100%
Branch coverage
100%
Covered branches: 6
Total branches: 6
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
.ctor(...)100%668100%

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

Methods/Properties

.ctor(System.Type)