PageRenderTime 59ms CodeModel.GetById 17ms 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

Large files files are truncated, but you can click here to view the full 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:…

Large files files are truncated, but you can click here to view the full file