Skip to main content

eventsdb_core/
params.rs

1//! What a caller binds into its own SQL.
2//!
3//! SQLite has one parameter space and four spellings for a slot in it: `?`,
4//! `?N`, `:name`, `@name` and `$name`. A store that accepted only the numbered
5//! form would not be refusing anything on an invariant's behalf — it would
6//! simply be handing the work back, because SQL written with named
7//! placeholders then has to be rewritten before it can be run, and rewriting
8//! it means reading it: skipping string literals, and the literals here
9//! include `json_extract(data, '$.n')`.
10//!
11//! So both forms are bound, and [`Params`] is which one a call is making.
12
13use serde_json::{Map, Value};
14
15/// Values for a statement's placeholders, by position or by name.
16///
17/// Not `#[non_exhaustive]`: a caller matches on this, there is no third way to
18/// bind a parameter in SQLite, and a variant for "no parameters" would be a
19/// second spelling of `Positional(vec![])`.
20///
21/// # A name carries its sigil
22///
23/// The name is the placeholder **as written in the SQL**, punctuation
24/// included: `(":kind", json!("placed"))` for `:kind`, `("$stream", ..)` for
25/// `$stream`. That is rusqlite's rule, not this crate's addition — "the
26/// initial `:` or `$` or `@` or `?` used to specify the parameter is included
27/// as part of the name" — and a name without it matches no placeholder at all,
28/// which is reported as [`crate::Error::Validation`] rather than bound to
29/// nothing.
30///
31/// # Every name the statement declares must be supplied
32///
33/// A `Named` set is checked against the prepared statement before it runs:
34/// each parameter the statement declares has to be among the names given. One
35/// that is not is [`crate::Error::Validation`] naming it.
36///
37/// The check is this crate's, because rusqlite has none — "unbound named
38/// parameters will be left to the value they previously were bound with,
39/// falling back to `NULL`". A placeholder the caller forgot would otherwise
40/// come back as a query that ran, answered, and silently meant something else:
41/// `WHERE kind = :kind` with no `:kind` bound is `WHERE kind = NULL`, which
42/// matches nothing and reports no fault. Mixing a bare `?` into a statement
43/// bound by name is refused for the same reason — nothing supplies it.
44///
45/// A `Positional` set is not checked here because SQLite checks it: a count
46/// that does not match the statement's is refused before the statement runs.
47///
48/// # Building one
49///
50/// A `Vec` converts, and it is the positional set; a JSON object converts, and
51/// it is the named one. There is deliberately **no** conversion from a
52/// `Vec<(String, Value)>`: a second `From` over a `Vec` would make every
53/// existing `query(sql, vec![])` ambiguous, since an empty literal cannot say
54/// which element type it has. Named pairs in a `Vec` are still available by
55/// naming the variant.
56///
57/// ```
58/// # use eventsdb_core::Params;
59/// # use serde_json::{json, Map};
60/// let by_position: Params = vec![json!("placed")].into();
61/// let nothing: Params = vec![].into();
62/// assert_eq!(nothing, Params::Positional(Vec::new()));
63///
64/// // A named set, as an object …
65/// let by_name: Params = json!({ ":kind": "placed" }).as_object().cloned().unwrap().into();
66/// // … or as the variant itself.
67/// let also_by_name = Params::Named(vec![(":kind".to_string(), json!("placed"))]);
68/// assert_eq!(by_name, also_by_name);
69///
70/// let mut map = Map::new();
71/// map.insert(":kind".to_string(), json!("placed"));
72/// assert_eq!(Params::from(map), also_by_name);
73/// ```
74#[derive(Debug, Clone, PartialEq)]
75pub enum Params {
76    /// Bound to `?`, `?1`, `?2` … in the order given.
77    Positional(Vec<Value>),
78    /// Bound to `:name`, `@name` or `$name`, sigil included in the key.
79    Named(Vec<(String, Value)>),
80}
81
82impl From<Vec<Value>> for Params {
83    fn from(values: Vec<Value>) -> Self {
84        Params::Positional(values)
85    }
86}
87
88impl From<Map<String, Value>> for Params {
89    /// A JSON object, one entry per placeholder. `Map` preserves whatever
90    /// order it was built with; binding is by name, so order does not reach
91    /// the statement either way.
92    fn from(map: Map<String, Value>) -> Self {
93        Params::Named(map.into_iter().collect())
94    }
95}