1pub mod builder;
39pub mod fragment;
40pub mod pre_query;
41pub mod sql_builder;
42pub mod statements;
43
44pub use sql_builder::{SqlBuilder, SqlParam};
46
47use crate::backend::SqlDialect;
48use crate::config::AppConfig;
49use crate::plan::{ActionPlan, CrudPlan, DbActionPlan};
50
51#[derive(Debug)]
66pub struct MainQuery {
67 pub tx_vars: Option<SqlBuilder>,
69 pub pre_req: Option<SqlBuilder>,
71 pub mutation: Option<SqlBuilder>,
77 pub main: Option<SqlBuilder>,
79}
80
81impl MainQuery {
82 pub fn empty() -> Self {
84 Self {
85 tx_vars: None,
86 pre_req: None,
87 mutation: None,
88 main: None,
89 }
90 }
91}
92
93#[allow(clippy::too_many_arguments)]
114pub fn main_query(
115 action_plan: &ActionPlan,
116 config: &AppConfig,
117 dialect: &dyn SqlDialect,
118 method: &str,
119 path: &str,
120 role: Option<&str>,
121 headers_json: Option<&str>,
122 cookies_json: Option<&str>,
123 claims_json: Option<&str>,
124) -> MainQuery {
125 let tx_vars = Some(pre_query::tx_var_query(
127 config,
128 dialect,
129 method,
130 path,
131 role,
132 headers_json,
133 cookies_json,
134 claims_json,
135 ));
136
137 let pre_req = config.db_pre_request.as_ref().map(pre_query::pre_req_query);
139
140 let (mutation, main) = match action_plan {
142 ActionPlan::Db(db_plan) => match db_plan {
143 DbActionPlan::DbCrud { plan, .. } => {
144 let (m, q) = build_crud_query(plan, config, dialect);
145 (m, Some(q))
146 }
147 DbActionPlan::MayUseDb(_inspect) => {
148 (None, None)
150 }
151 },
152 ActionPlan::NoDb(_) => {
153 (None, None)
155 }
156 };
157
158 MainQuery {
159 tx_vars,
160 pre_req,
161 mutation,
162 main,
163 }
164}
165
166fn build_crud_query(
179 plan: &CrudPlan,
180 config: &AppConfig,
181 dialect: &dyn SqlDialect,
182) -> (Option<SqlBuilder>, SqlBuilder) {
183 match plan {
184 CrudPlan::WrappedReadPlan {
185 read_plan,
186 headers_only,
187 handler,
188 ..
189 } => (
190 None,
191 statements::main_read(
192 read_plan,
193 None,
194 config.db_max_rows,
195 *headers_only,
196 Some(&handler.0),
197 dialect,
198 ),
199 ),
200 CrudPlan::MutateReadPlan {
201 read_plan,
202 mutate_plan,
203 handler,
204 ..
205 } => {
206 let return_representation = !mutate_plan.returning().is_empty();
207 if dialect.supports_dml_cte() {
208 (
209 None,
210 statements::main_write(
211 mutate_plan,
212 read_plan,
213 return_representation,
214 Some(&handler.0),
215 dialect,
216 ),
217 )
218 } else {
219 let (mutation, agg) = statements::main_write_split(
220 mutate_plan,
221 read_plan,
222 return_representation,
223 Some(&handler.0),
224 dialect,
225 );
226 (Some(mutation), agg)
227 }
228 }
229 CrudPlan::CallReadPlan {
230 call_plan, handler, ..
231 } => (
232 None,
233 statements::main_call(
234 call_plan,
235 None,
236 config.db_max_rows,
237 Some(&handler.0),
238 dialect,
239 ),
240 ),
241 }
242}
243
244#[cfg(test)]
249mod tests {
250 use super::*;
251 use crate::api_request::types::{InvokeMethod, Mutation, Payload};
252 use crate::plan::TxMode;
253 use crate::plan::call_plan::{CallArgs, CallParams, CallPlan};
254 use crate::plan::mutate_plan::{InsertPlan, MutatePlan};
255 use crate::plan::read_plan::{ReadPlan, ReadPlanTree};
256 use crate::plan::types::*;
257 use crate::schema_cache::media_handler::{MediaHandler, ResolvedHandler};
258 use crate::test_helpers::TestPgDialect;
259 use crate::types::identifiers::QualifiedIdentifier;
260 use crate::types::media::MediaType;
261 use bytes::Bytes;
262 use smallvec::SmallVec;
263
264 fn dialect() -> &'static dyn SqlDialect {
265 &TestPgDialect
266 }
267
268 fn test_qi() -> QualifiedIdentifier {
269 QualifiedIdentifier::new("test_api", "users")
270 }
271
272 fn test_config() -> AppConfig {
273 let mut config = AppConfig::default();
274 config.db_schemas = vec!["test_api".to_string()];
275 config.db_anon_role = Some("web_anon".to_string());
276 config
277 }
278
279 fn select_field(name: &str) -> CoercibleSelectField {
280 CoercibleSelectField {
281 field: CoercibleField::unknown(name.into(), SmallVec::new()),
282 agg_function: None,
283 agg_cast: None,
284 cast: None,
285 alias: None,
286 }
287 }
288
289 fn default_handler() -> ResolvedHandler {
290 (MediaHandler::BuiltinOvAggJson, MediaType::ApplicationJson)
291 }
292
293 fn make_read_plan() -> ActionPlan {
294 let mut rp = ReadPlan::root(test_qi());
295 rp.select = vec![select_field("id"), select_field("name")];
296 let tree = ReadPlanTree::leaf(rp);
297
298 ActionPlan::Db(DbActionPlan::DbCrud {
299 is_explain: false,
300 plan: CrudPlan::WrappedReadPlan {
301 read_plan: tree,
302 tx_mode: TxMode::default_mode(),
303 handler: default_handler(),
304 media: MediaType::ApplicationJson,
305 headers_only: false,
306 qi: test_qi(),
307 },
308 })
309 }
310
311 fn make_mutate_plan() -> ActionPlan {
312 let rp = ReadPlan::root(test_qi());
313 let tree = ReadPlanTree::leaf(rp);
314
315 let mutate = MutatePlan::Insert(InsertPlan {
316 into: test_qi(),
317 columns: vec![CoercibleField::from_column(
318 "name".into(),
319 SmallVec::new(),
320 "text".into(),
321 )],
322 body: Payload::RawJSON(Bytes::from(r#"[{"name":"Alice"}]"#)),
323 on_conflict: None,
324 where_: vec![],
325 returning: vec![select_field("id"), select_field("name")],
326 pk_cols: vec!["id".into()],
327 apply_defaults: false,
328 });
329
330 ActionPlan::Db(DbActionPlan::DbCrud {
331 is_explain: false,
332 plan: CrudPlan::MutateReadPlan {
333 read_plan: tree,
334 mutate_plan: mutate,
335 tx_mode: TxMode::default_mode(),
336 handler: default_handler(),
337 media: MediaType::ApplicationJson,
338 mutation: Mutation::MutationCreate,
339 qi: test_qi(),
340 },
341 })
342 }
343
344 fn make_call_plan() -> ActionPlan {
345 use crate::schema_cache::routine::{ReturnType, Routine, Volatility};
346 use smallvec::smallvec;
347
348 let rp = ReadPlan::root(test_qi());
349 let tree = ReadPlanTree::leaf(rp);
350
351 let call = CallPlan {
352 qi: QualifiedIdentifier::new("test_api", "get_time"),
353 params: CallParams::KeyParams(vec![]),
354 args: CallArgs::JsonArgs(None),
355 scalar: true,
356 set_of_scalar: false,
357 filter_fields: vec![],
358 returning: vec![],
359 };
360
361 ActionPlan::Db(DbActionPlan::DbCrud {
362 is_explain: false,
363 plan: CrudPlan::CallReadPlan {
364 read_plan: tree,
365 call_plan: call,
366 tx_mode: TxMode::default_mode(),
367 proc: Routine {
368 schema: "test_api".into(),
369 name: "get_time".into(),
370 params: smallvec![],
371 return_type: ReturnType::Single(crate::schema_cache::routine::PgType::Scalar(
372 QualifiedIdentifier::new("pg_catalog", "timestamptz"),
373 )),
374 is_variadic: false,
375 volatility: Volatility::Stable,
376 description: None,
377 executable: true,
378 },
379 handler: default_handler(),
380 media: MediaType::ApplicationJson,
381 inv_method: InvokeMethod::InvRead(false),
382 qi: QualifiedIdentifier::new("test_api", "get_time"),
383 },
384 })
385 }
386
387 #[test]
392 fn test_main_query_read() {
393 let plan = make_read_plan();
394 let config = test_config();
395
396 let mq = main_query(
397 &plan,
398 &config,
399 dialect(),
400 "GET",
401 "/users",
402 None,
403 None,
404 None,
405 None,
406 );
407
408 assert!(mq.tx_vars.is_some());
409 assert!(mq.pre_req.is_none()); assert!(mq.main.is_some());
411
412 let main_sql = mq.main.unwrap().sql().to_string();
413 assert!(main_sql.contains("dbrst_source"));
414 assert!(main_sql.contains("\"users\""));
415 }
416
417 #[test]
418 fn test_main_query_mutate() {
419 let plan = make_mutate_plan();
420 let config = test_config();
421
422 let mq = main_query(
423 &plan,
424 &config,
425 dialect(),
426 "POST",
427 "/users",
428 None,
429 None,
430 None,
431 None,
432 );
433
434 assert!(mq.main.is_some());
435 let main_sql = mq.main.unwrap().sql().to_string();
436 assert!(main_sql.contains("INSERT INTO"));
437 }
438
439 #[test]
440 fn test_main_query_call() {
441 let plan = make_call_plan();
442 let config = test_config();
443
444 let mq = main_query(
445 &plan,
446 &config,
447 dialect(),
448 "POST",
449 "/rpc/get_time",
450 None,
451 None,
452 None,
453 None,
454 );
455
456 assert!(mq.main.is_some());
457 let main_sql = mq.main.unwrap().sql().to_string();
458 assert!(main_sql.contains("get_time"));
459 }
460
461 #[test]
462 fn test_main_query_with_pre_request() {
463 let plan = make_read_plan();
464 let mut config = test_config();
465 config.db_pre_request = Some(QualifiedIdentifier::new("test_api", "check_request"));
466
467 let mq = main_query(
468 &plan,
469 &config,
470 dialect(),
471 "GET",
472 "/users",
473 None,
474 None,
475 None,
476 None,
477 );
478
479 assert!(mq.pre_req.is_some());
480 let pre_sql = mq.pre_req.unwrap().sql().to_string();
481 assert!(pre_sql.contains("check_request"));
482 }
483
484 #[test]
485 fn test_main_query_info_plan() {
486 let plan = ActionPlan::NoDb(crate::plan::InfoPlan::SchemaInfoPlan);
487 let config = test_config();
488
489 let mq = main_query(
490 &plan,
491 &config,
492 dialect(),
493 "OPTIONS",
494 "/",
495 None,
496 None,
497 None,
498 None,
499 );
500
501 assert!(mq.main.is_none());
503 assert!(mq.tx_vars.is_some());
505 }
506
507 #[test]
508 fn test_main_query_empty() {
509 let mq = MainQuery::empty();
510 assert!(mq.tx_vars.is_none());
511 assert!(mq.pre_req.is_none());
512 assert!(mq.main.is_none());
513 }
514}