Simplify construction of new expressions.

Fixes: IS_NOT_NULL() does not always add enough parentheses to the compiled SQL #500
This commit is contained in:
go-jet 2026-02-02 13:18:19 +01:00
parent 95224a793f
commit 4995a90483
26 changed files with 466 additions and 543 deletions

View file

@ -71,7 +71,7 @@ func (a arrayInterfaceImpl[E]) CONCAT_ELEMENT(rhs E) Array[E] {
} }
func (a arrayInterfaceImpl[E]) AT(at IntegerExpression) E { func (a arrayInterfaceImpl[E]) AT(at IntegerExpression) E {
return CastToArrayElemType[E](a.parent, CustomExpression(a.parent, Token("["), at, Token("]"))) return CastToArrayElemType[E](a.parent, AtomicCustomExpression(a.parent, Token("["), at, Token("]")))
} }
type arrayExpressionWrapper[E Expression] struct { type arrayExpressionWrapper[E Expression] struct {
@ -126,12 +126,8 @@ func CastToArrayElemType[E Expression](array Array[E], exp Expression) E {
// ARRAY constructor builds an array value using list of expressions. // ARRAY constructor builds an array value using list of expressions.
func ARRAY[E Expression](elems ...E) Array[E] { func ARRAY[E Expression](elems ...E) Array[E] {
var args = make([]Serializer, len(elems)) return ArrayExp[E](AtomicCustomExpression(Token("ARRAY["), ListSerializer{
for i, each := range elems { Serializers: ToSerializerList(elems),
args[i] = each
}
return ArrayExp[E](CustomExpression(Token("ARRAY["), ListSerializer{
Serializers: args,
Separator: ",", Separator: ",",
}, Token("]"))) }, Token("]")))
} }

View file

@ -6,6 +6,7 @@ import (
func TestBoolExpressionEQ(t *testing.T) { func TestBoolExpressionEQ(t *testing.T) {
assertClauseSerialize(t, table1ColBool.EQ(table2ColBool), "(table1.col_bool = table2.col_bool)") assertClauseSerialize(t, table1ColBool.EQ(table2ColBool), "(table1.col_bool = table2.col_bool)")
assertClauseSerialize(t, Bool(true).EQ(String("foo").IS_NOT_NULL()), `($1 = ($2 IS NOT NULL))`, true, "foo")
} }
func TestBoolExpressionNOT_EQ(t *testing.T) { func TestBoolExpressionNOT_EQ(t *testing.T) {
@ -24,31 +25,31 @@ func TestBoolExpressionIS_NOT_DISTINCT_FROM(t *testing.T) {
} }
func TestBoolExpressionIS_TRUE(t *testing.T) { func TestBoolExpressionIS_TRUE(t *testing.T) {
assertClauseSerialize(t, table1ColBool.IS_TRUE(), "table1.col_bool IS TRUE") assertClauseSerialize(t, table1ColBool.IS_TRUE(), "(table1.col_bool IS TRUE)")
assertClauseSerialize(t, (Int(2).EQ(table1ColInt)).IS_TRUE(), assertClauseSerialize(t, (Int(2).EQ(table1ColInt)).IS_TRUE(),
`($1 = table1.col_int) IS TRUE`, int64(2)) `(($1 = table1.col_int) IS TRUE)`, int64(2))
assertClauseSerialize(t, (Int(2).EQ(table1ColInt)).IS_TRUE().AND(Int(4).EQ(table2ColInt)), assertClauseSerialize(t, (Int(2).EQ(table1ColInt)).IS_TRUE().AND(Int(4).EQ(table2ColInt)),
`(($1 = table1.col_int) IS TRUE AND ($2 = table2.col_int))`, int64(2), int64(4)) `((($1 = table1.col_int) IS TRUE) AND ($2 = table2.col_int))`, int64(2), int64(4))
} }
func TestBoolExpressionIS_NOT_TRUE(t *testing.T) { func TestBoolExpressionIS_NOT_TRUE(t *testing.T) {
assertClauseSerialize(t, table1ColBool.IS_NOT_TRUE(), "table1.col_bool IS NOT TRUE") assertClauseSerialize(t, table1ColBool.IS_NOT_TRUE(), "(table1.col_bool IS NOT TRUE)")
} }
func TestBoolExpressionIS_FALSE(t *testing.T) { func TestBoolExpressionIS_FALSE(t *testing.T) {
assertClauseSerialize(t, table1ColBool.IS_FALSE(), "table1.col_bool IS FALSE") assertClauseSerialize(t, table1ColBool.IS_FALSE(), "(table1.col_bool IS FALSE)")
} }
func TestBoolExpressionIS_NOT_FALSE(t *testing.T) { func TestBoolExpressionIS_NOT_FALSE(t *testing.T) {
assertClauseSerialize(t, table1ColBool.IS_NOT_FALSE(), "table1.col_bool IS NOT FALSE") assertClauseSerialize(t, table1ColBool.IS_NOT_FALSE(), "(table1.col_bool IS NOT FALSE)")
} }
func TestBoolExpressionIS_UNKNOWN(t *testing.T) { func TestBoolExpressionIS_UNKNOWN(t *testing.T) {
assertClauseSerialize(t, table1ColBool.IS_UNKNOWN(), "table1.col_bool IS UNKNOWN") assertClauseSerialize(t, table1ColBool.IS_UNKNOWN(), "(table1.col_bool IS UNKNOWN)")
} }
func TestBoolExpressionIS_NOT_UNKNOWN(t *testing.T) { func TestBoolExpressionIS_NOT_UNKNOWN(t *testing.T) {
assertClauseSerialize(t, table1ColBool.IS_NOT_UNKNOWN(), "table1.col_bool IS NOT UNKNOWN") assertClauseSerialize(t, table1ColBool.IS_NOT_UNKNOWN(), "(table1.col_bool IS NOT UNKNOWN)")
} }
func TestBinaryBoolExpression(t *testing.T) { func TestBinaryBoolExpression(t *testing.T) {
@ -72,5 +73,5 @@ func TestBoolLiteral(t *testing.T) {
func TestBoolExp(t *testing.T) { func TestBoolExp(t *testing.T) {
assertClauseSerialize(t, BoolExp(String("true")), "$1", "true") assertClauseSerialize(t, BoolExp(String("true")), "$1", "true")
assertClauseSerialize(t, BoolExp(String("true")).IS_TRUE(), "$1 IS TRUE", "true") assertClauseSerialize(t, BoolExp(String("true")).IS_TRUE(), "($1 IS TRUE)", "true")
} }

View file

@ -1,22 +1,16 @@
package jet package jet
type enumValue struct {
ExpressionInterfaceImpl
stringInterfaceImpl
name string
}
// NewEnumValue creates new named enum value // NewEnumValue creates new named enum value
func NewEnumValue(name string) StringExpression { func NewEnumValue(name string) StringExpression {
enumValue := &enumValue{name: name} return StringExp(newExpression(
enumValueSerializer{name: name},
enumValue.ExpressionInterfaceImpl.Root = enumValue ))
enumValue.stringInterfaceImpl.root = enumValue
return enumValue
} }
func (e enumValue) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) { type enumValueSerializer struct {
name string
}
func (e enumValueSerializer) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) {
out.insertConstantArgument(e.name) out.insertConstantArgument(e.name)
} }

View file

@ -118,33 +118,29 @@ func (e *ExpressionInterfaceImpl) serializeForOrderBy(statement StatementType, o
e.Root.serialize(statement, out, NoWrap) e.Root.serialize(statement, out, NoWrap)
} }
// Representation of binary operations (e.g. comparisons, arithmetic) type expression struct {
type binaryOperatorExpression struct {
ExpressionInterfaceImpl ExpressionInterfaceImpl
Serializer
}
func newExpression(serializer Serializer) Expression {
expr := &expression{
Serializer: serializer,
}
expr.ExpressionInterfaceImpl.Root = expr
return expr
}
// Representation of binary operations (e.g. comparisons, arithmetic)
type binaryOperatorSerializer struct {
lhs, rhs Serializer lhs, rhs Serializer
additionalParam Serializer additionalParam Serializer
operator string operator string
} }
// NewBinaryOperatorExpression creates new binaryOperatorExpression func (c *binaryOperatorSerializer) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) {
func NewBinaryOperatorExpression(lhs, rhs Serializer, operator string, additionalParam ...Expression) Expression {
binaryExpression := &binaryOperatorExpression{
lhs: lhs,
rhs: rhs,
operator: operator,
}
if len(additionalParam) > 0 {
binaryExpression.additionalParam = additionalParam[0]
}
binaryExpression.ExpressionInterfaceImpl.Root = binaryExpression
return complexExpr(binaryExpression)
}
func (c *binaryOperatorExpression) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) {
if serializeOverride := out.Dialect.OperatorSerializeOverride(c.operator); serializeOverride != nil { if serializeOverride := out.Dialect.OperatorSerializeOverride(c.operator); serializeOverride != nil {
serializeOverrideFunc := serializeOverride(c.lhs, c.rhs, c.additionalParam) serializeOverrideFunc := serializeOverride(c.lhs, c.rhs, c.additionalParam)
serializeOverrideFunc(statement, out, FallTrough(options)...) serializeOverrideFunc(statement, out, FallTrough(options)...)
@ -155,55 +151,48 @@ func (c *binaryOperatorExpression) serialize(statement StatementType, out *SQLBu
} }
} }
type expressionListOperator struct { // NewBinaryOperatorExpression creates new binaryOperatorExpression
ExpressionInterfaceImpl func NewBinaryOperatorExpression(lhs, rhs Serializer, operator string, additionalParam ...Expression) Expression {
return newExpression(optionalWrap(&binaryOperatorSerializer{
lhs: lhs,
rhs: rhs,
additionalParam: OptionalOrDefault(additionalParam, nil),
operator: operator,
}))
}
type serializersWithOperator struct {
operator string operator string
expressions []Expression serializers []Serializer
} }
func newExpressionListOperator(operator string, expressions ...Expression) *expressionListOperator { func (s *serializersWithOperator) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) {
ret := &expressionListOperator{ if len(s.serializers) == 0 {
operator: operator,
expressions: expressions,
}
ret.ExpressionInterfaceImpl.Root = ret
return ret
}
func newBoolExpressionListOperator(operator string, expressions ...BoolExpression) BoolExpression {
return BoolExp(newExpressionListOperator(operator, ToExpressionList(expressions)...))
}
func (elo *expressionListOperator) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) {
if len(elo.expressions) == 0 {
panic("jet: syntax error, expression list empty") panic("jet: syntax error, expression list empty")
} }
shouldWrap := len(elo.expressions) > 1 shouldWrap := len(s.serializers) > 1
if shouldWrap { if shouldWrap {
out.WriteByte('(') out.WriteByte('(')
out.IncreaseIdent(tabSize) out.IncreaseIdent(tabSize)
out.NewLine() out.NewLine()
} }
for i, expression := range elo.expressions { for i, expression := range s.serializers {
if i == 1 { if i == 1 {
out.IncreaseIdent(tabSize) out.IncreaseIdent(tabSize)
} }
if i > 0 { if i > 0 {
out.NewLine() out.NewLine()
out.WriteString(elo.operator) out.WriteString(s.operator)
} }
out.IncreaseIdent(len(elo.operator) + 1) out.IncreaseIdent(len(s.operator) + 1)
expression.serialize(statement, out, FallTrough(options)...) expression.serialize(statement, out, FallTrough(options)...)
out.DecreaseIdent(len(elo.operator) + 1) out.DecreaseIdent(len(s.operator) + 1)
} }
if len(elo.expressions) > 1 { if len(s.serializers) > 1 {
out.DecreaseIdent(tabSize) out.DecreaseIdent(tabSize)
} }
@ -214,130 +203,47 @@ func (elo *expressionListOperator) serialize(statement StatementType, out *SQLBu
} }
} }
// A prefix operator Expression func newBoolExpressionListOperator(operator string, expressions []BoolExpression) BoolExpression {
type prefixExpression struct { return BoolExp(newExpression(&serializersWithOperator{
ExpressionInterfaceImpl operator: operator,
serializers: ToSerializerList(expressions),
expression Expression }))
operator string
} }
func newPrefixOperatorExpression(expression Expression, operator string) Expression { func newPrefixOperatorExpression(expression Expression, operator string) Expression {
prefixExpression := &prefixExpression{ return CustomExpression(Token(operator), expression)
expression: expression,
operator: operator,
}
prefixExpression.ExpressionInterfaceImpl.Root = prefixExpression
return complexExpr(prefixExpression)
} }
func (p *prefixExpression) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) { func newPostfixOperatorExpression(expression Expression, operator string) Expression {
out.WriteString(p.operator) return CustomExpression(expression, Token(operator))
p.expression.serialize(statement, out, FallTrough(options)...)
} }
// A postfix operator Expression type betweenOperatorSerializer struct {
type postfixOpExpression struct {
ExpressionInterfaceImpl
expression Expression
operator string
}
func newPostfixOperatorExpression(expression Expression, operator string) *postfixOpExpression {
postfixOpExpression := &postfixOpExpression{
expression: expression,
operator: operator,
}
postfixOpExpression.ExpressionInterfaceImpl.Root = postfixOpExpression
return postfixOpExpression
}
func (p *postfixOpExpression) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) {
p.expression.serialize(statement, out, FallTrough(options)...)
out.WriteString(p.operator)
}
type betweenOperatorExpression struct {
ExpressionInterfaceImpl
expression Expression expression Expression
notBetween bool notBetween bool
min Expression min Expression
max Expression max Expression
} }
// NewBetweenOperatorExpression creates new BETWEEN operator expression func (b *betweenOperatorSerializer) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) {
func NewBetweenOperatorExpression(expression, min, max Expression, notBetween bool) BoolExpression { b.expression.serialize(statement, out, FallTrough(options)...)
newBetweenOperator := &betweenOperatorExpression{ if b.notBetween {
expression: expression,
notBetween: notBetween,
min: min,
max: max,
}
newBetweenOperator.ExpressionInterfaceImpl.Root = newBetweenOperator
return BoolExp(complexExpr(newBetweenOperator))
}
func (p *betweenOperatorExpression) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) {
p.expression.serialize(statement, out, FallTrough(options)...)
if p.notBetween {
out.WriteString("NOT") out.WriteString("NOT")
} }
out.WriteString("BETWEEN") out.WriteString("BETWEEN")
p.min.serialize(statement, out, FallTrough(options)...) b.min.serialize(statement, out, FallTrough(options)...)
out.WriteString("AND") out.WriteString("AND")
p.max.serialize(statement, out, FallTrough(options)...) b.max.serialize(statement, out, FallTrough(options)...)
} }
type customExpression struct { // NewBetweenOperatorExpression creates new BETWEEN operator expression
ExpressionInterfaceImpl func NewBetweenOperatorExpression(expression, min, max Expression, notBetween bool) BoolExpression {
parts []Serializer return BoolExp(newExpression(
} optionalWrap(&betweenOperatorSerializer{
expression: expression,
func CustomExpression(parts ...Serializer) Expression { notBetween: notBetween,
ret := customExpression{ min: min,
parts: parts, max: max,
} }),
ret.ExpressionInterfaceImpl.Root = &ret ))
return &ret
}
func (c *customExpression) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) {
for _, expression := range c.parts {
expression.serialize(statement, out, options...)
}
}
type complexExpression struct {
ExpressionInterfaceImpl
expressions Expression
}
func complexExpr(expression Expression) Expression {
complexExpression := &complexExpression{expressions: expression}
complexExpression.ExpressionInterfaceImpl.Root = complexExpression
return complexExpression
}
func (s *complexExpression) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) {
if !contains(options, NoWrap) {
out.WriteString("(")
}
s.expressions.serialize(statement, out, options...) // FallTrough here because complexExpression is just a wrapper
if !contains(options, NoWrap) {
out.WriteString(")")
}
}
func wrap(expressions ...Expression) Expression {
return NewFunc("", expressions, nil)
} }

