Skip to content

Commit

Permalink
fix: secondary index range
Browse files Browse the repository at this point in the history
  • Loading branch information
vincent-herlemont committed Aug 18, 2024
1 parent 82ebe53 commit 56f4990
Show file tree
Hide file tree
Showing 7 changed files with 256 additions and 5 deletions.
17 changes: 15 additions & 2 deletions src/db_type/key/key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ impl Key {
self.0.extend(data.0.iter());
}

pub fn extend_with_delimiter(&mut self, data: &Key) {
self.0.push(0);
self.0.extend(data.0.iter());
}

pub(crate) fn as_slice(&self) -> &[u8] {
self.0.as_slice()
}
Expand Down Expand Up @@ -334,7 +339,11 @@ impl KeyRange {
{
match (bounds.start_bound(), bounds.end_bound()) {
(Bound::Included(start), Bound::Included(end)) => {
KeyRange::RangeInclusive(start.to_key()..=end.to_key())
let start = start.to_key();
let mut end = end.to_key();
// Add 255 to the end key to include the last key
end.extend(&Key::new(vec![255]));
KeyRange::RangeInclusive(start..=end)
}
(Bound::Included(start), Bound::Excluded(end)) => {
KeyRange::Range(start.to_key()..end.to_key())
Expand All @@ -343,7 +352,11 @@ impl KeyRange {
start: start.to_key(),
}),
(Bound::Excluded(start), Bound::Included(end)) => {
KeyRange::RangeInclusive(start.to_key()..=end.to_key())
let start = start.to_key();
let mut end = end.to_key();
// Add 255 to the end key to include the last key
end.extend(&Key::new(vec![255]));
KeyRange::RangeInclusive(start..=end)
}
(Bound::Excluded(start), Bound::Excluded(end)) => {
KeyRange::Range(start.to_key()..end.to_key())
Expand Down
2 changes: 1 addition & 1 deletion src/db_type/key/key_definition.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,6 @@ pub struct KeyOptions {

pub fn composite_key(secondary_key: &Key, primary_key: &Key) -> Key {
let mut secondary_key = secondary_key.clone();
secondary_key.extend(primary_key);
secondary_key.extend_with_delimiter(primary_key);
secondary_key
}
2 changes: 0 additions & 2 deletions src/metadata/table.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ pub fn load_or_create_metadata(database_instance: &DatabaseInstance) -> Result<M
let read_thx = database.begin_read()?;

if let Ok(table) = read_thx.open_table(TABLE) {
println!("Metadata table found");
let current_version = table
.get(VERSION_NATIVE_DB_NAME)?
.expect("Fatal error: current_version not found");
Expand All @@ -42,7 +41,6 @@ pub fn load_or_create_metadata(database_instance: &DatabaseInstance) -> Result<M
current_native_model_version.value().to_string(),
));
} else {
println!("Metadata table not found");
// Create the metadata table if it does not exist
let metadata = Metadata::default();
save_metadata(database_instance, &metadata)?;
Expand Down
1 change: 1 addition & 0 deletions tests/modules.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
mod custom_type;
mod macro_def;

mod metadata;
mod migrate;
mod primary_drain;
Expand Down
146 changes: 146 additions & 0 deletions tests/scan.rs
Original file line number Diff line number Diff line change
Expand Up @@ -636,3 +636,149 @@ fn test_txn_write_start_with_scenario() {
assert_eq!(obj3.name, format!("{}3", p));
}
}

#[derive(Serialize, Deserialize, Eq, PartialEq, Clone, Debug)]
#[native_model(id = 1, version = 1)]
#[native_db]
struct ItemScanRange {
#[primary_key]
id: u32,

#[secondary_key]
nr: u32,

#[secondary_key(unique)]
unique_nr: u32,
}

#[test]
fn test_scan_range() {
let item_1 = ItemScanRange {
id: 1,
nr: 1,
unique_nr: 1,
};
let item_2 = ItemScanRange {
id: 2,
nr: 2,
unique_nr: 2,
};
let item_3 = ItemScanRange {
id: 3,
nr: 2,
unique_nr: 3,
};
let item_4 = ItemScanRange {
id: 4,
nr: 3,
unique_nr: 4,
};

let mut models = Models::new();
models.define::<ItemScanRange>().unwrap();
let db = Builder::new().create_in_memory(&models).unwrap();

let rw = db.rw_transaction().unwrap();
rw.insert(item_1.clone()).unwrap();
rw.insert(item_2.clone()).unwrap();
rw.insert(item_3.clone()).unwrap();
rw.insert(item_4.clone()).unwrap();
rw.commit().unwrap();

let r = db.r_transaction().unwrap();
let result = r
.scan()
.secondary(ItemScanRangeKey::nr)
.unwrap()
.range(0..10)
.collect::<Result<Vec<ItemScanRange>, _>>()
.unwrap()
.iter()
.map(|x| x.nr)
.collect::<Vec<_>>();
assert_eq!(result, vec![1, 2, 2, 3], "range 0..10 for nr");

let result = r
.scan()
.secondary(ItemScanRangeKey::nr)
.unwrap()
.range(2..3)
.collect::<Result<Vec<ItemScanRange>, _>>()
.unwrap()
.iter()
.map(|x| x.nr)
.collect::<Vec<_>>();
assert_eq!(result, vec![2, 2], "range 2..3 for nr");

let result = r
.scan()
.secondary(ItemScanRangeKey::unique_nr)
.unwrap()
.range(1..3)
.collect::<Result<Vec<ItemScanRange>, _>>()
.unwrap()
.iter()
.map(|x| x.unique_nr)
.collect::<Vec<_>>();
assert_eq!(result, vec![1, 2], "range 1..3 for unique_nr");

let result = r
.scan()
.secondary(ItemScanRangeKey::unique_nr)
.unwrap()
.range(1..=3)
.collect::<Result<Vec<ItemScanRange>, _>>()
.unwrap()
.iter()
.map(|x| x.unique_nr)
.collect::<Vec<_>>();
assert_eq!(result, vec![1, 2, 3], "range 1..=3 for unique_nr");

let result = r
.scan()
.secondary(ItemScanRangeKey::unique_nr)
.unwrap()
.range(3..=3)
.collect::<Result<Vec<ItemScanRange>, _>>()
.unwrap()
.iter()
.map(|x| x.unique_nr)
.collect::<Vec<_>>();
assert_eq!(result, vec![3], "range 3..=3 for unique_nr");

let result = r
.scan()
.secondary(ItemScanRangeKey::nr)
.unwrap()
.range(2..=3)
.collect::<Result<Vec<ItemScanRange>, _>>()
.unwrap()
.iter()
.map(|x| x.nr)
.collect::<Vec<_>>();
assert_eq!(result, vec![2, 2, 3], "range 2..=3 for nr");

let result = r
.scan()
.secondary(ItemScanRangeKey::nr)
.unwrap()
.range(2..=2)
.collect::<Result<Vec<ItemScanRange>, _>>()
.unwrap()
.iter()
.map(|x| x.nr)
.collect::<Vec<_>>();
assert_eq!(result, vec![2, 2], "range 2..=2 for nr");

let result = r
.scan()
.secondary(ItemScanRangeKey::nr)
.unwrap()
.range(0..=2)
.collect::<Result<Vec<ItemScanRange>, _>>()
.unwrap()
.iter()
.map(|x| x.nr)
.collect::<Vec<_>>();
assert_eq!(result, vec![1, 2, 2], "range 0..=2 for nr");
}
92 changes: 92 additions & 0 deletions tests/upgrade/from_0_7_x_to_0_8_x.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
use native_db::*;
use native_model::{native_model, Model};
use redb::ReadableTable;
use serde::{Deserialize, Serialize};

const TABLE: redb::TableDefinition<Key, Key> = redb::TableDefinition::new("2_1_name");

#[derive(Serialize, Deserialize, Eq, PartialEq, Clone, Debug)]
#[native_model(id = 1, version = 1)]
#[native_db]
struct Item1 {
#[primary_key]
id: u32,
#[secondary_key(unique)]
name: String,
}

#[derive(Serialize, Deserialize, Eq, PartialEq, Clone, Debug)]
#[native_model(id = 2, version = 1)]
#[native_db]
struct Item2 {
#[primary_key]
id: u32,
#[secondary_key(optional)]
id2: Option<u32>,
#[secondary_key]
name: String,
}

#[test]
fn upgrade_from_0_7_x_to_0_8_x() {
use std::path::PathBuf;
#[cfg(any(target_os = "android", target_os = "ios"))]
let database_path = { dinghy_test::test_project_path().join("tests/data/db_0_7_1") };

#[cfg(not(any(target_os = "android", target_os = "ios")))]
let database_path = {
let root_project_path = env!("CARGO_MANIFEST_DIR");
PathBuf::from(format!("{}/tests/data/db_0_7_1", root_project_path))
};

use shortcut_assert_fs::TmpFs;
let tmp = TmpFs::new().unwrap();

// Copy the legacy database to the temporary directory.
let tmp_database_path = tmp.path("db_0_7_1");
std::fs::copy(&database_path, &tmp_database_path).unwrap();

// Check before refresh the number of bytes of the secondary table.
// We check that the delimiter is not included in the secondary table.
{
let db = redb::Database::open(&tmp_database_path).unwrap();
let rx = db.begin_read().unwrap();
let table = rx.open_table(TABLE).unwrap();
let (key, _) = table.first().unwrap().unwrap();
assert_eq!(
key.value(),
Key::new(vec![105, 116, 101, 109, 50, 95, 48, 0, 0, 0, 0])
);
}

// Refresh the database
let mut models = Models::new();
models.define::<Item1>().unwrap();
models.define::<Item2>().unwrap();
let db = Builder::new().open(&models, &tmp_database_path).unwrap();
if db.upgrading_from_version("<0.8.0").unwrap() {
// Refresh the database
let rw = db.rw_transaction().unwrap();
// Refresh the Item2 because there is a non-unique secondary key
rw.refresh::<Item2>().unwrap();
rw.commit().unwrap();
}
drop(db);

// Check after refresh the number of bytes of the secondary table.
// We check that the delimiter is not included in the secondary table.
let db = redb::Database::open(&tmp_database_path).unwrap();
let rx = db.begin_read().unwrap();
let table = rx.open_table(TABLE).unwrap();
let (key, _) = table.first().unwrap().unwrap();
// The delimiter is a "0" byte included between the secondary key and the primary key
// Ignore fmt
#[rustfmt::skip]
assert_eq!(
key.value(),
// | secondary key | delimiter | primary key |
Key::new(vec![105, 116, 101, 109, 50, 95, 48, 0, 0, 0, 0, 0])
);
drop(rx);
drop(db);
}
1 change: 1 addition & 0 deletions tests/upgrade/mod.rs
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
mod from_0_5_x_to_0_6_x;
mod from_0_7_x_to_0_8_x;

0 comments on commit 56f4990

Please sign in to comment.