Merge pull request #410 from go-jet/row-exp
Add support for ROW expressions and VALUES statement
This commit is contained in:
commit
369c657f85
53 changed files with 1532 additions and 176 deletions
|
|
@ -13,6 +13,7 @@ type Dialect interface {
|
||||||
ArgumentPlaceholder() QueryPlaceholderFunc
|
ArgumentPlaceholder() QueryPlaceholderFunc
|
||||||
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
|
||||||
}
|
}
|
||||||
|
|
||||||
// SerializerFunc func
|
// SerializerFunc func
|
||||||
|
|
@ -35,6 +36,7 @@ type DialectParams struct {
|
||||||
ArgumentPlaceholder QueryPlaceholderFunc
|
ArgumentPlaceholder QueryPlaceholderFunc
|
||||||
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
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewDialect creates new dialect with params
|
// NewDialect creates new dialect with params
|
||||||
|
|
@ -49,6 +51,7 @@ func NewDialect(params DialectParams) Dialect {
|
||||||
argumentPlaceholder: params.ArgumentPlaceholder,
|
argumentPlaceholder: params.ArgumentPlaceholder,
|
||||||
reservedWords: arrayOfStringsToMapOfStrings(params.ReservedWords),
|
reservedWords: arrayOfStringsToMapOfStrings(params.ReservedWords),
|
||||||
serializeOrderBy: params.SerializeOrderBy,
|
serializeOrderBy: params.SerializeOrderBy,
|
||||||
|
valuesDefaultColumnName: params.ValuesDefaultColumnName,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -62,6 +65,7 @@ type dialectImpl struct {
|
||||||
argumentPlaceholder QueryPlaceholderFunc
|
argumentPlaceholder QueryPlaceholderFunc
|
||||||
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
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *dialectImpl) Name() string {
|
func (d *dialectImpl) Name() string {
|
||||||
|
|
@ -107,6 +111,10 @@ func (d *dialectImpl) SerializeOrderBy() func(expression Expression, ascending,
|
||||||
return d.serializeOrderBy
|
return d.serializeOrderBy
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *dialectImpl) ValuesDefaultColumnName(index int) string {
|
||||||
|
return d.valuesDefaultColumnName(index)
|
||||||
|
}
|
||||||
|
|
||||||
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 {
|
||||||
|
|
|
||||||
|
|
@ -51,12 +51,12 @@ func (e *ExpressionInterfaceImpl) IS_NOT_NULL() BoolExpression {
|
||||||
|
|
||||||
// IN checks if this expressions matches any in expressions list
|
// IN checks if this expressions matches any in expressions list
|
||||||
func (e *ExpressionInterfaceImpl) IN(expressions ...Expression) BoolExpression {
|
func (e *ExpressionInterfaceImpl) IN(expressions ...Expression) BoolExpression {
|
||||||
return newBinaryBoolOperatorExpression(e.Parent, WRAP(expressions...), "IN")
|
return newBinaryBoolOperatorExpression(e.Parent, wrap(expressions...), "IN")
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOT_IN checks if this expressions is different of all expressions in expressions list
|
// NOT_IN checks if this expressions is different of all expressions in expressions list
|
||||||
func (e *ExpressionInterfaceImpl) NOT_IN(expressions ...Expression) BoolExpression {
|
func (e *ExpressionInterfaceImpl) NOT_IN(expressions ...Expression) BoolExpression {
|
||||||
return newBinaryBoolOperatorExpression(e.Parent, WRAP(expressions...), "NOT IN")
|
return newBinaryBoolOperatorExpression(e.Parent, wrap(expressions...), "NOT IN")
|
||||||
}
|
}
|
||||||
|
|
||||||
// AS the temporary alias name to assign to the expression
|
// AS the temporary alias name to assign to the expression
|
||||||
|
|
@ -316,15 +316,6 @@ func (s *complexExpression) serialize(statement StatementType, out *SQLBuilder,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type skipParenthesisWrap struct {
|
func wrap(expressions ...Expression) Expression {
|
||||||
Expression
|
return NewFunc("", expressions, nil)
|
||||||
}
|
|
||||||
|
|
||||||
func skipWrap(expression Expression) Expression {
|
|
||||||
return &skipParenthesisWrap{expression}
|
|
||||||
}
|
|
||||||
|
|
||||||
// since the expression is a function parameter, there is no need to wrap it in parentheses
|
|
||||||
func (s *skipParenthesisWrap) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) {
|
|
||||||
s.Expression.serialize(statement, out, append(options, NoWrap)...)
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,11 +12,6 @@ func OR(expressions ...BoolExpression) BoolExpression {
|
||||||
return newBoolExpressionListOperator("OR", expressions...)
|
return newBoolExpressionListOperator("OR", expressions...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ROW function is used to create a tuple value that consists of a set of expressions or column values.
|
|
||||||
func ROW(expressions ...Expression) Expression {
|
|
||||||
return NewFunc("ROW", expressions, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ------------------ Mathematical functions ---------------//
|
// ------------------ Mathematical functions ---------------//
|
||||||
|
|
||||||
// ABSf calculates absolute value from float expression
|
// ABSf calculates absolute value from float expression
|
||||||
|
|
@ -711,7 +706,7 @@ func (p parametersSerializer) serialize(statement StatementType, out *SQLBuilder
|
||||||
if _, isStatement := expression.(Statement); isStatement {
|
if _, isStatement := expression.(Statement); isStatement {
|
||||||
expression.serialize(statement, out, options...)
|
expression.serialize(statement, out, options...)
|
||||||
} else {
|
} else {
|
||||||
skipWrap(expression).serialize(statement, out, options...)
|
expression.serialize(statement, out, append(options, NoWrap, Ident)...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -374,32 +374,6 @@ func (n *starLiteral) serialize(statement StatementType, out *SQLBuilder, option
|
||||||
|
|
||||||
//---------------------------------------------------//
|
//---------------------------------------------------//
|
||||||
|
|
||||||
type wrap struct {
|
|
||||||
ExpressionInterfaceImpl
|
|
||||||
expressions []Expression
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *wrap) serialize(statementType StatementType, out *SQLBuilder, options ...SerializeOption) {
|
|
||||||
out.WriteString("(")
|
|
||||||
|
|
||||||
if len(n.expressions) == 1 {
|
|
||||||
options = append(options, NoWrap, Ident)
|
|
||||||
}
|
|
||||||
serializeExpressionList(statementType, n.expressions, ", ", out, options...)
|
|
||||||
|
|
||||||
out.WriteString(")")
|
|
||||||
}
|
|
||||||
|
|
||||||
// WRAP wraps list of expressions with brackets - ( expression1, expression2, ... )
|
|
||||||
func WRAP(expression ...Expression) Expression {
|
|
||||||
wrap := &wrap{expressions: expression}
|
|
||||||
wrap.ExpressionInterfaceImpl.Parent = wrap
|
|
||||||
|
|
||||||
return wrap
|
|
||||||
}
|
|
||||||
|
|
||||||
//---------------------------------------------------//
|
|
||||||
|
|
||||||
type rawExpression struct {
|
type rawExpression struct {
|
||||||
ExpressionInterfaceImpl
|
ExpressionInterfaceImpl
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -54,7 +54,12 @@ type orderSetAggregateFuncExpression struct {
|
||||||
|
|
||||||
func (p *orderSetAggregateFuncExpression) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) {
|
func (p *orderSetAggregateFuncExpression) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) {
|
||||||
out.WriteString(p.name)
|
out.WriteString(p.name)
|
||||||
WRAP(p.fraction).serialize(statement, out, FallTrough(options)...)
|
|
||||||
|
if p.fraction != nil {
|
||||||
|
wrap(p.fraction).serialize(statement, out, FallTrough(options)...)
|
||||||
|
} else {
|
||||||
|
wrap().serialize(statement, out, FallTrough(options)...)
|
||||||
|
}
|
||||||
out.WriteString("WITHIN GROUP")
|
out.WriteString("WITHIN GROUP")
|
||||||
p.orderBy.serialize(statement, out)
|
p.orderBy.serialize(statement, out)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,7 @@ AVG(table1.col_int) AS "avg",
|
||||||
table2.col3 AS "col3",
|
table2.col3 AS "col3",
|
||||||
table2.col4 AS "col4"`)
|
table2.col4 AS "col4"`)
|
||||||
|
|
||||||
subQueryProjections := projectionList.fromImpl(NewSelectTable(nil, "subQuery"))
|
subQueryProjections := projectionList.fromImpl(NewSelectTable(nil, "subQuery", nil))
|
||||||
|
|
||||||
assertProjectionSerialize(t, subQueryProjections,
|
assertProjectionSerialize(t, subQueryProjections,
|
||||||
`"subQuery"."table1.col3" AS "table1.col3",
|
`"subQuery"."table1.col3" AS "table1.col3",
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ type rawStatementImpl struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// RawStatement creates new sql statements from raw query and optional map of named arguments
|
// RawStatement creates new sql statements from raw query and optional map of named arguments
|
||||||
func RawStatement(dialect Dialect, rawQuery string, namedArgument ...map[string]interface{}) Statement {
|
func RawStatement(dialect Dialect, rawQuery string, namedArgument ...map[string]interface{}) SerializerStatement {
|
||||||
newRawStatement := rawStatementImpl{
|
newRawStatement := rawStatementImpl{
|
||||||
serializerStatementInterfaceImpl: serializerStatementInterfaceImpl{
|
serializerStatementInterfaceImpl: serializerStatementInterfaceImpl{
|
||||||
dialect: dialect,
|
dialect: dialect,
|
||||||
|
|
|
||||||
102
internal/jet/row_expression.go
Normal file
102
internal/jet/row_expression.go
Normal file
|
|
@ -0,0 +1,102 @@
|
||||||
|
package jet
|
||||||
|
|
||||||
|
// RowExpression interface
|
||||||
|
type RowExpression interface {
|
||||||
|
Expression
|
||||||
|
HasProjections
|
||||||
|
|
||||||
|
EQ(rhs RowExpression) BoolExpression
|
||||||
|
NOT_EQ(rhs RowExpression) BoolExpression
|
||||||
|
IS_DISTINCT_FROM(rhs RowExpression) BoolExpression
|
||||||
|
IS_NOT_DISTINCT_FROM(rhs RowExpression) BoolExpression
|
||||||
|
|
||||||
|
LT(rhs RowExpression) BoolExpression
|
||||||
|
LT_EQ(rhs RowExpression) BoolExpression
|
||||||
|
GT(rhs RowExpression) BoolExpression
|
||||||
|
GT_EQ(rhs RowExpression) BoolExpression
|
||||||
|
}
|
||||||
|
|
||||||
|
type rowInterfaceImpl struct {
|
||||||
|
parent Expression
|
||||||
|
dialect Dialect
|
||||||
|
elemCount int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *rowInterfaceImpl) EQ(rhs RowExpression) BoolExpression {
|
||||||
|
return Eq(n.parent, rhs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *rowInterfaceImpl) NOT_EQ(rhs RowExpression) BoolExpression {
|
||||||
|
return NotEq(n.parent, rhs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *rowInterfaceImpl) IS_DISTINCT_FROM(rhs RowExpression) BoolExpression {
|
||||||
|
return IsDistinctFrom(n.parent, rhs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *rowInterfaceImpl) IS_NOT_DISTINCT_FROM(rhs RowExpression) BoolExpression {
|
||||||
|
return IsNotDistinctFrom(n.parent, rhs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *rowInterfaceImpl) GT(rhs RowExpression) BoolExpression {
|
||||||
|
return Gt(n.parent, rhs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *rowInterfaceImpl) GT_EQ(rhs RowExpression) BoolExpression {
|
||||||
|
return GtEq(n.parent, rhs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *rowInterfaceImpl) LT(rhs RowExpression) BoolExpression {
|
||||||
|
return Lt(n.parent, rhs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *rowInterfaceImpl) LT_EQ(rhs RowExpression) BoolExpression {
|
||||||
|
return LtEq(n.parent, rhs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *rowInterfaceImpl) projections() ProjectionList {
|
||||||
|
var ret ProjectionList
|
||||||
|
|
||||||
|
for i := 0; i < n.elemCount; i++ {
|
||||||
|
rowColumn := NewColumnImpl(n.dialect.ValuesDefaultColumnName(i), "", nil)
|
||||||
|
ret = append(ret, &rowColumn)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------//
|
||||||
|
type rowExpressionWrapper struct {
|
||||||
|
rowInterfaceImpl
|
||||||
|
Expression
|
||||||
|
}
|
||||||
|
|
||||||
|
func newRowExpression(name string, dialect Dialect, expressions ...Expression) RowExpression {
|
||||||
|
ret := &rowExpressionWrapper{}
|
||||||
|
ret.rowInterfaceImpl.parent = ret
|
||||||
|
|
||||||
|
ret.Expression = NewFunc(name, expressions, ret)
|
||||||
|
ret.dialect = dialect
|
||||||
|
ret.elemCount = len(expressions)
|
||||||
|
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
// ROW function is used to create a tuple value that consists of a set of expressions or column values.
|
||||||
|
func ROW(dialect Dialect, expressions ...Expression) RowExpression {
|
||||||
|
return newRowExpression("ROW", dialect, expressions...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WRAP creates row expressions without ROW keyword `( expression1, expression2, ... )`.
|
||||||
|
func WRAP(dialect Dialect, expressions ...Expression) RowExpression {
|
||||||
|
return newRowExpression("", dialect, expressions...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RowExp serves as a wrapper for an arbitrary expression, treating it as a row expression.
|
||||||
|
// This enables the Go compiler to interpret any expression as a row expression
|
||||||
|
// Note: This does not modify the generated SQL builder output by adding a SQL CAST operation.
|
||||||
|
func RowExp(expression Expression) RowExpression {
|
||||||
|
rowExpressionWrap := rowExpressionWrapper{Expression: expression}
|
||||||
|
rowExpressionWrap.rowInterfaceImpl.parent = &rowExpressionWrap
|
||||||
|
return &rowExpressionWrap
|
||||||
|
}
|
||||||
|
|
@ -10,13 +10,19 @@ type SelectTable interface {
|
||||||
type selectTableImpl struct {
|
type selectTableImpl struct {
|
||||||
Statement SerializerHasProjections
|
Statement SerializerHasProjections
|
||||||
alias string
|
alias string
|
||||||
|
columnAliases []ColumnExpression
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewSelectTable func
|
// NewSelectTable func
|
||||||
func NewSelectTable(selectStmt SerializerHasProjections, alias string) selectTableImpl {
|
func NewSelectTable(selectStmt SerializerHasProjections, alias string, columnAliases []ColumnExpression) selectTableImpl {
|
||||||
selectTable := selectTableImpl{
|
selectTable := selectTableImpl{
|
||||||
Statement: selectStmt,
|
Statement: selectStmt,
|
||||||
alias: alias,
|
alias: alias,
|
||||||
|
columnAliases: columnAliases,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, column := range selectTable.columnAliases {
|
||||||
|
column.setSubQuery(selectTable)
|
||||||
}
|
}
|
||||||
|
|
||||||
return selectTable
|
return selectTable
|
||||||
|
|
@ -31,6 +37,10 @@ func (s selectTableImpl) Alias() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s selectTableImpl) AllColumns() ProjectionList {
|
func (s selectTableImpl) AllColumns() ProjectionList {
|
||||||
|
if len(s.columnAliases) > 0 {
|
||||||
|
return ColumnListToProjectionList(s.columnAliases)
|
||||||
|
}
|
||||||
|
|
||||||
projectionList := s.projections().fromImpl(s)
|
projectionList := s.projections().fromImpl(s)
|
||||||
return projectionList.(ProjectionList)
|
return projectionList.(ProjectionList)
|
||||||
}
|
}
|
||||||
|
|
@ -40,6 +50,12 @@ func (s selectTableImpl) serialize(statement StatementType, out *SQLBuilder, opt
|
||||||
|
|
||||||
out.WriteString("AS")
|
out.WriteString("AS")
|
||||||
out.WriteIdentifier(s.alias)
|
out.WriteIdentifier(s.alias)
|
||||||
|
|
||||||
|
if len(s.columnAliases) > 0 {
|
||||||
|
out.WriteByte('(')
|
||||||
|
SerializeColumnExpressionNames(s.columnAliases, out)
|
||||||
|
out.WriteByte(')')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// --------------------------------------
|
// --------------------------------------
|
||||||
|
|
@ -50,7 +66,7 @@ type lateralImpl struct {
|
||||||
|
|
||||||
// NewLateral creates new lateral expression from select statement with alias
|
// NewLateral creates new lateral expression from select statement with alias
|
||||||
func NewLateral(selectStmt SerializerStatement, alias string) SelectTable {
|
func NewLateral(selectStmt SerializerStatement, alias string) SelectTable {
|
||||||
return lateralImpl{selectTableImpl: NewSelectTable(selectStmt, alias)}
|
return lateralImpl{selectTableImpl: NewSelectTable(selectStmt, alias, nil)}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s lateralImpl) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) {
|
func (s lateralImpl) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) {
|
||||||
|
|
|
||||||
35
internal/jet/values.go
Normal file
35
internal/jet/values.go
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
package jet
|
||||||
|
|
||||||
|
// Values hold a set of one or more rows
|
||||||
|
type Values []RowExpression
|
||||||
|
|
||||||
|
func (v Values) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) {
|
||||||
|
out.WriteByte('(')
|
||||||
|
out.IncreaseIdent(5)
|
||||||
|
|
||||||
|
out.NewLine()
|
||||||
|
out.WriteString("VALUES")
|
||||||
|
|
||||||
|
for rowIndex, row := range v {
|
||||||
|
if rowIndex > 0 {
|
||||||
|
out.WriteString(",")
|
||||||
|
out.NewLine()
|
||||||
|
} else {
|
||||||
|
out.IncreaseIdent(7)
|
||||||
|
}
|
||||||
|
|
||||||
|
row.serialize(statement, out, options...)
|
||||||
|
}
|
||||||
|
out.DecreaseIdent(7)
|
||||||
|
out.DecreaseIdent(5)
|
||||||
|
out.NewLine()
|
||||||
|
out.WriteByte(')')
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v Values) projections() ProjectionList {
|
||||||
|
if len(v) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return v[0].projections()
|
||||||
|
}
|
||||||
|
|
@ -64,7 +64,7 @@ type CommonTableExpression struct {
|
||||||
// CTE creates new named CommonTableExpression
|
// CTE creates new named CommonTableExpression
|
||||||
func CTE(name string, columns ...ColumnExpression) CommonTableExpression {
|
func CTE(name string, columns ...ColumnExpression) CommonTableExpression {
|
||||||
cte := CommonTableExpression{
|
cte := CommonTableExpression{
|
||||||
selectTableImpl: NewSelectTable(nil, name),
|
selectTableImpl: NewSelectTable(nil, name, columns),
|
||||||
Columns: columns,
|
Columns: columns,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -99,12 +99,3 @@ func (c CommonTableExpression) serialize(statement StatementType, out *SQLBuilde
|
||||||
out.WriteIdentifier(c.alias)
|
out.WriteIdentifier(c.alias)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// AllColumns returns list of all projections in the CTE
|
|
||||||
func (c CommonTableExpression) AllColumns() ProjectionList {
|
|
||||||
if len(c.Columns) > 0 {
|
|
||||||
return ColumnListToProjectionList(c.Columns)
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.selectTableImpl.AllColumns()
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -6,23 +6,27 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type cast interface {
|
type cast interface {
|
||||||
// Cast expressions as castType type
|
// AS casts expressions as castType type
|
||||||
AS(castType string) Expression
|
AS(castType string) Expression
|
||||||
// Cast expression as char with optional length
|
// AS_CHAR casts expression as char with optional length
|
||||||
AS_CHAR(length ...int) StringExpression
|
AS_CHAR(length ...int) StringExpression
|
||||||
// Cast expression AS date type
|
// AS_DATE casts expression AS date type
|
||||||
AS_DATE() DateExpression
|
AS_DATE() DateExpression
|
||||||
// Cast expression AS numeric type, using precision and optionally scale
|
// AS_FLOAT casts expressions as float type
|
||||||
|
AS_FLOAT() FloatExpression
|
||||||
|
// AS_DOUBLE casts expressions as double type
|
||||||
|
AS_DOUBLE() FloatExpression
|
||||||
|
// AS_DECIMAL casts expression AS numeric type
|
||||||
AS_DECIMAL() FloatExpression
|
AS_DECIMAL() FloatExpression
|
||||||
// Cast expression AS time type
|
// AS_TIME casts expression AS time type
|
||||||
AS_TIME() TimeExpression
|
AS_TIME() TimeExpression
|
||||||
// Cast expression as datetime type
|
// AS_DATETIME casts expression as datetime type
|
||||||
AS_DATETIME() DateTimeExpression
|
AS_DATETIME() DateTimeExpression
|
||||||
// Cast expressions as signed integer type
|
// AS_SIGNED casts expressions as signed integer type
|
||||||
AS_SIGNED() IntegerExpression
|
AS_SIGNED() IntegerExpression
|
||||||
// Cast expression as unsigned integer type
|
// AS_UNSIGNED casts expression as unsigned integer type
|
||||||
AS_UNSIGNED() IntegerExpression
|
AS_UNSIGNED() IntegerExpression
|
||||||
// Cast expression as binary type
|
// AS_BINARY casts expression as binary type
|
||||||
AS_BINARY() StringExpression
|
AS_BINARY() StringExpression
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -73,6 +77,14 @@ func (c *castImpl) AS_DATE() DateExpression {
|
||||||
return DateExp(c.AS("DATE"))
|
return DateExp(c.AS("DATE"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *castImpl) AS_FLOAT() FloatExpression {
|
||||||
|
return FloatExp(c.AS("FLOAT"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *castImpl) AS_DOUBLE() FloatExpression {
|
||||||
|
return FloatExp(c.AS("DOUBLE"))
|
||||||
|
}
|
||||||
|
|
||||||
// AS_DECIMAL casts expression AS DECIMAL type
|
// AS_DECIMAL casts expression AS DECIMAL type
|
||||||
func (c *castImpl) AS_DECIMAL() FloatExpression {
|
func (c *castImpl) AS_DECIMAL() FloatExpression {
|
||||||
return FloatExp(c.AS("DECIMAL"))
|
return FloatExp(c.AS("DECIMAL"))
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package mysql
|
package mysql
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"github.com/go-jet/jet/v2/internal/jet"
|
"github.com/go-jet/jet/v2/internal/jet"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -28,6 +29,9 @@ func newDialect() jet.Dialect {
|
||||||
},
|
},
|
||||||
ReservedWords: reservedWords,
|
ReservedWords: reservedWords,
|
||||||
SerializeOrderBy: serializeOrderBy,
|
SerializeOrderBy: serializeOrderBy,
|
||||||
|
ValuesDefaultColumnName: func(index int) string {
|
||||||
|
return fmt.Sprintf("column_%d", index)
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
return jet.NewDialect(mySQLDialectParams)
|
return jet.NewDialect(mySQLDialectParams)
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,9 @@ type DateTimeExpression = jet.TimestampExpression
|
||||||
// TimestampExpression interface
|
// TimestampExpression interface
|
||||||
type TimestampExpression = jet.TimestampExpression
|
type TimestampExpression = jet.TimestampExpression
|
||||||
|
|
||||||
|
// RowExpression interface
|
||||||
|
type RowExpression = jet.RowExpression
|
||||||
|
|
||||||
// BoolExp is bool expression wrapper around arbitrary expression.
|
// BoolExp is bool expression wrapper around arbitrary expression.
|
||||||
// Allows go compiler to see any expression as bool expression.
|
// Allows go compiler to see any expression as bool expression.
|
||||||
// Does not add sql cast to generated sql builder output.
|
// Does not add sql cast to generated sql builder output.
|
||||||
|
|
@ -70,6 +73,11 @@ var DateTimeExp = jet.TimestampExp
|
||||||
// Does not add sql cast to generated sql builder output.
|
// Does not add sql cast to generated sql builder output.
|
||||||
var TimestampExp = jet.TimestampExp
|
var TimestampExp = jet.TimestampExp
|
||||||
|
|
||||||
|
// RowExp serves as a wrapper for an arbitrary expression, treating it as a row expression.
|
||||||
|
// This enables the Go compiler to interpret any expression as a row expression
|
||||||
|
// Note: This does not modify the generated SQL builder output by adding a SQL CAST operation.
|
||||||
|
var RowExp = jet.RowExp
|
||||||
|
|
||||||
// CustomExpression is used to define custom expressions.
|
// CustomExpression is used to define custom expressions.
|
||||||
var CustomExpression = jet.CustomExpression
|
var CustomExpression = jet.CustomExpression
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,9 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
// ROW function is used to create a tuple value that consists of a set of expressions or column values.
|
// ROW function is used to create a tuple value that consists of a set of expressions or column values.
|
||||||
var ROW = jet.ROW
|
func ROW(expressions ...Expression) RowExpression {
|
||||||
|
return jet.ROW(Dialect, expressions...)
|
||||||
|
}
|
||||||
|
|
||||||
// ------------------ Mathematical functions ---------------//
|
// ------------------ Mathematical functions ---------------//
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -172,7 +172,7 @@ func (s *selectStatementImpl) LOCK_IN_SHARE_MODE() SelectStatement {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *selectStatementImpl) AsTable(alias string) SelectTable {
|
func (s *selectStatementImpl) AsTable(alias string) SelectTable {
|
||||||
return newSelectTable(s, alias)
|
return newSelectTable(s, alias, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
//-----------------------------------------------------
|
//-----------------------------------------------------
|
||||||
|
|
|
||||||
|
|
@ -13,9 +13,9 @@ type selectTableImpl struct {
|
||||||
readableTableInterfaceImpl
|
readableTableInterfaceImpl
|
||||||
}
|
}
|
||||||
|
|
||||||
func newSelectTable(selectStmt jet.SerializerHasProjections, alias string) SelectTable {
|
func newSelectTable(selectStmt jet.SerializerHasProjections, alias string, columnAliases []jet.ColumnExpression) SelectTable {
|
||||||
subQuery := &selectTableImpl{
|
subQuery := &selectTableImpl{
|
||||||
SelectTable: jet.NewSelectTable(selectStmt, alias),
|
SelectTable: jet.NewSelectTable(selectStmt, alias, columnAliases),
|
||||||
}
|
}
|
||||||
|
|
||||||
subQuery.readableTableInterfaceImpl.parent = subQuery
|
subQuery.readableTableInterfaceImpl.parent = subQuery
|
||||||
|
|
|
||||||
|
|
@ -85,7 +85,7 @@ func (s *setStatementImpl) OFFSET(offset int64) setStatement {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *setStatementImpl) AsTable(alias string) SelectTable {
|
func (s *setStatementImpl) AsTable(alias string) SelectTable {
|
||||||
return newSelectTable(s, alias)
|
return newSelectTable(s, alias, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,6 @@ package mysql
|
||||||
import "github.com/go-jet/jet/v2/internal/jet"
|
import "github.com/go-jet/jet/v2/internal/jet"
|
||||||
|
|
||||||
// RawStatement creates new sql statements from raw query and optional map of named arguments
|
// RawStatement creates new sql statements from raw query and optional map of named arguments
|
||||||
func RawStatement(rawQuery string, namedArguments ...RawArgs) Statement {
|
func RawStatement(rawQuery string, namedArguments ...RawArgs) jet.SerializerStatement {
|
||||||
return jet.RawStatement(Dialect, rawQuery, namedArguments...)
|
return jet.RawStatement(Dialect, rawQuery, namedArguments...)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
32
mysql/values.go
Normal file
32
mysql/values.go
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
package mysql
|
||||||
|
|
||||||
|
import "github.com/go-jet/jet/v2/internal/jet"
|
||||||
|
|
||||||
|
type values struct {
|
||||||
|
jet.Values
|
||||||
|
}
|
||||||
|
|
||||||
|
// VALUES is a table value constructor that computes a set of one or more rows as a temporary constant table.
|
||||||
|
// Each row is defined by the ROW constructor, which takes one or more expressions.
|
||||||
|
//
|
||||||
|
// Example usage:
|
||||||
|
//
|
||||||
|
// VALUES(
|
||||||
|
// ROW(Int32(204), Float32(1.21)),
|
||||||
|
// ROW(Int32(207), Float32(1.02)),
|
||||||
|
// )
|
||||||
|
func VALUES(rows ...RowExpression) values {
|
||||||
|
return values{Values: jet.Values(rows)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AS assigns an alias to the temporary VALUES table, allowing it to be referenced
|
||||||
|
// within SQL FROM clauses, just like a regular table.
|
||||||
|
// By default, VALUES columns are named `column1`, `column2`, etc... Default column aliasing can be
|
||||||
|
// overwritten by passing new list of columns.
|
||||||
|
//
|
||||||
|
// Example usage:
|
||||||
|
//
|
||||||
|
// VALUES(...).AS("film_values", IntegerColumn("length"), TimestampColumn("update_date"))
|
||||||
|
func (v values) AS(alias string, columns ...Column) SelectTable {
|
||||||
|
return newSelectTable(v, alias, columns)
|
||||||
|
}
|
||||||
|
|
@ -6,7 +6,7 @@ import "github.com/go-jet/jet/v2/internal/jet"
|
||||||
type CommonTableExpression interface {
|
type CommonTableExpression interface {
|
||||||
SelectTable
|
SelectTable
|
||||||
|
|
||||||
AS(statement jet.SerializerStatement) CommonTableExpression
|
AS(statement jet.SerializerHasProjections) CommonTableExpression
|
||||||
// ALIAS is used to create another alias of the CTE, if a CTE needs to appear multiple times in the main query.
|
// ALIAS is used to create another alias of the CTE, if a CTE needs to appear multiple times in the main query.
|
||||||
ALIAS(alias string) SelectTable
|
ALIAS(alias string) SelectTable
|
||||||
|
|
||||||
|
|
@ -41,7 +41,7 @@ func CTE(name string, columns ...jet.ColumnExpression) CommonTableExpression {
|
||||||
}
|
}
|
||||||
|
|
||||||
// AS is used to define a CTE query
|
// AS is used to define a CTE query
|
||||||
func (c *commonTableExpression) AS(statement jet.SerializerStatement) CommonTableExpression {
|
func (c *commonTableExpression) AS(statement jet.SerializerHasProjections) CommonTableExpression {
|
||||||
c.CommonTableExpression.Statement = statement
|
c.CommonTableExpression.Statement = statement
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
@ -52,7 +52,7 @@ func (c *commonTableExpression) internalCTE() *jet.CommonTableExpression {
|
||||||
|
|
||||||
// ALIAS is used to create another alias of the CTE, if a CTE needs to appear multiple times in the main query.
|
// ALIAS is used to create another alias of the CTE, if a CTE needs to appear multiple times in the main query.
|
||||||
func (c *commonTableExpression) ALIAS(name string) SelectTable {
|
func (c *commonTableExpression) ALIAS(name string) SelectTable {
|
||||||
return newSelectTable(c, name)
|
return newSelectTable(c, name, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func toInternalCTE(ctes []CommonTableExpression) []*jet.CommonTableExpression {
|
func toInternalCTE(ctes []CommonTableExpression) []*jet.CommonTableExpression {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package postgres
|
package postgres
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"github.com/go-jet/jet/v2/internal/jet"
|
"github.com/go-jet/jet/v2/internal/jet"
|
||||||
"strconv"
|
"strconv"
|
||||||
)
|
)
|
||||||
|
|
@ -25,6 +26,9 @@ func newDialect() jet.Dialect {
|
||||||
return "$" + strconv.Itoa(ord)
|
return "$" + strconv.Itoa(ord)
|
||||||
},
|
},
|
||||||
ReservedWords: reservedWords,
|
ReservedWords: reservedWords,
|
||||||
|
ValuesDefaultColumnName: func(index int) string {
|
||||||
|
return fmt.Sprintf("column%d", index+1)
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
return jet.NewDialect(dialectParams)
|
return jet.NewDialect(dialectParams)
|
||||||
|
|
|
||||||
|
|
@ -46,33 +46,33 @@ func TestExists(t *testing.T) {
|
||||||
func TestIN(t *testing.T) {
|
func TestIN(t *testing.T) {
|
||||||
|
|
||||||
assertSerialize(t, Float(1.11).IN(table1.SELECT(table1Col1)),
|
assertSerialize(t, Float(1.11).IN(table1.SELECT(table1Col1)),
|
||||||
`($1 IN (
|
`($1 IN ((
|
||||||
SELECT table1.col1 AS "table1.col1"
|
SELECT table1.col1 AS "table1.col1"
|
||||||
FROM db.table1
|
FROM db.table1
|
||||||
))`, float64(1.11))
|
)))`, float64(1.11))
|
||||||
|
|
||||||
assertSerialize(t, ROW(Int(12), table1Col1).IN(table2.SELECT(table2Col3, table3Col1)),
|
assertSerialize(t, ROW(Int(12), table1Col1).IN(table2.SELECT(table2Col3, table3Col1)),
|
||||||
`(ROW($1, table1.col1) IN (
|
`(ROW($1, table1.col1) IN ((
|
||||||
SELECT table2.col3 AS "table2.col3",
|
SELECT table2.col3 AS "table2.col3",
|
||||||
table3.col1 AS "table3.col1"
|
table3.col1 AS "table3.col1"
|
||||||
FROM db.table2
|
FROM db.table2
|
||||||
))`, int64(12))
|
)))`, int64(12))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNOT_IN(t *testing.T) {
|
func TestNOT_IN(t *testing.T) {
|
||||||
|
|
||||||
assertSerialize(t, Float(1.11).NOT_IN(table1.SELECT(table1Col1)),
|
assertSerialize(t, Float(1.11).NOT_IN(table1.SELECT(table1Col1)),
|
||||||
`($1 NOT IN (
|
`($1 NOT IN ((
|
||||||
SELECT table1.col1 AS "table1.col1"
|
SELECT table1.col1 AS "table1.col1"
|
||||||
FROM db.table1
|
FROM db.table1
|
||||||
))`, float64(1.11))
|
)))`, float64(1.11))
|
||||||
|
|
||||||
assertSerialize(t, ROW(Int(12), table1Col1).NOT_IN(table2.SELECT(table2Col3, table3Col1)),
|
assertSerialize(t, ROW(Int(12), table1Col1).NOT_IN(table2.SELECT(table2Col3, table3Col1)),
|
||||||
`(ROW($1, table1.col1) NOT IN (
|
`(ROW($1, table1.col1) NOT IN ((
|
||||||
SELECT table2.col3 AS "table2.col3",
|
SELECT table2.col3 AS "table2.col3",
|
||||||
table3.col1 AS "table3.col1"
|
table3.col1 AS "table3.col1"
|
||||||
FROM db.table2
|
FROM db.table2
|
||||||
))`, int64(12))
|
)))`, int64(12))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestReservedWordEscaped(t *testing.T) {
|
func TestReservedWordEscaped(t *testing.T) {
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,9 @@ type TimestampExpression = jet.TimestampExpression
|
||||||
// TimestampzExpression interface
|
// TimestampzExpression interface
|
||||||
type TimestampzExpression = jet.TimestampzExpression
|
type TimestampzExpression = jet.TimestampzExpression
|
||||||
|
|
||||||
|
// RowExpression interface
|
||||||
|
type RowExpression = jet.RowExpression
|
||||||
|
|
||||||
// DateRange Expression interface
|
// DateRange Expression interface
|
||||||
type DateRange = jet.Range[DateExpression]
|
type DateRange = jet.Range[DateExpression]
|
||||||
|
|
||||||
|
|
@ -99,6 +102,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
|
||||||
|
|
||||||
|
// RowExp serves as a wrapper for an arbitrary expression, treating it as a row expression.
|
||||||
|
// This enables the Go compiler to interpret any expression as a row expression
|
||||||
|
// Note: This does not modify the generated SQL builder output by adding a SQL CAST operation.
|
||||||
|
var RowExp = jet.RowExp
|
||||||
|
|
||||||
// RangeExp is range expression wrapper around arbitrary expression.
|
// RangeExp is range expression wrapper around arbitrary expression.
|
||||||
// Allows go compiler to see any expression as range expression.
|
// Allows go compiler to see any expression as range expression.
|
||||||
// Does not add sql cast to generated sql builder output.
|
// Does not add sql cast to generated sql builder output.
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,9 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
// ROW function is used to create a tuple value that consists of a set of expressions or column values.
|
// ROW function is used to create a tuple value that consists of a set of expressions or column values.
|
||||||
var ROW = jet.ROW
|
func ROW(expressions ...Expression) RowExpression {
|
||||||
|
return jet.ROW(Dialect, expressions...)
|
||||||
|
}
|
||||||
|
|
||||||
// ------------------ Mathematical functions ---------------//
|
// ------------------ Mathematical functions ---------------//
|
||||||
|
|
||||||
|
|
@ -425,10 +427,12 @@ func castFloatLiteral(fraction FloatExpression) FloatExpression {
|
||||||
// ),
|
// ),
|
||||||
var GROUPING_SETS = jet.GROUPING_SETS
|
var GROUPING_SETS = jet.GROUPING_SETS
|
||||||
|
|
||||||
// WRAP wraps list of expressions with brackets - ( expression1, expression2, ... )
|
// WRAP surrounds a list of expressions or columns with parentheses, producing new row: (expression1, expression2, ...)
|
||||||
// The construct (a, b) is normally recognized in expressions as a row constructor. WRAP and ROW method behave exactly the same,
|
// The construct (a, b) is normally recognized in expressions as a row constructor. WRAP and ROW methods behave exactly the same,
|
||||||
// except when used in GROUPING_SETS. For top level GROUPING SETS expression lists WRAP has to be used.
|
// except when used in GROUPING_SETS and VALUES. In these contexts, WRAP must be used instead of ROW.
|
||||||
var WRAP = jet.WRAP
|
func WRAP(expressions ...Expression) RowExpression {
|
||||||
|
return jet.WRAP(Dialect, expressions...)
|
||||||
|
}
|
||||||
|
|
||||||
// ROLLUP operator is used with the GROUP BY clause to generate all prefixes of a group of columns including the empty list.
|
// ROLLUP operator is used with the GROUP BY clause to generate all prefixes of a group of columns including the empty list.
|
||||||
// It creates extra rows in the result set that represent the subtotal values for each combination of columns.
|
// It creates extra rows in the result set that represent the subtotal values for each combination of columns.
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
package postgres
|
package postgres
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/go-jet/jet/v2/internal/jet"
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
@ -151,7 +150,8 @@ func TestInsert_ON_CONFLICT(t *testing.T) {
|
||||||
VALUES("one", "two").
|
VALUES("one", "two").
|
||||||
VALUES("1", "2").
|
VALUES("1", "2").
|
||||||
VALUES("theta", "beta").
|
VALUES("theta", "beta").
|
||||||
ON_CONFLICT(table1ColBool).WHERE(table1ColBool.IS_NOT_FALSE()).DO_UPDATE(
|
ON_CONFLICT(table1ColBool).WHERE(table1ColBool.IS_NOT_FALSE()).
|
||||||
|
DO_UPDATE(
|
||||||
SET(table1ColBool.SET(Bool(true)),
|
SET(table1ColBool.SET(Bool(true)),
|
||||||
table2ColInt.SET(Int(1)),
|
table2ColInt.SET(Int(1)),
|
||||||
ColumnList{table1Col1, table1ColBool}.SET(ROW(Int(2), String("two"))),
|
ColumnList{table1Col1, table1ColBool}.SET(ROW(Int(2), String("two"))),
|
||||||
|
|
@ -178,12 +178,12 @@ func TestInsert_ON_CONFLICT_ON_CONSTRAINT(t *testing.T) {
|
||||||
stmt := table1.INSERT(table1Col1, table1ColBool).
|
stmt := table1.INSERT(table1Col1, table1ColBool).
|
||||||
VALUES("one", "two").
|
VALUES("one", "two").
|
||||||
VALUES("1", "2").
|
VALUES("1", "2").
|
||||||
ON_CONFLICT().ON_CONSTRAINT("idk_primary_key").DO_UPDATE(
|
ON_CONFLICT().ON_CONSTRAINT("idk_primary_key").
|
||||||
|
DO_UPDATE(
|
||||||
SET(table1ColBool.SET(Bool(false)),
|
SET(table1ColBool.SET(Bool(false)),
|
||||||
table2ColInt.SET(Int(1)),
|
table2ColInt.SET(Int(1)),
|
||||||
ColumnList{table1Col1, table1ColBool}.SET(jet.ROW(Int(2), String("two"))),
|
ColumnList{table1Col1, table1ColBool}.SET(ROW(Int(2), String("two"))),
|
||||||
).WHERE(table1Col1.GT(Int(2))),
|
).WHERE(table1Col1.GT(Int(2)))).
|
||||||
).
|
|
||||||
RETURNING(table1Col1, table1ColBool)
|
RETURNING(table1Col1, table1ColBool)
|
||||||
|
|
||||||
assertDebugStatementSql(t, stmt, `
|
assertDebugStatementSql(t, stmt, `
|
||||||
|
|
|
||||||
|
|
@ -57,6 +57,16 @@ func Uint64(value uint64) IntegerExpression {
|
||||||
// Float creates new float literal expression
|
// Float creates new float literal expression
|
||||||
var Float = jet.Float
|
var Float = jet.Float
|
||||||
|
|
||||||
|
// Float32 is constructor for 32 bit float literals
|
||||||
|
func Float32(value float32) FloatExpression {
|
||||||
|
return CAST(jet.Literal(value)).AS_REAL()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Float64 is constructor for 64 bit float literals
|
||||||
|
func Float64(value float64) FloatExpression {
|
||||||
|
return CAST(jet.Literal(value)).AS_DOUBLE()
|
||||||
|
}
|
||||||
|
|
||||||
// Decimal creates new float literal expression
|
// Decimal creates new float literal expression
|
||||||
var Decimal = jet.Decimal
|
var Decimal = jet.Decimal
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -181,7 +181,7 @@ func (s *selectStatementImpl) FOR(lock RowLock) SelectStatement {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *selectStatementImpl) AsTable(alias string) SelectTable {
|
func (s *selectStatementImpl) AsTable(alias string) SelectTable {
|
||||||
return newSelectTable(s, alias)
|
return newSelectTable(s, alias, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
//-----------------------------------------------------
|
//-----------------------------------------------------
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ package postgres
|
||||||
|
|
||||||
import "github.com/go-jet/jet/v2/internal/jet"
|
import "github.com/go-jet/jet/v2/internal/jet"
|
||||||
|
|
||||||
// SelectTable is interface for postgres sub-queries
|
// SelectTable is interface for postgres temporary tables like sub-queries, VALUES, CTEs etc...
|
||||||
type SelectTable interface {
|
type SelectTable interface {
|
||||||
readableTable
|
readableTable
|
||||||
jet.SelectTable
|
jet.SelectTable
|
||||||
|
|
@ -13,9 +13,9 @@ type selectTableImpl struct {
|
||||||
readableTableInterfaceImpl
|
readableTableInterfaceImpl
|
||||||
}
|
}
|
||||||
|
|
||||||
func newSelectTable(selectStmt jet.SerializerHasProjections, alias string) SelectTable {
|
func newSelectTable(serializerWithProjections jet.SerializerHasProjections, alias string, columnAliases []jet.ColumnExpression) SelectTable {
|
||||||
subQuery := &selectTableImpl{
|
subQuery := &selectTableImpl{
|
||||||
SelectTable: jet.NewSelectTable(selectStmt, alias),
|
SelectTable: jet.NewSelectTable(serializerWithProjections, alias, columnAliases),
|
||||||
}
|
}
|
||||||
|
|
||||||
subQuery.readableTableInterfaceImpl.parent = subQuery
|
subQuery.readableTableInterfaceImpl.parent = subQuery
|
||||||
|
|
|
||||||
|
|
@ -136,7 +136,7 @@ func (s *setStatementImpl) OFFSET_e(offset IntegerExpression) setStatement {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *setStatementImpl) AsTable(alias string) SelectTable {
|
func (s *setStatementImpl) AsTable(alias string) SelectTable {
|
||||||
return newSelectTable(s, alias)
|
return newSelectTable(s, alias, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
|
||||||
32
postgres/values.go
Normal file
32
postgres/values.go
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
package postgres
|
||||||
|
|
||||||
|
import "github.com/go-jet/jet/v2/internal/jet"
|
||||||
|
|
||||||
|
type values struct {
|
||||||
|
jet.Values
|
||||||
|
}
|
||||||
|
|
||||||
|
// VALUES is a table value constructor that computes a set of one or more rows as a temporary constant table.
|
||||||
|
// Each row is defined by the WRAP constructor, which takes one or more expressions.
|
||||||
|
//
|
||||||
|
// Example usage:
|
||||||
|
//
|
||||||
|
// VALUES(
|
||||||
|
// WRAP(Int32(204), Float32(1.21)),
|
||||||
|
// WRAP(Int32(207), Float32(1.02)),
|
||||||
|
// )
|
||||||
|
func VALUES(rows ...RowExpression) values {
|
||||||
|
return values{Values: jet.Values(rows)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AS assigns an alias to the temporary VALUES table, allowing it to be referenced
|
||||||
|
// within SQL FROM clauses, just like a regular table.
|
||||||
|
// By default, VALUES columns are named `column1`, `column2`, etc... Default column aliasing can be
|
||||||
|
// overwritten by passing new list of columns.
|
||||||
|
//
|
||||||
|
// Example usage:
|
||||||
|
//
|
||||||
|
// VALUES(...).AS("film_values", IntegerColumn("length"), TimestampColumn("update_date"))
|
||||||
|
func (v values) AS(alias string, columns ...Column) SelectTable {
|
||||||
|
return newSelectTable(v, alias, columns)
|
||||||
|
}
|
||||||
|
|
@ -6,7 +6,7 @@ import "github.com/go-jet/jet/v2/internal/jet"
|
||||||
type CommonTableExpression interface {
|
type CommonTableExpression interface {
|
||||||
SelectTable
|
SelectTable
|
||||||
|
|
||||||
AS(statement jet.SerializerStatement) CommonTableExpression
|
AS(statement jet.SerializerHasProjections) CommonTableExpression
|
||||||
AS_NOT_MATERIALIZED(statement jet.SerializerStatement) CommonTableExpression
|
AS_NOT_MATERIALIZED(statement jet.SerializerStatement) CommonTableExpression
|
||||||
// ALIAS is used to create another alias of the CTE, if a CTE needs to appear multiple times in the main query.
|
// ALIAS is used to create another alias of the CTE, if a CTE needs to appear multiple times in the main query.
|
||||||
ALIAS(alias string) SelectTable
|
ALIAS(alias string) SelectTable
|
||||||
|
|
@ -42,7 +42,7 @@ func CTE(name string, columns ...jet.ColumnExpression) CommonTableExpression {
|
||||||
}
|
}
|
||||||
|
|
||||||
// AS is used to define a CTE query
|
// AS is used to define a CTE query
|
||||||
func (c *commonTableExpression) AS(statement jet.SerializerStatement) CommonTableExpression {
|
func (c *commonTableExpression) AS(statement jet.SerializerHasProjections) CommonTableExpression {
|
||||||
c.CommonTableExpression.Statement = statement
|
c.CommonTableExpression.Statement = statement
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
@ -60,7 +60,7 @@ func (c *commonTableExpression) internalCTE() *jet.CommonTableExpression {
|
||||||
|
|
||||||
// ALIAS is used to create another alias of the CTE, if a CTE needs to appear multiple times in the main query.
|
// ALIAS is used to create another alias of the CTE, if a CTE needs to appear multiple times in the main query.
|
||||||
func (c *commonTableExpression) ALIAS(name string) SelectTable {
|
func (c *commonTableExpression) ALIAS(name string) SelectTable {
|
||||||
return newSelectTable(c, name)
|
return newSelectTable(c, name, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func toInternalCTE(ctes []CommonTableExpression) []*jet.CommonTableExpression {
|
func toInternalCTE(ctes []CommonTableExpression) []*jet.CommonTableExpression {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package sqlite
|
package sqlite
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"github.com/go-jet/jet/v2/internal/jet"
|
"github.com/go-jet/jet/v2/internal/jet"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -23,6 +24,9 @@ func newDialect() jet.Dialect {
|
||||||
return "?"
|
return "?"
|
||||||
},
|
},
|
||||||
ReservedWords: reservedWords2,
|
ReservedWords: reservedWords2,
|
||||||
|
ValuesDefaultColumnName: func(index int) string {
|
||||||
|
return fmt.Sprintf("column%d", index+1)
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
return jet.NewDialect(mySQLDialectParams)
|
return jet.NewDialect(mySQLDialectParams)
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,9 @@ type DateTimeExpression = jet.TimestampExpression
|
||||||
// TimestampExpression interface
|
// TimestampExpression interface
|
||||||
type TimestampExpression = jet.TimestampExpression
|
type TimestampExpression = jet.TimestampExpression
|
||||||
|
|
||||||
|
// RowExpression interface
|
||||||
|
type RowExpression = jet.RowExpression
|
||||||
|
|
||||||
// BoolExp is bool expression wrapper around arbitrary expression.
|
// BoolExp is bool expression wrapper around arbitrary expression.
|
||||||
// Allows go compiler to see any expression as bool expression.
|
// Allows go compiler to see any expression as bool expression.
|
||||||
// Does not add sql cast to generated sql builder output.
|
// Does not add sql cast to generated sql builder output.
|
||||||
|
|
@ -73,6 +76,11 @@ var DateTimeExp = jet.TimestampExp
|
||||||
// Does not add sql cast to generated sql builder output.
|
// Does not add sql cast to generated sql builder output.
|
||||||
var TimestampExp = jet.TimestampExp
|
var TimestampExp = jet.TimestampExp
|
||||||
|
|
||||||
|
// RowExp serves as a wrapper for an arbitrary expression, treating it as a row expression.
|
||||||
|
// This enables the Go compiler to interpret any expression as a row expression
|
||||||
|
// Note: This does not modify the generated SQL builder output by adding a SQL CAST operation.
|
||||||
|
var RowExp = jet.RowExp
|
||||||
|
|
||||||
// CustomExpression is used to define custom expressions.
|
// CustomExpression is used to define custom expressions.
|
||||||
var CustomExpression = jet.CustomExpression
|
var CustomExpression = jet.CustomExpression
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,9 +15,9 @@ var (
|
||||||
OR = jet.OR
|
OR = jet.OR
|
||||||
)
|
)
|
||||||
|
|
||||||
// ROW is construct one table row from list of expressions.
|
// ROW function is used to create a tuple value that consists of a set of expressions or column values.
|
||||||
func ROW(expressions ...Expression) Expression {
|
func ROW(expressions ...Expression) RowExpression {
|
||||||
return jet.NewFunc("", expressions, nil)
|
return jet.WRAP(Dialect, expressions...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------ Mathematical functions ---------------//
|
// ------------------ Mathematical functions ---------------//
|
||||||
|
|
|
||||||
|
|
@ -155,7 +155,7 @@ func (s *selectStatementImpl) LOCK_IN_SHARE_MODE() SelectStatement {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *selectStatementImpl) AsTable(alias string) SelectTable {
|
func (s *selectStatementImpl) AsTable(alias string) SelectTable {
|
||||||
return newSelectTable(s, alias)
|
return newSelectTable(s, alias, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
//-----------------------------------------------------
|
//-----------------------------------------------------
|
||||||
|
|
|
||||||
|
|
@ -13,9 +13,9 @@ type selectTableImpl struct {
|
||||||
readableTableInterfaceImpl
|
readableTableInterfaceImpl
|
||||||
}
|
}
|
||||||
|
|
||||||
func newSelectTable(selectStmt jet.SerializerHasProjections, alias string) SelectTable {
|
func newSelectTable(selectStmt jet.SerializerHasProjections, alias string, columnAliases []jet.ColumnExpression) SelectTable {
|
||||||
subQuery := &selectTableImpl{
|
subQuery := &selectTableImpl{
|
||||||
SelectTable: jet.NewSelectTable(selectStmt, alias),
|
SelectTable: jet.NewSelectTable(selectStmt, alias, columnAliases),
|
||||||
}
|
}
|
||||||
|
|
||||||
subQuery.readableTableInterfaceImpl.parent = subQuery
|
subQuery.readableTableInterfaceImpl.parent = subQuery
|
||||||
|
|
|
||||||
|
|
@ -86,7 +86,7 @@ func (s *setStatementImpl) OFFSET(offset int64) setStatement {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *setStatementImpl) AsTable(alias string) SelectTable {
|
func (s *setStatementImpl) AsTable(alias string) SelectTable {
|
||||||
return newSelectTable(s, alias)
|
return newSelectTable(s, alias, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
|
||||||
26
sqlite/values.go
Normal file
26
sqlite/values.go
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
package sqlite
|
||||||
|
|
||||||
|
import "github.com/go-jet/jet/v2/internal/jet"
|
||||||
|
|
||||||
|
type values struct {
|
||||||
|
jet.Values
|
||||||
|
}
|
||||||
|
|
||||||
|
// VALUES is a table value constructor that computes a set of one or more rows as a temporary constant table.
|
||||||
|
// Each row is defined by the ROW constructor, which takes one or more expressions.
|
||||||
|
//
|
||||||
|
// Example usage:
|
||||||
|
//
|
||||||
|
// VALUES(
|
||||||
|
// ROW(Int32(204), Float32(1.21)),
|
||||||
|
// ROW(Int32(207), Float32(1.02)),
|
||||||
|
// )
|
||||||
|
func VALUES(rows ...RowExpression) values {
|
||||||
|
return values{Values: jet.Values(rows)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AS assigns an alias to the temporary VALUES table, allowing it to be referenced
|
||||||
|
// within SQL FROM clauses, just like a regular table.
|
||||||
|
func (v values) AS(alias string) SelectTable {
|
||||||
|
return newSelectTable(v, alias, nil)
|
||||||
|
}
|
||||||
|
|
@ -6,8 +6,8 @@ import "github.com/go-jet/jet/v2/internal/jet"
|
||||||
type CommonTableExpression interface {
|
type CommonTableExpression interface {
|
||||||
SelectTable
|
SelectTable
|
||||||
|
|
||||||
AS(statement jet.SerializerStatement) CommonTableExpression
|
AS(statement jet.SerializerHasProjections) CommonTableExpression
|
||||||
AS_NOT_MATERIALIZED(statement jet.SerializerStatement) CommonTableExpression
|
AS_NOT_MATERIALIZED(statement jet.SerializerHasProjections) CommonTableExpression
|
||||||
// ALIAS is used to create another alias of the CTE, if a CTE needs to appear multiple times in the main query.
|
// ALIAS is used to create another alias of the CTE, if a CTE needs to appear multiple times in the main query.
|
||||||
ALIAS(alias string) SelectTable
|
ALIAS(alias string) SelectTable
|
||||||
|
|
||||||
|
|
@ -42,13 +42,13 @@ func CTE(name string, columns ...jet.ColumnExpression) CommonTableExpression {
|
||||||
}
|
}
|
||||||
|
|
||||||
// AS is used to define a CTE query
|
// AS is used to define a CTE query
|
||||||
func (c *commonTableExpression) AS(statement jet.SerializerStatement) CommonTableExpression {
|
func (c *commonTableExpression) AS(statement jet.SerializerHasProjections) CommonTableExpression {
|
||||||
c.CommonTableExpression.Statement = statement
|
c.CommonTableExpression.Statement = statement
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
// AS_NOT_MATERIALIZED is used to define not materialized CTE query
|
// AS_NOT_MATERIALIZED is used to define not materialized CTE query
|
||||||
func (c *commonTableExpression) AS_NOT_MATERIALIZED(statement jet.SerializerStatement) CommonTableExpression {
|
func (c *commonTableExpression) AS_NOT_MATERIALIZED(statement jet.SerializerHasProjections) CommonTableExpression {
|
||||||
c.CommonTableExpression.NotMaterialized = true
|
c.CommonTableExpression.NotMaterialized = true
|
||||||
c.CommonTableExpression.Statement = statement
|
c.CommonTableExpression.Statement = statement
|
||||||
return c
|
return c
|
||||||
|
|
@ -60,7 +60,7 @@ func (c *commonTableExpression) internalCTE() *jet.CommonTableExpression {
|
||||||
|
|
||||||
// ALIAS is used to create another alias of the CTE, if a CTE needs to appear multiple times in the main query.
|
// ALIAS is used to create another alias of the CTE, if a CTE needs to appear multiple times in the main query.
|
||||||
func (c *commonTableExpression) ALIAS(name string) SelectTable {
|
func (c *commonTableExpression) ALIAS(name string) SelectTable {
|
||||||
return newSelectTable(c, name)
|
return newSelectTable(c, name, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func toInternalCTE(ctes []CommonTableExpression) []*jet.CommonTableExpression {
|
func toInternalCTE(ctes []CommonTableExpression) []*jet.CommonTableExpression {
|
||||||
|
|
|
||||||
|
|
@ -97,18 +97,18 @@ func TestExpressionOperators(t *testing.T) {
|
||||||
SELECT all_types.'integer' IS NULL AS "result.is_null",
|
SELECT all_types.'integer' IS NULL AS "result.is_null",
|
||||||
all_types.date_ptr IS NOT NULL AS "result.is_not_null",
|
all_types.date_ptr IS NOT NULL AS "result.is_not_null",
|
||||||
(all_types.small_int_ptr IN (?, ?)) AS "result.in",
|
(all_types.small_int_ptr IN (?, ?)) AS "result.in",
|
||||||
(all_types.small_int_ptr IN (
|
(all_types.small_int_ptr IN ((
|
||||||
SELECT all_types.'integer' AS "all_types.integer"
|
SELECT all_types.'integer' AS "all_types.integer"
|
||||||
FROM test_sample.all_types
|
FROM test_sample.all_types
|
||||||
)) AS "result.in_select",
|
))) AS "result.in_select",
|
||||||
(CURRENT_USER()) AS "result.raw",
|
(CURRENT_USER()) AS "result.raw",
|
||||||
(? + COALESCE(all_types.small_int_ptr, 0) + ?) AS "result.raw_arg",
|
(? + COALESCE(all_types.small_int_ptr, 0) + ?) AS "result.raw_arg",
|
||||||
(? + all_types.integer + ? + ? + ? + ?) AS "result.raw_arg2",
|
(? + all_types.integer + ? + ? + ? + ?) AS "result.raw_arg2",
|
||||||
(all_types.small_int_ptr NOT IN (?, ?, NULL)) AS "result.not_in",
|
(all_types.small_int_ptr NOT IN (?, ?, NULL)) AS "result.not_in",
|
||||||
(all_types.small_int_ptr NOT IN (
|
(all_types.small_int_ptr NOT IN ((
|
||||||
SELECT all_types.'integer' AS "all_types.integer"
|
SELECT all_types.'integer' AS "all_types.integer"
|
||||||
FROM test_sample.all_types
|
FROM test_sample.all_types
|
||||||
)) AS "result.not_in_select"
|
))) AS "result.not_in_select"
|
||||||
FROM test_sample.all_types
|
FROM test_sample.all_types
|
||||||
LIMIT ?;
|
LIMIT ?;
|
||||||
`, "'", "`", -1), int64(11), int64(22), 78, 56, 11, 22, 11, 33, 44, int64(11), int64(22), int64(2))
|
`, "'", "`", -1), int64(11), int64(22), 78, 56, 11, 22, 11, 33, 44, int64(11), int64(22), int64(2))
|
||||||
|
|
@ -1404,3 +1404,34 @@ VALUES ('91.23', '45.67', '12.35', '56.79', 0.2, 0.22, 0.3, 0.33, 0.4, 0.44);
|
||||||
require.Equal(t, 45.67, *result.Floats.DecimalPtr)
|
require.Equal(t, 45.67, *result.Floats.DecimalPtr)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRowExpression(t *testing.T) {
|
||||||
|
now := time.Now()
|
||||||
|
nowAddHour := time.Now().Add(time.Hour)
|
||||||
|
|
||||||
|
stmt := SELECT(
|
||||||
|
ROW(Bool(false), DateT(now)).EQ(ROW(Bool(true), DateT(now))),
|
||||||
|
ROW(Bool(false), DateT(now)).NOT_EQ(ROW(Bool(true), DateT(now))),
|
||||||
|
ROW(TimestampT(nowAddHour), String("txt")).IS_DISTINCT_FROM(RowExp(Raw("row(NOW(), 'png')"))),
|
||||||
|
ROW(TimestampT(now), DateTimeT(nowAddHour)).GT(ROW(TimestampT(now), DateTimeT(now))),
|
||||||
|
ROW(DateTimeT(nowAddHour), Int(1)).GT_EQ(ROW(DateTimeT(now), Int(2))),
|
||||||
|
ROW(TimestampT(now), DateTimeT(nowAddHour)).LT(ROW(TimestampT(now), DateTimeT(now))),
|
||||||
|
ROW(DateTimeT(nowAddHour), Float(1.22)).LT_EQ(ROW(DateTimeT(now), Float(2.33))),
|
||||||
|
)
|
||||||
|
|
||||||
|
//fmt.Println(stmt.Sql())
|
||||||
|
//fmt.Println(stmt.DebugSql())
|
||||||
|
|
||||||
|
testutils.AssertStatementSql(t, stmt, `
|
||||||
|
SELECT ROW(?, CAST(? AS DATE)) = ROW(?, CAST(? AS DATE)),
|
||||||
|
ROW(?, CAST(? AS DATE)) != ROW(?, CAST(? AS DATE)),
|
||||||
|
NOT(ROW(TIMESTAMP(?), ?) <=> (row(NOW(), 'png'))),
|
||||||
|
ROW(TIMESTAMP(?), CAST(? AS DATETIME)) > ROW(TIMESTAMP(?), CAST(? AS DATETIME)),
|
||||||
|
ROW(CAST(? AS DATETIME), ?) >= ROW(CAST(? AS DATETIME), ?),
|
||||||
|
ROW(TIMESTAMP(?), CAST(? AS DATETIME)) < ROW(TIMESTAMP(?), CAST(? AS DATETIME)),
|
||||||
|
ROW(CAST(? AS DATETIME), ?) <= ROW(CAST(? AS DATETIME), ?);
|
||||||
|
`)
|
||||||
|
|
||||||
|
err := stmt.Query(db, &struct{}{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -92,3 +92,9 @@ func skipForMariaDB(t *testing.T) {
|
||||||
t.SkipNow()
|
t.SkipNow()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func onlyMariaDB(t *testing.T) {
|
||||||
|
if !sourceIsMariaDB() {
|
||||||
|
t.SkipNow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
347
tests/mysql/values_test.go
Normal file
347
tests/mysql/values_test.go
Normal file
|
|
@ -0,0 +1,347 @@
|
||||||
|
package mysql
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/go-jet/jet/v2/internal/testutils"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
. "github.com/go-jet/jet/v2/mysql"
|
||||||
|
|
||||||
|
"github.com/go-jet/jet/v2/tests/.gentestdata/mysql/dvds/model"
|
||||||
|
. "github.com/go-jet/jet/v2/tests/.gentestdata/mysql/dvds/table"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestVALUES(t *testing.T) {
|
||||||
|
skipForMariaDB(t)
|
||||||
|
|
||||||
|
valuesTable := VALUES(
|
||||||
|
ROW(Int32(1), Int32(2), Float(4.666), Bool(false), String("txt")),
|
||||||
|
ROW(Int32(11).ADD(Int32(2)), Int32(22), Float(33.222), Bool(true), String("png")),
|
||||||
|
ROW(Int32(11), Int32(22), Float(33.222), Bool(true), NULL),
|
||||||
|
).AS("values_table")
|
||||||
|
|
||||||
|
stmt := SELECT(
|
||||||
|
valuesTable.AllColumns(),
|
||||||
|
).FROM(
|
||||||
|
valuesTable,
|
||||||
|
)
|
||||||
|
|
||||||
|
testutils.AssertStatementSql(t, stmt, `
|
||||||
|
SELECT values_table.column_0 AS "column_0",
|
||||||
|
values_table.column_1 AS "column_1",
|
||||||
|
values_table.column_2 AS "column_2",
|
||||||
|
values_table.column_3 AS "column_3",
|
||||||
|
values_table.column_4 AS "column_4"
|
||||||
|
FROM (
|
||||||
|
VALUES ROW(?, ?, ?, ?, ?),
|
||||||
|
ROW(? + ?, ?, ?, ?, ?),
|
||||||
|
ROW(?, ?, ?, ?, NULL)
|
||||||
|
) AS values_table;
|
||||||
|
`)
|
||||||
|
|
||||||
|
var dest []struct {
|
||||||
|
Column0 int
|
||||||
|
Column1 int
|
||||||
|
Column2 float32
|
||||||
|
Column3 bool
|
||||||
|
Column4 *string
|
||||||
|
}
|
||||||
|
|
||||||
|
err := stmt.Query(db, &dest)
|
||||||
|
|
||||||
|
require.NoError(t, err)
|
||||||
|
testutils.AssertJSON(t, dest, `
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"Column0": 1,
|
||||||
|
"Column1": 2,
|
||||||
|
"Column2": 4.666,
|
||||||
|
"Column3": false,
|
||||||
|
"Column4": "txt"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Column0": 13,
|
||||||
|
"Column1": 22,
|
||||||
|
"Column2": 33.222,
|
||||||
|
"Column3": true,
|
||||||
|
"Column4": "png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Column0": 11,
|
||||||
|
"Column1": 22,
|
||||||
|
"Column2": 33.222,
|
||||||
|
"Column3": true,
|
||||||
|
"Column4": null
|
||||||
|
}
|
||||||
|
]
|
||||||
|
`)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVALUES_Join(t *testing.T) {
|
||||||
|
skipForMariaDB(t)
|
||||||
|
|
||||||
|
title := StringColumn("title")
|
||||||
|
releaseYear := IntegerColumn("ReleaseYear")
|
||||||
|
rentalRate := FloatColumn("rental_rate")
|
||||||
|
|
||||||
|
lastUpdate := Timestamp(2007, time.February, 11, 12, 0, 0)
|
||||||
|
|
||||||
|
films := VALUES(
|
||||||
|
ROW(String("Chamber Italian"), Int64(117), Int32(2005), Float(5.82), lastUpdate),
|
||||||
|
ROW(String("Grosse Wonderful"), Int64(49), Int32(2004), Float(6.242), lastUpdate.ADD(INTERVAL(1, HOUR))),
|
||||||
|
ROW(String("Airport Pollock"), Int64(54), Int32(2001), Float(7.22), NULL),
|
||||||
|
ROW(String("Bright Encounters"), Int64(73), Int32(2002), Float(8.25), NULL),
|
||||||
|
ROW(String("Academy Dinosaur"), Int64(83), Int32(2010), Float(9.22), lastUpdate.SUB(INTERVAL(2, MINUTE))),
|
||||||
|
).AS("film_values",
|
||||||
|
title, IntegerColumn("length"), releaseYear, rentalRate, TimestampColumn("last_update"))
|
||||||
|
|
||||||
|
stmt := SELECT(
|
||||||
|
Film.AllColumns,
|
||||||
|
films.AllColumns(),
|
||||||
|
).FROM(
|
||||||
|
Film.
|
||||||
|
INNER_JOIN(films, title.EQ(Film.Title)),
|
||||||
|
).WHERE(AND(
|
||||||
|
Film.ReleaseYear.GT(releaseYear),
|
||||||
|
Film.RentalRate.LT(rentalRate),
|
||||||
|
)).ORDER_BY(
|
||||||
|
title,
|
||||||
|
)
|
||||||
|
|
||||||
|
testutils.AssertDebugStatementSql(t, stmt, strings.ReplaceAll(`
|
||||||
|
SELECT film.film_id AS "film.film_id",
|
||||||
|
film.title AS "film.title",
|
||||||
|
film.description AS "film.description",
|
||||||
|
film.release_year AS "film.release_year",
|
||||||
|
film.language_id AS "film.language_id",
|
||||||
|
film.original_language_id AS "film.original_language_id",
|
||||||
|
film.rental_duration AS "film.rental_duration",
|
||||||
|
film.rental_rate AS "film.rental_rate",
|
||||||
|
film.length AS "film.length",
|
||||||
|
film.replacement_cost AS "film.replacement_cost",
|
||||||
|
film.rating AS "film.rating",
|
||||||
|
film.special_features AS "film.special_features",
|
||||||
|
film.last_update AS "film.last_update",
|
||||||
|
film_values.title AS "title",
|
||||||
|
film_values.length AS "length",
|
||||||
|
film_values.''ReleaseYear'' AS "ReleaseYear",
|
||||||
|
film_values.rental_rate AS "rental_rate",
|
||||||
|
film_values.last_update AS "last_update"
|
||||||
|
FROM dvds.film
|
||||||
|
INNER JOIN (
|
||||||
|
VALUES ROW('Chamber Italian', 117, 2005, 5.82, TIMESTAMP('2007-02-11 12:00:00')),
|
||||||
|
ROW('Grosse Wonderful', 49, 2004, 6.242, TIMESTAMP('2007-02-11 12:00:00') + INTERVAL 1 HOUR),
|
||||||
|
ROW('Airport Pollock', 54, 2001, 7.22, NULL),
|
||||||
|
ROW('Bright Encounters', 73, 2002, 8.25, NULL),
|
||||||
|
ROW('Academy Dinosaur', 83, 2010, 9.22, TIMESTAMP('2007-02-11 12:00:00') - INTERVAL 2 MINUTE)
|
||||||
|
) AS film_values (title, length, ''ReleaseYear'', rental_rate, last_update) ON (film_values.title = film.title)
|
||||||
|
WHERE (
|
||||||
|
(film.release_year > film_values.''ReleaseYear'')
|
||||||
|
AND (film.rental_rate < film_values.rental_rate)
|
||||||
|
)
|
||||||
|
ORDER BY film_values.title;
|
||||||
|
`, "''", "`"))
|
||||||
|
|
||||||
|
var dest []struct {
|
||||||
|
Film model.Film
|
||||||
|
|
||||||
|
Title string
|
||||||
|
Length int
|
||||||
|
ReleaseYear int
|
||||||
|
RentalRate float32
|
||||||
|
LastUpdate *time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
err := stmt.Query(db, &dest)
|
||||||
|
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, dest, 4)
|
||||||
|
testutils.AssertJSON(t, dest[0:2], `
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"Film": {
|
||||||
|
"FilmID": 8,
|
||||||
|
"Title": "AIRPORT POLLOCK",
|
||||||
|
"Description": "A Epic Tale of a Moose And a Girl who must Confront a Monkey in Ancient India",
|
||||||
|
"ReleaseYear": 2006,
|
||||||
|
"LanguageID": 1,
|
||||||
|
"OriginalLanguageID": null,
|
||||||
|
"RentalDuration": 6,
|
||||||
|
"RentalRate": 4.99,
|
||||||
|
"Length": 54,
|
||||||
|
"ReplacementCost": 15.99,
|
||||||
|
"Rating": "R",
|
||||||
|
"SpecialFeatures": "Trailers",
|
||||||
|
"LastUpdate": "2006-02-15T05:03:42Z"
|
||||||
|
},
|
||||||
|
"Title": "Airport Pollock",
|
||||||
|
"Length": 54,
|
||||||
|
"ReleaseYear": 2001,
|
||||||
|
"RentalRate": 7.22,
|
||||||
|
"LastUpdate": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Film": {
|
||||||
|
"FilmID": 98,
|
||||||
|
"Title": "BRIGHT ENCOUNTERS",
|
||||||
|
"Description": "A Fateful Yarn of a Lumberjack And a Feminist who must Conquer a Student in A Jet Boat",
|
||||||
|
"ReleaseYear": 2006,
|
||||||
|
"LanguageID": 1,
|
||||||
|
"OriginalLanguageID": null,
|
||||||
|
"RentalDuration": 4,
|
||||||
|
"RentalRate": 4.99,
|
||||||
|
"Length": 73,
|
||||||
|
"ReplacementCost": 12.99,
|
||||||
|
"Rating": "PG-13",
|
||||||
|
"SpecialFeatures": "Trailers",
|
||||||
|
"LastUpdate": "2006-02-15T05:03:42Z"
|
||||||
|
},
|
||||||
|
"Title": "Bright Encounters",
|
||||||
|
"Length": 73,
|
||||||
|
"ReleaseYear": 2002,
|
||||||
|
"RentalRate": 8.25,
|
||||||
|
"LastUpdate": null
|
||||||
|
}
|
||||||
|
]
|
||||||
|
`)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVALUES_CTE_Update(t *testing.T) {
|
||||||
|
skipForMariaDB(t)
|
||||||
|
|
||||||
|
paymentID := IntegerColumn("payment_id")
|
||||||
|
increase := FloatColumn("increase")
|
||||||
|
paymentsToUpdate := CTE("values_cte", paymentID, increase)
|
||||||
|
|
||||||
|
stmt := WITH(
|
||||||
|
paymentsToUpdate.AS(
|
||||||
|
VALUES(
|
||||||
|
ROW(Int32(204), Float(1.21)),
|
||||||
|
ROW(Int32(207), Float(1.02)),
|
||||||
|
ROW(Int32(200), Float(1.34)),
|
||||||
|
ROW(Int32(203), Float(1.72)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)(
|
||||||
|
Payment.INNER_JOIN(paymentsToUpdate, paymentID.EQ(Payment.PaymentID)).
|
||||||
|
UPDATE().
|
||||||
|
SET(
|
||||||
|
Payment.Amount.SET(Payment.Amount.MUL(increase)),
|
||||||
|
).WHERE(Bool(true)),
|
||||||
|
)
|
||||||
|
|
||||||
|
testutils.AssertStatementSql(t, stmt, `
|
||||||
|
WITH values_cte (payment_id, increase) AS (
|
||||||
|
VALUES ROW(?, ?),
|
||||||
|
ROW(?, ?),
|
||||||
|
ROW(?, ?),
|
||||||
|
ROW(?, ?)
|
||||||
|
)
|
||||||
|
UPDATE dvds.payment
|
||||||
|
INNER JOIN values_cte ON (values_cte.payment_id = payment.payment_id)
|
||||||
|
SET amount = (payment.amount * values_cte.increase)
|
||||||
|
WHERE ?;
|
||||||
|
`)
|
||||||
|
|
||||||
|
testutils.AssertExecAndRollback(t, stmt, db, 4)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVALUES_MariaDB(t *testing.T) {
|
||||||
|
onlyMariaDB(t) // mariadb won't accept values rows if all the elements are placeholders, so we have to use raw statement
|
||||||
|
|
||||||
|
paymentID := IntegerColumn("payment_id")
|
||||||
|
increase := FloatColumn("increase")
|
||||||
|
paymentsToUpdate := CTE("values_cte", paymentID, increase)
|
||||||
|
|
||||||
|
stmt := WITH(
|
||||||
|
paymentsToUpdate.AS(
|
||||||
|
RawStatement(`
|
||||||
|
VALUES (204, 1.21),
|
||||||
|
(207, 1.02),
|
||||||
|
(200, 1.34),
|
||||||
|
(203, 1.72)
|
||||||
|
`),
|
||||||
|
),
|
||||||
|
)(
|
||||||
|
SELECT(
|
||||||
|
Payment.AllColumns,
|
||||||
|
paymentsToUpdate.AllColumns(),
|
||||||
|
).FROM(
|
||||||
|
Payment.
|
||||||
|
INNER_JOIN(paymentsToUpdate, paymentID.EQ(Payment.PaymentID)),
|
||||||
|
).WHERE(
|
||||||
|
increase.GT(Float(1.03)),
|
||||||
|
).ORDER_BY(
|
||||||
|
increase,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
testutils.AssertStatementSql(t, stmt, `
|
||||||
|
WITH values_cte (payment_id, increase) AS (
|
||||||
|
VALUES (204, 1.21),
|
||||||
|
(207, 1.02),
|
||||||
|
(200, 1.34),
|
||||||
|
(203, 1.72)
|
||||||
|
|
||||||
|
)
|
||||||
|
SELECT payment.payment_id AS "payment.payment_id",
|
||||||
|
payment.customer_id AS "payment.customer_id",
|
||||||
|
payment.staff_id AS "payment.staff_id",
|
||||||
|
payment.rental_id AS "payment.rental_id",
|
||||||
|
payment.amount AS "payment.amount",
|
||||||
|
payment.payment_date AS "payment.payment_date",
|
||||||
|
payment.last_update AS "payment.last_update",
|
||||||
|
values_cte.payment_id AS "payment_id",
|
||||||
|
values_cte.increase AS "increase"
|
||||||
|
FROM dvds.payment
|
||||||
|
INNER JOIN values_cte ON (values_cte.payment_id = payment.payment_id)
|
||||||
|
WHERE values_cte.increase > ?
|
||||||
|
ORDER BY values_cte.increase;
|
||||||
|
`)
|
||||||
|
|
||||||
|
var dest []struct {
|
||||||
|
model.Payment
|
||||||
|
|
||||||
|
Increase float64
|
||||||
|
}
|
||||||
|
|
||||||
|
err := stmt.Query(db, &dest)
|
||||||
|
|
||||||
|
require.NoError(t, err)
|
||||||
|
testutils.AssertJSON(t, dest, `
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"PaymentID": 204,
|
||||||
|
"CustomerID": 7,
|
||||||
|
"StaffID": 1,
|
||||||
|
"RentalID": 13476,
|
||||||
|
"Amount": 2.99,
|
||||||
|
"PaymentDate": "2005-08-20T01:06:04Z",
|
||||||
|
"LastUpdate": "2006-02-15T22:12:31Z",
|
||||||
|
"Increase": 1.21
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"PaymentID": 200,
|
||||||
|
"CustomerID": 7,
|
||||||
|
"StaffID": 2,
|
||||||
|
"RentalID": 11542,
|
||||||
|
"Amount": 7.99,
|
||||||
|
"PaymentDate": "2005-08-17T00:51:32Z",
|
||||||
|
"LastUpdate": "2006-02-15T22:12:31Z",
|
||||||
|
"Increase": 1.34
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"PaymentID": 203,
|
||||||
|
"CustomerID": 7,
|
||||||
|
"StaffID": 2,
|
||||||
|
"RentalID": 13373,
|
||||||
|
"Amount": 2.99,
|
||||||
|
"PaymentDate": "2005-08-19T21:23:31Z",
|
||||||
|
"LastUpdate": "2006-02-15T22:12:31Z",
|
||||||
|
"Increase": 1.72
|
||||||
|
}
|
||||||
|
]
|
||||||
|
`)
|
||||||
|
}
|
||||||
|
|
@ -164,10 +164,10 @@ WITH payments_to_delete AS (
|
||||||
WHERE payment.amount < 0.5
|
WHERE payment.amount < 0.5
|
||||||
)
|
)
|
||||||
DELETE FROM dvds.payment
|
DELETE FROM dvds.payment
|
||||||
WHERE payment.payment_id IN (
|
WHERE payment.payment_id IN ((
|
||||||
SELECT payments_to_delete.''payment.payment_id'' AS "payment.payment_id"
|
SELECT payments_to_delete.''payment.payment_id'' AS "payment.payment_id"
|
||||||
FROM payments_to_delete
|
FROM payments_to_delete
|
||||||
);
|
));
|
||||||
`, "''", "`"))
|
`, "''", "`"))
|
||||||
|
|
||||||
tx, err := db.Begin()
|
tx, err := db.Begin()
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"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/stretchr/testify/assert"
|
||||||
"log/slog"
|
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
|
@ -347,24 +347,22 @@ func TestExpressionOperators(t *testing.T) {
|
||||||
AllTypes.SmallIntPtr.NOT_IN(AllTypes.SELECT(AllTypes.Integer)).AS("result.not_in_select"),
|
AllTypes.SmallIntPtr.NOT_IN(AllTypes.SELECT(AllTypes.Integer)).AS("result.not_in_select"),
|
||||||
).LIMIT(2)
|
).LIMIT(2)
|
||||||
|
|
||||||
//fmt.Println(query.Sql())
|
|
||||||
|
|
||||||
testutils.AssertStatementSql(t, query, `
|
testutils.AssertStatementSql(t, query, `
|
||||||
SELECT all_types.integer IS NULL AS "result.is_null",
|
SELECT all_types.integer IS NULL AS "result.is_null",
|
||||||
all_types.date_ptr IS NOT NULL AS "result.is_not_null",
|
all_types.date_ptr IS NOT NULL AS "result.is_not_null",
|
||||||
(all_types.small_int_ptr IN ($1::smallint, $2::smallint)) AS "result.in",
|
(all_types.small_int_ptr IN ($1::smallint, $2::smallint)) AS "result.in",
|
||||||
(all_types.small_int_ptr IN (
|
(all_types.small_int_ptr IN ((
|
||||||
SELECT all_types.integer AS "all_types.integer"
|
SELECT all_types.integer AS "all_types.integer"
|
||||||
FROM test_sample.all_types
|
FROM test_sample.all_types
|
||||||
)) AS "result.in_select",
|
))) AS "result.in_select",
|
||||||
(CURRENT_USER) AS "result.raw",
|
(CURRENT_USER) AS "result.raw",
|
||||||
($3 + COALESCE(all_types.small_int_ptr, 0) + $4) AS "result.raw_arg",
|
($3 + COALESCE(all_types.small_int_ptr, 0) + $4) AS "result.raw_arg",
|
||||||
($5 + all_types.integer + $6 + $5 + $7 + $8) AS "result.raw_arg2",
|
($5 + all_types.integer + $6 + $5 + $7 + $8) AS "result.raw_arg2",
|
||||||
(all_types.small_int_ptr NOT IN ($9, $10::smallint, NULL)) AS "result.not_in",
|
(all_types.small_int_ptr NOT IN ($9, $10::smallint, NULL)) AS "result.not_in",
|
||||||
(all_types.small_int_ptr NOT IN (
|
(all_types.small_int_ptr NOT IN ((
|
||||||
SELECT all_types.integer AS "all_types.integer"
|
SELECT all_types.integer AS "all_types.integer"
|
||||||
FROM test_sample.all_types
|
FROM test_sample.all_types
|
||||||
)) AS "result.not_in_select"
|
))) AS "result.not_in_select"
|
||||||
FROM test_sample.all_types
|
FROM test_sample.all_types
|
||||||
LIMIT $11;
|
LIMIT $11;
|
||||||
`, int8(11), int8(22), 78, 56, 11, 22, 33, 44, int64(11), int16(22), int64(2))
|
`, int8(11), int8(22), 78, 56, 11, 22, 33, 44, int64(11), int16(22), int64(2))
|
||||||
|
|
@ -376,9 +374,6 @@ LIMIT $11;
|
||||||
err := query.Query(db, &dest)
|
err := query.Query(db, &dest)
|
||||||
|
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
//testutils.PrintJson(dest)
|
|
||||||
|
|
||||||
testutils.AssertJSON(t, dest, `
|
testutils.AssertJSON(t, dest, `
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
|
|
@ -936,7 +931,7 @@ func TestTimeExpression(t *testing.T) {
|
||||||
func TestIntervalSetFunctionality(t *testing.T) {
|
func TestIntervalSetFunctionality(t *testing.T) {
|
||||||
|
|
||||||
t.Run("updateQueryIntervalTest", func(t *testing.T) {
|
t.Run("updateQueryIntervalTest", func(t *testing.T) {
|
||||||
slog.Info("Running test", slog.Any("test", t.Name()))
|
|
||||||
expectedQuery := `
|
expectedQuery := `
|
||||||
UPDATE test_sample.employee
|
UPDATE test_sample.employee
|
||||||
SET pto_accrual = INTERVAL '3 HOUR'
|
SET pto_accrual = INTERVAL '3 HOUR'
|
||||||
|
|
@ -1111,6 +1106,46 @@ FROM test_sample.all_types;
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRowExpression(t *testing.T) {
|
||||||
|
now := time.Now()
|
||||||
|
nowAddHour := time.Now().Add(time.Hour)
|
||||||
|
|
||||||
|
stmt := SELECT(
|
||||||
|
ROW(Int32(1), Float32(11.22), String("john")).AS("row"),
|
||||||
|
WRAP(Int64(1), Float64(11.22), String("john")).AS("wrap"),
|
||||||
|
|
||||||
|
ROW(Bool(false), DateT(now)).EQ(ROW(Bool(true), DateT(now))),
|
||||||
|
WRAP(Bool(false), DateT(now)).NOT_EQ(WRAP(Bool(true), DateT(now))),
|
||||||
|
|
||||||
|
ROW(TimeT(nowAddHour)).IS_DISTINCT_FROM(RowExp(Raw("row(NOW()::time)"))),
|
||||||
|
ROW().IS_NOT_DISTINCT_FROM(ROW()),
|
||||||
|
|
||||||
|
ROW(TimestampT(now), TimestampzT(nowAddHour)).GT(WRAP(TimestampT(now), TimestampzT(now))),
|
||||||
|
ROW(TimestampzT(nowAddHour)).GT_EQ(ROW(TimestampzT(now))),
|
||||||
|
WRAP(TimestampT(now), TimestampzT(nowAddHour)).LT(ROW(TimestampT(now), TimestampzT(now))),
|
||||||
|
ROW(TimestampzT(nowAddHour)).LT_EQ(ROW(TimestampzT(now))),
|
||||||
|
)
|
||||||
|
|
||||||
|
//fmt.Println(stmt.Sql())
|
||||||
|
//fmt.Println(stmt.DebugSql())
|
||||||
|
|
||||||
|
testutils.AssertStatementSql(t, stmt, `
|
||||||
|
SELECT ROW($1::integer, $2::real, $3::text) AS "row",
|
||||||
|
($4::bigint, $5::double precision, $6::text) AS "wrap",
|
||||||
|
ROW($7::boolean, $8::date) = ROW($9::boolean, $10::date),
|
||||||
|
($11::boolean, $12::date) != ($13::boolean, $14::date),
|
||||||
|
ROW($15::time without time zone) IS DISTINCT FROM (row(NOW()::time)),
|
||||||
|
ROW() IS NOT DISTINCT FROM ROW(),
|
||||||
|
ROW($16::timestamp without time zone, $17::timestamp with time zone) > ($18::timestamp without time zone, $19::timestamp with time zone),
|
||||||
|
ROW($20::timestamp with time zone) >= ROW($21::timestamp with time zone),
|
||||||
|
($22::timestamp without time zone, $23::timestamp with time zone) < ROW($24::timestamp without time zone, $25::timestamp with time zone),
|
||||||
|
ROW($26::timestamp with time zone) <= ROW($27::timestamp with time zone);
|
||||||
|
`)
|
||||||
|
|
||||||
|
err := stmt.Query(db, &struct{}{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
func TestSubQueryColumnReference(t *testing.T) {
|
func TestSubQueryColumnReference(t *testing.T) {
|
||||||
type expected struct {
|
type expected struct {
|
||||||
sql string
|
sql string
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ package postgres
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/go-jet/jet/v2/internal/testutils"
|
"github.com/go-jet/jet/v2/internal/testutils"
|
||||||
|
. "github.com/go-jet/jet/v2/postgres"
|
||||||
"github.com/go-jet/jet/v2/tests/.gentestdata/jetdb/northwind/model"
|
"github.com/go-jet/jet/v2/tests/.gentestdata/jetdb/northwind/model"
|
||||||
. "github.com/go-jet/jet/v2/tests/.gentestdata/jetdb/northwind/table"
|
. "github.com/go-jet/jet/v2/tests/.gentestdata/jetdb/northwind/table"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
@ -10,7 +11,18 @@ import (
|
||||||
|
|
||||||
func TestNorthwindJoinEverything(t *testing.T) {
|
func TestNorthwindJoinEverything(t *testing.T) {
|
||||||
|
|
||||||
stmt := Customers.
|
stmt :=
|
||||||
|
SELECT(
|
||||||
|
Customers.AllColumns,
|
||||||
|
CustomerDemographics.AllColumns,
|
||||||
|
Orders.AllColumns,
|
||||||
|
Shippers.AllColumns,
|
||||||
|
OrderDetails.AllColumns,
|
||||||
|
Products.AllColumns,
|
||||||
|
Categories.AllColumns,
|
||||||
|
Suppliers.AllColumns,
|
||||||
|
).FROM(
|
||||||
|
Customers.
|
||||||
LEFT_JOIN(CustomerCustomerDemo, Customers.CustomerID.EQ(CustomerCustomerDemo.CustomerID)).
|
LEFT_JOIN(CustomerCustomerDemo, Customers.CustomerID.EQ(CustomerCustomerDemo.CustomerID)).
|
||||||
LEFT_JOIN(CustomerDemographics, CustomerCustomerDemo.CustomerTypeID.EQ(CustomerDemographics.CustomerTypeID)).
|
LEFT_JOIN(CustomerDemographics, CustomerCustomerDemo.CustomerTypeID.EQ(CustomerDemographics.CustomerTypeID)).
|
||||||
LEFT_JOIN(Orders, Orders.CustomerID.EQ(Customers.CustomerID)).
|
LEFT_JOIN(Orders, Orders.CustomerID.EQ(Customers.CustomerID)).
|
||||||
|
|
@ -22,18 +34,8 @@ func TestNorthwindJoinEverything(t *testing.T) {
|
||||||
LEFT_JOIN(Employees, Orders.EmployeeID.EQ(Employees.EmployeeID)).
|
LEFT_JOIN(Employees, Orders.EmployeeID.EQ(Employees.EmployeeID)).
|
||||||
LEFT_JOIN(EmployeeTerritories, EmployeeTerritories.EmployeeID.EQ(Employees.EmployeeID)).
|
LEFT_JOIN(EmployeeTerritories, EmployeeTerritories.EmployeeID.EQ(Employees.EmployeeID)).
|
||||||
LEFT_JOIN(Territories, EmployeeTerritories.TerritoryID.EQ(Territories.TerritoryID)).
|
LEFT_JOIN(Territories, EmployeeTerritories.TerritoryID.EQ(Territories.TerritoryID)).
|
||||||
LEFT_JOIN(Region, Territories.RegionID.EQ(Region.RegionID)).
|
LEFT_JOIN(Region, Territories.RegionID.EQ(Region.RegionID)),
|
||||||
SELECT(
|
).ORDER_BY(Customers.CustomerID, Orders.OrderID, Products.ProductID)
|
||||||
Customers.AllColumns,
|
|
||||||
CustomerDemographics.AllColumns,
|
|
||||||
Orders.AllColumns,
|
|
||||||
Shippers.AllColumns,
|
|
||||||
OrderDetails.AllColumns,
|
|
||||||
Products.AllColumns,
|
|
||||||
Categories.AllColumns,
|
|
||||||
Suppliers.AllColumns,
|
|
||||||
).
|
|
||||||
ORDER_BY(Customers.CustomerID, Orders.OrderID, Products.ProductID)
|
|
||||||
|
|
||||||
var dest []struct {
|
var dest []struct {
|
||||||
model.Customers
|
model.Customers
|
||||||
|
|
|
||||||
|
|
@ -344,7 +344,10 @@ func TestUpdateExecContext(t *testing.T) {
|
||||||
|
|
||||||
time.Sleep(10 * time.Millisecond)
|
time.Sleep(10 * time.Millisecond)
|
||||||
|
|
||||||
testutils.AssertExecContextErr(ctx, t, updateStmt, db, "context deadline exceeded")
|
testutils.ExecuteInTxAndRollback(t, db, func(tx *sql.Tx) {
|
||||||
|
_, err := updateStmt.ExecContext(ctx, tx)
|
||||||
|
require.Error(t, err, "context deadline exceeded")
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUpdateFrom(t *testing.T) {
|
func TestUpdateFrom(t *testing.T) {
|
||||||
|
|
|
||||||
284
tests/postgres/values_test.go
Normal file
284
tests/postgres/values_test.go
Normal file
|
|
@ -0,0 +1,284 @@
|
||||||
|
package postgres
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"github.com/go-jet/jet/v2/internal/testutils"
|
||||||
|
. "github.com/go-jet/jet/v2/postgres"
|
||||||
|
"github.com/go-jet/jet/v2/tests/.gentestdata/jetdb/dvds/model"
|
||||||
|
. "github.com/go-jet/jet/v2/tests/.gentestdata/jetdb/dvds/table"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestVALUES(t *testing.T) {
|
||||||
|
|
||||||
|
values := VALUES(
|
||||||
|
WRAP(Int32(1), Int32(2), Float32(4.666), Bool(false), String("txt")),
|
||||||
|
WRAP(Int32(11).ADD(Int32(2)), Int32(22), Float32(33.222), Bool(true), String("png")),
|
||||||
|
WRAP(Int32(11), Int32(22), Float32(33.222), Bool(true), NULL),
|
||||||
|
).AS("values_table")
|
||||||
|
|
||||||
|
stmt := SELECT(
|
||||||
|
values.AllColumns(),
|
||||||
|
).FROM(
|
||||||
|
values,
|
||||||
|
)
|
||||||
|
|
||||||
|
testutils.AssertStatementSql(t, stmt, `
|
||||||
|
SELECT values_table.column1 AS "column1",
|
||||||
|
values_table.column2 AS "column2",
|
||||||
|
values_table.column3 AS "column3",
|
||||||
|
values_table.column4 AS "column4",
|
||||||
|
values_table.column5 AS "column5"
|
||||||
|
FROM (
|
||||||
|
VALUES ($1::integer, $2::integer, $3::real, $4::boolean, $5::text),
|
||||||
|
($6::integer + $7::integer, $8::integer, $9::real, $10::boolean, $11::text),
|
||||||
|
($12::integer, $13::integer, $14::real, $15::boolean, NULL)
|
||||||
|
) AS values_table;
|
||||||
|
`)
|
||||||
|
|
||||||
|
var dest []struct {
|
||||||
|
Column1 int
|
||||||
|
Column2 int
|
||||||
|
Column3 float32
|
||||||
|
Column4 bool
|
||||||
|
Column5 *string
|
||||||
|
}
|
||||||
|
|
||||||
|
err := stmt.Query(db, &dest)
|
||||||
|
|
||||||
|
require.NoError(t, err)
|
||||||
|
testutils.AssertJSON(t, dest, `
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"Column1": 1,
|
||||||
|
"Column2": 2,
|
||||||
|
"Column3": 4.666,
|
||||||
|
"Column4": false,
|
||||||
|
"Column5": "txt"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Column1": 13,
|
||||||
|
"Column2": 22,
|
||||||
|
"Column3": 33.222,
|
||||||
|
"Column4": true,
|
||||||
|
"Column5": "png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Column1": 11,
|
||||||
|
"Column2": 22,
|
||||||
|
"Column3": 33.222,
|
||||||
|
"Column4": true,
|
||||||
|
"Column5": null
|
||||||
|
}
|
||||||
|
]
|
||||||
|
`)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVALUES_Join(t *testing.T) {
|
||||||
|
|
||||||
|
title := StringColumn("title")
|
||||||
|
releaseYear := IntegerColumn("ReleaseYear")
|
||||||
|
rentalRate := FloatColumn("rental_rate")
|
||||||
|
|
||||||
|
lastUpdate := Timestamp(2007, time.February, 11, 12, 0, 0)
|
||||||
|
|
||||||
|
filmValues := VALUES(
|
||||||
|
WRAP(String("Chamber Italian"), Int64(117), Int32(2005), Float32(5.82), lastUpdate),
|
||||||
|
WRAP(String("Grosse Wonderful"), Int64(49), Int32(2004), Float32(6.242), lastUpdate.ADD(INTERVAL(1, HOUR))),
|
||||||
|
WRAP(String("Airport Pollock"), Int64(54), Int32(2001), Float32(7.22), NULL),
|
||||||
|
WRAP(String("Bright Encounters"), Int64(73), Int32(2002), Float32(8.25), NULL),
|
||||||
|
WRAP(String("Academy Dinosaur"), Int64(83), Int32(2010), Float32(9.22), lastUpdate.SUB(INTERVAL(2, MINUTE))),
|
||||||
|
).AS("film_values",
|
||||||
|
title, IntegerColumn("length"), releaseYear, rentalRate, TimestampColumn("update_date"))
|
||||||
|
|
||||||
|
stmt := SELECT(
|
||||||
|
Film.AllColumns,
|
||||||
|
filmValues.AllColumns(),
|
||||||
|
).FROM(
|
||||||
|
Film.
|
||||||
|
INNER_JOIN(filmValues, title.EQ(Film.Title)),
|
||||||
|
).WHERE(AND(
|
||||||
|
Film.ReleaseYear.GT(releaseYear),
|
||||||
|
Film.RentalRate.LT(rentalRate),
|
||||||
|
)).ORDER_BY(
|
||||||
|
title,
|
||||||
|
)
|
||||||
|
|
||||||
|
testutils.AssertDebugStatementSql(t, stmt, `
|
||||||
|
SELECT film.film_id AS "film.film_id",
|
||||||
|
film.title AS "film.title",
|
||||||
|
film.description AS "film.description",
|
||||||
|
film.release_year AS "film.release_year",
|
||||||
|
film.language_id AS "film.language_id",
|
||||||
|
film.rental_duration AS "film.rental_duration",
|
||||||
|
film.rental_rate AS "film.rental_rate",
|
||||||
|
film.length AS "film.length",
|
||||||
|
film.replacement_cost AS "film.replacement_cost",
|
||||||
|
film.rating AS "film.rating",
|
||||||
|
film.last_update AS "film.last_update",
|
||||||
|
film.special_features AS "film.special_features",
|
||||||
|
film.fulltext AS "film.fulltext",
|
||||||
|
film_values.title AS "title",
|
||||||
|
film_values.length AS "length",
|
||||||
|
film_values."ReleaseYear" AS "ReleaseYear",
|
||||||
|
film_values.rental_rate AS "rental_rate",
|
||||||
|
film_values.update_date AS "update_date"
|
||||||
|
FROM dvds.film
|
||||||
|
INNER JOIN (
|
||||||
|
VALUES ('Chamber Italian'::text, 117::bigint, 2005::integer, 5.820000171661377::real, '2007-02-11 12:00:00'::timestamp without time zone),
|
||||||
|
('Grosse Wonderful'::text, 49::bigint, 2004::integer, 6.242000102996826::real, '2007-02-11 12:00:00'::timestamp without time zone + INTERVAL '1 HOUR'),
|
||||||
|
('Airport Pollock'::text, 54::bigint, 2001::integer, 7.21999979019165::real, NULL),
|
||||||
|
('Bright Encounters'::text, 73::bigint, 2002::integer, 8.25::real, NULL),
|
||||||
|
('Academy Dinosaur'::text, 83::bigint, 2010::integer, 9.220000267028809::real, '2007-02-11 12:00:00'::timestamp without time zone - INTERVAL '2 MINUTE')
|
||||||
|
) AS film_values (title, length, "ReleaseYear", rental_rate, update_date) ON (film_values.title = film.title)
|
||||||
|
WHERE (
|
||||||
|
(film.release_year > film_values."ReleaseYear")
|
||||||
|
AND (film.rental_rate < film_values.rental_rate)
|
||||||
|
)
|
||||||
|
ORDER BY film_values.title;
|
||||||
|
`)
|
||||||
|
|
||||||
|
//fmt.Println(stmt.DebugSql())
|
||||||
|
|
||||||
|
var dest []struct {
|
||||||
|
Film model.Film
|
||||||
|
|
||||||
|
Title string
|
||||||
|
Length int
|
||||||
|
ReleaseYear int
|
||||||
|
RentalRate float32
|
||||||
|
UpdateDate *time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
err := stmt.Query(db, &dest)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Len(t, dest, 4)
|
||||||
|
testutils.AssertJSON(t, dest[0:2], `
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"Film": {
|
||||||
|
"FilmID": 8,
|
||||||
|
"Title": "Airport Pollock",
|
||||||
|
"Description": "A Epic Tale of a Moose And a Girl who must Confront a Monkey in Ancient India",
|
||||||
|
"ReleaseYear": 2006,
|
||||||
|
"LanguageID": 1,
|
||||||
|
"RentalDuration": 6,
|
||||||
|
"RentalRate": 4.99,
|
||||||
|
"Length": 54,
|
||||||
|
"ReplacementCost": 15.99,
|
||||||
|
"Rating": "R",
|
||||||
|
"LastUpdate": "2013-05-26T14:50:58.951Z",
|
||||||
|
"SpecialFeatures": "{Trailers}",
|
||||||
|
"Fulltext": "'airport':1 'ancient':18 'confront':14 'epic':4 'girl':11 'india':19 'monkey':16 'moos':8 'must':13 'pollock':2 'tale':5"
|
||||||
|
},
|
||||||
|
"Title": "Airport Pollock",
|
||||||
|
"Length": 54,
|
||||||
|
"ReleaseYear": 2001,
|
||||||
|
"RentalRate": 7.22,
|
||||||
|
"UpdateDate": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Film": {
|
||||||
|
"FilmID": 98,
|
||||||
|
"Title": "Bright Encounters",
|
||||||
|
"Description": "A Fateful Yarn of a Lumberjack And a Feminist who must Conquer a Student in A Jet Boat",
|
||||||
|
"ReleaseYear": 2006,
|
||||||
|
"LanguageID": 1,
|
||||||
|
"RentalDuration": 4,
|
||||||
|
"RentalRate": 4.99,
|
||||||
|
"Length": 73,
|
||||||
|
"ReplacementCost": 12.99,
|
||||||
|
"Rating": "PG-13",
|
||||||
|
"LastUpdate": "2013-05-26T14:50:58.951Z",
|
||||||
|
"SpecialFeatures": "{Trailers}",
|
||||||
|
"Fulltext": "'boat':20 'bright':1 'conquer':14 'encount':2 'fate':4 'feminist':11 'jet':19 'lumberjack':8 'must':13 'student':16 'yarn':5"
|
||||||
|
},
|
||||||
|
"Title": "Bright Encounters",
|
||||||
|
"Length": 73,
|
||||||
|
"ReleaseYear": 2002,
|
||||||
|
"RentalRate": 8.25,
|
||||||
|
"UpdateDate": null
|
||||||
|
}
|
||||||
|
]
|
||||||
|
`)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVALUES_CTE_Update(t *testing.T) {
|
||||||
|
|
||||||
|
paymentID := IntegerColumn("payment_ID")
|
||||||
|
increase := FloatColumn("increase")
|
||||||
|
paymentsToUpdate := CTE("values_cte", paymentID, increase)
|
||||||
|
|
||||||
|
stmt := WITH(
|
||||||
|
paymentsToUpdate.AS(
|
||||||
|
VALUES(
|
||||||
|
WRAP(Int32(20564), Float32(1.21)),
|
||||||
|
WRAP(Int32(20567), Float32(1.02)),
|
||||||
|
WRAP(Int32(20570), Float32(1.34)),
|
||||||
|
WRAP(Int32(20573), Float32(1.72)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)(
|
||||||
|
Payment.UPDATE().
|
||||||
|
SET(
|
||||||
|
Payment.Amount.SET(Payment.Amount.MUL(CAST(increase).AS_DECIMAL())),
|
||||||
|
).
|
||||||
|
FROM(paymentsToUpdate).
|
||||||
|
WHERE(Payment.PaymentID.EQ(paymentID)).
|
||||||
|
RETURNING(Payment.AllColumns),
|
||||||
|
)
|
||||||
|
|
||||||
|
testutils.AssertDebugStatementSql(t, stmt, `
|
||||||
|
WITH values_cte ("payment_ID", increase) AS (
|
||||||
|
VALUES (20564::integer, 1.2100000381469727::real),
|
||||||
|
(20567::integer, 1.0199999809265137::real),
|
||||||
|
(20570::integer, 1.340000033378601::real),
|
||||||
|
(20573::integer, 1.7200000286102295::real)
|
||||||
|
)
|
||||||
|
UPDATE dvds.payment
|
||||||
|
SET amount = (payment.amount * values_cte.increase::decimal)
|
||||||
|
FROM values_cte
|
||||||
|
WHERE payment.payment_id = values_cte."payment_ID"
|
||||||
|
RETURNING payment.payment_id AS "payment.payment_id",
|
||||||
|
payment.customer_id AS "payment.customer_id",
|
||||||
|
payment.staff_id AS "payment.staff_id",
|
||||||
|
payment.rental_id AS "payment.rental_id",
|
||||||
|
payment.amount AS "payment.amount",
|
||||||
|
payment.payment_date AS "payment.payment_date";
|
||||||
|
`)
|
||||||
|
|
||||||
|
testutils.ExecuteInTxAndRollback(t, db, func(tx *sql.Tx) {
|
||||||
|
|
||||||
|
var payments []model.Payment
|
||||||
|
|
||||||
|
err := stmt.Query(tx, &payments)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Len(t, payments, 4)
|
||||||
|
testutils.AssertJSON(t, payments[0:2], `
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"PaymentID": 20564,
|
||||||
|
"CustomerID": 379,
|
||||||
|
"StaffID": 2,
|
||||||
|
"RentalID": 11457,
|
||||||
|
"Amount": 4.83,
|
||||||
|
"PaymentDate": "2007-03-02T19:42:42.996577Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"PaymentID": 20567,
|
||||||
|
"CustomerID": 379,
|
||||||
|
"StaffID": 2,
|
||||||
|
"RentalID": 13397,
|
||||||
|
"Amount": 8.15,
|
||||||
|
"PaymentDate": "2007-03-19T20:35:01.996577Z"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
`)
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -83,10 +83,10 @@ SELECT orders.ship_region AS "orders.ship_region",
|
||||||
SUM(order_details.quantity) AS "product_sales"
|
SUM(order_details.quantity) AS "product_sales"
|
||||||
FROM northwind.orders
|
FROM northwind.orders
|
||||||
INNER JOIN northwind.order_details ON (orders.order_id = order_details.order_id)
|
INNER JOIN northwind.order_details ON (orders.order_id = order_details.order_id)
|
||||||
WHERE orders.ship_region IN (
|
WHERE orders.ship_region IN ((
|
||||||
SELECT top_region."orders.ship_region" AS "orders.ship_region"
|
SELECT top_region."orders.ship_region" AS "orders.ship_region"
|
||||||
FROM top_region
|
FROM top_region
|
||||||
)
|
))
|
||||||
GROUP BY orders.ship_region, order_details.product_id
|
GROUP BY orders.ship_region, order_details.product_id
|
||||||
ORDER BY SUM(order_details.quantity) DESC;
|
ORDER BY SUM(order_details.quantity) DESC;
|
||||||
`)
|
`)
|
||||||
|
|
@ -157,19 +157,19 @@ func TestWithStatementDeleteAndInsert(t *testing.T) {
|
||||||
testutils.AssertStatementSql(t, stmt, `
|
testutils.AssertStatementSql(t, stmt, `
|
||||||
WITH remove_discontinued_orders AS (
|
WITH remove_discontinued_orders AS (
|
||||||
DELETE FROM northwind.order_details
|
DELETE FROM northwind.order_details
|
||||||
WHERE order_details.product_id IN (
|
WHERE order_details.product_id IN ((
|
||||||
SELECT products.product_id AS "products.product_id"
|
SELECT products.product_id AS "products.product_id"
|
||||||
FROM northwind.products
|
FROM northwind.products
|
||||||
WHERE products.discontinued = $1
|
WHERE products.discontinued = $1
|
||||||
)
|
))
|
||||||
RETURNING order_details.product_id AS "order_details.product_id"
|
RETURNING order_details.product_id AS "order_details.product_id"
|
||||||
),update_discontinued_price AS (
|
),update_discontinued_price AS (
|
||||||
UPDATE northwind.products
|
UPDATE northwind.products
|
||||||
SET unit_price = $2
|
SET unit_price = $2
|
||||||
WHERE products.product_id IN (
|
WHERE products.product_id IN ((
|
||||||
SELECT remove_discontinued_orders."order_details.product_id" AS "order_details.product_id"
|
SELECT remove_discontinued_orders."order_details.product_id" AS "order_details.product_id"
|
||||||
FROM remove_discontinued_orders
|
FROM remove_discontinued_orders
|
||||||
)
|
))
|
||||||
RETURNING products.product_id AS "products.product_id",
|
RETURNING products.product_id AS "products.product_id",
|
||||||
products.product_name AS "products.product_name",
|
products.product_name AS "products.product_name",
|
||||||
products.supplier_id AS "products.supplier_id",
|
products.supplier_id AS "products.supplier_id",
|
||||||
|
|
|
||||||
|
|
@ -234,18 +234,18 @@ func TestExpressionOperators(t *testing.T) {
|
||||||
SELECT all_types.integer IS NULL AS "result.is_null",
|
SELECT all_types.integer IS NULL AS "result.is_null",
|
||||||
all_types.date_ptr IS NOT NULL AS "result.is_not_null",
|
all_types.date_ptr IS NOT NULL AS "result.is_not_null",
|
||||||
(all_types.small_int_ptr IN (?, ?)) AS "result.in",
|
(all_types.small_int_ptr IN (?, ?)) AS "result.in",
|
||||||
(all_types.small_int_ptr IN (
|
(all_types.small_int_ptr IN ((
|
||||||
SELECT all_types.integer AS "all_types.integer"
|
SELECT all_types.integer AS "all_types.integer"
|
||||||
FROM all_types
|
FROM all_types
|
||||||
)) AS "result.in_select",
|
))) AS "result.in_select",
|
||||||
(length(121232459)) AS "result.raw",
|
(length(121232459)) AS "result.raw",
|
||||||
(? + COALESCE(all_types.small_int_ptr, 0) + ?) AS "result.raw_arg",
|
(? + COALESCE(all_types.small_int_ptr, 0) + ?) AS "result.raw_arg",
|
||||||
(? + all_types.integer + ? + ? + ? + ?) AS "result.raw_arg2",
|
(? + all_types.integer + ? + ? + ? + ?) AS "result.raw_arg2",
|
||||||
(all_types.small_int_ptr NOT IN (?, ?, NULL)) AS "result.not_in",
|
(all_types.small_int_ptr NOT IN (?, ?, NULL)) AS "result.not_in",
|
||||||
(all_types.small_int_ptr NOT IN (
|
(all_types.small_int_ptr NOT IN ((
|
||||||
SELECT all_types.integer AS "all_types.integer"
|
SELECT all_types.integer AS "all_types.integer"
|
||||||
FROM all_types
|
FROM all_types
|
||||||
)) AS "result.not_in_select"
|
))) AS "result.not_in_select"
|
||||||
FROM all_types
|
FROM all_types
|
||||||
LIMIT ?;
|
LIMIT ?;
|
||||||
`, "'", "`", -1), int64(11), int64(22), 78, 56, 11, 22, 11, 33, 44, int64(11), int64(22), int64(2))
|
`, "'", "`", -1), int64(11), int64(22), 78, 56, 11, 22, 11, 33, 44, int64(11), int64(22), int64(2))
|
||||||
|
|
@ -900,3 +900,36 @@ func TestDateTimeExpressions(t *testing.T) {
|
||||||
require.Equal(t, dest.JulianDay, 2.4551543576232754e+06)
|
require.Equal(t, dest.JulianDay, 2.4551543576232754e+06)
|
||||||
require.Equal(t, dest.StrfTime, "20:34")
|
require.Equal(t, dest.StrfTime, "20:34")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRowExpression(t *testing.T) {
|
||||||
|
date := Date(2000, 9, 9)
|
||||||
|
time := Time(11, 22, 11)
|
||||||
|
dateTime := DateTime(2008, 11, 22, 10, 12, 40)
|
||||||
|
dateTime2 := DateTime(2011, 1, 2, 5, 12, 40)
|
||||||
|
|
||||||
|
stmt := SELECT(
|
||||||
|
ROW(Bool(false), date).EQ(ROW(Bool(true), date)),
|
||||||
|
ROW(Bool(false), time).NOT_EQ(ROW(Bool(true), time)),
|
||||||
|
ROW(time).IS_DISTINCT_FROM(RowExp(Raw("(time('now'))"))),
|
||||||
|
ROW(dateTime, dateTime2).GT(ROW(dateTime, dateTime2)),
|
||||||
|
ROW(dateTime2).GT_EQ(ROW(dateTime)),
|
||||||
|
ROW(dateTime, dateTime2).LT(ROW(dateTime, dateTime2)),
|
||||||
|
ROW(dateTime2).LT_EQ(ROW(dateTime2)),
|
||||||
|
)
|
||||||
|
|
||||||
|
//fmt.Println(stmt.Sql())
|
||||||
|
//fmt.Println(stmt.DebugSql())
|
||||||
|
|
||||||
|
testutils.AssertDebugStatementSql(t, stmt, `
|
||||||
|
SELECT (FALSE, DATE('2000-09-09')) = (TRUE, DATE('2000-09-09')),
|
||||||
|
(FALSE, TIME('11:22:11')) != (TRUE, TIME('11:22:11')),
|
||||||
|
(TIME('11:22:11')) IS NOT ((time('now'))),
|
||||||
|
(DATETIME('2008-11-22 10:12:40'), DATETIME('2011-01-02 05:12:40')) > (DATETIME('2008-11-22 10:12:40'), DATETIME('2011-01-02 05:12:40')),
|
||||||
|
(DATETIME('2011-01-02 05:12:40')) >= (DATETIME('2008-11-22 10:12:40')),
|
||||||
|
(DATETIME('2008-11-22 10:12:40'), DATETIME('2011-01-02 05:12:40')) < (DATETIME('2008-11-22 10:12:40'), DATETIME('2011-01-02 05:12:40')),
|
||||||
|
(DATETIME('2011-01-02 05:12:40')) <= (DATETIME('2011-01-02 05:12:40'));
|
||||||
|
`)
|
||||||
|
|
||||||
|
err := stmt.Query(db, &struct{}{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -283,7 +283,7 @@ func TestUpdateContextDeadlineExceeded(t *testing.T) {
|
||||||
|
|
||||||
time.Sleep(10 * time.Millisecond)
|
time.Sleep(10 * time.Millisecond)
|
||||||
|
|
||||||
dest := []model.Link{}
|
var dest []model.Link
|
||||||
err := updateStmt.QueryContext(ctx, tx, &dest)
|
err := updateStmt.QueryContext(ctx, tx, &dest)
|
||||||
require.Error(t, err, "context deadline exceeded")
|
require.Error(t, err, "context deadline exceeded")
|
||||||
|
|
||||||
|
|
|
||||||
344
tests/sqlite/values_test.go
Normal file
344
tests/sqlite/values_test.go
Normal file
|
|
@ -0,0 +1,344 @@
|
||||||
|
package sqlite
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"github.com/go-jet/jet/v2/internal/testutils"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
. "github.com/go-jet/jet/v2/sqlite"
|
||||||
|
"github.com/go-jet/jet/v2/tests/.gentestdata/sqlite/sakila/model"
|
||||||
|
. "github.com/go-jet/jet/v2/tests/.gentestdata/sqlite/sakila/table"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestVALUES(t *testing.T) {
|
||||||
|
|
||||||
|
values := VALUES(
|
||||||
|
ROW(Int32(1), Int32(2), Float(4.666), Bool(false), String("txt")),
|
||||||
|
ROW(Int32(11).ADD(Int32(2)), Int32(22), Float(33.222), Bool(true), String("png")),
|
||||||
|
ROW(Int32(11), Int32(22), Float(33.222), Bool(true), NULL),
|
||||||
|
).AS("values_table")
|
||||||
|
|
||||||
|
stmt := SELECT(
|
||||||
|
values.AllColumns(),
|
||||||
|
).FROM(
|
||||||
|
values,
|
||||||
|
)
|
||||||
|
|
||||||
|
testutils.AssertStatementSql(t, stmt, `
|
||||||
|
SELECT values_table.column1 AS "column1",
|
||||||
|
values_table.column2 AS "column2",
|
||||||
|
values_table.column3 AS "column3",
|
||||||
|
values_table.column4 AS "column4",
|
||||||
|
values_table.column5 AS "column5"
|
||||||
|
FROM (
|
||||||
|
VALUES (?, ?, ?, ?, ?),
|
||||||
|
(? + ?, ?, ?, ?, ?),
|
||||||
|
(?, ?, ?, ?, NULL)
|
||||||
|
) AS values_table;
|
||||||
|
`)
|
||||||
|
|
||||||
|
var dest []struct {
|
||||||
|
Column1 int
|
||||||
|
Column2 int
|
||||||
|
Column3 float32
|
||||||
|
Column4 bool
|
||||||
|
Column5 *string
|
||||||
|
}
|
||||||
|
|
||||||
|
err := stmt.Query(db, &dest)
|
||||||
|
|
||||||
|
require.NoError(t, err)
|
||||||
|
testutils.AssertJSON(t, dest, `
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"Column1": 1,
|
||||||
|
"Column2": 2,
|
||||||
|
"Column3": 4.666,
|
||||||
|
"Column4": false,
|
||||||
|
"Column5": "txt"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Column1": 13,
|
||||||
|
"Column2": 22,
|
||||||
|
"Column3": 33.222,
|
||||||
|
"Column4": true,
|
||||||
|
"Column5": "png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Column1": 11,
|
||||||
|
"Column2": 22,
|
||||||
|
"Column3": 33.222,
|
||||||
|
"Column4": true,
|
||||||
|
"Column5": null
|
||||||
|
}
|
||||||
|
]
|
||||||
|
`)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVALUES_Join(t *testing.T) {
|
||||||
|
|
||||||
|
lastUpdate := DateTime(2007, time.February, 11, 12, 0, 0)
|
||||||
|
|
||||||
|
films := VALUES(
|
||||||
|
ROW(String("Chamber Italian"), Int64(117), Int32(2005), Float(5.82), lastUpdate),
|
||||||
|
ROW(String("Grosse Wonderful"), Int64(49), Int32(2004), Float(6.242), lastUpdate),
|
||||||
|
ROW(String("Airport Pollock"), Int64(54), Int32(2001), Float(7.22), NULL),
|
||||||
|
ROW(String("Bright Encounters"), Int64(73), Int32(2002), Float(8.25), NULL),
|
||||||
|
ROW(String("Academy Dinosaur"), Int64(83), Int32(2010), Float(9.22), DATETIME(lastUpdate, YEARS(2))),
|
||||||
|
).AS("film_values")
|
||||||
|
|
||||||
|
title := StringColumn("column1").From(films)
|
||||||
|
releaseYear := IntegerColumn("column3").From(films)
|
||||||
|
rentalRate := FloatColumn("column4").From(films)
|
||||||
|
|
||||||
|
stmt := SELECT(
|
||||||
|
Film.AllColumns,
|
||||||
|
films.AllColumns(),
|
||||||
|
).FROM(
|
||||||
|
Film.
|
||||||
|
INNER_JOIN(films, LOWER(title).EQ(LOWER(Film.Title))),
|
||||||
|
).WHERE(AND(
|
||||||
|
CAST(Film.ReleaseYear).AS_INTEGER().GT(releaseYear),
|
||||||
|
Film.RentalRate.LT(rentalRate),
|
||||||
|
)).ORDER_BY(
|
||||||
|
title,
|
||||||
|
)
|
||||||
|
|
||||||
|
testutils.AssertDebugStatementSql(t, stmt, `
|
||||||
|
SELECT film.film_id AS "film.film_id",
|
||||||
|
film.title AS "film.title",
|
||||||
|
film.description AS "film.description",
|
||||||
|
film.release_year AS "film.release_year",
|
||||||
|
film.language_id AS "film.language_id",
|
||||||
|
film.original_language_id AS "film.original_language_id",
|
||||||
|
film.rental_duration AS "film.rental_duration",
|
||||||
|
film.rental_rate AS "film.rental_rate",
|
||||||
|
film.length AS "film.length",
|
||||||
|
film.replacement_cost AS "film.replacement_cost",
|
||||||
|
film.rating AS "film.rating",
|
||||||
|
film.special_features AS "film.special_features",
|
||||||
|
film.last_update AS "film.last_update",
|
||||||
|
film_values.column1 AS "column1",
|
||||||
|
film_values.column2 AS "column2",
|
||||||
|
film_values.column3 AS "column3",
|
||||||
|
film_values.column4 AS "column4",
|
||||||
|
film_values.column5 AS "column5"
|
||||||
|
FROM film
|
||||||
|
INNER JOIN (
|
||||||
|
VALUES ('Chamber Italian', 117, 2005, 5.82, DATETIME('2007-02-11 12:00:00')),
|
||||||
|
('Grosse Wonderful', 49, 2004, 6.242, DATETIME('2007-02-11 12:00:00')),
|
||||||
|
('Airport Pollock', 54, 2001, 7.22, NULL),
|
||||||
|
('Bright Encounters', 73, 2002, 8.25, NULL),
|
||||||
|
('Academy Dinosaur', 83, 2010, 9.22, DATETIME(DATETIME('2007-02-11 12:00:00'), '2 YEARS'))
|
||||||
|
) AS film_values ON (LOWER(film_values.column1) = LOWER(film.title))
|
||||||
|
WHERE (
|
||||||
|
(CAST(film.release_year AS INTEGER) > film_values.column3)
|
||||||
|
AND (film.rental_rate < film_values.column4)
|
||||||
|
)
|
||||||
|
ORDER BY film_values.column1;
|
||||||
|
`)
|
||||||
|
|
||||||
|
var dest []struct {
|
||||||
|
Film model.Film
|
||||||
|
|
||||||
|
Column1 string
|
||||||
|
Column2 int
|
||||||
|
Column3 int
|
||||||
|
Column4 float32
|
||||||
|
Column5 *time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
err := stmt.Query(db, &dest)
|
||||||
|
|
||||||
|
require.NoError(t, err)
|
||||||
|
testutils.AssertJSON(t, dest, `
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"Film": {
|
||||||
|
"FilmID": 8,
|
||||||
|
"Title": "AIRPORT POLLOCK",
|
||||||
|
"Description": "A Epic Tale of a Moose And a Girl who must Confront a Monkey in Ancient India",
|
||||||
|
"ReleaseYear": "2006",
|
||||||
|
"LanguageID": 1,
|
||||||
|
"OriginalLanguageID": null,
|
||||||
|
"RentalDuration": 6,
|
||||||
|
"RentalRate": 4.99,
|
||||||
|
"Length": 54,
|
||||||
|
"ReplacementCost": 15.99,
|
||||||
|
"Rating": "R",
|
||||||
|
"SpecialFeatures": "Trailers",
|
||||||
|
"LastUpdate": "2019-04-11T18:11:48Z"
|
||||||
|
},
|
||||||
|
"Column1": "Airport Pollock",
|
||||||
|
"Column2": 54,
|
||||||
|
"Column3": 2001,
|
||||||
|
"Column4": 7.22,
|
||||||
|
"Column5": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Film": {
|
||||||
|
"FilmID": 98,
|
||||||
|
"Title": "BRIGHT ENCOUNTERS",
|
||||||
|
"Description": "A Fateful Yarn of a Lumberjack And a Feminist who must Conquer a Student in A Jet Boat",
|
||||||
|
"ReleaseYear": "2006",
|
||||||
|
"LanguageID": 1,
|
||||||
|
"OriginalLanguageID": null,
|
||||||
|
"RentalDuration": 4,
|
||||||
|
"RentalRate": 4.99,
|
||||||
|
"Length": 73,
|
||||||
|
"ReplacementCost": 12.99,
|
||||||
|
"Rating": "PG-13",
|
||||||
|
"SpecialFeatures": "Trailers",
|
||||||
|
"LastUpdate": "2019-04-11T18:11:48Z"
|
||||||
|
},
|
||||||
|
"Column1": "Bright Encounters",
|
||||||
|
"Column2": 73,
|
||||||
|
"Column3": 2002,
|
||||||
|
"Column4": 8.25,
|
||||||
|
"Column5": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Film": {
|
||||||
|
"FilmID": 133,
|
||||||
|
"Title": "CHAMBER ITALIAN",
|
||||||
|
"Description": "A Fateful Reflection of a Moose And a Husband who must Overcome a Monkey in Nigeria",
|
||||||
|
"ReleaseYear": "2006",
|
||||||
|
"LanguageID": 1,
|
||||||
|
"OriginalLanguageID": null,
|
||||||
|
"RentalDuration": 7,
|
||||||
|
"RentalRate": 4.99,
|
||||||
|
"Length": 117,
|
||||||
|
"ReplacementCost": 14.99,
|
||||||
|
"Rating": "NC-17",
|
||||||
|
"SpecialFeatures": "Trailers",
|
||||||
|
"LastUpdate": "2019-04-11T18:11:48Z"
|
||||||
|
},
|
||||||
|
"Column1": "Chamber Italian",
|
||||||
|
"Column2": 117,
|
||||||
|
"Column3": 2005,
|
||||||
|
"Column4": 5.82,
|
||||||
|
"Column5": "2007-02-11T12:00:00Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Film": {
|
||||||
|
"FilmID": 384,
|
||||||
|
"Title": "GROSSE WONDERFUL",
|
||||||
|
"Description": "A Epic Drama of a Cat And a Explorer who must Redeem a Moose in Australia",
|
||||||
|
"ReleaseYear": "2006",
|
||||||
|
"LanguageID": 1,
|
||||||
|
"OriginalLanguageID": null,
|
||||||
|
"RentalDuration": 5,
|
||||||
|
"RentalRate": 4.99,
|
||||||
|
"Length": 49,
|
||||||
|
"ReplacementCost": 19.99,
|
||||||
|
"Rating": "R",
|
||||||
|
"SpecialFeatures": "Behind the Scenes",
|
||||||
|
"LastUpdate": "2019-04-11T18:11:48Z"
|
||||||
|
},
|
||||||
|
"Column1": "Grosse Wonderful",
|
||||||
|
"Column2": 49,
|
||||||
|
"Column3": 2004,
|
||||||
|
"Column4": 6.242,
|
||||||
|
"Column5": "2007-02-11T12:00:00Z"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
`)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVALUES_CTE_Update(t *testing.T) {
|
||||||
|
|
||||||
|
paymentID := IntegerColumn("payment_ID")
|
||||||
|
increase := FloatColumn("increase")
|
||||||
|
paymentsToUpdate := CTE("values_cte", paymentID, increase)
|
||||||
|
|
||||||
|
stmt := WITH(
|
||||||
|
paymentsToUpdate.AS(
|
||||||
|
VALUES(
|
||||||
|
ROW(Int32(204), Float(1.21)),
|
||||||
|
ROW(Int32(207), Float(1.02)),
|
||||||
|
ROW(Int32(200), Float(1.34)),
|
||||||
|
ROW(Int32(203), Float(1.72)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)(
|
||||||
|
Payment.UPDATE().
|
||||||
|
SET(
|
||||||
|
Payment.Amount.SET(Payment.Amount.MUL(increase)),
|
||||||
|
).
|
||||||
|
FROM(paymentsToUpdate).
|
||||||
|
WHERE(Payment.PaymentID.EQ(paymentID)).
|
||||||
|
RETURNING(Payment.AllColumns),
|
||||||
|
)
|
||||||
|
|
||||||
|
testutils.AssertStatementSql(t, stmt, strings.ReplaceAll(`
|
||||||
|
WITH values_cte (''payment_ID'', increase) AS (
|
||||||
|
VALUES (?, ?),
|
||||||
|
(?, ?),
|
||||||
|
(?, ?),
|
||||||
|
(?, ?)
|
||||||
|
)
|
||||||
|
UPDATE payment
|
||||||
|
SET amount = (payment.amount * values_cte.increase)
|
||||||
|
FROM values_cte
|
||||||
|
WHERE payment.payment_id = values_cte.''payment_ID''
|
||||||
|
RETURNING payment.payment_id AS "payment.payment_id",
|
||||||
|
payment.customer_id AS "payment.customer_id",
|
||||||
|
payment.staff_id AS "payment.staff_id",
|
||||||
|
payment.rental_id AS "payment.rental_id",
|
||||||
|
payment.amount AS "payment.amount",
|
||||||
|
payment.payment_date AS "payment.payment_date",
|
||||||
|
payment.last_update AS "payment.last_update";
|
||||||
|
`, "''", "`"))
|
||||||
|
|
||||||
|
testutils.ExecuteInTxAndRollback(t, db, func(tx *sql.Tx) {
|
||||||
|
var payments []model.Payment
|
||||||
|
|
||||||
|
err := stmt.Query(tx, &payments)
|
||||||
|
|
||||||
|
require.NoError(t, err)
|
||||||
|
testutils.AssertJSON(t, payments, `
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"PaymentID": 200,
|
||||||
|
"CustomerID": 7,
|
||||||
|
"StaffID": 2,
|
||||||
|
"RentalID": 11542,
|
||||||
|
"Amount": 10.706600000000002,
|
||||||
|
"PaymentDate": "2005-08-17T00:51:32Z",
|
||||||
|
"LastUpdate": "2019-04-11T18:11:50Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"PaymentID": 203,
|
||||||
|
"CustomerID": 7,
|
||||||
|
"StaffID": 2,
|
||||||
|
"RentalID": 13373,
|
||||||
|
"Amount": 5.1428,
|
||||||
|
"PaymentDate": "2005-08-19T21:23:31Z",
|
||||||
|
"LastUpdate": "2019-04-11T18:11:50Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"PaymentID": 204,
|
||||||
|
"CustomerID": 7,
|
||||||
|
"StaffID": 1,
|
||||||
|
"RentalID": 13476,
|
||||||
|
"Amount": 3.6179,
|
||||||
|
"PaymentDate": "2005-08-20T01:06:04Z",
|
||||||
|
"LastUpdate": "2019-04-11T18:11:50Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"PaymentID": 207,
|
||||||
|
"CustomerID": 8,
|
||||||
|
"StaffID": 2,
|
||||||
|
"RentalID": 866,
|
||||||
|
"Amount": 7.1298,
|
||||||
|
"PaymentDate": "2005-05-30T03:43:54Z",
|
||||||
|
"LastUpdate": "2019-04-11T18:11:50Z"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
`)
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -153,10 +153,10 @@ WITH payments_to_update AS (
|
||||||
)
|
)
|
||||||
UPDATE payment
|
UPDATE payment
|
||||||
SET amount = 0
|
SET amount = 0
|
||||||
WHERE payment.payment_id IN (
|
WHERE payment.payment_id IN ((
|
||||||
SELECT payments_to_update.''payment.payment_id'' AS "payment.payment_id"
|
SELECT payments_to_update.''payment.payment_id'' AS "payment.payment_id"
|
||||||
FROM payments_to_update
|
FROM payments_to_update
|
||||||
);
|
));
|
||||||
`, "''", "`", -1))
|
`, "''", "`", -1))
|
||||||
|
|
||||||
tx := beginDBTx(t)
|
tx := beginDBTx(t)
|
||||||
|
|
@ -205,10 +205,10 @@ WITH payments_to_delete AS (
|
||||||
WHERE payment.amount < 0.5
|
WHERE payment.amount < 0.5
|
||||||
)
|
)
|
||||||
DELETE FROM payment
|
DELETE FROM payment
|
||||||
WHERE payment.payment_id IN (
|
WHERE payment.payment_id IN ((
|
||||||
SELECT payments_to_delete.''payment.payment_id'' AS "payment.payment_id"
|
SELECT payments_to_delete.''payment.payment_id'' AS "payment.payment_id"
|
||||||
FROM payments_to_delete
|
FROM payments_to_delete
|
||||||
);
|
));
|
||||||
`, "''", "`", -1))
|
`, "''", "`", -1))
|
||||||
|
|
||||||
tx := beginDBTx(t)
|
tx := beginDBTx(t)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue