Skip to main content

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}