Skip to content

Commit 92fdd71

Browse files
authored
Merge pull request #1135 from wprzytula/fix-batches-to-different-tables
frame/result: allow differing TableSpecs in PreparedMetadata
2 parents 4b6ad84 + 693c9db commit 92fdd71

File tree

3 files changed

+45
-73
lines changed

3 files changed

+45
-73
lines changed

scylla-cql/src/frame/frame_errors.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -425,6 +425,7 @@ pub struct ColumnSpecParseError {
425425
pub enum ColumnSpecParseErrorKind {
426426
#[error("Invalid table spec: {0}")]
427427
TableSpecParseError(#[from] TableSpecParseError),
428+
// TODO: remove this variant before the next major release.
428429
#[error("Table spec differs across columns - got specs: {0:?} and {1:?}")]
429430
TableSpecDiffersAcrossColumns(TableSpec<'static>, TableSpec<'static>),
430431
#[error("Malformed column name: {0}")]

scylla-cql/src/frame/response/result.rs

Lines changed: 7 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -977,79 +977,22 @@ fn mk_col_spec_parse_error(
977977
}
978978
}
979979

980-
/// Deserializes table spec of a column spec in the borrowed form.
981-
///
982-
/// Checks for equality of table specs across columns, because the protocol
983-
/// does not guarantee that and we want to be sure that the assumption
984-
/// of them being all the same is correct.
985-
/// To this end, the first column's table spec is written to `known_table_spec`
986-
/// and compared with remaining columns' table spec.
987-
///
988-
/// To avoid needless allocations, it is advised to pass `known_table_spec`
989-
/// in the borrowed form, so that cloning it is cheap.
990-
fn deser_table_spec_for_col_spec<'frame>(
991-
buf: &'_ mut &'frame [u8],
992-
global_table_spec_provided: bool,
993-
known_table_spec: &'_ mut Option<TableSpec<'frame>>,
994-
col_idx: usize,
995-
) -> StdResult<TableSpec<'frame>, ColumnSpecParseError> {
996-
let table_spec = match known_table_spec {
997-
// If global table spec was provided, we simply clone it to each column spec.
998-
Some(ref known_spec) if global_table_spec_provided => known_spec.clone(),
999-
1000-
// Else, we deserialize the table spec for a column and, if we already know some
1001-
// previous spec (i.e. that of the first column), we perform equality check
1002-
// against it.
1003-
Some(_) | None => {
1004-
let table_spec =
1005-
deser_table_spec(buf).map_err(|err| mk_col_spec_parse_error(col_idx, err))?;
1006-
1007-
if let Some(ref known_spec) = known_table_spec {
1008-
// We assume that for each column, table spec is the same.
1009-
// As this is not guaranteed by the CQL protocol specification but only by how
1010-
// Cassandra and ScyllaDB work (no support for joins), we perform a sanity check here.
1011-
if known_spec.table_name != table_spec.table_name
1012-
|| known_spec.ks_name != table_spec.ks_name
1013-
{
1014-
return Err(mk_col_spec_parse_error(
1015-
col_idx,
1016-
ColumnSpecParseErrorKind::TableSpecDiffersAcrossColumns(
1017-
known_spec.clone().into_owned(),
1018-
table_spec.into_owned(),
1019-
),
1020-
));
1021-
}
1022-
} else {
1023-
// Once we have read the first column spec, we save its table spec
1024-
// in order to verify its equality with other columns'.
1025-
*known_table_spec = Some(table_spec.clone());
1026-
}
1027-
1028-
table_spec
1029-
}
1030-
};
1031-
1032-
Ok(table_spec)
1033-
}
1034-
1035980
fn deser_col_specs_generic<'frame, 'result>(
1036981
buf: &mut &'frame [u8],
1037982
global_table_spec: Option<TableSpec<'frame>>,
1038983
col_count: usize,
1039984
make_col_spec: fn(&'frame str, ColumnType<'result>, TableSpec<'frame>) -> ColumnSpec<'result>,
1040985
deser_type: fn(&mut &'frame [u8]) -> StdResult<ColumnType<'result>, CqlTypeParseError>,
1041986
) -> StdResult<Vec<ColumnSpec<'result>>, ColumnSpecParseError> {
1042-
let global_table_spec_provided = global_table_spec.is_some();
1043-
let mut known_table_spec = global_table_spec;
1044-
1045987
let mut col_specs = Vec::with_capacity(col_count);
1046988
for col_idx in 0..col_count {
1047-
let table_spec = deser_table_spec_for_col_spec(
1048-
buf,
1049-
global_table_spec_provided,
1050-
&mut known_table_spec,
1051-
col_idx,
1052-
)?;
989+
let table_spec = match global_table_spec {
990+
// If global table spec was provided, we simply clone it to each column spec.
991+
Some(ref known_spec) => known_spec.clone(),
992+
993+
// Else, we deserialize the table spec for a column.
994+
None => deser_table_spec(buf).map_err(|err| mk_col_spec_parse_error(col_idx, err))?,
995+
};
1053996

1054997
let name = types::read_string(buf).map_err(|err| mk_col_spec_parse_error(col_idx, err))?;
1055998
let typ = deser_type(buf).map_err(|err| mk_col_spec_parse_error(col_idx, err))?;
@@ -1062,10 +1005,6 @@ fn deser_col_specs_generic<'frame, 'result>(
10621005
/// Deserializes col specs (part of ResultMetadata or PreparedMetadata)
10631006
/// in the borrowed form.
10641007
///
1065-
/// Checks for equality of table specs across columns, because the protocol
1066-
/// does not guarantee that and we want to be sure that the assumption
1067-
/// of them being all the same is correct.
1068-
///
10691008
/// To avoid needless allocations, it is advised to pass `global_table_spec`
10701009
/// in the borrowed form, so that cloning it is cheap.
10711010
fn deser_col_specs_borrowed<'frame>(
@@ -1085,10 +1024,6 @@ fn deser_col_specs_borrowed<'frame>(
10851024
/// Deserializes col specs (part of ResultMetadata or PreparedMetadata)
10861025
/// in the owned form.
10871026
///
1088-
/// Checks for equality of table specs across columns, because the protocol
1089-
/// does not guarantee that and we want to be sure that the assumption
1090-
/// of them being all the same is correct.
1091-
///
10921027
/// To avoid needless allocations, it is advised to pass `global_table_spec`
10931028
/// in the borrowed form, so that cloning it is cheap.
10941029
fn deser_col_specs_owned<'frame>(

scylla/src/transport/session_test.rs

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -457,7 +457,7 @@ async fn test_batch() {
457457
.await
458458
.unwrap();
459459

460-
// TODO: Add API, that supports binding values to statements in batch creation process,
460+
// TODO: Add API that supports binding values to statements in batch creation process,
461461
// to avoid problem of statements/values count mismatch
462462
use crate::batch::Batch;
463463
let mut batch: Batch = Default::default();
@@ -537,6 +537,42 @@ async fn test_batch() {
537537
assert_eq!(results, vec![(4, 20, String::from("foobar"))]);
538538
}
539539

540+
// This is a regression test for #1134.
541+
#[tokio::test]
542+
async fn test_batch_to_multiple_tables() {
543+
setup_tracing();
544+
let session = create_new_session_builder().build().await.unwrap();
545+
let ks = unique_keyspace_name();
546+
547+
session.ddl(format!("CREATE KEYSPACE IF NOT EXISTS {} WITH REPLICATION = {{'class' : 'NetworkTopologyStrategy', 'replication_factor' : 1}}", ks)).await.unwrap();
548+
session.use_keyspace(&ks, true).await.unwrap();
549+
session
550+
.ddl("CREATE TABLE IF NOT EXISTS t_batch1 (a int, b int, c text, primary key (a, b))")
551+
.await
552+
.unwrap();
553+
session
554+
.ddl("CREATE TABLE IF NOT EXISTS t_batch2 (a int, b int, c text, primary key (a, b))")
555+
.await
556+
.unwrap();
557+
558+
let prepared_statement = session
559+
.prepare(
560+
"
561+
BEGIN BATCH
562+
INSERT INTO t_batch1 (a, b, c) VALUES (?, ?, ?);
563+
INSERT INTO t_batch2 (a, b, c) VALUES (?, ?, ?);
564+
APPLY BATCH;
565+
",
566+
)
567+
.await
568+
.unwrap();
569+
570+
session
571+
.execute_unpaged(&prepared_statement, (1, 2, "ala", 4, 5, "ma"))
572+
.await
573+
.unwrap();
574+
}
575+
540576
#[tokio::test]
541577
async fn test_token_calculation() {
542578
setup_tracing();

0 commit comments

Comments
 (0)