From 647ef21aaf649858f4b5cbc0962475ebd28db6e5 Mon Sep 17 00:00:00 2001 From: go-jet Date: Tue, 6 Aug 2019 10:29:04 +0200 Subject: [PATCH] Dialect refactor improvements and clean up. --- internal/jet/README.md => README.md | 0 generator/internal/metadata/column_info.go | 5 +- internal/jet/bool_expression_test.go | 32 +- internal/jet/clause_test.go | 1 + internal/jet/dialects.go | 53 ++- internal/jet/func_expression.go | 45 ++- internal/jet/literal_expression.go | 118 +++++- internal/jet/select_statement_test.go | 13 +- internal/jet/string_expression.go | 18 +- internal/jet/string_expression_test.go | 11 +- internal/jet/testutils.go | 19 +- internal/jet/update_statement.go | 2 +- internal/jet/update_statement_test.go | 25 +- internal/testutils/test_utils.go | 5 +- mysql/bool_expression_test.go | 89 ----- mysql/column.go | 9 - mysql/columns.go | 41 +++ mysql/dialect.go | 53 +-- mysql/dialect_test.go | 35 ++ mysql/expressions.go | 13 +- mysql/functions.go | 122 ++++++ mysql/integer_expression_test.go | 107 ------ mysql/literal.go | 13 + mysql/statements.go | 27 ++ mysql/types.go | 86 ----- mysql/{testutils.go => utils_test.go} | 0 postgres/{postgres_cast.go => cast.go} | 25 +- .../{postgres_cast_test.go => cast_test.go} | 0 postgres/{postgres_types.go => columns.go} | 30 +- postgres/dialect.go | 44 ++- postgres/dialect_test.go | 15 + postgres/expressions.go | 31 ++ .../{postgres_functions.go => functions.go} | 44 +++ .../{postgres_keywords.go => keywords.go} | 0 postgres/{postgres_literal.go => literal.go} | 18 +- postgres/literal_test.go | 7 + .../{postgres_lock.go => lock_statement.go} | 0 .../{postgres_operators.go => operators.go} | 10 +- postgres/postgres_column.go | 9 - postgres/postgres_enum.go | 5 - postgres/postgres_expressions.go | 5 - ...postgres_select.go => select_statement.go} | 18 +- ...elect_test.go => select_statement_test.go} | 0 postgres/{postgres_table.go => table.go} | 0 postgres/time_expression_test.go | 53 --- postgres/timestamp_expression_test.go | 54 --- postgres/{testutils.go => utils_test.go} | 0 tests/init/data/mysql/test_sample.sql | 12 +- tests/init/data/postgres/test_sample.sql | 10 +- tests/mysql/alltypes_test.go | 346 +++++++++++++++++- tests/postgres/alltypes_test.go | 88 +++-- tests/postgres/select_test.go | 2 - 52 files changed, 1097 insertions(+), 671 deletions(-) rename internal/jet/README.md => README.md (100%) delete mode 100644 mysql/bool_expression_test.go delete mode 100644 mysql/column.go create mode 100644 mysql/columns.go create mode 100644 mysql/dialect_test.go create mode 100644 mysql/functions.go delete mode 100644 mysql/integer_expression_test.go create mode 100644 mysql/literal.go create mode 100644 mysql/statements.go delete mode 100644 mysql/types.go rename mysql/{testutils.go => utils_test.go} (100%) rename postgres/{postgres_cast.go => cast.go} (81%) rename postgres/{postgres_cast_test.go => cast_test.go} (100%) rename postgres/{postgres_types.go => columns.go} (50%) create mode 100644 postgres/dialect_test.go rename postgres/{postgres_functions.go => functions.go} (67%) rename postgres/{postgres_keywords.go => keywords.go} (100%) rename postgres/{postgres_literal.go => literal.go} (57%) create mode 100644 postgres/literal_test.go rename postgres/{postgres_lock.go => lock_statement.go} (100%) rename postgres/{postgres_operators.go => operators.go} (55%) delete mode 100644 postgres/postgres_column.go delete mode 100644 postgres/postgres_enum.go delete mode 100644 postgres/postgres_expressions.go rename postgres/{postgres_select.go => select_statement.go} (97%) rename postgres/{postgres_select_test.go => select_statement_test.go} (100%) rename postgres/{postgres_table.go => table.go} (100%) delete mode 100644 postgres/time_expression_test.go delete mode 100644 postgres/timestamp_expression_test.go rename postgres/{testutils.go => utils_test.go} (100%) diff --git a/internal/jet/README.md b/README.md similarity index 100% rename from internal/jet/README.md rename to README.md diff --git a/generator/internal/metadata/column_info.go b/generator/internal/metadata/column_info.go index 8dc2398..0f8e882 100644 --- a/generator/internal/metadata/column_info.go +++ b/generator/internal/metadata/column_info.go @@ -31,7 +31,8 @@ func (c ColumnInfo) SqlBuilderColumnType() string { return "Timestamp" case "timestamp with time zone": return "Timestampz" - case "time without time zone": + case "time without time zone", + "time": //MySQL return "Time" case "time with time zone": return "Timez" @@ -67,7 +68,7 @@ func (c ColumnInfo) GoBaseType() string { case "bigint": return "int64" case "date", "timestamp without time zone", "timestamp with time zone", "time with time zone", "time without time zone", - "timestamp", "datetime": // MySQL + "timestamp", "datetime", "time": // MySQL return "time.Time" case "bytea", "binary", "varbinary", "tinyblob", "blob", "mediumblob", "longblob": //MySQL diff --git a/internal/jet/bool_expression_test.go b/internal/jet/bool_expression_test.go index 2b7a8e0..995d302 100644 --- a/internal/jet/bool_expression_test.go +++ b/internal/jet/bool_expression_test.go @@ -11,25 +11,25 @@ func TestBoolExpressionEQ(t *testing.T) { func TestBoolExpressionNOT_EQ(t *testing.T) { assertClauseSerialize(t, table1ColBool.NOT_EQ(table2ColBool), "(table1.col_bool != table2.col_bool)") - assertClauseSerialize(t, table1ColBool.NOT_EQ(Bool(true)), "(table1.col_bool != TRUE)") + assertClauseSerialize(t, table1ColBool.NOT_EQ(Bool(true)), "(table1.col_bool != $1)", true) } func TestBoolExpressionIS_DISTINCT_FROM(t *testing.T) { assertClauseSerialize(t, table1ColBool.IS_DISTINCT_FROM(table2ColBool), "(table1.col_bool IS DISTINCT FROM table2.col_bool)") - assertClauseSerialize(t, table1ColBool.IS_DISTINCT_FROM(Bool(false)), "(table1.col_bool IS DISTINCT FROM FALSE)") + assertClauseSerialize(t, table1ColBool.IS_DISTINCT_FROM(Bool(false)), "(table1.col_bool IS DISTINCT FROM $1)", false) } func TestBoolExpressionIS_NOT_DISTINCT_FROM(t *testing.T) { assertClauseSerialize(t, table1ColBool.IS_NOT_DISTINCT_FROM(table2ColBool), "(table1.col_bool IS NOT DISTINCT FROM table2.col_bool)") - assertClauseSerialize(t, table1ColBool.IS_NOT_DISTINCT_FROM(Bool(false)), "(table1.col_bool IS NOT DISTINCT FROM FALSE)") + assertClauseSerialize(t, table1ColBool.IS_NOT_DISTINCT_FROM(Bool(false)), "(table1.col_bool IS NOT DISTINCT FROM $1)", false) } func TestBoolExpressionIS_TRUE(t *testing.T) { assertClauseSerialize(t, table1ColBool.IS_TRUE(), "table1.col_bool IS TRUE") assertClauseSerialize(t, (Int(2).EQ(table1ColInt)).IS_TRUE(), - `(2 = table1.col_int) IS TRUE`) + `($1 = table1.col_int) IS TRUE`, int64(2)) assertClauseSerialize(t, (Int(2).EQ(table1ColInt)).IS_TRUE().AND(Int(4).EQ(table2ColInt)), - `((2 = table1.col_int) IS TRUE AND (4 = table2.col_int))`) + `(($1 = table1.col_int) IS TRUE AND ($2 = table2.col_int))`, int64(2), int64(4)) } func TestBoolExpressionIS_NOT_TRUE(t *testing.T) { @@ -55,20 +55,20 @@ func TestBoolExpressionIS_NOT_UNKNOWN(t *testing.T) { func TestBinaryBoolExpression(t *testing.T) { boolExpression := Int(2).EQ(Int(3)) - assertClauseSerialize(t, boolExpression, "(2 = 3)") + assertClauseSerialize(t, boolExpression, "($1 = $2)", int64(2), int64(3)) - assertProjectionSerialize(t, boolExpression, "2 = 3") + assertProjectionSerialize(t, boolExpression, "$1 = $2", int64(2), int64(3)) assertProjectionSerialize(t, boolExpression.AS("alias_eq_expression"), - `(2 = 3) AS "alias_eq_expression"`) + `($1 = $2) AS "alias_eq_expression"`, int64(2), int64(3)) assertClauseSerialize(t, boolExpression.AND(Int(4).EQ(Int(5))), - "((2 = 3) AND (4 = 5))") + "(($1 = $2) AND ($3 = $4))", int64(2), int64(3), int64(4), int64(5)) assertClauseSerialize(t, boolExpression.OR(Int(4).EQ(Int(5))), - "((2 = 3) OR (4 = 5))") + "(($1 = $2) OR ($3 = $4))", int64(2), int64(3), int64(4), int64(5)) } func TestBoolLiteral(t *testing.T) { - assertClauseSerialize(t, Bool(true), "TRUE", true) - assertClauseSerialize(t, Bool(false), "FALSE", false) + assertClauseSerialize(t, Bool(true), "$1", true) + assertClauseSerialize(t, Bool(false), "$1", false) } func TestExists(t *testing.T) { @@ -78,13 +78,13 @@ func TestExists(t *testing.T) { WHERE(table1Col1.EQ(table2Col3)), ), `(EXISTS ( - SELECT 1 + SELECT $1 FROM db.table2 WHERE table1.col1 = table2.col3 -))`) +))`, int64(1)) } func TestBoolExp(t *testing.T) { - assertClauseSerialize(t, BoolExp(String("true")), "'true'") - assertClauseSerialize(t, BoolExp(String("true")).IS_TRUE(), "'true' IS TRUE") + assertClauseSerialize(t, BoolExp(String("true")), "$1", "true") + assertClauseSerialize(t, BoolExp(String("true")).IS_TRUE(), "$1 IS TRUE", "true") } diff --git a/internal/jet/clause_test.go b/internal/jet/clause_test.go index a105cb3..0d55807 100644 --- a/internal/jet/clause_test.go +++ b/internal/jet/clause_test.go @@ -21,6 +21,7 @@ func TestArgToString(t *testing.T) { assert.Equal(t, argToString(uint(32)), "32") assert.Equal(t, argToString(uint32(32)), "32") assert.Equal(t, argToString(uint64(64)), "64") + assert.Equal(t, argToString(float64(1.11)), "1.11") assert.Equal(t, argToString("john"), "'john'") assert.Equal(t, argToString([]byte("john")), "'john'") diff --git a/internal/jet/dialects.go b/internal/jet/dialects.go index 5a0e15f..9f730b7 100644 --- a/internal/jet/dialects.go +++ b/internal/jet/dialects.go @@ -1,10 +1,17 @@ package jet +import ( + "errors" + "strconv" +) + var ANSII = NewDialect(DialectParams{ // just for tests - AliasQuoteChar: '"', + AliasQuoteChar: '"', + IdentifierQuoteChar: '"', ArgumentPlaceholder: func(ord int) string { - return "#" + return "$" + strconv.Itoa(ord) }, + SupportsReturning: true, }) type Dialect interface { @@ -15,7 +22,7 @@ type Dialect interface { AliasQuoteChar() byte IdentifierQuoteChar() byte ArgumentPlaceholder() QueryPlaceholderFunc - UpdateAssigment() func(columns []IColumn, values []Clause, out *SqlBuilder) (err error) + SetClause() func(columns []IColumn, values []Clause, out *SqlBuilder) (err error) SupportsReturning() bool } @@ -35,7 +42,7 @@ type DialectParams struct { AliasQuoteChar byte IdentifierQuoteChar byte ArgumentPlaceholder QueryPlaceholderFunc - UpdateAssigment func(columns []IColumn, values []Clause, out *SqlBuilder) (err error) + SetClause func(columns []IColumn, values []Clause, out *SqlBuilder) (err error) SupportsReturning bool } @@ -49,7 +56,7 @@ func NewDialect(params DialectParams) Dialect { aliasQuoteChar: params.AliasQuoteChar, identifierQuoteChar: params.IdentifierQuoteChar, argumentPlaceholder: params.ArgumentPlaceholder, - updateAssigment: params.UpdateAssigment, + setClause: params.SetClause, supportsReturning: params.SupportsReturning, } } @@ -62,7 +69,7 @@ type dialectImpl struct { aliasQuoteChar byte identifierQuoteChar byte argumentPlaceholder QueryPlaceholderFunc - updateAssigment UpdateAssigmentFunc + setClause UpdateAssigmentFunc supportsReturning bool } @@ -95,10 +102,40 @@ func (d *dialectImpl) ArgumentPlaceholder() QueryPlaceholderFunc { return d.argumentPlaceholder } -func (d *dialectImpl) UpdateAssigment() func(columns []IColumn, values []Clause, out *SqlBuilder) (err error) { - return d.updateAssigment +func (d *dialectImpl) SetClause() func(columns []IColumn, values []Clause, out *SqlBuilder) (err error) { + if d.setClause != nil { + return d.setClause + } + return setClause } func (d *dialectImpl) SupportsReturning() bool { return d.supportsReturning } + +func setClause(columns []IColumn, values []Clause, out *SqlBuilder) (err error) { + + if len(columns) != len(values) { + return errors.New("jet: mismatch in numers of columns and values") + } + + for i, column := range columns { + if i > 0 { + out.WriteString(", ") + } + + if column == nil { + return errors.New("jet: nil column in columns list") + } + + out.WriteString(column.Name()) + + out.WriteString(" = ") + + if err = Serialize(values[i], UpdateStatementType, out); err != nil { + return err + } + } + + return nil +} diff --git a/internal/jet/func_expression.go b/internal/jet/func_expression.go index d8e269a..1a94dbf 100644 --- a/internal/jet/func_expression.go +++ b/internal/jet/func_expression.go @@ -204,14 +204,13 @@ func CHR(integerExpression IntegerExpression) StringExpression { return newStringFunc("CHR", integerExpression) } -// -//func CONCAT(expressions ...Expression) StringExpression { -// return newStringFunc("CONCAT", expressions...) -//} -// -//func CONCAT_WS(expressions ...Expression) StringExpression { -// return newStringFunc("CONCAT_WS", expressions...) -//} +func CONCAT(expressions ...Expression) StringExpression { + return newStringFunc("CONCAT", expressions...) +} + +func CONCAT_WS(expressions ...Expression) StringExpression { + return newStringFunc("CONCAT_WS", expressions...) +} // CONVERT converts string to dest_encoding. The original encoding is // specified by src_encoding. The string must be valid in this encoding. @@ -243,11 +242,11 @@ func DECODE(data StringExpression, format StringExpression) StringExpression { return newStringFunc("DECODE", data, format) } -//func FORMAT(formatStr StringExpression, formatArgs ...expressions) StringExpression { -// args := []expressions{formatStr} -// args = append(args, formatArgs...) -// return newStringFunc("FORMAT", args...) -//} +func FORMAT(formatStr StringExpression, formatArgs ...Expression) StringExpression { + args := []Expression{formatStr} + args = append(args, formatArgs...) + return newStringFunc("FORMAT", args...) +} // INITCAP converts the first letter of each word to upper case // and the rest to lower case. Words are sequences of alphanumeric @@ -344,6 +343,14 @@ func TO_HEX(number IntegerExpression) StringExpression { return newStringFunc("TO_HEX", number) } +func REGEXP_LIKE(stringExp StringExpression, pattern StringExpression, matchType ...string) BoolExpression { + if len(matchType) > 0 { + return newBoolFunc("REGEXP_LIKE", stringExp, pattern, String(matchType[0], true)) + } + + return newBoolFunc("REGEXP_LIKE", stringExp, pattern) +} + //----------Data Type Formatting Functions ----------------------// // TO_CHAR converts expression to string with format @@ -425,9 +432,9 @@ func LOCALTIMESTAMP(precision ...int) TimestampExpression { var timestampFunc *timestampFunc if len(precision) > 0 { - timestampFunc = newTimestampFunc("LOCALTIMESTAMP", constLiteral(precision[0])) + timestampFunc = NewTimestampFunc("LOCALTIMESTAMP", constLiteral(precision[0])) } else { - timestampFunc = newTimestampFunc("LOCALTIMESTAMP") + timestampFunc = NewTimestampFunc("LOCALTIMESTAMP") } timestampFunc.noBrackets = true @@ -506,6 +513,12 @@ func (f *funcExpressionImpl) serialize(statement StatementType, out *SqlBuilder, return errors.New("jet: Function expressions is nil. ") } + if serializeOverride := out.Dialect.SerializeOverride(f.name); serializeOverride != nil { + + serializeOverrideFunc := serializeOverride(f.expressions...) + return serializeOverrideFunc(statement, out, options...) + } + addBrackets := !f.noBrackets || len(f.expressions) > 0 if addBrackets { @@ -629,7 +642,7 @@ type timestampFunc struct { timestampInterfaceImpl } -func newTimestampFunc(name string, expressions ...Expression) *timestampFunc { +func NewTimestampFunc(name string, expressions ...Expression) *timestampFunc { timestampFunc := ×tampFunc{} timestampFunc.funcExpressionImpl = *newFunc(name, expressions, timestampFunc) diff --git a/internal/jet/literal_expression.go b/internal/jet/literal_expression.go index ba5b692..62d9d4c 100644 --- a/internal/jet/literal_expression.go +++ b/internal/jet/literal_expression.go @@ -1,9 +1,20 @@ package jet -import "fmt" +import ( + "fmt" + "strconv" + "time" +) // Representation of an escaped literal -type literalExpression struct { +type LiteralExpression interface { + Expression + + Value() interface{} + SetConstant(constant bool) +} + +type literalExpressionImpl struct { expressionInterfaceImpl noOpVisitorImpl @@ -11,8 +22,8 @@ type literalExpression struct { constant bool } -func literal(value interface{}, optionalConstant ...bool) *literalExpression { - exp := literalExpression{value: value} +func literal(value interface{}, optionalConstant ...bool) *literalExpressionImpl { + exp := literalExpressionImpl{value: value} if len(optionalConstant) > 0 { exp.constant = optionalConstant[0] @@ -23,14 +34,14 @@ func literal(value interface{}, optionalConstant ...bool) *literalExpression { return &exp } -func constLiteral(value interface{}) *literalExpression { +func constLiteral(value interface{}) *literalExpressionImpl { exp := literal(value) exp.constant = true return exp } -func (l literalExpression) serialize(statement StatementType, out *SqlBuilder, options ...SerializeOption) error { +func (l *literalExpressionImpl) serialize(statement StatementType, out *SqlBuilder, options ...SerializeOption) error { if l.constant { out.insertConstantArgument(l.value) } else { @@ -40,29 +51,102 @@ func (l literalExpression) serialize(statement StatementType, out *SqlBuilder, o return nil } +func (l *literalExpressionImpl) Value() interface{} { + return l.value +} + +func (l *literalExpressionImpl) SetConstant(constant bool) { + l.constant = constant +} + +type integerLiteralExpression struct { + literalExpressionImpl + integerInterfaceImpl +} + // Int is constructor for integer expressions literals. func Int(value int64, constant ...bool) IntegerExpression { - return IntExp(literal(value, constant...)) + numLiteral := &integerLiteralExpression{} + + numLiteral.literalExpressionImpl = *literal(value) + if len(constant) > 0 && constant[0] == true { + numLiteral.constant = true + } + + numLiteral.literalExpressionImpl.parent = numLiteral + numLiteral.integerInterfaceImpl.parent = numLiteral + + return numLiteral +} + +//---------------------------------------------------// +type boolLiteralExpression struct { + boolInterfaceImpl + literalExpressionImpl } // Bool creates new bool literal expression func Bool(value bool) BoolExpression { - return BoolExp(literal(value)) + boolLiteralExpression := boolLiteralExpression{} + + boolLiteralExpression.literalExpressionImpl = *literal(value) + boolLiteralExpression.boolInterfaceImpl.parent = &boolLiteralExpression + + return &boolLiteralExpression +} + +//---------------------------------------------------// +type floatLiteral struct { + floatInterfaceImpl + literalExpressionImpl } // Float creates new float literal expression func Float(value float64) FloatExpression { - return FloatExp(literal(value)) + floatLiteral := floatLiteral{} + floatLiteral.literalExpressionImpl = *literal(value) + + floatLiteral.floatInterfaceImpl.parent = &floatLiteral + + return &floatLiteral +} + +//---------------------------------------------------// +type stringLiteral struct { + stringInterfaceImpl + literalExpressionImpl } // String creates new string literal expression -func String(value string) StringExpression { - return StringExp(literal(value)) +func String(value string, constant ...bool) StringExpression { + stringLiteral := stringLiteral{} + stringLiteral.literalExpressionImpl = *literal(value) + if len(constant) > 0 && constant[0] == true { + stringLiteral.constant = true + } + + stringLiteral.stringInterfaceImpl.parent = &stringLiteral + + return &stringLiteral +} + +func formatMilliseconds(milliseconds ...int) string { + if len(milliseconds) > 0 { + if milliseconds[0] < 1000 { + return fmt.Sprintf(".%03d", milliseconds[0]) + } else { + return "." + strconv.Itoa(milliseconds[0]) + } + } + + return "" } // Time creates new time literal expression -func Time(hour, minute, second, milliseconds int) TimeExpression { - timeStr := fmt.Sprintf("%02d:%02d:%02d.%03d", hour, minute, second, milliseconds) +func Time(hour, minute, second int, milliseconds ...int) TimeExpression { + timeStr := fmt.Sprintf("%02d:%02d:%02d", hour, minute, second) + + timeStr += formatMilliseconds(milliseconds...) return TimeExp(literal(timeStr)) } @@ -75,8 +159,10 @@ func Timez(hour, minute, second, milliseconds, timezone int) TimezExpression { } // Timestamp creates new timestamp literal expression -func Timestamp(year, month, day, hour, minute, second, milliseconds int) TimestampExpression { - timeStr := fmt.Sprintf("%04d-%02d-%02d %02d:%02d:%02d.%03d", year, month, day, hour, minute, second, milliseconds) +func Timestamp(year int, month time.Month, day, hour, minute, second int, milliseconds ...int) TimestampExpression { + timeStr := fmt.Sprintf("%04d-%02d-%02d %02d:%02d:%02d", year, month, day, hour, minute, second) + + timeStr += formatMilliseconds(milliseconds...) return TimestampExp(literal(timeStr)) } @@ -90,7 +176,7 @@ func Timestampz(year, month, day, hour, minute, second, milliseconds, timezone i } //Date creates new date expression -func Date(year, month, day int) DateExpression { +func Date(year int, month time.Month, day int) DateExpression { timeStr := fmt.Sprintf("%04d-%02d-%02d", year, month, day) return DateExp(literal(timeStr)) diff --git a/internal/jet/select_statement_test.go b/internal/jet/select_statement_test.go index 219eb79..4c79c08 100644 --- a/internal/jet/select_statement_test.go +++ b/internal/jet/select_statement_test.go @@ -19,16 +19,17 @@ FROM db.table2; func TestSelectLiterals(t *testing.T) { assertStatement(t, SELECT(Int(1), Float(2.2), Bool(false)).FROM(table1), ` -SELECT 1, - 2.2, - FALSE +SELECT $1, + $2, + $3 FROM db.table1; -`) +`, int64(1), 2.2, false) } func TestSelectDistinct(t *testing.T) { - assertStatement(t, SELECT(table1ColBool).DISTINCT(), ` -SELECT DISTINCT table1.col_bool AS "table1.col_bool"; + assertStatement(t, SELECT(table1ColBool).DISTINCT().FROM(table1), ` +SELECT DISTINCT table1.col_bool AS "table1.col_bool" +FROM db.table1; `) } diff --git a/internal/jet/string_expression.go b/internal/jet/string_expression.go index 4e5d5d3..74ffecd 100644 --- a/internal/jet/string_expression.go +++ b/internal/jet/string_expression.go @@ -1,5 +1,9 @@ package jet +const ( + StringConcatOperator = "||" +) + // StringExpression interface type StringExpression interface { Expression @@ -18,8 +22,8 @@ type StringExpression interface { LIKE(pattern StringExpression) BoolExpression NOT_LIKE(pattern StringExpression) BoolExpression - SIMILAR_TO(pattern StringExpression) BoolExpression - NOT_SIMILAR_TO(pattern StringExpression) BoolExpression + + REGEXP_LIKE(pattern StringExpression, matchType ...string) BoolExpression } type stringInterfaceImpl struct { @@ -59,7 +63,7 @@ func (s *stringInterfaceImpl) LT_EQ(rhs StringExpression) BoolExpression { } func (s *stringInterfaceImpl) CONCAT(rhs Expression) StringExpression { - return newBinaryStringExpression(s.parent, rhs, "||") + return newBinaryStringExpression(s.parent, rhs, StringConcatOperator) } func (s *stringInterfaceImpl) LIKE(pattern StringExpression) BoolExpression { @@ -70,12 +74,8 @@ func (s *stringInterfaceImpl) NOT_LIKE(pattern StringExpression) BoolExpression return newBinaryBoolOperator(s.parent, pattern, "NOT LIKE") } -func (s *stringInterfaceImpl) SIMILAR_TO(pattern StringExpression) BoolExpression { - return newBinaryBoolOperator(s.parent, pattern, "SIMILAR TO") -} - -func (s *stringInterfaceImpl) NOT_SIMILAR_TO(pattern StringExpression) BoolExpression { - return newBinaryBoolOperator(s.parent, pattern, "NOT SIMILAR TO") +func (s *stringInterfaceImpl) REGEXP_LIKE(pattern StringExpression, matchType ...string) BoolExpression { + return REGEXP_LIKE(s.parent, pattern, matchType...) } //---------------------------------------------------// diff --git a/internal/jet/string_expression_test.go b/internal/jet/string_expression_test.go index a1209ce..cd53e51 100644 --- a/internal/jet/string_expression_test.go +++ b/internal/jet/string_expression_test.go @@ -66,14 +66,9 @@ func TestStringNOT_LIKE(t *testing.T) { assertClauseSerialize(t, table3StrCol.NOT_LIKE(String("JOHN")), "(table3.col2 NOT LIKE $1)", "JOHN") } -func TestStringSIMILAR_TO(t *testing.T) { - assertClauseSerialize(t, table3StrCol.SIMILAR_TO(table2ColStr), "(table3.col2 SIMILAR TO table2.col_str)") - assertClauseSerialize(t, table3StrCol.SIMILAR_TO(String("JOHN")), "(table3.col2 SIMILAR TO $1)", "JOHN") -} - -func TestStringNOT_SIMILAR_TO(t *testing.T) { - assertClauseSerialize(t, table3StrCol.NOT_SIMILAR_TO(table2ColStr), "(table3.col2 NOT SIMILAR TO table2.col_str)") - assertClauseSerialize(t, table3StrCol.NOT_SIMILAR_TO(String("JOHN")), "(table3.col2 NOT SIMILAR TO $1)", "JOHN") +func TestStringREGEXP_LIKE(t *testing.T) { + assertClauseSerialize(t, table3StrCol.REGEXP_LIKE(table2ColStr), "REGEXP_LIKE(table3.col2, table2.col_str)") + assertClauseSerialize(t, table3StrCol.REGEXP_LIKE(String("JOHN"), "c"), "REGEXP_LIKE(table3.col2, $1, 'c')", "JOHN") } func TestStringExp(t *testing.T) { diff --git a/internal/jet/testutils.go b/internal/jet/testutils.go index fc1fec0..5d5dff7 100644 --- a/internal/jet/testutils.go +++ b/internal/jet/testutils.go @@ -79,12 +79,8 @@ func assertClauseSerialize(t *testing.T, clause Clause, query string, args ...in assert.NilError(t, err) - assert.DeepEqual(t, out.DebugSQL(), query) - - if len(args) > 0 { - assert.DeepEqual(t, out.Args, args) - } - + assert.DeepEqual(t, out.Buff.String(), query) + assert.DeepEqual(t, out.Args, args) } func assertClauseSerializeErr(t *testing.T, clause Clause, errString string) { @@ -102,22 +98,19 @@ func assertProjectionSerialize(t *testing.T, projection Projection, query string assert.NilError(t, err) - assert.DeepEqual(t, out.DebugSQL(), query) - - if len(args) > 0 { - assert.DeepEqual(t, out.Args, args) - } + assert.DeepEqual(t, out.Buff.String(), query) + assert.DeepEqual(t, out.Args, args) } func assertStatement(t *testing.T, query Statement, expectedQuery string, expectedArgs ...interface{}) { - queryStr, err := query.DebugSql() + queryStr, args, err := query.Sql() assert.NilError(t, err) fmt.Println(queryStr) //fmt.Println(queryStr) assert.Equal(t, queryStr, expectedQuery) - //assert.DeepEqual(t, args, expectedArgs) + assert.DeepEqual(t, args, expectedArgs) } func assertStatementErr(t *testing.T, stmt Statement, errorStr string) { diff --git a/internal/jet/update_statement.go b/internal/jet/update_statement.go index 938c6ab..b4e20b4 100644 --- a/internal/jet/update_statement.go +++ b/internal/jet/update_statement.go @@ -89,7 +89,7 @@ func (u *updateStatementImpl) Sql(dialect ...Dialect) (query string, args []inte out.newLine() out.WriteString("SET") - if err = out.Dialect.UpdateAssigment()(u.columns, u.values, out); err != nil { + if err = out.Dialect.SetClause()(u.columns, u.values, out); err != nil { return } diff --git a/internal/jet/update_statement_test.go b/internal/jet/update_statement_test.go index 2647a94..37ddde7 100644 --- a/internal/jet/update_statement_test.go +++ b/internal/jet/update_statement_test.go @@ -20,7 +20,7 @@ WHERE table1.col_int >= $2; func TestUpdateWithValues(t *testing.T) { expectedSQL := ` UPDATE db.table1 -SET (col_int, col_float) = ($1, $2) +SET col_int = $1, col_float = $2 WHERE table1.col_int >= $3; ` stmt := table1.UPDATE(table1ColInt, table1ColFloat). @@ -51,26 +51,7 @@ RETURNING table1.col1 AS "table1.col1"; assertStatement(t, stmt, expectedSQL, int64(2)) } -func TestUpdateColumnsWithSelect(t *testing.T) { - expectedSQL := ` -UPDATE db.table1 -SET (col1, col_float) = ( - SELECT table1.col_float AS "table1.col_float", - table2.col3 AS "table2.col3" - FROM db.table1 -) -WHERE table1.col1 = $1 -RETURNING table1.col1 AS "table1.col1"; -` - stmt := table1.UPDATE(table1Col1, table1ColFloat). - SET(table1.SELECT(table1ColFloat, table2Col3)). - WHERE(table1Col1.EQ(Int(2))). - RETURNING(table1Col1) - - assertStatement(t, stmt, expectedSQL, int64(2)) -} - func TestInvalidInputs(t *testing.T) { - assertStatementErr(t, table1.UPDATE(table1ColInt).SET(1, 2), "jet: WHERE clause not set") - assertStatementErr(t, table1.UPDATE(nil).SET(1, 2), "jet: nil column in columns list") + assertStatementErr(t, table1.UPDATE(table1ColInt).SET(1), "jet: WHERE clause not set") + assertStatementErr(t, table1.UPDATE(nil).SET(1), "jet: nil column in columns list") } diff --git a/internal/testutils/test_utils.go b/internal/testutils/test_utils.go index 2998e6d..d380173 100644 --- a/internal/testutils/test_utils.go +++ b/internal/testutils/test_utils.go @@ -84,11 +84,14 @@ func AssertDebugStatementSql(t *testing.T, query jet.Statement, expectedQuery st _, args, err := query.Sql() assert.NilError(t, err) //assert.Equal(t, queryStr, expectedQuery) - assert.DeepEqual(t, args, expectedArgs) + if len(expectedArgs) > 0 { + assert.DeepEqual(t, args, expectedArgs) + } debuqSql, err := query.DebugSql() assert.NilError(t, err) + assert.Equal(t, debuqSql, expectedQuery) } diff --git a/mysql/bool_expression_test.go b/mysql/bool_expression_test.go deleted file mode 100644 index 60ef3e9..0000000 --- a/mysql/bool_expression_test.go +++ /dev/null @@ -1,89 +0,0 @@ -package mysql - -import ( - "testing" -) - -func TestBoolExpressionEQ(t *testing.T) { - assertClauseSerializeErr(t, table1ColBool.EQ(nil), "jet: nil rhs") - - assertClauseSerialize(t, table1ColBool.EQ(table2ColBool), "(table1.col_bool = table2.col_bool)") - assertClauseSerialize(t, table1ColBool.EQ(Bool(true)), "(table1.col_bool = ?)", true) -} - -func TestBoolExpressionNOT_EQ(t *testing.T) { - assertClauseSerialize(t, table1ColBool.NOT_EQ(table2ColBool), "(table1.col_bool != table2.col_bool)") - assertClauseSerialize(t, table1ColBool.NOT_EQ(Bool(true)), "(table1.col_bool != ?)", true) -} - -func TestBoolExpressionIS_DISTINCT_FROM(t *testing.T) { - assertClauseSerialize(t, table1ColBool.IS_DISTINCT_FROM(table2ColBool), "(NOT table1.col_bool <=> table2.col_bool)") - assertClauseSerialize(t, table1ColBool.IS_DISTINCT_FROM(Bool(false)), "(NOT table1.col_bool <=> ?)", false) -} - -func TestBoolExpressionIS_NOT_DISTINCT_FROM(t *testing.T) { - assertClauseSerialize(t, table1ColBool.IS_NOT_DISTINCT_FROM(table2ColBool), "(table1.col_bool <=> table2.col_bool)") - assertClauseSerialize(t, table1ColBool.IS_NOT_DISTINCT_FROM(Bool(false)), "(table1.col_bool <=> ?)", false) - -} - -func TestBoolExpressionIS_TRUE(t *testing.T) { - assertClauseSerialize(t, table1ColBool.IS_TRUE(), "table1.col_bool IS TRUE") - assertClauseSerialize(t, (Int(2).EQ(table1ColInt)).IS_TRUE(), - `(? = table1.col_int) IS TRUE`, int64(2)) - assertClauseSerialize(t, (Int(2).EQ(table1ColInt)).IS_TRUE().AND(Int(4).EQ(table2ColInt)), - `((? = table1.col_int) IS TRUE AND (? = table2.col_int))`, int64(2), int64(4)) -} - -func TestBoolExpressionIS_NOT_TRUE(t *testing.T) { - assertClauseSerialize(t, table1ColBool.IS_NOT_TRUE(), "table1.col_bool IS NOT TRUE") -} - -func TestBoolExpressionIS_FALSE(t *testing.T) { - assertClauseSerialize(t, table1ColBool.IS_FALSE(), "table1.col_bool IS FALSE") -} - -func TestBoolExpressionIS_NOT_FALSE(t *testing.T) { - assertClauseSerialize(t, table1ColBool.IS_NOT_FALSE(), "table1.col_bool IS NOT FALSE") -} - -func TestBoolExpressionIS_UNKNOWN(t *testing.T) { - assertClauseSerialize(t, table1ColBool.IS_UNKNOWN(), "table1.col_bool IS UNKNOWN") -} - -func TestBoolExpressionIS_NOT_UNKNOWN(t *testing.T) { - assertClauseSerialize(t, table1ColBool.IS_NOT_UNKNOWN(), "table1.col_bool IS NOT UNKNOWN") -} - -func TestBinaryBoolExpressionAsProjection(t *testing.T) { - boolExpression := Int(2).EQ(Int(3)) - - assertProjectionSerialize(t, boolExpression, "? = ?", int64(2), int64(3)) - assertProjectionSerialize(t, boolExpression.AS("alias_eq_expression"), - `(? = ?) AS "alias_eq_expression"`, int64(2), int64(3)) -} - -func TestBoolLiteral(t *testing.T) { - assertClauseSerialize(t, Bool(true), "?", true) - assertClauseSerialize(t, Bool(false), "?", false) -} - -// -//func TestExists(t *testing.T) { -// -// assertClauseSerialize(t, EXISTS( -// table2. -// SELECT(Int(1)). -// WHERE(table1Col1.EQ(table2Col3)), -// ), -// `(EXISTS ( -// SELECT ? -// FROM db.table2 -// WHERE table1.col1 = table2.col3 -//))`, int64(1)) -//} -// -//func TestBoolExp(t *testing.T) { -// assertClauseSerialize(t, BoolExp(String("true")), "?", "true") -// assertClauseSerialize(t, BoolExp(String("true")).IS_TRUE(), "? IS TRUE", "true") -//} diff --git a/mysql/column.go b/mysql/column.go deleted file mode 100644 index 3727b1b..0000000 --- a/mysql/column.go +++ /dev/null @@ -1,9 +0,0 @@ -package mysql - -import "github.com/go-jet/jet/internal/jet" - -type Column jet.Column - -type IColumnList jet.IColumnList - -var ColumnList = jet.ColumnList diff --git a/mysql/columns.go b/mysql/columns.go new file mode 100644 index 0000000..819cee4 --- /dev/null +++ b/mysql/columns.go @@ -0,0 +1,41 @@ +package mysql + +import "github.com/go-jet/jet/internal/jet" + +type Column jet.Column + +type IColumnList jet.IColumnList + +var ColumnList = jet.ColumnList + +type ColumnBool jet.ColumnBool + +var BoolColumn = jet.BoolColumn + +type ColumnString jet.ColumnString + +var StringColumn = jet.StringColumn + +type ColumnInteger jet.ColumnInteger + +var IntegerColumn = jet.IntegerColumn + +type ColumnFloat jet.ColumnFloat + +var FloatColumn = jet.FloatColumn + +type ColumnTime jet.ColumnTime + +var TimeColumn = jet.TimeColumn + +type ColumnDate jet.ColumnDate + +var DateColumn = jet.DateColumn + +type ColumnDateTime jet.ColumnTimestamp + +var DateTimeColumn = jet.TimestampColumn + +type ColumnTimestamp jet.ColumnTimestamp + +var TimestampColumn = jet.TimestampColumn diff --git a/mysql/dialect.go b/mysql/dialect.go index c653274..a0b3d50 100644 --- a/mysql/dialect.go +++ b/mysql/dialect.go @@ -14,6 +14,7 @@ func NewDialect() jet.Dialect { serializeOverrides["IS NOT DISTINCT FROM"] = mysql_IS_NOT_DISTINCT_FROM serializeOverrides["/"] = mysql_DIVISION serializeOverrides["#"] = mysql_BIT_XOR + serializeOverrides[jet.StringConcatOperator] = mysql_CONCAT_operator mySQLDialectParams := jet.DialectParams{ Name: "MySQL", @@ -24,36 +25,12 @@ func NewDialect() jet.Dialect { ArgumentPlaceholder: func(int) string { return "?" }, - UpdateAssigment: mysqlUpdateAssigment, SupportsReturning: false, } return jet.NewDialect(mySQLDialectParams) } -func mysqlUpdateAssigment(columns []jet.IColumn, values []jet.Clause, out *jet.SqlBuilder) (err error) { - - if len(columns) != len(values) { - return errors.New("jet: mismatch in numers of columns and values") - } - - for i, column := range columns { - if i > 0 { - out.WriteString(", ") - } - - out.WriteString(column.Name()) - - out.WriteString(" = ") - - if err = jet.Serialize(values[i], jet.UpdateStatementType, out); err != nil { - return err - } - } - - return nil -} - func mysql_BIT_XOR(expressions ...jet.Expression) jet.SerializeFunc { return func(statement jet.StatementType, out *jet.SqlBuilder, options ...jet.SerializeOption) error { if len(expressions) != 2 { @@ -76,6 +53,30 @@ func mysql_BIT_XOR(expressions ...jet.Expression) jet.SerializeFunc { } } +func mysql_CONCAT_operator(expressions ...jet.Expression) jet.SerializeFunc { + return func(statement jet.StatementType, out *jet.SqlBuilder, options ...jet.SerializeOption) error { + if len(expressions) != 2 { + return errors.New("jet: invalid number of expressions for operator") + } + + out.WriteString("CONCAT(") + + if err := jet.Serialize(expressions[0], statement, out, options...); err != nil { + return err + } + + out.WriteString(", ") + + if err := jet.Serialize(expressions[1], statement, out, options...); err != nil { + return err + } + + out.WriteString(")") + + return nil + } +} + func mysql_DIVISION(expressions ...jet.Expression) jet.SerializeFunc { return func(statement jet.StatementType, out *jet.SqlBuilder, options ...jet.SerializeOption) error { if len(expressions) != 2 { @@ -126,7 +127,7 @@ func mysql_IS_NOT_DISTINCT_FROM(expressions ...jet.Expression) jet.SerializeFunc func mysql_IS_DISTINCT_FROM(expressions ...jet.Expression) jet.SerializeFunc { return func(statement jet.StatementType, out *jet.SqlBuilder, options ...jet.SerializeOption) error { - out.WriteString("NOT") + out.WriteString("NOT(") err := mysql_IS_NOT_DISTINCT_FROM(expressions...)(statement, out, options...) @@ -134,6 +135,8 @@ func mysql_IS_DISTINCT_FROM(expressions ...jet.Expression) jet.SerializeFunc { return err } + out.WriteString(")") + return nil } } diff --git a/mysql/dialect_test.go b/mysql/dialect_test.go new file mode 100644 index 0000000..e334219 --- /dev/null +++ b/mysql/dialect_test.go @@ -0,0 +1,35 @@ +package mysql + +import ( + "testing" +) + +func TestBoolExpressionIS_DISTINCT_FROM(t *testing.T) { + assertClauseSerialize(t, table1ColBool.IS_DISTINCT_FROM(table2ColBool), "(NOT(table1.col_bool <=> table2.col_bool))") + assertClauseSerialize(t, table1ColBool.IS_DISTINCT_FROM(Bool(false)), "(NOT(table1.col_bool <=> ?))", false) +} + +func TestBoolExpressionIS_NOT_DISTINCT_FROM(t *testing.T) { + assertClauseSerialize(t, table1ColBool.IS_NOT_DISTINCT_FROM(table2ColBool), "(table1.col_bool <=> table2.col_bool)") + assertClauseSerialize(t, table1ColBool.IS_NOT_DISTINCT_FROM(Bool(false)), "(table1.col_bool <=> ?)", false) +} + +func TestBoolLiteral(t *testing.T) { + assertClauseSerialize(t, Bool(true), "?", true) + assertClauseSerialize(t, Bool(false), "?", false) +} + +func TestIntegerExpressionDIV(t *testing.T) { + assertClauseSerialize(t, table1ColInt.DIV(table2ColInt), "(table1.col_int DIV table2.col_int)") + assertClauseSerialize(t, table1ColInt.DIV(Int(11)), "(table1.col_int DIV ?)", int64(11)) +} + +func TestIntExpressionPOW(t *testing.T) { + assertClauseSerialize(t, table1ColInt.POW(table2ColInt), "POW(table1.col_int, table2.col_int)") + assertClauseSerialize(t, table1ColInt.POW(Int(11)), "POW(table1.col_int, ?)", int64(11)) +} + +func TestIntExpressionBIT_XOR(t *testing.T) { + assertClauseSerialize(t, table1ColInt.BIT_XOR(table2ColInt), "(table1.col_int ^ table2.col_int)") + assertClauseSerialize(t, table1ColInt.BIT_XOR(Int(11)), "(table1.col_int ^ ?)", int64(11)) +} diff --git a/mysql/expressions.go b/mysql/expressions.go index 7fe386b..c5bf182 100644 --- a/mysql/expressions.go +++ b/mysql/expressions.go @@ -12,10 +12,21 @@ type IntegerExpression jet.IntegerExpression type FloatExpression jet.FloatExpression +type TimeExpression jet.TimeExpression + type DateExpression jet.DateExpression type DateTimeExpression jet.TimestampExpression type TimestampExpression jet.TimestampExpression -type TimeExpression jet.TimeExpression +var BoolExp = jet.BoolExp +var StringExp = jet.StringExp +var IntExp = jet.IntExp +var FloatExp = jet.FloatExp +var TimeExp = jet.TimeExp +var DateExp = jet.DateExp +var DateTimeExp = jet.TimestampExp +var TimestampExp = jet.TimestampExp + +var RAW = jet.RAW diff --git a/mysql/functions.go b/mysql/functions.go new file mode 100644 index 0000000..2f73e2c --- /dev/null +++ b/mysql/functions.go @@ -0,0 +1,122 @@ +package mysql + +import "github.com/go-jet/jet/internal/jet" + +// ------------------ Mathematical functions ---------------// + +var POW = jet.POW +var LN = jet.LN +var LOG = jet.LOG + +var ABSf = jet.ABSf +var ABSi = jet.ABSi +var POWER = jet.POWER +var SQRT = jet.SQRT + +func CBRT(number jet.NumericExpression) jet.FloatExpression { + return POWER(number, Float(1.0).DIV(Float(3.0))) +} + +var CEIL = jet.CEIL +var FLOOR = jet.FLOOR +var ROUND = jet.ROUND +var SIGN = jet.SIGN +var TRUNC = TRUNCATE + +var TRUNCATE = func(floatExpression jet.FloatExpression, precision jet.IntegerExpression) jet.FloatExpression { + return jet.NewFloatFunc("TRUNCATE", floatExpression, precision) +} + +var MINUSi = jet.MINUSi +var MINUSf = jet.MINUSf +var BIT_NOT = jet.BIT_NOT + +// ----------------- Aggregate functions -------------------// + +var BIT_AND = jet.BIT_AND +var BIT_OR = jet.BIT_OR +var BOOL_AND = jet.BOOL_AND +var BOOL_OR = jet.BOOL_OR +var EVERY = jet.EVERY +var MAXi = jet.MAXi +var MINi = jet.MINi +var SUMi = jet.SUMi + +var SUMf = jet.SUMf +var AVG = jet.AVG +var MAXf = jet.MAXf +var MINf = jet.MINf +var COUNT = jet.COUNT + +//--------------------- String functions ------------------// + +var REGEXP_LIKE = jet.REGEXP_LIKE +var BIT_LENGTH = jet.BIT_LENGTH +var CHAR_LENGTH = jet.CHAR_LENGTH +var OCTET_LENGTH = jet.OCTET_LENGTH +var LOWER = jet.LOWER +var UPPER = jet.UPPER +var BTRIM = jet.BTRIM +var LTRIM = jet.LTRIM +var RTRIM = jet.RTRIM +var CHR = jet.CHR +var CONCAT = jet.CONCAT +var CONCAT_WS = jet.CONCAT_WS +var CONVERT = jet.CONVERT +var CONVERT_FROM = jet.CONVERT_FROM +var CONVERT_TO = jet.CONVERT_TO +var ENCODE = jet.ENCODE +var DECODE = jet.DECODE +var FORMAT = jet.FORMAT +var INITCAP = jet.INITCAP +var LEFT = jet.LEFT +var RIGHT = jet.RIGHT + +func LENGTH(str jet.StringExpression) jet.StringExpression { + return jet.LENGTH(str) +} + +func LPAD(str jet.StringExpression, length jet.IntegerExpression, text jet.StringExpression) jet.StringExpression { + return jet.LPAD(str, length, text) +} +func RPAD(str jet.StringExpression, length jet.IntegerExpression, text jet.StringExpression) jet.StringExpression { + return jet.RPAD(str, length, text) +} + +var MD5 = jet.MD5 +var REPEAT = jet.REPEAT +var REPLACE = jet.REPLACE +var REVERSE = jet.REVERSE +var STRPOS = jet.STRPOS +var SUBSTR = jet.SUBSTR +var TO_ASCII = jet.TO_ASCII +var TO_HEX = jet.TO_HEX + +//----------Data Type Formatting Functions ----------------------// +var TO_CHAR = jet.TO_CHAR +var TO_DATE = jet.TO_DATE +var TO_NUMBER = jet.TO_NUMBER +var TO_TIMESTAMP = jet.TO_TIMESTAMP + +//----------------- Date/Time Functions and Operators ------------// + +var CURRENT_DATE = jet.CURRENT_DATE +var CURRENT_TIME = jet.CURRENT_TIME +var CURRENT_TIMESTAMP = jet.CURRENT_TIMESTAMP +var LOCALTIME = jet.LOCALTIME +var LOCALTIMESTAMP = jet.LOCALTIMESTAMP + +func NOW(fsp ...int) DateTimeExpression { + if len(fsp) > 0 { + return jet.NewTimestampFunc("NOW", Int(int64(fsp[0]), true)) + } + return jet.NewTimestampFunc("NOW") +} + +// --------------- Conditional Expressions Functions -------------// +var COALESCE = jet.COALESCE +var NULLIF = jet.NULLIF +var GREATEST = jet.GREATEST +var LEAST = jet.LEAST +var EXISTS = jet.EXISTS +var CASE = jet.CASE diff --git a/mysql/integer_expression_test.go b/mysql/integer_expression_test.go deleted file mode 100644 index f7902e3..0000000 --- a/mysql/integer_expression_test.go +++ /dev/null @@ -1,107 +0,0 @@ -package mysql - -import ( - "testing" -) - -func TestIntegerExpressionEQ(t *testing.T) { - assertClauseSerialize(t, table1ColInt.EQ(table2ColInt), "(table1.col_int = table2.col_int)") - assertClauseSerialize(t, table1ColInt.EQ(Int(11)), "(table1.col_int = ?)", int64(11)) -} - -func TestIntegerExpressionNOT_EQ(t *testing.T) { - assertClauseSerialize(t, table1ColInt.NOT_EQ(table2ColInt), "(table1.col_int != table2.col_int)") - assertClauseSerialize(t, table1ColInt.NOT_EQ(Int(11)), "(table1.col_int != ?)", int64(11)) -} - -func TestIntegerExpressionGT(t *testing.T) { - assertClauseSerialize(t, table1ColInt.GT(table2ColInt), "(table1.col_int > table2.col_int)") - assertClauseSerialize(t, table1ColInt.GT(Int(11)), "(table1.col_int > ?)", int64(11)) -} - -func TestIntegerExpressionGT_EQ(t *testing.T) { - assertClauseSerialize(t, table1ColInt.GT_EQ(table2ColInt), "(table1.col_int >= table2.col_int)") - assertClauseSerialize(t, table1ColInt.GT_EQ(Int(11)), "(table1.col_int >= ?)", int64(11)) -} - -func TestIntegerExpressionLT(t *testing.T) { - assertClauseSerialize(t, table1ColInt.LT(table2ColInt), "(table1.col_int < table2.col_int)") - assertClauseSerialize(t, table1ColInt.LT(Int(11)), "(table1.col_int < ?)", int64(11)) -} - -func TestIntegerExpressionLT_EQ(t *testing.T) { - assertClauseSerialize(t, table1ColInt.LT_EQ(table2ColInt), "(table1.col_int <= table2.col_int)") - assertClauseSerialize(t, table1ColInt.LT_EQ(Int(11)), "(table1.col_int <= ?)", int64(11)) -} - -func TestIntegerExpressionADD(t *testing.T) { - assertClauseSerialize(t, table1ColInt.ADD(table2ColInt), "(table1.col_int + table2.col_int)") - assertClauseSerialize(t, table1ColInt.ADD(Int(11)), "(table1.col_int + ?)", int64(11)) -} - -func TestIntegerExpressionSUB(t *testing.T) { - assertClauseSerialize(t, table1ColInt.SUB(table2ColInt), "(table1.col_int - table2.col_int)") - assertClauseSerialize(t, table1ColInt.SUB(Int(11)), "(table1.col_int - ?)", int64(11)) -} - -func TestIntegerExpressionMUL(t *testing.T) { - assertClauseSerialize(t, table1ColInt.MUL(table2ColInt), "(table1.col_int * table2.col_int)") - assertClauseSerialize(t, table1ColInt.MUL(Int(11)), "(table1.col_int * ?)", int64(11)) -} - -func TestIntegerExpressionDIV(t *testing.T) { - assertClauseSerialize(t, table1ColInt.DIV(table2ColInt), "(table1.col_int DIV table2.col_int)") - assertClauseSerialize(t, table1ColInt.DIV(Int(11)), "(table1.col_int DIV ?)", int64(11)) -} - -func TestIntExpressionMOD(t *testing.T) { - assertClauseSerialize(t, table1ColInt.MOD(table2ColInt), "(table1.col_int % table2.col_int)") - assertClauseSerialize(t, table1ColInt.MOD(Int(11)), "(table1.col_int % ?)", int64(11)) -} - -func TestIntExpressionPOW(t *testing.T) { - assertClauseSerialize(t, table1ColInt.POW(table2ColInt), "POW(table1.col_int, table2.col_int)") - assertClauseSerialize(t, table1ColInt.POW(Int(11)), "POW(table1.col_int, ?)", int64(11)) -} - -func TestIntExpressionBIT_NOT(t *testing.T) { - assertClauseSerialize(t, BIT_NOT(table2ColInt), "(~ table2.col_int)") - assertClauseSerialize(t, BIT_NOT(Int(11)), "(~ ?)", int64(11)) -} - -func TestIntExpressionBIT_AND(t *testing.T) { - assertClauseSerialize(t, table1ColInt.BIT_AND(table2ColInt), "(table1.col_int & table2.col_int)") - assertClauseSerialize(t, table1ColInt.BIT_AND(Int(11)), "(table1.col_int & ?)", int64(11)) -} - -func TestIntExpressionBIT_OR(t *testing.T) { - assertClauseSerialize(t, table1ColInt.BIT_OR(table2ColInt), "(table1.col_int | table2.col_int)") - assertClauseSerialize(t, table1ColInt.BIT_OR(Int(11)), "(table1.col_int | ?)", int64(11)) -} - -func TestIntExpressionBIT_XOR(t *testing.T) { - assertClauseSerialize(t, table1ColInt.BIT_XOR(table2ColInt), "(table1.col_int ^ table2.col_int)") - assertClauseSerialize(t, table1ColInt.BIT_XOR(Int(11)), "(table1.col_int ^ ?)", int64(11)) -} - -func TestIntExpressionBIT_SHIFT_LEFT(t *testing.T) { - assertClauseSerialize(t, table1ColInt.BIT_SHIFT_LEFT(table2ColInt), "(table1.col_int << table2.col_int)") - assertClauseSerialize(t, table1ColInt.BIT_SHIFT_LEFT(Int(11)), "(table1.col_int << ?)", int64(11)) -} - -func TestIntExpressionBIT_SHIFT_RIGHT(t *testing.T) { - assertClauseSerialize(t, table1ColInt.BIT_SHIFT_RIGHT(table2ColInt), "(table1.col_int >> table2.col_int)") - assertClauseSerialize(t, table1ColInt.BIT_SHIFT_RIGHT(Int(11)), "(table1.col_int >> ?)", int64(11)) -} - -// -//func TestIntExpressionIntExp(t *testing.T) { -// assertClauseSerialize(t, IntExp(table1ColFloat), "table1.col_float") -// assertClauseSerialize(t, IntExp(table1ColFloat.ADD(table2ColFloat)).ADD(Int(11)), -// "((table1.col_float + table2.col_float) + ?)", int64(11)) -//} - -func TestIntExpression_MINUSi(t *testing.T) { - assertClauseSerialize(t, MINUSi(table2ColInt), "(- table2.col_int)") - assertClauseSerialize(t, MINUSi(Int(3)), "(- ?)", int64(3)) -} diff --git a/mysql/literal.go b/mysql/literal.go new file mode 100644 index 0000000..a064303 --- /dev/null +++ b/mysql/literal.go @@ -0,0 +1,13 @@ +package mysql + +import "github.com/go-jet/jet/internal/jet" + +var Bool = jet.Bool +var Int = jet.Int +var Float = jet.Float +var String = jet.String + +var Time = jet.Time +var Date = jet.Date +var DateTime = jet.Timestamp +var Timestamp = jet.Timestamp diff --git a/mysql/statements.go b/mysql/statements.go new file mode 100644 index 0000000..458d029 --- /dev/null +++ b/mysql/statements.go @@ -0,0 +1,27 @@ +package mysql + +import "github.com/go-jet/jet/internal/jet" + +// ----------------- FUNCTIONS ----------------------// + +var SELECT = jet.SELECT + +type SelectLock jet.SelectLock + +var ( + UPDATE = jet.NewSelectLock("UPDATE") + SHARE = jet.NewSelectLock("SHARE") +) + +var UNION = jet.UNION +var UNION_ALL = jet.UNION_ALL +var INTERSECT = jet.INTERSECT +var INTERSECT_ALL = jet.INTERSECT_ALL +var EXCEPT = jet.EXCEPT +var EXCEPT_ALL = jet.EXCEPT_ALL + +//-----------------literals----------------------// + +var STAR = jet.STAR +var NULL = jet.NULL +var DEFAULT = jet.DEFAULT diff --git a/mysql/types.go b/mysql/types.go deleted file mode 100644 index b3652de..0000000 --- a/mysql/types.go +++ /dev/null @@ -1,86 +0,0 @@ -package mysql - -import "github.com/go-jet/jet/internal/jet" - -type ColumnBool jet.ColumnBool - -var BoolColumn = jet.BoolColumn -var Bool = jet.Bool - -type ColumnString jet.ColumnString - -var StringColumn = jet.StringColumn -var String = jet.String - -type ColumnInteger jet.ColumnInteger - -var IntegerColumn = jet.IntegerColumn -var Int = jet.Int - -type ColumnFloat jet.ColumnFloat - -var FloatColumn = jet.FloatColumn -var Float = jet.Float - -type ColumnDate jet.ColumnDate - -var DateColumn = jet.DateColumn -var Date = jet.Date - -type ColumnDateTime jet.ColumnTimestamp - -var DateTimeColumn = jet.TimestampColumn -var DateTime = jet.Timestamp - -type ColumnTimestamp jet.ColumnTimestamp - -var TimestampColumn = jet.TimestampColumn -var Timestamp = jet.Timestamp - -// ----------------- FUNCTIONS ----------------------// - -var ABSf = jet.ABSf -var ABSi = jet.ABSi -var POWER = jet.POWER -var SQRT = jet.SQRT - -func CBRT(number jet.NumericExpression) jet.FloatExpression { - return POWER(number, Float(1.0).DIV(Float(3.0))) -} - -var CEIL = jet.CEIL -var FLOOR = jet.FLOOR -var ROUND = jet.ROUND -var SIGN = jet.SIGN -var TRUNC = TRUNCATE - -var TRUNCATE = func(floatExpression jet.FloatExpression, precision jet.IntegerExpression) jet.FloatExpression { - return jet.NewFloatFunc("TRUNCATE", floatExpression, precision) -} - -var MINUSi = jet.MINUSi -var MINUSf = jet.MINUSf -var BIT_NOT = jet.BIT_NOT - -var SUMf = jet.SUMf -var AVG = jet.AVG -var MAXf = jet.MAXf -var MINf = jet.MINf -var COUNT = jet.COUNT - -var SELECT = jet.SELECT - -type SelectLock jet.SelectLock - -var ( - UPDATE = jet.NewSelectLock("UPDATE") - SHARE = jet.NewSelectLock("SHARE") -) - -var UNION = jet.UNION - -//-----------------literals----------------------// - -var STAR = jet.STAR -var NULL = jet.NULL -var DEFAULT = jet.DEFAULT diff --git a/mysql/testutils.go b/mysql/utils_test.go similarity index 100% rename from mysql/testutils.go rename to mysql/utils_test.go diff --git a/postgres/postgres_cast.go b/postgres/cast.go similarity index 81% rename from postgres/postgres_cast.go rename to postgres/cast.go index 4f23530..1469267 100644 --- a/postgres/postgres_cast.go +++ b/postgres/cast.go @@ -9,6 +9,7 @@ type cast interface { jet.Cast // Cast expression AS bool type AS_BOOL() BoolExpression + // Cast expression AS smallint type AS_SMALLINT() IntegerExpression // Cast expression AS integer type @@ -16,7 +17,7 @@ type cast interface { // Cast expression AS bigint type AS_BIGINT() IntegerExpression // Cast expression AS numeric type, using precision and optionally scale - AS_NUMERIC(precision int, scale ...int) FloatExpression + AS_NUMERIC(precisionAndScale ...int) FloatExpression // Cast expression AS real type AS_REAL() FloatExpression @@ -25,6 +26,8 @@ type cast interface { // Cast expression AS text type AS_TEXT() StringExpression + AS_BYTEA() StringExpression + // Cast expression AS time with time timezone type AS_TIMEZ() TimezExpression // Cast expression AS timestamp type @@ -64,16 +67,17 @@ func (b *castImpl) AS_BIGINT() IntegerExpression { } // Cast expression AS numeric type, using precision and optionally scale -func (b *castImpl) AS_NUMERIC(precision int, scale ...int) FloatExpression { - var castType string +func (b *castImpl) AS_NUMERIC(precisionAndScale ...int) FloatExpression { + var castArgs string - if len(scale) > 0 { - castType = fmt.Sprintf("numeric(%d, %d)", precision, scale[0]) - } else { - castType = fmt.Sprintf("numeric(%d)", precision) + var argLen = len(precisionAndScale) + if argLen >= 2 { + castArgs = fmt.Sprintf("(%d, %d)", precisionAndScale[0], precisionAndScale[1]) + } else if argLen == 1 { + castArgs = fmt.Sprintf("(%d)", precisionAndScale[0]) } - return jet.FloatExp(b.AS(castType)) + return jet.FloatExp(b.AS("numeric" + castArgs)) } // Cast expression AS real type @@ -91,6 +95,11 @@ func (b *castImpl) AS_TEXT() StringExpression { return jet.StringExp(b.AS("text")) } +// Cast expression AS text type +func (b *castImpl) AS_BYTEA() StringExpression { + return jet.StringExp(b.AS("bytea")) +} + // Cast expression AS date type func (b *castImpl) AS_TIME() jet.TimeExpression { return TimeExp(b.AS("time without time zone")) diff --git a/postgres/postgres_cast_test.go b/postgres/cast_test.go similarity index 100% rename from postgres/postgres_cast_test.go rename to postgres/cast_test.go diff --git a/postgres/postgres_types.go b/postgres/columns.go similarity index 50% rename from postgres/postgres_types.go rename to postgres/columns.go index b9ba70b..2ccc252 100644 --- a/postgres/postgres_types.go +++ b/postgres/columns.go @@ -2,62 +2,44 @@ package postgres import "github.com/go-jet/jet/internal/jet" +type Column jet.Column + +type IColumnList jet.IColumnList + +var ColumnList = jet.ColumnList + type ColumnBool jet.ColumnBool -type BoolExpression jet.BoolExpression var BoolColumn = jet.BoolColumn type ColumnString jet.ColumnString -type StringExpression jet.StringExpression var StringColumn = jet.StringColumn type ColumnInteger jet.ColumnInteger -type IntegerExpression jet.IntegerExpression var IntegerColumn = jet.IntegerColumn type ColumnFloat jet.ColumnFloat -type FloatExpression jet.FloatExpression var FloatColumn = jet.FloatColumn -var FloatExp = jet.FloatExp - type ColumnDate jet.ColumnDate -type DateExpression jet.DateExpression var DateColumn = jet.DateColumn -type ColumnDateTime jet.ColumnTimestamp -type DateTimeExpression jet.TimestampExpression - -var DateTimeColumn = jet.TimestampColumn - -type TimeExpression jet.TimeExpression type ColumnTime jet.ColumnTime var TimeColumn = jet.TimeColumn -var TimeExp = jet.TimeExp - -type TimezExpression jet.TimezExpression type ColumnTimez jet.ColumnTimez var TimezColumn = jet.TimezColumn type ColumnTimestamp jet.ColumnTimestamp -type TimestampExpression jet.TimestampExpression var TimestampColumn = jet.TimestampColumn -var TimestampExp = jet.TimestampExp - -type TimestampzExpression jet.TimestampzExpression type ColumnTimestampz jet.ColumnTimestampz var TimestampzColumn = jet.TimestampzColumn - -type SelectTable jet.SelectTable - -// ---------------- statements -----------------// diff --git a/postgres/dialect.go b/postgres/dialect.go index 0c7b217..f7e1ec3 100644 --- a/postgres/dialect.go +++ b/postgres/dialect.go @@ -1,24 +1,30 @@ package postgres import ( + "errors" "github.com/go-jet/jet/internal/jet" "strconv" + "strings" ) var Dialect = NewDialect() func NewDialect() jet.Dialect { + serializeOverrides := map[string]jet.SerializeOverride{} + serializeOverrides["REGEXP_LIKE"] = postgres_REGEXP_LIKE_function + dialectParams := jet.DialectParams{ Name: "PostgreSQL", PackageName: "postgres", CastOverride: castFunc, + SerializeOverrides: serializeOverrides, AliasQuoteChar: '"', IdentifierQuoteChar: '"', ArgumentPlaceholder: func(ord int) string { return "$" + strconv.Itoa(ord) }, - UpdateAssigment: postgresUpdateAssigment, + SetClause: postgresSetClause, SupportsReturning: true, } @@ -35,7 +41,7 @@ func castFunc(expression jet.Expression, castType string) jet.SerializeFunc { } } -func postgresUpdateAssigment(columns []jet.IColumn, values []jet.Clause, out *jet.SqlBuilder) (err error) { +func postgresSetClause(columns []jet.IColumn, values []jet.Clause, out *jet.SqlBuilder) (err error) { if len(columns) > 1 { out.WriteString("(") } @@ -68,3 +74,37 @@ func postgresUpdateAssigment(columns []jet.IColumn, values []jet.Clause, out *je return } + +func postgres_REGEXP_LIKE_function(expressions ...jet.Expression) jet.SerializeFunc { + return func(statement jet.StatementType, out *jet.SqlBuilder, options ...jet.SerializeOption) error { + if len(expressions) < 2 { + return errors.New("jet: invalid number of expressions for operator") + } + + if err := jet.Serialize(expressions[0], statement, out, options...); err != nil { + return err + } + + caseSensitive := false + + if len(expressions) >= 3 { + if stringLiteral, ok := expressions[2].(jet.LiteralExpression); ok { + matchType := stringLiteral.Value().(string) + + caseSensitive = !strings.Contains(matchType, "i") + } + } + + if caseSensitive { + out.WriteString("~") + } else { + out.WriteString("~*") + } + + if err := jet.Serialize(expressions[1], statement, out, options...); err != nil { + return err + } + + return nil + } +} diff --git a/postgres/dialect_test.go b/postgres/dialect_test.go new file mode 100644 index 0000000..16c694f --- /dev/null +++ b/postgres/dialect_test.go @@ -0,0 +1,15 @@ +package postgres + +import "testing" + +func TestString_REGEXP_LIKE_operator(t *testing.T) { + assertClauseSerialize(t, table3StrCol.REGEXP_LIKE(table2ColStr), "table3.col2 ~* table2.col_str") + assertClauseSerialize(t, table3StrCol.REGEXP_LIKE(String("JOHN"), "c"), "table3.col2 ~ $1", "JOHN") + assertClauseSerialize(t, table3StrCol.REGEXP_LIKE(String("JOHN"), "i"), "table3.col2 ~* $1", "JOHN") +} + +func TestString_REGEXP_LIKE_function(t *testing.T) { + assertClauseSerialize(t, REGEXP_LIKE(table3StrCol, table2ColStr), "table3.col2 ~* table2.col_str") + assertClauseSerialize(t, REGEXP_LIKE(table3StrCol, String("JOHN"), "c"), "table3.col2 ~ $1", "JOHN") + assertClauseSerialize(t, REGEXP_LIKE(table3StrCol, String("JOHN"), "i"), "table3.col2 ~* $1", "JOHN") +} diff --git a/postgres/expressions.go b/postgres/expressions.go index 1b8faa9..46deb88 100644 --- a/postgres/expressions.go +++ b/postgres/expressions.go @@ -3,3 +3,34 @@ package postgres import "github.com/go-jet/jet/internal/jet" type Expression jet.Expression + +type BoolExpression jet.BoolExpression + +type StringExpression jet.StringExpression + +type IntegerExpression jet.IntegerExpression + +type FloatExpression jet.FloatExpression + +type TimeExpression jet.TimeExpression + +type TimezExpression jet.TimezExpression + +type DateExpression jet.DateExpression + +type TimestampExpression jet.TimestampExpression + +type TimestampzExpression jet.TimestampzExpression + +var BoolExp = jet.BoolExp +var IntExp = jet.IntExp +var FloatExp = jet.FloatExp +var TimeExp = jet.TimeExp +var TimezExp = jet.TimezExp +var DateExp = jet.DateExp +var TimestampExp = jet.TimestampExp +var TimestampzExp = jet.TimestampzExp + +var RAW = jet.RAW + +var NewEnumValue = jet.NewEnumValue diff --git a/postgres/postgres_functions.go b/postgres/functions.go similarity index 67% rename from postgres/postgres_functions.go rename to postgres/functions.go index 750a307..2865f06 100644 --- a/postgres/postgres_functions.go +++ b/postgres/functions.go @@ -38,6 +38,7 @@ var SUMi = jet.SUMi //--------------------- String functions ------------------// +var REGEXP_LIKE = jet.REGEXP_LIKE var BIT_LENGTH = jet.BIT_LENGTH var CHAR_LENGTH = jet.CHAR_LENGTH var OCTET_LENGTH = jet.OCTET_LENGTH @@ -47,11 +48,25 @@ var BTRIM = jet.BTRIM var LTRIM = jet.LTRIM var RTRIM = jet.RTRIM var CHR = jet.CHR + +var CONCAT = func(expressions ...Expression) StringExpression { + return jet.CONCAT(explicitCasts(expressions...)...) +} + +func CONCAT_WS(expressions ...Expression) StringExpression { + return jet.CONCAT_WS(explicitCasts(expressions...)...) +} + var CONVERT = jet.CONVERT var CONVERT_FROM = jet.CONVERT_FROM var CONVERT_TO = jet.CONVERT_TO var ENCODE = jet.ENCODE var DECODE = jet.DECODE + +func FORMAT(formatStr StringExpression, formatArgs ...Expression) StringExpression { + return jet.FORMAT(formatStr, explicitCasts(formatArgs...)...) +} + var INITCAP = jet.INITCAP var LEFT = jet.LEFT var RIGHT = jet.RIGHT @@ -91,3 +106,32 @@ var GREATEST = jet.GREATEST var LEAST = jet.LEAST var EXISTS = jet.EXISTS var CASE = jet.CASE + +func explicitCasts(expressions ...Expression) []jet.Expression { + ret := []jet.Expression{} + + for _, exp := range expressions { + ret = append(ret, explicitCast(exp)) + } + + return ret +} + +func explicitCast(expresion Expression) jet.Expression { + if _, ok := expresion.(jet.LiteralExpression); !ok { + return expresion + } + + switch expresion.(type) { + case jet.BoolExpression: + return CAST(expresion).AS_BOOL() + case jet.IntegerExpression: + return CAST(expresion).AS_INTEGER() + case jet.FloatExpression: + return CAST(expresion).AS_NUMERIC() + case jet.StringExpression: + return CAST(expresion).AS_TEXT() + } + + return expresion +} diff --git a/postgres/postgres_keywords.go b/postgres/keywords.go similarity index 100% rename from postgres/postgres_keywords.go rename to postgres/keywords.go diff --git a/postgres/postgres_literal.go b/postgres/literal.go similarity index 57% rename from postgres/postgres_literal.go rename to postgres/literal.go index ac33b43..73964c8 100644 --- a/postgres/postgres_literal.go +++ b/postgres/literal.go @@ -1,24 +1,32 @@ package postgres -import "github.com/go-jet/jet/internal/jet" +import ( + "github.com/go-jet/jet/internal/jet" + "time" +) var Bool = jet.Bool var Int = jet.Int var Float = jet.Float var String = jet.String -var Date = func(year, month, day int) DateExpression { + +var Bytea = func(value string) StringExpression { + return CAST(jet.String(value)).AS_BYTEA() +} + +var Date = func(year int, month time.Month, day int) DateExpression { return CAST(jet.Date(year, month, day)).AS_DATE() } -var Time = func(hour, minute, second, milliseconds int) TimeExpression { - return CAST(jet.Time(hour, minute, second, milliseconds)).AS_TIME() +var Time = func(hour, minute, second int, milliseconds ...int) TimeExpression { + return CAST(jet.Time(hour, minute, second, milliseconds...)).AS_TIME() } var Timez = func(hour, minute, second, milliseconds int, timezone int) TimezExpression { return CAST(jet.Timez(hour, minute, second, milliseconds, timezone)).AS_TIMEZ() } -var Timestamp = func(year, month, day, hour, minute, second, milliseconds int) TimestampExpression { +var Timestamp = func(year int, month time.Month, day, hour, minute, second, milliseconds int) TimestampExpression { return CAST(jet.Timestamp(year, month, day, hour, minute, second, milliseconds)).AS_TIMESTAMP() } diff --git a/postgres/literal_test.go b/postgres/literal_test.go new file mode 100644 index 0000000..89f7601 --- /dev/null +++ b/postgres/literal_test.go @@ -0,0 +1,7 @@ +package postgres + +import "testing" + +func TestDateLiteral(t *testing.T) { + assertClauseSerialize(t, Date(2019, 8, 6), "$1::DATE", "2019-08-06") +} diff --git a/postgres/postgres_lock.go b/postgres/lock_statement.go similarity index 100% rename from postgres/postgres_lock.go rename to postgres/lock_statement.go diff --git a/postgres/postgres_operators.go b/postgres/operators.go similarity index 55% rename from postgres/postgres_operators.go rename to postgres/operators.go index 2c1eb47..6f50818 100644 --- a/postgres/postgres_operators.go +++ b/postgres/operators.go @@ -4,10 +4,18 @@ import "github.com/go-jet/jet/internal/jet" // --------- Arithmetic operators -------------// -var MINUSi = jet.MINUSi +//var MINUSi = jet.MINUSi var MINUSf = jet.MINUSf //----------- Logical operators ---------------// var NOT = jet.NOT var BIT_NOT = jet.BIT_NOT + +func MINUSi(intExp IntegerExpression) IntegerExpression { + if intLit, ok := intExp.(jet.LiteralExpression); ok { + intLit.SetConstant(true) + } + + return intExp +} diff --git a/postgres/postgres_column.go b/postgres/postgres_column.go deleted file mode 100644 index a4ee133..0000000 --- a/postgres/postgres_column.go +++ /dev/null @@ -1,9 +0,0 @@ -package postgres - -import "github.com/go-jet/jet/internal/jet" - -type Column jet.Column - -type IColumnList jet.IColumnList - -var ColumnList = jet.ColumnList diff --git a/postgres/postgres_enum.go b/postgres/postgres_enum.go deleted file mode 100644 index 1d3ebc7..0000000 --- a/postgres/postgres_enum.go +++ /dev/null @@ -1,5 +0,0 @@ -package postgres - -import "github.com/go-jet/jet/internal/jet" - -var NewEnumValue = jet.NewEnumValue diff --git a/postgres/postgres_expressions.go b/postgres/postgres_expressions.go deleted file mode 100644 index ca4d6ea..0000000 --- a/postgres/postgres_expressions.go +++ /dev/null @@ -1,5 +0,0 @@ -package postgres - -import "github.com/go-jet/jet/internal/jet" - -var RAW = jet.RAW diff --git a/postgres/postgres_select.go b/postgres/select_statement.go similarity index 97% rename from postgres/postgres_select.go rename to postgres/select_statement.go index a7a2166..ac74830 100644 --- a/postgres/postgres_select.go +++ b/postgres/select_statement.go @@ -3,6 +3,15 @@ package postgres import "github.com/go-jet/jet/internal/jet" type SelectStatement jet.SelectStatement +type SelectTable jet.SelectTable +type SelectLock jet.SelectLock + +var ( + UPDATE = jet.NewSelectLock("UPDATE") + NO_KEY_UPDATE = jet.NewSelectLock("NO KEY UPDATE") + SHARE = jet.NewSelectLock("SHARE") + KEY_SHARE = jet.NewSelectLock("KEY SHARE") +) var SELECT = jet.SELECT @@ -31,12 +40,3 @@ func toJetSelects(selects ...SelectStatement) []jet.SelectStatement { return ret } - -type SelectLock jet.SelectLock - -var ( - UPDATE = jet.NewSelectLock("UPDATE") - NO_KEY_UPDATE = jet.NewSelectLock("NO KEY UPDATE") - SHARE = jet.NewSelectLock("SHARE") - KEY_SHARE = jet.NewSelectLock("KEY SHARE") -) diff --git a/postgres/postgres_select_test.go b/postgres/select_statement_test.go similarity index 100% rename from postgres/postgres_select_test.go rename to postgres/select_statement_test.go diff --git a/postgres/postgres_table.go b/postgres/table.go similarity index 100% rename from postgres/postgres_table.go rename to postgres/table.go diff --git a/postgres/time_expression_test.go b/postgres/time_expression_test.go deleted file mode 100644 index c7cbf0c..0000000 --- a/postgres/time_expression_test.go +++ /dev/null @@ -1,53 +0,0 @@ -package postgres - -import ( - "testing" -) - -var timeVar = Time(10, 20, 0, 0) - -func TestTimeExpressionEQ(t *testing.T) { - assertClauseSerialize(t, table1ColTime.EQ(table2ColTime), "(table1.col_time = table2.col_time)") - assertClauseSerialize(t, table1ColTime.EQ(timeVar), "(table1.col_time = $1::time without time zone)", "10:20:00.000") -} - -func TestTimeExpressionNOT_EQ(t *testing.T) { - assertClauseSerialize(t, table1ColTime.NOT_EQ(table2ColTime), "(table1.col_time != table2.col_time)") - assertClauseSerialize(t, table1ColTime.NOT_EQ(timeVar), "(table1.col_time != $1::time without time zone)", "10:20:00.000") -} - -func TestTimeExpressionIS_DISTINCT_FROM(t *testing.T) { - assertClauseSerialize(t, table1ColTime.IS_DISTINCT_FROM(table2ColTime), "(table1.col_time IS DISTINCT FROM table2.col_time)") - assertClauseSerialize(t, table1ColTime.IS_DISTINCT_FROM(timeVar), "(table1.col_time IS DISTINCT FROM $1::time without time zone)", "10:20:00.000") -} - -func TestTimeExpressionIS_NOT_DISTINCT_FROM(t *testing.T) { - assertClauseSerialize(t, table1ColTime.IS_NOT_DISTINCT_FROM(table2ColTime), "(table1.col_time IS NOT DISTINCT FROM table2.col_time)") - assertClauseSerialize(t, table1ColTime.IS_NOT_DISTINCT_FROM(timeVar), "(table1.col_time IS NOT DISTINCT FROM $1::time without time zone)", "10:20:00.000") -} - -func TestTimeExpressionLT(t *testing.T) { - assertClauseSerialize(t, table1ColTime.LT(table2ColTime), "(table1.col_time < table2.col_time)") - assertClauseSerialize(t, table1ColTime.LT(timeVar), "(table1.col_time < $1::time without time zone)", "10:20:00.000") -} - -func TestTimeExpressionLT_EQ(t *testing.T) { - assertClauseSerialize(t, table1ColTime.LT_EQ(table2ColTime), "(table1.col_time <= table2.col_time)") - assertClauseSerialize(t, table1ColTime.LT_EQ(timeVar), "(table1.col_time <= $1::time without time zone)", "10:20:00.000") -} - -func TestTimeExpressionGT(t *testing.T) { - assertClauseSerialize(t, table1ColTime.GT(table2ColTime), "(table1.col_time > table2.col_time)") - assertClauseSerialize(t, table1ColTime.GT(timeVar), "(table1.col_time > $1::time without time zone)", "10:20:00.000") -} - -func TestTimeExpressionGT_EQ(t *testing.T) { - assertClauseSerialize(t, table1ColTime.GT_EQ(table2ColTime), "(table1.col_time >= table2.col_time)") - assertClauseSerialize(t, table1ColTime.GT_EQ(timeVar), "(table1.col_time >= $1::time without time zone)", "10:20:00.000") -} - -func TestTimeExp(t *testing.T) { - assertClauseSerialize(t, TimeExp(table1ColFloat), "table1.col_float") - assertClauseSerialize(t, TimeExp(table1ColFloat).LT(Time(1, 1, 1, 1)), - "(table1.col_float < $1::time without time zone)", string("01:01:01.001")) -} diff --git a/postgres/timestamp_expression_test.go b/postgres/timestamp_expression_test.go deleted file mode 100644 index 77f490d..0000000 --- a/postgres/timestamp_expression_test.go +++ /dev/null @@ -1,54 +0,0 @@ -package postgres - -import ( - "testing" -) - -var timestamp = Timestamp(2000, 1, 31, 10, 20, 0, 0) - -func TestTimestampExpressionEQ(t *testing.T) { - assertClauseSerialize(t, table1ColTimestamp.EQ(table2ColTimestamp), "(table1.col_timestamp = table2.col_timestamp)") - assertClauseSerialize(t, table1ColTimestamp.EQ(timestamp), - "(table1.col_timestamp = $1::timestamp without time zone)", "2000-01-31 10:20:00.000") -} - -func TestTimestampExpressionNOT_EQ(t *testing.T) { - assertClauseSerialize(t, table1ColTimestamp.NOT_EQ(table2ColTimestamp), "(table1.col_timestamp != table2.col_timestamp)") - assertClauseSerialize(t, table1ColTimestamp.NOT_EQ(timestamp), "(table1.col_timestamp != $1::timestamp without time zone)", "2000-01-31 10:20:00.000") -} - -func TestTimestampExpressionIS_DISTINCT_FROM(t *testing.T) { - assertClauseSerialize(t, table1ColTimestamp.IS_DISTINCT_FROM(table2ColTimestamp), "(table1.col_timestamp IS DISTINCT FROM table2.col_timestamp)") - assertClauseSerialize(t, table1ColTimestamp.IS_DISTINCT_FROM(timestamp), "(table1.col_timestamp IS DISTINCT FROM $1::timestamp without time zone)", "2000-01-31 10:20:00.000") -} - -func TestTimestampExpressionIS_NOT_DISTINCT_FROM(t *testing.T) { - assertClauseSerialize(t, table1ColTimestamp.IS_NOT_DISTINCT_FROM(table2ColTimestamp), "(table1.col_timestamp IS NOT DISTINCT FROM table2.col_timestamp)") - assertClauseSerialize(t, table1ColTimestamp.IS_NOT_DISTINCT_FROM(timestamp), "(table1.col_timestamp IS NOT DISTINCT FROM $1::timestamp without time zone)", "2000-01-31 10:20:00.000") -} - -func TestTimestampExpressionLT(t *testing.T) { - assertClauseSerialize(t, table1ColTimestamp.LT(table2ColTimestamp), "(table1.col_timestamp < table2.col_timestamp)") - assertClauseSerialize(t, table1ColTimestamp.LT(timestamp), "(table1.col_timestamp < $1::timestamp without time zone)", "2000-01-31 10:20:00.000") -} - -func TestTimestampExpressionLT_EQ(t *testing.T) { - assertClauseSerialize(t, table1ColTimestamp.LT_EQ(table2ColTimestamp), "(table1.col_timestamp <= table2.col_timestamp)") - assertClauseSerialize(t, table1ColTimestamp.LT_EQ(timestamp), "(table1.col_timestamp <= $1::timestamp without time zone)", "2000-01-31 10:20:00.000") -} - -func TestTimestampExpressionGT(t *testing.T) { - assertClauseSerialize(t, table1ColTimestamp.GT(table2ColTimestamp), "(table1.col_timestamp > table2.col_timestamp)") - assertClauseSerialize(t, table1ColTimestamp.GT(timestamp), "(table1.col_timestamp > $1::timestamp without time zone)", "2000-01-31 10:20:00.000") -} - -func TestTimestampExpressionGT_EQ(t *testing.T) { - assertClauseSerialize(t, table1ColTimestamp.GT_EQ(table2ColTimestamp), "(table1.col_timestamp >= table2.col_timestamp)") - assertClauseSerialize(t, table1ColTimestamp.GT_EQ(timestamp), "(table1.col_timestamp >= $1::timestamp without time zone)", "2000-01-31 10:20:00.000") -} - -func TestTimestampExp(t *testing.T) { - assertClauseSerialize(t, TimestampExp(table1ColFloat), "table1.col_float") - assertClauseSerialize(t, TimestampExp(table1ColFloat).LT(timestamp), - "(table1.col_float < $1::timestamp without time zone)", "2000-01-31 10:20:00.000") -} diff --git a/postgres/testutils.go b/postgres/utils_test.go similarity index 100% rename from postgres/testutils.go rename to postgres/utils_test.go diff --git a/tests/init/data/mysql/test_sample.sql b/tests/init/data/mysql/test_sample.sql index a64d3d1..16c8932 100644 --- a/tests/init/data/mysql/test_sample.sql +++ b/tests/init/data/mysql/test_sample.sql @@ -66,6 +66,8 @@ CREATE TABLE `all_types` ( `bit_ptr` bit(10), -- date and time + `time` time NOT NULL, + `time_ptr` time, `date` date NOT NULL, `date_ptr` date, @@ -84,8 +86,8 @@ CREATE TABLE `all_types` ( `char` char(20) NOT NULL, `char_ptr` char(20), - `varchar` varchar(20) NOT NULL, - `varchar_ptr` varchar(20), + `var_char` varchar(20) NOT NULL, + `var_char_ptr` varchar(20), `binary` binary(20) NOT NULL, `binary_ptr` binary(20), @@ -117,12 +119,14 @@ INSERT INTO `all_types` VALUES -3,3,14,14,-150,150,-1600,1600,5000,50000, -3,3,14,14,-150,150,-1600,1600,50000,50000, 1.11,1.11,2.22,2.22,3.33,3.33,4.44,4.44,5.55,5.55, -_binary '\0',_binary '\0','2008-07-04','2008-07-04','2011-12-18 13:17:17','2011-12-18 13:17:17','2007-12-31 23:00:01','2007-12-31 23:00:01',2004,2004,'char','char','varchar','varchar',_binary 'binary\0\0\0\0\0\0\0\0\0\0\0\0\0\0',_binary 'binary\0\0\0\0\0\0\0\0\0\0\0\0\0\0',_binary 'varbinary',_binary 'varbinary',_binary 'blob',_binary 'blob','text','text','value1','value1','s1','s2','{\"key1\": \"value1\", \"key2\": \"value2\"}','{\"key1\": \"value1\", \"key2\": \"value2\"}'), +_binary '\0',_binary '\0', +'10:11:12.33', '10:11:12.33', '2008-07-04','2008-07-04','2011-12-18 13:17:17','2011-12-18 13:17:17','2007-12-31 23:00:01','2007-12-31 23:00:01',2004,2004,'char','char','varchar','varchar',_binary 'binary\0\0\0\0\0\0\0\0\0\0\0\0\0\0',_binary 'binary\0\0\0\0\0\0\0\0\0\0\0\0\0\0',_binary 'varbinary',_binary 'varbinary',_binary 'blob',_binary 'blob','text','text','value1','value1','s1','s2','{\"key1\": \"value1\", \"key2\": \"value2\"}','{\"key1\": \"value1\", \"key2\": \"value2\"}'), (false, NULL, -3,3,14,14,-150,150,-1600,1600,5000,50000, NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL, 1.11,NULL,2.22,NULL,3.33,NULL,4.44,NULL,5.55,NULL, -_binary '\0',NULL,'2008-07-04',NULL,'2011-12-18 13:17:17',NULL,'2007-12-31 23:00:01',NULL,2004,NULL,'char',NULL,'varchar',NULL,_binary 'binary\0\0\0\0\0\0\0\0\0\0\0\0\0\0',NULL,_binary 'varbinary',NULL,_binary 'blob',NULL,'text',NULL,'value1',NULL,'s1',NULL,'{\"key1\": \"value1\", \"key2\": \"value2\"}',NULL); +_binary '\0',NULL, +'10:11:12.33', NULL, '2008-07-04',NULL,'2011-12-18 13:17:17',NULL,'2007-12-31 23:00:01',NULL,2004,NULL,'char',NULL,'varchar',NULL,_binary 'binary\0\0\0\0\0\0\0\0\0\0\0\0\0\0',NULL,_binary 'varbinary',NULL,_binary 'blob',NULL,'text',NULL,'value1',NULL,'s1',NULL,'{\"key1\": \"value1\", \"key2\": \"value2\"}',NULL); diff --git a/tests/init/data/postgres/test_sample.sql b/tests/init/data/postgres/test_sample.sql index 5e5f55a..cc12d72 100644 --- a/tests/init/data/postgres/test_sample.sql +++ b/tests/init/data/postgres/test_sample.sql @@ -30,10 +30,10 @@ CREATE TABLE test_sample.ALL_TYPES -- money_ptr money, -- money money NOT NULL, - character_varying_ptr character varying(100), - character_varying character varying(200) NOT NULL, - character_ptr character(80), - character character(80) NOT NULL, + var_char_ptr character varying(100), + var_char character varying(200) NOT NULL, + char_ptr character(80), + char character(80) NOT NULL, text_ptr text, text text NOT NULL, @@ -99,7 +99,7 @@ CREATE TABLE test_sample.ALL_TYPES INSERT INTO test_sample.ALL_types( small_int_ptr, "small_int", integer_ptr, "integer", big_int_ptr, "big_int", decimal_ptr, "decimal", numeric_ptr, "numeric", real_ptr, "real", double_precision_ptr, double_precision, smallserial, serial, bigserial, -- money_ptr, money, - character_varying_ptr, character_varying, character_ptr, "character", text_ptr, text, + var_char_ptr, var_char, char_ptr, char, text_ptr, text, bytea_ptr, bytea, timestampz_ptr, timestampz, timestamp_ptr, "timestamp", date_ptr, date, timez_ptr, timez, time_ptr, "time", interval_ptr, "interval", boolean_ptr, "boolean", diff --git a/tests/mysql/alltypes_test.go b/tests/mysql/alltypes_test.go index 972e464..dcd03f3 100644 --- a/tests/mysql/alltypes_test.go +++ b/tests/mysql/alltypes_test.go @@ -1,10 +1,12 @@ package mysql import ( + "fmt" "github.com/go-jet/jet/internal/testutils" "github.com/go-jet/jet/tests/.gentestdata/mysql/test_sample/model" . "github.com/go-jet/jet/tests/.gentestdata/mysql/test_sample/table" "github.com/go-jet/jet/tests/testdata/common" + "time" . "github.com/go-jet/jet/mysql" @@ -35,6 +37,8 @@ func TestExpressionOperators(t *testing.T) { AllTypes.SmallIntPtr.IN(AllTypes.SELECT(AllTypes.Integer)).AS("result.in_select"), AllTypes.SmallIntPtr.NOT_IN(Int(11), Int(22), NULL).AS("result.not_in"), AllTypes.SmallIntPtr.NOT_IN(AllTypes.SELECT(AllTypes.Integer)).AS("result.not_in_select"), + + RAW("DATABASE()"), ).LIMIT(2) //fmt.Println(query.Sql()) @@ -51,7 +55,8 @@ SELECT all_types.integer IS NULL AS "result.is_null", (all_types.small_int_ptr NOT IN (( SELECT all_types.integer AS "all_types.integer" FROM test_sample.all_types - ))) AS "result.not_in_select" + ))) AS "result.not_in_select", + DATABASE() FROM test_sample.all_types LIMIT ?; `, int64(11), int64(22), int64(11), int64(22), int64(2)) @@ -116,8 +121,8 @@ SELECT (all_types.boolean = all_types.boolean_ptr) AS "EQ1", (all_types.boolean = ?) AS "EQ2", (all_types.boolean != all_types.boolean_ptr) AS "NEq1", (all_types.boolean != ?) AS "NEq2", - (NOT all_types.boolean <=> all_types.boolean_ptr) AS "distinct1", - (NOT all_types.boolean <=> ?) AS "distinct2", + (NOT(all_types.boolean <=> all_types.boolean_ptr)) AS "distinct1", + (NOT(all_types.boolean <=> ?)) AS "distinct2", (all_types.boolean <=> all_types.boolean_ptr) AS "not_distinct_1", (all_types.boolean <=> ?) AS "NOTDISTINCT2", all_types.boolean IS TRUE AS "ISTRUE", @@ -208,9 +213,9 @@ func TestFloatOperators(t *testing.T) { SELECT (all_types.numeric = all_types.numeric) AS "eq1", (all_types.decimal = ?) AS "eq2", (all_types.real = ?) AS "eq3", - (NOT all_types.numeric <=> all_types.numeric) AS "distinct1", - (NOT all_types.decimal <=> ?) AS "distinct2", - (NOT all_types.real <=> ?) AS "distinct3", + (NOT(all_types.numeric <=> all_types.numeric)) AS "distinct1", + (NOT(all_types.decimal <=> ?)) AS "distinct2", + (NOT(all_types.real <=> ?)) AS "distinct3", (all_types.numeric <=> all_types.numeric) AS "not_distinct1", (all_types.decimal <=> ?) AS "not_distinct2", (all_types.real <=> ?) AS "not_distinct3", @@ -338,8 +343,8 @@ SELECT all_types.big_int AS "all_types.big_int", (all_types.big_int = ?) AS "eq2", (all_types.big_int != all_types.big_int_ptr) AS "neq1", (all_types.big_int != ?) AS "neq2", - (NOT all_types.big_int <=> all_types.big_int) AS "distinct1", - (NOT all_types.big_int <=> ?) AS "distinct2", + (NOT(all_types.big_int <=> all_types.big_int)) AS "distinct1", + (NOT(all_types.big_int <=> ?)) AS "distinct2", (all_types.big_int <=> all_types.big_int) AS "not distinct1", (all_types.big_int <=> ?) AS "not distinct2", (all_types.big_int < all_types.big_int_ptr) AS "lt1", @@ -394,6 +399,319 @@ LIMIT ?; testutils.AssertJSONFile(t, dest, "./testdata/common/int_operators.json") } +func TestStringOperators(t *testing.T) { + query := AllTypes.SELECT( + AllTypes.Text.EQ(AllTypes.Char), + AllTypes.Text.EQ(String("Text")), + AllTypes.Text.NOT_EQ(AllTypes.VarCharPtr), + AllTypes.Text.NOT_EQ(String("Text")), + AllTypes.Text.GT(AllTypes.Text), + AllTypes.Text.GT(String("Text")), + AllTypes.Text.GT_EQ(AllTypes.TextPtr), + AllTypes.Text.GT_EQ(String("Text")), + AllTypes.Text.LT(AllTypes.Char), + AllTypes.Text.LT(String("Text")), + AllTypes.Text.LT_EQ(AllTypes.VarCharPtr), + AllTypes.Text.LT_EQ(String("Text")), + AllTypes.Text.CONCAT(String("text2")), + AllTypes.Text.CONCAT(Int(11)), + AllTypes.Text.LIKE(String("abc")), + AllTypes.Text.NOT_LIKE(String("_b_")), + AllTypes.Text.REGEXP_LIKE(String("aba")), + AllTypes.Text.REGEXP_LIKE(String("aba"), "c"), + String("ABA").REGEXP_LIKE(String("aba"), "i"), + + BIT_LENGTH(AllTypes.Text), + CHAR_LENGTH(AllTypes.Char), + OCTET_LENGTH(AllTypes.Text), + LOWER(AllTypes.VarCharPtr), + UPPER(AllTypes.Char), + LTRIM(AllTypes.VarCharPtr), + RTRIM(AllTypes.VarCharPtr), + CONCAT(String("string1"), Int(1), Float(11.12)), + CONCAT_WS(String("string1"), Int(1), Float(11.12)), + FORMAT(String("Hello %s, %1$s"), String("World")), + LEFT(String("abcde"), Int(2)), + RIGHT(String("abcde"), Int(2)), + LENGTH(String("jose")), + LPAD(String("Hi"), Int(5), String("xy")), + RPAD(String("Hi"), Int(5), String("xy")), + MD5(AllTypes.VarCharPtr), + REPEAT(AllTypes.Text, Int(33)), + REPLACE(AllTypes.Char, String("BA"), String("AB")), + REVERSE(AllTypes.VarCharPtr), + SUBSTR(AllTypes.CharPtr, Int(3)), + SUBSTR(AllTypes.CharPtr, Int(3), Int(2)), + REGEXP_LIKE(String("ABA"), String("aba")), + REGEXP_LIKE(String("ABA"), String("aba"), "i"), + REGEXP_LIKE(AllTypes.Text, String("aba"), "i"), + ) + + //_, args, _ := query.Sql() + + //fmt.Println(query.Sql()) + //fmt.Println(args[15]) + + fmt.Println(query.Sql()) + + err := query.Query(db, &struct{}{}) + + assert.NilError(t, err) +} + +var timeT = time.Date(2009, 11, 17, 20, 34, 58, 651387237, time.UTC) + +func TestTimeExpressions(t *testing.T) { + + query := AllTypes.SELECT( + Time(timeT.Clock()), + + AllTypes.Time.EQ(AllTypes.Time), + AllTypes.Time.EQ(Time(23, 6, 6)), + AllTypes.Time.EQ(Time(22, 6, 6, 11)), + AllTypes.Time.EQ(Time(21, 6, 6, 11111)), + + AllTypes.TimePtr.NOT_EQ(AllTypes.Time), + AllTypes.TimePtr.NOT_EQ(Time(20, 16, 6)), + + AllTypes.Time.IS_DISTINCT_FROM(AllTypes.Time), + AllTypes.Time.IS_DISTINCT_FROM(Time(19, 26, 6)), + + AllTypes.Time.IS_NOT_DISTINCT_FROM(AllTypes.Time), + AllTypes.Time.IS_NOT_DISTINCT_FROM(Time(18, 36, 6)), + + AllTypes.Time.LT(AllTypes.Time), + AllTypes.Time.LT(Time(17, 46, 6)), + + AllTypes.Time.LT_EQ(AllTypes.Time), + AllTypes.Time.LT_EQ(Time(16, 56, 56)), + + AllTypes.Time.GT(AllTypes.Time), + AllTypes.Time.GT(Time(15, 16, 46)), + + AllTypes.Time.GT_EQ(AllTypes.Time), + AllTypes.Time.GT_EQ(Time(14, 26, 36)), + + CURRENT_TIME(), + CURRENT_TIME(3), + ) + + fmt.Println(query.Sql()) + + testutils.AssertStatementSql(t, query, ` +SELECT ?, + all_types.time = all_types.time, + all_types.time = ?, + all_types.time = ?, + all_types.time = ?, + all_types.time_ptr != all_types.time, + all_types.time_ptr != ?, + NOT(all_types.time <=> all_types.time), + NOT(all_types.time <=> ?), + all_types.time <=> all_types.time, + all_types.time <=> ?, + all_types.time < all_types.time, + all_types.time < ?, + all_types.time <= all_types.time, + all_types.time <= ?, + all_types.time > all_types.time, + all_types.time > ?, + all_types.time >= all_types.time, + all_types.time >= ?, + CURRENT_TIME, + CURRENT_TIME(3) +FROM test_sample.all_types; +`, "20:34:58", "23:06:06", "22:06:06.011", "21:06:06.11111", "20:16:06", + "19:26:06", "18:36:06", "17:46:06", "16:56:56", "15:16:46", "14:26:36") + + err := query.Query(db, &struct{}{}) + + assert.NilError(t, err) +} + +func TestDateExpressions(t *testing.T) { + query := AllTypes.SELECT( + Date(timeT.Date()), + + AllTypes.Date.EQ(AllTypes.Date), + AllTypes.Date.EQ(Date(2019, 6, 6)), + + AllTypes.DatePtr.NOT_EQ(AllTypes.Date), + AllTypes.DatePtr.NOT_EQ(Date(2019, 1, 6)), + + AllTypes.Date.IS_DISTINCT_FROM(AllTypes.Date), + AllTypes.Date.IS_DISTINCT_FROM(Date(2019, 2, 6)), + + AllTypes.Date.IS_NOT_DISTINCT_FROM(AllTypes.Date), + AllTypes.Date.IS_NOT_DISTINCT_FROM(Date(2019, 3, 6)), + + AllTypes.Date.LT(AllTypes.Date), + AllTypes.Date.LT(Date(2019, 4, 6)), + + AllTypes.Date.LT_EQ(AllTypes.Date), + AllTypes.Date.LT_EQ(Date(2019, 5, 5)), + + AllTypes.Date.GT(AllTypes.Date), + AllTypes.Date.GT(Date(2019, 1, 4)), + + AllTypes.Date.GT_EQ(AllTypes.Date), + AllTypes.Date.GT_EQ(Date(2019, 2, 3)), + + CURRENT_DATE(), + ) + + //fmt.Println(query.Sql()) + + testutils.AssertStatementSql(t, query, ` +SELECT ?, + all_types.date = all_types.date, + all_types.date = ?, + all_types.date_ptr != all_types.date, + all_types.date_ptr != ?, + NOT(all_types.date <=> all_types.date), + NOT(all_types.date <=> ?), + all_types.date <=> all_types.date, + all_types.date <=> ?, + all_types.date < all_types.date, + all_types.date < ?, + all_types.date <= all_types.date, + all_types.date <= ?, + all_types.date > all_types.date, + all_types.date > ?, + all_types.date >= all_types.date, + all_types.date >= ?, + CURRENT_DATE +FROM test_sample.all_types; +`) + + err := query.Query(db, &struct{}{}) + + assert.NilError(t, err) +} + +func TestDateTimeExpressions(t *testing.T) { + + var dateTime = DateTime(2019, 6, 6, 10, 2, 46) + + query := AllTypes.SELECT( + AllTypes.DateTime.EQ(AllTypes.DateTime), + AllTypes.DateTime.EQ(dateTime), + + AllTypes.DateTimePtr.NOT_EQ(AllTypes.DateTime), + AllTypes.DateTimePtr.NOT_EQ(DateTime(2019, 6, 6, 10, 2, 46, 1000)), + + AllTypes.DateTime.IS_DISTINCT_FROM(AllTypes.DateTime), + AllTypes.DateTime.IS_DISTINCT_FROM(dateTime), + + AllTypes.DateTime.IS_NOT_DISTINCT_FROM(AllTypes.DateTime), + AllTypes.DateTime.IS_NOT_DISTINCT_FROM(dateTime), + + AllTypes.DateTime.LT(AllTypes.DateTime), + AllTypes.DateTime.LT(dateTime), + + AllTypes.DateTime.LT_EQ(AllTypes.DateTime), + AllTypes.DateTime.LT_EQ(dateTime), + + AllTypes.DateTime.GT(AllTypes.DateTime), + AllTypes.DateTime.GT(dateTime), + + AllTypes.DateTime.GT_EQ(AllTypes.DateTime), + AllTypes.DateTime.GT_EQ(dateTime), + + NOW(), + NOW(1), + ) + + fmt.Println(query.DebugSql()) + + testutils.AssertDebugStatementSql(t, query, ` +SELECT all_types.date_time = all_types.date_time, + all_types.date_time = '2019-06-06 10:02:46', + all_types.date_time_ptr != all_types.date_time, + all_types.date_time_ptr != '2019-06-06 10:02:46.1000', + NOT(all_types.date_time <=> all_types.date_time), + NOT(all_types.date_time <=> '2019-06-06 10:02:46'), + all_types.date_time <=> all_types.date_time, + all_types.date_time <=> '2019-06-06 10:02:46', + all_types.date_time < all_types.date_time, + all_types.date_time < '2019-06-06 10:02:46', + all_types.date_time <= all_types.date_time, + all_types.date_time <= '2019-06-06 10:02:46', + all_types.date_time > all_types.date_time, + all_types.date_time > '2019-06-06 10:02:46', + all_types.date_time >= all_types.date_time, + all_types.date_time >= '2019-06-06 10:02:46', + NOW(), + NOW(1) +FROM test_sample.all_types; +`) + + err := query.Query(db, &struct{}{}) + + assert.NilError(t, err) +} + +func TestTimestampExpressions(t *testing.T) { + + var timestamp = Timestamp(2019, 6, 6, 10, 2, 46) + + query := AllTypes.SELECT( + AllTypes.Timestamp.EQ(AllTypes.Timestamp), + AllTypes.Timestamp.EQ(timestamp), + + AllTypes.TimestampPtr.NOT_EQ(AllTypes.Timestamp), + AllTypes.TimestampPtr.NOT_EQ(Timestamp(2019, 6, 6, 10, 2, 46, 1000)), + + AllTypes.Timestamp.IS_DISTINCT_FROM(AllTypes.Timestamp), + AllTypes.Timestamp.IS_DISTINCT_FROM(timestamp), + + AllTypes.Timestamp.IS_NOT_DISTINCT_FROM(AllTypes.Timestamp), + AllTypes.Timestamp.IS_NOT_DISTINCT_FROM(timestamp), + + AllTypes.Timestamp.LT(AllTypes.Timestamp), + AllTypes.Timestamp.LT(timestamp), + + AllTypes.Timestamp.LT_EQ(AllTypes.Timestamp), + AllTypes.Timestamp.LT_EQ(timestamp), + + AllTypes.Timestamp.GT(AllTypes.Timestamp), + AllTypes.Timestamp.GT(timestamp), + + AllTypes.Timestamp.GT_EQ(AllTypes.Timestamp), + AllTypes.Timestamp.GT_EQ(timestamp), + + CURRENT_TIMESTAMP(), + CURRENT_TIMESTAMP(2), + ) + + fmt.Println(query.DebugSql()) + + testutils.AssertDebugStatementSql(t, query, ` +SELECT all_types.timestamp = all_types.timestamp, + all_types.timestamp = '2019-06-06 10:02:46', + all_types.timestamp_ptr != all_types.timestamp, + all_types.timestamp_ptr != '2019-06-06 10:02:46.1000', + NOT(all_types.timestamp <=> all_types.timestamp), + NOT(all_types.timestamp <=> '2019-06-06 10:02:46'), + all_types.timestamp <=> all_types.timestamp, + all_types.timestamp <=> '2019-06-06 10:02:46', + all_types.timestamp < all_types.timestamp, + all_types.timestamp < '2019-06-06 10:02:46', + all_types.timestamp <= all_types.timestamp, + all_types.timestamp <= '2019-06-06 10:02:46', + all_types.timestamp > all_types.timestamp, + all_types.timestamp > '2019-06-06 10:02:46', + all_types.timestamp >= all_types.timestamp, + all_types.timestamp >= '2019-06-06 10:02:46', + CURRENT_TIMESTAMP, + CURRENT_TIMESTAMP(2) +FROM test_sample.all_types; +`) + err := query.Query(db, &struct{}{}) + + assert.NilError(t, err) +} + var allTypesJson = ` [ { @@ -431,6 +749,8 @@ var allTypesJson = ` "RealPtr": 5.55, "Bit": "\u0000\u0003", "BitPtr": "\u0000\u0003", + "Time": "0000-01-01T10:11:12Z", + "TimePtr": "0000-01-01T10:11:12Z", "Date": "2008-07-04T00:00:00Z", "DatePtr": "2008-07-04T00:00:00Z", "DateTime": "2011-12-18T13:17:17Z", @@ -441,8 +761,8 @@ var allTypesJson = ` "YearPtr": 2004, "Char": "char", "CharPtr": "char", - "Varchar": "varchar", - "VarcharPtr": "varchar", + "VarChar": "varchar", + "VarCharPtr": "varchar", "Binary": "YmluYXJ5AAAAAAAAAAAAAAAAAAA=", "BinaryPtr": "YmluYXJ5AAAAAAAAAAAAAAAAAAA=", "VarBinary": "dmFyYmluYXJ5", @@ -493,6 +813,8 @@ var allTypesJson = ` "RealPtr": null, "Bit": "\u0000\u0003", "BitPtr": null, + "Time": "0000-01-01T10:11:12Z", + "TimePtr": null, "Date": "2008-07-04T00:00:00Z", "DatePtr": null, "DateTime": "2011-12-18T13:17:17Z", @@ -503,8 +825,8 @@ var allTypesJson = ` "YearPtr": null, "Char": "char", "CharPtr": null, - "Varchar": "varchar", - "VarcharPtr": null, + "VarChar": "varchar", + "VarCharPtr": null, "Binary": "YmluYXJ5AAAAAAAAAAAAAAAAAAA=", "BinaryPtr": null, "VarBinary": "dmFyYmluYXJ5", diff --git a/tests/postgres/alltypes_test.go b/tests/postgres/alltypes_test.go index 3f5bf0b..acabca0 100644 --- a/tests/postgres/alltypes_test.go +++ b/tests/postgres/alltypes_test.go @@ -127,6 +127,7 @@ func TestExpressionCast(t *testing.T) { postgres.CAST(String("111")).AS_BIGINT(), postgres.CAST(String("11.23")).AS_NUMERIC(30, 10), postgres.CAST(String("11.23")).AS_NUMERIC(30), + postgres.CAST(String("11.23")).AS_NUMERIC(), postgres.CAST(String("11.23")).AS_REAL(), postgres.CAST(String("11.23")).AS_DOUBLE(), postgres.CAST(Int(234)).AS_TEXT(), @@ -162,45 +163,56 @@ func TestExpressionCast(t *testing.T) { func TestStringOperators(t *testing.T) { query := AllTypes.SELECT( - AllTypes.Text.EQ(AllTypes.Character), + AllTypes.Text.EQ(AllTypes.Char), AllTypes.Text.EQ(String("Text")), - AllTypes.Text.NOT_EQ(AllTypes.CharacterVaryingPtr), + AllTypes.Text.NOT_EQ(AllTypes.VarCharPtr), AllTypes.Text.NOT_EQ(String("Text")), AllTypes.Text.GT(AllTypes.Text), AllTypes.Text.GT(String("Text")), AllTypes.Text.GT_EQ(AllTypes.TextPtr), AllTypes.Text.GT_EQ(String("Text")), - AllTypes.Text.LT(AllTypes.Character), + AllTypes.Text.LT(AllTypes.Char), AllTypes.Text.LT(String("Text")), - AllTypes.Text.LT_EQ(AllTypes.CharacterVaryingPtr), + AllTypes.Text.LT_EQ(AllTypes.VarChar), AllTypes.Text.LT_EQ(String("Text")), AllTypes.Text.CONCAT(String("text2")), AllTypes.Text.CONCAT(Int(11)), AllTypes.Text.LIKE(String("abc")), AllTypes.Text.NOT_LIKE(String("_b_")), - AllTypes.Text.SIMILAR_TO(String("%(b|d)%")), - AllTypes.Text.NOT_SIMILAR_TO(String("(b|c)%")), + AllTypes.Text.REGEXP_LIKE(String("aba")), + AllTypes.Text.REGEXP_LIKE(String("aba"), "c"), + AllTypes.Text.REGEXP_LIKE(String("aba"), "i"), - BIT_LENGTH(AllTypes.Text), - CHAR_LENGTH(AllTypes.Character), + BIT_LENGTH(String("length")), + CHAR_LENGTH(AllTypes.Char), + CHAR_LENGTH(String("length")), OCTET_LENGTH(AllTypes.Text), - LOWER(AllTypes.CharacterVaryingPtr), - UPPER(AllTypes.Character), - BTRIM(AllTypes.CharacterVarying), - BTRIM(AllTypes.CharacterVarying, String("AA")), - LTRIM(AllTypes.CharacterVarying), - LTRIM(AllTypes.CharacterVarying, String("A")), - RTRIM(AllTypes.CharacterVarying), - RTRIM(AllTypes.CharacterVarying, String("B")), + OCTET_LENGTH(String("length")), + LOWER(AllTypes.VarCharPtr), + LOWER(String("length")), + UPPER(AllTypes.Char), + UPPER(String("upper")), + BTRIM(AllTypes.VarChar), + BTRIM(String("btrim")), + BTRIM(AllTypes.VarChar, String("AA")), + BTRIM(String("btrim"), String("AA")), + LTRIM(AllTypes.VarChar), + LTRIM(String("ltrim")), + LTRIM(AllTypes.VarChar, String("A")), + LTRIM(String("Ltrim"), String("A")), + RTRIM(String("rtrim")), + RTRIM(AllTypes.VarChar, String("B")), CHR(Int(65)), - //CONCAT(String("string1"), Int(1), Float(11.12)), - //CONCAT_WS(String("string1"), Int(1), Float(11.12)), - CONVERT(String("text_in_utf8"), String("UTF8"), String("LATIN1")), + CONCAT(AllTypes.VarCharPtr, AllTypes.VarCharPtr, String("aaa"), Int(1)), + CONCAT(Bool(false), Int(1), Float(22.2), String("test test")), + CONCAT_WS(String("string1"), Int(1), Float(11.22), String("bytea"), Bool(false)), //Float(11.12)), + CONVERT(String("bytea"), String("UTF8"), String("LATIN1")), + CONVERT(AllTypes.Bytea, String("UTF8"), String("LATIN1")), CONVERT_FROM(String("text_in_utf8"), String("UTF8")), CONVERT_TO(String("text_in_utf8"), String("UTF8")), ENCODE(String("123\000\001"), String("base64")), DECODE(String("MTIzAAE="), String("base64")), - //FORMAT(String("Hello %s, %1$s"), String("World")), + FORMAT(String("Hello %s, %1$s"), String("World")), INITCAP(String("hi THOMAS")), LEFT(String("abcde"), Int(2)), RIGHT(String("abcde"), Int(2)), @@ -210,13 +222,13 @@ func TestStringOperators(t *testing.T) { LPAD(String("Hi"), Int(5), String("xy")), RPAD(String("Hi"), Int(5)), RPAD(String("Hi"), Int(5), String("xy")), - MD5(AllTypes.CharacterVarying), + MD5(AllTypes.VarChar), REPEAT(AllTypes.Text, Int(33)), - REPLACE(AllTypes.Character, String("BA"), String("AB")), - REVERSE(AllTypes.CharacterVarying), + REPLACE(AllTypes.Char, String("BA"), String("AB")), + REVERSE(AllTypes.VarChar), STRPOS(AllTypes.Text, String("A")), - SUBSTR(AllTypes.CharacterPtr, Int(3)), - SUBSTR(AllTypes.CharacterPtr, Int(3), Int(2)), + SUBSTR(AllTypes.Char, Int(3)), + SUBSTR(AllTypes.CharPtr, Int(3), Int(2)), TO_HEX(AllTypes.IntegerPtr), ) @@ -224,7 +236,7 @@ func TestStringOperators(t *testing.T) { //fmt.Println(query.Sql()) //fmt.Println(args[15]) - //fmt.Println(query.DebugSql()) + fmt.Println(query.Sql()) err := query.Query(db, &struct{}{}) @@ -459,7 +471,7 @@ func TestIntegerOperators(t *testing.T) { AllTypes.SmallInt.BIT_XOR(Int(11)).AS("bit xor 2"), BIT_NOT(MINUSi(AllTypes.SmallInt)).AS("bit_not_1"), - BIT_NOT(MINUSi(Int(11, true))).AS("bit_not_2"), + BIT_NOT(MINUSi(Int(11))).AS("bit_not_2"), AllTypes.SmallInt.BIT_SHIFT_LEFT(AllTypes.SmallInt.DIV(Int(2))).AS("bit shift left 1"), AllTypes.SmallInt.BIT_SHIFT_LEFT(Int(4)).AS("bit shift left 2"), @@ -513,8 +525,8 @@ SELECT all_types.big_int AS "all_types.big_int", (all_types.small_int | $16) AS "bit or 2", (all_types.small_int # all_types.small_int) AS "bit xor 1", (all_types.small_int # $17) AS "bit xor 2", - (~ (- all_types.small_int)) AS "bit_not_1", - (~ (- 11)) AS "bit_not_2", + (~ all_types.small_int) AS "bit_not_1", + (~ 11) AS "bit_not_2", (all_types.small_int << (all_types.small_int / $18)) AS "bit shift left 1", (all_types.small_int << $19) AS "bit shift left 2", (all_types.small_int >> (all_types.small_int / $20)) AS "bit shift right 1", @@ -539,7 +551,7 @@ LIMIT $22; testutils.AssertJSONFile(t, dest, "./testdata/common/int_operators.json") } -func TestTimeOperators(t *testing.T) { +func TestTimeExpression(t *testing.T) { query := AllTypes.SELECT( AllTypes.Time.EQ(AllTypes.Time), AllTypes.Time.EQ(Time(23, 6, 6, 1)), @@ -799,10 +811,10 @@ var allTypesRow0 = model.AllTypes{ Bigserial: 1, //MoneyPtr: nil, //Money: - CharacterVaryingPtr: StringPtr("ABBA"), - CharacterVarying: "ABBA", - CharacterPtr: StringPtr("JOHN "), - Character: "JOHN ", + VarCharPtr: StringPtr("ABBA"), + VarChar: "ABBA", + CharPtr: StringPtr("JOHN "), + Char: "JOHN ", TextPtr: StringPtr("Some text"), Text: "Some text", ByteaPtr: ByteArrayPtr([]byte("bytea")), @@ -865,10 +877,10 @@ var allTypesRow1 = model.AllTypes{ Bigserial: 2, //MoneyPtr: nil, //Money: - CharacterVaryingPtr: nil, - CharacterVarying: "ABBA", - CharacterPtr: nil, - Character: "JOHN ", + VarCharPtr: nil, + VarChar: "ABBA", + CharPtr: nil, + Char: "JOHN ", TextPtr: nil, Text: "Some text", ByteaPtr: nil, diff --git a/tests/postgres/select_test.go b/tests/postgres/select_test.go index c0cedf9..3b715b4 100644 --- a/tests/postgres/select_test.go +++ b/tests/postgres/select_test.go @@ -1273,8 +1273,6 @@ func TestAllSetOperators(t *testing.T) { UNION_ALL, INTERSECT, INTERSECT_ALL, - //EXCEPT, - //EXCEPT_ALL, } expectedDestLen := []int{