init - add project files

This commit is contained in:
2025-03-06 23:54:11 -05:00
commit e724ff1120
1363 changed files with 897467 additions and 0 deletions

View File

@@ -0,0 +1,32 @@
package jet
type alias struct {
expression Expression
alias string
}
func newAlias(expression Expression, aliasName string) Projection {
return &alias{
expression: expression,
alias: aliasName,
}
}
func (a *alias) fromImpl(subQuery SelectTable) Projection {
// if alias is in the form "table.column", we break it into two parts so that ProjectionList.As(newAlias) can
// overwrite tableName with a new alias. This method is called only for exporting aliased custom columns.
// Generated columns have default aliasing.
tableName, columnName := extractTableAndColumnName(a.alias)
column := NewColumnImpl(columnName, tableName, nil)
column.subQuery = subQuery
return &column
}
func (a *alias) serializeForProjection(statement StatementType, out *SQLBuilder) {
a.expression.serialize(statement, out)
out.WriteString("AS")
out.WriteAlias(a.alias)
}

View File

@@ -0,0 +1,115 @@
package jet
// BoolExpression interface
type BoolExpression interface {
Expression
// Check if this expression is equal to rhs
EQ(rhs BoolExpression) BoolExpression
// Check if this expression is not equal to rhs
NOT_EQ(rhs BoolExpression) BoolExpression
// Check if this expression is distinct to rhs
IS_DISTINCT_FROM(rhs BoolExpression) BoolExpression
// Check if this expression is not distinct to rhs
IS_NOT_DISTINCT_FROM(rhs BoolExpression) BoolExpression
// Check if this expression is true
IS_TRUE() BoolExpression
// Check if this expression is not true
IS_NOT_TRUE() BoolExpression
// Check if this expression is false
IS_FALSE() BoolExpression
// Check if this expression is not false
IS_NOT_FALSE() BoolExpression
// Check if this expression is unknown
IS_UNKNOWN() BoolExpression
// Check if this expression is not unknown
IS_NOT_UNKNOWN() BoolExpression
// expression AND operator rhs
AND(rhs BoolExpression) BoolExpression
// expression OR operator rhs
OR(rhs BoolExpression) BoolExpression
}
type boolInterfaceImpl struct {
parent BoolExpression
}
func (b *boolInterfaceImpl) EQ(expression BoolExpression) BoolExpression {
return Eq(b.parent, expression)
}
func (b *boolInterfaceImpl) NOT_EQ(expression BoolExpression) BoolExpression {
return NotEq(b.parent, expression)
}
func (b *boolInterfaceImpl) IS_DISTINCT_FROM(rhs BoolExpression) BoolExpression {
return IsDistinctFrom(b.parent, rhs)
}
func (b *boolInterfaceImpl) IS_NOT_DISTINCT_FROM(rhs BoolExpression) BoolExpression {
return IsNotDistinctFrom(b.parent, rhs)
}
func (b *boolInterfaceImpl) AND(expression BoolExpression) BoolExpression {
return newBinaryBoolOperatorExpression(b.parent, expression, "AND")
}
func (b *boolInterfaceImpl) OR(expression BoolExpression) BoolExpression {
return newBinaryBoolOperatorExpression(b.parent, expression, "OR")
}
func (b *boolInterfaceImpl) IS_TRUE() BoolExpression {
return newPostfixBoolOperatorExpression(b.parent, "IS TRUE")
}
func (b *boolInterfaceImpl) IS_NOT_TRUE() BoolExpression {
return newPostfixBoolOperatorExpression(b.parent, "IS NOT TRUE")
}
func (b *boolInterfaceImpl) IS_FALSE() BoolExpression {
return newPostfixBoolOperatorExpression(b.parent, "IS FALSE")
}
func (b *boolInterfaceImpl) IS_NOT_FALSE() BoolExpression {
return newPostfixBoolOperatorExpression(b.parent, "IS NOT FALSE")
}
func (b *boolInterfaceImpl) IS_UNKNOWN() BoolExpression {
return newPostfixBoolOperatorExpression(b.parent, "IS UNKNOWN")
}
func (b *boolInterfaceImpl) IS_NOT_UNKNOWN() BoolExpression {
return newPostfixBoolOperatorExpression(b.parent, "IS NOT UNKNOWN")
}
func newBinaryBoolOperatorExpression(lhs, rhs Expression, operator string, additionalParams ...Expression) BoolExpression {
return BoolExp(NewBinaryOperatorExpression(lhs, rhs, operator, additionalParams...))
}
func newPrefixBoolOperatorExpression(expression Expression, operator string) BoolExpression {
return BoolExp(newPrefixOperatorExpression(expression, operator))
}
func newPostfixBoolOperatorExpression(expression Expression, operator string) BoolExpression {
return BoolExp(newPostfixOperatorExpression(expression, operator))
}
type boolExpressionWrapper struct {
boolInterfaceImpl
Expression
}
func newBoolExpressionWrap(expression Expression) BoolExpression {
boolExpressionWrap := boolExpressionWrapper{Expression: expression}
boolExpressionWrap.boolInterfaceImpl.parent = &boolExpressionWrap
return &boolExpressionWrap
}
// BoolExp is bool expression wrapper around arbitrary expression.
// Allows go compiler to see any expression as bool expression.
// Does not add sql cast to generated sql builder output.
func BoolExp(expression Expression) BoolExpression {
return newBoolExpressionWrap(expression)
}

View File

@@ -0,0 +1,76 @@
package jet
import (
"testing"
)
func TestBoolExpressionEQ(t *testing.T) {
assertClauseSerialize(t, table1ColBool.EQ(table2ColBool), "(table1.col_bool = table2.col_bool)")
}
func TestBoolExpressionNOT_EQ(t *testing.T) {
assertClauseSerialize(t, table1ColBool.NOT_EQ(table2ColBool), "(table1.col_bool != table2.col_bool)")
assertClauseSerialize(t, table1ColBool.NOT_EQ(Bool(true)), "(table1.col_bool != $1)", true)
}
func TestBoolExpressionIS_DISTINCT_FROM(t *testing.T) {
assertClauseSerialize(t, table1ColBool.IS_DISTINCT_FROM(table2ColBool), "(table1.col_bool IS DISTINCT FROM table2.col_bool)")
assertClauseSerialize(t, table1ColBool.IS_DISTINCT_FROM(Bool(false)), "(table1.col_bool IS DISTINCT FROM $1)", false)
}
func TestBoolExpressionIS_NOT_DISTINCT_FROM(t *testing.T) {
assertClauseSerialize(t, table1ColBool.IS_NOT_DISTINCT_FROM(table2ColBool), "(table1.col_bool IS NOT DISTINCT FROM table2.col_bool)")
assertClauseSerialize(t, table1ColBool.IS_NOT_DISTINCT_FROM(Bool(false)), "(table1.col_bool IS NOT DISTINCT FROM $1)", false)
}
func TestBoolExpressionIS_TRUE(t *testing.T) {
assertClauseSerialize(t, table1ColBool.IS_TRUE(), "table1.col_bool IS TRUE")
assertClauseSerialize(t, (Int(2).EQ(table1ColInt)).IS_TRUE(),
`($1 = table1.col_int) IS TRUE`, int64(2))
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))
}
func TestBoolExpressionIS_NOT_TRUE(t *testing.T) {
assertClauseSerialize(t, table1ColBool.IS_NOT_TRUE(), "table1.col_bool IS NOT TRUE")
}
func TestBoolExpressionIS_FALSE(t *testing.T) {
assertClauseSerialize(t, table1ColBool.IS_FALSE(), "table1.col_bool IS FALSE")
}
func TestBoolExpressionIS_NOT_FALSE(t *testing.T) {
assertClauseSerialize(t, table1ColBool.IS_NOT_FALSE(), "table1.col_bool IS NOT FALSE")
}
func TestBoolExpressionIS_UNKNOWN(t *testing.T) {
assertClauseSerialize(t, table1ColBool.IS_UNKNOWN(), "table1.col_bool IS UNKNOWN")
}
func TestBoolExpressionIS_NOT_UNKNOWN(t *testing.T) {
assertClauseSerialize(t, table1ColBool.IS_NOT_UNKNOWN(), "table1.col_bool IS NOT UNKNOWN")
}
func TestBinaryBoolExpression(t *testing.T) {
boolExpression := Int(2).EQ(Int(3))
assertClauseSerialize(t, boolExpression, "($1 = $2)", int64(2), int64(3))
assertProjectionSerialize(t, boolExpression, "$1 = $2", int64(2), int64(3))
assertProjectionSerialize(t, boolExpression.AS("alias_eq_expression"),
`($1 = $2) AS "alias_eq_expression"`, int64(2), int64(3))
assertClauseSerialize(t, boolExpression.AND(Int(4).EQ(Int(5))),
"(($1 = $2) AND ($3 = $4))", int64(2), int64(3), int64(4), int64(5))
assertClauseSerialize(t, boolExpression.OR(Int(4).EQ(Int(5))),
"(($1 = $2) OR ($3 = $4))", int64(2), int64(3), int64(4), int64(5))
}
func TestBoolLiteral(t *testing.T) {
assertClauseSerialize(t, Bool(true), "$1", true)
assertClauseSerialize(t, Bool(false), "$1", false)
}
func TestBoolExp(t *testing.T) {
assertClauseSerialize(t, BoolExp(String("true")), "$1", "true")
assertClauseSerialize(t, BoolExp(String("true")).IS_TRUE(), "$1 IS TRUE", "true")
}

View File

@@ -0,0 +1,53 @@
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.Parent = 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

@@ -0,0 +1,11 @@
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

@@ -0,0 +1,672 @@
package jet
import "github.com/go-jet/jet/v2/internal/utils/is"
// Clause interface
type Clause interface {
Serialize(statementType StatementType, out *SQLBuilder, options ...SerializeOption)
}
// ClauseWithProjections interface
type ClauseWithProjections interface {
Clause
Projections() ProjectionList
}
// OptimizerHint provides a way to optimize query execution per-statement basis
type OptimizerHint string
type optimizerHints []OptimizerHint
func (o optimizerHints) Serialize(statementType StatementType, out *SQLBuilder, options ...SerializeOption) {
if len(o) == 0 {
return
}
out.WriteString("/*+")
for i, hint := range o {
if i > 0 {
out.WriteByte(' ')
}
out.WriteString(string(hint))
}
out.WriteString("*/")
}
// ClauseSelect struct
type ClauseSelect struct {
Distinct bool
DistinctOnColumns []ColumnExpression
ProjectionList []Projection
// MySQL only
OptimizerHints optimizerHints
}
// Projections returns list of projections for select clause
func (s *ClauseSelect) Projections() ProjectionList {
return s.ProjectionList
}
// Serialize serializes clause into SQLBuilder
func (s *ClauseSelect) Serialize(statementType StatementType, out *SQLBuilder, options ...SerializeOption) {
out.NewLine()
out.WriteString("SELECT")
s.OptimizerHints.Serialize(statementType, out, options...)
if s.Distinct {
out.WriteString("DISTINCT")
}
if len(s.DistinctOnColumns) > 0 {
out.WriteString("ON (")
SerializeColumnExpressions(s.DistinctOnColumns, statementType, out)
out.WriteByte(')')
}
if len(s.ProjectionList) == 0 {
panic("jet: SELECT clause has to have at least one projection")
}
out.WriteProjections(statementType, s.ProjectionList)
}
// ClauseFrom struct
type ClauseFrom struct {
Name string
Tables []Serializer
}
// Serialize serializes clause into SQLBuilder
func (f *ClauseFrom) Serialize(statementType StatementType, out *SQLBuilder, options ...SerializeOption) {
if len(f.Tables) == 0 { // SELECT statement does not have to have FROM clause
return
}
out.NewLine()
if f.Name != "" {
out.WriteString(f.Name)
} else {
out.WriteString("FROM")
}
out.IncreaseIdent()
for i, table := range f.Tables {
if i > 0 {
out.WriteString(",")
out.NewLine()
}
table.serialize(statementType, out, FallTrough(options)...)
}
out.DecreaseIdent()
}
// ClauseWhere struct
type ClauseWhere struct {
Condition BoolExpression
Mandatory bool
}
// Serialize serializes clause into SQLBuilder
func (c *ClauseWhere) Serialize(statementType StatementType, out *SQLBuilder, options ...SerializeOption) {
if c.Condition == nil {
if c.Mandatory {
panic("jet: WHERE clause not set")
}
return
}
if !contains(options, SkipNewLine) {
out.NewLine()
}
out.WriteString("WHERE")
out.IncreaseIdent(6)
c.Condition.serialize(statementType, out, NoWrap.WithFallTrough(options)...)
out.DecreaseIdent(6)
}
// ClauseGroupBy struct
type ClauseGroupBy struct {
List []GroupByClause
}
// Serialize serializes clause into SQLBuilder
func (c *ClauseGroupBy) Serialize(statementType StatementType, out *SQLBuilder, options ...SerializeOption) {
if len(c.List) == 0 {
return
}
out.NewLine()
out.WriteString("GROUP BY")
out.IncreaseIdent()
for i, c := range c.List {
if i > 0 {
out.WriteString(", ")
}
if c == nil {
panic("jet: nil clause in GROUP BY list")
}
c.serializeForGroupBy(statementType, out)
}
out.DecreaseIdent()
}
// ClauseHaving struct
type ClauseHaving struct {
Condition BoolExpression
}
// Serialize serializes clause into SQLBuilder
func (c *ClauseHaving) Serialize(statementType StatementType, out *SQLBuilder, options ...SerializeOption) {
if c.Condition == nil {
return
}
out.NewLine()
out.WriteString("HAVING")
out.IncreaseIdent()
c.Condition.serialize(statementType, out, NoWrap.WithFallTrough(options)...)
out.DecreaseIdent()
}
// ClauseOrderBy struct
type ClauseOrderBy struct {
List []OrderByClause
SkipNewLine bool
}
// Serialize serializes clause into SQLBuilder
func (o *ClauseOrderBy) Serialize(statementType StatementType, out *SQLBuilder, options ...SerializeOption) {
if o.List == nil {
return
}
if !o.SkipNewLine {
out.NewLine()
}
out.WriteString("ORDER BY")
out.IncreaseIdent()
for i, value := range o.List {
if i > 0 {
out.WriteString(", ")
}
value.serializeForOrderBy(statementType, out)
}
out.DecreaseIdent()
}
// ClauseLimit struct
type ClauseLimit struct {
Count int64
}
// Serialize serializes clause into SQLBuilder
func (l *ClauseLimit) Serialize(statementType StatementType, out *SQLBuilder, options ...SerializeOption) {
if l.Count >= 0 {
out.NewLine()
out.WriteString("LIMIT")
out.insertParametrizedArgument(l.Count)
}
}
// ClauseOffset struct
type ClauseOffset struct {
Count IntegerExpression
}
// Serialize serializes clause into SQLBuilder
func (o *ClauseOffset) Serialize(statementType StatementType, out *SQLBuilder, options ...SerializeOption) {
if is.Nil(o.Count) {
return
}
out.NewLine()
out.WriteString("OFFSET")
o.Count.serialize(statementType, out, options...)
}
// ClauseFetch struct
type ClauseFetch struct {
Count IntegerExpression
WithTies bool
}
// Serialize serializes ClauseFetch into sql builder output
func (o *ClauseFetch) Serialize(statementType StatementType, out *SQLBuilder, options ...SerializeOption) {
if is.Nil(o.Count) {
return
}
out.NewLine()
out.WriteString("FETCH FIRST")
o.Count.serialize(statementType, out, options...)
if o.WithTies {
out.WriteString("ROWS WITH TIES")
} else {
out.WriteString("ROWS ONLY")
}
}
// ClauseFor struct
type ClauseFor struct {
Lock RowLock
}
// Serialize serializes clause into SQLBuilder
func (f *ClauseFor) Serialize(statementType StatementType, out *SQLBuilder, options ...SerializeOption) {
if f.Lock == nil {
return
}
out.NewLine()
out.WriteString("FOR")
f.Lock.serialize(statementType, out, FallTrough(options)...)
}
// ClauseSetStmtOperator struct
type ClauseSetStmtOperator struct {
Operator string
All bool
Selects []SerializerStatement
OrderBy ClauseOrderBy
Limit ClauseLimit
Offset ClauseOffset
SkipSelectWrap bool
}
// Projections returns set of projections for ClauseSetStmtOperator
func (s *ClauseSetStmtOperator) Projections() ProjectionList {
if len(s.Selects) > 0 {
return s.Selects[0].projections()
}
return nil
}
// Serialize serializes clause into SQLBuilder
func (s *ClauseSetStmtOperator) Serialize(statementType StatementType, out *SQLBuilder, options ...SerializeOption) {
if len(s.Selects) < 2 {
panic("jet: UNION Statement must contain at least two SELECT statements")
}
for i, selectStmt := range s.Selects {
out.NewLine()
if i > 0 {
if s.SkipSelectWrap {
out.NewLine()
}
out.WriteString(s.Operator)
if s.All {
out.WriteString("ALL")
}
out.NewLine()
}
if selectStmt == nil {
panic("jet: select statement of '" + s.Operator + "' is nil")
}
if s.SkipSelectWrap {
options = append(FallTrough(options), NoWrap)
}
selectStmt.serialize(statementType, out, options...)
}
s.OrderBy.Serialize(statementType, out)
s.Limit.Serialize(statementType, out)
s.Offset.Serialize(statementType, out)
}
// ClauseUpdate struct
type ClauseUpdate struct {
Table SerializerTable
// MySQL only
OptimizerHints optimizerHints
}
// Serialize serializes clause into SQLBuilder
func (u *ClauseUpdate) Serialize(statementType StatementType, out *SQLBuilder, options ...SerializeOption) {
out.NewLine()
out.WriteString("UPDATE")
u.OptimizerHints.Serialize(statementType, out, options...)
if is.Nil(u.Table) {
panic("jet: table to update is nil")
}
u.Table.serialize(statementType, out, FallTrough(options)...)
}
// SetClause struct
type SetClause struct {
Columns []Column
Values []Serializer
}
// Serialize serializes clause into SQLBuilder
func (s *SetClause) Serialize(statementType StatementType, out *SQLBuilder, options ...SerializeOption) {
if len(s.Values) == 0 {
return
}
out.NewLine()
out.WriteString("SET")
if len(s.Columns) != len(s.Values) {
panic("jet: mismatch in numbers of columns and values for SET clause")
}
out.IncreaseIdent(4)
for i, column := range s.Columns {
if i > 0 {
out.WriteString(",")
out.NewLine()
}
if column == nil {
panic("jet: nil column in columns list for SET clause")
}
out.WriteIdentifier(column.Name())
out.WriteString(" = ")
s.Values[i].serialize(UpdateStatementType, out, FallTrough(options)...)
}
out.DecreaseIdent(4)
}
// ClauseInsert struct
type ClauseInsert struct {
Table SerializerTable
Columns []Column
// MySQL only
OptimizerHints optimizerHints
}
// GetColumns gets list of columns for insert
func (i *ClauseInsert) GetColumns() []Column {
if len(i.Columns) > 0 {
return i.Columns
}
return i.Table.columns()
}
// Serialize serializes clause into SQLBuilder
func (i *ClauseInsert) Serialize(statementType StatementType, out *SQLBuilder, options ...SerializeOption) {
if is.Nil(i.Table) {
panic("jet: table is nil for INSERT clause")
}
out.NewLine()
out.WriteString("INSERT")
i.OptimizerHints.Serialize(statementType, out, options...)
out.WriteString("INTO")
i.Table.serialize(statementType, out)
if len(i.Columns) > 0 {
out.WriteString("(")
SerializeColumnNames(i.Columns, out)
out.WriteString(")")
}
}
// ClauseValuesQuery struct
type ClauseValuesQuery struct {
ClauseValues
ClauseQuery
}
// Serialize serializes clause into SQLBuilder
func (v *ClauseValuesQuery) Serialize(statementType StatementType, out *SQLBuilder, options ...SerializeOption) {
if len(v.Rows) > 0 && v.Query != nil {
panic("jet: VALUES or QUERY has to be specified for INSERT statement")
}
v.ClauseValues.Serialize(statementType, out, FallTrough(options)...)
v.ClauseQuery.Serialize(statementType, out, FallTrough(options)...)
}
// ClauseValues struct
type ClauseValues struct {
Rows [][]Serializer
As string
}
// Serialize serializes clause into SQLBuilder
func (v *ClauseValues) Serialize(statementType StatementType, out *SQLBuilder, options ...SerializeOption) {
if len(v.Rows) == 0 {
return
}
out.NewLine()
out.WriteString("VALUES")
for rowIndex, row := range v.Rows {
if rowIndex > 0 {
out.WriteString(",")
out.NewLine()
} else {
out.IncreaseIdent(7)
}
out.WriteString("(")
SerializeClauseList(statementType, row, out)
out.WriteByte(')')
}
if len(v.As) > 0 {
out.WriteString("AS")
out.WriteIdentifier(v.As)
}
out.DecreaseIdent(7)
}
// ClauseQuery struct
type ClauseQuery struct {
Query SerializerStatement
SkipSelectWrap bool
}
// Serialize serializes clause into SQLBuilder
func (v *ClauseQuery) Serialize(statementType StatementType, out *SQLBuilder, options ...SerializeOption) {
if v.Query == nil {
return
}
if v.SkipSelectWrap {
options = append(FallTrough(options), NoWrap)
}
v.Query.serialize(statementType, out, options...)
}
// ClauseDelete struct
type ClauseDelete struct {
Table SerializerTable
// MySQL only
OptimizerHints optimizerHints
}
// Serialize serializes clause into SQLBuilder
func (d *ClauseDelete) Serialize(statementType StatementType, out *SQLBuilder, options ...SerializeOption) {
out.NewLine()
out.WriteString("DELETE")
d.OptimizerHints.Serialize(statementType, out, options...)
out.WriteString("FROM")
d.Table.serialize(statementType, out, FallTrough(options)...)
}
// ClauseStatementBegin struct
type ClauseStatementBegin struct {
Name string
Tables []SerializerTable
}
// Serialize serializes clause into SQLBuilder
func (d *ClauseStatementBegin) Serialize(statementType StatementType, out *SQLBuilder, options ...SerializeOption) {
out.NewLine()
out.WriteString(d.Name)
for i, table := range d.Tables {
if i > 0 {
out.WriteString(", ")
}
table.serialize(statementType, out, FallTrough(options)...)
}
}
// ClauseOptional struct
type ClauseOptional struct {
Name string
Show bool
InNewLine bool
}
// Serialize serializes clause into SQLBuilder
func (d *ClauseOptional) Serialize(statementType StatementType, out *SQLBuilder, options ...SerializeOption) {
if !d.Show {
return
}
if d.InNewLine {
out.NewLine()
}
out.WriteString(d.Name)
}
// ClauseIn struct
type ClauseIn struct {
LockMode string
}
// Serialize serializes clause into SQLBuilder
func (i *ClauseIn) Serialize(statementType StatementType, out *SQLBuilder, options ...SerializeOption) {
if i.LockMode == "" {
return
}
out.WriteString("IN")
out.WriteString(string(i.LockMode))
out.WriteString("MODE")
}
// WindowDefinition struct
type WindowDefinition struct {
Name string
Window Window
}
// ClauseWindow struct
type ClauseWindow struct {
Definitions []WindowDefinition
}
// Serialize serializes clause into SQLBuilder
func (i *ClauseWindow) Serialize(statementType StatementType, out *SQLBuilder, options ...SerializeOption) {
if len(i.Definitions) == 0 {
return
}
out.NewLine()
out.WriteString("WINDOW")
for i, def := range i.Definitions {
if i > 0 {
out.WriteString(", ")
}
out.WriteString(def.Name)
out.WriteString("AS")
if def.Window == nil {
out.WriteString("()")
continue
}
def.Window.serialize(statementType, out, FallTrough(options)...)
}
}
// SetPair clause
type SetPair struct {
Column ColumnSerializer
Value Serializer
}
// SetClauseNew clause
type SetClauseNew []ColumnAssigment
// Serialize for SetClauseNew
func (s SetClauseNew) Serialize(statementType StatementType, out *SQLBuilder, options ...SerializeOption) {
if len(s) == 0 {
return
}
out.NewLine()
out.WriteString("SET")
out.IncreaseIdent(4)
for i, assigment := range s {
if i > 0 {
out.WriteString(",")
out.NewLine()
}
assigment.serialize(statementType, out, FallTrough(options)...)
}
out.DecreaseIdent(4)
}
// KeywordClause type
type KeywordClause struct {
Keyword
}
// Serialize for KeywordClause
func (k KeywordClause) Serialize(statementType StatementType, out *SQLBuilder, options ...SerializeOption) {
k.serialize(statementType, out, FallTrough(options)...)
}
// ClauseReturning type
type ClauseReturning struct {
ProjectionList []Projection
}
// Serialize for ClauseReturning
func (r *ClauseReturning) Serialize(statementType StatementType, out *SQLBuilder, options ...SerializeOption) {
if len(r.ProjectionList) == 0 {
return
}
out.NewLine()
out.WriteString("RETURNING")
out.IncreaseIdent()
out.WriteProjections(statementType, r.ProjectionList)
out.DecreaseIdent()
}
// Projections for ClauseReturning
func (r ClauseReturning) Projections() ProjectionList {
return r.ProjectionList
}

View File

@@ -0,0 +1,16 @@
package jet
import (
"github.com/stretchr/testify/require"
"testing"
)
func TestClauseSelect_Serialize(t *testing.T) {
defer func() {
r := recover()
require.Equal(t, r, "jet: SELECT clause has to have at least one projection")
}()
selectClause := &ClauseSelect{}
selectClause.Serialize(SelectStatementType, &SQLBuilder{})
}

View File

@@ -0,0 +1,117 @@
// Modeling of columns
package jet
// Column is common column interface for all types of columns.
type Column interface {
Name() string
TableName() string
setTableName(table string)
setSubQuery(subQuery SelectTable)
defaultAlias() string
}
// ColumnSerializer is interface for all serializable columns
type ColumnSerializer interface {
Serializer
Column
}
// ColumnExpression interface
type ColumnExpression interface {
Column
Expression
}
// ColumnExpressionImpl is base type for sql columns.
type ColumnExpressionImpl struct {
ExpressionInterfaceImpl
name string
tableName string
subQuery SelectTable
}
// NewColumnImpl creates new ColumnExpressionImpl
func NewColumnImpl(name string, tableName string, parent ColumnExpression) ColumnExpressionImpl {
bc := ColumnExpressionImpl{
name: name,
tableName: tableName,
}
if parent != nil {
bc.ExpressionInterfaceImpl.Parent = parent
} else {
bc.ExpressionInterfaceImpl.Parent = &bc
}
return bc
}
// Name returns name of the column
func (c *ColumnExpressionImpl) Name() string {
return c.name
}
// TableName returns column table name
func (c *ColumnExpressionImpl) TableName() string {
return c.tableName
}
func (c *ColumnExpressionImpl) setTableName(table string) {
c.tableName = table
}
func (c *ColumnExpressionImpl) setSubQuery(subQuery SelectTable) {
c.subQuery = subQuery
}
func (c *ColumnExpressionImpl) defaultAlias() string {
if c.tableName != "" {
return c.tableName + "." + c.name
}
return c.name
}
func (c *ColumnExpressionImpl) fromImpl(subQuery SelectTable) Projection {
newColumn := NewColumnImpl(c.name, c.tableName, nil)
newColumn.setSubQuery(subQuery)
return &newColumn
}
func (c *ColumnExpressionImpl) serializeForOrderBy(statement StatementType, out *SQLBuilder) {
if statement == SetStatementType {
// set Statement (UNION, EXCEPT ...) can reference only select projections in order by clause
out.WriteAlias(c.defaultAlias()) //always quote
return
}
c.serialize(statement, out)
}
func (c ColumnExpressionImpl) serializeForProjection(statement StatementType, out *SQLBuilder) {
c.serialize(statement, out)
out.WriteString("AS")
out.WriteAlias(c.defaultAlias())
}
func (c ColumnExpressionImpl) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) {
if c.subQuery != nil {
out.WriteIdentifier(c.subQuery.Alias())
out.WriteByte('.')
out.WriteIdentifier(c.defaultAlias())
} else {
if c.tableName != "" && !contains(options, ShortName) {
out.WriteIdentifier(c.tableName)
out.WriteByte('.')
}
out.WriteIdentifier(c.name)
}
}

View File

@@ -0,0 +1,27 @@
package jet
// ColumnAssigment is interface wrapper around column assigment
type ColumnAssigment interface {
Serializer
isColumnAssigment()
}
type columnAssigmentImpl struct {
column ColumnSerializer
expression Expression
}
func NewColumnAssignment(serializer ColumnSerializer, expression Expression) ColumnAssigment {
return &columnAssigmentImpl{
column: serializer,
expression: expression,
}
}
func (a columnAssigmentImpl) isColumnAssigment() {}
func (a columnAssigmentImpl) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) {
a.column.serialize(statement, out, ShortName.WithFallTrough(options)...)
out.WriteString("=")
a.expression.serialize(statement, out, FallTrough(options)...)
}

View File

@@ -0,0 +1,100 @@
package jet
// ColumnList is a helper type to support list of columns as single projection
type ColumnList []ColumnExpression
// SET creates column assigment for each column in column list. expression should be created by ROW function
//
// Link.UPDATE().
// SET(Link.MutableColumns.SET(ROW(String("github.com"), Bool(false))).
// WHERE(Link.ID.EQ(Int(0)))
func (cl ColumnList) SET(expression Expression) ColumnAssigment {
return columnAssigmentImpl{
column: cl,
expression: expression,
}
}
// Except will create new column list in which columns contained in list of excluded column names are removed
//
// Address.AllColumns.Except(Address.PostalCode, Address.Phone)
func (cl ColumnList) Except(excludedColumns ...Column) ColumnList {
excludedColumnList := UnwidColumnList(excludedColumns)
excludedColumnNames := map[string]bool{}
for _, excludedColumn := range excludedColumnList {
excludedColumnNames[excludedColumn.Name()] = true
}
var ret ColumnList
for _, column := range cl {
if excludedColumnNames[column.Name()] {
continue
}
ret = append(ret, column)
}
return ret
}
// As will create new projection list where each column is wrapped with a new table alias.
// tableAlias should be in the form 'name' or 'name.*', or it can also be an empty string.
// For instance: If projection list has a column 'Artist.Name', and tableAlias is 'Musician.*', returned projection list will
// have a column wrapped in alias 'Musician.Name'. If tableAlias is empty string, it removes existing table alias ('Artist.Name' becomes 'Name').
func (cl ColumnList) As(tableAlias string) ProjectionList {
ret := make(ProjectionList, 0, len(cl))
for _, c := range cl {
ret = append(ret, c.AS(joinAlias(tableAlias, c.Name())))
}
return ret
}
func (cl ColumnList) fromImpl(subQuery SelectTable) Projection {
newProjectionList := ProjectionList{}
for _, column := range cl {
newProjectionList = append(newProjectionList, column.fromImpl(subQuery))
}
return newProjectionList
}
func (cl ColumnList) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) {
out.WriteString("(")
for i, column := range cl {
if i > 0 {
out.WriteString(", ")
}
column.serialize(statement, out, FallTrough(options)...)
}
out.WriteString(")")
}
func (cl ColumnList) serializeForProjection(statement StatementType, out *SQLBuilder) {
projections := ColumnListToProjectionList(cl)
SerializeProjectionList(statement, projections, out)
}
// dummy column interface implementation
// Name is placeholder for ColumnList to implement Column interface
func (cl ColumnList) Name() string { return "" }
// TableName is placeholder for ColumnList to implement Column interface
func (cl ColumnList) TableName() string { return "" }
func (cl ColumnList) setTableName(name string) {}
func (cl ColumnList) setSubQuery(subQuery SelectTable) {}
func (cl ColumnList) defaultAlias() string { return "" }
// SetTableName is utility function to set table name from outside of jet package to avoid making public setTableName
func SetTableName(columnExpression ColumnExpression, tableName string) {
columnExpression.setTableName(tableName)
}
// SetSubQuery is utility function to set table name from outside of jet package to avoid making public setSubQuery
func SetSubQuery(columnExpression ColumnExpression, subQuery SelectTable) {
columnExpression.setSubQuery(subQuery)
}

View File

@@ -0,0 +1,14 @@
package jet
import "testing"
func TestColumn(t *testing.T) {
column := NewColumnImpl("col", "", nil)
column.ExpressionInterfaceImpl.Parent = &column
assertClauseSerialize(t, column, "col")
column.setTableName("table1")
assertClauseSerialize(t, column, "table1.col")
assertProjectionSerialize(t, &column, `table1.col AS "table1.col"`)
assertProjectionSerialize(t, column.AS("alias1"), `table1.col AS "alias1"`)
}

View File

