From 386e20a5b7dca9d4aa7247caaa51992d9fbe03c9 Mon Sep 17 00:00:00 2001 From: josh-wong Date: Thu, 12 Oct 2023 10:53:46 +0900 Subject: [PATCH] Add updated doc --- docs/3.10/api-guide.md | 722 ++++++++++++++++++++---------------- docs/3.6/api-guide.md | 525 ++++++++++++++------------ docs/3.7/api-guide.md | 612 +++++++++++++++++-------------- docs/3.8/api-guide.md | 609 +++++++++++++++++-------------- docs/3.9/api-guide.md | 769 +++++++++++++++++++++++++-------------- docs/latest/api-guide.md | 722 ++++++++++++++++++++---------------- 6 files changed, 2290 insertions(+), 1669 deletions(-) diff --git a/docs/3.10/api-guide.md b/docs/3.10/api-guide.md index 6f8d97f..ba32ddb 100644 --- a/docs/3.10/api-guide.md +++ b/docs/3.10/api-guide.md @@ -1,29 +1,33 @@ -# Java API Guide +# ScalarDB Java API Guide -ScalarDB Java API is mainly composed of Administrative API and Transactional API. -This guide briefly explains what kind of APIs exist and how to use them. - -* [Administrative API](#administrative-api) -* [Transactional API](#transactional-api) +The ScalarDB Java API is mainly composed of the Administrative API and Transactional API. This guide briefly explains what kinds of APIs exist, how to use them, and related topics like how to handle exceptions. ## Administrative API -This section explains how to execute administrative operations with Administrative API in ScalarDB. -You can execute administrative operations programmatically as follows, but you can also execute those operations through [Schema Loader](schema-loader.md). +This section explains how to execute administrative operations programmatically by using the Administrative API in ScalarDB. + +{% capture notice--info %} +**Note** + +Another method for executing administrative operations is to use [Schema Loader](schema-loader.md). +{% endcapture %} -### Get a DistributedTransactionAdmin instance +
{{ notice--info | markdownify }}
-To execute administrative operations, you first need to get a `DistributedTransactionAdmin` instance. -The `DistributedTransactionAdmin` instance can be obtained from `TransactionFactory` as follows: +### Get a `DistributedTransactionAdmin` instance + +You first need to get a `DistributedTransactionAdmin` instance to execute administrative operations. + +To get a `DistributedTransactionAdmin` instance, you can use `TransactionFactory` as follows: ```java -TransactionFactory transactionFactory = TransactionFactory.create(""); +TransactionFactory transactionFactory = TransactionFactory.create(""); DistributedTransactionAdmin admin = transactionFactory.getTransactionAdmin(); ``` For details about configurations, see [ScalarDB Configurations](configurations.md). -Once you have executed all administrative operations, you should close the `DistributedTransactionAdmin` instance as follows: +After you have executed all administrative operations, you should close the `DistributedTransactionAdmin` instance as follows: ```java admin.close(); @@ -32,60 +36,77 @@ admin.close(); ### Create a namespace Before creating tables, namespaces must be created since a table belongs to one namespace. + You can create a namespace as follows: ```java -// Create a namespace "ns". It will throw an exception if the namespace already exists +// Create the namespace "ns". If the namespace already exists, an exception will be thrown. admin.createNamespace("ns"); -// Create a namespace only if it does not already exist +// Create the namespace only if it does not already exist. boolean ifNotExists = true; admin.createNamespace("ns", ifNotExists); -// Create a namespace with options +// Create the namespace with options. Map options = ...; admin.createNamespace("ns", options); ``` -#### Creation Options +#### Creation options -In the creation operations (creating a namespace, creating a table, etc.), you can specify options that are maps of option names and values (`Map`). -With the options, we can set storage adapter specific configurations. +In the creation operations, like creating a namespace and creating a table, you can specify options that are maps of option names and values (`Map`). By using the options, you can set storage adapter–specific configurations. -Currently, we can set the following options for the storage adapters: +Select your database to see the options available: -For Cosmos DB for NoSQL: +
+
+ + + + +
-| name | value | default | -|------------|----------------------------------------------|---------| -| ru | Base resource unit | 400 | -| no-scaling | Disable auto-scaling for Cosmos DB for NoSQL | false | +
-For DynamoDB: +| Name | Description | Default | +|----------------------|----------------------------------------------------------------------------------------|------------------| +| replication-strategy | Cassandra replication strategy. Must be `SimpleStrategy` or `NetworkTopologyStrategy`. | `SimpleStrategy` | +| compaction-strategy | Cassandra compaction strategy, Must be `LCS`, `STCS` or `TWCS`. | `STCS` | +| replication-factor | Cassandra replication factor. | 1 | -| name | value | default | -|------------|----------------------------------------|---------| -| no-scaling | Disable auto-scaling for DynamoDB | false | -| no-backup | Disable continuous backup for DynamoDB | false | -| ru | Base resource unit | 10 | +
+
-For Cassandra: +| Name | Description | Default | +|------------|-----------------------------------------------------|---------| +| ru | Base resource unit. | 400 | +| no-scaling | Disable auto-scaling for Cosmos DB for NoSQL. | false | -| name | value | default | -|----------------------|---------------------------------------------------------------------------------------|------------------| -| replication-strategy | Cassandra replication strategy, must be `SimpleStrategy` or `NetworkTopologyStrategy` | `SimpleStrategy` | -| compaction-strategy | Cassandra compaction strategy, must be `LCS`, `STCS` or `TWCS` | `STCS` | -| replication-factor | Cassandra replication factor | 1 | +
+
+| Name | Description | Default | +|------------|-----------------------------------------|---------| +| no-scaling | Disable auto-scaling for DynamoDB. | false | +| no-backup | Disable continuous backup for DynamoDB. | false | +| ru | Base resource unit. | 10 | + +
+
+ +No options are available for JDBC databases. + +
+
### Create a table -Next, we will discuss table creation. +When creating a table, you should define the table metadata and then create the table. -You firstly need to create the TaleMetadata as follows: +To define the table metadata, you can use `TableMetadata`. The following shows how to define the columns, partition key, clustering key including clustering orders, and secondary indexes of a table: ```java -// Define a table metadata +// Define the table metadata. TableMetadata tableMetadata = TableMetadata.newBuilder() .addColumn("c1", DataType.INT) @@ -100,21 +121,19 @@ TableMetadata tableMetadata = .build(); ``` -Here you define columns, a partition key, a clustering key including clustering orders, and secondary indexes of a table. +For details about the data model of ScalarDB, see [Data Model](design.md#data-model). -Please see [ScalarDB design document - Data Model](design.md#data-model) for the details of the ScalarDB Data Model. - -And then, you can create a table as follows: +Then, create a table as follows: ```java -// Create a table "ns.tbl". It will throw an exception if the table already exists +// Create the table "ns.tbl". If the table already exists, an exception will be thrown. admin.createTable("ns", "tbl", tableMetadata); -// Create a table only if it does not already exist +// Create the table only if it does not already exist. boolean ifNotExists = true; admin.createTable("ns", "tbl", tableMetadata, ifNotExists); -// Create a table with options +// Create the table with options. Map options = ...; admin.createTable("ns", "tbl", tableMetadata, options); ``` @@ -124,43 +143,45 @@ admin.createTable("ns", "tbl", tableMetadata, options); You can create a secondary index as follows: ```java -// Create a secondary index on a column "c5" of a table "ns.tbl". It will throw an exception if the secondary index already exists +// Create a secondary index on column "c5" for table "ns.tbl". If a secondary index already exists, an exception will be thrown. admin.createIndex("ns", "tbl", "c5"); -// Create a secondary index only if it does not already exist +// Create the secondary index only if it does not already exist. boolean ifNotExists = true; admin.createIndex("ns", "tbl", "c5", ifNotExists); -// Create a secondary index with options +// Create the secondary index with options. Map options = ...; admin.createIndex("ns", "tbl", "c5", options); ``` ### Add a new column to a table -You can add a new non-partition key column to a table as follows: +You can add a new, non-partition key column to a table as follows: + ```java -// Add the new column "c6" of type INT to the table "ns.tbl" +// Add a new column "c6" with the INT data type to the table "ns.tbl". admin.addNewColumnToTable("ns", "tbl", "c6", DataType.INT) ``` -This should be executed with significant consideration as the execution time may vary greatly -depending on the underlying storage. Please plan accordingly especially if the database runs in production: -- For Cosmos DB for NoSQL and DynamoDB: this operation is almost instantaneous as the table - schema is not modified. Only the table metadata stored in a separated table are updated. -- For Cassandra: adding a column will only update the schema metadata and do not modify existing - schema records. The cluster topology is the main factor for the execution time. Since the schema - metadata change propagates to each cluster node via a gossip protocol, the larger the cluster, the - longer it will take for all nodes to be updated. -- For relational databases (MySQL, Oracle, etc.): it may take a very long time to execute and a - table-lock may be performed. +{% capture notice--warning %} +**Attention** + +You should carefully consider adding a new column to a table because the execution time may vary greatly depending on the underlying storage. Please plan accordingly and consider the following, especially if the database runs in production: + +- **For Cosmos DB for NoSQL and DynamoDB:** Adding a column is almost instantaneous as the table schema is not modified. Only the table metadata stored in a separate table is updated. +- **For Cassandra:** Adding a column will only update the schema metadata and will not modify the existing schema records. The cluster topology is the main factor for the execution time. Changes to the schema metadata are shared to each cluster node via a gossip protocol. Because of this, the larger the cluster, the longer it will take for all nodes to be updated. +- **For relational databases (MySQL, Oracle, etc.):** Adding a column shouldn't take a long time to execute. +{% endcapture %} + +
{{ notice--warning | markdownify }}
### Truncate a table You can truncate a table as follows: ```java -// Truncate a table "ns.tbl" +// Truncate the table "ns.tbl". admin.truncateTable("ns", "tbl"); ``` @@ -169,10 +190,10 @@ admin.truncateTable("ns", "tbl"); You can drop a secondary index as follows: ```java -// Drop a secondary index on a column "c5" of a table "ns.tbl". It will throw an exception if the secondary index does not exist +// Drop the secondary index on column "c5" from table "ns.tbl". If the secondary index does not exist, an exception will be thrown. admin.dropIndex("ns", "tbl", "c5"); -// Drop a secondary index only if it exists +// Drop the secondary index only if it exists. boolean ifExists = true; admin.dropIndex("ns", "tbl", "c5", ifExists); ``` @@ -182,10 +203,10 @@ admin.dropIndex("ns", "tbl", "c5", ifExists); You can drop a table as follows: ```java -// Drop a table "ns.tbl". It will throw an exception if the table does not exist +// Drop the table "ns.tbl". If the table does not exist, an exception will be thrown. admin.dropTable("ns", "tbl"); -// Drop a table only if it exists +// Drop the table only if it exists. boolean ifExists = true; admin.dropTable("ns", "tbl", ifExists); ``` @@ -195,181 +216,232 @@ admin.dropTable("ns", "tbl", ifExists); You can drop a namespace as follows: ```java -// Drop a namespace "ns". It will throw an exception if the namespace does not exist +// Drop the namespace "ns". If the namespace does not exist, an exception will be thrown. admin.dropNamespace("ns"); -// Drop a namespace only if it exists +// Drop the namespace only if it exists. boolean ifExists = true; admin.dropNamespace("ns", ifExists); ``` -### Get a table metadata +### Get the tables of a namespace -You can get a table metadata as follows: +You can get the tables of a namespace as follows: ```java -// Get a table metadata of "ns.tbl" +// Get the tables of the namespace "ns". +Set tables = admin.getNamespaceTableNames("ns"); +``` + +### Get table metadata + +You can get table metadata as follows: + +```java +// Get the table metadata for "ns.tbl". TableMetadata tableMetadata = admin.getTableMetadata("ns", "tbl"); ``` -### Operations for Coordinator tables +### Specify operations for the Coordinator table + +The Coordinator table is used by the [Transactional API](#transactional-api) to track the statuses of transactions. -Depending on the transaction manager type, you need to create coordinator tables to execute transactions. -The following items describe the operations for the coordinator table. +When using a transaction manager, you must create the Coordinator table to execute transactions. In addition to creating the table, you can truncate and drop the Coordinator table. -#### Create Coordinator tables +#### Create the Coordinator table -You can create coordinator tables as follows: +You can create the Coordinator table as follows: ```java -// Create coordinator tables +// Create the Coordinator table. admin.createCoordinatorTables(); -// Create coordinator tables only if they do not already exist +// Create the Coordinator table only if one does not already exist. boolean ifNotExist = true; admin.createCoordinatorTables(ifNotExist); -// Create coordinator tables with options +// Create the Coordinator table with options. Map options = ...; admin.createCoordinatorTables(options); ``` -#### Truncate Coordinator tables +#### Truncate the Coordinator table -You can truncate coordinator tables as follows: +You can truncate the Coordinator table as follows: ```java -// Truncate coordinator tables +// Truncate the Coordinator table. admin.truncateCoordinatorTables(); ``` -#### Drop Coordinator tables +#### Drop the Coordinator table -You can drop coordinator tables as follows: +You can drop the Coordinator table as follows: ```java -// Drop coordinator tables +// Drop the Coordinator table. admin.dropCoordinatorTables(); -// Drop coordinator tables if they exist +// Drop the Coordinator table if one exist. boolean ifExist = true; admin.dropCoordinatorTables(ifExist); ``` ## Transactional API -This section explains how to execute transactional operations with Transactional API in ScalarDB. +This section explains how to execute transactional operations by using the Transactional API in ScalarDB. -### Get a DistributedTransactionManager instance +### Get a `DistributedTransactionManager` instance -You need to get a `DistributedTransactionManager` instance to execute transactional operations. -You can get it in the following way: +You first need to get a `DistributedTransactionManager` instance to execute transactional operations. + +To get a `DistributedTransactionManager` instance, you can use `TransactionFactory` as follows: ```java -TransactionFactory transactionFactory = TransactionFactory.create(""); +TransactionFactory transactionFactory = TransactionFactory.create(""); DistributedTransactionManager transactionManager = transactionFactory.getTransactionManager(); ``` -Once you have executed all transactional operations, you should close the `DistributedTransactionManager` instance as follows: +After you have executed all transactional operations, you should close the `DistributedTransactionManager` instance as follows: ```java transactionManager.close(); ``` -### Begin/Start a transaction +### Begin or start a transaction + +Before executing transactional CRUD operations, you need to begin or start a transaction. -You need to begin/start a transaction before executing transactional CRUD operations. -You can begin/start a transaction as follows: +You can begin a transaction as follows: ```java -// Begin a transaction +// Begin a transaction. DistributedTransaction transaction = transactionManager.begin(); +``` -Or +Or, you can start a transaction as follows: -// Start a transaction +```java +// Start a transaction. DistributedTransaction transaction = transactionManager.start(); ``` -You can also begin/start a transaction with specifying a transaction ID as follows: +Alternatively, you can use the `begin` method for a transaction by specifying a transaction ID as follows: ```java -// Begin a transaction with specifying a transaction ID -DistributedTransaction transaction = transactionManager.begin(""); +// Begin a transaction with specifying a transaction ID. +DistributedTransaction transaction = transactionManager.begin(""); +``` -Or +Or, you can use the `start` method for a transaction by specifying a transaction ID as follows: -// Start a transaction with specifying a transaction ID -DistributedTransaction transaction = transactionManager.start(""); +```java +// Start a transaction with specifying a transaction ID. +DistributedTransaction transaction = transactionManager.start(""); ``` -Note that you must guarantee uniqueness of the transaction ID in this case. +{% capture notice--info %} +**Note** + +Specifying a transaction ID is useful when you want to link external systems to ScalarDB. Otherwise, you should use the `begin()` method or the `start()` method. + +When you specify a transaction ID, make sure you specify a unique ID (for example, UUID v4) throughout the system since ScalarDB depends on the uniqueness of transaction IDs for correctness. +{% endcapture %} + +
{{ notice--info | markdownify }}
### Join a transaction -You can join an ongoing transaction that has already begun with specifying a transaction ID as follows: +Joining a transaction is particularly useful in a stateful application where a transaction spans multiple client requests. In such a scenario, the application can start a transaction during the first client request. Then, in subsequent client requests, the application can join the ongoing transaction by using the `join()` method. + +You can join an ongoing transaction that has already begun by specifying the transaction ID as follows: ```java -// Join a transaction -DistributedTransaction transaction = transactionManager.join(""); +// Join a transaction. +DistributedTransaction transaction = transactionManager.join(""); ``` -This is particularly useful in a stateful application where a transaction spans across multiple client requests. -In such a scenario, the application can start a transaction during the first client request. -Then, in the subsequent client requests, it can join the ongoing transaction using the `join()` method. +{% capture notice--info %} +**Note** + +To get the transaction ID with `getId()`, you can specify the following: + +```java +tx.getId(); +``` +{% endcapture %} + +
{{ notice--info | markdownify }}
### Resume a transaction -You can resume an ongoing transaction you have already begun with specifying a transaction ID as follows: +Resuming a transaction is particularly useful in a stateful application where a transaction spans multiple client requests. In such a scenario, the application can start a transaction during the first client request. Then, in subsequent client requests, the application can resume the ongoing transaction by using the `resume()` method. + +You can resume an ongoing transaction that you have already begun by specifying a transaction ID as follows: + +```java +// Resume a transaction. +DistributedTransaction transaction = transactionManager.resume(""); +``` + +{% capture notice--info %} +**Note** + +To get the transaction ID with `getId()`, you can specify the following: ```java -// Resume a transaction -DistributedTransaction transaction = transactionManager.resume(""); +tx.getId(); ``` +{% endcapture %} + +
{{ notice--info | markdownify }}
+ +### Implement CRUD operations -This is particularly useful in a stateful application where a transaction spans across multiple client requests. -In such a scenario, the application can start a transaction during the first client request. -Then, in the subsequent client requests, it can resume the ongoing transaction using the `resume()` method. +The following sections describe key construction and CRUD operations. -### CRUD operations +{% capture notice--info %} +**Note** + +Although all the builders of the CRUD operations can specify consistency by using the `consistency()` methods, those methods are ignored. Instead, the `LINEARIZABLE` consistency level is always used in transactions. +{% endcapture %} + +
{{ notice--info | markdownify }}
#### Key construction -Most CRUD operations need to specify `Key` objects (partition-key, clustering-key, etc.). -So, before moving on to CRUD operations, the following explains how to construct a `Key` object. +Most CRUD operations need to specify `Key` objects (partition-key, clustering-key, etc.). So, before moving on to CRUD operations, the following explains how to construct a `Key` object. -For a single column key, you can use the `Key.ofXXX()` methods (XXX is a type name) to construct it as follows: +For a single column key, you can use `Key.of()` methods to construct the key as follows: ```java -// for a key that consists of a single column of Int +// For a key that consists of a single column of INT. Key key1 = Key.ofInt("col1", 1); -// for a key that consists of a single column of BigInt +// For a key that consists of a single column of BIGINT. Key key2 = Key.ofBigInt("col1", 100L); -// for a key that consists of a single column of Double +// For a key that consists of a single column of DOUBLE. Key key3 = Key.ofDouble("col1", 1.3d); -// for a key that consists of a single column of Text +// For a key that consists of a single column of TEXT. Key key4 = Key.ofText("col1", "value"); ``` -For a key that consists of 2 - 5 columns, you can use the `Key.of()` methods to construct it as follows: +For a key that consists of two to five columns, you can use the `Key.of()` method to construct the key as follows. Similar to `ImmutableMap.of()` in Guava, you need to specify column names and values in turns: ```java -// for a key that consists of 2 - 5 columns +// For a key that consists of two to five columns. Key key1 = Key.of("col1", 1, "col2", 100L); Key key2 = Key.of("col1", 1, "col2", 100L, "col3", 1.3d); Key key3 = Key.of("col1", 1, "col2", 100L, "col3", 1.3d, "col4", "value"); Key key4 = Key.of("col1", 1, "col2", 100L, "col3", 1.3d, "col4", "value", "col5", false); ``` -Similar to `ImmutableMap.of()` in Guava, you need to specify column names and values in turns. - -For a key that consists of more than 5 columns, we can use the builder to construct it as follows: +For a key that consists of more than five columns, we can use the builder to construct the key as follows: ```java -// for a key that consists of more than 5 columns +// For a key that consists of more than five columns. Key key = Key.newBuilder() .addInt("col1", 1) .addBigInt("col2", 100L) @@ -380,14 +452,14 @@ Key key = Key.newBuilder() .build(); ``` -#### Get operation +#### `Get` operation `Get` is an operation to retrieve a single record specified by a primary key. -You need to create a Get object first, and then you can execute it with the `transaction.get()` method as follows: +You need to create a `Get` object first, and then you can execute the object by using the `transaction.get()` method as follows: ```java -// Create a Get operation +// Create a `Get` operation. Key partitionKey = Key.ofInt("c1", 10); Key clusteringKey = Key.of("c2", "aaa", "c3", 100L); @@ -400,62 +472,61 @@ Get get = .projections("c1", "c2", "c3", "c4") .build(); -// Execute the Get operation +// Execute the `Get` operation. Optional result = transaction.get(get); ``` You can also specify projections to choose which columns are returned. -##### Handle Result objects +##### Handle `Result` objects -The Get operation and Scan operation return `Result` objects. -So the following shows how to handle `Result` objects. +The `Get` operation and `Scan` operation return `Result` objects. The following shows how to handle `Result` objects. -You can get a column value of a result with `getXXX("")` methods (XXX is a type name) as follows: +You can get a column value of a result by using `get("")` methods as follows: ```java -// Get a Boolean value of a column -boolean booleanValue = result.getBoolean(""); +// Get the BOOLEAN value of a column. +boolean booleanValue = result.getBoolean(""); -// Get an Int value of a column -int intValue = result.getInt(""); +// Get the INT value of a column. +int intValue = result.getInt(""); -// Get a BigInt value of a column -long bigIntValue = result.getBigInt(""); +// Get the BIGINT value of a column. +long bigIntValue = result.getBigInt(""); -// Get a Float value of a column -float floatValue = result.getFloat(""); +// Get the FLOAT value of a column. +float floatValue = result.getFloat(""); -// Get a Double value of a column -double doubleValue = result.getDouble(""); +// Get the DOUBLE value of a column. +double doubleValue = result.getDouble(""); -// Get a Text value of a column -String textValue = result.getText(""); +// Get the TEXT value of a column. +String textValue = result.getText(""); -// Get a Blob value of a column (as a ByteBuffer) -ByteBuffer blobValue = result.getBlob(""); +// Get the BLOB value of a column as a `ByteBuffer`. +ByteBuffer blobValue = result.getBlob(""); -// Get a Blob value of a column as a byte array -byte[] blobValueAsBytes = result.getBlobAsBytes(""); +// Get the BLOB value of a column as a `byte` array. +byte[] blobValueAsBytes = result.getBlobAsBytes(""); ``` -And if you need to check if a value of a column is null, you can use the `isNull("")` method. +And if you need to check if a value of a column is null, you can use the `isNull("")` method. ``` java -// Check if a value of a column is null -boolean isNull = result.isNull(""); +// Check if a value of a column is null. +boolean isNull = result.isNull(""); ``` -Please see also [Javadoc of `Result`](https://javadoc.io/static/com.scalar-labs/scalardb/3.6.0/com/scalar/db/api/Result.html) for more details. +For more details, see the `Result` page in the [Javadoc](https://javadoc.io/doc/com.scalar-labs/scalardb/latest/index.html) of the version of ScalarDB that you're using. -##### Get with a secondary index +##### Execute `Get` by using a secondary index -You can also execute a Get operation with a secondary index. +You can execute a `Get` operation by using a secondary index. -Instead of specifying a partition key, you can specify an index key (specifying an indexed column) to use a secondary index as follows: +Instead of specifying a partition key, you can specify an index key (indexed column) to use a secondary index as follows: ```java -// Create a Get operation with a secondary index +// Create a `Get` operation by using a secondary index. Key indexKey = Key.ofFloat("c4", 1.23F); Get get = @@ -466,22 +537,27 @@ Get get = .projections("c1", "c2", "c3", "c4") .build(); -// Execute the Get operation +// Execute the `Get` operation. Optional result = transaction.get(get); ``` -Note that if the result has more than one record, the `transaction.get()` throws an exception. -If you want to handle multiple results, use [Scan with a secondary index](#scan-with-a-secondary-index). +{% capture notice--info %} +**Note** + +If the result has more than one record, `transaction.get()` will throw an exception. If you want to handle multiple results, see [Execute `Scan` by using a secondary index](#execute-scan-by-using-a-secondary-index). + +{% endcapture %} -#### Scan operation +
{{ notice--info | markdownify }}
-`Scan` is an operation to retrieve multiple records within a partition. -You can specify clustering key boundaries and orderings for clustering key columns in Scan operations. +#### `Scan` operation -You need to create a Scan object first, and then you can execute it with the `transaction.scan()` method as follows: +`Scan` is an operation to retrieve multiple records within a partition. You can specify clustering-key boundaries and orderings for clustering-key columns in `Scan` operations. + +You need to create a `Scan` object first, and then you can execute the object by using the `transaction.scan()` method as follows: ```java -// Create a Scan operation +// Create a `Scan` operation. Key partitionKey = Key.ofInt("c1", 10); Key startClusteringKey = Key.of("c2", "aaa", "c3", 100L); Key endClusteringKey = Key.of("c2", "aaa", "c3", 300L); @@ -498,23 +574,22 @@ Scan scan = .limit(10) .build(); -// Execute the Scan operation +// Execute the `Scan` operation. List results = transaction.scan(scan); ``` -You can omit the clustering key boundaries, or you can specify either a start boundary or an end boundary. -If you don't specify orderings, you get results ordered by clustering order you defined when creating the table. +You can omit the clustering-key boundaries or specify either a `start` boundary or an `end` boundary. If you don't specify `orderings`, you will get results ordered by the clustering order that you defined when creating the table. -Also, you can specify projections to choose which columns are returned, and limit to specify the number of records to return in Scan operations. +In addition, you can specify `projections` to choose which columns are returned and use `limit` to specify the number of records to return in `Scan` operations. -##### Scan with a secondary index +##### Execute `Scan` by using a secondary index -You can also execute a Scan operation with a secondary index. +You can execute a `Scan` operation by using a secondary index. -Instead of specifying a partition key, you can specify an index key (specifying an indexed column) to use a secondary index as follows: +Instead of specifying a partition key, you can specify an index key (indexed column) to use a secondary index as follows: ```java -// Create a Scan operation with a secondary index +// Create a `Scan` operation by using a secondary index. Key indexKey = Key.ofFloat("c4", 1.23F); Scan scan = @@ -526,20 +601,26 @@ Scan scan = .limit(10) .build(); -// Execute the Scan operation +// Execute the `Scan` operation. List results = transaction.scan(scan); ``` -Note that you can't specify clustering key boundaries and orderings in Scan with a secondary index. +{% capture notice--info %} +**Note** + +You can't specify clustering-key boundaries and orderings in `Scan` by using a secondary index. +{% endcapture %} + +
{{ notice--info | markdownify }}
-##### Scan without a partition key to retrieve all the records of a table +##### Execute `Scan` without specifying a partition key to retrieve all the records of a table -You can also execute a Scan operation without specifying a partition key. +You can execute a `Scan` operation without specifying a partition key. Instead of calling the `partitionKey()` method in the builder, you can call the `all()` method to scan a table without specifying a partition key as follows: ```java -// Create a Scan operation without a partition key +// Create a `Scan` operation without specifying a partition key. Scan scan = Scan.newBuilder() .namespace("ns") @@ -549,22 +630,34 @@ Scan scan = .limit(10) .build(); -// Execute the Scan operation +// Execute the `Scan` operation. List results = transaction.scan(scan); ``` -Note that you can't specify clustering key boundaries and orderings in Scan without a partition key. +{% capture notice--info %} +**Note** + +You can't specify clustering-key boundaries and orderings in `Scan` without specifying a partition key. +{% endcapture %} + +
{{ notice--info | markdownify }}
-#### Put operation +#### `Put` operation -`Put` is an operation to put a record specified by a primary key. -It behaves as an upsert operation for a record, i.e., updating the record if the record exists; otherwise, inserting the record. -Note that when you update an existing record, you need to read it using a `Get` or a `Scan` before a `Put` operation. +`Put` is an operation to put a record specified by a primary key. The operation behaves as an upsert operation for a record, in which the operation updates the record if the record exists or inserts the record if the record does not exist. -You need to create a Put object first, and then you can execute it with the `transaction.put()` method as follows: +{% capture notice--info %} +**Note** + +When you update an existing record, you need to read the record by using `Get` or `Scan` before using a `Put` operation. +{% endcapture %} + +
{{ notice--info | markdownify }}
+ +You need to create a `Put` object first, and then you can execute the object by using the `transaction.put()` method as follows: ```java -// Create a Put operation +// Create a `Put` operation. Key partitionKey = Key.ofInt("c1", 10); Key clusteringKey = Key.of("c2", "aaa", "c3", 100L); @@ -578,11 +671,11 @@ Put put = .doubleValue("c5", 4.56) .build(); -// Execute the Put operation +// Execute the `Put` operation. transaction.put(put); ``` -You can also put a record with null values as follows: +You can also put a record with `null` values as follows: ```java Put put = @@ -596,15 +689,22 @@ Put put = .build(); ``` -#### Delete operation +#### `Delete` operation `Delete` is an operation to delete a record specified by a primary key. -Note that when you delete a record, you need to read it using a `Get` or a `Scan` before a `Delete` operation. -You need to create a Delete object first, and then you can execute it with the `transaction.delete()` method as follows: +{% capture notice--info %} +**Note** + +When you delete a record, you need to read the record by using `Get` or `Scan` before using a `Delete` operation. +{% endcapture %} + +
{{ notice--info | markdownify }}
+ +You need to create a `Delete` object first, and then you can execute the object by using the `transaction.delete()` method as follows: ```java -// Create a Delete operation +// Create a `Delete` operation. Key partitionKey = Key.ofInt("c1", 10); Key clusteringKey = Key.of("c2", "aaa", "c3", 100L); @@ -616,22 +716,22 @@ Delete delete = .clusteringKey(clusteringKey) .build(); -// Execute the Delete operation +// Execute the `Delete` operation. transaction.delete(delete); ``` -#### Put and Delete with a condition -You can write arbitrary conditions (e.g., a bank account balance must be equal to or more than zero) that you require a transaction to meet before being committed by having logic that checks the conditions in the transaction. -Alternatively, you can write simple conditions in a mutation operation, such as Put and Delete. +#### `Put` and `Delete` with a condition + +You can write arbitrary conditions (for example, a bank account balance must be equal to or more than zero) that you require a transaction to meet before being committed by implementing logic that checks the conditions in the transaction. Alternatively, you can write simple conditions in a mutation operation, such as `Put` and `Delete`. + +When a `Put` or `Delete` operation includes a condition, the operation is executed only if the specified condition is met. If the condition is not met when the operation is executed, an exception called `UnsatisfiedConditionException` will be thrown. -When a Put or Delete operation includes a condition, the operation is executed only if the specified condition is met. -If the condition fails to be satisfied when the operation is executed, an exception called `UnsatisfiedConditionException` is thrown. +##### Conditions for `Put` -##### Conditions for Put -You can specify a condition in a Put operation as follows: +You can specify a condition in a `Put` operation as follows: ```java -// Build a condition +// Build a condition. MutationCondition condition = ConditionBuilder.putIf(ConditionBuilder.column("c4").isEqualToFloat(0.0F)) .and(ConditionBuilder.column("c5").isEqualToDouble(0.0)) @@ -648,26 +748,26 @@ Put put = .condition(condition) // condition .build(); -// Execute the Put operation +// Execute the `Put` operation. transaction.put(put); ``` In addition to using the `putIf` condition, you can specify the `putIfExists` and `putIfNotExists` conditions as follows: ```java -// Build a putIfExists condition +// Build a `putIfExists` condition. MutationCondition putIfExistsCondition = ConditionBuilder.putIfExists(); -// Build a putIfNotExists condition +// Build a `putIfNotExists` condition. MutationCondition putIfNotExistsCondition = ConditionBuilder.putIfNotExists(); ``` -##### Conditions for Delete +##### Conditions for `Delete` -You can specify a condition in a Delete operation as follows: +You can specify a condition in a `Delete` operation as follows: ```java -// Build a condition +// Build a condition. MutationCondition condition = ConditionBuilder.deleteIf(ConditionBuilder.column("c4").isEqualToFloat(0.0F)) .and(ConditionBuilder.column("c5").isEqualToDouble(0.0)) @@ -679,28 +779,28 @@ Delete delete = .table("tbl") .partitionKey(partitionKey) .clusteringKey(clusteringKey) - .condition(condition) // condition + .condition(condition) // condition .build(); -// Execute the Delete operation +// Execute the `Delete` operation. transaction.delete(delete); ``` In addition to using the `deleteIf` condition, you can specify the `deleteIfExists` condition as follows: ```java -// Build a deleteIfExists condition +// Build a `deleteIfExists` condition. MutationCondition deleteIfExistsCondition = ConditionBuilder.deleteIfExists(); ``` #### Mutate operation -Mutate is an operation to execute multiple mutations (Put and Delete operations). +Mutate is an operation to execute multiple mutations (`Put` and `Delete` operations). -You need to create mutation objects first, and then you can execute them with the `transaction.mutate()` method as follows: +You need to create mutation objects first, and then you can execute the objects by using the `transaction.mutate()` method as follows: ```java -// Create Put and Delete operations +// Create `Put` and `Delete` operations. Key partitionKey = Key.ofInt("c1", 10); Key clusteringKeyForPut = Key.of("c2", "aaa", "c3", 100L); @@ -725,29 +825,28 @@ Delete delete = .clusteringKey(clusteringKeyForDelete) .build(); -// Execute the operations +// Execute the operations. transaction.mutate(Arrays.asList(put, delete)); ``` -#### Use a default namespace for CRUD operations +#### Default namespace for CRUD operations -A default namespace for all the CRUD operations can be set with a property of the ScalarDB configuration. -If you would like to use this setting with ScalarDB server, it needs to be set on the client-side configuration. +A default namespace for all CRUD operations can be set by using a property in the ScalarDB configuration. ```properties -scalar.db.default_namespace_name= +scalar.db.default_namespace_name= ``` Any operation that does not specify a namespace will use the default namespace set in the configuration. ```java -//This operation will target the default namespace +// This operation will target the default namespace. Scan scanUsingDefaultNamespace = Scan.newBuilder() .table("tbl") .all() .build(); -//This operation will target the "ns" namespace +// This operation will target the "ns" namespace. Scan scanUsingSpecifiedNamespace = Scan.newBuilder() .namespace("ns") @@ -756,51 +855,55 @@ Scan scanUsingSpecifiedNamespace = .build(); ``` -#### Notes - -Although all the builders of the CRUD operations can specify consistency by using the `consistency()` methods, those methods are ignored. Instead, the `LINEARIZABLE` consistency level is always used in transactions. - ### Commit a transaction After executing CRUD operations, you need to commit a transaction to finish it. -You can commit a transaction as follows; +You can commit a transaction as follows: ```java -// Commit a transaction +// Commit a transaction. transaction.commit(); ``` -### Rollback/Abort a transaction +### Roll back or abort a transaction -If you want to rollback/abort a transaction or an error happens during the execution, you can rollback/abort a transaction. +If an error occurs when executing a transaction, you can roll back or abort the transaction. -You can rollback/abort a transaction as follows; +You can roll back a transaction as follows: ```java -// Rollback a transaction +// Roll back a transaction. transaction.rollback(); +``` -Or +Or, you can abort a transaction as follows: -// Abort a transaction +```java +// Abort a transaction. transaction.abort(); ``` -Please see [Handle exceptions](#handle-exceptions) for the details of how to handle exceptions in ScalarDB. +For details about how to handle exceptions in ScalarDB, see [How to handle exceptions](#how-to-handle-exceptions). + +## How to handle exceptions + +When executing a transaction, you will also need to handle exceptions properly. -## Handle exceptions +{% capture notice--warning %} +**Attention** -Handling exceptions correctly in ScalarDB is very important. -If you mishandle exceptions, your data could become inconsistent. -This document explains how to handle exceptions properly in ScalarDB. +If you don't handle exceptions properly, you may face anomalies or data inconsistency. +{% endcapture %} -Let's look at the following example code to see how to handle exceptions in ScalarDB. +
{{ notice--warning | markdownify }}
+ +The following sample code shows how to handle exceptions: ```java public class Sample { public static void main(String[] args) throws Exception { - TransactionFactory factory = TransactionFactory.create(""); + TransactionFactory factory = TransactionFactory.create(""); DistributedTransactionManager transactionManager = factory.getTransactionManager(); int retryCount = 0; @@ -808,65 +911,63 @@ public class Sample { while (true) { if (retryCount++ > 0) { - // Retry the transaction three times maximum in this sample code + // Retry the transaction three times maximum. if (retryCount >= 3) { - // Throw the last exception if the number of retries exceeds the maximum + // Throw the last exception if the number of retries exceeds the maximum. throw lastException; } - // Sleep 100 milliseconds before retrying the transaction in this sample code + // Sleep 100 milliseconds before retrying the transaction. TimeUnit.MILLISECONDS.sleep(100); } DistributedTransaction transaction = null; try { - // Begin a transaction + // Begin a transaction. transaction = transactionManager.begin(); - // Execute CRUD operations in the transaction + // Execute CRUD operations in the transaction. Optional result = transaction.get(...); List results = transaction.scan(...); transaction.put(...); transaction.delete(...); - // Commit the transaction + // Commit the transaction. transaction.commit(); } catch (UnsatisfiedConditionException e) { - // You need to handle `UnsatisfiedConditionException` only if a mutation operation specifies - // a condition. This exception indicates the condition for the mutation operation is not met + // You need to handle `UnsatisfiedConditionException` only if a mutation operation specifies a condition. + // This exception indicates the condition for the mutation operation is not met. try { transaction.rollback(); } catch (RollbackException ex) { - // Rolling back the transaction failed. As the transaction should eventually recover, you - // don't need to do anything further. You can simply log the occurrence here + // Rolling back the transaction failed. Since the transaction should eventually recover, + // you don't need to do anything further. You can simply log the occurrence here. } - // You can handle the exception here, according to your application requirements + // You can handle the exception here, according to your application requirements. return; } catch (UnknownTransactionStatusException e) { - // If you catch `UnknownTransactionStatusException` when committing the transaction, it - // indicates that the status of the transaction, whether it has succeeded or not, is - // unknown. In such a case, you need to check if the transaction is committed successfully - // or not and retry it if it failed. How to identify a transaction status is delegated to - // users + // If you catch `UnknownTransactionStatusException` when committing the transaction, + // it indicates that the status of the transaction, whether it was successful or not, is unknown. + // In such a case, you need to check if the transaction is committed successfully or not and + // retry the transaction if it failed. How to identify a transaction status is delegated to users. return; } catch (TransactionException e) { // For other exceptions, you can try retrying the transaction. - // For `CrudConflictException` and `CommitConflictException` and - // `TransactionNotFoundException`, you can basically retry the transaction. However, for the - // other exceptions, the transaction may still fail if the cause of the exception is - // nontransient. In such a case, you will exhaust the number of retries and throw the last - // exception + // For `CrudConflictException`, `CommitConflictException`, and `TransactionNotFoundException`, + // you can basically retry the transaction. However, for the other exceptions, the transaction + // will still fail if the cause of the exception is non-transient. In such a case, you will + // exhaust the number of retries and throw the last exception. if (transaction != null) { try { transaction.rollback(); } catch (RollbackException ex) { - // Rolling back the transaction failed. As the transaction should eventually recover, - // you don't need to do anything further. You can simply log the occurrence here + // Rolling back the transaction failed. The transaction should eventually recover, + // so you don't need to do anything further. You can simply log the occurrence here. } } @@ -877,60 +978,59 @@ public class Sample { } ``` -The `begin()` API could throw `TransactionException` or `TransactionNotFoundException`. -If you catch `TransactionException`, it indicates that the transaction has failed to begin due to transient or nontransient faults. You can try retrying the transaction, but you may not be able to begin the transaction due to nontransient faults. -If you catch `TransactionNotFoundException`, it indicates that the transaction has failed to begin due to transient faults. You can retry the transaction. +### `TransactionException` and `TransactionNotFoundException` + +The `begin()` API could throw `TransactionException` or `TransactionNotFoundException`: + +- If you catch `TransactionException`, this exception indicates that the transaction has failed to begin due to transient or non-transient faults. You can try retrying the transaction, but you may not be able to begin the transaction due to non-transient faults. +- If you catch `TransactionNotFoundException`, this exception indicates that the transaction has failed to begin due to transient faults. In this case, you can retry the transaction. + +The `join()` API could also throw `TransactionNotFoundException`. You can handle this exception in the same way that you handle the exceptions for the `begin()` API. + +### `CrudException` and `CrudConflictException` -The APIs for CRUD operations (`get()`, `scan()`, `put()`, `delete()`, and `mutate()`) could throw `CrudException` or `CrudConflictException`. -If you catch `CrudException`, it indicates that the transaction CRUD operation has failed due to transient or nontransient faults. You can try retrying the transaction from the beginning, but the transaction may still fail if the cause is nontransient. -If you catch `CrudConflictException`, it indicates that the transaction CRUD operation has failed due to transient faults (e.g., a conflict error). You can retry the transaction from the beginning. +The APIs for CRUD operations (`get()`, `scan()`, `put()`, `delete()`, and `mutate()`) could throw `CrudException` or `CrudConflictException`: + +- If you catch `CrudException`, this exception indicates that the transaction CRUD operation has failed due to transient or non-transient faults. You can try retrying the transaction from the beginning, but the transaction may still fail if the cause is non-transient. +- If you catch `CrudConflictException`, this exception indicates that the transaction CRUD operation has failed due to transient faults (for example, a conflict error). In this case, you can retry the transaction from the beginning. + +### `UnsatisfiedConditionException` The APIs for mutation operations (`put()`, `delete()`, and `mutate()`) could also throw `UnsatisfiedConditionException`. -If you catch this exception, it indicates that the condition for the mutation operation is not met. -You can handle this exception according to your application requirements. -Also, the `commit()` API could throw `CommitException`, `CommitConflictException`, or `UnknownTransactionStatusException`. -If you catch `CommitException`, it indicates that committing the transaction fails due to transient or nontransient faults. You can try retrying the transaction from the beginning, but the transaction may still fail if the cause is nontransient. -If you catch `CommitConflictException`, it indicates that committing the transaction has failed due to transient faults (e.g., a conflict error). You can retry the transaction from the beginning. -If you catch `UnknownTransactionStatusException`, it indicates that the status of the transaction, whether it has succeeded or not, is unknown. -In such a case, you need to check if the transaction is committed successfully and retry the transaction if it has failed. -How to identify a transaction status is delegated to users. -You may want to create a transaction status table and update it transactionally with other application data so that you can get the status of a transaction from the status table. +If you catch `UnsatisfiedConditionException`, this exception indicates that the condition for the mutation operation is not met. You can handle this exception according to your application requirements. + +### `CommitException`, `CommitConflictException`, and `UnknownTransactionStatusException` + +The `commit()` API could throw `CommitException`, `CommitConflictException`, or `UnknownTransactionStatusException`: + +- If you catch `CommitException`, this exception indicates that committing the transaction fails due to transient or non-transient faults. You can try retrying the transaction from the beginning, but the transaction may still fail if the cause is non-transient. +- If you catch `CommitConflictException`, this exception indicates that committing the transaction has failed due to transient faults (for example, a conflict error). In this case, you can retry the transaction from the beginning. +- If you catch `UnknownTransactionStatusException`, this exception indicates that the status of the transaction, whether it was successful or not, is unknown. In this case, you need to check if the transaction is committed successfully and retry the transaction if it has failed. -Although not illustrated in the sample code, the `resume()` API could also throw `TransactionNotFoundException`. -This exception indicates that the transaction associated with the specified ID was not found and/or the transaction might have expired. -In either case, you can retry the transaction from the beginning since the cause of this exception is basically transient. +How to identify a transaction status is delegated to users. You may want to create a transaction status table and update it transactionally with other application data so that you can get the status of a transaction from the status table. -In the sample code, for `UnknownTransactionStatusException`, the transaction is not retried because the cause of the exception is nontransient. -Also, for `UnsatisfiedConditionException`, the transaction is not retried because how to handle this exception depends on your application requirements. -For other exceptions, the transaction is retried because the cause of the exception is transient or nontransient. -If the cause of the exception is transient, the transaction may succeed if you retry it. -However, if the cause of the exception is nontransient, the transaction may still fail even if you retry it. -In such a case, you will exhaust the number of retries. +### Notes about some exceptions -Please note that if you begin a transaction by specifying a transaction ID, you must use a different ID when you retry the transaction. -And, in the sample code, the transaction is retried three times maximum and sleeps for 100 milliseconds before it is retried. -But you can choose a retry policy, such as exponential backoff, according to your application requirements. +Although not illustrated in the sample code, the `resume()` API could also throw `TransactionNotFoundException`. This exception indicates that the transaction associated with the specified ID was not found and/or the transaction might have expired. In either case, you can retry the transaction from the beginning since the cause of this exception is basically transient. -## Transactional operations for Two-phase Commit Transaction +In the sample code, for `UnknownTransactionStatusException`, the transaction is not retried because the application must check if the transaction was successful to avoid potential duplicate operations. For other exceptions, the transaction is retried because the cause of the exception is transient or non-transient. If the cause of the exception is transient, the transaction may succeed if you retry it. However, if the cause of the exception is non-transient, the transaction will still fail even if you retry it. In such a case, you will exhaust the number of retries. -Please see [Two-phase Commit Transactions](two-phase-commit-transactions.md). +{% capture notice--info %} +**Note** -## Investigate Consensus Commit transactions errors +In the sample code, the transaction is retried three times maximum and sleeps for 100 milliseconds before it is retried. But you can choose a retry policy, such as exponential backoff, according to your application requirements. +{% endcapture %} -This configuration is only available to troubleshoot Consensus Commit transactions. By adding the following configuration, `Get` and `Scan` operations results will contain [transaction metadata](schema-loader.md#internal-metadata-for-consensus-commit). -To see the transaction metadata columns details for a given table, you can use the `DistributedTransactionAdmin.getTableMetadata()` method which will return the table metadata augmented with the transaction metadata columns. -All in all, using this configuration can be useful to investigate transaction related issues. +
{{ notice--info | markdownify }}
+ +## Investigating Consensus Commit transaction manager errors + +To investigate errors when using the Consensus Commit transaction manager, you can enable a configuration that will return table metadata augmented with transaction metadata columns, which can be helpful when investigating transaction-related issues. This configuration, which is only available when troubleshooting the Consensus Commit transaction manager, enables you to see transaction metadata column details for a given table by using the `DistributedTransactionAdmin.getTableMetadata()` method. + +By adding the following configuration, `Get` and `Scan` operations results will contain [transaction metadata](schema-loader.md#internal-metadata-for-consensus-commit): ```properties -# By default, it is set to "false". +# By default, this configuration is set to `false`. scalar.db.consensus_commit.include_metadata.enabled=true ``` - -## References - -* [Design document](design.md) -* [Getting started](getting-started-with-scalardb.md) -* [Multi-storage Transactions](multi-storage-transactions.md) -* [Two-phase Commit Transactions](two-phase-commit-transactions.md) -* [ScalarDB Server](scalardb-server.md) diff --git a/docs/3.6/api-guide.md b/docs/3.6/api-guide.md index 91dd65c..370d991 100644 --- a/docs/3.6/api-guide.md +++ b/docs/3.6/api-guide.md @@ -1,29 +1,33 @@ -# Java API Guide +# ScalarDB Java API Guide -Scalar DB Java API is mainly composed of Administrative API and Transactional API. -This guide briefly explains what kind of APIs exist and how to use them. - -* [Administrative API](#administrative-api) -* [Transactional API](#transactional-api) +The ScalarDB Java API is mainly composed of the Administrative API and Transactional API. This guide briefly explains what kinds of APIs exist, how to use them, and related topics like how to handle exceptions. ## Administrative API -This section explains how to execute administrative operations with Administrative API in Scalar DB. -You can execute administrative operations programmatically as follows, but you can also execute those operations through [Schema Loader](https://github.com/scalar-labs/scalardb/tree/master/schema-loader/README.md). +This section explains how to execute administrative operations programmatically by using the Administrative API in ScalarDB. + +{% capture notice--info %} +**Note** + +Another method for executing administrative operations is to use [Schema Loader](schema-loader.md). +{% endcapture %} + +
{{ notice--info | markdownify }}
-### Get a DistributedTransactionAdmin instance +### Get a `DistributedTransactionAdmin` instance -To execute administrative operations, you first need to get a `DistributedTransactionAdmin` instance. -The `DistributedTransactionAdmin` instance can be obtained from `TransactionFactory` as follows: +You first need to get a `DistributedTransactionAdmin` instance to execute administrative operations. + +To get a `DistributedTransactionAdmin` instance, you can use `TransactionFactory` as follows: ```java -TransactionFactory transactionFactory = TransactionFactory.create(""); +TransactionFactory transactionFactory = TransactionFactory.create(""); DistributedTransactionAdmin admin = transactionFactory.getTransactionAdmin(); ``` -Please see [Getting Started](getting-started.md) for the details of the configuration file. +For details about configurations, see [ScalarDB Configurations](configurations.md). -Once you have executed all administrative operations, you should close the `DistributedTransactionAdmin` instance as follows: +After you have executed all administrative operations, you should close the `DistributedTransactionAdmin` instance as follows: ```java admin.close(); @@ -32,60 +36,77 @@ admin.close(); ### Create a namespace Before creating tables, namespaces must be created since a table belongs to one namespace. + You can create a namespace as follows: ```java -// Create a namespace "ns". It will throw an exception if the namespace already exists +// Create the namespace "ns". If the namespace already exists, an exception will be thrown. admin.createNamespace("ns"); -// Create a namespace only if it does not already exist +// Create the namespace only if it does not already exist. boolean ifNotExists = true; admin.createNamespace("ns", ifNotExists); -// Create a namespace with options +// Create the namespace with options. Map options = ...; admin.createNamespace("ns", options); ``` -#### Creation Options +#### Creation options + +In the creation operations, like creating a namespace and creating a table, you can specify options that are maps of option names and values (`Map`). By using the options, you can set storage adapter–specific configurations. -In the creation operations (creating a namespace, creating a table, etc.), you can specify options that are maps of option names and values (`Map`). -With the options, we can set storage adapter specific configurations. +Select your database to see the options available: -Currently, we can set the following options for the storage adapters: +
+
+ + + + +
-For Cosmos DB: +
-| name | value | default | -| ---------- | ---------------------------------- | ------- | -| ru | Base resource unit | 400 | -| no-scaling | Disable auto-scaling for Cosmos DB | false | +| Name | Description | Default | +|----------------------|----------------------------------------------------------------------------------------|------------------| +| replication-strategy | Cassandra replication strategy. Must be `SimpleStrategy` or `NetworkTopologyStrategy`. | `SimpleStrategy` | +| compaction-strategy | Cassandra compaction strategy, Must be `LCS`, `STCS` or `TWCS`. | `STCS` | +| replication-factor | Cassandra replication factor. | 1 | -For DynamoDB: +
+
-| name | value | default | -| ---------- | -------------------------------------- | ------- | -| no-scaling | Disable auto-scaling for DynamoDB | false | -| no-backup | Disable continuous backup for DynamoDB | false | -| ru | Base resource unit | 10 | +| Name | Description | Default | +|------------|-----------------------------------------------------|---------| +| ru | Base resource unit. | 400 | +| no-scaling | Disable auto-scaling for Cosmos DB for NoSQL. | false | -For Cassandra: +
+
-| name | value | default | -| -------------------- | ------------------------------------------------------------------------------------- | ---------------- | -| replication-strategy | Cassandra replication strategy, must be `SimpleStrategy` or `NetworkTopologyStrategy` | `SimpleStrategy` | -| compaction-strategy | Cassandra compaction strategy, must be `LCS`, `STCS` or `TWCS` | `STCS` | -| replication-factor | Cassandra replication factor | 1 | +| Name | Description | Default | +|------------|-----------------------------------------|---------| +| no-scaling | Disable auto-scaling for DynamoDB. | false | +| no-backup | Disable continuous backup for DynamoDB. | false | +| ru | Base resource unit. | 10 | +
+
+ +No options are available for JDBC databases. + +
+
### Create a table -Next, we will discuss table creation. +When creating a table, you should define the table metadata and then create the table. -You firstly need to create the TaleMetadata as follows: +To define the table metadata, you can use `TableMetadata`. The following shows how to define the columns, partition key, clustering key including clustering orders, and secondary indexes of a table: ```java -// Define a table metadata +// Define the table metadata. TableMetadata tableMetadata = TableMetadata.newBuilder() .addColumn("c1", DataType.INT) @@ -100,21 +121,19 @@ TableMetadata tableMetadata = .build(); ``` -Here you define columns, a partition key, a clustering key including clustering orders, and secondary indexes of a table. - -Please see [Scalar DB design document - Data Model](design.md#data-model) for the details of the Scalar DB Data Model. +For details about the data model of ScalarDB, see [Data Model](design.md#data-model). -And then, you can create a table as follows: +Then, create a table as follows: ```java -// Create a table "ns.tbl". It will throw an exception if the table already exists +// Create the table "ns.tbl". If the table already exists, an exception will be thrown. admin.createTable("ns", "tbl", tableMetadata); -// Create a table only if it does not already exist +// Create the table only if it does not already exist. boolean ifNotExists = true; admin.createTable("ns", "tbl", tableMetadata, ifNotExists); -// Create a table with options +// Create the table with options. Map options = ...; admin.createTable("ns", "tbl", tableMetadata, options); ``` @@ -124,14 +143,14 @@ admin.createTable("ns", "tbl", tableMetadata, options); You can create a secondary index as follows: ```java -// Create a secondary index on a column "c5" of a table "ns.tbl". It will throw an exception if the secondary index already exists +// Create a secondary index on column "c5" for table "ns.tbl". If a secondary index already exists, an exception will be thrown. admin.createIndex("ns", "tbl", "c5"); -// Create a secondary index only if it does not already exist +// Create the secondary index only if it does not already exist. boolean ifNotExists = true; admin.createIndex("ns", "tbl", "c5", ifNotExists); -// Create a secondary index with options +// Create the secondary index with options. Map options = ...; admin.createIndex("ns", "tbl", "c5", options); ``` @@ -141,7 +160,7 @@ admin.createIndex("ns", "tbl", "c5", options); You can truncate a table as follows: ```java -// Truncate a table "ns.tbl" +// Truncate the table "ns.tbl". admin.truncateTable("ns", "tbl"); ``` @@ -150,10 +169,10 @@ admin.truncateTable("ns", "tbl"); You can drop a secondary index as follows: ```java -// Drop a secondary index on a column "c5" of a table "ns.tbl". It will throw an exception if the secondary index does not exist +// Drop the secondary index on column "c5" from table "ns.tbl". If the secondary index does not exist, an exception will be thrown. admin.dropIndex("ns", "tbl", "c5"); -// Drop a secondary index only if it exists +// Drop the secondary index only if it exists. boolean ifExists = true; admin.dropIndex("ns", "tbl", "c5", ifExists); ``` @@ -163,10 +182,10 @@ admin.dropIndex("ns", "tbl", "c5", ifExists); You can drop a table as follows: ```java -// Drop a table "ns.tbl". It will throw an exception if the table does not exist +// Drop the table "ns.tbl". If the table does not exist, an exception will be thrown. admin.dropTable("ns", "tbl"); -// Drop a table only if it exists +// Drop the table only if it exists. boolean ifExists = true; admin.dropTable("ns", "tbl", ifExists); ``` @@ -176,135 +195,157 @@ admin.dropTable("ns", "tbl", ifExists); You can drop a namespace as follows: ```java -// Drop a namespace "ns". It will throw an exception if the namespace does not exist +// Drop the namespace "ns". If the namespace does not exist, an exception will be thrown. admin.dropNamespace("ns"); -// Drop a namespace only if it exists +// Drop the namespace only if it exists. boolean ifExists = true; admin.dropNamespace("ns", ifExists); ``` -### Get a table metadata +### Get the tables of a namespace -You can get a table metadata as follows: +You can get the tables of a namespace as follows: ```java -// Get a table metadata of "ns.tbl" +// Get the tables of the namespace "ns". +Set tables = admin.getNamespaceTableNames("ns"); +``` + +### Get table metadata + +You can get table metadata as follows: + +```java +// Get the table metadata for "ns.tbl". TableMetadata tableMetadata = admin.getTableMetadata("ns", "tbl"); ``` -### Operations for Coordinator tables +### Specify operations for the Coordinator table + +The Coordinator table is used by the [Transactional API](#transactional-api) to track the statuses of transactions. -Depending on the transaction manager type, you need to create coordinator tables to execute transactions. -The following items describe the operations for the coordinator table. +When using a transaction manager, you must create the Coordinator table to execute transactions. In addition to creating the table, you can truncate and drop the Coordinator table. -#### Create Coordinator tables +#### Create the Coordinator table -You can create coordinator table as follows: +You can create the Coordinator table as follows: ```java -// Create coordinator tables +// Create the Coordinator table. admin.createCoordinatorTables(); -// Create coordinator tables if not exist +// Create the Coordinator table only if one does not already exist. boolean ifNotExist = true; admin.createCoordinatorTables(ifNotExist); -// Create coordinator tables with options +// Create the Coordinator table with options. Map options = ...; admin.createCoordinatorTables(options); ``` -#### Truncate Coordinator tables +#### Truncate the Coordinator table -You can create truncate table as follows: +You can truncate the Coordinator table as follows: ```java -// Truncate coordinator tables +// Truncate the Coordinator table. admin.truncateCoordinatorTables(); ``` -#### Drop Coordinator tables +#### Drop the Coordinator table -You can drop truncate table as follows: +You can drop the Coordinator table as follows: ```java -// Drop coordinator tables +// Drop the Coordinator table. admin.dropCoordinatorTables(); -// Drop coordinator tables if exist +// Drop the Coordinator table if one exist. boolean ifExist = true; admin.dropCoordinatorTables(ifExist); ``` ## Transactional API -This section explains how to execute transactional operations with Transactional API in Scalar DB. +This section explains how to execute transactional operations by using the Transactional API in ScalarDB. -### Get a DistributedTransactionManager instance +### Get a `DistributedTransactionManager` instance -You need to get a `DistributedTransactionManager` instance to execute transactional operations. -You can get it in the following way: +You first need to get a `DistributedTransactionManager` instance to execute transactional operations. + +To get a `DistributedTransactionManager` instance, you can use `TransactionFactory` as follows: ```java -TransactionFactory transactionFactory = TransactionFactory.create(""); +TransactionFactory transactionFactory = TransactionFactory.create(""); DistributedTransactionManager manager = transactionFactory.getTransactionManager(); ``` -Once you have executed all transactional operations, you should close the `DistributedTransactionManager` instance as follows: +After you have executed all transactional operations, you should close the `DistributedTransactionManager` instance as follows: ```java manager.close(); ``` -### Start transaction +### Start a transaction + +Before executing transactional CRUD operations, you need to start a transaction. -You need to start a transaction before executing transactional CRUD operations. You can start a transaction as follows: ```java +// Start a transaction. DistributedTransaction transaction = manager.start(); ``` -### CRUD operations +### Implement CRUD operations + +The following sections describe key construction and CRUD operations. + +{% capture notice--info %} +**Note** + +Although all the builders of the CRUD operations can specify consistency by using the `consistency()` methods, those methods are ignored. Instead, the `LINEARIZABLE` consistency level is always used in transactions. + +In addition, although the builders of the mutation operations (`Put` and `Delete` operations) can specify a condition by using the `condition()` methods, those methods are also ignored. Instead, if you want to implement conditional mutation, please program such conditions for transactions. +{% endcapture %} + +
{{ notice--info | markdownify }}
#### Key construction -Most CRUD operations need to specify `Key` objects (partition-key, clustering-key, etc.). -So, before moving on to CRUD operations, here explains how to construct a `Key` object. +Most CRUD operations need to specify `Key` objects (partition-key, clustering-key, etc.). So, before moving on to CRUD operations, the following explains how to construct a `Key` object. -For a single column key, you can use the `Key.ofXXX()` methods (XXX is a type name) to construct it as follows: +For a single column key, you can use `Key.of()` methods to construct the key as follows: ```java -// for a key that consists of a single column of Int +// For a key that consists of a single column of INT. Key key1 = Key.ofInt("col1", 1); -// for a key that consists of a single column of BigInt +// For a key that consists of a single column of BIGINT. Key key2 = Key.ofBigInt("col1", 100L); -// for a key that consists of a single column of Double +// For a key that consists of a single column of DOUBLE. Key key3 = Key.ofDouble("col1", 1.3d); -// for a key that consists of a single column of Text +// For a key that consists of a single column of TEXT. Key key4 = Key.ofText("col1", "value"); ``` -For a key that consists of 2 - 5 columns, you can use the `Key.of()` methods to construct it as follows: +For a key that consists of two to five columns, you can use the `Key.of()` method to construct the key as follows. Similar to `ImmutableMap.of()` in Guava, you need to specify column names and values in turns: ```java -// for a key that consists of 2 - 5 columns +// For a key that consists of two to five columns. Key key1 = Key.of("col1", 1, "col2", 100L); Key key2 = Key.of("col1", 1, "col2", 100L, "col3", 1.3d); Key key3 = Key.of("col1", 1, "col2", 100L, "col3", 1.3d, "col4", "value"); Key key4 = Key.of("col1", 1, "col2", 100L, "col3", 1.3d, "col4", "value", "col5", false); ``` -Similar to `ImmutableMap.of()` in Guava, you need to specify column names and values in turns. - -For a key that consists of more than 5 columns, we can use the builder to construct it as follows: +For a key that consists of more than five columns, we can use the builder to construct the key as follows: ```java -// for a key that consists of more than 5 columns +// For a key that consists of more than five columns. Key key = Key.newBuilder() .addInt("col1", 1) .addBigInt("col2", 100L) @@ -315,14 +356,14 @@ Key key = Key.newBuilder() .build(); ``` -#### Get operation +#### `Get` operation `Get` is an operation to retrieve a single record specified by a primary key. -You need to create a Get object first, and then you can execute it with the `transaction.get()` method as follows: +You need to create a `Get` object first, and then you can execute the object by using the `transaction.get()` method as follows: ```java -// Create a Get operation +// Create a `Get` operation. Key partitionKey = Key.ofInt("c1", 10); Key clusteringKey = Key.of("c2", "aaa", "c3", 100L); @@ -335,60 +376,61 @@ Get get = .projections("c1", "c2", "c3", "c4") .build(); -// Execute the Get operation +// Execute the `Get` operation. Optional result = transaction.get(get); ``` You can also specify projections to choose which columns are returned. -##### Handle Result objects +##### Handle `Result` objects -The Get operation and Scan operation return `Result` objects. -So this section shows how to handle `Result` objects. +The `Get` operation and `Scan` operation return `Result` objects. The following shows how to handle `Result` objects. -You can get a column value of a result with `getXXX("")` methods (XXX is a type name) as follows: +You can get a column value of a result by using `get("")` methods as follows: ```java -// Get a Boolean value of a column -boolean booleanValue = result.getBoolean(""); +// Get the BOOLEAN value of a column. +boolean booleanValue = result.getBoolean(""); -// Get an Int value of a column -int intValue = result.getInt(""); +// Get the INT value of a column. +int intValue = result.getInt(""); -// Get a BigInt value of a column -long bigIntValue = result.getBigInt(""); +// Get the BIGINT value of a column. +long bigIntValue = result.getBigInt(""); -// Get a Float value of a column -float floatValue = result.getFloat(""); +// Get the FLOAT value of a column. +float floatValue = result.getFloat(""); -// Get a Double value of a column -double doubleValue = result.getDouble(""); +// Get the DOUBLE value of a column. +double doubleValue = result.getDouble(""); -// Get a Text value of a column -String textValue = result.getText(""); +// Get the TEXT value of a column. +String textValue = result.getText(""); -// Get a Blob value of a column (as a ByteBuffer) -ByteBuffer blobValue = result.getBlob(""); +// Get the BLOB value of a column as a `ByteBuffer`. +ByteBuffer blobValue = result.getBlob(""); -// Get a Blob value of a column as a byte array -byte[] blobValueAsBytes = result.getBlobAsBytes(""); +// Get the BLOB value of a column as a `byte` array. +byte[] blobValueAsBytes = result.getBlobAsBytes(""); ``` -And if you need to check if a value of a column is null, you can use the `isNull("")` method. +And if you need to check if a value of a column is null, you can use the `isNull("")` method. ``` java -// Check if a value of a column is null -boolean isNull = result.isNull(""); +// Check if a value of a column is null. +boolean isNull = result.isNull(""); ``` -##### Get with a secondary index +For more details, see the `Result` page in the [Javadoc](https://javadoc.io/doc/com.scalar-labs/scalardb/latest/index.html) of the version of ScalarDB that you're using. + +##### Execute `Get` by using a secondary index -You can also execute a Get operation with a secondary index. +You can execute a `Get` operation by using a secondary index. -Instead of specifying a partition key, you can specify an index key (specifying an indexed column) to use a secondary index as follows: +Instead of specifying a partition key, you can specify an index key (indexed column) to use a secondary index as follows: ```java -// Create a Get operation with a secondary index +// Create a `Get` operation by using a secondary index. Key indexKey = Key.ofFloat("c4", 1.23F); Get get = @@ -399,22 +441,27 @@ Get get = .projections("c1", "c2", "c3", "c4") .build(); -// Execute the Get operation +// Execute the `Get` operation. Optional result = transaction.get(get); ``` -Note that if the result has more than one record, the `transaction.get()` throws an exception. -If you want to handle multiple results, use [Scan with a secondary index](#scan-with-a-secondary-index). +{% capture notice--info %} +**Note** -#### Scan operation +If the result has more than one record, `transaction.get()` will throw an exception. If you want to handle multiple results, see [Execute `Scan` by using a secondary index](#execute-scan-by-using-a-secondary-index). -`Scan` is an operation to retrieve multiple records within a partition. -You can specify clustering key boundaries and orderings for clustering key columns in Scan operations. +{% endcapture %} -You need to create a Scan object first, and then you can execute it with the `transaction.scan()` method as follows: +
{{ notice--info | markdownify }}
+ +#### `Scan` operation + +`Scan` is an operation to retrieve multiple records within a partition. You can specify clustering-key boundaries and orderings for clustering-key columns in `Scan` operations. + +You need to create a `Scan` object first, and then you can execute the object by using the `transaction.scan()` method as follows: ```java -// Create a Scan operation +// Create a `Scan` operation. Key partitionKey = Key.ofInt("c1", 10); Key startClusteringKey = Key.of("c2", "aaa", "c3", 100L); Key endClusteringKey = Key.of("c2", "aaa", "c3", 300L); @@ -431,23 +478,22 @@ Scan scan = .limit(10) .build(); -// Execute the Scan operation +// Execute the `Scan` operation. List results = transaction.scan(scan); ``` -You can omit the clustering key boundaries, or you can specify either a start boundary or an end boundary. -If you don't specify orderings, you get results ordered by clustering order you defined when creating the table. +You can omit the clustering-key boundaries or specify either a `start` boundary or an `end` boundary. If you don't specify `orderings`, you will get results ordered by the clustering order that you defined when creating the table. -Also, you can specify projections to choose which columns are returned, and limit to specify the number of records to return in Scan operations. +In addition, you can specify `projections` to choose which columns are returned and use `limit` to specify the number of records to return in `Scan` operations. -##### Scan with a secondary index +##### Execute `Scan` by using a secondary index -You can also execute a Scan operation with a secondary index. +You can execute a `Scan` operation by using a secondary index. -Instead of specifying a partition key, you can specify an index key (specifying an indexed column) to use a secondary index as follows: +Instead of specifying a partition key, you can specify an index key (indexed column) to use a secondary index as follows: ```java -// Create a Scan operation with a secondary index +// Create a `Scan` operation by using a secondary index. Key indexKey = Key.ofFloat("c4", 1.23F); Scan scan = @@ -459,24 +505,26 @@ Scan scan = .limit(10) .build(); -// Execute the Scan operation +// Execute the `Scan` operation. List results = transaction.scan(scan); ``` -Note that you can't specify clustering key boundaries and orderings in Scan with a secondary index. +{% capture notice--info %} +**Note** -##### Scan without a partition key to retrieve all the records of a table +You can't specify clustering-key boundaries and orderings in `Scan` by using a secondary index. +{% endcapture %} -You can also execute a Scan operation without specifying a partition key. +
{{ notice--info | markdownify }}
+ +##### Execute `Scan` without specifying a partition key to retrieve all the records of a table + +You can execute a `Scan` operation without specifying a partition key. Instead of calling the `partitionKey()` method in the builder, you can call the `all()` method to scan a table without specifying a partition key as follows: ```java -// Create a Scan operation without a partition key -Key partitionKey = Key.ofInt("c1", 10); -Key startClusteringKey = Key.of("c2", "aaa", "c3", 100L); -Key endClusteringKey = Key.of("c2", "aaa", "c3", 300L); - +// Create a `Scan` operation without specifying a partition key. Scan scan = Scan.newBuilder() .namespace("ns") @@ -486,21 +534,34 @@ Scan scan = .limit(10) .build(); -// Execute the Scan operation +// Execute the `Scan` operation. List results = transaction.scan(scan); ``` -Note that you can't specify clustering key boundaries and orderings in Scan without a partition key. +{% capture notice--info %} +**Note** + +You can't specify clustering-key boundaries and orderings in `Scan` without specifying a partition key. +{% endcapture %} + +
{{ notice--info | markdownify }}
+ +#### `Put` operation -#### Put operation +`Put` is an operation to put a record specified by a primary key. The operation behaves as an upsert operation for a record, in which the operation updates the record if the record exists or inserts the record if the record does not exist. -`Put` is an operation to put a record specified by a primary key. -It behaves as an upsert operation for a record, i.e., updating the record if the record exists; otherwise, inserting the record. +{% capture notice--info %} +**Note** -You need to create a Put object first, and then you can execute it with the `transaction.put()` method as follows: +When you update an existing record, you need to read the record by using `Get` or `Scan` before using a `Put` operation. +{% endcapture %} + +
{{ notice--info | markdownify }}
+ +You need to create a `Put` object first, and then you can execute the object by using the `transaction.put()` method as follows: ```java -// Create a Put operation +// Create a `Put` operation. Key partitionKey = Key.ofInt("c1", 10); Key clusteringKey = Key.of("c2", "aaa", "c3", 100L); @@ -514,11 +575,11 @@ Put put = .doubleValue("c5", 4.56) .build(); -// Execute the Put operation +// Execute the `Put` operation. transaction.put(put); ``` -You can also put a record with null values as follows: +You can also put a record with `null` values as follows: ```java Put put = @@ -532,14 +593,22 @@ Put put = .build(); ``` -#### Delete operation +#### `Delete` operation `Delete` is an operation to delete a record specified by a primary key. -You need to create a Delete object first, and then you can execute it with the `transaction.delete()` method as follows: +{% capture notice--info %} +**Note** + +When you delete a record, you need to read the record by using `Get` or `Scan` before using a `Delete` operation. +{% endcapture %} + +
{{ notice--info | markdownify }}
+ +You need to create a `Delete` object first, and then you can execute the object by using the `transaction.delete()` method as follows: ```java -// Create a Delete operation +// Create a `Delete` operation. Key partitionKey = Key.ofInt("c1", 10); Key clusteringKey = Key.of("c2", "aaa", "c3", 100L); @@ -551,18 +620,18 @@ Delete delete = .clusteringKey(clusteringKey) .build(); -// Execute the Delete operation +// Execute the `Delete` operation. transaction.delete(delete); ``` #### Mutate operation -Mutate is an operation to execute multiple mutations (Put and Delete operations). +Mutate is an operation to execute multiple mutations (`Put` and `Delete` operations). -You need to create mutation objects first, and then you can execute them with the `transaction.mutate()` method as follows: +You need to create mutation objects first, and then you can execute the objects by using the `transaction.mutate()` method as follows: ```java -// Create Put and Delete operations +// Create `Put` and `Delete` operations. Key partitionKey = Key.ofInt("c1", 10); Key clusteringKeyForPut = Key.of("c2", "aaa", "c3", 100L); @@ -587,67 +656,63 @@ Delete delete = .clusteringKey(clusteringKeyForDelete) .build(); -// Execute the operations +// Execute the operations. transaction.mutate(Arrays.asList(put, delete)); ``` -#### Notes - -- All the builders of the CRUD operations can specify consistency with the `consistency()` methods, but it's ignored, and the `LINEARIZABLE` consistency level is always used in transactions. -- Also, the builders of the mutation operations (Put and Delete operations) can specify a condition with the `condition()` methods, but it's ignored, too. -Please program such conditions in a transaction if you want to implement conditional mutation. - ### Commit a transaction After executing CRUD operations, you need to commit a transaction to finish it. -You can commit a transaction as follows; +You can commit a transaction as follows: ```java -// Commit a transaction +// Commit a transaction. transaction.commit(); ``` ### Abort a transaction -If you want to abort a transaction or an error happens during the execution, you can abort a transaction. +If an error occurs when executing a transaction, you can abort the transaction. -You can abort a transaction as follows; +You can abort a transaction as follows: ```java -// Abort a transaction +// Abort a transaction. transaction.abort(); ``` -Please see [Handle Exceptions](#handle-exceptions) for the details of how to handle exceptions in Scalar DB. +For details about how to handle exceptions in ScalarDB, see [How to handle exceptions](#how-to-handle-exceptions). + +## How to handle exceptions -## Transactional operations for Two-phase Commit Transaction +When executing a transaction, you will also need to handle exceptions properly. -Please see [Two-phase Commit Transactions](two-phase-commit-transactions.md). +{% capture notice--warning %} +**Attention** -## Handle Exceptions +If you don't handle exceptions properly, you may face anomalies or data inconsistency. +{% endcapture %} -Handling exceptions correctly in Scalar DB is very important. -If you mishandle exceptions, your data could become inconsistent. -This document explains how to handle exceptions properly in Scalar DB. +
{{ notice--warning | markdownify }}
-Let's look at the following example code to see how to handle exceptions in Scalar DB. +The following sample code shows how to handle exceptions: ```java public class Sample { public static void main(String[] args) throws IOException, InterruptedException { - TransactionFactory factory = TransactionFactory.create(""); + TransactionFactory factory = TransactionFactory.create(""); DistributedTransactionManager manager = factory.getTransactionManager(); int retryCount = 0; while (true) { if (retryCount > 0) { - // Retry the transaction three times maximum in this sample code + // Retry the transaction three times maximum in this sample code. if (retryCount == 3) { return; } - // Sleep 100 milliseconds before retrying the transaction in this sample code + // Sleep 100 milliseconds before retrying the transaction in this sample code. TimeUnit.MILLISECONDS.sleep(100); } @@ -656,44 +721,43 @@ public class Sample { try { tx = manager.start(); } catch (TransactionException e) { - // If starting a transaction fails, it indicates some failure happens during a transaction, - // so you should cancel the transaction or retry the transaction after the failure/error is - // fixed + // If starting a transaction fails, it indicates some failure has happened during the transaction, + // so you should cancel the transaction or retry the transaction after fixing the failure/error. return; } try { - // Execute CRUD operations in the transaction + // Execute CRUD operations in the transaction. Optional result = tx.get(...); List results = tx.scan(...); tx.put(...); tx.delete(...); - // Commit the transaction + // Commit the transaction. tx.commit(); } catch (CrudConflictException | CommitConflictException e) { - // If you catch CrudConflictException or CommitConflictException, it indicates conflicts - // happen during a transaction so that you can retry the transaction + // If you catch `CrudConflictException` or `CommitConflictException`, it indicates conflicts + // happened during a transaction, so you should retry the transaction. try { tx.abort(); } catch (AbortException ex) { - // Aborting the transaction fails. You can log it here + // Aborting the transaction fails. You can log it here. } retryCount++; } catch (CrudException | CommitException e) { - // If you catch CrudException or CommitException, it indicates some failure happens, so you - // should cancel the transaction or retry the transaction after the failure/error is fixed + // If you catch `CrudException` or `CommitException`, it indicates some failure has happened, so you + // should cancel the transaction or retry the transaction after fixing the failure/error. try { tx.abort(); } catch (AbortException ex) { - // Aborting the transaction fails. You can log it here + // Aborting the transaction fails. You can log it here. } return; } catch (UnknownTransactionStatusException e) { - // If you catch `UnknownTransactionException`, you are not sure if the transaction succeeds - // or not. In such a case, you need to check if the transaction is committed successfully - // or not and retry it if it failed. How to identify a transaction status is delegated to - // users + // If you catch `UnknownTransactionStatusException` when committing the transaction, you are + // not sure if the transaction succeeds or not. In such a case, you need to check if the + // transaction is committed successfully or not and retry it if it failed. How to identify a + // transaction status is delegated to users. return; } } @@ -701,35 +765,30 @@ public class Sample { } ``` -The APIs for CRUD operations (`get()`/`scan()`/`put()`/`delete()`/`mutate()`) could throw `CrudException` and `CrudConflictException`. -If you catch `CrudException`, it indicates some failure (e.g., database failure and network error) happens during a transaction, so you should cancel the transaction or retry the transaction after the failure/error is fixed. -If you catch `CrudConflictException`, it indicates conflicts happen during a transaction so that you can retry the transaction, preferably with well-adjusted exponential backoff based on your application and environment. -The sample code retries three times maximum and sleeps 100 milliseconds before retrying the transaction. +### `CrudException` and `CrudConflictException` + +The APIs for CRUD operations (`get()`, `scan()`, `put()`, `delete()`, and `mutate()`) could throw `CrudException` or `CrudConflictException`: -Also, the `commit()` API could throw `CommitException`, `CommitConflictException`, and `UnknownTransactionStatusException`. -If you catch `CommitException`, like the `CrudException` case, you should cancel the transaction or retry the transaction after the failure/error is fixed. -If you catch `CommitConflictException`, like the `CrudConflictException` case, you can retry the transaction. -If you catch `UnknownTransactionStatusException`, you are not sure if the transaction succeeds or not. -In such a case, you need to check if the transaction is committed successfully or not and retry it if it fails. -How to identify a transaction status is delegated to users. -You may want to create a transaction status table and update it transactionally with other application data so that you can get the status of a transaction from the status table. +- If you catch `CrudException`, this exception indicates that the transaction CRUD operation has failed due to transient or non-transient faults. You can try retrying the transaction from the beginning, but the transaction may still fail if the cause is non-transient. +- If you catch `CrudConflictException`, this exception indicates that the transaction CRUD operation has failed due to transient faults (for example, a conflict error). In this case, you can retry the transaction from the beginning. -### For Two-phase Commit Transactions +### `CommitException`, `CommitConflictException`, and `UnknownTransactionStatusException` -You need to handle more exceptions when you use [Two-phase Commit Transactions](two-phase-commit-transactions.md) because you additionally need to call the `prepare()` API (and the `validate()` API when required). +The `commit()` API could throw `CommitException`, `CommitConflictException`, or `UnknownTransactionStatusException`: -The `prepare()` API could throw `PreparationException` and `PreparationConflictException`. -If you catch `PreparationException`, like the `CrudException` case, you should cancel the transaction or retry the transaction after the failure/error is fixed. -If you catch `PreparationConflictException`, like the `CrudConflictException` case, you can retry the transaction. +- If you catch `CommitException`, this exception indicates that committing the transaction fails due to transient or non-transient faults. You can try retrying the transaction from the beginning, but the transaction may still fail if the cause is non-transient. +- If you catch `CommitConflictException`, this exception indicates that committing the transaction has failed due to transient faults (for example, a conflict error). In this case, you can retry the transaction from the beginning. +- If you catch `UnknownTransactionStatusException`, this exception indicates that the status of the transaction, whether it was successful or not, is unknown. In this case, you need to check if the transaction is committed successfully and retry the transaction if it has failed. -Also, the `validate()` API could throw `ValidationException` and `ValidationConflictException`. -If you catch `ValidationException`, like the `CrudException` case, you should cancel the transaction or retry the transaction after the failure/error is fixed. -If you catch `ValidationConflictException`, like the `CrudConflictException` case, you can retry the transaction. +How to identify a transaction status is delegated to users. You may want to create a transaction status table and update it transactionally with other application data so that you can get the status of a transaction from the status table. -## References +## Investigating Consensus Commit transaction manager errors -* [Design document](design.md) -* [Getting started](getting-started-with-scalardb.md) -* [Multi-storage Transactions](multi-storage-transactions.md) -* [Two-phase Commit Transactions](two-phase-commit-transactions.md) -* [Scalar DB server](scalardb-server.md) +To investigate errors when using the Consensus Commit transaction manager, you can enable a configuration that will return table metadata augmented with transaction metadata columns, which can be helpful when investigating transaction-related issues. This configuration, which is only available when troubleshooting the Consensus Commit transaction manager, enables you to see transaction metadata column details for a given table by using the `DistributedTransactionAdmin.getTableMetadata()` method. + +By adding the following configuration, `Get` and `Scan` operations results will contain [transaction metadata](schema-loader.md#internal-metadata-for-consensus-commit): + +```properties +# By default, this configuration is set to `false`. +scalar.db.consensus_commit.include_metadata.enabled=true +``` diff --git a/docs/3.7/api-guide.md b/docs/3.7/api-guide.md index 335a4fc..c192613 100644 --- a/docs/3.7/api-guide.md +++ b/docs/3.7/api-guide.md @@ -1,29 +1,33 @@ -# Java API Guide +# ScalarDB Java API Guide -Scalar DB Java API is mainly composed of Administrative API and Transactional API. -This guide briefly explains what kind of APIs exist and how to use them. - -* [Administrative API](#administrative-api) -* [Transactional API](#transactional-api) +The ScalarDB Java API is mainly composed of the Administrative API and Transactional API. This guide briefly explains what kinds of APIs exist, how to use them, and related topics like how to handle exceptions. ## Administrative API -This section explains how to execute administrative operations with Administrative API in Scalar DB. -You can execute administrative operations programmatically as follows, but you can also execute those operations through [Schema Loader](https://github.com/scalar-labs/scalardb/tree/master/schema-loader/README.md). +This section explains how to execute administrative operations programmatically by using the Administrative API in ScalarDB. + +{% capture notice--info %} +**Note** + +Another method for executing administrative operations is to use [Schema Loader](schema-loader.md). +{% endcapture %} + +
{{ notice--info | markdownify }}
+ +### Get a `DistributedTransactionAdmin` instance -### Get a DistributedTransactionAdmin instance +You first need to get a `DistributedTransactionAdmin` instance to execute administrative operations. -To execute administrative operations, you first need to get a `DistributedTransactionAdmin` instance. -The `DistributedTransactionAdmin` instance can be obtained from `TransactionFactory` as follows: +To get a `DistributedTransactionAdmin` instance, you can use `TransactionFactory` as follows: ```java -TransactionFactory transactionFactory = TransactionFactory.create(""); +TransactionFactory transactionFactory = TransactionFactory.create(""); DistributedTransactionAdmin admin = transactionFactory.getTransactionAdmin(); ``` -Please see [Getting Started](getting-started.md) for the details of the configuration file. +For details about configurations, see [ScalarDB Configurations](configurations.md). -Once you have executed all administrative operations, you should close the `DistributedTransactionAdmin` instance as follows: +After you have executed all administrative operations, you should close the `DistributedTransactionAdmin` instance as follows: ```java admin.close(); @@ -32,60 +36,77 @@ admin.close(); ### Create a namespace Before creating tables, namespaces must be created since a table belongs to one namespace. + You can create a namespace as follows: ```java -// Create a namespace "ns". It will throw an exception if the namespace already exists +// Create the namespace "ns". If the namespace already exists, an exception will be thrown. admin.createNamespace("ns"); -// Create a namespace only if it does not already exist +// Create the namespace only if it does not already exist. boolean ifNotExists = true; admin.createNamespace("ns", ifNotExists); -// Create a namespace with options +// Create the namespace with options. Map options = ...; admin.createNamespace("ns", options); ``` -#### Creation Options +#### Creation options + +In the creation operations, like creating a namespace and creating a table, you can specify options that are maps of option names and values (`Map`). By using the options, you can set storage adapter–specific configurations. + +Select your database to see the options available: -In the creation operations (creating a namespace, creating a table, etc.), you can specify options that are maps of option names and values (`Map`). -With the options, we can set storage adapter specific configurations. +
+
+ + + + +
-Currently, we can set the following options for the storage adapters: +
-For Cosmos DB: +| Name | Description | Default | +|----------------------|----------------------------------------------------------------------------------------|------------------| +| replication-strategy | Cassandra replication strategy. Must be `SimpleStrategy` or `NetworkTopologyStrategy`. | `SimpleStrategy` | +| compaction-strategy | Cassandra compaction strategy, Must be `LCS`, `STCS` or `TWCS`. | `STCS` | +| replication-factor | Cassandra replication factor. | 1 | -| name | value | default | -|------------|------------------------------------|---------| -| ru | Base resource unit | 400 | -| no-scaling | Disable auto-scaling for Cosmos DB | false | +
+
-For DynamoDB: +| Name | Description | Default | +|------------|-----------------------------------------------------|---------| +| ru | Base resource unit. | 400 | +| no-scaling | Disable auto-scaling for Cosmos DB for NoSQL. | false | -| name | value | default | -|------------|----------------------------------------|---------| -| no-scaling | Disable auto-scaling for DynamoDB | false | -| no-backup | Disable continuous backup for DynamoDB | false | -| ru | Base resource unit | 10 | +
+
-For Cassandra: +| Name | Description | Default | +|------------|-----------------------------------------|---------| +| no-scaling | Disable auto-scaling for DynamoDB. | false | +| no-backup | Disable continuous backup for DynamoDB. | false | +| ru | Base resource unit. | 10 | -| name | value | default | -|----------------------|---------------------------------------------------------------------------------------|------------------| -| replication-strategy | Cassandra replication strategy, must be `SimpleStrategy` or `NetworkTopologyStrategy` | `SimpleStrategy` | -| compaction-strategy | Cassandra compaction strategy, must be `LCS`, `STCS` or `TWCS` | `STCS` | -| replication-factor | Cassandra replication factor | 1 | +
+
+No options are available for JDBC databases. + +
+
### Create a table -Next, we will discuss table creation. +When creating a table, you should define the table metadata and then create the table. -You firstly need to create the TaleMetadata as follows: +To define the table metadata, you can use `TableMetadata`. The following shows how to define the columns, partition key, clustering key including clustering orders, and secondary indexes of a table: ```java -// Define a table metadata +// Define the table metadata. TableMetadata tableMetadata = TableMetadata.newBuilder() .addColumn("c1", DataType.INT) @@ -100,21 +121,19 @@ TableMetadata tableMetadata = .build(); ``` -Here you define columns, a partition key, a clustering key including clustering orders, and secondary indexes of a table. - -Please see [Scalar DB design document - Data Model](design.md#data-model) for the details of the Scalar DB Data Model. +For details about the data model of ScalarDB, see [Data Model](design.md#data-model). -And then, you can create a table as follows: +Then, create a table as follows: ```java -// Create a table "ns.tbl". It will throw an exception if the table already exists +// Create the table "ns.tbl". If the table already exists, an exception will be thrown. admin.createTable("ns", "tbl", tableMetadata); -// Create a table only if it does not already exist +// Create the table only if it does not already exist. boolean ifNotExists = true; admin.createTable("ns", "tbl", tableMetadata, ifNotExists); -// Create a table with options +// Create the table with options. Map options = ...; admin.createTable("ns", "tbl", tableMetadata, options); ``` @@ -124,43 +143,45 @@ admin.createTable("ns", "tbl", tableMetadata, options); You can create a secondary index as follows: ```java -// Create a secondary index on a column "c5" of a table "ns.tbl". It will throw an exception if the secondary index already exists +// Create a secondary index on column "c5" for table "ns.tbl". If a secondary index already exists, an exception will be thrown. admin.createIndex("ns", "tbl", "c5"); -// Create a secondary index only if it does not already exist +// Create the secondary index only if it does not already exist. boolean ifNotExists = true; admin.createIndex("ns", "tbl", "c5", ifNotExists); -// Create a secondary index with options +// Create the secondary index with options. Map options = ...; admin.createIndex("ns", "tbl", "c5", options); ``` ### Add a new column to a table -You can add a new non-partition key column to a table as follows: +You can add a new, non-partition key column to a table as follows: + ```java -// Add the new column "c6" of type INT to the table "ns.tbl" +// Add a new column "c6" with the INT data type to the table "ns.tbl". admin.addNewColumnToTable("ns", "tbl", "c6", DataType.INT) ``` -This should be executed with significant consideration as the execution time may vary greatly -depending on the underlying storage. Please plan accordingly especially if the database runs in production: -- For Cosmos and Dynamo DB: this operation is almost instantaneous as the table - schema is not modified. Only the table metadata stored in a separated table are updated. -- For Cassandra: adding a column will only update the schema metadata and do not modify existing - schema records. The cluster topology is the main factor for the execution time. Since the schema - metadata change propagates to each cluster node via a gossip protocol, the larger the cluster, the - longer it will take for all nodes to be updated. -- For relational databases (MySQL, Oracle, etc.): it may take a very long time to execute and a - table-lock may be performed. +{% capture notice--warning %} +**Attention** + +You should carefully consider adding a new column to a table because the execution time may vary greatly depending on the underlying storage. Please plan accordingly and consider the following, especially if the database runs in production: + +- **For Cosmos DB for NoSQL and DynamoDB:** Adding a column is almost instantaneous as the table schema is not modified. Only the table metadata stored in a separate table is updated. +- **For Cassandra:** Adding a column will only update the schema metadata and will not modify the existing schema records. The cluster topology is the main factor for the execution time. Changes to the schema metadata are shared to each cluster node via a gossip protocol. Because of this, the larger the cluster, the longer it will take for all nodes to be updated. +- **For relational databases (MySQL, Oracle, etc.):** Adding a column shouldn't take a long time to execute. +{% endcapture %} + +
{{ notice--warning | markdownify }}
### Truncate a table You can truncate a table as follows: ```java -// Truncate a table "ns.tbl" +// Truncate the table "ns.tbl". admin.truncateTable("ns", "tbl"); ``` @@ -169,10 +190,10 @@ admin.truncateTable("ns", "tbl"); You can drop a secondary index as follows: ```java -// Drop a secondary index on a column "c5" of a table "ns.tbl". It will throw an exception if the secondary index does not exist +// Drop the secondary index on column "c5" from table "ns.tbl". If the secondary index does not exist, an exception will be thrown. admin.dropIndex("ns", "tbl", "c5"); -// Drop a secondary index only if it exists +// Drop the secondary index only if it exists. boolean ifExists = true; admin.dropIndex("ns", "tbl", "c5", ifExists); ``` @@ -182,10 +203,10 @@ admin.dropIndex("ns", "tbl", "c5", ifExists); You can drop a table as follows: ```java -// Drop a table "ns.tbl". It will throw an exception if the table does not exist +// Drop the table "ns.tbl". If the table does not exist, an exception will be thrown. admin.dropTable("ns", "tbl"); -// Drop a table only if it exists +// Drop the table only if it exists. boolean ifExists = true; admin.dropTable("ns", "tbl", ifExists); ``` @@ -195,155 +216,211 @@ admin.dropTable("ns", "tbl", ifExists); You can drop a namespace as follows: ```java -// Drop a namespace "ns". It will throw an exception if the namespace does not exist +// Drop the namespace "ns". If the namespace does not exist, an exception will be thrown. admin.dropNamespace("ns"); -// Drop a namespace only if it exists +// Drop the namespace only if it exists. boolean ifExists = true; admin.dropNamespace("ns", ifExists); ``` -### Get a table metadata +### Get the tables of a namespace + +You can get the tables of a namespace as follows: + +```java +// Get the tables of the namespace "ns". +Set tables = admin.getNamespaceTableNames("ns"); +``` + +### Get table metadata -You can get a table metadata as follows: +You can get table metadata as follows: ```java -// Get a table metadata of "ns.tbl" +// Get the table metadata for "ns.tbl". TableMetadata tableMetadata = admin.getTableMetadata("ns", "tbl"); ``` -### Operations for Coordinator tables +### Specify operations for the Coordinator table -Depending on the transaction manager type, you need to create coordinator tables to execute transactions. -The following items describe the operations for the coordinator table. +The Coordinator table is used by the [Transactional API](#transactional-api) to track the statuses of transactions. -#### Create Coordinator tables +When using a transaction manager, you must create the Coordinator table to execute transactions. In addition to creating the table, you can truncate and drop the Coordinator table. -You can create coordinator tables as follows: +#### Create the Coordinator table + +You can create the Coordinator table as follows: ```java -// Create coordinator tables +// Create the Coordinator table. admin.createCoordinatorTables(); -// Create coordinator tables only if they do not already exist +// Create the Coordinator table only if one does not already exist. boolean ifNotExist = true; admin.createCoordinatorTables(ifNotExist); -// Create coordinator tables with options +// Create the Coordinator table with options. Map options = ...; admin.createCoordinatorTables(options); ``` -#### Truncate Coordinator tables +#### Truncate the Coordinator table -You can truncate coordinator tables as follows: +You can truncate the Coordinator table as follows: ```java -// Truncate coordinator tables +// Truncate the Coordinator table. admin.truncateCoordinatorTables(); ``` -#### Drop Coordinator tables +#### Drop the Coordinator table -You can drop coordinator tables as follows: +You can drop the Coordinator table as follows: ```java -// Drop coordinator tables +// Drop the Coordinator table. admin.dropCoordinatorTables(); -// Drop coordinator tables if they exist +// Drop the Coordinator table if one exist. boolean ifExist = true; admin.dropCoordinatorTables(ifExist); ``` ## Transactional API -This section explains how to execute transactional operations with Transactional API in Scalar DB. +This section explains how to execute transactional operations by using the Transactional API in ScalarDB. + +### Get a `DistributedTransactionManager` instance -### Get a DistributedTransactionManager instance +You first need to get a `DistributedTransactionManager` instance to execute transactional operations. -You need to get a `DistributedTransactionManager` instance to execute transactional operations. -You can get it in the following way: +To get a `DistributedTransactionManager` instance, you can use `TransactionFactory` as follows: ```java -TransactionFactory transactionFactory = TransactionFactory.create(""); +TransactionFactory transactionFactory = TransactionFactory.create(""); DistributedTransactionManager manager = transactionFactory.getTransactionManager(); ``` -Once you have executed all transactional operations, you should close the `DistributedTransactionManager` instance as follows: +After you have executed all transactional operations, you should close the `DistributedTransactionManager` instance as follows: ```java manager.close(); ``` -### Begin/Start a transaction +### Begin or start a transaction + +Before executing transactional CRUD operations, you need to begin or start a transaction. -You need to begin/start a transaction before executing transactional CRUD operations. -You can begin/start a transaction as follows: +You can begin a transaction as follows: ```java -// Begin a transaction +// Begin a transaction. DistributedTransaction transaction = manager.begin(); +``` -Or +Or, you can start a transaction as follows: -// Start a transaction +```java +// Start a transaction. DistributedTransaction transaction = manager.start(); ``` -You can also begin/start a transaction with specifying a transaction ID as follows: +Alternatively, you can use the `begin` method for a transaction by specifying a transaction ID as follows: + +```java +// Begin a transaction with specifying a transaction ID. +DistributedTransaction transaction = manager.begin(""); +``` + +Or, you can use the `start` method for a transaction by specifying a transaction ID as follows: ```java -// Begin a transaction with specifying a transaction ID -DistributedTransaction transaction = manager.begin(""); +// Start a transaction with specifying a transaction ID. +DistributedTransaction transaction = manager.start(""); +``` + +{% capture notice--info %} +**Note** + +Specifying a transaction ID is useful when you want to link external systems to ScalarDB. Otherwise, you should use the `begin()` method or the `start()` method. + +When you specify a transaction ID, make sure you specify a unique ID (for example, UUID v4) throughout the system since ScalarDB depends on the uniqueness of transaction IDs for correctness. +{% endcapture %} + +
{{ notice--info | markdownify }}
-Or +### Resume a transaction -// Start a transaction with specifying a transaction ID -DistributedTransaction transaction = manager.start(""); +Resuming a transaction is particularly useful in a stateful application where a transaction spans multiple client requests. In such a scenario, the application can start a transaction during the first client request. Then, in subsequent client requests, the application can resume the ongoing transaction by using the `resume()` method. + +You can resume an ongoing transaction that you have already begun by specifying a transaction ID as follows: + +```java +// Resume a transaction. +DistributedTransaction transaction = manager.resume(""); ``` -Note that you must guarantee uniqueness of the transaction ID in this case. +{% capture notice--info %} +**Note** -### CRUD operations +To get the transaction ID with `getId()`, you can specify the following: + +```java +tx.getId(); +``` +{% endcapture %} + +
{{ notice--info | markdownify }}
+ +### Implement CRUD operations + +The following sections describe key construction and CRUD operations. + +{% capture notice--info %} +**Note** + +Although all the builders of the CRUD operations can specify consistency by using the `consistency()` methods, those methods are ignored. Instead, the `LINEARIZABLE` consistency level is always used in transactions. + +In addition, although the builders of the mutation operations (`Put` and `Delete` operations) can specify a condition by using the `condition()` methods, those methods are also ignored. Instead, if you want to implement conditional mutation, please program such conditions for transactions. +{% endcapture %} + +
{{ notice--info | markdownify }}
#### Key construction -Most CRUD operations need to specify `Key` objects (partition-key, clustering-key, etc.). -So, before moving on to CRUD operations, the following explains how to construct a `Key` object. +Most CRUD operations need to specify `Key` objects (partition-key, clustering-key, etc.). So, before moving on to CRUD operations, the following explains how to construct a `Key` object. -For a single column key, you can use the `Key.ofXXX()` methods (XXX is a type name) to construct it as follows: +For a single column key, you can use `Key.of()` methods to construct the key as follows: ```java -// for a key that consists of a single column of Int +// For a key that consists of a single column of INT. Key key1 = Key.ofInt("col1", 1); -// for a key that consists of a single column of BigInt +// For a key that consists of a single column of BIGINT. Key key2 = Key.ofBigInt("col1", 100L); -// for a key that consists of a single column of Double +// For a key that consists of a single column of DOUBLE. Key key3 = Key.ofDouble("col1", 1.3d); -// for a key that consists of a single column of Text +// For a key that consists of a single column of TEXT. Key key4 = Key.ofText("col1", "value"); ``` -For a key that consists of 2 - 5 columns, you can use the `Key.of()` methods to construct it as follows: +For a key that consists of two to five columns, you can use the `Key.of()` method to construct the key as follows. Similar to `ImmutableMap.of()` in Guava, you need to specify column names and values in turns: ```java -// for a key that consists of 2 - 5 columns +// For a key that consists of two to five columns. Key key1 = Key.of("col1", 1, "col2", 100L); Key key2 = Key.of("col1", 1, "col2", 100L, "col3", 1.3d); Key key3 = Key.of("col1", 1, "col2", 100L, "col3", 1.3d, "col4", "value"); Key key4 = Key.of("col1", 1, "col2", 100L, "col3", 1.3d, "col4", "value", "col5", false); ``` -Similar to `ImmutableMap.of()` in Guava, you need to specify column names and values in turns. - -For a key that consists of more than 5 columns, we can use the builder to construct it as follows: +For a key that consists of more than five columns, we can use the builder to construct the key as follows: ```java -// for a key that consists of more than 5 columns +// For a key that consists of more than five columns. Key key = Key.newBuilder() .addInt("col1", 1) .addBigInt("col2", 100L) @@ -354,14 +431,14 @@ Key key = Key.newBuilder() .build(); ``` -#### Get operation +#### `Get` operation `Get` is an operation to retrieve a single record specified by a primary key. -You need to create a Get object first, and then you can execute it with the `transaction.get()` method as follows: +You need to create a `Get` object first, and then you can execute the object by using the `transaction.get()` method as follows: ```java -// Create a Get operation +// Create a `Get` operation. Key partitionKey = Key.ofInt("c1", 10); Key clusteringKey = Key.of("c2", "aaa", "c3", 100L); @@ -374,62 +451,61 @@ Get get = .projections("c1", "c2", "c3", "c4") .build(); -// Execute the Get operation +// Execute the `Get` operation. Optional result = transaction.get(get); ``` You can also specify projections to choose which columns are returned. -##### Handle Result objects +##### Handle `Result` objects -The Get operation and Scan operation return `Result` objects. -So the following shows how to handle `Result` objects. +The `Get` operation and `Scan` operation return `Result` objects. The following shows how to handle `Result` objects. -You can get a column value of a result with `getXXX("")` methods (XXX is a type name) as follows: +You can get a column value of a result by using `get("")` methods as follows: ```java -// Get a Boolean value of a column -boolean booleanValue = result.getBoolean(""); +// Get the BOOLEAN value of a column. +boolean booleanValue = result.getBoolean(""); -// Get an Int value of a column -int intValue = result.getInt(""); +// Get the INT value of a column. +int intValue = result.getInt(""); -// Get a BigInt value of a column -long bigIntValue = result.getBigInt(""); +// Get the BIGINT value of a column. +long bigIntValue = result.getBigInt(""); -// Get a Float value of a column -float floatValue = result.getFloat(""); +// Get the FLOAT value of a column. +float floatValue = result.getFloat(""); -// Get a Double value of a column -double doubleValue = result.getDouble(""); +// Get the DOUBLE value of a column. +double doubleValue = result.getDouble(""); -// Get a Text value of a column -String textValue = result.getText(""); +// Get the TEXT value of a column. +String textValue = result.getText(""); -// Get a Blob value of a column (as a ByteBuffer) -ByteBuffer blobValue = result.getBlob(""); +// Get the BLOB value of a column as a `ByteBuffer`. +ByteBuffer blobValue = result.getBlob(""); -// Get a Blob value of a column as a byte array -byte[] blobValueAsBytes = result.getBlobAsBytes(""); +// Get the BLOB value of a column as a `byte` array. +byte[] blobValueAsBytes = result.getBlobAsBytes(""); ``` -And if you need to check if a value of a column is null, you can use the `isNull("")` method. +And if you need to check if a value of a column is null, you can use the `isNull("")` method. ``` java -// Check if a value of a column is null -boolean isNull = result.isNull(""); +// Check if a value of a column is null. +boolean isNull = result.isNull(""); ``` -Please see also [Javadoc of `Result`](https://javadoc.io/static/com.scalar-labs/scalardb/3.6.0/com/scalar/db/api/Result.html) for more details. +For more details, see the `Result` page in the [Javadoc](https://javadoc.io/doc/com.scalar-labs/scalardb/latest/index.html) of the version of ScalarDB that you're using. -##### Get with a secondary index +##### Execute `Get` by using a secondary index -You can also execute a Get operation with a secondary index. +You can execute a `Get` operation by using a secondary index. -Instead of specifying a partition key, you can specify an index key (specifying an indexed column) to use a secondary index as follows: +Instead of specifying a partition key, you can specify an index key (indexed column) to use a secondary index as follows: ```java -// Create a Get operation with a secondary index +// Create a `Get` operation by using a secondary index. Key indexKey = Key.ofFloat("c4", 1.23F); Get get = @@ -440,22 +516,27 @@ Get get = .projections("c1", "c2", "c3", "c4") .build(); -// Execute the Get operation +// Execute the `Get` operation. Optional result = transaction.get(get); ``` -Note that if the result has more than one record, the `transaction.get()` throws an exception. -If you want to handle multiple results, use [Scan with a secondary index](#scan-with-a-secondary-index). +{% capture notice--info %} +**Note** + +If the result has more than one record, `transaction.get()` will throw an exception. If you want to handle multiple results, see [Execute `Scan` by using a secondary index](#execute-scan-by-using-a-secondary-index). + +{% endcapture %} -#### Scan operation +
{{ notice--info | markdownify }}
-`Scan` is an operation to retrieve multiple records within a partition. -You can specify clustering key boundaries and orderings for clustering key columns in Scan operations. +#### `Scan` operation -You need to create a Scan object first, and then you can execute it with the `transaction.scan()` method as follows: +`Scan` is an operation to retrieve multiple records within a partition. You can specify clustering-key boundaries and orderings for clustering-key columns in `Scan` operations. + +You need to create a `Scan` object first, and then you can execute the object by using the `transaction.scan()` method as follows: ```java -// Create a Scan operation +// Create a `Scan` operation. Key partitionKey = Key.ofInt("c1", 10); Key startClusteringKey = Key.of("c2", "aaa", "c3", 100L); Key endClusteringKey = Key.of("c2", "aaa", "c3", 300L); @@ -472,23 +553,22 @@ Scan scan = .limit(10) .build(); -// Execute the Scan operation +// Execute the `Scan` operation. List results = transaction.scan(scan); ``` -You can omit the clustering key boundaries, or you can specify either a start boundary or an end boundary. -If you don't specify orderings, you get results ordered by clustering order you defined when creating the table. +You can omit the clustering-key boundaries or specify either a `start` boundary or an `end` boundary. If you don't specify `orderings`, you will get results ordered by the clustering order that you defined when creating the table. -Also, you can specify projections to choose which columns are returned, and limit to specify the number of records to return in Scan operations. +In addition, you can specify `projections` to choose which columns are returned and use `limit` to specify the number of records to return in `Scan` operations. -##### Scan with a secondary index +##### Execute `Scan` by using a secondary index -You can also execute a Scan operation with a secondary index. +You can execute a `Scan` operation by using a secondary index. -Instead of specifying a partition key, you can specify an index key (specifying an indexed column) to use a secondary index as follows: +Instead of specifying a partition key, you can specify an index key (indexed column) to use a secondary index as follows: ```java -// Create a Scan operation with a secondary index +// Create a `Scan` operation by using a secondary index. Key indexKey = Key.ofFloat("c4", 1.23F); Scan scan = @@ -500,24 +580,26 @@ Scan scan = .limit(10) .build(); -// Execute the Scan operation +// Execute the `Scan` operation. List results = transaction.scan(scan); ``` -Note that you can't specify clustering key boundaries and orderings in Scan with a secondary index. +{% capture notice--info %} +**Note** + +You can't specify clustering-key boundaries and orderings in `Scan` by using a secondary index. +{% endcapture %} -##### Scan without a partition key to retrieve all the records of a table +
{{ notice--info | markdownify }}
-You can also execute a Scan operation without specifying a partition key. +##### Execute `Scan` without specifying a partition key to retrieve all the records of a table + +You can execute a `Scan` operation without specifying a partition key. Instead of calling the `partitionKey()` method in the builder, you can call the `all()` method to scan a table without specifying a partition key as follows: ```java -// Create a Scan operation without a partition key -Key partitionKey = Key.ofInt("c1", 10); -Key startClusteringKey = Key.of("c2", "aaa", "c3", 100L); -Key endClusteringKey = Key.of("c2", "aaa", "c3", 300L); - +// Create a `Scan` operation without specifying a partition key. Scan scan = Scan.newBuilder() .namespace("ns") @@ -527,21 +609,34 @@ Scan scan = .limit(10) .build(); -// Execute the Scan operation +// Execute the `Scan` operation. List results = transaction.scan(scan); ``` -Note that you can't specify clustering key boundaries and orderings in Scan without a partition key. +{% capture notice--info %} +**Note** + +You can't specify clustering-key boundaries and orderings in `Scan` without specifying a partition key. +{% endcapture %} + +
{{ notice--info | markdownify }}
+ +#### `Put` operation + +`Put` is an operation to put a record specified by a primary key. The operation behaves as an upsert operation for a record, in which the operation updates the record if the record exists or inserts the record if the record does not exist. -#### Put operation +{% capture notice--info %} +**Note** -`Put` is an operation to put a record specified by a primary key. -It behaves as an upsert operation for a record, i.e., updating the record if the record exists; otherwise, inserting the record. +When you update an existing record, you need to read the record by using `Get` or `Scan` before using a `Put` operation. +{% endcapture %} -You need to create a Put object first, and then you can execute it with the `transaction.put()` method as follows: +
{{ notice--info | markdownify }}
+ +You need to create a `Put` object first, and then you can execute the object by using the `transaction.put()` method as follows: ```java -// Create a Put operation +// Create a `Put` operation. Key partitionKey = Key.ofInt("c1", 10); Key clusteringKey = Key.of("c2", "aaa", "c3", 100L); @@ -555,11 +650,11 @@ Put put = .doubleValue("c5", 4.56) .build(); -// Execute the Put operation +// Execute the `Put` operation. transaction.put(put); ``` -You can also put a record with null values as follows: +You can also put a record with `null` values as follows: ```java Put put = @@ -573,14 +668,22 @@ Put put = .build(); ``` -#### Delete operation +#### `Delete` operation `Delete` is an operation to delete a record specified by a primary key. -You need to create a Delete object first, and then you can execute it with the `transaction.delete()` method as follows: +{% capture notice--info %} +**Note** + +When you delete a record, you need to read the record by using `Get` or `Scan` before using a `Delete` operation. +{% endcapture %} + +
{{ notice--info | markdownify }}
+ +You need to create a `Delete` object first, and then you can execute the object by using the `transaction.delete()` method as follows: ```java -// Create a Delete operation +// Create a `Delete` operation. Key partitionKey = Key.ofInt("c1", 10); Key clusteringKey = Key.of("c2", "aaa", "c3", 100L); @@ -592,18 +695,18 @@ Delete delete = .clusteringKey(clusteringKey) .build(); -// Execute the Delete operation +// Execute the `Delete` operation. transaction.delete(delete); ``` #### Mutate operation -Mutate is an operation to execute multiple mutations (Put and Delete operations). +Mutate is an operation to execute multiple mutations (`Put` and `Delete` operations). -You need to create mutation objects first, and then you can execute them with the `transaction.mutate()` method as follows: +You need to create mutation objects first, and then you can execute the objects by using the `transaction.mutate()` method as follows: ```java -// Create Put and Delete operations +// Create `Put` and `Delete` operations. Key partitionKey = Key.ofInt("c1", 10); Key clusteringKeyForPut = Key.of("c2", "aaa", "c3", 100L); @@ -628,72 +731,70 @@ Delete delete = .clusteringKey(clusteringKeyForDelete) .build(); -// Execute the operations +// Execute the operations. transaction.mutate(Arrays.asList(put, delete)); ``` -#### Notes - -- All the builders of the CRUD operations can specify consistency with the `consistency()` methods, but it's ignored, and the `LINEARIZABLE` consistency level is always used in transactions. -- Also, the builders of the mutation operations (Put and Delete operations) can specify a condition with the `condition()` methods, but it's ignored, too. -Please program such conditions in a transaction if you want to implement conditional mutation. - ### Commit a transaction After executing CRUD operations, you need to commit a transaction to finish it. -You can commit a transaction as follows; +You can commit a transaction as follows: ```java -// Commit a transaction +// Commit a transaction. transaction.commit(); ``` -### Rollback/Abort a transaction +### Roll back or abort a transaction -If you want to rollback/abort a transaction or an error happens during the execution, you can rollback/abort a transaction. +If an error occurs when executing a transaction, you can roll back or abort the transaction. -You can rollback/abort a transaction as follows; +You can roll back a transaction as follows: ```java -// Rollback a transaction +// Roll back a transaction. transaction.rollback(); +``` -Or +Or, you can abort a transaction as follows: -// Abort a transaction +```java +// Abort a transaction. transaction.abort(); ``` -Please see [Handle Exceptions](#handle-exceptions) for the details of how to handle exceptions in Scalar DB. +For details about how to handle exceptions in ScalarDB, see [How to handle exceptions](#how-to-handle-exceptions). + +## How to handle exceptions -## Transactional operations for Two-phase Commit Transaction +When executing a transaction, you will also need to handle exceptions properly. -Please see [Two-phase Commit Transactions](two-phase-commit-transactions.md). +{% capture notice--warning %} +**Attention** -## Handle Exceptions +If you don't handle exceptions properly, you may face anomalies or data inconsistency. +{% endcapture %} -Handling exceptions correctly in Scalar DB is very important. -If you mishandle exceptions, your data could become inconsistent. -This document explains how to handle exceptions properly in Scalar DB. +
{{ notice--warning | markdownify }}
-Let's look at the following example code to see how to handle exceptions in Scalar DB. +The following sample code shows how to handle exceptions: ```java public class Sample { public static void main(String[] args) throws IOException, InterruptedException { - TransactionFactory factory = TransactionFactory.create(""); + TransactionFactory factory = TransactionFactory.create(""); DistributedTransactionManager manager = factory.getTransactionManager(); int retryCount = 0; while (true) { if (retryCount > 0) { - // Retry the transaction three times maximum in this sample code + // Retry the transaction three times maximum in this sample code. if (retryCount == 3) { return; } - // Sleep 100 milliseconds before retrying the transaction in this sample code + // Sleep 100 milliseconds before retrying the transaction in this sample code. TimeUnit.MILLISECONDS.sleep(100); } @@ -702,44 +803,43 @@ public class Sample { try { tx = manager.start(); } catch (TransactionException e) { - // If starting a transaction fails, it indicates some failure happens during a transaction, - // so you should cancel the transaction or retry the transaction after the failure/error is - // fixed + // If starting a transaction fails, it indicates some failure has happened during the transaction, + // so you should cancel the transaction or retry the transaction after fixing the failure/error. return; } try { - // Execute CRUD operations in the transaction + // Execute CRUD operations in the transaction. Optional result = tx.get(...); List results = tx.scan(...); tx.put(...); tx.delete(...); - // Commit the transaction + // Commit the transaction. tx.commit(); } catch (CrudConflictException | CommitConflictException e) { - // If you catch CrudConflictException or CommitConflictException, it indicates conflicts - // happen during a transaction so that you can retry the transaction + // If you catch `CrudConflictException` or `CommitConflictException`, it indicates conflicts + // happened during a transaction, so you should retry the transaction. try { tx.abort(); } catch (AbortException ex) { - // Aborting the transaction fails. You can log it here + // Aborting the transaction fails. You can log it here. } retryCount++; } catch (CrudException | CommitException e) { - // If you catch CrudException or CommitException, it indicates some failure happens, so you - // should cancel the transaction or retry the transaction after the failure/error is fixed + // If you catch `CrudException` or `CommitException`, it indicates some failure has happened, so you + // should cancel the transaction or retry the transaction after fixing the failure/error. try { tx.abort(); } catch (AbortException ex) { - // Aborting the transaction fails. You can log it here + // Aborting the transaction fails. You can log it here. } return; } catch (UnknownTransactionStatusException e) { // If you catch `UnknownTransactionStatusException` when committing the transaction, you are // not sure if the transaction succeeds or not. In such a case, you need to check if the // transaction is committed successfully or not and retry it if it failed. How to identify a - // transaction status is delegated to users + // transaction status is delegated to users. return; } } @@ -747,46 +847,30 @@ public class Sample { } ``` -The APIs for CRUD operations (`get()`/`scan()`/`put()`/`delete()`/`mutate()`) could throw `CrudException` and `CrudConflictException`. -If you catch `CrudException`, it indicates some failure (e.g., database failure and network error) happens during a transaction, so you should cancel the transaction or retry the transaction after the failure/error is fixed. -If you catch `CrudConflictException`, it indicates conflicts happen during a transaction so that you can retry the transaction, preferably with well-adjusted exponential backoff based on your application and environment. -The sample code retries three times maximum and sleeps 100 milliseconds before retrying the transaction. +### `CrudException` and `CrudConflictException` -Also, the `commit()` API could throw `CommitException`, `CommitConflictException`, and `UnknownTransactionStatusException`. -If you catch `CommitException`, like the `CrudException` case, you should cancel the transaction or retry the transaction after the failure/error is fixed. -If you catch `CommitConflictException`, like the `CrudConflictException` case, you can retry the transaction. -If you catch `UnknownTransactionStatusException`, you are not sure if the transaction succeeds or not. -In such a case, you need to check if the transaction is committed successfully or not and retry it if it fails. -How to identify a transaction status is delegated to users. -You may want to create a transaction status table and update it transactionally with other application data so that you can get the status of a transaction from the status table. +The APIs for CRUD operations (`get()`, `scan()`, `put()`, `delete()`, and `mutate()`) could throw `CrudException` or `CrudConflictException`: -### For Two-phase Commit Transactions +- If you catch `CrudException`, this exception indicates that the transaction CRUD operation has failed due to transient or non-transient faults. You can try retrying the transaction from the beginning, but the transaction may still fail if the cause is non-transient. +- If you catch `CrudConflictException`, this exception indicates that the transaction CRUD operation has failed due to transient faults (for example, a conflict error). In this case, you can retry the transaction from the beginning. -You need to handle more exceptions when you use [Two-phase Commit Transactions](two-phase-commit-transactions.md) because you additionally need to call the `prepare()` API (and the `validate()` API when required). +### `CommitException`, `CommitConflictException`, and `UnknownTransactionStatusException` -The `prepare()` API could throw `PreparationException` and `PreparationConflictException`. -If you catch `PreparationException`, like the `CrudException` case, you should cancel the transaction or retry the transaction after the failure/error is fixed. -If you catch `PreparationConflictException`, like the `CrudConflictException` case, you can retry the transaction. +The `commit()` API could throw `CommitException`, `CommitConflictException`, or `UnknownTransactionStatusException`: -Also, the `validate()` API could throw `ValidationException` and `ValidationConflictException`. -If you catch `ValidationException`, like the `CrudException` case, you should cancel the transaction or retry the transaction after the failure/error is fixed. -If you catch `ValidationConflictException`, like the `CrudConflictException` case, you can retry the transaction. +- If you catch `CommitException`, this exception indicates that committing the transaction fails due to transient or non-transient faults. You can try retrying the transaction from the beginning, but the transaction may still fail if the cause is non-transient. +- If you catch `CommitConflictException`, this exception indicates that committing the transaction has failed due to transient faults (for example, a conflict error). In this case, you can retry the transaction from the beginning. +- If you catch `UnknownTransactionStatusException`, this exception indicates that the status of the transaction, whether it was successful or not, is unknown. In this case, you need to check if the transaction is committed successfully and retry the transaction if it has failed. -## Investigate Consensus Commit transactions errors +How to identify a transaction status is delegated to users. You may want to create a transaction status table and update it transactionally with other application data so that you can get the status of a transaction from the status table. -This configuration is only available to troubleshoot Consensus Commit transactions. By adding the following configuration, `Get` and `Scan` operations results will contain [transaction metadata](https://github.com/scalar-labs/scalardb/blob/master/schema-loader/README.md#internal-metadata-for-consensus-commit). -To see the transaction metadata columns details for a given table, you can use the `DistributedTransactionAdmin.getTableMetadata()` method which will return the table metadata augmented with the transaction metadata columns. -All in all, using this configuration can be useful to investigate transaction related issues. +## Investigating Consensus Commit transaction manager errors + +To investigate errors when using the Consensus Commit transaction manager, you can enable a configuration that will return table metadata augmented with transaction metadata columns, which can be helpful when investigating transaction-related issues. This configuration, which is only available when troubleshooting the Consensus Commit transaction manager, enables you to see transaction metadata column details for a given table by using the `DistributedTransactionAdmin.getTableMetadata()` method. + +By adding the following configuration, `Get` and `Scan` operations results will contain [transaction metadata](schema-loader.md#internal-metadata-for-consensus-commit): ```properties -# By default, it is set to "false". +# By default, this configuration is set to `false`. scalar.db.consensus_commit.include_metadata.enabled=true ``` - -## References - -* [Design document](design.md) -* [Getting started](getting-started-with-scalardb.md) -* [Multi-storage Transactions](multi-storage-transactions.md) -* [Two-phase Commit Transactions](two-phase-commit-transactions.md) -* [Scalar DB Server](scalardb-server.md) diff --git a/docs/3.8/api-guide.md b/docs/3.8/api-guide.md index 6b3bd2c..c192613 100644 --- a/docs/3.8/api-guide.md +++ b/docs/3.8/api-guide.md @@ -1,29 +1,33 @@ -# Java API Guide +# ScalarDB Java API Guide -ScalarDB Java API is mainly composed of Administrative API and Transactional API. -This guide briefly explains what kind of APIs exist and how to use them. - -* [Administrative API](#administrative-api) -* [Transactional API](#transactional-api) +The ScalarDB Java API is mainly composed of the Administrative API and Transactional API. This guide briefly explains what kinds of APIs exist, how to use them, and related topics like how to handle exceptions. ## Administrative API -This section explains how to execute administrative operations with Administrative API in ScalarDB. -You can execute administrative operations programmatically as follows, but you can also execute those operations through [Schema Loader](schema-loader.md). +This section explains how to execute administrative operations programmatically by using the Administrative API in ScalarDB. + +{% capture notice--info %} +**Note** + +Another method for executing administrative operations is to use [Schema Loader](schema-loader.md). +{% endcapture %} + +
{{ notice--info | markdownify }}
-### Get a DistributedTransactionAdmin instance +### Get a `DistributedTransactionAdmin` instance -To execute administrative operations, you first need to get a `DistributedTransactionAdmin` instance. -The `DistributedTransactionAdmin` instance can be obtained from `TransactionFactory` as follows: +You first need to get a `DistributedTransactionAdmin` instance to execute administrative operations. + +To get a `DistributedTransactionAdmin` instance, you can use `TransactionFactory` as follows: ```java -TransactionFactory transactionFactory = TransactionFactory.create(""); +TransactionFactory transactionFactory = TransactionFactory.create(""); DistributedTransactionAdmin admin = transactionFactory.getTransactionAdmin(); ``` -Please see [Getting Started](getting-started.md) for the details of the configuration file. +For details about configurations, see [ScalarDB Configurations](configurations.md). -Once you have executed all administrative operations, you should close the `DistributedTransactionAdmin` instance as follows: +After you have executed all administrative operations, you should close the `DistributedTransactionAdmin` instance as follows: ```java admin.close(); @@ -32,60 +36,77 @@ admin.close(); ### Create a namespace Before creating tables, namespaces must be created since a table belongs to one namespace. + You can create a namespace as follows: ```java -// Create a namespace "ns". It will throw an exception if the namespace already exists +// Create the namespace "ns". If the namespace already exists, an exception will be thrown. admin.createNamespace("ns"); -// Create a namespace only if it does not already exist +// Create the namespace only if it does not already exist. boolean ifNotExists = true; admin.createNamespace("ns", ifNotExists); -// Create a namespace with options +// Create the namespace with options. Map options = ...; admin.createNamespace("ns", options); ``` -#### Creation Options +#### Creation options + +In the creation operations, like creating a namespace and creating a table, you can specify options that are maps of option names and values (`Map`). By using the options, you can set storage adapter–specific configurations. + +Select your database to see the options available: -In the creation operations (creating a namespace, creating a table, etc.), you can specify options that are maps of option names and values (`Map`). -With the options, we can set storage adapter specific configurations. +
+
+ + + + +
-Currently, we can set the following options for the storage adapters: +
-For Cosmos DB: +| Name | Description | Default | +|----------------------|----------------------------------------------------------------------------------------|------------------| +| replication-strategy | Cassandra replication strategy. Must be `SimpleStrategy` or `NetworkTopologyStrategy`. | `SimpleStrategy` | +| compaction-strategy | Cassandra compaction strategy, Must be `LCS`, `STCS` or `TWCS`. | `STCS` | +| replication-factor | Cassandra replication factor. | 1 | -| name | value | default | -|------------|------------------------------------|---------| -| ru | Base resource unit | 400 | -| no-scaling | Disable auto-scaling for Cosmos DB | false | +
+
-For DynamoDB: +| Name | Description | Default | +|------------|-----------------------------------------------------|---------| +| ru | Base resource unit. | 400 | +| no-scaling | Disable auto-scaling for Cosmos DB for NoSQL. | false | -| name | value | default | -|------------|----------------------------------------|---------| -| no-scaling | Disable auto-scaling for DynamoDB | false | -| no-backup | Disable continuous backup for DynamoDB | false | -| ru | Base resource unit | 10 | +
+
-For Cassandra: +| Name | Description | Default | +|------------|-----------------------------------------|---------| +| no-scaling | Disable auto-scaling for DynamoDB. | false | +| no-backup | Disable continuous backup for DynamoDB. | false | +| ru | Base resource unit. | 10 | -| name | value | default | -|----------------------|---------------------------------------------------------------------------------------|------------------| -| replication-strategy | Cassandra replication strategy, must be `SimpleStrategy` or `NetworkTopologyStrategy` | `SimpleStrategy` | -| compaction-strategy | Cassandra compaction strategy, must be `LCS`, `STCS` or `TWCS` | `STCS` | -| replication-factor | Cassandra replication factor | 1 | +
+
+No options are available for JDBC databases. + +
+
### Create a table -Next, we will discuss table creation. +When creating a table, you should define the table metadata and then create the table. -You firstly need to create the TaleMetadata as follows: +To define the table metadata, you can use `TableMetadata`. The following shows how to define the columns, partition key, clustering key including clustering orders, and secondary indexes of a table: ```java -// Define a table metadata +// Define the table metadata. TableMetadata tableMetadata = TableMetadata.newBuilder() .addColumn("c1", DataType.INT) @@ -100,21 +121,19 @@ TableMetadata tableMetadata = .build(); ``` -Here you define columns, a partition key, a clustering key including clustering orders, and secondary indexes of a table. - -Please see [ScalarDB design document - Data Model](design.md#data-model) for the details of the ScalarDB Data Model. +For details about the data model of ScalarDB, see [Data Model](design.md#data-model). -And then, you can create a table as follows: +Then, create a table as follows: ```java -// Create a table "ns.tbl". It will throw an exception if the table already exists +// Create the table "ns.tbl". If the table already exists, an exception will be thrown. admin.createTable("ns", "tbl", tableMetadata); -// Create a table only if it does not already exist +// Create the table only if it does not already exist. boolean ifNotExists = true; admin.createTable("ns", "tbl", tableMetadata, ifNotExists); -// Create a table with options +// Create the table with options. Map options = ...; admin.createTable("ns", "tbl", tableMetadata, options); ``` @@ -124,43 +143,45 @@ admin.createTable("ns", "tbl", tableMetadata, options); You can create a secondary index as follows: ```java -// Create a secondary index on a column "c5" of a table "ns.tbl". It will throw an exception if the secondary index already exists +// Create a secondary index on column "c5" for table "ns.tbl". If a secondary index already exists, an exception will be thrown. admin.createIndex("ns", "tbl", "c5"); -// Create a secondary index only if it does not already exist +// Create the secondary index only if it does not already exist. boolean ifNotExists = true; admin.createIndex("ns", "tbl", "c5", ifNotExists); -// Create a secondary index with options +// Create the secondary index with options. Map options = ...; admin.createIndex("ns", "tbl", "c5", options); ``` ### Add a new column to a table -You can add a new non-partition key column to a table as follows: +You can add a new, non-partition key column to a table as follows: + ```java -// Add the new column "c6" of type INT to the table "ns.tbl" +// Add a new column "c6" with the INT data type to the table "ns.tbl". admin.addNewColumnToTable("ns", "tbl", "c6", DataType.INT) ``` -This should be executed with significant consideration as the execution time may vary greatly -depending on the underlying storage. Please plan accordingly especially if the database runs in production: -- For Cosmos and Dynamo DB: this operation is almost instantaneous as the table - schema is not modified. Only the table metadata stored in a separated table are updated. -- For Cassandra: adding a column will only update the schema metadata and do not modify existing - schema records. The cluster topology is the main factor for the execution time. Since the schema - metadata change propagates to each cluster node via a gossip protocol, the larger the cluster, the - longer it will take for all nodes to be updated. -- For relational databases (MySQL, Oracle, etc.): it may take a very long time to execute and a - table-lock may be performed. +{% capture notice--warning %} +**Attention** + +You should carefully consider adding a new column to a table because the execution time may vary greatly depending on the underlying storage. Please plan accordingly and consider the following, especially if the database runs in production: + +- **For Cosmos DB for NoSQL and DynamoDB:** Adding a column is almost instantaneous as the table schema is not modified. Only the table metadata stored in a separate table is updated. +- **For Cassandra:** Adding a column will only update the schema metadata and will not modify the existing schema records. The cluster topology is the main factor for the execution time. Changes to the schema metadata are shared to each cluster node via a gossip protocol. Because of this, the larger the cluster, the longer it will take for all nodes to be updated. +- **For relational databases (MySQL, Oracle, etc.):** Adding a column shouldn't take a long time to execute. +{% endcapture %} + +
{{ notice--warning | markdownify }}
### Truncate a table You can truncate a table as follows: ```java -// Truncate a table "ns.tbl" +// Truncate the table "ns.tbl". admin.truncateTable("ns", "tbl"); ``` @@ -169,10 +190,10 @@ admin.truncateTable("ns", "tbl"); You can drop a secondary index as follows: ```java -// Drop a secondary index on a column "c5" of a table "ns.tbl". It will throw an exception if the secondary index does not exist +// Drop the secondary index on column "c5" from table "ns.tbl". If the secondary index does not exist, an exception will be thrown. admin.dropIndex("ns", "tbl", "c5"); -// Drop a secondary index only if it exists +// Drop the secondary index only if it exists. boolean ifExists = true; admin.dropIndex("ns", "tbl", "c5", ifExists); ``` @@ -182,10 +203,10 @@ admin.dropIndex("ns", "tbl", "c5", ifExists); You can drop a table as follows: ```java -// Drop a table "ns.tbl". It will throw an exception if the table does not exist +// Drop the table "ns.tbl". If the table does not exist, an exception will be thrown. admin.dropTable("ns", "tbl"); -// Drop a table only if it exists +// Drop the table only if it exists. boolean ifExists = true; admin.dropTable("ns", "tbl", ifExists); ``` @@ -195,168 +216,211 @@ admin.dropTable("ns", "tbl", ifExists); You can drop a namespace as follows: ```java -// Drop a namespace "ns". It will throw an exception if the namespace does not exist +// Drop the namespace "ns". If the namespace does not exist, an exception will be thrown. admin.dropNamespace("ns"); -// Drop a namespace only if it exists +// Drop the namespace only if it exists. boolean ifExists = true; admin.dropNamespace("ns", ifExists); ``` -### Get a table metadata +### Get the tables of a namespace + +You can get the tables of a namespace as follows: + +```java +// Get the tables of the namespace "ns". +Set tables = admin.getNamespaceTableNames("ns"); +``` + +### Get table metadata -You can get a table metadata as follows: +You can get table metadata as follows: ```java -// Get a table metadata of "ns.tbl" +// Get the table metadata for "ns.tbl". TableMetadata tableMetadata = admin.getTableMetadata("ns", "tbl"); ``` -### Operations for Coordinator tables +### Specify operations for the Coordinator table + +The Coordinator table is used by the [Transactional API](#transactional-api) to track the statuses of transactions. -Depending on the transaction manager type, you need to create coordinator tables to execute transactions. -The following items describe the operations for the coordinator table. +When using a transaction manager, you must create the Coordinator table to execute transactions. In addition to creating the table, you can truncate and drop the Coordinator table. -#### Create Coordinator tables +#### Create the Coordinator table -You can create coordinator tables as follows: +You can create the Coordinator table as follows: ```java -// Create coordinator tables +// Create the Coordinator table. admin.createCoordinatorTables(); -// Create coordinator tables only if they do not already exist +// Create the Coordinator table only if one does not already exist. boolean ifNotExist = true; admin.createCoordinatorTables(ifNotExist); -// Create coordinator tables with options +// Create the Coordinator table with options. Map options = ...; admin.createCoordinatorTables(options); ``` -#### Truncate Coordinator tables +#### Truncate the Coordinator table -You can truncate coordinator tables as follows: +You can truncate the Coordinator table as follows: ```java -// Truncate coordinator tables +// Truncate the Coordinator table. admin.truncateCoordinatorTables(); ``` -#### Drop Coordinator tables +#### Drop the Coordinator table -You can drop coordinator tables as follows: +You can drop the Coordinator table as follows: ```java -// Drop coordinator tables +// Drop the Coordinator table. admin.dropCoordinatorTables(); -// Drop coordinator tables if they exist +// Drop the Coordinator table if one exist. boolean ifExist = true; admin.dropCoordinatorTables(ifExist); ``` ## Transactional API -This section explains how to execute transactional operations with Transactional API in ScalarDB. +This section explains how to execute transactional operations by using the Transactional API in ScalarDB. -### Get a DistributedTransactionManager instance +### Get a `DistributedTransactionManager` instance -You need to get a `DistributedTransactionManager` instance to execute transactional operations. -You can get it in the following way: +You first need to get a `DistributedTransactionManager` instance to execute transactional operations. + +To get a `DistributedTransactionManager` instance, you can use `TransactionFactory` as follows: ```java -TransactionFactory transactionFactory = TransactionFactory.create(""); +TransactionFactory transactionFactory = TransactionFactory.create(""); DistributedTransactionManager manager = transactionFactory.getTransactionManager(); ``` -Once you have executed all transactional operations, you should close the `DistributedTransactionManager` instance as follows: +After you have executed all transactional operations, you should close the `DistributedTransactionManager` instance as follows: ```java manager.close(); ``` -### Begin/Start a transaction +### Begin or start a transaction + +Before executing transactional CRUD operations, you need to begin or start a transaction. -You need to begin/start a transaction before executing transactional CRUD operations. -You can begin/start a transaction as follows: +You can begin a transaction as follows: ```java -// Begin a transaction +// Begin a transaction. DistributedTransaction transaction = manager.begin(); +``` -Or +Or, you can start a transaction as follows: -// Start a transaction +```java +// Start a transaction. DistributedTransaction transaction = manager.start(); ``` -You can also begin/start a transaction with specifying a transaction ID as follows: +Alternatively, you can use the `begin` method for a transaction by specifying a transaction ID as follows: ```java -// Begin a transaction with specifying a transaction ID -DistributedTransaction transaction = manager.begin(""); +// Begin a transaction with specifying a transaction ID. +DistributedTransaction transaction = manager.begin(""); +``` -Or +Or, you can use the `start` method for a transaction by specifying a transaction ID as follows: -// Start a transaction with specifying a transaction ID -DistributedTransaction transaction = manager.start(""); +```java +// Start a transaction with specifying a transaction ID. +DistributedTransaction transaction = manager.start(""); ``` -Note that you must guarantee uniqueness of the transaction ID in this case. +{% capture notice--info %} +**Note** + +Specifying a transaction ID is useful when you want to link external systems to ScalarDB. Otherwise, you should use the `begin()` method or the `start()` method. + +When you specify a transaction ID, make sure you specify a unique ID (for example, UUID v4) throughout the system since ScalarDB depends on the uniqueness of transaction IDs for correctness. +{% endcapture %} + +
{{ notice--info | markdownify }}
### Resume a transaction -You can resume a transaction you have already begun with specifying a transaction ID as follows: +Resuming a transaction is particularly useful in a stateful application where a transaction spans multiple client requests. In such a scenario, the application can start a transaction during the first client request. Then, in subsequent client requests, the application can resume the ongoing transaction by using the `resume()` method. + +You can resume an ongoing transaction that you have already begun by specifying a transaction ID as follows: ```java -// Resume a transaction -DistributedTransaction transaction = manager.resume(""); +// Resume a transaction. +DistributedTransaction transaction = manager.resume(""); ``` -It is helpful in a stateful application where a transaction spans multiple client requests. -In that case, the application can begin a transaction in the first client request. -And in the following client requests, it can resume the transaction with the `resume()` method. +{% capture notice--info %} +**Note** -### CRUD operations +To get the transaction ID with `getId()`, you can specify the following: + +```java +tx.getId(); +``` +{% endcapture %} + +
{{ notice--info | markdownify }}
+ +### Implement CRUD operations + +The following sections describe key construction and CRUD operations. + +{% capture notice--info %} +**Note** + +Although all the builders of the CRUD operations can specify consistency by using the `consistency()` methods, those methods are ignored. Instead, the `LINEARIZABLE` consistency level is always used in transactions. + +In addition, although the builders of the mutation operations (`Put` and `Delete` operations) can specify a condition by using the `condition()` methods, those methods are also ignored. Instead, if you want to implement conditional mutation, please program such conditions for transactions. +{% endcapture %} + +
{{ notice--info | markdownify }}
#### Key construction -Most CRUD operations need to specify `Key` objects (partition-key, clustering-key, etc.). -So, before moving on to CRUD operations, the following explains how to construct a `Key` object. +Most CRUD operations need to specify `Key` objects (partition-key, clustering-key, etc.). So, before moving on to CRUD operations, the following explains how to construct a `Key` object. -For a single column key, you can use the `Key.ofXXX()` methods (XXX is a type name) to construct it as follows: +For a single column key, you can use `Key.of()` methods to construct the key as follows: ```java -// for a key that consists of a single column of Int +// For a key that consists of a single column of INT. Key key1 = Key.ofInt("col1", 1); -// for a key that consists of a single column of BigInt +// For a key that consists of a single column of BIGINT. Key key2 = Key.ofBigInt("col1", 100L); -// for a key that consists of a single column of Double +// For a key that consists of a single column of DOUBLE. Key key3 = Key.ofDouble("col1", 1.3d); -// for a key that consists of a single column of Text +// For a key that consists of a single column of TEXT. Key key4 = Key.ofText("col1", "value"); ``` -For a key that consists of 2 - 5 columns, you can use the `Key.of()` methods to construct it as follows: +For a key that consists of two to five columns, you can use the `Key.of()` method to construct the key as follows. Similar to `ImmutableMap.of()` in Guava, you need to specify column names and values in turns: ```java -// for a key that consists of 2 - 5 columns +// For a key that consists of two to five columns. Key key1 = Key.of("col1", 1, "col2", 100L); Key key2 = Key.of("col1", 1, "col2", 100L, "col3", 1.3d); Key key3 = Key.of("col1", 1, "col2", 100L, "col3", 1.3d, "col4", "value"); Key key4 = Key.of("col1", 1, "col2", 100L, "col3", 1.3d, "col4", "value", "col5", false); ``` -Similar to `ImmutableMap.of()` in Guava, you need to specify column names and values in turns. - -For a key that consists of more than 5 columns, we can use the builder to construct it as follows: +For a key that consists of more than five columns, we can use the builder to construct the key as follows: ```java -// for a key that consists of more than 5 columns +// For a key that consists of more than five columns. Key key = Key.newBuilder() .addInt("col1", 1) .addBigInt("col2", 100L) @@ -367,14 +431,14 @@ Key key = Key.newBuilder() .build(); ``` -#### Get operation +#### `Get` operation `Get` is an operation to retrieve a single record specified by a primary key. -You need to create a Get object first, and then you can execute it with the `transaction.get()` method as follows: +You need to create a `Get` object first, and then you can execute the object by using the `transaction.get()` method as follows: ```java -// Create a Get operation +// Create a `Get` operation. Key partitionKey = Key.ofInt("c1", 10); Key clusteringKey = Key.of("c2", "aaa", "c3", 100L); @@ -387,62 +451,61 @@ Get get = .projections("c1", "c2", "c3", "c4") .build(); -// Execute the Get operation +// Execute the `Get` operation. Optional result = transaction.get(get); ``` You can also specify projections to choose which columns are returned. -##### Handle Result objects +##### Handle `Result` objects -The Get operation and Scan operation return `Result` objects. -So the following shows how to handle `Result` objects. +The `Get` operation and `Scan` operation return `Result` objects. The following shows how to handle `Result` objects. -You can get a column value of a result with `getXXX("")` methods (XXX is a type name) as follows: +You can get a column value of a result by using `get("")` methods as follows: ```java -// Get a Boolean value of a column -boolean booleanValue = result.getBoolean(""); +// Get the BOOLEAN value of a column. +boolean booleanValue = result.getBoolean(""); -// Get an Int value of a column -int intValue = result.getInt(""); +// Get the INT value of a column. +int intValue = result.getInt(""); -// Get a BigInt value of a column -long bigIntValue = result.getBigInt(""); +// Get the BIGINT value of a column. +long bigIntValue = result.getBigInt(""); -// Get a Float value of a column -float floatValue = result.getFloat(""); +// Get the FLOAT value of a column. +float floatValue = result.getFloat(""); -// Get a Double value of a column -double doubleValue = result.getDouble(""); +// Get the DOUBLE value of a column. +double doubleValue = result.getDouble(""); -// Get a Text value of a column -String textValue = result.getText(""); +// Get the TEXT value of a column. +String textValue = result.getText(""); -// Get a Blob value of a column (as a ByteBuffer) -ByteBuffer blobValue = result.getBlob(""); +// Get the BLOB value of a column as a `ByteBuffer`. +ByteBuffer blobValue = result.getBlob(""); -// Get a Blob value of a column as a byte array -byte[] blobValueAsBytes = result.getBlobAsBytes(""); +// Get the BLOB value of a column as a `byte` array. +byte[] blobValueAsBytes = result.getBlobAsBytes(""); ``` -And if you need to check if a value of a column is null, you can use the `isNull("")` method. +And if you need to check if a value of a column is null, you can use the `isNull("")` method. ``` java -// Check if a value of a column is null -boolean isNull = result.isNull(""); +// Check if a value of a column is null. +boolean isNull = result.isNull(""); ``` -Please see also [Javadoc of `Result`](https://javadoc.io/static/com.scalar-labs/scalardb/3.6.0/com/scalar/db/api/Result.html) for more details. +For more details, see the `Result` page in the [Javadoc](https://javadoc.io/doc/com.scalar-labs/scalardb/latest/index.html) of the version of ScalarDB that you're using. -##### Get with a secondary index +##### Execute `Get` by using a secondary index -You can also execute a Get operation with a secondary index. +You can execute a `Get` operation by using a secondary index. -Instead of specifying a partition key, you can specify an index key (specifying an indexed column) to use a secondary index as follows: +Instead of specifying a partition key, you can specify an index key (indexed column) to use a secondary index as follows: ```java -// Create a Get operation with a secondary index +// Create a `Get` operation by using a secondary index. Key indexKey = Key.ofFloat("c4", 1.23F); Get get = @@ -453,22 +516,27 @@ Get get = .projections("c1", "c2", "c3", "c4") .build(); -// Execute the Get operation +// Execute the `Get` operation. Optional result = transaction.get(get); ``` -Note that if the result has more than one record, the `transaction.get()` throws an exception. -If you want to handle multiple results, use [Scan with a secondary index](#scan-with-a-secondary-index). +{% capture notice--info %} +**Note** + +If the result has more than one record, `transaction.get()` will throw an exception. If you want to handle multiple results, see [Execute `Scan` by using a secondary index](#execute-scan-by-using-a-secondary-index). + +{% endcapture %} + +
{{ notice--info | markdownify }}
-#### Scan operation +#### `Scan` operation -`Scan` is an operation to retrieve multiple records within a partition. -You can specify clustering key boundaries and orderings for clustering key columns in Scan operations. +`Scan` is an operation to retrieve multiple records within a partition. You can specify clustering-key boundaries and orderings for clustering-key columns in `Scan` operations. -You need to create a Scan object first, and then you can execute it with the `transaction.scan()` method as follows: +You need to create a `Scan` object first, and then you can execute the object by using the `transaction.scan()` method as follows: ```java -// Create a Scan operation +// Create a `Scan` operation. Key partitionKey = Key.ofInt("c1", 10); Key startClusteringKey = Key.of("c2", "aaa", "c3", 100L); Key endClusteringKey = Key.of("c2", "aaa", "c3", 300L); @@ -485,23 +553,22 @@ Scan scan = .limit(10) .build(); -// Execute the Scan operation +// Execute the `Scan` operation. List results = transaction.scan(scan); ``` -You can omit the clustering key boundaries, or you can specify either a start boundary or an end boundary. -If you don't specify orderings, you get results ordered by clustering order you defined when creating the table. +You can omit the clustering-key boundaries or specify either a `start` boundary or an `end` boundary. If you don't specify `orderings`, you will get results ordered by the clustering order that you defined when creating the table. -Also, you can specify projections to choose which columns are returned, and limit to specify the number of records to return in Scan operations. +In addition, you can specify `projections` to choose which columns are returned and use `limit` to specify the number of records to return in `Scan` operations. -##### Scan with a secondary index +##### Execute `Scan` by using a secondary index -You can also execute a Scan operation with a secondary index. +You can execute a `Scan` operation by using a secondary index. -Instead of specifying a partition key, you can specify an index key (specifying an indexed column) to use a secondary index as follows: +Instead of specifying a partition key, you can specify an index key (indexed column) to use a secondary index as follows: ```java -// Create a Scan operation with a secondary index +// Create a `Scan` operation by using a secondary index. Key indexKey = Key.ofFloat("c4", 1.23F); Scan scan = @@ -513,20 +580,26 @@ Scan scan = .limit(10) .build(); -// Execute the Scan operation +// Execute the `Scan` operation. List results = transaction.scan(scan); ``` -Note that you can't specify clustering key boundaries and orderings in Scan with a secondary index. +{% capture notice--info %} +**Note** -##### Scan without a partition key to retrieve all the records of a table +You can't specify clustering-key boundaries and orderings in `Scan` by using a secondary index. +{% endcapture %} -You can also execute a Scan operation without specifying a partition key. +
{{ notice--info | markdownify }}
+ +##### Execute `Scan` without specifying a partition key to retrieve all the records of a table + +You can execute a `Scan` operation without specifying a partition key. Instead of calling the `partitionKey()` method in the builder, you can call the `all()` method to scan a table without specifying a partition key as follows: ```java -// Create a Scan operation without a partition key +// Create a `Scan` operation without specifying a partition key. Scan scan = Scan.newBuilder() .namespace("ns") @@ -536,22 +609,34 @@ Scan scan = .limit(10) .build(); -// Execute the Scan operation +// Execute the `Scan` operation. List results = transaction.scan(scan); ``` -Note that you can't specify clustering key boundaries and orderings in Scan without a partition key. +{% capture notice--info %} +**Note** + +You can't specify clustering-key boundaries and orderings in `Scan` without specifying a partition key. +{% endcapture %} + +
{{ notice--info | markdownify }}
-#### Put operation +#### `Put` operation -`Put` is an operation to put a record specified by a primary key. -It behaves as an upsert operation for a record, i.e., updating the record if the record exists; otherwise, inserting the record. -Note that when you update an existing record, you need to read it using a `Get` or a `Scan` before a `Put` operation. +`Put` is an operation to put a record specified by a primary key. The operation behaves as an upsert operation for a record, in which the operation updates the record if the record exists or inserts the record if the record does not exist. -You need to create a Put object first, and then you can execute it with the `transaction.put()` method as follows: +{% capture notice--info %} +**Note** + +When you update an existing record, you need to read the record by using `Get` or `Scan` before using a `Put` operation. +{% endcapture %} + +
{{ notice--info | markdownify }}
+ +You need to create a `Put` object first, and then you can execute the object by using the `transaction.put()` method as follows: ```java -// Create a Put operation +// Create a `Put` operation. Key partitionKey = Key.ofInt("c1", 10); Key clusteringKey = Key.of("c2", "aaa", "c3", 100L); @@ -565,11 +650,11 @@ Put put = .doubleValue("c5", 4.56) .build(); -// Execute the Put operation +// Execute the `Put` operation. transaction.put(put); ``` -You can also put a record with null values as follows: +You can also put a record with `null` values as follows: ```java Put put = @@ -583,15 +668,22 @@ Put put = .build(); ``` -#### Delete operation +#### `Delete` operation `Delete` is an operation to delete a record specified by a primary key. -Note that when you delete a record, you need to read it using a `Get` or a `Scan` before a `Delete` operation. -You need to create a Delete object first, and then you can execute it with the `transaction.delete()` method as follows: +{% capture notice--info %} +**Note** + +When you delete a record, you need to read the record by using `Get` or `Scan` before using a `Delete` operation. +{% endcapture %} + +
{{ notice--info | markdownify }}
+ +You need to create a `Delete` object first, and then you can execute the object by using the `transaction.delete()` method as follows: ```java -// Create a Delete operation +// Create a `Delete` operation. Key partitionKey = Key.ofInt("c1", 10); Key clusteringKey = Key.of("c2", "aaa", "c3", 100L); @@ -603,18 +695,18 @@ Delete delete = .clusteringKey(clusteringKey) .build(); -// Execute the Delete operation +// Execute the `Delete` operation. transaction.delete(delete); ``` #### Mutate operation -Mutate is an operation to execute multiple mutations (Put and Delete operations). +Mutate is an operation to execute multiple mutations (`Put` and `Delete` operations). -You need to create mutation objects first, and then you can execute them with the `transaction.mutate()` method as follows: +You need to create mutation objects first, and then you can execute the objects by using the `transaction.mutate()` method as follows: ```java -// Create Put and Delete operations +// Create `Put` and `Delete` operations. Key partitionKey = Key.ofInt("c1", 10); Key clusteringKeyForPut = Key.of("c2", "aaa", "c3", 100L); @@ -639,72 +731,70 @@ Delete delete = .clusteringKey(clusteringKeyForDelete) .build(); -// Execute the operations +// Execute the operations. transaction.mutate(Arrays.asList(put, delete)); ``` -#### Notes - -- All the builders of the CRUD operations can specify consistency with the `consistency()` methods, but it's ignored, and the `LINEARIZABLE` consistency level is always used in transactions. -- Also, the builders of the mutation operations (Put and Delete operations) can specify a condition with the `condition()` methods, but it's ignored, too. -Please program such conditions in a transaction if you want to implement conditional mutation. - ### Commit a transaction After executing CRUD operations, you need to commit a transaction to finish it. -You can commit a transaction as follows; +You can commit a transaction as follows: ```java -// Commit a transaction +// Commit a transaction. transaction.commit(); ``` -### Rollback/Abort a transaction +### Roll back or abort a transaction -If you want to rollback/abort a transaction or an error happens during the execution, you can rollback/abort a transaction. +If an error occurs when executing a transaction, you can roll back or abort the transaction. -You can rollback/abort a transaction as follows; +You can roll back a transaction as follows: ```java -// Rollback a transaction +// Roll back a transaction. transaction.rollback(); +``` -Or +Or, you can abort a transaction as follows: -// Abort a transaction +```java +// Abort a transaction. transaction.abort(); ``` -Please see [Handle Exceptions](#handle-exceptions) for the details of how to handle exceptions in ScalarDB. +For details about how to handle exceptions in ScalarDB, see [How to handle exceptions](#how-to-handle-exceptions). -## Transactional operations for Two-phase Commit Transaction +## How to handle exceptions -Please see [Two-phase Commit Transactions](two-phase-commit-transactions.md). +When executing a transaction, you will also need to handle exceptions properly. -## Handle Exceptions +{% capture notice--warning %} +**Attention** -Handling exceptions correctly in ScalarDB is very important. -If you mishandle exceptions, your data could become inconsistent. -This document explains how to handle exceptions properly in ScalarDB. +If you don't handle exceptions properly, you may face anomalies or data inconsistency. +{% endcapture %} -Let's look at the following example code to see how to handle exceptions in ScalarDB. +
{{ notice--warning | markdownify }}
+ +The following sample code shows how to handle exceptions: ```java public class Sample { public static void main(String[] args) throws IOException, InterruptedException { - TransactionFactory factory = TransactionFactory.create(""); + TransactionFactory factory = TransactionFactory.create(""); DistributedTransactionManager manager = factory.getTransactionManager(); int retryCount = 0; while (true) { if (retryCount > 0) { - // Retry the transaction three times maximum in this sample code + // Retry the transaction three times maximum in this sample code. if (retryCount == 3) { return; } - // Sleep 100 milliseconds before retrying the transaction in this sample code + // Sleep 100 milliseconds before retrying the transaction in this sample code. TimeUnit.MILLISECONDS.sleep(100); } @@ -713,44 +803,43 @@ public class Sample { try { tx = manager.start(); } catch (TransactionException e) { - // If starting a transaction fails, it indicates some failure happens during a transaction, - // so you should cancel the transaction or retry the transaction after the failure/error is - // fixed + // If starting a transaction fails, it indicates some failure has happened during the transaction, + // so you should cancel the transaction or retry the transaction after fixing the failure/error. return; } try { - // Execute CRUD operations in the transaction + // Execute CRUD operations in the transaction. Optional result = tx.get(...); List results = tx.scan(...); tx.put(...); tx.delete(...); - // Commit the transaction + // Commit the transaction. tx.commit(); } catch (CrudConflictException | CommitConflictException e) { - // If you catch CrudConflictException or CommitConflictException, it indicates conflicts - // happen during a transaction so that you can retry the transaction + // If you catch `CrudConflictException` or `CommitConflictException`, it indicates conflicts + // happened during a transaction, so you should retry the transaction. try { tx.abort(); } catch (AbortException ex) { - // Aborting the transaction fails. You can log it here + // Aborting the transaction fails. You can log it here. } retryCount++; } catch (CrudException | CommitException e) { - // If you catch CrudException or CommitException, it indicates some failure happens, so you - // should cancel the transaction or retry the transaction after the failure/error is fixed + // If you catch `CrudException` or `CommitException`, it indicates some failure has happened, so you + // should cancel the transaction or retry the transaction after fixing the failure/error. try { tx.abort(); } catch (AbortException ex) { - // Aborting the transaction fails. You can log it here + // Aborting the transaction fails. You can log it here. } return; } catch (UnknownTransactionStatusException e) { // If you catch `UnknownTransactionStatusException` when committing the transaction, you are // not sure if the transaction succeeds or not. In such a case, you need to check if the // transaction is committed successfully or not and retry it if it failed. How to identify a - // transaction status is delegated to users + // transaction status is delegated to users. return; } } @@ -758,46 +847,30 @@ public class Sample { } ``` -The APIs for CRUD operations (`get()`/`scan()`/`put()`/`delete()`/`mutate()`) could throw `CrudException` and `CrudConflictException`. -If you catch `CrudException`, it indicates some failure (e.g., database failure and network error) happens during a transaction, so you should cancel the transaction or retry the transaction after the failure/error is fixed. -If you catch `CrudConflictException`, it indicates conflicts happen during a transaction so that you can retry the transaction, preferably with well-adjusted exponential backoff based on your application and environment. -The sample code retries three times maximum and sleeps 100 milliseconds before retrying the transaction. +### `CrudException` and `CrudConflictException` + +The APIs for CRUD operations (`get()`, `scan()`, `put()`, `delete()`, and `mutate()`) could throw `CrudException` or `CrudConflictException`: -Also, the `commit()` API could throw `CommitException`, `CommitConflictException`, and `UnknownTransactionStatusException`. -If you catch `CommitException`, like the `CrudException` case, you should cancel the transaction or retry the transaction after the failure/error is fixed. -If you catch `CommitConflictException`, like the `CrudConflictException` case, you can retry the transaction. -If you catch `UnknownTransactionStatusException`, you are not sure if the transaction succeeds or not. -In such a case, you need to check if the transaction is committed successfully or not and retry it if it fails. -How to identify a transaction status is delegated to users. -You may want to create a transaction status table and update it transactionally with other application data so that you can get the status of a transaction from the status table. +- If you catch `CrudException`, this exception indicates that the transaction CRUD operation has failed due to transient or non-transient faults. You can try retrying the transaction from the beginning, but the transaction may still fail if the cause is non-transient. +- If you catch `CrudConflictException`, this exception indicates that the transaction CRUD operation has failed due to transient faults (for example, a conflict error). In this case, you can retry the transaction from the beginning. -### For Two-phase Commit Transactions +### `CommitException`, `CommitConflictException`, and `UnknownTransactionStatusException` -You need to handle more exceptions when you use [Two-phase Commit Transactions](two-phase-commit-transactions.md) because you additionally need to call the `prepare()` API (and the `validate()` API when required). +The `commit()` API could throw `CommitException`, `CommitConflictException`, or `UnknownTransactionStatusException`: -The `prepare()` API could throw `PreparationException` and `PreparationConflictException`. -If you catch `PreparationException`, like the `CrudException` case, you should cancel the transaction or retry the transaction after the failure/error is fixed. -If you catch `PreparationConflictException`, like the `CrudConflictException` case, you can retry the transaction. +- If you catch `CommitException`, this exception indicates that committing the transaction fails due to transient or non-transient faults. You can try retrying the transaction from the beginning, but the transaction may still fail if the cause is non-transient. +- If you catch `CommitConflictException`, this exception indicates that committing the transaction has failed due to transient faults (for example, a conflict error). In this case, you can retry the transaction from the beginning. +- If you catch `UnknownTransactionStatusException`, this exception indicates that the status of the transaction, whether it was successful or not, is unknown. In this case, you need to check if the transaction is committed successfully and retry the transaction if it has failed. -Also, the `validate()` API could throw `ValidationException` and `ValidationConflictException`. -If you catch `ValidationException`, like the `CrudException` case, you should cancel the transaction or retry the transaction after the failure/error is fixed. -If you catch `ValidationConflictException`, like the `CrudConflictException` case, you can retry the transaction. +How to identify a transaction status is delegated to users. You may want to create a transaction status table and update it transactionally with other application data so that you can get the status of a transaction from the status table. -## Investigate Consensus Commit transactions errors +## Investigating Consensus Commit transaction manager errors -This configuration is only available to troubleshoot Consensus Commit transactions. By adding the following configuration, `Get` and `Scan` operations results will contain [transaction metadata](schema-loader.md#internal-metadata-for-consensus-commit). -To see the transaction metadata columns details for a given table, you can use the `DistributedTransactionAdmin.getTableMetadata()` method which will return the table metadata augmented with the transaction metadata columns. -All in all, using this configuration can be useful to investigate transaction related issues. +To investigate errors when using the Consensus Commit transaction manager, you can enable a configuration that will return table metadata augmented with transaction metadata columns, which can be helpful when investigating transaction-related issues. This configuration, which is only available when troubleshooting the Consensus Commit transaction manager, enables you to see transaction metadata column details for a given table by using the `DistributedTransactionAdmin.getTableMetadata()` method. + +By adding the following configuration, `Get` and `Scan` operations results will contain [transaction metadata](schema-loader.md#internal-metadata-for-consensus-commit): ```properties -# By default, it is set to "false". +# By default, this configuration is set to `false`. scalar.db.consensus_commit.include_metadata.enabled=true ``` - -## References - -* [Design document](design.md) -* [Getting started](getting-started-with-scalardb.md) -* [Multi-storage Transactions](multi-storage-transactions.md) -* [Two-phase Commit Transactions](two-phase-commit-transactions.md) -* [ScalarDB Server](scalardb-server.md) diff --git a/docs/3.9/api-guide.md b/docs/3.9/api-guide.md index 5f2f49a..e659333 100644 --- a/docs/3.9/api-guide.md +++ b/docs/3.9/api-guide.md @@ -1,29 +1,33 @@ -# Java API Guide +# ScalarDB Java API Guide -ScalarDB Java API is mainly composed of Administrative API and Transactional API. -This guide briefly explains what kind of APIs exist and how to use them. - -* [Administrative API](#administrative-api) -* [Transactional API](#transactional-api) +The ScalarDB Java API is mainly composed of the Administrative API and Transactional API. This guide briefly explains what kinds of APIs exist, how to use them, and related topics like how to handle exceptions. ## Administrative API -This section explains how to execute administrative operations with Administrative API in ScalarDB. -You can execute administrative operations programmatically as follows, but you can also execute those operations through [Schema Loader](schema-loader.md). +This section explains how to execute administrative operations programmatically by using the Administrative API in ScalarDB. + +{% capture notice--info %} +**Note** + +Another method for executing administrative operations is to use [Schema Loader](schema-loader.md). +{% endcapture %} + +
{{ notice--info | markdownify }}
+ +### Get a `DistributedTransactionAdmin` instance -### Get a DistributedTransactionAdmin instance +You first need to get a `DistributedTransactionAdmin` instance to execute administrative operations. -To execute administrative operations, you first need to get a `DistributedTransactionAdmin` instance. -The `DistributedTransactionAdmin` instance can be obtained from `TransactionFactory` as follows: +To get a `DistributedTransactionAdmin` instance, you can use `TransactionFactory` as follows: ```java -TransactionFactory transactionFactory = TransactionFactory.create(""); +TransactionFactory transactionFactory = TransactionFactory.create(""); DistributedTransactionAdmin admin = transactionFactory.getTransactionAdmin(); ``` For details about configurations, see [ScalarDB Configurations](configurations.md). -Once you have executed all administrative operations, you should close the `DistributedTransactionAdmin` instance as follows: +After you have executed all administrative operations, you should close the `DistributedTransactionAdmin` instance as follows: ```java admin.close(); @@ -32,60 +36,77 @@ admin.close(); ### Create a namespace Before creating tables, namespaces must be created since a table belongs to one namespace. + You can create a namespace as follows: ```java -// Create a namespace "ns". It will throw an exception if the namespace already exists +// Create the namespace "ns". If the namespace already exists, an exception will be thrown. admin.createNamespace("ns"); -// Create a namespace only if it does not already exist +// Create the namespace only if it does not already exist. boolean ifNotExists = true; admin.createNamespace("ns", ifNotExists); -// Create a namespace with options +// Create the namespace with options. Map options = ...; admin.createNamespace("ns", options); ``` -#### Creation Options +#### Creation options + +In the creation operations, like creating a namespace and creating a table, you can specify options that are maps of option names and values (`Map`). By using the options, you can set storage adapter–specific configurations. + +Select your database to see the options available: + +
+
+ + + + +
-In the creation operations (creating a namespace, creating a table, etc.), you can specify options that are maps of option names and values (`Map`). -With the options, we can set storage adapter specific configurations. +
-Currently, we can set the following options for the storage adapters: +| Name | Description | Default | +|----------------------|----------------------------------------------------------------------------------------|------------------| +| replication-strategy | Cassandra replication strategy. Must be `SimpleStrategy` or `NetworkTopologyStrategy`. | `SimpleStrategy` | +| compaction-strategy | Cassandra compaction strategy, Must be `LCS`, `STCS` or `TWCS`. | `STCS` | +| replication-factor | Cassandra replication factor. | 1 | -For Cosmos DB for NoSQL: +
+
-| name | value | default | -|------------|----------------------------------------------|---------| -| ru | Base resource unit | 400 | -| no-scaling | Disable auto-scaling for Cosmos DB for NoSQL | false | +| Name | Description | Default | +|------------|-----------------------------------------------------|---------| +| ru | Base resource unit. | 400 | +| no-scaling | Disable auto-scaling for Cosmos DB for NoSQL. | false | -For DynamoDB: +
+
-| name | value | default | -|------------|----------------------------------------|---------| -| no-scaling | Disable auto-scaling for DynamoDB | false | -| no-backup | Disable continuous backup for DynamoDB | false | -| ru | Base resource unit | 10 | +| Name | Description | Default | +|------------|-----------------------------------------|---------| +| no-scaling | Disable auto-scaling for DynamoDB. | false | +| no-backup | Disable continuous backup for DynamoDB. | false | +| ru | Base resource unit. | 10 | -For Cassandra: +
+
-| name | value | default | -|----------------------|---------------------------------------------------------------------------------------|------------------| -| replication-strategy | Cassandra replication strategy, must be `SimpleStrategy` or `NetworkTopologyStrategy` | `SimpleStrategy` | -| compaction-strategy | Cassandra compaction strategy, must be `LCS`, `STCS` or `TWCS` | `STCS` | -| replication-factor | Cassandra replication factor | 1 | +No options are available for JDBC databases. +
+
### Create a table -Next, we will discuss table creation. +When creating a table, you should define the table metadata and then create the table. -You firstly need to create the TaleMetadata as follows: +To define the table metadata, you can use `TableMetadata`. The following shows how to define the columns, partition key, clustering key including clustering orders, and secondary indexes of a table: ```java -// Define a table metadata +// Define the table metadata. TableMetadata tableMetadata = TableMetadata.newBuilder() .addColumn("c1", DataType.INT) @@ -100,21 +121,19 @@ TableMetadata tableMetadata = .build(); ``` -Here you define columns, a partition key, a clustering key including clustering orders, and secondary indexes of a table. +For details about the data model of ScalarDB, see [Data Model](design.md#data-model). -Please see [ScalarDB design document - Data Model](design.md#data-model) for the details of the ScalarDB Data Model. - -And then, you can create a table as follows: +Then, create a table as follows: ```java -// Create a table "ns.tbl". It will throw an exception if the table already exists +// Create the table "ns.tbl". If the table already exists, an exception will be thrown. admin.createTable("ns", "tbl", tableMetadata); -// Create a table only if it does not already exist +// Create the table only if it does not already exist. boolean ifNotExists = true; admin.createTable("ns", "tbl", tableMetadata, ifNotExists); -// Create a table with options +// Create the table with options. Map options = ...; admin.createTable("ns", "tbl", tableMetadata, options); ``` @@ -124,43 +143,45 @@ admin.createTable("ns", "tbl", tableMetadata, options); You can create a secondary index as follows: ```java -// Create a secondary index on a column "c5" of a table "ns.tbl". It will throw an exception if the secondary index already exists +// Create a secondary index on column "c5" for table "ns.tbl". If a secondary index already exists, an exception will be thrown. admin.createIndex("ns", "tbl", "c5"); -// Create a secondary index only if it does not already exist +// Create the secondary index only if it does not already exist. boolean ifNotExists = true; admin.createIndex("ns", "tbl", "c5", ifNotExists); -// Create a secondary index with options +// Create the secondary index with options. Map options = ...; admin.createIndex("ns", "tbl", "c5", options); ``` ### Add a new column to a table -You can add a new non-partition key column to a table as follows: +You can add a new, non-partition key column to a table as follows: + ```java -// Add the new column "c6" of type INT to the table "ns.tbl" +// Add a new column "c6" with the INT data type to the table "ns.tbl". admin.addNewColumnToTable("ns", "tbl", "c6", DataType.INT) ``` -This should be executed with significant consideration as the execution time may vary greatly -depending on the underlying storage. Please plan accordingly especially if the database runs in production: -- For Cosmos DB for NoSQL and DynamoDB: this operation is almost instantaneous as the table - schema is not modified. Only the table metadata stored in a separated table are updated. -- For Cassandra: adding a column will only update the schema metadata and do not modify existing - schema records. The cluster topology is the main factor for the execution time. Since the schema - metadata change propagates to each cluster node via a gossip protocol, the larger the cluster, the - longer it will take for all nodes to be updated. -- For relational databases (MySQL, Oracle, etc.): it may take a very long time to execute and a - table-lock may be performed. +{% capture notice--warning %} +**Attention** + +You should carefully consider adding a new column to a table because the execution time may vary greatly depending on the underlying storage. Please plan accordingly and consider the following, especially if the database runs in production: + +- **For Cosmos DB for NoSQL and DynamoDB:** Adding a column is almost instantaneous as the table schema is not modified. Only the table metadata stored in a separate table is updated. +- **For Cassandra:** Adding a column will only update the schema metadata and will not modify the existing schema records. The cluster topology is the main factor for the execution time. Changes to the schema metadata are shared to each cluster node via a gossip protocol. Because of this, the larger the cluster, the longer it will take for all nodes to be updated. +- **For relational databases (MySQL, Oracle, etc.):** Adding a column shouldn't take a long time to execute. +{% endcapture %} + +
{{ notice--warning | markdownify }}
### Truncate a table You can truncate a table as follows: ```java -// Truncate a table "ns.tbl" +// Truncate the table "ns.tbl". admin.truncateTable("ns", "tbl"); ``` @@ -169,10 +190,10 @@ admin.truncateTable("ns", "tbl"); You can drop a secondary index as follows: ```java -// Drop a secondary index on a column "c5" of a table "ns.tbl". It will throw an exception if the secondary index does not exist +// Drop the secondary index on column "c5" from table "ns.tbl". If the secondary index does not exist, an exception will be thrown. admin.dropIndex("ns", "tbl", "c5"); -// Drop a secondary index only if it exists +// Drop the secondary index only if it exists. boolean ifExists = true; admin.dropIndex("ns", "tbl", "c5", ifExists); ``` @@ -182,10 +203,10 @@ admin.dropIndex("ns", "tbl", "c5", ifExists); You can drop a table as follows: ```java -// Drop a table "ns.tbl". It will throw an exception if the table does not exist +// Drop the table "ns.tbl". If the table does not exist, an exception will be thrown. admin.dropTable("ns", "tbl"); -// Drop a table only if it exists +// Drop the table only if it exists. boolean ifExists = true; admin.dropTable("ns", "tbl", ifExists); ``` @@ -195,168 +216,234 @@ admin.dropTable("ns", "tbl", ifExists); You can drop a namespace as follows: ```java -// Drop a namespace "ns". It will throw an exception if the namespace does not exist +// Drop the namespace "ns". If the namespace does not exist, an exception will be thrown. admin.dropNamespace("ns"); -// Drop a namespace only if it exists +// Drop the namespace only if it exists. boolean ifExists = true; admin.dropNamespace("ns", ifExists); ``` -### Get a table metadata +### Get the tables of a namespace -You can get a table metadata as follows: +You can get the tables of a namespace as follows: ```java -// Get a table metadata of "ns.tbl" +// Get the tables of the namespace "ns". +Set tables = admin.getNamespaceTableNames("ns"); +``` + +### Get table metadata + +You can get table metadata as follows: + +```java +// Get the table metadata for "ns.tbl". TableMetadata tableMetadata = admin.getTableMetadata("ns", "tbl"); ``` -### Operations for Coordinator tables +### Specify operations for the Coordinator table + +The Coordinator table is used by the [Transactional API](#transactional-api) to track the statuses of transactions. -Depending on the transaction manager type, you need to create coordinator tables to execute transactions. -The following items describe the operations for the coordinator table. +When using a transaction manager, you must create the Coordinator table to execute transactions. In addition to creating the table, you can truncate and drop the Coordinator table. -#### Create Coordinator tables +#### Create the Coordinator table -You can create coordinator tables as follows: +You can create the Coordinator table as follows: ```java -// Create coordinator tables +// Create the Coordinator table. admin.createCoordinatorTables(); -// Create coordinator tables only if they do not already exist +// Create the Coordinator table only if one does not already exist. boolean ifNotExist = true; admin.createCoordinatorTables(ifNotExist); -// Create coordinator tables with options +// Create the Coordinator table with options. Map options = ...; admin.createCoordinatorTables(options); ``` -#### Truncate Coordinator tables +#### Truncate the Coordinator table -You can truncate coordinator tables as follows: +You can truncate the Coordinator table as follows: ```java -// Truncate coordinator tables +// Truncate the Coordinator table. admin.truncateCoordinatorTables(); ``` -#### Drop Coordinator tables +#### Drop the Coordinator table -You can drop coordinator tables as follows: +You can drop the Coordinator table as follows: ```java -// Drop coordinator tables +// Drop the Coordinator table. admin.dropCoordinatorTables(); -// Drop coordinator tables if they exist +// Drop the Coordinator table if one exist. boolean ifExist = true; admin.dropCoordinatorTables(ifExist); ``` ## Transactional API -This section explains how to execute transactional operations with Transactional API in ScalarDB. +This section explains how to execute transactional operations by using the Transactional API in ScalarDB. + +### Get a `DistributedTransactionManager` instance -### Get a DistributedTransactionManager instance +You first need to get a `DistributedTransactionManager` instance to execute transactional operations. -You need to get a `DistributedTransactionManager` instance to execute transactional operations. -You can get it in the following way: +To get a `DistributedTransactionManager` instance, you can use `TransactionFactory` as follows: ```java -TransactionFactory transactionFactory = TransactionFactory.create(""); +TransactionFactory transactionFactory = TransactionFactory.create(""); DistributedTransactionManager transactionManager = transactionFactory.getTransactionManager(); ``` -Once you have executed all transactional operations, you should close the `DistributedTransactionManager` instance as follows: +After you have executed all transactional operations, you should close the `DistributedTransactionManager` instance as follows: ```java transactionManager.close(); ``` -### Begin/Start a transaction +### Begin or start a transaction -You need to begin/start a transaction before executing transactional CRUD operations. -You can begin/start a transaction as follows: +Before executing transactional CRUD operations, you need to begin or start a transaction. + +You can begin a transaction as follows: ```java -// Begin a transaction +// Begin a transaction. DistributedTransaction transaction = transactionManager.begin(); +``` -Or +Or, you can start a transaction as follows: -// Start a transaction +```java +// Start a transaction. DistributedTransaction transaction = transactionManager.start(); ``` -You can also begin/start a transaction with specifying a transaction ID as follows: +Alternatively, you can use the `begin` method for a transaction by specifying a transaction ID as follows: ```java -// Begin a transaction with specifying a transaction ID -DistributedTransaction transaction = transactionManager.begin(""); +// Begin a transaction with specifying a transaction ID. +DistributedTransaction transaction = transactionManager.begin(""); +``` -Or +Or, you can use the `start` method for a transaction by specifying a transaction ID as follows: -// Start a transaction with specifying a transaction ID -DistributedTransaction transaction = transactionManager.start(""); +```java +// Start a transaction with specifying a transaction ID. +DistributedTransaction transaction = transactionManager.start(""); ``` -Note that you must guarantee uniqueness of the transaction ID in this case. +{% capture notice--info %} +**Note** + +Specifying a transaction ID is useful when you want to link external systems to ScalarDB. Otherwise, you should use the `begin()` method or the `start()` method. + +When you specify a transaction ID, make sure you specify a unique ID (for example, UUID v4) throughout the system since ScalarDB depends on the uniqueness of transaction IDs for correctness. +{% endcapture %} + +
{{ notice--info | markdownify }}
+ +### Join a transaction + +Joining a transaction is particularly useful in a stateful application where a transaction spans multiple client requests. In such a scenario, the application can start a transaction during the first client request. Then, in subsequent client requests, the application can join the ongoing transaction by using the `join()` method. + +You can join an ongoing transaction that has already begun by specifying the transaction ID as follows: + +```java +// Join a transaction. +DistributedTransaction transaction = transactionManager.join(""); +``` + +{% capture notice--info %} +**Note** + +To get the transaction ID with `getId()`, you can specify the following: + +```java +tx.getId(); +``` +{% endcapture %} + +
{{ notice--info | markdownify }}
### Resume a transaction -You can resume a transaction you have already begun with specifying a transaction ID as follows: +Resuming a transaction is particularly useful in a stateful application where a transaction spans multiple client requests. In such a scenario, the application can start a transaction during the first client request. Then, in subsequent client requests, the application can resume the ongoing transaction by using the `resume()` method. + +You can resume an ongoing transaction that you have already begun by specifying a transaction ID as follows: ```java -// Resume a transaction -DistributedTransaction transaction = transactionManager.resume(""); +// Resume a transaction. +DistributedTransaction transaction = transactionManager.resume(""); ``` -It is helpful in a stateful application where a transaction spans multiple client requests. -In that case, the application can begin a transaction in the first client request. -And in the following client requests, it can resume the transaction with the `resume()` method. +{% capture notice--info %} +**Note** + +To get the transaction ID with `getId()`, you can specify the following: + +```java +tx.getId(); +``` +{% endcapture %} + +
{{ notice--info | markdownify }}
+ +### Implement CRUD operations + +The following sections describe key construction and CRUD operations. + +{% capture notice--info %} +**Note** + +Although all the builders of the CRUD operations can specify consistency by using the `consistency()` methods, those methods are ignored. Instead, the `LINEARIZABLE` consistency level is always used in transactions. + +In addition, although the builders of the mutation operations (`Put` and `Delete` operations) can specify a condition by using the `condition()` methods, those methods are also ignored. Instead, if you want to implement conditional mutation, please program such conditions for transactions. +{% endcapture %} -### CRUD operations +
{{ notice--info | markdownify }}
#### Key construction -Most CRUD operations need to specify `Key` objects (partition-key, clustering-key, etc.). -So, before moving on to CRUD operations, the following explains how to construct a `Key` object. +Most CRUD operations need to specify `Key` objects (partition-key, clustering-key, etc.). So, before moving on to CRUD operations, the following explains how to construct a `Key` object. -For a single column key, you can use the `Key.ofXXX()` methods (XXX is a type name) to construct it as follows: +For a single column key, you can use `Key.of()` methods to construct the key as follows: ```java -// for a key that consists of a single column of Int +// For a key that consists of a single column of INT. Key key1 = Key.ofInt("col1", 1); -// for a key that consists of a single column of BigInt +// For a key that consists of a single column of BIGINT. Key key2 = Key.ofBigInt("col1", 100L); -// for a key that consists of a single column of Double +// For a key that consists of a single column of DOUBLE. Key key3 = Key.ofDouble("col1", 1.3d); -// for a key that consists of a single column of Text +// For a key that consists of a single column of TEXT. Key key4 = Key.ofText("col1", "value"); ``` -For a key that consists of 2 - 5 columns, you can use the `Key.of()` methods to construct it as follows: +For a key that consists of two to five columns, you can use the `Key.of()` method to construct the key as follows. Similar to `ImmutableMap.of()` in Guava, you need to specify column names and values in turns: ```java -// for a key that consists of 2 - 5 columns +// For a key that consists of two to five columns. Key key1 = Key.of("col1", 1, "col2", 100L); Key key2 = Key.of("col1", 1, "col2", 100L, "col3", 1.3d); Key key3 = Key.of("col1", 1, "col2", 100L, "col3", 1.3d, "col4", "value"); Key key4 = Key.of("col1", 1, "col2", 100L, "col3", 1.3d, "col4", "value", "col5", false); ``` -Similar to `ImmutableMap.of()` in Guava, you need to specify column names and values in turns. - -For a key that consists of more than 5 columns, we can use the builder to construct it as follows: +For a key that consists of more than five columns, we can use the builder to construct the key as follows: ```java -// for a key that consists of more than 5 columns +// For a key that consists of more than five columns. Key key = Key.newBuilder() .addInt("col1", 1) .addBigInt("col2", 100L) @@ -367,14 +454,14 @@ Key key = Key.newBuilder() .build(); ``` -#### Get operation +#### `Get` operation `Get` is an operation to retrieve a single record specified by a primary key. -You need to create a Get object first, and then you can execute it with the `transaction.get()` method as follows: +You need to create a `Get` object first, and then you can execute the object by using the `transaction.get()` method as follows: ```java -// Create a Get operation +// Create a `Get` operation. Key partitionKey = Key.ofInt("c1", 10); Key clusteringKey = Key.of("c2", "aaa", "c3", 100L); @@ -387,62 +474,61 @@ Get get = .projections("c1", "c2", "c3", "c4") .build(); -// Execute the Get operation +// Execute the `Get` operation. Optional result = transaction.get(get); ``` You can also specify projections to choose which columns are returned. -##### Handle Result objects +##### Handle `Result` objects -The Get operation and Scan operation return `Result` objects. -So the following shows how to handle `Result` objects. +The `Get` operation and `Scan` operation return `Result` objects. The following shows how to handle `Result` objects. -You can get a column value of a result with `getXXX("")` methods (XXX is a type name) as follows: +You can get a column value of a result by using `get("")` methods as follows: ```java -// Get a Boolean value of a column -boolean booleanValue = result.getBoolean(""); +// Get the BOOLEAN value of a column. +boolean booleanValue = result.getBoolean(""); -// Get an Int value of a column -int intValue = result.getInt(""); +// Get the INT value of a column. +int intValue = result.getInt(""); -// Get a BigInt value of a column -long bigIntValue = result.getBigInt(""); +// Get the BIGINT value of a column. +long bigIntValue = result.getBigInt(""); -// Get a Float value of a column -float floatValue = result.getFloat(""); +// Get the FLOAT value of a column. +float floatValue = result.getFloat(""); -// Get a Double value of a column -double doubleValue = result.getDouble(""); +// Get the DOUBLE value of a column. +double doubleValue = result.getDouble(""); -// Get a Text value of a column -String textValue = result.getText(""); +// Get the TEXT value of a column. +String textValue = result.getText(""); -// Get a Blob value of a column (as a ByteBuffer) -ByteBuffer blobValue = result.getBlob(""); +// Get the BLOB value of a column as a `ByteBuffer`. +ByteBuffer blobValue = result.getBlob(""); -// Get a Blob value of a column as a byte array -byte[] blobValueAsBytes = result.getBlobAsBytes(""); +// Get the BLOB value of a column as a `byte` array. +byte[] blobValueAsBytes = result.getBlobAsBytes(""); ``` -And if you need to check if a value of a column is null, you can use the `isNull("")` method. +And if you need to check if a value of a column is null, you can use the `isNull("")` method. ``` java -// Check if a value of a column is null -boolean isNull = result.isNull(""); +// Check if a value of a column is null. +boolean isNull = result.isNull(""); ``` -Please see also [Javadoc of `Result`](https://javadoc.io/static/com.scalar-labs/scalardb/3.6.0/com/scalar/db/api/Result.html) for more details. +For more details, see the `Result` page in the [Javadoc](https://javadoc.io/doc/com.scalar-labs/scalardb/latest/index.html) of the version of ScalarDB that you're using. -##### Get with a secondary index +##### Execute `Get` by using a secondary index -You can also execute a Get operation with a secondary index. +You can execute a `Get` operation by using a secondary index. -Instead of specifying a partition key, you can specify an index key (specifying an indexed column) to use a secondary index as follows: +Instead of specifying a partition key, you can specify an index key (indexed column) to use a secondary index as follows: ```java -// Create a Get operation with a secondary index +// Create a `Get` operation by using a secondary index. Key indexKey = Key.ofFloat("c4", 1.23F); Get get = @@ -453,22 +539,27 @@ Get get = .projections("c1", "c2", "c3", "c4") .build(); -// Execute the Get operation +// Execute the `Get` operation. Optional result = transaction.get(get); ``` -Note that if the result has more than one record, the `transaction.get()` throws an exception. -If you want to handle multiple results, use [Scan with a secondary index](#scan-with-a-secondary-index). +{% capture notice--info %} +**Note** + +If the result has more than one record, `transaction.get()` will throw an exception. If you want to handle multiple results, see [Execute `Scan` by using a secondary index](#execute-scan-by-using-a-secondary-index). + +{% endcapture %} -#### Scan operation +
{{ notice--info | markdownify }}
-`Scan` is an operation to retrieve multiple records within a partition. -You can specify clustering key boundaries and orderings for clustering key columns in Scan operations. +#### `Scan` operation -You need to create a Scan object first, and then you can execute it with the `transaction.scan()` method as follows: +`Scan` is an operation to retrieve multiple records within a partition. You can specify clustering-key boundaries and orderings for clustering-key columns in `Scan` operations. + +You need to create a `Scan` object first, and then you can execute the object by using the `transaction.scan()` method as follows: ```java -// Create a Scan operation +// Create a `Scan` operation. Key partitionKey = Key.ofInt("c1", 10); Key startClusteringKey = Key.of("c2", "aaa", "c3", 100L); Key endClusteringKey = Key.of("c2", "aaa", "c3", 300L); @@ -485,23 +576,22 @@ Scan scan = .limit(10) .build(); -// Execute the Scan operation +// Execute the `Scan` operation. List results = transaction.scan(scan); ``` -You can omit the clustering key boundaries, or you can specify either a start boundary or an end boundary. -If you don't specify orderings, you get results ordered by clustering order you defined when creating the table. +You can omit the clustering-key boundaries or specify either a `start` boundary or an `end` boundary. If you don't specify `orderings`, you will get results ordered by the clustering order that you defined when creating the table. -Also, you can specify projections to choose which columns are returned, and limit to specify the number of records to return in Scan operations. +In addition, you can specify `projections` to choose which columns are returned and use `limit` to specify the number of records to return in `Scan` operations. -##### Scan with a secondary index +##### Execute `Scan` by using a secondary index -You can also execute a Scan operation with a secondary index. +You can execute a `Scan` operation by using a secondary index. -Instead of specifying a partition key, you can specify an index key (specifying an indexed column) to use a secondary index as follows: +Instead of specifying a partition key, you can specify an index key (indexed column) to use a secondary index as follows: ```java -// Create a Scan operation with a secondary index +// Create a `Scan` operation by using a secondary index. Key indexKey = Key.ofFloat("c4", 1.23F); Scan scan = @@ -513,20 +603,26 @@ Scan scan = .limit(10) .build(); -// Execute the Scan operation +// Execute the `Scan` operation. List results = transaction.scan(scan); ``` -Note that you can't specify clustering key boundaries and orderings in Scan with a secondary index. +{% capture notice--info %} +**Note** + +You can't specify clustering-key boundaries and orderings in `Scan` by using a secondary index. +{% endcapture %} -##### Scan without a partition key to retrieve all the records of a table +
{{ notice--info | markdownify }}
-You can also execute a Scan operation without specifying a partition key. +##### Execute `Scan` without specifying a partition key to retrieve all the records of a table + +You can execute a `Scan` operation without specifying a partition key. Instead of calling the `partitionKey()` method in the builder, you can call the `all()` method to scan a table without specifying a partition key as follows: ```java -// Create a Scan operation without a partition key +// Create a `Scan` operation without specifying a partition key. Scan scan = Scan.newBuilder() .namespace("ns") @@ -536,22 +632,34 @@ Scan scan = .limit(10) .build(); -// Execute the Scan operation +// Execute the `Scan` operation. List results = transaction.scan(scan); ``` -Note that you can't specify clustering key boundaries and orderings in Scan without a partition key. +{% capture notice--info %} +**Note** + +You can't specify clustering-key boundaries and orderings in `Scan` without specifying a partition key. +{% endcapture %} + +
{{ notice--info | markdownify }}
+ +#### `Put` operation -#### Put operation +`Put` is an operation to put a record specified by a primary key. The operation behaves as an upsert operation for a record, in which the operation updates the record if the record exists or inserts the record if the record does not exist. -`Put` is an operation to put a record specified by a primary key. -It behaves as an upsert operation for a record, i.e., updating the record if the record exists; otherwise, inserting the record. -Note that when you update an existing record, you need to read it using a `Get` or a `Scan` before a `Put` operation. +{% capture notice--info %} +**Note** -You need to create a Put object first, and then you can execute it with the `transaction.put()` method as follows: +When you update an existing record, you need to read the record by using `Get` or `Scan` before using a `Put` operation. +{% endcapture %} + +
{{ notice--info | markdownify }}
+ +You need to create a `Put` object first, and then you can execute the object by using the `transaction.put()` method as follows: ```java -// Create a Put operation +// Create a `Put` operation. Key partitionKey = Key.ofInt("c1", 10); Key clusteringKey = Key.of("c2", "aaa", "c3", 100L); @@ -565,11 +673,11 @@ Put put = .doubleValue("c5", 4.56) .build(); -// Execute the Put operation +// Execute the `Put` operation. transaction.put(put); ``` -You can also put a record with null values as follows: +You can also put a record with `null` values as follows: ```java Put put = @@ -583,15 +691,22 @@ Put put = .build(); ``` -#### Delete operation +#### `Delete` operation `Delete` is an operation to delete a record specified by a primary key. -Note that when you delete a record, you need to read it using a `Get` or a `Scan` before a `Delete` operation. -You need to create a Delete object first, and then you can execute it with the `transaction.delete()` method as follows: +{% capture notice--info %} +**Note** + +When you delete a record, you need to read the record by using `Get` or `Scan` before using a `Delete` operation. +{% endcapture %} + +
{{ notice--info | markdownify }}
+ +You need to create a `Delete` object first, and then you can execute the object by using the `transaction.delete()` method as follows: ```java -// Create a Delete operation +// Create a `Delete` operation. Key partitionKey = Key.ofInt("c1", 10); Key clusteringKey = Key.of("c2", "aaa", "c3", 100L); @@ -603,18 +718,91 @@ Delete delete = .clusteringKey(clusteringKey) .build(); -// Execute the Delete operation +// Execute the `Delete` operation. +transaction.delete(delete); +``` + +#### `Put` and `Delete` with a condition + +You can write arbitrary conditions (for example, a bank account balance must be equal to or more than zero) that you require a transaction to meet before being committed by implementing logic that checks the conditions in the transaction. Alternatively, you can write simple conditions in a mutation operation, such as `Put` and `Delete`. + +When a `Put` or `Delete` operation includes a condition, the operation is executed only if the specified condition is met. If the condition is not met when the operation is executed, an exception called `UnsatisfiedConditionException` will be thrown. + +##### Conditions for `Put` + +You can specify a condition in a `Put` operation as follows: + +```java +// Build a condition. +MutationCondition condition = + ConditionBuilder.putIf(ConditionBuilder.column("c4").isEqualToFloat(0.0F)) + .and(ConditionBuilder.column("c5").isEqualToDouble(0.0)) + .build(); + +Put put = + Put.newBuilder() + .namespace("ns") + .table("tbl") + .partitionKey(partitionKey) + .clusteringKey(clusteringKey) + .floatValue("c4", 1.23F) + .doubleValue("c5", 4.56) + .condition(condition) // condition + .build(); + +// Execute the `Put` operation. +transaction.put(put); +``` + +In addition to using the `putIf` condition, you can specify the `putIfExists` and `putIfNotExists` conditions as follows: + +```java +// Build a `putIfExists` condition. +MutationCondition putIfExistsCondition = ConditionBuilder.putIfExists(); + +// Build a `putIfNotExists` condition. +MutationCondition putIfNotExistsCondition = ConditionBuilder.putIfNotExists(); +``` + +##### Conditions for `Delete` + +You can specify a condition in a `Delete` operation as follows: + +```java +// Build a condition. +MutationCondition condition = + ConditionBuilder.deleteIf(ConditionBuilder.column("c4").isEqualToFloat(0.0F)) + .and(ConditionBuilder.column("c5").isEqualToDouble(0.0)) + .build(); + +Delete delete = + Delete.newBuilder() + .namespace("ns") + .table("tbl") + .partitionKey(partitionKey) + .clusteringKey(clusteringKey) + .condition(condition) // condition + .build(); + +// Execute the `Delete` operation. transaction.delete(delete); ``` +In addition to using the `deleteIf` condition, you can specify the `deleteIfExists` condition as follows: + +```java +// Build a `deleteIfExists` condition. +MutationCondition deleteIfExistsCondition = ConditionBuilder.deleteIfExists(); +``` + #### Mutate operation -Mutate is an operation to execute multiple mutations (Put and Delete operations). +Mutate is an operation to execute multiple mutations (`Put` and `Delete` operations). -You need to create mutation objects first, and then you can execute them with the `transaction.mutate()` method as follows: +You need to create mutation objects first, and then you can execute the objects by using the `transaction.mutate()` method as follows: ```java -// Create Put and Delete operations +// Create `Put` and `Delete` operations. Key partitionKey = Key.ofInt("c1", 10); Key clusteringKeyForPut = Key.of("c2", "aaa", "c3", 100L); @@ -639,29 +827,28 @@ Delete delete = .clusteringKey(clusteringKeyForDelete) .build(); -// Execute the operations +// Execute the operations. transaction.mutate(Arrays.asList(put, delete)); ``` -#### Use a default namespace for CRUD operations +#### Default namespace for CRUD operations -A default namespace for all the CRUD operations can be set with a property of the ScalarDB configuration. -If you would like to use this setting with ScalarDB server, it needs to be set on the client-side configuration. +A default namespace for all CRUD operations can be set by using a property in the ScalarDB configuration. ```properties -scalar.db.default_namespace_name= +scalar.db.default_namespace_name= ``` Any operation that does not specify a namespace will use the default namespace set in the configuration. ```java -//This operation will target the default namespace +// This operation will target the default namespace. Scan scanUsingDefaultNamespace = Scan.newBuilder() .table("tbl") .all() .build(); -//This operation will target the "ns" namespace +// This operation will target the "ns" namespace. Scan scanUsingSpecifiedNamespace = Scan.newBuilder() .namespace("ns") @@ -670,53 +857,55 @@ Scan scanUsingSpecifiedNamespace = .build(); ``` -#### Notes - -- All the builders of the CRUD operations can specify consistency with the `consistency()` methods, but it's ignored, and the `LINEARIZABLE` consistency level is always used in transactions. -- Also, the builders of the mutation operations (Put and Delete operations) can specify a condition with the `condition()` methods, but it's ignored, too. -Please program such conditions in a transaction if you want to implement conditional mutation. - ### Commit a transaction After executing CRUD operations, you need to commit a transaction to finish it. -You can commit a transaction as follows; +You can commit a transaction as follows: ```java -// Commit a transaction +// Commit a transaction. transaction.commit(); ``` -### Rollback/Abort a transaction +### Roll back or abort a transaction -If you want to rollback/abort a transaction or an error happens during the execution, you can rollback/abort a transaction. +If an error occurs when executing a transaction, you can roll back or abort the transaction. -You can rollback/abort a transaction as follows; +You can roll back a transaction as follows: ```java -// Rollback a transaction +// Roll back a transaction. transaction.rollback(); +``` -Or +Or, you can abort a transaction as follows: -// Abort a transaction +```java +// Abort a transaction. transaction.abort(); ``` -Please see [Handle exceptions](#handle-exceptions) for the details of how to handle exceptions in ScalarDB. +For details about how to handle exceptions in ScalarDB, see [How to handle exceptions](#how-to-handle-exceptions). + +## How to handle exceptions -## Handle exceptions +When executing a transaction, you will also need to handle exceptions properly. -Handling exceptions correctly in ScalarDB is very important. -If you mishandle exceptions, your data could become inconsistent. -This document explains how to handle exceptions properly in ScalarDB. +{% capture notice--warning %} +**Attention** -Let's look at the following example code to see how to handle exceptions in ScalarDB. +If you don't handle exceptions properly, you may face anomalies or data inconsistency. +{% endcapture %} + +
{{ notice--warning | markdownify }}
+ +The following sample code shows how to handle exceptions: ```java public class Sample { public static void main(String[] args) throws Exception { - TransactionFactory factory = TransactionFactory.create(""); + TransactionFactory factory = TransactionFactory.create(""); DistributedTransactionManager transactionManager = factory.getTransactionManager(); int retryCount = 0; @@ -724,51 +913,63 @@ public class Sample { while (true) { if (retryCount++ > 0) { - // Retry the transaction three times maximum in this sample code + // Retry the transaction three times maximum. if (retryCount >= 3) { - // Throw the last exception if the number of retries exceeds the maximum + // Throw the last exception if the number of retries exceeds the maximum. throw lastException; } - // Sleep 100 milliseconds before retrying the transaction in this sample code + // Sleep 100 milliseconds before retrying the transaction. TimeUnit.MILLISECONDS.sleep(100); } DistributedTransaction transaction = null; try { - // Begin a transaction + // Begin a transaction. transaction = transactionManager.begin(); - // Execute CRUD operations in the transaction + // Execute CRUD operations in the transaction. Optional result = transaction.get(...); List results = transaction.scan(...); transaction.put(...); transaction.delete(...); - // Commit the transaction + // Commit the transaction. transaction.commit(); + } catch (UnsatisfiedConditionException e) { + // You need to handle `UnsatisfiedConditionException` only if a mutation operation specifies a condition. + // This exception indicates the condition for the mutation operation is not met. + + try { + transaction.rollback(); + } catch (RollbackException ex) { + // Rolling back the transaction failed. Since the transaction should eventually recover, + // you don't need to do anything further. You can simply log the occurrence here. + } + + // You can handle the exception here, according to your application requirements. + + return; } catch (UnknownTransactionStatusException e) { - // If you catch `UnknownTransactionStatusException` when committing the transaction, it - // indicates that the status of the transaction, whether it has succeeded or not, is - // unknown. In such a case, you need to check if the transaction is committed successfully - // or not and retry it if it failed. How to identify a transaction status is delegated to - // users + // If you catch `UnknownTransactionStatusException` when committing the transaction, + // it indicates that the status of the transaction, whether it was successful or not, is unknown. + // In such a case, you need to check if the transaction is committed successfully or not and + // retry the transaction if it failed. How to identify a transaction status is delegated to users. return; } catch (TransactionException e) { // For other exceptions, you can try retrying the transaction. - // For `CrudConflictException` and `CommitConflictException` and - // `TransactionNotFoundException`, you can basically retry the transaction. However, for the - // other exceptions, the transaction may still fail if the cause of the exception is - // nontransient. In such a case, you will exhaust the number of retries and throw the last - // exception + // For `CrudConflictException`, `CommitConflictException`, and `TransactionNotFoundException`, + // you can basically retry the transaction. However, for the other exceptions, the transaction + // will still fail if the cause of the exception is non-transient. In such a case, you will + // exhaust the number of retries and throw the last exception. if (transaction != null) { try { transaction.rollback(); } catch (RollbackException ex) { - // Rolling back the transaction failed. As the transaction should eventually recover, - // you don't need to do anything further. You can simply log the occurrence here + // Rolling back the transaction failed. The transaction should eventually recover, + // so you don't need to do anything further. You can simply log the occurrence here. } } @@ -779,55 +980,59 @@ public class Sample { } ``` -The `begin()` API could throw `TransactionException` or `TransactionNotFoundException`. -If you catch `TransactionException`, it indicates that the transaction has failed to begin due to transient or nontransient faults. You can try retrying the transaction, but you may not be able to begin the transaction due to nontransient faults. -If you catch `TransactionNotFoundException`, it indicates that the transaction has failed to begin due to transient faults. You can retry the transaction. +### `TransactionException` and `TransactionNotFoundException` + +The `begin()` API could throw `TransactionException` or `TransactionNotFoundException`: + +- If you catch `TransactionException`, this exception indicates that the transaction has failed to begin due to transient or non-transient faults. You can try retrying the transaction, but you may not be able to begin the transaction due to non-transient faults. +- If you catch `TransactionNotFoundException`, this exception indicates that the transaction has failed to begin due to transient faults. In this case, you can retry the transaction. + +The `join()` API could also throw `TransactionNotFoundException`. You can handle this exception in the same way that you handle the exceptions for the `begin()` API. + +### `CrudException` and `CrudConflictException` + +The APIs for CRUD operations (`get()`, `scan()`, `put()`, `delete()`, and `mutate()`) could throw `CrudException` or `CrudConflictException`: + +- If you catch `CrudException`, this exception indicates that the transaction CRUD operation has failed due to transient or non-transient faults. You can try retrying the transaction from the beginning, but the transaction may still fail if the cause is non-transient. +- If you catch `CrudConflictException`, this exception indicates that the transaction CRUD operation has failed due to transient faults (for example, a conflict error). In this case, you can retry the transaction from the beginning. + +### `UnsatisfiedConditionException` -The APIs for CRUD operations (`get()`, `scan()`, `put()`, `delete()`, and `mutate()`) could throw `CrudException` or `CrudConflictException`. -If you catch `CrudException`, it indicates that the transaction CRUD operation has failed due to transient or nontransient faults. You can try retrying the transaction from the beginning, but the transaction may still fail if the cause is nontransient. -If you catch `CrudConflictException`, it indicates that the transaction CRUD operation has failed due to transient faults (e.g., a conflict error). You can retry the transaction from the beginning. +The APIs for mutation operations (`put()`, `delete()`, and `mutate()`) could also throw `UnsatisfiedConditionException`. -Also, the `commit()` API could throw `CommitException`, `CommitConflictException`, or `UnknownTransactionStatusException`. -If you catch `CommitException`, it indicates that committing the transaction fails due to transient or nontransient faults. You can try retrying the transaction from the beginning, but the transaction may still fail if the cause is nontransient. -If you catch `CommitConflictException`, it indicates that committing the transaction has failed due to transient faults (e.g., a conflict error). You can retry the transaction from the beginning. -If you catch `UnknownTransactionStatusException`, it indicates that the status of the transaction, whether it has succeeded or not, is unknown. -In such a case, you need to check if the transaction is committed successfully and retry the transaction if it has failed. -How to identify a transaction status is delegated to users. -You may want to create a transaction status table and update it transactionally with other application data so that you can get the status of a transaction from the status table. +If you catch `UnsatisfiedConditionException`, this exception indicates that the condition for the mutation operation is not met. You can handle this exception according to your application requirements. -Although not illustrated in the sample code, the `resume()` API could also throw `TransactionNotFoundException`. -This exception indicates that the transaction associated with the specified ID was not found and/or the transaction might have expired. -In either case, you can retry the transaction from the beginning since the cause of this exception is basically transient. +### `CommitException`, `CommitConflictException`, and `UnknownTransactionStatusException` -In the sample code, for `UnknownTransactionStatusException`, the transaction is not retried because the cause of the exception is nontransient. -For other exceptions, the transaction is retried because the cause of the exception is transient or nontransient. -If the cause of the exception is transient, the transaction may succeed if you retry it. -However, if the cause of the exception is nontransient, the transaction may still fail even if you retry it. -In such a case, you will exhaust the number of retries. +The `commit()` API could throw `CommitException`, `CommitConflictException`, or `UnknownTransactionStatusException`: -Please note that if you begin a transaction by specifying a transaction ID, you must use a different ID when you retry the transaction. -And, in the sample code, the transaction is retried three times maximum and sleeps for 100 milliseconds before it is retried. -But you can choose a retry policy, such as exponential backoff, according to your application requirements. +- If you catch `CommitException`, this exception indicates that committing the transaction fails due to transient or non-transient faults. You can try retrying the transaction from the beginning, but the transaction may still fail if the cause is non-transient. +- If you catch `CommitConflictException`, this exception indicates that committing the transaction has failed due to transient faults (for example, a conflict error). In this case, you can retry the transaction from the beginning. +- If you catch `UnknownTransactionStatusException`, this exception indicates that the status of the transaction, whether it was successful or not, is unknown. In this case, you need to check if the transaction is committed successfully and retry the transaction if it has failed. -## Transactional operations for Two-phase Commit Transaction +How to identify a transaction status is delegated to users. You may want to create a transaction status table and update it transactionally with other application data so that you can get the status of a transaction from the status table. -Please see [Two-phase Commit Transactions](two-phase-commit-transactions.md). +### Notes about some exceptions -## Investigate Consensus Commit transactions errors +Although not illustrated in the sample code, the `resume()` API could also throw `TransactionNotFoundException`. This exception indicates that the transaction associated with the specified ID was not found and/or the transaction might have expired. In either case, you can retry the transaction from the beginning since the cause of this exception is basically transient. -This configuration is only available to troubleshoot Consensus Commit transactions. By adding the following configuration, `Get` and `Scan` operations results will contain [transaction metadata](schema-loader.md#internal-metadata-for-consensus-commit). -To see the transaction metadata columns details for a given table, you can use the `DistributedTransactionAdmin.getTableMetadata()` method which will return the table metadata augmented with the transaction metadata columns. -All in all, using this configuration can be useful to investigate transaction related issues. +In the sample code, for `UnknownTransactionStatusException`, the transaction is not retried because the application must check if the transaction was successful to avoid potential duplicate operations. For other exceptions, the transaction is retried because the cause of the exception is transient or non-transient. If the cause of the exception is transient, the transaction may succeed if you retry it. However, if the cause of the exception is non-transient, the transaction will still fail even if you retry it. In such a case, you will exhaust the number of retries. + +{% capture notice--info %} +**Note** + +In the sample code, the transaction is retried three times maximum and sleeps for 100 milliseconds before it is retried. But you can choose a retry policy, such as exponential backoff, according to your application requirements. +{% endcapture %} + +
{{ notice--info | markdownify }}
+ +## Investigating Consensus Commit transaction manager errors + +To investigate errors when using the Consensus Commit transaction manager, you can enable a configuration that will return table metadata augmented with transaction metadata columns, which can be helpful when investigating transaction-related issues. This configuration, which is only available when troubleshooting the Consensus Commit transaction manager, enables you to see transaction metadata column details for a given table by using the `DistributedTransactionAdmin.getTableMetadata()` method. + +By adding the following configuration, `Get` and `Scan` operations results will contain [transaction metadata](schema-loader.md#internal-metadata-for-consensus-commit): ```properties -# By default, it is set to "false". +# By default, this configuration is set to `false`. scalar.db.consensus_commit.include_metadata.enabled=true ``` - -## References - -* [Design document](design.md) -* [Getting started](getting-started-with-scalardb.md) -* [Multi-storage Transactions](multi-storage-transactions.md) -* [Two-phase Commit Transactions](two-phase-commit-transactions.md) -* [ScalarDB Server](scalardb-server.md) diff --git a/docs/latest/api-guide.md b/docs/latest/api-guide.md index 6f8d97f..ba32ddb 100644 --- a/docs/latest/api-guide.md +++ b/docs/latest/api-guide.md @@ -1,29 +1,33 @@ -# Java API Guide +# ScalarDB Java API Guide -ScalarDB Java API is mainly composed of Administrative API and Transactional API. -This guide briefly explains what kind of APIs exist and how to use them. - -* [Administrative API](#administrative-api) -* [Transactional API](#transactional-api) +The ScalarDB Java API is mainly composed of the Administrative API and Transactional API. This guide briefly explains what kinds of APIs exist, how to use them, and related topics like how to handle exceptions. ## Administrative API -This section explains how to execute administrative operations with Administrative API in ScalarDB. -You can execute administrative operations programmatically as follows, but you can also execute those operations through [Schema Loader](schema-loader.md). +This section explains how to execute administrative operations programmatically by using the Administrative API in ScalarDB. + +{% capture notice--info %} +**Note** + +Another method for executing administrative operations is to use [Schema Loader](schema-loader.md). +{% endcapture %} -### Get a DistributedTransactionAdmin instance +
{{ notice--info | markdownify }}
-To execute administrative operations, you first need to get a `DistributedTransactionAdmin` instance. -The `DistributedTransactionAdmin` instance can be obtained from `TransactionFactory` as follows: +### Get a `DistributedTransactionAdmin` instance + +You first need to get a `DistributedTransactionAdmin` instance to execute administrative operations. + +To get a `DistributedTransactionAdmin` instance, you can use `TransactionFactory` as follows: ```java -TransactionFactory transactionFactory = TransactionFactory.create(""); +TransactionFactory transactionFactory = TransactionFactory.create(""); DistributedTransactionAdmin admin = transactionFactory.getTransactionAdmin(); ``` For details about configurations, see [ScalarDB Configurations](configurations.md). -Once you have executed all administrative operations, you should close the `DistributedTransactionAdmin` instance as follows: +After you have executed all administrative operations, you should close the `DistributedTransactionAdmin` instance as follows: ```java admin.close(); @@ -32,60 +36,77 @@ admin.close(); ### Create a namespace Before creating tables, namespaces must be created since a table belongs to one namespace. + You can create a namespace as follows: ```java -// Create a namespace "ns". It will throw an exception if the namespace already exists +// Create the namespace "ns". If the namespace already exists, an exception will be thrown. admin.createNamespace("ns"); -// Create a namespace only if it does not already exist +// Create the namespace only if it does not already exist. boolean ifNotExists = true; admin.createNamespace("ns", ifNotExists); -// Create a namespace with options +// Create the namespace with options. Map options = ...; admin.createNamespace("ns", options); ``` -#### Creation Options +#### Creation options -In the creation operations (creating a namespace, creating a table, etc.), you can specify options that are maps of option names and values (`Map`). -With the options, we can set storage adapter specific configurations. +In the creation operations, like creating a namespace and creating a table, you can specify options that are maps of option names and values (`Map`). By using the options, you can set storage adapter–specific configurations. -Currently, we can set the following options for the storage adapters: +Select your database to see the options available: -For Cosmos DB for NoSQL: +
+
+ + + + +
-| name | value | default | -|------------|----------------------------------------------|---------| -| ru | Base resource unit | 400 | -| no-scaling | Disable auto-scaling for Cosmos DB for NoSQL | false | +
-For DynamoDB: +| Name | Description | Default | +|----------------------|----------------------------------------------------------------------------------------|------------------| +| replication-strategy | Cassandra replication strategy. Must be `SimpleStrategy` or `NetworkTopologyStrategy`. | `SimpleStrategy` | +| compaction-strategy | Cassandra compaction strategy, Must be `LCS`, `STCS` or `TWCS`. | `STCS` | +| replication-factor | Cassandra replication factor. | 1 | -| name | value | default | -|------------|----------------------------------------|---------| -| no-scaling | Disable auto-scaling for DynamoDB | false | -| no-backup | Disable continuous backup for DynamoDB | false | -| ru | Base resource unit | 10 | +
+
-For Cassandra: +| Name | Description | Default | +|------------|-----------------------------------------------------|---------| +| ru | Base resource unit. | 400 | +| no-scaling | Disable auto-scaling for Cosmos DB for NoSQL. | false | -| name | value | default | -|----------------------|---------------------------------------------------------------------------------------|------------------| -| replication-strategy | Cassandra replication strategy, must be `SimpleStrategy` or `NetworkTopologyStrategy` | `SimpleStrategy` | -| compaction-strategy | Cassandra compaction strategy, must be `LCS`, `STCS` or `TWCS` | `STCS` | -| replication-factor | Cassandra replication factor | 1 | +
+
+| Name | Description | Default | +|------------|-----------------------------------------|---------| +| no-scaling | Disable auto-scaling for DynamoDB. | false | +| no-backup | Disable continuous backup for DynamoDB. | false | +| ru | Base resource unit. | 10 | + +
+
+ +No options are available for JDBC databases. + +
+
### Create a table -Next, we will discuss table creation. +When creating a table, you should define the table metadata and then create the table. -You firstly need to create the TaleMetadata as follows: +To define the table metadata, you can use `TableMetadata`. The following shows how to define the columns, partition key, clustering key including clustering orders, and secondary indexes of a table: ```java -// Define a table metadata +// Define the table metadata. TableMetadata tableMetadata = TableMetadata.newBuilder() .addColumn("c1", DataType.INT) @@ -100,21 +121,19 @@ TableMetadata tableMetadata = .build(); ``` -Here you define columns, a partition key, a clustering key including clustering orders, and secondary indexes of a table. +For details about the data model of ScalarDB, see [Data Model](design.md#data-model). -Please see [ScalarDB design document - Data Model](design.md#data-model) for the details of the ScalarDB Data Model. - -And then, you can create a table as follows: +Then, create a table as follows: ```java -// Create a table "ns.tbl". It will throw an exception if the table already exists +// Create the table "ns.tbl". If the table already exists, an exception will be thrown. admin.createTable("ns", "tbl", tableMetadata); -// Create a table only if it does not already exist +// Create the table only if it does not already exist. boolean ifNotExists = true; admin.createTable("ns", "tbl", tableMetadata, ifNotExists); -// Create a table with options +// Create the table with options. Map options = ...; admin.createTable("ns", "tbl", tableMetadata, options); ``` @@ -124,43 +143,45 @@ admin.createTable("ns", "tbl", tableMetadata, options); You can create a secondary index as follows: ```java -// Create a secondary index on a column "c5" of a table "ns.tbl". It will throw an exception if the secondary index already exists +// Create a secondary index on column "c5" for table "ns.tbl". If a secondary index already exists, an exception will be thrown. admin.createIndex("ns", "tbl", "c5"); -// Create a secondary index only if it does not already exist +// Create the secondary index only if it does not already exist. boolean ifNotExists = true; admin.createIndex("ns", "tbl", "c5", ifNotExists); -// Create a secondary index with options +// Create the secondary index with options. Map options = ...; admin.createIndex("ns", "tbl", "c5", options); ``` ### Add a new column to a table -You can add a new non-partition key column to a table as follows: +You can add a new, non-partition key column to a table as follows: + ```java -// Add the new column "c6" of type INT to the table "ns.tbl" +// Add a new column "c6" with the INT data type to the table "ns.tbl". admin.addNewColumnToTable("ns", "tbl", "c6", DataType.INT) ``` -This should be executed with significant consideration as the execution time may vary greatly -depending on the underlying storage. Please plan accordingly especially if the database runs in production: -- For Cosmos DB for NoSQL and DynamoDB: this operation is almost instantaneous as the table - schema is not modified. Only the table metadata stored in a separated table are updated. -- For Cassandra: adding a column will only update the schema metadata and do not modify existing - schema records. The cluster topology is the main factor for the execution time. Since the schema - metadata change propagates to each cluster node via a gossip protocol, the larger the cluster, the - longer it will take for all nodes to be updated. -- For relational databases (MySQL, Oracle, etc.): it may take a very long time to execute and a - table-lock may be performed. +{% capture notice--warning %} +**Attention** + +You should carefully consider adding a new column to a table because the execution time may vary greatly depending on the underlying storage. Please plan accordingly and consider the following, especially if the database runs in production: + +- **For Cosmos DB for NoSQL and DynamoDB:** Adding a column is almost instantaneous as the table schema is not modified. Only the table metadata stored in a separate table is updated. +- **For Cassandra:** Adding a column will only update the schema metadata and will not modify the existing schema records. The cluster topology is the main factor for the execution time. Changes to the schema metadata are shared to each cluster node via a gossip protocol. Because of this, the larger the cluster, the longer it will take for all nodes to be updated. +- **For relational databases (MySQL, Oracle, etc.):** Adding a column shouldn't take a long time to execute. +{% endcapture %} + +
{{ notice--warning | markdownify }}
### Truncate a table You can truncate a table as follows: ```java -// Truncate a table "ns.tbl" +// Truncate the table "ns.tbl". admin.truncateTable("ns", "tbl"); ``` @@ -169,10 +190,10 @@ admin.truncateTable("ns", "tbl"); You can drop a secondary index as follows: ```java -// Drop a secondary index on a column "c5" of a table "ns.tbl". It will throw an exception if the secondary index does not exist +// Drop the secondary index on column "c5" from table "ns.tbl". If the secondary index does not exist, an exception will be thrown. admin.dropIndex("ns", "tbl", "c5"); -// Drop a secondary index only if it exists +// Drop the secondary index only if it exists. boolean ifExists = true; admin.dropIndex("ns", "tbl", "c5", ifExists); ``` @@ -182,10 +203,10 @@ admin.dropIndex("ns", "tbl", "c5", ifExists); You can drop a table as follows: ```java -// Drop a table "ns.tbl". It will throw an exception if the table does not exist +// Drop the table "ns.tbl". If the table does not exist, an exception will be thrown. admin.dropTable("ns", "tbl"); -// Drop a table only if it exists +// Drop the table only if it exists. boolean ifExists = true; admin.dropTable("ns", "tbl", ifExists); ``` @@ -195,181 +216,232 @@ admin.dropTable("ns", "tbl", ifExists); You can drop a namespace as follows: ```java -// Drop a namespace "ns". It will throw an exception if the namespace does not exist +// Drop the namespace "ns". If the namespace does not exist, an exception will be thrown. admin.dropNamespace("ns"); -// Drop a namespace only if it exists +// Drop the namespace only if it exists. boolean ifExists = true; admin.dropNamespace("ns", ifExists); ``` -### Get a table metadata +### Get the tables of a namespace -You can get a table metadata as follows: +You can get the tables of a namespace as follows: ```java -// Get a table metadata of "ns.tbl" +// Get the tables of the namespace "ns". +Set tables = admin.getNamespaceTableNames("ns"); +``` + +### Get table metadata + +You can get table metadata as follows: + +```java +// Get the table metadata for "ns.tbl". TableMetadata tableMetadata = admin.getTableMetadata("ns", "tbl"); ``` -### Operations for Coordinator tables +### Specify operations for the Coordinator table + +The Coordinator table is used by the [Transactional API](#transactional-api) to track the statuses of transactions. -Depending on the transaction manager type, you need to create coordinator tables to execute transactions. -The following items describe the operations for the coordinator table. +When using a transaction manager, you must create the Coordinator table to execute transactions. In addition to creating the table, you can truncate and drop the Coordinator table. -#### Create Coordinator tables +#### Create the Coordinator table -You can create coordinator tables as follows: +You can create the Coordinator table as follows: ```java -// Create coordinator tables +// Create the Coordinator table. admin.createCoordinatorTables(); -// Create coordinator tables only if they do not already exist +// Create the Coordinator table only if one does not already exist. boolean ifNotExist = true; admin.createCoordinatorTables(ifNotExist); -// Create coordinator tables with options +// Create the Coordinator table with options. Map options = ...; admin.createCoordinatorTables(options); ``` -#### Truncate Coordinator tables +#### Truncate the Coordinator table -You can truncate coordinator tables as follows: +You can truncate the Coordinator table as follows: ```java -// Truncate coordinator tables +// Truncate the Coordinator table. admin.truncateCoordinatorTables(); ``` -#### Drop Coordinator tables +#### Drop the Coordinator table -You can drop coordinator tables as follows: +You can drop the Coordinator table as follows: ```java -// Drop coordinator tables +// Drop the Coordinator table. admin.dropCoordinatorTables(); -// Drop coordinator tables if they exist +// Drop the Coordinator table if one exist. boolean ifExist = true; admin.dropCoordinatorTables(ifExist); ``` ## Transactional API -This section explains how to execute transactional operations with Transactional API in ScalarDB. +This section explains how to execute transactional operations by using the Transactional API in ScalarDB. -### Get a DistributedTransactionManager instance +### Get a `DistributedTransactionManager` instance -You need to get a `DistributedTransactionManager` instance to execute transactional operations. -You can get it in the following way: +You first need to get a `DistributedTransactionManager` instance to execute transactional operations. + +To get a `DistributedTransactionManager` instance, you can use `TransactionFactory` as follows: ```java -TransactionFactory transactionFactory = TransactionFactory.create(""); +TransactionFactory transactionFactory = TransactionFactory.create(""); DistributedTransactionManager transactionManager = transactionFactory.getTransactionManager(); ``` -Once you have executed all transactional operations, you should close the `DistributedTransactionManager` instance as follows: +After you have executed all transactional operations, you should close the `DistributedTransactionManager` instance as follows: ```java transactionManager.close(); ``` -### Begin/Start a transaction +### Begin or start a transaction + +Before executing transactional CRUD operations, you need to begin or start a transaction. -You need to begin/start a transaction before executing transactional CRUD operations. -You can begin/start a transaction as follows: +You can begin a transaction as follows: ```java -// Begin a transaction +// Begin a transaction. DistributedTransaction transaction = transactionManager.begin(); +``` -Or +Or, you can start a transaction as follows: -// Start a transaction +```java +// Start a transaction. DistributedTransaction transaction = transactionManager.start(); ``` -You can also begin/start a transaction with specifying a transaction ID as follows: +Alternatively, you can use the `begin` method for a transaction by specifying a transaction ID as follows: ```java -// Begin a transaction with specifying a transaction ID -DistributedTransaction transaction = transactionManager.begin(""); +// Begin a transaction with specifying a transaction ID. +DistributedTransaction transaction = transactionManager.begin(""); +``` -Or +Or, you can use the `start` method for a transaction by specifying a transaction ID as follows: -// Start a transaction with specifying a transaction ID -DistributedTransaction transaction = transactionManager.start(""); +```java +// Start a transaction with specifying a transaction ID. +DistributedTransaction transaction = transactionManager.start(""); ``` -Note that you must guarantee uniqueness of the transaction ID in this case. +{% capture notice--info %} +**Note** + +Specifying a transaction ID is useful when you want to link external systems to ScalarDB. Otherwise, you should use the `begin()` method or the `start()` method. + +When you specify a transaction ID, make sure you specify a unique ID (for example, UUID v4) throughout the system since ScalarDB depends on the uniqueness of transaction IDs for correctness. +{% endcapture %} + +
{{ notice--info | markdownify }}
### Join a transaction -You can join an ongoing transaction that has already begun with specifying a transaction ID as follows: +Joining a transaction is particularly useful in a stateful application where a transaction spans multiple client requests. In such a scenario, the application can start a transaction during the first client request. Then, in subsequent client requests, the application can join the ongoing transaction by using the `join()` method. + +You can join an ongoing transaction that has already begun by specifying the transaction ID as follows: ```java -// Join a transaction -DistributedTransaction transaction = transactionManager.join(""); +// Join a transaction. +DistributedTransaction transaction = transactionManager.join(""); ``` -This is particularly useful in a stateful application where a transaction spans across multiple client requests. -In such a scenario, the application can start a transaction during the first client request. -Then, in the subsequent client requests, it can join the ongoing transaction using the `join()` method. +{% capture notice--info %} +**Note** + +To get the transaction ID with `getId()`, you can specify the following: + +```java +tx.getId(); +``` +{% endcapture %} + +
{{ notice--info | markdownify }}
### Resume a transaction -You can resume an ongoing transaction you have already begun with specifying a transaction ID as follows: +Resuming a transaction is particularly useful in a stateful application where a transaction spans multiple client requests. In such a scenario, the application can start a transaction during the first client request. Then, in subsequent client requests, the application can resume the ongoing transaction by using the `resume()` method. + +You can resume an ongoing transaction that you have already begun by specifying a transaction ID as follows: + +```java +// Resume a transaction. +DistributedTransaction transaction = transactionManager.resume(""); +``` + +{% capture notice--info %} +**Note** + +To get the transaction ID with `getId()`, you can specify the following: ```java -// Resume a transaction -DistributedTransaction transaction = transactionManager.resume(""); +tx.getId(); ``` +{% endcapture %} + +
{{ notice--info | markdownify }}
+ +### Implement CRUD operations -This is particularly useful in a stateful application where a transaction spans across multiple client requests. -In such a scenario, the application can start a transaction during the first client request. -Then, in the subsequent client requests, it can resume the ongoing transaction using the `resume()` method. +The following sections describe key construction and CRUD operations. -### CRUD operations +{% capture notice--info %} +**Note** + +Although all the builders of the CRUD operations can specify consistency by using the `consistency()` methods, those methods are ignored. Instead, the `LINEARIZABLE` consistency level is always used in transactions. +{% endcapture %} + +
{{ notice--info | markdownify }}
#### Key construction -Most CRUD operations need to specify `Key` objects (partition-key, clustering-key, etc.). -So, before moving on to CRUD operations, the following explains how to construct a `Key` object. +Most CRUD operations need to specify `Key` objects (partition-key, clustering-key, etc.). So, before moving on to CRUD operations, the following explains how to construct a `Key` object. -For a single column key, you can use the `Key.ofXXX()` methods (XXX is a type name) to construct it as follows: +For a single column key, you can use `Key.of()` methods to construct the key as follows: ```java -// for a key that consists of a single column of Int +// For a key that consists of a single column of INT. Key key1 = Key.ofInt("col1", 1); -// for a key that consists of a single column of BigInt +// For a key that consists of a single column of BIGINT. Key key2 = Key.ofBigInt("col1", 100L); -// for a key that consists of a single column of Double +// For a key that consists of a single column of DOUBLE. Key key3 = Key.ofDouble("col1", 1.3d); -// for a key that consists of a single column of Text +// For a key that consists of a single column of TEXT. Key key4 = Key.ofText("col1", "value"); ``` -For a key that consists of 2 - 5 columns, you can use the `Key.of()` methods to construct it as follows: +For a key that consists of two to five columns, you can use the `Key.of()` method to construct the key as follows. Similar to `ImmutableMap.of()` in Guava, you need to specify column names and values in turns: ```java -// for a key that consists of 2 - 5 columns +// For a key that consists of two to five columns. Key key1 = Key.of("col1", 1, "col2", 100L); Key key2 = Key.of("col1", 1, "col2", 100L, "col3", 1.3d); Key key3 = Key.of("col1", 1, "col2", 100L, "col3", 1.3d, "col4", "value"); Key key4 = Key.of("col1", 1, "col2", 100L, "col3", 1.3d, "col4", "value", "col5", false); ``` -Similar to `ImmutableMap.of()` in Guava, you need to specify column names and values in turns. - -For a key that consists of more than 5 columns, we can use the builder to construct it as follows: +For a key that consists of more than five columns, we can use the builder to construct the key as follows: ```java -// for a key that consists of more than 5 columns +// For a key that consists of more than five columns. Key key = Key.newBuilder() .addInt("col1", 1) .addBigInt("col2", 100L) @@ -380,14 +452,14 @@ Key key = Key.newBuilder() .build(); ``` -#### Get operation +#### `Get` operation `Get` is an operation to retrieve a single record specified by a primary key. -You need to create a Get object first, and then you can execute it with the `transaction.get()` method as follows: +You need to create a `Get` object first, and then you can execute the object by using the `transaction.get()` method as follows: ```java -// Create a Get operation +// Create a `Get` operation. Key partitionKey = Key.ofInt("c1", 10); Key clusteringKey = Key.of("c2", "aaa", "c3", 100L); @@ -400,62 +472,61 @@ Get get = .projections("c1", "c2", "c3", "c4") .build(); -// Execute the Get operation +// Execute the `Get` operation. Optional result = transaction.get(get); ``` You can also specify projections to choose which columns are returned. -##### Handle Result objects +##### Handle `Result` objects -The Get operation and Scan operation return `Result` objects. -So the following shows how to handle `Result` objects. +The `Get` operation and `Scan` operation return `Result` objects. The following shows how to handle `Result` objects. -You can get a column value of a result with `getXXX("")` methods (XXX is a type name) as follows: +You can get a column value of a result by using `get("")` methods as follows: ```java -// Get a Boolean value of a column -boolean booleanValue = result.getBoolean(""); +// Get the BOOLEAN value of a column. +boolean booleanValue = result.getBoolean(""); -// Get an Int value of a column -int intValue = result.getInt(""); +// Get the INT value of a column. +int intValue = result.getInt(""); -// Get a BigInt value of a column -long bigIntValue = result.getBigInt(""); +// Get the BIGINT value of a column. +long bigIntValue = result.getBigInt(""); -// Get a Float value of a column -float floatValue = result.getFloat(""); +// Get the FLOAT value of a column. +float floatValue = result.getFloat(""); -// Get a Double value of a column -double doubleValue = result.getDouble(""); +// Get the DOUBLE value of a column. +double doubleValue = result.getDouble(""); -// Get a Text value of a column -String textValue = result.getText(""); +// Get the TEXT value of a column. +String textValue = result.getText(""); -// Get a Blob value of a column (as a ByteBuffer) -ByteBuffer blobValue = result.getBlob(""); +// Get the BLOB value of a column as a `ByteBuffer`. +ByteBuffer blobValue = result.getBlob(""); -// Get a Blob value of a column as a byte array -byte[] blobValueAsBytes = result.getBlobAsBytes(""); +// Get the BLOB value of a column as a `byte` array. +byte[] blobValueAsBytes = result.getBlobAsBytes(""); ``` -And if you need to check if a value of a column is null, you can use the `isNull("")` method. +And if you need to check if a value of a column is null, you can use the `isNull("")` method. ``` java -// Check if a value of a column is null -boolean isNull = result.isNull(""); +// Check if a value of a column is null. +boolean isNull = result.isNull(""); ``` -Please see also [Javadoc of `Result`](https://javadoc.io/static/com.scalar-labs/scalardb/3.6.0/com/scalar/db/api/Result.html) for more details. +For more details, see the `Result` page in the [Javadoc](https://javadoc.io/doc/com.scalar-labs/scalardb/latest/index.html) of the version of ScalarDB that you're using. -##### Get with a secondary index +##### Execute `Get` by using a secondary index -You can also execute a Get operation with a secondary index. +You can execute a `Get` operation by using a secondary index. -Instead of specifying a partition key, you can specify an index key (specifying an indexed column) to use a secondary index as follows: +Instead of specifying a partition key, you can specify an index key (indexed column) to use a secondary index as follows: ```java -// Create a Get operation with a secondary index +// Create a `Get` operation by using a secondary index. Key indexKey = Key.ofFloat("c4", 1.23F); Get get = @@ -466,22 +537,27 @@ Get get = .projections("c1", "c2", "c3", "c4") .build(); -// Execute the Get operation +// Execute the `Get` operation. Optional result = transaction.get(get); ``` -Note that if the result has more than one record, the `transaction.get()` throws an exception. -If you want to handle multiple results, use [Scan with a secondary index](#scan-with-a-secondary-index). +{% capture notice--info %} +**Note** + +If the result has more than one record, `transaction.get()` will throw an exception. If you want to handle multiple results, see [Execute `Scan` by using a secondary index](#execute-scan-by-using-a-secondary-index). + +{% endcapture %} -#### Scan operation +
{{ notice--info | markdownify }}
-`Scan` is an operation to retrieve multiple records within a partition. -You can specify clustering key boundaries and orderings for clustering key columns in Scan operations. +#### `Scan` operation -You need to create a Scan object first, and then you can execute it with the `transaction.scan()` method as follows: +`Scan` is an operation to retrieve multiple records within a partition. You can specify clustering-key boundaries and orderings for clustering-key columns in `Scan` operations. + +You need to create a `Scan` object first, and then you can execute the object by using the `transaction.scan()` method as follows: ```java -// Create a Scan operation +// Create a `Scan` operation. Key partitionKey = Key.ofInt("c1", 10); Key startClusteringKey = Key.of("c2", "aaa", "c3", 100L); Key endClusteringKey = Key.of("c2", "aaa", "c3", 300L); @@ -498,23 +574,22 @@ Scan scan = .limit(10) .build(); -// Execute the Scan operation +// Execute the `Scan` operation. List results = transaction.scan(scan); ``` -You can omit the clustering key boundaries, or you can specify either a start boundary or an end boundary. -If you don't specify orderings, you get results ordered by clustering order you defined when creating the table. +You can omit the clustering-key boundaries or specify either a `start` boundary or an `end` boundary. If you don't specify `orderings`, you will get results ordered by the clustering order that you defined when creating the table. -Also, you can specify projections to choose which columns are returned, and limit to specify the number of records to return in Scan operations. +In addition, you can specify `projections` to choose which columns are returned and use `limit` to specify the number of records to return in `Scan` operations. -##### Scan with a secondary index +##### Execute `Scan` by using a secondary index -You can also execute a Scan operation with a secondary index. +You can execute a `Scan` operation by using a secondary index. -Instead of specifying a partition key, you can specify an index key (specifying an indexed column) to use a secondary index as follows: +Instead of specifying a partition key, you can specify an index key (indexed column) to use a secondary index as follows: ```java -// Create a Scan operation with a secondary index +// Create a `Scan` operation by using a secondary index. Key indexKey = Key.ofFloat("c4", 1.23F); Scan scan = @@ -526,20 +601,26 @@ Scan scan = .limit(10) .build(); -// Execute the Scan operation +// Execute the `Scan` operation. List results = transaction.scan(scan); ``` -Note that you can't specify clustering key boundaries and orderings in Scan with a secondary index. +{% capture notice--info %} +**Note** + +You can't specify clustering-key boundaries and orderings in `Scan` by using a secondary index. +{% endcapture %} + +
{{ notice--info | markdownify }}
-##### Scan without a partition key to retrieve all the records of a table +##### Execute `Scan` without specifying a partition key to retrieve all the records of a table -You can also execute a Scan operation without specifying a partition key. +You can execute a `Scan` operation without specifying a partition key. Instead of calling the `partitionKey()` method in the builder, you can call the `all()` method to scan a table without specifying a partition key as follows: ```java -// Create a Scan operation without a partition key +// Create a `Scan` operation without specifying a partition key. Scan scan = Scan.newBuilder() .namespace("ns") @@ -549,22 +630,34 @@ Scan scan = .limit(10) .build(); -// Execute the Scan operation +// Execute the `Scan` operation. List results = transaction.scan(scan); ``` -Note that you can't specify clustering key boundaries and orderings in Scan without a partition key. +{% capture notice--info %} +**Note** + +You can't specify clustering-key boundaries and orderings in `Scan` without specifying a partition key. +{% endcapture %} + +
{{ notice--info | markdownify }}
-#### Put operation +#### `Put` operation -`Put` is an operation to put a record specified by a primary key. -It behaves as an upsert operation for a record, i.e., updating the record if the record exists; otherwise, inserting the record. -Note that when you update an existing record, you need to read it using a `Get` or a `Scan` before a `Put` operation. +`Put` is an operation to put a record specified by a primary key. The operation behaves as an upsert operation for a record, in which the operation updates the record if the record exists or inserts the record if the record does not exist. -You need to create a Put object first, and then you can execute it with the `transaction.put()` method as follows: +{% capture notice--info %} +**Note** + +When you update an existing record, you need to read the record by using `Get` or `Scan` before using a `Put` operation. +{% endcapture %} + +
{{ notice--info | markdownify }}
+ +You need to create a `Put` object first, and then you can execute the object by using the `transaction.put()` method as follows: ```java -// Create a Put operation +// Create a `Put` operation. Key partitionKey = Key.ofInt("c1", 10); Key clusteringKey = Key.of("c2", "aaa", "c3", 100L); @@ -578,11 +671,11 @@ Put put = .doubleValue("c5", 4.56) .build(); -// Execute the Put operation +// Execute the `Put` operation. transaction.put(put); ``` -You can also put a record with null values as follows: +You can also put a record with `null` values as follows: ```java Put put = @@ -596,15 +689,22 @@ Put put = .build(); ``` -#### Delete operation +#### `Delete` operation `Delete` is an operation to delete a record specified by a primary key. -Note that when you delete a record, you need to read it using a `Get` or a `Scan` before a `Delete` operation. -You need to create a Delete object first, and then you can execute it with the `transaction.delete()` method as follows: +{% capture notice--info %} +**Note** + +When you delete a record, you need to read the record by using `Get` or `Scan` before using a `Delete` operation. +{% endcapture %} + +
{{ notice--info | markdownify }}
+ +You need to create a `Delete` object first, and then you can execute the object by using the `transaction.delete()` method as follows: ```java -// Create a Delete operation +// Create a `Delete` operation. Key partitionKey = Key.ofInt("c1", 10); Key clusteringKey = Key.of("c2", "aaa", "c3", 100L); @@ -616,22 +716,22 @@ Delete delete = .clusteringKey(clusteringKey) .build(); -// Execute the Delete operation +// Execute the `Delete` operation. transaction.delete(delete); ``` -#### Put and Delete with a condition -You can write arbitrary conditions (e.g., a bank account balance must be equal to or more than zero) that you require a transaction to meet before being committed by having logic that checks the conditions in the transaction. -Alternatively, you can write simple conditions in a mutation operation, such as Put and Delete. +#### `Put` and `Delete` with a condition + +You can write arbitrary conditions (for example, a bank account balance must be equal to or more than zero) that you require a transaction to meet before being committed by implementing logic that checks the conditions in the transaction. Alternatively, you can write simple conditions in a mutation operation, such as `Put` and `Delete`. + +When a `Put` or `Delete` operation includes a condition, the operation is executed only if the specified condition is met. If the condition is not met when the operation is executed, an exception called `UnsatisfiedConditionException` will be thrown. -When a Put or Delete operation includes a condition, the operation is executed only if the specified condition is met. -If the condition fails to be satisfied when the operation is executed, an exception called `UnsatisfiedConditionException` is thrown. +##### Conditions for `Put` -##### Conditions for Put -You can specify a condition in a Put operation as follows: +You can specify a condition in a `Put` operation as follows: ```java -// Build a condition +// Build a condition. MutationCondition condition = ConditionBuilder.putIf(ConditionBuilder.column("c4").isEqualToFloat(0.0F)) .and(ConditionBuilder.column("c5").isEqualToDouble(0.0)) @@ -648,26 +748,26 @@ Put put = .condition(condition) // condition .build(); -// Execute the Put operation +// Execute the `Put` operation. transaction.put(put); ``` In addition to using the `putIf` condition, you can specify the `putIfExists` and `putIfNotExists` conditions as follows: ```java -// Build a putIfExists condition +// Build a `putIfExists` condition. MutationCondition putIfExistsCondition = ConditionBuilder.putIfExists(); -// Build a putIfNotExists condition +// Build a `putIfNotExists` condition. MutationCondition putIfNotExistsCondition = ConditionBuilder.putIfNotExists(); ``` -##### Conditions for Delete +##### Conditions for `Delete` -You can specify a condition in a Delete operation as follows: +You can specify a condition in a `Delete` operation as follows: ```java -// Build a condition +// Build a condition. MutationCondition condition = ConditionBuilder.deleteIf(ConditionBuilder.column("c4").isEqualToFloat(0.0F)) .and(ConditionBuilder.column("c5").isEqualToDouble(0.0)) @@ -679,28 +779,28 @@ Delete delete = .table("tbl") .partitionKey(partitionKey) .clusteringKey(clusteringKey) - .condition(condition) // condition + .condition(condition) // condition .build(); -// Execute the Delete operation +// Execute the `Delete` operation. transaction.delete(delete); ``` In addition to using the `deleteIf` condition, you can specify the `deleteIfExists` condition as follows: ```java -// Build a deleteIfExists condition +// Build a `deleteIfExists` condition. MutationCondition deleteIfExistsCondition = ConditionBuilder.deleteIfExists(); ``` #### Mutate operation -Mutate is an operation to execute multiple mutations (Put and Delete operations). +Mutate is an operation to execute multiple mutations (`Put` and `Delete` operations). -You need to create mutation objects first, and then you can execute them with the `transaction.mutate()` method as follows: +You need to create mutation objects first, and then you can execute the objects by using the `transaction.mutate()` method as follows: ```java -// Create Put and Delete operations +// Create `Put` and `Delete` operations. Key partitionKey = Key.ofInt("c1", 10); Key clusteringKeyForPut = Key.of("c2", "aaa", "c3", 100L); @@ -725,29 +825,28 @@ Delete delete = .clusteringKey(clusteringKeyForDelete) .build(); -// Execute the operations +// Execute the operations. transaction.mutate(Arrays.asList(put, delete)); ``` -#### Use a default namespace for CRUD operations +#### Default namespace for CRUD operations -A default namespace for all the CRUD operations can be set with a property of the ScalarDB configuration. -If you would like to use this setting with ScalarDB server, it needs to be set on the client-side configuration. +A default namespace for all CRUD operations can be set by using a property in the ScalarDB configuration. ```properties -scalar.db.default_namespace_name= +scalar.db.default_namespace_name= ``` Any operation that does not specify a namespace will use the default namespace set in the configuration. ```java -//This operation will target the default namespace +// This operation will target the default namespace. Scan scanUsingDefaultNamespace = Scan.newBuilder() .table("tbl") .all() .build(); -//This operation will target the "ns" namespace +// This operation will target the "ns" namespace. Scan scanUsingSpecifiedNamespace = Scan.newBuilder() .namespace("ns") @@ -756,51 +855,55 @@ Scan scanUsingSpecifiedNamespace = .build(); ``` -#### Notes - -Although all the builders of the CRUD operations can specify consistency by using the `consistency()` methods, those methods are ignored. Instead, the `LINEARIZABLE` consistency level is always used in transactions. - ### Commit a transaction After executing CRUD operations, you need to commit a transaction to finish it. -You can commit a transaction as follows; +You can commit a transaction as follows: ```java -// Commit a transaction +// Commit a transaction. transaction.commit(); ``` -### Rollback/Abort a transaction +### Roll back or abort a transaction -If you want to rollback/abort a transaction or an error happens during the execution, you can rollback/abort a transaction. +If an error occurs when executing a transaction, you can roll back or abort the transaction. -You can rollback/abort a transaction as follows; +You can roll back a transaction as follows: ```java -// Rollback a transaction +// Roll back a transaction. transaction.rollback(); +``` -Or +Or, you can abort a transaction as follows: -// Abort a transaction +```java +// Abort a transaction. transaction.abort(); ``` -Please see [Handle exceptions](#handle-exceptions) for the details of how to handle exceptions in ScalarDB. +For details about how to handle exceptions in ScalarDB, see [How to handle exceptions](#how-to-handle-exceptions). + +## How to handle exceptions + +When executing a transaction, you will also need to handle exceptions properly. -## Handle exceptions +{% capture notice--warning %} +**Attention** -Handling exceptions correctly in ScalarDB is very important. -If you mishandle exceptions, your data could become inconsistent. -This document explains how to handle exceptions properly in ScalarDB. +If you don't handle exceptions properly, you may face anomalies or data inconsistency. +{% endcapture %} -Let's look at the following example code to see how to handle exceptions in ScalarDB. +
{{ notice--warning | markdownify }}
+ +The following sample code shows how to handle exceptions: ```java public class Sample { public static void main(String[] args) throws Exception { - TransactionFactory factory = TransactionFactory.create(""); + TransactionFactory factory = TransactionFactory.create(""); DistributedTransactionManager transactionManager = factory.getTransactionManager(); int retryCount = 0; @@ -808,65 +911,63 @@ public class Sample { while (true) { if (retryCount++ > 0) { - // Retry the transaction three times maximum in this sample code + // Retry the transaction three times maximum. if (retryCount >= 3) { - // Throw the last exception if the number of retries exceeds the maximum + // Throw the last exception if the number of retries exceeds the maximum. throw lastException; } - // Sleep 100 milliseconds before retrying the transaction in this sample code + // Sleep 100 milliseconds before retrying the transaction. TimeUnit.MILLISECONDS.sleep(100); } DistributedTransaction transaction = null; try { - // Begin a transaction + // Begin a transaction. transaction = transactionManager.begin(); - // Execute CRUD operations in the transaction + // Execute CRUD operations in the transaction. Optional result = transaction.get(...); List results = transaction.scan(...); transaction.put(...); transaction.delete(...); - // Commit the transaction + // Commit the transaction. transaction.commit(); } catch (UnsatisfiedConditionException e) { - // You need to handle `UnsatisfiedConditionException` only if a mutation operation specifies - // a condition. This exception indicates the condition for the mutation operation is not met + // You need to handle `UnsatisfiedConditionException` only if a mutation operation specifies a condition. + // This exception indicates the condition for the mutation operation is not met. try { transaction.rollback(); } catch (RollbackException ex) { - // Rolling back the transaction failed. As the transaction should eventually recover, you - // don't need to do anything further. You can simply log the occurrence here + // Rolling back the transaction failed. Since the transaction should eventually recover, + // you don't need to do anything further. You can simply log the occurrence here. } - // You can handle the exception here, according to your application requirements + // You can handle the exception here, according to your application requirements. return; } catch (UnknownTransactionStatusException e) { - // If you catch `UnknownTransactionStatusException` when committing the transaction, it - // indicates that the status of the transaction, whether it has succeeded or not, is - // unknown. In such a case, you need to check if the transaction is committed successfully - // or not and retry it if it failed. How to identify a transaction status is delegated to - // users + // If you catch `UnknownTransactionStatusException` when committing the transaction, + // it indicates that the status of the transaction, whether it was successful or not, is unknown. + // In such a case, you need to check if the transaction is committed successfully or not and + // retry the transaction if it failed. How to identify a transaction status is delegated to users. return; } catch (TransactionException e) { // For other exceptions, you can try retrying the transaction. - // For `CrudConflictException` and `CommitConflictException` and - // `TransactionNotFoundException`, you can basically retry the transaction. However, for the - // other exceptions, the transaction may still fail if the cause of the exception is - // nontransient. In such a case, you will exhaust the number of retries and throw the last - // exception + // For `CrudConflictException`, `CommitConflictException`, and `TransactionNotFoundException`, + // you can basically retry the transaction. However, for the other exceptions, the transaction + // will still fail if the cause of the exception is non-transient. In such a case, you will + // exhaust the number of retries and throw the last exception. if (transaction != null) { try { transaction.rollback(); } catch (RollbackException ex) { - // Rolling back the transaction failed. As the transaction should eventually recover, - // you don't need to do anything further. You can simply log the occurrence here + // Rolling back the transaction failed. The transaction should eventually recover, + // so you don't need to do anything further. You can simply log the occurrence here. } } @@ -877,60 +978,59 @@ public class Sample { } ``` -The `begin()` API could throw `TransactionException` or `TransactionNotFoundException`. -If you catch `TransactionException`, it indicates that the transaction has failed to begin due to transient or nontransient faults. You can try retrying the transaction, but you may not be able to begin the transaction due to nontransient faults. -If you catch `TransactionNotFoundException`, it indicates that the transaction has failed to begin due to transient faults. You can retry the transaction. +### `TransactionException` and `TransactionNotFoundException` + +The `begin()` API could throw `TransactionException` or `TransactionNotFoundException`: + +- If you catch `TransactionException`, this exception indicates that the transaction has failed to begin due to transient or non-transient faults. You can try retrying the transaction, but you may not be able to begin the transaction due to non-transient faults. +- If you catch `TransactionNotFoundException`, this exception indicates that the transaction has failed to begin due to transient faults. In this case, you can retry the transaction. + +The `join()` API could also throw `TransactionNotFoundException`. You can handle this exception in the same way that you handle the exceptions for the `begin()` API. + +### `CrudException` and `CrudConflictException` -The APIs for CRUD operations (`get()`, `scan()`, `put()`, `delete()`, and `mutate()`) could throw `CrudException` or `CrudConflictException`. -If you catch `CrudException`, it indicates that the transaction CRUD operation has failed due to transient or nontransient faults. You can try retrying the transaction from the beginning, but the transaction may still fail if the cause is nontransient. -If you catch `CrudConflictException`, it indicates that the transaction CRUD operation has failed due to transient faults (e.g., a conflict error). You can retry the transaction from the beginning. +The APIs for CRUD operations (`get()`, `scan()`, `put()`, `delete()`, and `mutate()`) could throw `CrudException` or `CrudConflictException`: + +- If you catch `CrudException`, this exception indicates that the transaction CRUD operation has failed due to transient or non-transient faults. You can try retrying the transaction from the beginning, but the transaction may still fail if the cause is non-transient. +- If you catch `CrudConflictException`, this exception indicates that the transaction CRUD operation has failed due to transient faults (for example, a conflict error). In this case, you can retry the transaction from the beginning. + +### `UnsatisfiedConditionException` The APIs for mutation operations (`put()`, `delete()`, and `mutate()`) could also throw `UnsatisfiedConditionException`. -If you catch this exception, it indicates that the condition for the mutation operation is not met. -You can handle this exception according to your application requirements. -Also, the `commit()` API could throw `CommitException`, `CommitConflictException`, or `UnknownTransactionStatusException`. -If you catch `CommitException`, it indicates that committing the transaction fails due to transient or nontransient faults. You can try retrying the transaction from the beginning, but the transaction may still fail if the cause is nontransient. -If you catch `CommitConflictException`, it indicates that committing the transaction has failed due to transient faults (e.g., a conflict error). You can retry the transaction from the beginning. -If you catch `UnknownTransactionStatusException`, it indicates that the status of the transaction, whether it has succeeded or not, is unknown. -In such a case, you need to check if the transaction is committed successfully and retry the transaction if it has failed. -How to identify a transaction status is delegated to users. -You may want to create a transaction status table and update it transactionally with other application data so that you can get the status of a transaction from the status table. +If you catch `UnsatisfiedConditionException`, this exception indicates that the condition for the mutation operation is not met. You can handle this exception according to your application requirements. + +### `CommitException`, `CommitConflictException`, and `UnknownTransactionStatusException` + +The `commit()` API could throw `CommitException`, `CommitConflictException`, or `UnknownTransactionStatusException`: + +- If you catch `CommitException`, this exception indicates that committing the transaction fails due to transient or non-transient faults. You can try retrying the transaction from the beginning, but the transaction may still fail if the cause is non-transient. +- If you catch `CommitConflictException`, this exception indicates that committing the transaction has failed due to transient faults (for example, a conflict error). In this case, you can retry the transaction from the beginning. +- If you catch `UnknownTransactionStatusException`, this exception indicates that the status of the transaction, whether it was successful or not, is unknown. In this case, you need to check if the transaction is committed successfully and retry the transaction if it has failed. -Although not illustrated in the sample code, the `resume()` API could also throw `TransactionNotFoundException`. -This exception indicates that the transaction associated with the specified ID was not found and/or the transaction might have expired. -In either case, you can retry the transaction from the beginning since the cause of this exception is basically transient. +How to identify a transaction status is delegated to users. You may want to create a transaction status table and update it transactionally with other application data so that you can get the status of a transaction from the status table. -In the sample code, for `UnknownTransactionStatusException`, the transaction is not retried because the cause of the exception is nontransient. -Also, for `UnsatisfiedConditionException`, the transaction is not retried because how to handle this exception depends on your application requirements. -For other exceptions, the transaction is retried because the cause of the exception is transient or nontransient. -If the cause of the exception is transient, the transaction may succeed if you retry it. -However, if the cause of the exception is nontransient, the transaction may still fail even if you retry it. -In such a case, you will exhaust the number of retries. +### Notes about some exceptions -Please note that if you begin a transaction by specifying a transaction ID, you must use a different ID when you retry the transaction. -And, in the sample code, the transaction is retried three times maximum and sleeps for 100 milliseconds before it is retried. -But you can choose a retry policy, such as exponential backoff, according to your application requirements. +Although not illustrated in the sample code, the `resume()` API could also throw `TransactionNotFoundException`. This exception indicates that the transaction associated with the specified ID was not found and/or the transaction might have expired. In either case, you can retry the transaction from the beginning since the cause of this exception is basically transient. -## Transactional operations for Two-phase Commit Transaction +In the sample code, for `UnknownTransactionStatusException`, the transaction is not retried because the application must check if the transaction was successful to avoid potential duplicate operations. For other exceptions, the transaction is retried because the cause of the exception is transient or non-transient. If the cause of the exception is transient, the transaction may succeed if you retry it. However, if the cause of the exception is non-transient, the transaction will still fail even if you retry it. In such a case, you will exhaust the number of retries. -Please see [Two-phase Commit Transactions](two-phase-commit-transactions.md). +{% capture notice--info %} +**Note** -## Investigate Consensus Commit transactions errors +In the sample code, the transaction is retried three times maximum and sleeps for 100 milliseconds before it is retried. But you can choose a retry policy, such as exponential backoff, according to your application requirements. +{% endcapture %} -This configuration is only available to troubleshoot Consensus Commit transactions. By adding the following configuration, `Get` and `Scan` operations results will contain [transaction metadata](schema-loader.md#internal-metadata-for-consensus-commit). -To see the transaction metadata columns details for a given table, you can use the `DistributedTransactionAdmin.getTableMetadata()` method which will return the table metadata augmented with the transaction metadata columns. -All in all, using this configuration can be useful to investigate transaction related issues. +
{{ notice--info | markdownify }}
+ +## Investigating Consensus Commit transaction manager errors + +To investigate errors when using the Consensus Commit transaction manager, you can enable a configuration that will return table metadata augmented with transaction metadata columns, which can be helpful when investigating transaction-related issues. This configuration, which is only available when troubleshooting the Consensus Commit transaction manager, enables you to see transaction metadata column details for a given table by using the `DistributedTransactionAdmin.getTableMetadata()` method. + +By adding the following configuration, `Get` and `Scan` operations results will contain [transaction metadata](schema-loader.md#internal-metadata-for-consensus-commit): ```properties -# By default, it is set to "false". +# By default, this configuration is set to `false`. scalar.db.consensus_commit.include_metadata.enabled=true ``` - -## References - -* [Design document](design.md) -* [Getting started](getting-started-with-scalardb.md) -* [Multi-storage Transactions](multi-storage-transactions.md) -* [Two-phase Commit Transactions](two-phase-commit-transactions.md) -* [ScalarDB Server](scalardb-server.md)