use nautilus_schema::ir::{
DefaultValue, EnumIr, FieldIr, FunctionCall, ResolvedFieldType, ScalarType,
};
use serde::Serialize;
use std::collections::HashMap;
#[derive(Debug, Clone, Serialize)]
pub struct FilterOperator {
pub suffix: String,
pub type_name: String,
}
pub trait LanguageBackend {
fn scalar_to_type(&self, scalar: &ScalarType) -> &'static str;
fn array_type(&self, inner: &str) -> String;
fn not_in_suffix(&self) -> &'static str;
fn startswith_suffix(&self) -> &'static str;
fn endswith_suffix(&self) -> &'static str;
fn null_suffix(&self) -> &'static str;
fn is_auto_generated(&self, field: &FieldIr) -> bool {
if field.computed.is_some() {
return true;
}
if let Some(default) = &field.default_value {
matches!(
default,
DefaultValue::Function(f)
if f.name == "autoincrement" || f.name == "uuid" || f.name == "now"
)
} else {
false
}
}
fn numeric_operators(&self, type_name: &str) -> Vec<FilterOperator> {
let arr = self.array_type(type_name);
vec![
FilterOperator {
suffix: "lt".to_string(),
type_name: type_name.to_string(),
},
FilterOperator {
suffix: "lte".to_string(),
type_name: type_name.to_string(),
},
FilterOperator {
suffix: "gt".to_string(),
type_name: type_name.to_string(),
},
FilterOperator {
suffix: "gte".to_string(),
type_name: type_name.to_string(),
},
FilterOperator {
suffix: "in".to_string(),
type_name: arr.clone(),
},
FilterOperator {
suffix: self.not_in_suffix().to_string(),
type_name: arr,
},
]
}
fn get_filter_operators_for_scalar(&self, scalar: &ScalarType) -> Vec<FilterOperator> {
let mut ops: Vec<FilterOperator> = Vec::new();
match scalar {
ScalarType::String => {
let str_t = self.scalar_to_type(&ScalarType::String);
let arr = self.array_type(str_t);
ops.push(FilterOperator {
suffix: "contains".to_string(),
type_name: str_t.to_string(),
});
ops.push(FilterOperator {
suffix: self.startswith_suffix().to_string(),
type_name: str_t.to_string(),
});
ops.push(FilterOperator {
suffix: self.endswith_suffix().to_string(),
type_name: str_t.to_string(),
});
ops.push(FilterOperator {
suffix: "in".to_string(),
type_name: arr.clone(),
});
ops.push(FilterOperator {
suffix: self.not_in_suffix().to_string(),
type_name: arr,
});
}
ScalarType::Int | ScalarType::BigInt => {
ops.extend(self.numeric_operators(self.scalar_to_type(scalar)));
}
ScalarType::Float => {
ops.extend(self.numeric_operators(self.scalar_to_type(scalar)));
}
ScalarType::Decimal { .. } => {
ops.extend(self.numeric_operators(self.scalar_to_type(scalar)));
}
ScalarType::DateTime => {
ops.extend(self.numeric_operators(self.scalar_to_type(scalar)));
}
ScalarType::Uuid => {
let uuid_t = self.scalar_to_type(&ScalarType::Uuid);
let arr = self.array_type(uuid_t);
ops.push(FilterOperator {
suffix: "in".to_string(),
type_name: arr.clone(),
});
ops.push(FilterOperator {
suffix: self.not_in_suffix().to_string(),
type_name: arr,
});
}
ScalarType::Xml | ScalarType::Char { .. } | ScalarType::VarChar { .. } => {
let str_t = self.scalar_to_type(scalar);
let arr = self.array_type(str_t);
ops.push(FilterOperator {
suffix: "contains".to_string(),
type_name: str_t.to_string(),
});
ops.push(FilterOperator {
suffix: self.startswith_suffix().to_string(),
type_name: str_t.to_string(),
});
ops.push(FilterOperator {
suffix: self.endswith_suffix().to_string(),
type_name: str_t.to_string(),
});
ops.push(FilterOperator {
suffix: "in".to_string(),
type_name: arr.clone(),
});
ops.push(FilterOperator {
suffix: self.not_in_suffix().to_string(),
type_name: arr,
});
}
ScalarType::Boolean | ScalarType::Bytes | ScalarType::Json | ScalarType::Jsonb => {}
}
ops.push(FilterOperator {
suffix: "not".to_string(),
type_name: self.scalar_to_type(scalar).to_string(),
});
ops
}
fn get_filter_operators_for_field(
&self,
field: &FieldIr,
enums: &HashMap<String, EnumIr>,
) -> Vec<FilterOperator> {
let mut ops: Vec<FilterOperator> = Vec::new();
match &field.field_type {
ResolvedFieldType::Scalar(scalar) => {
ops = self.get_filter_operators_for_scalar(scalar);
}
ResolvedFieldType::Enum { enum_name } => {
let enum_type = if enums.contains_key(enum_name) {
enum_name.clone()
} else {
self.scalar_to_type(&ScalarType::String).to_string()
};
let arr = self.array_type(&enum_type);
ops.push(FilterOperator {
suffix: "in".to_string(),
type_name: arr.clone(),
});
ops.push(FilterOperator {
suffix: self.not_in_suffix().to_string(),
type_name: arr,
});
ops.push(FilterOperator {
suffix: "not".to_string(),
type_name: enum_type,
});
}
ResolvedFieldType::Relation(_) | ResolvedFieldType::CompositeType { .. } => {
}
}
if !field.is_required || self.is_auto_generated(field) {
ops.push(FilterOperator {
suffix: self.null_suffix().to_string(),
type_name: self.scalar_to_type(&ScalarType::Boolean).to_string(),
});
}
ops
}
fn null_literal(&self) -> &'static str;
fn true_literal(&self) -> &'static str;
fn false_literal(&self) -> &'static str;
fn string_literal(&self, s: &str) -> String;
fn empty_array_literal(&self) -> &'static str;
fn enum_variant_literal(&self, variant: &str) -> String {
variant.to_string()
}
fn relation_type(&self, target_model: &str) -> String {
target_model.to_string()
}
fn get_base_type(&self, field: &FieldIr, enums: &HashMap<String, EnumIr>) -> String {
match &field.field_type {
ResolvedFieldType::Scalar(scalar) => self.scalar_to_type(scalar).to_string(),
ResolvedFieldType::Enum { enum_name } => {
if enums.contains_key(enum_name) {
enum_name.clone()
} else {
self.scalar_to_type(&ScalarType::String).to_string()
}
}
ResolvedFieldType::CompositeType { type_name } => type_name.clone(),
ResolvedFieldType::Relation(rel) => self.relation_type(&rel.target_model),
}
}
fn get_default_value(&self, field: &FieldIr) -> Option<String> {
if let Some(default) = &field.default_value {
match default {
DefaultValue::Function(FunctionCall { name, .. })
if matches!(name.as_str(), "now" | "uuid" | "autoincrement") =>
{
return Some(self.null_literal().to_string());
}
DefaultValue::Function(_) => return None,
DefaultValue::String(s) => return Some(self.string_literal(s)),
DefaultValue::Number(n) => return Some(n.clone()),
DefaultValue::Boolean(b) => {
return Some(if *b {
self.true_literal().to_string()
} else {
self.false_literal().to_string()
});
}
DefaultValue::EnumVariant(v) => return Some(self.enum_variant_literal(v)),
}
}
if field.is_array {
Some(self.empty_array_literal().to_string())
} else if !field.is_required {
Some(self.null_literal().to_string())
} else {
None
}
}
}