Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion crates/codegen/src/cxx_bridge.rs
Original file line number Diff line number Diff line change
Expand Up @@ -530,7 +530,7 @@ fn emit_context_bridge(
let (#(#build_fields,)*) = match opts {
None => (#(#build_wraps(#q::Observer::<#build_event_tys>::noop()),)*),
Some(options) => {
let inner = #q::Context::try_new(id).map_err(|e| e.to_string())?;
let inner = #q::ContextInner::try_new(id).map_err(|e| e.to_string())?;
#q::write_sidecar(
&options,
id,
Expand Down
6 changes: 3 additions & 3 deletions crates/codegen/src/pyo3_bridge.rs
Original file line number Diff line number Diff line change
Expand Up @@ -685,7 +685,7 @@ fn emit_context(
quote! {
#[pyclass(name = "Context")]
pub struct PyContext {
inner: Option<#q::Context>,
inner: Option<#q::ContextInner>,
#(#struct_fields,)*
id: #q::uuid::Uuid,
}
Expand Down Expand Up @@ -715,9 +715,9 @@ fn emit_context(
};
let id = #q::uuid::Uuid::now_v7();
let inner = match &opts {
Some(_) => #q::Context::try_new(id)
Some(_) => #q::ContextInner::try_new(id)
.map_err(|err| pyo3::exceptions::PyRuntimeError::new_err(err.to_string()))?,
None => #q::Context::noop(id),
None => #q::ContextInner::noop(id),
};
// Single sync/async bridge: build every entity's observer (each
// constructing its exporter from the options, bound to the id)
Expand Down
12 changes: 6 additions & 6 deletions crates/instrumentation-build/example/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

use quent_instrumentation::{EventCallback, ExporterOptions};

use crate::demo::{ConnectionHandle, ConnectionObserver, DemoContext, Uuid};
use crate::demo::{Connection, Context, Demo, Handle, Observer, Query, Server, Uuid};

#[allow(unused)]
mod demo {
Expand All @@ -12,16 +12,16 @@ mod demo {

fn main() -> Result<(), Box<dyn std::error::Error>> {
// The context owns the exporter and exposes one observer per entity type.
let context: DemoContext = demo::DemoContext::try_new(Some(debug_printing_exporter()))?;
let context: Context<Demo> = Context::try_new(Some(debug_printing_exporter()))?;

// `observer.handle()` creates a fresh entity instance to events emit for.
let mut server = context.server_observer().handle();
let mut server = context.observer::<Server>().handle();
server.booted()?;

let observer: ConnectionObserver = context.connection_observer();
let observer: Observer<Connection> = context.observer::<Connection>();
// Once-cardinality events take `&mut self` and may fire only once, tracked
// by the handle, hence it is mut:
let mut conn: ConnectionHandle = observer.handle();
let mut conn: Handle<Connection> = observer.handle();

// One method per entity event:
conn.opened(
Expand Down Expand Up @@ -57,7 +57,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
//
// FSMs will get typestate pattern handles in the future, also see
// https://github.com/rapidsai/quent/issues/416
let mut query = context.query_observer().handle();
let mut query = context.observer::<Query>().handle();
query.submitted("select 1".to_owned(), conn.as_entity_ref())?;
query.running(10)?;
query.ready(true)?;
Expand Down
80 changes: 49 additions & 31 deletions crates/instrumentation-build/src/any_event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@

use convert_case::Case;
use proc_macro2::TokenStream;
use quent_schema::Schema;
use quote::quote;
use syn::Ident;

use crate::common::{derive_attr, raw_ident, to_case};
use crate::common::{
derive_attr, module_ident, path_name_pascal, raw_ident, relative_type_path, to_case,
};
use crate::namespace::Namespace;
use crate::{GenerateError, Options};

/// Generate `AnyEvent` and its `from_any` decoder, carrying the event enums'
Expand All @@ -20,41 +22,67 @@ use crate::{GenerateError, Options};
///
/// Returns [`GenerateError`] if a derive entry is not a parseable Rust path.
pub(crate) fn generate_any_event(
schema: &Schema,
namespace: &Namespace<'_>,
opts: &Options,
) -> Result<TokenStream, GenerateError> {
let derives = derive_attr(opts.event_derives)?;

let variants: Vec<(Ident, Ident)> = schema
let variants: Vec<(Ident, TokenStream)> = namespace
.entities()
.iter()
.map(|entity| {
let pascal = to_case(entity.path().name(), Case::Pascal);
let variant = raw_ident(path_name_pascal(entity.path()));
let event = relative_type_path(entity.path(), namespace.path(), "Event");
(variant, event)
})
.collect();
let children: Vec<(Ident, Ident)> = namespace
.children()
.iter()
.filter(|child| child.has_entities())
.map(|child| {
let segment = child
.path()
.last()
.expect("child namespaces extend their parent");
(
raw_ident(pascal.clone()),
raw_ident(format!("{pascal}Event")),
raw_ident(to_case(segment, Case::Pascal)),
module_ident(segment),
)
})
.collect();

let decls = variants.iter().map(|(variant, event)| {
quote! { #variant(&'a ::quent_instrumentation::Event<#event>) }
});
let arms = variants.iter().map(|(variant, event)| {
let child_decls = children.iter().map(|(variant, module)| {
quote! { #variant(#module::AnyEvent<'a>) }
});
let direct_arms = variants.iter().map(|(variant, event)| {
quote! {
if let Some(event) = any.downcast_ref::<::quent_instrumentation::Event<#event>>() {
return Some(AnyEvent::#variant(event));
return Some(Self::#variant(event));
}
}
});
let child_arms = children.iter().map(|(variant, module)| {
quote! {
if let Some(event) = #module::AnyEvent::from_any(any) {
return Some(Self::#variant(event));
}
}
});

Ok(quote! {
#derives
pub enum AnyEvent<'a> {
#(#decls),*
#(#decls,)*
#(#child_decls,)*
}
impl<'a> AnyEvent<'a> {
pub fn from_any(any: &'a (dyn ::core::any::Any)) -> Option<AnyEvent<'a>> {
#(#arms)*
pub fn from_any(any: &'a dyn ::core::any::Any) -> Option<Self> {
#(#direct_arms)*
#(#child_arms)*
None
}
}
Expand All @@ -65,31 +93,21 @@ pub(crate) fn generate_any_event(
mod tests {
use super::*;
use crate::common::pretty;
use quent_schema::builder::{EntityBuilder, EventBuilder, SchemaBuilder};
use quent_schema::{Cardinality, test_utils::ident};

fn entity(name: &str, event: &str) -> quent_schema::Entity {
EntityBuilder::new(ident(name))
.with_event(
EventBuilder::new(ident(event), Cardinality::Once)
.build()
.unwrap(),
)
.build()
.unwrap()
}
use quent_schema::builder::SchemaBuilder;
use quent_schema::test_utils::{entity, event, ident};

#[test]
fn emits_a_variant_and_arm_per_entity() {
let schema = SchemaBuilder::new(ident("Demo"))
.with_entity(entity("Query", "submitted"))
.with_entity(entity("Server", "booted"))
.with_entity(entity("Query", [event("submitted", [])]))
.with_entity(entity("Server", [event("booted", [])]))
.build()
.unwrap();
let opts = Options {
event_derives: &["Debug"],
..Options::default()
};
let namespaces = Namespace::root(&schema);
let expected = quote! {
#[derive(Debug)]
pub enum AnyEvent<'a> {
Expand All @@ -98,23 +116,23 @@ mod tests {
}

impl<'a> AnyEvent<'a> {
pub fn from_any(any: &'a (dyn ::core::any::Any)) -> Option<AnyEvent<'a>> {
pub fn from_any(any: &'a dyn ::core::any::Any) -> Option<Self> {
if let Some(event) =
any.downcast_ref::<::quent_instrumentation::Event<QueryEvent>>()
{
return Some(AnyEvent::Query(event));
return Some(Self::Query(event));
}
if let Some(event) =
any.downcast_ref::<::quent_instrumentation::Event<ServerEvent>>()
{
return Some(AnyEvent::Server(event));
return Some(Self::Server(event));
}
None
}
}
};
assert_eq!(
pretty(generate_any_event(&schema, &opts).unwrap()),
pretty(generate_any_event(&namespaces, &opts).unwrap()),
pretty(expected)
);
}
Expand Down
42 changes: 41 additions & 1 deletion crates/instrumentation-build/src/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

use convert_case::{Boundary, Case, Casing};
use proc_macro2::{Span, TokenStream};
use quent_schema::Identifier;
use quent_schema::{Identifier, Path};
use quote::quote;
use syn::Ident;

Expand Down Expand Up @@ -52,6 +52,46 @@ pub(crate) fn to_case(id: &Identifier, case: Case) -> String {
.to_case(case)
}

/// Return the Pascal-case type name for the final path segment.
pub(crate) fn path_name_pascal(path: &Path) -> String {
to_case(path.name(), Case::Pascal)
}

/// Return the Rust module name for a path segment.
pub(crate) fn module_ident(segment: &Identifier) -> Ident {
raw_ident(to_case(segment, Case::Snake))
}

/// Return a generated type path relative to `source_namespace`.
pub(crate) fn relative_type_path(
path: &Path,
source_namespace: &[Identifier],
suffix: &str,
) -> TokenStream {
let common = path
.namespace()
.iter()
.zip(source_namespace)
.take_while(|(left, right)| left == right)
.count();
let mut segments = Vec::new();
segments.extend((common..source_namespace.len()).map(|_| quote! { super }));
for segment in &path.namespace()[common..] {
let module = module_ident(segment);
segments.push(quote! { #module });
}
let ty = raw_ident(format!("{}{}", path_name_pascal(path), suffix));
segments.push(quote! { #ty });
quote! { #(#segments)::* }
}

/// Return a root type path relative to `source_namespace`.
pub(crate) fn relative_root_type(name: &str, source_namespace: &[Identifier]) -> TokenStream {
let parents = source_namespace.iter().map(|_| quote! { super });
let ty = raw_ident(name.to_owned());
quote! { #(#parents::)* #ty }
}

/// Build an identifier from an already-cased name, raw-escaping Rust keywords.
/// The keywords that cannot be raw (`crate`, `self`, `super`, `Self`) instead
/// receive a trailing underscore.
Expand Down
38 changes: 19 additions & 19 deletions crates/instrumentation-build/src/data_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,12 @@

//! Mapping from schema [`DataType`]s to Rust type tokens.

use convert_case::Case;
use proc_macro2::TokenStream;
use quent_ref_target::RefTarget;
use quent_schema::{Annotations, DataType};
use quote::quote;

use crate::common::{raw_ident, to_case};
use crate::common::{relative_root_type, relative_type_path};

/// Maximum nesting depth of `Option`/`List`/`EntityRef` wrappers a single field
/// type may have, far above any realistic schema. Self-referential records are
Expand All @@ -23,7 +22,11 @@ pub(crate) const MAX_TYPE_DEPTH: usize = 64;
/// # Panics
///
/// Panics if `ty` nests deeper than [`MAX_TYPE_DEPTH`].
pub(crate) fn map_data_type(ty: &DataType, depth: usize) -> TokenStream {
pub(crate) fn map_data_type(
ty: &DataType,
depth: usize,
source_namespace: &[quent_schema::Identifier],
) -> TokenStream {
assert!(
depth <= MAX_TYPE_DEPTH,
"field type nesting exceeds the maximum depth of {MAX_TYPE_DEPTH}"
Expand All @@ -43,23 +46,20 @@ pub(crate) fn map_data_type(ty: &DataType, depth: usize) -> TokenStream {
DataType::F32 => quote! { f32 },
DataType::F64 => quote! { f64 },
DataType::Option(inner) => {
let inner = map_data_type(inner, depth + 1);
let inner = map_data_type(inner, depth + 1, source_namespace);
quote! { Option<#inner> }
}
DataType::List(inner) => {
let inner = map_data_type(inner, depth + 1);
let inner = map_data_type(inner, depth + 1, source_namespace);
quote! { Vec<#inner> }
}
DataType::Record(name) => {
let ident = raw_ident(to_case(name.name(), Case::Pascal));
quote! { #ident }
}
DataType::Record(path) => relative_type_path(path, source_namespace, ""),
DataType::DynamicRecord => quote! { ::quent_instrumentation::DynamicAttributes },
DataType::EntityRef { data, annotations } => {
let target = ref_target_marker(annotations);
let target = ref_target_marker(annotations, source_namespace);
match data {
Some(inner) => {
let inner = map_data_type(inner, depth + 1);
let inner = map_data_type(inner, depth + 1, source_namespace);
quote! { ::quent_instrumentation::EntityRef<#target, #inner> }
}
None => quote! { ::quent_instrumentation::EntityRef<#target> },
Expand All @@ -71,13 +71,13 @@ pub(crate) fn map_data_type(ty: &DataType, depth: usize) -> TokenStream {
/// The target-entity marker type for an entity reference, taken from its
/// ref-target constraint, or the `AnyEntity` marker when it is not restricted
/// to a target entity.
fn ref_target_marker(annotations: &Annotations) -> TokenStream {
fn ref_target_marker(
annotations: &Annotations,
source_namespace: &[quent_schema::Identifier],
) -> TokenStream {
match RefTarget::from_annotations(annotations) {
Some(entity) => {
let marker = raw_ident(to_case(entity.as_ref().name(), Case::Pascal));
quote! { #marker }
}
None => quote! { AnyEntity },
Some(entity) => relative_type_path(entity.as_ref(), source_namespace, ""),
None => relative_root_type("AnyEntity", source_namespace),
}
}

Expand All @@ -95,7 +95,7 @@ mod tests {
for _ in 0..(MAX_TYPE_DEPTH + 5) {
ty = DataType::Option(Box::new(ty));
}
let _ = map_data_type(&ty, 0);
let _ = map_data_type(&ty, 0, &[]);
}

#[test]
Expand All @@ -108,7 +108,7 @@ mod tests {
data: Some(Box::new(DataType::U64)),
annotations: annotations.build().unwrap(),
};
let tokens = map_data_type(&ty, 0).to_string();
let tokens = map_data_type(&ty, 0, &[]).to_string();
assert!(tokens.contains("EntityRef < Cluster , u64 >"), "{tokens}");
}
}
Loading
Loading