cratestack_sql/descriptor/defaults.rs
1use super::ModelDescriptor;
2use crate::{SqlColumnValue, SqlValue};
3
4#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5pub enum CreateDefaultType {
6 Bool,
7 Int,
8 BigInt,
9 String,
10}
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub struct CreateDefault {
14 pub column: &'static str,
15 pub auth_field: &'static str,
16 pub ty: CreateDefaultType,
17 pub nullable: bool,
18 /// Whether the auth field is required (non-optional) in the auth block.
19 /// When true, a missing auth field should cause validation to fail,
20 /// even if the model field is nullable. This prevents tenant-isolation
21 /// issues where NULL values bypass policy predicates.
22 pub auth_field_required: bool,
23}
24
25/// What cratestack generates for a `@default(now())`, `@default(uuid())`
26/// or `@default(cuid())` column (cratestack#1209). Chosen per field type at
27/// codegen, so the value binds as the column's own type.
28#[derive(Debug, Clone, Copy, PartialEq, Eq)]
29pub enum GeneratedValue {
30 /// `now()` on a `DateTime` field.
31 Now,
32 /// `uuid()` on a `Uuid` field.
33 Uuid,
34 /// `uuid()` on a `String` field: the hyphenated text form.
35 UuidText,
36 /// `cuid()` on a `Cuid` or `String` field.
37 Cuid,
38}
39
40impl GeneratedValue {
41 pub fn generate(self) -> SqlValue {
42 match self {
43 Self::Now => SqlValue::DateTime(chrono::Utc::now()),
44 Self::Uuid => SqlValue::Uuid(uuid::Uuid::new_v4()),
45 Self::UuidText => SqlValue::String(uuid::Uuid::new_v4().hyphenated().to_string()),
46 Self::Cuid => SqlValue::String(cratestack_core::cuid::new_cuid()),
47 }
48 }
49}
50
51/// A column cratestack fills on create when the input leaves it out.
52///
53/// Generated in Rust rather than left to a database `DEFAULT`, so the
54/// value is the same on every backend: an embedded runtime's table may
55/// predate its `DEFAULT` clauses (cratestack#1234), and `cuid()` has no
56/// database equivalent at all.
57/// The migration emitter still writes a matching database default where
58/// one exists, for writes that do not go through cratestack.
59#[derive(Debug, Clone, Copy, PartialEq, Eq)]
60pub struct GeneratedDefault {
61 pub column: &'static str,
62 pub value: GeneratedValue,
63}
64
65impl<M, PK> ModelDescriptor<M, PK> {
66 /// Attach the model's generated defaults. A separate step from
67 /// [`ModelDescriptor::new`] so descriptors built without one keep
68 /// compiling unchanged.
69 pub const fn with_generated_defaults(
70 self,
71 generated_defaults: &'static [GeneratedDefault],
72 ) -> Self {
73 Self {
74 generated_defaults,
75 ..self
76 }
77 }
78
79 /// Append a generated value for each generated-default column the
80 /// create values do not already carry.
81 pub fn fill_generated_defaults(&self, values: &mut Vec<SqlColumnValue>) {
82 for default in self.generated_defaults {
83 if values.iter().all(|value| value.column != default.column) {
84 values.push(SqlColumnValue {
85 column: default.column,
86 value: default.value.generate(),
87 });
88 }
89 }
90 }
91}