Encode json values implicitly in the sql queries according the golang json package spec.

This commit is contained in:
go-jet 2025-03-08 19:01:37 +01:00
parent 9616bb5cfe
commit 17646ca99c
54 changed files with 1446 additions and 744 deletions

View file

@ -18,10 +18,45 @@ func (a *alias) fromImpl(subQuery SelectTable) Projection {
// Generated columns have default aliasing. // Generated columns have default aliasing.
tableName, columnName := extractTableAndColumnName(a.alias) tableName, columnName := extractTableAndColumnName(a.alias)
column := NewColumnImpl(columnName, tableName, nil) newDummyColumn := newDummyColumnForExpression(a.expression, columnName)
column.subQuery = subQuery newDummyColumn.setTableName(tableName)
newDummyColumn.setSubQuery(subQuery)
return &column return newDummyColumn
}
// This function is used to create dummy columns when exporting sub-query columns using subQuery.AllColumns()
// In most case we don't care about type of the column, except when sub-query columns are used as SELECT_JSON projection.
// We need to know type to encode value for json unmarshal. At the moment only bool, time and blob columns are of interest,
// so we don't have to support every column type.
func newDummyColumnForExpression(exp Expression, name string) ColumnExpression {
switch exp.(type) {
case BoolExpression:
return BoolColumn(name)
case IntegerExpression:
return IntegerColumn(name)
case FloatExpression:
return FloatColumn(name)
case BlobExpression:
return BlobColumn(name)
case DateExpression:
return DateColumn(name)
case TimeExpression:
return TimeColumn(name)
case TimezExpression:
return TimezColumn(name)
case TimestampExpression:
return TimestampColumn(name)
case TimestampzExpression:
return TimestampzColumn(name)
case IntervalExpression:
return IntervalColumn(name)
case StringExpression:
return StringColumn(name)
}
return StringColumn(name)
} }
func (a *alias) serializeForProjection(statement StatementType, out *SQLBuilder) { func (a *alias) serializeForProjection(statement StatementType, out *SQLBuilder) {
@ -31,7 +66,14 @@ func (a *alias) serializeForProjection(statement StatementType, out *SQLBuilder)
out.WriteAlias(a.alias) out.WriteAlias(a.alias)
} }
func (a *alias) serializeForJsonObj(statement StatementType, out *SQLBuilder) { func (a *alias) serializeForJsonObjEntry(statement StatementType, out *SQLBuilder) {
out.WriteJsonObjKey(a.alias) out.WriteJsonObjKey(a.alias)
a.expression.serialize(statement, out) a.expression.serializeForJsonValue(statement, out)
}
func (a *alias) serializeForRowToJsonProjection(statement StatementType, out *SQLBuilder) {
a.expression.serializeForJsonValue(statement, out)
out.WriteString("AS")
out.WriteAlias(a.alias)
} }

View file

@ -28,71 +28,72 @@ type blobInterfaceImpl struct {
parent BlobExpression parent BlobExpression
} }
func (s *blobInterfaceImpl) isStringOrBlob() {} func (b *blobInterfaceImpl) isStringOrBlob() {}
func (s *blobInterfaceImpl) EQ(rhs BlobExpression) BoolExpression { func (b *blobInterfaceImpl) EQ(rhs BlobExpression) BoolExpression {
return Eq(s.parent, rhs) return Eq(b.parent, rhs)
} }
func (s *blobInterfaceImpl) NOT_EQ(rhs BlobExpression) BoolExpression { func (b *blobInterfaceImpl) NOT_EQ(rhs BlobExpression) BoolExpression {
return NotEq(s.parent, rhs) return NotEq(b.parent, rhs)
} }
func (s *blobInterfaceImpl) IS_DISTINCT_FROM(rhs BlobExpression) BoolExpression { func (b *blobInterfaceImpl) IS_DISTINCT_FROM(rhs BlobExpression) BoolExpression {
return IsDistinctFrom(s.parent, rhs) return IsDistinctFrom(b.parent, rhs)
} }
func (s *blobInterfaceImpl) IS_NOT_DISTINCT_FROM(rhs BlobExpression) BoolExpression { func (b *blobInterfaceImpl) IS_NOT_DISTINCT_FROM(rhs BlobExpression) BoolExpression {
return IsNotDistinctFrom(s.parent, rhs) return IsNotDistinctFrom(b.parent, rhs)
} }
func (s *blobInterfaceImpl) GT(rhs BlobExpression) BoolExpression { func (b *blobInterfaceImpl) GT(rhs BlobExpression) BoolExpression {
return Gt(s.parent, rhs) return Gt(b.parent, rhs)
} }
func (s *blobInterfaceImpl) GT_EQ(rhs BlobExpression) BoolExpression { func (b *blobInterfaceImpl) GT_EQ(rhs BlobExpression) BoolExpression {
return GtEq(s.parent, rhs) return GtEq(b.parent, rhs)
} }
func (s *blobInterfaceImpl) LT(rhs BlobExpression) BoolExpression { func (b *blobInterfaceImpl) LT(rhs BlobExpression) BoolExpression {
return Lt(s.parent, rhs) return Lt(b.parent, rhs)
} }
func (s *blobInterfaceImpl) LT_EQ(rhs BlobExpression) BoolExpression { func (b *blobInterfaceImpl) LT_EQ(rhs BlobExpression) BoolExpression {
return LtEq(s.parent, rhs) return LtEq(b.parent, rhs)
} }
func (s *blobInterfaceImpl) BETWEEN(min, max BlobExpression) BoolExpression { func (b *blobInterfaceImpl) BETWEEN(min, max BlobExpression) BoolExpression {
return NewBetweenOperatorExpression(s.parent, min, max, false) return NewBetweenOperatorExpression(b.parent, min, max, false)
} }
func (s *blobInterfaceImpl) NOT_BETWEEN(min, max BlobExpression) BoolExpression { func (b *blobInterfaceImpl) NOT_BETWEEN(min, max BlobExpression) BoolExpression {
return NewBetweenOperatorExpression(s.parent, min, max, true) return NewBetweenOperatorExpression(b.parent, min, max, true)
} }
func (s *blobInterfaceImpl) CONCAT(rhs BlobExpression) BlobExpression { func (b *blobInterfaceImpl) CONCAT(rhs BlobExpression) BlobExpression {
return BlobExp(newBinaryStringOperatorExpression(s.parent, rhs, StringConcatOperator)) return BlobExp(newBinaryStringOperatorExpression(b.parent, rhs, StringConcatOperator))
} }
func (s *blobInterfaceImpl) LIKE(pattern BlobExpression) BoolExpression { func (b *blobInterfaceImpl) LIKE(pattern BlobExpression) BoolExpression {
return newBinaryBoolOperatorExpression(s.parent, pattern, "LIKE") return newBinaryBoolOperatorExpression(b.parent, pattern, "LIKE")
} }
func (s *blobInterfaceImpl) NOT_LIKE(pattern BlobExpression) BoolExpression { func (b *blobInterfaceImpl) NOT_LIKE(pattern BlobExpression) BoolExpression {
return newBinaryBoolOperatorExpression(s.parent, pattern, "NOT LIKE") return newBinaryBoolOperatorExpression(b.parent, pattern, "NOT LIKE")
} }
//---------------------------------------------------// //---------------------------------------------------//
type blobExpressionWrapper struct { type blobExpressionWrapper struct {
blobInterfaceImpl
Expression Expression
blobInterfaceImpl
} }
func newBlobExpressionWrap(expression Expression) BlobExpression { func newBlobExpressionWrap(expression Expression) BlobExpression {
blobExpressionWrap := blobExpressionWrapper{Expression: expression} blobExpressionWrap := &blobExpressionWrapper{Expression: expression}
blobExpressionWrap.blobInterfaceImpl.parent = &blobExpressionWrap blobExpressionWrap.blobInterfaceImpl.parent = blobExpressionWrap
return &blobExpressionWrap expression.setParent(blobExpressionWrap)
return blobExpressionWrap
} }
// BlobExp is blob expression wrapper around arbitrary expression. // BlobExp is blob expression wrapper around arbitrary expression.

View file

@ -102,9 +102,10 @@ type boolExpressionWrapper struct {
} }
func newBoolExpressionWrap(expression Expression) BoolExpression { func newBoolExpressionWrap(expression Expression) BoolExpression {
boolExpressionWrap := boolExpressionWrapper{Expression: expression} boolExpressionWrap := &boolExpressionWrapper{Expression: expression}
boolExpressionWrap.boolInterfaceImpl.parent = &boolExpressionWrap boolExpressionWrap.boolInterfaceImpl.parent = boolExpressionWrap
return &boolExpressionWrap expression.setParent(boolExpressionWrap)
return boolExpressionWrap
} }
// BoolExp is bool expression wrapper around arbitrary expression. // BoolExp is bool expression wrapper around arbitrary expression.

View file

