2 Revize 64228785a9 ... f0d6504eb9

Autor SHA1 Zpráva Datum
  IVogel f0d6504eb9 Changed version před 2 roky
  IVogel 4ae88dfb07 Implemented more methods. před 2 roky
6 změnil soubory, kde provedl 343 přidání a 79 odebrání
  1. 1 1
      Cargo.toml
  2. 100 6
      src/ldb.rs
  3. 1 17
      src/lib.rs
  4. 70 6
      src/ltree.rs
  5. 56 49
      src/lua_struct.rs
  6. 115 0
      src/macros.rs

+ 1 - 1
Cargo.toml

@@ -1,6 +1,6 @@
 [package]
 name = "lsled"
-version = "0.6.6"
+version = "0.6.7"
 edition = "2021"
 
 [lib]

+ 100 - 6
src/ldb.rs

@@ -5,7 +5,8 @@ use lua_shared as lua;
 use lua_shared::lua_State;
 
 use crate::ltree::LTree;
-use crate::{check_slice, insert_function, lua_struct};
+use crate::lua_struct::StructError;
+use crate::{check_slice, insert_function, lua_struct, tree_get_key, tree_get_no_arg};
 
 #[derive(Debug, Clone)]
 pub struct LDb(pub sled::Db);
@@ -56,18 +57,31 @@ impl LDb {
             let this = &mut *lua::Lcheckudata(state, 1, lua::cstr!("csldb")).cast::<Self>();
             if let Some(ivec) = this.0.get(check_slice!(state, 2))? {
                 lua::pushlstring(state, ivec.as_ptr(), ivec.len());
+                Ok(1)
             } else {
-                lua::pushnil(state)
+                Ok(0)
             }
-            Ok(1)
         }
     }
 
     fn lm_get_struct(state: lua_State) -> Result<i32, Box<dyn std::error::Error>> {
         unsafe {
             let this = &mut *lua::Lcheckudata(state, 1, lua::cstr!("csldb")).cast::<Self>();
-            if let Some(ivec) = this.0.get(check_slice!(state, 2))? {
-                Ok(lua_struct::unpack(state, check_slice!(state, 3), &ivec)?)
+            let key = check_slice!(state, 2);
+            let fmt = check_slice!(state, 3);
+            if let Some(ivec) = this.0.get(key)? {
+                match lua_struct::unpack(state, fmt, &ivec) {
+                    Ok(args) => Ok(args),
+                    Err(e) => {
+                        drop(ivec);
+                        match e {
+                            StructError::Error(e) => lua::Lerror(state, e),
+                            StructError::ArgError(arg, e) => lua::Largerror(state, arg, e),
+                            StructError::InvalidFormatOption(e, opt) => lua::Lerror(state, e, opt),
+                            StructError::IOError(e) => return Err(e)?,
+                        }
+                    }
+                }
             } else {
                 lua::pushnil(state);
                 Ok(1)
@@ -87,7 +101,14 @@ impl LDb {
         unsafe {
             let this = &mut *lua::Lcheckudata(state, 1, lua::cstr!("csldb")).cast::<Self>();
             let key = check_slice!(state, 2);
-            let value = lua_struct::pack(state, check_slice!(state, 3), 4)?;
+            let fmt = check_slice!(state, 3);
+            let value = match lua_struct::pack(state, fmt, 4) {
+                Ok(result) => result,
+                Err(StructError::Error(e)) => lua::Lerror(state, e),
+                Err(StructError::ArgError(arg, e)) => lua::Largerror(state, arg, e),
+                Err(StructError::InvalidFormatOption(e, opt)) => lua::Lerror(state, e, opt),
+                Err(StructError::IOError(e)) => return Err(e)?,
+            };
             this.insert(key, value)?;
             Ok(0)
         }
@@ -169,6 +190,33 @@ impl LDb {
         }
     }
 
+    fn lm_drop_tree(state: lua_State) -> Result<i32, Box<dyn std::error::Error>> {
+        unsafe {
+            let this = &mut *lua::Lcheckudata(state, 1, lua::cstr!("csldb")).cast::<Self>();
+            lua::pushboolean(
+                state,
+                this.drop_tree(std::str::from_utf8_unchecked(check_slice!(state, 2)))? as _,
+            );
+            Ok(1)
+        }
+    }
+
+    fn lm_was_recovered(state: lua_State) -> Result<i32, Box<dyn std::error::Error>> {
+        unsafe {
+            let this = &mut *lua::Lcheckudata(state, 1, lua::cstr!("csldb")).cast::<Self>();
+            lua::pushboolean(state, this.0.was_recovered() as _);
+            Ok(1)
+        }
+    }
+
+    fn lm_size_on_disk(state: lua_State) -> Result<i32, Box<dyn std::error::Error>> {
+        unsafe {
+            let this = &mut *lua::Lcheckudata(state, 1, lua::cstr!("csldb")).cast::<Self>();
+            lua::pushinteger(state, this.size_on_disk()? as _);
+            Ok(1)
+        }
+    }
+
     fn lm_generate_id(state: lua_State) -> Result<i32, Box<dyn std::error::Error>> {
         unsafe {
             let this = &mut *lua::Lcheckudata(state, 1, lua::cstr!("csldb")).cast::<Self>();
@@ -212,6 +260,34 @@ impl LDb {
         }
     }
 
+    fn lm_flush(state: lua_State) -> Result<i32, Box<dyn std::error::Error>> {
+        unsafe {
+            let this = &mut *lua::Lcheckudata(state, 1, lua::cstr!("csldb")).cast::<Self>();
+            lua::pushinteger(state, this.flush()? as _);
+            Ok(1)
+        }
+    }
+
+    fn lm_checksum(state: lua_State) -> Result<i32, Box<dyn std::error::Error>> {
+        unsafe {
+            let this = &mut *lua::Lcheckudata(state, 1, lua::cstr!("csldb")).cast::<Self>();
+            lua::pushinteger(state, this.checksum()? as _);
+            Ok(1)
+        }
+    }
+
+    fn lm_contains_key(state: lua_State) -> Result<i32, Box<dyn std::error::Error>> {
+        unsafe {
+            let this = &mut *lua::Lcheckudata(state, 1, lua::cstr!("csldb")).cast::<Self>();
+            let key = check_slice!(state, 2);
+            lua::pushboolean(state, this.contains_key(key)? as _);
+            Ok(1)
+        }
+    }
+
+    tree_get_key!(get_lt get_gt, "csldb");
+    tree_get_no_arg!(first last pop_max pop_min, "csldb");
+
     fn __gc(state: lua_State) -> Result<i32, Box<dyn std::error::Error>> {
         unsafe {
             let _ = lua::Lcheckudata(state, 1, lua::cstr!("csldb"))
@@ -241,6 +317,24 @@ impl LDb {
                 insert_function!(state, "GenerateID", Self::lm_generate_id);
                 insert_function!(state, "Export", Self::lm_export);
                 insert_function!(state, "Import", Self::lm_import);
+                insert_function!(state, "DropTree", Self::lm_drop_tree);
+                insert_function!(state, "WasRecovered", Self::lm_was_recovered);
+                insert_function!(state, "SizeOnDisk", Self::lm_size_on_disk);
+                insert_function!(state, "Flush", Self::lm_flush);
+                insert_function!(state, "Checksum", Self::lm_checksum);
+                insert_function!(state, "ContainsKey", Self::lm_contains_key);
+                insert_function!(state, "GetLT", Self::lm_get_lt);
+                insert_function!(state, "GetLTStruct", Self::lm_get_lt_struct);
+                insert_function!(state, "GetGT", Self::lm_get_gt);
+                insert_function!(state, "GetGTStruct", Self::lm_get_gt_struct);
+                insert_function!(state, "First", Self::lm_first);
+                insert_function!(state, "FirstStruct", Self::lm_first_struct);
+                insert_function!(state, "Last", Self::lm_last);
+                insert_function!(state, "LastStruct", Self::lm_last_struct);
+                insert_function!(state, "PopMax", Self::lm_pop_max);
+                insert_function!(state, "PopMaxStruct", Self::lm_pop_max_struct);
+                insert_function!(state, "PopMin", Self::lm_pop_min);
+                insert_function!(state, "PopMinStruct", Self::lm_pop_min_struct);
             }
         }
     }

+ 1 - 17
src/lib.rs

@@ -9,23 +9,7 @@ mod buffer;
 mod ldb;
 mod ltree;
 mod lua_struct;
-
-#[macro_export]
-macro_rules! check_slice {
-    ($state:ident, $index:expr) => {{
-        let mut len = 0;
-        let str_ptr = lua_shared::Lchecklstring($state, $index, &mut len);
-        std::slice::from_raw_parts(str_ptr, len)
-    }};
-}
-
-#[macro_export]
-macro_rules! insert_function {
-    ($state:ident, $name:expr, $func:expr) => {
-        lua_shared::pushfunction($state, $func);
-        lua_shared::setfield($state, -2, lua::cstr!($name));
-    };
-}
+mod macros;
 
 #[no_mangle]
 unsafe extern "C" fn gmod13_open(state: lua_State) -> i32 {

+ 70 - 6
src/ltree.rs

@@ -4,7 +4,8 @@ use std::ptr::null;
 use lua_shared as lua;
 use lua_shared::lua_State;
 
-use crate::{check_slice, insert_function, lua_struct};
+use crate::lua_struct::StructError;
+use crate::{check_slice, insert_function, lua_struct, tree_get_key, tree_get_no_arg};
 
 #[derive(Debug, Clone)]
 pub struct LTree(pub sled::Tree);
@@ -53,9 +54,22 @@ impl LTree {
 
     fn lm_get_struct(state: lua_State) -> Result<i32, Box<dyn std::error::Error>> {
         unsafe {
-            let this = &mut *lua::Lcheckudata(state, 1, lua::cstr!("csldb")).cast::<Self>();
-            if let Some(ivec) = this.0.get(check_slice!(state, 2))? {
-                Ok(lua_struct::unpack(state, check_slice!(state, 3), &ivec)?)
+            let this = &mut *lua::Lcheckudata(state, 1, lua::cstr!("cslt")).cast::<Self>();
+            let key = check_slice!(state, 2);
+            let fmt = check_slice!(state, 3);
+            if let Some(ivec) = this.0.get(key)? {
+                match lua_struct::unpack(state, fmt, &ivec) {
+                    Ok(args) => Ok(args),
+                    Err(e) => {
+                        drop(ivec);
+                        match e {
+                            StructError::Error(e) => lua::Lerror(state, e),
+                            StructError::ArgError(arg, e) => lua::Largerror(state, arg, e),
+                            StructError::InvalidFormatOption(e, opt) => lua::Lerror(state, e, opt),
+                            StructError::IOError(e) => return Err(e)?,
+                        }
+                    }
+                }
             } else {
                 lua::pushnil(state);
                 Ok(1)
@@ -73,9 +87,16 @@ impl LTree {
 
     fn lm_insert_struct(state: lua_State) -> Result<i32, Box<dyn std::error::Error>> {
         unsafe {
-            let this = &mut *lua::Lcheckudata(state, 1, lua::cstr!("csldb")).cast::<Self>();
+            let this = &mut *lua::Lcheckudata(state, 1, lua::cstr!("cslt")).cast::<Self>();
             let key = check_slice!(state, 2);
-            let value = lua_struct::pack(state, check_slice!(state, 3), 4)?;
+            let fmt = check_slice!(state, 3);
+            let value = match lua_struct::pack(state, fmt, 4) {
+                Ok(result) => result,
+                Err(StructError::Error(e)) => lua::Lerror(state, e),
+                Err(StructError::ArgError(arg, e)) => lua::Largerror(state, arg, e),
+                Err(StructError::InvalidFormatOption(e, opt)) => lua::Lerror(state, e, opt),
+                Err(StructError::IOError(e)) => return Err(e)?,
+            };
             this.insert(key, value)?;
             Ok(0)
         }
@@ -129,6 +150,34 @@ impl LTree {
         }
     }
 
+    fn lm_flush(state: lua_State) -> Result<i32, Box<dyn std::error::Error>> {
+        unsafe {
+            let this = &mut *lua::Lcheckudata(state, 1, lua::cstr!("cslt")).cast::<Self>();
+            lua::pushinteger(state, this.flush()? as _);
+            Ok(1)
+        }
+    }
+
+    fn lm_checksum(state: lua_State) -> Result<i32, Box<dyn std::error::Error>> {
+        unsafe {
+            let this = &mut *lua::Lcheckudata(state, 1, lua::cstr!("cslt")).cast::<Self>();
+            lua::pushinteger(state, this.checksum()? as _);
+            Ok(1)
+        }
+    }
+
+    fn lm_contains_key(state: lua_State) -> Result<i32, Box<dyn std::error::Error>> {
+        unsafe {
+            let this = &mut *lua::Lcheckudata(state, 1, lua::cstr!("cslt")).cast::<Self>();
+            let key = check_slice!(state, 2);
+            lua::pushboolean(state, this.contains_key(key)? as _);
+            Ok(1)
+        }
+    }
+
+    tree_get_key!(get_lt get_gt, "cslt");
+    tree_get_no_arg!(first last pop_max pop_min, "cslt");
+
     fn __gc(state: lua_State) -> Result<i32, Box<dyn std::error::Error>> {
         unsafe {
             let _ = lua::Lcheckudata(state, 1, lua::cstr!("cslt"))
@@ -153,6 +202,21 @@ impl LTree {
                 insert_function!(state, "Remove", Self::lm_remove);
                 insert_function!(state, "Range", Self::lm_range);
                 insert_function!(state, "ScanPrefix", Self::lm_scan_prefix);
+                insert_function!(state, "Flush", Self::lm_flush);
+                insert_function!(state, "Checksum", Self::lm_checksum);
+                insert_function!(state, "ContainsKey", Self::lm_contains_key);
+                insert_function!(state, "GetLT", Self::lm_get_lt);
+                insert_function!(state, "GetLTStruct", Self::lm_get_lt_struct);
+                insert_function!(state, "GetGT", Self::lm_get_gt);
+                insert_function!(state, "GetGTStruct", Self::lm_get_gt_struct);
+                insert_function!(state, "First", Self::lm_first);
+                insert_function!(state, "FirstStruct", Self::lm_first_struct);
+                insert_function!(state, "Last", Self::lm_last);
+                insert_function!(state, "LastStruct", Self::lm_last_struct);
+                insert_function!(state, "PopMax", Self::lm_pop_max);
+                insert_function!(state, "PopMaxStruct", Self::lm_pop_max_struct);
+                insert_function!(state, "PopMin", Self::lm_pop_min);
+                insert_function!(state, "PopMinStruct", Self::lm_pop_min_struct);
             }
         }
     }

+ 56 - 49
src/lua_struct.rs

@@ -1,5 +1,5 @@
 use std::ffi::c_void;
-use std::io::Cursor;
+use std::io::{self, Cursor};
 use std::io::{Read, Seek, Write};
 use std::os::raw::c_uint;
 
@@ -53,71 +53,74 @@ unsafe fn read_number(state: &mut ReaderState) -> Option<usize> {
     Some(result)
 }
 
-unsafe fn get_option(state: &mut ReaderState) -> Option<(KOption, usize)> {
+#[derive(Debug)]
+pub enum StructError {
+    Error(*const u8),
+    ArgError(i32, *const u8),
+    InvalidFormatOption(*const u8, c_uint),
+    IOError(io::Error),
+}
+
+impl From<std::io::Error> for StructError {
+    fn from(e: std::io::Error) -> Self {
+        StructError::IOError(e)
+    }
+}
+
+unsafe fn get_option(state: &mut ReaderState) -> Result<Option<(KOption, usize)>, StructError> {
     if state.fmt.len() == 0 {
-        return None;
+        return Ok(None);
     }
     let (opt, rest) = state.fmt.split_at(1);
     state.fmt = rest;
     match opt[0] {
-        b'b' => Some((KOption::I8, std::mem::size_of::<i8>())),
-        b'B' => Some((KOption::U8, std::mem::size_of::<u8>())),
-        b'h' => Some((KOption::I16, std::mem::size_of::<i16>())),
-        b'H' => Some((KOption::U16, std::mem::size_of::<u16>())),
-        b'l' => Some((KOption::I32, std::mem::size_of::<i32>())),
-        b'L' => Some((KOption::U32, std::mem::size_of::<u32>())),
-        b'T' => Some((KOption::Usize, std::mem::size_of::<usize>())),
-        b'f' => Some((KOption::Float, std::mem::size_of::<f32>())),
-        b'd' => Some((KOption::Double, std::mem::size_of::<f64>())),
-        b'n' => Some((KOption::Double, std::mem::size_of::<f64>())),
-        b's' => Some((KOption::String, std::mem::size_of::<u16>())),
+        b'b' => Ok(Some((KOption::I8, std::mem::size_of::<i8>()))),
+        b'B' => Ok(Some((KOption::U8, std::mem::size_of::<u8>()))),
+        b'h' => Ok(Some((KOption::I16, std::mem::size_of::<i16>()))),
+        b'H' => Ok(Some((KOption::U16, std::mem::size_of::<u16>()))),
+        b'l' => Ok(Some((KOption::I32, std::mem::size_of::<i32>()))),
+        b'L' => Ok(Some((KOption::U32, std::mem::size_of::<u32>()))),
+        b'T' => Ok(Some((KOption::Usize, std::mem::size_of::<usize>()))),
+        b'f' => Ok(Some((KOption::Float, std::mem::size_of::<f32>()))),
+        b'd' => Ok(Some((KOption::Double, std::mem::size_of::<f64>()))),
+        b'n' => Ok(Some((KOption::Double, std::mem::size_of::<f64>()))),
+        b's' => Ok(Some((KOption::String, std::mem::size_of::<u16>()))),
         b'c' => match read_number(state) {
-            Some(len) => Some((KOption::Char, len)),
-            None => {
-                lua::Lerror(
-                    state.state,
-                    lua::cstr!("missing size for format option 'c'"),
-                );
-            }
+            Some(len) => Ok(Some((KOption::Char, len))),
+            None => Err(StructError::Error(lua::cstr!(
+                "missing size for format option 'c'"
+            ))),
         },
-        b' ' => Some((KOption::NOP, 0)),
+        b' ' => Ok(Some((KOption::NOP, 0))),
         b'<' => {
             state.endianness = Endianness::Little;
-            Some((KOption::NOP, 0))
+            Ok(Some((KOption::NOP, 0)))
         }
         b'>' => {
             state.endianness = Endianness::Big;
-            Some((KOption::NOP, 0))
+            Ok(Some((KOption::NOP, 0)))
         }
         b'=' => {
             state.endianness = Endianness::Native;
-            Some((KOption::NOP, 0))
-        }
-        token @ _ => {
-            lua::Lerror(
-                state.state,
-                lua::cstr!("invalid format option '%c'"),
-                token as c_uint,
-            );
+            Ok(Some((KOption::NOP, 0)))
         }
+        token @ _ => Err(StructError::InvalidFormatOption(
+            lua::cstr!("invalid format option '%c'"),
+            token as c_uint,
+        )),
     }
 }
 
 // I don't fucking care. luaL_Buffer is allocated on the stack.
+// At the same time, this buffer is two times bigger than lua's string buffer.
 static mut STRING_BUFFER: [u8; 65536] = [0; 65536];
 
 macro_rules! pack_number {
     ($state:ident, $buffer:ident, $value:tt) => {
         match $state.endianness {
-            Endianness::Little => {
-                $buffer.write(&$value.to_le_bytes())?;
-            }
-            Endianness::Big => {
-                $buffer.write(&$value.to_be_bytes())?;
-            }
-            Endianness::Native => {
-                $buffer.write(&$value.to_ne_bytes())?;
-            }
+            Endianness::Little => $buffer.write(&$value.to_le_bytes())?,
+            Endianness::Big => $buffer.write(&$value.to_be_bytes())?,
+            Endianness::Native => $buffer.write(&$value.to_ne_bytes())?,
         }
     };
 }
@@ -134,7 +137,7 @@ macro_rules! unpack_number {
     }};
 }
 
-pub fn pack(state: lua_State, fmt: &[u8], start: i32) -> Result<&'static [u8], std::io::Error> {
+pub fn pack(state: lua_State, fmt: &[u8], start: i32) -> Result<&'static [u8], StructError> {
     unsafe {
         let mut arg = start;
         let mut reader_state = ReaderState {
@@ -143,12 +146,12 @@ pub fn pack(state: lua_State, fmt: &[u8], start: i32) -> Result<&'static [u8], s
             fmt: fmt,
         };
         let mut buffer = Cursor::new(&mut STRING_BUFFER[..]);
-        while let Some((option, size)) = get_option(&mut reader_state) {
+        while let Some((option, size)) = get_option(&mut reader_state)? {
             if let KOption::NOP = option {
                 continue;
             }
             if STRING_BUFFER.len() - (buffer.seek(std::io::SeekFrom::Current(0))? as usize) < size {
-                lua::Lerror(state, lua::cstr!("buffer overflow"));
+                return Err(StructError::Error(lua::cstr!("buffer overflow")));
             }
             match option {
                 KOption::I8 => {
@@ -204,7 +207,10 @@ pub fn pack(state: lua_State, fmt: &[u8], start: i32) -> Result<&'static [u8], s
                         || ((buffer.seek(std::io::SeekFrom::Current(0))? as usize) + size
                             > STRING_BUFFER.len())
                     {
-                        lua::Largerror(state, arg, lua::cstr!("string won't fit in the buffer"));
+                        return Err(StructError::ArgError(
+                            arg,
+                            lua::cstr!("string won't fit in the buffer"),
+                        ));
                     }
                     pack_number!(reader_state, buffer, (str.len() as u16));
                     buffer.write(str)?;
@@ -218,7 +224,7 @@ pub fn pack(state: lua_State, fmt: &[u8], start: i32) -> Result<&'static [u8], s
     }
 }
 
-pub fn unpack(state: lua_State, fmt: &[u8], data: &[u8]) -> Result<i32, std::io::Error> {
+pub fn unpack(state: lua_State, fmt: &[u8], data: &[u8]) -> Result<i32, StructError> {
     unsafe {
         let mut nrets = 0;
         let mut reader_state = ReaderState {
@@ -227,12 +233,12 @@ pub fn unpack(state: lua_State, fmt: &[u8], data: &[u8]) -> Result<i32, std::io:
             fmt: fmt,
         };
         let mut buffer = Cursor::new(data);
-        while let Some((option, size)) = get_option(&mut reader_state) {
+        while let Some((option, size)) = get_option(&mut reader_state)? {
             if let KOption::NOP = option {
                 continue;
             }
             if data.len() - (buffer.seek(std::io::SeekFrom::Current(0))? as usize) < size {
-                lua::Lerror(state, lua::cstr!("data string too short"));
+                return Err(StructError::Error(lua::cstr!("data string too short")));
             }
             nrets += 1;
             match option {
@@ -280,8 +286,9 @@ pub fn unpack(state: lua_State, fmt: &[u8], data: &[u8]) -> Result<i32, std::io:
                     let value = unpack_number!(reader_state, buffer, u16);
                     let offset = buffer.seek(std::io::SeekFrom::Current(0))? as usize;
                     if offset + value as usize > data.len() {
-                        lua::Lerror(state, lua::cstr!("data string too short"));
+                        return Err(StructError::Error(lua::cstr!("data string too short")));
                     }
+                    buffer.seek(std::io::SeekFrom::Current(value as _))?;
                     lua::pushlstring(state, data.as_ptr().add(offset), value as _);
                 }
                 KOption::NOP => {}

+ 115 - 0
src/macros.rs

@@ -0,0 +1,115 @@
+#[macro_export]
+macro_rules! check_slice {
+    ($state:ident, $index:expr) => {{
+        let mut len = 0;
+        let str_ptr = lua_shared::Lchecklstring($state, $index, &mut len);
+        std::slice::from_raw_parts(str_ptr, len)
+    }};
+}
+
+#[macro_export]
+macro_rules! insert_function {
+    ($state:ident, $name:expr, $func:expr) => {
+        lua_shared::pushfunction($state, $func);
+        lua_shared::setfield($state, -2, lua::cstr!($name));
+    };
+}
+
+#[macro_export]
+macro_rules! tree_get_key {
+    ($name:ident, $udata:expr) => {
+        paste::paste! {
+            fn [<lm_ $name>](state: lua_State) -> Result<i32, Box<dyn std::error::Error>> {
+                unsafe {
+                    let this = &mut *lua::Lcheckudata(state, 1, lua::cstr!("csldb")).cast::<Self>();
+                    let key = check_slice!(state, 2);
+                    let lt = this.$name(key)?;
+                    if let Some((key, value)) = lt {
+                        lua::pushlstring(state, key.as_ptr(), key.len());
+                        lua::pushlstring(state, value.as_ptr(), value.len());
+                        Ok(2)
+                    } else {
+                        Ok(0)
+                    }
+                }
+            }
+
+            fn [<lm_ $name _struct>](state: lua_State) -> Result<i32, Box<dyn std::error::Error>> {
+                unsafe {
+                    let this = &mut *lua::Lcheckudata(state, 1, lua::cstr!("csldb")).cast::<Self>();
+                    let key = check_slice!(state, 2);
+                    let fmt = check_slice!(state, 3);
+                    if let Some((key, value)) = this.$name(key)? {
+                        lua::pushlstring(state, key.as_ptr(), key.len());
+                        match lua_struct::unpack(state, fmt, &value) {
+                            Ok(args) => Ok(args + 1),
+                            Err(e) => {
+                                drop(key);
+                                drop(value);
+                                match e {
+                                    StructError::Error(e) => lua::Lerror(state, e),
+                                    StructError::ArgError(arg, e) => lua::Largerror(state, arg, e),
+                                    StructError::InvalidFormatOption(e, opt) => lua::Lerror(state, e, opt),
+                                    StructError::IOError(e) => return Err(e)?,
+                                }
+                            }
+                        }
+                    } else {
+                        Ok(0)
+                    }
+                }
+            }
+        }
+    };
+    ($($name:ident )+, $udata:expr) => {
+        $(tree_get_key!($name, $udata);)+
+    };
+}
+
+#[macro_export]
+macro_rules! tree_get_no_arg {
+    ($name:ident, $udata:expr) => {
+        paste::paste! {
+            fn [<lm_ $name>](state: lua_State) -> Result<i32, Box<dyn std::error::Error>> {
+                unsafe {
+                    let this = &mut *lua::Lcheckudata(state, 1, lua::cstr!($udata)).cast::<Self>();
+                    if let Some((key, value)) = this.$name()? {
+                        lua::pushlstring(state, key.as_ptr(), key.len());
+                        lua::pushlstring(state, value.as_ptr(), value.len());
+                        Ok(2)
+                    } else {
+                        Ok(0)
+                    }
+                }
+            }
+
+            fn [<lm_ $name _struct>](state: lua_State) -> Result<i32, Box<dyn std::error::Error>> {
+                unsafe {
+                    let this = &mut *lua::Lcheckudata(state, 1, lua::cstr!($udata)).cast::<Self>();
+                    let fmt = check_slice!(state, 2);
+                    if let Some((key, value)) = this.$name()? {
+                        lua::pushlstring(state, key.as_ptr(), key.len());
+                        match lua_struct::unpack(state, fmt, &value) {
+                            Ok(args) => Ok(args + 1),
+                            Err(e) => {
+                                drop(key);
+                                drop(value);
+                                match e {
+                                    StructError::Error(e) => lua::Lerror(state, e),
+                                    StructError::ArgError(arg, e) => lua::Largerror(state, arg, e),
+                                    StructError::InvalidFormatOption(e, opt) => lua::Lerror(state, e, opt),
+                                    StructError::IOError(e) => return Err(e)?,
+                                }
+                            }
+                        }
+                    } else {
+                        Ok(0)
+                    }
+                }
+            }
+        }
+    };
+    ($($name:ident )+, $udata:expr) => {
+        $(tree_get_no_arg!($name, $udata);)+
+    };
+}