View file

@ -5,13 +5,13 @@ import (
) )
func TestExpressionIS_NULL(t *testing.T) { func TestExpressionIS_NULL(t *testing.T) {
assertClauseSerialize(t, table2Col3.IS_NULL(), "table2.col3 IS NULL") assertClauseSerialize(t, table2Col3.IS_NULL(), "(table2.col3 IS NULL)")
assertClauseSerialize(t, table2Col3.ADD(table2Col3).IS_NULL(), "(table2.col3 + table2.col3) IS NULL") assertClauseSerialize(t, table2Col3.ADD(table2Col3).IS_NULL(), "((table2.col3 + table2.col3) IS NULL)")
} }
func TestExpressionIS_NOT_NULL(t *testing.T) { func TestExpressionIS_NOT_NULL(t *testing.T) {
assertClauseSerialize(t, table2Col3.IS_NOT_NULL(), "table2.col3 IS NOT NULL") assertClauseSerialize(t, table2Col3.IS_NOT_NULL(), "(table2.col3 IS NOT NULL)")
assertClauseSerialize(t, table2Col3.ADD(table2Col3).IS_NOT_NULL(), "(table2.col3 + table2.col3) IS NOT NULL") assertClauseSerialize(t, table2Col3.ADD(table2Col3).IS_NOT_NULL(), "((table2.col3 + table2.col3) IS NOT NULL)")
} }
func TestExpressionIS_DISTINCT_FROM(t *testing.T) { func TestExpressionIS_DISTINCT_FROM(t *testing.T) {

View file

@ -3,13 +3,13 @@ package jet
// AND function adds AND operator between expressions. This function can be used, instead of method AND, // AND function adds AND operator between expressions. This function can be used, instead of method AND,
// to have a better inlining of a complex condition in the Go code and in the generated SQL. // to have a better inlining of a complex condition in the Go code and in the generated SQL.
func AND(expressions ...BoolExpression) BoolExpression { func AND(expressions ...BoolExpression) BoolExpression {
return newBoolExpressionListOperator("AND", expressions...) return newBoolExpressionListOperator("AND", expressions)
} }
// OR function adds OR operator between expressions. This function can be used, instead of method OR, // OR function adds OR operator between expressions. This function can be used, instead of method OR,
// to have a better inlining of a complex condition in the Go code and in the generated SQL. // to have a better inlining of a complex condition in the Go code and in the generated SQL.
func OR(expressions ...BoolExpression) BoolExpression { func OR(expressions ...BoolExpression) BoolExpression {
return newBoolExpressionListOperator("OR", expressions...) return newBoolExpressionListOperator("OR", expressions)
} }
// ------------------ Mathematical functions ---------------// // ------------------ Mathematical functions ---------------//
@ -484,12 +484,12 @@ func REGEXP_LIKE(stringExp StringExpression, pattern StringExpression, matchType
// LOWER_BOUND returns range expressions lower bound. Returns null if range is empty or the requested bound is infinite. // LOWER_BOUND returns range expressions lower bound. Returns null if range is empty or the requested bound is infinite.
func LOWER_BOUND[T Expression](rangeExpression Range[T]) T { func LOWER_BOUND[T Expression](rangeExpression Range[T]) T {
return rangeTypeCaster[T](rangeExpression, NewFunc("LOWER", []Expression{rangeExpression}, nil)) return rangeTypeCaster[T](rangeExpression, newFunc("LOWER", []Expression{rangeExpression}))
} }
// UPPER_BOUND returns range expressions upper bound. Returns null if range is empty or the requested bound is infinite. // UPPER_BOUND returns range expressions upper bound. Returns null if range is empty or the requested bound is infinite.
func UPPER_BOUND[T Expression](rangeExpression Range[T]) T { func UPPER_BOUND[T Expression](rangeExpression Range[T]) T {
return rangeTypeCaster[T](rangeExpression, NewFunc("UPPER", []Expression{rangeExpression}, nil)) return rangeTypeCaster[T](rangeExpression, newFunc("UPPER", []Expression{rangeExpression}))
} }
func rangeTypeCaster[T Expression](rangeExpression Range[T], exp Expression) T { func rangeTypeCaster[T Expression](rangeExpression Range[T], exp Expression) T {
@ -543,7 +543,7 @@ func TO_CHAR(expression Expression, format StringExpression) StringExpression {
// TO_DATE converts string to date using format // TO_DATE converts string to date using format
func TO_DATE(dateStr, format StringExpression) DateExpression { func TO_DATE(dateStr, format StringExpression) DateExpression {
return NewDateFunc("TO_DATE", dateStr, format) return DateExp(newFunc("TO_DATE", []Expression{dateStr, format}))
} }
// TO_NUMBER converts string to numeric using format // TO_NUMBER converts string to numeric using format
@ -560,74 +560,47 @@ func TO_TIMESTAMP(timestampzStr, format StringExpression) TimestampzExpression {
// EXTRACT extracts time component from time expression // EXTRACT extracts time component from time expression
func EXTRACT(field string, from Expression) Expression { func EXTRACT(field string, from Expression) Expression {
return CustomExpression(Token("EXTRACT("), Token(field), Token("FROM"), from, Token(")")) return AtomicCustomExpression(Token("EXTRACT("), Token(field), Token("FROM"), from, Token(")"))
} }
// CURRENT_DATE returns current date // CURRENT_DATE returns current date
func CURRENT_DATE() DateExpression { func CURRENT_DATE() DateExpression {
dateFunc := NewDateFunc("CURRENT_DATE") return DateKeyword("CURRENT_DATE")
dateFunc.noBrackets = true
return dateFunc
} }
// CURRENT_TIME returns current time with time zone // CURRENT_TIME returns current time with time zone
func CURRENT_TIME(precision ...int) TimezExpression { func CURRENT_TIME(precision ...int) TimezExpression {
var timezFunc *timezFunc
if len(precision) > 0 { if len(precision) > 0 {
timezFunc = newTimezFunc("CURRENT_TIME", FixedLiteral(precision[0])) return newTimezFunc("CURRENT_TIME", FixedLiteral(precision[0]))
} else {
timezFunc = newTimezFunc("CURRENT_TIME")
} }
timezFunc.noBrackets = true return TimezKeyword("CURRENT_TIME")
return timezFunc
} }
// CURRENT_TIMESTAMP returns current timestamp with time zone // CURRENT_TIMESTAMP returns current timestamp with time zone
func CURRENT_TIMESTAMP(precision ...int) TimestampzExpression { func CURRENT_TIMESTAMP(precision ...int) TimestampzExpression {
var timestampzFunc *timestampzFunc
if len(precision) > 0 { if len(precision) > 0 {
timestampzFunc = newTimestampzFunc("CURRENT_TIMESTAMP", FixedLiteral(precision[0])) return newTimestampzFunc("CURRENT_TIMESTAMP", FixedLiteral(precision[0]))
} else {
timestampzFunc = newTimestampzFunc("CURRENT_TIMESTAMP")
} }
timestampzFunc.noBrackets = true return TimestampzKeyword("CURRENT_TIMESTAMP")
return timestampzFunc
} }
// LOCALTIME returns local time of day using optional precision // LOCALTIME returns local time of day using optional precision
func LOCALTIME(precision ...int) TimeExpression { func LOCALTIME(precision ...int) TimeExpression {
var timeFunc *timeFunc
if len(precision) > 0 { if len(precision) > 0 {
timeFunc = NewTimeFunc("LOCALTIME", FixedLiteral(precision[0])) return NewTimeFunc("LOCALTIME", FixedLiteral(precision[0]))
} else {
timeFunc = NewTimeFunc("LOCALTIME")
} }
timeFunc.noBrackets = true return TimeKeyword("LOCALTIME")
return timeFunc
} }
// LOCALTIMESTAMP returns current date and time using optional precision // LOCALTIMESTAMP returns current date and time using optional precision
func LOCALTIMESTAMP(precision ...int) TimestampExpression { func LOCALTIMESTAMP(precision ...int) TimestampExpression {
var timestampFunc *timestampFunc
if len(precision) > 0 { if len(precision) > 0 {
timestampFunc = NewTimestampFunc("LOCALTIMESTAMP", FixedLiteral(precision[0])) return NewTimestampFunc("LOCALTIMESTAMP", FixedLiteral(precision[0]))
} else {
timestampFunc = NewTimestampFunc("LOCALTIMESTAMP")
} }
return TimestampKeyword("LOCALTIMESTAMP")
timestampFunc.noBrackets = true
return timestampFunc
} }
// NOW returns current date and time // NOW returns current date and time
@ -641,74 +614,59 @@ func NOW() TimestampzExpression {
func COALESCE(value Expression, values ...Expression) Expression { func COALESCE(value Expression, values ...Expression) Expression {
var allValues = []Expression{value} var allValues = []Expression{value}
allValues = append(allValues, values...) allValues = append(allValues, values...)
return NewFunc("COALESCE", allValues, nil) return newFunc("COALESCE", allValues)
} }
// NULLIF function returns a null value if value1 equals value2; otherwise it returns value1. // NULLIF function returns a null value if value1 equals value2; otherwise it returns value1.
func NULLIF(value1, value2 Expression) Expression { func NULLIF(value1, value2 Expression) Expression {
return NewFunc("NULLIF", []Expression{value1, value2}, nil) return newFunc("NULLIF", []Expression{value1, value2})
} }
// GREATEST selects the largest value from a list of expressions // GREATEST selects the largest value from a list of expressions
func GREATEST(value Expression, values ...Expression) Expression { func GREATEST(value Expression, values ...Expression) Expression {
var allValues = []Expression{value} var allValues = []Expression{value}
allValues = append(allValues, values...) allValues = append(allValues, values...)
return NewFunc("GREATEST", allValues, nil) return newFunc("GREATEST", allValues)
} }
// LEAST selects the smallest value from a list of expressions // LEAST selects the smallest value from a list of expressions
func LEAST(value Expression, values ...Expression) Expression { func LEAST(value Expression, values ...Expression) Expression {
var allValues = []Expression{value} var allValues = []Expression{value}
allValues = append(allValues, values...) allValues = append(allValues, values...)
return NewFunc("LEAST", allValues, nil) return newFunc("LEAST", allValues)
} }
//--------------------------------------------------------------------// //--------------------------------------------------------------------//
type funcExpressionImpl struct { // newFunc creates new function with name and expressions parameters
ExpressionInterfaceImpl func newFunc(name string, expressions []Expression) Expression {
return newExpression(&funcSerializer{
name: name,
parameters: expressions,
})
}
type funcSerializer struct {
name string name string
parameters parametersSerializer parameters parametersSerializer
noBrackets bool
} }
// NewFunc creates new function with name and expressions parameters func (f *funcSerializer) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) {
func NewFunc(name string, expressions []Expression, root Expression) *funcExpressionImpl {
funcExp := &funcExpressionImpl{
name: name,
parameters: parametersSerializer(expressions),
}
if root != nil {
funcExp.ExpressionInterfaceImpl.Root = root
} else {
funcExp.ExpressionInterfaceImpl.Root = funcExp
}
return funcExp
}
func (f *funcExpressionImpl) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) {
if serializeOverride := out.Dialect.FunctionSerializeOverride(f.name); serializeOverride != nil { if serializeOverride := out.Dialect.FunctionSerializeOverride(f.name); serializeOverride != nil {
serializeOverrideFunc := serializeOverride(ExpressionListToSerializerList(f.parameters)...) serializeOverrideFunc := serializeOverride(ToSerializerList(f.parameters)...)
serializeOverrideFunc(statement, out, FallTrough(options)...) serializeOverrideFunc(statement, out, FallTrough(options)...)
return return
} }
addBrackets := !f.noBrackets || len(f.parameters) > 0 out.WriteString(f.name + "(")
if addBrackets {
out.WriteString(f.name + "(")
} else {
out.WriteString(f.name)
}
f.parameters.serialize(statement, out, options...) f.parameters.serialize(statement, out, options...)
if addBrackets { out.WriteString(")")
out.WriteString(")") }
}
func newBoolFunc(name string, expressions ...Expression) BoolExpression {
return BoolExp(newFunc(name, expressions))
} }
type parametersSerializer []Expression type parametersSerializer []Expression
@ -730,208 +688,83 @@ func (p parametersSerializer) serialize(statement StatementType, out *SQLBuilder
// NewFloatWindowFunc creates new float function with name and expressions // NewFloatWindowFunc creates new float function with name and expressions
func newWindowFunc(name string, expressions ...Expression) windowExpression { func newWindowFunc(name string, expressions ...Expression) windowExpression {
newFun := NewFunc(name, expressions, nil) return newWindowExpression(newFunc(name, expressions))
windowExpr := newWindowExpression(newFun)
newFun.ExpressionInterfaceImpl.Root = windowExpr
return windowExpr
}
type boolFunc struct {
funcExpressionImpl
boolInterfaceImpl
}
func newBoolFunc(name string, expressions ...Expression) BoolExpression {
boolFunc := &boolFunc{}
boolFunc.funcExpressionImpl = *NewFunc(name, expressions, boolFunc)
boolFunc.boolInterfaceImpl.root = boolFunc
boolFunc.ExpressionInterfaceImpl.Root = boolFunc
return boolFunc
} }
// NewFloatWindowFunc creates new float function with name and expressions // NewFloatWindowFunc creates new float function with name and expressions
func newBoolWindowFunc(name string, expressions ...Expression) boolWindowExpression { func newBoolWindowFunc(name string, expressions ...Expression) boolWindowExpression {
boolFunc := &boolFunc{} return newBoolWindowExpression(BoolExp(newFunc(name, expressions)))
boolFunc.funcExpressionImpl = *NewFunc(name, expressions, boolFunc)
intWindowFunc := newBoolWindowExpression(boolFunc)
boolFunc.boolInterfaceImpl.root = intWindowFunc
boolFunc.ExpressionInterfaceImpl.Root = intWindowFunc
return intWindowFunc
}
type floatFunc struct {
funcExpressionImpl
floatInterfaceImpl
} }
// NewFloatFunc creates new float function with name and expressions // NewFloatFunc creates new float function with name and expressions
func NewFloatFunc(name string, expressions ...Expression) FloatExpression { func NewFloatFunc(name string, expressions ...Expression) FloatExpression {
floatFunc := &floatFunc{} return FloatExp(newFunc(name, expressions))
floatFunc.funcExpressionImpl = *NewFunc(name, expressions, floatFunc)
floatFunc.floatInterfaceImpl.root = floatFunc
return floatFunc
} }
// NewFloatWindowFunc creates new float function with name and expressions // NewFloatWindowFunc creates new float function with name and expressions
func NewFloatWindowFunc(name string, expressions ...Expression) floatWindowExpression { func NewFloatWindowFunc(name string, expressions ...Expression) floatWindowExpression {
floatFunc := &floatFunc{} return newFloatWindowExpression(FloatExp(newFunc(name, expressions)))
floatFunc.funcExpressionImpl = *NewFunc(name, expressions, floatFunc)
floatWindowFunc := newFloatWindowExpression(floatFunc)
floatFunc.floatInterfaceImpl.root = floatWindowFunc
floatFunc.ExpressionInterfaceImpl.Root = floatWindowFunc
return floatWindowFunc
}
type integerFunc struct {
funcExpressionImpl
integerInterfaceImpl
} }
func newIntegerFunc(name string, expressions ...Expression) IntegerExpression { func newIntegerFunc(name string, expressions ...Expression) IntegerExpression {
intFunc := &integerFunc{} return IntExp(newFunc(name, expressions))
intFunc.funcExpressionImpl = *NewFunc(name, expressions, intFunc)
intFunc.integerInterfaceImpl.root = intFunc
return intFunc
} }
// NewFloatWindowFunc creates new float function with name and expressions // NewFloatWindowFunc creates new float function with name and expressions
func newIntegerWindowFunc(name string, expressions ...Expression) integerWindowExpression { func newIntegerWindowFunc(name string, expressions ...Expression) integerWindowExpression {
integerFunc := &integerFunc{} return newIntegerWindowExpression(IntExp(newFunc(name, expressions)))
integerFunc.funcExpressionImpl = *NewFunc(name, expressions, integerFunc)
intWindowFunc := newIntegerWindowExpression(integerFunc)
integerFunc.integerInterfaceImpl.root = intWindowFunc
integerFunc.ExpressionInterfaceImpl.Root = intWindowFunc
return intWindowFunc
}
type stringFunc struct {
funcExpressionImpl
stringInterfaceImpl
} }
// NewStringFunc creates new string function with name and expression parameters // NewStringFunc creates new string function with name and expression parameters
func NewStringFunc(name string, expressions ...Expression) StringExpression { func NewStringFunc(name string, expressions ...Expression) StringExpression {
stringFunc := &stringFunc{} return StringExp(newFunc(name, expressions))
stringFunc.funcExpressionImpl = *NewFunc(name, expressions, stringFunc)
stringFunc.stringInterfaceImpl.root = stringFunc
return stringFunc
}
type dateFunc struct {
funcExpressionImpl
dateInterfaceImpl
}
// NewDateFunc creates new date function with name and expression parameters
func NewDateFunc(name string, expressions ...Expression) *dateFunc {
dateFunc := &dateFunc{}
dateFunc.funcExpressionImpl = *NewFunc(name, expressions, dateFunc)
dateFunc.dateInterfaceImpl.root = dateFunc
return dateFunc
}
type timeFunc struct {
funcExpressionImpl
timeInterfaceImpl
} }
// NewTimeFunc creates new time function with name and expression parameters // NewTimeFunc creates new time function with name and expression parameters
func NewTimeFunc(name string, expressions ...Expression) *timeFunc { func NewTimeFunc(name string, expressions ...Expression) TimeExpression {
timeFun := &timeFunc{} return TimeExp(newFunc(name, expressions))
timeFun.funcExpressionImpl = *NewFunc(name, expressions, timeFun)
timeFun.timeInterfaceImpl.root = timeFun
return timeFun
} }
type timezFunc struct { func newTimezFunc(name string, expressions ...Expression) TimezExpression {
funcExpressionImpl return TimezExp(newFunc(name, expressions))
timezInterfaceImpl
}
func newTimezFunc(name string, expressions ...Expression) *timezFunc {
timezFun := &timezFunc{}
timezFun.funcExpressionImpl = *NewFunc(name, expressions, timezFun)
timezFun.timezInterfaceImpl.root = timezFun
return timezFun
}
type timestampFunc struct {
funcExpressionImpl
timestampInterfaceImpl
} }
// NewTimestampFunc creates new timestamp function with name and expressions // NewTimestampFunc creates new timestamp function with name and expressions
func NewTimestampFunc(name string, expressions ...Expression) *timestampFunc { func NewTimestampFunc(name string, expressions ...Expression) TimestampExpression {
timestampFunc := &timestampFunc{} return TimestampExp(newFunc(name, expressions))
timestampFunc.funcExpressionImpl = *NewFunc(name, expressions, timestampFunc)
timestampFunc.timestampInterfaceImpl.root = timestampFunc
return timestampFunc
} }
type timestampzFunc struct { func newTimestampzFunc(name string, expressions ...Expression) TimestampzExpression {
funcExpressionImpl return TimestampzExp(newFunc(name, expressions))
timestampzInterfaceImpl
}
func newTimestampzFunc(name string, expressions ...Expression) *timestampzFunc {
timestampzFunc := &timestampzFunc{}
timestampzFunc.funcExpressionImpl = *NewFunc(name, expressions, timestampzFunc)
timestampzFunc.timestampzInterfaceImpl.root = timestampzFunc
return timestampzFunc
} }
// Func can be used to call custom or unsupported database functions. // Func can be used to call custom or unsupported database functions.
func Func(name string, expressions ...Expression) Expression { func Func(name string, expressions ...Expression) Expression {
return NewFunc(name, expressions, nil) return newFunc(name, expressions)
} }
func NumRange(lowNum, highNum NumericExpression, bounds ...StringExpression) Range[NumericExpression] { func NumRange(lowNum, highNum NumericExpression, bounds ...StringExpression) Range[NumericExpression] {
return NumRangeExp(NewFunc("numrange", rangeFuncParamCombiner(lowNum, highNum, bounds...), nil)) return NumRangeExp(newFunc("numrange", rangeFuncParamCombiner(lowNum, highNum, bounds...)))
} }
func Int4Range(lowNum, highNum IntegerExpression, bounds ...StringExpression) Range[Int4Expression] { func Int4Range(lowNum, highNum IntegerExpression, bounds ...StringExpression) Range[Int4Expression] {
return Int4RangeExp(NewFunc("int4range", rangeFuncParamCombiner(lowNum, highNum, bounds...), nil)) return Int4RangeExp(newFunc("int4range", rangeFuncParamCombiner(lowNum, highNum, bounds...)))
} }
func Int8Range(lowNum, highNum Int8Expression, bounds ...StringExpression) Range[Int8Expression] { func Int8Range(lowNum, highNum Int8Expression, bounds ...StringExpression) Range[Int8Expression] {
return Int8RangeExp(NewFunc("int8range", rangeFuncParamCombiner(lowNum, highNum, bounds...), nil)) return Int8RangeExp(newFunc("int8range", rangeFuncParamCombiner(lowNum, highNum, bounds...)))
} }
func TsRange(lowTs, highTs TimestampExpression, bounds ...StringExpression) Range[TimestampExpression] { func TsRange(lowTs, highTs TimestampExpression, bounds ...StringExpression) Range[TimestampExpression] {
return TsRangeExp(NewFunc("tsrange", rangeFuncParamCombiner(lowTs, highTs, bounds...), nil)) return TsRangeExp(newFunc("tsrange", rangeFuncParamCombiner(lowTs, highTs, bounds...)))
} }
func TstzRange(lowTs, highTs TimestampzExpression, bounds ...StringExpression) Range[TimestampzExpression] { func TstzRange(lowTs, highTs TimestampzExpression, bounds ...StringExpression) Range[TimestampzExpression] {
return TstzRangeExp(NewFunc("tstzrange", rangeFuncParamCombiner(lowTs, highTs, bounds...), nil)) return TstzRangeExp(newFunc("tstzrange", rangeFuncParamCombiner(lowTs, highTs, bounds...)))
} }
func DateRange(lowTs, highTs DateExpression, bounds ...StringExpression) Range[DateExpression] { func DateRange(lowTs, highTs DateExpression, bounds ...StringExpression) Range[DateExpression] {
return DateRangeExp(NewFunc("daterange", rangeFuncParamCombiner(lowTs, highTs, bounds...), nil)) return DateRangeExp(newFunc("daterange", rangeFuncParamCombiner(lowTs, highTs, bounds...)))
} }
func rangeFuncParamCombiner(low, high Expression, bounds ...StringExpression) []Expression { func rangeFuncParamCombiner(low, high Expression, bounds ...StringExpression) []Expression {
@ -941,3 +774,23 @@ func rangeFuncParamCombiner(low, high Expression, bounds ...StringExpression) []
} }
return exp return exp
} }
func TimeKeyword(name string) TimeExpression {
return TimeExp(newExpression(Keyword(name)))
}
func TimezKeyword(name string) TimezExpression {
return TimezExp(newExpression(Keyword(name)))
}
func TimestampKeyword(name string) TimestampExpression {
return TimestampExp(newExpression(Keyword(name)))
}
func TimestampzKeyword(name string) TimestampzExpression {
return TimestampzExp(newExpression(Keyword(name)))
}
func DateKeyword(name string) DateExpression {
return DateExp(newExpression(Keyword(name)))
}

View file

@ -6,7 +6,7 @@ import (
func TestAND(t *testing.T) { func TestAND(t *testing.T) {
assertClauseSerializeErr(t, AND(), "jet: syntax error, expression list empty") assertClauseSerializeErr(t, AND(), "jet: syntax error, expression list empty")
assertClauseSerialize(t, AND(table1ColInt.IS_NULL()), `table1.col_int IS NULL`) // IS NULL doesn't add parenthesis assertClauseSerialize(t, AND(table1ColInt.IS_NULL()), `(table1.col_int IS NULL)`) // IS NULL doesn't add parenthesis
assertClauseSerialize(t, AND(table1ColInt.LT(Int(11))), `(table1.col_int < $1)`, int64(11)) assertClauseSerialize(t, AND(table1ColInt.LT(Int(11))), `(table1.col_int < $1)`, int64(11))
assertClauseSerialize(t, AND(table1ColInt.GT(Int(11)), table1ColFloat.EQ(Float(0))), assertClauseSerialize(t, AND(table1ColInt.GT(Int(11)), table1ColFloat.EQ(Float(0))),
`( `(
@ -17,7 +17,7 @@ func TestAND(t *testing.T) {
func TestOR(t *testing.T) { func TestOR(t *testing.T) {
assertClauseSerializeErr(t, OR(), "jet: syntax error, expression list empty") assertClauseSerializeErr(t, OR(), "jet: syntax error, expression list empty")
assertClauseSerialize(t, OR(table1ColInt.IS_NULL()), `table1.col_int IS NULL`) // IS NULL doesn't add parenthesis assertClauseSerialize(t, OR(table1ColInt.IS_NULL()), `(table1.col_int IS NULL)`) // IS NULL doesn't add parenthesis
assertClauseSerialize(t, OR(table1ColInt.LT(Int(11))), `(table1.col_int < $1)`, int64(11)) assertClauseSerialize(t, OR(table1ColInt.LT(Int(11))), `(table1.col_int < $1)`, int64(11))
assertClauseSerialize(t, OR(table1ColInt.GT(Int(11)), table1ColFloat.EQ(Float(0))), assertClauseSerialize(t, OR(table1ColInt.GT(Int(11)), table1ColFloat.EQ(Float(0))),
`( `(
@ -205,7 +205,7 @@ func TestFunc(t *testing.T) {
func Test_rangePointCaster(t *testing.T) { func Test_rangePointCaster(t *testing.T) {
mainRange := Int8Range(Int8(10), Int8(12)) mainRange := Int8Range(Int8(10), Int8(12))
exp := NewFunc("UPPER", []Expression{mainRange}, nil) exp := newFunc("UPPER", []Expression{mainRange})
got := rangeTypeCaster(mainRange, exp) got := rangeTypeCaster(mainRange, exp)
_, ok := got.(IntegerExpression) _, ok := got.(IntegerExpression)

View file

@ -74,7 +74,7 @@ func newRowExpression(name string, dialect Dialect, expressions ...Expression) R
ret := &rowExpressionWrapper{} ret := &rowExpressionWrapper{}
ret.rowInterfaceImpl.root = ret ret.rowInterfaceImpl.root = ret
ret.Expression = NewFunc(name, expressions, ret) ret.Expression = newFunc(name, expressions)
ret.dialect = dialect ret.dialect = dialect
ret.expressions = expressions ret.expressions = expressions

View file

@ -1,5 +1,7 @@
package jet package jet
import "slices"
// SerializeOption type // SerializeOption type
type SerializeOption int type SerializeOption int
@ -73,6 +75,12 @@ func FallTrough(options []SerializeOption) []SerializeOption {
return ret return ret
} }
func without(options []SerializeOption, option SerializeOption) []SerializeOption {
return slices.DeleteFunc(options, func(elem SerializeOption) bool {
return elem == option
})
}
// ListSerializer serializes list of serializers with separator // ListSerializer serializes list of serializers with separator
type ListSerializer struct { type ListSerializer struct {
Serializers []Serializer Serializers []Serializer
@ -109,3 +117,68 @@ type Token string
func (t Token) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) { func (t Token) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) {
out.WriteString(string(t)) out.WriteString(string(t))
} }
// CustomExpression creates new custom expression. When serialized may require parentheses
// depending on context.
func CustomExpression(parts ...Serializer) Expression {
return newExpression(optionalWrap(&customSerializer{
parts: parts,
}))
}
type customSerializer struct {
parts []Serializer
}
func (c *customSerializer) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) {
for _, expression := range c.parts {
expression.serialize(statement, out, options...)
}
}
type optionalWrapSerializer struct {
serializer []Serializer
}
func optionalWrap(serializer ...Serializer) Serializer {
return &optionalWrapSerializer{serializer: serializer}
}
func (s *optionalWrapSerializer) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) {
if !contains(options, NoWrap) {
out.WriteString("(")
}
for _, ser := range s.serializer {
ser.serialize(statement, out, without(options, NoWrap)...)
}
if !contains(options, NoWrap) {
out.WriteString(")")
}
}
// AtomicCustomExpression creates new custom expression. When serialized does not require parentheses.
func AtomicCustomExpression(parts ...Serializer) Expression {
return newExpression(noWrap(&customSerializer{
parts: parts,
}))
}
type noWrapSerializer struct {
serializer []Serializer
}
func noWrap(serializer ...Serializer) Serializer {
return &noWrapSerializer{serializer: serializer}
}
func (s *noWrapSerializer) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) {
for _, ser := range s.serializer {
ser.serialize(statement, out, without(options, NoWrap)...)
}
}
func wrap(expressions ...Expression) Expression {
return newFunc("", expressions)
}

View file

@ -121,12 +121,12 @@ func SerializeColumnExpressionNames(columns []ColumnExpression, out *SQLBuilder)
} }
} }
// ExpressionListToSerializerList converts list of expressions to list of serializers // ToSerializerList converts list of expressions to list of serializers
func ExpressionListToSerializerList(expressions []Expression) []Serializer { func ToSerializerList[T Serializer](elems []T) []Serializer {
var ret []Serializer ret := make([]Serializer, len(elems))
for _, expr := range expressions { for i, ser := range elems {
ret = append(ret, expr) ret[i] = ser
} }
return ret return ret
@ -134,10 +134,10 @@ func ExpressionListToSerializerList(expressions []Expression) []Serializer {
// ToExpressionList converts list of any expressions to list of expressions // ToExpressionList converts list of any expressions to list of expressions
func ToExpressionList[T Expression](expressions []T) []Expression { func ToExpressionList[T Expression](expressions []T) []Expression {
var ret []Expression ret := make([]Expression, len(expressions))
for _, expression := range expressions { for i, expr := range expressions {
ret = append(ret, expression) ret[i] = expr
} }
return ret return ret
@ -145,10 +145,10 @@ func ToExpressionList[T Expression](expressions []T) []Expression {
// ColumnListToProjectionList func // ColumnListToProjectionList func
func ColumnListToProjectionList(columns []ColumnExpression) []Projection { func ColumnListToProjectionList(columns []ColumnExpression) []Projection {
var ret []Projection ret := make([]Projection, len(columns))
for _, column := range columns { for i, column := range columns {
ret = append(ret, column) ret[i] = column
} }
return ret return ret
@ -252,11 +252,11 @@ func OptionalOrDefaultString(defaultStr string, str ...string) string {
return defaultStr return defaultStr
} }
// OptionalOrDefaultExpression will return first value from variable argument list expression or // OptionalOrDefault will return first value from variable argument list expression or
// defaultExpression if variable argument list is empty // defaultExpression if variable argument list is empty
func OptionalOrDefaultExpression(defaultExpression Expression, expression ...Expression) Expression { func OptionalOrDefault(expressions []Expression, defaultExpression Expression) Expression {
if len(expression) > 0 { if len(expressions) > 0 {
return expression[0] return expressions[0]
} }
return defaultExpression return defaultExpression
@ -292,3 +292,13 @@ func joinAlias(tableAlias, columnAlias string) string {
} }
return strings.TrimRight(tableAlias, ".*") + "." + columnAlias return strings.TrimRight(tableAlias, ".*") + "." + columnAlias
} }
func optional[T any](value []T) T {
if len(value) > 0 {
return value[0]
}
var def T
return def
}

View file

@ -12,11 +12,12 @@ func TestOptionalOrDefaultString(t *testing.T) {
} }
func TestOptionalOrDefaultExpression(t *testing.T) { func TestOptionalOrDefaultExpression(t *testing.T) {
defaultExpression := table2ColFloat defaultExpression := []Expression{table2ColFloat}
optionalExpression := table1Col1 optionalExpression := table1Col1
require.Equal(t, OptionalOrDefaultExpression(defaultExpression), defaultExpression) require.Equal(t, OptionalOrDefault(defaultExpression, nil), table2ColFloat)
require.Equal(t, OptionalOrDefaultExpression(defaultExpression, optionalExpression), optionalExpression) require.Equal(t, OptionalOrDefault(defaultExpression, optionalExpression), table2ColFloat)
require.Equal(t, OptionalOrDefault(nil, optionalExpression), table1Col1)
} }
func TestJoinAlias(t *testing.T) { func TestJoinAlias(t *testing.T) {

View file

@ -28,12 +28,13 @@ type windowExpression interface {
OVER(window ...Window) Expression OVER(window ...Window) Expression
} }
func newWindowExpression(Exp Expression) windowExpression { func newWindowExpression(exp Expression) windowExpression {
newExp := &windowExpressionImpl{ newExp := &windowExpressionImpl{
Expression: Exp, Expression: exp,
} }
newExp.commonWindowImpl.expression = Exp newExp.commonWindowImpl.expression = exp
exp.setRoot(newExp)
return newExp return newExp
} }
@ -65,6 +66,7 @@ func newFloatWindowExpression(floatExp FloatExpression) floatWindowExpression {
} }
newExp.commonWindowImpl.expression = floatExp newExp.commonWindowImpl.expression = floatExp
floatExp.setRoot(newExp)
return newExp return newExp
} }
@ -96,6 +98,7 @@ func newIntegerWindowExpression(intExp IntegerExpression) integerWindowExpressio
} }
newExp.commonWindowImpl.expression = intExp newExp.commonWindowImpl.expression = intExp
intExp.setRoot(newExp)
return newExp return newExp
} }
@ -127,6 +130,7 @@ func newBoolWindowExpression(boolExp BoolExpression) boolWindowExpression {
} }
newExp.commonWindowImpl.expression = boolExp newExp.commonWindowImpl.expression = boolExp
boolExp.setRoot(newExp)
return newExp return newExp
} }

View file

@ -42,11 +42,11 @@ func newDialect() jet.Dialect {
// CustomExpression used bellow (instead DATE_FORMAT function) so that only expr is parametrized // CustomExpression used bellow (instead DATE_FORMAT function) so that only expr is parametrized
case TimestampExpression: case TimestampExpression:
return CustomExpression(Token("DATE_FORMAT("), e, Token(",'%Y-%m-%dT%H:%i:%s.%fZ')")) return jet.AtomicCustomExpression(Token("DATE_FORMAT("), e, Token(",'%Y-%m-%dT%H:%i:%s.%fZ')"))
case TimeExpression: case TimeExpression:
return CustomExpression(Token("CONCAT('0000-01-01T', DATE_FORMAT("), e, Token(",'%H:%i:%s.%fZ'))")) return jet.AtomicCustomExpression(Token("CONCAT('0000-01-01T', DATE_FORMAT("), e, Token(",'%H:%i:%s.%fZ'))"))
case DateExpression: case DateExpression:
return CustomExpression(Token("CONCAT(DATE_FORMAT("), e, Token(",'%Y-%m-%d')"), Token(", 'T00:00:00Z')")) return jet.AtomicCustomExpression(Token("CONCAT(DATE_FORMAT("), e, Token(",'%Y-%m-%d')"), Token(", 'T00:00:00Z')"))
case BoolExpression: case BoolExpression:
return CustomExpression(e, Token(" = 1")) return CustomExpression(e, Token(" = 1"))
} }

View file

@ -2,10 +2,11 @@ package mysql
import ( import (
"fmt" "fmt"
"github.com/go-jet/jet/v2/internal/utils/datetime"
"regexp" "regexp"
"time" "time"
"github.com/go-jet/jet/v2/internal/utils/datetime"
"github.com/go-jet/jet/v2/internal/jet" "github.com/go-jet/jet/v2/internal/jet"
) )
@ -98,7 +99,7 @@ func INTERVAL(value interface{}, unitType unitType) Interval {
// INTERVALe creates new temporal interval from expresion and unit type. // INTERVALe creates new temporal interval from expresion and unit type.
func INTERVALe(expr Expression, unitType unitType) Interval { func INTERVALe(expr Expression, unitType unitType) Interval {
return jet.IntervalExp(CustomExpression(Token("INTERVAL"), expr, Token(unitType))) return jet.IntervalExp(jet.AtomicCustomExpression(Token("INTERVAL"), expr, Token(unitType)))
} }
// INTERVALd creates new temporal interval from time.Duration // INTERVALd creates new temporal interval from time.Duration

View file

@ -3,8 +3,9 @@ package postgres
import ( import (
"encoding/hex" "encoding/hex"
"fmt" "fmt"
"github.com/go-jet/jet/v2/internal/jet"
"strconv" "strconv"
"github.com/go-jet/jet/v2/internal/jet"
) )
// Dialect is implementation of postgres dialect for SQL Builder serialisation. // Dialect is implementation of postgres dialect for SQL Builder serialisation.
@ -42,7 +43,7 @@ func newDialect() jet.Dialect {
case TimezExpression: case TimezExpression:
return CustomExpression(Token("'0000-01-01T' || to_char('2000-10-10'::date + "), e, Token(`, 'HH24:MI:SS.USTZH:TZM')`)) return CustomExpression(Token("'0000-01-01T' || to_char('2000-10-10'::date + "), e, Token(`, 'HH24:MI:SS.USTZH:TZM')`))
case TimestampExpression: case TimestampExpression:
return CustomExpression(Token("to_char("), e, Token(`, 'YYYY-MM-DD"T"HH24:MI:SS.USZ')`)) return jet.AtomicCustomExpression(Token("to_char("), e, Token(`, 'YYYY-MM-DD"T"HH24:MI:SS.USZ')`))
case DateExpression: case DateExpression:
return CustomExpression(Token("to_char("), e, Token(`::timestamp, 'YYYY-MM-DD') || 'T00:00:00Z'`)) return CustomExpression(Token("to_char("), e, Token(`::timestamp, 'YYYY-MM-DD') || 'T00:00:00Z'`))
} }

View file

@ -552,10 +552,10 @@ func DATE_TRUNC(field unit, source Expression, timezone ...string) TimestampExpr
// GENERATE_SERIES generates a series of values from start to stop, with a step size of step. // GENERATE_SERIES generates a series of values from start to stop, with a step size of step.
func GENERATE_SERIES(start Expression, stop Expression, step ...Expression) Expression { func GENERATE_SERIES(start Expression, stop Expression, step ...Expression) Expression {
if len(step) > 0 { if len(step) > 0 {
return jet.NewFunc("GENERATE_SERIES", []Expression{start, stop, step[0]}, nil) return Func("GENERATE_SERIES", start, stop, step[0])
} }
return jet.NewFunc("GENERATE_SERIES", []Expression{start, stop}, nil) return Func("GENERATE_SERIES", start, stop)
} }
// --------------- Conditional Expressions Functions -------------// // --------------- Conditional Expressions Functions -------------//

View file

@ -2,10 +2,12 @@ package postgres
import ( import (
"fmt" "fmt"
"github.com/go-jet/jet/v2/internal/utils/datetime"
"strconv" "strconv"
"strings" "strings"
"time" "time"
"github.com/go-jet/jet/v2/internal/jet"
"github.com/go-jet/jet/v2/internal/utils/datetime"
) )
type quantityAndUnit = float64 type quantityAndUnit = float64
@ -44,7 +46,7 @@ func INTERVAL(quantityAndUnit ...quantityAndUnit) IntervalExpression {
fields = append(fields, quantity+" "+unitString) fields = append(fields, quantity+" "+unitString)
} }
return IntervalExp(CustomExpression(Token(fmt.Sprintf("INTERVAL '%s'", strings.Join(fields, " "))))) return IntervalExp(jet.AtomicCustomExpression(Token(fmt.Sprintf("INTERVAL '%s'", strings.Join(fields, " ")))))
} }
// INTERVALd creates interval expression from time.Duration // INTERVALd creates interval expression from time.Duration

View file

@ -2,8 +2,9 @@ package sqlite
import ( import (
"fmt" "fmt"
"github.com/go-jet/jet/v2/internal/jet"
"time" "time"
"github.com/go-jet/jet/v2/internal/jet"
) )
// This functions can be used, instead of its method counterparts, to have a better indentation of a complex condition // This functions can be used, instead of its method counterparts, to have a better indentation of a complex condition
@ -297,7 +298,7 @@ func modifier(modifierName string) func(value float64) Expression {
func DATE(timeValue interface{}, modifiers ...Expression) DateExpression { func DATE(timeValue interface{}, modifiers ...Expression) DateExpression {
exprList := getFuncExprList(timeValue, modifiers...) exprList := getFuncExprList(timeValue, modifiers...)
return jet.NewDateFunc("DATE", exprList...) return DateExp(Func("DATE", exprList...))
} }
// TIME function creates new time from time-value and zero or more time modifiers // TIME function creates new time from time-value and zero or more time modifiers

View file

@ -1,13 +1,14 @@
package mysql package mysql
import ( import (
"github.com/go-jet/jet/v2/internal/utils/ptr"
"github.com/shopspring/decimal"
"github.com/stretchr/testify/require"
"strings" "strings"
"testing" "testing"
"time" "time"
"github.com/go-jet/jet/v2/internal/utils/ptr"
"github.com/shopspring/decimal"
"github.com/stretchr/testify/require"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/go-jet/jet/v2/internal/testutils" "github.com/go-jet/jet/v2/internal/testutils"
@ -53,8 +54,8 @@ func TestAllTypesJSON(t *testing.T) {
testutils.AssertStatementSql(t, stmt, strings.ReplaceAll(` testutils.AssertStatementSql(t, stmt, strings.ReplaceAll(`
SELECT JSON_ARRAYAGG(JSON_OBJECT( SELECT JSON_ARRAYAGG(JSON_OBJECT(
'id', all_types.id, 'id', all_types.id,
'boolean', all_types.boolean = 1, 'boolean', (all_types.boolean = 1),
'booleanPtr', all_types.boolean_ptr = 1, 'booleanPtr', (all_types.boolean_ptr = 1),
'tinyInt', all_types.tiny_int, 'tinyInt', all_types.tiny_int,
'uTinyInt', all_types.u_tiny_int, 'uTinyInt', all_types.u_tiny_int,
'smallInt', all_types.small_int, 'smallInt', all_types.small_int,
@ -190,8 +191,8 @@ func TestExpressionOperators(t *testing.T) {
).LIMIT(2) ).LIMIT(2)
testutils.AssertStatementSql(t, query, strings.Replace(` testutils.AssertStatementSql(t, query, strings.Replace(`
SELECT all_types.'integer' IS NULL AS "result.is_null", SELECT (all_types.'integer' IS NULL) AS "result.is_null",
all_types.date_ptr IS NOT NULL AS "result.is_not_null", (all_types.date_ptr IS NOT NULL) AS "result.is_not_null",
(all_types.small_int_ptr IN (?, ?)) AS "result.in", (all_types.small_int_ptr IN (?, ?)) AS "result.in",
(all_types.small_int_ptr IN (( (all_types.small_int_ptr IN ((
SELECT all_types.'integer' AS "all_types.integer" SELECT all_types.'integer' AS "all_types.integer"
@ -259,12 +260,12 @@ SELECT (all_types.boolean = all_types.boolean_ptr) AS "EQ1",
(NOT(all_types.boolean <=> ?)) AS "distinct2", (NOT(all_types.boolean <=> ?)) AS "distinct2",
(all_types.boolean <=> all_types.boolean_ptr) AS "not_distinct_1", (all_types.boolean <=> all_types.boolean_ptr) AS "not_distinct_1",
(all_types.boolean <=> ?) AS "NOTDISTINCT2", (all_types.boolean <=> ?) AS "NOTDISTINCT2",
all_types.boolean IS TRUE AS "ISTRUE", (all_types.boolean IS TRUE) AS "ISTRUE",
all_types.boolean IS NOT TRUE AS "isnottrue", (all_types.boolean IS NOT TRUE) AS "isnottrue",
all_types.boolean IS FALSE AS "is_False", (all_types.boolean IS FALSE) AS "is_False",
all_types.boolean IS NOT FALSE AS "is not false", (all_types.boolean IS NOT FALSE) AS "is not false",
all_types.boolean IS UNKNOWN AS "is unknown", (all_types.boolean IS UNKNOWN) AS "is unknown",
all_types.boolean IS NOT UNKNOWN AS "is_not_unknown", (all_types.boolean IS NOT UNKNOWN) AS "is_not_unknown",
((all_types.boolean AND all_types.boolean) = (all_types.boolean AND all_types.boolean)) AS "complex1", ((all_types.boolean AND all_types.boolean) = (all_types.boolean AND all_types.boolean)) AS "complex1",
((all_types.boolean OR all_types.boolean) = (all_types.boolean AND all_types.boolean)) AS "complex2" ((all_types.boolean OR all_types.boolean) = (all_types.boolean AND all_types.boolean)) AS "complex2"
FROM test_sample.all_types; FROM test_sample.all_types;
@ -1143,7 +1144,7 @@ SELECT EXTRACT(MICROSECOND FROM CAST(? AS TIME)),
EXTRACT(HOUR FROM all_types.timestamp), EXTRACT(HOUR FROM all_types.timestamp),
EXTRACT(DAY FROM all_types.date), EXTRACT(DAY FROM all_types.date),
EXTRACT(WEEK FROM all_types.timestamp), EXTRACT(WEEK FROM all_types.timestamp),
EXTRACT(MONTH FROM all_types.timestamp + INTERVAL 1 DAY), EXTRACT(MONTH FROM (all_types.timestamp + INTERVAL 1 DAY)),
EXTRACT(QUARTER FROM all_types.timestamp), EXTRACT(QUARTER FROM all_types.timestamp),
EXTRACT(YEAR FROM all_types.timestamp) = ?, EXTRACT(YEAR FROM all_types.timestamp) = ?,
EXTRACT(SECOND_MICROSECOND FROM all_types.time), EXTRACT(SECOND_MICROSECOND FROM all_types.time),
@ -1305,7 +1306,7 @@ FROM (
testutils.AssertDebugStatementSql(t, stmtJson, strings.ReplaceAll(` testutils.AssertDebugStatementSql(t, stmtJson, strings.ReplaceAll(`
SELECT JSON_ARRAYAGG(JSON_OBJECT( SELECT JSON_ARRAYAGG(JSON_OBJECT(
'boolean', sub_query.''all_types.boolean'' = 1, 'boolean', (sub_query.''all_types.boolean'' = 1),
'integer', sub_query.''all_types.integer'', 'integer', sub_query.''all_types.integer'',
'double', sub_query.''all_types.double'', 'double', sub_query.''all_types.double'',
'text', sub_query.''all_types.text'', 'text', sub_query.''all_types.text'',

View file

@ -2,11 +2,12 @@ package mysql
import ( import (
"context" "context"
"github.com/go-jet/jet/v2/qrm"
"slices" "slices"
"strings" "strings"
"testing" "testing"
"github.com/go-jet/jet/v2/qrm"
"github.com/go-jet/jet/v2/internal/testutils" "github.com/go-jet/jet/v2/internal/testutils"
. "github.com/go-jet/jet/v2/mysql" . "github.com/go-jet/jet/v2/mysql"
"github.com/go-jet/jet/v2/tests/.gentestdata/mysql/dvds/model" "github.com/go-jet/jet/v2/tests/.gentestdata/mysql/dvds/model"
@ -410,7 +411,7 @@ SELECT JSON_ARRAYAGG(JSON_OBJECT(
'lastName', customers_info.''customer.last_name'', 'lastName', customers_info.''customer.last_name'',
'email', customers_info.''customer.email'', 'email', customers_info.''customer.email'',
'addressID', customers_info.''customer.address_id'', 'addressID', customers_info.''customer.address_id'',
'active', customers_info.''customer.active'' = 1, 'active', (customers_info.''customer.active'' = 1),
'createDate', DATE_FORMAT(customers_info.''customer.create_date'','%Y-%m-%dT%H:%i:%s.%fZ'), 'createDate', DATE_FORMAT(customers_info.''customer.create_date'','%Y-%m-%dT%H:%i:%s.%fZ'),
'lastUpdate', DATE_FORMAT(customers_info.''customer.last_update'','%Y-%m-%dT%H:%i:%s.%fZ'), 'lastUpdate', DATE_FORMAT(customers_info.''customer.last_update'','%Y-%m-%dT%H:%i:%s.%fZ'),
'amount', ( 'amount', (

View file

@ -3,13 +3,14 @@ package postgres
import ( import (
"encoding/base64" "encoding/base64"
"fmt" "fmt"
"math"
"testing"
"time"
"github.com/go-jet/jet/v2/internal/utils/ptr" "github.com/go-jet/jet/v2/internal/utils/ptr"
"github.com/go-jet/jet/v2/qrm" "github.com/go-jet/jet/v2/qrm"
"github.com/lib/pq" "github.com/lib/pq"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"math"
"testing"
"time"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@ -89,12 +90,12 @@ FROM (
all_types.timestampz AS "timestampz", all_types.timestampz AS "timestampz",
to_char(all_types.timestamp_ptr, 'YYYY-MM-DD"T"HH24:MI:SS.USZ') AS "timestampPtr", to_char(all_types.timestamp_ptr, 'YYYY-MM-DD"T"HH24:MI:SS.USZ') AS "timestampPtr",
to_char(all_types.timestamp, 'YYYY-MM-DD"T"HH24:MI:SS.USZ') AS "timestamp", to_char(all_types.timestamp, 'YYYY-MM-DD"T"HH24:MI:SS.USZ') AS "timestamp",
to_char(all_types.date_ptr::timestamp, 'YYYY-MM-DD') || 'T00:00:00Z' AS "datePtr", (to_char(all_types.date_ptr::timestamp, 'YYYY-MM-DD') || 'T00:00:00Z') AS "datePtr",
to_char(all_types.date::timestamp, 'YYYY-MM-DD') || 'T00:00:00Z' AS "date", (to_char(all_types.date::timestamp, 'YYYY-MM-DD') || 'T00:00:00Z') AS "date",
'0000-01-01T' || to_char('2000-10-10'::date + all_types.timez_ptr, 'HH24:MI:SS.USTZH:TZM') AS "timezPtr", ('0000-01-01T' || to_char('2000-10-10'::date + all_types.timez_ptr, 'HH24:MI:SS.USTZH:TZM')) AS "timezPtr",
'0000-01-01T' || to_char('2000-10-10'::date + all_types.timez, 'HH24:MI:SS.USTZH:TZM') AS "timez", ('0000-01-01T' || to_char('2000-10-10'::date + all_types.timez, 'HH24:MI:SS.USTZH:TZM')) AS "timez",
'0000-01-01T' || to_char('2000-10-10'::date + all_types.time_ptr, 'HH24:MI:SS.USZ') AS "timePtr", ('0000-01-01T' || to_char('2000-10-10'::date + all_types.time_ptr, 'HH24:MI:SS.USZ')) AS "timePtr",
'0000-01-01T' || to_char('2000-10-10'::date + all_types.time, 'HH24:MI:SS.USZ') AS "time", ('0000-01-01T' || to_char('2000-10-10'::date + all_types.time, 'HH24:MI:SS.USZ')) AS "time",
all_types.interval_ptr AS "intervalPtr", all_types.interval_ptr AS "intervalPtr",
all_types.interval AS "interval", all_types.interval AS "interval",
all_types.boolean_ptr AS "booleanPtr", all_types.boolean_ptr AS "booleanPtr",
@ -480,11 +481,14 @@ func TestExpressionOperators(t *testing.T) {
AllTypes.SmallIntPtr.NOT_IN(Int(11), Int16(22), NULL).AS("result.not_in"), AllTypes.SmallIntPtr.NOT_IN(Int(11), Int16(22), NULL).AS("result.not_in"),
AllTypes.SmallIntPtr.NOT_IN(AllTypes.SELECT(AllTypes.Integer)).AS("result.not_in_select"), AllTypes.SmallIntPtr.NOT_IN(AllTypes.SELECT(AllTypes.Integer)).AS("result.not_in_select"),
Bool(true).EQ(String("foo").IS_NOT_NULL()),
Bool(true).EQ(String("foo").IS_NOT_NULL()).AS("complex"),
).LIMIT(2) ).LIMIT(2)
testutils.AssertStatementSql(t, query, ` testutils.AssertStatementSql(t, query, `
SELECT all_types.integer IS NULL AS "result.is_null", SELECT (all_types.integer IS NULL) AS "result.is_null",
all_types.date_ptr IS NOT NULL AS "result.is_not_null", (all_types.date_ptr IS NOT NULL) AS "result.is_not_null",
(all_types.small_int_ptr IN ($1::smallint, $2::smallint)) AS "result.in", (all_types.small_int_ptr IN ($1::smallint, $2::smallint)) AS "result.in",
(all_types.small_int_ptr IN (( (all_types.small_int_ptr IN ((
SELECT all_types.integer AS "all_types.integer" SELECT all_types.integer AS "all_types.integer"
@ -497,18 +501,22 @@ SELECT all_types.integer IS NULL AS "result.is_null",
(all_types.small_int_ptr NOT IN (( (all_types.small_int_ptr NOT IN ((
SELECT all_types.integer AS "all_types.integer" SELECT all_types.integer AS "all_types.integer"
FROM test_sample.all_types FROM test_sample.all_types
))) AS "result.not_in_select" ))) AS "result.not_in_select",
$11::boolean = ($12::text IS NOT NULL),
($13::boolean = ($14::text IS NOT NULL)) AS "complex"
FROM test_sample.all_types FROM test_sample.all_types
LIMIT $11; LIMIT $15;
`, int8(11), int8(22), 78, 56, 11, 22, 33, 44, int64(11), int16(22), int64(2)) `, int8(11), int8(22), 78, 56, 11, 22, 33, 44, int64(11), int16(22), true, "foo", true, "foo", int64(2))
var dest []struct { var dest []struct {
common.ExpressionTestResult `alias:"result.*"` common.ExpressionTestResult `alias:"result.*"`
} }
err := query.Query(db, &dest) allowUnusedColumns(func() {
err := query.Query(db, &dest)
require.NoError(t, err)
})
require.NoError(t, err)
testutils.AssertJSON(t, dest, ` testutils.AssertJSON(t, dest, `
[ [
{ {
@ -904,12 +912,12 @@ SELECT (all_types.boolean = all_types.boolean_ptr) AS "EQ1",
(all_types.boolean IS DISTINCT FROM $3::boolean) AS "distinct2", (all_types.boolean IS DISTINCT FROM $3::boolean) AS "distinct2",
(all_types.boolean IS NOT DISTINCT FROM all_types.boolean_ptr) AS "not_distinct_1", (all_types.boolean IS NOT DISTINCT FROM all_types.boolean_ptr) AS "not_distinct_1",
(all_types.boolean IS NOT DISTINCT FROM $4::boolean) AS "NOTDISTINCT2", (all_types.boolean IS NOT DISTINCT FROM $4::boolean) AS "NOTDISTINCT2",
all_types.boolean IS TRUE AS "ISTRUE", (all_types.boolean IS TRUE) AS "ISTRUE",
all_types.boolean IS NOT TRUE AS "isnottrue", (all_types.boolean IS NOT TRUE) AS "isnottrue",
all_types.boolean IS FALSE AS "is_False", (all_types.boolean IS FALSE) AS "is_False",
all_types.boolean IS NOT FALSE AS "is not false", (all_types.boolean IS NOT FALSE) AS "is not false",
all_types.boolean IS UNKNOWN AS "is unknown", (all_types.boolean IS UNKNOWN) AS "is unknown",
all_types.boolean IS NOT UNKNOWN AS "is_not_unknown", (all_types.boolean IS NOT UNKNOWN) AS "is_not_unknown",
((all_types.boolean AND all_types.boolean) = (all_types.boolean AND all_types.boolean)) AS "complex1", ((all_types.boolean AND all_types.boolean) = (all_types.boolean AND all_types.boolean)) AS "complex1",
((all_types.boolean OR all_types.boolean) = (all_types.boolean AND all_types.boolean)) AS "complex2" ((all_types.boolean OR all_types.boolean) = (all_types.boolean AND all_types.boolean)) AS "complex2"
FROM test_sample.all_types FROM test_sample.all_types
@ -1267,7 +1275,72 @@ func TestTimeExpression(t *testing.T) {
NOW(), NOW(),
) )
// fmt.Println(query.DebugSql()) testutils.AssertStatementSql(t, query, `
SELECT all_types.time = all_types.time,
all_types.time = $1::time without time zone,
all_types.timez = all_types.timez_ptr,
all_types.timez = $2::time with time zone,
all_types.timestamp = all_types.timestamp_ptr,
all_types.timestamp = $3::timestamp without time zone,
all_types.timestampz = all_types.timestampz_ptr,
all_types.timestampz = $4::timestamp with time zone,
all_types.date = all_types.date_ptr,
all_types.date = $5::date,
all_types.time != all_types.time,
all_types.time != $6::time without time zone,
all_types.timez != all_types.timez_ptr,
all_types.timez != $7::time with time zone,
all_types.timestamp != all_types.timestamp_ptr,
all_types.timestamp != $8::timestamp without time zone,
all_types.timestampz != all_types.timestampz_ptr,
all_types.timestampz != $9::timestamp with time zone,
all_types.date != all_types.date_ptr,
all_types.date != $10::date,
all_types.time IS DISTINCT FROM all_types.time,
all_types.time IS DISTINCT FROM $11::time without time zone,
all_types.time IS NOT DISTINCT FROM all_types.time,
all_types.time IS NOT DISTINCT FROM $12::time without time zone,
all_types.time < all_types.time,
all_types.time < $13::time without time zone,
all_types.time <= all_types.time,
all_types.time <= $14::time without time zone,
all_types.time > all_types.time,
all_types.time > $15::time without time zone,
all_types.time >= all_types.time,
all_types.time >= $16::time without time zone,
all_types.time BETWEEN $17::time without time zone AND $18::time without time zone,
all_types.time NOT BETWEEN all_types.time_ptr AND (all_types.time + INTERVAL '2 HOUR'),
all_types.date + INTERVAL '1 HOUR',
all_types.date - INTERVAL '1 MINUTE',
all_types.time + INTERVAL '1 HOUR',
all_types.time - INTERVAL '1 MINUTE',
all_types.timez + INTERVAL '1 HOUR',
all_types.timez - INTERVAL '1 MINUTE',
all_types.timez BETWEEN $19::time with time zone AND all_types.timez_ptr,
all_types.timez NOT BETWEEN all_types.timez AND $20::time with time zone,
all_types.timestamp + INTERVAL '1 HOUR',
all_types.timestamp - INTERVAL '1 MINUTE',
all_types.timestamp BETWEEN all_types.timestamp_ptr AND $21::timestamp without time zone,
all_types.timestamp NOT BETWEEN $22::timestamp without time zone AND all_types.timestamp_ptr,
all_types.timestampz + INTERVAL '1 HOUR',
all_types.timestampz - INTERVAL '1 MINUTE',
all_types.timestamp BETWEEN all_types.timestamp_ptr AND $23::timestamp without time zone,
all_types.timestamp NOT BETWEEN all_types.timestamp_ptr AND $24::timestamp without time zone,
all_types.date - $25::text::interval,
all_types.date BETWEEN $26::date AND $27::date,
all_types.date NOT BETWEEN all_types.date_ptr AND $28::date,
CURRENT_DATE,
CURRENT_TIME,
CURRENT_TIME(2),
CURRENT_TIMESTAMP,
CURRENT_TIMESTAMP(1),
LOCALTIME,
LOCALTIME(11),
LOCALTIMESTAMP,
LOCALTIMESTAMP(4),
NOW()
FROM test_sample.all_types;
`)
var dest []struct{} var dest []struct{}
@ -1339,16 +1412,16 @@ SELECT $1::time without time zone AS "time",
( (
SELECT row_to_json(json_records) AS "json_json" SELECT row_to_json(json_records) AS "json_json"
FROM ( FROM (
SELECT '0000-01-01T' || to_char('2000-10-10'::date + $11::time without time zone, 'HH24:MI:SS.USZ') AS "time", SELECT ('0000-01-01T' || to_char('2000-10-10'::date + $11::time without time zone, 'HH24:MI:SS.USZ')) AS "time",
'0000-01-01T' || to_char('2000-10-10'::date + $12::time without time zone, 'HH24:MI:SS.USZ') AS "timeWithNanoSeconds", ('0000-01-01T' || to_char('2000-10-10'::date + $12::time without time zone, 'HH24:MI:SS.USZ')) AS "timeWithNanoSeconds",
'0000-01-01T' || to_char('2000-10-10'::date + $13::time with time zone, 'HH24:MI:SS.USTZH:TZM') AS "timez", ('0000-01-01T' || to_char('2000-10-10'::date + $13::time with time zone, 'HH24:MI:SS.USTZH:TZM')) AS "timez",
'0000-01-01T' || to_char('2000-10-10'::date + $14::time with time zone, 'HH24:MI:SS.USTZH:TZM') AS "timezWithNanoSeconds", ('0000-01-01T' || to_char('2000-10-10'::date + $14::time with time zone, 'HH24:MI:SS.USTZH:TZM')) AS "timezWithNanoSeconds",
to_char($15::timestamp without time zone, 'YYYY-MM-DD"T"HH24:MI:SS.USZ') AS "timestamp", to_char($15::timestamp without time zone, 'YYYY-MM-DD"T"HH24:MI:SS.USZ') AS "timestamp",
to_char($16::timestamp without time zone, 'YYYY-MM-DD"T"HH24:MI:SS.USZ') AS "timestampWithNanoSeconds", to_char($16::timestamp without time zone, 'YYYY-MM-DD"T"HH24:MI:SS.USZ') AS "timestampWithNanoSeconds",
$17::timestamp with time zone AS "timestampz", $17::timestamp with time zone AS "timestampz",
$18::timestamp with time zone AS "timestampzWithNanoSeconds", $18::timestamp with time zone AS "timestampzWithNanoSeconds",
to_char($19::date::timestamp, 'YYYY-MM-DD') || 'T00:00:00Z' AS "date", (to_char($19::date::timestamp, 'YYYY-MM-DD') || 'T00:00:00Z') AS "date",
'0000-01-01T' || to_char('2000-10-10'::date + ($20::time without time zone + INTERVAL '2 HOUR'), 'HH24:MI:SS.USZ') AS "timeExpression" ('0000-01-01T' || to_char('2000-10-10'::date + ($20::time without time zone + INTERVAL '2 HOUR'), 'HH24:MI:SS.USZ')) AS "timeExpression"
) AS json_records ) AS json_records
) AS "json"; ) AS "json";
`) `)
@ -1626,21 +1699,21 @@ FROM test_sample.all_types;
func TestTimeEXTRACT(t *testing.T) { func TestTimeEXTRACT(t *testing.T) {
stmt := SELECT( stmt := SELECT(
EXTRACT(CENTURY, AllTypes.Timestampz), EXTRACT(CENTURY, AllTypes.Timestampz).AS("century"),
EXTRACT(DAY, AllTypes.Timestamp), EXTRACT(DAY, AllTypes.Timestamp),
EXTRACT(DECADE, AllTypes.Date), EXTRACT(DECADE, AllTypes.Date),
EXTRACT(DOW, AllTypes.TimestampzPtr), EXTRACT(DOW, AllTypes.TimestampzPtr),
EXTRACT(DOY, DateT(time.Now())), EXTRACT(DOY, DateT(time.Now())).AS("date"),
EXTRACT(EPOCH, TimestampT(time.Now())), EXTRACT(EPOCH, TimestampT(time.Now())),
EXTRACT(HOUR, AllTypes.Time.ADD(INTERVAL(1, HOUR))), EXTRACT(HOUR, AllTypes.Time.ADD(INTERVAL(1, HOUR))).AS("hour"),
EXTRACT(ISODOW, AllTypes.Timestampz), EXTRACT(ISODOW, AllTypes.Date.SUB(INTERVAL(1, DAY))),
EXTRACT(ISOYEAR, AllTypes.Timestampz), EXTRACT(ISOYEAR, AllTypes.Timestampz),
EXTRACT(JULIAN, AllTypes.Timestampz).EQ(Float(3456.123)), EXTRACT(JULIAN, AllTypes.Timestampz).EQ(Float(3456.123)).AS("microsecond_equal"),
EXTRACT(MICROSECOND, AllTypes.Timestampz), EXTRACT(MICROSECOND, AllTypes.Timestampz).EQ(Float(123.001)),
EXTRACT(MILLENNIUM, AllTypes.Timestampz), EXTRACT(MILLENNIUM, AllTypes.Timestampz),
EXTRACT(MILLISECOND, AllTypes.Timez), EXTRACT(MILLISECOND, AllTypes.Timez),
EXTRACT(MINUTE, INTERVAL(1, HOUR, 2, MINUTE)), EXTRACT(MINUTE, INTERVAL(1, HOUR, 2, MINUTE)).AS("minute_interval"),
EXTRACT(MONTH, AllTypes.Timestampz), EXTRACT(MONTH, INTERVAL(11, DAY)),
EXTRACT(QUARTER, AllTypes.Timestampz), EXTRACT(QUARTER, AllTypes.Timestampz),
EXTRACT(SECOND, AllTypes.Timestampz), EXTRACT(SECOND, AllTypes.Timestampz),
EXTRACT(TIMEZONE, AllTypes.Timestampz), EXTRACT(TIMEZONE, AllTypes.Timestampz),
@ -1652,24 +1725,24 @@ func TestTimeEXTRACT(t *testing.T) {
AllTypes, AllTypes,
) )
// fmt.Println(stmt.Sql()) //fmt.Println(stmt.Sql())
testutils.AssertStatementSql(t, stmt, ` testutils.AssertStatementSql(t, stmt, `
SELECT EXTRACT(CENTURY FROM all_types.timestampz), SELECT EXTRACT(CENTURY FROM all_types.timestampz) AS "century",
EXTRACT(DAY FROM all_types.timestamp), EXTRACT(DAY FROM all_types.timestamp),
EXTRACT(DECADE FROM all_types.date), EXTRACT(DECADE FROM all_types.date),
EXTRACT(DOW FROM all_types.timestampz_ptr), EXTRACT(DOW FROM all_types.timestampz_ptr),
EXTRACT(DOY FROM $1::date), EXTRACT(DOY FROM $1::date) AS "date",
EXTRACT(EPOCH FROM $2::timestamp without time zone), EXTRACT(EPOCH FROM $2::timestamp without time zone),
EXTRACT(HOUR FROM all_types.time + INTERVAL '1 HOUR'), EXTRACT(HOUR FROM (all_types.time + INTERVAL '1 HOUR')) AS "hour",
EXTRACT(ISODOW FROM all_types.timestampz), EXTRACT(ISODOW FROM (all_types.date - INTERVAL '1 DAY')),
EXTRACT(ISOYEAR FROM all_types.timestampz), EXTRACT(ISOYEAR FROM all_types.timestampz),
EXTRACT(JULIAN FROM all_types.timestampz) = $3, (EXTRACT(JULIAN FROM all_types.timestampz) = $3) AS "microsecond_equal",
EXTRACT(MICROSECOND FROM all_types.timestampz), EXTRACT(MICROSECOND FROM all_types.timestampz) = $4,
EXTRACT(MILLENNIUM FROM all_types.timestampz), EXTRACT(MILLENNIUM FROM all_types.timestampz),
EXTRACT(MILLISECOND FROM all_types.timez), EXTRACT(MILLISECOND FROM all_types.timez),
EXTRACT(MINUTE FROM INTERVAL '1 HOUR 2 MINUTE'), EXTRACT(MINUTE FROM INTERVAL '1 HOUR 2 MINUTE') AS "minute_interval",
EXTRACT(MONTH FROM all_types.timestampz), EXTRACT(MONTH FROM INTERVAL '11 DAY'),
EXTRACT(QUARTER FROM all_types.timestampz), EXTRACT(QUARTER FROM all_types.timestampz),
EXTRACT(SECOND FROM all_types.timestampz), EXTRACT(SECOND FROM all_types.timestampz),
EXTRACT(TIMEZONE FROM all_types.timestampz), EXTRACT(TIMEZONE FROM all_types.timestampz),
@ -1947,9 +2020,9 @@ FROM (
"subQuery"."all_types.integer" AS "integer", "subQuery"."all_types.integer" AS "integer",
"subQuery"."all_types.double_precision" AS "doublePrecision", "subQuery"."all_types.double_precision" AS "doublePrecision",
"subQuery"."all_types.text" AS "text", "subQuery"."all_types.text" AS "text",
to_char("subQuery"."all_types.date"::timestamp, 'YYYY-MM-DD') || 'T00:00:00Z' AS "date", (to_char("subQuery"."all_types.date"::timestamp, 'YYYY-MM-DD') || 'T00:00:00Z') AS "date",
'0000-01-01T' || to_char('2000-10-10'::date + "subQuery"."all_types.time", 'HH24:MI:SS.USZ') AS "time", ('0000-01-01T' || to_char('2000-10-10'::date + "subQuery"."all_types.time", 'HH24:MI:SS.USZ')) AS "time",
'0000-01-01T' || to_char('2000-10-10'::date + "subQuery"."all_types.timez", 'HH24:MI:SS.USTZH:TZM') AS "timez", ('0000-01-01T' || to_char('2000-10-10'::date + "subQuery"."all_types.timez", 'HH24:MI:SS.USTZH:TZM')) AS "timez",
to_char("subQuery"."all_types.timestamp", 'YYYY-MM-DD"T"HH24:MI:SS.USZ') AS "timestamp", to_char("subQuery"."all_types.timestamp", 'YYYY-MM-DD"T"HH24:MI:SS.USZ') AS "timestamp",
"subQuery"."all_types.timestampz" AS "timestampz", "subQuery"."all_types.timestampz" AS "timestampz",
"subQuery"."all_types.interval" AS "interval", "subQuery"."all_types.interval" AS "interval",

View file

@ -1,6 +1,9 @@
package postgres package postgres
import ( import (
"testing"
"time"
"github.com/go-jet/jet/v2/internal/testutils" "github.com/go-jet/jet/v2/internal/testutils"
"github.com/go-jet/jet/v2/internal/utils/ptr" "github.com/go-jet/jet/v2/internal/utils/ptr"
. "github.com/go-jet/jet/v2/postgres" . "github.com/go-jet/jet/v2/postgres"
@ -11,8 +14,6 @@ import (
"github.com/google/uuid" "github.com/google/uuid"
"github.com/lib/pq" "github.com/lib/pq"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"testing"
"time"
) )
func TestArraySelect(t *testing.T) { func TestArraySelect(t *testing.T) {
@ -158,8 +159,8 @@ SELECT $1::boolean[] AS "bool_array",
(sample_arrays.bool_array = $13::boolean[]) AS "bool_eq", (sample_arrays.bool_array = $13::boolean[]) AS "bool_eq",
(sample_arrays.text_array = sample_arrays.text_array) AS "text_eq", (sample_arrays.text_array = sample_arrays.text_array) AS "text_eq",
(sample_arrays.text_array != $14::text[]) AS "text_neq", (sample_arrays.text_array != $14::text[]) AS "text_neq",
(sample_arrays.int4_array < $15::integer[]) IS TRUE AS "int4_lt", ((sample_arrays.int4_array < $15::integer[]) IS TRUE) AS "int4_lt",
(sample_arrays.int8_array <= $16::bigint[]) IS FALSE AS "int8_lteq", ((sample_arrays.int8_array <= $16::bigint[]) IS FALSE) AS "int8_lteq",
(sample_arrays.real_array > $17::real[]) AS "decimal_gt", (sample_arrays.real_array > $17::real[]) AS "decimal_gt",
(sample_arrays.double_array >= $18::double precision[]) AS "numeric_gt_eq", (sample_arrays.double_array >= $18::double precision[]) AS "numeric_gt_eq",
(sample_arrays.bytea_array @> $19::bytea[]) AS "bytea_contains", (sample_arrays.bytea_array @> $19::bytea[]) AS "bytea_contains",

View file

@ -4,14 +4,15 @@
package postgres package postgres
import ( import (
"math/big"
"testing"
"time"
"github.com/go-jet/jet/v2/internal/testutils" "github.com/go-jet/jet/v2/internal/testutils"
"github.com/go-jet/jet/v2/qrm" "github.com/go-jet/jet/v2/qrm"
"github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp"
"github.com/jackc/pgtype" "github.com/jackc/pgtype"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"math/big"
"testing"
"time"
. "github.com/go-jet/jet/v2/postgres" . "github.com/go-jet/jet/v2/postgres"
"github.com/go-jet/jet/v2/tests/.gentestdata/jetdb/test_sample/model" "github.com/go-jet/jet/v2/tests/.gentestdata/jetdb/test_sample/model"
@ -81,8 +82,8 @@ SELECT sample_ranges.date_range AS "sample_ranges.date_range",
(sample_ranges.int4_range = sample_ranges.int4_range) AS "sample.int4eq", (sample_ranges.int4_range = sample_ranges.int4_range) AS "sample.int4eq",
(sample_ranges.int8_range = int8range($1, $2, $3::text)) AS "sample.int8eq", (sample_ranges.int8_range = int8range($1, $2, $3::text)) AS "sample.int8eq",
(sample_ranges.int4_range != int4range($4, $5)) AS "sample.int4neq", (sample_ranges.int4_range != int4range($4, $5)) AS "sample.int4neq",
(sample_ranges.num_range < numrange($6, $7)) IS TRUE AS "sample.num_lt", ((sample_ranges.num_range < numrange($6, $7)) IS TRUE) AS "sample.num_lt",
(sample_ranges.date_range <= daterange($8::date, $9)) IS FALSE AS "sample.date_lteq", ((sample_ranges.date_range <= daterange($8::date, $9)) IS FALSE) AS "sample.date_lteq",
(sample_ranges.timestamp_range > tsrange($10::timestamp without time zone, $11::timestamp without time zone)) AS "sample.ts_gt", (sample_ranges.timestamp_range > tsrange($10::timestamp without time zone, $11::timestamp without time zone)) AS "sample.ts_gt",
(sample_ranges.timestampz_range >= tstzrange($12, $13::timestamp with time zone)) AS "sample.tstz_gteq", (sample_ranges.timestampz_range >= tstzrange($12, $13::timestamp with time zone)) AS "sample.tstz_gteq",
(sample_ranges.int4_range @> $14::integer) AS "sample.int4cont", (sample_ranges.int4_range @> $14::integer) AS "sample.int4cont",

View file

@ -1,13 +1,14 @@
package postgres package postgres
import ( import (
"testing"
"time"
"github.com/go-jet/jet/v2/internal/testutils" "github.com/go-jet/jet/v2/internal/testutils"
"github.com/go-jet/jet/v2/internal/utils/ptr" "github.com/go-jet/jet/v2/internal/utils/ptr"
"github.com/go-jet/jet/v2/qrm" "github.com/go-jet/jet/v2/qrm"
"github.com/go-jet/jet/v2/tests/.gentestdata/jetdb/dvds/view" "github.com/go-jet/jet/v2/tests/.gentestdata/jetdb/dvds/view"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"testing"
"time"
. "github.com/go-jet/jet/v2/postgres" . "github.com/go-jet/jet/v2/postgres"
"github.com/go-jet/jet/v2/tests/.gentestdata/jetdb/dvds/model" "github.com/go-jet/jet/v2/tests/.gentestdata/jetdb/dvds/model"
@ -155,7 +156,7 @@ FROM (
customer.email AS "email", customer.email AS "email",
customer.address_id AS "addressID", customer.address_id AS "addressID",
customer.activebool AS "activebool", customer.activebool AS "activebool",
to_char(customer.create_date::timestamp, 'YYYY-MM-DD') || 'T00:00:00Z' AS "createDate", (to_char(customer.create_date::timestamp, 'YYYY-MM-DD') || 'T00:00:00Z') AS "createDate",
to_char(customer.last_update, 'YYYY-MM-DD"T"HH24:MI:SS.USZ') AS "lastUpdate", to_char(customer.last_update, 'YYYY-MM-DD"T"HH24:MI:SS.USZ') AS "lastUpdate",
customer.active AS "active", customer.active AS "active",
( (
@ -307,7 +308,7 @@ FROM (
customer.email AS "email", customer.email AS "email",
customer.address_id AS "addressID", customer.address_id AS "addressID",
customer.activebool AS "activebool", customer.activebool AS "activebool",
to_char(customer.create_date::timestamp, 'YYYY-MM-DD') || 'T00:00:00Z' AS "createDate", (to_char(customer.create_date::timestamp, 'YYYY-MM-DD') || 'T00:00:00Z') AS "createDate",
to_char(customer.last_update, 'YYYY-MM-DD"T"HH24:MI:SS.USZ') AS "lastUpdate", to_char(customer.last_update, 'YYYY-MM-DD"T"HH24:MI:SS.USZ') AS "lastUpdate",
customer.active AS "active", customer.active AS "active",
( (
@ -522,7 +523,7 @@ RETURNING rental.rental_id AS "rental.rental_id",
customer.email AS "email", customer.email AS "email",
customer.address_id AS "addressID", customer.address_id AS "addressID",
customer.activebool AS "activebool", customer.activebool AS "activebool",
to_char(customer.create_date::timestamp, 'YYYY-MM-DD') || 'T00:00:00Z' AS "createDate", (to_char(customer.create_date::timestamp, 'YYYY-MM-DD') || 'T00:00:00Z') AS "createDate",
to_char(customer.last_update, 'YYYY-MM-DD"T"HH24:MI:SS.USZ') AS "lastUpdate", to_char(customer.last_update, 'YYYY-MM-DD"T"HH24:MI:SS.USZ') AS "lastUpdate",
customer.active AS "active" customer.active AS "active"
FROM dvds.customer FROM dvds.customer

View file

@ -2,6 +2,10 @@ package sqlite
import ( import (
"encoding/hex" "encoding/hex"
"strings"
"testing"
"time"
"github.com/go-jet/jet/v2/internal/testutils" "github.com/go-jet/jet/v2/internal/testutils"
"github.com/go-jet/jet/v2/internal/utils/ptr" "github.com/go-jet/jet/v2/internal/utils/ptr"
. "github.com/go-jet/jet/v2/sqlite" . "github.com/go-jet/jet/v2/sqlite"
@ -12,9 +16,6 @@ import (
"github.com/google/uuid" "github.com/google/uuid"
"github.com/shopspring/decimal" "github.com/shopspring/decimal"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"strings"
"testing"
"time"
) )
func TestAllTypes(t *testing.T) { func TestAllTypes(t *testing.T) {
@ -232,8 +233,8 @@ func TestExpressionOperators(t *testing.T) {
).LIMIT(2) ).LIMIT(2)
testutils.AssertStatementSql(t, query, strings.Replace(` testutils.AssertStatementSql(t, query, strings.Replace(`
SELECT all_types.integer IS NULL AS "result.is_null", SELECT (all_types.integer IS NULL) AS "result.is_null",
all_types.date_ptr IS NOT NULL AS "result.is_not_null", (all_types.date_ptr IS NOT NULL) AS "result.is_not_null",
(all_types.small_int_ptr IN (?, ?)) AS "result.in", (all_types.small_int_ptr IN (?, ?)) AS "result.in",
(all_types.small_int_ptr IN (( (all_types.small_int_ptr IN ((
SELECT all_types.integer AS "all_types.integer" SELECT all_types.integer AS "all_types.integer"
@ -299,12 +300,12 @@ SELECT (all_types.boolean = all_types.boolean_ptr) AS "EQ1",
(all_types.boolean IS NOT ?) AS "distinct2", (all_types.boolean IS NOT ?) AS "distinct2",
(all_types.boolean IS all_types.boolean_ptr) AS "not_distinct_1", (all_types.boolean IS all_types.boolean_ptr) AS "not_distinct_1",
(all_types.boolean IS ?) AS "NOTDISTINCT2", (all_types.boolean IS ?) AS "NOTDISTINCT2",
all_types.boolean IS TRUE AS "ISTRUE", (all_types.boolean IS TRUE) AS "ISTRUE",
all_types.boolean IS NOT TRUE AS "isnottrue", (all_types.boolean IS NOT TRUE) AS "isnottrue",
all_types.boolean IS FALSE AS "is_False", (all_types.boolean IS FALSE) AS "is_False",
all_types.boolean IS NOT FALSE AS "is not false", (all_types.boolean IS NOT FALSE) AS "is not false",
all_types.boolean IS NULL AS "is unknown", (all_types.boolean IS NULL) AS "is unknown",
all_types.boolean IS NOT NULL AS "is_not_unknown", (all_types.boolean IS NOT NULL) AS "is_not_unknown",
((all_types.boolean AND all_types.boolean) = (all_types.boolean AND all_types.boolean)) AS "complex1", ((all_types.boolean AND all_types.boolean) = (all_types.boolean AND all_types.boolean)) AS "complex1",
((all_types.boolean OR all_types.boolean) = (all_types.boolean AND all_types.boolean)) AS "complex2" ((all_types.boolean OR all_types.boolean) = (all_types.boolean AND all_types.boolean)) AS "complex2"
FROM all_types; FROM all_types;

View file

@ -2,11 +2,12 @@ package sqlite
import ( import (
"context" "context"
"testing"
"time"
"github.com/go-jet/jet/v2/qrm" "github.com/go-jet/jet/v2/qrm"
model2 "github.com/go-jet/jet/v2/tests/.gentestdata/sqlite/sakila/model" model2 "github.com/go-jet/jet/v2/tests/.gentestdata/sqlite/sakila/model"
"github.com/go-jet/jet/v2/tests/.gentestdata/sqlite/sakila/table" "github.com/go-jet/jet/v2/tests/.gentestdata/sqlite/sakila/table"
"testing"
"time"
"github.com/go-jet/jet/v2/internal/testutils" "github.com/go-jet/jet/v2/internal/testutils"
. "github.com/go-jet/jet/v2/sqlite" . "github.com/go-jet/jet/v2/sqlite"
@ -156,7 +157,7 @@ RETURNING link.id AS "link.id",
(link.id + ?) AS "dest.binary_operator", (link.id + ?) AS "dest.binary_operator",
CAST(link.id AS TEXT) AS "dest.cast_operator", CAST(link.id AS TEXT) AS "dest.cast_operator",
(link.name LIKE ?) AS "dest.like_operator", (link.name LIKE ?) AS "dest.like_operator",
link.description IS NULL AS "dest.is_null", (link.description IS NULL) AS "dest.is_null",
(CASE link.name WHEN ? THEN ? WHEN ? THEN ? ELSE ? END) AS "dest.case_operator"; (CASE link.name WHEN ? THEN ? WHEN ? THEN ? ELSE ? END) AS "dest.case_operator";
` `
testutils.AssertStatementSql(t, stmt, expectedSQL, int32(20), "http://www.duckduckgo.com", "DuckDuckGo", nil, int32(20), testutils.AssertStatementSql(t, stmt, expectedSQL, int32(20), "http://www.duckduckgo.com", "DuckDuckGo", nil, int32(20),