Skip to content

Commit

Permalink
Global AND and OR functions for better indentation of a complex c…
Browse files Browse the repository at this point in the history
…ondition in the Go code and in the generated SQL.
  • Loading branch information
go-jet committed Feb 11, 2022
1 parent 8ffbe38 commit 9f91fd7
Show file tree
Hide file tree
Showing 17 changed files with 338 additions and 44 deletions.
4 changes: 2 additions & 2 deletions internal/jet/clause.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,9 +98,9 @@ func (c *ClauseWhere) Serialize(statementType StatementType, out *SQLBuilder, op
}
out.WriteString("WHERE")

out.IncreaseIdent()
out.IncreaseIdent(6)
c.Condition.serialize(statementType, out, NoWrap.WithFallTrough(options)...)
out.DecreaseIdent()
out.DecreaseIdent(6)
}

// ClauseGroupBy struct
Expand Down
63 changes: 61 additions & 2 deletions internal/jet/expression.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,65 @@ func (c *binaryOperatorExpression) serialize(statement StatementType, out *SQLBu
}
}

type expressionListOperator struct {
ExpressionInterfaceImpl

operator string
expressions []Expression
}

func newExpressionListOperator(operator string, expressions ...Expression) *expressionListOperator {
ret := &expressionListOperator{
operator: operator,
expressions: expressions,
}

ret.ExpressionInterfaceImpl.Parent = ret

return ret
}

func newBoolExpressionListOperator(operator string, expressions ...BoolExpression) BoolExpression {
return BoolExp(newExpressionListOperator(operator, BoolExpressionListToExpressionList(expressions)...))
}

func (elo *expressionListOperator) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) {
if len(elo.expressions) == 0 {
panic("jet: syntax error, expression list empty")
}

shouldWrap := len(elo.expressions) > 1
if shouldWrap {
out.WriteByte('(')
out.IncreaseIdent(tabSize)
out.NewLine()
}

for i, expression := range elo.expressions {
if i == 1 {
out.IncreaseIdent(tabSize)
}
if i > 0 {
out.NewLine()
out.WriteString(elo.operator)
}

out.IncreaseIdent(len(elo.operator) + 1)
expression.serialize(statement, out, FallTrough(options)...)
out.DecreaseIdent(len(elo.operator) + 1)
}

if len(elo.expressions) > 1 {
out.DecreaseIdent(tabSize)
}

if shouldWrap {
out.DecreaseIdent(tabSize)
out.NewLine()
out.WriteByte(')')
}
}

// A prefix operator Expression
type prefixExpression struct {
ExpressionInterfaceImpl
Expand Down Expand Up @@ -209,8 +268,8 @@ type complexExpression struct {
expressions Expression
}

func complexExpr(expressions Expression) Expression {
complexExpression := &complexExpression{expressions: expressions}
func complexExpr(expression Expression) Expression {
complexExpression := &complexExpression{expressions: expression}
complexExpression.ExpressionInterfaceImpl.Parent = complexExpression

return complexExpression
Expand Down
12 changes: 12 additions & 0 deletions internal/jet/func_expression.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
package jet

// 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.
func AND(expressions ...BoolExpression) BoolExpression {
return newBoolExpressionListOperator("AND", expressions...)
}

// 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.
func OR(expressions ...BoolExpression) BoolExpression {
return newBoolExpressionListOperator("OR", expressions...)
}

// ROW is construct one table row from list of expressions.
func ROW(expressions ...Expression) Expression {
return NewFunc("ROW", expressions, nil)
Expand Down
22 changes: 22 additions & 0 deletions internal/jet/func_expression_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,28 @@ import (
"testing"
)

func TestAND(t *testing.T) {
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.LT(Int(11))), `(table1.col_int < $1)`, int64(11))
assertClauseSerialize(t, AND(table1ColInt.GT(Int(11)), table1ColFloat.EQ(Float(0))),
`(
(table1.col_int > $1)
AND (table1.col_float = $2)
)`, int64(11), 0.0)
}

func TestOR(t *testing.T) {
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.LT(Int(11))), `(table1.col_int < $1)`, int64(11))
assertClauseSerialize(t, OR(table1ColInt.GT(Int(11)), table1ColFloat.EQ(Float(0))),
`(
(table1.col_int > $1)
OR (table1.col_float = $2)
)`, int64(11), 0.0)
}

