wasm_dbms_macros/lib.rs
1#![crate_name = "wasm_dbms_macros"]
2#![crate_type = "lib"]
3#![cfg_attr(docsrs, feature(doc_cfg))]
4#![deny(clippy::print_stdout)]
5#![deny(clippy::print_stderr)]
6
7//! Runtime-agnostic procedural macros for the wasm-dbms DBMS engine.
8//!
9//! This crate provides procedural macros to automatically implement traits
10//! required by the `wasm-dbms` engine.
11//!
12//! ## Provided Derive Macros
13//!
14//! - `Encode`: Automatically implements the `Encode` trait for structs.
15//! - `Table`: Automatically implements the `TableSchema` trait and associated types.
16//! - `DatabaseSchema`: Generates `DatabaseSchema<M>` trait dispatch and `register_tables`.
17//! - `CustomDataType`: Bridge user-defined types into the `Value` system.
18
19#![doc(html_playground_url = "https://play.rust-lang.org")]
20#![doc(
21 html_favicon_url = "https://raw.githubusercontent.com/veeso/wasm-dbms/main/assets/images/cargo/logo-128.png"
22)]
23#![doc(
24 html_logo_url = "https://raw.githubusercontent.com/veeso/wasm-dbms/main/assets/images/cargo/logo-512.png"
25)]
26
27use proc_macro::TokenStream;
28use syn::{DeriveInput, parse_macro_input};
29
30mod custom_data_type;
31mod database_schema;
32mod encode;
33mod table;
34mod utils;
35
36/// Automatically implements the `Encode` trait for a struct.
37///
38/// This derive macro generates two methods required by the `Encode` trait:
39///
40/// - `fn data_size() -> DataSize`
41/// Computes the static size of the encoded type.
42/// If all fields implement `Encode::data_size()` returning
43/// `DataSize::Fixed(n)`, then the type is also considered fixed-size.
44/// Otherwise, the type is `DataSize::Dynamic`.
45///
46/// - `fn size(&self) -> MSize`
47/// Computes the runtime-encoding size of the value by summing the
48/// sizes of all fields.
49///
50/// # What the macro generates
51///
52/// Given a struct like:
53///
54/// ```rust,ignore
55/// #[derive(Encode)]
56/// struct User {
57/// id: Uint32,
58/// name: Text,
59/// }
60/// ```
61///
62/// The macro expands into:
63///
64/// ```rust,ignore
65/// impl Encode for User {
66/// const DATA_SIZE: DataSize = DataSize::Dynamic; // or DataSize::Fixed(n) if applicable
67///
68/// fn size(&self) -> MSize {
69/// self.id.size() + self.name.size()
70/// }
71///
72/// fn encode(&'_ self) -> std::borrow::Cow<'_, [u8]> {
73/// let mut encoded = Vec::with_capacity(self.size() as usize);
74/// encoded.extend_from_slice(&self.id.encode());
75/// encoded.extend_from_slice(&self.name.encode());
76/// std::borrow::Cow::Owned(encoded)
77/// }
78///
79/// fn decode(data: std::borrow::Cow<[u8]>) -> ::wasm_dbms_api::prelude::MemoryResult<Self> {
80/// let mut offset = 0;
81/// let id = Uint32::decode(std::borrow::Borrowed(&data[offset..]))?;
82/// offset += id.size() as usize;
83/// let name = Text::decode(std::borrow::Borrowed(&data[offset..]))?;
84/// offset += name.size() as usize;
85/// Ok(Self { id, name })
86/// }
87/// }
88/// ```
89/// # Requirements
90///
91/// - Each field type must implement `Encode`.
92/// - Only works on `struct`s; enums and unions are not supported.
93/// - All field identifiers must be valid Rust identifiers (no tuple structs).
94///
95/// # Notes
96///
97/// - It is intended for internal use within the `wasm-dbms` DBMS memory
98/// system.
99///
100/// # Errors
101///
102/// The macro will fail to expand if:
103///
104/// - The struct has unnamed fields (tuple struct)
105/// - A field type does not implement `Encode`
106/// - The macro is applied to a non-struct item.
107///
108/// # Example
109///
110/// ```rust,ignore
111/// #[derive(Encode, Debug, PartialEq, Eq)]
112/// struct Position {
113/// x: Int32,
114/// y: Int32,
115/// }
116///
117/// let pos = Position { x: 10.into(), y: 20.into() };
118/// assert_eq!(Position::data_size(), DataSize::Fixed(8));
119/// assert_eq!(pos.size(), 8);
120/// let encoded = pos.encode();
121/// let decoded = Position::decode(encoded).unwrap();
122/// assert_eq!(pos, decoded);
123/// ```
124#[proc_macro_derive(Encode)]
125pub fn derive_encode(input: TokenStream) -> TokenStream {
126 let input = parse_macro_input!(input as DeriveInput);
127 self::encode::encode(input, None)
128 .expect("Failed to derive `Encode`")
129 .into()
130}
131
132/// Given a struct representing a database table, automatically implements
133/// the `TableSchema` trait with all the necessary types to work with the wasm-dbms engine.
134/// So given this struct:
135///
136/// ```rust,ignore
137/// #[derive(Table, Encode)]
138/// #[table = "posts"]
139/// struct Post {
140/// #[primary_key]
141/// id: Uint32,
142/// title: Text,
143/// content: Text,
144/// #[foreign_key(entity = "User", table = "users", column = "id")]
145/// author_id: Uint32,
146/// }
147/// ```
148///
149/// What we expect as output is:
150///
151/// - To implement the `TableSchema` trait for the struct as follows:
152///
153/// ```rust,ignore
154/// impl TableSchema for Post {
155/// type Insert = PostInsertRequest;
156/// type Record = PostRecord;
157/// type Update = PostUpdateRequest;
158/// type ForeignFetcher = PostForeignFetcher;
159///
160/// fn columns() -> &'static [ColumnDef] {
161/// &[
162/// ColumnDef {
163/// name: "id",
164/// data_type: DataTypeKind::Uint32,
165/// auto_increment: false,
166/// nullable: false,
167/// primary_key: true,
168/// unique: true,
169/// foreign_key: None,
170/// },
171/// ColumnDef {
172/// name: "title",
173/// data_type: DataTypeKind::Text,
174/// auto_increment: false,
175/// nullable: false,
176/// primary_key: false,
177/// unique: false,
178/// foreign_key: None,
179/// },
180/// ColumnDef {
181/// name: "content",
182/// data_type: DataTypeKind::Text,
183/// auto_increment: false,
184/// nullable: false,
185/// primary_key: false,
186/// unique: false,
187/// foreign_key: None,
188/// },
189/// ColumnDef {
190/// name: "user_id",
191/// data_type: DataTypeKind::Uint32,
192/// auto_increment: false,
193/// nullable: false,
194/// primary_key: false,
195/// unique: false,
196/// foreign_key: Some(ForeignKeyDef {
197/// local_column: "user_id",
198/// foreign_table: "users",
199/// foreign_column: "id",
200/// }),
201/// },
202/// ]
203/// }
204///
205/// fn table_name() -> &'static str {
206/// "posts"
207/// }
208///
209/// fn primary_key() -> &'static str {
210/// "id"
211/// }
212///
213/// fn to_values(self) -> Vec<(ColumnDef, Value)> {
214/// vec![
215/// (Self::columns()[0], Value::Uint32(self.id)),
216/// (Self::columns()[1], Value::Text(self.title)),
217/// (Self::columns()[2], Value::Text(self.content)),
218/// (Self::columns()[3], Value::Uint32(self.user_id)),
219/// ]
220/// }
221/// }
222/// ```
223///
224/// - Implement the associated `Record` type
225/// - Implement the associated `InsertRecord` type
226/// - Implement the associated `UpdateRecord` type
227/// - If has foreign keys, implement the associated `ForeignFetcher`
228///
229/// So for each struct deriving `Table`, we will generate the following type. Given `${StructName}`, we will generate:
230///
231/// - `${StructName}Record` - implementing `TableRecord`
232/// - `${StructName}InsertRequest` - implementing `InsertRecord`
233/// - `${StructName}UpdateRequest` - implementing `UpdateRecord`
234/// - `${StructName}ForeignFetcher` (only if foreign keys are present)
235///
236/// Also, we will implement the `TableSchema` trait for the struct itself and derive `Encode` for `${StructName}`.
237///
238/// ## Attributes
239///
240/// The `Table` derive macro supports the following attributes:
241///
242/// - `#[alignment = N]`: (optional) Specifies the alignment for the table records. Use only if you know what you are doing.
243/// - `#[autoincrement]`: Marks a field as auto-incrementing. The macro will generate code to automatically fill in values for this field during inserts. Auto-increment fields must be non-nullable and cannot be marked as `#[unique]`.
244/// - `#[candid]`: Marks the table as compatible with Candid serialization.
245/// - `#[custom_type = "TypeName"]`: Specifies a custom data type for the
246/// - `#[foreign_key(entity = "EntityName", table = "table_name", column = "column_name")]`: Defines a foreign key relationship.
247/// - `#[index]`: Marks a field to be indexed for faster queries.
248/// - `#[primary_key]`: Marks a field as the primary key of the table.
249/// - `#[sanitizer(SanitizerType)]`: Specifies a sanitize for the field.
250/// - `#[table = "table_name"]`: Specifies the name of the table in the database.
251/// - `#[unique]`: Marks a field to have a unique constraint.
252/// - `#[validate(ValidatorType)]`: Specifies a validator for the field.
253///
254#[proc_macro_derive(
255 Table,
256 attributes(
257 alignment,
258 autoincrement,
259 candid,
260 custom_type,
261 foreign_key,
262 index,
263 primary_key,
264 sanitizer,
265 table,
266 unique,
267 validate
268 )
269)]
270pub fn derive_table(input: TokenStream) -> TokenStream {
271 let input = parse_macro_input!(input as DeriveInput);
272 self::table::table(input)
273 .expect("failed to derive `Table`")
274 .into()
275}
276
277/// Derives the [`CustomDataType`] trait and an `impl From<T> for Value` conversion
278/// for a user-defined enum or struct.
279///
280/// The type must also derive [`Encode`] (for binary serialization) and implement
281/// [`Display`](std::fmt::Display) (for the cached display string in [`CustomValue`]).
282///
283/// # Required attribute
284///
285/// - `#[type_tag = "..."]`: A unique string identifier for this custom data type.
286///
287/// # What the macro generates
288///
289/// Given a type like:
290///
291/// ```rust,ignore
292/// #[derive(Encode, CustomDataType)]
293/// #[type_tag = "status"]
294/// enum Status { Active, Inactive }
295/// ```
296///
297/// The macro expands into:
298///
299/// ```rust,ignore
300/// impl CustomDataType for Status {
301/// const TYPE_TAG: &'static str = "status";
302/// }
303///
304/// impl From<Status> for Value {
305/// fn from(val: Status) -> Value {
306/// Value::Custom(CustomValue {
307/// type_tag: "status".to_string(),
308/// encoded: Encode::encode(&val).into_owned(),
309/// display: val.to_string(),
310/// })
311/// }
312/// }
313/// ```
314///
315/// # Note
316///
317/// The user must also provide `Display`, `Default`, and `DataType` implementations
318/// for the type. This macro only bridges the custom type to the `Value` system.
319#[proc_macro_derive(CustomDataType, attributes(type_tag))]
320pub fn derive_custom_data_type(input: TokenStream) -> TokenStream {
321 let input = parse_macro_input!(input as DeriveInput);
322 custom_data_type::custom_data_type(&input)
323 .unwrap_or_else(|e| e.to_compile_error())
324 .into()
325}
326
327/// Generates a [`DatabaseSchema`] implementation that dispatches generic
328/// DBMS operations to the correct concrete table types.
329///
330/// Given a struct annotated with `#[tables(User = "users", Post = "posts")]`,
331/// this macro produces:
332///
333/// - `impl<M: MemoryProvider> DatabaseSchema<M>` with match-arm dispatch
334/// for `select`, `insert`, `delete`, `update`, `validate_insert`,
335/// `validate_update`, and `referenced_tables`.
336/// - An inherent `register_tables` method that registers all tables in a
337/// [`DbmsContext`].
338///
339/// # Example
340///
341/// ```rust,ignore
342/// #[derive(DatabaseSchema)]
343/// #[tables(User = "users", Post = "posts")]
344/// pub struct MySchema;
345///
346/// // Register tables during initialization:
347/// MySchema::register_tables(&ctx)?;
348/// ```
349///
350/// # Requirements
351///
352/// - Each type in the `#[tables(...)]` attribute must implement
353/// [`TableSchema`].
354/// - The generated types (`UserInsertRequest`, `UserUpdateRequest`,
355/// `UserRecord`, etc.) must be in scope.
356#[proc_macro_derive(DatabaseSchema, attributes(tables))]
357pub fn derive_database_schema(input: TokenStream) -> TokenStream {
358 let input = parse_macro_input!(input as DeriveInput);
359 self::database_schema::database_schema(input)
360 .expect("failed to derive `DatabaseSchema`")
361 .into()
362}