1use std::marker::PhantomData;
2
3use sofie_core::{Cast, Error, Result, SqlValue};
4
5use crate::{BoxFuture, Decoding, Read, RowSink, Source};
6
7pub struct Sql<'r, T> {
9 read: &'r Read,
10 statement: &'static str,
11 params: Vec<SqlValue>,
12 marker: PhantomData<fn() -> T>,
13}
14
15impl Read {
16 #[must_use]
19 pub fn sql<T: Source>(&self, sql: &'static str) -> Sql<'_, T> {
20 Sql {
21 read: self,
22 statement: sql,
23 params: Vec::new(),
24 marker: PhantomData,
25 }
26 }
27}
28
29impl<T: Source> Sql<'_, T> {
30 #[must_use]
31 pub fn bind<V: Cast>(mut self, value: &V) -> Self {
32 value.encode(&mut self.params);
33 self
34 }
35
36 pub async fn all(self) -> Result<Vec<T>> {
37 if self.read.owner().is_some() {
38 return Err(Error::internal_message(format!(
39 "raw SQL cannot be kept to one owner's records; read `{}` with the query \
40 builder in an owned area",
41 std::any::type_name::<T>()
42 )));
43 }
44 let role = cfg!(feature = "module-roles").then_some(<T::Base as crate::Aggregate>::MODULE);
45 let mut rows = Decoding::new(T::column_types(), T::decode);
46 self.read
47 .query_read_only(role, self.statement, &self.params, &mut rows)
48 .await?;
49 Ok(rows.into_decoded())
50 }
51}
52
53pub(crate) fn refuse_read_only<'a>(sql: &str) -> BoxFuture<'a, Result<()>> {
54 Box::pin(std::future::ready(Err(Error::internal_message(format!(
55 "this database cannot run raw SQL read-only, so `{sql}` was not run"
56 )))))
57}
58
59impl Read {
60 async fn query_read_only(
61 &self,
62 role: Option<&str>,
63 sql: &str,
64 params: &[SqlValue],
65 rows: &mut dyn RowSink,
66 ) -> Result<()> {
67 #[cfg(feature = "live")]
68 crate::live::read_anything();
69 let driver = self.db.driver();
70 #[cfg(feature = "etag")]
71 if crate::read_versions::tracking() {
72 let mut fingerprinted = crate::read_versions::Fingerprinted::new(rows);
73 driver
74 .query_read_only(role, sql, params, &mut fingerprinted)
75 .await?;
76 fingerprinted.record();
77 return Ok(());
78 }
79 driver.query_read_only(role, sql, params, rows).await
80 }
81}