#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum DeleteSide {
Target,
Source,
}
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum DeleteAction {
Allow,
Restrict,
DeferredRestrict,
DeleteSource,
DeleteTarget,
DeleteTargetIfOrphan,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct OnDeletePolicy {
pub side: DeleteSide,
pub action: DeleteAction,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct RewriteEntry {
pub on: u8,
pub handler: String,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct SignalEntry {
pub on: u8,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct PropertyDescriptor {
pub name: String,
pub pg_type: String,
pub nullable: bool,
pub default_sql: Option<String>,
pub default_pyql: Option<String>,
pub description: Option<String>,
pub check_constraints: Vec<String>,
pub is_exclusive: bool,
pub is_pk: bool,
pub is_readonly: bool,
pub rewrites: Vec<RewriteEntry>,
pub tuple_members: Option<Vec<TupleMemberDescriptor>>,
pub column_type: Option<String>,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct LinkDescriptor {
pub name: String,
pub target: String,
pub nullable: bool,
pub description: Option<String>,
pub default_pyql: Option<String>,
pub is_exclusive: bool,
pub is_readonly: bool,
pub rewrites: Vec<RewriteEntry>,
pub on_delete: Vec<OnDeletePolicy>,
pub through: Option<String>,
}
impl LinkDescriptor {
pub fn is_junction_backed(&self) -> bool {
self.through.is_some()
}
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct MultiLinkDescriptor {
pub name: String,
pub target: String,
pub through: Option<String>,
pub nullable: bool,
pub description: Option<String>,
pub default_pyql: Option<String>,
pub on_delete: Vec<OnDeletePolicy>,
#[serde(default)]
pub is_exclusive: bool,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct ComputedDescriptor {
pub name: String,
pub expression: String,
pub return_type: Option<String>,
#[serde(default)]
pub link_target: Option<String>,
#[serde(default)]
pub link_multi: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum SearchBackend {
Postgres,
OpenSearch,
Meilisearch,
}
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum SearchWeight {
A,
B,
C,
D,
}
impl SearchWeight {
pub fn as_str(&self) -> &'static str {
match self {
SearchWeight::A => "A",
SearchWeight::B => "B",
SearchWeight::C => "C",
SearchWeight::D => "D",
}
}
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct SearchPointerDescriptor {
pub name: String,
pub weight: SearchWeight,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct SearchIndexDescriptor {
pub index_name: Option<String>,
pub backend: SearchBackend,
pub pointers: Vec<SearchPointerDescriptor>,
}
impl SearchIndexDescriptor {
pub fn column_name(&self) -> String {
match &self.index_name {
None => "__search__".to_string(),
Some(name) => format!("__search_{}__", name),
}
}
pub fn deferred_index_name(&self, module: &str, type_name: &str) -> String {
let base = format!("{}__{}", module, type_name).to_lowercase();
match &self.index_name {
None => base,
Some(n) => format!("{}__{}", base, n.to_lowercase()),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum PartitionInterval {
Daily,
Weekly,
Monthly,
Yearly,
}
impl PartitionInterval {
pub fn as_pg_interval(self) -> &'static str {
match self {
PartitionInterval::Daily => "1 day",
PartitionInterval::Weekly => "1 week",
PartitionInterval::Monthly => "1 month",
PartitionInterval::Yearly => "1 year",
}
}
pub fn as_str(self) -> &'static str {
match self {
PartitionInterval::Daily => "daily",
PartitionInterval::Weekly => "weekly",
PartitionInterval::Monthly => "monthly",
PartitionInterval::Yearly => "yearly",
}
}
pub fn parse(s: &str) -> Option<Self> {
match s {
"daily" => Some(PartitionInterval::Daily),
"weekly" => Some(PartitionInterval::Weekly),
"monthly" => Some(PartitionInterval::Monthly),
"yearly" => Some(PartitionInterval::Yearly),
_ => None,
}
}
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct PartitionDescriptor {
pub pointer: String,
pub interval: PartitionInterval,
pub premake: u32,
pub retention: Option<u32>,
}
impl PartitionDescriptor {
pub fn retention_interval(&self) -> Option<String> {
self.retention.map(|n| match self.interval {
PartitionInterval::Daily => format!("{n} days"),
PartitionInterval::Weekly => format!("{n} weeks"),
PartitionInterval::Monthly => format!("{n} months"),
PartitionInterval::Yearly => format!("{n} years"),
})
}
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct VectorIndexDescriptor {
pub index_name: Option<String>,
pub pointers: Vec<String>,
pub model: String,
pub metric: String,
pub dimensions: u32,
}
impl VectorIndexDescriptor {
pub fn column_name(&self) -> String {
match &self.index_name {
None => "__vector__".to_string(),
Some(name) => format!("__vector_{}__", name),
}
}
pub fn ops_class(&self) -> &'static str {
match self.metric.as_str() {
"euclidean" => "vector_l2_ops",
"inner_product" => "vector_ip_ops",
_ => "vector_cosine_ops",
}
}
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct IndexDescriptor {
pub pointers: Vec<String>,
pub expression: Option<String>,
pub unique: bool,
pub unless: Option<String>,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct TriggerDescriptor {
pub on: u8,
pub timing: String,
pub handler: String,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub enum TypeConstraint {
Exclusive {
pointers: Vec<String>,
unless: Option<String>,
},
Expression { expr: String },
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct TypeDescriptor {
pub name: String,
pub module: String,
pub table: String,
pub abstract_: bool,
pub materialized: bool,
pub description: Option<String>,
pub parents: Vec<String>,
pub interfaces: Vec<String>,
#[serde(default)]
pub bases: Vec<String>,
pub properties: Vec<PropertyDescriptor>,
pub links: Vec<LinkDescriptor>,
pub multilinks: Vec<MultiLinkDescriptor>,
pub computed: Vec<ComputedDescriptor>,
pub constraints: Vec<TypeConstraint>,
pub indexes: Vec<IndexDescriptor>,
#[serde(default)]
pub partition: Option<PartitionDescriptor>,
pub vector_indexes: Vec<VectorIndexDescriptor>,
pub search_indexes: Vec<SearchIndexDescriptor>,
pub triggers: Vec<TriggerDescriptor>,
pub junction: bool,
pub signals: Vec<SignalEntry>,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct ScalarDescriptor {
pub name: String,
pub module: String,
pub base: String,
pub pg_type: String,
pub check_constraints: Vec<String>,
pub is_sequence: bool,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct EnumDescriptor {
pub name: String,
pub module: String,
pub members: Vec<String>,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub enum TupleMemberKind {
Scalar {
pg_type: String,
},
Enum {
module: String,
name: String,
},
NamedTuple {
module: String,
name: String,
},
Tuple {
members: Vec<TupleMemberDescriptor>,
},
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct TupleMemberDescriptor {
pub name: Option<String>,
pub kind: TupleMemberKind,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct NamedTupleDescriptor {
pub name: String,
pub module: String,
pub members: Vec<TupleMemberDescriptor>,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct GlobalDescriptor {
pub name: String,
pub module: String,
pub scalar_type: String,
pub required: bool,
pub default_expr: Option<String>,
pub computed_expr: Option<String>,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct FunctionParamDescriptor {
pub name: String,
pub pg_type: String,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct FunctionDescriptor {
pub name: String,
pub module: String,
pub params: Vec<FunctionParamDescriptor>,
pub return_pg_type: String,
pub return_is_object: bool,
pub return_is_set: bool,
pub return_is_polymorphic: bool,
pub volatility: String,
pub body: String,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct AliasDescriptor {
pub name: String,
pub module: String,
pub expr: String,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub enum ChannelPayload {
Type(String),
Scalar(String),
Object(Vec<(String, String)>),
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct ChannelDescriptor {
pub name: String,
pub module: String,
pub wire_name: String,
pub payload: ChannelPayload,
pub description: Option<String>,
}
#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
pub struct SchemaDescriptor {
pub types: Vec<TypeDescriptor>,
pub scalars: Vec<ScalarDescriptor>,
pub enums: Vec<EnumDescriptor>,
pub named_tuples: Vec<NamedTupleDescriptor>,
pub globals: Vec<GlobalDescriptor>,
pub functions: Vec<FunctionDescriptor>,
pub aliases: Vec<AliasDescriptor>,
pub channels: Vec<ChannelDescriptor>,
#[serde(skip)]
pub functions_needing_globals: Derived<std::collections::HashSet<String>>,
}
#[derive(Debug)]
pub struct Derived<T>(std::sync::OnceLock<T>);
impl<T> Derived<T> {
pub fn get_or_init(&self, init: impl FnOnce() -> T) -> &T {
self.0.get_or_init(init)
}
}
impl<T> Default for Derived<T> {
fn default() -> Self {
Derived(std::sync::OnceLock::new())
}
}
impl<T> Clone for Derived<T> {
fn clone(&self) -> Self {
Derived::default()
}
}
impl SchemaDescriptor {
pub fn find_channel(&self, name: &str) -> Option<&ChannelDescriptor> {
self.channels
.iter()
.find(|c| c.name == name || format!("{}::{}", c.module, c.name) == name)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn schema_with_one_channel() -> SchemaDescriptor {
SchemaDescriptor {
channels: vec![ChannelDescriptor {
name: "Pings".into(),
module: "shop".into(),
wire_name: "shop__pings".into(),
payload: ChannelPayload::Scalar("text".into()),
description: None,
}],
..Default::default()
}
}
#[test]
fn find_channel_matches_bare_name() {
let schema = schema_with_one_channel();
assert_eq!(
schema.find_channel("Pings").map(|c| c.wire_name.as_str()),
Some("shop__pings")
);
}
#[test]
fn find_channel_matches_qualified_name() {
let schema = schema_with_one_channel();
assert_eq!(
schema.find_channel("shop::Pings").map(|c| c.wire_name.as_str()),
Some("shop__pings")
);
}
#[test]
fn find_channel_returns_none_for_unknown_name() {
let schema = schema_with_one_channel();
assert!(schema.find_channel("NoSuchChannel").is_none());
}
}