1use nautilus_schema::ir::{
12 DefaultValue, EnumIr, FieldIr, FunctionCall, ResolvedFieldType, ScalarType,
13};
14use serde::Serialize;
15use std::collections::HashMap;
16
17#[derive(Debug, Clone, Serialize)]
24pub struct FilterOperator {
25 pub suffix: String,
26 pub type_name: String,
27}
28
29pub trait LanguageBackend {
41 fn scalar_to_type(&self, scalar: &ScalarType) -> &'static str;
43
44 fn array_type(&self, inner: &str) -> String;
48
49 fn not_in_suffix(&self) -> &'static str;
53
54 fn startswith_suffix(&self) -> &'static str;
58
59 fn endswith_suffix(&self) -> &'static str;
63
64 fn null_suffix(&self) -> &'static str;
68
69 fn is_auto_generated(&self, field: &FieldIr) -> bool {
76 if field.computed.is_some() {
77 return true;
78 }
79 if let Some(default) = &field.default_value {
80 matches!(
81 default,
82 DefaultValue::Function(f)
83 if f.name == "autoincrement" || f.name == "uuid" || f.name == "now"
84 )
85 } else {
86 false
87 }
88 }
89
90 fn numeric_operators(&self, type_name: &str) -> Vec<FilterOperator> {
93 let arr = self.array_type(type_name);
94 vec![
95 FilterOperator {
96 suffix: "lt".to_string(),
97 type_name: type_name.to_string(),
98 },
99 FilterOperator {
100 suffix: "lte".to_string(),
101 type_name: type_name.to_string(),
102 },
103 FilterOperator {
104 suffix: "gt".to_string(),
105 type_name: type_name.to_string(),
106 },
107 FilterOperator {
108 suffix: "gte".to_string(),
109 type_name: type_name.to_string(),
110 },
111 FilterOperator {
112 suffix: "in".to_string(),
113 type_name: arr.clone(),
114 },
115 FilterOperator {
116 suffix: self.not_in_suffix().to_string(),
117 type_name: arr,
118 },
119 ]
120 }
121
122 fn get_filter_operators_for_scalar(&self, scalar: &ScalarType) -> Vec<FilterOperator> {
124 let mut ops: Vec<FilterOperator> = Vec::new();
125
126 match scalar {
127 ScalarType::String => {
128 let str_t = self.scalar_to_type(&ScalarType::String);
129 let arr = self.array_type(str_t);
130 ops.push(FilterOperator {
131 suffix: "contains".to_string(),
132 type_name: str_t.to_string(),
133 });
134 ops.push(FilterOperator {
135 suffix: self.startswith_suffix().to_string(),
136 type_name: str_t.to_string(),
137 });
138 ops.push(FilterOperator {
139 suffix: self.endswith_suffix().to_string(),
140 type_name: str_t.to_string(),
141 });
142 ops.push(FilterOperator {
143 suffix: "in".to_string(),
144 type_name: arr.clone(),
145 });
146 ops.push(FilterOperator {
147 suffix: self.not_in_suffix().to_string(),
148 type_name: arr,
149 });
150 }
151 ScalarType::Int | ScalarType::BigInt => {
152 ops.extend(self.numeric_operators(self.scalar_to_type(scalar)));
153 }
154 ScalarType::Float => {
155 ops.extend(self.numeric_operators(self.scalar_to_type(scalar)));
156 }
157 ScalarType::Decimal { .. } => {
158 ops.extend(self.numeric_operators(self.scalar_to_type(scalar)));
159 }
160 ScalarType::DateTime => {
161 ops.extend(self.numeric_operators(self.scalar_to_type(scalar)));
162 }
163 ScalarType::Uuid => {
164 let uuid_t = self.scalar_to_type(&ScalarType::Uuid);
165 let arr = self.array_type(uuid_t);
166 ops.push(FilterOperator {
167 suffix: "in".to_string(),
168 type_name: arr.clone(),
169 });
170 ops.push(FilterOperator {
171 suffix: self.not_in_suffix().to_string(),
172 type_name: arr,
173 });
174 }
175 ScalarType::Xml | ScalarType::Char { .. } | ScalarType::VarChar { .. } => {
176 let str_t = self.scalar_to_type(scalar);
177 let arr = self.array_type(str_t);
178 ops.push(FilterOperator {
179 suffix: "contains".to_string(),
180 type_name: str_t.to_string(),
181 });
182 ops.push(FilterOperator {
183 suffix: self.startswith_suffix().to_string(),
184 type_name: str_t.to_string(),
185 });
186 ops.push(FilterOperator {
187 suffix: self.endswith_suffix().to_string(),
188 type_name: str_t.to_string(),
189 });
190 ops.push(FilterOperator {
191 suffix: "in".to_string(),
192 type_name: arr.clone(),
193 });
194 ops.push(FilterOperator {
195 suffix: self.not_in_suffix().to_string(),
196 type_name: arr,
197 });
198 }
199 ScalarType::Boolean | ScalarType::Bytes | ScalarType::Json | ScalarType::Jsonb => {}
201 }
202
203 ops.push(FilterOperator {
205 suffix: "not".to_string(),
206 type_name: self.scalar_to_type(scalar).to_string(),
207 });
208
209 ops
210 }
211
212 fn get_filter_operators_for_field(
215 &self,
216 field: &FieldIr,
217 enums: &HashMap<String, EnumIr>,
218 ) -> Vec<FilterOperator> {
219 let mut ops: Vec<FilterOperator> = Vec::new();
220
221 match &field.field_type {
222 ResolvedFieldType::Scalar(scalar) => {
223 ops = self.get_filter_operators_for_scalar(scalar);
224 }
225 ResolvedFieldType::Enum { enum_name } => {
226 let enum_type = if enums.contains_key(enum_name) {
227 enum_name.clone()
228 } else {
229 self.scalar_to_type(&ScalarType::String).to_string()
231 };
232 let arr = self.array_type(&enum_type);
233 ops.push(FilterOperator {
234 suffix: "in".to_string(),
235 type_name: arr.clone(),
236 });
237 ops.push(FilterOperator {
238 suffix: self.not_in_suffix().to_string(),
239 type_name: arr,
240 });
241 ops.push(FilterOperator {
242 suffix: "not".to_string(),
243 type_name: enum_type,
244 });
245 }
246 ResolvedFieldType::Relation(_) | ResolvedFieldType::CompositeType { .. } => {
247 }
249 }
250
251 if !field.is_required || self.is_auto_generated(field) {
253 ops.push(FilterOperator {
254 suffix: self.null_suffix().to_string(),
255 type_name: self.scalar_to_type(&ScalarType::Boolean).to_string(),
256 });
257 }
258
259 ops
260 }
261
262 fn null_literal(&self) -> &'static str;
264
265 fn true_literal(&self) -> &'static str;
267
268 fn false_literal(&self) -> &'static str;
270
271 fn string_literal(&self, s: &str) -> String;
273
274 fn empty_array_literal(&self) -> &'static str;
276
277 fn enum_variant_literal(&self, variant: &str) -> String {
279 variant.to_string()
280 }
281
282 fn relation_type(&self, target_model: &str) -> String {
286 target_model.to_string()
287 }
288
289 fn get_base_type(&self, field: &FieldIr, enums: &HashMap<String, EnumIr>) -> String {
291 match &field.field_type {
292 ResolvedFieldType::Scalar(scalar) => self.scalar_to_type(scalar).to_string(),
293 ResolvedFieldType::Enum { enum_name } => {
294 if enums.contains_key(enum_name) {
295 enum_name.clone()
296 } else {
297 self.scalar_to_type(&ScalarType::String).to_string()
298 }
299 }
300 ResolvedFieldType::CompositeType { type_name } => type_name.clone(),
301 ResolvedFieldType::Relation(rel) => self.relation_type(&rel.target_model),
302 }
303 }
304
305 fn get_default_value(&self, field: &FieldIr) -> Option<String> {
307 if let Some(default) = &field.default_value {
308 match default {
309 DefaultValue::Function(FunctionCall { name, .. })
310 if matches!(name.as_str(), "now" | "uuid" | "autoincrement") =>
311 {
312 return Some(self.null_literal().to_string());
313 }
314 DefaultValue::Function(_) => return None,
315 DefaultValue::String(s) => return Some(self.string_literal(s)),
316 DefaultValue::Number(n) => return Some(n.clone()),
317 DefaultValue::Boolean(b) => {
318 return Some(if *b {
319 self.true_literal().to_string()
320 } else {
321 self.false_literal().to_string()
322 });
323 }
324 DefaultValue::EnumVariant(v) => return Some(self.enum_variant_literal(v)),
325 }
326 }
327
328 if field.is_array {
329 Some(self.empty_array_literal().to_string())
330 } else if !field.is_required {
331 Some(self.null_literal().to_string())
332 } else {
333 None
334 }
335 }
336}
337
338pub struct PythonBackend;
340
341impl LanguageBackend for PythonBackend {
342 fn scalar_to_type(&self, scalar: &ScalarType) -> &'static str {
343 match scalar {
344 ScalarType::String => "str",
345 ScalarType::Int => "int",
346 ScalarType::BigInt => "int",
347 ScalarType::Float => "float",
348 ScalarType::Decimal { .. } => "Decimal",
349 ScalarType::Boolean => "bool",
350 ScalarType::DateTime => "datetime",
351 ScalarType::Bytes => "bytes",
352 ScalarType::Json => "Dict[str, Any]",
353 ScalarType::Uuid => "UUID",
354 ScalarType::Jsonb => "Dict[str, Any]",
355 ScalarType::Xml | ScalarType::Char { .. } | ScalarType::VarChar { .. } => "str",
356 }
357 }
358
359 fn array_type(&self, inner: &str) -> String {
360 format!("List[{}]", inner)
361 }
362
363 fn not_in_suffix(&self) -> &'static str {
364 "not_in"
365 }
366
367 fn startswith_suffix(&self) -> &'static str {
368 "startswith"
369 }
370
371 fn endswith_suffix(&self) -> &'static str {
372 "endswith"
373 }
374
375 fn null_suffix(&self) -> &'static str {
376 "is_null"
377 }
378
379 fn null_literal(&self) -> &'static str {
380 "None"
381 }
382 fn true_literal(&self) -> &'static str {
383 "True"
384 }
385 fn false_literal(&self) -> &'static str {
386 "False"
387 }
388 fn string_literal(&self, s: &str) -> String {
389 format!("\"{}\"", s)
390 }
391 fn empty_array_literal(&self) -> &'static str {
392 "Field(default_factory=list)"
393 }
394}
395
396pub struct JsBackend;
398
399impl LanguageBackend for JsBackend {
400 fn scalar_to_type(&self, scalar: &ScalarType) -> &'static str {
401 match scalar {
402 ScalarType::String => "string",
403 ScalarType::Int => "number",
404 ScalarType::BigInt => "number",
405 ScalarType::Float => "number",
406 ScalarType::Decimal { .. } => "string", ScalarType::Boolean => "boolean",
408 ScalarType::DateTime => "Date",
409 ScalarType::Bytes => "Buffer",
410 ScalarType::Json => "Record<string, unknown>",
411 ScalarType::Uuid => "string",
412 ScalarType::Jsonb => "Record<string, unknown>",
413 ScalarType::Xml | ScalarType::Char { .. } | ScalarType::VarChar { .. } => "string",
414 }
415 }
416
417 fn array_type(&self, inner: &str) -> String {
418 format!("{}[]", inner)
419 }
420
421 fn not_in_suffix(&self) -> &'static str {
422 "notIn"
423 }
424
425 fn startswith_suffix(&self) -> &'static str {
426 "startsWith"
427 }
428
429 fn endswith_suffix(&self) -> &'static str {
430 "endsWith"
431 }
432
433 fn null_suffix(&self) -> &'static str {
434 "isNull"
435 }
436
437 fn null_literal(&self) -> &'static str {
438 "null"
439 }
440 fn true_literal(&self) -> &'static str {
441 "true"
442 }
443 fn false_literal(&self) -> &'static str {
444 "false"
445 }
446 fn string_literal(&self, s: &str) -> String {
447 format!("'{}'", s)
448 }
449 fn empty_array_literal(&self) -> &'static str {
450 "[]"
451 }
452 fn enum_variant_literal(&self, variant: &str) -> String {
453 format!("'{}'", variant)
454 }
455 fn relation_type(&self, target_model: &str) -> String {
456 format!("{}Model", target_model)
457 }
458}