diff --git a/docs/ai-integration/_category_.json b/docs/ai-integration/_category_.json index 1b43344b4c..dffbdf0935 100644 --- a/docs/ai-integration/_category_.json +++ b/docs/ai-integration/_category_.json @@ -1,4 +1,4 @@ { - "position": 9, - "label": AI Integration, -} \ No newline at end of file + "position": 10, + "label": "AI Integration" +} diff --git a/docs/client-api/_category_.json b/docs/client-api/_category_.json index 2b13474af7..b5b683de90 100644 --- a/docs/client-api/_category_.json +++ b/docs/client-api/_category_.json @@ -1,4 +1,4 @@ { "position": 1, - "label": Client API, -} \ No newline at end of file + "label": "Client API" +} diff --git a/docs/client-api/operations/_what-are-operations-csharp.mdx b/docs/client-api/operations/_what-are-operations-csharp.mdx index 4d1cab2c70..135caa5cf9 100644 --- a/docs/client-api/operations/_what-are-operations-csharp.mdx +++ b/docs/client-api/operations/_what-are-operations-csharp.mdx @@ -169,10 +169,10 @@ Task> SendAsync(PatchOperation        [DeleteByQueryOperation](../../client-api/operations/common/delete-by-query.mdx) * **Compare-exchange**: -        [PutCompareExchangeValueOperation](../../client-api/operations/compare-exchange/put-compare-exchange-value.mdx) -        [GetCompareExchangeValueOperation](../../client-api/operations/compare-exchange/get-compare-exchange-value.mdx) -        [GetCompareExchangeValuesOperation](../../client-api/operations/compare-exchange/get-compare-exchange-values.mdx) -        [DeleteCompareExchangeValueOperation](../../client-api/operations/compare-exchange/delete-compare-exchange-value.mdx) +        [PutCompareExchangeValueOperation](../../compare-exchange/create-cmpxchg-items#create-item-using-a-store-operation) +        [GetCompareExchangeValueOperation](../../compare-exchange/get-cmpxchg-item#get-item-using-a-store-operation) +        [GetCompareExchangeValuesOperation](../../compare-exchange/get-cmpxchg-items) +        [DeleteCompareExchangeValueOperation](../../compare-exchange/delete-cmpxchg-items#delete-compare-exchange-item-using-a-store-operation) diff --git a/docs/client-api/operations/_what-are-operations-java.mdx b/docs/client-api/operations/_what-are-operations-java.mdx index e2c6c11e42..e73c3f9ba8 100644 --- a/docs/client-api/operations/_what-are-operations-java.mdx +++ b/docs/client-api/operations/_what-are-operations-java.mdx @@ -50,7 +50,7 @@ public Operation sendAsync(IOperation operation, SessionInfo #### Compare Exchange -* [CompareExchange](../../client-api/operations/compare-exchange/overview.mdx) +* [CompareExchange](../../compare-exchange/overview) #### Attachments diff --git a/docs/client-api/operations/_what-are-operations-nodejs.mdx b/docs/client-api/operations/_what-are-operations-nodejs.mdx index d939005e4f..d96ef939eb 100644 --- a/docs/client-api/operations/_what-are-operations-nodejs.mdx +++ b/docs/client-api/operations/_what-are-operations-nodejs.mdx @@ -142,10 +142,10 @@ await send(patchOperation, sessionInfo, resultType);        [DeleteByQueryOperation](../../client-api/operations/common/delete-by-query.mdx) * __Compare-exchange__: -        PutCompareExchangeValueOperation -        GetCompareExchangeValueOperation -        GetCompareExchangeValuesOperation -        DeleteCompareExchangeValueOperation +        [PutCompareExchangeValueOperation](../../compare-exchange/create-cmpxchg-items#create-item-using-a-store-operation) +        [GetCompareExchangeValueOperation](../../compare-exchange/get-cmpxchg-item#get-item-using-a-store-operation) +        [GetCompareExchangeValuesOperation](../../compare-exchange/get-cmpxchg-items) +        [DeleteCompareExchangeValueOperation](../../compare-exchange/delete-cmpxchg-items#delete-compare-exchange-item-using-a-store-operation) diff --git a/docs/client-api/operations/_what-are-operations-php.mdx b/docs/client-api/operations/_what-are-operations-php.mdx index 6ca3645f83..9b7d330139 100644 --- a/docs/client-api/operations/_what-are-operations-php.mdx +++ b/docs/client-api/operations/_what-are-operations-php.mdx @@ -138,10 +138,10 @@ public function send(...$parameters);        [DeleteByQueryOperation](../../client-api/operations/common/delete-by-query.mdx) * **Compare-exchange**: -        [PutCompareExchangeValueOperation](../../client-api/operations/compare-exchange/put-compare-exchange-value.mdx) -        [GetCompareExchangeValueOperation](../../client-api/operations/compare-exchange/get-compare-exchange-value.mdx) -        [GetCompareExchangeValuesOperation](../../client-api/operations/compare-exchange/get-compare-exchange-values.mdx) -        [DeleteCompareExchangeValueOperation](../../client-api/operations/compare-exchange/delete-compare-exchange-value.mdx) +        PutCompareExchangeValueOperation +        GetCompareExchangeValueOperation +        [GetCompareExchangeValuesOperation](../../compare-exchange/get-cmpxchg-items) +        DeleteCompareExchangeValueOperation diff --git a/docs/client-api/operations/_what-are-operations-python.mdx b/docs/client-api/operations/_what-are-operations-python.mdx index 81080490c7..2953c39ffd 100644 --- a/docs/client-api/operations/_what-are-operations-python.mdx +++ b/docs/client-api/operations/_what-are-operations-python.mdx @@ -138,10 +138,10 @@ def send_patch_operation_with_entity_class(        [DeleteByQueryOperation](../../client-api/operations/common/delete-by-query.mdx) * **Compare-exchange**: -        [PutCompareExchangeValueOperation](../../client-api/operations/compare-exchange/put-compare-exchange-value.mdx) -        [GetCompareExchangeValueOperation](../../client-api/operations/compare-exchange/get-compare-exchange-value.mdx) -        [GetCompareExchangeValuesOperation](../../client-api/operations/compare-exchange/get-compare-exchange-values.mdx) -        [DeleteCompareExchangeValueOperation](../../client-api/operations/compare-exchange/delete-compare-exchange-value.mdx) +        PutCompareExchangeValueOperation +        GetCompareExchangeValueOperation +        [GetCompareExchangeValuesOperation](../../compare-exchange/get-cmpxchg-items) +        DeleteCompareExchangeValueOperation] diff --git a/docs/client-api/operations/compare-exchange/_category_.json b/docs/client-api/operations/compare-exchange/_category_.json deleted file mode 100644 index c528bcf4fc..0000000000 --- a/docs/client-api/operations/compare-exchange/_category_.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "position": 7, - "label": Compare Exchange, -} \ No newline at end of file diff --git a/docs/client-api/operations/compare-exchange/_compare-exchange-expiration-csharp.mdx b/docs/client-api/operations/compare-exchange/_compare-exchange-expiration-csharp.mdx deleted file mode 100644 index 393003ab47..0000000000 --- a/docs/client-api/operations/compare-exchange/_compare-exchange-expiration-csharp.mdx +++ /dev/null @@ -1,81 +0,0 @@ -import Admonition from '@theme/Admonition'; -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; -import CodeBlock from '@theme/CodeBlock'; - - - -* Compare exchange value expiration works very similar to [document expiration](../../../server/extensions/expiration.mdx). - -* Use the `@expires` field in a [compare exchange value's metadata](../../../client-api/operations/compare-exchange/compare-exchange-metadata.mdx) to schedule its expiration. - -* In this page: - * [Syntax](../../../client-api/operations/compare-exchange/compare-exchange-expiration.mdx#syntax) - * [Examples](../../../client-api/operations/compare-exchange/compare-exchange-expiration.mdx#examples) - - -## Syntax - -`@expires` is a metadata property that holds a `DateTime` value. Once the -specified date and time has passed, the compare exchange value is set for -deletion by the expiration feature. The _exact_ time this happens depends -on the expiration frequency and other -[expiration configurations](../../../server/extensions/expiration.mdx#configuring-the-expiration-feature). - -To set a compare exchange value to expire, simply put a `DateTime` value -(in UTC format) in the `@expires` field, then to send it to the server. - - - -## Examples - -Creating a new key with `CreateCompareExchangeValue()`: - - - -{`using (IAsyncDocumentSession session = store.OpenAsyncSession()) -\{ - // Set a time exactly one week from now - DateTime expirationTime = DateTime.UtcNow.AddDays(7); - - // Create a new compare exchange value - var cmpxchgValue = session.Advanced.ClusterTransaction.CreateCompareExchangeValue("key", "value"); - - // Edit Metadata - cmpxchgValue.Metadata[Constants.Documents.Metadata.Expires] = expirationTime; - - // Send to server - session.SaveChangesAsync(); -\} -`} - - - -Updating an existing key with `PutCompareExchangeValueOperation`: - - - -{`// Retrieve an existing key -CompareExchangeValue cmpxchgValue = store.Operations.Send( - new GetCompareExchangeValueOperation("key")); - -// Set time -DateTime expirationTime = DateTime.UtcNow.AddDays(7); - -// Edit Metadata -cmpxchgValue.Metadata[Constants.Documents.Metadata.Expires] = expirationTime; - -// Update value. Index must match the index on the server side, -// or the operation will fail. -CompareExchangeResult result = store.Operations.Send( - new PutCompareExchangeValueOperation( - cmpxchgValue.Key, - cmpxchgValue.Value, - cmpxchgValue.Index)); -`} - - - - - - diff --git a/docs/client-api/operations/compare-exchange/_compare-exchange-metadata-csharp.mdx b/docs/client-api/operations/compare-exchange/_compare-exchange-metadata-csharp.mdx deleted file mode 100644 index e4871b3bfa..0000000000 --- a/docs/client-api/operations/compare-exchange/_compare-exchange-metadata-csharp.mdx +++ /dev/null @@ -1,68 +0,0 @@ -import Admonition from '@theme/Admonition'; -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; -import CodeBlock from '@theme/CodeBlock'; - - - -* RavenDB 5.0 added metadata to compare exchange values. - -* In this page: - * [Syntax](../../../client-api/operations/compare-exchange/compare-exchange-metadata.mdx#syntax) - - -## Syntax - -A compare exchange value's metadata is very similar to a -[document's metadata](../../../client-api/session/how-to/get-and-modify-entity-metadata.mdx). - -The metadata can be used to set [compare exchange expiration](../../../client-api/operations/compare-exchange/compare-exchange-expiration.mdx). - -The metadata is accessible as a root property of the compare exchange value object: - - - -{`using (IAsyncDocumentSession session = store.OpenAsyncSession( - new SessionOptions \{ - TransactionMode = TransactionMode.ClusterWide - \})) -\{ - // Create a new compare exchange value - var cmpxchgValue = session.Advanced.ClusterTransaction.CreateCompareExchangeValue("key", "value"); - - // Add a field to the metadata - // with a value of type string - cmpxchgValue.Metadata["Field name"] = "some value"; - - // Retrieve metadata as a dictionary - IDictionary cmpxchgMetadata = cmpxchgValue.Metadata; -\} -`} - - - -You can send it as a parameter of the -[Put Compare Exchange Value operation](../../../client-api/operations/compare-exchange/put-compare-exchange-value.mdx): - - - -{`// Create some metadata -var cmpxchgMetadata = new MetadataAsDictionary \{ - ["Year"] = "2020" - \}; - -// Create/update the compare exchange value "best" -await documentStore.Operations.SendAsync( - new PutCompareExchangeValueOperation( - "best", - new User \{ Name = "Alice" \}, - 0, - cmpxchgMetadata)); -`} - - - - - - - diff --git a/docs/client-api/operations/compare-exchange/_delete-compare-exchange-value-csharp.mdx b/docs/client-api/operations/compare-exchange/_delete-compare-exchange-value-csharp.mdx deleted file mode 100644 index a32f08a32a..0000000000 --- a/docs/client-api/operations/compare-exchange/_delete-compare-exchange-value-csharp.mdx +++ /dev/null @@ -1,73 +0,0 @@ -import Admonition from '@theme/Admonition'; -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; -import CodeBlock from '@theme/CodeBlock'; - - - -* Use `DeleteCompareExchangeValueOperation` to delete a _Key_ and its _Value_. - -* The _Key_ and its _Value_ are deleted only if the _index_ in the request matches the current index stored in the server for the specified key. - -* For an overview of the 'Compare Exchange' feature click: [Compare Exchange Overview](../../../client-api/operations/compare-exchange/overview.mdx) - -* In this page: - * [Syntax](../../../client-api/operations/compare-exchange/get-compare-exchange-values.mdx#syntax) - * [Example](../../../client-api/operations/compare-exchange/delete-compare-exchange-value.mdx#example) - -## Syntax - -**Method**: - - -{`public DeleteCompareExchangeValueOperation(string key, long index) -`} - - - -| Parameters | | | -| ------------- | ------------- | ----- | -| **key** | string | The key to be deleted | -| **index** | long | The version number of the value to be deleted | - -**Returned object**: - - -{`public class CompareExchangeResult -\{ - public bool Successful; - public T Value; - public long Index; -\} -`} - - - -| Return Value | Type | Description | -|----------------|------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| **Successful** | bool | * _True_ if the delete operation was successfully completed
* _True_ if _key_ doesn't exist
* _False_ if the delete operation failed | -| **Value** | `T` | * The value that was deleted upon a successful delete
* `null` if _key_ doesn't exist
* The currently existing value on the server if delete operation failed | -| **Index** | long | * The next available version number upon success
* The next available version number if _key_ doesn't exist
* The currently existing index on the server if the delete operation failed | - -## Example - - - -{`// First, get existing value -CompareExchangeValue readResult = - store.Operations.Send( - new GetCompareExchangeValueOperation("AdminUser")); - -// Delete the key - use the index received from the 'Get' operation -CompareExchangeResult deleteResult - = store.Operations.Send( - new DeleteCompareExchangeValueOperation("AdminUser", readResult.Index)); - -// The delete result is successful only if the index has not changed between the read and delete operations -bool deleteResultSuccessful = deleteResult.Successful; -`} - - - - - diff --git a/docs/client-api/operations/compare-exchange/_delete-compare-exchange-value-java.mdx b/docs/client-api/operations/compare-exchange/_delete-compare-exchange-value-java.mdx deleted file mode 100644 index 4334b3027e..0000000000 --- a/docs/client-api/operations/compare-exchange/_delete-compare-exchange-value-java.mdx +++ /dev/null @@ -1,96 +0,0 @@ -import Admonition from '@theme/Admonition'; -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; -import CodeBlock from '@theme/CodeBlock'; - - - -* Use `DeleteCompareExchangeValueOperation` to delete a _Key_ and its _Value_. - -* The _Key_ and its _Value_ are deleted only if the _index_ in the request matches the current index stored in the server for the specified key. - -* For an overview of the 'Compare Exchange' feature click: [Compare Exchange Overview](../../../client-api/operations/compare-exchange/overview.mdx) - -* In this page: - * [Syntax](../../../client-api/operations/compare-exchange/get-compare-exchange-values.mdx#syntax) - * [Example](../../../client-api/operations/compare-exchange/delete-compare-exchange-value.mdx#example) - -## Syntax - -**Method**: - - -{`public DeleteCompareExchangeValueOperation(Class clazz, String key, long index) -`} - - - -| Parameters | | | -| ------------- | ------------- | ----- | -| **key** | String | The key to be deleted | -| **index** | long | The version number of the value to be deleted | - -**Returned object**: - - -{`public class CompareExchangeResult \{ - private T value; - private long index; - private boolean successful; - - public T getValue() \{ - return value; - \} - - public void setValue(T value) \{ - this.value = value; - \} - - public long getIndex() \{ - return index; - \} - - public void setIndex(long index) \{ - this.index = index; - \} - - public boolean isSuccessful() \{ - return successful; - \} - - public void setSuccessful(boolean successful) \{ - this.successful = successful; - \} -\} -`} - - - -| Return Value | Type | Description | -|----------------|---------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| **Successful** | boolean | * _True_ if the delete operation was successfully completed
* _True_ if _key_ doesn't exist
* _False_ if the delete operation failed | -| **Value** | `T` | * The value that was deleted upon a successful delete
* `null` if _key_ doesn't exist
* The currently existing value on the server if delete operation failed | -| **Index** | long | * The next available version number upon success
* The next available version number if _key_ doesn't exist
* The currently existing index on the server if the delete operation failed | - -## Example - - - -{`// First, get existing value -CompareExchangeValue readResult - = store.operations().send( - new GetCompareExchangeValueOperation<>(User.class, "AdminUser")); - -// Delete the key - use the index received from the 'Get' operation -CompareExchangeResult deleteResult - = store.operations().send( - new DeleteCompareExchangeValueOperation<>(User.class, "AdminUser", readResult.getIndex())); - -// The delete result is successful only if the index has not changed between the read and delete operations -boolean deleteResultSuccessful = deleteResult.isSuccessful(); -`} - - - - - diff --git a/docs/client-api/operations/compare-exchange/_delete-compare-exchange-value-nodejs.mdx b/docs/client-api/operations/compare-exchange/_delete-compare-exchange-value-nodejs.mdx deleted file mode 100644 index 7a4b153086..0000000000 --- a/docs/client-api/operations/compare-exchange/_delete-compare-exchange-value-nodejs.mdx +++ /dev/null @@ -1,88 +0,0 @@ -import Admonition from '@theme/Admonition'; -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; -import CodeBlock from '@theme/CodeBlock'; - - - -* Use `DeleteCompareExchangeValueOperation` to delete a compare-exchange item. - -* The compare-exchange item is only deleted if the _index_ in the request is __equal__ to the current _index_ - stored on the server for the specified _key_. - -* Compare-exchange items can also be deleted via [advanced session methods](../../../client-api/session/cluster-transaction/compare-exchange.mdx), - or from the [Studio](../../../studio/database/documents/compare-exchange-view.mdx). - -* In this page: - * [Delete compare exchange item](../../../client-api/operations/compare-exchange/delete-compare-exchange-value.mdx#delete-compare-exchange-item) - * [Syntax](../../../client-api/operations/compare-exchange/delete-compare-exchange-value.mdx#syntax) - - -## Delete compare exchange item - - - -{`// Get an existing compare-exchange item -const getCmpXchgOp = new GetCompareExchangeValueOperation("johnDoe@gmail.com"); -const itemResult = await documentStore.operations.send(getCmpXchgOp); - -// Keep the item's version -const versionOfItem = itemResult.index; - -// Delete the compare-exchange item: -// ================================= - -// Define the delete compare-exchange operation, pass: -// * The item's KEY -// * The item's INDEX (its version) -// The compare-exchange item will only be deleted if this number -// is equal to the one stored on the server when the delete operation is executed. -const deleteCmpXchgOp = new DeleteCompareExchangeValueOperation("johnDoe@gmail.com", versionOfItem); - -// Execute the operation by passing it to operations.send -const deleteResult = await documentStore.operations.send(deleteCmpXchgOp); - -// Verify delete results: -assert.ok(deleteResult.successful); -`} - - - - - -## Syntax - - - -{`const deleteCmpXchgOp = new DeleteCompareExchangeValueOperation(key, index, clazz?); -`} - - - -| Parameter | Type | Description | -|-------------|--------|------------------------------------------------------------------------------| -| __key__ | string | The key of the item to be deleted | -| __index__ | number | The version number of the item to be deleted | -| __clazz__ | object | When the item's value is a class, you can specify its type in this parameter | - - - -{`// Return value of store.operations.send(deleteCmpXchgOp) -// ====================================================== -class CompareExchangeResult \{ - successful; - value; - index; -\} -`} - - - -| Return Value | Type | Description | -|----------------|---------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| __successful__ | boolean |
  • `true` if the delete operation was successfully completed.
  • `true` if _key_ doesn't exist.
  • `false` if the delete operation has failed, e.g. when the index version doesn't match.
| -| __value__ | object |
  • The value that was deleted upon a successful delete.
  • `null` if _key_ doesn't exist.
  • The currently existing _value_ on the server if the delete operation has failed.
| -| __index__ | number |
  • The next available version number upon success.
  • The next available version number if _key_ doesn't exist.
  • The currently existing _index_ on the server if the delete operation has failed.
| - - - diff --git a/docs/client-api/operations/compare-exchange/_get-compare-exchange-value-csharp.mdx b/docs/client-api/operations/compare-exchange/_get-compare-exchange-value-csharp.mdx deleted file mode 100644 index a7064f42be..0000000000 --- a/docs/client-api/operations/compare-exchange/_get-compare-exchange-value-csharp.mdx +++ /dev/null @@ -1,79 +0,0 @@ -import Admonition from '@theme/Admonition'; -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; -import CodeBlock from '@theme/CodeBlock'; - - - -* Use `GetCompareExchangeValueOperation` to return the saved compare-exchange _Value_ for the specified _Key_. - -* For an overview of the 'Compare Exchange' feature click: [Compare Exchange Overview](../../../client-api/operations/compare-exchange/overview.mdx) - -* In this page: - * [Syntax](../../../client-api/operations/compare-exchange/get-compare-exchange-value.mdx#syntax) - * [Example I - Value is 'long'](../../../client-api/operations/compare-exchange/get-compare-exchange-value.mdx#example-i---value-is-) - * [Example II - Value is a custom object](../../../client-api/operations/compare-exchange/get-compare-exchange-value.mdx#example-ii---value-is-a-custom-object) - -## Syntax - -**Method**: - - -{`public GetCompareExchangeValueOperation(string key) -`} - - - -**Returned object**: - - -{`public class CompareExchangeValue -\{ - public readonly string Key; - public readonly T Value; - public readonly long Index; -\} -`} - - - -| Parameter | Type | Description | -|-----------|--------|--------------------------------------------------------------------------| -| **Key** | string | The unique object identifier | -| **Value** | `T` | The existing value that _Key_ has | -| **Index** | long | The version number of the _Value_ that is stored for the specified _Key_ | - - -You can also get compare exchange values through the [session cluster transactions](../../../client-api/session/cluster-transaction/compare-exchange.mdx#get-compare-exchange) -at `session.Advanced.ClusterTransaction`. - -This method also exposes methods getting compare exchange [lazily](../../../client-api/session/cluster-transaction/compare-exchange.mdx#get-compare-exchange). - - - - -## Example I - Value is 'long' - - -{`CompareExchangeValue readResult = - store.Operations.Send(new GetCompareExchangeValueOperation("NextClientId")); - -long value = readResult.Value; -`} - - - - -## Example II - Value is a custom object - - -{`CompareExchangeValue readResult = - store.Operations.Send(new GetCompareExchangeValueOperation("AdminUser")); - -User admin = readResult.Value; -`} - - - - - diff --git a/docs/client-api/operations/compare-exchange/_get-compare-exchange-value-java.mdx b/docs/client-api/operations/compare-exchange/_get-compare-exchange-value-java.mdx deleted file mode 100644 index c9f10aa447..0000000000 --- a/docs/client-api/operations/compare-exchange/_get-compare-exchange-value-java.mdx +++ /dev/null @@ -1,100 +0,0 @@ -import Admonition from '@theme/Admonition'; -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; -import CodeBlock from '@theme/CodeBlock'; - - - -* Use `GetCompareExchangeValueOperation` to return the saved compare-exchange _Value_ for the specified _Key_. - -* For an overview of the 'Compare Exchange' feature click: [Compare Exchange Overview](../../../client-api/operations/compare-exchange/overview.mdx) - -* In this page: - * [Syntax](../../../client-api/operations/compare-exchange/get-compare-exchange-value.mdx#syntax) - * [Example I - Value is 'long'](../../../client-api/operations/compare-exchange/get-compare-exchange-value.mdx#example-i---value-is-) - * [Example II - Value is a custom object](../../../client-api/operations/compare-exchange/get-compare-exchange-value.mdx#example-ii---value-is-a-custom-object) - -## Syntax - -**Method**: - - -{`GetCompareExchangeValueOperation(Class clazz, String key); -`} - - - -**Returned object**: - - -{`public class CompareExchangeValue \{ - private String key; - private long index; - private T value; - - public CompareExchangeValue(String key, long index, T value) \{ - this.key = key; - this.index = index; - this.value = value; - \} - - public String getKey() \{ - return key; - \} - - public void setKey(String key) \{ - this.key = key; - \} - - public long getIndex() \{ - return index; - \} - - public void setIndex(long index) \{ - this.index = index; - \} - - public T getValue() \{ - return value; - \} - - public void setValue(T value) \{ - this.value = value; - \} -\} -`} - - - -| Parameter | Type | Description | -|-----------|--------|--------------------------------------------------------------------------| -| **Key** | String | The unique object identifier | -| **Value** | `T` | The existing value that _Key_ has | -| **Index** | long | The version number of the _Value_ that is stored for the specified _Key_ | - -## Example I - Value is 'long' - - -{`CompareExchangeValue readResult - = store.operations().send(new GetCompareExchangeValueOperation<>(Long.class, "nextClientId")); - -Long value = readResult.getValue(); -`} - - - - -## Example II - Value is a custom object - - -{`CompareExchangeValue readResult - = store.operations().send( - new GetCompareExchangeValueOperation<>(User.class, "AdminUser")); - -User admin = readResult.getValue(); -`} - - - - - diff --git a/docs/client-api/operations/compare-exchange/_get-compare-exchange-value-nodejs.mdx b/docs/client-api/operations/compare-exchange/_get-compare-exchange-value-nodejs.mdx deleted file mode 100644 index bdb6252636..0000000000 --- a/docs/client-api/operations/compare-exchange/_get-compare-exchange-value-nodejs.mdx +++ /dev/null @@ -1,160 +0,0 @@ -import Admonition from '@theme/Admonition'; -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; -import CodeBlock from '@theme/CodeBlock'; - - - -* Use the `GetCompareExchangeValueOperation` operation to get a compare-exchange item by its _key_. - -* Compare-exchange items can also be managed via [advanced session methods](../../../client-api/session/cluster-transaction/compare-exchange.mdx), - which also expose getting the compare-exchange lazily, - or from the [Studio](../../../studio/database/documents/compare-exchange-view.mdx). - -* In this page: - * [Examples](../../../client-api/operations/compare-exchange/get-compare-exchange-value.mdx#examples) - * [Get cmpXchg item that has a number and metadata](../../../client-api/operations/compare-exchange/get-compare-exchange-value.mdx#get-cmpxchg-item-that-has-a-number-and-metadata) - * [Get cmpXchg item that has an object](../../../client-api/operations/compare-exchange/get-compare-exchange-value.mdx#get-cmpxchg-item-that-has-an-object) - * [Syntax](../../../client-api/operations/compare-exchange/get-compare-exchange-value.mdx#syntax) - - -## Examples - - - - __Get cmpXchg item that has a number and metadata__: - - -{`// Put a new compare-exchange item, -// e.g. save the number of sales made by an employee as the value + some metadata info -const putCmpXchgOp = new PutCompareExchangeValueOperation("employees/1-A", 12345, 0,\{ - "Department": "Sales", - "Role": "Salesperson", -\}); -const result = await documentStore.operations.send(putCmpXchgOp); - -// Get the compare-exchange item: -// ============================== - -// Define the get compare-exchange operation, pass the unique item key -const getCmpXchgOp = new GetCompareExchangeValueOperation("employees/1-A"); - -// Execute the operation by passing it to operations.send -const item = await documentStore.operations.send(getCmpXchgOp); - -// Access the value and metadata of the retrieved item -const numberOfSales = item.value; -const employeeRole = item.metadata["Role"]; - -// Access the version number of the retrieved item -const version = item.index; -`} - - - - - - - - __Get cmpXchg item that has an object__: - - - -{`// Put a new compare-exchange item, -// e.g. save an object as the value -const employee = new Employee(); -employee.role = "Salesperson" -employee.department = "Sales"; -employee.numberOfSales = 12345; - -const putCmpXchgOp = new PutCompareExchangeValueOperation("employees/1-A", employee, 0); -const result = await documentStore.operations.send(putCmpXchgOp); - -// Get the compare-exchange item: -// ============================== - -// Define the get compare-exchange operation, pass the unique item key & the class type -const getCmpXchgOp = new GetCompareExchangeValueOperation("employees/1-A", Employee); - -// Execute the operation by passing it to operations.send -const item = await documentStore.operations.send(getCmpXchgOp); - -// Access the value of the retrieved item -const employeeResult = item.value; -const employeeClass = employeeResult.constructor; // Employee - -const employeeRole = employeeResult.role; // Salesperson -const employeeDep = employeeResult.department; // Sales -const employeeSales = employeeResult.numberOfSales; // 12345 - -// Access the version number of the retrieved item -const version = item.index; -`} - - - - -{`class Employee { - constructor( - id = null, - department = "", - role = "", - numberOfSales = 0 - - ) { - Object.assign(this, { - id, - department, - role, - numberOfSales - }); - } -} -`} - - - - - - - - -## Syntax - - - -{`const getCmpXchgOp = new GetCompareExchangeValueOperation(key, clazz, materializeMetadata); -`} - - - -| Parameter | Type | Description | -|-------------------------|-----------|-----------------------------------------------------------------------------------------------------------------| -| __key__ | `string` | The unique identifier of the cmpXchg item. | -| __clazz__ | `object` | The class type of the item's value. | -| __materializeMetadata__ | `boolean` | The Metadata will be retrieved and available regardless of the value of this param. Used for internal purposes. | - - - -{`// Return value of store.operations.send(getCmpXchgOp) -// =================================================== -class CompareExchangeValue \{ - key; - value; - metadata; - index; -\} -`} - - - -| Parameter | Type | Description | -|--------------|----------|-------------------------------------------------------| -| __key__ | `string` | The unique identifier of the cmpXchg item. | -| __value__ | `object` | The existing `value` of the returned cmpXchg item. | -| __metadata__ | `object` | The existing `metadata` of the returned cmpXchg item. | -| __index__ | `number` | The compare-exchange item's version. | - - - - diff --git a/docs/client-api/operations/compare-exchange/_get-compare-exchange-values-csharp.mdx b/docs/client-api/operations/compare-exchange/_get-compare-exchange-values-csharp.mdx deleted file mode 100644 index d52ff3ebf2..0000000000 --- a/docs/client-api/operations/compare-exchange/_get-compare-exchange-values-csharp.mdx +++ /dev/null @@ -1,88 +0,0 @@ -import Admonition from '@theme/Admonition'; -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; -import CodeBlock from '@theme/CodeBlock'; - - - -* Use `GetCompareExchangeValuesOperation` to return the saved compare-exchange _Values_ for the specified _Keys_. - -* For an overview of the 'Compare Exchange' feature click: [Compare Exchange Overview](../../../client-api/operations/compare-exchange/overview.mdx) - -* In this page: - * [Syntax](../../../client-api/operations/compare-exchange/get-compare-exchange-values.mdx#syntax) - * [Example I - Get Values for Specified Keys](../../../client-api/operations/compare-exchange/get-compare-exchange-values.mdx#example-i---get-values-for-specified-keys) - * [Example II - Get Values for Keys with Common Prefix](../../../client-api/operations/compare-exchange/get-compare-exchange-values.mdx#example-ii---get-values-for-keys-with-common-prefix) - -## Syntax - -**Methods**: - - - -{`public GetCompareExchangeValuesOperation(string startWith, int? start = null, int? pageSize = null) -`} - - - -| Parameters | Type | Description | -| ------------- | ------------- | ----- | -| **startWith** | string | A common prefix for those keys whose values should be returned | -| **start** | int | The number of items that should be skipped | -| **pageSize** | int | The maximum number of values that will be retrieved | - -**Returned object**: - - - -{`public class CompareExchangeValue -\{ - public readonly string Key; - public readonly T Value; - public readonly long Index; -\} -`} - - - -| Return Value | Description | -| ------------- | ----- | -| `Dictionary>` | A Dictionary containing _'Key'_ to _'CompareExchangeValue'_ associations | - - -You can also get compare exchange values through the [session cluster transactions](../../../client-api/session/cluster-transaction/compare-exchange.mdx#get-compare-exchange) -at `session.Advanced.ClusterTransaction`. - -This method also exposes methods getting compare exchange [lazily](../../../client-api/session/cluster-transaction/compare-exchange.mdx#get-compare-exchange). - - - - -## Example I - Get Values for Specified Keys - - - -{`Dictionary> compareExchangeValues - = store.Operations.Send( - new GetCompareExchangeValuesOperation(new[] \{ "Key-1", "Key-2" \})); -`} - - - - - -## Example II - Get Values for Keys with Common Prefix - - - -{`// Get values for keys that have the common prefix 'users' -// Retrieve maximum 20 entries -Dictionary> compareExchangeValues - = store.Operations.Send(new GetCompareExchangeValuesOperation("users", 0, 20)); -`} - - - - - - diff --git a/docs/client-api/operations/compare-exchange/_get-compare-exchange-values-java.mdx b/docs/client-api/operations/compare-exchange/_get-compare-exchange-values-java.mdx deleted file mode 100644 index 7ea4a14f70..0000000000 --- a/docs/client-api/operations/compare-exchange/_get-compare-exchange-values-java.mdx +++ /dev/null @@ -1,116 +0,0 @@ -import Admonition from '@theme/Admonition'; -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; -import CodeBlock from '@theme/CodeBlock'; - - - -* Use `GetCompareExchangeValuesOperation` to return the saved compare-exchange _Values_ for the specified _Keys_. - -* For an overview of the 'Compare Exchange' feature click: [Compare Exchange Overview](../../../client-api/operations/compare-exchange/overview.mdx) - -* In this page: - * [Syntax](../../../client-api/operations/compare-exchange/get-compare-exchange-values.mdx#syntax) - * [Example I - Get Value for Specified Keys](../../../client-api/operations/compare-exchange/get-compare-exchange-values.mdx#example-i---get-values-for-specified-keys) - * [Example II - Get Values for Keys with Common Prefix](../../../client-api/operations/compare-exchange/get-compare-exchange-values.mdx#example-ii---get-values-for-keys-with-common-prefix) - -## Syntax - -**Methods**: - - -{`GetCompareExchangeValuesOperation(Class clazz, String[] keys); -`} - - - - - -{`GetCompareExchangeValuesOperation(Class clazz, String startWith) -GetCompareExchangeValuesOperation(Class clazz, String startWith, Integer start) -GetCompareExchangeValuesOperation(Class clazz, String startWith, Integer start, Integer pageSize) -`} - - - -| Parameters | Type | Description | -| ------------- | ------------- | ----- | -| **keys** | String[] | List of keys to get | -| **startWith** | String | A common prefix for those keys whose values should be returned | -| **start** | int | The number of items that should be skipped | -| **pageSize** | int | The maximum number of values that will be retrieved | - -**Returned object**: - - -{`public class CompareExchangeValue \{ - private String key; - private long index; - private T value; - - public CompareExchangeValue(String key, long index, T value) \{ - this.key = key; - this.index = index; - this.value = value; - \} - - public String getKey() \{ - return key; - \} - - public void setKey(String key) \{ - this.key = key; - \} - - public long getIndex() \{ - return index; - \} - - public void setIndex(long index) \{ - this.index = index; - \} - - public T getValue() \{ - return value; - \} - - public void setValue(T value) \{ - this.value = value; - \} -\} -`} - - - -| Return Value | Description | -| ------------- | ----- | -| `Map>` | A map containing _'Key'_ to _'CompareExchangeValue'_ associations | - - -## Example I - Get Values for Specified Keys - - - -{`Map> compareExchangeValues - = store.operations().send( - new GetCompareExchangeValuesOperation<>(String.class, new String[]\{"Key-1", "Key-2"\})); -`} - - - - -## Example II - Get Values for Keys with Common Prefix - - - -{`// Get values for keys that have the common prefix 'users' -// Retrieve maximum 20 entries -Map> compareExchangeValues - = store.operations().send( - new GetCompareExchangeValuesOperation<>(User.class, "users", 0, 20)); -`} - - - - - diff --git a/docs/client-api/operations/compare-exchange/_get-compare-exchange-values-nodejs.mdx b/docs/client-api/operations/compare-exchange/_get-compare-exchange-values-nodejs.mdx deleted file mode 100644 index 45ad9102ca..0000000000 --- a/docs/client-api/operations/compare-exchange/_get-compare-exchange-values-nodejs.mdx +++ /dev/null @@ -1,140 +0,0 @@ -import Admonition from '@theme/Admonition'; -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; -import CodeBlock from '@theme/CodeBlock'; - - - -* Use `GetCompareExchangeValuesOperation` to get multiple compare-exchange items by specifying either: - * List of keys - * A common prefix for keys to retrieve - -* Compare-exchange items can also be managed via [advanced session methods](../../../client-api/session/cluster-transaction/compare-exchange.mdx), - which also expose getting the compare-exchange lazily, - or from the [Studio](../../../studio/database/documents/compare-exchange-view.mdx). - -* In this page: - * [Examples](../../../client-api/operations/compare-exchange/get-compare-exchange-values.mdx#examples) - * [Get cmpXchg items for specified keys](../../../client-api/operations/compare-exchange/get-compare-exchange-values.mdx#get-cmpxchg-items-for-specified-keys) - * [Get cmpXchg items for keys with common prefix](../../../client-api/operations/compare-exchange/get-compare-exchange-values.mdx#get-cmpxchg-items-for-keys-with-common-prefix) - * [Syntax](../../../client-api/operations/compare-exchange/get-compare-exchange-values.mdx#syntax) - - -## Examples - - - - __Get cmpXchg items for specified keys__: - - -{`// Save some new compare-exchange items -await documentStore.operations.send( - new PutCompareExchangeValueOperation("employees/1-A", "someValue1", 0)); -await documentStore.operations.send( - new PutCompareExchangeValueOperation("employees/2-A", "someValue2", 0)); -await documentStore.operations.send( - new PutCompareExchangeValueOperation("employees/3-A", "someValue3", 0)); - -// Get multiple compare-exchange items by specifying keys: -// ======================================================= - -// Define the get compare-exchange operation, -// Specify the keys of the items to get -const getCmpXchgOp = new GetCompareExchangeValuesOperation(\{ - keys: ["employees/1-A", "employees/3-A"] -\}); - -// Execute the operation by passing it to operations.send -const items = await documentStore.operations.send(getCmpXchgOp); - -// Access the returned items: -assert.equal(Object.keys(items).length, 2); -assert.equal(items["employees/1-A"].value, "someValue1"); -assert.equal(items["employees/3-A"].value, "someValue3"); -`} - - - - - - - - __Get cmpXchg items for keys with common prefix__: - - -{`// Get multiple compare-exchange items with common key prefix: -// =========================================================== - -// Define the get compare-exchange operation, specify: -// * startWith: The common key prefix -// * pageSize: Max items to get (this is optional) -// * start: The start position (this is optional) -const getCmpXchgOp = new GetCompareExchangeValuesOperation(\{ - startWith: "employees", - pageSize: 10, - start: 0 -\}); - -// Execute the operation by passing it to operations.send -const items = await documentStore.operations.send(getCmpXchgOp); - -// Results will include only cmpXchg items with keys that start with "employees" -`} - - - - - - - -## Syntax - - - -{`const getCmpXchgOp = new GetCompareExchangeValuesOperation(parameters); -`} - - - - - -{`// the parameters object: -\{ - // Keys of the items to retrieve - keys?; // string[] - - // The common key prefix of the items to retrieve - startWith?; // string - - // The number of items that should be skipped - start?; // number - - // The maximum number of values that will be retrieved - pageSize?; // number - - // When the item's value is a class, you can specify its type in this parameter - clazz?; // object -\} -`} - - - -| Return value | Description | -|--------------------------------------------|------------------------------------------------------| -| `Dictionary` | A Dictionary with a compare-exchange value per _key_ | - - - -{`class CompareExchangeValue \{ - key; - value; - metadata; - index; -\} -`} - - - - - - diff --git a/docs/client-api/operations/compare-exchange/_include-compare-exchange-csharp.mdx b/docs/client-api/operations/compare-exchange/_include-compare-exchange-csharp.mdx deleted file mode 100644 index c49cfc40c6..0000000000 --- a/docs/client-api/operations/compare-exchange/_include-compare-exchange-csharp.mdx +++ /dev/null @@ -1,180 +0,0 @@ -import Admonition from '@theme/Admonition'; -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; -import CodeBlock from '@theme/CodeBlock'; - - - -* Compare-Exchange items can be included when [loading entities](../../../client-api/session/loading-entities.mdx) - and when making [queries](../../../client-api/session/querying/how-to-query.mdx). - -* The Session [tracks](../../../client-api/session/what-is-a-session-and-how-does-it-work.mdx#tracking-changes) - the included Compare Exchange items which means their value can be accessed - in that Session without making an additional call to the server. - -* The Compare Exchange include syntax is based on the [include method](../../../client-api/how-to/handle-document-relationships.mdx#includes). - -* In this page: - * [Syntax](../../../client-api/operations/compare-exchange/include-compare-exchange.mdx#syntax) - * [Examples](../../../client-api/operations/compare-exchange/include-compare-exchange.mdx#examples) - - -## Syntax - -#### Include method - -Chain the method `IncludeCompareExchangeValue()` to include compare exchange values -along with `Session.Load()` or LINQ queries. - - - -{`public interface ICompareExchangeValueIncludeBuilder -\{ - TBuilder IncludeCompareExchangeValue(string path); - - TBuilder IncludeCompareExchangeValue(Expression> path); - - TBuilder IncludeCompareExchangeValue(Expression>> path); -\} -`} - - - -| Parameter | Type | Description | -|-----------|-------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------| -| **path** | `string`;
`Expression>`;
`Expression>>` | The key of the compare exchange value you want to include, a path to the key, or a path to a `string[]` of keys. | - -#### RQL - -In an RQL query, you can use the [`include` keyword](../../../client-api/session/querying/what-is-rql.mdx#include) -followed by `cmpxchg()` to include a compare exchange value: - - - -{`include cmpxchg(key) -`} - - - -In [javascript functions](../../../client-api/session/querying/what-is-rql.mdx#declare) within queries, -use `includes.cmpxchg()` (see the RawQuery example -[below](../../../client-api/operations/compare-exchange/include-compare-exchange.mdx#examples)): - - - -{`includes.cmpxchg(key); -`} - - - -| Parameter | Type | Description | -|-----------|----------------------------------|----------------------------------------------------------------------------------| -| **key** | `string`;
path to `string` | The key of the compare exchange value you want to include, or a path to the key. | - -## Examples - - - - -{`using (IDocumentSession session = documentStore.OpenSession()) -{ - // Load a user document, include the compare exchange value - // with the key equal to the user's Name field - var user = session.Load("users/1-A", - includes => includes.IncludeCompareExchangeValue( - x => x.Name)); - - // Getting the compare exchange value does not require - // another call to the server - var value = session.Advanced.ClusterTransaction - .GetCompareExchangeValue(user.Name); -} -`} - - - - -{`using (IAsyncDocumentSession session = documentStore.OpenAsyncSession()) -{ - // Load a user document, include the compare exchange value - // with the key equal to the user's Name field - var user = await session.LoadAsync("users/1-A", - includes => includes.IncludeCompareExchangeValue( - x => x.Name)); - - // Getting the compare exchange value does not require - // another call to the server - var value = await session.Advanced.ClusterTransaction - .GetCompareExchangeValueAsync(user.Name); -} -`} - - - - -{`var users = session.Query() - .Include(builder => builder.IncludeCompareExchangeValue(x => x.Name)) - .ToList(); - -List> compareExchangeValues = null; - -// Getting the compare exchange values does not require -// additional calls to the server -foreach (User u in users){ - compareExchangeValues.Add(session.Advanced.ClusterTransaction - .GetCompareExchangeValue(u.Name)); -} -`} - - - - -{`// First method: from body of query -var users1 = session.Advanced - .RawQuery(@" - from Users as u - select u - include cmpxchg(u.Name)" - ) - .ToList(); - -List> compareExchangeValues1 = null; - -// Getting the compare exchange values does not require -// additional calls to the server -foreach (User u in users1){ - compareExchangeValues1.Add(session.Advanced.ClusterTransaction - .GetCompareExchangeValue(u.Name)); -} - - -// Second method: from a JavaScript function -var users2 = session.Advanced - .RawQuery(@" - declare function includeCEV(user) { - includes.cmpxchg(user.Name); - return user; - } - - from Users as u - select includeCEV(u)" - ) - .ToList(); -// Note that includeCEV() returns the same User -// entity it received, without modifying it - -List> compareExchangeValues2 = null; - -// Does not require calls to the server -foreach (User u in users2){ - compareExchangeValues2.Add(session.Advanced.ClusterTransaction - .GetCompareExchangeValue(u.Name)); -} -`} - - - - - - - diff --git a/docs/client-api/operations/compare-exchange/_overview-csharp.mdx b/docs/client-api/operations/compare-exchange/_overview-csharp.mdx deleted file mode 100644 index fd2c75702b..0000000000 --- a/docs/client-api/operations/compare-exchange/_overview-csharp.mdx +++ /dev/null @@ -1,326 +0,0 @@ -import Admonition from '@theme/Admonition'; -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; -import CodeBlock from '@theme/CodeBlock'; - - - -* Compare Exchange items are **key/value pairs** where the key is unique across your database. - -* Compare-exchange operations require cluster consensus to ensure consistency across all nodes. - Once a consensus is reached, the compare-exchange items are distributed through the Raft algorithm to all nodes in the database group. - -* Compare-exchange items can be used to coordinate work between sessions that are trying to modify a shared resource (such as a document) at the same time. - -* Compare-exchange items are [not replicated externally](../../../client-api/operations/compare-exchange/overview.mdx#why-compare-exchange-items-are-not-replicated-to-external-databases) to other databases. - -* In this page: -* [What Compare Exchange Items Are](../../../client-api/operations/compare-exchange/overview.mdx#what-compare-exchange-items-are) -* [Creating and Managing Compare-Exchange Items](../../../client-api/operations/compare-exchange/overview.mdx#creating-and-managing-compare-exchange-items) -* [Why Compare-Exchange Items are Not Replicated to External Databases](../../../client-api/operations/compare-exchange/overview.mdx#why-compare-exchange-items-are-not-replicated-to-external-databases) -* [Example I - Email Address Reservation](../../../client-api/operations/compare-exchange/overview.mdx#example-i---email-address-reservation) -* [Example II - Reserve a Shared Resource](../../../client-api/operations/compare-exchange/overview.mdx#example-ii---reserve-a-shared-resource) -* [Example III - Ensuring Unique Values without Using Compare Exchange](../../../client-api/operations/compare-exchange/overview.mdx#example-iii---ensuring-unique-values-without-using-compare-exchange) - - -## What Compare Exchange Items Are - -Compare Exchange items are key/value pairs where the key servers a unique value across your database. - -* Each compare-exchange item contains: - * **A key** - A unique string identifier in the database scope. - * **A value** - Can be any object (a number, string, array, or any valid JSON object). - * **Metadata** - Data that is associated with the compare-exchange item. - Must be a valid JSON object. - * For example, the metadata can be used to set expiration time for the compare-exchange item. - Learn more in [compare-exchange expiration](../../../client-api/operations/compare-exchange/compare-exchange-expiration.mdx). - * **Raft index** - The compare-exchange item's version. - Any change to the value or metadata will increase this number. - -* Creating and modifying a compare-exchange item is an atomic, thread-safe [compare-and-swap](https://en.wikipedia.org/wiki/Compare-and-swap) interlocked - compare-exchange operation. - - - -## Creating and Managing Compare-Exchange Items - -Compare exchange items are created and managed with any of the following approaches: - -* **Document Store Operations** - You can manage a compare-exchange item as an [Operation on the document store](../../../client-api/operations/compare-exchange/put-compare-exchange-value.mdx). - This can be done within or outside of a session (cluster-wide or single-node session). - * When inside a session: - If the session fails, the compare-exchange operation can still succeed - because store Operations do not rely on the success of the session. - You will need to delete the compare-exchange item explicitly upon session failure if you don't want the compare-exchange item to persist. - -* **Cluster-Wide Sessions** - You can manage a compare-exchange item from inside a [Cluster-Wide session](../../../client-api/session/cluster-transaction/compare-exchange.mdx). - If the session fails, the compare-exchange item creation also fails. - None of the nodes in the group will have the new compare-exchange item. - - -* **Atomic Guards** - When creating documents using a cluster-wide session RavenDB automatically creates [Atomic Guards](../../../client-api/session/cluster-transaction/atomic-guards.mdx), - which are compare-exchange items that guarantee ACID transactions. - See [Cluster-wide vs. Single-node](../../../client-api/session/cluster-transaction/overview.mdx#cluster-wide-transaction-vs-single-node-transaction) for a session comparision overview. - -* **Studio** - Compare-exchange items can be created from the [Studio](../../../studio/database/documents/compare-exchange-view.mdx#the-compare-exchange-view) as well. - - - -## Why Compare-Exchange Items are Not Replicated to External Databases - -* Each cluster defines its policies and configurations, and should ideally have sole responsibility for managing its own documents. - Read [Consistency in a Globally Distributed System](https://ayende.com/blog/196769-B/data-ownership-in-a-distributed-system) - to learn more about why global database modeling is more efficient this way. - -* When creating a compare-exchange item a Raft consensus is required from the nodes in the database group. - Externally replicating such data is problematic as the target database may reside within a cluster that is in an - unstable state where Raft decisions cannot be made. In such a state, the compare-exchange item will not be persisted in the target database. - -* Conflicts between documents that occur between two databases are solved with the help of the documents - Change-Vector. Compare-exchange conflicts cannot be handled properly as they do not have a similar - mechanism to resolve conflicts. - -* To ensure unique values between two databases without using compare-exchange items see [Example III](../../../client-api/operations/compare-exchange/overview.mdx#example-iii---ensuring-unique-values-without-using-compare-exchange). - - - -## Example I - Email Address Reservation - -The following example shows how to use compare-exchange to create documents with unique values. -The scope is within the database group on a single cluster. - -Compare-exchange items are not externally replicated to other databases. -To establish uniqueness without using compare-exchange see [Example III](../../../client-api/operations/compare-exchange/overview.mdx#example-iii---ensuring-unique-values-without-using-compare-exchange). - - - -{`string email = "user@example.com"; - -User user = new User -\{ - Email = email -\}; - -using (IDocumentSession session = store.OpenSession()) -\{ - session.Store(user); - // At this point, the user document has an Id assigned - - // Try to reserve a new user email - // Note: This operation takes place outside of the session transaction, - // It is a cluster-wide reservation - CompareExchangeResult cmpXchgResult - = store.Operations.Send( - new PutCompareExchangeValueOperation("emails/" + email, user.Id, 0)); - - if (cmpXchgResult.Successful == false) - throw new Exception("Email is already in use"); - - // At this point we managed to reserve/save the user email - - // The document can be saved in SaveChanges - session.SaveChanges(); -\} -`} - - - -**Implications**: - -* The `User` object is saved as a document, hence it can be indexed, queried, etc. - -* This compare-exchange item was [created as an operation](../../../client-api/operations/compare-exchange/put-compare-exchange-value.mdx) - rather than with a [cluster-wide session](../../../client-api/session/cluster-transaction/overview.mdx). - Thus, if `session.SaveChanges` fails, then the email reservation is _not_ rolled back automatically. - It is your responsibility to do so. - -* The compare-exchange value that was saved can be accessed in a query using `CmpXchg`: - - - -{`var query = from u in session.Query() - where u.Id == RavenQuery.CmpXchg("emails/ayende@ayende.com") - select u; -`} - - - - -{`var q = session.Advanced - .DocumentQuery() - .WhereEquals("Id", CmpXchg.Value("emails/ayende@ayende.com")); -`} - - - - -{`from Users as s where id() == cmpxchg("emails/ayende@ayende.com") -`} - - - - - -## Example II - Reserve a Shared Resource - -In the following example, we use compare-exchange to reserve a shared resource. -The scope is within the database group on a single cluster. - -The code also checks for clients which never release resources (i.e. due to failure) by using timeout. - - - -{`private class SharedResource -\{ - public DateTime? ReservedUntil \{ get; set; \} -\} - -public void PrintWork() -\{ - // Try to get hold of the printer resource - long reservationIndex = LockResource(store, "Printer/First-Floor", TimeSpan.FromMinutes(20)); - - try - \{ - // Do some work for the duration that was set. - // Don't exceed the duration, otherwise resource is available for someone else. - \} - finally - \{ - ReleaseResource(store, "Printer/First-Floor", reservationIndex); - \} -\} - -public long LockResource(IDocumentStore store, string resourceName, TimeSpan duration) -\{ - while (true) - \{ - DateTime now = DateTime.UtcNow; - - SharedResource resource = new SharedResource - \{ - ReservedUntil = now.Add(duration) - \}; - - CompareExchangeResult saveResult = store.Operations.Send( - new PutCompareExchangeValueOperation(resourceName, resource, 0)); - - if (saveResult.Successful) - \{ - // resourceName wasn't present - we managed to reserve - return saveResult.Index; - \} - - // At this point, Put operation failed - someone else owns the lock or lock time expired - if (saveResult.Value.ReservedUntil < now) - \{ - // Time expired - Update the existing key with the new value - CompareExchangeResult takeLockWithTimeoutResult = store.Operations.Send( - new PutCompareExchangeValueOperation(resourceName, resource, saveResult.Index)); - - if (takeLockWithTimeoutResult.Successful) - \{ - return takeLockWithTimeoutResult.Index; - \} - \} - - // Wait a little bit and retry - Thread.Sleep(20); - \} -\} - -public void ReleaseResource(IDocumentStore store, string resourceName, long index) -\{ - CompareExchangeResult deleteResult - = store.Operations.Send(new DeleteCompareExchangeValueOperation(resourceName, index)); - - // We have 2 options here: - // deleteResult.Successful is true - we managed to release resource - // deleteResult.Successful is false - someone else took the lock due to timeout -\} -`} - - - - - -## Example III - Ensuring Unique Values without Using Compare Exchange - -Unique values can also be ensured without using compare-exchange. - -The below example shows how to achieve that by using **reference documents**. -The reference documents' IDs will contain the unique values instead of the compare-exchange items. - -Using reference documents is especially useful when [External Replication](../../../server/ongoing-tasks/external-replication.mdx) -is defined between two databases that need to be synced with unique values. -The reference documents will replicate to the destination database, -as opposed to compare-exchange items, which are not externally replicated. - - -Sessions which process fields that must be unique should be set to [TransactionMode.ClusterWide](../../../client-api/session/cluster-transaction/overview.mdx). - - - - -{`// When you create documents that must contain a unique value such as a phone or email, etc., -// you can create reference documents that will have that unique value in their IDs. -// To know if a value already exists, all you need to do is check whether a reference document with such ID exists. - -// The reference document class -class UniquePhoneReference -\{ - public class PhoneReference - \{ - public string Id; - public string CompanyId; - \} - - static void Main(string[] args) - \{ - // A company document class that must be created with a unique 'Phone' field - Company newCompany = new Company - \{ - Name = "companyName", - Phone = "phoneNumber", - Contact = new Contact - \{ - Name = "contactName", - Title = "contactTitle" - \}, - \}; - - void CreateCompanyWithUniquePhone(Company newCompany) - \{ - // Open a cluster-wide session in your document store - using var session = DocumentStoreHolder.Store.OpenSession( - new SessionOptions \{ TransactionMode = TransactionMode.ClusterWide \} - ); - - // Check whether the new company phone already exists - // by checking if there is already a reference document that has the new phone in its ID. - var phoneRefDocument = session.Load("phones/" + newCompany.Phone); - if (phoneRefDocument != null) - \{ - var msg = $"Phone '\{newCompany.Phone\}' already exists in ID: \{phoneRefDocument.CompanyId\}"; - throw new ConcurrencyException(msg); - \} - - // If the new phone number doesn't already exist, store the new entity - session.Store(newCompany); - // Store a new reference document with the new phone value in its ID for future checks. - session.Store(new PhoneReference \{ CompanyId = newCompany.Id \}, "phones/" + newCompany.Phone); - - // May fail if called concurrently with the same phone number - session.SaveChanges(); - \} - \} -\} -`} - - - - - diff --git a/docs/client-api/operations/compare-exchange/_overview-java.mdx b/docs/client-api/operations/compare-exchange/_overview-java.mdx deleted file mode 100644 index c3f98188d2..0000000000 --- a/docs/client-api/operations/compare-exchange/_overview-java.mdx +++ /dev/null @@ -1,219 +0,0 @@ -import Admonition from '@theme/Admonition'; -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; -import CodeBlock from '@theme/CodeBlock'; - - - -* The **Compare Exchange** feature allows you to perform cluster-wide _interlocked_ distributed operations. - -* Unique **Keys** can be reserved in the [Database Group](../../../studio/database/settings/manage-database-group.mdx) accross the cluster. - Each key has an associated **Value**. - -* Modifying these values is an ***interlocked compare exchange*** operation. - -* Once defined, the Compare Exchange Values can be accessed via [GetCompareExchangeValuesOperation](../../../client-api/operations/compare-exchange/get-compare-exchange-values.mdx), - or by using RQL in a query (see example-I below) - - -Compare exchange key/value pairs can be created and managed explicitly in your code. -Starting from RavenDB 5.2, they can also be created and managed automatically by RavenDB. -Compare exchange entries that are automatically administered by RavenDB are called -**Atomic Guards**, read more about them [here](../../../client-api/session/cluster-transaction/atomic-guards.mdx). - - - -* In this page: - * [Compare Exchange Transaction Scope](../../../client-api/operations/compare-exchange/overview.mdx#compare-exchange-transaction-scope) - * [Creating a Key](../../../client-api/operations/compare-exchange/overview.mdx#creating-a-key) - * [Updating a Key](../../../client-api/operations/compare-exchange/overview.mdx#updating-a-key) - * [Example I - Email Address Reservation](../../../client-api/operations/compare-exchange/overview.mdx#example-i---email-address-reservation) - * [Example II- Reserve a Shared Resource](../../../client-api/operations/compare-exchange/overview.mdx#example-ii---reserve-a-shared-resource) - -## Compare Exchange Transaction Scope - -* Since the compare-exchange operations guarantee atomicity across the entire cluster, - the feature is **not** using the transaction associated with a session object, as a session transaction spans only a single node. - -* So if a compare-exchange operation has failed when used inside a session block, it will **not** be rolled back automatically upon a session transaction failure. - - -## Creating a Key - -* Provide the following when saving a **key**: - -| Parameter | Description | -|-----------|---------------------------------------------------------------------------------------------------------------------------------------------| -| **Key** | A string under which _Value_ is saved, unique in the database scope across the cluster. This string can be up to 512 bytes. | -| **Value** | The Value that is associated with the _Key_.
Can be a number, string, boolean, array or any JSON formatted object. | -| **Index** | The _Index_ number is indicating the version of _Value_.
The _Index_ is used for the concurrency control, similar to documents Etags. | - -* When creating a _new_ 'Compare Exchange Key', the index should be set to `0`. - -* The [Put](../../../client-api/operations/compare-exchange/put-compare-exchange-value.mdx) operation will succeed only if this key doesn't exist yet. - -* Note: Two different keys _can_ have the same values as long as the keys are unique. - - -## Updating a Key - -* Updating a 'Compare Exchange' key can be divided into 2 phases: - - 1. [Get](../../../client-api/operations/compare-exchange/get-compare-exchange-value.mdx) the existing _Key_. The associated _Value_ and _Index_ are received. - - 2. The _Index_ obtained from the read operation is provided to the [Put](../../../client-api/operations/compare-exchange/put-compare-exchange-value.mdx) operation along with the new _Value_ to be saved. - This save will succeed only if the index that is provided to the 'Put' operation is the **same** as the index that was received from the server in the previous 'Get', - which means that the _Value_ was not modified by someone else between the read and write operations. - - -## Example I - Email Address Reservation - -* Compare Exchange can be used to maintain uniqueness across users emails accounts. - -* First try to reserve a new user email. - If the email is successfully reserved then save the user account document. - - - -{`String email = "user@example.com"; - -User user = new User(); -user.setEmail(email); - -try (IDocumentSession session = store.openSession()) \{ - session.store(user); - - // At this point, the user document has an Id assigned - - // Try to reserve a new user email - // Note: This operation takes place outside of the session transaction, - // It is a cluster-wide reservation - CompareExchangeResult cmpXchgResult = store - .operations().send( - new PutCompareExchangeValueOperation<>( - "emails/" + email, user.getId(), 0)); - - if (!cmpXchgResult.isSuccessful()) \{ - throw new RuntimeException("Email is already in use"); - \} - - // At this point we managed to reserve/save the user email - - // The document can be saved in SaveChanges - session.saveChanges(); -\} -`} - - - -**Implications**: - -* The `User` object is saved as a document, hence it can be indexed, queried, etc. - -* If `session.saveChanges` fails, the email reservation is _not_ rolled back automatically. It is your responsibility to do so. - -* The compare exchange value that was saved can be accessed from `RQL` in a query: - - - - -{`try (IDocumentSession session = store.openSession()) { - List query = session.advanced().rawQuery(User.class, - "from Users as s where id() == cmpxchg(\\"emails/ayende@ayende.com\\")") - .toList(); - - IDocumentQuery q = session.advanced() - .documentQuery(User.class) - .whereEquals("id", CmpXchg.value("emails/ayende@ayende.com")); -} -`} - - - - -{`from Users as s where id() == cmpxchg("emails/ayende@ayende.com") -`} - - - - - -## Example II - Reserve a Shared Resource - -* Use compare exchange for a shared resource reservation. - -* The code also checks for clients which never release resources (i.e. due to failure) by using timeout. - - - -{`private class SharedResource \{ - private LocalDateTime reservedUntil; - - public LocalDateTime getReservedUntil() \{ - return reservedUntil; - \} - - public void setReservedUntil(LocalDateTime reservedUntil) \{ - this.reservedUntil = reservedUntil; - \} -\} - -public void printWork() throws InterruptedException \{ - // Try to get hold of the printer resource - long reservationIndex = lockResource(store, "Printer/First-Floor", Duration.ofMinutes(20)); - - try \{ - // Do some work for the duration that was set. - // Don't exceed the duration, otherwise resource is available for someone else. - \} finally \{ - releaseResource(store, "Printer/First-Floor", reservationIndex); - \} -\} - -public long lockResource(IDocumentStore store, String resourceName, Duration duration) throws InterruptedException \{ - while (true) \{ - LocalDateTime now = LocalDateTime.now(); - - SharedResource resource = new SharedResource(); - resource.setReservedUntil(now.plus(duration)); - - CompareExchangeResult saveResult = - store.operations().send( - new PutCompareExchangeValueOperation(resourceName, resource, 0)); - - if (saveResult.isSuccessful()) \{ - // resourceName wasn't present - we managed to reserve - return saveResult.getIndex(); - \} - - // At this point, Put operation failed - someone else owns the lock or lock time expired - if (saveResult.getValue().reservedUntil.isBefore(now)) \{ - // Time expired - Update the existing key with the new value - CompareExchangeResult takeLockWithTimeoutResult = - store.operations().send( - new PutCompareExchangeValueOperation<>(resourceName, resource, saveResult.getIndex())); - - if (takeLockWithTimeoutResult.isSuccessful()) \{ - return takeLockWithTimeoutResult.getIndex(); - \} - \} - - // Wait a little bit and retry - Thread.sleep(20); - \} -\} - -public void releaseResource(IDocumentStore store, String resourceName, long index) \{ - CompareExchangeResult deleteResult = store - .operations().send( - new DeleteCompareExchangeValueOperation<>(SharedResource.class, resourceName, index)); - - // We have 2 options here: - // deleteResult.Successful is true - we managed to release resource - // deleteResult.Successful is false - someone else took the lock due to timeout -\} -`} - - - - - diff --git a/docs/client-api/operations/compare-exchange/_overview-php.mdx b/docs/client-api/operations/compare-exchange/_overview-php.mdx deleted file mode 100644 index 4afb502392..0000000000 --- a/docs/client-api/operations/compare-exchange/_overview-php.mdx +++ /dev/null @@ -1,366 +0,0 @@ -import Admonition from '@theme/Admonition'; -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; -import CodeBlock from '@theme/CodeBlock'; - - - -* Compare Exchange items are **key/value pairs** where the key is unique across your database. - -* Compare-exchange operations require cluster consensus to ensure consistency across all nodes. - Once a consensus is reached, the compare-exchange items are distributed through the Raft algorithm to all nodes in the database group. - -* Compare-exchange items can be used to coordinate work between sessions that are trying to modify a shared resource (such as a document) at the same time. - -* Compare-exchange items are [not replicated externally](../../../client-api/operations/compare-exchange/overview.mdx#why-compare-exchange-items-are-not-replicated-to-external-databases) to other databases. - -* In this page: -* [What Compare Exchange Items Are](../../../client-api/operations/compare-exchange/overview.mdx#what-compare-exchange-items-are) -* [Creating and Managing Compare-Exchange Items](../../../client-api/operations/compare-exchange/overview.mdx#creating-and-managing-compare-exchange-items) -* [Why Compare-Exchange Items are Not Replicated to External Databases](../../../client-api/operations/compare-exchange/overview.mdx#why-compare-exchange-items-are-not-replicated-to-external-databases) -* [Example I - Email Address Reservation](../../../client-api/operations/compare-exchange/overview.mdx#example-i---email-address-reservation) -* [Example II - Reserve a Shared Resource](../../../client-api/operations/compare-exchange/overview.mdx#example-ii---reserve-a-shared-resource) -* [Example III - Ensuring Unique Values without Using Compare Exchange](../../../client-api/operations/compare-exchange/overview.mdx#example-iii---ensuring-unique-values-without-using-compare-exchange) - - -## What Compare Exchange Items Are - -Compare Exchange items are key/value pairs where the key servers a unique value across your database. - -* Each compare-exchange item contains: - * **A key** - A unique string identifier in the database scope. - * **A value** - Can be any object (a number, string, array, or any valid JSON object). - * **Metadata** - Data that is associated with the compare-exchange item. - Must be a valid JSON object. - * For example, the metadata can be used to set expiration time for the compare-exchange item. - Learn more in [compare-exchange expiration](../../../client-api/operations/compare-exchange/compare-exchange-expiration.mdx). - * **Raft index** - The compare-exchange item's version. - Any change to the value or metadata will increase this number. - -* Creating and modifying a compare-exchange item is an atomic, thread-safe [compare-and-swap](https://en.wikipedia.org/wiki/Compare-and-swap) interlocked - compare-exchange operation. - - - -## Creating and Managing Compare-Exchange Items - -Compare exchange items are created and managed with any of the following approaches: - -* **Document Store Operations** - You can manage a compare-exchange item as an [Operation on the document store](../../../client-api/operations/compare-exchange/put-compare-exchange-value.mdx). - This can be done within or outside of a session (cluster-wide or single-node session). - * When inside a session: - If the session fails, the compare-exchange operation can still succeed - because store Operations do not rely on the success of the session. - You will need to delete the compare-exchange item explicitly upon session failure if you don't want the compare-exchange item to persist. - -* **Cluster-Wide Sessions** - You can manage a compare-exchange item from inside a [Cluster-Wide session](../../../client-api/session/cluster-transaction/compare-exchange.mdx). - If the session fails, the compare-exchange item creation also fails. - None of the nodes in the group will have the new compare-exchange item. - - -* **Atomic Guards** - When creating documents using a cluster-wide session RavenDB automatically creates [Atomic Guards](../../../client-api/session/cluster-transaction/atomic-guards.mdx), - which are compare-exchange items that guarantee ACID transactions. - See [Cluster-wide vs. Single-node](../../../client-api/session/cluster-transaction/overview.mdx#cluster-wide-transaction-vs-single-node-transaction) for a session comparision overview. - -* **Studio** - Compare-exchange items can be created from the [Studio](../../../studio/database/documents/compare-exchange-view.mdx#the-compare-exchange-view) as well. - - - -## Why Compare-Exchange Items are Not Replicated to External Databases - -* Each cluster defines its policies and configurations, and should ideally have sole responsibility for managing its own documents. - Read [Consistency in a Globally Distributed System](https://ayende.com/blog/196769-B/data-ownership-in-a-distributed-system) - to learn more about why global database modeling is more efficient this way. - -* When creating a compare-exchange item a Raft consensus is required from the nodes in the database group. - Externally replicating such data is problematic as the target database may reside within a cluster that is in an - unstable state where Raft decisions cannot be made. In such a state, the compare-exchange item will not be persisted in the target database. - -* Conflicts between documents that occur between two databases are solved with the help of the documents - Change-Vector. Compare-exchange conflicts cannot be handled properly as they do not have a similar - mechanism to resolve conflicts. - -* To ensure unique values between two databases without using compare-exchange items see [Example III](../../../client-api/operations/compare-exchange/overview.mdx#example-iii---ensuring-unique-values-without-using-compare-exchange). - - - -## Example I - Email Address Reservation - -The following example shows how to use compare-exchange to create documents with unique values. -The scope is within the database group on a single cluster. - -Compare-exchange items are not externally replicated to other databases. -To establish uniqueness without using compare-exchange see [Example III](../../../client-api/operations/compare-exchange/overview.mdx#example-iii---ensuring-unique-values-without-using-compare-exchange). - - - -{`$email = "user@example.com"; - -$user = new User(); -$user->setEmail($email); - -$session = $store->openSession(); -try \{ - $session->store($user); - - // At this point, the user document has an Id assigned - - // Try to reserve a new user email - // Note: This operation takes place outside of the session transaction, - // It is a cluster-wide reservation - - /** @var CompareExchangeResult $cmpXchgResult */ - $cmpXchgResult = $store->operations()->send(new PutCompareExchangeValueOperation("emails/" . $email, $user->getId(), 0)); - - if (!$cmpXchgResult->isSuccessful()) \{ - throw new RuntimeException("Email is already in use"); - \} - - // At this point we managed to reserve/save the user email - - // The document can be saved in SaveChanges - $session->saveChanges(); -\} finally \{ - $session->close(); -\} -`} - - - -**Implications**: - -* The `User` object is saved as a document, hence it can be indexed, queried, etc. - -* This compare-exchange item was [created as an operation](../../../client-api/operations/compare-exchange/put-compare-exchange-value.mdx) - rather than with a [cluster-wide session](../../../client-api/session/cluster-transaction/overview.mdx). - Thus, if `session.saveChanges` fails, then the email reservation is _not_ rolled back automatically. - It is your responsibility to do so. - -* The compare-exchange value that was saved can be accessed in a query using `CmpXchg`: - - - -{`$query = $session->advanced()->rawQuery(User::class, - "from Users as s where id() == cmpxchg(\\"emails/ayende@ayende.com\\")") - ->toList(); -`} - - - - -{`$q = $session->advanced() - ->documentQuery(User::class) - ->whereEquals("id", CmpXchg::value("emails/ayende@ayende.com")); -`} - - - - -{`from Users as s where id() == cmpxchg("emails/ayende@ayende.com") -`} - - - - - -## Example II - Reserve a Shared Resource - -In the following example, we use compare-exchange to reserve a shared resource. -The scope is within the database group on a single cluster. - -The code also checks for clients which never release resources (i.e. due to failure) by using timeout. - - - -{`class SharedResource -\{ - private ?DateTime $reservedUntil = null; - - public function getReservedUntil(): ?DateTime - \{ - return $this->reservedUntil; - \} - - public function setReservedUntil(?DateTime $reservedUntil): void - \{ - $this->reservedUntil = $reservedUntil; - \} -\} - -class CompareExchangeSharedResource -\{ - private ?DocumentStore $store = null; - - public function printWork(): void - \{ - // Try to get hold of the printer resource - $reservationIndex = $this->lockResource($this->store, "Printer/First-Floor", Duration::ofMinutes(20)); - - try \{ - // Do some work for the duration that was set. - // Don't exceed the duration, otherwise resource is available for someone else. - \} finally \{ - $this->releaseResource($this->store, "Printer/First-Floor", $reservationIndex); - \} - \} - - /** throws InterruptedException */ - public function lockResource(DocumentStoreInterface $store, ?string $resourceName, Duration $duration): int - \{ - while (true) \{ - $now = new DateTime(); - - $resource = new SharedResource(); - $resource->setReservedUntil($now->add($duration->toDateInterval())); - - /** @var CompareExchangeResult $saveResult */ - $saveResult = $store->operations()->send( - new PutCompareExchangeValueOperation($resourceName, $resource, 0)); - - if ($saveResult->isSuccessful()) \{ - // resourceName wasn't present - we managed to reserve - return $saveResult->getIndex(); - \} - - // At this point, Put operation failed - someone else owns the lock or lock time expired - if ($saveResult->getValue()->getReservedUntil() < $now) \{ - // Time expired - Update the existing key with the new value - /** @var CompareExchangeResult takeLockWithTimeoutResult */ - $takeLockWithTimeoutResult = $store->operations()->send( - new PutCompareExchangeValueOperation($resourceName, $resource, $saveResult->getIndex())); - - if ($takeLockWithTimeoutResult->isSuccessful()) \{ - return $takeLockWithTimeoutResult->getIndex(); - \} - \} - - // Wait a little bit and retry - usleep(20000); - \} - \} - - public function releaseResource(DocumentStoreInterface $store, ?string $resourceName, int $index): void - \{ - $deleteResult = $store->operations()->send( - new DeleteCompareExchangeValueOperation(SharedResource::class, $resourceName, $index) - ); - - // We have 2 options here: - // $deleteResult->isSuccessful is true - we managed to release resource - // $deleteResult->isSuccessful is false - someone else took the lock due to timeout - \} -\} -`} - - - - - -## Example III - Ensuring Unique Values without Using Compare Exchange - -Unique values can also be ensured without using compare-exchange. - -The below example shows how to achieve that by using **reference documents**. -The reference documents' IDs will contain the unique values instead of the compare-exchange items. - -Using reference documents is especially useful when [External Replication](../../../server/ongoing-tasks/external-replication.mdx) -is defined between two databases that need to be synced with unique values. -The reference documents will replicate to the destination database, -as opposed to compare-exchange items, which are not externally replicated. - - -Sessions which process fields that must be unique should be set to [TransactionMode::clusterWide()](../../../client-api/session/cluster-transaction/overview.mdx). - - - - -{`// When you create documents that must contain a unique value such as a phone or email, etc., -// you can create reference documents that will have that unique value in their IDs. -// To know if a value already exists, all you need to do is check whether a reference document with such ID exists. - -public - -class PhoneReference -\{ - public ?string $id = null; - public ?string $companyId = null; - - public function getId(): ?string - \{ - return $this->id; - \} - - public function setId(?string $id): void - \{ - $this->id = $id; - \} - - public function getCompanyId(): ?string - \{ - return $this->companyId; - \} - - public function setCompanyId(?string $companyId): void - \{ - $this->companyId = $companyId; - \} -\} - -// The reference document class -class UniquePhoneReference -\{ - public function sample(): void - \{ - // A company document class that must be created with a unique 'Phone' field - $newCompany = new Company(); - $newCompany->setName("companyName"); - $newCompany->setPhone("phoneNumber"); - - $newContact = new Contact(); - $newContact->setName("contactName"); - $newContact->setTitle("contactTitle"); - - $newCompany->setContact($newContact); - - $this->createCompanyWithUniquePhone($newCompany); - \} - - public function createCompanyWithUniquePhone(Company $newCompany): void - \{ - // Open a cluster-wide session in your document store - $sessionOptions = new SessionOptions(); - $sessionOptions->setTransactionMode(TransactionMode::clusterWide()); - $session = DocumentStoreHolder::getStore()->openSession($sessionOptions); - - try \{ - // Check whether the new company phone already exists - // by checking if there is already a reference document that has the new phone in its ID. - $phoneRefDocument = $session->load(PhoneReference::class, "phones/" . $newCompany->getPhone()); - if ($phoneRefDocument != null) \{ - $msg = "Phone '" . $newCompany->getPhone() . "' already exists in ID: " . $phoneRefDocument->getCompanyId(); - throw new ConcurrencyException($msg); - \} - - // If the new phone number doesn't already exist, store the new entity - $session->store($newCompany); - // Store a new reference document with the new phone value in its ID for future checks. - $newPhoneReference = new PhoneReference(); - $newPhoneReference->setCompanyId($newCompany->getId()); - $session->store($newPhoneReference, "phones/" . $newCompany->getPhone()); - - // May fail if called concurrently with the same phone number - $session->saveChanges(); - \} finally \{ - $session->close(); - \} - \} -\} -`} - - - - - diff --git a/docs/client-api/operations/compare-exchange/_overview-python.mdx b/docs/client-api/operations/compare-exchange/_overview-python.mdx deleted file mode 100644 index 71178dc7e5..0000000000 --- a/docs/client-api/operations/compare-exchange/_overview-python.mdx +++ /dev/null @@ -1,274 +0,0 @@ -import Admonition from '@theme/Admonition'; -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; -import CodeBlock from '@theme/CodeBlock'; - - - -* Compare Exchange items are **key/value pairs** where the key is unique across your database. - -* Compare-exchange operations require cluster consensus to ensure consistency across all nodes. - Once a consensus is reached, the compare-exchange items are distributed through the Raft algorithm to all nodes in the database group. - -* Compare-exchange items can be used to coordinate work between sessions that are trying to modify a shared resource (such as a document) at the same time. - -* Compare-exchange items are [not replicated externally](../../../client-api/operations/compare-exchange/overview.mdx#why-compare-exchange-items-are-not-replicated-to-external-databases) to other databases. - -* In this page: -* [What Compare Exchange Items Are](../../../client-api/operations/compare-exchange/overview.mdx#what-compare-exchange-items-are) -* [Creating and Managing Compare-Exchange Items](../../../client-api/operations/compare-exchange/overview.mdx#creating-and-managing-compare-exchange-items) -* [Why Compare-Exchange Items are Not Replicated to External Databases](../../../client-api/operations/compare-exchange/overview.mdx#why-compare-exchange-items-are-not-replicated-to-external-databases) -* [Example I - Email Address Reservation](../../../client-api/operations/compare-exchange/overview.mdx#example-i---email-address-reservation) -* [Example II - Reserve a Shared Resource](../../../client-api/operations/compare-exchange/overview.mdx#example-ii---reserve-a-shared-resource) -* [Example III - Ensuring Unique Values without Using Compare Exchange](../../../client-api/operations/compare-exchange/overview.mdx#example-iii---ensuring-unique-values-without-using-compare-exchange) - - -## What Compare Exchange Items Are - -Compare Exchange items are key/value pairs where the key servers a unique value across your database. - -* Each compare-exchange item contains: - * **A key** - A unique string identifier in the database scope. - * **A value** - Can be any object (a number, string, array, or any valid JSON object). - * **Metadata** - Data that is associated with the compare-exchange item. - Must be a valid JSON object. - * For example, the metadata can be used to set expiration time for the compare-exchange item. - Learn more in [compare-exchange expiration](../../../client-api/operations/compare-exchange/compare-exchange-expiration.mdx). - * **Raft index** - The compare-exchange item's version. - Any change to the value or metadata will increase this number. - -* Creating and modifying a compare-exchange item is an atomic, thread-safe [compare-and-swap](https://en.wikipedia.org/wiki/Compare-and-swap) interlocked - compare-exchange operation. - - - -## Creating and Managing Compare-Exchange Items - -Compare exchange items are created and managed with any of the following approaches: - -* **Document Store Operations** - You can manage a compare-exchange item as an [Operation on the document store](../../../client-api/operations/compare-exchange/put-compare-exchange-value.mdx). - This can be done within or outside of a session (cluster-wide or single-node session). - * When inside a session: - If the session fails, the compare-exchange operation can still succeed - because store Operations do not rely on the success of the session. - You will need to delete the compare-exchange item explicitly upon session failure if you don't want the compare-exchange item to persist. - -* **Cluster-Wide Sessions** - You can manage a compare-exchange item from inside a [Cluster-Wide session](../../../client-api/session/cluster-transaction/compare-exchange.mdx). - If the session fails, the compare-exchange item creation also fails. - None of the nodes in the group will have the new compare-exchange item. - - -* **Atomic Guards** - When creating documents using a cluster-wide session RavenDB automatically creates [Atomic Guards](../../../client-api/session/cluster-transaction/atomic-guards.mdx), - which are compare-exchange items that guarantee ACID transactions. - See [Cluster-wide vs. Single-node](../../../client-api/session/cluster-transaction/overview.mdx#cluster-wide-transaction-vs-single-node-transaction) for a session comparision overview. - -* **Studio** - Compare-exchange items can be created from the [Studio](../../../studio/database/documents/compare-exchange-view.mdx#the-compare-exchange-view) as well. - - - -## Why Compare-Exchange Items are Not Replicated to External Databases - -* Each cluster defines its policies and configurations, and should ideally have sole responsibility for managing its own documents. - Read [Consistency in a Globally Distributed System](https://ayende.com/blog/196769-B/data-ownership-in-a-distributed-system) - to learn more about why global database modeling is more efficient this way. - -* When creating a compare-exchange item a Raft consensus is required from the nodes in the database group. - Externally replicating such data is problematic as the target database may reside within a cluster that is in an - unstable state where Raft decisions cannot be made. In such a state, the compare-exchange item will not be persisted in the target database. - -* Conflicts between documents that occur between two databases are solved with the help of the documents - Change-Vector. Compare-exchange conflicts cannot be handled properly as they do not have a similar - mechanism to resolve conflicts. - -* To ensure unique values between two databases without using compare-exchange items see [Example III](../../../client-api/operations/compare-exchange/overview.mdx#example-iii---ensuring-unique-values-without-using-compare-exchange). - - - -## Example I - Email Address Reservation - -The following example shows how to use compare-exchange to create documents with unique values. -The scope is within the database group on a single cluster. - -Compare-exchange items are not externally replicated to other databases. -To establish uniqueness without using compare-exchange see [Example III](../../../client-api/operations/compare-exchange/overview.mdx#example-iii---ensuring-unique-values-without-using-compare-exchange). - - - -{`email = "user@example.com" - -user = User(email=email) - -with store.open_session() as sesion: - sesion.store(user) - # At this point, the user document has an Id assigned - - # Try to reserve a new user email - # Note: This operation takes place outside the session transaction, - # It is a cluster-wide reservation - cmp_xchg_result = store.operations.send(PutCompareExchangeValueOperation(f"emails/\{email\}", user.Id, 0)) - - if cmp_xchg_result.successful is False: - raise RuntimeError("Email is already in use") - - # At this point we managed to reserve/save the user mail - - # The document can be saved in save_changes - sesion.save_changes() -`} - - - -**Implications**: - -* The `User` object is saved as a document, hence it can be indexed, queried, etc. - -* This compare-exchange item was [created as an operation](../../../client-api/operations/compare-exchange/put-compare-exchange-value.mdx) - rather than with a [cluster-wide session](../../../client-api/session/cluster-transaction/overview.mdx). - Thus, if `session.save_changes` fails, then the email reservation is _not_ rolled back automatically. - It is your responsibility to do so. - -* The compare-exchange value that was saved can be accessed in a query using `CmpXchg`: - - - -{`query = sesion.query(object_type=User).where_equals("Id", CmpXchg.value("emails/ayende@ayende.com")) -`} - - - - -{`from Users as s where id() == cmpxchg("emails/ayende@ayende.com") -`} - - - - - -## Example II - Reserve a Shared Resource - -In the following example, we use compare-exchange to reserve a shared resource. -The scope is within the database group on a single cluster. - -The code also checks for clients which never release resources (i.e. due to failure) by using timeout. - - - -{`class SharedResource: - def __init__(self, reserved_until: datetime = None): - self.reserved_until = reserved_until - -def print_work() -> None: - # Try to get hold of the printer resource - reservation_index = lock_resource(store, "Printer/First-Floor", timedelta(minutes=20)) - - try: - ... - # Do some work for the duration that was set - # Don't exceed the duration, otherwise resource is available for someone else - finally: - release_resource(store, "Printer/First-Floor", reservation_index) - -def lock_resource(document_store: DocumentStore, resource_name: str, duration: timedelta): - while True: - now = datetime.utcnow() - - resource = SharedResource(reserved_until=now + duration) - save_result = document_store.operations.send( - PutCompareExchangeValueOperation(resource_name, resource, 0) - ) - - if save_result.successful: - # resource_name wasn't present - we managed to reserve - return save_result.index - - # At this point, Put operation failed - someone else owns the lock or lock time expired - if save_result.value.reserved_until < now: - # Time expired - Update the existing key with new value - take_lock_with_timeout_result = document_store.operations.send( - PutCompareExchangeValueOperation(resource_name, resource, save_result.index) - ) - - if take_lock_with_timeout_result.successful: - return take_lock_with_timeout_result.index - - # Wait a little bit and retry - time.sleep(0.02) - -def release_resource(store: DocumentStore, resource_name: str, index: int) -> None: - delete_result = store.operations.send(DeleteCompareExchangeValueOperation(resource_name, index)) - - # We have 2 options here: - # delete_result.successful is True - we managed to release resource - # delete_result.successful is False - someone else took the lock due to timeout -`} - - - - - -## Example III - Ensuring Unique Values without Using Compare Exchange - -Unique values can also be ensured without using compare-exchange. - -The below example shows how to achieve that by using **reference documents**. -The reference documents' IDs will contain the unique values instead of the compare-exchange items. - -Using reference documents is especially useful when [External Replication](../../../server/ongoing-tasks/external-replication.mdx) -is defined between two databases that need to be synced with unique values. -The reference documents will replicate to the destination database, -as opposed to compare-exchange items, which are not externally replicated. - - -Sessions which process fields that must be unique should be set to [TransactionMode.CLUSTER_WIDE](../../../client-api/session/cluster-transaction/overview.mdx). - - - - -{`# When you create documents that must contain a unique value such as a phone or email, etc., -# you can create reference documents that will have that unique value in their IDs. -# To know if a value already exists, all you need to do is check whether a reference document with such ID exists. - -# The reference document class -class UniquePhoneReference: - class PhoneReference: - def __init__(self, Id: str = None, company_id: str = None): - self.Id = Id - self.company_id = company_id - - def main(self): - # A company document class that must be created with a unique 'Phone' field - new_company = Company( - name="companyName", phone="phoneNumber", contact=Contact(name="contactName", title="contactTitle") - ) - - def create_company_with_unique_phone(new_company: Company) -> None: - # Open a cluster-wide session in your document store - with store.open_session( - session_options=SessionOptions(transaction_mode=TransactionMode.CLUSTER_WIDE) - ) as session: - # Check whether the new company phone already exists - # by checking if there is already a reference document that has the new phone in its ID. - phone_ref_document = session.load(f"phones/\{new_company.phone\}") - if phone_ref_document is not None: - msg = f"Phone '\{new_company.phone\}' already exists, store the new entity" - raise RuntimeError(msg) - - # If the new phone number doesn't already exist, store the new entity - session.store(new_company) - # Store a new reference document with the new phone value in its ID for future checks - session.store( - UniquePhoneReference.PhoneReference(company_id=new_company.Id), - f"phones/\{new_company.phone\}", - ) - - # May fail if called concurrently with the same phone number - session.save_changes() -`} - - - - - diff --git a/docs/client-api/operations/compare-exchange/_put-compare-exchange-value-csharp.mdx b/docs/client-api/operations/compare-exchange/_put-compare-exchange-value-csharp.mdx deleted file mode 100644 index 950c26fbe3..0000000000 --- a/docs/client-api/operations/compare-exchange/_put-compare-exchange-value-csharp.mdx +++ /dev/null @@ -1,121 +0,0 @@ -import Admonition from '@theme/Admonition'; -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; -import CodeBlock from '@theme/CodeBlock'; - - - -* Use `PutCompareExchangeValueOperation` to save a compare-exchange _Value_ for the specified _Key_. - -* Create a new _Key_ or modify an existing one. - -* The _Value_ is saved only if the passed _index_ is equal to the _index_ currently stored in the server for the specified _Key_. - -* Creating a new compare exchange item is possible only when the passed _index_ is `0`. - -* For an overview of the 'Compare Exchange' feature see: [Compare Exchange Overview](../../../client-api/operations/compare-exchange/overview.mdx). - -* In this page: - * [Syntax](../../../client-api/operations/compare-exchange/put-compare-exchange-value.mdx#syntax) - * [Example I - Create a new key](../../../client-api/operations/compare-exchange/put-compare-exchange-value.mdx#example-i---create-a-new-key) - * [Example II - Update an existing key](../../../client-api/operations/compare-exchange/put-compare-exchange-value.mdx#example-ii---update-an-existing-key) - * [Example III - Try to create an item with a non-zero index](../../../client-api/operations/compare-exchange/put-compare-exchange-value.mdx#example-iii---try-to-create-an-item-with-a-non-zero-index) - - -## Syntax - -**Method**: - - -{`public PutCompareExchangeValueOperation(string key, T value, long index) -`} - - - -| Parameter | Type | Description | -|-----------|----------|--------------------------------------------------------------------------------------------------------------------------------------| -| **key** | `string` | Object identifier under which _value_ is saved, unique in the database scope across the cluster. This string can be up to 512 bytes. | -| **value** | `T` | The value to be saved for the specified _key_. | -| **index** | `long` | * `0` if creating a new key
* The current version of _Value_ when updating a value for an existing key. | - -**Returned object**: - - -{`public class CompareExchangeResult -\{ - public bool Successful; - public T Value; - public long Index; -\} -`} - - - -| Return Value | Type | Description | -|----------------|--------|----------------------------------------------------------------------------------------------------------------------------------------| -| **Successful** | `bool` | * _True_ if the save operation has completed successfully
* _False_ if the save operation failed | -| **Value** | `T` | * The value that was saved if operation was successful
* The currently existing value in the server upon failure | -| **Index** | `long` | * The version number of the value that was saved upon success
* The currently existing version number in the server upon failure | - - -When calling the 'Put' operation, the index from the request is compared to the index that is currently stored in the server (compare stage). -The value is updated only if the two are **equal** (exchange stage). - - - -## Example I - Create a new key - - - -{`CompareExchangeResult compareExchangeResult - = store.Operations.Send( - new PutCompareExchangeValueOperation("Emails/foo@example.org", "users/123", 0)); - -bool successful = compareExchangeResult.Successful; -// If successfull is true: then Key 'foo@example.org' now has the value of "users/123" -`} - - - - -## Example II - Update an existing key - - - -{`// Get existing value -CompareExchangeValue readResult = - store.Operations.Send( - new GetCompareExchangeValueOperation("AdminUser")); - -readResult.Value.Age++; - -// Update value -CompareExchangeResult saveResult - = store.Operations.Send( - new PutCompareExchangeValueOperation("AdminUser", readResult.Value, readResult.Index)); - -// The save result is successful only if 'index' wasn't changed between the read and write operations -bool saveResultSuccessful = saveResult.Successful; -`} - - - - -## Example III - Try to create an item with a non-zero index - - - -{`// Creating the item will fail because the index is not 0 -CompareExchangeResult compareExchangeResult - = store.Operations.Send( - new PutCompareExchangeValueOperation("key", "value", 123)); - -// Creating the item will succeed because the index is 0 -compareExchangeResult = store.Operations.Send( - new PutCompareExchangeValueOperation("key", "value", 0)); -`} - - - - - diff --git a/docs/client-api/operations/compare-exchange/_put-compare-exchange-value-java.mdx b/docs/client-api/operations/compare-exchange/_put-compare-exchange-value-java.mdx deleted file mode 100644 index a69f838df6..0000000000 --- a/docs/client-api/operations/compare-exchange/_put-compare-exchange-value-java.mdx +++ /dev/null @@ -1,122 +0,0 @@ -import Admonition from '@theme/Admonition'; -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; -import CodeBlock from '@theme/CodeBlock'; - - - -* Use `PutCompareExchangeValueOperation` to save a compare-exchange _Value_ for the specified _Key_. - -* Create a new _Key_ or modify an existing one. - -* The _Value_ is saved only if the _index_ passed is equal to the _index_ currently stored in the server for the specified _Key_. - -* For an overview of the 'Compare Exchange' feature see: [Compare Exchange Overview](../../../client-api/operations/compare-exchange/overview.mdx). - -* In this page: - * [Syntax](../../../client-api/operations/compare-exchange/put-compare-exchange-value.mdx#syntax) - * [Example I - Create a new key](../../../client-api/operations/compare-exchange/put-compare-exchange-value.mdx#example-i---create-a-new-key) - * [Example II - Update an existing key](../../../client-api/operations/compare-exchange/put-compare-exchange-value.mdx#example-ii---update-an-existing-key) - -## Syntax - -**Method**: - - -{`public PutCompareExchangeValueOperation(String key, T value, long index) -`} - - - -| Parameter | Type | Description | -|-----------|--------|--------------------------------------------------------------------------------------------------------------------------------------| -| **key** | String | Object identifier under which _value_ is saved, unique in the database scope across the cluster. This string can be up to 512 bytes. | -| **value** | `T` | The value to be saved for the specified _key_. | -| **index** | long | * `0` if creating a new key
* The current version of _Value_ when updating a value for an existing key. | - -**Returned object**: - - -{`public class CompareExchangeResult \{ - private T value; - private long index; - private boolean successful; - - public T getValue() \{ - return value; - \} - - public void setValue(T value) \{ - this.value = value; - \} - - public long getIndex() \{ - return index; - \} - - public void setIndex(long index) \{ - this.index = index; - \} - - public boolean isSuccessful() \{ - return successful; - \} - - public void setSuccessful(boolean successful) \{ - this.successful = successful; - \} -\} -`} - - - -| Return Value | Type | Description | -|----------------|---------|----------------------------------------------------------------------------------------------------------------------------------------| -| **Successful** | boolean | * _True_ if the save operation has completed successfully
* _False_ if the save operation failed | -| **Value** | `T` | * The value that was saved if operation was successful
* The currently existing value in the server upon failure | -| **Index** | long | * The version number of the value that was saved upon success
* The currently existing version number in the server upon failure | - - -When calling the 'Put' operation, the index from the request is compared to the index that is currently stored in the server (compare stage). -The value is updated only if the two are **equal** (exchange stage). - - - -## Example I - Create a new key - - - -{`CompareExchangeResult compareExchangeResult = store.operations().send( - new PutCompareExchangeValueOperation<>("Emails/foo@example.org", "users/123", 0)); - -boolean successful = compareExchangeResult.isSuccessful(); -// If successful is true: then Key 'foo@example.org' now has the value of "users/123" -`} - - - - -## Example II - Update an existing key - - - -{`// Get existing value -CompareExchangeValue readResult - = store.operations().send( - new GetCompareExchangeValueOperation<>(User.class, "AdminUser")); - -readResult.getValue().setAge(readResult.getValue().getAge() + 1); - -// Update value -CompareExchangeResult saveResult - = store.operations().send( - new PutCompareExchangeValueOperation<>("AdminUser", readResult.getValue(), readResult.getIndex())); - -// The save result is successful only if 'index' wasn't changed between the read and write operations -boolean saveResultSuccessful = saveResult.isSuccessful(); -`} - - - - - diff --git a/docs/client-api/operations/compare-exchange/_put-compare-exchange-value-nodejs.mdx b/docs/client-api/operations/compare-exchange/_put-compare-exchange-value-nodejs.mdx deleted file mode 100644 index 1c8d56eb1f..0000000000 --- a/docs/client-api/operations/compare-exchange/_put-compare-exchange-value-nodejs.mdx +++ /dev/null @@ -1,167 +0,0 @@ -import Admonition from '@theme/Admonition'; -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; -import CodeBlock from '@theme/CodeBlock'; - - - - -* Use the `PutCompareExchangeValueOperation` operation to either: - * **Create** a new compare-exchange item - * **Update** the value or metadata of an existing compare-exchange item - -* Compare-exchange items can also be managed via [advanced session](../../../client-api/session/cluster-transaction/compare-exchange.mdx) methods - or from the [Studio](../../../studio/database/documents/compare-exchange-view.mdx). - -* In this page: - * [Create new cmpXchg item](../../../client-api/operations/compare-exchange/put-compare-exchange-value.mdx#create-new-cmpxchg-item) - * [Create new cmpXchg item with metadata](../../../client-api/operations/compare-exchange/put-compare-exchange-value.mdx#create-new-cmpxchg-item-with-metadata) - * [Update existing cmpXchg item](../../../client-api/operations/compare-exchange/put-compare-exchange-value.mdx#update-existing-cmpxchg-item) - * [Syntax](../../../client-api/operations/compare-exchange/put-compare-exchange-value.mdx#syntax) - - -## Create new cmpXchg item - - - -{`// Create a new CmpXchg item: -// ========================== - -// Define the put compare-exchange operation. Pass: -// * KEY: a new unique identifier (e.g. a user's email) -// * VALUE: an associated value (e.g. the user's name) -// * INDEX: pass '0' to indicate that this is a new key -const putCmpXchgOp = new PutCompareExchangeValueOperation("johnDoe@gmail.com", "John Doe", 0); - -// Execute the operation by passing it to operations.send -const result = await documentStore.operations.send(putCmpXchgOp); - -// Check results -const successful = result.successful; // Has operation succeeded -const indexForItem = result.index; // The version number assigned to the new item - -// If successful is true then a new compare-exchange item has been created -// with the unique email key and the associated value. -`} - - - -* Note: - Using `0` with a key that already exists will Not modify existing compare-exchange item. - - - -## Create new cmpXchg item with metadata - - - -{`// Create a new CmpXchg item with metadata: -// ======================================== - -// Define the put compare-exchange operation. -// Pass a 4'th parameter with the metadata object. -const putCmpXchgOp = new PutCompareExchangeValueOperation("+48-123-456-789", "John Doe", 0, - \{ - "Provider": "T-Mobile", - "Network": "5G", - "Work phone": false - \}); - -// Execute the operation by passing it to operations.send -const result = await documentStore.operations.send(putCmpXchgOp); - -// Check results -const successful = result.successful; // Has operation succeeded -const indexForItem = result.index; // The version number assigned to the new item - -// If successful is true then a new compare-exchange item has been created -// with the unique phone number key, value, and metadata. -`} - - - -* Find more examples of adding metadata to a compare-exchange item in [compare-exchange metadata](../../../client-api/operations/compare-exchange/compare-exchange-metadata.mdx). - - - -## Update existing cmpXchg item - -* When calling `PutCompareExchangeValueOperation`, - the index from the request is compared to the index that is currently stored in the server for the specified _key_. -* The compare-exchange item is updated only if the two are equal. - - - -{`// Modify an existing CmpXchg item: -// ================================ - -// Get the existing compare-exchange item -const item = await documentStore.operations.send( - new GetCompareExchangeValueOperation("+48-123-456-789") -); - -// Make some changes -const newValue = "Jane Doe"; -const metadata = item.metadata; -metadata["Work phone"] = true; - -// Update the compare-exchange item: -// The put operation will succeed only if the 'index' of the compare-exchange item -// has not changed between the read and write operations. -const result = await documentStore.operations.send( - new PutCompareExchangeValueOperation("+48-123-456-789", newValue, item.index, metadata) -); - -// Check results -const successful = result.successful; // Has operation succeeded -const newIndex = result.index; // The new version number assigned to the item - -// Version has increased -assert(newIndex > item.index); -`} - - - - - -## Syntax - - - -{`// Available overloads: -// ==================== -const putCmpXchgOp = new PutCompareExchangeValueOperation(key, value, index); -const putCmpXchgOp = new PutCompareExchangeValueOperation(key, value, index, metadata); -`} - - - -| Parameter | Type | Description | -|---------------|-------------|---------------------------------------------------------------------------------------------------------------------------------------------| -| **key** | `string` | <ul><li>A unique identifier in the database scope.</li><li>Can be up to 512 bytes.</li><ul> | -| **value** | `object` | <ul><li>A value to be saved for the specified _key_.</li><li>Can be any object (number, string, array, or any valid JSON object).</li></ul> | -| **index** | `number` | <ul><li>Pass `0` to create a new key.</li><li>When updating an existing key, pass the current number for concurrency control.</li><ul> | -| **metadata** | `object` | <ul><li>Metadata to be saved for the specified _key_.</li><li>Must be a valid JSON object.</li></ul> | - - - -{`// Return value of store.operations.send(putCmpXchgOp) -// =================================================== -class CompareExchangeResult \{ - successful; - value; - index; -\} -`} - - - -| Return Value | Type | Description | -|----------------|-----------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| **successful** | `boolean` | <ul><li>`true` if the put operation has completed successfully.</li><li>`false` if the put operation has failed.</li></ul> | -| **value** | `object` | <ul><li>Upon success - the value of the compare-exchange item that was saved.</li><li>Upon failure - the existing value on the server.</li></ul> | -| **index** | `number` | <ul><li>The compare-exchange item's version.</li><li>This number increases with each successful modification of the `value` or `metadata`.</li><li>Upon success - the updated version of the compare-exchange item that was saved.</li><li>Upon failure - the existing version number in the server.</li></ul> | - - - - diff --git a/docs/client-api/operations/compare-exchange/compare-exchange-expiration.mdx b/docs/client-api/operations/compare-exchange/compare-exchange-expiration.mdx deleted file mode 100644 index 2f4afa0be7..0000000000 --- a/docs/client-api/operations/compare-exchange/compare-exchange-expiration.mdx +++ /dev/null @@ -1,32 +0,0 @@ ---- -title: "Compare Exchange Expiration" -hide_table_of_contents: true -sidebar_label: Compare Exchange Expiration -sidebar_position: 6 ---- - -import LanguageSwitcher from "@site/src/components/LanguageSwitcher"; -import LanguageContent from "@site/src/components/LanguageContent"; - -import CompareExchangeExpirationCsharp from './_compare-exchange-expiration-csharp.mdx'; - -export const supportedLanguages = ["csharp"]; - - - - - - - - - \ No newline at end of file diff --git a/docs/client-api/operations/compare-exchange/compare-exchange-metadata.mdx b/docs/client-api/operations/compare-exchange/compare-exchange-metadata.mdx deleted file mode 100644 index 9b23cffd6d..0000000000 --- a/docs/client-api/operations/compare-exchange/compare-exchange-metadata.mdx +++ /dev/null @@ -1,32 +0,0 @@ ---- -title: "Compare Exchange Metadata" -hide_table_of_contents: true -sidebar_label: Compare Exchange Metadata -sidebar_position: 7 ---- - -import LanguageSwitcher from "@site/src/components/LanguageSwitcher"; -import LanguageContent from "@site/src/components/LanguageContent"; - -import CompareExchangeMetadataCsharp from './_compare-exchange-metadata-csharp.mdx'; - -export const supportedLanguages = ["csharp"]; - - - - - - - - - \ No newline at end of file diff --git a/docs/client-api/operations/compare-exchange/delete-compare-exchange-value.mdx b/docs/client-api/operations/compare-exchange/delete-compare-exchange-value.mdx deleted file mode 100644 index 893f93c9e5..0000000000 --- a/docs/client-api/operations/compare-exchange/delete-compare-exchange-value.mdx +++ /dev/null @@ -1,43 +0,0 @@ ---- -title: "Delete Compare Exchange Operation" -hide_table_of_contents: true -sidebar_label: Delete Compare Exchange -sidebar_position: 2 ---- - -import LanguageSwitcher from "@site/src/components/LanguageSwitcher"; -import LanguageContent from "@site/src/components/LanguageContent"; - -import DeleteCompareExchangeValueCsharp from './_delete-compare-exchange-value-csharp.mdx'; -import DeleteCompareExchangeValueJava from './_delete-compare-exchange-value-java.mdx'; -import DeleteCompareExchangeValueNodejs from './_delete-compare-exchange-value-nodejs.mdx'; - -export const supportedLanguages = ["csharp", "java", "nodejs"]; - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/docs/client-api/operations/compare-exchange/get-compare-exchange-value.mdx b/docs/client-api/operations/compare-exchange/get-compare-exchange-value.mdx deleted file mode 100644 index a15fa9137f..0000000000 --- a/docs/client-api/operations/compare-exchange/get-compare-exchange-value.mdx +++ /dev/null @@ -1,46 +0,0 @@ ---- -title: "Get Compare Exchange Value Operation" -hide_table_of_contents: true -sidebar_label: Get Compare Exchange Value -sidebar_position: 3 ---- - -import LanguageSwitcher from "@site/src/components/LanguageSwitcher"; -import LanguageContent from "@site/src/components/LanguageContent"; - -import GetCompareExchangeValueCsharp from './_get-compare-exchange-value-csharp.mdx'; -import GetCompareExchangeValueJava from './_get-compare-exchange-value-java.mdx'; -import GetCompareExchangeValueNodejs from './_get-compare-exchange-value-nodejs.mdx'; - -export const supportedLanguages = ["csharp", "java", "nodejs"]; - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/docs/client-api/operations/compare-exchange/get-compare-exchange-values.mdx b/docs/client-api/operations/compare-exchange/get-compare-exchange-values.mdx deleted file mode 100644 index 198c8c6cb9..0000000000 --- a/docs/client-api/operations/compare-exchange/get-compare-exchange-values.mdx +++ /dev/null @@ -1,42 +0,0 @@ ---- -title: "Get Compare Exchange Values Operation" -hide_table_of_contents: true -sidebar_label: Get Compare Exchange Values -sidebar_position: 4 ---- - -import LanguageSwitcher from "@site/src/components/LanguageSwitcher"; -import LanguageContent from "@site/src/components/LanguageContent"; - -import GetCompareExchangeValuesCsharp from './_get-compare-exchange-values-csharp.mdx'; -import GetCompareExchangeValuesJava from './_get-compare-exchange-values-java.mdx'; -import GetCompareExchangeValuesNodejs from './_get-compare-exchange-values-nodejs.mdx'; - -export const supportedLanguages = ["csharp", "java", "nodejs"]; - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/docs/client-api/operations/compare-exchange/include-compare-exchange.mdx b/docs/client-api/operations/compare-exchange/include-compare-exchange.mdx deleted file mode 100644 index 8ca2dce563..0000000000 --- a/docs/client-api/operations/compare-exchange/include-compare-exchange.mdx +++ /dev/null @@ -1,37 +0,0 @@ ---- -title: "Include Compare Exchange Values" -hide_table_of_contents: true -sidebar_label: Include Compare Exchange Values -sidebar_position: 5 ---- - -import LanguageSwitcher from "@site/src/components/LanguageSwitcher"; -import LanguageContent from "@site/src/components/LanguageContent"; - -import IncludeCompareExchangeCsharp from './_include-compare-exchange-csharp.mdx'; -import IncludeCompareExchangeNodejs from './_include-compare-exchange-nodejs.mdx'; - -export const supportedLanguages = ["csharp", "nodejs"]; - - - - - - - - - - - - - \ No newline at end of file diff --git a/docs/client-api/operations/compare-exchange/overview.mdx b/docs/client-api/operations/compare-exchange/overview.mdx deleted file mode 100644 index 14af6d6f48..0000000000 --- a/docs/client-api/operations/compare-exchange/overview.mdx +++ /dev/null @@ -1,68 +0,0 @@ ---- -title: "Compare Exchange Overview" -hide_table_of_contents: true -sidebar_label: Overview -sidebar_position: 0 ---- - -import LanguageSwitcher from "@site/src/components/LanguageSwitcher"; -import LanguageContent from "@site/src/components/LanguageContent"; - -import OverviewCsharp from './_overview-csharp.mdx'; -import OverviewJava from './_overview-java.mdx'; -import OverviewPython from './_overview-python.mdx'; -import OverviewPhp from './_overview-php.mdx'; - -export const supportedLanguages = ["csharp", "java", "python", "php"]; - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/docs/client-api/operations/compare-exchange/put-compare-exchange-value.mdx b/docs/client-api/operations/compare-exchange/put-compare-exchange-value.mdx deleted file mode 100644 index 9419f2b7c7..0000000000 --- a/docs/client-api/operations/compare-exchange/put-compare-exchange-value.mdx +++ /dev/null @@ -1,40 +0,0 @@ ---- -title: "Put Compare Exchange Operation" -hide_table_of_contents: true -sidebar_label: Put Compare Exchange -sidebar_position: 1 ---- - -import LanguageSwitcher from "@site/src/components/LanguageSwitcher"; -import LanguageContent from "@site/src/components/LanguageContent"; - -import PutCompareExchangeValueCsharp from './_put-compare-exchange-value-csharp.mdx'; -import PutCompareExchangeValueJava from './_put-compare-exchange-value-java.mdx'; -import PutCompareExchangeValueNodejs from './_put-compare-exchange-value-nodejs.mdx'; - -export const supportedLanguages = ["csharp", "java", "nodejs"]; - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/docs/client-api/operations/patching/_category_.json b/docs/client-api/operations/patching/_category_.json index 0019fb4f20..f0be7deba4 100644 --- a/docs/client-api/operations/patching/_category_.json +++ b/docs/client-api/operations/patching/_category_.json @@ -1,4 +1,4 @@ { - "position": 8, + "position": 7, "label": Patching, -} \ No newline at end of file +} diff --git a/docs/compare-exchange/_category_.json b/docs/compare-exchange/_category_.json new file mode 100644 index 0000000000..3c5a076b2f --- /dev/null +++ b/docs/compare-exchange/_category_.json @@ -0,0 +1,4 @@ +{ + "position": 5, + "label": "Compare-Exchange" +} diff --git a/docs/compare-exchange/api-studio-quick-links/_category_.json b/docs/compare-exchange/api-studio-quick-links/_category_.json new file mode 100644 index 0000000000..be90a8f2ba --- /dev/null +++ b/docs/compare-exchange/api-studio-quick-links/_category_.json @@ -0,0 +1,4 @@ +{ + "position": 13, + "label": "API/Studio Quick Links" +} diff --git a/docs/compare-exchange/api-studio-quick-links/client-api-references.mdx b/docs/compare-exchange/api-studio-quick-links/client-api-references.mdx new file mode 100644 index 0000000000..b4bffb2b85 --- /dev/null +++ b/docs/compare-exchange/api-studio-quick-links/client-api-references.mdx @@ -0,0 +1,60 @@ +--- +title: "Client API References" +hide_table_of_contents: true +sidebar_label: "Client API References" +sidebar_position: 1 +--- + +import Admonition from '@theme/Admonition'; + + + +Refer to the following links for managing compare-exchange items via the Client API: + +* **Overview examples**: + [Example I - Email address reservation](../../compare-exchange/overview#example-i---email-address-reservation) + [Example II - Reserve a shared resource](../../compare-exchange/overview#example-ii---reserve-a-shared-resource) + [Example III - Ensuring unique values without using compare exchange](../../compare-exchange/overview#example-iii---ensuring-unique-values-without-using-compare-exchange) + +* **Create compare-exchange items**: + [Create item using a cluster-wide session](../../compare-exchange/create-cmpxchg-items#create-item-using-a-cluster-wide-session) + [Create item using a store operation](../../compare-exchange/create-cmpxchg-items#create-item-using-a-store-operation) + +* **Get compare-exchange item**: + [Get item using a cluster-wide session](../../compare-exchange/get-cmpxchg-item#get-item-using-a-cluster-wide-session) + [Get item using a store operation](../../compare-exchange/get-cmpxchg-item#get-item-using-a-store-operation) + +* **Get compare-exchange items**: + [Get compare-exchange items by list of keys](../../compare-exchange/get-cmpxchg-items#get-compare-exchange-items-by-list-of-keys) + [Get compare-exchange items by prefix](../../compare-exchange/get-cmpxchg-items#get-compare-exchange-items-by-prefix) + [Get compare-exchange items count](../../compare-exchange/get-cmpxchg-items#get-compare-exchange-items-count) + +* **Delete compare-exchange items**: + [Delete compare-exchange item using a cluster-wide session](../../compare-exchange/delete-cmpxchg-items#delete-compare-exchange-item-using-a-cluster-wide-session) + [Delete compare-exchange item using a store operation](../../compare-exchange/delete-cmpxchg-items#delete-compare-exchange-item-using-a-store-operation) + +* **Update compare-exchange item**: + [Update compare-exchange item using a cluster-wide session](../../compare-exchange/update-cmpxchg-item#update-compare-exchange-item-using-a-cluster-wide-session) + [Update compare-exchange item using a store operation](../../compare-exchange/update-cmpxchg-item#update-compare-exchange-item-using-a-store-operation) + +* **Include compare-exchange items**: + [Include compare-exchange items when loading](../../compare-exchange/include-cmpxchg-items#include-compare-exchange-items-when-loading) + [Include compare-exchange items when querying](../../compare-exchange/include-cmpxchg-items#include-compare-exchange-items-when-querying) + +* **Indexing compare-exchange values**: + [Index compare-exchange values](../../compare-exchange/indexing-cmpxchg-values#index-compare-exchange-values) + [Query the index](../../compare-exchange/indexing-cmpxchg-values#query-the-index) + [Query the index and project compare-exchange values](../../compare-exchange/indexing-cmpxchg-values#query-the-index-and-project-compare-exchange-values) + +* **Compare-exchange in dynamic queries**: + [Projecting compare-exchange values in query results](../../compare-exchange/cmpxchg-in-dynamic-queries#projecting-compare-exchange-values-in-query-results) + [Filtering by compare-exchange value](../../compare-exchange/cmpxchg-in-dynamic-queries#filtering-by-compare-exchange-value) + +* **Compare-exchange expiration**: + [Add expiration date using the Client API](../../compare-exchange/cmpxchg-expiration#add-expiration-date-using-the-client-api) + +* **Atomic guards**: + [Atomic guard usage example](../../compare-exchange/atomic-guards#atomic-guard-usage-example) + [Best practice when storing a document in a cluster-wide transaction](../../compare-exchange/atomic-guards#best-practice-when-storing-a-document-in-a-cluster-wide-transaction) + + diff --git a/docs/compare-exchange/api-studio-quick-links/studio-references.mdx b/docs/compare-exchange/api-studio-quick-links/studio-references.mdx new file mode 100644 index 0000000000..bea5636bda --- /dev/null +++ b/docs/compare-exchange/api-studio-quick-links/studio-references.mdx @@ -0,0 +1,29 @@ +--- +title: "Studio References" +hide_table_of_contents: true +sidebar_label: "Studio References" +sidebar_position: 0 +--- + +import Admonition from '@theme/Admonition'; + + + +Refer to the following links for managing compare-exchange items via the Studio: + +* **Overview**: + [Ways to create and manage compare-exchange items](../../compare-exchange/overview#ways-to-create-and-manage-compare-exchange-items) + +* **Create compare-exchange items**: + [Create item using the Studio](../../compare-exchange/create-cmpxchg-items#create-item-using-the-studio) + +* **Delete compare-exchange items**: + [Delete compare-exchange items using the Studio](../../compare-exchange/delete-cmpxchg-items#delete-compare-exchange-items-using-the-studio) + +* **Update compare-exchange item**: + [Update compare-exchange item using the Studio](../../compare-exchange/update-cmpxchg-item#update-compare-exchange-item-using-the-studio) + +* **Compare-exchange expiration**: + [Add expiration date using the Studio](../../compare-exchange/cmpxchg-expiration#add-expiration-date-using-the-studio) + + diff --git a/docs/compare-exchange/assets/atomic-guard.png b/docs/compare-exchange/assets/atomic-guard.png new file mode 100644 index 0000000000..4e2a12abe7 Binary files /dev/null and b/docs/compare-exchange/assets/atomic-guard.png differ diff --git a/docs/compare-exchange/assets/create-new-cmpxchg-1.png b/docs/compare-exchange/assets/create-new-cmpxchg-1.png new file mode 100644 index 0000000000..e0a43f84c9 Binary files /dev/null and b/docs/compare-exchange/assets/create-new-cmpxchg-1.png differ diff --git a/docs/compare-exchange/assets/create-new-cmpxchg-2.png b/docs/compare-exchange/assets/create-new-cmpxchg-2.png new file mode 100644 index 0000000000..b29f4abdcd Binary files /dev/null and b/docs/compare-exchange/assets/create-new-cmpxchg-2.png differ diff --git a/docs/compare-exchange/assets/delete-cmpxchg.png b/docs/compare-exchange/assets/delete-cmpxchg.png new file mode 100644 index 0000000000..ca732e14be Binary files /dev/null and b/docs/compare-exchange/assets/delete-cmpxchg.png differ diff --git a/docs/compare-exchange/assets/set-expiration.png b/docs/compare-exchange/assets/set-expiration.png new file mode 100644 index 0000000000..aefe36fbfd Binary files /dev/null and b/docs/compare-exchange/assets/set-expiration.png differ diff --git a/docs/compare-exchange/assets/the-cmpxchg-view.png b/docs/compare-exchange/assets/the-cmpxchg-view.png new file mode 100644 index 0000000000..a1faf41437 Binary files /dev/null and b/docs/compare-exchange/assets/the-cmpxchg-view.png differ diff --git a/docs/compare-exchange/assets/update-cmpxchg-1.png b/docs/compare-exchange/assets/update-cmpxchg-1.png new file mode 100644 index 0000000000..85587c6c1e Binary files /dev/null and b/docs/compare-exchange/assets/update-cmpxchg-1.png differ diff --git a/docs/compare-exchange/assets/update-cmpxchg-2.png b/docs/compare-exchange/assets/update-cmpxchg-2.png new file mode 100644 index 0000000000..f848006979 Binary files /dev/null and b/docs/compare-exchange/assets/update-cmpxchg-2.png differ diff --git a/docs/compare-exchange/atomic-guards.mdx b/docs/compare-exchange/atomic-guards.mdx new file mode 100644 index 0000000000..1f49046005 --- /dev/null +++ b/docs/compare-exchange/atomic-guards.mdx @@ -0,0 +1,47 @@ +--- +title: "Atomic Guards" +hide_table_of_contents: true +sidebar_label: Atomic Guards +sidebar_position: 11 +--- + +import LanguageSwitcher from "@site/src/components/LanguageSwitcher"; +import LanguageContent from "@site/src/components/LanguageContent"; + +import AtomicGuardsCsharp from './content/_atomic-guards-csharp.mdx'; +import AtomicGuardsPython from './content/_atomic-guards-python.mdx'; +import AtomicGuardsPhp from './content/_atomic-guards-php.mdx'; +import AtomicGuardsNodejs from './content/_atomic-guards-nodejs.mdx'; + +export const supportedLanguages = ["csharp", "python", "php", "nodejs"]; + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/compare-exchange/cmpxchg-expiration.mdx b/docs/compare-exchange/cmpxchg-expiration.mdx new file mode 100644 index 0000000000..e7858d3dab --- /dev/null +++ b/docs/compare-exchange/cmpxchg-expiration.mdx @@ -0,0 +1,34 @@ +--- +title: "Compare-Exchange Expiration" +hide_table_of_contents: true +sidebar_label: "Compare-Exchange Expiration" +sidebar_position: 10 +--- + +import LanguageSwitcher from "@site/src/components/LanguageSwitcher"; +import LanguageContent from "@site/src/components/LanguageContent"; + +import CmpXchgItemExpirationCsharp from './content/_cmpxchg-item-expiration-csharp.mdx'; +import CmpXchgItemExpirationNodejs from './content/_cmpxchg-item-expiration-nodejs.mdx'; + +export const supportedLanguages = ["csharp", "nodejs"]; + + + + + + + + + + + + diff --git a/docs/compare-exchange/cmpxchg-in-dynamic-queries.mdx b/docs/compare-exchange/cmpxchg-in-dynamic-queries.mdx new file mode 100644 index 0000000000..9446d6e720 --- /dev/null +++ b/docs/compare-exchange/cmpxchg-in-dynamic-queries.mdx @@ -0,0 +1,37 @@ +--- +title: "Compare-Exchange in Dynamic Queries" +hide_table_of_contents: true +sidebar_label: "Compare-Exchange in Dynamic Queries" +sidebar_position: 9 +--- + +import LanguageSwitcher from "@site/src/components/LanguageSwitcher"; +import LanguageContent from "@site/src/components/LanguageContent"; + +import CmpXchgInDynamicQueriesCsharp from './content/_cmpxchg-in-dynamic-queries-csharp.mdx'; +import CmpXchgInDynamicQueriesNodejs from './content/_cmpxchg-in-dynamic-queries-nodejs.mdx'; + +export const supportedLanguages = ["csharp", "nodejs"]; + + + + + + + + + + + + diff --git a/docs/compare-exchange/configuration.mdx b/docs/compare-exchange/configuration.mdx new file mode 100644 index 0000000000..2b9393e08a --- /dev/null +++ b/docs/compare-exchange/configuration.mdx @@ -0,0 +1,98 @@ +--- +title: "Compare-Exchange Configuration" +hide_table_of_contents: true +sidebar_label: "Configuration" +sidebar_position: 12 +--- + +import Admonition from '@theme/Admonition'; +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +import CodeBlock from '@theme/CodeBlock'; +import LanguageSwitcher from "@site/src/components/LanguageSwitcher"; +import LanguageContent from "@site/src/components/LanguageContent"; + + + +* For an overview of what configuration options are and how they can be applied, + see the [Configuration Overview](../server/configuration/configuration-options) article. + +* The following configuration options are available for compare-exchange: + * [Cluster.CompareExchangeExpiredDeleteFrequencyInSec](../compare-exchange/configuration#clustercompareexchangeexpireddeletefrequencyinsec) + * [Cluster.CompareExchangeTombstonesCleanupIntervalInMin](../compare-exchange/configuration#clustercompareexchangetombstonescleanupintervalinmin) + * [Cluster.MaxClusterTransactionCompareExchangeTombstoneCheckIntervalInMin](../compare-exchange/configuration#clustermaxclustertransactioncompareexchangetombstonecheckintervalinmin) + * [Cluster.DisableAtomicDocumentWrites](../todo..) + + + +--- + + + +## Cluster.CompareExchangeExpiredDeleteFrequencyInSec + +Time (in seconds) between cleanup of **expired** compare-exchange items. + +- **Type**: `int` +- **Default**: `60` +- **Scope**: Server-wide only + + + + + +## Cluster.CompareExchangeTombstonesCleanupIntervalInMin + +* Compare-exchange tombstones are created when compare-exchange items are [deleted](..//compare-exchange/delete-cmpxchg-items). + These tombstones are not removed immediately - RavenDB uses an internal cleaner task to periodically remove tombstones that are eligible for deletion. + +* This configuration option sets the interval, in minutes, between each cleanup run. + +* At each interval, the cleaner will only remove tombstones that are ready for removal - meaning their deletion has already been processed by all relevant subscribers. + Subscribers are internal RavenDB processes that need to observe or react to compare-exchange deletions, + such as indexes, ETL tasks, subscriptions, etc. + Tombstones become eligible for removal only after all such processes have handled the deletion. + +--- + +- **Type**: `int` +- **Default**: `10` +- **Scope**: Server-wide only + + + + + +## Cluster.MaxClusterTransactionCompareExchangeTombstoneCheckIntervalInMin + +EXPERT ONLY: + +* This configuration sets the interval (in minutes) between checks for compare-exchange tombstones that can be marked for deletion by the cluster-wide transaction mechanism on the node. + This is separate from checks performed by other subscribers (such as indexes, subscriptions, or ETL tasks). + +* Normally, whenever a cluster-wide transaction command is processed, the cluster-wide transaction mechanism checks for compare-exchange tombstones that can be marked as eligible for deletion. + If no cluster-wide transaction command occurs within the specified interval, the mechanism will automatically perform this check after the configured time has elapsed. + +* Any tombstones that have been fully handled by the cluster-wide transaction mechanism will be marked for deletion, making them eligible for cleanup by the tombstone cleaner task. + +--- + +- **Type**: `int` +- **Default**: `5` +- **Scope**: Server-wide only + + + + + +## Cluster.DisableAtomicDocumentWrites + +EXPERT ONLY: +Disable automatic atomic writes with cluster write transactions. +If set to _true_, will only consider explicitly added compare exchange values to validate cluster wide transactions. + +- **Type**: `bool` +- **Default**: `false` +- **Scope**: Server-wide or per database + + diff --git a/docs/compare-exchange/content/_atomic-guards-csharp.mdx b/docs/compare-exchange/content/_atomic-guards-csharp.mdx new file mode 100644 index 0000000000..c98fe8c8b1 --- /dev/null +++ b/docs/compare-exchange/content/_atomic-guards-csharp.mdx @@ -0,0 +1,357 @@ +import Admonition from '@theme/Admonition'; +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +import CodeBlock from '@theme/CodeBlock'; + + + +* **Atomic Guards** are [compare-exchange key/value items](../compare-exchange/overview) + that RavenDB creates and manages **automatically** to guarantee [ACID](../server/clustering/cluster-transactions#cluster-transactions-properties) behavior in cluster-wide sessions. + +* When a document is created in a cluster-wide session, RavenDB associates it with a unique atomic guard item. + Atomic guards coordinate concurrent writes by different sessions to the same document. + +* In this article: + * [Atomic guard creation and update](../compare-exchange/atomic-guards#atomic-guard-creation-and-update) + * [Atomic guard usage example](../compare-exchange/atomic-guards#atomic-guard-creation-and-update) + * [Atomic guard database scope](../compare-exchange/atomic-guards#atomic-guard-database-scope) + * [Disabling atomic guards](../compare-exchange/atomic-guards#disabling-atomic-guards) + * [When are atomic guards removed](../compare-exchange/atomic-guards#when-are-atomic-guards-removed) + * [Best practice when storing a document in a cluster-wide transaction](../compare-exchange/atomic-guards#best-practice-when-storing-a-document-in-a-cluster-wide-transaction) + + + +--- + +## Atomic guard creation and update + + +Atomic guards are created and managed **only when the session's transaction mode is set to [ClusterWide](../client-api/session/cluster-transaction/overview#open-a-cluster-transaction)**. + + +* **When creating a new document**: + A new atomic guard is created when a new document is successfully saved. + +* **When modifying an existing document that already has an atomic guard**: + * The atomic guard’s Raft index is incremented when the document is successfully saved after being modified. + This allows RavenDB to detect that the document has changed. + * If another session had loaded the document before the document's version changed, it will not be able to save its changes + unless it first reloads the updated version. Otherwise, a `ConcurrencyException` is thrown. + +* **When modifying an existing document that doesn't have an atomic guard**: + * A new atomic guard is created when modifying an existing document that does not yet have one. + * The absence of the atomic guard may be because the document was created in a single-node session, + or because its atomic guard was manually removed (which is not recommended). + +* **When saving a document fails**: + * If a session's `SaveChanges()` fails, the entire session is rolled back and the atomic guard is Not created. + * Ensure your business logic is designed to re-execute the session in case saving changes fails for any reason. + +--- + +## Atomic guard usage example + +In the code sample below, an atomic guard is automatically created when a new document is saved. +It is then used to detect and prevent conflicting writes: when two sessions load and modify the same document, +only the first save succeeds, and the second fails with a _ConcurrencyException_. + + + +```csharp +using (var session = store.OpenSession(new SessionOptions +{ + // Open a cluster-wide session: + TransactionMode = TransactionMode.ClusterWide +})) +{ + session.Store(new User(), "users/johndoe"); + session.SaveChanges(); + // An atomic guard is now automatically created for the new document "users/johndoe". +} + +// Open two concurrent cluster-wide sessions: +using (var session1 = store.OpenSession( + new SessionOptions + {TransactionMode = TransactionMode.ClusterWide})) +using (var session2 = store.OpenSession( + new SessionOptions + {TransactionMode = TransactionMode.ClusterWide})) +{ + // Both sessions load the same document: + var loadedUser1 = session1.Load("users/johndoe"); + loadedUser1.Name = "jindoe"; + + var loadedUser2 = session2.Load("users/johndoe"); + loadedUser2.Name = "jandoe"; + + // session1 saves its changes first — + // this increments the Raft index of the associated atomic guard. + session1.SaveChanges(); + + // session2 tries to save using an outdated atomic guard version + // and fails with a ConcurrencyException. + session2.SaveChanges(); +} +``` + + +```csharp +using (var asyncSession = store.OpenAsyncSession(new SessionOptions +{ + // Open a cluster-wide session: + TransactionMode = TransactionMode.ClusterWide +})) +{ + await asyncSession.StoreAsync(new User(), "users/johndoe"); + await asyncSession.SaveChangesAsync(); + // An atomic guard is now automatically created for the new document "users/johndoe". +} + +// Open two concurrent cluster-wide sessions: +using (var asyncSession1 = store.OpenAsyncSession( + new SessionOptions + {TransactionMode = TransactionMode.ClusterWide})) +using (var asyncSession2 = store.OpenAsyncSession( + new SessionOptions + {TransactionMode = TransactionMode.ClusterWide})) +{ + // Both sessions load the same document: + var loadedUser1 = await asyncSession1.LoadAsync("users/johndoe"); + loadedUser1.Name = "jindoe"; + + var loadedUser2 = await asyncSession2.LoadAsync("users/johndoe"); + loadedUser2.Name = "jandoe"; + + // asyncSession1 saves its changes first — + // this increments the Raft index of the associated atomic guard. + await asyncSession1.SaveChangesAsync(); + + // asyncSession2 tries to save using an outdated atomic guard version + // and fails with a ConcurrencyException. + await asyncSession2.SaveChangesAsync(); +} +``` + + + +After running the above example, you can view the automatically created atomic guard in the **Compare-Exchange view** +in the Studio: + +![Atomic Guard](../assets/atomic-guard.png) + +1. These are **custom compare-exchange items**, created by the user for any purpose, + as described in [Create compare-exchange items](../compare-exchange/create-cmpxchg-items). + They are NOT the automatically created atomic guards. + +2. This is the **atomic guard** that was generated by running the example above. + The generated atomic guard **key** is: `rvn-atomic/users/johndoe`. It is composed of: + * The prefix `rvn-atomic/`. + * The ID of the associated document (`users/johndoe`). + + + * Although this Studio view allows editing compare-exchange items, **do NOT delete or modify atomic guard entries**. + * Doing so will interfere with RavenDB's ability to track document versioning through atomic guards. + + +--- + +## Atomic guard database scope + +* Atomic guards are local to the database on which they were defined. + +* Since atomic guards are implemented as compare-exchange items, + they are Not externally replicated to other databases by any ongoing replication task. + Learn more in [why compare-exchange items are not replicated](../compare-exchange/overview#why-compare-exchange-items-are-not-replicated-to-external-databases). + +--- + +## Disabling atomic guards + +* Before atomic guards were introduced (in RavenDB 5.2), client code had to explicitly manage compare-exchange entries + to ensure concurrency control and maintain ACID guarantees in cluster-wide transactions. + +* You can still take this manual approach by disabling the automatic use of atomic guards in a cluster-wide session, + and managing the required [compare-exchange key/value pairs](../compare-exchange/overview) yourself, + as shown in this [example](../compare-exchange/overview#example-i---email-address-reservation). + +* To disable the automatic creation and use of atomic guards in a cluster-wide session, + set the session's `DisableAtomicDocumentWritesInClusterWideTransaction` configuration option to `true`. + + + +```csharp +using (var session = store.OpenSession(new SessionOptions +{ + TransactionMode = TransactionMode.ClusterWide, + // Disable atomic-guards + DisableAtomicDocumentWritesInClusterWideTransaction = true +})) +{ + session.Store(new User(), "users/johndoe"); + + // No atomic-guard will be created upon saveChanges + session.SaveChanges(); +} +``` + + +```csharp +using (var asyncSession = store.OpenAsyncSession(new SessionOptions +{ + TransactionMode = TransactionMode.ClusterWide, + // Disable atomic-guards + DisableAtomicDocumentWritesInClusterWideTransaction = true +})) +{ + await asyncSession.StoreAsync(new User(), "users/johndoe"); + + // No atomic-guard will be created upon saveChanges + await asyncSession.SaveChangesAsync(); +} +``` + + + +--- + +## When are atomic guards removed + +Atomic guards are removed **automatically** in the following scenarios: +(you don't need to clean them up manually) + +* **Document deleted via a cluster-wide session**: + * Create a document using a cluster-wide session (an associated atomic guard is created). + * Delete the document using a cluster-wide session - its atomic guard will be removed automatically. + +* **Document expires via the expiration feature**: + * Create a document using a cluster-wide session (an associated atomic guard is created). + * Add the `@expires` metadata property the document, as described in [Document expiration](../studio/database/settings/document-expiration). + * When the expiration time is reached, the document and its atomic guard will both be removed automatically. + * Since different cleanup tasks handle the removal of **expired** documents and the removal of their associated atomic guards, + it may happen that atomic guards of removed documents would linger in the compare-exchange entries list a short while longer before they are removed. + You do Not need to remove such atomic guards yourself, they will be removed by the cleanup task. + + + +* **Do not delete or modify atomic guards manually**. + If a session attempts to save a document whose atomic guard was removed or modified, it will fail with an error. + +* If you accidentally remove an atomic guard that is associated with an existing document, + you can restore it by re-saving the document in a cluster-wide session, + this will re-create the atomic guard automatically. + + + +--- + +## Best practice when storing a document in a cluster-wide transaction + +* When working with a cluster-wide session, + we recommend that you always **`Load` the document into the session before storing it** - + even if the document is expected to be new. + +* This is especially important if a document (originally created in a cluster-wide transaction) was deleted **outside** of a cluster-wide session - + as when using a [single-node session](../client-api/session/cluster-transaction/overview#cluster-wide-transaction-vs-single-node-transaction) + or the [DeleteByQueryOperation](../client-api/operations/common/delete-by-query). + In these cases, the document is deleted, but the atomic guard remains (it is not automatically removed). + If you attempt to re-create such a document without loading it first, + RavenDB will fail to save it because the session is unaware of the existing atomic guard’s latest Raft index. + +* In this example, the document is loaded into the session BEFORE creating or modifying it: + + + +```csharp +using (var session = store.OpenSession(new SessionOptions +{ + // Open a cluster-wide session + TransactionMode = TransactionMode.ClusterWide +})) +{ + // Load the user document BEFORE creating a new one or modifying if already exists + var user = session.Load("users/johndoe"); + + if (user == null) + { + // Document doesn't exist => create a new document: + var newUser = new User + { + Name = "John Doe", + // ... initialize other properties + }; + + // Store the new user document in the session + session.Store(newUser, "users/johndoe"); + } + else + { + // Document exists => apply your modifications: + user.Name = "New name"; + // ... make any other updates + + // No need to call Store() again + // RavenDB tracks changes on loaded entities + } + + // Commit your changes + session.SaveChanges(); +} +``` + + +```csharp +using (var asyncSession = store.OpenAsyncSession(new SessionOptions +{ + // Open a cluster-wide session + TransactionMode = TransactionMode.ClusterWide +})) +{ + // Load the user document BEFORE creating or updating + var user = await asyncSession.LoadAsync("users/johndoe"); + + if (user == null) + { + // Document doesn't exist => create a new document: + var newUser = new User + { + Name = "John Doe", + // ... initialize other properties + }; + + // Store the new user document in the session + await asyncSession.StoreAsync(newUser, "users/johndoe"); + } + else + { + // Document exists => apply your modifications: + user.Name = "New name"; + // ... make any other updates + + // No need to call Store() again + // RavenDB tracks changes on loaded entities + } + + // Commit your changes + await asyncSession.SaveChangesAsync(); +} +``` + + + + + +When _loading_ a document in a cluster-wide session, RavenDB attempts to retrieve the document from the document store: + +* **If the document is found**, it is loaded into the session, + and modifications will be saved successfully as long as no other session has modified the document in the meantime. + * RavenDB checks whether the Raft index of the atomic guard associated with the document matches the version tracked by the current session. + If another session has already updated the document (and incremented the atomic guard’s Raft index), the save will fail with a _ConcurrencyException_. + +* **If no document is found**, RavenDB will check whether a matching atomic guard exists + (as in the case when the document was deleted outside of a cluster-wide session): + * **If an atomic guard exists**, + the client constructs a change vector for the document using the atomic guard’s Raft index, and the document will be saved with this change vector. + * **If no atomic guard exists**, + the document is treated as a brand new document and will be saved as usual. + + diff --git a/docs/compare-exchange/content/_atomic-guards-nodejs.mdx b/docs/compare-exchange/content/_atomic-guards-nodejs.mdx new file mode 100644 index 0000000000..03d044c3b7 --- /dev/null +++ b/docs/compare-exchange/content/_atomic-guards-nodejs.mdx @@ -0,0 +1,259 @@ +import Admonition from '@theme/Admonition'; +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +import CodeBlock from '@theme/CodeBlock'; + + + +* **Atomic Guards** are [compare-exchange key/value items](../compare-exchange/overview) + that RavenDB creates and manages **automatically** to guarantee [ACID](../server/clustering/cluster-transactions#cluster-transactions-properties) behavior in cluster-wide sessions. + +* When a document is created in a cluster-wide session, RavenDB associates it with a unique atomic guard item. + Atomic guards coordinate concurrent writes by different sessions to the same document. + +* In this article: + * [Atomic guard creation and update](../compare-exchange/atomic-guards#atomic-guard-creation-and-update) + * [Atomic guard usage example](../compare-exchange/atomic-guards#atomic-guard-usage-example) + * [Atomic guard database scope](../compare-exchange/atomic-guards#atomic-guard-database-scope) + * [Disabling atomic guards](../compare-exchange/atomic-guards#disabling-atomic-guards) + * [When are atomic guards removed](../compare-exchange/atomic-guards#when-are-atomic-guards-removed) + * [Best practice when storing a document in a cluster-wide transaction](../compare-exchange/atomic-guards#best-practice-when-storing-a-document-in-a-cluster-wide-transaction) + + + +--- + +## Atomic guard creation and update + + +Atomic guards are created and managed **only when the session's transaction mode is set to [ClusterWide](../client-api/session/cluster-transaction/overview#open-a-cluster-transaction)**. + + +* **When creating a new document**: + A new atomic guard is created when a new document is successfully saved. + +* **When modifying an existing document that already has an atomic guard**: + * The atomic guard’s Raft index is incremented when the document is successfully saved after being modified. + This allows RavenDB to detect that the document has changed. + * If another session had loaded the document before the document's version changed, it will not be able to save its changes + unless it first reloads the updated version. Otherwise, a `ConcurrencyException` is thrown. + +* **When modifying an existing document that doesn't have an atomic guard**: + * A new atomic guard is created when modifying an existing document that does not yet have one. + * The absence of the atomic guard may be because the document was created in a single-node session, + or because its atomic guard was manually removed (which is not recommended). + +* **When saving a document fails**: + * If a session's `saveChanges()` fails, the entire session is rolled back and the atomic guard is Not created. + * Ensure your business logic is designed to re-execute the session in case saving changes fails for any reason. + +--- + +## Atomic guard usage example + +In the code sample below, an atomic guard is automatically created when a new document is saved. +It is then used to detect and prevent conflicting writes: when two sessions load and modify the same document, +only the first save succeeds, and the second fails with a _ConcurrencyException_. + + + +{`const user = \{ + firstName: "John", + lastName: "Doe" +\}; + +// Open a cluster-wide session: +const session = documentStore.openSession(\{ + transactionMode: "ClusterWide" +\}); + +await session.store(user, "users/johndoe"); +await session.saveChanges(); +// An atomic-guard is now automatically created for the new document "users/johndoe". + +// Open two concurrent cluster-wide sessions: +const session1 = documentStore.openSession(\{ + transactionMode: "ClusterWide" +\}); +const session2 = documentStore.openSession(\{ + transactionMode: "ClusterWide" +\}); + +// Both sessions load the same document: +const loadedUser1 = await session1.load("users/johndoe"); +loadedUser1.name = "jindoe"; + +const loadedUser2 = await session2.load("users/johndoe"); +loadedUser2.name = "jandoe"; + +// session1 saves its changes first — +// this increments the Raft index of the associated atomic guard. +await session1.saveChanges(); + +// session2 tries to save using an outdated atomic guard version +// and fails with a ConcurrencyException. +await session2.saveChanges(); +`} + + + +After running the above example, you can view the automatically created atomic guard in the **Compare-Exchange view** +in the Studio: + +![Atomic Guard](../assets/atomic-guard.png) + +1. These are **custom compare-exchange items**, created by the user for any purpose, + as described in [Create compare-exchange items](../compare-exchange/create-cmpxchg-items). + They are NOT the automatically created atomic guards. + +2. This is the **atomic guard** that was generated by running the example above. + The generated atomic guard **key** is: `rvn-atomic/users/johndoe`. It is composed of: + * The prefix `rvn-atomic/`. + * The ID of the associated document (`users/johndoe`). + + + * Although this Studio view allows editing compare-exchange items, **do NOT delete or modify atomic guard entries**. + * Doing so will interfere with RavenDB's ability to track document versioning through atomic guards. + + +--- + +## Atomic guard database scope + +* Atomic guards are local to the database on which they were defined. + +* Since atomic guards are implemented as compare-exchange items, + they are Not externally replicated to other databases by any ongoing replication task. + Learn more in [why compare-exchange items are not replicated](../compare-exchange/overview#why-compare-exchange-items-are-not-replicated-to-external-databases). + +--- + +## Disabling atomic guards + +* Before atomic guards were introduced (in RavenDB 5.2), client code had to explicitly manage compare-exchange entries + to ensure concurrency control and maintain ACID guarantees in cluster-wide transactions. + +* You can still take this manual approach by disabling the automatic use of atomic guards in a cluster-wide session, + and managing the required [compare-exchange key/value pairs](../compare-exchange/overview) yourself, + as shown in this [example](../compare-exchange/overview#example-i---email-address-reservation). + +* To disable the automatic creation and use of atomic guards in a cluster-wide session, + set the session's `DisableAtomicDocumentWritesInClusterWideTransaction` configuration option to `true`. + + + +{`// Open a cluster-wide session +const session = documentStore.openSession(\{ + transactionMode: "ClusterWide", + // Disable atomic-guards + disableAtomicDocumentWritesInClusterWideTransaction: true +\}); + +await session.store(user, "users/johndoe"); + +// No atomic-guard will be created upon saveChanges +await session.saveChanges(); +`} + + + +--- + +## When are atomic guards removed + +Atomic guards are removed **automatically** in the following scenarios: +(you don't need to clean them up manually) + +* **Document deleted via a cluster-wide session**: + * Create a document using a cluster-wide session (an associated atomic guard is created). + * Delete the document using a cluster-wide session - its atomic guard will be removed automatically. + +* **Document expires via the expiration feature**: + * Create a document using a cluster-wide session (an associated atomic guard is created). + * Add the `@expires` metadata property the document, as described in [Document expiration](../studio/database/settings/document-expiration.mdx). + * When the expiration time is reached, the document and its atomic guard will both be removed automatically. + * Since different cleanup tasks handle the removal of **expired** documents and the removal of their associated atomic guards, + it may happen that atomic guards of removed documents would linger in the compare-exchange entries list a short while longer before they are removed. + You do Not need to remove such atomic guards yourself, they will be removed by the cleanup task. + + + +* **Do not delete or modify atomic guards manually**. + If a session attempts to save a document whose atomic guard was removed or modified, it will fail with an error. + +* If you accidentally remove an atomic guard that is associated with an existing document, + you can restore it by re-saving the document in a cluster-wide session, + this will re-create the atomic guard automatically. + + + +--- + +## Best practice when storing a document in a cluster-wide transaction + +* When working with a cluster-wide session, + we recommend that you always **`load` the document into the session before storing it** - + even if the document is expected to be a new document. + +* **Document expires via the expiration feature**: + * Create a document using a cluster-wide session (an associated atomic guard is created). + * Add the `@expires` metadata property the document, as described in [Document expiration](../studio/database/settings/document-expiration). + * When the expiration time is reached, the document and its atomic guard will both be removed automatically. + * Since different cleanup tasks handle the removal of **expired** documents and the removal of their associated atomic guards, + it may happen that atomic guards of removed documents would linger in the compare-exchange entries list a short while longer before they are removed. + You do Not need to remove such atomic guards yourself, they will be removed by the cleanup task. + +* In this example, the document is loaded into the session BEFORE creating or modifying it: + + + +{`const session = documentStore.openSession(\{ + // Open a cluster-wide session + transactionMode: "ClusterWide" +\}); + +// Load the user document BEFORE creating or updating +const user = await session.load("users/johndoe"); + +if (!user) \{ + // Document doesn't exist => create a new document + const newUser = \{ + name: "John Doe", + // ... initialize other properties + \}; + + // Store the new user document in the session + await session.store(newUser, "users/johndoe"); + +\} else \{ + // Document exists => apply your modifications + user.name = "New name"; + // ... make any other updates + + // No need to call store() again + // RavenDB tracks changes on loaded entities +\} + +// Commit your changes +await session.saveChanges(); +`} + + + + + +When _loading_ a document in a cluster-wide session, RavenDB attempts to retrieve the document from the document store: + +* **If the document is found**, it is loaded into the session, + and modifications will be saved successfully as long as no other session has modified the document in the meantime. + * RavenDB checks whether the Raft index of the atomic guard associated with the document matches the version tracked by the current session. + If another session has already updated the document (and incremented the atomic guard’s Raft index), the save will fail with a _ConcurrencyException_. + +* **If no document is found**, RavenDB will check whether a matching atomic guard exists + (as in the case when the document was deleted outside of a cluster-wide session): + * **If an atomic guard exists**, + the client constructs a change vector for the document using the atomic guard’s Raft index, and the document will be saved with this change vector. + * **If no atomic guard exists**, + the document is treated as a brand new document and will be saved as usual. + + diff --git a/docs/compare-exchange/content/_atomic-guards-php.mdx b/docs/compare-exchange/content/_atomic-guards-php.mdx new file mode 100644 index 0000000000..04ed102fb7 --- /dev/null +++ b/docs/compare-exchange/content/_atomic-guards-php.mdx @@ -0,0 +1,273 @@ +import Admonition from '@theme/Admonition'; +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +import CodeBlock from '@theme/CodeBlock'; + + + +* **Atomic Guards** are [compare-exchange key/value items](../compare-exchange/overview) + that RavenDB creates and manages **automatically** to guarantee [ACID](../server/clustering/cluster-transactions#cluster-transactions-properties) behavior in cluster-wide sessions. + +* When a document is created in a cluster-wide session, RavenDB associates it with a unique atomic guard item. + Atomic guards coordinate concurrent writes by different sessions to the same document. + +* In this article: + * [Atomic guard creation and update](../compare-exchange/atomic-guards#atomic-guard-creation-and-update) + * [Atomic guard usage example](../compare-exchange/atomic-guards#atomic-guard-usage-example) + * [Atomic guard database scope](../compare-exchange/atomic-guards#atomic-guard-database-scope) + * [Disabling atomic guards](../compare-exchange/atomic-guards#disabling-atomic-guards) + * [When are atomic guards removed](../compare-exchange/atomic-guards#when-are-atomic-guards-removed) + * [Best practice when storing a document in a cluster-wide transaction](../compare-exchange/atomic-guards#best-practice-when-storing-a-document-in-a-cluster-wide-transaction) + + + +--- + +## Atomic guard creation and update + + +Atomic guards are created and managed **only when the session's transaction mode is set to [ClusterWide](../client-api/session/cluster-transaction/overview#open-a-cluster-transaction)**. + + +* **When creating a new document**: + A new atomic guard is created when a new document is successfully saved. + +* **When modifying an existing document that already has an atomic guard**: + * The atomic guard’s Raft index is incremented when the document is successfully saved after being modified. + This allows RavenDB to detect that the document has changed. + * If another session had loaded the document before the document's version changed, it will not be able to save its changes + unless it first reloads the updated version. Otherwise, a `ConcurrencyException` is thrown. + +* **When modifying an existing document that doesn't have an atomic guard**: + * A new atomic guard is created when modifying an existing document that does not yet have one. + * The absence of the atomic guard may be because the document was created in a single-node session, + or because its atomic guard was manually removed (which is not recommended). + +* **When saving a document fails**: + * If a session's `saveChanges()` fails, the entire session is rolled back and the atomic guard is Not created. + * Ensure your business logic is designed to re-execute the session in case saving changes fails for any reason. + +--- + +## Atomic guard usage example + +In the code sample below, an atomic guard is automatically created when a new document is saved. +It is then used to detect and prevent conflicting writes: when two sessions load and modify the same document, +only the first save succeeds, and the second fails with a _ConcurrencyException_. + + + +{`// Open a cluster-wide session: +$sessionOptions = new SessionOptions(); +$sessionOptions->setTransactionMode(TransactionMode::clusterWide()); + +$session = $store->openSession($sessionOptions); + +try \{ + $session->store(new User(), "users/johndoe"); + // An atomic-guard is now automatically created for the new document "users/johndoe". + $session->saveChanges(); +\} finally \{ + $session->close(); +\} + +// Open two concurrent cluster-wide sessions: + +$sessionOptions1 = new SessionOptions(); +$sessionOptions1->setTransactionMode(TransactionMode::clusterWide()); + +$session1 = $store->openSession($sessionOptions1); +try \{ + $sessionOptions2 = new SessionOptions(); + $sessionOptions2->setTransactionMode(TransactionMode::clusterWide()); + $session2 = $store->openSession($sessionOptions2); + + try \{ + // Both sessions load the same document: + var $loadedUser1 = $session1->load(User::class, "users/johndoe"); + $loadedUser1->setName("jindoe"); + + $loadedUser2 = $session2->load(User::class, "users/johndoe"); + $loadedUser2->setName("jandoe"); + + // session1 saves its changes first — + // this increments the Raft index of the associated atomic guard. + $session1->saveChanges(); + + // session2 tries to save using an outdated atomic guard version + // and fails with a ConcurrencyException. + $session2->saveChanges(); + \} finally \{ + $session2->close(); + \} + +\} finally \{ + $session1->close(); +\} +`} + + + +After running the above example, you can view the automatically created atomic guard in the **Compare-Exchange view** +in the Studio: + +![Atomic Guard](../assets/atomic-guard.png) + +1. These are **custom compare-exchange items**, created by the user for any purpose, + as described in [Create compare-exchange items](../compare-exchange/create-cmpxchg-items). + They are NOT the automatically created atomic guards. + +2. This is the **atomic guard** that was generated by running the example above. + The generated atomic guard **key** is: `rvn-atomic/users/johndoe`. It is composed of: + * The prefix `rvn-atomic/`. + * The ID of the associated document (`users/johndoe`). + + + * Although this Studio view allows editing compare-exchange items, **do NOT delete or modify atomic guard entries**. + * Doing so will interfere with RavenDB's ability to track document versioning through atomic guards. + + +--- + +## Atomic guard database scope + +* Atomic guards are local to the database on which they were defined. + +* Since atomic guards are implemented as compare-exchange items, + they are Not externally replicated to other databases by any ongoing replication task. + Learn more in [why compare-exchange items are not replicated](../compare-exchange/overview#why-compare-exchange-items-are-not-replicated-to-external-databases). + +--- + +## Disabling atomic guards + +* Before atomic guards were introduced (in RavenDB 5.2), client code had to explicitly manage compare-exchange entries + to ensure concurrency control and maintain ACID guarantees in cluster-wide transactions. + +* You can still take this manual approach by disabling the automatic use of atomic guards in a cluster-wide session, + and managing the required [compare-exchange key/value pairs](../compare-exchange/overview) yourself, + as shown in this [example](../compare-exchange/overview#example-i---email-address-reservation). + +* To disable the automatic creation and use of atomic guards in a cluster-wide session, + set the session's `DisableAtomicDocumentWritesInClusterWideTransaction` configuration option to `true`. + + + +{`$sessionOptions = new SessionOptions(); +$sessionOptions->setTransactionMode(TransactionMode::clusterWide()); +$sessionOptions->setDisableAtomicDocumentWritesInClusterWideTransaction(true); + +$session = $store->openSession($sessionOptions); + +try \{ + $session->store(new User(), "users/johndoe"); + // No atomic-guard will be created upon saveChanges + $session->saveChanges(); +\} finally \{ + $session->close(); +\} +`} + + + +--- + +## When are atomic guards removed + +Atomic guards are removed **automatically** in the following scenarios: +(you don't need to clean them up manually) + +* **Document deleted via a cluster-wide session**: + * Create a document using a cluster-wide session (an associated atomic guard is created). + * Delete the document using a cluster-wide session - its atomic guard will be removed automatically. + +* **Document expires via the expiration feature**: + * Create a document using a cluster-wide session (an associated atomic guard is created). + * Add the `@expires` metadata property the document, as described in [Document expiration](../studio/database/settings/document-expiration). + * When the expiration time is reached, the document and its atomic guard will both be removed automatically. + * Since different cleanup tasks handle the removal of **expired** documents and the removal of their associated atomic guards, + it may happen that atomic guards of removed documents would linger in the compare-exchange entries list a short while longer before they are removed. + You do Not need to remove such atomic guards yourself, they will be removed by the cleanup task. + + + +* **Do not delete or modify atomic guards manually**. + If a session attempts to save a document whose atomic guard was removed or modified, it will fail with an error. + +* If you accidentally remove an atomic guard that is associated with an existing document, + you can restore it by re-saving the document in a cluster-wide session, + this will re-create the atomic guard automatically. + + + +--- + +## Best practice when storing a document in a cluster-wide transaction + +* When working with a cluster-wide session, + we recommend that you always **`load` the document into the session before storing it** - + even if the document is expected to be a new document. + +* This is especially important if a document (originally created in a cluster-wide transaction) was deleted **outside** of a cluster-wide session - + as when using a [single-node session](../client-api/session/cluster-transaction/overview#cluster-wide-transaction-vs-single-node-transaction) + or the [DeleteByQueryOperation](../client-api/operations/common/delete-by-query). + In these cases, the document is deleted, but the atomic guard remains (it is not automatically removed). + If you attempt to re-create such a document without loading it first, + RavenDB will fail to save it because the session is unaware of the existing atomic guard’s latest Raft index. + +* In this example, the document is loaded into the session BEFORE creating or modifying it: + + + +{`// Open a cluster-wide session +$sessionOptions = new SessionOptions(); +$sessionOptions->setTransactionMode(TransactionMode::clusterWide()); + +$session = $store->openSession($sessionOptions); +try \{ + // Load the user document BEFORE creating or updating + $user = $session->load(User::class, "users/johndoe"); + + if ($user === null) \{ + // Document doesn't exist => create a new document: + $newUser = new User(); + $newUser->setName("John Doe"); + // ... initialize other properties + + // Store the new user document in the session + $session->store($newUser, "users/johndoe"); + \} else \{ + // Document exists => apply your modifications: + $user->setName("New name"); + // ... make any other updates + + // No need to call Store() again + // RavenDB tracks changes on loaded entities + \} + + // Commit your changes + $session->saveChanges(); +\} finally \{ + $session->close(); +\} +`} + + + + + +When _loading_ a document in a cluster-wide session, RavenDB attempts to retrieve the document from the document store: + +* **If the document is found**, it is loaded into the session, + and modifications will be saved successfully as long as no other session has modified the document in the meantime. + * RavenDB checks whether the Raft index of the atomic guard associated with the document matches the version tracked by the current session. + If another session has already updated the document (and incremented the atomic guard’s Raft index), the save will fail with a _ConcurrencyException_. + +* **If no document is found**, RavenDB will check whether a matching atomic guard exists + (as in the case when the document was deleted outside of a cluster-wide session): + * **If an atomic guard exists**, + the client constructs a change vector for the document using the atomic guard’s Raft index, and the document will be saved with this change vector. + * **If no atomic guard exists**, + the document is treated as a brand new document and will be saved as usual. + + diff --git a/docs/compare-exchange/content/_atomic-guards-python.mdx b/docs/compare-exchange/content/_atomic-guards-python.mdx new file mode 100644 index 0000000000..9380170379 --- /dev/null +++ b/docs/compare-exchange/content/_atomic-guards-python.mdx @@ -0,0 +1,248 @@ +import Admonition from '@theme/Admonition'; +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +import CodeBlock from '@theme/CodeBlock'; + + + +* **Atomic Guards** are [compare-exchange key/value items](../compare-exchange/overview) + that RavenDB creates and manages **automatically** to guarantee [ACID](../server/clustering/cluster-transactions#cluster-transactions-properties) behavior in cluster-wide sessions. + +* When a document is created in a cluster-wide session, RavenDB associates it with a unique atomic guard item. + Atomic guards coordinate concurrent writes by different sessions to the same document. + +* In this article: + * [Atomic guard creation and update](../compare-exchange/atomic-guards#atomic-guard-creation-and-update) + * [Atomic guard usage example](../compare-exchange/atomic-guards#atomic-guard-usage-example) + * [Atomic guard database scope](../compare-exchange/atomic-guards#atomic-guard-database-scope) + * [Disabling atomic guards](../compare-exchange/atomic-guards#disabling-atomic-guards) + * [When are atomic guards removed](../compare-exchange/atomic-guards#when-are-atomic-guards-removed) + * [Best practice when storing a document in a cluster-wide transaction](../compare-exchange/atomic-guards#best-practice-when-storing-a-document-in-a-cluster-wide-transaction) + + + +--- + +## Atomic guard creation and update + + +Atomic guards are created and managed **only when the session's transaction mode is set to [CLUSTER_WIDE](../client-api/session/cluster-transaction/overview#open-a-cluster-transaction)**. + + +* **When creating a new document**: + A new atomic guard is created when a new document is successfully saved. + +* **When modifying an existing document that already has an atomic guard**: + * The atomic guard’s Raft index is incremented when the document is successfully saved after being modified. + This allows RavenDB to detect that the document has changed. + * If another session had loaded the document before the document's version changed, it will not be able to save its changes + unless it first reloads the updated version. Otherwise, a `ConcurrencyException` is thrown. + +* **When modifying an existing document that doesn't have an atomic guard**: + * A new atomic guard is created when modifying an existing document that does not yet have one. + * The absence of the atomic guard may be because the document was created in a single-node session, + or because its atomic guard was manually removed (which is not recommended). + +* **When saving a document fails**: + * If a session's `save_changes()` fails, the entire session is rolled back and the atomic guard is Not created. + * Ensure your business logic is designed to re-execute the session in case saving changes fails for any reason. + +--- + +## Atomic guard usage example + +In the code sample below, an atomic guard is automatically created when a new document is saved. +It is then used to detect and prevent conflicting writes: when two sessions load and modify the same document, +only the first save succeeds, and the second fails with a _ConcurrencyException_. + + + +{`with store.open_session( + # Open a cluster-wide session: + session_options=SessionOptions(transaction_mode=TransactionMode.CLUSTER_WIDE) +) as session: + session.store(User(), "users/johndoe") + session.save_changes() + # An atomic-guard is now automatically created for the new document "users/johndoe" + +# Open two concurrent cluster-wide sessions: +with store.open_session( + session_options=SessionOptions(transaction_mode=TransactionMode.CLUSTER_WIDE) +) as session1: + with store.open_session( + session_options=SessionOptions(transaction_mode=TransactionMode.CLUSTER_WIDE) + ) as session2: + # Both sessions load the same document: + loaded_user_1 = session1.load("users/johndoe", User) + loaded_user_1.name = "jindoe" + loaded_user_2 = session2.load("users/johndoe", User) + loaded_user_2.name = "jandoe" + + # session1 saves its changes first — + # this increments the Raft index of the associated atomic guard. + session1.save_changes() + + # session2 tries to save using an outdated atomic guard version + # and fails with a ConcurrencyException. + session2.save_changes() +`} + + + +After running the above example, you can view the automatically created atomic guard in the **Compare-Exchange view** +in the Studio: + +![Atomic Guard](../assets/atomic-guard.png) + +1. These are **custom compare-exchange items**, created by the user for any purpose, + as described in [Create compare-exchange items](../compare-exchange/create-cmpxchg-items). + They are NOT the automatically created atomic guards. + +2. This is the **atomic guard** that was generated by running the example above. + The generated atomic guard **key** is: `rvn-atomic/users/johndoe`. It is composed of: + * The prefix `rvn-atomic/`. + * The ID of the associated document (`users/johndoe`). + + + * Although this Studio view allows editing compare-exchange items, **do NOT delete or modify atomic guard entries**. + * Doing so will interfere with RavenDB's ability to track document versioning through atomic guards. + + +--- + +## Atomic guard database scope + +* Atomic guards are local to the database on which they were defined. + +* Since atomic guards are implemented as compare-exchange items, + they are Not externally replicated to other databases by any ongoing replication task. + Learn more in [why compare-exchange items are not replicated](../compare-exchange/overview#why-compare-exchange-items-are-not-replicated-to-external-databases). + +--- + +## Disabling atomic guards + +* Before atomic guards were introduced (in RavenDB 5.2), client code had to explicitly manage compare-exchange entries + to ensure concurrency control and maintain ACID guarantees in cluster-wide transactions. + +* You can still take this manual approach by disabling the automatic use of atomic guards in a cluster-wide session, + and managing the required [compare-exchange key/value pairs](../compare-exchange/overview) yourself, + as shown in this [example](../compare-exchange/overview#example-i---email-address-reservation). + +* To disable the automatic creation and use of atomic guards in a cluster-wide session, + set the session's `DisableAtomicDocumentWritesInClusterWideTransaction` configuration option to `true`. + + + +{`with store.open_session( + # Open a cluster-wide session + session_options=SessionOptions( + transaction_mode=TransactionMode.CLUSTER_WIDE, + disable_atomic_document_writes_in_cluster_wide_transaction=True, + ) +) as session: + session.store(User(), "users/johndoe") + + # No atomic-guard will be created upon save_changes + session.save_changes() +`} + + + +--- + +## When are atomic guards removed + +Atomic guards are removed **automatically** in the following scenarios: +(you don't need to clean them up manually) + +* **Document deleted via a cluster-wide session**: + * Create a document using a cluster-wide session (an associated atomic guard is created). + * Delete the document using a cluster-wide session - its atomic guard will be removed automatically. + +* **Document expires via the expiration feature**: + * Create a document using a cluster-wide session (an associated atomic guard is created). + * Add the `@expires` metadata property the document, as described in [Document expiration](../studio/database/settings/document-expiration). + * When the expiration time is reached, the document and its atomic guard will both be removed automatically. + * Since different cleanup tasks handle the removal of **expired** documents and the removal of their associated atomic guards, + it may happen that atomic guards of removed documents would linger in the compare-exchange entries list a short while longer before they are removed. + You do Not need to remove such atomic guards yourself, they will be removed by the cleanup task. + + + +* **Do not delete or modify atomic guards manually**. + If a session attempts to save a document whose atomic guard was removed or modified, it will fail with an error. + +* If you accidentally remove an atomic guard that is associated with an existing document, + you can restore it by re-saving the document in a cluster-wide session, + this will re-create the atomic guard automatically. + + + +--- + +## Best practice when storing a document in a cluster-wide transaction + +* When working with a cluster-wide session, + we recommend that you always **`load` the document into the session before storing it** - + even if the document is expected to be a new document. + +* This is especially important if a document (originally created in a cluster-wide transaction) was deleted **outside** of a cluster-wide session - + as when using a [single-node session](../client-api/session/cluster-transaction/overview#cluster-wide-transaction-vs-single-node-transaction) + or the [DeleteByQueryOperation](../client-api/operations/common/delete-by-query). + In these cases, the document is deleted, but the atomic guard remains (it is not automatically removed). + If you attempt to re-create such a document without loading it first, + RavenDB will fail to save it because the session is unaware of the existing atomic guard’s latest Raft index. + +* In this example, the document is loaded into the session BEFORE creating or modifying it: + + + +{`with store.open_session( + session_options=SessionOptions( + # Open a cluster-wide session + transaction_mode=TransactionMode.CLUSTER_WIDE + ) +) as session: + # Load the user document BEFORE creating or updating + user = session.load("users/johndoe", User) + + if user is None: + # Document doesn't exist => create a new document + new_user = User() + new_user.name = "John Doe" + # ... initialize other properties + + # Store the new user document in the session + session.store(new_user, "users/johndoe") + else: + # Document exists => apply your modifications + user.name = "New name" + # ... make any other updates + + # No need to call store() again + # RavenDB tracks changes on loaded entities + + # Commit your changes + session.save_changes() +`} + + + + + +When _loading_ a document in a cluster-wide session, RavenDB attempts to retrieve the document from the document store: + +* **If the document is found**, it is loaded into the session, + and modifications will be saved successfully as long as no other session has modified the document in the meantime. + * RavenDB checks whether the Raft index of the atomic guard associated with the document matches the version tracked by the current session. + If another session has already updated the document (and incremented the atomic guard’s Raft index), the save will fail with a _ConcurrencyException_. + +* **If no document is found**, RavenDB will check whether a matching atomic guard exists + (as in the case when the document was deleted outside of a cluster-wide session): + * **If an atomic guard exists**, + the client constructs a change vector for the document using the atomic guard’s Raft index, and the document will be saved with this change vector. + * **If no atomic guard exists**, + the document is treated as a brand new document and will be saved as usual. + + diff --git a/docs/compare-exchange/content/_cmpxchg-in-dynamic-queries-csharp.mdx b/docs/compare-exchange/content/_cmpxchg-in-dynamic-queries-csharp.mdx new file mode 100644 index 0000000000..c0f29f37e9 --- /dev/null +++ b/docs/compare-exchange/content/_cmpxchg-in-dynamic-queries-csharp.mdx @@ -0,0 +1,494 @@ +import Admonition from '@theme/Admonition'; +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +import CodeBlock from '@theme/CodeBlock'; + + + +* You can use compare-exchange values in **dynamic queries** in two ways: + * **Project** compare-exchange values into the query results. + * **Filter** documents based on compare-exchange values. + +* Dynamic queries are queries that do not rely on a predefined static index. + When you run a dynamic query with a filtering condition, RavenDB automatically creates an auto-index to serve it. + Learn more about dynamic queries in the [Query overview](../client-api/session/querying/how-to-query). + +* In this article: + * [Projecting compare-exchange values in query results](../compare-exchange/cmpxchg-in-dynamic-queries#projecting-compare-exchange-values-in-query-results) + * [Filtering by compare-exchange value](../compare-exchange/cmpxchg-in-dynamic-queries#filtering-by-compare-exchange-value) + * [Example 1 - Filtering when the compare-exchange value is a string](../compare-exchange/cmpxchg-in-dynamic-queries#example-1---filtering-when-the-compare-exchange-value-is-a-string) + * [Example 2 - Filtering when the compare-exchange value is an object](../compare-exchange/cmpxchg-in-dynamic-queries#example-2---filtering-when-the-compare-exchange-value-is-an-object) + * [Syntax](../compare-exchange/cmpxchg-in-dynamic-queries#syntax) + + + +--- + +## Projecting compare-exchange values in query results + +* You can project values from compare-exchange items alongside fields from the queried documents. + +* The following example is based on the sample data described in: [Create sample compare-exchange items](../compare-exchange/indexing-cmpxchg-values#create-sample-compare-exchange-items). + In this example, we want to retrieve the current number of guests in each room. + We query all _HotelRoom_ documents and project: + * the number of guests (from the compare-exchange item's value), and + * the room number (from the document field). + +* No auto-index is created in this case, since the query doesn’t apply any filters. + + + +```csharp +// The session does not need to be opened in cluster-wide mode +using (var session = store.OpenSession()) +{ + List numberOfGuestsPerRoom = session + .Query() + .Select(x => new ProjectedNumberOfGuests + { + // Project content from the compare-exchange item: + // Call 'RavenQuery.CmpXchg' to load the compare-exchange value by its key. + CurrentNumberOfGuests = + RavenQuery.CmpXchg(x.RoomNumber).CurrentNumberOfGuests, + + // Project content from the document: + RoomNumber = x.RoomNumber + }) + .ToList(); +} +``` + + +```csharp +// The session does not need to be opened in cluster-wide mode +using (var asyncSession = store.OpenAsyncSession()) +{ + List numberOfGuestsPerRoom = await asyncSession + .Query() + .Select(x => new ProjectedNumberOfGuests + { + // Project content from the compare-exchange item: + // Call 'RavenQuery.CmpXchg' to load the compare-exchange value by its key. + CurrentNumberOfGuests = + RavenQuery.CmpXchg(x.RoomNumber).CurrentNumberOfGuests, + + // Project content from the document: + RoomNumber = x.RoomNumber + }) + .ToListAsync(); +} +``` + + +```csharp +// The session does not need to be opened in cluster-wide mode +using (var session = store.OpenSession()) +{ + List numberOfGuestsPerRoom = session.Advanced + .DocumentQuery() + .SelectFields(QueryData.CustomFunction( + alias: "room", + func: @" + { + // Project content from the compare-exchange item: + // Call 'cmpxchg' to load the compare-exchange value by its key. + CurrentNumberOfGuests : cmpxchg(room.RoomNumber).CurrentNumberOfGuests, + + // Project content from the document: + RoomNumber : room.RoomNumber + }")) + .ToList(); +} +``` + + +```csharp +// The session does not need to be opened in cluster-wide mode +using (var asyncSession = store.OpenAsyncSession()) +{ + var numberOfGuestsPerRoom = await asyncSession.Advanced + .AsyncDocumentQuery() + .SelectFields(QueryData.CustomFunction( + alias: "room", + func: @" + { + // Project content from the compare-exchange item: + // Call 'cmpxchg' to load the compare-exchange value by its key. + CurrentNumberOfGuests : cmpxchg(room.RoomNumber).CurrentNumberOfGuests, + + // Project content from the document: + RoomNumber : room.RoomNumber + }")) + .ToListAsync(); +} +``` + + +```csharp +using (var session = store.OpenSession()) +{ + var numberOfGuestsPerRoom = session.Advanced + .RawQuery(@" + from 'HotelRooms' as x + select { + CurrentNumberOfGuests : cmpxchg(x.RoomNumber).CurrentNumberOfGuests, + RoomNumber : x.RoomNumber + }") + .ToList(); +} +``` + + +```csharp +using (var asyncSession = store.OpenAsyncSession()) +{ + var numberOfGuestsPerRoom = await asyncSession.Advanced + .AsyncRawQuery(@" + from 'HotelRooms' as x + select { + CurrentNumberOfGuests : cmpxchg(x.RoomNumber).CurrentNumberOfGuests, + RoomNumber : x.RoomNumber + }") + .ToListAsync(); +} +``` + + +```sql +from "HotelRooms" as x +select { + CurrentNumberOfGuests : cmpxchg(x.RoomNumber).CurrentNumberOfGuests, + RoomNumber : x.RoomNumber +} +``` + + + +--- + +## Filtering by compare-exchange value + +#### Example 1 - Filtering when the compare-exchange value is a string + +* You can filter documents based on a compare-exchange value. + For example, to find a user with a blocked email address, + you can filter by the compare-exchange value that stores the blocked email. + +* **Limitation**: + When filtering with `RavenQuery.CmpXchg` inside the `Where` predicate (as shown in this example), + the compare-exchange value must be a _string_. + If your compare-exchange value is of another type, you can still filter the results - + see the [next example](../compare-exchange/cmpxchg-in-dynamic-queries#example-2---filtering-when-the-compare-exchange-value-is-an-object). + +* Since this query uses filtering, RavenDB will automatically create an auto-index to serve it. + In this case, the auto-index `Auto/Users/ByEmail` will be created. + + + +```csharp +// First, let's create a compare-exchange item with a blocked email address for this example: +var putResult = store.Operations.Send(new PutCompareExchangeValueOperation( + "blocked-address", "someUser@Company.com", 0)); + +// Query the 'Users' collection +// Find a user with an email address that matches the blocked address: +// (The session does not need to be opened in cluster-wide mode) +using (var session = store.OpenSession()) +{ + var blockedUser = session.Query() + // Call 'RavenQuery.CmpXchg' to load the compare-exchange value by its key + // Filter the users collection by the value: + .Where(x => x.Email == RavenQuery.CmpXchg("blocked-address")) + .FirstOrDefault(); +} + +// The results will include the user document whose email address is "someUser@company.com" +// (assuming such a user exists in your data). +``` + + +```csharp +// First, let's create a compare-exchange item with a blocked email address for this example: +var putResult = await store.Operations.SendAsync(new PutCompareExchangeValueOperation( + "blocked-address", "someUser@Company.com", 0)); + +// Query the 'Users' collection +// Find a user with an email address that matches the blocked address: +// (The session does not need to be opened in cluster-wide mode) +using (var asyncSession = store.OpenAsyncSession()) +{ + var blockedUser = await asyncSession.Query() + // Call 'RavenQuery.CmpXchg' to load the compare-exchange value by its key + // Filter the users collection by the value: + .Where(x => x.Email == RavenQuery.CmpXchg("blocked-address")) + .FirstOrDefaultAsync(); +} + +// The results will include the user document whose email address is "someUser@company.com" +// (assuming such a user exists in your data). +``` + + +```csharp +var putResult = store.Operations.Send(new PutCompareExchangeValueOperation( + "blocked-address", "someUser@Company.com", 0)); + +using (var session = store.OpenSession()) +{ + var blockedUser = session.Advanced + .RawQuery(@" + from 'Users' + where Email == cmpxchg('blocked-address') + ") + .FirstOrDefault(); +} +``` + + +```csharp +var putResult = await store.Operations.SendAsync(new PutCompareExchangeValueOperation( + "blocked-address", "someUser@Company.com", 0)); + +using (var asyncSession = store.OpenAsyncSession()) +{ + var blockedUser = await asyncSession.Advanced + .AsyncRawQuery(@" + from 'Users' + where Email == cmpxchg('blocked-address') + ") + .FirstOrDefaultAsync(); +} +``` + + +```sql +from "Users" +where Email == cmpxchg("blocked-address") +limit 0, 1 +``` + + +```csharp +public class User +{ + public string Id { get; set; } + public string Name { get; set; } + public string Email { get; set; } +} +``` + + + +#### Example 2 - Filtering when the compare-exchange value is an object + +* The following example shows how to filter documents based on a compare-exchange value that is an _object_. + We query for users whose email ends with one of the domains stored in the object held by the compare-exchange item. + +* Retrieve the compare-exchange value outside the query, then use its content to build the filter logic. + +* Since this query uses filtering, RavenDB will automatically create an auto-index to serve it. + In this case, the auto-index `Auto/Users/ByEmail` will be created. + + + +```csharp +// First, let's create a compare-exchange item with a value that is an object. +// The object contains multiple blocked email domains for this example. +var putResult = store.Operations.Send(new PutCompareExchangeValueOperation( + "blocked-domains", + new BlockedDomains() { Domains = ["suspicious-company-1.com", "suspicious-company-2.org"]}, + 0)); + +// Retrieve the compare-exchange value before running the query +BlockedDomains blockedDomains = store.Operations.Send( + new GetCompareExchangeValueOperation("blocked-domains")).Value; + +// Query the 'Users' collection +// Find users whose email address ends with one of the blocked domains. +// (The session does not need to be opened in cluster-wide mode) +using (var session = store.OpenSession()) +{ + var blockedUsers = session.Query() + .Where(user => + user.Email.EndsWith(blockedDomains.Domains[0]) || + user.Email.EndsWith(blockedDomains.Domains[1])) + .ToList(); +} + +// The results will include users whose email address domain ends with +// one of the values stored in the compare-exchange item, +// assuming such users exist in your data. +``` + + +```csharp +// First, let's create a compare-exchange item with a value that is an object. +// The object contains multiple blocked email domains for this example. +var putResult = await store.Operations.SendAsync(new PutCompareExchangeValueOperation( + "blocked-domains", + new BlockedDomains() { Domains = ["suspicious-company-1.com", "suspicious-company-2.org"]}, + 0)); + +// Retrieve the compare-exchange value before running the query +BlockedDomains blockedDomains = await store.Operations.SendAsync( + new GetCompareExchangeValueOperation("blocked-domains")).Value; + +// Query the 'Users' collection +// Find users whose email address ends with one of the blocked domains. +// (The session does not need to be opened in cluster-wide mode) +using (var asyncSession = store.OpenAsyncSession()) +{ + var blockedUsers = await asyncSession.Query() + .Where(user => + user.Email.EndsWith(blockedDomains.Domains[0]) || + user.Email.EndsWith(blockedDomains.Domains[1])) + .ToListAsync(); +} + +// The results will include users whose email address domain ends with +// one of the values stored in the compare-exchange item, +// assuming such users exist in your data. +``` + + +```csharp +// First, let's create a compare-exchange item with a value that is an object. +// The object contains multiple blocked email domains for this example. +var putResult = store.Operations.Send(new PutCompareExchangeValueOperation( + "blocked-domains", + new BlockedDomains() { Domains = ["suspicious-company-1.com", "suspicious-company-2.org"]}, + 0)); + +// Retrieve the compare-exchange value before running the query +BlockedDomains blockedDomains = store.Operations.Send( + new GetCompareExchangeValueOperation("blocked-domains")).Value; + +// Query the 'Users' collection +// Find users whose email address ends with one of the blocked domains. +// (The session does not need to be opened in cluster-wide mode) +using (var session = store.OpenSession()) +{ + var blockedUsers = session.Advanced + .DocumentQuery() + .WhereEndsWith(x => x.Email, blockedDomains.Domains[0]) + .OrElse() + .WhereEndsWith(x=> x.Email, blockedDomains.Domains[1]) + .ToList(); +} + +// The results will include users whose email address domain ends with +// one of the values stored in the compare-exchange item, +// assuming such users exist in your data. +``` + + +```csharp +// First, let's create a compare-exchange item with a value that is an object. +// The object contains multiple blocked email domains for this example. +var putResult = await store.Operations.SendAsync(new PutCompareExchangeValueOperation( + "blocked-domains", + new BlockedDomains() { Domains = ["suspicious-company-1.com", "suspicious-company-2.org"]}, + 0)); + +// Retrieve the compare-exchange value before running the query +BlockedDomains blockedDomains = await store.Operations.SendAsync( + new GetCompareExchangeValueOperation("blocked-domains")).Value; + +// Query the 'Users' collection +// Find users whose email address ends with one of the blocked domains. +// (The session does not need to be opened in cluster-wide mode) +using (var asyncSession = store.OpenAsyncSession()) +{ + var blockedUsers = await asyncSession.Advanced + .AsyncDocumentQuery() + .WhereEndsWith(x => x.Email, blockedDomains.Domains[0]) + .OrElse() + .WhereEndsWith(x=> x.Email, blockedDomains.Domains[1]) + .ToListAsync(); +} + +// The results will include users whose email address domain ends with +// one of the values stored in the compare-exchange item, +// assuming such users exist in your data. +``` + + +```csharp +var putResult = store.Operations.Send(new PutCompareExchangeValueOperation( + "blocked-domains", + new BlockedDomains() { Domains = ["suspicious-company-1.com", "suspicious-company-2.org"]}, + 0)); + +BlockedDomains blockedDomains = store.Operations.Send( + new GetCompareExchangeValueOperation("blocked-domains")).Value; + +using (var session = store.OpenSession()) +{ + var blockedUsers = session.Advanced + .RawQuery(@" + from 'Users' + where endsWith(Email, 'suspicious-company-1.com') + or + endsWith(Email, 'suspicious-company-2.org') + ") + .ToList(); +} +``` + + +```csharp +var putResult = await store.Operations.SendAsync(new PutCompareExchangeValueOperation( + "blocked-domains", + new BlockedDomains() { Domains = ["suspicious-company-1.com", "suspicious-company-2.org"]}, + 0)); + +BlockedDomains blockedDomains = await store.Operations.SendAsync( + new GetCompareExchangeValueOperation("blocked-domains")).Value; + +using (var asyncSession = store.OpenAsyncSession()) +{ + var blockedUsers = await asyncSession.Advanced + .AsyncRawQuery(@" + from 'Users' + where endsWith(Email, 'suspicious-company-1.com') + or + endsWith(Email, 'suspicious-company-2.org') + ") + .ToListAsync(); +} +``` + + +```sql +from "Users" +where endsWith(Email, "suspicious-company-1.com") +or +endsWith(Email, "suspicious-company-2.org") +``` + + +```csharp +public class User +{ + public string Id { get; set; } + public string Name { get; set; } + public string Email { get; set; } +} +``` + + + +--- + +## Syntax + +### `RavenQuery.CmpXchg()` +This method can be used to filter a LINQ query +or to project fields from a compare-exchange value into the query results. + + +```csharp +// Get a compare-exchange value by key. +public static T CmpXchg(string key) +``` + diff --git a/docs/compare-exchange/content/_cmpxchg-in-dynamic-queries-nodejs.mdx b/docs/compare-exchange/content/_cmpxchg-in-dynamic-queries-nodejs.mdx new file mode 100644 index 0000000000..2209ef9e56 --- /dev/null +++ b/docs/compare-exchange/content/_cmpxchg-in-dynamic-queries-nodejs.mdx @@ -0,0 +1,256 @@ +import Admonition from '@theme/Admonition'; +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +import CodeBlock from '@theme/CodeBlock'; + + + +* You can use compare-exchange values in **dynamic queries** in two ways: + * **Project** compare-exchange values into the query results. + * **Filter** documents based on compare-exchange values. + +* Dynamic queries are queries that do not rely on a predefined static index. + When you run a dynamic query with a filtering condition, RavenDB automatically creates an auto-index to serve it. + Learn more about dynamic queries in the [Query overview](../client-api/session/querying/how-to-query). + +* In this article: + * [Projecting compare-exchange values in query results](../compare-exchange/cmpxchg-in-dynamic-queries#projecting-compare-exchange-values-in-query-results) + * [Filtering by compare-exchange value](../compare-exchange/cmpxchg-in-dynamic-queries#filtering-by-compare-exchange-value) + * [Example 1 - Filtering when the compare-exchange value is a string](../compare-exchange/cmpxchg-in-dynamic-queries#example-1---filtering-when-the-compare-exchange-value-is-a-string) + * [Example 2 - Filtering when the compare-exchange value is an object](../compare-exchange/cmpxchg-in-dynamic-queries#example-2---filtering-when-the-compare-exchange-value-is-an-object) + * [Syntax](../compare-exchange/cmpxchg-in-dynamic-queries#syntax) + + + +--- + +## Projecting compare-exchange values in query results + +* You can project values from compare-exchange items alongside fields from the queried documents. + +* The following example is based on the sample data described in: [Create sample compare-exchange items](../compare-exchange/indexing-cmpxchg-values#create-sample-compare-exchange-items). + In this example, we want to retrieve the current number of guests in each room. + We query all _HotelRoom_ documents and project: + * the number of guests (from the compare-exchange item's value), and + * the room number (from the document field). + +* No auto-index is created in this case, since the query doesn’t apply any filters. + + + +```js +// The session does not need to be opened in cluster-wide mode +const session = documentStore.openSession(); + +// Define the projection using QueryData: +const queryData = QueryData.customFunction("room", `{ + // Project from compare-exchange: call cmpxchg with RoomNumber + CurrentNumberOfGuests: cmpxchg(room.RoomNumber).CurrentNumberOfGuests, + + // Project from document field + RoomNumber: room.RoomNumber +}`); + +const numberOfGuestsPerRoom = await session + .query({ collection: "HotelRooms" }) + // Project content: + .selectFields(queryData) + .all(); +``` + + +```js +const session = documentStore.openSession(); + +const numberOfGuestsPerRoom = await session.advanced + .rawQuery(` + from 'HotelRooms' as x + select { + CurrentNumberOfGuests : cmpxchg(x.RoomNumber).CurrentNumberOfGuests, + RoomNumber : x.RoomNumber + } + `) + .all(); +``` + + +```sql +from "HotelRooms" as x +select { + CurrentNumberOfGuests : cmpxchg(x.RoomNumber).CurrentNumberOfGuests, + RoomNumber : x.RoomNumber +} +``` + + + +--- + +## Filtering by compare-exchange value + +#### Example 1 - Filtering when the compare-exchange value is a string + +* You can filter documents based on a compare-exchange value. + For example, to find a user with a blocked email address, + you can filter by the compare-exchange value that stores the blocked email. + +* **Limitation**: + When filtering with `cmpxchg` inside the `where` predicate (as shown in this example), + the compare-exchange value must be a _string_. + If your compare-exchange value is of another type, you can still filter the results - + see the [next example](../compare-exchange/cmpxchg-in-dynamic-queries#example-2---filtering-when-the-compare-exchange-value-is-an-object). + +* Since this query uses filtering, RavenDB will automatically create an auto-index to serve it. + In this case, the auto-index `Auto/Users/ByEmail` will be created. + + + +```js +// First, let's create a compare-exchange item with a blocked email address for this example: +const putResult = await documentStore.operations.send( + new PutCompareExchangeValueOperation("blocked-address", "someUser@Company.com", 0) +); + +// Query the 'Users' collection +// Find a user with an email address that matches the blocked address: +// (The session does not need to be opened in cluster-wide mode) +const session = documentStore.openSession(); + +const blockedUser = await session.advanced + .rawQuery(` + from 'Users' + where Email == cmpxchg('blocked-address') + `) + .firstOrNull(); + +// The results will include the user document whose email address is "someUser@company.com" +// (assuming such a user exists in your data). +``` + + +```sql +from "Users" +where Email == cmpxchg("blocked-address") +limit 0, 1 +``` + + +```js +class User { + constructor(id, name, email) { + this.Id = id; + this.Name = name; + this.Email = email; + } +} +``` + + + +#### Example 2 - Filtering when the compare-exchange value is an object + +* The following example shows how to filter documents based on a compare-exchange value that is an _object_. + We query for users whose email ends with one of the domains stored in the object held by the compare-exchange item. + +* Retrieve the compare-exchange value outside the query, then use its content to build the filter logic. + +* Since this query uses filtering, RavenDB will automatically create an auto-index to serve it. + In this case, the auto-index `Auto/Users/ByEmail` will be created. + + + +```js +// First, let's create a compare-exchange item with a value that is an object. +// The object contains multiple blocked email domains for this example. +const blockedDomainsValue = { + Domains: ["suspicious-company-1.com", "suspicious-company-2.org"] +}; +const putResult = await documentStore.operations.send( + new PutCompareExchangeValueOperation("blocked-domains", blockedDomainsValue, 0) +); + +// Retrieve the compare-exchange value before running the query +const blockedDomainsResult = await documentStore.operations.send( + new GetCompareExchangeValueOperation("blocked-domains") +); +const blockedDomains = blockedDomainsResult.value; + +// Query the 'Users' collection +// Find users whose email address ends with one of the blocked domains. +// (The session does not need to be opened in cluster-wide mode) +const session = documentStore.openSession(); + +const blockedUsers = await session + .query({ collection: "Users" }) + .whereEndsWith("Email", blockedDomains.Domains[0]) + .orElse() + .whereEndsWith("Email", blockedDomains.Domains[1]) + .all(); + +// The results will include users whose email address domain ends with +// one of the values stored in the compare-exchange item, +// assuming such users exist in your data. +``` + + +```js +const blockedDomainsValue = { + Domains: ["suspicious-company-1.com", "suspicious-company-2.org"] +}; + +const putResult = await documentStore.operations.send( + new PutCompareExchangeValueOperation("blocked-domains", blockedDomainsValue, 0) +); + +const blockedDomainsResult = await documentStore.operations.send( + new GetCompareExchangeValueOperation("blocked-domains") +); +const blockedDomains = blockedDomainsResult.value; + +const session = documentStore.openSession(); + +const blockedUsers = await session.advanced + .rawQuery(` + from 'Users' + where endsWith(Email, 'suspicious-company-1.com') + or endsWith(Email, 'suspicious-company-2.org') + `) + .all(); +``` + + +```sql +from "Users" +where endsWith(Email, "suspicious-company-1.com") +or +endsWith(Email, "suspicious-company-2.org") +``` + + +```js +class User { + constructor(id, name, email) { + this.Id = id; + this.Name = name; + this.Email = email; + } +} +``` + + + +--- + +## Syntax + +### `cmpxchg()` +This function can be used to filter a query +or to project fields from a compare-exchange value into the query results. + + +```js +// Get a compare-exchange value by key. +// Used inside 'where', 'selectFields', or rawQuery. +cmpxchg(key) +``` + diff --git a/docs/compare-exchange/content/_cmpxchg-item-expiration-csharp.mdx b/docs/compare-exchange/content/_cmpxchg-item-expiration-csharp.mdx new file mode 100644 index 0000000000..8236c3b6ab --- /dev/null +++ b/docs/compare-exchange/content/_cmpxchg-item-expiration-csharp.mdx @@ -0,0 +1,140 @@ +import Admonition from '@theme/Admonition'; +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +import CodeBlock from '@theme/CodeBlock'; + + + +* Compare-exchange items can be set to be deleted automatically in a future time. + +* **To schedule expiration for a compare-exchange item**, set the `@expires` field in the item's **metadata**. + This can be done when creating a new item or updating an existing one. + +* RavenDB scans the database periodically to remove expired items. + Any compare-exchange item whose `@expires` timestamp has passed at the time of the scan will be automatically removed. + +* The **scan frequency** is configurable - + see the [Cluster.CompareExchangeExpiredDeleteFrequencyInSec](../compare-exchange/configuration#clustercompareexchangeexpireddeletefrequencyinsec) configuration key. + The default is 60 seconds. + +* To manually remove a compare-exchange item, see [Delete compare-exchange items](../compare-exchange/delete-cmpxchg-items). + +* Note: The compare-exchange expiration feature is not related to [document expiration](../server/extensions/expiration). + You do NOT need to enable document expiration in order to use compare-exchange expiration. + +--- + +* In this article: + * [Add expiration date using the **Client API**](../compare-exchange/cmpxchg-expiration#add-expiration-date-using-the-client-api) + * [Add expiration date using the **Studio**](../compare-exchange/cmpxchg-expiration#add-expiration-date-using-the-studio) + * [Syntax](../compare-exchange/cmpxchg-expiration#syntax) + + + +--- + +## Add expiration date using the Client API + + + +```csharp +// The session must be opened in cluster-wide mode +using (var session = store.OpenSession( + new SessionOptions { TransactionMode = TransactionMode.ClusterWide })) +{ + // Call 'CreateCompareExchangeValue', specify the item's KEY and VALUE + CompareExchangeValue item = + session.Advanced.ClusterTransaction.CreateCompareExchangeValue( + key: "user1-name@example.com", + value: "users/1" + ); + + // Add METADATA fields to the item + // Set a future UTC DateTime in the `@expires` field to schedule expiration + // "Constants.Documents.Metadata.Expires" = "@expires" + item.Metadata[Constants.Documents.Metadata.Expires] = DateTime.UtcNow.AddDays(7); + + // The item will be created on the server once 'SaveChanges' is called + session.SaveChanges(); +} +``` + + +```csharp +// The session must be opened in cluster-wide mode +using (var asyncSession = store.OpenAsyncSession( + new SessionOptions { TransactionMode = TransactionMode.ClusterWide })) +{ + // Call 'CreateCompareExchangeValue', specify the item's KEY and VALUE + CompareExchangeValue item = + asyncSession.Advanced.ClusterTransaction.CreateCompareExchangeValue( + key: "user1-name@example.com", + value: "users/1" + ); + + // Add METADATA fields to the item + // Set a future UTC DateTime in the `@expires` field to schedule expiration + // "Constants.Documents.Metadata.Expires" = "@expires" + item.Metadata[Constants.Documents.Metadata.Expires] = DateTime.UtcNow.AddDays(7); + + // The item will be created on the server once 'SaveChangesAsync' is called + await asyncSession.SaveChangesAsync(); +} +``` + + +```csharp +// Define the metadata with the future expiration date +var metadata = new MetadataAsDictionary +{ + // Constants.Documents.Metadata.Expires = "@expires" + // Specify the expiration time (UTC) in ISO 8601 format + { Constants.Documents.Metadata.Expires, DateTime.UtcNow.AddDays(7).ToString("o") } +}; + +// Pass the metadata when creating the item +var putCmpXchgOp = new PutCompareExchangeValueOperation( + "user1-name@example.com", "users/1", 0, metadata); + +// Execute the operation +CompareExchangeResult putResult = store.Operations.Send(putCmpXchgOp); +``` + + +```csharp +// Define the metadata with the future expiration date +var metadata = new MetadataAsDictionary +{ + // Constants.Documents.Metadata.Expires = "@expires" + // Specify the expiration time (UTC) in ISO 8601 format + { Constants.Documents.Metadata.Expires, DateTime.UtcNow.AddDays(7).ToString("o") } +}; + +// Pass the metadata when creating the item +var putCmpXchgOp = new PutCompareExchangeValueOperation( + "user1-name@example.com", "users/1", 0, metadata); + +// Execute the operation +CompareExchangeResult putResult = await store.Operations.SendAsync(putCmpXchgOp); +``` + + + +--- + +## Add expiration date using the Studio + +* You can set or update the expiration date of a compare-exchange item directly from the Studio. + +* Go to **Documents > Compare Exchange**. + Edit an existing item or create a new one. + In the item's metadata, set the `@expires` field to a future UTC date/time (ISO 8601 format). + +![The compare-exchange view](../assets/set-expiration.png) + +--- + +## Syntax + +* The syntax for **creating** a compare-exchange item is available in [Create compare-exchange item - Syntax](../compare-exchange/create-cmpxchg-item#syntax) +* The syntax for **updating** a compare-exchange item is available in [Update compare-exchange item - Syntax](../compare-exchange/update-cmpxchg-item#syntax) diff --git a/docs/compare-exchange/content/_cmpxchg-item-expiration-nodejs.mdx b/docs/compare-exchange/content/_cmpxchg-item-expiration-nodejs.mdx new file mode 100644 index 0000000000..1265d951ae --- /dev/null +++ b/docs/compare-exchange/content/_cmpxchg-item-expiration-nodejs.mdx @@ -0,0 +1,98 @@ +import Admonition from '@theme/Admonition'; +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +import CodeBlock from '@theme/CodeBlock'; + + + +* Compare-exchange items can be set to be deleted automatically in a future time. + +* **To schedule expiration for a compare-exchange item**, set the `@expires` field in the item's **metadata**. + This can be done when creating a new item or updating an existing one. + +* RavenDB scans the database periodically to remove expired items. + Any compare-exchange item whose `@expires` timestamp has passed at the time of the scan will be automatically removed. + +* The **scan frequency** is configurable - + see the [Cluster.CompareExchangeExpiredDeleteFrequencyInSec](../compare-exchange/configuration#clustercompareexchangeexpireddeletefrequencyinsec) configuration key. + The default is 60 seconds. + +* To manually remove a compare-exchange item, see [Delete compare-exchange items](../compare-exchange/delete-cmpxchg-items). + +* Note: The compare-exchange expiration feature is not related to [document expiration](../server/extensions/expiration). + You do NOT need to enable document expiration in order to use compare-exchange expiration. + +--- + +* In this article: + * [Add expiration date using the **Client API**](../compare-exchange/cmpxchg-expiration#add-expiration-date-using-the-client-api) + * [Add expiration date using the **Studio**](../compare-exchange/cmpxchg-expiration#add-expiration-date-using-the-studio) + * [Syntax](../compare-exchange/cmpxchg-expiration#syntax) + + + +--- + +## Add expiration date using the Client API + + + +```js +// The session must be opened in cluster-wide mode +const session = documentStore.openSession({ + transactionMode: "ClusterWide" +}); + +// Call 'createCompareExchangeValue', specify the item's KEY and VALUE +const item = session.advanced.clusterTransaction.createCompareExchangeValue( + "user1-name@example.com", "users/1" // key, value +); + +// Add METADATA fields to the item +// Set a future UTC DateTime in the `@expires` field to schedule expiration +// "CONSTANTS.Documents.Metadata.EXPIRES" = "@expires" +const expireAt = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString(); // expire in 7 days +item.metadata[CONSTANTS.Documents.Metadata.EXPIRES] = expireAt; + +// The item will be created on the server once 'SaveChanges' is called +await session.saveChanges(); +``` + + +```js +// Define the metadata with the future expiration date +const expireAt = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString(); // expire in 7 days + +const metadata = { + // CONSTANTS.Documents.Metadata.EXPIRES = "@expires" + [CONSTANTS.Documents.Metadata.EXPIRES]: expireAt +}; + +// Pass the metadata when creating the item +const putCmpXchgOp = new PutCompareExchangeValueOperation( + "user1-name@example.com", "users/1", 0, metadata); + +// Execute the operation +const result = await documentStore.operations.send(putCmpXchgOp); +``` + + + +--- + +## Add expiration date using the Studio + +* You can set or update the expiration date of a compare-exchange item directly from the Studio. + +* Go to **Documents > Compare Exchange**. + Edit an existing item or create a new one. + In the item's metadata, set the `@expires` field to a future UTC date/time (ISO 8601 format). + +![The compare-exchange view](../assets/set-expiration.png) + +--- + +## Syntax + +* The syntax for **creating** a compare-exchange item is available in [Create compare-exchange item - Syntax](../compare-exchange/create-cmpxchg-item#syntax) +* The syntax for **updating** a compare-exchange item is available in [Update compare-exchange item - Syntax](../compare-exchange/update-cmpxchg-item#syntax) diff --git a/docs/compare-exchange/content/_create-cmpxchg-items-csharp.mdx b/docs/compare-exchange/content/_create-cmpxchg-items-csharp.mdx new file mode 100644 index 0000000000..f54ee96665 --- /dev/null +++ b/docs/compare-exchange/content/_create-cmpxchg-items-csharp.mdx @@ -0,0 +1,571 @@ +import Admonition from '@theme/Admonition'; +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +import CodeBlock from '@theme/CodeBlock'; + + + +* A new compare-exchange item can be created in the following ways: + * Using a cluster-wide session + * Using a store operation + * Using the Studio + +* To create a new compare-exchange item, you must provide: + * **A unique key** (string, up to 512 bytes) + * **An associated value** (number, string, array, or any valid JSON object) + * You can optionally add **metadata** (a valid JSON object) to store extra information with the item. + A common use case is to set an expiration time for the compare-exchange item in its metadata. + Learn more in [Set expiration for compare-exchange items](../compare-exchange/cmpxchg-expiration). + +* To modify an existing compare-exchange item, see: [Update compare-exchange item](../compare-exchange/update-cmpxchg-item). + +* In this article: + * [Create item using a **cluster-wide session**](../compare-exchange/create-cmpxchg-items#create-item-using-a-cluster-wide-session) + * [Ex.1 - Create compare-exchange item - with string value](../compare-exchange/create-cmpxchg-items#ex1---create-compare-exchange-item---with-string-value) + * [Ex.2 - Create compare-exchange item - with custom object value](../compare-exchange/create-cmpxchg-items#ex2---create-compare-exchange-item---with-custom-object-value) + * [Ex.3 - Create compare-exchange item - with metadata](../compare-exchange/create-cmpxchg-items#ex3---create-compare-exchange-item---with-metadata) + * [Ex.4 - Create multiple items](../compare-exchange/create-cmpxchg-items#ex4---create-multiple-items) + * [Create item using a **store operation**](../compare-exchange/create-cmpxchg-items#create-item-using-a-store-operation) + * [Ex.5 - Create compare-exchange item - with string value](../compare-exchange/create-cmpxchg-items#ex5---create-compare-exchange-item---with-string-value) + * [Ex.6 - Create compare-exchange item - with custom object value](../compare-exchange/create-cmpxchg-items#ex6---create-compare-exchange-item---with-custom-object-value) + * [Ex.7 - Create compare-exchange item - with metadata](../compare-exchange/create-cmpxchg-items#ex7---create-compare-exchange-item---with-metadata) + * [Create item using the **Studio**](../compare-exchange/create-cmpxchg-items#create-item-using-the-studio) + * [Syntax](../compare-exchange/create-cmpxchg-items#syntax) + + + + +--- + +## Create item using a cluster-wide session + +* Create compare-exchange items using a cluster-wide session when you want the creation to be part of a transaction committed via `SaveChanges()`. + This is suitable if you want to include compare-exchange item creation and document changes in the same transaction. + Learn more about cluster-wide sessions in [Cluster transactions - overview](../client-api/session/cluster-transaction/overview). + +* Use `CreateCompareExchangeValue()` to register the creation of a new compare-exchange item in the session. + The item will be created as part of the cluster-wide transaction when _SaveChanges()_ is called. + +* Exceptions: + An `InvalidOperationException` is thrown if the session is not opened in cluster-wide mode. + If the key already exists, _SaveChanges()_ will throw a `ClusterTransactionConcurrencyException`. + +* Examples: + + #### Ex.1 - Create compare-exchange item - with string value + + + + ```csharp + // The session must be opened in cluster-wide mode + using (var session = store.OpenSession( + new SessionOptions { TransactionMode = TransactionMode.ClusterWide })) + { + // Call 'CreateCompareExchangeValue' to register the creation of a new compare-exchange item + // as part of the cluster-wide transaction. Specify the item's KEY and VALUE. + // The item will be created only when 'SaveChanges' is called. + CompareExchangeValue item = + session.Advanced.ClusterTransaction.CreateCompareExchangeValue( + key: "user1-name@example.com", + value: "users/1" + ); + + // Commit the cluster-wide transaction. + // This will create the compare-exchange item, + // or throw a 'ClusterTransactionConcurrencyException' if the key already exists. + session.SaveChanges(); + } + ``` + + + ```csharp + // The session must be opened in cluster-wide mode + using (var asyncSession = store.OpenAsyncSession( + new SessionOptions { TransactionMode = TransactionMode.ClusterWide })) + { + // Call 'CreateCompareExchangeValue' to register the creation of a new compare-exchange item + // as part of the cluster-wide transaction. Specify the item's KEY and VALUE. + // The item will be created only when 'SaveChanges' is called. + CompareExchangeValue item = + asyncSession.Advanced.ClusterTransaction.CreateCompareExchangeValue( + key: "user1-name@example.com", + value: "users/1" + ); + + // Commit the cluster-wide transaction. + // This will create the compare-exchange item, + // or throw a 'ClusterTransactionConcurrencyException' if the key already exists. + await asyncSession.SaveChangesAsync(); + } + ``` + + + + #### Ex.2 - Create compare-exchange item - with custom object value + + + + ```csharp + // Define the object to be stored as the value + var user1Info = new UserInfo() + { + UserDocumentId = "users/1", + AdditionalInfo = "someInfo.." + }; + + // The session must be opened in cluster-wide mode + using (var session = store.OpenSession( + new SessionOptions { TransactionMode = TransactionMode.ClusterWide })) + { + // Call 'CreateCompareExchangeValue' to register the creation of a new compare-exchange item + // as part of the cluster-wide transaction. Specify the item's KEY and VALUE. + // The item will be created only when 'SaveChanges' is called. + CompareExchangeValue item = + session.Advanced.ClusterTransaction.CreateCompareExchangeValue( + key: "user1-name@example.com", + value: user1Info // Pass the object instance + ); + + // Commit the cluster-wide transaction. + // This will create the compare-exchange item, + // or throw a 'ClusterTransactionConcurrencyException' if the key already exists. + session.SaveChanges(); + } + ``` + + + ```csharp + // Define the object to be stored as the value + var user1Info = new UserInfo() + { + UserDocumentId = "users/1", + AdditionalInfo = "someInfo.." + }; + + // The session must be opened in cluster-wide mode + using (var asyncSession = store.OpenAsyncSession( + new SessionOptions { TransactionMode = TransactionMode.ClusterWide })) + { + // Call 'CreateCompareExchangeValue' to register the creation of a new compare-exchange item + // as part of the cluster-wide transaction. Specify the item's KEY and VALUE. + // The item will be created only when 'SaveChanges' is called. + CompareExchangeValue item = + asyncSession.Advanced.ClusterTransaction.CreateCompareExchangeValue( + key: "user1-name@example.com", + value: user1Info // Pass the object instance + ); + + // Commit the cluster-wide transaction. + // This will create the compare-exchange item, + // or throw a 'ClusterTransactionConcurrencyException' if the key already exists. + await asyncSession.SaveChangesAsync(); + } + ``` + + + + #### Ex.3 - Create compare-exchange item - with metadata + + + + ```csharp + // The session must be opened in cluster-wide mode + using (var session = store.OpenSession( + new SessionOptions { TransactionMode = TransactionMode.ClusterWide })) + { + // Call 'CreateCompareExchangeValue', specify the item's KEY and VALUE: + CompareExchangeValue item = + session.Advanced.ClusterTransaction.CreateCompareExchangeValue( + key: "user1-name@example.com", + value: "users/1" + ); + + // Add METADATA fields to the item: + item.Metadata["field-name"] = "some value"; + item.Metadata["email-type"] = "work email"; // e.g. describe the email type + + // Commit the cluster-wide transaction. + // This will create the compare-exchange item, + // or throw a 'ClusterTransactionConcurrencyException' if the key already exists. + session.SaveChanges(); + } + ``` + + + ```csharp + // The session must be opened in cluster-wide mode + using (var asyncSession = store.OpenAsyncSession( + new SessionOptions { TransactionMode = TransactionMode.ClusterWide })) + { + // Call 'CreateCompareExchangeValue', specify the item's KEY and VALUE: + CompareExchangeValue item = + asyncSession.Advanced.ClusterTransaction.CreateCompareExchangeValue( + key: "user1-name@example.com", + value: "users/1" + ); + + // Add METADATA fields to the item: + item.Metadata["field-name"] = "some value"; + item.Metadata["email-type"] = "work email"; // e.g. describe the email type + + // Commit the cluster-wide transaction. + // This will create the compare-exchange item, + // or throw a 'ClusterTransactionConcurrencyException' if the key already exists. + await asyncSession.SaveChangesAsync(); + } + ``` + + + + #### Ex.4 - Create multiple items + + You can create multiple compare-exchange items in the same cluster-wide transaction. + + + + ```csharp + // The session must be opened in cluster-wide mode + using (var session = store.OpenSession( + new SessionOptions { TransactionMode = TransactionMode.ClusterWide })) + { + // You can create multiple compare-exchange items before calling 'SaveChanges'. + // Call 'CreateCompareExchangeValue' for each item you want to create in the transaction. + session.Advanced.ClusterTransaction.CreateCompareExchangeValue( + key: "user7-name@example.com", value: "users/7" + ); + + session.Advanced.ClusterTransaction.CreateCompareExchangeValue( + key: "user8-name@example.com", value: "users/8" + ); + + session.Advanced.ClusterTransaction.CreateCompareExchangeValue( + key: "user9-name@example.com", value: "users/9" + ); + + // All three items will be created atomically as part of the same transaction. + // If any creation fails (e.g., due to an existing key), the entire transaction is rolled back + // and none of the new items will be created. + session.SaveChanges(); + } + ``` + + + ```csharp + // The session must be opened in cluster-wide mode + using (var asyncSession = store.OpenAsyncSession( + new SessionOptions { TransactionMode = TransactionMode.ClusterWide })) + { + // You can create multiple compare-exchange items before calling 'SaveChanges'. + // Call 'CreateCompareExchangeValue' for each item you want to create in the transaction. + asyncSession.Advanced.ClusterTransaction.CreateCompareExchangeValue( + key: "user7-name@example.com", value: "users/7" + ); + + asyncSession.Advanced.ClusterTransaction.CreateCompareExchangeValue( + key: "user8-name@example.com", value: "users/8" + ); + + asyncSession.Advanced.ClusterTransaction.CreateCompareExchangeValue( + key: "user9-name@example.com", value: "users/9" + ); + + // All three items will be created atomically as part of the same transaction. + // If any creation fails (e.g., due to an existing key), the entire transaction is rolled back + // and none of the new items will be created. + await asyncSession.SaveChangesAsync(); + } + ``` + + + +--- + +## Create item using a store operation + +* Use the `PutCompareExchangeValueOperation` [store operation](../client-api/operations/what-are-operations) to create a compare-exchange item independently, + without opening a session. + This is ideal for stand-alone tasks that don't require batching multiple commands into a single transactional session. + +* Note: + _PutCompareExchangeValueOperation_ is used for both **creating** and [modifying](../compare-exchange/update-cmpxchg-item) compare-exchange items. + The intent of the operation is determined by the index you pass: + * An index of `0` indicates a **create** operation. + * A non-zero index indicates a **modify** operation. + +* Creating a new compare-exchange item will succeed only if: + * The passed index is 0, and + * The specified key does not already exist in the database. + +* The operation will return a failed result (no exception is thrown) in the following cases: + * A new key is provided, but the index is not 0. + * The key already exists, even if the passed index is 0. + +* Examples: + + #### Ex.5 - Create compare-exchange item - with string value + + + + ```csharp + // Create a new compare-exchange item: + // =================================== + + // Define the put compare-exchange operation. Pass: + // * KEY: a new unique string identifier (e.g. a user's email) + // * VALUE: an associated value (e.g. the user's document ID) + // * INDEX: pass '0' to indicate that this is a new compare-exchange item + var putCmpXchgOp = new PutCompareExchangeValueOperation( + "user1-name@example.com", "users/1", 0); + + // Execute the operation by passing it to Operations.Send + CompareExchangeResult putResult = store.Operations.Send(putCmpXchgOp); + + // Check results + bool successful = putResult.Successful; // Has operation succeeded + long indexForItem = putResult.Index; // The version number assigned to the new item + + // If 'successful' is true, then a new compare-exchange item has been created + // with the unique email key and the associated value. + ``` + + + ```csharp + // Create a new compare-exchange item: + // =================================== + + // Define the put compare-exchange operation. Pass: + // * KEY: a new unique string identifier (e.g. a user's email) + // * VALUE: an associated value (e.g. the user's document ID) + // * INDEX: pass '0' to indicate that this is a new compare-exchange item + var putCmpXchgOp = new PutCompareExchangeValueOperation( + "user1-name@example.com", "users/1", 0); + + // Execute the operation by passing it to Operations.SendAsync + CompareExchangeResult putResult = await store.Operations.SendAsync(putCmpXchgOp); + + // Check results + bool successful = putResult.Successful; // Has operation succeeded + long indexForItem = putResult.Index; // The version number assigned to the new item + + // If 'successful' is true, then a new compare-exchange item has been created + // with the unique email key and the associated value. + ``` + + + + #### Ex.6 - Create compare-exchange item - with custom object value + + + + ```csharp + // Create a new compare-exchange item: + // =================================== + + // Define the object to be stored as the value + var user1Info = new UserInfo() + { + UserDocumentId = "users/1", + AdditionalInfo = "someInfo.." + }; + + // Define the put compare-exchange operation. + // Pass the key, the object instance, and '0' to indicate that this is a new item. + // Specify the object type in the generic parameter. + var putCmpXchgOp = new PutCompareExchangeValueOperation( + "user1-name@example.com", user1Info, 0); + + // Execute the operation by passing it to Operations.Send + CompareExchangeResult putResult = store.Operations.Send(putCmpXchgOp); + + // Check results + bool successful = putResult.Successful; // Has operation succeeded + long indexForItem = putResult.Index; // The version number assigned to the new item + + // If 'successful' is true, then a new compare-exchange item has been created + // with the unique email key and the associated value. + ``` + + + ```csharp + // Create a new compare-exchange item: + // =================================== + + // Define the object to be stored as the value + var user1Info = new UserInfo() + { + UserDocumentId = "users/1", + AdditionalInfo = "someInfo.." + }; + + // Define the put compare-exchange operation. + // Pass the key, the object instance, and '0' to indicate that this is a new item. + // Specify the object type in the generic parameter. + var putCmpXchgOp = new PutCompareExchangeValueOperation( + "user1-name@example.com", user1Info, 0); + + // Execute the operation by passing it to Operations.SendAsync + CompareExchangeResult putResult = await store.Operations.SendAsync(putCmpXchgOp); + + // Check results + bool successful = putResult.Successful; // Has operation succeeded + long indexForItem = putResult.Index; // The version number assigned to the new item + + // If 'successful' is true, then a new compare-exchange item has been created + // with the unique email key and the associated value. + ``` + + + + #### Ex.7 - Create compare-exchange item - with metadata + + + + ```csharp + // Create a new compare-exchange item with metadata: + // ================================================= + + // Define the metadata - must be a valid JSON object + var metadata = new MetadataAsDictionary + { + { "email-type", "work email" } + }; + + // Define the put compare-exchange operation. + // Pass a 4'th parameter with the metadata object. + var putCmpXchgOp = new PutCompareExchangeValueOperation( + "user1-name@example.com", "users/1", 0, metadata); + + // Execute the operation by passing it to Operations.Send + CompareExchangeResult putResult = store.Operations.Send(putCmpXchgOp); + + // Check results + bool successful = putResult.Successful; // Has operation succeeded + long indexForItem = putResult.Index; // The version number assigned to the new item + + // If successful is true then a new compare-exchange item has been created + // with the unique key, value, and metadata. + ``` + + + ```csharp + // Create a new compare-exchange item with metadata: + // ================================================= + + // Define the metadata - must be a valid JSON object + var metadata = new MetadataAsDictionary + { + { "email-type", "work email" } + }; + + // Define the put compare-exchange operation. + // Pass a 4'th parameter with the metadata object. + var putCmpXchgOp = new PutCompareExchangeValueOperation( + "user1-name@example.com", "users/1", 0, metadata); + + // Execute the operation by passing it to Operations.SendAsync + CompareExchangeResult putResult = await store.Operations.SendAsync(putCmpXchgOp); + + // Check results + bool successful = putResult.Successful; // Has operation succeeded + long indexForItem = putResult.Index; // The version number assigned to the new item + + // If successful is true then a new compare-exchange item has been created + // with the unique key, value, and metadata. + ``` + + + +--- + +## Create item using the Studio + +To create compare-exchange items using the Studio, go to **Documents > Compare Exchange**. + +![The compare-exchange view](../assets/create-new-cmpxchg-1.png) + +![The compare-exchange view](../assets/create-new-cmpxchg-2.png) + +1. **Key** + Enter a unique identifier for the compare-exchange item (up to 512 bytes). + This key must be unique across the entire database. +2. **Value** + Enter the value to associate with the key. + Can be a number, string, array, or any valid JSON object. +3. **Metadata** (optional) + Add any additional data you want to store with the item. + Must be a valid JSON object. + Can be used to [set expiration time](../todo..) for the compare-exchange item. +4. **Save** + Click to create the compare-exchange item. + If the key already exists, an error message will be shown. + +--- + +## Syntax + +--- + +### `PutCompareExchangeValueOperation` +Create compare-exchange item using a store operation: + + +```csharp +public PutCompareExchangeValueOperation( + string key, T value, long index, IMetadataDictionary metadata = null) +``` + + +| Parameter | Type | Description | +|--------------|-----------------------|-------------| +| **key** | `string` |
  • A unique identifier in the database scope.
  • Can be up to 512 bytes.
| +| **value** | `T` |
  • A value to be saved for the specified _key_.
  • Can be any value (number, string, array, or any valid JSON object).
| +| **index** | `long` |
  • Pass `0` to create a new key.
  • When updating an existing key, pass the current number for concurrency control.
| +| **metadata** | `IMetadataDictionary` |
  • Optional metadata to be saved for the specified _key_.
  • Must be a valid JSON object.
| + + +**Returned object**: + + +```csharp +public class CompareExchangeResult +{ + public bool Successful; + public T Value; + public long Index; +} +``` + + +| Return Value | Type | Description | +|---------------|--------|-------------| +| **Successful**| `bool` |
  • `true` if the put operation has completed successfully.
  • `false` if the put operation has failed.
| +| **Value** | `T` |
  • Upon success - the value of the compare-exchange item that was saved.
  • Upon failure - the existing value on the server.
| +| **Index** | `long` |
  • The compare-exchange item's version.
  • This number increases with each successful modification of the `value` or `metadata`.
  • Upon success - the updated version of the compare-exchange item that was saved.
  • Upon failure - the existing version number on the server.
| + +--- + +### `CreateCompareExchangeValue` +Create compare-exchange item using cluster-wide session: + + +```csharp +session.Advanced.ClusterTransaction.CreateCompareExchangeValue(key, value); +``` + + +| Parameter | Type | Description | +|------------|----------|--------------------------------------------------------------------| +| **key** | `string` | The compare-exchange item key. This string can be up to 512 bytes. | +| **value** | `T` | The associated value to store for the key.
Can be a number, string, array, or any valid JSON object. | + +| `CreateCompareExchangeValue` returns: | Description | +|---------------------------|---------------------------------------------------------------------------------------------------------------------| +| `CompareExchangeValue` | The compare-exchange item that is added to the transaction.
It will be created when `SaveChanges()` is called. | + +The returned `CompareExchangeValue` contains: + +| Property | Type | Description | +|------------|----------|--------------------------------------------------------------------| +| **key** | `string` | The compare-exchange item key. This string can be up to 512 bytes. | +| **value** | `T` | The value associated with the key. | +| **index** | `long` | The index used for concurrency control.
Will be `0` when calling `CreateCompareExchangeValue`. | diff --git a/docs/compare-exchange/content/_create-cmpxchg-items-java.mdx b/docs/compare-exchange/content/_create-cmpxchg-items-java.mdx new file mode 100644 index 0000000000..a75225bf1c --- /dev/null +++ b/docs/compare-exchange/content/_create-cmpxchg-items-java.mdx @@ -0,0 +1,153 @@ +import Admonition from '@theme/Admonition'; +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +import CodeBlock from '@theme/CodeBlock'; + + + +* A new compare-exchange item can be created in the following ways: + * Using a cluster-wide session + * Using a store operation + * Using the Studio + +* To create a new compare-exchange item, you must provide: + * **A unique key** (string, up to 512 bytes) + * **An associated value** (number, string, array, or any valid JSON object) + * You can optionally add **metadata** (a valid JSON object) to store extra information with the item. + A common use case is to set an expiration time for the compare-exchange item in its metadata. + Learn more in [Set expiration for compare-exchange items](../compare-exchange/cmpxchg-expiration). + +* To modify an existing compare-exchange item, see: [Update compare-exchange item](../compare-exchange/update-cmpxchg-item). + +* In this article: + * [Create item using a **store operation**](../compare-exchange/create-cmpxchg-items#create-item-using-a-store-operation) + * [Create item using the **Studio**](../compare-exchange/create-cmpxchg-items#create-item-using-the-studio) + * [Syntax](../compare-exchange/create-cmpxchg-items#syntax) + + + +--- + +## Create item using a store operation + +* Use the `PutCompareExchangeValueOperation` [store operation](../client-api/operations/what-are-operations) to create a compare-exchange item independently, + without opening a session. + This is ideal for stand-alone tasks that don't require batching multiple commands into a single transactional session. + +* Note: + _PutCompareExchangeValueOperation_ is used for both **creating** and [modifying](../compare-exchange/update-cmpxchg-item) compare-exchange items. + The intent of the operation is determined by the index you pass: + * An index of `0` indicates a **create** operation. + * A non-zero index indicates a **modify** operation. + +* Creating a new compare-exchange item will succeed only if: + * The passed index is 0, and + * The specified key does not already exist in the database. + +* The operation will return a failed result (no exception is thrown) in the following cases: + * A new key is provided, but the index is not 0. + * The key already exists, even if the passed index is 0. + +--- + +#### Example + + + +{`CompareExchangeResult compareExchangeResult = store.operations().send( + new PutCompareExchangeValueOperation<>("user1-name@example.com", "users/1", 0)); + +boolean successful = compareExchangeResult.isSuccessful(); +// If successful is true: then Key 'user1-name@example.com' now has the value of "users/1" +`} + + + +--- + +## Create item using the Studio + +To create compare-exchange items using the Studio, go to **Documents > Compare Exchange**. + +![The compare-exchange view](../assets/create-new-cmpxchg-1.png) + +![The compare-exchange view](../assets/create-new-cmpxchg-2.png) + +1. **Key** + Enter a unique identifier for the compare-exchange item (up to 512 bytes). + This key must be unique across the entire database. +2. **Value** + Enter the value to associate with the key. + Can be a number, string, array, or any valid JSON object. +3. **Metadata** (optional) + Add any additional data you want to store with the item. + Must be a valid JSON object. + Can be used to [set expiration time](../todo..) for the compare-exchange item. +4. **Save** + Click to create the compare-exchange item. + If the key already exists, an error message will be shown. + +--- + +## Syntax + +--- + +### `PutCompareExchangeValueOperation` +Create compare-exchange item using a store operation: + + + +{`public PutCompareExchangeValueOperation(String key, T value, long index) +`} + + + +| Parameter | Type | Description | +|-----------|--------|-------------| +| **key** | String | Object identifier under which _value_ is saved, unique in the database scope across the cluster. This string can be up to 512 bytes. | +| **value** | `T` | The value to be saved for the specified _key_. | +| **index** | long | * `0` if creating a new key
* The current version of _value_ when updating a value for an existing key. | + +**Returned object**: + + + +{`public class CompareExchangeResult \{ + private T value; + private long index; + private boolean successful; + + public T getValue() \{ + return value; + \} + + public void setValue(T value) \{ + this.value = value; + \} + + public long getIndex() \{ + return index; + \} + + public void setIndex(long index) \{ + this.index = index; + \} + + public boolean isSuccessful() \{ + return successful; + \} + + public void setSuccessful(boolean successful) \{ + this.successful = successful; + \} +\} +`} + + + +| Return Value | Type | Description | +|----------------|---------|-----------------------------------------------------------------------------| +| **Successful** | boolean | * `true` if the save operation has completed successfully
* `false` if the save operation failed | +| **Value** | `T` | * The value that was saved if the operation was successful
* The currently existing value in the server upon failure | +| **Index** | long | * The version number of the value that was saved upon success
* The currently existing version number in the server upon failure | diff --git a/docs/compare-exchange/content/_create-cmpxchg-items-nodejs.mdx b/docs/compare-exchange/content/_create-cmpxchg-items-nodejs.mdx new file mode 100644 index 0000000000..52010ece5f --- /dev/null +++ b/docs/compare-exchange/content/_create-cmpxchg-items-nodejs.mdx @@ -0,0 +1,315 @@ +import Admonition from '@theme/Admonition'; +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +import CodeBlock from '@theme/CodeBlock'; + + + +* A new compare-exchange item can be created in the following ways: + * Using a cluster-wide session + * Using a store operation + * Using the Studio + +* To create a new compare-exchange item, you must provide: + * **A unique key** (string, up to 512 bytes) + * **An associated value** (number, string, array, or any valid JSON object) + * You can optionally add **metadata** (a valid JSON object) to store extra information with the item. + A common use case is to set an expiration time for the compare-exchange item in its metadata. + Learn more in [Set expiration for compare-exchange items](../compare-exchange/cmpxchg-expiration). + +* To modify an existing compare-exchange item, see: [Update compare-exchange items](../compare-exchange/update-cmpxchg-item). + +* In this article: + * [Create item using a **cluster-wide session**](../compare-exchange/create-cmpxchg-items#create-item-using-a-cluster-wide-session) + * [Ex.1 - Create new compare-exchange item](../compare-exchange/create-cmpxchg-items#ex1---create-new-compare-exchange-item) + * [Ex.2 - Create new compare-exchange item with metadata](../compare-exchange/create-cmpxchg-items#ex2---create-new-compare-exchange-item-with-metadata) + * [Ex.3 - Create multiple items](../compare-exchange/create-cmpxchg-items#ex3---create-multiple-items) + * [Create item using a **store operation**](../compare-exchange/create-cmpxchg-items#create-item-using-a-store-operation) + * [Ex.4 - Create new compare-exchange item](../compare-exchange/create-cmpxchg-items#ex4---create-new-compare-exchange-item) + * [Ex.5 - Create new compare-exchange item with metadata](../compare-exchange/create-cmpxchg-items#ex5---create-new-compare-exchange-item-with-metadata) + * [Create item using the **Studio**](../compare-exchange/create-cmpxchg-items#create-item-using-the-studio) + * [Syntax](../compare-exchange/create-cmpxchg-items#syntax) + + + +--- + +## Create item using a cluster-wide session + +* Create compare-exchange items using a cluster-wide session when you want the creation to be part of a transaction committed via `saveChanges()`. + This is suitable if you want to include compare-exchange item creation and document changes in the same transaction. + Learn more about cluster-wide sessions in [Cluster transactions - overview](../client-api/session/cluster-transaction/overview). + +* Use `createCompareExchangeValue()` to register the creation of a new compare-exchange item in the session. + The item will be created as part of the cluster-wide transaction when _saveChanges()_ is called. + +* Exceptions: + An `InvalidOperationException` is thrown if the session is not opened in cluster-wide mode. + If the key already exists, _saveChanges()_ will throw a `ClusterTransactionConcurrencyException`. + +* Examples: + + #### Ex.1 - Create new compare-exchange item + + + + ```js + // The session must be opened in cluster-wide mode + const session = documentStore.openSession({ + transactionMode: "ClusterWide" + }); + + // Call 'createCompareExchangeValue' to register the creation of a new compare-exchange item + // as part of the cluster-wide transaction. Specify the item's KEY and VALUE. + // The item will be created only when 'saveChanges' is called. + const item = session.advanced.clusterTransaction.createCompareExchangeValue( + "user1-name@example.com", "users/1" // key, value + ); + + // Commit the cluster-wide transaction. + // This will create the compare-exchange item, + // or throw a 'ClusterTransactionConcurrencyException' if the key already exists. + await session.saveChanges(); + ``` + + + + #### Ex.2 - Create new compare-exchange item with metadata + + + + ```js + // The session must be opened in cluster-wide mode + const session = documentStore.openSession({ + transactionMode: "ClusterWide" + }); + + // Call 'createCompareExchangeValue' to register the creation of a new compare-exchange item + // as part of the cluster-wide transaction. Specify the item's KEY and VALUE. + // The item will be created only when 'saveChanges' is called. + const item = session.advanced.clusterTransaction.createCompareExchangeValue( + "user1-name@example.com", "users/1" // key, value + ); + + // Add METADATA fields to the item: + item.metadata["field-name"] = "some value"; + item.metadata["email-type"] = "work email"; // e.g. describe the email type + + // Commit the cluster-wide transaction. + // This will create the compare-exchange item, + // or throw a 'ClusterTransactionConcurrencyException' if the key already exists. + await session.saveChanges(); + ``` + + + + #### Ex.3 - Create multiple items + + You can create multiple compare-exchange items in the same cluster-wide transaction. + + + + ```js + // The session must be opened in cluster-wide mode + const session = documentStore.openSession({ + transactionMode: "ClusterWide" + }); + + // You can create multiple compare-exchange items before calling 'saveChanges'. + // Call 'createCompareExchangeValue' for each item you want to create in the transaction. + session.advanced.clusterTransaction.createCompareExchangeValue( + "user7-name@example.com", "users/7" + ); + + session.advanced.clusterTransaction.createCompareExchangeValue( + "user8-name@example.com", "users/8" + ); + + session.advanced.clusterTransaction.createCompareExchangeValue( + "user9-name@example.com", "users/9" + ); + + // All three items will be created atomically as part of the same transaction. + // If any creation fails (e.g., due to an existing key), the entire transaction is rolled back + // and none of the new items will be created. + await session.saveChanges(); + ``` + + + +--- + +## Create item using a store operation + +* Use the `PutCompareExchangeValueOperation` [store operation](../client-api/operations/what-are-operations) to create a compare-exchange item independently, + without opening a session. + This is ideal for stand-alone tasks that don't require batching multiple commands into a single transactional session. + +* Note: + _PutCompareExchangeValueOperation_ is used for both **creating** and [modifying](../compare-exchange/update-cmpxchg-item) compare-exchange items. + The intent of the operation is determined by the index you pass: + * An index of `0` indicates a **create** operation. + * A non-zero index indicates a **modify** operation. + +* Creating a new compare-exchange item will succeed only if: + * The passed index is 0, and + * The specified key does not already exist in the database. + +* The operation will return a failed result (no exception is thrown) in the following cases: + * A new key is provided, but the index is not 0. + * The key already exists, even if the passed index is 0. + +* Examples: + + #### Ex.4 - Create new compare-exchange item + + + + ```js + // Create a new compare-exchange item: + // =================================== + + // Define the put compare-exchange operation. Pass: + // * KEY: a new unique identifier (e.g. a user's email) + // * VALUE: an associated value (e.g. the user's document ID) + // * INDEX: pass '0' to indicate that this is a new key + const putCmpXchgOp = new PutCompareExchangeValueOperation("user1-name@example.com", "users/1", 0); + + // Execute the operation by passing it to operations.send + const result = await documentStore.operations.send(putCmpXchgOp); + + // Check results + const successful = result.successful; // Has operation succeeded + const indexForItem = result.index; // The version number assigned to the new item + + // If successful is true then a new compare-exchange item has been created + // with the unique email key and the associated value. + ``` + + + + #### Ex.5 - Create new compare-exchange item with metadata + + + + ```js + // Create a new compare-exchange item with metadata: + // ================================================= + + // Define the put compare-exchange operation. + // Pass a 4'th parameter with the metadata object. + const putCmpXchgOp = new PutCompareExchangeValueOperation("user1-name@example.com", "users/1", 0, + { + "email-type": "work email" + }); + + // Execute the operation by passing it to operations.send + const result = await documentStore.operations.send(putCmpXchgOp); + + // Check results + const successful = result.successful; // Has operation succeeded + const indexForItem = result.index; // The version number assigned to the new item + + // If successful is true then a new compare-exchange item has been created + // with the unique phone number key, value, and metadata. + ``` + + + +--- + +## Create item using the Studio + +To create compare-exchange items using the Studio, go to **Documents > Compare Exchange**. + +![The compare-exchange view](../assets/create-new-cmpxchg-1.png) + +![The compare-exchange view](../assets/create-new-cmpxchg-2.png) + +1. **Key** + Enter a unique identifier for the compare-exchange item (up to 512 bytes). + This key must be unique across the entire database. +2. **Value** + Enter the value to associate with the key. + Can be a number, string, array, or any valid JSON object. +3. **Metadata** (optional) + Add any additional data you want to store with the item. + Must be a valid JSON object. + Can be used to [set expiration time](../todo..) for the compare-exchange item. +4. **Save** + Click to create the compare-exchange item. + If the key already exists, an error message will be shown. + +--- + +## Syntax + +--- + +### `PutCompareExchangeValueOperation` +Create compare-exchange item using a store operation: + + +```js +// Available overloads: +// ==================== +const putCmpXchgOp = new PutCompareExchangeValueOperation(key, value, index); +const putCmpXchgOp = new PutCompareExchangeValueOperation(key, value, index, metadata); +``` + + +| Parameter | Type | Description | +|--------------|----------|-------------| +| **key** | `string` |
  • A unique identifier in the database scope.
  • Can be up to 512 bytes.
| +| **value** | `object` |
  • A value to be saved for the specified _key_.
  • Can be any value (number, string, array, or any valid JSON object).
| +| **index** | `number` |
  • Pass `0` to create a new key.
  • When updating an existing key, pass the current number for concurrency control.
| +| **metadata** | `object` |
  • Optional metadata to be saved for the specified _key_.
  • Must be a valid JSON object.
| + + +**Returned object**: + + +```js +// Return value of store.operations.send(putCmpXchgOp) +// =================================================== +class CompareExchangeResult { + successful; + value; + index; +} +``` + + +| Return Value | Type | Description | +|----------------|-----------|-------------| +| **successful** | `boolean` |
  • `true` if the put operation has completed successfully.
  • `false` if the put operation has failed.
| +| **value** | `object` |
  • Upon success - the value of the compare-exchange item that was saved.
  • Upon failure - the existing value on the server.
| +| **index** | `number` |
  • The compare-exchange item's version.
  • This number increases with each successful modification of the `value` or `metadata`.
  • Upon success - the updated version of the compare-exchange item that was saved.
  • Upon failure - the existing version number on the server.
| + +--- + +### `createCompareExchangeValue` +Create compare-exchange item using cluster-wide session: + + +```js +session.advanced.clusterTransaction.createCompareExchangeValue(key, item); +``` + + +| Parameter | Type | Description | +|------------|----------|--------------------------------------------------------------------| +| **key** | `string` | The compare-exchange item key. This string can be up to 512 bytes. | +| **value** | `object` | The associated value to store for the key.
Can be a number, string, array, or any valid JSON object. | + +| `createCompareExchangeValue` returns: | Description | +|---------------------------|----------------------------------------------------------------------------------------------------| +| `object` | The compare-exchange item that is added to the transaction.
It will be created when `saveChanges()` is called. | + +The returned object contains: + +| Property | Type | Description | +|------------|----------|--------------------------------------------------------------------| +| **key** | `string` | The compare-exchange item key. This string can be up to 512 bytes. | +| **value** | `object` | The value associated with the key. | +| **index** | `number` | The index used for concurrency control.
Will be `0` when calling `createCompareExchangeValue`. | diff --git a/docs/compare-exchange/content/_create-cmpxchg-items-php.mdx b/docs/compare-exchange/content/_create-cmpxchg-items-php.mdx new file mode 100644 index 0000000000..d32ece6a51 --- /dev/null +++ b/docs/compare-exchange/content/_create-cmpxchg-items-php.mdx @@ -0,0 +1,112 @@ +import Admonition from '@theme/Admonition'; +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +import CodeBlock from '@theme/CodeBlock'; + + + +* A new compare-exchange item can be created in the following ways: + * Using a cluster-wide session + * Using a store operation + * Using the Studio + +* To create a new compare-exchange item, you must provide: + * **A unique key** (string, up to 512 bytes) + * **An associated value** (number, string, array, or any valid JSON object) + * You can optionally add **metadata** (a valid JSON object) to store extra information with the item. + A common use case is to set an expiration time for the compare-exchange item in its metadata. + Learn more in [Set expiration for compare-exchange items](../compare-exchange/cmpxchg-expiration). + +* To modify an existing compare-exchange item, see: [Update compare-exchange item](../compare-exchange/update-cmpxchg-item). + +* In this article: + * [Create item using a **cluster-wide session**](../compare-exchange/create-cmpxchg-items#create-item-using-a-cluster-wide-session) + * [Create item using the **Studio**](../compare-exchange/create-cmpxchg-items#create-item-using-the-studio) + * [Syntax](../compare-exchange/create-cmpxchg-items#syntax) + + + +--- + +## Create item using a cluster-wide session + +* Create compare-exchange items using a cluster-wide session when you want the creation to be part of a transaction committed via `saveChanges()`. + This is suitable if you want to include compare-exchange item creation and document changes in the same transaction. + Learn more about cluster-wide sessions in [Cluster transactions - overview](../client-api/session/cluster-transaction/overview). + +* Use `createCompareExchangeValue()` to register the creation of a new compare-exchange item in the session. + The item will be created as part of the cluster-wide transaction when _saveChanges()_ is called. + +* Exceptions: + An `InvalidOperationException` is thrown if the session is not opened in cluster-wide mode. + If the key already exists, _saveChanges()_ will throw a `ConcurrencyException`. + +#### Example + + +```php +// The session must be first opened with cluster-wide mode +$session->advanced()->clusterTransaction()->createCompareExchangeValue( + key: "Best NoSQL Transactional Database", + value: "RavenDB" +); + +$session->saveChanges(); +``` + + +--- + +## Create item using the Studio + +To create compare-exchange items using the Studio, go to **Documents > Compare Exchange**. + +![The compare-exchange view](../assets/create-new-cmpxchg-1.png) + +![The compare-exchange view](../assets/create-new-cmpxchg-2.png) + +1. **Key** + Enter a unique identifier for the compare-exchange item (up to 512 bytes). + This key must be unique across the entire database. +2. **Value** + Enter the value to associate with the key. + Can be a number, string, array, or any valid JSON object. +3. **Metadata** (optional) + Add any additional data you want to store with the item. + Must be a valid JSON object. + Can be used to [set expiration time](../todo..) for the compare-exchange item. +4. **Save** + Click to create the compare-exchange item. + If the key already exists, an error message will be shown. + +--- + +## Syntax + +--- + +### `createCompareExchangeValue` +Create compare-exchange item using cluster-wide session: + + +```php +$session->advanced()->clusterTransaction()->createCompareExchangeValue($key, $value); +``` + + +| Parameter | Type | Description | +|------------|----------|--------------------------------------------------------------------| +| **key** | `string` | The compare-exchange item key. This string can be up to 512 bytes. | +| **value** | `T` | The associated value to store for the key.
Can be a number, string, array, or any valid JSON object. | + +| `create_compare_exchange_value` returns: | Description | +|---------------------------|----------------------------------------------------------------------------------------------------------------------| +| `CompareExchangeValue[T]` | The compare-exchange item that is added to the transaction.
It will be created when `save_changes()` is called. | + +The `CompareExchangeValue`: + +| Property | Type | Description | +|------------|----------|--------------------------------------------------------------------| +| **key** | `string` | The compare-exchange item key. This string can be up to 512 bytes. | +| **value** | `T` | The value associated with the key. | +| **index** | `int` | The index used for concurrency control.
Will be `0` when calling `createCompareExchangeValue`. | diff --git a/docs/compare-exchange/content/_create-cmpxchg-items-python.mdx b/docs/compare-exchange/content/_create-cmpxchg-items-python.mdx new file mode 100644 index 0000000000..6bbf563583 --- /dev/null +++ b/docs/compare-exchange/content/_create-cmpxchg-items-python.mdx @@ -0,0 +1,113 @@ +import Admonition from '@theme/Admonition'; +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +import CodeBlock from '@theme/CodeBlock'; + + + +* A new compare-exchange item can be created in the following ways: + * Using a cluster-wide session + * Using a store operation + * Using the Studio + +* To create a new compare-exchange item, you must provide: + * **A unique key** (string, up to 512 bytes) + * **An associated value** (number, string, array, or any valid JSON object) + * You can optionally add **metadata** (a valid JSON object) to store extra information with the item. + A common use case is to set an expiration time for the compare-exchange item in its metadata. + Learn more in [Set expiration for compare-exchange items](../compare-exchange/cmpxchg-expiration). + +* To modify an existing compare-exchange item, see: [Update compare-exchange item](../compare-exchange/update-cmpxchg-item). + +* In this article: + * [Create item using a **cluster-wide session**](../compare-exchange/create-cmpxchg-items#create-item-using-a-cluster-wide-session) + * [Create item using the **Studio**](../compare-exchange/create-cmpxchg-items#create-item-using-the-studio) + * [Syntax](../compare-exchange/create-cmpxchg-items#syntax) + + + +--- + +## Create item using a cluster-wide session + +* Create compare-exchange items using a cluster-wide session when you want the creation to be part of a transaction committed via `save_changes()`. + This is suitable if you want to include compare-exchange item creation and document changes in the same transaction. + Learn more about cluster-wide sessions in [Cluster transactions - overview](../client-api/session/cluster-transaction/overview). + +* Use `create_compare_exchange_value()` to register the creation of a new compare-exchange item in the session. + The item will be created as part of the cluster-wide transaction when _save_changes()_ is called. + +* Exceptions: + A `RuntimeError` is thrown when the session is Not opened in cluster-wide mode. + If the key already exists, save_changes()_ will throw a `ConcurrencyException`. + +#### Example + + +```python +# The session must be first opened with cluster-wide mode + +session.advanced.cluster_transaction.create_compare_exchange_value( + key="Best NoSQL Transactional Database", + item="RavenDB", +) + +session.save_changes(); +``` + + +--- + +## Create item using the Studio + +To create compare-exchange items using the Studio, go to **Documents > Compare Exchange**. + +![The compare-exchange view](../assets/create-new-cmpxchg-1.png) + +![The compare-exchange view](../assets/create-new-cmpxchg-2.png) + +1. **Key** + Enter a unique identifier for the compare-exchange item (up to 512 bytes). + This key must be unique across the entire database. +2. **Value** + Enter the value to associate with the key. + Can be a number, string, array, or any valid JSON object. +3. **Metadata** (optional) + Add any additional data you want to store with the item. + Must be a valid JSON object. + Can be used to [set expiration time](../todo..) for the compare-exchange item. +4. **Save** + Click to create the compare-exchange item. + If the key already exists, an error message will be shown. + +--- + +## Syntax + +--- + +### `create_compare_exchange_value` +Create compare-exchange item using cluster-wide session: + + +```python +session.advanced.cluster_transaction.create_compare_exchange_value(key, value) +``` + + +| Parameter | Type | Description | +|------------|-------|--------------------------------------------------------------------| +| **key** | `str` | The compare-exchange item key. This string can be up to 512 bytes. | +| **value** | `T` | The associated value to store for the key.
Can be a number, string, array, or any valid JSON object. | + +| `create_compare_exchange_value` returns: | Description | +|---------------------------|----------------------------------------------------------------------------------------------------------------------| +| `CompareExchangeValue[T]` | The compare-exchange item that is added to the transaction.
It will be created when `save_changes()` is called. | + +The `CompareExchangeValue`: + +| Property | Type | Description | +|------------|--------|--------------------------------------------------------------------| +| **key** | `str` | The compare-exchange item key. This string can be up to 512 bytes. | +| **value** | `T` | The value associated with the key. | +| **index** | `int` | The index used for concurrency control.
Will be `0` when calling `create_compare_exchange_value`. | diff --git a/docs/compare-exchange/content/_delete-cmpxchg-items-csharp.mdx b/docs/compare-exchange/content/_delete-cmpxchg-items-csharp.mdx new file mode 100644 index 0000000000..f699229740 --- /dev/null +++ b/docs/compare-exchange/content/_delete-cmpxchg-items-csharp.mdx @@ -0,0 +1,365 @@ +import Admonition from '@theme/Admonition'; +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +import CodeBlock from '@theme/CodeBlock'; + + + +* **Custom compare-exchange items can be deleted**: + You can delete your own custom compare-exchange items. + An item is deleted only if the index you provide in the request matches the current index stored on the server for the specified key. + +* **Delete items by expiration**: + Compare-exchange items can also be deleted by adding an expiration date to them. + Learn more in [Compare-exchange expiration](../compare-exchange/cmpxchg-expiration). + +* **Compare-exchange tombstones**: + Whenever a compare-exchange item is deleted, a compare-exchange tombstone is created for it. + These tombstones are used to indicate to other RavenDB processes that the compare-exchange item was deleted, + so they can react accordingly. + For example, indexes referencing the deleted item will update themselves to remove those references. + Compare-exchange tombstones that are eligible for deletion are removed periodically by an internal cleanup task. + See: [Cluster.CompareExchangeTombstonesCleanupIntervalInMin](../compare-exchange/configuration#clustercompareexchangetombstonescleanupintervalinmin). + +* + Do NOT attempt to delete [atomic guards](../compare-exchange/atomic-guards), which RavenDB uses internally to ensure ACID guarantees in cluster-wide transactions. + These compare-exchange items are created automatically and must not be modified or removed. + + If your custom compare-exchange item was set up to protect the consistency of a transaction, deleting it will break the ACID guarantees. + Only delete or modify such items if you truly know what you're doing. + + +--- + +* In this article: + * [Delete compare-exchange item using a **cluster-wide session**](../compare-exchange/delete-cmpxchg-items#delete-compare-exchange-item-using-a-cluster-wide-session) + * [Delete by item](../compare-exchange/delete-cmpxchg-items#delete-by-item) + * [Delete by key and index](../compare-exchange/delete-cmpxchg-items#delete-by-key-and-index) + * [Delete multiple items](../compare-exchange/delete-cmpxchg-items#delete-multiple-items) + * [Delete compare-exchange item using a **store operation**](../compare-exchange/delete-cmpxchg-items#delete-compare-exchange-item-using-a-store-operation) + * [Delete compare-exchange items using the **Studio**](../compare-exchange/delete-cmpxchg-items#delete-compare-exchange-items-using-the-studio) + * [Syntax](../compare-exchange/delete-cmpxchg-items#syntax) + + + +--- + +## Delete compare-exchange item using a cluster-wide session + +* Delete compare-exchange items using a cluster-wide session when you want the deletion to be part of a transaction committed via `SaveChanges()`. + This is suitable if you want to include compare-exchange deletions alongside other operations, such as putting or deleting documents and compare-exchange items, in a single transaction. + Learn more about cluster-wide sessions in [Cluster transactions - overview](../client-api/session/cluster-transaction/overview). + +* Use `DeleteCompareExchangeValue()` to register the deletion of an existing compare-exchange item in the session. + The item will be deleted as part of the cluster-wide transaction when _SaveChanges()_ is called. + +* If the item's index (its version) on the server is different from the index you provide, _SaveChanges()_ will throw a `ClusterTransactionConcurrencyException`. + This means the item was modified by another operation after it was loaded into the session, and the entire transaction will be rejected. + +* Examples: + + #### Delete by item + + + + ```csharp + // The session must be opened in cluster-wide mode. + // An `InvalidOperationException` is thrown if the session is not opened in cluster-wide mode. + using (var session = store.OpenSession( + new SessionOptions { TransactionMode = TransactionMode.ClusterWide })) + { + // Get the latest version of the existing compare-exchange item to be deleted. + CompareExchangeValue itemToDelete = session.Advanced.ClusterTransaction + .GetCompareExchangeValue("user1-name@example.com"); + + if (itemToDelete != null) + { + // Call 'DeleteCompareExchangeValue' to register the deletion as part of the cluster-wide + // transaction. Pass the item to delete. + session.Advanced.ClusterTransaction.DeleteCompareExchangeValue(itemToDelete); + + // Commit the cluster-wide transaction. This will delete the compare-exchange item, + // or throw a 'ClusterTransactionConcurrencyException' if the item's index (its version) + // on the server is different than the one provided in the delete request. + session.SaveChanges(); + } + } + ``` + + + ```csharp + // The session must be opened in cluster-wide mode. + // An `InvalidOperationException` is thrown if the session is not opened in cluster-wide mode. + using (var asyncSession = store.OpenAsyncSession( + new SessionOptions { TransactionMode = TransactionMode.ClusterWide })) + { + // Get the latest version of the existing compare-exchange item to be deleted. + CompareExchangeValue itemToDelete = await asyncSession.Advanced.ClusterTransaction + .GetCompareExchangeValueAsync("user1-name@example.com"); + + if (itemToDelete != null) + { + // Call 'DeleteCompareExchangeValue' to register the deletion as part of the cluster-wide + // transaction. Pass the item to delete. + asyncSession.Advanced.ClusterTransaction.DeleteCompareExchangeValue(itemToDelete); + + // Commit the cluster-wide transaction. This will delete the compare-exchange item, + // or throw a 'ClusterTransactionConcurrencyException' if the item's index (its version) + // on the server is different than the one provided in the delete request. + await asyncSession.SaveChangesAsync(); + } + } + ``` + + + + #### Delete by key and index + + + + ```csharp + // The session must be opened in cluster-wide mode. + // An `InvalidOperationException` is thrown if the session is not opened in cluster-wide mode. + using (var session = store.OpenSession( + new SessionOptions { TransactionMode = TransactionMode.ClusterWide })) + { + // Get the latest version of the existing compare-exchange item to be deleted. + CompareExchangeValue itemToDelete = session.Advanced.ClusterTransaction + .GetCompareExchangeValue("user1-name@example.com"); + + if (itemToDelete != null) + { + // Call 'DeleteCompareExchangeValue' to register the deletion as part of the cluster-wide + // transaction. Specify the item's KEY and current INDEX (its version). + session.Advanced.ClusterTransaction.DeleteCompareExchangeValue( + itemToDelete.Key, itemToDelete.Index); + + // Commit the cluster-wide transaction. This will delete the compare-exchange item, + // or throw a 'ClusterTransactionConcurrencyException' if the item's index (its version) + // on the server is different than the one provided in the delete request. + session.SaveChanges(); + } + } + ``` + + + ```csharp + // The session must be opened in cluster-wide mode. + // An `InvalidOperationException` is thrown if the session is not opened in cluster-wide mode. + using (var asyncSession = store.OpenAsyncSession( + new SessionOptions { TransactionMode = TransactionMode.ClusterWide })) + { + // Get the latest version of the existing compare-exchange item to be deleted. + CompareExchangeValue itemToDelete = await asyncSession.Advanced.ClusterTransaction + .GetCompareExchangeValueAsync("user1-name@example.com"); + + if (itemToDelete != null) + { + // Call 'DeleteCompareExchangeValue' to register the deletion as part of the cluster-wide + // transaction. Specify the item's KEY and current INDEX (its version). + asyncSession.Advanced.ClusterTransaction.DeleteCompareExchangeValue( + itemToDelete.Key, itemToDelete.Index); + + // Commit the cluster-wide transaction. This will delete the compare-exchange item, + // or throw a 'ClusterTransactionConcurrencyException' if the item's index (its version) + // on the server is different than the one provided in the delete request. + await asyncSession.SaveChangesAsync(); + } + } + ``` + + + + #### Delete multiple items + + + + ```csharp + // The session must be opened in cluster-wide mode + using (var session = store.OpenSession( + new SessionOptions { TransactionMode = TransactionMode.ClusterWide })) + { + // Get the latest version of the items to be deleted. + CompareExchangeValue itemToDelete1 = session.Advanced.ClusterTransaction + .GetCompareExchangeValue("user1-name@example.com"); + CompareExchangeValue itemToDelete2 = session.Advanced.ClusterTransaction + .GetCompareExchangeValue("user2-name@example.com"); + CompareExchangeValue itemToDelete3 = session.Advanced.ClusterTransaction + .GetCompareExchangeValue("user3-name@example.com"); + + // You can delete multiple compare-exchange items before calling 'SaveChanges'. + // Call 'DeleteCompareExchangeValue' for each item you want to delete in the transaction. + session.Advanced.ClusterTransaction.DeleteCompareExchangeValue(itemToDelete1); + session.Advanced.ClusterTransaction.DeleteCompareExchangeValue(itemToDelete2); + session.Advanced.ClusterTransaction.DeleteCompareExchangeValue(itemToDelete3); + + // All items will be deleted atomically as part of the same transaction. + // If any deletion fails, the entire transaction is rolled back + // and none of the items will be deleted. + session.SaveChanges(); + } + ``` + + + ```csharp + // The session must be opened in cluster-wide mode + using (var asyncSession = store.OpenAsyncSession( + new SessionOptions { TransactionMode = TransactionMode.ClusterWide })) + { + // Get the latest version of the items to be deleted. + CompareExchangeValue itemToDelete1 = await asyncSession.Advanced.ClusterTransaction + .GetCompareExchangeValue("user1-name@example.com"); + CompareExchangeValue itemToDelete2 = await asyncSession.Advanced.ClusterTransaction + .GetCompareExchangeValue("user2-name@example.com"); + CompareExchangeValue itemToDelete3 = await asyncSession.Advanced.ClusterTransaction + .GetCompareExchangeValue("user3-name@example.com"); + + // You can delete multiple compare-exchange items before calling 'SaveChanges'. + // Call 'DeleteCompareExchangeValue' for each item you want to delete in the transaction. + asyncSession.Advanced.ClusterTransaction.DeleteCompareExchangeValue(itemToDelete1); + asyncSession.Advanced.ClusterTransaction.DeleteCompareExchangeValue(itemToDelete2); + asyncSession.Advanced.ClusterTransaction.DeleteCompareExchangeValue(itemToDelete3); + + // All items will be deleted atomically as part of the same transaction. + // If any deletion fails, the entire transaction is rolled back + // and none of the items will be deleted. + await asyncSession.SaveChangesAsync(); + } + ``` + + + +--- + +## Delete compare-exchange item using a store operation + +* Use the `DeleteCompareExchangeValueOperation` [store operation](../client-api/operations/what-are-operations) to delete a compare-exchange item by its key and index, without opening a session. + This is ideal for stand-alone tasks that don't require batching multiple commands into a single transactional session. + +* The delete operation will only succeed if the item's current index on the server is the same as the one you provide. + If the indexes do not match, the item is not deleted and no exception is thrown. + +* Examples: + + + + ```csharp + // Get the latest version of the existing compare-exchange item to be deleted + var getCmpXchgOp = new GetCompareExchangeValueOperation("user1-name@example.com"); + CompareExchangeValue itemToDelete = store.Operations.Send(getCmpXchgOp); + + if (itemToDelete != null) + { + // Define the delete compare-exchange operation + // Pass the item's KEY and INDEX (its version) + var deleteCmpXchgOp = new DeleteCompareExchangeValueOperation( + itemToDelete.Key, itemToDelete.Index); + + // Execute the delete operation by passing it to Operations.Send + CompareExchangeResult resultOfDelete = store.Operations.Send(deleteCmpXchgOp); + + // Check results + bool successful = resultOfDelete.Successful; // Has operation succeeded + long indexOfItem = resultOfDelete.Index; // The version of the deleted item + + // If 'successful' is true - the compare-exchange item was deleted. + // If 'successful' is false - the item was not deleted (index mismatch). + } + ``` + + + ```csharp + // Get the latest version of the existing compare-exchange item to be deleted + var getCmpXchgOp = new GetCompareExchangeValueOperation("user1-name@example.com"); + CompareExchangeValue itemToDelete = await store.Operations.SendAsync(getCmpXchgOp); + + if (itemToDelete != null) + { + // Define the delete compare-exchange operation + // Pass the item's KEY and its INDEX (its version) + var deleteCmpXchgOp = new DeleteCompareExchangeValueOperation( + itemToDelete.Key, itemToDelete.Index); + + // Execute the delete operation by passing it to Operations.SendAsync + CompareExchangeResult resultOfDelete = await store.Operations.SendAsync(deleteCmpXchgOp); + + // Check results + bool successful = resultOfDelete.Successful; // Has operation succeeded + long indexOfItem = resultOfDelete.Index; // The version of the deleted item + + // If 'successful' is true - the compare-exchange item was deleted. + // If 'successful' is false - the item was not deleted (index mismatch). + } + ``` + + + +--- + +## Delete compare-exchange items using the Studio + +You can delete one or multiple compare-exchange items from the Studio. + +![The compare-exchange view](../assets/delete-cmpxchg.png) + +1. Go to **Documents > Compare Exchange**. +2. Select the compare-exchange items you want to delete. +3. Click **Delete**. + +--- + +## Syntax + +--- + +### `DeleteCompareExchangeValueOperation` +Delete compare-exchange item using a store operation: + + +```csharp +public DeleteCompareExchangeValueOperation(string key, long index) +``` + + +| Parameter | Type | Description | +|-----------|----------|-------------| +| **key** | `string` | The unique key of the compare-exchange item. | +| **index** | `long` | The current version of the item.
Deletion will only succeed if this matches the version stored on the server. | + +**Returned object**: + + +```csharp +public class CompareExchangeResult +{ + public bool Successful; + public T Value; + public long Index; +} +``` + + +| Return Value | Type | Description | +|---------------|--------|-------------| +| **Successful**| `bool` |
  • `true` if the delete operation completed successfully.
  • `true` if _key_ doesn't exist
  • `false` if the delete operation has failed, e.g. when the index version doesn't match.
| +| **Value** | `T` |
  • The value that was deleted upon a successful delete.
  • `null` if _key_ doesn't exist
  • The currently existing value on the server if the delete operation has failed.
| +| **Index** | `long` |
  • The next available version number upon success.
  • The next available version number if _key_ doesn't exist.
  • The currently existing index on the server if the delete operation has failed.
| + +--- + +### `DeleteCompareExchangeValue` +Delete compare-exchange item using cluster-wide session: + + +```csharp +// Available overloads: +void DeleteCompareExchangeValue(CompareExchangeValue item); +void DeleteCompareExchangeValue(string key, long index); +``` + + +| Parameter | Type | Description | +|-----------|---------------------------|-------------| +| **item** | `CompareExchangeValue` | The compare-exchange item to delete. | +| **key** | `string` | The unique key of the compare-exchange item. | +| **index** | `long` | The current version of the item.
Deletion will only succeed if this matches the version stored on the server. | diff --git a/docs/compare-exchange/content/_delete-cmpxchg-items-java.mdx b/docs/compare-exchange/content/_delete-cmpxchg-items-java.mdx new file mode 100644 index 0000000000..6fe2d159e7 --- /dev/null +++ b/docs/compare-exchange/content/_delete-cmpxchg-items-java.mdx @@ -0,0 +1,142 @@ +import Admonition from '@theme/Admonition'; +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +import CodeBlock from '@theme/CodeBlock'; + + + +* **Custom compare-exchange items can be deleted**: + You can delete your own custom compare-exchange items. + An item is deleted only if the index you provide in the request matches the current index stored on the server for the specified key. + +* **Delete items by expiration**: + Compare-exchange items can also be deleted by adding an expiration date to them. + Learn more in [Compare-exchange expiration](../compare-exchange/cmpxchg-expiration). + +* **Compare-exchange tombstones**: + Whenever a compare-exchange item is deleted, a compare-exchange tombstone is created for it. + These tombstones are used to indicate to other RavenDB processes that the compare-exchange item was deleted, + so they can react accordingly. + For example, indexes referencing the deleted item will update themselves to remove those references. + Compare-exchange tombstones that are eligible for deletion are removed periodically by an internal cleanup task. + See: [Cluster.CompareExchangeTombstonesCleanupIntervalInMin](../compare-exchange/configuration#clustercompareexchangetombstonescleanupintervalinmin). + +* + Do not attempt to delete [atomic guards](../compare-exchange/atomic-guards), which RavenDB uses internally to ensure ACID guarantees in cluster-wide transactions. + These items are created automatically and must not be modified or removed. + + If your custom compare-exchange item was set up to protect the consistency of a transaction, deleting it will break the ACID guarantees. + Only delete or modify such items if you truly know what you're doing. + + +--- + +* In this article: + * [Delete compare-exchange item using a **store operation**](../compare-exchange/delete-cmpxchg-items#delete-compare-exchange-item-using-a-store-operation) + * [Delete compare-exchange items using the **Studio**](../compare-exchange/delete-cmpxchg-items#delete-compare-exchange-items-using-the-studio) + * [Syntax](../compare-exchange/delete-cmpxchg-items#syntax) + + + +--- + +## Delete compare-exchange item using a store operation + +* Use the `DeleteCompareExchangeValueOperation` [store operation](../client-api/operations/what-are-operations) to delete a compare-exchange item by its key and index, without opening a session. + This is ideal for stand-alone tasks that don't require batching multiple commands into a single transactional session. + +* The delete operation will only succeed if the item's current index on the server is the same as the one you provide. + If the indexes do not match, the item is not deleted and no exception is thrown. + +* Example: + + + + ```java + // Get the latest version of the existing compare-exchange item to be deleted + CompareExchangeValue itemToDelete = store.operations().send( + new GetCompareExchangeValueOperation<>(User.class, "AdminUser")); + + // Execute the delete operation + CompareExchangeResult deleteResult = store.operations().send( + new DeleteCompareExchangeValueOperation<>(User.class, "AdminUser", itemToDelete.getIndex())); + + // Check results + boolean deleteResultSuccessful = deleteResult.isSuccessful(); + ``` + + + +--- + +## Delete compare-exchange items using the Studio + +You can delete one or multiple compare-exchange items from the Studio. + +![The compare-exchange view](../assets/delete-cmpxchg.png) + +1. Go to **Documents > Compare Exchange**. +2. Select the compare-exchange items you want to delete. +3. Click **Delete**. + +--- + +## Syntax + +--- + +### `DeleteCompareExchangeValueOperation` +Delete compare-exchange item using a store operation: + + +```java +public DeleteCompareExchangeValueOperation(Class clazz, String key, long index) +``` + + +| Parameter | Type | Description | +|-----------|----------|-------------| +| **key** | `string` | The unique key of the compare-exchange item. | +| **index** | `long` | The current version of the item.
Deletion will only succeed if this matches the version stored on the server. | + +**Returned object**: + + +```java +public class CompareExchangeResult { + private T value; + private long index; + private boolean successful; + + public T getValue() { + return value; + } + + public void setValue(T value) { + this.value = value; + } + + public long getIndex() { + return index; + } + + public void setIndex(long index) { + this.index = index; + } + + public boolean isSuccessful() { + return successful; + } + + public void setSuccessful(boolean successful) { + this.successful = successful; + } +} +``` + + +| Return Value | Type | Description | +|---------------|-----------|-------------| +| **Successful**| `boolean` |
  • `true` if the delete operation completed successfully.
  • `true` if _key_ doesn't exist
  • `false` if the delete operation has failed, e.g. when the index version doesn't match.
| +| **Value** | `T` |
  • The value that was deleted upon a successful delete.
  • `null` if _key_ doesn't exist
  • The currently existing value on the server if the delete operation has failed.
| +| **Index** | `long` |
  • The next available version number upon success.
  • The next available version number if _key_ doesn't exist.
  • The currently existing index on the server if the delete operation has failed.
| diff --git a/docs/compare-exchange/content/_delete-cmpxchg-items-nodejs.mdx b/docs/compare-exchange/content/_delete-cmpxchg-items-nodejs.mdx new file mode 100644 index 0000000000..98733ea1b6 --- /dev/null +++ b/docs/compare-exchange/content/_delete-cmpxchg-items-nodejs.mdx @@ -0,0 +1,262 @@ +import Admonition from '@theme/Admonition'; +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +import CodeBlock from '@theme/CodeBlock'; + + + +* **Custom compare-exchange items can be deleted**: + You can delete your own custom compare-exchange items. + An item is deleted only if the index you provide in the request matches the current index stored on the server for the specified key. + +* **Delete items by expiration**: + Compare-exchange items can also be deleted by adding an expiration date to them. + Learn more in [Compare-exchange expiration](../compare-exchange/cmpxchg-expiration). + +* **Compare-exchange tombstones**: + Whenever a compare-exchange item is deleted, a compare-exchange tombstone is created for it. + These tombstones are used to indicate to other RavenDB processes that the compare-exchange item was deleted, + so they can react accordingly. + For example, indexes referencing the deleted item will update themselves to remove those references. + Compare-exchange tombstones that are eligible for deletion are removed periodically by an internal cleanup task. + See: [Cluster.CompareExchangeTombstonesCleanupIntervalInMin](../compare-exchange/configuration#clustercompareexchangetombstonescleanupintervalinmin). + +* + Do not attempt to delete [atomic guards](../compare-exchange/atomic-guards), which RavenDB uses internally to ensure ACID guarantees in cluster-wide transactions. + These items are created automatically and must not be modified or removed. + + If your custom compare-exchange item was set up to protect the consistency of a transaction, deleting it will break the ACID guarantees. + Only delete or modify such items if you truly know what you're doing. + + +--- + +* In this article: + * [Delete compare-exchange item using a **cluster-wide session**](../compare-exchange/delete-cmpxchg-items#delete-compare-exchange-item-using-a-cluster-wide-session) + * [Delete by item](../compare-exchange/delete-cmpxchg-items#delete-by-item) + * [Delete by key and index](../compare-exchange/delete-cmpxchg-items#delete-by-key-and-index) + * [Delete multiple items](../compare-exchange/delete-cmpxchg-items#delete-multiple-items) + * [Delete compare-exchange item using a **store operation**](../compare-exchange/delete-cmpxchg-items#delete-compare-exchange-item-using-a-store-operation) + * [Delete compare-exchange items using the **Studio**](../compare-exchange/delete-cmpxchg-items#delete-compare-exchange-items-using-the-studio) + * [Syntax](../compare-exchange/delete-cmpxchg-items#syntax) + + + +--- + +## Delete compare-exchange item using a cluster-wide session + +* Delete compare-exchange items using a cluster-wide session when you want the deletion to be part of a transaction committed via `saveChanges()`. + This is suitable if you want to include compare-exchange deletions alongside other operations, such as putting or deleting documents and compare-exchange items, in a single transaction. + Learn more about cluster-wide sessions in [Cluster transactions - overview](../client-api/session/cluster-transaction/overview). + +* Use `deleteCompareExchangeValue()` to register the deletion of an existing compare-exchange item in the session. + The item will be deleted as part of the cluster-wide transaction when _saveChanges()_ is called. + +* If the item's index (its version) on the server is different from the index you provide, _saveChanges()_ will throw a `ClusterTransactionConcurrencyException`. + This means the item was modified by another operation after it was loaded into the session, and the entire transaction will be rejected. + +* Examples: + + #### Delete by item + + + + ```js + // The session must be opened in cluster-wide mode. + // An `InvalidOperationException` is thrown if the session is not opened in cluster-wide mode. + const session = documentStore.openSession({ + transactionMode: "ClusterWide" + }); + + // Get the latest version of the existing compare-exchange item to be deleted. + const itemToDelete = await session.advanced.clusterTransaction + .getCompareExchangeValue("user1-name@example.com"); + + if (itemToDelete) { + // Call 'deleteCompareExchangeValue' to register the deletion as part of the cluster-wide + // transaction. Pass the item to delete. + session.advanced.clusterTransaction.deleteCompareExchangeValue(itemToDelete); + + // Commit the cluster-wide transaction. This will delete the compare-exchange item, + // or throw a 'ClusterTransactionConcurrencyException' if the item's index (its version) + // on the server is different than the one provided in the delete request. + await session.saveChanges(); + } + ``` + + + + #### Delete by key and index + + + + ```js + // The session must be opened in cluster-wide mode. + // An `InvalidOperationException` is thrown if the session is not opened in cluster-wide mode. + const session = documentStore.openSession({ + transactionMode: "ClusterWide" + }); + + // Get the latest version of the existing compare-exchange item to be deleted. + const itemToDelete = await session.advanced.clusterTransaction + .getCompareExchangeValue("user1-name@example.com"); + + if (itemToDelete) { + // Call 'deleteCompareExchangeValue' to register the deletion as part of the cluster-wide + // transaction. Specify the item's KEY and current INDEX (its version). + session.advanced.clusterTransaction.deleteCompareExchangeValue(itemToDelete.key, itemToDelete.index); + + // Commit the cluster-wide transaction. This will delete the compare-exchange item, + // or throw a 'ClusterTransactionConcurrencyException' if the item's index (its version) + // on the server is different than the one provided in the delete request. + await session.saveChanges(); + } + ``` + + + + #### Delete multiple items + + + + ```js + // The session must be opened in cluster-wide mode + const session = documentStore.openSession({ + transactionMode: "ClusterWide" + }); + + // Get the latest version of the items to be deleted. + const itemToDelete1 = await session.advanced.clusterTransaction + .getCompareExchangeValue("user1-name@example.com"); + const itemToDelete2 = await session.advanced.clusterTransaction + .getCompareExchangeValue("user2-name@example.com"); + const itemToDelete3 = await session.advanced.clusterTransaction + .getCompareExchangeValue("user3-name@example.com"); + + // You can delete multiple compare-exchange items before calling 'saveChanges'. + // Call 'deleteCompareExchangeValue' for each item you want to delete in the transaction. + session.advanced.clusterTransaction.deleteCompareExchangeValue(itemToDelete1); + session.advanced.clusterTransaction.deleteCompareExchangeValue(itemToDelete2); + session.advanced.clusterTransaction.deleteCompareExchangeValue(itemToDelete3); + + // All items will be deleted atomically as part of the same transaction. + // If any deletion fails, the entire transaction is rolled back + // and none of the items will be deleted. + await session.saveChanges(); + ``` + + + +--- + +## Delete compare-exchange item using a store operation + +* Use the `DeleteCompareExchangeValueOperation` [store operation](../client-api/operations/what-are-operations) to delete a compare-exchange item by its key and index, without opening a session. + This is ideal for stand-alone tasks that don't require batching multiple commands into a single transactional session. + +* The delete operation will only succeed if the item's current index on the server is the same as the one you provide. + If the indexes do not match, the item is not deleted and no exception is thrown. + +* Examples: + + + + ```js + // Get the latest version of the existing compare-exchange item to be deleted + const getCmpXchgOp = new GetCompareExchangeValueOperation("user1-name@example.com"); + const itemToDelete = await documentStore.operations.send(getCmpXchgOp); + + if (itemToDelete) + { + // Define the delete compare-exchange operation + // Pass the item's KEY and INDEX (its version) + const deleteCmpXchgOp = new DeleteCompareExchangeValueOperation(itemToDelete.key, itemToDelete.index); + + // Execute the delete operation by passing it to operations.send + const resultOfDelete = await documentStore.operations.send(deleteCmpXchgOp); + + // Check results + const successful = resultOfDelete.successful; // Has operation succeeded + const indexOfItem = resultOfDelete.index; // The version of the deleted item + + // If 'successful' is true - the compare-exchange item was deleted. + // If 'successful' is false - the item was not deleted (index mismatch). + } + ``` + + + +--- + +## Delete compare-exchange items using the Studio + +You can delete one or multiple compare-exchange items from the Studio. + +![The compare-exchange view](../assets/delete-cmpxchg.png) + +1. Go to **Documents > Compare Exchange**. +2. Select the compare-exchange items you want to delete. +3. Click **Delete**. + +--- + +## Syntax + +--- + +### `DeleteCompareExchangeValueOperation` +Delete compare-exchange item using a store operation: + + +```js +const deleteCmpXchgOp = new DeleteCompareExchangeValueOperation(key, index, clazz?); +``` + + +| Parameter | Type | Description | +|-----------|----------|-------------| +| **key** | `string` | The unique key of the compare-exchange item to be deleted. | +| **index** | `long` | The current version of the item to be deleted.
Deletion will only succeed if this matches the version stored on the server. | +| **clazz** | `object` | When the item's value is a class, you can specify its type in this parameter. | + +**Returned object**: + + +```js +// Return value of store.operations.send(deleteCmpXchgOp) +// ====================================================== + +class CompareExchangeResult +{ + Successful; + Value; + Index; +} +``` + + +| Return Value | Type | Description | +|---------------|-----------|-------------| +| **Successful**| `boolean` |
  • `true` if the delete operation completed successfully.
  • `true` if _key_ doesn't exist
  • `false` if the delete operation has failed, e.g. when the index version doesn't match.
| +| **Value** | `object` |
  • The value that was deleted upon a successful delete.
  • `null` if _key_ doesn't exist
  • The currently existing value on the server if the delete operation has failed.
| +| **Index** | `number` |
  • The next available version number upon success.
  • The next available version number if _key_ doesn't exist.
  • The currently existing index on the server if the delete operation has failed.
| + +--- + +### `deleteCompareExchangeValue` +Delete compare-exchange item using cluster-wide session: + + +```js +// Available overloads: +deleteCompareExchangeValue(key, index); +deleteCompareExchangeValue(item); +``` + + +| Parameter | Type | Description | +|-----------|-------------------------|-------------| +| **item** | `CompareExchangeValue` | The compare-exchange item to delete. | +| **key** | `string` | The unique key of the compare-exchange item. | +| **index** | `number` | The current version of the item.
Deletion will only succeed if this matches the version stored on the server. | diff --git a/docs/compare-exchange/content/_delete-cmpxchg-items-php.mdx b/docs/compare-exchange/content/_delete-cmpxchg-items-php.mdx new file mode 100644 index 0000000000..0685a70894 --- /dev/null +++ b/docs/compare-exchange/content/_delete-cmpxchg-items-php.mdx @@ -0,0 +1,90 @@ +import Admonition from '@theme/Admonition'; +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +import CodeBlock from '@theme/CodeBlock'; + + + +* **Custom compare-exchange items can be deleted**: + You can delete your own custom compare-exchange items. + An item is deleted only if the index you provide in the request matches the current index stored on the server for the specified key. + +* **Delete items by expiration**: + Compare-exchange items can also be deleted by adding an expiration date to them. + Learn more in [Compare-exchange expiration](../compare-exchange/cmpxchg-expiration). + +* **Compare-exchange tombstones**: + Whenever a compare-exchange item is deleted, a compare-exchange tombstone is created for it. + These tombstones are used to indicate to other RavenDB processes that the compare-exchange item was deleted, + so they can react accordingly. + For example, indexes referencing the deleted item will update themselves to remove those references. + Compare-exchange tombstones that are eligible for deletion are removed periodically by an internal cleanup task. + See: [Cluster.CompareExchangeTombstonesCleanupIntervalInMin](../compare-exchange/configuration#clustercompareexchangetombstonescleanupintervalinmin). + +* + Do not attempt to delete [atomic guards](../compare-exchange/atomic-guards), which RavenDB uses internally to ensure ACID guarantees in cluster-wide transactions. + These items are created automatically and must not be modified or removed. + + If your custom compare-exchange item was set up to protect the consistency of a transaction, deleting it will break the ACID guarantees. + Only delete or modify such items if you truly know what you're doing. + + +--- + +* In this article: + * [Delete compare-exchange item using a **cluster-wide session**](../compare-exchange/delete-cmpxchg-items#delete-compare-exchange-item-using-a-cluster-wide-session) + * [Delete by item](../compare-exchange/delete-cmpxchg-items#delete-by-item) + * [Delete by key and index](../compare-exchange/delete-cmpxchg-items#delete-by-key-and-index) + * [Delete compare-exchange items using the **Studio**](../compare-exchange/delete-cmpxchg-items#delete-compare-exchange-items-using-the-studio) + * [Syntax](../compare-exchange/delete-cmpxchg-items#syntax) + + + +--- + +## Delete compare-exchange item using a cluster-wide session + +* Delete compare-exchange items using a cluster-wide session when you want the deletion to be part of a transaction committed via `saveChanges()`. + This is suitable if you want to include compare-exchange deletions alongside other operations, such as putting or deleting documents and compare-exchange items, in a single transaction. + Learn more about cluster-wide sessions in [Cluster transactions - overview](../client-api/session/cluster-transaction/overview). + +* Use `deleteCompareExchangeValue()` to register the deletion of an existing compare-exchange item in the session. + The item will be deleted as part of the cluster-wide transaction when _saveChanges()_ is called. + +* If the item's index (its version) on the server is different from the index you provide, _saveChanges()_ will throw a `ClusterTransactionConcurrencyException`. + This means the item was modified by another operation after it was loaded into the session, and the entire transaction will be rejected. + +--- + +## Delete compare-exchange items using the Studio + +You can delete one or multiple compare-exchange items from the Studio. + +![The compare-exchange view](../assets/delete-cmpxchg.png) + +1. Go to **Documents > Compare Exchange**. +2. Select the compare-exchange items you want to delete. +3. Click **Delete**. + +--- + +## Syntax + +--- + +### `deleteCompareExchangeValue` +Delete compare-exchange item using cluster-wide session: + + +```csharp +// Available overloads: +$session->advanced()->clusterTransaction()->deleteCompareExchangeValue($item); +$session->advanced()->clusterTransaction()->deleteCompareExchangeValue($key, $index); +``` + + +| Parameter | Type | Description | +|-----------|---------------------------|-------------| +| **item** | `CompareExchangeValue` | The compare-exchange item to delete. | +| **key** | `string` | The unique key of the compare-exchange item. | +| **index** | `long` | The current version of the item.
Deletion will only succeed if this matches the version stored on the server. | diff --git a/docs/compare-exchange/content/_delete-cmpxchg-items-python.mdx b/docs/compare-exchange/content/_delete-cmpxchg-items-python.mdx new file mode 100644 index 0000000000..331dc75a23 --- /dev/null +++ b/docs/compare-exchange/content/_delete-cmpxchg-items-python.mdx @@ -0,0 +1,90 @@ +import Admonition from '@theme/Admonition'; +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +import CodeBlock from '@theme/CodeBlock'; + + + +* **Custom compare-exchange items can be deleted**: + You can delete your own custom compare-exchange items. + An item is deleted only if the index you provide in the request matches the current index stored on the server for the specified key. + +* **Delete items by expiration**: + Compare-exchange items can also be deleted by adding an expiration date to them. + Learn more in [Compare-exchange expiration](../compare-exchange/cmpxchg-expiration). + +* **Compare-exchange tombstones**: + Whenever a compare-exchange item is deleted, a compare-exchange tombstone is created for it. + These tombstones are used to indicate to other RavenDB processes that the compare-exchange item was deleted, + so they can react accordingly. + For example, indexes referencing the deleted item will update themselves to remove those references. + Compare-exchange tombstones that are eligible for deletion are removed periodically by an internal cleanup task. + See: [Cluster.CompareExchangeTombstonesCleanupIntervalInMin](../compare-exchange/configuration#clustercompareexchangetombstonescleanupintervalinmin). + +* + Do not attempt to delete [atomic guards](../compare-exchange/atomic-guards), which RavenDB uses internally to ensure ACID guarantees in cluster-wide transactions. + These items are created automatically and must not be modified or removed. + + If your custom compare-exchange item was set up to protect the consistency of a transaction, deleting it will break the ACID guarantees. + Only delete or modify such items if you truly know what you're doing. + + +--- + +* In this article: + * [Delete compare-exchange item using a **cluster-wide session**](../compare-exchange/delete-cmpxchg-items#delete-compare-exchange-item-using-a-cluster-wide-session) + * [Delete by item](../compare-exchange/delete-cmpxchg-items#delete-by-item) + * [Delete by key and index](../compare-exchange/delete-cmpxchg-items#delete-by-key-and-index) + * [Delete compare-exchange items using the **Studio**](../compare-exchange/delete-cmpxchg-items#delete-compare-exchange-items-using-the-studio) + * [Syntax](../compare-exchange/delete-cmpxchg-items#syntax) + + + +--- + +## Delete compare-exchange item using a cluster-wide session + +* Delete compare-exchange items using a cluster-wide session when you want the deletion to be part of a transaction committed via `save_changes()`. + This is suitable if you want to include compare-exchange deletions alongside other operations, such as putting or deleting documents and compare-exchange items, in a single transaction. + Learn more about cluster-wide sessions in [Cluster transactions - overview](../client-api/session/cluster-transaction/overview). + +* Use `delete_compare_exchange_value()` to register the deletion of an existing compare-exchange item in the session. + The item will be deleted as part of the cluster-wide transaction when _save_changes()_ is called. + +* If the item's index (its version) on the server is different from the index you provide, _save_changes()_ will throw a `ClusterTransactionConcurrencyException`. + This means the item was modified by another operation after it was loaded into the session, and the entire transaction will be rejected. + +--- + +## Delete compare-exchange items using the Studio + +You can delete one or multiple compare-exchange items from the Studio. + +![The compare-exchange view](../assets/delete-cmpxchg.png) + +1. Go to **Documents > Compare Exchange**. +2. Select the compare-exchange items you want to delete. +3. Click **Delete**. + +--- + +## Syntax + +--- + +### `delete_compare_exchange_value` +Delete compare-exchange item using cluster-wide session: + + +```csharp +// Available overloads: +session.advanced.cluster_transaction.delete_compare_exchange_value(item) +session.advanced.cluster_transaction.delete_compare_exchange_value(key, index) +``` + + +| Parameter | Type | Description | +|-----------|---------------------------|-------------| +| **item** | `CompareExchangeValue[T]` | The compare-exchange item to delete. | +| **key** | `str` | The unique key of the compare-exchange item. | +| **index** | `int` | The current version of the item.
Deletion will only succeed if this matches the version stored on the server. | diff --git a/docs/compare-exchange/content/_get-cmpxchg-item-csharp.mdx b/docs/compare-exchange/content/_get-cmpxchg-item-csharp.mdx new file mode 100644 index 0000000000..ee35aaf78e --- /dev/null +++ b/docs/compare-exchange/content/_get-cmpxchg-item-csharp.mdx @@ -0,0 +1,530 @@ +import Admonition from '@theme/Admonition'; +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +import CodeBlock from '@theme/CodeBlock'; + + + +* To retrieve an existing compare-exchange item using the **Client API**, + use either a store operation or a cluster-wide session - as described below. + A cluster-wide session also supports lazy retrieval. + +* To view existing compare-exchange items in the **Studio**, go to _Documents > Compare Exchange_, + as described in [Ways to manage compare-exchange items](../compare-exchange/overview#ways-to-create-and-manage-compare-exchange-items). + +* This article shows how to get a **single** compare-exchange item by its unique _key_. + To get **multiple** items at once, see [Get multiple compare-exchange items](../compare-exchange/get-cmpxchg-items). + +* In this article: + * [Get item using a **cluster-wide session**](../compare-exchange/get-cmpxchg-item#get-item-using-a-cluster-wide-session) + * [Get compare-exchange item](../compare-exchange/get-cmpxchg-item#get-compare-exchange-item) + * [Get compare-exchange item lazily](../compare-exchange/get-cmpxchg-item#get-compare-exchange-item-lazily) + * [Retrieved compare-exchange items are tracked by the session](../compare-exchange/get-cmpxchg-item#retrieved-compare-exchange-items-are-tracked-by-the-session) + * [Get item using a **store operation**](../compare-exchange/get-cmpxchg-item#get-item-using-a-store-operation) + * [Get compare-exchange item that has a number value and metadata](../compare-exchange/get-cmpxchg-item#get-compare-exchange-item-that-has-a-number-value-and-metadata) + * [Get compare-exchange item that has a custom object value](../compare-exchange/get-cmpxchg-item#get-compare-exchange-item-that-has-a-custom-object-value) + * [Syntax](../compare-exchange/get-cmpxchg-item#syntax) + + + +--- + +## Get item using a cluster-wide session + +* You can retrieve compare-exchange items using a [cluster-wide session](../client-api/session/cluster-transaction/overview#open-a-cluster-transaction). + The session must be opened in cluster-wide mode. + +* Use the `GetCompareExchangeValue` advanced session method to get a compare-exchange item by its _key_. + If the specified key does not exist, the method returns `null`. No exception is thrown. + +* Once a compare-exchange item is retrieved using a cluster-wide session, the item is **tracked** by the session. + Repeating the same `GetCompareExchangeValue` call with the same key does not send another request to the server, + the value is returned from the session's internal state. + To force a re-fetch from the server, call `session.Advanced.Clear()` first. + +* Examples: + + #### Get compare-exchange item + + + + ```csharp + // First, let's create a compare-exchange item for the example, + // e.g. store a user's email as the key and the user document id as the value. + using (var session = store.OpenSession( + new SessionOptions { TransactionMode = TransactionMode.ClusterWide })) + { + CompareExchangeValue itemToCreate = + session.Advanced.ClusterTransaction.CreateCompareExchangeValue( + key: "user1-name@example.com", + value: "users/1" + ); + + // Optionally, add some metadata: + itemToCreate.Metadata["email-type"] = "work email"; + + session.SaveChanges(); + } + + using (var session = store.OpenSession( + new SessionOptions { TransactionMode = TransactionMode.ClusterWide })) + { + // Get the compare-exchange item: + // ============================== + + CompareExchangeValue retrievedItem = session.Advanced.ClusterTransaction + .GetCompareExchangeValue("user1-name@example.com"); + + if (retrievedItem != null) + { + // Access the VALUE of the retrieved item + var userDocumentId = retrievedItem.Value; // "users/1" + + // Access the METADATA of the retrieved item + var emailType = retrievedItem.Metadata["email-type"]; // "work email" + + // Access the VERSION number of the retrieved item + var version = retrievedItem.Index; + } + else + { + Console.WriteLine("Compare-exchange item not found"); + } + } + ``` + + + ```csharp + // First, let's create a compare-exchange item for the example, + // e.g. store a user's email as the key and the user document id as the value. + using (var asyncSession = store.OpenAsyncSession( + new SessionOptions { TransactionMode = TransactionMode.ClusterWide })) + { + CompareExchangeValue itemToCreate = + asyncSession.Advanced.ClusterTransaction.CreateCompareExchangeValue( + key: "user1-name@example.com", + value: "users/1" + ); + + // Optionally, add some metadata: + itemToCreate.Metadata["email-type"] = "work email"; + + await asyncSession.SaveChangesAsync(); + } + + using (var asyncSession = store.OpenAsyncSession( + new SessionOptions { TransactionMode = TransactionMode.ClusterWide })) + { + // Get the compare-exchange item: + // ============================== + + CompareExchangeValue retrievedItem = await asyncSession.Advanced.ClusterTransaction + .GetCompareExchangeValueAsync("user1-name@example.com"); + + if (retrievedItem != null) + { + // Access the VALUE of the retrieved item + var userDocumentId = retrievedItem.Value; // "users/1" + + // Access the METADATA of the retrieved item + var emailType = retrievedItem.Metadata["email-type"]; // "work email" + + // Access the VERSION number of the retrieved item + var version = retrievedItem.Index; + } + else + { + Console.WriteLine("Compare-exchange item not found"); + } + } + ``` + + + + #### Get compare-exchange item lazily + + + + ```csharp + // Create a compare-exchange item for the example: + using (var session = store.OpenSession( + new SessionOptions { TransactionMode = TransactionMode.ClusterWide })) + { + CompareExchangeValue itemToCreate = + session.Advanced.ClusterTransaction.CreateCompareExchangeValue( + key: "user1-name@example.com", + value: "users/1" + ); + session.SaveChanges(); + } + + using (var session = store.OpenSession( + new SessionOptions { TransactionMode = TransactionMode.ClusterWide })) + { + // Get the compare-exchange item lazily: + // ===================================== + + var lazyItem = session.Advanced.ClusterTransaction.Lazily + .GetCompareExchangeValue("user1-name@example.com"); + + // Access the item: + CompareExchangeValue retrievedItem = lazyItem.Value; + + if (retrievedItem != null) { + // Access the VALUE of the retrieved item + var userDocumentId = retrievedItem.Value; // "users/1" + + // Access the VERSION number of the retrieved item + var version = retrievedItem.Index; + } + else + { + Console.WriteLine("Compare-exchange item not found"); + } + } + ``` + + + ```csharp + // Create a compare-exchange item for the example: + using (var asyncSession = store.OpenAsyncSession( + new SessionOptions { TransactionMode = TransactionMode.ClusterWide })) + { + CompareExchangeValue itemToCreate = + asyncSession.Advanced.ClusterTransaction.CreateCompareExchangeValue( + key: "user1-name@example.com", + value: "users/1" + ); + + await asyncSession.SaveChangesAsync(); + } + + using (var asyncSession = store.OpenAsyncSession( + new SessionOptions { TransactionMode = TransactionMode.ClusterWide })) + { + // Get the compare-exchange item lazily: + // ===================================== + + var lazyItem = asyncSession.Advanced.ClusterTransaction.Lazily + .GetCompareExchangeValueAsync("user1-name@example.com"); + + // Access the item: + CompareExchangeValue retrievedItem = await lazyItem.Value; + + if (retrievedItem != null) { + // Access the VALUE of the retrieved item + var userDocumentId = retrievedItem.Value; // "users/1" + + // Access the VERSION number of the retrieved item + var version = retrievedItem.Index; + } + else + { + Console.WriteLine("Compare-exchange item not found"); + } + } + ``` + + + + #### Retrieved compare-exchange items are tracked by the session + + + + ```csharp + using (var session = store.OpenSession( + new SessionOptions { TransactionMode = TransactionMode.ClusterWide })) + { + // First retrieval — server call will happen + var item1 = session.Advanced.ClusterTransaction + .GetCompareExchangeValue("user1-name@example.com"); + + // No server call - the item is returned from session + var item2 = session.Advanced.ClusterTransaction + .GetCompareExchangeValue("user1-name@example.com"); + + // Clear tracked entities and compare-exchange items + session.Advanced.Clear(); + + // Server call will happen again + var item3 = session.Advanced.ClusterTransaction + .GetCompareExchangeValue("user1-name@example.com"); + } + ``` + + + ```csharp + using (var asyncSession = store.OpenAsyncSession( + new SessionOptions { TransactionMode = TransactionMode.ClusterWide })) + { + // First retrieval — server call will happen + var item1 = await asyncSession.Advanced.ClusterTransaction + .GetCompareExchangeValueAsync("user1-name@example.com"); + + // No server call - the item is returned from session + var item2 = await asyncSession.Advanced.ClusterTransaction + .GetCompareExchangeValueAsync("user1-name@example.com"); + + // Clear tracked entities and compare-exchange items + asyncSession.Advanced.Clear(); + + // Server call will happen again + var item3 = await asyncSession.Advanced.ClusterTransaction + .GetCompareExchangeValueAsync("user1-name@example.com"); + } + ``` + + + +--- + +## Get item using a store operation + +* You can retrieve compare-exchange items using a [store operation](../client-api/operations/what-are-operations). + Use the `GetCompareExchangeValueOperation` operation to get a compare-exchange item by its _key_. + +* If the specified key does not exist, the operation returns `null`. No exception is thrown. + +* Examples: + + #### Get compare-exchange item that has a number value and metadata + + + + ```csharp + // First, let's create a new compare-exchange item for the example, + // e.g. store the number of sales made by an employee as the value + some metadata info: + var putCmpXchgOp = new PutCompareExchangeValueOperation("employees/1-A", 12345, 0, + new MetadataAsDictionary + { + { "Department", "Sales" }, + { "Role", "Salesperson" } + }); + + CompareExchangeResult putResult = store.Operations.Send(putCmpXchgOp); + + // Get the compare-exchange item: + // ============================== + + // Define the get compare-exchange operation, pass the unique item key + var getCmpXchgOp = new GetCompareExchangeValueOperation("employees/1-A"); + + // Execute the operation by passing it to Operations.Send + CompareExchangeValue retrievedItem = store.Operations.Send(getCmpXchgOp); + + if (retrievedItem != null) + { + // Access the VALUE of the retrieved item + long numberOfSales = retrievedItem.Value; // 12345 + + // Access the METADATA of the retrieved item + var employeeRole = retrievedItem.Metadata["Role"]; // "Salesperson" + + // Access the VERSION number of the retrieved item + long version = retrievedItem.Index; + } + else + { + Console.WriteLine("Compare-exchange item not found"); + } + ``` + + + ```csharp + // First, let's create a new compare-exchange item for the example, + // e.g. store the number of sales made by an employee as the value + some metadata info: + var putCmpXchgOp = new PutCompareExchangeValueOperation("employees/1-A", 12345, 0, + new MetadataAsDictionary + { + { "Department", "Sales" }, + { "Role", "Salesperson" } + }); + + CompareExchangeResult putResult = await store.Operations.SendAsync(putCmpXchgOp); + + // Get the compare-exchange item: + // ============================== + + // Define the get compare-exchange operation, pass the unique item key + var getCmpXchgOp = new GetCompareExchangeValueOperation("employees/1-A"); + + // Execute the operation by passing it to Operations.SendAsync + CompareExchangeValue retrievedItem = await store.Operations.SendAsync(getCmpXchgOp); + + if (retrievedItem != null) + { + // Access the VALUE of the retrieved item + long numberOfSales = retrievedItem.Value; // 12345 + + // Access the METADATA of the retrieved item + var employeeRole = retrievedItem.Metadata["Role"]; // "Salesperson" + + // Access the VERSION number of the retrieved item + long version = retrievedItem.Index; + } + else + { + Console.WriteLine("Compare-exchange item not found"); + } + ``` + + + + #### Get compare-exchange item that has a custom object value + + + + ```csharp + // Create a new compare-exchange item for the example: + // Put a new compare-exchange item with an object as the value + var employeeRole = new EmployeeRole + { + Role = "Salesperson", + Department = "Sales", + NumberOfSales = 12345 + }; + + var putCmpXchgOp = new PutCompareExchangeValueOperation( + "employees/1-A", employeeRole, 0); + + CompareExchangeResult putResult = store.Operations.Send(putCmpXchgOp); + + // Get the compare-exchange item: + // ============================== + + // Define the get compare-exchange operation, pass the unique item key + var getCmpXchgOp = new GetCompareExchangeValueOperation("employees/1-A"); + + // Execute the operation by passing it to Operations.Send + CompareExchangeValue retrievedItem = store.Operations.Send(getCmpXchgOp); + + if (retrievedItem != null) + { + // Access the VALUE of the retrieved item + var employeeDetails = retrievedItem.Value; + var objectType = employeeDetails.GetType(); // typeof(EmployeeRole) + var role = employeeDetails.Role; // "Salesperson" + var Dep = employeeDetails.Department; // "Sales" + var Sales = employeeDetails.NumberOfSales; // 12345 + + // Access the VERSION number of the retrieved item + long version = retrievedItem.Index; + } + else + { + Console.WriteLine("Compare-exchange item not found"); + } + ``` + + + ```csharp + // Create a new compare-exchange item for the example: + // Put a new compare-exchange item with an object as the value + var employeeRole = new EmployeeRole + { + Role = "Salesperson", + Department = "Sales", + NumberOfSales = 12345 + }; + + var putCmpXchgOp = new PutCompareExchangeValueOperation( + "employees/1-A", employeeRole, 0); + + CompareExchangeResult putResult = await store.Operations.SendAsync(putCmpXchgOp); + + // Get the compare-exchange item: + // ============================== + + // Define the get compare-exchange operation, pass the unique item key + var getCmpXchgOp = new GetCompareExchangeValueOperation("employees/1-A"); + + // Execute the operation by passing it to Operations.SendAsync + CompareExchangeValue retrievedItem = await store.Operations.SendAsync(getCmpXchgOp); + + if (retrievedItem != null) + { + // Access the VALUE of the retrieved item + var employeeDetails = retrievedItem.Value; + var objectType = employeeDetails.GetType(); // typeof(EmployeeRole) + var role = employeeDetails.Role; // "Salesperson" + var Dep = employeeDetails.Department; // "Sales" + var Sales = employeeDetails.NumberOfSales; // 12345 + + // Access the VERSION number of the retrieved item + long version = retrievedItem.Index; + } + else + { + Console.WriteLine("Compare-exchange item not found"); + } + ``` + + + ```csharp + public class EmployeeRole + { + public string Id { get; set; } + public string Department { get; set; } = ""; + public string Role { get; set; } = ""; + public int NumberOfSales { get; set; } + } + ``` + + + +--- + +## Syntax + +--- + +### `GetCompareExchangeValueOperation` +Get compare-exchange item using a store operation: + + +```csharp +public GetCompareExchangeValueOperation(string key); +``` + + +### `GetCompareExchangeValue` +Get compare-exchange item using cluster-wide session: + + +```csharp +CompareExchangeValue GetCompareExchangeValue(string key); +Task> GetCompareExchangeValueAsync(string key, + CancellationToken token = default); + +Lazy> GetCompareExchangeValue(string key); +Lazy>> GetCompareExchangeValueAsync(string key, + CancellationToken token = default); +``` + + +| Input parameter | Type | Description | +|-----------------|----------|-----------------------------------------------------------------| +| **key** | `string` | The unique identifier of the compare-exchange item to retrieve. | + +| The returned object: | Description | +|---------------------------|----------------------------------------------------------------| +| `CompareExchangeValue` | The compare-exchange item is returned.
Returns `null` if key doesn't exist. | + + +```csharp +public class CompareExchangeValue +{ + // The unique identifier of the compare-exchange item. + public string Key { get; } + + // The existing `value` of the returned compare-exchange item. + public T Value { get; set; } + + // The compare-exchange item's version. + public long Index { get; internal set; } + + // The existing `metadata` of the returned compare-exchange item. + public IMetadataDictionary Metadata; +} +``` + diff --git a/docs/compare-exchange/content/_get-cmpxchg-item-java.mdx b/docs/compare-exchange/content/_get-cmpxchg-item-java.mdx new file mode 100644 index 0000000000..3e8a562730 --- /dev/null +++ b/docs/compare-exchange/content/_get-cmpxchg-item-java.mdx @@ -0,0 +1,121 @@ +import Admonition from '@theme/Admonition'; +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +import CodeBlock from '@theme/CodeBlock'; + + + +* To retrieve an existing compare-exchange item using the **Client API**, + use either a store operation or a cluster-wide session - as described below. + A cluster-wide session also supports lazy retrieval. + +* To view existing compare-exchange items in the **Studio**, go to _Documents > Compare Exchange_, + as described in [Ways to manage compare-exchange items](../compare-exchange/overview#ways-to-create-and-manage-compare-exchange-items). + +* This article shows how to get a **single** compare-exchange item by its unique _key_. + To get **multiple** items at once, see [Get multiple compare-exchange items](../compare-exchange/get-cmpxchg-items). + +* In this article: + * [Get item using a **store operation**](../compare-exchange/get-cmpxchg-item#get-item-using-a-store-operation) + * [Get compare-exchange item that has a number value](../compare-exchange/get-cmpxchg-item#get-compare-exchange-item-that-has-a-number-value) + * [Get compare-exchange item that has a custom object value](../compare-exchange/get-cmpxchg-item#get-compare-exchange-item-that-has-a-custom-object-value) + * [Syntax](../compare-exchange/get-cmpxchg-item#syntax) + + + +--- + +## Get item using a store operation + +* You can retrieve compare-exchange items using a [store operation](../client-api/operations/what-are-operations). + Use the `GetCompareExchangeValueOperation` operation to get a compare-exchange item by its _key_. + +* If the specified key does not exist, the operation returns `null`. No exception is thrown. + +* Examples: + + #### Get compare-exchange item that has a number value + + + ```java + CompareExchangeValue readResult = + store.operations().send(new GetCompareExchangeValueOperation<>(Long.class, "nextClientId")); + + Long value = readResult.getValue(); + ``` + + + #### Get compare-exchange item that has a custom object value + + + ```java + CompareExchangeValue readResult = store.operations().send( + new GetCompareExchangeValueOperation<>(User.class, "AdminUser")); + + User admin = readResult.getValue(); + ``` + + +--- + +## Syntax + +--- + +### `GetCompareExchangeValueOperation` +Get compare-exchange item using a store operation: + + +```java +GetCompareExchangeValueOperation(Class clazz, String key); +``` + + + +| Input parameter | Type | Description | +|-----------------|----------|-----------------------------------------------------------------| +| **key** | `String` | The unique identifier of the compare-exchange item to retrieve. | + +| The returned object: | Description | +|---------------------------|----------------------------------------------------------------| +| `CompareExchangeValue` | The compare-exchange item is returned.
Returns `null` if key doesn't exist. | + + +```java +public class CompareExchangeValue { + private String key; // The unique identifier of the compare-exchange item. + private long index; // The compare-exchange item's version. + private T value; // The existing `value` of the returned compare-exchange item. + + public CompareExchangeValue(String key, long index, T value) { + this.key = key; + this.index = index; + this.value = value; + } + + public String getKey() { + return key; + } + + public void setKey(String key) { + this.key = key; + } + + public long getIndex() { + return index; + } + + public void setIndex(long index) { + this.index = index; + } + + public T getValue() { + return value; + } + + public void setValue(T value) { + this.value = value; + } +} +``` + diff --git a/docs/compare-exchange/content/_get-cmpxchg-item-nodejs.mdx b/docs/compare-exchange/content/_get-cmpxchg-item-nodejs.mdx new file mode 100644 index 0000000000..2ff0323223 --- /dev/null +++ b/docs/compare-exchange/content/_get-cmpxchg-item-nodejs.mdx @@ -0,0 +1,346 @@ +import Admonition from '@theme/Admonition'; +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +import CodeBlock from '@theme/CodeBlock'; + + + +* To retrieve an existing compare-exchange item using the **Client API**, + use either a store operation or a cluster-wide session - as described below. + A cluster-wide session also supports lazy retrieval. + +* To view existing compare-exchange items in the **Studio**, go to _Documents > Compare Exchange_, + as described in [Ways to manage compare-exchange items](../compare-exchange/overview#ways-to-create-and-manage-compare-exchange-items). + +* This article shows how to get a **single** compare-exchange item by its unique _key_. + To get **multiple** items at once, see [Get multiple compare-exchange items](../compare-exchange/get-cmpxchg-items). + +* In this article: + * [Get item using a **cluster-wide session**](../compare-exchange/get-cmpxchg-item#get-item-using-a-cluster-wide-session) + * [Get compare-exchange item](../compare-exchange/get-cmpxchg-item#get-compare-exchange-item) + * [Get compare-exchange item lazily](../compare-exchange/get-cmpxchg-item#get-compare-exchange-item-lazily) + * [Retrieved compare-exchange items are tracked by the session](../compare-exchange/get-cmpxchg-item#retrieved-compare-exchange-items-are-tracked-by-the-session) + * [Get item using a **store operation**](../compare-exchange/get-cmpxchg-item#get-item-using-a-store-operation) + * [Get compare-exchange item that has a number value and metadata](../compare-exchange/get-cmpxchg-item#get-compare-exchange-item-that-has-a-number-value-and-metadata) + * [Get compare-exchange item that has a custom object value](../compare-exchange/get-cmpxchg-item#get-compare-exchange-item-that-has-a-custom-object-value) + * [Syntax](../compare-exchange/get-cmpxchg-item#syntax) + + + +--- + +## Get item using a cluster-wide session + +* You can retrieve compare-exchange items using a [cluster-wide session](../client-api/session/cluster-transaction/overview#open-a-cluster-transaction). + The session must be opened in cluster-wide mode. + +* Use the `getCompareExchangeValue` advanced session method to get a compare-exchange item by its _key_. + If the specified key does not exist, the method returns `null`. No exception is thrown. + +* Once a compare-exchange item is retrieved using a cluster-wide session, the item is **tracked** by the session. + Repeating the same `getCompareExchangeValue` call with the same key does not send another request to the server, + the value is returned from the session's internal state. + To force a re-fetch from the server, call `session.advanced.clear()` first. + +* Examples: + + #### Get compare-exchange item + + + ```js + // First, let's create a compare-exchange item for the example, + // e.g. store a user's email as the key and the user document id as the value. + + // The session must be opened in cluster-wide mode. + const session = documentStore.openSession({ + transactionMode: "ClusterWide" + }); + + const itemToCreate = session.advanced.clusterTransaction.createCompareExchangeValue( + "user1-name@example.com", "users/1" // key, value + ); + + // Optionally, add some metadata: + itemToCreate.metadata["email-type"] = "work email"; + + await session.saveChanges(); + + // Get the compare-exchange item: + // ============================== + + const item = await session.advanced.clusterTransaction.getCompareExchangeValue("user1-name@example.com"); + + if (item) { + // Access the VALUE of the retrieved item + const userDocumentId = item.value; // "users/1" + + // Access the METADATA of the retrieved item + const emailType = item.metadata["email-type"]; // "work email" + + // Access the VERSION number of the retrieved item + const version = item.index; + } else { + console.log("Compare-exchange item not found"); + } + ``` + + + + #### Get compare-exchange item lazily + + + + ```js + // Create a compare-exchange item for the example: + const session = documentStore.openSession({ + transactionMode: "ClusterWide" + }); + session.advanced.clusterTransaction.createCompareExchangeValue( + "user1-name@example.com", { userDocumentId: "users/1" } + ); + await session.saveChanges(); + + // Get the compare-exchange item lazily: + // ===================================== + + const lazyItem = session.advanced.clusterTransaction.lazily + .getCompareExchangeValue("user1-name@example.com"); + + // Access the item: + const item = await lazyItem.getValue(); + + if (item) { + // Access the VALUE of the retrieved item + const userDocumentId = item.value; // { "userDocumentId": "users/1" } + + // Access the VERSION number of the retrieved item + const version = item.index; + } else { + console.log("Compare-exchange item not found"); + } + ``` + + + + #### Retrieved compare-exchange items are tracked by the session + + + + ```js + const session = documentStore.openSession({ + transactionMode: "ClusterWide" + }); + + // First retrieval — server call will happen + const item1 = await session.advanced.clusterTransaction.getCompareExchangeValue( + "user1-name@example.com"); + + // No server call — the item is returned from session tracking + const item2 = await session.advanced.clusterTransaction.getCompareExchangeValue( + "user1-name@example.com"); + + // Clear tracked entities and compare-exchange items + session.advanced.clear(); + + // Server call will happen again + const item3 = await session.advanced.clusterTransaction.getCompareExchangeValue( + "user1-name@example.com"); + ``` + + + +--- + +## Get item uaing a store operation + +* You can retrieve compare-exchange items using a [store operation](../client-api/operations/what-are-operations). + Use the `GetCompareExchangeValueOperation` operation to get a compare-exchange item by its _key_. + +* If the specified key does not exist, the operation returns `null`. No exception is thrown. + +* Examples: + + #### Get compare-exchange item that has a number value and metadata + + + + ```js + // First, let's create a new compare-exchange item for the example, + // e.g. store the number of sales made by an employee as the value + some metadata info: + const putCmpXchgOp = new PutCompareExchangeValueOperation("employees/1-A", 12345, 0, { + "Department": "Sales", + "Role": "Salesperson", + }); + const putResult = await documentStore.operations.send(putCmpXchgOp); + + // Get the compare-exchange item: + // ============================== + + // Define the get compare-exchange operation, pass the unique item key + const getCmpXchgOp = new GetCompareExchangeValueOperation("employees/1-A"); + + // Execute the operation by passing it to operations.send + const item = await documentStore.operations.send(getCmpXchgOp); + + if (item) { + // Access the VALUE of the retrieved item + const numberOfSales = item.value; // 12345 + + // Access the METADATA of the retrieved item + const employeeRole = item.metadata["Role"]; // "Salesperson" + + // Access the VERSION number of the retrieved item + const version = item.index; + } else { + console.log("Compare-exchange item not found"); + } + ``` + + + + #### Get compare-exchange item that has a custom object value + + + + ```js + // Put a new compare-exchange item with an object as the value + const employee = new EmployeeRole(); + employee.role = "Salesperson" + employee.department = "Sales"; + employee.numberOfSales = 12345; + + const putCmpXchgOp = new PutCompareExchangeValueOperation("employees/1-A", employee, 0); + const putResult = await documentStore.operations.send(putCmpXchgOp); + + // Get the compare-exchange item: + // ============================== + + // Define the get compare-exchange operation, pass the unique item key & the class type + const getCmpXchgOp = new GetCompareExchangeValueOperation("employees/1-A", EmployeeRole); + + // Execute the operation by passing it to operations.send + const item = await documentStore.operations.send(getCmpXchgOp); + + if (item) { + // Access the VALUE of the retrieved item + const employeeResult = item.value; + const employeeClass = employeeResult.constructor; // EmployeeRole + + const employeeRole = employeeResult.role; // Salesperson + const employeeDep = employeeResult.department; // Sales + const employeeSales = employeeResult.numberOfSales; // 12345 + + // Access the VERSION number of the retrieved item + const version = item.index; + } else { + console.log("Compare-exchange item not found"); + } + ``` + + + ```js + class EmployeeRole { + constructor( + id = null, + department = "", + role = "", + numberOfSales = 0 + + ) { + Object.assign(this, { + id, + department, + role, + numberOfSales + }); + } + } + ``` + + + +--- + +## Syntax + +--- + +### `GetCompareExchangeValueOperation` +Get compare-exchange item using a store operation: + + +```js +const getCmpXchgOp = new GetCompareExchangeValueOperation(key, clazz, materializeMetadata); +``` + + +| Parameter | Type | Description | +|-------------------------|-----------|-----------------------------------------------------------------------------------------------------------------| +| **key** | `string` | The unique identifier of the compare-exchange item to retrieve. | +| **clazz** | `object` | The class type of the item's value. | +| **materializeMetadata** | `boolean` | The metadata will be retrieved and available regardless of the value of this param. Used for internal purposes. | + +| Returned object | Description | +|------------------------|---------------------------------------------------------------------------------| +| `CompareExchangeValue` | The compare-exchange item is returned.
Returns `null` if key doesn't exist. | + +--- + +### `getCompareExchangeValue` +Get compare-exchange item using cluster-wide session: + + +```js +await session.advanced.clusterTransaction.getCompareExchangeValue(key); +``` + + +| Parameter | Type | Description | +|------------|----------|--------------------------------------------------| +| **key** | `string` | The key of the compare-exchange item to retrieve | + +| `getCompareExchangeValue` returns: | Description | +|---------------------------|---------------------------------------------------------------------------------| +| `CompareExchangeValue` | The compare-exchange item is returned.
Returns `null` if key doesn't exist. | + +--- + +### `lazily.getCompareExchangeValue` +Get compare-exchange item using cluster-wide session lazily: + + +```js +await session.advanced.clusterTransaction.lazily.getCompareExchangeValue(key); +``` + + +| Parameter | Type | Description | +|------------|----------|--------------------------------------------------| +| **key** | `string` | The key of the compare-exchange item to retrieve | + +| Return value - after calling `getValue()` | Description | +|---------------------------|---------------------------------------------------------------------------------| +| `CompareExchangeValue` | The compare-exchange item is returned.
Returns `null` if key doesn't exist. | + +--- + + +```js +// The CompareExchangeValue object: +// ================================ + +class CompareExchangeValue { + // The unique identifier of the compare-exchange item. + key; // string + + // The existing `value` of the returned compare-exchange item. + value; // object + + // The existing `metadata` of the returned compare-exchange item. + metadata; // object + + // The compare-exchange item's version. + index; // number +} +``` + diff --git a/docs/compare-exchange/content/_get-cmpxchg-item-php.mdx b/docs/compare-exchange/content/_get-cmpxchg-item-php.mdx new file mode 100644 index 0000000000..9615ea7e60 --- /dev/null +++ b/docs/compare-exchange/content/_get-cmpxchg-item-php.mdx @@ -0,0 +1,73 @@ +import Admonition from '@theme/Admonition'; +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +import CodeBlock from '@theme/CodeBlock'; + + + +* To retrieve an existing compare-exchange item using the **Client API**, + use either a store operation or a cluster-wide session - as described below. + A cluster-wide session also supports lazy retrieval. + +* To view existing compare-exchange items in the **Studio**, go to _Documents > Compare Exchange_, + as described in [Ways to manage compare-exchange items](../compare-exchange/overview#ways-to-create-and-manage-compare-exchange-items). + +* This article shows how to get a **single** compare-exchange item by its unique _key_. + To get **multiple** items at once, see [Get multiple compare-exchange items](../compare-exchange/get-cmpxchg-items). + +* In this article: + * [Get item using a **cluster-wide session**](../compare-exchange/get-cmpxchg-item#get-item-using-a-cluster-wide-session) + * [Syntax](../compare-exchange/get-cmpxchg-item#syntax) + + + +--- + +## Get item using a cluster-wide session + +* You can retrieve compare-exchange items using a [cluster-wide session](../client-api/session/cluster-transaction/overview#open-a-cluster-transaction). + The session must be opened in cluster-wide mode. + +* Use the `getCompareExchangeValue` advanced session method to get a compare-exchange item by its _key_. + If the specified key does not exist, the method returns `null`. No exception is thrown. + +--- + +## Syntax + +--- + +### `getCompareExchangeValue` +Get compare-exchange item using a cluster wide session: + + +```php +session.advanced.cluster_transaction.get_compare_exchange_value(key) +``` + + +| Input parameter | Type | Description | +|-----------------|----------|-----------------------------------------------------------------| +| **key** | `string` | The unique identifier of the compare-exchange item to retrieve. | + +| The returned object: | Description | +|---------------------------|----------------------------------------------------------------| +| `CompareExchangeValue` | The compare-exchange item is returned.
Returns `null` if key doesn't exist. | + + +### `lazily()->getCompareExchangeValue` +Get compare-exchange item using a cluster wide session lazily: + + +```php +$session->advanced()->clusterTransaction()->lazily()->getCompareExchangeValue(null, $key); +``` + + +| Input parameter | Type | Description | +|-----------------|----------|-----------------------------------------------------------------| +| **key** | `string` | The unique identifier of the compare-exchange item to retrieve. | + +| The returned object: | Description | +|--------------------------------|----------------------------------------------------------------| +| `Lazy>` | The compare-exchange item is returned.
Returns `None` if key doesn't exist. | diff --git a/docs/compare-exchange/content/_get-cmpxchg-item-python.mdx b/docs/compare-exchange/content/_get-cmpxchg-item-python.mdx new file mode 100644 index 0000000000..bfeccdb9d6 --- /dev/null +++ b/docs/compare-exchange/content/_get-cmpxchg-item-python.mdx @@ -0,0 +1,73 @@ +import Admonition from '@theme/Admonition'; +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +import CodeBlock from '@theme/CodeBlock'; + + + +* To retrieve an existing compare-exchange item using the **Client API**, + use either a store operation or a cluster-wide session - as described below. + A cluster-wide session also supports lazy retrieval. + +* To view existing compare-exchange items in the **Studio**, go to _Documents > Compare Exchange_, + as described in [Ways to manage compare-exchange items](../compare-exchange/overview#ways-to-create-and-manage-compare-exchange-items). + +* This article shows how to get a **single** compare-exchange item by its unique _key_. + To get **multiple** items at once, see [Get multiple compare-exchange items](../compare-exchange/get-cmpxchg-items). + +* In this article: + * [Get item using a **cluster-wide session**](../compare-exchange/get-cmpxchg-item#get-item-using-a-cluster-wide-session) + * [Syntax](../compare-exchange/get-cmpxchg-item#syntax) + + + +--- + +## Get item using a cluster-wide session + +* You can retrieve compare-exchange items using a [cluster-wide session](../client-api/session/cluster-transaction/overview#open-a-cluster-transaction). + The session must be opened in cluster-wide mode. + +* Use the `get_compare_exchange_value` advanced session method to get a compare-exchange item by its _key_. + If the specified key does not exist, the method returns `null`. No exception is thrown. + +--- + +## Syntax + +--- + +### `get_compare_exchange_value` +Get compare-exchange item using a cluster wide session: + + +```python +session.advanced.cluster_transaction.get_compare_exchange_value(key) +``` + + +| Input parameter | Type | Description | +|-----------------|-------|-----------------------------------------------------------------| +| **key** | `str` | The unique identifier of the compare-exchange item to retrieve. | + +| The returned object: | Description | +|---------------------------|----------------------------------------------------------------| +| `CompareExchangeValue` | The compare-exchange item is returned.
Returns `None` if key doesn't exist. | + + +### `lazily.get_compare_exchange_value` +Get compare-exchange item using a cluster wide session lazily: + + +```python +session.advanced.cluster_transaction.lazily.get_compare_exchange_value(key) +``` + + +| Input parameter | Type | Description | +|-----------------|-------|-----------------------------------------------------------------| +| **key** | `str` | The unique identifier of the compare-exchange item to retrieve. | + +| The returned object: | Description | +|---------------------------------|----------------------------------------------------------------| +| `Lazy[CompareExchangeValue[T]]` | The compare-exchange item is returned.
Returns `None` if key doesn't exist. | diff --git a/docs/compare-exchange/content/_get-cmpxchg-items-csharp.mdx b/docs/compare-exchange/content/_get-cmpxchg-items-csharp.mdx new file mode 100644 index 0000000000..c6845fc4b3 --- /dev/null +++ b/docs/compare-exchange/content/_get-cmpxchg-items-csharp.mdx @@ -0,0 +1,456 @@ +import Admonition from '@theme/Admonition'; +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +import CodeBlock from '@theme/CodeBlock'; + + + +* You can retrieve multiple existing compare-exchange items at once using the **Client API** by either: + * specifying a list of unique keys, or + * using a common key prefix + + Retrieval can be done using either a store operation or a cluster-wide session - as described below. + A cluster-wide session also supports lazy retrieval. + +* To view existing compare-exchange items in the **Studio**, go to _Documents > Compare Exchange_, + as described in [Ways to manage compare-exchange items](../compare-exchange/overview#ways-to-create-and-manage-compare-exchange-items). + +* This article shows how to get **multiple** compare-exchange items. + To get a **single** item, see [Get compare-exchange item](../compare-exchange/get-cmpxchg-item). + +* In this article: + * [Create sample compare-exchange items](../compare-exchange/get-cmpxchg-items#create-sample-compare-exchange-items) + * [Get compare-exchange items by list of keys](../compare-exchange/get-cmpxchg-items#get-compare-exchange-items-by-list-of-keys) + * [Get compare-exchange items by prefix](../compare-exchange/get-cmpxchg-items#get-compare-exchange-items-by-prefix) + * [Get compare-exchange items count](../compare-exchange/get-cmpxchg-items#get-compare-exchange-items-count) + * [Syntax](../compare-exchange/get-cmpxchg-items#syntax) + + + +--- + +## Create sample compare-exchange items + +Let’s create some sample compare-exchange items to use in the examples below. +To learn about ALL the available methods for creating a compare-exchange item, see [Create compare-exchange item](../compare-exchange/create-cmpxchg-items). + + +```csharp +store.Operations.Send( + new PutCompareExchangeValueOperation("employees/1", "someValue1", 0)); +store.Operations.Send( + new PutCompareExchangeValueOperation("employees/2", "someValue2", 0)); +store.Operations.Send( + new PutCompareExchangeValueOperation("employees/3", "someValue3", 0)); +store.Operations.Send( + new PutCompareExchangeValueOperation("customers/1", "someValue4", 0)); +store.Operations.Send( + new PutCompareExchangeValueOperation("customers/2", "someValue5", 0)); +``` + + +--- + +## Get compare-exchange items by list of keys + +* To retrieve multiple compare-exchange items by specifying a list of unique keys, use either: + + * **Cluster-wide session**: + Use the `GetCompareExchangeValues` session method – which also supports lazy retrieval. + If one of the specified keys does not exist, its corresponding entry in the returned dictionary will be `null`. + No exception is thrown. + + * **Store operation**: + Use the `GetCompareExchangeValuesOperation` store operation. + If one of the specified keys does not exist, the `.Value` property of the corresponding entry in the returned dictionary is `null`. + No exception is thrown. + +* Examples: + + #### Get compare-exchange items by list of keys + + + + ```csharp + // The session must be opened in cluster-wide mode. + // An `InvalidOperationException` is thrown if the session is not opened in cluster-wide mode. + using (var session = store.OpenSession( + new SessionOptions { TransactionMode = TransactionMode.ClusterWide })) + { + // Define the list of keys of the compare-exchange items to retrieve + var keys = new[] { "employees/1", "employees/2", "customers/2", "non-existing-key" }; + + // Call 'GetCompareExchangeValues', pass the list of keys + Dictionary> items = + session.Advanced.ClusterTransaction.GetCompareExchangeValues(keys); + + // Check results + Console.WriteLine($"Number of compare-exchange items retrieved: {items.Count}"); // Expecting 4 + + // Access a retrieved item - an existing key + if (items.TryGetValue("employees/1", out var item)) + { + string value = item.Value; // "someValue1" + long version = item.Index; + } + + // The entry of a non-existing key will be null + if (items.TryGetValue("non-existing-key", out var nonExistingItem)) + { + Console.WriteLine(nonExistingItem == null); // true + } + } + ``` + + + ```csharp + // The session must be opened in cluster-wide mode. + // An `InvalidOperationException` is thrown if the session is not opened in cluster-wide mode. + using (var asyncSession = store.OpenAsyncSession( + new SessionOptions { TransactionMode = TransactionMode.ClusterWide })) + { + // Define the list of keys of the compare-exchange items to retrieve + var keys = new[] { "employees/1", "employees/2", "customers/2", "non-existing-key" }; + + // Call 'GetCompareExchangeValues', pass the list of keys + Dictionary> items = + await asyncSession.Advanced.ClusterTransaction.GetCompareExchangeValuesAsync(keys); + + // Check results + Console.WriteLine($"Number of compare-exchange items retrieved: {items.Count}"); // Expecting 4 + + // Access a retrieved item - an existing key + if (items.TryGetValue("employees/1", out var item)) + { + string value = item.Value; // "someValue1" + long version = item.Index; + } + + // The entry of a non-existing key will be null + if (items.TryGetValue("non-existing-key", out var nonExistingItem)) + { + Console.WriteLine(nonExistingItem == null); // true + } + } + ``` + + + ```csharp + // Define the list of keys of the compare-exchange items to retrieve + var keys = new[] { "employees/1", "employees/2", "customers/2", "non-existing-key" }; + + // Define the get compare-exchange items operation, pass the unique item key + var getCmpXchgItemsOp = new GetCompareExchangeValuesOperation(keys); + + // Execute the operation by passing it to Operations.Send + Dictionary> items = store.Operations.Send(getCmpXchgItemsOp); + + // Check results + Console.WriteLine($"Number of compare-exchange items retrieved: {items.Count}"); // Expecting 4 + + // Access a retrieved item - an existing key + if (items.TryGetValue("employees/1", out var item)) + { + string value = item.Value; // "someValue1" + long version = item.Index; // e.g. 321 + } + + // The 'Value' of the non-existing key will be null + if (items.TryGetValue("non-existing-key", out var nonExistingItem)) + { + string value = nonExistingItem.Value; // null + long version = nonExistingItem.Index; // -1 + } + ``` + + + ```csharp + // Define the list of keys of the compare-exchange items to retrieve + var keys = new[] { "employees/1", "employees/2", "customers/2", "non-existing-key" }; + + // Define the get compare-exchange items operation, pass the unique item key + var getCmpXchgItemsOp = new GetCompareExchangeValuesOperation(keys); + + // Execute the operation by passing it to Operations.SendAsync + Dictionary> items = await + store.Operations.SendAsync(getCmpXchgItemsOp); + + // Check results + Console.WriteLine($"Number of compare-exchange items retrieved: {items.Count}"); // Expecting 4 + + // Access a retrieved item - an existing key + if (items.TryGetValue("employees/1", out var item)) + { + string value = item.Value; // "someValue1" + long version = item.Index; // e.g. 321 + } + + // The 'Value' of the non-existing key will be null + if (items.TryGetValue("non-existing-key", out var nonExistingItem)) + { + string value = nonExistingItem.Value; // null + long version = nonExistingItem.Index; // -1 + } + ``` + + + + #### Get compare-exchange items by list of keys - lazily + + A list of compare-exchange items can be retrieved lazily when working within a cluster-wide session. + + + + ```csharp + // The session must be opened in cluster-wide mode. + // An `InvalidOperationException` is thrown if the session is not opened in cluster-wide mode. + using (var session = store.OpenSession( + new SessionOptions { TransactionMode = TransactionMode.ClusterWide })) + { + // Define the list of keys of the compare-exchange items to retrieve + var keys = new[] { "employees/1", "employees/2", "customers/2", "non-existing-key" }; + + // Call 'Lazily.GetCompareExchangeValues', pass the list of keys + var lazyItems = + session.Advanced.ClusterTransaction.Lazily.GetCompareExchangeValues(keys); + + Dictionary> items = lazyItems.Value; + + // Check results + Console.WriteLine($"Number of compare-exchange items retrieved: {items.Count}"); // Expecting 4 + + // Access a retrieved item - an existing key + if (items.TryGetValue("employees/1", out var item)) + { + string value = item.Value; // "someValue1" + long version = item.Index; // e.g. 321 + } + + // The entry of a non-existing key will be null + if (items.TryGetValue("non-existing-key", out var nonExistingItem)) + { + Console.WriteLine(nonExistingItem == null); // true + } + } + ``` + + + ```csharp + // The session must be opened in cluster-wide mode. + // An `InvalidOperationException` is thrown if the session is not opened in cluster-wide mode. + using var asyncSession = store.OpenAsyncSession( + new SessionOptions { TransactionMode = TransactionMode.ClusterWide })) + { + // Define the list of keys of the compare-exchange items to retrieve + var keys = new[] { "employees/1", "employees/2", "customers/2", "non-existing-key" }; + + // Call 'Lazily.GetCompareExchangeValues', pass the list of keys + var lazyItems = asyncSession.Advanced.ClusterTransaction + .Lazily.GetCompareExchangeValuesAsync(keys); + + Dictionary> items = await lazyItems.Value; + + // Check results + Console.WriteLine($"Number of compare-exchange items retrieved: {items.Count}"); // Expecting 4 + + // Access a retrieved item - an existing key + if (items.TryGetValue("employees/1", out var item)) + { + string value = item.Value; // "someValue1" + long version = item.Index; + } + + // The entry of a non-existing key will be null + if (items.TryGetValue("non-existing-key", out var nonExistingItem)) + { + Console.WriteLine(nonExistingItem == null); // true + } + } + ``` + + + +--- + +## Get compare-exchange items by prefix + +* You can retrieve compare-exchange items whose keys start with a specific **prefix**. + You can also control the **maximum number of items** to return and the **starting position** for paging. + +* Examples: + + + + ```csharp + // The session must be opened in cluster-wide mode. + // An `InvalidOperationException` is thrown if the session is not opened in cluster-wide mode. + using var session = store.OpenSession( + new SessionOptions { TransactionMode = TransactionMode.ClusterWide })) + { + // Call 'GetCompareExchangeValues', pass: + // * startWith: The common key prefix + // * start: The start position (optional, default is 0) + // * pageSize: Max items to get (optional, default is 25) + Dictionary> items = + session.Advanced.ClusterTransaction.GetCompareExchangeValues( + startsWith: "employees", start: 0, pageSize: 10); + + // Results will include only compare-exchange items with keys that start with "employees" + Console.WriteLine($"Number of compare-exchange items with prefix 'employees': {items.Count}"); + // Should be 3 + } + ``` + + + ```csharp + // The session must be opened in cluster-wide mode. + // An `InvalidOperationException` is thrown if the session is not opened in cluster-wide mode. + using var asyncSession = store.OpenAsyncSession( + new SessionOptions { TransactionMode = TransactionMode.ClusterWide })) + { + // Call 'GetCompareExchangeValues', pass: + // * startWith: The common key prefix + // * start: The start position (optional, default is 0) + // * pageSize: Max items to get (optional, default is 25) + Dictionary> items = await + asyncSession.Advanced.ClusterTransaction.GetCompareExchangeValuesAsync( + startsWith: "employees", start: 0, pageSize: 10); + + // Results will include only compare-exchange items with keys that start with "employees" + Console.WriteLine($"Number of compare-exchange items with prefix 'employees': {items.Count}"); + // Should be 3 + } + ``` + + + ```csharp + // Define the get compare-exchange items operation, pass: + // * startWith: The common key prefix + // * start: The start position (optional, default is 0) + // * pageSize: Max items to get (optional, default is int.MaxValue) + var getCmpXchgItemsOp = new GetCompareExchangeValuesOperation( + startWith: "employees", start: 0, pageSize: 10); + + // Execute the operation by passing it to Operations.Send + Dictionary> items = store.Operations.Send(getCmpXchgItemsOp); + + // Results will include only compare-exchange items with keys that start with "employees" + Debug.Assert(items.Count == 3); // There are 3 keys with the "employees" prefix + ``` + + + ```csharp + // Define the get compare-exchange items operation, pass: + // * startWith: The common key prefix + // * start: The start position (optional, default is 0) + // * pageSize: Max items to get (optional, default is int.MaxValue) + var getCmpXchgItemsOp = new GetCompareExchangeValuesOperation( + startWith: "employees", start: 0, pageSize: 10); + + // Execute the operation by passing it to Operations.SendAsync + Dictionary> items = await + store.Operations.SendAsync(getCmpXchgItemsOp); + + // Results will include only compare-exchange items with keys that start with "employees" + Debug.Assert(items.Count == 3); // There are 3 keys with the "employees" prefix + ``` + + + +--- + +## Get compare-exchange items count + +Use `GetDetailedStatisticsOperation` to get the total number of existing compare-exchange items. +This operation does not retrieve any actual items, only the total count. + + + +```csharp +var stats = store.Maintenance.Send(new GetDetailedStatisticsOperation()); +var itemsCount = stats.CountOfCompareExchange; +``` + + +```csharp +var stats = await store.Maintenance.SendAsync(new GetDetailedStatisticsOperation()); +var itemsCount = stats.CountOfCompareExchange; +``` + + + +--- + +## Syntax + +--- + +### `GetCompareExchangeValuesOperation` +Get multiple compare-exchange items using a store operation: + + +```csharp +// Available overloads: +GetCompareExchangeValuesOperation(string[] keys); +GetCompareExchangeValuesOperation(string startWith, int? start = null, int? pageSize = null) +``` + + +| Parameter | Type | Description | +|---------------|------------|-------------------------------------------------------| +| **keys** | `string[]` | Keys of the compare-exchange items to retrieve. | +| **startWith** | `string` | The common key prefix of the items to retrieve. | +| **start** | `int?` | The number of items that should be skipped. Default is `0` | +| **pageSize** | `int?` | The maximum number of values that will be retrieved. Default is `int.MaxValue` | + +| Returned object | Description | +|-----------------------------------------------|---------------------------------------------------| +| `Dictionary>` | A Dictionary with a compare-exchange item per key | + +--- + +### `GetCompareExchangeValues` +Get multiple compare-exchange items using cluster-wide session + + +```csharp +GetCompareExchangeValues(string[] keys); +GetCompareExchangeValues(string startsWith, int start = 0, int pageSize = 25); +``` + + +| Parameter | Type | Description | +|---------------|------------|-------------------------------------------------------| +| **keys** | `string[]` | Keys of the compare-exchange items to retrieve. | +| **startWith** | `string` | The common key prefix of the items to retrieve. | +| **start** | `int?` | The number of items that should be skipped. Default is `0` | +| **pageSize** | `int?` | The maximum number of values that will be retrieved. Default is `25` | + + +| Returned object | Description | +|-----------------------------------------------|---------------------------------------------------| +| `Dictionary>` | A Dictionary with a compare-exchange item per key | + +--- + + +```csharp +// The CompareExchangeValue object: +// ================================ + +public class CompareExchangeValue +{ + // The unique identifier of the compare-exchange item. + public string Key { get; } + + // The existing `value` of the returned compare-exchange item. + public T Value { get; set; } + + // The compare-exchange item's version. + public long Index { get; internal set; } + + // The existing `metadata` of the returned compare-exchange item. + public IMetadataDictionary Metadata; +} +``` + diff --git a/docs/compare-exchange/content/_get-cmpxchg-items-java.mdx b/docs/compare-exchange/content/_get-cmpxchg-items-java.mdx new file mode 100644 index 0000000000..a7db2144a4 --- /dev/null +++ b/docs/compare-exchange/content/_get-cmpxchg-items-java.mdx @@ -0,0 +1,98 @@ +import Admonition from '@theme/Admonition'; +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +import CodeBlock from '@theme/CodeBlock'; + + + +* You can retrieve multiple existing compare-exchange items at once using the **Client API** by either: + * specifying a list of unique keys, or + * using a common key prefix + + Retrieval can be done using either a store operation or a cluster-wide session - as described below. + A cluster-wide session also supports lazy retrieval. + +* To view existing compare-exchange items in the **Studio**, go to _Documents > Compare Exchange_, + as described in [Ways to manage compare-exchange items](../compare-exchange/overview#ways-to-create-and-manage-compare-exchange-items). + +* This article shows how to get **multiple** compare-exchange items. + To get a **single** item, see [Get compare-exchange item](../compare-exchange/get-cmpxchg-item). + +* In this article: + * [Get compare-exchange items by list of keys](../compare-exchange/get-cmpxchg-items#get-compare-exchange-items-by-list-of-keys) + * [Get compare-exchange items by prefix](../compare-exchange/get-cmpxchg-items#get-compare-exchange-items-by-prefix) + * [Syntax](../compare-exchange/get-cmpxchg-items#syntax) + + + +--- + +## Get compare-exchange items by list of keys + +* Example: + + + ```java + Dictionary> compareExchangeValues + = store.Operations.Send( + new GetCompareExchangeValuesOperation(new[] { "Key-1", "Key-2" })); + ``` + + +--- + +## Get compare-exchange items by prefix + +* You can retrieve compare-exchange items whose keys start with a specific **prefix**. + You can also control the **maximum number of items** to return and the **starting position** for paging. + +* Example: + + + ```java + // Get values for keys that have the common prefix 'users' + // Retrieve maximum 20 entries + Dictionary> compareExchangeValues + = store.Operations.Send(new GetCompareExchangeValuesOperation("users", 0, 20)); + ``` + + +--- + +## Syntax + +--- + +### `GetCompareExchangeValuesOperation` +Get multiple compare-exchange items using a store operation: + + +```java +public GetCompareExchangeValuesOperation(Class clazz, String[] keys, boolean materializeMetadata) +public GetCompareExchangeValuesOperation(Class clazz, String startWith, Integer start, Integer pageSize) +``` + + +| Parameter | Type | Description | +|---------------|------------|-------------------------------------------------------| +| **keys** | `String[]` | Keys of the compare-exchange items to retrieve. | +| **startWith** | `String` | The common key prefix of the items to retrieve. | +| **start** | `Integer` | The number of items that should be skipped. Default is `0` | +| **pageSize** | `Integer` | The maximum number of values that will be retrieved. Default is `int.MaxValue` | + +| Returned object | Description | +|----------------------------------------|---------------------------------------------------| +| `Map>` | A Dictionary with a compare-exchange item per key | + +--- + + +```java +public class CompareExchangeValue +{ + public readonly string Key; + public readonly T Value; + public readonly long Index; +} +``` + diff --git a/docs/compare-exchange/content/_get-cmpxchg-items-nodejs.mdx b/docs/compare-exchange/content/_get-cmpxchg-items-nodejs.mdx new file mode 100644 index 0000000000..1d202a7b9c --- /dev/null +++ b/docs/compare-exchange/content/_get-cmpxchg-items-nodejs.mdx @@ -0,0 +1,302 @@ +import Admonition from '@theme/Admonition'; +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +import CodeBlock from '@theme/CodeBlock'; + + + +* You can retrieve multiple existing compare-exchange items at once using the **Client API** by either: + * specifying a list of unique keys, or + * using a common key prefix + + Retrieval can be done using either a store operation or a cluster-wide session - as described below. + A cluster-wide session also supports lazy retrieval. + +* To view existing compare-exchange items in the **Studio**, go to _Documents > Compare Exchange_, + as described in [Ways to manage compare-exchange items](../compare-exchange/overview#ways-to-create-and-manage-compare-exchange-items). + +* This article shows how to get **multiple** compare-exchange items. + To get a **single** item, see [Get compare-exchange item](../compare-exchange/get-cmpxchg-item). + +* In this article: + * [Create sample compare-exchange items](../compare-exchange/get-cmpxchg-items#create-sample-compare-exchange-items) + * [Get compare-exchange items by list of keys](../compare-exchange/get-cmpxchg-items#get-compare-exchange-items-by-list-of-keys) + * [Get compare-exchange items by prefix](../compare-exchange/get-cmpxchg-items#get-compare-exchange-items-by-prefix) + * [Get compare-exchange items count](../compare-exchange/get-cmpxchg-items#get-compare-exchange-items-count) + * [Syntax](../compare-exchange/get-cmpxchg-items#syntax) + + + +--- + +## Create sample compare-exchange items + +Let’s create some sample compare-exchange items to use in the examples below. +To learn about ALL the available methods for creating a compare-exchange item, see [Create compare-exchange item](../compare-exchange/create-cmpxchg-items). + + +```js +await documentStore.operations.send( + new PutCompareExchangeValueOperation("employees/1", "someValue1", 0)); +await documentStore.operations.send( + new PutCompareExchangeValueOperation("employees/2", "someValue2", 0)); +await documentStore.operations.send( + new PutCompareExchangeValueOperation("employees/3", "someValue3", 0)); +await documentStore.operations.send( + new PutCompareExchangeValueOperation("customers/1", "someValue4", 0)); +await documentStore.operations.send( + new PutCompareExchangeValueOperation("customers/2", "someValue5", 0)); +``` + + +--- + +## Get compare-exchange items by list of keys + +* To retrieve multiple compare-exchange items by specifying a list of unique keys, use either: + * `GetCompareExchangeValuesOperation` store operation, or + * `getCompareExchangeValues` method of a cluster-wide session - which also supports lazy retrieval. + +* If one of the specified keys does not exist, its corresponding entry in the retrieved items will be `null`. + No exception is thrown. + +* Examples: + + #### Get compare-exchange items by list of keys + + + + ```js + // The session must be opened in cluster-wide mode. + // An `InvalidOperationException` is thrown if the session is not opened in cluster-wide mode. + const session = documentStore.openSession({ + transactionMode: "ClusterWide" + }); + + // Define the list of keys of the compare-exchange items to retrieve + const keys = ["employees/1", "employees/2", "customers/2", "non-existing-key"]; + + // Call 'getCompareExchangeValues', pass the list of keys + const items = await session.advanced.clusterTransaction.getCompareExchangeValues(keys); + + // Check results + console.log( + `Number of compare-exchange items retrieved: ${Object.keys(items).length}` + ); // Expecting 4 + + // Access the retrieved items + const value = items["employees/1"].value; + const version = items["employees/1"].index; + + // A non-existing item returns null + const nonExistingItem = items["non-existing-key"]; // null + ``` + + + ```js + // Define the list of keys of the compare-exchange items to retrieve + const keys = ["employees/1", "employees/2", "customers/2", "non-existing-key"]; + + // Define the get operation, pass the list of keys + const getCmpXchgOp = new GetCompareExchangeValuesOperation({keys}); + + // Execute the operation by passing it to operations.send + const items = await documentStore.operations.send(getCmpXchgOp); + + // Check results + console.log( + `Number of compare-exchange items retrieved: ${Object.keys(items).length}` + ); // Expecting 4 + + // Access the retrieved items + const value = items["employees/1"].value; + const version = items["employees/1"].index; + + // The value of a non-existing item returns null + const nonExistingItem = items["non-existing-key"].value; // null + ``` + + + + #### Get compare-exchange items by list of keys - lazily + + A list of compare-exchange items can be retrieved lazily when working within a cluster-wide session. + + + + ```js + // The session must be opened in cluster-wide mode. + // An `InvalidOperationException` is thrown if the session is not opened in cluster-wide mode. + const session = documentStore.openSession({ + transactionMode: "ClusterWide" + }); + + // Define the list of keys of the compare-exchange items to retrieve + const keys = ["employees/1", "employees/2", "customers/2", "non-existing-key"]; + + // Call 'lazily.getCompareExchangeValues', pass the list of keys + const lazyItems = await session.advanced.clusterTransaction.lazily.getCompareExchangeValues(keys); + + // Execute the lazy operation to get the actual items + const items = await lazyItems.getValue(); + + // Check results + console.log( + `Number of compare-exchange items retrieved: ${Object.keys(items).length}` + ); // Expecting 4 + + // Access the retrieved items + const value = items["employees/1"].value; + const version = items["employees/1"].index; + + // A non-existing item returns null + const nonExistingItem = items["non-existing-key"]; // null + ``` + + + +--- + +## Get compare-exchange items by prefix + +* You can retrieve compare-exchange items whose keys start with a specific **prefix**. + You can also control the **maximum number of items** to return and the **starting position** for paging. + +* Example: + + + + ```js + // Define the get compare-exchange operation, specify: + // * startWith: The common key prefix + // * start: The start position (this is optional, default is 0) + // * pageSize: Max items to get (this is optional, default is int.MaxValue) + const getCmpXchgOp = new GetCompareExchangeValuesOperation({ + startWith: "employees", + start: 0, + pageSize: 10 + }); + + // Execute the operation by passing it to operations.send + const items = await documentStore.operations.send(getCmpXchgOp); + + // Results will include only compare-exchange items with keys that start with "employees" + console.log( + `Number of compare-exchange items with prefix 'employees': ${Object.keys(items).length}` + ); // Should be 3 + ``` + + + +--- + +## Get compare-exchange items count + +Use `GetDetailedStatisticsOperation` to get the total number of existing compare-exchange items. +This operation does not retrieve any actual items, only the total count. + + + +```js +const stats = await documentStore.maintenance.send(new GetDetailedStatisticsOperation()); +const itemsCount = stats.countOfCompareExchange; +``` + + + +--- + +## Syntax + +--- + +### `GetCompareExchangeValuesOperation` +Get multiple compare-exchange items using a store operation: + + +```js +const getCmpXchgOp = new GetCompareExchangeValuesOperation(parameters); +``` + + + +```js +// the parameters object: +{ + // Keys of the items to retrieve + keys?; // string[] + + // The common key prefix of the items to retrieve + startWith?; // string + + // The number of items that should be skipped + start?; // number + + // The maximum number of values that will be retrieved + pageSize?; // number + + // When the item's value is a class, you can specify its type in this parameter + clazz?; // object + + // The metadata will be retrieved and available regardless of the value of this param. + // Used for internal purposes. + materializeMetadata?; +} +``` + + +| Returned object | Description | +|----------------------------------------|---------------------------------------------------| +| `Record` | A Dictionary with a compare-exchange item per key | + +--- + +### `getCompareExchangeValues` +Get multiple compare-exchange items using cluster-wide session + + +```js +await session.advanced.clusterTransaction.getCompareExchangeValues(keys); +``` + +### `lazily.getCompareExchangeValue` +Get compare-exchange item using cluster-wide session lazily + + +```js +await session.advanced.clusterTransaction.lazily.getCompareExchangeValues(keys); +``` + + + + +| Parameter | Type | Description | +|-----------|------------|-------------------------------------------------| +| **keys** | `string[]` | Keys of the compare-exchange items to retrieve | + +| Returned object | Description | +|---------------------------|-----------------------------------------------------------------| +| `Record` | A Dictionary with a compare-exchange item per key | + +--- + + +```js +// The CompareExchangeValue object: +// ================================ + +class CompareExchangeValue { + // The unique identifier of the compare-exchange item. + key; // string + + // The existing `value` of the returned compare-exchange item. + value; // object + + // The existing `metadata` of the returned compare-exchange item. + metadata; // object + + // The compare-exchange item's version. + index; // number +} +``` + diff --git a/docs/compare-exchange/content/_get-cmpxchg-items-php.mdx b/docs/compare-exchange/content/_get-cmpxchg-items-php.mdx new file mode 100644 index 0000000000..9500a8f0df --- /dev/null +++ b/docs/compare-exchange/content/_get-cmpxchg-items-php.mdx @@ -0,0 +1,55 @@ +import Admonition from '@theme/Admonition'; +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +import CodeBlock from '@theme/CodeBlock'; + + + +* You can retrieve multiple existing compare-exchange items at once using the **Client API** by either: + * specifying a list of unique keys, or + * using a common key prefix + + Retrieval can be done using either a store operation or a cluster-wide session - as described below. + A cluster-wide session also supports lazy retrieval. + +* To view existing compare-exchange items in the **Studio**, go to _Documents > Compare Exchange_, + as described in [Ways to manage compare-exchange items](../compare-exchange/overview#ways-to-create-and-manage-compare-exchange-items). + +* This article shows how to get **multiple** compare-exchange items. + To get a **single** item, see [Get compare-exchange item](../compare-exchange/get-cmpxchg-item). + +* In this article: + * [Syntax](../compare-exchange/get-cmpxchg-items#syntax) + + + +--- + +## Syntax + +--- + +### `getCompareExchangeValues` + + +```php +// Get compare-exchange item using a cluster wide session: +$session->advanced()->clusterTransaction()->getCompareExchangeValues(null, $keys); +``` + + + +```php +// Get compare-exchange item using a cluster wide session - lazily: +$session->advanced()->clusterTransaction()->lazily()->getCompareExchangeValues(null, $keys); +``` + + +| Parameter | Type | Description | +|---------------|------------|-------------------------------------------------------| +| **keys** | `string[]` | Keys of the compare-exchange items to retrieve. | + +| Returned object | Description | +|-----------------------------------------------------|-------------------------------------------------------------| +| `Dictionary>` | If a key doesn't exists the associate value will be `null`. | +| `Lazy>>` | If a key doesn't exists the associate value will be `null`. | diff --git a/docs/compare-exchange/content/_get-cmpxchg-items-python.mdx b/docs/compare-exchange/content/_get-cmpxchg-items-python.mdx new file mode 100644 index 0000000000..63cb6a2e02 --- /dev/null +++ b/docs/compare-exchange/content/_get-cmpxchg-items-python.mdx @@ -0,0 +1,55 @@ +import Admonition from '@theme/Admonition'; +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +import CodeBlock from '@theme/CodeBlock'; + + + +* You can retrieve multiple existing compare-exchange items at once using the **Client API** by either: + * specifying a list of unique keys, or + * using a common key prefix + + Retrieval can be done using either a store operation or a cluster-wide session - as described below. + A cluster-wide session also supports lazy retrieval. + +* To view existing compare-exchange items in the **Studio**, go to _Documents > Compare Exchange_, + as described in [Ways to manage compare-exchange items](../compare-exchange/overview#ways-to-create-and-manage-compare-exchange-items). + +* This article shows how to get **multiple** compare-exchange items. + To get a **single** item, see [Get compare-exchange item](../compare-exchange/get-cmpxchg-item). + +* In this article: + * [Syntax](../compare-exchange/get-cmpxchg-items#syntax) + + + +--- + +## Syntax + +--- + +### `get_compare_exchange_values` + + +```python +# Get compare-exchange item using a cluster wide session: +session.advanced.cluster_transaction.get_compare_exchange_values(keys) +``` + + + +```python +# Get compare-exchange item using a cluster wide session - lazily: +session.advanced.cluster_transaction.lazily.get_compare_exchange_values(keys) +``` + + +| Parameter | Type | Description | +|---------------|-------------|-------------------------------------------------| +| **keys** | `List[str]` | Keys of the compare-exchange items to retrieve. | + +| Returned object | Description | +|--------------------------------------------|-----------------| +| `Dict[str, CompareExchangeValue[T]]` | A Dictionary with a compare-exchange item per key.
If a key doesn't exist the value associated with it will be `None`. | +| `Lazy[Dict[str, CompareExchangeValue[T]]]` | A Dictionary with a compare-exchange item per key.
If a key doesn't exist the value associated with it will be `None`. | diff --git a/docs/compare-exchange/content/_include-compare-exchange-items-csharp.mdx b/docs/compare-exchange/content/_include-compare-exchange-items-csharp.mdx new file mode 100644 index 0000000000..d9dc01bd6f --- /dev/null +++ b/docs/compare-exchange/content/_include-compare-exchange-items-csharp.mdx @@ -0,0 +1,681 @@ +import Admonition from '@theme/Admonition'; +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +import CodeBlock from '@theme/CodeBlock'; + + + +* Compare-exchange items can be included when [loading entities](../client-api/session/loading-entities) + or when making [queries](../client-api/session/querying/how-to-query). + +* The Session [tracks](../client-api/session/what-is-a-session-and-how-does-it-work) the included compare-exchange items, + which means their values can be accessed later in the same session without making additional requests to the server. + +* In this page: + * [Sample data](../compare-exchange/include-cmpxchg-items#sample-data) + * [Include compare-exchange items when loading](../compare-exchange/include-cmpxchg-items#include-compare-exchange-items-when-loading) + * [Include compare-exchange items when querying](../compare-exchange/include-cmpxchg-items#include-compare-exchange-items-when-querying) + * [Syntax](../compare-exchange/include-cmpxchg-items#syntax) + + + +--- + +## Sample data + +The examples in this article are based on the following **sample data**: +To learn about ALL the available methods for creating a compare-exchange item, see [Create compare-exchange item](../compare-exchange/create-cmpxchg-items). + + + +```csharp +using (var session = store.OpenSession()) +{ + // Create some company documents: + // ============================== + + var company1 = new Company + { + Id = "companies/1", + Name = "Apple", + Supplier = "suppliers/1", + Workers = new[] { "employees/1", "employees/2" } + }; + + var company2 = new Company + { + Id = "companies/2", + Name = "Google", + Supplier = "suppliers/2", + Workers = new[] { "employees/3", "employees/4" } + }; + + var company3 = new Company + { + Id = "companies/3", + Name = "Microsoft", + Supplier = "suppliers/3", + Workers = new[] { "employees/6", "employees/5" } + }; + + session.Store(company1); + session.Store(company2); + session.Store(company3); + + session.SaveChanges(); +} +``` + + +```csharp +using (var session = store.OpenSession(new SessionOptions +{ + TransactionMode = TransactionMode.ClusterWide +})) +{ + // Create some compare-exchange items: + // =================================== + + session.Advanced.ClusterTransaction.CreateCompareExchangeValue( + "employees/1", "content for employee 1 .."); + session.Advanced.ClusterTransaction.CreateCompareExchangeValue( + "employees/2", "content for employee 2 .."); + session.Advanced.ClusterTransaction.CreateCompareExchangeValue( + "employees/3", "content for employee 3 .."); + + session.Advanced.ClusterTransaction.CreateCompareExchangeValue( + "suppliers/1", "content for supplier 1 .."); + session.Advanced.ClusterTransaction.CreateCompareExchangeValue( + "suppliers/2", "content for supplier 2 .."); + session.Advanced.ClusterTransaction.CreateCompareExchangeValue( + "suppliers/3", "content for supplier 3 .."); + + session.SaveChanges(); +} +``` + + +```csharp +public class Company +{ + public string Id { get; set; } + public string Name { get; set; } + public string Supplier { get; set; } + public string[] Workers { get; set; } + + public Company() { } + + public Company(string id, string name, string supplier, string[] workers) + { + Id = id; + Name = name; + Supplier = supplier; + Workers = workers; + } +} +``` + + + +--- + +## Include compare-exchange items when loading + + + +**Include single item**: + + + +```csharp +// Open a session with cluster-wide mode to enable calling 'IncludeCompareExchangeValue' +using (var session = store.OpenSession(new SessionOptions +{ + TransactionMode = TransactionMode.ClusterWide +})) +{ + // Load a company document + include a CmpXchg item: + // ================================================= + + var company1 = session.Load("companies/1", includes => + // Call 'IncludeCompareExchangeValue' + // "Supplier" is the document property that holds the CmpXchg key to include + includes.IncludeCompareExchangeValue(c => c.Supplier)); + + // Calling 'Load' has triggered a server call + var numberOfRequests = session.Advanced.NumberOfRequests; + Console.WriteLine($"Number of requests made: {numberOfRequests}"); // Should be 1 + + // Access the included CmpXchg item: + // ================================= + + // Call 'GetCompareExchangeValue' to access the content of the included CmpXchg item. + // Pass the CmpXchg item KEY. This will NOT trigger another server call. + var item = session.Advanced.ClusterTransaction + .GetCompareExchangeValue(company1.Supplier); + + // You can check that no further server calls were made + Console.WriteLine(session.Advanced.NumberOfRequests == numberOfRequests); // Should be true + + // The CmpXchg item value is available + var value = item.Value; +} +``` + + +```csharp +// Open a session with cluster-wide mode to enable calling 'IncludeCompareExchangeValue' +using (var asyncSession = store.OpenAsyncSession(new SessionOptions +{ + TransactionMode = TransactionMode.ClusterWide +})) +{ + // Load a company document + include a CmpXchg item: + // ================================================= + + var company1 = await asyncSession.LoadAsync("companies/1", includes => + // Call 'IncludeCompareExchangeValue' + // "Supplier" is the document property that holds the CmpXchg key to include + includes.IncludeCompareExchangeValue(c => c.Supplier)); + + // Calling 'LoadAsync' has triggered a server call + var numberOfRequests = asyncSession.Advanced.NumberOfRequests; + Console.WriteLine($"Number of requests made: {numberOfRequests}"); // Should be 1 + + // Access the included CmpXchg item: + // ================================= + + // Call 'GetCompareExchangeValue' to access the content of the included CmpXchg item, + // pass the CmpXchg item KEY. This will NOT trigger another server call. + var item = await asyncSession.Advanced.ClusterTransaction + .GetCompareExchangeValueAsync(company1.Supplier); + + // You can check that no further server calls were made + Console.WriteLine(session.Advanced.NumberOfRequests == numberOfRequests); // Should be true + + // The CmpXchg item value is available + var value = item.Value; +} +``` + + + + + + + +**Include multiple items**: + + + +```csharp +// Open a session with cluster-wide mode +using (var session = store.OpenSession(new SessionOptions +{ + TransactionMode = TransactionMode.ClusterWide +})) +{ + // Load a company document + include multiple CmpXchg items: + // ========================================================= + + var company1 = session.Load("companies/1", includes => + // Call 'IncludeCompareExchangeValue' + // "Workers" is the document property that holds the list of keys to include + includes.IncludeCompareExchangeValue(c => c.Workers)); + + var numberOfRequests = session.Advanced.NumberOfRequests; + Console.WriteLine($"Number of requests made: {numberOfRequests}"); // Should be 1 + + // Access the included CmpXchg items: + // ================================== + + // Call 'GetCompareExchangeValues' to access the content of the included CmpXchg items. + // Pass the list of KEYS. This will NOT trigger another server call. + var items = session.Advanced.ClusterTransaction + .GetCompareExchangeValues(company1.Workers); + + // You can check that no further server calls were made + Console.WriteLine(session.Advanced.NumberOfRequests == numberOfRequests); // Should be true + + // The value of each item is available + var value1 = items["employees/1"].Value; + var value2 = items["employees/2"].Value; +} +``` + + +```csharp +// Open a session with cluster-wide mode +using (var asyncSession = store.OpenAsyncSession(new SessionOptions +{ + TransactionMode = TransactionMode.ClusterWide +})) +{ + // Load a company document + include multiple CmpXchg items: + // ========================================================= + + var company1 = session.Load("companies/1", includes => + // Call 'IncludeCompareExchangeValue' + // "Workers" is the document property that holds the list of keys to include + includes.IncludeCompareExchangeValue(c => c.Workers)); + + var numberOfRequests = asyncSession.Advanced.NumberOfRequests; + Console.WriteLine($"Number of requests made: {numberOfRequests}"); // Should be 1 + + // Access the included CmpXchg items: + // ================================== + + // Call 'GetCompareExchangeValues' to access the content of the included CmpXchg items. + // Pass the list of KEYS. This will NOT trigger another server call. + var items = await asyncSession.Advanced.ClusterTransaction + .GetCompareExchangeValuesAsync(company1.Workers); + + // You can check that no further server calls were made + Console.WriteLine(session.Advanced.NumberOfRequests == numberOfRequests); // Should be true + + // The value of each item is available + var value1 = items["employees/1"].Value; + var value2 = items["employees/2"].Value; +} +``` + + + + + +--- + +## Include compare-exchange items when querying + + + +**Dynamic query**: + + + +```csharp +// Open a session with cluster-wide mode to enable calling 'IncludeCompareExchangeValue' +using (var session = store.OpenSession(new SessionOptions +{ + TransactionMode = TransactionMode.ClusterWide +})) +{ + // Make a dynamic query + include CmpXchg items: + // ============================================= + + var companies = session.Query() + // Call 'Include' with 'IncludeCompareExchangeValue' + // pass the PATH of the document property that contains the key of the CmpXchg item to include + .Include(x => x.IncludeCompareExchangeValue(c => c.Supplier)) + .ToList(); + + // Making the query has triggered a server call + var numberOfRequests = session.Advanced.NumberOfRequests; + Console.WriteLine($"Number of requests made: {numberOfRequests}"); // Should be 1 + + // Access the included CmpXchg items: + // ================================== + + var cmpXchgItems = new List>(); + + foreach (var company in companies) + { + // Call 'GetCompareExchangeValue' to access the included CmpXchg item. + // Pass the KEY. This will NOT trigger another server call. + var item = session.Advanced.ClusterTransaction + .GetCompareExchangeValue(company.Supplier); + + cmpXchgItems.Add(item); + } + + // You can check that no further server calls were made + Console.WriteLine(session.Advanced.NumberOfRequests == numberOfRequests); // Should be true +} +``` + + +```csharp +// Open a session with cluster-wide mode to enable calling 'IncludeCompareExchangeValue' +using (var asyncSession = store.OpenAsyncSession(new SessionOptions +{ + TransactionMode = TransactionMode.ClusterWide +})) +{ + // Make a dynamic query + include CmpXchg items: + // ============================================= + + var companies = await asyncSession.Query() + // Call 'Include' with 'IncludeCompareExchangeValue' + // pass the PATH of the document property that contains the key of the CmpXchg item to include + .Include(x => x.IncludeCompareExchangeValue(c => c.Supplier)) + .ToListAsync(); + + // Making the query has triggered a server call + var numberOfRequests = asyncSession.Advanced.NumberOfRequests; + Console.WriteLine($"Number of requests made: {numberOfRequests}"); // Should be 1 + + var cmpXchgItems = new List>(); + + foreach (var company in companies) + { + // Call 'GetCompareExchangeValue' to access the included CmpXchg item. + // Pass the KEY. This will NOT trigger another server call. + var item = await asyncSession.Advanced.ClusterTransaction + .GetCompareExchangeValueAsync(company.Supplier); + + cmpXchgItems.Add(item); + } + + // You can check that no further server calls were made + Console.WriteLine(session.Advanced.NumberOfRequests == numberOfRequests); // Should be true +} +``` + + +```csharp +// Open a session with cluster-wide mode to enable calling 'include cmpxchg' +using (var session = store.OpenSession(new SessionOptions +{ + TransactionMode = TransactionMode.ClusterWide +})) +{ + // Make a raw query + include CmpXchg items: + // ========================================= + + // In the provided RQL: + // * Call 'include' with 'cmpxchg' + // * Pass the PATH of the document property that contains the key of the CmpXchg item to include + var companies = session.Advanced + .RawQuery(@" + from 'Companies' + include cmpxchg('Supplier')") + .ToList(); + + var numberOfRequests = session.Advanced.NumberOfRequests; + Console.WriteLine($"Number of requests made: {numberOfRequests}"); // Should be 1 + + // Access the included CmpXchg items: + // ================================== + + var cmpXchgItems = new List>(); + + foreach (var company in companies) + { + // Call 'getCompareExchangeValues' to access the content of the included CmpXchg items, + // pass the KEY, this will NOT trigger another server call. + var item = session.Advanced.ClusterTransaction + .GetCompareExchangeValue(company.Supplier); + + cmpXchgItems.Add(item); + } + + Console.WriteLine(session.Advanced.NumberOfRequests == numberOfRequests); // Should be true +} +``` + + +```csharp +// Open a session with cluster-wide mode to enable calling 'includes.cmpxchg' +using (var session = store.OpenSession(new SessionOptions +{ + TransactionMode = TransactionMode.ClusterWide +})) +{ + // Make a raw query + include CmpXchg items using JavaScript method: + // ================================================================= + + // In the provided RQL: + // * Call 'includes.cmpxchg' + // * Pass the PATH of the document property that contains the key of the CmpXchg item to include + var companies = session.Advanced + .RawQuery(@" + declare function includeCmpXchg(company) { + includes.cmpxchg(company.Supplier); + return company; + } + + from companies as c + select includeCmpXchg(c)") + .ToList(); + + var numberOfRequests = session.Advanced.NumberOfRequests; + Console.WriteLine($"Number of requests made: {numberOfRequests}"); // Should be 1 + + // Access the included CmpXchg items: + // ================================== + + var cmpXchgItems = new List>(); + + foreach (var company in companies) + { + // Call 'getCompareExchangeValues' to access the content of the included CmpXchg items, + // pass the KEY. This will NOT trigger another server call. + var item = session.Advanced.ClusterTransaction + .GetCompareExchangeValue(company.Supplier); + + cmpXchgItems.Add(item); + } + + Console.WriteLine(session.Advanced.NumberOfRequests == numberOfRequests); // Should be true +} +``` + + +```sql +// RQL that can be used with a Raw Query: +// ====================================== + +from "Companies" +include cmpxchg("Supplier") + +// or: + +from companies as c +select c +include cmpxchg(c.Supplier) + +// Using JS method: +// ================ + +declare function includeCmpXchg(company) { + includes.cmpxchg(company.Supplier); + return company; +} + +from companies as c +select includeCmpXchg(c) +``` + + + + + + + +**Index query**: + + + +```csharp +// Open a session with cluster-wide mode +using (var session = store.OpenSession(new SessionOptions +{ + TransactionMode = TransactionMode.ClusterWide +})) +{ + // Make an index query + include CmpXchg items: + // ============================================ + + var companies = session.Query() + // Call 'Include' with 'IncludeCompareExchangeValue' + // pass the PATH of the property that contains the key of the CmpXchg item to include + .Include(x => x.IncludeCompareExchangeValue(c => c.Supplier)) + .OfType() + .ToList(); + + var numberOfRequests = session.Advanced.NumberOfRequests; + Console.WriteLine($"Number of requests made: {numberOfRequests}"); // Should be 1 + + // Access the included CmpXchg items: + // ================================== + + var cmpXchgItems = new List>(); + + foreach (var company in companies) + { + var item = session.Advanced.ClusterTransaction + .GetCompareExchangeValue(company.Supplier); + + cmpXchgItems.Add(item); + } + + Console.WriteLine(session.Advanced.NumberOfRequests == numberOfRequests); // Should be true +} +``` + + +```csharp +// Open a session with cluster-wide mode +using (var asyncSession = store.OpenAsyncSession(new SessionOptions +{ + TransactionMode = TransactionMode.ClusterWide +})) +{ + // Make an index query + include CmpXchg items: + // ============================================ + + var companies = await asyncSession.Query() + // Call 'Include' with 'IncludeCompareExchangeValue' + // pass the PATH of the property that contains the key of the CmpXchg item to include + .Include(x => x.IncludeCompareExchangeValue(c => c.Supplier)) + .OfType() + .ToListAsync(); + + var numberOfRequests = asyncSession.Advanced.NumberOfRequests; + Console.WriteLine($"Number of requests made: {numberOfRequests}"); // Should be 1 + + // Access the included CmpXchg items: + // ================================== + + var cmpXchgItems = new List>(); + + foreach (var company in companies) + { + var item = await asyncSession.Advanced.ClusterTransaction + .GetCompareExchangeValueAsync(company.Supplier); + + cmpXchgItems.Add(item); + } + + Console.WriteLine(session.Advanced.NumberOfRequests == numberOfRequests); // Should be true +} +``` + + +```sql +// RQL that can be used with a Raw Query: +// ====================================== + +from index "Companies/ByNameAndSupplier" +include cmpxchg("Supplier") + +// Using JS method: +// ================ + +declare function includeCmpXchg(company) { + includes.cmpxchg(company.Supplier); + return company; +} + +from index "Companies/ByNameAndSupplier" as c +select includeCmpXchg(c) +``` + + +```csharp +public class Companies_ByNameAndSupplier : AbstractIndexCreationTask +{ + public class IndexEntry + { + public string Name { get; set;} + public string Supplier { get; set; } + } + + public Companies_ByNameAndSupplier() + { + Map = companies => from company in companies + select new IndexEntry() + { + Name = company.Name, + Supplier = company.Supplier + }; + } +} +``` + + + +* Note: + Similar to the above dynamic query example, you can query the index with a [raw query](../indexes/querying/query-index#query-an-index-by-rawquery) using the provided RQL. + + + +--- + +## Syntax + + + +**When loading entities or making queries**: + +* Use method `IncludeCompareExchangeValue()` to include compare-exchange items with `session.Load()` or with queries. + + +```csharp +// Available overloads: +IncludeCompareExchangeValue(string path); +IncludeCompareExchangeValue(Expression> path); +IncludeCompareExchangeValue(Expression>> path); + +``` + + +| Parameter | Type | Description | +|-----------|--------------------------------|--------------------------------------------------------------------------------------------------------------| +| **path** | `string` | The key of the compare-exchange item to include. | +| **path** | `Expression>` | An expression indicating the property path that resolves to the key of the compare-exchange item to include. | +| **path** | `Expression>>` | An expression indicating the property path that resolves to an array of keys of the items to include. | + + + + + +**When querying with RQL**: + +* Use the [include](../client-api/session/querying/what-is-rql.mdx#include) keyword followed by `cmpxchg()` to include a compare-exchange item. + + +```sql +include cmpxchg(key) +``` + + + + + + +**When using JavaScript functions within RQL**: + +* Use `includes.cmpxchg()` In [JavaScript functions](../client-api/session/querying/what-is-rql.mdx#declare) within RQL queries. + + +```csharp +includes.cmpxchg(key); +``` + + +| Parameter | Type | Description | +|-----------|-----------|----------------------------------------------------------------------------------| +| **key** | `string` | The key of the compare exchange value you want to include, or a path to the key. | + + diff --git a/docs/client-api/operations/compare-exchange/_include-compare-exchange-nodejs.mdx b/docs/compare-exchange/content/_include-compare-exchange-items-nodejs.mdx similarity index 61% rename from docs/client-api/operations/compare-exchange/_include-compare-exchange-nodejs.mdx rename to docs/compare-exchange/content/_include-compare-exchange-items-nodejs.mdx index f16621338b..b17a4b1a9d 100644 --- a/docs/client-api/operations/compare-exchange/_include-compare-exchange-nodejs.mdx +++ b/docs/compare-exchange/content/_include-compare-exchange-items-nodejs.mdx @@ -1,32 +1,35 @@ -import Admonition from '@theme/Admonition'; +import Admonition from '@theme/Admonition'; import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; import CodeBlock from '@theme/CodeBlock'; -* Compare-Exchange items can be included when [loading entities](../../../client-api/session/loading-entities.mdx) - and when making [queries](../../../client-api/session/querying/how-to-query.mdx). +* Compare-exchange items can be included when [loading entities](../client-api/session/loading-entities) + or when making [queries](../client-api/session/querying/how-to-query). -* The Session [tracks](../../../client-api/session/what-is-a-session-and-how-does-it-work.mdx#tracking-changes) - the included Compare Exchange items which means their value can be accessed - in that Session without making an additional call to the server. +* The Session [tracks](../client-api/session/what-is-a-session-and-how-does-it-work) the included compare-exchange items, + which means their values can be accessed later in the same session without making additional requests to the server. * In this page: - * [Sample data](../../../client-api/operations/compare-exchange/include-compare-exchange.mdx#sample-data) - * [Include CmpXchg items when loading](../../../client-api/operations/compare-exchange/include-compare-exchange.mdx#include-cmpxchg-items-when-loading) - * [Include CmpXchg items when querying](../../../client-api/operations/compare-exchange/include-compare-exchange.mdx#include-cmpxchg-items-when-querying) - * [Syntax](../../../client-api/operations/compare-exchange/include-compare-exchange.mdx#syntax) + * [Sample data](../compare-exchange/include-cmpxchg-items#sample-data) + * [Include compare-exchange items when loading](../compare-exchange/include-cmpxchg-items#include-compare-exchange-items-when-loading) + * [Include compare-exchange items when querying](../compare-exchange/include-cmpxchg-items#include-compare-exchange-items-when-querying) + * [Syntax](../compare-exchange/include-cmpxchg-items#syntax) + +--- + ## Sample data -* The examples in this article are based on the following __sample data__: +The examples in this article are based on the following **sample data**: +To learn about ALL the available methods for creating a compare-exchange item, see [Create compare-exchange item](../compare-exchange/create-cmpxchg-items). - - -{`{ + +```js +{ // Create some company documents: // ============================== @@ -38,8 +41,11 @@ import CodeBlock from '@theme/CodeBlock'; company1.supplier = "suppliers/1"; company1.workers = ["employees/1", "employees/2"]; - const company2 = new Company("companies/2", "Google", "suppliers/2", ["employees/3", "employees/4"]); - const company3 = new Company("companies/3", "Microsoft", "suppliers/3", ["employees/6", "employees/5"]); + const company2 = new Company( + "companies/2", "Google", "suppliers/2", ["employees/3", "employees/4"]); + + const company3 = new Company( + "companies/3", "Microsoft", "suppliers/3", ["employees/6", "employees/5"]); await session.store(company1); await session.store(company2); @@ -47,31 +53,40 @@ import CodeBlock from '@theme/CodeBlock'; await session.saveChanges(); } +``` + + +```js { - // Create some CmpXchg items: - // ========================== + // Create some compare-exchange items: + // =================================== // Open a session with cluster-wide mode so that we can call 'createCompareExchangeValue' const session = documentStore.openSession({ transactionMode: "ClusterWide" }); - session.advanced.clusterTransaction.createCompareExchangeValue("employee/1", "content for employee 1 .."); - session.advanced.clusterTransaction.createCompareExchangeValue("employee/2", "content for employee 2 .."); - session.advanced.clusterTransaction.createCompareExchangeValue("employee/3", "content for employee 3 .."); - - session.advanced.clusterTransaction.createCompareExchangeValue("suppliers/1", "content for supplier 1 .."); - session.advanced.clusterTransaction.createCompareExchangeValue("suppliers/2", "content for supplier 2 .."); - session.advanced.clusterTransaction.createCompareExchangeValue("suppliers/3", "content for supplier 3 .."); + session.advanced.clusterTransaction.createCompareExchangeValue( + "employees/1", "content for employee 1 .."); + session.advanced.clusterTransaction.createCompareExchangeValue( + "employees/2", "content for employee 2 .."); + session.advanced.clusterTransaction.createCompareExchangeValue( + "employees/3", "content for employee 3 .."); + + session.advanced.clusterTransaction.createCompareExchangeValue( + "suppliers/1", "content for supplier 1 .."); + session.advanced.clusterTransaction.createCompareExchangeValue( + "suppliers/2", "content for supplier 2 .."); + session.advanced.clusterTransaction.createCompareExchangeValue( + "suppliers/3", "content for supplier 3 .."); await session.saveChanges(); } -`} - +``` - -{`class Company { +```js +class Company { constructor( id = null, name = "", @@ -87,81 +102,82 @@ import CodeBlock from '@theme/CodeBlock'; }); } } -`} - +``` +--- - -## Include CmpXchg items when loading +## Include compare-exchange items when loading -__Include single item__: - - -{`// Open a session with cluster-wide mode to enable calling 'includeCompareExchangeValue' -const session = documentStore.openSession(\{ +**Include single item**: + + +```js +// Open a session with cluster-wide mode to enable calling 'includeCompareExchangeValue' +const session = documentStore.openSession({ transactionMode: "ClusterWide" -\}); +}); // Load a company document + include a CmpXchg item: // ================================================= // Call 'includeCompareExchangeValue' within the 'load' options, // pass the PATH of the document property that contains the key of the CmpXchg item to include -const company1 = await session.load("companies/1", \{ +const company1 = await session.load("companies/1", { documentType: Company, - // "supplier" is the document property that holds the cmpXchg key + // "supplier" is the document property that holds the CmpXchg key includes: i => i.includeCompareExchangeValue("supplier") -\}); +}); // Calling 'load' has triggered a server call const numberOfRequests = session.advanced.numberOfRequests; -assert.equal(numberOfRequests, 1); +console.log(`Number of requests made: ${numberOfRequests}`); // Should be 1 // Access the included CmpXchg item: // ================================= // Call 'getCompareExchangeValue' to access the content of the included CmpXchg item, -// pass the cmpXchg item KEY. This will NOT trigger another server call. +// pass the CmpXchg item KEY. This will NOT trigger another server call. const item = await session.advanced.clusterTransaction .getCompareExchangeValue(company1.supplier); -// You can assert that no further server calls were made -assert.equal(session.advanced.numberOfRequests, numberOfRequests); +// You can check that no further server calls were made +console.log(session.advanced.numberOfRequests === numberOfRequests); // Should be true -// The cmpXchg item value is available +// The CmpXchg item value is available const value = item.value; -`} - +``` -__Include multiple items__: - - -{`const session = documentStore.openSession(\{ +**Include multiple items**: + + +```js +// Open a session with cluster-wide mode +const session = documentStore.openSession({ transactionMode: "ClusterWide" -\}); +}); // Load a company document + include multiple CmpXchg items: // ========================================================= // Call 'includeCompareExchangeValue' within the 'load' options, -// pass the PATH of the document property that contains the list keys of the CmpXchg items to include -const company1 = await session.load("companies/1", \{ +// pass the PATH of the document property that contains the list of the CmpXchg items to include +const company1 = await session.load("companies/1", { documentType: Company, // "workers" is the document property that holds the list of keys includes: i => i.includeCompareExchangeValue("workers") -\}); +}); const numberOfRequests = session.advanced.numberOfRequests; -assert.equal(numberOfRequests, 1); +console.log(`Number of requests made: ${numberOfRequests}`); // Should be 1 // Access the included CmpXchg items: // ================================== @@ -171,28 +187,29 @@ assert.equal(numberOfRequests, 1); const items = await session.advanced.clusterTransaction .getCompareExchangeValues(company1.workers); -assert.equal(session.advanced.numberOfRequests, numberOfRequests); +// You can check that no further server calls were made +console.log(session.advanced.numberOfRequests === numberOfRequests); // Should be true // The value of each item is available const value1 = items["employees/1"].value; const value2 = items["employees/2"].value; -`} - +``` +--- - -## Include CmpXchg items when querying +## Include compare-exchange items when querying -__dynamic query__: +**Dynamic query**: + - -{`// Open a session with cluster-wide mode to enable calling 'includeCompareExchangeValue' +```js +// Open a session with cluster-wide mode to enable calling 'includeCompareExchangeValue' const session = documentStore.openSession({ transactionMode: "ClusterWide" }); @@ -208,7 +225,7 @@ const companies = await session.query({ collection: "companies" }) // Making the query has triggered a server call const numberOfRequests = session.advanced.numberOfRequests; -assert.equal(numberOfRequests, 1); +console.log(`Number of requests made: ${numberOfRequests}`); // Should be 1 // Access the included CmpXchg items: // ================================== @@ -224,14 +241,13 @@ for (let i = 0; i < companies.length; i++) { cmpXchgItems.push(item); } -// You can assert that no further server calls were made -assert.equal(session.advanced.numberOfRequests, numberOfRequests); -`} - +// You can check that no further server calls were made +console.log(session.advanced.numberOfRequests === numberOfRequests); // Should be true +``` - -{`// Open a session with cluster-wide mode to enable calling 'include cmpxchg' +```js +// Open a session with cluster-wide mode to enable calling 'include cmpxchg' const session = documentStore.openSession({ transactionMode: "ClusterWide" }); @@ -243,13 +259,13 @@ const session = documentStore.openSession({ // * Call 'include' with 'cmpxchg' // * Pass the PATH of the document property that contains the key of the CmpXchg item to include const companies = await session.advanced - .rawQuery(\`from companies as c + .rawQuery(`from companies as c select c - include cmpxchg(c.supplier)\`) + include cmpxchg(c.supplier)`) .all(); const numberOfRequests = session.advanced.numberOfRequests; -assert.equal(numberOfRequests, 1); +console.log(`Number of requests made: ${numberOfRequests}`); // Should be 1 // Access the included CmpXchg items: // ================================== @@ -257,21 +273,21 @@ assert.equal(numberOfRequests, 1); const cmpXchgItems = []; for (let i = 0; i < companies.length; i++) { - // Call 'getCompareExchangeValues' to access the content of the included CmpXchg items, pass the KEY, - // this will NOT trigger another server call + // Call 'getCompareExchangeValues' to access the content of the included CmpXchg items, + // pass the KEY, this will NOT trigger another server call. const item = await session.advanced.clusterTransaction .getCompareExchangeValue(companies[i].supplier); cmpXchgItems.push(item); } -assert.equal(session.advanced.numberOfRequests, numberOfRequests); -`} - +// You can check that no further server calls were made +console.log(session.advanced.numberOfRequests === numberOfRequests); // Should be true +``` - -{`// Open a session with cluster-wide mode to enable calling 'includes.cmpxchg' +```js +// Open a session with cluster-wide mode to enable calling 'includes.cmpxchg' const session = documentStore.openSession({ transactionMode: "ClusterWide" }); @@ -283,17 +299,17 @@ const session = documentStore.openSession({ // * Call 'includes.cmpxchg' // * Pass the PATH of the document property that contains the key of the CmpXchg item to include const companies = await session.advanced - .rawQuery(\`declare function includeCmpXchg(company) { + .rawQuery(`declare function includeCmpXchg(company) { includes.cmpxchg(company.supplier); return company; } from companies as c - select includeCmpXchg(c)\`) + select includeCmpXchg(c)`) .all(); const numberOfRequests = session.advanced.numberOfRequests; -assert.equal(numberOfRequests, 1); +console.log(`Number of requests made: ${numberOfRequests}`); // Should be 1 // Access the included CmpXchg items: // ================================== @@ -302,16 +318,16 @@ const cmpXchgItems = []; for (let i = 0; i < companies.length; i++) { // Call 'getCompareExchangeValues' to access the content of the included CmpXchg items, - // pass the KEY. This will NOT trigger another server call + // pass the KEY. This will NOT trigger another server call. const item = await session.advanced.clusterTransaction .getCompareExchangeValue(companies[i].supplier); cmpXchgItems.push(item); } -assert.equal(session.advanced.numberOfRequests, numberOfRequests); -`} - +// You can check that no further server calls were made +console.log(session.advanced.numberOfRequests === numberOfRequests); // Should be true +``` @@ -319,11 +335,13 @@ assert.equal(session.advanced.numberOfRequests, numberOfRequests); -__Index query__: +**Index query**: + - -{`const session = documentStore.openSession({ +```js +// Open a session with cluster-wide mode +const session = documentStore.openSession({ transactionMode: "ClusterWide" }); @@ -337,7 +355,7 @@ const companies = await session.query({ indexName: "Companies/ByName" }) .all(); const numberOfRequests = session.advanced.numberOfRequests; -assert.equal(numberOfRequests, 1); +console.log(`Number of requests made: ${numberOfRequests}`); // Should be 1 // Access the included CmpXchg items: // ================================== @@ -346,36 +364,20 @@ const cmpXchgItems = []; for (let i = 0; i < companies.length; i++) { // Call 'getCompareExchangeValues' to access the content of the included CmpXchg items, - // pass the KEY. This will NOT trigger another server call + // pass the KEY. This will NOT trigger another server call. const item = await session.advanced.clusterTransaction .getCompareExchangeValue(companies[i].supplier); cmpXchgItems.push(item); } -assert.equal(session.advanced.numberOfRequests, numberOfRequests); -`} - - - - -{`class Companies_ByName extends AbstractJavaScriptIndexCreationTask { - constructor() { - super(); - - this.map('companies', company => { - return { - name: company.name - }; - }); - } -} -`} - +// You can check that no further server calls were made +console.log(session.advanced.numberOfRequests === numberOfRequests); // Should be true +``` - -{`// RQL that can be used with a Raw Query: +```sql +// RQL that can be used with a Raw Query: // ====================================== from index "Companies/ByName" @@ -391,70 +393,80 @@ declare function includeCmpXchg(company) { from index "Companies/ByName" as c select includeCmpXchg(c) -`} - +``` + + +```js +class Companies_ByName extends AbstractJavaScriptIndexCreationTask { + constructor() { + super(); + + this.map('companies', company => { + return { + name: company.name + }; + }); + } +} +``` * Note: - Similar to the above dynamic query example, you can query the index with a [raw query](../../../indexes/querying/query-index.mdx#sessionadvancedrawquery) using the provided RQL. + Similar to the above dynamic query example, you can query the index with a [raw query](../indexes/querying/query-index#query-an-index-by-rawquery) using the provided RQL. - +--- ## Syntax -__When loading entities or making queries__: +**When loading entities or making queries**: -* Use method `includeCompareExchangeValue()` to include compare-exchange items along with `session.load()` or with queries. +* Use method `includeCompareExchangeValue()` to include compare-exchange items with `session.load()` or with queries. - - -{`includeCompareExchangeValue(path); -`} - + +```js +includeCompareExchangeValue(path); +``` | Parameter | Type | Description | |-----------|-----------|------------------------------------------------------------------------------------------------------------| -| __path__ | `string` | The path of the document property that contains the key (or list of keys) of the CmpXchg items to include | +| **path** | `string` | The path of the document property that contains the key (or list of keys) of the CmpXchg items to include. | + -__When querying with RQL__: +**When querying with RQL**: -* Use the [include](../../../client-api/session/querying/what-is-rql.mdx#include) keyword followed by `cmpxchg()` to include a compare-exchange item. +* Use the [include](../client-api/session/querying/what-is-rql.mdx#include) keyword followed by `cmpxchg()` to include a compare-exchange item. - - -{`include cmpxchg(key) -`} - + +```sql +include cmpxchg(key) +``` + -__When using javascript functions within RQL__: +**When using JavaScript functions within RQL**: -* Use `includes.cmpxchg()` In [javascript functions](../../../client-api/session/querying/what-is-rql.mdx#declare) within RQL queries. +* Use `includes.cmpxchg()` In [JavaScript functions](../client-api/session/querying/what-is-rql.mdx#declare) within RQL queries. - - -{`includes.cmpxchg(key); -`} - + +```js +includes.cmpxchg(key); +``` | Parameter | Type | Description | |-----------|-----------|----------------------------------------------------------------------------------| -| __key__ | `string` | The key of the compare exchange value you want to include, or a path to the key. | +| **key** | `string` | The key of the compare exchange value you want to include, or a path to the key. | - - - diff --git a/docs/compare-exchange/content/_indexing-compare-exchange-values-csharp.mdx b/docs/compare-exchange/content/_indexing-compare-exchange-values-csharp.mdx new file mode 100644 index 0000000000..c72007f252 --- /dev/null +++ b/docs/compare-exchange/content/_indexing-compare-exchange-values-csharp.mdx @@ -0,0 +1,556 @@ +import Admonition from '@theme/Admonition'; +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +import CodeBlock from '@theme/CodeBlock'; + + + +* You can index compare-exchange values in a static-index. + This lets you query documents based on values stored in compare-exchange items. + +* Modifications to the indexed compare-exchange values will trigger index updates. + The index will be updated whenever an indexed compare-exchange value changes, + or when documents in the indexed collection(s) are modified. + +* In this article: + * [Create sample compare-exchange items](../compare-exchange/indexing-cmpxchg-values#create-sample-compare-exchange-items) + * [Index compare-exchange values](../compare-exchange/indexing-cmpxchg-values#index-compare-exchange-values) + * [Query the index](../compare-exchange/indexing-cmpxchg-values#query-the-index) + * [Query the index and project compare-exchange values](../compare-exchange/indexing-cmpxchg-values#query-the-index-and-project-compare-exchange-values) + * [Syntax](../compare-exchange/indexing-cmpxchg-values#syntax) + + + +--- + +## Create sample compare-exchange items + +Let’s create some sample documents and compare-exchange items to use in the examples below. +To learn about ALL the available methods for creating a compare-exchange item, see [Create compare-exchange item](../compare-exchange/create-cmpxchg-items). + + + +```csharp +// Create some hotel room DOCUMENTS with general info: +// =================================================== + +using (var session = store.OpenSession()) +{ + for (int i = 215; i <= 217; i++) + { + var room = new HotelRoom + { + RoomNumber = $"R{i}", + Description = $"Description of room number R{i}" + }; + + session.Store(room, $"hotelRooms/R{i}"); + } + + session.SaveChanges(); +} +``` + + +```csharp +// Create some COMPARE-EXCHANGE ITEMS to track current details of each room: +// ========================================================================= + +// Value for room R215 +var hotelRoomDetails = new HotelRoomCurrentDetails() +{ + CurrentNumberOfGuests = 2, + ReservedBy = "customers/2", + ReservedUntil = DateTime.Now.AddDays(2), + FullyPaid = true, + CustomerEmail = "customer2@gmail.com", + CustomerPhone = "123-123-1234" +}; + +CompareExchangeResult putResult = store.Operations.Send( + new PutCompareExchangeValueOperation( + "R215", hotelRoomDetails, 0)); + +// Value for room R216 +hotelRoomDetails = new HotelRoomCurrentDetails() +{ + CurrentNumberOfGuests = 3, + ReservedBy = "customers/3", + ReservedUntil = DateTime.Now.AddDays(5), + FullyPaid = false, + CustomerEmail = "customer3@gmail.com", + CustomerPhone = "456-456-6789" +}; + +putResult = store.Operations.Send( + new PutCompareExchangeValueOperation( + "R216", hotelRoomDetails, 0)); + +// Value for room R217 +// This room is currently not occupied... +hotelRoomDetails = new HotelRoomCurrentDetails() +{ + CurrentNumberOfGuests = 0 +}; + +putResult = store.Operations.Send( + new PutCompareExchangeValueOperation( + "R217", hotelRoomDetails, 0)); +``` + + +```csharp +// Sample classes used: +// ==================== + +// The document +public class HotelRoom +{ + public string RoomNumber { get; set; } + public string Description { get; set; } +} + +// The compare-exchange value +public class HotelRoomCurrentDetails +{ + public int CurrentNumberOfGuests { get; set; } + public string ReservedBy { get; set; } + public DateTime ReservedUntil { get; set; } + public bool FullyPaid { get; set; } + public string CustomerEmail { get; set; } + public string CustomerPhone { get; set; } +} + +// Projected content +public class ProjectedCustomerDetails +{ + public string CustomerEmail { get; set; } + public string CustomerPhone { get; set; } + public string RoomNumber { get; set; } +} + +// Projected content +public class ProjectedNumberOfGuests +{ + public int CurrentNumberOfGuests { get; set; } + public string RoomNumber { get; set; } +} +``` + + + +--- + +## Index compare-exchange values + + +* This index maps the rooms in a hotel, as well as compare-exchange values representing the guests in those rooms. + +* Use method `LoadCompareExchangeValue` to load the current details of each room from the associated compare-exchange value. + + + +```csharp +public class Rooms_ByGuestsAndPaymentStatus : AbstractIndexCreationTask +{ + // The index-fields + public class IndexEntry + { + public string RoomNumber; + public int? NumberOfGuests; + public boo? FullyPaid { get; set; } + } + + public Rooms_ByGuestsAndPaymentStatus() + { + Map = HotelRooms => from room in HotelRooms + // Call method 'LoadCompareExchangeValue' + // to load the compare-exchange value by its key (room number) + let cmpXchgValue = LoadCompareExchangeValue(room.RoomNumber) + + // Define the index-fields + select new IndexEntry() + { + // Index content from the document: + RoomNumber = room.RoomNumber, + + // Index content from the compare-exchange value: + NumberOfGuests = cmpXchgValue != null ? cmpXchgValue.CurrentNumberOfGuests : (int?)null, + FullyPaid = cmpXchgValue != null ? cmpXchgValue.FullyPaid : (bool?)null + }; + } +} +``` + + +```csharp +public class Rooms_ByGuestsAndPaymentStatus_JS : AbstractJavaScriptIndexCreationTask +{ + public Compare_Exchange_JS_Index() + { + Maps = new HashSet + { + @" + map('HotelRooms', function(room) { + // Call method 'cmpxchg' + // to load the compare-exchange value by its key (room number) + var cmpXchgValue = cmpxchg(room.RoomNumber); + + // Define the index-fields + return { + // Index content from the document: + RoomNumber: room.RoomNumber, + + // Index content from the compare-exchange value: + NumberOfGuests: cmpXchgValue ? cmpXchgValue.CurrentNumberOfGuests : null, + FullyPaid: cmpXchgValue ? cmpXchgValue.FullyPaid : null + }; + }); + " + }; + } +} +``` + + +```csharp +var indexDefinition = new IndexDefinition +{ + Name = "Rooms/ByGuestsAndPaymentStatus", + + Maps = new HashSet + { + @"from room in docs.HotelRooms + + // Call method 'LoadCompareExchangeValue' + // to load the compare-exchange value by its key (room number) + let cmpXchgValue = LoadCompareExchangeValue(room.RoomNumber) + where cmpXchgValue != null + + select new + { + // Index content from the document: + RoomNumber = room.RoomNumber, + + // Index content from the compare-exchange value: + NumberOfGuests = cmpXchgValue.CurrentNumberOfGuests, + FullyPaid = cmpXchgValue.FullyPaid + }" + } +}; + +store.Maintenance.Send(new PutIndexesOperation(indexDefinition)); +``` + + + +--- + +## Query the index + +* Using the index above, you can query for all rooms (room documents) that are occupied by a specific number of guests. + The _NumberOfGuests_ index-field, which is used in the query, contains the number of guests taken from the related compare-exchange value. + +* For example, you can find all vacant rooms (0 guests) or rooms occupied by any specific number of guests. + + + +```csharp +// When querying the index, +// the session does not need to be opened in cluster-wide mode. +using (var session = store.OpenSession()) +{ + // Query for all vacant rooms (0 guests) + List vacantRooms = session + .Query() + // Index-field 'NumberOfGuests' contains the guest count for each room, + // taken from the compare-exchange item. + .Where(x => x.NumberOfGuests == 0) + .OfType() + .ToList(); + + // Using the sample data created above, Room R217 will be returned, since it has no guests. +} +``` + + +```csharp +// When querying the index, +// the session does not need to be opened in cluster-wide mode. +using (var asyncSession = store.OpenAsyncSession()) +{ + // Query for all vacant rooms (0 guests) + List vacantRooms = await asyncSession + .Query() + // Index-field 'NumberOfGuests' contains the guest count for each room, + // taken from the compare-exchange item. + .Where(x => x.NumberOfGuests == 0) + .OfType() + .ToListAsync(); + + // Using the sample data created above, Room R217 will be returned, since it has no guests. +} +``` + + +```csharp +using (var session = store.OpenSession()) +{ + List vacantRooms = session.Advanced + .DocumentQuery() + .WhereEquals(x => x.NumberOfGuests, 0) + .OfType() + .ToList(); +} +``` + + +```csharp +using (var asyncSession = store.OpenAsyncSession()) +{ + List vacantRooms = await asyncSession.Advanced + .AsyncDocumentQuery() + .WhereEquals(x => x.NumberOfGuests, 0) + .OfType() + .ToListAsync(); +} +``` + + +```csharp +using (var session = store.OpenSession()) +{ + var vacantRooms = session.Advanced + .RawQuery(@" + from index 'Rooms/ByGuestsAndPaymentStatus' + where NumberOfGuests == 0") + .ToList(); +} +``` + + +```csharp +using (var asyncSession = store.OpenAsyncSession()) +{ + var vacantRooms = await asyncSession.Advanced + .AsyncRawQuery(@" + from index 'Rooms/ByGuestsAndPaymentStatus' + where NumberOfGuests = 0") + .ToListAsync(); +} +``` + + +```sql +from index "Rooms/ByGuestsAndPaymentStatus" +where NumberOfGuests == 0 +``` + + + +--- + +## Query the index and project compare-exchange values + +* In addition to querying index-fields that already contain information from the related compare-exchange value, + you can also project fields from the compare-exchange value into the query results. + +* In the following query example, we retrieve all customers who haven't fully paid yet, + and project their phone number from the compare-exchange value using `RavenQuery.CmpXchg`. + + + +```csharp +// The session does not need to be opened in cluster-wide mode +using (var session = store.OpenSession()) +{ + List phonesOfCustomersThatNeedToPay = session + .Query() + // Index-field 'FullyPaid' contains info from the compare-exchange item + .Where(x => x.FullyPaid == false && x.NumberOfGuests > 0) + // Project query results: + .Select(x => new ProjectedCustomerDetails + { + // Project content from the compare-exchange item: + // Call 'RavenQuery.CmpXchg' to load the compare-exchange value by its key. + CustomerPhone = RavenQuery.CmpXchg(x.RoomNumber).CustomerPhone, + // Project content from the index-field: + RoomNumber = x.RoomNumber + }) + .ToList(); + + // Using the sample data created above, customer from room R216 will be returned + // in the projected data, since they haven't fully paid yet. +} +``` + + +```csharp +// The session does not need to be opened in cluster-wide mode +using (var asyncSession = store.OpenAsyncSession()) +{ + List phonesOfCustomersThatNeedToPay = await asyncSession + .Query() + // Index-field 'FullyPaid' contains info from the compare-exchange item + .Where(x => x.FullyPaid == false && x.NumberOfGuests > 0) + // Project query results: + .Select(x => new ProjectedCustomerDetails + { + // Project content from the compare-exchange item: + // Call 'RavenQuery.CmpXchg' to load the compare-exchange value by its key. + CustomerPhone = RavenQuery.CmpXchg(x.RoomNumber).CustomerPhone, + // Project content from the index-field: + RoomNumber = x.RoomNumber + }) + .ToListAsync(); + + // Using the sample data created above, customer from room R216 will be returned + // in the projected data, since they haven't fully paid yet. +} +``` + + +```csharp +using (var session = store.OpenSession()) +{ + List phonesOfCustomersThatNeedToPay = session.Advanced + .DocumentQuery() + .WhereEquals(x => x.FullyPaid, false) + .AndAlso() + .WhereGreaterThan(x=> x.NumberOfGuests, 0) + // Define the projection using a custom function: + .SelectFields(QueryData.CustomFunction( + alias: "room", + func: @"{ + // Project content from the compare-exchange item: + // Call 'cmpxchg' to load the compare-exchange value by its key. + CustomerPhone : cmpxchg(room.RoomNumber).CustomerPhone, + + // Project content from the index-field: + RoomNumber : room.RoomNumber + }") + ) + .ToList(); +} +``` + + +```csharp +using (var asyncSession = store.OpenAsyncSession()) +{ + List phonesOfCustomersThatNeedToPay = await asyncSession.Advanced + .AsyncDocumentQuery() + .WhereEquals(x => x.FullyPaid, false) + .AndAlso() + .WhereGreaterThan(x=> x.NumberOfGuests, 0) + // Define the projection using a custom function: + .SelectFields(QueryData.CustomFunction( + alias: "room", + func: @"{ + // Project content from the compare-exchange item: + // Call 'cmpxchg' to load the compare-exchange value by its key. + CustomerPhone : cmpxchg(room.RoomNumber).CustomerPhone, + + // Project content from the index-field: + RoomNumber : room.RoomNumber + }") + ) + .ToListAsync(); +} +``` + + +```csharp +using (var session = store.OpenSession()) +{ + var phonesOfCustomersThatNeedToPay = session.Advanced + .RawQuery(@" + from index "Rooms/ByGuestsAndPaymentStatus" as x + where x.FullyPaid = false and (x.NumberOfGuests > 0 and x.NumberOfGuests != null) + select { + CustomerPhone : cmpxchg(x.RoomNumber).CustomerPhone, + RoomNumber : x.RoomNumber + } + ") + .ToList(); +} +``` + + +```csharp +using (var asyncSession = store.OpenAsyncSession()) +{ + var phonesOfCustomersThatNeedToPay = await asyncSession.Advanced + .AsyncRawQuery(@" + from index "Rooms/ByGuestsAndPaymentStatus" as x + where x.FullyPaid = false and (x.NumberOfGuests > 0 and x.NumberOfGuests != null) + select { + CustomerPhone : cmpxchg(x.RoomNumber).CustomerPhone, + RoomNumber : x.RoomNumber + } + ") + .ToListAsync(); +} +``` + + +```sql +from index "Rooms/ByGuestsAndPaymentStatus" as x +where x.FullyPaid = false and (x.NumberOfGuests > 0 and x.NumberOfGuests != null) +select { + CustomerPhone : cmpxchg(x.RoomNumber).CustomerPhone, + RoomNumber : x.RoomNumber +} +``` + + + +--- + +## Syntax + +--- + +### `LoadCompareExchangeValue()` +Load a compare exchange value in the LINQ index-definition by its key. + + +```csharp +//Load one compare exchange value +T LoadCompareExchangeValue(string key); + +//Load multiple compare exchange values +T[] LoadCompareExchangeValue(IEnumerable keys); +``` + + +### `cmpxchg()` +Load a compare exchange value in the JavaScript index-definition by its key. + + +```js +//Load one compare exchange value +cmpxchg(key); + +``` + + +| Parameter | Type | Description | +|-----------|----------------------|---------------------------------------------------| +| **T** | `object` | The type of the compare-exchange item's value | +| **key** | `string` | The key of a particular compare exchange value. | +| **keys** | `IEnumerable`| The keys of multiple compare exchange values. | + +### `RavenQuery.CmpXchg()` +This method is used when **querying the index** with a LINQ query +and projecting fields from the compare-exchange value into the query results. + + +```csharp +// Get a compare-exchange value by key. +public static T CmpXchg(string key) +``` + diff --git a/docs/indexes/_indexing-compare-exchange-values-java.mdx b/docs/compare-exchange/content/_indexing-compare-exchange-values-java.mdx similarity index 80% rename from docs/indexes/_indexing-compare-exchange-values-java.mdx rename to docs/compare-exchange/content/_indexing-compare-exchange-values-java.mdx index ddae50fff7..4210ecca70 100644 --- a/docs/indexes/_indexing-compare-exchange-values-java.mdx +++ b/docs/compare-exchange/content/_indexing-compare-exchange-values-java.mdx @@ -1,14 +1,16 @@ -import Admonition from '@theme/Admonition'; +import Admonition from '@theme/Admonition'; import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; import CodeBlock from '@theme/CodeBlock'; -* Compare exchange values can be loaded within an index using the value's key. +* You can index compare-exchange values in a static-index. + This lets you query documents based on values stored in compare-exchange items. -* The index will update when the compare exchange value is updated, as well -as when documents are modified in the indexed collection(s). +* Modifications to the indexed compare-exchange values will trigger index updates. + The index will be updated whenever an indexed compare-exchange value changes, + or when documents in the indexed collection(s) are modified. * In this page: * [How to use](../indexes/indexing-compare-exchange-values.mdx#how-to-use) @@ -16,19 +18,17 @@ as when documents are modified in the indexed collection(s). * [Querying the Index](../indexes/indexing-compare-exchange-values.mdx#querying-the-index) + ## How to use When creating an index using `AbstractIndexCreationTask`, use javaScript to load a compare exchange value by its key. - - ### Examples These indexes map the rooms in a hotel, as well as compare exchange values representing the guests in those rooms. - @@ -52,8 +52,6 @@ representing the guests in those rooms. - - ## Querying the Index @@ -66,7 +64,3 @@ representing the guests in those rooms. - - - - diff --git a/docs/compare-exchange/content/_indexing-compare-exchange-values-nodejs.mdx b/docs/compare-exchange/content/_indexing-compare-exchange-values-nodejs.mdx new file mode 100644 index 0000000000..65b08dd941 --- /dev/null +++ b/docs/compare-exchange/content/_indexing-compare-exchange-values-nodejs.mdx @@ -0,0 +1,323 @@ +import Admonition from '@theme/Admonition'; +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +import CodeBlock from '@theme/CodeBlock'; + + + +* You can index compare-exchange values in a static-index. + This lets you query documents based on values stored in compare-exchange items. + +* Modifications to the indexed compare-exchange values will trigger index updates. + The index will be updated whenever an indexed compare-exchange value changes, + or when documents in the indexed collection(s) are modified. + +* In this article: + * [Create sample compare-exchange items](../compare-exchange/indexing-cmpxchg-values#create-sample-compare-exchange-items) + * [Index compare-exchange values](../compare-exchange/indexing-cmpxchg-values#index-compare-exchange-values) + * [Query the index](../compare-exchange/indexing-cmpxchg-values#query-the-index) + * [Query the index and project compare-exchange values](../compare-exchange/indexing-cmpxchg-values#query-the-index-and-project-compare-exchange-values) + * [Syntax](../compare-exchange/indexing-cmpxchg-values#syntax) + + + +--- + +## Create sample compare-exchange items + +Let’s create some sample documents and compare-exchange items to use in the examples below. +To learn about ALL the available methods for creating a compare-exchange item, see [Create compare-exchange item](../compare-exchange/create-cmpxchg-items). + + + +```js +// Create some hotel room DOCUMENTS with general info: +// =================================================== + +const session = documentStore.openSession(); + +for (let i = 215; i <= 217; i++) { + const room = new HotelRoom(`R${i}`, `Description of room number R${i}`); + await session.store(room, `hotelRooms/R${i}`); +} + +await session.saveChanges(); +``` + + +```js +// Create some COMPARE-EXCHANGE ITEMS to track current details of each room: +// ========================================================================== + +// Value for room R215 +let hotelRoomDetails = new HotelRoomCurrentDetails({ + CurrentNumberOfGuests: 2, + ReservedBy: "customers/2", + ReservedUntil: new Date(Date.now() + 2 * 24 * 60 * 60 * 1000), + FullyPaid: true, + CustomerEmail: "customer2@gmail.com", + CustomerPhone: "123-123-1234" +}); + +let putResult = await documentStore.operations.send( + new PutCompareExchangeValueOperation("R215", hotelRoomDetails, 0) +); + +// Value for room R216 +hotelRoomDetails = new HotelRoomCurrentDetails({ + CurrentNumberOfGuests: 3, + ReservedBy: "customers/3", + ReservedUntil: new Date(Date.now() + 5 * 24 * 60 * 60 * 1000), + FullyPaid: false, + CustomerEmail: "customer3@gmail.com", + CustomerPhone: "456-456-6789" +}); + +putResult = await documentStore.operations.send( + new PutCompareExchangeValueOperation("R216", hotelRoomDetails, 0) +); + +// Value for room R217 +// This room is currently not occupied... +hotelRoomDetails = new HotelRoomCurrentDetails({ + CurrentNumberOfGuests: 0 +}); + +putResult = await documentStore.operations.send( + new PutCompareExchangeValueOperation("R217", hotelRoomDetails, 0) +); +``` + + +```js +// Sample classes used: +// ==================== + +// The document +class HotelRoom { + constructor(roomNumber = '', description = '') { + Object.assign(this, { + RoomNumber: roomNumber, + Description: description + }); + } +} + +// The compare-exchange value +class HotelRoomCurrentDetails { + constructor({ + CurrentNumberOfGuests = 0, + ReservedBy = null, + ReservedUntil = null, + FullyPaid = false, + CustomerEmail = null, + CustomerPhone = null + } = {}) { + Object.assign(this, { + CurrentNumberOfGuests, + ReservedBy, + ReservedUntil, + FullyPaid, + CustomerEmail, + CustomerPhone + }); + } +} + +// Projected content +class ProjectedCustomerDetails { + constructor({ + CustomerEmail = null, + CustomerPhone = null, + RoomNumber = null + } = {}) { + Object.assign(this, { + CustomerEmail, + CustomerPhone, + RoomNumber + }); + } +} + +// Projected content +class ProjectedNumberOfGuests { + constructor({ + CurrentNumberOfGuests = 0, + RoomNumber = null + } = {}) { + Object.assign(this, { + CurrentNumberOfGuests, + RoomNumber + }); + } +} +``` + + + +--- + +## Index compare-exchange values + +* This index maps the rooms in a hotel, as well as compare-exchange values representing the guests in those rooms. + +* Use method `cmpxchg` to load the current details of each room from the associated compare-exchange value. + + + +```js +class Rooms_ByGuestsAndPaymentStatus extends AbstractJavaScriptIndexCreationTask { + constructor() { + super(); + + this.map("HotelRooms", room => { + // Call method 'cmpxchg' + // to load the compare-exchange value by its key (room number) + const cmpXchgValue = cmpxchg(room.RoomNumber); + + return { + // Index content from the document: + RoomNumber: room.RoomNumber, + + // Index content from the compare-exchange value: + NumberOfGuests: cmpXchgValue ? cmpXchgValue.CurrentNumberOfGuests : null, + FullyPaid: cmpXchgValue ? cmpXchgValue.FullyPaid : null + }; + }); + } +} +``` + + + +--- + +## Query the index + +* Using the index above, you can query for all rooms (room documents) that are occupied by a specific number of guests. + The _NumberOfGuests_ index-field, which is used in the query, contains the number of guests taken from the related compare-exchange value. + +* For example, you can find all vacant rooms (0 guests) or rooms occupied by any specific number of guests. + + + +```js +// When querying the index, +// the session does not need to be opened in cluster-wide mode. +const session = documentStore.openSession(); + +// Query for all vacant rooms (0 guests) +const vacantRooms = await session + .query({ + indexName: "Rooms/ByGuestsAndPaymentStatus" + }) + // Index-field 'NumberOfGuests' contains guest count from the compare-exchange item + .whereEquals("NumberOfGuests", 0) + .all(); + +// Using the sample data created above, Room R217 will be returned, since it has no guests. +``` + + +```js +const session = documentStore.openSession(); + +const vacantRooms = await session.advanced + .rawQuery("from index 'Rooms/ByGuestsAndPaymentStatus' where NumberOfGuests == 0") + .all(); +``` + + +```sql +from index "Rooms/ByGuestsAndPaymentStatus" +where NumberOfGuests == 0 +``` + + + +--- + +## Query the index and project compare-exchange values + +* In addition to querying index-fields that already contain information from the related compare-exchange value, + you can also project fields from the compare-exchange value into the query results. + +* In the following query example, we retrieve all customers who haven't fully paid yet, + and project their phone number from the compare-exchange value using `cmpxchg()`. + + + +```js +// The session does not need to be opened in cluster-wide mode +const session = documentStore.openSession(); + +// Define the projection +const queryData = QueryData.customFunction("room", `{ + // Use method 'cmpxchg' to retrieve data from the compare-exchange value + customerPhone: cmpxchg(room.RoomNumber).CustomerPhone, + roomNumber: room.RoomNumber +}`); + +const phonesOfCustomersThatNeedToPay = await session + .query({ indexName: "Rooms/ByGuestsAndPaymentStatus" }) + // Index-fields 'FullyPaid' & 'NumberOfGuests' contain info from the compare-exchange item + .whereGreaterThanOrEqual("NumberOfGuests", 1) + .andAlso() + .whereEquals("FullyPaid", false) + // Project query results + .selectFields(queryData) + .all(); + +// Using the sample data created above, +// customer from room R216 will be returned in the projected data, since they haven't fully paid yet. +``` + + +```js +const session = documentStore.openSession(); + +const phonesOfCustomersThatNeedToPay = await session.advanced + .rawQuery(` + from index "Rooms/ByGuestsAndPaymentStatus" as x + where x.FullyPaid = false + and (x.NumberOfGuests > 0 and x.NumberOfGuests != null) + select { + CustomerPhone : cmpxchg(x.RoomNumber).CustomerPhone, + RoomNumber : x.RoomNumber + } + `) + .all(); +``` + + +```sql +from index "Rooms/ByGuestsAndPaymentStatus" as x +where x.FullyPaid = false and (x.NumberOfGuests > 0 and x.NumberOfGuests != null) +select { + CustomerPhone : cmpxchg(x.RoomNumber).CustomerPhone, + RoomNumber : x.RoomNumber +} +``` + + + +--- + +## Syntax + +--- + +### `cmpxchg()` +Load a compare exchange value in the index-definition by its key. + + +```js +// Load one compare exchange value +cmpxchg(key); + +``` + + +| Parameter | Type | Description | +|-----------|----------------------|---------------------------------------------------| +| **key** | `string` | The key of a particular compare exchange value. | diff --git a/docs/compare-exchange/content/_overview-csharp.mdx b/docs/compare-exchange/content/_overview-csharp.mdx new file mode 100644 index 0000000000..a289b21b09 --- /dev/null +++ b/docs/compare-exchange/content/_overview-csharp.mdx @@ -0,0 +1,311 @@ +import Admonition from '@theme/Admonition'; +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +import CodeBlock from '@theme/CodeBlock'; + + + +* Compare-exchange items are **key/value pairs** where the key is a globally unique identifier in the database. + Items are versioned and managed at the cluster level. + +* Compare-exchange provides a built-in consensus mechanism for safe coordination across sessions and nodes. + It ensures global consistency in the database and prevents conflicts when multiple clients try to modify or reserve + the same resource, allowing you to: + * Enforce global uniqueness (e.g., prevent duplicate usernames or emails). + * Assign work to a single client or reserve a resource once. + * Handle concurrency safely, without external services or custom locking logic. + +* Compare-exchange items are also suitable for storing shared or global values that aren't tied to a specific document - + such as configuration flags, feature toggles, or reusable identifiers stored under a unique key. + However, [unlike regular documents](../compare-exchange/overview#why-not-use-regular-documents-to-enforce-uniqueness), + compare-exchange provides atomic updates, version-based conflict prevention, and Raft-based consistency for distributed safety. + +* Compare-exchange items are [not replicated externally](../compare-exchange/overview#why-compare-exchange-items-are-not-replicated-to-external-databases) to other databases. + +* In this article: + * [What compare-exchange items are](../compare-exchange/overview#what-compare-exchange-items-are) + * [Ways to create and manage compare-exchange items](../compare-exchange/overview#ways-to-create-and-manage-compare-exchange-items) + * [Why compare-exchange items are not replicated to external databases](../compare-exchange/overview#why-compare-exchange-items-are-not-replicated-to-external-databases) + * [Why not use regular documents to enforce uniqueness](../compare-exchange/overview#why-not-use-regular-documents-to-enforce-uniqueness) + * [Example I - Email address reservation](../compare-exchange/overview#example-i---email-address-reservation) + * [Example II - Reserve a shared resource](../compare-exchange/overview#example-ii---reserve-a-shared-resource) + + + +--- + +## What compare-exchange items are + +Compare-exchange items are key/value pairs where the key serves as a unique value across your database. + +* Each compare-exchange item contains: + * **A key** - A unique string identifier in the database scope. + * **A value** - Can be any value (a number, string, array, or any valid JSON object). + * **Metadata** - Optional data that is associated with the compare-exchange item. Must be a valid JSON object. + For example, the metadata can be used to set expiration time for the compare-exchange item. + Learn more in [compare-exchange expiration](../compare-exchange/cmpxchg-expiration). + * **Raft index** - The compare-exchange item's version. + Any change to the value or metadata will increase this number. + +* Creating and modifying a compare-exchange item follows the same principle as the [compare-and-swap](https://en.wikipedia.org/wiki/Compare-and-swap) operation in multi-threaded systems, + but in RavenDB, this concept is applied to a distributed environment across multiple nodes instead of within a single multi-threaded process. + These operations require cluster consensus to ensure consistency. + Once consensus is reached, the compare-exchange items are distributed through the Raft algorithm to all nodes in the database group. + +--- + +## Ways to create and manage compare-exchange items + +Compare exchange items can be created and managed using any of the following approaches: + +* **Document Store Operations** + You can create and manage compare-exchange items using _document store_ operations. + For example, see [Create items using a store operation](../compare-exchange/create-cmpxchg-items#create-items-using-a-store-operation). + +* **Cluster-Wide Sessions** + You can create and manage compare-exchange items from within a [Cluster-Wide session](../client-api/session/cluster-transaction/overview#cluster-wide-transaction-vs-single-node-transaction). + For example, see [Create items using a cluster-wide session](../compare-exchange/create-cmpxchg-items#create-items-using-a-cluster-wide-session). + When using a cluster-wide session, the compare-exchange item is created as part of the cluster-wide transaction. + Like any transaction, all operations either succeed as a group or are rolled back. + If the transaction is not committed, the new compare-exchange item will not be stored on any node in the database group. + +* **Atomic Guards** + When creating _documents_ using a cluster-wide session, RavenDB automatically creates [Atomic Guards](../compare-exchange/atomic-guards), + which are special compare-exchange items that guarantee ACID transactions. + See [Cluster-wide transaction vs. Single-node transaction](../client-api/session/cluster-transaction/overview#cluster-wide-transaction-vs-single-node-transaction) for a session comparison overview. + +* **Studio** + All compare-exchange items can also be managed from the **Compare-Exchange view** in the [Studio](../todo..): + + ![The compare-exchange view](../assets/the-cmpxchg-view.png) + + 1. Open the **Documents** section in the Studio sidebar. + 2. Click on the **Compare-Exchange** tab. + 3. This is a compare-exchange item. + In this view you can create, edit, and delete compare-exchange items. + +--- + +## Why compare-exchange items are not replicated to external databases + +* Each cluster defines its own policies and configurations, and should ideally have sole responsibility for managing its own documents. + Read [Consistency in a Globally Distributed System](https://ayende.com/blog/196769-B/data-ownership-in-a-distributed-system) + to learn more about why global database modeling is more efficient this way. + +* When creating a compare-exchange item, a Raft consensus is required from the nodes in the database group. + Externally replicating such data is problematic because the target database may reside within a cluster that is in an + unstable state where Raft decisions cannot be made. In such a state, the compare-exchange item will not be persisted in the target database. + +* Conflicts between documents that occur between two databases are resolved using the documents' change-vector. + Compare-exchange conflicts cannot be resolved in the same way, as they lack a similar conflict resolution mechanism. + +* To ensure unique values between two databases without using compare-exchange items see [Example III](../compare-exchange/overview#example-iii---ensuring-unique-values-without-using-compare-exchange). + +* Learn more about Replication in RavenDB in [Replication overview](../server/clustering/replication/replication-overview). + For details about what is and what isn't replicated in [What is Replicated](../server/ongoing-tasks/external-replication#what-is-replicated). + +--- + +## Why not use regular documents to enforce uniqueness + +* You might consider storing a document with a predictable ID (for example, _phones/123456_) as a way to enforce uniqueness, + and then checking for its existence before allowing another document to use the same value. + +* While this might work in a single-node setup or with external replication, + it does not reliably enforce uniqueness in a clustered environment. + +* If a node was not part of the cluster when the document was created, it might not be aware of its existence when it comes back online. + In such cases, attempting to load the document on this node may return _null_, leading to duplicate values being inserted. + +* To reliably enforce uniqueness across all cluster nodes, you must use compare-exchange items, + which are designed for this purpose and ensure global consistency. + +--- + +## Example I - Email address reservation + +The following example shows how to use compare-exchange to create documents with unique values. +The scope is within the database group on a single cluster. + + +```csharp +string email = "user@example.com"; + +User user = new User +{ + Email = email +}; + +using (IDocumentSession session = store.OpenSession()) +{ + session.Store(user); + // At this point, the user object has a document ID assigned by the session. + + // Try to reserve the user email using a compare-exchange item. + // Note: This 'put compare-exchange operation' is not part of the session transaction, + // It is a separate, cluster-wide reservation. + CompareExchangeResult cmpXchgResult + = store.Operations.Send( + // parameters passed to the operation: + // email - the unique key of the compare-exchange item + // user.Id - the value associated with the key + // 0 - pass `0` to ensure the item is created only if it doesn't already exist. + // If a compare-exchange item with the given key already exists, the operation will fail. + new PutCompareExchangeValueOperation(email, user.Id, 0)); + + if (cmpXchgResult.Successful == false) + throw new Exception("Email is already in use"); + + // At this point, the email has been successfully reserved/saved. + // We can now save the user document to the database. + session.SaveChanges(); +} +``` + + +**Implications**: + +* This compare-exchange item was [created as an operation](../compare-exchange/create-cmpxchg-items#create-items-using-a-store-operation) + rather than with a [cluster-wide session](../compare-exchange/create-cmpxchg-items#create-items-using-a-cluster-wide-session). + Thus, if `session.SaveChanges` fails, then the email reservation is Not rolled back automatically. + It is your responsibility to do so. + +* The compare-exchange value that was saved can be accessed in a query using the `CmpXchg` method: + + + +```csharp + using (var session = store.OpenSession()) + { + // Retrieve the user document that has the specified email: + var user = session.Query() + // Access the compare-exchange value using the CmpXchg method: + .Where(x => x.Id == RavenQuery.CmpXchg("ayende@ayende.com")) + .FirstOrDefault(); + } +``` + + +```csharp +using (var asyncSession = store.OpenAsyncSession()) +{ + var user = await asyncSession.Query() + .Where(x => x.Id == RavenQuery.CmpXchg("ayende@ayende.com")) + .FirstOrDefaultAsync(); +} +``` + + +```csharp +using (var session = store.OpenSession()) +{ + var user = session.Advanced + .DocumentQuery() + .WhereEquals("Id", CmpXchg.Value("ayende@ayende.com")) + .FirstOrDefault(); + } +``` + + +```sql +from "Users" +where id() == cmpxchg("ayende@ayende.com") +limit 0, 1 // take the first result +``` + + + +--- + +## Example II - Reserve a shared resource + +In the following example, we use compare-exchange to reserve a shared resource. +The scope is within the database group on a single cluster. + +The code also checks for clients which never release resources (i.e. due to failure) by using timeout. + + +```csharp +private class SharedResource +{ + public DateTime? ReservedUntil { get; set; } +} + +public void PrintWork() +{ + // Try to get hold of the printer resource + long reservationIndex = LockResource(store, "Printer/First-Floor", TimeSpan.FromMinutes(20)); + + try + { + // Do some work for the duration that was set (TimeSpan.FromMinutes(20)). + // + // In a distributed system (unlike a multi-threaded app), a process may crash or exit unexpectedly + // without releasing the resource it reserved (i.e. never reaching the 'finally' block). + // This can leave the resource locked indefinitely. + // + // To prevent that, each reservation includes a timeout (TimeSpan.FromMinutes(20)). + // If the process fails or exits, the resource becomes available again once the timeout expires. + // + // Important: Ensure the work completes within the timeout period. + // If it runs longer, another client may acquire the same resource at the same time. + } + finally + { + ReleaseResource(store, "Printer/First-Floor", reservationIndex); + } +} + +public long LockResource(IDocumentStore store, string resourceName, TimeSpan duration) +{ + while (true) + { + DateTime now = DateTime.UtcNow; + + SharedResource resource = new SharedResource + { + ReservedUntil = now.Add(duration) + }; + + CompareExchangeResult putResult = store.Operations.Send( + new PutCompareExchangeValueOperation(resourceName, resource, 0)); + + if (putResult.Successful) + { + // resourceName wasn't present - we managed to reserve + return putResult.Index; + } + + // At this point, another process owns the resource. + // But if that process crashed and never released the resource, the reservation may have expired, + // so we can try to take the lock by overwriting the value using the current index. + if (putResult.Value.ReservedUntil < now) + { + // Time expired - Update the existing key with the new value + CompareExchangeResult takeLockWithTimeoutResult = store.Operations.Send( + new PutCompareExchangeValueOperation( + resourceName, resource, putResult.Index + )); + + if (takeLockWithTimeoutResult.Successful) + { + return takeLockWithTimeoutResult.Index; + } + } + + // Wait a little bit and retry + Thread.Sleep(20); + } +} + +public void ReleaseResource(IDocumentStore store, string resourceName, long index) +{ + CompareExchangeResult deleteResult = store.Operations.Send( + new DeleteCompareExchangeValueOperation(resourceName, index)); + + // We have 2 options here: + // deleteResult.Successful is true - we managed to release resource + // deleteResult.Successful is false - someone else took the lock due to timeout +} +``` + diff --git a/docs/compare-exchange/content/_overview-java.mdx b/docs/compare-exchange/content/_overview-java.mdx new file mode 100644 index 0000000000..ffb07e4506 --- /dev/null +++ b/docs/compare-exchange/content/_overview-java.mdx @@ -0,0 +1,288 @@ +import Admonition from '@theme/Admonition'; +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +import CodeBlock from '@theme/CodeBlock'; + + + +* Compare-exchange items are **key/value pairs** where the key is a globally unique identifier in the database. + Items are versioned and managed at the cluster level. + +* Compare-exchange provides a built-in consensus mechanism for safe coordination across sessions and nodes. + It ensures global consistency in the database and prevents conflicts when multiple clients try to modify or reserve + the same resource, allowing you to: + * Enforce global uniqueness (e.g., prevent duplicate usernames or emails). + * Assign work to a single client or reserve a resource once. + * Handle concurrency safely, without external services or custom locking logic. + +* Compare-exchange items are also suitable for storing shared or global values that aren't tied to a specific document - + such as configuration flags, feature toggles, or reusable identifiers stored under a unique key. + However, [unlike regular documents](../compare-exchange/overview#why-not-use-regular-documents-to-enforce-uniqueness), + compare-exchange provides atomic updates, version-based conflict prevention, and Raft-based consistency for distributed safety. + +* Compare-exchange items are [not replicated externally](../compare-exchange/overview#why-compare-exchange-items-are-not-replicated-to-external-databases) to other databases. + +* In this article: + * [What compare-exchange items are](../compare-exchange/overview#what-compare-exchange-items-are) + * [Ways to create and manage compare-exchange items](../compare-exchange/overview#ways-to-create-and-manage-compare-exchange-items) + * [Why compare-exchange items are not replicated to external databases](../compare-exchange/overview#why-compare-exchange-items-are-not-replicated-to-external-databases) + * [Why not use regular documents to enforce uniqueness](../compare-exchange/overview#why-not-use-regular-documents-to-enforce-uniqueness) + * [Example I - Email address reservation](../compare-exchange/overview#example-i---email-address-reservation) + * [Example II - Reserve a shared resource](../compare-exchange/overview#example-ii---reserve-a-shared-resource) + + + +--- + +## What compare-exchange items are + +Compare-exchange items are key/value pairs where the key serves as a unique value across your database. + +* Each compare-exchange item contains: + * **A key** - A unique string identifier in the database scope. + * **A value** - Can be any value (a number, string, array, or any valid JSON object). + * **Metadata** - Optional data that is associated with the compare-exchange item. Must be a valid JSON object. + For example, the metadata can be used to set expiration time for the compare-exchange item. + Learn more in [compare-exchange expiration](../compare-exchange/cmpxchg-expiration). + * **Raft index** - The compare-exchange item's version. + Any change to the value or metadata will increase this number. + +* Creating and modifying a compare-exchange item follows the same principle as the [compare-and-swap](https://en.wikipedia.org/wiki/Compare-and-swap) operation in multi-threaded systems, + but in RavenDB, this concept is applied to a distributed environment across multiple nodes instead of within a single multi-threaded process. + These operations require cluster consensus to ensure consistency. + Once consensus is reached, the compare-exchange items are distributed through the Raft algorithm to all nodes in the database group. + +--- + +## Ways to create and manage compare-exchange items + +Compare exchange items can be created and managed using any of the following approaches: + +* **Document Store Operations** + You can create and manage compare-exchange items using _document store_ operations. + For example, see [Create items using a store operation](../compare-exchange/create-cmpxchg-items#create-items-using-a-store-operation). + +* **Cluster-Wide Sessions** + You can create and manage compare-exchange items from within a [Cluster-Wide session](../client-api/session/cluster-transaction/overview#cluster-wide-transaction-vs-single-node-transaction). + For example, see [Create items using a cluster-wide session](../compare-exchange/create-cmpxchg-items#create-items-using-a-cluster-wide-session). + When using a cluster-wide session, the compare-exchange item is created as part of the cluster-wide transaction. + If the session fails, the item creation also fails, and none of the nodes in the database group will store the new compare-exchange item. + +* **Atomic Guards** + When creating _documents_ using a cluster-wide session, RavenDB automatically creates [Atomic Guards](../compare-exchange/atomic-guards), + which are special compare-exchange items that guarantee ACID transactions. + See [Cluster-wide transaction vs. Single-node transaction](../client-api/session/cluster-transaction/overview#cluster-wide-transaction-vs-single-node-transaction) for a session comparison overview. + +* **Studio** + All compare-exchange items can also be managed from the **Compare-Exchange view** in the [Studio](../todo..): + + ![The compare-exchange view](../assets/the-cmpxchg-view.png) + + 1. Open the **Documents** section in the Studio sidebar. + 2. Click on the **Compare-Exchange** tab. + 3. This is a compare-exchange item. + In this view you can create, edit, and delete compare-exchange items. + +--- + +## Why compare-exchange items are not replicated to external databases + +* Each cluster defines its own policies and configurations, and should ideally have sole responsibility for managing its own documents. + Read [Consistency in a Globally Distributed System](https://ayende.com/blog/196769-B/data-ownership-in-a-distributed-system) + to learn more about why global database modeling is more efficient this way. + +* When creating a compare-exchange item, a Raft consensus is required from the nodes in the database group. + Externally replicating such data is problematic because the target database may reside within a cluster that is in an + unstable state where Raft decisions cannot be made. In such a state, the compare-exchange item will not be persisted in the target database. + +* Conflicts between documents that occur between two databases are resolved using the documents' change-vector. + Compare-exchange conflicts cannot be resolved in the same way, as they lack a similar conflict resolution mechanism. + +* To ensure unique values between two databases without using compare-exchange items see [Example III](../compare-exchange/overview#example-iii---ensuring-unique-values-without-using-compare-exchange). + +* Learn more about Replication in RavenDB in [Replication overview](../server/clustering/replication/replication-overview). + For details about what is and what isn't replicated in [What is Replicated](../server/ongoing-tasks/external-replication#what-is-replicated). + + --- + + ## Why not use regular documents to enforce uniqueness + +* You might consider storing a document with a predictable ID (for example, _phones/123456_) as a way to enforce uniqueness, + and then checking for its existence before allowing another document to use the same value. + +* While this might work in a single-node setup or with external replication, + it does not reliably enforce uniqueness in a clustered environment. + +* If a node was not part of the cluster when the document was created, it might not be aware of its existence when it comes back online. + In such cases, attempting to load the document on this node may return _null_, leading to duplicate values being inserted. + +* To reliably enforce uniqueness across all cluster nodes, you must use compare-exchange items, + which are designed for this purpose and ensure global consistency. + +--- + +## Example I - Email address reservation + +The following example shows how to use compare-exchange to create documents with unique values. +The scope is within the database group on a single cluster. + + +```java +String email = "user@example.com"; + +User user = new User(); +user.setEmail(email); + +try (IDocumentSession session = store.openSession()) { + session.store(user); + + // At this point, the user object has a document ID assigned by the session. + + // Try to reserve the user email using a compare-exchange item. + // Note: This 'put compare-exchange operation' is not part of the session transaction, + // It is a separate, cluster-wide reservation. + CompareExchangeResult cmpXchgResult = store + .operations().send( + // parameters passed to the operation: + // email - the unique key of the compare-exchange item + // user.getId() - the value associated with the key + // 0 - pass `0` to ensure the item is created only if it doesn't already exist. + // If a compare-exchange item with the given key already exists, the operation will fail. + new PutCompareExchangeValueOperation<>(email, user.getId(), 0)); + + if (!cmpXchgResult.isSuccessful()) { + throw new RuntimeException("Email is already in use"); + } + + // At this point, the email has been successfully reserved/saved. + // We can now save the user document to the database. + session.saveChanges(); +} +``` + + +**Implications**: + +* This compare-exchange item was [created as an operation](../compare-exchange/create-cmpxchg-items#create-items-using-a-store-operation) + rather than with a [cluster-wide session](../compare-exchange/create-cmpxchg-items#create-items-using-a-cluster-wide-session). + Thus, if `session.SaveChanges` fails, then the email reservation is Not rolled back automatically. + It is your responsibility to do so. + +* The compare-exchange value that was saved can be accessed in a query using the `CmpXchg` method: + + + + + +{`try (IDocumentSession session = store.openSession()) { + List query = session.advanced().rawQuery(User.class, + "from Users as s where id() == cmpxchg(\\"ayende@ayende.com\\")") + .toList(); + + IDocumentQuery q = session.advanced() + .documentQuery(User.class) + .whereEquals("id", CmpXchg.value("ayende@ayende.com")); +} +`} + + + +```sql +from "Users" +where id() == cmpxchg("ayende@ayende.com") +limit 0, 1 // take the first result +``` + + + +--- + +## Example II - Reserve a shared resource + +In the following example, we use compare-exchange to reserve a shared resource. +The scope is within the database group on a single cluster. + +The code also checks for clients which never release resources (i.e. due to failure) by using timeout. + + + +{`private class SharedResource \{ + private LocalDateTime reservedUntil; + + public LocalDateTime getReservedUntil() \{ + return reservedUntil; + \} + + public void setReservedUntil(LocalDateTime reservedUntil) \{ + this.reservedUntil = reservedUntil; + \} +\} + +public void printWork() throws InterruptedException \{ + // Try to get hold of the printer resource + long reservationIndex = lockResource(store, "Printer/First-Floor", Duration.ofMinutes(20)); + + try \{ + // Do some work for the duration that was set (TimeSpan.FromMinutes(20)). + // + // In a distributed system (unlike a multi-threaded app), a process may crash or exit unexpectedly + // without releasing the resource it reserved (i.e. never reaching the 'finally' block). + // This can leave the resource locked indefinitely. + // + // To prevent that, each reservation includes a timeout (TimeSpan.FromMinutes(20)). + // If the process fails or exits, the resource becomes available again once the timeout expires. + // + // Important: Ensure the work completes within the timeout period. + // If it runs longer, another client may acquire the same resource at the same time. + \} finally \{ + releaseResource(store, "Printer/First-Floor", reservationIndex); + \} +\} + +public long lockResource(IDocumentStore store, String resourceName, Duration duration) throws InterruptedException \{ + while (true) \{ + LocalDateTime now = LocalDateTime.now(); + + SharedResource resource = new SharedResource(); + resource.setReservedUntil(now.plus(duration)); + + CompareExchangeResult saveResult = + store.operations().send( + new PutCompareExchangeValueOperation(resourceName, resource, 0)); + + if (saveResult.isSuccessful()) \{ + // resourceName wasn't present - we managed to reserve + return saveResult.getIndex(); + \} + + // At this point, another process owns the resource. + // But if that process crashed and never released the resource, the reservation may have expired, + // so we can try to take the lock by overwriting the value using the current index. + if (saveResult.getValue().reservedUntil.isBefore(now)) \{ + // Time expired - Update the existing key with the new value + CompareExchangeResult takeLockWithTimeoutResult = + store.operations().send( + new PutCompareExchangeValueOperation<>(resourceName, resource, saveResult.getIndex())); + + if (takeLockWithTimeoutResult.isSuccessful()) \{ + return takeLockWithTimeoutResult.getIndex(); + \} + \} + + // Wait a little bit and retry + Thread.sleep(20); + \} +\} + +public void releaseResource(IDocumentStore store, String resourceName, long index) \{ + CompareExchangeResult deleteResult = store + .operations().send( + new DeleteCompareExchangeValueOperation<>(SharedResource.class, resourceName, index)); + + // We have 2 options here: + // deleteResult.Successful is true - we managed to release resource + // deleteResult.Successful is false - someone else took the lock due to timeout +\} +`} + + diff --git a/docs/compare-exchange/content/_overview-php.mdx b/docs/compare-exchange/content/_overview-php.mdx new file mode 100644 index 0000000000..03f0abab9f --- /dev/null +++ b/docs/compare-exchange/content/_overview-php.mdx @@ -0,0 +1,305 @@ +import Admonition from '@theme/Admonition'; +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +import CodeBlock from '@theme/CodeBlock'; + + + +* Compare-exchange items are **key/value pairs** where the key is a globally unique identifier in the database. + Items are versioned and managed at the cluster level. + +* Compare-exchange provides a built-in consensus mechanism for safe coordination across sessions and nodes. + It ensures global consistency in the database and prevents conflicts when multiple clients try to modify or reserve + the same resource, allowing you to: + * Enforce global uniqueness (e.g., prevent duplicate usernames or emails). + * Assign work to a single client or reserve a resource once. + * Handle concurrency safely, without external services or custom locking logic. + +* Compare-exchange items are also suitable for storing shared or global values that aren't tied to a specific document - + such as configuration flags, feature toggles, or reusable identifiers stored under a unique key. + However, [unlike regular documents](../compare-exchange/overview#why-not-use-regular-documents-to-enforce-uniqueness), + compare-exchange provides atomic updates, version-based conflict prevention, and Raft-based consistency for distributed safety. + +* Compare-exchange items are [not replicated externally](../compare-exchange/overview#why-compare-exchange-items-are-not-replicated-to-external-databases) to other databases. + +* In this article: + * [What compare-exchange items are](../compare-exchange/overview#what-compare-exchange-items-are) + * [Ways to create and manage compare-exchange items](../compare-exchange/overview#ways-to-create-and-manage-compare-exchange-items) + * [Why compare-exchange items are not replicated to external databases](../compare-exchange/overview#why-compare-exchange-items-are-not-replicated-to-external-databases) + * [Why not use regular documents to enforce uniqueness](../compare-exchange/overview#why-not-use-regular-documents-to-enforce-uniqueness) + * [Example I - Email address reservation](../compare-exchange/overview#example-i---email-address-reservation) + * [Example II - Reserve a shared resource](../compare-exchange/overview#example-ii---reserve-a-shared-resource) + + + +--- + +## What compare-exchange items are + +Compare-exchange items are key/value pairs where the key serves as a unique value across your database. + +* Each compare-exchange item contains: + * **A key** - A unique string identifier in the database scope. + * **A value** - Can be any value (a number, string, array, or any valid JSON object). + * **Metadata** - Optional data that is associated with the compare-exchange item. Must be a valid JSON object. + For example, the metadata can be used to set expiration time for the compare-exchange item. + Learn more in [compare-exchange expiration](../compare-exchange/cmpxchg-expiration). + * **Raft index** - The compare-exchange item's version. + Any change to the value or metadata will increase this number. + +* Creating and modifying a compare-exchange item follows the same principle as the [compare-and-swap](https://en.wikipedia.org/wiki/Compare-and-swap) operation in multi-threaded systems, + but in RavenDB, this concept is applied to a distributed environment across multiple nodes instead of within a single multi-threaded process. + These operations require cluster consensus to ensure consistency. + Once consensus is reached, the compare-exchange items are distributed through the Raft algorithm to all nodes in the database group. + +--- + +## Ways to create and manage compare-exchange items + +Compare exchange items can be created and managed using any of the following approaches: + +* **Document Store Operations** + You can create and manage compare-exchange items using _document store_ operations. + For example, see [Create items using a store operation](../compare-exchange/create-cmpxchg-items#create-items-using-a-store-operation). + +* **Cluster-Wide Sessions** + You can create and manage compare-exchange items from within a [Cluster-Wide session](../client-api/session/cluster-transaction/overview#cluster-wide-transaction-vs-single-node-transaction). + For example, see [Create items using a cluster-wide session](../compare-exchange/create-cmpxchg-items#create-items-using-a-cluster-wide-session). + When using a cluster-wide session, the compare-exchange item is created as part of the cluster-wide transaction. + If the session fails, the item creation also fails, and none of the nodes in the database group will store the new compare-exchange item. + +* **Atomic Guards** + When creating _documents_ using a cluster-wide session, RavenDB automatically creates [Atomic Guards](../compare-exchange/atomic-guards), + which are special compare-exchange items that guarantee ACID transactions. + See [Cluster-wide transaction vs. Single-node transaction](../client-api/session/cluster-transaction/overview#cluster-wide-transaction-vs-single-node-transaction) for a session comparison overview. + +* **Studio** + All compare-exchange items can also be managed from the **Compare-Exchange view** in the [Studio](../todo..): + + ![The compare-exchange view](../assets/the-cmpxchg-view.png) + + 1. Open the **Documents** section in the Studio sidebar. + 2. Click on the **Compare-Exchange** tab. + 3. This is a compare-exchange item. + In this view you can create, edit, and delete compare-exchange items. + +--- + +## Why compare-exchange items are not replicated to external databases + +* Each cluster defines its own policies and configurations, and should ideally have sole responsibility for managing its own documents. + Read [Consistency in a Globally Distributed System](https://ayende.com/blog/196769-B/data-ownership-in-a-distributed-system) + to learn more about why global database modeling is more efficient this way. + +* When creating a compare-exchange item, a Raft consensus is required from the nodes in the database group. + Externally replicating such data is problematic because the target database may reside within a cluster that is in an + unstable state where Raft decisions cannot be made. In such a state, the compare-exchange item will not be persisted in the target database. + +* Conflicts between documents that occur between two databases are resolved using the documents' change-vector. + Compare-exchange conflicts cannot be resolved in the same way, as they lack a similar conflict resolution mechanism. + +* To ensure unique values between two databases without using compare-exchange items see [Example III](../compare-exchange/overview#example-iii---ensuring-unique-values-without-using-compare-exchange). + +* Learn more about Replication in RavenDB in [Replication overview](../server/clustering/replication/replication-overview). + For details about what is and what isn't replicated in [What is Replicated](../server/ongoing-tasks/external-replication#what-is-replicated). + +--- + +## Why not use regular documents to enforce uniqueness + +* You might consider storing a document with a predictable ID (for example, _phones/123456_) as a way to enforce uniqueness, + and then checking for its existence before allowing another document to use the same value. + +* While this might work in a single-node setup or with external replication, + it does not reliably enforce uniqueness in a clustered environment. + +* If a node was not part of the cluster when the document was created, it might not be aware of its existence when it comes back online. + In such cases, attempting to load the document on this node may return _null_, leading to duplicate values being inserted. + +* To reliably enforce uniqueness across all cluster nodes, you must use compare-exchange items, + which are designed for this purpose and ensure global consistency. + +--- + +## Example I - Email address reservation + +The following example shows how to use compare-exchange to create documents with unique values. +The scope is within the database group on a single cluster. + + + +{`$email = "user@example.com"; + +$user = new User(); +$user->setEmail($email); + +$session = $store->openSession(); +try \{ + $session->store($user); + + // At this point, the user object has a document ID assigned by the session. + + // Try to reserve the user email using a compare-exchange item. + // Note: This 'put compare-exchange operation' is not part of the session transaction, + // It is a separate, cluster-wide reservation. + + /** @var CompareExchangeResult $cmpXchgResult */ + $cmpXchgResult = $store->operations()->send( + // Parameters passed to the operation: + // $email - the unique key of the compare-exchange item + // $user->getId() - the value associated with the key + // 0 - ensures the item is created only if it doesn't already exist + // If a compare-exchange item with the given key already exists, the operation will fail. + new PutCompareExchangeValueOperation($email, $user->getId(), 0)); + + if (!$cmpXchgResult->isSuccessful()) \{ + throw new RuntimeException("Email is already in use"); + \} + + // At this point, the email has been successfully reserved/saved. + // We can now save the user document to the database. + $session->saveChanges(); +\} finally \{ + $session->close(); +\} +`} + + + +**Implications**: + +* This compare-exchange item was [created as an operation](../compare-exchange/create-cmpxchg-items#create-items-using-a-store-operation) + rather than with a [cluster-wide session](../compare-exchange/create-cmpxchg-items#create-items-using-a-cluster-wide-session). + Thus, if `session.SaveChanges` fails, then the email reservation is Not rolled back automatically. + It is your responsibility to do so. + +* The compare-exchange value that was saved can be accessed in a query using the `CmpXchg` method: + + + + +{`$query = $session->advanced()->rawQuery(User::class, + "from Users as s where id() == cmpxchg(\\"emails/ayende@ayende.com\\")") + ->toList(); +`} + + + + +{`$q = $session->advanced() + ->documentQuery(User::class) + ->whereEquals("id", CmpXchg::value("emails/ayende@ayende.com")); +`} + + + + +{`from Users as s where id() == cmpxchg("emails/ayende@ayende.com") +`} + + + + +--- + +## Example II - Reserve a shared resource + +In the following example, we use compare-exchange to reserve a shared resource. +The scope is within the database group on a single cluster. + +The code also checks for clients which never release resources (i.e. due to failure) by using timeout. + + + +{`class SharedResource +\{ + private ?DateTime $reservedUntil = null; + + public function getReservedUntil(): ?DateTime + \{ + return $this->reservedUntil; + \} + + public function setReservedUntil(?DateTime $reservedUntil): void + \{ + $this->reservedUntil = $reservedUntil; + \} +\} + +class CompareExchangeSharedResource +\{ + private ?DocumentStore $store = null; + + public function printWork(): void + \{ + // Try to get hold of the printer resource + $reservationIndex = $this->lockResource($this->store, "Printer/First-Floor", Duration::ofMinutes(20)); + + try \{ + // Do some work for the duration that was set (TimeSpan.FromMinutes(20)). + // + // In a distributed system (unlike a multi-threaded app), a process may crash or exit unexpectedly + // without releasing the resource it reserved (i.e. never reaching the 'finally' block). + // This can leave the resource locked indefinitely. + // + // To prevent that, each reservation includes a timeout (TimeSpan.FromMinutes(20)). + // If the process fails or exits, the resource becomes available again once the timeout expires. + // + // Important: Ensure the work completes within the timeout period. + // If it runs longer, another client may acquire the same resource at the same time. + \} finally \{ + $this->releaseResource($this->store, "Printer/First-Floor", $reservationIndex); + \} + \} + + /** throws InterruptedException */ + public function lockResource(DocumentStoreInterface $store, ?string $resourceName, Duration $duration): int + \{ + while (true) \{ + $now = new DateTime(); + + $resource = new SharedResource(); + $resource->setReservedUntil($now->add($duration->toDateInterval())); + + /** @var CompareExchangeResult $saveResult */ + $saveResult = $store->operations()->send( + new PutCompareExchangeValueOperation($resourceName, $resource, 0)); + + if ($saveResult->isSuccessful()) \{ + // resourceName wasn't present - we managed to reserve + return $saveResult->getIndex(); + \} + + // At this point, another process owns the resource. + // But if that process crashed and never released the resource, the reservation may have expired, + // so we can try to take the lock by overwriting the value using the current index. + if ($saveResult->getValue()->getReservedUntil() < $now) \{ + // Time expired - Update the existing key with the new value + /** @var CompareExchangeResult takeLockWithTimeoutResult */ + $takeLockWithTimeoutResult = $store->operations()->send( + new PutCompareExchangeValueOperation($resourceName, $resource, $saveResult->getIndex())); + + if ($takeLockWithTimeoutResult->isSuccessful()) \{ + return $takeLockWithTimeoutResult->getIndex(); + \} + \} + + // Wait a little bit and retry + usleep(20000); + \} + \} + + public function releaseResource(DocumentStoreInterface $store, ?string $resourceName, int $index): void + \{ + $deleteResult = $store->operations()->send( + new DeleteCompareExchangeValueOperation(SharedResource::class, $resourceName, $index) + ); + + // We have 2 options here: + // $deleteResult->isSuccessful is true - we managed to release resource + // $deleteResult->isSuccessful is false - someone else took the lock due to timeout + \} +\} +`} + + diff --git a/docs/compare-exchange/content/_overview-python.mdx b/docs/compare-exchange/content/_overview-python.mdx new file mode 100644 index 0000000000..86c6644d25 --- /dev/null +++ b/docs/compare-exchange/content/_overview-python.mdx @@ -0,0 +1,256 @@ +import Admonition from '@theme/Admonition'; +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +import CodeBlock from '@theme/CodeBlock'; + + + +* Compare-exchange items are **key/value pairs** where the key is a globally unique identifier in the database. + Items are versioned and managed at the cluster level. + +* Compare-exchange provides a built-in consensus mechanism for safe coordination across sessions and nodes. + It ensures global consistency in the database and prevents conflicts when multiple clients try to modify or reserve + the same resource, allowing you to: + * Enforce global uniqueness (e.g., prevent duplicate usernames or emails). + * Assign work to a single client or reserve a resource once. + * Handle concurrency safely, without external services or custom locking logic. + +* Compare-exchange items are also suitable for storing shared or global values that aren't tied to a specific document - + such as configuration flags, feature toggles, or reusable identifiers stored under a unique key. + However, [unlike regular documents](../compare-exchange/overview#why-not-use-regular-documents-to-enforce-uniqueness), + compare-exchange provides atomic updates, version-based conflict prevention, and Raft-based consistency for distributed safety. + +* Compare-exchange items are [not replicated externally](../compare-exchange/overview#why-compare-exchange-items-are-not-replicated-to-external-databases) to other databases. + +* In this article: + * [What compare-exchange items are](../compare-exchange/overview#what-compare-exchange-items-are) + * [Ways to create and manage compare-exchange items](../compare-exchange/overview#ways-to-create-and-manage-compare-exchange-items) + * [Why compare-exchange items are not replicated to external databases](../compare-exchange/overview#why-compare-exchange-items-are-not-replicated-to-external-databases) + * [Why not use regular documents to enforce uniqueness](../compare-exchange/overview#why-not-use-regular-documents-to-enforce-uniqueness) + * [Example I - Email address reservation](../compare-exchange/overview#example-i---email-address-reservation) + * [Example II - Reserve a shared resource](../compare-exchange/overview#example-ii---reserve-a-shared-resource) + + + +--- + +## What compare-exchange items are + +Compare-exchange items are key/value pairs where the key serves as a unique value across your database. + +* Each compare-exchange item contains: + * **A key** - A unique string identifier in the database scope. + * **A value** - Can be any value (a number, string, array, or any valid JSON object). + * **Metadata** - Optional data that is associated with the compare-exchange item. Must be a valid JSON object. + For example, the metadata can be used to set expiration time for the compare-exchange item. + Learn more in [compare-exchange expiration](../compare-exchange/cmpxchg-expiration). + * **Raft index** - The compare-exchange item's version. + Any change to the value or metadata will increase this number. + +* Creating and modifying a compare-exchange item follows the same principle as the [compare-and-swap](https://en.wikipedia.org/wiki/Compare-and-swap) operation in multi-threaded systems, + but in RavenDB, this concept is applied to a distributed environment across multiple nodes instead of within a single multi-threaded process. + These operations require cluster consensus to ensure consistency. + Once consensus is reached, the compare-exchange items are distributed through the Raft algorithm to all nodes in the database group. + +--- + +## Ways to create and manage compare-exchange items + +Compare exchange items can be created and managed using any of the following approaches: + +* **Document Store Operations** + You can create and manage compare-exchange items using _document store_ operations. + For example, see [Create items using a store operation](../compare-exchange/create-cmpxchg-items#create-items-using-a-store-operation). + +* **Cluster-Wide Sessions** + You can create and manage compare-exchange items from within a [Cluster-Wide session](../client-api/session/cluster-transaction/overview#cluster-wide-transaction-vs-single-node-transaction). + For example, see [Create items using a cluster-wide session](../compare-exchange/create-cmpxchg-items#create-items-using-a-cluster-wide-session). + When using a cluster-wide session, the compare-exchange item is created as part of the cluster-wide transaction. + If the session fails, the item creation also fails, and none of the nodes in the database group will store the new compare-exchange item. + +* **Atomic Guards** + When creating _documents_ using a cluster-wide session, RavenDB automatically creates [Atomic Guards](../compare-exchange/atomic-guards), + which are special compare-exchange items that guarantee ACID transactions. + See [Cluster-wide transaction vs. Single-node transaction](../client-api/session/cluster-transaction/overview#cluster-wide-transaction-vs-single-node-transaction) for a session comparison overview. + +* **Studio** + All compare-exchange items can also be managed from the **Compare-Exchange view** in the [Studio](../todo..): + + ![The compare-exchange view](../assets/the-cmpxchg-view.png) + + 1. Open the **Documents** section in the Studio sidebar. + 2. Click on the **Compare-Exchange** tab. + 3. This is a compare-exchange item. + In this view you can create, edit, and delete compare-exchange items. + +--- + +## Why compare-exchange items are not replicated to external databases + +* Each cluster defines its own policies and configurations, and should ideally have sole responsibility for managing its own documents. + Read [Consistency in a Globally Distributed System](https://ayende.com/blog/196769-B/data-ownership-in-a-distributed-system) + to learn more about why global database modeling is more efficient this way. + +* When creating a compare-exchange item, a Raft consensus is required from the nodes in the database group. + Externally replicating such data is problematic because the target database may reside within a cluster that is in an + unstable state where Raft decisions cannot be made. In such a state, the compare-exchange item will not be persisted in the target database. + +* Conflicts between documents that occur between two databases are resolved using the documents' change-vector. + Compare-exchange conflicts cannot be resolved in the same way, as they lack a similar conflict resolution mechanism. + +* To ensure unique values between two databases without using compare-exchange items see [Example III](../compare-exchange/overview#example-iii---ensuring-unique-values-without-using-compare-exchange). + +* Learn more about Replication in RavenDB in [Replication overview](../server/clustering/replication/replication-overview). + For details about what is and what isn't replicated in [What is Replicated](../server/ongoing-tasks/external-replication#what-is-replicated). + +--- + +## Why not use regular documents to enforce uniqueness + +* You might consider storing a document with a predictable ID (for example, _phones/123456_) as a way to enforce uniqueness, + and then checking for its existence before allowing another document to use the same value. + +* While this might work in a single-node setup or with external replication, + it does not reliably enforce uniqueness in a clustered environment. + +* If a node was not part of the cluster when the document was created, it might not be aware of its existence when it comes back online. + In such cases, attempting to load the document on this node may return _null_, leading to duplicate values being inserted. + +* To reliably enforce uniqueness across all cluster nodes, you must use compare-exchange items, + which are designed for this purpose and ensure global consistency. + +--- + +## Example I - Email address reservation + +The following example shows how to use compare-exchange to create documents with unique values. +The scope is within the database group on a single cluster. + + + +{`email = "user@example.com" + +user = User(email=email) + +with store.open_session() as session: + session.store(user) + # At this point, the user object has a document ID assigned by the session. + + # Try to reserve the user email using a compare-exchange item. + # Note: This 'put compare-exchange operation' is not part of the session transaction, + # It is a separate, cluster-wide reservation. + cmp_xchg_result = store.operations.send( + # Parameters passed to the operation: + # email - the unique key of the compare-exchange item + # user.Id - the value associated with the key + # 0 - ensures the item is created only if it doesn't already exist + # If a compare-exchange item with the given key already exists, the operation will fail. + PutCompareExchangeValueOperation(email, user.Id, 0) + ) + + if cmp_xchg_result.successful is False: + raise RuntimeError("Email is already in use") + + # At this point, the email has been successfully reserved/saved. + # We can now save the user document to the database. + session.save_changes() +`} + + + +**Implications**: + +* This compare-exchange item was [created as an operation](../compare-exchange/create-cmpxchg-items#create-items-using-a-store-operation) + rather than with a [cluster-wide session](../compare-exchange/create-cmpxchg-items#create-items-using-a-cluster-wide-session). + Thus, if `session.SaveChanges` fails, then the email reservation is Not rolled back automatically. + It is your responsibility to do so. + +* The compare-exchange value that was saved can be accessed in a query using the `CmpXchg` method: + + + + +{`query = sesion.query(object_type=User).where_equals("Id", CmpXchg.value("emails/ayende@ayende.com")) +`} + + + + +{`from Users as s where id() == cmpxchg("emails/ayende@ayende.com") +`} + + + + +--- + +## Example II - Reserve a shared resource + +In the following example, we use compare-exchange to reserve a shared resource. +The scope is within the database group on a single cluster. + +The code also checks for clients which never release resources (i.e. due to failure) by using timeout. + + + +{`class SharedResource: + def __init__(self, reserved_until: datetime = None): + self.reserved_until = reserved_until + +def print_work() -> None: + # Try to get hold of the printer resource + reservation_index = lock_resource(store, "Printer/First-Floor", timedelta(minutes=20)) + + try: + ... + # Do some work for the duration that was set (TimeSpan.FromMinutes(20)). + # + # In a distributed system (unlike a multi-threaded app), a process may crash or exit unexpectedly + # without releasing the resource it reserved (i.e. never reaching the 'finally' block). + # This can leave the resource locked indefinitely. + # + # To prevent that, each reservation includes a timeout (TimeSpan.FromMinutes(20)). + # If the process fails or exits, the resource becomes available again once the timeout expires. + # + # Important: Ensure the work completes within the timeout period. + # If it runs longer, another client may acquire the same resource at the same time. + finally: + release_resource(store, "Printer/First-Floor", reservation_index) + +def lock_resource(document_store: DocumentStore, resource_name: str, duration: timedelta): + while True: + now = datetime.utcnow() + + resource = SharedResource(reserved_until=now + duration) + save_result = document_store.operations.send( + PutCompareExchangeValueOperation(resource_name, resource, 0) + ) + + if save_result.successful: + # resource_name wasn't present - we managed to reserve + return save_result.index + + # At this point, another process owns the resource. + # But if that process crashed and never released the resource, the reservation may have expired, + # so we can try to take the lock by overwriting the value using the current index. + if save_result.value.reserved_until < now: + # Time expired - Update the existing key with new value + take_lock_with_timeout_result = document_store.operations.send( + PutCompareExchangeValueOperation(resource_name, resource, save_result.index) + ) + + if take_lock_with_timeout_result.successful: + return take_lock_with_timeout_result.index + + # Wait a little bit and retry + time.sleep(0.02) + +def release_resource(store: DocumentStore, resource_name: str, index: int) -> None: + delete_result = store.operations.send(DeleteCompareExchangeValueOperation(resource_name, index)) + + # We have 2 options here: + # delete_result.successful is True - we managed to release resource + # delete_result.successful is False - someone else took the lock due to timeout +`} + + diff --git a/docs/compare-exchange/content/_update-cmpxchg-item-csharp.mdx b/docs/compare-exchange/content/_update-cmpxchg-item-csharp.mdx new file mode 100644 index 0000000000..41d46fc4f3 --- /dev/null +++ b/docs/compare-exchange/content/_update-cmpxchg-item-csharp.mdx @@ -0,0 +1,236 @@ +import Admonition from '@theme/Admonition'; +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +import CodeBlock from '@theme/CodeBlock'; + + + +* An existing compare-exchange item can be updated in the following ways: + * Using a cluster-wide session + * Using a store operation + * Using the Studio + +* In this article: + * [Update compare-exchange item using a **cluster-wide session**](../compare-exchange/update-cmpxchg-item#update-compare-exchange-item-using-a-cluster-wide-session) + * [Update compare-exchange item using a **store operation**](../compare-exchange/update-cmpxchg-item#update-compare-exchange-item-using-a-store-operation) + * [Update compare-exchange item using the **Studio**](../compare-exchange/update-cmpxchg-item#update-compare-exchange-item-using-the-studio) + * [Syntax](../compare-exchange/update-cmpxchg-item#syntax) + + + +--- + +## Update compare-exchange item using a cluster-wide session + + + +```csharp +// The session must be opened in cluster-wide mode. +using (var session = store.OpenSession( + new SessionOptions { TransactionMode = TransactionMode.ClusterWide })) +{ + // Get the existing item from the server + // ===================================== + + CompareExchangeValue item = session.Advanced.ClusterTransaction + .GetCompareExchangeValue("user1-name@example.com"); + + // The item is now tracked in the session's internal state + // Modify the value / metadata as needed + // ===================================== + + item.Value = "users/99"; + item.Metadata["email-type"] = "work email"; + item.Metadata["updated-at"] = DateTime.UtcNow.ToString("o"); + + // Save changes for the update to take effect + // ========================================== + + session.SaveChanges(); + + // A 'ClusterTransactionConcurrencyException' is thrown if the compare-exchange item + // no longer exists on the server at the time of calling saveChanges(). + // This can happen if another client deletes or modifies the item before your update is saved. +} +``` + + +```csharp +// The session must be opened in cluster-wide mode. +using (var asyncSession = store.OpenAsyncSession( + new SessionOptions { TransactionMode = TransactionMode.ClusterWide })) +{ + // Get the existing item from the server + // ===================================== + + CompareExchangeValue item = await asyncSession.Advanced.ClusterTransaction + .GetCompareExchangeValueAsync("user1-name@example.com"); + + // The item is now tracked in the session's internal state + // Modify the value / metadata as needed + // ===================================== + + item.Value = "users/99"; + item.Metadata["email-type"] = "work email"; + item.Metadata["updated-at"] = DateTime.UtcNow.ToString("o"); + + // Save changes for the update to take effect + // ========================================== + + await asyncSession.SaveChangesAsync(); +} +``` + + + +--- + +## Update compare-exchange item using a store operation + +* Use `PutCompareExchangeValueOperation` to **update the _value_ and/or _metadata_** of an existing compare-exchange item. + This operation is also used to create new compare-exchange items, see [Create compare-exchange item](../compare-exchange/create-cmpxchg-items). + +* To perform an update, provide: + * The existing key + * A new value and/or metadata + * The expected index (version) of the item, which must match the current version stored on the server. + +* The update will succeed only if the index you provide matches the current index stored on the server for that key. + This ensures that the item hasn’t been modified by another client since you last read it. + +* If the index does not match, or if the specified key does not exist: + * The item is not updated. + * No exception is thrown. + * The operation result has `Successful = false`. + +* If the update is successful: + * The value and/or metadata are updated. + * The server increments the index number of the item. + * The operation result has `Successful = true` and will contain the new value and new index. + + + +```csharp +// Get the existing item from the server +// ===================================== + +CompareExchangeValue item = store.Operations.Send( + new GetCompareExchangeValueOperation("user1-name@example.com")); + +// Modify the value / metadata as needed +// ===================================== + +var newValue = "users/99"; // Modify the value to be associated with the unique email key +var newMetadata = new MetadataAsDictionary +{ + { "email-type", "work email" }, + { "updated-at", DateTime.UtcNow.ToString("o") } // Add entries / modify the metadata +}; + +// Update the item +// =============== + +// The put operation will succeed only if the 'index' of the compare-exchange item +// has not changed between the read and write operations. +CompareExchangeResult putResult = store.Operations.Send( + new PutCompareExchangeValueOperation(item.Key, newValue, item.Index, newMetadata)); + +// Check results +// ============= + +bool putResultSuccessful = putResult.Successful; // Has operation succeeded +long putResultIndex = putResult.Index; // The new version number assigned if update succeeded +``` + + +```csharp +// Get the existing item from the server +// ===================================== + +CompareExchangeValue item = await store.Operations.SendAsync( + new GetCompareExchangeValueOperation("user1-name@example.com")); + +// Modify the value / metadata as needed +// ===================================== + +var newValue = "users/99"; // Modify the value associated with the unique email key +var newMetadata = new MetadataAsDictionary +{ + { "email-type", "work email" }, + { "updated-at", DateTime.UtcNow.ToString("o") } // Add entries / modify the metadata +}; + +// Update the item +// =============== + +// The put operation will succeed only if the 'index' of the compare-exchange item +// has not changed between the read and write operations. +CompareExchangeResult putResult = await store.Operations.SendAsync( + new PutCompareExchangeValueOperation(item.Key, newValue, item.Index, newMetadata)); + +// Check results +// ============= + +bool putResultSuccessful = putResult.Successful; // Has operation succeeded +long putResultIndex = putResult.Index; // The new version number assigned if update succeeded +``` + + + +--- + +## Update compare-exchange item using the Studio + +You can update any existing compare-exchange item from the Studio. + +![The compare-exchange view](../assets/update-cmpxchg-1.png) + +1. Go to **Documents > Compare Exchange**. +2. Click to edit a compare-exchange item. + +--- + +![The compare-exchange view](../assets/update-cmpxchg-2.png) + +1. Enter the value. +2. Enter the metadata (optional). +3. Click _Save_ to update the item. + +--- + +## Syntax + +**Method**: + + +```csharp +public PutCompareExchangeValueOperation( + string key, T value, long index, IMetadataDictionary metadata = null) +``` + + +| Parameter | Type | Description | +|--------------|----------|-------------------------------------------------------------------------------------------------------------------------| +| **key** | `string` | The unique identifier in the database scope. | +| **value** | `T` | The value to be saved for the specified _key_.
Can be any object (number, string, array, or any valid JSON object). | +| **index** | `long` | The current version of the item when updating an existing item.
Pass `0` to [create a new key](../compare-exchange/create-cmpxchg-items). | +| **metadata** | `IMetadataDictionary` | Metadata to be saved for the specified key.
Must be a valid JSON object. | + +**Returned object**: + + +```csharp +public class CompareExchangeResult +{ + public bool Successful; + public T Value; + public long Index; +} +``` + + +| Return Value | Type | Description | +|---------------|--------|---------------------------------------------------------------------------------------------------------------------------------------------------| +| **Successful**| `bool` |
  • _true_ if the put operation has completed successfully.
  • _false_ if the put operation failed.
| +| **Value** | `T` |
  • Upon success - the value of the compare-exchange item that was saved.
  • Upon failure - the existing value on the server.
| +| **Index** | `long` |
  • Upon success - the updated version (the incremented index of the modified item).
  • Upon failure (if indexes do not match) - the existing version from the server.
| diff --git a/docs/compare-exchange/content/_update-cmpxchg-item-java.mdx b/docs/compare-exchange/content/_update-cmpxchg-item-java.mdx new file mode 100644 index 0000000000..7647be2255 --- /dev/null +++ b/docs/compare-exchange/content/_update-cmpxchg-item-java.mdx @@ -0,0 +1,140 @@ +import Admonition from '@theme/Admonition'; +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +import CodeBlock from '@theme/CodeBlock'; + + + +* Use `PutCompareExchangeValueOperation` to **update the _value_ and/or _metadata_** of an existing compare-exchange item. + This operation is also used to create new compare-exchange items, see [Create compare-exchange item](../compare-exchange/create-cmpxchg-items). + +* To perform an update, provide: + * The existing key + * A new value and/or metadata + * The expected index (version) of the item, which must match the current version stored on the server. + +* The update will succeed only if the index you provide matches the current index stored on the server for that key. + This ensures that the item hasn’t been modified by another client since you last read it. + +* If the index does not match, or if the specified key does not exist: + * The item is not updated. + * No exception is thrown. + * The operation result has `Successful = false`. + +* If the update is successful: + * The value and/or metadata are updated. + * The server increments the index number of the item. + * The operation result has `Successful = true` and will contain the new value and new index. + +--- + +* In this article: + * [Update compare-exchange item using a **store operation**](../compare-exchange/update-cmpxchg-item#update-compare-exchange-item-using-a-store-operation) + * [Update compare-exchange item using the **Studio**](../compare-exchange/update-cmpxchg-item#update-compare-exchange-item-using-the-studio) + * [Syntax](../compare-exchange/update-cmpxchg-item#syntax) + + + +--- + +## Update compare-exchange item using a Store operation + + + +```java +// Get existing value +CompareExchangeValue readResult + = store.operations().send( + new GetCompareExchangeValueOperation<>(User.class, "AdminUser")); + +readResult.getValue().setAge(readResult.getValue().getAge() + 1); + +// Update value +CompareExchangeResult saveResult + = store.operations().send( + new PutCompareExchangeValueOperation<>("AdminUser", readResult.getValue(), readResult.getIndex())); + +// The save result is successful only if 'index' wasn't changed between the read and write operations +boolean saveResultSuccessful = saveResult.isSuccessful(); +``` + + + +--- + +## Update compare-exchange item using the Studio + +You can update any existing compare-exchange item from the Studio. + +![The compare-exchange view](../assets/update-cmpxchg-1.png) + +1. Go to **Documents > Compare Exchange**. +2. Click to edit a compare-exchange item. + +--- + +![The compare-exchange view](../assets/update-cmpxchg-2.png) + +1. Enter the value. +2. Enter the metadata (optional). +3. Click _Save_ to update the item. + +--- + +## Syntax + +**Method**: + + +```java +public PutCompareExchangeValueOperation(String key, T value, long index) +``` + + +| Parameter | Type | Description | +|--------------|----------|------------------------------------------------| +| **key** | `String` | The unique identifier in the database scope. | +| **value** | `T` | The value to be saved for the specified _key_. | +| **index** | `long` | The current version of the item when updating an existing item. | + +**Returned object**: + + +```java +public class CompareExchangeResult { + private T value; + private long index; + private boolean successful; + + public T getValue() { + return value; + } + + public void setValue(T value) { + this.value = value; + } + + public long getIndex() { + return index; + } + + public void setIndex(long index) { + this.index = index; + } + + public boolean isSuccessful() { + return successful; + } + + public void setSuccessful(boolean successful) { + this.successful = successful; + } +} +``` + + +| Return Value | Type | Description | +|---------------|-----------|---------------------------------------------------------------------------------------------------------------------------------------------------| +| **Successful**| `boolean` |
  • _true_ if the put operation has completed successfully.
  • _false_ if the put operation failed.
| +| **Value** | `T` |
  • Upon success - the value of the compare-exchange item that was saved.
  • Upon failure - the existing value on the server.
| +| **Index** | `long` |
  • Upon success - the updated version (the incremented index of the modified item).
  • Upon failure (if indexes do not match) - the existing version from the server.
| diff --git a/docs/compare-exchange/content/_update-cmpxchg-item-nodejs.mdx b/docs/compare-exchange/content/_update-cmpxchg-item-nodejs.mdx new file mode 100644 index 0000000000..ec114a40cb --- /dev/null +++ b/docs/compare-exchange/content/_update-cmpxchg-item-nodejs.mdx @@ -0,0 +1,178 @@ +import Admonition from '@theme/Admonition'; +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +import CodeBlock from '@theme/CodeBlock'; + + + +* An existing compare-exchange item can be updated in the following ways: + * Using a cluster-wide session + * Using a store operation + * Using the Studio + +* In this article: + * [Update compare-exchange item using a **cluster-wide session**](../compare-exchange/update-cmpxchg-item#update-compare-exchange-item-using-a-cluster-wide-session) + * [Update compare-exchange item using a **store operation**](../compare-exchange/update-cmpxchg-item#update-compare-exchange-item-using-a-store-operation) + * [Update compare-exchange item using the **Studio**](../compare-exchange/update-cmpxchg-item#update-compare-exchange-item-using-the-studio) + * [Syntax](../compare-exchange/update-cmpxchg-item#syntax) + + + +--- + +## Update compare-exchange item using a cluster-wide session + + + +```csharp +// The session must be opened in cluster-wide mode. +const session = documentStore.openSession({ + transactionMode: "ClusterWide" +}); + +// Get the existing item from the server +// ===================================== + +const item = await session.advanced.clusterTransaction + .getCompareExchangeValue("user1-name@example.com"); + +// The item is now tracked in the session's internal state +// Modify the value / metadata as needed +// ===================================== + +item.value = "users/99"; +item.metadata["email-type"] = "work email"; +item.metadata["updated-at"] = new Date().toISOString(); + +// Save changes for the update to take effect +// ========================================== + +await session.saveChanges(); + +// A 'ClusterTransactionConcurrencyException' is thrown if the compare-exchange item +// no longer exists on the server at the time of calling saveChanges(). +// This can happen if another client deletes or modifies the item before your update is saved. +``` + + + +--- + +## Update compare-exchange item using a Store operation + +* Use `PutCompareExchangeValueOperation` to **update the _value_ and/or _metadata_** of an existing compare-exchange item. + This operation is also used to create new compare-exchange items, see [Create compare-exchange item](../compare-exchange/create-cmpxchg-items). + +* To perform an update, provide: + * The existing key + * A new value and/or metadata + * The expected index (version) of the item, which must match the current version stored on the server. + +* The update will succeed only if the index you provide matches the current index stored on the server for that key. + This ensures that the item hasn’t been modified by another client since you last read it. + +* If the index does not match, or if the specified key does not exist: + * The item is not updated. + * No exception is thrown. + * The operation result has `Successful = false`. + +* If the update is successful: + * The value and/or metadata are updated. + * The server increments the index number of the item. + * The operation result has `Successful = true` and will contain the new value and new index. + + + +```js +// Get the existing item from the server +// ===================================== + +const getCmpXchgOp = new GetCompareExchangeValueOperation("user1-name@example.com"); +const item = await documentStore.operations.send(getCmpXchgOp); + +// Modify the value / metadata as needed +// ===================================== + +const newValue = "users/99"; // Modify the value associated with the unique email key +const newMetadata = { + "email-type": "work email", + "updated-at": new Date().toISOString() // Add entries / modify the metadata +}; + +// Update the item +// =============== + +// The put operation will succeed only if the 'index' of the compare-exchange item +// has not changed between the read and write operations. +const putCmpXchgOp = new PutCompareExchangeValueOperation(item.key, newValue, item.index, newMetadata); +const putResult = await documentStore.operations.send(putCmpXchgOp); + +// Check results +// ============= + +const successful = putResult.successful; // Has operation succeeded +const indexForItem = putResult.index; // The new version number assigned if update succeeded +``` + + + +--- + +## Update compare-exchange item using the Studio + +You can update any existing compare-exchange item from the Studio. + +![The compare-exchange view](../assets/update-cmpxchg-1.png) + +1. Go to **Documents > Compare Exchange**. +2. Click to edit a compare-exchange item. + +--- + +![The compare-exchange view](../assets/update-cmpxchg-2.png) + +1. Enter the value. +2. Enter the metadata (optional). +3. Click _Save_ to update the item. + +--- + +## Syntax + +**Method**: + + +```js +// Available overloads: +// ==================== +const putCmpXchgOp = new PutCompareExchangeValueOperation(key, value, index); +const putCmpXchgOp = new PutCompareExchangeValueOperation(key, value, index, metadata); +``` + + +| Parameter | Type | Description | +|--------------|----------|-------------------------------------------------------------------------------------------------------------------------| +| **key** | `string` | The unique identifier in the database scope. | +| **value** | `object` | The value to be saved for the specified _key_.
Can be any object (number, string, array, or any valid JSON object). | +| **index** | `number` | The current version of the item when updating an existing item.
Pass `0` to [create a new key](../compare-exchange/create-cmpxchg-items). | +| **metadata** | `object` | Metadata to be saved for the specified key.
Must be a valid JSON object. | + +**Returned object**: + + +```js +// Return value of store.operations.send(putCmpXchgOp) +// =================================================== +class CompareExchangeResult { + successful; + value; + index; +} +``` + + +| Return Value | Type | Description | +|---------------|--------|---------------------------------------------------------------------------------------------------------------------------------------------------| +| **Successful**| `bool` |
  • _true_ if the put operation has completed successfully.
  • _false_ if the put operation failed.
| +| **Value** | `T` |
  • Upon success - the value of the compare-exchange item that was saved.
  • Upon failure - the existing value on the server.
| +| **Index** | `long` |
  • Upon success - the updated version (the incremented index of the modified item).
  • Upon failure (if indexes do not match) - the existing version from the server.
| diff --git a/docs/compare-exchange/create-cmpxchg-items.mdx b/docs/compare-exchange/create-cmpxchg-items.mdx new file mode 100644 index 0000000000..a9b6a98bf0 --- /dev/null +++ b/docs/compare-exchange/create-cmpxchg-items.mdx @@ -0,0 +1,48 @@ +--- +title: "Create Compare-Exchange Items" +hide_table_of_contents: true +sidebar_label: "Create Compare-Exchange Items" +sidebar_position: 2 +--- + +import LanguageSwitcher from "@site/src/components/LanguageSwitcher"; +import LanguageContent from "@site/src/components/LanguageContent"; + +import CreateCmpXchgItemsCsharp from './content/_create-cmpxchg-items-csharp.mdx'; +import CreateCmpXchgItemsJava from './content/_create-cmpxchg-items-java.mdx'; +import CreateCmpXchgItemsNodejs from './content/_create-cmpxchg-items-nodejs.mdx'; +import CreateCmpXchgItemsPython from './content/_create-cmpxchg-items-python.mdx'; +import CreateCmpXchgItemsPhp from './content/_create-cmpxchg-items-php.mdx'; + +export const supportedLanguages = ["csharp", "java", "python", "php", "nodejs"]; + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/compare-exchange/delete-cmpxchg-items.mdx b/docs/compare-exchange/delete-cmpxchg-items.mdx new file mode 100644 index 0000000000..8d36c64cc6 --- /dev/null +++ b/docs/compare-exchange/delete-cmpxchg-items.mdx @@ -0,0 +1,46 @@ +--- +title: "Delete Compare-Exchange Items" +hide_table_of_contents: true +sidebar_label: "Delete Compare-Exchange Items" +sidebar_position: 5 +--- + +import LanguageSwitcher from "@site/src/components/LanguageSwitcher"; +import LanguageContent from "@site/src/components/LanguageContent"; + +import DeleteCmpXchgItemsCsharp from './content/_delete-cmpxchg-items-csharp.mdx'; +import DeleteCmpXchgItemsJava from './content/_delete-cmpxchg-items-java.mdx'; +import DeleteCmpXchgItemsNodejs from './content/_delete-cmpxchg-items-nodejs.mdx'; +import DeleteCmpXchgItemsPython from './content/_delete-cmpxchg-items-python.mdx'; +import DeleteCmpXchgItemsPhp from './content/_delete-cmpxchg-items-php.mdx'; + +export const supportedLanguages = ["csharp", "java", "python", "php", "nodejs"]; + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/compare-exchange/get-cmpxchg-item.mdx b/docs/compare-exchange/get-cmpxchg-item.mdx new file mode 100644 index 0000000000..7d3204c85d --- /dev/null +++ b/docs/compare-exchange/get-cmpxchg-item.mdx @@ -0,0 +1,46 @@ +--- +title: "Get Compare-Exchange Item" +hide_table_of_contents: true +sidebar_label: "Get Compare-Exchange Item" +sidebar_position: 3 +--- + +import LanguageSwitcher from "@site/src/components/LanguageSwitcher"; +import LanguageContent from "@site/src/components/LanguageContent"; + +import GetCmpXchgItemCsharp from './content/_get-cmpxchg-item-csharp.mdx'; +import GetCmpXchgItemJava from './content/_get-cmpxchg-item-java.mdx'; +import GetCmpXchgItemNodejs from './content/_get-cmpxchg-item-nodejs.mdx'; +import GetCmpXchgItemPython from './content/_get-cmpxchg-item-python.mdx'; +import GetCmpXchgItemPhp from './content/_get-cmpxchg-item-php.mdx'; + +export const supportedLanguages = ["csharp", "java", "python", "php", "nodejs"]; + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/compare-exchange/get-cmpxchg-items.mdx b/docs/compare-exchange/get-cmpxchg-items.mdx new file mode 100644 index 0000000000..21adaf744d --- /dev/null +++ b/docs/compare-exchange/get-cmpxchg-items.mdx @@ -0,0 +1,46 @@ +--- +title: "Get Multiple Compare-Exchange Items" +hide_table_of_contents: true +sidebar_label: "Get Compare-Exchange Items" +sidebar_position: 4 +--- + +import LanguageSwitcher from "@site/src/components/LanguageSwitcher"; +import LanguageContent from "@site/src/components/LanguageContent"; + +import GetCmpXchgItemsCsharp from './content/_get-cmpxchg-items-csharp.mdx'; +import GetCmpXchgItemsJava from './content/_get-cmpxchg-items-java.mdx'; +import GetCmpXchgItemsNodejs from './content/_get-cmpxchg-items-nodejs.mdx'; +import GetCmpXchgItemsPython from './content/_get-cmpxchg-items-python.mdx'; +import GetCmpXchgItemsPhp from './content/_get-cmpxchg-items-php.mdx'; + +export const supportedLanguages = ["csharp", "java", "python", "php", "nodejs"]; + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/compare-exchange/include-cmpxchg-items.mdx b/docs/compare-exchange/include-cmpxchg-items.mdx new file mode 100644 index 0000000000..fd60e9878b --- /dev/null +++ b/docs/compare-exchange/include-cmpxchg-items.mdx @@ -0,0 +1,33 @@ +--- +title: "Include Compare-Exchange Items" +hide_table_of_contents: true +sidebar_label: "Include Compare-Exchange Items" +sidebar_position: 7 +--- + +import LanguageSwitcher from "@site/src/components/LanguageSwitcher"; +import LanguageContent from "@site/src/components/LanguageContent"; + +import IncludeCmpXchgItemsCsharp from './content/_include-compare-exchange-items-csharp.mdx'; +import IncludeCmpXchgItemsNodejs from './content/_include-compare-exchange-items-nodejs.mdx'; + +export const supportedLanguages = ["csharp", "nodejs"]; + + + + + + + + + + + + diff --git a/docs/compare-exchange/indexing-cmpxchg-values.mdx b/docs/compare-exchange/indexing-cmpxchg-values.mdx new file mode 100644 index 0000000000..33b7ee1ac3 --- /dev/null +++ b/docs/compare-exchange/indexing-cmpxchg-values.mdx @@ -0,0 +1,42 @@ +--- +title: "Indexing Compare-Exchange Values" +hide_table_of_contents: true +sidebar_label: "Indexing Compare-Exchange Values" +sidebar_position: 8 +--- + +import LanguageSwitcher from "@site/src/components/LanguageSwitcher"; +import LanguageContent from "@site/src/components/LanguageContent"; + +import IndexingCmpXchgValuesCsharp from './content/_indexing-compare-exchange-values-csharp.mdx'; +import IndexingCmpXchgValuesJava from './content/_indexing-compare-exchange-values-java.mdx'; +import IndexingCmpXchgValuesNodejs from './content/_indexing-compare-exchange-values-nodejs.mdx'; + +export const supportedLanguages = ["csharp", "java", "nodejs"]; + + + + + + + + + + + + + + + + diff --git a/docs/compare-exchange/overview.mdx b/docs/compare-exchange/overview.mdx new file mode 100644 index 0000000000..510d9e2c59 --- /dev/null +++ b/docs/compare-exchange/overview.mdx @@ -0,0 +1,45 @@ +--- +title: "Compare Exchange Overview" +hide_table_of_contents: true +sidebar_label: Overview +sidebar_position: 1 +--- + +import LanguageSwitcher from "@site/src/components/LanguageSwitcher"; +import LanguageContent from "@site/src/components/LanguageContent"; + +import OverviewCsharp from './content/_overview-csharp.mdx'; +import OverviewJava from './content/_overview-java.mdx'; +import OverviewPython from './content/_overview-python.mdx'; +import OverviewPhp from './content/_overview-php.mdx'; + +export const supportedLanguages = ["csharp", "java", "python", "php"]; + + + + + + + + + + + + + + + + + + + + diff --git a/docs/compare-exchange/start.mdx b/docs/compare-exchange/start.mdx new file mode 100644 index 0000000000..3a0c2d1c4e --- /dev/null +++ b/docs/compare-exchange/start.mdx @@ -0,0 +1,292 @@ +--- +title: "Compare-Exchange: Start" +hide_table_of_contents: true +sidebar_label: "Start" +sidebar_position: 0 +--- + +import Card from "@site/src/components/Common/Card"; +import CardWithImage from "@site/src/components/Common/CardWithImage"; +import CardWithImageHorizontal from "@site/src/components/Common/CardWithImageHorizontal"; +import ColGrid from "@site/src/components/ColGrid"; +import CardWithIcon from "@site/src/components/Common/CardWithIcon"; +import Admonition from '@theme/Admonition'; + + + +* Compare-exchange is a RavenDB feature for storing atomic, cluster-wide **key-value pairs** where each key is a globally unique identifier in the database. + Items are versioned and managed at the cluster level. + +* Compare-exchange provides a built-in consensus mechanism ideal for safe coordination and global consistency + in distributed environments, allowing you to: + * Enforce global uniqueness (e.g., prevent duplicate usernames or emails). + * Assign work to a single client or reserve a resource once. + * Handle concurrency safely, without external services or custom locking logic. + +* Key Characteristics of a compare-exchange item: + * Cluster-wide - Visible and consistent across all nodes in the [database group](../studio/database/settings/manage-database-group). + * Atomic - Only one client can successfully modify an item at a time (all-or-nothing updates). + * Versioned - Each update increments the version, enabling conflict detection. + * Flexible - Values can be strings, numbers, arrays, or JSON objects. + * Internal - Not replicated outside the database. + +* On this start page, you'll find: + * [Technical documentation links](../compare-exchange/start#technical-documentation-links-) + * [RavenDB Demo links](../compare-exchange/start#ravendb-demo-links) + * [Related blog posts](../compare-exchange/start#related-blog-posts) + * [Related in-depth articles](../compare-exchange/start#related-in-depth-articles) + * [Sample use cases](../compare-exchange/start#sample-use-cases) + + + +--- + +## Technical documentation links ✨ + +* [Overview](../compare-exchange/overview) +* [Create Compare-Exchange Items](../compare-exchange/create-cmpxchg-items) +* [Get Compare-Exchange Item](../compare-exchange/get-cmpxchg-item) +* [Get Compare-Exchange Items](../compare-exchange/get-cmpxchg-items) +* [Delete Compare-Exchange Items](../compare-exchange/delete-cmpxchg-items) +* [Update Compare-Exchange Item](../compare-exchange/update-cmpxchg-item) +* [Include Compare-Exchange Items](../compare-exchange/include-cmpxchg-items) +* [Indexing Compare-Exchange Values](../compare-exchange/indexing-cmpxchg-values) +* [Compare-Exchange in Dynamic Queries](../compare-exchange/cmpxchg-in-dynamic-queries) +* [Compare-Exchange Expiration](../compare-exchange/cmpxchg-expiration) +* [Atomic Guards](../compare-exchange/atomic-guards) + +--- + +## RavenDB Demo links + + + + + + +## Related blog posts + + + + + + + +## Related in-depth articles + + + + + + +--- + +## Sample use cases + + + +### Enforce unique usernames or emails + +* Use compare-exchange to enforce global uniqueness in your database even under concurrent operations. + For example, ensure that no two users can register with the same username or email, even if they do so simultaneously on different servers. + Compare-exchange guarantees that a specific value can only be claimed once across the cluster reliably and without race conditions. + +* ✅ Why compare-exchange? + It provides a guaranteed, cluster-wide check for uniqueness. + +* How it works: + * When a user registers, the app attempts to create a compare-exchange item like + (**key**: `"emails/john@example.com"`, **value**: `"users/1-A"`). + * Only the first attempt to claim this key succeeds. + * Any concurrent or repeated attempts to claim the same key fail automatically. + +* This makes it easy to enforce rules like: + * No two users can register with the same email address. + * No two orders can use the same external reference ID. + + + + + +### Claim a job or task once + +* Use compare-exchange to safely assign client-side jobs or tasks in a distributed system, + ensuring that each task is claimed only once. + +* ✅ Why compare-exchange? + It provides a reliable, cluster-wide locking mechanism for coordination within your database scope. + +* How it works: + * Each worker attempts to create a compare-exchange item like (**key**: `"locks/job/1234"`, **value**: `"worker-A"`). + * The first worker to succeed gets the job. + * Other workers trying to claim the same job will fail - they can back off or retry later. + +* This ensures: + * No two workers process the same job. + * Each job runs exactly once, even with multiple competing workers or nodes. + +* Also useful for: + * Implementing mutex-style locks between clients. + * Ensuring that scheduled tasks or batch jobs run only once across the cluster. + + + + + +### Reserve a resource + +* Need to reserve a table in a restaurant app or a seat at an event? + Use compare-exchange to lock the reservation and prevent double booking, even under concurrent access. + +* ✅ Why compare-exchange? + It gives you a reliable, cluster-wide way to reserve something exactly once - no race conditions, no conflicts. + +* How it works: + * Try to create a Compare-Exchange item for the resource + (e.g., **key**: `"reservations/seat/17"`, **value**: `"user/123"`). + * If the item doesn't exist, the reservation is successful. + * If it already exists, someone else claimed it - you can show an error or let the user pick another. + +* This pattern is useful for: + * Reserving seats, tables, or event slots. + * Assigning support engineers to incoming tickets. + * Allocating limited resources like promotion codes or serial numbers. + +* Only one client can claim the item so your reservation logic stays safe and simple, even under high load. + + + + + +### Prevent double processing + +* Use compare-exchange to make sure an operation runs only once even in a distributed setup. + This is useful for avoiding things like sending the same email twice, processing the same order multiple times, + or executing duplicate actions after retries. + +* ✅ Why compare-exchange? + It acts as a once-only flag - a lightweight, atomic check to prevent duplicate processing. + +* How it works: + * Before running the operation, try to create a compare-exchange key like `processed/orders/9876`. + * If the key creation succeeds - run the operation. + * If the key already exists - skip processing. It's already been handled. + +* This approach is especially useful in retry scenarios, background jobs, or any flow where idempotency matters. + + + + + +### Run business logic only if data hasn't changed + +* Use compare-exchange as a version guard to ensure the data wasn't modified while you were working on it. + This is useful when applying business logic that depends on the current state of the data - like approving a request, processing a payment, or updating a workflow step. + +* ✅ Why compare-exchange? + It helps detect changes and prevents acting on stale or outdated data. + +* How it works: + * Load the compare-exchange item that tracks the current version or state of the resource. + * After performing your checks and logic, attempt to update the item - but only if the version is still current. + * If the item was modified in the meantime, the update fails and you can abort or retry your business logic. + +* This pattern helps you maintain correctness and consistency in flows that involve multiple steps, + long-running tasks, or user input. + + + + + +### Lock a document for editing + +* In collaborative systems, it's common to allow only one user edit a document at a time. + Use compare-exchange to create a lightweight, distributed lock on the document. + +* ✅ Why compare-exchange? + It ensures that only one client can acquire the lock - preventing conflicting edits across users or servers. + +* How it works: + * When a user starts editing a document (e.g., `task/72`), try to create a compare-exchange item: + (**key**: `"editing/task/72"`, **value**: `"user/123"`). + * If the item is created successfully, the user holds the lock. + * Other users attempting the same key will fail and can be blocked, shown a message, or put into read-only mode. + * When editing is done, delete the compare-exchange item to release the lock. + +* This is useful for: + * Locking tasks, issues, or shared forms during editing. + * Preventing data loss or conflicts from simultaneous updates. + * Letting users know who’s currently editing a shared resource. + +* Simple to implement and works seamlessly across the cluster. + + + + + +### Add safety to cluster-wide transactions + +* When using cluster-wide sessions to handle documents, RavenDB automatically creates internal compare-exchange items, + called [atomic guards](../compare-exchange/atomic-guards), to enforce atomic document modifications. + These items coordinate access and prevent conflicting writes across nodes. + +* ✅ Why compare-exchange? + It provides a Raft-based coordination mechanism that ensures consistency and safety during multi-node transactions. + +* How it works: + * When you store or update a document in a cluster-wide session, + RavenDB creates an atomic guard to track the document’s version across the cluster. + * If another session modifies the document in the meantime, + your transaction fails with a `ConcurrencyException`, ensuring data consistency. + +* This protects you from: + * Writing over documents that were modified by other sessions. + * Acting on stale data in a distributed environment. + * Violating ACID guarantees in multi-node clusters. + +* You don’t need to manage these guards manually - + RavenDB handles everything automatically when you use a session in cluster-wide mode. + + diff --git a/docs/compare-exchange/update-cmpxchg-item.mdx b/docs/compare-exchange/update-cmpxchg-item.mdx new file mode 100644 index 0000000000..e871f63245 --- /dev/null +++ b/docs/compare-exchange/update-cmpxchg-item.mdx @@ -0,0 +1,37 @@ +--- +title: "Update Compare-Exchange Item" +hide_table_of_contents: true +sidebar_label: "Update Compare-Exchange Item" +sidebar_position: 6 +--- + +import LanguageSwitcher from "@site/src/components/LanguageSwitcher"; +import LanguageContent from "@site/src/components/LanguageContent"; + +import UpdateCmpXchgItemCsharp from './content/_update-cmpxchg-item-csharp.mdx'; +import UpdateCmpXchgItemJava from './content/_update-cmpxchg-item-java.mdx'; +import UpdateCmpXchgItemNodejs from './content/_update-cmpxchg-item-nodejs.mdx'; + +export const supportedLanguages = ["csharp", "java", "nodejs"]; + + + + + + + + + + + + + + + + diff --git a/docs/data-archival/_category_.json b/docs/data-archival/_category_.json index 20f724236e..f2d5bb4b23 100644 --- a/docs/data-archival/_category_.json +++ b/docs/data-archival/_category_.json @@ -1,4 +1,4 @@ { "position": 4, - "label": Data Archival, -} \ No newline at end of file + "label": "Data Archival" +} diff --git a/docs/document-extensions/_category_.json b/docs/document-extensions/_category_.json index cdf53fd5d0..49bb9c4b28 100644 --- a/docs/document-extensions/_category_.json +++ b/docs/document-extensions/_category_.json @@ -1,4 +1,4 @@ { "position": 3, - "label": Document Extensions, -} \ No newline at end of file + "label": "Document Extensions" +} diff --git a/docs/glossary/_category_.json b/docs/glossary/_category_.json index ff3a75b291..d6a7d964e3 100644 --- a/docs/glossary/_category_.json +++ b/docs/glossary/_category_.json @@ -1,4 +1,4 @@ { - "position": 10, - "label": Glossary, -} \ No newline at end of file + "position": 11, + "label": "Glossary" +} diff --git a/docs/glossary/_delete-compare-exchange-command-data-csharp.mdx b/docs/glossary/_delete-compare-exchange-command-data-csharp.mdx deleted file mode 100644 index 105e0642e5..0000000000 --- a/docs/glossary/_delete-compare-exchange-command-data-csharp.mdx +++ /dev/null @@ -1,18 +0,0 @@ -import Admonition from '@theme/Admonition'; -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; -import CodeBlock from '@theme/CodeBlock'; - -### Properties - -| Name | Type | Description | -| ------------- | ------------- | ----- | -| **key** | string | The key to be deleted | -| **index** | long | The version number of the value to be deleted | - -### Methods - -| Signature | Description | -| ---------- | ----------- | -| **DynamicJsonValue ToJson(DocumentConventions conventions, JsonOperationContext context)** | Translate this instance to a Json. | - diff --git a/docs/glossary/_put-compare-exchange-command-data-csharp.mdx b/docs/glossary/_put-compare-exchange-command-data-csharp.mdx deleted file mode 100644 index 0634a8390f..0000000000 --- a/docs/glossary/_put-compare-exchange-command-data-csharp.mdx +++ /dev/null @@ -1,19 +0,0 @@ -import Admonition from '@theme/Admonition'; -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; -import CodeBlock from '@theme/CodeBlock'; - -### Properties - -| Name | Type | Description | -| ------------- | ------------- | ----- | -| **key** | string | Object identifier under which _value_ is saved, unique in the database scope across the cluster. | -| **value** | BlittableJsonReaderObject | The value to be saved for the specified _key_. | -| **index** | long | The current version of _Value_ when updating a value for an existing key | - -### Methods - -| Signature | Description | -| ---------- | ----------- | -| **DynamicJsonValue ToJson(DocumentConventions conventions, JsonOperationContext context)** | Translate this instance to a Json. | - diff --git a/docs/glossary/counters-batch-command-data.mdx b/docs/glossary/counters-batch-command-data.mdx index d59eba6d03..1a4421b658 100644 --- a/docs/glossary/counters-batch-command-data.mdx +++ b/docs/glossary/counters-batch-command-data.mdx @@ -2,7 +2,7 @@ title: "Glossary: CountersBatchCommandData" hide_table_of_contents: true sidebar_label: CountersBatchCommandData -sidebar_position: 22 +sidebar_position: 20 --- import LanguageSwitcher from "@site/src/components/LanguageSwitcher"; @@ -22,4 +22,4 @@ export const supportedLanguages = ["csharp"]; \ No newline at end of file +--> diff --git a/docs/glossary/delete-compare-exchange-command-data.mdx b/docs/glossary/delete-compare-exchange-command-data.mdx deleted file mode 100644 index a3df7bf751..0000000000 --- a/docs/glossary/delete-compare-exchange-command-data.mdx +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: "Glossary: DeleteCompareExchangeCommandData" -hide_table_of_contents: true -sidebar_label: DeleteCompareExchangeCommandData -sidebar_position: 21 ---- - -import LanguageSwitcher from "@site/src/components/LanguageSwitcher"; -import LanguageContent from "@site/src/components/LanguageContent"; - -import DeleteCompareExchangeCommandDataCsharp from './_delete-compare-exchange-command-data-csharp.mdx'; - -export const supportedLanguages = ["csharp"]; - - - - - - - - - \ No newline at end of file diff --git a/docs/glossary/put-compare-exchange-command-data.mdx b/docs/glossary/put-compare-exchange-command-data.mdx deleted file mode 100644 index c78d688496..0000000000 --- a/docs/glossary/put-compare-exchange-command-data.mdx +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: "Glossary: PutCompareExchangeCommandData" -hide_table_of_contents: true -sidebar_label: PutCompareExchangeCommandData -sidebar_position: 20 ---- - -import LanguageSwitcher from "@site/src/components/LanguageSwitcher"; -import LanguageContent from "@site/src/components/LanguageContent"; - -import PutCompareExchangeCommandDataCsharp from './_put-compare-exchange-command-data-csharp.mdx'; - -export const supportedLanguages = ["csharp"]; - - - - - - - - - \ No newline at end of file diff --git a/docs/indexes/_category_.json b/docs/indexes/_category_.json index 8adbf43f8b..13d6071a5f 100644 --- a/docs/indexes/_category_.json +++ b/docs/indexes/_category_.json @@ -1,4 +1,4 @@ { "position": 2, - "label": Indexes, -} \ No newline at end of file + "label": "Indexes" +} diff --git a/docs/indexes/_indexing-compare-exchange-values-csharp.mdx b/docs/indexes/_indexing-compare-exchange-values-csharp.mdx deleted file mode 100644 index 7779f31d72..0000000000 --- a/docs/indexes/_indexing-compare-exchange-values-csharp.mdx +++ /dev/null @@ -1,139 +0,0 @@ -import Admonition from '@theme/Admonition'; -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; -import CodeBlock from '@theme/CodeBlock'; - - - -* Compare exchange values can be loaded within an index using the value's key. - -* The index will update when the compare exchange value is updated, as well -as when documents are modified in the indexed collection(s). - -* In this page: - * [Syntax](../indexes/indexing-compare-exchange-values.mdx#syntax) - * [Examples](../indexes/indexing-compare-exchange-values.mdx#examples) - * [Querying the Index](../indexes/indexing-compare-exchange-values.mdx#querying-the-index) - - -## Syntax - -When creating an index using `AbstractIndexCreationTask`, use the method -`LoadCompareExchangeValue()` to load a compare exchange value by its key. - - - -{`//Load one compare exchange value -T LoadCompareExchangeValue(string key); - -//Load multiple compare exchange values -T[] LoadCompareExchangeValue(IEnumerable keys); -`} - - - -For javascript indexes, use the method `cmpxchg()`. - -| Parameter | Type | Description | -| - | - | - | -| **key** | `string` | The key of a particular compare exchange value. | -| **keys** | `IEnumerable` | The keys of multiple compare exchange values. | - - -### Examples - -These indexes map the rooms in a hotel, as well as compare exchange values -representing the guests in those rooms. - - - - -{`private class Compare_Exchange_Index : AbstractIndexCreationTask -{ - public class Result - { - public string Room; - public string Guests; - } - - public Compare_Exchange_Index() - { - Map = HotelRooms => from room in HotelRooms - let guests = LoadCompareExchangeValue(room.RoomID) - select new Result - { - Room = room.RoomID, - Guests = guests - }; - } -} -`} - - - - -{`private class Compare_Exchange_JS_Index : AbstractJavaScriptIndexCreationTask -{ - public Compare_Exchange_JS_Index() - { - Maps = new HashSet - { - @"map('HotelRooms', function (room) { - var guests = cmpxchg(room.RoomID); - return { - RoomID: room.RoomID, - Guests: guests - }; - })", - }; - } -} -`} - - - - - - -## Querying the Index - - - - -{`// Return all vacant hotel rooms -List vacantRooms = session - .Query() - .Where(x => RavenQuery.CmpXchg(x.RoomID) == null) - .OfType() - .ToList(); -`} - - - - -{`// Return all vacant hotel rooms -List vacantRooms = await asyncSession - .Query() - .Where(x => RavenQuery.CmpXchg(x.RoomID) == null) - .OfType() - .ToListAsync(); -`} - - - - -{`// Return the room occupied by VIP guests -List VIPRooms = session - .Advanced.RawQuery( - @"from Hotelrooms as room - where room.Guests == cmpxchg('VIP')" - ) - .ToList(); -`} - - - - - - - diff --git a/docs/indexes/_indexing-compare-exchange-values-nodejs.mdx b/docs/indexes/_indexing-compare-exchange-values-nodejs.mdx deleted file mode 100644 index 2cf8d90235..0000000000 --- a/docs/indexes/_indexing-compare-exchange-values-nodejs.mdx +++ /dev/null @@ -1,65 +0,0 @@ -import Admonition from '@theme/Admonition'; -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; -import CodeBlock from '@theme/CodeBlock'; - - - -* Compare exchange values can be loaded within an index using the value's key. - -* The index will update when the compare exchange value is updated, as well -as when documents are modified in the indexed collection(s). - -* In this page: - * [Syntax](../indexes/indexing-compare-exchange-values.mdx#syntax) - * [Examples](../indexes/indexing-compare-exchange-values.mdx#examples) - * [Querying the Index](../indexes/indexing-compare-exchange-values.mdx#querying-the-index) - - -## Syntax -For javascript indexes, use the method `cmpxchg()`. - -| Parameter | Type | Description | -| - | - | - | -| **key** | `string` | The key of a particular compare exchange value. | - - -### Examples - -These indexes map the rooms in a hotel, as well as compare exchange values -representing the guests in those rooms. - - - -{`class Compare_Exchange_Index extends AbstractCsharpIndexCreationTask \{ - constructor() \{ - super(); - - this.map = \`docs.HotelRooms.Select(room => new \{ - RoomID = room.RoomID, - Guests = cmpxchg(room.RoomID) - \})\`; - \} -\} -`} - - - - - -## Querying the Index - - - -{`const VIPRooms = await session - .advanced.rawQuery( - " from Hotelrooms as room\\n"+ - " where room.Guests == cmpxchg('VIP')" -) -`} - - - - - - diff --git a/docs/indexes/additional-assemblies.mdx b/docs/indexes/additional-assemblies.mdx index 74685410c4..519bd01e3f 100644 --- a/docs/indexes/additional-assemblies.mdx +++ b/docs/indexes/additional-assemblies.mdx @@ -2,7 +2,7 @@ title: "Indexes: Additional Assemblies" hide_table_of_contents: true sidebar_label: Additional Assemblies -sidebar_position: 30 +sidebar_position: 29 --- import Admonition from '@theme/Admonition'; diff --git a/docs/indexes/boosting.mdx b/docs/indexes/boosting.mdx index ad3034c70b..385a2cce56 100644 --- a/docs/indexes/boosting.mdx +++ b/docs/indexes/boosting.mdx @@ -2,7 +2,7 @@ title: "Indexes: Boosting" hide_table_of_contents: true sidebar_label: Boosting -sidebar_position: 24 +sidebar_position: 23 --- import LanguageSwitcher from "@site/src/components/LanguageSwitcher"; @@ -46,4 +46,4 @@ export const supportedLanguages = ["csharp", "java", "php", "nodejs"]; - [Dynamic Fields](../indexes/using-dynamic-fields) ---> \ No newline at end of file +--> diff --git a/docs/indexes/extending-indexes.mdx b/docs/indexes/extending-indexes.mdx index 37b3b90fba..7091bda617 100644 --- a/docs/indexes/extending-indexes.mdx +++ b/docs/indexes/extending-indexes.mdx @@ -2,7 +2,7 @@ title: "Indexes: Extending Indexes" hide_table_of_contents: true sidebar_label: Extending Indexes -sidebar_position: 29 +sidebar_position: 28 --- import LanguageSwitcher from "@site/src/components/LanguageSwitcher"; @@ -34,4 +34,4 @@ export const supportedLanguages = ["csharp", "java", "nodejs"]; - [What are Indexes](../indexes/what-are-indexes) ---> \ No newline at end of file +--> diff --git a/docs/indexes/index-throttling.mdx b/docs/indexes/index-throttling.mdx index 7c1497d8b4..852f480168 100644 --- a/docs/indexes/index-throttling.mdx +++ b/docs/indexes/index-throttling.mdx @@ -2,7 +2,7 @@ title: "Indexes: Index Throttling" hide_table_of_contents: true sidebar_label: Index Throttling -sidebar_position: 23 +sidebar_position: 22 --- import Admonition from '@theme/Admonition'; diff --git a/docs/indexes/indexing-compare-exchange-values.mdx b/docs/indexes/indexing-compare-exchange-values.mdx deleted file mode 100644 index c32c7ca457..0000000000 --- a/docs/indexes/indexing-compare-exchange-values.mdx +++ /dev/null @@ -1,41 +0,0 @@ ---- -title: "Indexes: Indexing Compare Exchange Values" -hide_table_of_contents: true -sidebar_label: Indexing Compare Exchange Values -sidebar_position: 20 ---- - -import LanguageSwitcher from "@site/src/components/LanguageSwitcher"; -import LanguageContent from "@site/src/components/LanguageContent"; - -import IndexingCompareExchangeValuesCsharp from './_indexing-compare-exchange-values-csharp.mdx'; -import IndexingCompareExchangeValuesJava from './_indexing-compare-exchange-values-java.mdx'; -import IndexingCompareExchangeValuesNodejs from './_indexing-compare-exchange-values-nodejs.mdx'; - -export const supportedLanguages = ["csharp", "java", "nodejs"]; - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/docs/indexes/indexing-metadata.mdx b/docs/indexes/indexing-metadata.mdx index f0e8a14107..d9cbf3b6f8 100644 --- a/docs/indexes/indexing-metadata.mdx +++ b/docs/indexes/indexing-metadata.mdx @@ -2,7 +2,7 @@ title: "Indexing Metadata" hide_table_of_contents: true sidebar_label: Indexing Metadata -sidebar_position: 21 +sidebar_position: 20 --- import LanguageSwitcher from "@site/src/components/LanguageSwitcher"; @@ -47,4 +47,4 @@ export const supportedLanguages = ["csharp", "java", "python", "php", "nodejs"]; - [How to get and modify the metadata](../client-api/session/how-to/get-and-modify-entity-metadata) ---> \ No newline at end of file +--> diff --git a/docs/indexes/number-type-conversion.mdx b/docs/indexes/number-type-conversion.mdx index c1966a034c..7429433e0a 100644 --- a/docs/indexes/number-type-conversion.mdx +++ b/docs/indexes/number-type-conversion.mdx @@ -2,7 +2,7 @@ title: "Indexing: Numerical Type Conversion" hide_table_of_contents: true sidebar_label: Numerical Type Conversion -sidebar_position: 31 +sidebar_position: 30 --- import LanguageSwitcher from "@site/src/components/LanguageSwitcher"; @@ -30,4 +30,4 @@ export const supportedLanguages = ["csharp"]; - [Knowledge Base: Numbers in RavenDB](../server/kb/numbers-in-ravendb) ---> \ No newline at end of file +--> diff --git a/docs/indexes/rolling-index-deployment.mdx b/docs/indexes/rolling-index-deployment.mdx index 2f23445205..3cad3805ff 100644 --- a/docs/indexes/rolling-index-deployment.mdx +++ b/docs/indexes/rolling-index-deployment.mdx @@ -2,7 +2,7 @@ title: "Indexes: Rolling Index Deployment" hide_table_of_contents: true sidebar_label: Rolling Index Deployment -sidebar_position: 22 +sidebar_position: 21 --- import Admonition from '@theme/Admonition'; diff --git a/docs/indexes/search-engine/_category_.json b/docs/indexes/search-engine/_category_.json index 4e976a4479..03f3ccc982 100644 --- a/docs/indexes/search-engine/_category_.json +++ b/docs/indexes/search-engine/_category_.json @@ -1,4 +1,4 @@ { - "position": 32, + "position": 31, "label": Search Engine, -} \ No newline at end of file +} diff --git a/docs/indexes/storing-data-in-index.mdx b/docs/indexes/storing-data-in-index.mdx index 5ae7b44b5e..6d61cc64ad 100644 --- a/docs/indexes/storing-data-in-index.mdx +++ b/docs/indexes/storing-data-in-index.mdx @@ -2,7 +2,7 @@ title: "Storing Data in Index" hide_table_of_contents: true sidebar_label: Storing Data in Index -sidebar_position: 26 +sidebar_position: 25 --- import LanguageSwitcher from "@site/src/components/LanguageSwitcher"; @@ -51,4 +51,4 @@ export const supportedLanguages = ["java", "python", "php", "nodejs", "csharp"]; - [Projections and Stored Fields](../indexes/querying/projections#projections-and-stored-fields) ---> \ No newline at end of file +--> diff --git a/docs/indexes/troubleshooting/_category_.json b/docs/indexes/troubleshooting/_category_.json index bfc5d7ff0a..ca00d767fb 100644 --- a/docs/indexes/troubleshooting/_category_.json +++ b/docs/indexes/troubleshooting/_category_.json @@ -1,4 +1,4 @@ { - "position": 33, + "position": 32, "label": Troubleshooting, -} \ No newline at end of file +} diff --git a/docs/indexes/using-analyzers.mdx b/docs/indexes/using-analyzers.mdx index 5188320f59..ab3c5b5af6 100644 --- a/docs/indexes/using-analyzers.mdx +++ b/docs/indexes/using-analyzers.mdx @@ -2,7 +2,7 @@ title: "Indexes: Analyzers" hide_table_of_contents: true sidebar_label: Analyzers -sidebar_position: 25 +sidebar_position: 24 --- import LanguageSwitcher from "@site/src/components/LanguageSwitcher"; @@ -40,4 +40,4 @@ export const supportedLanguages = ["java", "csharp", "nodejs"]; - [Create Map Index](../studio/database/indexes/create-map-index) ---> \ No newline at end of file +--> diff --git a/docs/indexes/using-dynamic-fields.mdx b/docs/indexes/using-dynamic-fields.mdx index 26634225a2..00e55486dc 100644 --- a/docs/indexes/using-dynamic-fields.mdx +++ b/docs/indexes/using-dynamic-fields.mdx @@ -2,7 +2,7 @@ title: "Indexes: Dynamic Index Fields" hide_table_of_contents: true sidebar_label: Dynamic Fields -sidebar_position: 28 +sidebar_position: 27 --- import LanguageSwitcher from "@site/src/components/LanguageSwitcher"; @@ -47,4 +47,4 @@ export const supportedLanguages = ["csharp", "java", "python", "php", "nodejs"]; - [Term Vectors](../indexes/using-term-vectors) ---> \ No newline at end of file +--> diff --git a/docs/indexes/using-term-vectors.mdx b/docs/indexes/using-term-vectors.mdx index f6913820c4..2cf7030d1a 100644 --- a/docs/indexes/using-term-vectors.mdx +++ b/docs/indexes/using-term-vectors.mdx @@ -2,7 +2,7 @@ title: "Indexes: Term Vectors" hide_table_of_contents: true sidebar_label: Term Vectors -sidebar_position: 27 +sidebar_position: 26 --- import LanguageSwitcher from "@site/src/components/LanguageSwitcher"; @@ -37,4 +37,4 @@ export const supportedLanguages = ["java", "nodejs", "csharp"]; - [Dynamic Fields](../indexes/using-dynamic-fields) ---> \ No newline at end of file +--> diff --git a/docs/integrations/_category_.json b/docs/integrations/_category_.json index eecdb77dcc..b1527a08a3 100644 --- a/docs/integrations/_category_.json +++ b/docs/integrations/_category_.json @@ -1,4 +1,4 @@ { - "position": 8, - "label": Integrations, -} \ No newline at end of file + "position": 9, + "label": "Integrations" +} diff --git a/docs/migration/_category_.json b/docs/migration/_category_.json new file mode 100644 index 0000000000..88231dc12d --- /dev/null +++ b/docs/migration/_category_.json @@ -0,0 +1,4 @@ +{ + "position": 8, + "label": "Migration Guide" +} diff --git a/docs/server/_category_.json b/docs/server/_category_.json index 055cf45bb7..950e45a759 100644 --- a/docs/server/_category_.json +++ b/docs/server/_category_.json @@ -1,4 +1,4 @@ { - "position": 5, - "label": Server, -} \ No newline at end of file + "position": 6, + "label": "Server" +} diff --git a/docs/sharding/_category_.json b/docs/sharding/_category_.json index 74fd38943c..677f1e31cc 100644 --- a/docs/sharding/_category_.json +++ b/docs/sharding/_category_.json @@ -1,4 +1,4 @@ { "position": 13, - "label": Sharding, -} \ No newline at end of file + "label": "Sharding" +} diff --git a/docs/start/_category_.json b/docs/start/_category_.json index 13d42231a4..202cf83040 100644 --- a/docs/start/_category_.json +++ b/docs/start/_category_.json @@ -1,4 +1,4 @@ { "position": 0, - "label": Getting Started, -} \ No newline at end of file + "label": "Getting Started" +} diff --git a/docs/studio/_category_.json b/docs/studio/_category_.json index dde30a4412..090e5b76a7 100644 --- a/docs/studio/_category_.json +++ b/docs/studio/_category_.json @@ -1,4 +1,4 @@ { - "position": 6, - "label": Studio, -} \ No newline at end of file + "position": 7, + "label": "Studio", +} diff --git a/docs/studio/database/documents/assets/compare-exchange-atomic-guard.png b/docs/studio/database/documents/assets/compare-exchange-atomic-guard.png deleted file mode 100644 index eb51bf72a5..0000000000 Binary files a/docs/studio/database/documents/assets/compare-exchange-atomic-guard.png and /dev/null differ diff --git a/docs/studio/database/documents/assets/compare-exchange-single-pair.png b/docs/studio/database/documents/assets/compare-exchange-single-pair.png deleted file mode 100644 index f3202dc0fd..0000000000 Binary files a/docs/studio/database/documents/assets/compare-exchange-single-pair.png and /dev/null differ diff --git a/docs/studio/database/documents/assets/compare-exchange-view.png b/docs/studio/database/documents/assets/compare-exchange-view.png deleted file mode 100644 index 3221aeb309..0000000000 Binary files a/docs/studio/database/documents/assets/compare-exchange-view.png and /dev/null differ diff --git a/docs/studio/database/documents/compare-exchange-view.mdx b/docs/studio/database/documents/compare-exchange-view.mdx deleted file mode 100644 index 66c2e8d3d4..0000000000 --- a/docs/studio/database/documents/compare-exchange-view.mdx +++ /dev/null @@ -1,81 +0,0 @@ ---- -title: "Documents: Compare Exchange View" -hide_table_of_contents: true -sidebar_label: Compare Exchange -sidebar_position: 5 ---- - -import Admonition from '@theme/Admonition'; -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; -import CodeBlock from '@theme/CodeBlock'; -import LanguageSwitcher from "@site/src/components/LanguageSwitcher"; -import LanguageContent from "@site/src/components/LanguageContent"; - -# Documents: Compare Exchange View - - -* [Compare-Exchange](../../../client-api/operations/compare-exchange/overview.mdx) are cluster-wide key/value pair items where the key - is a unique identifier in the database. - -* CmpXchg items coordinate work between sessions that are - trying to modify a shared resource (such as a document) at the same time. - -* Compare exchange items are created and managed by either of the following: - * RavenDB [Atomic Guards](../../../client-api/session/cluster-transaction/atomic-guards.mdx) - To guarantee ACIDity across the cluster, - as of RavenDB 5.2, we automatically create and maintain Atomic Guard CmpXchg items in cluster-wide sessions. - * [API Operations](../../../client-api/operations/compare-exchange/overview.mdx) - * [Session - Cluster Transaction](../../../client-api/session/cluster-transaction/overview.mdx) - * Using the [RavenDB Studio](../../../studio/database/documents/compare-exchange-view.mdx#the-compare-exchange-view) - -* In this page: - * [The Compare Exchange View](../../../studio/database/documents/compare-exchange-view.mdx#the-compare-exchange-view) - - -## The Compare Exchange View - -![Compare Exchange View](./assets/compare-exchange-view.png) - -1. **Documents Tab** - Select to see document-related options and the list of documents in this database . -2. **Compare Exchange** - Select to see the Compare Exchange view. -3. **Add new item** - Click to add a new compare exchange key/value pair. -4. **Compare exchange key/value properties** - Click the edit button or key name to edit this item. - ![Compare Exchange Single Pair](./assets/compare-exchange-single-pair.png) - 1. **Key** - A unique identifier that is reserved across the cluster. - Enter any string of your choice. - - If keys start with "rvn-atomic", they are [Atomic Guards](../../../client-api/session/cluster-transaction/atomic-guards.mdx). - They are created and maintained automatically to guarantee ACID cluster-wide transactions. - **Do not remove or edit these** as this will disable ACID guarantees. - - ![Atomic Guard](./assets/compare-exchange-atomic-guard.png) - - - 2. **Value** - Enter a value that will be associated with the key. - Values can be numbers, strings, arrays, or objects. Any value that can be represented as JSON is valid. - 3. **Metadata** - Click to add metadata. - The metadata is associated with the key, similar to document's metadata. - 4. **Raft Index** - The raft index increments automatically each time the value or metadata are changed, indicating the compare-exchange item's - current version. - 5. **Delete** - Click to delete this compare exchange item. - - Deleting a compare exchange item will remove ACID guarantees for transactions if the pair was set up to protect ACIDity. - Only remove or edit these if you _truly_ know what you're doing. - - 6. **Save** - Save will only succeed if the Raft Index version that is currently stored within RavenDB for this key - matches the version that shows in the view. - - - - diff --git a/docs/studio/database/documents/conflicts-view.mdx b/docs/studio/database/documents/conflicts-view.mdx index 326ee3b0fd..6385b5449a 100644 --- a/docs/studio/database/documents/conflicts-view.mdx +++ b/docs/studio/database/documents/conflicts-view.mdx @@ -2,7 +2,7 @@ title: "Documents: Conflicts View" hide_table_of_contents: true sidebar_label: Conflicts View -sidebar_position: 6 +sidebar_position: 5 --- import Admonition from '@theme/Admonition'; diff --git a/docs/users-issues/_category_.json b/docs/users-issues/_category_.json index f2f1383c32..5d76e25274 100644 --- a/docs/users-issues/_category_.json +++ b/docs/users-issues/_category_.json @@ -1,4 +1,4 @@ { - "position": 11, - "label": Users Issues, -} \ No newline at end of file + "position": 12, + "label": "Users Issues" +} diff --git a/sidebars.ts b/sidebars.ts index 2eefd0ccda..576f8c5de0 100644 --- a/sidebars.ts +++ b/sidebars.ts @@ -1,85 +1,90 @@ - import type {SidebarsConfig} from '@docusaurus/plugin-content-docs'; +import type {SidebarsConfig} from '@docusaurus/plugin-content-docs'; const sidebars: SidebarsConfig = { - docs: [ - { - type: 'doc', - id: 'home', - label: 'Home', - className: 'hidden' // <-- This makes the default item invisible, while still rendering the sidebar - }, - { - type: 'doc', - id: 'whats-new', - label: 'Whats New', - className: 'hidden' - }, - { - type: 'category', - label: 'Getting Started', - items: [{ type: 'autogenerated', dirName: 'start' }], - }, - { - type: 'category', - label: 'Client API', - items: [{ type: 'autogenerated', dirName: 'client-api' }], - }, - { - type: 'category', - label: 'Indexes', - items: [{ type: 'autogenerated', dirName: 'indexes' }], - }, - { - type: 'category', - label: 'Document Extensions', - items: [{ type: 'autogenerated', dirName: 'document-extensions' }], - }, - { - type: 'category', - label: 'Data Archival', - items: [{ type: 'autogenerated', dirName: 'data-archival' }], - }, - { - type: 'category', - label: 'Server', - items: [{ type: 'autogenerated', dirName: 'server' }], - }, - { - type: 'category', - label: 'Studio', - items: [{ type: 'autogenerated', dirName: 'studio' }], - }, - { - type: 'category', - label: 'Migration Guide', - items: [{ type: 'autogenerated', dirName: 'migration' }], - }, - { - type: 'category', - label: 'Integrations', - items: [{ type: 'autogenerated', dirName: 'integrations' }], - }, - { - type: 'category', - label: 'AI Integration', - items: [{ type: 'autogenerated', dirName: 'ai-integration' }], - }, - { - type: 'category', - label: 'Glossary', - items: [{ type: 'autogenerated', dirName: 'glossary' }], - }, - { - type: 'category', - label: 'Users Issues', - items: [{ type: 'autogenerated', dirName: 'users-issues' }], - }, - { - type: 'category', - label: 'Sharding', - items: [{ type: 'autogenerated', dirName: 'sharding' }], - }, - ] + docs: [ + { + type: 'doc', + id: 'home', + label: 'Home', + className: 'hidden' // <-- This makes the default item invisible, while still rendering the sidebar + }, + { + type: 'doc', + id: 'whats-new', + label: 'Whats New', + className: 'hidden' + }, + { + type: 'category', + label: 'Getting Started', + items: [{type: 'autogenerated', dirName: 'start'}], + }, + { + type: 'category', + label: 'Client API', + items: [{type: 'autogenerated', dirName: 'client-api'}], + }, + { + type: 'category', + label: 'Indexes', + items: [{type: 'autogenerated', dirName: 'indexes'}], + }, + { + type: 'category', + label: 'Document Extensions', + items: [{type: 'autogenerated', dirName: 'document-extensions'}], + }, + { + type: 'category', + label: 'Data Archival', + items: [{type: 'autogenerated', dirName: 'data-archival'}], + }, + { + type: 'category', + label: 'Compare-Exchange', + items: [{type: 'autogenerated', dirName: 'compare-exchange'}], + }, + { + type: 'category', + label: 'Server', + items: [{type: 'autogenerated', dirName: 'server'}], + }, + { + type: 'category', + label: 'Studio', + items: [{type: 'autogenerated', dirName: 'studio'}], + }, + { + type: 'category', + label: 'Migration Guide', + items: [{type: 'autogenerated', dirName: 'migration'}], + }, + { + type: 'category', + label: 'Integrations', + items: [{type: 'autogenerated', dirName: 'integrations'}], + }, + { + type: 'category', + label: 'AI Integration', + items: [{type: 'autogenerated', dirName: 'ai-integration'}], + }, + { + type: 'category', + label: 'Glossary', + items: [{type: 'autogenerated', dirName: 'glossary'}], + }, + { + type: 'category', + label: 'Users Issues', + items: [{type: 'autogenerated', dirName: 'users-issues'}], + }, + { + type: 'category', + label: 'Sharding', + items: [{type: 'autogenerated', dirName: 'sharding'}], + } + ] }; -export default sidebars; \ No newline at end of file +export default sidebars; diff --git a/src/components/Common/Card.tsx b/src/components/Common/Card.tsx new file mode 100644 index 0000000000..83bda950ad --- /dev/null +++ b/src/components/Common/Card.tsx @@ -0,0 +1,49 @@ +import React, { ReactNode } from "react"; +import Heading from "@theme/Heading"; +import Button, { type ButtonVariant } from "@site/src/components/Common/Button"; +import { Icon } from "@site/src/components/Common/Icon"; +import { IconName } from "@site/src/typescript/iconName"; +import Badge from "@site/src/components/Common/Badge"; +import isInternalUrl from "@docusaurus/isInternalUrl"; + +export interface CardProps { + title: string; + description: ReactNode; + url?: string; + buttonVariant?: ButtonVariant; + ctaLabel?: string; + iconName?: IconName; // optional icon at top +} + +export default function Card({ + title, + description, + url, + buttonVariant = "secondary", + ctaLabel = "Read now", + iconName, +}: CardProps) { + return ( +
+ {iconName && ( +
+ + {url && !isInternalUrl(url) && ( + + External + + )} +
+ )} + + {title} + +

{description}

+ {url && ( + + )} +
+ ); +} diff --git a/src/components/Homepage/Features/Features.tsx b/src/components/Homepage/Features/Features.tsx index e0736bb2fc..d2617a5125 100644 --- a/src/components/Homepage/Features/Features.tsx +++ b/src/components/Homepage/Features/Features.tsx @@ -14,7 +14,7 @@ import IntegrationFeaturesGrid from "@site/src/components/Homepage/Features/Feat export default function Features() { return (
- Browse by features + Browse by feature