docs: ADR-0036 (Proposed) — bind literal DML values, verbatim text only for expressions/queries
Records the decision that advanced-mode SQL DML should stop handing literal data values to the engine as text and instead parse/validate/bind them through the DSL's proven path — closing the value-validation gap, the hint/highlight gap, and the offending-value-in-errors gap together. Verbatim text stays for expressions, WHERE, INSERT…SELECT, and SELECT (full SQL surface preserved; ADR-0026's limited Expr not imposed). Narrows ADR-0030 §4 / ADR-0033 §10 once accepted; SELECT half of §4 stands. Includes a characterization test (tests/sql_insert.rs) proving the bind-layer gap: the DSL rejects the malformed date 2025/01/15, advanced-mode SQL accepts it. Forward-notes added to ADR-0030/0033; README index updated. Status: Proposed (design + /runda done; pending go-ahead to implement).
This commit is contained in:
+54
-1
@@ -21,9 +21,11 @@
|
||||
//! real reconstructed SQL.
|
||||
|
||||
use rdbms_playground::db::{Database, DbError, InsertResult};
|
||||
use rdbms_playground::dsl::{ColumnSpec, Command, Type, parse_command};
|
||||
use rdbms_playground::dsl::{ColumnSpec, Command, Type, Value, parse_command};
|
||||
use rdbms_playground::event::AppEvent;
|
||||
use rdbms_playground::persistence::Persistence;
|
||||
use rdbms_playground::project;
|
||||
use rdbms_playground::runtime::run_replay;
|
||||
|
||||
fn rt() -> tokio::runtime::Runtime {
|
||||
tokio::runtime::Builder::new_current_thread()
|
||||
@@ -1033,3 +1035,54 @@ fn autofill_preserves_on_conflict_clause() {
|
||||
let rows = csv_rows(&project, "t");
|
||||
assert_eq!(rows.len(), 1, "one row landed");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sql_dml_skips_app_level_value_validation_that_the_dsl_enforces() {
|
||||
// CHARACTERIZATION of a real divergence (ADR pending — see the
|
||||
// verbatim-vs-structural DML discussion). The DSL insert path validates
|
||||
// a value against the playground type system at *bind* time
|
||||
// (`Value::bind_for_column` → `validate_date`); the verbatim SQL insert
|
||||
// path hands the literal straight to the engine, whose STRICT `date`
|
||||
// column is TEXT and accepts any string. `2025/01/15` is a malformed
|
||||
// date (slashes, not dashes): the DSL rejects it, advanced-mode SQL
|
||||
// currently accepts it. When the forthcoming ADR routes SQL literal
|
||||
// values through the same validation, FLIP the SQL assertion to expect
|
||||
// a rejection.
|
||||
let (project, db, _d) = open_project_db();
|
||||
let r = rt();
|
||||
r.block_on(db.create_table(
|
||||
"T".to_string(),
|
||||
vec![
|
||||
ColumnSpec::new("id", Type::Int),
|
||||
ColumnSpec::new("d", Type::Date),
|
||||
],
|
||||
vec!["id".to_string()],
|
||||
Some("create table T with pk id(int)".to_string()),
|
||||
))
|
||||
.expect("create T(id int pk, d date)");
|
||||
|
||||
// DSL path — validates the `date` and rejects the malformed value.
|
||||
let dsl = r.block_on(db.insert(
|
||||
"T".to_string(),
|
||||
Some(vec!["id".to_string(), "d".to_string()]),
|
||||
vec![Value::Number("1".to_string()), Value::Text("2025/01/15".to_string())],
|
||||
Some("insert".to_string()),
|
||||
));
|
||||
assert!(
|
||||
dsl.is_err(),
|
||||
"the DSL insert path validates `date` and rejects 2025/01/15; got {dsl:?}"
|
||||
);
|
||||
|
||||
// SQL path (advanced mode, full pipeline) — currently ACCEPTS it.
|
||||
std::fs::write(
|
||||
project.path().join("ins.commands"),
|
||||
"insert into T (id, d) values (2, '2025/01/15')\n",
|
||||
)
|
||||
.expect("write script");
|
||||
let events = r.block_on(run_replay(&db, project.path(), "ins.commands"));
|
||||
assert!(
|
||||
matches!(events.last(), Some(AppEvent::ReplayCompleted { count, .. }) if *count == 1),
|
||||
"CHARACTERIZATION: verbatim SQL insert skips `date` validation and \
|
||||
accepts 2025/01/15 — the gap the ADR will close; events: {events:?}"
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user