sqlx_cloudflare_d1/batch.rs
1use std::future::Future;
2
3use futures_core::Stream;
4use futures_util::{stream, TryFutureExt};
5use sqlx_core::error::Error;
6use sqlx_core::executor::Execute;
7use sqlx_core::query::Query;
8use worker::send::SendFuture;
9
10use crate::{js, D1Arguments, D1BatchResult, D1Connection, D1QueryResult, D1};
11
12impl D1Connection {
13 /// Runs `queries` as one D1 batch: in order, in one round trip, and
14 /// atomically -- if any statement fails, none of them take effect.
15 ///
16 /// This is the D1 answer to a transaction, which it does not support
17 /// across calls (see [`D1TransactionManager`](crate::D1TransactionManager)).
18 /// Returns what each statement did, in the order given; for the rows each
19 /// statement returns as well, see [`fetch_batch`](Self::fetch_batch).
20 ///
21 /// ```no_run
22 /// # async fn transfer(
23 /// # conn: &sqlx_cloudflare_d1::D1Connection,
24 /// # amount: i64,
25 /// # from: i64,
26 /// # to: i64,
27 /// # ) -> Result<(), sqlx::Error> {
28 /// let results = conn
29 /// .execute_batch([
30 /// sqlx::query("UPDATE accounts SET balance = balance - ?1 WHERE id = ?2")
31 /// .bind(amount)
32 /// .bind(from),
33 /// sqlx::query("UPDATE accounts SET balance = balance + ?1 WHERE id = ?2")
34 /// .bind(amount)
35 /// .bind(to),
36 /// ])
37 /// .await?;
38 /// # Ok(())
39 /// # }
40 /// ```
41 ///
42 /// # Errors
43 ///
44 /// When binding an argument fails, or when D1 rejects any statement -- in
45 /// which case the whole batch was rolled back.
46 pub fn execute_batch<'q, I>(
47 &self,
48 queries: I,
49 ) -> impl Future<Output = Result<Vec<D1QueryResult>, Error>> + Send + '_
50 where
51 I: IntoIterator<Item = Query<'q, D1, D1Arguments>>,
52 {
53 self.run_batch(queries).map_ok(|results| {
54 results
55 .into_iter()
56 .map(|result| result.into_parts().1)
57 .collect()
58 })
59 }
60
61 /// Runs `queries` as one D1 batch, as
62 /// [`execute_batch`](Self::execute_batch) does, and streams each
63 /// statement's rows along with what it did, in the order given.
64 ///
65 /// The shape sqlc's `:batchexec`, `:batchone` and `:batchmany` queries
66 /// take, each a stream with one item per statement -- but atomic, and in
67 /// one round trip. So unlike a loop of queries it is not lazy: the whole
68 /// batch runs when the stream is first polled, and a failure is the one
69 /// item it yields, with nothing applied.
70 ///
71 /// ```no_run
72 /// # async fn users(conn: &sqlx_cloudflare_d1::D1Connection) -> Result<(), sqlx::Error> {
73 /// use futures_util::TryStreamExt;
74 /// use sqlx::Row;
75 ///
76 /// let names: Vec<String> = conn
77 /// .fetch_batch([1_i64, 2, 3].map(|id| {
78 /// sqlx::query("SELECT name FROM users WHERE id = ?").bind(id)
79 /// }))
80 /// .map_ok(|result| result.rows()[0].get("name"))
81 /// .try_collect()
82 /// .await?;
83 /// # Ok(())
84 /// # }
85 /// ```
86 ///
87 /// # Rows come back keyed by name
88 ///
89 /// D1's `batch()` returns rows as objects, not the arrays the executor
90 /// reads. Two columns with one name -- `SELECT a.id, b.id` -- collapse to
91 /// the last, and a column named like an integer is ordered first. Alias
92 /// such columns apart inside a batch.
93 ///
94 /// # Errors
95 ///
96 /// The stream's one item is an error when binding an argument fails, or
97 /// when D1 rejects any statement -- in which case the whole batch was
98 /// rolled back.
99 pub fn fetch_batch<'q, I>(
100 &self,
101 queries: I,
102 ) -> impl Stream<Item = Result<D1BatchResult, Error>> + Send + '_
103 where
104 I: IntoIterator<Item = Query<'q, D1, D1Arguments>>,
105 {
106 self.run_batch(queries)
107 .map_ok(|results| stream::iter(results.into_iter().map(Ok)))
108 .try_flatten_stream()
109 }
110
111 fn run_batch<'q, I>(
112 &self,
113 queries: I,
114 ) -> impl Future<Output = Result<Vec<D1BatchResult>, Error>> + Send + '_
115 where
116 I: IntoIterator<Item = Query<'q, D1, D1Arguments>>,
117 {
118 // Taken apart here, before the future, so it holds only owned SQL and
119 // arguments: the queries themselves need not outlive this call.
120 let queries = queries
121 .into_iter()
122 .map(|mut query| {
123 let arguments = query.take_arguments().map_err(Error::Encode)?;
124 Ok((query.sql(), arguments.unwrap_or_default()))
125 })
126 .collect::<Result<Vec<_>, Error>>();
127
128 SendFuture::new(async move {
129 let queries = queries?;
130
131 // D1 rejects an empty batch -- "No SQL statements detected" --
132 // but running nothing has nothing to report, and needs no round
133 // trip to find that out.
134 if queries.is_empty() {
135 return Ok(Vec::new());
136 }
137
138 let statements = queries
139 .iter()
140 .map(|(sql, arguments)| js::prepare(&self.db, sql.as_str(), arguments.values()))
141 .collect::<Result<Vec<_>, _>>()?;
142
143 js::batch(&self.db, statements).await
144 })
145 }
146}