Skip to content
Merged
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 Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]

name = "eventql-parser"
version = "0.1.12"
version = "0.1.13"
authors = ["Yorick Laupa <yo.eight@gmail.com>"]
description = "EventQL Lexer and Parser"
homepage = "https://github.com/YoEight/eventql-parser"
Expand Down
2 changes: 1 addition & 1 deletion src/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -297,7 +297,7 @@ pub enum Order {
/// In `GROUP BY e.age HAVING age > 123`, this would be represented as:
/// - `expr`: expression for `e.age`
/// - `predicate`: `age > 123`
#[derive(Debug, Clone, Serialize)]
#[derive(Debug, Clone, Copy, Serialize)]
pub struct GroupBy {
/// Expression to group by
pub expr: ExprRef,
Expand Down
100 changes: 83 additions & 17 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,33 +71,78 @@ impl<'a, const N: usize> From<&'a [Type; N]> for FunArgsBuilder<'a> {
}
}

/// Builder for configuring event type information on a [`SessionBuilder`].
/// Builder for configuring type information on a [`SessionBuilder`].
///
/// Obtained by calling [`SessionBuilder::declare_event_type`]. Use [`record`](EventTypeBuilder::record)
/// to define a record-shaped event type or [`custom`](EventTypeBuilder::custom) for a named custom type.
/// Obtained by calling [`SessionBuilder::declare_type`]. Use [`define_record`](EventTypeBuilder::define_record)
/// to define a record-shaped type or [`custom`](EventTypeBuilder::custom) for a named custom type.
/// Call [`done`](EventTypeBuilder::done) to return to the [`SessionBuilder`].
pub struct EventTypeBuilder {
parent: SessionBuilder,
}

