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.
|
// Generated columns have default aliasing.
|
||||||
tableName, columnName := extractTableAndColumnName(a.alias)
|
tableName, columnName := extractTableAndColumnName(a.alias)
|
||||||
|
|
||||||
column := NewColumnImpl(columnName, tableName, nil)
|
newDummyColumn := newDummyColumnForExpression(a.expression, columnName)
|
||||||
column.subQuery = subQuery
|
newDummyColumn.setTableName(tableName)
|
||||||
|
newDummyColumn.setSubQuery(subQuery)
|
||||||
|
|
||||||
return &column
|
return newDummyColumn
|
||||||
|
}
|
||||||
|
|
||||||
|
// This function is used to create dummy columns when exporting sub-query columns using subQuery.AllColumns()
|
||||||
|
// In most case we don't care about type of the column, except when sub-query columns are used as SELECT_JSON projection.
|
||||||
|
// We need to know type to encode value for json unmarshal. At the moment only bool, time and blob columns are of interest,
|
||||||
|
// so we don't have to support every column type.
|
||||||
|
func newDummyColumnForExpression(exp Expression, name string) ColumnExpression {
|
||||||
|
|
||||||
|
switch exp.(type) {
|
||||||
|
case BoolExpression:
|
||||||
|
return BoolColumn(name)
|
||||||
|
case IntegerExpression:
|
||||||
|
return IntegerColumn(name)
|
||||||
|
case FloatExpression:
|
||||||
|
return FloatColumn(name)
|
||||||
|
case BlobExpression:
|
||||||
|
return BlobColumn(name)
|
||||||
|
case DateExpression:
|
||||||
|
return DateColumn(name)
|
||||||
|
case TimeExpression:
|
||||||
|
return TimeColumn(name)
|
||||||
|
case TimezExpression:
|
||||||
|
return TimezColumn(name)
|
||||||
|
case TimestampExpression:
|
||||||
|
return TimestampColumn(name)
|
||||||
|
case TimestampzExpression:
|
||||||
|
return TimestampzColumn(name)
|
||||||
|
case IntervalExpression:
|
||||||
|
return IntervalColumn(name)
|
||||||
|
case StringExpression:
|
||||||
|
return StringColumn(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
return StringColumn(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *alias) serializeForProjection(statement StatementType, out *SQLBuilder) {
|
func (a *alias) serializeForProjection(statement StatementType, out *SQLBuilder) {
|
||||||
|
|
@ -31,7 +66,14 @@ func (a *alias) serializeForProjection(statement StatementType, out *SQLBuilder)
|
||||||
out.WriteAlias(a.alias)
|
out.WriteAlias(a.alias)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *alias) serializeForJsonObj(statement StatementType, out *SQLBuilder) {
|
func (a *alias) serializeForJsonObjEntry(statement StatementType, out *SQLBuilder) {
|
||||||
out.WriteJsonObjKey(a.alias)
|
out.WriteJsonObjKey(a.alias)
|
||||||
a.expression.serialize(statement, out)
|
a.expression.serializeForJsonValue(statement, out)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *alias) serializeForRowToJsonProjection(statement StatementType, out *SQLBuilder) {
|
||||||
|
a.expression.serializeForJsonValue(statement, out)
|
||||||
|
|
||||||
|
out.WriteString("AS")
|
||||||
|
out.WriteAlias(a.alias)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,71 +28,72 @@ type blobInterfaceImpl struct {
|
||||||
parent BlobExpression
|
parent BlobExpression
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *blobInterfaceImpl) isStringOrBlob() {}
|
func (b *blobInterfaceImpl) isStringOrBlob() {}
|
||||||
|
|
||||||
func (s *blobInterfaceImpl) EQ(rhs BlobExpression) BoolExpression {
|
func (b *blobInterfaceImpl) EQ(rhs BlobExpression) BoolExpression {
|
||||||
return Eq(s.parent, rhs)
|
return Eq(b.parent, rhs)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *blobInterfaceImpl) NOT_EQ(rhs BlobExpression) BoolExpression {
|
func (b *blobInterfaceImpl) NOT_EQ(rhs BlobExpression) BoolExpression {
|
||||||
return NotEq(s.parent, rhs)
|
return NotEq(b.parent, rhs)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *blobInterfaceImpl) IS_DISTINCT_FROM(rhs BlobExpression) BoolExpression {
|
func (b *blobInterfaceImpl) IS_DISTINCT_FROM(rhs BlobExpression) BoolExpression {
|
||||||
return IsDistinctFrom(s.parent, rhs)
|
return IsDistinctFrom(b.parent, rhs)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *blobInterfaceImpl) IS_NOT_DISTINCT_FROM(rhs BlobExpression) BoolExpression {
|
func (b *blobInterfaceImpl) IS_NOT_DISTINCT_FROM(rhs BlobExpression) BoolExpression {
|
||||||
return IsNotDistinctFrom(s.parent, rhs)
|
return IsNotDistinctFrom(b.parent, rhs)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *blobInterfaceImpl) GT(rhs BlobExpression) BoolExpression {
|
func (b *blobInterfaceImpl) GT(rhs BlobExpression) BoolExpression {
|
||||||
return Gt(s.parent, rhs)
|
return Gt(b.parent, rhs)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *blobInterfaceImpl) GT_EQ(rhs BlobExpression) BoolExpression {
|
func (b *blobInterfaceImpl) GT_EQ(rhs BlobExpression) BoolExpression {
|
||||||
return GtEq(s.parent, rhs)
|
return GtEq(b.parent, rhs)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *blobInterfaceImpl) LT(rhs BlobExpression) BoolExpression {
|
func (b *blobInterfaceImpl) LT(rhs BlobExpression) BoolExpression {
|
||||||
return Lt(s.parent, rhs)
|
return Lt(b.parent, rhs)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *blobInterfaceImpl) LT_EQ(rhs BlobExpression) BoolExpression {
|
func (b *blobInterfaceImpl) LT_EQ(rhs BlobExpression) BoolExpression {
|
||||||
return LtEq(s.parent, rhs)
|
return LtEq(b.parent, rhs)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *blobInterfaceImpl) BETWEEN(min, max BlobExpression) BoolExpression {
|
func (b *blobInterfaceImpl) BETWEEN(min, max BlobExpression) BoolExpression {
|
||||||
return NewBetweenOperatorExpression(s.parent, min, max, false)
|
return NewBetweenOperatorExpression(b.parent, min, max, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *blobInterfaceImpl) NOT_BETWEEN(min, max BlobExpression) BoolExpression {
|
func (b *blobInterfaceImpl) NOT_BETWEEN(min, max BlobExpression) BoolExpression {
|
||||||
return NewBetweenOperatorExpression(s.parent, min, max, true)
|
return NewBetweenOperatorExpression(b.parent, min, max, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *blobInterfaceImpl) CONCAT(rhs BlobExpression) BlobExpression {
|
func (b *blobInterfaceImpl) CONCAT(rhs BlobExpression) BlobExpression {
|
||||||
return BlobExp(newBinaryStringOperatorExpression(s.parent, rhs, StringConcatOperator))
|
return BlobExp(newBinaryStringOperatorExpression(b.parent, rhs, StringConcatOperator))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *blobInterfaceImpl) LIKE(pattern BlobExpression) BoolExpression {
|
func (b *blobInterfaceImpl) LIKE(pattern BlobExpression) BoolExpression {
|
||||||
return newBinaryBoolOperatorExpression(s.parent, pattern, "LIKE")
|
return newBinaryBoolOperatorExpression(b.parent, pattern, "LIKE")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *blobInterfaceImpl) NOT_LIKE(pattern BlobExpression) BoolExpression {
|
func (b *blobInterfaceImpl) NOT_LIKE(pattern BlobExpression) BoolExpression {
|
||||||
return newBinaryBoolOperatorExpression(s.parent, pattern, "NOT LIKE")
|
return newBinaryBoolOperatorExpression(b.parent, pattern, "NOT LIKE")
|
||||||
}
|
}
|
||||||
|
|
||||||
//---------------------------------------------------//
|
//---------------------------------------------------//
|
||||||
|
|
||||||
type blobExpressionWrapper struct {
|
type blobExpressionWrapper struct {
|
||||||
blobInterfaceImpl
|
|
||||||
Expression
|
Expression
|
||||||
|
blobInterfaceImpl
|
||||||
}
|
}
|
||||||
|
|
||||||
func newBlobExpressionWrap(expression Expression) BlobExpression {
|
func newBlobExpressionWrap(expression Expression) BlobExpression {
|
||||||
blobExpressionWrap := blobExpressionWrapper{Expression: expression}
|
blobExpressionWrap := &blobExpressionWrapper{Expression: expression}
|
||||||
blobExpressionWrap.blobInterfaceImpl.parent = &blobExpressionWrap
|
blobExpressionWrap.blobInterfaceImpl.parent = blobExpressionWrap
|
||||||
return &blobExpressionWrap
|
expression.setParent(blobExpressionWrap)
|
||||||
|
return blobExpressionWrap
|
||||||
}
|
}
|
||||||
|
|
||||||
// BlobExp is blob expression wrapper around arbitrary expression.
|
// BlobExp is blob expression wrapper around arbitrary expression.
|
||||||
|
|
|
||||||
|
|
@ -102,9 +102,10 @@ type boolExpressionWrapper struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func newBoolExpressionWrap(expression Expression) BoolExpression {
|
func newBoolExpressionWrap(expression Expression) BoolExpression {
|
||||||
boolExpressionWrap := boolExpressionWrapper{Expression: expression}
|
boolExpressionWrap := &boolExpressionWrapper{Expression: expression}
|
||||||
boolExpressionWrap.boolInterfaceImpl.parent = &boolExpressionWrap
|
boolExpressionWrap.boolInterfaceImpl.parent = boolExpressionWrap
|
||||||
return &boolExpressionWrap
|
expression.setParent(boolExpressionWrap)
|
||||||
|
return boolExpressionWrap
|
||||||
}
|
}
|
||||||
|
|
||||||
// BoolExp is bool expression wrapper around arbitrary expression.
|
// BoolExp is bool expression wrapper around arbitrary expression.
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,8 @@ type ClauseSelect struct {
|
||||||
DistinctOnColumns []ColumnExpression
|
DistinctOnColumns []ColumnExpression
|
||||||
ProjectionList []Projection
|
ProjectionList []Projection
|
||||||
|
|
||||||
|
IsForRowToJson bool
|
||||||
|
|
||||||
// MySQL only
|
// MySQL only
|
||||||
OptimizerHints optimizerHints
|
OptimizerHints optimizerHints
|
||||||
}
|
}
|
||||||
|
|
@ -70,7 +72,13 @@ func (s *ClauseSelect) Serialize(statementType StatementType, out *SQLBuilder, o
|
||||||
out.WriteByte(')')
|
out.WriteByte(')')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if s.IsForRowToJson {
|
||||||
|
out.IncreaseIdent()
|
||||||
|
out.WriteRowToJsonProjections(statementType, s.ProjectionList)
|
||||||
|
out.DecreaseIdent()
|
||||||
|
} else {
|
||||||
out.WriteProjections(statementType, s.ProjectionList)
|
out.WriteProjections(statementType, s.ProjectionList)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ClauseFrom struct
|
// ClauseFrom struct
|
||||||
|
|
|
||||||
|
|
@ -39,19 +39,19 @@ type ColumnExpressionImpl struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewColumnImpl creates new ColumnExpressionImpl
|
// NewColumnImpl creates new ColumnExpressionImpl
|
||||||
func NewColumnImpl(name string, tableName string, parent ColumnExpression) ColumnExpressionImpl {
|
func NewColumnImpl(name string, tableName string, parent ColumnExpression) *ColumnExpressionImpl {
|
||||||
bc := ColumnExpressionImpl{
|
newColumn := &ColumnExpressionImpl{
|
||||||
name: name,
|
name: name,
|
||||||
tableName: tableName,
|
tableName: tableName,
|
||||||
}
|
}
|
||||||
|
|
||||||
if parent != nil {
|
if parent != nil {
|
||||||
bc.ExpressionInterfaceImpl.Parent = parent
|
newColumn.ExpressionInterfaceImpl.Parent = parent
|
||||||
} else {
|
} else {
|
||||||
bc.ExpressionInterfaceImpl.Parent = &bc
|
newColumn.ExpressionInterfaceImpl.Parent = newColumn
|
||||||
}
|
}
|
||||||
|
|
||||||
return bc
|
return newColumn
|
||||||
}
|
}
|
||||||
|
|
||||||
// Name returns name of the column
|
// Name returns name of the column
|
||||||
|
|
@ -80,13 +80,6 @@ func (c *ColumnExpressionImpl) defaultAlias() string {
|
||||||
return c.name
|
return c.name
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ColumnExpressionImpl) fromImpl(subQuery SelectTable) Projection {
|
|
||||||
newColumn := NewColumnImpl(c.name, c.tableName, nil)
|
|
||||||
newColumn.setSubQuery(subQuery)
|
|
||||||
|
|
||||||
return &newColumn
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ColumnExpressionImpl) serializeForOrderBy(statement StatementType, out *SQLBuilder) {
|
func (c *ColumnExpressionImpl) serializeForOrderBy(statement StatementType, out *SQLBuilder) {
|
||||||
if statement == SetStatementType {
|
if statement == SetStatementType {
|
||||||
// set Statement (UNION, EXCEPT ...) can reference only select projections in order by clause
|
// set Statement (UNION, EXCEPT ...) can reference only select projections in order by clause
|
||||||
|
|
@ -97,24 +90,28 @@ func (c *ColumnExpressionImpl) serializeForOrderBy(statement StatementType, out
|
||||||
c.serialize(statement, out)
|
c.serialize(statement, out)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c ColumnExpressionImpl) serializeForProjection(statement StatementType, out *SQLBuilder) {
|
func (c *ColumnExpressionImpl) serializeForProjection(statement StatementType, out *SQLBuilder) {
|
||||||
c.serialize(statement, out)
|
c.serialize(statement, out)
|
||||||
|
|
||||||
out.WriteString("AS")
|
out.WriteString("AS")
|
||||||
|
|
||||||
if statement.IsSelectJSON() {
|
|
||||||
out.WriteAlias(snaker.SnakeToCamel(c.name, false))
|
|
||||||
} else {
|
|
||||||
out.WriteAlias(c.defaultAlias())
|
out.WriteAlias(c.defaultAlias())
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c ColumnExpressionImpl) serializeForJsonObj(statement StatementType, out *SQLBuilder) {
|
func (c *ColumnExpressionImpl) serializeForJsonObjEntry(statement StatementType, out *SQLBuilder) {
|
||||||
out.WriteJsonObjKey(snaker.SnakeToCamel(c.name, false))
|
out.WriteJsonObjKey(snaker.SnakeToCamel(c.name, false))
|
||||||
c.serialize(statement, out)
|
c.Parent.serializeForJsonValue(statement, out)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c ColumnExpressionImpl) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) {
|
func (c *ColumnExpressionImpl) serializeForRowToJsonProjection(statement StatementType, out *SQLBuilder) {
|
||||||
|
c.Parent.serializeForJsonValue(statement, out)
|
||||||
|
|
||||||
|
out.WriteString("AS")
|
||||||
|
|
||||||
|
out.WriteAlias(snaker.SnakeToCamel(c.name, false))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ColumnExpressionImpl) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) {
|
||||||
|
|
||||||
if c.subQuery != nil {
|
if c.subQuery != nil {
|
||||||
out.WriteIdentifier(c.subQuery.Alias())
|
out.WriteIdentifier(c.subQuery.Alias())
|
||||||
|
|
|
||||||
|
|
@ -78,12 +78,18 @@ func (cl ColumnList) serializeForProjection(statement StatementType, out *SQLBui
|
||||||
SerializeProjectionList(statement, projections, out)
|
SerializeProjectionList(statement, projections, out)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cl ColumnList) serializeForJsonObj(statement StatementType, out *SQLBuilder) {
|
func (cl ColumnList) serializeForJsonObjEntry(statement StatementType, out *SQLBuilder) {
|
||||||
projections := ColumnListToProjectionList(cl)
|
projections := ColumnListToProjectionList(cl)
|
||||||
|
|
||||||
SerializeProjectionListJsonObj(statement, projections, out)
|
SerializeProjectionListJsonObj(statement, projections, out)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (cl ColumnList) serializeForRowToJsonProjection(statement StatementType, out *SQLBuilder) {
|
||||||
|
projections := ColumnListToProjectionList(cl)
|
||||||
|
|
||||||
|
out.WriteRowToJsonProjections(statement, projections)
|
||||||
|
}
|
||||||
|
|
||||||
// dummy column interface implementation
|
// dummy column interface implementation
|
||||||
|
|
||||||
// Name is placeholder for ColumnList to implement Column interface
|
// Name is placeholder for ColumnList to implement Column interface
|
||||||
|
|
|
||||||
|
|
@ -4,11 +4,10 @@ import "testing"
|
||||||
|
|
||||||
func TestColumn(t *testing.T) {
|
func TestColumn(t *testing.T) {
|
||||||
column := NewColumnImpl("col", "", nil)
|
column := NewColumnImpl("col", "", nil)
|
||||||
column.ExpressionInterfaceImpl.Parent = &column
|
|
||||||
|
|
||||||
assertClauseSerialize(t, column, "col")
|
assertClauseSerialize(t, column, "col")
|
||||||
column.setTableName("table1")
|
column.setTableName("table1")
|
||||||
assertClauseSerialize(t, column, "table1.col")
|
assertClauseSerialize(t, column, "table1.col")
|
||||||
assertProjectionSerialize(t, &column, `table1.col AS "table1.col"`)
|
assertProjectionSerialize(t, column, `table1.col AS "table1.col"`)
|
||||||
assertProjectionSerialize(t, column.AS("alias1"), `table1.col AS "alias1"`)
|
assertProjectionSerialize(t, column.AS("alias1"), `table1.col AS "alias1"`)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,11 @@ type ColumnBool interface {
|
||||||
|
|
||||||
type boolColumnImpl struct {
|
type boolColumnImpl struct {
|
||||||
boolInterfaceImpl
|
boolInterfaceImpl
|
||||||
ColumnExpressionImpl
|
*ColumnExpressionImpl
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *boolColumnImpl) fromImpl(subQuery SelectTable) Projection {
|
||||||
|
return i.From(subQuery)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *boolColumnImpl) From(subQuery SelectTable) ColumnBool {
|
func (i *boolColumnImpl) From(subQuery SelectTable) ColumnBool {
|
||||||
|
|
@ -51,7 +55,11 @@ type ColumnFloat interface {
|
||||||
|
|
||||||
type floatColumnImpl struct {
|
type floatColumnImpl struct {
|
||||||
floatInterfaceImpl
|
floatInterfaceImpl
|
||||||
ColumnExpressionImpl
|
*ColumnExpressionImpl
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *floatColumnImpl) fromImpl(subQuery SelectTable) Projection {
|
||||||
|
return i.From(subQuery)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *floatColumnImpl) From(subQuery SelectTable) ColumnFloat {
|
func (i *floatColumnImpl) From(subQuery SelectTable) ColumnFloat {
|
||||||
|
|
@ -92,7 +100,11 @@ type ColumnInteger interface {
|
||||||
type integerColumnImpl struct {
|
type integerColumnImpl struct {
|
||||||
integerInterfaceImpl
|
integerInterfaceImpl
|
||||||
|
|
||||||
ColumnExpressionImpl
|
*ColumnExpressionImpl
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *integerColumnImpl) fromImpl(subQuery SelectTable) Projection {
|
||||||
|
return i.From(subQuery)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *integerColumnImpl) From(subQuery SelectTable) ColumnInteger {
|
func (i *integerColumnImpl) From(subQuery SelectTable) ColumnInteger {
|
||||||
|
|
@ -134,7 +146,11 @@ type ColumnString interface {
|
||||||
type stringColumnImpl struct {
|
type stringColumnImpl struct {
|
||||||
stringInterfaceImpl
|
stringInterfaceImpl
|
||||||
|
|
||||||
ColumnExpressionImpl
|
*ColumnExpressionImpl
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *stringColumnImpl) fromImpl(subQuery SelectTable) Projection {
|
||||||
|
return i.From(subQuery)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *stringColumnImpl) From(subQuery SelectTable) ColumnString {
|
func (i *stringColumnImpl) From(subQuery SelectTable) ColumnString {
|
||||||
|
|
@ -175,7 +191,11 @@ type ColumnBlob interface {
|
||||||
type blobColumnImpl struct {
|
type blobColumnImpl struct {
|
||||||
blobInterfaceImpl
|
blobInterfaceImpl
|
||||||
|
|
||||||
ColumnExpressionImpl
|
*ColumnExpressionImpl
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *blobColumnImpl) fromImpl(subQuery SelectTable) Projection {
|
||||||
|
return i.From(subQuery)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *blobColumnImpl) From(subQuery SelectTable) ColumnBlob {
|
func (i *blobColumnImpl) From(subQuery SelectTable) ColumnBlob {
|
||||||
|
|
@ -215,7 +235,11 @@ type ColumnTime interface {
|
||||||
|
|
||||||
type timeColumnImpl struct {
|
type timeColumnImpl struct {
|
||||||
timeInterfaceImpl
|
timeInterfaceImpl
|
||||||
ColumnExpressionImpl
|
*ColumnExpressionImpl
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *timeColumnImpl) fromImpl(subQuery SelectTable) Projection {
|
||||||
|
return i.From(subQuery)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *timeColumnImpl) From(subQuery SelectTable) ColumnTime {
|
func (i *timeColumnImpl) From(subQuery SelectTable) ColumnTime {
|
||||||
|
|
@ -254,7 +278,11 @@ type ColumnTimez interface {
|
||||||
|
|
||||||
type timezColumnImpl struct {
|
type timezColumnImpl struct {
|
||||||
timezInterfaceImpl
|
timezInterfaceImpl
|
||||||
ColumnExpressionImpl
|
*ColumnExpressionImpl
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *timezColumnImpl) fromImpl(subQuery SelectTable) Projection {
|
||||||
|
return i.From(subQuery)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *timezColumnImpl) From(subQuery SelectTable) ColumnTimez {
|
func (i *timezColumnImpl) From(subQuery SelectTable) ColumnTimez {
|
||||||
|
|
@ -294,7 +322,11 @@ type ColumnTimestamp interface {
|
||||||
|
|
||||||
type timestampColumnImpl struct {
|
type timestampColumnImpl struct {
|
||||||
timestampInterfaceImpl
|
timestampInterfaceImpl
|
||||||
ColumnExpressionImpl
|
*ColumnExpressionImpl
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *timestampColumnImpl) fromImpl(subQuery SelectTable) Projection {
|
||||||
|
return i.From(subQuery)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *timestampColumnImpl) From(subQuery SelectTable) ColumnTimestamp {
|
func (i *timestampColumnImpl) From(subQuery SelectTable) ColumnTimestamp {
|
||||||
|
|
@ -334,7 +366,11 @@ type ColumnTimestampz interface {
|
||||||
|
|
||||||
type timestampzColumnImpl struct {
|
type timestampzColumnImpl struct {
|
||||||
timestampzInterfaceImpl
|
timestampzInterfaceImpl
|
||||||
ColumnExpressionImpl
|
*ColumnExpressionImpl
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *timestampzColumnImpl) fromImpl(subQuery SelectTable) Projection {
|
||||||
|
return i.From(subQuery)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *timestampzColumnImpl) From(subQuery SelectTable) ColumnTimestampz {
|
func (i *timestampzColumnImpl) From(subQuery SelectTable) ColumnTimestampz {
|
||||||
|
|
@ -374,7 +410,11 @@ type ColumnDate interface {
|
||||||
|
|
||||||
type dateColumnImpl struct {
|
type dateColumnImpl struct {
|
||||||
dateInterfaceImpl
|
dateInterfaceImpl
|
||||||
ColumnExpressionImpl
|
*ColumnExpressionImpl
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *dateColumnImpl) fromImpl(subQuery SelectTable) Projection {
|
||||||
|
return i.From(subQuery)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *dateColumnImpl) From(subQuery SelectTable) ColumnDate {
|
func (i *dateColumnImpl) From(subQuery SelectTable) ColumnDate {
|
||||||
|
|
@ -402,6 +442,51 @@ func DateColumn(name string) ColumnDate {
|
||||||
|
|
||||||
//------------------------------------------------------//
|
//------------------------------------------------------//
|
||||||
|
|
||||||
|
// ColumnInterval is interface of PostgreSQL interval columns.
|
||||||
|
type ColumnInterval interface {
|
||||||
|
IntervalExpression
|
||||||
|
Column
|
||||||
|
|
||||||
|
From(subQuery SelectTable) ColumnInterval
|
||||||
|
SET(intervalExp IntervalExpression) ColumnAssigment
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------------------------//
|
||||||
|
|
||||||
|
type intervalColumnImpl struct {
|
||||||
|
*ColumnExpressionImpl
|
||||||
|
intervalInterfaceImpl
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *intervalColumnImpl) SET(intervalExp IntervalExpression) ColumnAssigment {
|
||||||
|
return columnAssigmentImpl{
|
||||||
|
column: i,
|
||||||
|
expression: intervalExp,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *intervalColumnImpl) fromImpl(subQuery SelectTable) Projection {
|
||||||
|
return i.From(subQuery)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *intervalColumnImpl) From(subQuery SelectTable) ColumnInterval {
|
||||||
|
newIntervalColumn := IntervalColumn(i.name)
|
||||||
|
newIntervalColumn.setTableName(i.tableName)
|
||||||
|
newIntervalColumn.setSubQuery(subQuery)
|
||||||
|
|
||||||
|
return newIntervalColumn
|
||||||
|
}
|
||||||
|
|
||||||
|
// IntervalColumn creates named interval column.
|
||||||
|
func IntervalColumn(name string) ColumnInterval {
|
||||||
|
intervalColumn := &intervalColumnImpl{}
|
||||||
|
intervalColumn.ColumnExpressionImpl = NewColumnImpl(name, "", intervalColumn)
|
||||||
|
intervalColumn.intervalInterfaceImpl.parent = intervalColumn
|
||||||
|
return intervalColumn
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------------------------//
|
||||||
|
|
||||||
// ColumnRange is interface for range columns which can be int range, string range
|
// ColumnRange is interface for range columns which can be int range, string range
|
||||||
// timestamp range or date range.
|
// timestamp range or date range.
|
||||||
type ColumnRange[T Expression] interface {
|
type ColumnRange[T Expression] interface {
|
||||||
|
|
@ -414,7 +499,11 @@ type ColumnRange[T Expression] interface {
|
||||||
|
|
||||||
type rangeColumnImpl[T Expression] struct {
|
type rangeColumnImpl[T Expression] struct {
|
||||||
rangeInterfaceImpl[T]
|
rangeInterfaceImpl[T]
|
||||||
ColumnExpressionImpl
|
*ColumnExpressionImpl
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *rangeColumnImpl[T]) fromImpl(subQuery SelectTable) Projection {
|
||||||
|
return i.From(subQuery)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *rangeColumnImpl[T]) From(subQuery SelectTable) ColumnRange[T] {
|
func (i *rangeColumnImpl[T]) From(subQuery SelectTable) ColumnRange[T] {
|
||||||
|
|
|
||||||
|
|
@ -80,9 +80,10 @@ type dateExpressionWrapper struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func newDateExpressionWrap(expression Expression) DateExpression {
|
func newDateExpressionWrap(expression Expression) DateExpression {
|
||||||
dateExpressionWrap := dateExpressionWrapper{Expression: expression}
|
dateExpressionWrap := &dateExpressionWrapper{Expression: expression}
|
||||||
dateExpressionWrap.dateInterfaceImpl.parent = &dateExpressionWrap
|
dateExpressionWrap.dateInterfaceImpl.parent = dateExpressionWrap
|
||||||
return &dateExpressionWrap
|
expression.setParent(dateExpressionWrap)
|
||||||
|
return dateExpressionWrap
|
||||||
}
|
}
|
||||||
|
|
||||||
// DateExp is date expression wrapper around arbitrary expression.
|
// DateExp is date expression wrapper around arbitrary expression.
|
||||||
|
|
|
||||||
|
|
@ -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
|
IsReservedWord(name string) bool
|
||||||
SerializeOrderBy() func(expression Expression, ascending, nullsFirst *bool) SerializerFunc
|
SerializeOrderBy() func(expression Expression, ascending, nullsFirst *bool) SerializerFunc
|
||||||
ValuesDefaultColumnName(index int) string
|
ValuesDefaultColumnName(index int) string
|
||||||
|
JsonValueEncode(expr Expression) Expression
|
||||||
}
|
}
|
||||||
|
|
||||||
// SerializerFunc func
|
// SerializerFunc func
|
||||||
|
|
@ -41,6 +42,7 @@ type DialectParams struct {
|
||||||
ReservedWords []string
|
ReservedWords []string
|
||||||
SerializeOrderBy func(expression Expression, ascending, nullsFirst *bool) SerializerFunc
|
SerializeOrderBy func(expression Expression, ascending, nullsFirst *bool) SerializerFunc
|
||||||
ValuesDefaultColumnName func(index int) string
|
ValuesDefaultColumnName func(index int) string
|
||||||
|
JsonValueEncode func(expr Expression) Expression
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewDialect creates new dialect with params
|
// NewDialect creates new dialect with params
|
||||||
|
|
@ -57,6 +59,7 @@ func NewDialect(params DialectParams) Dialect {
|
||||||
reservedWords: arrayOfStringsToMapOfStrings(params.ReservedWords),
|
reservedWords: arrayOfStringsToMapOfStrings(params.ReservedWords),
|
||||||
serializeOrderBy: params.SerializeOrderBy,
|
serializeOrderBy: params.SerializeOrderBy,
|
||||||
valuesDefaultColumnName: params.ValuesDefaultColumnName,
|
valuesDefaultColumnName: params.ValuesDefaultColumnName,
|
||||||
|
jsonValueEncode: params.JsonValueEncode,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -72,6 +75,7 @@ type dialectImpl struct {
|
||||||
reservedWords map[string]bool
|
reservedWords map[string]bool
|
||||||
serializeOrderBy func(expression Expression, ascending, nullsFirst *bool) SerializerFunc
|
serializeOrderBy func(expression Expression, ascending, nullsFirst *bool) SerializerFunc
|
||||||
valuesDefaultColumnName func(index int) string
|
valuesDefaultColumnName func(index int) string
|
||||||
|
jsonValueEncode func(expr Expression) Expression
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *dialectImpl) Name() string {
|
func (d *dialectImpl) Name() string {
|
||||||
|
|
@ -125,6 +129,10 @@ func (d *dialectImpl) ValuesDefaultColumnName(index int) string {
|
||||||
return d.valuesDefaultColumnName(index)
|
return d.valuesDefaultColumnName(index)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *dialectImpl) JsonValueEncode(expr Expression) Expression {
|
||||||
|
return d.jsonValueEncode(expr)
|
||||||
|
}
|
||||||
|
|
||||||
func arrayOfStringsToMapOfStrings(arr []string) map[string]bool {
|
func arrayOfStringsToMapOfStrings(arr []string) map[string]bool {
|
||||||
ret := map[string]bool{}
|
ret := map[string]bool{}
|
||||||
for _, elem := range arr {
|
for _, elem := range arr {
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,9 @@ type Expression interface {
|
||||||
GroupByClause
|
GroupByClause
|
||||||
OrderByClause
|
OrderByClause
|
||||||
|
|
||||||
|
serializeForJsonValue(statement StatementType, out *SQLBuilder)
|
||||||
|
setParent(parent Expression)
|
||||||
|
|
||||||
// IS_NULL tests expression whether it is a NULL value.
|
// IS_NULL tests expression whether it is a NULL value.
|
||||||
IS_NULL() BoolExpression
|
IS_NULL() BoolExpression
|
||||||
// IS_NOT_NULL tests expression whether it is a non-NULL value.
|
// IS_NOT_NULL tests expression whether it is a non-NULL value.
|
||||||
|
|
@ -34,6 +37,10 @@ type ExpressionInterfaceImpl struct {
|
||||||
Parent Expression
|
Parent Expression
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (e *ExpressionInterfaceImpl) setParent(parent Expression) {
|
||||||
|
e.Parent = parent
|
||||||
|
}
|
||||||
|
|
||||||
func (e *ExpressionInterfaceImpl) fromImpl(subQuery SelectTable) Projection {
|
func (e *ExpressionInterfaceImpl) fromImpl(subQuery SelectTable) Projection {
|
||||||
panic(fmt.Sprintf("jet: can't export unaliased expression subQuery: %s, expression: %s",
|
panic(fmt.Sprintf("jet: can't export unaliased expression subQuery: %s, expression: %s",
|
||||||
subQuery.Alias(), serializeToDefaultDebugString(e.Parent)))
|
subQuery.Alias(), serializeToDefaultDebugString(e.Parent)))
|
||||||
|
|
@ -89,16 +96,21 @@ func (e *ExpressionInterfaceImpl) serializeForGroupBy(statement StatementType, o
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *ExpressionInterfaceImpl) serializeForProjection(statement StatementType, out *SQLBuilder) {
|
func (e *ExpressionInterfaceImpl) serializeForProjection(statement StatementType, out *SQLBuilder) {
|
||||||
if statement.IsSelectJSON() {
|
|
||||||
panic("jet: expression need to be aliased when used as SELECT JSON projection.")
|
|
||||||
}
|
|
||||||
e.Parent.serialize(statement, out, NoWrap)
|
e.Parent.serialize(statement, out, NoWrap)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *ExpressionInterfaceImpl) serializeForJsonObj(statement StatementType, out *SQLBuilder) {
|
func (e *ExpressionInterfaceImpl) serializeForJsonObjEntry(statement StatementType, out *SQLBuilder) {
|
||||||
panic("jet: expression need to be aliased when used as SELECT JSON projection.")
|
panic("jet: expression need to be aliased when used as SELECT JSON projection.")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (e *ExpressionInterfaceImpl) serializeForRowToJsonProjection(statement StatementType, out *SQLBuilder) {
|
||||||
|
panic("jet: expression need to be aliased when used as SELECT JSON projection.")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *ExpressionInterfaceImpl) serializeForJsonValue(statement StatementType, out *SQLBuilder) {
|
||||||
|
out.Dialect.JsonValueEncode(e.Parent).serialize(statement, out)
|
||||||
|
}
|
||||||
|
|
||||||
func (e *ExpressionInterfaceImpl) serializeForOrderBy(statement StatementType, out *SQLBuilder) {
|
func (e *ExpressionInterfaceImpl) serializeForOrderBy(statement StatementType, out *SQLBuilder) {
|
||||||
e.Parent.serialize(statement, out, NoWrap)
|
e.Parent.serialize(statement, out, NoWrap)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -102,9 +102,10 @@ type floatExpressionWrapper struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func newFloatExpressionWrap(expression Expression) FloatExpression {
|
func newFloatExpressionWrap(expression Expression) FloatExpression {
|
||||||
floatExpressionWrap := floatExpressionWrapper{Expression: expression}
|
floatExpressionWrap := &floatExpressionWrapper{Expression: expression}
|
||||||
floatExpressionWrap.floatInterfaceImpl.parent = &floatExpressionWrap
|
floatExpressionWrap.floatInterfaceImpl.parent = floatExpressionWrap
|
||||||
return &floatExpressionWrap
|
expression.setParent(floatExpressionWrap)
|
||||||
|
return floatExpressionWrap
|
||||||
}
|
}
|
||||||
|
|
||||||
// FloatExp is date expression wrapper around arbitrary expression.
|
// FloatExp is date expression wrapper around arbitrary expression.
|
||||||
|
|
|
||||||
|
|
@ -141,11 +141,11 @@ type integerExpressionWrapper struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func newIntExpressionWrap(expression Expression) IntegerExpression {
|
func newIntExpressionWrap(expression Expression) IntegerExpression {
|
||||||
intExpressionWrap := integerExpressionWrapper{Expression: expression}
|
intExpressionWrap := &integerExpressionWrapper{Expression: expression}
|
||||||
|
intExpressionWrap.integerInterfaceImpl.parent = intExpressionWrap
|
||||||
|
expression.setParent(intExpressionWrap)
|
||||||
|
|
||||||
intExpressionWrap.integerInterfaceImpl.parent = &intExpressionWrap
|
return intExpressionWrap
|
||||||
|
|
||||||
return &intExpressionWrap
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// IntExp is int expression wrapper around arbitrary expression.
|
// IntExp is int expression wrapper around arbitrary expression.
|
||||||
|
|
|
||||||
|
|
@ -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
|
return rawExp
|
||||||
}
|
}
|
||||||
|
|
||||||
// RawWithParent is a Raw constructor used for construction dialect specific expression
|
|
||||||
func RawWithParent(raw string, parent ...Expression) Expression {
|
|
||||||
rawExp := &rawExpression{
|
|
||||||
Raw: raw,
|
|
||||||
noWrap: true,
|
|
||||||
}
|
|
||||||
rawExp.ExpressionInterfaceImpl.Parent = OptionalOrDefaultExpression(rawExp, parent...)
|
|
||||||
|
|
||||||
return rawExp
|
|
||||||
}
|
|
||||||
|
|
||||||
// RawBool helper that for raw string boolean expressions
|
// RawBool helper that for raw string boolean expressions
|
||||||
func RawBool(raw string, namedArgs ...map[string]interface{}) BoolExpression {
|
func RawBool(raw string, namedArgs ...map[string]interface{}) BoolExpression {
|
||||||
return BoolExp(Raw(raw, namedArgs...))
|
return BoolExp(Raw(raw, namedArgs...))
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,8 @@ package jet
|
||||||
// Projection is interface for all projection types. Types that can be part of, for instance SELECT clause.
|
// Projection is interface for all projection types. Types that can be part of, for instance SELECT clause.
|
||||||
type Projection interface {
|
type Projection interface {
|
||||||
serializeForProjection(statement StatementType, out *SQLBuilder)
|
serializeForProjection(statement StatementType, out *SQLBuilder)
|
||||||
serializeForJsonObj(statement StatementType, out *SQLBuilder)
|
serializeForJsonObjEntry(statement StatementType, out *SQLBuilder)
|
||||||
|
serializeForRowToJsonProjection(statement StatementType, out *SQLBuilder)
|
||||||
fromImpl(subQuery SelectTable) Projection
|
fromImpl(subQuery SelectTable) Projection
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -29,7 +30,7 @@ func (pl ProjectionList) serializeForProjection(statement StatementType, out *SQ
|
||||||
SerializeProjectionList(statement, pl, out)
|
SerializeProjectionList(statement, pl, out)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pl ProjectionList) serializeForJsonObj(statement StatementType, out *SQLBuilder) {
|
func (pl ProjectionList) serializeForJsonObjEntry(statement StatementType, out *SQLBuilder) {
|
||||||
SerializeProjectionListJsonObj(statement, pl, out)
|
SerializeProjectionListJsonObj(statement, pl, out)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -85,9 +86,17 @@ func (pl ProjectionList) Except(toExclude ...Column) ProjectionList {
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
// JsonProjectionList redefines []Projection so projections can be serialized as json object key/values
|
func (pl ProjectionList) serializeForRowToJsonProjection(statement StatementType, out *SQLBuilder) {
|
||||||
type JsonProjectionList []Projection
|
out.WriteRowToJsonProjections(statement, pl)
|
||||||
|
}
|
||||||
func (j JsonProjectionList) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) {
|
|
||||||
SerializeProjectionListJsonObj(statement, j, out)
|
// JsonObjProjectionList redefines []Projection so projections can be serialized as json object key/values
|
||||||
|
type JsonObjProjectionList []Projection
|
||||||
|
|
||||||
|
func (j JsonObjProjectionList) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) {
|
||||||
|
out.IncreaseIdent()
|
||||||
|
out.NewLine()
|
||||||
|
SerializeProjectionListJsonObj(statement, j, out)
|
||||||
|
out.DecreaseIdent()
|
||||||
|
out.NewLine()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -118,9 +118,10 @@ type rangeExpressionWrapper[T Expression] struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func newRangeExpressionWrap[T Expression](expression Expression) Range[T] {
|
func newRangeExpressionWrap[T Expression](expression Expression) Range[T] {
|
||||||
rangeExpressionWrap := rangeExpressionWrapper[T]{Expression: expression}
|
rangeExpressionWrap := &rangeExpressionWrapper[T]{Expression: expression}
|
||||||
rangeExpressionWrap.rangeInterfaceImpl.parent = &rangeExpressionWrap
|
rangeExpressionWrap.rangeInterfaceImpl.parent = rangeExpressionWrap
|
||||||
return &rangeExpressionWrap
|
expression.setParent(rangeExpressionWrap)
|
||||||
|
return rangeExpressionWrap
|
||||||
}
|
}
|
||||||
|
|
||||||
// RangeExp is range expression wrapper around arbitrary expression.
|
// RangeExp is range expression wrapper around arbitrary expression.
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ type RowExpression interface {
|
||||||
type rowInterfaceImpl struct {
|
type rowInterfaceImpl struct {
|
||||||
parent Expression
|
parent Expression
|
||||||
dialect Dialect
|
dialect Dialect
|
||||||
elemCount int
|
expressions []Expression
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *rowInterfaceImpl) EQ(rhs RowExpression) BoolExpression {
|
func (n *rowInterfaceImpl) EQ(rhs RowExpression) BoolExpression {
|
||||||
|
|
@ -57,9 +57,8 @@ func (n *rowInterfaceImpl) LT_EQ(rhs RowExpression) BoolExpression {
|
||||||
func (n *rowInterfaceImpl) projections() ProjectionList {
|
func (n *rowInterfaceImpl) projections() ProjectionList {
|
||||||
var ret ProjectionList
|
var ret ProjectionList
|
||||||
|
|
||||||
for i := 0; i < n.elemCount; i++ {
|
for i, expression := range n.expressions {
|
||||||
rowColumn := NewColumnImpl(n.dialect.ValuesDefaultColumnName(i), "", nil)
|
ret = append(ret, newDummyColumnForExpression(expression, n.dialect.ValuesDefaultColumnName(i)))
|
||||||
ret = append(ret, &rowColumn)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret
|
return ret
|
||||||
|
|
@ -77,7 +76,7 @@ func newRowExpression(name string, dialect Dialect, expressions ...Expression) R
|
||||||
|
|
||||||
ret.Expression = NewFunc(name, expressions, ret)
|
ret.Expression = NewFunc(name, expressions, ret)
|
||||||
ret.dialect = dialect
|
ret.dialect = dialect
|
||||||
ret.elemCount = len(expressions)
|
ret.expressions = expressions
|
||||||
|
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,10 +22,6 @@ func (s SerializeOption) WithFallTrough(options []SerializeOption) []SerializeOp
|
||||||
// StatementType is type of the SQL statement
|
// StatementType is type of the SQL statement
|
||||||
type StatementType string
|
type StatementType string
|
||||||
|
|
||||||
func (s StatementType) IsSelectJSON() bool {
|
|
||||||
return s == SelectJsonObjStatementType || s == SelectJsonArrStatementType
|
|
||||||
}
|
|
||||||
|
|
||||||
// Statement types
|
// Statement types
|
||||||
const (
|
const (
|
||||||
SelectStatementType StatementType = "SELECT"
|
SelectStatementType StatementType = "SELECT"
|
||||||
|
|
|
||||||
|
|
@ -61,6 +61,16 @@ func (s *SQLBuilder) WriteProjections(statement StatementType, projections []Pro
|
||||||
s.DecreaseIdent()
|
s.DecreaseIdent()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *SQLBuilder) WriteRowToJsonProjections(statement StatementType, projections []Projection) {
|
||||||
|
for i, projection := range projections {
|
||||||
|
if i > 0 {
|
||||||
|
s.WriteString(",")
|
||||||
|
s.NewLine()
|
||||||
|
}
|
||||||
|
projection.serializeForRowToJsonProjection(statement, s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// NewLine adds new line to output SQL
|
// NewLine adds new line to output SQL
|
||||||
func (s *SQLBuilder) NewLine() {
|
func (s *SQLBuilder) NewLine() {
|
||||||
s.write([]byte{'\n'})
|
s.write([]byte{'\n'})
|
||||||
|
|
|
||||||
|
|
@ -257,12 +257,13 @@ type expressionStatementImpl struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *expressionStatementImpl) serializeForProjection(statement StatementType, out *SQLBuilder) {
|
func (s *expressionStatementImpl) serializeForProjection(statement StatementType, out *SQLBuilder) {
|
||||||
if statement.IsSelectJSON() {
|
|
||||||
panic("jet: SELECT JSON statements need to be aliased when used as a projection.")
|
|
||||||
}
|
|
||||||
s.serialize(statement, out)
|
s.serialize(statement, out)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (e *expressionStatementImpl) serializeForRowToJsonProjection(statement StatementType, out *SQLBuilder) {
|
||||||
|
panic("jet: SELECT JSON statements need to be aliased when used as a projection.")
|
||||||
|
}
|
||||||
|
|
||||||
// NewStatementImpl creates new statementImpl
|
// NewStatementImpl creates new statementImpl
|
||||||
func NewStatementImpl(Dialect Dialect, statementType StatementType, parent SerializerStatement, clauses ...Clause) SerializerStatement {
|
func NewStatementImpl(Dialect Dialect, statementType StatementType, parent SerializerStatement, clauses ...Clause) SerializerStatement {
|
||||||
return &statementImpl{
|
return &statementImpl{
|
||||||
|
|
|
||||||
|
|
@ -105,9 +105,10 @@ type stringExpressionWrapper struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func newStringExpressionWrap(expression Expression) StringExpression {
|
func newStringExpressionWrap(expression Expression) StringExpression {
|
||||||
stringExpressionWrap := stringExpressionWrapper{Expression: expression}
|
stringExpressionWrap := &stringExpressionWrapper{Expression: expression}
|
||||||
stringExpressionWrap.stringInterfaceImpl.parent = &stringExpressionWrap
|
stringExpressionWrap.stringInterfaceImpl.parent = stringExpressionWrap
|
||||||
return &stringExpressionWrap
|
expression.setParent(stringExpressionWrap)
|
||||||
|
return stringExpressionWrap
|
||||||
}
|
}
|
||||||
|
|
||||||
// StringExp is string expression wrapper around arbitrary expression.
|
// StringExp is string expression wrapper around arbitrary expression.
|
||||||
|
|
|
||||||
|
|
@ -75,14 +75,15 @@ func (t *timeInterfaceImpl) SUB(rhs Interval) TimeExpression {
|
||||||
//---------------------------------------------------//
|
//---------------------------------------------------//
|
||||||
|
|
||||||
type timeExpressionWrapper struct {
|
type timeExpressionWrapper struct {
|
||||||
timeInterfaceImpl
|
|
||||||
Expression
|
Expression
|
||||||
|
timeInterfaceImpl
|
||||||
}
|
}
|
||||||
|
|
||||||
func newTimeExpressionWrap(expression Expression) TimeExpression {
|
func newTimeExpressionWrap(expression Expression) TimeExpression {
|
||||||
timeExpressionWrap := timeExpressionWrapper{Expression: expression}
|
timeExpressionWrap := &timeExpressionWrapper{Expression: expression}
|
||||||
timeExpressionWrap.timeInterfaceImpl.parent = &timeExpressionWrap
|
timeExpressionWrap.timeInterfaceImpl.parent = timeExpressionWrap
|
||||||
return &timeExpressionWrap
|
expression.setParent(timeExpressionWrap)
|
||||||
|
return timeExpressionWrap
|
||||||
}
|
}
|
||||||
|
|
||||||
// TimeExp is time expression wrapper around arbitrary expression.
|
// TimeExp is time expression wrapper around arbitrary expression.
|
||||||
|
|
|
||||||
|
|
@ -52,11 +52,3 @@ func TestTimeExp(t *testing.T) {
|
||||||
assertClauseSerialize(t, TimeExp(table1ColFloat).LT(Time(1, 1, 1, 1*time.Millisecond)),
|
assertClauseSerialize(t, TimeExp(table1ColFloat).LT(Time(1, 1, 1, 1*time.Millisecond)),
|
||||||
"(table1.col_float < $1)", string("01:01:01.001"))
|
"(table1.col_float < $1)", string("01:01:01.001"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTimeArithmetic(t *testing.T) {
|
|
||||||
time := Time(10, 20, 3)
|
|
||||||
assertClauseDebugSerialize(t, table1ColTime.ADD(NewInterval(String("1 HOUR"))).EQ(time),
|
|
||||||
"((table1.col_time + INTERVAL '1 HOUR') = '10:20:03')")
|
|
||||||
assertClauseDebugSerialize(t, table1ColTime.SUB(NewInterval(String("1 HOUR"))).EQ(time),
|
|
||||||
"((table1.col_time - INTERVAL '1 HOUR') = '10:20:03')")
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -80,9 +80,10 @@ type timestampExpressionWrapper struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func newTimestampExpressionWrap(expression Expression) TimestampExpression {
|
func newTimestampExpressionWrap(expression Expression) TimestampExpression {
|
||||||
timestampExpressionWrap := timestampExpressionWrapper{Expression: expression}
|
timestampExpressionWrap := ×tampExpressionWrapper{Expression: expression}
|
||||||
timestampExpressionWrap.timestampInterfaceImpl.parent = ×tampExpressionWrap
|
timestampExpressionWrap.timestampInterfaceImpl.parent = timestampExpressionWrap
|
||||||
return ×tampExpressionWrap
|
expression.setParent(timestampExpressionWrap)
|
||||||
|
return timestampExpressionWrap
|
||||||
}
|
}
|
||||||
|
|
||||||
// TimestampExp is timestamp expression wrapper around arbitrary expression.
|
// TimestampExp is timestamp expression wrapper around arbitrary expression.
|
||||||
|
|
|
||||||
|
|
@ -53,11 +53,3 @@ func TestTimestampExp(t *testing.T) {
|
||||||
assertClauseSerialize(t, TimestampExp(table1ColFloat).LT(timestamp),
|
assertClauseSerialize(t, TimestampExp(table1ColFloat).LT(timestamp),
|
||||||
"(table1.col_float < $1)", "2000-01-31 10:20:00.003")
|
"(table1.col_float < $1)", "2000-01-31 10:20:00.003")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTimestampArithmetic(t *testing.T) {
|
|
||||||
timestamp := Timestamp(2000, 1, 1, 0, 0, 0)
|
|
||||||
assertClauseDebugSerialize(t, table1ColTimestamp.ADD(NewInterval(String("1 HOUR"))).EQ(timestamp),
|
|
||||||
"((table1.col_timestamp + INTERVAL '1 HOUR') = '2000-01-01 00:00:00')")
|
|
||||||
assertClauseDebugSerialize(t, table1ColTimestamp.SUB(NewInterval(String("1 HOUR"))).EQ(timestamp),
|
|
||||||
"((table1.col_timestamp - INTERVAL '1 HOUR') = '2000-01-01 00:00:00')")
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -80,9 +80,10 @@ type timestampzExpressionWrapper struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func newTimestampzExpressionWrap(expression Expression) TimestampzExpression {
|
func newTimestampzExpressionWrap(expression Expression) TimestampzExpression {
|
||||||
timestampzExpressionWrap := timestampzExpressionWrapper{Expression: expression}
|
timestampzExpressionWrap := ×tampzExpressionWrapper{Expression: expression}
|
||||||
timestampzExpressionWrap.timestampzInterfaceImpl.parent = ×tampzExpressionWrap
|
timestampzExpressionWrap.timestampzInterfaceImpl.parent = timestampzExpressionWrap
|
||||||
return ×tampzExpressionWrap
|
expression.setParent(timestampzExpressionWrap)
|
||||||
|
return timestampzExpressionWrap
|
||||||
}
|
}
|
||||||
|
|
||||||
// TimestampzExp is timestamp with time zone expression wrapper around arbitrary expression.
|
// TimestampzExp is timestamp with time zone expression wrapper around arbitrary expression.
|
||||||
|
|
|
||||||
|
|
@ -53,11 +53,3 @@ func TestTimestampzExp(t *testing.T) {
|
||||||
assertClauseSerialize(t, TimestampzExp(table1ColFloat).LT(timestampz),
|
assertClauseSerialize(t, TimestampzExp(table1ColFloat).LT(timestampz),
|
||||||
"(table1.col_float < $1)", "2000-01-31 10:20:05.000023 +200")
|
"(table1.col_float < $1)", "2000-01-31 10:20:05.000023 +200")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTimestampzArithmetic(t *testing.T) {
|
|
||||||
timestampz := Timestampz(2000, 1, 1, 0, 0, 0, 100, "UTC")
|
|
||||||
assertClauseDebugSerialize(t, table1ColTimestampz.ADD(NewInterval(String("1 HOUR"))).EQ(timestampz),
|
|
||||||
"((table1.col_timestampz + INTERVAL '1 HOUR') = '2000-01-01 00:00:00.0000001 UTC')")
|
|
||||||
assertClauseDebugSerialize(t, table1ColTimestampz.SUB(NewInterval(String("1 HOUR"))).EQ(timestampz),
|
|
||||||
"((table1.col_timestampz - INTERVAL '1 HOUR') = '2000-01-01 00:00:00.0000001 UTC')")
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -75,14 +75,15 @@ func (t *timezInterfaceImpl) SUB(rhs Interval) TimezExpression {
|
||||||
//---------------------------------------------------//
|
//---------------------------------------------------//
|
||||||
|
|
||||||
type timezExpressionWrapper struct {
|
type timezExpressionWrapper struct {
|
||||||
timezInterfaceImpl
|
|
||||||
Expression
|
Expression
|
||||||
|
timezInterfaceImpl
|
||||||
}
|
}
|
||||||
|
|
||||||
func newTimezExpressionWrap(expression Expression) TimezExpression {
|
func newTimezExpressionWrap(expression Expression) TimezExpression {
|
||||||
timezExpressionWrap := timezExpressionWrapper{Expression: expression}
|
timezExpressionWrap := &timezExpressionWrapper{Expression: expression}
|
||||||
timezExpressionWrap.timezInterfaceImpl.parent = &timezExpressionWrap
|
timezExpressionWrap.timezInterfaceImpl.parent = timezExpressionWrap
|
||||||
return &timezExpressionWrap
|
expression.setParent(timezExpressionWrap)
|
||||||
|
return timezExpressionWrap
|
||||||
}
|
}
|
||||||
|
|
||||||
// TimezExp is time with time zone expression wrapper around arbitrary expression.
|
// TimezExp is time with time zone expression wrapper around arbitrary expression.
|
||||||
|
|
|
||||||
|
|
@ -51,11 +51,3 @@ func TestTimezExp(t *testing.T) {
|
||||||
assertClauseSerialize(t, TimezExp(table1ColFloat).LT(Timez(1, 1, 1, 1, "+4:00")),
|
assertClauseSerialize(t, TimezExp(table1ColFloat).LT(Timez(1, 1, 1, 1, "+4:00")),
|
||||||
"(table1.col_float < $1)", string("01:01:01.000000001 +4:00"))
|
"(table1.col_float < $1)", string("01:01:01.000000001 +4:00"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTimezArithmetic(t *testing.T) {
|
|
||||||
timez := Timez(0, 0, 0, 100, "UTC")
|
|
||||||
assertClauseDebugSerialize(t, table1ColTimez.ADD(NewInterval(String("1 HOUR"))).EQ(timez),
|
|
||||||
"((table1.col_timez + INTERVAL '1 HOUR') = '00:00:00.0000001 UTC')")
|
|
||||||
assertClauseDebugSerialize(t, table1ColTimez.SUB(NewInterval(String("1 HOUR"))).EQ(timez),
|
|
||||||
"((table1.col_timez - INTERVAL '1 HOUR') = '00:00:00.0000001 UTC')")
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -71,7 +71,7 @@ func SerializeProjectionListJsonObj(statement StatementType, projections []Proje
|
||||||
panic("jet: Projection is nil")
|
panic("jet: Projection is nil")
|
||||||
}
|
}
|
||||||
|
|
||||||
p.serializeForJsonObj(statement, out)
|
p.serializeForJsonObjEntry(statement, out)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -122,7 +122,7 @@ func AssertJsonEqual(t require.TestingT, actual, expected interface{}, option ..
|
||||||
expectedJsonData, err := json.MarshalIndent(expected, "", "\t")
|
expectedJsonData, err := json.MarshalIndent(expected, "", "\t")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
require.Equal(t, actualJsonData, expectedJsonData)
|
require.Equal(t, string(actualJsonData), string(expectedJsonData))
|
||||||
}
|
}
|
||||||
|
|
||||||
// SaveJSONFile saves v as json at testRelativePath
|
// SaveJSONFile saves v as json at testRelativePath
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ import (
|
||||||
"github.com/go-jet/jet/v2/internal/jet"
|
"github.com/go-jet/jet/v2/internal/jet"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Dialect is implementation of MySQL dialect for SQL Builder serialisation.
|
// Dialect is implementation of MySQL dialect for SQL Builder serialization.
|
||||||
var Dialect = newDialect()
|
var Dialect = newDialect()
|
||||||
|
|
||||||
func newDialect() jet.Dialect {
|
func newDialect() jet.Dialect {
|
||||||
|
|
@ -34,6 +34,23 @@ func newDialect() jet.Dialect {
|
||||||
ValuesDefaultColumnName: func(index int) string {
|
ValuesDefaultColumnName: func(index int) string {
|
||||||
return fmt.Sprintf("column_%d", index)
|
return fmt.Sprintf("column_%d", index)
|
||||||
},
|
},
|
||||||
|
JsonValueEncode: func(expr Expression) Expression {
|
||||||
|
switch e := expr.(type) {
|
||||||
|
case BlobExpression:
|
||||||
|
return TO_BASE64(e)
|
||||||
|
|
||||||
|
// CustomExpression used bellow (instead DATE_FORMAT function) so that only expr is parametrized
|
||||||
|
case TimestampExpression:
|
||||||
|
return CustomExpression(Token("DATE_FORMAT("), e, Token(",'%Y-%m-%dT%H:%i:%s.%fZ')"))
|
||||||
|
case TimeExpression:
|
||||||
|
return CustomExpression(Token("CONCAT('0000-01-01T', DATE_FORMAT("), e, Token(",'%H:%i:%s.%fZ'))"))
|
||||||
|
case DateExpression:
|
||||||
|
return CustomExpression(Token("CONCAT(DATE_FORMAT("), e, Token(",'%Y-%m-%d')"), Token(", 'T00:00:00Z')"))
|
||||||
|
case BoolExpression:
|
||||||
|
return CustomExpression(e, Token(" = 1"))
|
||||||
|
}
|
||||||
|
return expr
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
return jet.NewDialect(mySQLDialectParams)
|
return jet.NewDialect(mySQLDialectParams)
|
||||||
|
|
|
||||||
|
|
@ -98,13 +98,10 @@ func INTERVAL(value interface{}, unitType unitType) Interval {
|
||||||
|
|
||||||
// INTERVALe creates new temporal interval from expresion and unit type.
|
// INTERVALe creates new temporal interval from expresion and unit type.
|
||||||
func INTERVALe(expr Expression, unitType unitType) Interval {
|
func INTERVALe(expr Expression, unitType unitType) Interval {
|
||||||
return jet.NewInterval(jet.ListSerializer{
|
return jet.IntervalExp(CustomExpression(Token("INTERVAL"), expr, Token(unitType)))
|
||||||
Serializers: []jet.Serializer{expr, jet.RawWithParent(string(unitType))},
|
|
||||||
Separator: " ",
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// INTERVALd temoral interval from time.Duration
|
// INTERVALd creates new temporal interval from time.Duration
|
||||||
func INTERVALd(duration time.Duration) Interval {
|
func INTERVALd(duration time.Duration) Interval {
|
||||||
var sign int64 = 1
|
var sign int64 = 1
|
||||||
if duration < 0 {
|
if duration < 0 {
|
||||||
|
|
@ -43,7 +43,7 @@ func newSelectStatementJson(projections []Projection, statementType jet.Statemen
|
||||||
}
|
}
|
||||||
|
|
||||||
func constructJsonFunc(projections []Projection, statementType jet.StatementType) Expression {
|
func constructJsonFunc(projections []Projection, statementType jet.StatementType) Expression {
|
||||||
jsonObj := Func("JSON_OBJECT", CustomExpression(jet.JsonProjectionList(projections)))
|
jsonObj := Func("JSON_OBJECT", CustomExpression(jet.JsonObjProjectionList(projections)))
|
||||||
|
|
||||||
if statementType == jet.SelectJsonArrStatementType {
|
if statementType == jet.SelectJsonArrStatementType {
|
||||||
return Func("JSON_ARRAYAGG", jsonObj)
|
return Func("JSON_ARRAYAGG", jsonObj)
|
||||||
|
|
|
||||||
|
|
@ -71,6 +71,12 @@ type ColumnTimestampz = jet.ColumnTimestampz
|
||||||
// TimestampzColumn creates named timestamp with time zone column.
|
// TimestampzColumn creates named timestamp with time zone column.
|
||||||
var TimestampzColumn = jet.TimestampzColumn
|
var TimestampzColumn = jet.TimestampzColumn
|
||||||
|
|
||||||
|
// ColumnInterval is interface of PostgreSQL interval columns.
|
||||||
|
type ColumnInterval = jet.ColumnInterval
|
||||||
|
|
||||||
|
// IntervalColumn creates named interval column
|
||||||
|
var IntervalColumn = jet.IntervalColumn
|
||||||
|
|
||||||
// ColumnDateRange is interface of SQL date range column
|
// ColumnDateRange is interface of SQL date range column
|
||||||
type ColumnDateRange = jet.ColumnRange[DateExpression]
|
type ColumnDateRange = jet.ColumnRange[DateExpression]
|
||||||
|
|
||||||
|
|
@ -106,41 +112,3 @@ type ColumnInt8Range jet.ColumnRange[jet.Int8Expression]
|
||||||
|
|
||||||
// Int8RangeColumn creates named range with range column
|
// Int8RangeColumn creates named range with range column
|
||||||
var Int8RangeColumn = jet.RangeColumn[jet.Int8Expression]
|
var Int8RangeColumn = jet.RangeColumn[jet.Int8Expression]
|
||||||
|
|
||||||
//------------------------------------------------------//
|
|
||||||
|
|
||||||
// ColumnInterval is interface of PostgreSQL interval columns.
|
|
||||||
type ColumnInterval interface {
|
|
||||||
IntervalExpression
|
|
||||||
jet.Column
|
|
||||||
|
|
||||||
From(subQuery SelectTable) ColumnInterval
|
|
||||||
SET(intervalExp IntervalExpression) ColumnAssigment
|
|
||||||
}
|
|
||||||
|
|
||||||
//------------------------------------------------------//
|
|
||||||
|
|
||||||
type intervalColumnImpl struct {
|
|
||||||
jet.ColumnExpressionImpl
|
|
||||||
intervalInterfaceImpl
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *intervalColumnImpl) SET(intervalExp IntervalExpression) ColumnAssigment {
|
|
||||||
return jet.NewColumnAssignment(i, intervalExp)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *intervalColumnImpl) From(subQuery SelectTable) ColumnInterval {
|
|
||||||
newIntervalColumn := IntervalColumn(i.Name())
|
|
||||||
jet.SetTableName(newIntervalColumn, i.TableName())
|
|
||||||
jet.SetSubQuery(newIntervalColumn, subQuery)
|
|
||||||
|
|
||||||
return newIntervalColumn
|
|
||||||
}
|
|
||||||
|
|
||||||
// IntervalColumn creates named interval column.
|
|
||||||
func IntervalColumn(name string) ColumnInterval {
|
|
||||||
intervalColumn := &intervalColumnImpl{}
|
|
||||||
intervalColumn.ColumnExpressionImpl = jet.NewColumnImpl(name, "", intervalColumn)
|
|
||||||
intervalColumn.intervalInterfaceImpl.parent = intervalColumn
|
|
||||||
return intervalColumn
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,8 @@ package postgres
|
||||||
import (
|
import (
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/go-jet/jet/v2/internal/jet"
|
"github.com/go-jet/jet/v2/internal/jet"
|
||||||
|
"strconv"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Dialect is implementation of postgres dialect for SQL Builder serialisation.
|
// Dialect is implementation of postgres dialect for SQL Builder serialisation.
|
||||||
|
|
@ -32,6 +31,23 @@ func newDialect() jet.Dialect {
|
||||||
ValuesDefaultColumnName: func(index int) string {
|
ValuesDefaultColumnName: func(index int) string {
|
||||||
return fmt.Sprintf("column%d", index+1)
|
return fmt.Sprintf("column%d", index+1)
|
||||||
},
|
},
|
||||||
|
JsonValueEncode: func(expr Expression) Expression {
|
||||||
|
switch e := expr.(type) {
|
||||||
|
case ByteaExpression:
|
||||||
|
return ENCODE(e, Base64)
|
||||||
|
|
||||||
|
// CustomExpression used bellow (instead TO_CHAR function) so that only expr is parametrized
|
||||||
|
case TimeExpression:
|
||||||
|
return CustomExpression(Token("'0000-01-01T' || to_char('2000-10-10'::date + "), e, Token(`, 'HH24:MI:SS.USZ')`))
|
||||||
|
case TimezExpression:
|
||||||
|
return CustomExpression(Token("'0000-01-01T' || to_char('2000-10-10'::date + "), e, Token(`, 'HH24:MI:SS.USTZH:TZM')`))
|
||||||
|
case TimestampExpression:
|
||||||
|
return CustomExpression(Token("to_char("), e, Token(`, 'YYYY-MM-DD"T"HH24:MI:SS.USZ')`))
|
||||||
|
case DateExpression:
|
||||||
|
return CustomExpression(Token("to_char("), e, Token(`::timestamp, 'YYYY-MM-DD') || 'T00:00:00Z'`))
|
||||||
|
}
|
||||||
|
return expr
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
return jet.NewDialect(dialectParams)
|
return jet.NewDialect(dialectParams)
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,9 @@ type TimestampzExpression = jet.TimestampzExpression
|
||||||
// RowExpression interface
|
// RowExpression interface
|
||||||
type RowExpression = jet.RowExpression
|
type RowExpression = jet.RowExpression
|
||||||
|
|
||||||
|
// IntervalExpression interface
|
||||||
|
type IntervalExpression = jet.IntervalExpression
|
||||||
|
|
||||||
// DateRange Expression interface
|
// DateRange Expression interface
|
||||||
type DateRange = jet.Range[DateExpression]
|
type DateRange = jet.Range[DateExpression]
|
||||||
|
|
||||||
|
|
@ -109,6 +112,11 @@ var TimestampExp = jet.TimestampExp
|
||||||
// Does not add sql cast to generated sql builder output.
|
// Does not add sql cast to generated sql builder output.
|
||||||
var TimestampzExp = jet.TimestampzExp
|
var TimestampzExp = jet.TimestampzExp
|
||||||
|
|
||||||
|
// IntervalExp is interval expression wrapper around arbitrary expression.
|
||||||
|
// Allows go compiler to see any expression as interval expression.
|
||||||
|
// Does not add sql cast to generated sql builder output.
|
||||||
|
var IntervalExp = jet.IntervalExp
|
||||||
|
|
||||||
// RowExp serves as a wrapper for an arbitrary expression, treating it as a row expression.
|
// RowExp serves as a wrapper for an arbitrary expression, treating it as a row expression.
|
||||||
// This enables the Go compiler to interpret any expression as a row expression
|
// This enables the Go compiler to interpret any expression as a row expression
|
||||||
// Note: This does not modify the generated SQL builder output by adding a SQL CAST operation.
|
// Note: This does not modify the generated SQL builder output by adding a SQL CAST operation.
|
||||||
|
|
|
||||||
|
|
@ -194,18 +194,18 @@ func CONCAT_WS(separator Expression, expressions ...Expression) StringExpression
|
||||||
|
|
||||||
// Character encodings for CONVERT, CONVERT_FROM and CONVERT_TO functions
|
// Character encodings for CONVERT, CONVERT_FROM and CONVERT_TO functions
|
||||||
var (
|
var (
|
||||||
UTF8 = String("UTF8")
|
UTF8 = StringExp(jet.FixedLiteral("UTF8"))
|
||||||
LATIN1 = String("LATIN1")
|
LATIN1 = StringExp(jet.FixedLiteral("LATIN1"))
|
||||||
LATIN2 = String("LATIN2")
|
LATIN2 = StringExp(jet.FixedLiteral("LATIN2"))
|
||||||
LATIN3 = String("LATIN3")
|
LATIN3 = StringExp(jet.FixedLiteral("LATIN3"))
|
||||||
LATIN4 = String("LATIN4")
|
LATIN4 = StringExp(jet.FixedLiteral("LATIN4"))
|
||||||
WIN1252 = String("WIN1252")
|
WIN1252 = StringExp(jet.FixedLiteral("WIN1252"))
|
||||||
ISO_8859_5 = String("ISO_8859_5")
|
ISO_8859_5 = StringExp(jet.FixedLiteral("ISO_8859_5"))
|
||||||
ISO_8859_6 = String("ISO_8859_6")
|
ISO_8859_6 = StringExp(jet.FixedLiteral("ISO_8859_6"))
|
||||||
ISO_8859_7 = String("ISO_8859_7")
|
ISO_8859_7 = StringExp(jet.FixedLiteral("ISO_8859_7"))
|
||||||
ISO_8859_8 = String("ISO_8859_8")
|
ISO_8859_8 = StringExp(jet.FixedLiteral("ISO_8859_8"))
|
||||||
KOI8R = String("KOI8R")
|
KOI8R = StringExp(jet.FixedLiteral("KOI8R"))
|
||||||
KOI8U = String("KOI8U")
|
KOI8U = StringExp(jet.FixedLiteral("KOI8U"))
|
||||||
)
|
)
|
||||||
|
|
||||||
// CONVERT converts string to dest_encoding. The original encoding is
|
// CONVERT converts string to dest_encoding. The original encoding is
|
||||||
|
|
@ -223,9 +223,9 @@ var CONVERT_TO = jet.CONVERT_TO
|
||||||
|
|
||||||
// ENCODE/DECODE textual formats
|
// ENCODE/DECODE textual formats
|
||||||
var (
|
var (
|
||||||
Base64 StringExpression = String("base64")
|
Base64 = StringExp(jet.FixedLiteral("base64"))
|
||||||
Escape StringExpression = String("escape")
|
Escape = StringExp(jet.FixedLiteral("escape"))
|
||||||
Hex StringExpression = String("hex")
|
Hex = StringExp(jet.FixedLiteral("hex"))
|
||||||
)
|
)
|
||||||
|
|
||||||
// ENCODE encodes binary data into a textual representation.
|
// ENCODE encodes binary data into a textual representation.
|
||||||
|
|
|
||||||
|
|
@ -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.setOperatorsImpl.stmtRoot = newSelectJson
|
||||||
|
newSelectJson.subQuery.Select.IsForRowToJson = true
|
||||||
|
|
||||||
newSelectJson.setSubQueryAlias("")
|
newSelectJson.setSubQueryAlias("")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,9 @@ package qrm
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/go-jet/jet/v2/internal/3rdparty/json"
|
|
||||||
"github.com/go-jet/jet/v2/internal/utils/must"
|
"github.com/go-jet/jet/v2/internal/utils/must"
|
||||||
"reflect"
|
"reflect"
|
||||||
)
|
)
|
||||||
|
|
@ -67,8 +67,8 @@ func QueryJsonArr(ctx context.Context, db Queryable, query string, args []interf
|
||||||
return queryJson(ctx, db, query, args, destPtr)
|
return queryJson(ctx, db, query, args, destPtr)
|
||||||
}
|
}
|
||||||
|
|
||||||
var jsonDestObjErr = "jet: destination has to be a pointer to struct or pointer to map[string]any"
|
var jsonDestObjErr = "jet: SELECT_JSON_OBJ destination has to be a pointer to struct or pointer to map[string]any"
|
||||||
var jsonDestArrErr = "jet: destination has to be a pointer to slice of struct or pointer to []map[string]any"
|
var jsonDestArrErr = "jet: SELECT_JSON_ARR destination has to be a pointer to slice of struct or pointer to []map[string]any"
|
||||||
|
|
||||||
func queryJson(ctx context.Context, db Queryable, query string, args []interface{}, destPtr interface{}) (rowsProcessed int64, err error) {
|
func queryJson(ctx context.Context, db Queryable, query string, args []interface{}, destPtr interface{}) (rowsProcessed int64, err error) {
|
||||||
must.BeInitializedPtr(db, "jet: db is nil")
|
must.BeInitializedPtr(db, "jet: db is nil")
|
||||||
|
|
|
||||||
|
|
@ -43,29 +43,82 @@ func TestAllTypesJSON(t *testing.T) {
|
||||||
AllTypes.JSONPtr,
|
AllTypes.JSONPtr,
|
||||||
AllTypes.Bit,
|
AllTypes.Bit,
|
||||||
AllTypes.BitPtr,
|
AllTypes.BitPtr,
|
||||||
AllTypes.Blob,
|
|
||||||
AllTypes.BlobPtr,
|
|
||||||
AllTypes.Binary,
|
|
||||||
AllTypes.BinaryPtr,
|
|
||||||
AllTypes.VarBinary,
|
|
||||||
AllTypes.VarBinaryPtr,
|
|
||||||
),
|
),
|
||||||
CAST(AllTypes.JSON).AS_CHAR().AS("Json"),
|
CAST(AllTypes.JSON).AS_CHAR().AS("Json"),
|
||||||
CAST(AllTypes.JSONPtr).AS_CHAR().AS("JsonPtr"),
|
CAST(AllTypes.JSONPtr).AS_CHAR().AS("JsonPtr"),
|
||||||
CAST(AllTypes.Bit).AS_CHAR().AS("Bit"),
|
CAST(AllTypes.Bit).AS_CHAR().AS("Bit"),
|
||||||
CAST(AllTypes.BitPtr).AS_CHAR().AS("BitPtr"),
|
CAST(AllTypes.BitPtr).AS_CHAR().AS("BitPtr"),
|
||||||
|
|
||||||
// TODO: remove when binary string is implemented
|
|
||||||
CONCAT(String("\\x"), HEX(AllTypes.Blob)).AS("Blob"),
|
|
||||||
CONCAT(String("\\x"), HEX(AllTypes.BlobPtr)).AS("BlobPtr"),
|
|
||||||
|
|
||||||
CONCAT(String("\\x"), HEX(AllTypes.Binary)).AS("Binary"),
|
|
||||||
CONCAT(String("\\x"), HEX(AllTypes.BinaryPtr)).AS("BinaryPtr"),
|
|
||||||
|
|
||||||
CONCAT(String("\\x"), HEX(AllTypes.VarBinary)).AS("VarBinary"),
|
|
||||||
CONCAT(String("\\x"), HEX(AllTypes.VarBinaryPtr)).AS("VarBinaryPtr"),
|
|
||||||
).FROM(AllTypes)
|
).FROM(AllTypes)
|
||||||
|
|
||||||
|
testutils.AssertStatementSql(t, stmt, strings.ReplaceAll(`
|
||||||
|
SELECT JSON_ARRAYAGG(JSON_OBJECT(
|
||||||
|
'id', all_types.id,
|
||||||
|
'boolean', all_types.boolean = 1,
|
||||||
|
'booleanPtr', all_types.boolean_ptr = 1,
|
||||||
|
'tinyInt', all_types.tiny_int,
|
||||||
|
'uTinyInt', all_types.u_tiny_int,
|
||||||
|
'smallInt', all_types.small_int,
|
||||||
|
'uSmallInt', all_types.u_small_int,
|
||||||
|
'mediumInt', all_types.medium_int,
|
||||||
|
'uMediumInt', all_types.u_medium_int,
|
||||||
|
'integer', all_types.''integer'',
|
||||||
|
'uInteger', all_types.u_integer,
|
||||||
|
'bigInt', all_types.big_int,
|
||||||
|
'uBigInt', all_types.u_big_int,
|
||||||
|
'tinyIntPtr', all_types.tiny_int_ptr,
|
||||||
|
'uTinyIntPtr', all_types.u_tiny_int_ptr,
|
||||||
|
'smallIntPtr', all_types.small_int_ptr,
|
||||||
|
'uSmallIntPtr', all_types.u_small_int_ptr,
|
||||||
|
'mediumIntPtr', all_types.medium_int_ptr,
|
||||||
|
'uMediumIntPtr', all_types.u_medium_int_ptr,
|
||||||
|
'integerPtr', all_types.integer_ptr,
|
||||||
|
'uIntegerPtr', all_types.u_integer_ptr,
|
||||||
|
'bigIntPtr', all_types.big_int_ptr,
|
||||||
|
'uBigIntPtr', all_types.u_big_int_ptr,
|
||||||
|
'decimal', all_types.''decimal'',
|
||||||
|
'decimalPtr', all_types.decimal_ptr,
|
||||||
|
'numeric', all_types.''numeric'',
|
||||||
|
'numericPtr', all_types.numeric_ptr,
|
||||||
|
'float', all_types.''float'',
|
||||||
|
'floatPtr', all_types.float_ptr,
|
||||||
|
'double', all_types.''double'',
|
||||||
|
'doublePtr', all_types.double_ptr,
|
||||||
|
'real', all_types.''real'',
|
||||||
|
'realPtr', all_types.real_ptr,
|
||||||
|
'time', CONCAT('0000-01-01T', DATE_FORMAT(all_types.time,'%H:%i:%s.%fZ')),
|
||||||
|
'timePtr', CONCAT('0000-01-01T', DATE_FORMAT(all_types.time_ptr,'%H:%i:%s.%fZ')),
|
||||||
|
'date', CONCAT(DATE_FORMAT(all_types.date,'%Y-%m-%d'), 'T00:00:00Z'),
|
||||||
|
'datePtr', CONCAT(DATE_FORMAT(all_types.date_ptr,'%Y-%m-%d'), 'T00:00:00Z'),
|
||||||
|
'dateTime', DATE_FORMAT(all_types.date_time,'%Y-%m-%dT%H:%i:%s.%fZ'),
|
||||||
|
'dateTimePtr', DATE_FORMAT(all_types.date_time_ptr,'%Y-%m-%dT%H:%i:%s.%fZ'),
|
||||||
|
'timestamp', DATE_FORMAT(all_types.timestamp,'%Y-%m-%dT%H:%i:%s.%fZ'),
|
||||||
|
'timestampPtr', DATE_FORMAT(all_types.timestamp_ptr,'%Y-%m-%dT%H:%i:%s.%fZ'),
|
||||||
|
'year', all_types.year,
|
||||||
|
'yearPtr', all_types.year_ptr,
|
||||||
|
'char', all_types.''char'',
|
||||||
|
'charPtr', all_types.char_ptr,
|
||||||
|
'varChar', all_types.var_char,
|
||||||
|
'varCharPtr', all_types.var_char_ptr,
|
||||||
|
'binary', TO_BASE64(all_types.''binary''),
|
||||||
|
'binaryPtr', TO_BASE64(all_types.binary_ptr),
|
||||||
|
'varBinary', TO_BASE64(all_types.var_binary),
|
||||||
|
'varBinaryPtr', TO_BASE64(all_types.var_binary_ptr),
|
||||||
|
'blob', TO_BASE64(all_types.''blob''),
|
||||||
|
'blobPtr', TO_BASE64(all_types.blob_ptr),
|
||||||
|
'text', all_types.text,
|
||||||
|
'textPtr', all_types.text_ptr,
|
||||||
|
'enum', all_types.enum,
|
||||||
|
'enumPtr', all_types.enum_ptr,
|
||||||
|
'set', all_types.''set'',
|
||||||
|
'setPtr', all_types.set_ptr,
|
||||||
|
'Json', CAST(all_types.json AS CHAR),
|
||||||
|
'JsonPtr', CAST(all_types.json_ptr AS CHAR),
|
||||||
|
'Bit', CAST(all_types.bit AS CHAR),
|
||||||
|
'BitPtr', CAST(all_types.bit_ptr AS CHAR)
|
||||||
|
)) AS "json"
|
||||||
|
FROM test_sample.all_types;
|
||||||
|
`, "''", "`"))
|
||||||
|
|
||||||
var dest []model.AllTypes
|
var dest []model.AllTypes
|
||||||
|
|
||||||
err := stmt.QueryJSON(ctx, db, &dest)
|
err := stmt.QueryJSON(ctx, db, &dest)
|
||||||
|
|
@ -76,7 +129,6 @@ func TestAllTypesJSON(t *testing.T) {
|
||||||
dest[0].FloatPtr = ptr.Of(3.33)
|
dest[0].FloatPtr = ptr.Of(3.33)
|
||||||
dest[1].Float = 3.33
|
dest[1].Float = 3.33
|
||||||
|
|
||||||
//fmt.Println(allTypesJson)
|
|
||||||
testutils.AssertJSON(t, dest, allTypesJson)
|
testutils.AssertJSON(t, dest, allTypesJson)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1182,6 +1234,118 @@ func TestAllTypesInsertOnDuplicateKeyUpdate(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAllTypesSubQueryFrom(t *testing.T) {
|
||||||
|
subQuery := SELECT(
|
||||||
|
AllTypes.Boolean,
|
||||||
|
AllTypes.Integer,
|
||||||
|
AllTypes.Double,
|
||||||
|
AllTypes.Text,
|
||||||
|
AllTypes.Date,
|
||||||
|
AllTypes.Time,
|
||||||
|
AllTypes.Timestamp,
|
||||||
|
AllTypes.Blob,
|
||||||
|
).FROM(
|
||||||
|
AllTypes,
|
||||||
|
).AsTable("sub_query")
|
||||||
|
|
||||||
|
stmt := SELECT(
|
||||||
|
AllTypes.Boolean.From(subQuery),
|
||||||
|
AllTypes.Integer.From(subQuery),
|
||||||
|
AllTypes.Double.From(subQuery),
|
||||||
|
AllTypes.Text.From(subQuery),
|
||||||
|
AllTypes.Date.From(subQuery),
|
||||||
|
AllTypes.Time.From(subQuery),
|
||||||
|
AllTypes.Timestamp.From(subQuery),
|
||||||
|
AllTypes.Blob.From(subQuery),
|
||||||
|
).FROM(
|
||||||
|
subQuery,
|
||||||
|
)
|
||||||
|
|
||||||
|
testutils.AssertStatementSql(t, stmt, strings.ReplaceAll(`
|
||||||
|
SELECT sub_query.''all_types.boolean'' AS "all_types.boolean",
|
||||||
|
sub_query.''all_types.integer'' AS "all_types.integer",
|
||||||
|
sub_query.''all_types.double'' AS "all_types.double",
|
||||||
|
sub_query.''all_types.text'' AS "all_types.text",
|
||||||
|
sub_query.''all_types.date'' AS "all_types.date",
|
||||||
|
sub_query.''all_types.time'' AS "all_types.time",
|
||||||
|
sub_query.''all_types.timestamp'' AS "all_types.timestamp",
|
||||||
|
sub_query.''all_types.blob'' AS "all_types.blob"
|
||||||
|
FROM (
|
||||||
|
SELECT all_types.boolean AS "all_types.boolean",
|
||||||
|
all_types.''integer'' AS "all_types.integer",
|
||||||
|
all_types.''double'' AS "all_types.double",
|
||||||
|
all_types.text AS "all_types.text",
|
||||||
|
all_types.date AS "all_types.date",
|
||||||
|
all_types.time AS "all_types.time",
|
||||||
|
all_types.timestamp AS "all_types.timestamp",
|
||||||
|
all_types.''blob'' AS "all_types.blob"
|
||||||
|
FROM test_sample.all_types
|
||||||
|
) AS sub_query;
|
||||||
|
`, "''", "`"))
|
||||||
|
|
||||||
|
var dest []model.AllTypes
|
||||||
|
|
||||||
|
err := stmt.Query(db, &dest)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotEmpty(t, dest)
|
||||||
|
|
||||||
|
t.Run("using SELECT_JSON", func(t *testing.T) {
|
||||||
|
stmtJson := SELECT_JSON_ARR(
|
||||||
|
AllTypes.Boolean.From(subQuery),
|
||||||
|
AllTypes.Integer.From(subQuery),
|
||||||
|
AllTypes.Double.From(subQuery),
|
||||||
|
AllTypes.Text.From(subQuery),
|
||||||
|
AllTypes.Date.From(subQuery),
|
||||||
|
AllTypes.Time.From(subQuery),
|
||||||
|
AllTypes.Timestamp.From(subQuery),
|
||||||
|
AllTypes.Blob.From(subQuery),
|
||||||
|
).FROM(
|
||||||
|
subQuery,
|
||||||
|
)
|
||||||
|
|
||||||
|
testutils.AssertDebugStatementSql(t, stmtJson, strings.ReplaceAll(`
|
||||||
|
SELECT JSON_ARRAYAGG(JSON_OBJECT(
|
||||||
|
'boolean', sub_query.''all_types.boolean'' = 1,
|
||||||
|
'integer', sub_query.''all_types.integer'',
|
||||||
|
'double', sub_query.''all_types.double'',
|
||||||
|
'text', sub_query.''all_types.text'',
|
||||||
|
'date', CONCAT(DATE_FORMAT(sub_query.''all_types.date'','%Y-%m-%d'), 'T00:00:00Z'),
|
||||||
|
'time', CONCAT('0000-01-01T', DATE_FORMAT(sub_query.''all_types.time'','%H:%i:%s.%fZ')),
|
||||||
|
'timestamp', DATE_FORMAT(sub_query.''all_types.timestamp'','%Y-%m-%dT%H:%i:%s.%fZ'),
|
||||||
|
'blob', TO_BASE64(sub_query.''all_types.blob'')
|
||||||
|
)) AS "json"
|
||||||
|
FROM (
|
||||||
|
SELECT all_types.boolean AS "all_types.boolean",
|
||||||
|
all_types.''integer'' AS "all_types.integer",
|
||||||
|
all_types.''double'' AS "all_types.double",
|
||||||
|
all_types.text AS "all_types.text",
|
||||||
|
all_types.date AS "all_types.date",
|
||||||
|
all_types.time AS "all_types.time",
|
||||||
|
all_types.timestamp AS "all_types.timestamp",
|
||||||
|
all_types.''blob'' AS "all_types.blob"
|
||||||
|
FROM test_sample.all_types
|
||||||
|
) AS sub_query;
|
||||||
|
`, "''", "`"))
|
||||||
|
|
||||||
|
var destJson []model.AllTypes
|
||||||
|
|
||||||
|
err := stmtJson.QueryJSON(ctx, db, &destJson)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
t.Run("using AllColumns()", func(t *testing.T) {
|
||||||
|
stmtJsonAllColumns := SELECT_JSON_ARR(
|
||||||
|
subQuery.AllColumns(),
|
||||||
|
).FROM(
|
||||||
|
subQuery,
|
||||||
|
)
|
||||||
|
|
||||||
|
require.Equal(t, stmtJson.DebugSql(), stmtJsonAllColumns.DebugSql())
|
||||||
|
})
|
||||||
|
|
||||||
|
testutils.AssertJsonEqual(t, dest, destJson)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
var toInsert = model.AllTypes{
|
var toInsert = model.AllTypes{
|
||||||
Boolean: false,
|
Boolean: false,
|
||||||
BooleanPtr: ptr.Of(true),
|
BooleanPtr: ptr.Of(true),
|
||||||
|
|
|
||||||
|
|
@ -23,10 +23,12 @@ func TestSelectJsonObj(t *testing.T) {
|
||||||
WHERE(Actor.ActorID.EQ(Int(2)))
|
WHERE(Actor.ActorID.EQ(Int(2)))
|
||||||
|
|
||||||
testutils.AssertStatementSql(t, stmt, `
|
testutils.AssertStatementSql(t, stmt, `
|
||||||
SELECT JSON_OBJECT('actorID', actor.actor_id,
|
SELECT JSON_OBJECT(
|
||||||
|
'actorID', actor.actor_id,
|
||||||
'firstName', actor.first_name,
|
'firstName', actor.first_name,
|
||||||
'lastName', actor.last_name,
|
'lastName', actor.last_name,
|
||||||
'lastUpdate', actor.last_update) AS "json"
|
'lastUpdate', DATE_FORMAT(actor.last_update,'%Y-%m-%dT%H:%i:%s.%fZ')
|
||||||
|
) AS "json"
|
||||||
FROM dvds.actor
|
FROM dvds.actor
|
||||||
WHERE actor.actor_id = ?;
|
WHERE actor.actor_id = ?;
|
||||||
`, int64(2))
|
`, int64(2))
|
||||||
|
|
@ -57,12 +59,14 @@ func TestSelectJsonObj_NestedObj(t *testing.T) {
|
||||||
)
|
)
|
||||||
|
|
||||||
testutils.AssertStatementSql(t, stmt, `
|
testutils.AssertStatementSql(t, stmt, `
|
||||||
SELECT JSON_OBJECT('actorID', actor.actor_id,
|
SELECT JSON_OBJECT(
|
||||||
|
'actorID', actor.actor_id,
|
||||||
'firstName', actor.first_name,
|
'firstName', actor.first_name,
|
||||||
'lastName', actor.last_name,
|
'lastName', actor.last_name,
|
||||||
'lastUpdate', actor.last_update,
|
'lastUpdate', DATE_FORMAT(actor.last_update,'%Y-%m-%dT%H:%i:%s.%fZ'),
|
||||||
'LongestFilm', (
|
'LongestFilm', (
|
||||||
SELECT JSON_OBJECT('filmID', film.film_id,
|
SELECT JSON_OBJECT(
|
||||||
|
'filmID', film.film_id,
|
||||||
'title', film.title,
|
'title', film.title,
|
||||||
'description', film.description,
|
'description', film.description,
|
||||||
'releaseYear', film.release_year,
|
'releaseYear', film.release_year,
|
||||||
|
|
@ -74,13 +78,15 @@ SELECT JSON_OBJECT('actorID', actor.actor_id,
|
||||||
'replacementCost', film.replacement_cost,
|
'replacementCost', film.replacement_cost,
|
||||||
'rating', film.rating,
|
'rating', film.rating,
|
||||||
'specialFeatures', film.special_features,
|
'specialFeatures', film.special_features,
|
||||||
'lastUpdate', film.last_update) AS "json"
|
'lastUpdate', DATE_FORMAT(film.last_update,'%Y-%m-%dT%H:%i:%s.%fZ')
|
||||||
|
) AS "json"
|
||||||
FROM dvds.film_actor
|
FROM dvds.film_actor
|
||||||
INNER JOIN dvds.film ON (film.film_id = film_actor.film_id)
|
INNER JOIN dvds.film ON (film.film_id = film_actor.film_id)
|
||||||
WHERE actor.actor_id = film_actor.actor_id
|
WHERE actor.actor_id = film_actor.actor_id
|
||||||
ORDER BY film.length DESC
|
ORDER BY film.length DESC
|
||||||
LIMIT ?
|
LIMIT ?
|
||||||
)) AS "json"
|
)
|
||||||
|
) AS "json"
|
||||||
FROM dvds.actor
|
FROM dvds.actor
|
||||||
WHERE actor.actor_id = ?;
|
WHERE actor.actor_id = ?;
|
||||||
`)
|
`)
|
||||||
|
|
@ -125,10 +131,12 @@ func TestSelectJsonArr(t *testing.T) {
|
||||||
ORDER_BY(Actor.ActorID)
|
ORDER_BY(Actor.ActorID)
|
||||||
|
|
||||||
testutils.AssertDebugStatementSql(t, stmt, `
|
testutils.AssertDebugStatementSql(t, stmt, `
|
||||||
SELECT JSON_ARRAYAGG(JSON_OBJECT('actorID', actor.actor_id,
|
SELECT JSON_ARRAYAGG(JSON_OBJECT(
|
||||||
|
'actorID', actor.actor_id,
|
||||||
'firstName', actor.first_name,
|
'firstName', actor.first_name,
|
||||||
'lastName', actor.last_name,
|
'lastName', actor.last_name,
|
||||||
'lastUpdate', actor.last_update)) AS "json"
|
'lastUpdate', DATE_FORMAT(actor.last_update,'%Y-%m-%dT%H:%i:%s.%fZ')
|
||||||
|
)) AS "json"
|
||||||
FROM dvds.actor
|
FROM dvds.actor
|
||||||
ORDER BY actor.actor_id;
|
ORDER BY actor.actor_id;
|
||||||
`)
|
`)
|
||||||
|
|
@ -169,12 +177,14 @@ func TestSelectJsonArr_NestedArr(t *testing.T) {
|
||||||
)
|
)
|
||||||
|
|
||||||
testutils.AssertDebugStatementSql(t, stmt, `
|
testutils.AssertDebugStatementSql(t, stmt, `
|
||||||
SELECT JSON_ARRAYAGG(JSON_OBJECT('actorID', actor.actor_id,
|
SELECT JSON_ARRAYAGG(JSON_OBJECT(
|
||||||
|
'actorID', actor.actor_id,
|
||||||
'firstName', actor.first_name,
|
'firstName', actor.first_name,
|
||||||
'lastName', actor.last_name,
|
'lastName', actor.last_name,
|
||||||
'lastUpdate', actor.last_update,
|
'lastUpdate', DATE_FORMAT(actor.last_update,'%Y-%m-%dT%H:%i:%s.%fZ'),
|
||||||
'Films', (
|
'Films', (
|
||||||
SELECT JSON_ARRAYAGG(JSON_OBJECT('filmID', film.film_id,
|
SELECT JSON_ARRAYAGG(JSON_OBJECT(
|
||||||
|
'filmID', film.film_id,
|
||||||
'title', film.title,
|
'title', film.title,
|
||||||
'description', film.description,
|
'description', film.description,
|
||||||
'releaseYear', film.release_year,
|
'releaseYear', film.release_year,
|
||||||
|
|
@ -186,12 +196,14 @@ SELECT JSON_ARRAYAGG(JSON_OBJECT('actorID', actor.actor_id,
|
||||||
'replacementCost', film.replacement_cost,
|
'replacementCost', film.replacement_cost,
|
||||||
'rating', film.rating,
|
'rating', film.rating,
|
||||||
'specialFeatures', film.special_features,
|
'specialFeatures', film.special_features,
|
||||||
'lastUpdate', film.last_update)) AS "json"
|
'lastUpdate', DATE_FORMAT(film.last_update,'%Y-%m-%dT%H:%i:%s.%fZ')
|
||||||
|
)) AS "json"
|
||||||
FROM dvds.film_actor
|
FROM dvds.film_actor
|
||||||
INNER JOIN dvds.film ON ((film.film_id = film_actor.film_id) AND (actor.actor_id = film_actor.actor_id))
|
INNER JOIN dvds.film ON ((film.film_id = film_actor.film_id) AND (actor.actor_id = film_actor.actor_id))
|
||||||
WHERE (film.film_id % 17) = 0
|
WHERE (film.film_id % 17) = 0
|
||||||
ORDER BY film.length DESC
|
ORDER BY film.length DESC
|
||||||
))) AS "json"
|
)
|
||||||
|
)) AS "json"
|
||||||
FROM dvds.actor
|
FROM dvds.actor
|
||||||
WHERE actor.actor_id BETWEEN 1 AND 3
|
WHERE actor.actor_id BETWEEN 1 AND 3
|
||||||
ORDER BY actor.actor_id;
|
ORDER BY actor.actor_id;
|
||||||
|
|
@ -337,22 +349,26 @@ func TestSelectJson_GroupBy(t *testing.T) {
|
||||||
).FROM(subQuery)
|
).FROM(subQuery)
|
||||||
|
|
||||||
testutils.AssertDebugStatementSql(t, stmt, strings.ReplaceAll(`
|
testutils.AssertDebugStatementSql(t, stmt, strings.ReplaceAll(`
|
||||||
SELECT JSON_ARRAYAGG(JSON_OBJECT('customerID', customers_info.""customer.customer_id"",
|
SELECT JSON_ARRAYAGG(JSON_OBJECT(
|
||||||
'storeID', customers_info.""customer.store_id"",
|
'customerID', customers_info.''customer.customer_id'',
|
||||||
'firstName', customers_info.""customer.first_name"",
|
'storeID', customers_info.''customer.store_id'',
|
||||||
'lastName', customers_info.""customer.last_name"",
|
'firstName', customers_info.''customer.first_name'',
|
||||||
'email', customers_info.""customer.email"",
|
'lastName', customers_info.''customer.last_name'',
|
||||||
'addressID', customers_info.""customer.address_id"",
|
'email', customers_info.''customer.email'',
|
||||||
'active', customers_info.""customer.active"",
|
'addressID', customers_info.''customer.address_id'',
|
||||||
'createDate', customers_info.""customer.create_date"",
|
'active', customers_info.''customer.active'' = 1,
|
||||||
'lastUpdate', customers_info.""customer.last_update"",
|
'createDate', DATE_FORMAT(customers_info.''customer.create_date'','%Y-%m-%dT%H:%i:%s.%fZ'),
|
||||||
|
'lastUpdate', DATE_FORMAT(customers_info.''customer.last_update'','%Y-%m-%dT%H:%i:%s.%fZ'),
|
||||||
'amount', (
|
'amount', (
|
||||||
SELECT JSON_OBJECT('sum', customers_info.sum,
|
SELECT JSON_OBJECT(
|
||||||
|
'sum', customers_info.sum,
|
||||||
'avg', customers_info.avg,
|
'avg', customers_info.avg,
|
||||||
'max', customers_info.max,
|
'max', customers_info.max,
|
||||||
'min', customers_info.min,
|
'min', customers_info.min,
|
||||||
'count', customers_info.count) AS "json"
|
'count', customers_info.count
|
||||||
))) AS "json"
|
) AS "json"
|
||||||
|
)
|
||||||
|
)) AS "json"
|
||||||
FROM (
|
FROM (
|
||||||
SELECT customer.customer_id AS "customer.customer_id",
|
SELECT customer.customer_id AS "customer.customer_id",
|
||||||
customer.store_id AS "customer.store_id",
|
customer.store_id AS "customer.store_id",
|
||||||
|
|
@ -374,7 +390,7 @@ FROM (
|
||||||
HAVING SUM(payment.amount) > 125
|
HAVING SUM(payment.amount) > 125
|
||||||
ORDER BY customer.customer_id, SUM(payment.amount) ASC
|
ORDER BY customer.customer_id, SUM(payment.amount) ASC
|
||||||
) AS customers_info;
|
) AS customers_info;
|
||||||
`, `""`, "`"))
|
`, "''", "`"))
|
||||||
|
|
||||||
var dest []struct {
|
var dest []struct {
|
||||||
model.Customer
|
model.Customer
|
||||||
|
|
@ -389,7 +405,6 @@ FROM (
|
||||||
}
|
}
|
||||||
|
|
||||||
err := stmt.QueryJSON(ctx, db, &dest)
|
err := stmt.QueryJSON(ctx, db, &dest)
|
||||||
fmt.Println(err)
|
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
|
|
||||||
testutils.AssertJSONFile(t, dest, "./testdata/results/mysql/customer_payment_sum.json")
|
testutils.AssertJSONFile(t, dest, "./testdata/results/mysql/customer_payment_sum.json")
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,10 @@ package postgres
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
"github.com/go-jet/jet/v2/internal/utils/ptr"
|
"github.com/go-jet/jet/v2/internal/utils/ptr"
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
|
|
||||||
"github.com/go-jet/jet/v2/qrm"
|
"github.com/go-jet/jet/v2/qrm"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
|
@ -46,6 +46,7 @@ func TestAllTypesSelectJson(t *testing.T) {
|
||||||
AllTypes.JsonbArray, AllTypes.IntegerArray, AllTypes.IntegerArrayPtr,
|
AllTypes.JsonbArray, AllTypes.IntegerArray, AllTypes.IntegerArrayPtr,
|
||||||
AllTypes.TextMultiDimArray, AllTypes.TextMultiDimArrayPtr,
|
AllTypes.TextMultiDimArray, AllTypes.TextMultiDimArrayPtr,
|
||||||
),
|
),
|
||||||
|
// unsupported at the moment, casting to text allows these columns to be assigned to string fields
|
||||||
CAST(AllTypes.JSONPtr).AS_TEXT().AS("jsonPtr"),
|
CAST(AllTypes.JSONPtr).AS_TEXT().AS("jsonPtr"),
|
||||||
CAST(AllTypes.JSON).AS_TEXT().AS("JSON"),
|
CAST(AllTypes.JSON).AS_TEXT().AS("JSON"),
|
||||||
CAST(AllTypes.JsonbPtr).AS_TEXT().AS("jsonbPtr"),
|
CAST(AllTypes.JsonbPtr).AS_TEXT().AS("jsonbPtr"),
|
||||||
|
|
@ -59,7 +60,75 @@ func TestAllTypesSelectJson(t *testing.T) {
|
||||||
CAST(AllTypes.TextMultiDimArrayPtr).AS_TEXT().AS("TextMultiDimArrayPtr"),
|
CAST(AllTypes.TextMultiDimArrayPtr).AS_TEXT().AS("TextMultiDimArrayPtr"),
|
||||||
).FROM(AllTypes)
|
).FROM(AllTypes)
|
||||||
|
|
||||||
//fmt.Println(stmt.DebugSql())
|
testutils.AssertStatementSql(t, stmt, `
|
||||||
|
SELECT json_agg(row_to_json(records)) AS "json"
|
||||||
|
FROM (
|
||||||
|
SELECT all_types.small_int_ptr AS "smallIntPtr",
|
||||||
|
all_types.small_int AS "smallInt",
|
||||||
|
all_types.integer_ptr AS "integerPtr",
|
||||||
|
all_types.integer AS "integer",
|
||||||
|
all_types.big_int_ptr AS "bigIntPtr",
|
||||||
|
all_types.big_int AS "bigInt",
|
||||||
|
all_types.decimal_ptr AS "decimalPtr",
|
||||||
|
all_types.decimal AS "decimal",
|
||||||
|
all_types.numeric_ptr AS "numericPtr",
|
||||||
|
all_types.numeric AS "numeric",
|
||||||
|
all_types.real_ptr AS "realPtr",
|
||||||
|
all_types.real AS "real",
|
||||||
|
all_types.double_precision_ptr AS "doublePrecisionPtr",
|
||||||
|
all_types.double_precision AS "doublePrecision",
|
||||||
|
all_types.smallserial AS "smallserial",
|
||||||
|
all_types.serial AS "serial",
|
||||||
|
all_types.bigserial AS "bigserial",
|
||||||
|
all_types.var_char_ptr AS "varCharPtr",
|
||||||
|
all_types.var_char AS "varChar",
|
||||||
|
all_types.char_ptr AS "charPtr",
|
||||||
|
all_types.char AS "char",
|
||||||
|
all_types.text_ptr AS "textPtr",
|
||||||
|
all_types.text AS "text",
|
||||||
|
ENCODE(all_types.bytea_ptr, 'base64') AS "byteaPtr",
|
||||||
|
ENCODE(all_types.bytea, 'base64') AS "bytea",
|
||||||
|
all_types.timestampz_ptr AS "timestampzPtr",
|
||||||
|
all_types.timestampz AS "timestampz",
|
||||||
|
to_char(all_types.timestamp_ptr, 'YYYY-MM-DD"T"HH24:MI:SS.USZ') AS "timestampPtr",
|
||||||
|
to_char(all_types.timestamp, 'YYYY-MM-DD"T"HH24:MI:SS.USZ') AS "timestamp",
|
||||||
|
to_char(all_types.date_ptr::timestamp, 'YYYY-MM-DD') || 'T00:00:00Z' AS "datePtr",
|
||||||
|
to_char(all_types.date::timestamp, 'YYYY-MM-DD') || 'T00:00:00Z' AS "date",
|
||||||
|
'0000-01-01T' || to_char('2000-10-10'::date + all_types.timez_ptr, 'HH24:MI:SS.USTZH:TZM') AS "timezPtr",
|
||||||
|
'0000-01-01T' || to_char('2000-10-10'::date + all_types.timez, 'HH24:MI:SS.USTZH:TZM') AS "timez",
|
||||||
|
'0000-01-01T' || to_char('2000-10-10'::date + all_types.time_ptr, 'HH24:MI:SS.USZ') AS "timePtr",
|
||||||
|
'0000-01-01T' || to_char('2000-10-10'::date + all_types.time, 'HH24:MI:SS.USZ') AS "time",
|
||||||
|
all_types.interval_ptr AS "intervalPtr",
|
||||||
|
all_types.interval AS "interval",
|
||||||
|
all_types.boolean_ptr AS "booleanPtr",
|
||||||
|
all_types.boolean AS "boolean",
|
||||||
|
all_types.point_ptr AS "pointPtr",
|
||||||
|
all_types.bit_ptr AS "bitPtr",
|
||||||
|
all_types.bit AS "bit",
|
||||||
|
all_types.bit_varying_ptr AS "bitVaryingPtr",
|
||||||
|
all_types.bit_varying AS "bitVarying",
|
||||||
|
all_types.tsvector_ptr AS "tsvectorPtr",
|
||||||
|
all_types.tsvector AS "tsvector",
|
||||||
|
all_types.uuid_ptr AS "uuidPtr",
|
||||||
|
all_types.uuid AS "uuid",
|
||||||
|
all_types.xml_ptr AS "xmlPtr",
|
||||||
|
all_types.xml AS "xml",
|
||||||
|
all_types.mood_ptr AS "moodPtr",
|
||||||
|
all_types.mood AS "mood",
|
||||||
|
all_types.json_ptr::text AS "jsonPtr",
|
||||||
|
all_types.json::text AS "JSON",
|
||||||
|
all_types.jsonb_ptr::text AS "jsonbPtr",
|
||||||
|
all_types.jsonb::text AS "Jsonb",
|
||||||
|
all_types.text_array_ptr::text AS "TextArrayPtr",
|
||||||
|
all_types.text_array::text AS "TextArray",
|
||||||
|
all_types.jsonb_array::text AS "JsonbArray",
|
||||||
|
all_types.integer_array::text AS "IntegerArray",
|
||||||
|
all_types.integer_array_ptr::text AS "IntegerArrayPtr",
|
||||||
|
all_types.text_multi_dim_array::text AS "TextMultiDimArray",
|
||||||
|
all_types.text_multi_dim_array_ptr::text AS "TextMultiDimArrayPtr"
|
||||||
|
FROM test_sample.all_types
|
||||||
|
) AS records;
|
||||||
|
`)
|
||||||
|
|
||||||
var dest []model.AllTypes
|
var dest []model.AllTypes
|
||||||
|
|
||||||
|
|
@ -76,24 +145,30 @@ func TestAllTypesSelectJson(t *testing.T) {
|
||||||
dest[1].CharPtr = allTypesRow1.CharPtr
|
dest[1].CharPtr = allTypesRow1.CharPtr
|
||||||
}
|
}
|
||||||
|
|
||||||
// set time local before comparison
|
minus8 := time.FixedZone("UTC", -8*60*60)
|
||||||
dest[0].Timestampz = toCET(t, dest[0].Timestampz)
|
plus1 := time.FixedZone("UTC", 60*60)
|
||||||
|
|
||||||
if dest[0].TimestampzPtr != nil {
|
// set time local before comparison
|
||||||
dest[0].TimestampzPtr = ptr.Of(toCET(t, *dest[0].TimestampzPtr))
|
dest[0].Timez = *toTZ(&dest[0].Timez, minus8)
|
||||||
}
|
dest[0].TimezPtr = toTZ(dest[0].TimezPtr, minus8)
|
||||||
dest[1].Timestampz = toCET(t, dest[1].Timestampz)
|
dest[1].Timez = *toTZ(&dest[1].Timez, minus8)
|
||||||
|
dest[1].TimezPtr = toTZ(dest[1].TimezPtr, minus8)
|
||||||
|
|
||||||
|
dest[0].Timestampz = *toTZ(&dest[0].Timestampz, plus1)
|
||||||
|
dest[0].TimestampzPtr = toTZ(dest[0].TimestampzPtr, plus1)
|
||||||
|
dest[1].Timestampz = *toTZ(&dest[1].Timestampz, plus1)
|
||||||
|
dest[1].TimestampzPtr = toTZ(dest[1].TimestampzPtr, plus1)
|
||||||
|
|
||||||
testutils.AssertJsonEqual(t, dest[0], allTypesRow0)
|
testutils.AssertJsonEqual(t, dest[0], allTypesRow0)
|
||||||
testutils.AssertJsonEqual(t, dest[1], allTypesRow1)
|
testutils.AssertJsonEqual(t, dest[1], allTypesRow1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func toCET(t *testing.T, tm time.Time) time.Time {
|
func toTZ(tm *time.Time, loc *time.Location) *time.Time {
|
||||||
cet, err := time.LoadLocation("CET") // "Europe/Berlin" also works
|
if tm == nil {
|
||||||
if err != nil {
|
return nil
|
||||||
t.Fail()
|
|
||||||
}
|
}
|
||||||
return tm.In(cet)
|
|
||||||
|
return ptr.Of(tm.In(loc))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAllTypesViewSelect(t *testing.T) {
|
func TestAllTypesViewSelect(t *testing.T) {
|
||||||
|
|
@ -192,7 +267,7 @@ WHERE all_types.uuid = 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11';
|
||||||
requireLogged(t, query)
|
requireLogged(t, query)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBytea(t *testing.T) {
|
func TestByteaInsert(t *testing.T) {
|
||||||
byteArrHex := "\\x48656c6c6f20476f7068657221"
|
byteArrHex := "\\x48656c6c6f20476f7068657221"
|
||||||
byteArrBin := []byte("\x48\x65\x6c\x6c\x6f\x20\x47\x6f\x70\x68\x65\x72\x21")
|
byteArrBin := []byte("\x48\x65\x6c\x6c\x6f\x20\x47\x6f\x70\x68\x65\x72\x21")
|
||||||
|
|
||||||
|
|
@ -602,22 +677,22 @@ func TestStringOperators(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBlob(t *testing.T) {
|
func TestBytea(t *testing.T) {
|
||||||
|
|
||||||
var sampleBlob = Bytea([]byte{11, 0, 22, 33, 44})
|
var sampleBytea = Bytea([]byte{11, 0, 22, 33, 44})
|
||||||
var textBlob = Bytea([]byte("text blob"))
|
var textBytea = Bytea([]byte("text blob"))
|
||||||
|
|
||||||
stmt := SELECT(
|
stmt := SELECT(
|
||||||
AllTypes.Bytea.EQ(sampleBlob),
|
AllTypes.Bytea.EQ(sampleBytea),
|
||||||
AllTypes.Bytea.EQ(AllTypes.ByteaPtr),
|
AllTypes.Bytea.EQ(AllTypes.ByteaPtr),
|
||||||
AllTypes.Bytea.NOT_EQ(sampleBlob),
|
AllTypes.Bytea.NOT_EQ(sampleBytea),
|
||||||
AllTypes.Bytea.GT(textBlob),
|
AllTypes.Bytea.GT(textBytea),
|
||||||
AllTypes.Bytea.GT_EQ(AllTypes.ByteaPtr),
|
AllTypes.Bytea.GT_EQ(AllTypes.ByteaPtr),
|
||||||
AllTypes.Bytea.LT(AllTypes.ByteaPtr),
|
AllTypes.Bytea.LT(AllTypes.ByteaPtr),
|
||||||
AllTypes.Bytea.LT_EQ(sampleBlob),
|
AllTypes.Bytea.LT_EQ(sampleBytea),
|
||||||
AllTypes.Bytea.BETWEEN(Bytea([]byte("min")), Bytea([]byte("max"))),
|
AllTypes.Bytea.BETWEEN(Bytea([]byte("min")), Bytea([]byte("max"))),
|
||||||
AllTypes.Bytea.NOT_BETWEEN(AllTypes.Bytea, AllTypes.ByteaPtr),
|
AllTypes.Bytea.NOT_BETWEEN(AllTypes.Bytea, AllTypes.ByteaPtr),
|
||||||
AllTypes.Bytea.CONCAT(textBlob),
|
AllTypes.Bytea.CONCAT(textBytea),
|
||||||
|
|
||||||
func() ProjectionList {
|
func() ProjectionList {
|
||||||
if sourceIsCockroachDB() {
|
if sourceIsCockroachDB() {
|
||||||
|
|
@ -629,25 +704,25 @@ func TestBlob(t *testing.T) {
|
||||||
AllTypes.Bytea.NOT_LIKE(Bytea("b'%pattern%'")),
|
AllTypes.Bytea.NOT_LIKE(Bytea("b'%pattern%'")),
|
||||||
|
|
||||||
BTRIM(AllTypes.Bytea, Bytea([]byte{33})),
|
BTRIM(AllTypes.Bytea, Bytea([]byte{33})),
|
||||||
RTRIM(AllTypes.ByteaPtr, sampleBlob),
|
RTRIM(AllTypes.ByteaPtr, sampleBytea),
|
||||||
LTRIM(sampleBlob, textBlob),
|
LTRIM(sampleBytea, textBytea),
|
||||||
CONCAT(sampleBlob, AllTypes.ByteaPtr, textBlob),
|
CONCAT(sampleBytea, AllTypes.ByteaPtr, textBytea),
|
||||||
BIT_COUNT(sampleBlob).EQ(Int(3)),
|
BIT_COUNT(sampleBytea).EQ(Int(3)),
|
||||||
LENGTH(textBlob, UTF8).EQ(Int(4)),
|
LENGTH(textBytea, UTF8).EQ(Int(4)),
|
||||||
|
|
||||||
CONVERT(textBlob, UTF8, WIN1252),
|
CONVERT(textBytea, UTF8, WIN1252),
|
||||||
CONVERT(AllTypes.Bytea, UTF8, LATIN1).EQ(sampleBlob),
|
CONVERT(AllTypes.Bytea, UTF8, LATIN1).EQ(sampleBytea),
|
||||||
}
|
}
|
||||||
}(),
|
}(),
|
||||||
|
|
||||||
BIT_LENGTH(textBlob),
|
BIT_LENGTH(textBytea),
|
||||||
OCTET_LENGTH(textBlob),
|
OCTET_LENGTH(textBytea),
|
||||||
|
|
||||||
GET_BIT(textBlob, Int(2)).EQ(Int(23)),
|
GET_BIT(textBytea, Int(2)).EQ(Int(23)),
|
||||||
GET_BYTE(sampleBlob, Int(1)).EQ(Int(0)),
|
GET_BYTE(sampleBytea, Int(1)).EQ(Int(0)),
|
||||||
SET_BIT(textBlob, Int(1), Int(0)).EQ(sampleBlob),
|
SET_BIT(textBytea, Int(1), Int(0)).EQ(sampleBytea),
|
||||||
SET_BYTE(textBlob, Int(1), Int(0)).EQ(textBlob),
|
SET_BYTE(textBytea, Int(1), Int(0)).EQ(textBytea),
|
||||||
LENGTH(sampleBlob),
|
LENGTH(sampleBytea),
|
||||||
|
|
||||||
SUBSTR(AllTypes.Bytea, Int(0), Int(2)),
|
SUBSTR(AllTypes.Bytea, Int(0), Int(2)),
|
||||||
|
|
||||||
|
|
@ -657,16 +732,16 @@ func TestBlob(t *testing.T) {
|
||||||
SHA384(AllTypes.Bytea),
|
SHA384(AllTypes.Bytea),
|
||||||
SHA512(AllTypes.Bytea),
|
SHA512(AllTypes.Bytea),
|
||||||
|
|
||||||
ENCODE(sampleBlob, Base64),
|
ENCODE(sampleBytea, Base64),
|
||||||
DECODE(String("A234C12B"), Hex).EQ(sampleBlob),
|
DECODE(String("A234C12B"), Hex).EQ(sampleBytea),
|
||||||
|
|
||||||
CONVERT_FROM(AllTypes.ByteaPtr, UTF8).EQ(AllTypes.VarChar),
|
CONVERT_FROM(AllTypes.ByteaPtr, UTF8).EQ(AllTypes.VarChar),
|
||||||
CONVERT_TO(AllTypes.Text, UTF8).NOT_EQ(textBlob),
|
CONVERT_TO(AllTypes.Text, UTF8).NOT_EQ(textBytea),
|
||||||
|
|
||||||
RawBytea("DECODE(#1::text, #2)", RawArgs{
|
RawBytea("DECODE(#1::text, #2)", RawArgs{
|
||||||
"#1": "A234C12B",
|
"#1": "A234C12B",
|
||||||
"#2": "hex",
|
"#2": "hex",
|
||||||
}).EQ(sampleBlob),
|
}).EQ(sampleBytea),
|
||||||
).FROM(
|
).FROM(
|
||||||
AllTypes,
|
AllTypes,
|
||||||
)
|
)
|
||||||
|
|
@ -690,27 +765,27 @@ SELECT all_types.bytea = $1::bytea,
|
||||||
LTRIM($12::bytea, $13::bytea),
|
LTRIM($12::bytea, $13::bytea),
|
||||||
CONCAT($14::bytea, all_types.bytea_ptr, $15::bytea),
|
CONCAT($14::bytea, all_types.bytea_ptr, $15::bytea),
|
||||||
BIT_COUNT($16::bytea) = $17,
|
BIT_COUNT($16::bytea) = $17,
|
||||||
LENGTH($18::bytea, $19::text) = $20,
|
LENGTH($18::bytea, 'UTF8') = $19,
|
||||||
CONVERT($21::bytea, $22::text, $23::text),
|
CONVERT($20::bytea, 'UTF8', 'WIN1252'),
|
||||||
CONVERT(all_types.bytea, $24::text, $25::text) = $26::bytea,
|
CONVERT(all_types.bytea, 'UTF8', 'LATIN1') = $21::bytea,
|
||||||
BIT_LENGTH($27::bytea),
|
BIT_LENGTH($22::bytea),
|
||||||
OCTET_LENGTH($28::bytea),
|
OCTET_LENGTH($23::bytea),
|
||||||
GET_BIT($29::bytea, $30) = $31,
|
GET_BIT($24::bytea, $25) = $26,
|
||||||
GET_BYTE($32::bytea, $33) = $34,
|
GET_BYTE($27::bytea, $28) = $29,
|
||||||
SET_BIT($35::bytea, $36, $37) = $38::bytea,
|
SET_BIT($30::bytea, $31, $32) = $33::bytea,
|
||||||
SET_BYTE($39::bytea, $40, $41) = $42::bytea,
|
SET_BYTE($34::bytea, $35, $36) = $37::bytea,
|
||||||
LENGTH($43::bytea),
|
LENGTH($38::bytea),
|
||||||
SUBSTR(all_types.bytea, $44, $45),
|
SUBSTR(all_types.bytea, $39, $40),
|
||||||
MD5(all_types.bytea),
|
MD5(all_types.bytea),
|
||||||
SHA224(all_types.bytea),
|
SHA224(all_types.bytea),
|
||||||
SHA256(all_types.bytea),
|
SHA256(all_types.bytea),
|
||||||
SHA384(all_types.bytea),
|
SHA384(all_types.bytea),
|
||||||
SHA512(all_types.bytea),
|
SHA512(all_types.bytea),
|
||||||
ENCODE($46::bytea, $47::text),
|
ENCODE($41::bytea, 'base64'),
|
||||||
DECODE($48::text, $49::text) = $50::bytea,
|
DECODE($42::text, 'hex') = $43::bytea,
|
||||||
CONVERT_FROM(all_types.bytea_ptr, $51::text) = all_types.var_char,
|
CONVERT_FROM(all_types.bytea_ptr, 'UTF8') = all_types.var_char,
|
||||||
CONVERT_TO(all_types.text, $52::text) != $53::bytea,
|
CONVERT_TO(all_types.text, 'UTF8') != $44::bytea,
|
||||||
(DECODE($54::text, $55)) = $56::bytea
|
(DECODE($45::text, $46)) = $47::bytea
|
||||||
FROM test_sample.all_types;
|
FROM test_sample.all_types;
|
||||||
`)
|
`)
|
||||||
}
|
}
|
||||||
|
|
@ -727,28 +802,75 @@ func TestBlobConversion(t *testing.T) {
|
||||||
printable := []byte("this is blob")
|
printable := []byte("this is blob")
|
||||||
|
|
||||||
stmt := SELECT(
|
stmt := SELECT(
|
||||||
Bytea(nonPrintable).AS("non_printable"),
|
Bytea(nonPrintable).AS("test_dest.non_printable"),
|
||||||
Bytea(printable).AS("printable"),
|
Bytea(printable).AS("test_dest.printable"),
|
||||||
|
|
||||||
ENCODE(Bytea(nonPrintable), Base64).AS("non_printable_base64"),
|
Bytea(nonPrintable).CONCAT(Bytea(printable)).AS("test_dest.bytea_concat"),
|
||||||
CONVERT_FROM(Bytea(printable), UTF8).AS("printable_utf8"),
|
|
||||||
|
ENCODE(Bytea(nonPrintable), Base64).AS("test_dest.non_printable_base64"),
|
||||||
|
CONVERT_FROM(Bytea(printable), UTF8).AS("test_dest.printable_utf8"),
|
||||||
)
|
)
|
||||||
|
|
||||||
var dest struct {
|
testutils.AssertDebugStatementSql(t, stmt, `
|
||||||
|
SELECT '\x0b16212c37'::bytea AS "test_dest.non_printable",
|
||||||
|
'\x7468697320697320626c6f62'::bytea AS "test_dest.printable",
|
||||||
|
('\x0b16212c37'::bytea || '\x7468697320697320626c6f62'::bytea) AS "test_dest.bytea_concat",
|
||||||
|
ENCODE('\x0b16212c37'::bytea, 'base64') AS "test_dest.non_printable_base64",
|
||||||
|
CONVERT_FROM('\x7468697320697320626c6f62'::bytea, 'UTF8') AS "test_dest.printable_utf8";
|
||||||
|
`)
|
||||||
|
|
||||||
|
type testDest struct {
|
||||||
NonPrintable []byte
|
NonPrintable []byte
|
||||||
Printable []byte
|
Printable []byte
|
||||||
|
|
||||||
NonPrintableBase64 []byte
|
ByteaConcat []byte
|
||||||
|
NonPrintableBase64 string
|
||||||
PrintableUTF8 string
|
PrintableUTF8 string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var dest testDest
|
||||||
|
|
||||||
err := stmt.Query(db, &dest)
|
err := stmt.Query(db, &dest)
|
||||||
|
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, dest.NonPrintable, nonPrintable)
|
require.Equal(t, dest.NonPrintable, nonPrintable)
|
||||||
require.Equal(t, dest.Printable, printable)
|
require.Equal(t, dest.Printable, printable)
|
||||||
require.Equal(t, dest.NonPrintableBase64, []byte(base64.StdEncoding.EncodeToString(nonPrintable)))
|
|
||||||
|
require.Equal(t, dest.ByteaConcat, append(nonPrintable, printable...))
|
||||||
|
require.Equal(t, dest.NonPrintableBase64, base64.StdEncoding.EncodeToString(nonPrintable))
|
||||||
require.Equal(t, dest.PrintableUTF8, string(printable))
|
require.Equal(t, dest.PrintableUTF8, string(printable))
|
||||||
|
|
||||||
|
t.Run("using select json", func(t *testing.T) {
|
||||||
|
stmtJson := SELECT_JSON_OBJ(
|
||||||
|
Bytea(nonPrintable).AS("nonPrintable"),
|
||||||
|
Bytea(printable).AS("printable"),
|
||||||
|
|
||||||
|
Bytea(nonPrintable).CONCAT(Bytea(printable)).AS("byteaConcat"),
|
||||||
|
|
||||||
|
ENCODE(Bytea(nonPrintable), Base64).AS("nonPrintableBase64"),
|
||||||
|
CONVERT_FROM(Bytea(printable), UTF8).AS("printableUtf8"),
|
||||||
|
)
|
||||||
|
|
||||||
|
testutils.AssertStatementSql(t, stmtJson, `
|
||||||
|
SELECT row_to_json(records) AS "json"
|
||||||
|
FROM (
|
||||||
|
SELECT ENCODE($1::bytea, 'base64') AS "nonPrintable",
|
||||||
|
ENCODE($2::bytea, 'base64') AS "printable",
|
||||||
|
ENCODE($3::bytea || $4::bytea, 'base64') AS "byteaConcat",
|
||||||
|
ENCODE($5::bytea, 'base64') AS "nonPrintableBase64",
|
||||||
|
CONVERT_FROM($6::bytea, 'UTF8') AS "printableUtf8"
|
||||||
|
) AS records;
|
||||||
|
`)
|
||||||
|
|
||||||
|
var destSelectJson testDest
|
||||||
|
|
||||||
|
err := stmtJson.QueryJSON(ctx, db, &destSelectJson)
|
||||||
|
require.NoError(t, err)
|
||||||
|
testutils.PrintJson(destSelectJson)
|
||||||
|
|
||||||
|
require.Equal(t, dest, destSelectJson)
|
||||||
|
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBoolOperators(t *testing.T) {
|
func TestBoolOperators(t *testing.T) {
|
||||||
|
|
@ -1140,6 +1262,190 @@ func TestTimeExpression(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestTimeScan(t *testing.T) {
|
||||||
|
loc, err := time.LoadLocation("Japan")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
timeT := time.Date(3, 3, 3, 11, 22, 33, 0, time.UTC)
|
||||||
|
timeWithNanoSeconds := time.Date(3, 3, 3, 1, 2, 3, 1000, time.UTC)
|
||||||
|
|
||||||
|
timez := time.Date(3, 3, 3, 7, 8, 9, 0, time.UTC)
|
||||||
|
timezWithNanoSeconds := time.Date(3, 3, 3, 4, 5, 6, 1000, loc)
|
||||||
|
|
||||||
|
// '1999-01-08 04:05:06'
|
||||||
|
timestamp := time.Date(1999, 01, 8, 4, 5, 6, 0, time.UTC)
|
||||||
|
timestampWithNanoSeconds := time.Date(3, 3, 3, 8, 9, 10, 1000, time.UTC)
|
||||||
|
|
||||||
|
timestampz := time.Date(2003, 10, 3, 9, 10, 11, 0, loc)
|
||||||
|
timestampzWithNanoSeconds := time.Date(3, 3, 3, 8, 9, 10, 1000, loc)
|
||||||
|
|
||||||
|
date := time.Date(2010, 2, 3, 0, 0, 0, 0, time.UTC)
|
||||||
|
|
||||||
|
stmt := SELECT(
|
||||||
|
TimeT(timeT).AS("time"),
|
||||||
|
TimeT(timeWithNanoSeconds).AS("timeWithNanoSeconds"),
|
||||||
|
TimezT(timez).AS("timez"),
|
||||||
|
TimezT(timezWithNanoSeconds).AS("timezWithNanoSeconds"),
|
||||||
|
Timestamp(1999, 01, 8, 4, 5, 6).AS("timestamp"),
|
||||||
|
TimestampT(timestampWithNanoSeconds).AS("timestampWithNanoSeconds"),
|
||||||
|
TimestampzT(timestampz).AS("timestampz"),
|
||||||
|
TimestampzT(timestampzWithNanoSeconds).AS("timestampzWithNanoSeconds"),
|
||||||
|
DateT(date).AS("date"),
|
||||||
|
|
||||||
|
TimeT(timeT).ADD(INTERVAL(2, HOUR)).AS("timeExpression"),
|
||||||
|
|
||||||
|
SELECT_JSON_OBJ(
|
||||||
|
TimeT(timeT).AS("time"),
|
||||||
|
TimeT(timeWithNanoSeconds).AS("timeWithNanoSeconds"),
|
||||||
|
TimezT(timez).AS("timez"),
|
||||||
|
TimezT(timezWithNanoSeconds).AS("timezWithNanoSeconds"),
|
||||||
|
TimestampT(timestamp).AS("timestamp"),
|
||||||
|
TimestampT(timestampWithNanoSeconds).AS("timestampWithNanoSeconds"),
|
||||||
|
TimestampzT(timestampz).AS("timestampz"),
|
||||||
|
TimestampzT(timestampzWithNanoSeconds).AS("timestampzWithNanoSeconds"),
|
||||||
|
DateT(date).AS("date"),
|
||||||
|
|
||||||
|
TimeT(timeT).ADD(INTERVAL(2, HOUR)).AS("timeExpression"),
|
||||||
|
).AS("json"),
|
||||||
|
)
|
||||||
|
|
||||||
|
testutils.AssertStatementSql(t, stmt, `
|
||||||
|
SELECT $1::time without time zone AS "time",
|
||||||
|
$2::time without time zone AS "timeWithNanoSeconds",
|
||||||
|
$3::time with time zone AS "timez",
|
||||||
|
$4::time with time zone AS "timezWithNanoSeconds",
|
||||||
|
$5::timestamp without time zone AS "timestamp",
|
||||||
|
$6::timestamp without time zone AS "timestampWithNanoSeconds",
|
||||||
|
$7::timestamp with time zone AS "timestampz",
|
||||||
|
$8::timestamp with time zone AS "timestampzWithNanoSeconds",
|
||||||
|
$9::date AS "date",
|
||||||
|
($10::time without time zone + INTERVAL '2 HOUR') AS "timeExpression",
|
||||||
|
(
|
||||||
|
SELECT row_to_json(json_records) AS "json_json"
|
||||||
|
FROM (
|
||||||
|
SELECT '0000-01-01T' || to_char('2000-10-10'::date + $11::time without time zone, 'HH24:MI:SS.USZ') AS "time",
|
||||||
|
'0000-01-01T' || to_char('2000-10-10'::date + $12::time without time zone, 'HH24:MI:SS.USZ') AS "timeWithNanoSeconds",
|
||||||
|
'0000-01-01T' || to_char('2000-10-10'::date + $13::time with time zone, 'HH24:MI:SS.USTZH:TZM') AS "timez",
|
||||||
|
'0000-01-01T' || to_char('2000-10-10'::date + $14::time with time zone, 'HH24:MI:SS.USTZH:TZM') AS "timezWithNanoSeconds",
|
||||||
|
to_char($15::timestamp without time zone, 'YYYY-MM-DD"T"HH24:MI:SS.USZ') AS "timestamp",
|
||||||
|
to_char($16::timestamp without time zone, 'YYYY-MM-DD"T"HH24:MI:SS.USZ') AS "timestampWithNanoSeconds",
|
||||||
|
$17::timestamp with time zone AS "timestampz",
|
||||||
|
$18::timestamp with time zone AS "timestampzWithNanoSeconds",
|
||||||
|
to_char($19::date::timestamp, 'YYYY-MM-DD') || 'T00:00:00Z' AS "date",
|
||||||
|
'0000-01-01T' || to_char('2000-10-10'::date + ($20::time without time zone + INTERVAL '2 HOUR'), 'HH24:MI:SS.USZ') AS "timeExpression"
|
||||||
|
) AS json_records
|
||||||
|
) AS "json";
|
||||||
|
`)
|
||||||
|
|
||||||
|
var dest struct {
|
||||||
|
Time time.Time
|
||||||
|
TimeWithNanoSeconds time.Time
|
||||||
|
Timez time.Time
|
||||||
|
TimezWithNanoSeconds time.Time
|
||||||
|
Timestamp time.Time
|
||||||
|
TimestampWithNanoSeconds time.Time
|
||||||
|
Timestampz time.Time
|
||||||
|
TimestampzWithNanoSeconds time.Time
|
||||||
|
Date time.Time
|
||||||
|
|
||||||
|
TimeExpression time.Time
|
||||||
|
|
||||||
|
Json struct {
|
||||||
|
Time time.Time
|
||||||
|
TimeWithNanoSeconds time.Time
|
||||||
|
Timez time.Time
|
||||||
|
TimezWithNanoSeconds time.Time
|
||||||
|
Timestamp time.Time
|
||||||
|
TimestampWithNanoSeconds time.Time
|
||||||
|
Timestampz time.Time
|
||||||
|
TimestampzWithNanoSeconds time.Time
|
||||||
|
Date time.Time
|
||||||
|
|
||||||
|
TimeExpression time.Time
|
||||||
|
} `json_column:"json"`
|
||||||
|
}
|
||||||
|
|
||||||
|
err = stmt.Query(db, &dest)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
ensureTimezEqual(t, timeT.Add(2*time.Hour), dest.TimeExpression, loc)
|
||||||
|
ensureTimezEqual(t, timeT.Add(2*time.Hour), dest.Json.TimeExpression, loc)
|
||||||
|
|
||||||
|
ensureTimezEqual(t, timeT, dest.Time, loc)
|
||||||
|
ensureTimezEqual(t, timeT, dest.Json.Time, loc)
|
||||||
|
ensureTimezEqual(t, timeWithNanoSeconds, dest.TimeWithNanoSeconds, loc)
|
||||||
|
ensureTimezEqual(t, timeWithNanoSeconds, dest.Json.TimeWithNanoSeconds, loc)
|
||||||
|
|
||||||
|
ensureTimezEqual(t, timez, dest.Timez, loc)
|
||||||
|
ensureTimezEqual(t, timez, dest.Json.Timez, loc)
|
||||||
|
ensureTimezEqual(t, timezWithNanoSeconds, dest.TimezWithNanoSeconds, loc)
|
||||||
|
ensureTimezEqual(t, timezWithNanoSeconds, dest.Json.TimezWithNanoSeconds, loc)
|
||||||
|
|
||||||
|
ensureTimezEqual(t, timestamp, dest.Timestamp, loc)
|
||||||
|
ensureTimezEqual(t, timestamp, dest.Json.Timestamp, loc)
|
||||||
|
ensureTimezEqual(t, timestampWithNanoSeconds, dest.TimestampWithNanoSeconds, loc)
|
||||||
|
ensureTimezEqual(t, timestampWithNanoSeconds, dest.Json.TimestampWithNanoSeconds, loc)
|
||||||
|
|
||||||
|
ensureTimezEqual(t, timestampz, dest.Timestampz, loc)
|
||||||
|
ensureTimezEqual(t, timestampz, dest.Json.Timestampz, loc)
|
||||||
|
ensureTimezEqual(t, timestampzWithNanoSeconds, dest.TimestampzWithNanoSeconds, loc)
|
||||||
|
ensureTimezEqual(t, timestampzWithNanoSeconds, dest.Json.TimestampzWithNanoSeconds, loc)
|
||||||
|
|
||||||
|
ensureTimezEqual(t, date, dest.Date, loc)
|
||||||
|
ensureTimezEqual(t, date, dest.Json.Date, loc)
|
||||||
|
|
||||||
|
t.Run("json only", func(t *testing.T) {
|
||||||
|
stmtJson := SELECT_JSON_OBJ(
|
||||||
|
TimeT(timeT).AS("time"),
|
||||||
|
TimeT(timeWithNanoSeconds).AS("timeWithNanoSeconds"),
|
||||||
|
|
||||||
|
TimezT(timez).AS("timez"),
|
||||||
|
TimezT(timezWithNanoSeconds).AS("timezWithNanoSeconds"),
|
||||||
|
|
||||||
|
Timestamp(1999, 01, 8, 4, 5, 6).AS("timestamp"),
|
||||||
|
TimestampT(timestampWithNanoSeconds).AS("timestampWithNanoSeconds"),
|
||||||
|
|
||||||
|
TimestampzT(timestampz).AS("timestampz"),
|
||||||
|
TimestampzT(timestampzWithNanoSeconds).AS("timestampzWithNanoSeconds"),
|
||||||
|
|
||||||
|
DateT(date).AS("date"),
|
||||||
|
)
|
||||||
|
|
||||||
|
var jsonDest struct {
|
||||||
|
Time time.Time
|
||||||
|
TimeWithNanoSeconds time.Time
|
||||||
|
|
||||||
|
Timez time.Time
|
||||||
|
TimezWithNanoSeconds time.Time
|
||||||
|
|
||||||
|
Timestamp time.Time
|
||||||
|
TimestampWithNanoSeconds time.Time
|
||||||
|
|
||||||
|
Timestampz time.Time
|
||||||
|
TimestampzWithNanoSeconds time.Time
|
||||||
|
|
||||||
|
Date time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
err := stmtJson.QueryJSON(ctx, db, &jsonDest)
|
||||||
|
require.NoError(t, err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func ensureTimezEqual(t *testing.T, time1, time2 time.Time, loc *time.Location) {
|
||||||
|
time1Loc := time1.In(loc)
|
||||||
|
time2Loc := time2.In(loc)
|
||||||
|
|
||||||
|
require.Equal(t, time1Loc.Hour(), time2Loc.Hour())
|
||||||
|
require.Equal(t, time1Loc.Minute(), time2Loc.Minute())
|
||||||
|
require.Equal(t, time1Loc.Second(), time2Loc.Second())
|
||||||
|
require.Equal(t, toMicroSeconds(time1Loc.Nanosecond()), toMicroSeconds(time2Loc.Nanosecond()))
|
||||||
|
}
|
||||||
|
|
||||||
|
func toMicroSeconds(nanoseconds int) int {
|
||||||
|
return nanoseconds / 1000
|
||||||
|
}
|
||||||
|
|
||||||
func TestIntervalSetFunctionality(t *testing.T) {
|
func TestIntervalSetFunctionality(t *testing.T) {
|
||||||
|
|
||||||
t.Run("updateQueryIntervalTest", func(t *testing.T) {
|
t.Run("updateQueryIntervalTest", func(t *testing.T) {
|
||||||
|
|
@ -1251,7 +1557,50 @@ func TestInterval(t *testing.T) {
|
||||||
AllTypes.IntervalPtr.DIV(Float(22.222)).EQ(AllTypes.IntervalPtr),
|
AllTypes.IntervalPtr.DIV(Float(22.222)).EQ(AllTypes.IntervalPtr),
|
||||||
).FROM(AllTypes)
|
).FROM(AllTypes)
|
||||||
|
|
||||||
//fmt.Println(stmt.DebugSql())
|
fmt.Println(stmt.Sql())
|
||||||
|
|
||||||
|
testutils.AssertDebugStatementSql(t, stmt, `
|
||||||
|
SELECT INTERVAL '1 YEAR',
|
||||||
|
INTERVAL '1 MONTH',
|
||||||
|
INTERVAL '1 WEEK',
|
||||||
|
INTERVAL '1 DAY',
|
||||||
|
INTERVAL '1 HOUR',
|
||||||
|
INTERVAL '1 MINUTE',
|
||||||
|
INTERVAL '1 SECOND',
|
||||||
|
INTERVAL '1 MILLISECOND',
|
||||||
|
INTERVAL '1 MICROSECOND',
|
||||||
|
INTERVAL '1 DECADE',
|
||||||
|
INTERVAL '1 CENTURY',
|
||||||
|
INTERVAL '1 MILLENNIUM',
|
||||||
|
INTERVAL '1 YEAR 10 MONTH',
|
||||||
|
INTERVAL '1 YEAR 10 MONTH 20 DAY',
|
||||||
|
INTERVAL '1 YEAR 10 MONTH 20 DAY 3 HOUR',
|
||||||
|
INTERVAL '1 YEAR' IS NOT NULL,
|
||||||
|
INTERVAL '1 YEAR' AS "one year",
|
||||||
|
INTERVAL '0 MICROSECOND',
|
||||||
|
INTERVAL '1 MICROSECOND',
|
||||||
|
INTERVAL '1000 MICROSECOND',
|
||||||
|
INTERVAL '1 SECOND',
|
||||||
|
INTERVAL '1 MINUTE',
|
||||||
|
INTERVAL '1 HOUR',
|
||||||
|
INTERVAL '1 DAY',
|
||||||
|
INTERVAL '1 DAY 2 HOUR 3 MINUTE 4 SECOND 5 MICROSECOND',
|
||||||
|
(all_types.interval = INTERVAL '2 HOUR 20 MINUTE') = TRUE::boolean,
|
||||||
|
(all_types.interval_ptr != INTERVAL '2 HOUR 20 MINUTE') = FALSE::boolean,
|
||||||
|
(all_types.interval IS DISTINCT FROM INTERVAL '2 HOUR 20 MINUTE') = all_types.boolean,
|
||||||
|
(all_types.interval_ptr IS NOT DISTINCT FROM INTERVAL '10 MICROSECOND') = all_types.boolean,
|
||||||
|
(all_types.interval < all_types.interval_ptr) = all_types.boolean_ptr,
|
||||||
|
(all_types.interval <= all_types.interval_ptr) = all_types.boolean_ptr,
|
||||||
|
(all_types.interval > all_types.interval_ptr) = all_types.boolean_ptr,
|
||||||
|
(all_types.interval >= all_types.interval_ptr) = all_types.boolean_ptr,
|
||||||
|
all_types.interval BETWEEN INTERVAL '1 HOUR' AND INTERVAL '2 HOUR',
|
||||||
|
all_types.interval NOT BETWEEN all_types.interval_ptr AND INTERVAL '30 SECOND',
|
||||||
|
(all_types.interval + all_types.interval_ptr) = INTERVAL '17 SECOND',
|
||||||
|
(all_types.interval - all_types.interval_ptr) = INTERVAL '100 MICROSECOND',
|
||||||
|
(all_types.interval_ptr * 11) = all_types.interval,
|
||||||
|
(all_types.interval_ptr / 22.222) = all_types.interval_ptr
|
||||||
|
FROM test_sample.all_types;
|
||||||
|
`)
|
||||||
|
|
||||||
err := stmt.Query(db, &struct{}{})
|
err := stmt.Query(db, &struct{}{})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
@ -1368,6 +1717,7 @@ func TestAllTypesSubQueryFrom(t *testing.T) {
|
||||||
AllTypes.Time,
|
AllTypes.Time,
|
||||||
AllTypes.Timez,
|
AllTypes.Timez,
|
||||||
AllTypes.Timestamp,
|
AllTypes.Timestamp,
|
||||||
|
AllTypes.Timestampz,
|
||||||
AllTypes.Interval,
|
AllTypes.Interval,
|
||||||
AllTypes.Bytea,
|
AllTypes.Bytea,
|
||||||
).FROM(
|
).FROM(
|
||||||
|
|
@ -1383,6 +1733,7 @@ func TestAllTypesSubQueryFrom(t *testing.T) {
|
||||||
AllTypes.Time.From(subQuery),
|
AllTypes.Time.From(subQuery),
|
||||||
AllTypes.Timez.From(subQuery),
|
AllTypes.Timez.From(subQuery),
|
||||||
AllTypes.Timestamp.From(subQuery),
|
AllTypes.Timestamp.From(subQuery),
|
||||||
|
AllTypes.Timestampz.From(subQuery),
|
||||||
AllTypes.Interval.From(subQuery),
|
AllTypes.Interval.From(subQuery),
|
||||||
AllTypes.Bytea.From(subQuery),
|
AllTypes.Bytea.From(subQuery),
|
||||||
).FROM(
|
).FROM(
|
||||||
|
|
@ -1398,6 +1749,7 @@ SELECT "subQuery"."all_types.boolean" AS "all_types.boolean",
|
||||||
"subQuery"."all_types.time" AS "all_types.time",
|
"subQuery"."all_types.time" AS "all_types.time",
|
||||||
"subQuery"."all_types.timez" AS "all_types.timez",
|
"subQuery"."all_types.timez" AS "all_types.timez",
|
||||||
"subQuery"."all_types.timestamp" AS "all_types.timestamp",
|
"subQuery"."all_types.timestamp" AS "all_types.timestamp",
|
||||||
|
"subQuery"."all_types.timestampz" AS "all_types.timestampz",
|
||||||
"subQuery"."all_types.interval" AS "all_types.interval",
|
"subQuery"."all_types.interval" AS "all_types.interval",
|
||||||
"subQuery"."all_types.bytea" AS "all_types.bytea"
|
"subQuery"."all_types.bytea" AS "all_types.bytea"
|
||||||
FROM (
|
FROM (
|
||||||
|
|
@ -1409,6 +1761,7 @@ FROM (
|
||||||
all_types.time AS "all_types.time",
|
all_types.time AS "all_types.time",
|
||||||
all_types.timez AS "all_types.timez",
|
all_types.timez AS "all_types.timez",
|
||||||
all_types.timestamp AS "all_types.timestamp",
|
all_types.timestamp AS "all_types.timestamp",
|
||||||
|
all_types.timestampz AS "all_types.timestampz",
|
||||||
all_types.interval AS "all_types.interval",
|
all_types.interval AS "all_types.interval",
|
||||||
all_types.bytea AS "all_types.bytea"
|
all_types.bytea AS "all_types.bytea"
|
||||||
FROM test_sample.all_types
|
FROM test_sample.all_types
|
||||||
|
|
@ -1419,6 +1772,83 @@ FROM (
|
||||||
|
|
||||||
err := stmt.Query(db, &dest)
|
err := stmt.Query(db, &dest)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
t.Run("using SELECT_JSON", func(t *testing.T) {
|
||||||
|
stmtJson := SELECT_JSON_ARR(
|
||||||
|
AllTypes.Boolean.From(subQuery),
|
||||||
|
AllTypes.Integer.From(subQuery),
|
||||||
|
AllTypes.DoublePrecision.From(subQuery),
|
||||||
|
AllTypes.Text.From(subQuery),
|
||||||
|
AllTypes.Date.From(subQuery),
|
||||||
|
AllTypes.Time.From(subQuery),
|
||||||
|
AllTypes.Timez.From(subQuery),
|
||||||
|
AllTypes.Timestamp.From(subQuery),
|
||||||
|
AllTypes.Timestampz.From(subQuery),
|
||||||
|
AllTypes.Interval.From(subQuery),
|
||||||
|
AllTypes.Bytea.From(subQuery),
|
||||||
|
).FROM(
|
||||||
|
subQuery,
|
||||||
|
)
|
||||||
|
|
||||||
|
testutils.AssertDebugStatementSql(t, stmtJson, `
|
||||||
|
SELECT json_agg(row_to_json(records)) AS "json"
|
||||||
|
FROM (
|
||||||
|
SELECT "subQuery"."all_types.boolean" AS "boolean",
|
||||||
|
"subQuery"."all_types.integer" AS "integer",
|
||||||
|
"subQuery"."all_types.double_precision" AS "doublePrecision",
|
||||||
|
"subQuery"."all_types.text" AS "text",
|
||||||
|
to_char("subQuery"."all_types.date"::timestamp, 'YYYY-MM-DD') || 'T00:00:00Z' AS "date",
|
||||||
|
'0000-01-01T' || to_char('2000-10-10'::date + "subQuery"."all_types.time", 'HH24:MI:SS.USZ') AS "time",
|
||||||
|
'0000-01-01T' || to_char('2000-10-10'::date + "subQuery"."all_types.timez", 'HH24:MI:SS.USTZH:TZM') AS "timez",
|
||||||
|
to_char("subQuery"."all_types.timestamp", 'YYYY-MM-DD"T"HH24:MI:SS.USZ') AS "timestamp",
|
||||||
|
"subQuery"."all_types.timestampz" AS "timestampz",
|
||||||
|
"subQuery"."all_types.interval" AS "interval",
|
||||||
|
ENCODE("subQuery"."all_types.bytea", 'base64') AS "bytea"
|
||||||
|
FROM (
|
||||||
|
SELECT all_types.boolean AS "all_types.boolean",
|
||||||
|
all_types.integer AS "all_types.integer",
|
||||||
|
all_types.double_precision AS "all_types.double_precision",
|
||||||
|
all_types.text AS "all_types.text",
|
||||||
|
all_types.date AS "all_types.date",
|
||||||
|
all_types.time AS "all_types.time",
|
||||||
|
all_types.timez AS "all_types.timez",
|
||||||
|
all_types.timestamp AS "all_types.timestamp",
|
||||||
|
all_types.timestampz AS "all_types.timestampz",
|
||||||
|
all_types.interval AS "all_types.interval",
|
||||||
|
all_types.bytea AS "all_types.bytea"
|
||||||
|
FROM test_sample.all_types
|
||||||
|
) AS "subQuery"
|
||||||
|
) AS records;
|
||||||
|
`)
|
||||||
|
|
||||||
|
var destJson []model.AllTypes
|
||||||
|
|
||||||
|
err := stmtJson.QueryJSON(ctx, db, &destJson)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
t.Run("using AllColumns()", func(t *testing.T) {
|
||||||
|
stmtJsonAllColumns := SELECT_JSON_ARR(
|
||||||
|
subQuery.AllColumns(),
|
||||||
|
).FROM(
|
||||||
|
subQuery,
|
||||||
|
)
|
||||||
|
|
||||||
|
require.Equal(t, stmtJson.DebugSql(), stmtJsonAllColumns.DebugSql())
|
||||||
|
})
|
||||||
|
|
||||||
|
// fix timezone before comparisons
|
||||||
|
minus8 := time.FixedZone("UTC", -8*60*60)
|
||||||
|
destJson[0].Timez = *toTZ(&destJson[0].Timez, minus8)
|
||||||
|
destJson[1].Timez = *toTZ(&destJson[1].Timez, minus8)
|
||||||
|
|
||||||
|
destJson[0].Timestampz = *toTZ(&destJson[0].Timestampz, time.UTC)
|
||||||
|
destJson[1].Timestampz = *toTZ(&destJson[1].Timestampz, time.UTC)
|
||||||
|
|
||||||
|
dest[0].Timestampz = *toTZ(&dest[0].Timestampz, time.UTC)
|
||||||
|
dest[1].Timestampz = *toTZ(&dest[1].Timestampz, time.UTC)
|
||||||
|
|
||||||
|
testutils.AssertJsonEqual(t, dest, destJson)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAllTypesUpdateSet(t *testing.T) {
|
func TestAllTypesUpdateSet(t *testing.T) {
|
||||||
|
|
|
||||||
|
|
@ -251,9 +251,8 @@ func testJoinEverythingJSON(t require.TestingT) {
|
||||||
WHERE(MediaType.MediaTypeId.EQ(Track.MediaTypeId)).AS("MediaType"),
|
WHERE(MediaType.MediaTypeId.EQ(Track.MediaTypeId)).AS("MediaType"),
|
||||||
|
|
||||||
SELECT_JSON_ARR(Playlist.AllColumns).
|
SELECT_JSON_ARR(Playlist.AllColumns).
|
||||||
FROM(Playlist.INNER_JOIN(
|
FROM(Playlist.
|
||||||
PlaylistTrack,
|
INNER_JOIN(PlaylistTrack, Playlist.PlaylistId.EQ(PlaylistTrack.PlaylistId))).
|
||||||
Playlist.PlaylistId.EQ(PlaylistTrack.PlaylistId))).
|
|
||||||
WHERE(PlaylistTrack.TrackId.EQ(Track.TrackId)).
|
WHERE(PlaylistTrack.TrackId.EQ(Track.TrackId)).
|
||||||
ORDER_BY(Playlist.PlaylistId).AS("Playlists"),
|
ORDER_BY(Playlist.PlaylistId).AS("Playlists"),
|
||||||
|
|
||||||
|
|
@ -273,9 +272,9 @@ func testJoinEverythingJSON(t require.TestingT) {
|
||||||
WHERE(Employee.EmployeeId.EQ(Customer.SupportRepId)).AS("Employee"),
|
WHERE(Employee.EmployeeId.EQ(Customer.SupportRepId)).AS("Employee"),
|
||||||
).FROM(Customer).
|
).FROM(Customer).
|
||||||
WHERE(Customer.CustomerId.EQ(Invoice.CustomerId)).AS("Customer"),
|
WHERE(Customer.CustomerId.EQ(Invoice.CustomerId)).AS("Customer"),
|
||||||
).FROM(Invoice.INNER_JOIN(
|
).FROM(
|
||||||
InvoiceLine,
|
Invoice.
|
||||||
InvoiceLine.InvoiceId.EQ(Invoice.InvoiceId)),
|
INNER_JOIN(InvoiceLine, InvoiceLine.InvoiceId.EQ(Invoice.InvoiceId)),
|
||||||
).WHERE(InvoiceLine.TrackId.EQ(Track.TrackId)).
|
).WHERE(InvoiceLine.TrackId.EQ(Track.TrackId)).
|
||||||
ORDER_BY(Invoice.InvoiceId).AS("Invoices"),
|
ORDER_BY(Invoice.InvoiceId).AS("Invoices"),
|
||||||
).FROM(Track).
|
).FROM(Track).
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,8 @@ import (
|
||||||
_ "github.com/jackc/pgx/v4/stdlib"
|
_ "github.com/jackc/pgx/v4/stdlib"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var ctx = context.Background()
|
||||||
|
|
||||||
var db *stmtcache.DB
|
var db *stmtcache.DB
|
||||||
var testRoot string
|
var testRoot string
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -139,7 +139,7 @@ func testNorthwindJoinEverythingJson(t require.TestingT) {
|
||||||
Territories,
|
Territories,
|
||||||
EmployeeTerritories.TerritoryID.EQ(Territories.TerritoryID)),
|
EmployeeTerritories.TerritoryID.EQ(Territories.TerritoryID)),
|
||||||
).WHERE(
|
).WHERE(
|
||||||
EmployeeTerritories.EmployeeID.EQ(Employees.EmployeeID), // TODO: move to join
|
EmployeeTerritories.EmployeeID.EQ(Employees.EmployeeID),
|
||||||
).AS("Territories"),
|
).AS("Territories"),
|
||||||
).FROM(Employees).
|
).FROM(Employees).
|
||||||
WHERE(Orders.EmployeeID.EQ(Employees.EmployeeID)).AS("Employee"),
|
WHERE(Orders.EmployeeID.EQ(Employees.EmployeeID)).AS("Employee"),
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
Loading…
Add table
Add a link
Reference in a new issue