Skip to content

Commit e8fb95f

Browse files
committed
tests: add integration tests for batch statements
1 parent 73beaa3 commit e8fb95f

File tree

2 files changed

+375
-0
lines changed

2 files changed

+375
-0
lines changed
Lines changed: 374 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,374 @@
1+
use assert_matches::assert_matches;
2+
use crate::utils::{create_new_session_builder, setup_tracing, unique_keyspace_name, PerformDDL};
3+
use scylla::batch::{Batch, BatchType};
4+
use scylla::client::session::Session;
5+
use scylla::errors::ExecutionError;
6+
use scylla::prepared_statement::PreparedStatement;
7+
use scylla::query::Query;
8+
use scylla::value::{CqlValue, MaybeUnset};
9+
use std::collections::HashMap;
10+
use std::string::String;
11+
12+
13+
const BATCH_COUNT: usize = 100;
14+
15+
async fn create_test_session(table_name: &str) -> (Session, String) {
16+
let session = create_new_session_builder().build().await.unwrap();
17+
let ks = unique_keyspace_name();
18+
session
19+
.ddl(format!(
20+
"CREATE KEYSPACE IF NOT EXISTS {} WITH REPLICATION = {{'class' : 'NetworkTopologyStrategy', 'replication_factor' : 1}}", ks))
21+
.await
22+
.unwrap();
23+
session.use_keyspace(&ks, false).await.unwrap();
24+
session
25+
.ddl(format!(
26+
"CREATE TABLE IF NOT EXISTS {} (k0 text, k1 int, v int, PRIMARY KEY (k0, k1))", table_name))
27+
.await
28+
.unwrap();
29+
30+
(session, ks)
31+
}
32+
33+
async fn create_counter_tables(session: &Session, ks: &str) {
34+
for &table in ["counter1", "counter2", "counter3"].iter() {
35+
session
36+
.ddl(format!("CREATE TABLE {}.{} (k0 text PRIMARY KEY, c counter)", ks, table))
37+
.await
38+
.unwrap();
39+
}
40+
}
41+
42+
async fn verify_batch_insert(session: &Session, keyspace: &str, test_name: &str, count: usize) {
43+
let select_query = format!("SELECT k0, k1, v FROM {}.{} WHERE k0 = ?", keyspace, test_name);
44+
let query_result = session
45+
.query_unpaged(select_query, (test_name,))
46+
.await
47+
.unwrap()
48+
.into_rows_result()
49+
.unwrap();
50+
let rows: Vec<(String, i32, i32)> = query_result
51+
.rows::<(String, i32, i32)>()
52+
.unwrap()
53+
.map(|r| r.unwrap())
54+
.collect();
55+
assert_eq!(rows.len(), count);
56+
for (k0, k1, v) in rows {
57+
assert_eq!(k0, test_name);
58+
assert_eq!(v, k1 + 1);
59+
}
60+
}
61+
62+
async fn prepare_insert_statement(session: &Session, ks: &str, table: &str) -> PreparedStatement {
63+
let query_str = format!("INSERT INTO {}.{} (k0, k1, v) VALUES (?, ?, ?)", ks, table);
64+
session.prepare(Query::new(query_str)).await.unwrap()
65+
}
66+
67+
#[tokio::test]
68+
async fn test_batch_of_simple_statements() {
69+
setup_tracing();
70+
let test_name = String::from("test_batch_simple_statements");
71+
let (session, ks) = create_test_session(&test_name).await;
72+
73+
let mut batch = Batch::new(BatchType::Unlogged);
74+
for i in 0..BATCH_COUNT {
75+
let simple_statement = Query::new(format!(
76+
"INSERT INTO {}.{} (k0, k1, v) VALUES ('{}', {}, {})",
77+
ks, &test_name, &test_name, i, i + 1
78+
));
79+
batch.append_statement(simple_statement);
80+
}
81+
session.batch(&batch, vec![(); BATCH_COUNT]).await.unwrap();
82+
verify_batch_insert(&session, &ks, &test_name, BATCH_COUNT).await;
83+
}
84+
85+
#[tokio::test]
86+
async fn test_batch_of_bound_statements() {
87+
setup_tracing();
88+
let test_name = String::from("test_batch_bound_statements");
89+
let (session, ks) = create_test_session(&test_name).await;
90+
91+
let prepared = prepare_insert_statement(&session, &ks, &test_name).await;
92+
let mut batch = Batch::new(BatchType::Unlogged);
93+
let mut batch_values: Vec<Vec<CqlValue>> = Vec::with_capacity(BATCH_COUNT);
94+
for i in 0..BATCH_COUNT as i32 {
95+
batch.append_statement(prepared.clone());
96+
batch_values.push(vec![
97+
CqlValue::Text(test_name.clone()),
98+
CqlValue::Int(i),
99+
CqlValue::Int(i + 1),
100+
]);
101+
}
102+
session.batch(&batch, batch_values).await.unwrap();
103+
verify_batch_insert(&session, &ks, &test_name, BATCH_COUNT).await;
104+
}
105+
106+
#[tokio::test]
107+
async fn test_prepared_batch() {
108+
setup_tracing();
109+
let test_name = String::from("test_prepared_batch");
110+
let (session, ks) = create_test_session(&test_name).await;
111+
112+
let mut batch = Batch::new(BatchType::Unlogged);
113+
let mut batch_values = Vec::with_capacity(BATCH_COUNT);
114+
let query_str = format!("INSERT INTO {}.{} (k0, k1, v) VALUES (?, ?, ?)", ks, &test_name);
115+
for i in 0..BATCH_COUNT as i32 {
116+
batch.append_statement(Query::new(query_str.clone()));
117+
batch_values.push(vec![
118+
CqlValue::Text(test_name.clone()),
119+
CqlValue::Int(i),
120+
CqlValue::Int(i + 1),
121+
]);
122+
}
123+
let prepared_batch = session.prepare_batch(&batch).await.unwrap();
124+
session.batch(&prepared_batch, batch_values).await.unwrap();
125+
verify_batch_insert(&session, &ks, &test_name, BATCH_COUNT).await;
126+
}
127+
128+
#[tokio::test]
129+
async fn test_batch_of_bound_statements_with_unset_values() {
130+
setup_tracing();
131+
let test_name = String::from("test_batch_bound_statements_with_unset_values");
132+
let (session, ks) = create_test_session(&test_name).await;
133+
134+
let prepared = prepare_insert_statement(&session, &ks, &test_name).await;
135+
let mut batch1 = Batch::new(BatchType::Unlogged);
136+
let mut batch1_values = Vec::with_capacity(BATCH_COUNT);
137+
for i in 0..BATCH_COUNT as i32{
138+
batch1.append_statement(prepared.clone());
139+
batch1_values.push(vec![
140+
CqlValue::Text(test_name.clone()),
141+
CqlValue::Int(i),
142+
CqlValue::Int(i + 1),
143+
]);
144+
}
145+
session.batch(&batch1, batch1_values).await.unwrap();
146+
147+
// Update v to (k1 + 2), but for every 20th row leave v unset.
148+
let mut batch2 = Batch::new(BatchType::Unlogged);
149+
let mut batch2_values = Vec::with_capacity(BATCH_COUNT);
150+
for i in 0..BATCH_COUNT as i32 {
151+
batch2.append_statement(prepared.clone());
152+
if i % 20 == 0 {
153+
batch2_values.push(vec![
154+
MaybeUnset::Set(CqlValue::Text(test_name.clone())),
155+
MaybeUnset::Set(CqlValue::Int(i)),
156+
MaybeUnset::Unset
157+
]);
158+
} else {
159+
batch2_values.push(vec![
160+
MaybeUnset::Set(CqlValue::Text(test_name.clone())),
161+
MaybeUnset::Set(CqlValue::Int(i)),
162+
MaybeUnset::Set(CqlValue::Int(i + 2))
163+
]);
164+
}
165+
}
166+
session.batch(&batch2, batch2_values).await.unwrap();
167+
168+
// Verify that rows with k1 % 20 == 0 retain the original value.
169+
let select_query = format!("SELECT k0, k1, v FROM {}.{} WHERE k0 = ?", ks, &test_name);
170+
let query_result = session
171+
.query_unpaged(select_query, (&test_name,))
172+
.await
173+
.unwrap()
174+
.into_rows_result()
175+
.unwrap();
176+
let rows: Vec<(String, i32, i32)> = query_result
177+
.rows::<(String, i32, i32)>()
178+
.unwrap()
179+
.map(|r| r.unwrap())
180+
.collect();
181+
assert_eq!(rows.len(), BATCH_COUNT, "Expected {} rows, got {}", BATCH_COUNT, rows.len());
182+
for (k0, k1, v) in rows {
183+
assert_eq!(k0, test_name);
184+
assert_eq!(v, if k1 % 20 == 0 { k1 + 1 } else { k1 + 2 });
185+
}
186+
}
187+
188+
#[tokio::test]
189+
async fn test_batch_of_bound_statements_named_variables() {
190+
setup_tracing();
191+
let test_name = String::from("test_batch_bound_statements_named_variables");
192+
let (session, ks) = create_test_session(&test_name).await;
193+
194+
let query_str = format!("INSERT INTO {}.{} (k0, k1, v) VALUES (:k0, :k1, :v)", ks, &test_name);
195+
let prepared = session.prepare(query_str).await.unwrap();
196+
197+
let mut batch = Batch::new(BatchType::Unlogged);
198+
let mut batch_values = Vec::with_capacity(BATCH_COUNT);
199+
for i in 0..BATCH_COUNT as i32 {
200+
batch.append_statement(prepared.clone());
201+
let mut values = HashMap::new();
202+
values.insert("k0", CqlValue::Text(test_name.clone()));
203+
values.insert("k1", CqlValue::Int(i));
204+
values.insert("v", CqlValue::Int(i + 1));
205+
batch_values.push(values);
206+
}
207+
session.batch(&batch, batch_values).await.unwrap();
208+
verify_batch_insert(&session, &ks, &test_name, BATCH_COUNT).await;
209+
}
210+
211+
#[tokio::test]
212+
async fn test_batch_of_mixed_bound_and_simple_statements() {
213+
setup_tracing();
214+
let test_name = String::from("test_batch_mixed_bound_and_simple_statements");
215+
let (session, ks) = create_test_session(&test_name).await;
216+
217+
let query_str = format!("INSERT INTO {}.{} (k0, k1, v) VALUES (?, ?, ?)", ks, &test_name);
218+
let prepared_bound = session.prepare(Query::new(query_str.clone())).await.unwrap();
219+
220+
let mut batch = Batch::new(BatchType::Unlogged);
221+
let mut batch_values = Vec::with_capacity(BATCH_COUNT);
222+
for i in 0..BATCH_COUNT as i32 {
223+
if i % 2 == 1 {
224+
let simple_statement = Query::new(format!(
225+
"INSERT INTO {}.{} (k0, k1, v) VALUES ('{}', {}, {})",
226+
ks, &test_name, &test_name, i, i + 1
227+
));
228+
batch.append_statement(simple_statement);
229+
batch_values.push(vec![]);
230+
} else {
231+
batch.append_statement(prepared_bound.clone());
232+
batch_values.push(vec![
233+
CqlValue::Text(test_name.clone()),
234+
CqlValue::Int(i),
235+
CqlValue::Int(i + 1),
236+
]);
237+
}
238+
}
239+
session.batch(&batch, batch_values).await.unwrap();
240+
verify_batch_insert(&session, &ks,&test_name, BATCH_COUNT).await;
241+
}
242+
243+
/// TODO: Remove #[ignore] once LWTs are supported with tablets.
244+
#[tokio::test]
245+
#[ignore]
246+
async fn test_cas_batch() {
247+
setup_tracing();
248+
let test_name = String::from("test_cas_batch");
249+
let (session, ks) = create_test_session(&test_name).await;
250+
251+
let prepared = prepare_insert_statement(&session, &ks, &test_name).await;
252+
let mut batch = Batch::new(BatchType::Unlogged);
253+
let mut batch_values = Vec::with_capacity(BATCH_COUNT);
254+
for i in 0..BATCH_COUNT as i32 {
255+
batch.append_statement(prepared.clone());
256+
batch_values.push(vec![
257+
CqlValue::Text(test_name.clone()),
258+
CqlValue::Int(i),
259+
CqlValue::Int(i + 1)]);
260+
}
261+
let result = session.batch(&batch, batch_values.clone()).await.unwrap();
262+
let (applied,): (bool,) = result
263+
.into_rows_result()
264+
.unwrap()
265+
.first_row::<(bool,)>()
266+
.unwrap();
267+
assert!(applied, "First CAS batch should be applied");
268+
269+
verify_batch_insert(&session, &ks, &test_name, BATCH_COUNT).await;
270+
271+
let result2 = session.batch(&batch, batch_values).await.unwrap();
272+
let (applied2,): (bool,) = result2
273+
.into_rows_result()
274+
.unwrap()
275+
.first_row::<(bool,)>()
276+
.unwrap();
277+
assert!(applied2, "Second CAS batch should not be applied");
278+
}
279+
280+
/// TODO: Remove #[ignore] once counters are supported with tablets.
281+
#[tokio::test]
282+
#[ignore]
283+
async fn test_counter_batch() {
284+
setup_tracing();
285+
let test_name = String::from("test_counter_batch");
286+
let (session, ks) = create_test_session(&test_name).await;
287+
create_counter_tables(&session, &ks).await;
288+
289+
let mut batch = Batch::new(BatchType::Counter);
290+
let mut batch_values = Vec::with_capacity(3);
291+
for i in 1..=3 {
292+
let query_str = format!("UPDATE {}.counter{} SET c = c + ? WHERE k0 = ?", ks, i);
293+
let prepared = session.prepare(Query::new(query_str)).await.unwrap();
294+
batch.append_statement(prepared);
295+
batch_values.push(vec![
296+
CqlValue::Int(i),
297+
CqlValue::Text(test_name.clone())]);
298+
}
299+
session.batch(&batch, batch_values).await.unwrap();
300+
301+
for i in 1..=3 {
302+
let query_str = format!("SELECT c FROM {}.counter{} WHERE k0 = ?", ks, i);
303+
let query_result = session
304+
.query_unpaged(query_str, (&test_name,))
305+
.await
306+
.unwrap()
307+
.into_rows_result()
308+
.unwrap();
309+
let row = query_result.single_row::<(i64,)>().unwrap();
310+
let (c,) = row;
311+
assert_eq!(c, i as i64);
312+
}
313+
}
314+
315+
/// TODO: Remove #[ignore] once counters are supported with tablets.
316+
#[tokio::test]
317+
#[ignore]
318+
async fn test_fail_logged_batch_with_counter_increment() {
319+
setup_tracing();
320+
let test_name = String::from("test_fail_logged_batch");
321+
let (session, ks) = create_test_session(&test_name).await;
322+
create_counter_tables(&session, &ks).await;
323+
324+
let mut batch = Batch::new(BatchType::Logged);
325+
let mut batch_values: Vec<Vec<CqlValue>> = Vec::with_capacity(3);
326+
for i in 1..=3 {
327+
let query_str = format!("UPDATE {}.counter{} SET c = c + ? WHERE k0 = ?", ks, i);
328+
let prepared = session.prepare(Query::new(query_str)).await.unwrap();
329+
batch.append_statement(prepared);
330+
batch_values.push(vec![
331+
CqlValue::Int(i),
332+
CqlValue::Text(test_name.clone())]);
333+
}
334+
let err = session.batch(&batch, batch_values).await.unwrap_err();
335+
assert_matches!(
336+
err,
337+
ExecutionError::BadQuery(_),
338+
"Expected a BadQuery error when using counter statements in a LOGGED batch"
339+
);
340+
}
341+
342+
/// TODO: Remove #[ignore] once counters are supported with tablets.
343+
#[tokio::test]
344+
#[ignore]
345+
async fn test_fail_counter_batch_with_non_counter_increment() {
346+
setup_tracing();
347+
let test_name = String::from("test_fail_counter_batch");
348+
let (session, ks) = create_test_session(&test_name).await;
349+
create_counter_tables(&session, &ks).await;
350+
351+
let mut batch = Batch::new(BatchType::Counter);
352+
let mut batch_values: Vec<Vec<CqlValue>> = Vec::new();
353+
for i in 1..=3 {
354+
let query_str = format!("UPDATE {}.counter{} SET c = c + ? WHERE k0 = ?", ks, i);
355+
let prepared = session.prepare(Query::new(query_str)).await.unwrap();
356+
batch.append_statement(prepared);
357+
batch_values.push(vec![
358+
CqlValue::Int(i),
359+
CqlValue::Text(test_name.clone())]);
360+
}
361+
362+
let prepared = prepare_insert_statement(&session, &ks, &test_name).await;
363+
batch.append_statement(prepared);
364+
batch_values.push(vec![
365+
CqlValue::Text(test_name.clone()),
366+
CqlValue::Int(0),
367+
CqlValue::Int(1)]);
368+
let err = session.batch(&batch, batch_values).await.unwrap_err();
369+
assert_matches!(
370+
err,
371+
ExecutionError::BadQuery(_),
372+
"Expected a BadQuery error when including a non-counter statement in a COUNTER batch"
373+
);
374+
}

scylla/tests/integration/main.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
mod authenticate;
22
mod batch;
3+
mod batch_statements;
34
mod consistency;
45
mod cql_collections;
56
mod cql_types;

0 commit comments

Comments
 (0)