PageRenderTime 43ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 0ms

/src/proj/EventStore.Persistence.SqlPersistence/SqlPersistenceEngine.cs

http://github.com/joliver/EventStore
C# | 333 lines | 296 code | 37 blank | 0 comment | 30 complexity | 603a79d56efc4aec992f874b46385486 MD5 | raw file
  1. namespace EventStore.Persistence.SqlPersistence
  2. {
  3. using System;
  4. using System.Collections.Generic;
  5. using System.Data;
  6. using System.Linq;
  7. using System.Threading;
  8. using System.Transactions;
  9. using Logging;
  10. using Persistence;
  11. using Serialization;
  12. public class SqlPersistenceEngine : IPersistStreams
  13. {
  14. private static readonly ILog Logger = LogFactory.BuildLogger(typeof(SqlPersistenceEngine));
  15. private static readonly DateTime EpochTime = new DateTime(1970, 1, 1);
  16. private readonly IConnectionFactory connectionFactory;
  17. private readonly ISqlDialect dialect;
  18. private readonly ISerialize serializer;
  19. private readonly TransactionScopeOption scopeOption;
  20. private readonly int pageSize;
  21. private int initialized;
  22. private bool disposed;
  23. public SqlPersistenceEngine(
  24. IConnectionFactory connectionFactory,
  25. ISqlDialect dialect,
  26. ISerialize serializer,
  27. TransactionScopeOption scopeOption,
  28. int pageSize)
  29. {
  30. if (connectionFactory == null)
  31. throw new ArgumentNullException("connectionFactory");
  32. if (dialect == null)
  33. throw new ArgumentNullException("dialect");
  34. if (serializer == null)
  35. throw new ArgumentNullException("serializer");
  36. if (pageSize < 0)
  37. throw new ArgumentException("pageSize");
  38. this.connectionFactory = connectionFactory;
  39. this.dialect = dialect;
  40. this.serializer = serializer;
  41. this.scopeOption = scopeOption;
  42. this.pageSize = pageSize;
  43. Logger.Debug(Messages.UsingScope, this.scopeOption.ToString());
  44. }
  45. public void Dispose()
  46. {
  47. this.Dispose(true);
  48. GC.SuppressFinalize(this);
  49. }
  50. protected virtual void Dispose(bool disposing)
  51. {
  52. if (!disposing || this.disposed)
  53. return;
  54. Logger.Debug(Messages.ShuttingDownPersistence);
  55. this.disposed = true;
  56. }
  57. public virtual void Initialize()
  58. {
  59. if (Interlocked.Increment(ref this.initialized) > 1)
  60. return;
  61. Logger.Debug(Messages.InitializingStorage);
  62. this.ExecuteCommand(Guid.Empty, statement =>
  63. statement.ExecuteWithoutExceptions(this.dialect.InitializeStorage));
  64. }
  65. public virtual IEnumerable<Commit> GetFrom(Guid streamId, int minRevision, int maxRevision)
  66. {
  67. Logger.Debug(Messages.GettingAllCommitsBetween, streamId, minRevision, maxRevision);
  68. return this.ExecuteQuery(streamId, query =>
  69. {
  70. var statement = this.dialect.GetCommitsFromStartingRevision;
  71. query.AddParameter(this.dialect.StreamId, streamId);
  72. query.AddParameter(this.dialect.StreamRevision, minRevision);
  73. query.AddParameter(this.dialect.MaxStreamRevision, maxRevision);
  74. query.AddParameter(this.dialect.CommitSequence, 0);
  75. return query.ExecutePagedQuery(statement,
  76. (q, r) => q.SetParameter(this.dialect.CommitSequence, r.CommitSequence()))
  77. .Select(x => x.GetCommit(this.serializer));
  78. });
  79. }
  80. public virtual IEnumerable<Commit> GetFrom(DateTime start)
  81. {
  82. start = start < EpochTime ? EpochTime : start;
  83. Logger.Debug(Messages.GettingAllCommitsFrom, start);
  84. return this.ExecuteQuery(Guid.Empty, query =>
  85. {
  86. var statement = this.dialect.GetCommitsFromInstant;
  87. query.AddParameter(this.dialect.CommitStamp, start);
  88. return query.ExecutePagedQuery(statement, (q, r) => { })
  89. .Select(x => x.GetCommit(this.serializer));
  90. });
  91. }
  92. public virtual IEnumerable<Commit> GetFromTo(DateTime start, DateTime end)
  93. {
  94. start = start < EpochTime ? EpochTime : start;
  95. end = end < EpochTime ? EpochTime : end;
  96. Logger.Debug(Messages.GettingAllCommitsFromTo, start, end);
  97. return this.ExecuteQuery(Guid.Empty, query =>
  98. {
  99. var statement = this.dialect.GetCommitsFromToInstant;
  100. query.AddParameter(this.dialect.CommitStampStart, start);
  101. query.AddParameter(this.dialect.CommitStampEnd, end);
  102. return query.ExecutePagedQuery(statement, (q, r) => { })
  103. .Select(x => x.GetCommit(this.serializer));
  104. });
  105. }
  106. public virtual void Commit(Commit attempt)
  107. {
  108. try
  109. {
  110. this.PersistCommit(attempt);
  111. Logger.Debug(Messages.CommitPersisted, attempt.CommitId);
  112. }
  113. catch (Exception e)
  114. {
  115. if (!(e is UniqueKeyViolationException))
  116. throw;
  117. if (this.DetectDuplicate(attempt))
  118. {
  119. Logger.Info(Messages.DuplicateCommit);
  120. throw new DuplicateCommitException(e.Message, e);
  121. }
  122. Logger.Info(Messages.ConcurrentWriteDetected);
  123. throw new ConcurrencyException(e.Message, e);
  124. }
  125. }
  126. private void PersistCommit(Commit attempt)
  127. {
  128. Logger.Debug(Messages.AttemptingToCommit,
  129. attempt.Events.Count, attempt.StreamId, attempt.CommitSequence);
  130. this.ExecuteCommand(attempt.StreamId, cmd =>
  131. {
  132. cmd.AddParameter(this.dialect.StreamId, attempt.StreamId);
  133. cmd.AddParameter(this.dialect.StreamRevision, attempt.StreamRevision);
  134. cmd.AddParameter(this.dialect.Items, attempt.Events.Count);
  135. cmd.AddParameter(this.dialect.CommitId, attempt.CommitId);
  136. cmd.AddParameter(this.dialect.CommitSequence, attempt.CommitSequence);
  137. cmd.AddParameter(this.dialect.CommitStamp, attempt.CommitStamp);
  138. cmd.AddParameter(this.dialect.Headers, this.serializer.Serialize(attempt.Headers));
  139. cmd.AddParameter(this.dialect.Payload, this.serializer.Serialize(attempt.Events));
  140. return cmd.ExecuteNonQuery(this.dialect.PersistCommit);
  141. });
  142. }
  143. private bool DetectDuplicate(Commit attempt)
  144. {
  145. return this.ExecuteCommand(attempt.StreamId, cmd =>
  146. {
  147. cmd.AddParameter(this.dialect.StreamId, attempt.StreamId);
  148. cmd.AddParameter(this.dialect.CommitId, attempt.CommitId);
  149. cmd.AddParameter(this.dialect.CommitSequence, attempt.CommitSequence);
  150. var value = cmd.ExecuteScalar(this.dialect.DuplicateCommit);
  151. return (value is long ? (long)value : (int)value) > 0;
  152. });
  153. }
  154. public virtual IEnumerable<Commit> GetUndispatchedCommits()
  155. {
  156. Logger.Debug(Messages.GettingUndispatchedCommits);
  157. return this.ExecuteQuery(Guid.Empty, query =>
  158. query.ExecutePagedQuery(this.dialect.GetUndispatchedCommits, (q, r) => { }))
  159. .Select(x => x.GetCommit(this.serializer))
  160. .ToArray(); // avoid paging
  161. }
  162. public virtual void MarkCommitAsDispatched(Commit commit)
  163. {
  164. Logger.Debug(Messages.MarkingCommitAsDispatched, commit.CommitId);
  165. this.ExecuteCommand(commit.StreamId, cmd =>
  166. {
  167. cmd.AddParameter(this.dialect.StreamId, commit.StreamId);
  168. cmd.AddParameter(this.dialect.CommitSequence, commit.CommitSequence);
  169. return cmd.ExecuteWithoutExceptions(this.dialect.MarkCommitAsDispatched);
  170. });
  171. }
  172. public virtual IEnumerable<StreamHead> GetStreamsToSnapshot(int maxThreshold)
  173. {
  174. Logger.Debug(Messages.GettingStreamsToSnapshot);
  175. return this.ExecuteQuery(Guid.Empty, query =>
  176. {
  177. var statement = this.dialect.GetStreamsRequiringSnapshots;
  178. query.AddParameter(this.dialect.StreamId, Guid.Empty);
  179. query.AddParameter(this.dialect.Threshold, maxThreshold);
  180. return query.ExecutePagedQuery(statement,
  181. (q, s) => q.SetParameter(this.dialect.StreamId, this.dialect.CoalesceParameterValue(s.StreamId())))
  182. .Select(x => x.GetStreamToSnapshot());
  183. });
  184. }
  185. public virtual Snapshot GetSnapshot(Guid streamId, int maxRevision)
  186. {
  187. Logger.Debug(Messages.GettingRevision, streamId, maxRevision);
  188. return this.ExecuteQuery(streamId, query =>
  189. {
  190. var statement = this.dialect.GetSnapshot;
  191. query.AddParameter(this.dialect.StreamId, streamId);
  192. query.AddParameter(this.dialect.StreamRevision, maxRevision);
  193. return query.ExecuteWithQuery(statement).Select(x => x.GetSnapshot(this.serializer));
  194. }).FirstOrDefault();
  195. }
  196. public virtual bool AddSnapshot(Snapshot snapshot)
  197. {
  198. Logger.Debug(Messages.AddingSnapshot, snapshot.StreamId, snapshot.StreamRevision);
  199. return this.ExecuteCommand(snapshot.StreamId, cmd =>
  200. {
  201. cmd.AddParameter(this.dialect.StreamId, snapshot.StreamId);
  202. cmd.AddParameter(this.dialect.StreamRevision, snapshot.StreamRevision);
  203. cmd.AddParameter(this.dialect.Payload, this.serializer.Serialize(snapshot.Payload));
  204. return cmd.ExecuteWithoutExceptions(this.dialect.AppendSnapshotToCommit);
  205. }) > 0;
  206. }
  207. public virtual void Purge()
  208. {
  209. Logger.Warn(Messages.PurgingStorage);
  210. this.ExecuteCommand(Guid.Empty, cmd =>
  211. cmd.ExecuteNonQuery(this.dialect.PurgeStorage));
  212. }
  213. protected virtual IEnumerable<T> ExecuteQuery<T>(Guid streamId, Func<IDbStatement, IEnumerable<T>> query)
  214. {
  215. this.ThrowWhenDisposed();
  216. var scope = this.OpenQueryScope();
  217. IDbConnection connection = null;
  218. IDbTransaction transaction = null;
  219. IDbStatement statement = null;
  220. try
  221. {
  222. connection = this.connectionFactory.OpenReplica(streamId);
  223. transaction = this.dialect.OpenTransaction(connection);
  224. statement = this.dialect.BuildStatement(scope, connection, transaction);
  225. statement.PageSize = this.pageSize;
  226. Logger.Verbose(Messages.ExecutingQuery);
  227. return query(statement);
  228. }
  229. catch (Exception e)
  230. {
  231. if (statement != null)
  232. statement.Dispose();
  233. if (transaction != null)
  234. transaction.Dispose();
  235. if (connection != null)
  236. connection.Dispose();
  237. if (scope != null)
  238. scope.Dispose();
  239. Logger.Debug(Messages.StorageThrewException, e.GetType());
  240. if (e is StorageUnavailableException)
  241. throw;
  242. throw new StorageException(e.Message, e);
  243. }
  244. }
  245. protected virtual TransactionScope OpenQueryScope()
  246. {
  247. return this.OpenCommandScope() ?? new TransactionScope(TransactionScopeOption.Suppress);
  248. }
  249. private void ThrowWhenDisposed()
  250. {
  251. if (!this.disposed)
  252. return;
  253. Logger.Warn(Messages.AlreadyDisposed);
  254. throw new ObjectDisposedException(Messages.AlreadyDisposed);
  255. }
  256. protected virtual T ExecuteCommand<T>(Guid streamId, Func<IDbStatement, T> command)
  257. {
  258. this.ThrowWhenDisposed();
  259. using (var scope = this.OpenCommandScope())
  260. using (var connection = this.connectionFactory.OpenMaster(streamId))
  261. using (var transaction = this.dialect.OpenTransaction(connection))
  262. using (var statement = this.dialect.BuildStatement(scope, connection, transaction))
  263. {
  264. try
  265. {
  266. Logger.Verbose(Messages.ExecutingCommand);
  267. var rowsAffected = command(statement);
  268. Logger.Verbose(Messages.CommandExecuted, rowsAffected);
  269. if (transaction != null)
  270. transaction.Commit();
  271. if (scope != null)
  272. scope.Complete();
  273. return rowsAffected;
  274. }
  275. catch (Exception e)
  276. {
  277. Logger.Debug(Messages.StorageThrewException, e.GetType());
  278. if (!RecoverableException(e))
  279. throw new StorageException(e.Message, e);
  280. Logger.Info(Messages.RecoverableExceptionCompletesScope);
  281. if (scope != null)
  282. scope.Complete();
  283. throw;
  284. }
  285. }
  286. }
  287. protected virtual TransactionScope OpenCommandScope()
  288. {
  289. return new TransactionScope(this.scopeOption);
  290. }
  291. private static bool RecoverableException(Exception e)
  292. {
  293. return e is UniqueKeyViolationException || e is StorageUnavailableException;
  294. }
  295. }
  296. }