scylladb / scylla-rust-driver

Async CQL driver for Rust, optimized for ScyllaDB!
Apache License 2.0
583 stars 104 forks source link

PreparedMetadata is not updated during repreparation #575

Open cvybhu opened 2 years ago

cvybhu commented 2 years ago

A PreparedStatement sometimes has to be prepared again. This happens when its removed from the cache, or the schema changes.

PreparedStatement contains PreparedMetadata, which keeps types of values bound to the query. After a schema change this field should be updated during repreparation. For now this doesn't cause any issues, but in the future we might add more validation to the bound values (#463) and some problems may arise.

Here's an example, which creates a user type, prepares a statement and adds a new field to the user type. The PreparedStatement still thinks that there's only one field in the user type.

use scylla::{Session, SessionBuilder};

#[tokio::main]
async fn main() {
    let uri = std::env::var("SCYLLA_URI").unwrap_or_else(|_| "127.0.0.1:9042".to_string());
    let session: Session = SessionBuilder::new().known_node(uri).build().await.unwrap();

    session
        .query("DROP KEYSPACE IF EXISTS ks", ())
        .await
        .unwrap();
    session
    .query(
        "CREATE KEYSPACE IF NOT EXISTS ks WITH REPLICATION = {'class' : 'SimpleStrategy', 'replication_factor' : 1}",
        &[],
    )
    .await.unwrap();
    session.use_keyspace("ks", true).await.unwrap();

    session.query("CREATE TYPE mytype (a int)", ()).await.unwrap();
    session
        .query("CREATE TABLE tab (p int primary key, m mytype)", ())
        .await
        .unwrap();

    let prepared = session.prepare("INSERT INTO tab (p, m) VALUES (0, ?)").await.unwrap();
    println!("{:#?}", prepared.get_prepared_metadata());

    session
        .query("ALTER TYPE mytype ADD b int", ())
        .await
        .unwrap();

    session.execute(&prepared, ((1, 2),)).await.unwrap();

    println!("{:#?}", prepared.get_prepared_metadata());
}

Output:

PreparedMetadata {
    col_count: 1,
    pk_indexes: [],
    col_specs: [
        ColumnSpec {
            table_spec: TableSpec {
                ks_name: "ks",
                table_name: "tab",
            },
            name: "m",
            typ: UserDefinedType {
                type_name: "mytype",
                keyspace: "ks",
                field_types: [
                    (
                        "a",
                        Int,
                    ),
                ],
            },
        },
    ],
}
PreparedMetadata {
    col_count: 1,
    pk_indexes: [],
    col_specs: [
        ColumnSpec {
            table_spec: TableSpec {
                ks_name: "ks",
                table_name: "tab",
            },
            name: "m",
            typ: UserDefinedType {
                type_name: "mytype",
                keyspace: "ks",
                field_types: [
                    (
                        "a",
                        Int,
                    ),
                ],
            },
        },
    ],
}
piodul commented 2 years ago

Hmm, that's a tough pickle. I'm not sure this can be fixed without changes to the protocol/database. The issue with changing types of columns sounds prone to a race like in the prepared SELECT * fiasco (https://docs.datastax.com/en/developer/java-driver/3.0/manual/statements/prepared/#avoid-preparing-select-queries), i.e. another client may quickly re-prepare the statement and our current client will not even notice that it needs to update the metadata.

I think there are some fixes for the problem in protocol version 5, although I'm not familiar with them and ScyllaDB doesn't implement V5 (apart from the duration datatype, AFAIK).

wprzytula commented 6 months ago

The problem has been described in #986. Let's consider closing this. @Lorak-mmk @muzarski

Lorak-mmk commented 6 months ago

The problem is described, but not fixed.