Merge branch 'arjen-ag5/master' into pg_arrays
# Conflicts: # generator/template/model_template.go # generator/template/sql_builder_template.go # internal/jet/expression.go # postgres/cast.go # postgres/columns.go # postgres/expressions.go # postgres/insert_statement_test.go # postgres/literal.go # tests/postgres/alltypes_test.go # tests/postgres/generator_template_test.go # tests/postgres/scan_test.go # tests/postgres/select_test.go
This commit is contained in:
commit
45d4ced9b0
25 changed files with 575 additions and 85 deletions
|
|
@ -28,4 +28,5 @@ type DataType struct {
|
||||||
Name string
|
Name string
|
||||||
Kind DataTypeKind
|
Kind DataTypeKind
|
||||||
IsUnsigned bool
|
IsUnsigned bool
|
||||||
|
Dimensions int // The number of array dimensions
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -66,6 +66,7 @@ select
|
||||||
not attr.attnotnull as "column.isNullable",
|
not attr.attnotnull as "column.isNullable",
|
||||||
attr.attgenerated = 's' as "column.isGenerated",
|
attr.attgenerated = 's' as "column.isGenerated",
|
||||||
attr.atthasdef as "column.hasDefault",
|
attr.atthasdef as "column.hasDefault",
|
||||||
|
attr.attndims as "dataType.dimensions",
|
||||||
(case
|
(case
|
||||||
when tp.typtype = 'b' AND tp.typcategory <> 'A' then 'base'
|
when tp.typtype = 'b' AND tp.typcategory <> 'A' then 'base'
|
||||||
when tp.typtype = 'b' AND tp.typcategory = 'A' then 'array'
|
when tp.typtype = 'b' AND tp.typcategory = 'A' then 'array'
|
||||||
|
|
|
||||||
|
|
@ -2,16 +2,15 @@ package template
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/go-jet/jet/v2/generator/metadata"
|
||||||
|
"github.com/go-jet/jet/v2/internal/utils/dbidentifier"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/jackc/pgtype"
|
||||||
|
"github.com/lib/pq"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
|
||||||
"github.com/jackc/pgtype"
|
|
||||||
|
|
||||||
"github.com/go-jet/jet/v2/generator/metadata"
|
|
||||||
"github.com/go-jet/jet/v2/internal/utils/dbidentifier"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Model is template for model files generation
|
// Model is template for model files generation
|
||||||
|
|
@ -260,7 +259,7 @@ func getUserDefinedType(column metadata.Column) string {
|
||||||
switch column.DataType.Kind {
|
switch column.DataType.Kind {
|
||||||
case metadata.EnumType:
|
case metadata.EnumType:
|
||||||
return dbidentifier.ToGoIdentifier(column.DataType.Name)
|
return dbidentifier.ToGoIdentifier(column.DataType.Name)
|
||||||
case metadata.UserDefinedType, metadata.ArrayType:
|
case metadata.UserDefinedType:
|
||||||
return "string"
|
return "string"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -279,6 +278,11 @@ func getGoType(column metadata.Column) interface{} {
|
||||||
|
|
||||||
// toGoType returns model type for column info.
|
// toGoType returns model type for column info.
|
||||||
func toGoType(column metadata.Column) interface{} {
|
func toGoType(column metadata.Column) interface{} {
|
||||||
|
// We don't support multi-dimensional arrays
|
||||||
|
if column.DataType.Dimensions > 1 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
switch strings.ToLower(column.DataType.Name) {
|
switch strings.ToLower(column.DataType.Name) {
|
||||||
case "user-defined", "enum":
|
case "user-defined", "enum":
|
||||||
return ""
|
return ""
|
||||||
|
|
@ -344,6 +348,14 @@ func toGoType(column metadata.Column) interface{} {
|
||||||
return pgtype.Int8range{}
|
return pgtype.Int8range{}
|
||||||
case "numrange":
|
case "numrange":
|
||||||
return pgtype.Numrange{}
|
return pgtype.Numrange{}
|
||||||
|
case "bool[]":
|
||||||
|
return pq.BoolArray{}
|
||||||
|
case "integer[]", "int4[]":
|
||||||
|
return pq.Int32Array{}
|
||||||
|
case "bigint[]":
|
||||||
|
return pq.Int64Array{}
|
||||||
|
case "text[]", "jsonb[]", "json[]":
|
||||||
|
return pq.StringArray{}
|
||||||
default:
|
default:
|
||||||
fmt.Println("- [Model ] Unsupported sql column '" + column.Name + " " + column.DataType.Name + "', using string instead.")
|
fmt.Println("- [Model ] Unsupported sql column '" + column.Name + " " + column.DataType.Name + "', using string instead.")
|
||||||
return ""
|
return ""
|
||||||
|
|
|
||||||
|
|
@ -163,11 +163,42 @@ func DefaultTableSQLBuilderColumn(columnMetaData metadata.Column) TableSQLBuilde
|
||||||
// getSqlBuilderColumnType returns type of jet sql builder column
|
// getSqlBuilderColumnType returns type of jet sql builder column
|
||||||
func getSqlBuilderColumnType(columnMetaData metadata.Column) string {
|
func getSqlBuilderColumnType(columnMetaData metadata.Column) string {
|
||||||
if columnMetaData.DataType.Kind != metadata.BaseType &&
|
if columnMetaData.DataType.Kind != metadata.BaseType &&
|
||||||
columnMetaData.DataType.Kind != metadata.RangeType {
|
columnMetaData.DataType.Kind != metadata.RangeType &&
|
||||||
|
columnMetaData.DataType.Kind != metadata.ArrayType {
|
||||||
return "String"
|
return "String"
|
||||||
}
|
}
|
||||||
|
|
||||||
switch strings.ToLower(columnMetaData.DataType.Name) {
|
typeName := columnMetaData.DataType.Name
|
||||||
|
columnName := columnMetaData.Name
|
||||||
|
|
||||||
|
if columnMetaData.DataType.Kind == metadata.ArrayType {
|
||||||
|
if columnMetaData.DataType.Dimensions > 1 {
|
||||||
|
fmt.Println("- [SQL Builder] Unsupported sql array with multiple dimensions column '" + columnName + " " + typeName + "', using StringColumn instead.")
|
||||||
|
return "String"
|
||||||
|
}
|
||||||
|
|
||||||
|
c := sqlToColumnType(strings.TrimSuffix(typeName, "[]"))
|
||||||
|
|
||||||
|
// These are the supported array types
|
||||||
|
if slices.Index([]string{"Bool", "String", "Integer"}, c) == -1 {
|
||||||
|
fmt.Println("- [SQL Builder] Unsupported sql array column '" + columnName + " " + typeName + "', using StringColumn instead.")
|
||||||
|
return "String"
|
||||||
|
}
|
||||||
|
|
||||||
|
return c + "Array"
|
||||||
|
}
|
||||||
|
|
||||||
|
columnType := sqlToColumnType(typeName)
|
||||||
|
if columnType == "" {
|
||||||
|
fmt.Println("- [SQL Builder] Unsupported sql column '" + columnName + " " + typeName + "', using StringColumn instead.")
|
||||||
|
return "String"
|
||||||
|
}
|
||||||
|
|
||||||
|
return columnType
|
||||||
|
}
|
||||||
|
|
||||||
|
func sqlToColumnType(typeName string) string {
|
||||||
|
switch strings.ToLower(typeName) {
|
||||||
case "boolean", "bool":
|
case "boolean", "bool":
|
||||||
return "Bool"
|
return "Bool"
|
||||||
case "smallint", "integer", "bigint", "int2", "int4", "int8",
|
case "smallint", "integer", "bigint", "int2", "int4", "int8",
|
||||||
|
|
@ -212,8 +243,7 @@ func getSqlBuilderColumnType(columnMetaData metadata.Column) string {
|
||||||
case "numrange":
|
case "numrange":
|
||||||
return "NumericRange"
|
return "NumericRange"
|
||||||
default:
|
default:
|
||||||
fmt.Println("- [SQL Builder] Unsupported sql column '" + columnMetaData.Name + " " + columnMetaData.DataType.Name + "', using StringColumn instead.")
|
return ""
|
||||||
return "String"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
93
internal/jet/array_expression.go
Normal file
93
internal/jet/array_expression.go
Normal file
|
|
@ -0,0 +1,93 @@
|
||||||
|
package jet
|
||||||
|
|
||||||
|
// ArrayExpression interface
|
||||||
|
type ArrayExpression[E Expression] interface {
|
||||||
|
Expression
|
||||||
|
|
||||||
|
EQ(rhs ArrayExpression[E]) BoolExpression
|
||||||
|
NOT_EQ(rhs ArrayExpression[E]) BoolExpression
|
||||||
|
LT(rhs ArrayExpression[E]) BoolExpression
|
||||||
|
GT(rhs ArrayExpression[E]) BoolExpression
|
||||||
|
LT_EQ(rhs ArrayExpression[E]) BoolExpression
|
||||||
|
GT_EQ(rhs ArrayExpression[E]) BoolExpression
|
||||||
|
|
||||||
|
CONTAINS(rhs ArrayExpression[E]) BoolExpression
|
||||||
|
IS_CONTAINED_BY(rhs ArrayExpression[E]) BoolExpression
|
||||||
|
OVERLAP(rhs ArrayExpression[E]) BoolExpression
|
||||||
|
CONCAT(rhs ArrayExpression[E]) ArrayExpression[E]
|
||||||
|
CONCAT_ELEMENT(E) ArrayExpression[E]
|
||||||
|
|
||||||
|
AT(expression IntegerExpression) Expression
|
||||||
|
}
|
||||||
|
|
||||||
|
type arrayInterfaceImpl[E Expression] struct {
|
||||||
|
parent ArrayExpression[E]
|
||||||
|
}
|
||||||
|
|
||||||
|
type BinaryBoolOp func(Expression, Expression) BoolExpression
|
||||||
|
|
||||||
|
func (a arrayInterfaceImpl[E]) EQ(rhs ArrayExpression[E]) BoolExpression {
|
||||||
|
return Eq(a.parent, rhs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a arrayInterfaceImpl[E]) NOT_EQ(rhs ArrayExpression[E]) BoolExpression {
|
||||||
|
return NotEq(a.parent, rhs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a arrayInterfaceImpl[E]) LT(rhs ArrayExpression[E]) BoolExpression {
|
||||||
|
return Lt(a.parent, rhs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a arrayInterfaceImpl[E]) GT(rhs ArrayExpression[E]) BoolExpression {
|
||||||
|
return Gt(a.parent, rhs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a arrayInterfaceImpl[E]) LT_EQ(rhs ArrayExpression[E]) BoolExpression {
|
||||||
|
return LtEq(a.parent, rhs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a arrayInterfaceImpl[E]) GT_EQ(rhs ArrayExpression[E]) BoolExpression {
|
||||||
|
return GtEq(a.parent, rhs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a arrayInterfaceImpl[E]) CONTAINS(rhs ArrayExpression[E]) BoolExpression {
|
||||||
|
return Contains(a.parent, rhs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a arrayInterfaceImpl[E]) IS_CONTAINED_BY(rhs ArrayExpression[E]) BoolExpression {
|
||||||
|
return IsContainedBy(a.parent, rhs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a arrayInterfaceImpl[E]) OVERLAP(rhs ArrayExpression[E]) BoolExpression {
|
||||||
|
return Overlap(a.parent, rhs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a arrayInterfaceImpl[E]) CONCAT(rhs ArrayExpression[E]) ArrayExpression[E] {
|
||||||
|
return ArrayExp[E](NewBinaryOperatorExpression(a.parent, rhs, "||"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a arrayInterfaceImpl[E]) CONCAT_ELEMENT(rhs E) ArrayExpression[E] {
|
||||||
|
return ArrayExp[E](NewBinaryOperatorExpression(a.parent, rhs, "||"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a arrayInterfaceImpl[E]) AT(expression IntegerExpression) Expression {
|
||||||
|
return arraySubscriptExpr(a.parent, expression)
|
||||||
|
}
|
||||||
|
|
||||||
|
type arrayExpressionWrapper[E Expression] struct {
|
||||||
|
arrayInterfaceImpl[E]
|
||||||
|
Expression
|
||||||
|
}
|
||||||
|
|
||||||
|
func newArrayExpressionWrap[E Expression](expression Expression) ArrayExpression[E] {
|
||||||
|
arrayExpressionWrapper := arrayExpressionWrapper[E]{Expression: expression}
|
||||||
|
arrayExpressionWrapper.arrayInterfaceImpl.parent = &arrayExpressionWrapper
|
||||||
|
return &arrayExpressionWrapper
|
||||||
|
}
|
||||||
|
|
||||||
|
// ArrayExp is array expression wrapper around arbitrary expression.
|
||||||
|
// Allows go compiler to see any expression as array expression.
|
||||||
|
// Does not add sql cast to generated sql builder output.
|
||||||
|
func ArrayExp[E Expression](expression Expression) ArrayExpression[E] {
|
||||||
|
return newArrayExpressionWrap[E](expression)
|
||||||
|
}
|
||||||
59
internal/jet/array_expression_test.go
Normal file
59
internal/jet/array_expression_test.go
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
package jet
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/lib/pq"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestArrayExpressionEQ(t *testing.T) {
|
||||||
|
assertClauseSerialize(t, table1ColStringArray.EQ(table2ColArray), "(table1.col_array_string = table2.col_array_string)")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestArrayExpressionNOT_EQ(t *testing.T) {
|
||||||
|
assertClauseSerialize(t, table1ColStringArray.NOT_EQ(table2ColArray), "(table1.col_array_string != table2.col_array_string)")
|
||||||
|
assertClauseSerialize(t, table1ColStringArray.NOT_EQ(StringArray([]string{"x"})), "(table1.col_array_string != $1)", pq.StringArray{"x"})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestArrayExpressionLT(t *testing.T) {
|
||||||
|
assertClauseSerialize(t, table1ColStringArray.LT(table2ColArray), "(table1.col_array_string < table2.col_array_string)")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestArrayExpressionGT(t *testing.T) {
|
||||||
|
assertClauseSerialize(t, table1ColStringArray.GT(table2ColArray), "(table1.col_array_string > table2.col_array_string)")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestArrayExpressionLT_EQ(t *testing.T) {
|
||||||
|
assertClauseSerialize(t, table1ColStringArray.LT_EQ(table2ColArray), "(table1.col_array_string <= table2.col_array_string)")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestArrayExpressionGT_EQ(t *testing.T) {
|
||||||
|
assertClauseSerialize(t, table1ColStringArray.GT_EQ(table2ColArray), "(table1.col_array_string >= table2.col_array_string)")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestArrayExpressionCONTAINS(t *testing.T) {
|
||||||
|
assertClauseSerialize(t, table1ColStringArray.CONTAINS(table2ColArray), "(table1.col_array_string @> table2.col_array_string)")
|
||||||
|
assertClauseSerialize(t, table1ColStringArray.CONTAINS(StringArray([]string{"x"})), "(table1.col_array_string @> $1)", pq.StringArray{"x"})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestArrayExpressionCONTAINED_BY(t *testing.T) {
|
||||||
|
assertClauseSerialize(t, table1ColStringArray.IS_CONTAINED_BY(table2ColArray), "(table1.col_array_string <@ table2.col_array_string)")
|
||||||
|
assertClauseSerialize(t, table1ColStringArray.IS_CONTAINED_BY(StringArray([]string{"x"})), "(table1.col_array_string <@ $1)", pq.StringArray{"x"})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestArrayExpressionOVERLAP(t *testing.T) {
|
||||||
|
assertClauseSerialize(t, table1ColStringArray.OVERLAP(table2ColArray), "(table1.col_array_string && table2.col_array_string)")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestArrayExpressionCONCAT(t *testing.T) {
|
||||||
|
assertClauseSerialize(t, table1ColStringArray.CONCAT(table2ColArray), "(table1.col_array_string || table2.col_array_string)")
|
||||||
|
assertClauseSerialize(t, table1ColStringArray.CONCAT(StringArray([]string{"x"})), "(table1.col_array_string || $1)", pq.StringArray{"x"})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestArrayExpressionCONCAT_ELEMENT(t *testing.T) {
|
||||||
|
assertClauseSerialize(t, table1ColStringArray.CONCAT_ELEMENT(StringExp(table2ColArray.AT(Int(1)))), "(table1.col_array_string || (table2.col_array_string[$1]))", int64(1))
|
||||||
|
assertClauseSerialize(t, table1ColStringArray.CONCAT_ELEMENT(String("x")), "(table1.col_array_string || $1)", "x")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestArrayExpressionAT(t *testing.T) {
|
||||||
|
assertClauseSerialize(t, table1ColStringArray.AT(Int(1)), "(table1.col_array_string[$1])", int64(1))
|
||||||
|
}
|
||||||
|
|
@ -133,6 +133,46 @@ func IntegerColumn(name string) ColumnInteger {
|
||||||
|
|
||||||
//------------------------------------------------------//
|
//------------------------------------------------------//
|
||||||
|
|
||||||
|
type ColumnArray[E Expression] interface {
|
||||||
|
ArrayExpression[E]
|
||||||
|
Column
|
||||||
|
|
||||||
|
From(subQuery SelectTable) ColumnArray[E]
|
||||||
|
SET(stringExp ArrayExpression[E]) ColumnAssigment
|
||||||
|
}
|
||||||
|
|
||||||
|
type arrayColumnImpl[E Expression] struct {
|
||||||
|
arrayInterfaceImpl[E]
|
||||||
|
|
||||||
|
ColumnExpressionImpl
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a arrayColumnImpl[E]) From(subQuery SelectTable) ColumnArray[E] {
|
||||||
|
newArrayColumn := ArrayColumn[E](a.name)
|
||||||
|
newArrayColumn.setTableName(a.tableName)
|
||||||
|
newArrayColumn.setSubQuery(subQuery)
|
||||||
|
|
||||||
|
return newArrayColumn
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *arrayColumnImpl[E]) SET(stringExp ArrayExpression[E]) ColumnAssigment {
|
||||||
|
return columnAssigmentImpl{
|
||||||
|
column: a,
|
||||||
|
expression: stringExp,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// StringColumn creates named string column.
|
||||||
|
func ArrayColumn[E Expression](name string) ColumnArray[E] {
|
||||||
|
arrayColumn := &arrayColumnImpl[E]{}
|
||||||
|
arrayColumn.arrayInterfaceImpl.parent = arrayColumn
|
||||||
|
arrayColumn.ColumnExpressionImpl = NewColumnImpl(name, "", arrayColumn)
|
||||||
|
|
||||||
|
return arrayColumn
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------------------------//
|
||||||
|
|
||||||
// ColumnString is interface for SQL text, character, character varying
|
// ColumnString is interface for SQL text, character, character varying
|
||||||
// uuid columns and enums types.
|
// uuid columns and enums types.
|
||||||
type ColumnString interface {
|
type ColumnString interface {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package jet
|
package jet
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/lib/pq"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -8,6 +9,42 @@ var subQuery = &selectTableImpl{
|
||||||
alias: "sub_query",
|
alias: "sub_query",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestNewArrayColumnString(t *testing.T) {
|
||||||
|
stringArrayColumn := ArrayColumn[StringExpression]("colArray").From(subQuery)
|
||||||
|
assertClauseSerialize(t, stringArrayColumn, `sub_query."colArray"`)
|
||||||
|
assertClauseSerialize(t, stringArrayColumn.EQ(StringArray([]string{"X"})), `(sub_query."colArray" = $1)`, pq.StringArray{"X"})
|
||||||
|
assertProjectionSerialize(t, stringArrayColumn, `sub_query."colArray" AS "colArray"`)
|
||||||
|
|
||||||
|
arrayColumn2 := table1ColStringArray.From(subQuery)
|
||||||
|
assertClauseSerialize(t, arrayColumn2, `sub_query."table1.col_array_string"`)
|
||||||
|
assertClauseSerialize(t, arrayColumn2.EQ(StringArray([]string{"X"})), `(sub_query."table1.col_array_string" = $1)`, pq.StringArray{"X"})
|
||||||
|
assertProjectionSerialize(t, arrayColumn2, `sub_query."table1.col_array_string" AS "table1.col_array_string"`)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewArrayColumnBool(t *testing.T) {
|
||||||
|
boolArrayColumn := ArrayColumn[BoolExpression]("colArrayBool").From(subQuery)
|
||||||
|
assertClauseSerialize(t, boolArrayColumn, `sub_query."colArrayBool"`)
|
||||||
|
assertClauseSerialize(t, boolArrayColumn.EQ(BoolArray([]bool{true})), `(sub_query."colArrayBool" = $1)`, pq.BoolArray{true})
|
||||||
|
assertProjectionSerialize(t, boolArrayColumn, `sub_query."colArrayBool" AS "colArrayBool"`)
|
||||||
|
|
||||||
|
arrayColumn2 := table1ColBoolArray.From(subQuery)
|
||||||
|
assertClauseSerialize(t, arrayColumn2, `sub_query."table1.col_array_bool"`)
|
||||||
|
assertClauseSerialize(t, arrayColumn2.EQ(BoolArray([]bool{true})), `(sub_query."table1.col_array_bool" = $1)`, pq.BoolArray{true})
|
||||||
|
assertProjectionSerialize(t, arrayColumn2, `sub_query."table1.col_array_bool" AS "table1.col_array_bool"`)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewArrayColumnInteger(t *testing.T) {
|
||||||
|
intArrayColumn := ArrayColumn[IntegerExpression]("colArrayInt").From(subQuery)
|
||||||
|
assertClauseSerialize(t, intArrayColumn, `sub_query."colArrayInt"`)
|
||||||
|
assertClauseSerialize(t, intArrayColumn.EQ(Int32Array([]int32{42})), `(sub_query."colArrayInt" = $1)`, pq.Int32Array{42})
|
||||||
|
assertProjectionSerialize(t, intArrayColumn, `sub_query."colArrayInt" AS "colArrayInt"`)
|
||||||
|
|
||||||
|
arrayColumn2 := table1ColIntArray.From(subQuery)
|
||||||
|
assertClauseSerialize(t, arrayColumn2, `sub_query."table1.col_array_int"`)
|
||||||
|
assertClauseSerialize(t, arrayColumn2.EQ(Int32Array([]int32{42})), `(sub_query."table1.col_array_int" = $1)`, pq.Int32Array{42})
|
||||||
|
assertProjectionSerialize(t, arrayColumn2, `sub_query."table1.col_array_int" AS "table1.col_array_int"`)
|
||||||
|
}
|
||||||
|
|
||||||
func TestNewBoolColumn(t *testing.T) {
|
func TestNewBoolColumn(t *testing.T) {
|
||||||
boolColumn := BoolColumn("colBool").From(subQuery)
|
boolColumn := BoolColumn("colBool").From(subQuery)
|
||||||
assertClauseSerialize(t, boolColumn, `sub_query."colBool"`)
|
assertClauseSerialize(t, boolColumn, `sub_query."colBool"`)
|
||||||
|
|
|
||||||
|
|
@ -341,3 +341,42 @@ func (s *complexExpression) serialize(statement StatementType, out *SQLBuilder,
|
||||||
func wrap(expressions ...Expression) Expression {
|
func wrap(expressions ...Expression) Expression {
|
||||||
return NewFunc("", expressions, nil)
|
return NewFunc("", expressions, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type arraySubscriptExpression struct {
|
||||||
|
ExpressionInterfaceImpl
|
||||||
|
array Expression
|
||||||
|
subscript IntegerExpression
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a arraySubscriptExpression) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) {
|
||||||
|
if !contains(options, NoWrap) {
|
||||||
|
out.WriteString("(")
|
||||||
|
}
|
||||||
|
a.array.serialize(statement, out, FallTrough(options)...) // FallTrough here because complexExpression is just a wrapper
|
||||||
|
out.WriteString("[")
|
||||||
|
a.subscript.serialize(statement, out, FallTrough(options)...) // FallTrough here because complexExpression is just a wrapper
|
||||||
|
out.WriteString("]")
|
||||||
|
if !contains(options, NoWrap) {
|
||||||
|
out.WriteString(")")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func arraySubscriptExpr(array Expression, subscript IntegerExpression) Expression {
|
||||||
|
arraySubscriptExpression := &arraySubscriptExpression{array: array, subscript: subscript}
|
||||||
|
arraySubscriptExpression.ExpressionInterfaceImpl.Parent = arraySubscriptExpression
|
||||||
|
|
||||||
|
return arraySubscriptExpression
|
||||||
|
}
|
||||||
|
|
||||||
|
type skipParenthesisWrap struct {
|
||||||
|
Expression
|
||||||
|
}
|
||||||
|
|
||||||
|
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)...)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ package jet
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/lib/pq"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -160,6 +161,66 @@ func Decimal(value string) FloatExpression {
|
||||||
return &floatLiteral
|
return &floatLiteral
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------//
|
||||||
|
|
||||||
|
type boolArrayLiteral struct {
|
||||||
|
arrayInterfaceImpl[BoolExpression]
|
||||||
|
literalExpressionImpl
|
||||||
|
}
|
||||||
|
|
||||||
|
func BoolArray(values []bool) ArrayExpression[BoolExpression] {
|
||||||
|
l := boolArrayLiteral{}
|
||||||
|
l.literalExpressionImpl = *literal(pq.BoolArray(values))
|
||||||
|
l.arrayInterfaceImpl.parent = &l
|
||||||
|
|
||||||
|
return &l
|
||||||
|
}
|
||||||
|
|
||||||
|
type integerArrayLiteral struct {
|
||||||
|
arrayInterfaceImpl[IntegerExpression]
|
||||||
|
literalExpressionImpl
|
||||||
|
}
|
||||||
|
|
||||||
|
func Int64Array(values []int64) ArrayExpression[IntegerExpression] {
|
||||||
|
l := integerArrayLiteral{}
|
||||||
|
l.literalExpressionImpl = *literal(pq.Int64Array(values))
|
||||||
|
l.arrayInterfaceImpl.parent = &l
|
||||||
|
return &l
|
||||||
|
}
|
||||||
|
|
||||||
|
func Int32Array(values []int32) ArrayExpression[IntegerExpression] {
|
||||||
|
l := integerArrayLiteral{}
|
||||||
|
l.literalExpressionImpl = *literal(pq.Int32Array(values))
|
||||||
|
l.arrayInterfaceImpl.parent = &l
|
||||||
|
return &l
|
||||||
|
}
|
||||||
|
|
||||||
|
type stringArrayLiteral struct {
|
||||||
|
arrayInterfaceImpl[StringExpression]
|
||||||
|
literalExpressionImpl
|
||||||
|
}
|
||||||
|
|
||||||
|
func StringArray(values []string) ArrayExpression[StringExpression] {
|
||||||
|
l := stringArrayLiteral{}
|
||||||
|
l.literalExpressionImpl = *literal(pq.StringArray(values))
|
||||||
|
l.arrayInterfaceImpl.parent = &l
|
||||||
|
|
||||||
|
return &l
|
||||||
|
}
|
||||||
|
|
||||||
|
type unsafeArrayLiteral[E Expression] struct {
|
||||||
|
arrayInterfaceImpl[E]
|
||||||
|
literalExpressionImpl
|
||||||
|
}
|
||||||
|
|
||||||
|
func UnsafeArray[E LiteralExpression](values []interface{}) ArrayExpression[E] {
|
||||||
|
l := unsafeArrayLiteral[E]{}
|
||||||
|
l.literalExpressionImpl = *literal(pq.Array(values))
|
||||||
|
l.arrayInterfaceImpl.parent = &l
|
||||||
|
|
||||||
|
return &l
|
||||||
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------//
|
// ---------------------------------------------------//
|
||||||
type stringLiteral struct {
|
type stringLiteral struct {
|
||||||
stringInterfaceImpl
|
stringInterfaceImpl
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,15 @@ func BIT_NOT(expr IntegerExpression) IntegerExpression {
|
||||||
return newPrefixIntegerOperatorExpression(expr, "~")
|
return newPrefixIntegerOperatorExpression(expr, "~")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ----------- Array operators -------------- //
|
||||||
|
func Any(lhs Expression, op BinaryBoolOp, rhs Expression) BoolExpression {
|
||||||
|
return op(lhs, Func("ANY", rhs))
|
||||||
|
}
|
||||||
|
|
||||||
|
func All(lhs Expression, op BinaryBoolOp, rhs Expression) BoolExpression {
|
||||||
|
return op(lhs, Func("ALL", rhs))
|
||||||
|
}
|
||||||
|
|
||||||
//----------- Comparison operators ---------------//
|
//----------- Comparison operators ---------------//
|
||||||
|
|
||||||
// EXISTS checks for existence of the rows in subQuery
|
// EXISTS checks for existence of the rows in subQuery
|
||||||
|
|
@ -74,6 +83,11 @@ func Contains(lhs Expression, rhs Expression) BoolExpression {
|
||||||
return newBinaryBoolOperatorExpression(lhs, rhs, "@>")
|
return newBinaryBoolOperatorExpression(lhs, rhs, "@>")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsContainedBy returns a representation of "a <@ b"
|
||||||
|
func IsContainedBy(lhs Expression, rhs Expression) BoolExpression {
|
||||||
|
return newBinaryBoolOperatorExpression(lhs, rhs, "<@")
|
||||||
|
}
|
||||||
|
|
||||||
// Overlap returns a representation of "a && b"
|
// Overlap returns a representation of "a && b"
|
||||||
func Overlap(lhs, rhs Expression) BoolExpression {
|
func Overlap(lhs, rhs Expression) BoolExpression {
|
||||||
return newBinaryBoolOperatorExpression(lhs, rhs, "&&")
|
return newBinaryBoolOperatorExpression(lhs, rhs, "&&")
|
||||||
|
|
|
||||||
|
|
@ -93,11 +93,11 @@ func (s *SQLBuilder) write(data []byte) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func isPreSeparator(b byte) bool {
|
func isPreSeparator(b byte) bool {
|
||||||
return b == ' ' || b == '.' || b == ',' || b == '(' || b == '\n' || b == ':'
|
return b == ' ' || b == '.' || b == ',' || b == '(' || b == '\n' || b == ':' || b == '['
|
||||||
}
|
}
|
||||||
|
|
||||||
func isPostSeparator(b byte) bool {
|
func isPostSeparator(b byte) bool {
|
||||||
return b == ' ' || b == '.' || b == ',' || b == ')' || b == '\n' || b == ':'
|
return b == ' ' || b == '.' || b == ',' || b == ')' || b == '\n' || b == ':' || b == '[' || b == ']'
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteAlias is used to add alias to output SQL
|
// WriteAlias is used to add alias to output SQL
|
||||||
|
|
@ -249,6 +249,8 @@ func (s *SQLBuilder) argToString(value interface{}) string {
|
||||||
|
|
||||||
case string:
|
case string:
|
||||||
return stringQuote(bindVal)
|
return stringQuote(bindVal)
|
||||||
|
case []string:
|
||||||
|
return stringArrayQuote(bindVal)
|
||||||
case []byte:
|
case []byte:
|
||||||
return stringQuote(string(bindVal))
|
return stringQuote(string(bindVal))
|
||||||
case uuid.UUID:
|
case uuid.UUID:
|
||||||
|
|
@ -276,6 +278,19 @@ func (s *SQLBuilder) argToString(value interface{}) string {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func stringArrayQuote(val []string) string {
|
||||||
|
var sb strings.Builder
|
||||||
|
sb.WriteString(`'{`)
|
||||||
|
for i := 0; i < len(val); i++ {
|
||||||
|
if i > 0 {
|
||||||
|
sb.WriteString(`, `)
|
||||||
|
}
|
||||||
|
sb.WriteString(stringDoubleQuote(val[i]))
|
||||||
|
}
|
||||||
|
sb.WriteString(`}'`)
|
||||||
|
return sb.String()
|
||||||
|
}
|
||||||
|
|
||||||
func integerTypesToString(value interface{}) string {
|
func integerTypesToString(value interface{}) string {
|
||||||
switch bindVal := value.(type) {
|
switch bindVal := value.(type) {
|
||||||
case int:
|
case int:
|
||||||
|
|
@ -324,3 +339,7 @@ func shouldQuoteIdentifier(identifier string) bool {
|
||||||
func stringQuote(value string) string {
|
func stringQuote(value string) string {
|
||||||
return `'` + strings.Replace(value, "'", "''", -1) + `'`
|
return `'` + strings.Replace(value, "'", "''", -1) + `'`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func stringDoubleQuote(value string) string {
|
||||||
|
return `"` + strings.Replace(value, `"`, `""`, -1) + `"`
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,9 @@ type StringExpression interface {
|
||||||
BETWEEN(min, max StringExpression) BoolExpression
|
BETWEEN(min, max StringExpression) BoolExpression
|
||||||
NOT_BETWEEN(min, max StringExpression) BoolExpression
|
NOT_BETWEEN(min, max StringExpression) BoolExpression
|
||||||
|
|
||||||
|
ANY_EQ(rhs ArrayExpression[StringExpression]) BoolExpression
|
||||||
|
ALL_EQ(rhs ArrayExpression[StringExpression]) BoolExpression
|
||||||
|
|
||||||
CONCAT(rhs Expression) StringExpression
|
CONCAT(rhs Expression) StringExpression
|
||||||
|
|
||||||
LIKE(pattern StringExpression) BoolExpression
|
LIKE(pattern StringExpression) BoolExpression
|
||||||
|
|
@ -72,6 +75,14 @@ func (s *stringInterfaceImpl) NOT_BETWEEN(min, max StringExpression) BoolExpress
|
||||||
return NewBetweenOperatorExpression(s.root, min, max, true)
|
return NewBetweenOperatorExpression(s.root, min, max, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (i *stringInterfaceImpl) ANY_EQ(rhs ArrayExpression[StringExpression]) BoolExpression {
|
||||||
|
return Any(i.parent, Eq, rhs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *stringInterfaceImpl) ALL_EQ(rhs ArrayExpression[StringExpression]) BoolExpression {
|
||||||
|
return All(i.parent, Eq, rhs)
|
||||||
|
}
|
||||||
|
|
||||||
func (s *stringInterfaceImpl) CONCAT(rhs Expression) StringExpression {
|
func (s *stringInterfaceImpl) CONCAT(rhs Expression) StringExpression {
|
||||||
return newBinaryStringOperatorExpression(s.root, rhs, StringConcatOperator)
|
return newBinaryStringOperatorExpression(s.root, rhs, StringConcatOperator)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -76,6 +76,14 @@ func TestStringNOT_REGEXP_LIKE(t *testing.T) {
|
||||||
assertClauseSerialize(t, table3StrCol.NOT_REGEXP_LIKE(String("JOHN"), true), "(table3.col2 NOT REGEXP $1)", "JOHN")
|
assertClauseSerialize(t, table3StrCol.NOT_REGEXP_LIKE(String("JOHN"), true), "(table3.col2 NOT REGEXP $1)", "JOHN")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestStringANY_EQ(t *testing.T) {
|
||||||
|
assertClauseSerialize(t, table2ColStr.ANY_EQ(table1ColStringArray), "(table2.col_str = ANY(table1.col_array_string))")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStringALL_EQ(t *testing.T) {
|
||||||
|
assertClauseSerialize(t, table2ColStr.ALL_EQ(table1ColStringArray), "(table2.col_str = ALL(table1.col_array_string))")
|
||||||
|
}
|
||||||
|
|
||||||
func TestStringExp(t *testing.T) {
|
func TestStringExp(t *testing.T) {
|
||||||
assertClauseSerialize(t, StringExp(table2ColFloat), "table2.col_float")
|
assertClauseSerialize(t, StringExp(table2ColFloat), "table2.col_float")
|
||||||
assertClauseSerialize(t, StringExp(table2ColFloat).NOT_LIKE(String("abc")), "(table2.col_float NOT LIKE $1)", "abc")
|
assertClauseSerialize(t, StringExp(table2ColFloat).NOT_LIKE(String("abc")), "(table2.col_float NOT LIKE $1)", "abc")
|
||||||
|
|
|
||||||
|
|
@ -29,8 +29,11 @@ var (
|
||||||
table1ColBool = BoolColumn("col_bool")
|
table1ColBool = BoolColumn("col_bool")
|
||||||
table1ColDate = DateColumn("col_date")
|
table1ColDate = DateColumn("col_date")
|
||||||
table1ColRange = RangeColumn[Int8Expression]("col_range")
|
table1ColRange = RangeColumn[Int8Expression]("col_range")
|
||||||
|
table1ColStringArray = ArrayColumn[StringExpression]("col_array_string")
|
||||||
|
table1ColBoolArray = ArrayColumn[BoolExpression]("col_array_bool")
|
||||||
|
table1ColIntArray = ArrayColumn[IntegerExpression]("col_array_int")
|
||||||
)
|
)
|
||||||
var table1 = NewTable("db", "table1", "", table1Col1, table1ColInt, table1ColFloat, table1Col3, table1ColTime, table1ColTimez, table1ColBool, table1ColDate, table1ColRange, table1ColTimestamp, table1ColTimestampz)
|
var table1 = NewTable("db", "table1", "", table1Col1, table1ColInt, table1ColFloat, table1Col3, table1ColTime, table1ColTimez, table1ColBool, table1ColDate, table1ColRange, table1ColTimestamp, table1ColTimestampz, table1ColStringArray, table1ColBoolArray, table1ColIntArray)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
table2Col3 = IntegerColumn("col3")
|
table2Col3 = IntegerColumn("col3")
|
||||||
|
|
@ -45,8 +48,9 @@ var (
|
||||||
table2ColTimestampz = TimestampzColumn("col_timestampz")
|
table2ColTimestampz = TimestampzColumn("col_timestampz")
|
||||||
table2ColDate = DateColumn("col_date")
|
table2ColDate = DateColumn("col_date")
|
||||||
table2ColRange = RangeColumn[Int8Expression]("col_range")
|
table2ColRange = RangeColumn[Int8Expression]("col_range")
|
||||||
|
table2ColArray = ArrayColumn[StringExpression]("col_array_string")
|
||||||
)
|
)
|
||||||
var table2 = NewTable("db", "table2", "", table2Col3, table2Col4, table2ColInt, table2ColFloat, table2ColStr, table2ColBool, table2ColTime, table2ColTimez, table2ColDate, table2ColRange, table2ColTimestamp, table2ColTimestampz)
|
var table2 = NewTable("db", "table2", "", table2Col3, table2Col4, table2ColInt, table2ColFloat, table2ColStr, table2ColBool, table2ColTime, table2ColTimez, table2ColDate, table2ColRange, table2ColTimestamp, table2ColTimestampz, table2ColArray)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
table3Col1 = IntegerColumn("col1")
|
table3Col1 = IntegerColumn("col1")
|
||||||
|
|
|
||||||
|
|
@ -112,3 +112,21 @@ type ColumnInt8Range jet.ColumnRange[jet.Int8Expression]
|
||||||
|
|
||||||
// Int8RangeColumn creates named range with range column
|
// Int8RangeColumn creates named range with range column
|
||||||
var Int8RangeColumn = jet.RangeColumn[jet.Int8Expression]
|
var Int8RangeColumn = jet.RangeColumn[jet.Int8Expression]
|
||||||
|
|
||||||
|
// ColumnStringArray is interface of column
|
||||||
|
type ColumnStringArray jet.ColumnArray[jet.StringExpression]
|
||||||
|
|
||||||
|
// StringArrayColumn creates named string array column
|
||||||
|
var StringArrayColumn = jet.ArrayColumn[jet.StringExpression]
|
||||||
|
|
||||||
|
// ColumnIntegerArray is interface of column
|
||||||
|
type ColumnIntegerArray jet.ColumnArray[jet.IntegerExpression]
|
||||||
|
|
||||||
|
// IntegerArrayColumn creates named integer array column
|
||||||
|
var IntegerArrayColumn = jet.ArrayColumn[jet.IntegerExpression]
|
||||||
|
|
||||||
|
// ColumnBoolArray is interface of column
|
||||||
|
type ColumnBoolArray jet.ColumnArray[jet.BoolExpression]
|
||||||
|
|
||||||
|
// BoolArrayColumn creates named bool array column
|
||||||
|
var BoolArrayColumn = jet.ArrayColumn[jet.BoolExpression]
|
||||||
|
|
|
||||||
|
|
@ -9,17 +9,26 @@ type Expression = jet.Expression
|
||||||
// BoolExpression interface
|
// BoolExpression interface
|
||||||
type BoolExpression = jet.BoolExpression
|
type BoolExpression = jet.BoolExpression
|
||||||
|
|
||||||
|
// BoolArrayExpression interface
|
||||||
|
type BoolArrayExpression = jet.ArrayExpression[BoolExpression]
|
||||||
|
|
||||||
// StringExpression interface
|
// StringExpression interface
|
||||||
type StringExpression = jet.StringExpression
|
type StringExpression = jet.StringExpression
|
||||||
|
|
||||||
type ByteaExpression = jet.BlobExpression
|
type ByteaExpression = jet.BlobExpression
|
||||||
|
|
||||||
|
// StringArrayExpression interface
|
||||||
|
type StringArrayExpression = jet.ArrayExpression[StringExpression]
|
||||||
|
|
||||||
// NumericExpression interface
|
// NumericExpression interface
|
||||||
type NumericExpression = jet.NumericExpression
|
type NumericExpression = jet.NumericExpression
|
||||||
|
|
||||||
// IntegerExpression interface
|
// IntegerExpression interface
|
||||||
type IntegerExpression = jet.IntegerExpression
|
type IntegerExpression = jet.IntegerExpression
|
||||||
|
|
||||||
|
// IntegerArrayExpression interface
|
||||||
|
type IntegerArrayExpression = jet.ArrayExpression[IntegerExpression]
|
||||||
|
|
||||||
// FloatExpression is interface
|
// FloatExpression is interface
|
||||||
type FloatExpression = jet.FloatExpression
|
type FloatExpression = jet.FloatExpression
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -175,27 +175,30 @@ RETURNING table1.col1 AS "table1.col1",
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestInsert_ON_CONFLICT_ON_CONSTRAINT(t *testing.T) {
|
func TestInsert_ON_CONFLICT_ON_CONSTRAINT(t *testing.T) {
|
||||||
stmt := table1.INSERT(table1Col1, table1ColBool).
|
stmt := table1.INSERT(table1Col1, table1ColBool, table1ColStringArray).
|
||||||
VALUES("one", "two").
|
VALUES("one", "two", "three").
|
||||||
VALUES("1", "2").
|
VALUES("1", "2", "3").
|
||||||
ON_CONFLICT().ON_CONSTRAINT("idk_primary_key").
|
ON_CONFLICT().ON_CONSTRAINT("idk_primary_key").DO_UPDATE(
|
||||||
DO_UPDATE(
|
|
||||||
SET(table1ColBool.SET(Bool(false)),
|
SET(table1ColBool.SET(Bool(false)),
|
||||||
table2ColInt.SET(Int(1)),
|
table2ColInt.SET(Int(1)),
|
||||||
ColumnList{table1Col1, table1ColBool}.SET(ROW(Int(2), String("two"))),
|
table1ColStringArray.SET(StringArray([]string{"one"})),
|
||||||
).WHERE(table1Col1.GT(Int(2)))).
|
ColumnList{table1Col1, table1ColBool, table1ColStringArray}.SET(jet.ROW(Int(2), String("two"), StringArray([]string{"two"}))),
|
||||||
RETURNING(table1Col1, table1ColBool)
|
).WHERE(table1Col1.GT(Int(2))),
|
||||||
|
).
|
||||||
|
RETURNING(table1Col1, table1ColBool, table1ColStringArray)
|
||||||
|
|
||||||
assertDebugStatementSql(t, stmt, `
|
assertDebugStatementSql(t, stmt, `
|
||||||
INSERT INTO db.table1 (col1, col_bool)
|
INSERT INTO db.table1 (col1, col_bool, col_string_array)
|
||||||
VALUES ('one', 'two'),
|
VALUES ('one', 'two', 'three'),
|
||||||
('1', '2')
|
('1', '2', '3')
|
||||||
ON CONFLICT ON CONSTRAINT idk_primary_key DO UPDATE
|
ON CONFLICT ON CONSTRAINT idk_primary_key DO UPDATE
|
||||||
SET col_bool = FALSE::boolean,
|
SET col_bool = FALSE::boolean,
|
||||||
col_int = 1,
|
col_int = 1,
|
||||||
(col1, col_bool) = ROW(2, 'two'::text)
|
col_string_array = '{"one"}',
|
||||||
|
(col1, col_bool, col_string_array) = ROW(2, 'two'::text, '{"two"}')
|
||||||
WHERE table1.col1 > 2
|
WHERE table1.col1 > 2
|
||||||
RETURNING table1.col1 AS "table1.col1",
|
RETURNING table1.col1 AS "table1.col1",
|
||||||
table1.col_bool AS "table1.col_bool";
|
table1.col_bool AS "table1.col_bool",
|
||||||
|
table1.col_string_array AS "table1.col_string_array";
|
||||||
`)
|
`)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,11 @@ func Bool(value bool) BoolExpression {
|
||||||
return CAST(jet.Bool(value)).AS_BOOL()
|
return CAST(jet.Bool(value)).AS_BOOL()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BoolArray creates new bool array literal expression
|
||||||
|
func BoolArray(elements []bool) BoolArrayExpression {
|
||||||
|
return jet.BoolArray(elements)
|
||||||
|
}
|
||||||
|
|
||||||
// Int is constructor for 64 bit signed integer expressions literals.
|
// Int is constructor for 64 bit signed integer expressions literals.
|
||||||
var Int = jet.Int
|
var Int = jet.Int
|
||||||
|
|
||||||
|
|
@ -80,6 +85,11 @@ func String(value string) StringExpression {
|
||||||
return CAST(jet.String(value)).AS_TEXT()
|
return CAST(jet.String(value)).AS_TEXT()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// StringArray creates new string array literal expression
|
||||||
|
func StringArray(elements []string) StringArrayExpression {
|
||||||
|
return jet.StringArray(elements)
|
||||||
|
}
|
||||||
|
|
||||||
// Text is a parameter constructor for the PostgreSQL text type. This constructor also adds an
|
// Text is a parameter constructor for the PostgreSQL text type. This constructor also adds an
|
||||||
// explicit placeholder type cast to text in the generated query, such as `$3::text`.
|
// explicit placeholder type cast to text in the generated query, such as `$3::text`.
|
||||||
// Example usage:
|
// Example usage:
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,8 @@ var table1ColBool = BoolColumn("col_bool")
|
||||||
var table1ColDate = DateColumn("col_date")
|
var table1ColDate = DateColumn("col_date")
|
||||||
var table1ColInterval = IntervalColumn("col_interval")
|
var table1ColInterval = IntervalColumn("col_interval")
|
||||||
var table1ColRange = Int8RangeColumn("col_range")
|
var table1ColRange = Int8RangeColumn("col_range")
|
||||||
|
var table1ColStringArray = StringArrayColumn("col_string_array")
|
||||||
|
var table1ColIntArray = IntegerArrayColumn("col_int_array")
|
||||||
|
|
||||||
var table1 = NewTable(
|
var table1 = NewTable(
|
||||||
"db",
|
"db",
|
||||||
|
|
@ -34,6 +36,8 @@ var table1 = NewTable(
|
||||||
table1ColTimestampz,
|
table1ColTimestampz,
|
||||||
table1ColInterval,
|
table1ColInterval,
|
||||||
table1ColRange,
|
table1ColRange,
|
||||||
|
table1ColStringArray,
|
||||||
|
table1ColIntArray,
|
||||||
)
|
)
|
||||||
|
|
||||||
var table2Col3 = IntegerColumn("col3")
|
var table2Col3 = IntegerColumn("col3")
|
||||||
|
|
@ -49,8 +53,10 @@ var table2ColTimestampz = TimestampzColumn("col_timestampz")
|
||||||
var table2ColDate = DateColumn("col_date")
|
var table2ColDate = DateColumn("col_date")
|
||||||
var table2ColInterval = IntervalColumn("col_interval")
|
var table2ColInterval = IntervalColumn("col_interval")
|
||||||
var table2ColRange = Int8RangeColumn("col_range")
|
var table2ColRange = Int8RangeColumn("col_range")
|
||||||
|
var table2ColStringArray = StringArrayColumn("col_string_array")
|
||||||
|
var table2ColIntArray = IntegerArrayColumn("col_int_array")
|
||||||
|
|
||||||
var table2 = NewTable("db", "table2", "", table2Col3, table2Col4, table2ColInt, table2ColFloat, table2ColStr, table2ColBool, table2ColTime, table2ColTimez, table2ColDate, table2ColTimestamp, table2ColTimestampz, table2ColInterval, table2ColRange)
|
var table2 = NewTable("db", "table2", "", table2Col3, table2Col4, table2ColInt, table2ColFloat, table2ColStr, table2ColBool, table2ColTime, table2ColTimez, table2ColDate, table2ColTimestamp, table2ColTimestampz, table2ColInterval, table2ColRange, table2ColStringArray, table2ColIntArray)
|
||||||
|
|
||||||
var table3Col1 = IntegerColumn("col1")
|
var table3Col1 = IntegerColumn("col1")
|
||||||
var table3ColInt = IntegerColumn("col_int")
|
var table3ColInt = IntegerColumn("col_int")
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
version: '3'
|
|
||||||
services:
|
services:
|
||||||
postgres:
|
postgres:
|
||||||
image: postgres:14.1
|
image: postgres:14.1
|
||||||
|
|
@ -13,7 +12,7 @@ services:
|
||||||
- ./testdata/init/postgres:/docker-entrypoint-initdb.d
|
- ./testdata/init/postgres:/docker-entrypoint-initdb.d
|
||||||
|
|
||||||
mysql:
|
mysql:
|
||||||
image: mysql:8.0.27
|
image: mysql/mysql-server:8.0.27
|
||||||
command: ['--default-authentication-plugin=mysql_native_password', '--log_bin_trust_function_creators=1']
|
command: ['--default-authentication-plugin=mysql_native_password', '--log_bin_trust_function_creators=1']
|
||||||
restart: always
|
restart: always
|
||||||
environment:
|
environment:
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
package postgres
|
package postgres
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"github.com/lib/pq"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/go-jet/jet/v2/internal/utils/ptr"
|
"github.com/go-jet/jet/v2/internal/utils/ptr"
|
||||||
|
|
@ -2243,12 +2245,12 @@ var allTypesRow0 = model.AllTypes{
|
||||||
JSON: `{"a": 1, "b": 3}`,
|
JSON: `{"a": 1, "b": 3}`,
|
||||||
JsonbPtr: ptr.Of(`{"a": 1, "b": 3}`),
|
JsonbPtr: ptr.Of(`{"a": 1, "b": 3}`),
|
||||||
Jsonb: `{"a": 1, "b": 3}`,
|
Jsonb: `{"a": 1, "b": 3}`,
|
||||||
IntegerArrayPtr: ptr.Of("{1,2,3}"),
|
IntegerArrayPtr: &pq.Int32Array{1, 2, 3},
|
||||||
IntegerArray: "{1,2,3}",
|
IntegerArray: pq.Int32Array{1, 2, 3},
|
||||||
TextArrayPtr: ptr.Of("{breakfast,consulting}"),
|
TextArrayPtr: &pq.StringArray{"breakfast", "consulting"},
|
||||||
TextArray: "{breakfast,consulting}",
|
TextArray: pq.StringArray{"breakfast", "consulting"},
|
||||||
JsonbArray: `{"{\"a\": 1, \"b\": 2}","{\"a\": 3, \"b\": 4}"}`,
|
JsonbArray: pq.StringArray{`{"a": 1, "b": 2}`, `{"a": 3, "b": 4}`},
|
||||||
TextMultiDimArrayPtr: ptr.Of("{{meeting,lunch},{training,presentation}}"),
|
TextMultiDimArrayPtr: testutils.StringPtr("{{meeting,lunch},{training,presentation}}"),
|
||||||
TextMultiDimArray: "{{meeting,lunch},{training,presentation}}",
|
TextMultiDimArray: "{{meeting,lunch},{training,presentation}}",
|
||||||
MoodPtr: &moodSad,
|
MoodPtr: &moodSad,
|
||||||
Mood: model.Mood_Happy,
|
Mood: model.Mood_Happy,
|
||||||
|
|
@ -2312,10 +2314,10 @@ var allTypesRow1 = model.AllTypes{
|
||||||
JsonbPtr: nil,
|
JsonbPtr: nil,
|
||||||
Jsonb: `{"a": 1, "b": 3}`,
|
Jsonb: `{"a": 1, "b": 3}`,
|
||||||
IntegerArrayPtr: nil,
|
IntegerArrayPtr: nil,
|
||||||
IntegerArray: "{1,2,3}",
|
IntegerArray: pq.Int32Array{1, 2, 3},
|
||||||
TextArrayPtr: nil,
|
TextArrayPtr: nil,
|
||||||
TextArray: "{breakfast,consulting}",
|
TextArray: pq.StringArray{"breakfast", "consulting"},
|
||||||
JsonbArray: `{"{\"a\": 1, \"b\": 2}","{\"a\": 3, \"b\": 4}"}`,
|
JsonbArray: pq.StringArray{`{"a": 1, "b": 2}`, `{"a": 3, "b": 4}`},
|
||||||
TextMultiDimArrayPtr: nil,
|
TextMultiDimArrayPtr: nil,
|
||||||
TextMultiDimArray: "{{meeting,lunch},{training,presentation}}",
|
TextMultiDimArray: "{{meeting,lunch},{training,presentation}}",
|
||||||
MoodPtr: nil,
|
MoodPtr: nil,
|
||||||
|
|
|
||||||
|
|
@ -844,6 +844,7 @@ package model
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
|
"github.com/lib/pq"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -902,11 +903,11 @@ type AllTypes struct {
|
||||||
JSON string
|
JSON string
|
||||||
JsonbPtr *string
|
JsonbPtr *string
|
||||||
Jsonb string
|
Jsonb string
|
||||||
IntegerArrayPtr *string
|
IntegerArrayPtr *pq.Int32Array
|
||||||
IntegerArray string
|
IntegerArray pq.Int32Array
|
||||||
TextArrayPtr *string
|
TextArrayPtr *pq.StringArray
|
||||||
TextArray string
|
TextArray pq.StringArray
|
||||||
JsonbArray string
|
JsonbArray pq.StringArray
|
||||||
TextMultiDimArrayPtr *string
|
TextMultiDimArrayPtr *string
|
||||||
TextMultiDimArray string
|
TextMultiDimArray string
|
||||||
MoodPtr *Mood
|
MoodPtr *Mood
|
||||||
|
|
@ -1007,11 +1008,11 @@ type allTypesTable struct {
|
||||||
JSON postgres.ColumnString
|
JSON postgres.ColumnString
|
||||||
JsonbPtr postgres.ColumnString
|
JsonbPtr postgres.ColumnString
|
||||||
Jsonb postgres.ColumnString
|
Jsonb postgres.ColumnString
|
||||||
IntegerArrayPtr postgres.ColumnString
|
IntegerArrayPtr postgres.ColumnIntegerArray
|
||||||
IntegerArray postgres.ColumnString
|
IntegerArray postgres.ColumnIntegerArray
|
||||||
TextArrayPtr postgres.ColumnString
|
TextArrayPtr postgres.ColumnStringArray
|
||||||
TextArray postgres.ColumnString
|
TextArray postgres.ColumnStringArray
|
||||||
JsonbArray postgres.ColumnString
|
JsonbArray postgres.ColumnStringArray
|
||||||
TextMultiDimArrayPtr postgres.ColumnString
|
TextMultiDimArrayPtr postgres.ColumnString
|
||||||
TextMultiDimArray postgres.ColumnString
|
TextMultiDimArray postgres.ColumnString
|
||||||
MoodPtr postgres.ColumnString
|
MoodPtr postgres.ColumnString
|
||||||
|
|
@ -1111,11 +1112,11 @@ func newAllTypesTableImpl(schemaName, tableName, alias string) allTypesTable {
|
||||||
JSONColumn = postgres.StringColumn("json")
|
JSONColumn = postgres.StringColumn("json")
|
||||||
JsonbPtrColumn = postgres.StringColumn("jsonb_ptr")
|
JsonbPtrColumn = postgres.StringColumn("jsonb_ptr")
|
||||||
JsonbColumn = postgres.StringColumn("jsonb")
|
JsonbColumn = postgres.StringColumn("jsonb")
|
||||||
IntegerArrayPtrColumn = postgres.StringColumn("integer_array_ptr")
|
IntegerArrayPtrColumn = postgres.IntegerArrayColumn("integer_array_ptr")
|
||||||
IntegerArrayColumn = postgres.StringColumn("integer_array")
|
IntegerArrayColumn = postgres.IntegerArrayColumn("integer_array")
|
||||||
TextArrayPtrColumn = postgres.StringColumn("text_array_ptr")
|
TextArrayPtrColumn = postgres.StringArrayColumn("text_array_ptr")
|
||||||
TextArrayColumn = postgres.StringColumn("text_array")
|
TextArrayColumn = postgres.StringArrayColumn("text_array")
|
||||||
JsonbArrayColumn = postgres.StringColumn("jsonb_array")
|
JsonbArrayColumn = postgres.StringArrayColumn("jsonb_array")
|
||||||
TextMultiDimArrayPtrColumn = postgres.StringColumn("text_multi_dim_array_ptr")
|
TextMultiDimArrayPtrColumn = postgres.StringColumn("text_multi_dim_array_ptr")
|
||||||
TextMultiDimArrayColumn = postgres.StringColumn("text_multi_dim_array")
|
TextMultiDimArrayColumn = postgres.StringColumn("text_multi_dim_array")
|
||||||
MoodPtrColumn = postgres.StringColumn("mood_ptr")
|
MoodPtrColumn = postgres.StringColumn("mood_ptr")
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ package postgres
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"github.com/lib/pq"
|
||||||
"github.com/go-jet/jet/v2/internal/utils/ptr"
|
"github.com/go-jet/jet/v2/internal/utils/ptr"
|
||||||
"github.com/volatiletech/null/v8"
|
"github.com/volatiletech/null/v8"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
@ -1006,6 +1007,7 @@ func TestScanIntoCustomBaseTypes(t *testing.T) {
|
||||||
type MyFloat32 float32
|
type MyFloat32 float32
|
||||||
type MyFloat64 float64
|
type MyFloat64 float64
|
||||||
type MyString string
|
type MyString string
|
||||||
|
type MyStringArray pq.StringArray
|
||||||
type MyTime = time.Time
|
type MyTime = time.Time
|
||||||
|
|
||||||
type film struct {
|
type film struct {
|
||||||
|
|
@ -1020,26 +1022,25 @@ func TestScanIntoCustomBaseTypes(t *testing.T) {
|
||||||
ReplacementCost MyFloat64
|
ReplacementCost MyFloat64
|
||||||
Rating *model.MpaaRating
|
Rating *model.MpaaRating
|
||||||
LastUpdate MyTime
|
LastUpdate MyTime
|
||||||
SpecialFeatures *MyString
|
SpecialFeatures MyStringArray
|
||||||
Fulltext MyString
|
Fulltext MyString
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We'll skip special features, because it's a slice and it does not implement sql.Scanner
|
||||||
stmt := SELECT(
|
stmt := SELECT(
|
||||||
Film.AllColumns,
|
Film.AllColumns.Except(Film.SpecialFeatures),
|
||||||
).FROM(
|
).FROM(
|
||||||
Film,
|
Film,
|
||||||
).ORDER_BY(
|
).ORDER_BY(
|
||||||
Film.FilmID.ASC(),
|
Film.FilmID.ASC(),
|
||||||
).LIMIT(3)
|
).LIMIT(3)
|
||||||
|
|
||||||
var films []model.Film
|
var myFilms []film
|
||||||
|
err := stmt.Query(db, &myFilms)
|
||||||
err := stmt.Query(db, &films)
|
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
var myFilms []film
|
var films []model.Film
|
||||||
|
err = stmt.Query(db, &films)
|
||||||
err = stmt.Query(db, &myFilms)
|
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
require.Equal(t, testutils.ToJSON(films), testutils.ToJSON(myFilms))
|
require.Equal(t, testutils.ToJSON(films), testutils.ToJSON(myFilms))
|
||||||
|
|
@ -1254,7 +1255,7 @@ var film1 = model.Film{
|
||||||
ReplacementCost: 20.99,
|
ReplacementCost: 20.99,
|
||||||
Rating: &pgRating,
|
Rating: &pgRating,
|
||||||
LastUpdate: *testutils.TimestampWithoutTimeZone("2013-05-26 14:50:58.951", 3),
|
LastUpdate: *testutils.TimestampWithoutTimeZone("2013-05-26 14:50:58.951", 3),
|
||||||
SpecialFeatures: ptr.Of("{\"Deleted Scenes\",\"Behind the Scenes\"}"),
|
SpecialFeatures: &pq.StringArray{"Deleted Scenes", "Behind the Scenes"},
|
||||||
Fulltext: "'academi':1 'battl':15 'canadian':20 'dinosaur':2 'drama':5 'epic':4 'feminist':8 'mad':11 'must':14 'rocki':21 'scientist':12 'teacher':17",
|
Fulltext: "'academi':1 'battl':15 'canadian':20 'dinosaur':2 'drama':5 'epic':4 'feminist':8 'mad':11 'must':14 'rocki':21 'scientist':12 'teacher':17",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1270,7 +1271,7 @@ var film2 = model.Film{
|
||||||
ReplacementCost: 12.99,
|
ReplacementCost: 12.99,
|
||||||
Rating: &gRating,
|
Rating: &gRating,
|
||||||
LastUpdate: *testutils.TimestampWithoutTimeZone("2013-05-26 14:50:58.951", 3),
|
LastUpdate: *testutils.TimestampWithoutTimeZone("2013-05-26 14:50:58.951", 3),
|
||||||
SpecialFeatures: ptr.Of(`{Trailers,"Deleted Scenes"}`),
|
SpecialFeatures: &pq.StringArray{"Trailers", "Deleted Scenes"},
|
||||||
Fulltext: `'ace':1 'administr':9 'ancient':19 'astound':4 'car':17 'china':20 'databas':8 'epistl':5 'explor':12 'find':15 'goldfing':2 'must':14`,
|
Fulltext: `'ace':1 'administr':9 'ancient':19 'astound':4 'car':17 'china':20 'databas':8 'epistl':5 'explor':12 'find':15 'goldfing':2 'must':14`,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"github.com/go-jet/jet/v2/internal/utils/ptr"
|
"github.com/go-jet/jet/v2/internal/utils/ptr"
|
||||||
|
"github.com/lib/pq"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
|
@ -1852,7 +1853,7 @@ ORDER BY film.film_id ASC;
|
||||||
Rating: &gRating,
|
Rating: &gRating,
|
||||||
RentalDuration: 3,
|
RentalDuration: 3,
|
||||||
LastUpdate: *testutils.TimestampWithoutTimeZone("2013-05-26 14:50:58.951", 3),
|
LastUpdate: *testutils.TimestampWithoutTimeZone("2013-05-26 14:50:58.951", 3),
|
||||||
SpecialFeatures: ptr.Of("{Trailers,\"Deleted Scenes\"}"),
|
SpecialFeatures: &pq.StringArray{"Trailers", "Deleted Scenes"},
|
||||||
Fulltext: "'ace':1 'administr':9 'ancient':19 'astound':4 'car':17 'china':20 'databas':8 'epistl':5 'explor':12 'find':15 'goldfing':2 'must':14",
|
Fulltext: "'ace':1 'administr':9 'ancient':19 'astound':4 'car':17 'china':20 'databas':8 'epistl':5 'explor':12 'find':15 'goldfing':2 'must':14",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -2783,7 +2784,6 @@ ORDER BY actor.actor_id ASC, film.film_id ASC;
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
//testutils.SaveJSONFile(dest, "./testdata/results/postgres/quick-start-dest.json")
|
//testutils.SaveJSONFile(dest, "./testdata/results/postgres/quick-start-dest.json")
|
||||||
|
|
||||||
testutils.AssertJSONFile(t, dest, "./testdata/results/postgres/quick-start-dest.json")
|
testutils.AssertJSONFile(t, dest, "./testdata/results/postgres/quick-start-dest.json")
|
||||||
|
|
||||||
var dest2 []struct {
|
var dest2 []struct {
|
||||||
|
|
@ -2798,7 +2798,7 @@ ORDER BY actor.actor_id ASC, film.film_id ASC;
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
})
|
})
|
||||||
|
|
||||||
//testutils.SaveJSONFile(dest2, "./testdata/results/postgres/quick-start-dest2.json")
|
//testutils.SaveJSONFile(dest, "./testdata/results/postgres/quick-start-dest2.json")
|
||||||
testutils.AssertJSONFile(t, dest2, "./testdata/results/postgres/quick-start-dest2.json")
|
testutils.AssertJSONFile(t, dest2, "./testdata/results/postgres/quick-start-dest2.json")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -3389,7 +3389,10 @@ func TestSelectRecursionScanNxM(t *testing.T) {
|
||||||
"ReplacementCost": 20.99,
|
"ReplacementCost": 20.99,
|
||||||
"Rating": "PG",
|
"Rating": "PG",
|
||||||
"LastUpdate": "2013-05-26T14:50:58.951Z",
|
"LastUpdate": "2013-05-26T14:50:58.951Z",
|
||||||
"SpecialFeatures": "{\"Deleted Scenes\",\"Behind the Scenes\"}",
|
"SpecialFeatures": [
|
||||||
|
"Deleted Scenes",
|
||||||
|
"Behind the Scenes"
|
||||||
|
],
|
||||||
"Fulltext": "'academi':1 'battl':15 'canadian':20 'dinosaur':2 'drama':5 'epic':4 'feminist':8 'mad':11 'must':14 'rocki':21 'scientist':12 'teacher':17",
|
"Fulltext": "'academi':1 'battl':15 'canadian':20 'dinosaur':2 'drama':5 'epic':4 'feminist':8 'mad':11 'must':14 'rocki':21 'scientist':12 'teacher':17",
|
||||||
"Actors": [
|
"Actors": [
|
||||||
{
|
{
|
||||||
|
|
@ -3413,7 +3416,10 @@ func TestSelectRecursionScanNxM(t *testing.T) {
|
||||||
"ReplacementCost": 9.99,
|
"ReplacementCost": 9.99,
|
||||||
"Rating": "R",
|
"Rating": "R",
|
||||||
"LastUpdate": "2013-05-26T14:50:58.951Z",
|
"LastUpdate": "2013-05-26T14:50:58.951Z",
|
||||||
"SpecialFeatures": "{Trailers,\"Deleted Scenes\"}",
|
"SpecialFeatures": [
|
||||||
|
"Trailers",
|
||||||
|
"Deleted Scenes"
|
||||||
|
],
|
||||||
"Fulltext": "'anaconda':1 'australia':18 'confess':2 'dentist':8,11 'display':5 'fight':14 'girl':16 'lacklustur':4 'must':13",
|
"Fulltext": "'anaconda':1 'australia':18 'confess':2 'dentist':8,11 'display':5 'fight':14 'girl':16 'lacklustur':4 'must':13",
|
||||||
"Actors": [
|
"Actors": [
|
||||||
{
|
{
|
||||||
|
|
@ -3461,7 +3467,10 @@ func TestSelectRecursionScanNxM(t *testing.T) {
|
||||||
"ReplacementCost": 20.99,
|
"ReplacementCost": 20.99,
|
||||||
"Rating": "PG",
|
"Rating": "PG",
|
||||||
"LastUpdate": "2013-05-26T14:50:58.951Z",
|
"LastUpdate": "2013-05-26T14:50:58.951Z",
|
||||||
"SpecialFeatures": "{\"Deleted Scenes\",\"Behind the Scenes\"}",
|
"SpecialFeatures": [
|
||||||
|
"Deleted Scenes",
|
||||||
|
"Behind the Scenes"
|
||||||
|
],
|
||||||
"Fulltext": "'academi':1 'battl':15 'canadian':20 'dinosaur':2 'drama':5 'epic':4 'feminist':8 'mad':11 'must':14 'rocki':21 'scientist':12 'teacher':17",
|
"Fulltext": "'academi':1 'battl':15 'canadian':20 'dinosaur':2 'drama':5 'epic':4 'feminist':8 'mad':11 'must':14 'rocki':21 'scientist':12 'teacher':17",
|
||||||
"Actors": null
|
"Actors": null
|
||||||
},
|
},
|
||||||
|
|
@ -3477,7 +3486,10 @@ func TestSelectRecursionScanNxM(t *testing.T) {
|
||||||
"ReplacementCost": 9.99,
|
"ReplacementCost": 9.99,
|
||||||
"Rating": "R",
|
"Rating": "R",
|
||||||
"LastUpdate": "2013-05-26T14:50:58.951Z",
|
"LastUpdate": "2013-05-26T14:50:58.951Z",
|
||||||
"SpecialFeatures": "{Trailers,\"Deleted Scenes\"}",
|
"SpecialFeatures": [
|
||||||
|
"Trailers",
|
||||||
|
"Deleted Scenes"
|
||||||
|
],
|
||||||
"Fulltext": "'anaconda':1 'australia':18 'confess':2 'dentist':8,11 'display':5 'fight':14 'girl':16 'lacklustur':4 'must':13",
|
"Fulltext": "'anaconda':1 'australia':18 'confess':2 'dentist':8,11 'display':5 'fight':14 'girl':16 'lacklustur':4 'must':13",
|
||||||
"Actors": null
|
"Actors": null
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue