Skip to content

Commit 321d28a

Browse files
committed
Update doc for simple and prepared statements.
1 parent 33a0cf1 commit 321d28a

4 files changed

Lines changed: 195 additions & 43 deletions

File tree

manual/statements/.nav

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
simple
2+
prepared
3+
built
4+
batch

manual/statements/README.md

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
## Statements
22

3-
To execute a query, you create a [Statement] instance and pass it to
4-
`Session#execute()`. The driver provides various implementations:
3+
To execute a query, you create a [Statement] instance and pass it to [Session#execute()][execute] or
4+
[Session#executeAsync][executeAsync]. The driver provides various implementations:
55

66
* [SimpleStatement](simple/): a simple implementation built directly from a
77
character string. Typically used for queries that are executed only
@@ -14,16 +14,6 @@ To execute a query, you create a [Statement] instance and pass it to
1414
* [BatchStatement](batch/): a statement that groups multiple statements to be
1515
executed as a batch.
1616

17-
`Session` also has a shortcut to build and execute a simple statement in
18-
a single call:
19-
20-
```java
21-
session.execute("select release_version from system.local");
22-
23-
// Is equivalent to:
24-
Statement s = new SimpleStatement("select release_version from system.local");
25-
session.execute(s);
26-
```
2717

2818
### Customizing execution
2919

@@ -48,3 +38,5 @@ wrap your statements in a custom [StatementWrapper] implementation.
4838
[RetryPolicy]: http://docs.datastax.com/en/drivers/java/2.1/com/datastax/driver/core/policies/RetryPolicy.html
4939
[LoadBalancingPolicy]: http://docs.datastax.com/en/drivers/java/2.1/com/datastax/driver/core/policies/LoadBalancingPolicy.html
5040
[SpeculativeExecutionPolicy]: http://docs.datastax.com/en/drivers/java/2.1/com/datastax/driver/core/policies/SpeculativeExecutionPolicy.html
41+
[execute]: http://docs.datastax.com/en/drivers/java/3.0/com/datastax/driver/core/Session.html#execute-com.datastax.driver.core.Statement-
42+
[executeAsync]: http://docs.datastax.com/en/drivers/java/3.0/com/datastax/driver/core/Session.html#executeAsync-com.datastax.driver.core.Statement-

manual/statements/prepared/README.md

Lines changed: 73 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,68 @@
11
## Prepared statements
22

3-
When Cassandra executes a query, the first thing it does is parse the
4-
query string to an internal representation. If the same query is used
5-
often, you can use a prepared statement, which allows Cassandra to cache
6-
that representation, and save time and resources for subsequent
7-
executions. Prepared statements are typically parameterized, using
8-
different values for each execution:
3+
Use [PreparedStatement] for queries that are executed multiple times in your application:
94

105
```java
116
PreparedStatement prepared = session.prepare(
127
"insert into product (sku, description) values (?, ?)");
138

14-
BoundStatement bound;
15-
16-
bound = prepared.bind("234827", "Mouse");
9+
BoundStatement bound = prepared.bind("234827", "Mouse");
1710
session.execute(bound);
1811

19-
bound = prepared.bind("987274", "Keyboard");
20-
session.execute(bound);
12+
session.execute(prepared.bind("987274", "Keyboard"));
2113
```
2214

23-
Statements should be be prepared only once. If you call `prepare`
24-
multiple times with the same query string, the driver will log a
25-
warning. So your application should cache the `PreparedStatement` object
26-
once it's been created (this can be as simple as storing it as a field
27-
in a DAO).
15+
When you prepare the statement, Cassandra will parse the query string, cache the result and return a unique identifier
16+
(the `PreparedStatement` object keeps an internal reference to that identifier):
17+
18+
```ditaa
19+
client driver Cassandra
20+
--+------------------------+----------------+------
21+
| | |
22+
| session.prepare(query) | |
23+
|----------------------->| |
24+
| | PREPARE(query) |
25+
| |--------------->|
26+
| | |
27+
| | |
28+
| | | - compute id
29+
| | | - parse query string
30+
| | | - cache (id, parsed)
31+
| | |
32+
| | PREPARED(id) |
33+
| |<---------------|
34+
| PreparedStatement(id) | |
35+
|<-----------------------| |
36+
```
37+
38+
When you bind and execute a prepared statement, the driver will only send the identifier, which allows Cassandra to
39+
skip the parsing phase:
40+
41+
```ditaa
42+
client driver Cassandra
43+
--+---------------------------------+---------------------+------
44+
| | |
45+
| session.execute(BoundStatement) | |
46+
|-------------------------------->| |
47+
| | EXECUTE(id, values) |
48+
| |-------------------->|
49+
| | |
50+
| | |
51+
| | | - get cache(id)
52+
| | | - execute query
53+
| | |
54+
| | ROWS |
55+
| |<--------------------|
56+
| | |
57+
|<--------------------------------| |
58+
```
59+
60+
61+
You should prepare only once, and cache the `PreparedStatement` in your application (it is thread-safe). If you call
62+
`prepare` multiple times with the same query string, the driver will log a warning.
63+
64+
If you execute a query only once, a prepared statement is inefficient because it requires two roundtrips. Consider a
65+
[simple statement](../simple/) instead.
2866

2967
### Parameters and binding
3068

@@ -36,9 +74,8 @@ ps1 = session.prepare("insert into product (sku, description) values (?, ?)");
3674
ps2 = session.prepare("insert into product (sku, description) values (:s, :d)");
3775
```
3876

39-
To turn the statement into its executable form, you need to *bind* it
40-
and provide values for the parameters. As shown previously, there is a
41-
shorthand form to do it all in one call:
77+
To turn the statement into its executable form, you need to *bind* it to create a [BoundStatement]. As shown previously,
78+
there is a shorthand to provide the parameters in the same call:
4279

4380
```java
4481
BoundStatement bound = ps1.bind("324378", "LCD screen");
@@ -59,8 +96,9 @@ BoundStatement bound = ps2.bind()
5996
.setString("d", "LCD screen");
6097
```
6198

62-
If you don't set a parameter, it is sent as `null` (note that this
63-
behavior changes in the 2.1 branch of the driver).
99+
You must set all parameters. If you fail to do so, the driver will throw an error when executing the statement. If you
100+
want to send a `NULL` value, set it explicitly to `null` (use `setToNull` for Java primitives). Note that this is a new
101+
behavior in 2.1, branch 2.0 of the driver assumed unset values were null.
64102

65103
You can use named setters even if the query uses anonymous parameters;
66104
Cassandra will name the parameters after the column they apply to:
@@ -79,21 +117,19 @@ A bound statement also has getters to retrieve the values. Note that
79117
this has a small performance overhead since values are stored in their
80118
serialized form.
81119

82-
### How the driver handles prepared statements
120+
`BoundStatement` is **not thread-safe**. You can reuse an instance multiple times with different parameters, but only
121+
from a single thread, and only if you use the synchronous API ([Session#execute][execute] is fine,
122+
[Session#executeAsync][executeAsync] is not).
123+
Also, make sure you don't accidentally reuse parameters from previous executions.
83124

84-
When the driver prepares a statement, it sends the query string to
85-
Cassandra, which caches the statement and returns an identifier. Later,
86-
when the driver needs to execute the statement, it just sends the
87-
identifier and parameter values. Note that the identifier is
88-
deterministic, so it will always be the same for all nodes (it's a
89-
actually a hash of the query string).
125+
### Preparing on multiple nodes
90126

91-
Prepared statements are not replicated across the cluster. It is the
127+
Cassandra does not replicate prepared statements across the cluster. It is the
92128
driver's responsibility to ensure that each node's cache is up to
93129
date. It uses a number of strategies to achieve this:
94130

95131
1. When a statement is initially prepared, it is first sent to a single
96-
node in the cluster (this prevents from hitting all nodes in case
132+
node in the cluster (this avoids hitting all nodes in case
97133
the query string is wrong). Once that node replies successfully, the
98134
driver re-prepares on all remaining nodes:
99135

@@ -119,6 +155,9 @@ date. It uses a number of strategies to achieve this:
119155
|<-----------------------| | | |
120156
```
121157
158+
The prepared statement identifier is deterministic (it's a hash of the query string), so it is the same
159+
for all nodes.
160+
122161
2. if a node crashes, it loses all of its prepared statements. So the
123162
driver keeps a client-side cache; anytime a node is marked back up,
124163
the driver re-prepares all statements on it;
@@ -168,5 +207,9 @@ Changing the driver's defaults should be done with care and only in
168207
specific situations; read each method's Javadoc for detailed
169208
explanations.
170209
210+
[PreparedStatement]: http://docs.datastax.com/en/drivers/java/2.1/com/datastax/driver/core/PreparedStatement.html
211+
[BoundStatement]: http://docs.datastax.com/en/drivers/java/2.1/com/datastax/driver/core/BoundStatement.html
171212
[setPrepareOnAllHosts]: http://docs.datastax.com/en/drivers/java/2.1/com/datastax/driver/core/QueryOptions.html#setPrepareOnAllHosts-boolean-
172213
[setReprepareOnUp]: http://docs.datastax.com/en/drivers/java/2.1/com/datastax/driver/core/QueryOptions.html#setReprepareOnUp-boolean-
214+
[execute]: http://docs.datastax.com/en/drivers/java/3.0/com/datastax/driver/core/Session.html#execute-com.datastax.driver.core.Statement-
215+
[executeAsync]: http://docs.datastax.com/en/drivers/java/3.0/com/datastax/driver/core/Session.html#executeAsync-com.datastax.driver.core.Statement-

manual/statements/simple/README.md

Lines changed: 114 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,118 @@
11
## Simple statements
22

3-
*Coming soon... In the meantime, see the javadoc for [SimpleStatement].*
3+
Use [SimpleStatement] for queries that will be executed only once (or a few times) in your application:
4+
5+
```java
6+
SimpleStatement statement = new SimpleStatement(
7+
"SELECT value FROM application_params WHERE name = 'greeting_message'");
8+
session.execute(statement);
9+
```
10+
11+
When you don't need to customize anything on the `SimpleStatement` object, there is a convenient shortcut:
12+
13+
```java
14+
session.execute("SELECT value FROM application_params WHERE name = 'greeting_message'");
15+
```
16+
17+
Each time you execute a simple statement, Cassandra will parse the query string again; nothing is cached (neither on the
18+
client nor on the server):
19+
20+
```ditaa
21+
client driver Cassandra
22+
--+----------------------------------+---------------------+------
23+
| | |
24+
| session.execute(SimpleStatement) | |
25+
|--------------------------------->| |
26+
| | QUERY(query_string) |
27+
| |-------------------->|
28+
| | |
29+
| | |
30+
| | | - parse query string
31+
| | | - execute query
32+
| | |
33+
| | ROWS |
34+
| |<--------------------|
35+
| | |
36+
|<---------------------------------| |
37+
```
38+
39+
If you execute the same query often (or a similar query with different column values), consider a
40+
[prepared statement](../prepared/) instead.
41+
42+
43+
### Using values
44+
45+
Instead of sending a raw query string, you can use anonymous bind markers and provide values separately:
46+
47+
```java
48+
String paramName = ...
49+
session.execute(
50+
"SELECT value FROM application_params WHERE name = ?",
51+
paramName);
52+
```
53+
54+
This syntax has a few advantages:
55+
56+
* if the values already come from some other part of your code, it looks cleaner than doing the concatenation yourself;
57+
* you don't need to translate the values to their string representation. The driver will sent them alongside the query,
58+
in their serialized binary form.
59+
60+
The number of values must match the query string, and their types must match the database schema. Note that the driver
61+
does not parse query strings, so it cannot perform those checks on the client side; if you make a mistake, the query
62+
will be sent anyway, and the error will be caught by Cassandra (`InvalidQueryException` is a server-side error):
63+
64+
```java
65+
session.execute(
66+
"SELECT value FROM application_params WHERE name = ?",
67+
"foo", "bar");
68+
// Exception in thread "main" com.datastax.driver.core.exceptions.InvalidQueryException:
69+
// Invalid amount of bind variables
70+
```
71+
72+
### Value type inference
73+
74+
Another consequence of not parsing query strings is that the driver has to make a guess on how to serialize values,
75+
based on their Java type (see the [default type mappings](../../#cql-to-java-type-mapping)). This can be tricky, in
76+
particular for numeric types:
77+
78+
```java
79+
// schema: create table bigints(b bigint primary key)
80+
session.execute(
81+
"INSERT INTO bigints (b) VALUES (?)",
82+
1);
83+
// Exception in thread "main" com.datastax.driver.core.exceptions.InvalidQueryException:
84+
// Expected 8 or 0 byte long (4)
85+
```
86+
87+
The problem here is that the literal `1` has the Java type `int`. So the driver serializes it as a CQL `int` (4 bytes),
88+
but the server expects a CQL `bigint` (8 bytes). The fix is to specify the correct Java type:
89+
90+
```java
91+
session.execute(
92+
"INSERT INTO bigints (b) VALUES (?)",
93+
1L);
94+
```
95+
96+
In the same vein, strings are always serialized to `varchar`, so you could have a problem if you target an `ascii`
97+
column:
98+
99+
```java
100+
// schema: create table ascii_quotes(id int primary key, t ascii)
101+
session.execute(
102+
"INSERT INTO ascii_quotes (id, t) VALUES (?, ?)",
103+
1, "Touché sir, touché...");
104+
// Exception in thread "main" com.datastax.driver.core.exceptions.InvalidQueryException:
105+
// Invalid byte for ascii: -61
106+
```
107+
108+
In that situation, there is no way to hint at the correct type. Your only option is to serialize the value manually:
109+
110+
```java
111+
ProtocolVersion protocolVersion = cluster.getConfiguration().getProtocolOptions().getProtocolVersionEnum();
112+
ByteBuffer bytes = DataType.ascii().serialize("Touché sir, touché...", protocolVersion);
113+
session.execute(
114+
"INSERT INTO ascii_quotes (id, t) VALUES (?, ?)",
115+
1, bytes);
116+
```
4117

5118
[SimpleStatement]: http://docs.datastax.com/en/drivers/java/2.1/com/datastax/driver/core/SimpleStatement.html

0 commit comments

Comments
 (0)