-
Notifications
You must be signed in to change notification settings - Fork 224
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Delete by prefix operator in kvdb #360
Changes from 24 commits
616b401
b0317f6
b6cf86b
9ce83b0
6e4f390
168b103
6495423
dac8aa1
a3a2a54
a2b481c
81e460e
2018880
27a3828
1f60726
2eb1fd8
ca2269f
fcc4e58
34b1e0b
5a892f8
1cedb14
da49663
05d9469
20bac79
815d6ca
1564f5f
904ebae
2456135
1f18d0f
9e33a73
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -443,6 +443,17 @@ impl Database { | |||||
stats_total_bytes += key.len(); | ||||||
batch.delete_cf(cf, &key).map_err(other_io_err)? | ||||||
} | ||||||
DBOp::DeletePrefix { col: _, prefix } => { | ||||||
if prefix.len() > 0 { | ||||||
let end_range = kvdb::end_prefix(&prefix[..]); | ||||||
batch.delete_range_cf(cf, &prefix[..], &end_range[..]).map_err(other_io_err)?; | ||||||
} else { | ||||||
// delete every values in the column | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
let end_range = &[u8::max_value()]; | ||||||
batch.delete_range_cf(cf, &prefix[..], &end_range[..]).map_err(other_io_err)?; | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why do we need to delete a range, if we're deleting the whole column later? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I did not delete the whole column (at first I tried to but it requires mutable access and some changes overall). So I end up thinking that if we use the delete range to delete the column we are doing something wrong, so using delete range for it felt more consistent. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
batch.delete_cf(cf, &end_range[..]).map_err(other_io_err)?; | ||||||
} | ||||||
} | ||||||
}; | ||||||
} | ||||||
self.stats.tally_bytes_written(stats_total_bytes as u64); | ||||||
|
@@ -705,6 +716,12 @@ mod tests { | |||||
st::test_delete_and_get(&db) | ||||||
} | ||||||
|
||||||
#[test] | ||||||
fn delete_prefix() -> io::Result<()> { | ||||||
let db = create(st::DELETE_PREFIX_NUM_COLUMNS)?; | ||||||
st::test_delete_prefix(&db) | ||||||
} | ||||||
|
||||||
#[test] | ||||||
fn iter() -> io::Result<()> { | ||||||
let db = create(1)?; | ||||||
|
@@ -725,7 +742,7 @@ mod tests { | |||||
|
||||||
#[test] | ||||||
fn stats() -> io::Result<()> { | ||||||
let db = create(3)?; | ||||||
let db = create(st::IOSTATS_NUM_COLUMNS)?; | ||||||
st::test_io_stats(&db) | ||||||
} | ||||||
|
||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -35,6 +35,7 @@ pub struct DBTransaction { | |
pub enum DBOp { | ||
Insert { col: u32, key: DBKey, value: DBValue }, | ||
Delete { col: u32, key: DBKey }, | ||
DeletePrefix { col: u32, prefix: DBKey }, | ||
} | ||
|
||
impl DBOp { | ||
|
@@ -43,6 +44,7 @@ impl DBOp { | |
match *self { | ||
DBOp::Insert { ref key, .. } => key, | ||
DBOp::Delete { ref key, .. } => key, | ||
DBOp::DeletePrefix { ref prefix, .. } => prefix, | ||
} | ||
} | ||
|
||
|
@@ -51,6 +53,7 @@ impl DBOp { | |
match *self { | ||
DBOp::Insert { col, .. } => col, | ||
DBOp::Delete { col, .. } => col, | ||
DBOp::DeletePrefix { col, .. } => col, | ||
} | ||
} | ||
} | ||
|
@@ -80,6 +83,11 @@ impl DBTransaction { | |
pub fn delete(&mut self, col: u32, key: &[u8]) { | ||
self.ops.push(DBOp::Delete { col, key: DBKey::from_slice(key) }); | ||
} | ||
|
||
/// Delete all values with the given key prefix. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we document what happens if called with an empty prefix (deletes all values)? |
||
pub fn delete_prefix(&mut self, col: u32, prefix: &[u8]) { | ||
self.ops.push(DBOp::DeletePrefix { col, prefix: DBKey::from_slice(prefix) }); | ||
} | ||
} | ||
|
||
/// Generic key-value database. | ||
|
@@ -129,3 +137,33 @@ pub trait KeyValueDB: Sync + Send + parity_util_mem::MallocSizeOf { | |
IoStats::empty() | ||
} | ||
} | ||
|
||
/// Return for a start inclusive prefix, the non inclusive end. | ||
cheme marked this conversation as resolved.
Show resolved
Hide resolved
|
||
/// This assume key are ordered in a lexicographical order. | ||
cheme marked this conversation as resolved.
Show resolved
Hide resolved
|
||
pub fn end_prefix(prefix: &[u8]) -> Vec<u8> { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this function can be simplified fn end_prefix(mut prefix: Vec<u8>) -> Vec<u8> {
while let Some(0xff) = prefix.last() {
prefix.pop();
}
if let Some(byte) = prefix.last_mut() {
*byte += 1;
}
prefix
} I'm not sure we want to expose it There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also if the prefix is empty or 0xfffffff...ff, will it delete the whole db? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. And we could reuse this function for iter_from_prefix by setting https://docs.rs/rocksdb/0.13.0/rocksdb/struct.ReadOptions.html#method.set_iterate_upper_bound There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. for empty prefix it does indeed delete every values (not in a very efficient way). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What about There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am not sure how to document that, I will add a test it may make thing more explicit. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. oups, I actually meant There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Let's add a few examples and include interesting cases such as |
||
let mut end_range = prefix.to_vec(); | ||
while let Some(0xff) = end_range.last() { | ||
end_range.pop(); | ||
} | ||
if let Some(byte) = end_range.last_mut() { | ||
*byte += 1; | ||
} | ||
end_range | ||
} | ||
|
||
#[cfg(test)] | ||
mod test { | ||
use super::end_prefix; | ||
|
||
#[test] | ||
fn end_prefix_test() { | ||
assert_eq!(end_prefix(&[5, 6, 7]), vec![5, 6, 8]); | ||
assert_eq!(end_prefix(&[5, 6, 255]), vec![5, 7]); | ||
// this is incorrect as the result is before start | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In what sense is it incorrect? Is it "misusage"? Or is the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I also thought this comment was a bit confusing, but the test below uses There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes it is redundant, it is mostly documentation (at some point I was not remembering why we can recurse on popping the 0xff). |
||
assert_ne!(end_prefix(&[5, 255, 255]), vec![5, 255]); | ||
// this is correct ([5, 255] will not be deleted because | ||
// it is before start). | ||
assert_eq!(end_prefix(&[5, 255, 255]), vec![6]); | ||
assert_eq!(end_prefix(&[255, 255, 255]), vec![]); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this could be optimized by using
split_off
andappend
, but currently append is slow: rust-lang/rust#34666 and I think memorydb is only used for tests and kvdb-web, so it's fine for now