pub mod partition_macros;
pub mod partition_store;
pub mod span_index;
pub use partition_store::{
BluegumStores, CascadingRefCollector, CollectCascadingRefs,
CollectIndexHashes, HasIndexPartition, HasPartition, HasPartitionAt,
MergeFrom, MergeFromWithRefcount, MergeResult, NewStores, PartitionStore,
PartitionStoreInner, RecordRef, RefcountOps,
};
pub use span_index::SpanIndex;
use crate::{
Ident,
database::{chunk::RecordWriter, storage::Partitions},
};
pub trait PartitionKey {
const KEY: Ident;
const KEY_NAME: Option<&'static str> = None;
}
pub trait Partition: PartitionKey + Send + Sync {
type Record: crate::record::Record + bluegum::BluegumWithState<dyn crate::SpanResolver>;
type IndexEntry: IndexEntry + bluegum::BluegumWithState<dyn crate::SpanResolver>;
type SortKey: std::fmt::Display;
fn index_entry_from_handle(
handle: crate::database::RecordHandle<Self>,
) -> Self::IndexEntry
where
Self: Sized,
{
let _ = handle;
unreachable!(
"Partition {} does not support creating index entries from just a handle. \
Use RecordWriter::index_entry() instead of index() for this partition.",
Self::KEY
)
}
}
pub trait IndexEntry: Clone + Send + Sync {
fn primary_hash(&self) -> Option<crate::ContentHash> {
None
}
}
impl IndexEntry for () {}
pub trait IndexedEntry: IndexEntry {
fn content_hash(&self) -> crate::ContentHash;
}
#[derive(Debug)]
pub struct HandleEntry<P: PartitionKey> {
handle: crate::database::RecordHandle<P>,
}
impl<P: PartitionKey> HandleEntry<P> {
pub fn new(handle: crate::database::RecordHandle<P>) -> Self {
Self { handle }
}
pub fn handle(&self) -> crate::database::RecordHandle<P> {
self.handle
}
pub fn content_hash(&self) -> crate::ContentHash {
self.handle.content_hash()
}
}
impl<P: PartitionKey> Clone for HandleEntry<P> {
fn clone(&self) -> Self {
*self
}
}
impl<P: PartitionKey> Copy for HandleEntry<P> {}
impl<P: PartitionKey> PartialEq for HandleEntry<P> {
fn eq(&self, other: &Self) -> bool {
self.handle == other.handle
}
}
impl<P: PartitionKey> Eq for HandleEntry<P> {}
impl<P: PartitionKey> std::hash::Hash for HandleEntry<P> {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.handle.hash(state);
}
}
impl<P: PartitionKey + Send + Sync> IndexEntry for HandleEntry<P> {
fn primary_hash(&self) -> Option<crate::ContentHash> {
Some(self.handle.content_hash())
}
}
impl<P: PartitionKey + Send + Sync> IndexedEntry for HandleEntry<P> {
fn content_hash(&self) -> crate::ContentHash {
self.handle.content_hash()
}
}
impl<Ps, P> crate::record::CollectReferences<Ps> for HandleEntry<P>
where
Ps: crate::database::storage::Partitions,
P: Partition,
Ps::Stores: HasPartition<P>,
{
fn collect_references<R: crate::record::References<Ps>>(&self, refs: &mut R) {
refs.add(self.handle);
}
}
impl<P: PartitionKey> From<crate::database::RecordHandle<P>>
for HandleEntry<P>
{
fn from(handle: crate::database::RecordHandle<P>) -> Self {
Self { handle }
}
}
impl<P: PartitionKey> bluegum::Bluegum for HandleEntry<P> {
fn node(&self, b: &mut bluegum::Builder) {
b.name("HandleEntry")
.field("hash", self.handle.content_hash());
}
}
impl<P: PartitionKey> bluegum::BluegumWithState<dyn crate::SpanResolver> for HandleEntry<P> {}
impl bluegum::BluegumWithState<dyn crate::SpanResolver> for () {}
impl<P> DynPartition for P
where
P: Partition + PartitionKey,
{
type DynSortKey = P::SortKey;
type RecordConstraint = P::Record;
}
pub trait PartitionWriter<P: Partition + PartitionKey> {
fn write(&mut self, sort_key: P::SortKey, record: P::Record);
}
pub trait PartitionReader<Part: Partition + PartitionKey>: Partitions {
fn as_record(record_ref: &Self::RecordRef<'_>) -> Option<Part::Record>;
}
pub trait DynPartition: PartitionKey {
type RecordConstraint: ?Sized;
type DynSortKey: std::fmt::Display;
}
pub trait DynPartitionRecord<P: DynPartition>: crate::record::Record {}
#[macro_export]
macro_rules! impl_partition_for_dyn {
($wrapper:ident, $dyn_partition:ty, $record:ty) => {
pub struct $wrapper;
const _: () = {
fn _assert_record_implements_trait<
T: $crate::database::partitions::DynPartitionRecord<$dyn_partition>,
>() {
}
fn _check() {
_assert_record_implements_trait::<$record>();
}
};
impl $crate::database::partitions::PartitionKey for $wrapper {
const KEY: $crate::Ident =
<$dyn_partition as $crate::database::partitions::PartitionKey>::KEY;
const KEY_NAME: Option<&'static str> =
<$dyn_partition as $crate::database::partitions::PartitionKey>::KEY_NAME;
}
impl $crate::database::partitions::Partition for $wrapper {
type Record = $record;
type IndexEntry = $crate::database::partitions::HandleEntry<Self>;
type SortKey =
<$dyn_partition as $crate::database::partitions::DynPartition>::DynSortKey;
fn index_entry_from_handle(
handle: $crate::database::RecordHandle<Self>,
) -> Self::IndexEntry {
$crate::database::partitions::HandleEntry::new(handle)
}
}
};
}
pub(crate) struct InternalPartitionWriteContext<P: Partitions> {
writer: RecordWriter<P>,
}
impl<P: Partitions> InternalPartitionWriteContext<P> {
pub(crate) fn new(writer: RecordWriter<P>) -> Self {
Self { writer }
}
pub(crate) fn into_inner(self) -> RecordWriter<P> {
self.writer
}
pub(crate) fn set_source(
&mut self,
key: crate::SourceKey,
source: crate::Source,
) {
self.writer.set_source(key, source);
}
pub fn as_ref(&mut self) -> PartitionWriteContextRef<'_, P> {
PartitionWriteContextRef::new(&mut self.writer)
}
}
pub struct PartitionWriteContextRef<'a, P: Partitions> {
writer: &'a mut RecordWriter<P>,
blocked_partition: Option<Ident>,
}
impl<'a, P: Partitions> PartitionWriteContextRef<'a, P> {
#[cfg(any(test, feature = "test"))]
pub fn new(writer: &'a mut RecordWriter<P>) -> Self {
Self {
writer,
blocked_partition: None,
}
}
#[cfg(not(any(test, feature = "test")))]
pub(crate) fn new(writer: &'a mut RecordWriter<P>) -> Self {
Self {
writer,
blocked_partition: None,
}
}
pub(crate) fn new_for_watcher(
writer: &'a mut RecordWriter<P>,
trigger_partition: Ident,
) -> Self {
Self {
writer,
blocked_partition: Some(trigger_partition),
}
}
fn check_write_allowed(&self, target: Ident) -> bool {
if let Some(blocked) = self.blocked_partition
&& target == blocked
{
debug_assert!(
false,
"Watcher attempted to write back to its trigger partition '{}'",
blocked
);
otel::error!(
"watcher.blocked_self_write",
format!(
"Watcher attempted to write back to its trigger partition '{}'. Write skipped.",
blocked
)
);
return false;
}
true
}
pub fn clear_prefix(
&mut self,
partition_key: Ident,
prefix: impl std::fmt::Display,
) {
if !self.check_write_allowed(partition_key) {
return;
}
self.writer.clear_prefix(partition_key, prefix);
}
pub fn write<Part>(&mut self, sort_key: Part::SortKey, record: Part::Record)
where
Part: Partition + PartitionKey + 'static,
P::Stores: HasPartition<Part>,
{
if !self.check_write_allowed(Part::KEY) {
return;
}
self.writer.insert::<Part, _>(sort_key, record);
}
pub fn store<Part>(
&mut self,
record: Part::Record,
) -> crate::database::RecordHandle<Part>
where
Part: Partition,
P::Stores: HasPartition<Part>,
{
if !self.check_write_allowed(Part::KEY) {
return crate::database::RecordHandle::new(crate::ContentHash::ZERO);
}
self.writer.store::<Part>(record)
}
pub fn index_span<Part>(
&mut self,
span: crate::Span,
content_hash: crate::ContentHash,
) where
Part: Partition + PartitionKey + 'static,
{
self.writer.index_span::<Part>(span, content_hash);
}
pub fn index_entry<Part>(
&mut self,
sort_key: impl std::fmt::Display,
entry: Part::IndexEntry,
) where
Part: Partition,
Part::IndexEntry: IndexedEntry,
P::Stores: HasPartition<Part>,
{
if !self.check_write_allowed(Part::KEY) {
return;
}
self.writer.index_entry::<Part, _>(sort_key, entry);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn dyn_partition_record_blanket_impl_works() {
use crate::{
ContentHash,
partitions::{
DocumentFoldingRange, document_folding_range::FoldingRangeRecord,
},
record::Record,
};
#[derive(Debug, Hash)]
struct TestFoldingRange;
impl Record for TestFoldingRange {
fn content_hash(&self) -> ContentHash {
ContentHash::new(&[])
}
}
impl FoldingRangeRecord for TestFoldingRange {
fn source_key(&self) -> crate::SourceKey {
crate::SourceKey::new(0, 0)
}
fn to_folding_range(&self) -> crate::protocol::lsp::FoldingRange {
crate::protocol::lsp::FoldingRange {
start_line: 0,
start_character: None,
end_line: 1,
end_character: None,
kind: None,
collapsed_text: None,
}
}
}
fn assert_dyn_partition_record<
P: DynPartition,
R: DynPartitionRecord<P>,
>() {
}
assert_dyn_partition_record::<DocumentFoldingRange, TestFoldingRange>();
}
mod watcher_write_guard {
use crate::{
Ident,
database::{
chunk::RecordWriter,
partitions::PartitionWriteContextRef,
tests::storage::{
Test1Partition, Test2Partition, TestPartitions, TestRecordData,
},
},
};
fn make_test_record() -> TestRecordData {
TestRecordData::Module {
exports: vec![Ident::new("test")],
}
}
#[test]
#[should_panic(
expected = "Watcher attempted to write back to its trigger partition"
)]
fn watcher_context_blocks_self_write() {
let pk1 =
<Test1Partition as crate::database::partitions::PartitionKey>::KEY;
let mut writer = RecordWriter::<TestPartitions>::new(pk1);
let mut ctx = PartitionWriteContextRef::new_for_watcher(&mut writer, pk1);
ctx.write::<Test1Partition>("key".to_string(), make_test_record());
}
#[test]
fn watcher_context_allows_write_to_other_partition() {
let pk1 =
<Test1Partition as crate::database::partitions::PartitionKey>::KEY;
let mut writer = RecordWriter::<TestPartitions>::new(pk1);
let mut ctx = PartitionWriteContextRef::new_for_watcher(&mut writer, pk1);
ctx.write::<Test2Partition>("key".to_string(), make_test_record());
}
#[test]
fn non_watcher_context_allows_all_writes() {
let pk1 =
<Test1Partition as crate::database::partitions::PartitionKey>::KEY;
let mut writer = RecordWriter::<TestPartitions>::new(pk1);
let mut ctx = PartitionWriteContextRef::new(&mut writer);
ctx.write::<Test1Partition>("key1".to_string(), make_test_record());
ctx.write::<Test2Partition>("key2".to_string(), make_test_record());
}
#[test]
#[should_panic(
expected = "Watcher attempted to write back to its trigger partition"
)]
fn watcher_context_blocks_store_to_trigger_partition() {
let pk1 =
<Test1Partition as crate::database::partitions::PartitionKey>::KEY;
let mut writer = RecordWriter::<TestPartitions>::new(pk1);
let mut ctx = PartitionWriteContextRef::new_for_watcher(&mut writer, pk1);
ctx.store::<Test1Partition>(make_test_record());
}
#[test]
#[should_panic(
expected = "Watcher attempted to write back to its trigger partition"
)]
fn watcher_context_blocks_clear_prefix_on_trigger_partition() {
let pk1 =
<Test1Partition as crate::database::partitions::PartitionKey>::KEY;
let mut writer = RecordWriter::<TestPartitions>::new(pk1);
let mut ctx = PartitionWriteContextRef::new_for_watcher(&mut writer, pk1);
ctx.clear_prefix(pk1, "prefix");
}
}
}