Merge pull request #559 from go-jet/cleanup

Cleanup: Simplify expression construction
This commit is contained in:
go-jet 2026-02-02 14:08:04 +01:00 committed by GitHub
commit 4ae762a3d4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
44 changed files with 640 additions and 1117 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,53 +0,0 @@
package jet
// Cast interface
type Cast interface {
AS(castType string) Expression
}
type castImpl struct {
expression Expression
}
// NewCastImpl creates new generic cast
func NewCastImpl(expression Expression) Cast {
castImpl := castImpl{
expression: expression,
}
return &castImpl
}
func (b *castImpl) AS(castType string) Expression {
castExp := &castExpression{
expression: b.expression,
cast: string(castType),
}
castExp.ExpressionInterfaceImpl.Root = castExp
return castExp
}
type castExpression struct {
ExpressionInterfaceImpl
expression Expression
cast string
}
func (b *castExpression) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) {
expression := b.expression
castType := b.cast
if castOverride := out.Dialect.OperatorSerializeOverride("CAST"); castOverride != nil {
castOverride(expression, String(castType))(statement, out, FallTrough(options)...)
return
}
out.WriteString("CAST(")
expression.serialize(statement, out, FallTrough(options)...)
out.WriteString("AS")
out.WriteString(castType + ")")
}

View file

@ -1,11 +0,0 @@
package jet
import (
"testing"
)
func TestCastAS(t *testing.T) {
assertClauseSerialize(t, NewCastImpl(Int(1)).AS("boolean"), "CAST($1 AS boolean)", int64(1))
assertClauseSerialize(t, NewCastImpl(table2Col3).AS("real"), "CAST(table2.col3 AS real)")
assertClauseSerialize(t, NewCastImpl(table2Col3.ADD(table2Col3)).AS("integer"), "CAST((table2.col3 + table2.col3) AS integer)")
}

View file