@@ -0,0 +1,401 @@
package jet
// ColumnBool is interface for SQL boolean columns.
type ColumnBool interface {
BoolExpression
Column
From(subQuery SelectTable) ColumnBool
SET(boolExp BoolExpression) ColumnAssigment
}
type boolColumnImpl struct {
boolInterfaceImpl
ColumnExpressionImpl
}
func (i *boolColumnImpl) From(subQuery SelectTable) ColumnBool {
newBoolColumn := BoolColumn(i.name)
newBoolColumn.setTableName(i.tableName)
newBoolColumn.setSubQuery(subQuery)
return newBoolColumn
}
func (i *boolColumnImpl) SET(boolExp BoolExpression) ColumnAssigment {
return columnAssigmentImpl{
column: i,
expression: boolExp,
}
}
// BoolColumn creates named bool column.
func BoolColumn(name string) ColumnBool {
boolColumn := &boolColumnImpl{}
boolColumn.ColumnExpressionImpl = NewColumnImpl(name, "", boolColumn)
boolColumn.boolInterfaceImpl.parent = boolColumn
return boolColumn
}
//------------------------------------------------------//
// ColumnFloat is interface for SQL real, numeric, decimal or double precision column.
type ColumnFloat interface {
FloatExpression
Column
From(subQuery SelectTable) ColumnFloat
SET(floatExp FloatExpression) ColumnAssigment
}
type floatColumnImpl struct {
floatInterfaceImpl
ColumnExpressionImpl
}
func (i *floatColumnImpl) From(subQuery SelectTable) ColumnFloat {
newFloatColumn := FloatColumn(i.name)
newFloatColumn.setTableName(i.tableName)
newFloatColumn.setSubQuery(subQuery)
return newFloatColumn
}
func (i *floatColumnImpl) SET(floatExp FloatExpression) ColumnAssigment {
return columnAssigmentImpl{
column: i,
expression: floatExp,
}
}
// FloatColumn creates named float column.
func FloatColumn(name string) ColumnFloat {
floatColumn := &floatColumnImpl{}
floatColumn.floatInterfaceImpl.parent = floatColumn
floatColumn.ColumnExpressionImpl = NewColumnImpl(name, "", floatColumn)
return floatColumn
}
//------------------------------------------------------//
// ColumnInteger is interface for SQL smallint, integer, bigint columns.
type ColumnInteger interface {
IntegerExpression
Column
From(subQuery SelectTable) ColumnInteger
SET(intExp IntegerExpression) ColumnAssigment
}
type integerColumnImpl struct {
integerInterfaceImpl
ColumnExpressionImpl
}
func (i *integerColumnImpl) From(subQuery SelectTable) ColumnInteger {
newIntColumn := IntegerColumn(i.name)
newIntColumn.setTableName(i.tableName)
newIntColumn.setSubQuery(subQuery)
return newIntColumn
}
func (i *integerColumnImpl) SET(intExp IntegerExpression) ColumnAssigment {
return columnAssigmentImpl{
column: i,
expression: intExp,
}
}
// IntegerColumn creates named integer column.
func IntegerColumn(name string) ColumnInteger {
integerColumn := &integerColumnImpl{}
integerColumn.integerInterfaceImpl.parent = integerColumn
integerColumn.ColumnExpressionImpl = NewColumnImpl(name, "", integerColumn)
return integerColumn
}
//------------------------------------------------------//
// ColumnString is interface for SQL text, character, character varying
// bytea, uuid columns and enums types.
type ColumnString interface {
StringExpression
Column
From(subQuery SelectTable) ColumnString
SET(stringExp StringExpression) ColumnAssigment
}
type stringColumnImpl struct {
stringInterfaceImpl
ColumnExpressionImpl
}
func (i *stringColumnImpl) From(subQuery SelectTable) ColumnString {
newStrColumn := StringColumn(i.name)
newStrColumn.setTableName(i.tableName)
newStrColumn.setSubQuery(subQuery)
return newStrColumn
}
func (i *stringColumnImpl) SET(stringExp StringExpression) ColumnAssigment {
return columnAssigmentImpl{
column: i,
expression: stringExp,
}
}
// StringColumn creates named string column.
func StringColumn(name string) ColumnString {
stringColumn := &stringColumnImpl{}
stringColumn.stringInterfaceImpl.parent = stringColumn
stringColumn.ColumnExpressionImpl = NewColumnImpl(name, "", stringColumn)
return stringColumn
}
//------------------------------------------------------//
// ColumnTime is interface for SQL time column.
type ColumnTime interface {
TimeExpression
Column
From(subQuery SelectTable) ColumnTime
SET(timeExp TimeExpression) ColumnAssigment
}
type timeColumnImpl struct {
timeInterfaceImpl
ColumnExpressionImpl
}
func (i *timeColumnImpl) From(subQuery SelectTable) ColumnTime {
newTimeColumn := TimeColumn(i.name)
newTimeColumn.setTableName(i.tableName)
newTimeColumn.setSubQuery(subQuery)
return newTimeColumn
}
func (i *timeColumnImpl) SET(timeExp TimeExpression) ColumnAssigment {
return columnAssigmentImpl{
column: i,
expression: timeExp,
}
}
// TimeColumn creates named time column
func TimeColumn(name string) ColumnTime {
timeColumn := &timeColumnImpl{}
timeColumn.timeInterfaceImpl.parent = timeColumn
timeColumn.ColumnExpressionImpl = NewColumnImpl(name, "", timeColumn)
return timeColumn
}
//------------------------------------------------------//
// ColumnTimez is interface of SQL time with time zone columns.
type ColumnTimez interface {
TimezExpression
Column
From(subQuery SelectTable) ColumnTimez
SET(timeExp TimezExpression) ColumnAssigment
}
type timezColumnImpl struct {
timezInterfaceImpl
ColumnExpressionImpl
}
func (i *timezColumnImpl) From(subQuery SelectTable) ColumnTimez {
newTimezColumn := TimezColumn(i.name)
newTimezColumn.setTableName(i.tableName)
newTimezColumn.setSubQuery(subQuery)
return newTimezColumn
}
func (i *timezColumnImpl) SET(timezExp TimezExpression) ColumnAssigment {
return columnAssigmentImpl{
column: i,
expression: timezExp,
}
}
// TimezColumn creates named time with time zone column.
func TimezColumn(name string) ColumnTimez {
timezColumn := &timezColumnImpl{}
timezColumn.timezInterfaceImpl.parent = timezColumn
timezColumn.ColumnExpressionImpl = NewColumnImpl(name, "", timezColumn)
return timezColumn
}
//------------------------------------------------------//
// ColumnTimestamp is interface of SQL timestamp columns.
type ColumnTimestamp interface {
TimestampExpression
Column
From(subQuery SelectTable) ColumnTimestamp
SET(timestampExp TimestampExpression) ColumnAssigment
}
type timestampColumnImpl struct {
timestampInterfaceImpl
ColumnExpressionImpl
}
func (i *timestampColumnImpl) From(subQuery SelectTable) ColumnTimestamp {
newTimestampColumn := TimestampColumn(i.name)
newTimestampColumn.setTableName(i.tableName)
newTimestampColumn.setSubQuery(subQuery)
return newTimestampColumn
}
func (i *timestampColumnImpl) SET(timestampExp TimestampExpression) ColumnAssigment {
return columnAssigmentImpl{
column: i,
expression: timestampExp,
}
}
// TimestampColumn creates named timestamp column
func TimestampColumn(name string) ColumnTimestamp {
timestampColumn := &timestampColumnImpl{}
timestampColumn.timestampInterfaceImpl.parent = timestampColumn
timestampColumn.ColumnExpressionImpl = NewColumnImpl(name, "", timestampColumn)
return timestampColumn
}
//------------------------------------------------------//
// ColumnTimestampz is interface of SQL timestamp with timezone columns.
type ColumnTimestampz interface {
TimestampzExpression
Column
From(subQuery SelectTable) ColumnTimestampz
SET(timestampzExp TimestampzExpression) ColumnAssigment
}
type timestampzColumnImpl struct {
timestampzInterfaceImpl
ColumnExpressionImpl
}
func (i *timestampzColumnImpl) From(subQuery SelectTable) ColumnTimestampz {
newTimestampzColumn := TimestampzColumn(i.name)
newTimestampzColumn.setTableName(i.tableName)
newTimestampzColumn.setSubQuery(subQuery)
return newTimestampzColumn
}
func (i *timestampzColumnImpl) SET(timestampzExp TimestampzExpression) ColumnAssigment {
return columnAssigmentImpl{
column: i,
expression: timestampzExp,
}
}
// TimestampzColumn creates named timestamp with time zone column.
func TimestampzColumn(name string) ColumnTimestampz {
timestampzColumn := &timestampzColumnImpl{}
timestampzColumn.timestampzInterfaceImpl.parent = timestampzColumn
timestampzColumn.ColumnExpressionImpl = NewColumnImpl(name, "", timestampzColumn)
return timestampzColumn
}
//------------------------------------------------------//
// ColumnDate is interface of SQL date columns.
type ColumnDate interface {
DateExpression
Column
From(subQuery SelectTable) ColumnDate
SET(dateExp DateExpression) ColumnAssigment
}
type dateColumnImpl struct {
dateInterfaceImpl
ColumnExpressionImpl
}
func (i *dateColumnImpl) From(subQuery SelectTable) ColumnDate {
newDateColumn := DateColumn(i.name)
newDateColumn.setTableName(i.tableName)
newDateColumn.setSubQuery(subQuery)
return newDateColumn
}
func (i *dateColumnImpl) SET(dateExp DateExpression) ColumnAssigment {
return columnAssigmentImpl{
column: i,
expression: dateExp,
}
}
// DateColumn creates named date column.
func DateColumn(name string) ColumnDate {
dateColumn := &dateColumnImpl{}
dateColumn.dateInterfaceImpl.parent = dateColumn
dateColumn.ColumnExpressionImpl = NewColumnImpl(name, "", dateColumn)
return dateColumn
}
//------------------------------------------------------//
// ColumnRange is interface for range columns which can be int range, string range
// timestamp range or date range.
type ColumnRange[T Expression] interface {
Range[T]
Column
From(subQuery SelectTable) ColumnRange[T]
SET(rangeExp Range[T]) ColumnAssigment
}
type rangeColumnImpl[T Expression] struct {
rangeInterfaceImpl[T]
ColumnExpressionImpl
}
func (i *rangeColumnImpl[T]) From(subQuery SelectTable) ColumnRange[T] {
newRangeColumn := RangeColumn[T](i.name)
newRangeColumn.setTableName(i.tableName)
newRangeColumn.setSubQuery(subQuery)
return newRangeColumn
}
func (i *rangeColumnImpl[T]) SET(rangeExp Range[T]) ColumnAssigment {
return columnAssigmentImpl{
column: i,
expression: rangeExp,
}
}
// RangeColumn creates named range column.
func RangeColumn[T Expression](name string) ColumnRange[T] {
rangeColumn := &rangeColumnImpl[T]{}
rangeColumn.rangeInterfaceImpl.parent = rangeColumn
rangeColumn.ColumnExpressionImpl = NewColumnImpl(name, "", rangeColumn)
return rangeColumn
}

View File

@@ -0,0 +1,116 @@
package jet
import (
"testing"
)
var subQuery = &selectTableImpl{
alias: "sub_query",
}
func TestNewBoolColumn(t *testing.T) {
boolColumn := BoolColumn("colBool").From(subQuery)
assertClauseSerialize(t, boolColumn, `sub_query."colBool"`)
assertClauseSerialize(t, boolColumn.EQ(Bool(true)), `(sub_query."colBool" = $1)`, true)
assertProjectionSerialize(t, boolColumn, `sub_query."colBool" AS "colBool"`)
boolColumn2 := table1ColBool.From(subQuery)
assertClauseSerialize(t, boolColumn2, `sub_query."table1.col_bool"`)
assertClauseSerialize(t, boolColumn2.EQ(Bool(true)), `(sub_query."table1.col_bool" = $1)`, true)
assertProjectionSerialize(t, boolColumn2, `sub_query."table1.col_bool" AS "table1.col_bool"`)
}
func TestNewIntColumn(t *testing.T) {
intColumn := IntegerColumn("col_int").From(subQuery)
assertClauseSerialize(t, intColumn, `sub_query.col_int`)
assertClauseSerialize(t, intColumn.EQ(Int(12)), `(sub_query.col_int = $1)`, int64(12))
assertProjectionSerialize(t, intColumn, `sub_query.col_int AS "col_int"`)
intColumn2 := table1ColInt.From(subQuery)
assertClauseSerialize(t, intColumn2, `sub_query."table1.col_int"`)
assertClauseSerialize(t, intColumn2.EQ(Int(14)), `(sub_query."table1.col_int" = $1)`, int64(14))
assertProjectionSerialize(t, intColumn2, `sub_query."table1.col_int" AS "table1.col_int"`)
}
func TestNewFloatColumnColumn(t *testing.T) {
floatColumn := FloatColumn("col_float").From(subQuery)
assertClauseSerialize(t, floatColumn, `sub_query.col_float`)
assertClauseSerialize(t, floatColumn.EQ(Float(1.11)), `(sub_query.col_float = $1)`, float64(1.11))
assertProjectionSerialize(t, floatColumn, `sub_query.col_float AS "col_float"`)
floatColumn2 := table1ColFloat.From(subQuery)
assertClauseSerialize(t, floatColumn2, `sub_query."table1.col_float"`)
assertClauseSerialize(t, floatColumn2.EQ(Float(2.22)), `(sub_query."table1.col_float" = $1)`, float64(2.22))
assertProjectionSerialize(t, floatColumn2, `sub_query."table1.col_float" AS "table1.col_float"`)
}
func TestNewDateColumnColumn(t *testing.T) {
dateColumn := DateColumn("col_date").From(subQuery)
assertClauseSerialize(t, dateColumn, `sub_query.col_date`)
assertClauseSerialize(t, dateColumn.EQ(Date(2002, 2, 3)),
`(sub_query.col_date = $1)`, "2002-02-03")
assertProjectionSerialize(t, dateColumn, `sub_query.col_date AS "col_date"`)
dateColumn2 := table1ColDate.From(subQuery)
assertClauseSerialize(t, dateColumn2, `sub_query."table1.col_date"`)
assertClauseSerialize(t, dateColumn2.EQ(Date(2002, 2, 3)),
`(sub_query."table1.col_date" = $1)`, "2002-02-03")
assertProjectionSerialize(t, dateColumn2, `sub_query."table1.col_date" AS "table1.col_date"`)
}
func TestNewTimeColumnColumn(t *testing.T) {
timeColumn := TimeColumn("col_time").From(subQuery)
assertClauseSerialize(t, timeColumn, `sub_query.col_time`)
assertClauseSerialize(t, timeColumn.EQ(Time(1, 1, 1, 1)),
`(sub_query.col_time = $1)`, "01:01:01.000000001")
assertProjectionSerialize(t, timeColumn, `sub_query.col_time AS "col_time"`)
timeColumn2 := table1ColTime.From(subQuery)
assertClauseSerialize(t, timeColumn2, `sub_query."table1.col_time"`)
assertClauseSerialize(t, timeColumn2.EQ(Time(2, 2, 2)),
`(sub_query."table1.col_time" = $1)`, "02:02:02")
assertProjectionSerialize(t, timeColumn2, `sub_query."table1.col_time" AS "table1.col_time"`)
}
func TestNewTimezColumnColumn(t *testing.T) {
timezColumn := TimezColumn("col_timez").From(subQuery)
assertClauseSerialize(t, timezColumn, `sub_query.col_timez`)
assertClauseSerialize(t, timezColumn.EQ(Timez(1, 1, 1, 1, "UTC")),
`(sub_query.col_timez = $1)`, "01:01:01.000000001 UTC")
assertProjectionSerialize(t, timezColumn, `sub_query.col_timez AS "col_timez"`)
timezColumn2 := table1ColTimez.From(subQuery)
assertClauseSerialize(t, timezColumn2, `sub_query."table1.col_timez"`)
assertClauseSerialize(t, timezColumn2.EQ(Timez(2, 2, 2, 0, "UTC")),
`(sub_query."table1.col_timez" = $1)`, "02:02:02 UTC")
assertProjectionSerialize(t, timezColumn2, `sub_query."table1.col_timez" AS "table1.col_timez"`)
}
func TestNewTimestampColumnColumn(t *testing.T) {
timestampColumn := TimestampColumn("col_timestamp").From(subQuery)
assertClauseSerialize(t, timestampColumn, `sub_query.col_timestamp`)
assertClauseSerialize(t, timestampColumn.EQ(Timestamp(1, 1, 1, 1, 1, 1)),
`(sub_query.col_timestamp = $1)`, "0001-01-01 01:01:01")
assertProjectionSerialize(t, timestampColumn, `sub_query.col_timestamp AS "col_timestamp"`)
timestampColumn2 := table1ColTimestamp.From(subQuery)
assertClauseSerialize(t, timestampColumn2, `sub_query."table1.col_timestamp"`)
assertClauseSerialize(t, timestampColumn2.EQ(Timestamp(2, 2, 2, 2, 2, 2)),
`(sub_query."table1.col_timestamp" = $1)`, "0002-02-02 02:02:02")
assertProjectionSerialize(t, timestampColumn2, `sub_query."table1.col_timestamp" AS "table1.col_timestamp"`)
}
func TestNewTimestampzColumnColumn(t *testing.T) {
timestampzColumn := TimestampzColumn("col_timestampz").From(subQuery)
assertClauseSerialize(t, timestampzColumn, `sub_query.col_timestampz`)
assertClauseSerialize(t, timestampzColumn.EQ(Timestampz(1, 1, 1, 1, 1, 1, 0, "UTC")),
`(sub_query.col_timestampz = $1)`, "0001-01-01 01:01:01 UTC")
assertProjectionSerialize(t, timestampzColumn, `sub_query.col_timestampz AS "col_timestampz"`)
timestampzColumn2 := table1ColTimestampz.From(subQuery)
assertClauseSerialize(t, timestampzColumn2, `sub_query."table1.col_timestampz"`)
assertClauseSerialize(t, timestampzColumn2.EQ(Timestampz(2, 2, 2, 2, 2, 2, 0, "UTC")),
`(sub_query."table1.col_timestampz" = $1)`, "0002-02-02 02:02:02 UTC")
assertProjectionSerialize(t, timestampzColumn2, `sub_query."table1.col_timestampz" AS "table1.col_timestampz"`)
}

View File

@@ -0,0 +1,93 @@
package jet
// DateExpression is interface for date types
type DateExpression interface {
Expression
EQ(rhs DateExpression) BoolExpression
NOT_EQ(rhs DateExpression) BoolExpression
IS_DISTINCT_FROM(rhs DateExpression) BoolExpression
IS_NOT_DISTINCT_FROM(rhs DateExpression) BoolExpression
LT(rhs DateExpression) BoolExpression
LT_EQ(rhs DateExpression) BoolExpression
GT(rhs DateExpression) BoolExpression
GT_EQ(rhs DateExpression) BoolExpression
BETWEEN(min, max DateExpression) BoolExpression
NOT_BETWEEN(min, max DateExpression) BoolExpression
ADD(rhs Interval) TimestampExpression
SUB(rhs Interval) TimestampExpression
}
type dateInterfaceImpl struct {
parent DateExpression
}
func (d *dateInterfaceImpl) EQ(rhs DateExpression) BoolExpression {
return Eq(d.parent, rhs)
}
func (d *dateInterfaceImpl) NOT_EQ(rhs DateExpression) BoolExpression {
return NotEq(d.parent, rhs)
}
func (d *dateInterfaceImpl) IS_DISTINCT_FROM(rhs DateExpression) BoolExpression {
return IsDistinctFrom(d.parent, rhs)
}
func (d *dateInterfaceImpl) IS_NOT_DISTINCT_FROM(rhs DateExpression) BoolExpression {
return IsNotDistinctFrom(d.parent, rhs)
}
func (d *dateInterfaceImpl) LT(rhs DateExpression) BoolExpression {
return Lt(d.parent, rhs)
}
func (d *dateInterfaceImpl) LT_EQ(rhs DateExpression) BoolExpression {
return LtEq(d.parent, rhs)
}
func (d *dateInterfaceImpl) GT(rhs DateExpression) BoolExpression {
return Gt(d.parent, rhs)
}
func (d *dateInterfaceImpl) GT_EQ(rhs DateExpression) BoolExpression {
return GtEq(d.parent, rhs)
}
func (d *dateInterfaceImpl) BETWEEN(min, max DateExpression) BoolExpression {
return NewBetweenOperatorExpression(d.parent, min, max, false)
}
func (d *dateInterfaceImpl) NOT_BETWEEN(min, max DateExpression) BoolExpression {
return NewBetweenOperatorExpression(d.parent, min, max, true)
}
func (d *dateInterfaceImpl) ADD(rhs Interval) TimestampExpression {
return TimestampExp(Add(d.parent, rhs))
}
func (d *dateInterfaceImpl) SUB(rhs Interval) TimestampExpression {
return TimestampExp(Sub(d.parent, rhs))
}
//---------------------------------------------------//
type dateExpressionWrapper struct {
dateInterfaceImpl
Expression
}
func newDateExpressionWrap(expression Expression) DateExpression {
dateExpressionWrap := dateExpressionWrapper{Expression: expression}
dateExpressionWrap.dateInterfaceImpl.parent = &dateExpressionWrap
return &dateExpressionWrap
}
// DateExp is date expression wrapper around arbitrary expression.
// Allows go compiler to see any expression as date expression.
// Does not add sql cast to generated sql builder output.
func DateExp(expression Expression) DateExpression {
return newDateExpressionWrap(expression)
}

View File

@@ -0,0 +1,13 @@
package jet
import (
"testing"
)
func TestDateArithmetic(t *testing.T) {
timestamp := Timestamp(2000, 1, 1, 0, 0, 0)
assertClauseDebugSerialize(t, table1ColDate.ADD(NewInterval(String("1 HOUR"))).EQ(timestamp),
"((table1.col_date + INTERVAL '1 HOUR') = '2000-01-01 00:00:00')")
assertClauseDebugSerialize(t, table1ColDate.SUB(NewInterval(String("1 HOUR"))).EQ(timestamp),
"((table1.col_date - INTERVAL '1 HOUR') = '2000-01-01 00:00:00')")
}

View File

