From 584e4a9dc62db091304d45f52bd269050dd271d2 Mon Sep 17 00:00:00 2001 From: Bilal Mahmoud <7252775+indietyp@users.noreply.github.com> Date: Sat, 28 Mar 2026 20:08:47 +0100 Subject: [PATCH 1/2] feat: type aware operator selection --- .../src/store/postgres/query/mod.rs | 2 +- libs/@local/hashql/eval/src/postgres/error.rs | 33 ++++ .../hashql/eval/src/postgres/filter/mod.rs | 141 ++++++++++++------ libs/@local/hashql/eval/src/postgres/types.rs | 40 ++++- .../hashql/mir/src/body/rvalue/binary.rs | 2 +- libs/@local/hashql/mir/src/body/rvalue/mod.rs | 2 +- .../hashql/mir/src/body/rvalue/unary.rs | 50 ++++++- libs/@local/hashql/mir/src/builder/rvalue.rs | 4 +- libs/@local/hashql/mir/src/interpret/error.rs | 3 +- .../hashql/mir/src/interpret/runtime.rs | 31 +--- .../pass/analysis/size_estimation/dynamic.rs | 6 +- .../src/pass/transform/inst_simplify/mod.rs | 23 ++- libs/@local/hashql/mir/src/reify/rvalue.rs | 2 +- 13 files changed, 239 insertions(+), 100 deletions(-) diff --git a/libs/@local/graph/postgres-store/src/store/postgres/query/mod.rs b/libs/@local/graph/postgres-store/src/store/postgres/query/mod.rs index fc20c4f8ed6..2b8256f562a 100644 --- a/libs/@local/graph/postgres-store/src/store/postgres/query/mod.rs +++ b/libs/@local/graph/postgres-store/src/store/postgres/query/mod.rs @@ -35,7 +35,7 @@ pub use self::{ Expression, FromItem, FromItemFunctionBuilder, FromItemJoinBuilder, FromItemSubqueryBuilder, FromItemTableBuilder, Function, Identifier, JoinType, PostgresType, SelectExpression, TableName, TableReference, UnaryExpression, UnaryOperator, - VariadicExpression, WhereExpression, WithExpression, + VariadicExpression, VariadicOperator, WhereExpression, WithExpression, }, statement::{ Distinctness, InsertStatementBuilder, SelectStatement, Statement, WindowStatement, diff --git a/libs/@local/hashql/eval/src/postgres/error.rs b/libs/@local/hashql/eval/src/postgres/error.rs index cdce1994c81..d153b955733 100644 --- a/libs/@local/hashql/eval/src/postgres/error.rs +++ b/libs/@local/hashql/eval/src/postgres/error.rs @@ -61,6 +61,11 @@ const GRAPH_READ_TERMINATOR: TerminalDiagnosticCategory = TerminalDiagnosticCate name: "Nested Graph Reads Not Supported in SQL", }; +const AMBIGUOUS_INTEGER_TYPE: TerminalDiagnosticCategory = TerminalDiagnosticCategory { + id: "ambiguous-integer-type", + name: "Cannot Determine Integer Type for SQL Operator Selection", +}; + const MISSING_ISLAND_GRAPH: TerminalDiagnosticCategory = TerminalDiagnosticCategory { id: "missing-island-graph", name: "Missing Island Graph for Body", @@ -93,6 +98,13 @@ pub enum PostgresDiagnosticCategory { ProjectedAssignment, /// A nested graph read terminator reached the SQL backend. GraphReadTerminator, + /// The operand type could not be classified as boolean or integer for SQL operator + /// selection. + /// + /// Boolean and integer operations share a single MIR operator (e.g. `BitNot` covers both + /// logical `NOT` and bitwise `~`), but PostgreSQL requires distinct SQL operators. When the + /// operand type cannot be resolved, the compiler cannot select the correct SQL form. + AmbiguousIntegerType, /// Island analysis did not produce an island graph for a filter body. MissingIslandGraph, } @@ -117,6 +129,7 @@ impl DiagnosticCategory for PostgresDiagnosticCategory { Self::FunctionPointerConstant => Some(&FUNCTION_POINTER_CONSTANT), Self::ProjectedAssignment => Some(&PROJECTED_ASSIGNMENT), Self::GraphReadTerminator => Some(&GRAPH_READ_TERMINATOR), + Self::AmbiguousIntegerType => Some(&AMBIGUOUS_INTEGER_TYPE), Self::MissingIslandGraph => Some(&MISSING_ISLAND_GRAPH), } } @@ -290,6 +303,26 @@ pub(super) fn graph_read_terminator(span: SpanId) -> EvalDiagnostic { diagnostic } +#[coverage(off)] +pub(super) fn ambiguous_integer_type(span: SpanId, operator: &str) -> EvalDiagnostic { + let mut diagnostic = Diagnostic::new( + category(PostgresDiagnosticCategory::AmbiguousIntegerType), + Severity::Bug, + ) + .primary(Label::new( + span, + format!("cannot determine operand type for `{operator}`"), + )); + + diagnostic.add_message(Message::note(format!( + "the `{operator}` operator compiles to different SQL depending on whether the operand is \ + a boolean or an integer, but the type could not be resolved; this indicates a \ + compiler/type-checking bug or an unanticipated type (e.g. a union produced by GVN)" + ))); + + diagnostic +} + #[coverage(off)] pub(super) fn missing_island_graph(span: SpanId) -> EvalDiagnostic { let mut diagnostic = Diagnostic::new( diff --git a/libs/@local/hashql/eval/src/postgres/filter/mod.rs b/libs/@local/hashql/eval/src/postgres/filter/mod.rs index f900b3f08f0..ccf0c2b4060 100644 --- a/libs/@local/hashql/eval/src/postgres/filter/mod.rs +++ b/libs/@local/hashql/eval/src/postgres/filter/mod.rs @@ -24,8 +24,8 @@ use alloc::alloc::Global; use core::alloc::Allocator; use hash_graph_postgres_store::store::postgres::query::{ - self, BinaryExpression, BinaryOperator, Expression, PostgresType, UnaryExpression, - UnaryOperator, + self, BinaryExpression, BinaryOperator, Expression, Function, PostgresType, UnaryExpression, + UnaryOperator, VariadicExpression, VariadicOperator, }; use hashql_core::{ graph::Predecessors as _, @@ -36,7 +36,7 @@ use hashql_core::{ span::SpanId, }; use hashql_diagnostics::DiagnosticIssues; -use hashql_hir::node::operation::{InputOp, UnOp}; +use hashql_hir::node::operation::InputOp; use hashql_mir::{ body::{ Body, @@ -45,7 +45,7 @@ use hashql_mir::{ local::{Local, LocalSnapshotVec}, operand::Operand, place::{FieldIndex, Place, Projection, ProjectionKind}, - rvalue::{Aggregate, AggregateKind, Apply, BinOp, Binary, Input, RValue, Unary}, + rvalue::{Aggregate, AggregateKind, Apply, BinOp, Binary, Input, RValue, UnOp, Unary}, statement::{Assign, Statement, StatementKind}, terminator::{Goto, Return, SwitchInt, SwitchTargets, Target, TerminatorKind}, }, @@ -55,11 +55,12 @@ use hashql_mir::{ use super::{ DatabaseContext, error::{ - closure_aggregate, closure_application, entity_path_resolution, function_pointer_constant, - graph_read_terminator, invalid_env_access, invalid_env_projection, projected_assignment, - unsupported_vertex_type, + ambiguous_integer_type, closure_aggregate, closure_application, entity_path_resolution, + function_pointer_constant, graph_read_terminator, invalid_env_access, + invalid_env_projection, projected_assignment, unsupported_vertex_type, }, traverse::eval_entity_path, + types::{IntegerType, integer_type}, }; use crate::{context::EvalContext, error::EvalDiagnosticIssues}; @@ -410,13 +411,20 @@ impl<'ctx, 'heap, A: Allocator, S: Allocator> GraphReadFilterCompiler<'ctx, 'hea &mut self, db: &mut DatabaseContext<'heap, A>, span: SpanId, - Unary { op, operand }: &Unary<'heap>, + unary @ Unary { op, operand }: &Unary<'heap>, ) -> Expression { let operand = self.compile_operand(db, span, operand); let op = match *op { - UnOp::Not => UnaryOperator::Not, - UnOp::BitNot => UnaryOperator::BitwiseNot, + UnOp::BitNot => match integer_type(self.context.env, self.body, &unary.operand) { + Some(IntegerType::Boolean) => UnaryOperator::Not, + Some(IntegerType::Integer) => UnaryOperator::BitwiseNot, + None => { + self.diagnostics + .push(ambiguous_integer_type(span, UnOp::BitNot.as_str())); + return Expression::Constant(query::Constant::Null); + } + }, UnOp::Neg => UnaryOperator::Negate, }; @@ -430,46 +438,95 @@ impl<'ctx, 'heap, A: Allocator, S: Allocator> GraphReadFilterCompiler<'ctx, 'hea &mut self, db: &mut DatabaseContext<'heap, A>, span: SpanId, - Binary { op, left, right }: &Binary<'heap>, + binary @ Binary { op, left, right }: &Binary<'heap>, ) -> Expression { + struct Operands { + left: Expression, + right: Expression, + } + + impl Operands { + fn cast(self, r#type: PostgresType) -> Self { + Self { + left: self.left.cast(r#type.clone()), + right: self.right.cast(r#type), + } + } + + fn call(self, function: fn(Box) -> Function) -> Self { + Self { + left: Expression::Function(function(Box::new(self.left))), + right: Expression::Function(function(Box::new(self.right))), + } + } + + fn binary(self, op: BinaryOperator) -> Expression { + Expression::Binary(BinaryExpression { + op, + left: Box::new(self.left), + right: Box::new(self.right), + }) + } + + fn variadic(self, op: VariadicOperator) -> Expression { + Expression::Variadic(VariadicExpression { + op, + exprs: vec![self.left, self.right], + }) + } + } + let mut left = self.compile_operand(db, span, left); let mut right = self.compile_operand(db, span, right); + let mut operands = Operands { left, right }; // Operands coming from jsonb extraction are untyped from Postgres' perspective. // Arithmetic and bitwise operators need explicit casts; comparisons work on jsonb // directly. - let (op, cast, function) = match *op { - BinOp::Add => (BinaryOperator::Add, Some(PostgresType::Numeric), None), - BinOp::Sub => (BinaryOperator::Subtract, Some(PostgresType::Numeric), None), - BinOp::BitAnd => (BinaryOperator::BitwiseAnd, Some(PostgresType::BigInt), None), - BinOp::BitOr => (BinaryOperator::BitwiseOr, Some(PostgresType::BigInt), None), - BinOp::Eq => (BinaryOperator::Equal, None, Some(query::Function::ToJson)), - BinOp::Ne => ( - BinaryOperator::NotEqual, - None, - Some(query::Function::ToJson), - ), - BinOp::Lt => (BinaryOperator::Less, None, None), - BinOp::Lte => (BinaryOperator::LessOrEqual, None, None), - BinOp::Gt => (BinaryOperator::Greater, None, None), - BinOp::Gte => (BinaryOperator::GreaterOrEqual, None, None), - }; - - if let Some(target) = cast { - left = left.grouped().cast(target.clone()); - right = right.grouped().cast(target); - } - - if let Some(function) = function { - left = Expression::Function(function(Box::new(left))); - right = Expression::Function(function(Box::new(right))); + match *op { + BinOp::Add => operands + .cast(PostgresType::Numeric) + .binary(BinaryOperator::Add), + BinOp::Sub => operands + .cast(PostgresType::Numeric) + .binary(BinaryOperator::Subtract), + BinOp::BitAnd => match integer_type(self.context.env, self.body, &binary.left) { + Some(IntegerType::Integer) => operands + .cast(PostgresType::BigInt) + .binary(BinaryOperator::BitwiseAnd), + Some(IntegerType::Boolean) => operands + .cast(PostgresType::Boolean) + .variadic(VariadicOperator::And), + None => { + self.diagnostics + .push(ambiguous_integer_type(span, BinOp::BitAnd.as_str())); + return Expression::Constant(query::Constant::Null); + } + }, + BinOp::BitOr => match integer_type(self.context.env, self.body, &binary.left) { + Some(IntegerType::Integer) => operands + .cast(PostgresType::BigInt) + .binary(BinaryOperator::BitwiseOr), + Some(IntegerType::Boolean) => operands + .cast(PostgresType::Boolean) + .variadic(VariadicOperator::Or), + None => { + self.diagnostics + .push(ambiguous_integer_type(span, BinOp::BitOr.as_str())); + return Expression::Constant(query::Constant::Null); + } + }, + BinOp::Eq => operands + .call(query::Function::ToJson) + .binary(BinaryOperator::Equal), + BinOp::Ne => operands + .call(query::Function::ToJson) + .binary(BinaryOperator::NotEqual), + BinOp::Lt => operands.binary(BinaryOperator::Less), + BinOp::Lte => operands.binary(BinaryOperator::LessOrEqual), + BinOp::Gt => operands.binary(BinaryOperator::Greater), + BinOp::Gte => operands.binary(BinaryOperator::GreaterOrEqual), } - - Expression::Binary(BinaryExpression { - op, - left: Box::new(left), - right: Box::new(right), - }) } fn compile_input( diff --git a/libs/@local/hashql/eval/src/postgres/types.rs b/libs/@local/hashql/eval/src/postgres/types.rs index e1dbdf91d99..e0e041d69f5 100644 --- a/libs/@local/hashql/eval/src/postgres/types.rs +++ b/libs/@local/hashql/eval/src/postgres/types.rs @@ -6,9 +6,10 @@ use hashql_core::{ r#type::{ TypeId, environment::Environment, - kind::{Apply, Generic, OpaqueType, TypeKind}, + kind::{Apply, Generic, OpaqueType, PrimitiveType, TypeKind}, }, }; +use hashql_mir::body::{Body, operand::Operand}; /// Recursively navigates a type structure following a sequence of struct field names. /// @@ -116,3 +117,40 @@ pub(crate) fn traverse_struct( } } } + +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub(crate) enum IntegerType { + Boolean, + Integer, +} + +pub(crate) fn integer_type<'heap>( + env: &Environment<'heap>, + body: &Body<'heap>, + operand: &Operand<'heap>, +) -> Option { + match operand { + Operand::Place(place) => { + let r#type = place.type_id(&body.local_decls); + match env.r#type(r#type).kind.primitive()? { + PrimitiveType::Boolean => Some(IntegerType::Boolean), + PrimitiveType::Integer => Some(IntegerType::Integer), + _ => None, + } + } + Operand::Constant(hashql_mir::body::constant::Constant::Int(value)) => { + if value.is_bool() { + Some(IntegerType::Boolean) + } else { + Some(IntegerType::Integer) + } + } + Operand::Constant(hashql_mir::body::constant::Constant::Primitive( + hashql_core::value::Primitive::Boolean(_), + )) => Some(IntegerType::Boolean), + Operand::Constant(hashql_mir::body::constant::Constant::Primitive( + hashql_core::value::Primitive::Integer(_), + )) => Some(IntegerType::Integer), + _ => None, + } +} diff --git a/libs/@local/hashql/mir/src/body/rvalue/binary.rs b/libs/@local/hashql/mir/src/body/rvalue/binary.rs index c6bae1ae131..36b4ea3c8a2 100644 --- a/libs/@local/hashql/mir/src/body/rvalue/binary.rs +++ b/libs/@local/hashql/mir/src/body/rvalue/binary.rs @@ -12,7 +12,7 @@ use crate::body::operand::Operand; /// /// Represents the various operations that can be performed with two operands, /// including arithmetic, comparison, logical, and bitwise operations. -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub enum BinOp { /// The `+` operator (addition). Add, diff --git a/libs/@local/hashql/mir/src/body/rvalue/mod.rs b/libs/@local/hashql/mir/src/body/rvalue/mod.rs index ee98dce73d9..4f2feb7afb7 100644 --- a/libs/@local/hashql/mir/src/body/rvalue/mod.rs +++ b/libs/@local/hashql/mir/src/body/rvalue/mod.rs @@ -15,7 +15,7 @@ pub use self::{ apply::{Apply, ArgIndex, ArgSlice, ArgVec}, binary::{BinOp, Binary}, input::Input, - unary::Unary, + unary::{UnOp, Unary}, }; use crate::body::operand::Operand; diff --git a/libs/@local/hashql/mir/src/body/rvalue/unary.rs b/libs/@local/hashql/mir/src/body/rvalue/unary.rs index 7547b093eb6..b8ae05b7893 100644 --- a/libs/@local/hashql/mir/src/body/rvalue/unary.rs +++ b/libs/@local/hashql/mir/src/body/rvalue/unary.rs @@ -4,10 +4,54 @@ //! produce a single result value. They are used for operations like negation, //! logical NOT, type conversions, and other single-operand transformations. -use hashql_hir::node::operation::UnOp; +use hashql_core::symbol::{Symbol, sym}; use crate::body::operand::Operand; +/// The kinds of unary operators available in HashQL. +/// +/// Represents the various operations that can be performed with a single +/// operand, including bitwise and arithmetic negation. +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +pub enum UnOp { + /// The `~` operator (bitwise not). + BitNot, + /// The `-` operator (negation). + Neg, +} + +impl UnOp { + #[must_use] + pub const fn as_str(self) -> &'static str { + match self { + Self::BitNot => "~", + Self::Neg => "-", + } + } + + #[must_use] + pub const fn as_symbol(self) -> Symbol<'static> { + match self { + Self::BitNot => sym::symbol::tilde, + Self::Neg => sym::symbol::minus, + } + } +} + +impl From for UnOp { + fn from(value: hashql_hir::node::operation::UnOp) -> Self { + // `!` is equivalent to `~` on booleans, but `~` only operates on integers, whereas `!` + // operates on booleans, we are able to collapse that disctinction inside the MIR, because + // we specialize bools to be bit-width 1 integers. + match value { + hashql_hir::node::operation::UnOp::Not | hashql_hir::node::operation::UnOp::BitNot => { + Self::BitNot + } + hashql_hir::node::operation::UnOp::Neg => Self::Neg, + } + } +} + /// A unary operation in the HashQL MIR. /// /// Unary operations represent computations that take a single input operand and @@ -17,10 +61,6 @@ use crate::body::operand::Operand; #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub struct Unary<'heap> { /// The unary operator to apply to the operand. - /// - /// This [`UnOp`] specifies what kind of unary operation to perform, - /// such as arithmetic negation, logical NOT. The operator determines both the computation - /// performed and the result type. pub op: UnOp, /// The operand of the unary operation. diff --git a/libs/@local/hashql/mir/src/builder/rvalue.rs b/libs/@local/hashql/mir/src/builder/rvalue.rs index fe0c0b98fb8..5b6102a9972 100644 --- a/libs/@local/hashql/mir/src/builder/rvalue.rs +++ b/libs/@local/hashql/mir/src/builder/rvalue.rs @@ -6,7 +6,7 @@ use hashql_core::{ symbol::Symbol, r#type::builder::IntoSymbol, }; -use hashql_hir::node::operation::{InputOp, UnOp}; +use hashql_hir::node::operation::InputOp; use super::base::BaseBuilder; use crate::{ @@ -14,7 +14,7 @@ use crate::{ constant::Constant, operand::Operand, place::Place, - rvalue::{Aggregate, AggregateKind, Apply, BinOp, Binary, Input, RValue, Unary}, + rvalue::{Aggregate, AggregateKind, Apply, BinOp, Binary, Input, RValue, UnOp, Unary}, }, def::DefId, }; diff --git a/libs/@local/hashql/mir/src/interpret/error.rs b/libs/@local/hashql/mir/src/interpret/error.rs index 0bbd9c766db..e6ac8eeecf8 100644 --- a/libs/@local/hashql/mir/src/interpret/error.rs +++ b/libs/@local/hashql/mir/src/interpret/error.rs @@ -16,13 +16,12 @@ use hashql_diagnostics::{ diagnostic::Message, severity::Severity, }; -use hashql_hir::node::operation::UnOp; use super::value::{Int, Ptr, Value, ValueTypeName}; use crate::body::{ local::{Local, LocalDecl}, place::FieldIndex, - rvalue::BinOp, + rvalue::{BinOp, UnOp}, }; /// Type alias for interpreter diagnostics. diff --git a/libs/@local/hashql/mir/src/interpret/runtime.rs b/libs/@local/hashql/mir/src/interpret/runtime.rs index 933453a9e72..e65019a4cab 100644 --- a/libs/@local/hashql/mir/src/interpret/runtime.rs +++ b/libs/@local/hashql/mir/src/interpret/runtime.rs @@ -43,7 +43,7 @@ use alloc::{alloc::Global, borrow::Cow}; use core::{alloc::Allocator, debug_assert_matches, hint::cold_path, ops::ControlFlow}; use hashql_core::span::SpanId; -use hashql_hir::node::operation::{InputOp, UnOp}; +use hashql_hir::node::operation::InputOp; use super::{ Inputs, @@ -57,7 +57,7 @@ use crate::{ body::{ Body, basic_block::{BasicBlock, BasicBlockId}, - rvalue::{Apply, BinOp, Binary, Input, RValue, Unary}, + rvalue::{Apply, BinOp, Binary, Input, RValue, UnOp, Unary}, statement::{Assign, StatementKind}, terminator::{Goto, GraphReadHead, Return, SwitchInt, Target, TerminatorKind}, }, @@ -600,31 +600,6 @@ impl<'ctx, 'heap, A: Allocator + Clone> Runtime<'ctx, 'heap, A> { let operand = frame.locals.operand(operand)?; match op { - UnOp::Not => match operand.as_ref() { - Value::Integer(int) if let Some(bool) = int.as_bool() => { - Ok(Value::Integer((!bool).into())) - } - Value::Unit - | Value::Integer(_) - | Value::Number(_) - | Value::String(_) - | Value::Pointer(_) - | Value::Opaque(_) - | Value::Struct(_) - | Value::Tuple(_) - | Value::List(_) - | Value::Dict(_) => { - cold_path(); - - Err(RuntimeError::UnaryTypeMismatch(Box::new( - UnaryTypeMismatch { - op: *op, - expected: TypeName::terse("Boolean"), - value: operand.into_owned(), - }, - ))) - } - }, UnOp::BitNot => match operand.as_ref() { Value::Integer(int) => Ok(Value::Integer(!int)), Value::Unit @@ -641,7 +616,7 @@ impl<'ctx, 'heap, A: Allocator + Clone> Runtime<'ctx, 'heap, A> { Err(RuntimeError::UnaryTypeMismatch(Box::new( UnaryTypeMismatch { op: *op, - expected: TypeName::terse("Integer"), + expected: TypeName::terse("Integer | Boolean"), value: operand.into_owned(), }, ))) diff --git a/libs/@local/hashql/mir/src/pass/analysis/size_estimation/dynamic.rs b/libs/@local/hashql/mir/src/pass/analysis/size_estimation/dynamic.rs index ee2eb1f05b6..74a075019da 100644 --- a/libs/@local/hashql/mir/src/pass/analysis/size_estimation/dynamic.rs +++ b/libs/@local/hashql/mir/src/pass/analysis/size_estimation/dynamic.rs @@ -14,7 +14,7 @@ use hashql_core::{ id::{Id as _, bit_vec::DenseBitSet}, r#type::environment::Environment, }; -use hashql_hir::node::operation::{InputOp, UnOp}; +use hashql_hir::node::operation::InputOp; use super::{ footprint::{BodyFootprint, BodyFootprintSemilattice, Footprint}, @@ -29,7 +29,7 @@ use crate::{ location::Location, operand::Operand, place::{Place, Projection, ProjectionKind}, - rvalue::{Aggregate, Apply, ArgSlice, BinOp, Binary, Input, RValue, Unary}, + rvalue::{Aggregate, Apply, ArgSlice, BinOp, Binary, Input, RValue, UnOp, Unary}, statement::{Assign, Statement, StatementKind}, }, def::{DefId, DefIdSlice}, @@ -225,7 +225,7 @@ impl<'ctx, 'footprints, 'env, 'heap, A: Allocator, C: Allocator> right: _, }) => Eval::Footprint(Footprint::scalar()), RValue::Unary(Unary { - op: UnOp::BitNot | UnOp::Neg | UnOp::Not, + op: UnOp::BitNot | UnOp::Neg, operand: _, }) => Eval::Footprint(Footprint::scalar()), RValue::Aggregate(Aggregate { kind: _, operands }) => { diff --git a/libs/@local/hashql/mir/src/pass/transform/inst_simplify/mod.rs b/libs/@local/hashql/mir/src/pass/transform/inst_simplify/mod.rs index 9081ad56b10..ade4c6e0626 100644 --- a/libs/@local/hashql/mir/src/pass/transform/inst_simplify/mod.rs +++ b/libs/@local/hashql/mir/src/pass/transform/inst_simplify/mod.rs @@ -96,7 +96,6 @@ use hashql_core::{ id::IdVec, r#type::{environment::Environment, kind::PrimitiveType}, }; -use hashql_hir::node::operation::UnOp; use super::copy_propagation::propagate_block_params; use crate::{ @@ -107,7 +106,7 @@ use crate::{ location::Location, operand::Operand, place::Place, - rvalue::{Aggregate, AggregateKind, BinOp, Binary, RValue, Unary}, + rvalue::{Aggregate, AggregateKind, BinOp, Binary, RValue, UnOp, Unary}, statement::Assign, }, context::MirContext, @@ -273,9 +272,7 @@ impl<'heap, A: Allocator> InstSimplifyVisitor<'_, 'heap, A> { /// Evaluates a unary operation on a constant integer. fn eval_un_op(op: UnOp, operand: Int) -> Int { match op { - // Both Not and BitNot use the `!` operator, which dispatches on size: - // booleans get logical NOT, integers get bitwise NOT. - UnOp::Not | UnOp::BitNot => !operand, + UnOp::BitNot => !operand, UnOp::Neg => Int::from(-operand.as_int()), } } @@ -322,17 +319,17 @@ impl<'heap, A: Allocator> InstSimplifyVisitor<'_, 'heap, A> { (BinOp::BitOr, _) => None, // true == rhs => rhs (boolean equivalence) (BinOp::Eq, 1) if is_bool => Some(RValue::Load(Operand::Place(rhs))), - // false == rhs => !rhs (boolean equivalence) + // false == rhs => !rhs == ~rhs (boolean equivalence) (BinOp::Eq, 0) if is_bool => Some(RValue::Unary(Unary { - op: UnOp::Not, + op: UnOp::BitNot, operand: Operand::Place(rhs), })), (BinOp::Eq, _) => None, // false != rhs => rhs (boolean equivalence) (BinOp::Ne, 0) if is_bool => Some(RValue::Load(Operand::Place(rhs))), - // true != rhs => !rhs (boolean equivalence) + // true != rhs => !rhs == ~rhs (boolean equivalence) (BinOp::Ne, 1) if is_bool => Some(RValue::Unary(Unary { - op: UnOp::Not, + op: UnOp::BitNot, operand: Operand::Place(rhs), })), (BinOp::Ne, _) => None, @@ -382,17 +379,17 @@ impl<'heap, A: Allocator> InstSimplifyVisitor<'_, 'heap, A> { (BinOp::BitOr, _) => None, // lhs == true => lhs (boolean equivalence) (BinOp::Eq, 1) if is_bool => Some(RValue::Load(Operand::Place(lhs))), - // lhs == false => !lhs (boolean equivalence) + // lhs == false => !lhs == ~lhs (boolean equivalence) (BinOp::Eq, 0) if is_bool => Some(RValue::Unary(Unary { - op: UnOp::Not, + op: UnOp::BitNot, operand: Operand::Place(lhs), })), (BinOp::Eq, _) => None, // lhs != false => lhs (boolean equivalence) (BinOp::Ne, 0) if is_bool => Some(RValue::Load(Operand::Place(lhs))), - // lhs != true => !lhs (boolean equivalence) + // lhs != true => !lhs == ~lhs (boolean equivalence) (BinOp::Ne, 1) if is_bool => Some(RValue::Unary(Unary { - op: UnOp::Not, + op: UnOp::BitNot, operand: Operand::Place(lhs), })), (BinOp::Ne, _) => None, diff --git a/libs/@local/hashql/mir/src/reify/rvalue.rs b/libs/@local/hashql/mir/src/reify/rvalue.rs index 2c811a3b07e..b29a0442b80 100644 --- a/libs/@local/hashql/mir/src/reify/rvalue.rs +++ b/libs/@local/hashql/mir/src/reify/rvalue.rs @@ -153,7 +153,7 @@ impl<'mir, 'heap> Reifier<'_, 'mir, '_, '_, 'heap> { let operand = self.operand(expr); RValue::Unary(Unary { - op: op.value, + op: op.value.into(), operand, }) } From d6dbdd50d6372aaf21974a00e6872217e4ab7fae Mon Sep 17 00:00:00 2001 From: Bilal Mahmoud <7252775+indietyp@users.noreply.github.com> Date: Sat, 28 Mar 2026 22:08:26 +0100 Subject: [PATCH 2/2] chore: format --- Cargo.lock | 201 ++++++++++++++++-- libs/@local/hashql/eval/Cargo.toml | 2 + libs/@local/hashql/eval/src/lib.rs | 4 + .../hashql/eval/src/postgres/filter/mod.rs | 10 +- .../hashql/eval/src/postgres/filter/tests.rs | 123 ++++++++++- .../filter/binary_bitand_bigint_cast.snap | 12 +- .../filter/binary_bitand_boolean_and.snap | 22 ++ .../filter/binary_bitor_bigint_cast.snap | 17 ++ .../filter/binary_bitor_boolean_or.snap | 24 +++ .../filter/binary_sub_numeric_cast.snap | 16 +- .../data_island_provides_without_lateral.snap | 125 +++++++++-- .../ui/postgres/filter/diamond_cfg_merge.snap | 27 ++- .../filter/dynamic_index_projection.snap | 18 +- .../filter/field_by_name_projection.snap | 19 +- .../filter/field_index_projection.snap | 18 +- .../filter/island_exit_empty_arrays.snap | 2 +- .../ui/postgres/filter/island_exit_goto.snap | 23 +- .../filter/island_exit_switch_int.snap | 19 +- .../filter/island_exit_with_live_out.snap | 23 +- .../postgres/filter/left_entity_filter.snap | 19 +- .../filter/nested_property_access.snap | 19 +- .../filter/property_field_equality.snap | 19 +- .../ui/postgres/filter/property_mask.snap | 138 ++++++++++-- .../provides_drives_select_and_joins.snap | 125 +++++++++-- .../filter/straight_line_goto_chain.snap | 6 +- .../filter/switch_int_many_branches.snap | 47 +++- .../temporal_decision_time_interval.snap | 18 +- .../ui/postgres/filter/unary_bitnot.snap | 6 +- .../tests/ui/postgres/filter/unary_neg.snap | 6 +- .../tests/ui/postgres/filter/unary_not.snap | 6 +- libs/@local/hashql/mir/src/builder/mod.rs | 5 +- 31 files changed, 1024 insertions(+), 95 deletions(-) create mode 100644 libs/@local/hashql/eval/tests/ui/postgres/filter/binary_bitand_boolean_and.snap create mode 100644 libs/@local/hashql/eval/tests/ui/postgres/filter/binary_bitor_bigint_cast.snap create mode 100644 libs/@local/hashql/eval/tests/ui/postgres/filter/binary_bitor_boolean_or.snap diff --git a/Cargo.lock b/Cargo.lock index d550ed232d1..8ba0b22be01 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -224,7 +224,7 @@ version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -248,7 +248,7 @@ checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -1633,6 +1633,12 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "common-path" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2382f75942f4b3be3690fe4f86365e9c853c1587d6ee58212cebf6e2a9ccd101" + [[package]] name = "compact_str" version = "0.9.0" @@ -1656,6 +1662,12 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "configparser" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e57e3272f0190c3f1584272d613719ba5fc7df7f4942fe542e63d949cf3a649b" + [[package]] name = "console" version = "0.15.11" @@ -2142,7 +2154,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ab67060fc6b8ef687992d439ca0fa36e7ed17e9a0b16b25b601e8757df720de" dependencies = [ "data-encoding", - "syn 2.0.117", + "syn 1.0.109", ] [[package]] @@ -2535,6 +2547,18 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "enum_dispatch" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa18ce2bc66555b3218614519ac839ddb759a7d6720732f979ef8d13be147ecd" +dependencies = [ + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "enumflags2" version = "0.7.12" @@ -2580,7 +2604,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -2677,6 +2701,17 @@ dependencies = [ "regex", ] +[[package]] +name = "fancy-regex" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72cf461f865c862bb7dc573f643dd6a2b6842f7c30b07882b56bd148cc2761b8" +dependencies = [ + "bit-set 0.8.0", + "regex-automata", + "regex-syntax", +] + [[package]] name = "fastrand" version = "2.3.0" @@ -3906,6 +3941,8 @@ dependencies = [ "allocator-api2 0.2.21", "equivalent", "foldhash 0.2.0", + "serde", + "serde_core", ] [[package]] @@ -4044,6 +4081,8 @@ dependencies = [ "serde_json", "similar-asserts", "simple-mermaid", + "sqruff-lib", + "sqruff-lib-core", "testcontainers", "testcontainers-modules", "tokio", @@ -4402,7 +4441,7 @@ dependencies = [ "libc", "percent-encoding", "pin-project-lite", - "socket2 0.6.3", + "socket2 0.5.10", "tokio", "tower-service", "tracing", @@ -4779,7 +4818,7 @@ checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" dependencies = [ "hermit-abi", "libc", - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -4941,6 +4980,29 @@ dependencies = [ "rustversion", ] +[[package]] +name = "lazy-regex" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d8e41c97e6bc7ecb552016274b99fbb5d035e8de288c582d9b933af6677bfda" +dependencies = [ + "lazy-regex-proc_macros", + "once_cell", + "regex", +] + +[[package]] +name = "lazy-regex-proc_macros" +version = "3.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4de9c1e1439d8b7b3061b2d209809f447ca33241733d9a3c01eabf2dc8d94358" +dependencies = [ + "proc-macro2", + "quote", + "regex", + "syn 2.0.117", +] + [[package]] name = "lazy_static" version = "1.5.0" @@ -6022,7 +6084,7 @@ version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -7570,7 +7632,7 @@ dependencies = [ "quinn-udp", "rustc-hash", "rustls", - "socket2 0.6.3", + "socket2 0.5.10", "thiserror 2.0.18", "tokio", "tracing", @@ -7607,7 +7669,7 @@ dependencies = [ "cfg_aliases", "libc", "once_cell", - "socket2 0.6.3", + "socket2 0.5.10", "tracing", "windows-sys 0.60.2", ] @@ -7750,7 +7812,7 @@ dependencies = [ "itertools 0.14.0", "kasuari", "lru", - "strum", + "strum 0.27.2", "thiserror 2.0.18", "unicode-segmentation", "unicode-truncate", @@ -7802,7 +7864,7 @@ dependencies = [ "itertools 0.14.0", "line-clipping", "ratatui-core", - "strum", + "strum 0.27.2", "time", "unicode-segmentation", "unicode-width 0.2.2", @@ -8197,7 +8259,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -8658,6 +8720,19 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "serde_yaml" +version = "0.9.34+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" +dependencies = [ + "indexmap 2.13.0", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + [[package]] name = "sha1" version = "0.10.6" @@ -8862,7 +8937,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -8911,6 +8986,70 @@ dependencies = [ "der", ] +[[package]] +name = "sqruff-lib" +version = "0.37.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be5b2c243bc2e0702fb25a8047535e74d0e6dc17d6ce1ba9074b5fe0bc981b0a" +dependencies = [ + "common-path", + "configparser", + "enum_dispatch", + "fancy-regex 0.17.0", + "getrandom 0.2.17", + "hashbrown 0.16.1", + "indexmap 2.13.0", + "itertools 0.14.0", + "lazy-regex", + "log", + "nohash-hasher", + "pretty_assertions", + "rayon", + "regex", + "serde", + "serde_json", + "smol_str", + "sqruff-lib-core", + "sqruff-lib-dialects", + "strum 0.28.0", + "strum_macros 0.28.0", + "walkdir", +] + +[[package]] +name = "sqruff-lib-core" +version = "0.37.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d54c4f22e0cc3a8460b00989ca7a89d22d6872e6638d84e0d2c54dd4d169c15" +dependencies = [ + "enum_dispatch", + "fancy-regex 0.17.0", + "hashbrown 0.16.1", + "indexmap 2.13.0", + "itertools 0.14.0", + "log", + "nohash-hasher", + "pretty_assertions", + "regex-automata", + "smol_str", + "strum 0.28.0", + "strum_macros 0.28.0", + "thiserror 2.0.18", +] + +[[package]] +name = "sqruff-lib-dialects" +version = "0.37.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5d39b894e050e8f23fc2754a5afce97f7a507949eadcc16592b9de6227356e6" +dependencies = [ + "hashbrown 0.16.1", + "itertools 0.14.0", + "serde_yaml", + "sqruff-lib-core", + "strum 0.28.0", +] + [[package]] name = "stable_deref_trait" version = "1.2.1" @@ -9010,9 +9149,15 @@ version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" dependencies = [ - "strum_macros", + "strum_macros 0.27.2", ] +[[package]] +name = "strum" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9628de9b8791db39ceda2b119bbe13134770b56c138ec1d3af810d045c04f9bd" + [[package]] name = "strum_macros" version = "0.27.2" @@ -9025,6 +9170,18 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "strum_macros" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab85eea0270ee17587ed4156089e10b9e6880ee688791d45a905f5b1ca36f664" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "subtle" version = "2.6.1" @@ -9200,10 +9357,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ "fastrand", - "getrandom 0.4.2", + "getrandom 0.3.4", "once_cell", "rustix", - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -9269,7 +9426,7 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8c27177b12a6399ffc08b98f76f7c9a1f4fe9fc967c784c5a071fa8d93cf7e1" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -9321,7 +9478,7 @@ dependencies = [ "anyhow", "base64 0.22.1", "bitflags 2.11.0", - "fancy-regex", + "fancy-regex 0.11.0", "filedescriptor", "finl_unicode", "fixedbitset 0.4.2", @@ -10298,6 +10455,12 @@ dependencies = [ "subtle", ] +[[package]] +name = "unsafe-libyaml" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" + [[package]] name = "unsigned-varint" version = "0.7.2" @@ -10826,7 +10989,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] diff --git a/libs/@local/hashql/eval/Cargo.toml b/libs/@local/hashql/eval/Cargo.toml index 96ae18e8dda..8d865a81ac9 100644 --- a/libs/@local/hashql/eval/Cargo.toml +++ b/libs/@local/hashql/eval/Cargo.toml @@ -46,6 +46,8 @@ insta = { workspace = true } libtest-mimic = { workspace = true } regex = { workspace = true } similar-asserts = { workspace = true } +sqruff-lib = "0.37.3" +sqruff-lib-core = "0.37.3" testcontainers = { workspace = true, features = ["reusable-containers"] } testcontainers-modules = { workspace = true, features = ["postgres"] } diff --git a/libs/@local/hashql/eval/src/lib.rs b/libs/@local/hashql/eval/src/lib.rs index abb9b449033..627b7411bc5 100644 --- a/libs/@local/hashql/eval/src/lib.rs +++ b/libs/@local/hashql/eval/src/lib.rs @@ -18,6 +18,10 @@ impl_trait_in_assoc_type, try_blocks )] +#![cfg_attr(test, feature( + // Library Features + iter_intersperse +))] extern crate alloc; pub mod context; diff --git a/libs/@local/hashql/eval/src/postgres/filter/mod.rs b/libs/@local/hashql/eval/src/postgres/filter/mod.rs index ccf0c2b4060..e82dbcab888 100644 --- a/libs/@local/hashql/eval/src/postgres/filter/mod.rs +++ b/libs/@local/hashql/eval/src/postgres/filter/mod.rs @@ -448,8 +448,8 @@ impl<'ctx, 'heap, A: Allocator, S: Allocator> GraphReadFilterCompiler<'ctx, 'hea impl Operands { fn cast(self, r#type: PostgresType) -> Self { Self { - left: self.left.cast(r#type.clone()), - right: self.right.cast(r#type), + left: self.left.grouped().cast(r#type.clone()), + right: self.right.grouped().cast(r#type), } } @@ -476,9 +476,9 @@ impl<'ctx, 'heap, A: Allocator, S: Allocator> GraphReadFilterCompiler<'ctx, 'hea } } - let mut left = self.compile_operand(db, span, left); - let mut right = self.compile_operand(db, span, right); - let mut operands = Operands { left, right }; + let left = self.compile_operand(db, span, left); + let right = self.compile_operand(db, span, right); + let operands = Operands { left, right }; // Operands coming from jsonb extraction are untyped from Postgres' perspective. // Arithmetic and bitwise operators need explicit casts; comparisons work on jsonb diff --git a/libs/@local/hashql/eval/src/postgres/filter/tests.rs b/libs/@local/hashql/eval/src/postgres/filter/tests.rs index ed184c25007..fd10e99a937 100644 --- a/libs/@local/hashql/eval/src/postgres/filter/tests.rs +++ b/libs/@local/hashql/eval/src/postgres/filter/tests.rs @@ -34,6 +34,8 @@ use hashql_mir::{ }, }; use insta::{Settings, assert_snapshot}; +use sqruff_lib::core::{config::FluffConfig, linter::core::Linter}; +use sqruff_lib_core::dialects::init::DialectKind; use crate::{ context::EvalContext, @@ -156,6 +158,13 @@ fn compile_filter_islands<'heap>(fixture: &Fixture<'heap>, heap: &'heap Heap) -> let mut island_reports = Vec::new(); + let mut linter_config = FluffConfig::default(); + linter_config + .override_dialect(DialectKind::Postgres) + .expect("dialect should be loaded"); + let mut linter = + Linter::new(linter_config, None, None, false).expect("linter should be created"); + for (island_id, entry_block) in postgres_islands { let island = &residual.islands[island_id]; @@ -172,10 +181,21 @@ fn compile_filter_islands<'heap>(fixture: &Fixture<'heap>, heap: &'heap Heap) -> let sql = expression.transpile_to_string(); + let linted = linter + .lint_string(&format!("SELECT {sql}"), None, true) + .expect("should be valid SQL"); + + let mut fixed = linted.fix_string(); + let fixed: String = fixed[7..] + .lines() + .map(|line| &line[4..]) + .intersperse("\n") + .collect(); + island_reports.push(FilterIslandReport { entry_block, target: island.target(), - sql, + sql: fixed, }); } @@ -266,10 +286,24 @@ fn compile_full_query_with_mask<'heap>( "unexpected diagnostics from full compilation", ); + let mut linter_config = FluffConfig::default(); + linter_config + .override_dialect(DialectKind::Postgres) + .expect("dialect should be loaded"); + let mut linter = + Linter::new(linter_config, None, None, false).expect("linter should be created"); + let sql = prepared_query.transpile().to_string(); + let linted = linter + .lint_string(&sql, None, true) + .expect("should be valid SQL"); + let parameters = format!("{}", prepared_query.parameters); - QueryReport { sql, parameters } + QueryReport { + sql: linted.fix_string(), + parameters, + } } fn snapshot_settings() -> Settings { @@ -918,7 +952,7 @@ fn unary_not() { bb0() { x = input.load! "val"; - result = un.! x; + result = un.~ x; return result; } }); @@ -1021,7 +1055,7 @@ fn temporal_decision_time_interval() { assert_snapshot!("temporal_decision_time_interval", report.to_string()); } -/// `BinOp::BitAnd` → `BinaryOperator::BitwiseAnd` with `::bigint` casts on both operands. +/// `BinOp::BitAnd` on integers → `BinaryOperator::BitwiseAnd` with `::bigint` casts. #[test] fn binary_bitand_bigint_cast() { let heap = Heap::new(); @@ -1047,3 +1081,84 @@ fn binary_bitand_bigint_cast() { let _guard = settings.bind_to_scope(); assert_snapshot!("binary_bitand_bigint_cast", report.to_string()); } + +/// `BinOp::BitAnd` on booleans → `VariadicOperator::And` with `::boolean` casts. +#[test] +fn binary_bitand_boolean_and() { + let heap = Heap::new(); + let interner = Interner::new(&heap); + let env = Environment::new(&heap); + + let body = body!(interner, env; [graph::read::filter]@0/2 -> Bool { + decl env: (), vertex: [Opaque sym::path::Entity; ?], + x: Bool, y: Bool, result: Bool; + + bb0() { + x = input.load! "a"; + y = input.load! "b"; + result = bin.& x y; + return result; + } + }); + + let fixture = Fixture::new(&heap, env, body); + let report = compile_filter_islands(&fixture, &heap); + + let settings = snapshot_settings(); + let _guard = settings.bind_to_scope(); + assert_snapshot!("binary_bitand_boolean_and", report.to_string()); +} + +/// `BinOp::BitOr` on integers → `BinaryOperator::BitwiseOr` with `::bigint` casts. +#[test] +fn binary_bitor_bigint_cast() { + let heap = Heap::new(); + let interner = Interner::new(&heap); + let env = Environment::new(&heap); + + let body = body!(interner, env; [graph::read::filter]@0/2 -> Int { + decl env: (), vertex: [Opaque sym::path::Entity; ?], + x: Int, y: Int, result: Int; + + bb0() { + x = input.load! "a"; + y = input.load! "b"; + result = bin.| x y; + return result; + } + }); + + let fixture = Fixture::new(&heap, env, body); + let report = compile_filter_islands(&fixture, &heap); + + let settings = snapshot_settings(); + let _guard = settings.bind_to_scope(); + assert_snapshot!("binary_bitor_bigint_cast", report.to_string()); +} + +/// `BinOp::BitOr` on booleans → `VariadicOperator::Or` with `::boolean` casts. +#[test] +fn binary_bitor_boolean_or() { + let heap = Heap::new(); + let interner = Interner::new(&heap); + let env = Environment::new(&heap); + + let body = body!(interner, env; [graph::read::filter]@0/2 -> Bool { + decl env: (), vertex: [Opaque sym::path::Entity; ?], + x: Bool, y: Bool, result: Bool; + + bb0() { + x = input.load! "a"; + y = input.load! "b"; + result = bin.| x y; + return result; + } + }); + + let fixture = Fixture::new(&heap, env, body); + let report = compile_filter_islands(&fixture, &heap); + + let settings = snapshot_settings(); + let _guard = settings.bind_to_scope(); + assert_snapshot!("binary_bitor_boolean_or", report.to_string()); +} diff --git a/libs/@local/hashql/eval/tests/ui/postgres/filter/binary_bitand_bigint_cast.snap b/libs/@local/hashql/eval/tests/ui/postgres/filter/binary_bitand_bigint_cast.snap index 835cd62fae5..75375d1ab1f 100644 --- a/libs/@local/hashql/eval/tests/ui/postgres/filter/binary_bitand_bigint_cast.snap +++ b/libs/@local/hashql/eval/tests/ui/postgres/filter/binary_bitand_bigint_cast.snap @@ -4,4 +4,14 @@ expression: report.to_string() --- ==================== Island (entry: bb0, target: postgres) ===================== -(ROW(COALESCE(((((($1::jsonb))::bigint) & ((($2::jsonb))::bigint))::boolean), FALSE), NULL, NULL, NULL)::continuation) +( + ROW( + COALESCE( + ((((($1::jsonb))::bigint) & ((($2::jsonb))::bigint))::boolean), + FALSE + ), + NULL, + NULL, + NULL + )::continuation +) diff --git a/libs/@local/hashql/eval/tests/ui/postgres/filter/binary_bitand_boolean_and.snap b/libs/@local/hashql/eval/tests/ui/postgres/filter/binary_bitand_boolean_and.snap new file mode 100644 index 00000000000..1591a9dfdf1 --- /dev/null +++ b/libs/@local/hashql/eval/tests/ui/postgres/filter/binary_bitand_boolean_and.snap @@ -0,0 +1,22 @@ +--- +source: libs/@local/hashql/eval/src/postgres/filter/tests.rs +expression: report.to_string() +--- +==================== Island (entry: bb0, target: postgres) ===================== + +( + ROW( + COALESCE( + ( + ( + (((($1::jsonb))::boolean)) + AND (((($2::jsonb))::boolean)) + )::boolean + ), + FALSE + ), + NULL, + NULL, + NULL + )::continuation +) diff --git a/libs/@local/hashql/eval/tests/ui/postgres/filter/binary_bitor_bigint_cast.snap b/libs/@local/hashql/eval/tests/ui/postgres/filter/binary_bitor_bigint_cast.snap new file mode 100644 index 00000000000..9ca69bb9bef --- /dev/null +++ b/libs/@local/hashql/eval/tests/ui/postgres/filter/binary_bitor_bigint_cast.snap @@ -0,0 +1,17 @@ +--- +source: libs/@local/hashql/eval/src/postgres/filter/tests.rs +expression: report.to_string() +--- +==================== Island (entry: bb0, target: postgres) ===================== + +( + ROW( + COALESCE( + ((((($1::jsonb))::bigint) | ((($2::jsonb))::bigint))::boolean), + FALSE + ), + NULL, + NULL, + NULL + )::continuation +) diff --git a/libs/@local/hashql/eval/tests/ui/postgres/filter/binary_bitor_boolean_or.snap b/libs/@local/hashql/eval/tests/ui/postgres/filter/binary_bitor_boolean_or.snap new file mode 100644 index 00000000000..e20702f6ab3 --- /dev/null +++ b/libs/@local/hashql/eval/tests/ui/postgres/filter/binary_bitor_boolean_or.snap @@ -0,0 +1,24 @@ +--- +source: libs/@local/hashql/eval/src/postgres/filter/tests.rs +expression: report.to_string() +--- +==================== Island (entry: bb0, target: postgres) ===================== + +( + ROW( + COALESCE( + ( + ( + ( + (((($1::jsonb))::boolean)) + OR (((($2::jsonb))::boolean)) + ) + )::boolean + ), + FALSE + ), + NULL, + NULL, + NULL + )::continuation +) diff --git a/libs/@local/hashql/eval/tests/ui/postgres/filter/binary_sub_numeric_cast.snap b/libs/@local/hashql/eval/tests/ui/postgres/filter/binary_sub_numeric_cast.snap index 505deb51e9a..40fd5223429 100644 --- a/libs/@local/hashql/eval/tests/ui/postgres/filter/binary_sub_numeric_cast.snap +++ b/libs/@local/hashql/eval/tests/ui/postgres/filter/binary_sub_numeric_cast.snap @@ -4,4 +4,18 @@ expression: report.to_string() --- ==================== Island (entry: bb0, target: postgres) ===================== -(ROW(COALESCE(((((($1::jsonb))::numeric) - ((($2::jsonb))::numeric))::boolean), FALSE), NULL, NULL, NULL)::continuation) +( + ROW( + COALESCE( + ( + ( + ((($1::jsonb))::numeric) - ((($2::jsonb))::numeric) + )::boolean + ), + FALSE + ), + NULL, + NULL, + NULL + )::continuation +) diff --git a/libs/@local/hashql/eval/tests/ui/postgres/filter/data_island_provides_without_lateral.snap b/libs/@local/hashql/eval/tests/ui/postgres/filter/data_island_provides_without_lateral.snap index 29859b9025d..23cbfa8610f 100644 --- a/libs/@local/hashql/eval/tests/ui/postgres/filter/data_island_provides_without_lateral.snap +++ b/libs/@local/hashql/eval/tests/ui/postgres/filter/data_island_provides_without_lateral.snap @@ -4,25 +4,122 @@ expression: query_report.to_string() --- ===================================== SQL ====================================== -SELECT "entity_editions_0_0_1"."properties" AS "properties", jsonb_build_object(($3::text), jsonb_build_object(($4::text), "entity_temporal_metadata_0_0_0"."web_id", ($5::text), "entity_temporal_metadata_0_0_0"."entity_uuid", ($6::text), "entity_temporal_metadata_0_0_0"."draft_id"), ($7::text), "entity_temporal_metadata_0_0_0"."entity_edition_id") AS "record_id", jsonb_build_object(($8::text), jsonb_build_object(($9::text), (extract(epoch from lower("entity_temporal_metadata_0_0_0"."decision_time")) * 1000)::int8, ($10::text), CASE WHEN upper_inf("entity_temporal_metadata_0_0_0"."decision_time") THEN NULL ELSE (extract(epoch from upper("entity_temporal_metadata_0_0_0"."decision_time")) * 1000)::int8 END), ($11::text), jsonb_build_object(($9::text), (extract(epoch from lower("entity_temporal_metadata_0_0_0"."transaction_time")) * 1000)::int8, ($10::text), CASE WHEN upper_inf("entity_temporal_metadata_0_0_0"."transaction_time") THEN NULL ELSE (extract(epoch from upper("entity_temporal_metadata_0_0_0"."transaction_time")) * 1000)::int8 END)) AS "temporal_versioning", "entity_is_of_type_ids_0_0_2"."entity_type_ids" AS "entity_type_ids", "entity_editions_0_0_1"."archived" AS "archived", "entity_editions_0_0_1"."confidence" AS "confidence", "entity_ids_0_0_3"."provenance" AS "provenance_inferred", "entity_editions_0_0_1"."provenance" AS "provenance_edition", "entity_editions_0_0_1"."property_metadata" AS "property_metadata", "entity_has_left_entity_0_0_4"."left_web_id" AS "left_entity_web_id", "entity_has_left_entity_0_0_4"."left_entity_uuid" AS "left_entity_uuid", "entity_has_right_entity_0_0_5"."right_web_id" AS "right_entity_web_id", "entity_has_right_entity_0_0_5"."right_entity_uuid" AS "right_entity_uuid", "entity_has_left_entity_0_0_4"."confidence" AS "left_entity_confidence", "entity_has_right_entity_0_0_5"."confidence" AS "right_entity_confidence", "entity_has_left_entity_0_0_4"."provenance" AS "left_entity_provenance", "entity_has_right_entity_0_0_5"."provenance" AS "right_entity_provenance" +SELECT + "entity_editions_0_0_1"."properties" AS "properties", + jsonb_build_object( + ($3::text), + jsonb_build_object( + ($4::text), + "entity_temporal_metadata_0_0_0"."web_id", + ($5::text), + "entity_temporal_metadata_0_0_0"."entity_uuid", + ($6::text), + "entity_temporal_metadata_0_0_0"."draft_id" + ), + ($7::text), + "entity_temporal_metadata_0_0_0"."entity_edition_id" + ) AS "record_id", + jsonb_build_object( + ($8::text), + jsonb_build_object( + ($9::text), + ( + extract( + EPOCH FROM lower( + "entity_temporal_metadata_0_0_0"."decision_time" + ) + ) + * 1000 + )::int8, + ($10::text), + CASE + WHEN + upper_inf("entity_temporal_metadata_0_0_0"."decision_time") + THEN NULL + ELSE + ( + extract(EPOCH FROM upper("entity_temporal_metadata_0_0_0"."decision_time")) * 1000 + )::int8 + END + ), + ($11::text), + jsonb_build_object( + ($9::text), + ( + extract( + EPOCH FROM lower( + "entity_temporal_metadata_0_0_0"."transaction_time" + ) + ) + * 1000 + )::int8, + ($10::text), + CASE + WHEN + upper_inf( + "entity_temporal_metadata_0_0_0"."transaction_time" + ) + THEN NULL + ELSE + ( + extract(EPOCH FROM upper("entity_temporal_metadata_0_0_0"."transaction_time")) * 1000 + )::int8 + END + ) + ) AS "temporal_versioning", + "entity_is_of_type_ids_0_0_2"."entity_type_ids" AS "entity_type_ids", + "entity_editions_0_0_1"."archived" AS "archived", + "entity_editions_0_0_1"."confidence" AS "confidence", + "entity_ids_0_0_3"."provenance" AS "provenance_inferred", + "entity_editions_0_0_1"."provenance" AS "provenance_edition", + "entity_editions_0_0_1"."property_metadata" AS "property_metadata", + "entity_has_left_entity_0_0_4"."left_web_id" AS "left_entity_web_id", + "entity_has_left_entity_0_0_4"."left_entity_uuid" AS "left_entity_uuid", + "entity_has_right_entity_0_0_5"."right_web_id" AS "right_entity_web_id", + "entity_has_right_entity_0_0_5"."right_entity_uuid" AS "right_entity_uuid", + "entity_has_left_entity_0_0_4"."confidence" AS "left_entity_confidence", + "entity_has_right_entity_0_0_5"."confidence" AS "right_entity_confidence", + "entity_has_left_entity_0_0_4"."provenance" AS "left_entity_provenance", + "entity_has_right_entity_0_0_5"."provenance" AS "right_entity_provenance" FROM "entity_temporal_metadata" AS "entity_temporal_metadata_0_0_0" INNER JOIN "entity_editions" AS "entity_editions_0_0_1" - ON "entity_editions_0_0_1"."entity_edition_id" = "entity_temporal_metadata_0_0_0"."entity_edition_id" + ON + "entity_editions_0_0_1"."entity_edition_id" + = "entity_temporal_metadata_0_0_0"."entity_edition_id" INNER JOIN "entity_ids" AS "entity_ids_0_0_3" - ON "entity_ids_0_0_3"."web_id" = "entity_temporal_metadata_0_0_0"."web_id" - AND "entity_ids_0_0_3"."entity_uuid" = "entity_temporal_metadata_0_0_0"."entity_uuid" -LEFT OUTER JOIN LATERAL (SELECT jsonb_agg(jsonb_build_object(($12::text), "b", ($13::text), "v")) AS "entity_type_ids" -FROM "entity_is_of_type_ids" AS "eit" -CROSS JOIN UNNEST("eit"."base_urls", ("eit"."versions"::text[])) AS "u"("b", "v") -WHERE "eit"."entity_edition_id" = "entity_temporal_metadata_0_0_0"."entity_edition_id") AS "entity_is_of_type_ids_0_0_2" - ON TRUE + ON + "entity_ids_0_0_3"."web_id" = "entity_temporal_metadata_0_0_0"."web_id" + AND "entity_ids_0_0_3"."entity_uuid" + = "entity_temporal_metadata_0_0_0"."entity_uuid" +LEFT OUTER JOIN LATERAL ( + SELECT + jsonb_agg( + jsonb_build_object(($12::text), "b", ($13::text), "v") + ) AS "entity_type_ids" + FROM "entity_is_of_type_ids" AS "eit" + CROSS JOIN + unnest("eit"."base_urls", ("eit"."versions"::text [])) AS "u" ("b", "v") + WHERE + "eit"."entity_edition_id" + = "entity_temporal_metadata_0_0_0"."entity_edition_id" +) AS "entity_is_of_type_ids_0_0_2" + ON TRUE LEFT OUTER JOIN "entity_has_left_entity" AS "entity_has_left_entity_0_0_4" - ON "entity_has_left_entity_0_0_4"."web_id" = "entity_temporal_metadata_0_0_0"."web_id" - AND "entity_has_left_entity_0_0_4"."entity_uuid" = "entity_temporal_metadata_0_0_0"."entity_uuid" + ON + "entity_has_left_entity_0_0_4"."web_id" + = "entity_temporal_metadata_0_0_0"."web_id" + AND "entity_has_left_entity_0_0_4"."entity_uuid" + = "entity_temporal_metadata_0_0_0"."entity_uuid" LEFT OUTER JOIN "entity_has_right_entity" AS "entity_has_right_entity_0_0_5" - ON "entity_has_right_entity_0_0_5"."web_id" = "entity_temporal_metadata_0_0_0"."web_id" - AND "entity_has_right_entity_0_0_5"."entity_uuid" = "entity_temporal_metadata_0_0_0"."entity_uuid" -WHERE "entity_temporal_metadata_0_0_0"."transaction_time" && ($1::tstzrange) AND "entity_temporal_metadata_0_0_0"."decision_time" && ($2::tstzrange) + ON + "entity_has_right_entity_0_0_5"."web_id" + = "entity_temporal_metadata_0_0_0"."web_id" + AND "entity_has_right_entity_0_0_5"."entity_uuid" + = "entity_temporal_metadata_0_0_0"."entity_uuid" +WHERE + "entity_temporal_metadata_0_0_0"."transaction_time" && ($1::tstzrange) + AND "entity_temporal_metadata_0_0_0"."decision_time" && ($2::tstzrange) + ================================== Parameters ================================== $1: TemporalAxis(Transaction) diff --git a/libs/@local/hashql/eval/tests/ui/postgres/filter/diamond_cfg_merge.snap b/libs/@local/hashql/eval/tests/ui/postgres/filter/diamond_cfg_merge.snap index ce95e99dd42..80b0edf03a9 100644 --- a/libs/@local/hashql/eval/tests/ui/postgres/filter/diamond_cfg_merge.snap +++ b/libs/@local/hashql/eval/tests/ui/postgres/filter/diamond_cfg_merge.snap @@ -4,4 +4,29 @@ expression: report.to_string() --- ==================== Island (entry: bb0, target: postgres) ===================== -CASE WHEN ((($1::jsonb))::int) IS NULL THEN (ROW(COALESCE(((FALSE)::boolean), FALSE), NULL, NULL, NULL)::continuation) WHEN ((($1::jsonb))::int) = 0 THEN (ROW(COALESCE(((0)::boolean), FALSE), NULL, NULL, NULL)::continuation) WHEN ((($1::jsonb))::int) = 1 THEN (ROW(COALESCE(((1)::boolean), FALSE), NULL, NULL, NULL)::continuation) END +CASE + WHEN + ((($1::jsonb))::int) IS NULL + THEN + ( + ROW( + COALESCE(((FALSE)::boolean), FALSE), NULL, NULL, NULL + )::continuation + ) + WHEN + ((($1::jsonb))::int) = 0 + THEN + ( + ROW( + COALESCE(((0)::boolean), FALSE), NULL, NULL, NULL + )::continuation + ) + WHEN + ((($1::jsonb))::int) = 1 + THEN + ( + ROW( + COALESCE(((1)::boolean), FALSE), NULL, NULL, NULL + )::continuation + ) +END diff --git a/libs/@local/hashql/eval/tests/ui/postgres/filter/dynamic_index_projection.snap b/libs/@local/hashql/eval/tests/ui/postgres/filter/dynamic_index_projection.snap index bcb8bcf5e31..c8d6fe6c89d 100644 --- a/libs/@local/hashql/eval/tests/ui/postgres/filter/dynamic_index_projection.snap +++ b/libs/@local/hashql/eval/tests/ui/postgres/filter/dynamic_index_projection.snap @@ -4,4 +4,20 @@ expression: report.to_string() --- ==================== Island (entry: bb0, target: postgres) ===================== -(ROW(COALESCE(((jsonb_extract_path(jsonb_build_array(10, 20, 30), ((($1::jsonb))::text)))::boolean), FALSE), NULL, NULL, NULL)::continuation) +( + ROW( + COALESCE( + ( + ( + JSONB_EXTRACT_PATH( + JSONB_BUILD_ARRAY(10, 20, 30), ((($1::jsonb))::text) + ) + )::boolean + ), + FALSE + ), + NULL, + NULL, + NULL + )::continuation +) diff --git a/libs/@local/hashql/eval/tests/ui/postgres/filter/field_by_name_projection.snap b/libs/@local/hashql/eval/tests/ui/postgres/filter/field_by_name_projection.snap index 71094fb8d98..a04e1a891ad 100644 --- a/libs/@local/hashql/eval/tests/ui/postgres/filter/field_by_name_projection.snap +++ b/libs/@local/hashql/eval/tests/ui/postgres/filter/field_by_name_projection.snap @@ -4,4 +4,21 @@ expression: report.to_string() --- ==================== Island (entry: bb0, target: postgres) ===================== -(ROW(COALESCE(((jsonb_extract_path(jsonb_build_object(($1::text), 10, ($2::text), 20), ((($1::text))::text)))::boolean), FALSE), NULL, NULL, NULL)::continuation) +( + ROW( + COALESCE( + ( + ( + JSONB_EXTRACT_PATH( + JSONB_BUILD_OBJECT(($1::text), 10, ($2::text), 20), + ((($1::text))::text) + ) + )::boolean + ), + FALSE + ), + NULL, + NULL, + NULL + )::continuation +) diff --git a/libs/@local/hashql/eval/tests/ui/postgres/filter/field_index_projection.snap b/libs/@local/hashql/eval/tests/ui/postgres/filter/field_index_projection.snap index f66cabccbbf..c94fb35c1d1 100644 --- a/libs/@local/hashql/eval/tests/ui/postgres/filter/field_index_projection.snap +++ b/libs/@local/hashql/eval/tests/ui/postgres/filter/field_index_projection.snap @@ -4,4 +4,20 @@ expression: report.to_string() --- ==================== Island (entry: bb0, target: postgres) ===================== -(ROW(COALESCE(((jsonb_extract_path(jsonb_build_array(10, 20), ((0)::text)))::boolean), FALSE), NULL, NULL, NULL)::continuation) +( + ROW( + COALESCE( + ( + ( + JSONB_EXTRACT_PATH( + JSONB_BUILD_ARRAY(10, 20), ((0)::text) + ) + )::boolean + ), + FALSE + ), + NULL, + NULL, + NULL + )::continuation +) diff --git a/libs/@local/hashql/eval/tests/ui/postgres/filter/island_exit_empty_arrays.snap b/libs/@local/hashql/eval/tests/ui/postgres/filter/island_exit_empty_arrays.snap index fb7afe651a2..3385b61dfec 100644 --- a/libs/@local/hashql/eval/tests/ui/postgres/filter/island_exit_empty_arrays.snap +++ b/libs/@local/hashql/eval/tests/ui/postgres/filter/island_exit_empty_arrays.snap @@ -4,4 +4,4 @@ expression: report.to_string() --- ==================== Island (entry: bb0, target: postgres) ===================== -(ROW(NULL, 1, ARRAY[]::int[], ARRAY[]::jsonb[])::continuation) +(NULL, 1, ARRAY[]::int [], ARRAY[]::jsonb [])::continuation) diff --git a/libs/@local/hashql/eval/tests/ui/postgres/filter/island_exit_goto.snap b/libs/@local/hashql/eval/tests/ui/postgres/filter/island_exit_goto.snap index efce69a224c..e816593efb1 100644 --- a/libs/@local/hashql/eval/tests/ui/postgres/filter/island_exit_goto.snap +++ b/libs/@local/hashql/eval/tests/ui/postgres/filter/island_exit_goto.snap @@ -4,4 +4,25 @@ expression: report.to_string() --- ==================== Island (entry: bb0, target: postgres) ===================== -(ROW(NULL, 1, ARRAY[8]::int[], ARRAY[jsonb_build_object(($1::text), jsonb_build_object(($2::text), "entity_temporal_metadata_0_0_0"."web_id", ($3::text), "entity_temporal_metadata_0_0_0"."entity_uuid", ($4::text), "entity_temporal_metadata_0_0_0"."draft_id"), ($5::text), "entity_temporal_metadata_0_0_0"."entity_edition_id")]::jsonb[])::continuation) +( + ROW( + NULL, + 1, + ARRAY[8]::int [], + ARRAY[ + JSONB_BUILD_OBJECT( + ($1::text), + JSONB_BUILD_OBJECT( + ($2::text), + "entity_temporal_metadata_0_0_0"."web_id", + ($3::text), + "entity_temporal_metadata_0_0_0"."entity_uuid", + ($4::text), + "entity_temporal_metadata_0_0_0"."draft_id" + ), + ($5::text), + "entity_temporal_metadata_0_0_0"."entity_edition_id" + ) + ]::jsonb [] + )::continuation +) diff --git a/libs/@local/hashql/eval/tests/ui/postgres/filter/island_exit_switch_int.snap b/libs/@local/hashql/eval/tests/ui/postgres/filter/island_exit_switch_int.snap index 819313c4652..35503c23a53 100644 --- a/libs/@local/hashql/eval/tests/ui/postgres/filter/island_exit_switch_int.snap +++ b/libs/@local/hashql/eval/tests/ui/postgres/filter/island_exit_switch_int.snap @@ -4,4 +4,21 @@ expression: report.to_string() --- ==================== Island (entry: bb0, target: postgres) ===================== -CASE WHEN ((($10::jsonb))::int) IS NULL THEN (ROW(COALESCE(((FALSE)::boolean), FALSE), NULL, NULL, NULL)::continuation) WHEN ((($10::jsonb))::int) = 0 THEN (ROW(NULL, 2, ARRAY[]::int[], ARRAY[]::jsonb[])::continuation) WHEN ((($10::jsonb))::int) = 1 THEN (ROW(COALESCE(((1)::boolean), FALSE), NULL, NULL, NULL)::continuation) END +CASE + WHEN + ((($10::jsonb))::int) IS NULL + THEN + ( + ROW( + COALESCE(((FALSE)::boolean), FALSE), NULL, NULL, NULL + )::continuation + ) + WHEN + ((($10::jsonb))::int) = 0 + THEN + (ROW(NULL, 2, ARRAY[]::int [], ARRAY[]::jsonb [])::continuation) + WHEN + ((($10::jsonb))::int) = 1 + THEN + (ROW(NULL, 1, ARRAY[]::int [], ARRAY[]::jsonb [])::continuation) +END diff --git a/libs/@local/hashql/eval/tests/ui/postgres/filter/island_exit_with_live_out.snap b/libs/@local/hashql/eval/tests/ui/postgres/filter/island_exit_with_live_out.snap index 393d7e37651..71ff1bce186 100644 --- a/libs/@local/hashql/eval/tests/ui/postgres/filter/island_exit_with_live_out.snap +++ b/libs/@local/hashql/eval/tests/ui/postgres/filter/island_exit_with_live_out.snap @@ -4,4 +4,25 @@ expression: report.to_string() --- ==================== Island (entry: bb0, target: postgres) ===================== -(ROW(NULL, 1, ARRAY[7]::int[], ARRAY[jsonb_build_object(($1::text), jsonb_build_object(($2::text), "entity_temporal_metadata_0_0_0"."web_id", ($3::text), "entity_temporal_metadata_0_0_0"."entity_uuid", ($4::text), "entity_temporal_metadata_0_0_0"."draft_id"), ($5::text), "entity_temporal_metadata_0_0_0"."entity_edition_id")]::jsonb[])::continuation) +( + ROW( + NULL, + 1, + ARRAY[7]::int [], + ARRAY[ + JSONB_BUILD_OBJECT( + ($1::text), + JSONB_BUILD_OBJECT( + ($2::text), + "entity_temporal_metadata_0_0_0"."web_id", + ($3::text), + "entity_temporal_metadata_0_0_0"."entity_uuid", + ($4::text), + "entity_temporal_metadata_0_0_0"."draft_id" + ), + ($5::text), + "entity_temporal_metadata_0_0_0"."entity_edition_id" + ) + ]::jsonb [] + )::continuation +) diff --git a/libs/@local/hashql/eval/tests/ui/postgres/filter/left_entity_filter.snap b/libs/@local/hashql/eval/tests/ui/postgres/filter/left_entity_filter.snap index 663752ddf3f..038d17bcebc 100644 --- a/libs/@local/hashql/eval/tests/ui/postgres/filter/left_entity_filter.snap +++ b/libs/@local/hashql/eval/tests/ui/postgres/filter/left_entity_filter.snap @@ -4,4 +4,21 @@ expression: report.to_string() --- ==================== Island (entry: bb0, target: postgres) ===================== -(ROW(COALESCE(((to_jsonb("entity_has_left_entity_0_0_1"."left_entity_uuid") = to_jsonb(($1::jsonb)))::boolean), FALSE), NULL, NULL, NULL)::continuation) +( + ROW( + COALESCE( + ( + ( + TO_JSONB( + "entity_has_left_entity_0_0_1"."left_entity_uuid" + ) + = TO_JSONB(($1::jsonb)) + )::boolean + ), + FALSE + ), + NULL, + NULL, + NULL + )::continuation +) diff --git a/libs/@local/hashql/eval/tests/ui/postgres/filter/nested_property_access.snap b/libs/@local/hashql/eval/tests/ui/postgres/filter/nested_property_access.snap index 7901cb57be4..1529398660b 100644 --- a/libs/@local/hashql/eval/tests/ui/postgres/filter/nested_property_access.snap +++ b/libs/@local/hashql/eval/tests/ui/postgres/filter/nested_property_access.snap @@ -4,4 +4,21 @@ expression: report.to_string() --- ==================== Island (entry: bb0, target: postgres) ===================== -(ROW(COALESCE(((to_jsonb(jsonb_extract_path("entity_editions_0_0_1"."properties", ((($1::text))::text), ((($2::text))::text))) = to_jsonb(($3::jsonb)))::boolean), FALSE), NULL, NULL, NULL)::continuation) +( + ROW( + COALESCE( + ( + ( + TO_JSONB( + JSONB_EXTRACT_PATH("entity_editions_0_0_1"."properties", ((($1::text))::text), ((($2::text))::text)) + ) + = TO_JSONB(($3::jsonb)) + )::boolean + ), + FALSE + ), + NULL, + NULL, + NULL + )::continuation +) diff --git a/libs/@local/hashql/eval/tests/ui/postgres/filter/property_field_equality.snap b/libs/@local/hashql/eval/tests/ui/postgres/filter/property_field_equality.snap index 176806be5db..94e17805e05 100644 --- a/libs/@local/hashql/eval/tests/ui/postgres/filter/property_field_equality.snap +++ b/libs/@local/hashql/eval/tests/ui/postgres/filter/property_field_equality.snap @@ -4,4 +4,21 @@ expression: report.to_string() --- ==================== Island (entry: bb0, target: postgres) ===================== -(ROW(COALESCE(((to_jsonb(jsonb_extract_path("entity_editions_0_0_1"."properties", ((($1::text))::text))) = to_jsonb(($2::jsonb)))::boolean), FALSE), NULL, NULL, NULL)::continuation) +( + ROW( + COALESCE( + ( + ( + TO_JSONB( + JSONB_EXTRACT_PATH("entity_editions_0_0_1"."properties", ((($1::text))::text)) + ) + = TO_JSONB(($2::jsonb)) + )::boolean + ), + FALSE + ), + NULL, + NULL, + NULL + )::continuation +) diff --git a/libs/@local/hashql/eval/tests/ui/postgres/filter/property_mask.snap b/libs/@local/hashql/eval/tests/ui/postgres/filter/property_mask.snap index 8b8b8ab6555..5768dc6737a 100644 --- a/libs/@local/hashql/eval/tests/ui/postgres/filter/property_mask.snap +++ b/libs/@local/hashql/eval/tests/ui/postgres/filter/property_mask.snap @@ -4,27 +4,133 @@ expression: report.to_string() --- ===================================== SQL ====================================== -SELECT ("entity_editions_0_0_1"."properties" - $99) AS "properties", jsonb_build_object(($3::text), jsonb_build_object(($4::text), "entity_temporal_metadata_0_0_0"."web_id", ($5::text), "entity_temporal_metadata_0_0_0"."entity_uuid", ($6::text), "entity_temporal_metadata_0_0_0"."draft_id"), ($7::text), "entity_temporal_metadata_0_0_0"."entity_edition_id") AS "record_id", jsonb_build_object(($8::text), jsonb_build_object(($9::text), (extract(epoch from lower("entity_temporal_metadata_0_0_0"."decision_time")) * 1000)::int8, ($10::text), CASE WHEN upper_inf("entity_temporal_metadata_0_0_0"."decision_time") THEN NULL ELSE (extract(epoch from upper("entity_temporal_metadata_0_0_0"."decision_time")) * 1000)::int8 END), ($11::text), jsonb_build_object(($9::text), (extract(epoch from lower("entity_temporal_metadata_0_0_0"."transaction_time")) * 1000)::int8, ($10::text), CASE WHEN upper_inf("entity_temporal_metadata_0_0_0"."transaction_time") THEN NULL ELSE (extract(epoch from upper("entity_temporal_metadata_0_0_0"."transaction_time")) * 1000)::int8 END)) AS "temporal_versioning", "entity_is_of_type_ids_0_0_2"."entity_type_ids" AS "entity_type_ids", "entity_editions_0_0_1"."archived" AS "archived", "entity_editions_0_0_1"."confidence" AS "confidence", "entity_ids_0_0_3"."provenance" AS "provenance_inferred", "entity_editions_0_0_1"."provenance" AS "provenance_edition", ("entity_editions_0_0_1"."property_metadata" - $99) AS "property_metadata", "entity_has_left_entity_0_0_4"."left_web_id" AS "left_entity_web_id", "entity_has_left_entity_0_0_4"."left_entity_uuid" AS "left_entity_uuid", "entity_has_right_entity_0_0_5"."right_web_id" AS "right_entity_web_id", "entity_has_right_entity_0_0_5"."right_entity_uuid" AS "right_entity_uuid", "entity_has_left_entity_0_0_4"."confidence" AS "left_entity_confidence", "entity_has_right_entity_0_0_5"."confidence" AS "right_entity_confidence", "entity_has_left_entity_0_0_4"."provenance" AS "left_entity_provenance", "entity_has_right_entity_0_0_5"."provenance" AS "right_entity_provenance", ("continuation_0_0"."row")."block" AS "continuation_0_0_block", ("continuation_0_0"."row")."locals" AS "continuation_0_0_locals", ("continuation_0_0"."row")."values" AS "continuation_0_0_values" +SELECT + ("entity_editions_0_0_1"."properties" - $99) AS "properties", + jsonb_build_object( + ($3::text), + jsonb_build_object( + ($4::text), + "entity_temporal_metadata_0_0_0"."web_id", + ($5::text), + "entity_temporal_metadata_0_0_0"."entity_uuid", + ($6::text), + "entity_temporal_metadata_0_0_0"."draft_id" + ), + ($7::text), + "entity_temporal_metadata_0_0_0"."entity_edition_id" + ) AS "record_id", + jsonb_build_object( + ($8::text), + jsonb_build_object( + ($9::text), + ( + extract( + EPOCH FROM lower( + "entity_temporal_metadata_0_0_0"."decision_time" + ) + ) + * 1000 + )::int8, + ($10::text), + CASE + WHEN + upper_inf("entity_temporal_metadata_0_0_0"."decision_time") + THEN NULL + ELSE + ( + extract(EPOCH FROM upper("entity_temporal_metadata_0_0_0"."decision_time")) * 1000 + )::int8 + END + ), + ($11::text), + jsonb_build_object( + ($9::text), + ( + extract( + EPOCH FROM lower( + "entity_temporal_metadata_0_0_0"."transaction_time" + ) + ) + * 1000 + )::int8, + ($10::text), + CASE + WHEN + upper_inf( + "entity_temporal_metadata_0_0_0"."transaction_time" + ) + THEN NULL + ELSE + ( + extract(EPOCH FROM upper("entity_temporal_metadata_0_0_0"."transaction_time")) * 1000 + )::int8 + END + ) + ) AS "temporal_versioning", + "entity_is_of_type_ids_0_0_2"."entity_type_ids" AS "entity_type_ids", + "entity_editions_0_0_1"."archived" AS "archived", + "entity_editions_0_0_1"."confidence" AS "confidence", + "entity_ids_0_0_3"."provenance" AS "provenance_inferred", + "entity_editions_0_0_1"."provenance" AS "provenance_edition", + ("entity_editions_0_0_1"."property_metadata" - $99) AS "property_metadata", + "entity_has_left_entity_0_0_4"."left_web_id" AS "left_entity_web_id", + "entity_has_left_entity_0_0_4"."left_entity_uuid" AS "left_entity_uuid", + "entity_has_right_entity_0_0_5"."right_web_id" AS "right_entity_web_id", + "entity_has_right_entity_0_0_5"."right_entity_uuid" AS "right_entity_uuid", + "entity_has_left_entity_0_0_4"."confidence" AS "left_entity_confidence", + "entity_has_right_entity_0_0_5"."confidence" AS "right_entity_confidence", + "entity_has_left_entity_0_0_4"."provenance" AS "left_entity_provenance", + "entity_has_right_entity_0_0_5"."provenance" AS "right_entity_provenance", + ("continuation_0_0"."row")."block" AS "continuation_0_0_block", + ("continuation_0_0"."row")."locals" AS "continuation_0_0_locals", + ("continuation_0_0"."row")."values" AS "continuation_0_0_values" FROM "entity_temporal_metadata" AS "entity_temporal_metadata_0_0_0" INNER JOIN "entity_editions" AS "entity_editions_0_0_1" - ON "entity_editions_0_0_1"."entity_edition_id" = "entity_temporal_metadata_0_0_0"."entity_edition_id" + ON + "entity_editions_0_0_1"."entity_edition_id" + = "entity_temporal_metadata_0_0_0"."entity_edition_id" INNER JOIN "entity_ids" AS "entity_ids_0_0_3" - ON "entity_ids_0_0_3"."web_id" = "entity_temporal_metadata_0_0_0"."web_id" - AND "entity_ids_0_0_3"."entity_uuid" = "entity_temporal_metadata_0_0_0"."entity_uuid" -LEFT OUTER JOIN LATERAL (SELECT jsonb_agg(jsonb_build_object(($12::text), "b", ($13::text), "v")) AS "entity_type_ids" -FROM "entity_is_of_type_ids" AS "eit" -CROSS JOIN UNNEST("eit"."base_urls", ("eit"."versions"::text[])) AS "u"("b", "v") -WHERE "eit"."entity_edition_id" = "entity_temporal_metadata_0_0_0"."entity_edition_id") AS "entity_is_of_type_ids_0_0_2" - ON TRUE + ON + "entity_ids_0_0_3"."web_id" = "entity_temporal_metadata_0_0_0"."web_id" + AND "entity_ids_0_0_3"."entity_uuid" + = "entity_temporal_metadata_0_0_0"."entity_uuid" +LEFT OUTER JOIN LATERAL ( + SELECT + jsonb_agg( + jsonb_build_object(($12::text), "b", ($13::text), "v") + ) AS "entity_type_ids" + FROM "entity_is_of_type_ids" AS "eit" + CROSS JOIN + unnest("eit"."base_urls", ("eit"."versions"::text [])) AS "u" ("b", "v") + WHERE + "eit"."entity_edition_id" + = "entity_temporal_metadata_0_0_0"."entity_edition_id" +) AS "entity_is_of_type_ids_0_0_2" + ON TRUE LEFT OUTER JOIN "entity_has_left_entity" AS "entity_has_left_entity_0_0_4" - ON "entity_has_left_entity_0_0_4"."web_id" = "entity_temporal_metadata_0_0_0"."web_id" - AND "entity_has_left_entity_0_0_4"."entity_uuid" = "entity_temporal_metadata_0_0_0"."entity_uuid" + ON + "entity_has_left_entity_0_0_4"."web_id" + = "entity_temporal_metadata_0_0_0"."web_id" + AND "entity_has_left_entity_0_0_4"."entity_uuid" + = "entity_temporal_metadata_0_0_0"."entity_uuid" LEFT OUTER JOIN "entity_has_right_entity" AS "entity_has_right_entity_0_0_5" - ON "entity_has_right_entity_0_0_5"."web_id" = "entity_temporal_metadata_0_0_0"."web_id" - AND "entity_has_right_entity_0_0_5"."entity_uuid" = "entity_temporal_metadata_0_0_0"."entity_uuid" -CROSS JOIN LATERAL (SELECT (ROW(NULL, 1, ARRAY[]::int[], ARRAY[]::jsonb[])::continuation) AS "row" -OFFSET 0) AS "continuation_0_0" -WHERE "entity_temporal_metadata_0_0_0"."transaction_time" && ($1::tstzrange) AND "entity_temporal_metadata_0_0_0"."decision_time" && ($2::tstzrange) AND ("continuation_0_0"."row")."filter" IS NOT FALSE + ON + "entity_has_right_entity_0_0_5"."web_id" + = "entity_temporal_metadata_0_0_0"."web_id" + AND "entity_has_right_entity_0_0_5"."entity_uuid" + = "entity_temporal_metadata_0_0_0"."entity_uuid" +CROSS JOIN LATERAL ( + SELECT + ( + row(NULL, 1, ARRAY[]::int [], ARRAY[]::jsonb [])::continuation + ) AS "row" + OFFSET 0 +) AS "continuation_0_0" +WHERE + "entity_temporal_metadata_0_0_0"."transaction_time" && ($1::tstzrange) + AND "entity_temporal_metadata_0_0_0"."decision_time" && ($2::tstzrange) + AND ("continuation_0_0"."row")."filter" IS NOT FALSE + ================================== Parameters ================================== $1: TemporalAxis(Transaction) diff --git a/libs/@local/hashql/eval/tests/ui/postgres/filter/provides_drives_select_and_joins.snap b/libs/@local/hashql/eval/tests/ui/postgres/filter/provides_drives_select_and_joins.snap index 23acb01cbf7..eded8c13e00 100644 --- a/libs/@local/hashql/eval/tests/ui/postgres/filter/provides_drives_select_and_joins.snap +++ b/libs/@local/hashql/eval/tests/ui/postgres/filter/provides_drives_select_and_joins.snap @@ -4,25 +4,122 @@ expression: report.to_string() --- ===================================== SQL ====================================== -SELECT "entity_editions_0_0_1"."properties" AS "properties", jsonb_build_object(($3::text), jsonb_build_object(($4::text), "entity_temporal_metadata_0_0_0"."web_id", ($5::text), "entity_temporal_metadata_0_0_0"."entity_uuid", ($6::text), "entity_temporal_metadata_0_0_0"."draft_id"), ($7::text), "entity_temporal_metadata_0_0_0"."entity_edition_id") AS "record_id", jsonb_build_object(($8::text), jsonb_build_object(($9::text), (extract(epoch from lower("entity_temporal_metadata_0_0_0"."decision_time")) * 1000)::int8, ($10::text), CASE WHEN upper_inf("entity_temporal_metadata_0_0_0"."decision_time") THEN NULL ELSE (extract(epoch from upper("entity_temporal_metadata_0_0_0"."decision_time")) * 1000)::int8 END), ($11::text), jsonb_build_object(($9::text), (extract(epoch from lower("entity_temporal_metadata_0_0_0"."transaction_time")) * 1000)::int8, ($10::text), CASE WHEN upper_inf("entity_temporal_metadata_0_0_0"."transaction_time") THEN NULL ELSE (extract(epoch from upper("entity_temporal_metadata_0_0_0"."transaction_time")) * 1000)::int8 END)) AS "temporal_versioning", "entity_is_of_type_ids_0_0_2"."entity_type_ids" AS "entity_type_ids", "entity_editions_0_0_1"."archived" AS "archived", "entity_editions_0_0_1"."confidence" AS "confidence", "entity_ids_0_0_3"."provenance" AS "provenance_inferred", "entity_editions_0_0_1"."provenance" AS "provenance_edition", "entity_editions_0_0_1"."property_metadata" AS "property_metadata", "entity_has_left_entity_0_0_4"."left_web_id" AS "left_entity_web_id", "entity_has_left_entity_0_0_4"."left_entity_uuid" AS "left_entity_uuid", "entity_has_right_entity_0_0_5"."right_web_id" AS "right_entity_web_id", "entity_has_right_entity_0_0_5"."right_entity_uuid" AS "right_entity_uuid", "entity_has_left_entity_0_0_4"."confidence" AS "left_entity_confidence", "entity_has_right_entity_0_0_5"."confidence" AS "right_entity_confidence", "entity_has_left_entity_0_0_4"."provenance" AS "left_entity_provenance", "entity_has_right_entity_0_0_5"."provenance" AS "right_entity_provenance" +SELECT + "entity_editions_0_0_1"."properties" AS "properties", + jsonb_build_object( + ($3::text), + jsonb_build_object( + ($4::text), + "entity_temporal_metadata_0_0_0"."web_id", + ($5::text), + "entity_temporal_metadata_0_0_0"."entity_uuid", + ($6::text), + "entity_temporal_metadata_0_0_0"."draft_id" + ), + ($7::text), + "entity_temporal_metadata_0_0_0"."entity_edition_id" + ) AS "record_id", + jsonb_build_object( + ($8::text), + jsonb_build_object( + ($9::text), + ( + extract( + EPOCH FROM lower( + "entity_temporal_metadata_0_0_0"."decision_time" + ) + ) + * 1000 + )::int8, + ($10::text), + CASE + WHEN + upper_inf("entity_temporal_metadata_0_0_0"."decision_time") + THEN NULL + ELSE + ( + extract(EPOCH FROM upper("entity_temporal_metadata_0_0_0"."decision_time")) * 1000 + )::int8 + END + ), + ($11::text), + jsonb_build_object( + ($9::text), + ( + extract( + EPOCH FROM lower( + "entity_temporal_metadata_0_0_0"."transaction_time" + ) + ) + * 1000 + )::int8, + ($10::text), + CASE + WHEN + upper_inf( + "entity_temporal_metadata_0_0_0"."transaction_time" + ) + THEN NULL + ELSE + ( + extract(EPOCH FROM upper("entity_temporal_metadata_0_0_0"."transaction_time")) * 1000 + )::int8 + END + ) + ) AS "temporal_versioning", + "entity_is_of_type_ids_0_0_2"."entity_type_ids" AS "entity_type_ids", + "entity_editions_0_0_1"."archived" AS "archived", + "entity_editions_0_0_1"."confidence" AS "confidence", + "entity_ids_0_0_3"."provenance" AS "provenance_inferred", + "entity_editions_0_0_1"."provenance" AS "provenance_edition", + "entity_editions_0_0_1"."property_metadata" AS "property_metadata", + "entity_has_left_entity_0_0_4"."left_web_id" AS "left_entity_web_id", + "entity_has_left_entity_0_0_4"."left_entity_uuid" AS "left_entity_uuid", + "entity_has_right_entity_0_0_5"."right_web_id" AS "right_entity_web_id", + "entity_has_right_entity_0_0_5"."right_entity_uuid" AS "right_entity_uuid", + "entity_has_left_entity_0_0_4"."confidence" AS "left_entity_confidence", + "entity_has_right_entity_0_0_5"."confidence" AS "right_entity_confidence", + "entity_has_left_entity_0_0_4"."provenance" AS "left_entity_provenance", + "entity_has_right_entity_0_0_5"."provenance" AS "right_entity_provenance" FROM "entity_temporal_metadata" AS "entity_temporal_metadata_0_0_0" INNER JOIN "entity_editions" AS "entity_editions_0_0_1" - ON "entity_editions_0_0_1"."entity_edition_id" = "entity_temporal_metadata_0_0_0"."entity_edition_id" + ON + "entity_editions_0_0_1"."entity_edition_id" + = "entity_temporal_metadata_0_0_0"."entity_edition_id" INNER JOIN "entity_ids" AS "entity_ids_0_0_3" - ON "entity_ids_0_0_3"."web_id" = "entity_temporal_metadata_0_0_0"."web_id" - AND "entity_ids_0_0_3"."entity_uuid" = "entity_temporal_metadata_0_0_0"."entity_uuid" -LEFT OUTER JOIN LATERAL (SELECT jsonb_agg(jsonb_build_object(($12::text), "b", ($13::text), "v")) AS "entity_type_ids" -FROM "entity_is_of_type_ids" AS "eit" -CROSS JOIN UNNEST("eit"."base_urls", ("eit"."versions"::text[])) AS "u"("b", "v") -WHERE "eit"."entity_edition_id" = "entity_temporal_metadata_0_0_0"."entity_edition_id") AS "entity_is_of_type_ids_0_0_2" - ON TRUE + ON + "entity_ids_0_0_3"."web_id" = "entity_temporal_metadata_0_0_0"."web_id" + AND "entity_ids_0_0_3"."entity_uuid" + = "entity_temporal_metadata_0_0_0"."entity_uuid" +LEFT OUTER JOIN LATERAL ( + SELECT + jsonb_agg( + jsonb_build_object(($12::text), "b", ($13::text), "v") + ) AS "entity_type_ids" + FROM "entity_is_of_type_ids" AS "eit" + CROSS JOIN + unnest("eit"."base_urls", ("eit"."versions"::text [])) AS "u" ("b", "v") + WHERE + "eit"."entity_edition_id" + = "entity_temporal_metadata_0_0_0"."entity_edition_id" +) AS "entity_is_of_type_ids_0_0_2" + ON TRUE LEFT OUTER JOIN "entity_has_left_entity" AS "entity_has_left_entity_0_0_4" - ON "entity_has_left_entity_0_0_4"."web_id" = "entity_temporal_metadata_0_0_0"."web_id" - AND "entity_has_left_entity_0_0_4"."entity_uuid" = "entity_temporal_metadata_0_0_0"."entity_uuid" + ON + "entity_has_left_entity_0_0_4"."web_id" + = "entity_temporal_metadata_0_0_0"."web_id" + AND "entity_has_left_entity_0_0_4"."entity_uuid" + = "entity_temporal_metadata_0_0_0"."entity_uuid" LEFT OUTER JOIN "entity_has_right_entity" AS "entity_has_right_entity_0_0_5" - ON "entity_has_right_entity_0_0_5"."web_id" = "entity_temporal_metadata_0_0_0"."web_id" - AND "entity_has_right_entity_0_0_5"."entity_uuid" = "entity_temporal_metadata_0_0_0"."entity_uuid" -WHERE "entity_temporal_metadata_0_0_0"."transaction_time" && ($1::tstzrange) AND "entity_temporal_metadata_0_0_0"."decision_time" && ($2::tstzrange) + ON + "entity_has_right_entity_0_0_5"."web_id" + = "entity_temporal_metadata_0_0_0"."web_id" + AND "entity_has_right_entity_0_0_5"."entity_uuid" + = "entity_temporal_metadata_0_0_0"."entity_uuid" +WHERE + "entity_temporal_metadata_0_0_0"."transaction_time" && ($1::tstzrange) + AND "entity_temporal_metadata_0_0_0"."decision_time" && ($2::tstzrange) + ================================== Parameters ================================== $1: TemporalAxis(Transaction) diff --git a/libs/@local/hashql/eval/tests/ui/postgres/filter/straight_line_goto_chain.snap b/libs/@local/hashql/eval/tests/ui/postgres/filter/straight_line_goto_chain.snap index 97280fe34a5..87eb50b1146 100644 --- a/libs/@local/hashql/eval/tests/ui/postgres/filter/straight_line_goto_chain.snap +++ b/libs/@local/hashql/eval/tests/ui/postgres/filter/straight_line_goto_chain.snap @@ -4,4 +4,8 @@ expression: report.to_string() --- ==================== Island (entry: bb0, target: postgres) ===================== -(ROW(COALESCE(((($1::jsonb))::boolean), FALSE), NULL, NULL, NULL)::continuation) +( + ROW( + COALESCE(((($1::jsonb))::boolean), FALSE), NULL, NULL, NULL + )::continuation +) diff --git a/libs/@local/hashql/eval/tests/ui/postgres/filter/switch_int_many_branches.snap b/libs/@local/hashql/eval/tests/ui/postgres/filter/switch_int_many_branches.snap index 71a13ebe8ad..84a960c5521 100644 --- a/libs/@local/hashql/eval/tests/ui/postgres/filter/switch_int_many_branches.snap +++ b/libs/@local/hashql/eval/tests/ui/postgres/filter/switch_int_many_branches.snap @@ -4,4 +4,49 @@ expression: report.to_string() --- ==================== Island (entry: bb0, target: postgres) ===================== -CASE WHEN ((($1::jsonb))::int) IS NULL THEN (ROW(COALESCE(((FALSE)::boolean), FALSE), NULL, NULL, NULL)::continuation) WHEN ((($1::jsonb))::int) = 0 THEN (ROW(COALESCE(((1)::boolean), FALSE), NULL, NULL, NULL)::continuation) WHEN ((($1::jsonb))::int) = 1 THEN (ROW(COALESCE(((0)::boolean), FALSE), NULL, NULL, NULL)::continuation) WHEN ((($1::jsonb))::int) = 2 THEN (ROW(COALESCE(((1)::boolean), FALSE), NULL, NULL, NULL)::continuation) WHEN ((($1::jsonb))::int) = 3 THEN (ROW(COALESCE(((0)::boolean), FALSE), NULL, NULL, NULL)::continuation) ELSE (ROW(COALESCE(((1)::boolean), FALSE), NULL, NULL, NULL)::continuation) END +CASE + WHEN + ((($1::jsonb))::int) IS NULL + THEN + ( + ROW( + COALESCE(((FALSE)::boolean), FALSE), NULL, NULL, NULL + )::continuation + ) + WHEN + ((($1::jsonb))::int) = 0 + THEN + ( + ROW( + COALESCE(((1)::boolean), FALSE), NULL, NULL, NULL + )::continuation + ) + WHEN + ((($1::jsonb))::int) = 1 + THEN + ( + ROW( + COALESCE(((0)::boolean), FALSE), NULL, NULL, NULL + )::continuation + ) + WHEN + ((($1::jsonb))::int) = 2 + THEN + ( + ROW( + COALESCE(((1)::boolean), FALSE), NULL, NULL, NULL + )::continuation + ) + WHEN + ((($1::jsonb))::int) = 3 + THEN + ( + ROW( + COALESCE(((0)::boolean), FALSE), NULL, NULL, NULL + )::continuation + ) + ELSE + ( + ROW(COALESCE(((1)::boolean), FALSE), NULL, NULL, NULL)::continuation + ) +END diff --git a/libs/@local/hashql/eval/tests/ui/postgres/filter/temporal_decision_time_interval.snap b/libs/@local/hashql/eval/tests/ui/postgres/filter/temporal_decision_time_interval.snap index e62670cb3d0..128f7dad29d 100644 --- a/libs/@local/hashql/eval/tests/ui/postgres/filter/temporal_decision_time_interval.snap +++ b/libs/@local/hashql/eval/tests/ui/postgres/filter/temporal_decision_time_interval.snap @@ -4,9 +4,23 @@ expression: report.to_string() --- ===================================== SQL ====================================== -SELECT jsonb_build_object(($3::text), (extract(epoch from lower("entity_temporal_metadata_0_0_0"."decision_time")) * 1000)::int8, ($4::text), CASE WHEN upper_inf("entity_temporal_metadata_0_0_0"."decision_time") THEN NULL ELSE (extract(epoch from upper("entity_temporal_metadata_0_0_0"."decision_time")) * 1000)::int8 END) AS "decision_time" +SELECT + ("continuation_0_0"."row")."block" AS "continuation_0_0_block", + ("continuation_0_0"."row")."locals" AS "continuation_0_0_locals", + ("continuation_0_0"."row")."values" AS "continuation_0_0_values" FROM "entity_temporal_metadata" AS "entity_temporal_metadata_0_0_0" -WHERE "entity_temporal_metadata_0_0_0"."transaction_time" && ($1::tstzrange) AND "entity_temporal_metadata_0_0_0"."decision_time" && ($2::tstzrange) +CROSS JOIN LATERAL ( + SELECT + ( + ROW(NULL, 1, ARRAY[]::int [], ARRAY[]::jsonb [])::continuation + ) AS "row" + OFFSET 0 +) AS "continuation_0_0" +WHERE + "entity_temporal_metadata_0_0_0"."transaction_time" && ($1::tstzrange) + AND "entity_temporal_metadata_0_0_0"."decision_time" && ($2::tstzrange) + AND ("continuation_0_0"."row")."filter" IS NOT FALSE + ================================== Parameters ================================== $1: TemporalAxis(Transaction) diff --git a/libs/@local/hashql/eval/tests/ui/postgres/filter/unary_bitnot.snap b/libs/@local/hashql/eval/tests/ui/postgres/filter/unary_bitnot.snap index dc01e495757..0edbde173ec 100644 --- a/libs/@local/hashql/eval/tests/ui/postgres/filter/unary_bitnot.snap +++ b/libs/@local/hashql/eval/tests/ui/postgres/filter/unary_bitnot.snap @@ -4,4 +4,8 @@ expression: report.to_string() --- ==================== Island (entry: bb0, target: postgres) ===================== -(ROW(COALESCE(((~(($1::jsonb)))::boolean), FALSE), NULL, NULL, NULL)::continuation) +( + ROW( + COALESCE(((~(($1::jsonb)))::boolean), FALSE), NULL, NULL, NULL + )::continuation +) diff --git a/libs/@local/hashql/eval/tests/ui/postgres/filter/unary_neg.snap b/libs/@local/hashql/eval/tests/ui/postgres/filter/unary_neg.snap index 8aec2670c44..149a347dfcd 100644 --- a/libs/@local/hashql/eval/tests/ui/postgres/filter/unary_neg.snap +++ b/libs/@local/hashql/eval/tests/ui/postgres/filter/unary_neg.snap @@ -4,4 +4,8 @@ expression: report.to_string() --- ==================== Island (entry: bb0, target: postgres) ===================== -(ROW(COALESCE(((-(($1::jsonb)))::boolean), FALSE), NULL, NULL, NULL)::continuation) +( + ROW( + COALESCE(((-(($1::jsonb)))::boolean), FALSE), NULL, NULL, NULL + )::continuation +) diff --git a/libs/@local/hashql/eval/tests/ui/postgres/filter/unary_not.snap b/libs/@local/hashql/eval/tests/ui/postgres/filter/unary_not.snap index 1d92276af2c..ee017eaa0d5 100644 --- a/libs/@local/hashql/eval/tests/ui/postgres/filter/unary_not.snap +++ b/libs/@local/hashql/eval/tests/ui/postgres/filter/unary_not.snap @@ -4,4 +4,8 @@ expression: report.to_string() --- ==================== Island (entry: bb0, target: postgres) ===================== -(ROW(COALESCE(((NOT(($1::jsonb)))::boolean), FALSE), NULL, NULL, NULL)::continuation) +( + ROW( + COALESCE(((NOT (($1::jsonb)))::boolean), FALSE), NULL, NULL, NULL + )::continuation +) diff --git a/libs/@local/hashql/mir/src/builder/mod.rs b/libs/@local/hashql/mir/src/builder/mod.rs index fc58f037dfd..b56c57f31f0 100644 --- a/libs/@local/hashql/mir/src/builder/mod.rs +++ b/libs/@local/hashql/mir/src/builder/mod.rs @@ -143,9 +143,8 @@ macro_rules! op { [|] => { $crate::body::rvalue::BinOp::BitOr }; // Unary operators - [!] => { hashql_hir::node::operation::UnOp::Not }; - [neg] => { hashql_hir::node::operation::UnOp::Neg }; - [~] => { hashql_hir::node::operation::UnOp::BitNot }; + [neg] => { $crate::body::rvalue::UnOp::Neg }; + [~] => { $crate::body::rvalue::UnOp::BitNot }; } #[doc(hidden)]