macro_rules! create_either {
    ( $name:ident< $($t:ident),* >) => {
        #[derive(Debug, Clone)]
        pub enum $name< $( $t ),* > {
            $( $t($t) ),*
        }
        impl< $( $t ),* > $crate::wal::Wal for $name< $( $t ),* >
        where
            $( $t: $crate::wal::Wal ),*
        {
            fn limit(&mut self, size: i64) {
                match self {
                    $( $name::$t(inner) => inner.limit(size) ),*
                }
            }
            fn begin_read_txn(&mut self) -> super::Result<bool> {
                match self {
                    $( $name::$t(inner) => inner.begin_read_txn() ),*
                }
            }
            fn end_read_txn(&mut self) {
                match self {
                    $( $name::$t(inner) => inner.end_read_txn() ),*
                }
            }
            fn find_frame(&mut self, page_no: std::num::NonZeroU32) -> super::Result<Option<std::num::NonZeroU32>> {
                match self {
                    $( $name::$t(inner) => inner.find_frame(page_no) ),*
                }
            }
            fn read_frame(&mut self, frame_no: std::num::NonZeroU32, buffer: &mut [u8]) -> super::Result<()> {
                match self {
                    $( $name::$t(inner) => inner.read_frame(frame_no, buffer) ),*
                }
            }
            fn db_size(&self) -> u32 {
                match self {
                    $( $name::$t(inner) => inner.db_size() ),*
                }
            }
            fn begin_write_txn(&mut self) -> super::Result<()> {
                match self {
                    $( $name::$t(inner) => inner.begin_write_txn() ),*
                }
            }
            fn end_write_txn(&mut self) -> super::Result<()> {
                match self {
                    $( $name::$t(inner) => inner.end_write_txn() ),*
                }
            }
            fn undo<U: super::UndoHandler>(&mut self, handler: Option<&mut U>) -> super::Result<()> {
                match self {
                    $( $name::$t(inner) => inner.undo(handler) ),*
                }
            }
            fn savepoint(&mut self, rollback_data: &mut [u32]) {
                match self {
                    $( $name::$t(inner) => inner.savepoint(rollback_data) ),*
                }
            }
            fn savepoint_undo(&mut self, rollback_data: &mut [u32]) -> super::Result<()> {
                match self {
                    $( $name::$t(inner) => inner.savepoint_undo(rollback_data) ),*
                }
            }
            fn insert_frames(
                &mut self,
                page_size: std::ffi::c_int,
                page_headers: &mut super::PageHeaders,
                size_after: u32,
                is_commit: bool,
                sync_flags: std::ffi::c_int,
            ) -> super::Result<usize> {
                match self {
                    $( $name::$t(inner) => inner.insert_frames(page_size, page_headers, size_after, is_commit, sync_flags) ),*
                }
            }
            fn checkpoint(
                &mut self,
                db: &mut super::Sqlite3Db,
                mode: super::CheckpointMode,
                busy_handler: Option<&mut dyn super::BusyHandler>,
                sync_flags: u32,
                buf: &mut [u8],
                checkpoint_cb: Option<&mut dyn super::CheckpointCallback>,
                in_wal: Option<&mut i32>,
                backfilled: Option<&mut i32>,
            ) -> super::Result<()> {
                match self {
                    $( $name::$t(inner) => inner.checkpoint(db, mode, busy_handler, sync_flags, buf, checkpoint_cb, in_wal, backfilled) ),*
                }
            }
            fn exclusive_mode(&mut self, op: std::ffi::c_int) -> super::Result<()> {
                match self {
                    $( $name::$t(inner) => inner.exclusive_mode(op) ),*
                }
            }
            fn uses_heap_memory(&self) -> bool {
                match self {
                    $( $name::$t(inner) => inner.uses_heap_memory() ),*
                }
            }
            fn set_db(&mut self, db: &mut super::Sqlite3Db) {
                match self {
                    $( $name::$t(inner) => inner.set_db(db) ),*
                }
            }
            fn callback(&self) -> i32 {
                match self {
                    $( $name::$t(inner) => inner.callback() ),*
                }
            }
            fn frames_in_wal(&self) -> u32 {
                match self {
                    $( $name::$t(inner) => inner.frames_in_wal() ),*
                }
            }
        }
        impl< $( $t ),* > $crate::wal::WalManager for $name< $( $t ),* >
        where
            $( $t: $crate::wal::WalManager ),*
        {
            type Wal = $name< $( $t::Wal ),* >;
            fn use_shared_memory(&self) -> bool {
                match self {
                    $( $name::$t(inner) => inner.use_shared_memory() ),*
                }
            }
            fn open(
                &self,
                vfs: &mut super::Vfs,
                file: &mut super::Sqlite3File,
                no_shm_mode: std::ffi::c_int,
                max_log_size: i64,
                db_path: &std::ffi::CStr,
            ) -> super::Result<Self::Wal> {
                match self {
                    $( $name::$t(inner) => inner.open(vfs, file, no_shm_mode, max_log_size, db_path).map($name::$t) ),*
                }
            }
            fn close(
                &self,
                wal: &mut Self::Wal,
                db: &mut super::Sqlite3Db,
                sync_flags: std::ffi::c_int,
                scratch: Option<&mut [u8]>,
            ) -> super::Result<()> {
                match (self, wal) {
                    $(
                        ($name::$t(inner), $name::$t(wal)) => inner.close(wal, db, sync_flags, scratch),
                    )*
                    _ => unreachable!(),
                }
            }
            fn destroy_log(&self, vfs: &mut super::Vfs, db_path: &std::ffi::CStr) -> super::Result<()> {
                match self {
                    $( $name::$t(inner) => inner.destroy_log(vfs, db_path) ),*
                }
            }
            fn log_exists(&self, vfs: &mut super::Vfs, db_path: &std::ffi::CStr) -> super::Result<bool> {
                match self {
                    $( $name::$t(inner) => inner.log_exists(vfs, db_path) ),*
                }
            }
            fn destroy(self)
            where
                Self: Sized,
            {
                match self {
                    $( $name::$t(inner) => inner.destroy() ),*
                }
            }
        }
    };
}
create_either!(Either<A, B>);
create_either!(Either3<A, B, C>);
create_either!(Either4<A, B, C, D>);