1use dibs_sql::{ColumnName, ParamName, TableName};
9use facet::Facet;
10pub use facet_reflect::Span;
11use indexmap::IndexMap;
12use std::{borrow::Borrow, hash::Hash, ops::Deref};
13
14pub fn query_file_schema() -> String {
16 normalize_schema_tag_payload_spacing(&facet_styx::schema_from_type::<QueryFile>())
17}
18
19pub fn normalize_schema_tag_payload_spacing(schema: &str) -> String {
27 let mut normalized = String::with_capacity(schema.len());
28 let mut chars = schema.chars().peekable();
29
30 while let Some(ch) = chars.next() {
31 normalized.push(ch);
32
33 if ch != '@' || !matches!(chars.peek(), Some(c) if is_schema_tag_char(*c)) {
34 continue;
35 }
36
37 while let Some(c) = chars.peek().copied() {
38 if is_schema_tag_char(c) {
39 normalized.push(c);
40 chars.next();
41 } else {
42 break;
43 }
44 }
45
46 let mut whitespace = String::new();
47 while let Some(c) = chars.peek().copied() {
48 if c.is_whitespace() {
49 whitespace.push(c);
50 chars.next();
51 } else {
52 break;
53 }
54 }
55
56 if matches!(chars.peek(), Some('(')) {
57 continue;
58 }
59
60 normalized.push_str(&whitespace);
61 }
62
63 normalized
64}
65
66fn is_schema_tag_char(ch: char) -> bool {
67 ch.is_ascii_alphanumeric() || ch == '-' || ch == '_'
68}
69
70#[derive(Debug, Clone, Facet)]
76#[facet(metadata_container)]
77pub struct Meta<T> {
78 pub value: T,
80
81 #[facet(metadata = "tag")]
83 pub tag: Option<String>,
84
85 #[facet(metadata = "span")]
87 pub span: Span,
88
89 #[facet(metadata = "doc")]
91 pub doc: Option<Vec<String>>,
92}
93
94impl<T: Hash> Hash for Meta<T> {
95 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
96 self.value.hash(state);
97 }
98}
99
100impl<T: PartialEq> PartialEq for Meta<T> {
101 fn eq(&self, other: &Self) -> bool {
102 self.value == other.value
103 }
104}
105
106impl<T: Eq> Eq for Meta<T> {}
107
108impl Borrow<str> for Meta<String> {
109 fn borrow(&self) -> &str {
110 &self.value
111 }
112}
113
114impl PartialEq<&str> for Meta<String> {
115 fn eq(&self, other: &&str) -> bool {
116 self.value == *other
117 }
118}
119
120impl PartialEq<str> for Meta<String> {
121 fn eq(&self, other: &str) -> bool {
122 self.value == other
123 }
124}
125
126impl<T> Meta<T> {
127 pub fn with_span(value: T, span: Span) -> Self {
129 Self {
130 value,
131 span,
132 doc: None,
133 tag: None,
134 }
135 }
136
137 pub fn doc_string(&self) -> Option<String> {
139 self.doc.as_ref().map(|lines| lines.join("\n"))
140 }
141}
142
143impl Meta<String> {
144 pub fn as_str(&self) -> &str {
146 &self.value
147 }
148}
149
150impl<'a> Meta<std::borrow::Cow<'a, str>> {
151 pub fn as_str(&self) -> &str {
153 &self.value
154 }
155}
156
157impl<T: Copy> Meta<T> {
158 pub fn get(&self) -> T {
160 self.value
161 }
162}
163
164pub trait OptionMetaExt<T> {
166 fn inner(&self) -> Option<&T>;
168 fn meta_span(&self) -> Option<Span>;
170}
171
172impl<T> OptionMetaExt<T> for Option<Meta<T>> {
173 fn inner(&self) -> Option<&T> {
174 self.as_ref().map(|m| &m.value)
175 }
176
177 fn meta_span(&self) -> Option<Span> {
178 self.as_ref().map(|m| m.span)
179 }
180}
181
182pub trait OptionMetaCopyExt<T: Copy> {
184 fn value(&self) -> Option<T>;
186}
187
188impl<T: Copy> OptionMetaCopyExt<T> for Option<Meta<T>> {
189 fn value(&self) -> Option<T> {
190 self.as_ref().map(|m| m.value)
191 }
192}
193
194pub trait OptionMetaDerefExt<T> {
196 fn value_as_ref(&self) -> Option<&T>;
198 fn value_as_deref(&self) -> Option<&<T as Deref>::Target>
200 where
201 T: Deref;
202}
203
204impl<T> OptionMetaDerefExt<T> for Option<Meta<T>> {
205 fn value_as_ref(&self) -> Option<&T> {
206 self.as_ref().map(|m| &m.value)
207 }
208
209 fn value_as_deref(&self) -> Option<&<T as Deref>::Target>
210 where
211 T: Deref,
212 {
213 self.as_ref().map(|m| m.value.deref())
214 }
215}
216
217impl<T> OptionMetaDerefExt<T> for Option<&Meta<T>> {
218 fn value_as_ref(&self) -> Option<&T> {
219 self.map(|m| &m.value)
220 }
221
222 fn value_as_deref(&self) -> Option<&<T as Deref>::Target>
223 where
224 T: Deref,
225 {
226 self.map(|m| m.value.deref())
227 }
228}
229
230impl<T> Deref for Meta<T> {
231 type Target = T;
232 fn deref(&self) -> &Self::Target {
233 &self.value
234 }
235}
236
237#[derive(Debug, Facet)]
240#[facet(transparent)]
241pub struct QueryFile(pub IndexMap<Meta<String>, Decl>);
242
243#[derive(Debug, Facet)]
245#[facet(rename_all = "kebab-case")]
246#[repr(u8)]
247#[allow(clippy::large_enum_variant)]
248pub enum Decl {
249 Select(Select),
251 Insert(Insert),
253 InsertMany(InsertMany),
255 Upsert(Upsert),
257 UpsertMany(UpsertMany),
259 Update(Update),
261 Delete(Delete),
263}
264
265#[derive(Debug, Facet)]
270#[facet(rename_all = "kebab-case")]
271pub struct Select {
272 pub params: Option<Params>,
274
275 pub from: Option<Meta<TableName>>,
277
278 #[facet(rename = "where")]
280 pub where_clause: Option<Where>,
281
282 pub first: Option<Meta<bool>>,
284
285 pub distinct: Option<Meta<bool>>,
287
288 pub distinct_on: Option<DistinctOn>,
290
291 pub order_by: Option<OrderBy>,
293
294 pub limit: Option<Meta<String>>,
296
297 pub offset: Option<Meta<String>>,
299
300 pub fields: Option<SelectFields>,
302
303 pub sql: Option<Meta<String>>,
305
306 pub returns: Option<Returns>,
308}
309
310#[derive(Debug, Facet)]
312pub struct Returns {
313 #[facet(flatten)]
314 pub fields: IndexMap<Meta<ColumnName>, ParamType>,
315}
316
317#[derive(Debug, Facet)]
319#[facet(transparent)]
320pub struct DistinctOn(pub Vec<Meta<ColumnName>>);
321
322#[derive(Debug, Facet)]
324pub struct OrderBy {
325 #[facet(flatten)]
327 pub columns: IndexMap<Meta<ColumnName>, Option<Meta<String>>>,
328}
329
330#[derive(Debug, Clone, Facet)]
332pub struct Where {
333 #[facet(flatten)]
334 pub filters: IndexMap<Meta<ColumnName>, FilterValue>,
335}
336
337#[derive(Debug, Clone, Facet)]
352#[facet(rename_all = "kebab-case")]
353#[repr(u8)]
354pub enum FilterValue {
355 Null,
357 #[facet(rename = "not-null")]
359 NotNull,
360 Ilike(Vec<Meta<String>>),
362 Like(Vec<Meta<String>>),
364 Gt(Vec<Meta<String>>),
366 Lt(Vec<Meta<String>>),
368 Gte(Vec<Meta<String>>),
370 Lte(Vec<Meta<String>>),
372 Ne(Vec<Meta<String>>),
374 In(Vec<Meta<String>>),
376 JsonGet(Vec<Meta<String>>),
378 JsonGetText(Vec<Meta<String>>),
380 Contains(Vec<Meta<String>>),
382 KeyExists(Vec<Meta<String>>),
384 Eq(Vec<Meta<String>>),
386 #[facet(other)]
388 EqBare(Option<Meta<String>>),
389}
390
391#[derive(Debug, Clone, Facet)]
393pub struct Params {
394 #[facet(flatten)]
395 pub params: IndexMap<Meta<ParamName>, ParamType>,
396}
397
398#[derive(Debug, Clone, Facet)]
400#[facet(rename_all = "lowercase")]
401#[repr(u8)]
402pub enum ParamType {
403 String,
404 Int,
405 Float,
407 Bool,
408 Uuid,
409 Decimal,
410 Timestamp,
411 Bytes,
412 Jsonb,
416 Optional(Vec<ParamType>),
418}
419
420#[derive(Debug, Facet)]
422#[facet(metadata_container)]
423pub struct SelectFields {
424 #[facet(metadata = "span")]
426 pub span: Span,
427
428 #[facet(flatten)]
429 pub fields: IndexMap<Meta<ColumnName>, Option<FieldDef>>,
430}
431
432#[derive(Debug, Facet)]
434#[facet(rename_all = "lowercase")]
435#[repr(u8)]
436#[allow(clippy::large_enum_variant)]
437pub enum FieldDef {
438 Rel(Relation),
440 Count(Vec<Meta<TableName>>),
442}
443
444#[derive(Debug, Facet)]
446#[facet(rename_all = "kebab-case")]
447pub struct Relation {
448 pub from: Option<Meta<TableName>>,
450
451 #[facet(rename = "where")]
453 pub where_clause: Option<Where>,
454
455 pub order_by: Option<OrderBy>,
457
458 pub first: Option<Meta<bool>>,
460
461 pub fields: Option<SelectFields>,
463}
464
465#[derive(Debug, Clone, Facet)]
467pub struct Insert {
468 pub params: Option<Params>,
470 pub into: Meta<TableName>,
472 pub values: Values,
474 pub returning: Option<Returning>,
476}
477
478#[derive(Debug, Clone, Facet)]
480pub struct Upsert {
481 pub params: Option<Params>,
483 pub into: Meta<TableName>,
485 #[facet(rename = "on-conflict")]
487 pub on_conflict: OnConflict,
488 pub values: Values,
490 pub returning: Option<Returning>,
492}
493
494#[derive(Debug, Clone, Facet)]
508pub struct InsertMany {
509 pub params: Option<Params>,
511 pub into: Meta<TableName>,
513 pub values: Values,
516 pub returning: Option<Returning>,
518}
519
520#[derive(Debug, Clone, Facet)]
538pub struct UpsertMany {
539 pub params: Option<Params>,
541 pub into: Meta<TableName>,
543 #[facet(rename = "on-conflict")]
545 pub on_conflict: OnConflict,
546 pub values: Values,
548 pub returning: Option<Returning>,
550}
551
552#[derive(Debug, Clone, Facet)]
554pub struct Update {
555 pub params: Option<Params>,
557 pub table: Meta<TableName>,
559 pub set: Values,
561 #[facet(rename = "where")]
563 pub where_clause: Option<Where>,
564 pub returning: Option<Returning>,
566}
567
568#[derive(Debug, Clone, Facet)]
570pub struct Delete {
571 pub params: Option<Params>,
573 pub from: Meta<TableName>,
575 #[facet(rename = "where")]
577 pub where_clause: Option<Where>,
578 pub returning: Option<Returning>,
580}
581
582#[derive(Debug, Clone, Facet)]
584pub struct Values {
585 #[facet(flatten)]
587 pub columns: IndexMap<Meta<ColumnName>, Option<ValueExpr>>,
588}
589
590#[derive(Debug, Clone, Facet)]
592#[facet(untagged)]
593#[repr(u8)]
594pub enum Payload {
595 Scalar(Meta<String>),
597 Seq(Vec<ValueExpr>),
599}
600
601#[derive(Debug, Clone, Facet)]
608#[facet(rename_all = "lowercase")]
609#[repr(u8)]
610pub enum ValueExpr {
611 Default,
613 #[facet(other)]
618 Other {
619 #[facet(tag)]
620 tag: Option<String>,
621 #[facet(content)]
622 content: Option<Payload>,
623 },
624}
625
626#[derive(Debug, Clone, Facet)]
628pub struct OnConflict {
629 pub target: ConflictTarget,
631 pub update: ConflictUpdate,
633}
634
635#[derive(Debug, Clone, Facet)]
637pub struct ConflictTarget {
638 #[facet(flatten)]
639 pub columns: IndexMap<Meta<ColumnName>, ()>,
640}
641
642#[derive(Debug, Clone, Facet)]
644pub struct ConflictUpdate {
645 #[facet(flatten)]
646 pub columns: IndexMap<Meta<ColumnName>, Option<UpdateValue>>,
647}
648
649#[derive(Debug, Clone, Facet)]
651#[facet(rename_all = "lowercase")]
652#[repr(u8)]
653pub enum UpdateValue {
654 Default,
656 #[facet(other)]
658 Other {
659 #[facet(tag)]
660 tag: Option<String>,
661 #[facet(content)]
662 content: Option<Payload>,
663 },
664}
665
666#[derive(Debug, Clone, Facet)]
668pub struct Returning {
669 #[facet(flatten)]
670 pub columns: IndexMap<Meta<ColumnName>, ()>,
671}
672
673impl Select {
678 pub fn is_first(&self) -> bool {
680 self.first.is_some()
681 }
682
683 pub fn has_relations(&self) -> bool {
685 self.fields
686 .as_ref()
687 .map(|select| select.has_relations())
688 .unwrap_or(false)
689 }
690
691 pub fn has_vec_relations(&self) -> bool {
693 self.fields
694 .as_ref()
695 .map(|select| select.has_vec_relations())
696 .unwrap_or(false)
697 }
698
699 pub fn has_nested_vec_relations(&self) -> bool {
701 self.fields
702 .as_ref()
703 .map(|select| select.has_nested_vec_relations())
704 .unwrap_or(false)
705 }
706}
707
708impl SelectFields {
709 pub fn has_relations(&self) -> bool {
711 self.fields
712 .values()
713 .any(|field_def| matches!(field_def, Some(FieldDef::Rel(_))))
714 }
715
716 pub fn has_vec_relations(&self) -> bool {
718 self.fields.values().any(|field_def| {
719 if let Some(FieldDef::Rel(rel)) = field_def {
720 rel.first.is_none()
721 } else {
722 false
723 }
724 })
725 }
726
727 pub fn has_nested_vec_relations(&self) -> bool {
729 for field_def in self.fields.values() {
730 if let Some(FieldDef::Rel(rel)) = field_def
731 && rel.first.is_none()
732 {
733 if let Some(rel_select) = &rel.fields
735 && (rel_select.has_vec_relations() || rel_select.has_nested_vec_relations())
736 {
737 return true;
738 }
739 }
740 }
741 false
742 }
743
744 pub fn has_count(&self) -> bool {
746 self.fields
747 .values()
748 .any(|field_def| matches!(field_def, Some(FieldDef::Count(_))))
749 }
750
751 pub fn columns(&self) -> impl Iterator<Item = (&Meta<ColumnName>, &Option<FieldDef>)> {
753 self.fields
754 .iter()
755 .filter(|(_, field_def)| field_def.is_none())
756 }
757
758 pub fn relations(&self) -> impl Iterator<Item = (&Meta<ColumnName>, &Relation)> {
760 self.fields.iter().filter_map(|(name, field_def)| {
761 if let Some(FieldDef::Rel(rel)) = field_def {
762 Some((name, rel))
763 } else {
764 None
765 }
766 })
767 }
768
769 pub fn counts(&self) -> impl Iterator<Item = (&Meta<ColumnName>, &Vec<Meta<TableName>>)> {
771 self.fields.iter().filter_map(|(name, field_def)| {
772 if let Some(FieldDef::Count(tables)) = field_def {
773 Some((name, tables))
774 } else {
775 None
776 }
777 })
778 }
779
780 pub fn first_column(&self) -> Option<&ColumnName> {
783 self.fields
784 .iter()
785 .find(|(_, field_def)| field_def.is_none())
786 .map(|(name, _)| &name.value)
787 }
788
789 pub fn id_column(&self) -> Option<&ColumnName> {
792 self.fields
794 .iter()
795 .find(|(name, field_def)| field_def.is_none() && name.value.as_str() == "id")
796 .map(|(name, _)| &name.value)
797 .or_else(|| self.first_column())
798 }
799}
800
801impl Relation {
802 pub fn table_name(&self) -> Option<&str> {
806 self.from.as_ref().map(|m| m.value.as_str())
807 }
808
809 pub fn is_first(&self) -> bool {
811 self.first.is_some()
812 }
813
814 pub fn has_relations(&self) -> bool {
816 self.fields
817 .as_ref()
818 .map(|select| select.has_relations())
819 .unwrap_or(false)
820 }
821
822 pub fn has_vec_relations(&self) -> bool {
824 self.fields
825 .as_ref()
826 .map(|select| select.has_vec_relations())
827 .unwrap_or(false)
828 }
829}
830
831impl Params {
832 pub fn iter(&self) -> impl Iterator<Item = (&Meta<ParamName>, &ParamType)> {
834 self.params.iter()
835 }
836}
837
838#[cfg(test)]
839mod tests;