1use rainier_orm::sea_query::Value;
48use rainier_orm::{Dialect, Entity, ExecOutcome, ShardRoute};
49use rainier_support::{Error, Result};
50
51use crate::connection::Database;
52use crate::row::{ColumnRequest, OwnedRow};
53use crate::statement::{routing_key, Prepared};
54
55#[derive(Debug)]
59pub struct RawQuery<'a> {
60 database: &'a Database,
61 sql: String,
62 params: Vec<Value>,
63 route: ShardRoute,
64 rewrite_placeholders: bool,
65}
66
67impl<'a> RawQuery<'a> {
68 pub fn new(database: &'a Database, sql: impl Into<String>) -> Self {
70 Self {
71 database,
72 sql: sql.into(),
73 params: Vec::new(),
74 route: ShardRoute::Global,
75 rewrite_placeholders: true,
76 }
77 }
78
79 #[must_use = "this returns the query rather than modifying it in place"]
85 pub fn bind(mut self, value: impl Into<Value>) -> Self {
86 self.params.push(value.into());
87 self
88 }
89
90 #[must_use = "this returns the query rather than modifying it in place"]
92 pub fn bind_all(mut self, values: impl IntoIterator<Item = impl Into<Value>>) -> Self {
93 self.params.extend(values.into_iter().map(Into::into));
94 self
95 }
96
97 #[must_use = "this returns the query rather than modifying it in place"]
109 pub fn route_by(mut self, key: impl Into<Value>) -> Self {
110 let key = key.into();
111 self.route = match routing_key(&key) {
112 Some(key) => ShardRoute::Key(key),
113 None => ShardRoute::Global,
116 };
117 self
118 }
119
120 #[must_use = "this returns the query rather than modifying it in place"]
122 pub fn on_shard_key(mut self, key: u64) -> Self {
123 self.route = ShardRoute::Key(key);
124 self
125 }
126
127 #[must_use = "this returns the query rather than modifying it in place"]
133 pub fn raw_placeholders(mut self) -> Self {
134 self.rewrite_placeholders = false;
135 self
136 }
137
138 pub fn prepared(self) -> Prepared {
143 let sql = if self.rewrite_placeholders && self.database.dialect() == Dialect::Postgres {
144 to_numbered_placeholders(&self.sql)
145 } else {
146 self.sql
147 };
148
149 Prepared { sql, params: self.params, route: self.route }
150 }
151
152 pub async fn execute(self) -> Result<ExecOutcome> {
156 let database = self.database;
157 database.execute(self.prepared()).await
158 }
159
160 pub async fn fetch(self, columns: Vec<ColumnRequest>) -> Result<Vec<OwnedRow>> {
162 let database = self.database;
163 database.fetch(self.prepared(), columns).await
164 }
165
166 pub async fn fetch_all<E: Entity>(self) -> Result<Vec<E>> {
171 let database = self.database;
172 database.fetch_all::<E>(self.prepared()).await
173 }
174
175 pub async fn fetch_one<E: Entity>(self) -> Result<Option<E>> {
177 let database = self.database;
178 database.fetch_one::<E>(self.prepared()).await
179 }
180
181 pub async fn scalar_i64(self, column: &str) -> Result<Option<i64>> {
188 let requested = vec![ColumnRequest::new(column, rainier_orm::ColumnType::BigInt)];
189 let rows = self.fetch(requested).await?;
190
191 match rows.first() {
192 Some(row) => Ok(rainier_orm::Row::get_i64(row, column).map_err(Error::from)?),
193 None => Ok(None),
194 }
195 }
196
197 pub async fn scalar_string(self, column: &str) -> Result<Option<String>> {
199 let requested = vec![ColumnRequest::new(column, rainier_orm::ColumnType::Text)];
200 let rows = self.fetch(requested).await?;
201
202 match rows.first() {
203 Some(row) => Ok(rainier_orm::Row::get_string(row, column).map_err(Error::from)?),
204 None => Ok(None),
205 }
206 }
207
208 pub async fn column(self, column: &str) -> Result<Vec<String>> {
213 let requested = vec![ColumnRequest::new(column, rainier_orm::ColumnType::Text)];
214 let rows = self.fetch(requested).await?;
215
216 rows.iter()
217 .map(|row| rainier_orm::Row::get_string(row, column).map_err(Error::from))
218 .filter_map(Result::transpose)
219 .collect()
220 }
221}
222
223fn to_numbered_placeholders(sql: &str) -> String {
230 let mut out = String::with_capacity(sql.len() + 8);
231 let mut next = 1;
232 let mut quote: Option<char> = None;
233
234 for c in sql.chars() {
235 match quote {
236 Some(open) => {
237 out.push(c);
238 if c == open {
239 quote = None;
240 }
241 }
242 None => match c {
243 '\'' | '"' | '`' => {
244 quote = Some(c);
245 out.push(c);
246 }
247 '?' => {
248 out.push('$');
249 out.push_str(&next.to_string());
250 next += 1;
251 }
252 _ => out.push(c),
253 },
254 }
255 }
256
257 out
258}
259
260impl Database {
261 pub fn query(&self, sql: impl Into<String>) -> RawQuery<'_> {
273 RawQuery::new(self, sql)
274 }
275}
276
277#[cfg(test)]
278mod tests {
279 use super::*;
280 use crate::testing::MemoryConnection;
281
282 fn database(dialect: Dialect) -> Database {
283 Database::new(MemoryConnection::new(dialect))
284 }
285
286 #[test]
287 fn sqlite_and_mysql_keep_question_marks() {
288 for dialect in [Dialect::Sqlite, Dialect::MySql] {
289 let prepared = database(dialect)
290 .query("SELECT * FROM users WHERE team_id = ? AND name = ?")
291 .bind(7)
292 .bind("Ada")
293 .prepared();
294
295 assert_eq!(prepared.sql, "SELECT * FROM users WHERE team_id = ? AND name = ?");
296 assert_eq!(prepared.params.len(), 2);
297 }
298 }
299
300 #[test]
301 fn postgres_gets_numbered_placeholders() {
302 let prepared = database(Dialect::Postgres)
303 .query("SELECT * FROM users WHERE team_id = ? AND name = ?")
304 .bind(7)
305 .bind("Ada")
306 .prepared();
307
308 assert_eq!(prepared.sql, "SELECT * FROM users WHERE team_id = $1 AND name = $2");
309 }
310
311 #[test]
312 fn a_question_mark_inside_a_literal_is_not_a_placeholder() {
313 let prepared = database(Dialect::Postgres)
316 .query("SELECT * FROM posts WHERE title = 'why?' AND author_id = ?")
317 .bind(1)
318 .prepared();
319
320 assert_eq!(prepared.sql, "SELECT * FROM posts WHERE title = 'why?' AND author_id = $1");
321 }
322
323 #[test]
324 fn a_question_mark_inside_a_quoted_identifier_is_left_alone() {
325 let prepared = database(Dialect::Postgres)
326 .query(r#"SELECT "odd?column" FROM t WHERE id = ?"#)
327 .prepared();
328
329 assert_eq!(prepared.sql, r#"SELECT "odd?column" FROM t WHERE id = $1"#);
330 }
331
332 #[test]
333 fn raw_placeholders_leaves_postgres_alone() {
334 let sql = "SELECT * FROM events WHERE payload ? 'user_id' AND id = $1";
336 let prepared = database(Dialect::Postgres).query(sql).bind(1).raw_placeholders().prepared();
337
338 assert_eq!(prepared.sql, sql);
339 }
340
341 #[test]
342 fn a_query_is_global_until_it_is_routed() {
343 let prepared = database(Dialect::Sqlite).query("SELECT 1").prepared();
344 assert_eq!(prepared.route, ShardRoute::Global);
345
346 let routed = database(Dialect::Sqlite).query("SELECT 1").route_by(4096u64).prepared();
347 assert_eq!(routed.route, ShardRoute::Key(4096));
348 }
349
350 #[test]
351 fn a_string_key_routes_the_same_way_the_orm_routes_it() {
352 let routed = database(Dialect::Sqlite).query("SELECT 1").route_by("tenant-a").prepared();
353
354 assert_eq!(routed.route, ShardRoute::Key(rainier_orm::stable_hash(b"tenant-a")));
355 }
356
357 #[test]
358 fn a_key_with_no_routing_meaning_stays_global() {
359 let routed = database(Dialect::Sqlite).query("SELECT 1").route_by(1.5f64).prepared();
362
363 assert_eq!(routed.route, ShardRoute::Global);
364 }
365
366 #[test]
367 fn bind_all_keeps_its_order() {
368 let prepared = database(Dialect::Sqlite)
369 .query("SELECT * FROM users WHERE id IN (?, ?, ?)")
370 .bind_all([1, 2, 3])
371 .prepared();
372
373 assert_eq!(prepared.params.len(), 3);
374 assert_eq!(prepared.params[0], Value::Int(Some(1)));
375 assert_eq!(prepared.params[2], Value::Int(Some(3)));
376 }
377}