1use wasm_dbms_api::prelude::{
5 CandidColumnDef, ColumnDef, DbmsResult, DeleteBehavior, Filter, Query, Value,
6};
7use wasm_dbms_memory::prelude::{AccessControl, AccessControlList, MemoryProvider};
8
9use crate::database::WasmDbmsDatabase;
10
11pub trait DatabaseSchema<M, A = AccessControlList>
20where
21 M: MemoryProvider,
22 A: AccessControl,
23{
24 fn select(
26 &self,
27 dbms: &WasmDbmsDatabase<'_, M, A>,
28 table_name: &str,
29 query: Query,
30 ) -> DbmsResult<Vec<Vec<(ColumnDef, Value)>>>;
31
32 fn select_join(
35 &self,
36 dbms: &WasmDbmsDatabase<'_, M, A>,
37 from_table: &str,
38 query: Query,
39 ) -> DbmsResult<Vec<Vec<(CandidColumnDef, Value)>>> {
40 crate::join::JoinEngine::new(self).join(dbms, from_table, query)
41 }
42
43 fn referenced_tables(&self, table: &'static str) -> Vec<(&'static str, Vec<&'static str>)>;
45
46 fn insert(
48 &self,
49 dbms: &WasmDbmsDatabase<'_, M, A>,
50 table_name: &'static str,
51 record_values: &[(ColumnDef, Value)],
52 ) -> DbmsResult<()>;
53
54 fn delete(
56 &self,
57 dbms: &WasmDbmsDatabase<'_, M, A>,
58 table_name: &'static str,
59 delete_behavior: DeleteBehavior,
60 filter: Option<Filter>,
61 ) -> DbmsResult<u64>;
62
63 fn update(
65 &self,
66 dbms: &WasmDbmsDatabase<'_, M, A>,
67 table_name: &'static str,
68 patch_values: &[(ColumnDef, Value)],
69 filter: Option<Filter>,
70 ) -> DbmsResult<u64>;
71
72 fn validate_insert(
74 &self,
75 dbms: &WasmDbmsDatabase<'_, M, A>,
76 table_name: &'static str,
77 record_values: &[(ColumnDef, Value)],
78 ) -> DbmsResult<()>;
79
80 fn validate_update(
82 &self,
83 dbms: &WasmDbmsDatabase<'_, M, A>,
84 table_name: &'static str,
85 record_values: &[(ColumnDef, Value)],
86 old_pk: Value,
87 ) -> DbmsResult<()>;
88}
89
90#[cfg(test)]
91mod tests {
92 use wasm_dbms_api::prelude::{
93 Database as _, InsertRecord as _, Query, TableSchema as _, Text, Uint32, Value,
94 };
95 use wasm_dbms_macros::{DatabaseSchema, Table};
96 use wasm_dbms_memory::prelude::HeapMemoryProvider;
97
98 use super::DatabaseSchema as _;
99 use crate::prelude::{DbmsContext, WasmDbmsDatabase};
100
101 #[derive(Debug, Table, Clone, PartialEq, Eq)]
102 #[table = "items"]
103 pub struct Item {
104 #[primary_key]
105 pub id: Uint32,
106 pub name: Text,
107 }
108
109 #[derive(DatabaseSchema)]
110 #[tables(Item = "items")]
111 pub struct TestSchema;
112
113 fn setup() -> DbmsContext<HeapMemoryProvider> {
114 let ctx = DbmsContext::new(HeapMemoryProvider::default());
115 TestSchema::register_tables(&ctx).unwrap();
116 ctx
117 }
118
119 #[test]
120 fn test_should_register_tables_via_macro() {
121 let ctx = DbmsContext::new(HeapMemoryProvider::default());
122 TestSchema::register_tables(&ctx).unwrap();
123 }
124
125 #[test]
126 fn test_should_insert_and_select_via_schema() {
127 let ctx = setup();
128 let db = WasmDbmsDatabase::oneshot(&ctx, TestSchema);
129
130 let insert = ItemInsertRequest::from_values(&[
131 (Item::columns()[0], Value::Uint32(Uint32(1))),
132 (Item::columns()[1], Value::Text(Text("foo".to_string()))),
133 ])
134 .unwrap();
135 db.insert::<Item>(insert).unwrap();
136
137 let rows = TestSchema
138 .select(&db, "items", Query::builder().build())
139 .unwrap();
140 assert_eq!(rows.len(), 1);
141 assert_eq!(rows[0][1].1, Value::Text(Text("foo".to_string())));
142 }
143
144 #[test]
145 fn test_should_delete_via_schema() {
146 let ctx = setup();
147 let db = WasmDbmsDatabase::oneshot(&ctx, TestSchema);
148
149 let insert = ItemInsertRequest::from_values(&[
150 (Item::columns()[0], Value::Uint32(Uint32(1))),
151 (Item::columns()[1], Value::Text(Text("foo".to_string()))),
152 ])
153 .unwrap();
154 db.insert::<Item>(insert).unwrap();
155
156 let deleted = TestSchema
157 .delete(
158 &db,
159 "items",
160 wasm_dbms_api::prelude::DeleteBehavior::Restrict,
161 None,
162 )
163 .unwrap();
164 assert_eq!(deleted, 1);
165 }
166
167 #[test]
168 fn test_should_return_error_for_unknown_table() {
169 let ctx = setup();
170 let db = WasmDbmsDatabase::oneshot(&ctx, TestSchema);
171
172 let result = TestSchema.select(&db, "nonexistent", Query::builder().build());
173 assert!(result.is_err());
174 }
175
176 #[test]
177 fn test_should_return_referenced_tables() {
178 let refs = <TestSchema as super::DatabaseSchema<HeapMemoryProvider>>::referenced_tables(
179 &TestSchema,
180 "items",
181 );
182 assert!(refs.is_empty());
183 }
184
185 #[test]
186 fn test_commit_rolls_back_all_operations_on_failure() {
187 let ctx = setup();
188 let owner = vec![1, 2, 3];
189
190 let tx_id = ctx.begin_transaction(owner);
192 let mut db = WasmDbmsDatabase::from_transaction(&ctx, TestSchema, tx_id);
193
194 let first = ItemInsertRequest::from_values(&[
195 (Item::columns()[0], Value::Uint32(Uint32(1))),
196 (Item::columns()[1], Value::Text(Text("first".to_string()))),
197 ])
198 .unwrap();
199 db.insert::<Item>(first).unwrap();
200
201 let second = ItemInsertRequest::from_values(&[
202 (Item::columns()[0], Value::Uint32(Uint32(2))),
203 (Item::columns()[1], Value::Text(Text("second".to_string()))),
204 ])
205 .unwrap();
206 db.insert::<Item>(second).unwrap();
207
208 let oneshot = WasmDbmsDatabase::oneshot(&ctx, TestSchema);
211 let conflicting = ItemInsertRequest::from_values(&[
212 (Item::columns()[0], Value::Uint32(Uint32(2))),
213 (
214 Item::columns()[1],
215 Value::Text(Text("conflict".to_string())),
216 ),
217 ])
218 .unwrap();
219 oneshot.insert::<Item>(conflicting).unwrap();
220
221 let result = db.commit();
224 assert!(result.is_err());
225
226 let db = WasmDbmsDatabase::oneshot(&ctx, TestSchema);
229 let rows = db.select::<Item>(Query::builder().build()).unwrap();
230 assert_eq!(rows.len(), 1, "expected only the conflicting row");
231 assert_eq!(rows[0].id, Some(Uint32(2)));
232 assert_eq!(rows[0].name, Some(Text("conflict".to_string())));
233 }
234}