Skip to content

Commit 1d4b630

Browse files
authored
feat: support defining type per data source (#29)
1 parent a9afa7c commit 1d4b630

5 files changed

Lines changed: 105 additions & 23 deletions

File tree

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[package]
22

33
name = "eventql-parser"
4-
version = "0.1.12"
4+
version = "0.1.13"
55
authors = ["Yorick Laupa <yo.eight@gmail.com>"]
66
description = "EventQL Lexer and Parser"
77
homepage = "https://github.com/YoEight/eventql-parser"

src/ast.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -297,7 +297,7 @@ pub enum Order {
297297
/// In `GROUP BY e.age HAVING age > 123`, this would be represented as:
298298
/// - `expr`: expression for `e.age`
299299
/// - `predicate`: `age > 123`
300-
#[derive(Debug, Clone, Serialize)]
300+
#[derive(Debug, Clone, Copy, Serialize)]
301301
pub struct GroupBy {
302302
/// Expression to group by
303303
pub expr: ExprRef,

src/lib.rs

Lines changed: 83 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -71,33 +71,78 @@ impl<'a, const N: usize> From<&'a [Type; N]> for FunArgsBuilder<'a> {
7171
}
7272
}
7373

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

8283
impl EventTypeBuilder {
8384
/// Starts building a record-shaped event type with named fields.
84-
pub fn record(self) -> EventTypeRecordBuilder {
85+
pub fn define_record(self) -> EventTypeRecordBuilder {
8586
EventTypeRecordBuilder {
8687
inner: self,
8788
props: Default::default(),
8889
}
8990
}
9091

91-
/// Declares a custom (non-record) event type by name.
92-
pub fn custom(self, _name: &str) -> SessionBuilder {
93-
todo!("deal with custom type later")
92+
/// Sets the default event type used when no data source-specific type is found.
93+
pub fn default_event_type(mut self, tpe: Type) -> Self {
94+
self.parent.options.default_event_type = tpe;
95+
self
96+
}
97+
98+
/// Registers a type for a specific named data source.
99+
///
100+
/// Queries targeting `data_source` will use `tpe` for type checking instead of the default event type.
101+
/// Data source names are case-insensitive.
102+
pub fn data_source(mut self, data_source: &str, tpe: Type) -> Self {
103+
let data_source = self.parent.arena.strings.alloc_no_case(data_source);
104+
105+
self.parent.options.data_sources.insert(data_source, tpe);
106+
107+
self
108+
}
109+
110+
/// Declares a custom type by name.
111+
pub fn custom(mut self, name: &str) -> Self {
112+
let name = self.parent.arena.strings.alloc_no_case(name);
113+
self.parent.options.custom_types.insert(name);
114+
115+
self
116+
}
117+
118+
/// Declares a custom event type by name for as default event type.
119+
pub fn custom_default_event_type(mut self, name: &str) -> Self {
120+
let name = self.parent.arena.strings.alloc_no_case(name);
121+
self.parent.options.custom_types.insert(name);
122+
123+
self.parent.options.default_event_type = Type::Custom(name);
124+
self
125+
}
126+
127+
/// Declares a custom event type by name for a data source.
128+
pub fn custom_for_data_source(mut self, name: &str, data_source: &str) -> Self {
129+
let name = self.parent.arena.strings.alloc_no_case(name);
130+
self.parent.options.custom_types.insert(name);
131+
132+
self.data_source(data_source, Type::Custom(name))
133+
}
134+
135+
/// Finalizes type configuration and returns the [`SessionBuilder`].
136+
pub fn done(self) -> SessionBuilder {
137+
self.parent
94138
}
95139
}
96140

97141
/// Builder for defining the fields of a record-shaped event type.
98142
///
99-
/// Obtained by calling [`EventTypeBuilder::record`]. Add fields with [`prop`](EventTypeRecordBuilder::prop)
100-
/// and finalize with [`build`](EventTypeRecordBuilder::build) to return to the [`SessionBuilder`].
143+
/// Obtained by calling [`EventTypeBuilder::define_record`]. Add fields with [`prop`](EventTypeRecordBuilder::prop)
144+
/// and finalize with [`as_default_event_type`](EventTypeRecordBuilder::as_default_event_type) or
145+
/// [`for_data_source`](EventTypeRecordBuilder::for_data_source) to return to the [`EventTypeBuilder`].
101146
pub struct EventTypeRecordBuilder {
102147
inner: EventTypeBuilder,
103148
props: FxHashMap<StrRef, Type>,
@@ -135,10 +180,28 @@ impl EventTypeRecordBuilder {
135180
}
136181

137182
/// Finalizes the event record type and returns the [`SessionBuilder`].
138-
pub fn build(mut self) -> SessionBuilder {
183+
pub fn as_default_event_type(mut self) -> EventTypeBuilder {
139184
let ptr = self.inner.parent.arena.types.alloc_record(self.props);
140-
self.inner.parent.options.event_type_info = Type::Record(ptr);
141-
self.inner.parent
185+
self.inner.parent.options.default_event_type = Type::Record(ptr);
186+
self.inner
187+
}
188+
189+
/// Finalizes the record type and registers it for a specific named data source.
190+
///
191+
/// Queries targeting `data_source` will use this record type for type checking.
192+
/// Data source names are case-insensitive. Returns the [`EventTypeBuilder`] to allow
193+
/// chaining further type declarations.
194+
pub fn for_data_source(mut self, data_source: &str) -> EventTypeBuilder {
195+
let data_source = self.inner.parent.arena.strings.alloc_no_case(data_source);
196+
let ptr = self.inner.parent.arena.types.alloc_record(self.props);
197+
198+
self.inner
199+
.parent
200+
.options
201+
.data_sources
202+
.insert(data_source, Type::Record(ptr));
203+
204+
self.inner
142205
}
143206
}
144207

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

292355
self
@@ -300,7 +363,7 @@ impl SessionBuilder {
300363
/// # Arguments
301364
///
302365
/// * `tpe` - The `Type` representing the structure of event records.
303-
pub fn declare_event_type(self) -> EventTypeBuilder {
366+
pub fn declare_type(self) -> EventTypeBuilder {
304367
EventTypeBuilder { parent: self }
305368
}
306369

@@ -401,8 +464,10 @@ impl SessionBuilder {
401464
.declare_agg_func("stddev", &[Type::Number], Type::Number)
402465
.declare_agg_func("variance", &[Type::Number], Type::Number)
403466
.declare_agg_func("unique", &[Type::Unspecified], Type::Unspecified)
404-
.declare_event_type()
405-
.record()
467+
.declare_type()
468+
.data_source("eventtypes", Type::String)
469+
.data_source("subjects", Type::String)
470+
.define_record()
406471
.prop("specversion", Type::String)
407472
.prop("id", Type::String)
408473
.prop("time", Type::DateTime)
@@ -416,7 +481,8 @@ impl SessionBuilder {
416481
.prop("traceparent", Type::String)
417482
.prop("tracestate", Type::String)
418483
.prop("signature", Type::String)
419-
.build()
484+
.as_default_event_type()
485+
.done()
420486
}
421487

422488
/// Builds the `Session` object with the configured analysis options.

src/tests/analysis.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,7 @@ fn test_typecheck_datetime_contravariance_1() {
204204
.parse_expr()
205205
.unwrap();
206206

207-
let event_type = session.options.event_type_info;
207+
let event_type = session.options.default_event_type;
208208
let mut analysis = session.analysis();
209209

210210
analysis.test_declare("e", event_type);

src/typing/analysis.rs

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,13 +53,20 @@ pub struct AnalysisOptions {
5353
/// The default scope containing built-in functions and their type signatures.
5454
pub default_scope: Scope,
5555
/// Type information for event records being queried.
56-
pub event_type_info: Type,
56+
pub default_event_type: Type,
5757
/// Custom types that are not defined in the EventQL reference.
5858
///
5959
/// This set allows users to register custom type names that can be used
6060
/// in type conversion expressions (e.g., `field AS CustomType`). Custom
6161
/// type names are case-insensitive.
6262
pub custom_types: HashSet<StrRef>,
63+
64+
/// Per-data-source type overrides.
65+
///
66+
/// When a query targets a named data source, this map is checked first. If a match is
67+
/// found, the associated type is used instead of [`default_event_type`](AnalysisOptions::default_event_type).
68+
/// Keys are case-insensitive data source names.
69+
pub data_sources: FxHashMap<StrRef, Type>,
6370
}
6471

6572
/// Represents a variable scope during static analysis.
@@ -307,9 +314,18 @@ impl<'a> Analysis<'a> {
307314
fn analyze_source(&mut self, source: Source<Raw>) -> AnalysisResult<Source<Typed>> {
308315
let kind = self.analyze_source_kind(source.kind)?;
309316
let tpe = match &kind {
310-
SourceKind::Name(_) | SourceKind::Subject(_) => {
311-
self.arena.types.alloc_type(self.options.event_type_info)
317+
SourceKind::Name(name) => {
318+
let tpe = if let Some(tpe) = self.options.data_sources.get(name).copied() {
319+
tpe
320+
} else {
321+
self.options.default_event_type
322+
};
323+
324+
self.arena.types.alloc_type(tpe)
312325
}
326+
327+
SourceKind::Subject(_) => self.arena.types.alloc_type(self.options.default_event_type),
328+
313329
SourceKind::Subquery(query) => self.projection_type(query),
314330
};
315331

0 commit comments

Comments
 (0)