@ -9,7 +9,6 @@ type Dialect interface {
Name() string Name() string
PackageName() string PackageName() string
OperatorSerializeOverride(operator string) SerializeOverride OperatorSerializeOverride(operator string) SerializeOverride
FunctionSerializeOverride(function string) SerializeOverride
AliasQuoteChar() byte AliasQuoteChar() byte
IdentifierQuoteChar() byte IdentifierQuoteChar() byte
ArgumentPlaceholder() QueryPlaceholderFunc ArgumentPlaceholder() QueryPlaceholderFunc
@ -18,6 +17,7 @@ type Dialect interface {
SerializeOrderBy() func(expression Expression, ascending, nullsFirst *bool) SerializerFunc SerializeOrderBy() func(expression Expression, ascending, nullsFirst *bool) SerializerFunc
ValuesDefaultColumnName(index int) string ValuesDefaultColumnName(index int) string
JsonValueEncode(expr Expression) Expression JsonValueEncode(expr Expression) Expression
RegexpLike(str StringExpression, not bool, pattern StringExpression, caseSensitive bool) SerializerFunc
} }
// SerializerFunc func // SerializerFunc func
@ -34,7 +34,6 @@ type DialectParams struct {
Name string Name string
PackageName string PackageName string
OperatorSerializeOverrides map[string]SerializeOverride OperatorSerializeOverrides map[string]SerializeOverride
FunctionSerializeOverrides map[string]SerializeOverride
AliasQuoteChar byte AliasQuoteChar byte
IdentifierQuoteChar byte IdentifierQuoteChar byte
ArgumentPlaceholder QueryPlaceholderFunc ArgumentPlaceholder QueryPlaceholderFunc
@ -43,6 +42,7 @@ type DialectParams struct {
SerializeOrderBy func(expression Expression, ascending, nullsFirst *bool) SerializerFunc SerializeOrderBy func(expression Expression, ascending, nullsFirst *bool) SerializerFunc
ValuesDefaultColumnName func(index int) string ValuesDefaultColumnName func(index int) string
JsonValueEncode func(expr Expression) Expression JsonValueEncode func(expr Expression) Expression
RegexpLike func(str StringExpression, not bool, pattern StringExpression, caseSensitive bool) SerializerFunc
} }
// NewDialect creates new dialect with params // NewDialect creates new dialect with params
@ -51,7 +51,6 @@ func NewDialect(params DialectParams) Dialect {
name: params.Name, name: params.Name,
packageName: params.PackageName, packageName: params.PackageName,
operatorSerializeOverrides: params.OperatorSerializeOverrides, operatorSerializeOverrides: params.OperatorSerializeOverrides,
functionSerializeOverrides: params.FunctionSerializeOverrides,
aliasQuoteChar: params.AliasQuoteChar, aliasQuoteChar: params.AliasQuoteChar,
identifierQuoteChar: params.IdentifierQuoteChar, identifierQuoteChar: params.IdentifierQuoteChar,
argumentPlaceholder: params.ArgumentPlaceholder, argumentPlaceholder: params.ArgumentPlaceholder,
@ -60,6 +59,7 @@ func NewDialect(params DialectParams) Dialect {
serializeOrderBy: params.SerializeOrderBy, serializeOrderBy: params.SerializeOrderBy,
valuesDefaultColumnName: params.ValuesDefaultColumnName, valuesDefaultColumnName: params.ValuesDefaultColumnName,
jsonValueEncode: params.JsonValueEncode, jsonValueEncode: params.JsonValueEncode,
regexpLike: params.RegexpLike,
} }
} }
@ -67,7 +67,6 @@ type dialectImpl struct {
name string name string
packageName string packageName string
operatorSerializeOverrides map[string]SerializeOverride operatorSerializeOverrides map[string]SerializeOverride
functionSerializeOverrides map[string]SerializeOverride
aliasQuoteChar byte aliasQuoteChar byte
identifierQuoteChar byte identifierQuoteChar byte
argumentPlaceholder QueryPlaceholderFunc argumentPlaceholder QueryPlaceholderFunc
@ -76,6 +75,7 @@ type dialectImpl struct {
serializeOrderBy func(expression Expression, ascending, nullsFirst *bool) SerializerFunc serializeOrderBy func(expression Expression, ascending, nullsFirst *bool) SerializerFunc
valuesDefaultColumnName func(index int) string valuesDefaultColumnName func(index int) string
jsonValueEncode func(expr Expression) Expression jsonValueEncode func(expr Expression) Expression
regexpLike func(str StringExpression, not bool, pattern StringExpression, caseSensitive bool) SerializerFunc
} }
func (d *dialectImpl) Name() string { func (d *dialectImpl) Name() string {
@ -93,13 +93,6 @@ func (d *dialectImpl) OperatorSerializeOverride(operator string) SerializeOverri
return d.operatorSerializeOverrides[operator] return d.operatorSerializeOverrides[operator]
} }
func (d *dialectImpl) FunctionSerializeOverride(function string) SerializeOverride {
if d.functionSerializeOverrides == nil {
return nil
}
return d.functionSerializeOverrides[function]
}
func (d *dialectImpl) AliasQuoteChar() byte { func (d *dialectImpl) AliasQuoteChar() byte {
return d.aliasQuoteChar return d.aliasQuoteChar
} }
@ -133,6 +126,21 @@ func (d *dialectImpl) JsonValueEncode(expr Expression) Expression {
return d.jsonValueEncode(expr) return d.jsonValueEncode(expr)
} }
func (d *dialectImpl) RegexpLike(str StringExpression, not bool, pattern StringExpression, caseSensitive bool) SerializerFunc {
if d.regexpLike != nil {
return d.regexpLike(str, not, pattern, caseSensitive)
}
return func(statement StatementType, out *SQLBuilder, options ...SerializeOption) {
str.serialize(statement, out, FallTrough(options)...)
if not {
out.WriteString("NOT")
}
out.WriteString("REGEXP")
pattern.serialize(statement, out, FallTrough(options)...)
}
}
func arrayOfStringsToMapOfStrings(arr []string) map[string]bool { func arrayOfStringsToMapOfStrings(arr []string) map[string]bool {
ret := map[string]bool{} ret := map[string]bool{}
for _, elem := range arr { for _, elem := range arr {

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,30 @@ 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 { optionalWrap(out, options, func(out *SQLBuilder, options []SerializeOption) {
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)...)
@ -153,57 +150,52 @@ func (c *binaryOperatorExpression) serialize(statement StatementType, out *SQLBu
out.WriteString(c.operator) out.WriteString(c.operator)
c.rhs.serialize(statement, out, FallTrough(options)...) c.rhs.serialize(statement, out, FallTrough(options)...)
} }
})
} }
type expressionListOperator struct { // NewBinaryOperatorExpression creates new binaryOperatorExpression
ExpressionInterfaceImpl func NewBinaryOperatorExpression(lhs, rhs Serializer, operator string, additionalParam ...Expression) Expression {
return newExpression(&binaryOperatorSerializer{
operator string lhs: lhs,
expressions []Expression rhs: rhs,
} additionalParam: OptionalOrDefault(additionalParam, nil),
func newExpressionListOperator(operator string, expressions ...Expression) *expressionListOperator {
ret := &expressionListOperator{
operator: operator, operator: operator,
expressions: expressions, })
}
ret.ExpressionInterfaceImpl.Root = ret
return ret
} }
func newBoolExpressionListOperator(operator string, expressions ...BoolExpression) BoolExpression { type serializersWithOperator struct {
return BoolExp(newExpressionListOperator(operator, ToExpressionList(expressions)...)) operator string
serializers []Serializer
} }
func (elo *expressionListOperator) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) { func (s *serializersWithOperator) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) {
if len(elo.expressions) == 0 { if len(s.serializers) == 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 +206,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
} }
func (b *betweenOperatorSerializer) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) {
optionalWrap(out, options, func(out *SQLBuilder, options []SerializeOption) {
b.expression.serialize(statement, out, FallTrough(options)...)
if b.notBetween {
out.WriteString("NOT")
}
out.WriteString("BETWEEN")
b.min.serialize(statement, out, FallTrough(options)...)
out.WriteString("AND")
b.max.serialize(statement, out, FallTrough(options)...)
})
}
// NewBetweenOperatorExpression creates new BETWEEN operator expression // NewBetweenOperatorExpression creates new BETWEEN operator expression
func NewBetweenOperatorExpression(expression, min, max Expression, notBetween bool) BoolExpression { func NewBetweenOperatorExpression(expression, min, max Expression, notBetween bool) BoolExpression {
newBetweenOperator := &betweenOperatorExpression{ return BoolExp(newExpression(&betweenOperatorSerializer{
expression: expression, expression: expression,
notBetween: notBetween, notBetween: notBetween,
min: min, min: min,
max: max, 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("BETWEEN")
p.min.serialize(statement, out, FallTrough(options)...)
out.WriteString("AND")
p.max.serialize(statement, out, FallTrough(options)...)
}
type customExpression struct {
ExpressionInterfaceImpl
parts []Serializer
}
func CustomExpression(parts ...Serializer) Expression {
ret := customExpression{
parts: parts,
}
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 ---------------//
@ -244,7 +244,7 @@ func leadLagImpl(name string, expr Expression, offsetAndDefault ...interface{})
defaultValue, ok = offsetAndDefault[1].(Expression) defaultValue, ok = offsetAndDefault[1].(Expression)
if !ok { if !ok {
defaultValue = literal(offsetAndDefault[1]) defaultValue = Literal(offsetAndDefault[1])
} }
params = append(params, FixedLiteral(offset), defaultValue) params = append(params, FixedLiteral(offset), defaultValue)
@ -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,53 @@ 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 {
serializeOverrideFunc := serializeOverride(ExpressionListToSerializerList(f.parameters)...)
serializeOverrideFunc(statement, out, FallTrough(options)...)
return
}
addBrackets := !f.noBrackets || len(f.parameters) > 0
if addBrackets {
out.WriteString(f.name + "(") 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 +682,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 +768,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

@ -66,7 +66,7 @@ func TestIntExpressionPOW(t *testing.T) {
func TestIntExpressionBIT_NOT(t *testing.T) { func TestIntExpressionBIT_NOT(t *testing.T) {
assertClauseSerialize(t, BIT_NOT(table2ColInt), "(~ table2.col_int)") assertClauseSerialize(t, BIT_NOT(table2ColInt), "(~ table2.col_int)")
assertClauseSerialize(t, BIT_NOT(Int(11)), "(~ 11)") assertClauseSerialize(t, BIT_NOT(Int(11)), "(~ $1)", int64(11))
} }
func TestIntExpressionBIT_AND(t *testing.T) { func TestIntExpressionBIT_AND(t *testing.T) {

View file

@ -5,48 +5,12 @@ import (
"time" "time"
) )
// LiteralExpression is representation of an escaped literal type literalSerializer struct {
type LiteralExpression interface {
Expression
Value() interface{}
SetConstant(constant bool)
}
type literalExpressionImpl struct {
ExpressionInterfaceImpl
value interface{} value interface{}
constant bool constant bool
} }
func literal(value interface{}, optionalConstant ...bool) *literalExpressionImpl { func (l *literalSerializer) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) {
exp := literalExpressionImpl{value: value}
if len(optionalConstant) > 0 {
exp.constant = optionalConstant[0]
}
exp.ExpressionInterfaceImpl.Root = &exp
return &exp
}
// Literal is injected directly to SQL query, and does not appear in parametrized argument list.
func Literal(value interface{}) *literalExpressionImpl {
exp := literal(value)
return exp
}
// FixedLiteral is injected directly to SQL query, and does not appear in parametrized argument list.
func FixedLiteral(value interface{}) *literalExpressionImpl {
exp := literal(value)
exp.constant = true
return exp
}
func (l *literalExpressionImpl) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) {
if l.constant { if l.constant {
out.insertConstantArgument(l.value) out.insertConstantArgument(l.value)
} else { } else {
@ -54,260 +18,145 @@ func (l *literalExpressionImpl) serialize(statement StatementType, out *SQLBuild
} }
} }
func (l *literalExpressionImpl) Value() interface{} { // Literal is injected directly to SQL query, and does not appear in parametrized argument list.
return l.value func Literal(value interface{}) Expression {
return newExpression(&literalSerializer{
value: value,
constant: false,
})
} }
func (l *literalExpressionImpl) SetConstant(constant bool) { // FixedLiteral is injected directly to SQL query, and does not appear in parametrized argument list.
l.constant = constant func FixedLiteral(value interface{}) Expression {
} return newExpression(&literalSerializer{
value: value,
type integerLiteralExpression struct { constant: true,
literalExpressionImpl })
integerInterfaceImpl
}
func intLiteral(value interface{}) IntegerExpression {
numLiteral := &integerLiteralExpression{}
numLiteral.literalExpressionImpl = *literal(value)
numLiteral.literalExpressionImpl.Root = numLiteral
numLiteral.integerInterfaceImpl.root = numLiteral
return numLiteral
} }
// Int creates a new 64 bit signed integer literal // Int creates a new 64 bit signed integer literal
func Int(value int64) IntegerExpression { func Int(value int64) IntegerExpression {
return intLiteral(value) return IntExp(Literal(value))
} }
// Int8 creates a new 8 bit signed integer literal // Int8 creates a new 8 bit signed integer literal
func Int8(value int8) IntegerExpression { func Int8(value int8) IntegerExpression {
return intLiteral(value) return IntExp(Literal(value))
} }
// Int16 creates a new 16 bit signed integer literal // Int16 creates a new 16 bit signed integer literal
func Int16(value int16) IntegerExpression { func Int16(value int16) IntegerExpression {
return intLiteral(value) return IntExp(Literal(value))
} }
// Int32 creates a new 32 bit signed integer literal // Int32 creates a new 32 bit signed integer literal
func Int32(value int32) IntegerExpression { func Int32(value int32) IntegerExpression {
return intLiteral(value) return IntExp(Literal(value))
} }
// Uint8 creates a new 8 bit unsigned integer literal // Uint8 creates a new 8 bit unsigned integer literal
func Uint8(value uint8) IntegerExpression { func Uint8(value uint8) IntegerExpression {
return intLiteral(value) return IntExp(Literal(value))
} }
// Uint16 creates a new 16 bit unsigned integer literal // Uint16 creates a new 16 bit unsigned integer literal
func Uint16(value uint16) IntegerExpression { func Uint16(value uint16) IntegerExpression {
return intLiteral(value) return IntExp(Literal(value))
} }
// Uint32 creates a new 32 bit unsigned integer literal // Uint32 creates a new 32 bit unsigned integer literal
func Uint32(value uint32) IntegerExpression { func Uint32(value uint32) IntegerExpression {
return intLiteral(value) return IntExp(Literal(value))
} }
// Uint64 creates a new 64 bit unsigned integer literal // Uint64 creates a new 64 bit unsigned integer literal
func Uint64(value uint64) IntegerExpression { func Uint64(value uint64) IntegerExpression {
return intLiteral(value) return IntExp(Literal(value))
}
// ---------------------------------------------------//
type boolLiteralExpression struct {
boolInterfaceImpl
literalExpressionImpl
} }
// Bool creates new bool literal expression // Bool creates new bool literal expression
func Bool(value bool) BoolExpression { func Bool(value bool) BoolExpression {
boolLiteralExpression := boolLiteralExpression{} return BoolExp(Literal(value))
boolLiteralExpression.literalExpressionImpl = *literal(value)
boolLiteralExpression.boolInterfaceImpl.root = &boolLiteralExpression
return &boolLiteralExpression
}
// ---------------------------------------------------//
type floatLiteral struct {
floatInterfaceImpl
literalExpressionImpl
} }
// Float creates new float literal from float64 value // Float creates new float literal from float64 value
func Float(value float64) FloatExpression { func Float(value float64) FloatExpression {
floatLiteral := floatLiteral{} return FloatExp(Literal(value))
floatLiteral.literalExpressionImpl = *literal(value)
floatLiteral.floatInterfaceImpl.root = &floatLiteral
return &floatLiteral
} }
// Decimal creates new float literal from string value // Decimal creates new float literal from string value
func Decimal(value string) FloatExpression { func Decimal(value string) FloatExpression {
floatLiteral := floatLiteral{} return FloatExp(Literal(value))
floatLiteral.literalExpressionImpl = *literal(value)
floatLiteral.floatInterfaceImpl.root = &floatLiteral
return &floatLiteral
}
// ---------------------------------------------------//
type stringLiteral struct {
stringInterfaceImpl
literalExpressionImpl
} }
// String creates new string literal expression // String creates new string literal expression
func String(value string) StringExpression { func String(value string) StringExpression {
stringLiteral := stringLiteral{} return StringExp(Literal(value))
stringLiteral.literalExpressionImpl = *literal(value)
stringLiteral.stringInterfaceImpl.root = &stringLiteral
return &stringLiteral
}
//---------------------------------------------------//
type timeLiteral struct {
timeInterfaceImpl
literalExpressionImpl
} }
// Time creates new time literal expression // Time creates new time literal expression
func Time(hour, minute, second int, nanoseconds ...time.Duration) TimeExpression { func Time(hour, minute, second int, nanoseconds ...time.Duration) TimeExpression {
timeLiteral := &timeLiteral{}
timeStr := fmt.Sprintf("%02d:%02d:%02d", hour, minute, second) timeStr := fmt.Sprintf("%02d:%02d:%02d", hour, minute, second)
timeStr += formatNanoseconds(nanoseconds...) timeStr += formatNanoseconds(nanoseconds...)
timeLiteral.literalExpressionImpl = *literal(timeStr)
timeLiteral.timeInterfaceImpl.root = timeLiteral return TimeExp(Literal(timeStr))
return timeLiteral
} }
// TimeT creates new time literal expression from time.Time object // TimeT creates new time literal expression from time.Time object
func TimeT(t time.Time) TimeExpression { func TimeT(t time.Time) TimeExpression {
timeLiteral := &timeLiteral{} return TimeExp(Literal(t))
timeLiteral.literalExpressionImpl = *literal(t)
timeLiteral.timeInterfaceImpl.root = timeLiteral
return timeLiteral
}
//---------------------------------------------------//
type timezLiteral struct {
timezInterfaceImpl
literalExpressionImpl
} }
// Timez creates new time with time zone literal expression // Timez creates new time with time zone literal expression
func Timez(hour, minute, second int, nanoseconds time.Duration, timezone string) TimezExpression { func Timez(hour, minute, second int, nanoseconds time.Duration, timezone string) TimezExpression {
timezLiteral := timezLiteral{}
timeStr := fmt.Sprintf("%02d:%02d:%02d", hour, minute, second) timeStr := fmt.Sprintf("%02d:%02d:%02d", hour, minute, second)
timeStr += formatNanoseconds(nanoseconds) timeStr += formatNanoseconds(nanoseconds)
timeStr += " " + timezone timeStr += " " + timezone
timezLiteral.literalExpressionImpl = *literal(timeStr)
return TimezExp(literal(timeStr)) return TimezExp(Literal(timeStr))
} }
// TimezT creates new time with time zone literal expression from time.Time object // TimezT creates new time with time zone literal expression from time.Time object
func TimezT(t time.Time) TimezExpression { func TimezT(t time.Time) TimezExpression {
timeLiteral := &timezLiteral{} return TimezExp(Literal(t))
timeLiteral.literalExpressionImpl = *literal(t)
timeLiteral.timezInterfaceImpl.root = timeLiteral
return timeLiteral
}
//---------------------------------------------------//
type timestampLiteral struct {
timestampInterfaceImpl
literalExpressionImpl
} }
// Timestamp creates new timestamp literal expression // Timestamp creates new timestamp literal expression
func Timestamp(year int, month time.Month, day, hour, minute, second int, nanoseconds ...time.Duration) TimestampExpression { func Timestamp(year int, month time.Month, day, hour, minute, second int, nanoseconds ...time.Duration) TimestampExpression {
timestamp := &timestampLiteral{}
timeStr := fmt.Sprintf("%04d-%02d-%02d %02d:%02d:%02d", year, month, day, hour, minute, second) timeStr := fmt.Sprintf("%04d-%02d-%02d %02d:%02d:%02d", year, month, day, hour, minute, second)
timeStr += formatNanoseconds(nanoseconds...) timeStr += formatNanoseconds(nanoseconds...)
timestamp.literalExpressionImpl = *literal(timeStr)
timestamp.timestampInterfaceImpl.root = timestamp return TimestampExp(Literal(timeStr))
return timestamp
} }
// TimestampT creates new timestamp literal expression from time.Time object // TimestampT creates new timestamp literal expression from time.Time object
func TimestampT(t time.Time) TimestampExpression { func TimestampT(t time.Time) TimestampExpression {
timestamp := &timestampLiteral{} return TimestampExp(Literal(t))
timestamp.literalExpressionImpl = *literal(t)
timestamp.timestampInterfaceImpl.root = timestamp
return timestamp
}
//---------------------------------------------------//
type timestampzLiteral struct {
timestampzInterfaceImpl
literalExpressionImpl
} }
// Timestampz creates new timestamp with time zone literal expression // Timestampz creates new timestamp with time zone literal expression
func Timestampz(year int, month time.Month, day, hour, minute, second int, nanoseconds time.Duration, timezone string) TimestampzExpression { func Timestampz(year int, month time.Month, day, hour, minute, second int, nanoseconds time.Duration, timezone string) TimestampzExpression {
timestamp := &timestampzLiteral{}
timeStr := fmt.Sprintf("%04d-%02d-%02d %02d:%02d:%02d", year, month, day, hour, minute, second) timeStr := fmt.Sprintf("%04d-%02d-%02d %02d:%02d:%02d", year, month, day, hour, minute, second)
timeStr += formatNanoseconds(nanoseconds) timeStr += formatNanoseconds(nanoseconds)
timeStr += " " + timezone timeStr += " " + timezone
timestamp.literalExpressionImpl = *literal(timeStr) return TimestampzExp(Literal(timeStr))
timestamp.timestampzInterfaceImpl.root = timestamp
return timestamp
} }
// TimestampzT creates new timestamp literal expression from time.Time object // TimestampzT creates new timestamp literal expression from time.Time object
func TimestampzT(t time.Time) TimestampzExpression { func TimestampzT(t time.Time) TimestampzExpression {
timestamp := &timestampzLiteral{} return TimestampzExp(Literal(t))
timestamp.literalExpressionImpl = *literal(t)
timestamp.timestampzInterfaceImpl.root = timestamp
return timestamp
}
//---------------------------------------------------//
type dateLiteral struct {
dateInterfaceImpl
literalExpressionImpl
} }
// Date creates new date literal expression // Date creates new date literal expression
func Date(year int, month time.Month, day int) DateExpression { func Date(year int, month time.Month, day int) DateExpression {
dateLiteral := &dateLiteral{}
timeStr := fmt.Sprintf("%04d-%02d-%02d", year, month, day) timeStr := fmt.Sprintf("%04d-%02d-%02d", year, month, day)
dateLiteral.literalExpressionImpl = *literal(timeStr) return DateExp(Literal(timeStr))
dateLiteral.dateInterfaceImpl.root = dateLiteral
return dateLiteral
} }
// DateT creates new date literal expression from time.Time object // DateT creates new date literal expression from time.Time object
func DateT(t time.Time) DateExpression { func DateT(t time.Time) DateExpression {
dateLiteral := &dateLiteral{} return DateExp(Literal(t))
dateLiteral.literalExpressionImpl = *literal(t)
dateLiteral.dateInterfaceImpl.root = dateLiteral
return dateLiteral
} }
func formatNanoseconds(nanoseconds ...time.Duration) string { func formatNanoseconds(nanoseconds ...time.Duration) string {
@ -330,86 +179,35 @@ func formatNanoseconds(nanoseconds ...time.Duration) string {
var ( var (
// NULL is jet equivalent of SQL NULL // NULL is jet equivalent of SQL NULL
NULL = newNullLiteral() NULL = newExpression(Keyword("NULL"))
// STAR is jet equivalent of SQL * // STAR is jet equivalent of SQL *
STAR = newStarLiteral() STAR = newExpression(Keyword("*"))
// PLUS_INFINITY is jet equivalent for sql infinity // PLUS_INFINITY is jet equivalent for sql infinity
PLUS_INFINITY = String("infinity") PLUS_INFINITY = String("infinity")
// MINUS_INFINITY is jet equivalent for sql -infinity // MINUS_INFINITY is jet equivalent for sql -infinity
MINUS_INFINITY = String("-infinity") MINUS_INFINITY = String("-infinity")
) )
type nullLiteral struct {
ExpressionInterfaceImpl
}
func newNullLiteral() Expression {
nullExpression := &nullLiteral{}
nullExpression.ExpressionInterfaceImpl.Root = nullExpression
return nullExpression
}
func (n *nullLiteral) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) {
out.WriteString("NULL")
}
// --------------------------------------------------//
type starLiteral struct {
ExpressionInterfaceImpl
}
func newStarLiteral() Expression {
starExpression := &starLiteral{}
starExpression.ExpressionInterfaceImpl.Root = starExpression
return starExpression
}
func (n *starLiteral) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) {
out.WriteString("*")
}
//---------------------------------------------------// //---------------------------------------------------//
type rawExpression struct { type rawSerializer struct {
ExpressionInterfaceImpl
Raw string Raw string
NamedArgument map[string]interface{} NamedArgument map[string]interface{}
noWrap bool
} }
func (n *rawExpression) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) { func (n *rawSerializer) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) {
if !n.noWrap && !contains(options, NoWrap) { optionalWrap(out, options, func(out *SQLBuilder, options []SerializeOption) {
out.WriteByte('(')
}
out.insertRawQuery(n.Raw, n.NamedArgument) out.insertRawQuery(n.Raw, n.NamedArgument)
})
if !n.noWrap && !contains(options, NoWrap) {
out.WriteByte(')')
}
} }
// Raw can be used for any unsupported functions, operators or expressions. // Raw can be used for any unsupported functions, operators or expressions.
// For example: Raw("current_database()") // For example: Raw("current_database()")
func Raw(raw string, namedArgs ...map[string]interface{}) Expression { func Raw(raw string, namedArgs ...map[string]interface{}) Expression {
var namedArguments map[string]interface{} return newExpression(&rawSerializer{
if len(namedArgs) > 0 {
namedArguments = namedArgs[0]
}
rawExp := &rawExpression{
Raw: raw, Raw: raw,
NamedArgument: namedArguments, NamedArgument: singleOptional(namedArgs),
} })
rawExp.ExpressionInterfaceImpl.Root = rawExp
return rawExp
} }
// RawBool helper that for raw string boolean expressions // RawBool helper that for raw string boolean expressions

View file

@ -16,9 +16,6 @@ func NOT(exp BoolExpression) BoolExpression {
// BIT_NOT inverts every bit in integer expression result // BIT_NOT inverts every bit in integer expression result
func BIT_NOT(expr IntegerExpression) IntegerExpression { func BIT_NOT(expr IntegerExpression) IntegerExpression {
if literalExp, ok := expr.(LiteralExpression); ok {
literalExp.SetConstant(true)
}
return newPrefixIntegerOperatorExpression(expr, "~") return newPrefixIntegerOperatorExpression(expr, "~")
} }
@ -131,10 +128,8 @@ type caseOperatorImpl struct {
// CASE create CASE operator with optional list of expressions // CASE create CASE operator with optional list of expressions
func CASE(expression ...Expression) CaseOperator { func CASE(expression ...Expression) CaseOperator {
caseExp := &caseOperatorImpl{} caseExp := &caseOperatorImpl{
expression: singleOptional(expression),
if len(expression) > 0 {
caseExp.expression = expression[0]
} }
caseExp.ExpressionInterfaceImpl.Root = caseExp caseExp.ExpressionInterfaceImpl.Root = caseExp

View file

@ -34,25 +34,20 @@ func newOrderSetAggregateFunction(name string, fraction FloatExpression) *OrderS
// WITHIN_GROUP_ORDER_BY specifies ordered set of aggregated argument values // WITHIN_GROUP_ORDER_BY specifies ordered set of aggregated argument values
func (p *OrderSetAggregateFunc) WITHIN_GROUP_ORDER_BY(orderBy OrderByClause) Expression { func (p *OrderSetAggregateFunc) WITHIN_GROUP_ORDER_BY(orderBy OrderByClause) Expression {
p.orderBy = ORDER_BY(orderBy) p.orderBy = ORDER_BY(orderBy)
return newOrderSetAggregateFuncExpression(*p) return newOrderSetAggregateFuncExpression(p)
} }
func newOrderSetAggregateFuncExpression(aggFunc OrderSetAggregateFunc) *orderSetAggregateFuncExpression { func newOrderSetAggregateFuncExpression(aggFunc *OrderSetAggregateFunc) Expression {
ret := &orderSetAggregateFuncExpression{ return newExpression(&orderSetAggregateFuncSerializer{
OrderSetAggregateFunc: aggFunc, OrderSetAggregateFunc: aggFunc,
} })
ret.ExpressionInterfaceImpl.Root = ret
return ret
} }
type orderSetAggregateFuncExpression struct { type orderSetAggregateFuncSerializer struct {
ExpressionInterfaceImpl *OrderSetAggregateFunc
OrderSetAggregateFunc
} }
func (p *orderSetAggregateFuncExpression) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) { func (p *orderSetAggregateFuncSerializer) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) {
out.WriteString(p.name) out.WriteString(p.name)
if p.fraction != nil { if p.fraction != nil {

View file

@ -16,10 +16,7 @@ func RawStatement(dialect Dialect, rawQuery string, namedArgument ...map[string]
root: nil, root: nil,
}, },
RawQuery: rawQuery, RawQuery: rawQuery,
} NamedArguments: singleOptional(namedArgument),
if len(namedArgument) > 0 {
newRawStatement.NamedArguments = namedArgument[0]
} }
newRawStatement.root = &newRawStatement newRawStatement.root = &newRawStatement

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,54 @@ 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(&customSerializer{
parts: parts,
})
}
// AtomicCustomExpression creates new custom expression. When serialized does not require parentheses.
func AtomicCustomExpression(parts ...Serializer) Expression {
return newExpression(&customSerializer{
parts: parts,
atomic: true,
})
}
type customSerializer struct {
parts []Serializer
atomic bool
}
func (c *customSerializer) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) {
if c.atomic {
for _, expr := range c.parts {
expr.serialize(statement, out, without(options, NoWrap)...)
}
} else {
optionalWrap(out, options, func(out *SQLBuilder, options []SerializeOption) {
for _, expr := range c.parts {
expr.serialize(statement, out, options...)
}
})
}
}
func optionalWrap(out *SQLBuilder, options []SerializeOption, ser func(out *SQLBuilder, options []SerializeOption)) {
if !contains(options, NoWrap) {
out.WriteString("(")
}
ser(out, without(options, NoWrap))
if !contains(options, NoWrap) {
out.WriteString(")")
}
}
func wrap(expressions ...Expression) Expression {
return newFunc("", expressions)
}

View file

@ -85,11 +85,33 @@ func (s *stringInterfaceImpl) NOT_LIKE(pattern StringExpression) BoolExpression
} }
func (s *stringInterfaceImpl) REGEXP_LIKE(pattern StringExpression, caseSensitive ...bool) BoolExpression { func (s *stringInterfaceImpl) REGEXP_LIKE(pattern StringExpression, caseSensitive ...bool) BoolExpression {
return newBinaryBoolOperatorExpression(s.root, pattern, StringRegexpLikeOperator, Bool(len(caseSensitive) > 0 && caseSensitive[0])) return BoolExp(newExpression(&regexpLikeSerializer{
str: s.root,
pattern: pattern,
caseSensitive: len(caseSensitive) > 0 && caseSensitive[0],
}))
} }
func (s *stringInterfaceImpl) NOT_REGEXP_LIKE(pattern StringExpression, caseSensitive ...bool) BoolExpression { func (s *stringInterfaceImpl) NOT_REGEXP_LIKE(pattern StringExpression, caseSensitive ...bool) BoolExpression {
return newBinaryBoolOperatorExpression(s.root, pattern, StringNotRegexpLikeOperator, Bool(len(caseSensitive) > 0 && caseSensitive[0])) return BoolExp(newExpression(&regexpLikeSerializer{
not: true,
str: s.root,
pattern: pattern,
caseSensitive: len(caseSensitive) > 0 && caseSensitive[0],
}))
}
type regexpLikeSerializer struct {
not bool
str StringExpression
pattern StringExpression
caseSensitive bool
}
func (r *regexpLikeSerializer) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) {
optionalWrap(out, options, func(out *SQLBuilder, options []SerializeOption) {
out.Dialect.RegexpLike(r.str, r.not, r.pattern, r.caseSensitive)(statement, out, options...)
})
} }
// ---------------------------------------------------// // ---------------------------------------------------//

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
@ -160,7 +160,7 @@ func ToSerializerValue(value interface{}) Serializer {
return clause return clause
} }
return literal(value) return Literal(value)
} }
// UnwindRowFromModel func // UnwindRowFromModel func
@ -189,7 +189,7 @@ func UnwindRowFromModel(columns []Column, data interface{}) []Serializer {
field = reflect.Indirect(structField).Interface() field = reflect.Indirect(structField).Interface()
} }
row[i] = literal(field) row[i] = Literal(field)
} }
return row return row
@ -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 singleOptional[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

@ -1,25 +1,25 @@
package mysql package mysql
import ( import (
"github.com/go-jet/jet/v2/internal/jet"
"strconv" "strconv"
"github.com/go-jet/jet/v2/internal/jet"
) )
type cast struct { // CAST function converts an expr (of any type) into later specified datatype.
jet.Cast func CAST(expr Expression) *cast {
return &cast{
expr: expr,
}
} }
// CAST function converts a expr (of any type) into latter specified datatype. type cast struct {
func CAST(expr Expression) *cast { expr Expression
ret := &cast{}
ret.Cast = jet.NewCastImpl(expr)
return ret
} }
// AS casts expressions to castType // AS casts expressions to castType
func (c *cast) AS(castType string) Expression { func (c *cast) AS(castType string) Expression {
return c.Cast.AS(castType) return jet.AtomicCustomExpression(Token("CAST("), c.expr, Token("AS "+castType+")"))
} }
// AS_DATETIME cast expression to DATETIME type // AS_DATETIME cast expression to DATETIME type

