PageRenderTime 48ms CodeModel.GetById 10ms RepoModel.GetById 0ms app.codeStats 1ms

/BE2012/SQLite.cs

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