Encode json values implicitly in the sql queries according the golang json package spec.
This commit is contained in:
parent
9616bb5cfe
commit
17646ca99c
54 changed files with 1446 additions and 744 deletions
|
|
@ -18,10 +18,45 @@ func (a *alias) fromImpl(subQuery SelectTable) Projection {
|
|||
// Generated columns have default aliasing.
|
||||
tableName, columnName := extractTableAndColumnName(a.alias)
|
||||
|
||||
column := NewColumnImpl(columnName, tableName, nil)
|
||||
column.subQuery = subQuery
|
||||
newDummyColumn := newDummyColumnForExpression(a.expression, columnName)
|
||||
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) {
|
||||
|
|
@ -31,7 +66,14 @@ func (a *alias) serializeForProjection(statement StatementType, out *SQLBuilder)
|
|||
out.WriteAlias(a.alias)
|
||||
}
|
||||
|
||||
func (a *alias) serializeForJsonObj(statement StatementType, out *SQLBuilder) {
|
||||
func (a *alias) serializeForJsonObjEntry(statement StatementType, out *SQLBuilder) {
|
||||
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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,71 +28,72 @@ type blobInterfaceImpl struct {
|
|||
parent BlobExpression
|
||||
}
|
||||
|
||||
func (s *blobInterfaceImpl) isStringOrBlob() {}
|
||||
func (b *blobInterfaceImpl) isStringOrBlob() {}
|
||||
|
||||
func (s *blobInterfaceImpl) EQ(rhs BlobExpression) BoolExpression {
|
||||
return Eq(s.parent, rhs)
|
||||
func (b *blobInterfaceImpl) EQ(rhs BlobExpression) BoolExpression {
|
||||
return Eq(b.parent, rhs)
|
||||
}
|
||||
|
||||
func (s *blobInterfaceImpl) NOT_EQ(rhs BlobExpression) BoolExpression {
|
||||
return NotEq(s.parent, rhs)
|
||||
func (b *blobInterfaceImpl) NOT_EQ(rhs BlobExpression) BoolExpression {
|
||||
return NotEq(b.parent, rhs)
|
||||
}
|
||||
|
||||
func (s *blobInterfaceImpl) IS_DISTINCT_FROM(rhs BlobExpression) BoolExpression {
|
||||
return IsDistinctFrom(s.parent, rhs)
|
||||
func (b *blobInterfaceImpl) IS_DISTINCT_FROM(rhs BlobExpression) BoolExpression {
|
||||
return IsDistinctFrom(b.parent, rhs)
|
||||
}
|
||||
|
||||
func (s *blobInterfaceImpl) IS_NOT_DISTINCT_FROM(rhs BlobExpression) BoolExpression {
|
||||
return IsNotDistinctFrom(s.parent, rhs)
|
||||
func (b *blobInterfaceImpl) IS_NOT_DISTINCT_FROM(rhs BlobExpression) BoolExpression {
|
||||
return IsNotDistinctFrom(b.parent, rhs)
|
||||
}
|
||||
|
||||
func (s *blobInterfaceImpl) GT(rhs BlobExpression) BoolExpression {
|
||||
return Gt(s.parent, rhs)
|
||||
func (b *blobInterfaceImpl) GT(rhs BlobExpression) BoolExpression {
|
||||
return Gt(b.parent, rhs)
|
||||
}
|
||||
|
||||
func (s *blobInterfaceImpl) GT_EQ(rhs BlobExpression) BoolExpression {
|
||||
return GtEq(s.parent, rhs)
|
||||
func (b *blobInterfaceImpl) GT_EQ(rhs BlobExpression) BoolExpression {
|
||||
return GtEq(b.parent, rhs)
|
||||
}
|
||||
|
||||
func (s *blobInterfaceImpl) LT(rhs BlobExpression) BoolExpression {
|
||||
return Lt(s.parent, rhs)
|
||||
func (b *blobInterfaceImpl) LT(rhs BlobExpression) BoolExpression {
|
||||
return Lt(b.parent, rhs)
|
||||
}
|
||||
|
||||
func (s *blobInterfaceImpl) LT_EQ(rhs BlobExpression) BoolExpression {
|
||||
return LtEq(s.parent, rhs)
|
||||
func (b *blobInterfaceImpl) LT_EQ(rhs BlobExpression) BoolExpression {
|
||||
return LtEq(b.parent, rhs)
|
||||
}
|
||||
|
||||
func (s *blobInterfaceImpl) BETWEEN(min, max BlobExpression) BoolExpression {
|
||||
return NewBetweenOperatorExpression(s.parent, min, max, false)
|
||||
func (b *blobInterfaceImpl) BETWEEN(min, max BlobExpression) BoolExpression {
|
||||
return NewBetweenOperatorExpression(b.parent, min, max, false)
|
||||
}
|
||||
|
||||
func (s *blobInterfaceImpl) NOT_BETWEEN(min, max BlobExpression) BoolExpression {
|
||||
return NewBetweenOperatorExpression(s.parent, min, max, true)
|
||||
func (b *blobInterfaceImpl) NOT_BETWEEN(min, max BlobExpression) BoolExpression {
|
||||
return NewBetweenOperatorExpression(b.parent, min, max, true)
|
||||
}
|
||||
|
||||
func (s *blobInterfaceImpl) CONCAT(rhs BlobExpression) BlobExpression {
|
||||
return BlobExp(newBinaryStringOperatorExpression(s.parent, rhs, StringConcatOperator))
|
||||
func (b *blobInterfaceImpl) CONCAT(rhs BlobExpression) BlobExpression {
|
||||
return BlobExp(newBinaryStringOperatorExpression(b.parent, rhs, StringConcatOperator))
|
||||
}
|
||||
|
||||
func (s *blobInterfaceImpl) LIKE(pattern BlobExpression) BoolExpression {
|
||||
return newBinaryBoolOperatorExpression(s.parent, pattern, "LIKE")
|
||||
func (b *blobInterfaceImpl) LIKE(pattern BlobExpression) BoolExpression {
|
||||
return newBinaryBoolOperatorExpression(b.parent, pattern, "LIKE")
|
||||
}
|
||||
|
||||
func (s *blobInterfaceImpl) NOT_LIKE(pattern BlobExpression) BoolExpression {
|
||||
return newBinaryBoolOperatorExpression(s.parent, pattern, "NOT LIKE")
|
||||
func (b *blobInterfaceImpl) NOT_LIKE(pattern BlobExpression) BoolExpression {
|
||||
return newBinaryBoolOperatorExpression(b.parent, pattern, "NOT LIKE")
|
||||
}
|
||||
|
||||
//---------------------------------------------------//
|
||||
|
||||
type blobExpressionWrapper struct {
|
||||
blobInterfaceImpl
|
||||
Expression
|
||||
blobInterfaceImpl
|
||||
}
|
||||
|
||||
func newBlobExpressionWrap(expression Expression) BlobExpression {
|
||||
blobExpressionWrap := blobExpressionWrapper{Expression: expression}
|
||||
blobExpressionWrap.blobInterfaceImpl.parent = &blobExpressionWrap
|
||||
return &blobExpressionWrap
|
||||
blobExpressionWrap := &blobExpressionWrapper{Expression: expression}
|
||||
blobExpressionWrap.blobInterfaceImpl.parent = blobExpressionWrap
|
||||
expression.setParent(blobExpressionWrap)
|
||||
return blobExpressionWrap
|
||||
}
|
||||
|
||||
// BlobExp is blob expression wrapper around arbitrary expression.
|
||||
|
|
|
|||
|
|
@ -102,9 +102,10 @@ type boolExpressionWrapper struct {
|
|||
}
|
||||
|
||||
func newBoolExpressionWrap(expression Expression) BoolExpression {
|
||||
boolExpressionWrap := boolExpressionWrapper{Expression: expression}
|
||||
boolExpressionWrap.boolInterfaceImpl.parent = &boolExpressionWrap
|
||||
return &boolExpressionWrap
|
||||
boolExpressionWrap := &boolExpressionWrapper{Expression: expression}
|
||||
boolExpressionWrap.boolInterfaceImpl.parent = boolExpressionWrap
|
||||
expression.setParent(boolExpressionWrap)
|
||||
return boolExpressionWrap
|
||||
}
|
||||
|
||||
// BoolExp is bool expression wrapper around arbitrary expression.
|
||||
|
|
|
|||
|
|
@ -41,6 +41,8 @@ type ClauseSelect struct {
|
|||
DistinctOnColumns []ColumnExpression
|
||||
ProjectionList []Projection
|
||||
|
||||
IsForRowToJson bool
|
||||
|
||||
// MySQL only
|
||||
OptimizerHints optimizerHints
|
||||
}
|
||||
|
|
@ -70,7 +72,13 @@ func (s *ClauseSelect) Serialize(statementType StatementType, out *SQLBuilder, o
|
|||
out.WriteByte(')')
|
||||
}
|
||||
|
||||
out.WriteProjections(statementType, s.ProjectionList)
|
||||
if s.IsForRowToJson {
|
||||
out.IncreaseIdent()
|
||||
out.WriteRowToJsonProjections(statementType, s.ProjectionList)
|
||||
out.DecreaseIdent()
|
||||
} else {
|
||||
out.WriteProjections(statementType, s.ProjectionList)
|
||||
}
|
||||
}
|
||||
|
||||
// ClauseFrom struct
|
||||
|
|
|
|||
|
|
@ -39,19 +39,19 @@ type ColumnExpressionImpl struct {
|
|||
}
|
||||
|
||||
// NewColumnImpl creates new ColumnExpressionImpl
|
||||
func NewColumnImpl(name string, tableName string, parent ColumnExpression) ColumnExpressionImpl {
|
||||
bc := ColumnExpressionImpl{
|
||||
func NewColumnImpl(name string, tableName string, parent ColumnExpression) *ColumnExpressionImpl {
|
||||
newColumn := &ColumnExpressionImpl{
|
||||
name: name,
|
||||
tableName: tableName,
|
||||
}
|
||||
|
||||
if parent != nil {
|
||||
bc.ExpressionInterfaceImpl.Parent = parent
|
||||
newColumn.ExpressionInterfaceImpl.Parent = parent
|
||||
} else {
|
||||
bc.ExpressionInterfaceImpl.Parent = &bc
|
||||
newColumn.ExpressionInterfaceImpl.Parent = newColumn
|
||||
}
|
||||
|
||||
return bc
|
||||
return newColumn
|
||||
}
|
||||
|
||||
// Name returns name of the column
|
||||
|
|
@ -80,13 +80,6 @@ func (c *ColumnExpressionImpl) defaultAlias() string {
|
|||
return c.name
|
||||
}
|
||||
|
||||
func (c *ColumnExpressionImpl) fromImpl(subQuery SelectTable) Projection {
|
||||
newColumn := NewColumnImpl(c.name, c.tableName, nil)
|
||||
newColumn.setSubQuery(subQuery)
|
||||
|
||||
return &newColumn
|
||||
}
|
||||
|
||||
func (c *ColumnExpressionImpl) serializeForOrderBy(statement StatementType, out *SQLBuilder) {
|
||||
if statement == SetStatementType {
|
||||
// set Statement (UNION, EXCEPT ...) can reference only select projections in order by clause
|
||||
|
|
@ -97,24 +90,28 @@ func (c *ColumnExpressionImpl) serializeForOrderBy(statement StatementType, 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)
|
||||
|
||||
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))
|
||||
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 {
|
||||
out.WriteIdentifier(c.subQuery.Alias())
|
||||
|
|
|
|||
|
|
@ -78,12 +78,18 @@ func (cl ColumnList) serializeForProjection(statement StatementType, out *SQLBui
|
|||
SerializeProjectionList(statement, projections, out)
|
||||
}
|
||||
|
||||
func (cl ColumnList) serializeForJsonObj(statement StatementType, out *SQLBuilder) {
|
||||
func (cl ColumnList) serializeForJsonObjEntry(statement StatementType, out *SQLBuilder) {
|
||||
projections := ColumnListToProjectionList(cl)
|
||||
|
||||
SerializeProjectionListJsonObj(statement, projections, out)
|
||||
}
|
||||
|
||||
func (cl ColumnList) serializeForRowToJsonProjection(statement StatementType, out *SQLBuilder) {
|
||||
projections := ColumnListToProjectionList(cl)
|
||||
|
||||
out.WriteRowToJsonProjections(statement, projections)
|
||||
}
|
||||
|
||||
// dummy column interface implementation
|
||||
|
||||
// Name is placeholder for ColumnList to implement Column interface
|
||||
|
|
|
|||
|
|
@ -4,11 +4,10 @@ import "testing"
|
|||
|
||||
func TestColumn(t *testing.T) {
|
||||
column := NewColumnImpl("col", "", nil)
|
||||
column.ExpressionInterfaceImpl.Parent = &column
|
||||
|
||||
assertClauseSerialize(t, column, "col")
|
||||
column.setTableName("table1")
|
||||
assertClauseSerialize(t, column, "table1.col")
|
||||
assertProjectionSerialize(t, &column, `table1.col AS "table1.col"`)
|
||||
assertProjectionSerialize(t, column, `table1.col AS "table1.col"`)
|
||||
assertProjectionSerialize(t, column.AS("alias1"), `table1.col AS "alias1"`)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,11 @@ type ColumnBool interface {
|
|||
|
||||
type boolColumnImpl struct {
|
||||
boolInterfaceImpl
|
||||
ColumnExpressionImpl
|
||||
*ColumnExpressionImpl
|
||||
}
|
||||
|
||||
func (i *boolColumnImpl) fromImpl(subQuery SelectTable) Projection {
|
||||
return i.From(subQuery)
|
||||
}
|
||||
|
||||
func (i *boolColumnImpl) From(subQuery SelectTable) ColumnBool {
|
||||
|
|
@ -51,7 +55,11 @@ type ColumnFloat interface {
|
|||
|
||||
type floatColumnImpl struct {
|
||||
floatInterfaceImpl
|
||||
ColumnExpressionImpl
|
||||
*ColumnExpressionImpl
|
||||
}
|
||||
|
||||
func (i *floatColumnImpl) fromImpl(subQuery SelectTable) Projection {
|
||||
return i.From(subQuery)
|
||||
}
|
||||
|
||||
func (i *floatColumnImpl) From(subQuery SelectTable) ColumnFloat {
|
||||
|
|
@ -92,7 +100,11 @@ type ColumnInteger interface {
|
|||
type integerColumnImpl struct {
|
||||
integerInterfaceImpl
|
||||
|
||||
ColumnExpressionImpl
|
||||
*ColumnExpressionImpl
|
||||
}
|
||||
|
||||
func (i *integerColumnImpl) fromImpl(subQuery SelectTable) Projection {
|
||||
return i.From(subQuery)
|
||||
}
|
||||
|
||||
func (i *integerColumnImpl) From(subQuery SelectTable) ColumnInteger {
|
||||
|
|
@ -134,7 +146,11 @@ type ColumnString interface {
|
|||
type stringColumnImpl struct {
|
||||
stringInterfaceImpl
|
||||
|
||||
ColumnExpressionImpl
|
||||
*ColumnExpressionImpl
|
||||
}
|
||||
|
||||
func (i *stringColumnImpl) fromImpl(subQuery SelectTable) Projection {
|
||||
return i.From(subQuery)
|
||||
}
|
||||
|
||||
func (i *stringColumnImpl) From(subQuery SelectTable) ColumnString {
|
||||
|
|
@ -175,7 +191,11 @@ type ColumnBlob interface {
|
|||
type blobColumnImpl struct {
|
||||
blobInterfaceImpl
|
||||
|
||||
ColumnExpressionImpl
|
||||
*ColumnExpressionImpl
|
||||
}
|
||||
|
||||
func (i *blobColumnImpl) fromImpl(subQuery SelectTable) Projection {
|
||||
return i.From(subQuery)
|
||||
}
|
||||
|
||||
func (i *blobColumnImpl) From(subQuery SelectTable) ColumnBlob {
|
||||
|
|
@ -215,7 +235,11 @@ type ColumnTime interface {
|
|||
|
||||
type timeColumnImpl struct {
|
||||
timeInterfaceImpl
|
||||
ColumnExpressionImpl
|
||||
*ColumnExpressionImpl
|
||||
}
|
||||
|
||||
func (i *timeColumnImpl) fromImpl(subQuery SelectTable) Projection {
|
||||
return i.From(subQuery)
|
||||
}
|
||||
|
||||
func (i *timeColumnImpl) From(subQuery SelectTable) ColumnTime {
|
||||
|
|
@ -254,7 +278,11 @@ type ColumnTimez interface {
|
|||
|
||||
type timezColumnImpl struct {
|
||||
timezInterfaceImpl
|
||||
ColumnExpressionImpl
|
||||
*ColumnExpressionImpl
|
||||
}
|
||||
|
||||
func (i *timezColumnImpl) fromImpl(subQuery SelectTable) Projection {
|
||||
return i.From(subQuery)
|
||||
}
|
||||
|
||||
func (i *timezColumnImpl) From(subQuery SelectTable) ColumnTimez {
|
||||
|
|
@ -294,7 +322,11 @@ type ColumnTimestamp interface {
|
|||
|
||||
type timestampColumnImpl struct {
|
||||
timestampInterfaceImpl
|
||||
ColumnExpressionImpl
|
||||
*ColumnExpressionImpl
|
||||
}
|
||||
|
||||
func (i *timestampColumnImpl) fromImpl(subQuery SelectTable) Projection {
|
||||
return i.From(subQuery)
|
||||
}
|
||||
|
||||
func (i *timestampColumnImpl) From(subQuery SelectTable) ColumnTimestamp {
|
||||
|
|
@ -334,7 +366,11 @@ type ColumnTimestampz interface {
|
|||
|
||||
type timestampzColumnImpl struct {
|
||||
timestampzInterfaceImpl
|
||||
ColumnExpressionImpl
|
||||
*ColumnExpressionImpl
|
||||
}
|
||||
|
||||
func (i *timestampzColumnImpl) fromImpl(subQuery SelectTable) Projection {
|
||||
return i.From(subQuery)
|
||||
}
|
||||
|
||||
func (i *timestampzColumnImpl) From(subQuery SelectTable) ColumnTimestampz {
|
||||
|
|
@ -374,7 +410,11 @@ type ColumnDate interface {
|
|||
|
||||
type dateColumnImpl struct {
|
||||
dateInterfaceImpl
|
||||
ColumnExpressionImpl
|
||||
*ColumnExpressionImpl
|
||||
}
|
||||
|
||||
func (i *dateColumnImpl) fromImpl(subQuery SelectTable) Projection {
|
||||
return i.From(subQuery)
|
||||
}
|
||||
|
||||
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
|
||||
// timestamp range or date range.
|
||||
type ColumnRange[T Expression] interface {
|
||||
|
|
@ -414,7 +499,11 @@ type ColumnRange[T Expression] interface {
|
|||
|
||||
type rangeColumnImpl[T Expression] struct {
|
||||
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] {
|
||||
|
|
|
|||
|
|
@ -80,9 +80,10 @@ type dateExpressionWrapper struct {
|
|||
}
|
||||
|
||||
func newDateExpressionWrap(expression Expression) DateExpression {
|
||||
dateExpressionWrap := dateExpressionWrapper{Expression: expression}
|
||||
dateExpressionWrap.dateInterfaceImpl.parent = &dateExpressionWrap
|
||||
return &dateExpressionWrap
|
||||
dateExpressionWrap := &dateExpressionWrapper{Expression: expression}
|
||||
dateExpressionWrap.dateInterfaceImpl.parent = dateExpressionWrap
|
||||
expression.setParent(dateExpressionWrap)
|
||||
return dateExpressionWrap
|
||||
}
|
||||
|
||||
// DateExp is date expression wrapper around arbitrary expression.
|
||||
|
|
|
|||
|
|
@ -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')")
|
||||
}
|
||||
|
|
@ -17,6 +17,7 @@ type Dialect interface {
|
|||
IsReservedWord(name string) bool
|
||||
SerializeOrderBy() func(expression Expression, ascending, nullsFirst *bool) SerializerFunc
|
||||
ValuesDefaultColumnName(index int) string
|
||||
JsonValueEncode(expr Expression) Expression
|
||||
}
|
||||
|
||||
// SerializerFunc func
|
||||
|
|
@ -41,6 +42,7 @@ type DialectParams struct {
|
|||
ReservedWords []string
|
||||
SerializeOrderBy func(expression Expression, ascending, nullsFirst *bool) SerializerFunc
|
||||
ValuesDefaultColumnName func(index int) string
|
||||
JsonValueEncode func(expr Expression) Expression
|
||||
}
|
||||
|
||||
// NewDialect creates new dialect with params
|
||||
|
|
@ -57,6 +59,7 @@ func NewDialect(params DialectParams) Dialect {
|
|||
reservedWords: arrayOfStringsToMapOfStrings(params.ReservedWords),
|
||||
serializeOrderBy: params.SerializeOrderBy,
|
||||
valuesDefaultColumnName: params.ValuesDefaultColumnName,
|
||||
jsonValueEncode: params.JsonValueEncode,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -72,6 +75,7 @@ type dialectImpl struct {
|
|||
reservedWords map[string]bool
|
||||
serializeOrderBy func(expression Expression, ascending, nullsFirst *bool) SerializerFunc
|
||||
valuesDefaultColumnName func(index int) string
|
||||
jsonValueEncode func(expr Expression) Expression
|
||||
}
|
||||
|
||||
func (d *dialectImpl) Name() string {
|
||||
|
|
@ -125,6 +129,10 @@ func (d *dialectImpl) ValuesDefaultColumnName(index int) string {
|
|||
return d.valuesDefaultColumnName(index)
|
||||
}
|
||||
|
||||
func (d *dialectImpl) JsonValueEncode(expr Expression) Expression {
|
||||
return d.jsonValueEncode(expr)
|
||||
}
|
||||
|
||||
func arrayOfStringsToMapOfStrings(arr []string) map[string]bool {
|
||||
ret := map[string]bool{}
|
||||
for _, elem := range arr {
|
||||
|
|
|
|||
|
|
@ -10,6 +10,9 @@ type Expression interface {
|
|||
GroupByClause
|
||||
OrderByClause
|
||||
|
||||
serializeForJsonValue(statement StatementType, out *SQLBuilder)
|
||||
setParent(parent Expression)
|
||||
|
||||
// IS_NULL tests expression whether it is a NULL value.
|
||||
IS_NULL() BoolExpression
|
||||
// IS_NOT_NULL tests expression whether it is a non-NULL value.
|
||||
|
|
@ -34,6 +37,10 @@ type ExpressionInterfaceImpl struct {
|
|||
Parent Expression
|
||||
}
|
||||
|
||||
func (e *ExpressionInterfaceImpl) setParent(parent Expression) {
|
||||
e.Parent = parent
|
||||
}
|
||||
|
||||
func (e *ExpressionInterfaceImpl) fromImpl(subQuery SelectTable) Projection {
|
||||
panic(fmt.Sprintf("jet: can't export unaliased expression subQuery: %s, expression: %s",
|
||||
subQuery.Alias(), serializeToDefaultDebugString(e.Parent)))
|
||||
|
|
@ -89,16 +96,21 @@ func (e *ExpressionInterfaceImpl) serializeForGroupBy(statement StatementType, o
|
|||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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.")
|
||||
}
|
||||
|
||||
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) {
|
||||
e.Parent.serialize(statement, out, NoWrap)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -102,9 +102,10 @@ type floatExpressionWrapper struct {
|
|||
}
|
||||
|
||||
func newFloatExpressionWrap(expression Expression) FloatExpression {
|
||||
floatExpressionWrap := floatExpressionWrapper{Expression: expression}
|
||||
floatExpressionWrap.floatInterfaceImpl.parent = &floatExpressionWrap
|
||||
return &floatExpressionWrap
|
||||
floatExpressionWrap := &floatExpressionWrapper{Expression: expression}
|
||||
floatExpressionWrap.floatInterfaceImpl.parent = floatExpressionWrap
|
||||
expression.setParent(floatExpressionWrap)
|
||||
return floatExpressionWrap
|
||||
}
|
||||
|
||||
// FloatExp is date expression wrapper around arbitrary expression.
|
||||
|
|
|
|||
|
|
@ -141,11 +141,11 @@ type integerExpressionWrapper struct {
|
|||
}
|
||||
|
||||
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.
|
||||
|
|
|
|||
|
|
@ -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)...)
|
||||
}
|
||||
112
internal/jet/interval_expression.go
Normal file
112
internal/jet/interval_expression.go
Normal 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()
|
||||
}
|
||||
|
|
@ -412,17 +412,6 @@ func Raw(raw string, namedArgs ...map[string]interface{}) Expression {
|
|||
return rawExp
|
||||
}
|
||||
|
||||
// RawWithParent is a Raw constructor used for construction dialect specific expression
|
||||
func RawWithParent(raw string, parent ...Expression) Expression {
|
||||
rawExp := &rawExpression{
|
||||
Raw: raw,
|
||||
noWrap: true,
|
||||
}
|
||||
rawExp.ExpressionInterfaceImpl.Parent = OptionalOrDefaultExpression(rawExp, parent...)
|
||||
|
||||
return rawExp
|
||||
}
|
||||
|
||||
// RawBool helper that for raw string boolean expressions
|
||||
func RawBool(raw string, namedArgs ...map[string]interface{}) BoolExpression {
|
||||
return BoolExp(Raw(raw, namedArgs...))
|
||||
|
|
|
|||
|
|
@ -3,7 +3,8 @@ package jet
|
|||
// Projection is interface for all projection types. Types that can be part of, for instance SELECT clause.
|
||||
type Projection interface {
|
||||
serializeForProjection(statement StatementType, out *SQLBuilder)
|
||||
serializeForJsonObj(statement StatementType, out *SQLBuilder)
|
||||
serializeForJsonObjEntry(statement StatementType, out *SQLBuilder)
|
||||
serializeForRowToJsonProjection(statement StatementType, out *SQLBuilder)
|
||||
fromImpl(subQuery SelectTable) Projection
|
||||
}
|
||||
|
||||
|
|
@ -29,7 +30,7 @@ func (pl ProjectionList) serializeForProjection(statement StatementType, out *SQ
|
|||
SerializeProjectionList(statement, pl, out)
|
||||
}
|
||||
|
||||
func (pl ProjectionList) serializeForJsonObj(statement StatementType, out *SQLBuilder) {
|
||||
func (pl ProjectionList) serializeForJsonObjEntry(statement StatementType, out *SQLBuilder) {
|
||||
SerializeProjectionListJsonObj(statement, pl, out)
|
||||
}
|
||||
|
||||
|
|
@ -85,9 +86,17 @@ func (pl ProjectionList) Except(toExclude ...Column) ProjectionList {
|
|||
return ret
|
||||
}
|
||||
|
||||
// JsonProjectionList redefines []Projection so projections can be serialized as json object key/values
|
||||
type JsonProjectionList []Projection
|
||||
|
||||
func (j JsonProjectionList) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) {
|
||||
SerializeProjectionListJsonObj(statement, j, out)
|
||||
func (pl ProjectionList) serializeForRowToJsonProjection(statement StatementType, out *SQLBuilder) {
|
||||
out.WriteRowToJsonProjections(statement, pl)
|
||||
}
|
||||
|
||||
// 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()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -118,9 +118,10 @@ type rangeExpressionWrapper[T Expression] struct {
|
|||
}
|
||||
|
||||
func newRangeExpressionWrap[T Expression](expression Expression) Range[T] {
|
||||
rangeExpressionWrap := rangeExpressionWrapper[T]{Expression: expression}
|
||||
rangeExpressionWrap.rangeInterfaceImpl.parent = &rangeExpressionWrap
|
||||
return &rangeExpressionWrap
|
||||
rangeExpressionWrap := &rangeExpressionWrapper[T]{Expression: expression}
|
||||
rangeExpressionWrap.rangeInterfaceImpl.parent = rangeExpressionWrap
|
||||
expression.setParent(rangeExpressionWrap)
|
||||
return rangeExpressionWrap
|
||||
}
|
||||
|
||||
// RangeExp is range expression wrapper around arbitrary expression.
|
||||
|
|
|
|||
|
|
@ -17,9 +17,9 @@ type RowExpression interface {
|
|||
}
|
||||
|
||||
type rowInterfaceImpl struct {
|
||||
parent Expression
|
||||
dialect Dialect
|
||||
elemCount int
|
||||
parent Expression
|
||||
dialect Dialect
|
||||
expressions []Expression
|
||||
}
|
||||
|
||||
func (n *rowInterfaceImpl) EQ(rhs RowExpression) BoolExpression {
|
||||
|
|
@ -57,9 +57,8 @@ func (n *rowInterfaceImpl) LT_EQ(rhs RowExpression) BoolExpression {
|
|||
func (n *rowInterfaceImpl) projections() ProjectionList {
|
||||
var ret ProjectionList
|
||||
|
||||
for i := 0; i < n.elemCount; i++ {
|
||||
rowColumn := NewColumnImpl(n.dialect.ValuesDefaultColumnName(i), "", nil)
|
||||
ret = append(ret, &rowColumn)
|
||||
for i, expression := range n.expressions {
|
||||
ret = append(ret, newDummyColumnForExpression(expression, n.dialect.ValuesDefaultColumnName(i)))
|
||||
}
|
||||
|
||||
return ret
|
||||
|
|
@ -77,7 +76,7 @@ func newRowExpression(name string, dialect Dialect, expressions ...Expression) R
|
|||
|
||||
ret.Expression = NewFunc(name, expressions, ret)
|
||||
ret.dialect = dialect
|
||||
ret.elemCount = len(expressions)
|
||||
ret.expressions = expressions
|
||||
|
||||
return ret
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,10 +22,6 @@ func (s SerializeOption) WithFallTrough(options []SerializeOption) []SerializeOp
|
|||
// StatementType is type of the SQL statement
|
||||
type StatementType string
|
||||
|
||||
func (s StatementType) IsSelectJSON() bool {
|
||||
return s == SelectJsonObjStatementType || s == SelectJsonArrStatementType
|
||||
}
|
||||
|
||||
// Statement types
|
||||
const (
|
||||
SelectStatementType StatementType = "SELECT"
|
||||
|
|
|
|||
|
|
@ -61,6 +61,16 @@ func (s *SQLBuilder) WriteProjections(statement StatementType, projections []Pro
|
|||
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
|
||||
func (s *SQLBuilder) NewLine() {
|
||||
s.write([]byte{'\n'})
|
||||
|
|
|
|||
|
|
@ -257,12 +257,13 @@ type expressionStatementImpl struct {
|
|||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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
|
||||
func NewStatementImpl(Dialect Dialect, statementType StatementType, parent SerializerStatement, clauses ...Clause) SerializerStatement {
|
||||
return &statementImpl{
|
||||
|
|
|
|||
|
|
@ -105,9 +105,10 @@ type stringExpressionWrapper struct {
|
|||
}
|
||||
|
||||
func newStringExpressionWrap(expression Expression) StringExpression {
|
||||
stringExpressionWrap := stringExpressionWrapper{Expression: expression}
|
||||
stringExpressionWrap.stringInterfaceImpl.parent = &stringExpressionWrap
|
||||
return &stringExpressionWrap
|
||||
stringExpressionWrap := &stringExpressionWrapper{Expression: expression}
|
||||
stringExpressionWrap.stringInterfaceImpl.parent = stringExpressionWrap
|
||||
expression.setParent(stringExpressionWrap)
|
||||
return stringExpressionWrap
|
||||
}
|
||||
|
||||
// StringExp is string expression wrapper around arbitrary expression.
|
||||
|
|
|
|||
|
|
@ -75,14 +75,15 @@ func (t *timeInterfaceImpl) SUB(rhs Interval) TimeExpression {
|
|||
//---------------------------------------------------//
|
||||
|
||||
type timeExpressionWrapper struct {
|
||||
timeInterfaceImpl
|
||||
Expression
|
||||
timeInterfaceImpl
|
||||
}
|
||||
|
||||
func newTimeExpressionWrap(expression Expression) TimeExpression {
|
||||
timeExpressionWrap := timeExpressionWrapper{Expression: expression}
|
||||
timeExpressionWrap.timeInterfaceImpl.parent = &timeExpressionWrap
|
||||
return &timeExpressionWrap
|
||||
timeExpressionWrap := &timeExpressionWrapper{Expression: expression}
|
||||
timeExpressionWrap.timeInterfaceImpl.parent = timeExpressionWrap
|
||||
expression.setParent(timeExpressionWrap)
|
||||
return timeExpressionWrap
|
||||
}
|
||||
|
||||
// TimeExp is time expression wrapper around arbitrary expression.
|
||||
|
|
|
|||
|
|
@ -52,11 +52,3 @@ func TestTimeExp(t *testing.T) {
|
|||
assertClauseSerialize(t, TimeExp(table1ColFloat).LT(Time(1, 1, 1, 1*time.Millisecond)),
|
||||
"(table1.col_float < $1)", string("01:01:01.001"))
|
||||
}
|
||||
|
||||
func TestTimeArithmetic(t *testing.T) {
|
||||
time := Time(10, 20, 3)
|
||||
assertClauseDebugSerialize(t, table1ColTime.ADD(NewInterval(String("1 HOUR"))).EQ(time),
|
||||
"((table1.col_time + INTERVAL '1 HOUR') = '10:20:03')")
|
||||
assertClauseDebugSerialize(t, table1ColTime.SUB(NewInterval(String("1 HOUR"))).EQ(time),
|
||||
"((table1.col_time - INTERVAL '1 HOUR') = '10:20:03')")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -80,9 +80,10 @@ type timestampExpressionWrapper struct {
|
|||
}
|
||||
|
||||
func newTimestampExpressionWrap(expression Expression) TimestampExpression {
|
||||
timestampExpressionWrap := timestampExpressionWrapper{Expression: expression}
|
||||
timestampExpressionWrap.timestampInterfaceImpl.parent = ×tampExpressionWrap
|
||||
return ×tampExpressionWrap
|
||||
timestampExpressionWrap := ×tampExpressionWrapper{Expression: expression}
|
||||
timestampExpressionWrap.timestampInterfaceImpl.parent = timestampExpressionWrap
|
||||
expression.setParent(timestampExpressionWrap)
|
||||
return timestampExpressionWrap
|
||||
}
|
||||
|
||||
// TimestampExp is timestamp expression wrapper around arbitrary expression.
|
||||
|
|
|
|||
|
|
@ -53,11 +53,3 @@ func TestTimestampExp(t *testing.T) {
|
|||
assertClauseSerialize(t, TimestampExp(table1ColFloat).LT(timestamp),
|
||||
"(table1.col_float < $1)", "2000-01-31 10:20:00.003")
|
||||
}
|
||||
|
||||
func TestTimestampArithmetic(t *testing.T) {
|
||||
timestamp := Timestamp(2000, 1, 1, 0, 0, 0)
|
||||
assertClauseDebugSerialize(t, table1ColTimestamp.ADD(NewInterval(String("1 HOUR"))).EQ(timestamp),
|
||||
"((table1.col_timestamp + INTERVAL '1 HOUR') = '2000-01-01 00:00:00')")
|
||||
assertClauseDebugSerialize(t, table1ColTimestamp.SUB(NewInterval(String("1 HOUR"))).EQ(timestamp),
|
||||
"((table1.col_timestamp - INTERVAL '1 HOUR') = '2000-01-01 00:00:00')")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -80,9 +80,10 @@ type timestampzExpressionWrapper struct {
|
|||
}
|
||||
|
||||
func newTimestampzExpressionWrap(expression Expression) TimestampzExpression {
|
||||
timestampzExpressionWrap := timestampzExpressionWrapper{Expression: expression}
|
||||
timestampzExpressionWrap.timestampzInterfaceImpl.parent = ×tampzExpressionWrap
|
||||
return ×tampzExpressionWrap
|
||||
timestampzExpressionWrap := ×tampzExpressionWrapper{Expression: expression}
|
||||
timestampzExpressionWrap.timestampzInterfaceImpl.parent = timestampzExpressionWrap
|
||||
expression.setParent(timestampzExpressionWrap)
|
||||
return timestampzExpressionWrap
|
||||
}
|
||||
|
||||
// TimestampzExp is timestamp with time zone expression wrapper around arbitrary expression.
|
||||
|
|
|
|||
|
|
@ -53,11 +53,3 @@ func TestTimestampzExp(t *testing.T) {
|
|||
assertClauseSerialize(t, TimestampzExp(table1ColFloat).LT(timestampz),
|
||||
"(table1.col_float < $1)", "2000-01-31 10:20:05.000023 +200")
|
||||
}
|
||||
|
||||
func TestTimestampzArithmetic(t *testing.T) {
|
||||
timestampz := Timestampz(2000, 1, 1, 0, 0, 0, 100, "UTC")
|
||||
assertClauseDebugSerialize(t, table1ColTimestampz.ADD(NewInterval(String("1 HOUR"))).EQ(timestampz),
|
||||
"((table1.col_timestampz + INTERVAL '1 HOUR') = '2000-01-01 00:00:00.0000001 UTC')")
|
||||
assertClauseDebugSerialize(t, table1ColTimestampz.SUB(NewInterval(String("1 HOUR"))).EQ(timestampz),
|
||||
"((table1.col_timestampz - INTERVAL '1 HOUR') = '2000-01-01 00:00:00.0000001 UTC')")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -75,14 +75,15 @@ func (t *timezInterfaceImpl) SUB(rhs Interval) TimezExpression {
|
|||
//---------------------------------------------------//
|
||||
|
||||
type timezExpressionWrapper struct {
|
||||
timezInterfaceImpl
|
||||
Expression
|
||||
timezInterfaceImpl
|
||||
}
|
||||
|
||||
func newTimezExpressionWrap(expression Expression) TimezExpression {
|
||||
timezExpressionWrap := timezExpressionWrapper{Expression: expression}
|
||||
timezExpressionWrap.timezInterfaceImpl.parent = &timezExpressionWrap
|
||||
return &timezExpressionWrap
|
||||
timezExpressionWrap := &timezExpressionWrapper{Expression: expression}
|
||||
timezExpressionWrap.timezInterfaceImpl.parent = timezExpressionWrap
|
||||
expression.setParent(timezExpressionWrap)
|
||||
return timezExpressionWrap
|
||||
}
|
||||
|
||||
// TimezExp is time with time zone expression wrapper around arbitrary expression.
|
||||
|
|
|
|||
|
|
@ -51,11 +51,3 @@ func TestTimezExp(t *testing.T) {
|
|||
assertClauseSerialize(t, TimezExp(table1ColFloat).LT(Timez(1, 1, 1, 1, "+4:00")),
|
||||
"(table1.col_float < $1)", string("01:01:01.000000001 +4:00"))
|
||||
}
|
||||
|
||||
func TestTimezArithmetic(t *testing.T) {
|
||||
timez := Timez(0, 0, 0, 100, "UTC")
|
||||
assertClauseDebugSerialize(t, table1ColTimez.ADD(NewInterval(String("1 HOUR"))).EQ(timez),
|
||||
"((table1.col_timez + INTERVAL '1 HOUR') = '00:00:00.0000001 UTC')")
|
||||
assertClauseDebugSerialize(t, table1ColTimez.SUB(NewInterval(String("1 HOUR"))).EQ(timez),
|
||||
"((table1.col_timez - INTERVAL '1 HOUR') = '00:00:00.0000001 UTC')")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ func SerializeProjectionListJsonObj(statement StatementType, projections []Proje
|
|||
panic("jet: Projection is nil")
|
||||
}
|
||||
|
||||
p.serializeForJsonObj(statement, out)
|
||||
p.serializeForJsonObjEntry(statement, out)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -122,7 +122,7 @@ func AssertJsonEqual(t require.TestingT, actual, expected interface{}, option ..
|
|||
expectedJsonData, err := json.MarshalIndent(expected, "", "\t")
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, actualJsonData, expectedJsonData)
|
||||
require.Equal(t, string(actualJsonData), string(expectedJsonData))
|
||||
}
|
||||
|
||||
// SaveJSONFile saves v as json at testRelativePath
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import (
|
|||
"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()
|
||||
|
||||
func newDialect() jet.Dialect {
|
||||
|
|
@ -34,6 +34,23 @@ func newDialect() jet.Dialect {
|
|||
ValuesDefaultColumnName: func(index int) string {
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -98,13 +98,10 @@ func INTERVAL(value interface{}, unitType unitType) Interval {
|
|||
|
||||
// INTERVALe creates new temporal interval from expresion and unit type.
|
||||
func INTERVALe(expr Expression, unitType unitType) Interval {
|
||||
return jet.NewInterval(jet.ListSerializer{
|
||||
Serializers: []jet.Serializer{expr, jet.RawWithParent(string(unitType))},
|
||||
Separator: " ",
|
||||
})
|
||||
return jet.IntervalExp(CustomExpression(Token("INTERVAL"), expr, Token(unitType)))
|
||||
}
|
||||
|
||||
// INTERVALd temoral interval from time.Duration
|
||||
// INTERVALd creates new temporal interval from time.Duration
|
||||
func INTERVALd(duration time.Duration) Interval {
|
||||
var sign int64 = 1
|
||||
if duration < 0 {
|
||||
|
|
@ -43,7 +43,7 @@ func newSelectStatementJson(projections []Projection, statementType jet.Statemen
|
|||
}
|
||||
|
||||
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 {
|
||||
return Func("JSON_ARRAYAGG", jsonObj)
|
||||
|
|
|
|||
|
|
@ -71,6 +71,12 @@ type ColumnTimestampz = jet.ColumnTimestampz
|
|||
// TimestampzColumn creates named timestamp with time zone column.
|
||||
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
|
||||
type ColumnDateRange = jet.ColumnRange[DateExpression]
|
||||
|
||||
|
|
@ -106,41 +112,3 @@ type ColumnInt8Range jet.ColumnRange[jet.Int8Expression]
|
|||
|
||||
// Int8RangeColumn creates named range with range column
|
||||
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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,9 +3,8 @@ package postgres
|
|||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/go-jet/jet/v2/internal/jet"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// Dialect is implementation of postgres dialect for SQL Builder serialisation.
|
||||
|
|
@ -32,6 +31,23 @@ func newDialect() jet.Dialect {
|
|||
ValuesDefaultColumnName: func(index int) string {
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -41,6 +41,9 @@ type TimestampzExpression = jet.TimestampzExpression
|
|||
// RowExpression interface
|
||||
type RowExpression = jet.RowExpression
|
||||
|
||||
// IntervalExpression interface
|
||||
type IntervalExpression = jet.IntervalExpression
|
||||
|
||||
// DateRange Expression interface
|
||||
type DateRange = jet.Range[DateExpression]
|
||||
|
||||
|
|
@ -109,6 +112,11 @@ var TimestampExp = jet.TimestampExp
|
|||
// Does not add sql cast to generated sql builder output.
|
||||
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.
|
||||
// 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.
|
||||
|
|
|
|||
|
|
@ -194,18 +194,18 @@ func CONCAT_WS(separator Expression, expressions ...Expression) StringExpression
|
|||
|
||||
// Character encodings for CONVERT, CONVERT_FROM and CONVERT_TO functions
|
||||
var (
|
||||
UTF8 = String("UTF8")
|
||||
LATIN1 = String("LATIN1")
|
||||
LATIN2 = String("LATIN2")
|
||||
LATIN3 = String("LATIN3")
|
||||
LATIN4 = String("LATIN4")
|
||||
WIN1252 = String("WIN1252")
|
||||
ISO_8859_5 = String("ISO_8859_5")
|
||||
ISO_8859_6 = String("ISO_8859_6")
|
||||
ISO_8859_7 = String("ISO_8859_7")
|
||||
ISO_8859_8 = String("ISO_8859_8")
|
||||
KOI8R = String("KOI8R")
|
||||
KOI8U = String("KOI8U")
|
||||
UTF8 = StringExp(jet.FixedLiteral("UTF8"))
|
||||
LATIN1 = StringExp(jet.FixedLiteral("LATIN1"))
|
||||
LATIN2 = StringExp(jet.FixedLiteral("LATIN2"))
|
||||
LATIN3 = StringExp(jet.FixedLiteral("LATIN3"))
|
||||
LATIN4 = StringExp(jet.FixedLiteral("LATIN4"))
|
||||
WIN1252 = StringExp(jet.FixedLiteral("WIN1252"))
|
||||
ISO_8859_5 = StringExp(jet.FixedLiteral("ISO_8859_5"))
|
||||
ISO_8859_6 = StringExp(jet.FixedLiteral("ISO_8859_6"))
|
||||
ISO_8859_7 = StringExp(jet.FixedLiteral("ISO_8859_7"))
|
||||
ISO_8859_8 = StringExp(jet.FixedLiteral("ISO_8859_8"))
|
||||
KOI8R = StringExp(jet.FixedLiteral("KOI8R"))
|
||||
KOI8U = StringExp(jet.FixedLiteral("KOI8U"))
|
||||
)
|
||||
|
||||
// CONVERT converts string to dest_encoding. The original encoding is
|
||||
|
|
@ -223,9 +223,9 @@ var CONVERT_TO = jet.CONVERT_TO
|
|||
|
||||
// ENCODE/DECODE textual formats
|
||||
var (
|
||||
Base64 StringExpression = String("base64")
|
||||
Escape StringExpression = String("escape")
|
||||
Hex StringExpression = String("hex")
|
||||
Base64 = StringExp(jet.FixedLiteral("base64"))
|
||||
Escape = StringExp(jet.FixedLiteral("escape"))
|
||||
Hex = StringExp(jet.FixedLiteral("hex"))
|
||||
)
|
||||
|
||||
// ENCODE encodes binary data into a textual representation.
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
140
postgres/interval_literal.go
Normal file
140
postgres/interval_literal.go
Normal 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")
|
||||
}
|
||||
}
|
||||
|
||||
//---------------------------------------------------//
|
||||
|
|
@ -105,6 +105,7 @@ func newSelectStatementJson(projections []Projection, statementType jet.Statemen
|
|||
}
|
||||
|
||||
newSelectJson.setOperatorsImpl.stmtRoot = newSelectJson
|
||||
newSelectJson.subQuery.Select.IsForRowToJson = true
|
||||
|
||||
newSelectJson.setSubQueryAlias("")
|
||||
|
||||
|
|
|
|||
|
|
@ -3,9 +3,9 @@ package qrm
|
|||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/go-jet/jet/v2/internal/3rdparty/json"
|
||||
"github.com/go-jet/jet/v2/internal/utils/must"
|
||||
"reflect"
|
||||
)
|
||||
|
|
@ -67,8 +67,8 @@ func QueryJsonArr(ctx context.Context, db Queryable, query string, args []interf
|
|||
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 jsonDestArrErr = "jet: destination has to be a pointer to slice of 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: 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) {
|
||||
must.BeInitializedPtr(db, "jet: db is nil")
|
||||
|
|
|
|||
|
|
@ -43,29 +43,82 @@ func TestAllTypesJSON(t *testing.T) {
|
|||
AllTypes.JSONPtr,
|
||||
AllTypes.Bit,
|
||||
AllTypes.BitPtr,
|
||||
AllTypes.Blob,
|
||||
AllTypes.BlobPtr,
|
||||
AllTypes.Binary,
|
||||
AllTypes.BinaryPtr,
|
||||
AllTypes.VarBinary,
|
||||
AllTypes.VarBinaryPtr,
|
||||
),
|
||||
CAST(AllTypes.JSON).AS_CHAR().AS("Json"),
|
||||
CAST(AllTypes.JSONPtr).AS_CHAR().AS("JsonPtr"),
|
||||
CAST(AllTypes.Bit).AS_CHAR().AS("Bit"),
|
||||
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)
|
||||
|
||||
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
|
||||
|
||||
err := stmt.QueryJSON(ctx, db, &dest)
|
||||
|
|
@ -76,7 +129,6 @@ func TestAllTypesJSON(t *testing.T) {
|
|||
dest[0].FloatPtr = ptr.Of(3.33)
|
||||
dest[1].Float = 3.33
|
||||
|
||||
//fmt.Println(allTypesJson)
|
||||
testutils.AssertJSON(t, dest, allTypesJson)
|
||||
}
|
||||
|
||||
|
|
@ -1182,6 +1234,118 @@ func TestAllTypesInsertOnDuplicateKeyUpdate(t *testing.T) {
|
|||
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{
|
||||
Boolean: false,
|
||||
BooleanPtr: ptr.Of(true),
|
||||
|
|
|
|||
|
|
@ -23,10 +23,12 @@ func TestSelectJsonObj(t *testing.T) {
|
|||
WHERE(Actor.ActorID.EQ(Int(2)))
|
||||
|
||||
testutils.AssertStatementSql(t, stmt, `
|
||||
SELECT JSON_OBJECT('actorID', actor.actor_id,
|
||||
'firstName', actor.first_name,
|
||||
'lastName', actor.last_name,
|
||||
'lastUpdate', actor.last_update) AS "json"
|
||||
SELECT JSON_OBJECT(
|
||||
'actorID', actor.actor_id,
|
||||
'firstName', actor.first_name,
|
||||
'lastName', actor.last_name,
|
||||
'lastUpdate', DATE_FORMAT(actor.last_update,'%Y-%m-%dT%H:%i:%s.%fZ')
|
||||
) AS "json"
|
||||
FROM dvds.actor
|
||||
WHERE actor.actor_id = ?;
|
||||
`, int64(2))
|
||||
|
|
@ -57,30 +59,34 @@ func TestSelectJsonObj_NestedObj(t *testing.T) {
|
|||
)
|
||||
|
||||
testutils.AssertStatementSql(t, stmt, `
|
||||
SELECT JSON_OBJECT('actorID', actor.actor_id,
|
||||
'firstName', actor.first_name,
|
||||
'lastName', actor.last_name,
|
||||
'lastUpdate', actor.last_update,
|
||||
'LongestFilm', (
|
||||
SELECT JSON_OBJECT('filmID', film.film_id,
|
||||
'title', film.title,
|
||||
'description', film.description,
|
||||
'releaseYear', film.release_year,
|
||||
'languageID', film.language_id,
|
||||
'originalLanguageID', film.original_language_id,
|
||||
'rentalDuration', film.rental_duration,
|
||||
'rentalRate', film.rental_rate,
|
||||
'length', film.length,
|
||||
'replacementCost', film.replacement_cost,
|
||||
'rating', film.rating,
|
||||
'specialFeatures', film.special_features,
|
||||
'lastUpdate', film.last_update) AS "json"
|
||||
FROM dvds.film_actor
|
||||
INNER JOIN dvds.film ON (film.film_id = film_actor.film_id)
|
||||
WHERE actor.actor_id = film_actor.actor_id
|
||||
ORDER BY film.length DESC
|
||||
LIMIT ?
|
||||
)) AS "json"
|
||||
SELECT JSON_OBJECT(
|
||||
'actorID', actor.actor_id,
|
||||
'firstName', actor.first_name,
|
||||
'lastName', actor.last_name,
|
||||
'lastUpdate', DATE_FORMAT(actor.last_update,'%Y-%m-%dT%H:%i:%s.%fZ'),
|
||||
'LongestFilm', (
|
||||
SELECT JSON_OBJECT(
|
||||
'filmID', film.film_id,
|
||||
'title', film.title,
|
||||
'description', film.description,
|
||||
'releaseYear', film.release_year,
|
||||
'languageID', film.language_id,
|
||||
'originalLanguageID', film.original_language_id,
|
||||
'rentalDuration', film.rental_duration,
|
||||
'rentalRate', film.rental_rate,
|
||||
'length', film.length,
|
||||
'replacementCost', film.replacement_cost,
|
||||
'rating', film.rating,
|
||||
'specialFeatures', film.special_features,
|
||||
'lastUpdate', DATE_FORMAT(film.last_update,'%Y-%m-%dT%H:%i:%s.%fZ')
|
||||
) AS "json"
|
||||
FROM dvds.film_actor
|
||||
INNER JOIN dvds.film ON (film.film_id = film_actor.film_id)
|
||||
WHERE actor.actor_id = film_actor.actor_id
|
||||
ORDER BY film.length DESC
|
||||
LIMIT ?
|
||||
)
|
||||
) AS "json"
|
||||
FROM dvds.actor
|
||||
WHERE actor.actor_id = ?;
|
||||
`)
|
||||
|
|
@ -125,10 +131,12 @@ func TestSelectJsonArr(t *testing.T) {
|
|||
ORDER_BY(Actor.ActorID)
|
||||
|
||||
testutils.AssertDebugStatementSql(t, stmt, `
|
||||
SELECT JSON_ARRAYAGG(JSON_OBJECT('actorID', actor.actor_id,
|
||||
'firstName', actor.first_name,
|
||||
'lastName', actor.last_name,
|
||||
'lastUpdate', actor.last_update)) AS "json"
|
||||
SELECT JSON_ARRAYAGG(JSON_OBJECT(
|
||||
'actorID', actor.actor_id,
|
||||
'firstName', actor.first_name,
|
||||
'lastName', actor.last_name,
|
||||
'lastUpdate', DATE_FORMAT(actor.last_update,'%Y-%m-%dT%H:%i:%s.%fZ')
|
||||
)) AS "json"
|
||||
FROM dvds.actor
|
||||
ORDER BY actor.actor_id;
|
||||
`)
|
||||
|
|
@ -169,29 +177,33 @@ func TestSelectJsonArr_NestedArr(t *testing.T) {
|
|||
)
|
||||
|
||||
testutils.AssertDebugStatementSql(t, stmt, `
|
||||
SELECT JSON_ARRAYAGG(JSON_OBJECT('actorID', actor.actor_id,
|
||||
'firstName', actor.first_name,
|
||||
'lastName', actor.last_name,
|
||||
'lastUpdate', actor.last_update,
|
||||
'Films', (
|
||||
SELECT JSON_ARRAYAGG(JSON_OBJECT('filmID', film.film_id,
|
||||
'title', film.title,
|
||||
'description', film.description,
|
||||
'releaseYear', film.release_year,
|
||||
'languageID', film.language_id,
|
||||
'originalLanguageID', film.original_language_id,
|
||||
'rentalDuration', film.rental_duration,
|
||||
'rentalRate', film.rental_rate,
|
||||
'length', film.length,
|
||||
'replacementCost', film.replacement_cost,
|
||||
'rating', film.rating,
|
||||
'specialFeatures', film.special_features,
|
||||
'lastUpdate', film.last_update)) AS "json"
|
||||
FROM dvds.film_actor
|
||||
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
|
||||
ORDER BY film.length DESC
|
||||
))) AS "json"
|
||||
SELECT JSON_ARRAYAGG(JSON_OBJECT(
|
||||
'actorID', actor.actor_id,
|
||||
'firstName', actor.first_name,
|
||||
'lastName', actor.last_name,
|
||||
'lastUpdate', DATE_FORMAT(actor.last_update,'%Y-%m-%dT%H:%i:%s.%fZ'),
|
||||
'Films', (
|
||||
SELECT JSON_ARRAYAGG(JSON_OBJECT(
|
||||
'filmID', film.film_id,
|
||||
'title', film.title,
|
||||
'description', film.description,
|
||||
'releaseYear', film.release_year,
|
||||
'languageID', film.language_id,
|
||||
'originalLanguageID', film.original_language_id,
|
||||
'rentalDuration', film.rental_duration,
|
||||
'rentalRate', film.rental_rate,
|
||||
'length', film.length,
|
||||
'replacementCost', film.replacement_cost,
|
||||
'rating', film.rating,
|
||||
'specialFeatures', film.special_features,
|
||||
'lastUpdate', DATE_FORMAT(film.last_update,'%Y-%m-%dT%H:%i:%s.%fZ')
|
||||
)) AS "json"
|
||||
FROM dvds.film_actor
|
||||
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
|
||||
ORDER BY film.length DESC
|
||||
)
|
||||
)) AS "json"
|
||||
FROM dvds.actor
|
||||
WHERE actor.actor_id BETWEEN 1 AND 3
|
||||
ORDER BY actor.actor_id;
|
||||
|
|
@ -337,22 +349,26 @@ func TestSelectJson_GroupBy(t *testing.T) {
|
|||
).FROM(subQuery)
|
||||
|
||||
testutils.AssertDebugStatementSql(t, stmt, strings.ReplaceAll(`
|
||||
SELECT JSON_ARRAYAGG(JSON_OBJECT('customerID', customers_info.""customer.customer_id"",
|
||||
'storeID', customers_info.""customer.store_id"",
|
||||
'firstName', customers_info.""customer.first_name"",
|
||||
'lastName', customers_info.""customer.last_name"",
|
||||
'email', customers_info.""customer.email"",
|
||||
'addressID', customers_info.""customer.address_id"",
|
||||
'active', customers_info.""customer.active"",
|
||||
'createDate', customers_info.""customer.create_date"",
|
||||
'lastUpdate', customers_info.""customer.last_update"",
|
||||
'amount', (
|
||||
SELECT JSON_OBJECT('sum', customers_info.sum,
|
||||
'avg', customers_info.avg,
|
||||
'max', customers_info.max,
|
||||
'min', customers_info.min,
|
||||
'count', customers_info.count) AS "json"
|
||||
))) AS "json"
|
||||
SELECT JSON_ARRAYAGG(JSON_OBJECT(
|
||||
'customerID', customers_info.''customer.customer_id'',
|
||||
'storeID', customers_info.''customer.store_id'',
|
||||
'firstName', customers_info.''customer.first_name'',
|
||||
'lastName', customers_info.''customer.last_name'',
|
||||
'email', customers_info.''customer.email'',
|
||||
'addressID', customers_info.''customer.address_id'',
|
||||
'active', customers_info.''customer.active'' = 1,
|
||||
'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', (
|
||||
SELECT JSON_OBJECT(
|
||||
'sum', customers_info.sum,
|
||||
'avg', customers_info.avg,
|
||||
'max', customers_info.max,
|
||||
'min', customers_info.min,
|
||||
'count', customers_info.count
|
||||
) AS "json"
|
||||
)
|
||||
)) AS "json"
|
||||
FROM (
|
||||
SELECT customer.customer_id AS "customer.customer_id",
|
||||
customer.store_id AS "customer.store_id",
|
||||
|
|
@ -374,7 +390,7 @@ FROM (
|
|||
HAVING SUM(payment.amount) > 125
|
||||
ORDER BY customer.customer_id, SUM(payment.amount) ASC
|
||||
) AS customers_info;
|
||||
`, `""`, "`"))
|
||||
`, "''", "`"))
|
||||
|
||||
var dest []struct {
|
||||
model.Customer
|
||||
|
|
@ -389,7 +405,6 @@ FROM (
|
|||
}
|
||||
|
||||
err := stmt.QueryJSON(ctx, db, &dest)
|
||||
fmt.Println(err)
|
||||
require.Nil(t, err)
|
||||
|
||||
testutils.AssertJSONFile(t, dest, "./testdata/results/mysql/customer_payment_sum.json")
|
||||
|
|
|
|||
|
|
@ -2,10 +2,10 @@ package postgres
|
|||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"github.com/go-jet/jet/v2/internal/utils/ptr"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/go-jet/jet/v2/qrm"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
|
|
@ -46,6 +46,7 @@ func TestAllTypesSelectJson(t *testing.T) {
|
|||
AllTypes.JsonbArray, AllTypes.IntegerArray, AllTypes.IntegerArrayPtr,
|
||||
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.JSON).AS_TEXT().AS("JSON"),
|
||||
CAST(AllTypes.JsonbPtr).AS_TEXT().AS("jsonbPtr"),
|
||||
|
|
@ -59,7 +60,75 @@ func TestAllTypesSelectJson(t *testing.T) {
|
|||
CAST(AllTypes.TextMultiDimArrayPtr).AS_TEXT().AS("TextMultiDimArrayPtr"),
|
||||
).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
|
||||
|
||||
|
|
@ -76,24 +145,30 @@ func TestAllTypesSelectJson(t *testing.T) {
|
|||
dest[1].CharPtr = allTypesRow1.CharPtr
|
||||
}
|
||||
|
||||
// set time local before comparison
|
||||
dest[0].Timestampz = toCET(t, dest[0].Timestampz)
|
||||
minus8 := time.FixedZone("UTC", -8*60*60)
|
||||
plus1 := time.FixedZone("UTC", 60*60)
|
||||
|
||||
if dest[0].TimestampzPtr != nil {
|
||||
dest[0].TimestampzPtr = ptr.Of(toCET(t, *dest[0].TimestampzPtr))
|
||||
}
|
||||
dest[1].Timestampz = toCET(t, dest[1].Timestampz)
|
||||
// set time local before comparison
|
||||
dest[0].Timez = *toTZ(&dest[0].Timez, minus8)
|
||||
dest[0].TimezPtr = toTZ(dest[0].TimezPtr, minus8)
|
||||
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[1], allTypesRow1)
|
||||
}
|
||||
|
||||
func toCET(t *testing.T, tm time.Time) time.Time {
|
||||
cet, err := time.LoadLocation("CET") // "Europe/Berlin" also works
|
||||
if err != nil {
|
||||
t.Fail()
|
||||
func toTZ(tm *time.Time, loc *time.Location) *time.Time {
|
||||
if tm == nil {
|
||||
return nil
|
||||
}
|
||||
return tm.In(cet)
|
||||
|
||||
return ptr.Of(tm.In(loc))
|
||||
}
|
||||
|
||||
func TestAllTypesViewSelect(t *testing.T) {
|
||||
|
|
@ -192,7 +267,7 @@ WHERE all_types.uuid = 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11';
|
|||
requireLogged(t, query)
|
||||
}
|
||||
|
||||
func TestBytea(t *testing.T) {
|
||||
func TestByteaInsert(t *testing.T) {
|
||||
byteArrHex := "\\x48656c6c6f20476f7068657221"
|
||||
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)
|
||||
}
|
||||
|
||||
func TestBlob(t *testing.T) {
|
||||
func TestBytea(t *testing.T) {
|
||||
|
||||
var sampleBlob = Bytea([]byte{11, 0, 22, 33, 44})
|
||||
var textBlob = Bytea([]byte("text blob"))
|
||||
var sampleBytea = Bytea([]byte{11, 0, 22, 33, 44})
|
||||
var textBytea = Bytea([]byte("text blob"))
|
||||
|
||||
stmt := SELECT(
|
||||
AllTypes.Bytea.EQ(sampleBlob),
|
||||
AllTypes.Bytea.EQ(sampleBytea),
|
||||
AllTypes.Bytea.EQ(AllTypes.ByteaPtr),
|
||||
AllTypes.Bytea.NOT_EQ(sampleBlob),
|
||||
AllTypes.Bytea.GT(textBlob),
|
||||
AllTypes.Bytea.NOT_EQ(sampleBytea),
|
||||
AllTypes.Bytea.GT(textBytea),
|
||||
AllTypes.Bytea.GT_EQ(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.NOT_BETWEEN(AllTypes.Bytea, AllTypes.ByteaPtr),
|
||||
AllTypes.Bytea.CONCAT(textBlob),
|
||||
AllTypes.Bytea.CONCAT(textBytea),
|
||||
|
||||
func() ProjectionList {
|
||||
if sourceIsCockroachDB() {
|
||||
|
|
@ -629,25 +704,25 @@ func TestBlob(t *testing.T) {
|
|||
AllTypes.Bytea.NOT_LIKE(Bytea("b'%pattern%'")),
|
||||
|
||||
BTRIM(AllTypes.Bytea, Bytea([]byte{33})),
|
||||
RTRIM(AllTypes.ByteaPtr, sampleBlob),
|
||||
LTRIM(sampleBlob, textBlob),
|
||||
CONCAT(sampleBlob, AllTypes.ByteaPtr, textBlob),
|
||||
BIT_COUNT(sampleBlob).EQ(Int(3)),
|
||||
LENGTH(textBlob, UTF8).EQ(Int(4)),
|
||||
RTRIM(AllTypes.ByteaPtr, sampleBytea),
|
||||
LTRIM(sampleBytea, textBytea),
|
||||
CONCAT(sampleBytea, AllTypes.ByteaPtr, textBytea),
|
||||
BIT_COUNT(sampleBytea).EQ(Int(3)),
|
||||
LENGTH(textBytea, UTF8).EQ(Int(4)),
|
||||
|
||||
CONVERT(textBlob, UTF8, WIN1252),
|
||||
CONVERT(AllTypes.Bytea, UTF8, LATIN1).EQ(sampleBlob),
|
||||
CONVERT(textBytea, UTF8, WIN1252),
|
||||
CONVERT(AllTypes.Bytea, UTF8, LATIN1).EQ(sampleBytea),
|
||||
}
|
||||
}(),
|
||||
|
||||
BIT_LENGTH(textBlob),
|
||||
OCTET_LENGTH(textBlob),
|
||||
BIT_LENGTH(textBytea),
|
||||
OCTET_LENGTH(textBytea),
|
||||
|
||||
GET_BIT(textBlob, Int(2)).EQ(Int(23)),
|
||||
GET_BYTE(sampleBlob, Int(1)).EQ(Int(0)),
|
||||
SET_BIT(textBlob, Int(1), Int(0)).EQ(sampleBlob),
|
||||
SET_BYTE(textBlob, Int(1), Int(0)).EQ(textBlob),
|
||||
LENGTH(sampleBlob),
|
||||
GET_BIT(textBytea, Int(2)).EQ(Int(23)),
|
||||
GET_BYTE(sampleBytea, Int(1)).EQ(Int(0)),
|
||||
SET_BIT(textBytea, Int(1), Int(0)).EQ(sampleBytea),
|
||||
SET_BYTE(textBytea, Int(1), Int(0)).EQ(textBytea),
|
||||
LENGTH(sampleBytea),
|
||||
|
||||
SUBSTR(AllTypes.Bytea, Int(0), Int(2)),
|
||||
|
||||
|
|
@ -657,16 +732,16 @@ func TestBlob(t *testing.T) {
|
|||
SHA384(AllTypes.Bytea),
|
||||
SHA512(AllTypes.Bytea),
|
||||
|
||||
ENCODE(sampleBlob, Base64),
|
||||
DECODE(String("A234C12B"), Hex).EQ(sampleBlob),
|
||||
ENCODE(sampleBytea, Base64),
|
||||
DECODE(String("A234C12B"), Hex).EQ(sampleBytea),
|
||||
|
||||
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{
|
||||
"#1": "A234C12B",
|
||||
"#2": "hex",
|
||||
}).EQ(sampleBlob),
|
||||
}).EQ(sampleBytea),
|
||||
).FROM(
|
||||
AllTypes,
|
||||
)
|
||||
|
|
@ -690,27 +765,27 @@ SELECT all_types.bytea = $1::bytea,
|
|||
LTRIM($12::bytea, $13::bytea),
|
||||
CONCAT($14::bytea, all_types.bytea_ptr, $15::bytea),
|
||||
BIT_COUNT($16::bytea) = $17,
|
||||
LENGTH($18::bytea, $19::text) = $20,
|
||||
CONVERT($21::bytea, $22::text, $23::text),
|
||||
CONVERT(all_types.bytea, $24::text, $25::text) = $26::bytea,
|
||||
BIT_LENGTH($27::bytea),
|
||||
OCTET_LENGTH($28::bytea),
|
||||
GET_BIT($29::bytea, $30) = $31,
|
||||
GET_BYTE($32::bytea, $33) = $34,
|
||||
SET_BIT($35::bytea, $36, $37) = $38::bytea,
|
||||
SET_BYTE($39::bytea, $40, $41) = $42::bytea,
|
||||
LENGTH($43::bytea),
|
||||
SUBSTR(all_types.bytea, $44, $45),
|
||||
LENGTH($18::bytea, 'UTF8') = $19,
|
||||
CONVERT($20::bytea, 'UTF8', 'WIN1252'),
|
||||
CONVERT(all_types.bytea, 'UTF8', 'LATIN1') = $21::bytea,
|
||||
BIT_LENGTH($22::bytea),
|
||||
OCTET_LENGTH($23::bytea),
|
||||
GET_BIT($24::bytea, $25) = $26,
|
||||
GET_BYTE($27::bytea, $28) = $29,
|
||||
SET_BIT($30::bytea, $31, $32) = $33::bytea,
|
||||
SET_BYTE($34::bytea, $35, $36) = $37::bytea,
|
||||
LENGTH($38::bytea),
|
||||
SUBSTR(all_types.bytea, $39, $40),
|
||||
MD5(all_types.bytea),
|
||||
SHA224(all_types.bytea),
|
||||
SHA256(all_types.bytea),
|
||||
SHA384(all_types.bytea),
|
||||
SHA512(all_types.bytea),
|
||||
ENCODE($46::bytea, $47::text),
|
||||
DECODE($48::text, $49::text) = $50::bytea,
|
||||
CONVERT_FROM(all_types.bytea_ptr, $51::text) = all_types.var_char,
|
||||
CONVERT_TO(all_types.text, $52::text) != $53::bytea,
|
||||
(DECODE($54::text, $55)) = $56::bytea
|
||||
ENCODE($41::bytea, 'base64'),
|
||||
DECODE($42::text, 'hex') = $43::bytea,
|
||||
CONVERT_FROM(all_types.bytea_ptr, 'UTF8') = all_types.var_char,
|
||||
CONVERT_TO(all_types.text, 'UTF8') != $44::bytea,
|
||||
(DECODE($45::text, $46)) = $47::bytea
|
||||
FROM test_sample.all_types;
|
||||
`)
|
||||
}
|
||||
|
|
@ -727,28 +802,75 @@ func TestBlobConversion(t *testing.T) {
|
|||
printable := []byte("this is blob")
|
||||
|
||||
stmt := SELECT(
|
||||
Bytea(nonPrintable).AS("non_printable"),
|
||||
Bytea(printable).AS("printable"),
|
||||
Bytea(nonPrintable).AS("test_dest.non_printable"),
|
||||
Bytea(printable).AS("test_dest.printable"),
|
||||
|
||||
ENCODE(Bytea(nonPrintable), Base64).AS("non_printable_base64"),
|
||||
CONVERT_FROM(Bytea(printable), UTF8).AS("printable_utf8"),
|
||||
Bytea(nonPrintable).CONCAT(Bytea(printable)).AS("test_dest.bytea_concat"),
|
||||
|
||||
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
|
||||
Printable []byte
|
||||
|
||||
NonPrintableBase64 []byte
|
||||
ByteaConcat []byte
|
||||
NonPrintableBase64 string
|
||||
PrintableUTF8 string
|
||||
}
|
||||
|
||||
var dest testDest
|
||||
|
||||
err := stmt.Query(db, &dest)
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, dest.NonPrintable, nonPrintable)
|
||||
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))
|
||||
|
||||
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) {
|
||||
|
|
@ -1140,6 +1262,190 @@ func TestTimeExpression(t *testing.T) {
|
|||
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) {
|
||||
|
||||
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),
|
||||
).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{}{})
|
||||
require.NoError(t, err)
|
||||
|
|
@ -1368,6 +1717,7 @@ func TestAllTypesSubQueryFrom(t *testing.T) {
|
|||
AllTypes.Time,
|
||||
AllTypes.Timez,
|
||||
AllTypes.Timestamp,
|
||||
AllTypes.Timestampz,
|
||||
AllTypes.Interval,
|
||||
AllTypes.Bytea,
|
||||
).FROM(
|
||||
|
|
@ -1383,6 +1733,7 @@ func TestAllTypesSubQueryFrom(t *testing.T) {
|
|||
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(
|
||||
|
|
@ -1398,6 +1749,7 @@ SELECT "subQuery"."all_types.boolean" AS "all_types.boolean",
|
|||
"subQuery"."all_types.time" AS "all_types.time",
|
||||
"subQuery"."all_types.timez" AS "all_types.timez",
|
||||
"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.bytea" AS "all_types.bytea"
|
||||
FROM (
|
||||
|
|
@ -1409,6 +1761,7 @@ FROM (
|
|||
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
|
||||
|
|
@ -1419,6 +1772,83 @@ FROM (
|
|||
|
||||
err := stmt.Query(db, &dest)
|
||||
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) {
|
||||
|
|
|
|||
|
|
@ -251,9 +251,8 @@ func testJoinEverythingJSON(t require.TestingT) {
|
|||
WHERE(MediaType.MediaTypeId.EQ(Track.MediaTypeId)).AS("MediaType"),
|
||||
|
||||
SELECT_JSON_ARR(Playlist.AllColumns).
|
||||
FROM(Playlist.INNER_JOIN(
|
||||
PlaylistTrack,
|
||||
Playlist.PlaylistId.EQ(PlaylistTrack.PlaylistId))).
|
||||
FROM(Playlist.
|
||||
INNER_JOIN(PlaylistTrack, Playlist.PlaylistId.EQ(PlaylistTrack.PlaylistId))).
|
||||
WHERE(PlaylistTrack.TrackId.EQ(Track.TrackId)).
|
||||
ORDER_BY(Playlist.PlaylistId).AS("Playlists"),
|
||||
|
||||
|
|
@ -273,9 +272,9 @@ func testJoinEverythingJSON(t require.TestingT) {
|
|||
WHERE(Employee.EmployeeId.EQ(Customer.SupportRepId)).AS("Employee"),
|
||||
).FROM(Customer).
|
||||
WHERE(Customer.CustomerId.EQ(Invoice.CustomerId)).AS("Customer"),
|
||||
).FROM(Invoice.INNER_JOIN(
|
||||
InvoiceLine,
|
||||
InvoiceLine.InvoiceId.EQ(Invoice.InvoiceId)),
|
||||
).FROM(
|
||||
Invoice.
|
||||
INNER_JOIN(InvoiceLine, InvoiceLine.InvoiceId.EQ(Invoice.InvoiceId)),
|
||||
).WHERE(InvoiceLine.TrackId.EQ(Track.TrackId)).
|
||||
ORDER_BY(Invoice.InvoiceId).AS("Invoices"),
|
||||
).FROM(Track).
|
||||
|
|
|
|||
|
|
@ -20,6 +20,8 @@ import (
|
|||
_ "github.com/jackc/pgx/v4/stdlib"
|
||||
)
|
||||
|
||||
var ctx = context.Background()
|
||||
|
||||
var db *stmtcache.DB
|
||||
var testRoot string
|
||||
|
||||
|
|
|
|||
|
|
@ -139,7 +139,7 @@ func testNorthwindJoinEverythingJson(t require.TestingT) {
|
|||
Territories,
|
||||
EmployeeTerritories.TerritoryID.EQ(Territories.TerritoryID)),
|
||||
).WHERE(
|
||||
EmployeeTerritories.EmployeeID.EQ(Employees.EmployeeID), // TODO: move to join
|
||||
EmployeeTerritories.EmployeeID.EQ(Employees.EmployeeID),
|
||||
).AS("Territories"),
|
||||
).FROM(Employees).
|
||||
WHERE(Orders.EmployeeID.EQ(Employees.EmployeeID)).AS("Employee"),
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
Loading…
Add table
Add a link
Reference in a new issue