@@ -0,0 +1,125 @@
package jet
import "strings"
// Dialect interface
type Dialect interface {
Name() string
PackageName() string
OperatorSerializeOverride(operator string) SerializeOverride
FunctionSerializeOverride(function string) SerializeOverride
AliasQuoteChar() byte
IdentifierQuoteChar() byte
ArgumentPlaceholder() QueryPlaceholderFunc
IsReservedWord(name string) bool
SerializeOrderBy() func(expression Expression, ascending, nullsFirst *bool) SerializerFunc
ValuesDefaultColumnName(index int) string
}
// SerializerFunc func
type SerializerFunc func(statement StatementType, out *SQLBuilder, options ...SerializeOption)
// SerializeOverride func
type SerializeOverride func(expressions ...Serializer) SerializerFunc
// QueryPlaceholderFunc func
type QueryPlaceholderFunc func(ord int) string
// DialectParams struct
type DialectParams struct {
Name string
PackageName string
OperatorSerializeOverrides map[string]SerializeOverride
FunctionSerializeOverrides map[string]SerializeOverride
AliasQuoteChar byte
IdentifierQuoteChar byte
ArgumentPlaceholder QueryPlaceholderFunc
ReservedWords []string
SerializeOrderBy func(expression Expression, ascending, nullsFirst *bool) SerializerFunc
ValuesDefaultColumnName func(index int) string
}
// NewDialect creates new dialect with params
func NewDialect(params DialectParams) Dialect {
return &dialectImpl{
name: params.Name,
packageName: params.PackageName,
operatorSerializeOverrides: params.OperatorSerializeOverrides,
functionSerializeOverrides: params.FunctionSerializeOverrides,
aliasQuoteChar: params.AliasQuoteChar,
identifierQuoteChar: params.IdentifierQuoteChar,
argumentPlaceholder: params.ArgumentPlaceholder,
reservedWords: arrayOfStringsToMapOfStrings(params.ReservedWords),
serializeOrderBy: params.SerializeOrderBy,
valuesDefaultColumnName: params.ValuesDefaultColumnName,
}
}
type dialectImpl struct {
name string
packageName string
operatorSerializeOverrides map[string]SerializeOverride
functionSerializeOverrides map[string]SerializeOverride
aliasQuoteChar byte
identifierQuoteChar byte
argumentPlaceholder QueryPlaceholderFunc
reservedWords map[string]bool
serializeOrderBy func(expression Expression, ascending, nullsFirst *bool) SerializerFunc
valuesDefaultColumnName func(index int) string
}
func (d *dialectImpl) Name() string {
return d.name
}
func (d *dialectImpl) PackageName() string {
return d.packageName
}
func (d *dialectImpl) OperatorSerializeOverride(operator string) SerializeOverride {
if d.operatorSerializeOverrides == nil {
return nil
}
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 {
return d.aliasQuoteChar
}
func (d *dialectImpl) IdentifierQuoteChar() byte {
return d.identifierQuoteChar
}
func (d *dialectImpl) ArgumentPlaceholder() QueryPlaceholderFunc {
return d.argumentPlaceholder
}
func (d *dialectImpl) IsReservedWord(name string) bool {
_, isReservedWord := d.reservedWords[strings.ToLower(name)]
return isReservedWord
}
func (d *dialectImpl) SerializeOrderBy() func(expression Expression, ascending, nullsFirst *bool) SerializerFunc {
return d.serializeOrderBy
}
func (d *dialectImpl) ValuesDefaultColumnName(index int) string {
return d.valuesDefaultColumnName(index)
}
func arrayOfStringsToMapOfStrings(arr []string) map[string]bool {
ret := map[string]bool{}
for _, elem := range arr {
ret[strings.ToLower(elem)] = true
}
return ret
}

View File

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

View File

@@ -0,0 +1,321 @@
package jet
import "fmt"
// Expression is common interface for all expressions.
// Can be Bool, Int, Float, String, Date, Time, Timez, Timestamp or Timestampz expressions.
type Expression interface {
Serializer
Projection
GroupByClause
OrderByClause
// IS_NULL tests expression whether it is a NULL value.
IS_NULL() BoolExpression
// IS_NOT_NULL tests expression whether it is a non-NULL value.
IS_NOT_NULL() BoolExpression
// IN checks if this expressions matches any in expressions list
IN(expressions ...Expression) BoolExpression
// NOT_IN checks if this expressions is different of all expressions in expressions list
NOT_IN(expressions ...Expression) BoolExpression
// AS the temporary alias name to assign to the expression
AS(alias string) Projection
// ASC expression will be used to sort query result in ascending order
ASC() OrderByClause
// DESC expression will be used to sort query result in descending order
DESC() OrderByClause
}
// ExpressionInterfaceImpl implements Expression interface methods
type ExpressionInterfaceImpl struct {
Parent Expression
}
func (e *ExpressionInterfaceImpl) fromImpl(subQuery SelectTable) Projection {
panic(fmt.Sprintf("jet: can't export unaliased expression subQuery: %s, expression: %s",
subQuery.Alias(), serializeToDefaultDebugString(e.Parent)))
}
// IS_NULL tests expression whether it is a NULL value.
func (e *ExpressionInterfaceImpl) IS_NULL() BoolExpression {
return newPostfixBoolOperatorExpression(e.Parent, "IS NULL")
}
// IS_NOT_NULL tests expression whether it is a non-NULL value.
func (e *ExpressionInterfaceImpl) IS_NOT_NULL() BoolExpression {
return newPostfixBoolOperatorExpression(e.Parent, "IS NOT NULL")
}
// IN checks if this expressions matches any in expressions list
func (e *ExpressionInterfaceImpl) IN(expressions ...Expression) BoolExpression {
return newBinaryBoolOperatorExpression(e.Parent, wrap(expressions...), "IN")
}
// NOT_IN checks if this expressions is different of all expressions in expressions list
func (e *ExpressionInterfaceImpl) NOT_IN(expressions ...Expression) BoolExpression {
return newBinaryBoolOperatorExpression(e.Parent, wrap(expressions...), "NOT IN")
}
// AS the temporary alias name to assign to the expression
func (e *ExpressionInterfaceImpl) AS(alias string) Projection {
return newAlias(e.Parent, alias)
}
// ASC expression will be used to sort a query result in ascending order
func (e *ExpressionInterfaceImpl) ASC() OrderByClause {
return newOrderByAscending(e.Parent, true)
}
// DESC expression will be used to sort a query result in descending order
func (e *ExpressionInterfaceImpl) DESC() OrderByClause {
return newOrderByAscending(e.Parent, false)
}
// NULLS_FIRST specifies sort where null values appear before all non-null values
func (e *ExpressionInterfaceImpl) NULLS_FIRST() OrderByClause {
return newOrderByNullsFirst(e.Parent, true)
}
// NULLS_LAST specifies sort where null values appear after all non-null values
func (e *ExpressionInterfaceImpl) NULLS_LAST() OrderByClause {
return newOrderByNullsFirst(e.Parent, false)
}
func (e *ExpressionInterfaceImpl) serializeForGroupBy(statement StatementType, out *SQLBuilder) {
e.Parent.serialize(statement, out, NoWrap)
}
func (e *ExpressionInterfaceImpl) serializeForProjection(statement StatementType, out *SQLBuilder) {
e.Parent.serialize(statement, out, NoWrap)
}
func (e *ExpressionInterfaceImpl) serializeForOrderBy(statement StatementType, out *SQLBuilder) {
e.Parent.serialize(statement, out, NoWrap)
}
// Representation of binary operations (e.g. comparisons, arithmetic)
type binaryOperatorExpression struct {
ExpressionInterfaceImpl
lhs, rhs Serializer
additionalParam Serializer
operator string
}
// NewBinaryOperatorExpression creates new binaryOperatorExpression
func NewBinaryOperatorExpression(lhs, rhs Serializer, operator string, additionalParam ...Expression) Expression {
binaryExpression := &binaryOperatorExpression{
lhs: lhs,
rhs: rhs,
operator: operator,
}
if len(additionalParam) > 0 {
binaryExpression.additionalParam = additionalParam[0]
}
binaryExpression.ExpressionInterfaceImpl.Parent = binaryExpression
return complexExpr(binaryExpression)
}
func (c *binaryOperatorExpression) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) {
if serializeOverride := out.Dialect.OperatorSerializeOverride(c.operator); serializeOverride != nil {
serializeOverrideFunc := serializeOverride(c.lhs, c.rhs, c.additionalParam)
serializeOverrideFunc(statement, out, FallTrough(options)...)
} else {
c.lhs.serialize(statement, out, FallTrough(options)...)
out.WriteString(c.operator)
c.rhs.serialize(statement, out, FallTrough(options)...)
}
}
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
expression Expression
operator string
}
func newPrefixOperatorExpression(expression Expression, operator string) Expression {
prefixExpression := &prefixExpression{
expression: expression,
operator: operator,
}
prefixExpression.ExpressionInterfaceImpl.Parent = prefixExpression
return complexExpr(prefixExpression)
}
func (p *prefixExpression) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) {
out.WriteString(p.operator)
p.expression.serialize(statement, out, FallTrough(options)...)
}
// A postfix operator Expression
type postfixOpExpression struct {
ExpressionInterfaceImpl
expression Expression
operator string
}
func newPostfixOperatorExpression(expression Expression, operator string) *postfixOpExpression {
postfixOpExpression := &postfixOpExpression{
expression: expression,
operator: operator,
}
postfixOpExpression.ExpressionInterfaceImpl.Parent = 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
notBetween bool
min Expression
max Expression
}
// NewBetweenOperatorExpression creates new BETWEEN operator expression
func NewBetweenOperatorExpression(expression, min, max Expression, notBetween bool) BoolExpression {
newBetweenOperator := &betweenOperatorExpression{
expression: expression,
notBetween: notBetween,
min: min,
max: max,
}
newBetweenOperator.ExpressionInterfaceImpl.Parent = 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.Parent = &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.Parent = 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

@@ -0,0 +1,38 @@
package jet
import (
"testing"
)
func TestExpressionIS_NULL(t *testing.T) {
assertClauseSerialize(t, table2Col3.IS_NULL(), "table2.col3 IS NULL")
assertClauseSerialize(t, table2Col3.ADD(table2Col3).IS_NULL(), "(table2.col3 + table2.col3) IS NULL")
}
func TestExpressionIS_NOT_NULL(t *testing.T) {
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")
}
func TestExpressionIS_DISTINCT_FROM(t *testing.T) {
assertClauseSerialize(t, table2Col3.IS_DISTINCT_FROM(table2Col4), "(table2.col3 IS DISTINCT FROM table2.col4)")
assertClauseSerialize(t, table2Col3.ADD(table2Col3).IS_DISTINCT_FROM(Int(23)), "((table2.col3 + table2.col3) IS DISTINCT FROM $1)", int64(23))
}
func TestExpressionIS_NOT_DISTINCT_FROM(t *testing.T) {
assertClauseSerialize(t, table2Col3.IS_NOT_DISTINCT_FROM(table2Col4), "(table2.col3 IS NOT DISTINCT FROM table2.col4)")
assertClauseSerialize(t, table2Col3.ADD(table2Col3).IS_NOT_DISTINCT_FROM(Int(23)), "((table2.col3 + table2.col3) IS NOT DISTINCT FROM $1)", int64(23))
}
func TestIN(t *testing.T) {
assertClauseSerialize(t, table2ColInt.IN(Int(1), Int(2), Int(3)),
`(table2.col_int IN ($1, $2, $3))`, int64(1), int64(2), int64(3))
}
func TestNOT_IN(t *testing.T) {
assertClauseSerialize(t, table2ColInt.NOT_IN(Int(1), Int(2), Int(3)),
`(table2.col_int NOT IN ($1, $2, $3))`, int64(1), int64(2), int64(3))
}

View File

@@ -0,0 +1,115 @@
package jet
// FloatExpression is interface for SQL float columns
type FloatExpression interface {
Expression
numericExpression
EQ(rhs FloatExpression) BoolExpression
NOT_EQ(rhs FloatExpression) BoolExpression
IS_DISTINCT_FROM(rhs FloatExpression) BoolExpression
IS_NOT_DISTINCT_FROM(rhs FloatExpression) BoolExpression
LT(rhs FloatExpression) BoolExpression
LT_EQ(rhs FloatExpression) BoolExpression
GT(rhs FloatExpression) BoolExpression
GT_EQ(rhs FloatExpression) BoolExpression
BETWEEN(min, max FloatExpression) BoolExpression
NOT_BETWEEN(min, max FloatExpression) BoolExpression
ADD(rhs NumericExpression) FloatExpression
SUB(rhs NumericExpression) FloatExpression
MUL(rhs NumericExpression) FloatExpression
DIV(rhs NumericExpression) FloatExpression
MOD(rhs NumericExpression) FloatExpression
POW(rhs NumericExpression) FloatExpression
}
type floatInterfaceImpl struct {
numericExpressionImpl
parent FloatExpression
}
func (n *floatInterfaceImpl) EQ(rhs FloatExpression) BoolExpression {
return Eq(n.parent, rhs)
}
func (n *floatInterfaceImpl) NOT_EQ(rhs FloatExpression) BoolExpression {
return NotEq(n.parent, rhs)
}
func (n *floatInterfaceImpl) IS_DISTINCT_FROM(rhs FloatExpression) BoolExpression {
return IsDistinctFrom(n.parent, rhs)
}
func (n *floatInterfaceImpl) IS_NOT_DISTINCT_FROM(rhs FloatExpression) BoolExpression {
return IsNotDistinctFrom(n.parent, rhs)
}
func (n *floatInterfaceImpl) GT(rhs FloatExpression) BoolExpression {
return Gt(n.parent, rhs)
}
func (n *floatInterfaceImpl) GT_EQ(rhs FloatExpression) BoolExpression {
return GtEq(n.parent, rhs)
}
func (n *floatInterfaceImpl) LT(rhs FloatExpression) BoolExpression {
return Lt(n.parent, rhs)
}
func (n *floatInterfaceImpl) LT_EQ(rhs FloatExpression) BoolExpression {
return LtEq(n.parent, rhs)
}
func (n *floatInterfaceImpl) BETWEEN(min, max FloatExpression) BoolExpression {
return NewBetweenOperatorExpression(n.parent, min, max, false)
}
func (n *floatInterfaceImpl) NOT_BETWEEN(min, max FloatExpression) BoolExpression {
return NewBetweenOperatorExpression(n.parent, min, max, true)
}
func (n *floatInterfaceImpl) ADD(rhs NumericExpression) FloatExpression {
return FloatExp(Add(n.parent, rhs))
}
func (n *floatInterfaceImpl) SUB(rhs NumericExpression) FloatExpression {
return FloatExp(Sub(n.parent, rhs))
}
func (n *floatInterfaceImpl) MUL(rhs NumericExpression) FloatExpression {
return FloatExp(Mul(n.parent, rhs))
}
func (n *floatInterfaceImpl) DIV(rhs NumericExpression) FloatExpression {
return FloatExp(Div(n.parent, rhs))
}
func (n *floatInterfaceImpl) MOD(rhs NumericExpression) FloatExpression {
return FloatExp(Mod(n.parent, rhs))
}
func (n *floatInterfaceImpl) POW(rhs NumericExpression) FloatExpression {
return POW(n.parent, rhs)
}
//---------------------------------------------------//
type floatExpressionWrapper struct {
floatInterfaceImpl
Expression
}
func newFloatExpressionWrap(expression Expression) FloatExpression {
floatExpressionWrap := floatExpressionWrapper{Expression: expression}
floatExpressionWrap.floatInterfaceImpl.parent = &floatExpressionWrap
return &floatExpressionWrap
}
// FloatExp is date expression wrapper around arbitrary expression.
// Allows go compiler to see any expression as float expression.
// Does not add sql cast to generated sql builder output.
func FloatExp(expression Expression) FloatExpression {
return newFloatExpressionWrap(expression)
}

View File

@@ -0,0 +1,82 @@
package jet
import (
"testing"
)
func TestFloatExpressionEQ(t *testing.T) {
assertClauseSerialize(t, table1ColFloat.EQ(table2ColFloat), "(table1.col_float = table2.col_float)")
assertClauseSerialize(t, table1ColFloat.EQ(Float(2.11)), "(table1.col_float = $1)", float64(2.11))
}
func TestFloatExpressionNOT_EQ(t *testing.T) {
assertClauseSerialize(t, table1ColFloat.NOT_EQ(table2ColFloat), "(table1.col_float != table2.col_float)")
assertClauseSerialize(t, table1ColFloat.NOT_EQ(Float(2.11)), "(table1.col_float != $1)", float64(2.11))
}
func TestFloatExpressionIS_DISTINCT_FROM(t *testing.T) {
assertClauseSerialize(t, table1ColFloat.IS_DISTINCT_FROM(table2ColFloat), "(table1.col_float IS DISTINCT FROM table2.col_float)")
assertClauseSerialize(t, table1ColFloat.IS_DISTINCT_FROM(Float(2.11)), "(table1.col_float IS DISTINCT FROM $1)", float64(2.11))
}
func TestFloatExpressionIS_NOT_DISTINCT_FROM(t *testing.T) {
assertClauseSerialize(t, table1ColFloat.IS_NOT_DISTINCT_FROM(table2ColFloat), "(table1.col_float IS NOT DISTINCT FROM table2.col_float)")
assertClauseSerialize(t, table1ColFloat.IS_NOT_DISTINCT_FROM(Float(2.11)), "(table1.col_float IS NOT DISTINCT FROM $1)", float64(2.11))
}
func TestFloatExpressionGT(t *testing.T) {
assertClauseSerialize(t, table1ColFloat.GT(table2ColFloat), "(table1.col_float > table2.col_float)")
assertClauseSerialize(t, table1ColFloat.GT(Float(2.11)), "(table1.col_float > $1)", float64(2.11))
}
func TestFloatExpressionGT_EQ(t *testing.T) {
assertClauseSerialize(t, table1ColFloat.GT_EQ(table2ColFloat), "(table1.col_float >= table2.col_float)")
assertClauseSerialize(t, table1ColFloat.GT_EQ(Float(2.11)), "(table1.col_float >= $1)", float64(2.11))
}
func TestFloatExpressionLT(t *testing.T) {
assertClauseSerialize(t, table1ColFloat.LT(table2ColFloat), "(table1.col_float < table2.col_float)")
assertClauseSerialize(t, table1ColFloat.LT(Float(2.11)), "(table1.col_float < $1)", float64(2.11))
}
func TestFloatExpressionLT_EQ(t *testing.T) {
assertClauseSerialize(t, table1ColFloat.LT_EQ(table2ColFloat), "(table1.col_float <= table2.col_float)")
assertClauseSerialize(t, table1ColFloat.LT_EQ(Float(2.11)), "(table1.col_float <= $1)", float64(2.11))
}
func TestFloatExpressionADD(t *testing.T) {
assertClauseSerialize(t, table1ColFloat.ADD(table2ColFloat), "(table1.col_float + table2.col_float)")
assertClauseSerialize(t, table1ColFloat.ADD(Float(2.11)), "(table1.col_float + $1)", float64(2.11))
}
func TestFloatExpressionSUB(t *testing.T) {
assertClauseSerialize(t, table1ColFloat.SUB(table2ColFloat), "(table1.col_float - table2.col_float)")
assertClauseSerialize(t, table1ColFloat.SUB(Float(2.11)), "(table1.col_float - $1)", float64(2.11))
}
func TestFloatExpressionMUL(t *testing.T) {
assertClauseSerialize(t, table1ColFloat.MUL(table2ColFloat), "(table1.col_float * table2.col_float)")
assertClauseSerialize(t, table1ColFloat.MUL(Float(2.11)), "(table1.col_float * $1)", float64(2.11))
}
func TestFloatExpressionDIV(t *testing.T) {
assertClauseSerialize(t, table1ColFloat.DIV(table2ColFloat), "(table1.col_float / table2.col_float)")
assertClauseSerialize(t, table1ColFloat.DIV(Float(2.11)), "(table1.col_float / $1)", float64(2.11))
}
func TestFloatExpressionMOD(t *testing.T) {
assertClauseSerialize(t, table1ColFloat.MOD(table2ColFloat), "(table1.col_float % table2.col_float)")
assertClauseSerialize(t, table1ColFloat.MOD(Float(2.11)), "(table1.col_float % $1)", float64(2.11))
}
func TestFloatExpressionPOW(t *testing.T) {
assertClauseSerialize(t, table1ColFloat.POW(table2ColFloat), "POW(table1.col_float, table2.col_float)")
assertClauseSerialize(t, table1ColFloat.POW(Float(2.11)), "POW(table1.col_float, $1)", float64(2.11))
}
func TestFloatExp(t *testing.T) {
assertClauseSerialize(t, FloatExp(table1ColInt), "table1.col_int")
assertClauseSerialize(t, FloatExp(table1ColInt.ADD(table3ColInt)), "(table1.col_int + table3.col_int)")
assertClauseSerialize(t, FloatExp(table1ColInt.ADD(table3ColInt)).ADD(Float(11.11)),
"((table1.col_int + table3.col_int) + $1)", float64(11.11))
}

View File

@@ -0,0 +1,926 @@
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...)
}
// ------------------ Mathematical functions ---------------//
// ABSf calculates absolute value from float expression
func ABSf(floatExpression FloatExpression) FloatExpression {
return NewFloatFunc("ABS", floatExpression)
}
// ABSi calculates absolute value from int expression
func ABSi(integerExpression IntegerExpression) IntegerExpression {
return newIntegerFunc("ABS", integerExpression)
}
// POW calculates power of base with exponent
func POW(base, exponent NumericExpression) FloatExpression {
return NewFloatFunc("POW", base, exponent)
}
// POWER calculates power of base with exponent
func POWER(base, exponent NumericExpression) FloatExpression {
return NewFloatFunc("POWER", base, exponent)
}
// SQRT calculates square root of numeric expression
func SQRT(numericExpression NumericExpression) FloatExpression {
return NewFloatFunc("SQRT", numericExpression)
}
// CBRT calculates cube root of numeric expression
func CBRT(numericExpression NumericExpression) FloatExpression {
return NewFloatFunc("CBRT", numericExpression)
}
// CEIL calculates ceil of float expression
func CEIL(floatExpression FloatExpression) FloatExpression {
return NewFloatFunc("CEIL", floatExpression)
}
// FLOOR calculates floor of float expression
func FLOOR(floatExpression FloatExpression) FloatExpression {
return NewFloatFunc("FLOOR", floatExpression)
}
// ROUND calculates round of a float expressions with optional precision
func ROUND(floatExpression FloatExpression, precision ...IntegerExpression) FloatExpression {
if len(precision) > 0 {
return NewFloatFunc("ROUND", floatExpression, precision[0])
}
return NewFloatFunc("ROUND", floatExpression)
}
// SIGN returns sign of float expression
func SIGN(floatExpression FloatExpression) FloatExpression {
return NewFloatFunc("SIGN", floatExpression)
}
// TRUNC calculates trunc of float expression with optional precision
func TRUNC(floatExpression FloatExpression, precision ...IntegerExpression) FloatExpression {
if len(precision) > 0 {
return NewFloatFunc("TRUNC", floatExpression, precision[0])
}
return NewFloatFunc("TRUNC", floatExpression)
}
// LN calculates natural algorithm of float expression
func LN(floatExpression FloatExpression) FloatExpression {
return NewFloatFunc("LN", floatExpression)
}
// LOG calculates logarithm of float expression
func LOG(floatExpression FloatExpression) FloatExpression {
return NewFloatFunc("LOG", floatExpression)
}
// ----------------- Aggregate functions -------------------//
// AVG is aggregate function used to calculate avg value from numeric expression
func AVG(numericExpression Expression) floatWindowExpression {
return NewFloatWindowFunc("AVG", numericExpression)
}
// BIT_AND is aggregate function used to calculates the bitwise AND of all non-null input values, or null if none.
func BIT_AND(integerExpression IntegerExpression) integerWindowExpression {
return newIntegerWindowFunc("BIT_AND", integerExpression)
}
// BIT_OR is aggregate function used to calculates the bitwise OR of all non-null input values, or null if none.
func BIT_OR(integerExpression IntegerExpression) integerWindowExpression {
return newIntegerWindowFunc("BIT_OR", integerExpression)
}
// BOOL_AND is aggregate function. Returns true if all input values are true, otherwise false
func BOOL_AND(boolExpression BoolExpression) boolWindowExpression {
return newBoolWindowFunc("BOOL_AND", boolExpression)
}
// BOOL_OR is aggregate function. Returns true if at least one input value is true, otherwise false
func BOOL_OR(boolExpression BoolExpression) boolWindowExpression {
return newBoolWindowFunc("BOOL_OR", boolExpression)
}
// COUNT is aggregate function. Returns number of input rows for which the value of expression is not null.
func COUNT(expression Expression) integerWindowExpression {
return newIntegerWindowFunc("COUNT", expression)
}
// EVERY is aggregate function. Returns true if all input values are true, otherwise false
func EVERY(boolExpression BoolExpression) boolWindowExpression {
return newBoolWindowFunc("EVERY", boolExpression)
}
// MAX is aggregate function. Returns minimum value of expression across all input values.
func MAX(expression Expression) Expression {
return newWindowFunc("MAX", expression)
}
// MAXf is aggregate function. Returns maximum value of float expression across all input values
func MAXf(floatExpression FloatExpression) floatWindowExpression {
return NewFloatWindowFunc("MAX", floatExpression)
}
// MAXi is aggregate function. Returns maximum value of int expression across all input values
func MAXi(integerExpression IntegerExpression) integerWindowExpression {
return newIntegerWindowFunc("MAX", integerExpression)
}
// MIN is aggregate function. Returns minimum value of expression across all input values.
func MIN(expression Expression) Expression {
return newWindowFunc("MIN", expression)
}
// MINf is aggregate function. Returns minimum value of float expression across all input values
func MINf(floatExpression FloatExpression) floatWindowExpression {
return NewFloatWindowFunc("MIN", floatExpression)
}
// MINi is aggregate function. Returns minimum value of int expression across all input values
func MINi(integerExpression IntegerExpression) integerWindowExpression {
return newIntegerWindowFunc("MIN", integerExpression)
}
// SUM is aggregate function. Returns sum of all expressions
func SUM(expression Expression) Expression {
return newWindowFunc("SUM", expression)
}
// SUMf is aggregate function. Returns sum of expression across all float expressions
func SUMf(floatExpression FloatExpression) floatWindowExpression {
return NewFloatWindowFunc("SUM", floatExpression)
}
// SUMi is aggregate function. Returns sum of expression across all integer expression.
func SUMi(integerExpression IntegerExpression) integerWindowExpression {
return newIntegerWindowFunc("SUM", integerExpression)
}
// ----------------- Window functions -------------------//
// ROW_NUMBER returns number of the current row within its partition, counting from 1
func ROW_NUMBER() integerWindowExpression {
return newIntegerWindowFunc("ROW_NUMBER")
}
// RANK of the current row with gaps; same as row_number of its first peer
func RANK() integerWindowExpression {
return newIntegerWindowFunc("RANK")
}
// DENSE_RANK returns rank of the current row without gaps; this function counts peer groups
func DENSE_RANK() integerWindowExpression {
return newIntegerWindowFunc("DENSE_RANK")
}
// PERCENT_RANK calculates relative rank of the current row: (rank - 1) / (total partition rows - 1)
func PERCENT_RANK() floatWindowExpression {
return NewFloatWindowFunc("PERCENT_RANK")
}
// CUME_DIST calculates cumulative distribution: (number of partition rows preceding or peer with current row) / total partition rows
func CUME_DIST() floatWindowExpression {
return NewFloatWindowFunc("CUME_DIST")
}
// NTILE returns integer ranging from 1 to the argument value, dividing the partition as equally as possible
func NTILE(numOfBuckets int64) integerWindowExpression {
return newIntegerWindowFunc("NTILE", FixedLiteral(numOfBuckets))
}
// LAG returns value evaluated at the row that is offset rows before the current row within the partition;
// if there is no such row, instead return default (which must be of the same type as value).
// Both offset and default are evaluated with respect to the current row.
// If omitted, offset defaults to 1 and default to null
func LAG(expr Expression, offsetAndDefault ...interface{}) windowExpression {
return leadLagImpl("LAG", expr, offsetAndDefault...)
}
// LEAD returns value evaluated at the row that is offset rows after the current row within the partition;
// if there is no such row, instead return default (which must be of the same type as value).
// Both offset and default are evaluated with respect to the current row.
// If omitted, offset defaults to 1 and default to null
func LEAD(expr Expression, offsetAndDefault ...interface{}) windowExpression {
return leadLagImpl("LEAD", expr, offsetAndDefault...)
}
// FIRST_VALUE returns value evaluated at the row that is the first row of the window frame
func FIRST_VALUE(value Expression) windowExpression {
return newWindowFunc("FIRST_VALUE", value)
}
// LAST_VALUE returns value evaluated at the row that is the last row of the window frame
func LAST_VALUE(value Expression) windowExpression {
return newWindowFunc("LAST_VALUE", value)
}
// NTH_VALUE returns value evaluated at the row that is the nth row of the window frame (counting from 1); null if no such row
func NTH_VALUE(value Expression, nth int64) windowExpression {
return newWindowFunc("NTH_VALUE", value, FixedLiteral(nth))
}
func leadLagImpl(name string, expr Expression, offsetAndDefault ...interface{}) windowExpression {
params := []Expression{expr}
if len(offsetAndDefault) >= 2 {
offset, ok := offsetAndDefault[0].(int)
if !ok {
panic("jet: LAG offset should be an integer")
}
var defaultValue Expression
defaultValue, ok = offsetAndDefault[1].(Expression)
if !ok {
defaultValue = literal(offsetAndDefault[1])
}
params = append(params, FixedLiteral(offset), defaultValue)
}
return newWindowFunc(name, params...)
}
//------------ String functions ------------------//
// BIT_LENGTH returns number of bits in string expression
func BIT_LENGTH(stringExpression StringExpression) IntegerExpression {
return newIntegerFunc("BIT_LENGTH", stringExpression)
}
// CHAR_LENGTH returns number of characters in string expression
func CHAR_LENGTH(stringExpression StringExpression) IntegerExpression {
return newIntegerFunc("CHAR_LENGTH", stringExpression)
}
// OCTET_LENGTH returns number of bytes in string expression
func OCTET_LENGTH(stringExpression StringExpression) IntegerExpression {
return newIntegerFunc("OCTET_LENGTH", stringExpression)
}
// LOWER returns string expression in lower case
func LOWER(stringExpression StringExpression) StringExpression {
return NewStringFunc("LOWER", stringExpression)
}
// UPPER returns string expression in upper case
func UPPER(stringExpression StringExpression) StringExpression {
return NewStringFunc("UPPER", stringExpression)
}
// BTRIM removes the longest string consisting only of characters
// in characters (a space by default) from the start and end of string
func BTRIM(stringExpression StringExpression, trimChars ...StringExpression) StringExpression {
if len(trimChars) > 0 {
return NewStringFunc("BTRIM", stringExpression, trimChars[0])
}
return NewStringFunc("BTRIM", stringExpression)
}
// LTRIM removes the longest string containing only characters
// from characters (a space by default) from the start of string
func LTRIM(str StringExpression, trimChars ...StringExpression) StringExpression {
if len(trimChars) > 0 {
return NewStringFunc("LTRIM", str, trimChars[0])
}
return NewStringFunc("LTRIM", str)
}
// RTRIM removes the longest string containing only characters
// from characters (a space by default) from the end of string
func RTRIM(str StringExpression, trimChars ...StringExpression) StringExpression {
if len(trimChars) > 0 {
return NewStringFunc("RTRIM", str, trimChars[0])
}
return NewStringFunc("RTRIM", str)
}
// CHR returns character with the given code.
func CHR(integerExpression IntegerExpression) StringExpression {
return NewStringFunc("CHR", integerExpression)
}
// CONCAT adds two or more expressions together
func CONCAT(expressions ...Expression) StringExpression {
return NewStringFunc("CONCAT", expressions...)
}
// CONCAT_WS adds two or more expressions together with a separator.
func CONCAT_WS(separator Expression, expressions ...Expression) StringExpression {
return NewStringFunc("CONCAT_WS", append([]Expression{separator}, expressions...)...)
}
// CONVERT converts string to dest_encoding. The original encoding is
// specified by src_encoding. The string must be valid in this encoding.
func CONVERT(str StringExpression, srcEncoding StringExpression, destEncoding StringExpression) StringExpression {
return NewStringFunc("CONVERT", str, srcEncoding, destEncoding)
}
// CONVERT_FROM converts string to the database encoding. The original
// encoding is specified by src_encoding. The string must be valid in this encoding.
func CONVERT_FROM(str StringExpression, srcEncoding StringExpression) StringExpression {
return NewStringFunc("CONVERT_FROM", str, srcEncoding)
}
// CONVERT_TO converts string to dest_encoding.
func CONVERT_TO(str StringExpression, toEncoding StringExpression) StringExpression {
return NewStringFunc("CONVERT_TO", str, toEncoding)
}
// ENCODE encodes binary data into a textual representation.
// Supported formats are: base64, hex, escape. escape converts zero bytes and
// high-bit-set bytes to octal sequences (\nnn) and doubles backslashes.
func ENCODE(data StringExpression, format StringExpression) StringExpression {
return NewStringFunc("ENCODE", data, format)
}
// DECODE decodes binary data from textual representation in string.
// Options for format are same as in encode.
func DECODE(data StringExpression, format StringExpression) StringExpression {
return NewStringFunc("DECODE", data, format)
}
// FORMAT formats a number to a format like "#,###,###.##", rounded to a specified number of decimal places, then it returns the result as a string.
func FORMAT(formatStr StringExpression, formatArgs ...Expression) StringExpression {
args := []Expression{formatStr}
args = append(args, formatArgs...)
return NewStringFunc("FORMAT", args...)
}
// INITCAP converts the first letter of each word to upper case
// and the rest to lower case. Words are sequences of alphanumeric
// characters separated by non-alphanumeric characters.
func INITCAP(str StringExpression) StringExpression {
return NewStringFunc("INITCAP", str)
}
// LEFT returns first n characters in the string.
// When n is negative, return all but last |n| characters.
func LEFT(str StringExpression, n IntegerExpression) StringExpression {
return NewStringFunc("LEFT", str, n)
}
// RIGHT returns last n characters in the string.
// When n is negative, return all but first |n| characters.
func RIGHT(str StringExpression, n IntegerExpression) StringExpression {
return NewStringFunc("RIGHT", str, n)
}
// LENGTH returns number of characters in string with a given encoding
func LENGTH(str StringExpression, encoding ...StringExpression) StringExpression {
if len(encoding) > 0 {
return NewStringFunc("LENGTH", str, encoding[0])
}
return NewStringFunc("LENGTH", str)
}
// LPAD fills up the string to length length by prepending the characters
// fill (a space by default). If the string is already longer than length
// then it is truncated (on the right).
func LPAD(str StringExpression, length IntegerExpression, text ...StringExpression) StringExpression {
if len(text) > 0 {
return NewStringFunc("LPAD", str, length, text[0])
}
return NewStringFunc("LPAD", str, length)
}
// RPAD fills up the string to length length by appending the characters
// fill (a space by default). If the string is already longer than length then it is truncated.
func RPAD(str StringExpression, length IntegerExpression, text ...StringExpression) StringExpression {
if len(text) > 0 {
return NewStringFunc("RPAD", str, length, text[0])
}
return NewStringFunc("RPAD", str, length)
}
// MD5 calculates the MD5 hash of string, returning the result in hexadecimal
func MD5(stringExpression StringExpression) StringExpression {
return NewStringFunc("MD5", stringExpression)
}
// REPEAT repeats string the specified number of times
func REPEAT(str StringExpression, n IntegerExpression) StringExpression {
return NewStringFunc("REPEAT", str, n)
}
// REPLACE replaces all occurrences in string of substring from with substring to
func REPLACE(text, from, to StringExpression) StringExpression {
return NewStringFunc("REPLACE", text, from, to)
}
// REVERSE returns reversed string.
func REVERSE(stringExpression StringExpression) StringExpression {
return NewStringFunc("REVERSE", stringExpression)
}
// STRPOS returns location of specified substring (same as position(substring in string),
// but note the reversed argument order)
func STRPOS(str, substring StringExpression) IntegerExpression {
return newIntegerFunc("STRPOS", str, substring)
}
// SUBSTR extracts substring
func SUBSTR(str StringExpression, from IntegerExpression, count ...IntegerExpression) StringExpression {
if len(count) > 0 {
return NewStringFunc("SUBSTR", str, from, count[0])
}
return NewStringFunc("SUBSTR", str, from)
}
// TO_ASCII convert string to ASCII from another encoding
func TO_ASCII(str StringExpression, encoding ...StringExpression) StringExpression {
if len(encoding) > 0 {
return NewStringFunc("TO_ASCII", str, encoding[0])
}
return NewStringFunc("TO_ASCII", str)
}
// TO_HEX converts number to its equivalent hexadecimal representation
func TO_HEX(number IntegerExpression) StringExpression {
return NewStringFunc("TO_HEX", number)
}
// REGEXP_LIKE Returns 1 if the string expr matches the regular expression specified by the pattern pat, 0 otherwise.
func REGEXP_LIKE(stringExp StringExpression, pattern StringExpression, matchType ...string) BoolExpression {
if len(matchType) > 0 {
return newBoolFunc("REGEXP_LIKE", stringExp, pattern, FixedLiteral(matchType[0]))
}
return newBoolFunc("REGEXP_LIKE", stringExp, pattern)
}
//----------Range Type Functions ----------------------//
// 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 {
return rangeTypeCaster[T](rangeExpression, NewFunc("LOWER", []Expression{rangeExpression}, nil))
}
// 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 {
return rangeTypeCaster[T](rangeExpression, NewFunc("UPPER", []Expression{rangeExpression}, nil))
}
func rangeTypeCaster[T Expression](rangeExpression Range[T], exp Expression) T {
var i Expression
switch rangeExpression.(type) {
case Range[Int4Expression], Range[Int8Expression]:
i = IntExp(exp)
case Range[NumericExpression]:
i = FloatExp(exp)
case Range[DateExpression]:
i = DateExp(exp)
case Range[TimestampExpression]:
i = TimestampExp(exp)
case Range[TimestampzExpression]:
i = TimestampzExp(exp)
}
return i.(T)
}
// IS_EMPTY returns true if range is empty
func IS_EMPTY[T Expression](rangeExpression Range[T]) BoolExpression {
return newBoolFunc("ISEMPTY", rangeExpression)
}
// LOWER_INC returns true if lower bound is inclusive. Returns false for empty range.
func LOWER_INC[T Expression](rangeExpression Range[T]) BoolExpression {
return newBoolFunc("LOWER_INC", rangeExpression)
}
// UPPER_INC returns true if upper bound is inclusive. Returns false for empty range.
func UPPER_INC[T Expression](rangeExpression Range[T]) BoolExpression {
return newBoolFunc("UPPER_INC", rangeExpression)
}
// LOWER_INF returns true if upper bound is infinite. Returns false for empty range.
func LOWER_INF[T Expression](rangeExpression Range[T]) BoolExpression {
return newBoolFunc("LOWER_INF", rangeExpression)
}
// UPPER_INF returns true if lower bound is infinite. Returns false for empty range.
func UPPER_INF[T Expression](rangeExpression Range[T]) BoolExpression {
return newBoolFunc("UPPER_INF", rangeExpression)
}
//----------Data Type Formatting Functions ----------------------//
// TO_CHAR converts expression to string with format
func TO_CHAR(expression Expression, format StringExpression) StringExpression {
return NewStringFunc("TO_CHAR", expression, format)
}
// TO_DATE converts string to date using format
func TO_DATE(dateStr, format StringExpression) DateExpression {
return NewDateFunc("TO_DATE", dateStr, format)
}
// TO_NUMBER converts string to numeric using format
func TO_NUMBER(floatStr, format StringExpression) FloatExpression {
return NewFloatFunc("TO_NUMBER", floatStr, format)
}
// TO_TIMESTAMP converts string to time stamp with time zone using format
func TO_TIMESTAMP(timestampzStr, format StringExpression) TimestampzExpression {
return newTimestampzFunc("TO_TIMESTAMP", timestampzStr, format)
}
//----------------- Date/Time Functions and Operators ---------------//
// EXTRACT extracts time component from time expression
func EXTRACT(field string, from Expression) Expression {
return CustomExpression(Token("EXTRACT("), Token(field), Token("FROM"), from, Token(")"))
}
// CURRENT_DATE returns current date
func CURRENT_DATE() DateExpression {
dateFunc := NewDateFunc("CURRENT_DATE")
dateFunc.noBrackets = true
return dateFunc
}
// CURRENT_TIME returns current time with time zone
func CURRENT_TIME(precision ...int) TimezExpression {
var timezFunc *timezFunc
if len(precision) > 0 {
timezFunc = newTimezFunc("CURRENT_TIME", FixedLiteral(precision[0]))
} else {
timezFunc = newTimezFunc("CURRENT_TIME")
}
timezFunc.noBrackets = true
return timezFunc
}
// CURRENT_TIMESTAMP returns current timestamp with time zone
func CURRENT_TIMESTAMP(precision ...int) TimestampzExpression {
var timestampzFunc *timestampzFunc
if len(precision) > 0 {
timestampzFunc = newTimestampzFunc("CURRENT_TIMESTAMP", FixedLiteral(precision[0]))
} else {
timestampzFunc = newTimestampzFunc("CURRENT_TIMESTAMP")
}
timestampzFunc.noBrackets = true
return timestampzFunc
}
// LOCALTIME returns local time of day using optional precision
func LOCALTIME(precision ...int) TimeExpression {
var timeFunc *timeFunc
if len(precision) > 0 {
timeFunc = NewTimeFunc("LOCALTIME", FixedLiteral(precision[0]))
} else {
timeFunc = NewTimeFunc("LOCALTIME")
}
timeFunc.noBrackets = true
return timeFunc
}
// LOCALTIMESTAMP returns current date and time using optional precision
func LOCALTIMESTAMP(precision ...int) TimestampExpression {
var timestampFunc *timestampFunc
if len(precision) > 0 {
timestampFunc = NewTimestampFunc("LOCALTIMESTAMP", FixedLiteral(precision[0]))
} else {
timestampFunc = NewTimestampFunc("LOCALTIMESTAMP")
}
timestampFunc.noBrackets = true
return timestampFunc
}
// NOW returns current date and time
func NOW() TimestampzExpression {
return newTimestampzFunc("NOW")
}
// --------------- Conditional Expressions Functions -------------//
// COALESCE function returns the first of its arguments that is not null.
func COALESCE(value Expression, values ...Expression) Expression {
var allValues = []Expression{value}
allValues = append(allValues, values...)
return NewFunc("COALESCE", allValues, nil)
}
// NULLIF function returns a null value if value1 equals value2; otherwise it returns value1.
func NULLIF(value1, value2 Expression) Expression {
return NewFunc("NULLIF", []Expression{value1, value2}, nil)
}
// GREATEST selects the largest value from a list of expressions
func GREATEST(value Expression, values ...Expression) Expression {
var allValues = []Expression{value}
allValues = append(allValues, values...)
return NewFunc("GREATEST", allValues, nil)
}
// LEAST selects the smallest value from a list of expressions
func LEAST(value Expression, values ...Expression) Expression {
var allValues = []Expression{value}
allValues = append(allValues, values...)
return NewFunc("LEAST", allValues, nil)
}
//--------------------------------------------------------------------//
type funcExpressionImpl struct {
ExpressionInterfaceImpl
name string
parameters parametersSerializer
noBrackets bool
}
// NewFunc creates new function with name and expressions parameters
func NewFunc(name string, expressions []Expression, parent Expression) *funcExpressionImpl {
funcExp := &funcExpressionImpl{
name: name,
parameters: parametersSerializer(expressions),
}
if parent != nil {
funcExp.ExpressionInterfaceImpl.Parent = parent
} else {
funcExp.ExpressionInterfaceImpl.Parent = 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 + "(")
} else {
out.WriteString(f.name)
}
f.parameters.serialize(statement, out, options...)
if addBrackets {
out.WriteString(")")
}
}
type parametersSerializer []Expression
func (p parametersSerializer) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) {
for i, expression := range p {
if i > 0 {
out.WriteString(", ")
}
if _, isStatement := expression.(Statement); isStatement {
expression.serialize(statement, out, options...)
} else {
expression.serialize(statement, out, append(options, NoWrap, Ident)...)
}
}
}
// NewFloatWindowFunc creates new float function with name and expressions
func newWindowFunc(name string, expressions ...Expression) windowExpression {
newFun := NewFunc(name, expressions, nil)
windowExpr := newWindowExpression(newFun)
newFun.ExpressionInterfaceImpl.Parent = 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.parent = boolFunc
boolFunc.ExpressionInterfaceImpl.Parent = boolFunc
return boolFunc
}
// NewFloatWindowFunc creates new float function with name and expressions
func newBoolWindowFunc(name string, expressions ...Expression) boolWindowExpression {
boolFunc := &boolFunc{}
boolFunc.funcExpressionImpl = *NewFunc(name, expressions, boolFunc)
intWindowFunc := newBoolWindowExpression(boolFunc)
boolFunc.boolInterfaceImpl.parent = intWindowFunc
boolFunc.ExpressionInterfaceImpl.Parent = intWindowFunc
return intWindowFunc
}
type floatFunc struct {
funcExpressionImpl
floatInterfaceImpl
}
// NewFloatFunc creates new float function with name and expressions
func NewFloatFunc(name string, expressions ...Expression) FloatExpression {
floatFunc := &floatFunc{}
floatFunc.funcExpressionImpl = *NewFunc(name, expressions, floatFunc)
floatFunc.floatInterfaceImpl.parent = floatFunc
return floatFunc
}
// NewFloatWindowFunc creates new float function with name and expressions
func NewFloatWindowFunc(name string, expressions ...Expression) floatWindowExpression {
floatFunc := &floatFunc{}
floatFunc.funcExpressionImpl = *NewFunc(name, expressions, floatFunc)
floatWindowFunc := newFloatWindowExpression(floatFunc)
floatFunc.floatInterfaceImpl.parent = floatWindowFunc
floatFunc.ExpressionInterfaceImpl.Parent = floatWindowFunc
return floatWindowFunc
}
type integerFunc struct {
funcExpressionImpl
integerInterfaceImpl
}
func newIntegerFunc(name string, expressions ...Expression) IntegerExpression {
intFunc := &integerFunc{}
intFunc.funcExpressionImpl = *NewFunc(name, expressions, intFunc)
intFunc.integerInterfaceImpl.parent = intFunc
return intFunc
}
// NewFloatWindowFunc creates new float function with name and expressions
func newIntegerWindowFunc(name string, expressions ...Expression) integerWindowExpression {
integerFunc := &integerFunc{}
integerFunc.funcExpressionImpl = *NewFunc(name, expressions, integerFunc)
intWindowFunc := newIntegerWindowExpression(integerFunc)
integerFunc.integerInterfaceImpl.parent = intWindowFunc
integerFunc.ExpressionInterfaceImpl.Parent = intWindowFunc
return intWindowFunc
}
type stringFunc struct {
funcExpressionImpl
stringInterfaceImpl
}
// NewStringFunc creates new string function with name and expression parameters
func NewStringFunc(name string, expressions ...Expression) StringExpression {
stringFunc := &stringFunc{}
stringFunc.funcExpressionImpl = *NewFunc(name, expressions, stringFunc)
stringFunc.stringInterfaceImpl.parent = 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.parent = dateFunc
return dateFunc
}
type timeFunc struct {
funcExpressionImpl
timeInterfaceImpl
}
// NewTimeFunc creates new time function with name and expression parameters
func NewTimeFunc(name string, expressions ...Expression) *timeFunc {
timeFun := &timeFunc{}
timeFun.funcExpressionImpl = *NewFunc(name, expressions, timeFun)
timeFun.timeInterfaceImpl.parent = timeFun
return timeFun
}
type timezFunc struct {
funcExpressionImpl
timezInterfaceImpl
}
func newTimezFunc(name string, expressions ...Expression) *timezFunc {
timezFun := &timezFunc{}
timezFun.funcExpressionImpl = *NewFunc(name, expressions, timezFun)
timezFun.timezInterfaceImpl.parent = timezFun
return timezFun
}
type timestampFunc struct {
funcExpressionImpl
timestampInterfaceImpl
}
// NewTimestampFunc creates new timestamp function with name and expressions
func NewTimestampFunc(name string, expressions ...Expression) *timestampFunc {
timestampFunc := &timestampFunc{}
timestampFunc.funcExpressionImpl = *NewFunc(name, expressions, timestampFunc)
timestampFunc.timestampInterfaceImpl.parent = timestampFunc
return timestampFunc
}
type timestampzFunc struct {
funcExpressionImpl
timestampzInterfaceImpl
}
func newTimestampzFunc(name string, expressions ...Expression) *timestampzFunc {
timestampzFunc := &timestampzFunc{}
timestampzFunc.funcExpressionImpl = *NewFunc(name, expressions, timestampzFunc)
timestampzFunc.timestampzInterfaceImpl.parent = timestampzFunc
return timestampzFunc
}
// Func can be used to call custom or unsupported database functions.
func Func(name string, expressions ...Expression) Expression {
return NewFunc(name, expressions, nil)
}
func NumRange(lowNum, highNum NumericExpression, bounds ...StringExpression) Range[NumericExpression] {
return NumRangeExp(NewFunc("numrange", rangeFuncParamCombiner(lowNum, highNum, bounds...), nil))
}
func Int4Range(lowNum, highNum IntegerExpression, bounds ...StringExpression) Range[Int4Expression] {
return Int4RangeExp(NewFunc("int4range", rangeFuncParamCombiner(lowNum, highNum, bounds...), nil))
}
func Int8Range(lowNum, highNum Int8Expression, bounds ...StringExpression) Range[Int8Expression] {
return Int8RangeExp(NewFunc("int8range", rangeFuncParamCombiner(lowNum, highNum, bounds...), nil))
}
func TsRange(lowTs, highTs TimestampExpression, bounds ...StringExpression) Range[TimestampExpression] {
return TsRangeExp(NewFunc("tsrange", rangeFuncParamCombiner(lowTs, highTs, bounds...), nil))
}
func TstzRange(lowTs, highTs TimestampzExpression, bounds ...StringExpression) Range[TimestampzExpression] {
return TstzRangeExp(NewFunc("tstzrange", rangeFuncParamCombiner(lowTs, highTs, bounds...), nil))
}
func DateRange(lowTs, highTs DateExpression, bounds ...StringExpression) Range[DateExpression] {
return DateRangeExp(NewFunc("daterange", rangeFuncParamCombiner(lowTs, highTs, bounds...), nil))
}
func rangeFuncParamCombiner(low, high Expression, bounds ...StringExpression) []Expression {
exp := []Expression{low, high}
if len(bounds) != 0 {
exp = append(exp, bounds[0])
}
return exp
}