View file

@ -12,8 +12,6 @@ var Dialect = newDialect()
func newDialect() jet.Dialect { func newDialect() jet.Dialect {
operatorSerializeOverrides := map[string]jet.SerializeOverride{} operatorSerializeOverrides := map[string]jet.SerializeOverride{}
operatorSerializeOverrides[jet.StringRegexpLikeOperator] = mysqlREGEXPLIKEoperator
operatorSerializeOverrides[jet.StringNotRegexpLikeOperator] = mysqlNOTREGEXPLIKEoperator
operatorSerializeOverrides["IS DISTINCT FROM"] = mysqlISDISTINCTFROM operatorSerializeOverrides["IS DISTINCT FROM"] = mysqlISDISTINCTFROM
operatorSerializeOverrides["IS NOT DISTINCT FROM"] = mysqlISNOTDISTINCTFROM operatorSerializeOverrides["IS NOT DISTINCT FROM"] = mysqlISNOTDISTINCTFROM
operatorSerializeOverrides["/"] = mysqlDivision operatorSerializeOverrides["/"] = mysqlDivision
@ -42,16 +40,17 @@ 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"))
} }
return expr return expr
}, },
RegexpLike: regexpLikeOperator,
} }
return jet.NewDialect(mySQLDialectParams) return jet.NewDialect(mySQLDialectParams)
@ -144,20 +143,12 @@ func mysqlISDISTINCTFROM(expressions ...jet.Serializer) jet.SerializerFunc {
} }
} }
func mysqlREGEXPLIKEoperator(expressions ...jet.Serializer) jet.SerializerFunc { func regexpLikeOperator(str StringExpression, not bool, pattern StringExpression, caseSensitive bool) jet.SerializerFunc {
return func(statement jet.StatementType, out *jet.SQLBuilder, options ...jet.SerializeOption) { return func(statement jet.StatementType, out *jet.SQLBuilder, options ...jet.SerializeOption) {
if len(expressions) < 2 { jet.Serialize(str, statement, out, options...)
panic("jet: invalid number of expressions for operator")
}
jet.Serialize(expressions[0], statement, out, options...) if not {
out.WriteString("NOT")
caseSensitive := false
if len(expressions) >= 3 {
if stringLiteral, ok := expressions[2].(jet.LiteralExpression); ok {
caseSensitive = stringLiteral.Value().(bool)
}
} }
out.WriteString("REGEXP") out.WriteString("REGEXP")
@ -166,33 +157,7 @@ func mysqlREGEXPLIKEoperator(expressions ...jet.Serializer) jet.SerializerFunc {
out.WriteString("BINARY") out.WriteString("BINARY")
} }
jet.Serialize(expressions[1], statement, out, options...) jet.Serialize(pattern, statement, out, options...)
}
}
func mysqlNOTREGEXPLIKEoperator(expressions ...jet.Serializer) jet.SerializerFunc {
return func(statement jet.StatementType, out *jet.SQLBuilder, options ...jet.SerializeOption) {
if len(expressions) < 2 {
panic("jet: invalid number of expressions for operator")
}
jet.Serialize(expressions[0], statement, out, options...)
caseSensitive := false
if len(expressions) >= 3 {
if stringLiteral, ok := expressions[2].(jet.LiteralExpression); ok {
caseSensitive = stringLiteral.Value().(bool)
}
}
out.WriteString("NOT REGEXP")
if caseSensitive {
out.WriteString("BINARY")
}
jet.Serialize(expressions[1], statement, out, options...)
} }
} }

