diff --git a/partiql-ast/api/partiql-ast.api b/partiql-ast/api/partiql-ast.api index c90a11d46..9ed96e41c 100644 --- a/partiql-ast/api/partiql-ast.api +++ b/partiql-ast/api/partiql-ast.api @@ -15,9 +15,9 @@ public final class org/partiql/ast/Ast { public static final fun excludeStepCollWildcard ()Lorg/partiql/ast/Exclude$Step$CollWildcard; public static final fun excludeStepStructField (Lorg/partiql/ast/Identifier$Symbol;)Lorg/partiql/ast/Exclude$Step$StructField; public static final fun excludeStepStructWildcard ()Lorg/partiql/ast/Exclude$Step$StructWildcard; + public static final fun exprAnd (Lorg/partiql/ast/Expr;Lorg/partiql/ast/Expr;)Lorg/partiql/ast/Expr$And; public static final fun exprBagOp (Lorg/partiql/ast/SetOp;Lorg/partiql/ast/Expr;Lorg/partiql/ast/Expr;Ljava/lang/Boolean;)Lorg/partiql/ast/Expr$BagOp; public static final fun exprBetween (Lorg/partiql/ast/Expr;Lorg/partiql/ast/Expr;Lorg/partiql/ast/Expr;Ljava/lang/Boolean;)Lorg/partiql/ast/Expr$Between; - public static final fun exprBinary (Lorg/partiql/ast/Expr$Binary$Op;Lorg/partiql/ast/Expr;Lorg/partiql/ast/Expr;)Lorg/partiql/ast/Expr$Binary; public static final fun exprCall (Lorg/partiql/ast/Identifier;Ljava/util/List;Lorg/partiql/ast/SetQuantifier;)Lorg/partiql/ast/Expr$Call; public static final fun exprCanCast (Lorg/partiql/ast/Expr;Lorg/partiql/ast/Type;)Lorg/partiql/ast/Expr$CanCast; public static final fun exprCanLosslessCast (Lorg/partiql/ast/Expr;Lorg/partiql/ast/Type;)Lorg/partiql/ast/Expr$CanLosslessCast; @@ -35,7 +35,10 @@ public final class org/partiql/ast/Ast { public static final fun exprLike (Lorg/partiql/ast/Expr;Lorg/partiql/ast/Expr;Lorg/partiql/ast/Expr;Ljava/lang/Boolean;)Lorg/partiql/ast/Expr$Like; public static final fun exprLit (Lorg/partiql/value/PartiQLValue;)Lorg/partiql/ast/Expr$Lit; public static final fun exprMatch (Lorg/partiql/ast/Expr;Lorg/partiql/ast/GraphMatch;)Lorg/partiql/ast/Expr$Match; + public static final fun exprNot (Lorg/partiql/ast/Expr;)Lorg/partiql/ast/Expr$Not; public static final fun exprNullIf (Lorg/partiql/ast/Expr;Lorg/partiql/ast/Expr;)Lorg/partiql/ast/Expr$NullIf; + public static final fun exprOperator (Ljava/lang/String;Lorg/partiql/ast/Expr;Lorg/partiql/ast/Expr;)Lorg/partiql/ast/Expr$Operator; + public static final fun exprOr (Lorg/partiql/ast/Expr;Lorg/partiql/ast/Expr;)Lorg/partiql/ast/Expr$Or; public static final fun exprOverlay (Lorg/partiql/ast/Expr;Lorg/partiql/ast/Expr;Lorg/partiql/ast/Expr;Lorg/partiql/ast/Expr;)Lorg/partiql/ast/Expr$Overlay; public static final fun exprParameter (I)Lorg/partiql/ast/Expr$Parameter; public static final fun exprPath (Lorg/partiql/ast/Expr;Ljava/util/List;)Lorg/partiql/ast/Expr$Path; @@ -51,7 +54,6 @@ public final class org/partiql/ast/Ast { public static final fun exprStructField (Lorg/partiql/ast/Expr;Lorg/partiql/ast/Expr;)Lorg/partiql/ast/Expr$Struct$Field; public static final fun exprSubstring (Lorg/partiql/ast/Expr;Lorg/partiql/ast/Expr;Lorg/partiql/ast/Expr;)Lorg/partiql/ast/Expr$Substring; public static final fun exprTrim (Lorg/partiql/ast/Expr;Lorg/partiql/ast/Expr;Lorg/partiql/ast/Expr$Trim$Spec;)Lorg/partiql/ast/Expr$Trim; - public static final fun exprUnary (Lorg/partiql/ast/Expr$Unary$Op;Lorg/partiql/ast/Expr;)Lorg/partiql/ast/Expr$Unary; public static final fun exprValues (Ljava/util/List;)Lorg/partiql/ast/Expr$Values; public static final fun exprValuesRow (Ljava/util/List;)Lorg/partiql/ast/Expr$Values$Row; public static final fun exprVar (Lorg/partiql/ast/Identifier;Lorg/partiql/ast/Expr$Var$Scope;)Lorg/partiql/ast/Expr$Var; @@ -537,6 +539,27 @@ public abstract class org/partiql/ast/Expr : org/partiql/ast/AstNode { public fun accept (Lorg/partiql/ast/visitor/AstVisitor;Ljava/lang/Object;)Ljava/lang/Object; } +public final class org/partiql/ast/Expr$And : org/partiql/ast/Expr { + public static final field Companion Lorg/partiql/ast/Expr$And$Companion; + public final field lhs Lorg/partiql/ast/Expr; + public final field rhs Lorg/partiql/ast/Expr; + public fun (Lorg/partiql/ast/Expr;Lorg/partiql/ast/Expr;)V + public fun accept (Lorg/partiql/ast/visitor/AstVisitor;Ljava/lang/Object;)Ljava/lang/Object; + public static final fun builder ()Lorg/partiql/ast/builder/ExprAndBuilder; + public final fun component1 ()Lorg/partiql/ast/Expr; + public final fun component2 ()Lorg/partiql/ast/Expr; + public final fun copy (Lorg/partiql/ast/Expr;Lorg/partiql/ast/Expr;)Lorg/partiql/ast/Expr$And; + public static synthetic fun copy$default (Lorg/partiql/ast/Expr$And;Lorg/partiql/ast/Expr;Lorg/partiql/ast/Expr;ILjava/lang/Object;)Lorg/partiql/ast/Expr$And; + public fun equals (Ljava/lang/Object;)Z + public fun getChildren ()Ljava/util/List; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class org/partiql/ast/Expr$And$Companion { + public final fun builder ()Lorg/partiql/ast/builder/ExprAndBuilder; +} + public final class org/partiql/ast/Expr$BagOp : org/partiql/ast/Expr { public static final field Companion Lorg/partiql/ast/Expr$BagOp$Companion; public final field lhs Lorg/partiql/ast/Expr; @@ -587,49 +610,6 @@ public final class org/partiql/ast/Expr$Between$Companion { public final fun builder ()Lorg/partiql/ast/builder/ExprBetweenBuilder; } -public final class org/partiql/ast/Expr$Binary : org/partiql/ast/Expr { - public static final field Companion Lorg/partiql/ast/Expr$Binary$Companion; - public final field lhs Lorg/partiql/ast/Expr; - public final field op Lorg/partiql/ast/Expr$Binary$Op; - public final field rhs Lorg/partiql/ast/Expr; - public fun (Lorg/partiql/ast/Expr$Binary$Op;Lorg/partiql/ast/Expr;Lorg/partiql/ast/Expr;)V - public fun accept (Lorg/partiql/ast/visitor/AstVisitor;Ljava/lang/Object;)Ljava/lang/Object; - public static final fun builder ()Lorg/partiql/ast/builder/ExprBinaryBuilder; - public final fun component1 ()Lorg/partiql/ast/Expr$Binary$Op; - public final fun component2 ()Lorg/partiql/ast/Expr; - public final fun component3 ()Lorg/partiql/ast/Expr; - public final fun copy (Lorg/partiql/ast/Expr$Binary$Op;Lorg/partiql/ast/Expr;Lorg/partiql/ast/Expr;)Lorg/partiql/ast/Expr$Binary; - public static synthetic fun copy$default (Lorg/partiql/ast/Expr$Binary;Lorg/partiql/ast/Expr$Binary$Op;Lorg/partiql/ast/Expr;Lorg/partiql/ast/Expr;ILjava/lang/Object;)Lorg/partiql/ast/Expr$Binary; - public fun equals (Ljava/lang/Object;)Z - public fun getChildren ()Ljava/util/List; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class org/partiql/ast/Expr$Binary$Companion { - public final fun builder ()Lorg/partiql/ast/builder/ExprBinaryBuilder; -} - -public final class org/partiql/ast/Expr$Binary$Op : java/lang/Enum { - public static final field AND Lorg/partiql/ast/Expr$Binary$Op; - public static final field BITWISE_AND Lorg/partiql/ast/Expr$Binary$Op; - public static final field CONCAT Lorg/partiql/ast/Expr$Binary$Op; - public static final field DIVIDE Lorg/partiql/ast/Expr$Binary$Op; - public static final field EQ Lorg/partiql/ast/Expr$Binary$Op; - public static final field GT Lorg/partiql/ast/Expr$Binary$Op; - public static final field GTE Lorg/partiql/ast/Expr$Binary$Op; - public static final field LT Lorg/partiql/ast/Expr$Binary$Op; - public static final field LTE Lorg/partiql/ast/Expr$Binary$Op; - public static final field MINUS Lorg/partiql/ast/Expr$Binary$Op; - public static final field MODULO Lorg/partiql/ast/Expr$Binary$Op; - public static final field NE Lorg/partiql/ast/Expr$Binary$Op; - public static final field OR Lorg/partiql/ast/Expr$Binary$Op; - public static final field PLUS Lorg/partiql/ast/Expr$Binary$Op; - public static final field TIMES Lorg/partiql/ast/Expr$Binary$Op; - public static fun valueOf (Ljava/lang/String;)Lorg/partiql/ast/Expr$Binary$Op; - public static fun values ()[Lorg/partiql/ast/Expr$Binary$Op; -} - public final class org/partiql/ast/Expr$Call : org/partiql/ast/Expr { public static final field Companion Lorg/partiql/ast/Expr$Call$Companion; public final field args Ljava/util/List; @@ -1007,6 +987,25 @@ public final class org/partiql/ast/Expr$Match$Companion { public final fun builder ()Lorg/partiql/ast/builder/ExprMatchBuilder; } +public final class org/partiql/ast/Expr$Not : org/partiql/ast/Expr { + public static final field Companion Lorg/partiql/ast/Expr$Not$Companion; + public final field value Lorg/partiql/ast/Expr; + public fun (Lorg/partiql/ast/Expr;)V + public fun accept (Lorg/partiql/ast/visitor/AstVisitor;Ljava/lang/Object;)Ljava/lang/Object; + public static final fun builder ()Lorg/partiql/ast/builder/ExprNotBuilder; + public final fun component1 ()Lorg/partiql/ast/Expr; + public final fun copy (Lorg/partiql/ast/Expr;)Lorg/partiql/ast/Expr$Not; + public static synthetic fun copy$default (Lorg/partiql/ast/Expr$Not;Lorg/partiql/ast/Expr;ILjava/lang/Object;)Lorg/partiql/ast/Expr$Not; + public fun equals (Ljava/lang/Object;)Z + public fun getChildren ()Ljava/util/List; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class org/partiql/ast/Expr$Not$Companion { + public final fun builder ()Lorg/partiql/ast/builder/ExprNotBuilder; +} + public final class org/partiql/ast/Expr$NullIf : org/partiql/ast/Expr { public static final field Companion Lorg/partiql/ast/Expr$NullIf$Companion; public final field nullifier Lorg/partiql/ast/Expr; @@ -1028,6 +1027,50 @@ public final class org/partiql/ast/Expr$NullIf$Companion { public final fun builder ()Lorg/partiql/ast/builder/ExprNullIfBuilder; } +public final class org/partiql/ast/Expr$Operator : org/partiql/ast/Expr { + public static final field Companion Lorg/partiql/ast/Expr$Operator$Companion; + public final field lhs Lorg/partiql/ast/Expr; + public final field rhs Lorg/partiql/ast/Expr; + public final field symbol Ljava/lang/String; + public fun (Ljava/lang/String;Lorg/partiql/ast/Expr;Lorg/partiql/ast/Expr;)V + public fun accept (Lorg/partiql/ast/visitor/AstVisitor;Ljava/lang/Object;)Ljava/lang/Object; + public static final fun builder ()Lorg/partiql/ast/builder/ExprOperatorBuilder; + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Lorg/partiql/ast/Expr; + public final fun component3 ()Lorg/partiql/ast/Expr; + public final fun copy (Ljava/lang/String;Lorg/partiql/ast/Expr;Lorg/partiql/ast/Expr;)Lorg/partiql/ast/Expr$Operator; + public static synthetic fun copy$default (Lorg/partiql/ast/Expr$Operator;Ljava/lang/String;Lorg/partiql/ast/Expr;Lorg/partiql/ast/Expr;ILjava/lang/Object;)Lorg/partiql/ast/Expr$Operator; + public fun equals (Ljava/lang/Object;)Z + public fun getChildren ()Ljava/util/List; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class org/partiql/ast/Expr$Operator$Companion { + public final fun builder ()Lorg/partiql/ast/builder/ExprOperatorBuilder; +} + +public final class org/partiql/ast/Expr$Or : org/partiql/ast/Expr { + public static final field Companion Lorg/partiql/ast/Expr$Or$Companion; + public final field lhs Lorg/partiql/ast/Expr; + public final field rhs Lorg/partiql/ast/Expr; + public fun (Lorg/partiql/ast/Expr;Lorg/partiql/ast/Expr;)V + public fun accept (Lorg/partiql/ast/visitor/AstVisitor;Ljava/lang/Object;)Ljava/lang/Object; + public static final fun builder ()Lorg/partiql/ast/builder/ExprOrBuilder; + public final fun component1 ()Lorg/partiql/ast/Expr; + public final fun component2 ()Lorg/partiql/ast/Expr; + public final fun copy (Lorg/partiql/ast/Expr;Lorg/partiql/ast/Expr;)Lorg/partiql/ast/Expr$Or; + public static synthetic fun copy$default (Lorg/partiql/ast/Expr$Or;Lorg/partiql/ast/Expr;Lorg/partiql/ast/Expr;ILjava/lang/Object;)Lorg/partiql/ast/Expr$Or; + public fun equals (Ljava/lang/Object;)Z + public fun getChildren ()Ljava/util/List; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class org/partiql/ast/Expr$Or$Companion { + public final fun builder ()Lorg/partiql/ast/builder/ExprOrBuilder; +} + public final class org/partiql/ast/Expr$Overlay : org/partiql/ast/Expr { public static final field Companion Lorg/partiql/ast/Expr$Overlay$Companion; public final field length Lorg/partiql/ast/Expr; @@ -1378,35 +1421,6 @@ public final class org/partiql/ast/Expr$Trim$Spec : java/lang/Enum { public static fun values ()[Lorg/partiql/ast/Expr$Trim$Spec; } -public final class org/partiql/ast/Expr$Unary : org/partiql/ast/Expr { - public static final field Companion Lorg/partiql/ast/Expr$Unary$Companion; - public final field expr Lorg/partiql/ast/Expr; - public final field op Lorg/partiql/ast/Expr$Unary$Op; - public fun (Lorg/partiql/ast/Expr$Unary$Op;Lorg/partiql/ast/Expr;)V - public fun accept (Lorg/partiql/ast/visitor/AstVisitor;Ljava/lang/Object;)Ljava/lang/Object; - public static final fun builder ()Lorg/partiql/ast/builder/ExprUnaryBuilder; - public final fun component1 ()Lorg/partiql/ast/Expr$Unary$Op; - public final fun component2 ()Lorg/partiql/ast/Expr; - public final fun copy (Lorg/partiql/ast/Expr$Unary$Op;Lorg/partiql/ast/Expr;)Lorg/partiql/ast/Expr$Unary; - public static synthetic fun copy$default (Lorg/partiql/ast/Expr$Unary;Lorg/partiql/ast/Expr$Unary$Op;Lorg/partiql/ast/Expr;ILjava/lang/Object;)Lorg/partiql/ast/Expr$Unary; - public fun equals (Ljava/lang/Object;)Z - public fun getChildren ()Ljava/util/List; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class org/partiql/ast/Expr$Unary$Companion { - public final fun builder ()Lorg/partiql/ast/builder/ExprUnaryBuilder; -} - -public final class org/partiql/ast/Expr$Unary$Op : java/lang/Enum { - public static final field NEG Lorg/partiql/ast/Expr$Unary$Op; - public static final field NOT Lorg/partiql/ast/Expr$Unary$Op; - public static final field POS Lorg/partiql/ast/Expr$Unary$Op; - public static fun valueOf (Ljava/lang/String;)Lorg/partiql/ast/Expr$Unary$Op; - public static fun values ()[Lorg/partiql/ast/Expr$Unary$Op; -} - public final class org/partiql/ast/Expr$Values : org/partiql/ast/Expr { public static final field Companion Lorg/partiql/ast/Expr$Values$Companion; public final field rows Ljava/util/List; @@ -4030,12 +4044,12 @@ public final class org/partiql/ast/builder/AstBuilder { public static synthetic fun excludeStepStructField$default (Lorg/partiql/ast/builder/AstBuilder;Lorg/partiql/ast/Identifier$Symbol;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lorg/partiql/ast/Exclude$Step$StructField; public final fun excludeStepStructWildcard (Lkotlin/jvm/functions/Function1;)Lorg/partiql/ast/Exclude$Step$StructWildcard; public static synthetic fun excludeStepStructWildcard$default (Lorg/partiql/ast/builder/AstBuilder;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lorg/partiql/ast/Exclude$Step$StructWildcard; + public final fun exprAnd (Lorg/partiql/ast/Expr;Lorg/partiql/ast/Expr;Lkotlin/jvm/functions/Function1;)Lorg/partiql/ast/Expr$And; + public static synthetic fun exprAnd$default (Lorg/partiql/ast/builder/AstBuilder;Lorg/partiql/ast/Expr;Lorg/partiql/ast/Expr;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lorg/partiql/ast/Expr$And; public final fun exprBagOp (Lorg/partiql/ast/SetOp;Lorg/partiql/ast/Expr;Lorg/partiql/ast/Expr;Ljava/lang/Boolean;Lkotlin/jvm/functions/Function1;)Lorg/partiql/ast/Expr$BagOp; public static synthetic fun exprBagOp$default (Lorg/partiql/ast/builder/AstBuilder;Lorg/partiql/ast/SetOp;Lorg/partiql/ast/Expr;Lorg/partiql/ast/Expr;Ljava/lang/Boolean;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lorg/partiql/ast/Expr$BagOp; public final fun exprBetween (Lorg/partiql/ast/Expr;Lorg/partiql/ast/Expr;Lorg/partiql/ast/Expr;Ljava/lang/Boolean;Lkotlin/jvm/functions/Function1;)Lorg/partiql/ast/Expr$Between; public static synthetic fun exprBetween$default (Lorg/partiql/ast/builder/AstBuilder;Lorg/partiql/ast/Expr;Lorg/partiql/ast/Expr;Lorg/partiql/ast/Expr;Ljava/lang/Boolean;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lorg/partiql/ast/Expr$Between; - public final fun exprBinary (Lorg/partiql/ast/Expr$Binary$Op;Lorg/partiql/ast/Expr;Lorg/partiql/ast/Expr;Lkotlin/jvm/functions/Function1;)Lorg/partiql/ast/Expr$Binary; - public static synthetic fun exprBinary$default (Lorg/partiql/ast/builder/AstBuilder;Lorg/partiql/ast/Expr$Binary$Op;Lorg/partiql/ast/Expr;Lorg/partiql/ast/Expr;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lorg/partiql/ast/Expr$Binary; public final fun exprCall (Lorg/partiql/ast/Identifier;Ljava/util/List;Lorg/partiql/ast/SetQuantifier;Lkotlin/jvm/functions/Function1;)Lorg/partiql/ast/Expr$Call; public static synthetic fun exprCall$default (Lorg/partiql/ast/builder/AstBuilder;Lorg/partiql/ast/Identifier;Ljava/util/List;Lorg/partiql/ast/SetQuantifier;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lorg/partiql/ast/Expr$Call; public final fun exprCanCast (Lorg/partiql/ast/Expr;Lorg/partiql/ast/Type;Lkotlin/jvm/functions/Function1;)Lorg/partiql/ast/Expr$CanCast; @@ -4070,8 +4084,14 @@ public final class org/partiql/ast/builder/AstBuilder { public static synthetic fun exprLit$default (Lorg/partiql/ast/builder/AstBuilder;Lorg/partiql/value/PartiQLValue;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lorg/partiql/ast/Expr$Lit; public final fun exprMatch (Lorg/partiql/ast/Expr;Lorg/partiql/ast/GraphMatch;Lkotlin/jvm/functions/Function1;)Lorg/partiql/ast/Expr$Match; public static synthetic fun exprMatch$default (Lorg/partiql/ast/builder/AstBuilder;Lorg/partiql/ast/Expr;Lorg/partiql/ast/GraphMatch;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lorg/partiql/ast/Expr$Match; + public final fun exprNot (Lorg/partiql/ast/Expr;Lkotlin/jvm/functions/Function1;)Lorg/partiql/ast/Expr$Not; + public static synthetic fun exprNot$default (Lorg/partiql/ast/builder/AstBuilder;Lorg/partiql/ast/Expr;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lorg/partiql/ast/Expr$Not; public final fun exprNullIf (Lorg/partiql/ast/Expr;Lorg/partiql/ast/Expr;Lkotlin/jvm/functions/Function1;)Lorg/partiql/ast/Expr$NullIf; public static synthetic fun exprNullIf$default (Lorg/partiql/ast/builder/AstBuilder;Lorg/partiql/ast/Expr;Lorg/partiql/ast/Expr;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lorg/partiql/ast/Expr$NullIf; + public final fun exprOperator (Ljava/lang/String;Lorg/partiql/ast/Expr;Lorg/partiql/ast/Expr;Lkotlin/jvm/functions/Function1;)Lorg/partiql/ast/Expr$Operator; + public static synthetic fun exprOperator$default (Lorg/partiql/ast/builder/AstBuilder;Ljava/lang/String;Lorg/partiql/ast/Expr;Lorg/partiql/ast/Expr;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lorg/partiql/ast/Expr$Operator; + public final fun exprOr (Lorg/partiql/ast/Expr;Lorg/partiql/ast/Expr;Lkotlin/jvm/functions/Function1;)Lorg/partiql/ast/Expr$Or; + public static synthetic fun exprOr$default (Lorg/partiql/ast/builder/AstBuilder;Lorg/partiql/ast/Expr;Lorg/partiql/ast/Expr;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lorg/partiql/ast/Expr$Or; public final fun exprOverlay (Lorg/partiql/ast/Expr;Lorg/partiql/ast/Expr;Lorg/partiql/ast/Expr;Lorg/partiql/ast/Expr;Lkotlin/jvm/functions/Function1;)Lorg/partiql/ast/Expr$Overlay; public static synthetic fun exprOverlay$default (Lorg/partiql/ast/builder/AstBuilder;Lorg/partiql/ast/Expr;Lorg/partiql/ast/Expr;Lorg/partiql/ast/Expr;Lorg/partiql/ast/Expr;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lorg/partiql/ast/Expr$Overlay; public final fun exprParameter (Ljava/lang/Integer;Lkotlin/jvm/functions/Function1;)Lorg/partiql/ast/Expr$Parameter; @@ -4102,8 +4122,6 @@ public final class org/partiql/ast/builder/AstBuilder { public static synthetic fun exprSubstring$default (Lorg/partiql/ast/builder/AstBuilder;Lorg/partiql/ast/Expr;Lorg/partiql/ast/Expr;Lorg/partiql/ast/Expr;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lorg/partiql/ast/Expr$Substring; public final fun exprTrim (Lorg/partiql/ast/Expr;Lorg/partiql/ast/Expr;Lorg/partiql/ast/Expr$Trim$Spec;Lkotlin/jvm/functions/Function1;)Lorg/partiql/ast/Expr$Trim; public static synthetic fun exprTrim$default (Lorg/partiql/ast/builder/AstBuilder;Lorg/partiql/ast/Expr;Lorg/partiql/ast/Expr;Lorg/partiql/ast/Expr$Trim$Spec;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lorg/partiql/ast/Expr$Trim; - public final fun exprUnary (Lorg/partiql/ast/Expr$Unary$Op;Lorg/partiql/ast/Expr;Lkotlin/jvm/functions/Function1;)Lorg/partiql/ast/Expr$Unary; - public static synthetic fun exprUnary$default (Lorg/partiql/ast/builder/AstBuilder;Lorg/partiql/ast/Expr$Unary$Op;Lorg/partiql/ast/Expr;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lorg/partiql/ast/Expr$Unary; public final fun exprValues (Ljava/util/List;Lkotlin/jvm/functions/Function1;)Lorg/partiql/ast/Expr$Values; public static synthetic fun exprValues$default (Lorg/partiql/ast/builder/AstBuilder;Ljava/util/List;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lorg/partiql/ast/Expr$Values; public final fun exprValuesRow (Ljava/util/List;Lkotlin/jvm/functions/Function1;)Lorg/partiql/ast/Expr$Values$Row; @@ -4510,6 +4528,19 @@ public final class org/partiql/ast/builder/ExcludeStepStructWildcardBuilder { public final fun build ()Lorg/partiql/ast/Exclude$Step$StructWildcard; } +public final class org/partiql/ast/builder/ExprAndBuilder { + public fun ()V + public fun (Lorg/partiql/ast/Expr;Lorg/partiql/ast/Expr;)V + public synthetic fun (Lorg/partiql/ast/Expr;Lorg/partiql/ast/Expr;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun build ()Lorg/partiql/ast/Expr$And; + public final fun getLhs ()Lorg/partiql/ast/Expr; + public final fun getRhs ()Lorg/partiql/ast/Expr; + public final fun lhs (Lorg/partiql/ast/Expr;)Lorg/partiql/ast/builder/ExprAndBuilder; + public final fun rhs (Lorg/partiql/ast/Expr;)Lorg/partiql/ast/builder/ExprAndBuilder; + public final fun setLhs (Lorg/partiql/ast/Expr;)V + public final fun setRhs (Lorg/partiql/ast/Expr;)V +} + public final class org/partiql/ast/builder/ExprBagOpBuilder { public fun ()V public fun (Lorg/partiql/ast/SetOp;Lorg/partiql/ast/Expr;Lorg/partiql/ast/Expr;Ljava/lang/Boolean;)V @@ -4548,22 +4579,6 @@ public final class org/partiql/ast/builder/ExprBetweenBuilder { public final fun value (Lorg/partiql/ast/Expr;)Lorg/partiql/ast/builder/ExprBetweenBuilder; } -public final class org/partiql/ast/builder/ExprBinaryBuilder { - public fun ()V - public fun (Lorg/partiql/ast/Expr$Binary$Op;Lorg/partiql/ast/Expr;Lorg/partiql/ast/Expr;)V - public synthetic fun (Lorg/partiql/ast/Expr$Binary$Op;Lorg/partiql/ast/Expr;Lorg/partiql/ast/Expr;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun build ()Lorg/partiql/ast/Expr$Binary; - public final fun getLhs ()Lorg/partiql/ast/Expr; - public final fun getOp ()Lorg/partiql/ast/Expr$Binary$Op; - public final fun getRhs ()Lorg/partiql/ast/Expr; - public final fun lhs (Lorg/partiql/ast/Expr;)Lorg/partiql/ast/builder/ExprBinaryBuilder; - public final fun op (Lorg/partiql/ast/Expr$Binary$Op;)Lorg/partiql/ast/builder/ExprBinaryBuilder; - public final fun rhs (Lorg/partiql/ast/Expr;)Lorg/partiql/ast/builder/ExprBinaryBuilder; - public final fun setLhs (Lorg/partiql/ast/Expr;)V - public final fun setOp (Lorg/partiql/ast/Expr$Binary$Op;)V - public final fun setRhs (Lorg/partiql/ast/Expr;)V -} - public final class org/partiql/ast/builder/ExprCallBuilder { public fun ()V public fun (Lorg/partiql/ast/Identifier;Ljava/util/List;Lorg/partiql/ast/SetQuantifier;)V @@ -4800,6 +4815,16 @@ public final class org/partiql/ast/builder/ExprMatchBuilder { public final fun setPattern (Lorg/partiql/ast/GraphMatch;)V } +public final class org/partiql/ast/builder/ExprNotBuilder { + public fun ()V + public fun (Lorg/partiql/ast/Expr;)V + public synthetic fun (Lorg/partiql/ast/Expr;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun build ()Lorg/partiql/ast/Expr$Not; + public final fun getValue ()Lorg/partiql/ast/Expr; + public final fun setValue (Lorg/partiql/ast/Expr;)V + public final fun value (Lorg/partiql/ast/Expr;)Lorg/partiql/ast/builder/ExprNotBuilder; +} + public final class org/partiql/ast/builder/ExprNullIfBuilder { public fun ()V public fun (Lorg/partiql/ast/Expr;Lorg/partiql/ast/Expr;)V @@ -4813,6 +4838,35 @@ public final class org/partiql/ast/builder/ExprNullIfBuilder { public final fun value (Lorg/partiql/ast/Expr;)Lorg/partiql/ast/builder/ExprNullIfBuilder; } +public final class org/partiql/ast/builder/ExprOperatorBuilder { + public fun ()V + public fun (Ljava/lang/String;Lorg/partiql/ast/Expr;Lorg/partiql/ast/Expr;)V + public synthetic fun (Ljava/lang/String;Lorg/partiql/ast/Expr;Lorg/partiql/ast/Expr;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun build ()Lorg/partiql/ast/Expr$Operator; + public final fun getLhs ()Lorg/partiql/ast/Expr; + public final fun getRhs ()Lorg/partiql/ast/Expr; + public final fun getSymbol ()Ljava/lang/String; + public final fun lhs (Lorg/partiql/ast/Expr;)Lorg/partiql/ast/builder/ExprOperatorBuilder; + public final fun rhs (Lorg/partiql/ast/Expr;)Lorg/partiql/ast/builder/ExprOperatorBuilder; + public final fun setLhs (Lorg/partiql/ast/Expr;)V + public final fun setRhs (Lorg/partiql/ast/Expr;)V + public final fun setSymbol (Ljava/lang/String;)V + public final fun symbol (Ljava/lang/String;)Lorg/partiql/ast/builder/ExprOperatorBuilder; +} + +public final class org/partiql/ast/builder/ExprOrBuilder { + public fun ()V + public fun (Lorg/partiql/ast/Expr;Lorg/partiql/ast/Expr;)V + public synthetic fun (Lorg/partiql/ast/Expr;Lorg/partiql/ast/Expr;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun build ()Lorg/partiql/ast/Expr$Or; + public final fun getLhs ()Lorg/partiql/ast/Expr; + public final fun getRhs ()Lorg/partiql/ast/Expr; + public final fun lhs (Lorg/partiql/ast/Expr;)Lorg/partiql/ast/builder/ExprOrBuilder; + public final fun rhs (Lorg/partiql/ast/Expr;)Lorg/partiql/ast/builder/ExprOrBuilder; + public final fun setLhs (Lorg/partiql/ast/Expr;)V + public final fun setRhs (Lorg/partiql/ast/Expr;)V +} + public final class org/partiql/ast/builder/ExprOverlayBuilder { public fun ()V public fun (Lorg/partiql/ast/Expr;Lorg/partiql/ast/Expr;Lorg/partiql/ast/Expr;Lorg/partiql/ast/Expr;)V @@ -5016,19 +5070,6 @@ public final class org/partiql/ast/builder/ExprTrimBuilder { public final fun value (Lorg/partiql/ast/Expr;)Lorg/partiql/ast/builder/ExprTrimBuilder; } -public final class org/partiql/ast/builder/ExprUnaryBuilder { - public fun ()V - public fun (Lorg/partiql/ast/Expr$Unary$Op;Lorg/partiql/ast/Expr;)V - public synthetic fun (Lorg/partiql/ast/Expr$Unary$Op;Lorg/partiql/ast/Expr;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun build ()Lorg/partiql/ast/Expr$Unary; - public final fun expr (Lorg/partiql/ast/Expr;)Lorg/partiql/ast/builder/ExprUnaryBuilder; - public final fun getExpr ()Lorg/partiql/ast/Expr; - public final fun getOp ()Lorg/partiql/ast/Expr$Unary$Op; - public final fun op (Lorg/partiql/ast/Expr$Unary$Op;)Lorg/partiql/ast/builder/ExprUnaryBuilder; - public final fun setExpr (Lorg/partiql/ast/Expr;)V - public final fun setOp (Lorg/partiql/ast/Expr$Unary$Op;)V -} - public final class org/partiql/ast/builder/ExprValuesBuilder { public fun ()V public fun (Ljava/util/List;)V @@ -6392,12 +6433,12 @@ public abstract class org/partiql/ast/sql/SqlDialect : org/partiql/ast/visitor/A public fun visitExcludeStepStructField (Lorg/partiql/ast/Exclude$Step$StructField;Lorg/partiql/ast/sql/SqlBlock;)Lorg/partiql/ast/sql/SqlBlock; public synthetic fun visitExcludeStepStructWildcard (Lorg/partiql/ast/Exclude$Step$StructWildcard;Ljava/lang/Object;)Ljava/lang/Object; public fun visitExcludeStepStructWildcard (Lorg/partiql/ast/Exclude$Step$StructWildcard;Lorg/partiql/ast/sql/SqlBlock;)Lorg/partiql/ast/sql/SqlBlock; + public synthetic fun visitExprAnd (Lorg/partiql/ast/Expr$And;Ljava/lang/Object;)Ljava/lang/Object; + public fun visitExprAnd (Lorg/partiql/ast/Expr$And;Lorg/partiql/ast/sql/SqlBlock;)Lorg/partiql/ast/sql/SqlBlock; public synthetic fun visitExprBagOp (Lorg/partiql/ast/Expr$BagOp;Ljava/lang/Object;)Ljava/lang/Object; public fun visitExprBagOp (Lorg/partiql/ast/Expr$BagOp;Lorg/partiql/ast/sql/SqlBlock;)Lorg/partiql/ast/sql/SqlBlock; public synthetic fun visitExprBetween (Lorg/partiql/ast/Expr$Between;Ljava/lang/Object;)Ljava/lang/Object; public fun visitExprBetween (Lorg/partiql/ast/Expr$Between;Lorg/partiql/ast/sql/SqlBlock;)Lorg/partiql/ast/sql/SqlBlock; - public synthetic fun visitExprBinary (Lorg/partiql/ast/Expr$Binary;Ljava/lang/Object;)Ljava/lang/Object; - public fun visitExprBinary (Lorg/partiql/ast/Expr$Binary;Lorg/partiql/ast/sql/SqlBlock;)Lorg/partiql/ast/sql/SqlBlock; public synthetic fun visitExprCall (Lorg/partiql/ast/Expr$Call;Ljava/lang/Object;)Ljava/lang/Object; public fun visitExprCall (Lorg/partiql/ast/Expr$Call;Lorg/partiql/ast/sql/SqlBlock;)Lorg/partiql/ast/sql/SqlBlock; public synthetic fun visitExprCanCast (Lorg/partiql/ast/Expr$CanCast;Ljava/lang/Object;)Ljava/lang/Object; @@ -6430,8 +6471,14 @@ public abstract class org/partiql/ast/sql/SqlDialect : org/partiql/ast/visitor/A public fun visitExprLike (Lorg/partiql/ast/Expr$Like;Lorg/partiql/ast/sql/SqlBlock;)Lorg/partiql/ast/sql/SqlBlock; public synthetic fun visitExprLit (Lorg/partiql/ast/Expr$Lit;Ljava/lang/Object;)Ljava/lang/Object; public fun visitExprLit (Lorg/partiql/ast/Expr$Lit;Lorg/partiql/ast/sql/SqlBlock;)Lorg/partiql/ast/sql/SqlBlock; + public synthetic fun visitExprNot (Lorg/partiql/ast/Expr$Not;Ljava/lang/Object;)Ljava/lang/Object; + public fun visitExprNot (Lorg/partiql/ast/Expr$Not;Lorg/partiql/ast/sql/SqlBlock;)Lorg/partiql/ast/sql/SqlBlock; public synthetic fun visitExprNullIf (Lorg/partiql/ast/Expr$NullIf;Ljava/lang/Object;)Ljava/lang/Object; public fun visitExprNullIf (Lorg/partiql/ast/Expr$NullIf;Lorg/partiql/ast/sql/SqlBlock;)Lorg/partiql/ast/sql/SqlBlock; + public synthetic fun visitExprOperator (Lorg/partiql/ast/Expr$Operator;Ljava/lang/Object;)Ljava/lang/Object; + public fun visitExprOperator (Lorg/partiql/ast/Expr$Operator;Lorg/partiql/ast/sql/SqlBlock;)Lorg/partiql/ast/sql/SqlBlock; + public synthetic fun visitExprOr (Lorg/partiql/ast/Expr$Or;Ljava/lang/Object;)Ljava/lang/Object; + public fun visitExprOr (Lorg/partiql/ast/Expr$Or;Lorg/partiql/ast/sql/SqlBlock;)Lorg/partiql/ast/sql/SqlBlock; public synthetic fun visitExprOverlay (Lorg/partiql/ast/Expr$Overlay;Ljava/lang/Object;)Ljava/lang/Object; public fun visitExprOverlay (Lorg/partiql/ast/Expr$Overlay;Lorg/partiql/ast/sql/SqlBlock;)Lorg/partiql/ast/sql/SqlBlock; public synthetic fun visitExprParameter (Lorg/partiql/ast/Expr$Parameter;Ljava/lang/Object;)Ljava/lang/Object; @@ -6462,8 +6509,6 @@ public abstract class org/partiql/ast/sql/SqlDialect : org/partiql/ast/visitor/A public fun visitExprSubstring (Lorg/partiql/ast/Expr$Substring;Lorg/partiql/ast/sql/SqlBlock;)Lorg/partiql/ast/sql/SqlBlock; public synthetic fun visitExprTrim (Lorg/partiql/ast/Expr$Trim;Ljava/lang/Object;)Ljava/lang/Object; public fun visitExprTrim (Lorg/partiql/ast/Expr$Trim;Lorg/partiql/ast/sql/SqlBlock;)Lorg/partiql/ast/sql/SqlBlock; - public synthetic fun visitExprUnary (Lorg/partiql/ast/Expr$Unary;Ljava/lang/Object;)Ljava/lang/Object; - public fun visitExprUnary (Lorg/partiql/ast/Expr$Unary;Lorg/partiql/ast/sql/SqlBlock;)Lorg/partiql/ast/sql/SqlBlock; public synthetic fun visitExprValues (Lorg/partiql/ast/Expr$Values;Ljava/lang/Object;)Ljava/lang/Object; public fun visitExprValues (Lorg/partiql/ast/Expr$Values;Lorg/partiql/ast/sql/SqlBlock;)Lorg/partiql/ast/sql/SqlBlock; public synthetic fun visitExprValuesRow (Lorg/partiql/ast/Expr$Values$Row;Ljava/lang/Object;)Ljava/lang/Object; @@ -6667,12 +6712,12 @@ public abstract class org/partiql/ast/util/AstRewriter : org/partiql/ast/visitor public fun visitExcludeStepStructField (Lorg/partiql/ast/Exclude$Step$StructField;Ljava/lang/Object;)Lorg/partiql/ast/AstNode; public synthetic fun visitExcludeStepStructWildcard (Lorg/partiql/ast/Exclude$Step$StructWildcard;Ljava/lang/Object;)Ljava/lang/Object; public fun visitExcludeStepStructWildcard (Lorg/partiql/ast/Exclude$Step$StructWildcard;Ljava/lang/Object;)Lorg/partiql/ast/AstNode; + public synthetic fun visitExprAnd (Lorg/partiql/ast/Expr$And;Ljava/lang/Object;)Ljava/lang/Object; + public fun visitExprAnd (Lorg/partiql/ast/Expr$And;Ljava/lang/Object;)Lorg/partiql/ast/AstNode; public synthetic fun visitExprBagOp (Lorg/partiql/ast/Expr$BagOp;Ljava/lang/Object;)Ljava/lang/Object; public fun visitExprBagOp (Lorg/partiql/ast/Expr$BagOp;Ljava/lang/Object;)Lorg/partiql/ast/AstNode; public synthetic fun visitExprBetween (Lorg/partiql/ast/Expr$Between;Ljava/lang/Object;)Ljava/lang/Object; public fun visitExprBetween (Lorg/partiql/ast/Expr$Between;Ljava/lang/Object;)Lorg/partiql/ast/AstNode; - public synthetic fun visitExprBinary (Lorg/partiql/ast/Expr$Binary;Ljava/lang/Object;)Ljava/lang/Object; - public fun visitExprBinary (Lorg/partiql/ast/Expr$Binary;Ljava/lang/Object;)Lorg/partiql/ast/AstNode; public synthetic fun visitExprCall (Lorg/partiql/ast/Expr$Call;Ljava/lang/Object;)Ljava/lang/Object; public fun visitExprCall (Lorg/partiql/ast/Expr$Call;Ljava/lang/Object;)Lorg/partiql/ast/AstNode; public synthetic fun visitExprCanCast (Lorg/partiql/ast/Expr$CanCast;Ljava/lang/Object;)Ljava/lang/Object; @@ -6707,8 +6752,14 @@ public abstract class org/partiql/ast/util/AstRewriter : org/partiql/ast/visitor public fun visitExprLit (Lorg/partiql/ast/Expr$Lit;Ljava/lang/Object;)Lorg/partiql/ast/AstNode; public synthetic fun visitExprMatch (Lorg/partiql/ast/Expr$Match;Ljava/lang/Object;)Ljava/lang/Object; public fun visitExprMatch (Lorg/partiql/ast/Expr$Match;Ljava/lang/Object;)Lorg/partiql/ast/AstNode; + public synthetic fun visitExprNot (Lorg/partiql/ast/Expr$Not;Ljava/lang/Object;)Ljava/lang/Object; + public fun visitExprNot (Lorg/partiql/ast/Expr$Not;Ljava/lang/Object;)Lorg/partiql/ast/AstNode; public synthetic fun visitExprNullIf (Lorg/partiql/ast/Expr$NullIf;Ljava/lang/Object;)Ljava/lang/Object; public fun visitExprNullIf (Lorg/partiql/ast/Expr$NullIf;Ljava/lang/Object;)Lorg/partiql/ast/AstNode; + public synthetic fun visitExprOperator (Lorg/partiql/ast/Expr$Operator;Ljava/lang/Object;)Ljava/lang/Object; + public fun visitExprOperator (Lorg/partiql/ast/Expr$Operator;Ljava/lang/Object;)Lorg/partiql/ast/AstNode; + public synthetic fun visitExprOr (Lorg/partiql/ast/Expr$Or;Ljava/lang/Object;)Ljava/lang/Object; + public fun visitExprOr (Lorg/partiql/ast/Expr$Or;Ljava/lang/Object;)Lorg/partiql/ast/AstNode; public synthetic fun visitExprOverlay (Lorg/partiql/ast/Expr$Overlay;Ljava/lang/Object;)Ljava/lang/Object; public fun visitExprOverlay (Lorg/partiql/ast/Expr$Overlay;Ljava/lang/Object;)Lorg/partiql/ast/AstNode; public synthetic fun visitExprParameter (Lorg/partiql/ast/Expr$Parameter;Ljava/lang/Object;)Ljava/lang/Object; @@ -6739,8 +6790,6 @@ public abstract class org/partiql/ast/util/AstRewriter : org/partiql/ast/visitor public fun visitExprSubstring (Lorg/partiql/ast/Expr$Substring;Ljava/lang/Object;)Lorg/partiql/ast/AstNode; public synthetic fun visitExprTrim (Lorg/partiql/ast/Expr$Trim;Ljava/lang/Object;)Ljava/lang/Object; public fun visitExprTrim (Lorg/partiql/ast/Expr$Trim;Ljava/lang/Object;)Lorg/partiql/ast/AstNode; - public synthetic fun visitExprUnary (Lorg/partiql/ast/Expr$Unary;Ljava/lang/Object;)Ljava/lang/Object; - public fun visitExprUnary (Lorg/partiql/ast/Expr$Unary;Ljava/lang/Object;)Lorg/partiql/ast/AstNode; public synthetic fun visitExprValues (Lorg/partiql/ast/Expr$Values;Ljava/lang/Object;)Ljava/lang/Object; public fun visitExprValues (Lorg/partiql/ast/Expr$Values;Ljava/lang/Object;)Lorg/partiql/ast/AstNode; public synthetic fun visitExprValuesRow (Lorg/partiql/ast/Expr$Values$Row;Ljava/lang/Object;)Ljava/lang/Object; @@ -6998,9 +7047,9 @@ public abstract class org/partiql/ast/visitor/AstBaseVisitor : org/partiql/ast/v public fun visitExcludeStepStructField (Lorg/partiql/ast/Exclude$Step$StructField;Ljava/lang/Object;)Ljava/lang/Object; public fun visitExcludeStepStructWildcard (Lorg/partiql/ast/Exclude$Step$StructWildcard;Ljava/lang/Object;)Ljava/lang/Object; public fun visitExpr (Lorg/partiql/ast/Expr;Ljava/lang/Object;)Ljava/lang/Object; + public fun visitExprAnd (Lorg/partiql/ast/Expr$And;Ljava/lang/Object;)Ljava/lang/Object; public fun visitExprBagOp (Lorg/partiql/ast/Expr$BagOp;Ljava/lang/Object;)Ljava/lang/Object; public fun visitExprBetween (Lorg/partiql/ast/Expr$Between;Ljava/lang/Object;)Ljava/lang/Object; - public fun visitExprBinary (Lorg/partiql/ast/Expr$Binary;Ljava/lang/Object;)Ljava/lang/Object; public fun visitExprCall (Lorg/partiql/ast/Expr$Call;Ljava/lang/Object;)Ljava/lang/Object; public fun visitExprCanCast (Lorg/partiql/ast/Expr$CanCast;Ljava/lang/Object;)Ljava/lang/Object; public fun visitExprCanLosslessCast (Lorg/partiql/ast/Expr$CanLosslessCast;Ljava/lang/Object;)Ljava/lang/Object; @@ -7018,7 +7067,10 @@ public abstract class org/partiql/ast/visitor/AstBaseVisitor : org/partiql/ast/v public fun visitExprLike (Lorg/partiql/ast/Expr$Like;Ljava/lang/Object;)Ljava/lang/Object; public fun visitExprLit (Lorg/partiql/ast/Expr$Lit;Ljava/lang/Object;)Ljava/lang/Object; public fun visitExprMatch (Lorg/partiql/ast/Expr$Match;Ljava/lang/Object;)Ljava/lang/Object; + public fun visitExprNot (Lorg/partiql/ast/Expr$Not;Ljava/lang/Object;)Ljava/lang/Object; public fun visitExprNullIf (Lorg/partiql/ast/Expr$NullIf;Ljava/lang/Object;)Ljava/lang/Object; + public fun visitExprOperator (Lorg/partiql/ast/Expr$Operator;Ljava/lang/Object;)Ljava/lang/Object; + public fun visitExprOr (Lorg/partiql/ast/Expr$Or;Ljava/lang/Object;)Ljava/lang/Object; public fun visitExprOverlay (Lorg/partiql/ast/Expr$Overlay;Ljava/lang/Object;)Ljava/lang/Object; public fun visitExprParameter (Lorg/partiql/ast/Expr$Parameter;Ljava/lang/Object;)Ljava/lang/Object; public fun visitExprPath (Lorg/partiql/ast/Expr$Path;Ljava/lang/Object;)Ljava/lang/Object; @@ -7035,7 +7087,6 @@ public abstract class org/partiql/ast/visitor/AstBaseVisitor : org/partiql/ast/v public fun visitExprStructField (Lorg/partiql/ast/Expr$Struct$Field;Ljava/lang/Object;)Ljava/lang/Object; public fun visitExprSubstring (Lorg/partiql/ast/Expr$Substring;Ljava/lang/Object;)Ljava/lang/Object; public fun visitExprTrim (Lorg/partiql/ast/Expr$Trim;Ljava/lang/Object;)Ljava/lang/Object; - public fun visitExprUnary (Lorg/partiql/ast/Expr$Unary;Ljava/lang/Object;)Ljava/lang/Object; public fun visitExprValues (Lorg/partiql/ast/Expr$Values;Ljava/lang/Object;)Ljava/lang/Object; public fun visitExprValuesRow (Lorg/partiql/ast/Expr$Values$Row;Ljava/lang/Object;)Ljava/lang/Object; public fun visitExprVar (Lorg/partiql/ast/Expr$Var;Ljava/lang/Object;)Ljava/lang/Object; @@ -7192,9 +7243,9 @@ public abstract interface class org/partiql/ast/visitor/AstVisitor { public abstract fun visitExcludeStepStructField (Lorg/partiql/ast/Exclude$Step$StructField;Ljava/lang/Object;)Ljava/lang/Object; public abstract fun visitExcludeStepStructWildcard (Lorg/partiql/ast/Exclude$Step$StructWildcard;Ljava/lang/Object;)Ljava/lang/Object; public abstract fun visitExpr (Lorg/partiql/ast/Expr;Ljava/lang/Object;)Ljava/lang/Object; + public abstract fun visitExprAnd (Lorg/partiql/ast/Expr$And;Ljava/lang/Object;)Ljava/lang/Object; public abstract fun visitExprBagOp (Lorg/partiql/ast/Expr$BagOp;Ljava/lang/Object;)Ljava/lang/Object; public abstract fun visitExprBetween (Lorg/partiql/ast/Expr$Between;Ljava/lang/Object;)Ljava/lang/Object; - public abstract fun visitExprBinary (Lorg/partiql/ast/Expr$Binary;Ljava/lang/Object;)Ljava/lang/Object; public abstract fun visitExprCall (Lorg/partiql/ast/Expr$Call;Ljava/lang/Object;)Ljava/lang/Object; public abstract fun visitExprCanCast (Lorg/partiql/ast/Expr$CanCast;Ljava/lang/Object;)Ljava/lang/Object; public abstract fun visitExprCanLosslessCast (Lorg/partiql/ast/Expr$CanLosslessCast;Ljava/lang/Object;)Ljava/lang/Object; @@ -7212,7 +7263,10 @@ public abstract interface class org/partiql/ast/visitor/AstVisitor { public abstract fun visitExprLike (Lorg/partiql/ast/Expr$Like;Ljava/lang/Object;)Ljava/lang/Object; public abstract fun visitExprLit (Lorg/partiql/ast/Expr$Lit;Ljava/lang/Object;)Ljava/lang/Object; public abstract fun visitExprMatch (Lorg/partiql/ast/Expr$Match;Ljava/lang/Object;)Ljava/lang/Object; + public abstract fun visitExprNot (Lorg/partiql/ast/Expr$Not;Ljava/lang/Object;)Ljava/lang/Object; public abstract fun visitExprNullIf (Lorg/partiql/ast/Expr$NullIf;Ljava/lang/Object;)Ljava/lang/Object; + public abstract fun visitExprOperator (Lorg/partiql/ast/Expr$Operator;Ljava/lang/Object;)Ljava/lang/Object; + public abstract fun visitExprOr (Lorg/partiql/ast/Expr$Or;Ljava/lang/Object;)Ljava/lang/Object; public abstract fun visitExprOverlay (Lorg/partiql/ast/Expr$Overlay;Ljava/lang/Object;)Ljava/lang/Object; public abstract fun visitExprParameter (Lorg/partiql/ast/Expr$Parameter;Ljava/lang/Object;)Ljava/lang/Object; public abstract fun visitExprPath (Lorg/partiql/ast/Expr$Path;Ljava/lang/Object;)Ljava/lang/Object; @@ -7229,7 +7283,6 @@ public abstract interface class org/partiql/ast/visitor/AstVisitor { public abstract fun visitExprStructField (Lorg/partiql/ast/Expr$Struct$Field;Ljava/lang/Object;)Ljava/lang/Object; public abstract fun visitExprSubstring (Lorg/partiql/ast/Expr$Substring;Ljava/lang/Object;)Ljava/lang/Object; public abstract fun visitExprTrim (Lorg/partiql/ast/Expr$Trim;Ljava/lang/Object;)Ljava/lang/Object; - public abstract fun visitExprUnary (Lorg/partiql/ast/Expr$Unary;Ljava/lang/Object;)Ljava/lang/Object; public abstract fun visitExprValues (Lorg/partiql/ast/Expr$Values;Ljava/lang/Object;)Ljava/lang/Object; public abstract fun visitExprValuesRow (Lorg/partiql/ast/Expr$Values$Row;Ljava/lang/Object;)Ljava/lang/Object; public abstract fun visitExprVar (Lorg/partiql/ast/Expr$Var;Ljava/lang/Object;)Ljava/lang/Object; diff --git a/partiql-ast/src/main/kotlin/org/partiql/ast/helpers/ToLegacyAst.kt b/partiql-ast/src/main/kotlin/org/partiql/ast/helpers/ToLegacyAst.kt index 2549ea6d5..cd25a2fde 100644 --- a/partiql-ast/src/main/kotlin/org/partiql/ast/helpers/ToLegacyAst.kt +++ b/partiql-ast/src/main/kotlin/org/partiql/ast/helpers/ToLegacyAst.kt @@ -336,70 +336,86 @@ private class AstTranslator(val metas: Map) : AstBaseVisi return aggregates.contains(this) } - override fun visitExprUnary(node: Expr.Unary, ctx: Ctx) = translate(node) { metas -> - val arg = visitExpr(node.expr, ctx) - when (node.op) { - Expr.Unary.Op.NOT -> not(arg, metas) - Expr.Unary.Op.POS -> { - when { - arg !is PartiqlAst.Expr.Lit -> pos(arg) - arg.value is IntElement -> arg - arg.value is FloatElement -> arg - arg.value is DecimalElement -> arg - else -> pos(arg) + override fun visitExprOperator(node: Expr.Operator, ctx: Ctx) = translate(node) { metas -> + val lhs = node.lhs?.let { visitExpr(it, ctx) } + val rhs = visitExpr(node.rhs, ctx) + if (lhs == null) { + when (node.symbol) { + "+" -> { + when { + rhs !is PartiqlAst.Expr.Lit -> pos(rhs) + rhs.value is IntElement -> rhs + rhs.value is FloatElement -> rhs + rhs.value is DecimalElement -> rhs + else -> pos(rhs) + } } - } - Expr.Unary.Op.NEG -> { - when { - arg !is PartiqlAst.Expr.Lit -> neg(arg, metas) - arg.value is IntElement -> { - val intValue = when (arg.value.integerSize) { - IntElementSize.LONG -> ionInt(-arg.value.longValue) - IntElementSize.BIG_INTEGER -> when (arg.value.bigIntegerValue) { - Long.MAX_VALUE.toBigInteger() + (1L).toBigInteger() -> ionInt(Long.MIN_VALUE) - else -> ionInt(arg.value.bigIntegerValue * BigInteger.valueOf(-1L)) + "-" -> { + when { + rhs !is PartiqlAst.Expr.Lit -> neg(rhs, metas) + rhs.value is IntElement -> { + val intValue = when (rhs.value.integerSize) { + IntElementSize.LONG -> ionInt(-rhs.value.longValue) + IntElementSize.BIG_INTEGER -> when (rhs.value.bigIntegerValue) { + Long.MAX_VALUE.toBigInteger() + (1L).toBigInteger() -> ionInt(Long.MIN_VALUE) + else -> ionInt(rhs.value.bigIntegerValue * BigInteger.valueOf(-1L)) + } } + rhs.copy( + value = intValue.asAnyElement(), + metas = metas, + ) } - arg.copy( - value = intValue.asAnyElement(), + rhs.value is FloatElement -> rhs.copy( + value = ionFloat(-(rhs.value.doubleValue)).asAnyElement(), + metas = metas, + ) + rhs.value is DecimalElement -> rhs.copy( + value = ionDecimal(Decimal.valueOf(-(rhs.value.decimalValue))).asAnyElement(), metas = metas, ) + else -> neg(rhs, metas) } - arg.value is FloatElement -> arg.copy( - value = ionFloat(-(arg.value.doubleValue)).asAnyElement(), - metas = metas, - ) - arg.value is DecimalElement -> arg.copy( - value = ionDecimal(Decimal.valueOf(-(arg.value.decimalValue))).asAnyElement(), - metas = metas, - ) - else -> neg(arg, metas) } + else -> error("unsupported unary expr operator $node") + } + } else { + val operands = listOf(lhs, rhs) + when (node.symbol) { + "+" -> plus(operands, metas) + "-" -> minus(operands, metas) + "*" -> times(operands, metas) + "/" -> divide(operands, metas) + "%" -> modulo(operands, metas) + "||" -> concat(operands, metas) + "=" -> eq(operands, metas) + "<>" -> ne(operands, metas) + "!=" -> ne(operands, metas) + ">" -> gt(operands, metas) + ">=" -> gte(operands, metas) + "<" -> lt(operands, metas) + "<=" -> lte(operands, metas) + "&" -> bitwiseAnd(operands, metas) + else -> error("unsupported binary expr operator $node") } } } - override fun visitExprBinary(node: Expr.Binary, ctx: Ctx) = translate(node) { metas -> + override fun visitExprAnd(node: Expr.And, ctx: Ctx) = translate(node) { metas -> val lhs = visitExpr(node.lhs, ctx) val rhs = visitExpr(node.rhs, ctx) - val operands = listOf(lhs, rhs) - when (node.op) { - Expr.Binary.Op.PLUS -> plus(operands, metas) - Expr.Binary.Op.MINUS -> minus(operands, metas) - Expr.Binary.Op.TIMES -> times(operands, metas) - Expr.Binary.Op.DIVIDE -> divide(operands, metas) - Expr.Binary.Op.MODULO -> modulo(operands, metas) - Expr.Binary.Op.CONCAT -> concat(operands, metas) - Expr.Binary.Op.AND -> and(operands, metas) - Expr.Binary.Op.OR -> or(operands, metas) - Expr.Binary.Op.EQ -> eq(operands, metas) - Expr.Binary.Op.NE -> ne(operands, metas) - Expr.Binary.Op.GT -> gt(operands, metas) - Expr.Binary.Op.GTE -> gte(operands, metas) - Expr.Binary.Op.LT -> lt(operands, metas) - Expr.Binary.Op.LTE -> lte(operands, metas) - Expr.Binary.Op.BITWISE_AND -> bitwiseAnd(operands, metas) - } + and(lhs, rhs) + } + + override fun visitExprOr(node: Expr.Or, ctx: Ctx) = translate(node) { metas -> + val lhs = visitExpr(node.lhs, ctx) + val rhs = visitExpr(node.rhs, ctx) + or(lhs, rhs) + } + + override fun visitExprNot(node: Expr.Not, ctx: Ctx) = translate(node) { metas -> + val rhs = visitExpr(node.value, ctx) + not(rhs) } override fun visitExprPath(node: Expr.Path, ctx: Ctx) = translate(node) { metas -> diff --git a/partiql-ast/src/main/kotlin/org/partiql/ast/sql/SqlDialect.kt b/partiql-ast/src/main/kotlin/org/partiql/ast/sql/SqlDialect.kt index 97a863566..0b3b18043 100644 --- a/partiql-ast/src/main/kotlin/org/partiql/ast/sql/SqlDialect.kt +++ b/partiql-ast/src/main/kotlin/org/partiql/ast/sql/SqlDialect.kt @@ -229,44 +229,47 @@ public abstract class SqlDialect : AstBaseVisitor() { return head concat r("`$value`") } - override fun visitExprUnary(node: Expr.Unary, head: SqlBlock): SqlBlock { - val op = when (node.op) { - Expr.Unary.Op.NOT -> "NOT (" - Expr.Unary.Op.POS -> "+(" - Expr.Unary.Op.NEG -> "-(" + override fun visitExprOperator(node: Expr.Operator, head: SqlBlock): SqlBlock { + val lhs = node.lhs + return if (lhs != null) { + var h = head + h = visitExprWrapped(node.lhs, h) + h = h concat r(" ${node.symbol} ") + h = visitExprWrapped(node.rhs, h) + h + } else { + var h = head + h = h concat r(node.symbol + "(") + h = visitExprWrapped(node.rhs, h) + h = h concat r(")") + return h } + } + + override fun visitExprAnd(node: Expr.And, head: SqlBlock): SqlBlock { var h = head - h = h concat r(op) - h = visitExprWrapped(node.expr, h) - h = h concat r(")") + h = visitExprWrapped(node.lhs, h) + h = h concat r(" AND ") + h = visitExprWrapped(node.rhs, h) return h } - override fun visitExprBinary(node: Expr.Binary, head: SqlBlock): SqlBlock { - val op = when (node.op) { - Expr.Binary.Op.PLUS -> "+" - Expr.Binary.Op.MINUS -> "-" - Expr.Binary.Op.TIMES -> "*" - Expr.Binary.Op.DIVIDE -> "/" - Expr.Binary.Op.MODULO -> "%" - Expr.Binary.Op.CONCAT -> "||" - Expr.Binary.Op.AND -> "AND" - Expr.Binary.Op.OR -> "OR" - Expr.Binary.Op.EQ -> "=" - Expr.Binary.Op.NE -> "<>" - Expr.Binary.Op.GT -> ">" - Expr.Binary.Op.GTE -> ">=" - Expr.Binary.Op.LT -> "<" - Expr.Binary.Op.LTE -> "<=" - Expr.Binary.Op.BITWISE_AND -> "&" - } + override fun visitExprOr(node: Expr.Or, head: SqlBlock): SqlBlock { var h = head h = visitExprWrapped(node.lhs, h) - h = h concat r(" $op ") + h = h concat r(" OR ") h = visitExprWrapped(node.rhs, h) return h } + override fun visitExprNot(node: Expr.Not, head: SqlBlock): SqlBlock { + var h = head + h = h concat r("NOT (") + h = visitExprWrapped(node.value, h) + h = h concat r(")") + return h + } + override fun visitExprVar(node: Expr.Var, head: SqlBlock): SqlBlock { var h = head // Prepend @ diff --git a/partiql-ast/src/main/kotlin/org/partiql/ast/sql/internal/InternalSqlDialect.kt b/partiql-ast/src/main/kotlin/org/partiql/ast/sql/internal/InternalSqlDialect.kt index e09533722..adde21b4a 100644 --- a/partiql-ast/src/main/kotlin/org/partiql/ast/sql/internal/InternalSqlDialect.kt +++ b/partiql-ast/src/main/kotlin/org/partiql/ast/sql/internal/InternalSqlDialect.kt @@ -259,44 +259,47 @@ internal abstract class InternalSqlDialect : AstBaseVisitor "NOT (" - Expr.Unary.Op.POS -> "+(" - Expr.Unary.Op.NEG -> "-(" + override fun visitExprOperator(node: Expr.Operator, tail: InternalSqlBlock): InternalSqlBlock { + val lhs = node.lhs + return if (lhs != null) { + var t = tail + t = visitExprWrapped(node.lhs, t) + t = t concat " ${node.symbol} " + t = visitExprWrapped(node.rhs, t) + t + } else { + var t = tail + t = t concat node.symbol + "(" + t = visitExprWrapped(node.rhs, t) + t = t concat ")" + return t } + } + + override fun visitExprAnd(node: Expr.And, tail: InternalSqlBlock): InternalSqlBlock { var t = tail - t = t concat op - t = visitExprWrapped(node.expr, t) - t = t concat ")" + t = visitExprWrapped(node.lhs, t) + t = t concat " AND " + t = visitExprWrapped(node.rhs, t) return t } - override fun visitExprBinary(node: Expr.Binary, tail: InternalSqlBlock): InternalSqlBlock { - val op = when (node.op) { - Expr.Binary.Op.PLUS -> "+" - Expr.Binary.Op.MINUS -> "-" - Expr.Binary.Op.TIMES -> "*" - Expr.Binary.Op.DIVIDE -> "/" - Expr.Binary.Op.MODULO -> "%" - Expr.Binary.Op.CONCAT -> "||" - Expr.Binary.Op.AND -> "AND" - Expr.Binary.Op.OR -> "OR" - Expr.Binary.Op.EQ -> "=" - Expr.Binary.Op.NE -> "<>" - Expr.Binary.Op.GT -> ">" - Expr.Binary.Op.GTE -> ">=" - Expr.Binary.Op.LT -> "<" - Expr.Binary.Op.LTE -> "<=" - Expr.Binary.Op.BITWISE_AND -> "&" - } + override fun visitExprOr(node: Expr.Or, tail: InternalSqlBlock): InternalSqlBlock { var t = tail t = visitExprWrapped(node.lhs, t) - t = t concat " $op " + t = t concat " OR " t = visitExprWrapped(node.rhs, t) return t } + override fun visitExprNot(node: Expr.Not, tail: InternalSqlBlock): InternalSqlBlock { + var t = tail + t = t concat "NOT (" + t = visitExprWrapped(node.value, t) + t = t concat ")" + return t + } + override fun visitExprVar(node: Expr.Var, tail: InternalSqlBlock): InternalSqlBlock { var t = tail // Prepend @ diff --git a/partiql-ast/src/main/resources/partiql_ast.ion b/partiql-ast/src/main/resources/partiql_ast.ion index 070b8cd49..3cacacf49 100644 --- a/partiql-ast/src/main/resources/partiql_ast.ion +++ b/partiql-ast/src/main/resources/partiql_ast.ion @@ -362,19 +362,26 @@ expr::[ index: int, }, - // Unary Operators - unary::{ - op: [ NOT, POS, NEG ], - expr: expr, + // Operator expr node + operator::{ + symbol: string, + lhs: optional::expr, + rhs: expr }, - // Binary Operators - binary::{ - op: [ - PLUS, MINUS, TIMES, DIVIDE, MODULO, CONCAT, BITWISE_AND, - AND, OR, - EQ, NE, GT, GTE, LT, LTE, - ], + // SQL special form `NOT` + not::{ + value: expr, + }, + + // SQL special form `AND` + and::{ + lhs: expr, + rhs: expr, + }, + + // SQL special form `OR` + or::{ lhs: expr, rhs: expr, }, diff --git a/partiql-ast/src/test/kotlin/org/partiql/ast/helpers/ToLegacyAstTest.kt b/partiql-ast/src/test/kotlin/org/partiql/ast/helpers/ToLegacyAstTest.kt index d54fb42e6..cd5109e47 100644 --- a/partiql-ast/src/test/kotlin/org/partiql/ast/helpers/ToLegacyAstTest.kt +++ b/partiql-ast/src/test/kotlin/org/partiql/ast/helpers/ToLegacyAstTest.kt @@ -289,27 +289,26 @@ class ToLegacyAstTest { @JvmStatic fun operators() = listOf( expect("(not (lit null))") { - exprUnary { - op = Expr.Unary.Op.NOT - expr = NULL + exprNot { + value = NULL } }, expect("(pos (lit null))") { - exprUnary { - op = Expr.Unary.Op.POS - expr = NULL + exprOperator { + symbol = "+" + rhs = NULL } }, expect("(neg (lit null))") { - exprUnary { - op = Expr.Unary.Op.NEG - expr = NULL + exprOperator { + symbol = "-" + rhs = NULL } }, // we don't really need to test _all_ binary operators expect("(plus (lit null) (lit null))") { - exprBinary { - op = Expr.Binary.Op.PLUS + exprOperator { + symbol = "+" lhs = NULL rhs = NULL } diff --git a/partiql-ast/src/test/kotlin/org/partiql/ast/sql/SqlDialectTest.kt b/partiql-ast/src/test/kotlin/org/partiql/ast/sql/SqlDialectTest.kt index acea03a70..393846b1c 100644 --- a/partiql-ast/src/test/kotlin/org/partiql/ast/sql/SqlDialectTest.kt +++ b/partiql-ast/src/test/kotlin/org/partiql/ast/sql/SqlDialectTest.kt @@ -228,65 +228,62 @@ class SqlDialectTest { @JvmStatic fun exprOperators() = listOf( expect("NOT (NULL)") { - exprUnary { - op = Expr.Unary.Op.NOT - expr = NULL + exprNot { + value = NULL } }, expect("+(NULL)") { - exprUnary { - op = Expr.Unary.Op.POS - expr = NULL + exprOperator { + symbol = "+" + rhs = NULL } }, expect("-(NULL)") { - exprUnary { - op = Expr.Unary.Op.NEG - expr = NULL + exprOperator { + symbol = "-" + rhs = NULL } }, expect("NOT (NOT (NULL))") { - exprUnary { - op = Expr.Unary.Op.NOT - expr = exprUnary { - op = Expr.Unary.Op.NOT - expr = NULL + exprNot { + value = exprNot { + value = NULL } } }, expect("+(+(NULL))") { - exprUnary { - op = Expr.Unary.Op.POS - expr = exprUnary { - op = Expr.Unary.Op.POS - expr = NULL + exprOperator { + symbol = "+" + rhs = exprOperator { + symbol = "+" + rhs = NULL } } }, expect("-(-(NULL))") { - exprUnary { - op = Expr.Unary.Op.NEG - expr = exprUnary { - op = Expr.Unary.Op.NEG - expr = NULL + exprOperator { + symbol = "-" + rhs = exprOperator { + symbol = "-" + rhs = NULL } } }, expect("+(-(+(NULL)))") { - exprUnary { - op = Expr.Unary.Op.POS - expr = exprUnary { - op = Expr.Unary.Op.NEG - expr = exprUnary { - op = Expr.Unary.Op.POS - expr = NULL + exprOperator { + symbol = "+" + rhs = exprOperator { + symbol = "-" + rhs = exprOperator { + symbol = "+" + rhs = NULL } } } }, expect("NULL + NULL") { - exprBinary { - op = Expr.Binary.Op.PLUS + exprOperator { + symbol = "+" lhs = NULL rhs = NULL } @@ -538,8 +535,8 @@ class SqlDialectTest { scope = Expr.Var.Scope.DEFAULT } steps += exprPathStepIndex( - exprBinary { - op = Expr.Binary.Op.PLUS + exprOperator { + symbol = "+" lhs = exprLit(int32Value(1)) rhs = exprVar { identifier = id("a") @@ -1738,8 +1735,8 @@ class SqlDialectTest { @JvmStatic private fun subqueryCases() = listOf( expect("1 = (SELECT a FROM T)") { - exprBinary { - op = Expr.Binary.Op.EQ + exprOperator { + symbol = "=" lhs = exprLit(int32Value(1)) rhs = exprSFW { select = select("a") @@ -1748,8 +1745,8 @@ class SqlDialectTest { } }, expect("(1, 2) = (SELECT a FROM T)") { - exprBinary { - op = Expr.Binary.Op.EQ + exprOperator { + symbol = "=" lhs = exprCollection { type = Expr.Collection.Type.LIST values += exprLit(int32Value(1)) diff --git a/partiql-eval/src/main/java/org/partiql/eval/value/Datum.java b/partiql-eval/src/main/java/org/partiql/eval/value/Datum.java index be3892c1c..2b068b5f4 100644 --- a/partiql-eval/src/main/java/org/partiql/eval/value/Datum.java +++ b/partiql-eval/src/main/java/org/partiql/eval/value/Datum.java @@ -317,7 +317,6 @@ default Iterator getFields() { * @throws NullPointerException if this instance also returns true on {@link #isNull()}; callers should check that * {@link #isNull()} returns false before attempting to invoke this method. */ - @NotNull default Datum get(@NotNull String name) { throw new UnsupportedOperationException(); } @@ -331,7 +330,6 @@ default Datum get(@NotNull String name) { * @throws NullPointerException if this instance also returns true on {@link #isNull()}; callers should check that * {@link #isNull()} returns false before attempting to invoke this method. */ - @NotNull default Datum getInsensitive(@NotNull String name) { throw new UnsupportedOperationException(); } diff --git a/partiql-eval/src/main/java/org/partiql/eval/value/DatumStruct.java b/partiql-eval/src/main/java/org/partiql/eval/value/DatumStruct.java index 07b18dcdb..1bc319954 100644 --- a/partiql-eval/src/main/java/org/partiql/eval/value/DatumStruct.java +++ b/partiql-eval/src/main/java/org/partiql/eval/value/DatumStruct.java @@ -15,10 +15,10 @@ class DatumStruct implements Datum { @NotNull - private final Map> _delegate; + private final HashMap> _delegate; @NotNull - private final Map> _delegateNormalized; + private final HashMap> _delegateNormalized; private final static PType _type = PType.typeStruct(); @@ -50,24 +50,28 @@ public Iterator getFields() { ).iterator(); } - @NotNull @Override public Datum get(@NotNull String name) { - try { - return _delegate.get(name).get(0); - } catch (IndexOutOfBoundsException ex) { - throw new NullPointerException("Could not find struct key: " + name); + List values = _delegate.get(name); + if (values == null) { + return null; + } + if (values.isEmpty()) { + return null; } + return values.get(0); } - @NotNull @Override public Datum getInsensitive(@NotNull String name) { - try { - return _delegateNormalized.get(name).get(0); - } catch (IndexOutOfBoundsException ex) { - throw new NullPointerException("Could not find struct key: " + name); + List values = _delegateNormalized.get(name.toLowerCase()); + if (values == null) { + return null; + } + if (values.isEmpty()) { + return null; } + return values.get(0); } @NotNull @@ -75,4 +79,18 @@ public Datum getInsensitive(@NotNull String name) { public PType getType() { return _type; } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("struct::{ "); + for (Map.Entry> entry : _delegate.entrySet()) { + sb.append(entry.getKey()); + sb.append(": "); + sb.append(entry.getValue().toString()); + sb.append(", "); + } + sb.append(" }"); + return sb.toString(); + } } diff --git a/partiql-eval/src/main/java/org/partiql/eval/value/DatumTimestamp.java b/partiql-eval/src/main/java/org/partiql/eval/value/DatumTimestamp.java index 5063668e3..06f27c0c6 100644 --- a/partiql-eval/src/main/java/org/partiql/eval/value/DatumTimestamp.java +++ b/partiql-eval/src/main/java/org/partiql/eval/value/DatumTimestamp.java @@ -14,7 +14,7 @@ class DatumTimestamp implements Datum { // TODO: Pass precision to constructor. // TODO: Create a variant specifically for without TZ - private final static PType _type = PType.typeTimeWithTZ(6); + private final static PType _type = PType.typeTimestampWithTZ(6); DatumTimestamp(@NotNull Timestamp value) { _value = value; diff --git a/partiql-eval/src/main/kotlin/org/partiql/eval/internal/Compiler.kt b/partiql-eval/src/main/kotlin/org/partiql/eval/internal/Compiler.kt index 8c95fb078..a2245c1dc 100644 --- a/partiql-eval/src/main/kotlin/org/partiql/eval/internal/Compiler.kt +++ b/partiql-eval/src/main/kotlin/org/partiql/eval/internal/Compiler.kt @@ -11,9 +11,9 @@ import org.partiql.eval.internal.operator.rel.RelFilter import org.partiql.eval.internal.operator.rel.RelIntersectAll import org.partiql.eval.internal.operator.rel.RelIntersectDistinct import org.partiql.eval.internal.operator.rel.RelJoinInner -import org.partiql.eval.internal.operator.rel.RelJoinLeft import org.partiql.eval.internal.operator.rel.RelJoinOuterFull -import org.partiql.eval.internal.operator.rel.RelJoinRight +import org.partiql.eval.internal.operator.rel.RelJoinOuterLeft +import org.partiql.eval.internal.operator.rel.RelJoinOuterRight import org.partiql.eval.internal.operator.rel.RelLimit import org.partiql.eval.internal.operator.rel.RelOffset import org.partiql.eval.internal.operator.rel.RelProject @@ -364,15 +364,15 @@ internal class Compiler( val condition = visitRex(node.rex, ctx) return when (node.type) { Rel.Op.Join.Type.INNER -> RelJoinInner(lhs, rhs, condition) - Rel.Op.Join.Type.LEFT -> RelJoinLeft(lhs, rhs, condition) - Rel.Op.Join.Type.RIGHT -> RelJoinRight(lhs, rhs, condition) - Rel.Op.Join.Type.FULL -> RelJoinOuterFull(lhs, rhs, condition) + Rel.Op.Join.Type.LEFT -> RelJoinOuterLeft(lhs, rhs, condition, rhsType = node.rhs.type) + Rel.Op.Join.Type.RIGHT -> RelJoinOuterRight(lhs, rhs, condition, lhsType = node.lhs.type) + Rel.Op.Join.Type.FULL -> RelJoinOuterFull(lhs, rhs, condition, lhsType = node.lhs.type, rhsType = node.rhs.type) } } override fun visitRexOpCase(node: Rex.Op.Case, ctx: PType?): Operator { val branches = node.branches.map { branch -> - visitRex(branch.condition, ctx) to visitRex(branch.rex, ctx) + visitRex(branch.condition, ctx).modeHandled() to visitRex(branch.rex, ctx) } val default = visitRex(node.default, ctx) return ExprCase(branches, default) diff --git a/partiql-eval/src/main/kotlin/org/partiql/eval/internal/helpers/TypesUtility.kt b/partiql-eval/src/main/kotlin/org/partiql/eval/internal/helpers/TypesUtility.kt new file mode 100644 index 000000000..5857f2eee --- /dev/null +++ b/partiql-eval/src/main/kotlin/org/partiql/eval/internal/helpers/TypesUtility.kt @@ -0,0 +1,80 @@ +package org.partiql.eval.internal.helpers + +import org.partiql.types.AnyOfType +import org.partiql.types.AnyType +import org.partiql.types.BagType +import org.partiql.types.BlobType +import org.partiql.types.BoolType +import org.partiql.types.ClobType +import org.partiql.types.DateType +import org.partiql.types.DecimalType +import org.partiql.types.FloatType +import org.partiql.types.GraphType +import org.partiql.types.IntType +import org.partiql.types.ListType +import org.partiql.types.MissingType +import org.partiql.types.NullType +import org.partiql.types.SexpType +import org.partiql.types.StaticType +import org.partiql.types.StringType +import org.partiql.types.StructType +import org.partiql.types.SymbolType +import org.partiql.types.TimeType +import org.partiql.types.TimestampType +import org.partiql.value.PartiQLValueExperimental +import org.partiql.value.PartiQLValueType + +internal object TypesUtility { + + @OptIn(PartiQLValueExperimental::class) + internal fun StaticType.toRuntimeType(): PartiQLValueType { + if (this is AnyOfType) { + // handle anyOf(null, T) cases + val t = types.filter { it !is NullType && it !is MissingType } + return if (t.size != 1) { + PartiQLValueType.ANY + } else { + t.first().asRuntimeType() + } + } + return this.asRuntimeType() + } + + @OptIn(PartiQLValueExperimental::class) + private fun StaticType.asRuntimeType(): PartiQLValueType = when (this) { + is AnyOfType -> PartiQLValueType.ANY + is AnyType -> PartiQLValueType.ANY + is BlobType -> PartiQLValueType.BLOB + is BoolType -> PartiQLValueType.BOOL + is ClobType -> PartiQLValueType.CLOB + is BagType -> PartiQLValueType.BAG + is ListType -> PartiQLValueType.LIST + is SexpType -> PartiQLValueType.SEXP + is DateType -> PartiQLValueType.DATE + // TODO: Run time decimal type does not model precision scale constraint yet + // despite that we match to Decimal vs Decimal_ARBITRARY (PVT) here + // but when mapping it back to Static Type, (i.e, mapping function return type to Value Type) + // we can only map to Unconstrained decimal (Static Type) + is DecimalType -> { + when (this.precisionScaleConstraint) { + is DecimalType.PrecisionScaleConstraint.Constrained -> PartiQLValueType.DECIMAL + DecimalType.PrecisionScaleConstraint.Unconstrained -> PartiQLValueType.DECIMAL_ARBITRARY + } + } + is FloatType -> PartiQLValueType.FLOAT64 + is GraphType -> error("Graph type missing from runtime types") + is IntType -> when (this.rangeConstraint) { + IntType.IntRangeConstraint.SHORT -> PartiQLValueType.INT16 + IntType.IntRangeConstraint.INT4 -> PartiQLValueType.INT32 + IntType.IntRangeConstraint.LONG -> PartiQLValueType.INT64 + IntType.IntRangeConstraint.UNCONSTRAINED -> PartiQLValueType.INT + } + MissingType -> PartiQLValueType.MISSING + is NullType -> PartiQLValueType.NULL + is StringType -> PartiQLValueType.STRING + is StructType -> PartiQLValueType.STRUCT + is SymbolType -> PartiQLValueType.SYMBOL + is TimeType -> PartiQLValueType.TIME + is TimestampType -> PartiQLValueType.TIMESTAMP + } +} diff --git a/partiql-eval/src/main/kotlin/org/partiql/eval/internal/helpers/ValueUtility.kt b/partiql-eval/src/main/kotlin/org/partiql/eval/internal/helpers/ValueUtility.kt index 312f9a0a2..9b4db288d 100644 --- a/partiql-eval/src/main/kotlin/org/partiql/eval/internal/helpers/ValueUtility.kt +++ b/partiql-eval/src/main/kotlin/org/partiql/eval/internal/helpers/ValueUtility.kt @@ -49,7 +49,6 @@ internal object ValueUtility { * @throws NullPointerException if the value is null * @throws TypeCheckException if the value's type is not a text type (string, symbol, char) */ - @OptIn(PartiQLValueExperimental::class) fun Datum.getText(): String { return when (this.type.kind) { PType.Kind.STRING, PType.Kind.SYMBOL, PType.Kind.CHAR -> this.string @@ -67,7 +66,6 @@ internal object ValueUtility { * @throws NullPointerException if the value is null * @throws TypeCheckException if type is not an integer type */ - @OptIn(PartiQLValueExperimental::class) fun Datum.getBigIntCoerced(): BigInteger { return when (this.type.kind) { PType.Kind.TINYINT -> this.byte.toInt().toBigInteger() @@ -90,7 +88,6 @@ internal object ValueUtility { * @throws NullPointerException if the value is null * @throws TypeCheckException if type is not an integer type */ - @OptIn(PartiQLValueExperimental::class) fun Datum.getInt32Coerced(): Int { return when (this.type.kind) { PType.Kind.TINYINT -> this.byte.toInt() diff --git a/partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rel/RelJoinInner.kt b/partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rel/RelJoinInner.kt index 265e78a54..cc8feae87 100644 --- a/partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rel/RelJoinInner.kt +++ b/partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rel/RelJoinInner.kt @@ -1,17 +1,70 @@ package org.partiql.eval.internal.operator.rel +import org.partiql.eval.internal.Environment import org.partiql.eval.internal.Record +import org.partiql.eval.internal.helpers.ValueUtility.isTrue import org.partiql.eval.internal.operator.Operator +import org.partiql.value.PartiQLValueExperimental +/** + * Inner Join returns all joined records from the [lhs] and [rhs] when the [condition] evaluates to true. + * + * Note: This is currently the lateral version of the inner join. In the future, the two implementations + * (lateral vs non-lateral) may be separated for performance improvements. + */ internal class RelJoinInner( - override val lhs: Operator.Relation, - override val rhs: Operator.Relation, - override val condition: Operator.Expr, -) : RelJoinNestedLoop() { - override fun join(condition: Boolean, lhs: Record, rhs: Record): Record? { - return when (condition) { - true -> lhs + rhs + private val lhs: Operator.Relation, + private val rhs: Operator.Relation, + private val condition: Operator.Expr, +) : RelPeeking() { + + private lateinit var env: Environment + private lateinit var iterator: Iterator + + override fun openPeeking(env: Environment) { + this.env = env + lhs.open(env) + iterator = implementation() + } + + override fun peek(): Record? { + return when (iterator.hasNext()) { + true -> iterator.next() false -> null } } + + override fun closePeeking() { + lhs.close() + rhs.close() + iterator = emptyList().iterator() + } + + /** + * INNER JOIN (LATERAL) + * + * Algorithm: + * ``` + * for lhsRecord in lhs: + * for rhsRecord in rhs(lhsRecord): + * if (condition matches): + * conditionMatched = true + * yield(lhsRecord + rhsRecord) + * ``` + * + * Development Note: The non-lateral version wouldn't need to push to the current environment. + */ + @OptIn(PartiQLValueExperimental::class) + private fun implementation() = iterator { + for (lhsRecord in lhs) { + rhs.open(env.push(lhsRecord)) + for (rhsRecord in rhs) { + val input = lhsRecord + rhsRecord + val result = condition.eval(env.push(input)) + if (result.isTrue()) { + yield(lhsRecord + rhsRecord) + } + } + } + } } diff --git a/partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rel/RelJoinLeft.kt b/partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rel/RelJoinLeft.kt deleted file mode 100644 index eae51e9a3..000000000 --- a/partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rel/RelJoinLeft.kt +++ /dev/null @@ -1,18 +0,0 @@ -package org.partiql.eval.internal.operator.rel - -import org.partiql.eval.internal.Record -import org.partiql.eval.internal.operator.Operator - -internal class RelJoinLeft( - override val lhs: Operator.Relation, - override val rhs: Operator.Relation, - override val condition: Operator.Expr, -) : RelJoinNestedLoop() { - - override fun join(condition: Boolean, lhs: Record, rhs: Record): Record { - if (condition.not()) { - rhs.padNull() - } - return lhs + rhs - } -} diff --git a/partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rel/RelJoinNestedLoop.kt b/partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rel/RelJoinNestedLoop.kt deleted file mode 100644 index e52817db3..000000000 --- a/partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rel/RelJoinNestedLoop.kt +++ /dev/null @@ -1,91 +0,0 @@ -package org.partiql.eval.internal.operator.rel - -import org.partiql.eval.internal.Environment -import org.partiql.eval.internal.Record -import org.partiql.eval.internal.helpers.IteratorSupplier -import org.partiql.eval.internal.helpers.ValueUtility.isTrue -import org.partiql.eval.internal.operator.Operator -import org.partiql.eval.value.Datum -import org.partiql.eval.value.Field -import org.partiql.types.PType - -internal abstract class RelJoinNestedLoop : RelPeeking() { - - abstract val lhs: Operator.Relation - abstract val rhs: Operator.Relation - abstract val condition: Operator.Expr - - private var lhsRecord: Record? = null - private lateinit var env: Environment - - override fun openPeeking(env: Environment) { - this.env = env - lhs.open(env) - if (lhs.hasNext().not()) { - return - } - lhsRecord = lhs.next() - rhs.open(env.push(lhsRecord!!)) - } - - abstract fun join(condition: Boolean, lhs: Record, rhs: Record): Record? - - override fun peek(): Record? { - if (lhsRecord == null) { - return null - } - var rhsRecord = when (rhs.hasNext()) { - true -> rhs.next() - false -> null - } - var toReturn: Record? = null - do { - // Acquire LHS and RHS Records - if (rhsRecord == null) { - rhs.close() - if (lhs.hasNext().not()) { - return null - } - lhsRecord = lhs.next() - rhs.open(env.push(lhsRecord!!)) - rhsRecord = when (rhs.hasNext()) { - true -> rhs.next() - false -> null - } - } - // Return Joined Record - if (rhsRecord != null && lhsRecord != null) { - val input = lhsRecord!! + rhsRecord - val result = condition.eval(env.push(input)) - toReturn = join(result.isTrue(), lhsRecord!!, rhsRecord) - } - // Move the pointer to the next row for the RHS - if (toReturn == null) rhsRecord = if (rhs.hasNext()) rhs.next() else null - } - while (toReturn == null) - return toReturn - } - - override fun closePeeking() { - lhs.close() - rhs.close() - } - - internal fun Record.padNull() { - this.values.indices.forEach { index -> - this.values[index] = values[index].padNull() - } - } - - private fun Datum.padNull(): Datum { - return when (this.type.kind) { - PType.Kind.STRUCT, PType.Kind.ROW -> { - val newFields = IteratorSupplier { this.fields }.map { - Field.of(it.name, Datum.nullValue()) - } - Datum.structValue(newFields) - } - else -> Datum.nullValue() - } - } -} diff --git a/partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rel/RelJoinOuterFull.kt b/partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rel/RelJoinOuterFull.kt index 546c16171..a5b9c6a23 100644 --- a/partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rel/RelJoinOuterFull.kt +++ b/partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rel/RelJoinOuterFull.kt @@ -1,61 +1,114 @@ package org.partiql.eval.internal.operator.rel +import org.partiql.eval.internal.Environment import org.partiql.eval.internal.Record +import org.partiql.eval.internal.helpers.ValueUtility.isTrue import org.partiql.eval.internal.operator.Operator +import org.partiql.eval.value.Datum +import org.partiql.plan.Rel /** - * Here's a simple implementation of FULL OUTER JOIN. The idea is fairly straightforward: - * Iterate through LHS. For each iteration of the LHS, iterate through RHS. Now, check the condition. - * - If the condition passes, return the merged record (equivalent to result of INNER JOIN) - * - If the condition does not pass, we need a way to return two records (one where the LHS is padded with nulls, and - * one where the RHS is padded with nulls). How we do this: - * - We maintain the [previousLhs] and [previousRhs]. If they are null, we then compute the next LHS and RHS. We - * store their values in-memory. Then we return a merged Record where the LHS is padded and the RHS is not (equivalent - * to result of RIGHT OUTER JOIN). - * - If they aren't null, then we pad the RHS with NULLS (we assume we've already padded the LHS) and return (equivalent - * to result of LEFT OUTER JOIN). We also make sure [previousLhs] and [previousRhs] are now null. + * Full Outer Join returns all joined records from the [lhs] and [rhs] when the [condition] evaluates to true. For all + * records from the [lhs] that do not evaluate to true, these are also returned along with a NULL record from the [rhs]. + * For all records from the [rhs] that do not evaluate to true, these are also returned along with a NULL record from the [lhs]. * - * Performance Analysis: Assume that [lhs] has size M and [rhs] has size N. - * - Time: O(M * N) - * - Space: O(1) + * Full Outer Join cannot be lateral according to PartiQL Specification Section 5.5. */ internal class RelJoinOuterFull( - override val lhs: Operator.Relation, - override val rhs: Operator.Relation, - override val condition: Operator.Expr, -) : RelJoinNestedLoop() { - - private var previousLhs: Record? = null - private var previousRhs: Record? = null - - override fun next(): Record { - if (previousLhs != null && previousRhs != null) { - previousRhs!!.padNull() - val newRecord = previousLhs!! + previousRhs!! - previousLhs = null - previousRhs = null - return newRecord + private val lhs: Operator.Relation, + private val rhs: Operator.Relation, + private val condition: Operator.Expr, + lhsType: Rel.Type, + rhsType: Rel.Type +) : RelPeeking() { + + private val lhsPadded = Record( + Array(rhsType.schema.size) { Datum.nullValue(lhsType.schema[it].type) } + ) + + private val rhsPadded = Record( + Array(rhsType.schema.size) { Datum.nullValue(rhsType.schema[it].type) } + ) + + private lateinit var env: Environment + private lateinit var iterator: Iterator + + override fun openPeeking(env: Environment) { + this.env = env + lhs.open(env) + iterator = implementation() + } + + override fun peek(): Record? { + return when (iterator.hasNext()) { + true -> iterator.next() + false -> null } - return super.next() + } + + override fun closePeeking() { + lhs.close() + rhs.close() + iterator = emptyList().iterator() } /** - * Specifically, for FULL OUTER JOIN, when the JOIN Condition ([condition]) is TRUE, we need to return the - * rows merged (without modification). When the JOIN Condition ([condition]) is FALSE, we need to return - * the LHS padded (and merged with RHS not padded) and the RHS padded (merged with the LHS not padded). + * FULL OUTER JOIN (CANNOT BE LATERAL) + * + * Merge Join (special implementation for FULL OUTER). This is used because we don't have a sophisticated enough + * planner to perform the transformation specified by SQL Server: see section + * ["What about full outer joins?"](https://learn.microsoft.com/en-us/archive/blogs/craigfr/nested-loops-join). + * Furthermore, SQL Server allows for merge joins even without an equijoin predicate. See section + * ["Outer and semi-joins"](https://learn.microsoft.com/en-us/archive/blogs/craigfr/merge-join). + * + * + * Algorithm: + * ``` + * for lhsRecord, lhsIndex in lhs_sorted: + * for rhsRecord, rhsIndex in rhs_sorted: + * if (condition matches): + * lhsMatches[lhsIndex] = true + * rhsMatches[rhsIndex] = true + * yield(lhsRecord + rhsRecord) + * for lhsRecord, lhsIndex in lhs_sorted: + * if lhsMatches[lhsIndex] = false: + * yield(lhsRecord, null) + * for rhsRecord, rhsIndex in rhs_sorted: + * if rhsMatches[rhsIndex] = false: + * yield(null, rhsRecord) + * ``` + * + * TODO: Merge joins require that the LHS and RHS are sorted. */ - override fun join(condition: Boolean, lhs: Record, rhs: Record): Record { - when (condition) { - true -> { - previousLhs = null - previousRhs = null + private fun implementation() = iterator { + val lhsMatches = mutableSetOf() + val rhsMatches = mutableSetOf() + for ((lhsIndex, lhsRecord) in lhs.withIndex()) { + rhs.open(env) + for ((rhsIndex, rhsRecord) in rhs.withIndex()) { + val input = lhsRecord + rhsRecord + val result = condition.eval(env.push(input)) + if (result.isTrue()) { + lhsMatches.add(lhsIndex) + rhsMatches.add(rhsIndex) + yield(lhsRecord + rhsRecord) + } } - false -> { - previousLhs = lhs.copy() - previousRhs = rhs.copy() - lhs.padNull() + rhs.close() + } + lhs.close() + lhs.open(env) + for ((lhsIndex, lhsRecord) in lhs.withIndex()) { + if (!lhsMatches.contains(lhsIndex)) { + yield(lhsRecord + rhsPadded) + } + } + lhs.close() + rhs.open(env) + for ((rhsIndex, rhsRecord) in rhs.withIndex()) { + if (!rhsMatches.contains(rhsIndex)) { + yield(lhsPadded + rhsRecord) } } - return lhs + rhs } } diff --git a/partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rel/RelJoinOuterLeft.kt b/partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rel/RelJoinOuterLeft.kt new file mode 100644 index 000000000..6599b1d2d --- /dev/null +++ b/partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rel/RelJoinOuterLeft.kt @@ -0,0 +1,86 @@ +package org.partiql.eval.internal.operator.rel + +import org.partiql.eval.internal.Environment +import org.partiql.eval.internal.Record +import org.partiql.eval.internal.helpers.ValueUtility.isTrue +import org.partiql.eval.internal.operator.Operator +import org.partiql.eval.value.Datum +import org.partiql.plan.Rel +import org.partiql.value.PartiQLValueExperimental + +/** + * Left Outer Join returns all joined records from the [lhs] and [rhs] when the [condition] evaluates to true. For all + * records from the [lhs] that do not evaluate to true, these are also returned along with a NULL record from the [rhs]. + * + * Note: This is currently the lateral version of the left outer join. In the future, the two implementations + * (lateral vs non-lateral) may be separated for performance improvements. + */ +internal class RelJoinOuterLeft( + private val lhs: Operator.Relation, + private val rhs: Operator.Relation, + private val condition: Operator.Expr, + rhsType: Rel.Type +) : RelPeeking() { + + private val rhsPadded = Record( + Array(rhsType.schema.size) { Datum.nullValue(rhsType.schema[it].type) } + ) + + private lateinit var env: Environment + private lateinit var iterator: Iterator + + override fun openPeeking(env: Environment) { + this.env = env + lhs.open(env) + iterator = implementation() + } + + override fun peek(): Record? { + return when (iterator.hasNext()) { + true -> iterator.next() + false -> null + } + } + + override fun closePeeking() { + lhs.close() + rhs.close() + iterator = emptyList().iterator() + } + + /** + * LEFT OUTER JOIN (LATERAL) + * + * Algorithm: + * ``` + * for lhsRecord in lhs: + * for rhsRecord in rhs(lhsRecord): + * if (condition matches): + * conditionMatched = true + * yield(lhsRecord + rhsRecord) + * if (!conditionMatched): + * yield(lhsRecord + NULL_RECORD) + * ``` + * + * Development Note: The non-lateral version wouldn't need to push to the current environment. + */ + @OptIn(PartiQLValueExperimental::class) + private fun implementation() = iterator { + for (lhsRecord in lhs) { + var lhsMatched = false + rhs.open(env.push(lhsRecord)) + for (rhsRecord in rhs) { + val input = lhsRecord + rhsRecord + val result = condition.eval(env.push(input)) + if (result.isTrue()) { + lhsMatched = true + yield(lhsRecord + rhsRecord) + } + } + rhs.close() + if (!lhsMatched) { + yield(lhsRecord + rhsPadded) + } + } + } +} diff --git a/partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rel/RelJoinOuterRight.kt b/partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rel/RelJoinOuterRight.kt new file mode 100644 index 000000000..71c667f3d --- /dev/null +++ b/partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rel/RelJoinOuterRight.kt @@ -0,0 +1,81 @@ +package org.partiql.eval.internal.operator.rel + +import org.partiql.eval.internal.Environment +import org.partiql.eval.internal.Record +import org.partiql.eval.internal.helpers.ValueUtility.isTrue +import org.partiql.eval.internal.operator.Operator +import org.partiql.eval.value.Datum +import org.partiql.plan.Rel + +/** + * Right Outer Join returns all joined records from the [lhs] and [rhs] when the [condition] evaluates to true. For all + * records from the [rhs] that do not evaluate to true, these are also returned along with a NULL record from the [lhs]. + * + * Right Outer Join cannot be lateral according to PartiQL Specification Section 5.5. + */ +internal class RelJoinOuterRight( + private val lhs: Operator.Relation, + private val rhs: Operator.Relation, + private val condition: Operator.Expr, + lhsType: Rel.Type +) : RelPeeking() { + + private val lhsPadded = Record( + Array(lhsType.schema.size) { Datum.nullValue(lhsType.schema[it].type) } + ) + + private lateinit var env: Environment + private lateinit var iterator: Iterator + + override fun openPeeking(env: Environment) { + this.env = env + rhs.open(env) + iterator = implementation() + } + + override fun peek(): Record? { + return when (iterator.hasNext()) { + true -> iterator.next() + false -> null + } + } + + override fun closePeeking() { + lhs.close() + rhs.close() + iterator = emptyList().iterator() + } + + /** + * RIGHT OUTER JOIN (CANNOT BE LATERAL) + * + * Algorithm: + * ``` + * for rhsRecord in rhs: + * for lhsRecord in lhs(rhsRecord): + * if (condition matches): + * conditionMatched = true + * yield(lhsRecord + rhsRecord) + * if (!conditionMatched): + * yield(NULL_RECORD + rhsRecord) + * ``` + */ + private fun implementation() = iterator { + for (rhsRecord in rhs) { + var rhsMatched = false + lhs.open(env) + for (lhsRecord in lhs) { + val input = lhsRecord + rhsRecord + val result = condition.eval(env.push(input)) + if (result.isTrue()) { + rhsMatched = true + yield(lhsRecord + rhsRecord) + } + } + lhs.close() + if (!rhsMatched) { + yield(lhsPadded + rhsRecord) + } + } + } +} diff --git a/partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rel/RelJoinRight.kt b/partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rel/RelJoinRight.kt deleted file mode 100644 index 4c020f93f..000000000 --- a/partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rel/RelJoinRight.kt +++ /dev/null @@ -1,21 +0,0 @@ -package org.partiql.eval.internal.operator.rel - -import org.partiql.eval.internal.Record -import org.partiql.eval.internal.operator.Operator - -internal class RelJoinRight( - lhs: Operator.Relation, - rhs: Operator.Relation, - override val condition: Operator.Expr, -) : RelJoinNestedLoop() { - - override val lhs: Operator.Relation = rhs - override val rhs: Operator.Relation = lhs - - override fun join(condition: Boolean, lhs: Record, rhs: Record): Record { - if (condition.not()) { - lhs.padNull() - } - return lhs + rhs - } -} diff --git a/partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rex/ExprCallStatic.kt b/partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rex/ExprCallStatic.kt index cfa934764..6d7e66d54 100644 --- a/partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rex/ExprCallStatic.kt +++ b/partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rex/ExprCallStatic.kt @@ -1,5 +1,6 @@ package org.partiql.eval.internal.operator.rex +import org.partiql.errors.TypeCheckException import org.partiql.eval.internal.Environment import org.partiql.eval.internal.operator.Operator import org.partiql.eval.value.Datum @@ -23,6 +24,7 @@ internal class ExprCallStatic( val args = inputs.map { input -> val r = input.eval(env) if (r.isNull && fn.signature.isNullCall) return nil.invoke() + if (r.isMissing && fn.signature.isMissingCall) throw TypeCheckException() r.toPartiQLValue() }.toTypedArray() return Datum.of(fn.invoke(args)) diff --git a/partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rex/ExprCast.kt b/partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rex/ExprCast.kt index eb6751729..36362d093 100644 --- a/partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rex/ExprCast.kt +++ b/partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rex/ExprCast.kt @@ -23,6 +23,7 @@ import org.partiql.value.Int64Value import org.partiql.value.Int8Value import org.partiql.value.IntValue import org.partiql.value.ListValue +import org.partiql.value.MissingValue import org.partiql.value.NullValue import org.partiql.value.NumericValue import org.partiql.value.PartiQLValue @@ -46,6 +47,7 @@ import org.partiql.value.int64Value import org.partiql.value.int8Value import org.partiql.value.intValue import org.partiql.value.listValue +import org.partiql.value.missingValue import org.partiql.value.sexpValue import org.partiql.value.stringValue import org.partiql.value.structValue @@ -59,7 +61,8 @@ import java.math.BigInteger internal class ExprCast(val arg: Operator.Expr, val cast: Ref.Cast) : Operator.Expr { @OptIn(PartiQLValueExperimental::class) override fun eval(env: Environment): Datum { - val arg = arg.eval(env).toPartiQLValue() + val argDatum = arg.eval(env) + val arg = argDatum.toPartiQLValue() try { val partiqlValue = when (PType.fromPartiQLValueType(arg.type).kind) { PType.Kind.DYNAMIC -> TODO("Not Possible") @@ -86,9 +89,9 @@ internal class ExprCast(val arg: Operator.Expr, val cast: Ref.Cast) : Operator.E PType.Kind.BAG -> castFromCollection(arg as BagValue<*>, cast.target) PType.Kind.LIST -> castFromCollection(arg as ListValue<*>, cast.target) PType.Kind.SEXP -> castFromCollection(arg as SexpValue<*>, cast.target) - PType.Kind.STRUCT -> TODO("CAST FROM STRUCT not yet implemented") + PType.Kind.STRUCT -> castFromStruct(argDatum, cast.target).toPartiQLValue() PType.Kind.ROW -> TODO("CAST FROM ROW not yet implemented") - PType.Kind.UNKNOWN -> TODO("CAST FROM UNKNOWN not yet implemented") + PType.Kind.UNKNOWN -> castFromUnknown(arg, cast.target) PType.Kind.VARCHAR -> TODO("CAST FROM VARCHAR not yet implemented") } return Datum.of(partiqlValue) @@ -97,6 +100,22 @@ internal class ExprCast(val arg: Operator.Expr, val cast: Ref.Cast) : Operator.E } } + /** + * For now, we cannot cast from struct to anything else. Throw a type check exception. + */ + private fun castFromStruct(value: Datum, t: PType): Datum { + throw TypeCheckException() + } + + @OptIn(PartiQLValueExperimental::class) + private fun castFromUnknown(value: PartiQLValue, t: PType): PartiQLValue { + return when (value) { + is NullValue -> castFromNull(value, t) + is MissingValue -> missingValue() // TODO: Is this allowed? + else -> error("This shouldn't have happened") + } + } + @OptIn(PartiQLValueExperimental::class) private fun castFromNull(value: NullValue, t: PType): PartiQLValue { return when (t.kind) { @@ -322,12 +341,17 @@ internal class ExprCast(val arg: Operator.Expr, val cast: Ref.Cast) : Operator.E } } - // TODO: Fix NULL Collection @OptIn(PartiQLValueExperimental::class) private fun castFromCollection(value: CollectionValue<*>, t: PType): PartiQLValue { - val elements = mutableListOf() - value.iterator().forEachRemaining { - elements.add(it) + val elements = when (value.isNull) { + true -> null + false -> { + val elements = mutableListOf() + value.iterator().forEachRemaining { + elements.add(it) + } + elements + } } return when (t.kind) { PType.Kind.BAG -> bagValue(elements) diff --git a/partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rex/ExprPathKey.kt b/partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rex/ExprPathKey.kt index 4a4aef9e6..39cc19413 100644 --- a/partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rex/ExprPathKey.kt +++ b/partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rex/ExprPathKey.kt @@ -21,11 +21,6 @@ internal class ExprPathKey( return Datum.nullValue() } val keyString = keyEvaluated.string - for (entry in rootEvaluated.fields) { - if (entry.name == keyString) { - return entry.value - } - } - throw TypeCheckException() + return rootEvaluated.get(keyString) ?: throw TypeCheckException() } } diff --git a/partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rex/ExprPathSymbol.kt b/partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rex/ExprPathSymbol.kt index 1cdb7aca2..949828aaa 100644 --- a/partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rex/ExprPathSymbol.kt +++ b/partiql-eval/src/main/kotlin/org/partiql/eval/internal/operator/rex/ExprPathSymbol.kt @@ -19,11 +19,6 @@ internal class ExprPathSymbol( if (struct.isNull) { return Datum.nullValue() } - for (entry in struct.fields) { - if (entry.name.equals(symbol, ignoreCase = true)) { - return entry.value - } - } - throw TypeCheckException("Couldn't find symbol '$symbol' in $struct.") + return struct.getInsensitive(symbol) ?: throw TypeCheckException("Couldn't find symbol '$symbol' in $struct.") } } diff --git a/partiql-eval/src/test/kotlin/org/partiql/eval/internal/PartiQLEngineDefaultTest.kt b/partiql-eval/src/test/kotlin/org/partiql/eval/internal/PartiQLEngineDefaultTest.kt index b5049d0ae..13a9e3415 100644 --- a/partiql-eval/src/test/kotlin/org/partiql/eval/internal/PartiQLEngineDefaultTest.kt +++ b/partiql-eval/src/test/kotlin/org/partiql/eval/internal/PartiQLEngineDefaultTest.kt @@ -9,7 +9,6 @@ import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.MethodSource import org.partiql.eval.PartiQLEngine import org.partiql.eval.PartiQLResult -import org.partiql.eval.internal.PartiQLEngineDefaultTest.SuccessTestCase.Global import org.partiql.parser.PartiQLParser import org.partiql.plan.PartiQLPlan import org.partiql.plan.debug.PlanPrinter @@ -65,6 +64,11 @@ class PartiQLEngineDefaultTest { @Execution(ExecutionMode.CONCURRENT) fun aggregationTests(tc: SuccessTestCase) = tc.assert() + @ParameterizedTest + @MethodSource("joinTestCases") + @Execution(ExecutionMode.CONCURRENT) + fun joinTests(tc: SuccessTestCase) = tc.assert() + @ParameterizedTest @MethodSource("globalsTestCases") @Execution(ExecutionMode.CONCURRENT) @@ -155,6 +159,151 @@ class PartiQLEngineDefaultTest { ), ) + @JvmStatic + fun joinTestCases() = listOf( + // LEFT OUTER JOIN -- Easy + SuccessTestCase( + input = """ + SELECT VALUE [lhs, rhs] + FROM << 0, 1, 2 >> lhs + LEFT OUTER JOIN << 0, 2, 3 >> rhs + ON lhs = rhs + """.trimIndent(), + expected = bagValue( + listValue(int32Value(0), int32Value(0)), + listValue(int32Value(1), int32Value(null)), + listValue(int32Value(2), int32Value(2)), + ) + ), + // LEFT OUTER JOIN -- RHS Empty + SuccessTestCase( + input = """ + SELECT VALUE [lhs, rhs] + FROM + << 0, 1, 2 >> lhs + LEFT OUTER JOIN ( + SELECT VALUE n + FROM << 0, 2, 3 >> AS n + WHERE n > 100 + ) rhs + ON lhs = rhs + """.trimIndent(), + expected = bagValue( + listValue(int32Value(0), int32Value(null)), + listValue(int32Value(1), int32Value(null)), + listValue(int32Value(2), int32Value(null)), + ) + ), + // LEFT OUTER JOIN -- LHS Empty + SuccessTestCase( + input = """ + SELECT VALUE [lhs, rhs] + FROM <<>> lhs + LEFT OUTER JOIN << 0, 2, 3>> rhs + ON lhs = rhs + """.trimIndent(), + expected = bagValue() + ), + // LEFT OUTER JOIN -- No Matches + SuccessTestCase( + input = """ + SELECT VALUE [lhs, rhs] + FROM << 0, 1, 2 >> lhs + LEFT OUTER JOIN << 3, 4, 5 >> rhs + ON lhs = rhs + """.trimIndent(), + expected = bagValue( + listValue(int32Value(0), int32Value(null)), + listValue(int32Value(1), int32Value(null)), + listValue(int32Value(2), int32Value(null)), + ) + ), + // RIGHT OUTER JOIN -- Easy + SuccessTestCase( + input = """ + SELECT VALUE [lhs, rhs] + FROM << 0, 1, 2 >> lhs + RIGHT OUTER JOIN << 0, 2, 3 >> rhs + ON lhs = rhs + """.trimIndent(), + expected = bagValue( + listValue(int32Value(0), int32Value(0)), + listValue(int32Value(2), int32Value(2)), + listValue(int32Value(null), int32Value(3)), + ) + ), + // RIGHT OUTER JOIN -- RHS Empty + SuccessTestCase( + input = """ + SELECT VALUE [lhs, rhs] + FROM << 0, 1, 2 >> lhs + RIGHT OUTER JOIN <<>> rhs + ON lhs = rhs + """.trimIndent(), + expected = bagValue() + ), + // RIGHT OUTER JOIN -- LHS Empty + SuccessTestCase( + input = """ + SELECT VALUE [lhs, rhs] + FROM ( + SELECT VALUE n + FROM << 0, 1, 2 >> AS n + WHERE n > 100 + ) lhs RIGHT OUTER JOIN + << 0, 2, 3>> rhs + ON lhs = rhs + """.trimIndent(), + expected = bagValue( + listValue(int32Value(null), int32Value(0)), + listValue(int32Value(null), int32Value(2)), + listValue(int32Value(null), int32Value(3)), + ) + ), + // RIGHT OUTER JOIN -- No Matches + SuccessTestCase( + input = """ + SELECT VALUE [lhs, rhs] + FROM << 0, 1, 2 >> lhs + RIGHT OUTER JOIN << 3, 4, 5 >> rhs + ON lhs = rhs + """.trimIndent(), + expected = bagValue( + listValue(int32Value(null), int32Value(3)), + listValue(int32Value(null), int32Value(4)), + listValue(int32Value(null), int32Value(5)), + ) + ), + // LEFT OUTER JOIN -- LATERAL + SuccessTestCase( + input = """ + SELECT VALUE rhs + FROM << [0, 1, 2], [10, 11, 12], [20, 21, 22] >> AS lhs + LEFT OUTER JOIN lhs AS rhs + ON lhs[2] = rhs + """.trimIndent(), + expected = bagValue( + int32Value(2), + int32Value(12), + int32Value(22), + ) + ), + // INNER JOIN -- LATERAL + SuccessTestCase( + input = """ + SELECT VALUE rhs + FROM << [0, 1, 2], [10, 11, 12], [20, 21, 22] >> AS lhs + INNER JOIN lhs AS rhs + ON lhs[2] = rhs + """.trimIndent(), + expected = bagValue( + int32Value(2), + int32Value(12), + int32Value(22), + ) + ), + ) + @JvmStatic fun subqueryTestCases() = listOf( SuccessTestCase( @@ -517,7 +666,8 @@ class PartiQLEngineDefaultTest { ), SuccessTestCase( input = "SELECT t.a, s.b FROM << { 'a': 1 } >> t LEFT JOIN << { 'b': 2 } >> s ON false;", - expected = bagValue(structValue("a" to int32Value(1), "b" to nullValue())) + expected = bagValue(structValue("a" to int32Value(1), "b" to nullValue())), + mode = PartiQLEngine.Mode.STRICT ), SuccessTestCase( input = "SELECT t.a, s.b FROM << { 'a': 1 } >> t FULL OUTER JOIN << { 'b': 2 } >> s ON false;", @@ -1161,7 +1311,16 @@ class PartiQLEngineDefaultTest { internal fun assert() { val permissiveResult = run(mode = PartiQLEngine.Mode.PERMISSIVE) - assert(expectedPermissive == permissiveResult.first) { + val assertionCondition = try { + expectedPermissive == permissiveResult.first + } catch (t: Throwable) { + val str = buildString { + appendLine("Test Name: $name") + PlanPrinter.append(this, permissiveResult.second) + } + throw RuntimeException(str, t) + } + assert(assertionCondition) { comparisonString(expectedPermissive, permissiveResult.first, permissiveResult.second) } var error: Throwable? = null @@ -1194,7 +1353,13 @@ class PartiQLEngineDefaultTest { val prepared = engine.prepare(plan.plan, PartiQLEngine.Session(mapOf("memory" to connector), mode = mode)) when (val result = engine.execute(prepared)) { is PartiQLResult.Value -> return result.value to plan.plan - is PartiQLResult.Error -> throw result.cause + is PartiQLResult.Error -> { + val str = buildString { + appendLine("Execution resulted in an unexpected error. Plan:") + PlanPrinter.append(this, plan.plan) + } + throw RuntimeException(str, result.cause) + } } } @@ -1218,51 +1383,26 @@ class PartiQLEngineDefaultTest { } @Test + @Disabled fun developmentTest() { val tc = SuccessTestCase( input = """ - SELECT * - EXCLUDE - t.a.b.c[*].field_x - FROM [{ - 'a': { - 'b': { - 'c': [ - { -- c[0]; field_x to be removed - 'field_x': 0, - 'field_y': 0 - }, - { -- c[1]; field_x to be removed - 'field_x': 1, - 'field_y': 1 - }, - { -- c[2]; field_x to be removed - 'field_x': 2, - 'field_y': 2 - } - ] - } - } - }] AS t - """.trimIndent(), - expected = bagValue( - structValue( - "a" to structValue( - "b" to structValue( - "c" to listValue( - structValue( - "field_y" to int32Value(0) - ), - structValue( - "field_y" to int32Value(1) - ), - structValue( - "field_y" to int32Value(2) - ) - ) - ) - ) - ) + SELECT VALUE + CASE x + 1 + WHEN NULL THEN 'shouldnt be null' + WHEN MISSING THEN 'shouldnt be missing' + WHEN i THEN 'ONE' + WHEN f THEN 'TWO' + WHEN d THEN 'THREE' + ELSE '?' + END + FROM << i, f, d, null, missing >> AS x + """, + expected = boolValue(true), + globals = listOf( + SuccessTestCase.Global("i", "1"), + SuccessTestCase.Global("f", "2e0"), + SuccessTestCase.Global("d", "3.") ) ) tc.assert() diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/syntax/impl/PartiQLPigVisitor.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/syntax/impl/PartiQLPigVisitor.kt index b56599ab6..94fc44234 100644 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/syntax/impl/PartiQLPigVisitor.kt +++ b/partiql-lang/src/main/kotlin/org/partiql/lang/syntax/impl/PartiQLPigVisitor.kt @@ -1121,15 +1121,24 @@ internal class PartiQLPigVisitor( override fun visitNot(ctx: PartiQLParser.NotContext) = visitUnaryOperation(ctx.rhs, ctx.op, null) + private fun emptyListIfNull(ctx: ParserRuleContext?) = if (ctx == null) { + emptyList() + } else { + listOf(ctx.start) + } + override fun visitMathOp00(ctx: PartiQLParser.MathOp00Context): PartiqlAst.PartiqlAstNode = - visitBinaryOperation(ctx.lhs, ctx.rhs, listOf(ctx.op), ctx.parent) + visitBinaryOperation(ctx.lhs, ctx.rhs, emptyListIfNull(ctx.op), ctx.parent) - override fun visitMathOp01(ctx: PartiQLParser.MathOp01Context): PartiqlAst.PartiqlAstNode = - visitBinaryOperation(ctx.lhs, ctx.rhs, listOf(ctx.op), ctx.parent) + override fun visitMathOp01(ctx: PartiQLParser.MathOp01Context) = + visitUnaryOperation(ctx.rhs, ctx.op?.start, ctx.parent) override fun visitMathOp02(ctx: PartiQLParser.MathOp02Context): PartiqlAst.PartiqlAstNode = visitBinaryOperation(ctx.lhs, ctx.rhs, listOf(ctx.op), ctx.parent) + override fun visitMathOp03(ctx: PartiQLParser.MathOp03Context): PartiqlAst.PartiqlAstNode = + visitBinaryOperation(ctx.lhs, ctx.rhs, listOf(ctx.op), ctx.parent) + override fun visitValueExpr(ctx: PartiQLParser.ValueExprContext) = visitUnaryOperation(ctx.rhs, ctx.sign, ctx.parent) @@ -1785,25 +1794,30 @@ internal class PartiQLPigVisitor( if (parent != null) return@build visit(parent) as PartiqlAst.Expr val args = listOf(lhs!!, rhs!!).map { visit(it) as PartiqlAst.Expr } val metas = op.getSourceMetaContainer() - when (op.first().type) { - PartiQLParser.AND -> and(args, metas) - PartiQLParser.OR -> or(args, metas) - PartiQLParser.ASTERISK -> times(args, metas) - PartiQLParser.SLASH_FORWARD -> divide(args, metas) - PartiQLParser.PLUS -> plus(args, metas) - PartiQLParser.MINUS -> minus(args, metas) - PartiQLParser.PERCENT -> modulo(args, metas) - PartiQLParser.CONCAT -> concat(args, metas) - PartiQLParser.ANGLE_LEFT -> { - if (op.size > 1) ne(args, metas) - else lt(args, metas) - } - PartiQLParser.LT_EQ -> lte(args, metas) - PartiQLParser.ANGLE_RIGHT -> gt(args, metas) - PartiQLParser.GT_EQ -> gte(args, metas) - PartiQLParser.BANG -> ne(args, metas) - PartiQLParser.EQ -> eq(args, metas) - PartiQLParser.AMPERSAND -> bitwiseAnd(args, metas) + val start = op.first().tokenIndex + val stop = op.last().tokenIndex + val tokensInRange = tokens.get(start, stop) + if (tokensInRange.any { it.channel == PartiQLTokens.HIDDEN }) { + throw ParserException("Invalid whitespace or comment in operator", ErrorCode.PARSE_INVALID_QUERY) + } + val stringOp = op.joinToString("") { it.text.lowercase() } + when (stringOp) { + "and" -> and(args, metas) + "or" -> or(args, metas) + "*" -> times(args, metas) + "/" -> divide(args, metas) + "+" -> plus(args, metas) + "-" -> minus(args, metas) + "%" -> modulo(args, metas) + "||" -> concat(args, metas) + "<" -> lt(args, metas) + "<>" -> ne(args, metas) + "<=" -> lte(args, metas) + ">" -> gt(args, metas) + ">=" -> gte(args, metas) + "!=" -> ne(args, metas) + "=" -> eq(args, metas) + "&" -> bitwiseAnd(args, metas) else -> throw ParserException("Unknown binary operator", ErrorCode.PARSE_INVALID_QUERY) } } @@ -1843,7 +1857,6 @@ internal class PartiQLPigVisitor( else -> neg(arg, metas) } } - PartiQLParser.NOT -> not(arg, metas) else -> throw ParserException("Unknown unary operator", ErrorCode.PARSE_INVALID_QUERY) } diff --git a/partiql-lang/src/test/kotlin/org/partiql/lang/syntax/PartiQLParserTest.kt b/partiql-lang/src/test/kotlin/org/partiql/lang/syntax/PartiQLParserTest.kt index 98fc988f0..59d22541f 100644 --- a/partiql-lang/src/test/kotlin/org/partiql/lang/syntax/PartiQLParserTest.kt +++ b/partiql-lang/src/test/kotlin/org/partiql/lang/syntax/PartiQLParserTest.kt @@ -5053,4 +5053,69 @@ class PartiQLParserTest : PartiQLParserTestBase() { Property.TOKEN_VALUE to ION.newSymbol("<") ) ) + + // regression tests for neq operator + @Test + fun testNeqOp() = assertExpression("1 <> 2") { + ne( + lit(ionInt(1)), + lit(ionInt(2)) + ) + } + + @Test + fun testNeqOpAlt() = assertExpression("1 != 2") { + ne( + lit(ionInt(1)), + lit(ionInt(2)) + ) + } + + @Test + fun testSpacesInNeq() = checkInputThrowingParserException( + "1 < > 2", + ErrorCode.PARSE_UNEXPECTED_TOKEN, // partiql-ast parser ErrorCode + expectErrorContextValues = mapOf( + Property.LINE_NUMBER to 1L, + Property.COLUMN_NUMBER to 3L, + Property.TOKEN_DESCRIPTION to PartiQLParser.ANGLE_LEFT.getAntlrDisplayString(), + Property.TOKEN_VALUE to ION.newSymbol("<") + ) + ) + + @Test + fun testSpacesInNeqAlt() = checkInputThrowingParserException( + "1 ! = 2", + ErrorCode.PARSE_UNEXPECTED_TOKEN, // partiql-ast parser ErrorCode + expectErrorContextValues = mapOf( + Property.LINE_NUMBER to 1L, + Property.COLUMN_NUMBER to 3L, + Property.TOKEN_DESCRIPTION to PartiQLParser.BANG.getAntlrDisplayString(), + Property.TOKEN_VALUE to ION.newSymbol("!") + ) + ) + + @Test + fun testCommentsInNeq() = checkInputThrowingParserException( + "1 2", + ErrorCode.PARSE_UNEXPECTED_TOKEN, // partiql-ast parser ErrorCode + expectErrorContextValues = mapOf( + Property.LINE_NUMBER to 1L, + Property.COLUMN_NUMBER to 3L, + Property.TOKEN_DESCRIPTION to PartiQLParser.ANGLE_LEFT.getAntlrDisplayString(), + Property.TOKEN_VALUE to ION.newSymbol("<") + ) + ) + + @Test + fun testCommentsInNeqAlt() = checkInputThrowingParserException( + "1 !/* some comment*/= 2", + ErrorCode.PARSE_UNEXPECTED_TOKEN, // partiql-ast parser ErrorCode + expectErrorContextValues = mapOf( + Property.LINE_NUMBER to 1L, + Property.COLUMN_NUMBER to 3L, + Property.TOKEN_DESCRIPTION to PartiQLParser.BANG.getAntlrDisplayString(), + Property.TOKEN_VALUE to ION.newSymbol("!") + ) + ) } diff --git a/partiql-parser/src/main/antlr/PartiQLParser.g4 b/partiql-parser/src/main/antlr/PartiQLParser.g4 index 49f076bf2..54be03c23 100644 --- a/partiql-parser/src/main/antlr/PartiQLParser.g4 +++ b/partiql-parser/src/main/antlr/PartiQLParser.g4 @@ -619,20 +619,32 @@ comparisonOp | BANG EQ ; -// TODO : Opreator precedence of BITWISE_AND (&) may change in the future. +otherOp + : OPERATOR + | AMPERSAND + // TODO introduce a separate lexical mode for GPML MATCH expressions (https://github.com/partiql/partiql-lang-kotlin/issues/1512) + // This will eliminiate the need for this `AMPERSAND` parse branch. + ; + +// TODO : Opreator precedence of `otherOp` may change in the future. // SEE: https://github.com/partiql/partiql-docs/issues/50 mathOp00 - : lhs=mathOp00 op=(AMPERSAND|CONCAT) rhs=mathOp01 + : lhs=mathOp00 op=otherOp rhs=mathOp01 | parent=mathOp01 ; mathOp01 - : lhs=mathOp01 op=(PLUS|MINUS) rhs=mathOp02 + : op=otherOp rhs=mathOp02 | parent=mathOp02 ; mathOp02 - : lhs=mathOp02 op=(PERCENT|ASTERISK|SLASH_FORWARD) rhs=valueExpr + : lhs=mathOp02 op=(PLUS|MINUS) rhs=mathOp03 + | parent=mathOp03 + ; + +mathOp03 + : lhs=mathOp03 op=(PERCENT|ASTERISK|SLASH_FORWARD) rhs=valueExpr | parent=valueExpr ; diff --git a/partiql-parser/src/main/antlr/PartiQLTokens.g4 b/partiql-parser/src/main/antlr/PartiQLTokens.g4 index 28c1581da..20e2d9425 100644 --- a/partiql-parser/src/main/antlr/PartiQLTokens.g4 +++ b/partiql-parser/src/main/antlr/PartiQLTokens.g4 @@ -337,7 +337,6 @@ BANG: '!'; LT_EQ: '<='; GT_EQ: '>='; EQ: '='; -CONCAT: '||'; ANGLE_LEFT: '<'; ANGLE_RIGHT: '>'; BRACKET_LEFT: '['; @@ -351,6 +350,36 @@ COLON: ':'; COLON_SEMI: ';'; QUESTION_MARK: '?'; PERIOD: '.'; +HASH: '#'; + +// Operators w/ special characters +// Similar to postgresql's supported operator creation -- https://www.postgresql.org/docs/16/sql-createoperator.html +OPERATOR + // may not end with + or - + : OpBasic+ OpBasicEnd + // must include at least one of OpSpecial to end w/ anything + | (OpBasic | OpSpecial)* OpSpecial (OpBasic | OpSpecial)* + ; + +fragment OpBasic + : [+*=] // TODO support `<` and `>`? + // comments are not matched + | '-' {_input.LA(1) != '-'}? + | '/' {_input.LA(1) != '*'}? + ; + +fragment OpBasicEnd + : [*/=] // TODO support `<` and `>`? + ; +fragment OpSpecial + : [~@#%^?] // TODO support backtick (`)? + // graph patterns are not matched + // TODO make GPML MATCH patterns a separate lexical mode (https://github.com/partiql/partiql-lang-kotlin/issues/1512) + // Creating a separate lexical mode will allow us to get rid of the following semantic predicates. + | '|' {_input.LA(1) != '!'}? + | '!' {_input.LA(1) != '%'}? + | '&' {_input.LA(1) != '%'}? + ; /** * diff --git a/partiql-parser/src/main/kotlin/org/partiql/parser/internal/PartiQLParserDefault.kt b/partiql-parser/src/main/kotlin/org/partiql/parser/internal/PartiQLParserDefault.kt index 9ac22ede6..f830219b5 100644 --- a/partiql-parser/src/main/kotlin/org/partiql/parser/internal/PartiQLParserDefault.kt +++ b/partiql-parser/src/main/kotlin/org/partiql/parser/internal/PartiQLParserDefault.kt @@ -70,9 +70,9 @@ import org.partiql.ast.excludeStepCollIndex import org.partiql.ast.excludeStepCollWildcard import org.partiql.ast.excludeStepStructField import org.partiql.ast.excludeStepStructWildcard +import org.partiql.ast.exprAnd import org.partiql.ast.exprBagOp import org.partiql.ast.exprBetween -import org.partiql.ast.exprBinary import org.partiql.ast.exprCall import org.partiql.ast.exprCanCast import org.partiql.ast.exprCanLosslessCast @@ -90,7 +90,10 @@ import org.partiql.ast.exprIsType import org.partiql.ast.exprLike import org.partiql.ast.exprLit import org.partiql.ast.exprMatch +import org.partiql.ast.exprNot import org.partiql.ast.exprNullIf +import org.partiql.ast.exprOperator +import org.partiql.ast.exprOr import org.partiql.ast.exprOverlay import org.partiql.ast.exprParameter import org.partiql.ast.exprPath @@ -105,7 +108,6 @@ import org.partiql.ast.exprStruct import org.partiql.ast.exprStructField import org.partiql.ast.exprSubstring import org.partiql.ast.exprTrim -import org.partiql.ast.exprUnary import org.partiql.ast.exprVar import org.partiql.ast.exprWindow import org.partiql.ast.exprWindowOver @@ -1572,83 +1574,75 @@ internal class PartiQLParserDefault : PartiQLParser { */ override fun visitOr(ctx: GeneratedParser.OrContext) = translate(ctx) { - convertBinaryExpr(ctx.lhs, ctx.rhs, Expr.Binary.Op.OR) + val l = visit(ctx.lhs) as Expr + val r = visit(ctx.rhs) as Expr + exprOr(l, r) } override fun visitAnd(ctx: GeneratedParser.AndContext) = translate(ctx) { - convertBinaryExpr(ctx.lhs, ctx.rhs, Expr.Binary.Op.AND) + val l = visit(ctx.lhs) as Expr + val r = visit(ctx.rhs) as Expr + exprAnd(l, r) } override fun visitNot(ctx: GeneratedParser.NotContext) = translate(ctx) { val expr = visit(ctx.exprNot()) as Expr - exprUnary(Expr.Unary.Op.NOT, expr) + exprNot(expr) + } + + private fun checkForInvalidTokens(op: ParserRuleContext) { + val start = op.start.tokenIndex + val stop = op.stop.tokenIndex + val tokensInRange = tokens.get(start, stop) + if (tokensInRange.any { it.channel == GeneratedLexer.HIDDEN }) { + throw error(op, "Invalid whitespace or comment in operator") + } + } + + private fun convertToOperator(value: ParserRuleContext, op: ParserRuleContext): Expr { + checkForInvalidTokens(op) + return convertToOperator(value, op.text) + } + + private fun convertToOperator(value: ParserRuleContext, op: String): Expr { + val v = visit(value) as Expr + return exprOperator(op, null, v) + } + + private fun convertToOperator(lhs: ParserRuleContext, rhs: ParserRuleContext, op: ParserRuleContext): Expr { + checkForInvalidTokens(op) + return convertToOperator(lhs, rhs, op.text) + } + + private fun convertToOperator(lhs: ParserRuleContext, rhs: ParserRuleContext, op: String): Expr { + val l = visit(lhs) as Expr + val r = visit(rhs) as Expr + return exprOperator(op, l, r) } override fun visitMathOp00(ctx: GeneratedParser.MathOp00Context) = translate(ctx) { if (ctx.parent != null) return@translate visit(ctx.parent) - convertBinaryExpr(ctx.lhs, ctx.rhs, convertBinaryMathOp(ctx.op)) + convertToOperator(ctx.lhs, ctx.rhs, ctx.op) } override fun visitMathOp01(ctx: GeneratedParser.MathOp01Context) = translate(ctx) { if (ctx.parent != null) return@translate visit(ctx.parent) - convertBinaryExpr(ctx.lhs, ctx.rhs, convertBinaryMathOp(ctx.op)) + convertToOperator(ctx.rhs, ctx.op) } override fun visitMathOp02(ctx: GeneratedParser.MathOp02Context) = translate(ctx) { if (ctx.parent != null) return@translate visit(ctx.parent) - convertBinaryExpr(ctx.lhs, ctx.rhs, convertBinaryMathOp(ctx.op)) + convertToOperator(ctx.lhs, ctx.rhs, ctx.op.text) } - override fun visitValueExpr(ctx: GeneratedParser.ValueExprContext) = translate(ctx) { + override fun visitMathOp03(ctx: GeneratedParser.MathOp03Context) = translate(ctx) { if (ctx.parent != null) return@translate visit(ctx.parent) - val expr = visit(ctx.rhs) as Expr - exprUnary(convertUnaryOp(ctx.sign), expr) + convertToOperator(ctx.lhs, ctx.rhs, ctx.op.text) } - private fun convertBinaryExpr(lhs: ParserRuleContext, rhs: ParserRuleContext, op: Expr.Binary.Op): Expr { - val l = visit(lhs) as Expr - val r = visit(rhs) as Expr - return exprBinary(op, l, r) - } - - private fun convertBinaryOp(ctx: GeneratedParser.ComparisonOpContext) = when (ctx.start.type) { - GeneratedParser.AMPERSAND -> Expr.Binary.Op.BITWISE_AND - GeneratedParser.AND -> Expr.Binary.Op.AND - GeneratedParser.OR -> Expr.Binary.Op.OR - GeneratedParser.ASTERISK -> Expr.Binary.Op.TIMES - GeneratedParser.SLASH_FORWARD -> Expr.Binary.Op.DIVIDE - GeneratedParser.PLUS -> Expr.Binary.Op.PLUS - GeneratedParser.MINUS -> Expr.Binary.Op.MINUS - GeneratedParser.PERCENT -> Expr.Binary.Op.MODULO - GeneratedParser.CONCAT -> Expr.Binary.Op.CONCAT - GeneratedParser.ANGLE_LEFT -> { - if (ctx.stop.type == GeneratedParser.ANGLE_RIGHT) Expr.Binary.Op.NE - else Expr.Binary.Op.LT - } - GeneratedParser.LT_EQ -> Expr.Binary.Op.LTE - GeneratedParser.ANGLE_RIGHT -> Expr.Binary.Op.GT - GeneratedParser.GT_EQ -> Expr.Binary.Op.GTE - GeneratedParser.BANG -> Expr.Binary.Op.NE - GeneratedParser.EQ -> Expr.Binary.Op.EQ - else -> throw error(ctx.start, "Invalid binary operator") - } - - private fun convertBinaryMathOp(token: Token) = when (token.type) { - GeneratedParser.AMPERSAND -> Expr.Binary.Op.BITWISE_AND - GeneratedParser.CONCAT -> Expr.Binary.Op.CONCAT - GeneratedParser.PLUS -> Expr.Binary.Op.PLUS - GeneratedParser.MINUS -> Expr.Binary.Op.MINUS - GeneratedParser.PERCENT -> Expr.Binary.Op.MODULO - GeneratedParser.ASTERISK -> Expr.Binary.Op.TIMES - GeneratedParser.SLASH_FORWARD -> Expr.Binary.Op.DIVIDE - else -> throw error(token, "Invalid binary operator") - } - - private fun convertUnaryOp(token: Token) = when (token.type) { - GeneratedParser.PLUS -> Expr.Unary.Op.POS - GeneratedParser.MINUS -> Expr.Unary.Op.NEG - GeneratedParser.NOT -> Expr.Unary.Op.NOT - else -> throw error(token, "Invalid unary operator") + override fun visitValueExpr(ctx: GeneratedParser.ValueExprContext) = translate(ctx) { + if (ctx.parent != null) return@translate visit(ctx.parent) + convertToOperator(ctx.rhs, ctx.sign.text) } /** @@ -1658,8 +1652,7 @@ internal class PartiQLParserDefault : PartiQLParser { */ override fun visitPredicateComparison(ctx: GeneratedParser.PredicateComparisonContext) = translate(ctx) { - val op = convertBinaryOp(ctx.op) - convertBinaryExpr(ctx.lhs, ctx.rhs, op) + convertToOperator(ctx.lhs, ctx.rhs, ctx.op) } /** @@ -1866,7 +1859,7 @@ internal class PartiQLParserDefault : PartiQLParser { when (val funcName = ctx.qualifiedName()) { is GeneratedParser.QualifiedNameContext -> { when (funcName.name.start.type) { - GeneratedParser.MOD -> exprBinary(Expr.Binary.Op.MODULO, args[0], args[1]) + GeneratedParser.MOD -> exprOperator("%", args[0], args[1]) GeneratedParser.CHARACTER_LENGTH, GeneratedParser.CHAR_LENGTH -> { val path = ctx.qualifiedName().qualifier.map { visitSymbolPrimitive(it) } val name = identifierSymbol("char_length", Identifier.CaseSensitivity.INSENSITIVE) diff --git a/partiql-parser/src/test/kotlin/org/partiql/parser/internal/PartiQLParserDDLTests.kt b/partiql-parser/src/test/kotlin/org/partiql/parser/internal/PartiQLParserDDLTests.kt index bab101b08..ecfb5ba6e 100644 --- a/partiql-parser/src/test/kotlin/org/partiql/parser/internal/PartiQLParserDDLTests.kt +++ b/partiql-parser/src/test/kotlin/org/partiql/parser/internal/PartiQLParserDDLTests.kt @@ -19,8 +19,8 @@ import org.partiql.ast.constraintDefinitionNotNull import org.partiql.ast.constraintDefinitionUnique import org.partiql.ast.ddlOpCreateTable import org.partiql.ast.ddlOpDropTable -import org.partiql.ast.exprBinary import org.partiql.ast.exprLit +import org.partiql.ast.exprOperator import org.partiql.ast.exprVar import org.partiql.ast.identifierQualified import org.partiql.ast.identifierSymbol @@ -220,8 +220,8 @@ class PartiQLParserDDLTests { constraint( null, constraintDefinitionCheck( - exprBinary( - Expr.Binary.Op.GT, + exprOperator( + ">", exprVar(identifierSymbol("a", Identifier.CaseSensitivity.INSENSITIVE), Expr.Var.Scope.DEFAULT), exprLit(int32Value(0)) ) @@ -312,8 +312,8 @@ class PartiQLParserDDLTests { constraint( null, constraintDefinitionCheck( - exprBinary( - Expr.Binary.Op.GT, + exprOperator( + ">", exprVar(identifierSymbol("a", Identifier.CaseSensitivity.INSENSITIVE), Expr.Var.Scope.DEFAULT), exprLit(int32Value(0)) ) diff --git a/partiql-parser/src/test/kotlin/org/partiql/parser/internal/PartiQLParserOperatorTests.kt b/partiql-parser/src/test/kotlin/org/partiql/parser/internal/PartiQLParserOperatorTests.kt new file mode 100644 index 000000000..e05f33551 --- /dev/null +++ b/partiql-parser/src/test/kotlin/org/partiql/parser/internal/PartiQLParserOperatorTests.kt @@ -0,0 +1,73 @@ +package org.partiql.parser.internal + +import org.junit.jupiter.api.Test +import org.partiql.ast.AstNode +import org.partiql.ast.Expr +import org.partiql.ast.exprLit +import org.partiql.ast.exprOperator +import org.partiql.ast.statementQuery +import org.partiql.value.PartiQLValueExperimental +import org.partiql.value.int32Value +import kotlin.test.assertEquals + +@OptIn(PartiQLValueExperimental::class) +class PartiQLParserOperatorTests { + + private val parser = PartiQLParserDefault() + + private inline fun query(body: () -> Expr) = statementQuery(body()) + + @Test + fun builtinUnaryOperator() = assertExpression( + "-2", + query { + exprOperator( + symbol = "-", + lhs = null, + rhs = exprLit(int32Value(2)) + ) + } + ) + + @Test + fun builtinBinaryOperator() = assertExpression( + "1 <= 2", + query { + exprOperator( + symbol = "<=", + lhs = exprLit(int32Value(1)), + rhs = exprLit(int32Value(2)) + ) + } + ) + + @Test + fun customUnaryOperator() = assertExpression( + "==!2", + query { + exprOperator( + symbol = "==!", + lhs = null, + rhs = exprLit(int32Value(2)) + ) + } + ) + + @Test + fun customBinaryOperator() = assertExpression( + "1 ==! 2", + query { + exprOperator( + symbol = "==!", + lhs = exprLit(int32Value(1)), + rhs = exprLit(int32Value(2)) + ) + } + ) + + private fun assertExpression(input: String, expected: AstNode) { + val result = parser.parse(input) + val actual = result.root + assertEquals(expected, actual) + } +} diff --git a/partiql-parser/src/test/kotlin/org/partiql/parser/internal/PartiQLParserSessionAttributeTests.kt b/partiql-parser/src/test/kotlin/org/partiql/parser/internal/PartiQLParserSessionAttributeTests.kt index 7d02f659f..2ec95b2d7 100644 --- a/partiql-parser/src/test/kotlin/org/partiql/parser/internal/PartiQLParserSessionAttributeTests.kt +++ b/partiql-parser/src/test/kotlin/org/partiql/parser/internal/PartiQLParserSessionAttributeTests.kt @@ -3,8 +3,8 @@ package org.partiql.parser.internal import org.junit.jupiter.api.Test import org.partiql.ast.AstNode import org.partiql.ast.Expr -import org.partiql.ast.exprBinary import org.partiql.ast.exprLit +import org.partiql.ast.exprOperator import org.partiql.ast.exprSessionAttribute import org.partiql.ast.statementQuery import org.partiql.value.PartiQLValueExperimental @@ -46,8 +46,8 @@ class PartiQLParserSessionAttributeTests { fun currentUserEquals() = assertExpression( "1 = current_user", query { - exprBinary( - op = Expr.Binary.Op.EQ, + exprOperator( + symbol = "=", lhs = exprLit(int32Value(1)), rhs = exprSessionAttribute(Expr.SessionAttribute.Attribute.CURRENT_USER) ) diff --git a/partiql-plan/src/main/resources/partiql_plan.ion b/partiql-plan/src/main/resources/partiql_plan.ion index 675d32de7..801234b5b 100644 --- a/partiql-plan/src/main/resources/partiql_plan.ion +++ b/partiql-plan/src/main/resources/partiql_plan.ion @@ -300,6 +300,8 @@ rel::{ projections: list::[rex], }, + // TODO: Specify that this is a LATERAL JOIN. Create a separate JOIN. Also, determine the allowable types of JOIN. + // For context: Oracle SQL doesn't allow ... FULL OUTER JOIN LATERAL ... or ... RIGHT OUTER JOIN LATERAL ... join::{ lhs: rel, rhs: rel, diff --git a/partiql-planner/api/partiql-planner.api b/partiql-planner/api/partiql-planner.api index 1ec88bdb5..1264ed3a9 100644 --- a/partiql-planner/api/partiql-planner.api +++ b/partiql-planner/api/partiql-planner.api @@ -47,6 +47,290 @@ public abstract interface class org/partiql/planner/PartiQLPlannerPass { public abstract fun apply (Lorg/partiql/plan/PartiQLPlan;Lkotlin/jvm/functions/Function1;)Lorg/partiql/plan/PartiQLPlan; } +public abstract interface class org/partiql/planner/catalog/Catalog { + public abstract fun getFunctions (Lorg/partiql/planner/catalog/Session;Lorg/partiql/planner/catalog/Name;)Ljava/util/Collection; + public abstract fun getName ()Ljava/lang/String; + public abstract fun getTable (Lorg/partiql/planner/catalog/Session;Lorg/partiql/planner/catalog/Name;)Lorg/partiql/planner/catalog/Table; + public abstract fun getTableHandle (Lorg/partiql/planner/catalog/Session;Lorg/partiql/planner/catalog/Identifier;)Lorg/partiql/planner/catalog/Table$Handle; + public abstract fun listNamespaces (Lorg/partiql/planner/catalog/Session;)Ljava/util/Collection; + public abstract fun listNamespaces (Lorg/partiql/planner/catalog/Session;Lorg/partiql/planner/catalog/Namespace;)Ljava/util/Collection; + public abstract fun listTables (Lorg/partiql/planner/catalog/Session;)Ljava/util/Collection; + public abstract fun listTables (Lorg/partiql/planner/catalog/Session;Lorg/partiql/planner/catalog/Namespace;)Ljava/util/Collection; +} + +public final class org/partiql/planner/catalog/Catalog$DefaultImpls { + public static fun getFunctions (Lorg/partiql/planner/catalog/Catalog;Lorg/partiql/planner/catalog/Session;Lorg/partiql/planner/catalog/Name;)Ljava/util/Collection; + public static fun getTable (Lorg/partiql/planner/catalog/Catalog;Lorg/partiql/planner/catalog/Session;Lorg/partiql/planner/catalog/Name;)Lorg/partiql/planner/catalog/Table; + public static fun getTableHandle (Lorg/partiql/planner/catalog/Catalog;Lorg/partiql/planner/catalog/Session;Lorg/partiql/planner/catalog/Identifier;)Lorg/partiql/planner/catalog/Table$Handle; + public static fun listNamespaces (Lorg/partiql/planner/catalog/Catalog;Lorg/partiql/planner/catalog/Session;)Ljava/util/Collection; + public static fun listNamespaces (Lorg/partiql/planner/catalog/Catalog;Lorg/partiql/planner/catalog/Session;Lorg/partiql/planner/catalog/Namespace;)Ljava/util/Collection; + public static fun listTables (Lorg/partiql/planner/catalog/Catalog;Lorg/partiql/planner/catalog/Session;)Ljava/util/Collection; + public static fun listTables (Lorg/partiql/planner/catalog/Catalog;Lorg/partiql/planner/catalog/Session;Lorg/partiql/planner/catalog/Namespace;)Ljava/util/Collection; +} + +public abstract interface class org/partiql/planner/catalog/Catalogs { + public static final field Companion Lorg/partiql/planner/catalog/Catalogs$Companion; + public static fun builder ()Lorg/partiql/planner/catalog/Catalogs$Builder; + public abstract fun default ()Lorg/partiql/planner/catalog/Catalog; + public abstract fun get (Ljava/lang/String;Z)Lorg/partiql/planner/catalog/Catalog; + public abstract fun list ()Ljava/util/Collection; + public static fun of (Ljava/util/Collection;)Lorg/partiql/planner/catalog/Catalogs; + public static fun of ([Lorg/partiql/planner/catalog/Catalog;)Lorg/partiql/planner/catalog/Catalogs; +} + +public final class org/partiql/planner/catalog/Catalogs$Builder { + public fun ()V + public final fun add (Lorg/partiql/planner/catalog/Catalog;)Lorg/partiql/planner/catalog/Catalogs$Builder; + public final fun build ()Lorg/partiql/planner/catalog/Catalogs; + public final fun default (Lorg/partiql/planner/catalog/Catalog;)Lorg/partiql/planner/catalog/Catalogs$Builder; +} + +public final class org/partiql/planner/catalog/Catalogs$Companion { + public final fun builder ()Lorg/partiql/planner/catalog/Catalogs$Builder; + public final fun of (Ljava/util/Collection;)Lorg/partiql/planner/catalog/Catalogs; + public final fun of ([Lorg/partiql/planner/catalog/Catalog;)Lorg/partiql/planner/catalog/Catalogs; +} + +public final class org/partiql/planner/catalog/Catalogs$DefaultImpls { + public static fun get (Lorg/partiql/planner/catalog/Catalogs;Ljava/lang/String;Z)Lorg/partiql/planner/catalog/Catalog; + public static synthetic fun get$default (Lorg/partiql/planner/catalog/Catalogs;Ljava/lang/String;ZILjava/lang/Object;)Lorg/partiql/planner/catalog/Catalog; + public static fun list (Lorg/partiql/planner/catalog/Catalogs;)Ljava/util/Collection; +} + +public abstract interface class org/partiql/planner/catalog/Function { + public static final field Companion Lorg/partiql/planner/catalog/Function$Companion; + public static fun aggregation (Ljava/lang/String;Ljava/util/Collection;Lorg/partiql/types/PType$Kind;)Lorg/partiql/planner/catalog/Function$Aggregation; + public abstract fun computeReturnType (Ljava/util/List;)Lorg/partiql/types/PType; + public abstract fun getName ()Ljava/lang/String; + public abstract fun getParameters ()[Lorg/partiql/planner/catalog/Function$Parameter; + public abstract fun getReturnType ()Lorg/partiql/types/PType$Kind; + public abstract fun getSpecific ()Ljava/lang/String; + public static fun scalar (Ljava/lang/String;Ljava/util/Collection;Lorg/partiql/types/PType$Kind;)Lorg/partiql/planner/catalog/Function$Scalar; +} + +public abstract interface class org/partiql/planner/catalog/Function$Aggregation : org/partiql/planner/catalog/Function { + public abstract fun getSpecific ()Ljava/lang/String; + public abstract fun isDecomposable ()Z +} + +public final class org/partiql/planner/catalog/Function$Aggregation$DefaultImpls { + public static fun computeReturnType (Lorg/partiql/planner/catalog/Function$Aggregation;Ljava/util/List;)Lorg/partiql/types/PType; + public static fun getParameters (Lorg/partiql/planner/catalog/Function$Aggregation;)[Lorg/partiql/planner/catalog/Function$Parameter; + public static fun getSpecific (Lorg/partiql/planner/catalog/Function$Aggregation;)Ljava/lang/String; + public static fun isDecomposable (Lorg/partiql/planner/catalog/Function$Aggregation;)Z +} + +public final class org/partiql/planner/catalog/Function$Companion { + public final fun aggregation (Ljava/lang/String;Ljava/util/Collection;Lorg/partiql/types/PType$Kind;)Lorg/partiql/planner/catalog/Function$Aggregation; + public final fun scalar (Ljava/lang/String;Ljava/util/Collection;Lorg/partiql/types/PType$Kind;)Lorg/partiql/planner/catalog/Function$Scalar; +} + +public final class org/partiql/planner/catalog/Function$DefaultImpls { + public static fun computeReturnType (Lorg/partiql/planner/catalog/Function;Ljava/util/List;)Lorg/partiql/types/PType; + public static fun getParameters (Lorg/partiql/planner/catalog/Function;)[Lorg/partiql/planner/catalog/Function$Parameter; +} + +public final class org/partiql/planner/catalog/Function$Parameter { + public final field name Ljava/lang/String; + public final field type Lorg/partiql/types/PType$Kind; + public fun (Ljava/lang/String;Lorg/partiql/types/PType$Kind;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Lorg/partiql/types/PType$Kind; + public final fun copy (Ljava/lang/String;Lorg/partiql/types/PType$Kind;)Lorg/partiql/planner/catalog/Function$Parameter; + public static synthetic fun copy$default (Lorg/partiql/planner/catalog/Function$Parameter;Ljava/lang/String;Lorg/partiql/types/PType$Kind;ILjava/lang/Object;)Lorg/partiql/planner/catalog/Function$Parameter; + public fun equals (Ljava/lang/Object;)Z + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public abstract interface class org/partiql/planner/catalog/Function$Scalar : org/partiql/planner/catalog/Function { + public abstract fun getSpecific ()Ljava/lang/String; + public abstract fun isNullCall ()Z +} + +public final class org/partiql/planner/catalog/Function$Scalar$DefaultImpls { + public static fun computeReturnType (Lorg/partiql/planner/catalog/Function$Scalar;Ljava/util/List;)Lorg/partiql/types/PType; + public static fun getParameters (Lorg/partiql/planner/catalog/Function$Scalar;)[Lorg/partiql/planner/catalog/Function$Parameter; + public static fun getSpecific (Lorg/partiql/planner/catalog/Function$Scalar;)Ljava/lang/String; + public static fun isNullCall (Lorg/partiql/planner/catalog/Function$Scalar;)Z +} + +public final class org/partiql/planner/catalog/Identifier : java/lang/Iterable, kotlin/jvm/internal/markers/KMappedMarker { + public static final field Companion Lorg/partiql/planner/catalog/Identifier$Companion; + public synthetic fun ([Lorg/partiql/planner/catalog/Identifier$Part;Lorg/partiql/planner/catalog/Identifier$Part;Lkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun append (Lorg/partiql/planner/catalog/Identifier;)Lorg/partiql/planner/catalog/Identifier; + public final fun append ([Lorg/partiql/planner/catalog/Identifier$Part;)Lorg/partiql/planner/catalog/Identifier; + public static final fun delimited (Ljava/lang/String;)Lorg/partiql/planner/catalog/Identifier; + public static final fun delimited (Ljava/util/Collection;)Lorg/partiql/planner/catalog/Identifier; + public static final fun delimited ([Ljava/lang/String;)Lorg/partiql/planner/catalog/Identifier; + public fun equals (Ljava/lang/Object;)Z + public fun forEach (Ljava/util/function/Consumer;)V + public final fun getIdentifier ()Lorg/partiql/planner/catalog/Identifier$Part; + public final fun getParts ()Ljava/util/List; + public final fun getQualifier ()[Lorg/partiql/planner/catalog/Identifier$Part; + public final fun hasQualifier ()Z + public fun hashCode ()I + public fun iterator ()Ljava/util/Iterator; + public final fun matches (Ljava/lang/String;Z)Z + public static synthetic fun matches$default (Lorg/partiql/planner/catalog/Identifier;Ljava/lang/String;ZILjava/lang/Object;)Z + public static final fun of (Ljava/util/Collection;)Lorg/partiql/planner/catalog/Identifier; + public static final fun of ([Lorg/partiql/planner/catalog/Identifier$Part;)Lorg/partiql/planner/catalog/Identifier; + public static final fun regular (Ljava/lang/String;)Lorg/partiql/planner/catalog/Identifier; + public fun spliterator ()Ljava/util/Spliterator; + public fun toString ()Ljava/lang/String; +} + +public final class org/partiql/planner/catalog/Identifier$Companion { + public final fun delimited (Ljava/lang/String;)Lorg/partiql/planner/catalog/Identifier; + public final fun delimited (Ljava/util/Collection;)Lorg/partiql/planner/catalog/Identifier; + public final fun delimited ([Ljava/lang/String;)Lorg/partiql/planner/catalog/Identifier; + public final fun of (Ljava/util/Collection;)Lorg/partiql/planner/catalog/Identifier; + public final fun of ([Lorg/partiql/planner/catalog/Identifier$Part;)Lorg/partiql/planner/catalog/Identifier; + public final fun regular (Ljava/lang/String;)Lorg/partiql/planner/catalog/Identifier; +} + +public final class org/partiql/planner/catalog/Identifier$Part { + public static final field Companion Lorg/partiql/planner/catalog/Identifier$Part$Companion; + public synthetic fun (Ljava/lang/String;ZLkotlin/jvm/internal/DefaultConstructorMarker;)V + public static final fun delimited (Ljava/lang/String;)Lorg/partiql/planner/catalog/Identifier$Part; + public fun equals (Ljava/lang/Object;)Z + public final fun getText ()Ljava/lang/String; + public fun hashCode ()I + public final fun isRegular ()Z + public final fun matches (Ljava/lang/String;)Z + public static final fun regular (Ljava/lang/String;)Lorg/partiql/planner/catalog/Identifier$Part; + public fun toString ()Ljava/lang/String; +} + +public final class org/partiql/planner/catalog/Identifier$Part$Companion { + public final fun delimited (Ljava/lang/String;)Lorg/partiql/planner/catalog/Identifier$Part; + public final fun regular (Ljava/lang/String;)Lorg/partiql/planner/catalog/Identifier$Part; +} + +public final class org/partiql/planner/catalog/Name : java/lang/Iterable, kotlin/jvm/internal/markers/KMappedMarker { + public static final field Companion Lorg/partiql/planner/catalog/Name$Companion; + public fun (Lorg/partiql/planner/catalog/Namespace;Ljava/lang/String;)V + public fun equals (Ljava/lang/Object;)Z + public fun forEach (Ljava/util/function/Consumer;)V + public final fun getName ()Ljava/lang/String; + public final fun getNamespace ()Lorg/partiql/planner/catalog/Namespace; + public final fun hasNamespace ()Z + public fun hashCode ()I + public fun iterator ()Ljava/util/Iterator; + public static final fun of (Ljava/util/Collection;)Lorg/partiql/planner/catalog/Name; + public static final fun of ([Ljava/lang/String;)Lorg/partiql/planner/catalog/Name; + public fun spliterator ()Ljava/util/Spliterator; + public fun toString ()Ljava/lang/String; +} + +public final class org/partiql/planner/catalog/Name$Companion { + public final fun of (Ljava/util/Collection;)Lorg/partiql/planner/catalog/Name; + public final fun of ([Ljava/lang/String;)Lorg/partiql/planner/catalog/Name; +} + +public final class org/partiql/planner/catalog/Namespace : java/lang/Iterable, kotlin/jvm/internal/markers/KMappedMarker { + public static final field Companion Lorg/partiql/planner/catalog/Namespace$Companion; + public synthetic fun ([Ljava/lang/String;Lkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun append ([Ljava/lang/String;)Lorg/partiql/planner/catalog/Namespace; + public final fun asIdentifier ()Lorg/partiql/planner/catalog/Identifier; + public fun equals (Ljava/lang/Object;)Z + public fun forEach (Ljava/util/function/Consumer;)V + public final fun get (I)Ljava/lang/String; + public final fun getLength ()I + public final fun getLevels ()[Ljava/lang/String; + public fun hashCode ()I + public final fun isEmpty ()Z + public fun iterator ()Ljava/util/Iterator; + public static final fun of (Ljava/util/Collection;)Lorg/partiql/planner/catalog/Namespace; + public static final fun of ([Ljava/lang/String;)Lorg/partiql/planner/catalog/Namespace; + public fun spliterator ()Ljava/util/Spliterator; + public fun toString ()Ljava/lang/String; +} + +public final class org/partiql/planner/catalog/Namespace$Companion { + public final fun empty ()Lorg/partiql/planner/catalog/Namespace; + public final fun of (Ljava/util/Collection;)Lorg/partiql/planner/catalog/Namespace; + public final fun of ([Ljava/lang/String;)Lorg/partiql/planner/catalog/Namespace; +} + +public final class org/partiql/planner/catalog/Path : java/lang/Iterable, kotlin/jvm/internal/markers/KMappedMarker { + public static final field Companion Lorg/partiql/planner/catalog/Path$Companion; + public synthetic fun (Ljava/util/List;Lkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun forEach (Ljava/util/function/Consumer;)V + public final fun get (I)Lorg/partiql/planner/catalog/Namespace; + public final fun getLength ()I + public final fun isEmpty ()Z + public fun iterator ()Ljava/util/Iterator; + public static final fun of ([Lorg/partiql/planner/catalog/Namespace;)Lorg/partiql/planner/catalog/Path; + public fun spliterator ()Ljava/util/Spliterator; + public fun toString ()Ljava/lang/String; +} + +public final class org/partiql/planner/catalog/Path$Companion { + public final fun of ([Lorg/partiql/planner/catalog/Namespace;)Lorg/partiql/planner/catalog/Path; +} + +public abstract interface class org/partiql/planner/catalog/Session { + public static final field Companion Lorg/partiql/planner/catalog/Session$Companion; + public static fun builder ()Lorg/partiql/planner/catalog/Session$Builder; + public static fun empty (Ljava/lang/String;)Lorg/partiql/planner/catalog/Session; + public abstract fun getCatalog ()Ljava/lang/String; + public abstract fun getIdentity ()Ljava/lang/String; + public abstract fun getNamespace ()Lorg/partiql/planner/catalog/Namespace; + public abstract fun getPath ()Lorg/partiql/planner/catalog/Path; + public abstract fun getProperties ()Ljava/util/Map; +} + +public final class org/partiql/planner/catalog/Session$Builder { + public fun ()V + public final fun build ()Lorg/partiql/planner/catalog/Session; + public final fun catalog (Ljava/lang/String;)Lorg/partiql/planner/catalog/Session$Builder; + public final fun identity (Ljava/lang/String;)Lorg/partiql/planner/catalog/Session$Builder; + public final fun namespace (Lorg/partiql/planner/catalog/Namespace;)Lorg/partiql/planner/catalog/Session$Builder; + public final fun property (Ljava/lang/String;Ljava/lang/String;)Lorg/partiql/planner/catalog/Session$Builder; +} + +public final class org/partiql/planner/catalog/Session$Companion { + public final fun builder ()Lorg/partiql/planner/catalog/Session$Builder; + public final fun empty (Ljava/lang/String;)Lorg/partiql/planner/catalog/Session; +} + +public final class org/partiql/planner/catalog/Session$DefaultImpls { + public static fun getPath (Lorg/partiql/planner/catalog/Session;)Lorg/partiql/planner/catalog/Path; + public static fun getProperties (Lorg/partiql/planner/catalog/Session;)Ljava/util/Map; +} + +public abstract interface class org/partiql/planner/catalog/Table { + public static final field Companion Lorg/partiql/planner/catalog/Table$Companion; + public static fun builder ()Lorg/partiql/planner/catalog/Table$Builder; + public abstract fun getName ()Ljava/lang/String; + public abstract fun getSchema ()Lorg/partiql/types/PType; + public static fun of (Ljava/lang/String;Lorg/partiql/types/PType;)Lorg/partiql/planner/catalog/Table; +} + +public final class org/partiql/planner/catalog/Table$Builder { + public fun ()V + public final fun build ()Lorg/partiql/planner/catalog/Table; + public final fun name (Ljava/lang/String;)Lorg/partiql/planner/catalog/Table$Builder; + public final fun schema (Lorg/partiql/types/PType;)Lorg/partiql/planner/catalog/Table$Builder; +} + +public final class org/partiql/planner/catalog/Table$Companion { + public final fun builder ()Lorg/partiql/planner/catalog/Table$Builder; + public final fun of (Ljava/lang/String;Lorg/partiql/types/PType;)Lorg/partiql/planner/catalog/Table; + public static synthetic fun of$default (Lorg/partiql/planner/catalog/Table$Companion;Ljava/lang/String;Lorg/partiql/types/PType;ILjava/lang/Object;)Lorg/partiql/planner/catalog/Table; +} + +public final class org/partiql/planner/catalog/Table$DefaultImpls { + public static fun getSchema (Lorg/partiql/planner/catalog/Table;)Lorg/partiql/types/PType; +} + +public final class org/partiql/planner/catalog/Table$Handle { + public final field name Lorg/partiql/planner/catalog/Name; + public final field table Lorg/partiql/planner/catalog/Table; + public fun (Lorg/partiql/planner/catalog/Name;Lorg/partiql/planner/catalog/Table;)V +} + public final class org/partiql/planner/internal/utils/ToBinderKt { public static final fun toBinder (Lorg/partiql/ast/Expr;Lkotlin/jvm/functions/Function0;)Lorg/partiql/ast/Binder; } diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/catalog/Catalog.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/catalog/Catalog.kt new file mode 100644 index 000000000..a094835b9 --- /dev/null +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/catalog/Catalog.kt @@ -0,0 +1,72 @@ +package org.partiql.planner.catalog + +/** + * Catalog interface for access to tables and routines. + * + * Related + * - Iceberg — https://github.com/apache/iceberg/blob/main/api/src/main/java/org/apache/iceberg/catalog/Catalog.java + * - Calcite — https://github.com/apache/calcite/blob/main/core/src/main/java/org/apache/calcite/schema/Schema.java + */ +public interface Catalog { + + /** + * Returns the catalog name. + */ + public fun getName(): String + + /** + * Get a table by name. + */ + public fun getTable(session: Session, name: Name): Table? = null + + /** + * Given an [Identifier], returns a [Table.Handle] that corresponds to the longest-available requested path. + * + * For example, given a table named "Table" located within Catalog "AWS" and Namespace "a".b"."c", a user could + * call [getTableHandle] with the identifier "a"."b"."c"."Table". The returned [Table.Handle] will contain the table + * representation and the matching path: "a"."b"."c"."Table" + * + * As another example, consider a table within a [Namespace] that may be a struct with nested attributes. + * A user could call [getTableHandle] with the identifier "a"."b"."c"."Table"."x". In the Namespace, only table + * "Table" exists. Therefore, this method will return a [Table.Handle] with the "Table" representation and the + * matching path: "a"."b"."c"."Table". + * + * IMPORTANT: The returned [Table.Handle.name] must be correct for correct evaluation. + * + * If the [Identifier] does not correspond to an existing [Table], implementers should return null. + */ + public fun getTableHandle(session: Session, identifier: Identifier): Table.Handle? = null + + /** + * List top-level tables. + */ + public fun listTables(session: Session): Collection = listTables(session, Namespace.empty()) + + /** + * List all tables under this namespace. + */ + public fun listTables(session: Session, namespace: Namespace): Collection = emptyList() + + /** + * List top-level namespaces from the catalog. + */ + public fun listNamespaces(session: Session): Collection = listNamespaces(session, Namespace.empty()) + + /** + * List all child namespaces from the namespace. + * + * @param namespace + */ + public fun listNamespaces(session: Session, namespace: Namespace): Collection = emptyList() + + /** + * Get a routine's variants by name; the default implementation is backed by the SQL-99 builtins. + */ + public fun getFunctions(session: Session, name: Name): Collection { + // if (name.hasNamespace()) { + // error("The default catalog implementation does not support namespaced functions, found: $name") + // } + // return SqlFunctions.getFunctions(name.getName()) + error("Catalog functions are not implemented.") + } +} diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/catalog/Catalogs.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/catalog/Catalogs.kt new file mode 100644 index 000000000..cf5346519 --- /dev/null +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/catalog/Catalogs.kt @@ -0,0 +1,108 @@ +package org.partiql.planner.catalog + +/** + * Catalogs is used to provide the default catalog and possibly others by name. + */ +public interface Catalogs { + + /** + * Returns the default catalog. Required. + */ + public fun default(): Catalog + + /** + * Returns a catalog by name (single identifier). + */ + public fun get(name: String, ignoreCase: Boolean = false): Catalog? { + val default = default() + return if (name.equals(default.getName(), ignoreCase)) { + default + } else { + null + } + } + + /** + * Returns a list of all available catalogs. + */ + public fun list(): Collection = listOf(default()) + + /** + * Factory methods and builder. + */ + public companion object { + + @JvmStatic + public fun of(vararg catalogs: Catalog): Catalogs = of(catalogs.toList()) + + @JvmStatic + public fun of(catalogs: Collection): Catalogs { + if (catalogs.isEmpty()) { + error("Cannot create `Catalogs` with empty catalogs list.") + } + return builder().apply { catalogs.forEach { add(it) } }.build() + } + + @JvmStatic + public fun builder(): Builder = Builder() + } + + /** + * Java-style builder for a default [Catalogs] implementation. + */ + public class Builder { + + private var default: Catalog? = null + private val catalogs = mutableMapOf() + + /** + * Sets the default catalog. + */ + public fun default(default: Catalog): Builder = this.apply { + this.default = default + catalogs[default.getName()] = default + } + + /** + * Adds this catalog, overwriting any existing one with the same name. + */ + public fun add(catalog: Catalog): Builder = this.apply { + if (default == null) { + this.default = catalog + } + catalogs[catalog.getName()] = catalog + } + + public fun build(): Catalogs { + + val default = default ?: error("Default catalog is required") + + return object : Catalogs { + + override fun default(): Catalog = default + + override fun get(name: String, ignoreCase: Boolean): Catalog? { + if (ignoreCase) { + // search + var match: Catalog? = null + for (catalog in list()) { + if (catalog.getName().equals(name, ignoreCase = true)) { + if (match != null) { + // TODO exceptions for ambiguous catalog name lookup + error("Catalog name is ambiguous, found more than one match.") + } else { + match = catalog + } + } + } + return match + } + // lookup + return catalogs[name] + } + + override fun list(): Collection = catalogs.values + } + } + } +} diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/catalog/Function.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/catalog/Function.kt new file mode 100644 index 000000000..87116312c --- /dev/null +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/catalog/Function.kt @@ -0,0 +1,115 @@ +package org.partiql.planner.catalog + +import org.partiql.planner.internal.SqlTypes +import org.partiql.types.PType + +/** + * A [Function] is a PartiQL-routine callable from an expression context. + */ +public sealed interface Function { + + /** + * The routine name. Required. + */ + public fun getName(): String + + /** + * The formal argument definitions. Optional. + */ + public fun getParameters(): Array = DEFAULT_PARAMETERS + + /** + * The function return type. Required. + */ + public fun getReturnType(): PType.Kind + + /** + * Compute a [PType] from the given arguments. + */ + public fun computeReturnType(args: List): PType = SqlTypes.from(getReturnType()) + + /** + * !! DO NOT OVERRIDE !! + */ + public fun getSpecific(): String + + /** + * Represents an SQL row-value expression call. + */ + public interface Scalar : Function { + + /** + * SQL NULL CALL -> RETURNS NULL ON NULL INPUT + */ + public fun isNullCall(): Boolean = true + + /** + * !! DO NOT OVERRIDE !! + */ + public override fun getSpecific(): String { + val name = getName().uppercase() + val parameters = getParameters().joinToString("__") { it.type.name } + val returnType = getReturnType().name + return "FN_${name}___${parameters}___$returnType" + } + } + + /** + * Represents an SQL table-value expression call. + */ + public interface Aggregation : Function { + + public fun isDecomposable(): Boolean = true + + /** + * !! DO NOT OVERRIDE !! + */ + public override fun getSpecific(): String { + val name = getName() + val parameters = getParameters().joinToString("__") { it.type.name } + val returnType = getReturnType().name + return "AGG_${name}___${parameters}___$returnType" + } + } + + /** + * [Parameter] is a formal argument's definition. + * + * @property name + * @property type + */ + public data class Parameter( + @JvmField public val name: String, + @JvmField public val type: PType.Kind, + ) + + /** + * Memoized defaults. + */ + public companion object { + + private val DEFAULT_PARAMETERS = emptyArray() + + @JvmStatic + public fun scalar( + name: String, + parameters: Collection, + returnType: PType.Kind, + ): Scalar = object : Scalar { + override fun getName(): String = name + override fun getParameters(): Array = parameters.toTypedArray() + override fun getReturnType(): PType.Kind = returnType + } + + @JvmStatic + public fun aggregation( + name: String, + parameters: Collection, + returnType: PType.Kind, + ): Aggregation = object : Aggregation { + override fun getName(): String = name + override fun getParameters(): Array = parameters.toTypedArray() + override fun getReturnType(): PType.Kind = returnType + } + } +} diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/catalog/Identifier.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/catalog/Identifier.kt new file mode 100644 index 000000000..2077ffa02 --- /dev/null +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/catalog/Identifier.kt @@ -0,0 +1,223 @@ +package org.partiql.planner.catalog + +import java.util.Spliterator +import java.util.function.Consumer + +/** + * Represents an SQL identifier (possibly qualified). + * + * @property qualifier If + * @property identifier + */ +public class Identifier private constructor( + private val qualifier: Array, + private val identifier: Part, +) : Iterable { + + /** + * Returns the unqualified name part. + */ + public fun getIdentifier(): Part = identifier + + /** + * Returns the name's namespace. + */ + public fun getQualifier(): Array = qualifier + + /** + * Returns true if the namespace is non-empty. + */ + public fun hasQualifier(): Boolean = qualifier.isNotEmpty() + + /** + * Returns a collection of the identifier parts. + */ + public fun getParts(): List { + return listOf(*qualifier) + identifier + } + + /** + * Iterable forEach(action). + */ + override fun forEach(action: Consumer?) { + getParts().forEach(action) + } + + /** + * Iterable iterator(). + */ + override fun iterator(): Iterator { + return getParts().iterator() + } + + /** + * Iterable spliterator(). + */ + override fun spliterator(): Spliterator { + return getParts().spliterator() + } + + /** + * Returns a new identifier with the given parts appended. + */ + public fun append(other: Identifier): Identifier { + return of(this.toList() + other.toList()) + } + + /** + * Returns a new identifier with the given parts appended. + */ + public fun append(vararg parts: Part): Identifier { + return of(this.toList() + parts) + } + + /** + * Compares this identifier to string, possibly ignoring case. + */ + public fun matches(other: String, ignoreCase: Boolean = false): Boolean { + if (this.hasQualifier()) { + return false + } + return if (ignoreCase) { + this.identifier.matches(other) + } else { + other == this.identifier.getText() + } + } + + /** + * Compares the case-preserved text of two identifiers — that is case-sensitive equality. + */ + public override fun equals(other: Any?): Boolean { + if (this === other) { + return true + } + if (other == null || javaClass != other.javaClass) { + return false + } + other as Identifier + return (this.identifier == other.identifier && this.qualifier.contentEquals(other.qualifier)) + } + + /** + * The hashCode() is case-sensitive — java.util.Arrays.hashCode + */ + public override fun hashCode(): Int { + var result = 1 + result = 31 * result + qualifier.hashCode() + result = 31 * result + identifier.hashCode() + return result + } + + /** + * Return the SQL representation of this identifier. + */ + public override fun toString(): String = buildString { + if (qualifier.isNotEmpty()) { + append(qualifier.joinToString(".")) + append(".") + } + append(identifier) + } + + /** + * Represents an SQL identifier part which is either regular (unquoted) or delimited (double-quoted). + * + * @property text The case-preserved identifier text. + * @property regular True if the identifier should be treated as an SQL regular identifier. + */ + public class Part private constructor( + private val text: String, + private val regular: Boolean, + ) { + + /** + * Returns the identifier text. + */ + public fun getText(): String = text + + /** + * Returns true iff this is a regular identifier. + */ + public fun isRegular(): Boolean = regular + + /** + * Compares this identifier part to a string. + */ + public fun matches(other: String): Boolean { + return this.text.equals(other, ignoreCase = this.regular) + } + + /** + * Compares the case-preserved text of two identifiers — that is case-sensitive equality. + */ + override fun equals(other: Any?): Boolean { + if (this === other) { + return true + } + if (other == null || javaClass != other.javaClass) { + return false + } + return this.text == (other as Part).text + } + + /** + * Returns the hashcode of the identifier's case-preserved text. + */ + override fun hashCode(): Int { + return this.text.hashCode() + } + + /** + * Return the identifier as a SQL string. + */ + override fun toString(): String = when (regular) { + true -> text + false -> "\"${text}\"" + } + + public companion object { + + @JvmStatic + public fun regular(text: String): Part = Part(text, true) + + @JvmStatic + public fun delimited(text: String): Part = Part(text, false) + } + } + + public companion object { + + @JvmStatic + public fun regular(text: String): Identifier = Identifier(emptyArray(), Part.regular(text)) + + @JvmStatic + public fun delimited(text: String): Identifier = Identifier(emptyArray(), Part.delimited(text)) + + @JvmStatic + public fun of(vararg parts: Part): Identifier = of(parts.toList()) + + @JvmStatic + public fun of(parts: Collection): Identifier { + if (parts.isEmpty()) { + error("Cannot create an identifier with no parts") + } + val qualifier = parts.take(parts.size - 1).toTypedArray() + val identifier = parts.last() + return Identifier(qualifier, identifier) + } + + @JvmStatic + public fun delimited(vararg parts: String): Identifier = delimited(parts.toList()) + + @JvmStatic + public fun delimited(parts: Collection): Identifier { + if (parts.isEmpty()) { + error("Cannot create an identifier with no parts") + } + val qualifier = parts.take(parts.size - 1).map { Part.delimited(it) }.toTypedArray() + val identifier = Part.delimited(parts.last()) + return Identifier(qualifier, identifier) + } + } +} diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/catalog/Name.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/catalog/Name.kt new file mode 100644 index 000000000..d19d4469c --- /dev/null +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/catalog/Name.kt @@ -0,0 +1,101 @@ +package org.partiql.planner.catalog + +import java.util.Spliterator +import java.util.function.Consumer + +/** + * A reference to a named object in a catalog. + */ +public class Name( + private val namespace: Namespace, + private val name: String, +) : Iterable { + + /** + * Returns the unqualified name part. + */ + public fun getName(): String = name + + /** + * Returns the name's namespace. + */ + public fun getNamespace(): Namespace = namespace + + /** + * Returns true if the namespace is non-empty. + */ + public fun hasNamespace(): Boolean = !namespace.isEmpty() + + /** + * Returns an iterator of strings for this name. + */ + private fun getParts(): List { + val parts = mutableListOf() + parts.addAll(namespace) + parts.add(name) + return parts + } + + override fun forEach(action: Consumer?) { + getParts().forEach(action) + } + + override fun iterator(): Iterator { + return getParts().iterator() + } + + override fun spliterator(): Spliterator { + return getParts().spliterator() + } + + /** + * Compares two names including their namespaces and symbols. + */ + override fun equals(other: Any?): Boolean { + if (this === other) { + return true + } + if (other == null || javaClass != other.javaClass) { + return false + } + other as Name + return (this.name == other.name) && (this.namespace == other.namespace) + } + + /** + * The hashCode() is case-sensitive. + */ + override fun hashCode(): Int { + var result = 1 + result = 31 * result + namespace.hashCode() + result = 31 * result + name.hashCode() + return result + } + + /** + * Return the SQL name representation of this name — all parts delimited. + */ + override fun toString(): String { + return Identifier.delimited(getParts()).toString() + } + + public companion object { + + /** + * Construct a name from a string. + */ + @JvmStatic + public fun of(vararg names: String): Name = of(names.toList()) + + /** + * Construct a name from a collection of strings. + */ + @JvmStatic + public fun of(names: Collection): Name { + assert(names.size > 0) { "Cannot create an empty name" } + val namespace = Namespace.of(names.take(names.size - 1)) + val name = names.last() + return Name(namespace, name) + } + } +} diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/catalog/Namespace.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/catalog/Namespace.kt new file mode 100644 index 000000000..746fb6807 --- /dev/null +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/catalog/Namespace.kt @@ -0,0 +1,102 @@ +package org.partiql.planner.catalog + +import java.util.Spliterator +import java.util.function.Consumer + +/** + * A reference to a namespace within a catalog; case-preserved. + * + * Related + * - Iceberg — https://github.com/apache/iceberg/blob/main/api/src/main/java/org/apache/iceberg/catalog/Namespace.java + * - Calcite — https://github.com/apache/calcite/blob/main/core/src/main/java/org/apache/calcite/schema/Schema.java + */ +public class Namespace private constructor( + private val levels: Array, +) : Iterable { + + public fun getLevels(): Array { + return levels + } + + public fun getLength(): Int { + return levels.size + } + + public fun isEmpty(): Boolean { + return levels.isEmpty() + } + + public fun asIdentifier(): Identifier { + return Identifier.delimited(*levels) + } + + public operator fun get(index: Int): String { + return levels[index] + } + + override fun forEach(action: Consumer?) { + levels.toList().forEach(action) + } + + override fun iterator(): Iterator { + return levels.iterator() + } + + override fun spliterator(): Spliterator { + return levels.toList().spliterator() + } + + public override fun equals(other: Any?): Boolean { + if (this === other) { + return true + } + if (other == null || javaClass != other.javaClass) { + return false + } + return levels.contentEquals((other as Namespace).levels) + } + + public fun append(vararg levels: String): Namespace { + return Namespace(this.levels + levels) + } + + /** + * The hashCode() is case-sensitive — java.util.Arrays.hashCode + */ + public override fun hashCode(): Int { + return levels.contentHashCode() + } + + /** + * Return the SQL identifier representation of this namespace. + */ + public override fun toString(): String { + if (isEmpty()) { + return "" + } + return Identifier.delimited(*levels).toString() + } + + public companion object { + + private val EMPTY = Namespace(emptyArray()) + + public fun empty(): Namespace = EMPTY + + @JvmStatic + public fun of(vararg levels: String): Namespace { + if (levels.isEmpty()) { + return empty() + } + return Namespace(arrayOf(*levels)) + } + + @JvmStatic + public fun of(levels: Collection): Namespace { + if (levels.isEmpty()) { + return empty() + } + return Namespace(levels.toTypedArray()) + } + } +} diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/catalog/Path.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/catalog/Path.kt new file mode 100644 index 000000000..960054e67 --- /dev/null +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/catalog/Path.kt @@ -0,0 +1,44 @@ +package org.partiql.planner.catalog + +import java.util.Spliterator +import java.util.function.Consumer + +/** + * The routine resolution path, accessible via PATH. + */ +public class Path private constructor( + private val namespaces: List, +) : Iterable { + + public companion object { + + @JvmStatic + public fun of(vararg namespaces: Namespace): Path = Path(namespaces.toList()) + } + + public fun getLength(): Int { + return namespaces.size + } + + public fun isEmpty(): Boolean { + return namespaces.isEmpty() + } + + public operator fun get(index: Int): Namespace { + return namespaces[index] + } + + override fun forEach(action: Consumer?) { + namespaces.forEach(action) + } + + override fun iterator(): Iterator { + return namespaces.iterator() + } + + override fun spliterator(): Spliterator { + return namespaces.spliterator() + } + + override fun toString(): String = "PATH: (${namespaces.joinToString()})" +} diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/catalog/Session.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/catalog/Session.kt new file mode 100644 index 000000000..0fd8a7d4a --- /dev/null +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/catalog/Session.kt @@ -0,0 +1,95 @@ +package org.partiql.planner.catalog + +/** + * Session is used for authorization and name resolution. + */ +public interface Session { + + /** + * Returns the caller identity as a string; accessible via CURRENT_USER. + */ + public fun getIdentity(): String + + /** + * Returns the current [Catalog]; accessible via the CURRENT_CATALOG session variable. + */ + public fun getCatalog(): String + + /** + * Returns the current [Namespace]; accessible via the CURRENT_NAMESPACE session variable. + */ + public fun getNamespace(): Namespace + + /** + * Returns the current [Path]; accessible via the PATH and CURRENT_PATH session variables. + * + * Default implementation returns the current namespace. + */ + public fun getPath(): Path = Path.of(getNamespace()) + + /** + * Arbitrary session properties that may be used in planning or custom plan passes. + */ + public fun getProperties(): Map = emptyMap() + + /** + * Factory methods and builder. + */ + public companion object { + + /** + * Returns an empty [Session] with the provided [catalog]. + */ + @JvmStatic + public fun empty(catalog: String): Session = object : Session { + override fun getIdentity(): String = "unknown" + override fun getCatalog(): String = catalog + override fun getNamespace(): Namespace = Namespace.empty() + } + + @JvmStatic + public fun builder(): Builder = Builder() + } + + /** + * Java-style builder for a default [Session] implementation. + */ + public class Builder { + + private var identity: String = "unknown" + private var catalog: String? = null + private var namespace: Namespace = Namespace.empty() + private var properties: MutableMap = mutableMapOf() + + public fun identity(identity: String): Builder { + this.identity = identity + return this + } + + public fun catalog(catalog: String?): Builder { + this.catalog = catalog + return this + } + + public fun namespace(namespace: Namespace): Builder { + this.namespace = namespace + return this + } + + public fun property(name: String, value: String): Builder { + this.properties[name] = value + return this + } + + public fun build(): Session = object : Session { + + init { + require(catalog != null) { "Session catalog must be set" } + } + + override fun getIdentity(): String = identity + override fun getCatalog(): String = catalog!! + override fun getNamespace(): Namespace = namespace + } + } +} diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/catalog/Table.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/catalog/Table.kt new file mode 100644 index 000000000..6e63eb01d --- /dev/null +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/catalog/Table.kt @@ -0,0 +1,79 @@ +package org.partiql.planner.catalog + +import org.partiql.types.PType + +/** + * In PartiQL, a [Table] can take on any type and is not necessarily rows+columns. + */ +public interface Table { + + /** + * Handle holds both a table and its resolved name within its respective catalog. + * + * Note: This replaces ConnectorObjectHandle from versions < 1.0 + */ + public class Handle( + @JvmField public val name: Name, + @JvmField public val table: Table, + ) + + /** + * The table's name. + */ + public fun getName(): String + + /** + * The table's schema. + */ + public fun getSchema(): PType = PType.typeDynamic() + + /** + * Factory methods and builder. + */ + public companion object { + + /** + * Create a simple table with a name and schema. + */ + @JvmStatic + public fun of(name: String, schema: PType = PType.typeDynamic()): Table = object : Table { + override fun getName(): String = name + override fun getSchema(): PType = schema + } + + /** + * Returns the Java-style builder. + */ + @JvmStatic + public fun builder(): Builder = Builder() + } + + /** + * Java-style builder for a default Table implementation. + */ + public class Builder { + + private var name: String? = null + private var schema: PType = PType.typeDynamic() + + public fun name(name: String): Builder { + this.name = name + return this + } + + public fun schema(schema: PType): Builder { + this.schema = schema + return this + } + + public fun build(): Table { + // Validate builder parameters + val name = this.name ?: throw IllegalStateException("Table name cannot be null") + // Default implementation + return object : Table { + override fun getName(): String = name + override fun getSchema(): PType = schema + } + } + } +} diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/Env.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/Env.kt index aa7d22491..69ed9fb2f 100644 --- a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/Env.kt +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/Env.kt @@ -16,7 +16,7 @@ import org.partiql.planner.internal.ir.rexOpCallDynamicCandidate import org.partiql.planner.internal.ir.rexOpCastResolved import org.partiql.planner.internal.ir.rexOpVarGlobal import org.partiql.planner.internal.typer.CompilerType -import org.partiql.planner.internal.typer.TypeEnv.Companion.toPath +import org.partiql.planner.internal.typer.Scope.Companion.toPath import org.partiql.spi.BindingCase import org.partiql.spi.BindingName import org.partiql.spi.BindingPath diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/FnResolver.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/FnResolver.kt index ceea6f1c5..33501e4d4 100644 --- a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/FnResolver.kt +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/FnResolver.kt @@ -3,6 +3,7 @@ package org.partiql.planner.internal import org.partiql.planner.internal.casts.Coercions import org.partiql.planner.internal.ir.Ref import org.partiql.planner.internal.typer.CompilerType +import org.partiql.planner.internal.typer.PlanTyper.Companion.toCType import org.partiql.spi.fn.FnExperimental import org.partiql.spi.fn.FnSignature import org.partiql.types.PType.Kind @@ -144,10 +145,14 @@ internal object FnResolver { exactInputTypes++ continue } - // 2. Match ANY, no coercion needed - // TODO: Rewrite args in this scenario - arg.kind == Kind.UNKNOWN || p.type.kind == Kind.DYNAMIC || arg.kind == Kind.DYNAMIC -> continue - // 3. Check for a coercion + // 2. Match ANY parameter, no coercion needed + p.type.kind == Kind.DYNAMIC -> continue + arg.kind == Kind.UNKNOWN -> continue + // 3. Allow for ANY arguments + arg.kind == Kind.DYNAMIC -> { + mapping[i] = Ref.Cast(arg, p.type.toCType(), Ref.Cast.Safety.UNSAFE, true) + } + // 4. Check for a coercion else -> when (val coercion = Coercions.get(arg, p.type)) { null -> return null // short-circuit else -> mapping[i] = coercion diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/SqlTypes.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/SqlTypes.kt new file mode 100644 index 000000000..0d3938ab7 --- /dev/null +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/SqlTypes.kt @@ -0,0 +1,252 @@ +package org.partiql.planner.internal + +import org.partiql.ast.Type +import org.partiql.types.Field +import org.partiql.types.PType + +/** + * A factory for single-source of truth for type creations — DO NOT CREATE PTYPE DIRECTLY. + * + * This allows us to raise an interface if we need custom type factories; for now just use defaults with static methods. + */ +internal object SqlTypes { + + private const val MAX_SIZE = Int.MAX_VALUE + + // + // DYNAMIC + // + + @JvmStatic + fun dynamic(): PType = PType.typeDynamic() + + // + // BOOLEAN + // + + @JvmStatic + fun bool(): PType = PType.typeBool() + + // + // NUMERIC + // + + @JvmStatic + fun tinyint(): PType = PType.typeTinyInt() + + @JvmStatic + fun smallint(): PType = PType.typeSmallInt() + + @JvmStatic + fun int(): PType = PType.typeInt() + + @JvmStatic + fun bigint(): PType = PType.typeBigInt() + + /** + * NUMERIC represents an integer with arbitrary precision. It is equivalent to Ion’s integer type, and is conformant to SQL-99s rules for the NUMERIC type. In SQL-99, if a scale is omitted then we choose zero — and if a precision is omitted then the precision is implementation defined. For PartiQL, we define this precision to be inf — aka arbitrary precision. + * + * @param precision Defaults to inf. + * @param scale Defaults to 0. + * @return + */ + @JvmStatic + fun numeric(precision: Int? = null, scale: Int? = null): PType { + if (scale != null && precision == null) { + error("Precision can never be null while scale is specified.") + } + return when { + precision != null && scale != null -> PType.typeDecimal(precision, scale) + precision != null -> PType.typeDecimal(precision, 0) + else -> PType.typeIntArbitrary() + } + } + + /** + * DECIMAL represents an exact numeric type with arbitrary precision and arbitrary scale. It is equivalent to Ion’s decimal type. For a DECIMAL with no given scale we choose inf rather than the SQL prescribed 0 (zero). Here we diverge from SQL-99 for Ion compatibility. Finally, SQL defines decimals as having precision equal to or greater than the given precision. Like other systems, we truncate extraneous precision so that NUMERIC(p,s) is equivalent to DECIMAL(p,s). The only difference between them is the default scale when it’s not specified — we follow SQL-99 for NUMERIC, and we follow Postgres for DECIMAL. + * + * @param precision Defaults to inf. + * @param scale Defaults to 0 when precision is given, otherwise inf. + * @return + */ + @JvmStatic + fun decimal(precision: Int? = null, scale: Int? = null): PType { + if (scale != null && precision == null) { + error("Precision can never be null while scale is specified.") + } + return when { + precision != null && scale != null -> PType.typeDecimal(precision, scale) + precision != null -> PType.typeDecimal(precision, 0) + else -> PType.typeDecimalArbitrary() + } + } + + @JvmStatic + fun real(): PType = PType.typeReal() + + @JvmStatic + fun double(): PType = PType.typeDoublePrecision() + + // + // CHARACTER STRINGS + // + + @JvmStatic + fun char(length: Int? = null): PType = PType.typeChar(length ?: 1) + + @JvmStatic + fun varchar(length: Int? = null): PType = PType.typeVarChar(length ?: MAX_SIZE) + + @JvmStatic + fun string(): PType = PType.typeString() + + @JvmStatic + fun clob(length: Int? = null) = PType.typeClob(length ?: MAX_SIZE) + + // + // BIT STRINGS + // + + @JvmStatic + fun blob(length: Int? = null) = PType.typeBlob(length ?: MAX_SIZE) + + // + // DATETIME + // + + @JvmStatic + fun date(): PType = TODO() + + @JvmStatic + fun time(precision: Int? = null): PType = PType.typeTimeWithoutTZ(precision ?: 6) + + @JvmStatic + fun timez(precision: Int? = null): PType = PType.typeTimeWithTZ(precision ?: 6) + + @JvmStatic + fun timestamp(precision: Int? = null): PType = PType.typeTimeWithoutTZ(precision ?: 6) + + @JvmStatic + fun timestampz(precision: Int? = null): PType = PType.typeTimestampWithTZ(precision ?: 6) + + // + // COLLECTIONS + // + + @JvmStatic + fun array(element: PType? = null, size: Int? = null): PType { + if (size != null) { + error("Fixed-length ARRAY [N] is not supported.") + } + return when (element) { + null -> PType.typeList() + else -> PType.typeList(element) + } + } + + @JvmStatic + fun bag(element: PType? = null, size: Int? = null): PType { + if (size != null) { + error("Fixed-length BAG [N] is not supported.") + } + return when (element) { + null -> PType.typeBag() + else -> PType.typeBag(element) + } + } + + // + // STRUCTURAL + // + + @JvmStatic + fun struct(): PType = PType.typeStruct() + + @JvmStatic + fun row(fields: List): PType = PType.typeRow(fields) + + /** + * Create PType from the AST type. + */ + @JvmStatic + fun from(type: Type): PType = when (type) { + is Type.NullType -> error("Casting to NULL is not supported.") + is Type.Missing -> error("Casting to MISSING is not supported.") + is Type.Bool -> bool() + is Type.Tinyint -> tinyint() + is Type.Smallint, is Type.Int2 -> smallint() + is Type.Int4, is Type.Int -> int() + is Type.Bigint, is Type.Int8 -> bigint() + is Type.Numeric -> numeric(type.precision, type.scale) + is Type.Decimal -> decimal(type.precision, type.scale) + is Type.Real -> real() + is Type.Float32 -> real() + is Type.Float64 -> double() + is Type.Char -> char(type.length) + is Type.Varchar -> varchar(type.length) + is Type.String -> string() + is Type.Symbol -> { + // TODO will we continue supporting symbol? + PType.typeSymbol() + } + is Type.Bit -> error("BIT is not supported yet.") + is Type.BitVarying -> error("BIT VARYING is not supported yet.") + is Type.ByteString -> error("BINARY is not supported yet.") + is Type.Blob -> blob(type.length) + is Type.Clob -> clob(type.length) + is Type.Date -> date() + is Type.Time -> time(type.precision) + is Type.TimeWithTz -> timez(type.precision) + is Type.Timestamp -> timestamp(type.precision) + is Type.TimestampWithTz -> timestampz(type.precision) + is Type.Interval -> error("INTERVAL is not supported yet.") + is Type.Bag -> bag() + is Type.Sexp -> { + // TODO will we continue supporting s-expression? + PType.typeSexp() + } + is Type.Any -> dynamic() + is Type.List -> array() + is Type.Array -> array(type.type?.let { from(it) }) + is Type.Tuple -> struct() + is Type.Struct -> struct() + is Type.Custom -> TODO("Custom type not supported ") + } + + @JvmStatic + fun from(kind: PType.Kind): PType = when (kind) { + PType.Kind.DYNAMIC -> dynamic() + PType.Kind.BOOL -> bool() + PType.Kind.TINYINT -> tinyint() + PType.Kind.SMALLINT -> smallint() + PType.Kind.INT -> int() + PType.Kind.BIGINT -> bigint() + PType.Kind.INT_ARBITRARY -> numeric() + PType.Kind.DECIMAL, PType.Kind.DECIMAL_ARBITRARY -> decimal() + PType.Kind.REAL -> real() + PType.Kind.DOUBLE_PRECISION -> double() + PType.Kind.CHAR -> char() + PType.Kind.VARCHAR -> varchar() + PType.Kind.STRING -> string() + PType.Kind.SYMBOL -> { + // TODO will we continue supporting symbol? + PType.typeSymbol() + } + PType.Kind.BLOB -> blob() + PType.Kind.CLOB -> clob() + PType.Kind.DATE -> date() + PType.Kind.TIME_WITH_TZ -> timez() + PType.Kind.TIME_WITHOUT_TZ -> time() + PType.Kind.TIMESTAMP_WITH_TZ -> timestampz() + PType.Kind.TIMESTAMP_WITHOUT_TZ -> timestamp() + PType.Kind.BAG -> bag() + PType.Kind.LIST -> array() + PType.Kind.ROW -> error("Cannot create a ROW from Kind") + PType.Kind.SEXP -> { + // TODO will we continue supporting sexp? + PType.typeSexp() + } + PType.Kind.STRUCT -> struct() + PType.Kind.UNKNOWN -> PType.typeUnknown() + } +} diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/transforms/RexConverter.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/transforms/RexConverter.kt index 330397672..26e6c90a7 100644 --- a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/transforms/RexConverter.kt +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/transforms/RexConverter.kt @@ -147,65 +147,131 @@ internal object RexConverter { return rex(type, op) } - override fun visitExprUnary(node: Expr.Unary, context: Env): Rex { + private fun resolveUnaryOp(symbol: String, rhs: Expr, context: Env): Rex { val type = (ANY) // Args - val arg = visitExprCoerce(node.expr, context) + val arg = visitExprCoerce(rhs, context) val args = listOf(arg) // Fn - val id = identifierSymbol(node.op.name.lowercase(), Identifier.CaseSensitivity.INSENSITIVE) + val name = when (symbol) { + // TODO move hard-coded operator resolution into SPI + "+" -> "pos" + "-" -> "neg" + else -> error("unsupported unary op $symbol") + } + val id = identifierSymbol(name.lowercase(), Identifier.CaseSensitivity.INSENSITIVE) val op = rexOpCallUnresolved(id, args) return rex(type, op) } - override fun visitExprBinary(node: Expr.Binary, context: Env): Rex { + private fun resolveBinaryOp(lhs: Expr, symbol: String, rhs: Expr, context: Env): Rex { val type = (ANY) - val args = when (node.op) { - Expr.Binary.Op.LT, Expr.Binary.Op.GT, - Expr.Binary.Op.LTE, Expr.Binary.Op.GTE, - Expr.Binary.Op.EQ, Expr.Binary.Op.NE -> { + val args = when (symbol) { + "<", ">", + "<=", ">=", + "=", "<>", "!=" -> { when { // Example: [1, 2] < (SELECT a, b FROM t) - isLiteralArray(node.lhs) && isSqlSelect(node.rhs) -> { - val lhs = visitExprCoerce(node.lhs, context) - val rhs = visitExprCoerce(node.rhs, context, Rex.Op.Subquery.Coercion.ROW) - listOf(lhs, rhs) + isLiteralArray(lhs) && isSqlSelect(rhs) -> { + val l = visitExprCoerce(lhs, context) + val r = visitExprCoerce(rhs, context, Rex.Op.Subquery.Coercion.ROW) + listOf(l, r) } // Example: (SELECT a, b FROM t) < [1, 2] - isSqlSelect(node.lhs) && isLiteralArray(node.rhs) -> { - val lhs = visitExprCoerce(node.lhs, context, Rex.Op.Subquery.Coercion.ROW) - val rhs = visitExprCoerce(node.rhs, context) - listOf(lhs, rhs) + isSqlSelect(lhs) && isLiteralArray(rhs) -> { + val l = visitExprCoerce(lhs, context, Rex.Op.Subquery.Coercion.ROW) + val r = visitExprCoerce(rhs, context) + listOf(l, r) } // Example: 1 < 2 else -> { - val lhs = visitExprCoerce(node.lhs, context) - val rhs = visitExprCoerce(node.rhs, context) - listOf(lhs, rhs) + val l = visitExprCoerce(lhs, context) + val r = visitExprCoerce(rhs, context) + listOf(l, r) } } } // Example: 1 + 2 else -> { - val lhs = visitExprCoerce(node.lhs, context) - val rhs = visitExprCoerce(node.rhs, context) - listOf(lhs, rhs) + val l = visitExprCoerce(lhs, context) + val r = visitExprCoerce(rhs, context) + listOf(l, r) } } - // Wrap if a NOT if necessary - return when (node.op) { - Expr.Binary.Op.NE -> { + // Wrap if a NOT, if necessary + return when (symbol) { + "<>", "!=" -> { val op = negate(call("eq", *args.toTypedArray())) rex(type, op) } else -> { - val id = identifierSymbol(node.op.name.lowercase(), Identifier.CaseSensitivity.INSENSITIVE) + val name = when (symbol) { + // TODO eventually move hard-coded operator resolution into SPI + "<" -> "lt" + ">" -> "gt" + "<=" -> "lte" + ">=" -> "gte" + "=" -> "eq" + "||" -> "concat" + "+" -> "plus" + "-" -> "minus" + "*" -> "times" + "/" -> "divide" + "%" -> "modulo" + "&" -> "bitwise_and" + else -> error("unsupported binary op $symbol") + } + val id = identifierSymbol(name.lowercase(), Identifier.CaseSensitivity.INSENSITIVE) val op = rexOpCallUnresolved(id, args) rex(type, op) } } } + override fun visitExprOperator(node: Expr.Operator, ctx: Env): Rex { + val lhs = node.lhs + return if (lhs != null) { + resolveBinaryOp(lhs, node.symbol, node.rhs, ctx) + } else { + resolveUnaryOp(node.symbol, node.rhs, ctx) + } + } + + override fun visitExprNot(node: Expr.Not, ctx: Env): Rex { + val type = (ANY) + // Args + val arg = visitExprCoerce(node.value, ctx) + val args = listOf(arg) + // Fn + val id = identifierSymbol("not".lowercase(), Identifier.CaseSensitivity.INSENSITIVE) + val op = rexOpCallUnresolved(id, args) + return rex(type, op) + } + + override fun visitExprAnd(node: Expr.And, ctx: Env): Rex { + val type = (ANY) + val l = visitExprCoerce(node.lhs, ctx) + val r = visitExprCoerce(node.rhs, ctx) + val args = listOf(l, r) + + // Wrap if a NOT, if necessary + val id = identifierSymbol("and".lowercase(), Identifier.CaseSensitivity.INSENSITIVE) + val op = rexOpCallUnresolved(id, args) + return rex(type, op) + } + + override fun visitExprOr(node: Expr.Or, ctx: Env): Rex { + val type = (ANY) + val l = visitExprCoerce(node.lhs, ctx) + val r = visitExprCoerce(node.rhs, ctx) + val args = listOf(l, r) + + // Wrap if a NOT, if necessary + val id = identifierSymbol("or".lowercase(), Identifier.CaseSensitivity.INSENSITIVE) + val op = rexOpCallUnresolved(id, args) + return rex(type, op) + } + private fun isLiteralArray(node: Expr): Boolean = node is Expr.Collection && (node.type == Expr.Collection.Type.ARRAY || node.type == Expr.Collection.Type.LIST) private fun isSqlSelect(node: Expr): Boolean = node is Expr.SFW && @@ -440,7 +506,7 @@ internal object RexConverter { } // Converts AST CASE (x) WHEN y THEN z --> Plan CASE WHEN x = y THEN z - val id = identifierSymbol(Expr.Binary.Op.EQ.name.lowercase(), Identifier.CaseSensitivity.SENSITIVE) + val id = identifierSymbol("eq", Identifier.CaseSensitivity.SENSITIVE) val createBranch: (Rex, Rex) -> Rex.Op.Case.Branch = { condition: Rex, result: Rex -> val updatedCondition = when (rex) { null -> condition @@ -879,8 +945,7 @@ internal object RexConverter { // Helpers private fun negate(call: Rex.Op.Call): Rex.Op.Call { - val name = Expr.Unary.Op.NOT.name - val id = identifierSymbol(name.lowercase(), Identifier.CaseSensitivity.SENSITIVE) + val id = identifierSymbol("not", Identifier.CaseSensitivity.SENSITIVE) // wrap val arg = rex(BOOL, call) // rewrite call diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/typer/PlanTyper.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/typer/PlanTyper.kt index 33383eeef..976f60829 100644 --- a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/typer/PlanTyper.kt +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/typer/PlanTyper.kt @@ -64,6 +64,7 @@ import org.partiql.value.MissingValue import org.partiql.value.PartiQLValueExperimental import org.partiql.value.TextValue import org.partiql.value.stringValue +import java.lang.reflect.Type import kotlin.math.max /** @@ -82,7 +83,7 @@ internal class PlanTyper(private val env: Env) { throw IllegalArgumentException("PartiQLPlanner only supports Query statements") } // root TypeEnv has no bindings - val root = statement.root.type(emptyList(), emptyList(), Scope.GLOBAL) + val root = statement.root.type(emptyList(), emptyList(), Strategy.GLOBAL) return statementQuery(root) } internal companion object { @@ -189,8 +190,8 @@ internal class PlanTyper(private val env: Env) { * @property strategy */ private inner class RelTyper( - private val outer: List, - private val strategy: Scope, + private val outer: List, + private val strategy: Strategy, ) : PlanRewriter() { override fun visitRel(node: Rel, ctx: Rel.Type?) = visitRelOp(node.op, node.type) as Rel @@ -200,7 +201,7 @@ internal class PlanTyper(private val env: Env) { */ override fun visitRelOpScan(node: Rel.Op.Scan, ctx: Rel.Type?): Rel { // descend, with GLOBAL resolution strategy - val rex = node.rex.type(emptyList(), outer, Scope.GLOBAL) + val rex = node.rex.type(emptyList(), outer, Strategy.GLOBAL) // compute rel type val valueT = getElementTypeForFromSource(rex.type) val type = ctx!!.copyWithSchema(listOf(valueT)) @@ -214,7 +215,7 @@ internal class PlanTyper(private val env: Env) { */ override fun visitRelOpScanIndexed(node: Rel.Op.ScanIndexed, ctx: Rel.Type?): Rel { // descend, with GLOBAL resolution strategy - val rex = node.rex.type(emptyList(), outer, Scope.GLOBAL) + val rex = node.rex.type(emptyList(), outer, Strategy.GLOBAL) // compute rel type val valueT = getElementTypeForFromSource(rex.type) val indexT = PType.typeBigInt() @@ -228,7 +229,7 @@ internal class PlanTyper(private val env: Env) { * TODO handle NULL|STRUCT type */ override fun visitRelOpUnpivot(node: Rel.Op.Unpivot, ctx: Rel.Type?): Rel { - val rex = node.rex.type(emptyList(), outer, Scope.GLOBAL) + val rex = node.rex.type(emptyList(), outer, Strategy.GLOBAL) val op = relOpUnpivot(rex) val kType = PType.typeString() @@ -383,7 +384,7 @@ internal class PlanTyper(private val env: Env) { val input = visitRel(node.input, ctx) // type limit expression using outer scope with global resolution // TODO: Assert expression doesn't contain locals or upvalues. - val limit = node.limit.type(input.type.schema, outer, Scope.GLOBAL) + val limit = node.limit.type(input.type.schema, outer, Strategy.GLOBAL) // check types if (limit.type.isNumeric().not()) { val err = ProblemGenerator.missingRex( @@ -403,7 +404,7 @@ internal class PlanTyper(private val env: Env) { val input = visitRel(node.input, ctx) // type offset expression using outer scope with global resolution // TODO: Assert expression doesn't contain locals or upvalues. - val offset = node.offset.type(input.type.schema, outer, Scope.GLOBAL) + val offset = node.offset.type(input.type.schema, outer, Strategy.GLOBAL) // check types if (offset.type.isNumeric().not()) { val err = ProblemGenerator.missingRex( @@ -436,15 +437,19 @@ internal class PlanTyper(private val env: Env) { override fun visitRelOpJoin(node: Rel.Op.Join, ctx: Rel.Type?): Rel { // Rewrite LHS and RHS val lhs = visitRel(node.lhs, ctx) - val stack = outer + listOf(TypeEnv(lhs.type.schema, outer)) - val rhs = RelTyper(stack, Scope.GLOBAL).visitRel(node.rhs, ctx) + val stack = when (node.type) { + Rel.Op.Join.Type.INNER, Rel.Op.Join.Type.LEFT -> outer + listOf(Scope(lhs.type.schema, outer)) + Rel.Op.Join.Type.FULL, Rel.Op.Join.Type.RIGHT -> outer + } + val rhs = RelTyper(stack, Strategy.GLOBAL).visitRel(node.rhs, ctx) // Calculate output schema given JOIN type val schema = lhs.type.schema + rhs.type.schema val type = relType(schema, ctx!!.props) // Type the condition on the output schema - val condition = node.rex.type(TypeEnv(type.schema, outer)) + val typeEnv = TypeEnv(env, Scope(type.schema, outer)) + val condition = node.rex.type(typeEnv) val op = relOpJoin(lhs, rhs, condition, node.type) return rel(type, op) @@ -497,9 +502,10 @@ internal class PlanTyper(private val env: Env) { val resolvedRoot = when (val root = path.root) { is Rex.Op.Var.Unresolved -> { // resolve `root` to local binding - val locals = TypeEnv(input.type.schema, outer) + val locals = Scope(input.type.schema, outer) + val typeEnv = TypeEnv(env, locals) val path = root.identifier.toBindingPath() - val resolved = locals.resolve(path) + val resolved = typeEnv.resolve(path) if (resolved == null) { ProblemGenerator.missingRex( emptyList(), @@ -537,7 +543,8 @@ internal class PlanTyper(private val env: Env) { val input = visitRel(node.input, ctx) // type the calls and groups - val typer = RexTyper(TypeEnv(input.type.schema, outer), Scope.LOCAL) + val typeEnv = TypeEnv(env, Scope(input.type.schema, outer)) + val typer = RexTyper(typeEnv, Strategy.LOCAL) // typing of aggregate calls is slightly more complicated because they are not expressions. val calls = node.calls.mapIndexed { i, call -> @@ -571,12 +578,12 @@ internal class PlanTyper(private val env: Env) { * * We should consider making the PType? parameter non-nullable. * - * @property locals TypeEnv in which this rex tree is evaluated. + * @property typeEnv TypeEnv in which this rex tree is evaluated. */ @OptIn(PartiQLValueExperimental::class) private inner class RexTyper( - private val locals: TypeEnv, - private val strategy: Scope, + private val typeEnv: TypeEnv, + private val strategy: Strategy, ) : PlanRewriter() { override fun visitRex(node: Rex, ctx: CompilerType?): Rex = visitRexOp(node.op, node.type) as Rex @@ -587,9 +594,9 @@ internal class PlanTyper(private val env: Env) { } override fun visitRexOpVarLocal(node: Rex.Op.Var.Local, ctx: CompilerType?): Rex { - val scope = locals.getScope(node.depth) + val scope = typeEnv.locals.getScope(node.depth) assert(node.ref < scope.schema.size) { - "Invalid resolved variable (var ${node.ref}, stack frame ${node.depth}) in env: $locals" + "Invalid resolved variable (var ${node.ref}, stack frame ${node.depth}) in env: $typeEnv" } val type = scope.schema.getOrNull(node.ref)?.type ?: error("Can't find locals value.") return rex(type, node) @@ -602,17 +609,14 @@ internal class PlanTyper(private val env: Env) { override fun visitRexOpVarUnresolved(node: Rex.Op.Var.Unresolved, ctx: CompilerType?): Rex { val path = node.identifier.toBindingPath() - val scope = when (node.scope) { + val strategy = when (node.scope) { Rex.Op.Var.Scope.DEFAULT -> strategy - Rex.Op.Var.Scope.LOCAL -> Scope.LOCAL - } - val resolvedVar = when (scope) { - Scope.LOCAL -> locals.resolve(path) ?: env.resolveObj(path) - Scope.GLOBAL -> env.resolveObj(path) ?: locals.resolve(path) + Rex.Op.Var.Scope.LOCAL -> Strategy.LOCAL } + val resolvedVar = typeEnv.resolve(path, strategy) if (resolvedVar == null) { val id = PlanUtils.externalize(node.identifier) - val inScopeVariables = locals.schema.map { it.name }.toSet() + val inScopeVariables = typeEnv.locals.schema.map { it.name }.toSet() val err = ProblemGenerator.errorRex( causes = emptyList(), problem = ProblemGenerator.undefinedVariable(id, inScopeVariables) @@ -732,7 +736,7 @@ internal class PlanTyper(private val env: Env) { // Find Type val field = root.type.getSymbol(node.key) ?: run { - val inScopeVariables = locals.schema.map { it.name }.toSet() + val inScopeVariables = typeEnv.locals.schema.map { it.name }.toSet() return ProblemGenerator.missingRex( Rex.Op.Path.Symbol(root, node.key), ProblemGenerator.undefinedVariable( @@ -1060,10 +1064,11 @@ internal class PlanTyper(private val env: Env) { } override fun visitRexOpPivot(node: Rex.Op.Pivot, ctx: CompilerType?): Rex { - val stack = locals.outer + listOf(locals) + val stack = typeEnv.locals.outer + listOf(typeEnv.locals) val rel = node.rel.type(stack) - val typeEnv = TypeEnv(rel.type.schema, stack) - val typer = RexTyper(typeEnv, Scope.LOCAL) + val scope = Scope(rel.type.schema, stack) + val typeEnv = TypeEnv(env, scope) + val typer = RexTyper(typeEnv, Strategy.LOCAL) val key = typer.visitRex(node.key, null) val value = typer.visitRex(node.value, null) val op = rexOpPivot(key, value, rel) @@ -1071,9 +1076,10 @@ internal class PlanTyper(private val env: Env) { } override fun visitRexOpSubquery(node: Rex.Op.Subquery, ctx: CompilerType?): Rex { - val rel = node.rel.type(locals.outer + listOf(locals)) - val newTypeEnv = TypeEnv(schema = rel.type.schema, outer = locals.outer + listOf(locals)) - val constructor = node.constructor.type(newTypeEnv) + val rel = node.rel.type(typeEnv.locals.outer + listOf(typeEnv.locals)) + val newScope = Scope(schema = rel.type.schema, outer = typeEnv.locals.outer + listOf(typeEnv.locals)) + val typeEnv = TypeEnv(env, newScope) + val constructor = node.constructor.type(typeEnv) val subquery = rexOpSubquery(constructor, rel, node.coercion) return when (node.coercion) { Rex.Op.Subquery.Coercion.SCALAR -> visitRexOpSubqueryScalar(subquery, constructor.type) @@ -1118,9 +1124,10 @@ internal class PlanTyper(private val env: Env) { // TODO: Should we support the ROW type? override fun visitRexOpSelect(node: Rex.Op.Select, ctx: CompilerType?): Rex { - val rel = node.rel.type(locals.outer + listOf(locals)) - val newTypeEnv = TypeEnv(schema = rel.type.schema, outer = locals.outer + listOf(locals)) - val constructor = node.constructor.type(newTypeEnv) + val rel = node.rel.type(typeEnv.locals.outer + listOf(typeEnv.locals)) + val newScope = Scope(schema = rel.type.schema, outer = typeEnv.locals.outer + listOf(typeEnv.locals)) + val typeEnv = TypeEnv(env, newScope) + val constructor = node.constructor.type(typeEnv) val type = when (rel.isOrdered()) { true -> PType.typeList(constructor.type) false -> PType.typeBag(constructor.type) @@ -1289,20 +1296,20 @@ internal class PlanTyper(private val env: Env) { // HELPERS - private fun Rel.type(stack: List, strategy: Scope = Scope.LOCAL): Rel = + private fun Rel.type(stack: List, strategy: Strategy = Strategy.LOCAL): Rel = RelTyper(stack, strategy).visitRel(this, null) /** - * This types the [Rex] given the input record ([input]) and [stack] of [TypeEnv] (representing the outer scopes). + * This types the [Rex] given the input record ([input]) and [stack] of [Scope] (representing the outer scopes). */ - private fun Rex.type(input: List, stack: List, strategy: Scope = Scope.LOCAL) = - RexTyper(TypeEnv(input, stack), strategy).visitRex(this, this.type) + private fun Rex.type(input: List, stack: List, strategy: Strategy = Strategy.LOCAL) = + RexTyper(TypeEnv(env, Scope(input, stack)), strategy).visitRex(this, this.type) /** - * This types the [Rex] given a [TypeEnv]. We use the [TypeEnv.schema] as the input schema and the [TypeEnv.outer] + * This types the [Rex] given a [Scope]. We use the [Scope.schema] as the input schema and the [Scope.outer] * as the outer scopes/ */ - private fun Rex.type(typeEnv: TypeEnv, strategy: Scope = Scope.LOCAL) = + private fun Rex.type(typeEnv: TypeEnv, strategy: Strategy = Strategy.LOCAL) = RexTyper(typeEnv, strategy).visitRex(this, this.type) /** diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/typer/Scope.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/typer/Scope.kt index 42eda6d22..1d87b8175 100644 --- a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/typer/Scope.kt +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/typer/Scope.kt @@ -1,14 +1,177 @@ package org.partiql.planner.internal.typer +import org.partiql.planner.internal.ir.Rel +import org.partiql.planner.internal.ir.Rex +import org.partiql.planner.internal.ir.rex +import org.partiql.planner.internal.ir.rexOpLit +import org.partiql.planner.internal.ir.rexOpPathKey +import org.partiql.planner.internal.ir.rexOpPathSymbol +import org.partiql.planner.internal.ir.rexOpVarLocal +import org.partiql.spi.BindingCase +import org.partiql.spi.BindingName +import org.partiql.spi.BindingPath +import org.partiql.types.PType +import org.partiql.types.PType.Kind +import org.partiql.types.StaticType +import org.partiql.value.PartiQLValueExperimental +import org.partiql.value.stringValue + /** - * Variable resolution strategies — https://partiql.org/assets/PartiQL-Specification.pdf#page=35 + * Represents local variable scopes. * - * | Value | Strategy | Scoping Rules | - * |------------+-----------------------+---------------| - * | LOCAL | local-first lookup | Rules 1, 2 | - * | GLOBAL | global-first lookup | Rule 3 | + * @property outer refers to the outer variable scopes that we have access to. */ -internal enum class Scope { - LOCAL, - GLOBAL, +internal data class Scope( + public val schema: List, + public val outer: List +) { + + internal fun getScope(depth: Int): Scope { + return when (depth) { + 0 -> this + else -> outer[outer.size - depth] + } + } + + /** + * Attempts to resolve using just the local binding name. + */ + fun resolveName(path: BindingPath): Rex? { + val head: BindingName = path.steps[0] + val tail: List = path.steps.drop(1) + val r = matchRoot(head) ?: return null + // Convert any remaining binding names (tail) to an untyped path expression. + return if (tail.isEmpty()) r else r.toPath(tail) + } + + /** + * Check if the path root unambiguously matches a local binding struct value field. + * Convert any remaining binding names (tail) to a path expression. + * + * @param path + * @return + */ + fun resolveField(path: BindingPath): Rex? { + val head: BindingName = path.steps[0] + val r = matchStruct(head) ?: return null + val tail = path.steps + // Convert any remaining binding names (tail) to an untyped path expression. + return if (tail.isEmpty()) r else r.toPath(tail) + } + + /** + * Debugging string, ex: < x: int, y: string > + * + * @return + */ + override fun toString(): String = "< " + schema.joinToString { "${it.name}: ${it.type}" } + " >" + + /** + * Check if `name` unambiguously matches a local binding name and return its reference; otherwise return null. + * + * @param name + * @return + */ + private fun matchRoot(name: BindingName, depth: Int = 0): Rex? { + var r: Rex? = null + for (i in schema.indices) { + val local = schema[i] + val type = local.type + if (name.matches(local.name)) { + if (r != null) { + // TODO root was already matched, emit ambiguous error. + return null + } + r = rex(type, rexOpVarLocal(depth, i)) + } + } + if (r == null && outer.isNotEmpty()) { + return outer.last().matchRoot(name, depth + 1) + } + return r + } + + /** + * Check if `name` unambiguously matches a field within a struct and return its reference; otherwise return null. + * + * @param name + * @return + */ + private fun matchStruct(name: BindingName, depth: Int = 0): Rex? { + var c: Rex? = null + var known = false + for (i in schema.indices) { + val local = schema[i] + val type = local.type + when (type.containsKey(name)) { + true -> { + if (c != null && known) { + // TODO root was already definitively matched, emit ambiguous error. + return null + } + c = rex(type, rexOpVarLocal(depth, i)) + known = true + } + null -> { + if (c != null) { + if (known) { + continue + } else { + // TODO we have more than one possible match, emit ambiguous error. + return null + } + } + c = rex(type, rexOpVarLocal(depth, i)) + known = false + } + false -> continue + } + } + if (c == null && outer.isNotEmpty()) { + return outer.last().matchStruct(name, depth + 1) + } + return c + } + + /** + * Searches for the [BindingName] within the given [StaticType]. + * + * Returns + * - true iff known to contain key + * - false iff known to NOT contain key + * - null iff NOT known to contain key + * + * @param name + * @return + */ + private fun CompilerType.containsKey(name: BindingName): Boolean? { + return when (this.kind) { + Kind.ROW -> this.fields!!.any { name.matches(it.name) } + Kind.STRUCT -> null + Kind.DYNAMIC -> null + else -> false + } + } + + companion object { + + /** + * Converts a list of [BindingName] to a path expression. + * + * 1) Case SENSITIVE identifiers become string literal key lookups. + * 2) Case INSENSITIVE identifiers become symbol lookups. + * + * @param steps + * @return + */ + @JvmStatic + @OptIn(PartiQLValueExperimental::class) + internal fun Rex.toPath(steps: List): Rex = steps.fold(this) { curr, step -> + val op = when (step.case) { + BindingCase.SENSITIVE -> rexOpPathKey(curr, rex(CompilerType(PType.typeString()), rexOpLit(stringValue(step.name)))) + BindingCase.INSENSITIVE -> rexOpPathSymbol(curr, step.name) + } + rex(CompilerType(PType.typeDynamic()), op) + } + } } diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/typer/Strategy.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/typer/Strategy.kt new file mode 100644 index 000000000..c0fae1e54 --- /dev/null +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/typer/Strategy.kt @@ -0,0 +1,14 @@ +package org.partiql.planner.internal.typer + +/** + * Variable resolution strategies — https://partiql.org/assets/PartiQL-Specification.pdf#page=35 + * + * | Value | Strategy | Scoping Rules | + * |------------+-----------------------+---------------| + * | LOCAL | local-first lookup | Rules 1, 2 | + * | GLOBAL | global-first lookup | Rule 3 | + */ +internal enum class Strategy { + LOCAL, + GLOBAL, +} diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/typer/TypeEnv.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/typer/TypeEnv.kt index 17a7acb3c..137e51391 100644 --- a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/typer/TypeEnv.kt +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/typer/TypeEnv.kt @@ -1,174 +1,35 @@ package org.partiql.planner.internal.typer -import org.partiql.planner.internal.ir.Rel +import org.partiql.planner.internal.Env import org.partiql.planner.internal.ir.Rex -import org.partiql.planner.internal.ir.rex -import org.partiql.planner.internal.ir.rexOpLit -import org.partiql.planner.internal.ir.rexOpPathKey -import org.partiql.planner.internal.ir.rexOpPathSymbol -import org.partiql.planner.internal.ir.rexOpVarLocal -import org.partiql.spi.BindingCase -import org.partiql.spi.BindingName import org.partiql.spi.BindingPath -import org.partiql.types.PType -import org.partiql.types.PType.Kind -import org.partiql.types.StaticType -import org.partiql.value.PartiQLValueExperimental -import org.partiql.value.stringValue /** - * TypeEnv represents a variables type environment. - * - * @property outer refers to the outer variable scopes that we have access to. + * TypeEnv represents the variables type environment (holds references to both locals and globals). */ -internal data class TypeEnv( - public val schema: List, - public val outer: List +internal class TypeEnv( + val globals: Env, + val locals: Scope ) { - internal fun getScope(depth: Int): TypeEnv { - return when (depth) { - 0 -> this - else -> outer[outer.size - depth] - } - } - - /** - * We resolve a local with the following rules. See, PartiQL Specification p.35. - * - * 1) Check if the path root unambiguously matches a local binding name, set as root. - * 2) Check if the path root unambiguously matches a local binding struct value field. - * - * Convert any remaining binding names (tail) to a path expression. - * - * @param path - * @return - */ - fun resolve(path: BindingPath): Rex? { - val head: BindingName = path.steps[0] - var tail: List = path.steps.drop(1) - var r = matchRoot(head) - if (r == null) { - r = matchStruct(head) ?: return null - tail = path.steps - } - // Convert any remaining binding names (tail) to an untyped path expression. - return if (tail.isEmpty()) r else r.toPath(tail) - } - - /** - * Debugging string, ex: < x: int, y: string > - * - * @return - */ - override fun toString(): String = "< " + schema.joinToString { "${it.name}: ${it.type}" } + " >" - /** - * Check if `name` unambiguously matches a local binding name and return its reference; otherwise return null. - * - * @param name - * @return + * Search Algorithm (LOCALS_FIRST): + * 1. Match Binding Name + * - Match Locals + * - Match Globals + * 2. Match Nested Field + * - Match Locals + * Search Algorithm (GLOBALS_FIRST): + * 1. Match Binding Name + * - Match Globals + * - Match Locals + * 2. Match Nested Field + * - Match Locals */ - private fun matchRoot(name: BindingName, depth: Int = 0): Rex? { - var r: Rex? = null - for (i in schema.indices) { - val local = schema[i] - val type = local.type - if (name.matches(local.name)) { - if (r != null) { - // TODO root was already matched, emit ambiguous error. - return null - } - r = rex(type, rexOpVarLocal(depth, i)) - } - } - if (r == null && outer.isNotEmpty()) { - return outer.last().matchRoot(name, depth + 1) - } - return r - } - - /** - * Check if `name` unambiguously matches a field within a struct and return its reference; otherwise return null. - * - * @param name - * @return - */ - private fun matchStruct(name: BindingName, depth: Int = 0): Rex? { - var c: Rex? = null - var known = false - for (i in schema.indices) { - val local = schema[i] - val type = local.type - when (type.containsKey(name)) { - true -> { - if (c != null && known) { - // TODO root was already definitively matched, emit ambiguous error. - return null - } - c = rex(type, rexOpVarLocal(depth, i)) - known = true - } - null -> { - if (c != null) { - if (known) { - continue - } else { - // TODO we have more than one possible match, emit ambiguous error. - return null - } - } - c = rex(type, rexOpVarLocal(depth, i)) - known = false - } - false -> continue - } - } - if (c == null && outer.isNotEmpty()) { - return outer.last().matchStruct(name, depth + 1) - } - return c - } - - /** - * Searches for the [BindingName] within the given [StaticType]. - * - * Returns - * - true iff known to contain key - * - false iff known to NOT contain key - * - null iff NOT known to contain key - * - * @param name - * @return - */ - private fun CompilerType.containsKey(name: BindingName): Boolean? { - return when (this.kind) { - Kind.ROW -> this.fields!!.any { name.matches(it.name) } - Kind.STRUCT -> null - Kind.DYNAMIC -> null - else -> false - } - } - - companion object { - - /** - * Converts a list of [BindingName] to a path expression. - * - * 1) Case SENSITIVE identifiers become string literal key lookups. - * 2) Case INSENSITIVE identifiers become symbol lookups. - * - * @param steps - * @return - */ - @JvmStatic - @OptIn(PartiQLValueExperimental::class) - internal fun Rex.toPath(steps: List): Rex = steps.fold(this) { curr, step -> - val op = when (step.case) { - BindingCase.SENSITIVE -> rexOpPathKey(curr, rex(CompilerType(PType.typeString()), rexOpLit(stringValue(step.name)))) - BindingCase.INSENSITIVE -> rexOpPathSymbol(curr, step.name) - } - rex(CompilerType(PType.typeDynamic()), op) + fun resolve(path: BindingPath, strategy: Strategy = Strategy.LOCAL): Rex? { + return when (strategy) { + Strategy.LOCAL -> locals.resolveName(path) ?: globals.resolveObj(path) ?: locals.resolveField(path) + Strategy.GLOBAL -> globals.resolveObj(path) ?: locals.resolveName(path) ?: locals.resolveField(path) } } } diff --git a/partiql-planner/src/test/kotlin/org/partiql/planner/PlannerErrorReportingTests.kt b/partiql-planner/src/test/kotlin/org/partiql/planner/PlannerErrorReportingTests.kt index 4d8f33a87..e97c546a9 100644 --- a/partiql-planner/src/test/kotlin/org/partiql/planner/PlannerErrorReportingTests.kt +++ b/partiql-planner/src/test/kotlin/org/partiql/planner/PlannerErrorReportingTests.kt @@ -18,6 +18,7 @@ import org.partiql.types.PType import org.partiql.types.StaticType import org.partiql.types.StructType import org.partiql.types.TupleConstraint +import java.lang.AssertionError import kotlin.test.assertEquals internal class PlannerErrorReportingTests { @@ -61,38 +62,39 @@ internal class PlannerErrorReportingTests { parser.parse(query).root } - fun assertProblem( + private fun assertProblem( plan: org.partiql.plan.PlanNode, problems: List, - vararg block: () -> Boolean + block: (List) -> Unit ) { - block.forEachIndexed { index, function -> - assert(function.invoke()) { - buildString { - this.appendLine("assertion #${index + 1} failed") + try { + block.invoke(problems) + } catch (e: Throwable) { + val str = buildString { + this.appendLine("Assertion failed") - this.appendLine("--------Plan---------") - PlanPrinter.append(this, plan) + this.appendLine("--------Plan---------") + PlanPrinter.append(this, plan) - this.appendLine("----------Problems---------") - problems.forEach { - this.appendLine(it.toString()) - } + this.appendLine("----------Problems---------") + problems.forEach { + this.appendLine(it.toString()) } } + throw AssertionError(str, e) } } data class TestCase( val query: String, val isSignal: Boolean, - val assertion: (List) -> List<() -> Boolean>, + val assertion: (List) -> Unit, val expectedType: CompilerType ) { constructor( query: String, isSignal: Boolean, - assertion: (List) -> List<() -> Boolean>, + assertion: (List) -> Unit, expectedType: StaticType = StaticType.ANY ) : this(query, isSignal, assertion, PType.fromStaticType(expectedType).toCType()) } @@ -110,11 +112,9 @@ internal class PlannerErrorReportingTests { ) ) - private fun assertOnProblemCount(warningCount: Int, errorCount: Int): (List) -> List<() -> Boolean> = { problems -> - listOf( - { problems.filter { it.details.severity == ProblemSeverity.WARNING }.size == warningCount }, - { problems.filter { it.details.severity == ProblemSeverity.ERROR }.size == errorCount }, - ) + private fun assertOnProblemCount(warningCount: Int, errorCount: Int): (List) -> Unit = { problems -> + assertEquals(warningCount, problems.filter { it.details.severity == ProblemSeverity.WARNING }.size, "Number of warnings is wrong.") + assertEquals(errorCount, problems.filter { it.details.severity == ProblemSeverity.ERROR }.size, "Number of errors is wrong.") } /** @@ -278,13 +278,13 @@ internal class PlannerErrorReportingTests { TestCase( "1 + not_a_function(1)", false, - assertOnProblemCount(1, 1), + assertOnProblemCount(0, 1), StaticType.INT4, ), TestCase( "1 + not_a_function(1)", true, - assertOnProblemCount(0, 2), + assertOnProblemCount(0, 1), StaticType.INT4, ), @@ -408,7 +408,7 @@ internal class PlannerErrorReportingTests { assertProblem( plan, problems, - *tc.assertion(problems).toTypedArray() + tc.assertion ) assertEquals(tc.expectedType, (plan.statement as org.partiql.plan.Statement.Query).root.type) } @@ -420,28 +420,4 @@ internal class PlannerErrorReportingTests { @ParameterizedTest @MethodSource("testContinuation") fun testContinuation(tc: TestCase) = runTestCase(tc) - - private fun StaticType.assertStaticTypeEqual(other: StaticType) { - val thisAll = this.allTypes.toSet() - val otherAll = other.allTypes.toSet() - val diff = (thisAll - otherAll) + (otherAll - thisAll) - assert(diff.isEmpty()) { - buildString { - this.appendLine("expected: ") - thisAll.forEach { - this.append("$it, ") - } - this.appendLine() - this.appendLine("actual") - otherAll.forEach { - this.append("$it, ") - } - this.appendLine() - this.appendLine("diff") - diff.forEach { - this.append("$it, ") - } - } - } - } } diff --git a/partiql-planner/src/test/kotlin/org/partiql/planner/internal/typer/PlanTyperTestsPorted.kt b/partiql-planner/src/test/kotlin/org/partiql/planner/internal/typer/PlanTyperTestsPorted.kt index c5be09749..9284b5cb0 100644 --- a/partiql-planner/src/test/kotlin/org/partiql/planner/internal/typer/PlanTyperTestsPorted.kt +++ b/partiql-planner/src/test/kotlin/org/partiql/planner/internal/typer/PlanTyperTestsPorted.kt @@ -598,14 +598,13 @@ internal class PlanTyperTestsPorted { catalog = "pql", expected = StaticType.BOOL, ), - ErrorTestCase( + // TODO: For some reason, the conformance tests say that this results in TRUE. Regardless, we know it returns + // a boolean. We should re-look at what the conformance tests should return. + SuccessTestCase( name = "MISSING IS NULL", key = key("is-type-04"), catalog = "pql", expected = StaticType.BOOL, - problemHandler = assertProblemExists( - ProblemGenerator.expressionAlwaysReturnsMissing("Static function always receives MISSING arguments.") - ) ), SuccessTestCase( name = "NULL IS NULL", @@ -968,6 +967,50 @@ internal class PlanTyperTestsPorted { ProblemGenerator.undefinedVariable(insensitive("a"), setOf("t1", "t2")) ) ), + SuccessTestCase( + name = "LEFT JOIN (Lateral references)", + query = """ + SELECT VALUE rhs + FROM << [0, 1, 2], [10, 11, 12], [20, 21, 22] >> AS lhs + LEFT OUTER JOIN lhs AS rhs + ON lhs[2] = rhs + """.trimIndent(), + expected = BagType(INT4) + ), + SuccessTestCase( + name = "INNER JOIN (Lateral references)", + query = """ + SELECT VALUE rhs + FROM << [0, 1, 2], [10, 11, 12], [20, 21, 22] >> AS lhs + INNER JOIN lhs AS rhs + ON lhs[2] = rhs + """.trimIndent(), + expected = BagType(INT4) + ), + ErrorTestCase( + name = "RIGHT JOIN (Doesn't support lateral references)", + query = """ + SELECT VALUE rhs + FROM << [0, 1, 2], [10, 11, 12], [20, 21, 22] >> AS lhs + RIGHT OUTER JOIN lhs AS rhs + ON lhs[2] = rhs + """.trimIndent(), + problemHandler = assertProblemExists( + ProblemGenerator.undefinedVariable(insensitive("lhs"), setOf()) + ) + ), + ErrorTestCase( + name = "FULL JOIN (Doesn't support lateral references)", + query = """ + SELECT VALUE rhs + FROM << [0, 1, 2], [10, 11, 12], [20, 21, 22] >> AS lhs + FULL OUTER JOIN lhs AS rhs + ON lhs[2] = rhs + """.trimIndent(), + problemHandler = assertProblemExists( + ProblemGenerator.undefinedVariable(insensitive("lhs"), setOf()) + ) + ), ) @JvmStatic diff --git a/partiql-planner/src/test/kotlin/org/partiql/planner/internal/typer/TypeEnvTest.kt b/partiql-planner/src/test/kotlin/org/partiql/planner/internal/typer/ScopeTest.kt similarity index 63% rename from partiql-planner/src/test/kotlin/org/partiql/planner/internal/typer/TypeEnvTest.kt rename to partiql-planner/src/test/kotlin/org/partiql/planner/internal/typer/ScopeTest.kt index a5274f20d..97b12a84f 100644 --- a/partiql-planner/src/test/kotlin/org/partiql/planner/internal/typer/TypeEnvTest.kt +++ b/partiql-planner/src/test/kotlin/org/partiql/planner/internal/typer/ScopeTest.kt @@ -4,17 +4,22 @@ import org.junit.jupiter.api.parallel.Execution import org.junit.jupiter.api.parallel.ExecutionMode import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.MethodSource +import org.partiql.planner.PartiQLPlanner +import org.partiql.planner.internal.Env import org.partiql.planner.internal.ir.Rex import org.partiql.planner.internal.ir.relBinding import org.partiql.planner.internal.typer.PlanTyper.Companion.toCType import org.partiql.spi.BindingCase import org.partiql.spi.BindingName import org.partiql.spi.BindingPath +import org.partiql.spi.connector.ConnectorHandle +import org.partiql.spi.connector.ConnectorMetadata +import org.partiql.spi.fn.FnExperimental import org.partiql.types.PType import kotlin.test.assertEquals import kotlin.test.fail -internal class TypeEnvTest { +internal class ScopeTest { companion object { @@ -30,15 +35,41 @@ internal class TypeEnvTest { */ @JvmStatic val locals = TypeEnv( - listOf( - relBinding("A", struct("B" to PType.typeBool().toCType())), - relBinding("a", struct("b" to PType.typeBool().toCType())), - relBinding("X", struct(open = true)), - relBinding("x", struct("Y" to PType.typeBool().toCType(), open = false)), // We currently don't allow for partial schema structs - relBinding("y", struct(open = true)), - relBinding("T", struct("x" to PType.typeBool().toCType(), "x" to PType.typeBool().toCType())), + Env( + PartiQLPlanner.Session( + "queryId", + "userId", + "currentCatalog", + catalogs = mapOf( + "currentCatalog" to object : ConnectorMetadata { + override fun getObject(path: BindingPath): ConnectorHandle.Obj? { + return null + } + + @FnExperimental + override fun getFunction(path: BindingPath): ConnectorHandle.Fn? { + return null + } + + @FnExperimental + override fun getAggregation(path: BindingPath): ConnectorHandle.Agg? { + return null + } + } + ) + ) ), - outer = emptyList() + Scope( + listOf( + relBinding("A", struct("B" to PType.typeBool().toCType())), + relBinding("a", struct("b" to PType.typeBool().toCType())), + relBinding("X", struct(open = true)), + relBinding("x", struct("Y" to PType.typeBool().toCType(), open = false)), // We currently don't allow for partial schema structs + relBinding("y", struct(open = true)), + relBinding("T", struct("x" to PType.typeBool().toCType(), "x" to PType.typeBool().toCType())), + ), + outer = emptyList() + ) ) private fun struct(vararg fields: Pair, open: Boolean = false): CompilerType { diff --git a/partiql-spi/src/main/kotlin/org/partiql/spi/connector/sql/SqlBuiltins.kt b/partiql-spi/src/main/kotlin/org/partiql/spi/connector/sql/SqlBuiltins.kt index 6678cb084..bc13d3b52 100644 --- a/partiql-spi/src/main/kotlin/org/partiql/spi/connector/sql/SqlBuiltins.kt +++ b/partiql-spi/src/main/kotlin/org/partiql/spi/connector/sql/SqlBuiltins.kt @@ -121,33 +121,6 @@ internal object SqlBuiltins { Fn_DIVIDE__FLOAT32_FLOAT32__FLOAT32, Fn_DIVIDE__FLOAT64_FLOAT64__FLOAT64, Fn_DIVIDE__DECIMAL_ARBITRARY_DECIMAL_ARBITRARY__DECIMAL_ARBITRARY, - Fn_EQ__NULL_NULL__BOOL, - Fn_EQ__MISSING_MISSING__BOOL, - Fn_EQ__BOOL_BOOL__BOOL, - Fn_EQ__INT8_INT8__BOOL, - Fn_EQ__INT16_INT16__BOOL, - Fn_EQ__INT32_INT32__BOOL, - Fn_EQ__INT64_INT64__BOOL, - Fn_EQ__INT_INT__BOOL, - Fn_EQ__DECIMAL_DECIMAL__BOOL, - Fn_EQ__FLOAT32_FLOAT32__BOOL, - Fn_EQ__FLOAT64_FLOAT64__BOOL, - Fn_EQ__DECIMAL_ARBITRARY_DECIMAL_ARBITRARY__BOOL, - Fn_EQ__CHAR_CHAR__BOOL, - Fn_EQ__STRING_STRING__BOOL, - Fn_EQ__CLOB_CLOB__BOOL, - Fn_EQ__SYMBOL_SYMBOL__BOOL, - Fn_EQ__BINARY_BINARY__BOOL, - Fn_EQ__BYTE_BYTE__BOOL, - Fn_EQ__BLOB_BLOB__BOOL, - Fn_EQ__DATE_DATE__BOOL, - Fn_EQ__TIME_TIME__BOOL, - Fn_EQ__TIMESTAMP_TIMESTAMP__BOOL, - Fn_EQ__INTERVAL_INTERVAL__BOOL, - Fn_EQ__LIST_LIST__BOOL, - Fn_EQ__SEXP_SEXP__BOOL, - Fn_EQ__BAG_BAG__BOOL, - Fn_EQ__STRUCT_STRUCT__BOOL, Fn_EQ__ANY_ANY__BOOL, Fn_EXTRACT_DAY__DATE__INT32, Fn_EXTRACT_DAY__TIMESTAMP__INT32, @@ -365,7 +338,6 @@ internal object SqlBuiltins { Fn_NEG__FLOAT32__FLOAT32, Fn_NEG__FLOAT64__FLOAT64, Fn_NEG__DECIMAL_ARBITRARY__DECIMAL_ARBITRARY, - Fn_NOT__MISSING__BOOL, Fn_NOT__BOOL__BOOL, Fn_OR__BOOL_BOOL__BOOL, Fn_OCTET_LENGTH__STRING__INT32, diff --git a/partiql-spi/src/main/kotlin/org/partiql/spi/connector/sql/builtins/FnEq.kt b/partiql-spi/src/main/kotlin/org/partiql/spi/connector/sql/builtins/FnEq.kt index dbdee9679..3febb655a 100644 --- a/partiql-spi/src/main/kotlin/org/partiql/spi/connector/sql/builtins/FnEq.kt +++ b/partiql-spi/src/main/kotlin/org/partiql/spi/connector/sql/builtins/FnEq.kt @@ -7,69 +7,32 @@ import org.partiql.spi.fn.Fn import org.partiql.spi.fn.FnExperimental import org.partiql.spi.fn.FnParameter import org.partiql.spi.fn.FnSignature -import org.partiql.value.BagValue -import org.partiql.value.BinaryValue -import org.partiql.value.BlobValue -import org.partiql.value.BoolValue -import org.partiql.value.ByteValue -import org.partiql.value.CharValue -import org.partiql.value.ClobValue -import org.partiql.value.DateValue -import org.partiql.value.DecimalValue -import org.partiql.value.Float32Value -import org.partiql.value.Float64Value -import org.partiql.value.Int16Value -import org.partiql.value.Int32Value -import org.partiql.value.Int64Value -import org.partiql.value.Int8Value -import org.partiql.value.IntValue -import org.partiql.value.IntervalValue -import org.partiql.value.ListValue import org.partiql.value.PartiQLValue import org.partiql.value.PartiQLValueExperimental import org.partiql.value.PartiQLValueType.ANY -import org.partiql.value.PartiQLValueType.BAG -import org.partiql.value.PartiQLValueType.BINARY -import org.partiql.value.PartiQLValueType.BLOB import org.partiql.value.PartiQLValueType.BOOL -import org.partiql.value.PartiQLValueType.BYTE -import org.partiql.value.PartiQLValueType.CHAR -import org.partiql.value.PartiQLValueType.CLOB -import org.partiql.value.PartiQLValueType.DATE -import org.partiql.value.PartiQLValueType.DECIMAL -import org.partiql.value.PartiQLValueType.DECIMAL_ARBITRARY -import org.partiql.value.PartiQLValueType.FLOAT32 -import org.partiql.value.PartiQLValueType.FLOAT64 -import org.partiql.value.PartiQLValueType.INT -import org.partiql.value.PartiQLValueType.INT16 -import org.partiql.value.PartiQLValueType.INT32 -import org.partiql.value.PartiQLValueType.INT64 -import org.partiql.value.PartiQLValueType.INT8 -import org.partiql.value.PartiQLValueType.INTERVAL -import org.partiql.value.PartiQLValueType.LIST import org.partiql.value.PartiQLValueType.MISSING -import org.partiql.value.PartiQLValueType.NULL -import org.partiql.value.PartiQLValueType.SEXP -import org.partiql.value.PartiQLValueType.STRING -import org.partiql.value.PartiQLValueType.STRUCT -import org.partiql.value.PartiQLValueType.SYMBOL -import org.partiql.value.PartiQLValueType.TIME -import org.partiql.value.PartiQLValueType.TIMESTAMP -import org.partiql.value.SexpValue -import org.partiql.value.StringValue -import org.partiql.value.StructValue -import org.partiql.value.SymbolValue -import org.partiql.value.TimeValue -import org.partiql.value.TimestampValue import org.partiql.value.boolValue -import org.partiql.value.check +/** + * According to SQL:1999: + * > If either XV or YV is the null value, then `X Y` is unknown + * + * According to the PartiQL Specification: + * > Equality never fails in the type-checking mode and never returns MISSING in the permissive mode. Instead, it can + * compare values of any two types, according to the rules of the PartiQL type system. For example, 5 = 'a' is false. + * + * For the existing conformance tests, whenever an operand is NULL or MISSING, the output is NULL. This implementation + * follows this. + * + * TODO: The PartiQL Specification needs to clearly define the semantics of MISSING. That being said, this implementation + * follows the existing conformance tests and SQL:1999. + */ @OptIn(PartiQLValueExperimental::class, FnExperimental::class) internal object Fn_EQ__ANY_ANY__BOOL : Fn { private val comparator = PartiQLValue.comparator() - // Since the ANY_ANY function is the catch-all, we must be able to take in nulls. Therefore, isNullCall must be false. override val signature = FnSignature( name = "eq", returns = BOOL, @@ -77,717 +40,18 @@ internal object Fn_EQ__ANY_ANY__BOOL : Fn { FnParameter("lhs", ANY), FnParameter("rhs", ANY), ), - isNullable = false, - isNullCall = false, + isNullable = true, + isNullCall = true, isMissable = false, isMissingCall = false, ) - // TODO ANY, ANY equals not clearly defined at the moment. override fun invoke(args: Array): PartiQLValue { - if (args[0].type == MISSING || args[1].type == MISSING) { - return boolValue(args[0] == args[1]) - } val lhs = args[0] val rhs = args[1] - return boolValue(comparator.compare(lhs, rhs) == 0) - } -} - -@OptIn(PartiQLValueExperimental::class, FnExperimental::class) -internal object Fn_EQ__BOOL_BOOL__BOOL : Fn { - - override val signature = FnSignature( - name = "eq", - returns = BOOL, - parameters = listOf( - FnParameter("lhs", BOOL), - FnParameter("rhs", BOOL), - ), - isNullable = false, - isNullCall = true, - isMissable = false, - isMissingCall = false, - ) - - override fun invoke(args: Array): PartiQLValue { - if (args[0].type == MISSING || args[1].type == MISSING) { - return boolValue(args[0] == args[1]) - } - val lhs = args[0].check() - val rhs = args[1].check() - return boolValue(lhs == rhs) - } -} - -@OptIn(PartiQLValueExperimental::class, FnExperimental::class) -internal object Fn_EQ__INT8_INT8__BOOL : Fn { - - override val signature = FnSignature( - name = "eq", - returns = BOOL, - parameters = listOf( - FnParameter("lhs", INT8), - FnParameter("rhs", INT8), - ), - isNullable = false, - isNullCall = true, - isMissable = false, - isMissingCall = false, - ) - - override fun invoke(args: Array): PartiQLValue { - if (args[0].type == MISSING || args[1].type == MISSING) { - return boolValue(args[0] == args[1]) - } - val lhs = args[0].check() - val rhs = args[1].check() - return boolValue(lhs == rhs) - } -} - -@OptIn(PartiQLValueExperimental::class, FnExperimental::class) -internal object Fn_EQ__INT16_INT16__BOOL : Fn { - - override val signature = FnSignature( - name = "eq", - returns = BOOL, - parameters = listOf( - FnParameter("lhs", INT16), - FnParameter("rhs", INT16), - ), - isNullable = false, - isNullCall = true, - isMissable = false, - isMissingCall = false, - ) - - override fun invoke(args: Array): PartiQLValue { - if (args[0].type == MISSING || args[1].type == MISSING) { - return boolValue(args[0] == args[1]) - } - val lhs = args[0].check() - val rhs = args[1].check() - return boolValue(lhs == rhs) - } -} - -@OptIn(PartiQLValueExperimental::class, FnExperimental::class) -internal object Fn_EQ__INT32_INT32__BOOL : Fn { - - override val signature = FnSignature( - name = "eq", - returns = BOOL, - parameters = listOf( - FnParameter("lhs", INT32), - FnParameter("rhs", INT32), - ), - isNullable = false, - isNullCall = true, - isMissable = false, - isMissingCall = false, - ) - - override fun invoke(args: Array): PartiQLValue { - if (args[0].type == MISSING || args[1].type == MISSING) { - return boolValue(args[0] == args[1]) - } - val lhs = args[0].check() - val rhs = args[1].check() - return boolValue(lhs == rhs) - } -} - -@OptIn(PartiQLValueExperimental::class, FnExperimental::class) -internal object Fn_EQ__INT64_INT64__BOOL : Fn { - - override val signature = FnSignature( - name = "eq", - returns = BOOL, - parameters = listOf( - FnParameter("lhs", INT64), - FnParameter("rhs", INT64), - ), - isNullable = false, - isNullCall = true, - isMissable = false, - isMissingCall = false, - ) - - override fun invoke(args: Array): PartiQLValue { - if (args[0].type == MISSING || args[1].type == MISSING) { - return boolValue(args[0] == args[1]) - } - val lhs = args[0].check() - val rhs = args[1].check() - return boolValue(lhs == rhs) - } -} - -@OptIn(PartiQLValueExperimental::class, FnExperimental::class) -internal object Fn_EQ__INT_INT__BOOL : Fn { - - override val signature = FnSignature( - name = "eq", - returns = BOOL, - parameters = listOf( - FnParameter("lhs", INT), - FnParameter("rhs", INT), - ), - isNullable = false, - isNullCall = true, - isMissable = false, - isMissingCall = false, - ) - - override fun invoke(args: Array): PartiQLValue { - if (args[0].type == MISSING || args[1].type == MISSING) { - return boolValue(args[0] == args[1]) - } - val lhs = args[0].check() - val rhs = args[1].check() - return boolValue(lhs == rhs) - } -} - -@OptIn(PartiQLValueExperimental::class, FnExperimental::class) -internal object Fn_EQ__DECIMAL_DECIMAL__BOOL : Fn { - - override val signature = FnSignature( - name = "eq", - returns = BOOL, - parameters = listOf( - FnParameter("lhs", DECIMAL), - FnParameter("rhs", DECIMAL), - ), - isNullable = false, - isNullCall = true, - isMissable = false, - isMissingCall = false, - ) - - override fun invoke(args: Array): PartiQLValue { - if (args[0].type == MISSING || args[1].type == MISSING) { - return boolValue(args[0] == args[1]) - } - val lhs = args[0].check() - val rhs = args[1].check() - return boolValue(lhs == rhs) - } -} - -@OptIn(PartiQLValueExperimental::class, FnExperimental::class) -internal object Fn_EQ__DECIMAL_ARBITRARY_DECIMAL_ARBITRARY__BOOL : Fn { - - override val signature = FnSignature( - name = "eq", - returns = BOOL, - parameters = listOf( - FnParameter("lhs", DECIMAL_ARBITRARY), - FnParameter("rhs", DECIMAL_ARBITRARY), - ), - isNullable = false, - isNullCall = true, - isMissable = false, - isMissingCall = false, - ) - - override fun invoke(args: Array): PartiQLValue { - if (args[0].type == MISSING || args[1].type == MISSING) { - return boolValue(args[0] == args[1]) - } - val lhs = args[0].check() - val rhs = args[1].check() - return boolValue(lhs == rhs) - } -} - -@OptIn(PartiQLValueExperimental::class, FnExperimental::class) -internal object Fn_EQ__FLOAT32_FLOAT32__BOOL : Fn { - - override val signature = FnSignature( - name = "eq", - returns = BOOL, - parameters = listOf( - FnParameter("lhs", FLOAT32), - FnParameter("rhs", FLOAT32), - ), - isNullable = false, - isNullCall = true, - isMissable = false, - isMissingCall = false, - ) - - override fun invoke(args: Array): PartiQLValue { - if (args[0].type == MISSING || args[1].type == MISSING) { - return boolValue(args[0] == args[1]) - } - val lhs = args[0].check() - val rhs = args[1].check() - return boolValue(lhs == rhs) - } -} - -@OptIn(PartiQLValueExperimental::class, FnExperimental::class) -internal object Fn_EQ__FLOAT64_FLOAT64__BOOL : Fn { - - override val signature = FnSignature( - name = "eq", - returns = BOOL, - parameters = listOf( - FnParameter("lhs", FLOAT64), - FnParameter("rhs", FLOAT64), - ), - isNullable = false, - isNullCall = true, - isMissable = false, - isMissingCall = false, - ) - - override fun invoke(args: Array): PartiQLValue { - if (args[0].type == MISSING || args[1].type == MISSING) { - return boolValue(args[0] == args[1]) - } - val lhs = args[0].check() - val rhs = args[1].check() - return boolValue(lhs == rhs) - } -} - -@OptIn(PartiQLValueExperimental::class, FnExperimental::class) -internal object Fn_EQ__CHAR_CHAR__BOOL : Fn { - - override val signature = FnSignature( - name = "eq", - returns = BOOL, - parameters = listOf( - FnParameter("lhs", CHAR), - FnParameter("rhs", CHAR), - ), - isNullable = false, - isNullCall = true, - isMissable = false, - isMissingCall = false, - ) - - override fun invoke(args: Array): PartiQLValue { - if (args[0].type == MISSING || args[1].type == MISSING) { - return boolValue(args[0] == args[1]) - } - val lhs = args[0].check() - val rhs = args[1].check() - return boolValue(lhs == rhs) - } -} - -@OptIn(PartiQLValueExperimental::class, FnExperimental::class) -internal object Fn_EQ__STRING_STRING__BOOL : Fn { - - override val signature = FnSignature( - name = "eq", - returns = BOOL, - parameters = listOf( - FnParameter("lhs", STRING), - FnParameter("rhs", STRING), - ), - isNullable = false, - isNullCall = true, - isMissable = false, - isMissingCall = false, - ) - - override fun invoke(args: Array): PartiQLValue { - if (args[0].type == MISSING || args[1].type == MISSING) { - return boolValue(args[0] == args[1]) - } - val lhs = args[0].check() - val rhs = args[1].check() - return boolValue(lhs == rhs) - } -} - -@OptIn(PartiQLValueExperimental::class, FnExperimental::class) -internal object Fn_EQ__SYMBOL_SYMBOL__BOOL : Fn { - - override val signature = FnSignature( - name = "eq", - returns = BOOL, - parameters = listOf( - FnParameter("lhs", SYMBOL), - FnParameter("rhs", SYMBOL), - ), - isNullable = false, - isNullCall = true, - isMissable = false, - isMissingCall = false, - ) - - override fun invoke(args: Array): PartiQLValue { - if (args[0].type == MISSING || args[1].type == MISSING) { - return boolValue(args[0] == args[1]) - } - val lhs = args[0].check() - val rhs = args[1].check() - return boolValue(lhs == rhs) - } -} - -@OptIn(PartiQLValueExperimental::class, FnExperimental::class) -internal object Fn_EQ__BINARY_BINARY__BOOL : Fn { - - override val signature = FnSignature( - name = "eq", - returns = BOOL, - parameters = listOf( - FnParameter("lhs", BINARY), - FnParameter("rhs", BINARY), - ), - isNullable = false, - isNullCall = true, - isMissable = false, - isMissingCall = false, - ) - - override fun invoke(args: Array): PartiQLValue { - if (args[0].type == MISSING || args[1].type == MISSING) { - return boolValue(args[0] == args[1]) - } - val lhs = args[0].check() - val rhs = args[1].check() - return boolValue(lhs == rhs) - } -} - -@OptIn(PartiQLValueExperimental::class, FnExperimental::class) -internal object Fn_EQ__BYTE_BYTE__BOOL : Fn { - - override val signature = FnSignature( - name = "eq", - returns = BOOL, - parameters = listOf( - FnParameter("lhs", BYTE), - FnParameter("rhs", BYTE), - ), - isNullable = false, - isNullCall = true, - isMissable = false, - isMissingCall = false, - ) - - override fun invoke(args: Array): PartiQLValue { - if (args[0].type == MISSING || args[1].type == MISSING) { - return boolValue(args[0] == args[1]) - } - val lhs = args[0].check() - val rhs = args[1].check() - return boolValue(lhs == rhs) - } -} - -@OptIn(PartiQLValueExperimental::class, FnExperimental::class) -internal object Fn_EQ__BLOB_BLOB__BOOL : Fn { - - override val signature = FnSignature( - name = "eq", - returns = BOOL, - parameters = listOf( - FnParameter("lhs", BLOB), - FnParameter("rhs", BLOB), - ), - isNullable = false, - isNullCall = true, - isMissable = false, - isMissingCall = false, - ) - - override fun invoke(args: Array): PartiQLValue { - if (args[0].type == MISSING || args[1].type == MISSING) { - return boolValue(args[0] == args[1]) - } - val lhs = args[0].check() - val rhs = args[1].check() - return boolValue(lhs == rhs) - } -} - -@OptIn(PartiQLValueExperimental::class, FnExperimental::class) -internal object Fn_EQ__CLOB_CLOB__BOOL : Fn { - - override val signature = FnSignature( - name = "eq", - returns = BOOL, - parameters = listOf( - FnParameter("lhs", CLOB), - FnParameter("rhs", CLOB), - ), - isNullable = false, - isNullCall = true, - isMissable = false, - isMissingCall = false, - ) - - override fun invoke(args: Array): PartiQLValue { - if (args[0].type == MISSING || args[1].type == MISSING) { - return boolValue(args[0] == args[1]) - } - val lhs = args[0].check() - val rhs = args[1].check() - return boolValue(lhs == rhs) - } -} - -@OptIn(PartiQLValueExperimental::class, FnExperimental::class) -internal object Fn_EQ__DATE_DATE__BOOL : Fn { - - override val signature = FnSignature( - name = "eq", - returns = BOOL, - parameters = listOf( - FnParameter("lhs", DATE), - FnParameter("rhs", DATE), - ), - isNullable = false, - isNullCall = true, - isMissable = false, - isMissingCall = false, - ) - - override fun invoke(args: Array): PartiQLValue { - if (args[0].type == MISSING || args[1].type == MISSING) { - return boolValue(args[0] == args[1]) - } - val lhs = args[0].check() - val rhs = args[1].check() - return boolValue(lhs == rhs) - } -} - -@OptIn(PartiQLValueExperimental::class, FnExperimental::class) -internal object Fn_EQ__TIME_TIME__BOOL : Fn { - - override val signature = FnSignature( - name = "eq", - returns = BOOL, - parameters = listOf( - FnParameter("lhs", TIME), - FnParameter("rhs", TIME), - ), - isNullable = false, - isNullCall = true, - isMissable = false, - isMissingCall = false, - ) - - override fun invoke(args: Array): PartiQLValue { - if (args[0].type == MISSING || args[1].type == MISSING) { - return boolValue(args[0] == args[1]) - } - val lhs = args[0].check() - val rhs = args[1].check() - return boolValue(lhs == rhs) - } -} - -@OptIn(PartiQLValueExperimental::class, FnExperimental::class) -internal object Fn_EQ__TIMESTAMP_TIMESTAMP__BOOL : Fn { - - override val signature = FnSignature( - name = "eq", - returns = BOOL, - parameters = listOf( - FnParameter("lhs", TIMESTAMP), - FnParameter("rhs", TIMESTAMP), - ), - isNullable = false, - isNullCall = true, - isMissable = false, - isMissingCall = false, - ) - - override fun invoke(args: Array): PartiQLValue { - if (args[0].type == MISSING || args[1].type == MISSING) { - return boolValue(args[0] == args[1]) - } - val lhs = args[0].check() - val rhs = args[1].check() - return boolValue(lhs == rhs) - } -} - -@OptIn(PartiQLValueExperimental::class, FnExperimental::class) -internal object Fn_EQ__INTERVAL_INTERVAL__BOOL : Fn { - - override val signature = FnSignature( - name = "eq", - returns = BOOL, - parameters = listOf( - FnParameter("lhs", INTERVAL), - FnParameter("rhs", INTERVAL), - ), - isNullable = false, - isNullCall = true, - isMissable = false, - isMissingCall = false, - ) - - override fun invoke(args: Array): PartiQLValue { - if (args[0].type == MISSING || args[1].type == MISSING) { - return boolValue(args[0] == args[1]) - } - val lhs = args[0].check() - val rhs = args[1].check() - return boolValue(lhs == rhs) - } -} - -@OptIn(PartiQLValueExperimental::class, FnExperimental::class) -internal object Fn_EQ__BAG_BAG__BOOL : Fn { - - override val signature = FnSignature( - name = "eq", - returns = BOOL, - parameters = listOf( - FnParameter("lhs", BAG), - FnParameter("rhs", BAG), - ), - isNullable = false, - isNullCall = true, - isMissable = false, - isMissingCall = false, - ) - - override fun invoke(args: Array): PartiQLValue { - if (args[0].type == MISSING || args[1].type == MISSING) { - return boolValue(args[0] == args[1]) + if (lhs.type == MISSING || rhs.type == MISSING) { + return boolValue(null) } - val lhs = args[0].check>() - val rhs = args[1].check>() - return boolValue(lhs == rhs) - } -} - -@OptIn(PartiQLValueExperimental::class, FnExperimental::class) -internal object Fn_EQ__LIST_LIST__BOOL : Fn { - - override val signature = FnSignature( - name = "eq", - returns = BOOL, - parameters = listOf( - FnParameter("lhs", LIST), - FnParameter("rhs", LIST), - ), - isNullable = false, - isNullCall = true, - isMissable = false, - isMissingCall = false, - ) - - override fun invoke(args: Array): PartiQLValue { - if (args[0].type == MISSING || args[1].type == MISSING) { - return boolValue(args[0] == args[1]) - } - val lhs = args[0].check>() - val rhs = args[1].check>() - return boolValue(lhs == rhs) - } -} - -@OptIn(PartiQLValueExperimental::class, FnExperimental::class) -internal object Fn_EQ__SEXP_SEXP__BOOL : Fn { - - override val signature = FnSignature( - name = "eq", - returns = BOOL, - parameters = listOf( - FnParameter("lhs", SEXP), - FnParameter("rhs", SEXP), - ), - isNullable = false, - isNullCall = true, - isMissable = false, - isMissingCall = false, - ) - - override fun invoke(args: Array): PartiQLValue { - if (args[0].type == MISSING || args[1].type == MISSING) { - return boolValue(args[0] == args[1]) - } - val lhs = args[0].check>() - val rhs = args[1].check>() - return boolValue(lhs == rhs) - } -} - -@OptIn(PartiQLValueExperimental::class, FnExperimental::class) -internal object Fn_EQ__STRUCT_STRUCT__BOOL : Fn { - - override val signature = FnSignature( - name = "eq", - returns = BOOL, - parameters = listOf( - FnParameter("lhs", STRUCT), - FnParameter("rhs", STRUCT), - ), - isNullable = false, - isNullCall = true, - isMissable = false, - isMissingCall = false, - ) - - override fun invoke(args: Array): PartiQLValue { - if (args[0].type == MISSING || args[1].type == MISSING) { - return boolValue(args[0] == args[1]) - } - val lhs = args[0].check>() - val rhs = args[1].check>() - return boolValue(lhs == rhs) - } -} - -@OptIn(PartiQLValueExperimental::class, FnExperimental::class) -internal object Fn_EQ__NULL_NULL__BOOL : Fn { - - override val signature = FnSignature( - name = "eq", - returns = BOOL, - parameters = listOf( - FnParameter("lhs", NULL), - FnParameter("rhs", NULL), - ), - isNullable = false, - isNullCall = true, - isMissable = false, - isMissingCall = false, - ) - - // TODO how does null comparison work? ie null.null == null.null or int8.null == null.null ?? - override fun invoke(args: Array): PartiQLValue { - if (args[0].type == MISSING || args[1].type == MISSING) { - return boolValue(args[0] == args[1]) - } - // According to the conformance tests, NULL = NULL -> NULL - return boolValue(null) - } -} - -@OptIn(PartiQLValueExperimental::class, FnExperimental::class) -internal object Fn_EQ__MISSING_MISSING__BOOL : Fn { - - override val signature = FnSignature( - name = "eq", - returns = BOOL, - parameters = listOf( - FnParameter("lhs", MISSING), - FnParameter("rhs", MISSING), - ), - isNullable = false, - isNullCall = false, - isMissable = false, - isMissingCall = false, - ) - - // TODO how does `=` work with MISSING? As of now, always false. - override fun invoke(args: Array): PartiQLValue { - return boolValue(false) + return boolValue(comparator.compare(lhs, rhs) == 0) } } diff --git a/partiql-spi/src/main/kotlin/org/partiql/spi/connector/sql/builtins/FnIsNull.kt b/partiql-spi/src/main/kotlin/org/partiql/spi/connector/sql/builtins/FnIsNull.kt index 6c1a8ff7c..671364bb3 100644 --- a/partiql-spi/src/main/kotlin/org/partiql/spi/connector/sql/builtins/FnIsNull.kt +++ b/partiql-spi/src/main/kotlin/org/partiql/spi/connector/sql/builtins/FnIsNull.kt @@ -11,6 +11,7 @@ import org.partiql.value.PartiQLValue import org.partiql.value.PartiQLValueExperimental import org.partiql.value.PartiQLValueType.ANY import org.partiql.value.PartiQLValueType.BOOL +import org.partiql.value.PartiQLValueType.MISSING import org.partiql.value.boolValue @OptIn(PartiQLValueExperimental::class, FnExperimental::class) @@ -23,10 +24,13 @@ internal object Fn_IS_NULL__ANY__BOOL : Fn { isNullable = false, isNullCall = false, isMissable = false, - isMissingCall = true, + isMissingCall = false, ) override fun invoke(args: Array): PartiQLValue { + if (args[0].type == MISSING) { + return boolValue(true) + } return boolValue(args[0].isNull) } } diff --git a/partiql-spi/src/main/kotlin/org/partiql/spi/connector/sql/builtins/FnNot.kt b/partiql-spi/src/main/kotlin/org/partiql/spi/connector/sql/builtins/FnNot.kt index 5642e5ae5..935857e92 100644 --- a/partiql-spi/src/main/kotlin/org/partiql/spi/connector/sql/builtins/FnNot.kt +++ b/partiql-spi/src/main/kotlin/org/partiql/spi/connector/sql/builtins/FnNot.kt @@ -11,7 +11,6 @@ import org.partiql.value.BoolValue import org.partiql.value.PartiQLValue import org.partiql.value.PartiQLValueExperimental import org.partiql.value.PartiQLValueType.BOOL -import org.partiql.value.PartiQLValueType.MISSING import org.partiql.value.boolValue import org.partiql.value.check @@ -33,21 +32,3 @@ internal object Fn_NOT__BOOL__BOOL : Fn { return boolValue(value.not()) } } - -@OptIn(PartiQLValueExperimental::class, FnExperimental::class) -internal object Fn_NOT__MISSING__BOOL : Fn { - - override val signature = FnSignature( - name = "not", - returns = BOOL, - parameters = listOf(FnParameter("value", MISSING)), - isNullable = true, - isNullCall = true, - isMissable = false, - isMissingCall = false, - ) - - override fun invoke(args: Array): PartiQLValue { - return boolValue(null) - } -} diff --git a/partiql-types/src/main/kotlin/org/partiql/value/PartiQLValueComparatorInternal.kt b/partiql-types/src/main/kotlin/org/partiql/value/PartiQLValueComparatorInternal.kt index 604d03f19..474c0d4ce 100644 --- a/partiql-types/src/main/kotlin/org/partiql/value/PartiQLValueComparatorInternal.kt +++ b/partiql-types/src/main/kotlin/org/partiql/value/PartiQLValueComparatorInternal.kt @@ -8,9 +8,11 @@ import org.partiql.value.util.isZero @OptIn(PartiQLValueExperimental::class) internal class PartiQLValueComparatorInternal(private val nullsFirst: Boolean) : Comparator { - private val EQUAL = 0 - private val LESS = -1 - private val GREATER = 1 + companion object { + private const val EQUAL = 0 + private const val LESS = -1 + private const val GREATER = 1 + } private fun PartiQLValue.isNullOrMissing(): Boolean = this is NullValue || this is MissingValue || this.isNull private fun PartiQLValue.isLob(): Boolean = this is BlobValue || this is ClobValue diff --git a/test/partiql-tests b/test/partiql-tests index be88ae732..26ab08ec4 160000 --- a/test/partiql-tests +++ b/test/partiql-tests @@ -1 +1 @@ -Subproject commit be88ae732bec0388c88acab108a392f586094fc7 +Subproject commit 26ab08ec49b889550ecca4878e2a338a712dcf35