View File

@@ -0,0 +1,215 @@
package jet
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)")
}
func TestFuncBIT_AND(t *testing.T) {
assertClauseSerialize(t, BIT_AND(table1ColInt), "BIT_AND(table1.col_int)")
}
func TestFuncBIT_OR(t *testing.T) {
assertClauseSerialize(t, BIT_OR(table1ColInt), "BIT_OR(table1.col_int)")
}
func TestFuncBOOL_AND(t *testing.T) {
assertClauseSerialize(t, BOOL_AND(table1ColBool), "BOOL_AND(table1.col_bool)")
}
func TestFuncBOOL_OR(t *testing.T) {
assertClauseSerialize(t, BOOL_OR(table1ColBool), "BOOL_OR(table1.col_bool)")
}
func TestFuncEVERY(t *testing.T) {
assertClauseSerialize(t, EVERY(table1ColBool), "EVERY(table1.col_bool)")
}
func TestFuncMIN(t *testing.T) {
t.Run("expression", func(t *testing.T) {
assertClauseSerialize(t, MIN(table1ColDate), "MIN(table1.col_date)")
assertClauseSerialize(t, MIN(Date(2001, 1, 1)), "MIN($1)", "2001-01-01")
assertClauseSerialize(t, MIN(Time(12, 10, 10)), "MIN($1)", "12:10:10")
assertClauseSerialize(t, MIN(Timestamp(2001, 1, 1, 12, 10, 10)), "MIN($1)", "2001-01-01 12:10:10")
assertClauseSerialize(t, MIN(Timestampz(2001, 1, 1, 12, 10, 10, 1, "UTC")), "MIN($1)", "2001-01-01 12:10:10.000000001 UTC")
})
t.Run("float", func(t *testing.T) {
assertClauseSerialize(t, MINf(table1ColFloat), "MIN(table1.col_float)")
})
t.Run("integer", func(t *testing.T) {
assertClauseSerialize(t, MINi(table1ColInt), "MIN(table1.col_int)")
})
}
func TestFuncMAX(t *testing.T) {
t.Run("expression", func(t *testing.T) {
assertClauseSerialize(t, MAX(table1ColDate), "MAX(table1.col_date)")
assertClauseSerialize(t, MAX(Date(2001, 1, 1)), "MAX($1)", "2001-01-01")
assertClauseSerialize(t, MAX(Time(12, 10, 10)), "MAX($1)", "12:10:10")
assertClauseSerialize(t, MAX(Timestamp(2001, 1, 1, 12, 10, 10)), "MAX($1)", "2001-01-01 12:10:10")
assertClauseSerialize(t, MAX(Timestampz(2001, 1, 1, 12, 10, 10, 1, "UTC")), "MAX($1)", "2001-01-01 12:10:10.000000001 UTC")
})
t.Run("float", func(t *testing.T) {
assertClauseSerialize(t, MAXf(table1ColFloat), "MAX(table1.col_float)")
assertClauseSerialize(t, MAXf(Float(11.2222)), "MAX($1)", float64(11.2222))
})
t.Run("integer", func(t *testing.T) {
assertClauseSerialize(t, MAXi(table1ColInt), "MAX(table1.col_int)")
assertClauseSerialize(t, MAXi(Int(11)), "MAX($1)", int64(11))
})
}
func TestFuncSUM(t *testing.T) {
t.Run("float", func(t *testing.T) {
assertClauseSerialize(t, SUMf(table1ColFloat), "SUM(table1.col_float)")
assertClauseSerialize(t, SUMf(Float(11.2222)), "SUM($1)", float64(11.2222))
})
t.Run("integer", func(t *testing.T) {
assertClauseSerialize(t, SUMi(table1ColInt), "SUM(table1.col_int)")
assertClauseSerialize(t, SUMi(Int(11)), "SUM($1)", int64(11))
})
}
func TestFuncCOUNT(t *testing.T) {
assertClauseSerialize(t, COUNT(STAR), "COUNT(*)")
assertClauseSerialize(t, COUNT(table1ColFloat), "COUNT(table1.col_float)")
assertClauseSerialize(t, COUNT(Float(11.2222)), "COUNT($1)", float64(11.2222))
}
func TestFuncABS(t *testing.T) {
t.Run("float", func(t *testing.T) {
assertClauseSerialize(t, ABSf(table1ColFloat), "ABS(table1.col_float)")
assertClauseSerialize(t, ABSf(Float(11.2222)), "ABS($1)", float64(11.2222))
})
t.Run("integer", func(t *testing.T) {
assertClauseSerialize(t, ABSi(table1ColInt), "ABS(table1.col_int)")
assertClauseSerialize(t, ABSi(Int(11)), "ABS($1)", int64(11))
})
}
func TestFuncSQRT(t *testing.T) {
assertClauseSerialize(t, SQRT(table1ColFloat), "SQRT(table1.col_float)")
assertClauseSerialize(t, SQRT(Float(11.2222)), "SQRT($1)", float64(11.2222))
assertClauseSerialize(t, SQRT(table1ColInt), "SQRT(table1.col_int)")
assertClauseSerialize(t, SQRT(Int(11)), "SQRT($1)", int64(11))
}
func TestFuncCBRT(t *testing.T) {
assertClauseSerialize(t, CBRT(table1ColFloat), "CBRT(table1.col_float)")
assertClauseSerialize(t, CBRT(Float(11.2222)), "CBRT($1)", float64(11.2222))
assertClauseSerialize(t, CBRT(table1ColInt), "CBRT(table1.col_int)")
assertClauseSerialize(t, CBRT(Int(11)), "CBRT($1)", int64(11))
}
func TestFuncCEIL(t *testing.T) {
assertClauseSerialize(t, CEIL(table1ColFloat), "CEIL(table1.col_float)")
assertClauseSerialize(t, CEIL(Float(11.2222)), "CEIL($1)", float64(11.2222))
}
func TestFuncFLOOR(t *testing.T) {
assertClauseSerialize(t, FLOOR(table1ColFloat), "FLOOR(table1.col_float)")
assertClauseSerialize(t, FLOOR(Float(11.2222)), "FLOOR($1)", float64(11.2222))
}
func TestFuncROUND(t *testing.T) {
assertClauseSerialize(t, ROUND(table1ColFloat), "ROUND(table1.col_float)")
assertClauseSerialize(t, ROUND(Float(11.2222)), "ROUND($1)", float64(11.2222))
assertClauseSerialize(t, ROUND(table1ColFloat, Int(2)), "ROUND(table1.col_float, $1)", int64(2))
assertClauseSerialize(t, ROUND(Float(11.2222), Int(1)), "ROUND($1, $2)", float64(11.2222), int64(1))
}
func TestFuncSIGN(t *testing.T) {
assertClauseSerialize(t, SIGN(table1ColFloat), "SIGN(table1.col_float)")
assertClauseSerialize(t, SIGN(Float(11.2222)), "SIGN($1)", float64(11.2222))
}
func TestFuncTRUNC(t *testing.T) {
assertClauseSerialize(t, TRUNC(table1ColFloat), "TRUNC(table1.col_float)")
assertClauseSerialize(t, TRUNC(Float(11.2222)), "TRUNC($1)", float64(11.2222))
assertClauseSerialize(t, TRUNC(table1ColFloat, Int(2)), "TRUNC(table1.col_float, $1)", int64(2))
assertClauseSerialize(t, TRUNC(Float(11.2222), Int(1)), "TRUNC($1, $2)", float64(11.2222), int64(1))
}
func TestFuncLN(t *testing.T) {
assertClauseSerialize(t, LN(table1ColFloat), "LN(table1.col_float)")
assertClauseSerialize(t, LN(Float(11.2222)), "LN($1)", float64(11.2222))
}
func TestFuncLOG(t *testing.T) {
assertClauseSerialize(t, LOG(table1ColFloat), "LOG(table1.col_float)")
assertClauseSerialize(t, LOG(Float(11.2222)), "LOG($1)", float64(11.2222))
}
func TestFuncCOALESCE(t *testing.T) {
assertClauseSerialize(t, COALESCE(table1ColFloat), "COALESCE(table1.col_float)")
assertClauseSerialize(t, COALESCE(Float(11.2222), NULL, String("str")), "COALESCE($1, NULL, $2)", float64(11.2222), "str")
}
func TestFuncNULLIF(t *testing.T) {
assertClauseSerialize(t, NULLIF(table1ColFloat, table2ColInt), "NULLIF(table1.col_float, table2.col_int)")
assertClauseSerialize(t, NULLIF(Float(11.2222), NULL), "NULLIF($1, NULL)", float64(11.2222))
}
func TestFuncGREATEST(t *testing.T) {
assertClauseSerialize(t, GREATEST(table1ColFloat), "GREATEST(table1.col_float)")
assertClauseSerialize(t, GREATEST(Float(11.2222), NULL, String("str")), "GREATEST($1, NULL, $2)", float64(11.2222), "str")
}
func TestFuncLEAST(t *testing.T) {
assertClauseSerialize(t, LEAST(table1ColFloat), "LEAST(table1.col_float)")
assertClauseSerialize(t, LEAST(Float(11.2222), NULL, String("str")), "LEAST($1, NULL, $2)", float64(11.2222), "str")
}
func TestTO_ASCII(t *testing.T) {
assertClauseSerialize(t, TO_ASCII(String("Karel")), `TO_ASCII($1)`, "Karel")
assertClauseSerialize(t, TO_ASCII(String("Karel")), `TO_ASCII($1)`, "Karel")
}
func TestFunc(t *testing.T) {
assertClauseSerialize(t, Func("FOO", String("test"), NULL, MAX(Int(1))), "FOO($1, NULL, MAX($2))", "test", int64(1))
}
func Test_rangePointCaster(t *testing.T) {
mainRange := Int8Range(Int8(10), Int8(12))
exp := NewFunc("UPPER", []Expression{mainRange}, nil)
got := rangeTypeCaster(mainRange, exp)
_, ok := got.(IntegerExpression)
if !ok {
t.Errorf("expecting to get IntegerExpression but got %v", got)
}
}

View File

@@ -0,0 +1,41 @@
package jet
// GroupByClause interface
type GroupByClause interface {
serializeForGroupBy(statement StatementType, out *SQLBuilder)
}
// GROUPING_SETS operator allows grouping of the rows in a table by multiple sets of columns in a single query.
// This can be useful when we want to analyze data by different combinations of columns, without having to write separate
// queries for each combination.
func GROUPING_SETS(expressions ...Expression) GroupByClause {
return Func("GROUPING SETS", expressions...)
}
// ROLLUP operator is used with the GROUP BY clause to generate all prefixes of a group of columns including the empty list.
// It creates extra rows in the result set that represent the subtotal values for each combination of columns.
func ROLLUP(expressions ...Expression) GroupByClause {
return Func("ROLLUP", expressions...)
}
// CUBE operator is used with the GROUP BY clause to generate subtotals for all possible combinations of a group of columns.
// It creates extra rows in the result set that represent the subtotal values for each combination of columns.
func CUBE(expressions ...Expression) GroupByClause {
return Func("CUBE", expressions...)
}
// GROUPING function is used to identify which columns are included in a grouping set or a subtotal row. It takes as input
// the name of a column and returns 1 if the column is not included in the current grouping set, and 0 otherwise.
// It can be also used with multiple parameters to check if a set of columns is included in the current grouping set. The result
// of the GROUPING function would then be an integer bit mask having 1s for the arguments which have GROUPING(argument) as 1.
func GROUPING(expressions ...Expression) IntegerExpression {
return IntExp(Func("GROUPING", expressions...))
}
// WITH_ROLLUP operator is used with the GROUP BY clause to generate all prefixes of a group of columns including the empty list.
// It creates extra rows in the result set that represent the subtotal values for each combination of columns.
func WITH_ROLLUP(expressions ...Expression) GroupByClause {
return CustomExpression(
parametersSerializer(expressions), Token("WITH ROLLUP"),
)
}

View File

@@ -0,0 +1,156 @@
package jet
// IntegerExpression interface
type IntegerExpression interface {
Expression
numericExpression
EQ(rhs IntegerExpression) BoolExpression
NOT_EQ(rhs IntegerExpression) BoolExpression
IS_DISTINCT_FROM(rhs IntegerExpression) BoolExpression
IS_NOT_DISTINCT_FROM(rhs IntegerExpression) BoolExpression
LT(rhs IntegerExpression) BoolExpression
LT_EQ(rhs IntegerExpression) BoolExpression
GT(rhs IntegerExpression) BoolExpression
GT_EQ(rhs IntegerExpression) BoolExpression
BETWEEN(min, max IntegerExpression) BoolExpression
NOT_BETWEEN(min, max IntegerExpression) BoolExpression
ADD(rhs IntegerExpression) IntegerExpression
SUB(rhs IntegerExpression) IntegerExpression
MUL(rhs IntegerExpression) IntegerExpression
DIV(rhs IntegerExpression) IntegerExpression
MOD(rhs IntegerExpression) IntegerExpression
POW(rhs IntegerExpression) IntegerExpression
BIT_AND(rhs IntegerExpression) IntegerExpression
BIT_OR(rhs IntegerExpression) IntegerExpression
BIT_XOR(rhs IntegerExpression) IntegerExpression
BIT_SHIFT_LEFT(shift IntegerExpression) IntegerExpression
BIT_SHIFT_RIGHT(shift IntegerExpression) IntegerExpression
}
// additional integer expression subtypes, used in range expressions.
type (
Int4Expression IntegerExpression
Int8Expression IntegerExpression
)
type integerInterfaceImpl struct {
numericExpressionImpl
parent IntegerExpression
}
func (i *integerInterfaceImpl) EQ(rhs IntegerExpression) BoolExpression {
return Eq(i.parent, rhs)
}
func (i *integerInterfaceImpl) NOT_EQ(rhs IntegerExpression) BoolExpression {
return NotEq(i.parent, rhs)
}
func (i *integerInterfaceImpl) IS_DISTINCT_FROM(rhs IntegerExpression) BoolExpression {
return IsDistinctFrom(i.parent, rhs)
}
func (i *integerInterfaceImpl) IS_NOT_DISTINCT_FROM(rhs IntegerExpression) BoolExpression {
return IsNotDistinctFrom(i.parent, rhs)
}
func (i *integerInterfaceImpl) GT(rhs IntegerExpression) BoolExpression {
return Gt(i.parent, rhs)
}
func (i *integerInterfaceImpl) GT_EQ(rhs IntegerExpression) BoolExpression {
return GtEq(i.parent, rhs)
}
func (i *integerInterfaceImpl) LT(rhs IntegerExpression) BoolExpression {
return Lt(i.parent, rhs)
}
func (i *integerInterfaceImpl) LT_EQ(rhs IntegerExpression) BoolExpression {
return LtEq(i.parent, rhs)
}
func (i *integerInterfaceImpl) BETWEEN(min, max IntegerExpression) BoolExpression {
return NewBetweenOperatorExpression(i.parent, min, max, false)
}
func (i *integerInterfaceImpl) NOT_BETWEEN(min, max IntegerExpression) BoolExpression {
return NewBetweenOperatorExpression(i.parent, min, max, true)
}
func (i *integerInterfaceImpl) ADD(rhs IntegerExpression) IntegerExpression {
return IntExp(Add(i.parent, rhs))
}
func (i *integerInterfaceImpl) SUB(rhs IntegerExpression) IntegerExpression {
return IntExp(Sub(i.parent, rhs))
}
func (i *integerInterfaceImpl) MUL(rhs IntegerExpression) IntegerExpression {
return IntExp(Mul(i.parent, rhs))
}
func (i *integerInterfaceImpl) DIV(rhs IntegerExpression) IntegerExpression {
return IntExp(Div(i.parent, rhs))
}
func (i *integerInterfaceImpl) MOD(rhs IntegerExpression) IntegerExpression {
return IntExp(Mod(i.parent, rhs))
}
func (i *integerInterfaceImpl) POW(rhs IntegerExpression) IntegerExpression {
return IntExp(POW(i.parent, rhs))
}
func (i *integerInterfaceImpl) BIT_AND(rhs IntegerExpression) IntegerExpression {
return newBinaryIntegerOperatorExpression(i.parent, rhs, "&")
}
func (i *integerInterfaceImpl) BIT_OR(rhs IntegerExpression) IntegerExpression {
return newBinaryIntegerOperatorExpression(i.parent, rhs, "|")
}
func (i *integerInterfaceImpl) BIT_XOR(rhs IntegerExpression) IntegerExpression {
return newBinaryIntegerOperatorExpression(i.parent, rhs, "#")
}
func (i *integerInterfaceImpl) BIT_SHIFT_LEFT(intExpression IntegerExpression) IntegerExpression {
return newBinaryIntegerOperatorExpression(i.parent, intExpression, "<<")
}
func (i *integerInterfaceImpl) BIT_SHIFT_RIGHT(intExpression IntegerExpression) IntegerExpression {
return newBinaryIntegerOperatorExpression(i.parent, intExpression, ">>")
}
func newBinaryIntegerOperatorExpression(lhs, rhs IntegerExpression, operator string) IntegerExpression {
return IntExp(NewBinaryOperatorExpression(lhs, rhs, operator))
}
func newPrefixIntegerOperatorExpression(expression IntegerExpression, operator string) IntegerExpression {
return IntExp(newPrefixOperatorExpression(expression, operator))
}
type integerExpressionWrapper struct {
integerInterfaceImpl
Expression
}
func newIntExpressionWrap(expression Expression) IntegerExpression {
intExpressionWrap := integerExpressionWrapper{Expression: expression}
intExpressionWrap.integerInterfaceImpl.parent = &intExpressionWrap
return &intExpressionWrap
}
// IntExp is int expression wrapper around arbitrary expression.
// Allows go compiler to see any expression as int expression.
// Does not add sql cast to generated sql builder output.
func IntExp(expression Expression) IntegerExpression {
return newIntExpressionWrap(expression)
}

View File

@@ -0,0 +1,107 @@
package jet
import (
"testing"
)
func TestIntegerExpressionEQ(t *testing.T) {
assertClauseSerialize(t, table1ColInt.EQ(table2ColInt), "(table1.col_int = table2.col_int)")
assertClauseSerialize(t, table1ColInt.EQ(Int(11)), "(table1.col_int = $1)", int64(11))
}
func TestIntegerExpressionNOT_EQ(t *testing.T) {
assertClauseSerialize(t, table1ColInt.NOT_EQ(table2ColInt), "(table1.col_int != table2.col_int)")
assertClauseSerialize(t, table1ColInt.NOT_EQ(Int(11)), "(table1.col_int != $1)", int64(11))
}
func TestIntegerExpressionGT(t *testing.T) {
assertClauseSerialize(t, table1ColInt.GT(table2ColInt), "(table1.col_int > table2.col_int)")
assertClauseSerialize(t, table1ColInt.GT(Int(11)), "(table1.col_int > $1)", int64(11))
}
func TestIntegerExpressionGT_EQ(t *testing.T) {
assertClauseSerialize(t, table1ColInt.GT_EQ(table2ColInt), "(table1.col_int >= table2.col_int)")
assertClauseSerialize(t, table1ColInt.GT_EQ(Int(11)), "(table1.col_int >= $1)", int64(11))
}
func TestIntegerExpressionLT(t *testing.T) {
assertClauseSerialize(t, table1ColInt.LT(table2ColInt), "(table1.col_int < table2.col_int)")
assertClauseSerialize(t, table1ColInt.LT(Int(11)), "(table1.col_int < $1)", int64(11))
}
func TestIntegerExpressionLT_EQ(t *testing.T) {
assertClauseSerialize(t, table1ColInt.LT_EQ(table2ColInt), "(table1.col_int <= table2.col_int)")
assertClauseSerialize(t, table1ColInt.LT_EQ(Int(11)), "(table1.col_int <= $1)", int64(11))
}
func TestIntegerExpressionADD(t *testing.T) {
assertClauseSerialize(t, table1ColInt.ADD(table2ColInt), "(table1.col_int + table2.col_int)")
assertClauseSerialize(t, table1ColInt.ADD(Int(11)), "(table1.col_int + $1)", int64(11))
}
func TestIntegerExpressionSUB(t *testing.T) {
assertClauseSerialize(t, table1ColInt.SUB(table2ColInt), "(table1.col_int - table2.col_int)")
assertClauseSerialize(t, table1ColInt.SUB(Int(11)), "(table1.col_int - $1)", int64(11))
}
func TestIntegerExpressionMUL(t *testing.T) {
assertClauseSerialize(t, table1ColInt.MUL(table2ColInt), "(table1.col_int * table2.col_int)")
assertClauseSerialize(t, table1ColInt.MUL(Int(11)), "(table1.col_int * $1)", int64(11))
}
func TestIntegerExpressionDIV(t *testing.T) {
assertClauseSerialize(t, table1ColInt.DIV(table2ColInt), "(table1.col_int / table2.col_int)")
assertClauseSerialize(t, table1ColInt.DIV(Int(11)), "(table1.col_int / $1)", int64(11))
}
func TestIntExpressionMOD(t *testing.T) {
assertClauseSerialize(t, table1ColInt.MOD(table2ColInt), "(table1.col_int % table2.col_int)")
assertClauseSerialize(t, table1ColInt.MOD(Int(11)), "(table1.col_int % $1)", int64(11))
}
func TestIntExpressionPOW(t *testing.T) {
assertClauseSerialize(t, table1ColInt.POW(table2ColInt), "POW(table1.col_int, table2.col_int)")
assertClauseSerialize(t, table1ColInt.POW(Int(11)), "POW(table1.col_int, $1)", int64(11))
}
func TestIntExpressionBIT_NOT(t *testing.T) {
assertClauseSerialize(t, BIT_NOT(table2ColInt), "(~ table2.col_int)")
assertClauseSerialize(t, BIT_NOT(Int(11)), "(~ 11)")
}
func TestIntExpressionBIT_AND(t *testing.T) {
assertClauseSerialize(t, table1ColInt.BIT_AND(table2ColInt), "(table1.col_int & table2.col_int)")
assertClauseSerialize(t, table1ColInt.BIT_AND(Int(11)), "(table1.col_int & $1)", int64(11))
}
func TestIntExpressionBIT_OR(t *testing.T) {
assertClauseSerialize(t, table1ColInt.BIT_OR(table2ColInt), "(table1.col_int | table2.col_int)")
assertClauseSerialize(t, table1ColInt.BIT_OR(Int(11)), "(table1.col_int | $1)", int64(11))
}
func TestIntExpressionBIT_XOR(t *testing.T) {
assertClauseSerialize(t, table1ColInt.BIT_XOR(table2ColInt), "(table1.col_int # table2.col_int)")
assertClauseSerialize(t, table1ColInt.BIT_XOR(Int(11)), "(table1.col_int # $1)", int64(11))
}
func TestIntExpressionBIT_SHIFT_LEFT(t *testing.T) {
assertClauseSerialize(t, table1ColInt.BIT_SHIFT_LEFT(table2ColInt), "(table1.col_int << table2.col_int)")
assertClauseSerialize(t, table1ColInt.BIT_SHIFT_LEFT(Int(11)), "(table1.col_int << $1)", int64(11))
}
func TestIntExpressionBIT_SHIFT_RIGHT(t *testing.T) {
assertClauseSerialize(t, table1ColInt.BIT_SHIFT_RIGHT(table2ColInt), "(table1.col_int >> table2.col_int)")
assertClauseSerialize(t, table1ColInt.BIT_SHIFT_RIGHT(Int(11)), "(table1.col_int >> $1)", int64(11))
}
func TestIntExpressionIntExp(t *testing.T) {
assertClauseSerialize(t, IntExp(table1ColFloat), "table1.col_float")
assertClauseSerialize(t, IntExp(table1ColFloat.ADD(table2ColFloat)).ADD(Int(11)),
"((table1.col_float + table2.col_float) + $1)", int64(11))
}
func TestIntExpressionBetween(t *testing.T) {
assertClauseSerialize(t, table1ColInt.BETWEEN(Int(1), table1Col3), "(table1.col_int BETWEEN $1 AND table1.col3)", int64(1))
assertClauseSerialize(t, table1ColInt.BETWEEN(Int(1), table1Col3).AND(table1ColBool),
"((table1.col_int BETWEEN $1 AND table1.col3) AND table1.col_bool)", int64(1))
}

View File

@@ -0,0 +1,37 @@
package jet
// Interval is internal common representation of sql interval
type Interval interface {
Serializer
IsInterval
}
// IsInterval interface
type IsInterval interface {
isInterval()
}
// IsIntervalImpl is implementation of IsInterval interface
type IsIntervalImpl struct{}
func (i *IsIntervalImpl) isInterval() {}
// NewInterval creates new interval from serializer
func NewInterval(s Serializer) *IntervalImpl {
newInterval := &IntervalImpl{
Value: s,
}
return newInterval
}
// IntervalImpl is implementation of Interval type
type IntervalImpl struct {
Value Serializer
IsIntervalImpl
}
func (i IntervalImpl) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) {
out.WriteString("INTERVAL")
i.Value.serialize(statement, out, FallTrough(options)...)
}

View File

@@ -0,0 +1,13 @@
package jet
const (
// DEFAULT is jet equivalent of SQL DEFAULT
DEFAULT Keyword = "DEFAULT"
)
// Keyword type
type Keyword string
func (k Keyword) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) {
out.WriteString(string(k))
}

View File

