PageRenderTime 44ms CodeModel.GetById 1ms RepoModel.GetById 0ms app.codeStats 1ms

/src/ZebraFlickr/Core/Store/SQLite/SQLite.cs

https://bitbucket.org/garethl/zebraflickr
C# | 2980 lines | 2180 code | 337 blank | 463 comment | 422 complexity | 0004f7bedbb3202324210f3f13ede173 MD5 | raw file
  1. //
  2. // Copyright (c) 2009-2012 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
  23. #define USE_CSHARP_SQLITE
  24. #endif
  25. using System;
  26. using System.Diagnostics;
  27. using System.Runtime.InteropServices;
  28. using System.Collections.Generic;
  29. using System.Reflection;
  30. using System.Linq;
  31. using System.Linq.Expressions;
  32. using System.Threading;
  33. #if USE_CSHARP_SQLITE
  34. using Community.CsharpSqlite;
  35. using Sqlite3DatabaseHandle = Community.CsharpSqlite.Sqlite3.sqlite3;
  36. using Sqlite3Statement = Community.CsharpSqlite.Sqlite3.Vdbe;
  37. #else
  38. using Sqlite3DatabaseHandle = System.IntPtr;
  39. using Sqlite3Statement = System.IntPtr;
  40. #endif
  41. namespace SQLite
  42. {
  43. public class SQLiteException : System.Exception
  44. {
  45. public SQLite3.Result Result { get; private set; }
  46. protected SQLiteException(SQLite3.Result r, string message)
  47. : base(message)
  48. {
  49. Result = r;
  50. }
  51. public static SQLiteException New(SQLite3.Result r, string message)
  52. {
  53. return new SQLiteException(r, message);
  54. }
  55. }
  56. [Flags]
  57. public enum SQLiteOpenFlags
  58. {
  59. ReadOnly = 1, ReadWrite = 2, Create = 4,
  60. NoMutex = 0x8000, FullMutex = 0x10000,
  61. SharedCache = 0x20000, PrivateCache = 0x40000,
  62. ProtectionComplete = 0x00100000,
  63. ProtectionCompleteUnlessOpen = 0x00200000,
  64. ProtectionCompleteUntilFirstUserAuthentication = 0x00300000,
  65. ProtectionNone = 0x00400000
  66. }
  67. /// <summary>
  68. /// Represents an open connection to a SQLite database.
  69. /// </summary>
  70. public class SQLiteConnection : IDisposable
  71. {
  72. private bool _open;
  73. private TimeSpan _busyTimeout;
  74. private Dictionary<string, TableMapping> _mappings = null;
  75. private Dictionary<string, TableMapping> _tables = null;
  76. private System.Diagnostics.Stopwatch _sw;
  77. private long _elapsedMilliseconds = 0;
  78. private int _trasactionDepth = 0;
  79. private Random _rand = new Random();
  80. public Sqlite3DatabaseHandle Handle { get; private set; }
  81. #if USE_CSHARP_SQLITE
  82. internal static readonly Sqlite3DatabaseHandle NullHandle = null;
  83. #else
  84. internal static readonly Sqlite3DatabaseHandle NullHandle = IntPtr.Zero;
  85. #endif
  86. public string DatabasePath { get; private set; }
  87. public bool TimeExecution { get; set; }
  88. public bool Trace { get; set; }
  89. public bool StoreDateTimeAsTicks { get; private set; }
  90. /// <summary>
  91. /// Constructs a new SQLiteConnection and opens a SQLite database specified by databasePath.
  92. /// </summary>
  93. /// <param name="databasePath">
  94. /// Specifies the path to the database file.
  95. /// </param>
  96. /// <param name="storeDateTimeAsTicks">
  97. /// Specifies whether to store DateTime properties as ticks (true) or strings (false). You
  98. /// absolutely do want to store them as Ticks in all new projects. The default of false is
  99. /// only here for backwards compatibility. There is a *significant* speed advantage, with no
  100. /// down sides, when setting storeDateTimeAsTicks = true.
  101. /// </param>
  102. public SQLiteConnection(string databasePath, bool storeDateTimeAsTicks = false)
  103. {
  104. DatabasePath = databasePath;
  105. Sqlite3DatabaseHandle handle;
  106. var r = SQLite3.Open(DatabasePath, out handle);
  107. Handle = handle;
  108. if (r != SQLite3.Result.OK)
  109. {
  110. throw SQLiteException.New(r, String.Format("Could not open database file: {0} ({1})", DatabasePath, r));
  111. }
  112. _open = true;
  113. StoreDateTimeAsTicks = storeDateTimeAsTicks;
  114. BusyTimeout = TimeSpan.FromSeconds(1);
  115. }
  116. /// <summary>
  117. /// Constructs a new SQLiteConnection and opens a SQLite database specified by databasePath.
  118. /// </summary>
  119. /// <param name="databasePath">
  120. /// Specifies the path to the database file.
  121. /// </param>
  122. /// <param name="storeDateTimeAsTicks">
  123. /// Specifies whether to store DateTime properties as ticks (true) or strings (false). You
  124. /// absolutely do want to store them as Ticks in all new projects. The default of false is
  125. /// only here for backwards compatibility. There is a *significant* speed advantage, with no
  126. /// down sides, when setting storeDateTimeAsTicks = true.
  127. /// </param>
  128. public SQLiteConnection(string databasePath, SQLiteOpenFlags openFlags, bool storeDateTimeAsTicks = false)
  129. {
  130. DatabasePath = databasePath;
  131. Sqlite3DatabaseHandle handle;
  132. #if SILVERLIGHT
  133. var r = SQLite3.Open (databasePath, out handle, (int)openFlags, IntPtr.Zero);
  134. #else
  135. // open using the byte[]
  136. // in the case where the path may include Unicode
  137. // force open to using UTF-8 using sqlite3_open_v2
  138. byte[] databasePathAsBytes;
  139. int databasePathLength;
  140. databasePathLength = System.Text.Encoding.UTF8.GetByteCount(DatabasePath);
  141. databasePathAsBytes = new byte[databasePathLength + 1];
  142. databasePathLength = System.Text.Encoding.UTF8.GetBytes(DatabasePath, 0, DatabasePath.Length, databasePathAsBytes, 0);
  143. var r = SQLite3.Open(databasePathAsBytes, out handle, (int)openFlags, IntPtr.Zero);
  144. #endif
  145. Handle = handle;
  146. if (r != SQLite3.Result.OK)
  147. {
  148. throw SQLiteException.New(r, String.Format("Could not open database file: {0} ({1})", DatabasePath, r));
  149. }
  150. _open = true;
  151. StoreDateTimeAsTicks = storeDateTimeAsTicks;
  152. BusyTimeout = TimeSpan.FromSeconds(1);
  153. }
  154. static SQLiteConnection()
  155. {
  156. if (_preserveDuringLinkMagic)
  157. {
  158. var ti = new TableInfo();
  159. ti.name = "magic";
  160. }
  161. }
  162. /// <summary>
  163. /// Used to list some code that we want the MonoTouch linker
  164. /// to see, but that we never want to actually execute.
  165. /// </summary>
  166. static bool _preserveDuringLinkMagic = false;
  167. /// <summary>
  168. /// Sets a busy handler to sleep the specified amount of time when a table is locked.
  169. /// The handler will sleep multiple times until a total time of <see cref="BusyTimeout"/> has accumulated.
  170. /// </summary>
  171. public TimeSpan BusyTimeout
  172. {
  173. get { return _busyTimeout; }
  174. set
  175. {
  176. _busyTimeout = value;
  177. if (Handle != NullHandle)
  178. {
  179. SQLite3.BusyTimeout(Handle, (int)_busyTimeout.TotalMilliseconds);
  180. }
  181. }
  182. }
  183. /// <summary>
  184. /// Returns the mappings from types to tables that the connection
  185. /// currently understands.
  186. /// </summary>
  187. public IEnumerable<TableMapping> TableMappings
  188. {
  189. get
  190. {
  191. if (_tables == null)
  192. {
  193. return Enumerable.Empty<TableMapping>();
  194. }
  195. else
  196. {
  197. return _tables.Values;
  198. }
  199. }
  200. }
  201. /// <summary>
  202. /// Retrieves the mapping that is automatically generated for the given type.
  203. /// </summary>
  204. /// <param name="type">
  205. /// The type whose mapping to the database is returned.
  206. /// </param>
  207. /// <returns>
  208. /// The mapping represents the schema of the columns of the database and contains
  209. /// methods to set and get properties of objects.
  210. /// </returns>
  211. public TableMapping GetMapping(Type type)
  212. {
  213. if (_mappings == null)
  214. {
  215. _mappings = new Dictionary<string, TableMapping>();
  216. }
  217. TableMapping map;
  218. if (!_mappings.TryGetValue(type.FullName, out map))
  219. {
  220. map = new TableMapping(type);
  221. _mappings[type.FullName] = map;
  222. }
  223. return map;
  224. }
  225. /// <summary>
  226. /// Retrieves the mapping that is automatically generated for the given type.
  227. /// </summary>
  228. /// <returns>
  229. /// The mapping represents the schema of the columns of the database and contains
  230. /// methods to set and get properties of objects.
  231. /// </returns>
  232. public TableMapping GetMapping<T>()
  233. {
  234. return GetMapping(typeof(T));
  235. }
  236. private struct IndexedColumn
  237. {
  238. public int Order;
  239. public string ColumnName;
  240. }
  241. private struct IndexInfo
  242. {
  243. public string IndexName;
  244. public string TableName;
  245. public bool Unique;
  246. public List<IndexedColumn> Columns;
  247. }
  248. /// <summary>
  249. /// Executes a "drop table" on the database. This is non-recoverable.
  250. /// </summary>
  251. public int DropTable<T>()
  252. {
  253. var map = GetMapping(typeof(T));
  254. var query = string.Format("drop table if exists \"{0}\"", map.TableName);
  255. return Execute(query);
  256. }
  257. /// <summary>
  258. /// Executes a "create table if not exists" on the database. It also
  259. /// creates any specified indexes on the columns of the table. It uses
  260. /// a schema automatically generated from the specified type. You can
  261. /// later access this schema by calling GetMapping.
  262. /// </summary>
  263. /// <returns>
  264. /// The number of entries added to the database schema.
  265. /// </returns>
  266. public int CreateTable<T>()
  267. {
  268. return CreateTable(typeof(T));
  269. }
  270. /// <summary>
  271. /// Executes a "create table if not exists" on the database. It also
  272. /// creates any specified indexes on the columns of the table. It uses
  273. /// a schema automatically generated from the specified type. You can
  274. /// later access this schema by calling GetMapping.
  275. /// </summary>
  276. /// <param name="ty">Type to reflect to a database table.</param>
  277. /// <returns>
  278. /// The number of entries added to the database schema.
  279. /// </returns>
  280. public int CreateTable(Type ty)
  281. {
  282. if (_tables == null)
  283. {
  284. _tables = new Dictionary<string, TableMapping>();
  285. }
  286. TableMapping map;
  287. if (!_tables.TryGetValue(ty.FullName, out map))
  288. {
  289. map = GetMapping(ty);
  290. _tables.Add(ty.FullName, map);
  291. }
  292. var query = "create table if not exists \"" + map.TableName + "\"(\n";
  293. var decls = map.Columns.Select(p => Orm.SqlDecl(p, StoreDateTimeAsTicks));
  294. var decl = string.Join(",\n", decls.ToArray());
  295. query += decl;
  296. query += ")";
  297. var count = Execute(query);
  298. if (count == 0)
  299. { //Possible bug: This always seems to return 0?
  300. // Table already exists, migrate it
  301. MigrateTable(map);
  302. }
  303. var indexes = new Dictionary<string, IndexInfo>();
  304. foreach (var c in map.Columns)
  305. {
  306. foreach (var i in c.Indices)
  307. {
  308. var iname = i.Name ?? map.TableName + "_" + c.Name;
  309. IndexInfo iinfo;
  310. if (!indexes.TryGetValue(iname, out iinfo))
  311. {
  312. iinfo = new IndexInfo
  313. {
  314. IndexName = iname,
  315. TableName = map.TableName,
  316. Unique = i.Unique,
  317. Columns = new List<IndexedColumn>()
  318. };
  319. indexes.Add(iname, iinfo);
  320. }
  321. if (i.Unique != iinfo.Unique)
  322. throw new Exception("All the columns in an index must have the same value for their Unique property");
  323. iinfo.Columns.Add(new IndexedColumn
  324. {
  325. Order = i.Order,
  326. ColumnName = c.Name
  327. });
  328. }
  329. }
  330. foreach (var indexName in indexes.Keys)
  331. {
  332. var index = indexes[indexName];
  333. const string sqlFormat = "create {3} index if not exists \"{0}\" on \"{1}\"(\"{2}\")";
  334. var columns = String.Join("\",\"", index.Columns.OrderBy(i => i.Order).Select(i => i.ColumnName).ToArray());
  335. var sql = String.Format(sqlFormat, indexName, index.TableName, columns, index.Unique ? "unique" : "");
  336. count += Execute(sql);
  337. }
  338. return count;
  339. }
  340. public class TableInfo
  341. {
  342. public int cid { get; set; }
  343. public string name { get; set; }
  344. public string type { get; set; }
  345. public int notnull { get; set; }
  346. public string dflt_value { get; set; }
  347. public int pk { get; set; }
  348. }
  349. void MigrateTable(TableMapping map)
  350. {
  351. var query = "pragma table_info(\"" + map.TableName + "\")";
  352. var existingCols = Query<TableInfo>(query);
  353. var toBeAdded = new List<TableMapping.Column>();
  354. foreach (var p in map.Columns)
  355. {
  356. var found = false;
  357. foreach (var c in existingCols)
  358. {
  359. found = p.Name == c.name;
  360. if (found)
  361. break;
  362. }
  363. if (!found)
  364. {
  365. toBeAdded.Add(p);
  366. }
  367. }
  368. foreach (var p in toBeAdded)
  369. {
  370. var addCol = "alter table \"" + map.TableName + "\" add column " + Orm.SqlDecl(p, StoreDateTimeAsTicks);
  371. Execute(addCol);
  372. }
  373. }
  374. /// <summary>
  375. /// Creates a new SQLiteCommand. Can be overridden to provide a sub-class.
  376. /// </summary>
  377. /// <seealso cref="SQLiteCommand.OnInstanceCreated"/>
  378. protected virtual SQLiteCommand NewCommand()
  379. {
  380. return new SQLiteCommand(this);
  381. }
  382. /// <summary>
  383. /// Creates a new SQLiteCommand given the command text with arguments. Place a '?'
  384. /// in the command text for each of the arguments.
  385. /// </summary>
  386. /// <param name="cmdText">
  387. /// The fully escaped SQL.
  388. /// </param>
  389. /// <param name="args">
  390. /// Arguments to substitute for the occurences of '?' in the command text.
  391. /// </param>
  392. /// <returns>
  393. /// A <see cref="SQLiteCommand"/>
  394. /// </returns>
  395. public SQLiteCommand CreateCommand(string cmdText, params object[] ps)
  396. {
  397. if (!_open)
  398. {
  399. throw SQLiteException.New(SQLite3.Result.Error, "Cannot create commands from unopened database");
  400. }
  401. else
  402. {
  403. var cmd = NewCommand();
  404. cmd.CommandText = cmdText;
  405. foreach (var o in ps)
  406. {
  407. cmd.Bind(o);
  408. }
  409. return cmd;
  410. }
  411. }
  412. /// <summary>
  413. /// Creates a SQLiteCommand given the command text (SQL) with arguments. Place a '?'
  414. /// in the command text for each of the arguments and then executes that command.
  415. /// Use this method instead of Query when you don't expect rows back. Such cases include
  416. /// INSERTs, UPDATEs, and DELETEs.
  417. /// You can set the Trace or TimeExecution properties of the connection
  418. /// to profile execution.
  419. /// </summary>
  420. /// <param name="query">
  421. /// The fully escaped SQL.
  422. /// </param>
  423. /// <param name="args">
  424. /// Arguments to substitute for the occurences of '?' in the query.
  425. /// </param>
  426. /// <returns>
  427. /// The number of rows modified in the database as a result of this execution.
  428. /// </returns>
  429. public int Execute(string query, params object[] args)
  430. {
  431. var cmd = CreateCommand(query, args);
  432. if (TimeExecution)
  433. {
  434. if (_sw == null)
  435. {
  436. _sw = new System.Diagnostics.Stopwatch();
  437. }
  438. _sw.Reset();
  439. _sw.Start();
  440. }
  441. var r = cmd.ExecuteNonQuery();
  442. if (TimeExecution)
  443. {
  444. _sw.Stop();
  445. _elapsedMilliseconds += _sw.ElapsedMilliseconds;
  446. Debug.WriteLine(string.Format("Finished in {0} ms ({1:0.0} s total)", _sw.ElapsedMilliseconds, _elapsedMilliseconds / 1000.0));
  447. }
  448. return r;
  449. }
  450. public T ExecuteScalar<T>(string query, params object[] args)
  451. {
  452. var cmd = CreateCommand(query, args);
  453. if (TimeExecution)
  454. {
  455. if (_sw == null)
  456. {
  457. _sw = new System.Diagnostics.Stopwatch();
  458. }
  459. _sw.Reset();
  460. _sw.Start();
  461. }
  462. var r = cmd.ExecuteScalar<T>();
  463. if (TimeExecution)
  464. {
  465. _sw.Stop();
  466. _elapsedMilliseconds += _sw.ElapsedMilliseconds;
  467. Debug.WriteLine(string.Format("Finished in {0} ms ({1:0.0} s total)", _sw.ElapsedMilliseconds, _elapsedMilliseconds / 1000.0));
  468. }
  469. return r;
  470. }
  471. /// <summary>
  472. /// Creates a SQLiteCommand given the command text (SQL) with arguments. Place a '?'
  473. /// in the command text for each of the arguments and then executes that command.
  474. /// It returns each row of the result using the mapping automatically generated for
  475. /// the given type.
  476. /// </summary>
  477. /// <param name="query">
  478. /// The fully escaped SQL.
  479. /// </param>
  480. /// <param name="args">
  481. /// Arguments to substitute for the occurences of '?' in the query.
  482. /// </param>
  483. /// <returns>
  484. /// An enumerable with one result for each row returned by the query.
  485. /// </returns>
  486. public List<T> Query<T>(string query, params object[] args) where T : new()
  487. {
  488. var cmd = CreateCommand(query, args);
  489. return cmd.ExecuteQuery<T>();
  490. }
  491. /// <summary>
  492. /// Creates a SQLiteCommand given the command text (SQL) with arguments. Place a '?'
  493. /// in the command text for each of the arguments and then executes that command.
  494. /// It returns each row of the result using the mapping automatically generated for
  495. /// the given type.
  496. /// </summary>
  497. /// <param name="query">
  498. /// The fully escaped SQL.
  499. /// </param>
  500. /// <param name="args">
  501. /// Arguments to substitute for the occurences of '?' in the query.
  502. /// </param>
  503. /// <returns>
  504. /// An enumerable with one result for each row returned by the query.
  505. /// The enumerator will call sqlite3_step on each call to MoveNext, so the database
  506. /// connection must remain open for the lifetime of the enumerator.
  507. /// </returns>
  508. public IEnumerable<T> DeferredQuery<T>(string query, params object[] args) where T : new()
  509. {
  510. var cmd = CreateCommand(query, args);
  511. return cmd.ExecuteDeferredQuery<T>();
  512. }
  513. /// <summary>
  514. /// Creates a SQLiteCommand given the command text (SQL) with arguments. Place a '?'
  515. /// in the command text for each of the arguments and then executes that command.
  516. /// It returns each row of the result using the specified mapping. This function is
  517. /// only used by libraries in order to query the database via introspection. It is
  518. /// normally not used.
  519. /// </summary>
  520. /// <param name="map">
  521. /// A <see cref="TableMapping"/> to use to convert the resulting rows
  522. /// into objects.
  523. /// </param>
  524. /// <param name="query">
  525. /// The fully escaped SQL.
  526. /// </param>
  527. /// <param name="args">
  528. /// Arguments to substitute for the occurences of '?' in the query.
  529. /// </param>
  530. /// <returns>
  531. /// An enumerable with one result for each row returned by the query.
  532. /// </returns>
  533. public List<object> Query(TableMapping map, string query, params object[] args)
  534. {
  535. var cmd = CreateCommand(query, args);
  536. return cmd.ExecuteQuery<object>(map);
  537. }
  538. /// <summary>
  539. /// Creates a SQLiteCommand given the command text (SQL) with arguments. Place a '?'
  540. /// in the command text for each of the arguments and then executes that command.
  541. /// It returns each row of the result using the specified mapping. This function is
  542. /// only used by libraries in order to query the database via introspection. It is
  543. /// normally not used.
  544. /// </summary>
  545. /// <param name="map">
  546. /// A <see cref="TableMapping"/> to use to convert the resulting rows
  547. /// into objects.
  548. /// </param>
  549. /// <param name="query">
  550. /// The fully escaped SQL.
  551. /// </param>
  552. /// <param name="args">
  553. /// Arguments to substitute for the occurences of '?' in the query.
  554. /// </param>
  555. /// <returns>
  556. /// An enumerable with one result for each row returned by the query.
  557. /// The enumerator will call sqlite3_step on each call to MoveNext, so the database
  558. /// connection must remain open for the lifetime of the enumerator.
  559. /// </returns>
  560. public IEnumerable<object> DeferredQuery(TableMapping map, string query, params object[] args)
  561. {
  562. var cmd = CreateCommand(query, args);
  563. return cmd.ExecuteDeferredQuery<object>(map);
  564. }
  565. /// <summary>
  566. /// Returns a queryable interface to the table represented by the given type.
  567. /// </summary>
  568. /// <returns>
  569. /// A queryable object that is able to translate Where, OrderBy, and Take
  570. /// queries into native SQL.
  571. /// </returns>
  572. public TableQuery<T> Table<T>() where T : new()
  573. {
  574. return new TableQuery<T>(this);
  575. }
  576. /// <summary>
  577. /// Attempts to retrieve an object with the given primary key from the table
  578. /// associated with the specified type. Use of this method requires that
  579. /// the given type have a designated PrimaryKey (using the PrimaryKeyAttribute).
  580. /// </summary>
  581. /// <param name="pk">
  582. /// The primary key.
  583. /// </param>
  584. /// <returns>
  585. /// The object with the given primary key. Throws a not found exception
  586. /// if the object is not found.
  587. /// </returns>
  588. public T Get<T>(object pk) where T : new()
  589. {
  590. var map = GetMapping(typeof(T));
  591. return Query<T>(map.GetByPrimaryKeySql, pk).First();
  592. }
  593. /// <summary>
  594. /// Attempts to retrieve the first object that matches the predicate from the table
  595. /// associated with the specified type.
  596. /// </summary>
  597. /// <param name="predicate">
  598. /// A predicate for which object to find.
  599. /// </param>
  600. /// <returns>
  601. /// The object that matches the given predicate. Throws a not found exception
  602. /// if the object is not found.
  603. /// </returns>
  604. public T Get<T>(Expression<Func<T, bool>> predicate) where T : new()
  605. {
  606. return Table<T>().Where(predicate).First();
  607. }
  608. /// <summary>
  609. /// Attempts to retrieve an object with the given primary key from the table
  610. /// associated with the specified type. Use of this method requires that
  611. /// the given type have a designated PrimaryKey (using the PrimaryKeyAttribute).
  612. /// </summary>
  613. /// <param name="pk">
  614. /// The primary key.
  615. /// </param>
  616. /// <returns>
  617. /// The object with the given primary key or null
  618. /// if the object is not found.
  619. /// </returns>
  620. public T Find<T>(object pk) where T : new()
  621. {
  622. var map = GetMapping(typeof(T));
  623. return Query<T>(map.GetByPrimaryKeySql, pk).FirstOrDefault();
  624. }
  625. /// <summary>
  626. /// Attempts to retrieve an object with the given primary key from the table
  627. /// associated with the specified type. Use of this method requires that
  628. /// the given type have a designated PrimaryKey (using the PrimaryKeyAttribute).
  629. /// </summary>
  630. /// <param name="pk">
  631. /// The primary key.
  632. /// </param>
  633. /// <param name="map">
  634. /// The TableMapping used to identify the object type.
  635. /// </param>
  636. /// <returns>
  637. /// The object with the given primary key or null
  638. /// if the object is not found.
  639. /// </returns>
  640. public object Find(object pk, TableMapping map)
  641. {
  642. return Query(map, map.GetByPrimaryKeySql, pk).FirstOrDefault();
  643. }
  644. /// <summary>
  645. /// Attempts to retrieve the first object that matches the predicate from the table
  646. /// associated with the specified type.
  647. /// </summary>
  648. /// <param name="predicate">
  649. /// A predicate for which object to find.
  650. /// </param>
  651. /// <returns>
  652. /// The object that matches the given predicate or null
  653. /// if the object is not found.
  654. /// </returns>
  655. public T Find<T>(Expression<Func<T, bool>> predicate) where T : new()
  656. {
  657. return Table<T>().Where(predicate).FirstOrDefault();
  658. }
  659. /// <summary>
  660. /// Whether <see cref="BeginTransaction"/> has been called and the database is waiting for a <see cref="Commit"/>.
  661. /// </summary>
  662. public bool IsInTransaction
  663. {
  664. get { return _trasactionDepth > 0; }
  665. }
  666. /// <summary>
  667. /// Begins a new transaction. Call <see cref="Commit"/> to end the transaction.
  668. /// </summary>
  669. /// <example cref="System.InvalidOperationException">Throws if a transaction has already begun.</example>
  670. public void BeginTransaction()
  671. {
  672. // The BEGIN command only works if the transaction stack is empty,
  673. // or in other words if there are no pending transactions.
  674. // If the transaction stack is not empty when the BEGIN command is invoked,
  675. // then the command fails with an error.
  676. // Rather than crash with an error, we will just ignore calls to BeginTransaction
  677. // that would result in an error.
  678. if (Interlocked.CompareExchange(ref _trasactionDepth, 1, 0) == 0)
  679. {
  680. try
  681. {
  682. Execute("begin transaction");
  683. }
  684. catch (Exception ex)
  685. {
  686. var sqlExp = ex as SQLiteException;
  687. if (sqlExp != null)
  688. {
  689. // It is recommended that applications respond to the errors listed below
  690. // by explicitly issuing a ROLLBACK command.
  691. // TODO: This rollback failsafe should be localized to all throw sites.
  692. switch (sqlExp.Result)
  693. {
  694. case SQLite3.Result.IOError:
  695. case SQLite3.Result.Full:
  696. case SQLite3.Result.Busy:
  697. case SQLite3.Result.NoMem:
  698. case SQLite3.Result.Interrupt:
  699. RollbackTo(null, true);
  700. break;
  701. }
  702. }
  703. else
  704. {
  705. // Call decrement and not VolatileWrite in case we've already
  706. // created a transaction point in SaveTransactionPoint since the catch.
  707. Interlocked.Decrement(ref _trasactionDepth);
  708. }
  709. throw;
  710. }
  711. }
  712. else
  713. {
  714. // Calling BeginTransaction on an already open transaction is invalid
  715. throw new System.InvalidOperationException("Cannot begin a transaction while already in a transaction.");
  716. }
  717. }
  718. /// <summary>
  719. /// Creates a savepoint in the database at the current point in the transaction timeline.
  720. /// Begins a new transaction if one is not in progress.
  721. ///
  722. /// Call <see cref="RollbackTo"/> to undo transactions since the returned savepoint.
  723. /// Call <see cref="Release"/> to commit transactions after the savepoint returned here.
  724. /// Call <see cref="Commit"/> to end the transaction, committing all changes.
  725. /// </summary>
  726. /// <returns>A string naming the savepoint.</returns>
  727. public string SaveTransactionPoint()
  728. {
  729. int depth = Interlocked.Increment(ref _trasactionDepth) - 1;
  730. string retVal = "S" + (short)_rand.Next(short.MaxValue) + "D" + depth;
  731. try
  732. {
  733. Execute("savepoint " + retVal);
  734. }
  735. catch (Exception ex)
  736. {
  737. var sqlExp = ex as SQLiteException;
  738. if (sqlExp != null)
  739. {
  740. // It is recommended that applications respond to the errors listed below
  741. // by explicitly issuing a ROLLBACK command.
  742. // TODO: This rollback failsafe should be localized to all throw sites.
  743. switch (sqlExp.Result)
  744. {
  745. case SQLite3.Result.IOError:
  746. case SQLite3.Result.Full:
  747. case SQLite3.Result.Busy:
  748. case SQLite3.Result.NoMem:
  749. case SQLite3.Result.Interrupt:
  750. RollbackTo(null, true);
  751. break;
  752. }
  753. }
  754. else
  755. {
  756. Interlocked.Decrement(ref _trasactionDepth);
  757. }
  758. throw;
  759. }
  760. return retVal;
  761. }
  762. /// <summary>
  763. /// Rolls back the transaction that was begun by <see cref="BeginTransaction"/> or <see cref="SaveTransactionPoint"/>.
  764. /// </summary>
  765. public void Rollback()
  766. {
  767. RollbackTo(null, false);
  768. }
  769. /// <summary>
  770. /// Rolls back the savepoint created by <see cref="BeginTransaction"/> or SaveTransactionPoint.
  771. /// </summary>
  772. /// <param name="savepoint">The name of the savepoint to roll back to, as returned by <see cref="SaveTransactionPoint"/>. If savepoint is null or empty, this method is equivalent to a call to <see cref="Rollback"/></param>
  773. public void RollbackTo(string savepoint)
  774. {
  775. RollbackTo(savepoint, false);
  776. }
  777. /// <summary>
  778. /// Rolls back the transaction that was begun by <see cref="BeginTransaction"/>.
  779. /// </summary>
  780. /// <param name="noThrow">true to avoid throwing exceptions, false otherwise</param>
  781. private void RollbackTo(string savepoint, bool noThrow)
  782. {
  783. // Rolling back without a TO clause rolls backs all transactions
  784. // and leaves the transaction stack empty.
  785. try
  786. {
  787. if (String.IsNullOrEmpty(savepoint))
  788. {
  789. if (Interlocked.Exchange(ref _trasactionDepth, 0) > 0)
  790. {
  791. Execute("rollback");
  792. }
  793. }
  794. else
  795. {
  796. DoSavePointExecute(savepoint, "rollback to ");
  797. }
  798. }
  799. catch (SQLiteException)
  800. {
  801. if (!noThrow)
  802. throw;
  803. }
  804. // No need to rollback if there are no transactions open.
  805. }
  806. /// <summary>
  807. /// Releases a savepoint returned from <see cref="SaveTransactionPoint"/>. Releasing a savepoint
  808. /// makes changes since that savepoint permanent if the savepoint began the transaction,
  809. /// or otherwise the changes are permanent pending a call to <see cref="Commit"/>.
  810. ///
  811. /// The RELEASE command is like a COMMIT for a SAVEPOINT.
  812. /// </summary>
  813. /// <param name="savepoint">The name of the savepoint to release. The string should be the result of a call to <see cref="SaveTransactionPoint"/></param>
  814. public void Release(string savepoint)
  815. {
  816. DoSavePointExecute(savepoint, "release ");
  817. }
  818. private void DoSavePointExecute(string savepoint, string cmd)
  819. {
  820. // Validate the savepoint
  821. int firstLen = savepoint.IndexOf('D');
  822. if (firstLen >= 2 && savepoint.Length > firstLen + 1)
  823. {
  824. int depth;
  825. if (Int32.TryParse(savepoint.Substring(firstLen + 1), out depth))
  826. {
  827. // TODO: Mild race here, but inescapable without locking almost everywhere.
  828. if (0 <= depth && depth < _trasactionDepth)
  829. {
  830. #if NETFX_CORE
  831. Volatile.Write (ref _trasactionDepth, depth);
  832. #elif SILVERLIGHT
  833. _trasactionDepth = depth;
  834. #else
  835. Thread.VolatileWrite(ref _trasactionDepth, depth);
  836. #endif
  837. Execute(cmd + savepoint);
  838. return;
  839. }
  840. }
  841. }
  842. throw new ArgumentException("savePoint", "savePoint is not valid, and should be the result of a call to SaveTransactionPoint.");
  843. }
  844. /// <summary>
  845. /// Commits the transaction that was begun by <see cref="BeginTransaction"/>.
  846. /// </summary>
  847. public void Commit()
  848. {
  849. if (Interlocked.Exchange(ref _trasactionDepth, 0) != 0)
  850. {
  851. Execute("commit");
  852. }
  853. // Do nothing on a commit with no open transaction
  854. }
  855. /// <summary>
  856. /// Executes <param name="action"> within a (possibly nested) transaction by wrapping it in a SAVEPOINT. If an
  857. /// exception occurs the whole transaction is rolled back, not just the current savepoint. The exception
  858. /// is rethrown.
  859. /// </summary>
  860. /// <param name="action">
  861. /// The <see cref="Action"/> to perform within a transaction. <param name="action"> can contain any number
  862. /// of operations on the connection but should never call <see cref="BeginTransaction"/> or
  863. /// <see cref="Commit"/>.
  864. /// </param>
  865. public void RunInTransaction(Action action)
  866. {
  867. try
  868. {
  869. var savePoint = SaveTransactionPoint();
  870. action();
  871. Release(savePoint);
  872. }
  873. catch (Exception)
  874. {
  875. Rollback();
  876. throw;
  877. }
  878. }
  879. /// <summary>
  880. /// Inserts all specified objects.
  881. /// </summary>
  882. /// <param name="objects">
  883. /// An <see cref="IEnumerable"/> of the objects to insert.
  884. /// </param>
  885. /// <returns>
  886. /// The number of rows added to the table.
  887. /// </returns>
  888. public int InsertAll(System.Collections.IEnumerable objects, bool beginTransaction = true)
  889. {
  890. if (beginTransaction)
  891. {
  892. BeginTransaction();
  893. }
  894. var c = 0;
  895. foreach (var r in objects)
  896. {
  897. c += Insert(r);
  898. }
  899. if (beginTransaction)
  900. {
  901. Commit();
  902. }
  903. return c;
  904. }
  905. /// <summary>
  906. /// Inserts the given object and retrieves its
  907. /// auto incremented primary key if it has one.
  908. /// </summary>
  909. /// <param name="obj">
  910. /// The object to insert.
  911. /// </param>
  912. /// <returns>
  913. /// The number of rows added to the table.
  914. /// </returns>
  915. public int Insert(object obj)
  916. {
  917. if (obj == null)
  918. {
  919. return 0;
  920. }
  921. return Insert(obj, "", obj.GetType());
  922. }
  923. public int Insert(object obj, Type objType)
  924. {
  925. return Insert(obj, "", objType);
  926. }
  927. public int Insert(object obj, string extra)
  928. {
  929. if (obj == null)
  930. {
  931. return 0;
  932. }
  933. return Insert(obj, extra, obj.GetType());
  934. }
  935. /// <summary>
  936. /// Inserts the given object and retrieves its
  937. /// auto incremented primary key if it has one.
  938. /// </summary>
  939. /// <param name="obj">
  940. /// The object to insert.
  941. /// </param>
  942. /// <param name="extra">
  943. /// Literal SQL code that gets placed into the command. INSERT {extra} INTO ...
  944. /// </param>
  945. /// <param name="objType">
  946. /// The type of object to insert.
  947. /// </param>
  948. /// <returns>
  949. /// The number of rows added to the table.
  950. /// </returns>
  951. public int Insert(object obj, string extra, Type objType)
  952. {
  953. if (obj == null || objType == null)
  954. {
  955. return 0;
  956. }
  957. var map = GetMapping(objType);
  958. var cols = map.InsertColumns;
  959. var vals = new object[cols.Length];
  960. for (var i = 0; i < vals.Length; i++)
  961. {
  962. vals[i] = cols[i].GetValue(obj);
  963. }
  964. var insertCmd = map.GetInsertCommand(this, extra);
  965. var count = insertCmd.ExecuteNonQuery(vals);
  966. if (map.HasAutoIncPK)
  967. {
  968. var id = SQLite3.LastInsertRowid(Handle);
  969. map.SetAutoIncPK(obj, id);
  970. }
  971. return count;
  972. }
  973. /// <summary>
  974. /// Updates all of the columns of a table using the specified object
  975. /// except for its primary key.
  976. /// The object is required to have a primary key.
  977. /// </summary>
  978. /// <param name="obj">
  979. /// The object to update. It must have a primary key designated using the PrimaryKeyAttribute.
  980. /// </param>
  981. /// <returns>
  982. /// The number of rows updated.
  983. /// </returns>
  984. public int Update(object obj)
  985. {
  986. if (obj == null)
  987. {
  988. return 0;
  989. }
  990. return Update(obj, obj.GetType());
  991. }
  992. public int Update(object obj, Type objType)
  993. {
  994. if (obj == null || objType == null)
  995. {
  996. return 0;
  997. }
  998. var map = GetMapping(objType);
  999. var pk = map.PK;
  1000. if (pk == null)
  1001. {
  1002. throw new NotSupportedException("Cannot update " + map.TableName + ": it has no PK");
  1003. }
  1004. var cols = from p in map.Columns
  1005. where p != pk
  1006. select p;
  1007. var vals = from c in cols
  1008. select c.GetValue(obj);
  1009. var ps = new List<object>(vals);
  1010. ps.Add(pk.GetValue(obj));
  1011. var q = string.Format("update \"{0}\" set {1} where {2} = ? ", map.TableName, string.Join(",", (from c in cols
  1012. select "\"" + c.Name + "\" = ? ").ToArray()), pk.Name);
  1013. return Execute(q, ps.ToArray());
  1014. }
  1015. /// <summary>
  1016. /// Deletes the given object from the database using its primary key.
  1017. /// </summary>
  1018. /// <param name="objectToDelete">
  1019. /// The object to delete. It must have a primary key designated using the PrimaryKeyAttribute.
  1020. /// </param>
  1021. /// <returns>
  1022. /// The number of rows deleted.
  1023. /// </returns>
  1024. public int Delete(object objectToDelete)
  1025. {
  1026. var map = GetMapping(objectToDelete.GetType());
  1027. var pk = map.PK;
  1028. if (pk == null)
  1029. {
  1030. throw new NotSupportedException("Cannot delete " + map.TableName + ": it has no PK");
  1031. }
  1032. var q = string.Format("delete from \"{0}\" where \"{1}\" = ?", map.TableName, pk.Name);
  1033. return Execute(q, pk.GetValue(objectToDelete));
  1034. }
  1035. /// <summary>
  1036. /// Deletes the object with the specified primary key.
  1037. /// </summary>
  1038. /// <param name="primaryKey">
  1039. /// The primary key of the object to delete.
  1040. /// </param>
  1041. /// <returns>
  1042. /// The number of objects deleted.
  1043. /// </returns>
  1044. /// <typeparam name='T'>
  1045. /// The type of object.
  1046. /// </typeparam>
  1047. public int Delete<T>(object primaryKey)
  1048. {
  1049. var map = GetMapping(typeof(T));
  1050. var pk = map.PK;
  1051. if (pk == null)
  1052. {
  1053. throw new NotSupportedException("Cannot delete " + map.TableName + ": it has no PK");
  1054. }
  1055. var q = string.Format("delete from \"{0}\" where \"{1}\" = ?", map.TableName, pk.Name);
  1056. return Execute(q, primaryKey);
  1057. }
  1058. /// <summary>
  1059. /// Deletes all the objects from the specified table.
  1060. /// WARNING WARNING: Let me repeat. It deletes ALL the objects from the
  1061. /// specified table. Do you really want to do that?
  1062. /// </summary>
  1063. /// <returns>
  1064. /// The number of objects deleted.
  1065. /// </returns>
  1066. /// <typeparam name='T'>
  1067. /// The type of objects to delete.
  1068. /// </typeparam>
  1069. public int DeleteAll<T>()
  1070. {
  1071. var map = GetMapping(typeof(T));
  1072. var query = string.Format("delete from \"{0}\"", map.TableName);
  1073. return Execute(query);
  1074. }
  1075. public void Dispose()
  1076. {
  1077. Close();
  1078. }
  1079. public void Close()
  1080. {
  1081. if (_open && Handle != NullHandle)
  1082. {
  1083. try
  1084. {
  1085. if (_mappings != null)
  1086. {
  1087. foreach (var sqlInsertCommand in _mappings.Values)
  1088. {
  1089. sqlInsertCommand.Dispose();
  1090. }
  1091. }
  1092. var r = SQLite3.Close(Handle);
  1093. if (r != SQLite3.Result.OK)
  1094. {
  1095. string msg = SQLite3.GetErrmsg(Handle);
  1096. throw SQLiteException.New(r, msg);
  1097. }
  1098. }
  1099. finally
  1100. {
  1101. Handle = NullHandle;
  1102. _open = false;
  1103. }
  1104. }
  1105. }
  1106. }
  1107. /// <summary>
  1108. /// Represents a parsed connection string.
  1109. /// </summary>
  1110. class SQLiteConnectionString
  1111. {
  1112. public string ConnectionString { get; private set; }
  1113. public string DatabasePath { get; private set; }
  1114. public bool StoreDateTimeAsTicks { get; private set; }
  1115. #if NETFX_CORE
  1116. static readonly string MetroStyleDataPath = Windows.Storage.ApplicationData.Current.LocalFolder.Path;
  1117. #endif
  1118. public SQLiteConnectionString(string databasePath, bool storeDateTimeAsTicks)
  1119. {
  1120. ConnectionString = databasePath;
  1121. StoreDateTimeAsTicks = storeDateTimeAsTicks;
  1122. #if NETFX_CORE
  1123. DatabasePath = System.IO.Path.Combine (MetroStyleDataPath, databasePath);
  1124. #else
  1125. DatabasePath = databasePath;
  1126. #endif
  1127. }
  1128. }
  1129. [AttributeUsage(AttributeTargets.Class)]
  1130. public class TableAttribute : Attribute
  1131. {
  1132. public string Name { get; set; }
  1133. public TableAttribute(string name)
  1134. {
  1135. Name = name;
  1136. }
  1137. }
  1138. [AttributeUsage(AttributeTargets.Property)]
  1139. public class ColumnAttribute : Attribute
  1140. {
  1141. public string Name { get; set; }
  1142. public ColumnAttribute(string name)
  1143. {
  1144. Name = name;
  1145. }
  1146. }
  1147. [AttributeUsage(AttributeTargets.Property)]
  1148. public class PrimaryKeyAttribute : Attribute
  1149. {
  1150. }
  1151. [AttributeUsage(AttributeTargets.Property)]
  1152. public class AutoIncrementAttribute : Attribute
  1153. {
  1154. }
  1155. [AttributeUsage(AttributeTargets.Property)]
  1156. public class IndexedAttribute : Attribute
  1157. {
  1158. public string Name { get; set; }
  1159. public int Order { get; set; }
  1160. public virtual bool Unique { get; set; }
  1161. public IndexedAttribute()
  1162. {
  1163. }
  1164. public IndexedAttribute(string name, int order)
  1165. {
  1166. Name = name;
  1167. Order = order;
  1168. }
  1169. }
  1170. [AttributeUsage(AttributeTargets.Property)]
  1171. public class IgnoreAttribute : Attribute
  1172. {
  1173. }
  1174. [AttributeUsage(AttributeTargets.Property)]
  1175. public class UniqueAttribute : IndexedAttribute
  1176. {
  1177. public override bool Unique
  1178. {
  1179. get { return true; }
  1180. set { /* throw? */ }
  1181. }
  1182. }
  1183. [AttributeUsage(AttributeTargets.Property)]
  1184. public class MaxLengthAttribute : Attribute
  1185. {
  1186. public int Value { get; private set; }
  1187. public MaxLengthAttribute(int length)
  1188. {
  1189. Value = length;
  1190. }
  1191. }
  1192. [AttributeUsage(AttributeTargets.Property)]
  1193. public class CollationAttribute : Attribute
  1194. {
  1195. public string Value { get; private set; }
  1196. public CollationAttribute(string collation)
  1197. {
  1198. Value = collation;
  1199. }
  1200. }
  1201. public class TableMapping
  1202. {
  1203. public Type MappedType { get; private set; }
  1204. public string TableName { get; private set; }
  1205. public Column[] Columns { get; private set; }
  1206. public Column PK { get; private set; }
  1207. public string GetByPrimaryKeySql { get; private set; }
  1208. Column _autoPk = null;
  1209. Column[] _insertColumns = null;
  1210. public TableMapping(Type type)
  1211. {
  1212. MappedType = type;
  1213. #if NETFX_CORE
  1214. var tableAttr = (TableAttribute)System.Reflection.CustomAttributeExtensions
  1215. .GetCustomAttribute(type.GetTypeInfo(), typeof(TableAttribute), true);
  1216. #else
  1217. var tableAttr = (TableAttribute)type.GetCustomAttributes(typeof(TableAttribute), true).FirstOrDefault();
  1218. #endif
  1219. TableName = tableAttr != null ? tableAttr.Name : MappedType.Name;
  1220. #if !NETFX_CORE
  1221. var props = MappedType.GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.SetProperty);
  1222. #else
  1223. var props = from p in MappedType.GetRuntimeProperties()
  1224. where ((p.GetMethod != null && p.GetMethod.IsPublic) || (p.SetMethod != null && p.SetMethod.IsPublic) || (p.GetMethod != null && p.GetMethod.IsStatic) || (p.SetMethod != null && p.SetMethod.IsStatic))
  1225. select p;
  1226. #endif
  1227. var cols = new List<Column>();
  1228. foreach (var p in props)
  1229. {
  1230. #if !NETFX_CORE
  1231. var ignore = p.GetCustomAttributes(typeof(IgnoreAttribute), true).Length > 0;
  1232. #else
  1233. var ignore = p.GetCustomAttributes (typeof(IgnoreAttribute), true).Count() > 0;
  1234. #endif
  1235. if (p.CanWrite && !ignore)
  1236. {
  1237. cols.Add(new Column(p));
  1238. }
  1239. }
  1240. Columns = cols.ToArray();
  1241. foreach (var c in Columns)
  1242. {
  1243. if (c.IsAutoInc && c.IsPK)
  1244. {
  1245. _autoPk = c;
  1246. }
  1247. if (c.IsPK)
  1248. {
  1249. PK = c;
  1250. }
  1251. }
  1252. HasAutoIncPK = _autoPk != null;
  1253. if (PK != null)
  1254. {
  1255. GetByPrimaryKeySql = string.Format("select * from \"{0}\" where \"{1}\" = ?", TableName, PK.Name);
  1256. }
  1257. else
  1258. {
  1259. // People should not be calling Get/Find without a PK
  1260. GetByPrimaryKeySql = string.Format("select * from \"{0}\" limit 1", TableName);
  1261. }
  1262. }
  1263. public bool HasAutoIncPK { get; private set; }
  1264. public void SetAutoIncPK(object obj, long id)
  1265. {
  1266. if (_autoPk != null)
  1267. {
  1268. _autoPk.SetValue(obj, Convert.ChangeType(id, _autoPk.ColumnType, null));
  1269. }
  1270. }
  1271. public Column[] InsertColumns
  1272. {
  1273. get
  1274. {
  1275. if (_insertColumns == null)
  1276. {
  1277. _insertColumns = Columns.Where(c => !c.IsAutoInc).ToArray();
  1278. }
  1279. return _insertColumns;
  1280. }
  1281. }
  1282. public Column FindColumnWithPropertyName(string propertyName)
  1283. {
  1284. var exact = Columns.Where(c => c.PropertyName == propertyName).FirstOrDefault();
  1285. return exact;
  1286. }
  1287. public Column FindColumn(string columnName)
  1288. {
  1289. var exact = Columns.Where(c => c.Name == columnName).FirstOrDefault();
  1290. return exact;
  1291. }
  1292. PreparedSqlLiteInsertCommand _insertCommand;
  1293. string _insertCommandExtra = null;
  1294. public PreparedSqlLiteInsertCommand GetInsertCommand(SQLiteConnection conn, string extra)
  1295. {
  1296. if (_insertCommand == null)
  1297. {
  1298. _insertCommand = CreateInsertCommand(conn, extra);
  1299. _insertCommandExtra = extra;
  1300. }
  1301. else if (_insertCommandExtra != extra)
  1302. {
  1303. _insertCommand.Dispose();
  1304. _insertCommand = CreateInsertCommand(conn, extra);
  1305. _insertCommandExtra = extra;
  1306. }
  1307. return _insertCommand;
  1308. }
  1309. private PreparedSqlLiteInsertCommand CreateInsertCommand(SQLiteConnection conn, string extra)
  1310. {
  1311. var cols = InsertColumns;
  1312. string insertSql;
  1313. if (!cols.Any() && Columns.Count() == 1 && Columns[0].IsAutoInc)
  1314. {
  1315. insertSql = string.Format("insert {1} into \"{0}\" default values", TableName, extra);
  1316. }
  1317. else
  1318. {
  1319. insertSql = string.Format("insert {3} into \"{0}\"({1}) values ({2})", TableName,
  1320. string.Join(",", (from c in cols
  1321. select "\"" + c.Name + "\"").ToArray()),
  1322. string.Join(",", (from c in cols
  1323. select "?").ToArray()), extra);
  1324. }
  1325. var insertCommand = new PreparedSqlLiteInsertCommand(conn);
  1326. insertCommand.CommandText = insertSql;
  1327. return insertCommand;
  1328. }
  1329. protected internal void Dispose()
  1330. {
  1331. if (_insertCommand != null)
  1332. {
  1333. _insertCommand.Dispose();
  1334. _insertCommand = null;
  1335. }
  1336. }
  1337. public class Column
  1338. {
  1339. PropertyInfo _prop;
  1340. public string Name { get; private set; }
  1341. public string PropertyName { get { return _prop.Name; } }
  1342. public Type ColumnType { get; private set; }
  1343. public string Collation { get; private set; }
  1344. public bool IsAutoInc { get; private set; }
  1345. public bool IsPK { get; private set; }
  1346. public IEnumerable<IndexedAttribute> Indices { get; set; }
  1347. public bool IsNullable { get; private set; }
  1348. public int MaxStringLength { get; private set; }
  1349. public Column(PropertyInfo prop)
  1350. {
  1351. var colAttr = (ColumnAttribute)prop.GetCustomAttributes(typeof(ColumnAttribute), true).FirstOrDefault();
  1352. _prop = prop;
  1353. Name = colAttr == null ? prop.Name : colAttr.Name;
  1354. //If this type is Nullable<T> then Nullable.GetUnderlyingType returns the T, otherwise it returns null, so get the the actual type instead
  1355. ColumnType = Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType;
  1356. Collation = Orm.Collation(prop);
  1357. IsAutoInc = Orm.IsAutoInc(prop);
  1358. IsPK = Orm.IsPK(prop);
  1359. Indices = Orm.GetIndices(prop);
  1360. IsNullable = !IsPK;
  1361. MaxStringLength = Orm.MaxStringLength(prop);
  1362. }
  1363. public void SetValue(object obj, object val)
  1364. {
  1365. _prop.SetValue(obj, val, null);
  1366. }
  1367. public object GetValue(object obj)
  1368. {
  1369. return _prop.GetValue(obj, null);
  1370. }
  1371. }
  1372. }
  1373. public static class Orm
  1374. {
  1375. public const int DefaultMaxStringLength = 140;
  1376. public static string SqlDecl(TableMapping.Column p, bool storeDateTimeAsTicks)
  1377. {
  1378. string decl = "\"" + p.Name + "\" " + SqlType(p, storeDateTimeAsTicks) + " ";
  1379. if (p.IsPK)
  1380. {
  1381. decl += "primary key ";
  1382. }
  1383. if (p.IsAutoInc)
  1384. {
  1385. decl += "autoincrement ";
  1386. }
  1387. if (!p.IsNullable)
  1388. {
  1389. decl += "not null ";
  1390. }
  1391. if (!string.IsNullOrEmpty(p.Collation))
  1392. {
  1393. decl += "collate " + p.Collation + " ";
  1394. }
  1395. return decl;
  1396. }
  1397. public static string SqlType(TableMapping.Column p, bool storeDateTimeAsTicks)
  1398. {
  1399. var clrType = p.ColumnType;
  1400. if (clrType == typeof(Boolean) || clrType == typeof(Byte) || clrType == typeof(UInt16) || clrType == typeof(SByte) || clrType == typeof(Int16) || clrType == typeof(Int32))
  1401. {
  1402. return "integer";
  1403. }
  1404. else if (clrType == typeof(UInt32) || clrType == typeof(Int64))
  1405. {
  1406. return "bigint";
  1407. }
  1408. else if (clrType == typeof(Single) || clrType == typeof(Double) || clrType == typeof(Decimal))
  1409. {
  1410. return "float";
  1411. }
  1412. else if (clrType == typeof(String))
  1413. {
  1414. int len = p.MaxStringLength;
  1415. return "varchar(" + len + ")";
  1416. }
  1417. else if (clrType == typeof(DateTime))
  1418. {
  1419. return storeDateTimeAsTicks ? "bigint" : "datetime";
  1420. #if !NETFX_CORE
  1421. }
  1422. else if (clrType.IsEnum)
  1423. {
  1424. #else
  1425. } else if (clrType.GetTypeInfo().IsEnum) {
  1426. #endif
  1427. return "integer";
  1428. }
  1429. else if (clrType == typeof(byte[]))
  1430. {
  1431. return "blob";
  1432. #if SQLITE_SUPPORT_GUID
  1433. } else if (clrType == typeof(Guid)) {
  1434. return "varchar(36)";
  1435. #endif
  1436. }
  1437. else
  1438. {
  1439. #if SQLITE_SUPPORT_SERIALIZATION
  1440. return "text";
  1441. #else
  1442. throw new NotSupportedException("Don't know about " + clrType);
  1443. #endif
  1444. }
  1445. }
  1446. public static bool IsPK(MemberInfo p)
  1447. {
  1448. var attrs = p.GetCustomAttributes(typeof(PrimaryKeyAttribute), true);
  1449. #if !NETFX_CORE
  1450. return attrs.Length > 0;
  1451. #else
  1452. return attrs.Count() > 0;
  1453. #endif
  1454. }
  1455. public static string Collation(MemberInfo p)
  1456. {
  1457. var attrs = p.GetCustomAttributes(typeof(CollationAttribute), true);
  1458. #if !NETFX_CORE
  1459. if (attrs.Length > 0)
  1460. {
  1461. return ((CollationAttribute)attrs[0]).Value;
  1462. #else
  1463. if (attrs.Count() > 0) {
  1464. return ((CollationAttribute)attrs.First()).Value;
  1465. #endif
  1466. }
  1467. else
  1468. {
  1469. return string.Empty;
  1470. }
  1471. }
  1472. public static bool IsAutoInc(MemberInfo p)
  1473. {
  1474. var attrs = p.GetCustomAttributes(typeof(AutoIncrementAttribute), true);
  1475. #if !NETFX_CORE
  1476. return attrs.Length > 0;
  1477. #else
  1478. return attrs.Count() > 0;
  1479. #endif
  1480. }
  1481. public static IEnumerable<IndexedAttribute> GetIndices(MemberInfo p)
  1482. {
  1483. var attrs = p.GetCustomAttributes(typeof(IndexedAttribute), true);
  1484. return attrs.Cast<IndexedAttribute>();
  1485. }
  1486. public static int MaxStringLength(PropertyInfo p)
  1487. {
  1488. var attrs = p.GetCustomAttributes(typeof(MaxLengthAttribute), true);
  1489. #if !NETFX_CORE
  1490. if (attrs.Length > 0)
  1491. {
  1492. return ((MaxLengthAttribute)attrs[0]).Value;
  1493. #else
  1494. if (attrs.Count() > 0) {
  1495. return ((MaxLengthAttribute)attrs.First()).Value;
  1496. #endif
  1497. }
  1498. else
  1499. {
  1500. return DefaultMaxStringLength;
  1501. }
  1502. }
  1503. }
  1504. public class SQLiteCommand
  1505. {
  1506. SQLiteConnection _conn;
  1507. private List<Binding> _bindings;
  1508. public string CommandText { get; set; }
  1509. internal SQLiteCommand(SQLiteConnection conn)
  1510. {
  1511. _conn = conn;
  1512. _bindings = new List<Binding>();
  1513. CommandText = "";
  1514. }
  1515. public int ExecuteNonQuery()
  1516. {
  1517. if (_conn.Trace)
  1518. {
  1519. Debug.WriteLine("Executing: " + this);
  1520. }
  1521. var r = SQLite3.Result.OK;
  1522. var stmt = Prepare();
  1523. r = SQLite3.Step(stmt);
  1524. Finalize(stmt);
  1525. if (r == SQLite3.Result.Done)
  1526. {
  1527. int rowsAffected = SQLite3.Changes(_conn.Handle);
  1528. return rowsAffected;
  1529. }
  1530. else if (r == SQLite3.Result.Row)
  1531. {
  1532. return -1;
  1533. }
  1534. else if (r == SQLite3.Result.Error)
  1535. {
  1536. string msg = SQLite3.GetErrmsg(_conn.Handle);
  1537. throw SQLiteException.New(r, msg);
  1538. }
  1539. else
  1540. {
  1541. throw SQLiteException.New(r, r.ToString());
  1542. }
  1543. }
  1544. public IEnumerable<T> ExecuteDeferredQuery<T>() where T : new()
  1545. {
  1546. return ExecuteDeferredQuery<T>(_conn.GetMapping(typeof(T)));
  1547. }
  1548. public List<T> ExecuteQuery<T>() where T : new()
  1549. {
  1550. return ExecuteDeferredQuery<T>(_conn.GetMapping(typeof(T))).ToList();
  1551. }
  1552. public List<T> ExecuteQuery<T>(TableMapping map)
  1553. {
  1554. return ExecuteDeferredQuery<T>(map).ToList();
  1555. }
  1556. /// <summary>
  1557. /// Invoked every time an instance is loaded from the database.
  1558. /// </summary>
  1559. /// <param name='obj'>
  1560. /// The newly created object.
  1561. /// </param>
  1562. /// <remarks>
  1563. /// This can be overridden in combination with the <see cref="SQLiteConnection.NewCommand"/>
  1564. /// method to hook into the life-cycle of objects.
  1565. ///
  1566. /// Type safety is not possible because MonoTouch does not support virtual generic methods.
  1567. /// </remarks>
  1568. protected virtual void OnInstanceCreated(object obj)
  1569. {
  1570. // Can be overridden.
  1571. }
  1572. public IEnumerable<T> ExecuteDeferredQuery<T>(TableMapping map)
  1573. {
  1574. if (_conn.Trace)
  1575. {
  1576. Debug.WriteLine("Executing Query: " + this);
  1577. }
  1578. var stmt = Prepare();
  1579. try
  1580. {
  1581. var cols = new TableMapping.Column[SQLite3.ColumnCount(stmt)];
  1582. for (int i = 0; i < cols.Length; i++)
  1583. {
  1584. var name = SQLite3.ColumnName16(stmt, i);
  1585. cols[i] = map.FindColumn(name);
  1586. }
  1587. while (SQLite3.Step(stmt) == SQLite3.Result.Row)
  1588. {
  1589. var obj = Activator.CreateInstance(map.MappedType);
  1590. for (int i = 0; i < cols.Length; i++)
  1591. {
  1592. if (cols[i] == null)
  1593. continue;
  1594. var colType = SQLite3.ColumnType(stmt, i);
  1595. var val = ReadCol(stmt, i, colType, cols[i].ColumnType);
  1596. cols[i].SetValue(obj, val);
  1597. }
  1598. OnInstanceCreated(obj);
  1599. yield return (T)obj;
  1600. }
  1601. }
  1602. finally
  1603. {
  1604. SQLite3.Finalize(stmt);
  1605. }
  1606. }
  1607. public T ExecuteScalar<T>()
  1608. {
  1609. if (_conn.Trace)
  1610. {
  1611. Debug.WriteLine("Executing Query: " + this);
  1612. }
  1613. T val = default(T);
  1614. var stmt = Prepare();
  1615. if (SQLite3.Step(stmt) == SQLite3.Result.Row)
  1616. {
  1617. var colType = SQLite3.ColumnType(stmt, 0);
  1618. val = (T)ReadCol(stmt, 0, colType, typeof(T));
  1619. }
  1620. Finalize(stmt);
  1621. return val;
  1622. }
  1623. public void Bind(string name, object val)
  1624. {
  1625. _bindings.Add(new Binding
  1626. {
  1627. Name = name,
  1628. Value = val
  1629. });
  1630. }
  1631. public void Bind(object val)
  1632. {
  1633. Bind(null, val);
  1634. }
  1635. public override string ToString()
  1636. {
  1637. var parts = new string[1 + _bindings.Count];
  1638. parts[0] = CommandText;
  1639. var i = 1;
  1640. foreach (var b in _bindings)
  1641. {
  1642. parts[i] = string.Format(" {0}: {1}", i - 1, b.Value);
  1643. i++;
  1644. }
  1645. return string.Join(Environment.NewLine, parts);
  1646. }
  1647. Sqlite3Statement Prepare()
  1648. {
  1649. var stmt = SQLite3.Prepare2(_conn.Handle, CommandText);
  1650. BindAll(stmt);
  1651. return stmt;
  1652. }
  1653. void Finalize(Sqlite3Statement stmt)
  1654. {
  1655. SQLite3.Finalize(stmt);
  1656. }
  1657. void BindAll(Sqlite3Statement stmt)
  1658. {
  1659. int nextIdx = 1;
  1660. foreach (var b in _bindings)
  1661. {
  1662. if (b.Name != null)
  1663. {
  1664. b.Index = SQLite3.BindParameterIndex(stmt, b.Name);
  1665. }
  1666. else
  1667. {
  1668. b.Index = nextIdx++;
  1669. }
  1670. BindParameter(stmt, b.Index, b.Value, _conn.StoreDateTimeAsTicks);
  1671. }
  1672. }
  1673. internal static IntPtr NegativePointer = new IntPtr(-1);
  1674. internal static void BindParameter(Sqlite3Statement stmt, int index, object value, bool storeDateTimeAsTicks)
  1675. {
  1676. if (value == null)
  1677. {
  1678. SQLite3.BindNull(stmt, index);
  1679. }
  1680. else
  1681. {
  1682. if (value is Int32)
  1683. {
  1684. SQLite3.BindInt(stmt, index, (int)value);
  1685. }
  1686. else if (value is String)
  1687. {
  1688. SQLite3.BindText(stmt, index, (string)value, -1, NegativePointer);
  1689. }
  1690. else if (value is Byte || value is UInt16 || value is SByte || value is Int16)
  1691. {
  1692. SQLite3.BindInt(stmt, index, Convert.ToInt32(value));
  1693. }
  1694. else if (value is Boolean)
  1695. {
  1696. SQLite3.BindInt(stmt, index, (bool)value ? 1 : 0);
  1697. }
  1698. else if (value is UInt32 || value is Int64)
  1699. {
  1700. SQLite3.BindInt64(stmt, index, Convert.ToInt64(value));
  1701. }
  1702. else if (value is Single || value is Double || value is Decimal)
  1703. {
  1704. SQLite3.BindDouble(stmt, index, Convert.ToDouble(value));
  1705. }
  1706. else if (value is DateTime)
  1707. {
  1708. if (storeDateTimeAsTicks)
  1709. {
  1710. SQLite3.BindInt64(stmt, index, ((DateTime)value).Ticks);
  1711. }
  1712. else
  1713. {
  1714. SQLite3.BindText(stmt, index, ((DateTime)value).ToString("yyyy-MM-dd HH:mm:ss"), -1, NegativePointer);
  1715. }
  1716. #if !NETFX_CORE
  1717. }
  1718. else if (value.GetType().IsEnum)
  1719. {
  1720. #else
  1721. } else if (value.GetType().GetTypeInfo().IsEnum) {
  1722. #endif
  1723. SQLite3.BindInt(stmt, index, Convert.ToInt32(value));
  1724. }
  1725. else if (value is byte[])
  1726. {
  1727. SQLite3.BindBlob(stmt, index, (byte[])value, ((byte[])value).Length, NegativePointer);
  1728. #if SQLITE_SUPPORT_GUID
  1729. } else if (value is Guid) {
  1730. SQLite3.BindText(stmt, index, ((Guid)value).ToString(), 72, NegativePointer);
  1731. #endif
  1732. }
  1733. else
  1734. {
  1735. #if SQLITE_SUPPORT_SERIALIZATION
  1736. var result = Serialization.Serialize(value);
  1737. SQLite3.BindText(stmt, index, result, -1, NegativePointer);
  1738. #else
  1739. throw new NotSupportedException("Cannot store type: " + value.GetType());
  1740. #endif
  1741. }
  1742. }
  1743. }
  1744. class Binding
  1745. {
  1746. public string Name { get; set; }
  1747. public object Value { get; set; }
  1748. public int Index { get; set; }
  1749. }
  1750. object ReadCol(Sqlite3Statement stmt, int index, SQLite3.ColType type, Type clrType)
  1751. {
  1752. if (type == SQLite3.ColType.Null)
  1753. {
  1754. return null;
  1755. }
  1756. else
  1757. {
  1758. if (clrType == typeof(String))
  1759. {
  1760. return SQLite3.ColumnString(stmt, index);
  1761. }
  1762. else if (clrType == typeof(Int32))
  1763. {
  1764. return (int)SQLite3.ColumnInt(stmt, index);
  1765. }
  1766. else if (clrType == typeof(Boolean))
  1767. {
  1768. return SQLite3.ColumnInt(stmt, index) == 1;
  1769. }
  1770. else if (clrType == typeof(double))
  1771. {
  1772. return SQLite3.ColumnDouble(stmt, index);
  1773. }
  1774. else if (clrType == typeof(float))
  1775. {
  1776. return (float)SQLite3.ColumnDouble(stmt, index);
  1777. }
  1778. else if (clrType == typeof(DateTime))
  1779. {
  1780. if (_conn.StoreDateTimeAsTicks)
  1781. {
  1782. return new DateTime(SQLite3.ColumnInt64(stmt, index));
  1783. }
  1784. else
  1785. {
  1786. var text = SQLite3.ColumnString(stmt, index);
  1787. return DateTime.Parse(text);
  1788. }
  1789. #if !NETFX_CORE
  1790. }
  1791. else if (clrType.IsEnum)
  1792. {
  1793. #else
  1794. } else if (clrType.GetTypeInfo().IsEnum) {
  1795. #endif
  1796. return SQLite3.ColumnInt(stmt, index);
  1797. }
  1798. else if (clrType == typeof(Int64))
  1799. {
  1800. return SQLite3.ColumnInt64(stmt, index);
  1801. }
  1802. else if (clrType == typeof(UInt32))
  1803. {
  1804. return (uint)SQLite3.ColumnInt64(stmt, index);
  1805. }
  1806. else if (clrType == typeof(decimal))
  1807. {
  1808. return (decimal)SQLite3.ColumnDouble(stmt, index);
  1809. }
  1810. else if (clrType == typeof(Byte))
  1811. {
  1812. return (byte)SQLite3.ColumnInt(stmt, index);
  1813. }
  1814. else if (clrType == typeof(UInt16))
  1815. {
  1816. return (ushort)SQLite3.ColumnInt(stmt, index);
  1817. }
  1818. else if (clrType == typeof(Int16))
  1819. {
  1820. return (short)SQLite3.ColumnInt(stmt, index);
  1821. }
  1822. else if (clrType == typeof(sbyte))
  1823. {
  1824. return (sbyte)SQLite3.ColumnInt(stmt, index);
  1825. }
  1826. else if (clrType == typeof(byte[]))
  1827. {
  1828. return SQLite3.ColumnByteArray(stmt, index);
  1829. #if SQLITE_SUPPORT_GUID
  1830. } else if (clrType == typeof(Guid)) {
  1831. var text = SQLite3.ColumnString(stmt, index);
  1832. return new Guid(text);
  1833. #endif
  1834. }
  1835. else
  1836. {
  1837. #if SQLITE_SUPPORT_SERIALIZATION
  1838. var text = SQLite3.ColumnString(stmt, index);
  1839. return Serialization.Deserialize(text, clrType);
  1840. #else
  1841. throw new NotSupportedException("Don't know how to read " + clrType);
  1842. #endif
  1843. }
  1844. }
  1845. }
  1846. }
  1847. /// <summary>
  1848. /// Since the insert never changed, we only need to prepare once.
  1849. /// </summary>
  1850. public class PreparedSqlLiteInsertCommand : IDisposable
  1851. {
  1852. public bool Initialized { get; set; }
  1853. protected SQLiteConnection Connection { get; set; }
  1854. public string CommandText { get; set; }
  1855. protected Sqlite3Statement Statement { get; set; }
  1856. #if USE_CSHARP_SQLITE
  1857. internal static readonly Sqlite3Statement NullStatement = null;
  1858. #else
  1859. internal static readonly Sqlite3Statement NullStatement = IntPtr.Zero;
  1860. #endif
  1861. internal PreparedSqlLiteInsertCommand(SQLiteConnection conn)
  1862. {
  1863. Connection = conn;
  1864. }
  1865. public int ExecuteNonQuery(object[] source)
  1866. {
  1867. if (Connection.Trace)
  1868. {
  1869. Debug.WriteLine("Executing: " + CommandText);
  1870. }
  1871. var r = SQLite3.Result.OK;
  1872. if (!Initialized)
  1873. {
  1874. Statement = Prepare();
  1875. Initialized = true;
  1876. }
  1877. //bind the values.
  1878. if (source != null)
  1879. {
  1880. for (int i = 0; i < source.Length; i++)
  1881. {
  1882. SQLiteCommand.BindParameter(Statement, i + 1, source[i], Connection.StoreDateTimeAsTicks);
  1883. }
  1884. }
  1885. r = SQLite3.Step(Statement);
  1886. if (r == SQLite3.Result.Done)
  1887. {
  1888. int rowsAffected = SQLite3.Changes(Connection.Handle);
  1889. SQLite3.Reset(Statement);
  1890. return rowsAffected;
  1891. }
  1892. else if (r == SQLite3.Result.Error)
  1893. {
  1894. string msg = SQLite3.GetErrmsg(Connection.Handle);
  1895. SQLite3.Reset(Statement);
  1896. throw SQLiteException.New(r, msg);
  1897. }
  1898. else
  1899. {
  1900. SQLite3.Reset(Statement);
  1901. throw SQLiteException.New(r, r.ToString());
  1902. }
  1903. }
  1904. protected virtual Sqlite3Statement Prepare()
  1905. {
  1906. var stmt = SQLite3.Prepare2(Connection.Handle, CommandText);
  1907. return stmt;
  1908. }
  1909. public void Dispose()
  1910. {
  1911. Dispose(true);
  1912. GC.SuppressFinalize(this);
  1913. }
  1914. private void Dispose(bool disposing)
  1915. {
  1916. if (Statement != NullStatement)
  1917. {
  1918. try
  1919. {
  1920. SQLite3.Finalize(Statement);
  1921. }
  1922. finally
  1923. {
  1924. Statement = NullStatement;
  1925. Connection = null;
  1926. }
  1927. }
  1928. }
  1929. ~PreparedSqlLiteInsertCommand()
  1930. {
  1931. Dispose(false);
  1932. }
  1933. }
  1934. public class TableQuery<T> : IEnumerable<T> where T : new()
  1935. {
  1936. public SQLiteConnection Connection { get; private set; }
  1937. public TableMapping Table { get; private set; }
  1938. Expression _where;
  1939. List<Ordering> _orderBys;
  1940. int? _limit;
  1941. int? _offset;
  1942. class Ordering
  1943. {
  1944. public string ColumnName { get; set; }
  1945. public bool Ascending { get; set; }
  1946. }
  1947. TableQuery(SQLiteConnection conn, TableMapping table)
  1948. {
  1949. Connection = conn;
  1950. Table = table;
  1951. }
  1952. public TableQuery(SQLiteConnection conn)
  1953. {
  1954. Connection = conn;
  1955. Table = Connection.GetMapping(typeof(T));
  1956. }
  1957. public TableQuery<T> Clone()
  1958. {
  1959. var q = new TableQuery<T>(Connection, Table);
  1960. q._where = _where;
  1961. q._deferred = _deferred;
  1962. if (_orderBys != null)
  1963. {
  1964. q._orderBys = new List<Ordering>(_orderBys);
  1965. }
  1966. q._limit = _limit;
  1967. q._offset = _offset;
  1968. return q;
  1969. }
  1970. public TableQuery<T> Where(Expression<Func<T, bool>> predExpr)
  1971. {
  1972. if (predExpr.NodeType == ExpressionType.Lambda)
  1973. {
  1974. var lambda = (LambdaExpression)predExpr;
  1975. var pred = lambda.Body;
  1976. var q = Clone();
  1977. q.AddWhere(pred);
  1978. return q;
  1979. }
  1980. else
  1981. {
  1982. throw new NotSupportedException("Must be a predicate");
  1983. }
  1984. }
  1985. public TableQuery<T> Take(int n)
  1986. {
  1987. var q = Clone();
  1988. q._limit = n;
  1989. return q;
  1990. }
  1991. public TableQuery<T> Skip(int n)
  1992. {
  1993. var q = Clone();
  1994. q._offset = n;
  1995. return q;
  1996. }
  1997. public T ElementAt(int index)
  1998. {
  1999. return Skip(index).Take(1).First();
  2000. }
  2001. bool _deferred = false;
  2002. public TableQuery<T> Deferred()
  2003. {
  2004. var q = Clone();
  2005. q._deferred = true;
  2006. return q;
  2007. }
  2008. public TableQuery<T> OrderBy<U>(Expression<Func<T, U>> orderExpr)
  2009. {
  2010. return AddOrderBy<U>(orderExpr, true);
  2011. }
  2012. public TableQuery<T> OrderByDescending<U>(Expression<Func<T, U>> orderExpr)
  2013. {
  2014. return AddOrderBy<U>(orderExpr, false);
  2015. }
  2016. private TableQuery<T> AddOrderBy<U>(Expression<Func<T, U>> orderExpr, bool asc)
  2017. {
  2018. if (orderExpr.NodeType == ExpressionType.Lambda)
  2019. {
  2020. var lambda = (LambdaExpression)orderExpr;
  2021. var mem = lambda.Body as MemberExpression;
  2022. if (mem != null && (mem.Expression.NodeType == ExpressionType.Parameter))
  2023. {
  2024. var q = Clone();
  2025. if (q._orderBys == null)
  2026. {
  2027. q._orderBys = new List<Ordering>();
  2028. }
  2029. q._orderBys.Add(new Ordering
  2030. {
  2031. ColumnName = Table.FindColumnWithPropertyName(mem.Member.Name).Name,
  2032. Ascending = asc
  2033. });
  2034. return q;
  2035. }
  2036. else
  2037. {
  2038. throw new NotSupportedException("Order By does not support: " + orderExpr);
  2039. }
  2040. }
  2041. else
  2042. {
  2043. throw new NotSupportedException("Must be a predicate");
  2044. }
  2045. }
  2046. private void AddWhere(Expression pred)
  2047. {
  2048. if (_where == null)
  2049. {
  2050. _where = pred;
  2051. }
  2052. else
  2053. {
  2054. _where = Expression.AndAlso(_where, pred);
  2055. }
  2056. }
  2057. public TableQuery<TResult> Join<TInner, TKey, TResult>(
  2058. TableQuery<TInner> inner,
  2059. Expression<Func<T, TKey>> outerKeySelector,
  2060. Expression<Func<TInner, TKey>> innerKeySelector,
  2061. Expression<Func<T, TInner, TResult>> resultSelector
  2062. )
  2063. where TResult : new()
  2064. where TInner : new()
  2065. {
  2066. throw new NotImplementedException();
  2067. }
  2068. private SQLiteCommand GenerateCommand(string selectionList)
  2069. {
  2070. var cmdText = "select " + selectionList + " from \"" + Table.TableName + "\"";
  2071. var args = new List<object>();
  2072. if (_where != null)
  2073. {
  2074. var w = CompileExpr(_where, args);
  2075. cmdText += " where " + w.CommandText;
  2076. }
  2077. if ((_orderBys != null) && (_orderBys.Count > 0))
  2078. {
  2079. var t = string.Join(", ", _orderBys.Select(o => "\"" + o.ColumnName + "\"" + (o.Ascending ? "" : " desc")).ToArray());
  2080. cmdText += " order by " + t;
  2081. }
  2082. if (_limit.HasValue)
  2083. {
  2084. cmdText += " limit " + _limit.Value;
  2085. }
  2086. if (_offset.HasValue)
  2087. {
  2088. if (!_limit.HasValue)
  2089. {
  2090. cmdText += " limit -1 ";
  2091. }
  2092. cmdText += " offset " + _offset.Value;
  2093. }
  2094. return Connection.CreateCommand(cmdText, args.ToArray());
  2095. }
  2096. class CompileResult
  2097. {
  2098. public string CommandText { get; set; }
  2099. public object Value { get; set; }
  2100. }
  2101. private CompileResult CompileExpr(Expression expr, List<object> queryArgs)
  2102. {
  2103. if (expr == null)
  2104. {
  2105. throw new NotSupportedException("Expression is NULL");
  2106. }
  2107. else if (expr is BinaryExpression)
  2108. {
  2109. var bin = (BinaryExpression)expr;
  2110. var leftr = CompileExpr(bin.Left, queryArgs);
  2111. var rightr = CompileExpr(bin.Right, queryArgs);
  2112. //If either side is a parameter and is null, then handle the other side specially (for "is null"/"is not null")
  2113. string text;
  2114. if (leftr.CommandText == "?" && leftr.Value == null)
  2115. text = CompileNullBinaryExpression(bin, rightr);
  2116. else if (rightr.CommandText == "?" && rightr.Value == null)
  2117. text = CompileNullBinaryExpression(bin, leftr);
  2118. else
  2119. text = "(" + leftr.CommandText + " " + GetSqlName(bin) + " " + rightr.CommandText + ")";
  2120. return new CompileResult { CommandText = text };
  2121. }
  2122. else if (expr.NodeType == ExpressionType.Call)
  2123. {
  2124. var call = (MethodCallExpression)expr;
  2125. var args = new CompileResult[call.Arguments.Count];
  2126. var obj = call.Object != null ? CompileExpr(call.Object, queryArgs) : null;
  2127. for (var i = 0; i < args.Length; i++)
  2128. {
  2129. args[i] = CompileExpr(call.Arguments[i], queryArgs);
  2130. }
  2131. var sqlCall = "";
  2132. if (call.Method.Name == "Like" && args.Length == 2)
  2133. {
  2134. sqlCall = "(" + args[0].CommandText + " like " + args[1].CommandText + ")";
  2135. }
  2136. else if (call.Method.Name == "Contains" && args.Length == 2)
  2137. {
  2138. sqlCall = "(" + args[1].CommandText + " in " + args[0].CommandText + ")";
  2139. }
  2140. else if (call.Method.Name == "Contains" && args.Length == 1)
  2141. {
  2142. if (call.Object != null && call.Object.Type == typeof(string))
  2143. {
  2144. sqlCall = "(" + obj.CommandText + " like ('%' || " + args[0].CommandText + " || '%'))";
  2145. }
  2146. else
  2147. {
  2148. sqlCall = "(" + args[0].CommandText + " in " + obj.CommandText + ")";
  2149. }
  2150. }
  2151. else if (call.Method.Name == "StartsWith" && args.Length == 1)
  2152. {
  2153. sqlCall = "(" + obj.CommandText + " like (" + args[0].CommandText + " || '%'))";
  2154. }
  2155. else if (call.Method.Name == "EndsWith" && args.Length == 1)
  2156. {
  2157. sqlCall = "(" + obj.CommandText + " like ('%' || " + args[0].CommandText + "))";
  2158. }
  2159. else
  2160. {
  2161. sqlCall = call.Method.Name.ToLower() + "(" + string.Join(",", args.Select(a => a.CommandText).ToArray()) + ")";
  2162. }
  2163. return new CompileResult { CommandText = sqlCall };
  2164. }
  2165. else if (expr.NodeType == ExpressionType.Constant)
  2166. {
  2167. var c = (ConstantExpression)expr;
  2168. queryArgs.Add(c.Value);
  2169. return new CompileResult
  2170. {
  2171. CommandText = "?",
  2172. Value = c.Value
  2173. };
  2174. }
  2175. else if (expr.NodeType == ExpressionType.Convert)
  2176. {
  2177. var u = (UnaryExpression)expr;
  2178. var ty = u.Type;
  2179. var valr = CompileExpr(u.Operand, queryArgs);
  2180. return new CompileResult
  2181. {
  2182. CommandText = valr.CommandText,
  2183. Value = valr.Value != null ? Convert.ChangeType(valr.Value, ty, null) : null
  2184. };
  2185. }
  2186. else if (expr.NodeType == ExpressionType.MemberAccess)
  2187. {
  2188. var mem = (MemberExpression)expr;
  2189. if (mem.Expression.NodeType == ExpressionType.Parameter)
  2190. {
  2191. //
  2192. // This is a column of our table, output just the column name
  2193. // Need to translate it if that column name is mapped
  2194. //
  2195. var columnName = Table.FindColumnWithPropertyName(mem.Member.Name).Name;
  2196. return new CompileResult { CommandText = "\"" + columnName + "\"" };
  2197. }
  2198. else
  2199. {
  2200. object obj = null;
  2201. if (mem.Expression != null)
  2202. {
  2203. var r = CompileExpr(mem.Expression, queryArgs);
  2204. if (r.Value == null)
  2205. {
  2206. throw new NotSupportedException("Member access failed to compile expression");
  2207. }
  2208. if (r.CommandText == "?")
  2209. {
  2210. queryArgs.RemoveAt(queryArgs.Count - 1);
  2211. }
  2212. obj = r.Value;
  2213. }
  2214. //
  2215. // Get the member value
  2216. //
  2217. object val = null;
  2218. #if !NETFX_CORE
  2219. if (mem.Member.MemberType == MemberTypes.Property)
  2220. {
  2221. #else
  2222. if (mem.Member is PropertyInfo) {
  2223. #endif
  2224. var m = (PropertyInfo)mem.Member;
  2225. val = m.GetValue(obj, null);
  2226. #if !NETFX_CORE
  2227. }
  2228. else if (mem.Member.MemberType == MemberTypes.Field)
  2229. {
  2230. #else
  2231. } else if (mem.Member is FieldInfo) {
  2232. #endif
  2233. #if SILVERLIGHT
  2234. val = Expression.Lambda (expr).Compile ().DynamicInvoke ();
  2235. #else
  2236. var m = (FieldInfo)mem.Member;
  2237. val = m.GetValue(obj);
  2238. #endif
  2239. }
  2240. else
  2241. {
  2242. #if !NETFX_CORE
  2243. throw new NotSupportedException("MemberExpr: " + mem.Member.MemberType.ToString());
  2244. #else
  2245. throw new NotSupportedException ("MemberExpr: " + mem.Member.DeclaringType.ToString ());
  2246. #endif
  2247. }
  2248. //
  2249. // Work special magic for enumerables
  2250. //
  2251. if (val != null && val is System.Collections.IEnumerable && !(val is string) && !(val is byte[]))
  2252. {
  2253. var sb = new System.Text.StringBuilder();
  2254. sb.Append("(");
  2255. var head = "";
  2256. foreach (var a in (System.Collections.IEnumerable)val)
  2257. {
  2258. queryArgs.Add(a);
  2259. sb.Append(head);
  2260. sb.Append("?");
  2261. head = ",";
  2262. }
  2263. sb.Append(")");
  2264. return new CompileResult
  2265. {
  2266. CommandText = sb.ToString(),
  2267. Value = val
  2268. };
  2269. }
  2270. else
  2271. {
  2272. queryArgs.Add(val);
  2273. return new CompileResult
  2274. {
  2275. CommandText = "?",
  2276. Value = val
  2277. };
  2278. }
  2279. }
  2280. }
  2281. throw new NotSupportedException("Cannot compile: " + expr.NodeType.ToString());
  2282. }
  2283. /// <summary>
  2284. /// Compiles a BinaryExpression where one of the parameters is null.
  2285. /// </summary>
  2286. /// <param name="parameter">The non-null parameter</param>
  2287. private string CompileNullBinaryExpression(BinaryExpression expression, CompileResult parameter)
  2288. {
  2289. if (expression.NodeType == ExpressionType.Equal)
  2290. return "(" + parameter.CommandText + " is ?)";
  2291. else if (expression.NodeType == ExpressionType.NotEqual)
  2292. return "(" + parameter.CommandText + " is not ?)";
  2293. else
  2294. throw new NotSupportedException("Cannot compile Null-BinaryExpression with type " + expression.NodeType.ToString());
  2295. }
  2296. string GetSqlName(Expression expr)
  2297. {
  2298. var n = expr.NodeType;
  2299. if (n == ExpressionType.GreaterThan)
  2300. return ">";
  2301. else if (n == ExpressionType.GreaterThanOrEqual)
  2302. {
  2303. return ">=";
  2304. }
  2305. else if (n == ExpressionType.LessThan)
  2306. {
  2307. return "<";
  2308. }
  2309. else if (n == ExpressionType.LessThanOrEqual)
  2310. {
  2311. return "<=";
  2312. }
  2313. else if (n == ExpressionType.And)
  2314. {
  2315. return "and";
  2316. }
  2317. else if (n == ExpressionType.AndAlso)
  2318. {
  2319. return "and";
  2320. }
  2321. else if (n == ExpressionType.Or)
  2322. {
  2323. return "or";
  2324. }
  2325. else if (n == ExpressionType.OrElse)
  2326. {
  2327. return "or";
  2328. }
  2329. else if (n == ExpressionType.Equal)
  2330. {
  2331. return "=";
  2332. }
  2333. else if (n == ExpressionType.NotEqual)
  2334. {
  2335. return "!=";
  2336. }
  2337. else
  2338. {
  2339. throw new System.NotSupportedException("Cannot get SQL for: " + n.ToString());
  2340. }
  2341. }
  2342. public int Count()
  2343. {
  2344. return GenerateCommand("count(*)").ExecuteScalar<int>();
  2345. }
  2346. public IEnumerator<T> GetEnumerator()
  2347. {
  2348. if (!_deferred)
  2349. return GenerateCommand("*").ExecuteQuery<T>().GetEnumerator();
  2350. return GenerateCommand("*").ExecuteDeferredQuery<T>().GetEnumerator();
  2351. }
  2352. System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
  2353. {
  2354. return GetEnumerator();
  2355. }
  2356. public T First()
  2357. {
  2358. var query = Take(1);
  2359. return query.ToList<T>().First();
  2360. }
  2361. public T FirstOrDefault()
  2362. {
  2363. var query = this.Take(1);
  2364. return query.ToList<T>().FirstOrDefault();
  2365. }
  2366. }
  2367. public static class SQLite3
  2368. {
  2369. public enum Result : int
  2370. {
  2371. OK = 0,
  2372. Error = 1,
  2373. Internal = 2,
  2374. Perm = 3,
  2375. Abort = 4,
  2376. Busy = 5,
  2377. Locked = 6,
  2378. NoMem = 7,
  2379. ReadOnly = 8,
  2380. Interrupt = 9,
  2381. IOError = 10,
  2382. Corrupt = 11,
  2383. NotFound = 12,
  2384. Full = 13,
  2385. CannotOpen = 14,
  2386. LockErr = 15,
  2387. Empty = 16,
  2388. SchemaChngd = 17,
  2389. TooBig = 18,
  2390. Constraint = 19,
  2391. Mismatch = 20,
  2392. Misuse = 21,
  2393. NotImplementedLFS = 22,
  2394. AccessDenied = 23,
  2395. Format = 24,
  2396. Range = 25,
  2397. NonDBFile = 26,
  2398. Row = 100,
  2399. Done = 101
  2400. }
  2401. public enum ConfigOption : int
  2402. {
  2403. SingleThread = 1,
  2404. MultiThread = 2,
  2405. Serialized = 3
  2406. }
  2407. #if !USE_CSHARP_SQLITE
  2408. [DllImport("sqlite3", EntryPoint = "sqlite3_open", CallingConvention = CallingConvention.Cdecl)]
  2409. public static extern Result Open([MarshalAs(UnmanagedType.LPStr)] string filename, out IntPtr db);
  2410. [DllImport("sqlite3", EntryPoint = "sqlite3_open_v2", CallingConvention = CallingConvention.Cdecl)]
  2411. public static extern Result Open([MarshalAs(UnmanagedType.LPStr)] string filename, out IntPtr db, int flags, IntPtr zvfs);
  2412. [DllImport("sqlite3", EntryPoint = "sqlite3_open_v2", CallingConvention = CallingConvention.Cdecl)]
  2413. public static extern Result Open(byte[] filename, out IntPtr db, int flags, IntPtr zvfs);
  2414. [DllImport("sqlite3", EntryPoint = "sqlite3_open16", CallingConvention = CallingConvention.Cdecl)]
  2415. public static extern Result Open16([MarshalAs(UnmanagedType.LPWStr)] string filename, out IntPtr db);
  2416. [DllImport("sqlite3", EntryPoint = "sqlite3_close", CallingConvention = CallingConvention.Cdecl)]
  2417. public static extern Result Close(IntPtr db);
  2418. [DllImport("sqlite3", EntryPoint = "sqlite3_config", CallingConvention = CallingConvention.Cdecl)]
  2419. public static extern Result Config(ConfigOption option);
  2420. [DllImport("sqlite3", EntryPoint = "sqlite3_busy_timeout", CallingConvention = CallingConvention.Cdecl)]
  2421. public static extern Result BusyTimeout(IntPtr db, int milliseconds);
  2422. [DllImport("sqlite3", EntryPoint = "sqlite3_changes", CallingConvention = CallingConvention.Cdecl)]
  2423. public static extern int Changes(IntPtr db);
  2424. [DllImport("sqlite3", EntryPoint = "sqlite3_prepare_v2", CallingConvention = CallingConvention.Cdecl)]
  2425. public static extern Result Prepare2(IntPtr db, [MarshalAs(UnmanagedType.LPStr)] string sql, int numBytes, out IntPtr stmt, IntPtr pzTail);
  2426. public static IntPtr Prepare2(IntPtr db, string query)
  2427. {
  2428. IntPtr stmt;
  2429. var r = Prepare2(db, query, query.Length, out stmt, IntPtr.Zero);
  2430. if (r != Result.OK)
  2431. {
  2432. throw SQLiteException.New(r, GetErrmsg(db));
  2433. }
  2434. return stmt;
  2435. }
  2436. [DllImport("sqlite3", EntryPoint = "sqlite3_step", CallingConvention = CallingConvention.Cdecl)]
  2437. public static extern Result Step(IntPtr stmt);
  2438. [DllImport("sqlite3", EntryPoint = "sqlite3_reset", CallingConvention = CallingConvention.Cdecl)]
  2439. public static extern Result Reset(IntPtr stmt);
  2440. [DllImport("sqlite3", EntryPoint = "sqlite3_finalize", CallingConvention = CallingConvention.Cdecl)]
  2441. public static extern Result Finalize(IntPtr stmt);
  2442. [DllImport("sqlite3", EntryPoint = "sqlite3_last_insert_rowid", CallingConvention = CallingConvention.Cdecl)]
  2443. public static extern long LastInsertRowid(IntPtr db);
  2444. [DllImport("sqlite3", EntryPoint = "sqlite3_errmsg16", CallingConvention = CallingConvention.Cdecl)]
  2445. public static extern IntPtr Errmsg(IntPtr db);
  2446. public static string GetErrmsg(IntPtr db)
  2447. {
  2448. return Marshal.PtrToStringUni(Errmsg(db));
  2449. }
  2450. [DllImport("sqlite3", EntryPoint = "sqlite3_bind_parameter_index", CallingConvention = CallingConvention.Cdecl)]
  2451. public static extern int BindParameterIndex(IntPtr stmt, [MarshalAs(UnmanagedType.LPStr)] string name);
  2452. [DllImport("sqlite3", EntryPoint = "sqlite3_bind_null", CallingConvention = CallingConvention.Cdecl)]
  2453. public static extern int BindNull(IntPtr stmt, int index);
  2454. [DllImport("sqlite3", EntryPoint = "sqlite3_bind_int", CallingConvention = CallingConvention.Cdecl)]
  2455. public static extern int BindInt(IntPtr stmt, int index, int val);
  2456. [DllImport("sqlite3", EntryPoint = "sqlite3_bind_int64", CallingConvention = CallingConvention.Cdecl)]
  2457. public static extern int BindInt64(IntPtr stmt, int index, long val);
  2458. [DllImport("sqlite3", EntryPoint = "sqlite3_bind_double", CallingConvention = CallingConvention.Cdecl)]
  2459. public static extern int BindDouble(IntPtr stmt, int index, double val);
  2460. [DllImport("sqlite3", EntryPoint = "sqlite3_bind_text16", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
  2461. public static extern int BindText(IntPtr stmt, int index, [MarshalAs(UnmanagedType.LPWStr)] string val, int n, IntPtr free);
  2462. [DllImport("sqlite3", EntryPoint = "sqlite3_bind_blob", CallingConvention = CallingConvention.Cdecl)]
  2463. public static extern int BindBlob(IntPtr stmt, int index, byte[] val, int n, IntPtr free);
  2464. [DllImport("sqlite3", EntryPoint = "sqlite3_column_count", CallingConvention = CallingConvention.Cdecl)]
  2465. public static extern int ColumnCount(IntPtr stmt);
  2466. [DllImport("sqlite3", EntryPoint = "sqlite3_column_name", CallingConvention = CallingConvention.Cdecl)]
  2467. public static extern IntPtr ColumnName(IntPtr stmt, int index);
  2468. [DllImport("sqlite3", EntryPoint = "sqlite3_column_name16", CallingConvention = CallingConvention.Cdecl)]
  2469. private static extern IntPtr ColumnName16Internal(IntPtr stmt, int index);
  2470. public static string ColumnName16(IntPtr stmt, int index)
  2471. {
  2472. return Marshal.PtrToStringUni(ColumnName16Internal(stmt, index));
  2473. }
  2474. [DllImport("sqlite3", EntryPoint = "sqlite3_column_type", CallingConvention = CallingConvention.Cdecl)]
  2475. public static extern ColType ColumnType(IntPtr stmt, int index);
  2476. [DllImport("sqlite3", EntryPoint = "sqlite3_column_int", CallingConvention = CallingConvention.Cdecl)]
  2477. public static extern int ColumnInt(IntPtr stmt, int index);
  2478. [DllImport("sqlite3", EntryPoint = "sqlite3_column_int64", CallingConvention = CallingConvention.Cdecl)]
  2479. public static extern long ColumnInt64(IntPtr stmt, int index);
  2480. [DllImport("sqlite3", EntryPoint = "sqlite3_column_double", CallingConvention = CallingConvention.Cdecl)]
  2481. public static extern double ColumnDouble(IntPtr stmt, int index);
  2482. [DllImport("sqlite3", EntryPoint = "sqlite3_column_text", CallingConvention = CallingConvention.Cdecl)]
  2483. public static extern IntPtr ColumnText(IntPtr stmt, int index);
  2484. [DllImport("sqlite3", EntryPoint = "sqlite3_column_text16", CallingConvention = CallingConvention.Cdecl)]
  2485. public static extern IntPtr ColumnText16(IntPtr stmt, int index);
  2486. [DllImport("sqlite3", EntryPoint = "sqlite3_column_blob", CallingConvention = CallingConvention.Cdecl)]
  2487. public static extern IntPtr ColumnBlob(IntPtr stmt, int index);
  2488. [DllImport("sqlite3", EntryPoint = "sqlite3_column_bytes", CallingConvention = CallingConvention.Cdecl)]
  2489. public static extern int ColumnBytes(IntPtr stmt, int index);
  2490. public static string ColumnString(IntPtr stmt, int index)
  2491. {
  2492. return Marshal.PtrToStringUni(SQLite3.ColumnText16(stmt, index));
  2493. }
  2494. public static byte[] ColumnByteArray(IntPtr stmt, int index)
  2495. {
  2496. int length = ColumnBytes(stmt, index);
  2497. byte[] result = new byte[length];
  2498. if (length > 0)
  2499. Marshal.Copy(ColumnBlob(stmt, index), result, 0, length);
  2500. return result;
  2501. }
  2502. #else
  2503. public static Result Open(string filename, out Sqlite3.sqlite3 db)
  2504. {
  2505. return (Result) Sqlite3.sqlite3_open(filename, out db);
  2506. }
  2507. public static Result Open(string filename, out Sqlite3.sqlite3 db, int flags, IntPtr zVfs)
  2508. {
  2509. return (Result)Sqlite3.sqlite3_open_v2(filename, out db, flags, null);
  2510. }
  2511. public static Result Close(Sqlite3.sqlite3 db)
  2512. {
  2513. return (Result)Sqlite3.sqlite3_close(db);
  2514. }
  2515. public static Result BusyTimeout(Sqlite3.sqlite3 db, int milliseconds)
  2516. {
  2517. return (Result)Sqlite3.sqlite3_busy_timeout(db, milliseconds);
  2518. }
  2519. public static int Changes(Sqlite3.sqlite3 db)
  2520. {
  2521. return Sqlite3.sqlite3_changes(db);
  2522. }
  2523. public static Sqlite3.Vdbe Prepare2(Sqlite3.sqlite3 db, string query)
  2524. {
  2525. Sqlite3.Vdbe stmt = new Sqlite3.Vdbe();
  2526. var r = Sqlite3.sqlite3_prepare_v2(db, query, query.Length, ref stmt, 0);
  2527. if (r != 0)
  2528. {
  2529. throw SQLiteException.New((Result)r, GetErrmsg(db));
  2530. }
  2531. return stmt;
  2532. }
  2533. public static Result Step(Sqlite3.Vdbe stmt)
  2534. {
  2535. return (Result)Sqlite3.sqlite3_step(stmt);
  2536. }
  2537. public static Result Reset(Sqlite3.Vdbe stmt)
  2538. {
  2539. return (Result)Sqlite3.sqlite3_reset(stmt);
  2540. }
  2541. public static Result Finalize(Sqlite3.Vdbe stmt)
  2542. {
  2543. return (Result)Sqlite3.sqlite3_finalize(stmt);
  2544. }
  2545. public static long LastInsertRowid(Sqlite3.sqlite3 db)
  2546. {
  2547. return Sqlite3.sqlite3_last_insert_rowid(db);
  2548. }
  2549. public static string GetErrmsg(Sqlite3.sqlite3 db)
  2550. {
  2551. return Sqlite3.sqlite3_errmsg(db);
  2552. }
  2553. public static int BindParameterIndex(Sqlite3.Vdbe stmt, string name)
  2554. {
  2555. return Sqlite3.sqlite3_bind_parameter_index(stmt, name);
  2556. }
  2557. public static int BindNull(Sqlite3.Vdbe stmt, int index)
  2558. {
  2559. return Sqlite3.sqlite3_bind_null(stmt, index);
  2560. }
  2561. public static int BindInt(Sqlite3.Vdbe stmt, int index, int val)
  2562. {
  2563. return Sqlite3.sqlite3_bind_int(stmt, index, val);
  2564. }
  2565. public static int BindInt64(Sqlite3.Vdbe stmt, int index, long val)
  2566. {
  2567. return Sqlite3.sqlite3_bind_int64(stmt, index, val);
  2568. }
  2569. public static int BindDouble(Sqlite3.Vdbe stmt, int index, double val)
  2570. {
  2571. return Sqlite3.sqlite3_bind_double(stmt, index, val);
  2572. }
  2573. public static int BindText(Sqlite3.Vdbe stmt, int index, string val, int n, IntPtr free)
  2574. {
  2575. return Sqlite3.sqlite3_bind_text(stmt, index, val, n, null);
  2576. }
  2577. public static int BindBlob(Sqlite3.Vdbe stmt, int index, byte[] val, int n, IntPtr free)
  2578. {
  2579. return Sqlite3.sqlite3_bind_blob(stmt, index, val, n, null);
  2580. }
  2581. public static int ColumnCount(Sqlite3.Vdbe stmt)
  2582. {
  2583. return Sqlite3.sqlite3_column_count(stmt);
  2584. }
  2585. public static string ColumnName(Sqlite3.Vdbe stmt, int index)
  2586. {
  2587. return Sqlite3.sqlite3_column_name(stmt, index);
  2588. }
  2589. public static string ColumnName16(Sqlite3.Vdbe stmt, int index)
  2590. {
  2591. return Sqlite3.sqlite3_column_name(stmt, index);
  2592. }
  2593. public static ColType ColumnType(Sqlite3.Vdbe stmt, int index)
  2594. {
  2595. return (ColType)Sqlite3.sqlite3_column_type(stmt, index);
  2596. }
  2597. public static int ColumnInt(Sqlite3.Vdbe stmt, int index)
  2598. {
  2599. return Sqlite3.sqlite3_column_int(stmt, index);
  2600. }
  2601. public static long ColumnInt64(Sqlite3.Vdbe stmt, int index)
  2602. {
  2603. return Sqlite3.sqlite3_column_int64(stmt, index);
  2604. }
  2605. public static double ColumnDouble(Sqlite3.Vdbe stmt, int index)
  2606. {
  2607. return Sqlite3.sqlite3_column_double(stmt, index);
  2608. }
  2609. public static string ColumnText(Sqlite3.Vdbe stmt, int index)
  2610. {
  2611. return Sqlite3.sqlite3_column_text(stmt, index);
  2612. }
  2613. public static string ColumnText16(Sqlite3.Vdbe stmt, int index)
  2614. {
  2615. return Sqlite3.sqlite3_column_text(stmt, index);
  2616. }
  2617. public static byte[] ColumnBlob(Sqlite3.Vdbe stmt, int index)
  2618. {
  2619. return Sqlite3.sqlite3_column_blob(stmt, index);
  2620. }
  2621. public static int ColumnBytes(Sqlite3.Vdbe stmt, int index)
  2622. {
  2623. return Sqlite3.sqlite3_column_bytes(stmt, index);
  2624. }
  2625. public static string ColumnString(Sqlite3.Vdbe stmt, int index)
  2626. {
  2627. return Sqlite3.sqlite3_column_text(stmt, index);
  2628. }
  2629. public static byte[] ColumnByteArray(Sqlite3.Vdbe stmt, int index)
  2630. {
  2631. return ColumnBlob(stmt, index);
  2632. }
  2633. #endif
  2634. public enum ColType : int
  2635. {
  2636. Integer = 1,
  2637. Float = 2,
  2638. Text = 3,
  2639. Blob = 4,
  2640. Null = 5
  2641. }
  2642. }
  2643. }