referrerpolicy=no-referrer-when-downgrade

sp_database/
kvdb.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: Apache-2.0
5
6// Licensed under the Apache License, Version 2.0 (the "License");
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10// 	http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17
18/// A wrapper around `kvdb::Database` that implements `sp_database::Database` trait
19use ::kvdb::{DBTransaction, KeyValueDB};
20use std::collections::HashMap;
21#[cfg(debug_assertions)]
22use std::collections::HashSet;
23
24use crate::{error, Change, ColumnId, Database, Transaction};
25
26struct DbAdapter<D: KeyValueDB + 'static>(D);
27
28fn handle_err<T>(result: std::io::Result<T>) -> T {
29	match result {
30		Ok(r) => r,
31		Err(e) => {
32			panic!("Critical database error: {:?}", e);
33		},
34	}
35}
36
37/// Read the reference counter for a key.
38fn read_counter(
39	db: &dyn KeyValueDB,
40	col: ColumnId,
41	key: &[u8],
42) -> error::Result<(Vec<u8>, Option<u32>)> {
43	let mut counter_key = key.to_vec();
44	counter_key.push(0);
45	Ok(match db.get(col, &counter_key).map_err(|e| error::DatabaseError(Box::new(e)))? {
46		Some(data) => {
47			let mut counter_data = [0; 4];
48			if data.len() != 4 {
49				return Err(error::DatabaseError(Box::new(std::io::Error::other(format!(
50					"Unexpected counter len {}",
51					data.len(),
52				)))));
53			}
54			counter_data.copy_from_slice(&data);
55			let counter = u32::from_le_bytes(counter_data);
56			(counter_key, Some(counter))
57		},
58		None => (counter_key, None),
59	})
60}
61
62enum RefCountedOp {
63	Store(Vec<u8>),
64	Reference,
65	Release,
66}
67
68/// Commit a transaction to a KeyValueDB.
69///
70/// Ref-counted ops on the same `(col, key)` are replayed in order against one on-disk counter
71/// read, then the final counter/value state is emitted. Without this, multiple
72/// `Store`/`Reference`/`Release` in one tx would each read the stale on-disk counter and write
73/// back to the same counter key — the underlying batch keeps only the last `put`, collapsing N
74/// ops into one.
75///
76/// `Set`/`Remove` are emitted in submission order; ref-counted ops are emitted afterwards.
77/// Debug builds assert that raw and ref-counted ops are not mixed on the same `(col, key)`.
78fn commit_impl<H: Clone + AsRef<[u8]>>(
79	db: &dyn KeyValueDB,
80	transaction: Transaction<H>,
81) -> error::Result<()> {
82	let mut tx = DBTransaction::new();
83	let mut ref_counted: HashMap<(ColumnId, Vec<u8>), Vec<RefCountedOp>> = HashMap::new();
84	#[cfg(debug_assertions)]
85	let mut raw_keys: HashSet<(ColumnId, Vec<u8>)> = HashSet::new();
86
87	for change in transaction.0.into_iter() {
88		match change {
89			Change::Set(col, key, value) => {
90				#[cfg(debug_assertions)]
91				raw_keys.insert((col, key.clone()));
92				tx.put_vec(col, &key, value);
93			},
94			Change::Remove(col, key) => {
95				#[cfg(debug_assertions)]
96				raw_keys.insert((col, key.clone()));
97				tx.delete(col, &key);
98			},
99			Change::Store(col, key, value) => {
100				ref_counted
101					.entry((col, key.as_ref().to_vec()))
102					.or_default()
103					.push(RefCountedOp::Store(value));
104			},
105			Change::Reference(col, key) => {
106				ref_counted
107					.entry((col, key.as_ref().to_vec()))
108					.or_default()
109					.push(RefCountedOp::Reference);
110			},
111			Change::Release(col, key) => {
112				ref_counted
113					.entry((col, key.as_ref().to_vec()))
114					.or_default()
115					.push(RefCountedOp::Release);
116			},
117		}
118	}
119
120	#[cfg(debug_assertions)]
121	for raw_key in &raw_keys {
122		debug_assert!(
123			!ref_counted.contains_key(raw_key),
124			"mixed raw/ref-counted database ops on column {}, key {:02x?}",
125			raw_key.0,
126			raw_key.1,
127		);
128	}
129
130	for ((col, key), ops) in ref_counted {
131		let (counter_key, mut counter) = read_counter(db, col, &key)?;
132
133		let mut value_to_write = None;
134		for op in ops {
135			match op {
136				RefCountedOp::Store(value) => match counter {
137					Some(c) => counter = Some(c + 1),
138					None => {
139						counter = Some(1);
140						value_to_write = Some(value);
141					},
142				},
143				RefCountedOp::Reference => {
144					if let Some(c) = counter {
145						counter = Some(c + 1);
146					}
147				},
148				RefCountedOp::Release => match counter {
149					Some(1) => {
150						counter = None;
151						value_to_write = None;
152					},
153					Some(c) => counter = Some(c - 1),
154					None => {},
155				},
156			}
157		}
158
159		match counter {
160			Some(counter) => {
161				tx.put(col, &counter_key, &counter.to_le_bytes());
162				if let Some(value) = value_to_write {
163					tx.put_vec(col, &key, value);
164				}
165			},
166			None => {
167				tx.delete(col, &counter_key);
168				tx.delete(col, &key);
169			},
170		}
171	}
172
173	db.write(tx).map_err(|e| error::DatabaseError(Box::new(e)))
174}
175
176/// Wrap generic kvdb-based database into a trait object that implements [`Database`].
177pub fn as_database<D, H>(db: D) -> std::sync::Arc<dyn Database<H>>
178where
179	D: KeyValueDB + 'static,
180	H: Clone + AsRef<[u8]>,
181{
182	std::sync::Arc::new(DbAdapter(db))
183}
184
185impl<D: KeyValueDB, H: Clone + AsRef<[u8]>> Database<H> for DbAdapter<D> {
186	fn commit(&self, transaction: Transaction<H>) -> error::Result<()> {
187		commit_impl(&self.0, transaction)
188	}
189
190	fn get(&self, col: ColumnId, key: &[u8]) -> Option<Vec<u8>> {
191		handle_err(self.0.get(col, key))
192	}
193
194	fn contains(&self, col: ColumnId, key: &[u8]) -> bool {
195		handle_err(self.0.has_key(col, key))
196	}
197}
198
199/// RocksDB-specific adapter that implements `optimize_db` via `force_compact`.
200#[cfg(feature = "rocksdb")]
201pub struct RocksDbAdapter(kvdb_rocksdb::Database);
202
203#[cfg(feature = "rocksdb")]
204impl<H: Clone + AsRef<[u8]>> Database<H> for RocksDbAdapter {
205	fn commit(&self, transaction: Transaction<H>) -> error::Result<()> {
206		commit_impl(&self.0, transaction)
207	}
208
209	fn get(&self, col: ColumnId, key: &[u8]) -> Option<Vec<u8>> {
210		handle_err(self.0.get(col, key))
211	}
212
213	fn contains(&self, col: ColumnId, key: &[u8]) -> bool {
214		handle_err(self.0.has_key(col, key))
215	}
216
217	fn optimize_db_col(&self, col: ColumnId) -> error::Result<()> {
218		self.0.force_compact(col).map_err(|e| error::DatabaseError(Box::new(e)))
219	}
220}
221
222/// Wrap RocksDB database into a trait object with `optimize_db` support.
223#[cfg(feature = "rocksdb")]
224pub fn as_rocksdb_database<H>(db: kvdb_rocksdb::Database) -> std::sync::Arc<dyn Database<H>>
225where
226	H: Clone + AsRef<[u8]>,
227{
228	std::sync::Arc::new(RocksDbAdapter(db))
229}