@@ -0,0 +1,480 @@
package jet
import (
"fmt"
"time"
)
// LiteralExpression is representation of an escaped literal
type LiteralExpression interface {
Expression
Value() interface{}
SetConstant(constant bool)
}
type literalExpressionImpl struct {
ExpressionInterfaceImpl
value interface{}
constant bool
}
func literal(value interface{}, optionalConstant ...bool) *literalExpressionImpl {
exp := literalExpressionImpl{value: value}
if len(optionalConstant) > 0 {
exp.constant = optionalConstant[0]
}
exp.ExpressionInterfaceImpl.Parent = &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 {
out.insertConstantArgument(l.value)
} else {
out.insertParametrizedArgument(l.value)
}
}
func (l *literalExpressionImpl) Value() interface{} {
return l.value
}
func (l *literalExpressionImpl) SetConstant(constant bool) {
l.constant = constant
}
type integerLiteralExpression struct {
literalExpressionImpl
integerInterfaceImpl
}
func intLiteral(value interface{}) IntegerExpression {
numLiteral := &integerLiteralExpression{}
numLiteral.literalExpressionImpl = *literal(value)
numLiteral.literalExpressionImpl.Parent = numLiteral
numLiteral.integerInterfaceImpl.parent = numLiteral
return numLiteral
}
// Int creates a new 64 bit signed integer literal
func Int(value int64) IntegerExpression {
return intLiteral(value)
}
// Int8 creates a new 8 bit signed integer literal
func Int8(value int8) IntegerExpression {
return intLiteral(value)
}
// Int16 creates a new 16 bit signed integer literal
func Int16(value int16) IntegerExpression {
return intLiteral(value)
}
// Int32 creates a new 32 bit signed integer literal
func Int32(value int32) IntegerExpression {
return intLiteral(value)
}
// Uint8 creates a new 8 bit unsigned integer literal
func Uint8(value uint8) IntegerExpression {
return intLiteral(value)
}
// Uint16 creates a new 16 bit unsigned integer literal
func Uint16(value uint16) IntegerExpression {
return intLiteral(value)
}
// Uint32 creates a new 32 bit unsigned integer literal
func Uint32(value uint32) IntegerExpression {
return intLiteral(value)
}
// Uint64 creates a new 64 bit unsigned integer literal
func Uint64(value uint64) IntegerExpression {
return intLiteral(value)
}
// ---------------------------------------------------//
type boolLiteralExpression struct {
boolInterfaceImpl
literalExpressionImpl
}
// Bool creates new bool literal expression
func Bool(value bool) BoolExpression {
boolLiteralExpression := boolLiteralExpression{}
boolLiteralExpression.literalExpressionImpl = *literal(value)
boolLiteralExpression.boolInterfaceImpl.parent = &boolLiteralExpression
return &boolLiteralExpression
}
// ---------------------------------------------------//
type floatLiteral struct {
floatInterfaceImpl
literalExpressionImpl
}
// Float creates new float literal from float64 value
func Float(value float64) FloatExpression {
floatLiteral := floatLiteral{}
floatLiteral.literalExpressionImpl = *literal(value)
floatLiteral.floatInterfaceImpl.parent = &floatLiteral
return &floatLiteral
}
// Decimal creates new float literal from string value
func Decimal(value string) FloatExpression {
floatLiteral := floatLiteral{}
floatLiteral.literalExpressionImpl = *literal(value)
floatLiteral.floatInterfaceImpl.parent = &floatLiteral
return &floatLiteral
}
// ---------------------------------------------------//
type stringLiteral struct {
stringInterfaceImpl
literalExpressionImpl
}
// String creates new string literal expression
func String(value string) StringExpression {
stringLiteral := stringLiteral{}
stringLiteral.literalExpressionImpl = *literal(value)
stringLiteral.stringInterfaceImpl.parent = &stringLiteral
return &stringLiteral
}
//---------------------------------------------------//
type timeLiteral struct {
timeInterfaceImpl
literalExpressionImpl
}
// Time creates new time literal expression
func Time(hour, minute, second int, nanoseconds ...time.Duration) TimeExpression {
timeLiteral := &timeLiteral{}
timeStr := fmt.Sprintf("%02d:%02d:%02d", hour, minute, second)
timeStr += formatNanoseconds(nanoseconds...)
timeLiteral.literalExpressionImpl = *literal(timeStr)
timeLiteral.timeInterfaceImpl.parent = timeLiteral
return timeLiteral
}
// TimeT creates new time literal expression from time.Time object
func TimeT(t time.Time) TimeExpression {
timeLiteral := &timeLiteral{}
timeLiteral.literalExpressionImpl = *literal(t)
timeLiteral.timeInterfaceImpl.parent = timeLiteral
return timeLiteral
}
//---------------------------------------------------//
type timezLiteral struct {
timezInterfaceImpl
literalExpressionImpl
}
// Timez creates new time with time zone literal expression
func Timez(hour, minute, second int, nanoseconds time.Duration, timezone string) TimezExpression {
timezLiteral := timezLiteral{}
timeStr := fmt.Sprintf("%02d:%02d:%02d", hour, minute, second)
timeStr += formatNanoseconds(nanoseconds)
timeStr += " " + timezone
timezLiteral.literalExpressionImpl = *literal(timeStr)
return TimezExp(literal(timeStr))
}
// TimezT creates new time with time zone literal expression from time.Time object
func TimezT(t time.Time) TimezExpression {
timeLiteral := &timezLiteral{}
timeLiteral.literalExpressionImpl = *literal(t)
timeLiteral.timezInterfaceImpl.parent = timeLiteral
return timeLiteral
}
//---------------------------------------------------//
type timestampLiteral struct {
timestampInterfaceImpl
literalExpressionImpl
}
// Timestamp creates new timestamp literal expression
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 += formatNanoseconds(nanoseconds...)
timestamp.literalExpressionImpl = *literal(timeStr)
timestamp.timestampInterfaceImpl.parent = timestamp
return timestamp
}
// TimestampT creates new timestamp literal expression from time.Time object
func TimestampT(t time.Time) TimestampExpression {
timestamp := &timestampLiteral{}
timestamp.literalExpressionImpl = *literal(t)
timestamp.timestampInterfaceImpl.parent = timestamp
return timestamp
}
//---------------------------------------------------//
type timestampzLiteral struct {
timestampzInterfaceImpl
literalExpressionImpl
}
// 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 {
timestamp := &timestampzLiteral{}
timeStr := fmt.Sprintf("%04d-%02d-%02d %02d:%02d:%02d", year, month, day, hour, minute, second)
timeStr += formatNanoseconds(nanoseconds)
timeStr += " " + timezone
timestamp.literalExpressionImpl = *literal(timeStr)
timestamp.timestampzInterfaceImpl.parent = timestamp
return timestamp
}
// TimestampzT creates new timestamp literal expression from time.Time object
func TimestampzT(t time.Time) TimestampzExpression {
timestamp := &timestampzLiteral{}
timestamp.literalExpressionImpl = *literal(t)
timestamp.timestampzInterfaceImpl.parent = timestamp
return timestamp
}
//---------------------------------------------------//
type dateLiteral struct {
dateInterfaceImpl
literalExpressionImpl
}
// Date creates new date literal expression
func Date(year int, month time.Month, day int) DateExpression {
dateLiteral := &dateLiteral{}
timeStr := fmt.Sprintf("%04d-%02d-%02d", year, month, day)
dateLiteral.literalExpressionImpl = *literal(timeStr)
dateLiteral.dateInterfaceImpl.parent = dateLiteral
return dateLiteral
}
// DateT creates new date literal expression from time.Time object
func DateT(t time.Time) DateExpression {
dateLiteral := &dateLiteral{}
dateLiteral.literalExpressionImpl = *literal(t)
dateLiteral.dateInterfaceImpl.parent = dateLiteral
return dateLiteral
}
func formatNanoseconds(nanoseconds ...time.Duration) string {
if len(nanoseconds) > 0 && nanoseconds[0] != 0 {
duration := fmt.Sprintf("%09d", nanoseconds[0])
i := len(duration) - 1
for ; i >= 3; i-- {
if duration[i] != '0' {
break
}
}
return "." + duration[0:i+1]
}
return ""
}
//--------------------------------------------------//
var (
// NULL is jet equivalent of SQL NULL
NULL = newNullLiteral()
// STAR is jet equivalent of SQL *
STAR = newStarLiteral()
// PLUS_INFINITY is jet equivalent for sql infinity
PLUS_INFINITY = String("infinity")
// MINUS_INFINITY is jet equivalent for sql -infinity
MINUS_INFINITY = String("-infinity")
)
type nullLiteral struct {
ExpressionInterfaceImpl
}
func newNullLiteral() Expression {
nullExpression := &nullLiteral{}
nullExpression.ExpressionInterfaceImpl.Parent = 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.Parent = starExpression
return starExpression
}
func (n *starLiteral) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) {
out.WriteString("*")
}
//---------------------------------------------------//
type rawExpression struct {
ExpressionInterfaceImpl
Raw string
NamedArgument map[string]interface{}
noWrap bool
}
func (n *rawExpression) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) {
if !n.noWrap && !contains(options, NoWrap) {
out.WriteByte('(')
}
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.
// For example: Raw("current_database()")
func Raw(raw string, namedArgs ...map[string]interface{}) Expression {
var namedArguments map[string]interface{}
if len(namedArgs) > 0 {
namedArguments = namedArgs[0]
}
rawExp := &rawExpression{
Raw: raw,
NamedArgument: namedArguments,
}
rawExp.ExpressionInterfaceImpl.Parent = rawExp
return rawExp
}
// RawWithParent is a Raw constructor used for construction dialect specific expression
func RawWithParent(raw string, parent ...Expression) Expression {
rawExp := &rawExpression{
Raw: raw,
noWrap: true,
}
rawExp.ExpressionInterfaceImpl.Parent = OptionalOrDefaultExpression(rawExp, parent...)
return rawExp
}
// RawBool helper that for raw string boolean expressions
func RawBool(raw string, namedArgs ...map[string]interface{}) BoolExpression {
return BoolExp(Raw(raw, namedArgs...))
}
// RawInt helper that for integer expressions
func RawInt(raw string, namedArgs ...map[string]interface{}) IntegerExpression {
return IntExp(Raw(raw, namedArgs...))
}
// RawFloat helper that for float expressions
func RawFloat(raw string, namedArgs ...map[string]interface{}) FloatExpression {
return FloatExp(Raw(raw, namedArgs...))
}
// RawString helper that for string expressions
func RawString(raw string, namedArgs ...map[string]interface{}) StringExpression {
return StringExp(Raw(raw, namedArgs...))
}
// RawTime helper that for time expressions
func RawTime(raw string, namedArgs ...map[string]interface{}) TimeExpression {
return TimeExp(Raw(raw, namedArgs...))
}
// RawTimez helper that for time with time zone expressions
func RawTimez(raw string, namedArgs ...map[string]interface{}) TimezExpression {
return TimezExp(Raw(raw, namedArgs...))
}
// RawTimestamp helper that for timestamp expressions
func RawTimestamp(raw string, namedArgs ...map[string]interface{}) TimestampExpression {
return TimestampExp(Raw(raw, namedArgs...))
}
// RawTimestampz helper that for timestamp with time zone expressions
func RawTimestampz(raw string, namedArgs ...map[string]interface{}) TimestampzExpression {
return TimestampzExp(Raw(raw, namedArgs...))
}
// RawDate helper that for date expressions
func RawDate(raw string, namedArgs ...map[string]interface{}) DateExpression {
return DateExp(Raw(raw, namedArgs...))
}
// RawRange helper that for range expressions
func RawRange[T Expression](raw string, namedArgs ...map[string]interface{}) Range[T] {
return RangeExp[T](Raw(raw, namedArgs...))
}
// UUID is a helper function to create string literal expression from uuid object
// value can be any uuid type with a String method
func UUID(value fmt.Stringer) StringExpression {
return String(value.String())
}

View File

@@ -0,0 +1,60 @@
package jet
import (
"testing"
"time"
)
func TestRawExpression(t *testing.T) {
assertClauseSerialize(t, Raw("current_database()"), "(current_database())")
var timeT = time.Date(2009, 11, 17, 20, 34, 58, 651387237, time.UTC)
assertClauseSerialize(t, DateT(timeT), "$1", timeT)
}
func TestTimeLiteral(t *testing.T) {
assertClauseDebugSerialize(t, Time(11, 5, 30), "'11:05:30'")
assertClauseDebugSerialize(t, Time(11, 5, 30, 0), "'11:05:30'")
assertClauseDebugSerialize(t, Time(11, 5, 30, 3*time.Millisecond), "'11:05:30.003'")
assertClauseDebugSerialize(t, Time(11, 5, 30, 30*time.Millisecond), "'11:05:30.030'")
assertClauseDebugSerialize(t, Time(11, 5, 30, 300*time.Millisecond), "'11:05:30.300'")
assertClauseDebugSerialize(t, Time(11, 5, 30, 300*time.Microsecond), "'11:05:30.0003'")
assertClauseDebugSerialize(t, Time(11, 5, 30, 4*time.Nanosecond), "'11:05:30.000000004'")
}
func TestTimeT(t *testing.T) {
timeT := time.Date(2000, 1, 1, 11, 40, 20, 124, time.UTC)
assertClauseDebugSerialize(t, TimeT(timeT), `'2000-01-01 11:40:20.000000124Z'`)
}
func TestTimezLiteral(t *testing.T) {
assertClauseDebugSerialize(t, Timez(11, 5, 30, 10*time.Nanosecond, "UTC"), "'11:05:30.00000001 UTC'")
assertClauseDebugSerialize(t, Timez(11, 5, 30, 0, "+1"), "'11:05:30 +1'")
assertClauseDebugSerialize(t, Timez(11, 5, 30, 3*time.Microsecond, "-7"), "'11:05:30.000003 -7'")
assertClauseDebugSerialize(t, Timez(11, 5, 30, 30*time.Millisecond, "+8:00"), "'11:05:30.030 +8:00'")
assertClauseDebugSerialize(t, Timez(11, 5, 30, 300*time.Nanosecond, "America/New_Yor"), "'11:05:30.0000003 America/New_Yor'")
assertClauseDebugSerialize(t, Timez(11, 5, 30, 3000*time.Nanosecond, "zulu"), "'11:05:30.000003 zulu'")
}
func TestTimestampLiteral(t *testing.T) {
assertClauseDebugSerialize(t, Timestamp(2011, 1, 8, 11, 5, 30), "'2011-01-08 11:05:30'")
assertClauseDebugSerialize(t, Timestamp(2011, 2, 7, 11, 5, 30, 0), "'2011-02-07 11:05:30'")
assertClauseDebugSerialize(t, Timestamp(2011, 3, 6, 11, 5, 30, 3*time.Millisecond), "'2011-03-06 11:05:30.003'")
assertClauseDebugSerialize(t, Timestamp(2011, 4, 5, 11, 5, 30, 30*time.Millisecond), "'2011-04-05 11:05:30.030'")
assertClauseDebugSerialize(t, Timestamp(2011, 5, 4, 11, 5, 30, 300*time.Millisecond), "'2011-05-04 11:05:30.300'")
assertClauseDebugSerialize(t, Timestamp(2011, 6, 3, 11, 5, 30, 3000*time.Microsecond), "'2011-06-03 11:05:30.003'")
}
func TestTimestampzLiteral(t *testing.T) {
assertClauseDebugSerialize(t, Timestampz(2011, 1, 8, 11, 5, 30, 0, "UTC"), "'2011-01-08 11:05:30 UTC'")
assertClauseDebugSerialize(t, Timestampz(2011, 2, 7, 11, 5, 30, 0, "PST"), "'2011-02-07 11:05:30 PST'")
assertClauseDebugSerialize(t, Timestampz(2011, 3, 6, 11, 5, 30, 3, "+4:00"), "'2011-03-06 11:05:30.000000003 +4:00'")
assertClauseDebugSerialize(t, Timestampz(2011, 4, 5, 11, 5, 30, 30, "-8:00"), "'2011-04-05 11:05:30.00000003 -8:00'")
assertClauseDebugSerialize(t, Timestampz(2011, 5, 4, 11, 5, 30, 300, "400"), "'2011-05-04 11:05:30.0000003 400'")
assertClauseDebugSerialize(t, Timestampz(2011, 6, 3, 11, 5, 30, 3000, "zulu"), "'2011-06-03 11:05:30.000003 zulu'")
}
func TestDate(t *testing.T) {
assertClauseDebugSerialize(t, Date(2019, 8, 8), `'2019-08-08'`)
}

View File

@@ -0,0 +1,81 @@
package jet
import (
"context"
"runtime"
"strings"
"time"
)
// PrintableStatement is a statement which sql query can be logged
type PrintableStatement interface {
Sql() (query string, args []interface{})
DebugSql() (query string)
}
// LoggerFunc is a function user can implement to support automatic statement logging.
type LoggerFunc func(ctx context.Context, statement PrintableStatement)
var logger LoggerFunc
// SetLoggerFunc sets automatic statement logging
func SetLoggerFunc(loggerFunc LoggerFunc) {
logger = loggerFunc
}
func callLogger(ctx context.Context, statement Statement) {
if logger != nil {
logger(ctx, statement)
}
}
// QueryInfo contains information about executed query
type QueryInfo struct {
Statement PrintableStatement
// Depending on how the statement is executed, RowsProcessed is:
// - Number of rows returned for Query() and QueryContext() methods
// - RowsAffected() for Exec() and ExecContext() methods
// - Always 0 for Rows() method.
RowsProcessed int64
Duration time.Duration
Err error
}
// QueryLoggerFunc is a function user can implement to retrieve more information about statement executed.
type QueryLoggerFunc func(ctx context.Context, info QueryInfo)
var queryLoggerFunc QueryLoggerFunc
// SetQueryLogger sets automatic query logging function.
func SetQueryLogger(loggerFunc QueryLoggerFunc) {
queryLoggerFunc = loggerFunc
}
func callQueryLoggerFunc(ctx context.Context, info QueryInfo) {
if queryLoggerFunc != nil {
queryLoggerFunc(ctx, info)
}
}
// Caller returns information about statement caller
func (q QueryInfo) Caller() (file string, line int, function string) {
skip := 4
// depending on execution type (Query, QueryContext, Exec, ...) looped once or twice
for {
var pc uintptr
var ok bool
pc, file, line, ok = runtime.Caller(skip)
if !ok {
return
}
funcDetails := runtime.FuncForPC(pc)
if !strings.Contains(funcDetails.Name(), "github.com/go-jet/jet/v2/internal") {
function = funcDetails.Name()
return
}
skip++
}
}

View File

@@ -0,0 +1,15 @@
package jet
// NumericExpression is common interface for all integer and float expressions
type NumericExpression interface {
Expression
numericExpression
}
type numericExpression interface {
isNumericExpression()
}
type numericExpressionImpl struct{}
func (n *numericExpressionImpl) isNumericExpression() {}

View File

@@ -0,0 +1,194 @@
package jet
// Operators
const (
StringConcatOperator = "||"
StringRegexpLikeOperator = "REGEXP"
StringNotRegexpLikeOperator = "NOT REGEXP"
)
//----------- Logical operators ---------------//
// NOT returns negation of bool expression result
func NOT(exp BoolExpression) BoolExpression {
return newPrefixBoolOperatorExpression(exp, "NOT")
}
// BIT_NOT inverts every bit in integer expression result
func BIT_NOT(expr IntegerExpression) IntegerExpression {
if literalExp, ok := expr.(LiteralExpression); ok {
literalExp.SetConstant(true)
}
return newPrefixIntegerOperatorExpression(expr, "~")
}
//----------- Comparison operators ---------------//
// EXISTS checks for existence of the rows in subQuery
func EXISTS(subQuery Expression) BoolExpression {
return newPrefixBoolOperatorExpression(subQuery, "EXISTS")
}
// Eq returns a representation of "a=b"
func Eq(lhs, rhs Expression) BoolExpression {
return newBinaryBoolOperatorExpression(lhs, rhs, "=")
}
// NotEq returns a representation of "a!=b"
func NotEq(lhs, rhs Expression) BoolExpression {
return newBinaryBoolOperatorExpression(lhs, rhs, "!=")
}
// IsDistinctFrom returns a representation of "a IS DISTINCT FROM b"
func IsDistinctFrom(lhs, rhs Expression) BoolExpression {
return newBinaryBoolOperatorExpression(lhs, rhs, "IS DISTINCT FROM")
}
// IsNotDistinctFrom returns a representation of "a IS NOT DISTINCT FROM b"
func IsNotDistinctFrom(lhs, rhs Expression) BoolExpression {
return newBinaryBoolOperatorExpression(lhs, rhs, "IS NOT DISTINCT FROM")
}
// Lt returns a representation of "a<b"
func Lt(lhs Expression, rhs Expression) BoolExpression {
return newBinaryBoolOperatorExpression(lhs, rhs, "<")
}
// LtEq returns a representation of "a<=b"
func LtEq(lhs, rhs Expression) BoolExpression {
return newBinaryBoolOperatorExpression(lhs, rhs, "<=")
}
// Gt returns a representation of "a>b"
func Gt(lhs, rhs Expression) BoolExpression {
return newBinaryBoolOperatorExpression(lhs, rhs, ">")
}
// GtEq returns a representation of "a>=b"
func GtEq(lhs, rhs Expression) BoolExpression {
return newBinaryBoolOperatorExpression(lhs, rhs, ">=")
}
// Contains returns a representation of "a @> b"
func Contains(lhs Expression, rhs Expression) BoolExpression {
return newBinaryBoolOperatorExpression(lhs, rhs, "@>")
}
// Overlap returns a representation of "a && b"
func Overlap(lhs, rhs Expression) BoolExpression {
return newBinaryBoolOperatorExpression(lhs, rhs, "&&")
}
// Add notEq returns a representation of "a + b"
func Add(lhs, rhs Serializer) Expression {
return NewBinaryOperatorExpression(lhs, rhs, "+")
}
// Sub notEq returns a representation of "a - b"
func Sub(lhs, rhs Serializer) Expression {
return NewBinaryOperatorExpression(lhs, rhs, "-")
}
// Mul returns a representation of "a * b"
func Mul(lhs, rhs Serializer) Expression {
return NewBinaryOperatorExpression(lhs, rhs, "*")
}
// Div returns a representation of "a / b"
func Div(lhs, rhs Serializer) Expression {
return NewBinaryOperatorExpression(lhs, rhs, "/")
}
// Mod returns a representation of "a % b"
func Mod(lhs, rhs Serializer) Expression {
return NewBinaryOperatorExpression(lhs, rhs, "%")
}
// --------------- CASE operator -------------------//
// CaseOperator is interface for SQL case operator
type CaseOperator interface {
Expression
WHEN(condition Expression) CaseOperator
THEN(then Expression) CaseOperator
ELSE(els Expression) CaseOperator
}
type caseOperatorImpl struct {
ExpressionInterfaceImpl
expression Expression
when []Expression
then []Expression
els Expression
}
// CASE create CASE operator with optional list of expressions
func CASE(expression ...Expression) CaseOperator {
caseExp := &caseOperatorImpl{}
if len(expression) > 0 {
caseExp.expression = expression[0]
}
caseExp.ExpressionInterfaceImpl.Parent = caseExp
return caseExp
}
func (c *caseOperatorImpl) WHEN(when Expression) CaseOperator {
c.when = append(c.when, when)
return c
}
func (c *caseOperatorImpl) THEN(then Expression) CaseOperator {
c.then = append(c.then, then)
return c
}
func (c *caseOperatorImpl) ELSE(els Expression) CaseOperator {
c.els = els
return c
}
func (c *caseOperatorImpl) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) {
out.WriteString("(CASE")
if c.expression != nil {
c.expression.serialize(statement, out, FallTrough(options)...)
}
if len(c.when) == 0 || len(c.then) == 0 {
panic("jet: invalid case Statement. There should be at least one WHEN/THEN pair. ")
}
if len(c.when) != len(c.then) {
panic("jet: WHEN and THEN expression count mismatch. ")
}
for i, when := range c.when {
out.WriteString("WHEN")
when.serialize(statement, out, NoWrap)
out.WriteString("THEN")
c.then[i].serialize(statement, out, NoWrap)
}
if c.els != nil {
out.WriteString("ELSE")
c.els.serialize(statement, out, NoWrap)
}
out.WriteString("END)")
}
// DISTINCT operator can be used to return distinct values of expr
func DISTINCT(expr Expression) Expression {
return newPrefixOperatorExpression(expr, "DISTINCT")
}
func BinaryOperator(lhs Expression, rhs Expression, operator string) Expression {
return NewBinaryOperatorExpression(lhs, rhs, operator)
}

View File

@@ -0,0 +1,31 @@
package jet
import "testing"
func TestOperatorNOT(t *testing.T) {
notExpression := NOT(Int(2).EQ(Int(1)))
assertClauseSerialize(t, NOT(table1ColBool), "(NOT table1.col_bool)")
assertClauseSerialize(t, notExpression, "(NOT ($1 = $2))", int64(2), int64(1))
assertProjectionSerialize(t, notExpression.AS("alias_not_expression"), `(NOT ($1 = $2)) AS "alias_not_expression"`, int64(2), int64(1))
assertClauseSerialize(t, notExpression.AND(Int(4).EQ(Int(5))), `((NOT ($1 = $2)) AND ($3 = $4))`, int64(2), int64(1), int64(4), int64(5))
}
func TestCase1(t *testing.T) {
query := CASE().
WHEN(table3Col1.EQ(Int(1))).THEN(table3Col1.ADD(Int(1))).
WHEN(table3Col1.EQ(Int(2))).THEN(table3Col1.ADD(Int(2)))
assertClauseSerialize(t, query, `(CASE WHEN table3.col1 = $1 THEN table3.col1 + $2 WHEN table3.col1 = $3 THEN table3.col1 + $4 END)`,
int64(1), int64(1), int64(2), int64(2))
}
func TestCase2(t *testing.T) {
query := CASE(table3Col1).
WHEN(Int(1)).THEN(table3Col1.ADD(Int(1))).
WHEN(Int(2)).THEN(table3Col1.ADD(Int(2))).
ELSE(Int(0))
assertClauseSerialize(t, query, `(CASE table3.col1 WHEN $1 THEN table3.col1 + $2 WHEN $3 THEN table3.col1 + $4 ELSE $5 END)`,
int64(1), int64(1), int64(2), int64(2), int64(0))
}

View File

@@ -0,0 +1,79 @@
package jet
// OrderByClause interface
type OrderByClause interface {
// NULLS_FIRST specifies sort where null values appear before all non-null values.
// For some dialects(mysql,mariadb), which do not support NULL_FIRST, NULL_FIRST is simulated
// with additional IS_NOT_NULL expression.
// For instance,
// Rental.ReturnDate.DESC().NULLS_FIRST()
// would translate to,
// rental.return_date IS NOT NULL, rental.return_date DESC
NULLS_FIRST() OrderByClause
// NULLS_LAST specifies sort where null values appear after all non-null values.
// For some dialects(mysql,mariadb), which do not support NULLS_LAST, NULLS_LAST is simulated
// with additional IS_NULL expression.
// For instance,
// Rental.ReturnDate.ASC().NULLS_LAST()
// would translate to,
// rental.return_date IS NULL, rental.return_date ASC
NULLS_LAST() OrderByClause
serializeForOrderBy(statement StatementType, out *SQLBuilder)
}
type orderByClauseImpl struct {
expression Expression
ascending *bool
nullsFirst *bool
}
func (ord *orderByClauseImpl) NULLS_FIRST() OrderByClause {
nullsFirst := true
ord.nullsFirst = &nullsFirst
return ord
}
func (ord *orderByClauseImpl) NULLS_LAST() OrderByClause {
nullsFirst := false
ord.nullsFirst = &nullsFirst
return ord
}
func (ord *orderByClauseImpl) serializeForOrderBy(statement StatementType, out *SQLBuilder) {
customSerializer := out.Dialect.SerializeOrderBy()
if customSerializer != nil {
customSerializer(ord.expression, ord.ascending, ord.nullsFirst)(statement, out)
return
}
if ord.expression == nil {
panic("jet: nil expression in ORDER BY clause")
}
ord.expression.serializeForOrderBy(statement, out)
if ord.ascending != nil {
if *ord.ascending {
out.WriteString("ASC")
} else {
out.WriteString("DESC")
}
}
if ord.nullsFirst != nil {
if *ord.nullsFirst {
out.WriteString("NULLS FIRST")
} else {
out.WriteString("NULLS LAST")
}
}
}
func newOrderByAscending(expression Expression, ascending bool) OrderByClause {
return &orderByClauseImpl{expression: expression, ascending: &ascending}
}
func newOrderByNullsFirst(expression Expression, nullsFirst bool) OrderByClause {
return &orderByClauseImpl{expression: expression, nullsFirst: &nullsFirst}
}

View File

@@ -0,0 +1,65 @@
package jet
// MODE computes the most frequent value of the aggregated argument
func MODE() *OrderSetAggregateFunc {
return newOrderSetAggregateFunction("MODE", nil)
}
// 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.
func PERCENTILE_CONT(fraction FloatExpression) *OrderSetAggregateFunc {
return newOrderSetAggregateFunction("PERCENTILE_CONT", fraction)
}
// 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.
func PERCENTILE_DISC(fraction FloatExpression) *OrderSetAggregateFunc {
return newOrderSetAggregateFunction("PERCENTILE_DISC", fraction)
}
// OrderSetAggregateFunc implementation of order set aggregate function
type OrderSetAggregateFunc struct {
name string
fraction FloatExpression
orderBy Window
}
func newOrderSetAggregateFunction(name string, fraction FloatExpression) *OrderSetAggregateFunc {
return &OrderSetAggregateFunc{
name: name,
fraction: fraction,
}
}
// WITHIN_GROUP_ORDER_BY specifies ordered set of aggregated argument values
func (p *OrderSetAggregateFunc) WITHIN_GROUP_ORDER_BY(orderBy OrderByClause) Expression {
p.orderBy = ORDER_BY(orderBy)
return newOrderSetAggregateFuncExpression(*p)
}
func newOrderSetAggregateFuncExpression(aggFunc OrderSetAggregateFunc) *orderSetAggregateFuncExpression {
ret := &orderSetAggregateFuncExpression{
OrderSetAggregateFunc: aggFunc,
}
ret.ExpressionInterfaceImpl.Parent = ret
return ret
}
type orderSetAggregateFuncExpression struct {
ExpressionInterfaceImpl
OrderSetAggregateFunc
}
func (p *orderSetAggregateFuncExpression) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) {
out.WriteString(p.name)
if p.fraction != nil {
wrap(p.fraction).serialize(statement, out, FallTrough(options)...)
} else {
wrap().serialize(statement, out, FallTrough(options)...)
}
out.WriteString("WITHIN GROUP")
p.orderBy.serialize(statement, out)
}

View File

@@ -0,0 +1,81 @@
package jet
// Projection is interface for all projection types. Types that can be part of, for instance SELECT clause.
type Projection interface {
serializeForProjection(statement StatementType, out *SQLBuilder)
fromImpl(subQuery SelectTable) Projection
}
// SerializeForProjection is helper function for serializing projection outside of jet package
func SerializeForProjection(projection Projection, statementType StatementType, out *SQLBuilder) {
projection.serializeForProjection(statementType, out)
}
// ProjectionList is a redefined type, so that ProjectionList can be used as a Projection.
type ProjectionList []Projection
func (pl ProjectionList) fromImpl(subQuery SelectTable) Projection {
newProjectionList := ProjectionList{}
for _, projection := range pl {
newProjectionList = append(newProjectionList, projection.fromImpl(subQuery))
}
return newProjectionList
}
func (pl ProjectionList) serializeForProjection(statement StatementType, out *SQLBuilder) {
SerializeProjectionList(statement, pl, out)
}
// As will create new projection list where each column is wrapped with a new table alias.
// tableAlias should be in the form 'name' or 'name.*', or it can be an empty string, which will remove existing table alias.
// For instance: If projection list has a column 'Artist.Name', and tableAlias is 'Musician.*', returned projection list will
// have a column wrapped in alias 'Musician.Name'. If tableAlias is empty string, it removes existing table alias ('Artist.Name' becomes 'Name').
func (pl ProjectionList) As(tableAlias string) ProjectionList {
newProjectionList := ProjectionList{}
for _, projection := range pl {
switch p := projection.(type) {
case ProjectionList:
newProjectionList = append(newProjectionList, p.As(tableAlias))
case ColumnList:
newProjectionList = append(newProjectionList, p.As(tableAlias))
case ColumnExpression:
newProjectionList = append(newProjectionList, newAlias(p, joinAlias(tableAlias, p.Name())))
case *alias:
newAlias := *p
_, columnName := extractTableAndColumnName(newAlias.alias)
newAlias.alias = joinAlias(tableAlias, columnName)
newProjectionList = append(newProjectionList, &newAlias)
}
}
return newProjectionList
}
// Except will create new projection list in which columns contained in excluded column names are removed
func (pl ProjectionList) Except(toExclude ...Column) ProjectionList {
excludedColumnList := UnwidColumnList(toExclude)
excludedColumnNames := map[string]bool{}
for _, excludedColumn := range excludedColumnList {
excludedColumnNames[excludedColumn.Name()] = true
}
var ret ProjectionList
for _, projection := range pl {
switch p := projection.(type) {
case ProjectionList:
ret = append(ret, p.Except(toExclude...))
case ColumnExpression:
if excludedColumnNames[p.Name()] {
continue
}
ret = append(ret, p)
}
}
return ret
}

View File

@@ -0,0 +1,65 @@
package jet
import "testing"
func TestProjectionAs(t *testing.T) {
projectionList := ProjectionList{
table1Col3,
SUM(table1ColInt).AS("sum"),
SUM(table1ColInt).AS("table.sum"),
ProjectionList{
table1ColBool,
AVG(table1ColInt).AS("avg"),
AVG(table1ColInt).AS("t.avg"),
},
ColumnList{table2Col3, table2Col4},
}
aliasedProjectionList := projectionList.As("new_alias.*")
assertProjectionSerialize(t, aliasedProjectionList,
`table1.col3 AS "new_alias.col3",
SUM(table1.col_int) AS "new_alias.sum",
SUM(table1.col_int) AS "new_alias.sum",
table1.col_bool AS "new_alias.col_bool",
AVG(table1.col_int) AS "new_alias.avg",
AVG(table1.col_int) AS "new_alias.avg",
table2.col3 AS "new_alias.col3",
table2.col4 AS "new_alias.col4"`)
aliasedProjectionList = projectionList.As("")
assertProjectionSerialize(t, aliasedProjectionList,
`table1.col3 AS "col3",
SUM(table1.col_int) AS "sum",
SUM(table1.col_int) AS "sum",
table1.col_bool AS "col_bool",
AVG(table1.col_int) AS "avg",
AVG(table1.col_int) AS "avg",
table2.col3 AS "col3",
table2.col4 AS "col4"`)
subQueryProjections := projectionList.fromImpl(NewSelectTable(nil, "subQuery", nil))
assertProjectionSerialize(t, subQueryProjections,
`"subQuery"."table1.col3" AS "table1.col3",
"subQuery".sum AS "sum",
"subQuery"."table.sum" AS "table.sum",
"subQuery"."table1.col_bool" AS "table1.col_bool",
"subQuery".avg AS "avg",
"subQuery"."t.avg" AS "t.avg",
"subQuery"."table2.col3" AS "table2.col3",
"subQuery"."table2.col4" AS "table2.col4"`)
aliasedSubQueryProjectionList := subQueryProjections.(ProjectionList).As("subAlias")
assertProjectionSerialize(t, aliasedSubQueryProjectionList,
`"subQuery"."table1.col3" AS "subAlias.col3",
"subQuery".sum AS "subAlias.sum",
"subQuery"."table.sum" AS "subAlias.sum",
"subQuery"."table1.col_bool" AS "subAlias.col_bool",
"subQuery".avg AS "subAlias.avg",
"subQuery"."t.avg" AS "subAlias.avg",
"subQuery"."table2.col3" AS "subAlias.col3",
"subQuery"."table2.col4" AS "subAlias.col4"`)
}

View File

@@ -0,0 +1,141 @@
package jet
// Range Expression is interface for date range types
type Range[T Expression] interface {
Expression
EQ(rhs Range[T]) BoolExpression
NOT_EQ(rhs Range[T]) BoolExpression
LT(rhs Range[T]) BoolExpression
LT_EQ(rhs Range[T]) BoolExpression
GT(rhs Range[T]) BoolExpression
GT_EQ(rhs Range[T]) BoolExpression
CONTAINS(rhs T) BoolExpression
CONTAINS_RANGE(rhs Range[T]) BoolExpression
OVERLAP(rhs Range[T]) BoolExpression
UNION(rhs Range[T]) Range[T]
INTERSECTION(rhs Range[T]) Range[T]
DIFFERENCE(rhs Range[T]) Range[T]
UPPER_BOUND() T
LOWER_BOUND() T
IS_EMPTY() BoolExpression
LOWER_INC() BoolExpression
UPPER_INC() BoolExpression
LOWER_INF() BoolExpression
UPPER_INF() BoolExpression
}
type rangeInterfaceImpl[T Expression] struct {
parent Range[T]
}
func (r *rangeInterfaceImpl[T]) EQ(rhs Range[T]) BoolExpression {
return Eq(r.parent, rhs)
}
func (r *rangeInterfaceImpl[T]) NOT_EQ(rhs Range[T]) BoolExpression {
return NotEq(r.parent, rhs)
}
func (r *rangeInterfaceImpl[T]) LT(rhs Range[T]) BoolExpression {
return Lt(r.parent, rhs)
}
func (r *rangeInterfaceImpl[T]) LT_EQ(rhs Range[T]) BoolExpression {
return LtEq(r.parent, rhs)
}
func (r *rangeInterfaceImpl[T]) GT(rhs Range[T]) BoolExpression {
return Gt(r.parent, rhs)
}
func (r *rangeInterfaceImpl[T]) GT_EQ(rhs Range[T]) BoolExpression {
return GtEq(r.parent, rhs)
}
func (r *rangeInterfaceImpl[T]) CONTAINS(rhs T) BoolExpression {
return Contains(r.parent, rhs)
}
func (r *rangeInterfaceImpl[T]) CONTAINS_RANGE(rhs Range[T]) BoolExpression {
return Contains(r.parent, rhs)
}
func (r *rangeInterfaceImpl[T]) OVERLAP(rhs Range[T]) BoolExpression {
return Overlap(r.parent, rhs)
}
func (r *rangeInterfaceImpl[T]) UNION(rhs Range[T]) Range[T] {
return RangeExp[T](Add(r.parent, rhs))
}
func (r *rangeInterfaceImpl[T]) INTERSECTION(rhs Range[T]) Range[T] {
return RangeExp[T](Mul(r.parent, rhs))
}
func (r *rangeInterfaceImpl[T]) DIFFERENCE(rhs Range[T]) Range[T] {
return RangeExp[T](Sub(r.parent, rhs))
}
func (r *rangeInterfaceImpl[T]) UPPER_BOUND() T {
return UPPER_BOUND(r.parent)
}
func (r *rangeInterfaceImpl[T]) LOWER_BOUND() T {
return LOWER_BOUND(r.parent)
}
func (r *rangeInterfaceImpl[T]) IS_EMPTY() BoolExpression {
return IS_EMPTY(r.parent)
}
func (r *rangeInterfaceImpl[T]) LOWER_INC() BoolExpression {
return LOWER_INC(r.parent)
}
func (r *rangeInterfaceImpl[T]) UPPER_INC() BoolExpression {
return UPPER_INC(r.parent)
}
func (r *rangeInterfaceImpl[T]) LOWER_INF() BoolExpression {
return LOWER_INF(r.parent)
}
func (r *rangeInterfaceImpl[T]) UPPER_INF() BoolExpression {
return UPPER_INF(r.parent)
}
//---------------------------------------------------//
type rangeExpressionWrapper[T Expression] struct {
rangeInterfaceImpl[T]
Expression
}
func newRangeExpressionWrap[T Expression](expression Expression) Range[T] {
rangeExpressionWrap := rangeExpressionWrapper[T]{Expression: expression}
rangeExpressionWrap.rangeInterfaceImpl.parent = &rangeExpressionWrap
return &rangeExpressionWrap
}
// RangeExp is range expression wrapper around arbitrary expression.
// Allows go compiler to see any expression as range expression.
// Does not add sql cast to generated sql builder output.
func RangeExp[T Expression](expression Expression) Range[T] {
return newRangeExpressionWrap[T](expression)
}
// different range expression wrappers
var (
Int4RangeExp = RangeExp[Int4Expression]
Int8RangeExp = RangeExp[Int8Expression]
NumRangeExp = RangeExp[NumericExpression]
DateRangeExp = RangeExp[DateExpression]
TsRangeExp = RangeExp[TimestampExpression]
TstzRangeExp = RangeExp[TimestampzExpression]
)

