PageRenderTime 79ms CodeModel.GetById 26ms RepoModel.GetById 0ms app.codeStats 1ms

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

https://bitbucket.org/garethl/zebraflickr
C# | 2980 lines | 2180 code | 337 blank | 463 comment | 422 complexity | 0004f7bedbb3202324210f3f13ede173 MD5 | raw file

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)
  47. : base(message)
  48. {
  49. Result = r;
  50. }
  51. public static SQLiteException New(SQLite3.Result r, string message)
  52. {
  53. return new SQLiteException(r, message);
  54. }
  55. }
  56. [Flags]
  57. public enum SQLiteOpenFlags
  58. {
  59. ReadOnly = 1, ReadWrite = 2, Create = 4,
  60. NoMutex = 0x8000, FullMutex = 0x10000,
  61. SharedCache = 0x20000, PrivateCache = 0x40000,
  62. ProtectionComplete = 0x00100000,
  63. ProtectionCompleteUnlessOpen = 0x00200000,
  64. ProtectionCompleteUntilFirstUserAuthentication = 0x00300000,
  65. ProtectionNone = 0x00400000
  66. }
  67. /// <summary>
  68. /// Represents an open connection to a SQLite database.
  69. /// </summary>
  70. public class SQLiteConnection : IDisposable
  71. {
  72. private bool _open;
  73. private TimeSpan _busyTimeout;
  74. private Dictionary<string, TableMapping> _mappings = null;
  75. private Dictionary<string, TableMapping> _tables = null;
  76. private System.Diagnostics.Stopwatch _sw;
  77. private long _elapsedMilliseconds = 0;
  78. private int _trasactionDepth = 0;
  79. private Random _rand = new Random();
  80. public Sqlite3DatabaseHandle Handle { get; private set; }
  81. #if USE_CSHARP_SQLITE
  82. internal static readonly Sqlite3DatabaseHandle NullHandle = null;
  83. #else
  84. internal static readonly Sqlite3DatabaseHandle NullHandle = IntPtr.Zero;
  85. #endif
  86. public string DatabasePath { get; private set; }
  87. public bool TimeExecution { get; set; }
  88. public bool Trace { get; set; }
  89. public bool StoreDateTimeAsTicks { get; private set; }
  90. /// <summary>
  91. /// Constructs a new SQLiteConnection and opens a SQLite database specified by databasePath.
  92. /// </summary>
  93. /// <param name="databasePath">
  94. /// Specifies the path to the database file.
  95. /// </param>
  96. /// <param name="storeDateTimeAsTicks">
  97. /// Specifies whether to store DateTime properties as ticks (true) or strings (false). You
  98. /// absolutely do want to store them as Ticks in all new projects. The default of false is
  99. /// only here for backwards compatibility. There is a *significant* speed advantage, with no
  100. /// down sides, when setting storeDateTimeAsTicks = true.
  101. /// </param>
  102. public SQLiteConnection(string databasePath, bool storeDateTimeAsTicks = false)
  103. {
  104. DatabasePath = databasePath;
  105. Sqlite3DatabaseHandle handle;
  106. var r = SQLite3.Open(DatabasePath, out handle);
  107. Handle = handle;
  108. if (r != SQLite3.Result.OK)
  109. {
  110. throw SQLiteException.New(r, String.Format("Could not open database file: {0} ({1})", DatabasePath, r));
  111. }
  112. _open = true;
  113. StoreDateTimeAsTicks = storeDateTimeAsTicks;
  114. BusyTimeout = TimeSpan.FromSeconds(1);
  115. }
  116. /// <summary>
  117. /// Constructs a new SQLiteConnection and opens a SQLite database specified by databasePath.
  118. /// </summary>
  119. /// <param name="databasePath">
  120. /// Specifies the path to the database file.
  121. /// </param>
  122. /// <param name="storeDateTimeAsTicks">
  123. /// Specifies whether to store DateTime properties as ticks (true) or strings (false). You
  124. /// absolutely do want to store them as Ticks in all new projects. The default of false is
  125. /// only here for backwards compatibility. There is a *significant* speed advantage, with no
  126. /// down sides, when setting storeDateTimeAsTicks = true.
  127. /// </param>
  128. public SQLiteConnection(string databasePath, SQLiteOpenFlags openFlags, bool storeDateTimeAsTicks = false)
  129. {
  130. DatabasePath = databasePath;
  131. Sqlite3DatabaseHandle handle;
  132. #if SILVERLIGHT
  133. var r = SQLite3.Open (databasePath, out handle, (int)openFlags, IntPtr.Zero);
  134. #else
  135. // open using the byte[]
  136. // in the case where the path may include Unicode
  137. // force open to using UTF-8 using sqlite3_open_v2
  138. byte[] databasePathAsBytes;
  139. int databasePathLength;
  140. databasePathLength = System.Text.Encoding.UTF8.GetByteCount(DatabasePath);
  141. databasePathAsBytes = new byte[databasePathLength + 1];
  142. databasePathLength = System.Text.Encoding.UTF8.GetBytes(DatabasePath, 0, DatabasePath.Length, databasePathAsBytes, 0);
  143. var r = SQLite3.Open(databasePathAsBytes, out handle, (int)openFlags, IntPtr.Zero);
  144. #endif
  145. Handle = handle;
  146. if (r != SQLite3.Result.OK)
  147. {
  148. throw SQLiteException.New(r, String.Format("Could not open database file: {0} ({1})", DatabasePath, r));
  149. }
  150. _open = true;
  151. StoreDateTimeAsTicks = storeDateTimeAsTicks;
  152. BusyTimeout = TimeSpan.FromSeconds(1);
  153. }
  154. static SQLiteConnection()
  155. {
  156. if (_preserveDuringLinkMagic)
  157. {
  158. var ti = new TableInfo();
  159. ti.name = "magic";
  160. }
  161. }
  162. /// <summary>
  163. /// Used to list some code that we want the MonoTouch linker
  164. /// to see, but that we never want to actually execute.
  165. /// </summary>
  166. static bool _preserveDuringLinkMagic = false;
  167. /// <summary>
  168. /// Sets a busy handler to sleep the specified amount of time when a table is locked.
  169. /// The handler will sleep multiple times until a total time of <see cref="BusyTimeout"/> has accumulated.
  170. /// </summary>
  171. public TimeSpan BusyTimeout
  172. {
  173. get { return _busyTimeout; }
  174. set
  175. {
  176. _busyTimeout = value;
  177. if (Handle != NullHandle)
  178. {
  179. SQLite3.BusyTimeout(Handle, (int)_busyTimeout.TotalMilliseconds);
  180. }
  181. }
  182. }
  183. /// <summary>
  184. /// Returns the mappings from types to tables that the connection
  185. /// currently understands.
  186. /// </summary>
  187. public IEnumerable<TableMapping> TableMappings
  188. {
  189. get
  190. {
  191. if (_tables == null)
  192. {
  193. return Enumerable.Empty<TableMapping>();
  194. }
  195. else
  196. {
  197. return _tables.Values;
  198. }
  199. }
  200. }
  201. /// <summary>
  202. /// Retrieves the mapping that is automatically generated for the given type.
  203. /// </summary>
  204. /// <param name="type">
  205. /// The type whose mapping to the database is returned.
  206. /// </param>
  207. /// <returns>
  208. /// The mapping represents the schema of the columns of the database and contains
  209. /// methods to set and get properties of objects.
  210. /// </returns>
  211. public TableMapping GetMapping(Type type)
  212. {
  213. if (_mappings == null)
  214. {
  215. _mappings = new Dictionary<string, TableMapping>();
  216. }
  217. TableMapping map;
  218. if (!_mappings.TryGetValue(type.FullName, out map))
  219. {
  220. map = new TableMapping(type);
  221. _mappings[type.FullName] = map;
  222. }
  223. return map;
  224. }
  225. /// <summary>
  226. /// Retrieves the mapping that is automatically generated for the given type.
  227. /// </summary>
  228. /// <returns>
  229. /// The mapping represents the schema of the columns of the database and contains
  230. /// methods to set and get properties of objects.
  231. /// </returns>
  232. public TableMapping GetMapping<T>()
  233. {
  234. return GetMapping(typeof(T));
  235. }
  236. private struct IndexedColumn
  237. {
  238. public int Order;
  239. public string ColumnName;
  240. }
  241. private struct IndexInfo
  242. {
  243. public string IndexName;
  244. public string TableName;
  245. public bool Unique;
  246. public List<IndexedColumn> Columns;
  247. }
  248. /// <summary>
  249. /// Executes a "drop table" on the database. This is non-recoverable.
  250. /// </summary>
  251. public int DropTable<T>()
  252. {
  253. var map = GetMapping(typeof(T));
  254. var query = string.Format("drop table if exists \"{0}\"", map.TableName);
  255. return Execute(query);
  256. }
  257. /// <summary>
  258. /// Executes a "create table if not exists" on the database. It also
  259. /// creates any specified indexes on the columns of the table. It uses
  260. /// a schema automatically generated from the specified type. You can
  261. /// later access this schema by calling GetMapping.
  262. /// </summary>
  263. /// <returns>
  264. /// The number of entries added to the database schema.
  265. /// </returns>
  266. public int CreateTable<T>()
  267. {
  268. return CreateTable(typeof(T));
  269. }
  270. /// <summary>
  271. /// Executes a "create table if not exists" on the database. It also
  272. /// creates any specified indexes on the columns of the table. It uses
  273. /// a schema automatically generated from the specified type. You can
  274. /// later access this schema by calling GetMapping.
  275. /// </summary>
  276. /// <param name="ty">Type to reflect to a database table.</param>
  277. /// <returns>
  278. /// The number of entries added to the database schema.
  279. /// </returns>
  280. public int CreateTable(Type ty)
  281. {
  282. if (_tables == null)
  283. {
  284. _tables = new Dictionary<string, TableMapping>();
  285. }
  286. TableMapping map;
  287. if (!_tables.TryGetValue(ty.FullName, out map))
  288. {
  289. map = GetMapping(ty);
  290. _tables.Add(ty.FullName, map);
  291. }
  292. var query = "create table if not exists \"" + map.TableName + "\"(\n";
  293. var decls = map.Columns.Select(p => Orm.SqlDecl(p, StoreDateTimeAsTicks));
  294. var decl = string.Join(",\n", decls.ToArray());
  295. query += decl;
  296. query += ")";
  297. var count = Execute(query);
  298. if (count == 0)
  299. { //Possible bug: This always seems to return 0?
  300. // Table already exists, migrate it
  301. MigrateTable(map);
  302. }
  303. var indexes = new Dictionary<string, IndexInfo>();
  304. foreach (var c in map.Columns)
  305. {
  306. foreach (var i in c.Indices)
  307. {
  308. var iname = i.Name ?? map.TableName + "_" + c.Name;
  309. IndexInfo iinfo;
  310. if (!indexes.TryGetValue(iname, out iinfo))
  311. {
  312. iinfo = new IndexInfo
  313. {
  314. IndexName = iname,
  315. TableName = map.TableName,
  316. Unique = i.Unique,
  317. Columns = new List<IndexedColumn>()
  318. };
  319. indexes.Add(iname, iinfo);
  320. }
  321. if (i.Unique != iinfo.Unique)
  322. throw new Exception("All the columns in an index must have the same value for their Unique property");
  323. iinfo.Columns.Add(new IndexedColumn
  324. {
  325. Order = i.Order,
  326. ColumnName = c.Name
  327. });
  328. }
  329. }
  330. foreach (var indexName in indexes.Keys)
  331. {
  332. var index = indexes[indexName];
  333. const string sqlFormat = "create {3} index if not exists \"{0}\" on \"{1}\"(\"{2}\")";
  334. var columns = String.Join("\",\"", index.Columns.OrderBy(i => i.Order).Select(i => i.ColumnName).ToArray());
  335. var sql = String.Format(sqlFormat, indexName, index.TableName, columns, index.Unique ? "unique" : "");
  336. count += Execute(sql);
  337. }
  338. return count;
  339. }
  340. public class TableInfo
  341. {
  342. public int cid { get; set; }
  343. public string name { get; set; }
  344. public string type { get; set; }
  345. public int notnull { get; set; }
  346. public string dflt_value { get; set; }
  347. public int pk { get; set; }
  348. }
  349. void MigrateTable(TableMapping map)
  350. {
  351. var query = "pragma table_info(\"" + map.TableName + "\")";
  352. var existingCols = Query<TableInfo>(query);
  353. var toBeAdded = new List<TableMapping.Column>();
  354. foreach (var p in map.Columns)
  355. {
  356. var found = false;
  357. foreach (var c in existingCols)
  358. {
  359. found = p.Name == c.name;
  360. if (found)
  361. break;
  362. }
  363. if (!found)
  364. {
  365. toBeAdded.Add(p);
  366. }
  367. }
  368. foreach (var p in toBeAdded)
  369. {
  370. var addCol = "alter table \"" + map.TableName + "\" add column " + Orm.SqlDecl(p, StoreDateTimeAsTicks);
  371. Execute(addCol);
  372. }
  373. }
  374. /// <summary>
  375. /// Creates a new SQLiteCommand. Can be overridden to provide a sub-class.
  376. /// </summary>
  377. /// <seealso cref="SQLiteCommand.OnInstanceCreated"/>
  378. protected virtual SQLiteCommand NewCommand()
  379. {
  380. return new SQLiteCommand(this);
  381. }
  382. /// <summary>
  383. /// Creates a new SQLiteCommand given the command text with arguments. Place a '?'
  384. /// in the command text for each of the arguments.
  385. /// </summary>
  386. /// <param name="cmdText">
  387. /// The fully escaped SQL.
  388. /// </param>
  389. /// <param name="args">
  390. /// Arguments to substitute for the occurences of '?' in the command text.
  391. /// </param>
  392. /// <returns>
  393. /// A <see cref="SQLiteCommand"/>
  394. /// </returns>
  395. public SQLiteCommand CreateCommand(string cmdText, params object[] ps)
  396. {
  397. if (!_open)
  398. {
  399. throw SQLiteException.New(SQLite3.Result.Error, "Cannot create commands from unopened database");
  400. }
  401. else
  402. {
  403. var cmd = NewCommand();
  404. cmd.CommandText = cmdText;
  405. foreach (var o in ps)
  406. {
  407. cmd.Bind(o);
  408. }
  409. return cmd;
  410. }
  411. }
  412. /// <summary>
  413. /// Creates a SQLiteCommand given the command text (SQL) with arguments. Place a '?'
  414. /// in the command text for each of the arguments and then executes that command.
  415. /// Use this method instead of Query when you don't expect rows back. Such cases include
  416. /// INSERTs, UPDATEs, and DELETEs.
  417. /// You can set the Trace or TimeExecution properties of the connection
  418. /// to profile execution.
  419. /// </summary>
  420. /// <param name="query">
  421. /// The fully escaped SQL.
  422. /// </param>
  423. /// <param name="args">
  424. /// Arguments to substitute for the occurences of '?' in the query.
  425. /// </param>
  426. /// <returns>
  427. /// The number of rows modified in the database as a result of this execution.
  428. /// </returns>
  429. public int Execute(string query, params object[] args)
  430. {
  431. var cmd = CreateCommand(query, args);
  432. if (TimeExecution)
  433. {
  434. if (_sw == null)
  435. {
  436. _sw = new System.Diagnostics.Stopwatch();
  437. }
  438. _sw.Reset();
  439. _sw.Start();
  440. }
  441. var r = cmd.ExecuteNonQuery();
  442. if (TimeExecution)
  443. {
  444. _sw.Stop();
  445. _elapsedMilliseconds += _sw.ElapsedMilliseconds;
  446. Debug.WriteLine(string.Format("Finished in {0} ms ({1:0.0} s total)", _sw.ElapsedMilliseconds, _elapsedMilliseconds / 1000.0));
  447. }
  448. return r;
  449. }
  450. public T ExecuteScalar<T>(string query, params object[] args)
  451. {
  452. var cmd = CreateCommand(query, args);
  453. if (TimeExecution)
  454. {
  455. if (_sw == null)
  456. {
  457. _sw = new System.Diagnostics.Stopwatch();
  458. }
  459. _sw.Reset();
  460. _sw.Start();
  461. }
  462. var r = cmd.ExecuteScalar<T>();
  463. if (TimeExecution)
  464. {
  465. _sw.Stop();
  466. _elapsedMilliseconds += _sw.ElapsedMilliseconds;
  467. Debug.WriteLine(string.Format("Finished in {0} ms ({1:0.0} s total)", _sw.ElapsedMilliseconds, _elapsedMilliseconds / 1000.0));
  468. }
  469. return r;
  470. }
  471. /// <summary>
  472. /// Creates a SQLiteCommand given the command text (SQL) with arguments. Place a '?'
  473. /// in the command text for each of the arguments and then executes that command.
  474. /// It returns each row of the result using the mapping automatically generated for
  475. /// the given type.
  476. /// </summary>
  477. /// <param name="query">
  478. /// The fully escaped SQL.
  479. /// </param>
  480. /// <param name="args">
  481. /// Arguments to substitute for the occurences of '?' in the query.
  482. /// </param>
  483. /// <returns>
  484. /// An enumerable with one result for each row returned by the query.
  485. /// </returns>
  486. public List<T> Query<T>(string query, params object[] args) where T : new()
  487. {
  488. var cmd = CreateCommand(query, args);
  489. return cmd.ExecuteQuery<T>();
  490. }
  491. /// <summary>
  492. /// Creates a SQLiteCommand given the command text (SQL) with arguments. Place a '?'
  493. /// in the command text for each of the arguments and then executes that command.
  494. /// It returns each row of the result using the mapping automatically generated for
  495. /// the given type.
  496. /// </summary>
  497. /// <param name="query">
  498. /// The fully escaped SQL.
  499. /// </param>
  500. /// <param name="args">
  501. /// Arguments to substitute for the occurences of '?' in the query.
  502. /// </param>
  503. /// <returns>
  504. /// An enumerable with one result for each row returned by the query.
  505. /// The enumerator will call sqlite3_step on each call to MoveNext, so the database
  506. /// connection must remain open for the lifetime of the enumerator.
  507. /// </returns>
  508. public IEnumerable<T> DeferredQuery<T>(string query, params object[] args) where T : new()
  509. {
  510. var cmd = CreateCommand(query, args);
  511. return cmd.ExecuteDeferredQuery<T>();
  512. }
  513. /// <summary>
  514. /// Creates a SQLiteCommand given the command text (SQL) with arguments. Place a '?'
  515. /// in the command text for each of the arguments and then executes that command.
  516. /// It returns each row of the result using the specified mapping. This function is
  517. /// only used by libraries in order to query the database via introspection. It is
  518. /// normally not used.
  519. /// </summary>
  520. /// <param name="map">
  521. /// A <see cref="TableMapping"/> to use to convert the resulting rows
  522. /// into objects.
  523. /// </param>
  524. /// <param name="query">
  525. /// The fully escaped SQL.
  526. /// </param>
  527. /// <param name="args">
  528. /// Arguments to substitute for the occurences of '?' in the query.
  529. /// </param>
  530. /// <returns>
  531. /// An enumerable with one result for each row returned by the query.
  532. /// </returns>
  533. public List<object> Query(TableMapping map, string query, params object[] args)
  534. {
  535. var cmd = CreateCommand(query, args);
  536. return cmd.ExecuteQuery<object>(map);
  537. }
  538. /// <summary>
  539. /// Creates a SQLiteCommand given the command text (SQL) with arguments. Place a '?'
  540. /// in the command text for each of the arguments and then executes that command.
  541. /// It returns each row of the result using the specified mapping. This function is
  542. /// only used by libraries in order to query the database via introspection. It is
  543. /// normally not used.
  544. /// </summary>
  545. /// <param name="map">
  546. /// A <see cref="TableMapping"/> to use to convert the resulting rows
  547. /// into objects.
  548. /// </param>
  549. /// <param name="query">
  550. /// The fully escaped SQL.
  551. /// </param>
  552. /// <param name="args">
  553. /// Arguments to substitute for the occurences of '?' in the query.
  554. /// </param>
  555. /// <returns>
  556. /// An enumerable with one result for each row returned by the query.
  557. /// The enumerator will call sqlite3_step on each call to MoveNext, so the database
  558. /// connection must remain open for the lifetime of the enumerator.
  559. /// </returns>
  560. public IEnumerable<object> DeferredQuery(TableMapping map, string query, params object[] args)
  561. {
  562. var cmd = CreateCommand(query, args);
  563. return cmd.ExecuteDeferredQuery<object>(map);
  564. }
  565. /// <summary>
  566. /// Returns a queryable interface to the table represented by the given type.
  567. /// </summary>
  568. /// <returns>
  569. /// A queryable object that is able to translate Where, OrderBy, and Take
  570. /// queries into native SQL.
  571. /// </returns>
  572. public TableQuery<T> Table<T>() where T : new()
  573. {
  574. return new TableQuery<T>(this);
  575. }
  576. /// <summary>
  577. /// Attempts to retrieve an object with the given primary key from the table
  578. /// associated with the specified type. Use of this method requires that
  579. /// the given type have a designated PrimaryKey (using the PrimaryKeyAttribute).
  580. /// </summary>
  581. /// <param name="pk">
  582. /// The primary key.
  583. /// </param>
  584. /// <returns>
  585. /// The object with the given primary key. Throws a not found exception
  586. /// if the object is not found.
  587. /// </returns>
  588. public T Get<T>(object pk) where T : new()
  589. {
  590. var map = GetMapping(typeof(T));
  591. return Query<T>(map.GetByPrimaryKeySql, pk).First();
  592. }
  593. /// <summary>
  594. /// Attempts to retrieve the first object that matches the predicate from the table
  595. /// associated with the specified type.
  596. /// </summary>
  597. /// <param name="predicate">
  598. /// A predicate for which object to find.
  599. /// </param>
  600. /// <returns>
  601. /// The object that matches the given predicate. Throws a not found exception
  602. /// if the object is not found.
  603. /// </returns>
  604. public T Get<T>(Expression<Func<T, bool>> predicate) where T : new()
  605. {
  606. return Table<T>().Where(predicate).First();
  607. }
  608. /// <summary>
  609. /// Attempts to retrieve an object with the given primary key from the table
  610. /// associated with the specified type. Use of this method requires that
  611. /// the given type have a designated PrimaryKey (using the PrimaryKeyAttribute).
  612. /// </summary>
  613. /// <param name="pk">
  614. /// The primary key.
  615. /// </param>
  616. /// <returns>
  617. /// The object with the given primary key or null
  618. /// if the object is not found.
  619. /// </returns>
  620. public T Find<T>(object pk) where T : new()
  621. {
  622. var map = GetMapping(typeof(T));
  623. return Query<T>(map.GetByPrimaryKeySql, pk).FirstOrDefault();
  624. }
  625. /// <summary>
  626. /// Attempts to retrieve an object with the given primary key from the table
  627. /// associated with the specified type. Use of this method requires that
  628. /// the given type have a designated PrimaryKey (using the PrimaryKeyAttribute).
  629. /// </summary>
  630. /// <param name="pk">
  631. /// The primary key.
  632. /// </param>
  633. /// <param name="map">
  634. /// The TableMapping used to identify the object type.
  635. /// </param>
  636. /// <returns>
  637. /// The object with the given primary key or null
  638. /// if the object is not found.
  639. /// </returns>
  640. public object Find(object pk, TableMapping map)
  641. {
  642. return Query(map, map.GetByPrimaryKeySql, pk).FirstOrDefault();
  643. }
  644. /// <summary>
  645. /// Attempts to retrieve the first object that matches the predicate from the table
  646. /// associated with the specified type.
  647. /// </summary>
  648. /// <param name="predicate">
  649. /// A predicate for which object to find.
  650. /// </param>
  651. /// <returns>
  652. /// The object that matches the given predicate or null
  653. /// if the object is not found.
  654. /// </returns>
  655. public T Find<T>(Expression<Func<T, bool>> predicate) where T : new()
  656. {
  657. return Table<T>().Where(predicate).FirstOrDefault();
  658. }
  659. /// <summary>
  660. /// Whether <see cref="BeginTransaction"/> has been called and the database is waiting for a <see cref="Commit"/>.
  661. /// </summary>
  662. public bool IsInTransaction
  663. {
  664. get { return _trasactionDepth > 0; }
  665. }
  666. /// <summary>
  667. /// Begins a new transaction. Call <see cref="Commit"/> to end the transaction.
  668. /// </summary>
  669. /// <example cref="System.InvalidOperationException">Throws if a transaction has already begun.</example>
  670. public void BeginTransaction()
  671. {
  672. // The BEGIN command only works if the transaction stack is empty,
  673. // or in other words if there are no pending transactions.
  674. // If the transaction stack is not empty when the BEGIN command is invoked,
  675. // then the command fails with an error.
  676. // Rather than crash with an error, we will just ignore calls to BeginTransaction
  677. // that would result in an error.
  678. if (Interlocked.CompareExchange(ref _trasactionDepth, 1, 0) == 0)
  679. {
  680. try
  681. {
  682. Execute("begin transaction");
  683. }
  684. catch (Exception ex)
  685. {
  686. var sqlExp = ex as SQLiteException;
  687. if (sqlExp != null)
  688. {
  689. // It is recommended that applications respond to the errors listed below
  690. // by explicitly issuing a ROLLBACK command.
  691. // TODO: This rollback failsafe should be localized to all throw sites.
  692. switch (sqlExp.Result)
  693. {
  694. case SQLite3.Result.IOError:
  695. case SQLite3.Result.Full:
  696. case SQLite3.Result.Busy:
  697. case SQLite3.Result.NoMem:
  698. case SQLite3.Result.Interrupt:
  699. RollbackTo(null, true);
  700. break;
  701. }
  702. }
  703. else
  704. {
  705. // Call decrement and not VolatileWrite in case we've already
  706. // created a transaction point in SaveTransactionPoint since the catch.
  707. Interlocked.Decrement(ref _trasactionDepth);
  708. }
  709. throw;
  710. }
  711. }
  712. else
  713. {
  714. // Calling BeginTransaction on an already open transaction is invalid
  715. throw new System.InvalidOperationException("Cannot begin a transaction while already in a transaction.");
  716. }
  717. }
  718. /// <summary>
  719. /// Creates a savepoint in the database at the current point in the transaction timeline.
  720. /// Begins a new transaction if one is not in progress.
  721. ///
  722. /// Call <see cref="RollbackTo"/> to undo transactions since the returned savepoint.
  723. /// Call <see cref="Release"/> to commit transactions after the savepoint returned here.
  724. /// Call <see cref="Commit"/> to end the transaction, committing all changes.
  725. /// </summary>
  726. /// <returns>A string naming the savepoint.</returns>
  727. public string SaveTransactionPoint()
  728. {
  729. int depth = Interlocked.Increment(ref _trasactionDepth) - 1;
  730. string retVal = "S" + (short)_rand.Next(short.MaxValue) + "D" + depth;
  731. try
  732. {
  733. Execute("savepoint " + retVal);
  734. }
  735. catch (Exception ex)
  736. {
  737. var sqlExp = ex as SQLiteException;
  738. if (sqlExp != null)
  739. {
  740. // It is recommended that applications respond to the errors listed below
  741. // by explicitly issuing a ROLLBACK command.
  742. // TODO: This rollback failsafe should be localized to all throw sites.
  743. switch (sqlExp.Result)
  744. {
  745. case SQLite3.Result.IOError:
  746. case SQLite3.Result.Full:
  747. case SQLite3.Result.Busy:
  748. case SQLite3.Result.NoMem:
  749. case SQLite3.Result.Interrupt:
  750. RollbackTo(null, true);
  751. break;
  752. }
  753. }
  754. else
  755. {
  756. Interlocked.Decrement(ref _trasactionDepth);
  757. }
  758. throw;
  759. }
  760. return retVal;
  761. }
  762. /// <summary>
  763. /// Rolls back the transaction that was begun by <see cref="BeginTransaction"/> or <see cref="SaveTransactionPoint"/>.
  764. /// </summary>
  765. public void Rollback()
  766. {
  767. RollbackTo(null, false);
  768. }
  769. /// <summary>
  770. /// Rolls back the savepoint created by <see cref="BeginTransaction"/> or SaveTransactionPoint.
  771. /// </summary>
  772. /// <param name="savepoint">The name of the savepoint to roll back to, as returned by <see cref="SaveTransactionPoint"/>. If savepoint is null or empty, this method is equivalent to a call to <see cref="Rollback"/></param>
  773. public void RollbackTo(string savepoint)
  774. {
  775. RollbackTo(savepoint, false);
  776. }
  777. /// <summary>
  778. /// Rolls back the transaction that was begun by <see cref="BeginTransaction"/>.
  779. /// </summary>
  780. /// <param name="noThrow">true to avoid throwing exceptions, false otherwise</param>
  781. private void RollbackTo(string savepoint, bool noThrow)
  782. {
  783. // Rolling back without a TO clause rolls backs all transactions
  784. // and leaves the transaction stack empty.
  785. try
  786. {
  787. if (String.IsNullOrEmpty(savepoint))
  788. {
  789. if (Interlocked.Exchange(ref _trasactionDepth, 0) > 0)
  790. {
  791. Execute("rollback");
  792. }
  793. }
  794. else
  795. {
  796. DoSavePointExecute(savepoint, "rollback to ");
  797. }
  798. }
  799. catch (SQLiteException)
  800. {
  801. if (!noThrow)
  802. throw;
  803. }
  804. // No need to rollback if there are no transactions open.
  805. }
  806. /// <summary>
  807. /// Releases a savepoint returned from <see cref="SaveTransactionPoint"/>. Releasing a savepoint
  808. /// makes changes since that savepoint permanent if the savepoint began the transaction,
  809. /// or otherwise the changes are permanent pending a call to <see cref="Commit"/>.
  810. ///
  811. /// The RELEASE command is like a COMMIT for a SAVEPOINT.
  812. /// </summary>
  813. /// <param name="savepoint">The name of the savepoint to release. The string should be the result of a call to <see cref="SaveTransactionPoint"/></param>
  814. public void Release(string savepoint)
  815. {
  816. DoSavePointExecute(savepoint, "release ");
  817. }
  818. private void DoSavePointExecute(string savepoint, string cmd)
  819. {
  820. // Validate the savepoint
  821. int firstLen = savepoint.IndexOf('D');
  822. if (firstLen >= 2 && savepoint.Length > firstLen + 1)
  823. {
  824. int depth;
  825. if (Int32.TryParse(savepoint.Substring(firstLen + 1), out depth))
  826. {
  827. // TODO: Mild race here, but inescapable without locking almost everywhere.
  828. if (0 <= depth && depth < _trasactionDepth)
  829. {
  830. #if NETFX_CORE
  831. Volatile.Write (ref _trasactionDepth, depth);
  832. #elif SILVERLIGHT
  833. _trasactionDepth = depth;
  834. #else
  835. Thread.VolatileWrite(ref _trasactionDepth, depth);
  836. #endif
  837. Execute(cmd + savepoint);
  838. return;
  839. }
  840. }
  841. }
  842. throw new ArgumentException("savePoint", "savePoint is not valid, and should be the result of a call to SaveTransactionPoint.");
  843. }
  844. /// <summary>
  845. /// Commits the transaction that was begun by <see cref="BeginTransaction"/>.
  846. /// </summary>
  847. public void Commit()
  848. {
  849. if (Interlocked.Exchange(ref _trasactionDepth, 0) != 0)
  850. {
  851. Execute("commit");
  852. }
  853. // Do nothing on a commit with no open transaction
  854. }
  855. /// <summary>
  856. /// Executes <param name="action"> within a (possibly nested) transaction by wrapping it in a SAVEPOINT. If an
  857. /// exception occurs the whole transaction is rolled back, not just the current savepoint. The exception
  858. /// is rethrown.
  859. /// </summary>
  860. /// <param name="action">
  861. /// The <see cref="Action"/> to perform within a transaction. <param name="action"> can contain any number
  862. /// of operations on the connection but should never call <see cref="BeginTransaction"/> or
  863. /// <see cref="Commit"/>.
  864. /// </param>
  865. public void RunInTransaction(Action action)
  866. {
  867. try
  868. {
  869. var savePoint = SaveTransactionPoint();
  870. action();
  871. Release(savePoint);
  872. }
  873. catch (Exception)
  874. {
  875. Rollback();
  876. throw;
  877. }
  878. }
  879. /// <summary>
  880. /// Inserts all specified objects.
  881. /// </summary>
  882. /// <param name="objects">
  883. /// An <see cref="IEnumerable"/> of the objects to insert.
  884. /// </param>
  885. /// <returns>
  886. /// The number of rows added to the table.
  887. /// </returns>
  888. public int InsertAll(System.Collections.IEnumerable objects, bool beginTransaction = true)
  889. {
  890. if (beginTransaction)
  891. {
  892. BeginTransaction();
  893. }
  894. var c = 0;
  895. foreach (var r in objects)
  896. {
  897. c += Insert(r);
  898. }
  899. if (beginTransaction)
  900. {
  901. Commit();
  902. }
  903. return c;
  904. }
  905. /// <summary>
  906. /// Inserts the given object and retrieves its
  907. /// auto incremented primary key if it has one.
  908. /// </summary>
  909. /// <param name="obj">
  910. /// The object to insert.
  911. /// </param>
  912. /// <returns>
  913. /// The number of rows added to the table.
  914. /// </returns>
  915. public int Insert(object obj)
  916. {
  917. if (obj == null)
  918. {
  919. return 0;
  920. }
  921. return Insert(obj, "", obj.GetType());
  922. }
  923. public int Insert(object obj, Type objType)
  924. {
  925. return Insert(obj, "", objType);
  926. }
  927. public int Insert(object obj, string extra)
  928. {
  929. if (obj == null)
  930. {
  931. return 0;
  932. }
  933. return Insert(obj, extra, obj.GetType());
  934. }
  935. /// <summary>
  936. /// Inserts the given object and retrieves its
  937. /// auto incremented primary key if it has one.
  938. /// </summary>
  939. /// <param name="obj">
  940. /// The object to insert.
  941. /// </param>
  942. /// <param name="extra">
  943. /// Literal SQL code that gets placed into the command. INSERT {extra} INTO ...
  944. /// </param>
  945. /// <param name="objType">
  946. /// The type of object to insert.
  947. /// </param>
  948. /// <returns>
  949. /// The number of rows added to the table.
  950. /// </returns>
  951. public int Insert(object obj, string extra, Type objType)
  952. {
  953. if (obj == null || objType == null)
  954. {
  955. return 0;
  956. }
  957. var map = GetMapping(objType);
  958. var cols = map.InsertColumns;
  959. var vals = new object[cols.Length];
  960. for (var i = 0; i < vals.Length; i++)
  961. {
  962. vals[i] = cols[i].GetValue(obj);
  963. }
  964. var insertCmd = map.GetInsertCommand(this, extra);
  965. var count = insertCmd.ExecuteNonQuery(vals);
  966. if (map.HasAutoIncPK)
  967. {
  968. var id = SQLite3.LastInsertRowid(Handle);
  969. map.SetAutoIncPK(obj, id);
  970. }
  971. return count;
  972. }
  973. /// <summary>
  974. /// Updates all of the columns of a table using the specified object
  975. /// except for its primary key.
  976. /// The object is required to have a primary key.
  977. /// </summary>
  978. /// <param name="obj">
  979. /// The object to update. It must have a primary key designated using the PrimaryKeyAttribute.
  980. /// </param>
  981. /// <returns>
  982. /// The number of rows updated.
  983. /// </returns>
  984. public int Update(object obj)
  985. {
  986. if (obj == null)
  987. {
  988. return 0;
  989. }
  990. return Update(obj, obj.GetType());
  991. }
  992. public int Update(object obj, Type objType)
  993. {
  994. if (obj == null || objType == null)
  995. {
  996. return 0;
  997. }
  998. var map = GetMapping(objType);
  999. var pk = map.PK;
  1000. if (pk == null)
  1001. {
  1002. throw new NotSupportedException("Cannot update " + map.TableName + ": it has no PK");
  1003. }
  1004. var cols = from p in map.Columns
  1005. where p != pk
  1006. select p;
  1007. var vals = from c in cols
  1008. select c.GetValue(obj);
  1009. var ps = new List<object>(vals);
  1010. ps.Add(pk.GetValue(obj));
  1011. var q = string.Format("update \"{0}\" set {1} where {2} = ? ", map.TableName, string.Join(",", (from c in cols
  1012. select "\"" + c.Name + "\" = ? ").ToArray()), pk.Name);
  1013. return Execute(q, ps.ToArray());
  1014. }
  1015. /// <summary>
  1016. /// Deletes the given object from the database using its primary key.
  1017. /// </summary>
  1018. /// <param name="objectToDelete">
  1019. /// The object to delete. It must have a primary key designated using the PrimaryKeyAttribute.
  1020. /// </param>
  1021. /// <returns>
  1022. /// The number of rows deleted.
  1023. /// </returns>
  1024. public int Delete(object objectToDelete)
  1025. {
  1026. var map = GetMapping(objectToDelete.GetType());
  1027. var pk = map.PK;
  1028. if (pk == null)
  1029. {
  1030. throw new NotSupportedException("Cannot delete " + map.TableName + ": it has no PK");
  1031. }
  1032. var q = string.Format("delete from \"{0}\" where \"{1}\" = ?", map.TableName, pk.Name);
  1033. return Execute(q, pk.GetValue(objectToDelete));
  1034. }
  1035. /// <summary>
  1036. /// Deletes the object with the specified primary key.
  1037. /// </summary>
  1038. /// <param name="primaryKey">
  1039. /// The primary key of the object to delete.
  1040. /// </param>
  1041. /// <returns>
  1042. /// The number of objects deleted.
  1043. /// </returns>
  1044. /// <typeparam name='T'>
  1045. /// The type of object.
  1046. /// </typeparam>
  1047. public int Delete<T>(object primaryKey)
  1048. {
  1049. var map = GetMapping(typeof(T));
  1050. var pk = map.PK;
  1051. if (pk == null)
  1052. {
  1053. throw new NotSupportedException("Cannot delete " + map.TableName + ": it has no PK");
  1054. }
  1055. var q = string.Format("delete from \"{0}\" where \"{1}\" = ?", map.TableName, pk.Name);
  1056. return Execute(q, primaryKey);
  1057. }
  1058. /// <summary>
  1059. /// Deletes all the objects from the specified table.
  1060. /// WARNING WARNING: Let me repeat. It deletes ALL the objects from the
  1061. /// specified table. Do you really want to do that?
  1062. /// </summary>
  1063. /// <returns>
  1064. /// The number of objects deleted.
  1065. /// </returns>
  1066. /// <typeparam name='T'>
  1067. /// The type of objects to delete.
  1068. /// </typeparam>
  1069. public int DeleteAll<T>()
  1070. {
  1071. var map = GetMapping(typeof(T));
  1072. var query = string.Format("delete from \"{0}\"", map.TableName);
  1073. return Execute(query);
  1074. }
  1075. public void Dispose()
  1076. {
  1077. Close();
  1078. }
  1079. public void Close()
  1080. {
  1081. if (_open && Handle != NullHandle)
  1082. {
  1083. try
  1084. {
  1085. if (_mappings != null)
  1086. {
  1087. foreach (var sqlInsertCommand in _mappings.Values)
  1088. {
  1089. sqlInsertCommand.Dispose();
  1090. }
  1091. }
  1092. var r = SQLite3.Close(Handle);
  1093. if (r != SQLite3.Result.OK)
  1094. {
  1095. string msg = SQLite3.GetErrmsg(Handle);
  1096. throw SQLiteException.New(r, msg);
  1097. }
  1098. }
  1099. finally
  1100. {
  1101. Handle = NullHandle;
  1102. _open = false;
  1103. }
  1104. }
  1105. }
  1106. }
  1107. /// <summary>
  1108. /// Represents a parsed connection string.
  1109. /// </summary>
  1110. class SQLiteConnectionString
  1111. {
  1112. public string ConnectionString { get; private set; }
  1113. public string DatabasePath { get; private set; }
  1114. public bool StoreDateTimeAsTicks { get; private set; }
  1115. #if NETFX_CORE
  1116. static readonly string MetroStyleDataPath = Windows.Storage.ApplicationData.Current.LocalFolder.Path;
  1117. #endif
  1118. public SQLiteConnectionString(string databasePath, bool storeDateTimeAsTicks)
  1119. {
  1120. ConnectionString = databasePath;
  1121. StoreDateTimeAsTicks = storeDateTimeAsTicks;
  1122. #if NETFX_CORE
  1123. DatabasePath = System.IO.Path.Combine (MetroStyleDataPath, databasePath);
  1124. #else
  1125. DatabasePath = databasePath;
  1126. #endif
  1127. }
  1128. }
  1129. [AttributeUsage(AttributeTargets.Class)]
  1130. public class TableAttribute : Attribute
  1131. {
  1132. public string Name { get; set; }
  1133. public TableAttribute(string name)
  1134. {
  1135. Name = name;
  1136. }
  1137. }
  1138. [AttributeUsage(AttributeTargets.Property)]
  1139. public class ColumnAttribute : Attribute
  1140. {
  1141. public string Name { get; set; }
  1142. public ColumnAttribute(string name)
  1143. {
  1144. Name = name;
  1145. }
  1146. }
  1147. [AttributeUsage(AttributeTargets.Property)]
  1148. public class PrimaryKeyAttribute : Attribute
  1149. {
  1150. }
  1151. [AttributeUsage(AttributeTargets.Property)]
  1152. public class AutoIncrementAttribute : Attribute
  1153. {
  1154. }
  1155. [AttributeUsage(AttributeTargets.Property)]
  1156. public class IndexedAttribute : Attribute
  1157. {
  1158. public string Name { get; set; }
  1159. public int Order { get; set; }
  1160. public virtual bool Unique { get; set; }
  1161. public IndexedAttribute()
  1162. {
  1163. }
  1164. public IndexedAttribute(string name, int order)
  1165. {
  1166. Name = name;
  1167. Order = order;
  1168. }
  1169. }
  1170. [AttributeUsage(AttributeTargets.Property)]
  1171. public class IgnoreAttribute : Attribute
  1172. {
  1173. }
  1174. [AttributeUsage(AttributeTargets.Property)]
  1175. public class UniqueAttribute : IndexedAttribute
  1176. {
  1177. public override bool Unique
  1178. {
  1179. get { return true; }
  1180. set { /* throw? */ }
  1181. }
  1182. }
  1183. [AttributeUsage(AttributeTargets.Property)]
  1184. public class MaxLengthAttribute : Attribute
  1185. {
  1186. public int Value { get; private set; }
  1187. public MaxLengthAttribute(int length)
  1188. {
  1189. Value = length;
  1190. }
  1191. }
  1192. [AttributeUsage(AttributeTargets.Property)]
  1193. public class CollationAttribute : Attribute
  1194. {
  1195. public string Value { get; private set; }
  1196. public CollationAttribute(string collation)
  1197. {
  1198. Value = collation;
  1199. }
  1200. }
  1201. public class TableMapping
  1202. {
  1203. public Type MappedType { get; private set; }
  1204. public string TableName { get; private set; }
  1205. public Column[] Columns { get; private set; }
  1206. public Column PK { get; private set; }
  1207. public string GetByPrimaryKeySql { get; private set; }
  1208. Column _autoPk = null;
  1209. Column[] _insertColumns = null;
  1210. public TableMapping(Type type)
  1211. {
  1212. MappedType = type;
  1213. #if NETFX_CORE
  1214. var tableAttr = (TableAttribute)System.Reflection.CustomAttributeExtensions
  1215. .GetCustomAttribute(type.GetTypeInfo(), typeof(TableAttribute), true);
  1216. #else
  1217. var tableAttr = (TableAttribute)type.GetCustomAttributes(typeof(TableAttribute), true).FirstOrDefault();
  1218. #endif
  1219. TableName = tableAttr != null ? tableAttr.Name : MappedType.Name;
  1220. #if !NETFX_CORE
  1221. var props = MappedType.GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.SetProperty);
  1222. #else
  1223. var props = from p in MappedType.GetRuntimeProperties()
  1224. where ((p.GetMethod != null && p.GetMethod.IsPublic) || (p.SetMethod != null && p.SetMethod.IsPublic) || (p.GetMethod != null && p.GetMethod.IsStatic) || (p.SetMethod != null && p.SetMethod.IsStatic))
  1225. select p;
  1226. #endif
  1227. var cols = new List<Column>();
  1228. foreach (var p in props)
  1229. {
  1230. #if !NETFX_CORE
  1231. var ignore = p.GetCustomAttributes(typeof(IgnoreAttribute), true).Length > 0;
  1232. #else
  1233. var ignore = p.GetCustomAttributes (typeof(IgnoreAttribute), true).Count() > 0;
  1234. #endif
  1235. if (p.CanWrite && !ignore)
  1236. {
  1237. cols.Add(new Column(p));
  1238. }
  1239. }
  1240. Columns = cols.ToArray();
  1241. foreach (var c in Columns)
  1242. {
  1243. if (c.IsAutoInc && c.IsPK)
  1244. {
  1245. _autoPk = c;
  1246. }
  1247. if (c.IsPK)
  1248. {
  1249. PK = c;
  1250. }
  1251. }
  1252. HasAutoIncPK = _autoPk != null;
  1253. if (PK != null)
  1254. {
  1255. GetByPrimaryKeySql = string.Format("select * from \"{0}\" where \"{1}\" = ?", TableName, PK.Name);
  1256. }
  1257. else
  1258. {
  1259. // People should not be calling Get/Find without a PK
  1260. GetByPrimaryKeySql = string.Format("select * from \"{0}\" limit 1", TableName);
  1261. }
  1262. }
  1263. public bool HasAutoIncPK { get; private set; }
  1264. public void SetAutoIncPK(object obj, long id)
  1265. {
  1266. if (_autoPk != null)
  1267. {
  1268. _autoPk.SetValue(obj, Convert.ChangeType(id, _autoPk.ColumnType, null));
  1269. }
  1270. }
  1271. public Column[] InsertColumns
  1272. {
  1273. get
  1274. {
  1275. if (_insertColumns == null)
  1276. {
  1277. _insertColumns = Columns.Where(c => !c.IsAutoInc).ToArray();
  1278. }
  1279. return _insertColumns;
  1280. }
  1281. }
  1282. public Column FindColumnWithPropertyName(string propertyName)
  1283. {
  1284. var exact = Columns.Where(c => c.PropertyName == propertyName).FirstOrDefault();
  1285. return exact;
  1286. }
  1287. public Column FindColumn(string columnName)
  1288. {
  1289. var exact = Columns.Where(c => c.Name == columnName).FirstOrDefault();
  1290. return exact;
  1291. }
  1292. PreparedSqlLiteInsertCommand _insertCommand;
  1293. string _insertCommandExtra = null;
  1294. public PreparedSqlLiteInsertCommand GetInsertCommand(SQLiteConnection conn, string extra)
  1295. {
  1296. if (_insertCommand == null)
  1297. {
  1298. _insertCommand = CreateInsertCommand(conn, extra);
  1299. _insertCommandExtra = extra;
  1300. }
  1301. else if (_insertCommandExtra != extra)
  1302. {
  1303. _insertCommand.Dispose();
  1304. _insertCommand = CreateInsertCommand(conn, extra);
  1305. _insertCommandExtra = extra;
  1306. }
  1307. return _insertCommand;
  1308. }
  1309. private PreparedSqlLiteInsertCommand CreateInsertCommand(SQLiteConnection conn, string extra)
  1310. {
  1311. var cols = InsertColumns;
  1312. string insertSql;
  1313. if (!cols.Any() && Columns.Count() == 1 && Columns[0].IsAutoInc)
  1314. {
  1315. insertSql = string.Format("insert {1} into \"{0}\" default values", TableName, extra);
  1316. }
  1317. else
  1318. {
  1319. insertSql = string.Format("insert {3} into \"{0}\"({1}) values ({2})", TableName,
  1320. string.Join(",", (from c in cols
  1321. select "\"" + c.Name + "\"").ToArray()),
  1322. string.Join(",", (from c in cols
  1323. select "?").ToArray()), extra);
  1324. }
  1325. var insertCommand = new PreparedSqlLiteInsertCommand(conn);
  1326. insertCommand.CommandText = insertSql;
  1327. return insertCommand;
  1328. }
  1329. protected internal void Dispose()
  1330. {
  1331. if (_insertCommand != null)
  1332. {
  1333. _insertCommand.Dispose();
  1334. _insertCommand = null;
  1335. }
  1336. }
  1337. public class Column
  1338. {
  1339. PropertyInfo _prop;
  1340. public string Name { get; private set; }
  1341. public string PropertyName { get { return _prop.Name; } }
  1342. public Type ColumnType { get; private set; }
  1343. public string Collation { get; private set; }
  1344. public bool IsAutoInc { get; private set; }
  1345. public bool IsPK { get; private set; }
  1346. public IEnumerable<IndexedAttribute> Indices { get; set; }
  1347. public bool IsNullable { get; private set; }
  1348. public int MaxStringLength { get; private set; }
  1349. public Column(PropertyInfo prop)
  1350. {
  1351. var colAttr = (ColumnAttribute)prop.GetCustomAttributes(typeof(ColumnAttribute), true).FirstOrDefault();
  1352. _prop = prop;
  1353. Name = colAttr == null ? prop.Name : colAttr.Name;
  1354. //If this type is Nullable<T> then Nullable.GetUnderlyingType returns the T, otherwise it returns null, so get the the actual type instead
  1355. ColumnType = Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType;
  1356. Collation = Orm.Collation(prop);
  1357. IsAutoInc = Orm.IsAutoInc(prop);
  1358. IsPK = Orm.IsPK(prop);
  1359. Indices = Orm.GetIndices(prop);
  1360. IsNullable = !IsPK;
  1361. MaxStringLength = Orm.MaxStringLength(prop);
  1362. }
  1363. public void SetValue(object obj, object val)
  1364. {
  1365. _prop.SetValue(obj, val, null);
  1366. }
  1367. public object GetValue(object obj)
  1368. {
  1369. return _prop.GetValue(obj, null);
  1370. }
  1371. }
  1372. }
  1373. public static class Orm
  1374. {
  1375. public const int DefaultMaxStringLength = 140;
  1376. public static string SqlDecl(TableMapping.Column p, bool storeDateTimeAsTicks)
  1377. {
  1378. string decl = "\"" + p.Name + "\" " + SqlType(p, storeDateTimeAsTicks) + " ";
  1379. if (p.IsPK)
  1380. {
  1381. decl += "primary key ";
  1382. }
  1383. if (p.IsAutoInc)
  1384. {
  1385. decl += "autoincrement ";
  1386. }
  1387. if (!p.IsNullable)
  1388. {
  1389. decl += "not null ";
  1390. }
  1391. if (!string.IsNullOrEmpty(p.Collation))
  1392. {
  1393. decl += "collate " + p.Collation + " ";
  1394. }
  1395. return decl;
  1396. }
  1397. public static string SqlType(TableMapping.Column p, bool storeDateTimeAsTicks)
  1398. {
  1399. var clrType = p.ColumnType;
  1400. if (clrType == typeof(Boolean) || clrType == typeof(Byte) || clrType == typeof(UInt16) || clrType == typeof(SByte) || clrType == typeof(Int16) || clrType == typeof(Int32))
  1401. {
  1402. return "integer";
  1403. }
  1404. else if (clrType == typeof(UInt32) || clrType == typeof(Int64))
  1405. {
  1406. return "bigint";
  1407. }
  1408. else if (clrType == typeof(Single) || clrType == typeof(Double) || clrType == typeof(Decimal))
  1409. {
  1410. return "float";
  1411. }
  1412. else if (clrType == typeof(String))
  1413. {
  1414. int len = p.MaxStringLength;
  1415. return "varchar(" + len + ")";
  1416. }
  1417. else if (clrType == typeof(DateTime))
  1418. {
  1419. return storeDateTimeAsTicks ? "bigint" : "datetime";
  1420. #if !NETFX_CORE
  1421. }
  1422. else if (clrType.IsEnum)
  1423. {
  1424. #else
  1425. } else if (clrType.GetTypeInfo().IsEnum) {
  1426. #endif
  1427. return "integer";
  1428. }
  1429. else if (clrType == typeof(byte[]))
  1430. {
  1431. return "blob";
  1432. #if SQLITE_SUPPORT_GUID
  1433. } else if (clrType == typeof(Guid)) {
  1434. return "varchar(36)";
  1435. #endif
  1436. }
  1437. else
  1438. {
  1439. #if SQLITE_SUPPORT_SERIALIZATION
  1440. return "text";
  1441. #else
  1442. throw new NotSupportedException("Don't know about " + clrType);
  1443. #endif
  1444. }
  1445. }
  1446. public static bool IsPK(MemberInfo p)
  1447. {
  1448. var attrs = p.GetCustomAttributes(typeof(PrimaryKeyAttribute), true);
  1449. #if !NETFX_CORE
  1450. return attrs.Length > 0;
  1451. #else
  1452. return attrs.Count() > 0;
  1453. #endif
  1454. }
  1455. public static string Collation(MemberInfo p)
  1456. {
  1457. var attrs = p.GetCustomAttributes(typeof(CollationAttribute), true);
  1458. #if !NETFX_CORE
  1459. if (attrs.Length > 0)
  1460. {
  1461. return ((CollationAttribute)attrs[0]).Value;
  1462. #else
  1463. if (attrs.Count() > 0) {
  1464. return ((CollationAttribute)attrs.First()).Value;
  1465. #endif
  1466. }
  1467. else
  1468. {
  1469. return string.Empty;
  1470. }
  1471. }
  1472. public static bool IsAutoInc(MemberInfo p)
  1473. {
  1474. var attrs = p.GetCustomAttributes(typeof(AutoIncrementAttribute), true);
  1475. #if !NETFX_CORE
  1476. return attrs.Length > 0;
  1477. #else
  1478. return attrs.Count() > 0;
  1479. #endif
  1480. }
  1481. public static IEnumerable<IndexedAttribute> GetIndices(MemberInfo p)
  1482. {
  1483. var attrs = p.GetCustomAttributes(typeof(IndexedAttribute), true);
  1484. return attrs.Cast<IndexedAttribute>();
  1485. }
  1486. public static int MaxStringLength(PropertyInfo p)
  1487. {
  1488. var attrs = p.GetCustomAttributes(typeof(MaxLengthAttribute), true);
  1489. #if !NETFX_CORE
  1490. if (attrs.Length > 0)
  1491. {
  1492. return ((MaxLengthAttribute)attrs[0]).Value;
  1493. #else
  1494. if (attrs.Count() > 0) {
  1495. return ((MaxLengthAttribute)attrs.First()).Value;
  1496. #endif
  1497. }
  1498. else
  1499. {
  1500. return DefaultMaxStringLength;
  1501. }
  1502. }
  1503. }
  1504. public class SQLiteCommand
  1505. {
  1506. SQLiteConnection _conn;
  1507. private List<Binding> _bindings;
  1508. public string CommandText { get; set; }
  1509. internal SQLiteCommand(SQLiteConnection conn)
  1510. {
  1511. _conn = conn;
  1512. _bindings = new List<Binding>();
  1513. CommandText = "";
  1514. }
  1515. public int ExecuteNonQuery()
  1516. {
  1517. if (_conn.Trace)
  1518. {
  1519. Debug.WriteLine("Executing: " + this);
  1520. }
  1521. var r = SQLite3.Result.OK;
  1522. var stmt = Prepare();
  1523. r = SQLite3.Step(stmt);
  1524. Finalize(stmt);
  1525. if (r == SQLite3.Result.Done)
  1526. {
  1527. int rowsAffected = SQLite3.Changes(_conn.Handle);
  1528. return rowsAffected;
  1529. }
  1530. else if (r == SQLite3.Result.Row)
  1531. {
  1532. return -1;
  1533. }
  1534. else if (r == SQLite3.Result.Error)
  1535. {
  1536. string msg = SQLite3.GetErrmsg(_conn.Handle);
  1537. throw SQLiteException.New(r, msg);
  1538. }
  1539. else
  1540. {
  1541. throw SQLiteException.New(r, r.ToString());
  1542. }
  1543. }
  1544. public IEnumerable<T> ExecuteDeferredQuery<T>() where T : new()
  1545. {
  1546. return ExecuteDeferredQuery<T>(_conn.GetMapping(typeof(T)));
  1547. }
  1548. public List<T> ExecuteQuery<T>() where T : new()
  1549. {
  1550. return ExecuteDeferredQuery<T>(_conn.GetMapping(typeof(T))).ToList();
  1551. }
  1552. public List<T> ExecuteQuery<T>(TableMapping map)
  1553. {
  1554. return ExecuteDeferredQuery<T>(map).ToList();
  1555. }
  1556. /// <summary>
  1557. /// Invoked ev…

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