├── .gitignore ├── LICENSE ├── README.md └── src ├── PersistentQueue.sln ├── PersistentQueue.suo ├── PersistentQueue ├── FilterQueue.cs ├── PersistentQueue.cs ├── PersistentQueue.csproj ├── PersistentQueue.csproj.user ├── Properties │ └── AssemblyInfo.cs ├── Queue.cs ├── SQLite.cs ├── SQLiteAsync.cs ├── app.config └── packages.config ├── Tests ├── CommonAndBasicTests.cs ├── Properties │ └── AssemblyInfo.cs ├── QueueSpecificTests.cs ├── SQLiteShimResources │ ├── amd64 │ │ └── sqlite3.dll │ └── x86 │ │ └── sqlite3.dll ├── Tests.csproj └── packages.config └── packages ├── SQLiteShim ├── SQLiteShim.dll ├── SQLiteShim.pdb └── SQLiteShimResources │ ├── amd64 │ └── sqlite3.dll │ └── x86 │ └── sqlite3.dll └── repositories.config /.gitignore: -------------------------------------------------------------------------------- 1 | [Oo]bj/ 2 | [Bb]in/ 3 | obj/ 4 | *.suo 5 | *.user 6 | /TestResults 7 | *.vspscc 8 | *.vssscc 9 | !/src/packages/repositories.config 10 | !/src/packages/SQLiteShim 11 | /src/packages 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 2 | Version 2, December 2004 3 | 4 | Copyright (C) 2012 Victor Arias , 2015 ZipRecruiter (https://www.ziprecruiter.com) 5 | 6 | Everyone is permitted to copy and distribute verbatim or modified 7 | copies of this license document, and changing it is allowed as long 8 | as the name is changed. 9 | 10 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 11 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 12 | 13 | 0. You just DO WHAT THE FUCK YOU WANT TO. 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PersistentQueue 2 | A simple .NET in-process persistent queue using sqlite 3 | 4 | This distribution has been updated to run on .Net 4, use Nuget for dependency management 5 | -------------------------------------------------------------------------------- /src/PersistentQueue.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 2012 4 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PersistentQueue", "PersistentQueue\PersistentQueue.csproj", "{0FC1F6B5-D900-49D3-BCC7-9723C3FC2933}" 5 | EndProject 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests", "Tests\Tests.csproj", "{E347F373-C9E1-407B-9732-BAA355ACEA99}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Debug|x86 = Debug|x86 12 | Release|Any CPU = Release|Any CPU 13 | Release|x86 = Release|x86 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {0FC1F6B5-D900-49D3-BCC7-9723C3FC2933}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {0FC1F6B5-D900-49D3-BCC7-9723C3FC2933}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {0FC1F6B5-D900-49D3-BCC7-9723C3FC2933}.Debug|x86.ActiveCfg = Debug|Any CPU 19 | {0FC1F6B5-D900-49D3-BCC7-9723C3FC2933}.Debug|x86.Build.0 = Debug|Any CPU 20 | {0FC1F6B5-D900-49D3-BCC7-9723C3FC2933}.Release|Any CPU.ActiveCfg = Release|Any CPU 21 | {0FC1F6B5-D900-49D3-BCC7-9723C3FC2933}.Release|Any CPU.Build.0 = Release|Any CPU 22 | {0FC1F6B5-D900-49D3-BCC7-9723C3FC2933}.Release|x86.ActiveCfg = Release|Any CPU 23 | {0FC1F6B5-D900-49D3-BCC7-9723C3FC2933}.Release|x86.Build.0 = Release|Any CPU 24 | {E347F373-C9E1-407B-9732-BAA355ACEA99}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 25 | {E347F373-C9E1-407B-9732-BAA355ACEA99}.Debug|Any CPU.Build.0 = Debug|Any CPU 26 | {E347F373-C9E1-407B-9732-BAA355ACEA99}.Debug|x86.ActiveCfg = Debug|Any CPU 27 | {E347F373-C9E1-407B-9732-BAA355ACEA99}.Debug|x86.Build.0 = Debug|Any CPU 28 | {E347F373-C9E1-407B-9732-BAA355ACEA99}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | {E347F373-C9E1-407B-9732-BAA355ACEA99}.Release|Any CPU.Build.0 = Release|Any CPU 30 | {E347F373-C9E1-407B-9732-BAA355ACEA99}.Release|x86.ActiveCfg = Release|Any CPU 31 | {E347F373-C9E1-407B-9732-BAA355ACEA99}.Release|x86.Build.0 = Release|Any CPU 32 | EndGlobalSection 33 | GlobalSection(SolutionProperties) = preSolution 34 | HideSolutionNode = FALSE 35 | EndGlobalSection 36 | EndGlobal 37 | -------------------------------------------------------------------------------- /src/PersistentQueue.suo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorarias/PersistentQueue/e105de9c60638f447807216749e55747bf88bc54/src/PersistentQueue.suo -------------------------------------------------------------------------------- /src/PersistentQueue/FilterQueue.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using SQLite; 6 | 7 | namespace PersistentQueue 8 | { 9 | public class FilterQueue : Queue 10 | { 11 | public class Factory : PersistentQueueFactory { } 12 | 13 | public FilterQueue() : base() { } 14 | 15 | public FilterQueue(string name, bool reset = false) 16 | : base(name, reset) 17 | { 18 | 19 | } 20 | 21 | /// 22 | /// Removes the record from the queue without removing it from the database 23 | /// 24 | public override void Delete(IPersistentQueueItem item) 25 | { 26 | this.Delete(item, false); 27 | } 28 | 29 | /// 30 | /// Removes the record from the queue, optionally removing it from the database. 31 | /// 32 | public virtual void Delete(IPersistentQueueItem item, bool removeFromDB) 33 | { 34 | if (removeFromDB) 35 | { 36 | base.Delete(item); 37 | } 38 | else 39 | { 40 | var fqItem = (FilterQueueItem)item; 41 | fqItem.DeleteTime = DateTime.Now; 42 | store.Update(fqItem); 43 | } 44 | } 45 | 46 | /// 47 | /// Permanently deletes items where a DeleteTime is set. 48 | /// 49 | public virtual void PurgeDeletedItems() 50 | { 51 | lock (store) 52 | { 53 | var map = this.store.GetMapping(typeof(FilterQueueItem)); 54 | var query = string.Format("delete from \"{0}\" where \"DeleteTime\" IS NOT NULL", map.TableName); 55 | this.store.Execute(query); 56 | } 57 | } 58 | 59 | public virtual List AllItems(DateTime? since = null) 60 | { 61 | var query = this.store.Table(); 62 | 63 | if (since != null) 64 | { 65 | query = query.Where(a => a.CreateTime >= (DateTime)since); 66 | } 67 | 68 | return query.ToList(); 69 | } 70 | 71 | public virtual List ActiveItems(DateTime? since = null) 72 | { 73 | var query = this.ActiveItemQuery(); 74 | 75 | if (since != null) 76 | { 77 | query = query.Where(a => a.CreateTime >= (DateTime)since); 78 | } 79 | 80 | return query.ToList(); 81 | } 82 | 83 | public virtual List DeletedItems(DateTime? since = null) 84 | { 85 | var query = this.DeletedItemQuery(); 86 | 87 | if (since != null) 88 | { 89 | query = query.Where(a => a.CreateTime >= (DateTime)since); 90 | } 91 | 92 | return query.ToList(); 93 | } 94 | 95 | #region Filtering Queries 96 | 97 | protected virtual TableQuery ActiveItemQuery() 98 | { 99 | return store.Table() 100 | .Where(a => null == a.DeleteTime); 101 | } 102 | 103 | protected virtual TableQuery DeletedItemQuery() 104 | { 105 | return store.Table() 106 | .Where(a => null != a.DeleteTime); 107 | } 108 | 109 | protected override TableQuery NextItemQuery() 110 | { 111 | return base.NextItemQuery().Where(a => null == a.DeleteTime); 112 | } 113 | 114 | #endregion 115 | } 116 | 117 | [Table("FilterQueueItem")] 118 | public class FilterQueueItem : PersistentQueueItem 119 | { 120 | [Indexed] 121 | public DateTime CreateTime { get; set; } 122 | 123 | public DateTime? DeleteTime { get; set; } 124 | 125 | public FilterQueueItem() 126 | { 127 | var now = DateTime.Now; 128 | this.CreateTime = now; 129 | this.DeleteTime = null; 130 | } 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/PersistentQueue/PersistentQueue.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using SQLite; 6 | using System.IO; 7 | using System.Runtime.Serialization.Formatters.Binary; 8 | 9 | namespace PersistentQueue 10 | { 11 | public interface IPersistentQueueItem 12 | { 13 | long Id { get; } 14 | DateTime InvisibleUntil { get; set; } 15 | byte[] Message { get; set; } 16 | T CastTo(); 17 | String TableName(); 18 | } 19 | 20 | 21 | /// 22 | /// General Persistent Queue interface 23 | /// 24 | public interface IPersistentQueue : IDisposable 25 | { 26 | #region Public Properties 27 | 28 | string Name { get; } 29 | 30 | #endregion 31 | 32 | void Enqueue(object obj); 33 | IPersistentQueueItem Dequeue(bool remove = true, int invisibleTimeout = 30000); 34 | void Invalidate(IPersistentQueueItem item, int invisibleTimeout = 30000); 35 | void Delete(IPersistentQueueItem item); 36 | object Peek(); 37 | T Peek(); 38 | String TableName(); 39 | } 40 | 41 | /// 42 | /// Represents a factory that builds specific PersistentQueue implementations 43 | /// 44 | public interface IPersistentQueueFactory 45 | { 46 | /// 47 | /// Creates or returns a PersistentQueue instance with default parameters for storage. 48 | /// 49 | IPersistentQueue Default(); 50 | 51 | /// 52 | /// Creates or returns a PersistentQueue instance that is stored at given path. 53 | /// 54 | IPersistentQueue Create(string name); 55 | 56 | /// 57 | /// Attempts to create a new PersistentQueue instance with default parameters for storage. 58 | /// If the instance was already loaded, an exception will be thrown. 59 | /// 60 | IPersistentQueue CreateNew(); 61 | 62 | /// 63 | /// Attempts to create a new PersistentQueue instance that is stored at the given path. 64 | /// If the instance was already loaded, an exception will be thrown. 65 | /// 66 | IPersistentQueue CreateNew(string name); 67 | } 68 | 69 | public class QueueStorageMismatchException : Exception 70 | { 71 | public QueueStorageMismatchException(String message) : base(message) { } 72 | 73 | public QueueStorageMismatchException(IPersistentQueue queue, IPersistentQueueItem invalidQueueItem) 74 | : base(BuildMessage(queue, invalidQueueItem)) 75 | { 76 | 77 | } 78 | 79 | private static String BuildMessage(IPersistentQueue queue, IPersistentQueueItem invalidQueueItem) 80 | { 81 | return String.Format("Queue Item of type {0} stores data to a table named \"{1}\". Queue of type {2} stores data to a table names \"{3}\"", 82 | invalidQueueItem.GetType(), 83 | invalidQueueItem.TableName(), 84 | queue.GetType(), 85 | queue.TableName()); 86 | } 87 | } 88 | 89 | /// 90 | /// A class that implements a Persistent SQLite backed queue 91 | /// 92 | public abstract class Queue : IPersistentQueue where QueueItemType : PersistentQueueItem, new() 93 | { 94 | #region Factory 95 | 96 | private static Dictionary queues = new Dictionary(); 97 | 98 | public abstract class PersistentQueueFactory : IPersistentQueueFactory where ConcreteType : Queue, new() 99 | { 100 | 101 | public IPersistentQueue Default() 102 | { 103 | return Create(defaultQueueName); 104 | } 105 | 106 | public IPersistentQueue Create(string name) 107 | { 108 | lock (queues) 109 | { 110 | IPersistentQueue queue; 111 | 112 | if (!queues.TryGetValue(name, out queue)) 113 | { 114 | queue = new ConcreteType(); 115 | ((ConcreteType)queue).Initialize(name); 116 | queues.Add(name, queue); 117 | } 118 | 119 | return queue; 120 | } 121 | } 122 | 123 | public IPersistentQueue CreateNew() 124 | { 125 | return CreateNew(defaultQueueName); 126 | } 127 | 128 | public IPersistentQueue CreateNew(string name) 129 | { 130 | if (name == null) 131 | { 132 | throw new ArgumentNullException("name", 133 | "CreateNew(string name) requires a non null name parameter. Consider calling CreateNew() instead if you do not want to pass in a name"); 134 | } 135 | 136 | lock (queues) 137 | { 138 | if (queues.ContainsKey(name)) 139 | { 140 | throw new InvalidOperationException("there is already a queue with that name"); 141 | } 142 | 143 | ConcreteType queue = new ConcreteType(); 144 | queue.Initialize(name, true); 145 | queues.Add(name, queue); 146 | 147 | return (IPersistentQueue)queue; 148 | } 149 | } 150 | } 151 | 152 | #endregion 153 | 154 | #region Private Properties 155 | 156 | protected const string defaultQueueName = "persistentQueue"; 157 | protected SQLite.SQLiteConnection store; 158 | protected bool disposed = false; 159 | 160 | #endregion 161 | 162 | #region Public Properties 163 | 164 | public string Name { get; protected set; } 165 | 166 | #endregion 167 | 168 | public Queue() 169 | { 170 | 171 | } 172 | 173 | public Queue(string name, bool reset = false) 174 | { 175 | Initialize(name, reset); 176 | } 177 | 178 | ~Queue() 179 | { 180 | if (!disposed) 181 | { 182 | this.Dispose(); 183 | } 184 | } 185 | 186 | protected virtual void Initialize(string name, bool reset = false) 187 | { 188 | if (name == null) 189 | { 190 | throw new ArgumentNullException("name"); 191 | } 192 | 193 | if (reset && File.Exists(defaultQueueName)) 194 | { 195 | File.Delete(defaultQueueName); 196 | } 197 | 198 | Name = name; 199 | store = new SQLiteConnection(name); 200 | store.CreateTable(); 201 | } 202 | 203 | public void Enqueue(object obj) 204 | { 205 | lock (store) 206 | { 207 | store.Insert(obj.ToQueueItem()); 208 | } 209 | } 210 | 211 | public IPersistentQueueItem Dequeue(bool remove = true, int invisibleTimeout = 30000) 212 | { 213 | lock (store) 214 | { 215 | var item = GetNextItem(); 216 | 217 | if (null != item) 218 | { 219 | if (remove) 220 | { 221 | this.Delete(item); 222 | } 223 | else 224 | { 225 | this.Invalidate(item, invisibleTimeout); 226 | } 227 | 228 | return item; 229 | } 230 | else 231 | { 232 | return default(QueueItemType); 233 | } 234 | } 235 | } 236 | 237 | public virtual void Invalidate(IPersistentQueueItem item, int invisibleTimeout = 30000) 238 | { 239 | if (item is QueueItemType) 240 | { 241 | item.InvisibleUntil = DateTime.Now.AddMilliseconds(invisibleTimeout); 242 | store.Update(item); 243 | } 244 | else 245 | { 246 | throw new QueueStorageMismatchException(this, item); 247 | } 248 | } 249 | 250 | public virtual void Delete(IPersistentQueueItem item) 251 | { 252 | if (item is QueueItemType) 253 | { 254 | store.Delete(item); 255 | } 256 | else 257 | { 258 | throw new QueueStorageMismatchException(this, item); 259 | } 260 | } 261 | 262 | public object Peek() 263 | { 264 | lock (store) 265 | { 266 | var item = GetNextItem(); 267 | 268 | return null == item ? null : item.ToObject(); 269 | } 270 | } 271 | 272 | public T Peek() 273 | { 274 | return (T)Peek(); 275 | } 276 | 277 | public void Dispose() 278 | { 279 | if (!disposed) { 280 | lock (queues) 281 | { 282 | disposed = true; 283 | 284 | queues.Remove(this.Name); 285 | store.Dispose(); 286 | 287 | GC.SuppressFinalize(this); 288 | } 289 | } 290 | } 291 | 292 | protected QueueItemType GetNextItem() 293 | { 294 | return this.NextItemQuery().FirstOrDefault(); 295 | } 296 | 297 | protected virtual TableQuery NextItemQuery() 298 | { 299 | return store.Table() 300 | .Where(a => DateTime.Now > a.InvisibleUntil) 301 | .OrderBy(a => a.Id); 302 | } 303 | 304 | public String TableName() 305 | { 306 | String name = null; 307 | 308 | System.Attribute[] attrs = System.Attribute.GetCustomAttributes(typeof(QueueItemType)); 309 | 310 | foreach (System.Attribute attr in attrs) 311 | { 312 | if (attr is SQLite.TableAttribute) 313 | { 314 | var a = (SQLite.TableAttribute)attr; 315 | name = a.Name; 316 | break; 317 | } 318 | } 319 | 320 | return name; 321 | } 322 | } 323 | 324 | [Table("PersistentQueueItem")] 325 | public abstract class PersistentQueueItem : IPersistentQueueItem 326 | { 327 | [PrimaryKey] 328 | [AutoIncrement] 329 | public long Id { get; protected set; } 330 | 331 | [Indexed] 332 | public DateTime InvisibleUntil { get; set; } 333 | 334 | public byte[] Message { get; set; } 335 | 336 | public PersistentQueueItem() { } 337 | 338 | public T CastTo() 339 | { 340 | return (T)this.ToObject(); 341 | } 342 | 343 | public String TableName() 344 | { 345 | String name = null; 346 | 347 | System.Attribute[] attrs = System.Attribute.GetCustomAttributes(this.GetType()); 348 | 349 | foreach (System.Attribute attr in attrs) 350 | { 351 | if (attr is SQLite.TableAttribute) 352 | { 353 | var a = (SQLite.TableAttribute)attr; 354 | name = a.Name; 355 | break; 356 | } 357 | } 358 | 359 | return name; 360 | } 361 | } 362 | 363 | public static class Extensions 364 | { 365 | public static QueueItemType ToQueueItem(this object obj) where QueueItemType : PersistentQueueItem, new() 366 | { 367 | using (var stream = new MemoryStream()) 368 | { 369 | new BinaryFormatter().Serialize(stream, obj); 370 | 371 | return new QueueItemType { Message = stream.ToArray() }; 372 | } 373 | } 374 | 375 | public static object ToObject(this QueueItemType item) where QueueItemType : PersistentQueueItem 376 | { 377 | using (var stream = new MemoryStream(item.Message)) 378 | { 379 | return new BinaryFormatter().Deserialize(stream); 380 | } 381 | } 382 | } 383 | } 384 | -------------------------------------------------------------------------------- /src/PersistentQueue/PersistentQueue.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Debug 5 | AnyCPU 6 | 8.0.30703 7 | 2.0 8 | {0FC1F6B5-D900-49D3-BCC7-9723C3FC2933} 9 | Library 10 | Properties 11 | PersistentQueue 12 | PersistentQueue 13 | v4.0 14 | 512 15 | 16 | 17 | 18 | true 19 | full 20 | false 21 | bin\Debug\ 22 | TRACE;DEBUG 23 | prompt 24 | 4 25 | 26 | 27 | pdbonly 28 | true 29 | bin\Release\ 30 | TRACE 31 | prompt 32 | 4 33 | 34 | 35 | true 36 | bin\x86\Debug\ 37 | TRACE;DEBUG;USE_CSHARP_SQLITE 38 | full 39 | x86 40 | bin\Debug\PersistentQueue.dll.CodeAnalysisLog.xml 41 | true 42 | GlobalSuppressions.cs 43 | prompt 44 | MinimumRecommendedRules.ruleset 45 | ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets 46 | ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules 47 | false 48 | 49 | 50 | bin\x86\Release\ 51 | TRACE;USE_CSHARP_SQLITE 52 | true 53 | pdbonly 54 | x86 55 | bin\Release\PersistentQueue.dll.CodeAnalysisLog.xml 56 | true 57 | GlobalSuppressions.cs 58 | prompt 59 | MinimumRecommendedRules.ruleset 60 | ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets 61 | ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 92 | -------------------------------------------------------------------------------- /src/PersistentQueue/PersistentQueue.csproj.user: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | ProjectFiles 5 | 6 | -------------------------------------------------------------------------------- /src/PersistentQueue/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("PersistentQueue")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("PersistentQueue")] 13 | [assembly: AssemblyCopyright("Copyright © 2012,2015")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("38fd0891-0c84-4da7-aac0-1c815c5def74")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("2.0.0.*")] 36 | [assembly: AssemblyFileVersion("1.2.0.0")] 37 | -------------------------------------------------------------------------------- /src/PersistentQueue/Queue.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using SQLite; 6 | 7 | namespace PersistentQueue 8 | { 9 | public class Queue : Queue 10 | { 11 | public class Factory : PersistentQueueFactory { } 12 | 13 | public Queue() : base() { } 14 | 15 | public Queue(string name, bool reset = false) 16 | : base(name, reset) 17 | { 18 | 19 | } 20 | } 21 | 22 | [Table("QueueItem")] 23 | public class QueueItem : PersistentQueueItem 24 | { 25 | public QueueItem() 26 | { 27 | Id = DateTime.Now.Ticks; 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/PersistentQueue/SQLite.cs: -------------------------------------------------------------------------------- 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 | 23 | #if WINDOWS_PHONE && !USE_WP8_NATIVE_SQLITE 24 | #define USE_CSHARP_SQLITE 25 | #endif 26 | 27 | using System; 28 | using System.Diagnostics; 29 | using System.Runtime.InteropServices; 30 | using System.Collections.Generic; 31 | using System.Reflection; 32 | using System.Linq; 33 | using System.Linq.Expressions; 34 | using System.Threading; 35 | 36 | #if USE_CSHARP_SQLITE 37 | using Sqlite3 = Community.CsharpSqlite.Sqlite3; 38 | using Sqlite3DatabaseHandle = Community.CsharpSqlite.Sqlite3.sqlite3; 39 | using Sqlite3Statement = Community.CsharpSqlite.Sqlite3.Vdbe; 40 | #elif USE_WP8_NATIVE_SQLITE 41 | using Sqlite3 = Sqlite.Sqlite3; 42 | using Sqlite3DatabaseHandle = Sqlite.Database; 43 | using Sqlite3Statement = Sqlite.Statement; 44 | #else 45 | using Sqlite3DatabaseHandle = System.IntPtr; 46 | using Sqlite3Statement = System.IntPtr; 47 | #endif 48 | 49 | namespace SQLite 50 | { 51 | public class SQLiteException : Exception 52 | { 53 | public SQLite3.Result Result { get; private set; } 54 | 55 | protected SQLiteException (SQLite3.Result r,string message) : base(message) 56 | { 57 | Result = r; 58 | } 59 | 60 | public static SQLiteException New (SQLite3.Result r, string message) 61 | { 62 | return new SQLiteException (r, message); 63 | } 64 | } 65 | 66 | public class NotNullConstraintViolationException : SQLiteException 67 | { 68 | public IEnumerable Columns { get; protected set; } 69 | 70 | protected NotNullConstraintViolationException (SQLite3.Result r, string message) 71 | : this (r, message, null, null) 72 | { 73 | 74 | } 75 | 76 | protected NotNullConstraintViolationException (SQLite3.Result r, string message, TableMapping mapping, object obj) 77 | : base (r, message) 78 | { 79 | if (mapping != null && obj != null) { 80 | this.Columns = from c in mapping.Columns 81 | where c.IsNullable == false && c.GetValue (obj) == null 82 | select c; 83 | } 84 | } 85 | 86 | public static new NotNullConstraintViolationException New (SQLite3.Result r, string message) 87 | { 88 | return new NotNullConstraintViolationException (r, message); 89 | } 90 | 91 | public static NotNullConstraintViolationException New (SQLite3.Result r, string message, TableMapping mapping, object obj) 92 | { 93 | return new NotNullConstraintViolationException (r, message, mapping, obj); 94 | } 95 | 96 | public static NotNullConstraintViolationException New (SQLiteException exception, TableMapping mapping, object obj) 97 | { 98 | return new NotNullConstraintViolationException (exception.Result, exception.Message, mapping, obj); 99 | } 100 | } 101 | 102 | [Flags] 103 | public enum SQLiteOpenFlags { 104 | ReadOnly = 1, ReadWrite = 2, Create = 4, 105 | NoMutex = 0x8000, FullMutex = 0x10000, 106 | SharedCache = 0x20000, PrivateCache = 0x40000, 107 | ProtectionComplete = 0x00100000, 108 | ProtectionCompleteUnlessOpen = 0x00200000, 109 | ProtectionCompleteUntilFirstUserAuthentication = 0x00300000, 110 | ProtectionNone = 0x00400000 111 | } 112 | 113 | [Flags] 114 | public enum CreateFlags 115 | { 116 | None = 0, 117 | ImplicitPK = 1, // create a primary key for field called 'Id' (Orm.ImplicitPkName) 118 | ImplicitIndex = 2, // create an index for fields ending in 'Id' (Orm.ImplicitIndexSuffix) 119 | AllImplicit = 3, // do both above 120 | 121 | AutoIncPK = 4 // force PK field to be auto inc 122 | } 123 | 124 | /// 125 | /// Represents an open connection to a SQLite database. 126 | /// 127 | public partial class SQLiteConnection : IDisposable 128 | { 129 | private bool _open; 130 | private TimeSpan _busyTimeout; 131 | private Dictionary _mappings = null; 132 | private Dictionary _tables = null; 133 | private System.Diagnostics.Stopwatch _sw; 134 | private long _elapsedMilliseconds = 0; 135 | 136 | private int _transactionDepth = 0; 137 | private Random _rand = new Random (); 138 | 139 | public Sqlite3DatabaseHandle Handle { get; private set; } 140 | internal static readonly Sqlite3DatabaseHandle NullHandle = default(Sqlite3DatabaseHandle); 141 | 142 | public string DatabasePath { get; private set; } 143 | 144 | public bool TimeExecution { get; set; } 145 | 146 | public bool Trace { get; set; } 147 | 148 | public bool StoreDateTimeAsTicks { get; private set; } 149 | 150 | /// 151 | /// Constructs a new SQLiteConnection and opens a SQLite database specified by databasePath. 152 | /// 153 | /// 154 | /// Specifies the path to the database file. 155 | /// 156 | /// 157 | /// Specifies whether to store DateTime properties as ticks (true) or strings (false). You 158 | /// absolutely do want to store them as Ticks in all new projects. The default of false is 159 | /// only here for backwards compatibility. There is a *significant* speed advantage, with no 160 | /// down sides, when setting storeDateTimeAsTicks = true. 161 | /// 162 | public SQLiteConnection (string databasePath, bool storeDateTimeAsTicks = false) 163 | : this (databasePath, SQLiteOpenFlags.ReadWrite | SQLiteOpenFlags.Create, storeDateTimeAsTicks) 164 | { 165 | } 166 | 167 | /// 168 | /// Constructs a new SQLiteConnection and opens a SQLite database specified by databasePath. 169 | /// 170 | /// 171 | /// Specifies the path to the database file. 172 | /// 173 | /// 174 | /// Specifies whether to store DateTime properties as ticks (true) or strings (false). You 175 | /// absolutely do want to store them as Ticks in all new projects. The default of false is 176 | /// only here for backwards compatibility. There is a *significant* speed advantage, with no 177 | /// down sides, when setting storeDateTimeAsTicks = true. 178 | /// 179 | public SQLiteConnection (string databasePath, SQLiteOpenFlags openFlags, bool storeDateTimeAsTicks = false) 180 | { 181 | if (string.IsNullOrEmpty (databasePath)) 182 | throw new ArgumentException ("Must be specified", "databasePath"); 183 | 184 | DatabasePath = databasePath; 185 | 186 | #if NETFX_CORE 187 | SQLite3.SetDirectory(/*temp directory type*/2, Windows.Storage.ApplicationData.Current.TemporaryFolder.Path); 188 | #endif 189 | 190 | Sqlite3DatabaseHandle handle; 191 | 192 | #if SILVERLIGHT || USE_CSHARP_SQLITE 193 | var r = SQLite3.Open (databasePath, out handle, (int)openFlags, IntPtr.Zero); 194 | #else 195 | // open using the byte[] 196 | // in the case where the path may include Unicode 197 | // force open to using UTF-8 using sqlite3_open_v2 198 | var databasePathAsBytes = GetNullTerminatedUtf8 (DatabasePath); 199 | var r = SQLite3.Open (databasePathAsBytes, out handle, (int) openFlags, IntPtr.Zero); 200 | #endif 201 | 202 | Handle = handle; 203 | if (r != SQLite3.Result.OK) { 204 | throw SQLiteException.New (r, String.Format ("Could not open database file: {0} ({1})", DatabasePath, r)); 205 | } 206 | _open = true; 207 | 208 | StoreDateTimeAsTicks = storeDateTimeAsTicks; 209 | 210 | BusyTimeout = TimeSpan.FromSeconds (0.1); 211 | } 212 | 213 | static SQLiteConnection () 214 | { 215 | if (_preserveDuringLinkMagic) { 216 | var ti = new ColumnInfo (); 217 | ti.Name = "magic"; 218 | } 219 | } 220 | 221 | public void EnableLoadExtension(int onoff) 222 | { 223 | SQLite3.Result r = SQLite3.EnableLoadExtension(Handle, onoff); 224 | if (r != SQLite3.Result.OK) { 225 | string msg = SQLite3.GetErrmsg (Handle); 226 | throw SQLiteException.New (r, msg); 227 | } 228 | } 229 | 230 | static byte[] GetNullTerminatedUtf8 (string s) 231 | { 232 | var utf8Length = System.Text.Encoding.UTF8.GetByteCount (s); 233 | var bytes = new byte [utf8Length + 1]; 234 | utf8Length = System.Text.Encoding.UTF8.GetBytes(s, 0, s.Length, bytes, 0); 235 | return bytes; 236 | } 237 | 238 | /// 239 | /// Used to list some code that we want the MonoTouch linker 240 | /// to see, but that we never want to actually execute. 241 | /// 242 | static bool _preserveDuringLinkMagic; 243 | 244 | /// 245 | /// Sets a busy handler to sleep the specified amount of time when a table is locked. 246 | /// The handler will sleep multiple times until a total time of has accumulated. 247 | /// 248 | public TimeSpan BusyTimeout { 249 | get { return _busyTimeout; } 250 | set { 251 | _busyTimeout = value; 252 | if (Handle != NullHandle) { 253 | SQLite3.BusyTimeout (Handle, (int)_busyTimeout.TotalMilliseconds); 254 | } 255 | } 256 | } 257 | 258 | /// 259 | /// Returns the mappings from types to tables that the connection 260 | /// currently understands. 261 | /// 262 | public IEnumerable TableMappings { 263 | get { 264 | return _tables != null ? _tables.Values : Enumerable.Empty (); 265 | } 266 | } 267 | 268 | /// 269 | /// Retrieves the mapping that is automatically generated for the given type. 270 | /// 271 | /// 272 | /// The type whose mapping to the database is returned. 273 | /// 274 | /// 275 | /// Optional flags allowing implicit PK and indexes based on naming conventions 276 | /// 277 | /// 278 | /// The mapping represents the schema of the columns of the database and contains 279 | /// methods to set and get properties of objects. 280 | /// 281 | public TableMapping GetMapping(Type type, CreateFlags createFlags = CreateFlags.None) 282 | { 283 | if (_mappings == null) { 284 | _mappings = new Dictionary (); 285 | } 286 | TableMapping map; 287 | if (!_mappings.TryGetValue (type.FullName, out map)) { 288 | map = new TableMapping (type, createFlags); 289 | _mappings [type.FullName] = map; 290 | } 291 | return map; 292 | } 293 | 294 | /// 295 | /// Retrieves the mapping that is automatically generated for the given type. 296 | /// 297 | /// 298 | /// The mapping represents the schema of the columns of the database and contains 299 | /// methods to set and get properties of objects. 300 | /// 301 | public TableMapping GetMapping () 302 | { 303 | return GetMapping (typeof (T)); 304 | } 305 | 306 | private struct IndexedColumn 307 | { 308 | public int Order; 309 | public string ColumnName; 310 | } 311 | 312 | private struct IndexInfo 313 | { 314 | public string IndexName; 315 | public string TableName; 316 | public bool Unique; 317 | public List Columns; 318 | } 319 | 320 | /// 321 | /// Executes a "drop table" on the database. This is non-recoverable. 322 | /// 323 | public int DropTable() 324 | { 325 | var map = GetMapping (typeof (T)); 326 | 327 | var query = string.Format("drop table if exists \"{0}\"", map.TableName); 328 | 329 | return Execute (query); 330 | } 331 | 332 | /// 333 | /// Executes a "create table if not exists" on the database. It also 334 | /// creates any specified indexes on the columns of the table. It uses 335 | /// a schema automatically generated from the specified type. You can 336 | /// later access this schema by calling GetMapping. 337 | /// 338 | /// 339 | /// The number of entries added to the database schema. 340 | /// 341 | public int CreateTable(CreateFlags createFlags = CreateFlags.None) 342 | { 343 | return CreateTable(typeof (T), createFlags); 344 | } 345 | 346 | /// 347 | /// Executes a "create table if not exists" on the database. It also 348 | /// creates any specified indexes on the columns of the table. It uses 349 | /// a schema automatically generated from the specified type. You can 350 | /// later access this schema by calling GetMapping. 351 | /// 352 | /// Type to reflect to a database table. 353 | /// Optional flags allowing implicit PK and indexes based on naming conventions. 354 | /// 355 | /// The number of entries added to the database schema. 356 | /// 357 | public int CreateTable(Type ty, CreateFlags createFlags = CreateFlags.None) 358 | { 359 | if (_tables == null) { 360 | _tables = new Dictionary (); 361 | } 362 | TableMapping map; 363 | if (!_tables.TryGetValue (ty.FullName, out map)) { 364 | map = GetMapping (ty, createFlags); 365 | _tables.Add (ty.FullName, map); 366 | } 367 | var query = "create table if not exists \"" + map.TableName + "\"(\n"; 368 | 369 | var decls = map.Columns.Select (p => Orm.SqlDecl (p, StoreDateTimeAsTicks)); 370 | var decl = string.Join (",\n", decls.ToArray ()); 371 | query += decl; 372 | query += ")"; 373 | 374 | var count = Execute (query); 375 | 376 | if (count == 0) { //Possible bug: This always seems to return 0? 377 | // Table already exists, migrate it 378 | MigrateTable (map); 379 | } 380 | 381 | var indexes = new Dictionary (); 382 | foreach (var c in map.Columns) { 383 | foreach (var i in c.Indices) { 384 | var iname = i.Name ?? map.TableName + "_" + c.Name; 385 | IndexInfo iinfo; 386 | if (!indexes.TryGetValue (iname, out iinfo)) { 387 | iinfo = new IndexInfo { 388 | IndexName = iname, 389 | TableName = map.TableName, 390 | Unique = i.Unique, 391 | Columns = new List () 392 | }; 393 | indexes.Add (iname, iinfo); 394 | } 395 | 396 | if (i.Unique != iinfo.Unique) 397 | throw new Exception ("All the columns in an index must have the same value for their Unique property"); 398 | 399 | iinfo.Columns.Add (new IndexedColumn { 400 | Order = i.Order, 401 | ColumnName = c.Name 402 | }); 403 | } 404 | } 405 | 406 | foreach (var indexName in indexes.Keys) { 407 | var index = indexes[indexName]; 408 | var columns = index.Columns.OrderBy(i => i.Order).Select(i => i.ColumnName).ToArray(); 409 | count += CreateIndex(indexName, index.TableName, columns, index.Unique); 410 | } 411 | 412 | return count; 413 | } 414 | 415 | /// 416 | /// Creates an index for the specified table and columns. 417 | /// 418 | /// Name of the index to create 419 | /// Name of the database table 420 | /// An array of column names to index 421 | /// Whether the index should be unique 422 | public int CreateIndex(string indexName, string tableName, string[] columnNames, bool unique = false) 423 | { 424 | const string sqlFormat = "create {2} index if not exists \"{3}\" on \"{0}\"(\"{1}\")"; 425 | var sql = String.Format(sqlFormat, tableName, string.Join ("\", \"", columnNames), unique ? "unique" : "", indexName); 426 | return Execute(sql); 427 | } 428 | 429 | /// 430 | /// Creates an index for the specified table and column. 431 | /// 432 | /// Name of the index to create 433 | /// Name of the database table 434 | /// Name of the column to index 435 | /// Whether the index should be unique 436 | public int CreateIndex(string indexName, string tableName, string columnName, bool unique = false) 437 | { 438 | return CreateIndex(indexName, tableName, new string[] { columnName }, unique); 439 | } 440 | 441 | /// 442 | /// Creates an index for the specified table and column. 443 | /// 444 | /// Name of the database table 445 | /// Name of the column to index 446 | /// Whether the index should be unique 447 | public int CreateIndex(string tableName, string columnName, bool unique = false) 448 | { 449 | return CreateIndex(tableName + "_" + columnName, tableName, columnName, unique); 450 | } 451 | 452 | /// 453 | /// Creates an index for the specified table and columns. 454 | /// 455 | /// Name of the database table 456 | /// An array of column names to index 457 | /// Whether the index should be unique 458 | public int CreateIndex(string tableName, string[] columnNames, bool unique = false) 459 | { 460 | return CreateIndex(tableName + "_" + string.Join ("_", columnNames), tableName, columnNames, unique); 461 | } 462 | 463 | /// 464 | /// Creates an index for the specified object property. 465 | /// e.g. CreateIndex(c => c.Name); 466 | /// 467 | /// Type to reflect to a database table. 468 | /// Property to index 469 | /// Whether the index should be unique 470 | public void CreateIndex(Expression> property, bool unique = false) 471 | { 472 | MemberExpression mx; 473 | if (property.Body.NodeType == ExpressionType.Convert) 474 | { 475 | mx = ((UnaryExpression)property.Body).Operand as MemberExpression; 476 | } 477 | else 478 | { 479 | mx= (property.Body as MemberExpression); 480 | } 481 | var propertyInfo = mx.Member as PropertyInfo; 482 | if (propertyInfo == null) 483 | { 484 | throw new ArgumentException("The lambda expression 'property' should point to a valid Property"); 485 | } 486 | 487 | var propName = propertyInfo.Name; 488 | 489 | var map = GetMapping(); 490 | var colName = map.FindColumnWithPropertyName(propName).Name; 491 | 492 | CreateIndex(map.TableName, colName, unique); 493 | } 494 | 495 | public class ColumnInfo 496 | { 497 | // public int cid { get; set; } 498 | 499 | [Column ("name")] 500 | public string Name { get; set; } 501 | 502 | // [Column ("type")] 503 | // public string ColumnType { get; set; } 504 | 505 | public int notnull { get; set; } 506 | 507 | // public string dflt_value { get; set; } 508 | 509 | // public int pk { get; set; } 510 | 511 | public override string ToString () 512 | { 513 | return Name; 514 | } 515 | } 516 | 517 | public List GetTableInfo (string tableName) 518 | { 519 | var query = "pragma table_info(\"" + tableName + "\")"; 520 | return Query (query); 521 | } 522 | 523 | void MigrateTable (TableMapping map) 524 | { 525 | var existingCols = GetTableInfo (map.TableName); 526 | 527 | var toBeAdded = new List (); 528 | 529 | foreach (var p in map.Columns) { 530 | var found = false; 531 | foreach (var c in existingCols) { 532 | found = (string.Compare (p.Name, c.Name, StringComparison.OrdinalIgnoreCase) == 0); 533 | if (found) 534 | break; 535 | } 536 | if (!found) { 537 | toBeAdded.Add (p); 538 | } 539 | } 540 | 541 | foreach (var p in toBeAdded) { 542 | var addCol = "alter table \"" + map.TableName + "\" add column " + Orm.SqlDecl (p, StoreDateTimeAsTicks); 543 | Execute (addCol); 544 | } 545 | } 546 | 547 | /// 548 | /// Creates a new SQLiteCommand. Can be overridden to provide a sub-class. 549 | /// 550 | /// 551 | protected virtual SQLiteCommand NewCommand () 552 | { 553 | return new SQLiteCommand (this); 554 | } 555 | 556 | /// 557 | /// Creates a new SQLiteCommand given the command text with arguments. Place a '?' 558 | /// in the command text for each of the arguments. 559 | /// 560 | /// 561 | /// The fully escaped SQL. 562 | /// 563 | /// 564 | /// Arguments to substitute for the occurences of '?' in the command text. 565 | /// 566 | /// 567 | /// A 568 | /// 569 | public SQLiteCommand CreateCommand (string cmdText, params object[] ps) 570 | { 571 | if (!_open) 572 | throw SQLiteException.New (SQLite3.Result.Error, "Cannot create commands from unopened database"); 573 | 574 | var cmd = NewCommand (); 575 | cmd.CommandText = cmdText; 576 | foreach (var o in ps) { 577 | cmd.Bind (o); 578 | } 579 | return cmd; 580 | } 581 | 582 | /// 583 | /// Creates a SQLiteCommand given the command text (SQL) with arguments. Place a '?' 584 | /// in the command text for each of the arguments and then executes that command. 585 | /// Use this method instead of Query when you don't expect rows back. Such cases include 586 | /// INSERTs, UPDATEs, and DELETEs. 587 | /// You can set the Trace or TimeExecution properties of the connection 588 | /// to profile execution. 589 | /// 590 | /// 591 | /// The fully escaped SQL. 592 | /// 593 | /// 594 | /// Arguments to substitute for the occurences of '?' in the query. 595 | /// 596 | /// 597 | /// The number of rows modified in the database as a result of this execution. 598 | /// 599 | public int Execute (string query, params object[] args) 600 | { 601 | var cmd = CreateCommand (query, args); 602 | 603 | if (TimeExecution) { 604 | if (_sw == null) { 605 | _sw = new Stopwatch (); 606 | } 607 | _sw.Reset (); 608 | _sw.Start (); 609 | } 610 | 611 | var r = cmd.ExecuteNonQuery (); 612 | 613 | if (TimeExecution) { 614 | _sw.Stop (); 615 | _elapsedMilliseconds += _sw.ElapsedMilliseconds; 616 | Debug.WriteLine (string.Format ("Finished in {0} ms ({1:0.0} s total)", _sw.ElapsedMilliseconds, _elapsedMilliseconds / 1000.0)); 617 | } 618 | 619 | return r; 620 | } 621 | 622 | public T ExecuteScalar (string query, params object[] args) 623 | { 624 | var cmd = CreateCommand (query, args); 625 | 626 | if (TimeExecution) { 627 | if (_sw == null) { 628 | _sw = new Stopwatch (); 629 | } 630 | _sw.Reset (); 631 | _sw.Start (); 632 | } 633 | 634 | var r = cmd.ExecuteScalar (); 635 | 636 | if (TimeExecution) { 637 | _sw.Stop (); 638 | _elapsedMilliseconds += _sw.ElapsedMilliseconds; 639 | Debug.WriteLine (string.Format ("Finished in {0} ms ({1:0.0} s total)", _sw.ElapsedMilliseconds, _elapsedMilliseconds / 1000.0)); 640 | } 641 | 642 | return r; 643 | } 644 | 645 | /// 646 | /// Creates a SQLiteCommand given the command text (SQL) with arguments. Place a '?' 647 | /// in the command text for each of the arguments and then executes that command. 648 | /// It returns each row of the result using the mapping automatically generated for 649 | /// the given type. 650 | /// 651 | /// 652 | /// The fully escaped SQL. 653 | /// 654 | /// 655 | /// Arguments to substitute for the occurences of '?' in the query. 656 | /// 657 | /// 658 | /// An enumerable with one result for each row returned by the query. 659 | /// 660 | public List Query (string query, params object[] args) where T : new() 661 | { 662 | var cmd = CreateCommand (query, args); 663 | return cmd.ExecuteQuery (); 664 | } 665 | 666 | /// 667 | /// Creates a SQLiteCommand given the command text (SQL) with arguments. Place a '?' 668 | /// in the command text for each of the arguments and then executes that command. 669 | /// It returns each row of the result using the mapping automatically generated for 670 | /// the given type. 671 | /// 672 | /// 673 | /// The fully escaped SQL. 674 | /// 675 | /// 676 | /// Arguments to substitute for the occurences of '?' in the query. 677 | /// 678 | /// 679 | /// An enumerable with one result for each row returned by the query. 680 | /// The enumerator will call sqlite3_step on each call to MoveNext, so the database 681 | /// connection must remain open for the lifetime of the enumerator. 682 | /// 683 | public IEnumerable DeferredQuery(string query, params object[] args) where T : new() 684 | { 685 | var cmd = CreateCommand(query, args); 686 | return cmd.ExecuteDeferredQuery(); 687 | } 688 | 689 | /// 690 | /// Creates a SQLiteCommand given the command text (SQL) with arguments. Place a '?' 691 | /// in the command text for each of the arguments and then executes that command. 692 | /// It returns each row of the result using the specified mapping. This function is 693 | /// only used by libraries in order to query the database via introspection. It is 694 | /// normally not used. 695 | /// 696 | /// 697 | /// A to use to convert the resulting rows 698 | /// into objects. 699 | /// 700 | /// 701 | /// The fully escaped SQL. 702 | /// 703 | /// 704 | /// Arguments to substitute for the occurences of '?' in the query. 705 | /// 706 | /// 707 | /// An enumerable with one result for each row returned by the query. 708 | /// 709 | public List Query (TableMapping map, string query, params object[] args) 710 | { 711 | var cmd = CreateCommand (query, args); 712 | return cmd.ExecuteQuery (map); 713 | } 714 | 715 | /// 716 | /// Creates a SQLiteCommand given the command text (SQL) with arguments. Place a '?' 717 | /// in the command text for each of the arguments and then executes that command. 718 | /// It returns each row of the result using the specified mapping. This function is 719 | /// only used by libraries in order to query the database via introspection. It is 720 | /// normally not used. 721 | /// 722 | /// 723 | /// A to use to convert the resulting rows 724 | /// into objects. 725 | /// 726 | /// 727 | /// The fully escaped SQL. 728 | /// 729 | /// 730 | /// Arguments to substitute for the occurences of '?' in the query. 731 | /// 732 | /// 733 | /// An enumerable with one result for each row returned by the query. 734 | /// The enumerator will call sqlite3_step on each call to MoveNext, so the database 735 | /// connection must remain open for the lifetime of the enumerator. 736 | /// 737 | public IEnumerable DeferredQuery(TableMapping map, string query, params object[] args) 738 | { 739 | var cmd = CreateCommand(query, args); 740 | return cmd.ExecuteDeferredQuery(map); 741 | } 742 | 743 | /// 744 | /// Returns a queryable interface to the table represented by the given type. 745 | /// 746 | /// 747 | /// A queryable object that is able to translate Where, OrderBy, and Take 748 | /// queries into native SQL. 749 | /// 750 | public TableQuery Table () where T : new() 751 | { 752 | return new TableQuery (this); 753 | } 754 | 755 | /// 756 | /// Attempts to retrieve an object with the given primary key from the table 757 | /// associated with the specified type. Use of this method requires that 758 | /// the given type have a designated PrimaryKey (using the PrimaryKeyAttribute). 759 | /// 760 | /// 761 | /// The primary key. 762 | /// 763 | /// 764 | /// The object with the given primary key. Throws a not found exception 765 | /// if the object is not found. 766 | /// 767 | public T Get (object pk) where T : new() 768 | { 769 | var map = GetMapping (typeof(T)); 770 | return Query (map.GetByPrimaryKeySql, pk).First (); 771 | } 772 | 773 | /// 774 | /// Attempts to retrieve the first object that matches the predicate from the table 775 | /// associated with the specified type. 776 | /// 777 | /// 778 | /// A predicate for which object to find. 779 | /// 780 | /// 781 | /// The object that matches the given predicate. Throws a not found exception 782 | /// if the object is not found. 783 | /// 784 | public T Get (Expression> predicate) where T : new() 785 | { 786 | return Table ().Where (predicate).First (); 787 | } 788 | 789 | /// 790 | /// Attempts to retrieve an object with the given primary key from the table 791 | /// associated with the specified type. Use of this method requires that 792 | /// the given type have a designated PrimaryKey (using the PrimaryKeyAttribute). 793 | /// 794 | /// 795 | /// The primary key. 796 | /// 797 | /// 798 | /// The object with the given primary key or null 799 | /// if the object is not found. 800 | /// 801 | public T Find (object pk) where T : new () 802 | { 803 | var map = GetMapping (typeof (T)); 804 | return Query (map.GetByPrimaryKeySql, pk).FirstOrDefault (); 805 | } 806 | 807 | /// 808 | /// Attempts to retrieve an object with the given primary key from the table 809 | /// associated with the specified type. Use of this method requires that 810 | /// the given type have a designated PrimaryKey (using the PrimaryKeyAttribute). 811 | /// 812 | /// 813 | /// The primary key. 814 | /// 815 | /// 816 | /// The TableMapping used to identify the object type. 817 | /// 818 | /// 819 | /// The object with the given primary key or null 820 | /// if the object is not found. 821 | /// 822 | public object Find (object pk, TableMapping map) 823 | { 824 | return Query (map, map.GetByPrimaryKeySql, pk).FirstOrDefault (); 825 | } 826 | 827 | /// 828 | /// Attempts to retrieve the first object that matches the predicate from the table 829 | /// associated with the specified type. 830 | /// 831 | /// 832 | /// A predicate for which object to find. 833 | /// 834 | /// 835 | /// The object that matches the given predicate or null 836 | /// if the object is not found. 837 | /// 838 | public T Find (Expression> predicate) where T : new() 839 | { 840 | return Table ().Where (predicate).FirstOrDefault (); 841 | } 842 | 843 | /// 844 | /// Whether has been called and the database is waiting for a . 845 | /// 846 | public bool IsInTransaction { 847 | get { return _transactionDepth > 0; } 848 | } 849 | 850 | /// 851 | /// Begins a new transaction. Call to end the transaction. 852 | /// 853 | /// Throws if a transaction has already begun. 854 | public void BeginTransaction () 855 | { 856 | // The BEGIN command only works if the transaction stack is empty, 857 | // or in other words if there are no pending transactions. 858 | // If the transaction stack is not empty when the BEGIN command is invoked, 859 | // then the command fails with an error. 860 | // Rather than crash with an error, we will just ignore calls to BeginTransaction 861 | // that would result in an error. 862 | if (Interlocked.CompareExchange (ref _transactionDepth, 1, 0) == 0) { 863 | try { 864 | Execute ("begin transaction"); 865 | } catch (Exception ex) { 866 | var sqlExp = ex as SQLiteException; 867 | if (sqlExp != null) { 868 | // It is recommended that applications respond to the errors listed below 869 | // by explicitly issuing a ROLLBACK command. 870 | // TODO: This rollback failsafe should be localized to all throw sites. 871 | switch (sqlExp.Result) { 872 | case SQLite3.Result.IOError: 873 | case SQLite3.Result.Full: 874 | case SQLite3.Result.Busy: 875 | case SQLite3.Result.NoMem: 876 | case SQLite3.Result.Interrupt: 877 | RollbackTo (null, true); 878 | break; 879 | } 880 | } else { 881 | // Call decrement and not VolatileWrite in case we've already 882 | // created a transaction point in SaveTransactionPoint since the catch. 883 | Interlocked.Decrement (ref _transactionDepth); 884 | } 885 | 886 | throw; 887 | } 888 | } else { 889 | // Calling BeginTransaction on an already open transaction is invalid 890 | throw new InvalidOperationException ("Cannot begin a transaction while already in a transaction."); 891 | } 892 | } 893 | 894 | /// 895 | /// Creates a savepoint in the database at the current point in the transaction timeline. 896 | /// Begins a new transaction if one is not in progress. 897 | /// 898 | /// Call to undo transactions since the returned savepoint. 899 | /// Call to commit transactions after the savepoint returned here. 900 | /// Call to end the transaction, committing all changes. 901 | /// 902 | /// A string naming the savepoint. 903 | public string SaveTransactionPoint () 904 | { 905 | int depth = Interlocked.Increment (ref _transactionDepth) - 1; 906 | string retVal = "S" + _rand.Next (short.MaxValue) + "D" + depth; 907 | 908 | try { 909 | Execute ("savepoint " + retVal); 910 | } catch (Exception ex) { 911 | var sqlExp = ex as SQLiteException; 912 | if (sqlExp != null) { 913 | // It is recommended that applications respond to the errors listed below 914 | // by explicitly issuing a ROLLBACK command. 915 | // TODO: This rollback failsafe should be localized to all throw sites. 916 | switch (sqlExp.Result) { 917 | case SQLite3.Result.IOError: 918 | case SQLite3.Result.Full: 919 | case SQLite3.Result.Busy: 920 | case SQLite3.Result.NoMem: 921 | case SQLite3.Result.Interrupt: 922 | RollbackTo (null, true); 923 | break; 924 | } 925 | } else { 926 | Interlocked.Decrement (ref _transactionDepth); 927 | } 928 | 929 | throw; 930 | } 931 | 932 | return retVal; 933 | } 934 | 935 | /// 936 | /// Rolls back the transaction that was begun by or . 937 | /// 938 | public void Rollback () 939 | { 940 | RollbackTo (null, false); 941 | } 942 | 943 | /// 944 | /// Rolls back the savepoint created by or SaveTransactionPoint. 945 | /// 946 | /// The name of the savepoint to roll back to, as returned by . If savepoint is null or empty, this method is equivalent to a call to 947 | public void RollbackTo (string savepoint) 948 | { 949 | RollbackTo (savepoint, false); 950 | } 951 | 952 | /// 953 | /// Rolls back the transaction that was begun by . 954 | /// 955 | /// true to avoid throwing exceptions, false otherwise 956 | void RollbackTo (string savepoint, bool noThrow) 957 | { 958 | // Rolling back without a TO clause rolls backs all transactions 959 | // and leaves the transaction stack empty. 960 | try { 961 | if (String.IsNullOrEmpty (savepoint)) { 962 | if (Interlocked.Exchange (ref _transactionDepth, 0) > 0) { 963 | Execute ("rollback"); 964 | } 965 | } else { 966 | DoSavePointExecute (savepoint, "rollback to "); 967 | } 968 | } catch (SQLiteException) { 969 | if (!noThrow) 970 | throw; 971 | 972 | } 973 | // No need to rollback if there are no transactions open. 974 | } 975 | 976 | /// 977 | /// Releases a savepoint returned from . Releasing a savepoint 978 | /// makes changes since that savepoint permanent if the savepoint began the transaction, 979 | /// or otherwise the changes are permanent pending a call to . 980 | /// 981 | /// The RELEASE command is like a COMMIT for a SAVEPOINT. 982 | /// 983 | /// The name of the savepoint to release. The string should be the result of a call to 984 | public void Release (string savepoint) 985 | { 986 | DoSavePointExecute (savepoint, "release "); 987 | } 988 | 989 | void DoSavePointExecute (string savepoint, string cmd) 990 | { 991 | // Validate the savepoint 992 | int firstLen = savepoint.IndexOf ('D'); 993 | if (firstLen >= 2 && savepoint.Length > firstLen + 1) { 994 | int depth; 995 | if (Int32.TryParse (savepoint.Substring (firstLen + 1), out depth)) { 996 | // TODO: Mild race here, but inescapable without locking almost everywhere. 997 | if (0 <= depth && depth < _transactionDepth) { 998 | #if NETFX_CORE 999 | Volatile.Write (ref _transactionDepth, depth); 1000 | #elif SILVERLIGHT 1001 | _transactionDepth = depth; 1002 | #else 1003 | Thread.VolatileWrite (ref _transactionDepth, depth); 1004 | #endif 1005 | Execute (cmd + savepoint); 1006 | return; 1007 | } 1008 | } 1009 | } 1010 | 1011 | throw new ArgumentException ("savePoint is not valid, and should be the result of a call to SaveTransactionPoint.", "savePoint"); 1012 | } 1013 | 1014 | /// 1015 | /// Commits the transaction that was begun by . 1016 | /// 1017 | public void Commit () 1018 | { 1019 | if (Interlocked.Exchange (ref _transactionDepth, 0) != 0) { 1020 | Execute ("commit"); 1021 | } 1022 | // Do nothing on a commit with no open transaction 1023 | } 1024 | 1025 | /// 1026 | /// Executes within a (possibly nested) transaction by wrapping it in a SAVEPOINT. If an 1027 | /// exception occurs the whole transaction is rolled back, not just the current savepoint. The exception 1028 | /// is rethrown. 1029 | /// 1030 | /// 1031 | /// The to perform within a transaction. can contain any number 1032 | /// of operations on the connection but should never call or 1033 | /// . 1034 | /// 1035 | public void RunInTransaction (Action action) 1036 | { 1037 | try { 1038 | var savePoint = SaveTransactionPoint (); 1039 | action (); 1040 | Release (savePoint); 1041 | } catch (Exception) { 1042 | Rollback (); 1043 | throw; 1044 | } 1045 | } 1046 | 1047 | /// 1048 | /// Inserts all specified objects. 1049 | /// 1050 | /// 1051 | /// An of the objects to insert. 1052 | /// 1053 | /// 1054 | /// The number of rows added to the table. 1055 | /// 1056 | public int InsertAll (System.Collections.IEnumerable objects) 1057 | { 1058 | var c = 0; 1059 | RunInTransaction(() => { 1060 | foreach (var r in objects) { 1061 | c += Insert (r); 1062 | } 1063 | }); 1064 | return c; 1065 | } 1066 | 1067 | /// 1068 | /// Inserts all specified objects. 1069 | /// 1070 | /// 1071 | /// An of the objects to insert. 1072 | /// 1073 | /// 1074 | /// Literal SQL code that gets placed into the command. INSERT {extra} INTO ... 1075 | /// 1076 | /// 1077 | /// The number of rows added to the table. 1078 | /// 1079 | public int InsertAll (System.Collections.IEnumerable objects, string extra) 1080 | { 1081 | var c = 0; 1082 | RunInTransaction (() => { 1083 | foreach (var r in objects) { 1084 | c += Insert (r, extra); 1085 | } 1086 | }); 1087 | return c; 1088 | } 1089 | 1090 | /// 1091 | /// Inserts all specified objects. 1092 | /// 1093 | /// 1094 | /// An of the objects to insert. 1095 | /// 1096 | /// 1097 | /// The type of object to insert. 1098 | /// 1099 | /// 1100 | /// The number of rows added to the table. 1101 | /// 1102 | public int InsertAll (System.Collections.IEnumerable objects, Type objType) 1103 | { 1104 | var c = 0; 1105 | RunInTransaction (() => { 1106 | foreach (var r in objects) { 1107 | c += Insert (r, objType); 1108 | } 1109 | }); 1110 | return c; 1111 | } 1112 | 1113 | /// 1114 | /// Inserts the given object and retrieves its 1115 | /// auto incremented primary key if it has one. 1116 | /// 1117 | /// 1118 | /// The object to insert. 1119 | /// 1120 | /// 1121 | /// The number of rows added to the table. 1122 | /// 1123 | public int Insert (object obj) 1124 | { 1125 | if (obj == null) { 1126 | return 0; 1127 | } 1128 | return Insert (obj, "", obj.GetType ()); 1129 | } 1130 | 1131 | /// 1132 | /// Inserts the given object and retrieves its 1133 | /// auto incremented primary key if it has one. 1134 | /// If a UNIQUE constraint violation occurs with 1135 | /// some pre-existing object, this function deletes 1136 | /// the old object. 1137 | /// 1138 | /// 1139 | /// The object to insert. 1140 | /// 1141 | /// 1142 | /// The number of rows modified. 1143 | /// 1144 | public int InsertOrReplace (object obj) 1145 | { 1146 | if (obj == null) { 1147 | return 0; 1148 | } 1149 | return Insert (obj, "OR REPLACE", obj.GetType ()); 1150 | } 1151 | 1152 | /// 1153 | /// Inserts the given object and retrieves its 1154 | /// auto incremented primary key if it has one. 1155 | /// 1156 | /// 1157 | /// The object to insert. 1158 | /// 1159 | /// 1160 | /// The type of object to insert. 1161 | /// 1162 | /// 1163 | /// The number of rows added to the table. 1164 | /// 1165 | public int Insert (object obj, Type objType) 1166 | { 1167 | return Insert (obj, "", objType); 1168 | } 1169 | 1170 | /// 1171 | /// Inserts the given object and retrieves its 1172 | /// auto incremented primary key if it has one. 1173 | /// If a UNIQUE constraint violation occurs with 1174 | /// some pre-existing object, this function deletes 1175 | /// the old object. 1176 | /// 1177 | /// 1178 | /// The object to insert. 1179 | /// 1180 | /// 1181 | /// The type of object to insert. 1182 | /// 1183 | /// 1184 | /// The number of rows modified. 1185 | /// 1186 | public int InsertOrReplace (object obj, Type objType) 1187 | { 1188 | return Insert (obj, "OR REPLACE", objType); 1189 | } 1190 | 1191 | /// 1192 | /// Inserts the given object and retrieves its 1193 | /// auto incremented primary key if it has one. 1194 | /// 1195 | /// 1196 | /// The object to insert. 1197 | /// 1198 | /// 1199 | /// Literal SQL code that gets placed into the command. INSERT {extra} INTO ... 1200 | /// 1201 | /// 1202 | /// The number of rows added to the table. 1203 | /// 1204 | public int Insert (object obj, string extra) 1205 | { 1206 | if (obj == null) { 1207 | return 0; 1208 | } 1209 | return Insert (obj, extra, obj.GetType ()); 1210 | } 1211 | 1212 | /// 1213 | /// Inserts the given object and retrieves its 1214 | /// auto incremented primary key if it has one. 1215 | /// 1216 | /// 1217 | /// The object to insert. 1218 | /// 1219 | /// 1220 | /// Literal SQL code that gets placed into the command. INSERT {extra} INTO ... 1221 | /// 1222 | /// 1223 | /// The type of object to insert. 1224 | /// 1225 | /// 1226 | /// The number of rows added to the table. 1227 | /// 1228 | public int Insert (object obj, string extra, Type objType) 1229 | { 1230 | if (obj == null || objType == null) { 1231 | return 0; 1232 | } 1233 | 1234 | 1235 | var map = GetMapping (objType); 1236 | 1237 | #if NETFX_CORE 1238 | if (map.PK != null && map.PK.IsAutoGuid) 1239 | { 1240 | // no GetProperty so search our way up the inheritance chain till we find it 1241 | PropertyInfo prop; 1242 | while (objType != null) 1243 | { 1244 | var info = objType.GetTypeInfo(); 1245 | prop = info.GetDeclaredProperty(map.PK.PropertyName); 1246 | if (prop != null) 1247 | { 1248 | if (prop.GetValue(obj, null).Equals(Guid.Empty)) 1249 | { 1250 | prop.SetValue(obj, Guid.NewGuid(), null); 1251 | } 1252 | break; 1253 | } 1254 | 1255 | objType = info.BaseType; 1256 | } 1257 | } 1258 | #else 1259 | if (map.PK != null && map.PK.IsAutoGuid) { 1260 | var prop = objType.GetProperty(map.PK.PropertyName); 1261 | if (prop != null) { 1262 | if (prop.GetValue(obj, null).Equals(Guid.Empty)) { 1263 | prop.SetValue(obj, Guid.NewGuid(), null); 1264 | } 1265 | } 1266 | } 1267 | #endif 1268 | 1269 | 1270 | var replacing = string.Compare (extra, "OR REPLACE", StringComparison.OrdinalIgnoreCase) == 0; 1271 | 1272 | var cols = replacing ? map.InsertOrReplaceColumns : map.InsertColumns; 1273 | var vals = new object[cols.Length]; 1274 | for (var i = 0; i < vals.Length; i++) { 1275 | vals [i] = cols [i].GetValue (obj); 1276 | } 1277 | 1278 | var insertCmd = map.GetInsertCommand (this, extra); 1279 | int count; 1280 | 1281 | try { 1282 | count = insertCmd.ExecuteNonQuery (vals); 1283 | } 1284 | catch (SQLiteException ex) { 1285 | 1286 | if (SQLite3.ExtendedErrCode (this.Handle) == SQLite3.ExtendedResult.ConstraintNotNull) { 1287 | throw NotNullConstraintViolationException.New (ex.Result, ex.Message, map, obj); 1288 | } 1289 | throw; 1290 | } 1291 | 1292 | if (map.HasAutoIncPK) 1293 | { 1294 | var id = SQLite3.LastInsertRowid (Handle); 1295 | map.SetAutoIncPK (obj, id); 1296 | } 1297 | 1298 | return count; 1299 | } 1300 | 1301 | /// 1302 | /// Updates all of the columns of a table using the specified object 1303 | /// except for its primary key. 1304 | /// The object is required to have a primary key. 1305 | /// 1306 | /// 1307 | /// The object to update. It must have a primary key designated using the PrimaryKeyAttribute. 1308 | /// 1309 | /// 1310 | /// The number of rows updated. 1311 | /// 1312 | public int Update (object obj) 1313 | { 1314 | if (obj == null) { 1315 | return 0; 1316 | } 1317 | return Update (obj, obj.GetType ()); 1318 | } 1319 | 1320 | /// 1321 | /// Updates all of the columns of a table using the specified object 1322 | /// except for its primary key. 1323 | /// The object is required to have a primary key. 1324 | /// 1325 | /// 1326 | /// The object to update. It must have a primary key designated using the PrimaryKeyAttribute. 1327 | /// 1328 | /// 1329 | /// The type of object to insert. 1330 | /// 1331 | /// 1332 | /// The number of rows updated. 1333 | /// 1334 | public int Update (object obj, Type objType) 1335 | { 1336 | int rowsAffected = 0; 1337 | if (obj == null || objType == null) { 1338 | return 0; 1339 | } 1340 | 1341 | var map = GetMapping (objType); 1342 | 1343 | var pk = map.PK; 1344 | 1345 | if (pk == null) { 1346 | throw new NotSupportedException ("Cannot update " + map.TableName + ": it has no PK"); 1347 | } 1348 | 1349 | var cols = from p in map.Columns 1350 | where p != pk 1351 | select p; 1352 | var vals = from c in cols 1353 | select c.GetValue (obj); 1354 | var ps = new List (vals); 1355 | ps.Add (pk.GetValue (obj)); 1356 | var q = string.Format ("update \"{0}\" set {1} where {2} = ? ", map.TableName, string.Join (",", (from c in cols 1357 | select "\"" + c.Name + "\" = ? ").ToArray ()), pk.Name); 1358 | 1359 | try { 1360 | rowsAffected = Execute (q, ps.ToArray ()); 1361 | } 1362 | catch (SQLiteException ex) { 1363 | 1364 | if (ex.Result == SQLite3.Result.Constraint && SQLite3.ExtendedErrCode (this.Handle) == SQLite3.ExtendedResult.ConstraintNotNull) { 1365 | throw NotNullConstraintViolationException.New (ex, map, obj); 1366 | } 1367 | 1368 | throw ex; 1369 | } 1370 | 1371 | return rowsAffected; 1372 | } 1373 | 1374 | /// 1375 | /// Updates all specified objects. 1376 | /// 1377 | /// 1378 | /// An of the objects to insert. 1379 | /// 1380 | /// 1381 | /// The number of rows modified. 1382 | /// 1383 | public int UpdateAll (System.Collections.IEnumerable objects) 1384 | { 1385 | var c = 0; 1386 | RunInTransaction (() => { 1387 | foreach (var r in objects) { 1388 | c += Update (r); 1389 | } 1390 | }); 1391 | return c; 1392 | } 1393 | 1394 | /// 1395 | /// Deletes the given object from the database using its primary key. 1396 | /// 1397 | /// 1398 | /// The object to delete. It must have a primary key designated using the PrimaryKeyAttribute. 1399 | /// 1400 | /// 1401 | /// The number of rows deleted. 1402 | /// 1403 | public int Delete (object objectToDelete) 1404 | { 1405 | var map = GetMapping (objectToDelete.GetType ()); 1406 | var pk = map.PK; 1407 | if (pk == null) { 1408 | throw new NotSupportedException ("Cannot delete " + map.TableName + ": it has no PK"); 1409 | } 1410 | var q = string.Format ("delete from \"{0}\" where \"{1}\" = ?", map.TableName, pk.Name); 1411 | return Execute (q, pk.GetValue (objectToDelete)); 1412 | } 1413 | 1414 | /// 1415 | /// Deletes the object with the specified primary key. 1416 | /// 1417 | /// 1418 | /// The primary key of the object to delete. 1419 | /// 1420 | /// 1421 | /// The number of objects deleted. 1422 | /// 1423 | /// 1424 | /// The type of object. 1425 | /// 1426 | public int Delete (object primaryKey) 1427 | { 1428 | var map = GetMapping (typeof (T)); 1429 | var pk = map.PK; 1430 | if (pk == null) { 1431 | throw new NotSupportedException ("Cannot delete " + map.TableName + ": it has no PK"); 1432 | } 1433 | var q = string.Format ("delete from \"{0}\" where \"{1}\" = ?", map.TableName, pk.Name); 1434 | return Execute (q, primaryKey); 1435 | } 1436 | 1437 | /// 1438 | /// Deletes all the objects from the specified table. 1439 | /// WARNING WARNING: Let me repeat. It deletes ALL the objects from the 1440 | /// specified table. Do you really want to do that? 1441 | /// 1442 | /// 1443 | /// The number of objects deleted. 1444 | /// 1445 | /// 1446 | /// The type of objects to delete. 1447 | /// 1448 | public int DeleteAll () 1449 | { 1450 | var map = GetMapping (typeof (T)); 1451 | var query = string.Format("delete from \"{0}\"", map.TableName); 1452 | return Execute (query); 1453 | } 1454 | 1455 | ~SQLiteConnection () 1456 | { 1457 | Dispose (false); 1458 | } 1459 | 1460 | public void Dispose () 1461 | { 1462 | Dispose (true); 1463 | GC.SuppressFinalize (this); 1464 | } 1465 | 1466 | protected virtual void Dispose (bool disposing) 1467 | { 1468 | Close (); 1469 | } 1470 | 1471 | public void Close () 1472 | { 1473 | if (_open && Handle != NullHandle) { 1474 | try { 1475 | if (_mappings != null) { 1476 | foreach (var sqlInsertCommand in _mappings.Values) { 1477 | sqlInsertCommand.Dispose(); 1478 | } 1479 | } 1480 | var r = SQLite3.Close (Handle); 1481 | if (r != SQLite3.Result.OK) { 1482 | string msg = SQLite3.GetErrmsg (Handle); 1483 | throw SQLiteException.New (r, msg); 1484 | } 1485 | } 1486 | finally { 1487 | Handle = NullHandle; 1488 | _open = false; 1489 | } 1490 | } 1491 | } 1492 | } 1493 | 1494 | /// 1495 | /// Represents a parsed connection string. 1496 | /// 1497 | class SQLiteConnectionString 1498 | { 1499 | public string ConnectionString { get; private set; } 1500 | public string DatabasePath { get; private set; } 1501 | public bool StoreDateTimeAsTicks { get; private set; } 1502 | 1503 | #if NETFX_CORE 1504 | static readonly string MetroStyleDataPath = Windows.Storage.ApplicationData.Current.LocalFolder.Path; 1505 | #endif 1506 | 1507 | public SQLiteConnectionString (string databasePath, bool storeDateTimeAsTicks) 1508 | { 1509 | ConnectionString = databasePath; 1510 | StoreDateTimeAsTicks = storeDateTimeAsTicks; 1511 | 1512 | #if NETFX_CORE 1513 | DatabasePath = System.IO.Path.Combine (MetroStyleDataPath, databasePath); 1514 | #else 1515 | DatabasePath = databasePath; 1516 | #endif 1517 | } 1518 | } 1519 | 1520 | [AttributeUsage (AttributeTargets.Class)] 1521 | public class TableAttribute : Attribute 1522 | { 1523 | public string Name { get; set; } 1524 | 1525 | public TableAttribute (string name) 1526 | { 1527 | Name = name; 1528 | } 1529 | } 1530 | 1531 | [AttributeUsage (AttributeTargets.Property)] 1532 | public class ColumnAttribute : Attribute 1533 | { 1534 | public string Name { get; set; } 1535 | 1536 | public ColumnAttribute (string name) 1537 | { 1538 | Name = name; 1539 | } 1540 | } 1541 | 1542 | [AttributeUsage (AttributeTargets.Property)] 1543 | public class PrimaryKeyAttribute : Attribute 1544 | { 1545 | } 1546 | 1547 | [AttributeUsage (AttributeTargets.Property)] 1548 | public class AutoIncrementAttribute : Attribute 1549 | { 1550 | } 1551 | 1552 | [AttributeUsage (AttributeTargets.Property)] 1553 | public class IndexedAttribute : Attribute 1554 | { 1555 | public string Name { get; set; } 1556 | public int Order { get; set; } 1557 | public virtual bool Unique { get; set; } 1558 | 1559 | public IndexedAttribute() 1560 | { 1561 | } 1562 | 1563 | public IndexedAttribute(string name, int order) 1564 | { 1565 | Name = name; 1566 | Order = order; 1567 | } 1568 | } 1569 | 1570 | [AttributeUsage (AttributeTargets.Property)] 1571 | public class IgnoreAttribute : Attribute 1572 | { 1573 | } 1574 | 1575 | [AttributeUsage (AttributeTargets.Property)] 1576 | public class UniqueAttribute : IndexedAttribute 1577 | { 1578 | public override bool Unique { 1579 | get { return true; } 1580 | set { /* throw? */ } 1581 | } 1582 | } 1583 | 1584 | [AttributeUsage (AttributeTargets.Property)] 1585 | public class MaxLengthAttribute : Attribute 1586 | { 1587 | public int Value { get; private set; } 1588 | 1589 | public MaxLengthAttribute (int length) 1590 | { 1591 | Value = length; 1592 | } 1593 | } 1594 | 1595 | [AttributeUsage (AttributeTargets.Property)] 1596 | public class CollationAttribute: Attribute 1597 | { 1598 | public string Value { get; private set; } 1599 | 1600 | public CollationAttribute (string collation) 1601 | { 1602 | Value = collation; 1603 | } 1604 | } 1605 | 1606 | [AttributeUsage (AttributeTargets.Property)] 1607 | public class NotNullAttribute : Attribute 1608 | { 1609 | } 1610 | 1611 | public class TableMapping 1612 | { 1613 | public Type MappedType { get; private set; } 1614 | 1615 | public string TableName { get; private set; } 1616 | 1617 | public Column[] Columns { get; private set; } 1618 | 1619 | public Column PK { get; private set; } 1620 | 1621 | public string GetByPrimaryKeySql { get; private set; } 1622 | 1623 | Column _autoPk; 1624 | Column[] _insertColumns; 1625 | Column[] _insertOrReplaceColumns; 1626 | 1627 | public TableMapping(Type type, CreateFlags createFlags = CreateFlags.None) 1628 | { 1629 | MappedType = type; 1630 | 1631 | #if NETFX_CORE 1632 | var tableAttr = (TableAttribute)System.Reflection.CustomAttributeExtensions 1633 | .GetCustomAttribute(type.GetTypeInfo(), typeof(TableAttribute), true); 1634 | #else 1635 | var tableAttr = (TableAttribute)type.GetCustomAttributes (typeof (TableAttribute), true).FirstOrDefault (); 1636 | #endif 1637 | 1638 | TableName = tableAttr != null ? tableAttr.Name : MappedType.Name; 1639 | 1640 | #if !NETFX_CORE 1641 | var props = MappedType.GetProperties (BindingFlags.Public | BindingFlags.Instance | BindingFlags.SetProperty); 1642 | #else 1643 | var props = from p in MappedType.GetRuntimeProperties() 1644 | 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)) 1645 | select p; 1646 | #endif 1647 | var cols = new List (); 1648 | foreach (var p in props) { 1649 | #if !NETFX_CORE 1650 | var ignore = p.GetCustomAttributes (typeof(IgnoreAttribute), true).Length > 0; 1651 | #else 1652 | var ignore = p.GetCustomAttributes (typeof(IgnoreAttribute), true).Count() > 0; 1653 | #endif 1654 | if (p.CanWrite && !ignore) { 1655 | cols.Add (new Column (p, createFlags)); 1656 | } 1657 | } 1658 | Columns = cols.ToArray (); 1659 | foreach (var c in Columns) { 1660 | if (c.IsAutoInc && c.IsPK) { 1661 | _autoPk = c; 1662 | } 1663 | if (c.IsPK) { 1664 | PK = c; 1665 | } 1666 | } 1667 | 1668 | HasAutoIncPK = _autoPk != null; 1669 | 1670 | if (PK != null) { 1671 | GetByPrimaryKeySql = string.Format ("select * from \"{0}\" where \"{1}\" = ?", TableName, PK.Name); 1672 | } 1673 | else { 1674 | // People should not be calling Get/Find without a PK 1675 | GetByPrimaryKeySql = string.Format ("select * from \"{0}\" limit 1", TableName); 1676 | } 1677 | } 1678 | 1679 | public bool HasAutoIncPK { get; private set; } 1680 | 1681 | public void SetAutoIncPK (object obj, long id) 1682 | { 1683 | if (_autoPk != null) { 1684 | _autoPk.SetValue (obj, Convert.ChangeType (id, _autoPk.ColumnType, null)); 1685 | } 1686 | } 1687 | 1688 | public Column[] InsertColumns { 1689 | get { 1690 | if (_insertColumns == null) { 1691 | _insertColumns = Columns.Where (c => !c.IsAutoInc).ToArray (); 1692 | } 1693 | return _insertColumns; 1694 | } 1695 | } 1696 | 1697 | public Column[] InsertOrReplaceColumns { 1698 | get { 1699 | if (_insertOrReplaceColumns == null) { 1700 | _insertOrReplaceColumns = Columns.ToArray (); 1701 | } 1702 | return _insertOrReplaceColumns; 1703 | } 1704 | } 1705 | 1706 | public Column FindColumnWithPropertyName (string propertyName) 1707 | { 1708 | var exact = Columns.FirstOrDefault (c => c.PropertyName == propertyName); 1709 | return exact; 1710 | } 1711 | 1712 | public Column FindColumn (string columnName) 1713 | { 1714 | var exact = Columns.FirstOrDefault (c => c.Name == columnName); 1715 | return exact; 1716 | } 1717 | 1718 | PreparedSqlLiteInsertCommand _insertCommand; 1719 | string _insertCommandExtra; 1720 | 1721 | public PreparedSqlLiteInsertCommand GetInsertCommand(SQLiteConnection conn, string extra) 1722 | { 1723 | if (_insertCommand == null) { 1724 | _insertCommand = CreateInsertCommand(conn, extra); 1725 | _insertCommandExtra = extra; 1726 | } 1727 | else if (_insertCommandExtra != extra) { 1728 | _insertCommand.Dispose(); 1729 | _insertCommand = CreateInsertCommand(conn, extra); 1730 | _insertCommandExtra = extra; 1731 | } 1732 | return _insertCommand; 1733 | } 1734 | 1735 | PreparedSqlLiteInsertCommand CreateInsertCommand(SQLiteConnection conn, string extra) 1736 | { 1737 | var cols = InsertColumns; 1738 | string insertSql; 1739 | if (!cols.Any() && Columns.Count() == 1 && Columns[0].IsAutoInc) 1740 | { 1741 | insertSql = string.Format("insert {1} into \"{0}\" default values", TableName, extra); 1742 | } 1743 | else 1744 | { 1745 | var replacing = string.Compare (extra, "OR REPLACE", StringComparison.OrdinalIgnoreCase) == 0; 1746 | 1747 | if (replacing) { 1748 | cols = InsertOrReplaceColumns; 1749 | } 1750 | 1751 | insertSql = string.Format("insert {3} into \"{0}\"({1}) values ({2})", TableName, 1752 | string.Join(",", (from c in cols 1753 | select "\"" + c.Name + "\"").ToArray()), 1754 | string.Join(",", (from c in cols 1755 | select "?").ToArray()), extra); 1756 | 1757 | } 1758 | 1759 | var insertCommand = new PreparedSqlLiteInsertCommand(conn); 1760 | insertCommand.CommandText = insertSql; 1761 | return insertCommand; 1762 | } 1763 | 1764 | protected internal void Dispose() 1765 | { 1766 | if (_insertCommand != null) { 1767 | _insertCommand.Dispose(); 1768 | _insertCommand = null; 1769 | } 1770 | } 1771 | 1772 | public class Column 1773 | { 1774 | PropertyInfo _prop; 1775 | 1776 | public string Name { get; private set; } 1777 | 1778 | public string PropertyName { get { return _prop.Name; } } 1779 | 1780 | public Type ColumnType { get; private set; } 1781 | 1782 | public string Collation { get; private set; } 1783 | 1784 | public bool IsAutoInc { get; private set; } 1785 | public bool IsAutoGuid { get; private set; } 1786 | 1787 | public bool IsPK { get; private set; } 1788 | 1789 | public IEnumerable Indices { get; set; } 1790 | 1791 | public bool IsNullable { get; private set; } 1792 | 1793 | public int? MaxStringLength { get; private set; } 1794 | 1795 | public Column(PropertyInfo prop, CreateFlags createFlags = CreateFlags.None) 1796 | { 1797 | var colAttr = (ColumnAttribute)prop.GetCustomAttributes(typeof(ColumnAttribute), true).FirstOrDefault(); 1798 | 1799 | _prop = prop; 1800 | Name = colAttr == null ? prop.Name : colAttr.Name; 1801 | //If this type is Nullable then Nullable.GetUnderlyingType returns the T, otherwise it returns null, so get the actual type instead 1802 | ColumnType = Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType; 1803 | Collation = Orm.Collation(prop); 1804 | 1805 | IsPK = Orm.IsPK(prop) || 1806 | (((createFlags & CreateFlags.ImplicitPK) == CreateFlags.ImplicitPK) && 1807 | string.Compare (prop.Name, Orm.ImplicitPkName, StringComparison.OrdinalIgnoreCase) == 0); 1808 | 1809 | var isAuto = Orm.IsAutoInc(prop) || (IsPK && ((createFlags & CreateFlags.AutoIncPK) == CreateFlags.AutoIncPK)); 1810 | IsAutoGuid = isAuto && ColumnType == typeof(Guid); 1811 | IsAutoInc = isAuto && !IsAutoGuid; 1812 | 1813 | Indices = Orm.GetIndices(prop); 1814 | if (!Indices.Any() 1815 | && !IsPK 1816 | && ((createFlags & CreateFlags.ImplicitIndex) == CreateFlags.ImplicitIndex) 1817 | && Name.EndsWith (Orm.ImplicitIndexSuffix, StringComparison.OrdinalIgnoreCase) 1818 | ) 1819 | { 1820 | Indices = new IndexedAttribute[] { new IndexedAttribute() }; 1821 | } 1822 | IsNullable = !(IsPK || Orm.IsMarkedNotNull(prop)); 1823 | MaxStringLength = Orm.MaxStringLength(prop); 1824 | } 1825 | 1826 | public void SetValue (object obj, object val) 1827 | { 1828 | _prop.SetValue (obj, val, null); 1829 | } 1830 | 1831 | public object GetValue (object obj) 1832 | { 1833 | return _prop.GetValue (obj, null); 1834 | } 1835 | } 1836 | } 1837 | 1838 | public static class Orm 1839 | { 1840 | public const int DefaultMaxStringLength = 140; 1841 | public const string ImplicitPkName = "Id"; 1842 | public const string ImplicitIndexSuffix = "Id"; 1843 | 1844 | public static string SqlDecl (TableMapping.Column p, bool storeDateTimeAsTicks) 1845 | { 1846 | string decl = "\"" + p.Name + "\" " + SqlType (p, storeDateTimeAsTicks) + " "; 1847 | 1848 | if (p.IsPK) { 1849 | decl += "primary key "; 1850 | } 1851 | if (p.IsAutoInc) { 1852 | decl += "autoincrement "; 1853 | } 1854 | if (!p.IsNullable) { 1855 | decl += "not null "; 1856 | } 1857 | if (!string.IsNullOrEmpty (p.Collation)) { 1858 | decl += "collate " + p.Collation + " "; 1859 | } 1860 | 1861 | return decl; 1862 | } 1863 | 1864 | public static string SqlType (TableMapping.Column p, bool storeDateTimeAsTicks) 1865 | { 1866 | var clrType = p.ColumnType; 1867 | if (clrType == typeof(Boolean) || 1868 | clrType == typeof(Byte) || 1869 | clrType == typeof(UInt16) || 1870 | clrType == typeof(SByte) || 1871 | clrType == typeof(Int16) || 1872 | clrType == typeof(Int32) || 1873 | clrType == typeof(UInt32) || 1874 | clrType == typeof(Int64)) { 1875 | return "integer"; 1876 | } else if (clrType == typeof(Single) || clrType == typeof(Double) || clrType == typeof(Decimal)) { 1877 | return "float"; 1878 | } else if (clrType == typeof(String)) { 1879 | int? len = p.MaxStringLength; 1880 | 1881 | if (len.HasValue) 1882 | return "varchar(" + len.Value + ")"; 1883 | 1884 | return "varchar"; 1885 | } else if (clrType == typeof(TimeSpan)) { 1886 | return "bigint"; 1887 | } else if (clrType == typeof(DateTime)) { 1888 | return storeDateTimeAsTicks ? "bigint" : "datetime"; 1889 | } else if (clrType == typeof(DateTimeOffset)) { 1890 | return "bigint"; 1891 | #if !NETFX_CORE 1892 | } else if (clrType.IsEnum) { 1893 | #else 1894 | } else if (clrType.GetTypeInfo().IsEnum) { 1895 | #endif 1896 | return "integer"; 1897 | } else if (clrType == typeof(byte[])) { 1898 | return "blob"; 1899 | } else if (clrType == typeof(Guid)) { 1900 | return "varchar(36)"; 1901 | } else { 1902 | throw new NotSupportedException ("Don't know about " + clrType); 1903 | } 1904 | } 1905 | 1906 | public static bool IsPK (MemberInfo p) 1907 | { 1908 | var attrs = p.GetCustomAttributes (typeof(PrimaryKeyAttribute), true); 1909 | #if !NETFX_CORE 1910 | return attrs.Length > 0; 1911 | #else 1912 | return attrs.Count() > 0; 1913 | #endif 1914 | } 1915 | 1916 | public static string Collation (MemberInfo p) 1917 | { 1918 | var attrs = p.GetCustomAttributes (typeof(CollationAttribute), true); 1919 | #if !NETFX_CORE 1920 | if (attrs.Length > 0) { 1921 | return ((CollationAttribute)attrs [0]).Value; 1922 | #else 1923 | if (attrs.Count() > 0) { 1924 | return ((CollationAttribute)attrs.First()).Value; 1925 | #endif 1926 | } else { 1927 | return string.Empty; 1928 | } 1929 | } 1930 | 1931 | public static bool IsAutoInc (MemberInfo p) 1932 | { 1933 | var attrs = p.GetCustomAttributes (typeof(AutoIncrementAttribute), true); 1934 | #if !NETFX_CORE 1935 | return attrs.Length > 0; 1936 | #else 1937 | return attrs.Count() > 0; 1938 | #endif 1939 | } 1940 | 1941 | public static IEnumerable GetIndices(MemberInfo p) 1942 | { 1943 | var attrs = p.GetCustomAttributes(typeof(IndexedAttribute), true); 1944 | return attrs.Cast(); 1945 | } 1946 | 1947 | public static int? MaxStringLength(PropertyInfo p) 1948 | { 1949 | var attrs = p.GetCustomAttributes (typeof(MaxLengthAttribute), true); 1950 | #if !NETFX_CORE 1951 | if (attrs.Length > 0) 1952 | return ((MaxLengthAttribute)attrs [0]).Value; 1953 | #else 1954 | if (attrs.Count() > 0) 1955 | return ((MaxLengthAttribute)attrs.First()).Value; 1956 | #endif 1957 | 1958 | return null; 1959 | } 1960 | 1961 | public static bool IsMarkedNotNull(MemberInfo p) 1962 | { 1963 | var attrs = p.GetCustomAttributes (typeof (NotNullAttribute), true); 1964 | #if !NETFX_CORE 1965 | return attrs.Length > 0; 1966 | #else 1967 | return attrs.Count() > 0; 1968 | #endif 1969 | } 1970 | } 1971 | 1972 | public partial class SQLiteCommand 1973 | { 1974 | SQLiteConnection _conn; 1975 | private List _bindings; 1976 | 1977 | public string CommandText { get; set; } 1978 | 1979 | internal SQLiteCommand (SQLiteConnection conn) 1980 | { 1981 | _conn = conn; 1982 | _bindings = new List (); 1983 | CommandText = ""; 1984 | } 1985 | 1986 | public int ExecuteNonQuery () 1987 | { 1988 | if (_conn.Trace) { 1989 | Debug.WriteLine ("Executing: " + this); 1990 | } 1991 | 1992 | var r = SQLite3.Result.OK; 1993 | var stmt = Prepare (); 1994 | r = SQLite3.Step (stmt); 1995 | Finalize (stmt); 1996 | if (r == SQLite3.Result.Done) { 1997 | int rowsAffected = SQLite3.Changes (_conn.Handle); 1998 | return rowsAffected; 1999 | } else if (r == SQLite3.Result.Error) { 2000 | string msg = SQLite3.GetErrmsg (_conn.Handle); 2001 | throw SQLiteException.New (r, msg); 2002 | } 2003 | else if (r == SQLite3.Result.Constraint) { 2004 | if (SQLite3.ExtendedErrCode (_conn.Handle) == SQLite3.ExtendedResult.ConstraintNotNull) { 2005 | throw NotNullConstraintViolationException.New (r, SQLite3.GetErrmsg (_conn.Handle)); 2006 | } 2007 | } 2008 | 2009 | throw SQLiteException.New(r, r.ToString()); 2010 | } 2011 | 2012 | public IEnumerable ExecuteDeferredQuery () 2013 | { 2014 | return ExecuteDeferredQuery(_conn.GetMapping(typeof(T))); 2015 | } 2016 | 2017 | public List ExecuteQuery () 2018 | { 2019 | return ExecuteDeferredQuery(_conn.GetMapping(typeof(T))).ToList(); 2020 | } 2021 | 2022 | public List ExecuteQuery (TableMapping map) 2023 | { 2024 | return ExecuteDeferredQuery(map).ToList(); 2025 | } 2026 | 2027 | /// 2028 | /// Invoked every time an instance is loaded from the database. 2029 | /// 2030 | /// 2031 | /// The newly created object. 2032 | /// 2033 | /// 2034 | /// This can be overridden in combination with the 2035 | /// method to hook into the life-cycle of objects. 2036 | /// 2037 | /// Type safety is not possible because MonoTouch does not support virtual generic methods. 2038 | /// 2039 | protected virtual void OnInstanceCreated (object obj) 2040 | { 2041 | // Can be overridden. 2042 | } 2043 | 2044 | public IEnumerable ExecuteDeferredQuery (TableMapping map) 2045 | { 2046 | if (_conn.Trace) { 2047 | Debug.WriteLine ("Executing Query: " + this); 2048 | } 2049 | 2050 | var stmt = Prepare (); 2051 | try 2052 | { 2053 | var cols = new TableMapping.Column[SQLite3.ColumnCount (stmt)]; 2054 | 2055 | for (int i = 0; i < cols.Length; i++) { 2056 | var name = SQLite3.ColumnName16 (stmt, i); 2057 | cols [i] = map.FindColumn (name); 2058 | } 2059 | 2060 | while (SQLite3.Step (stmt) == SQLite3.Result.Row) { 2061 | var obj = Activator.CreateInstance(map.MappedType); 2062 | for (int i = 0; i < cols.Length; i++) { 2063 | if (cols [i] == null) 2064 | continue; 2065 | var colType = SQLite3.ColumnType (stmt, i); 2066 | var val = ReadCol (stmt, i, colType, cols [i].ColumnType); 2067 | cols [i].SetValue (obj, val); 2068 | } 2069 | OnInstanceCreated (obj); 2070 | yield return (T)obj; 2071 | } 2072 | } 2073 | finally 2074 | { 2075 | SQLite3.Finalize(stmt); 2076 | } 2077 | } 2078 | 2079 | public T ExecuteScalar () 2080 | { 2081 | if (_conn.Trace) { 2082 | Debug.WriteLine ("Executing Query: " + this); 2083 | } 2084 | 2085 | T val = default(T); 2086 | 2087 | var stmt = Prepare (); 2088 | 2089 | try 2090 | { 2091 | var r = SQLite3.Step (stmt); 2092 | if (r == SQLite3.Result.Row) { 2093 | var colType = SQLite3.ColumnType (stmt, 0); 2094 | val = (T)ReadCol (stmt, 0, colType, typeof(T)); 2095 | } 2096 | else if (r == SQLite3.Result.Done) { 2097 | } 2098 | else 2099 | { 2100 | throw SQLiteException.New (r, SQLite3.GetErrmsg (_conn.Handle)); 2101 | } 2102 | } 2103 | finally 2104 | { 2105 | Finalize (stmt); 2106 | } 2107 | 2108 | return val; 2109 | } 2110 | 2111 | public void Bind (string name, object val) 2112 | { 2113 | _bindings.Add (new Binding { 2114 | Name = name, 2115 | Value = val 2116 | }); 2117 | } 2118 | 2119 | public void Bind (object val) 2120 | { 2121 | Bind (null, val); 2122 | } 2123 | 2124 | public override string ToString () 2125 | { 2126 | var parts = new string[1 + _bindings.Count]; 2127 | parts [0] = CommandText; 2128 | var i = 1; 2129 | foreach (var b in _bindings) { 2130 | parts [i] = string.Format (" {0}: {1}", i - 1, b.Value); 2131 | i++; 2132 | } 2133 | return string.Join (Environment.NewLine, parts); 2134 | } 2135 | 2136 | Sqlite3Statement Prepare() 2137 | { 2138 | var stmt = SQLite3.Prepare2 (_conn.Handle, CommandText); 2139 | BindAll (stmt); 2140 | return stmt; 2141 | } 2142 | 2143 | void Finalize (Sqlite3Statement stmt) 2144 | { 2145 | SQLite3.Finalize (stmt); 2146 | } 2147 | 2148 | void BindAll (Sqlite3Statement stmt) 2149 | { 2150 | int nextIdx = 1; 2151 | foreach (var b in _bindings) { 2152 | if (b.Name != null) { 2153 | b.Index = SQLite3.BindParameterIndex (stmt, b.Name); 2154 | } else { 2155 | b.Index = nextIdx++; 2156 | } 2157 | 2158 | BindParameter (stmt, b.Index, b.Value, _conn.StoreDateTimeAsTicks); 2159 | } 2160 | } 2161 | 2162 | internal static IntPtr NegativePointer = new IntPtr (-1); 2163 | 2164 | internal static void BindParameter (Sqlite3Statement stmt, int index, object value, bool storeDateTimeAsTicks) 2165 | { 2166 | if (value == null) { 2167 | SQLite3.BindNull (stmt, index); 2168 | } else { 2169 | if (value is Int32) { 2170 | SQLite3.BindInt (stmt, index, (int)value); 2171 | } else if (value is String) { 2172 | SQLite3.BindText (stmt, index, (string)value, -1, NegativePointer); 2173 | } else if (value is Byte || value is UInt16 || value is SByte || value is Int16) { 2174 | SQLite3.BindInt (stmt, index, Convert.ToInt32 (value)); 2175 | } else if (value is Boolean) { 2176 | SQLite3.BindInt (stmt, index, (bool)value ? 1 : 0); 2177 | } else if (value is UInt32 || value is Int64) { 2178 | SQLite3.BindInt64 (stmt, index, Convert.ToInt64 (value)); 2179 | } else if (value is Single || value is Double || value is Decimal) { 2180 | SQLite3.BindDouble (stmt, index, Convert.ToDouble (value)); 2181 | } else if (value is TimeSpan) { 2182 | SQLite3.BindInt64(stmt, index, ((TimeSpan)value).Ticks); 2183 | } else if (value is DateTime) { 2184 | if (storeDateTimeAsTicks) { 2185 | SQLite3.BindInt64 (stmt, index, ((DateTime)value).Ticks); 2186 | } 2187 | else { 2188 | SQLite3.BindText (stmt, index, ((DateTime)value).ToString ("yyyy-MM-dd HH:mm:ss"), -1, NegativePointer); 2189 | } 2190 | } else if (value is DateTimeOffset) { 2191 | SQLite3.BindInt64 (stmt, index, ((DateTimeOffset)value).UtcTicks); 2192 | #if !NETFX_CORE 2193 | } else if (value.GetType().IsEnum) { 2194 | #else 2195 | } else if (value.GetType().GetTypeInfo().IsEnum) { 2196 | #endif 2197 | SQLite3.BindInt (stmt, index, Convert.ToInt32 (value)); 2198 | } else if (value is byte[]){ 2199 | SQLite3.BindBlob(stmt, index, (byte[]) value, ((byte[]) value).Length, NegativePointer); 2200 | } else if (value is Guid) { 2201 | SQLite3.BindText(stmt, index, ((Guid)value).ToString(), 72, NegativePointer); 2202 | } else { 2203 | throw new NotSupportedException("Cannot store type: " + value.GetType()); 2204 | } 2205 | } 2206 | } 2207 | 2208 | class Binding 2209 | { 2210 | public string Name { get; set; } 2211 | 2212 | public object Value { get; set; } 2213 | 2214 | public int Index { get; set; } 2215 | } 2216 | 2217 | object ReadCol (Sqlite3Statement stmt, int index, SQLite3.ColType type, Type clrType) 2218 | { 2219 | if (type == SQLite3.ColType.Null) { 2220 | return null; 2221 | } else { 2222 | if (clrType == typeof(String)) { 2223 | return SQLite3.ColumnString (stmt, index); 2224 | } else if (clrType == typeof(Int32)) { 2225 | return (int)SQLite3.ColumnInt (stmt, index); 2226 | } else if (clrType == typeof(Boolean)) { 2227 | return SQLite3.ColumnInt (stmt, index) == 1; 2228 | } else if (clrType == typeof(double)) { 2229 | return SQLite3.ColumnDouble (stmt, index); 2230 | } else if (clrType == typeof(float)) { 2231 | return (float)SQLite3.ColumnDouble (stmt, index); 2232 | } else if (clrType == typeof(TimeSpan)) { 2233 | return new TimeSpan(SQLite3.ColumnInt64(stmt, index)); 2234 | } else if (clrType == typeof(DateTime)) { 2235 | if (_conn.StoreDateTimeAsTicks) { 2236 | return new DateTime (SQLite3.ColumnInt64 (stmt, index)); 2237 | } 2238 | else { 2239 | var text = SQLite3.ColumnString (stmt, index); 2240 | return DateTime.Parse (text); 2241 | } 2242 | } else if (clrType == typeof(DateTimeOffset)) { 2243 | return new DateTimeOffset(SQLite3.ColumnInt64 (stmt, index),TimeSpan.Zero); 2244 | #if !NETFX_CORE 2245 | } else if (clrType.IsEnum) { 2246 | #else 2247 | } else if (clrType.GetTypeInfo().IsEnum) { 2248 | #endif 2249 | return SQLite3.ColumnInt (stmt, index); 2250 | } else if (clrType == typeof(Int64)) { 2251 | return SQLite3.ColumnInt64 (stmt, index); 2252 | } else if (clrType == typeof(UInt32)) { 2253 | return (uint)SQLite3.ColumnInt64 (stmt, index); 2254 | } else if (clrType == typeof(decimal)) { 2255 | return (decimal)SQLite3.ColumnDouble (stmt, index); 2256 | } else if (clrType == typeof(Byte)) { 2257 | return (byte)SQLite3.ColumnInt (stmt, index); 2258 | } else if (clrType == typeof(UInt16)) { 2259 | return (ushort)SQLite3.ColumnInt (stmt, index); 2260 | } else if (clrType == typeof(Int16)) { 2261 | return (short)SQLite3.ColumnInt (stmt, index); 2262 | } else if (clrType == typeof(sbyte)) { 2263 | return (sbyte)SQLite3.ColumnInt (stmt, index); 2264 | } else if (clrType == typeof(byte[])) { 2265 | return SQLite3.ColumnByteArray (stmt, index); 2266 | } else if (clrType == typeof(Guid)) { 2267 | var text = SQLite3.ColumnString(stmt, index); 2268 | return new Guid(text); 2269 | } else{ 2270 | throw new NotSupportedException ("Don't know how to read " + clrType); 2271 | } 2272 | } 2273 | } 2274 | } 2275 | 2276 | /// 2277 | /// Since the insert never changed, we only need to prepare once. 2278 | /// 2279 | public class PreparedSqlLiteInsertCommand : IDisposable 2280 | { 2281 | public bool Initialized { get; set; } 2282 | 2283 | protected SQLiteConnection Connection { get; set; } 2284 | 2285 | public string CommandText { get; set; } 2286 | 2287 | protected Sqlite3Statement Statement { get; set; } 2288 | internal static readonly Sqlite3Statement NullStatement = default(Sqlite3Statement); 2289 | 2290 | internal PreparedSqlLiteInsertCommand (SQLiteConnection conn) 2291 | { 2292 | Connection = conn; 2293 | } 2294 | 2295 | public int ExecuteNonQuery (object[] source) 2296 | { 2297 | if (Connection.Trace) { 2298 | Debug.WriteLine ("Executing: " + CommandText); 2299 | } 2300 | 2301 | var r = SQLite3.Result.OK; 2302 | 2303 | if (!Initialized) { 2304 | Statement = Prepare (); 2305 | Initialized = true; 2306 | } 2307 | 2308 | //bind the values. 2309 | if (source != null) { 2310 | for (int i = 0; i < source.Length; i++) { 2311 | SQLiteCommand.BindParameter (Statement, i + 1, source [i], Connection.StoreDateTimeAsTicks); 2312 | } 2313 | } 2314 | r = SQLite3.Step (Statement); 2315 | 2316 | if (r == SQLite3.Result.Done) { 2317 | int rowsAffected = SQLite3.Changes (Connection.Handle); 2318 | SQLite3.Reset (Statement); 2319 | return rowsAffected; 2320 | } else if (r == SQLite3.Result.Error) { 2321 | string msg = SQLite3.GetErrmsg (Connection.Handle); 2322 | SQLite3.Reset (Statement); 2323 | throw SQLiteException.New (r, msg); 2324 | } else if (r == SQLite3.Result.Constraint && SQLite3.ExtendedErrCode (Connection.Handle) == SQLite3.ExtendedResult.ConstraintNotNull) { 2325 | SQLite3.Reset (Statement); 2326 | throw NotNullConstraintViolationException.New (r, SQLite3.GetErrmsg (Connection.Handle)); 2327 | } else { 2328 | SQLite3.Reset (Statement); 2329 | throw SQLiteException.New (r, r.ToString ()); 2330 | } 2331 | } 2332 | 2333 | protected virtual Sqlite3Statement Prepare () 2334 | { 2335 | var stmt = SQLite3.Prepare2 (Connection.Handle, CommandText); 2336 | return stmt; 2337 | } 2338 | 2339 | public void Dispose () 2340 | { 2341 | Dispose (true); 2342 | GC.SuppressFinalize (this); 2343 | } 2344 | 2345 | private void Dispose (bool disposing) 2346 | { 2347 | if (Statement != NullStatement) { 2348 | try { 2349 | SQLite3.Finalize (Statement); 2350 | } finally { 2351 | Statement = NullStatement; 2352 | Connection = null; 2353 | } 2354 | } 2355 | } 2356 | 2357 | ~PreparedSqlLiteInsertCommand () 2358 | { 2359 | Dispose (false); 2360 | } 2361 | } 2362 | 2363 | public abstract class BaseTableQuery 2364 | { 2365 | protected class Ordering 2366 | { 2367 | public string ColumnName { get; set; } 2368 | public bool Ascending { get; set; } 2369 | } 2370 | } 2371 | 2372 | public class TableQuery : BaseTableQuery, IEnumerable 2373 | { 2374 | public SQLiteConnection Connection { get; private set; } 2375 | 2376 | public TableMapping Table { get; private set; } 2377 | 2378 | Expression _where; 2379 | List _orderBys; 2380 | int? _limit; 2381 | int? _offset; 2382 | 2383 | BaseTableQuery _joinInner; 2384 | Expression _joinInnerKeySelector; 2385 | BaseTableQuery _joinOuter; 2386 | Expression _joinOuterKeySelector; 2387 | Expression _joinSelector; 2388 | 2389 | Expression _selector; 2390 | 2391 | TableQuery (SQLiteConnection conn, TableMapping table) 2392 | { 2393 | Connection = conn; 2394 | Table = table; 2395 | } 2396 | 2397 | public TableQuery (SQLiteConnection conn) 2398 | { 2399 | Connection = conn; 2400 | Table = Connection.GetMapping (typeof(T)); 2401 | } 2402 | 2403 | public TableQuery Clone () 2404 | { 2405 | var q = new TableQuery (Connection, Table); 2406 | q._where = _where; 2407 | q._deferred = _deferred; 2408 | if (_orderBys != null) { 2409 | q._orderBys = new List (_orderBys); 2410 | } 2411 | q._limit = _limit; 2412 | q._offset = _offset; 2413 | q._joinInner = _joinInner; 2414 | q._joinInnerKeySelector = _joinInnerKeySelector; 2415 | q._joinOuter = _joinOuter; 2416 | q._joinOuterKeySelector = _joinOuterKeySelector; 2417 | q._joinSelector = _joinSelector; 2418 | q._selector = _selector; 2419 | return q; 2420 | } 2421 | 2422 | public TableQuery Where (Expression> predExpr) 2423 | { 2424 | if (predExpr.NodeType == ExpressionType.Lambda) { 2425 | var lambda = (LambdaExpression)predExpr; 2426 | var pred = lambda.Body; 2427 | var q = Clone (); 2428 | q.AddWhere (pred); 2429 | return q; 2430 | } else { 2431 | throw new NotSupportedException ("Must be a predicate"); 2432 | } 2433 | } 2434 | 2435 | public TableQuery Take (int n) 2436 | { 2437 | var q = Clone (); 2438 | q._limit = n; 2439 | return q; 2440 | } 2441 | 2442 | public TableQuery Skip (int n) 2443 | { 2444 | var q = Clone (); 2445 | q._offset = n; 2446 | return q; 2447 | } 2448 | 2449 | public T ElementAt (int index) 2450 | { 2451 | return Skip (index).Take (1).First (); 2452 | } 2453 | 2454 | bool _deferred; 2455 | public TableQuery Deferred () 2456 | { 2457 | var q = Clone (); 2458 | q._deferred = true; 2459 | return q; 2460 | } 2461 | 2462 | public TableQuery OrderBy (Expression> orderExpr) 2463 | { 2464 | return AddOrderBy (orderExpr, true); 2465 | } 2466 | 2467 | public TableQuery OrderByDescending (Expression> orderExpr) 2468 | { 2469 | return AddOrderBy (orderExpr, false); 2470 | } 2471 | 2472 | public TableQuery ThenBy(Expression> orderExpr) 2473 | { 2474 | return AddOrderBy(orderExpr, true); 2475 | } 2476 | 2477 | public TableQuery ThenByDescending(Expression> orderExpr) 2478 | { 2479 | return AddOrderBy(orderExpr, false); 2480 | } 2481 | 2482 | private TableQuery AddOrderBy (Expression> orderExpr, bool asc) 2483 | { 2484 | if (orderExpr.NodeType == ExpressionType.Lambda) { 2485 | var lambda = (LambdaExpression)orderExpr; 2486 | 2487 | MemberExpression mem = null; 2488 | 2489 | var unary = lambda.Body as UnaryExpression; 2490 | if (unary != null && unary.NodeType == ExpressionType.Convert) { 2491 | mem = unary.Operand as MemberExpression; 2492 | } 2493 | else { 2494 | mem = lambda.Body as MemberExpression; 2495 | } 2496 | 2497 | if (mem != null && (mem.Expression.NodeType == ExpressionType.Parameter)) { 2498 | var q = Clone (); 2499 | if (q._orderBys == null) { 2500 | q._orderBys = new List (); 2501 | } 2502 | q._orderBys.Add (new Ordering { 2503 | ColumnName = Table.FindColumnWithPropertyName(mem.Member.Name).Name, 2504 | Ascending = asc 2505 | }); 2506 | return q; 2507 | } else { 2508 | throw new NotSupportedException ("Order By does not support: " + orderExpr); 2509 | } 2510 | } else { 2511 | throw new NotSupportedException ("Must be a predicate"); 2512 | } 2513 | } 2514 | 2515 | private void AddWhere (Expression pred) 2516 | { 2517 | if (_where == null) { 2518 | _where = pred; 2519 | } else { 2520 | _where = Expression.AndAlso (_where, pred); 2521 | } 2522 | } 2523 | 2524 | public TableQuery Join ( 2525 | TableQuery inner, 2526 | Expression> outerKeySelector, 2527 | Expression> innerKeySelector, 2528 | Expression> resultSelector) 2529 | { 2530 | var q = new TableQuery (Connection, Connection.GetMapping (typeof (TResult))) { 2531 | _joinOuter = this, 2532 | _joinOuterKeySelector = outerKeySelector, 2533 | _joinInner = inner, 2534 | _joinInnerKeySelector = innerKeySelector, 2535 | _joinSelector = resultSelector, 2536 | }; 2537 | return q; 2538 | } 2539 | 2540 | public TableQuery Select (Expression> selector) 2541 | { 2542 | var q = Clone (); 2543 | q._selector = selector; 2544 | return q; 2545 | } 2546 | 2547 | private SQLiteCommand GenerateCommand (string selectionList) 2548 | { 2549 | if (_joinInner != null && _joinOuter != null) { 2550 | throw new NotSupportedException ("Joins are not supported."); 2551 | } 2552 | else { 2553 | var cmdText = "select " + selectionList + " from \"" + Table.TableName + "\""; 2554 | var args = new List (); 2555 | if (_where != null) { 2556 | var w = CompileExpr (_where, args); 2557 | cmdText += " where " + w.CommandText; 2558 | } 2559 | if ((_orderBys != null) && (_orderBys.Count > 0)) { 2560 | var t = string.Join (", ", _orderBys.Select (o => "\"" + o.ColumnName + "\"" + (o.Ascending ? "" : " desc")).ToArray ()); 2561 | cmdText += " order by " + t; 2562 | } 2563 | if (_limit.HasValue) { 2564 | cmdText += " limit " + _limit.Value; 2565 | } 2566 | if (_offset.HasValue) { 2567 | if (!_limit.HasValue) { 2568 | cmdText += " limit -1 "; 2569 | } 2570 | cmdText += " offset " + _offset.Value; 2571 | } 2572 | return Connection.CreateCommand (cmdText, args.ToArray ()); 2573 | } 2574 | } 2575 | 2576 | class CompileResult 2577 | { 2578 | public string CommandText { get; set; } 2579 | 2580 | public object Value { get; set; } 2581 | } 2582 | 2583 | private CompileResult CompileExpr (Expression expr, List queryArgs) 2584 | { 2585 | if (expr == null) { 2586 | throw new NotSupportedException ("Expression is NULL"); 2587 | } else if (expr is BinaryExpression) { 2588 | var bin = (BinaryExpression)expr; 2589 | 2590 | var leftr = CompileExpr (bin.Left, queryArgs); 2591 | var rightr = CompileExpr (bin.Right, queryArgs); 2592 | 2593 | //If either side is a parameter and is null, then handle the other side specially (for "is null"/"is not null") 2594 | string text; 2595 | if (leftr.CommandText == "?" && leftr.Value == null) 2596 | text = CompileNullBinaryExpression(bin, rightr); 2597 | else if (rightr.CommandText == "?" && rightr.Value == null) 2598 | text = CompileNullBinaryExpression(bin, leftr); 2599 | else 2600 | text = "(" + leftr.CommandText + " " + GetSqlName(bin) + " " + rightr.CommandText + ")"; 2601 | return new CompileResult { CommandText = text }; 2602 | } else if (expr.NodeType == ExpressionType.Call) { 2603 | 2604 | var call = (MethodCallExpression)expr; 2605 | var args = new CompileResult[call.Arguments.Count]; 2606 | var obj = call.Object != null ? CompileExpr (call.Object, queryArgs) : null; 2607 | 2608 | for (var i = 0; i < args.Length; i++) { 2609 | args [i] = CompileExpr (call.Arguments [i], queryArgs); 2610 | } 2611 | 2612 | var sqlCall = ""; 2613 | 2614 | if (call.Method.Name == "Like" && args.Length == 2) { 2615 | sqlCall = "(" + args [0].CommandText + " like " + args [1].CommandText + ")"; 2616 | } 2617 | else if (call.Method.Name == "Contains" && args.Length == 2) { 2618 | sqlCall = "(" + args [1].CommandText + " in " + args [0].CommandText + ")"; 2619 | } 2620 | else if (call.Method.Name == "Contains" && args.Length == 1) { 2621 | if (call.Object != null && call.Object.Type == typeof(string)) { 2622 | sqlCall = "(" + obj.CommandText + " like ('%' || " + args [0].CommandText + " || '%'))"; 2623 | } 2624 | else { 2625 | sqlCall = "(" + args [0].CommandText + " in " + obj.CommandText + ")"; 2626 | } 2627 | } 2628 | else if (call.Method.Name == "StartsWith" && args.Length == 1) { 2629 | sqlCall = "(" + obj.CommandText + " like (" + args [0].CommandText + " || '%'))"; 2630 | } 2631 | else if (call.Method.Name == "EndsWith" && args.Length == 1) { 2632 | sqlCall = "(" + obj.CommandText + " like ('%' || " + args [0].CommandText + "))"; 2633 | } 2634 | else if (call.Method.Name == "Equals" && args.Length == 1) { 2635 | sqlCall = "(" + obj.CommandText + " = (" + args[0].CommandText + "))"; 2636 | } else if (call.Method.Name == "ToLower") { 2637 | sqlCall = "(lower(" + obj.CommandText + "))"; 2638 | } else if (call.Method.Name == "ToUpper") { 2639 | sqlCall = "(upper(" + obj.CommandText + "))"; 2640 | } else { 2641 | sqlCall = call.Method.Name.ToLower () + "(" + string.Join (",", args.Select (a => a.CommandText).ToArray ()) + ")"; 2642 | } 2643 | return new CompileResult { CommandText = sqlCall }; 2644 | 2645 | } else if (expr.NodeType == ExpressionType.Constant) { 2646 | var c = (ConstantExpression)expr; 2647 | queryArgs.Add (c.Value); 2648 | return new CompileResult { 2649 | CommandText = "?", 2650 | Value = c.Value 2651 | }; 2652 | } else if (expr.NodeType == ExpressionType.Convert) { 2653 | var u = (UnaryExpression)expr; 2654 | var ty = u.Type; 2655 | var valr = CompileExpr (u.Operand, queryArgs); 2656 | return new CompileResult { 2657 | CommandText = valr.CommandText, 2658 | Value = valr.Value != null ? ConvertTo (valr.Value, ty) : null 2659 | }; 2660 | } else if (expr.NodeType == ExpressionType.MemberAccess) { 2661 | var mem = (MemberExpression)expr; 2662 | 2663 | if (mem.Expression!=null && mem.Expression.NodeType == ExpressionType.Parameter) { 2664 | // 2665 | // This is a column of our table, output just the column name 2666 | // Need to translate it if that column name is mapped 2667 | // 2668 | var columnName = Table.FindColumnWithPropertyName (mem.Member.Name).Name; 2669 | return new CompileResult { CommandText = "\"" + columnName + "\"" }; 2670 | } else { 2671 | object obj = null; 2672 | if (mem.Expression != null) { 2673 | var r = CompileExpr (mem.Expression, queryArgs); 2674 | if (r.Value == null) { 2675 | throw new NotSupportedException ("Member access failed to compile expression"); 2676 | } 2677 | if (r.CommandText == "?") { 2678 | queryArgs.RemoveAt (queryArgs.Count - 1); 2679 | } 2680 | obj = r.Value; 2681 | } 2682 | 2683 | // 2684 | // Get the member value 2685 | // 2686 | object val = null; 2687 | 2688 | #if !NETFX_CORE 2689 | if (mem.Member.MemberType == MemberTypes.Property) { 2690 | #else 2691 | if (mem.Member is PropertyInfo) { 2692 | #endif 2693 | var m = (PropertyInfo)mem.Member; 2694 | val = m.GetValue (obj, null); 2695 | #if !NETFX_CORE 2696 | } else if (mem.Member.MemberType == MemberTypes.Field) { 2697 | #else 2698 | } else if (mem.Member is FieldInfo) { 2699 | #endif 2700 | #if SILVERLIGHT 2701 | val = Expression.Lambda (expr).Compile ().DynamicInvoke (); 2702 | #else 2703 | var m = (FieldInfo)mem.Member; 2704 | val = m.GetValue (obj); 2705 | #endif 2706 | } else { 2707 | #if !NETFX_CORE 2708 | throw new NotSupportedException ("MemberExpr: " + mem.Member.MemberType); 2709 | #else 2710 | throw new NotSupportedException ("MemberExpr: " + mem.Member.DeclaringType); 2711 | #endif 2712 | } 2713 | 2714 | // 2715 | // Work special magic for enumerables 2716 | // 2717 | if (val != null && val is System.Collections.IEnumerable && !(val is string) && !(val is System.Collections.Generic.IEnumerable)) { 2718 | var sb = new System.Text.StringBuilder(); 2719 | sb.Append("("); 2720 | var head = ""; 2721 | foreach (var a in (System.Collections.IEnumerable)val) { 2722 | queryArgs.Add(a); 2723 | sb.Append(head); 2724 | sb.Append("?"); 2725 | head = ","; 2726 | } 2727 | sb.Append(")"); 2728 | return new CompileResult { 2729 | CommandText = sb.ToString(), 2730 | Value = val 2731 | }; 2732 | } 2733 | else { 2734 | queryArgs.Add (val); 2735 | return new CompileResult { 2736 | CommandText = "?", 2737 | Value = val 2738 | }; 2739 | } 2740 | } 2741 | } 2742 | throw new NotSupportedException ("Cannot compile: " + expr.NodeType.ToString ()); 2743 | } 2744 | 2745 | static object ConvertTo (object obj, Type t) 2746 | { 2747 | Type nut = Nullable.GetUnderlyingType(t); 2748 | 2749 | if (nut != null) { 2750 | if (obj == null) return null; 2751 | return Convert.ChangeType (obj, nut); 2752 | } else { 2753 | return Convert.ChangeType (obj, t); 2754 | } 2755 | } 2756 | 2757 | /// 2758 | /// Compiles a BinaryExpression where one of the parameters is null. 2759 | /// 2760 | /// The non-null parameter 2761 | private string CompileNullBinaryExpression(BinaryExpression expression, CompileResult parameter) 2762 | { 2763 | if (expression.NodeType == ExpressionType.Equal) 2764 | return "(" + parameter.CommandText + " is ?)"; 2765 | else if (expression.NodeType == ExpressionType.NotEqual) 2766 | return "(" + parameter.CommandText + " is not ?)"; 2767 | else 2768 | throw new NotSupportedException("Cannot compile Null-BinaryExpression with type " + expression.NodeType.ToString()); 2769 | } 2770 | 2771 | string GetSqlName (Expression expr) 2772 | { 2773 | var n = expr.NodeType; 2774 | if (n == ExpressionType.GreaterThan) 2775 | return ">"; else if (n == ExpressionType.GreaterThanOrEqual) { 2776 | return ">="; 2777 | } else if (n == ExpressionType.LessThan) { 2778 | return "<"; 2779 | } else if (n == ExpressionType.LessThanOrEqual) { 2780 | return "<="; 2781 | } else if (n == ExpressionType.And) { 2782 | return "&"; 2783 | } else if (n == ExpressionType.AndAlso) { 2784 | return "and"; 2785 | } else if (n == ExpressionType.Or) { 2786 | return "|"; 2787 | } else if (n == ExpressionType.OrElse) { 2788 | return "or"; 2789 | } else if (n == ExpressionType.Equal) { 2790 | return "="; 2791 | } else if (n == ExpressionType.NotEqual) { 2792 | return "!="; 2793 | } else { 2794 | throw new NotSupportedException ("Cannot get SQL for: " + n); 2795 | } 2796 | } 2797 | 2798 | public int Count () 2799 | { 2800 | return GenerateCommand("count(*)").ExecuteScalar (); 2801 | } 2802 | 2803 | public int Count (Expression> predExpr) 2804 | { 2805 | return Where (predExpr).Count (); 2806 | } 2807 | 2808 | public IEnumerator GetEnumerator () 2809 | { 2810 | if (!_deferred) 2811 | return GenerateCommand("*").ExecuteQuery().GetEnumerator(); 2812 | 2813 | return GenerateCommand("*").ExecuteDeferredQuery().GetEnumerator(); 2814 | } 2815 | 2816 | System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator () 2817 | { 2818 | return GetEnumerator (); 2819 | } 2820 | 2821 | public T First () 2822 | { 2823 | var query = Take (1); 2824 | return query.ToList().First (); 2825 | } 2826 | 2827 | public T FirstOrDefault () 2828 | { 2829 | var query = Take (1); 2830 | return query.ToList().FirstOrDefault (); 2831 | } 2832 | } 2833 | 2834 | public static class SQLite3 2835 | { 2836 | public enum Result : int 2837 | { 2838 | OK = 0, 2839 | Error = 1, 2840 | Internal = 2, 2841 | Perm = 3, 2842 | Abort = 4, 2843 | Busy = 5, 2844 | Locked = 6, 2845 | NoMem = 7, 2846 | ReadOnly = 8, 2847 | Interrupt = 9, 2848 | IOError = 10, 2849 | Corrupt = 11, 2850 | NotFound = 12, 2851 | Full = 13, 2852 | CannotOpen = 14, 2853 | LockErr = 15, 2854 | Empty = 16, 2855 | SchemaChngd = 17, 2856 | TooBig = 18, 2857 | Constraint = 19, 2858 | Mismatch = 20, 2859 | Misuse = 21, 2860 | NotImplementedLFS = 22, 2861 | AccessDenied = 23, 2862 | Format = 24, 2863 | Range = 25, 2864 | NonDBFile = 26, 2865 | Notice = 27, 2866 | Warning = 28, 2867 | Row = 100, 2868 | Done = 101 2869 | } 2870 | 2871 | public enum ExtendedResult : int 2872 | { 2873 | IOErrorRead = (Result.IOError | (1 << 8)), 2874 | IOErrorShortRead = (Result.IOError | (2 << 8)), 2875 | IOErrorWrite = (Result.IOError | (3 << 8)), 2876 | IOErrorFsync = (Result.IOError | (4 << 8)), 2877 | IOErrorDirFSync = (Result.IOError | (5 << 8)), 2878 | IOErrorTruncate = (Result.IOError | (6 << 8)), 2879 | IOErrorFStat = (Result.IOError | (7 << 8)), 2880 | IOErrorUnlock = (Result.IOError | (8 << 8)), 2881 | IOErrorRdlock = (Result.IOError | (9 << 8)), 2882 | IOErrorDelete = (Result.IOError | (10 << 8)), 2883 | IOErrorBlocked = (Result.IOError | (11 << 8)), 2884 | IOErrorNoMem = (Result.IOError | (12 << 8)), 2885 | IOErrorAccess = (Result.IOError | (13 << 8)), 2886 | IOErrorCheckReservedLock = (Result.IOError | (14 << 8)), 2887 | IOErrorLock = (Result.IOError | (15 << 8)), 2888 | IOErrorClose = (Result.IOError | (16 << 8)), 2889 | IOErrorDirClose = (Result.IOError | (17 << 8)), 2890 | IOErrorSHMOpen = (Result.IOError | (18 << 8)), 2891 | IOErrorSHMSize = (Result.IOError | (19 << 8)), 2892 | IOErrorSHMLock = (Result.IOError | (20 << 8)), 2893 | IOErrorSHMMap = (Result.IOError | (21 << 8)), 2894 | IOErrorSeek = (Result.IOError | (22 << 8)), 2895 | IOErrorDeleteNoEnt = (Result.IOError | (23 << 8)), 2896 | IOErrorMMap = (Result.IOError | (24 << 8)), 2897 | LockedSharedcache = (Result.Locked | (1 << 8)), 2898 | BusyRecovery = (Result.Busy | (1 << 8)), 2899 | CannottOpenNoTempDir = (Result.CannotOpen | (1 << 8)), 2900 | CannotOpenIsDir = (Result.CannotOpen | (2 << 8)), 2901 | CannotOpenFullPath = (Result.CannotOpen | (3 << 8)), 2902 | CorruptVTab = (Result.Corrupt | (1 << 8)), 2903 | ReadonlyRecovery = (Result.ReadOnly | (1 << 8)), 2904 | ReadonlyCannotLock = (Result.ReadOnly | (2 << 8)), 2905 | ReadonlyRollback = (Result.ReadOnly | (3 << 8)), 2906 | AbortRollback = (Result.Abort | (2 << 8)), 2907 | ConstraintCheck = (Result.Constraint | (1 << 8)), 2908 | ConstraintCommitHook = (Result.Constraint | (2 << 8)), 2909 | ConstraintForeignKey = (Result.Constraint | (3 << 8)), 2910 | ConstraintFunction = (Result.Constraint | (4 << 8)), 2911 | ConstraintNotNull = (Result.Constraint | (5 << 8)), 2912 | ConstraintPrimaryKey = (Result.Constraint | (6 << 8)), 2913 | ConstraintTrigger = (Result.Constraint | (7 << 8)), 2914 | ConstraintUnique = (Result.Constraint | (8 << 8)), 2915 | ConstraintVTab = (Result.Constraint | (9 << 8)), 2916 | NoticeRecoverWAL = (Result.Notice | (1 << 8)), 2917 | NoticeRecoverRollback = (Result.Notice | (2 << 8)) 2918 | } 2919 | 2920 | 2921 | public enum ConfigOption : int 2922 | { 2923 | SingleThread = 1, 2924 | MultiThread = 2, 2925 | Serialized = 3 2926 | } 2927 | 2928 | #if !USE_CSHARP_SQLITE && !USE_WP8_NATIVE_SQLITE 2929 | [DllImport("sqlite3", EntryPoint = "sqlite3_open", CallingConvention=CallingConvention.Cdecl)] 2930 | public static extern Result Open ([MarshalAs(UnmanagedType.LPStr)] string filename, out IntPtr db); 2931 | 2932 | [DllImport("sqlite3", EntryPoint = "sqlite3_open_v2", CallingConvention=CallingConvention.Cdecl)] 2933 | public static extern Result Open ([MarshalAs(UnmanagedType.LPStr)] string filename, out IntPtr db, int flags, IntPtr zvfs); 2934 | 2935 | [DllImport("sqlite3", EntryPoint = "sqlite3_open_v2", CallingConvention = CallingConvention.Cdecl)] 2936 | public static extern Result Open(byte[] filename, out IntPtr db, int flags, IntPtr zvfs); 2937 | 2938 | [DllImport("sqlite3", EntryPoint = "sqlite3_open16", CallingConvention = CallingConvention.Cdecl)] 2939 | public static extern Result Open16([MarshalAs(UnmanagedType.LPWStr)] string filename, out IntPtr db); 2940 | 2941 | [DllImport("sqlite3", EntryPoint = "sqlite3_enable_load_extension", CallingConvention=CallingConvention.Cdecl)] 2942 | public static extern Result EnableLoadExtension (IntPtr db, int onoff); 2943 | 2944 | [DllImport("sqlite3", EntryPoint = "sqlite3_close", CallingConvention=CallingConvention.Cdecl)] 2945 | public static extern Result Close (IntPtr db); 2946 | 2947 | [DllImport("sqlite3", EntryPoint = "sqlite3_initialize", CallingConvention=CallingConvention.Cdecl)] 2948 | public static extern Result Initialize(); 2949 | 2950 | [DllImport("sqlite3", EntryPoint = "sqlite3_shutdown", CallingConvention=CallingConvention.Cdecl)] 2951 | public static extern Result Shutdown(); 2952 | 2953 | [DllImport("sqlite3", EntryPoint = "sqlite3_config", CallingConvention=CallingConvention.Cdecl)] 2954 | public static extern Result Config (ConfigOption option); 2955 | 2956 | [DllImport("sqlite3", EntryPoint = "sqlite3_win32_set_directory", CallingConvention=CallingConvention.Cdecl, CharSet=CharSet.Unicode)] 2957 | public static extern int SetDirectory (uint directoryType, string directoryPath); 2958 | 2959 | [DllImport("sqlite3", EntryPoint = "sqlite3_busy_timeout", CallingConvention=CallingConvention.Cdecl)] 2960 | public static extern Result BusyTimeout (IntPtr db, int milliseconds); 2961 | 2962 | [DllImport("sqlite3", EntryPoint = "sqlite3_changes", CallingConvention=CallingConvention.Cdecl)] 2963 | public static extern int Changes (IntPtr db); 2964 | 2965 | [DllImport("sqlite3", EntryPoint = "sqlite3_prepare_v2", CallingConvention=CallingConvention.Cdecl)] 2966 | public static extern Result Prepare2 (IntPtr db, [MarshalAs(UnmanagedType.LPStr)] string sql, int numBytes, out IntPtr stmt, IntPtr pzTail); 2967 | 2968 | #if NETFX_CORE 2969 | [DllImport ("sqlite3", EntryPoint = "sqlite3_prepare_v2", CallingConvention = CallingConvention.Cdecl)] 2970 | public static extern Result Prepare2 (IntPtr db, byte[] queryBytes, int numBytes, out IntPtr stmt, IntPtr pzTail); 2971 | #endif 2972 | 2973 | public static IntPtr Prepare2 (IntPtr db, string query) 2974 | { 2975 | IntPtr stmt; 2976 | #if NETFX_CORE 2977 | byte[] queryBytes = System.Text.UTF8Encoding.UTF8.GetBytes (query); 2978 | var r = Prepare2 (db, queryBytes, queryBytes.Length, out stmt, IntPtr.Zero); 2979 | #else 2980 | var r = Prepare2 (db, query, System.Text.UTF8Encoding.UTF8.GetByteCount (query), out stmt, IntPtr.Zero); 2981 | #endif 2982 | if (r != Result.OK) { 2983 | throw SQLiteException.New (r, GetErrmsg (db)); 2984 | } 2985 | return stmt; 2986 | } 2987 | 2988 | [DllImport("sqlite3", EntryPoint = "sqlite3_step", CallingConvention=CallingConvention.Cdecl)] 2989 | public static extern Result Step (IntPtr stmt); 2990 | 2991 | [DllImport("sqlite3", EntryPoint = "sqlite3_reset", CallingConvention=CallingConvention.Cdecl)] 2992 | public static extern Result Reset (IntPtr stmt); 2993 | 2994 | [DllImport("sqlite3", EntryPoint = "sqlite3_finalize", CallingConvention=CallingConvention.Cdecl)] 2995 | public static extern Result Finalize (IntPtr stmt); 2996 | 2997 | [DllImport("sqlite3", EntryPoint = "sqlite3_last_insert_rowid", CallingConvention=CallingConvention.Cdecl)] 2998 | public static extern long LastInsertRowid (IntPtr db); 2999 | 3000 | [DllImport("sqlite3", EntryPoint = "sqlite3_errmsg16", CallingConvention=CallingConvention.Cdecl)] 3001 | public static extern IntPtr Errmsg (IntPtr db); 3002 | 3003 | public static string GetErrmsg (IntPtr db) 3004 | { 3005 | return Marshal.PtrToStringUni (Errmsg (db)); 3006 | } 3007 | 3008 | [DllImport("sqlite3", EntryPoint = "sqlite3_bind_parameter_index", CallingConvention=CallingConvention.Cdecl)] 3009 | public static extern int BindParameterIndex (IntPtr stmt, [MarshalAs(UnmanagedType.LPStr)] string name); 3010 | 3011 | [DllImport("sqlite3", EntryPoint = "sqlite3_bind_null", CallingConvention=CallingConvention.Cdecl)] 3012 | public static extern int BindNull (IntPtr stmt, int index); 3013 | 3014 | [DllImport("sqlite3", EntryPoint = "sqlite3_bind_int", CallingConvention=CallingConvention.Cdecl)] 3015 | public static extern int BindInt (IntPtr stmt, int index, int val); 3016 | 3017 | [DllImport("sqlite3", EntryPoint = "sqlite3_bind_int64", CallingConvention=CallingConvention.Cdecl)] 3018 | public static extern int BindInt64 (IntPtr stmt, int index, long val); 3019 | 3020 | [DllImport("sqlite3", EntryPoint = "sqlite3_bind_double", CallingConvention=CallingConvention.Cdecl)] 3021 | public static extern int BindDouble (IntPtr stmt, int index, double val); 3022 | 3023 | [DllImport("sqlite3", EntryPoint = "sqlite3_bind_text16", CallingConvention=CallingConvention.Cdecl, CharSet = CharSet.Unicode)] 3024 | public static extern int BindText (IntPtr stmt, int index, [MarshalAs(UnmanagedType.LPWStr)] string val, int n, IntPtr free); 3025 | 3026 | [DllImport("sqlite3", EntryPoint = "sqlite3_bind_blob", CallingConvention=CallingConvention.Cdecl)] 3027 | public static extern int BindBlob (IntPtr stmt, int index, byte[] val, int n, IntPtr free); 3028 | 3029 | [DllImport("sqlite3", EntryPoint = "sqlite3_column_count", CallingConvention=CallingConvention.Cdecl)] 3030 | public static extern int ColumnCount (IntPtr stmt); 3031 | 3032 | [DllImport("sqlite3", EntryPoint = "sqlite3_column_name", CallingConvention=CallingConvention.Cdecl)] 3033 | public static extern IntPtr ColumnName (IntPtr stmt, int index); 3034 | 3035 | [DllImport("sqlite3", EntryPoint = "sqlite3_column_name16", CallingConvention=CallingConvention.Cdecl)] 3036 | static extern IntPtr ColumnName16Internal (IntPtr stmt, int index); 3037 | public static string ColumnName16(IntPtr stmt, int index) 3038 | { 3039 | return Marshal.PtrToStringUni(ColumnName16Internal(stmt, index)); 3040 | } 3041 | 3042 | [DllImport("sqlite3", EntryPoint = "sqlite3_column_type", CallingConvention=CallingConvention.Cdecl)] 3043 | public static extern ColType ColumnType (IntPtr stmt, int index); 3044 | 3045 | [DllImport("sqlite3", EntryPoint = "sqlite3_column_int", CallingConvention=CallingConvention.Cdecl)] 3046 | public static extern int ColumnInt (IntPtr stmt, int index); 3047 | 3048 | [DllImport("sqlite3", EntryPoint = "sqlite3_column_int64", CallingConvention=CallingConvention.Cdecl)] 3049 | public static extern long ColumnInt64 (IntPtr stmt, int index); 3050 | 3051 | [DllImport("sqlite3", EntryPoint = "sqlite3_column_double", CallingConvention=CallingConvention.Cdecl)] 3052 | public static extern double ColumnDouble (IntPtr stmt, int index); 3053 | 3054 | [DllImport("sqlite3", EntryPoint = "sqlite3_column_text", CallingConvention=CallingConvention.Cdecl)] 3055 | public static extern IntPtr ColumnText (IntPtr stmt, int index); 3056 | 3057 | [DllImport("sqlite3", EntryPoint = "sqlite3_column_text16", CallingConvention=CallingConvention.Cdecl)] 3058 | public static extern IntPtr ColumnText16 (IntPtr stmt, int index); 3059 | 3060 | [DllImport("sqlite3", EntryPoint = "sqlite3_column_blob", CallingConvention=CallingConvention.Cdecl)] 3061 | public static extern IntPtr ColumnBlob (IntPtr stmt, int index); 3062 | 3063 | [DllImport("sqlite3", EntryPoint = "sqlite3_column_bytes", CallingConvention=CallingConvention.Cdecl)] 3064 | public static extern int ColumnBytes (IntPtr stmt, int index); 3065 | 3066 | public static string ColumnString (IntPtr stmt, int index) 3067 | { 3068 | return Marshal.PtrToStringUni (SQLite3.ColumnText16 (stmt, index)); 3069 | } 3070 | 3071 | public static byte[] ColumnByteArray (IntPtr stmt, int index) 3072 | { 3073 | int length = ColumnBytes (stmt, index); 3074 | var result = new byte[length]; 3075 | if (length > 0) 3076 | Marshal.Copy (ColumnBlob (stmt, index), result, 0, length); 3077 | return result; 3078 | } 3079 | 3080 | [DllImport ("sqlite3", EntryPoint = "sqlite3_extended_errcode", CallingConvention = CallingConvention.Cdecl)] 3081 | public static extern ExtendedResult ExtendedErrCode (IntPtr db); 3082 | 3083 | [DllImport ("sqlite3", EntryPoint = "sqlite3_libversion_number", CallingConvention = CallingConvention.Cdecl)] 3084 | public static extern int LibVersionNumber (); 3085 | #else 3086 | public static Result Open(string filename, out Sqlite3DatabaseHandle db) 3087 | { 3088 | return (Result) Sqlite3.sqlite3_open(filename, out db); 3089 | } 3090 | 3091 | public static Result Open(string filename, out Sqlite3DatabaseHandle db, int flags, IntPtr zVfs) 3092 | { 3093 | #if USE_WP8_NATIVE_SQLITE 3094 | return (Result)Sqlite3.sqlite3_open_v2(filename, out db, flags, ""); 3095 | #else 3096 | return (Result)Sqlite3.sqlite3_open_v2(filename, out db, flags, null); 3097 | #endif 3098 | } 3099 | 3100 | public static Result Close(Sqlite3DatabaseHandle db) 3101 | { 3102 | return (Result)Sqlite3.sqlite3_close(db); 3103 | } 3104 | 3105 | public static Result BusyTimeout(Sqlite3DatabaseHandle db, int milliseconds) 3106 | { 3107 | return (Result)Sqlite3.sqlite3_busy_timeout(db, milliseconds); 3108 | } 3109 | 3110 | public static int Changes(Sqlite3DatabaseHandle db) 3111 | { 3112 | return Sqlite3.sqlite3_changes(db); 3113 | } 3114 | 3115 | public static Sqlite3Statement Prepare2(Sqlite3DatabaseHandle db, string query) 3116 | { 3117 | Sqlite3Statement stmt = default(Sqlite3Statement); 3118 | #if USE_WP8_NATIVE_SQLITE 3119 | var r = Sqlite3.sqlite3_prepare_v2(db, query, out stmt); 3120 | #else 3121 | stmt = new Sqlite3Statement(); 3122 | var r = Sqlite3.sqlite3_prepare_v2(db, query, -1, ref stmt, 0); 3123 | #endif 3124 | if (r != 0) 3125 | { 3126 | throw SQLiteException.New((Result)r, GetErrmsg(db)); 3127 | } 3128 | return stmt; 3129 | } 3130 | 3131 | public static Result Step(Sqlite3Statement stmt) 3132 | { 3133 | return (Result)Sqlite3.sqlite3_step(stmt); 3134 | } 3135 | 3136 | public static Result Reset(Sqlite3Statement stmt) 3137 | { 3138 | return (Result)Sqlite3.sqlite3_reset(stmt); 3139 | } 3140 | 3141 | public static Result Finalize(Sqlite3Statement stmt) 3142 | { 3143 | return (Result)Sqlite3.sqlite3_finalize(stmt); 3144 | } 3145 | 3146 | public static long LastInsertRowid(Sqlite3DatabaseHandle db) 3147 | { 3148 | return Sqlite3.sqlite3_last_insert_rowid(db); 3149 | } 3150 | 3151 | public static string GetErrmsg(Sqlite3DatabaseHandle db) 3152 | { 3153 | return Sqlite3.sqlite3_errmsg(db); 3154 | } 3155 | 3156 | public static int BindParameterIndex(Sqlite3Statement stmt, string name) 3157 | { 3158 | return Sqlite3.sqlite3_bind_parameter_index(stmt, name); 3159 | } 3160 | 3161 | public static int BindNull(Sqlite3Statement stmt, int index) 3162 | { 3163 | return Sqlite3.sqlite3_bind_null(stmt, index); 3164 | } 3165 | 3166 | public static int BindInt(Sqlite3Statement stmt, int index, int val) 3167 | { 3168 | return Sqlite3.sqlite3_bind_int(stmt, index, val); 3169 | } 3170 | 3171 | public static int BindInt64(Sqlite3Statement stmt, int index, long val) 3172 | { 3173 | return Sqlite3.sqlite3_bind_int64(stmt, index, val); 3174 | } 3175 | 3176 | public static int BindDouble(Sqlite3Statement stmt, int index, double val) 3177 | { 3178 | return Sqlite3.sqlite3_bind_double(stmt, index, val); 3179 | } 3180 | 3181 | public static int BindText(Sqlite3Statement stmt, int index, string val, int n, IntPtr free) 3182 | { 3183 | #if USE_WP8_NATIVE_SQLITE 3184 | return Sqlite3.sqlite3_bind_text(stmt, index, val, n); 3185 | #else 3186 | return Sqlite3.sqlite3_bind_text(stmt, index, val, n, null); 3187 | #endif 3188 | } 3189 | 3190 | public static int BindBlob(Sqlite3Statement stmt, int index, byte[] val, int n, IntPtr free) 3191 | { 3192 | #if USE_WP8_NATIVE_SQLITE 3193 | return Sqlite3.sqlite3_bind_blob(stmt, index, val, n); 3194 | #else 3195 | return Sqlite3.sqlite3_bind_blob(stmt, index, val, n, null); 3196 | #endif 3197 | } 3198 | 3199 | public static int ColumnCount(Sqlite3Statement stmt) 3200 | { 3201 | return Sqlite3.sqlite3_column_count(stmt); 3202 | } 3203 | 3204 | public static string ColumnName(Sqlite3Statement stmt, int index) 3205 | { 3206 | return Sqlite3.sqlite3_column_name(stmt, index); 3207 | } 3208 | 3209 | public static string ColumnName16(Sqlite3Statement stmt, int index) 3210 | { 3211 | return Sqlite3.sqlite3_column_name(stmt, index); 3212 | } 3213 | 3214 | public static ColType ColumnType(Sqlite3Statement stmt, int index) 3215 | { 3216 | return (ColType)Sqlite3.sqlite3_column_type(stmt, index); 3217 | } 3218 | 3219 | public static int ColumnInt(Sqlite3Statement stmt, int index) 3220 | { 3221 | return Sqlite3.sqlite3_column_int(stmt, index); 3222 | } 3223 | 3224 | public static long ColumnInt64(Sqlite3Statement stmt, int index) 3225 | { 3226 | return Sqlite3.sqlite3_column_int64(stmt, index); 3227 | } 3228 | 3229 | public static double ColumnDouble(Sqlite3Statement stmt, int index) 3230 | { 3231 | return Sqlite3.sqlite3_column_double(stmt, index); 3232 | } 3233 | 3234 | public static string ColumnText(Sqlite3Statement stmt, int index) 3235 | { 3236 | return Sqlite3.sqlite3_column_text(stmt, index); 3237 | } 3238 | 3239 | public static string ColumnText16(Sqlite3Statement stmt, int index) 3240 | { 3241 | return Sqlite3.sqlite3_column_text(stmt, index); 3242 | } 3243 | 3244 | public static byte[] ColumnBlob(Sqlite3Statement stmt, int index) 3245 | { 3246 | return Sqlite3.sqlite3_column_blob(stmt, index); 3247 | } 3248 | 3249 | public static int ColumnBytes(Sqlite3Statement stmt, int index) 3250 | { 3251 | return Sqlite3.sqlite3_column_bytes(stmt, index); 3252 | } 3253 | 3254 | public static string ColumnString(Sqlite3Statement stmt, int index) 3255 | { 3256 | return Sqlite3.sqlite3_column_text(stmt, index); 3257 | } 3258 | 3259 | public static byte[] ColumnByteArray(Sqlite3Statement stmt, int index) 3260 | { 3261 | return ColumnBlob(stmt, index); 3262 | } 3263 | 3264 | public static Result EnableLoadExtension(Sqlite3DatabaseHandle db, int onoff) 3265 | { 3266 | return (Result)Sqlite3.sqlite3_enable_load_extension(db, onoff); 3267 | } 3268 | 3269 | private static MethodInfo _sqlite3_extended_errcode_Method = typeof(Sqlite3).GetMethod("sqlite3_extended_errcode", BindingFlags.NonPublic | BindingFlags.Static); 3270 | private static Func _sqlite3_extended_errcode_DM = (Func)Delegate.CreateDelegate(typeof(Func), _sqlite3_extended_errcode_Method); 3271 | public static ExtendedResult ExtendedErrCode(Sqlite3DatabaseHandle db) 3272 | { 3273 | //For some reason sqlite3_extended_errcode is marked as private, so lets do evil things to fix that 3274 | int result; 3275 | 3276 | try 3277 | { 3278 | result = _sqlite3_extended_errcode_DM(db); 3279 | } 3280 | catch (Exception ex) 3281 | { 3282 | Debug.Print("Unable to call Sqlite3.sqlite3_extended_errcode via reflection: {0}", ex); 3283 | result = Sqlite3.sqlite3_errcode(db); 3284 | } 3285 | 3286 | return (ExtendedResult)result;//Sqlite3.sqlite3_extended_errcode(db); 3287 | } 3288 | #endif 3289 | 3290 | public enum ColType : int 3291 | { 3292 | Integer = 1, 3293 | Float = 2, 3294 | Text = 3, 3295 | Blob = 4, 3296 | Null = 5 3297 | } 3298 | } 3299 | } 3300 | -------------------------------------------------------------------------------- /src/PersistentQueue/SQLiteAsync.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 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 | 23 | using System; 24 | using System.Collections; 25 | using System.Collections.Generic; 26 | using System.Linq; 27 | using System.Linq.Expressions; 28 | using System.Threading; 29 | using System.Threading.Tasks; 30 | 31 | namespace SQLite 32 | { 33 | public partial class SQLiteAsyncConnection 34 | { 35 | SQLiteConnectionString _connectionString; 36 | SQLiteOpenFlags _openFlags; 37 | 38 | public SQLiteAsyncConnection(string databasePath, bool storeDateTimeAsTicks = false) 39 | : this(databasePath, SQLiteOpenFlags.ReadWrite | SQLiteOpenFlags.Create, storeDateTimeAsTicks) 40 | { 41 | } 42 | 43 | public SQLiteAsyncConnection(string databasePath, SQLiteOpenFlags openFlags, bool storeDateTimeAsTicks = false) 44 | { 45 | _openFlags = openFlags; 46 | _connectionString = new SQLiteConnectionString(databasePath, storeDateTimeAsTicks); 47 | } 48 | 49 | SQLiteConnectionWithLock GetConnection () 50 | { 51 | return SQLiteConnectionPool.Shared.GetConnection (_connectionString, _openFlags); 52 | } 53 | 54 | public Task CreateTableAsync () 55 | where T : new () 56 | { 57 | return CreateTablesAsync (typeof (T)); 58 | } 59 | 60 | public Task CreateTablesAsync () 61 | where T : new () 62 | where T2 : new () 63 | { 64 | return CreateTablesAsync (typeof (T), typeof (T2)); 65 | } 66 | 67 | public Task CreateTablesAsync () 68 | where T : new () 69 | where T2 : new () 70 | where T3 : new () 71 | { 72 | return CreateTablesAsync (typeof (T), typeof (T2), typeof (T3)); 73 | } 74 | 75 | public Task CreateTablesAsync () 76 | where T : new () 77 | where T2 : new () 78 | where T3 : new () 79 | where T4 : new () 80 | { 81 | return CreateTablesAsync (typeof (T), typeof (T2), typeof (T3), typeof (T4)); 82 | } 83 | 84 | public Task CreateTablesAsync () 85 | where T : new () 86 | where T2 : new () 87 | where T3 : new () 88 | where T4 : new () 89 | where T5 : new () 90 | { 91 | return CreateTablesAsync (typeof (T), typeof (T2), typeof (T3), typeof (T4), typeof (T5)); 92 | } 93 | 94 | public Task CreateTablesAsync (params Type[] types) 95 | { 96 | return Task.Factory.StartNew (() => { 97 | CreateTablesResult result = new CreateTablesResult (); 98 | var conn = GetConnection (); 99 | using (conn.Lock ()) { 100 | foreach (Type type in types) { 101 | int aResult = conn.CreateTable (type); 102 | result.Results[type] = aResult; 103 | } 104 | } 105 | return result; 106 | }); 107 | } 108 | 109 | public Task DropTableAsync () 110 | where T : new () 111 | { 112 | return Task.Factory.StartNew (() => { 113 | var conn = GetConnection (); 114 | using (conn.Lock ()) { 115 | return conn.DropTable (); 116 | } 117 | }); 118 | } 119 | 120 | public Task InsertAsync (object item) 121 | { 122 | return Task.Factory.StartNew (() => { 123 | var conn = GetConnection (); 124 | using (conn.Lock ()) { 125 | return conn.Insert (item); 126 | } 127 | }); 128 | } 129 | 130 | public Task UpdateAsync (object item) 131 | { 132 | return Task.Factory.StartNew (() => { 133 | var conn = GetConnection (); 134 | using (conn.Lock ()) { 135 | return conn.Update (item); 136 | } 137 | }); 138 | } 139 | 140 | public Task DeleteAsync (object item) 141 | { 142 | return Task.Factory.StartNew (() => { 143 | var conn = GetConnection (); 144 | using (conn.Lock ()) { 145 | return conn.Delete (item); 146 | } 147 | }); 148 | } 149 | 150 | public Task GetAsync(object pk) 151 | where T : new() 152 | { 153 | return Task.Factory.StartNew(() => 154 | { 155 | var conn = GetConnection(); 156 | using (conn.Lock()) 157 | { 158 | return conn.Get(pk); 159 | } 160 | }); 161 | } 162 | 163 | public Task FindAsync (object pk) 164 | where T : new () 165 | { 166 | return Task.Factory.StartNew (() => { 167 | var conn = GetConnection (); 168 | using (conn.Lock ()) { 169 | return conn.Find (pk); 170 | } 171 | }); 172 | } 173 | 174 | public Task GetAsync (Expression> predicate) 175 | where T : new() 176 | { 177 | return Task.Factory.StartNew(() => 178 | { 179 | var conn = GetConnection(); 180 | using (conn.Lock()) 181 | { 182 | return conn.Get (predicate); 183 | } 184 | }); 185 | } 186 | 187 | public Task FindAsync (Expression> predicate) 188 | where T : new () 189 | { 190 | return Task.Factory.StartNew (() => { 191 | var conn = GetConnection (); 192 | using (conn.Lock ()) { 193 | return conn.Find (predicate); 194 | } 195 | }); 196 | } 197 | 198 | public Task ExecuteAsync (string query, params object[] args) 199 | { 200 | return Task.Factory.StartNew (() => { 201 | var conn = GetConnection (); 202 | using (conn.Lock ()) { 203 | return conn.Execute (query, args); 204 | } 205 | }); 206 | } 207 | 208 | public Task InsertAllAsync (IEnumerable items) 209 | { 210 | return Task.Factory.StartNew (() => { 211 | var conn = GetConnection (); 212 | using (conn.Lock ()) { 213 | return conn.InsertAll (items); 214 | } 215 | }); 216 | } 217 | 218 | public Task UpdateAllAsync (IEnumerable items) 219 | { 220 | return Task.Factory.StartNew (() => { 221 | var conn = GetConnection (); 222 | using (conn.Lock ()) { 223 | return conn.UpdateAll (items); 224 | } 225 | }); 226 | } 227 | 228 | [Obsolete("Will cause a deadlock if any call in action ends up in a different thread. Use RunInTransactionAsync(Action) instead.")] 229 | public Task RunInTransactionAsync (Action action) 230 | { 231 | return Task.Factory.StartNew (() => { 232 | var conn = this.GetConnection (); 233 | using (conn.Lock ()) { 234 | conn.BeginTransaction (); 235 | try { 236 | action (this); 237 | conn.Commit (); 238 | } 239 | catch (Exception) { 240 | conn.Rollback (); 241 | throw; 242 | } 243 | } 244 | }); 245 | } 246 | 247 | public Task RunInTransactionAsync(Action action) 248 | { 249 | return Task.Factory.StartNew(() => 250 | { 251 | var conn = this.GetConnection(); 252 | using (conn.Lock()) 253 | { 254 | conn.BeginTransaction(); 255 | try 256 | { 257 | action(conn); 258 | conn.Commit(); 259 | } 260 | catch (Exception) 261 | { 262 | conn.Rollback(); 263 | throw; 264 | } 265 | } 266 | }); 267 | } 268 | 269 | public AsyncTableQuery Table () 270 | where T : new () 271 | { 272 | // 273 | // This isn't async as the underlying connection doesn't go out to the database 274 | // until the query is performed. The Async methods are on the query iteself. 275 | // 276 | var conn = GetConnection (); 277 | return new AsyncTableQuery (conn.Table ()); 278 | } 279 | 280 | public Task ExecuteScalarAsync (string sql, params object[] args) 281 | { 282 | return Task.Factory.StartNew (() => { 283 | var conn = GetConnection (); 284 | using (conn.Lock ()) { 285 | var command = conn.CreateCommand (sql, args); 286 | return command.ExecuteScalar (); 287 | } 288 | }); 289 | } 290 | 291 | public Task> QueryAsync (string sql, params object[] args) 292 | where T : new () 293 | { 294 | return Task>.Factory.StartNew (() => { 295 | var conn = GetConnection (); 296 | using (conn.Lock ()) { 297 | return conn.Query (sql, args); 298 | } 299 | }); 300 | } 301 | } 302 | 303 | // 304 | // TODO: Bind to AsyncConnection.GetConnection instead so that delayed 305 | // execution can still work after a Pool.Reset. 306 | // 307 | public class AsyncTableQuery 308 | where T : new () 309 | { 310 | TableQuery _innerQuery; 311 | 312 | public AsyncTableQuery (TableQuery innerQuery) 313 | { 314 | _innerQuery = innerQuery; 315 | } 316 | 317 | public AsyncTableQuery Where (Expression> predExpr) 318 | { 319 | return new AsyncTableQuery (_innerQuery.Where (predExpr)); 320 | } 321 | 322 | public AsyncTableQuery Skip (int n) 323 | { 324 | return new AsyncTableQuery (_innerQuery.Skip (n)); 325 | } 326 | 327 | public AsyncTableQuery Take (int n) 328 | { 329 | return new AsyncTableQuery (_innerQuery.Take (n)); 330 | } 331 | 332 | public AsyncTableQuery OrderBy (Expression> orderExpr) 333 | { 334 | return new AsyncTableQuery (_innerQuery.OrderBy (orderExpr)); 335 | } 336 | 337 | public AsyncTableQuery OrderByDescending (Expression> orderExpr) 338 | { 339 | return new AsyncTableQuery (_innerQuery.OrderByDescending (orderExpr)); 340 | } 341 | 342 | public Task> ToListAsync () 343 | { 344 | return Task.Factory.StartNew (() => { 345 | using (((SQLiteConnectionWithLock)_innerQuery.Connection).Lock ()) { 346 | return _innerQuery.ToList (); 347 | } 348 | }); 349 | } 350 | 351 | public Task CountAsync () 352 | { 353 | return Task.Factory.StartNew (() => { 354 | using (((SQLiteConnectionWithLock)_innerQuery.Connection).Lock ()) { 355 | return _innerQuery.Count (); 356 | } 357 | }); 358 | } 359 | 360 | public Task ElementAtAsync (int index) 361 | { 362 | return Task.Factory.StartNew (() => { 363 | using (((SQLiteConnectionWithLock)_innerQuery.Connection).Lock ()) { 364 | return _innerQuery.ElementAt (index); 365 | } 366 | }); 367 | } 368 | 369 | public Task FirstAsync () 370 | { 371 | return Task.Factory.StartNew(() => { 372 | using (((SQLiteConnectionWithLock)_innerQuery.Connection).Lock ()) { 373 | return _innerQuery.First (); 374 | } 375 | }); 376 | } 377 | 378 | public Task FirstOrDefaultAsync () 379 | { 380 | return Task.Factory.StartNew(() => { 381 | using (((SQLiteConnectionWithLock)_innerQuery.Connection).Lock ()) { 382 | return _innerQuery.FirstOrDefault (); 383 | } 384 | }); 385 | } 386 | } 387 | 388 | public class CreateTablesResult 389 | { 390 | public Dictionary Results { get; private set; } 391 | 392 | internal CreateTablesResult () 393 | { 394 | this.Results = new Dictionary (); 395 | } 396 | } 397 | 398 | class SQLiteConnectionPool 399 | { 400 | class Entry 401 | { 402 | public SQLiteConnectionString ConnectionString { get; private set; } 403 | public SQLiteConnectionWithLock Connection { get; private set; } 404 | 405 | public Entry (SQLiteConnectionString connectionString, SQLiteOpenFlags openFlags) 406 | { 407 | ConnectionString = connectionString; 408 | Connection = new SQLiteConnectionWithLock (connectionString, openFlags); 409 | } 410 | 411 | public void OnApplicationSuspended () 412 | { 413 | Connection.Dispose (); 414 | Connection = null; 415 | } 416 | } 417 | 418 | readonly Dictionary _entries = new Dictionary (); 419 | readonly object _entriesLock = new object (); 420 | 421 | static readonly SQLiteConnectionPool _shared = new SQLiteConnectionPool (); 422 | 423 | /// 424 | /// Gets the singleton instance of the connection tool. 425 | /// 426 | public static SQLiteConnectionPool Shared 427 | { 428 | get 429 | { 430 | return _shared; 431 | } 432 | } 433 | 434 | public SQLiteConnectionWithLock GetConnection (SQLiteConnectionString connectionString, SQLiteOpenFlags openFlags) 435 | { 436 | lock (_entriesLock) { 437 | Entry entry; 438 | string key = connectionString.ConnectionString; 439 | 440 | if (!_entries.TryGetValue (key, out entry)) { 441 | entry = new Entry (connectionString, openFlags); 442 | _entries[key] = entry; 443 | } 444 | 445 | return entry.Connection; 446 | } 447 | } 448 | 449 | /// 450 | /// Closes all connections managed by this pool. 451 | /// 452 | public void Reset () 453 | { 454 | lock (_entriesLock) { 455 | foreach (var entry in _entries.Values) { 456 | entry.OnApplicationSuspended (); 457 | } 458 | _entries.Clear (); 459 | } 460 | } 461 | 462 | /// 463 | /// Call this method when the application is suspended. 464 | /// 465 | /// Behaviour here is to close any open connections. 466 | public void ApplicationSuspended () 467 | { 468 | Reset (); 469 | } 470 | } 471 | 472 | class SQLiteConnectionWithLock : SQLiteConnection 473 | { 474 | readonly object _lockPoint = new object (); 475 | 476 | public SQLiteConnectionWithLock (SQLiteConnectionString connectionString, SQLiteOpenFlags openFlags) 477 | : base (connectionString.DatabasePath, openFlags, connectionString.StoreDateTimeAsTicks) 478 | { 479 | } 480 | 481 | public IDisposable Lock () 482 | { 483 | return new LockWrapper (_lockPoint); 484 | } 485 | 486 | private class LockWrapper : IDisposable 487 | { 488 | object _lockPoint; 489 | 490 | public LockWrapper (object lockPoint) 491 | { 492 | _lockPoint = lockPoint; 493 | Monitor.Enter (_lockPoint); 494 | } 495 | 496 | public void Dispose () 497 | { 498 | Monitor.Exit (_lockPoint); 499 | } 500 | } 501 | } 502 | } 503 | 504 | -------------------------------------------------------------------------------- /src/PersistentQueue/app.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/PersistentQueue/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | -------------------------------------------------------------------------------- /src/Tests/CommonAndBasicTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using NUnit.Framework; 6 | using PersistentQueue; 7 | using FluentAssertions; 8 | using System.Threading; 9 | using SQLite; 10 | using ZipRecruiter; 11 | 12 | namespace Tests 13 | { 14 | /// 15 | /// Performs type checking tests 16 | /// 17 | [TestFixture] 18 | public class BasicTests 19 | { 20 | [TestFixtureSetUp] 21 | public void Init() 22 | { 23 | SQLiteShim.InjectSQLite(); 24 | } 25 | 26 | /// 27 | /// Ensures that IPersistentQueue and IPersistentQueueFactory types 28 | /// can be assigned to and used interchangeably 29 | /// 30 | [Test] 31 | public void InterfaceCovarience() 32 | { 33 | IPersistentQueue queue; 34 | IPersistentQueueFactory factory; 35 | 36 | factory = new Queue.Factory(); 37 | using (queue = factory.CreateNew()) { } 38 | 39 | factory = new FilterQueue.Factory(); 40 | using (queue = factory.CreateNew()) { }; 41 | } 42 | } 43 | 44 | /// 45 | /// An abstract class containing tests that should apply to all PersistentQueue subclasses 46 | /// 47 | [TestFixture] 48 | public abstract class CommonTests 49 | { 50 | [TestFixtureSetUp] 51 | public void Init() 52 | { 53 | SQLiteShim.InjectSQLite(); 54 | } 55 | 56 | #region Factory methods 57 | 58 | /// 59 | /// A Factory that builds the test class 60 | /// 61 | protected IPersistentQueueFactory factory; 62 | 63 | /// 64 | /// Abstract method used to build the tested Class' factory 65 | /// 66 | public abstract IPersistentQueueFactory BuildFactory(); 67 | 68 | /// 69 | /// Shortcut to call Create(string name) on the concrete test class' factory 70 | /// 71 | public virtual IPersistentQueue Create(string queueName) 72 | { 73 | return factory.Create(queueName); 74 | } 75 | 76 | /// 77 | /// Shortcut to call CreateNew() on the concrete test class' factory 78 | /// 79 | public virtual IPersistentQueue CreateNew() 80 | { 81 | return factory.CreateNew(); 82 | } 83 | 84 | /// 85 | /// Shortcut to call CreateNew(string name) on the concrete test class' factory 86 | /// 87 | public virtual IPersistentQueue CreateNew(string queueName) 88 | { 89 | return factory.CreateNew(queueName); 90 | } 91 | 92 | /// 93 | /// Performs type checks on the Interfaces returned by factory methods 94 | /// 95 | public abstract void InstanceTypeCheck(); 96 | 97 | public CommonTests() 98 | { 99 | factory = BuildFactory(); 100 | } 101 | 102 | #endregion 103 | 104 | [Test] 105 | public void ShouldQueueAndDequeueString() 106 | { 107 | using (var queue = this.CreateNew()) 108 | { 109 | var item = "woot"; 110 | 111 | queue.Enqueue(item); 112 | 113 | string dequeued = queue.Dequeue().CastTo(); 114 | dequeued.Should().Be(item); 115 | } 116 | } 117 | 118 | [Test] 119 | public void ShouldQueueAndDequeueInt() 120 | { 121 | using (var queue = this.CreateNew()) 122 | { 123 | var item = 1; 124 | 125 | queue.Enqueue(item); 126 | 127 | var dequeued = queue.Dequeue().CastTo(); 128 | dequeued.Should().Be(item); 129 | } 130 | } 131 | 132 | [Test] 133 | public void ShouldQueueAndPeekString() 134 | { 135 | using (var queue = this.CreateNew()) 136 | { 137 | var item = "woot"; 138 | 139 | queue.Enqueue(item); 140 | 141 | var peeked = queue.Peek(); 142 | peeked.Should().Be(item); 143 | } 144 | } 145 | 146 | [Test] 147 | public void ShouldHideInvisibleItemFromPeek() 148 | { 149 | using (var queue = this.CreateNew()) 150 | { 151 | var item = "woot"; 152 | 153 | queue.Enqueue(item); 154 | queue.Dequeue(false, 1000); 155 | 156 | var peeked = queue.Peek().Should().BeNull(); 157 | } 158 | } 159 | 160 | [Test] 161 | public void ShouldBeAbleToDequeueAComplexObjectAfterDisposeAndRecreation() 162 | { 163 | var queue = this.CreateNew(); 164 | var item = new ComplexObject { SomeTextProperty = "text lololo", SomeInt32Property = 123456 }; 165 | 166 | queue.Enqueue(item); 167 | 168 | queue.Dispose(); 169 | using (var newQueue = this.Create(queue.Name)) 170 | { 171 | var dequeueItem = newQueue.Dequeue(); 172 | 173 | dequeueItem.CastTo().Should().Equals(item); 174 | } 175 | } 176 | 177 | [Test] 178 | public void ShouldReturnSameQueueIfTheNameIsEqualToAnother() 179 | { 180 | using (var queue1 = this.Create("queue")) 181 | using (var queue2 = this.Create("queue")) 182 | { 183 | queue1.Should().BeSameAs(queue2); 184 | } 185 | } 186 | 187 | [Test] 188 | public void ShouldThrownExceptionTryingToCreateNewThatAlreadyExists() 189 | { 190 | using (var queue1 = this.Create("queue")) 191 | { 192 | Assert.Throws(() => this.CreateNew("queue")); 193 | } 194 | } 195 | 196 | [Test] 197 | public void ShouldReturnNullWhenQueueIsEmpty() 198 | { 199 | using (var queue = this.CreateNew()) 200 | { 201 | queue.Dequeue().Should().BeNull(); 202 | } 203 | } 204 | 205 | [Test] 206 | public void ShouldHideInvisibleMessages() 207 | { 208 | using (var queue = this.CreateNew()) 209 | { 210 | queue.Enqueue("oi"); 211 | queue.Dequeue(false, 1000); 212 | 213 | var item = queue.Dequeue(); 214 | 215 | item.Should().BeNull(); 216 | } 217 | } 218 | 219 | [Test] 220 | public void ShouldHideInvisibleMessagesUntilTimeout() 221 | { 222 | //this test, and any other that depends on the Thread.Sleep, can eventually fail... 223 | //run it again to be sure it is broken 224 | using (var queue = this.CreateNew()) 225 | { 226 | queue.Enqueue("oi"); 227 | queue.Dequeue(false, 1000); 228 | 229 | //Thread.Sleep isn't particularly precise so give this one 230 | //some breathing room. 1.5 Sec would fail ~50% of the time 231 | Thread.Sleep(2000); 232 | 233 | var item = queue.Dequeue(); 234 | 235 | item.Should().NotBeNull(); 236 | } 237 | } 238 | 239 | [Test] 240 | public void ShouldRemoveInvisibleItemWhenDeleted() 241 | { 242 | using (var queue = this.CreateNew()) 243 | { 244 | queue.Enqueue("oi"); 245 | var item = queue.Dequeue(false, 100); 246 | 247 | queue.Delete(item); 248 | 249 | Thread.Sleep(1000); 250 | 251 | queue.Dequeue().Should().BeNull(); 252 | } 253 | } 254 | 255 | [Serializable] 256 | public class ComplexObject 257 | { 258 | public string SomeTextProperty { get; set; } 259 | public int SomeInt32Property { get; set; } 260 | 261 | public override bool Equals(object obj) 262 | { 263 | if (obj is ComplexObject) 264 | { 265 | var item = obj as ComplexObject; 266 | return item.SomeInt32Property == this.SomeInt32Property 267 | && item.SomeTextProperty == this.SomeTextProperty; 268 | } 269 | 270 | return false; 271 | } 272 | } 273 | } 274 | } 275 | -------------------------------------------------------------------------------- /src/Tests/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("Tests")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("Tests")] 13 | [assembly: AssemblyCopyright("Copyright © 2012")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("bd4acc96-81ae-44ce-9a1b-1c3cfd2d4629")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | [assembly: AssemblyVersion("1.0.0.0")] 35 | [assembly: AssemblyFileVersion("1.0.0.0")] 36 | -------------------------------------------------------------------------------- /src/Tests/QueueSpecificTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using NUnit.Framework; 6 | using PersistentQueue; 7 | using FluentAssertions; 8 | using System.Threading; 9 | using SQLite; 10 | 11 | namespace Tests 12 | { 13 | [TestFixture] 14 | public class FilterQueueTests : CommonTests 15 | { 16 | public override IPersistentQueueFactory BuildFactory() 17 | { 18 | return new FilterQueue.Factory(); 19 | } 20 | 21 | [Test] 22 | public override void InstanceTypeCheck() 23 | { 24 | var className = "FilterQueue"; 25 | 26 | using (var queue = this.Create("InstanceTypeCheck_" + className)) 27 | { 28 | Assert.IsInstanceOf(typeof(FilterQueue), queue); 29 | } 30 | 31 | using (var queue = this.CreateNew("InstanceTypeCheck_" + className)) 32 | { 33 | Assert.IsInstanceOf(typeof(FilterQueue), queue); 34 | } 35 | 36 | using (var queue = this.CreateNew()) 37 | { 38 | Assert.IsInstanceOf(typeof(FilterQueue), queue); 39 | } 40 | } 41 | 42 | [Test] 43 | public void DeleteTimeShouldBeRespected() 44 | { 45 | using (var queue = this.CreateNew() as FilterQueue) 46 | { 47 | var entities = new[]{ 48 | "One", 49 | "Two", 50 | "Skipped", 51 | "Three" 52 | }; 53 | 54 | foreach (var entity in entities) 55 | { 56 | queue.Enqueue(entity); 57 | } 58 | 59 | var activeItems = queue.ActiveItems(); 60 | 61 | activeItems.Count.Should().Be(4); 62 | 63 | queue.Delete(activeItems[2]); 64 | 65 | activeItems = queue.ActiveItems(); 66 | 67 | activeItems.Count.Should().Be(3); 68 | 69 | var deletedItems = queue.DeletedItems(); 70 | 71 | deletedItems.Count.Should().Be(1); 72 | 73 | var item1 = queue.Dequeue().CastTo(); 74 | var item2 = queue.Dequeue().CastTo(); 75 | var item3 = queue.Dequeue().CastTo(); 76 | 77 | item1.Should().Be(entities[0]); 78 | item2.Should().Be(entities[1]); 79 | item3.Should().Be(entities[3]); 80 | 81 | //Still available 82 | deletedItems = queue.DeletedItems(); 83 | 84 | deletedItems.Count.Should().Be(4); 85 | 86 | queue.PurgeDeletedItems(); 87 | 88 | deletedItems = queue.DeletedItems(); 89 | 90 | deletedItems.Count.Should().Be(0); 91 | } 92 | } 93 | 94 | [Test] 95 | public void HardDeletesShouldBeRespected() 96 | { 97 | using (var queue = this.CreateNew() as FilterQueue) 98 | { 99 | var entities = new[]{ 100 | "One", 101 | "Two", 102 | "Three" 103 | }; 104 | 105 | foreach (var entity in entities) 106 | { 107 | queue.Enqueue(entity); 108 | } 109 | 110 | var activeItems = queue.ActiveItems(); 111 | 112 | activeItems.Count.Should().Be(3); 113 | 114 | queue.Delete(activeItems[1], true); 115 | 116 | activeItems = queue.ActiveItems(); 117 | 118 | activeItems.Count.Should().Be(2); 119 | 120 | queue.DeletedItems().Count.Should().Be(0); 121 | 122 | activeItems[0].CastTo().Should().Be(entities[0]); 123 | activeItems[1].CastTo().Should().Be(entities[2]); 124 | } 125 | } 126 | 127 | [Test] 128 | public void FilterCountCorrectness() 129 | { 130 | using (var queue = this.CreateNew() as FilterQueue) 131 | { 132 | var entities = new[]{ 133 | "One", 134 | "Two", 135 | "Three" 136 | }; 137 | 138 | foreach (var entity in entities) 139 | { 140 | queue.Enqueue(entity); 141 | } 142 | 143 | //All active items and all items should both contain the same entries as entities 144 | var activeItems = queue.ActiveItems(); 145 | activeItems.Count.Should().Be(entities.Length); 146 | queue.AllItems().Count.Should().Be(entities.Length); 147 | 148 | //Soft delete 149 | queue.Delete(activeItems[1]); 150 | 151 | //Active items should be less one 152 | queue.ActiveItems().Count.Should().Be(entities.Length - 1); 153 | //All items should be untouched 154 | queue.AllItems().Count.Should().Be(entities.Length); 155 | //Deleted items should have one 156 | queue.DeletedItems().Count.Should().Be(1); 157 | 158 | //Hard delete 159 | queue.Delete(activeItems[1],true); 160 | 161 | //Active items remain unchanged 162 | queue.ActiveItems().Count.Should().Be(entities.Length - 1); 163 | //All items now matches active 164 | queue.AllItems().Count.Should().Be(entities.Length - 1); 165 | //Deleted items are zero 166 | queue.DeletedItems().Count.Should().Be(0); 167 | } 168 | } 169 | 170 | [Test] 171 | public void CanHardDeleteMultipleTimes() 172 | { 173 | using (var queue = this.CreateNew() as FilterQueue) 174 | { 175 | var entities = new[]{ 176 | "One", 177 | "Two", 178 | "Three" 179 | }; 180 | 181 | foreach (var entity in entities) 182 | { 183 | queue.Enqueue(entity); 184 | } 185 | 186 | //All active items and all items should both contain the same entries as entities 187 | var activeItems = queue.ActiveItems(); 188 | 189 | //Hard delete 190 | for (int cnt = 0; cnt < 5; cnt++) 191 | { 192 | queue.Delete(activeItems[1], true); 193 | } 194 | } 195 | } 196 | } 197 | 198 | [TestFixture] 199 | public class StandardQueueTests : CommonTests 200 | { 201 | 202 | public override IPersistentQueueFactory BuildFactory() 203 | { 204 | return new Queue.Factory(); 205 | } 206 | 207 | [Test] 208 | public override void InstanceTypeCheck() 209 | { 210 | var className = "Queue"; 211 | 212 | using (var queue = this.Create("InstanceTypeCheck_" + className)) 213 | { 214 | Assert.IsInstanceOf(typeof(Queue), queue); 215 | } 216 | 217 | using (var queue = this.CreateNew("InstanceTypeCheck_" + className)) 218 | { 219 | Assert.IsInstanceOf(typeof(Queue), queue); 220 | } 221 | 222 | using (var queue = this.CreateNew()) 223 | { 224 | Assert.IsInstanceOf(typeof(Queue), queue); 225 | } 226 | } 227 | } 228 | } 229 | -------------------------------------------------------------------------------- /src/Tests/SQLiteShimResources/amd64/sqlite3.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorarias/PersistentQueue/e105de9c60638f447807216749e55747bf88bc54/src/Tests/SQLiteShimResources/amd64/sqlite3.dll -------------------------------------------------------------------------------- /src/Tests/SQLiteShimResources/x86/sqlite3.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorarias/PersistentQueue/e105de9c60638f447807216749e55747bf88bc54/src/Tests/SQLiteShimResources/x86/sqlite3.dll -------------------------------------------------------------------------------- /src/Tests/Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Debug 5 | AnyCPU 6 | 7 | 8 | 2.0 9 | {E347F373-C9E1-407B-9732-BAA355ACEA99} 10 | Library 11 | Properties 12 | Tests 13 | Tests 14 | v4.0 15 | 512 16 | {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 17 | 18 | 19 | 20 | 21 | 22 | 4.0 23 | 24 | 25 | true 26 | full 27 | false 28 | bin\Debug\ 29 | TRACE;DEBUG;USE_CSHARP_SQLITE 30 | prompt 31 | 4 32 | AllRules.ruleset 33 | 34 | 35 | pdbonly 36 | true 37 | bin\Release\ 38 | TRACE;USE_CSHARP_SQLITE 39 | prompt 40 | 4 41 | AllRules.ruleset 42 | 43 | 44 | true 45 | bin\x86\Debug\ 46 | TRACE;DEBUG;USE_CSHARP_SQLITE 47 | full 48 | x86 49 | bin\Debug\Tests.dll.CodeAnalysisLog.xml 50 | true 51 | GlobalSuppressions.cs 52 | prompt 53 | AllRules.ruleset 54 | ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets 55 | true 56 | ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules 57 | true 58 | 59 | 60 | bin\x86\Release\ 61 | TRACE;USE_CSHARP_SQLITE 62 | true 63 | pdbonly 64 | x86 65 | bin\Release\Tests.dll.CodeAnalysisLog.xml 66 | true 67 | GlobalSuppressions.cs 68 | prompt 69 | AllRules.ruleset 70 | ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets 71 | true 72 | ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules 73 | true 74 | 75 | 76 | 77 | False 78 | ..\packages\FluentAssertions.3.2.2\lib\net40\FluentAssertions.dll 79 | 80 | 81 | ..\packages\FluentAssertions.3.2.2\lib\net40\FluentAssertions.Core.dll 82 | 83 | 84 | ..\packages\NUnitTestAdapter.1.2\lib\nunit.core.dll 85 | False 86 | 87 | 88 | ..\packages\NUnitTestAdapter.1.2\lib\nunit.core.interfaces.dll 89 | False 90 | 91 | 92 | ..\packages\NUnit.2.6.4\lib\nunit.framework.dll 93 | 94 | 95 | ..\packages\NUnitTestAdapter.1.2\lib\nunit.util.dll 96 | False 97 | 98 | 99 | ..\packages\NUnitTestAdapter.1.2\lib\NUnit.VisualStudio.TestAdapter.dll 100 | False 101 | 102 | 103 | ..\packages\SQLiteShim\SQLiteShim.dll 104 | 105 | 106 | 107 | 3.5 108 | 109 | 110 | 111 | 112 | 113 | 114 | False 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | {0FC1F6B5-D900-49D3-BCC7-9723C3FC2933} 128 | PersistentQueue 129 | 130 | 131 | 132 | 133 | PreserveNewest 134 | 135 | 136 | PreserveNewest 137 | 138 | 139 | 140 | 147 | -------------------------------------------------------------------------------- /src/Tests/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/packages/SQLiteShim/SQLiteShim.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorarias/PersistentQueue/e105de9c60638f447807216749e55747bf88bc54/src/packages/SQLiteShim/SQLiteShim.dll -------------------------------------------------------------------------------- /src/packages/SQLiteShim/SQLiteShim.pdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorarias/PersistentQueue/e105de9c60638f447807216749e55747bf88bc54/src/packages/SQLiteShim/SQLiteShim.pdb -------------------------------------------------------------------------------- /src/packages/SQLiteShim/SQLiteShimResources/amd64/sqlite3.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorarias/PersistentQueue/e105de9c60638f447807216749e55747bf88bc54/src/packages/SQLiteShim/SQLiteShimResources/amd64/sqlite3.dll -------------------------------------------------------------------------------- /src/packages/SQLiteShim/SQLiteShimResources/x86/sqlite3.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorarias/PersistentQueue/e105de9c60638f447807216749e55747bf88bc54/src/packages/SQLiteShim/SQLiteShimResources/x86/sqlite3.dll -------------------------------------------------------------------------------- /src/packages/repositories.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | --------------------------------------------------------------------------------