eventql_parser/lib.rs
1//! EventQL parser library for parsing event sourcing query language.
2//!
3//! This library provides a complete lexer and parser for EventQL (EQL), a query language
4//! designed for event sourcing systems. It allows you to parse EQL query strings into
5//! an abstract syntax tree (AST) that can be analyzed or executed.
6pub mod arena;
7mod ast;
8mod error;
9mod lexer;
10mod parser;
11#[cfg(test)]
12mod tests;
13mod token;
14mod typing;
15
16use crate::arena::Arena;
17use crate::lexer::tokenize;
18use crate::prelude::{
19 Analysis, AnalysisOptions, FunArgs, Scope, Typed, display_type, parse, resolve_type_from_str,
20};
21use crate::token::Token;
22pub use ast::*;
23use rustc_hash::FxHashMap;
24pub use typing::Type;
25
26/// Convenience module that re-exports all public types and functions.
27///
28/// This module provides a single import point for all the library's public API,
29/// including AST types, error types, lexer, parser, and token types.
30pub mod prelude {
31 pub use super::arena::*;
32 pub use super::ast::*;
33 pub use super::error::*;
34 pub use super::parser::*;
35 pub use super::token::*;
36 pub use super::typing::analysis::*;
37 pub use super::typing::*;
38}
39
40/// Builder for function argument specifications.
41///
42/// Allows defining function signatures with both required and optional parameters.
43/// When `required` equals the length of `args`, all parameters are required.
44pub struct FunArgsBuilder<'a> {
45 args: &'a [Type],
46 required: usize,
47}
48
49impl<'a> FunArgsBuilder<'a> {
50 /// Creates a new `FunArgsBuilder` with the given argument types and required count.
51 pub fn new(args: &'a [Type], required: usize) -> Self {
52 Self { args, required }
53 }
54}
55
56impl<'a> From<&'a [Type]> for FunArgsBuilder<'a> {
57 fn from(args: &'a [Type]) -> Self {
58 Self {
59 args,
60 required: args.len(),
61 }
62 }
63}
64
65impl<'a, const N: usize> From<&'a [Type; N]> for FunArgsBuilder<'a> {
66 fn from(value: &'a [Type; N]) -> Self {
67 Self {
68 args: value.as_slice(),
69 required: value.len(),
70 }
71 }
72}
73
74/// Builder for configuring type information on a [`SessionBuilder`].
75///
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`].
79pub struct EventTypeBuilder {
80 parent: SessionBuilder,
81}
82
83impl EventTypeBuilder {
84 /// Starts building a record-shaped event type with named fields.
85 pub fn define_record(self) -> EventTypeRecordBuilder {
86 EventTypeRecordBuilder {
87 inner: self,
88 props: Default::default(),
89 }
90 }
91
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
138 }
139}
140
141/// Builder for defining the fields of a record-shaped event type.
142///
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`].
146pub struct EventTypeRecordBuilder {
147 inner: EventTypeBuilder,
148 props: FxHashMap<StrRef, Type>,
149}
150
151impl EventTypeRecordBuilder {
152 /// Conditionally adds a field to the event record type.
153 pub fn prop_when(mut self, test: bool, name: &str, tpe: Type) -> Self {
154 if test {
155 self.props
156 .insert(self.inner.parent.arena.strings.alloc(name), tpe);
157 }
158
159 self
160 }
161
162 /// Adds a field with the given name and type to the event record type.
163 pub fn prop(mut self, name: &str, tpe: Type) -> Self {
164 self.props
165 .insert(self.inner.parent.arena.strings.alloc(name), tpe);
166 self
167 }
168
169 /// Conditionally adds a field with a custom type to the event record type.
170 pub fn prop_with_custom_when(mut self, test: bool, name: &str, tpe: &str) -> Self {
171 if test {
172 let tpe = self.inner.parent.arena.strings.alloc(tpe);
173 self.props.insert(
174 self.inner.parent.arena.strings.alloc(name),
175 Type::Custom(tpe),
176 );
177 }
178
179 self
180 }
181
182 /// Finalizes the event record type and returns the [`SessionBuilder`].
183 pub fn as_default_event_type(mut self) -> EventTypeBuilder {
184 let ptr = self.inner.parent.arena.types.alloc_record(self.props);
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
205 }
206}
207
208/// A specialized `Result` type for EventQL parser operations.
209pub type Result<A> = std::result::Result<A, error::Error>;
210
211/// `SessionBuilder` is a builder for `Session` objects.
212///
213/// It allows for the configuration of analysis options, such as declaring
214/// functions (both regular and aggregate), event types, and custom types,
215/// before building an `EventQL` parsing session.
216#[derive(Default)]
217pub struct SessionBuilder {
218 arena: Arena,
219 options: AnalysisOptions,
220}
221
222impl SessionBuilder {
223 /// Declares a new function with the given name, arguments, and return type.
224 ///
225 /// This function adds a new entry to the session's default scope, allowing
226 /// the parser to recognize and type-check calls to this function.
227 ///
228 /// # Arguments
229 ///
230 /// * `name` - The name of the function.
231 /// * `args` - The arguments the function accepts, which can be converted into `FunArgs`.
232 /// * `result` - The return type of the function.
233 pub fn declare_func<'a>(
234 self,
235 name: &'a str,
236 args: impl Into<FunArgsBuilder<'a>>,
237 result: Type,
238 ) -> Self {
239 self.declare_func_when(true, name, args, result)
240 }
241
242 /// Conditionally declares a new function with the given name, arguments, and return type.
243 ///
244 /// This function behaves like `declare_func` but only declares the function
245 /// if the `test` argument is `true`. This is useful for conditionally
246 /// including functions based on configuration or features.
247 ///
248 /// # Arguments
249 ///
250 /// * `test` - A boolean indicating whether to declare the function.
251 /// * `name` - The name of the function.
252 /// * `args` - The arguments the function accepts, which can be converted into `FunArgs`.
253 /// * `result` - The return type of the function.
254 pub fn declare_func_when<'a>(
255 mut self,
256 test: bool,
257 name: &'a str,
258 args: impl Into<FunArgsBuilder<'a>>,
259 result: Type,
260 ) -> Self {
261 if test {
262 let builder = args.into();
263 let name = self.arena.strings.alloc_no_case(name);
264 let args = self.arena.types.alloc_args(builder.args);
265
266 self.options.default_scope.declare(
267 name,
268 Type::App {
269 args: FunArgs {
270 values: args,
271 needed: builder.required,
272 },
273 result: self.arena.types.register_type(result),
274 aggregate: false,
275 },
276 );
277 }
278
279 self
280 }
281
282 /// Declares a new aggregate function with the given name, arguments, and return type.
283 ///
284 /// Similar to `declare_func`, but marks the function as an aggregate function.
285 /// Aggregate functions have specific rules for where they can be used in an EQL query.
286 ///
287 /// # Arguments
288 ///
289 /// * `name` - The name of the aggregate function.
290 /// * `args` - The arguments the aggregate function accepts.
291 /// * `result` - The return type of the aggregate function.
292 pub fn declare_agg_func<'a>(
293 self,
294 name: &'a str,
295 args: impl Into<FunArgsBuilder<'a>>,
296 result: Type,
297 ) -> Self {
298 self.declare_agg_func_when(true, name, args, result)
299 }
300
301 /// Conditionally declares a new aggregate function.
302 ///
303 /// Behaves like `declare_agg_func` but only declares the function
304 /// if the `test` argument is `true`.
305 ///
306 /// # Arguments
307 ///
308 /// * `test` - A boolean indicating whether to declare the aggregate function.
309 /// * `name` - The name of the aggregate function.
310 /// * `args` - The arguments the aggregate function accepts.
311 /// * `result` - The return type of the aggregate function.
312 pub fn declare_agg_func_when<'a>(
313 mut self,
314 test: bool,
315 name: &'a str,
316 args: impl Into<FunArgsBuilder<'a>>,
317 result: Type,
318 ) -> Self {
319 if test {
320 let builder = args.into();
321 let name = self.arena.strings.alloc_no_case(name);
322 let args = self.arena.types.alloc_args(builder.args);
323
324 self.options.default_scope.declare(
325 name,
326 Type::App {
327 args: FunArgs {
328 values: args,
329 needed: builder.required,
330 },
331 result: self.arena.types.register_type(result),
332 aggregate: true,
333 },
334 );
335 }
336
337 self
338 }
339
340 /// Conditionally declares the expected type of event records.
341 ///
342 /// This type information is crucial for type-checking event properties
343 /// accessed in EQL queries (e.g., `e.id`, `e.data.value`).
344 /// The declaration only happens if `test` is `true`.
345 ///
346 /// # Arguments
347 ///
348 /// * `test` - A boolean indicating whether to declare the event type.
349 /// * `tpe` - The `Type` representing the structure of event records.
350 pub fn declare_event_type_when(mut self, test: bool, tpe: Type) -> Self {
351 if test {
352 self.options.default_event_type = tpe;
353 }
354
355 self
356 }
357
358 /// Declares the expected type of event records.
359 ///
360 /// This type information is crucial for type-checking event properties
361 /// accessed in EQL queries (e.g., `e.id`, `e.data.value`).
362 ///
363 /// # Arguments
364 ///
365 /// * `tpe` - The `Type` representing the structure of event records.
366 pub fn declare_type(self) -> EventTypeBuilder {
367 EventTypeBuilder { parent: self }
368 }
369
370 /// Conditionally declares a custom type that can be used in EQL queries.
371 ///
372 /// This allows the type-checker to recognize and validate custom types
373 /// that might be used in type conversions or record definitions.
374 /// The declaration only happens if `test` is `true`.
375 ///
376 /// # Arguments
377 ///
378 /// * `test` - A boolean indicating whether to declare the custom type.
379 /// * `name` - The name of the custom type.
380 pub fn declare_custom_type_when(mut self, test: bool, name: &str) -> Self {
381 if test {
382 let name = self.arena.strings.alloc_no_case(name);
383 self.options.custom_types.insert(name);
384 }
385
386 self
387 }
388
389 /// Declares a custom type that can be used in EQL queries.
390 ///
391 /// This allows the type-checker to recognize and validate custom types
392 /// that might be used in type conversions or record definitions.
393 ///
394 /// # Arguments
395 ///
396 /// * `name` - The name of the custom type.
397 pub fn declare_custom_type(self, name: &str) -> Self {
398 self.declare_custom_type_when(true, name)
399 }
400
401 /// Includes the standard library of functions and event types in the session.
402 ///
403 /// This method pre-configures the `SessionBuilder` with a set of commonly
404 /// used functions (e.g., mathematical, string, date/time) and a default
405 /// event type definition. Calling this method is equivalent to calling
406 /// `declare_func` and `declare_agg_func` for all standard library functions,
407 /// and `declare_event_type` for the default event structure.
408 pub fn use_stdlib(self) -> Self {
409 self.declare_func("abs", &[Type::Number], Type::Number)
410 .declare_func("ceil", &[Type::Number], Type::Number)
411 .declare_func("floor", &[Type::Number], Type::Number)
412 .declare_func("round", &[Type::Number], Type::Number)
413 .declare_func("cos", &[Type::Number], Type::Number)
414 .declare_func("exp", &[Type::Number], Type::Number)
415 .declare_func("pow", &[Type::Number, Type::Number], Type::Number)
416 .declare_func("sqrt", &[Type::Number], Type::Number)
417 .declare_func("rand", &[], Type::Number)
418 .declare_func("pi", &[Type::Number], Type::Number)
419 .declare_func("lower", &[Type::String], Type::String)
420 .declare_func("upper", &[Type::String], Type::String)
421 .declare_func("trim", &[Type::String], Type::String)
422 .declare_func("ltrim", &[Type::String], Type::String)
423 .declare_func("rtrim", &[Type::String], Type::String)
424 .declare_func("len", &[Type::String], Type::Number)
425 .declare_func("instr", &[Type::String], Type::Number)
426 .declare_func(
427 "substring",
428 &[Type::String, Type::Number, Type::Number],
429 Type::String,
430 )
431 .declare_func(
432 "replace",
433 &[Type::String, Type::String, Type::String],
434 Type::String,
435 )
436 .declare_func("startswith", &[Type::String, Type::String], Type::Bool)
437 .declare_func("endswith", &[Type::String, Type::String], Type::Bool)
438 .declare_func("now", &[], Type::DateTime)
439 .declare_func("year", &[Type::Date], Type::Number)
440 .declare_func("month", &[Type::Date], Type::Number)
441 .declare_func("day", &[Type::Date], Type::Number)
442 .declare_func("hour", &[Type::Time], Type::Number)
443 .declare_func("minute", &[Type::Time], Type::Number)
444 .declare_func("second", &[Type::Time], Type::Number)
445 .declare_func("weekday", &[Type::Date], Type::Number)
446 .declare_func(
447 "IF",
448 &[Type::Bool, Type::Unspecified, Type::Unspecified],
449 Type::Unspecified,
450 )
451 .declare_agg_func(
452 "count",
453 FunArgsBuilder {
454 args: &[Type::Bool],
455 required: 0,
456 },
457 Type::Number,
458 )
459 .declare_agg_func("sum", &[Type::Number], Type::Number)
460 .declare_agg_func("avg", &[Type::Number], Type::Number)
461 .declare_agg_func("min", &[Type::Number], Type::Number)
462 .declare_agg_func("max", &[Type::Number], Type::Number)
463 .declare_agg_func("median", &[Type::Number], Type::Number)
464 .declare_agg_func("stddev", &[Type::Number], Type::Number)
465 .declare_agg_func("variance", &[Type::Number], Type::Number)
466 .declare_agg_func("unique", &[Type::Unspecified], Type::Unspecified)
467 .declare_type()
468 .data_source("eventtypes", Type::String)
469 .data_source("subjects", Type::String)
470 .define_record()
471 .prop("specversion", Type::String)
472 .prop("id", Type::String)
473 .prop("time", Type::DateTime)
474 .prop("source", Type::String)
475 .prop("subject", Type::Subject)
476 .prop("type", Type::String)
477 .prop("datacontenttype", Type::String)
478 .prop("data", Type::Unspecified)
479 .prop("predecessorhash", Type::String)
480 .prop("hash", Type::String)
481 .prop("traceparent", Type::String)
482 .prop("tracestate", Type::String)
483 .prop("signature", Type::String)
484 .as_default_event_type()
485 .done()
486 }
487
488 /// Builds the `Session` object with the configured analysis options.
489 ///
490 /// This consumes the `SessionBuilder` and returns a `Session` instance
491 /// ready for tokenizing, parsing, and analyzing EventQL queries.
492 pub fn build(mut self) -> Session {
493 self.arena.types.freeze();
494
495 Session {
496 arena: self.arena,
497 options: self.options,
498 }
499 }
500}
501
502/// `Session` is the main entry point for parsing and analyzing EventQL queries.
503///
504/// It holds the necessary context, such as the expression arena and analysis options,
505/// to perform lexical analysis, parsing, and static analysis of EQL query strings.
506pub struct Session {
507 arena: Arena,
508 options: AnalysisOptions,
509}
510
511impl Session {
512 /// Creates a new `SessionBuilder` for configuring and building a `Session`.
513 ///
514 /// This is the recommended way to create a `Session` instance, allowing
515 /// for customization of functions, event types, and custom types.
516 ///
517 /// # Returns
518 ///
519 /// A new `SessionBuilder` instance.
520 pub fn builder() -> SessionBuilder {
521 SessionBuilder::default()
522 }
523
524 /// Tokenize an EventQL query string.
525 ///
526 /// This function performs lexical analysis on the input string, converting it
527 /// into a sequence of tokens. Each token includes position information (line
528 /// and column numbers) for error reporting.
529 /// # Recognized Tokens
530 ///
531 /// - **Identifiers**: Alphanumeric names starting with a letter (e.g., `events`, `e`)
532 /// - **Keywords**: Case-insensitive SQL-like keywords detected by the parser
533 /// - **Numbers**: Floating-point literals (e.g., `42`, `3.14`)
534 /// - **Strings**: Double-quoted string literals (e.g., `"hello"`)
535 /// - **Operators**: Arithmetic (`+`, `-`, `*`, `/`), comparison (`==`, `!=`, `<`, `<=`, `>`, `>=`), logical (`AND`, `OR`, `XOR`, `NOT`)
536 /// - **Symbols**: Structural characters (`(`, `)`, `[`, `]`, `{`, `}`, `.`, `,`, `:`)
537 pub fn tokenize<'a>(&self, input: &'a str) -> Result<Vec<Token<'a>>> {
538 let tokens = tokenize(input)?;
539 Ok(tokens)
540 }
541
542 /// Parse an EventQL query string into an abstract syntax tree.
543 ///
544 /// This is the main entry point for parsing EventQL queries. It performs both
545 /// lexical analysis (tokenization) and syntactic analysis (parsing) in a single call.
546 /// # Examples
547 ///
548 /// ```
549 /// use eventql_parser::Session;
550 ///
551 /// // Parse a simple query
552 /// let mut session = Session::builder().use_stdlib().build();
553 /// let query = session.parse("FROM e IN events WHERE e.id == \"1\" PROJECT INTO e").unwrap();
554 /// assert!(query.predicate.is_some());
555 /// ```
556 pub fn parse(&mut self, input: &str) -> Result<Query<Raw>> {
557 let tokens = self.tokenize(input)?;
558 Ok(parse(&mut self.arena, tokens.as_slice())?)
559 }
560
561 /// Performs static analysis on an EventQL query.
562 ///
563 /// This function takes a raw (untyped) query and performs type checking and
564 /// variable scoping analysis. It validates that:
565 /// - All variables are properly declared
566 /// - Types match expected types in expressions and operations
567 /// - Field accesses are valid for their record types
568 /// - Function calls have the correct argument types
569 /// - Aggregate functions are only used in PROJECT INTO clauses
570 /// - Aggregate functions are not mixed with source-bound fields in projections
571 /// - Aggregate function arguments are source-bound fields (not constants or function results)
572 /// - Record literals are non-empty in projection contexts
573 ///
574 /// # Arguments
575 ///
576 /// * `options` - Configuration containing type information and default scope
577 /// * `query` - The raw query to analyze
578 ///
579 /// # Returns
580 ///
581 /// Returns a typed query on success, or an `AnalysisError` if type checking fails.
582 pub fn run_static_analysis(&mut self, query: Query<Raw>) -> Result<Query<Typed>> {
583 let mut analysis = self.analysis();
584 Ok(analysis.analyze_query(query)?)
585 }
586
587 /// Converts a type name string to its corresponding [`Type`] variant.
588 ///
589 /// This function performs case-insensitive matching for built-in type names and checks
590 /// against custom types defined in the analysis options.
591 ///
592 /// # Returns
593 ///
594 /// * `Some(Type)` - If the name matches a built-in type or custom type
595 /// * `None` - If the name doesn't match any known type
596 ///
597 /// # Built-in Type Mappings
598 ///
599 /// The following type names are recognized (case-insensitive):
600 /// - `"string"` → [`Type::String`]
601 /// - `"int"` or `"float64"` → [`Type::Number`]
602 /// - `"boolean"` → [`Type::Bool`]
603 /// - `"date"` → [`Type::Date`]
604 /// - `"time"` → [`Type::Time`]
605 /// - `"datetime"` → [`Type::DateTime`]
606 ///
607 /// note: Registered custom types are also recognized (case-insensitive).
608 pub fn resolve_type(&self, name: &str) -> Option<Type> {
609 resolve_type_from_str(&self.arena, &self.options, name)
610 }
611
612 /// Provides human-readable string formatting for types.
613 ///
614 /// Function types display optional parameters with a `?` suffix. For example,
615 /// a function with signature `(boolean, number?) -> string` accepts 1 or 2 arguments.
616 /// Aggregate functions use `=>` instead of `->` in their signature.
617 pub fn display_type(&self, tpe: Type) -> String {
618 display_type(&self.arena, tpe)
619 }
620
621 /// Creates an [`Analysis`] instance for fine-grained control over static analysis.
622 ///
623 /// Use this when you need to analyze individual expressions or manage scopes manually,
624 /// rather than using [`run_static_analysis`](Session::run_static_analysis) for whole queries.
625 pub fn analysis(&mut self) -> Analysis<'_> {
626 Analysis::new(&mut self.arena, &self.options)
627 }
628
629 /// Returns a reference to the underlying [`Arena`].
630 pub fn arena(&self) -> &Arena {
631 &self.arena
632 }
633
634 /// Returns a mutable reference to the underlying [`Arena`].
635 pub fn arena_mut(&mut self) -> &mut Arena {
636 &mut self.arena
637 }
638
639 /// Returns the global [`Scope`]
640 pub fn global_scope(&self) -> &Scope {
641 &self.options.default_scope
642 }
643}