Skip to main content

circles_types/
query.rs

1use serde::{Deserialize, Serialize};
2use std::collections::BTreeMap;
3
4/// Filter types for query predicates.
5#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
6pub enum FilterType {
7    Equals,
8    NotEquals,
9    GreaterThan,
10    LessThan,
11    GreaterOrEqualThan,
12    LessOrEqualThan,
13    Like,
14}
15
16/// Conjunction types for combining predicates.
17#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
18pub enum ConjunctionType {
19    And,
20    Or,
21}
22
23/// Filter predicate for querying.
24#[derive(Debug, Clone, Serialize, Deserialize)]
25pub struct FilterPredicate {
26    #[serde(rename = "Type")]
27    pub predicate_type: String, // Always "FilterPredicate"
28    #[serde(rename = "FilterType")]
29    pub filter_type: FilterType,
30    #[serde(rename = "Column")]
31    pub column: String,
32    #[serde(rename = "Value")]
33    pub value: serde_json::Value, // Can be string, number, or boolean
34}
35
36impl FilterPredicate {
37    pub fn new(
38        filter_type: FilterType,
39        column: String,
40        value: impl Into<serde_json::Value>,
41    ) -> Self {
42        Self {
43            predicate_type: "FilterPredicate".to_string(),
44            filter_type,
45            column,
46            value: value.into(),
47        }
48    }
49
50    pub fn equals(column: String, value: impl Into<serde_json::Value>) -> Self {
51        Self::new(FilterType::Equals, column, value)
52    }
53
54    pub fn not_equals(column: String, value: impl Into<serde_json::Value>) -> Self {
55        Self::new(FilterType::NotEquals, column, value)
56    }
57
58    pub fn greater_than(column: String, value: impl Into<serde_json::Value>) -> Self {
59        Self::new(FilterType::GreaterThan, column, value)
60    }
61
62    pub fn less_than(column: String, value: impl Into<serde_json::Value>) -> Self {
63        Self::new(FilterType::LessThan, column, value)
64    }
65
66    pub fn like(column: String, pattern: String) -> Self {
67        Self::new(FilterType::Like, column, pattern)
68    }
69}
70
71/// Conjunction for combining multiple predicates.
72#[derive(Debug, Clone, Serialize, Deserialize)]
73pub struct Conjunction {
74    #[serde(rename = "Type")]
75    pub conjunction_type_name: String, // Always "Conjunction"
76    #[serde(rename = "ConjunctionType")]
77    pub conjunction_type: ConjunctionType,
78    #[serde(rename = "Predicates")]
79    pub predicates: Vec<Filter>,
80}
81
82impl Conjunction {
83    pub fn new(conjunction_type: ConjunctionType, predicates: Vec<Filter>) -> Self {
84        Self {
85            conjunction_type_name: "Conjunction".to_string(),
86            conjunction_type,
87            predicates,
88        }
89    }
90
91    pub fn and(predicates: Vec<Filter>) -> Self {
92        Self::new(ConjunctionType::And, predicates)
93    }
94
95    pub fn or(predicates: Vec<Filter>) -> Self {
96        Self::new(ConjunctionType::Or, predicates)
97    }
98}
99
100/// Filter type (either a predicate or conjunction).
101#[derive(Debug, Clone, Serialize, Deserialize)]
102#[serde(untagged)]
103pub enum Filter {
104    Predicate(FilterPredicate),
105    Conjunction(Conjunction),
106}
107
108impl From<FilterPredicate> for Filter {
109    fn from(predicate: FilterPredicate) -> Self {
110        Filter::Predicate(predicate)
111    }
112}
113
114impl From<Conjunction> for Filter {
115    fn from(conjunction: Conjunction) -> Self {
116        Filter::Conjunction(conjunction)
117    }
118}
119
120/// Order direction for query results.
121#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
122pub enum SortOrder {
123    ASC,
124    DESC,
125}
126
127/// Order by clause.
128#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
129pub struct OrderBy {
130    #[serde(rename = "Column")]
131    pub column: String,
132    #[serde(rename = "SortOrder")]
133    pub sort_order: SortOrder,
134}
135
136impl OrderBy {
137    pub fn new(column: String, sort_order: SortOrder) -> Self {
138        Self { column, sort_order }
139    }
140
141    pub fn asc(column: String) -> Self {
142        Self::new(column, SortOrder::ASC)
143    }
144
145    pub fn desc(column: String) -> Self {
146        Self::new(column, SortOrder::DESC)
147    }
148}
149
150/// Cursor column configuration for flexible paged queries.
151#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
152pub struct CursorColumn {
153    pub name: String,
154    pub sort_order: SortOrder,
155}
156
157impl CursorColumn {
158    pub fn new(name: String, sort_order: SortOrder) -> Self {
159        Self { name, sort_order }
160    }
161
162    pub fn asc(name: String) -> Self {
163        Self::new(name, SortOrder::ASC)
164    }
165
166    pub fn desc(name: String) -> Self {
167        Self::new(name, SortOrder::DESC)
168    }
169}
170
171/// Query parameters for `circles_query`.
172#[derive(Debug, Clone, Serialize, Deserialize)]
173pub struct QueryParams {
174    #[serde(rename = "Namespace")]
175    pub namespace: String,
176    #[serde(rename = "Table")]
177    pub table: String,
178    #[serde(rename = "Columns")]
179    pub columns: Vec<String>,
180    #[serde(rename = "Filter")]
181    pub filter: Vec<Filter>,
182    #[serde(rename = "Order")]
183    pub order: Vec<OrderBy>,
184    #[serde(rename = "Limit", skip_serializing_if = "Option::is_none")]
185    pub limit: Option<u32>,
186}
187
188impl QueryParams {
189    pub fn new(namespace: String, table: String, columns: Vec<String>) -> Self {
190        Self {
191            namespace,
192            table,
193            columns,
194            filter: Vec::new(),
195            order: Vec::new(),
196            limit: None,
197        }
198    }
199
200    pub fn with_filter(mut self, filter: Vec<Filter>) -> Self {
201        self.filter = filter;
202        self
203    }
204
205    pub fn with_order(mut self, order: Vec<OrderBy>) -> Self {
206        self.order = order;
207        self
208    }
209
210    pub fn with_limit(mut self, limit: u32) -> Self {
211        self.limit = Some(limit);
212        self
213    }
214}
215
216/// Column information for table metadata.
217#[derive(Debug, Clone, Serialize, Deserialize)]
218pub struct ColumnInfo {
219    #[serde(rename = "Name")]
220    pub name: String,
221    #[serde(rename = "Type")]
222    pub column_type: String,
223}
224
225/// Table information from circles_tables
226#[derive(Debug, Clone, Serialize, Deserialize)]
227pub struct TableInfo {
228    #[serde(rename = "Namespace")]
229    pub namespace: String,
230    #[serde(rename = "Table")]
231    pub table: String,
232    #[serde(rename = "Columns")]
233    pub columns: Vec<ColumnInfo>,
234}
235
236/// Defines the minimum columns any event row must have for cursor-based pagination.
237/// These values are important for determining cursor position in result sets.
238#[derive(Debug, Clone, Serialize, Deserialize)]
239pub struct EventRow {
240    pub block_number: u64,
241    pub transaction_index: u32,
242    pub log_index: u32,
243    pub batch_index: Option<u32>,
244    pub timestamp: Option<u64>,
245}
246
247/// A cursor is a sortable unique identifier for a specific log entry.
248/// Used to paginate through query results efficiently.
249#[derive(Debug, Clone, Serialize, Deserialize, Default)]
250pub struct Cursor {
251    pub block_number: u64,
252    pub transaction_index: u32,
253    pub log_index: u32,
254    pub batch_index: Option<u32>,
255    pub timestamp: Option<u64>,
256    #[serde(default)]
257    pub values: BTreeMap<String, serde_json::Value>,
258}
259
260impl Cursor {
261    pub fn value(&self, column: &str) -> Option<&serde_json::Value> {
262        self.values.get(column)
263    }
264
265    pub fn insert_value(&mut self, column: String, value: serde_json::Value) {
266        self.values.insert(column, value);
267    }
268}
269
270/// Result of a paginated query
271#[derive(Debug, Clone, Serialize, Deserialize)]
272pub struct PagedResult<TRow>
273where
274    TRow: Clone + Serialize,
275{
276    /// The number of results that were requested
277    pub limit: u32,
278    /// The number of results that were returned
279    pub size: u32,
280    /// If the query returned results, this will be the cursor for the first result
281    pub first_cursor: Option<Cursor>,
282    /// If the query returned results, this will be the cursor for the last result
283    pub last_cursor: Option<Cursor>,
284    /// The sort order of the results
285    pub sort_order: SortOrder,
286    /// Whether there are more results available
287    pub has_more: bool,
288    /// The results of the query
289    pub results: Vec<TRow>,
290}
291
292impl<TRow> PagedResult<TRow>
293where
294    TRow: Clone + Serialize,
295{
296    pub fn new(
297        limit: u32,
298        results: Vec<TRow>,
299        sort_order: SortOrder,
300        has_more: bool,
301        first_cursor: Option<Cursor>,
302        last_cursor: Option<Cursor>,
303    ) -> Self {
304        let size = results.len() as u32;
305        Self {
306            limit,
307            size,
308            first_cursor,
309            last_cursor,
310            sort_order,
311            has_more,
312            results,
313        }
314    }
315}
316
317/// Parameters for a paginated query
318#[derive(Debug, Clone, Serialize, Deserialize)]
319pub struct PagedQueryParams {
320    /// The namespace of the table to query
321    pub namespace: String,
322    /// The name of the table to query
323    pub table: String,
324    /// The order to sort the results
325    pub sort_order: SortOrder,
326    /// The columns to return in the results
327    pub columns: Vec<String>,
328    /// The filters to apply to the query
329    #[serde(skip_serializing_if = "Option::is_none")]
330    pub filter: Option<Vec<Filter>>,
331    /// Custom cursor columns for non-event tables.
332    #[serde(skip_serializing_if = "Option::is_none")]
333    pub cursor_columns: Option<Vec<CursorColumn>>,
334    /// Explicit order columns when they differ from the cursor columns.
335    #[serde(skip_serializing_if = "Option::is_none")]
336    pub order_columns: Option<Vec<OrderBy>>,
337    /// The number of results to return per page
338    pub limit: u32,
339}
340
341impl PagedQueryParams {
342    pub fn new(
343        namespace: String,
344        table: String,
345        sort_order: SortOrder,
346        columns: Vec<String>,
347        limit: u32,
348    ) -> Self {
349        Self {
350            namespace,
351            table,
352            sort_order,
353            columns,
354            filter: None,
355            cursor_columns: None,
356            order_columns: None,
357            limit,
358        }
359    }
360
361    pub fn with_filter(mut self, filter: Vec<Filter>) -> Self {
362        self.filter = Some(filter);
363        self
364    }
365
366    pub fn with_cursor_columns(mut self, cursor_columns: Vec<CursorColumn>) -> Self {
367        self.cursor_columns = Some(cursor_columns);
368        self
369    }
370
371    pub fn with_order_columns(mut self, order_columns: Vec<OrderBy>) -> Self {
372        self.order_columns = Some(order_columns);
373        self
374    }
375
376    pub fn resolved_cursor_columns(&self) -> Vec<CursorColumn> {
377        if let Some(cursor_columns) = &self.cursor_columns {
378            if !cursor_columns.is_empty() {
379                return cursor_columns.clone();
380            }
381        }
382
383        let mut columns = vec![
384            CursorColumn::new("blockNumber".to_string(), self.sort_order.clone()),
385            CursorColumn::new("transactionIndex".to_string(), self.sort_order.clone()),
386            CursorColumn::new("logIndex".to_string(), self.sort_order.clone()),
387        ];
388        if self.table == "TransferBatch" {
389            columns.push(CursorColumn::new(
390                "batchIndex".to_string(),
391                self.sort_order.clone(),
392            ));
393        }
394        columns
395    }
396
397    pub fn resolved_order_columns(&self) -> Vec<OrderBy> {
398        if let Some(order_columns) = &self.order_columns {
399            if !order_columns.is_empty() {
400                return order_columns.clone();
401            }
402        }
403
404        self.resolved_cursor_columns()
405            .into_iter()
406            .map(|column| OrderBy::new(column.name, column.sort_order))
407            .collect()
408    }
409}