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