View File

@@ -0,0 +1,63 @@
package jet
import "testing"
func TestRangeExpressionEQ(t *testing.T) {
assertClauseSerialize(t, table1ColRange.EQ(table2ColRange), "(table1.col_range = table2.col_range)")
assertClauseSerialize(t, table1ColRange.EQ(Int8Range(Int8(1), Int8(4), String("[)"))), "(table1.col_range = int8range($1, $2, $3))", int8(1), int8(4), "[)")
}
func TestRangeExpressionNOT_EQ(t *testing.T) {
assertClauseSerialize(t, table1ColRange.NOT_EQ(table2ColRange), "(table1.col_range != table2.col_range)")
assertClauseSerialize(t, table1ColRange.NOT_EQ(Int8Range(Int8(1), Int8(4), String("[)"))), "(table1.col_range != int8range($1, $2, $3))", int8(1), int8(4), "[)")
}
func TestRangeExpressionLT(t *testing.T) {
assertClauseSerialize(t, table1ColRange.LT(table2ColRange), "(table1.col_range < table2.col_range)")
assertClauseSerialize(t, table1ColRange.LT(Int8Range(Int8(1), Int8(4), String("[)"))), "(table1.col_range < int8range($1, $2, $3))", int8(1), int8(4), "[)")
}
func TestRangeExpressionLT_EQ(t *testing.T) {
assertClauseSerialize(t, table1ColRange.LT_EQ(table2ColRange), "(table1.col_range <= table2.col_range)")
assertClauseSerialize(t, table1ColRange.LT_EQ(Int8Range(Int8(1), Int8(4), String("[)"))), "(table1.col_range <= int8range($1, $2, $3))", int8(1), int8(4), "[)")
}
func TestRangeExpressionGT(t *testing.T) {
assertClauseSerialize(t, table1ColRange.GT(table2ColRange), "(table1.col_range > table2.col_range)")
assertClauseSerialize(t, table1ColRange.GT(Int8Range(Int8(1), Int8(4), String("[)"))), "(table1.col_range > int8range($1, $2, $3))", int8(1), int8(4), "[)")
}
func TestRangeExpressionGT_EQ(t *testing.T) {
assertClauseSerialize(t, table1ColRange.GT_EQ(table2ColRange), "(table1.col_range >= table2.col_range)")
assertClauseSerialize(t, table1ColRange.GT_EQ(Int8Range(Int8(1), Int8(4), String("[)"))), "(table1.col_range >= int8range($1, $2, $3))", int8(1), int8(4), "[)")
}
func TestRangeExpressionCONTAINS_RANGE(t *testing.T) {
assertClauseSerialize(t, table1ColRange.CONTAINS_RANGE(table2ColRange), "(table1.col_range @> table2.col_range)")
assertClauseSerialize(t, table1ColRange.CONTAINS_RANGE(Int8Range(Int8(1), Int8(4), String("[)"))), "(table1.col_range @> int8range($1, $2, $3))", int8(1), int8(4), "[)")
}
func TestRangeExpressionCONTAINS(t *testing.T) {
assertClauseSerialize(t, table1ColRange.CONTAINS(table2Col3), "(table1.col_range @> table2.col3)")
assertClauseSerialize(t, table1ColRange.CONTAINS(Int8(1)), "(table1.col_range @> $1)", int8(1))
}
func TestRangeExpressionOVERLAP(t *testing.T) {
assertClauseSerialize(t, table1ColRange.OVERLAP(table2ColRange), "(table1.col_range && table2.col_range)")
assertClauseSerialize(t, table1ColRange.OVERLAP(Int8Range(Int8(1), Int8(4), String("[)"))), "(table1.col_range && int8range($1, $2, $3))", int8(1), int8(4), "[)")
}
func TestRangeExpressionUNION(t *testing.T) {
assertClauseSerialize(t, table1ColRange.UNION(table2ColRange), "(table1.col_range + table2.col_range)")
assertClauseSerialize(t, table1ColRange.UNION(Int8Range(Int8(1), Int8(4), String("[)"))), "(table1.col_range + int8range($1, $2, $3))", int8(1), int8(4), "[)")
}
func TestRangeExpressionINTERSECTION(t *testing.T) {
assertClauseSerialize(t, table1ColRange.INTERSECTION(table2ColRange), "(table1.col_range * table2.col_range)")
assertClauseSerialize(t, table1ColRange.INTERSECTION(Int8Range(Int8(1), Int8(4), String("[)"))), "(table1.col_range * int8range($1, $2, $3))", int8(1), int8(4), "[)")
}
func TestRangeExpressionDIFFERENCE(t *testing.T) {
assertClauseSerialize(t, table1ColRange.DIFFERENCE(table2ColRange), "(table1.col_range - table2.col_range)")
assertClauseSerialize(t, table1ColRange.DIFFERENCE(Int8Range(Int8(1), Int8(4), String("[)"))), "(table1.col_range - int8range($1, $2, $3))", int8(1), int8(4), "[)")
}

View File

@@ -0,0 +1,47 @@
package jet
type rawStatementImpl struct {
serializerStatementInterfaceImpl
RawQuery string
NamedArguments map[string]interface{}
}
// RawStatement creates new sql statements from raw query and optional map of named arguments
func RawStatement(dialect Dialect, rawQuery string, namedArgument ...map[string]interface{}) SerializerStatement {
newRawStatement := rawStatementImpl{
serializerStatementInterfaceImpl: serializerStatementInterfaceImpl{
dialect: dialect,
statementType: "",
parent: nil,
},
RawQuery: rawQuery,
}
if len(namedArgument) > 0 {
newRawStatement.NamedArguments = namedArgument[0]
}
newRawStatement.parent = &newRawStatement
return &newRawStatement
}
func (s *rawStatementImpl) projections() ProjectionList {
return nil
}
func (s *rawStatementImpl) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) {
if !contains(options, NoWrap) {
out.WriteString("(")
out.IncreaseIdent()
}
out.insertRawQuery(s.RawQuery, s.NamedArguments)
if !contains(options, NoWrap) {
out.DecreaseIdent()
out.NewLine()
out.WriteString(")")
}
}

View File

@@ -0,0 +1,102 @@
package jet
// RowExpression interface
type RowExpression interface {
Expression
HasProjections
EQ(rhs RowExpression) BoolExpression
NOT_EQ(rhs RowExpression) BoolExpression
IS_DISTINCT_FROM(rhs RowExpression) BoolExpression
IS_NOT_DISTINCT_FROM(rhs RowExpression) BoolExpression
LT(rhs RowExpression) BoolExpression
LT_EQ(rhs RowExpression) BoolExpression
GT(rhs RowExpression) BoolExpression
GT_EQ(rhs RowExpression) BoolExpression
}
type rowInterfaceImpl struct {
parent Expression
dialect Dialect
elemCount int
}
func (n *rowInterfaceImpl) EQ(rhs RowExpression) BoolExpression {
return Eq(n.parent, rhs)
}
func (n *rowInterfaceImpl) NOT_EQ(rhs RowExpression) BoolExpression {
return NotEq(n.parent, rhs)
}
func (n *rowInterfaceImpl) IS_DISTINCT_FROM(rhs RowExpression) BoolExpression {
return IsDistinctFrom(n.parent, rhs)
}
func (n *rowInterfaceImpl) IS_NOT_DISTINCT_FROM(rhs RowExpression) BoolExpression {
return IsNotDistinctFrom(n.parent, rhs)
}
func (n *rowInterfaceImpl) GT(rhs RowExpression) BoolExpression {
return Gt(n.parent, rhs)
}
func (n *rowInterfaceImpl) GT_EQ(rhs RowExpression) BoolExpression {
return GtEq(n.parent, rhs)
}
func (n *rowInterfaceImpl) LT(rhs RowExpression) BoolExpression {
return Lt(n.parent, rhs)
}
func (n *rowInterfaceImpl) LT_EQ(rhs RowExpression) BoolExpression {
return LtEq(n.parent, rhs)
}
func (n *rowInterfaceImpl) projections() ProjectionList {
var ret ProjectionList
for i := 0; i < n.elemCount; i++ {
rowColumn := NewColumnImpl(n.dialect.ValuesDefaultColumnName(i), "", nil)
ret = append(ret, &rowColumn)
}
return ret
}
// ---------------------------------------------------//
type rowExpressionWrapper struct {
rowInterfaceImpl
Expression
}
func newRowExpression(name string, dialect Dialect, expressions ...Expression) RowExpression {
ret := &rowExpressionWrapper{}
ret.rowInterfaceImpl.parent = ret
ret.Expression = NewFunc(name, expressions, ret)
ret.dialect = dialect
ret.elemCount = len(expressions)
return ret
}
// ROW function is used to create a tuple value that consists of a set of expressions or column values.
func ROW(dialect Dialect, expressions ...Expression) RowExpression {
return newRowExpression("ROW", dialect, expressions...)
}
// WRAP creates row expressions without ROW keyword `( expression1, expression2, ... )`.
func WRAP(dialect Dialect, expressions ...Expression) RowExpression {
return newRowExpression("", dialect, expressions...)
}
// RowExp serves as a wrapper for an arbitrary expression, treating it as a row expression.
// This enables the Go compiler to interpret any expression as a row expression
// Note: This does not modify the generated SQL builder output by adding a SQL CAST operation.
func RowExp(expression Expression) RowExpression {
rowExpressionWrap := rowExpressionWrapper{Expression: expression}
rowExpressionWrap.rowInterfaceImpl.parent = &rowExpressionWrap
return &rowExpressionWrap
}

View File

@@ -0,0 +1,71 @@
package jet
// RowLock is interface for SELECT statement row lock types
type RowLock interface {
Serializer
OF(...Table) RowLock
NOWAIT() RowLock
SKIP_LOCKED() RowLock
}
type selectLockImpl struct {
lockStrength string
of []Table
noWait, skipLocked bool
}
// NewRowLock creates new RowLock
func NewRowLock(name string) func() RowLock {
return func() RowLock {
return newSelectLock(name)
}
}
func newSelectLock(lockStrength string) *selectLockImpl {
return &selectLockImpl{lockStrength: lockStrength}
}
func (s *selectLockImpl) OF(tables ...Table) RowLock {
s.of = tables
return s
}
func (s *selectLockImpl) NOWAIT() RowLock {
s.noWait = true
return s
}
func (s *selectLockImpl) SKIP_LOCKED() RowLock {
s.skipLocked = true
return s
}
func (s *selectLockImpl) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) {
out.WriteString(s.lockStrength)
if len(s.of) > 0 {
out.WriteString("OF")
for i, of := range s.of {
if i > 0 {
out.WriteString(", ")
}
table := of.Alias()
if table == "" {
table = of.TableName()
}
out.WriteIdentifier(table)
}
}
if s.noWait {
out.WriteString("NOWAIT")
}
if s.skipLocked {
out.WriteString("SKIP LOCKED")
}
}

View File

@@ -0,0 +1,78 @@
package jet
// SelectTable is interface for SELECT sub-queries
type SelectTable interface {
SerializerHasProjections
Alias() string
AllColumns() ProjectionList
}
type selectTableImpl struct {
Statement SerializerHasProjections
alias string
columnAliases []ColumnExpression
}
// NewSelectTable func
func NewSelectTable(selectStmt SerializerHasProjections, alias string, columnAliases []ColumnExpression) selectTableImpl {
selectTable := selectTableImpl{
Statement: selectStmt,
alias: alias,
columnAliases: columnAliases,
}
for _, column := range selectTable.columnAliases {
column.setSubQuery(selectTable)
}
return selectTable
}
func (s selectTableImpl) projections() ProjectionList {
return s.Statement.projections()
}
func (s selectTableImpl) Alias() string {
return s.alias
}
func (s selectTableImpl) AllColumns() ProjectionList {
if len(s.columnAliases) > 0 {
return ColumnListToProjectionList(s.columnAliases)
}
projectionList := s.projections().fromImpl(s)
return projectionList.(ProjectionList)
}
func (s selectTableImpl) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) {
s.Statement.serialize(statement, out)
out.WriteString("AS")
out.WriteIdentifier(s.alias)
if len(s.columnAliases) > 0 {
out.WriteByte('(')
SerializeColumnExpressionNames(s.columnAliases, out)
out.WriteByte(')')
}
}
// --------------------------------------
type lateralImpl struct {
selectTableImpl
}
// NewLateral creates new lateral expression from select statement with alias
func NewLateral(selectStmt SerializerStatement, alias string) SelectTable {
return lateralImpl{selectTableImpl: NewSelectTable(selectStmt, alias, nil)}
}
func (s lateralImpl) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) {
out.WriteString("LATERAL")
s.Statement.serialize(statement, out)
out.WriteString("AS")
out.WriteIdentifier(s.alias)
}

View File

@@ -0,0 +1,109 @@
package jet
// SerializeOption type
type SerializeOption int
// Serialize options
const (
NoWrap SerializeOption = iota
SkipNewLine
Ident
fallTroughOptions // fall trough options
ShortName
)
// WithFallTrough extends existing serialize options with additional
func (s SerializeOption) WithFallTrough(options []SerializeOption) []SerializeOption {
return append(FallTrough(options), s)
}
// StatementType is type of the SQL statement
type StatementType string
// Statement types
const (
SelectStatementType StatementType = "SELECT"
InsertStatementType StatementType = "INSERT"
UpdateStatementType StatementType = "UPDATE"
DeleteStatementType StatementType = "DELETE"
SetStatementType StatementType = "SET"
LockStatementType StatementType = "LOCK"
UnLockStatementType StatementType = "UNLOCK"
WithStatementType StatementType = "WITH"
)
// Serializer interface
type Serializer interface {
serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption)
}
// Serialize func
func Serialize(exp Serializer, statementType StatementType, out *SQLBuilder, options ...SerializeOption) {
exp.serialize(statementType, out, options...)
}
func SerializeForOrderBy(exp Expression, statementType StatementType, out *SQLBuilder) {
exp.serializeForOrderBy(statementType, out)
}
func contains(options []SerializeOption, option SerializeOption) bool {
for _, opt := range options {
if opt == option {
return true
}
}
return false
}
// FallTrough filters fall-trough options from the list
func FallTrough(options []SerializeOption) []SerializeOption {
var ret []SerializeOption
for _, option := range options {
if option > fallTroughOptions {
ret = append(ret, option)
}
}
return ret
}
// ListSerializer serializes list of serializers with separator
type ListSerializer struct {
Serializers []Serializer
Separator string
}
func (s ListSerializer) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) {
for i, ser := range s.Serializers {
if i > 0 {
out.WriteString(s.Separator)
}
ser.serialize(statement, out, FallTrough(options)...)
}
}
// NewSerializerClauseImpl is constructor for Seralizer with list of clauses
func NewSerializerClauseImpl(clauses ...Clause) Serializer {
return &serializerImpl{Clauses: clauses}
}
type serializerImpl struct {
Clauses []Clause
}
func (s serializerImpl) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) {
for _, clause := range s.Clauses {
clause.Serialize(statement, out, FallTrough(options)...)
}
}
// Token can be used to construct complex custom expressions
type Token string
func (t Token) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) {
out.WriteString(string(t))
}

View File

@@ -0,0 +1,303 @@
package jet
import (
"bytes"
"database/sql/driver"
"fmt"
"github.com/go-jet/jet/v2/internal/3rdparty/pq"
"github.com/go-jet/jet/v2/internal/utils/is"
"github.com/google/uuid"
"reflect"
"sort"
"strconv"
"strings"
"time"
"unicode"
)
// SQLBuilder generates output SQL
type SQLBuilder struct {
Dialect Dialect
Buff bytes.Buffer
Args []interface{}
lastChar byte
ident int
Debug bool
}
const tabSize = 4
const defaultIdent = 5
// IncreaseIdent adds ident or defaultIdent number of spaces to each new line
func (s *SQLBuilder) IncreaseIdent(ident ...int) {
if len(ident) > 0 {
s.ident += ident[0]
} else {
s.ident += defaultIdent
}
}
// DecreaseIdent removes ident or defaultIdent number of spaces for each new line
func (s *SQLBuilder) DecreaseIdent(ident ...int) {
toDecrease := defaultIdent
if len(ident) > 0 {
toDecrease = ident[0]
}
if s.ident < toDecrease {
s.ident = 0
}
s.ident -= toDecrease
}
// WriteProjections func
func (s *SQLBuilder) WriteProjections(statement StatementType, projections []Projection) {
s.IncreaseIdent()
SerializeProjectionList(statement, projections, s)
s.DecreaseIdent()
}
// NewLine adds new line to output SQL
func (s *SQLBuilder) NewLine() {
s.write([]byte{'\n'})
s.write(bytes.Repeat([]byte{' '}, s.ident))
}
func (s *SQLBuilder) write(data []byte) {
if len(data) == 0 {
return
}
if !isPreSeparator(s.lastChar) && !isPostSeparator(data[0]) && s.Buff.Len() > 0 {
s.Buff.WriteByte(' ')
}
s.Buff.Write(data)
s.lastChar = data[len(data)-1]
}
func isPreSeparator(b byte) bool {
return b == ' ' || b == '.' || b == ',' || b == '(' || b == '\n' || b == ':'
}
func isPostSeparator(b byte) bool {
return b == ' ' || b == '.' || b == ',' || b == ')' || b == '\n' || b == ':'
}
// WriteAlias is used to add alias to output SQL
func (s *SQLBuilder) WriteAlias(str string) {
aliasQuoteChar := string(s.Dialect.AliasQuoteChar())
s.WriteString(aliasQuoteChar + str + aliasQuoteChar)
}
// WriteString writes sting to output SQL
func (s *SQLBuilder) WriteString(str string) {
s.write([]byte(str))
}
// WriteIdentifier adds identifier to output SQL
func (s *SQLBuilder) WriteIdentifier(name string, alwaysQuote ...bool) {
if s.shouldQuote(name, alwaysQuote...) {
identQuoteChar := string(s.Dialect.IdentifierQuoteChar())
s.WriteString(identQuoteChar + name + identQuoteChar)
} else {
s.WriteString(name)
}
}
func (s *SQLBuilder) shouldQuote(name string, alwaysQuote ...bool) bool {
return s.Dialect.IsReservedWord(name) || shouldQuoteIdentifier(name) || len(alwaysQuote) > 0
}
// WriteByte writes byte to output SQL
func (s *SQLBuilder) WriteByte(b byte) {
s.write([]byte{b})
}
func (s *SQLBuilder) finalize() (string, []interface{}) {
return s.Buff.String() + ";\n", s.Args
}
func (s *SQLBuilder) insertConstantArgument(arg interface{}) {
s.WriteString(argToString(arg))
}
func (s *SQLBuilder) insertParametrizedArgument(arg interface{}) {
if s.Debug {
s.insertConstantArgument(arg)
return
}
s.Args = append(s.Args, arg)
argPlaceholder := s.Dialect.ArgumentPlaceholder()(len(s.Args))
s.WriteString(argPlaceholder)
}
func (s *SQLBuilder) insertRawQuery(raw string, namedArg map[string]interface{}) {
type namedArgumentPosition struct {
Name string
Value interface{}
Position int
}
var namedArgumentPositions []namedArgumentPosition
for namedArg, value := range namedArg {
rawCopy := raw
rawIndex := 0
exists := false
// one named argument can occur multiple times inside raw string
for {
index := strings.Index(rawCopy, namedArg)
if index == -1 {
break
}
exists = true
namedArgumentPositions = append(namedArgumentPositions, namedArgumentPosition{
Name: namedArg,
Value: value,
Position: rawIndex + index,
})
rawCopy = rawCopy[index+len(namedArg):]
rawIndex += index + len(namedArg)
}
if !exists {
panic("jet: named argument '" + namedArg + "' does not appear in raw query")
}
}
sort.Slice(namedArgumentPositions, func(i, j int) bool {
return namedArgumentPositions[i].Position < namedArgumentPositions[j].Position
})
for _, namedArgumentPos := range namedArgumentPositions {
// if named argument does not exists in raw string do not add argument to the list of arguments
// It can happen if the same argument occurs multiple times in postgres query.
if !strings.Contains(raw, namedArgumentPos.Name) {
continue
}
s.Args = append(s.Args, namedArgumentPos.Value)
currentArgNum := len(s.Args)
placeholder := s.Dialect.ArgumentPlaceholder()(currentArgNum)
// if placeholder is not unique identifier ($1, $2, etc..), we will replace just one occurrence of the argument
toReplace := -1 // all occurrences
if placeholder == "?" {
toReplace = 1 // just one occurrence
}
if s.Debug {
placeholder = argToString(namedArgumentPos.Value)
}
raw = strings.Replace(raw, namedArgumentPos.Name, placeholder, toReplace)
}
s.WriteString(raw)
}
func argToString(value interface{}) string {
if is.Nil(value) {
return "NULL"
}
switch bindVal := value.(type) {
case bool:
if bindVal {
return "TRUE"
}
return "FALSE"
case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64:
return integerTypesToString(bindVal)
case float32:
return strconv.FormatFloat(float64(bindVal), 'f', -1, 64)
case float64:
return strconv.FormatFloat(float64(bindVal), 'f', -1, 64)
case string:
return stringQuote(bindVal)
case []byte:
return stringQuote(string(bindVal))
case uuid.UUID:
return stringQuote(bindVal.String())
case time.Time:
return stringQuote(string(pq.FormatTimestamp(bindVal)))
default:
if strBindValue, ok := bindVal.(fmt.Stringer); ok {
return stringQuote(strBindValue.String())
}
if valuer, ok := bindVal.(driver.Valuer); ok {
val, err := valuer.Value()
if err != nil {
// If valuer for some reason returns an error, we return error string representation.
// This is fine because argToString is called only from DebugSQL, and DebugSQL shouldn't be used in production.
return err.Error()
}
return argToString(val)
}
panic(fmt.Sprintf("jet: %s type can not be used as SQL query parameter", reflect.TypeOf(value).String()))
}
}
func integerTypesToString(value interface{}) string {
switch bindVal := value.(type) {
case int:
return strconv.FormatInt(int64(bindVal), 10)
case uint:
return strconv.FormatUint(uint64(bindVal), 10)
case int8:
return strconv.FormatInt(int64(bindVal), 10)
case uint8:
return strconv.FormatUint(uint64(bindVal), 10)
case int16:
return strconv.FormatInt(int64(bindVal), 10)
case uint16:
return strconv.FormatUint(uint64(bindVal), 10)
case int32:
return strconv.FormatInt(int64(bindVal), 10)
case uint32:
return strconv.FormatUint(uint64(bindVal), 10)
case int64:
return strconv.FormatInt(bindVal, 10)
case uint64:
return strconv.FormatUint(bindVal, 10)
}
panic("jet: Unsupported integer type: " + reflect.TypeOf(value).String())
}
func shouldQuoteIdentifier(identifier string) bool {
_, err := strconv.ParseInt(identifier, 10, 64)
if err == nil { // if it is a number we should quote it
return true
}
// check if contains non ascii characters
for _, c := range identifier {
if unicode.IsNumber(c) || c == '_' {
continue
}
if c > unicode.MaxASCII || !unicode.IsLetter(c) || unicode.IsUpper(c) {
return true
}
}
return false
}
func stringQuote(value string) string {
return `'` + strings.Replace(value, "'", "''", -1) + `'`
}

View File

@@ -0,0 +1,59 @@
package jet
import (
"github.com/google/uuid"
"github.com/stretchr/testify/require"
"testing"
"time"
)
func TestArgToString(t *testing.T) {
require.Equal(t, argToString(true), "TRUE")
require.Equal(t, argToString(false), "FALSE")
require.Equal(t, argToString(int(-32)), "-32")
require.Equal(t, argToString(uint(32)), "32")
require.Equal(t, argToString(int8(-43)), "-43")
require.Equal(t, argToString(uint8(43)), "43")
require.Equal(t, argToString(int16(-54)), "-54")
require.Equal(t, argToString(uint16(54)), "54")
require.Equal(t, argToString(int32(-65)), "-65")
require.Equal(t, argToString(uint32(65)), "65")
require.Equal(t, argToString(int64(-64)), "-64")
require.Equal(t, argToString(uint64(64)), "64")
require.Equal(t, argToString(float32(2.0)), "2")
require.Equal(t, argToString(float64(1.11)), "1.11")
require.Equal(t, argToString("john"), "'john'")
require.Equal(t, argToString("It's text"), "'It''s text'")
require.Equal(t, argToString([]byte("john")), "'john'")
require.Equal(t, argToString(uuid.MustParse("b68dbff4-a87d-11e9-a7f2-98ded00c39c6")), "'b68dbff4-a87d-11e9-a7f2-98ded00c39c6'")
time, err := time.Parse("Mon Jan 2 15:04:05 -0700 MST 2006", "Mon Jan 2 15:04:05 -0700 MST 2006")
require.NoError(t, err)
require.Equal(t, argToString(time), "'2006-01-02 15:04:05-07:00'")
func() {
defer func() {
require.Equal(t, recover().(string), "jet: map[string]bool type can not be used as SQL query parameter")
}()
argToString(map[string]bool{})
}()
}
func TestFallTrough(t *testing.T) {
require.Equal(t, FallTrough([]SerializeOption{ShortName}), []SerializeOption{ShortName})
require.Equal(t, FallTrough([]SerializeOption{SkipNewLine}), []SerializeOption(nil))
require.Equal(t, FallTrough([]SerializeOption{ShortName, SkipNewLine}), []SerializeOption{ShortName})
}
func TestShouldQuote(t *testing.T) {
require.Equal(t, shouldQuoteIdentifier("123"), true)
require.Equal(t, shouldQuoteIdentifier("123.235"), true)
require.Equal(t, shouldQuoteIdentifier("abc123"), false)
require.Equal(t, shouldQuoteIdentifier("abc.123"), true)
require.Equal(t, shouldQuoteIdentifier("abc_123"), false)
require.Equal(t, shouldQuoteIdentifier("Abc_123"), true)
require.Equal(t, shouldQuoteIdentifier("DŽƜĐǶ"), true)
}

View File

@@ -0,0 +1,269 @@
package jet
import (
"context"
"database/sql"
"github.com/go-jet/jet/v2/qrm"
"time"
)
// Statement is common interface for all statements(SELECT, INSERT, UPDATE, DELETE, LOCK)
type Statement interface {
// Sql returns parametrized sql query with list of arguments.
Sql() (query string, args []interface{})
// DebugSql returns debug query where every parametrized placeholder is replaced with its argument string representation.
// Do not use it in production. Use it only for debug purposes.
DebugSql() (query string)
// Query executes statement over database connection/transaction db and stores row results in destination.
// Destination can be either pointer to struct or pointer to a slice.
// If destination is pointer to struct and query result set is empty, method returns qrm.ErrNoRows.
Query(db qrm.Queryable, destination interface{}) error
// QueryContext executes statement with a context over database connection/transaction db and stores row result in destination.
// Destination can be either pointer to struct or pointer to a slice.
// If destination is pointer to struct and query result set is empty, method returns qrm.ErrNoRows.
QueryContext(ctx context.Context, db qrm.Queryable, destination interface{}) error
// Exec executes statement over db connection/transaction without returning any rows.
Exec(db qrm.Executable) (sql.Result, error)
// ExecContext executes statement with context over db connection/transaction without returning any rows.
ExecContext(ctx context.Context, db qrm.Executable) (sql.Result, error)
// Rows executes statements over db connection/transaction and returns rows
Rows(ctx context.Context, db qrm.Queryable) (*Rows, error)
}
// Rows wraps sql.Rows type with a support for query result mapping
type Rows struct {
*sql.Rows
scanContext *qrm.ScanContext
}
// Scan will map the Row values into struct destination
func (r *Rows) Scan(destination interface{}) error {
return qrm.ScanOneRowToDest(r.scanContext, r.Rows, destination)
}
// SerializerStatement interface
type SerializerStatement interface {
Serializer
Statement
HasProjections
}
// HasProjections interface
type HasProjections interface {
projections() ProjectionList
}
// SerializerHasProjections interface is combination of Serializer and HasProjections interface
type SerializerHasProjections interface {
Serializer
HasProjections
}
// serializerStatementInterfaceImpl struct
type serializerStatementInterfaceImpl struct {
dialect Dialect
statementType StatementType
parent SerializerStatement
}
func (s *serializerStatementInterfaceImpl) Sql() (query string, args []interface{}) {
queryData := &SQLBuilder{Dialect: s.dialect}
s.parent.serialize(s.statementType, queryData, NoWrap)
query, args = queryData.finalize()
return
}
func (s *serializerStatementInterfaceImpl) DebugSql() (query string) {
sqlBuilder := &SQLBuilder{Dialect: s.dialect, Debug: true}
s.parent.serialize(s.statementType, sqlBuilder, NoWrap)
query, _ = sqlBuilder.finalize()
return
}
func (s *serializerStatementInterfaceImpl) Query(db qrm.Queryable, destination interface{}) error {
return s.QueryContext(context.Background(), db, destination)
}
func (s *serializerStatementInterfaceImpl) QueryContext(ctx context.Context, db qrm.Queryable, destination interface{}) error {
query, args := s.Sql()
callLogger(ctx, s)
var rowsProcessed int64
var err error
duration := duration(func() {
rowsProcessed, err = qrm.Query(ctx, db, query, args, destination)
})
callQueryLoggerFunc(ctx, QueryInfo{
Statement: s,
RowsProcessed: rowsProcessed,
Duration: duration,
Err: err,
})
return err
}
func (s *serializerStatementInterfaceImpl) Exec(db qrm.Executable) (res sql.Result, err error) {
return s.ExecContext(context.Background(), db)
}
func (s *serializerStatementInterfaceImpl) ExecContext(ctx context.Context, db qrm.Executable) (res sql.Result, err error) {
query, args := s.Sql()
callLogger(ctx, s)
duration := duration(func() {
res, err = db.ExecContext(ctx, query, args...)
})
var rowsAffected int64
if err == nil {
rowsAffected, _ = res.RowsAffected()
}
callQueryLoggerFunc(ctx, QueryInfo{
Statement: s,
RowsProcessed: rowsAffected,
Duration: duration,
Err: err,
})
return res, err
}
func (s *serializerStatementInterfaceImpl) Rows(ctx context.Context, db qrm.Queryable) (*Rows, error) {
query, args := s.Sql()
callLogger(ctx, s)
var rows *sql.Rows
var err error
duration := duration(func() {
rows, err = db.QueryContext(ctx, query, args...)
})
callQueryLoggerFunc(ctx, QueryInfo{
Statement: s,
Duration: duration,
Err: err,
})
if err != nil {
return nil, err
}
scanContext, err := qrm.NewScanContext(rows)
if err != nil {
return nil, err
}
return &Rows{
Rows: rows,
scanContext: scanContext,
}, nil
}
func duration(f func()) time.Duration {
start := time.Now()
f()
return time.Since(start)
}
// ExpressionStatement interfacess
type ExpressionStatement interface {
Expression
Statement
HasProjections
}
// NewExpressionStatementImpl creates new expression statement
func NewExpressionStatementImpl(Dialect Dialect, statementType StatementType, parent ExpressionStatement, clauses ...Clause) ExpressionStatement {
return &expressionStatementImpl{
ExpressionInterfaceImpl{Parent: parent},
statementImpl{
serializerStatementInterfaceImpl: serializerStatementInterfaceImpl{
parent: parent,
dialect: Dialect,
statementType: statementType,
},
Clauses: clauses,
},
}
}
type expressionStatementImpl struct {
ExpressionInterfaceImpl
statementImpl
}
func (s *expressionStatementImpl) serializeForProjection(statement StatementType, out *SQLBuilder) {
s.serialize(statement, out)
}
// NewStatementImpl creates new statementImpl
func NewStatementImpl(Dialect Dialect, statementType StatementType, parent SerializerStatement, clauses ...Clause) SerializerStatement {
return &statementImpl{
serializerStatementInterfaceImpl: serializerStatementInterfaceImpl{
parent: parent,
dialect: Dialect,
statementType: statementType,
},
Clauses: clauses,
}
}
type statementImpl struct {
serializerStatementInterfaceImpl
Clauses []Clause
}
func (s *statementImpl) projections() ProjectionList {
for _, clause := range s.Clauses {
if selectClause, ok := clause.(ClauseWithProjections); ok {
return selectClause.Projections()
}
}
return nil
}
func (s *statementImpl) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) {
if !contains(options, NoWrap) {
out.WriteString("(")
out.IncreaseIdent()
}
if contains(options, Ident) {
out.IncreaseIdent()
}
for _, clause := range s.Clauses {
clause.Serialize(s.statementType, out, FallTrough(options)...)
}
if contains(options, Ident) {
out.DecreaseIdent()
out.NewLine()
}
if !contains(options, NoWrap) {
out.DecreaseIdent()
out.NewLine()
out.WriteString(")")
}
}

