macrodb/lib.rs
1//! # MacroDB
2//!
3//! This crate exports a macro that can be used to turn an appropriate Rust struct into an
4//! in-memory database that guarantees consistency and supports indices. The Rust struct needs
5//! to be written by hand, the macro only generates a safe interface for using the in-memory
6//! database.
7//!
8//! ## Example
9//!
10//! Here is a full example of building a user database. An error type is defined, that is used
11//! by the database methods to return information failures. A struct for the record type that is
12//! to be stored is defined (the User struct). Finally, a struct for the database is defined
13//! that contains two indices. The [table][macro@table] macro generates methods for inserting, updating
14//! and deleting rows.
15//!
16//! ```rust
17//! use std::collections::{BTreeMap, BTreeSet};
18//! use macrodb::table;
19//!
20//! /// Error type for database interactions.
21//! pub enum Error {
22//! UserIdExists,
23//! UserEmailExists,
24//! UserNotFound,
25//! }
26//!
27//! type UserId = u64;
28//!
29//! /// Record type for a user (a row in the database).
30//! #[derive(Clone)]
31//! pub struct User {
32//! id: UserId,
33//! name: String,
34//! email: String,
35//! }
36//!
37//! /// Database definition.
38//! pub struct Database {
39//! /// Users table.
40//! users: BTreeMap<UserId, User>,
41//! /// Unique index of users by email.
42//! user_by_email: BTreeMap<String, UserId>,
43//! }
44//!
45//! // The table macro will autogenerate the users_insert(), users_update() and users_delete()
46//! // methods.
47//! impl Database {
48//! table!(
49//! users: User,
50//! id: UserId,
51//! missing Error => Error::UserNotFound,
52//! primary users id => Error::UserIdExists,
53//! unique user_by_email email => Error::UserEmailExists
54//! );
55//! }
56//! ```
57//!
58//! See the documentation on [table](macro@table) for more information.
59#![macro_use]
60
61/// Re-expport of paste, which is used internally.
62pub use paste::paste;
63
64#[doc(hidden)]
65#[macro_export]
66macro_rules! table_next_id {
67 ($table:ident: $type:ty) => {
68 $crate::paste! {
69 pub fn [<$table _next_id>](&self) -> $type {
70 self.$table
71 .keys()
72 .max()
73 .map(|key| *key + 1)
74 .unwrap_or_default()
75 }
76 }
77 };
78}
79
80#[doc(hidden)]
81#[macro_export]
82macro_rules! table_insert {
83 ($table:ident: $type:ty, $pk:ident, $errty:ty) => {
84 $crate::paste! {
85 pub fn [<$table _insert>](&mut self, data: $type) -> Result<(), $errty> {
86 self.[<$table _insert_check>](&data)?;
87 self.[<$table _insert_indices>](&data);
88 self.$table.insert(data.$pk.clone(), data);
89 Ok(())
90 }
91 }
92 };
93}
94
95#[doc(hidden)]
96#[macro_export]
97macro_rules! table_insert_check {
98 ($self:expr, foreign, $table:ident, $data:ident, $expr:expr, $err:expr) => {
99 if $self.$table.get(&$expr).is_none() {
100 return Err($err);
101 }
102 };
103 ($self:expr, unique, $table:ident, $data:ident, $expr:expr, $err:expr) => {
104 if $self.$table.get(&$expr).is_some() {
105 return Err($err);
106 }
107 };
108 ($self:expr, primary, $table:ident, $data:ident, $expr:expr, $err:expr) => {
109 if $self.$table.get(&$expr).is_some() {
110 return Err($err);
111 }
112 };
113 ($self:expr, constraint, $table:ident, $data:ident, $expr:expr, $err:expr) => {
114 if let Err(error) = $self.$table($data) {
115 return Err(error);
116 }
117 };
118 ($self:expr, $other:ident, $table:ident, $data:ident, $expr:expr, $err:expr) => {};
119}
120
121#[doc(hidden)]
122#[macro_export]
123macro_rules! table_insert_checks {
124 ($table:ident: $type:ty, $errty:ty, $($itype:ident $name:ident $prop:tt => $err:expr),*) => {
125 $crate::paste! {
126 fn [<$table _insert_check>](&mut self, data: &$type) -> Result<(), $errty> {
127 $($crate::table_insert_check!(self, $itype, $name, data, $crate::table_prop!(data, $prop), $err);)*
128 Ok(())
129 }
130 }
131 }
132}
133
134#[doc(hidden)]
135#[macro_export]
136macro_rules! table_delete {
137 ($table:ident: $type:ty, $pk:ty, $errty:ty) => {
138 $crate::paste! {
139 pub fn [<$table _delete>](&mut self, id: $pk) -> Result<$type, $errty> {
140 let data = self.[<$table _delete_check>](id.clone())?;
141 self.[<$table _delete_indices>](&data);
142 self.$table.remove(&id);
143 Ok(data)
144 }
145 }
146 };
147}
148
149#[doc(hidden)]
150#[macro_export]
151macro_rules! table_insert_index {
152 ($self:expr, $pk:expr, unique, $name:ident, $prop:expr) => {
153 if $self.$name.insert($prop.clone(), $pk.clone()).is_some() {
154 panic!(concat!(stringify!($name), " index entry already existsted"));
155 }
156 };
157 ($self:expr, $pk:expr, index, $name:ident, $prop:expr) => {
158 if !$self
159 .$name
160 .entry($prop.clone())
161 .or_default()
162 .insert($pk.clone())
163 {
164 panic!(concat!(stringify!($name), " index already had new user"));
165 }
166 };
167 ($self:expr, $pk:expr, $other:ident, $name:ident, $prop:expr) => {};
168}
169
170#[doc(hidden)]
171#[macro_export]
172macro_rules! table_prop {
173 ($data:expr, $prop:ident) => {
174 $data.$prop
175 };
176 ($data:expr, ($($prop:ident),*)) => {
177 ($($crate::table_prop!(@inner, $data, $prop)),*)
178 };
179 (@inner, $data:expr, $prop:ident) => { $data.$prop.clone() };
180 ($data:expr, $prop:tt) => { $prop };
181}
182
183#[doc(hidden)]
184#[macro_export]
185macro_rules! table_insert_indices {
186 ($table:ident: $type:ty, $pk:ident, $($itype:ident $name:ident $prop:tt => $err:expr),*) => {
187 $crate::paste! {
188 fn [<$table _insert_indices>](&mut self, data: &$type) {
189 $($crate::table_insert_index!(self, data.$pk, $itype, $name, $crate::table_prop!(data, $prop));)*
190 }
191 }
192 }
193}
194
195#[doc(hidden)]
196#[macro_export]
197macro_rules! table_delete_index {
198 ($self:expr, $pk:expr, unique, $name:ident, $prop:expr) => {
199 match $self.$name.remove(&$prop) {
200 None => panic!(concat!(stringify!($name), " unique index missing item")),
201 Some(value) if value != $pk => {
202 panic!(concat!(stringify!($name), " unique index had wrong key"))
203 }
204 _ => {}
205 }
206 };
207 ($self:expr, $pk:expr, reverse, $name:ident, $prop:expr) => {
208 match $self.$name.remove(&$pk) {
209 Some(value) if !value.is_empty() => {
210 panic!(concat!(stringify!($name, " reverse index not empty")))
211 }
212 _ => {}
213 }
214 };
215 ($self:expr, $pk:expr, index, $name:ident, $prop:expr) => {
216 let values = $self
217 .$name
218 .get_mut(&$prop)
219 .expect(concat!(stringify!($name), " index missing"));
220 if !values.remove(&$pk) {
221 panic!(concat!(stringify!($name), " index already had new user"));
222 }
223 if values.is_empty() {
224 $self.$name.remove(&$prop);
225 }
226 };
227 ($self:expr, $pk:expr, $other:ident, $name:ident, $prop:expr) => {};
228}
229
230#[doc(hidden)]
231#[macro_export]
232macro_rules! table_delete_indices {
233 ($table:ident: $type:ty, $pk:ident, $($itype:ident $name:ident $prop:tt => $err:expr),*) => {
234 $crate::paste! {
235 fn [<$table _delete_indices>](&mut self, data: &$type) {
236 $($crate::table_delete_index!(self, data.$pk, $itype, $name, $crate::table_prop!(data, $prop));)*
237 }
238 }
239 }
240}
241
242#[doc(hidden)]
243#[macro_export]
244macro_rules! table_update_index {
245 ($self:expr, $pk:expr, index, $name:ident, $old:expr, $new:expr) => {
246 if $old != $new {
247 $crate::table_delete_index!($self, $pk, index, $name, $old);
248 $crate::table_insert_index!($self, $pk, index, $name, $new);
249 }
250 };
251 ($self:expr, $pk:expr, unique, $name:ident, $old:expr, $new:expr) => {
252 if $old != $new {
253 $crate::table_delete_index!($self, $pk, unique, $name, $old);
254 $crate::table_insert_index!($self, $pk, unique, $name, $new);
255 }
256 };
257 ($self:expr, $pk:expr, reverse, $name:ident, $old:expr, $new:expr) => {
258 if $old != $new {
259 $crate::table_delete_index!($self, $pk, reverse, $name, $old);
260 $crate::table_insert_index!($self, $pk, reverse, $name, $new);
261 }
262 };
263 ($self:expr, $pk:expr, $other:ident, $name:ident, $old:expr, $new:expr) => {};
264}
265
266#[doc(hidden)]
267#[macro_export]
268macro_rules! table_update_indices {
269 ($table:ident: $type:ty, $pk:ident, $($itype:ident $name:ident $prop:tt => $err:expr),*) => {
270 $crate::paste! {
271 fn [<$table _update_indices>](&mut self, old: &$type, new: &$type) {
272 $($crate::table_update_index!(self, old.$pk, $itype, $name, $crate::table_prop!(old, $prop), $crate::table_prop!(new, $prop));)*
273 }
274 }
275 }
276}
277
278#[doc(hidden)]
279#[macro_export]
280macro_rules! table_update_check {
281 ($self:expr, $pk:expr, unique, $name:ident, $data:ident, $old:expr, $new:expr, $err:expr) => {
282 if $old != $new {
283 $crate::table_insert_check!($self, unique, $name, $data, $new, $err);
284 }
285 };
286 ($self:expr, $pk:expr, foreign, $name:ident, $data:ident, $old:expr, $new:expr, $err:expr) => {
287 if $old != $new {
288 $crate::table_insert_check!($self, foreign, $name, $data, $new, $err);
289 }
290 };
291 ($self:expr, $pk:expr, constraint, $name:ident, $data:ident, $old:expr, $new:expr, $err:expr) => {
292 $crate::table_insert_check!($self, constraint, $name, $data, $new, $err);
293 };
294 ($self:expr, $pk:expr, $other:ident, $name:ident, $data:ident, $old:expr, $new:expr, $err:expr) => {};
295}
296
297#[doc(hidden)]
298#[macro_export]
299macro_rules! table_update_checks {
300 ($table:ident: $type:ty, $pk:ident, $errty:ty, $($itype:ident $name:ident $prop:tt => $err:expr),*) => {
301 $crate::paste! {
302 fn [<$table _update_check>](&mut self, old: &$type, new: &$type) -> Result<(), $errty> {
303 $($crate::table_update_check!(self, old.$pk, $itype, $name, new, $crate::table_prop!(old, $prop), $crate::table_prop!(new, $prop), $err);)*
304 Ok(())
305 }
306 }
307 }
308}
309
310#[doc(hidden)]
311#[macro_export]
312macro_rules! table_delete_check {
313 ($self:expr, $pk:expr, reverse, $name:ident, $prop:expr, $err:expr) => {
314 match $self.$name.get(&$pk) {
315 None => {}
316 Some(items) if items.is_empty() => {
317 panic!(concat!(stringify!($name), " has empty index"))
318 }
319 Some(_items) => return Err($err),
320 }
321 };
322 ($self:expr, $pk:expr, $other:ident, $name:ident, $prop:expr, $err:expr) => {};
323}
324
325#[doc(hidden)]
326#[macro_export]
327macro_rules! table_delete_checks {
328 ($table:ident: $type:ty, $pk:ident: $pkty:ty, $errty:ty, $missing:expr, $($itype:ident $name:ident $prop:tt => $err:expr),*) => {
329 $crate::paste! {
330 fn [<$table _delete_check>](&mut self, id: $pkty) -> Result<$type, $errty> {
331 let row = match self.$table.get(&id) {
332 Some(row) => row.clone(),
333 None => return Err($missing),
334 };
335
336 $($crate::table_delete_check!(self, row.$pk, $itype, $name, $crate::table_prop!(row, $prop), $err);)*
337
338 Ok(row)
339 }
340 }
341 };
342}
343
344#[doc(hidden)]
345#[macro_export]
346macro_rules! table_indices {
347 ($table:ident: $type:ty, $pk:ident: $pkty:ty, $errty:ty, $error:expr, $($itype:ident $name:ident $prop:tt => $err:expr),*) => {
348 $crate::table_insert_checks!($table: $type, $errty, $($itype $name $prop => $err),*);
349 $crate::table_update_checks!($table: $type, $pk, $errty, $($itype $name $prop => $err),*);
350 $crate::table_delete_checks!($table: $type, $pk: $pkty, $errty, $error, $($itype $name $prop => $err),*);
351 $crate::table_insert_indices!($table: $type, $pk, $($itype $name $prop => $err),*);
352 $crate::table_delete_indices!($table: $type, $pk, $($itype $name $prop => $err),*);
353 $crate::table_update_indices!($table: $type, $pk, $($itype $name $prop => $err),*);
354 }
355}
356
357#[doc(hidden)]
358#[macro_export]
359macro_rules! table_update {
360 ($table:ident: $type:ty, $pk:ident => $err:expr, $errty:ty) => {
361 $crate::paste! {
362 pub fn [<$table _update>](&mut self, new: $type) -> Result<$type, $errty> {
363 let old = match self.$table.get(&new.$pk) {
364 Some(value) => value.clone(),
365 None => return Err($err),
366 };
367 self.[<$table _update_check>](&old, &new)?;
368 self.[<$table _update_indices>](&old, &new);
369 self.$table.insert(new.$pk.clone(), new);
370 Ok(old)
371 }
372 }
373 };
374}
375
376/// # Table Macro
377///
378/// Generate database methods (insert, update and delete) for a table.
379/// This macro takes a definition of the database table and indices and generates helper
380/// methods used to safely insert, update and delete rows in the database.
381///
382/// ## Usage
383///
384/// In order to use the macro, you need an error type for your database,
385/// a row type per table (for example, a User struct for your users table), and a struct
386/// for the database itself.
387///
388/// ### Error Type
389///
390/// The error type is usually a simple enum. For every table, you need an error case for when a
391/// row is missing, as well as when it exists. Additionally, you need one error type per unique
392/// index.
393///
394/// Here is an example of the what the error type might look like:
395///
396/// ```rust
397/// pub enum Error {
398/// UserIdExists,
399/// UserEmailExists,
400/// UserNotFound,
401/// UserNameEmpty,
402/// GroupNameExists,
403/// GroupIdExists,
404/// GroupNotFound,
405/// GroupNotEmpty,
406/// GroupNameEmpty,
407/// }
408/// ```
409///
410/// ### Row Types
411///
412/// For every table, you need a row type. This can just be a regular Rust struct. It needs to
413/// derive [Clone][], and it needs to have some kind of primary key. The primary key uniquely
414/// identifies the row for update and deletion operations. Usually, an integer type is
415/// recommended here. For visual clarity, you can create a type alias for this field. We will
416/// refer to the row type as `RowType` and to the type of the primary key as `RowId`.
417///
418/// Here is an example of what *User* and *Group* row types might look like.
419///
420/// ```rust
421/// type UserId = u64;
422///
423/// #[derive(Clone)]
424/// pub struct User {
425/// id: UserId,
426/// name: String,
427/// email: String,
428/// group: GroupId,
429/// }
430///
431/// type GroupId = u64;
432///
433/// #[derive(Clone)]
434/// pub struct Group {
435/// id: GroupId,
436/// name: String,
437/// }
438/// ```
439///
440///
441/// ### Database Struct
442///
443/// The database struct must contain one map per table and one per index.
444/// The table maps must be of the shape `MapType<RowPrimaryKey, RowType>`. The type of map that is used does not matter, although common choices are [BTreeMap][std::collections::BTreeMap]
445/// and [HashMap][std::collections::HashMap].
446/// For every unique index, a map of the shape `MapType<IndexType,
447/// RowId>` must be added.
448/// For every index, a map of the shape `MapType<IndexType, Set<RowId>>`
449/// needs to be added.
450/// The set type that is used
451/// does not matter, although common choices are [BTreeSet][std::collections::BTreeSet]
452/// and [HashSet][std::collections::HashSet].
453/// The *IndexType* is the type of the field of the index. For instance, if
454/// the index is on a field of type [String][], that is the IndexType.
455///
456/// | Kind | Type |
457/// | --- | --- |
458/// | Table | `MapType<RowId, RowType>` |
459/// | Index | `MapType<IndexType, Set<RowId>>` |
460/// | Unique index | `MapType<IndexType, RowId>` |
461///
462/// For example, to define a database struct with two tables (*users* and *groups*),
463/// two unique indices (*user_by_email* and *group_by_name*), and one regular index
464/// (*users_by_group*), this struct definition could be used:
465///
466/// ```rust
467/// # type UserId = u64;
468/// # type GroupId = u64;
469/// # struct User;
470/// # struct Group;
471/// use std::collections::{BTreeMap, HashMap, BTreeSet, HashSet};
472///
473/// pub struct Database {
474/// /// Users table
475/// users: BTreeMap<UserId, User>,
476/// /// User by email unique index
477/// user_by_email: HashMap<String, UserId>,
478///
479/// /// Groups table
480/// groups: HashMap<GroupId, Group>,
481/// /// Users by group index
482/// users_by_group: BTreeMap<GroupId, BTreeSet<UserId>>,
483/// /// Group by name unique index
484/// group_by_name: BTreeMap<String, GroupId>,
485/// }
486/// ```
487///
488/// ## Syntax
489///
490/// The basic syntax of the macro looks like this:
491///
492/// ```rust,ignore
493/// table!(
494/// $table_name: RowType,
495/// $id_field: RowId,
496/// missing ErrorType => $missing_error,
497/// primary $table_name $id_field => $exists_error,
498/// <constraints...>
499/// <indices...>
500/// );
501/// ```
502///
503/// Here is an overview of what the various placeholders mean:
504///
505/// | Placeholder | Example | Explanation |
506/// | --- | --- | --- |
507/// | `$table_name` | `users` | Name of the table map in the database struct |
508/// | `RowType` | `User` | Name of the data type of the rows |
509/// | `RowId` | `UserId` | Name of the type of the primary keys for the rows |
510/// | `$id_field` | `id` | Name of the struct field of the Row type that contains the primary key |
511/// | `ErrorType` | `Error` | Name of the error type (enum) |
512/// | `$missing_error` | `Error::UserNotFound` | Error to throw when trying to delete a row that does not exists |
513/// | `$exists_error` | `Error::UserIdExists` | Error to throw when trying to insert a row that already exists |
514/// | `<constraints...>` | Definitions for the constraints (if any). |
515/// | `<indices...>` | | Definitions for the indices (explained in next section) |
516///
517/// ### Constraints
518///
519/// The syntax for constraints is the following:
520///
521/// ```rust,ignore
522/// constraint fn_name _ => (),
523/// ```
524///
525/// In order to define constraints, you need to declare a method on your database struct. This
526/// method should return `Result<(), ErrorType>`. For example, you can create a constraint that
527/// enforces that a user name should not be empty. The constraint method might look like this:
528///
529/// ```rust
530/// # enum Error { UserNameEmpty, }
531/// # struct User { name: String }
532/// # struct Database {}
533/// impl Database {
534/// fn name_not_empty(&self, user: &User) -> Result<(), Error> {
535/// if user.name.is_empty() {
536/// return Err(Error::UserNameEmpty);
537/// }
538///
539/// Ok(())
540/// }
541/// }
542/// ```
543///
544/// Defining the constraint in the macro then looks as follows:
545///
546/// ```text
547/// constraint name_not_empty _ => ()
548/// ```
549///
550/// ### Indices
551///
552/// The macro also needs to be told of the various indices. The syntax for indices looks like
553/// this:
554///
555/// ```text
556/// $type $map $field => $error
557/// ```
558///
559/// Here, `$type` refers to the type of index (can be `index`, `unique`, `foreign` or
560/// `reverse`). `$map` is the name of the map field in the database struct that represents this
561/// index. `$field` is the name of the field of the `RowType` that this index is on. The field can
562/// also be a compound key, by writing it as a tuple (for example `(first_name, last_name)`. Finally,
563/// `$error` is the error that is thrown when this index is violated. Here is an overview of the
564/// index types:
565///
566/// | Type | Example | Explanation |
567/// | --- | --- | --- |
568/// | Index | `index users_by_group group => ()` | Defines a simple index to look up rows based on their group. Does not need an error. |
569/// | Foreign | `foreign groups group => Error::GroupNotFound` | Defines a foreign key constraint which enforces that the `group` field point to an existing row in the `groups` table. |
570/// | Unique | `unique user_by_email email => Error::UserEmailExists` | Defines a unique index which uses the `user_by_email` map and enforces that no two users share the same email. |
571/// | Reverse | `reverse users_by_group id => Error::GroupHasUsers` | Declares a reverse dependency (on an index by another table) that prevents a group row being deleted if there are still users with that group. |
572///
573/// The result of this is that the macro generates insertion, update and deletion methods for
574/// every table. It uses the table map name as the prefix for those methods. For example,
575/// calling it on a table with the name *users* results in three methods being generated:
576///
577/// ```rust,ignore
578/// impl Database {
579/// /// Insert a User into the database, or return an error.
580/// pub fn users_insert(row: User) -> Result<(), Error>;
581///
582/// /// Update a User row (identified by the primary key), returning the old row, or return
583/// /// an error.
584/// pub fn users_update(row: User) -> Result<User, Error>;
585///
586/// /// Delete a User row (identified by the primary key), returning the row, or return an
587/// /// error.
588/// pub fn users_delete(id: UserId) -> Result<User, Error>;
589/// }
590/// ```
591///
592/// ## Example
593///
594/// Here is an example invocation of the macro on the Database struct with two tables (*users* and
595/// *groups*), including indices and foreign key constraints:
596///
597/// ```rust
598/// # use std::collections::{BTreeMap, HashMap, BTreeSet, HashSet};
599/// use macrodb::table;
600/// # pub enum Error {
601/// # UserIdExists,
602/// # UserEmailExists,
603/// # UserNotFound,
604/// # UserNameEmpty,
605/// # GroupNameExists,
606/// # GroupIdExists,
607/// # GroupNotFound,
608/// # GroupNotEmpty,
609/// # GroupNameEmpty,
610/// # }
611/// # type UserId = u64;
612/// # #[derive(Clone)]
613/// # pub struct User {
614/// # id: UserId,
615/// # name: String,
616/// # email: String,
617/// # group: GroupId,
618/// # }
619/// # type GroupId = u64;
620/// # #[derive(Clone)]
621/// # pub struct Group {
622/// # id: GroupId,
623/// # name: String,
624/// # }
625/// # pub struct Database {
626/// # users: BTreeMap<UserId, User>,
627/// # user_by_email: HashMap<String, UserId>,
628/// # groups: HashMap<GroupId, Group>,
629/// # users_by_group: BTreeMap<GroupId, BTreeSet<UserId>>,
630/// # group_by_name: BTreeMap<String, GroupId>,
631/// # }
632/// impl Database {
633/// fn user_name_not_empty(&self, user: &User) -> Result<(), Error> {
634/// if user.name.is_empty() {
635/// return Err(Error::UserNameEmpty);
636/// }
637///
638/// Ok(())
639/// }
640///
641/// fn group_name_not_empty(&self, group: &Group) -> Result<(), Error> {
642/// if group.name.is_empty() {
643/// return Err(Error::GroupNameEmpty);
644/// }
645///
646/// Ok(())
647/// }
648///
649/// table!(
650/// users: User,
651/// id: UserId,
652/// missing Error => Error::UserNotFound,
653/// primary users id => Error::UserIdExists,
654/// foreign groups group => Error::GroupNotFound,
655/// index users_by_group group => (),
656/// constraint user_name_not_empty _ => (),
657/// unique user_by_email email => Error::UserEmailExists
658/// );
659/// table!(
660/// groups: Group,
661/// id: GroupId,
662/// missing Error => Error::GroupNotFound,
663/// primary users id => Error::GroupIdExists,
664/// constraint group_name_not_empty _ => (),
665/// reverse users_by_group id => Error::GroupNotEmpty,
666/// unique group_by_name name => Error::GroupNameExists
667/// );
668/// }
669/// ```
670#[macro_export]
671macro_rules! table {
672 ($table:ident: $type:ty, $pk:ident: $pkty:ty, missing $errty:ty => $missing:expr, $($itype:ident $name:ident $prop:tt => $err:expr),*) => {
673 $crate::table_next_id!($table: $pkty);
674 $crate::table_indices!($table: $type, $pk: $pkty, $errty, $missing, $($itype $name $prop => $err),*);
675 $crate::table_delete!($table: $type, $pkty, $errty);
676 $crate::table_insert!($table: $type, $pk, $errty);
677 $crate::table_update!($table: $type, $pk => $missing, $errty);
678 };
679 ($table:ident: $type:ty, $pk:ident: $pkty:ty, noautokey, missing $errty:ty => $missing:expr, $($itype:ident $name:ident $prop:tt => $err:expr),*) => {
680 $crate::table_indices!($table: $type, $pk: $pkty, $errty, $missing, $($itype $name $prop => $err),*);
681 $crate::table_delete!($table: $type, $pkty, $errty);
682 $crate::table_insert!($table: $type, $pk, $errty);
683 $crate::table_update!($table: $type, $pk => $missing, $errty);
684 }
685}