@ -41,6 +41,8 @@ type ClauseSelect struct {
DistinctOnColumns []ColumnExpression DistinctOnColumns []ColumnExpression
ProjectionList []Projection ProjectionList []Projection
IsForRowToJson bool
// MySQL only // MySQL only
OptimizerHints optimizerHints OptimizerHints optimizerHints
} }
@ -70,8 +72,14 @@ func (s *ClauseSelect) Serialize(statementType StatementType, out *SQLBuilder, o
out.WriteByte(')') out.WriteByte(')')
} }
if s.IsForRowToJson {
out.IncreaseIdent()
out.WriteRowToJsonProjections(statementType, s.ProjectionList)
out.DecreaseIdent()
} else {
out.WriteProjections(statementType, s.ProjectionList) out.WriteProjections(statementType, s.ProjectionList)
} }
}
// ClauseFrom struct // ClauseFrom struct
type ClauseFrom struct { type ClauseFrom struct {

View file

@ -39,19 +39,19 @@ type ColumnExpressionImpl struct {
} }
// NewColumnImpl creates new ColumnExpressionImpl // NewColumnImpl creates new ColumnExpressionImpl
func NewColumnImpl(name string, tableName string, parent ColumnExpression) ColumnExpressionImpl { func NewColumnImpl(name string, tableName string, parent ColumnExpression) *ColumnExpressionImpl {
bc := ColumnExpressionImpl{ newColumn := &ColumnExpressionImpl{
name: name, name: name,
tableName: tableName, tableName: tableName,
} }
if parent != nil { if parent != nil {
bc.ExpressionInterfaceImpl.Parent = parent newColumn.ExpressionInterfaceImpl.Parent = parent
} else { } else {
bc.ExpressionInterfaceImpl.Parent = &bc newColumn.ExpressionInterfaceImpl.Parent = newColumn
} }
return bc return newColumn
} }
// Name returns name of the column // Name returns name of the column
@ -80,13 +80,6 @@ func (c *ColumnExpressionImpl) defaultAlias() string {
return 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) { func (c *ColumnExpressionImpl) serializeForOrderBy(statement StatementType, out *SQLBuilder) {
if statement == SetStatementType { if statement == SetStatementType {
// set Statement (UNION, EXCEPT ...) can reference only select projections in order by clause // set Statement (UNION, EXCEPT ...) can reference only select projections in order by clause
@ -97,24 +90,28 @@ func (c *ColumnExpressionImpl) serializeForOrderBy(statement StatementType, out
c.serialize(statement, out) c.serialize(statement, out)
} }
func (c ColumnExpressionImpl) serializeForProjection(statement StatementType, out *SQLBuilder) { func (c *ColumnExpressionImpl) serializeForProjection(statement StatementType, out *SQLBuilder) {
c.serialize(statement, out) c.serialize(statement, out)
out.WriteString("AS") out.WriteString("AS")
if statement.IsSelectJSON() {
out.WriteAlias(snaker.SnakeToCamel(c.name, false))
} else {
out.WriteAlias(c.defaultAlias()) out.WriteAlias(c.defaultAlias())
} }
}
func (c ColumnExpressionImpl) serializeForJsonObj(statement StatementType, out *SQLBuilder) { func (c *ColumnExpressionImpl) serializeForJsonObjEntry(statement StatementType, out *SQLBuilder) {
out.WriteJsonObjKey(snaker.SnakeToCamel(c.name, false)) out.WriteJsonObjKey(snaker.SnakeToCamel(c.name, false))
c.serialize(statement, out) c.Parent.serializeForJsonValue(statement, out)
} }
func (c ColumnExpressionImpl) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) { func (c *ColumnExpressionImpl) serializeForRowToJsonProjection(statement StatementType, out *SQLBuilder) {
c.Parent.serializeForJsonValue(statement, out)
out.WriteString("AS")
out.WriteAlias(snaker.SnakeToCamel(c.name, false))
}
func (c *ColumnExpressionImpl) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) {
if c.subQuery != nil { if c.subQuery != nil {
out.WriteIdentifier(c.subQuery.Alias()) out.WriteIdentifier(c.subQuery.Alias())

View file

@ -78,12 +78,18 @@ func (cl ColumnList) serializeForProjection(statement StatementType, out *SQLBui
SerializeProjectionList(statement, projections, out) SerializeProjectionList(statement, projections, out)
} }
func (cl ColumnList) serializeForJsonObj(statement StatementType, out *SQLBuilder) { func (cl ColumnList) serializeForJsonObjEntry(statement StatementType, out *SQLBuilder) {
projections := ColumnListToProjectionList(cl) projections := ColumnListToProjectionList(cl)
SerializeProjectionListJsonObj(statement, projections, out) SerializeProjectionListJsonObj(statement, projections, out)
} }
func (cl ColumnList) serializeForRowToJsonProjection(statement StatementType, out *SQLBuilder) {
projections := ColumnListToProjectionList(cl)
out.WriteRowToJsonProjections(statement, projections)
}
// dummy column interface implementation // dummy column interface implementation
// Name is placeholder for ColumnList to implement Column interface // Name is placeholder for ColumnList to implement Column interface

View file

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

View file

@ -11,7 +11,11 @@ type ColumnBool interface {
type boolColumnImpl struct { type boolColumnImpl struct {
boolInterfaceImpl boolInterfaceImpl
ColumnExpressionImpl *ColumnExpressionImpl
}
func (i *boolColumnImpl) fromImpl(subQuery SelectTable) Projection {
return i.From(subQuery)
} }
func (i *boolColumnImpl) From(subQuery SelectTable) ColumnBool { func (i *boolColumnImpl) From(subQuery SelectTable) ColumnBool {
@ -51,7 +55,11 @@ type ColumnFloat interface {
type floatColumnImpl struct { type floatColumnImpl struct {
floatInterfaceImpl floatInterfaceImpl
ColumnExpressionImpl *ColumnExpressionImpl
}
func (i *floatColumnImpl) fromImpl(subQuery SelectTable) Projection {
return i.From(subQuery)
} }
func (i *floatColumnImpl) From(subQuery SelectTable) ColumnFloat { func (i *floatColumnImpl) From(subQuery SelectTable) ColumnFloat {
@ -92,7 +100,11 @@ type ColumnInteger interface {
type integerColumnImpl struct { type integerColumnImpl struct {
integerInterfaceImpl integerInterfaceImpl
ColumnExpressionImpl *ColumnExpressionImpl
}
func (i *integerColumnImpl) fromImpl(subQuery SelectTable) Projection {
return i.From(subQuery)
} }
func (i *integerColumnImpl) From(subQuery SelectTable) ColumnInteger { func (i *integerColumnImpl) From(subQuery SelectTable) ColumnInteger {
@ -134,7 +146,11 @@ type ColumnString interface {
type stringColumnImpl struct { type stringColumnImpl struct {
stringInterfaceImpl stringInterfaceImpl
ColumnExpressionImpl *ColumnExpressionImpl
}
func (i *stringColumnImpl) fromImpl(subQuery SelectTable) Projection {
return i.From(subQuery)
} }
func (i *stringColumnImpl) From(subQuery SelectTable) ColumnString { func (i *stringColumnImpl) From(subQuery SelectTable) ColumnString {
@ -175,7 +191,11 @@ type ColumnBlob interface {
type blobColumnImpl struct { type blobColumnImpl struct {
blobInterfaceImpl blobInterfaceImpl
ColumnExpressionImpl *ColumnExpressionImpl
}
func (i *blobColumnImpl) fromImpl(subQuery SelectTable) Projection {
return i.From(subQuery)
} }
func (i *blobColumnImpl) From(subQuery SelectTable) ColumnBlob { func (i *blobColumnImpl) From(subQuery SelectTable) ColumnBlob {
@ -215,7 +235,11 @@ type ColumnTime interface {
type timeColumnImpl struct { type timeColumnImpl struct {
timeInterfaceImpl timeInterfaceImpl
ColumnExpressionImpl *ColumnExpressionImpl
}
func (i *timeColumnImpl) fromImpl(subQuery SelectTable) Projection {
return i.From(subQuery)
} }
func (i *timeColumnImpl) From(subQuery SelectTable) ColumnTime { func (i *timeColumnImpl) From(subQuery SelectTable) ColumnTime {
@ -254,7 +278,11 @@ type ColumnTimez interface {
type timezColumnImpl struct { type timezColumnImpl struct {
timezInterfaceImpl timezInterfaceImpl
ColumnExpressionImpl *ColumnExpressionImpl
}
func (i *timezColumnImpl) fromImpl(subQuery SelectTable) Projection {
return i.From(subQuery)
} }
func (i *timezColumnImpl) From(subQuery SelectTable) ColumnTimez { func (i *timezColumnImpl) From(subQuery SelectTable) ColumnTimez {
@ -294,7 +322,11 @@ type ColumnTimestamp interface {
type timestampColumnImpl struct { type timestampColumnImpl struct {
timestampInterfaceImpl timestampInterfaceImpl
ColumnExpressionImpl *ColumnExpressionImpl
}
func (i *timestampColumnImpl) fromImpl(subQuery SelectTable) Projection {
return i.From(subQuery)
} }
func (i *timestampColumnImpl) From(subQuery SelectTable) ColumnTimestamp { func (i *timestampColumnImpl) From(subQuery SelectTable) ColumnTimestamp {
@ -334,7 +366,11 @@ type ColumnTimestampz interface {
type timestampzColumnImpl struct { type timestampzColumnImpl struct {
timestampzInterfaceImpl timestampzInterfaceImpl
ColumnExpressionImpl *ColumnExpressionImpl
}
func (i *timestampzColumnImpl) fromImpl(subQuery SelectTable) Projection {
return i.From(subQuery)
} }
func (i *timestampzColumnImpl) From(subQuery SelectTable) ColumnTimestampz { func (i *timestampzColumnImpl) From(subQuery SelectTable) ColumnTimestampz {
@ -374,7 +410,11 @@ type ColumnDate interface {
type dateColumnImpl struct { type dateColumnImpl struct {
dateInterfaceImpl dateInterfaceImpl
ColumnExpressionImpl *ColumnExpressionImpl
}
func (i *dateColumnImpl) fromImpl(subQuery SelectTable) Projection {
return i.From(subQuery)
} }
func (i *dateColumnImpl) From(subQuery SelectTable) ColumnDate { func (i *dateColumnImpl) From(subQuery SelectTable) ColumnDate {
@ -402,6 +442,51 @@ func DateColumn(name string) ColumnDate {
//------------------------------------------------------// //------------------------------------------------------//
// ColumnInterval is interface of PostgreSQL interval columns.
type ColumnInterval interface {
IntervalExpression
Column
From(subQuery SelectTable) ColumnInterval
SET(intervalExp IntervalExpression) ColumnAssigment
}
//------------------------------------------------------//
type intervalColumnImpl struct {
*ColumnExpressionImpl
intervalInterfaceImpl
}
func (i *intervalColumnImpl) SET(intervalExp IntervalExpression) ColumnAssigment {
return columnAssigmentImpl{
column: i,
expression: intervalExp,
}
}
func (i *intervalColumnImpl) fromImpl(subQuery SelectTable) Projection {
return i.From(subQuery)
}
func (i *intervalColumnImpl) From(subQuery SelectTable) ColumnInterval {
newIntervalColumn := IntervalColumn(i.name)
newIntervalColumn.setTableName(i.tableName)
newIntervalColumn.setSubQuery(subQuery)
return newIntervalColumn
}
// IntervalColumn creates named interval column.
func IntervalColumn(name string) ColumnInterval {
intervalColumn := &intervalColumnImpl{}
intervalColumn.ColumnExpressionImpl = NewColumnImpl(name, "", intervalColumn)
intervalColumn.intervalInterfaceImpl.parent = intervalColumn
return intervalColumn
}
//------------------------------------------------------//
// ColumnRange is interface for range columns which can be int range, string range // ColumnRange is interface for range columns which can be int range, string range
// timestamp range or date range. // timestamp range or date range.
type ColumnRange[T Expression] interface { type ColumnRange[T Expression] interface {
@ -414,7 +499,11 @@ type ColumnRange[T Expression] interface {
type rangeColumnImpl[T Expression] struct { type rangeColumnImpl[T Expression] struct {
rangeInterfaceImpl[T] rangeInterfaceImpl[T]
ColumnExpressionImpl *ColumnExpressionImpl
}
func (i *rangeColumnImpl[T]) fromImpl(subQuery SelectTable) Projection {
return i.From(subQuery)
} }
func (i *rangeColumnImpl[T]) From(subQuery SelectTable) ColumnRange[T] { func (i *rangeColumnImpl[T]) From(subQuery SelectTable) ColumnRange[T] {

View file

@ -80,9 +80,10 @@ type dateExpressionWrapper struct {
} }
func newDateExpressionWrap(expression Expression) DateExpression { func newDateExpressionWrap(expression Expression) DateExpression {
dateExpressionWrap := dateExpressionWrapper{Expression: expression} dateExpressionWrap := &dateExpressionWrapper{Expression: expression}
dateExpressionWrap.dateInterfaceImpl.parent = &dateExpressionWrap dateExpressionWrap.dateInterfaceImpl.parent = dateExpressionWrap
return &dateExpressionWrap expression.setParent(dateExpressionWrap)
return dateExpressionWrap
} }
// DateExp is date expression wrapper around arbitrary expression. // DateExp is date expression wrapper around arbitrary expression.

View file

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

@ -17,6 +17,7 @@ type Dialect interface {
IsReservedWord(name string) bool IsReservedWord(name string) bool
SerializeOrderBy() func(expression Expression, ascending, nullsFirst *bool) SerializerFunc SerializeOrderBy() func(expression Expression, ascending, nullsFirst *bool) SerializerFunc
ValuesDefaultColumnName(index int) string ValuesDefaultColumnName(index int) string
JsonValueEncode(expr Expression) Expression
} }
// SerializerFunc func // SerializerFunc func
@ -41,6 +42,7 @@ type DialectParams struct {
ReservedWords []string ReservedWords []string
SerializeOrderBy func(expression Expression, ascending, nullsFirst *bool) SerializerFunc SerializeOrderBy func(expression Expression, ascending, nullsFirst *bool) SerializerFunc
ValuesDefaultColumnName func(index int) string ValuesDefaultColumnName func(index int) string
JsonValueEncode func(expr Expression) Expression
} }
// NewDialect creates new dialect with params // NewDialect creates new dialect with params
@ -57,6 +59,7 @@ func NewDialect(params DialectParams) Dialect {
reservedWords: arrayOfStringsToMapOfStrings(params.ReservedWords), reservedWords: arrayOfStringsToMapOfStrings(params.ReservedWords),
serializeOrderBy: params.SerializeOrderBy, serializeOrderBy: params.SerializeOrderBy,
valuesDefaultColumnName: params.ValuesDefaultColumnName, valuesDefaultColumnName: params.ValuesDefaultColumnName,
jsonValueEncode: params.JsonValueEncode,
} }
} }
@ -72,6 +75,7 @@ type dialectImpl struct {
reservedWords map[string]bool reservedWords map[string]bool
serializeOrderBy func(expression Expression, ascending, nullsFirst *bool) SerializerFunc serializeOrderBy func(expression Expression, ascending, nullsFirst *bool) SerializerFunc
valuesDefaultColumnName func(index int) string valuesDefaultColumnName func(index int) string
jsonValueEncode func(expr Expression) Expression
} }
func (d *dialectImpl) Name() string { func (d *dialectImpl) Name() string {
@ -125,6 +129,10 @@ func (d *dialectImpl) ValuesDefaultColumnName(index int) string {
return d.valuesDefaultColumnName(index) return d.valuesDefaultColumnName(index)
} }
func (d *dialectImpl) JsonValueEncode(expr Expression) Expression {
return d.jsonValueEncode(expr)
}
func arrayOfStringsToMapOfStrings(arr []string) map[string]bool { func arrayOfStringsToMapOfStrings(arr []string) map[string]bool {
ret := map[string]bool{} ret := map[string]bool{}
for _, elem := range arr { for _, elem := range arr {

View file

@ -10,6 +10,9 @@ type Expression interface {
GroupByClause GroupByClause
OrderByClause OrderByClause
serializeForJsonValue(statement StatementType, out *SQLBuilder)
setParent(parent Expression)
// IS_NULL tests expression whether it is a NULL value. // IS_NULL tests expression whether it is a NULL value.
IS_NULL() BoolExpression IS_NULL() BoolExpression
// IS_NOT_NULL tests expression whether it is a non-NULL value. // IS_NOT_NULL tests expression whether it is a non-NULL value.
@ -34,6 +37,10 @@ type ExpressionInterfaceImpl struct {
Parent Expression Parent Expression
} }
func (e *ExpressionInterfaceImpl) setParent(parent Expression) {
e.Parent = parent
}
func (e *ExpressionInterfaceImpl) fromImpl(subQuery SelectTable) Projection { func (e *ExpressionInterfaceImpl) fromImpl(subQuery SelectTable) Projection {
panic(fmt.Sprintf("jet: can't export unaliased expression subQuery: %s, expression: %s", panic(fmt.Sprintf("jet: can't export unaliased expression subQuery: %s, expression: %s",
subQuery.Alias(), serializeToDefaultDebugString(e.Parent))) subQuery.Alias(), serializeToDefaultDebugString(e.Parent)))
@ -89,16 +96,21 @@ func (e *ExpressionInterfaceImpl) serializeForGroupBy(statement StatementType, o
} }
func (e *ExpressionInterfaceImpl) serializeForProjection(statement StatementType, out *SQLBuilder) { func (e *ExpressionInterfaceImpl) serializeForProjection(statement StatementType, out *SQLBuilder) {
if statement.IsSelectJSON() {
panic("jet: expression need to be aliased when used as SELECT JSON projection.")
}
e.Parent.serialize(statement, out, NoWrap) e.Parent.serialize(statement, out, NoWrap)
} }
func (e *ExpressionInterfaceImpl) serializeForJsonObj(statement StatementType, out *SQLBuilder) { func (e *ExpressionInterfaceImpl) serializeForJsonObjEntry(statement StatementType, out *SQLBuilder) {
panic("jet: expression need to be aliased when used as SELECT JSON projection.") panic("jet: expression need to be aliased when used as SELECT JSON projection.")
} }
func (e *ExpressionInterfaceImpl) serializeForRowToJsonProjection(statement StatementType, out *SQLBuilder) {
panic("jet: expression need to be aliased when used as SELECT JSON projection.")
}
func (e *ExpressionInterfaceImpl) serializeForJsonValue(statement StatementType, out *SQLBuilder) {
out.Dialect.JsonValueEncode(e.Parent).serialize(statement, out)
}
func (e *ExpressionInterfaceImpl) serializeForOrderBy(statement StatementType, out *SQLBuilder) { func (e *ExpressionInterfaceImpl) serializeForOrderBy(statement StatementType, out *SQLBuilder) {
e.Parent.serialize(statement, out, NoWrap) e.Parent.serialize(statement, out, NoWrap)
} }

View file

@ -102,9 +102,10 @@ type floatExpressionWrapper struct {
} }
func newFloatExpressionWrap(expression Expression) FloatExpression { func newFloatExpressionWrap(expression Expression) FloatExpression {
floatExpressionWrap := floatExpressionWrapper{Expression: expression} floatExpressionWrap := &floatExpressionWrapper{Expression: expression}
floatExpressionWrap.floatInterfaceImpl.parent = &floatExpressionWrap floatExpressionWrap.floatInterfaceImpl.parent = floatExpressionWrap
return &floatExpressionWrap expression.setParent(floatExpressionWrap)
return floatExpressionWrap
} }
// FloatExp is date expression wrapper around arbitrary expression. // FloatExp is date expression wrapper around arbitrary expression.

View file

@ -141,11 +141,11 @@ type integerExpressionWrapper struct {
} }
func newIntExpressionWrap(expression Expression) IntegerExpression { func newIntExpressionWrap(expression Expression) IntegerExpression {
intExpressionWrap := integerExpressionWrapper{Expression: expression} intExpressionWrap := &integerExpressionWrapper{Expression: expression}
intExpressionWrap.integerInterfaceImpl.parent = intExpressionWrap
expression.setParent(intExpressionWrap)
intExpressionWrap.integerInterfaceImpl.parent = &intExpressionWrap return intExpressionWrap
return &intExpressionWrap
} }
// IntExp is int expression wrapper around arbitrary expression. // IntExp is int expression wrapper around arbitrary expression.

View file

@ -1,37 +0,0 @@
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,112 @@
package jet
// IntervalExpression interface
type IntervalExpression interface {
Expression
isInterval()
EQ(rhs IntervalExpression) BoolExpression
NOT_EQ(rhs IntervalExpression) BoolExpression
IS_DISTINCT_FROM(rhs IntervalExpression) BoolExpression
IS_NOT_DISTINCT_FROM(rhs IntervalExpression) BoolExpression
LT(rhs IntervalExpression) BoolExpression
LT_EQ(rhs IntervalExpression) BoolExpression
GT(rhs IntervalExpression) BoolExpression
GT_EQ(rhs IntervalExpression) BoolExpression
BETWEEN(min, max IntervalExpression) BoolExpression
NOT_BETWEEN(min, max IntervalExpression) BoolExpression
ADD(rhs IntervalExpression) IntervalExpression
SUB(rhs IntervalExpression) IntervalExpression
MUL(rhs NumericExpression) IntervalExpression
DIV(rhs NumericExpression) IntervalExpression
}
type intervalInterfaceImpl struct {
parent IntervalExpression
}
func (i *intervalInterfaceImpl) isInterval() {}
func (i *intervalInterfaceImpl) EQ(rhs IntervalExpression) BoolExpression {
return Eq(i.parent, rhs)
}
func (i *intervalInterfaceImpl) NOT_EQ(rhs IntervalExpression) BoolExpression {
return NotEq(i.parent, rhs)
}
func (i *intervalInterfaceImpl) IS_DISTINCT_FROM(rhs IntervalExpression) BoolExpression {
return IsDistinctFrom(i.parent, rhs)
}
func (i *intervalInterfaceImpl) IS_NOT_DISTINCT_FROM(rhs IntervalExpression) BoolExpression {
return IsNotDistinctFrom(i.parent, rhs)
}
func (i *intervalInterfaceImpl) LT(rhs IntervalExpression) BoolExpression {
return Lt(i.parent, rhs)
}
func (i *intervalInterfaceImpl) LT_EQ(rhs IntervalExpression) BoolExpression {
return LtEq(i.parent, rhs)
}
func (i *intervalInterfaceImpl) GT(rhs IntervalExpression) BoolExpression {
return Gt(i.parent, rhs)
}
func (i *intervalInterfaceImpl) GT_EQ(rhs IntervalExpression) BoolExpression {
return GtEq(i.parent, rhs)
}
func (i *intervalInterfaceImpl) BETWEEN(min, max IntervalExpression) BoolExpression {
return NewBetweenOperatorExpression(i.parent, min, max, false)
}
func (i *intervalInterfaceImpl) NOT_BETWEEN(min, max IntervalExpression) BoolExpression {
return NewBetweenOperatorExpression(i.parent, min, max, true)
}
func (i *intervalInterfaceImpl) ADD(rhs IntervalExpression) IntervalExpression {
return IntervalExp(Add(i.parent, rhs))
}
func (i *intervalInterfaceImpl) SUB(rhs IntervalExpression) IntervalExpression {
return IntervalExp(Sub(i.parent, rhs))
}
func (i *intervalInterfaceImpl) MUL(rhs NumericExpression) IntervalExpression {
return IntervalExp(Mul(i.parent, rhs))
}
func (i *intervalInterfaceImpl) DIV(rhs NumericExpression) IntervalExpression {
return IntervalExp(Div(i.parent, rhs))
}
type intervalWrapper struct {
intervalInterfaceImpl
Expression
}
func newIntervalExpressionWrap(expression Expression) IntervalExpression {
intervalWrap := &intervalWrapper{Expression: expression}
intervalWrap.intervalInterfaceImpl.parent = intervalWrap
expression.setParent(intervalWrap)
return intervalWrap
}
// IntervalExp is interval expression wrapper around arbitrary expression.
// Allows go compiler to see any expression as interval expression.
// Does not add sql cast to generated sql builder output.
func IntervalExp(expression Expression) IntervalExpression {
return newIntervalExpressionWrap(expression)
}
// Interval interface
type Interval interface {
Serializer
isInterval()
}

View file

@ -412,17 +412,6 @@ func Raw(raw string, namedArgs ...map[string]interface{}) Expression {
return 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 // RawBool helper that for raw string boolean expressions
func RawBool(raw string, namedArgs ...map[string]interface{}) BoolExpression { func RawBool(raw string, namedArgs ...map[string]interface{}) BoolExpression {
return BoolExp(Raw(raw, namedArgs...)) return BoolExp(Raw(raw, namedArgs...))

View file

@ -3,7 +3,8 @@ package jet
// Projection is interface for all projection types. Types that can be part of, for instance SELECT clause. // Projection is interface for all projection types. Types that can be part of, for instance SELECT clause.
type Projection interface { type Projection interface {
serializeForProjection(statement StatementType, out *SQLBuilder) serializeForProjection(statement StatementType, out *SQLBuilder)
serializeForJsonObj(statement StatementType, out *SQLBuilder) serializeForJsonObjEntry(statement StatementType, out *SQLBuilder)
serializeForRowToJsonProjection(statement StatementType, out *SQLBuilder)
fromImpl(subQuery SelectTable) Projection fromImpl(subQuery SelectTable) Projection
} }
@ -29,7 +30,7 @@ func (pl ProjectionList) serializeForProjection(statement StatementType, out *SQ
SerializeProjectionList(statement, pl, out) SerializeProjectionList(statement, pl, out)
} }
func (pl ProjectionList) serializeForJsonObj(statement StatementType, out *SQLBuilder) { func (pl ProjectionList) serializeForJsonObjEntry(statement StatementType, out *SQLBuilder) {
SerializeProjectionListJsonObj(statement, pl, out) SerializeProjectionListJsonObj(statement, pl, out)
} }
@ -85,9 +86,17 @@ func (pl ProjectionList) Except(toExclude ...Column) ProjectionList {
return ret return ret
} }
// JsonProjectionList redefines []Projection so projections can be serialized as json object key/values func (pl ProjectionList) serializeForRowToJsonProjection(statement StatementType, out *SQLBuilder) {
type JsonProjectionList []Projection out.WriteRowToJsonProjections(statement, pl)
}
func (j JsonProjectionList) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) {
SerializeProjectionListJsonObj(statement, j, out) // JsonObjProjectionList redefines []Projection so projections can be serialized as json object key/values
type JsonObjProjectionList []Projection
func (j JsonObjProjectionList) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) {
out.IncreaseIdent()
out.NewLine()
SerializeProjectionListJsonObj(statement, j, out)
out.DecreaseIdent()
out.NewLine()
} }

View file

@ -118,9 +118,10 @@ type rangeExpressionWrapper[T Expression] struct {
} }
func newRangeExpressionWrap[T Expression](expression Expression) Range[T] { func newRangeExpressionWrap[T Expression](expression Expression) Range[T] {
rangeExpressionWrap := rangeExpressionWrapper[T]{Expression: expression} rangeExpressionWrap := &rangeExpressionWrapper[T]{Expression: expression}
rangeExpressionWrap.rangeInterfaceImpl.parent = &rangeExpressionWrap rangeExpressionWrap.rangeInterfaceImpl.parent = rangeExpressionWrap
return &rangeExpressionWrap expression.setParent(rangeExpressionWrap)
return rangeExpressionWrap
} }
// RangeExp is range expression wrapper around arbitrary expression. // RangeExp is range expression wrapper around arbitrary expression.

View file

@ -19,7 +19,7 @@ type RowExpression interface {
type rowInterfaceImpl struct { type rowInterfaceImpl struct {
parent Expression parent Expression
dialect Dialect dialect Dialect
elemCount int expressions []Expression
} }
func (n *rowInterfaceImpl) EQ(rhs RowExpression) BoolExpression { func (n *rowInterfaceImpl) EQ(rhs RowExpression) BoolExpression {
@ -57,9 +57,8 @@ func (n *rowInterfaceImpl) LT_EQ(rhs RowExpression) BoolExpression {
func (n *rowInterfaceImpl) projections() ProjectionList { func (n *rowInterfaceImpl) projections() ProjectionList {
var ret ProjectionList var ret ProjectionList
for i := 0; i < n.elemCount; i++ { for i, expression := range n.expressions {
rowColumn := NewColumnImpl(n.dialect.ValuesDefaultColumnName(i), "", nil) ret = append(ret, newDummyColumnForExpression(expression, n.dialect.ValuesDefaultColumnName(i)))
ret = append(ret, &rowColumn)
} }
return ret return ret
@ -77,7 +76,7 @@ func newRowExpression(name string, dialect Dialect, expressions ...Expression) R
ret.Expression = NewFunc(name, expressions, ret) ret.Expression = NewFunc(name, expressions, ret)
ret.dialect = dialect ret.dialect = dialect
ret.elemCount = len(expressions) ret.expressions = expressions
return ret return ret
} }

View file

@ -22,10 +22,6 @@ func (s SerializeOption) WithFallTrough(options []SerializeOption) []SerializeOp
// StatementType is type of the SQL statement // StatementType is type of the SQL statement
type StatementType string type StatementType string
func (s StatementType) IsSelectJSON() bool {
return s == SelectJsonObjStatementType || s == SelectJsonArrStatementType
}
// Statement types // Statement types
const ( const (
SelectStatementType StatementType = "SELECT" SelectStatementType StatementType = "SELECT"

View file

@ -61,6 +61,16 @@ func (s *SQLBuilder) WriteProjections(statement StatementType, projections []Pro
s.DecreaseIdent() s.DecreaseIdent()
} }
func (s *SQLBuilder) WriteRowToJsonProjections(statement StatementType, projections []Projection) {
for i, projection := range projections {
if i > 0 {
s.WriteString(",")
s.NewLine()
}
projection.serializeForRowToJsonProjection(statement, s)
}
}
// NewLine adds new line to output SQL // NewLine adds new line to output SQL
func (s *SQLBuilder) NewLine() { func (s *SQLBuilder) NewLine() {
s.write([]byte{'\n'}) s.write([]byte{'\n'})

View file

@ -257,12 +257,13 @@ type expressionStatementImpl struct {
} }
func (s *expressionStatementImpl) serializeForProjection(statement StatementType, out *SQLBuilder) { func (s *expressionStatementImpl) serializeForProjection(statement StatementType, out *SQLBuilder) {
if statement.IsSelectJSON() {
panic("jet: SELECT JSON statements need to be aliased when used as a projection.")
}
s.serialize(statement, out) s.serialize(statement, out)
} }
func (e *expressionStatementImpl) serializeForRowToJsonProjection(statement StatementType, out *SQLBuilder) {
panic("jet: SELECT JSON statements need to be aliased when used as a projection.")
}
// NewStatementImpl creates new statementImpl // NewStatementImpl creates new statementImpl
func NewStatementImpl(Dialect Dialect, statementType StatementType, parent SerializerStatement, clauses ...Clause) SerializerStatement { func NewStatementImpl(Dialect Dialect, statementType StatementType, parent SerializerStatement, clauses ...Clause) SerializerStatement {
return &statementImpl{ return &statementImpl{

View file

@ -105,9 +105,10 @@ type stringExpressionWrapper struct {
} }
func newStringExpressionWrap(expression Expression) StringExpression { func newStringExpressionWrap(expression Expression) StringExpression {
stringExpressionWrap := stringExpressionWrapper{Expression: expression} stringExpressionWrap := &stringExpressionWrapper{Expression: expression}
stringExpressionWrap.stringInterfaceImpl.parent = &stringExpressionWrap stringExpressionWrap.stringInterfaceImpl.parent = stringExpressionWrap
return &stringExpressionWrap expression.setParent(stringExpressionWrap)
return stringExpressionWrap
} }
// StringExp is string expression wrapper around arbitrary expression. // StringExp is string expression wrapper around arbitrary expression.

View file

@ -75,14 +75,15 @@ func (t *timeInterfaceImpl) SUB(rhs Interval) TimeExpression {
//---------------------------------------------------// //---------------------------------------------------//
type timeExpressionWrapper struct { type timeExpressionWrapper struct {
timeInterfaceImpl
Expression Expression
timeInterfaceImpl
} }
func newTimeExpressionWrap(expression Expression) TimeExpression { func newTimeExpressionWrap(expression Expression) TimeExpression {
timeExpressionWrap := timeExpressionWrapper{Expression: expression} timeExpressionWrap := &timeExpressionWrapper{Expression: expression}
timeExpressionWrap.timeInterfaceImpl.parent = &timeExpressionWrap timeExpressionWrap.timeInterfaceImpl.parent = timeExpressionWrap
return &timeExpressionWrap expression.setParent(timeExpressionWrap)
return timeExpressionWrap
} }
// TimeExp is time expression wrapper around arbitrary expression. // TimeExp is time expression wrapper around arbitrary expression.

View file

@ -52,11 +52,3 @@ func TestTimeExp(t *testing.T) {
assertClauseSerialize(t, TimeExp(table1ColFloat).LT(Time(1, 1, 1, 1*time.Millisecond)), assertClauseSerialize(t, TimeExp(table1ColFloat).LT(Time(1, 1, 1, 1*time.Millisecond)),
"(table1.col_float < $1)", string("01:01:01.001")) "(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

@ -80,9 +80,10 @@ type timestampExpressionWrapper struct {
} }
func newTimestampExpressionWrap(expression Expression) TimestampExpression { func newTimestampExpressionWrap(expression Expression) TimestampExpression {
timestampExpressionWrap := timestampExpressionWrapper{Expression: expression} timestampExpressionWrap := &timestampExpressionWrapper{Expression: expression}
timestampExpressionWrap.timestampInterfaceImpl.parent = &timestampExpressionWrap timestampExpressionWrap.timestampInterfaceImpl.parent = timestampExpressionWrap
return &timestampExpressionWrap expression.setParent(timestampExpressionWrap)
return timestampExpressionWrap
} }
// TimestampExp is timestamp expression wrapper around arbitrary expression. // TimestampExp is timestamp expression wrapper around arbitrary expression.

View file

@ -53,11 +53,3 @@ func TestTimestampExp(t *testing.T) {
assertClauseSerialize(t, TimestampExp(table1ColFloat).LT(timestamp), assertClauseSerialize(t, TimestampExp(table1ColFloat).LT(timestamp),
"(table1.col_float < $1)", "2000-01-31 10:20:00.003") "(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

@ -80,9 +80,10 @@ type timestampzExpressionWrapper struct {
} }
func newTimestampzExpressionWrap(expression Expression) TimestampzExpression { func newTimestampzExpressionWrap(expression Expression) TimestampzExpression {
timestampzExpressionWrap := timestampzExpressionWrapper{Expression: expression} timestampzExpressionWrap := &timestampzExpressionWrapper{Expression: expression}
timestampzExpressionWrap.timestampzInterfaceImpl.parent = &timestampzExpressionWrap timestampzExpressionWrap.timestampzInterfaceImpl.parent = timestampzExpressionWrap
return &timestampzExpressionWrap expression.setParent(timestampzExpressionWrap)
return timestampzExpressionWrap
} }
// TimestampzExp is timestamp with time zone expression wrapper around arbitrary expression. // TimestampzExp is timestamp with time zone expression wrapper around arbitrary expression.

View file

@ -53,11 +53,3 @@ func TestTimestampzExp(t *testing.T) {
assertClauseSerialize(t, TimestampzExp(table1ColFloat).LT(timestampz), assertClauseSerialize(t, TimestampzExp(table1ColFloat).LT(timestampz),
"(table1.col_float < $1)", "2000-01-31 10:20:05.000023 +200") "(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

@ -75,14 +75,15 @@ func (t *timezInterfaceImpl) SUB(rhs Interval) TimezExpression {
//---------------------------------------------------// //---------------------------------------------------//
type timezExpressionWrapper struct { type timezExpressionWrapper struct {
timezInterfaceImpl
Expression Expression
timezInterfaceImpl
} }
func newTimezExpressionWrap(expression Expression) TimezExpression { func newTimezExpressionWrap(expression Expression) TimezExpression {
timezExpressionWrap := timezExpressionWrapper{Expression: expression} timezExpressionWrap := &timezExpressionWrapper{Expression: expression}
timezExpressionWrap.timezInterfaceImpl.parent = &timezExpressionWrap timezExpressionWrap.timezInterfaceImpl.parent = timezExpressionWrap
return &timezExpressionWrap expression.setParent(timezExpressionWrap)
return timezExpressionWrap
} }
// TimezExp is time with time zone expression wrapper around arbitrary expression. // TimezExp is time with time zone expression wrapper around arbitrary expression.

View file

@ -51,11 +51,3 @@ func TestTimezExp(t *testing.T) {
assertClauseSerialize(t, TimezExp(table1ColFloat).LT(Timez(1, 1, 1, 1, "+4:00")), assertClauseSerialize(t, TimezExp(table1ColFloat).LT(Timez(1, 1, 1, 1, "+4:00")),
"(table1.col_float < $1)", string("01:01:01.000000001 +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

@ -71,7 +71,7 @@ func SerializeProjectionListJsonObj(statement StatementType, projections []Proje
panic("jet: Projection is nil") panic("jet: Projection is nil")
} }
p.serializeForJsonObj(statement, out) p.serializeForJsonObjEntry(statement, out)
} }
} }

View file

@ -122,7 +122,7 @@ func AssertJsonEqual(t require.TestingT, actual, expected interface{}, option ..
expectedJsonData, err := json.MarshalIndent(expected, "", "\t") expectedJsonData, err := json.MarshalIndent(expected, "", "\t")
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, actualJsonData, expectedJsonData) require.Equal(t, string(actualJsonData), string(expectedJsonData))
} }
// SaveJSONFile saves v as json at testRelativePath // SaveJSONFile saves v as json at testRelativePath

View file

@ -6,7 +6,7 @@ import (
"github.com/go-jet/jet/v2/internal/jet" "github.com/go-jet/jet/v2/internal/jet"
) )
// Dialect is implementation of MySQL dialect for SQL Builder serialisation. // Dialect is implementation of MySQL dialect for SQL Builder serialization.
var Dialect = newDialect() var Dialect = newDialect()
func newDialect() jet.Dialect { func newDialect() jet.Dialect {
@ -34,6 +34,23 @@ func newDialect() jet.Dialect {
ValuesDefaultColumnName: func(index int) string { ValuesDefaultColumnName: func(index int) string {
return fmt.Sprintf("column_%d", index) return fmt.Sprintf("column_%d", index)
}, },
JsonValueEncode: func(expr Expression) Expression {
switch e := expr.(type) {
case BlobExpression:
return TO_BASE64(e)
// CustomExpression used bellow (instead DATE_FORMAT function) so that only expr is parametrized
case TimestampExpression:
return CustomExpression(Token("DATE_FORMAT("), e, Token(",'%Y-%m-%dT%H:%i:%s.%fZ')"))
case TimeExpression:
return CustomExpression(Token("CONCAT('0000-01-01T', DATE_FORMAT("), e, Token(",'%H:%i:%s.%fZ'))"))
case DateExpression:
return CustomExpression(Token("CONCAT(DATE_FORMAT("), e, Token(",'%Y-%m-%d')"), Token(", 'T00:00:00Z')"))
case BoolExpression:
return CustomExpression(e, Token(" = 1"))
}
return expr
},
} }
return jet.NewDialect(mySQLDialectParams) return jet.NewDialect(mySQLDialectParams)

View file

@ -98,13 +98,10 @@ func INTERVAL(value interface{}, unitType unitType) Interval {
// INTERVALe creates new temporal interval from expresion and unit type. // INTERVALe creates new temporal interval from expresion and unit type.
func INTERVALe(expr Expression, unitType unitType) Interval { func INTERVALe(expr Expression, unitType unitType) Interval {
return jet.NewInterval(jet.ListSerializer{ return jet.IntervalExp(CustomExpression(Token("INTERVAL"), expr, Token(unitType)))
Serializers: []jet.Serializer{expr, jet.RawWithParent(string(unitType))},
Separator: " ",
})
} }
// INTERVALd temoral interval from time.Duration // INTERVALd creates new temporal interval from time.Duration
func INTERVALd(duration time.Duration) Interval { func INTERVALd(duration time.Duration) Interval {
var sign int64 = 1 var sign int64 = 1
if duration < 0 { if duration < 0 {

View file

@ -43,7 +43,7 @@ func newSelectStatementJson(projections []Projection, statementType jet.Statemen
} }
func constructJsonFunc(projections []Projection, statementType jet.StatementType) Expression { func constructJsonFunc(projections []Projection, statementType jet.StatementType) Expression {
jsonObj := Func("JSON_OBJECT", CustomExpression(jet.JsonProjectionList(projections))) jsonObj := Func("JSON_OBJECT", CustomExpression(jet.JsonObjProjectionList(projections)))
if statementType == jet.SelectJsonArrStatementType { if statementType == jet.SelectJsonArrStatementType {
return Func("JSON_ARRAYAGG", jsonObj) return Func("JSON_ARRAYAGG", jsonObj)

View file

@ -71,6 +71,12 @@ type ColumnTimestampz = jet.ColumnTimestampz
// TimestampzColumn creates named timestamp with time zone column. // TimestampzColumn creates named timestamp with time zone column.
var TimestampzColumn = jet.TimestampzColumn var TimestampzColumn = jet.TimestampzColumn
// ColumnInterval is interface of PostgreSQL interval columns.
type ColumnInterval = jet.ColumnInterval
// IntervalColumn creates named interval column
var IntervalColumn = jet.IntervalColumn
// ColumnDateRange is interface of SQL date range column // ColumnDateRange is interface of SQL date range column
type ColumnDateRange = jet.ColumnRange[DateExpression] type ColumnDateRange = jet.ColumnRange[DateExpression]
@ -106,41 +112,3 @@ type ColumnInt8Range jet.ColumnRange[jet.Int8Expression]
// Int8RangeColumn creates named range with range column // Int8RangeColumn creates named range with range column
var Int8RangeColumn = jet.RangeColumn[jet.Int8Expression] var Int8RangeColumn = jet.RangeColumn[jet.Int8Expression]
//------------------------------------------------------//
// ColumnInterval is interface of PostgreSQL interval columns.
type ColumnInterval interface {
IntervalExpression
jet.Column
From(subQuery SelectTable) ColumnInterval
SET(intervalExp IntervalExpression) ColumnAssigment
}
//------------------------------------------------------//
type intervalColumnImpl struct {
jet.ColumnExpressionImpl
intervalInterfaceImpl
}
func (i *intervalColumnImpl) SET(intervalExp IntervalExpression) ColumnAssigment {
return jet.NewColumnAssignment(i, intervalExp)
}
func (i *intervalColumnImpl) From(subQuery SelectTable) ColumnInterval {
newIntervalColumn := IntervalColumn(i.Name())
jet.SetTableName(newIntervalColumn, i.TableName())
jet.SetSubQuery(newIntervalColumn, subQuery)
return newIntervalColumn
}
// IntervalColumn creates named interval column.
func IntervalColumn(name string) ColumnInterval {
intervalColumn := &intervalColumnImpl{}
intervalColumn.ColumnExpressionImpl = jet.NewColumnImpl(name, "", intervalColumn)
intervalColumn.intervalInterfaceImpl.parent = intervalColumn
return intervalColumn
}

View file

@ -3,9 +3,8 @@ package postgres
import ( import (
"encoding/hex" "encoding/hex"
"fmt" "fmt"
"strconv"
"github.com/go-jet/jet/v2/internal/jet" "github.com/go-jet/jet/v2/internal/jet"
"strconv"
) )
// Dialect is implementation of postgres dialect for SQL Builder serialisation. // Dialect is implementation of postgres dialect for SQL Builder serialisation.
@ -32,6 +31,23 @@ func newDialect() jet.Dialect {
ValuesDefaultColumnName: func(index int) string { ValuesDefaultColumnName: func(index int) string {
return fmt.Sprintf("column%d", index+1) return fmt.Sprintf("column%d", index+1)
}, },
JsonValueEncode: func(expr Expression) Expression {
switch e := expr.(type) {
case ByteaExpression:
return ENCODE(e, Base64)
// CustomExpression used bellow (instead TO_CHAR function) so that only expr is parametrized
case TimeExpression:
return CustomExpression(Token("'0000-01-01T' || to_char('2000-10-10'::date + "), e, Token(`, 'HH24:MI:SS.USZ')`))
case TimezExpression:
return CustomExpression(Token("'0000-01-01T' || to_char('2000-10-10'::date + "), e, Token(`, 'HH24:MI:SS.USTZH:TZM')`))
case TimestampExpression:
return CustomExpression(Token("to_char("), e, Token(`, 'YYYY-MM-DD"T"HH24:MI:SS.USZ')`))
case DateExpression:
return CustomExpression(Token("to_char("), e, Token(`::timestamp, 'YYYY-MM-DD') || 'T00:00:00Z'`))
}
return expr
},
} }
return jet.NewDialect(dialectParams) return jet.NewDialect(dialectParams)

View file

@ -41,6 +41,9 @@ type TimestampzExpression = jet.TimestampzExpression
// RowExpression interface // RowExpression interface
type RowExpression = jet.RowExpression type RowExpression = jet.RowExpression
// IntervalExpression interface
type IntervalExpression = jet.IntervalExpression
// DateRange Expression interface // DateRange Expression interface
type DateRange = jet.Range[DateExpression] type DateRange = jet.Range[DateExpression]
@ -109,6 +112,11 @@ var TimestampExp = jet.TimestampExp
// Does not add sql cast to generated sql builder output. // Does not add sql cast to generated sql builder output.
var TimestampzExp = jet.TimestampzExp var TimestampzExp = jet.TimestampzExp
// IntervalExp is interval expression wrapper around arbitrary expression.
// Allows go compiler to see any expression as interval expression.
// Does not add sql cast to generated sql builder output.
var IntervalExp = jet.IntervalExp
// RowExp serves as a wrapper for an arbitrary expression, treating it as a row expression. // 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 // 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. // Note: This does not modify the generated SQL builder output by adding a SQL CAST operation.

View file

@ -194,18 +194,18 @@ func CONCAT_WS(separator Expression, expressions ...Expression) StringExpression
// Character encodings for CONVERT, CONVERT_FROM and CONVERT_TO functions // Character encodings for CONVERT, CONVERT_FROM and CONVERT_TO functions
var ( var (
UTF8 = String("UTF8") UTF8 = StringExp(jet.FixedLiteral("UTF8"))
LATIN1 = String("LATIN1") LATIN1 = StringExp(jet.FixedLiteral("LATIN1"))
LATIN2 = String("LATIN2") LATIN2 = StringExp(jet.FixedLiteral("LATIN2"))
LATIN3 = String("LATIN3") LATIN3 = StringExp(jet.FixedLiteral("LATIN3"))
LATIN4 = String("LATIN4") LATIN4 = StringExp(jet.FixedLiteral("LATIN4"))
WIN1252 = String("WIN1252") WIN1252 = StringExp(jet.FixedLiteral("WIN1252"))
ISO_8859_5 = String("ISO_8859_5") ISO_8859_5 = StringExp(jet.FixedLiteral("ISO_8859_5"))
ISO_8859_6 = String("ISO_8859_6") ISO_8859_6 = StringExp(jet.FixedLiteral("ISO_8859_6"))
ISO_8859_7 = String("ISO_8859_7") ISO_8859_7 = StringExp(jet.FixedLiteral("ISO_8859_7"))
ISO_8859_8 = String("ISO_8859_8") ISO_8859_8 = StringExp(jet.FixedLiteral("ISO_8859_8"))
KOI8R = String("KOI8R") KOI8R = StringExp(jet.FixedLiteral("KOI8R"))
KOI8U = String("KOI8U") KOI8U = StringExp(jet.FixedLiteral("KOI8U"))
) )
// CONVERT converts string to dest_encoding. The original encoding is // CONVERT converts string to dest_encoding. The original encoding is
@ -223,9 +223,9 @@ var CONVERT_TO = jet.CONVERT_TO
// ENCODE/DECODE textual formats // ENCODE/DECODE textual formats
var ( var (
Base64 StringExpression = String("base64") Base64 = StringExp(jet.FixedLiteral("base64"))
Escape StringExpression = String("escape") Escape = StringExp(jet.FixedLiteral("escape"))
Hex StringExpression = String("hex") Hex = StringExp(jet.FixedLiteral("hex"))
) )
// ENCODE encodes binary data into a textual representation. // ENCODE encodes binary data into a textual representation.

View file

@ -1,257 +0,0 @@
package postgres
import (
"fmt"
"github.com/go-jet/jet/v2/internal/jet"
"github.com/go-jet/jet/v2/internal/utils/datetime"
"strconv"
"strings"
"time"
)
type quantityAndUnit = float64
type unit = float64
// Interval unit types
const (
YEAR unit = 123456789 + iota
MONTH
WEEK
DAY
HOUR
MINUTE
SECOND
MILLISECOND
MICROSECOND
DECADE
CENTURY
MILLENNIUM
)
// IntervalExpression is representation of postgres INTERVAL
type IntervalExpression interface {
jet.IsInterval
jet.Expression
EQ(rhs IntervalExpression) BoolExpression
NOT_EQ(rhs IntervalExpression) BoolExpression
IS_DISTINCT_FROM(rhs IntervalExpression) BoolExpression
IS_NOT_DISTINCT_FROM(rhs IntervalExpression) BoolExpression
LT(rhs IntervalExpression) BoolExpression
LT_EQ(rhs IntervalExpression) BoolExpression
GT(rhs IntervalExpression) BoolExpression
GT_EQ(rhs IntervalExpression) BoolExpression
BETWEEN(min, max IntervalExpression) BoolExpression
NOT_BETWEEN(min, max IntervalExpression) BoolExpression
ADD(rhs IntervalExpression) IntervalExpression
SUB(rhs IntervalExpression) IntervalExpression
MUL(rhs NumericExpression) IntervalExpression
DIV(rhs NumericExpression) IntervalExpression
}
type intervalInterfaceImpl struct {
jet.IsIntervalImpl
parent IntervalExpression
}
func (i *intervalInterfaceImpl) EQ(rhs IntervalExpression) BoolExpression {
return jet.Eq(i.parent, rhs)
}
func (i *intervalInterfaceImpl) NOT_EQ(rhs IntervalExpression) BoolExpression {
return jet.NotEq(i.parent, rhs)
}
func (i *intervalInterfaceImpl) IS_DISTINCT_FROM(rhs IntervalExpression) BoolExpression {
return jet.IsDistinctFrom(i.parent, rhs)
}
func (i *intervalInterfaceImpl) IS_NOT_DISTINCT_FROM(rhs IntervalExpression) BoolExpression {
return jet.IsNotDistinctFrom(i.parent, rhs)
}
func (i *intervalInterfaceImpl) LT(rhs IntervalExpression) BoolExpression {
return jet.Lt(i.parent, rhs)
}
func (i *intervalInterfaceImpl) LT_EQ(rhs IntervalExpression) BoolExpression {
return jet.LtEq(i.parent, rhs)
}
func (i *intervalInterfaceImpl) GT(rhs IntervalExpression) BoolExpression {
return jet.Gt(i.parent, rhs)
}
func (i *intervalInterfaceImpl) GT_EQ(rhs IntervalExpression) BoolExpression {
return jet.GtEq(i.parent, rhs)
}
func (i *intervalInterfaceImpl) BETWEEN(min, max IntervalExpression) BoolExpression {
return jet.NewBetweenOperatorExpression(i.parent, min, max, false)
}
func (i *intervalInterfaceImpl) NOT_BETWEEN(min, max IntervalExpression) BoolExpression {
return jet.NewBetweenOperatorExpression(i.parent, min, max, true)
}
func (i *intervalInterfaceImpl) ADD(rhs IntervalExpression) IntervalExpression {
return IntervalExp(jet.Add(i.parent, rhs))
}
func (i *intervalInterfaceImpl) SUB(rhs IntervalExpression) IntervalExpression {
return IntervalExp(jet.Sub(i.parent, rhs))
}
func (i *intervalInterfaceImpl) MUL(rhs NumericExpression) IntervalExpression {
return IntervalExp(jet.Mul(i.parent, rhs))
}
func (i *intervalInterfaceImpl) DIV(rhs NumericExpression) IntervalExpression {
return IntervalExp(jet.Div(i.parent, rhs))
}
type intervalExpression struct {
jet.Expression
intervalInterfaceImpl
}
// INTERVAL creates new interval expression from the list of quantity-unit pairs.
//
// INTERVAL(1, DAY, 3, MINUTE)
func INTERVAL(quantityAndUnit ...quantityAndUnit) IntervalExpression {
quantityAndUnitLen := len(quantityAndUnit)
if quantityAndUnitLen == 0 || quantityAndUnitLen%2 != 0 {
panic("jet: invalid number of quantity and unit fields")
}
var fields []string
for i := 0; i < len(quantityAndUnit); i += 2 {
quantity := strconv.FormatFloat(quantityAndUnit[i], 'f', -1, 64)
unitString := unitToString(quantityAndUnit[i+1])
fields = append(fields, quantity+" "+unitString)
}
intervalStr := fmt.Sprintf("INTERVAL '%s'", strings.Join(fields, " "))
newInterval := &intervalExpression{}
newInterval.Expression = jet.RawWithParent(intervalStr, newInterval)
newInterval.intervalInterfaceImpl.parent = newInterval
return newInterval
}
// INTERVALd creates interval expression from time.Duration
func INTERVALd(duration time.Duration) IntervalExpression {
days, hours, minutes, seconds, microseconds := datetime.ExtractTimeComponents(duration)
var quantityAndUnits []quantityAndUnit
if days > 0 {
quantityAndUnits = append(quantityAndUnits, quantityAndUnit(days))
quantityAndUnits = append(quantityAndUnits, DAY)
}
if hours > 0 {
quantityAndUnits = append(quantityAndUnits, quantityAndUnit(hours))
quantityAndUnits = append(quantityAndUnits, HOUR)
}
if minutes > 0 {
quantityAndUnits = append(quantityAndUnits, quantityAndUnit(minutes))
quantityAndUnits = append(quantityAndUnits, MINUTE)
}
if seconds > 0 {
quantityAndUnits = append(quantityAndUnits, quantityAndUnit(seconds))
quantityAndUnits = append(quantityAndUnits, SECOND)
}
if microseconds > 0 {
quantityAndUnits = append(quantityAndUnits, quantityAndUnit(microseconds))
quantityAndUnits = append(quantityAndUnits, MICROSECOND)
}
if len(quantityAndUnits) == 0 {
return INTERVAL(0, MICROSECOND)
}
return INTERVAL(quantityAndUnits...)
}
func unitToString(unit quantityAndUnit) string {
switch unit {
case YEAR:
return "YEAR"
case MONTH:
return "MONTH"
case WEEK:
return "WEEK"
case DAY:
return "DAY"
case HOUR:
return "HOUR"
case MINUTE:
return "MINUTE"
case SECOND:
return "SECOND"
case MILLISECOND:
return "MILLISECOND"
case MICROSECOND:
return "MICROSECOND"
case DECADE:
return "DECADE"
case CENTURY:
return "CENTURY"
case MILLENNIUM:
return "MILLENNIUM"
// additional field units for EXTRACT function
case DOW:
return "DOW"
case DOY:
return "DOY"
case EPOCH:
return "EPOCH"
case ISODOW:
return "ISODOW"
case ISOYEAR:
return "ISOYEAR"
case JULIAN:
return "JULIAN"
case QUARTER:
return "QUARTER"
case TIMEZONE:
return "TIMEZONE"
case TIMEZONE_HOUR:
return "TIMEZONE_HOUR"
case TIMEZONE_MINUTE:
return "TIMEZONE_MINUTE"
default:
panic("jet: invalid INTERVAL unit type")
}
}
//---------------------------------------------------//
type intervalWrapper struct {
intervalInterfaceImpl
Expression
}
func newIntervalExpressionWrap(expression Expression) IntervalExpression {
intervalWrap := &intervalWrapper{Expression: expression}
intervalWrap.intervalInterfaceImpl.parent = intervalWrap
return intervalWrap
}
// IntervalExp is interval expression wrapper around arbitrary expression.
// Allows go compiler to see any expression as interval expression.
// Does not add sql cast to generated sql builder output.
func IntervalExp(expression Expression) IntervalExpression {
return newIntervalExpressionWrap(expression)
}

View file

@ -0,0 +1,140 @@
package postgres
import (
"fmt"
"github.com/go-jet/jet/v2/internal/utils/datetime"
"strconv"
"strings"
"time"
)
type quantityAndUnit = float64
type unit = float64
// Interval unit types
const (
YEAR unit = 123456789 + iota
MONTH
WEEK
DAY
HOUR
MINUTE
SECOND
MILLISECOND
MICROSECOND
DECADE
CENTURY
MILLENNIUM
)
// INTERVAL creates new interval expression from the list of quantity-unit pairs.
//
// INTERVAL(1, DAY, 3, MINUTE)
func INTERVAL(quantityAndUnit ...quantityAndUnit) IntervalExpression {
quantityAndUnitLen := len(quantityAndUnit)
if quantityAndUnitLen == 0 || quantityAndUnitLen%2 != 0 {
panic("jet: invalid number of quantity and unit fields")
}
var fields []string
for i := 0; i < len(quantityAndUnit); i += 2 {
quantity := strconv.FormatFloat(quantityAndUnit[i], 'f', -1, 64)
unitString := unitToString(quantityAndUnit[i+1])
fields = append(fields, quantity+" "+unitString)
}
return IntervalExp(CustomExpression(Token(fmt.Sprintf("INTERVAL '%s'", strings.Join(fields, " ")))))
}
// INTERVALd creates interval expression from time.Duration
func INTERVALd(duration time.Duration) IntervalExpression {
days, hours, minutes, seconds, microseconds := datetime.ExtractTimeComponents(duration)
var quantityAndUnits []quantityAndUnit
if days > 0 {
quantityAndUnits = append(quantityAndUnits, quantityAndUnit(days))
quantityAndUnits = append(quantityAndUnits, DAY)
}
if hours > 0 {
quantityAndUnits = append(quantityAndUnits, quantityAndUnit(hours))
quantityAndUnits = append(quantityAndUnits, HOUR)
}
if minutes > 0 {
quantityAndUnits = append(quantityAndUnits, quantityAndUnit(minutes))
quantityAndUnits = append(quantityAndUnits, MINUTE)
}
if seconds > 0 {
quantityAndUnits = append(quantityAndUnits, quantityAndUnit(seconds))
quantityAndUnits = append(quantityAndUnits, SECOND)
}
if microseconds > 0 {
quantityAndUnits = append(quantityAndUnits, quantityAndUnit(microseconds))
quantityAndUnits = append(quantityAndUnits, MICROSECOND)
}
if len(quantityAndUnits) == 0 {
return INTERVAL(0, MICROSECOND)
}
return INTERVAL(quantityAndUnits...)
}
func unitToString(unit quantityAndUnit) string {
switch unit {
case YEAR:
return "YEAR"
case MONTH:
return "MONTH"
case WEEK:
return "WEEK"
case DAY:
return "DAY"
case HOUR:
return "HOUR"
case MINUTE:
return "MINUTE"
case SECOND:
return "SECOND"
case MILLISECOND:
return "MILLISECOND"
case MICROSECOND:
return "MICROSECOND"
case DECADE:
return "DECADE"
case CENTURY:
return "CENTURY"
case MILLENNIUM:
return "MILLENNIUM"
// additional field units for EXTRACT function
case DOW:
return "DOW"
case DOY:
return "DOY"
case EPOCH:
return "EPOCH"
case ISODOW:
return "ISODOW"
case ISOYEAR:
return "ISOYEAR"
case JULIAN:
return "JULIAN"
case QUARTER:
return "QUARTER"
case TIMEZONE:
return "TIMEZONE"
case TIMEZONE_HOUR:
return "TIMEZONE_HOUR"
case TIMEZONE_MINUTE:
return "TIMEZONE_MINUTE"
default:
panic("jet: invalid INTERVAL unit type")
}
}
//---------------------------------------------------//

View file

@ -105,6 +105,7 @@ func newSelectStatementJson(projections []Projection, statementType jet.Statemen
} }
newSelectJson.setOperatorsImpl.stmtRoot = newSelectJson newSelectJson.setOperatorsImpl.stmtRoot = newSelectJson
newSelectJson.subQuery.Select.IsForRowToJson = true
newSelectJson.setSubQueryAlias("") newSelectJson.setSubQueryAlias("")

View file

@ -3,9 +3,9 @@ package qrm
import ( import (
"context" "context"
"database/sql" "database/sql"
"encoding/json"
"errors" "errors"
"fmt" "fmt"
"github.com/go-jet/jet/v2/internal/3rdparty/json"
"github.com/go-jet/jet/v2/internal/utils/must" "github.com/go-jet/jet/v2/internal/utils/must"
"reflect" "reflect"
) )
@ -67,8 +67,8 @@ func QueryJsonArr(ctx context.Context, db Queryable, query string, args []interf
return queryJson(ctx, db, query, args, destPtr) return queryJson(ctx, db, query, args, destPtr)
} }
var jsonDestObjErr = "jet: destination has to be a pointer to struct or pointer to map[string]any" var jsonDestObjErr = "jet: SELECT_JSON_OBJ destination has to be a pointer to struct or pointer to map[string]any"
var jsonDestArrErr = "jet: destination has to be a pointer to slice of struct or pointer to []map[string]any" var jsonDestArrErr = "jet: SELECT_JSON_ARR destination has to be a pointer to slice of struct or pointer to []map[string]any"
func queryJson(ctx context.Context, db Queryable, query string, args []interface{}, destPtr interface{}) (rowsProcessed int64, err error) { func queryJson(ctx context.Context, db Queryable, query string, args []interface{}, destPtr interface{}) (rowsProcessed int64, err error) {
must.BeInitializedPtr(db, "jet: db is nil") must.BeInitializedPtr(db, "jet: db is nil")

View file

@ -43,29 +43,82 @@ func TestAllTypesJSON(t *testing.T) {
AllTypes.JSONPtr, AllTypes.JSONPtr,
AllTypes.Bit, AllTypes.Bit,
AllTypes.BitPtr, AllTypes.BitPtr,
AllTypes.Blob,
AllTypes.BlobPtr,
AllTypes.Binary,
AllTypes.BinaryPtr,
AllTypes.VarBinary,
AllTypes.VarBinaryPtr,
), ),
CAST(AllTypes.JSON).AS_CHAR().AS("Json"), CAST(AllTypes.JSON).AS_CHAR().AS("Json"),
CAST(AllTypes.JSONPtr).AS_CHAR().AS("JsonPtr"), CAST(AllTypes.JSONPtr).AS_CHAR().AS("JsonPtr"),
CAST(AllTypes.Bit).AS_CHAR().AS("Bit"), CAST(AllTypes.Bit).AS_CHAR().AS("Bit"),
CAST(AllTypes.BitPtr).AS_CHAR().AS("BitPtr"), CAST(AllTypes.BitPtr).AS_CHAR().AS("BitPtr"),
// TODO: remove when binary string is implemented
CONCAT(String("\\x"), HEX(AllTypes.Blob)).AS("Blob"),
CONCAT(String("\\x"), HEX(AllTypes.BlobPtr)).AS("BlobPtr"),
CONCAT(String("\\x"), HEX(AllTypes.Binary)).AS("Binary"),
CONCAT(String("\\x"), HEX(AllTypes.BinaryPtr)).AS("BinaryPtr"),
CONCAT(String("\\x"), HEX(AllTypes.VarBinary)).AS("VarBinary"),
CONCAT(String("\\x"), HEX(AllTypes.VarBinaryPtr)).AS("VarBinaryPtr"),
).FROM(AllTypes) ).FROM(AllTypes)
testutils.AssertStatementSql(t, stmt, strings.ReplaceAll(`
SELECT JSON_ARRAYAGG(JSON_OBJECT(
'id', all_types.id,
'boolean', all_types.boolean = 1,
'booleanPtr', all_types.boolean_ptr = 1,
'tinyInt', all_types.tiny_int,
'uTinyInt', all_types.u_tiny_int,
'smallInt', all_types.small_int,
'uSmallInt', all_types.u_small_int,
'mediumInt', all_types.medium_int,
'uMediumInt', all_types.u_medium_int,
'integer', all_types.''integer'',
'uInteger', all_types.u_integer,
'bigInt', all_types.big_int,
'uBigInt', all_types.u_big_int,
'tinyIntPtr', all_types.tiny_int_ptr,
'uTinyIntPtr', all_types.u_tiny_int_ptr,
'smallIntPtr', all_types.small_int_ptr,
'uSmallIntPtr', all_types.u_small_int_ptr,
'mediumIntPtr', all_types.medium_int_ptr,
'uMediumIntPtr', all_types.u_medium_int_ptr,
'integerPtr', all_types.integer_ptr,
'uIntegerPtr', all_types.u_integer_ptr,
'bigIntPtr', all_types.big_int_ptr,
'uBigIntPtr', all_types.u_big_int_ptr,
'decimal', all_types.''decimal'',
'decimalPtr', all_types.decimal_ptr,
'numeric', all_types.''numeric'',
'numericPtr', all_types.numeric_ptr,
'float', all_types.''float'',
'floatPtr', all_types.float_ptr,
'double', all_types.''double'',
'doublePtr', all_types.double_ptr,
'real', all_types.''real'',
'realPtr', all_types.real_ptr,
'time', CONCAT('0000-01-01T', DATE_FORMAT(all_types.time,'%H:%i:%s.%fZ')),
'timePtr', CONCAT('0000-01-01T', DATE_FORMAT(all_types.time_ptr,'%H:%i:%s.%fZ')),
'date', CONCAT(DATE_FORMAT(all_types.date,'%Y-%m-%d'), 'T00:00:00Z'),
'datePtr', CONCAT(DATE_FORMAT(all_types.date_ptr,'%Y-%m-%d'), 'T00:00:00Z'),
'dateTime', DATE_FORMAT(all_types.date_time,'%Y-%m-%dT%H:%i:%s.%fZ'),
'dateTimePtr', DATE_FORMAT(all_types.date_time_ptr,'%Y-%m-%dT%H:%i:%s.%fZ'),
'timestamp', DATE_FORMAT(all_types.timestamp,'%Y-%m-%dT%H:%i:%s.%fZ'),
'timestampPtr', DATE_FORMAT(all_types.timestamp_ptr,'%Y-%m-%dT%H:%i:%s.%fZ'),
'year', all_types.year,
'yearPtr', all_types.year_ptr,
'char', all_types.''char'',
'charPtr', all_types.char_ptr,
'varChar', all_types.var_char,
'varCharPtr', all_types.var_char_ptr,
'binary', TO_BASE64(all_types.''binary''),
'binaryPtr', TO_BASE64(all_types.binary_ptr),
'varBinary', TO_BASE64(all_types.var_binary),
'varBinaryPtr', TO_BASE64(all_types.var_binary_ptr),
'blob', TO_BASE64(all_types.''blob''),
'blobPtr', TO_BASE64(all_types.blob_ptr),
'text', all_types.text,
'textPtr', all_types.text_ptr,
'enum', all_types.enum,
'enumPtr', all_types.enum_ptr,
'set', all_types.''set'',
'setPtr', all_types.set_ptr,
'Json', CAST(all_types.json AS CHAR),
'JsonPtr', CAST(all_types.json_ptr AS CHAR),
'Bit', CAST(all_types.bit AS CHAR),
'BitPtr', CAST(all_types.bit_ptr AS CHAR)
)) AS "json"
FROM test_sample.all_types;
`, "''", "`"))
var dest []model.AllTypes var dest []model.AllTypes
err := stmt.QueryJSON(ctx, db, &dest) err := stmt.QueryJSON(ctx, db, &dest)
@ -76,7 +129,6 @@ func TestAllTypesJSON(t *testing.T) {
dest[0].FloatPtr = ptr.Of(3.33) dest[0].FloatPtr = ptr.Of(3.33)
dest[1].Float = 3.33 dest[1].Float = 3.33
//fmt.Println(allTypesJson)
testutils.AssertJSON(t, dest, allTypesJson) testutils.AssertJSON(t, dest, allTypesJson)
} }
@ -1182,6 +1234,118 @@ func TestAllTypesInsertOnDuplicateKeyUpdate(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
} }
func TestAllTypesSubQueryFrom(t *testing.T) {
subQuery := SELECT(
AllTypes.Boolean,
AllTypes.Integer,
AllTypes.Double,
AllTypes.Text,
AllTypes.Date,
AllTypes.Time,
AllTypes.Timestamp,
AllTypes.Blob,
).FROM(
AllTypes,
).AsTable("sub_query")
stmt := SELECT(
AllTypes.Boolean.From(subQuery),
AllTypes.Integer.From(subQuery),
AllTypes.Double.From(subQuery),
AllTypes.Text.From(subQuery),
AllTypes.Date.From(subQuery),
AllTypes.Time.From(subQuery),
AllTypes.Timestamp.From(subQuery),
AllTypes.Blob.From(subQuery),
).FROM(
subQuery,
)
testutils.AssertStatementSql(t, stmt, strings.ReplaceAll(`
SELECT sub_query.''all_types.boolean'' AS "all_types.boolean",
sub_query.''all_types.integer'' AS "all_types.integer",
sub_query.''all_types.double'' AS "all_types.double",
sub_query.''all_types.text'' AS "all_types.text",
sub_query.''all_types.date'' AS "all_types.date",
sub_query.''all_types.time'' AS "all_types.time",
sub_query.''all_types.timestamp'' AS "all_types.timestamp",
sub_query.''all_types.blob'' AS "all_types.blob"
FROM (
SELECT all_types.boolean AS "all_types.boolean",
all_types.''integer'' AS "all_types.integer",
all_types.''double'' AS "all_types.double",
all_types.text AS "all_types.text",
all_types.date AS "all_types.date",
all_types.time AS "all_types.time",
all_types.timestamp AS "all_types.timestamp",
all_types.''blob'' AS "all_types.blob"
FROM test_sample.all_types
) AS sub_query;
`, "''", "`"))
var dest []model.AllTypes
err := stmt.Query(db, &dest)
require.NoError(t, err)
require.NotEmpty(t, dest)
t.Run("using SELECT_JSON", func(t *testing.T) {
stmtJson := SELECT_JSON_ARR(
AllTypes.Boolean.From(subQuery),
AllTypes.Integer.From(subQuery),
AllTypes.Double.From(subQuery),
AllTypes.Text.From(subQuery),
AllTypes.Date.From(subQuery),
AllTypes.Time.From(subQuery),
AllTypes.Timestamp.From(subQuery),
AllTypes.Blob.From(subQuery),
).FROM(
subQuery,
)
testutils.AssertDebugStatementSql(t, stmtJson, strings.ReplaceAll(`
SELECT JSON_ARRAYAGG(JSON_OBJECT(
'boolean', sub_query.''all_types.boolean'' = 1,
'integer', sub_query.''all_types.integer'',
'double', sub_query.''all_types.double'',
'text', sub_query.''all_types.text'',
'date', CONCAT(DATE_FORMAT(sub_query.''all_types.date'','%Y-%m-%d'), 'T00:00:00Z'),
'time', CONCAT('0000-01-01T', DATE_FORMAT(sub_query.''all_types.time'','%H:%i:%s.%fZ')),
'timestamp', DATE_FORMAT(sub_query.''all_types.timestamp'','%Y-%m-%dT%H:%i:%s.%fZ'),
'blob', TO_BASE64(sub_query.''all_types.blob'')
)) AS "json"
FROM (
SELECT all_types.boolean AS "all_types.boolean",
all_types.''integer'' AS "all_types.integer",
all_types.''double'' AS "all_types.double",
all_types.text AS "all_types.text",
all_types.date AS "all_types.date",
all_types.time AS "all_types.time",
all_types.timestamp AS "all_types.timestamp",
all_types.''blob'' AS "all_types.blob"
FROM test_sample.all_types
) AS sub_query;
`, "''", "`"))
var destJson []model.AllTypes
err := stmtJson.QueryJSON(ctx, db, &destJson)
require.NoError(t, err)
t.Run("using AllColumns()", func(t *testing.T) {
stmtJsonAllColumns := SELECT_JSON_ARR(
subQuery.AllColumns(),
).FROM(
subQuery,
)
require.Equal(t, stmtJson.DebugSql(), stmtJsonAllColumns.DebugSql())
})
testutils.AssertJsonEqual(t, dest, destJson)
})
}
var toInsert = model.AllTypes{ var toInsert = model.AllTypes{
Boolean: false, Boolean: false,
BooleanPtr: ptr.Of(true), BooleanPtr: ptr.Of(true),

View file

@ -23,10 +23,12 @@ func TestSelectJsonObj(t *testing.T) {
WHERE(Actor.ActorID.EQ(Int(2))) WHERE(Actor.ActorID.EQ(Int(2)))
testutils.AssertStatementSql(t, stmt, ` testutils.AssertStatementSql(t, stmt, `
SELECT JSON_OBJECT('actorID', actor.actor_id, SELECT JSON_OBJECT(
'actorID', actor.actor_id,
'firstName', actor.first_name, 'firstName', actor.first_name,
'lastName', actor.last_name, 'lastName', actor.last_name,
'lastUpdate', actor.last_update) AS "json" 'lastUpdate', DATE_FORMAT(actor.last_update,'%Y-%m-%dT%H:%i:%s.%fZ')
) AS "json"
FROM dvds.actor FROM dvds.actor
WHERE actor.actor_id = ?; WHERE actor.actor_id = ?;
`, int64(2)) `, int64(2))
@ -57,12 +59,14 @@ func TestSelectJsonObj_NestedObj(t *testing.T) {
) )
testutils.AssertStatementSql(t, stmt, ` testutils.AssertStatementSql(t, stmt, `
SELECT JSON_OBJECT('actorID', actor.actor_id, SELECT JSON_OBJECT(
'actorID', actor.actor_id,
'firstName', actor.first_name, 'firstName', actor.first_name,
'lastName', actor.last_name, 'lastName', actor.last_name,
'lastUpdate', actor.last_update, 'lastUpdate', DATE_FORMAT(actor.last_update,'%Y-%m-%dT%H:%i:%s.%fZ'),
'LongestFilm', ( 'LongestFilm', (
SELECT JSON_OBJECT('filmID', film.film_id, SELECT JSON_OBJECT(
'filmID', film.film_id,
'title', film.title, 'title', film.title,
'description', film.description, 'description', film.description,
'releaseYear', film.release_year, 'releaseYear', film.release_year,
@ -74,13 +78,15 @@ SELECT JSON_OBJECT('actorID', actor.actor_id,
'replacementCost', film.replacement_cost, 'replacementCost', film.replacement_cost,
'rating', film.rating, 'rating', film.rating,
'specialFeatures', film.special_features, 'specialFeatures', film.special_features,
'lastUpdate', film.last_update) AS "json" 'lastUpdate', DATE_FORMAT(film.last_update,'%Y-%m-%dT%H:%i:%s.%fZ')
) AS "json"
FROM dvds.film_actor FROM dvds.film_actor
INNER JOIN dvds.film ON (film.film_id = film_actor.film_id) INNER JOIN dvds.film ON (film.film_id = film_actor.film_id)
WHERE actor.actor_id = film_actor.actor_id WHERE actor.actor_id = film_actor.actor_id
ORDER BY film.length DESC ORDER BY film.length DESC
LIMIT ? LIMIT ?
)) AS "json" )
) AS "json"
FROM dvds.actor FROM dvds.actor
WHERE actor.actor_id = ?; WHERE actor.actor_id = ?;
`) `)
@ -125,10 +131,12 @@ func TestSelectJsonArr(t *testing.T) {
ORDER_BY(Actor.ActorID) ORDER_BY(Actor.ActorID)
testutils.AssertDebugStatementSql(t, stmt, ` testutils.AssertDebugStatementSql(t, stmt, `
SELECT JSON_ARRAYAGG(JSON_OBJECT('actorID', actor.actor_id, SELECT JSON_ARRAYAGG(JSON_OBJECT(
'actorID', actor.actor_id,
'firstName', actor.first_name, 'firstName', actor.first_name,
'lastName', actor.last_name, 'lastName', actor.last_name,
'lastUpdate', actor.last_update)) AS "json" 'lastUpdate', DATE_FORMAT(actor.last_update,'%Y-%m-%dT%H:%i:%s.%fZ')
)) AS "json"
FROM dvds.actor FROM dvds.actor
ORDER BY actor.actor_id; ORDER BY actor.actor_id;
`) `)
@ -169,12 +177,14 @@ func TestSelectJsonArr_NestedArr(t *testing.T) {
) )
testutils.AssertDebugStatementSql(t, stmt, ` testutils.AssertDebugStatementSql(t, stmt, `
SELECT JSON_ARRAYAGG(JSON_OBJECT('actorID', actor.actor_id, SELECT JSON_ARRAYAGG(JSON_OBJECT(
'actorID', actor.actor_id,
'firstName', actor.first_name, 'firstName', actor.first_name,
'lastName', actor.last_name, 'lastName', actor.last_name,
'lastUpdate', actor.last_update, 'lastUpdate', DATE_FORMAT(actor.last_update,'%Y-%m-%dT%H:%i:%s.%fZ'),
'Films', ( 'Films', (
SELECT JSON_ARRAYAGG(JSON_OBJECT('filmID', film.film_id, SELECT JSON_ARRAYAGG(JSON_OBJECT(
'filmID', film.film_id,
'title', film.title, 'title', film.title,
'description', film.description, 'description', film.description,
'releaseYear', film.release_year, 'releaseYear', film.release_year,
@ -186,12 +196,14 @@ SELECT JSON_ARRAYAGG(JSON_OBJECT('actorID', actor.actor_id,
'replacementCost', film.replacement_cost, 'replacementCost', film.replacement_cost,
'rating', film.rating, 'rating', film.rating,
'specialFeatures', film.special_features, 'specialFeatures', film.special_features,
'lastUpdate', film.last_update)) AS "json" 'lastUpdate', DATE_FORMAT(film.last_update,'%Y-%m-%dT%H:%i:%s.%fZ')
)) AS "json"
FROM dvds.film_actor FROM dvds.film_actor
INNER JOIN dvds.film ON ((film.film_id = film_actor.film_id) AND (actor.actor_id = film_actor.actor_id)) INNER JOIN dvds.film ON ((film.film_id = film_actor.film_id) AND (actor.actor_id = film_actor.actor_id))
WHERE (film.film_id % 17) = 0 WHERE (film.film_id % 17) = 0
ORDER BY film.length DESC ORDER BY film.length DESC
))) AS "json" )
)) AS "json"
FROM dvds.actor FROM dvds.actor
WHERE actor.actor_id BETWEEN 1 AND 3 WHERE actor.actor_id BETWEEN 1 AND 3
ORDER BY actor.actor_id; ORDER BY actor.actor_id;
@ -337,22 +349,26 @@ func TestSelectJson_GroupBy(t *testing.T) {
).FROM(subQuery) ).FROM(subQuery)
testutils.AssertDebugStatementSql(t, stmt, strings.ReplaceAll(` testutils.AssertDebugStatementSql(t, stmt, strings.ReplaceAll(`
SELECT JSON_ARRAYAGG(JSON_OBJECT('customerID', customers_info.""customer.customer_id"", SELECT JSON_ARRAYAGG(JSON_OBJECT(
'storeID', customers_info.""customer.store_id"", 'customerID', customers_info.''customer.customer_id'',
'firstName', customers_info.""customer.first_name"", 'storeID', customers_info.''customer.store_id'',
'lastName', customers_info.""customer.last_name"", 'firstName', customers_info.''customer.first_name'',
'email', customers_info.""customer.email"", 'lastName', customers_info.''customer.last_name'',
'addressID', customers_info.""customer.address_id"", 'email', customers_info.''customer.email'',
'active', customers_info.""customer.active"", 'addressID', customers_info.''customer.address_id'',
'createDate', customers_info.""customer.create_date"", 'active', customers_info.''customer.active'' = 1,
'lastUpdate', customers_info.""customer.last_update"", 'createDate', DATE_FORMAT(customers_info.''customer.create_date'','%Y-%m-%dT%H:%i:%s.%fZ'),
'lastUpdate', DATE_FORMAT(customers_info.''customer.last_update'','%Y-%m-%dT%H:%i:%s.%fZ'),
'amount', ( 'amount', (
SELECT JSON_OBJECT('sum', customers_info.sum, SELECT JSON_OBJECT(
'sum', customers_info.sum,
'avg', customers_info.avg, 'avg', customers_info.avg,
'max', customers_info.max, 'max', customers_info.max,
'min', customers_info.min, 'min', customers_info.min,
'count', customers_info.count) AS "json" 'count', customers_info.count
))) AS "json" ) AS "json"
)
)) AS "json"
FROM ( FROM (
SELECT customer.customer_id AS "customer.customer_id", SELECT customer.customer_id AS "customer.customer_id",
customer.store_id AS "customer.store_id", customer.store_id AS "customer.store_id",
@ -374,7 +390,7 @@ FROM (
HAVING SUM(payment.amount) > 125 HAVING SUM(payment.amount) > 125
ORDER BY customer.customer_id, SUM(payment.amount) ASC ORDER BY customer.customer_id, SUM(payment.amount) ASC
) AS customers_info; ) AS customers_info;
`, `""`, "`")) `, "''", "`"))
var dest []struct { var dest []struct {
model.Customer model.Customer
@ -389,7 +405,6 @@ FROM (
} }
err := stmt.QueryJSON(ctx, db, &dest) err := stmt.QueryJSON(ctx, db, &dest)
fmt.Println(err)
require.Nil(t, err) require.Nil(t, err)
testutils.AssertJSONFile(t, dest, "./testdata/results/mysql/customer_payment_sum.json") testutils.AssertJSONFile(t, dest, "./testdata/results/mysql/customer_payment_sum.json")

View file

@ -2,10 +2,10 @@ package postgres
import ( import (
"encoding/base64" "encoding/base64"
"fmt"
"github.com/go-jet/jet/v2/internal/utils/ptr" "github.com/go-jet/jet/v2/internal/utils/ptr"
"github.com/stretchr/testify/assert"
"github.com/go-jet/jet/v2/qrm" "github.com/go-jet/jet/v2/qrm"
"github.com/stretchr/testify/assert"
"testing" "testing"
"time" "time"
@ -46,6 +46,7 @@ func TestAllTypesSelectJson(t *testing.T) {
AllTypes.JsonbArray, AllTypes.IntegerArray, AllTypes.IntegerArrayPtr, AllTypes.JsonbArray, AllTypes.IntegerArray, AllTypes.IntegerArrayPtr,
AllTypes.TextMultiDimArray, AllTypes.TextMultiDimArrayPtr, AllTypes.TextMultiDimArray, AllTypes.TextMultiDimArrayPtr,
), ),
// unsupported at the moment, casting to text allows these columns to be assigned to string fields
CAST(AllTypes.JSONPtr).AS_TEXT().AS("jsonPtr"), CAST(AllTypes.JSONPtr).AS_TEXT().AS("jsonPtr"),
CAST(AllTypes.JSON).AS_TEXT().AS("JSON"), CAST(AllTypes.JSON).AS_TEXT().AS("JSON"),
CAST(AllTypes.JsonbPtr).AS_TEXT().AS("jsonbPtr"), CAST(AllTypes.JsonbPtr).AS_TEXT().AS("jsonbPtr"),
@ -59,7 +60,75 @@ func TestAllTypesSelectJson(t *testing.T) {
CAST(AllTypes.TextMultiDimArrayPtr).AS_TEXT().AS("TextMultiDimArrayPtr"), CAST(AllTypes.TextMultiDimArrayPtr).AS_TEXT().AS("TextMultiDimArrayPtr"),
).FROM(AllTypes) ).FROM(AllTypes)
//fmt.Println(stmt.DebugSql()) testutils.AssertStatementSql(t, stmt, `
SELECT json_agg(row_to_json(records)) AS "json"
FROM (
SELECT all_types.small_int_ptr AS "smallIntPtr",
all_types.small_int AS "smallInt",
all_types.integer_ptr AS "integerPtr",
all_types.integer AS "integer",
all_types.big_int_ptr AS "bigIntPtr",
all_types.big_int AS "bigInt",
all_types.decimal_ptr AS "decimalPtr",
all_types.decimal AS "decimal",
all_types.numeric_ptr AS "numericPtr",
all_types.numeric AS "numeric",
all_types.real_ptr AS "realPtr",
all_types.real AS "real",
all_types.double_precision_ptr AS "doublePrecisionPtr",
all_types.double_precision AS "doublePrecision",
all_types.smallserial AS "smallserial",
all_types.serial AS "serial",
all_types.bigserial AS "bigserial",
all_types.var_char_ptr AS "varCharPtr",
all_types.var_char AS "varChar",
all_types.char_ptr AS "charPtr",
all_types.char AS "char",
all_types.text_ptr AS "textPtr",
all_types.text AS "text",
ENCODE(all_types.bytea_ptr, 'base64') AS "byteaPtr",
ENCODE(all_types.bytea, 'base64') AS "bytea",
all_types.timestampz_ptr AS "timestampzPtr",
all_types.timestampz AS "timestampz",
to_char(all_types.timestamp_ptr, 'YYYY-MM-DD"T"HH24:MI:SS.USZ') AS "timestampPtr",
to_char(all_types.timestamp, 'YYYY-MM-DD"T"HH24:MI:SS.USZ') AS "timestamp",
to_char(all_types.date_ptr::timestamp, 'YYYY-MM-DD') || 'T00:00:00Z' AS "datePtr",
to_char(all_types.date::timestamp, 'YYYY-MM-DD') || 'T00:00:00Z' AS "date",
'0000-01-01T' || to_char('2000-10-10'::date + all_types.timez_ptr, 'HH24:MI:SS.USTZH:TZM') AS "timezPtr",
'0000-01-01T' || to_char('2000-10-10'::date + all_types.timez, 'HH24:MI:SS.USTZH:TZM') AS "timez",
'0000-01-01T' || to_char('2000-10-10'::date + all_types.time_ptr, 'HH24:MI:SS.USZ') AS "timePtr",
'0000-01-01T' || to_char('2000-10-10'::date + all_types.time, 'HH24:MI:SS.USZ') AS "time",
all_types.interval_ptr AS "intervalPtr",
all_types.interval AS "interval",
all_types.boolean_ptr AS "booleanPtr",
all_types.boolean AS "boolean",
all_types.point_ptr AS "pointPtr",
all_types.bit_ptr AS "bitPtr",
all_types.bit AS "bit",
all_types.bit_varying_ptr AS "bitVaryingPtr",
all_types.bit_varying AS "bitVarying",
all_types.tsvector_ptr AS "tsvectorPtr",
all_types.tsvector AS "tsvector",
all_types.uuid_ptr AS "uuidPtr",
all_types.uuid AS "uuid",
all_types.xml_ptr AS "xmlPtr",
all_types.xml AS "xml",
all_types.mood_ptr AS "moodPtr",
all_types.mood AS "mood",
all_types.json_ptr::text AS "jsonPtr",
all_types.json::text AS "JSON",
all_types.jsonb_ptr::text AS "jsonbPtr",
all_types.jsonb::text AS "Jsonb",
all_types.text_array_ptr::text AS "TextArrayPtr",
all_types.text_array::text AS "TextArray",
all_types.jsonb_array::text AS "JsonbArray",
all_types.integer_array::text AS "IntegerArray",
all_types.integer_array_ptr::text AS "IntegerArrayPtr",
all_types.text_multi_dim_array::text AS "TextMultiDimArray",
all_types.text_multi_dim_array_ptr::text AS "TextMultiDimArrayPtr"
FROM test_sample.all_types
) AS records;
`)
var dest []model.AllTypes var dest []model.AllTypes
@ -76,24 +145,30 @@ func TestAllTypesSelectJson(t *testing.T) {
dest[1].CharPtr = allTypesRow1.CharPtr dest[1].CharPtr = allTypesRow1.CharPtr
} }
// set time local before comparison minus8 := time.FixedZone("UTC", -8*60*60)
dest[0].Timestampz = toCET(t, dest[0].Timestampz) plus1 := time.FixedZone("UTC", 60*60)
if dest[0].TimestampzPtr != nil { // set time local before comparison
dest[0].TimestampzPtr = ptr.Of(toCET(t, *dest[0].TimestampzPtr)) dest[0].Timez = *toTZ(&dest[0].Timez, minus8)
} dest[0].TimezPtr = toTZ(dest[0].TimezPtr, minus8)
dest[1].Timestampz = toCET(t, dest[1].Timestampz) dest[1].Timez = *toTZ(&dest[1].Timez, minus8)
dest[1].TimezPtr = toTZ(dest[1].TimezPtr, minus8)
dest[0].Timestampz = *toTZ(&dest[0].Timestampz, plus1)
dest[0].TimestampzPtr = toTZ(dest[0].TimestampzPtr, plus1)
dest[1].Timestampz = *toTZ(&dest[1].Timestampz, plus1)
dest[1].TimestampzPtr = toTZ(dest[1].TimestampzPtr, plus1)
testutils.AssertJsonEqual(t, dest[0], allTypesRow0) testutils.AssertJsonEqual(t, dest[0], allTypesRow0)
testutils.AssertJsonEqual(t, dest[1], allTypesRow1) testutils.AssertJsonEqual(t, dest[1], allTypesRow1)
} }
func toCET(t *testing.T, tm time.Time) time.Time { func toTZ(tm *time.Time, loc *time.Location) *time.Time {
cet, err := time.LoadLocation("CET") // "Europe/Berlin" also works if tm == nil {
if err != nil { return nil
t.Fail()
} }
return tm.In(cet)
return ptr.Of(tm.In(loc))
} }
func TestAllTypesViewSelect(t *testing.T) { func TestAllTypesViewSelect(t *testing.T) {
@ -192,7 +267,7 @@ WHERE all_types.uuid = 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11';
requireLogged(t, query) requireLogged(t, query)
} }
func TestBytea(t *testing.T) { func TestByteaInsert(t *testing.T) {
byteArrHex := "\\x48656c6c6f20476f7068657221" byteArrHex := "\\x48656c6c6f20476f7068657221"
byteArrBin := []byte("\x48\x65\x6c\x6c\x6f\x20\x47\x6f\x70\x68\x65\x72\x21") byteArrBin := []byte("\x48\x65\x6c\x6c\x6f\x20\x47\x6f\x70\x68\x65\x72\x21")
@ -602,22 +677,22 @@ func TestStringOperators(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
} }
func TestBlob(t *testing.T) { func TestBytea(t *testing.T) {
var sampleBlob = Bytea([]byte{11, 0, 22, 33, 44}) var sampleBytea = Bytea([]byte{11, 0, 22, 33, 44})
var textBlob = Bytea([]byte("text blob")) var textBytea = Bytea([]byte("text blob"))
stmt := SELECT( stmt := SELECT(
AllTypes.Bytea.EQ(sampleBlob), AllTypes.Bytea.EQ(sampleBytea),
AllTypes.Bytea.EQ(AllTypes.ByteaPtr), AllTypes.Bytea.EQ(AllTypes.ByteaPtr),
AllTypes.Bytea.NOT_EQ(sampleBlob), AllTypes.Bytea.NOT_EQ(sampleBytea),
AllTypes.Bytea.GT(textBlob), AllTypes.Bytea.GT(textBytea),
AllTypes.Bytea.GT_EQ(AllTypes.ByteaPtr), AllTypes.Bytea.GT_EQ(AllTypes.ByteaPtr),
AllTypes.Bytea.LT(AllTypes.ByteaPtr), AllTypes.Bytea.LT(AllTypes.ByteaPtr),
AllTypes.Bytea.LT_EQ(sampleBlob), AllTypes.Bytea.LT_EQ(sampleBytea),
AllTypes.Bytea.BETWEEN(Bytea([]byte("min")), Bytea([]byte("max"))), AllTypes.Bytea.BETWEEN(Bytea([]byte("min")), Bytea([]byte("max"))),
AllTypes.Bytea.NOT_BETWEEN(AllTypes.Bytea, AllTypes.ByteaPtr), AllTypes.Bytea.NOT_BETWEEN(AllTypes.Bytea, AllTypes.ByteaPtr),
AllTypes.Bytea.CONCAT(textBlob), AllTypes.Bytea.CONCAT(textBytea),
func() ProjectionList { func() ProjectionList {
if sourceIsCockroachDB() { if sourceIsCockroachDB() {
@ -629,25 +704,25 @@ func TestBlob(t *testing.T) {
AllTypes.Bytea.NOT_LIKE(Bytea("b'%pattern%'")), AllTypes.Bytea.NOT_LIKE(Bytea("b'%pattern%'")),
BTRIM(AllTypes.Bytea, Bytea([]byte{33})), BTRIM(AllTypes.Bytea, Bytea([]byte{33})),
RTRIM(AllTypes.ByteaPtr, sampleBlob), RTRIM(AllTypes.ByteaPtr, sampleBytea),
LTRIM(sampleBlob, textBlob), LTRIM(sampleBytea, textBytea),
CONCAT(sampleBlob, AllTypes.ByteaPtr, textBlob), CONCAT(sampleBytea, AllTypes.ByteaPtr, textBytea),
BIT_COUNT(sampleBlob).EQ(Int(3)), BIT_COUNT(sampleBytea).EQ(Int(3)),
LENGTH(textBlob, UTF8).EQ(Int(4)), LENGTH(textBytea, UTF8).EQ(Int(4)),
CONVERT(textBlob, UTF8, WIN1252), CONVERT(textBytea, UTF8, WIN1252),
CONVERT(AllTypes.Bytea, UTF8, LATIN1).EQ(sampleBlob), CONVERT(AllTypes.Bytea, UTF8, LATIN1).EQ(sampleBytea),
} }
}(), }(),
BIT_LENGTH(textBlob), BIT_LENGTH(textBytea),
OCTET_LENGTH(textBlob), OCTET_LENGTH(textBytea),
GET_BIT(textBlob, Int(2)).EQ(Int(23)), GET_BIT(textBytea, Int(2)).EQ(Int(23)),
GET_BYTE(sampleBlob, Int(1)).EQ(Int(0)), GET_BYTE(sampleBytea, Int(1)).EQ(Int(0)),
SET_BIT(textBlob, Int(1), Int(0)).EQ(sampleBlob), SET_BIT(textBytea, Int(1), Int(0)).EQ(sampleBytea),
SET_BYTE(textBlob, Int(1), Int(0)).EQ(textBlob), SET_BYTE(textBytea, Int(1), Int(0)).EQ(textBytea),
LENGTH(sampleBlob), LENGTH(sampleBytea),
SUBSTR(AllTypes.Bytea, Int(0), Int(2)), SUBSTR(AllTypes.Bytea, Int(0), Int(2)),
@ -657,16 +732,16 @@ func TestBlob(t *testing.T) {
SHA384(AllTypes.Bytea), SHA384(AllTypes.Bytea),
SHA512(AllTypes.Bytea), SHA512(AllTypes.Bytea),
ENCODE(sampleBlob, Base64), ENCODE(sampleBytea, Base64),
DECODE(String("A234C12B"), Hex).EQ(sampleBlob), DECODE(String("A234C12B"), Hex).EQ(sampleBytea),
CONVERT_FROM(AllTypes.ByteaPtr, UTF8).EQ(AllTypes.VarChar), CONVERT_FROM(AllTypes.ByteaPtr, UTF8).EQ(AllTypes.VarChar),
CONVERT_TO(AllTypes.Text, UTF8).NOT_EQ(textBlob), CONVERT_TO(AllTypes.Text, UTF8).NOT_EQ(textBytea),
RawBytea("DECODE(#1::text, #2)", RawArgs{ RawBytea("DECODE(#1::text, #2)", RawArgs{
"#1": "A234C12B", "#1": "A234C12B",
"#2": "hex", "#2": "hex",
}).EQ(sampleBlob), }).EQ(sampleBytea),
).FROM( ).FROM(
AllTypes, AllTypes,
) )
@ -690,27 +765,27 @@ SELECT all_types.bytea = $1::bytea,
LTRIM($12::bytea, $13::bytea), LTRIM($12::bytea, $13::bytea),
CONCAT($14::bytea, all_types.bytea_ptr, $15::bytea), CONCAT($14::bytea, all_types.bytea_ptr, $15::bytea),
BIT_COUNT($16::bytea) = $17, BIT_COUNT($16::bytea) = $17,
LENGTH($18::bytea, $19::text) = $20, LENGTH($18::bytea, 'UTF8') = $19,
CONVERT($21::bytea, $22::text, $23::text), CONVERT($20::bytea, 'UTF8', 'WIN1252'),
CONVERT(all_types.bytea, $24::text, $25::text) = $26::bytea, CONVERT(all_types.bytea, 'UTF8', 'LATIN1') = $21::bytea,
BIT_LENGTH($27::bytea), BIT_LENGTH($22::bytea),
OCTET_LENGTH($28::bytea), OCTET_LENGTH($23::bytea),
GET_BIT($29::bytea, $30) = $31, GET_BIT($24::bytea, $25) = $26,
GET_BYTE($32::bytea, $33) = $34, GET_BYTE($27::bytea, $28) = $29,
SET_BIT($35::bytea, $36, $37) = $38::bytea, SET_BIT($30::bytea, $31, $32) = $33::bytea,
SET_BYTE($39::bytea, $40, $41) = $42::bytea, SET_BYTE($34::bytea, $35, $36) = $37::bytea,
LENGTH($43::bytea), LENGTH($38::bytea),
SUBSTR(all_types.bytea, $44, $45), SUBSTR(all_types.bytea, $39, $40),
MD5(all_types.bytea), MD5(all_types.bytea),
SHA224(all_types.bytea), SHA224(all_types.bytea),
SHA256(all_types.bytea), SHA256(all_types.bytea),
SHA384(all_types.bytea), SHA384(all_types.bytea),
SHA512(all_types.bytea), SHA512(all_types.bytea),
ENCODE($46::bytea, $47::text), ENCODE($41::bytea, 'base64'),
DECODE($48::text, $49::text) = $50::bytea, DECODE($42::text, 'hex') = $43::bytea,
CONVERT_FROM(all_types.bytea_ptr, $51::text) = all_types.var_char, CONVERT_FROM(all_types.bytea_ptr, 'UTF8') = all_types.var_char,
CONVERT_TO(all_types.text, $52::text) != $53::bytea, CONVERT_TO(all_types.text, 'UTF8') != $44::bytea,
(DECODE($54::text, $55)) = $56::bytea (DECODE($45::text, $46)) = $47::bytea
FROM test_sample.all_types; FROM test_sample.all_types;
`) `)
} }
@ -727,28 +802,75 @@ func TestBlobConversion(t *testing.T) {
printable := []byte("this is blob") printable := []byte("this is blob")
stmt := SELECT( stmt := SELECT(
Bytea(nonPrintable).AS("non_printable"), Bytea(nonPrintable).AS("test_dest.non_printable"),
Bytea(printable).AS("printable"), Bytea(printable).AS("test_dest.printable"),
ENCODE(Bytea(nonPrintable), Base64).AS("non_printable_base64"), Bytea(nonPrintable).CONCAT(Bytea(printable)).AS("test_dest.bytea_concat"),
CONVERT_FROM(Bytea(printable), UTF8).AS("printable_utf8"),
ENCODE(Bytea(nonPrintable), Base64).AS("test_dest.non_printable_base64"),
CONVERT_FROM(Bytea(printable), UTF8).AS("test_dest.printable_utf8"),
) )
var dest struct { testutils.AssertDebugStatementSql(t, stmt, `
SELECT '\x0b16212c37'::bytea AS "test_dest.non_printable",
'\x7468697320697320626c6f62'::bytea AS "test_dest.printable",
('\x0b16212c37'::bytea || '\x7468697320697320626c6f62'::bytea) AS "test_dest.bytea_concat",
ENCODE('\x0b16212c37'::bytea, 'base64') AS "test_dest.non_printable_base64",
CONVERT_FROM('\x7468697320697320626c6f62'::bytea, 'UTF8') AS "test_dest.printable_utf8";
`)
type testDest struct {
NonPrintable []byte NonPrintable []byte
Printable []byte Printable []byte
NonPrintableBase64 []byte ByteaConcat []byte
NonPrintableBase64 string
PrintableUTF8 string PrintableUTF8 string
} }
var dest testDest
err := stmt.Query(db, &dest) err := stmt.Query(db, &dest)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, dest.NonPrintable, nonPrintable) require.Equal(t, dest.NonPrintable, nonPrintable)
require.Equal(t, dest.Printable, printable) require.Equal(t, dest.Printable, printable)
require.Equal(t, dest.NonPrintableBase64, []byte(base64.StdEncoding.EncodeToString(nonPrintable)))
require.Equal(t, dest.ByteaConcat, append(nonPrintable, printable...))
require.Equal(t, dest.NonPrintableBase64, base64.StdEncoding.EncodeToString(nonPrintable))
require.Equal(t, dest.PrintableUTF8, string(printable)) require.Equal(t, dest.PrintableUTF8, string(printable))
t.Run("using select json", func(t *testing.T) {
stmtJson := SELECT_JSON_OBJ(
Bytea(nonPrintable).AS("nonPrintable"),
Bytea(printable).AS("printable"),
Bytea(nonPrintable).CONCAT(Bytea(printable)).AS("byteaConcat"),
ENCODE(Bytea(nonPrintable), Base64).AS("nonPrintableBase64"),
CONVERT_FROM(Bytea(printable), UTF8).AS("printableUtf8"),
)
testutils.AssertStatementSql(t, stmtJson, `
SELECT row_to_json(records) AS "json"
FROM (
SELECT ENCODE($1::bytea, 'base64') AS "nonPrintable",
ENCODE($2::bytea, 'base64') AS "printable",
ENCODE($3::bytea || $4::bytea, 'base64') AS "byteaConcat",
ENCODE($5::bytea, 'base64') AS "nonPrintableBase64",
CONVERT_FROM($6::bytea, 'UTF8') AS "printableUtf8"
) AS records;
`)
var destSelectJson testDest
err := stmtJson.QueryJSON(ctx, db, &destSelectJson)
require.NoError(t, err)
testutils.PrintJson(destSelectJson)
require.Equal(t, dest, destSelectJson)
})
} }
func TestBoolOperators(t *testing.T) { func TestBoolOperators(t *testing.T) {
@ -1140,6 +1262,190 @@ func TestTimeExpression(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
} }
func TestTimeScan(t *testing.T) {
loc, err := time.LoadLocation("Japan")
require.NoError(t, err)
timeT := time.Date(3, 3, 3, 11, 22, 33, 0, time.UTC)
timeWithNanoSeconds := time.Date(3, 3, 3, 1, 2, 3, 1000, time.UTC)
timez := time.Date(3, 3, 3, 7, 8, 9, 0, time.UTC)
timezWithNanoSeconds := time.Date(3, 3, 3, 4, 5, 6, 1000, loc)
// '1999-01-08 04:05:06'
timestamp := time.Date(1999, 01, 8, 4, 5, 6, 0, time.UTC)
timestampWithNanoSeconds := time.Date(3, 3, 3, 8, 9, 10, 1000, time.UTC)
timestampz := time.Date(2003, 10, 3, 9, 10, 11, 0, loc)
timestampzWithNanoSeconds := time.Date(3, 3, 3, 8, 9, 10, 1000, loc)
date := time.Date(2010, 2, 3, 0, 0, 0, 0, time.UTC)
stmt := SELECT(
TimeT(timeT).AS("time"),
TimeT(timeWithNanoSeconds).AS("timeWithNanoSeconds"),
TimezT(timez).AS("timez"),
TimezT(timezWithNanoSeconds).AS("timezWithNanoSeconds"),
Timestamp(1999, 01, 8, 4, 5, 6).AS("timestamp"),
TimestampT(timestampWithNanoSeconds).AS("timestampWithNanoSeconds"),
TimestampzT(timestampz).AS("timestampz"),
TimestampzT(timestampzWithNanoSeconds).AS("timestampzWithNanoSeconds"),
DateT(date).AS("date"),
TimeT(timeT).ADD(INTERVAL(2, HOUR)).AS("timeExpression"),
SELECT_JSON_OBJ(
TimeT(timeT).AS("time"),
TimeT(timeWithNanoSeconds).AS("timeWithNanoSeconds"),
TimezT(timez).AS("timez"),
TimezT(timezWithNanoSeconds).AS("timezWithNanoSeconds"),
TimestampT(timestamp).AS("timestamp"),
TimestampT(timestampWithNanoSeconds).AS("timestampWithNanoSeconds"),
TimestampzT(timestampz).AS("timestampz"),
TimestampzT(timestampzWithNanoSeconds).AS("timestampzWithNanoSeconds"),
DateT(date).AS("date"),
TimeT(timeT).ADD(INTERVAL(2, HOUR)).AS("timeExpression"),
).AS("json"),
)
testutils.AssertStatementSql(t, stmt, `
SELECT $1::time without time zone AS "time",
$2::time without time zone AS "timeWithNanoSeconds",
$3::time with time zone AS "timez",
$4::time with time zone AS "timezWithNanoSeconds",
$5::timestamp without time zone AS "timestamp",
$6::timestamp without time zone AS "timestampWithNanoSeconds",
$7::timestamp with time zone AS "timestampz",
$8::timestamp with time zone AS "timestampzWithNanoSeconds",
$9::date AS "date",
($10::time without time zone + INTERVAL '2 HOUR') AS "timeExpression",
(
SELECT row_to_json(json_records) AS "json_json"
FROM (
SELECT '0000-01-01T' || to_char('2000-10-10'::date + $11::time without time zone, 'HH24:MI:SS.USZ') AS "time",
'0000-01-01T' || to_char('2000-10-10'::date + $12::time without time zone, 'HH24:MI:SS.USZ') AS "timeWithNanoSeconds",
'0000-01-01T' || to_char('2000-10-10'::date + $13::time with time zone, 'HH24:MI:SS.USTZH:TZM') AS "timez",
'0000-01-01T' || to_char('2000-10-10'::date + $14::time with time zone, 'HH24:MI:SS.USTZH:TZM') AS "timezWithNanoSeconds",
to_char($15::timestamp without time zone, 'YYYY-MM-DD"T"HH24:MI:SS.USZ') AS "timestamp",
to_char($16::timestamp without time zone, 'YYYY-MM-DD"T"HH24:MI:SS.USZ') AS "timestampWithNanoSeconds",
$17::timestamp with time zone AS "timestampz",
$18::timestamp with time zone AS "timestampzWithNanoSeconds",
to_char($19::date::timestamp, 'YYYY-MM-DD') || 'T00:00:00Z' AS "date",
'0000-01-01T' || to_char('2000-10-10'::date + ($20::time without time zone + INTERVAL '2 HOUR'), 'HH24:MI:SS.USZ') AS "timeExpression"
) AS json_records
) AS "json";
`)
var dest struct {
Time time.Time
TimeWithNanoSeconds time.Time
Timez time.Time
TimezWithNanoSeconds time.Time
Timestamp time.Time
TimestampWithNanoSeconds time.Time
Timestampz time.Time
TimestampzWithNanoSeconds time.Time
Date time.Time
TimeExpression time.Time
Json struct {
Time time.Time
TimeWithNanoSeconds time.Time
Timez time.Time
TimezWithNanoSeconds time.Time
Timestamp time.Time
TimestampWithNanoSeconds time.Time
Timestampz time.Time
TimestampzWithNanoSeconds time.Time
Date time.Time
TimeExpression time.Time
} `json_column:"json"`
}
err = stmt.Query(db, &dest)
require.NoError(t, err)
ensureTimezEqual(t, timeT.Add(2*time.Hour), dest.TimeExpression, loc)
ensureTimezEqual(t, timeT.Add(2*time.Hour), dest.Json.TimeExpression, loc)
ensureTimezEqual(t, timeT, dest.Time, loc)
ensureTimezEqual(t, timeT, dest.Json.Time, loc)
ensureTimezEqual(t, timeWithNanoSeconds, dest.TimeWithNanoSeconds, loc)
ensureTimezEqual(t, timeWithNanoSeconds, dest.Json.TimeWithNanoSeconds, loc)
ensureTimezEqual(t, timez, dest.Timez, loc)
ensureTimezEqual(t, timez, dest.Json.Timez, loc)
ensureTimezEqual(t, timezWithNanoSeconds, dest.TimezWithNanoSeconds, loc)
ensureTimezEqual(t, timezWithNanoSeconds, dest.Json.TimezWithNanoSeconds, loc)
ensureTimezEqual(t, timestamp, dest.Timestamp, loc)
ensureTimezEqual(t, timestamp, dest.Json.Timestamp, loc)
ensureTimezEqual(t, timestampWithNanoSeconds, dest.TimestampWithNanoSeconds, loc)
ensureTimezEqual(t, timestampWithNanoSeconds, dest.Json.TimestampWithNanoSeconds, loc)
ensureTimezEqual(t, timestampz, dest.Timestampz, loc)
ensureTimezEqual(t, timestampz, dest.Json.Timestampz, loc)
ensureTimezEqual(t, timestampzWithNanoSeconds, dest.TimestampzWithNanoSeconds, loc)
ensureTimezEqual(t, timestampzWithNanoSeconds, dest.Json.TimestampzWithNanoSeconds, loc)
ensureTimezEqual(t, date, dest.Date, loc)
ensureTimezEqual(t, date, dest.Json.Date, loc)
t.Run("json only", func(t *testing.T) {
stmtJson := SELECT_JSON_OBJ(
TimeT(timeT).AS("time"),
TimeT(timeWithNanoSeconds).AS("timeWithNanoSeconds"),
TimezT(timez).AS("timez"),
TimezT(timezWithNanoSeconds).AS("timezWithNanoSeconds"),
Timestamp(1999, 01, 8, 4, 5, 6).AS("timestamp"),
TimestampT(timestampWithNanoSeconds).AS("timestampWithNanoSeconds"),
TimestampzT(timestampz).AS("timestampz"),
TimestampzT(timestampzWithNanoSeconds).AS("timestampzWithNanoSeconds"),
DateT(date).AS("date"),
)
var jsonDest struct {
Time time.Time
TimeWithNanoSeconds time.Time
Timez time.Time
TimezWithNanoSeconds time.Time
Timestamp time.Time
TimestampWithNanoSeconds time.Time
Timestampz time.Time
TimestampzWithNanoSeconds time.Time
Date time.Time
}
err := stmtJson.QueryJSON(ctx, db, &jsonDest)
require.NoError(t, err)
})
}
func ensureTimezEqual(t *testing.T, time1, time2 time.Time, loc *time.Location) {
time1Loc := time1.In(loc)
time2Loc := time2.In(loc)
require.Equal(t, time1Loc.Hour(), time2Loc.Hour())
require.Equal(t, time1Loc.Minute(), time2Loc.Minute())
require.Equal(t, time1Loc.Second(), time2Loc.Second())
require.Equal(t, toMicroSeconds(time1Loc.Nanosecond()), toMicroSeconds(time2Loc.Nanosecond()))
}
func toMicroSeconds(nanoseconds int) int {
return nanoseconds / 1000
}
func TestIntervalSetFunctionality(t *testing.T) { func TestIntervalSetFunctionality(t *testing.T) {
t.Run("updateQueryIntervalTest", func(t *testing.T) { t.Run("updateQueryIntervalTest", func(t *testing.T) {
@ -1251,7 +1557,50 @@ func TestInterval(t *testing.T) {
AllTypes.IntervalPtr.DIV(Float(22.222)).EQ(AllTypes.IntervalPtr), AllTypes.IntervalPtr.DIV(Float(22.222)).EQ(AllTypes.IntervalPtr),
).FROM(AllTypes) ).FROM(AllTypes)
//fmt.Println(stmt.DebugSql()) fmt.Println(stmt.Sql())
testutils.AssertDebugStatementSql(t, stmt, `
SELECT INTERVAL '1 YEAR',
INTERVAL '1 MONTH',
INTERVAL '1 WEEK',
INTERVAL '1 DAY',
INTERVAL '1 HOUR',
INTERVAL '1 MINUTE',
INTERVAL '1 SECOND',
INTERVAL '1 MILLISECOND',
INTERVAL '1 MICROSECOND',
INTERVAL '1 DECADE',
INTERVAL '1 CENTURY',
INTERVAL '1 MILLENNIUM',
INTERVAL '1 YEAR 10 MONTH',
INTERVAL '1 YEAR 10 MONTH 20 DAY',
INTERVAL '1 YEAR 10 MONTH 20 DAY 3 HOUR',
INTERVAL '1 YEAR' IS NOT NULL,
INTERVAL '1 YEAR' AS "one year",
INTERVAL '0 MICROSECOND',
INTERVAL '1 MICROSECOND',
INTERVAL '1000 MICROSECOND',
INTERVAL '1 SECOND',
INTERVAL '1 MINUTE',
INTERVAL '1 HOUR',
INTERVAL '1 DAY',
INTERVAL '1 DAY 2 HOUR 3 MINUTE 4 SECOND 5 MICROSECOND',
(all_types.interval = INTERVAL '2 HOUR 20 MINUTE') = TRUE::boolean,
(all_types.interval_ptr != INTERVAL '2 HOUR 20 MINUTE') = FALSE::boolean,
(all_types.interval IS DISTINCT FROM INTERVAL '2 HOUR 20 MINUTE') = all_types.boolean,
(all_types.interval_ptr IS NOT DISTINCT FROM INTERVAL '10 MICROSECOND') = all_types.boolean,
(all_types.interval < all_types.interval_ptr) = all_types.boolean_ptr,
(all_types.interval <= all_types.interval_ptr) = all_types.boolean_ptr,
(all_types.interval > all_types.interval_ptr) = all_types.boolean_ptr,
(all_types.interval >= all_types.interval_ptr) = all_types.boolean_ptr,
all_types.interval BETWEEN INTERVAL '1 HOUR' AND INTERVAL '2 HOUR',
all_types.interval NOT BETWEEN all_types.interval_ptr AND INTERVAL '30 SECOND',
(all_types.interval + all_types.interval_ptr) = INTERVAL '17 SECOND',
(all_types.interval - all_types.interval_ptr) = INTERVAL '100 MICROSECOND',
(all_types.interval_ptr * 11) = all_types.interval,
(all_types.interval_ptr / 22.222) = all_types.interval_ptr
FROM test_sample.all_types;
`)
err := stmt.Query(db, &struct{}{}) err := stmt.Query(db, &struct{}{})
require.NoError(t, err) require.NoError(t, err)
@ -1368,6 +1717,7 @@ func TestAllTypesSubQueryFrom(t *testing.T) {
AllTypes.Time, AllTypes.Time,
AllTypes.Timez, AllTypes.Timez,
AllTypes.Timestamp, AllTypes.Timestamp,
AllTypes.Timestampz,
AllTypes.Interval, AllTypes.Interval,
AllTypes.Bytea, AllTypes.Bytea,
).FROM( ).FROM(
@ -1383,6 +1733,7 @@ func TestAllTypesSubQueryFrom(t *testing.T) {
AllTypes.Time.From(subQuery), AllTypes.Time.From(subQuery),
AllTypes.Timez.From(subQuery), AllTypes.Timez.From(subQuery),
AllTypes.Timestamp.From(subQuery), AllTypes.Timestamp.From(subQuery),
AllTypes.Timestampz.From(subQuery),
AllTypes.Interval.From(subQuery), AllTypes.Interval.From(subQuery),
AllTypes.Bytea.From(subQuery), AllTypes.Bytea.From(subQuery),
).FROM( ).FROM(
@ -1398,6 +1749,7 @@ SELECT "subQuery"."all_types.boolean" AS "all_types.boolean",
"subQuery"."all_types.time" AS "all_types.time", "subQuery"."all_types.time" AS "all_types.time",
"subQuery"."all_types.timez" AS "all_types.timez", "subQuery"."all_types.timez" AS "all_types.timez",
"subQuery"."all_types.timestamp" AS "all_types.timestamp", "subQuery"."all_types.timestamp" AS "all_types.timestamp",
"subQuery"."all_types.timestampz" AS "all_types.timestampz",
"subQuery"."all_types.interval" AS "all_types.interval", "subQuery"."all_types.interval" AS "all_types.interval",
"subQuery"."all_types.bytea" AS "all_types.bytea" "subQuery"."all_types.bytea" AS "all_types.bytea"
FROM ( FROM (
@ -1409,6 +1761,7 @@ FROM (
all_types.time AS "all_types.time", all_types.time AS "all_types.time",
all_types.timez AS "all_types.timez", all_types.timez AS "all_types.timez",
all_types.timestamp AS "all_types.timestamp", all_types.timestamp AS "all_types.timestamp",
all_types.timestampz AS "all_types.timestampz",
all_types.interval AS "all_types.interval", all_types.interval AS "all_types.interval",
all_types.bytea AS "all_types.bytea" all_types.bytea AS "all_types.bytea"
FROM test_sample.all_types FROM test_sample.all_types
@ -1419,6 +1772,83 @@ FROM (
err := stmt.Query(db, &dest) err := stmt.Query(db, &dest)
require.NoError(t, err) require.NoError(t, err)
t.Run("using SELECT_JSON", func(t *testing.T) {
stmtJson := SELECT_JSON_ARR(
AllTypes.Boolean.From(subQuery),
AllTypes.Integer.From(subQuery),
AllTypes.DoublePrecision.From(subQuery),
AllTypes.Text.From(subQuery),
AllTypes.Date.From(subQuery),
AllTypes.Time.From(subQuery),
AllTypes.Timez.From(subQuery),
AllTypes.Timestamp.From(subQuery),
AllTypes.Timestampz.From(subQuery),
AllTypes.Interval.From(subQuery),
AllTypes.Bytea.From(subQuery),
).FROM(
subQuery,
)
testutils.AssertDebugStatementSql(t, stmtJson, `
SELECT json_agg(row_to_json(records)) AS "json"
FROM (
SELECT "subQuery"."all_types.boolean" AS "boolean",
"subQuery"."all_types.integer" AS "integer",
"subQuery"."all_types.double_precision" AS "doublePrecision",
"subQuery"."all_types.text" AS "text",
to_char("subQuery"."all_types.date"::timestamp, 'YYYY-MM-DD') || 'T00:00:00Z' AS "date",
'0000-01-01T' || to_char('2000-10-10'::date + "subQuery"."all_types.time", 'HH24:MI:SS.USZ') AS "time",
'0000-01-01T' || to_char('2000-10-10'::date + "subQuery"."all_types.timez", 'HH24:MI:SS.USTZH:TZM') AS "timez",
to_char("subQuery"."all_types.timestamp", 'YYYY-MM-DD"T"HH24:MI:SS.USZ') AS "timestamp",
"subQuery"."all_types.timestampz" AS "timestampz",
"subQuery"."all_types.interval" AS "interval",
ENCODE("subQuery"."all_types.bytea", 'base64') AS "bytea"
FROM (
SELECT all_types.boolean AS "all_types.boolean",
all_types.integer AS "all_types.integer",
all_types.double_precision AS "all_types.double_precision",
all_types.text AS "all_types.text",
all_types.date AS "all_types.date",
all_types.time AS "all_types.time",
all_types.timez AS "all_types.timez",
all_types.timestamp AS "all_types.timestamp",
all_types.timestampz AS "all_types.timestampz",
all_types.interval AS "all_types.interval",
all_types.bytea AS "all_types.bytea"
FROM test_sample.all_types
) AS "subQuery"
) AS records;
`)
var destJson []model.AllTypes
err := stmtJson.QueryJSON(ctx, db, &destJson)
require.NoError(t, err)
t.Run("using AllColumns()", func(t *testing.T) {
stmtJsonAllColumns := SELECT_JSON_ARR(
subQuery.AllColumns(),
).FROM(
subQuery,
)
require.Equal(t, stmtJson.DebugSql(), stmtJsonAllColumns.DebugSql())
})
// fix timezone before comparisons
minus8 := time.FixedZone("UTC", -8*60*60)
destJson[0].Timez = *toTZ(&destJson[0].Timez, minus8)
destJson[1].Timez = *toTZ(&destJson[1].Timez, minus8)
destJson[0].Timestampz = *toTZ(&destJson[0].Timestampz, time.UTC)
destJson[1].Timestampz = *toTZ(&destJson[1].Timestampz, time.UTC)
dest[0].Timestampz = *toTZ(&dest[0].Timestampz, time.UTC)
dest[1].Timestampz = *toTZ(&dest[1].Timestampz, time.UTC)
testutils.AssertJsonEqual(t, dest, destJson)
})
} }
func TestAllTypesUpdateSet(t *testing.T) { func TestAllTypesUpdateSet(t *testing.T) {

View file

@ -251,9 +251,8 @@ func testJoinEverythingJSON(t require.TestingT) {
WHERE(MediaType.MediaTypeId.EQ(Track.MediaTypeId)).AS("MediaType"), WHERE(MediaType.MediaTypeId.EQ(Track.MediaTypeId)).AS("MediaType"),
SELECT_JSON_ARR(Playlist.AllColumns). SELECT_JSON_ARR(Playlist.AllColumns).
FROM(Playlist.INNER_JOIN( FROM(Playlist.
PlaylistTrack, INNER_JOIN(PlaylistTrack, Playlist.PlaylistId.EQ(PlaylistTrack.PlaylistId))).
Playlist.PlaylistId.EQ(PlaylistTrack.PlaylistId))).
WHERE(PlaylistTrack.TrackId.EQ(Track.TrackId)). WHERE(PlaylistTrack.TrackId.EQ(Track.TrackId)).
ORDER_BY(Playlist.PlaylistId).AS("Playlists"), ORDER_BY(Playlist.PlaylistId).AS("Playlists"),
@ -273,9 +272,9 @@ func testJoinEverythingJSON(t require.TestingT) {
WHERE(Employee.EmployeeId.EQ(Customer.SupportRepId)).AS("Employee"), WHERE(Employee.EmployeeId.EQ(Customer.SupportRepId)).AS("Employee"),
).FROM(Customer). ).FROM(Customer).
WHERE(Customer.CustomerId.EQ(Invoice.CustomerId)).AS("Customer"), WHERE(Customer.CustomerId.EQ(Invoice.CustomerId)).AS("Customer"),
).FROM(Invoice.INNER_JOIN( ).FROM(
InvoiceLine, Invoice.
InvoiceLine.InvoiceId.EQ(Invoice.InvoiceId)), INNER_JOIN(InvoiceLine, InvoiceLine.InvoiceId.EQ(Invoice.InvoiceId)),
).WHERE(InvoiceLine.TrackId.EQ(Track.TrackId)). ).WHERE(InvoiceLine.TrackId.EQ(Track.TrackId)).
ORDER_BY(Invoice.InvoiceId).AS("Invoices"), ORDER_BY(Invoice.InvoiceId).AS("Invoices"),
).FROM(Track). ).FROM(Track).

View file

@ -20,6 +20,8 @@ import (
_ "github.com/jackc/pgx/v4/stdlib" _ "github.com/jackc/pgx/v4/stdlib"
) )
var ctx = context.Background()
var db *stmtcache.DB var db *stmtcache.DB
var testRoot string var testRoot string

View file

@ -139,7 +139,7 @@ func testNorthwindJoinEverythingJson(t require.TestingT) {
Territories, Territories,
EmployeeTerritories.TerritoryID.EQ(Territories.TerritoryID)), EmployeeTerritories.TerritoryID.EQ(Territories.TerritoryID)),
).WHERE( ).WHERE(
EmployeeTerritories.EmployeeID.EQ(Employees.EmployeeID), // TODO: move to join EmployeeTerritories.EmployeeID.EQ(Employees.EmployeeID),
).AS("Territories"), ).AS("Territories"),
).FROM(Employees). ).FROM(Employees).
WHERE(Orders.EmployeeID.EQ(Employees.EmployeeID)).AS("Employee"), WHERE(Orders.EmployeeID.EQ(Employees.EmployeeID)).AS("Employee"),

File diff suppressed because one or more lines are too long