1use std::collections::{BTreeMap, BTreeSet};
6
7use crate::operations::{
8 Aggregate, AggregateQuery, AppSchema, Claim, DatabaseError, Entity, Field, FieldConstraint,
9 FieldDefault, FieldType, Identity, Index, IndexKey, MAX_TEXT_LENGTH, Mutation, OnDelete,
10 Predicate, Query, SchemaChange, WriteValue, decimal_cents, decimal_from_cents, entity,
11 materialize_generated_predicate, valid_identifier, validate_claim, validate_copy_field,
12 validate_predicate, validate_query,
13};
14use serde_json::Value;
15
16#[derive(Debug, Clone, Copy, Eq, PartialEq)]
18pub enum Dialect {
19 Sqlite,
20 Postgresql,
21 Mysql,
22}
23
24#[cfg(test)]
25mod schema_literal_tests {
26 use super::{Dialect, ddl_literal};
27 use serde_json::json;
28
29 #[test]
31 fn mysql_schema_literals_encode_utf8_bytes() {
32 assert_eq!(
33 ddl_literal(Dialect::Mysql, &json!("\\'")),
34 Ok("_utf8mb4 X'5c27'".to_owned())
35 );
36 assert_eq!(
37 ddl_literal(Dialect::Mysql, &json!("é")),
38 Ok("_utf8mb4 X'c3a9'".to_owned())
39 );
40 assert_eq!(
41 ddl_literal(Dialect::Mysql, &json!("")),
42 Ok("_utf8mb4 X''".to_owned())
43 );
44 }
45
46 #[test]
48 fn standard_schema_literals_preserve_values() {
49 for dialect in [Dialect::Sqlite, Dialect::Postgresql] {
50 assert_eq!(ddl_literal(dialect, &json!("\\'")), Ok("'\\'''".to_owned()));
51 }
52 }
53}
54
55#[derive(Debug, Clone, PartialEq)]
57pub struct Statement {
58 pub sql: String,
59 pub parameters: Vec<Value>,
60 pub binary_parameters: BTreeSet<usize>,
62}
63
64struct Builder {
65 dialect: Dialect,
66 parameters: Vec<Value>,
67 binary_parameters: BTreeSet<usize>,
68}
69
70impl Builder {
71 fn new(dialect: Dialect) -> Self {
73 Self {
74 dialect,
75 parameters: Vec::new(),
76 binary_parameters: BTreeSet::new(),
77 }
78 }
79
80 fn name(&self, value: &str) -> Result<String, DatabaseError> {
82 if !valid_identifier(value) {
83 return Err(DatabaseError::InvalidContract);
84 }
85 Ok(match self.dialect {
86 Dialect::Mysql => format!("`{value}`"),
87 Dialect::Sqlite | Dialect::Postgresql => format!("\"{value}\""),
88 })
89 }
90
91 fn bind(&mut self, value: Value) -> String {
93 self.parameters.push(value);
94 match self.dialect {
95 Dialect::Postgresql => format!("${}", self.parameters.len()),
96 Dialect::Sqlite | Dialect::Mysql => "?".to_owned(),
97 }
98 }
99
100 fn bind_field(&mut self, field: &Field, value: Value) -> Result<String, DatabaseError> {
102 let value = if field.kind == FieldType::Json && !value.is_null() {
103 crate::operations::canonical_json_value(&value)?
104 } else {
105 value
106 };
107 if field.kind == FieldType::Binary && !value.is_null() {
108 self.binary_parameters.insert(self.parameters.len());
109 return Ok(self.bind(value));
110 }
111 if self.dialect == Dialect::Mysql && field.kind == FieldType::Json && !value.is_null() {
112 return Ok(format!("CAST({} AS JSON)", self.bind(value)));
113 }
114 if field.kind == FieldType::Decimal {
115 if value.is_null() {
116 return Ok(match self.dialect {
117 Dialect::Postgresql => {
118 format!("CAST({}::TEXT AS NUMERIC(10,2))", self.bind(Value::Null))
119 }
120 _ => self.bind(Value::Null),
121 });
122 }
123 let cents = decimal_cents(&value)?;
124 return match self.dialect {
125 Dialect::Sqlite => Ok(self.bind(Value::from(cents as f64 / 100.0))),
126 Dialect::Postgresql => Ok(format!(
127 "CAST({}::TEXT AS NUMERIC(10,2))",
128 self.bind(Value::String(decimal_from_cents(cents)))
129 )),
130 Dialect::Mysql => Ok(self.bind(Value::String(decimal_from_cents(cents)))),
131 };
132 }
133 if field.kind == FieldType::Timestamp && !value.is_null() {
134 let value = crate::operations::canonical_stored_timestamp(
135 value.as_str().ok_or(DatabaseError::InvalidInput)?,
136 )?;
137 return Ok(self.bind(Value::String(value)));
138 }
139 Ok(self.bind(value))
140 }
141
142 fn bind_named_field(
144 &mut self,
145 declaration: &Entity,
146 field: &str,
147 value: Value,
148 ) -> Result<String, DatabaseError> {
149 let field = declaration
150 .fields
151 .iter()
152 .find(|item| item.name == field)
153 .ok_or(DatabaseError::InvalidContract)?;
154 if field.kind == FieldType::Json {
155 return Ok(self.bind(crate::index_values::json_comparison_value(&value)?));
156 }
157 self.bind_field(field, value)
158 }
159
160 fn comparison_name(&self, declaration: &Entity, name: &str) -> Result<String, DatabaseError> {
162 let field = declaration
163 .fields
164 .iter()
165 .find(|field| field.name == name)
166 .ok_or(DatabaseError::InvalidContract)?;
167 self.name(&if field.kind == FieldType::Json {
168 crate::index_values::json_comparison_key(name)
169 } else {
170 name.to_owned()
171 })
172 }
173
174 fn filter(
176 &mut self,
177 declaration: &Entity,
178 predicate: &Predicate,
179 ) -> Result<String, DatabaseError> {
180 match predicate {
181 Predicate::Equal(field, value) => self.compare(declaration, field, "=", value),
182 Predicate::GeneratedEqual(_, _) => Err(DatabaseError::InvalidInput),
183 Predicate::NotEqual(field, value) => self.compare(declaration, field, "<>", value),
184 Predicate::LessThan(field, value) => self.compare(declaration, field, "<", value),
185 Predicate::AtMost(field, value) => self.compare(declaration, field, "<=", value),
186 Predicate::GreaterThan(field, value) => self.compare(declaration, field, ">", value),
187 Predicate::AtLeast(field, value) => self.compare(declaration, field, ">=", value),
188 Predicate::Contains(field, needle) => {
189 let name = self.name(field)?;
190 let value = self.bind(Value::String(needle.clone()));
191 Ok(match self.dialect {
192 Dialect::Mysql => format!("LOCATE(BINARY {value}, BINARY {name}) > 0"),
193 Dialect::Sqlite => format!("INSTR({name}, {value}) > 0"),
194 Dialect::Postgresql => format!("STRPOS({name}, {value}) > 0"),
195 })
196 }
197 Predicate::IsNull(field) => Ok(format!(
198 "{} IS NULL",
199 self.comparison_name(declaration, field)?
200 )),
201 Predicate::In(field, values) => {
202 if values.len() > 64 {
203 return Err(DatabaseError::InvalidInput);
204 }
205 if values.is_empty() {
206 return Ok("1 = 0".to_owned());
207 }
208 let name = self.comparison_name(declaration, field)?;
209 let placeholders = values
210 .iter()
211 .map(|value| self.bind_named_field(declaration, field, value.clone()))
212 .collect::<Result<Vec<_>, _>>()?
213 .join(", ");
214 Ok(format!("{name} IN ({placeholders})"))
215 }
216 Predicate::All(parts) => self.combine(declaration, parts, " AND "),
217 Predicate::Any(parts) => self.combine(declaration, parts, " OR "),
218 }
219 }
220
221 fn compare(
223 &mut self,
224 declaration: &Entity,
225 field: &str,
226 operator: &str,
227 value: &Value,
228 ) -> Result<String, DatabaseError> {
229 let name = self.comparison_name(declaration, field)?;
230 if value.is_null() {
231 return match operator {
232 "=" => Ok(format!("{name} IS NULL")),
233 "<>" => Ok(format!("{name} IS NOT NULL")),
234 _ => Err(DatabaseError::InvalidInput),
235 };
236 }
237 let placeholder = self.bind_named_field(declaration, field, value.clone())?;
238 if self.dialect == Dialect::Mysql
239 && declaration
240 .fields
241 .iter()
242 .any(|entry| entry.name == field && entry.kind == FieldType::Text)
243 {
244 return Ok(format!("BINARY {name} {operator} BINARY {placeholder}"));
245 }
246 Ok(format!("{name} {operator} {placeholder}"))
247 }
248
249 fn combine(
251 &mut self,
252 declaration: &Entity,
253 parts: &[Predicate],
254 separator: &str,
255 ) -> Result<String, DatabaseError> {
256 if parts.is_empty() || parts.len() > 32 {
257 return Err(DatabaseError::InvalidInput);
258 }
259 let fragments = parts
260 .iter()
261 .map(|part| self.filter(declaration, part))
262 .collect::<Result<Vec<_>, _>>()?;
263 Ok(format!("({})", fragments.join(separator)))
264 }
265
266 fn finish(self, sql: String) -> Statement {
268 Statement {
269 sql,
270 parameters: self.parameters,
271 binary_parameters: self.binary_parameters,
272 }
273 }
274}
275
276pub fn compile_query(
278 dialect: Dialect,
279 schema: &AppSchema,
280 query: &Query,
281) -> Result<Statement, DatabaseError> {
282 validate_query(schema, query)?;
283 let mut builder = Builder::new(dialect);
284 let declaration = entity(schema, &query.entity)?;
285 let columns = query
286 .fields
287 .iter()
288 .map(|field| {
289 let name = builder.name(field)?;
290 if dialect == Dialect::Postgresql
291 && declaration
292 .fields
293 .iter()
294 .any(|item| item.name == *field && item.kind == FieldType::Decimal)
295 {
296 Ok(format!("CAST({name} AS TEXT) AS {name}"))
297 } else {
298 Ok(name)
299 }
300 })
301 .collect::<Result<Vec<_>, _>>()?
302 .join(", ");
303 let mut sql = format!("SELECT {columns} FROM {}", builder.name(&query.entity)?);
304 if let Some(filter) = &query.filter {
305 sql.push_str(" WHERE ");
306 sql.push_str(&builder.filter(declaration, filter)?);
307 }
308 if !query.order.is_empty() {
309 sql.push_str(" ORDER BY ");
310 let order = query
311 .order
312 .iter()
313 .map(|key| {
314 let name = builder.comparison_name(declaration, &key.field)?;
315 let direction = if key.descending { "DESC" } else { "ASC" };
316 let nullable = declaration
317 .fields
318 .iter()
319 .any(|field| field.name == key.field && field.nullable);
320 if nullable && dialect == Dialect::Postgresql {
321 let null_order = if key.descending { "LAST" } else { "FIRST" };
324 return Ok(format!("{name} {direction} NULLS {null_order}"));
325 }
326 Ok(format!("{name} {direction}"))
327 })
328 .collect::<Result<Vec<_>, DatabaseError>>()?;
329 sql.push_str(&order.join(", "));
330 }
331 sql.push_str(&format!(" LIMIT {} OFFSET {}", query.limit, query.offset));
332 Ok(builder.finish(sql))
333}
334
335pub fn compile_claim_select(
337 dialect: Dialect,
338 schema: &AppSchema,
339 claim: &Claim,
340) -> Result<Statement, DatabaseError> {
341 validate_claim(schema, claim)?;
342 let identity = entity(schema, &claim.entity)?
343 .identity
344 .generated_field()
345 .ok_or(DatabaseError::InvalidInput)?
346 .to_owned();
347 let mut statement = compile_query(
348 dialect,
349 schema,
350 &Query {
351 entity: claim.entity.clone(),
352 fields: vec![identity],
353 filter: Some(claim.filter.clone()),
354 order: claim.order.clone(),
355 limit: 1,
356 offset: 0,
357 },
358 )?;
359 if dialect != Dialect::Sqlite {
360 statement.sql.push_str(" FOR UPDATE SKIP LOCKED");
361 }
362 Ok(statement)
363}
364
365pub fn compile_aggregate(
367 dialect: Dialect,
368 schema: &AppSchema,
369 query: &AggregateQuery,
370) -> Result<Statement, DatabaseError> {
371 let declaration = entity(schema, &query.entity)?;
372 let result_kind = crate::operations::aggregate_result_kind(schema, query)?;
373 let mut builder = Builder::new(dialect);
374 let expression = match &query.aggregate {
375 Aggregate::Count => "COUNT(*)".to_owned(),
376 Aggregate::Sum(field) | Aggregate::Minimum(field) | Aggregate::Maximum(field) => {
377 if !declaration.fields.iter().any(|item| item.name == *field) {
378 return Err(DatabaseError::InvalidInput);
379 }
380 let function = match &query.aggregate {
381 Aggregate::Sum(_) => "SUM",
382 Aggregate::Minimum(_) => "MIN",
383 Aggregate::Maximum(_) => "MAX",
384 Aggregate::Count => unreachable!(),
385 };
386 let name = builder.comparison_name(declaration, field)?;
387 let name = match (dialect, result_kind) {
388 (Dialect::Postgresql, FieldType::Boolean) => format!("CAST({name} AS INTEGER)"),
389 (Dialect::Postgresql, FieldType::Binary) => format!("encode({name}, 'hex')"),
390 (Dialect::Mysql, FieldType::Binary) => format!("HEX({name})"),
391 _ => name,
392 };
393 format!("{function}({name})")
394 }
395 };
396 let expression = match dialect {
397 Dialect::Postgresql => format!("CAST({expression} AS TEXT)"),
398 Dialect::Mysql => format!("CAST({expression} AS CHAR)"),
399 Dialect::Sqlite => expression,
400 };
401 let mut sql = format!("SELECT {expression} FROM {}", builder.name(&query.entity)?);
402 if let Some(filter) = &query.filter {
403 validate_predicate(declaration, filter)?;
404 sql.push_str(" WHERE ");
405 sql.push_str(&builder.filter(declaration, filter)?);
406 }
407 Ok(builder.finish(sql))
408}
409
410fn resolve_value(
412 value: &WriteValue,
413 generated_ids: &[Option<i64>],
414) -> Result<Value, DatabaseError> {
415 crate::operations::resolve_write_value(value, generated_ids)
416}
417
418pub fn compile_ensured_identity_query(
421 dialect: Dialect,
422 schema: &AppSchema,
423 name: &str,
424 unique_key: &[String],
425 values: &BTreeMap<String, WriteValue>,
426 generated_ids: &[Option<i64>],
427) -> Result<Statement, DatabaseError> {
428 let declaration = entity(schema, name)?;
429 let identity = declaration
430 .identity
431 .generated_field()
432 .ok_or(DatabaseError::InvalidContract)?;
433 if !declaration.accepts_upsert_key(unique_key) {
434 return Err(DatabaseError::InvalidContract);
435 }
436 let filters = unique_key
437 .iter()
438 .map(|field| {
439 let value = values.get(field).ok_or(DatabaseError::InvalidInput)?;
440 Ok(Predicate::Equal(
441 field.clone(),
442 resolve_value(value, generated_ids)?,
443 ))
444 })
445 .collect::<Result<Vec<_>, DatabaseError>>()?;
446 compile_query(
447 dialect,
448 schema,
449 &Query {
450 entity: name.to_owned(),
451 fields: vec![identity.to_owned()],
452 filter: Some(Predicate::All(filters)),
453 order: Vec::new(),
454 limit: 1,
455 offset: 0,
456 },
457 )
458}
459
460pub struct ReservationStatements {
462 pub expire: Option<Statement>,
463 pub owner: Statement,
464 pub occupied: Statement,
465 pub insert: Statement,
466}
467
468pub fn compile_reservation(
469 dialect: Dialect,
470 schema: &AppSchema,
471 mutation: &Mutation,
472 generated_ids: &[Option<i64>],
473) -> Result<ReservationStatements, DatabaseError> {
474 let Mutation::Reserve {
475 entity: name,
476 status_field,
477 active_statuses,
478 owner_filter,
479 scope_filter,
480 expiry,
481 values,
482 ..
483 } = mutation
484 else {
485 return Err(DatabaseError::InvalidContract);
486 };
487 let identity = entity(schema, name)?
488 .identity
489 .generated_field()
490 .ok_or(DatabaseError::InvalidContract)?
491 .to_owned();
492 let owner_filter = materialize_generated_predicate(owner_filter, generated_ids)?;
493 let scope_filter = materialize_generated_predicate(scope_filter, generated_ids)?;
494 let active = Predicate::In(status_field.clone(), active_statuses.clone());
495 let owner = compile_query(
496 dialect,
497 schema,
498 &Query {
499 entity: name.clone(),
500 fields: vec![identity],
501 filter: Some(Predicate::All(vec![owner_filter.clone(), active.clone()])),
502 order: Vec::new(),
503 limit: 1,
504 offset: 0,
505 },
506 )?;
507 let occupied = compile_aggregate(
508 dialect,
509 schema,
510 &AggregateQuery {
511 entity: name.clone(),
512 filter: Some(Predicate::All(vec![scope_filter.clone(), active])),
513 aggregate: Aggregate::Count,
514 },
515 )?;
516 let expire = expiry
517 .as_ref()
518 .map(|rule| {
519 let mut values = BTreeMap::from([(
520 status_field.clone(),
521 WriteValue::Literal(rule.to_status.clone()),
522 )]);
523 if let Some((name, value)) = &rule.released_at {
524 values.insert(name.clone(), WriteValue::Literal(value.clone()));
525 }
526 if let Some((name, value)) = &rule.reason {
527 values.insert(name.clone(), WriteValue::Literal(value.clone()));
528 }
529 compile_mutation(
530 dialect,
531 schema,
532 &Mutation::Update {
533 entity: name.clone(),
534 filter: crate::operations::reservation_expiry_filter(
535 scope_filter.clone(),
536 status_field,
537 rule,
538 generated_ids,
539 )?,
540 values,
541 expected_rows: None,
542 },
543 generated_ids,
544 )
545 })
546 .transpose()?;
547 let insert = compile_mutation(
548 dialect,
549 schema,
550 &Mutation::Insert {
551 entity: name.clone(),
552 values: values.clone(),
553 },
554 generated_ids,
555 )?;
556 Ok(ReservationStatements {
557 expire,
558 owner,
559 occupied,
560 insert,
561 })
562}
563
564pub fn compile_mutation(
566 dialect: Dialect,
567 schema: &AppSchema,
568 mutation: &Mutation,
569 generated_ids: &[Option<i64>],
570) -> Result<Statement, DatabaseError> {
571 let name = match mutation {
572 Mutation::Insert { entity, .. }
573 | Mutation::Upsert { entity, .. }
574 | Mutation::InsertIfAbsent { entity, .. }
575 | Mutation::Update { entity, .. }
576 | Mutation::Delete { entity, .. }
577 | Mutation::RequireCount { entity, .. }
578 | Mutation::Reserve { entity, .. } => entity,
579 };
580 let declaration = entity(schema, name)?;
581 let mut builder = Builder::new(dialect);
582 let table = builder.name(name)?;
583 let sql = match mutation {
584 Mutation::Insert { values, .. }
585 | Mutation::Upsert { values, .. }
586 | Mutation::InsertIfAbsent { values, .. } => {
587 let mut columns = Vec::with_capacity(declaration.fields.len());
588 let mut placeholders = Vec::with_capacity(declaration.fields.len());
589 for field in &declaration.fields {
590 let value = if let Some(value) = values.get(&field.name) {
591 Some(builder.bind_field(
592 field,
593 crate::operations::resolve_field_write(field, value, generated_ids)?,
594 )?)
595 } else {
596 match &field.default {
597 Some(FieldDefault::Literal(value)) => {
598 Some(builder.bind_field(field, value.clone())?)
599 }
600 Some(FieldDefault::CurrentTimestamp) => {
601 Some(current_timestamp_sql(dialect).to_owned())
602 }
603 None => None,
604 }
605 };
606 if let Some(value) = value {
607 columns.push(builder.name(&field.name)?);
608 placeholders.push(value);
609 }
610 }
611 for name in crate::index_values::derived_fields(declaration) {
612 let field = declaration
613 .fields
614 .iter()
615 .find(|field| field.name == name)
616 .ok_or(DatabaseError::InvalidContract)?;
617 let original = if let Some(value) = values.get(name) {
618 crate::operations::resolve_field_write(field, value, generated_ids)?
619 } else if let Some(FieldDefault::Literal(value)) = &field.default {
620 value.clone()
621 } else {
622 continue;
623 };
624 columns.push(builder.name(&crate::index_values::derived_key(declaration, name)?)?);
625 placeholders.push(builder.bind(crate::index_values::derived_value(
626 declaration,
627 name,
628 &original,
629 )?));
630 }
631 let columns = columns.join(", ");
632 let placeholders = placeholders.join(", ");
633 let mut sql = format!("INSERT INTO {table} ({columns}) VALUES ({placeholders})");
634 if let Mutation::Upsert { unique_key, .. } = mutation {
635 if !declaration.accepts_upsert_key(unique_key) {
636 return Err(DatabaseError::InvalidContract);
637 }
638 let mut assignments = values
639 .keys()
640 .filter(|field| !unique_key.contains(field))
641 .map(|field| {
642 let column = builder.name(field)?;
643 Ok(match dialect {
644 Dialect::Mysql => format!("{column} = VALUES({column})"),
645 Dialect::Sqlite | Dialect::Postgresql => {
646 format!("{column} = EXCLUDED.{column}")
647 }
648 })
649 })
650 .collect::<Result<Vec<_>, DatabaseError>>()?;
651 for name in crate::index_values::derived_fields(declaration) {
652 if values.contains_key(name) && !unique_key.iter().any(|key| key == name) {
653 let column =
654 builder.name(&crate::index_values::derived_key(declaration, name)?)?;
655 assignments.push(match dialect {
656 Dialect::Mysql => format!("{column} = VALUES({column})"),
657 _ => format!("{column} = EXCLUDED.{column}"),
658 });
659 }
660 }
661 if assignments.is_empty() {
662 match dialect {
664 Dialect::Mysql => {
665 let key = builder.name(&unique_key[0])?;
666 sql.push_str(&format!(" ON DUPLICATE KEY UPDATE {key} = {key}"));
667 }
668 Dialect::Sqlite | Dialect::Postgresql => {
669 let key = unique_key
670 .iter()
671 .map(|field| builder.name(field))
672 .collect::<Result<Vec<_>, _>>()?
673 .join(", ");
674 sql.push_str(&format!(" ON CONFLICT ({key}) DO NOTHING"));
675 }
676 }
677 return Ok(builder.finish(sql));
678 }
679 match dialect {
680 Dialect::Mysql => sql.push_str(&format!(
681 " ON DUPLICATE KEY UPDATE {}",
682 assignments.join(", ")
683 )),
684 Dialect::Sqlite | Dialect::Postgresql => {
685 let key = unique_key
686 .iter()
687 .map(|field| builder.name(field))
688 .collect::<Result<Vec<_>, _>>()?
689 .join(", ");
690 sql.push_str(&format!(
691 " ON CONFLICT ({key}) DO UPDATE SET {}",
692 assignments.join(", ")
693 ));
694 }
695 }
696 }
697 if let Mutation::InsertIfAbsent { unique_key, .. } = mutation {
698 if !declaration.accepts_upsert_key(unique_key) {
699 return Err(DatabaseError::InvalidContract);
700 }
701 if dialect != Dialect::Mysql {
702 let key = unique_key
703 .iter()
704 .map(|field| builder.name(field))
705 .collect::<Result<Vec<_>, _>>()?
706 .join(", ");
707 sql.push_str(&format!(" ON CONFLICT ({key}) DO NOTHING"));
708 }
709 }
710 sql
711 }
712 Mutation::Update { values, filter, .. } => {
713 validate_predicate(declaration, filter)?;
714 let filter = materialize_generated_predicate(filter, generated_ids)?;
715 let mut assignments = values
716 .iter()
717 .map(|(field, value)| {
718 let column = builder.name(field)?;
719 let declared_field = declaration
720 .fields
721 .iter()
722 .find(|item| item.name == *field)
723 .ok_or(DatabaseError::InvalidContract)?;
724 let placeholder = builder.bind_field(
725 declared_field,
726 crate::operations::resolve_field_write(
727 declared_field,
728 value,
729 generated_ids,
730 )?,
731 )?;
732 Ok(format!("{column} = {placeholder}"))
733 })
734 .collect::<Result<Vec<_>, DatabaseError>>()?;
735 for name in crate::index_values::derived_fields(declaration) {
736 if let Some(value) = values.get(name) {
737 let field = declaration
738 .fields
739 .iter()
740 .find(|field| field.name == name)
741 .ok_or(DatabaseError::InvalidContract)?;
742 let value =
743 crate::operations::resolve_field_write(field, value, generated_ids)?;
744 let key =
745 builder.name(&crate::index_values::derived_key(declaration, name)?)?;
746 let binding = builder.bind(crate::index_values::derived_value(
747 declaration,
748 name,
749 &value,
750 )?);
751 assignments.push(format!("{key} = {binding}"));
752 }
753 }
754 let assignments = assignments.join(", ");
755 let condition = builder.filter(declaration, &filter)?;
756 format!("UPDATE {table} SET {assignments} WHERE {condition}")
757 }
758 Mutation::Delete { filter, .. } | Mutation::RequireCount { filter, .. } => {
759 validate_predicate(declaration, filter)?;
760 let filter = materialize_generated_predicate(filter, generated_ids)?;
761 let condition = builder.filter(declaration, &filter)?;
762 match mutation {
763 Mutation::Delete { .. } => format!("DELETE FROM {table} WHERE {condition}"),
764 Mutation::RequireCount { .. } => {
765 format!("SELECT COUNT(*) FROM {table} WHERE {condition}")
766 }
767 _ => unreachable!(),
768 }
769 }
770 Mutation::Reserve { .. } => return Err(DatabaseError::InvalidContract),
771 };
772 Ok(builder.finish(sql))
773}
774
775pub fn compile_delete_effects(
778 dialect: Dialect,
779 schema: &AppSchema,
780 mutation: &Mutation,
781 generated_ids: &[Option<i64>],
782) -> Result<Vec<Statement>, DatabaseError> {
783 let Mutation::Delete {
784 entity: name,
785 filter,
786 ..
787 } = mutation
788 else {
789 return Ok(Vec::new());
790 };
791 let declaration = entity(schema, name)?;
792 validate_predicate(declaration, filter)?;
793 let mut builder = Builder::new(dialect);
794 let condition = builder.filter(
795 declaration,
796 &materialize_generated_predicate(filter, generated_ids)?,
797 )?;
798 let mut pending = vec![(name.clone(), condition, BTreeSet::new())];
799 let mut statements = Vec::new();
800 let mut traversed = 0usize;
801 while let Some((parent_name, condition, mut path)) = pending.pop() {
802 if !path.insert(parent_name.clone()) {
803 continue;
804 }
805 traversed += 1;
806 if traversed > 256 {
807 return Err(DatabaseError::CapacityExceeded);
808 }
809 let parent = entity(schema, &parent_name)?;
810 for child in &schema.entities {
811 for field in &child.fields {
812 let Some((target, key)) = &field.references else {
813 continue;
814 };
815 if target != &parent_name {
816 continue;
817 }
818 let source = builder.name(&crate::index_values::reference_keys(field)?.0)?;
819 let selected = format!(
820 "SELECT {} FROM {} WHERE {condition}",
821 builder.comparison_name(parent, key)?,
822 builder.name(&parent_name)?
823 );
824 let selected = if dialect == Dialect::Mysql {
825 format!("SELECT * FROM ({selected}) AS _pharos_delete_source")
826 } else {
827 selected
828 };
829 let child_condition = format!("{source} IN ({selected})");
830 match field.on_delete.unwrap_or(OnDelete::Restrict) {
831 OnDelete::Cascade => {
832 pending.push((child.name.clone(), child_condition, path.clone()))
833 }
834 OnDelete::SetNull if field.kind == FieldType::Json => {
835 let sql = format!(
836 "UPDATE {} SET {} = NULL, {source} = NULL WHERE {child_condition}",
837 builder.name(&child.name)?,
838 builder.name(&field.name)?
839 );
840 statements.push(Statement {
841 sql,
842 parameters: builder.parameters.clone(),
843 binary_parameters: builder.binary_parameters.clone(),
844 });
845 }
846 _ => {}
847 }
848 }
849 }
850 }
851 Ok(statements)
852}
853
854pub fn compile_detached_delete_effects(
858 dialect: Dialect,
859 schema: &AppSchema,
860 name: &str,
861 filter: &Predicate,
862) -> Result<Vec<Statement>, DatabaseError> {
863 let declaration = entity(schema, name)?;
864 validate_predicate(declaration, filter)?;
865 let mut builder = Builder::new(dialect);
866 let condition = builder.filter(declaration, filter)?;
867 let mut pending = vec![(name.to_owned(), condition, BTreeSet::new(), false)];
868 let mut statements = Vec::new();
869 let mut traversed = 0usize;
870 while let Some((parent_name, condition, mut path, delete)) = pending.pop() {
871 if delete {
872 statements.push(Statement {
873 sql: format!(
874 "DELETE FROM {} WHERE {condition}",
875 builder.name(&parent_name)?
876 ),
877 parameters: builder.parameters.clone(),
878 binary_parameters: builder.binary_parameters.clone(),
879 });
880 continue;
881 }
882 if !path.insert(parent_name.clone()) {
883 return Err(DatabaseError::Conflict);
884 }
885 traversed += 1;
886 if traversed > 256 {
887 return Err(DatabaseError::CapacityExceeded);
888 }
889 let parent = entity(schema, &parent_name)?;
890 if parent_name != name {
891 pending.push((parent_name.clone(), condition.clone(), path.clone(), true));
892 }
893 for child in &schema.entities {
894 for field in &child.fields {
895 let Some((target, key)) = &field.references else {
896 continue;
897 };
898 if target != &parent_name {
899 continue;
900 }
901 let source = builder.name(&crate::index_values::reference_keys(field)?.0)?;
902 let selected = format!(
903 "SELECT {} FROM {} WHERE {condition}",
904 builder.comparison_name(parent, key)?,
905 builder.name(&parent_name)?
906 );
907 let child_condition = format!("{source} IN ({selected})");
908 match field.on_delete.unwrap_or(OnDelete::Restrict) {
909 OnDelete::Cascade => {
910 pending.push((child.name.clone(), child_condition, path.clone(), false))
911 }
912 OnDelete::SetNull => {
913 let column = builder.name(&field.name)?;
914 let assignments = if column == source {
915 format!("{column} = NULL")
916 } else {
917 format!("{column} = NULL, {source} = NULL")
918 };
919 statements.push(Statement {
920 sql: format!(
921 "UPDATE {} SET {assignments} WHERE {child_condition}",
922 builder.name(&child.name)?
923 ),
924 parameters: builder.parameters.clone(),
925 binary_parameters: builder.binary_parameters.clone(),
926 });
927 }
928 OnDelete::Restrict => {}
929 }
930 }
931 }
932 }
933 Ok(statements)
934}
935
936pub fn record_values(fields: &serde_json::Map<String, Value>) -> BTreeMap<String, WriteValue> {
938 fields
939 .iter()
940 .map(|(name, value)| (name.clone(), WriteValue::Literal(value.clone())))
941 .collect()
942}
943
944pub fn derived_column(
946 dialect: Dialect,
947 declaration: &Entity,
948 name: &str,
949) -> Result<String, DatabaseError> {
950 column(dialect, declaration, &derived_field(declaration, name)?)
951}
952
953pub fn derived_field(declaration: &Entity, name: &str) -> Result<Field, DatabaseError> {
955 let source = declaration
956 .fields
957 .iter()
958 .find(|field| field.name == name)
959 .ok_or(DatabaseError::InvalidContract)?;
960 let mut key = source.clone();
961 key.name = crate::index_values::derived_key(declaration, name)?;
962 key.kind = FieldType::Text;
963 key.max_length = if source.kind == FieldType::Json {
964 source.max_length
965 } else {
966 source
967 .max_length
968 .and_then(|length| length.checked_mul(crate::index_values::LOWERCASE_EXPANSION))
969 };
970 key.default = match &source.default {
971 Some(FieldDefault::Literal(value)) => Some(FieldDefault::Literal(
972 crate::index_values::derived_value(declaration, name, value)?,
973 )),
974 None => None,
975 Some(FieldDefault::CurrentTimestamp) => return Err(DatabaseError::InvalidContract),
976 };
977 key.references = None;
978 key.constraints.clear();
979 key.nullable = true;
980 Ok(key)
981}
982
983pub fn derived_adoption_query(
985 declaration: &Entity,
986 after: Option<Value>,
987) -> Result<Query, DatabaseError> {
988 let mut fields = declaration
989 .identity
990 .fields()
991 .into_iter()
992 .map(str::to_owned)
993 .collect::<Vec<_>>();
994 for name in crate::index_values::derived_fields(declaration) {
995 if !fields.iter().any(|field| field == name) {
996 fields.push(name.to_owned());
997 }
998 }
999 let (filter, order) = crate::operations::identity_page(declaration, after)?;
1000 Ok(Query {
1001 entity: declaration.name.clone(),
1002 fields,
1003 filter,
1004 order,
1005 limit: 1000,
1006 offset: 0,
1007 })
1008}
1009
1010pub fn derived_adoption_statements(
1012 dialect: Dialect,
1013 declaration: &Entity,
1014 rows: &[serde_json::Map<String, Value>],
1015) -> Result<Vec<Statement>, DatabaseError> {
1016 rows.iter()
1017 .map(|row| {
1018 let mut builder = Builder::new(dialect);
1019 let mut assignments = Vec::new();
1020 for name in crate::index_values::derived_fields(declaration) {
1021 let key = builder.name(&crate::index_values::derived_key(declaration, name)?)?;
1022 let value = crate::index_values::derived_value(
1023 declaration,
1024 name,
1025 row.get(name).ok_or(DatabaseError::StorageFailure)?,
1026 )?;
1027 let binding = builder.bind(value);
1028 assignments.push(format!("{key} = {binding}"));
1029 }
1030 let identity = Predicate::All(
1031 declaration
1032 .identity
1033 .fields()
1034 .into_iter()
1035 .map(|name| {
1036 row.get(name)
1037 .cloned()
1038 .map(|value| Predicate::Equal(name.to_owned(), value))
1039 .ok_or(DatabaseError::StorageFailure)
1040 })
1041 .collect::<Result<Vec<_>, _>>()?,
1042 );
1043 let condition = builder.filter(declaration, &identity)?;
1044 let table = builder.name(&declaration.name)?;
1045 Ok(builder.finish(format!(
1046 "UPDATE {table} SET {} WHERE {condition}",
1047 assignments.join(", ")
1048 )))
1049 })
1050 .collect()
1051}
1052
1053pub fn adoption_cursor(
1055 declaration: &Entity,
1056 row: &serde_json::Map<String, Value>,
1057) -> Result<Value, DatabaseError> {
1058 match &declaration.identity {
1059 Identity::AutoInteger(name) => row.get(name).cloned().ok_or(DatabaseError::StorageFailure),
1060 Identity::Composite(names) => names
1061 .iter()
1062 .map(|name| {
1063 row.get(name)
1064 .cloned()
1065 .map(|value| (name.clone(), value))
1066 .ok_or(DatabaseError::StorageFailure)
1067 })
1068 .collect::<Result<serde_json::Map<String, Value>, _>>()
1069 .map(Value::Object),
1070 }
1071}
1072
1073pub fn field_copy_query(
1075 schema: &AppSchema,
1076 change: &SchemaChange,
1077 after: Option<Value>,
1078) -> Result<Query, DatabaseError> {
1079 let SchemaChange::CopyField {
1080 entity: name,
1081 source,
1082 target,
1083 filter,
1084 } = change
1085 else {
1086 return Err(DatabaseError::InvalidContract);
1087 };
1088 let declaration = entity(schema, name)?;
1089 validate_copy_field(declaration, source, target, filter)?;
1090 let (cursor, order) = crate::operations::identity_page(declaration, after)?;
1091 let mut fields = declaration
1092 .identity
1093 .fields()
1094 .into_iter()
1095 .map(str::to_owned)
1096 .collect::<Vec<_>>();
1097 if !fields.contains(source) {
1098 fields.push(source.clone());
1099 }
1100 Ok(Query {
1101 entity: name.clone(),
1102 fields,
1103 filter: Some(match cursor {
1104 Some(cursor) => Predicate::All(vec![filter.clone(), cursor]),
1105 None => filter.clone(),
1106 }),
1107 order,
1108 limit: 1000,
1109 offset: 0,
1110 })
1111}
1112
1113pub fn field_copy_statements(
1115 dialect: Dialect,
1116 schema: &AppSchema,
1117 change: &SchemaChange,
1118 rows: &[serde_json::Map<String, Value>],
1119) -> Result<Vec<Statement>, DatabaseError> {
1120 let SchemaChange::CopyField {
1121 entity: name,
1122 source,
1123 target,
1124 ..
1125 } = change
1126 else {
1127 return Err(DatabaseError::InvalidContract);
1128 };
1129 let declaration = entity(schema, name)?;
1130 rows.iter()
1131 .map(|row| {
1132 let filter = Predicate::All(
1133 declaration
1134 .identity
1135 .fields()
1136 .into_iter()
1137 .map(|name| {
1138 row.get(name)
1139 .cloned()
1140 .map(|value| Predicate::Equal(name.to_owned(), value))
1141 .ok_or(DatabaseError::StorageFailure)
1142 })
1143 .collect::<Result<Vec<_>, _>>()?,
1144 );
1145 compile_mutation(
1146 dialect,
1147 schema,
1148 &Mutation::Update {
1149 entity: name.clone(),
1150 filter,
1151 values: BTreeMap::from([(
1152 target.clone(),
1153 WriteValue::Literal(
1154 row.get(source)
1155 .cloned()
1156 .ok_or(DatabaseError::StorageFailure)?,
1157 ),
1158 )]),
1159 expected_rows: Some(1),
1160 },
1161 &[],
1162 )
1163 })
1164 .collect()
1165}
1166
1167pub fn sql_type(dialect: Dialect, field: &Field) -> Result<String, DatabaseError> {
1170 Ok(match (dialect, field.kind) {
1171 (Dialect::Mysql, FieldType::Text) => {
1172 let storage = match field.max_length {
1173 Some(length) if length > 0 && length <= 4096 => format!("VARCHAR({length})"),
1174 None | Some(4097..=MAX_TEXT_LENGTH) => "MEDIUMTEXT".to_owned(),
1175 Some(_) => return Err(DatabaseError::InvalidContract),
1176 };
1177 format!("{storage} CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_bin")
1178 }
1179 (dialect, FieldType::Text) => {
1180 let storage = match field.max_length {
1181 Some(length) if length > 0 && length <= MAX_TEXT_LENGTH => {
1182 format!("VARCHAR({length})")
1183 }
1184 Some(_) => return Err(DatabaseError::InvalidContract),
1185 None => "TEXT".to_owned(),
1186 };
1187 match dialect {
1188 Dialect::Postgresql => format!("{storage} COLLATE \"C\""),
1189 Dialect::Sqlite => format!("{storage} COLLATE BINARY"),
1190 Dialect::Mysql => unreachable!("MySQL text storage is handled above"),
1191 }
1192 }
1193 (Dialect::Sqlite, FieldType::Integer | FieldType::Boolean) => "INTEGER".to_owned(),
1194 (Dialect::Sqlite, FieldType::Decimal) => "NUMERIC".to_owned(),
1195 (Dialect::Sqlite, FieldType::Timestamp | FieldType::Json) => "TEXT".to_owned(),
1196 (Dialect::Sqlite, FieldType::Binary) => "BLOB".to_owned(),
1197 (Dialect::Postgresql, FieldType::Integer) => "BIGINT".to_owned(),
1198 (Dialect::Postgresql, FieldType::Decimal) => "NUMERIC(10,2)".to_owned(),
1199 (Dialect::Postgresql, FieldType::Boolean) => "BOOLEAN".to_owned(),
1200 (Dialect::Postgresql, FieldType::Timestamp) => "VARCHAR(30) COLLATE \"C\"".to_owned(),
1201 (Dialect::Postgresql, FieldType::Json) => "JSONB".to_owned(),
1202 (Dialect::Postgresql, FieldType::Binary) => "BYTEA".to_owned(),
1203 (Dialect::Mysql, FieldType::Integer) => "BIGINT".to_owned(),
1204 (Dialect::Mysql, FieldType::Decimal) => "DECIMAL(10,2)".to_owned(),
1205 (Dialect::Mysql, FieldType::Boolean) => "BOOLEAN".to_owned(),
1206 (Dialect::Mysql, FieldType::Timestamp) => {
1207 "VARCHAR(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_bin".to_owned()
1208 }
1209 (Dialect::Mysql, FieldType::Json) => "JSON".to_owned(),
1210 (Dialect::Mysql, FieldType::Binary) => match field.max_length {
1211 Some(length) if length <= 65535 => format!("VARBINARY({length})"),
1212 _ => "LONGBLOB".to_owned(),
1213 },
1214 })
1215}
1216
1217fn ddl_literal(dialect: Dialect, value: &Value) -> Result<String, DatabaseError> {
1219 match value {
1220 Value::Null => Ok("NULL".to_owned()),
1221 Value::Bool(value) => Ok(match (dialect, value) {
1222 (Dialect::Postgresql, true) => "TRUE",
1223 (Dialect::Postgresql, false) => "FALSE",
1224 (_, true) => "1",
1225 (_, false) => "0",
1226 }
1227 .to_owned()),
1228 Value::Number(value) => Ok(value.to_string()),
1229 Value::String(value) if value.len() <= 4096 && dialect == Dialect::Mysql => {
1230 let mut encoded = String::with_capacity(value.len() * 2);
1233 const DIGITS: &[u8; 16] = b"0123456789abcdef";
1234 for byte in value.bytes() {
1235 encoded.push(char::from(DIGITS[usize::from(byte >> 4)]));
1236 encoded.push(char::from(DIGITS[usize::from(byte & 15)]));
1237 }
1238 Ok(format!("_utf8mb4 X'{encoded}'"))
1239 }
1240 Value::String(value) if value.len() <= 4096 => {
1241 Ok(format!("'{}'", value.replace('\'', "''")))
1242 }
1243 _ => Err(DatabaseError::InvalidContract),
1244 }
1245}
1246
1247pub fn current_timestamp_sql(dialect: Dialect) -> &'static str {
1249 match dialect {
1250 Dialect::Sqlite => "(strftime('%Y-%m-%dT%H:%M:%f', 'now') || '000000Z')",
1251 Dialect::Mysql => "(CONCAT(DATE_FORMAT(UTC_TIMESTAMP(6), '%Y-%m-%dT%H:%i:%s.%f'), '000Z'))",
1252 Dialect::Postgresql => {
1253 "(to_char(CURRENT_TIMESTAMP AT TIME ZONE 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS.US') || '000Z')"
1254 }
1255 }
1256}
1257
1258pub fn field_default_sql(dialect: Dialect, field: &Field) -> Result<Option<String>, DatabaseError> {
1260 field
1261 .default
1262 .as_ref()
1263 .map(|default| match default {
1264 FieldDefault::Literal(value)
1265 if dialect == Dialect::Mysql
1266 && field.kind == FieldType::Text
1267 && field.max_length.is_none_or(|length| length > 4096) =>
1268 {
1269 Ok(format!("({})", ddl_literal(dialect, value)?))
1270 }
1271 FieldDefault::Literal(value) => ddl_field_literal(dialect, field, value),
1272 FieldDefault::CurrentTimestamp => Ok(current_timestamp_sql(dialect).to_owned()),
1273 })
1274 .transpose()
1275}
1276
1277fn ddl_field_literal(
1278 dialect: Dialect,
1279 field: &Field,
1280 value: &Value,
1281) -> Result<String, DatabaseError> {
1282 if field.kind == FieldType::Binary && !value.is_null() {
1283 use std::fmt::Write;
1284 let bytes = value.as_array().ok_or(DatabaseError::InvalidContract)?;
1285 if bytes.len() > 1_048_576 {
1286 return Err(DatabaseError::CapacityExceeded);
1287 }
1288 let mut encoded = String::with_capacity(bytes.len() * 2);
1289 for byte in bytes {
1290 let byte = byte
1291 .as_u64()
1292 .filter(|byte| *byte <= 255)
1293 .ok_or(DatabaseError::InvalidContract)?;
1294 write!(&mut encoded, "{byte:02x}").map_err(|_| DatabaseError::InvalidContract)?;
1295 }
1296 return Ok(match dialect {
1297 Dialect::Postgresql => format!("decode('{encoded}', 'hex')"),
1298 Dialect::Sqlite | Dialect::Mysql => format!("X'{encoded}'"),
1299 });
1300 }
1301 if field.kind == FieldType::Timestamp && !value.is_null() {
1302 let timestamp = crate::operations::canonical_stored_timestamp(
1303 value.as_str().ok_or(DatabaseError::InvalidContract)?,
1304 )?;
1305 return ddl_literal(dialect, &Value::String(timestamp));
1306 }
1307 if field.kind == FieldType::Json && !value.is_null() {
1308 let canonical = crate::operations::canonical_json_value(value)?;
1309 let serialized =
1310 serde_json::to_string(&canonical).map_err(|_| DatabaseError::InvalidContract)?;
1311 let encoded = ddl_literal(dialect, &Value::String(serialized))?;
1312 return Ok(match dialect {
1313 Dialect::Mysql => format!("(CAST({encoded} AS JSON))"),
1314 Dialect::Sqlite | Dialect::Postgresql => encoded,
1315 });
1316 }
1317 if field.kind == FieldType::Decimal && !value.is_null() {
1318 let cents = decimal_cents(value)?;
1319 return Ok(match dialect {
1320 Dialect::Sqlite | Dialect::Postgresql | Dialect::Mysql => decimal_from_cents(cents),
1321 });
1322 }
1323 ddl_literal(dialect, value)
1324}
1325
1326pub fn column(dialect: Dialect, entity: &Entity, field: &Field) -> Result<String, DatabaseError> {
1328 let builder = Builder::new(dialect);
1329 let name = builder.name(&field.name)?;
1330 if entity.identity.generated_field() == Some(field.name.as_str()) {
1331 return Ok(format!(
1332 "{name} {}",
1333 match dialect {
1334 Dialect::Sqlite => "INTEGER PRIMARY KEY AUTOINCREMENT",
1335 Dialect::Postgresql => "BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY",
1336 Dialect::Mysql => "BIGINT AUTO_INCREMENT PRIMARY KEY",
1337 }
1338 ));
1339 }
1340 let mut sql = format!("{name} {}", sql_type(dialect, field)?);
1341 if !field.nullable {
1342 sql.push_str(" NOT NULL");
1343 }
1344 if let Some(default) = field_default_sql(dialect, field)? {
1345 sql.push_str(" DEFAULT ");
1346 sql.push_str(&default);
1347 }
1348 if dialect != Dialect::Mysql
1349 && field.kind != FieldType::Json
1350 && let Some((parent, parent_field)) = &field.references
1351 {
1352 sql.push_str(&format!(
1353 " REFERENCES {}({})",
1354 builder.name(parent)?,
1355 builder.name(parent_field)?
1356 ));
1357 sql.push_str(match field.on_delete.unwrap_or(OnDelete::Restrict) {
1358 OnDelete::Restrict => " ON DELETE RESTRICT",
1359 OnDelete::Cascade => " ON DELETE CASCADE",
1360 OnDelete::SetNull => " ON DELETE SET NULL",
1361 });
1362 }
1363 for constraint in &field.constraints {
1364 let name = builder.name(&field.name)?;
1365 let check = match constraint {
1366 FieldConstraint::Allowed(values) => {
1367 let values = values
1368 .iter()
1369 .map(|value| ddl_field_literal(dialect, field, value))
1370 .collect::<Result<Vec<_>, _>>()?
1371 .join(", ");
1372 format!("{name} IN ({values})")
1373 }
1374 FieldConstraint::LengthBetween { minimum, maximum } => {
1375 let function = if dialect == Dialect::Mysql {
1376 "CHAR_LENGTH"
1377 } else {
1378 "LENGTH"
1379 };
1380 format!("{function}({name}) BETWEEN {minimum} AND {maximum}")
1381 }
1382 FieldConstraint::GreaterThan(value) => {
1383 format!("{name} > {}", ddl_field_literal(dialect, field, value)?)
1384 }
1385 FieldConstraint::AtMost(value) => {
1386 format!("{name} <= {}", ddl_field_literal(dialect, field, value)?)
1387 }
1388 FieldConstraint::Between { minimum, maximum } => format!(
1389 "{name} BETWEEN {} AND {}",
1390 ddl_field_literal(dialect, field, minimum)?,
1391 ddl_field_literal(dialect, field, maximum)?
1392 ),
1393 };
1394 sql.push_str(&format!(" CHECK ({check})"));
1395 }
1396 Ok(sql)
1397}
1398
1399fn foreign_key_clause(
1401 dialect: Dialect,
1402 table: &str,
1403 field: &Field,
1404) -> Result<Option<String>, DatabaseError> {
1405 let Some(_) = &field.references else {
1406 return Ok(None);
1407 };
1408 let builder = Builder::new(dialect);
1409 let constraint =
1410 crate::operations::generated_index_name("fk", table, std::slice::from_ref(&field.name))?;
1411 let constraint = builder.name(&constraint)?;
1412 let (physical_source, physical_parent, physical_key) =
1413 crate::index_values::reference_keys(field)?;
1414 let source = builder.name(&physical_source)?;
1415 let target = builder.name(&physical_parent)?;
1416 let target_field = builder.name(&physical_key)?;
1417 let on_delete = match field.on_delete.unwrap_or(OnDelete::Restrict) {
1418 OnDelete::Restrict => "RESTRICT",
1419 OnDelete::Cascade => "CASCADE",
1420 OnDelete::SetNull => "SET NULL",
1421 };
1422 Ok(Some(format!(
1423 "CONSTRAINT {constraint} FOREIGN KEY ({source}) REFERENCES {target}({target_field}) ON DELETE {on_delete}"
1424 )))
1425}
1426
1427fn ddl_filter(
1429 builder: &Builder,
1430 declaration: &Entity,
1431 predicate: &Predicate,
1432) -> Result<String, DatabaseError> {
1433 let literal = |name: &str, value: &Value| {
1434 let field = declaration
1435 .fields
1436 .iter()
1437 .find(|field| field.name == name)
1438 .ok_or(DatabaseError::InvalidContract)?;
1439 if field.kind == FieldType::Json {
1440 ddl_literal(
1441 builder.dialect,
1442 &crate::index_values::json_comparison_value(value)?,
1443 )
1444 } else {
1445 ddl_field_literal(builder.dialect, field, value)
1446 }
1447 };
1448 let compare = |name: &str, operator: &str, value: &Value| {
1449 Ok::<_, DatabaseError>(format!(
1450 "{} {operator} {}",
1451 builder.comparison_name(declaration, name)?,
1452 literal(name, value)?
1453 ))
1454 };
1455 match predicate {
1456 Predicate::Equal(name, value) if value.is_null() => Ok(format!(
1457 "{} IS NULL",
1458 builder.comparison_name(declaration, name)?
1459 )),
1460 Predicate::NotEqual(name, value) if value.is_null() => Ok(format!(
1461 "{} IS NOT NULL",
1462 builder.comparison_name(declaration, name)?
1463 )),
1464 Predicate::Equal(name, value) => compare(name, "=", value),
1465 Predicate::GeneratedEqual(_, _) => Err(DatabaseError::InvalidContract),
1466 Predicate::NotEqual(name, value) => compare(name, "<>", value),
1467 Predicate::LessThan(name, value) => compare(name, "<", value),
1468 Predicate::AtMost(name, value) => compare(name, "<=", value),
1469 Predicate::GreaterThan(name, value) => compare(name, ">", value),
1470 Predicate::AtLeast(name, value) => compare(name, ">=", value),
1471 Predicate::Contains(name, needle) => {
1472 let name = builder.name(name)?;
1473 let needle = ddl_literal(builder.dialect, &Value::String(needle.clone()))?;
1474 Ok(match builder.dialect {
1475 Dialect::Mysql => format!("LOCATE(BINARY {needle}, BINARY {name}) > 0"),
1476 Dialect::Sqlite => format!("INSTR({name}, {needle}) > 0"),
1477 Dialect::Postgresql => format!("STRPOS({name}, {needle}) > 0"),
1478 })
1479 }
1480 Predicate::IsNull(name) => Ok(format!(
1481 "{} IS NULL",
1482 builder.comparison_name(declaration, name)?
1483 )),
1484 Predicate::In(name, values) => {
1485 if values.is_empty() {
1486 return Ok("1 = 0".to_owned());
1487 }
1488 let values = values
1489 .iter()
1490 .map(|value| literal(name, value))
1491 .collect::<Result<Vec<_>, _>>()?
1492 .join(", ");
1493 Ok(format!(
1494 "{} IN ({values})",
1495 builder.comparison_name(declaration, name)?
1496 ))
1497 }
1498 Predicate::All(parts) | Predicate::Any(parts) => {
1499 let separator = if matches!(predicate, Predicate::All(_)) {
1500 " AND "
1501 } else {
1502 " OR "
1503 };
1504 let parts = parts
1505 .iter()
1506 .map(|part| ddl_filter(builder, declaration, part))
1507 .collect::<Result<Vec<_>, _>>()?;
1508 Ok(format!("({})", parts.join(separator)))
1509 }
1510 }
1511}
1512
1513fn conditional_index(
1515 dialect: Dialect,
1516 declaration: &Entity,
1517 index: &Index,
1518) -> Result<Statement, DatabaseError> {
1519 expression_index(dialect, declaration, index, false)
1520}
1521
1522pub fn previous_expression_index(
1524 dialect: Dialect,
1525 declaration: &Entity,
1526 index: &Index,
1527) -> Result<Statement, DatabaseError> {
1528 expression_index(dialect, declaration, index, true)
1529}
1530
1531fn expression_index(
1533 dialect: Dialect,
1534 declaration: &Entity,
1535 index: &Index,
1536 previous: bool,
1537) -> Result<Statement, DatabaseError> {
1538 let builder = Builder::new(dialect);
1539 if !valid_identifier(&index.name) || index.keys.is_empty() {
1540 return Err(DatabaseError::InvalidContract);
1541 }
1542 let condition = index
1543 .filter
1544 .as_ref()
1545 .map(|filter| {
1546 crate::operations::validate_predicate(declaration, filter)?;
1547 ddl_filter(&builder, declaration, filter)
1548 })
1549 .transpose()?;
1550 let columns = index
1551 .keys
1552 .iter()
1553 .map(|key| {
1554 let expression = match key {
1555 IndexKey::Field(name) => builder.comparison_name(declaration, name)?,
1556 IndexKey::Lowercase(name) => {
1557 if previous {
1558 format!("LOWER({})", builder.name(name)?)
1559 } else {
1560 builder.name(&crate::index_values::lowercase_key(name))?
1561 }
1562 }
1563 };
1564 Ok::<_, DatabaseError>(match (dialect, &condition) {
1565 (Dialect::Mysql, Some(filter)) => {
1566 format!("(CASE WHEN {filter} THEN {expression} ELSE NULL END)")
1567 }
1568 (Dialect::Mysql, None) if previous && matches!(key, IndexKey::Lowercase(_)) => {
1569 format!("({expression})")
1570 }
1571 _ => expression,
1572 })
1573 })
1574 .collect::<Result<Vec<_>, DatabaseError>>()?
1575 .join(", ");
1576 let mut sql = format!(
1577 "CREATE {}INDEX {} ON {} ({columns})",
1578 if index.unique { "UNIQUE " } else { "" },
1579 builder.name(&index.name)?,
1580 builder.name(&declaration.name)?
1581 );
1582 if let Some(filter) = &condition
1583 && dialect != Dialect::Mysql
1584 {
1585 sql.push_str(" WHERE ");
1586 sql.push_str(filter);
1587 }
1588 Ok(builder.finish(sql))
1589}
1590
1591pub fn compile_schema_change(
1594 dialect: Dialect,
1595 schema: &AppSchema,
1596 change: &SchemaChange,
1597) -> Result<Vec<Statement>, DatabaseError> {
1598 let builder = Builder::new(dialect);
1599 match change {
1600 SchemaChange::SeedFrom { .. }
1603 | SchemaChange::CopyJsonRecords { .. }
1604 | SchemaChange::RetireFieldConstraint { .. }
1605 | SchemaChange::ConvertField { .. } => Err(DatabaseError::InvalidInput),
1606 SchemaChange::WidenIntegerToDecimal {
1607 entity: name,
1608 field,
1609 } => {
1610 let declaration = entity(schema, name)?;
1611 let column_field = declaration
1612 .fields
1613 .iter()
1614 .find(|item| item.name == *field)
1615 .ok_or(DatabaseError::InvalidContract)?;
1616 if column_field.kind != FieldType::Decimal
1617 || column_field.references.is_some()
1618 || declaration.identity.fields().contains(&field.as_str())
1619 {
1620 return Err(DatabaseError::InvalidContract);
1621 }
1622 let table = builder.name(name)?;
1623 let name = builder.name(field)?;
1624 match dialect {
1625 Dialect::Sqlite => Ok(Vec::new()),
1628 Dialect::Postgresql => Ok(vec![builder.finish(format!("ALTER TABLE {table} ALTER COLUMN {name} TYPE NUMERIC(10,2) USING {name}::NUMERIC(10,2)"))]),
1629 Dialect::Mysql => Ok(vec![builder.finish(format!("ALTER TABLE {table} MODIFY COLUMN {}", column(dialect, declaration, column_field)?))]),
1630 }
1631 }
1632 SchemaChange::CreateEntity(declaration) => {
1633 if !schema
1634 .entities
1635 .iter()
1636 .any(|entity| entity.name == declaration.name)
1637 {
1638 return Err(DatabaseError::InvalidContract);
1639 }
1640 let mut columns = declaration
1641 .fields
1642 .iter()
1643 .map(|field| column(dialect, declaration, field))
1644 .collect::<Result<Vec<_>, _>>()?;
1645 for name in crate::index_values::derived_fields(declaration) {
1646 columns.push(derived_column(dialect, declaration, name)?);
1647 }
1648 if dialect == Dialect::Mysql
1649 || declaration
1650 .fields
1651 .iter()
1652 .any(|field| field.kind == FieldType::Json && field.references.is_some())
1653 {
1654 columns.extend(
1655 declaration
1656 .fields
1657 .iter()
1658 .filter(|field| dialect == Dialect::Mysql || field.kind == FieldType::Json)
1659 .map(|field| foreign_key_clause(dialect, &declaration.name, field))
1660 .collect::<Result<Vec<_>, _>>()?
1661 .into_iter()
1662 .flatten(),
1663 );
1664 }
1665 if let Identity::Composite(names) = &declaration.identity {
1666 let key = names
1667 .iter()
1668 .map(|name| builder.comparison_name(declaration, name))
1669 .collect::<Result<Vec<_>, _>>()?
1670 .join(", ");
1671 columns.push(format!("PRIMARY KEY ({key})"));
1672 }
1673 let columns = columns.join(", ");
1674 let table = builder.name(&declaration.name)?;
1675 let mut statements =
1676 vec![builder.finish(format!("CREATE TABLE IF NOT EXISTS {table} ({columns})"))];
1677 for fields in &declaration.unique {
1678 statements.extend(compile_schema_change(
1679 dialect,
1680 schema,
1681 &SchemaChange::AddUnique {
1682 entity: declaration.name.clone(),
1683 fields: fields.clone(),
1684 },
1685 )?);
1686 }
1687 for fields in &declaration.indexes {
1688 statements.extend(compile_schema_change(
1689 dialect,
1690 schema,
1691 &SchemaChange::AddIndex {
1692 entity: declaration.name.clone(),
1693 fields: fields.clone(),
1694 },
1695 )?);
1696 }
1697 for index in &declaration.conditional_indexes {
1698 statements.push(conditional_index(dialect, declaration, index)?);
1699 }
1700 Ok(statements)
1701 }
1702 SchemaChange::AddField {
1703 entity: name,
1704 field,
1705 } => {
1706 let declaration = entity(schema, name)?;
1707 if declaration.identity.fields().contains(&field.name.as_str()) {
1708 return Err(DatabaseError::InvalidContract);
1709 }
1710 let table = builder.name(name)?;
1711 let definition = column(dialect, declaration, field)?;
1712 let mut statements =
1713 vec![builder.finish(format!("ALTER TABLE {table} ADD COLUMN {definition}"))];
1714 if crate::index_values::derived_fields(declaration).contains(field.name.as_str()) {
1715 let mut definition = derived_column(dialect, declaration, &field.name)?;
1716 if dialect == Dialect::Sqlite
1717 && field.kind == FieldType::Json
1718 && field.references.is_some()
1719 {
1720 let (_, parent, key) = crate::index_values::reference_keys(field)?;
1721 let names = Builder::new(dialect);
1722 let rule = match field.on_delete.unwrap_or(OnDelete::Restrict) {
1723 OnDelete::Restrict => "RESTRICT",
1724 OnDelete::Cascade => "CASCADE",
1725 OnDelete::SetNull => "SET NULL",
1726 };
1727 definition.push_str(&format!(
1728 " REFERENCES {}({}) ON DELETE {rule}",
1729 names.name(&parent)?,
1730 names.name(&key)?
1731 ));
1732 }
1733 statements.push(
1734 Builder::new(dialect)
1735 .finish(format!("ALTER TABLE {table} ADD COLUMN {definition}")),
1736 );
1737 }
1738 if (dialect == Dialect::Mysql
1739 || (dialect == Dialect::Postgresql && field.kind == FieldType::Json))
1740 && let Some(constraint) = foreign_key_clause(dialect, name, field)?
1741 {
1742 statements.push(
1743 Builder::new(dialect).finish(format!("ALTER TABLE {table} ADD {constraint}")),
1744 );
1745 }
1746 Ok(statements)
1747 }
1748 SchemaChange::PromoteIdentity {
1749 entity: name,
1750 field,
1751 } => {
1752 let declaration = entity(schema, name)?;
1753 if declaration.identity.generated_field() != Some(field.as_str()) {
1754 return Err(DatabaseError::InvalidContract);
1755 }
1756 let table = builder.name(name)?;
1757 let column = builder.name(field)?;
1758 match dialect {
1759 Dialect::Sqlite => Ok(Vec::new()),
1761 Dialect::Postgresql => {
1762 let table_literal =
1763 ddl_literal(dialect, &serde_json::Value::String(name.clone()))?;
1764 let field_literal =
1765 ddl_literal(dialect, &serde_json::Value::String(field.clone()))?;
1766 Ok(vec![
1767 builder.finish(format!("ALTER TABLE {table} ALTER COLUMN {column} ADD GENERATED BY DEFAULT AS IDENTITY")),
1768 Builder::new(dialect).finish(format!("SELECT setval(pg_get_serial_sequence({table_literal}, {field_literal}), GREATEST(COALESCE((SELECT MAX({column}) FROM {table}), 0), 1), EXISTS(SELECT 1 FROM {table}))")),
1769 ])
1770 }
1771 Dialect::Mysql => Ok(vec![builder.finish(format!(
1772 "ALTER TABLE {table} MODIFY COLUMN {column} BIGINT NOT NULL AUTO_INCREMENT"
1773 ))]),
1774 }
1775 }
1776 SchemaChange::AddUnique {
1777 entity: name,
1778 fields,
1779 }
1780 | SchemaChange::AddIndex {
1781 entity: name,
1782 fields,
1783 } => {
1784 let declaration = entity(schema, name)?;
1785 if fields.is_empty()
1786 || fields.len() > 16
1787 || fields
1788 .iter()
1789 .any(|field| !declaration.fields.iter().any(|item| item.name == *field))
1790 {
1791 return Err(DatabaseError::InvalidContract);
1792 }
1793 let unique = matches!(change, SchemaChange::AddUnique { .. });
1794 let index_name = crate::operations::generated_index_name(
1795 if unique { "uq" } else { "ix" },
1796 name,
1797 fields,
1798 )?;
1799 let columns = fields
1800 .iter()
1801 .map(|field| builder.comparison_name(entity(schema, name)?, field))
1802 .collect::<Result<Vec<_>, _>>()?
1803 .join(", ");
1804 let sql = format!(
1805 "CREATE {}INDEX {} ON {} ({columns})",
1806 if unique { "UNIQUE " } else { "" },
1807 builder.name(&index_name)?,
1808 builder.name(name)?
1809 );
1810 Ok(vec![builder.finish(sql)])
1811 }
1812 SchemaChange::AddConditionalIndex {
1813 entity: name,
1814 index,
1815 } => Ok(vec![conditional_index(
1816 dialect,
1817 entity(schema, name)?,
1818 index,
1819 )?]),
1820 SchemaChange::Seed {
1821 entity: name,
1822 values,
1823 } => {
1824 let declaration = entity(schema, name)?;
1825 if values.is_empty()
1826 || values.len() > 64
1827 || values
1828 .keys()
1829 .any(|field| !declaration.fields.iter().any(|item| item.name == *field))
1830 {
1831 return Err(DatabaseError::InvalidContract);
1832 }
1833 let mut columns = values
1834 .keys()
1835 .map(|field| builder.name(field))
1836 .collect::<Result<Vec<_>, _>>()?
1837 .join(", ");
1838 let mut builder = builder;
1839 let mut placeholders = values
1840 .iter()
1841 .map(|(name, value)| {
1842 let field = declaration
1843 .fields
1844 .iter()
1845 .find(|item| item.name == *name)
1846 .ok_or(DatabaseError::InvalidContract)?;
1847 builder.bind_field(field, value.clone())
1848 })
1849 .collect::<Result<Vec<_>, _>>()?
1850 .join(", ");
1851 for source in crate::index_values::derived_fields(declaration) {
1852 if let Some(value) = values.get(source) {
1853 columns.push_str(&format!(
1854 ", {}",
1855 builder.name(&crate::index_values::derived_key(declaration, source)?)?
1856 ));
1857 let binding = builder.bind(crate::index_values::derived_value(
1858 declaration,
1859 source,
1860 value,
1861 )?);
1862 placeholders.push_str(&format!(", {binding}"));
1863 }
1864 }
1865 let table = builder.name(name)?;
1866 Ok(vec![builder.finish(format!(
1867 "INSERT INTO {table} ({columns}) VALUES ({placeholders})"
1868 ))])
1869 }
1870 SchemaChange::Backfill {
1871 entity: name,
1872 filter,
1873 values,
1874 } => {
1875 let declaration = entity(schema, name)?;
1876 if values.is_empty()
1877 || values.len() > 64
1878 || values
1879 .keys()
1880 .any(|field| !declaration.fields.iter().any(|item| item.name == *field))
1881 {
1882 return Err(DatabaseError::InvalidContract);
1883 }
1884 validate_predicate(declaration, filter)?;
1885 let values = values
1886 .iter()
1887 .map(|(name, value)| (name.clone(), WriteValue::Literal(value.clone())))
1888 .collect();
1889 Ok(vec![compile_mutation(
1890 dialect,
1891 schema,
1892 &Mutation::Update {
1893 entity: name.clone(),
1894 filter: filter.clone(),
1895 values,
1896 expected_rows: None,
1897 },
1898 &[],
1899 )?])
1900 }
1901 SchemaChange::DeleteWhere {
1902 entity: name,
1903 filter,
1904 } => {
1905 validate_predicate(entity(schema, name)?, filter)?;
1906 let mutation = Mutation::Delete {
1907 entity: name.clone(),
1908 filter: filter.clone(),
1909 expected_rows: None,
1910 };
1911 let mut statements = compile_delete_effects(dialect, schema, &mutation, &[])?;
1912 statements.push(compile_mutation(dialect, schema, &mutation, &[])?);
1913 Ok(statements)
1914 }
1915 SchemaChange::CopyField {
1916 entity: name,
1917 source,
1918 target,
1919 filter,
1920 } => {
1921 let declaration = entity(schema, name)?;
1922 validate_copy_field(declaration, source, target, filter)?;
1923 let mut builder = builder;
1924 let where_clause = builder.filter(declaration, filter)?;
1925 let sql = format!(
1926 "UPDATE {} SET {} = {} WHERE {where_clause}",
1927 builder.name(name)?,
1928 builder.name(target)?,
1929 builder.name(source)?
1930 );
1931 Ok(vec![builder.finish(sql)])
1932 }
1933 }
1934}