func TestFuncAVG(t *testing.T) {
assertClauseSerialize(t, AVG(table1ColFloat), "AVG(table1.col_float)")
assertClauseSerialize(t, AVG(table1ColInt), "AVG(table1.col_int)")
Expand Down
1 change: 1 addition & 0 deletions internal/jet/sql_builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ type SQLBuilder struct {
Debug bool
}

const tabSize = 4
const defaultIdent = 5

// IncreaseIdent adds ident or defaultIdent number of spaces to each new line
Expand Down
11 changes: 11 additions & 0 deletions internal/jet/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,17 @@ func ExpressionListToSerializerList(expressions []Expression) []Serializer {
return ret
}

// BoolExpressionListToExpressionList converts list of bool expressions to list of expressions
func BoolExpressionListToExpressionList(expressions []BoolExpression) []Expression {
var ret []Expression

for _, expression := range expressions {
ret = append(ret, expression)
}

return ret
}

// ColumnListToProjectionList func
func ColumnListToProjectionList(columns []ColumnExpression) []Projection {
var ret []Projection
Expand Down
9 changes: 9 additions & 0 deletions mysql/functions.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,15 @@ package mysql

import "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
// in the Go code and in the generated SQL.
var (
// AND function adds AND operator between expressions.
AND = jet.AND
// OR function adds OR operator between expressions.
OR = jet.OR
)

// ROW is construct one table row from list of expressions.
var ROW = jet.ROW

Expand Down
8 changes: 4 additions & 4 deletions mysql/select_statement_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,9 +148,9 @@ func TestSelect_NOT_EXISTS(t *testing.T) {
SELECT table1.col_int AS "table1.col_int"
FROM db.table1
WHERE NOT (EXISTS (
SELECT table2.col_int AS "table2.col_int"
FROM db.table2
WHERE table1.col_int = table2.col_int
));
SELECT table2.col_int AS "table2.col_int"
FROM db.table2
WHERE table1.col_int = table2.col_int
));
`)
}
9 changes: 9 additions & 0 deletions postgres/functions.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,15 @@ package postgres

import "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
// in the Go code and in the generated SQL.
var (
// AND function adds AND operator between expressions.
AND = jet.AND
// OR function adds OR operator between expressions.
OR = jet.OR
)

// ROW is construct one table row from list of expressions.
var ROW = jet.ROW

Expand Down
9 changes: 9 additions & 0 deletions sqlite/functions.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,15 @@ import (
"time"
)

// This functions can be used, instead of its method counterparts, to have a better indentation of a complex condition
// in the Go code and in the generated SQL.
var (
// AND function adds AND operator between expressions.
AND = jet.AND
// OR function adds OR operator between expressions.
OR = jet.OR
)

// ROW is construct one table row from list of expressions.
func ROW(expressions ...Expression) Expression {
return jet.NewFunc("", expressions, nil)
Expand Down
8 changes: 4 additions & 4 deletions sqlite/select_statement_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,9 +148,9 @@ func TestSelect_NOT_EXISTS(t *testing.T) {
SELECT table1.col_int AS "table1.col_int"
FROM db.table1
WHERE NOT (EXISTS (
SELECT table2.col_int AS "table2.col_int"
FROM db.table2
WHERE table1.col_int = table2.col_int
));
SELECT table2.col_int AS "table2.col_int"
FROM db.table2
WHERE table1.col_int = table2.col_int
));
`)
}
6 changes: 3 additions & 3 deletions tests/mysql/with_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -165,9 +165,9 @@ WITH payments_to_delete AS (
)
DELETE FROM dvds.payment
WHERE payment.payment_id IN (
SELECT payments_to_delete.''payment.payment_id'' AS "payment.payment_id"
FROM payments_to_delete
);
SELECT payments_to_delete.''payment.payment_id'' AS "payment.payment_id"
FROM payments_to_delete
);
`, "''", "`"))

tx, err := db.Begin()
Expand Down
Loading

0 comments on commit 9f91fd7

Please sign in to comment.