From 795181d0ff95dff0220f90d254b12cea88149cfb Mon Sep 17 00:00:00 2001 From: YoEight Date: Fri, 8 May 2026 21:56:35 -0400 Subject: [PATCH 1/2] refact: refactor type declarations and drop custom types --- src/arena.rs | 12 - src/error.rs | 7 +- src/lib.rs | 374 ++++++------------ src/tests/analysis.rs | 36 +- src/tests/mod.rs | 2 - .../resources/type_conversion_custom_type.eql | 2 - ...e_invalid_type_conversion_custom_type.snap | 10 - ...yze_valid_type_conversion_custom_type.snap | 78 ---- src/typing/analysis.rs | 56 +-- src/typing/mod.rs | 18 - 10 files changed, 150 insertions(+), 445 deletions(-) delete mode 100644 src/tests/resources/type_conversion_custom_type.eql delete mode 100644 src/tests/snapshots/eventql_parser__tests__analysis__analyze_invalid_type_conversion_custom_type.snap delete mode 100644 src/tests/snapshots/eventql_parser__tests__analysis__analyze_valid_type_conversion_custom_type.snap diff --git a/src/arena.rs b/src/arena.rs index fe87399..4f70836 100644 --- a/src/arena.rs +++ b/src/arena.rs @@ -39,13 +39,6 @@ impl StringArena { self.cache.get(&self.hasher.hash_one(value)).copied() } - /// Like ['str_ref'] but case-insensitive - pub fn str_ref_no_case(&self, value: &str) -> Option { - self.cache - .get(&self.hasher.hash_one(Ascii::new(value))) - .copied() - } - /// Interns a string using case-insensitive hashing. /// /// Two strings that differ only in ASCII case will resolve to the same [`StrRef`]. @@ -67,11 +60,6 @@ impl StringArena { pub fn get(&self, key: StrRef) -> &str { &self.slots[key.0] } - - /// Compares two interned strings for case-insensitive ASCII equality. - pub fn eq_ignore_ascii_case(&self, ka: StrRef, kb: StrRef) -> bool { - self.get(ka).eq_ignore_ascii_case(self.get(kb)) - } } /// An expression node stored in the [`ExprArena`]. diff --git a/src/error.rs b/src/error.rs index 7e2c3b3..b1d4bd3 100644 --- a/src/error.rs +++ b/src/error.rs @@ -199,10 +199,9 @@ pub enum AnalysisError { #[error("{0}:{1}: expected a record")] ExpectRecordLiteral(u32, u32), - /// When a custom type (meaning a type not supported by EventQL by default) is used but - /// not registered in the `AnalysisOptions` custom type set. - #[error("{0}:{1}: unsupported custom type '{2}'")] - UnsupportedCustomType(u32, u32, String), + /// When a type is not supported by EventQL. + #[error("{0}:{1}: unknown type '{2}'")] + UnknownType(u32, u32, String), /// A function was called with the wrong number of arguments. /// diff --git a/src/lib.rs b/src/lib.rs index 39143c2..513ccb5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -74,32 +74,25 @@ impl<'a, const N: usize> From<&'a [Type; N]> for FunArgsBuilder<'a> { /// Builder for configuring type information on a [`SessionBuilder`]. /// /// 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, +/// to define a record-shaped type. Call [`done`](EventTypeBuilder::done) to return to the [`SessionBuilder`]. +pub struct EventTypeBuilder<'a> { + parent: &'a mut SessionBuilder, } -impl EventTypeBuilder { +impl<'a> EventTypeBuilder<'a> { /// Starts building a record-shaped event type with named fields. - pub fn define_record(self) -> EventTypeRecordBuilder { + pub fn define_record(self) -> EventTypeRecordBuilder<'a> { EventTypeRecordBuilder { inner: self, props: Default::default(), } } - /// 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 { + pub fn data_source(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); @@ -107,35 +100,8 @@ impl EventTypeBuilder { 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 - } + pub fn done(self) {} } /// Builder for defining the fields of a record-shaped event type. @@ -143,12 +109,12 @@ impl EventTypeBuilder { /// 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, +pub struct EventTypeRecordBuilder<'a> { + inner: EventTypeBuilder<'a>, props: FxHashMap, } -impl EventTypeRecordBuilder { +impl<'a> EventTypeRecordBuilder<'a> { /// Conditionally adds a field to the event record type. pub fn prop_when(mut self, test: bool, name: &str, tpe: Type) -> Self { if test { @@ -166,23 +132,10 @@ impl EventTypeRecordBuilder { self } - /// Conditionally adds a field with a custom type to the event record type. - pub fn prop_with_custom_when(mut self, test: bool, name: &str, tpe: &str) -> Self { - if test { - let tpe = self.inner.parent.arena.strings.alloc(tpe); - self.props.insert( - self.inner.parent.arena.strings.alloc(name), - Type::Custom(tpe), - ); - } - - self - } - /// Finalizes the event record type and returns the [`SessionBuilder`]. - pub fn as_default_event_type(mut self) -> EventTypeBuilder { + pub fn as_default_event_type(self) -> EventTypeBuilder<'a> { let ptr = self.inner.parent.arena.types.alloc_record(self.props); - self.inner.parent.options.default_event_type = Type::Record(ptr); + self.inner.parent.set_default_event_type(Type::Record(ptr)); self.inner } @@ -191,7 +144,7 @@ impl EventTypeRecordBuilder { /// 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 { + pub fn for_data_source(self, data_source: &str) -> EventTypeBuilder<'a> { let data_source = self.inner.parent.arena.strings.alloc_no_case(data_source); let ptr = self.inner.parent.arena.types.alloc_record(self.props); @@ -203,6 +156,11 @@ impl EventTypeRecordBuilder { self.inner } + + pub fn build(self) -> Type { + let ptr = self.inner.parent.arena.types.alloc_record(self.props); + Type::Record(ptr) + } } /// A specialized `Result` type for EventQL parser operations. @@ -211,8 +169,7 @@ pub type Result = std::result::Result; /// `SessionBuilder` is a builder for `Session` objects. /// /// It allows for the configuration of analysis options, such as declaring -/// functions (both regular and aggregate), event types, and custom types, -/// before building an `EventQL` parsing session. +/// functions (both regular and aggregate), and event types before building an `EventQL` parsing session. #[derive(Default)] pub struct SessionBuilder { arena: Arena, @@ -231,52 +188,26 @@ impl SessionBuilder { /// * `args` - The arguments the function accepts, which can be converted into `FunArgs`. /// * `result` - The return type of the function. pub fn declare_func<'a>( - self, - name: &'a str, - args: impl Into>, - result: Type, - ) -> Self { - self.declare_func_when(true, name, args, result) - } - - /// Conditionally declares a new function with the given name, arguments, and return type. - /// - /// This function behaves like `declare_func` but only declares the function - /// if the `test` argument is `true`. This is useful for conditionally - /// including functions based on configuration or features. - /// - /// # Arguments - /// - /// * `test` - A boolean indicating whether to declare the function. - /// * `name` - The name of the function. - /// * `args` - The arguments the function accepts, which can be converted into `FunArgs`. - /// * `result` - The return type of the function. - pub fn declare_func_when<'a>( - mut self, - test: bool, + &mut self, name: &'a str, args: impl Into>, result: Type, - ) -> Self { - if test { - let builder = args.into(); - let name = self.arena.strings.alloc_no_case(name); - let args = self.arena.types.alloc_args(builder.args); - - self.options.default_scope.declare( - name, - Type::App { - args: FunArgs { - values: args, - needed: builder.required, - }, - result: self.arena.types.register_type(result), - aggregate: false, + ) { + let builder = args.into(); + let name = self.arena.strings.alloc_no_case(name); + let args = self.arena.types.alloc_args(builder.args); + + self.options.default_scope.declare( + name, + Type::App { + args: FunArgs { + values: args, + needed: builder.required, }, - ); - } - - self + result: self.arena.types.register_type(result), + aggregate: false, + }, + ); } /// Declares a new aggregate function with the given name, arguments, and return type. @@ -290,51 +221,26 @@ impl SessionBuilder { /// * `args` - The arguments the aggregate function accepts. /// * `result` - The return type of the aggregate function. pub fn declare_agg_func<'a>( - self, + &mut self, name: &'a str, args: impl Into>, result: Type, - ) -> Self { - self.declare_agg_func_when(true, name, args, result) - } - - /// Conditionally declares a new aggregate function. - /// - /// Behaves like `declare_agg_func` but only declares the function - /// if the `test` argument is `true`. - /// - /// # Arguments - /// - /// * `test` - A boolean indicating whether to declare the aggregate function. - /// * `name` - The name of the aggregate function. - /// * `args` - The arguments the aggregate function accepts. - /// * `result` - The return type of the aggregate function. - pub fn declare_agg_func_when<'a>( - mut self, - test: bool, - name: &'a str, - args: impl Into>, - result: Type, - ) -> Self { - if test { - let builder = args.into(); - let name = self.arena.strings.alloc_no_case(name); - let args = self.arena.types.alloc_args(builder.args); - - self.options.default_scope.declare( - name, - Type::App { - args: FunArgs { - values: args, - needed: builder.required, - }, - result: self.arena.types.register_type(result), - aggregate: true, + ) { + let builder = args.into(); + let name = self.arena.strings.alloc_no_case(name); + let args = self.arena.types.alloc_args(builder.args); + + self.options.default_scope.declare( + name, + Type::App { + args: FunArgs { + values: args, + needed: builder.required, }, - ); - } - - self + result: self.arena.types.register_type(result), + aggregate: true, + }, + ); } /// Conditionally declares the expected type of event records. @@ -347,12 +253,8 @@ impl SessionBuilder { /// /// * `test` - A boolean indicating whether to declare the event type. /// * `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.default_event_type = tpe; - } - - self + pub fn set_default_event_type(&mut self, tpe: Type) { + self.options.default_event_type = tpe; } /// Declares the expected type of event records. @@ -363,41 +265,10 @@ impl SessionBuilder { /// # Arguments /// /// * `tpe` - The `Type` representing the structure of event records. - pub fn declare_type(self) -> EventTypeBuilder { + pub fn declare_type(&mut self) -> EventTypeBuilder<'_> { EventTypeBuilder { parent: self } } - /// Conditionally declares a custom type that can be used in EQL queries. - /// - /// This allows the type-checker to recognize and validate custom types - /// that might be used in type conversions or record definitions. - /// The declaration only happens if `test` is `true`. - /// - /// # Arguments - /// - /// * `test` - A boolean indicating whether to declare the custom type. - /// * `name` - The name of the custom type. - pub fn declare_custom_type_when(mut self, test: bool, name: &str) -> Self { - if test { - let name = self.arena.strings.alloc_no_case(name); - self.options.custom_types.insert(name); - } - - self - } - - /// Declares a custom type that can be used in EQL queries. - /// - /// This allows the type-checker to recognize and validate custom types - /// that might be used in type conversions or record definitions. - /// - /// # Arguments - /// - /// * `name` - The name of the custom type. - pub fn declare_custom_type(self, name: &str) -> Self { - self.declare_custom_type_when(true, name) - } - /// Includes the standard library of functions and event types in the session. /// /// This method pre-configures the `SessionBuilder` with a set of commonly @@ -405,66 +276,66 @@ impl SessionBuilder { /// event type definition. Calling this method is equivalent to calling /// `declare_func` and `declare_agg_func` for all standard library functions, /// and `declare_event_type` for the default event structure. - pub fn use_stdlib(self) -> Self { - self.declare_func("abs", &[Type::Number], Type::Number) - .declare_func("ceil", &[Type::Number], Type::Number) - .declare_func("floor", &[Type::Number], Type::Number) - .declare_func("round", &[Type::Number], Type::Number) - .declare_func("cos", &[Type::Number], Type::Number) - .declare_func("exp", &[Type::Number], Type::Number) - .declare_func("pow", &[Type::Number, Type::Number], Type::Number) - .declare_func("sqrt", &[Type::Number], Type::Number) - .declare_func("rand", &[], Type::Number) - .declare_func("pi", &[Type::Number], Type::Number) - .declare_func("lower", &[Type::String], Type::String) - .declare_func("upper", &[Type::String], Type::String) - .declare_func("trim", &[Type::String], Type::String) - .declare_func("ltrim", &[Type::String], Type::String) - .declare_func("rtrim", &[Type::String], Type::String) - .declare_func("len", &[Type::String], Type::Number) - .declare_func("instr", &[Type::String], Type::Number) - .declare_func( - "substring", - &[Type::String, Type::Number, Type::Number], - Type::String, - ) - .declare_func( - "replace", - &[Type::String, Type::String, Type::String], - Type::String, - ) - .declare_func("startswith", &[Type::String, Type::String], Type::Bool) - .declare_func("endswith", &[Type::String, Type::String], Type::Bool) - .declare_func("now", &[], Type::DateTime) - .declare_func("year", &[Type::Date], Type::Number) - .declare_func("month", &[Type::Date], Type::Number) - .declare_func("day", &[Type::Date], Type::Number) - .declare_func("hour", &[Type::Time], Type::Number) - .declare_func("minute", &[Type::Time], Type::Number) - .declare_func("second", &[Type::Time], Type::Number) - .declare_func("weekday", &[Type::Date], Type::Number) - .declare_func( - "IF", - &[Type::Bool, Type::Unspecified, Type::Unspecified], - Type::Unspecified, - ) - .declare_agg_func( - "count", - FunArgsBuilder { - args: &[Type::Bool], - required: 0, - }, - Type::Number, - ) - .declare_agg_func("sum", &[Type::Number], Type::Number) - .declare_agg_func("avg", &[Type::Number], Type::Number) - .declare_agg_func("min", &[Type::Number], Type::Number) - .declare_agg_func("max", &[Type::Number], Type::Number) - .declare_agg_func("median", &[Type::Number], Type::Number) - .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_type() + pub fn use_stdlib(mut self) -> Self { + self.declare_func("abs", &[Type::Number], Type::Number); + self.declare_func("ceil", &[Type::Number], Type::Number); + self.declare_func("floor", &[Type::Number], Type::Number); + self.declare_func("round", &[Type::Number], Type::Number); + self.declare_func("cos", &[Type::Number], Type::Number); + self.declare_func("exp", &[Type::Number], Type::Number); + self.declare_func("pow", &[Type::Number, Type::Number], Type::Number); + self.declare_func("sqrt", &[Type::Number], Type::Number); + self.declare_func("rand", &[], Type::Number); + self.declare_func("pi", &[Type::Number], Type::Number); + self.declare_func("lower", &[Type::String], Type::String); + self.declare_func("upper", &[Type::String], Type::String); + self.declare_func("trim", &[Type::String], Type::String); + self.declare_func("ltrim", &[Type::String], Type::String); + self.declare_func("rtrim", &[Type::String], Type::String); + self.declare_func("len", &[Type::String], Type::Number); + self.declare_func("instr", &[Type::String], Type::Number); + self.declare_func( + "substring", + &[Type::String, Type::Number, Type::Number], + Type::String, + ); + self.declare_func( + "replace", + &[Type::String, Type::String, Type::String], + Type::String, + ); + self.declare_func("startswith", &[Type::String, Type::String], Type::Bool); + self.declare_func("endswith", &[Type::String, Type::String], Type::Bool); + self.declare_func("now", &[], Type::DateTime); + self.declare_func("year", &[Type::Date], Type::Number); + self.declare_func("month", &[Type::Date], Type::Number); + self.declare_func("day", &[Type::Date], Type::Number); + self.declare_func("hour", &[Type::Time], Type::Number); + self.declare_func("minute", &[Type::Time], Type::Number); + self.declare_func("second", &[Type::Time], Type::Number); + self.declare_func("weekday", &[Type::Date], Type::Number); + self.declare_func( + "IF", + &[Type::Bool, Type::Unspecified, Type::Unspecified], + Type::Unspecified, + ); + self.declare_agg_func( + "count", + FunArgsBuilder { + args: &[Type::Bool], + required: 0, + }, + Type::Number, + ); + self.declare_agg_func("sum", &[Type::Number], Type::Number); + self.declare_agg_func("avg", &[Type::Number], Type::Number); + self.declare_agg_func("min", &[Type::Number], Type::Number); + self.declare_agg_func("max", &[Type::Number], Type::Number); + self.declare_agg_func("median", &[Type::Number], Type::Number); + self.declare_agg_func("stddev", &[Type::Number], Type::Number); + self.declare_agg_func("variance", &[Type::Number], Type::Number); + self.declare_agg_func("unique", &[Type::Unspecified], Type::Unspecified); + self.declare_type() .data_source("eventtypes", Type::String) .data_source("subjects", Type::String) .define_record() @@ -481,8 +352,9 @@ impl SessionBuilder { .prop("traceparent", Type::String) .prop("tracestate", Type::String) .prop("signature", Type::String) - .as_default_event_type() - .done() + .as_default_event_type(); + + self } /// Builds the `Session` object with the configured analysis options. @@ -512,7 +384,7 @@ impl Session { /// Creates a new `SessionBuilder` for configuring and building a `Session`. /// /// This is the recommended way to create a `Session` instance, allowing - /// for customization of functions, event types, and custom types. + /// for customization of functions, and event types. /// /// # Returns /// @@ -586,12 +458,12 @@ impl Session { /// Converts a type name string to its corresponding [`Type`] variant. /// - /// This function performs case-insensitive matching for built-in type names and checks - /// against custom types defined in the analysis options. + /// This function performs case-insensitive matching for built-in type names defined + /// in the analysis options. /// /// # Returns /// - /// * `Some(Type)` - If the name matches a built-in type or custom type + /// * `Some(Type)` - If the name matches a built-in type /// * `None` - If the name doesn't match any known type /// /// # Built-in Type Mappings @@ -603,10 +475,8 @@ impl Session { /// - `"date"` → [`Type::Date`] /// - `"time"` → [`Type::Time`] /// - `"datetime"` → [`Type::DateTime`] - /// - /// note: Registered custom types are also recognized (case-insensitive). pub fn resolve_type(&self, name: &str) -> Option { - resolve_type_from_str(&self.arena, &self.options, name) + resolve_type_from_str(name) } /// Provides human-readable string formatting for types. diff --git a/src/tests/analysis.rs b/src/tests/analysis.rs index e891740..faf27a4 100644 --- a/src/tests/analysis.rs +++ b/src/tests/analysis.rs @@ -98,23 +98,11 @@ fn test_analyze_valid_type_conversion() { } #[test] -fn test_analyze_invalid_type_conversion_custom_type() { +fn test_analyze_valid_type_conversion_weird_case() { let mut session = Session::builder().use_stdlib().build(); - let query = session.parse(include_str!("./resources/type_conversion_custom_type.eql")); - insta::assert_yaml_snapshot!(query.and_then(|q| { - session - .run_static_analysis(q) - .map(|q| q.view(&session.arena)) - })); -} - -#[test] -fn test_analyze_valid_type_conversion_custom_type() { - let mut session = Session::builder() - .use_stdlib() - .declare_custom_type("Foobar") - .build(); - let query = session.parse(include_str!("./resources/type_conversion_custom_type.eql")); + let query = session.parse(include_str!( + "./resources/valid_type_conversion-weird-case.eql" + )); insta::assert_yaml_snapshot!(query.and_then(|q| { session .run_static_analysis(q) @@ -123,16 +111,22 @@ fn test_analyze_valid_type_conversion_custom_type() { } #[test] -fn test_analyze_valid_type_conversion_weird_case() { +fn test_analyze_unknown_type_conversion() { let mut session = Session::builder().use_stdlib().build(); - let query = session.parse(include_str!( - "./resources/valid_type_conversion-weird-case.eql" - )); + let query = session.parse("FROM e IN events PROJECT INTO { value: e.data.value AS Foobar }"); + insta::assert_yaml_snapshot!(query.and_then(|q| { session .run_static_analysis(q) .map(|q| q.view(&session.arena)) - })); + }), @r###" + Err: + Analysis: + UnknownType: + - 1 + - 56 + - Foobar + "###); } #[test] diff --git a/src/tests/mod.rs b/src/tests/mod.rs index d0090eb..0b7f60e 100644 --- a/src/tests/mod.rs +++ b/src/tests/mod.rs @@ -234,7 +234,6 @@ pub enum TypeView { Date, Time, DateTime, - Custom(String), Array(Box), Record(BTreeMap), App { @@ -284,7 +283,6 @@ fn project_type(arena: &Arena, tpe: Type) -> TypeView { Type::Date => TypeView::Date, Type::Time => TypeView::Time, Type::DateTime => TypeView::DateTime, - Type::Custom(key) => TypeView::Custom(arena.strings.get(key).to_owned()), Type::Array(arr) => { TypeView::Array(Box::new(project_type(arena, arena.types.get_type(arr)))) diff --git a/src/tests/resources/type_conversion_custom_type.eql b/src/tests/resources/type_conversion_custom_type.eql deleted file mode 100644 index 594c367..0000000 --- a/src/tests/resources/type_conversion_custom_type.eql +++ /dev/null @@ -1,2 +0,0 @@ -FROM e IN events -PROJECT INTO { date: e.data.date as Foobar } diff --git a/src/tests/snapshots/eventql_parser__tests__analysis__analyze_invalid_type_conversion_custom_type.snap b/src/tests/snapshots/eventql_parser__tests__analysis__analyze_invalid_type_conversion_custom_type.snap deleted file mode 100644 index d939599..0000000 --- a/src/tests/snapshots/eventql_parser__tests__analysis__analyze_invalid_type_conversion_custom_type.snap +++ /dev/null @@ -1,10 +0,0 @@ ---- -source: src/tests/analysis.rs -expression: "query.and_then(|q|\n{\n q.run_static_analysis(&arena, &Default::default()).map(|q| q.view(&arena))\n})" ---- -Err: - Analysis: - UnsupportedCustomType: - - 2 - - 37 - - Foobar diff --git a/src/tests/snapshots/eventql_parser__tests__analysis__analyze_valid_type_conversion_custom_type.snap b/src/tests/snapshots/eventql_parser__tests__analysis__analyze_valid_type_conversion_custom_type.snap deleted file mode 100644 index 4452e14..0000000 --- a/src/tests/snapshots/eventql_parser__tests__analysis__analyze_valid_type_conversion_custom_type.snap +++ /dev/null @@ -1,78 +0,0 @@ ---- -source: src/tests/analysis.rs -expression: "query.and_then(|q|\n{\n q.run_static_analysis(&arena,\n &AnalysisOptions::default().add_custom_type(\"Foobar\"),).map(|q|\n q.view(&arena))\n})" ---- -Ok: - attrs: - pos: - line: 1 - col: 1 - sources: - - binding: - name: e - pos: - line: 1 - col: 6 - kind: - Name: events - predicate: ~ - group_by: ~ - order_by: ~ - limit: ~ - projection: - attrs: - pos: - line: 2 - col: 14 - value: - Record: - - attrs: - pos: - line: 2 - col: 16 - name: date - value: - attrs: - pos: - line: 2 - col: 22 - value: - Binary: - lhs: - attrs: - pos: - line: 2 - col: 22 - value: - Access: - target: - attrs: - pos: - line: 2 - col: 22 - value: - Access: - target: - attrs: - pos: - line: 2 - col: 22 - value: - Id: e - field: data - field: date - operator: As - rhs: - attrs: - pos: - line: 2 - col: 37 - value: - Id: Foobar - distinct: false - meta: - project: - Record: - date: - Custom: Foobar - aggregate: false diff --git a/src/typing/analysis.rs b/src/typing/analysis.rs index e0cd0c5..e7b5052 100644 --- a/src/typing/analysis.rs +++ b/src/typing/analysis.rs @@ -1,6 +1,6 @@ use rustc_hash::FxHashMap; use serde::Serialize; -use std::{collections::HashSet, mem}; +use std::mem; use crate::arena::Arena; use crate::typing::{Record, Type}; @@ -54,13 +54,6 @@ pub struct AnalysisOptions { pub default_scope: Scope, /// Type information for event records being queried. 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, - /// Per-data-source type overrides. /// /// When a query targets a named data source, this map is checked first. If a match is @@ -173,17 +166,6 @@ impl<'a> Analysis<'a> { &self.scope } - /// Returns a mutable reference to the current scope. - /// - /// This allows you to modify the scope by adding or removing variable bindings. - /// This is useful when you need to set up custom type environments before - /// analyzing expressions. Note that this only provides access to local variable - /// bindings; global definitions like built-in functions are managed through - /// `AnalysisOptions` and cannot be modified via the scope. - pub fn scope_mut(&mut self) -> &mut Scope { - &mut self.scope - } - fn enter_scope(&mut self) { if self.scope.is_empty() { return; @@ -1108,11 +1090,11 @@ impl<'a> Analysis<'a> { Operator::As => { let rhs = self.arena.exprs.get(binary.rhs); if let Value::Id(name) = rhs.value { - return if let Some(tpe) = resolve_type(self.arena, self.options, name) { + return if let Some(tpe) = resolve_type(self.arena, name) { // NOTE - we could check if it's safe to convert the left branch to that type Ok(tpe) } else { - Err(AnalysisError::UnsupportedCustomType( + Err(AnalysisError::UnknownType( rhs.attrs.pos.line, rhs.attrs.pos.col, self.arena.strings.get(name).to_owned(), @@ -1449,7 +1431,7 @@ impl<'a> Analysis<'a> { Operator::Add | Operator::Sub | Operator::Mul | Operator::Div => Type::Number, Operator::As => { if let Value::Id(n) = self.arena.exprs.get(binary.rhs).value - && let Some(tpe) = resolve_type(self.arena, self.options, n) + && let Some(tpe) = resolve_type(self.arena, n) { tpe } else { @@ -1519,9 +1501,7 @@ impl Arena { (Type::Date, Type::DateTime) => Ok(Type::Date), (Type::DateTime, Type::Time) => Ok(Type::Time), (Type::Time, Type::DateTime) => Ok(Type::Time), - (Type::Custom(a), Type::Custom(b)) if self.strings.eq_ignore_ascii_case(a, b) => { - Ok(Type::Custom(a)) - } + (Type::Array(a), Type::Array(b)) => { let a = self.types.get_type(a); let b = self.types.get_type(b); @@ -1610,12 +1590,11 @@ impl Arena { /// Converts a type name string to its corresponding [`Type`] variant. /// -/// This function performs case-insensitive matching for built-in type names and checks -/// against custom types defined in the analysis options. +/// This function performs case-insensitive matching for built-in type names /// /// # Returns /// -/// * `Some(Type)` - If the name matches a built-in type or custom type +/// * `Some(Type)` - If the name matches a built-in type /// * `None` - If the name doesn't match any known type /// /// # Built-in Type Mappings @@ -1627,13 +1606,7 @@ impl Arena { /// - `"date"` → [`Type::Date`] /// - `"time"` → [`Type::Time`] /// - `"datetime"` → [`Type::DateTime`] -/// -/// note: Registered custom types are also recognized (case-insensitive). -pub(crate) fn resolve_type_from_str( - arena: &Arena, - opts: &AnalysisOptions, - name: &str, -) -> Option { +pub(crate) fn resolve_type_from_str(name: &str) -> Option { if name.eq_ignore_ascii_case("string") { Some(Type::String) } else if name.eq_ignore_ascii_case("int") @@ -1649,22 +1622,14 @@ pub(crate) fn resolve_type_from_str( Some(Type::Time) } else if name.eq_ignore_ascii_case("datetime") { Some(Type::DateTime) - } else if let Some(str_ref) = arena.strings.str_ref_no_case(name) - && opts.custom_types.contains(&str_ref) - { - Some(Type::Custom(str_ref)) } else { None } } -pub(crate) fn resolve_type( - arena: &Arena, - opts: &AnalysisOptions, - name_ref: StrRef, -) -> Option { +pub(crate) fn resolve_type(arena: &Arena, name_ref: StrRef) -> Option { let name = arena.strings.get(name_ref); - resolve_type_from_str(arena, opts, name) + resolve_type_from_str(name) } pub(crate) fn display_type(arena: &Arena, tpe: Type) -> String { @@ -1678,7 +1643,6 @@ pub(crate) fn display_type(arena: &Arena, tpe: Type) -> String { Type::Date => buffer.push_str("Date"), Type::Time => buffer.push_str("Time"), Type::DateTime => buffer.push_str("DateTime"), - Type::Custom(n) => buffer.push_str(arena.strings.get(n)), Type::Array(tpe) => { buffer.push_str("[]"); diff --git a/src/typing/mod.rs b/src/typing/mod.rs index 4305319..1e8b67b 100644 --- a/src/typing/mod.rs +++ b/src/typing/mod.rs @@ -1,4 +1,3 @@ -use crate::StrRef; use serde::Serialize; pub mod analysis; @@ -73,23 +72,6 @@ pub enum Type { /// /// Used when a field is explicitly converted to a datetime using the `AS DATETIME` syntax. DateTime, - /// Custom type not defined in the EventQL reference - /// - /// Used when a field is converted to a custom type registered in the analysis options. - /// The string contains the custom type name as it appears in the query. - /// - /// # Examples - /// - /// ``` - /// use eventql_parser::Session; - /// - /// let mut session = Session::builder() - /// .declare_custom_type("CustomTimestamp") - /// .build(); - /// let query = session.parse("FROM e IN events PROJECT INTO { ts: e.data.timestamp as CustomTimestamp }").unwrap(); - /// let typed_query = session.run_static_analysis(query).unwrap(); - /// ``` - Custom(StrRef), } impl Type { From dd5e6e6dfedfb3ac3b68bd44382a398e295b35e2 Mon Sep 17 00:00:00 2001 From: YoEight Date: Fri, 8 May 2026 22:59:11 -0400 Subject: [PATCH 2/2] fixup --- src/lib.rs | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 513ccb5..fd9e736 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -19,6 +19,7 @@ use crate::prelude::{ Analysis, AnalysisOptions, FunArgs, Scope, Typed, display_type, parse, resolve_type_from_str, }; use crate::token::Token; +use crate::typing::TypeRef; pub use ast::*; use rustc_hash::FxHashMap; pub use typing::Type; @@ -157,9 +158,21 @@ impl<'a> EventTypeRecordBuilder<'a> { self.inner } - pub fn build(self) -> Type { + /// Creates a record type and returns it with its registered type reference. + /// + /// Use the returned [`Type`] where an API expects the record type directly. Use the + /// returned [`TypeRef`] when building another type that needs to point at this record, + /// such as [`Type::Array`]. + /// + /// The [`TypeRef`] belongs to the current [`SessionBuilder`]'s arena and should only + /// be used with types configured through the same builder. + pub fn build(self) -> (Type, TypeRef) { let ptr = self.inner.parent.arena.types.alloc_record(self.props); - Type::Record(ptr) + let tpe = Type::Record(ptr); + + let type_ref = self.inner.parent.arena.types.register_type(tpe); + + (tpe, type_ref) } }