View File

@@ -0,0 +1,115 @@
package jet
// StringExpression interface
type StringExpression interface {
Expression
EQ(rhs StringExpression) BoolExpression
NOT_EQ(rhs StringExpression) BoolExpression
IS_DISTINCT_FROM(rhs StringExpression) BoolExpression
IS_NOT_DISTINCT_FROM(rhs StringExpression) BoolExpression
LT(rhs StringExpression) BoolExpression
LT_EQ(rhs StringExpression) BoolExpression
GT(rhs StringExpression) BoolExpression
GT_EQ(rhs StringExpression) BoolExpression
BETWEEN(min, max StringExpression) BoolExpression
NOT_BETWEEN(min, max StringExpression) BoolExpression
CONCAT(rhs Expression) StringExpression
LIKE(pattern StringExpression) BoolExpression
NOT_LIKE(pattern StringExpression) BoolExpression
REGEXP_LIKE(pattern StringExpression, caseSensitive ...bool) BoolExpression
NOT_REGEXP_LIKE(pattern StringExpression, caseSensitive ...bool) BoolExpression
}
type stringInterfaceImpl struct {
parent StringExpression
}
func (s *stringInterfaceImpl) EQ(rhs StringExpression) BoolExpression {
return Eq(s.parent, rhs)
}
func (s *stringInterfaceImpl) NOT_EQ(rhs StringExpression) BoolExpression {
return NotEq(s.parent, rhs)
}
func (s *stringInterfaceImpl) IS_DISTINCT_FROM(rhs StringExpression) BoolExpression {
return IsDistinctFrom(s.parent, rhs)
}
func (s *stringInterfaceImpl) IS_NOT_DISTINCT_FROM(rhs StringExpression) BoolExpression {
return IsNotDistinctFrom(s.parent, rhs)
}
func (s *stringInterfaceImpl) GT(rhs StringExpression) BoolExpression {
return Gt(s.parent, rhs)
}
func (s *stringInterfaceImpl) GT_EQ(rhs StringExpression) BoolExpression {
return GtEq(s.parent, rhs)
}
func (s *stringInterfaceImpl) LT(rhs StringExpression) BoolExpression {
return Lt(s.parent, rhs)
}
func (s *stringInterfaceImpl) LT_EQ(rhs StringExpression) BoolExpression {
return LtEq(s.parent, rhs)
}
func (s *stringInterfaceImpl) BETWEEN(min, max StringExpression) BoolExpression {
return NewBetweenOperatorExpression(s.parent, min, max, false)
}
func (s *stringInterfaceImpl) NOT_BETWEEN(min, max StringExpression) BoolExpression {
return NewBetweenOperatorExpression(s.parent, min, max, true)
}
func (s *stringInterfaceImpl) CONCAT(rhs Expression) StringExpression {
return newBinaryStringOperatorExpression(s.parent, rhs, StringConcatOperator)
}
func (s *stringInterfaceImpl) LIKE(pattern StringExpression) BoolExpression {
return newBinaryBoolOperatorExpression(s.parent, pattern, "LIKE")
}
func (s *stringInterfaceImpl) NOT_LIKE(pattern StringExpression) BoolExpression {
return newBinaryBoolOperatorExpression(s.parent, pattern, "NOT LIKE")
}
func (s *stringInterfaceImpl) REGEXP_LIKE(pattern StringExpression, caseSensitive ...bool) BoolExpression {
return newBinaryBoolOperatorExpression(s.parent, pattern, StringRegexpLikeOperator, Bool(len(caseSensitive) > 0 && caseSensitive[0]))
}
func (s *stringInterfaceImpl) NOT_REGEXP_LIKE(pattern StringExpression, caseSensitive ...bool) BoolExpression {
return newBinaryBoolOperatorExpression(s.parent, pattern, StringNotRegexpLikeOperator, Bool(len(caseSensitive) > 0 && caseSensitive[0]))
}
// ---------------------------------------------------//
func newBinaryStringOperatorExpression(lhs, rhs Expression, operator string) StringExpression {
return StringExp(NewBinaryOperatorExpression(lhs, rhs, operator))
}
//---------------------------------------------------//
type stringExpressionWrapper struct {
stringInterfaceImpl
Expression
}
func newStringExpressionWrap(expression Expression) StringExpression {
stringExpressionWrap := stringExpressionWrapper{Expression: expression}
stringExpressionWrap.stringInterfaceImpl.parent = &stringExpressionWrap
return &stringExpressionWrap
}
// StringExp is string expression wrapper around arbitrary expression.
// Allows go compiler to see any expression as string expression.
// Does not add sql cast to generated sql builder output.
func StringExp(expression Expression) StringExpression {
return newStringExpressionWrap(expression)
}

View File

@@ -0,0 +1,82 @@
package jet
import (
"testing"
)
func TestStringEQ(t *testing.T) {
exp := table3StrCol.EQ(table2ColStr)
assertClauseSerialize(t, exp, "(table3.col2 = table2.col_str)")
exp = table3StrCol.EQ(String("JOHN"))
assertClauseSerialize(t, exp, "(table3.col2 = $1)", "JOHN")
}
func TestStringNOT_EQ(t *testing.T) {
exp := table3StrCol.NOT_EQ(table2ColStr)
assertClauseSerialize(t, exp, "(table3.col2 != table2.col_str)")
assertClauseSerialize(t, table3StrCol.NOT_EQ(String("JOHN")), "(table3.col2 != $1)", "JOHN")
}
func TestStringExpressionIS_DISTINCT_FROM(t *testing.T) {
assertClauseSerialize(t, table3StrCol.IS_DISTINCT_FROM(table2ColStr), "(table3.col2 IS DISTINCT FROM table2.col_str)")
assertClauseSerialize(t, table3StrCol.IS_DISTINCT_FROM(String("JOHN")), "(table3.col2 IS DISTINCT FROM $1)", "JOHN")
}
func TestStringExpressionIS_NOT_DISTINCT_FROM(t *testing.T) {
assertClauseSerialize(t, table3StrCol.IS_NOT_DISTINCT_FROM(table2ColStr), "(table3.col2 IS NOT DISTINCT FROM table2.col_str)")
assertClauseSerialize(t, table3StrCol.IS_NOT_DISTINCT_FROM(String("JOHN")), "(table3.col2 IS NOT DISTINCT FROM $1)", "JOHN")
}
func TestStringGT(t *testing.T) {
exp := table3StrCol.GT(table2ColStr)
assertClauseSerialize(t, exp, "(table3.col2 > table2.col_str)")
assertClauseSerialize(t, table3StrCol.GT(String("JOHN")), "(table3.col2 > $1)", "JOHN")
}
func TestStringGT_EQ(t *testing.T) {
exp := table3StrCol.GT_EQ(table2ColStr)
assertClauseSerialize(t, exp, "(table3.col2 >= table2.col_str)")
assertClauseSerialize(t, table3StrCol.GT_EQ(String("JOHN")), "(table3.col2 >= $1)", "JOHN")
}
func TestStringLT(t *testing.T) {
exp := table3StrCol.LT(table2ColStr)
assertClauseSerialize(t, exp, "(table3.col2 < table2.col_str)")
assertClauseSerialize(t, table3StrCol.LT(String("JOHN")), "(table3.col2 < $1)", "JOHN")
}
func TestStringLT_EQ(t *testing.T) {
exp := table3StrCol.LT_EQ(table2ColStr)
assertClauseSerialize(t, exp, "(table3.col2 <= table2.col_str)")
assertClauseSerialize(t, table3StrCol.LT_EQ(String("JOHN")), "(table3.col2 <= $1)", "JOHN")
}
func TestStringCONCAT(t *testing.T) {
assertClauseSerialize(t, table3StrCol.CONCAT(table2ColStr), "(table3.col2 || table2.col_str)")
assertClauseSerialize(t, table3StrCol.CONCAT(String("JOHN")), "(table3.col2 || $1)", "JOHN")
}
func TestStringLIKE(t *testing.T) {
assertClauseSerialize(t, table3StrCol.LIKE(table2ColStr), "(table3.col2 LIKE table2.col_str)")
assertClauseSerialize(t, table3StrCol.LIKE(String("JOHN")), "(table3.col2 LIKE $1)", "JOHN")
}
func TestStringNOT_LIKE(t *testing.T) {
assertClauseSerialize(t, table3StrCol.NOT_LIKE(table2ColStr), "(table3.col2 NOT LIKE table2.col_str)")
assertClauseSerialize(t, table3StrCol.NOT_LIKE(String("JOHN")), "(table3.col2 NOT LIKE $1)", "JOHN")
}
func TestStringREGEXP_LIKE(t *testing.T) {
assertClauseSerialize(t, table3StrCol.REGEXP_LIKE(table2ColStr), "(table3.col2 REGEXP table2.col_str)")
assertClauseSerialize(t, table3StrCol.REGEXP_LIKE(String("JOHN"), true), "(table3.col2 REGEXP $1)", "JOHN")
}
func TestStringNOT_REGEXP_LIKE(t *testing.T) {
assertClauseSerialize(t, table3StrCol.NOT_REGEXP_LIKE(table2ColStr), "(table3.col2 NOT REGEXP table2.col_str)")
assertClauseSerialize(t, table3StrCol.NOT_REGEXP_LIKE(String("JOHN"), true), "(table3.col2 NOT REGEXP $1)", "JOHN")
}
func TestStringExp(t *testing.T) {
assertClauseSerialize(t, StringExp(table2ColFloat), "table2.col_float")
assertClauseSerialize(t, StringExp(table2ColFloat).NOT_LIKE(String("abc")), "(table2.col_float NOT LIKE $1)", "abc")
}

View File

@@ -0,0 +1,194 @@
package jet
import "github.com/go-jet/jet/v2/internal/utils/is"
// SerializerTable interface
type SerializerTable interface {
Serializer
Table
}
// Table interface
type Table interface {
columns() []Column
SchemaName() string
TableName() string
Alias() string
}
// NewTable creates new table with schema Name, table Name and list of columns
func NewTable(schemaName, name, alias string, columns ...ColumnExpression) SerializerTable {
t := tableImpl{
schemaName: schemaName,
name: name,
alias: alias,
columnList: columns,
}
columnTableName := name
if alias != "" {
columnTableName = alias
}
for _, c := range columns {
c.setTableName(columnTableName)
}
return &t
}
type tableImpl struct {
schemaName string
name string
alias string
columnList []ColumnExpression
}
func (t *tableImpl) SchemaName() string {
return t.schemaName
}
func (t *tableImpl) TableName() string {
return t.name
}
func (t *tableImpl) columns() []Column {
ret := []Column{}
for _, col := range t.columnList {
ret = append(ret, col)
}
return ret
}
func (t *tableImpl) Alias() string {
return t.alias
}
func (t *tableImpl) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) {
if t == nil {
panic("jet: tableImpl is nil")
}
// Use default schema if the schema name is not set
if len(t.schemaName) > 0 {
out.WriteIdentifier(t.schemaName)
out.WriteString(".")
}
out.WriteIdentifier(t.name)
if len(t.alias) > 0 {
out.WriteString("AS")
out.WriteIdentifier(t.alias)
}
}
// JoinType is type of table join
type JoinType int
// Table join types
const (
InnerJoin JoinType = iota
LeftJoin
RightJoin
FullJoin
CrossJoin
)
// Join expressions are pseudo readable tables.
type joinTableImpl struct {
lhs Serializer
rhs Serializer
joinType JoinType
onCondition BoolExpression
}
// JoinTable interface
type JoinTable SerializerTable
// NewJoinTable creates new join table
func NewJoinTable(lhs Serializer, rhs Serializer, joinType JoinType, onCondition BoolExpression) JoinTable {
joinTable := joinTableImpl{
lhs: lhs,
rhs: rhs,
joinType: joinType,
onCondition: onCondition,
}
return &joinTable
}
func (t *joinTableImpl) SchemaName() string {
if table, ok := t.lhs.(Table); ok {
return table.SchemaName()
}
return ""
}
func (t *joinTableImpl) TableName() string {
return ""
}
func (t *joinTableImpl) columns() []Column {
var ret []Column
if lhsTable, ok := t.lhs.(Table); ok {
ret = append(ret, lhsTable.columns()...)
}
if rhsTable, ok := t.rhs.(Table); ok {
ret = append(ret, rhsTable.columns()...)
}
return ret
}
func (t *joinTableImpl) Alias() string {
return ""
}
func (t *joinTableImpl) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) {
if t == nil {
panic("jet: Join table is nil. ")
}
if is.Nil(t.lhs) {
panic("jet: left hand side of join operation is nil table")
}
t.lhs.serialize(statement, out, FallTrough(options)...)
out.NewLine()
switch t.joinType {
case InnerJoin:
out.WriteString("INNER JOIN")
case LeftJoin:
out.WriteString("LEFT JOIN")
case RightJoin:
out.WriteString("RIGHT JOIN")
case FullJoin:
out.WriteString("FULL JOIN")
case CrossJoin:
out.WriteString("CROSS JOIN")
}
if is.Nil(t.rhs) {
panic("jet: right hand side of join operation is nil table")
}
t.rhs.serialize(statement, out)
if t.onCondition == nil && t.joinType != CrossJoin {
panic("jet: join condition is nil")
}
if t.onCondition != nil {
out.WriteString("ON")
t.onCondition.serialize(statement, out)
}
}

View File

@@ -0,0 +1,33 @@
package jet
import (
"github.com/stretchr/testify/require"
"testing"
)
func TestNewTable(t *testing.T) {
newTable := NewTable("schema", "table", "", IntegerColumn("intCol"))
require.Equal(t, newTable.SchemaName(), "schema")
require.Equal(t, newTable.TableName(), "table")
require.Equal(t, len(newTable.columns()), 1)
require.Equal(t, newTable.columns()[0].Name(), "intCol")
}
func TestNewJoinTable(t *testing.T) {
newTable1 := NewTable("schema", "table", "", IntegerColumn("intCol1"))
newTable2 := NewTable("schema", "table2", "", IntegerColumn("intCol2"))
joinTable := NewJoinTable(newTable1, newTable2, InnerJoin, IntegerColumn("intCol1").EQ(IntegerColumn("intCol2")))
assertClauseSerialize(t, joinTable, `schema.table
INNER JOIN schema.table2 ON ("intCol1" = "intCol2")`)
require.Equal(t, joinTable.SchemaName(), "schema")
require.Equal(t, joinTable.TableName(), "")
require.Equal(t, len(joinTable.columns()), 2)
require.Equal(t, joinTable.columns()[0].Name(), "intCol1")
require.Equal(t, joinTable.columns()[1].Name(), "intCol2")
}

View File

@@ -0,0 +1,91 @@
package jet
import (
"github.com/stretchr/testify/require"
"strconv"
"testing"
)
var defaultDialect = NewDialect(DialectParams{ // just for tests
AliasQuoteChar: '"',
IdentifierQuoteChar: '"',
ArgumentPlaceholder: func(ord int) string {
return "$" + strconv.Itoa(ord)
},
})
var (
table1Col1 = IntegerColumn("col1")
table1ColInt = IntegerColumn("col_int")
table1ColFloat = FloatColumn("col_float")
table1Col3 = IntegerColumn("col3")
table1ColTime = TimeColumn("col_time")
table1ColTimez = TimezColumn("col_timez")
table1ColTimestamp = TimestampColumn("col_timestamp")
table1ColTimestampz = TimestampzColumn("col_timestampz")
table1ColBool = BoolColumn("col_bool")
table1ColDate = DateColumn("col_date")
table1ColRange = RangeColumn[Int8Expression]("col_range")
)
var table1 = NewTable("db", "table1", "", table1Col1, table1ColInt, table1ColFloat, table1Col3, table1ColTime, table1ColTimez, table1ColBool, table1ColDate, table1ColRange, table1ColTimestamp, table1ColTimestampz)
var (
table2Col3 = IntegerColumn("col3")
table2Col4 = IntegerColumn("col4")
table2ColInt = IntegerColumn("col_int")
table2ColFloat = FloatColumn("col_float")
table2ColStr = StringColumn("col_str")
table2ColBool = BoolColumn("col_bool")
table2ColTime = TimeColumn("col_time")
table2ColTimez = TimezColumn("col_timez")
table2ColTimestamp = TimestampColumn("col_timestamp")
table2ColTimestampz = TimestampzColumn("col_timestampz")
table2ColDate = DateColumn("col_date")
table2ColRange = RangeColumn[Int8Expression]("col_range")
)
var table2 = NewTable("db", "table2", "", table2Col3, table2Col4, table2ColInt, table2ColFloat, table2ColStr, table2ColBool, table2ColTime, table2ColTimez, table2ColDate, table2ColRange, table2ColTimestamp, table2ColTimestampz)
var (
table3Col1 = IntegerColumn("col1")
table3ColInt = IntegerColumn("col_int")
table3StrCol = StringColumn("col2")
)
var table3 = NewTable("db", "table3", "", table3Col1, table3ColInt, table3StrCol)
func assertClauseSerialize(t *testing.T, clause Serializer, query string, args ...interface{}) {
out := SQLBuilder{Dialect: defaultDialect}
clause.serialize(SelectStatementType, &out)
//fmt.Println(out.Buff.String())
require.Equal(t, out.Buff.String(), query)
require.Equal(t, out.Args, args)
}
func assertClauseSerializeErr(t *testing.T, clause Serializer, errString string) {
defer func() {
r := recover()
require.Equal(t, r, errString)
}()
out := SQLBuilder{Dialect: defaultDialect}
clause.serialize(SelectStatementType, &out)
}
func assertClauseDebugSerialize(t *testing.T, clause Serializer, query string, args ...interface{}) {
out := SQLBuilder{Dialect: defaultDialect, Debug: true}
clause.serialize(SelectStatementType, &out)
//fmt.Println(out.Buff.String())
require.Equal(t, out.Buff.String(), query)
require.Equal(t, out.Args, args)
}
func assertProjectionSerialize(t *testing.T, projection Projection, query string, args ...interface{}) {
out := SQLBuilder{Dialect: defaultDialect}
projection.serializeForProjection(SelectStatementType, &out)
require.Equal(t, out.Buff.String(), query)
require.Equal(t, out.Args, args)
}

View File

@@ -0,0 +1,93 @@
package jet
// TimeExpression interface
type TimeExpression interface {
Expression
EQ(rhs TimeExpression) BoolExpression
NOT_EQ(rhs TimeExpression) BoolExpression
IS_DISTINCT_FROM(rhs TimeExpression) BoolExpression
IS_NOT_DISTINCT_FROM(rhs TimeExpression) BoolExpression
LT(rhs TimeExpression) BoolExpression
LT_EQ(rhs TimeExpression) BoolExpression
GT(rhs TimeExpression) BoolExpression
GT_EQ(rhs TimeExpression) BoolExpression
BETWEEN(min, max TimeExpression) BoolExpression
NOT_BETWEEN(min, max TimeExpression) BoolExpression
ADD(rhs Interval) TimeExpression
SUB(rhs Interval) TimeExpression
}
type timeInterfaceImpl struct {
parent TimeExpression
}
func (t *timeInterfaceImpl) EQ(rhs TimeExpression) BoolExpression {
return Eq(t.parent, rhs)
}
func (t *timeInterfaceImpl) NOT_EQ(rhs TimeExpression) BoolExpression {
return NotEq(t.parent, rhs)
}
func (t *timeInterfaceImpl) IS_DISTINCT_FROM(rhs TimeExpression) BoolExpression {
return IsDistinctFrom(t.parent, rhs)
}
func (t *timeInterfaceImpl) IS_NOT_DISTINCT_FROM(rhs TimeExpression) BoolExpression {
return IsNotDistinctFrom(t.parent, rhs)
}
func (t *timeInterfaceImpl) LT(rhs TimeExpression) BoolExpression {
return Lt(t.parent, rhs)
}
func (t *timeInterfaceImpl) LT_EQ(rhs TimeExpression) BoolExpression {
return LtEq(t.parent, rhs)
}
func (t *timeInterfaceImpl) GT(rhs TimeExpression) BoolExpression {
return Gt(t.parent, rhs)
}
func (t *timeInterfaceImpl) GT_EQ(rhs TimeExpression) BoolExpression {
return GtEq(t.parent, rhs)
}
func (t *timeInterfaceImpl) BETWEEN(min, max TimeExpression) BoolExpression {
return NewBetweenOperatorExpression(t.parent, min, max, false)
}
func (t *timeInterfaceImpl) NOT_BETWEEN(min, max TimeExpression) BoolExpression {
return NewBetweenOperatorExpression(t.parent, min, max, true)
}
func (t *timeInterfaceImpl) ADD(rhs Interval) TimeExpression {
return TimeExp(Add(t.parent, rhs))
}
func (t *timeInterfaceImpl) SUB(rhs Interval) TimeExpression {
return TimeExp(Sub(t.parent, rhs))
}
//---------------------------------------------------//
type timeExpressionWrapper struct {
timeInterfaceImpl
Expression
}
func newTimeExpressionWrap(expression Expression) TimeExpression {
timeExpressionWrap := timeExpressionWrapper{Expression: expression}
timeExpressionWrap.timeInterfaceImpl.parent = &timeExpressionWrap
return &timeExpressionWrap
}
// TimeExp is time expression wrapper around arbitrary expression.
// Allows go compiler to see any expression as time expression.
// Does not add sql cast to generated sql builder output.
func TimeExp(expression Expression) TimeExpression {
return newTimeExpressionWrap(expression)
}

View File

@@ -0,0 +1,62 @@
package jet
import (
"testing"
"time"
)
var timeVar = Time(10, 20, 0, 0)
func TestTimeExpressionEQ(t *testing.T) {
assertClauseSerialize(t, table1ColTime.EQ(table2ColTime), "(table1.col_time = table2.col_time)")
assertClauseSerialize(t, table1ColTime.EQ(timeVar), "(table1.col_time = $1)", "10:20:00")
}
func TestTimeExpressionNOT_EQ(t *testing.T) {
assertClauseSerialize(t, table1ColTime.NOT_EQ(table2ColTime), "(table1.col_time != table2.col_time)")
assertClauseSerialize(t, table1ColTime.NOT_EQ(timeVar), "(table1.col_time != $1)", "10:20:00")
}
func TestTimeExpressionIS_DISTINCT_FROM(t *testing.T) {
assertClauseSerialize(t, table1ColTime.IS_DISTINCT_FROM(table2ColTime), "(table1.col_time IS DISTINCT FROM table2.col_time)")
assertClauseSerialize(t, table1ColTime.IS_DISTINCT_FROM(timeVar), "(table1.col_time IS DISTINCT FROM $1)", "10:20:00")
}
func TestTimeExpressionIS_NOT_DISTINCT_FROM(t *testing.T) {
assertClauseSerialize(t, table1ColTime.IS_NOT_DISTINCT_FROM(table2ColTime), "(table1.col_time IS NOT DISTINCT FROM table2.col_time)")
assertClauseSerialize(t, table1ColTime.IS_NOT_DISTINCT_FROM(timeVar), "(table1.col_time IS NOT DISTINCT FROM $1)", "10:20:00")
}
func TestTimeExpressionLT(t *testing.T) {
assertClauseSerialize(t, table1ColTime.LT(table2ColTime), "(table1.col_time < table2.col_time)")
assertClauseSerialize(t, table1ColTime.LT(timeVar), "(table1.col_time < $1)", "10:20:00")
}
func TestTimeExpressionLT_EQ(t *testing.T) {
assertClauseSerialize(t, table1ColTime.LT_EQ(table2ColTime), "(table1.col_time <= table2.col_time)")
assertClauseSerialize(t, table1ColTime.LT_EQ(timeVar), "(table1.col_time <= $1)", "10:20:00")
}
func TestTimeExpressionGT(t *testing.T) {
assertClauseSerialize(t, table1ColTime.GT(table2ColTime), "(table1.col_time > table2.col_time)")
assertClauseSerialize(t, table1ColTime.GT(timeVar), "(table1.col_time > $1)", "10:20:00")
}
func TestTimeExpressionGT_EQ(t *testing.T) {
assertClauseSerialize(t, table1ColTime.GT_EQ(table2ColTime), "(table1.col_time >= table2.col_time)")
assertClauseSerialize(t, table1ColTime.GT_EQ(timeVar), "(table1.col_time >= $1)", "10:20:00")
}
func TestTimeExp(t *testing.T) {
assertClauseSerialize(t, TimeExp(table1ColFloat), "table1.col_float")
assertClauseSerialize(t, TimeExp(table1ColFloat).LT(Time(1, 1, 1, 1*time.Millisecond)),
"(table1.col_float < $1)", string("01:01:01.001"))
}
func TestTimeArithmetic(t *testing.T) {
time := Time(10, 20, 3)
assertClauseDebugSerialize(t, table1ColTime.ADD(NewInterval(String("1 HOUR"))).EQ(time),
"((table1.col_time + INTERVAL '1 HOUR') = '10:20:03')")
assertClauseDebugSerialize(t, table1ColTime.SUB(NewInterval(String("1 HOUR"))).EQ(time),
"((table1.col_time - INTERVAL '1 HOUR') = '10:20:03')")
}

View File

@@ -0,0 +1,93 @@
package jet
// TimestampExpression interface
type TimestampExpression interface {
Expression
EQ(rhs TimestampExpression) BoolExpression
NOT_EQ(rhs TimestampExpression) BoolExpression
IS_DISTINCT_FROM(rhs TimestampExpression) BoolExpression
IS_NOT_DISTINCT_FROM(rhs TimestampExpression) BoolExpression
LT(rhs TimestampExpression) BoolExpression
LT_EQ(rhs TimestampExpression) BoolExpression
GT(rhs TimestampExpression) BoolExpression
GT_EQ(rhs TimestampExpression) BoolExpression
BETWEEN(min, max TimestampExpression) BoolExpression
NOT_BETWEEN(min, max TimestampExpression) BoolExpression
ADD(rhs Interval) TimestampExpression
SUB(rhs Interval) TimestampExpression
}
type timestampInterfaceImpl struct {
parent TimestampExpression
}
func (t *timestampInterfaceImpl) EQ(rhs TimestampExpression) BoolExpression {
return Eq(t.parent, rhs)
}
func (t *timestampInterfaceImpl) NOT_EQ(rhs TimestampExpression) BoolExpression {
return NotEq(t.parent, rhs)
}
func (t *timestampInterfaceImpl) IS_DISTINCT_FROM(rhs TimestampExpression) BoolExpression {
return IsDistinctFrom(t.parent, rhs)
}
func (t *timestampInterfaceImpl) IS_NOT_DISTINCT_FROM(rhs TimestampExpression) BoolExpression {
return IsNotDistinctFrom(t.parent, rhs)
}
func (t *timestampInterfaceImpl) LT(rhs TimestampExpression) BoolExpression {
return Lt(t.parent, rhs)
}
func (t *timestampInterfaceImpl) LT_EQ(rhs TimestampExpression) BoolExpression {
return LtEq(t.parent, rhs)
}
func (t *timestampInterfaceImpl) GT(rhs TimestampExpression) BoolExpression {
return Gt(t.parent, rhs)
}
func (t *timestampInterfaceImpl) GT_EQ(rhs TimestampExpression) BoolExpression {
return GtEq(t.parent, rhs)
}
func (t *timestampInterfaceImpl) BETWEEN(min, max TimestampExpression) BoolExpression {
return NewBetweenOperatorExpression(t.parent, min, max, false)
}
func (t *timestampInterfaceImpl) NOT_BETWEEN(min, max TimestampExpression) BoolExpression {
return NewBetweenOperatorExpression(t.parent, min, max, true)
}
func (t *timestampInterfaceImpl) ADD(rhs Interval) TimestampExpression {
return TimestampExp(Add(t.parent, rhs))
}
func (t *timestampInterfaceImpl) SUB(rhs Interval) TimestampExpression {
return TimestampExp(Sub(t.parent, rhs))
}
//-------------------------------------------------
type timestampExpressionWrapper struct {
timestampInterfaceImpl
Expression
}
func newTimestampExpressionWrap(expression Expression) TimestampExpression {
timestampExpressionWrap := timestampExpressionWrapper{Expression: expression}
timestampExpressionWrap.timestampInterfaceImpl.parent = &timestampExpressionWrap
return &timestampExpressionWrap
}
// TimestampExp is timestamp expression wrapper around arbitrary expression.
// Allows go compiler to see any expression as timestamp expression.
// Does not add sql cast to generated sql builder output.
func TimestampExp(expression Expression) TimestampExpression {
return newTimestampExpressionWrap(expression)
}

View File

@@ -0,0 +1,63 @@
package jet
import (
"testing"
"time"
)
var timestamp = Timestamp(2000, 1, 31, 10, 20, 0, 3*time.Millisecond)
func TestTimestampExpressionEQ(t *testing.T) {
assertClauseSerialize(t, table1ColTimestamp.EQ(table2ColTimestamp), "(table1.col_timestamp = table2.col_timestamp)")
assertClauseSerialize(t, table1ColTimestamp.EQ(timestamp),
"(table1.col_timestamp = $1)", "2000-01-31 10:20:00.003")
}
func TestTimestampExpressionNOT_EQ(t *testing.T) {
assertClauseSerialize(t, table1ColTimestamp.NOT_EQ(table2ColTimestamp), "(table1.col_timestamp != table2.col_timestamp)")
assertClauseSerialize(t, table1ColTimestamp.NOT_EQ(timestamp), "(table1.col_timestamp != $1)", "2000-01-31 10:20:00.003")
}
func TestTimestampExpressionIS_DISTINCT_FROM(t *testing.T) {
assertClauseSerialize(t, table1ColTimestamp.IS_DISTINCT_FROM(table2ColTimestamp), "(table1.col_timestamp IS DISTINCT FROM table2.col_timestamp)")
assertClauseSerialize(t, table1ColTimestamp.IS_DISTINCT_FROM(timestamp), "(table1.col_timestamp IS DISTINCT FROM $1)", "2000-01-31 10:20:00.003")
}
func TestTimestampExpressionIS_NOT_DISTINCT_FROM(t *testing.T) {
assertClauseSerialize(t, table1ColTimestamp.IS_NOT_DISTINCT_FROM(table2ColTimestamp), "(table1.col_timestamp IS NOT DISTINCT FROM table2.col_timestamp)")
assertClauseSerialize(t, table1ColTimestamp.IS_NOT_DISTINCT_FROM(timestamp), "(table1.col_timestamp IS NOT DISTINCT FROM $1)", "2000-01-31 10:20:00.003")
}
func TestTimestampExpressionLT(t *testing.T) {
assertClauseSerialize(t, table1ColTimestamp.LT(table2ColTimestamp), "(table1.col_timestamp < table2.col_timestamp)")
assertClauseSerialize(t, table1ColTimestamp.LT(timestamp), "(table1.col_timestamp < $1)", "2000-01-31 10:20:00.003")
}
func TestTimestampExpressionLT_EQ(t *testing.T) {
assertClauseSerialize(t, table1ColTimestamp.LT_EQ(table2ColTimestamp), "(table1.col_timestamp <= table2.col_timestamp)")
assertClauseSerialize(t, table1ColTimestamp.LT_EQ(timestamp), "(table1.col_timestamp <= $1)", "2000-01-31 10:20:00.003")
}
func TestTimestampExpressionGT(t *testing.T) {
assertClauseSerialize(t, table1ColTimestamp.GT(table2ColTimestamp), "(table1.col_timestamp > table2.col_timestamp)")
assertClauseSerialize(t, table1ColTimestamp.GT(timestamp), "(table1.col_timestamp > $1)", "2000-01-31 10:20:00.003")
}
func TestTimestampExpressionGT_EQ(t *testing.T) {
assertClauseSerialize(t, table1ColTimestamp.GT_EQ(table2ColTimestamp), "(table1.col_timestamp >= table2.col_timestamp)")
assertClauseSerialize(t, table1ColTimestamp.GT_EQ(timestamp), "(table1.col_timestamp >= $1)", "2000-01-31 10:20:00.003")
}
func TestTimestampExp(t *testing.T) {
assertClauseSerialize(t, TimestampExp(table1ColFloat), "table1.col_float")
assertClauseSerialize(t, TimestampExp(table1ColFloat).LT(timestamp),
"(table1.col_float < $1)", "2000-01-31 10:20:00.003")
}
func TestTimestampArithmetic(t *testing.T) {
timestamp := Timestamp(2000, 1, 1, 0, 0, 0)
assertClauseDebugSerialize(t, table1ColTimestamp.ADD(NewInterval(String("1 HOUR"))).EQ(timestamp),
"((table1.col_timestamp + INTERVAL '1 HOUR') = '2000-01-01 00:00:00')")
assertClauseDebugSerialize(t, table1ColTimestamp.SUB(NewInterval(String("1 HOUR"))).EQ(timestamp),
"((table1.col_timestamp - INTERVAL '1 HOUR') = '2000-01-01 00:00:00')")
}

View File

