pharos_runtime_database/
seed_from.rs1use serde_json::{Map, Value};
4
5use crate::operations::{
6 AppSchema, DatabaseError, Query, SchemaChange, entity, validate_field_value,
7 validate_predicate, validate_query,
8};
9
10pub fn queries(schema: &AppSchema, change: &SchemaChange) -> Result<(Query, Query), DatabaseError> {
12 let SchemaChange::SeedFrom {
13 entity: name,
14 absent_filter,
15 source_entity,
16 source_filter,
17 fields,
18 values,
19 } = change
20 else {
21 return Err(DatabaseError::InvalidContract);
22 };
23 let target = entity(schema, name)?;
24 let source = entity(schema, source_entity)?;
25 validate_predicate(target, absent_filter)?;
26 validate_predicate(source, source_filter)?;
27 let mut selection = std::collections::BTreeMap::new();
28 selection_values(absent_filter, &mut selection)?;
29 if selection
30 .iter()
31 .any(|(name, value)| values.get(*name) != Some(*value))
32 || !(target
33 .unique
34 .iter()
35 .any(|key| key.iter().all(|name| selection.contains_key(name.as_str())))
36 || target
37 .identity
38 .fields()
39 .iter()
40 .all(|name| selection.contains_key(*name)))
41 {
42 return Err(DatabaseError::InvalidContract);
43 }
44 if fields.is_empty() || fields.len() + values.len() > 64 {
45 return Err(DatabaseError::InvalidContract);
46 }
47 for (target_name, source_name) in fields {
48 let target_field = target
49 .fields
50 .iter()
51 .find(|field| field.name == *target_name)
52 .ok_or(DatabaseError::InvalidContract)?;
53 let source_field = source
54 .fields
55 .iter()
56 .find(|field| field.name == *source_name)
57 .ok_or(DatabaseError::InvalidContract)?;
58 if values.contains_key(target_name)
59 || target_field.kind != source_field.kind
60 || (!target_field.nullable && source_field.nullable)
61 {
62 return Err(DatabaseError::InvalidContract);
63 }
64 }
65 for (name, value) in values {
66 let field = target
67 .fields
68 .iter()
69 .find(|field| field.name == *name)
70 .ok_or(DatabaseError::InvalidContract)?;
71 validate_field_value(field, value)?;
72 }
73 for field in &target.fields {
74 if !field.nullable
75 && field.default.is_none()
76 && target.identity.generated_field() != Some(field.name.as_str())
77 && !fields.contains_key(&field.name)
78 && !values.contains_key(&field.name)
79 {
80 return Err(DatabaseError::InvalidContract);
81 }
82 }
83 let target_query = Query {
84 entity: name.clone(),
85 fields: target
86 .identity
87 .fields()
88 .into_iter()
89 .map(str::to_owned)
90 .collect(),
91 filter: Some(absent_filter.clone()),
92 order: vec![],
93 limit: 1,
94 offset: 0,
95 };
96 let source_query = Query {
97 entity: source_entity.clone(),
98 fields: fields
99 .values()
100 .cloned()
101 .collect::<std::collections::BTreeSet<_>>()
102 .into_iter()
103 .collect(),
104 filter: Some(source_filter.clone()),
105 order: vec![],
106 limit: 2,
107 offset: 0,
108 };
109 validate_query(schema, &target_query)?;
110 validate_query(schema, &source_query)?;
111 Ok((target_query, source_query))
112}
113
114fn selection_values<'a>(
116 predicate: &'a crate::operations::Predicate,
117 values: &mut std::collections::BTreeMap<&'a str, &'a Value>,
118) -> Result<(), DatabaseError> {
119 match predicate {
120 crate::operations::Predicate::Equal(name, value) => {
121 if value.is_null() || values.insert(name, value).is_some() {
122 return Err(DatabaseError::InvalidContract);
123 }
124 }
125 crate::operations::Predicate::All(items) => {
126 for item in items {
127 selection_values(item, values)?;
128 }
129 }
130 _ => return Err(DatabaseError::InvalidContract),
131 }
132 Ok(())
133}
134
135pub fn seed(
137 schema: &AppSchema,
138 change: &SchemaChange,
139 rows: &[Map<String, Value>],
140) -> Result<SchemaChange, DatabaseError> {
141 queries(schema, change)?;
142 let SchemaChange::SeedFrom {
143 entity: name,
144 fields,
145 values,
146 ..
147 } = change
148 else {
149 return Err(DatabaseError::InvalidContract);
150 };
151 let [row] = rows else {
152 return Err(DatabaseError::Conflict);
153 };
154 let target = entity(schema, name)?;
155 let mut values = values.clone();
156 for (target_name, source_name) in fields {
157 let value = row.get(source_name).ok_or(DatabaseError::Conflict)?;
158 let field = target
159 .fields
160 .iter()
161 .find(|field| field.name == *target_name)
162 .ok_or(DatabaseError::InvalidContract)?;
163 validate_field_value(field, value)?;
164 values.insert(target_name.clone(), value.clone());
165 }
166 Ok(SchemaChange::Seed {
167 entity: name.clone(),
168 values,
169 })
170}