impl EventTypeBuilder {
/// Starts building a record-shaped event type with named fields.
pub fn record(self) -> EventTypeRecordBuilder {
pub fn define_record(self) -> EventTypeRecordBuilder {
EventTypeRecordBuilder {
inner: self,
props: Default::default(),
}
}

/// Declares a custom (non-record) event type by name.
pub fn custom(self, _name: &str) -> SessionBuilder {
todo!("deal with custom type later")
/// Sets the default event type used when no data source-specific type is found.
pub fn default_event_type(mut self, tpe: Type) -> Self {
self.parent.options.default_event_type = tpe;
self
}

/// Registers a type for a specific named data source.
///
/// Queries targeting `data_source` will use `tpe` for type checking instead of the default event type.
/// Data source names are case-insensitive.
pub fn data_source(mut self, data_source: &str, tpe: Type) -> Self {
let data_source = self.parent.arena.strings.alloc_no_case(data_source);

self.parent.options.data_sources.insert(data_source, tpe);

self
}

/// Declares a custom type by name.
pub fn custom(mut self, name: &str) -> Self {
let name = self.parent.arena.strings.alloc_no_case(name);
self.parent.options.custom_types.insert(name);

self
}

/// Declares a custom event type by name for as default event type.
pub fn custom_default_event_type(mut self, name: &str) -> Self {
let name = self.parent.arena.strings.alloc_no_case(name);
self.parent.options.custom_types.insert(name);

self.parent.options.default_event_type = Type::Custom(name);
self
}

/// Declares a custom event type by name for a data source.
pub fn custom_for_data_source(mut self, name: &str, data_source: &str) -> Self {
let name = self.parent.arena.strings.alloc_no_case(name);
self.parent.options.custom_types.insert(name);

self.data_source(data_source, Type::Custom(name))
}

/// Finalizes type configuration and returns the [`SessionBuilder`].
pub fn done(self) -> SessionBuilder {
self.parent
}
}

/// Builder for defining the fields of a record-shaped event type.
///
/// Obtained by calling [`EventTypeBuilder::record`]. Add fields with [`prop`](EventTypeRecordBuilder::prop)
/// and finalize with [`build`](EventTypeRecordBuilder::build) to return to the [`SessionBuilder`].
/// Obtained by calling [`EventTypeBuilder::define_record`]. Add fields with [`prop`](EventTypeRecordBuilder::prop)
/// and finalize with [`as_default_event_type`](EventTypeRecordBuilder::as_default_event_type) or
/// [`for_data_source`](EventTypeRecordBuilder::for_data_source) to return to the [`EventTypeBuilder`].
pub struct EventTypeRecordBuilder {
inner: EventTypeBuilder,
props: FxHashMap<StrRef, Type>,
Expand Down Expand Up @@ -135,10 +180,28 @@ impl EventTypeRecordBuilder {
}

/// Finalizes the event record type and returns the [`SessionBuilder`].
pub fn build(mut self) -> SessionBuilder {
pub fn as_default_event_type(mut self) -> EventTypeBuilder {
let ptr = self.inner.parent.arena.types.alloc_record(self.props);
self.inner.parent.options.event_type_info = Type::Record(ptr);
self.inner.parent
self.inner.parent.options.default_event_type = Type::Record(ptr);
self.inner
}

/// Finalizes the record type and registers it for a specific named data source.
///
/// Queries targeting `data_source` will use this record type for type checking.
/// Data source names are case-insensitive. Returns the [`EventTypeBuilder`] to allow
/// chaining further type declarations.
pub fn for_data_source(mut self, data_source: &str) -> EventTypeBuilder {
let data_source = self.inner.parent.arena.strings.alloc_no_case(data_source);
let ptr = self.inner.parent.arena.types.alloc_record(self.props);

self.inner
.parent
.options
.data_sources
.insert(data_source, Type::Record(ptr));

self.inner
}
}

Expand Down Expand Up @@ -286,7 +349,7 @@ impl SessionBuilder {
/// * `tpe` - The `Type` representing the structure of event records.
pub fn declare_event_type_when(mut self, test: bool, tpe: Type) -> Self {
if test {
self.options.event_type_info = tpe;
self.options.default_event_type = tpe;
}

self
Expand All @@ -300,7 +363,7 @@ impl SessionBuilder {
/// # Arguments
///
/// * `tpe` - The `Type` representing the structure of event records.
pub fn declare_event_type(self) -> EventTypeBuilder {
pub fn declare_type(self) -> EventTypeBuilder {
EventTypeBuilder { parent: self }
}

Expand Down Expand Up @@ -401,8 +464,10 @@ impl SessionBuilder {
.declare_agg_func("stddev", &[Type::Number], Type::Number)
.declare_agg_func("variance", &[Type::Number], Type::Number)
.declare_agg_func("unique", &[Type::Unspecified], Type::Unspecified)
.declare_event_type()
.record()
.declare_type()
.data_source("eventtypes", Type::String)
.data_source("subjects", Type::String)
.define_record()
.prop("specversion", Type::String)
.prop("id", Type::String)
.prop("time", Type::DateTime)
Expand All @@ -416,7 +481,8 @@ impl SessionBuilder {
.prop("traceparent", Type::String)
.prop("tracestate", Type::String)
.prop("signature", Type::String)
.build()
.as_default_event_type()
.done()
}

/// Builds the `Session` object with the configured analysis options.
Expand Down
2 changes: 1 addition & 1 deletion src/tests/analysis.rs
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ fn test_typecheck_datetime_contravariance_1() {
.parse_expr()
.unwrap();

let event_type = session.options.event_type_info;
let event_type = session.options.default_event_type;
let mut analysis = session.analysis();

analysis.test_declare("e", event_type);
Expand Down
22 changes: 19 additions & 3 deletions src/typing/analysis.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,13 +53,20 @@ pub struct AnalysisOptions {
/// The default scope containing built-in functions and their type signatures.
pub default_scope: Scope,
/// Type information for event records being queried.
pub event_type_info: Type,
pub default_event_type: Type,
/// Custom types that are not defined in the EventQL reference.
///
/// This set allows users to register custom type names that can be used
/// in type conversion expressions (e.g., `field AS CustomType`). Custom
/// type names are case-insensitive.
pub custom_types: HashSet<StrRef>,

/// Per-data-source type overrides.
///
/// When a query targets a named data source, this map is checked first. If a match is
/// found, the associated type is used instead of [`default_event_type`](AnalysisOptions::default_event_type).
/// Keys are case-insensitive data source names.
pub data_sources: FxHashMap<StrRef, Type>,
}

/// Represents a variable scope during static analysis.
Expand Down Expand Up @@ -307,9 +314,18 @@ impl<'a> Analysis<'a> {
fn analyze_source(&mut self, source: Source<Raw>) -> AnalysisResult<Source<Typed>> {
let kind = self.analyze_source_kind(source.kind)?;
let tpe = match &kind {
SourceKind::Name(_) | SourceKind::Subject(_) => {
self.arena.types.alloc_type(self.options.event_type_info)
SourceKind::Name(name) => {
let tpe = if let Some(tpe) = self.options.data_sources.get(name).copied() {
tpe
} else {
self.options.default_event_type
};

self.arena.types.alloc_type(tpe)
}

SourceKind::Subject(_) => self.arena.types.alloc_type(self.options.default_event_type),

SourceKind::Subquery(query) => self.projection_type(query),
};

Expand Down