View file

@ -48,7 +48,7 @@ func TestRawInvalidArguments(t *testing.T) {
func TestRawType(t *testing.T) { func TestRawType(t *testing.T) {
assertSerialize(t, RawBool("table.colInt < :float", RawArgs{":float": 11.22}).IS_FALSE(), assertSerialize(t, RawBool("table.colInt < :float", RawArgs{":float": 11.22}).IS_FALSE(),
"(table.colInt < ?) IS FALSE", 11.22) "((table.colInt < ?) IS FALSE)", 11.22)
assertSerialize(t, RawFloat("table.colInt + &float", RawArgs{"&float": 11.22}).EQ(Float(3.14)), assertSerialize(t, RawFloat("table.colInt + &float", RawArgs{"&float": 11.22}).EQ(Float(3.14)),
"((table.colInt + ?) = ?)", 11.22, 3.14) "((table.colInt + ?) = ?)", 11.22, 3.14)

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

@ -7,21 +7,19 @@ import (
"github.com/go-jet/jet/v2/internal/jet" "github.com/go-jet/jet/v2/internal/jet"
) )
type cast struct {
jet.Cast
}
// CAST function converts an expr (of any type) into later specified datatype. // CAST function converts an expr (of any type) into later specified datatype.
func CAST(expr Expression) *cast { func CAST(expr Expression) *cast {
ret := &cast{} return &cast{
ret.Cast = jet.NewCastImpl(expr) expr: expr,
}
return ret }
type cast struct {
expr Expression
} }
// AS casts expression as castType
func (b *cast) AS(castType string) Expression { func (b *cast) AS(castType string) Expression {
return b.Cast.AS(castType) return jet.AtomicCustomExpression(b.expr, Token("::"+castType))
} }
// AS_BOOL casts expression as bool type // AS_BOOL casts expression as bool type

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.
@ -12,15 +13,10 @@ var Dialect = newDialect()
func newDialect() jet.Dialect { func newDialect() jet.Dialect {
operatorSerializeOverrides := map[string]jet.SerializeOverride{}
operatorSerializeOverrides[jet.StringRegexpLikeOperator] = postgresREGEXPLIKEoperator
operatorSerializeOverrides[jet.StringNotRegexpLikeOperator] = postgresNOTREGEXPLIKEoperator
operatorSerializeOverrides["CAST"] = postgresCAST
dialectParams := jet.DialectParams{ dialectParams := jet.DialectParams{
Name: "PostgreSQL", Name: "PostgreSQL",
PackageName: "postgres", PackageName: "postgres",
OperatorSerializeOverrides: operatorSerializeOverrides, OperatorSerializeOverrides: nil,
AliasQuoteChar: '"', AliasQuoteChar: '"',
IdentifierQuoteChar: '"', IdentifierQuoteChar: '"',
ArgumentPlaceholder: func(ord int) string { ArgumentPlaceholder: func(ord int) string {
@ -42,12 +38,13 @@ 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'`))
} }
return expr return expr
}, },
RegexpLike: regexpLike,
} }
return jet.NewDialect(dialectParams) return jet.NewDialect(dialectParams)
@ -62,80 +59,23 @@ func argumentToString(value any) (string, bool) {
return "", false return "", false
} }
func postgresCAST(expressions ...jet.Serializer) jet.SerializerFunc { func regexpLike(str jet.StringExpression, not bool, pattern jet.StringExpression, caseSensitive bool) jet.SerializerFunc {
return func(statement jet.StatementType, out *jet.SQLBuilder, options ...jet.SerializeOption) { return func(statement jet.StatementType, out *jet.SQLBuilder, options ...jet.SerializeOption) {
if len(expressions) < 2 { jet.Serialize(str, statement, out, options...)
panic("jet: invalid number of expressions for operator")
}
expression := expressions[0] var notOperator string
litExpr, ok := expressions[1].(jet.LiteralExpression) if not {
notOperator = "!"
if !ok {
panic("jet: cast invalid cast type")
}
castType, ok := litExpr.Value().(string)
if !ok {
panic("jet: cast type is not string")
}
jet.Serialize(expression, statement, out, options...)
out.WriteString("::" + castType)
}
}
func postgresREGEXPLIKEoperator(expressions ...jet.Serializer) jet.SerializerFunc {
return func(statement jet.StatementType, out *jet.SQLBuilder, options ...jet.SerializeOption) {
if len(expressions) < 2 {
panic("jet: invalid number of expressions for operator")
}
jet.Serialize(expressions[0], statement, out, options...)
caseSensitive := false
if len(expressions) >= 3 {
if stringLiteral, ok := expressions[2].(jet.LiteralExpression); ok {
caseSensitive = stringLiteral.Value().(bool)
}
} }
if caseSensitive { if caseSensitive {
out.WriteString("~") out.WriteString(notOperator + "~")
} else { } else {
out.WriteString("~*") out.WriteString(notOperator + "~*")
} }
jet.Serialize(expressions[1], statement, out, options...) jet.Serialize(pattern, statement, out, options...)
}
}
func postgresNOTREGEXPLIKEoperator(expressions ...jet.Serializer) jet.SerializerFunc {
return func(statement jet.StatementType, out *jet.SQLBuilder, options ...jet.SerializeOption) {
if len(expressions) < 2 {
panic("jet: invalid number of expressions for operator")
}
jet.Serialize(expressions[0], statement, out, options...)
caseSensitive := false
if len(expressions) >= 3 {
if stringLiteral, ok := expressions[2].(jet.LiteralExpression); ok {
caseSensitive = stringLiteral.Value().(bool)
}
}
if caseSensitive {
out.WriteString("!~")
} else {
out.WriteString("!~*")
}
jet.Serialize(expressions[1], statement, out, options...)
} }
} }

View file

@ -58,7 +58,7 @@ func TestRawInvalidArguments(t *testing.T) {
func TestRawHelperMethods(t *testing.T) { func TestRawHelperMethods(t *testing.T) {
assertSerialize(t, RawBool("table.colInt < :float", RawArgs{":float": 11.22}).IS_FALSE(), assertSerialize(t, RawBool("table.colInt < :float", RawArgs{":float": 11.22}).IS_FALSE(),
"(table.colInt < $1) IS FALSE", 11.22) "((table.colInt < $1) IS FALSE)", 11.22)
assertSerialize(t, RawFloat("table.colInt + :float", RawArgs{":float": 11.22}).EQ(Float(3.14)), assertSerialize(t, RawFloat("table.colInt + :float", RawArgs{":float": 11.22}).EQ(Float(3.14)),
"((table.colInt + $1) = $2)", 11.22, 3.14) "((table.colInt + $1) = $2)", 11.22, 3.14)

View file

@ -184,12 +184,12 @@ var CHR = jet.CHR
// CONCAT adds two or more expressions together // CONCAT adds two or more expressions together
var CONCAT = func(expressions ...Expression) StringExpression { var CONCAT = func(expressions ...Expression) StringExpression {
return jet.CONCAT(explicitLiteralCasts(expressions...)...) return jet.CONCAT(expressions...)
} }
// CONCAT_WS adds two or more expressions together with a separator. // CONCAT_WS adds two or more expressions together with a separator.
func CONCAT_WS(separator Expression, expressions ...Expression) StringExpression { func CONCAT_WS(separator Expression, expressions ...Expression) StringExpression {
return jet.CONCAT_WS(explicitLiteralCast(separator), explicitLiteralCasts(expressions...)...) return jet.CONCAT_WS(separator, expressions...)
} }
// Character encodings for CONVERT, CONVERT_FROM and CONVERT_TO functions // Character encodings for CONVERT, CONVERT_FROM and CONVERT_TO functions
@ -239,7 +239,7 @@ var DECODE = jet.DECODE
// FORMAT formats the arguments according to a format string. This function is similar to the C function sprintf. // FORMAT formats the arguments according to a format string. This function is similar to the C function sprintf.
func FORMAT(formatStr StringExpression, formatArgs ...Expression) StringExpression { func FORMAT(formatStr StringExpression, formatArgs ...Expression) StringExpression {
return jet.FORMAT(formatStr, explicitLiteralCasts(formatArgs...)...) return jet.FORMAT(formatStr, formatArgs...)
} }
// INITCAP converts the first letter of each word to upper case // INITCAP converts the first letter of each word to upper case
@ -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 -------------//
@ -578,55 +578,19 @@ var EXISTS = jet.EXISTS
// CASE create CASE operator with optional list of expressions // CASE create CASE operator with optional list of expressions
var CASE = jet.CASE var CASE = jet.CASE
func explicitLiteralCasts(expressions ...Expression) []jet.Expression {
ret := []jet.Expression{}
for _, exp := range expressions {
ret = append(ret, explicitLiteralCast(exp))
}
return ret
}
func explicitLiteralCast(expresion Expression) jet.Expression {
if _, ok := expresion.(jet.LiteralExpression); !ok {
return expresion
}
switch expresion.(type) {
case jet.BoolExpression:
return CAST(expresion).AS_BOOL()
case jet.IntegerExpression:
return CAST(expresion).AS_INTEGER()
case jet.FloatExpression:
return CAST(expresion).AS_NUMERIC()
case jet.StringExpression:
return CAST(expresion).AS_TEXT()
}
return expresion
}
// MODE computes the most frequent value of the aggregated argument // MODE computes the most frequent value of the aggregated argument
var MODE = jet.MODE var MODE = jet.MODE
// PERCENTILE_CONT computes a value corresponding to the specified fraction within the ordered set of // PERCENTILE_CONT computes a value corresponding to the specified fraction within the ordered set of
// aggregated argument values. This will interpolate between adjacent input items if needed. // aggregated argument values. This will interpolate between adjacent input items if needed.
func PERCENTILE_CONT(fraction FloatExpression) *jet.OrderSetAggregateFunc { func PERCENTILE_CONT(fraction FloatExpression) *jet.OrderSetAggregateFunc {
return jet.PERCENTILE_CONT(castFloatLiteral(fraction)) return jet.PERCENTILE_CONT(fraction)
} }
// PERCENTILE_DISC computes the first value within the ordered set of aggregated argument values whose position // PERCENTILE_DISC computes the first value within the ordered set of aggregated argument values whose position
// in the ordering equals or exceeds the specified fraction. The aggregated argument must be of a sortable type. // in the ordering equals or exceeds the specified fraction. The aggregated argument must be of a sortable type.
func PERCENTILE_DISC(fraction FloatExpression) *jet.OrderSetAggregateFunc { func PERCENTILE_DISC(fraction FloatExpression) *jet.OrderSetAggregateFunc {
return jet.PERCENTILE_DISC(castFloatLiteral(fraction)) return jet.PERCENTILE_DISC(fraction)
}
func castFloatLiteral(fraction FloatExpression) FloatExpression {
if _, ok := fraction.(jet.LiteralExpression); ok {
return CAST(fraction).AS_DOUBLE() // to make postgres aware of the type
}
return fraction
} }
// ----------------- Group By operators --------------------------// // ----------------- Group By operators --------------------------//

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

@ -23,7 +23,7 @@ func TestINTERVAL(t *testing.T) {
assertSerialize(t, INTERVAL(1, YEAR, 10, MONTH, 20, DAY), "INTERVAL '1 YEAR 10 MONTH 20 DAY'") assertSerialize(t, INTERVAL(1, YEAR, 10, MONTH, 20, DAY), "INTERVAL '1 YEAR 10 MONTH 20 DAY'")
assertSerialize(t, INTERVAL(1, YEAR, 10, MONTH, 20, DAY, 3, HOUR), "INTERVAL '1 YEAR 10 MONTH 20 DAY 3 HOUR'") assertSerialize(t, INTERVAL(1, YEAR, 10, MONTH, 20, DAY, 3, HOUR), "INTERVAL '1 YEAR 10 MONTH 20 DAY 3 HOUR'")
assertSerialize(t, INTERVAL(1, YEAR).IS_NOT_NULL(), "INTERVAL '1 YEAR' IS NOT NULL") assertSerialize(t, INTERVAL(1, YEAR).IS_NOT_NULL(), "(INTERVAL '1 YEAR' IS NOT NULL)")
assertProjectionSerialize(t, INTERVAL(1, YEAR).AS("one year"), `INTERVAL '1 YEAR' AS "one year"`) assertProjectionSerialize(t, INTERVAL(1, YEAR).AS("one year"), `INTERVAL '1 YEAR' AS "one year"`)
f := 5.2 f := 5.2

View file

@ -2,9 +2,10 @@ package postgres
import ( import (
"fmt" "fmt"
"github.com/lib/pq"
"time" "time"
"github.com/lib/pq"
"github.com/go-jet/jet/v2/internal/jet" "github.com/go-jet/jet/v2/internal/jet"
) )

View file

@ -4,21 +4,20 @@ import (
"github.com/go-jet/jet/v2/internal/jet" "github.com/go-jet/jet/v2/internal/jet"
) )
type cast struct { // CAST function converts an expr (of any type) into later specified datatype.
jet.Cast func CAST(expr Expression) *cast {
return &cast{
expr: expr,
}
} }
// CAST function converts a expr (of any type) into latter specified datatype. type cast struct {
func CAST(expr Expression) *cast { expr Expression
ret := &cast{}
ret.Cast = jet.NewCastImpl(expr)
return ret
} }
// AS casts expressions to castType // AS casts expressions to castType
func (c *cast) AS(castType string) Expression { func (c *cast) AS(castType string) Expression {
return c.Cast.AS(castType) return jet.AtomicCustomExpression(Token("CAST("), c.expr, Token("AS "+castType+")"))
} }
// AS_TEXT cast expression to TEXT type // AS_TEXT cast expression to TEXT type

View file

@ -1,8 +1,9 @@
package sqlite package sqlite
import ( import (
"github.com/stretchr/testify/require"
"testing" "testing"
"github.com/stretchr/testify/require"
) )
func TestRaw(t *testing.T) { func TestRaw(t *testing.T) {
@ -46,7 +47,7 @@ func TestRawInvalidArguments(t *testing.T) {
func TestRawType(t *testing.T) { func TestRawType(t *testing.T) {
assertSerialize(t, RawBool("table.colInt < :float", RawArgs{":float": 11.22}).IS_FALSE(), assertSerialize(t, RawBool("table.colInt < :float", RawArgs{":float": 11.22}).IS_FALSE(),
"(table.colInt < ?) IS FALSE", 11.22) "((table.colInt < ?) IS FALSE)", 11.22)
assertSerialize(t, RawFloat("table.colInt + &float", RawArgs{"&float": 11.22}).EQ(Float(3.14)), assertSerialize(t, RawFloat("table.colInt + &float", RawArgs{"&float": 11.22}).EQ(Float(3.14)),
"((table.colInt + ?) = ?)", 11.22, 3.14) "((table.colInt + ?) = ?)", 11.22, 3.14)

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.*"`
} }
allowUnusedColumns(func() {
err := query.Query(db, &dest) err := query.Query(db, &dest)
require.NoError(t, err) require.NoError(t, err)
})
testutils.AssertJSON(t, dest, ` testutils.AssertJSON(t, dest, `
[ [
{ {
@ -640,10 +648,10 @@ func TestStringOperators(t *testing.T) {
LTRIM(String("Ltrim"), String("A")), LTRIM(String("Ltrim"), String("A")),
RTRIM(String("rtrim")), RTRIM(String("rtrim")),
RTRIM(AllTypes.VarChar, String("B")), RTRIM(AllTypes.VarChar, String("B")),
CHR(Int(65)), CHR(Int8(65)),
CONCAT(AllTypes.VarCharPtr, AllTypes.VarCharPtr, String("aaa"), Int(1)), CONCAT(AllTypes.VarCharPtr, AllTypes.VarCharPtr, Text("aaa"), Int8(1)),
CONCAT(Bool(false), Int(1), Float(22.2), String("test test")), CONCAT(Bool(false), Int16(1), Real(22.2), Text("test test")),
CONCAT_WS(String("string1"), Int(1), Float(11.22), String("bytea"), Bool(false)), //Float(11.12)), CONCAT_WS(Text("string1"), Int64(1), Real(11.22), Text("bytea"), Bool(false)), //Float(11.12)),
CONVERT(Bytea("bytea"), UTF8, LATIN1), CONVERT(Bytea("bytea"), UTF8, LATIN1),
CONVERT(AllTypes.Bytea, UTF8, LATIN1), CONVERT(AllTypes.Bytea, UTF8, LATIN1),
CONVERT_FROM(Bytea("text_in_utf8"), UTF8), CONVERT_FROM(Bytea("text_in_utf8"), UTF8),
@ -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
@ -1109,8 +1117,8 @@ func TestIntegerOperators(t *testing.T) {
AllTypes.SmallInt.BIT_XOR(AllTypes.SmallInt).AS("bit xor 1"), AllTypes.SmallInt.BIT_XOR(AllTypes.SmallInt).AS("bit xor 1"),
AllTypes.SmallInt.BIT_XOR(Int(11)).AS("bit xor 2"), AllTypes.SmallInt.BIT_XOR(Int(11)).AS("bit xor 2"),
BIT_NOT(Int(-1).MUL(AllTypes.SmallInt)).AS("bit_not_1"), BIT_NOT(Int32(-1).MUL(AllTypes.SmallInt)).AS("bit_not_1"),
BIT_NOT(Int(-11)).AS("bit_not_2"), BIT_NOT(Int32(-11)).AS("bit_not_2"),
AllTypes.SmallInt.BIT_SHIFT_LEFT(AllTypes.SmallInt.DIV(Int8(2))).AS("bit shift left 1"), AllTypes.SmallInt.BIT_SHIFT_LEFT(AllTypes.SmallInt.DIV(Int8(2))).AS("bit shift left 1"),
AllTypes.SmallInt.BIT_SHIFT_LEFT(Int(4)).AS("bit shift left 2"), AllTypes.SmallInt.BIT_SHIFT_LEFT(Int(4)).AS("bit shift left 2"),
@ -1122,8 +1130,6 @@ func TestIntegerOperators(t *testing.T) {
CBRT(ABSi(AllTypes.BigInt)).AS("cbrt"), CBRT(ABSi(AllTypes.BigInt)).AS("cbrt"),
).LIMIT(2) ).LIMIT(2)
// fmt.Println(query.Sql())
testutils.AssertStatementSql(t, query, ` testutils.AssertStatementSql(t, query, `
SELECT all_types.big_int AS "all_types.big_int", SELECT all_types.big_int AS "all_types.big_int",
all_types.big_int_ptr AS "all_types.big_int_ptr", all_types.big_int_ptr AS "all_types.big_int_ptr",
@ -1165,17 +1171,17 @@ SELECT all_types.big_int AS "all_types.big_int",
(all_types.small_int | $20) AS "bit or 2", (all_types.small_int | $20) AS "bit or 2",
(all_types.small_int # all_types.small_int) AS "bit xor 1", (all_types.small_int # all_types.small_int) AS "bit xor 1",
(all_types.small_int # $21) AS "bit xor 2", (all_types.small_int # $21) AS "bit xor 2",
(~ ($22 * all_types.small_int)) AS "bit_not_1", (~ ($22::integer * all_types.small_int)) AS "bit_not_1",
(~ -11) AS "bit_not_2", (~ $23::integer) AS "bit_not_2",
(all_types.small_int << (all_types.small_int / $23::smallint)) AS "bit shift left 1", (all_types.small_int << (all_types.small_int / $24::smallint)) AS "bit shift left 1",
(all_types.small_int << $24) AS "bit shift left 2", (all_types.small_int << $25) AS "bit shift left 2",
(all_types.small_int >> (all_types.small_int / $25)) AS "bit shift right 1", (all_types.small_int >> (all_types.small_int / $26)) AS "bit shift right 1",
(all_types.small_int >> $26) AS "bit shift right 2", (all_types.small_int >> $27) AS "bit shift right 2",
ABS(all_types.big_int) AS "abs", ABS(all_types.big_int) AS "abs",
SQRT(ABS(all_types.big_int)) AS "sqrt", SQRT(ABS(all_types.big_int)) AS "sqrt",
CBRT(ABS(all_types.big_int)) AS "cbrt" CBRT(ABS(all_types.big_int)) AS "cbrt"
FROM test_sample.all_types FROM test_sample.all_types
LIMIT $27; LIMIT $28;
`) `)
var dest []struct { var dest []struct {
@ -1267,7 +1273,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 +1410,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 +1697,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 +1723,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 +2018,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

@ -2,6 +2,9 @@ package postgres
import ( import (
"context" "context"
"testing"
"time"
"github.com/bytedance/sonic" "github.com/bytedance/sonic"
"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"
@ -10,8 +13,6 @@ import (
. "github.com/go-jet/jet/v2/tests/.gentestdata/jetdb/chinook/table" . "github.com/go-jet/jet/v2/tests/.gentestdata/jetdb/chinook/table"
"github.com/go-jet/jet/v2/tests/.gentestdata/jetdb/chinook2/table" "github.com/go-jet/jet/v2/tests/.gentestdata/jetdb/chinook2/table"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"testing"
"time"
) )
func TestSelectAlbum(t *testing.T) { func TestSelectAlbum(t *testing.T) {
@ -1301,13 +1302,13 @@ func TestAggregateFunc(t *testing.T) {
skipForCockroachDB(t) skipForCockroachDB(t)
stmt := SELECT( stmt := SELECT(
PERCENTILE_DISC(Float(0.1)).WITHIN_GROUP_ORDER_BY(Invoice.InvoiceId).AS("percentile_disc_1"), PERCENTILE_DISC(Double(0.1)).WITHIN_GROUP_ORDER_BY(Invoice.InvoiceId).AS("percentile_disc_1"),
PERCENTILE_DISC(Invoice.Total.DIV(Float(100))).WITHIN_GROUP_ORDER_BY(Invoice.InvoiceDate.ASC()).AS("percentile_disc_2"), PERCENTILE_DISC(Invoice.Total.DIV(Float(100))).WITHIN_GROUP_ORDER_BY(Invoice.InvoiceDate.ASC()).AS("percentile_disc_2"),
PERCENTILE_DISC(RawFloat("(select array_agg(s) from generate_series(0, 1, 0.2) as s)")). PERCENTILE_DISC(RawFloat("(select array_agg(s) from generate_series(0, 1, 0.2) as s)")).
WITHIN_GROUP_ORDER_BY(Invoice.BillingAddress.DESC()).AS("percentile_disc_3"), WITHIN_GROUP_ORDER_BY(Invoice.BillingAddress.DESC()).AS("percentile_disc_3"),
PERCENTILE_CONT(Float(0.3)).WITHIN_GROUP_ORDER_BY(Invoice.Total).AS("percentile_cont_1"), PERCENTILE_CONT(Double(0.3)).WITHIN_GROUP_ORDER_BY(Invoice.Total).AS("percentile_cont_1"),
PERCENTILE_CONT(Float(0.2)).WITHIN_GROUP_ORDER_BY(INTERVAL(1, HOUR).DESC()).AS("percentile_cont_interval"), PERCENTILE_CONT(Double(0.2)).WITHIN_GROUP_ORDER_BY(INTERVAL(1, HOUR).DESC()).AS("percentile_cont_interval"),
MODE().WITHIN_GROUP_ORDER_BY(Invoice.BillingPostalCode.DESC()).AS("mode_1"), MODE().WITHIN_GROUP_ORDER_BY(Invoice.BillingPostalCode.DESC()).AS("mode_1"),
).FROM( ).FROM(

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),