public void CanModifyTxId() { var transactionInformation = new TransactionInformation { Id = Guid.NewGuid(), Timeout = TimeSpan.FromDays(7) }; using (var tx = NewTransactionalStorage()) { tx.Batch(mutator => mutator.Transactions.AddDocumentInTransaction("Ayende", null, JObject.FromObject(new { Name = "Rahien" }), new JObject(), transactionInformation)); var txInfo2 = new TransactionInformation { Id = Guid.NewGuid(), Timeout = TimeSpan.FromDays(1) }; tx.Batch(mutator => mutator.Transactions.ModifyTransactionId(transactionInformation.Id, txInfo2.Id, txInfo2.Timeout)); tx.Batch(viewer => Assert.NotNull(viewer.Documents.DocumentByKey("Ayende", txInfo2))); } }
public JsonDocument ProcessReadVetoes(JsonDocument document, TransactionInformation transactionInformation, ReadOperation operation) { if (document == null) return document; foreach (var readTrigger in triggers) { var readVetoResult = readTrigger.AllowRead(document.Key, document.DataAsJson ?? document.Projection, document.Metadata, operation, transactionInformation); switch (readVetoResult.Veto) { case ReadVetoResult.ReadAllow.Allow: break; case ReadVetoResult.ReadAllow.Deny: return new JsonDocument { DataAsJson = new JObject(), Metadata = new JObject( new JProperty("Raven-Read-Veto", new JObject(new JProperty("Reason", readVetoResult.Reason), new JProperty("Trigger", readTrigger.ToString()) )) ) }; case ReadVetoResult.ReadAllow.Ignore: return null; default: throw new ArgumentOutOfRangeException(readVetoResult.Veto.ToString()); } } return document; }
public void AddingDocInTxWhenItWasAddedInAnotherWillFail() { var transactionInformation = new TransactionInformation { Id = Guid.NewGuid(), Timeout = TimeSpan.FromDays(7) }; using (var tx = NewTransactionalStorage()) { tx.Batch(mutator => mutator.Transactions.AddDocumentInTransaction("Ayende", null, JObject.FromObject(new { Name = "Rahien" }), new JObject(), transactionInformation)); Assert.Throws<ConcurrencyException>( () => tx.Batch( mutator => mutator.Transactions.AddDocumentInTransaction("Ayende", null, JObject.FromObject(new { Name = "Rahien" }), new JObject(), new TransactionInformation { Id = Guid.NewGuid(), Timeout = TimeSpan.FromDays(7) }))); } }
public void AfterCommittingCanSeeChangesWithoutTx() { var transactionInformation = new TransactionInformation { Id = Guid.NewGuid(), Timeout = TimeSpan.FromDays(7) }; using (var tx = NewTransactionalStorage()) { tx.Batch(mutator => mutator.Transactions.AddDocumentInTransaction("Ayende", null, JObject.FromObject(new { Name = "Rahien" }), new JObject(), transactionInformation)); tx.Batch(mutator => mutator.Transactions.CompleteTransaction(transactionInformation.Id, data => { if (data.Delete) { JObject metadata; mutator.Documents.DeleteDocument(data.Key, null, out metadata); } else mutator.Documents.AddDocument(data.Key, null, data.Data, data.Metadata); })); tx.Batch(viewer => Assert.NotNull(viewer.Documents.DocumentByKey("Ayende", null))); } }
public void Delete(string key, Guid?etag, TransactionInformation transactionInformation) { TransactionalStorage.Batch(actions => { if (transactionInformation == null) { AssertDeleteOperationNotVetoed(key, transactionInformation); DeleteTriggers.Apply(trigger => trigger.OnDelete(key, transactionInformation)); JObject metadata; if (actions.Documents.DeleteDocument(key, etag, out metadata)) { AddIndexingTask(actions, metadata, () => new RemoveFromIndexTask { Keys = new[] { key } }); DeleteTriggers.Apply(trigger => trigger.AfterDelete(key, transactionInformation)); } } else { actions.Transactions.DeleteDocumentInTransaction(transactionInformation, key, etag); } workContext.ShouldNotifyAboutWork(); }); TransactionalStorage .ExecuteImmediatelyOrRegisterForSyncronization(() => DeleteTriggers.Apply(trigger => trigger.AfterCommit(key))); }
public JsonDocument ExecuteReadTriggers(JsonDocument document, TransactionInformation transactionInformation, ReadOperation operation) { if(disableReadTriggers.Value) return document; return ExecuteReadTriggersOnRead(ProcessReadVetoes(document, transactionInformation, operation), transactionInformation, operation); }
private void AssertDeleteOperationNotVetoed(string key, TransactionInformation transactionInformation) { var vetoResult = DeleteTriggers .Select(trigger => new { Trigger = trigger, VetoResult = trigger.AllowDelete(key, transactionInformation) }) .FirstOrDefault(x => x.VetoResult.IsAllowed == false); if (vetoResult != null) { throw new OperationVetoedException("DELETE vetoed by " + vetoResult.Trigger + " because: " + vetoResult.VetoResult.Reason); } }
public AddIncludesCommand( DocumentDatabase database, TransactionInformation transactionInformation, Action<JObject> add, string[] includes, HashSet<string> loadedIds) { Add = add; Includes = includes; Database = database; TransactionInformation = transactionInformation; LoadedIds = loadedIds; }
public JsonDocumentMetadata GetDocumentMetadata(string key, TransactionInformation transactionInformation) { JsonDocumentMetadata document = null; TransactionalStorage.Batch(actions => { document = actions.Documents.DocumentMetadataByKey(key, transactionInformation); }); DocumentRetriever.EnsureIdInMetadata(document); return(new DocumentRetriever(null, ReadTriggers) .ProcessReadVetoes(document, transactionInformation, ReadOperation.Load)); }
public override void OnPut(string key, JObject document, JObject metadata, TransactionInformation transactionInformation) { if (key.StartsWith("Raven/")) // we don't deal with system documents return; if (ReplicationContext.IsInReplicationContext) return; var doc = Database.Get(key, null); if (doc != null) { metadata[ReplicationConstants.RavenReplicationParentVersion] = doc.Metadata[ReplicationConstants.RavenReplicationVersion]; metadata[ReplicationConstants.RavenReplicationParentSource] = doc.Metadata[ReplicationConstants.RavenReplicationSource]; } metadata[ReplicationConstants.RavenReplicationVersion] = JToken.FromObject(hiLo.NextId()); metadata[ReplicationConstants.RavenReplicationSource] = JToken.FromObject(Database.TransactionalStorage.Id); }
public void AddingDocInTxCannotBeReadOutside() { var transactionInformation = new TransactionInformation { Id = Guid.NewGuid(), Timeout = TimeSpan.FromDays(7) }; using (var tx = NewTransactionalStorage()) { tx.Batch(mutator => mutator.Transactions.AddDocumentInTransaction("Ayende", null, JObject.FromObject(new { Name = "Rahien" }), new JObject(), transactionInformation)); tx.Batch(viewer => Assert.Null(viewer.Documents.DocumentByKey("Ayende", null))); } }
public override ReadVetoResult AllowRead(string key, JObject metadata, ReadOperation operation, TransactionInformation transactionInformation) { if(key == null) return ReadVetoResult.Allowed; if (key.StartsWith("Raven/")) { switch (operation) { case ReadOperation.Load: return ReadVetoResult.Allowed; case ReadOperation.Query: case ReadOperation.Index: return ReadVetoResult.Ignore; default: throw new ArgumentOutOfRangeException("operation"); } } return ReadVetoResult.Allowed; }
public static ICommandData CreateCommand(JObject jsonCommand, TransactionInformation transactionInformation) { var key = jsonCommand["Key"].Value<string>(); switch (jsonCommand.Value<string>("Method")) { case "PUT": return new PutCommandData { Key = key, Etag = GetEtagFromCommand(jsonCommand), Document = jsonCommand["Document"] as JObject, Metadata = jsonCommand["Metadata"] as JObject, TransactionInformation = transactionInformation }; case "DELETE": return new DeleteCommandData { Key = key, Etag = GetEtagFromCommand(jsonCommand), TransactionInformation = transactionInformation }; case "PATCH": return new PatchCommandData { Key = key, Etag = GetEtagFromCommand(jsonCommand), TransactionInformation = transactionInformation, Patches = jsonCommand .Value<JArray>("Patches") .Cast<JObject>() .Select(PatchRequest.FromJson) .ToArray() }; default: throw new ArgumentException("Batching only supports PUT, PATCH and DELETE."); } }
public void AfterRollbackCannotSeeChangesEvenInSameTxId() { var transactionInformation = new TransactionInformation { Id = Guid.NewGuid(), Timeout = TimeSpan.FromDays(7) }; using (var tx = NewTransactionalStorage()) { tx.Batch(mutator => mutator.Transactions.AddDocumentInTransaction("Ayende", null, JObject.FromObject(new { Name = "Rahien" }), new JObject(), transactionInformation)); tx.Batch(viewer => Assert.NotNull(viewer.Documents.DocumentByKey("Ayende", transactionInformation))); tx.Batch(mutator => mutator.Transactions.RollbackTransaction(transactionInformation.Id)); tx.Batch(viewer => Assert.Null(viewer.Documents.DocumentByKey("Ayende", transactionInformation))); } }
private void AssertPutOperationNotVetoed(string key, JObject metadata, JObject document, TransactionInformation transactionInformation) { var vetoResult = PutTriggers .Select(trigger => new { Trigger = trigger, VetoResult = trigger.AllowPut(key, document, metadata, transactionInformation) }) .FirstOrDefault(x => x.VetoResult.IsAllowed == false); if (vetoResult != null) { throw new OperationVetoedException("PUT vetoed by " + vetoResult.Trigger + " because: " + vetoResult.VetoResult.Reason); } }
private JsonDocument ExecuteReadTriggersOnRead(JsonDocument resultingDocument, TransactionInformation transactionInformation, ReadOperation operation) { if (resultingDocument == null) return null; foreach (var readTrigger in triggers) { readTrigger.OnRead(resultingDocument.Key, resultingDocument.DataAsJson, resultingDocument.Metadata, operation, transactionInformation); } return resultingDocument; }
/// <summary> /// Allow the trigger to perform any logic just before the document is deleted. /// </summary><remarks> /// If the trigger need to access the previous state of the document, the trigger should /// implement <seealso cref="IRequiresDocumentDatabaseInitialization" /> and use the provided /// <seealso cref="DocumentDatabase" /> instance to Get it. The returned result would be the old /// document (if it exists) or null. /// Any call to the provided <seealso cref="DocumentDatabase" /> instance will be done under the /// same transaction as the DELETE operation. /// </remarks><param name="transactionInformation">The current transaction, if any</param><param name="key">The document key</param> public virtual void OnDelete(string key, TransactionInformation transactionInformation) { }
public PutResult Put(string key, Guid?etag, JObject document, JObject metadata, TransactionInformation transactionInformation) { if (key != null && Encoding.Unicode.GetByteCount(key) >= 255) { throw new ArgumentException("The key must be a maximum of 255 bytes in unicode, 127 characters", "key"); } if (string.IsNullOrEmpty(key)) { // we no longer sort by the key, so it doesn't matter // that the key is no longer sequential key = Guid.NewGuid().ToString(); } RemoveReservedProperties(document); RemoveReservedProperties(metadata); Guid newEtag = Guid.Empty; lock (this) { TransactionalStorage.Batch(actions => { if (key.EndsWith("/")) { key += actions.General.GetNextIdentityValue(key); } if (transactionInformation == null) { AssertPutOperationNotVetoed(key, metadata, document, transactionInformation); PutTriggers.Apply(trigger => trigger.OnPut(key, document, metadata, transactionInformation)); newEtag = actions.Documents.AddDocument(key, etag, document, metadata); // We detect this by using the etags // AddIndexingTask(actions, metadata, () => new IndexDocumentsTask { Keys = new[] { key } }); PutTriggers.Apply(trigger => trigger.AfterPut(key, document, metadata, newEtag, transactionInformation)); } else { newEtag = actions.Transactions.AddDocumentInTransaction(key, etag, document, metadata, transactionInformation); } workContext.ShouldNotifyAboutWork(); }); } TransactionalStorage .ExecuteImmediatelyOrRegisterForSyncronization(() => PutTriggers.Apply(trigger => trigger.AfterCommit(key, document, metadata, newEtag))); return(new PutResult { Key = key, ETag = newEtag }); }
public PutResult Put(string key, Guid? etag, JObject document, JObject metadata, TransactionInformation transactionInformation) { if (key != null && Encoding.Unicode.GetByteCount(key) >= 255) throw new ArgumentException("The key must be a maximum of 255 bytes in unicode, 127 characters", "key"); if (string.IsNullOrEmpty(key)) { // we no longer sort by the key, so it doesn't matter // that the key is no longer sequential key = Guid.NewGuid().ToString(); } RemoveReservedProperties(document); RemoveReservedProperties(metadata); Guid newEtag = Guid.Empty; lock (this) { TransactionalStorage.Batch(actions => { if (key.EndsWith("/")) { key += actions.General.GetNextIdentityValue(key); } if (transactionInformation == null) { AssertPutOperationNotVetoed(key, metadata, document, transactionInformation); PutTriggers.Apply(trigger => trigger.OnPut(key, document, metadata, transactionInformation)); newEtag = actions.Documents.AddDocument(key, etag, document, metadata); // We detect this by using the etags // AddIndexingTask(actions, metadata, () => new IndexDocumentsTask { Keys = new[] { key } }); PutTriggers.Apply(trigger => trigger.AfterPut(key, document, metadata, newEtag, transactionInformation)); } else { newEtag = actions.Transactions.AddDocumentInTransaction(key, etag, document, metadata, transactionInformation); } workContext.ShouldNotifyAboutWork(); }); } TransactionalStorage .ExecuteImmediatelyOrRegisterForSyncronization(() => PutTriggers.Apply(trigger => trigger.AfterCommit(key, document, metadata, newEtag))); return new PutResult { Key = key, ETag = newEtag }; }
public void Delete(string key, Guid? etag, TransactionInformation transactionInformation) { TransactionalStorage.Batch(actions => { if (transactionInformation == null) { AssertDeleteOperationNotVetoed(key, transactionInformation); DeleteTriggers.Apply(trigger => trigger.OnDelete(key, transactionInformation)); JObject metadata; if (actions.Documents.DeleteDocument(key, etag, out metadata)) { AddIndexingTask(actions, metadata, () => new RemoveFromIndexTask { Keys = new[] { key } }); DeleteTriggers.Apply(trigger => trigger.AfterDelete(key, transactionInformation)); } } else { actions.Transactions.DeleteDocumentInTransaction(transactionInformation, key, etag); } workContext.ShouldNotifyAboutWork(); }); TransactionalStorage .ExecuteImmediatelyOrRegisterForSyncronization(() => DeleteTriggers.Apply(trigger => trigger.AfterCommit(key))); }
public override void OnPut(string key, Newtonsoft.Json.Linq.JObject document, Newtonsoft.Json.Linq.JObject metadata, TransactionInformation transactionInformation) { Hello = CurrentOperationContext.Headers.Value["Hello"]; base.OnPut(key, document, metadata, transactionInformation); }
public PatchResult ApplyPatch(string docId, Guid?etag, PatchRequest[] patchDoc, TransactionInformation transactionInformation) { var result = PatchResult.Patched; TransactionalStorage.Batch(actions => { var doc = actions.Documents.DocumentByKey(docId, transactionInformation); if (doc == null) { result = PatchResult.DocumentDoesNotExists; } else if (etag != null && doc.Etag != etag.Value) { throw new ConcurrencyException("Could not patch document '" + docId + "' because non current etag was used") { ActualETag = doc.Etag, ExpectedETag = etag.Value, }; } else { var jsonDoc = doc.ToJson(); new JsonPatcher(jsonDoc).Apply(patchDoc); Put(doc.Key, doc.Etag, jsonDoc, jsonDoc.Value <JObject>("@metadata"), transactionInformation); result = PatchResult.Patched; } workContext.ShouldNotifyAboutWork(); }); return(result); }
public PatchResult ApplyPatch(string docId, Guid? etag, PatchRequest[] patchDoc, TransactionInformation transactionInformation) { var result = PatchResult.Patched; TransactionalStorage.Batch(actions => { var doc = actions.Documents.DocumentByKey(docId, transactionInformation); if (doc == null) { result = PatchResult.DocumentDoesNotExists; } else if (etag != null && doc.Etag != etag.Value) { throw new ConcurrencyException("Could not patch document '" + docId + "' because non current etag was used") { ActualETag = doc.Etag, ExpectedETag = etag.Value, }; } else { var jsonDoc = doc.ToJson(); new JsonPatcher(jsonDoc).Apply(patchDoc); Put(doc.Key, doc.Etag, jsonDoc, jsonDoc.Value<JObject>("@metadata"), transactionInformation); result = PatchResult.Patched; } workContext.ShouldNotifyAboutWork(); }); return result; }
/// <summary> /// Ask the trigger whatever the PUT should be vetoed. /// If the trigger vote to veto the PUT, it needs to provide a human readable /// explanation why the PUT was rejected. /// </summary><remarks> /// This method SHOULD NOT modify either the document or the metadata. /// </remarks><param name="key">The document key</param><param name="document">The new document about to be put into Raven</param><param name="metadata">The new document metadata</param><param name="transactionInformation">The current transaction, if it exists</param><returns>Whatever the put was vetoed or not</returns> public virtual VetoResult AllowPut(string key, JObject document, JObject metadata, TransactionInformation transactionInformation) { return VetoResult.Allowed; }
/// <summary> /// Allow the trigger to perform any logic just before the document is saved to disk. /// Any modifications the trigger makes to the document or the metadata will be persisted /// to disk. /// </summary><remarks> /// If the trigger need to access the previous state of the document, the trigger should /// implement <seealso cref="IRequiresDocumentDatabaseInitialization" /> and use the provided /// <seealso cref="DocumentDatabase" /> instance to Get it. The returned result would be the old /// document (if it exists) or null. /// Any call to the provided <seealso cref="DocumentDatabase" /> instance will be done under the /// same transaction as the PUT operation. /// </remarks><param name="key">The document key</param><param name="document">The new document about to be put into Raven</param><param name="metadata">The new document metadata</param><param name="transactionInformation">The current transaction, if it exists</param> public virtual void OnPut(string key, JObject document, JObject metadata, TransactionInformation transactionInformation) { }
/// <summary> /// Ask the trigger whatever the document should be read by the user. /// </summary><remarks> /// The document and metadata instances SHOULD NOT be modified. /// </remarks> /// <param name="key">The key of the read document - can be null if reading a projection</param> /// <param name="document">The document being read</param> /// <param name="metadata">The document metadata</param> /// <param name="operation">Whatever the operation is a load or a query</param> /// <param name="transactionInformation">The transaction information, if any</param> /// <returns> /// * If the result is Allow, the operation continues as usual. /// * If the result is Deny, the operation will return an error to the user /// if asking for a particular document, or an error document in place of /// the result if asking for a query. /// * If the result is Ignore, the operation will return null to the user if /// asking for a particular document, or skip including the result entirely /// in the query results. /// </returns> public virtual ReadVetoResult AllowRead(string key, JObject document, JObject metadata, ReadOperation operation, TransactionInformation transactionInformation) { return ReadVetoResult.Allowed; }
public void AddingDocInTxWillReadOldValueOutsideIt() { var transactionInformation = new TransactionInformation { Id = Guid.NewGuid(), Timeout = TimeSpan.FromDays(7) }; using (var tx = NewTransactionalStorage()) { tx.Batch(mutator => mutator.Documents.AddDocument("Ayende", null, JObject.FromObject(new { Name = "Rahien" }), new JObject())); tx.Batch(mutator => mutator.Transactions.AddDocumentInTransaction("Ayende", null, JObject.FromObject(new { Name = "Rahien2" }), new JObject(), transactionInformation)); tx.Batch(viewer => { var doc = viewer.Documents.DocumentByKey("Ayende", null); Assert.Equal("Rahien", doc.DataAsJson.Value<string>("Name")); }); } }
/// <summary> /// Allow the trigger the option of modifying the document and metadata instances /// before the user can see them. /// </summary><remarks> /// The modified values are transient, and are NOT saved to the database. /// </remarks><param name="key">The key of the read document - can be null if reading a projection</param><param name="document">The document being read</param><param name="metadata">The document metadata</param><param name="operation">Whatever the operation is a load or a query</param><param name="transactionInformation">The transaction information, if any</param> public virtual void OnRead(string key, JObject document, JObject metadata, ReadOperation operation, TransactionInformation transactionInformation) { }
public void AddingDocumentInTxThenAddingWithoutTxThrows() { var transactionInformation = new TransactionInformation { Id = Guid.NewGuid(), Timeout = TimeSpan.FromDays(7) }; using (var tx = NewTransactionalStorage()) { tx.Batch(mutator => mutator.Transactions.AddDocumentInTransaction("Ayende", null, JObject.FromObject(new { Name = "Rahien" }), new JObject(), transactionInformation)); tx.Batch(mutator => Assert.Throws<ConcurrencyException>( () => mutator.Documents.AddDocument("Ayende", Guid.NewGuid(), JObject.FromObject(new { Name = "Rahien" }), new JObject()))); } }
public JsonDocumentMetadata GetDocumentMetadata(string key, TransactionInformation transactionInformation) { JsonDocumentMetadata document = null; TransactionalStorage.Batch(actions => { document = actions.Documents.DocumentMetadataByKey(key, transactionInformation); }); DocumentRetriever.EnsureIdInMetadata(document); return new DocumentRetriever(null, ReadTriggers) .ProcessReadVetoes(document, transactionInformation, ReadOperation.Load); }
public void CanDeleteDocumentInTransaction() { var transactionInformation = new TransactionInformation { Id = Guid.NewGuid(), Timeout = TimeSpan.FromDays(7) }; using (var tx = NewTransactionalStorage()) { tx.Batch(mutator => mutator.Documents.AddDocument("Ayende", null, JObject.FromObject(new { Name = "Rahien" }), new JObject())); tx.Batch(mutator => mutator.Transactions.DeleteDocumentInTransaction(transactionInformation, "Ayende", null)); tx.Batch(viewer => { Assert.NotNull(viewer.Documents.DocumentByKey("Ayende", null)); Assert.Null(viewer.Documents.DocumentByKey("Ayende", transactionInformation)); }); } }
public void AddingDocumentInTxThenAddingWithoutTxAfterTxExpiredWorks() { var transactionInformation = new TransactionInformation { Id = Guid.NewGuid(), Timeout = TimeSpan.FromDays(-7) }; using (var tx = NewTransactionalStorage()) { tx.Batch(mutator => mutator.Transactions.AddDocumentInTransaction("Ayende", null, JObject.FromObject(new { Name = "Rahien1" }), new JObject(), transactionInformation)); tx.Batch(mutator => mutator.Documents.AddDocument("Ayende", Guid.NewGuid(), JObject.FromObject(new { Name = "Rahien2" }), new JObject())); tx.Batch(viewer => { var doc = viewer.Documents.DocumentByKey("Ayende", transactionInformation); Assert.Equal("Rahien2", doc.DataAsJson.Value<string>("Name")); }); } }
public override void OnPut(string key, Newtonsoft.Json.Linq.JObject document, Newtonsoft.Json.Linq.JObject metadata, Raven.Http.TransactionInformation transactionInformation) { if (AuditContext.IsInAuditContext) { return; } using (AuditContext.Enter()) { if (metadata.Value <string>("Raven-Entity-Name") == "People") { if (metadata["CreatedByPersonId"] == null) { metadata["CreatedByPersonId"] = CurrentOperationContext.Headers.Value["CurrentUserPersonId"]; metadata["CreatedDate"] = new DateTime(2011, 02, 19, 15, 00, 00); } else { metadata["LastUpdatedPersonId"] = CurrentOperationContext.Headers.Value["CurrentUserPersonId"]; metadata["LastUpdatedDate"] = new DateTime(2011, 02, 19, 15, 00, 00); } } } }
/// <summary> /// Allow the trigger to perform any logic after the document was put but still in the /// same transaction as the put /// </summary><remarks> /// Any call to the provided <seealso cref="DocumentDatabase" /> instance will be done under the /// same transaction as the PUT operation. /// </remarks><param name="key">The document key</param><param name="document">The new document about to be put into Raven</param><param name="metadata">The new document metadata</param> /// <param name="etag">The etag of the just put document</param> /// <param name="transactionInformation">The current transaction, if it exists</param> public virtual void AfterPut(string key, JObject document, JObject metadata, Guid etag, TransactionInformation transactionInformation) { }
public DatabaseBulkOperations(DocumentDatabase database, TransactionInformation transactionInformation) { this.database = database; this.transactionInformation = transactionInformation; }