Skip to content

Instantly share code, notes, and snippets.

@leiless
Last active October 10, 2023 06:50
Show Gist options
  • Save leiless/363b289db2940388cf1ec87376f5d53e to your computer and use it in GitHub Desktop.
Save leiless/363b289db2940388cf1ec87376f5d53e to your computer and use it in GitHub Desktop.
LMDB `DUP_SORT` `cursor.put(CURRENT)` different value size bug
use lmdb::{Cursor, Transaction};
fn test_lmdb_dup_sort_update_in_place() -> anyhow::Result<()> {
let db_dir = "lmdb-dir";
if let Err(err) = std::fs::remove_dir_all(db_dir) {
if err.kind() != std::io::ErrorKind::NotFound {
return Err(err.try_into()?);
}
}
std::fs::create_dir_all(db_dir)?;
let env = {
let builder = lmdb::Environment::new();
builder.open(std::path::Path::new(db_dir)).unwrap()
};
let db = env.create_db(
None,
lmdb::DatabaseFlags::INTEGER_KEY | lmdb::DatabaseFlags::DUP_SORT,
)?;
let key = 0x3003_0000_0000_003d_u64;
eprintln!("Input key: {:#x}", key);
let key_bytes = key.to_ne_bytes();
// cursor.put(..., CURRENT) BUG: when new val len is less than old val len
let val1 = "新建"; // "新建".len() = 3 * 2 = 6
let val2 = "123456";
let mut txn = env.begin_rw_txn()?;
txn.put(db, &key_bytes, &val1, lmdb::WriteFlags::empty())?;
let val = txn.get(db, &key_bytes)?;
assert_eq!(val, val1.as_bytes());
let mut cursor = txn.open_rw_cursor(db)?;
for it in cursor.iter_from(key_bytes) {
let (it_key, it_val) = it?;
assert_eq!(it_key, key_bytes);
assert_eq!(it_val, val1.as_bytes());
cursor.put(&it_key, &val2, lmdb::WriteFlags::CURRENT)?;
// HOWTO fix:
//cursor.del(lmdb::WriteFlags::empty())?;
//cursor.put(&it_key, &val2, lmdb::WriteFlags::empty())?;
break;
}
drop(cursor);
txn.commit()?;
let txn = env.begin_ro_txn()?;
let mut cursor = txn.open_ro_cursor(db)?;
let mut count = 0;
for it in cursor.iter_from(key_bytes) {
let (it_key, it_val) = it?;
let a = u64::from_ne_bytes(it_key.try_into()?);
let b = std::str::from_utf8(it_val)?;
eprintln!(" Read key: {:#x}", a);
eprintln!(" Read val: {}", b);
count += 1;
assert_eq!(count, 1);
assert_eq!(a, key, "{:#x} vs {:#x}", a, key);
assert_eq!(b, val2);
}
drop(cursor);
txn.abort();
assert_eq!(count, 1);
let txn = env.begin_ro_txn()?;
let val = txn.get(db, &key_bytes)?;
assert_eq!(val, val2.as_bytes());
txn.abort();
Ok(())
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
test_lmdb_dup_sort_update_in_place()?;
eprintln!("OK!");
Ok(())
}
@leiless
Copy link
Author

leiless commented Oct 10, 2023

Reproduce

Change let val1 = "新建"; to

  • let val1 = "新建_";
  • let val1 = "新建__";
  • let val1 = "新建___";

See

https://github.com/leiless/lmdb
leiless/lmdb#1
meilisearch/lmdb#2

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment