cratestack_sql/descriptor/
mod.rs1use std::fmt::Write;
2use std::marker::PhantomData;
3
4use cratestack_core::ModelEventKind;
5use cratestack_policy::ReadPolicy;
6
7mod column_lookup;
8mod defaults;
9mod embedded_defaults;
10mod model_impls;
11mod read_source;
12mod subscribed;
13mod unique_columns;
14mod view;
15
16#[cfg(test)]
17mod tests_view;
18
19pub use defaults::{CreateDefault, CreateDefaultType, GeneratedDefault, GeneratedValue};
20pub use embedded_defaults::{DatabaseDefault, LiteralDefault};
21pub use read_source::{ReadSource, WriteSource};
22pub use subscribed::EventTarget;
23pub use view::ViewDescriptor;
24
25#[derive(Debug, Clone, Copy, PartialEq, Eq)]
26pub struct ModelColumn {
27 pub rust_name: &'static str,
28 pub sql_name: &'static str,
29}
30
31#[derive(Debug, Clone, Copy)]
32pub struct ModelDescriptor<M, PK> {
33 pub schema_name: &'static str,
34 pub table_name: &'static str,
35 pub columns: &'static [ModelColumn],
36 pub primary_key: &'static str,
37 pub allowed_fields: &'static [&'static str],
38 pub allowed_includes: &'static [&'static str],
39 pub allowed_sorts: &'static [&'static str],
40 pub read_allow_policies: &'static [ReadPolicy],
41 pub read_deny_policies: &'static [ReadPolicy],
42 pub detail_allow_policies: &'static [ReadPolicy],
43 pub detail_deny_policies: &'static [ReadPolicy],
44 pub create_allow_policies: &'static [ReadPolicy],
45 pub create_deny_policies: &'static [ReadPolicy],
46 pub update_allow_policies: &'static [ReadPolicy],
47 pub update_deny_policies: &'static [ReadPolicy],
48 pub delete_allow_policies: &'static [ReadPolicy],
49 pub delete_deny_policies: &'static [ReadPolicy],
50 pub create_defaults: &'static [CreateDefault],
51 pub generated_defaults: &'static [GeneratedDefault],
53 pub literal_defaults: &'static [LiteralDefault],
57 pub database_defaults: &'static [DatabaseDefault],
59 pub emitted_events: &'static [ModelEventKind],
60 pub subscribed: bool,
63 pub unique_columns: &'static [&'static str],
66 pub version_column: Option<&'static str>,
70 pub audit_enabled: bool,
74 pub pii_columns: &'static [&'static str],
83 pub sensitive_columns: &'static [&'static str],
86 pub soft_delete_column: Option<&'static str>,
92 pub retention_days: Option<u32>,
97 pub upsert_update_columns: &'static [&'static str],
104 _marker: PhantomData<fn() -> (M, PK)>,
105}
106
107impl<M, PK> ModelDescriptor<M, PK> {
108 #[allow(clippy::too_many_arguments)]
111 pub const fn new(
112 schema_name: &'static str,
113 table_name: &'static str,
114 columns: &'static [ModelColumn],
115 primary_key: &'static str,
116 allowed_fields: &'static [&'static str],
117 allowed_includes: &'static [&'static str],
118 allowed_sorts: &'static [&'static str],
119 read_allow_policies: &'static [ReadPolicy],
120 read_deny_policies: &'static [ReadPolicy],
121 detail_allow_policies: &'static [ReadPolicy],
122 detail_deny_policies: &'static [ReadPolicy],
123 create_allow_policies: &'static [ReadPolicy],
124 create_deny_policies: &'static [ReadPolicy],
125 update_allow_policies: &'static [ReadPolicy],
126 update_deny_policies: &'static [ReadPolicy],
127 delete_allow_policies: &'static [ReadPolicy],
128 delete_deny_policies: &'static [ReadPolicy],
129 create_defaults: &'static [CreateDefault],
130 emitted_events: &'static [ModelEventKind],
131 version_column: Option<&'static str>,
132 audit_enabled: bool,
133 pii_columns: &'static [&'static str],
134 sensitive_columns: &'static [&'static str],
135 soft_delete_column: Option<&'static str>,
136 retention_days: Option<u32>,
137 upsert_update_columns: &'static [&'static str],
138 ) -> Self {
139 Self {
140 schema_name,
141 table_name,
142 columns,
143 primary_key,
144 allowed_fields,
145 allowed_includes,
146 allowed_sorts,
147 read_allow_policies,
148 read_deny_policies,
149 detail_allow_policies,
150 detail_deny_policies,
151 create_allow_policies,
152 create_deny_policies,
153 update_allow_policies,
154 update_deny_policies,
155 delete_allow_policies,
156 delete_deny_policies,
157 create_defaults,
158 generated_defaults: &[],
159 literal_defaults: &[],
160 database_defaults: &[],
161 emitted_events,
162 subscribed: false,
163 unique_columns: &[],
164 version_column,
165 audit_enabled,
166 pii_columns,
167 sensitive_columns,
168 soft_delete_column,
169 retention_days,
170 upsert_update_columns,
171 _marker: PhantomData,
172 }
173 }
174
175 pub fn emits(&self, operation: ModelEventKind) -> bool {
176 self.emitted_events.contains(&operation)
177 }
178
179 pub fn select_projection(&self) -> String {
180 let mut sql = String::new();
181 for (index, column) in self.columns.iter().enumerate() {
182 if index > 0 {
183 sql.push_str(", ");
184 }
185 let _ = write!(sql, "{} AS \"{}\"", column.sql_name, column.rust_name);
186 }
187 sql
188 }
189
190 pub fn select_projection_subset(&self, columns: &[&str]) -> String {
199 let mut sql = String::new();
200 let mut emitted = false;
201 for column in self.columns.iter() {
202 if columns.contains(&column.sql_name) && {
203 if emitted {
204 sql.push_str(", ");
205 }
206 let _ = write!(sql, "{} AS \"{}\"", column.sql_name, column.rust_name);
207 emitted = true;
208 true
209 } {}
210 }
211 if !emitted {
212 if let Some(pk_column) = self
218 .columns
219 .iter()
220 .find(|column| column.sql_name == self.primary_key)
221 {
222 let _ = write!(sql, "{} AS \"{}\"", pk_column.sql_name, pk_column.rust_name,);
223 }
224 }
225 sql
226 }
227}
228
229