evergreen/common/
jq.rs

1//! JSON Query Parser
2use crate as eg;
3use eg::db;
4use eg::idl;
5use eg::osrf::message;
6use eg::result::EgResult;
7use eg::EgValue;
8use std::sync::Arc;
9
10const JOIN_WITH_AND: &str = "AND";
11const JOIN_WITH_OR: &str = "OR";
12
13/// Models an IDL class used as a data source.
14///
15/// Data for a class may come from its associated databsase table
16/// or from an inline SQL query.
17#[derive(Debug, Clone)]
18pub struct SourceDef {
19    idl_class: Arc<idl::Class>,
20    alias: Option<String>,
21}
22
23impl SourceDef {
24    pub fn idl_class(&self) -> &Arc<idl::Class> {
25        &self.idl_class
26    }
27    pub fn classname(&self) -> &str {
28        self.idl_class.classname()
29    }
30}
31
32#[derive(Debug)]
33pub struct JsonQueryCompiler {
34    /// Used for oils_i18n_xlate()
35    locale: String,
36
37    /// Avoid calling oils_i18n_xlate()
38    disable_i18n: bool,
39
40    /// I.e. EG service name.  Compare to 'suppress_controller' values
41    /// to see of this instance can view selected fields.
42    controllername: Option<String>,
43
44    /// All tables (IDL classes) included in our query.
45    /// Basically FROM + JOINs.
46    sources: Vec<SourceDef>,
47
48    /// Final compiled SQL string
49    query_string: Option<String>,
50
51    /// Query parameters whose values are replaced at query execution time.
52    params: Option<Vec<String>>,
53
54    /// True if one or more fields have the "aggregate" flag set.
55    has_aggregate: bool,
56
57    /// List of fields (by position) to add to the GROUP BY clause.
58    group_by: Vec<usize>,
59
60    /// Current index into the list of SELECT'ed fields.
61    select_index: usize,
62}
63
64/// # Examples
65///
66/// ```
67/// use evergreen::common::jq::JsonQueryCompiler;
68/// let compiler = JsonQueryCompiler::new();
69/// let cloned_compiler = compiler.clone();
70/// assert_eq!(compiler.query_params(), cloned_compiler.query_params());
71/// ```
72impl Clone for JsonQueryCompiler {
73    fn clone(self: &JsonQueryCompiler) -> JsonQueryCompiler {
74        let mut new = JsonQueryCompiler::new();
75
76        new.locale.clone_from(&self.locale);
77        new.disable_i18n = self.disable_i18n;
78        new.controllername.clone_from(&self.controllername);
79
80        new
81    }
82}
83
84impl Default for JsonQueryCompiler {
85    fn default() -> Self {
86        Self::new()
87    }
88}
89
90/// Translates JSON-Query into SQL.
91impl JsonQueryCompiler {
92    pub fn new() -> Self {
93        Self {
94            locale: message::thread_locale(),
95            controllername: None,
96            sources: Vec::new(),
97            query_string: None,
98            disable_i18n: false,
99            params: None,
100            group_by: Vec::new(),
101            has_aggregate: false,
102            select_index: 0,
103        }
104    }
105
106    /// Returns a list of parameter values as strs.
107    ///
108    /// Note we only have to concern ourselves with Strings because
109    /// all other parameter types are included as bare values (numbers)
110    /// or futher decomposed into number and strings.
111    pub fn query_params(&self) -> Vec<&str> {
112        if let Some(params) = self.params.as_ref() {
113            // Every parameter should have a value at compile/execute time.
114            params.iter().map(|s| s.as_str()).collect()
115        } else {
116            vec![]
117        }
118    }
119
120    /// Stringified JSON array of parameter indexes and values.
121    pub fn debug_params(&self) -> String {
122        let mut array = eg::array![];
123        if let Some(params) = self.params.as_ref() {
124            for (idx, value) in params.iter().enumerate() {
125                let mut obj = eg::hash! {};
126
127                // Every parameter should have a value at compile/execute time.
128                obj[&format!("${}", idx + 1)] = EgValue::from(value.as_str());
129
130                array.push(obj).expect("this is an array");
131            }
132        }
133
134        array.dump()
135    }
136
137    /// KLUDGE: Generates the (likely) SQL that will run on the server.
138    ///
139    /// Parameter replacement for executed queries occurs in the PG
140    /// server, which this module does not have direct access to.
141    /// This is for debugging purpose only.
142    pub fn debug_query_kludge(&self) -> String {
143        let mut sql = match self.query_string.as_ref() {
144            Some(s) => s.to_string(),
145            None => return String::new(),
146        };
147
148        if let Some(params) = self.params.as_ref() {
149            // Iterate params in reverse so we're replacing larger
150            // paramters indexes first.  This way replace('$1') does not
151            // affect $10, $11, etc.
152            let mut idx = params.len();
153            for value in params.iter().rev() {
154                let target = format!("${idx}");
155
156                // Counting down from the top.
157                idx -= 1;
158
159                // Assume most values do not contain embedded single-quotes
160                // and avoid the extra string allocation.
161                if value.contains('\'') {
162                    // Escape single quotes
163                    let escaped = value.replace('\'', "''");
164                    sql = sql.replace(&target, &format!("'{escaped}'"));
165                } else {
166                    sql = sql.replace(&target, &format!("'{value}'"));
167                }
168            }
169        }
170
171        sql
172    }
173
174    /// The final compiled SQL string
175    pub fn query_string(&self) -> Option<&str> {
176        self.query_string.as_deref()
177    }
178
179    /// Take ownership of the compiled SQL string.
180    fn take_query_string(&mut self) -> Option<String> {
181        self.query_string.take()
182    }
183
184    /// Get the IDL classname linked to a table alias.
185    /// The alias may also be the classname.
186    fn get_alias_classname(&self, alias: &str) -> EgResult<&str> {
187        Ok(self.get_alias_class(alias)?.classname())
188    }
189
190    pub fn sources(&self) -> &Vec<SourceDef> {
191        &self.sources
192    }
193
194    fn get_alias_class(&self, alias: &str) -> EgResult<&Arc<idl::Class>> {
195        self.sources
196            .iter()
197            .filter(|c| {
198                if let Some(als) = c.alias.as_ref() {
199                    alias == als
200                } else {
201                    alias == c.classname()
202                }
203            })
204            .map(|c| c.idl_class())
205            .next()
206            .ok_or_else(|| format!("No such class alias: {alias}").into())
207    }
208
209    /// Get an IDL Class object from its classname as an owned/cloned Arc.
210    ///
211    /// If you don't need an owned value, idl::get_class() will sufffice.
212    fn get_idl_class(&self, classname: &str) -> EgResult<Arc<idl::Class>> {
213        idl::get_class(classname).cloned()
214    }
215
216    /// Returns the base IDL class, i.e. the root class of the FROM clause.
217    fn get_base_class(&self) -> EgResult<&Arc<idl::Class>> {
218        // The base class is the first source.
219        self.sources
220            .first()
221            .map(|s| s.idl_class())
222            .ok_or("No bass class has been set".into())
223    }
224
225    /// Returns true if the field is valid and not suppressed.
226    ///
227    /// Returns false if suppressed.
228    ///
229    /// Returns Err if the field cannot be found or is virtual.
230    fn field_may_be_selected(&self, name: &str, class: &str) -> EgResult<bool> {
231        let idl_class = match idl::get_class(class) {
232            Ok(c) => c,
233            Err(_) => return Err(format!("Invalid IDL class: {class}").into()),
234        };
235
236        let idl_field = match idl_class.fields().get(name) {
237            Some(f) => f,
238            None => return Err(format!("Class {class} has no field '{name}'").into()),
239        };
240
241        if idl_field.is_virtual() {
242            return Err(format!("Field {name} on {class} is virtual").into());
243        }
244
245        if let Some(suppress) = idl_field.suppress_controller() {
246            if let Some(module) = self.controllername.as_ref() {
247                if suppress.contains(module) {
248                    // Field is not visible to this module.
249                    return Ok(false);
250                }
251            }
252        }
253
254        Ok(true)
255    }
256
257    /// Entry point for compiling the JSON-Query.
258    ///
259    /// The resulting SQL may be found in self.query_string() and
260    /// the resulting query parameters may be found in self.query_params();
261    pub fn compile(&mut self, query: &EgValue) -> EgResult<()> {
262        if !query.is_object() {
263            return Err("json_query must be a JSON hash".into());
264        }
265
266        if query["no_i18n"].boolish() {
267            self.disable_i18n = true;
268        }
269
270        if query["distinct"].boolish() {
271            self.has_aggregate = true;
272        }
273
274        if query["from"].is_array() {
275            // {"from": ["actor.org_unit_ancestors", 2, 1]
276
277            let func_str = self.compile_function_query(&query["from"])?;
278            self.query_string = Some(func_str);
279            return Ok(());
280        }
281
282        if query.has_key("union") || query.has_key("except") || query.has_key("intersect") {
283            let combo_str = self.compile_combo_query(query)?;
284            self.query_string = Some(combo_str);
285            return Ok(());
286        }
287
288        self.set_base_source(&query["from"])?;
289        let base_class = self.get_base_class()?.clone();
290        let cname = base_class.classname();
291
292        // Compile JOINs first so we can populate our data sources.
293        let join_op = self.compile_joins_for_class(cname, &query["from"][cname])?;
294
295        let join_str = join_op.map(|joins| format!(" {joins}"));
296
297        let select_str = self.compile_selects(&query["select"])?;
298        let from_str = self.class_table_or_source_def(cname)?;
299        let where_str = self.compile_where_for_class(&query["where"], cname, JOIN_WITH_AND)?;
300
301        let mut sql = format!(
302            r#"SELECT {select_str} FROM {from_str} AS "{cname}"{} WHERE {where_str}"#,
303            join_str.as_deref().unwrap_or("")
304        );
305
306        if self.has_aggregate {
307            let positions: Vec<String> = self.group_by.iter().map(|n| n.to_string()).collect();
308            sql += &format!(" GROUP BY {}", positions.join(", "));
309        }
310
311        if !query["order_by"].is_null() {
312            sql += &format!(" ORDER BY {}", self.compile_order_by(&query["order_by"])?);
313        }
314
315        if let Some(count) = query["limit"].as_usize() {
316            sql += &format!(" LIMIT {count}");
317        }
318
319        if let Some(count) = query["offset"].as_usize() {
320            sql += &format!(" OFFSET {count}");
321        }
322
323        self.query_string = Some(sql);
324
325        Ok(())
326    }
327
328    fn compile_order_by(&mut self, order_by: &EgValue) -> EgResult<String> {
329        if order_by.is_array() {
330            return self.compile_order_by_array(order_by);
331        }
332
333        Ok(String::new())
334    }
335
336    fn compile_order_by_array(&mut self, order_by: &EgValue) -> EgResult<String> {
337        let mut order_bys = Vec::new();
338
339        for hash in order_by.members() {
340            if !hash.is_object() {
341                return Err(format!("Malformed ORDER BY: {}", order_by.dump()).into());
342            }
343
344            let class_alias = hash["class"]
345                .as_str()
346                .ok_or_else(|| format!("ORDER BY has no class: {}", order_by.dump()))?;
347
348            let field_name = hash["field"]
349                .as_str()
350                .ok_or_else(|| format!("ORDER BY has no field: {}", order_by.dump()))?;
351
352            let classname = self.get_alias_classname(class_alias)?;
353
354            if !self.field_may_be_selected(field_name, classname)? {
355                return Err(format!("Field '{field_name}' is not valid in ORDER BY").into());
356            }
357
358            let order_by_str;
359            if !hash["transform"].is_null() {
360                order_by_str = self.select_one_field(
361                    class_alias,
362                    None, // field alias
363                    field_name,
364                    Some(hash),
365                    false,
366                )?;
367            } else if !hash["compare"].is_null() {
368                // "compare": {"!=" : {"+acn": "owning_lib"}}
369                order_by_str = self.search_predicate(class_alias, field_name, &hash["compare"])?;
370            } else {
371                // Simple field order-by
372                order_by_str = format!(r#""{class_alias}".{field_name}"#);
373            }
374
375            let mut direction = "ASC";
376            if let Some(dir) = hash["direction"].as_str() {
377                if dir.starts_with('d') || dir.starts_with('D') {
378                    direction = "DESC";
379                }
380            }
381
382            order_bys.push(format!("{order_by_str} {direction}"));
383        }
384
385        Ok(order_bys.join(", "))
386    }
387
388    /// Compiles a UNION, INTERSECT, or EXCEPT query.
389    fn compile_combo_query(&mut self, query: &EgValue) -> EgResult<String> {
390        let all = query["all"].boolish();
391        let qtype;
392
393        let query_array = if query["union"].is_array() {
394            qtype = "UNION";
395            &query["union"]
396        } else if query["except"].is_array() {
397            qtype = "EXCEPT";
398            &query["except"]
399        } else if query["intersect"].is_array() {
400            qtype = "INTERSECT";
401            &query["intersect"]
402        } else {
403            return Err(format!("Invalid UNION/INTERSECT/EXCEPT query: {}", query.dump()).into());
404        };
405
406        if !query["order_by"].is_null() {
407            return Err(format!("ORDER BY not supported for query type: {}", query.dump()).into());
408        }
409
410        // At this point we're guaranteed it's an array.
411        if query_array.len() < 2 {
412            return Err(format!("Invalid query array for query type: {}", query.dump()).into());
413        }
414
415        if qtype == "EXCEPT" && query_array.len() > 2 {
416            return Err(format!(
417                "EXCEPT operator has too many query operands: {}",
418                query.dump()
419            )
420            .into());
421        }
422
423        let mut sql = String::new();
424        for (idx, hash) in query_array.members().enumerate() {
425            if !hash.is_object() {
426                return Err(format!("Invalid sub-query for query type: {}", query.dump()).into());
427            }
428
429            if idx > 0 {
430                sql += " ";
431                sql += qtype;
432                if all {
433                    sql += " ALL ";
434                }
435            }
436
437            sql += &self.compile_sub_query(hash)?;
438        }
439
440        Ok(sql)
441    }
442
443    /// Compile a wholly-formed subquery and absorb its parameter values.
444    fn compile_sub_query(&mut self, query: &EgValue) -> EgResult<String> {
445        let mut compiler = self.clone();
446
447        compiler.compile(query)?;
448
449        let sub_sql = compiler
450            .take_query_string()
451            .ok_or_else(|| format!("Sub-query produced no SQL: {}", query.dump()))?;
452
453        if let Some(params) = compiler.params.as_mut() {
454            for value in params.drain(..) {
455                self.add_param_string(value);
456            }
457        }
458
459        Ok(sub_sql)
460    }
461
462    fn compile_selects(&mut self, select_def: &EgValue) -> EgResult<String> {
463        if select_def.is_null() {
464            let base_class = self.get_base_class()?.clone();
465            let cn = base_class.classname(); // parallel mutes
466
467            // If we have no SELECT clause at all, just select the default fields.
468            return self.build_default_select_list(cn, None);
469        } else if !select_def.is_object() {
470            // The root SELECT clause is a map of classname (or alias) to field list
471            return Err(format!("Invalid SELECT clause: {select_def}").into());
472        }
473
474        let mut selects = Vec::new();
475        for (alias, payload) in select_def.entries() {
476            selects.push(self.compile_selects_for_class(alias, payload)?);
477        }
478
479        Ok(selects.join(", "))
480    }
481
482    fn compile_selects_for_class(
483        &mut self,
484        class_alias: &str,
485        select_def: &EgValue,
486    ) -> EgResult<String> {
487        if select_def.is_null() {
488            return self.build_default_select_list(class_alias, None);
489        }
490
491        let alias_class = self.get_alias_class(class_alias)?.clone();
492        let classname = alias_class.classname();
493
494        if let Some(col) = select_def.as_str() {
495            if col == "*" {
496                // Wildcard queries use the default select list.
497                return self.build_default_select_list(class_alias, None);
498            } else {
499                // Selecting a single column by name.
500
501                if self.field_may_be_selected(col, classname)? {
502                    return self.select_one_field(class_alias, None, col, None, true);
503                }
504            }
505        }
506
507        if select_def.is_object() {
508            // An 'exclude' SELECT is a wildcard SELECT minus certain fields.
509            // E.g., {"bre": {"exclude": ["marc"]}
510
511            let fields: Vec<&str> = select_def["exclude"]
512                .members()
513                .filter(|f| f.is_string())
514                .map(|f| f.as_str().unwrap())
515                .collect();
516
517            if !fields.is_empty() {
518                return self.build_default_select_list(class_alias, Some(fields.as_slice()));
519            }
520
521            return Err(format!(
522                "SELECT received invalid 'exclude' query: {}",
523                select_def.dump()
524            )
525            .into());
526        }
527
528        if !select_def.is_array() {
529            return Err("SELECT must be string, null, excluder, or array".into());
530        }
531
532        let mut fields = Vec::new();
533
534        for field_struct in select_def.members() {
535            if let Some(column) = field_struct.as_str() {
536                // Field entry is a string field name.
537
538                if self.field_may_be_selected(column, classname)? {
539                    fields.push(self.select_one_field(class_alias, None, column, None, true)?);
540                }
541
542                continue;
543            }
544
545            let column = field_struct["column"]
546                .as_str()
547                .ok_or_else(|| format!("SELECT hash requires a 'column': {field_struct}"))?;
548
549            if !self.field_may_be_selected(column, classname)? {
550                continue;
551            }
552
553            fields.push(self.select_one_field(
554                class_alias,
555                field_struct["alias"].as_str(),
556                column,
557                Some(field_struct),
558                true,
559            )?);
560        }
561
562        Ok(fields.join(", "))
563    }
564
565    fn build_default_select_list(
566        &mut self,
567        alias: &str,
568        exclude: Option<&[&str]>,
569    ) -> EgResult<String> {
570        let alias_class = self.get_alias_class(alias)?.clone();
571        let classname = alias_class.classname();
572
573        // If we have an alias it's known to be valid
574        let idl_class = idl::get_class(classname)?;
575
576        let mut fields = Vec::new();
577        for field in idl_class.real_fields_sorted().iter() {
578            if self.field_may_be_selected(field.name(), classname)? {
579                if let Some(list) = exclude {
580                    if list.contains(&field.name()) {
581                        continue;
582                    }
583                }
584            }
585            fields.push(self.select_one_field(alias, None, field.name(), None, true)?);
586        }
587
588        Ok(fields.join(", "))
589    }
590
591    /// Format a field, with transform if needed, for inclusion in a
592    /// SELECT or WHERE clause entry.
593    fn select_one_field(
594        &mut self,
595        class_alias: &str,
596        field_alias: Option<&str>,
597        field_name: &str,
598        field_def: Option<&EgValue>,
599        // Fields within a query predicate (e.g. WHERE "aou".id = 1)
600        // are not part of the SELECT clause and cannot be grouped on.
601        handle_group_by: bool,
602    ) -> EgResult<String> {
603        if handle_group_by {
604            self.select_index += 1;
605        }
606
607        let mut is_aggregate = false;
608
609        if let Some(fdef) = field_def {
610            // If we have a field_def, it may mean the field has extended
611            // properties, like a transform or other flags.
612
613            // Do we support aggregate functions?  Maybe.
614            is_aggregate = fdef["aggregate"].boolish();
615
616            if let Some(xform) = fdef["transform"].as_str() {
617                let mut sql = String::new();
618
619                sql += self.check_identifier(xform)?;
620                sql += "(";
621
622                if fdef["distinct"].boolish() {
623                    sql += "DISTINCT ";
624                }
625
626                // Avoid sending the field alias here since any alias
627                // should apply to our transform as a whole.
628                sql += &self.format_one_select_field(class_alias, None, field_name)?;
629
630                for param in fdef["params"].members() {
631                    let index = self.add_param(param)?;
632                    sql += &format!(", ${index}");
633                }
634
635                sql += ")";
636
637                if let Some(rfield) = fdef["result_field"].as_str() {
638                    // Append (...).xform_result_field.
639                    sql = format!(r#"({sql})."{}""#, self.check_identifier(rfield)?);
640                } else if let Some(alias) = field_alias {
641                    sql += &format!(r#" AS "{}""#, self.check_identifier(alias)?);
642                }
643
644                if handle_group_by {
645                    if is_aggregate {
646                        self.has_aggregate = true;
647                    } else {
648                        self.group_by.push(self.select_index);
649                    }
650                }
651
652                return Ok(sql);
653            }
654        }
655
656        if handle_group_by {
657            if is_aggregate {
658                self.has_aggregate = true;
659            } else {
660                self.group_by.push(self.select_index);
661            }
662        }
663
664        self.format_one_select_field(class_alias, field_alias, field_name)
665    }
666
667    /// Format the SELECT component for a single field, adding the
668    /// oils_i18n_xlate() where needed.
669    fn format_one_select_field(
670        &self,
671        class_alias: &str,
672        field_alias: Option<&str>,
673        field_name: &str,
674    ) -> EgResult<String> {
675        let mut sql;
676
677        let classname = self.get_alias_classname(class_alias)?;
678        let idl_class = idl::get_class(classname)?;
679
680        let idl_field = idl_class
681            .fields()
682            .get(field_name)
683            .ok_or_else(|| format!("Invalid field {}::{field_name}", idl_class.classname()))?;
684
685        if !idl_field.i18n() || self.disable_i18n {
686            sql = format!(
687                r#""{}".{}"#,
688                self.check_identifier(class_alias)?,
689                self.check_identifier(idl_field.name())?
690            );
691        } else {
692            let pkey = idl_class
693                .pkey()
694                .ok_or_else(|| format!("{} has no primary key", idl_class.classname()))?;
695
696            let tablename = idl_class
697                .tablename()
698                .ok_or_else(|| format!("Class {classname} has no table definition"))?;
699
700            sql = format!(
701                r#"oils_i18n_xlate('{}', '{}', '{}', '{}', "{}".{}::TEXT, '{}')"#,
702                self.check_identifier(tablename)?,
703                self.check_identifier(class_alias)?,
704                self.check_identifier(idl_field.name())?,
705                self.check_identifier(pkey)?,
706                self.check_identifier(class_alias)?,
707                self.check_identifier(pkey)?,
708                self.locale.as_str(), // format verified by opensrf
709            );
710        }
711
712        if let Some(alias) = field_alias {
713            sql += &format!(r#" AS "{}""#, self.check_identifier(alias)?);
714        }
715
716        Ok(sql)
717    }
718
719    /// Unpack the JOIN clauses into their constituent parts.
720    fn compile_joins_for_class(
721        &mut self,
722        left_alias: &str,
723        joins: &EgValue,
724    ) -> EgResult<Option<String>> {
725        let class_to_hash = |c| {
726            // Sometimes we JOIN to a class with no additional info beyond
727            // the classname.  Put that info into a json object for consistency.
728            let mut hash = eg::hash! {};
729            hash[c] = EgValue::Null;
730            hash
731        };
732
733        let join_binding;
734
735        let join_list = if let EgValue::Array(list) = joins {
736            list.iter().collect::<Vec<&EgValue>>()
737        } else if let Some(class) = joins.as_str() {
738            join_binding = class_to_hash(class);
739            vec![&join_binding]
740        } else {
741            vec![joins]
742        };
743
744        let mut joins = Vec::new();
745        for join_entry in join_list {
746            let hash_binding;
747
748            let hash_ref = if let Some(class) = join_entry.as_str() {
749                hash_binding = class_to_hash(class);
750                &hash_binding
751            } else {
752                join_entry
753            };
754
755            for (right_alias, join_def) in hash_ref.entries() {
756                joins.push(self.add_one_join(left_alias, right_alias, join_def)?);
757            }
758        }
759
760        if joins.is_empty() {
761            Ok(None)
762        } else {
763            Ok(Some(joins.join(" ")))
764        }
765    }
766
767    fn add_one_join(
768        &mut self,
769        left_alias: &str,
770        right_alias: &str,
771        join_def: &EgValue,
772    ) -> EgResult<String> {
773        // If there's no "class" in the hash, the alias is the classname
774        let right_class = join_def["class"].as_str().unwrap_or(right_alias);
775        let right_idl_class = self.get_idl_class(right_class)?;
776
777        let left_class = self.get_alias_classname(left_alias)?;
778        let left_idl_class = self.get_idl_class(left_class)?;
779
780        // Field on the left/source table to JOIN on. Optional.
781        let mut left_join_field = join_def["fkey"].as_str();
782
783        // Field on the right/target table to JOIN on. Optional.
784        let mut right_join_field = join_def["field"].as_str();
785
786        // Find the left and right field names from the IDL via links.
787
788        if right_join_field.is_some() && left_join_field.is_none() {
789            let rfield_name = right_join_field.unwrap(); // verified
790
791            // Find the link definition that points from the target/joined
792            // class to the left/source class.
793            let idl_link = right_idl_class
794                .links()
795                .get(rfield_name)
796                .ok_or_else(|| format!("No such link  for class '{right_class}'"))?;
797
798            let reltype = idl_link.reltype();
799
800            let maybe_left_class = idl_link.class();
801
802            if reltype != idl::RelType::HasMany && maybe_left_class == left_class {
803                left_join_field = Some(idl_link.key());
804            }
805
806            if left_join_field.is_none() {
807                return Err(format!(
808                    "No link defined from {right_class}::{rfield_name} to {maybe_left_class}"
809                )
810                .into());
811            }
812        } else if right_join_field.is_none() && left_join_field.is_some() {
813            let lfield_name = left_join_field.unwrap(); // verified above.
814
815            let idl_link = left_idl_class
816                .links()
817                .get(lfield_name)
818                .ok_or_else(|| format!("No such link {lfield_name} for class {left_class}"))?;
819
820            let reltype = idl_link.reltype();
821
822            let maybe_right_class = idl_link.class();
823            if reltype != idl::RelType::HasMany && maybe_right_class == right_class {
824                right_join_field = Some(idl_link.key());
825            }
826
827            if right_join_field.is_none() {
828                return Err(format!(
829                    "No link defined from {left_class}::{lfield_name} to {maybe_right_class}"
830                )
831                .into());
832            }
833        } else if right_join_field.is_none() && left_join_field.is_none() {
834            // See if we can determine the left and right join fields
835            // based solely on the 2 tables being joined.
836
837            for (link_key, cur_link) in left_idl_class.links() {
838                let maybe_right_class = cur_link.class();
839
840                if maybe_right_class == right_class {
841                    let reltype = cur_link.reltype();
842                    if reltype != idl::RelType::HasMany {
843                        left_join_field = Some(link_key);
844                        right_join_field = Some(cur_link.key());
845                        break;
846                    }
847                }
848            }
849
850            // Do another search with the classes reversed.
851            if right_join_field.is_none() && left_join_field.is_none() {
852                for (link_key, cur_link) in right_idl_class.links() {
853                    let maybe_left_class = cur_link.class();
854
855                    if maybe_left_class == left_class {
856                        let reltype = cur_link.reltype();
857                        if reltype != idl::RelType::HasMany {
858                            right_join_field = Some(link_key);
859                            left_join_field = Some(cur_link.key());
860                            break;
861                        }
862                    }
863                }
864            }
865
866            if right_join_field.is_none() && left_join_field.is_none() {
867                return Err(format!(
868                    "Could not find link between classes {left_class} and {right_class}"
869                )
870                .into());
871            }
872        }
873
874        let join_type = if let Some(jtype) = join_def["type"].as_str() {
875            match jtype {
876                "left" => "LEFT JOIN",
877                "right" => "RIGHT JOIN",
878                "full" => "FULL JOIN",
879                _ => "INNER JOIN",
880            }
881        } else {
882            "INNER JOIN"
883        };
884
885        // Table name or subquery wrapped in parens.
886        let source_str = self.class_table_or_source_def(right_idl_class.classname())?;
887
888        let mut sql = format!(
889            r#"{} {} AS "{}" ON ("{}".{} = "{}".{}"#,
890            join_type,
891            source_str,
892            self.check_identifier(right_alias)?,
893            self.check_identifier(right_alias)?,
894            self.check_identifier(right_join_field.unwrap())?,
895            self.check_identifier(left_alias)?,
896            self.check_identifier(left_join_field.unwrap())?,
897        );
898
899        // ----
900        // Add this new class to our list of sources before we
901        // potentially start adding recursive JOINs.
902        let mut source_def = SourceDef {
903            idl_class: right_idl_class,
904            alias: None,
905        };
906
907        if right_alias != right_class {
908            // Alias may be the same as the classname.
909            source_def.alias = Some(right_alias.to_string());
910        }
911
912        self.sources.push(source_def);
913        // ----
914
915        // Some JOINS have filters, which are mini WHERE clauses tacked
916        // on to the JOIN.
917        let filter = &join_def["filter"];
918        if !filter.is_null() {
919            let mut op = " AND ";
920            if let Some(filter_op) = filter["filter_op"].as_str() {
921                if filter_op == "or" {
922                    op = " OR ";
923                }
924            }
925            sql += op;
926            sql += &self.compile_where_for_class(filter, right_alias, JOIN_WITH_AND)?;
927        }
928
929        sql += ")";
930
931        // Add nested JOINs if we have any
932        let sub_join = &join_def["join"];
933        if !sub_join.is_null() {
934            if let Some(sjoin) = self.compile_joins_for_class(right_alias, sub_join)? {
935                sql += " ";
936                sql += &sjoin;
937            }
938        }
939
940        Ok(sql)
941    }
942
943    /// Returns the SQL representing the data source for an IDL
944    /// class.
945    ///
946    /// Typically this will be a DB table name, but for classes with
947    /// a source definition, it will be source SQL wrappen in parens
948    /// for inclusion in a containing query.
949    fn class_table_or_source_def(&self, classname: &str) -> EgResult<String> {
950        if let Ok(idl_class) = idl::get_class(classname) {
951            if let Some(tablename) = idl_class.tablename() {
952                return Ok(self.check_identifier(tablename)?.to_string());
953            } else if let Some(source_def) = idl_class.source_definition() {
954                // Wrap the source def in params since it's sub-query.
955                return Ok(format!("({source_def})"));
956            }
957        }
958
959        Err(format!("Class {classname} has no table or source definition").into())
960    }
961
962    fn compile_where_for_class(
963        &mut self,
964        where_def: &EgValue,
965        class_alias: &str,
966        join_op: &str,
967    ) -> EgResult<String> {
968        // println!("compile_where_for_class() {class_alias} {}", where_def.dump());
969
970        let mut sql = String::new();
971
972        if where_def.is_array() {
973            if where_def.is_empty() {
974                return Err("Invalid WHERE clause / empty array".into());
975            }
976
977            for (idx, part) in where_def.members().enumerate() {
978                if idx > 0 {
979                    sql += " ";
980                    sql += join_op;
981                    sql += " ";
982                }
983                let sub_pred = self.compile_where_for_class(part, class_alias, join_op)?;
984                sql += &format!("({sub_pred})");
985            }
986
987            return Ok(sql);
988        } else if where_def.is_object() {
989            if where_def.is_empty() {
990                return Err("Invalid predicate structure: empty JSON object".into());
991            }
992
993            for (idx, (key, sub_blob)) in where_def.entries().enumerate() {
994                if idx > 0 {
995                    sql += " ";
996                    sql += join_op;
997                    sql += " ";
998                }
999
1000                if key.starts_with('+') && key.len() > 1 {
1001                    // Class alias
1002                    // E.g. {"+aou": {"shortname": "BR1"}}
1003
1004                    let alias = &key[1..];
1005                    let classname = self.get_alias_classname(alias)?;
1006
1007                    if let Some(field) = sub_blob.as_str() {
1008                        // {"+aou": "shortname"}
1009                        // This can happen in order-by clauses.
1010
1011                        if !idl::get_class(classname)?.has_real_field(field) {
1012                            return Err(
1013                                format!("Class {classname} has no field named {field}").into()
1014                            );
1015                        }
1016
1017                        sql += &format!(r#" "{alias}".{field} "#);
1018                    } else {
1019                        // {"+aou": {"shortname": ...}}
1020
1021                        let sub_pred = self.compile_where_for_class(sub_blob, alias, join_op)?;
1022                        sql += &format!("({sub_pred})");
1023                    }
1024                } else if key.starts_with('-') {
1025                    if key == "-or" {
1026                        let sub_pred =
1027                            self.compile_where_for_class(sub_blob, class_alias, JOIN_WITH_OR)?;
1028                        sql += &format!("({sub_pred})");
1029                    } else if key == "-and" {
1030                        let sub_pred =
1031                            self.compile_where_for_class(sub_blob, class_alias, JOIN_WITH_AND)?;
1032                        sql += &format!("({sub_pred})");
1033                    } else if key == "-not" {
1034                        let sub_pred =
1035                            self.compile_where_for_class(sub_blob, class_alias, JOIN_WITH_AND)?;
1036                        sql += &format!("NOT ({sub_pred})");
1037                    } else if key == "-exists" || key == "-not-exists" {
1038                        let sub_sql = self.compile_sub_query(sub_blob)?;
1039
1040                        let question = if key.contains("not") {
1041                            "NOT EXISTS"
1042                        } else {
1043                            "EXISTS"
1044                        };
1045
1046                        sql += &format!("{question} ({sub_sql})");
1047                    }
1048                } else {
1049                    // key is assumed to be a field name
1050
1051                    let classname = self.get_alias_classname(class_alias)?;
1052
1053                    // classname verified above.
1054                    // Make sure it's a valid field name
1055                    if !idl::get_class(classname)?.has_real_field(key) {
1056                        return Err(format!("Class {classname} has no field called {key}").into());
1057                    }
1058
1059                    sql += &self.search_predicate(class_alias, key, sub_blob)?;
1060                }
1061            }
1062        } else if where_def.is_null() {
1063            // A query with no WHERE is valid, but return something to the
1064            // caller so they don't have to make a special case for, say,
1065            // an empty string.
1066            sql = "TRUE".to_string();
1067        } else {
1068            return Err(format!("Invalid WHERE structure: {where_def}").into());
1069        }
1070
1071        Ok(sql)
1072    }
1073
1074    /// Does the provided field match some value?
1075    /// Value may be a simple thing, like a string, or a more complex
1076    /// comparison (IN list, between, etc.)
1077    ///
1078    /// Examples:
1079    ///
1080    /// {"shortname": "BR1"}
1081    /// {"shortname": ["BR1", "BR2"]}
1082    /// {"shortname": {"in": ["BR1", "BR2"]}}
1083    /// {"shortname": {"not in": {"select": ...}}}
1084    fn search_predicate(
1085        &mut self,
1086        class_alias: &str,
1087        field_name: &str,
1088        value_def: &EgValue,
1089    ) -> EgResult<String> {
1090        // println!("search_predicate {class_alias} {field_name} {}", value_def.dump());
1091
1092        if value_def.is_array() {
1093            // Equality IN search
1094            self.search_in_predicate(class_alias, field_name, value_def, false)
1095        } else if value_def.is_object() {
1096            if value_def.len() != 1 {
1097                return Err(format!(
1098                    "Invalid search predicate for field: {field_name} {value_def}",
1099                )
1100                .into());
1101            }
1102
1103            let (key, sub_def) = value_def.entries().next().unwrap(); // above
1104
1105            if key == "between" {
1106                self.search_between_predicate(class_alias, field_name, sub_def)
1107            } else if key == "in" || key == "not in" {
1108                self.search_in_predicate(class_alias, field_name, sub_def, key.contains("not"))
1109            } else if sub_def.is_array() {
1110                self.search_function_predicate(key, class_alias, field_name, sub_def)
1111            } else if sub_def.is_object() {
1112                self.search_field_transform_predicate(key, class_alias, field_name, sub_def)
1113            } else {
1114                self.simple_search_predicate(key, class_alias, field_name, sub_def)
1115            }
1116        } else {
1117            self.simple_search_predicate("=", class_alias, field_name, value_def)
1118        }
1119    }
1120
1121    /// Compiles a variety of somefield-someoprator-somevalue scenarios.
1122    ///
1123    /// Examples (the inner {...}):
1124    ///
1125    /// {"label": {">=": {"transform": "oils_text_as_bytea", "value": ["oils_text_as_bytea", "ABC"]}}
1126    fn search_field_transform_predicate(
1127        &mut self,
1128        operator: &str,
1129        class_alias: &str,
1130        field_name: &str,
1131        value_def: &EgValue,
1132    ) -> EgResult<String> {
1133        // println!("search_field_transform_predicate() {class_alias}.{field_name} {}", value_def.dump());
1134
1135        let field_str =
1136            self.select_one_field(class_alias, None, field_name, Some(value_def), false)?;
1137
1138        let value_obj = &value_def["value"];
1139
1140        let mut extra_parens = false;
1141
1142        let value_str = if value_obj.is_null() {
1143            extra_parens = true;
1144            self.compile_where_for_class(value_def, class_alias, JOIN_WITH_AND)?
1145        } else if value_obj.is_array() {
1146            self.compile_function_from(value_obj)?
1147        } else if value_obj.is_object() {
1148            extra_parens = true;
1149            self.compile_where_for_class(value_obj, class_alias, JOIN_WITH_AND)?
1150        } else if value_obj.is_string() || value_obj.is_number() {
1151            self.scalar_param_as_string(class_alias, field_name, value_obj)?
1152        } else {
1153            return Err(format!(
1154                "Invalid predicate for field transform for {field_name}: {}",
1155                value_obj.dump()
1156            )
1157            .into());
1158        };
1159
1160        let left_parens = if extra_parens { "(" } else { "" };
1161        let right_parens = if extra_parens { ")" } else { "" };
1162
1163        Ok(format!(
1164            r#"{}{} {} {}{}{}{}"#,
1165            left_parens,
1166            field_str,
1167            self.check_operator(operator)?,
1168            left_parens,
1169            value_str,
1170            right_parens,
1171            right_parens
1172        ))
1173    }
1174
1175    /// Encode a function call as the right-hand part of a WHERE entry.
1176    ///
1177    /// Examples:
1178    ///
1179    /// ["actor.org_unit_ancestor_setting_batch", "4", "{circ.course_materials_opt_in}"]
1180    ///
1181    /// Output:
1182    ///
1183    /// "aou".id = some.function()
1184    fn search_function_predicate(
1185        &mut self,
1186        operator: &str,
1187        class_alias: &str,
1188        field_name: &str,
1189        value_def: &EgValue,
1190    ) -> EgResult<String> {
1191        let func_str = self.compile_function_from(value_def)?;
1192
1193        Ok(format!(
1194            r#""{class_alias}".{field_name} {} {func_str}"#,
1195            self.check_operator(operator)?,
1196        ))
1197    }
1198
1199    /// Compiles a BETWEEN search.
1200    ///
1201    /// Examples (but really just the array part):
1202    ///
1203    /// {"somefield": {"between": [123, 456]}}
1204    fn search_between_predicate(
1205        &mut self,
1206        class_alias: &str,
1207        field_name: &str,
1208        value_def: &EgValue,
1209    ) -> EgResult<String> {
1210        let value_def = if !value_def["value"].is_null() {
1211            // Could be a field transformed w/ a function
1212            &value_def["value"]
1213        } else {
1214            value_def
1215        };
1216
1217        if !value_def.is_array() || value_def.len() != 2 {
1218            return Err(format!("Invalid BETWEEN clause for {field_name}: {value_def}").into());
1219        }
1220
1221        Ok(format!(
1222            "{} BETWEEN {} AND {}",
1223            self.select_one_field(class_alias, None, field_name, Some(value_def), false)?,
1224            self.scalar_param_as_string(class_alias, field_name, &value_def[0])?,
1225            self.scalar_param_as_string(class_alias, field_name, &value_def[1])?
1226        ))
1227    }
1228
1229    /// This is your class a.b = 'c' scenario.
1230    ///
1231    /// Examples:
1232    ///
1233    /// {"somefield": {"is not": null}}
1234    /// {"somefield": "foobar"}
1235    /// {"somefield": true}
1236    fn simple_search_predicate(
1237        &mut self,
1238        operator: &str,
1239        class_alias: &str,
1240        field_name: &str,
1241        value: &EgValue,
1242    ) -> EgResult<String> {
1243        if value.is_object() || value.is_array() {
1244            return Err(format!("Invalid simple search predicate: {}", value.dump()).into());
1245        }
1246
1247        let prefix = format!(r#""{class_alias}".{field_name}"#);
1248
1249        if value.is_null() {
1250            let val_str = if operator == "=" || operator.to_uppercase() == "IS" {
1251                "NULL"
1252            } else {
1253                "NOT NULL"
1254            };
1255
1256            return Ok(format!("{prefix} IS {val_str}"));
1257        } else if let Some(b) = value.as_bool() {
1258            let val_str = if b { "TRUE" } else { "FALSE" };
1259
1260            let oper_str = if operator == "=" || operator.to_uppercase() == "IS" {
1261                "IS"
1262            } else {
1263                "IS NOT"
1264            };
1265
1266            return Ok(format!("{prefix} {oper_str} {val_str}"));
1267        }
1268
1269        let param_str = self.scalar_param_as_string(class_alias, field_name, value)?;
1270
1271        // Numbers and strings from here on out.
1272        Ok(format!(
1273            "{prefix} {} {param_str}",
1274            self.check_operator(operator)?,
1275        ))
1276    }
1277
1278    /// Encode a String or Number parameter value as a String suitable
1279    /// for including in the main SQL string.
1280    ///
1281    /// Values that requires quoting are added as replaceable parameters.
1282    ///
1283    /// Results in an error if the value is not appropriate for the
1284    /// field, e.g. a numeric field compared to a non-numeric string value.
1285    ///
1286    /// Examples:
1287    /// 1
1288    /// "1" -- will be parameterized and eventually quoted
1289    fn scalar_param_as_string(
1290        &mut self,
1291        class_alias: &str,
1292        field_name: &str,
1293        value: &EgValue,
1294    ) -> EgResult<String> {
1295        if !value.is_string() && !value.is_number() {
1296            return Err(format!("Invalid scalar value for field {field_name}: {value}").into());
1297        }
1298
1299        // If the field in question is non-numeric, then we need
1300        // to treat it as a replaceable parameter.
1301        let classname = self.get_alias_classname(class_alias)?;
1302        let idl_class = idl::get_class(classname)?;
1303
1304        let idl_field = idl_class
1305            .get_field(field_name)
1306            .ok_or_else(|| format!("IDL class {classname} has no field named {field_name}"))?;
1307
1308        if idl_field.datatype().is_numeric() {
1309            // No need to quote numeric parameters for numeric columns.
1310
1311            if let Some(num) = value.as_int() {
1312                // Handle cases where we receive numeric values as JSON strings.
1313                Ok(num.to_string())
1314            } else if let Some(num) = value.as_float() {
1315                // Handle cases where we receive numeric values as JSON strings.
1316                Ok(num.to_string())
1317            } else {
1318                return Err(format!(
1319                    "Field {field_name} is numeric, but query value isn't: {value}",
1320                )
1321                .into());
1322            }
1323        } else {
1324            // IDL field is non-numeric.  Quote the param.
1325            Ok(format!("${}", self.add_param(value)?))
1326        }
1327    }
1328
1329    /// Compiles an IN clause.
1330    ///
1331    /// Examples:
1332    ///
1333    /// {"somefield": [1, 2, 3, 4]}
1334    /// {"somefield": {"not in": [1, 2, 3, 4]}}
1335    /// {"somefield": {"in": {"select": {"au":["id"]}, "from", ...}}}
1336    fn search_in_predicate(
1337        &mut self,
1338        class_alias: &str,
1339        field_name: &str,
1340        value_def: &EgValue,
1341        is_not_in: bool,
1342    ) -> EgResult<String> {
1343        let field_str =
1344            self.select_one_field(class_alias, None, field_name, Some(value_def), false)?;
1345
1346        let in_str = self.search_in_list(class_alias, field_name, value_def)?;
1347
1348        Ok(format!(
1349            "{field_str} {} ({in_str})",
1350            if is_not_in { "NOT IN" } else { "IN" }
1351        ))
1352    }
1353
1354    /// Compiles right-hand part of an IN clause.
1355    ///
1356    /// Examples (minus the outermost container):
1357    ///
1358    /// {"somefield": [1, 2, 3, 4]}
1359    /// {"somefield": {"not in": [1, 2, 3, 4]}}
1360    /// {"somefield": {"in": {"select": {"au":["id"]}, "from", ...}}}
1361    fn search_in_list(
1362        &mut self,
1363        class_alias: &str,
1364        field_name: &str,
1365        value_def: &EgValue,
1366    ) -> EgResult<String> {
1367        if !value_def.is_object() && !value_def.is_array() {
1368            return Err(format!("Unexpected IN clause: {value_def}").into());
1369        }
1370
1371        let value_def = if !value_def["value"].is_null() {
1372            &value_def["value"]
1373        } else {
1374            value_def
1375        };
1376
1377        if value_def.is_object() {
1378            return self.compile_sub_query(value_def);
1379        }
1380
1381        if value_def.is_empty() {
1382            return Err(format!("Empty IN list for field {field_name}"))?;
1383        }
1384
1385        let mut values = Vec::new();
1386        for value in value_def.members() {
1387            values.push(self.scalar_param_as_string(class_alias, field_name, value)?);
1388        }
1389
1390        Ok(values.join(", "))
1391    }
1392
1393    /// Verify the provided string may act as a valid PG identifier.
1394    ///
1395    /// Returns the source value on success for convenience.
1396    fn check_identifier<'a>(&'a self, s: &'a str) -> EgResult<&'a str> {
1397        if db::is_identifier(s) {
1398            Ok(s)
1399        } else {
1400            Err(format!("Value is not a valid identifier: {s}").into())
1401        }
1402    }
1403
1404    /// Verify the provided string may act as a valid SQL operator
1405    ///
1406    /// Returns the source value on success for convenience.
1407    fn check_operator<'a>(&'a self, operator: &'a str) -> EgResult<&'a str> {
1408        if db::is_supported_operator(operator) {
1409            Ok(operator)
1410        } else {
1411            Err(format!("Invalid operator: {operator}").into())
1412        }
1413    }
1414
1415    /// See add_param_string()
1416    ///
1417    /// The value parameter Must be a String or Number.
1418    fn add_param(&mut self, value: &EgValue) -> EgResult<usize> {
1419        let s = value
1420            .to_string()
1421            .ok_or_else(|| format!("Cannot stringify: {value}"))?;
1422        Ok(self.add_param_string(s))
1423    }
1424
1425    /// Adds a new query parameter and returns the index of the new
1426    /// param for SQL variable replacement.
1427    ///
1428    /// At SQL compile time, parameter values that require escaping
1429    /// (i.e. Strings) are encoded as numeric placeholders
1430    /// ($1, $2, ...).
1431    ///
1432    /// Query parameter indexes are 1-based.
1433    fn add_param_string(&mut self, value: String) -> usize {
1434        if let Some(list) = self.params.as_mut() {
1435            list.push(value);
1436            list.len()
1437        } else {
1438            self.params = Some(vec![value]);
1439            1
1440        }
1441    }
1442
1443    /// Get the core IDL class from the main FROM clause.
1444    ///
1445    /// Examples:
1446    ///
1447    /// {"acp": {"acn": {"join": {"bre": ... }}}
1448    fn set_base_source(&mut self, from_blob: &EgValue) -> EgResult<&SourceDef> {
1449        let classname = if from_blob.is_object() && from_blob.len() == 1 {
1450            // "from":{"aou": ...}
1451            let (class, _) = from_blob.entries().next().unwrap();
1452            class
1453        } else if let Some(class) = from_blob.as_str() {
1454            // "from": "aou"
1455            class
1456        } else {
1457            return Err(format!("Invalid FROM clause: {from_blob}").into());
1458        };
1459
1460        let idl_class = self.get_idl_class(classname)?;
1461
1462        // Add our first source
1463        self.sources.push(SourceDef {
1464            idl_class: idl_class.clone(),
1465            alias: None,
1466        });
1467
1468        Ok(self.sources.first().unwrap())
1469    }
1470
1471    /// Compile a (sub-)query which is simply a function call.
1472    ///
1473    /// Examples:
1474    ///
1475    /// {"from": ["actor.org_unit_ancestor_setting_batch", "4", "{circ.course_materials_opt_in}"]}
1476    fn compile_function_query(&mut self, from_def: &EgValue) -> EgResult<String> {
1477        let from_str = self.compile_function_from(from_def)?;
1478
1479        // This is verified in compile_function_from().
1480        let func_name = from_def[0].as_str().unwrap();
1481
1482        Ok(format!(r#"SELECT * FROM {from_str} AS "{func_name}""#))
1483    }
1484
1485    /// Compiles the FROM component of a function call array.
1486    ///
1487    /// Examples:
1488    ///
1489    /// ["actor.org_unit_ancestor_setting_batch", "4", "{circ.course_materials_opt_in}"]
1490    fn compile_function_from(&mut self, from_def: &EgValue) -> EgResult<String> {
1491        if from_def.is_empty() || !from_def.is_array() {
1492            return Err(format!("Invalid FROM function spec: {}", from_def.dump()).into());
1493        }
1494
1495        let func_name = match from_def[0].as_str() {
1496            Some(f) => self.check_identifier(f)?.to_string(),
1497            None => return Err(format!("Invalid function name: {}", from_def[0].dump()).into()),
1498        };
1499
1500        let mut sql = func_name.to_string();
1501
1502        if from_def.len() > 1 {
1503            let mut params = Vec::new();
1504
1505            // Skip the first member since that's the function name
1506            for value in from_def.members().skip(1) {
1507                if value.is_null() {
1508                    params.push("NULL".to_string());
1509                } else if let Some(b) = value.as_bool() {
1510                    let s = if b { "TRUE" } else { "FALSE" };
1511                    params.push(s.to_string());
1512                } else if value.is_string() {
1513                    let index = self.add_param(value)?;
1514                    params.push(format!("${index}"));
1515                } else if value.is_number() {
1516                    params.push(value.to_string().unwrap());
1517                } else {
1518                    return Err(format!("Invalid function parameter: {}", value.dump()).into());
1519                };
1520            }
1521
1522            sql += &format!("({})", &params.join(", "));
1523        }
1524
1525        Ok(sql)
1526    }
1527}