1use std::fmt;
2use std::marker::PhantomData;
3
4use crate::{ConfigError, Operator, SortOrder};
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
8#[repr(transparent)]
9pub struct FieldId(pub u16);
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
13#[cfg_attr(feature = "serde", derive(serde::Serialize))]
14#[non_exhaustive]
15pub enum ValueType {
16 Bool,
17 I32,
18 I64,
19 F32,
20 F64,
21 #[cfg(feature = "decimal")]
22 Decimal,
23 Text,
24 Bytes,
25 #[cfg(feature = "chrono")]
26 Date,
27 #[cfg(feature = "chrono")]
28 Time,
29 #[cfg(feature = "chrono")]
30 DateTime,
31 #[cfg(feature = "chrono")]
32 Timestamp,
33 #[cfg(feature = "uuid")]
34 Uuid,
35 #[cfg(feature = "json")]
36 Json,
37 #[cfg(feature = "postgres-types")]
38 ArrayI64,
39 #[cfg(feature = "postgres-types")]
40 ArrayText,
41 #[cfg(feature = "postgres-types")]
42 Int8Range,
43 #[cfg(feature = "postgres-types")]
44 Network,
45}
46
47impl ValueType {
49 pub fn supports_cursor(self) -> bool {
50 match self {
51 #[cfg(feature = "json")]
52 Self::Json => false,
53 #[cfg(feature = "postgres-types")]
54 Self::ArrayI64 | Self::ArrayText | Self::Int8Range | Self::Network => false,
55 _ => true,
56 }
57 }
58}
59
60#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
62pub enum FieldKind {
63 Scalar,
64 GroupKey,
65 Aggregate,
66 FilterOnly,
67}
68
69#[derive(Debug, Clone, Copy)]
71pub struct OperatorSet(pub &'static [Operator]);
72
73impl OperatorSet {
74 #[must_use]
75 pub fn contains(self, operator: Operator) -> bool {
76 self.0.contains(&operator)
77 }
78}
79
80#[derive(Debug)]
82pub struct FieldMeta {
83 pub id: FieldId,
84 pub public_name: &'static str,
85 pub column: &'static str,
86 pub result_alias: Option<&'static str>,
87 pub value_type: ValueType,
88 pub kind: FieldKind,
89 pub filterable: bool,
90 pub selectable: bool,
91 pub sortable: bool,
92 pub operators: OperatorSet,
93}
94
95#[derive(Debug, Clone, Copy, PartialEq, Eq)]
97pub struct OrderMeta {
98 pub field: FieldId,
99 pub direction: SortOrder,
100}
101
102#[derive(Debug, Clone, Copy, PartialEq, Eq)]
104pub struct EntityLimits {
105 pub max_page_size: u32,
106 pub max_offset: u64,
107 pub timeout_ms: u64,
108}
109
110impl Default for EntityLimits {
111 fn default() -> Self {
112 Self {
113 max_page_size: 100,
114 max_offset: 20_000,
115 timeout_ms: 5_000,
116 }
117 }
118}
119
120#[derive(Debug)]
122pub struct EntityMeta {
123 pub entity_name: &'static str,
124 pub from: &'static str,
125 pub base_where: Option<&'static str>,
126 pub fixed_having: Option<&'static str>,
127 pub distinct: bool,
128 pub fields: &'static [FieldMeta],
129 pub default_order: &'static [OrderMeta],
130 pub stable_order: &'static [OrderMeta],
131 pub limits: EntityLimits,
132 pub schema_fingerprint: u64,
133}
134
135impl EntityMeta {
136 #[must_use]
137 pub fn field(&'static self, id: FieldId) -> Option<&'static FieldMeta> {
138 self.fields.get(usize::from(id.0))
139 }
140
141 #[must_use]
142 pub fn resolve_field(&'static self, name: &str) -> Option<&'static FieldMeta> {
143 self.fields.iter().find(|field| field.public_name == name)
144 }
145
146 #[must_use]
147 pub fn is_grouped(&self) -> bool {
148 self.fields
149 .iter()
150 .any(|field| matches!(field.kind, FieldKind::GroupKey | FieldKind::Aggregate))
151 }
152
153 pub fn validate(&self) -> Result<(), ConfigError> {
154 if self.entity_name.is_empty() || self.from.trim().is_empty() {
155 return Err(ConfigError::Invalid(
156 "entity_name and from must not be empty".to_owned(),
157 ));
158 }
159 for (index, field) in self.fields.iter().enumerate() {
160 if usize::from(field.id.0) != index {
161 return Err(ConfigError::Invalid(format!(
162 "field {} has non-contiguous FieldId {}",
163 field.public_name, field.id.0
164 )));
165 }
166 }
167 if self.is_grouped()
168 && self.fields.iter().any(|field| {
169 field.result_alias.is_some() && matches!(field.kind, FieldKind::Scalar)
170 })
171 {
172 return Err(ConfigError::Invalid(
173 "grouped entities cannot return Scalar fields".to_owned(),
174 ));
175 }
176 Ok(())
177 }
178}
179
180pub trait SearchEntity: Sized + Send + Sync + 'static {
182 fn condition_order(_field: FieldId) -> Option<ConditionOrder> {
184 None
185 }
186 fn custom_operator_names(_field: FieldId) -> &'static [&'static str] {
189 &[]
190 }
191 fn codec_name(_field: FieldId) -> Option<&'static str> {
193 None
194 }
195 fn label_bindings() -> &'static [(&'static str, &'static str)] {
198 &[]
199 }
200 const META: &'static EntityMeta;
201
202 fn cursor_key_non_null(_field: FieldId) -> bool {
205 false
206 }
207
208 fn cursor_value(&self, _field: FieldId) -> Option<crate::CursorValue> {
209 None
210 }
211}
212
213#[derive(Debug, Clone, Copy)]
214pub struct ConditionOrder {
215 pub source: FieldId,
216 pub operator: &'static str,
217 pub expression: crate::CustomOperator,
218}
219
220#[derive(Debug, Clone, Copy, PartialEq, Eq)]
221pub struct ProjectionField {
222 pub source: FieldId,
223 pub result_alias: &'static str,
224}
225
226pub trait SearchProjection<E: SearchEntity>: Sized + Send + Sync + 'static {
227 const FIELDS: &'static [ProjectionField];
228
229 fn cursor_value(&self, _source: FieldId) -> Option<crate::CursorValue> {
230 None
231 }
232}
233
234#[derive(PartialEq, Eq, Hash)]
236pub struct FieldRef<E, T, K> {
237 id: FieldId,
238 _marker: PhantomData<fn() -> (E, T, K)>,
239}
240
241impl<E, T, K> FieldRef<E, T, K> {
242 #[doc(hidden)]
243 pub const fn new(id: FieldId) -> Self {
244 Self {
245 id,
246 _marker: PhantomData,
247 }
248 }
249
250 #[must_use]
251 pub const fn id(self) -> FieldId {
252 self.id
253 }
254}
255
256impl<E, T, K> Clone for FieldRef<E, T, K> {
257 fn clone(&self) -> Self {
258 *self
259 }
260}
261
262impl<E, T, K> Copy for FieldRef<E, T, K> {}
263
264impl<E, T, K> fmt::Debug for FieldRef<E, T, K> {
265 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
266 formatter.debug_tuple("FieldRef").field(&self.id).finish()
267 }
268}
269
270pub enum ScalarKind {}
271pub enum GroupKeyKind {}
272pub enum AggregateKind {}
273pub enum FilterOnlyKind {}
274
275pub trait RowPhaseKind {}
276impl RowPhaseKind for ScalarKind {}
277impl RowPhaseKind for GroupKeyKind {}
278impl RowPhaseKind for FilterOnlyKind {}
279
280pub trait SummableKind {}
281impl SummableKind for ScalarKind {}
282impl SummableKind for GroupKeyKind {}
283impl SummableKind for AggregateKind {}
284
285pub struct FilterOnly<T>(PhantomData<fn() -> T>);
287
288impl<T> Default for FilterOnly<T> {
289 fn default() -> Self {
290 Self(PhantomData)
291 }
292}
293
294impl<T> Clone for FilterOnly<T> {
295 fn clone(&self) -> Self {
296 *self
297 }
298}
299
300impl<T> Copy for FilterOnly<T> {}
301
302impl<T> fmt::Debug for FilterOnly<T> {
303 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
304 formatter.write_str("FilterOnly")
305 }
306}