Skip to main content

toasty_driver_integration_suite/
helpers.rs

1/// Helper function to look up TableId by table name (handles database-specific prefixes)
2pub fn table_id(db: &toasty::Db, table_name: &str) -> toasty_core::schema::db::TableId {
3    let schema = db.schema();
4
5    // First try exact match
6    if let Some(position) = schema.db.tables.iter().position(|t| t.name == table_name) {
7        return toasty_core::schema::db::TableId(position);
8    }
9
10    // If not found, try to find a table that ends with the given name (for database prefixes)
11    if let Some(position) = schema
12        .db
13        .tables
14        .iter()
15        .position(|t| t.name.ends_with(table_name))
16    {
17        return toasty_core::schema::db::TableId(position);
18    }
19
20    // If still not found, show available tables for debugging
21    let available_tables: Vec<_> = schema.db.tables.iter().map(|t| &t.name).collect();
22    panic!(
23        "Table '{}' not found. Available tables: {:?}",
24        table_name, available_tables
25    );
26}
27
28/// Helper function to get a single ColumnId for specified table and column
29pub fn column(
30    db: &toasty::Db,
31    table_name: &str,
32    column_name: &str,
33) -> toasty_core::schema::db::ColumnId {
34    columns(db, table_name, &[column_name])[0]
35}
36
37/// Helper function to generate a `Vec<ColumnId>` for specified table and columns
38pub fn columns(
39    db: &toasty::Db,
40    table_name: &str,
41    column_names: &[&str],
42) -> Vec<toasty_core::schema::db::ColumnId> {
43    let schema = db.schema();
44
45    // Find the table using the same logic as table_id (handles prefixes)
46    let table = schema
47        .db
48        .tables
49        .iter()
50        .find(|t| t.name == table_name || t.name.ends_with(table_name))
51        .unwrap_or_else(|| panic!("Table '{}' not found", table_name));
52
53    let table_id = table_id(db, table_name);
54
55    column_names
56        .iter()
57        .map(|col_name| {
58            let index = table
59                .columns
60                .iter()
61                .position(|c| c.name == *col_name)
62                .unwrap_or_else(|| {
63                    panic!("Column '{}' not found in table '{}'", col_name, table_name)
64                });
65
66            toasty_core::schema::db::ColumnId {
67                table: table_id,
68                index,
69            }
70        })
71        .collect()
72}
73
74use std::collections::BTreeMap;
75
76use toasty_core::{
77    driver::{Operation, operation::TypedValue},
78    stmt::{Assignment, Expr, ExprSet, Statement, Value},
79};
80
81use crate::Test;
82
83/// Resolve a value expression: a literal `Value`, or a bound param looked up in
84/// `params` (SQL drivers replace scalars with `Expr::Arg` placeholders).
85fn expr_value(expr: &Expr, params: &[Value]) -> Value {
86    match expr {
87        Expr::Value(value) => value.clone(),
88        Expr::Arg(arg) => params[arg.position].clone(),
89        other => panic!("expected a value expression, got {other:#?}"),
90    }
91}
92
93fn params_of(params: Vec<TypedValue>) -> Vec<Value> {
94    params.into_iter().map(|tv| tv.value).collect()
95}
96
97/// Pop the next logged op (a create) and return `column index -> inserted
98/// value`, normalizing SQL (`QuerySql`) vs key-value (`Insert`) ops and inlining
99/// bound params. Useful for asserting exactly what an `INSERT` writes.
100pub fn pop_insert(test: &mut Test) -> BTreeMap<usize, Value> {
101    let (op, _) = test.log().pop();
102    let (stmt, params) = match op {
103        Operation::QuerySql(q) => (q.stmt, params_of(q.params)),
104        Operation::Insert(i) => (i.stmt, params_of(i.params)),
105        other => panic!("expected an insert op, got {other:#?}"),
106    };
107    let Statement::Insert(insert) = stmt else {
108        panic!("expected an Insert statement");
109    };
110    let toasty_core::stmt::InsertTarget::Table(target) = &insert.target else {
111        panic!("expected a table insert target");
112    };
113    let ExprSet::Values(values) = &insert.source.body else {
114        panic!("expected a VALUES source");
115    };
116    // The single row is an `Expr::Record` of value/param exprs (SQL) or an
117    // already-evaluated `Value::Record` (key-value drivers).
118    let row: Vec<Value> = match &values.rows[0] {
119        Expr::Record(record) => record
120            .fields
121            .iter()
122            .map(|expr| expr_value(expr, &params))
123            .collect(),
124        Expr::Value(Value::Record(record)) => record.fields.clone(),
125        other => panic!("expected a record row, got {other:#?}"),
126    };
127    target
128        .columns
129        .iter()
130        .zip(row)
131        .map(|(col, value)| (col.index, value))
132        .collect()
133}
134
135/// Pop the next logged op (an update) and return `column index -> assigned
136/// value`, normalizing SQL (`QuerySql`) vs key-value (`UpdateByKey`) ops and
137/// inlining bound params.
138pub fn pop_update(test: &mut Test) -> BTreeMap<usize, Value> {
139    let (op, _) = test.log().pop();
140    let (assignments, params) = match op {
141        Operation::QuerySql(q) => match q.stmt {
142            Statement::Update(update) => (update.assignments, params_of(q.params)),
143            other => panic!("expected an Update statement, got {other:#?}"),
144        },
145        Operation::UpdateByKey(update) => (update.assignments, vec![]),
146        other => panic!("expected an update op, got {other:#?}"),
147    };
148    assignments
149        .iter()
150        .map(|(projection, assignment)| {
151            let Assignment::Set(expr) = assignment else {
152                panic!("expected a Set assignment, got {assignment:#?}");
153            };
154            (projection.as_slice()[0], expr_value(expr, &params))
155        })
156        .collect()
157}
158
159/// Pop the next logged op (a read) and return its filter predicate, normalizing
160/// SQL (`QuerySql` select) vs key-value (`Scan`) ops.
161pub fn pop_filter(test: &mut Test) -> Expr {
162    let (op, _) = test.log().pop();
163    match op {
164        Operation::QuerySql(q) => {
165            let Statement::Query(query) = q.stmt else {
166                panic!("expected a Query statement");
167            };
168            let ExprSet::Select(select) = query.body else {
169                panic!("expected a Select body");
170            };
171            select.filter.expr.expect("filter predicate present")
172        }
173        Operation::Scan(scan) => scan.filter.expect("filter predicate present"),
174        other => panic!("expected a query/scan op, got {other:#?}"),
175    }
176}