@@ -0,0 +1,93 @@
package jet
// TimestampzExpression interface
type TimestampzExpression interface {
Expression
EQ(rhs TimestampzExpression) BoolExpression
NOT_EQ(rhs TimestampzExpression) BoolExpression
IS_DISTINCT_FROM(rhs TimestampzExpression) BoolExpression
IS_NOT_DISTINCT_FROM(rhs TimestampzExpression) BoolExpression
LT(rhs TimestampzExpression) BoolExpression
LT_EQ(rhs TimestampzExpression) BoolExpression
GT(rhs TimestampzExpression) BoolExpression
GT_EQ(rhs TimestampzExpression) BoolExpression
BETWEEN(min, max TimestampzExpression) BoolExpression
NOT_BETWEEN(min, max TimestampzExpression) BoolExpression
ADD(rhs Interval) TimestampzExpression
SUB(rhs Interval) TimestampzExpression
}
type timestampzInterfaceImpl struct {
parent TimestampzExpression
}
func (t *timestampzInterfaceImpl) EQ(rhs TimestampzExpression) BoolExpression {
return Eq(t.parent, rhs)
}
func (t *timestampzInterfaceImpl) NOT_EQ(rhs TimestampzExpression) BoolExpression {
return NotEq(t.parent, rhs)
}
func (t *timestampzInterfaceImpl) IS_DISTINCT_FROM(rhs TimestampzExpression) BoolExpression {
return IsDistinctFrom(t.parent, rhs)
}
func (t *timestampzInterfaceImpl) IS_NOT_DISTINCT_FROM(rhs TimestampzExpression) BoolExpression {
return IsNotDistinctFrom(t.parent, rhs)
}
func (t *timestampzInterfaceImpl) LT(rhs TimestampzExpression) BoolExpression {
return Lt(t.parent, rhs)
}
func (t *timestampzInterfaceImpl) LT_EQ(rhs TimestampzExpression) BoolExpression {
return LtEq(t.parent, rhs)
}
func (t *timestampzInterfaceImpl) GT(rhs TimestampzExpression) BoolExpression {
return Gt(t.parent, rhs)
}
func (t *timestampzInterfaceImpl) GT_EQ(rhs TimestampzExpression) BoolExpression {
return GtEq(t.parent, rhs)
}
func (t *timestampzInterfaceImpl) BETWEEN(min, max TimestampzExpression) BoolExpression {
return NewBetweenOperatorExpression(t.parent, min, max, false)
}
func (t *timestampzInterfaceImpl) NOT_BETWEEN(min, max TimestampzExpression) BoolExpression {
return NewBetweenOperatorExpression(t.parent, min, max, true)
}
func (t *timestampzInterfaceImpl) ADD(rhs Interval) TimestampzExpression {
return TimestampzExp(Add(t.parent, rhs))
}
func (t *timestampzInterfaceImpl) SUB(rhs Interval) TimestampzExpression {
return TimestampzExp(Sub(t.parent, rhs))
}
//-------------------------------------------------
type timestampzExpressionWrapper struct {
timestampzInterfaceImpl
Expression
}
func newTimestampzExpressionWrap(expression Expression) TimestampzExpression {
timestampzExpressionWrap := timestampzExpressionWrapper{Expression: expression}
timestampzExpressionWrap.timestampzInterfaceImpl.parent = &timestampzExpressionWrap
return &timestampzExpressionWrap
}
// TimestampzExp is timestamp with time zone expression wrapper around arbitrary expression.
// Allows go compiler to see any expression as timestamp with time zone expression.
// Does not add sql cast to generated sql builder output.
func TimestampzExp(expression Expression) TimestampzExpression {
return newTimestampzExpressionWrap(expression)
}

View File

@@ -0,0 +1,63 @@
package jet
import (
"testing"
"time"
)
var timestampz = Timestampz(2000, 1, 31, 10, 20, 5, 23*time.Microsecond, "+200")
func TestTimestampzExpressionEQ(t *testing.T) {
assertClauseSerialize(t, table1ColTimestampz.EQ(table2ColTimestampz), "(table1.col_timestampz = table2.col_timestampz)")
assertClauseSerialize(t, table1ColTimestampz.EQ(timestampz),
"(table1.col_timestampz = $1)", "2000-01-31 10:20:05.000023 +200")
}
func TestTimestampzExpressionNOT_EQ(t *testing.T) {
assertClauseSerialize(t, table1ColTimestampz.NOT_EQ(table2ColTimestampz), "(table1.col_timestampz != table2.col_timestampz)")
assertClauseSerialize(t, table1ColTimestampz.NOT_EQ(timestampz), "(table1.col_timestampz != $1)", "2000-01-31 10:20:05.000023 +200")
}
func TestTimestampzExpressionIS_DISTINCT_FROM(t *testing.T) {
assertClauseSerialize(t, table1ColTimestampz.IS_DISTINCT_FROM(table2ColTimestampz), "(table1.col_timestampz IS DISTINCT FROM table2.col_timestampz)")
assertClauseSerialize(t, table1ColTimestampz.IS_DISTINCT_FROM(timestampz), "(table1.col_timestampz IS DISTINCT FROM $1)", "2000-01-31 10:20:05.000023 +200")
}
func TestTimestampzExpressionIS_NOT_DISTINCT_FROM(t *testing.T) {
assertClauseSerialize(t, table1ColTimestampz.IS_NOT_DISTINCT_FROM(table2ColTimestampz), "(table1.col_timestampz IS NOT DISTINCT FROM table2.col_timestampz)")
assertClauseSerialize(t, table1ColTimestampz.IS_NOT_DISTINCT_FROM(timestampz), "(table1.col_timestampz IS NOT DISTINCT FROM $1)", "2000-01-31 10:20:05.000023 +200")
}
func TestTimestampzExpressionLT(t *testing.T) {
assertClauseSerialize(t, table1ColTimestampz.LT(table2ColTimestampz), "(table1.col_timestampz < table2.col_timestampz)")
assertClauseSerialize(t, table1ColTimestampz.LT(timestampz), "(table1.col_timestampz < $1)", "2000-01-31 10:20:05.000023 +200")
}
func TestTimestampzExpressionLT_EQ(t *testing.T) {
assertClauseSerialize(t, table1ColTimestampz.LT_EQ(table2ColTimestampz), "(table1.col_timestampz <= table2.col_timestampz)")
assertClauseSerialize(t, table1ColTimestampz.LT_EQ(timestampz), "(table1.col_timestampz <= $1)", "2000-01-31 10:20:05.000023 +200")
}
func TestTimestampzExpressionGT(t *testing.T) {
assertClauseSerialize(t, table1ColTimestampz.GT(table2ColTimestampz), "(table1.col_timestampz > table2.col_timestampz)")
assertClauseSerialize(t, table1ColTimestampz.GT(timestampz), "(table1.col_timestampz > $1)", "2000-01-31 10:20:05.000023 +200")
}
func TestTimestampzExpressionGT_EQ(t *testing.T) {
assertClauseSerialize(t, table1ColTimestampz.GT_EQ(table2ColTimestampz), "(table1.col_timestampz >= table2.col_timestampz)")
assertClauseSerialize(t, table1ColTimestampz.GT_EQ(timestampz), "(table1.col_timestampz >= $1)", "2000-01-31 10:20:05.000023 +200")
}
func TestTimestampzExp(t *testing.T) {
assertClauseSerialize(t, TimestampzExp(table1ColFloat), "table1.col_float")
assertClauseSerialize(t, TimestampzExp(table1ColFloat).LT(timestampz),
"(table1.col_float < $1)", "2000-01-31 10:20:05.000023 +200")
}
func TestTimestampzArithmetic(t *testing.T) {
timestampz := Timestampz(2000, 1, 1, 0, 0, 0, 100, "UTC")
assertClauseDebugSerialize(t, table1ColTimestampz.ADD(NewInterval(String("1 HOUR"))).EQ(timestampz),
"((table1.col_timestampz + INTERVAL '1 HOUR') = '2000-01-01 00:00:00.0000001 UTC')")
assertClauseDebugSerialize(t, table1ColTimestampz.SUB(NewInterval(String("1 HOUR"))).EQ(timestampz),
"((table1.col_timestampz - INTERVAL '1 HOUR') = '2000-01-01 00:00:00.0000001 UTC')")
}

View File

@@ -0,0 +1,93 @@
package jet
// TimezExpression interface for 'time with time zone' types
type TimezExpression interface {
Expression
EQ(rhs TimezExpression) BoolExpression
NOT_EQ(rhs TimezExpression) BoolExpression
IS_DISTINCT_FROM(rhs TimezExpression) BoolExpression
IS_NOT_DISTINCT_FROM(rhs TimezExpression) BoolExpression
LT(rhs TimezExpression) BoolExpression
LT_EQ(rhs TimezExpression) BoolExpression
GT(rhs TimezExpression) BoolExpression
GT_EQ(rhs TimezExpression) BoolExpression
BETWEEN(min, max TimezExpression) BoolExpression
NOT_BETWEEN(min, max TimezExpression) BoolExpression
ADD(rhs Interval) TimezExpression
SUB(rhs Interval) TimezExpression
}
type timezInterfaceImpl struct {
parent TimezExpression
}
func (t *timezInterfaceImpl) EQ(rhs TimezExpression) BoolExpression {
return Eq(t.parent, rhs)
}
func (t *timezInterfaceImpl) NOT_EQ(rhs TimezExpression) BoolExpression {
return NotEq(t.parent, rhs)
}
func (t *timezInterfaceImpl) IS_DISTINCT_FROM(rhs TimezExpression) BoolExpression {
return IsDistinctFrom(t.parent, rhs)
}
func (t *timezInterfaceImpl) IS_NOT_DISTINCT_FROM(rhs TimezExpression) BoolExpression {
return IsNotDistinctFrom(t.parent, rhs)
}
func (t *timezInterfaceImpl) LT(rhs TimezExpression) BoolExpression {
return Lt(t.parent, rhs)
}
func (t *timezInterfaceImpl) LT_EQ(rhs TimezExpression) BoolExpression {
return LtEq(t.parent, rhs)
}
func (t *timezInterfaceImpl) GT(rhs TimezExpression) BoolExpression {
return Gt(t.parent, rhs)
}
func (t *timezInterfaceImpl) GT_EQ(rhs TimezExpression) BoolExpression {
return GtEq(t.parent, rhs)
}
func (t *timezInterfaceImpl) BETWEEN(min, max TimezExpression) BoolExpression {
return NewBetweenOperatorExpression(t.parent, min, max, false)
}
func (t *timezInterfaceImpl) NOT_BETWEEN(min, max TimezExpression) BoolExpression {
return NewBetweenOperatorExpression(t.parent, min, max, true)
}
func (t *timezInterfaceImpl) ADD(rhs Interval) TimezExpression {
return TimezExp(Add(t.parent, rhs))
}
func (t *timezInterfaceImpl) SUB(rhs Interval) TimezExpression {
return TimezExp(Sub(t.parent, rhs))
}
//---------------------------------------------------//
type timezExpressionWrapper struct {
timezInterfaceImpl
Expression
}
func newTimezExpressionWrap(expression Expression) TimezExpression {
timezExpressionWrap := timezExpressionWrapper{Expression: expression}
timezExpressionWrap.timezInterfaceImpl.parent = &timezExpressionWrap
return &timezExpressionWrap
}
// TimezExp is time with time zone expression wrapper around arbitrary expression.
// Allows go compiler to see any expression as time with time zone expression.
// Does not add sql cast to generated sql builder output.
func TimezExp(expression Expression) TimezExpression {
return newTimezExpressionWrap(expression)
}

View File

@@ -0,0 +1,61 @@
package jet
import (
"testing"
)
var timezVar = Timez(10, 20, 0, 0, "+4:00")
func TestTimezExpressionEQ(t *testing.T) {
assertClauseSerialize(t, table1ColTimez.EQ(table2ColTimez), "(table1.col_timez = table2.col_timez)")
assertClauseSerialize(t, table1ColTimez.EQ(timezVar), "(table1.col_timez = $1)", "10:20:00 +4:00")
}
func TestTimezExpressionNOT_EQ(t *testing.T) {
assertClauseSerialize(t, table1ColTimez.NOT_EQ(table2ColTimez), "(table1.col_timez != table2.col_timez)")
assertClauseSerialize(t, table1ColTimez.NOT_EQ(timezVar), "(table1.col_timez != $1)", "10:20:00 +4:00")
}
func TestTimezExpressionIS_DISTINCT_FROM(t *testing.T) {
assertClauseSerialize(t, table1ColTimez.IS_DISTINCT_FROM(table2ColTimez), "(table1.col_timez IS DISTINCT FROM table2.col_timez)")
assertClauseSerialize(t, table1ColTimez.IS_DISTINCT_FROM(timezVar), "(table1.col_timez IS DISTINCT FROM $1)", "10:20:00 +4:00")
}
func TestTimezExpressionIS_NOT_DISTINCT_FROM(t *testing.T) {
assertClauseSerialize(t, table1ColTimez.IS_NOT_DISTINCT_FROM(table2ColTimez), "(table1.col_timez IS NOT DISTINCT FROM table2.col_timez)")
assertClauseSerialize(t, table1ColTimez.IS_NOT_DISTINCT_FROM(timezVar), "(table1.col_timez IS NOT DISTINCT FROM $1)", "10:20:00 +4:00")
}
func TestTimezExpressionLT(t *testing.T) {
assertClauseSerialize(t, table1ColTimez.LT(table2ColTimez), "(table1.col_timez < table2.col_timez)")
assertClauseSerialize(t, table1ColTimez.LT(timezVar), "(table1.col_timez < $1)", "10:20:00 +4:00")
}
func TestTimezExpressionLT_EQ(t *testing.T) {
assertClauseSerialize(t, table1ColTimez.LT_EQ(table2ColTimez), "(table1.col_timez <= table2.col_timez)")
assertClauseSerialize(t, table1ColTimez.LT_EQ(timezVar), "(table1.col_timez <= $1)", "10:20:00 +4:00")
}
func TestTimezExpressionGT(t *testing.T) {
assertClauseSerialize(t, table1ColTimez.GT(table2ColTimez), "(table1.col_timez > table2.col_timez)")
assertClauseSerialize(t, table1ColTimez.GT(timezVar), "(table1.col_timez > $1)", "10:20:00 +4:00")
}
func TestTimezExpressionGT_EQ(t *testing.T) {
assertClauseSerialize(t, table1ColTimez.GT_EQ(table2ColTimez), "(table1.col_timez >= table2.col_timez)")
assertClauseSerialize(t, table1ColTimez.GT_EQ(timezVar), "(table1.col_timez >= $1)", "10:20:00 +4:00")
}
func TestTimezExp(t *testing.T) {
assertClauseSerialize(t, TimezExp(table1ColFloat), "table1.col_float")
assertClauseSerialize(t, TimezExp(table1ColFloat).LT(Timez(1, 1, 1, 1, "+4:00")),
"(table1.col_float < $1)", string("01:01:01.000000001 +4:00"))
}
func TestTimezArithmetic(t *testing.T) {
timez := Timez(0, 0, 0, 100, "UTC")
assertClauseDebugSerialize(t, table1ColTimez.ADD(NewInterval(String("1 HOUR"))).EQ(timez),
"((table1.col_timez + INTERVAL '1 HOUR') = '00:00:00.0000001 UTC')")
assertClauseDebugSerialize(t, table1ColTimez.SUB(NewInterval(String("1 HOUR"))).EQ(timez),
"((table1.col_timez - INTERVAL '1 HOUR') = '00:00:00.0000001 UTC')")
}

View File

@@ -0,0 +1,293 @@
package jet
import (
"reflect"
"strings"
"github.com/go-jet/jet/v2/internal/utils/dbidentifier"
"github.com/go-jet/jet/v2/internal/utils/must"
)
// SerializeClauseList func
func SerializeClauseList(statement StatementType, clauses []Serializer, out *SQLBuilder) {
for i, c := range clauses {
if i > 0 {
out.WriteString(", ")
}
if c == nil {
panic("jet: nil clause")
}
c.serialize(statement, out)
}
}
func serializeExpressionList(
statement StatementType,
expressions []Expression,
separator string,
out *SQLBuilder,
options ...SerializeOption) {
for i, expression := range expressions {
if i > 0 {
out.WriteString(separator)
}
if expression != nil {
expression.serialize(statement, out, options...)
}
}
}
// SerializeProjectionList func
func SerializeProjectionList(statement StatementType, projections []Projection, out *SQLBuilder) {
for i, col := range projections {
if i > 0 {
out.WriteString(",")
out.NewLine()
}
if col == nil {
panic("jet: Projection is nil")
}
col.serializeForProjection(statement, out)
}
}
// SerializeColumnNames func
func SerializeColumnNames(columns []Column, out *SQLBuilder) {
for i, col := range columns {
if i > 0 {
out.WriteString(", ")
}
if col == nil {
panic("jet: nil column in columns list")
}
out.WriteIdentifier(col.Name())
}
}
// SerializeColumnExpressions func
func SerializeColumnExpressions(columns []ColumnExpression, statementType StatementType,
out *SQLBuilder, options ...SerializeOption) {
for i, col := range columns {
if i > 0 {
out.WriteString(", ")
}
if col == nil {
panic("jet: nil column in columns list")
}
col.serialize(statementType, out, options...)
}
}
// SerializeColumnExpressionNames func
func SerializeColumnExpressionNames(columns []ColumnExpression, out *SQLBuilder) {
for i, col := range columns {
if i > 0 {
out.WriteString(", ")
}
if col == nil {
panic("jet: nil column in columns list")
}
out.WriteIdentifier(col.Name())
}
}
// ExpressionListToSerializerList converts list of expressions to list of serializers
func ExpressionListToSerializerList(expressions []Expression) []Serializer {
var ret []Serializer
for _, expr := range expressions {
ret = append(ret, expr)
}
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
for _, column := range columns {
ret = append(ret, column)
}
return ret
}
// ToSerializerValue creates Serializer type from the value
func ToSerializerValue(value interface{}) Serializer {
if clause, ok := value.(Serializer); ok {
return clause
}
return literal(value)
}
// UnwindRowFromModel func
func UnwindRowFromModel(columns []Column, data interface{}) []Serializer {
structValue := reflect.Indirect(reflect.ValueOf(data))
row := []Serializer{}
must.ValueBeOfTypeKind(structValue, reflect.Struct, "jet: data has to be a struct")
for _, column := range columns {
columnName := column.Name()
structFieldName := dbidentifier.ToGoIdentifier(columnName)
structField := structValue.FieldByName(structFieldName)
if !structField.IsValid() {
panic("missing struct field for column : " + columnName)
}
var field interface{}
if structField.Kind() == reflect.Ptr && structField.IsNil() {
field = nil
} else {
field = reflect.Indirect(structField).Interface()
}
row = append(row, literal(field))
}
return row
}
// UnwindRowsFromModels func
func UnwindRowsFromModels(columns []Column, data interface{}) [][]Serializer {
sliceValue := reflect.Indirect(reflect.ValueOf(data))
must.ValueBeOfTypeKind(sliceValue, reflect.Slice, "jet: data has to be a slice.")
rows := [][]Serializer{}
for i := 0; i < sliceValue.Len(); i++ {
structValue := sliceValue.Index(i)
rows = append(rows, UnwindRowFromModel(columns, structValue.Interface()))
}
return rows
}
// UnwindRowFromValues func
func UnwindRowFromValues(value interface{}, values []interface{}) []Serializer {
row := []Serializer{}
allValues := append([]interface{}{value}, values...)
for _, val := range allValues {
row = append(row, ToSerializerValue(val))
}
return row
}
// UnwindColumns func
func UnwindColumns(column1 Column, columns ...Column) []Column {
columnList := []Column{}
if val, ok := column1.(ColumnList); ok {
for _, col := range val {
columnList = append(columnList, col)
}
columnList = append(columnList, columns...)
} else {
columnList = append(columnList, column1)
columnList = append(columnList, columns...)
}
return columnList
}
// UnwidColumnList func
func UnwidColumnList(columns []Column) []Column {
ret := []Column{}
for _, col := range columns {
if columnList, ok := col.(ColumnList); ok {
for _, c := range columnList {
ret = append(ret, c)
}
} else {
ret = append(ret, col)
}
}
return ret
}
// OptionalOrDefaultString will return first value from variable argument list str or
// defaultStr if variable argument list is empty
func OptionalOrDefaultString(defaultStr string, str ...string) string {
if len(str) > 0 {
return str[0]
}
return defaultStr
}
// OptionalOrDefaultExpression will return first value from variable argument list expression or
// defaultExpression if variable argument list is empty
func OptionalOrDefaultExpression(defaultExpression Expression, expression ...Expression) Expression {
if len(expression) > 0 {
return expression[0]
}
return defaultExpression
}
func extractTableAndColumnName(alias string) (tableName string, columnName string) {
parts := strings.Split(alias, ".")
if len(parts) >= 2 {
tableName = parts[0]
columnName = parts[1]
} else {
columnName = parts[0]
}
return
}
func serializeToDefaultDebugString(expr Serializer) string {
out := SQLBuilder{Dialect: defaultDialect, Debug: true}
expr.serialize(SelectStatementType, &out)
return out.Buff.String()
}
// joinAlias examples:
//
// joinAlias("foo", "bar") // "foo.bar"
// joinAlias("foo.*", "bar") // "foo.bar"
// joinAlias("", "bar") // "bar"
func joinAlias(tableAlias, columnAlias string) string {
if tableAlias == "" {
return columnAlias
}
return strings.TrimRight(tableAlias, ".*") + "." + columnAlias
}

View File

@@ -0,0 +1,27 @@
package jet
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestOptionalOrDefaultString(t *testing.T) {
require.Equal(t, OptionalOrDefaultString("default"), "default")
require.Equal(t, OptionalOrDefaultString("default", "optional"), "optional")
}
func TestOptionalOrDefaultExpression(t *testing.T) {
defaultExpression := table2ColFloat
optionalExpression := table1Col1
require.Equal(t, OptionalOrDefaultExpression(defaultExpression), defaultExpression)
require.Equal(t, OptionalOrDefaultExpression(defaultExpression, optionalExpression), optionalExpression)
}
func TestJoinAlias(t *testing.T) {
require.Equal(t, joinAlias("", ""), "")
require.Equal(t, joinAlias("foo", "bar"), "foo.bar")
require.Equal(t, joinAlias("foo.*", "bar"), "foo.bar")
require.Equal(t, joinAlias("", "bar"), "bar")
}

View File

@@ -0,0 +1,35 @@
package jet
// Values hold a set of one or more rows
type Values []RowExpression
func (v Values) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) {
out.WriteByte('(')
out.IncreaseIdent(5)
out.NewLine()
out.WriteString("VALUES")
for rowIndex, row := range v {
if rowIndex > 0 {
out.WriteString(",")
out.NewLine()
} else {
out.IncreaseIdent(7)
}
row.serialize(statement, out, options...)
}
out.DecreaseIdent(7)
out.DecreaseIdent(5)
out.NewLine()
out.WriteByte(')')
}
func (v Values) projections() ProjectionList {
if len(v) == 0 {
return nil
}
return v[0].projections()
}

View File

@@ -0,0 +1,146 @@
package jet
type commonWindowImpl struct {
expression Expression
window Window
}
func (w *commonWindowImpl) over(window ...Window) {
if len(window) > 0 {
w.window = window[0]
} else {
w.window = newWindowImpl(nil)
}
}
func (w *commonWindowImpl) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) {
w.expression.serialize(statement, out)
if w.window != nil {
out.WriteString("OVER")
w.window.serialize(statement, out, FallTrough(options)...)
}
}
// --------------------------------------
type windowExpression interface {
Expression
OVER(window ...Window) Expression
}
func newWindowExpression(Exp Expression) windowExpression {
newExp := &windowExpressionImpl{
Expression: Exp,
}
newExp.commonWindowImpl.expression = Exp
return newExp
}
type windowExpressionImpl struct {
Expression
commonWindowImpl
}
func (f *windowExpressionImpl) OVER(window ...Window) Expression {
f.commonWindowImpl.over(window...)
return f
}
func (f *windowExpressionImpl) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) {
f.commonWindowImpl.serialize(statement, out, FallTrough(options)...)
}
// -----------------------------------------------------
type floatWindowExpression interface {
FloatExpression
OVER(window ...Window) FloatExpression
}
func newFloatWindowExpression(floatExp FloatExpression) floatWindowExpression {
newExp := &floatWindowExpressionImpl{
FloatExpression: floatExp,
}
newExp.commonWindowImpl.expression = floatExp
return newExp
}
type floatWindowExpressionImpl struct {
FloatExpression
commonWindowImpl
}
func (f *floatWindowExpressionImpl) OVER(window ...Window) FloatExpression {
f.commonWindowImpl.over(window...)
return f
}
func (f *floatWindowExpressionImpl) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) {
f.commonWindowImpl.serialize(statement, out, FallTrough(options)...)
}
// ------------------------------------------------
type integerWindowExpression interface {
IntegerExpression
OVER(window ...Window) IntegerExpression
}
func newIntegerWindowExpression(intExp IntegerExpression) integerWindowExpression {
newExp := &integerWindowExpressionImpl{
IntegerExpression: intExp,
}
newExp.commonWindowImpl.expression = intExp
return newExp
}
type integerWindowExpressionImpl struct {
IntegerExpression
commonWindowImpl
}
func (f *integerWindowExpressionImpl) OVER(window ...Window) IntegerExpression {
f.commonWindowImpl.over(window...)
return f
}
func (f *integerWindowExpressionImpl) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) {
f.commonWindowImpl.serialize(statement, out, FallTrough(options)...)
}
// ------------------------------------------------
type boolWindowExpression interface {
BoolExpression
OVER(window ...Window) BoolExpression
}
func newBoolWindowExpression(boolExp BoolExpression) boolWindowExpression {
newExp := &boolWindowExpressionImpl{
BoolExpression: boolExp,
}
newExp.commonWindowImpl.expression = boolExp
return newExp
}
type boolWindowExpressionImpl struct {
BoolExpression
commonWindowImpl
}
func (f *boolWindowExpressionImpl) OVER(window ...Window) BoolExpression {
f.commonWindowImpl.over(window...)
return f
}
func (f *boolWindowExpressionImpl) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) {
f.commonWindowImpl.serialize(statement, out, FallTrough(options)...)
}

View File

@@ -0,0 +1,186 @@
package jet
// Window interface
type Window interface {
Serializer
ORDER_BY(expr ...OrderByClause) Window
ROWS(start FrameExtent, end ...FrameExtent) Window
RANGE(start FrameExtent, end ...FrameExtent) Window
GROUPS(start FrameExtent, end ...FrameExtent) Window
}
type windowImpl struct {
partitionBy []Expression
orderBy ClauseOrderBy
frameUnits string
start, end FrameExtent
parent Window
}
func newWindowImpl(parent Window) *windowImpl {
newWindow := &windowImpl{}
if parent == nil {
newWindow.parent = newWindow
} else {
newWindow.parent = parent
}
return newWindow
}
func (w *windowImpl) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) {
if !contains(options, NoWrap) {
out.WriteByte('(')
}
if w.partitionBy != nil {
out.WriteString("PARTITION BY")
serializeExpressionList(statement, w.partitionBy, ", ", out)
}
w.orderBy.SkipNewLine = true
w.orderBy.Serialize(statement, out, FallTrough(options)...)
if w.frameUnits != "" {
out.WriteString(w.frameUnits)
if w.end == nil {
w.start.serialize(statement, out)
} else {
out.WriteString("BETWEEN")
w.start.serialize(statement, out)
out.WriteString("AND")
w.end.serialize(statement, out)
}
}
if !contains(options, NoWrap) {
out.WriteByte(')')
}
}
func (w *windowImpl) ORDER_BY(exprs ...OrderByClause) Window {
w.orderBy.List = exprs
return w.parent
}
func (w *windowImpl) ROWS(start FrameExtent, end ...FrameExtent) Window {
w.frameUnits = "ROWS"
w.setFrameRange(start, end...)
return w.parent
}
func (w *windowImpl) RANGE(start FrameExtent, end ...FrameExtent) Window {
w.frameUnits = "RANGE"
w.setFrameRange(start, end...)
return w.parent
}
func (w *windowImpl) GROUPS(start FrameExtent, end ...FrameExtent) Window {
w.frameUnits = "GROUPS"
w.setFrameRange(start, end...)
return w.parent
}
func (w *windowImpl) setFrameRange(start FrameExtent, end ...FrameExtent) {
w.start = start
if len(end) > 0 {
w.end = end[0]
}
}
// PARTITION_BY window function constructor
func PARTITION_BY(exp Expression, exprs ...Expression) Window {
funImpl := newWindowImpl(nil)
funImpl.partitionBy = append([]Expression{exp}, exprs...)
return funImpl
}
// ORDER_BY window function constructor
func ORDER_BY(expr ...OrderByClause) Window {
funImpl := newWindowImpl(nil)
funImpl.orderBy.List = expr
return funImpl
}
// -----------------------------------------------
// FrameExtent interface
type FrameExtent interface {
Serializer
isFrameExtent()
}
// PRECEDING window frame clause
func PRECEDING(offset Serializer) FrameExtent {
return &frameExtentImpl{
preceding: true,
offset: offset,
}
}
// FOLLOWING window frame clause
func FOLLOWING(offset Serializer) FrameExtent {
return &frameExtentImpl{
preceding: false,
offset: offset,
}
}
type frameExtentImpl struct {
preceding bool
offset Serializer
}
func (f *frameExtentImpl) isFrameExtent() {}
func (f *frameExtentImpl) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) {
if f == nil {
return
}
f.offset.serialize(statement, out, FallTrough(options)...)
if f.preceding {
out.WriteString("PRECEDING")
} else {
out.WriteString("FOLLOWING")
}
}
// -----------------------------------------------
// Window function keywords
var (
UNBOUNDED = Keyword("UNBOUNDED")
CURRENT_ROW = frameExtentKeyword{"CURRENT ROW"}
)
type frameExtentKeyword struct {
Keyword
}
func (f frameExtentKeyword) isFrameExtent() {}
// -----------------------------------------------
// WindowName is used to specify window reference from WINDOW clause
func WindowName(name string) Window {
newWindow := &windowName{name: name}
newWindow.parent = newWindow
return newWindow
}
type windowName struct {
windowImpl
name string
}
func (w windowName) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) {
out.WriteByte('(')
out.WriteString(w.name)
w.windowImpl.serialize(statement, out, NoWrap.WithFallTrough(options)...)
out.WriteByte(')')
}

View File

@@ -0,0 +1,21 @@
package jet
import "testing"
func TestFrameExtent(t *testing.T) {
assertClauseSerialize(t, PRECEDING(Int(2)), "$1 PRECEDING", int64(2))
assertClauseSerialize(t, FOLLOWING(Int(4)), "$1 FOLLOWING", int64(4))
}
func TestWindowFunctions(t *testing.T) {
assertClauseSerialize(t, PARTITION_BY(table1Col1), "(PARTITION BY table1.col1)")
assertClauseSerialize(t, PARTITION_BY(table1Col3).ORDER_BY(table1Col1), "(PARTITION BY table1.col3 ORDER BY table1.col1)")
assertClauseSerialize(t, ORDER_BY(table1Col1), "(ORDER BY table1.col1)")
assertClauseSerialize(t, ORDER_BY(table1Col1).ROWS(PRECEDING(Int(1))), "(ORDER BY table1.col1 ROWS $1 PRECEDING)", int64(1))
assertClauseSerialize(t, ORDER_BY(table1Col1).ROWS(PRECEDING(Int(1)), FOLLOWING(Int(33))),
"(ORDER BY table1.col1 ROWS BETWEEN $1 PRECEDING AND $2 FOLLOWING)", int64(1), int64(33))
assertClauseSerialize(t, ORDER_BY(table1Col1).RANGE(PRECEDING(UNBOUNDED), FOLLOWING(UNBOUNDED)),
"(ORDER BY table1.col1 RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING)")
assertClauseSerialize(t, ORDER_BY(table1Col1).RANGE(PRECEDING(UNBOUNDED), CURRENT_ROW),
"(ORDER BY table1.col1 RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)")
}

View File

@@ -0,0 +1,101 @@
package jet
import "fmt"
// WITH function creates new with statement from list of common table expressions for specified dialect
func WITH(dialect Dialect, recursive bool, cte ...*CommonTableExpression) func(statement Statement) Statement {
newWithImpl := &withImpl{
recursive: recursive,
ctes: cte,
serializerStatementInterfaceImpl: serializerStatementInterfaceImpl{
dialect: dialect,
statementType: WithStatementType,
},
}
newWithImpl.parent = newWithImpl
return func(primaryStatement Statement) Statement {
serializerStatement, ok := primaryStatement.(SerializerStatement)
if !ok {
panic("jet: unsupported main WITH statement.")
}
newWithImpl.primaryStatement = serializerStatement
return newWithImpl
}
}
type withImpl struct {
serializerStatementInterfaceImpl
recursive bool
ctes []*CommonTableExpression
primaryStatement SerializerStatement
}
func (w withImpl) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) {
out.NewLine()
out.WriteString("WITH")
if w.recursive {
out.WriteString("RECURSIVE")
}
for i, cte := range w.ctes {
if i > 0 {
out.WriteString(",")
}
cte.serialize(statement, out, FallTrough(options)...)
}
w.primaryStatement.serialize(statement, out, NoWrap.WithFallTrough(options)...)
}
func (w withImpl) projections() ProjectionList {
return ProjectionList{}
}
// CommonTableExpression contains information about a CTE.
type CommonTableExpression struct {
selectTableImpl
NotMaterialized bool
Columns []ColumnExpression
}
// CTE creates new named CommonTableExpression
func CTE(name string, columns ...ColumnExpression) CommonTableExpression {
cte := CommonTableExpression{
selectTableImpl: NewSelectTable(nil, name, columns),
Columns: columns,
}
for _, column := range cte.Columns {
column.setSubQuery(cte)
}
return cte
}
func (c CommonTableExpression) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) {
if statement == WithStatementType { // serialize CTE definition
out.WriteIdentifier(c.alias)
if len(c.Columns) > 0 {
out.WriteByte('(')
SerializeColumnExpressionNames(c.Columns, out)
out.WriteByte(')')
}
out.WriteString("AS")
if c.NotMaterialized {
out.WriteString("NOT MATERIALIZED")
}
if c.Statement == nil {
panic(fmt.Sprintf("jet: '%s' CTE is not defined", c.alias))
}
c.Statement.serialize(statement, out, FallTrough(options)...)
} else { // serialize CTE in FROM clause
out.WriteIdentifier(c.alias)
}
}