diff --git a/sqlbuilder/cast.go b/sqlbuilder/cast.go new file mode 100644 index 0000000..a28129f --- /dev/null +++ b/sqlbuilder/cast.go @@ -0,0 +1,154 @@ +package sqlbuilder + +type cast struct { + expression expression + castType string +} + +func newCast(expression expression, castType string) cast { + return cast{ + expression: expression, + castType: castType, + } +} + +func (b *cast) serialize(statement statementType, out *queryData, options ...serializeOption) error { + err := b.expression.serialize(statement, out, options...) + out.writeString("::" + b.castType) + return err +} + +type boolCast struct { + expressionInterfaceImpl + boolInterfaceImpl + cast +} + +func newBoolCast(expression expression) BoolExpression { + boolCast := &boolCast{cast: newCast(expression, "boolean")} + + boolCast.boolInterfaceImpl.parent = boolCast + boolCast.expressionInterfaceImpl.parent = boolCast + + return boolCast +} + +type integerCast struct { + expressionInterfaceImpl + integerInterfaceImpl + cast +} + +func newIntegerCast(expression expression) IntegerExpression { + integerCast := &integerCast{cast: newCast(expression, "integer")} + + integerCast.integerInterfaceImpl.parent = integerCast + integerCast.expressionInterfaceImpl.parent = integerCast + + return integerCast +} + +type floatCast struct { + expressionInterfaceImpl + floatInterfaceImpl + cast +} + +func newDoubleCast(expression expression) FloatExpression { + floatCast := &floatCast{cast: newCast(expression, "double precision")} + + floatCast.floatInterfaceImpl.parent = floatCast + floatCast.expressionInterfaceImpl.parent = floatCast + + return floatCast +} + +type textCast struct { + expressionInterfaceImpl + stringInterfaceImpl + cast +} + +func newTextCast(expression expression) StringExpression { + textCast := &textCast{cast: newCast(expression, "text")} + + textCast.stringInterfaceImpl.parent = textCast + textCast.expressionInterfaceImpl.parent = textCast + + return textCast +} + +type dateCast struct { + expressionInterfaceImpl + dateInterfaceImpl + cast +} + +func newDateCast(expression expression) DateExpression { + dateCast := &dateCast{cast: newCast(expression, "date")} + + dateCast.dateInterfaceImpl.parent = dateCast + dateCast.expressionInterfaceImpl.parent = dateCast + + return dateCast +} + +type timeCast struct { + expressionInterfaceImpl + timeInterfaceImpl + cast +} + +func newTimeCast(expression expression) TimeExpression { + timeCast := &timeCast{cast: newCast(expression, "time without time zone")} + + timeCast.timeInterfaceImpl.parent = timeCast + timeCast.expressionInterfaceImpl.parent = timeCast + + return timeCast +} + +type timezCast struct { + expressionInterfaceImpl + timezInterfaceImpl + cast +} + +func newTimezCast(expression expression) TimezExpression { + timezCast := &timezCast{cast: newCast(expression, "time with time zone")} + + timezCast.timezInterfaceImpl.parent = timezCast + timezCast.expressionInterfaceImpl.parent = timezCast + + return timezCast +} + +type timestampCast struct { + expressionInterfaceImpl + timestampInterfaceImpl + cast +} + +func newTimestampCast(expression expression) TimestampExpression { + timestampCast := ×tampCast{cast: newCast(expression, "timestamp without time zone")} + + timestampCast.timestampInterfaceImpl.parent = timestampCast + timestampCast.expressionInterfaceImpl.parent = timestampCast + + return timestampCast +} + +type timestampzCast struct { + expressionInterfaceImpl + timestampzInterfaceImpl + cast +} + +func newTimestampzCast(expression expression) TimestampzExpression { + timestampzCast := ×tampzCast{cast: newCast(expression, "timestamp with time zone")} + + timestampzCast.timestampzInterfaceImpl.parent = timestampzCast + timestampzCast.expressionInterfaceImpl.parent = timestampzCast + + return timestampzCast +} diff --git a/sqlbuilder/clause.go b/sqlbuilder/clause.go index b15764c..1d8bbcc 100644 --- a/sqlbuilder/clause.go +++ b/sqlbuilder/clause.go @@ -139,11 +139,11 @@ func (q *queryData) write(data []byte) { } func isPreSeparator(b byte) bool { - return b == ' ' || b == '.' || b == ',' || b == '(' || b == '\n' + return b == ' ' || b == '.' || b == ',' || b == '(' || b == '\n' || b == ':' } func isPostSeparator(b byte) bool { - return b == ' ' || b == '.' || b == ',' || b == ')' || b == '\n' + return b == ' ' || b == '.' || b == ',' || b == ')' || b == '\n' || b == ':' } func (q *queryData) writeString(str string) { diff --git a/sqlbuilder/expression.go b/sqlbuilder/expression.go index 932c7f4..6b162cc 100644 --- a/sqlbuilder/expression.go +++ b/sqlbuilder/expression.go @@ -21,6 +21,16 @@ type expression interface { ASC() orderByClause DESC() orderByClause + + CAST_TO_BOOL() BoolExpression + CAST_TO_INTEGER() IntegerExpression + CAST_TO_DOUBLE() FloatExpression + CAST_TO_TEXT() StringExpression + CAST_TO_DATE() DateExpression + CAST_TO_TIME() TimeExpression + CAST_TO_TIMEZ() TimezExpression + CAST_TO_TIMESTAMP() TimestampExpression + CAST_TO_TIMESTAMPZ() TimestampzExpression } type expressionInterfaceImpl struct { @@ -55,6 +65,42 @@ func (e *expressionInterfaceImpl) DESC() orderByClause { return &orderByClauseImpl{expression: e.parent, ascent: false} } +func (e *expressionInterfaceImpl) CAST_TO_BOOL() BoolExpression { + return newBoolCast(e.parent) +} + +func (e *expressionInterfaceImpl) CAST_TO_INTEGER() IntegerExpression { + return newIntegerCast(e.parent) +} + +func (e *expressionInterfaceImpl) CAST_TO_DOUBLE() FloatExpression { + return newDoubleCast(e.parent) +} + +func (e *expressionInterfaceImpl) CAST_TO_TEXT() StringExpression { + return newTextCast(e.parent) +} + +func (e *expressionInterfaceImpl) CAST_TO_DATE() DateExpression { + return newDateCast(e.parent) +} + +func (e *expressionInterfaceImpl) CAST_TO_TIME() TimeExpression { + return newTimeCast(e.parent) +} + +func (e *expressionInterfaceImpl) CAST_TO_TIMEZ() TimezExpression { + return newTimezCast(e.parent) +} + +func (e *expressionInterfaceImpl) CAST_TO_TIMESTAMP() TimestampExpression { + return newTimestampCast(e.parent) +} + +func (e *expressionInterfaceImpl) CAST_TO_TIMESTAMPZ() TimestampzExpression { + return newTimestampzCast(e.parent) +} + func (e *expressionInterfaceImpl) serializeForGroupBy(statement statementType, out *queryData) error { return e.parent.serialize(statement, out, NO_WRAP) } diff --git a/sqlbuilder/expression_test.go b/sqlbuilder/expression_test.go index d440540..429410d 100644 --- a/sqlbuilder/expression_test.go +++ b/sqlbuilder/expression_test.go @@ -23,3 +23,40 @@ func TestExpressionIS_NOT_DISTINCT_FROM(t *testing.T) { assertExpressionSerialize(t, table2Col3.IS_NOT_DISTINCT_FROM(table2Col4), "(table2.col3 IS NOT DISTINCT FROM table2.col4)") assertExpressionSerialize(t, table2Col3.ADD(table2Col3).IS_NOT_DISTINCT_FROM(Int(23)), "((table2.col3 + table2.col3) IS NOT DISTINCT FROM $1)", int64(23)) } + +func TestExpressionCAST_TO_BOOL(t *testing.T) { + assertExpressionSerialize(t, table2Col3.CAST_TO_BOOL(), "table2.col3::boolean") + assertExpressionSerialize(t, table2Col3.ADD(table2Col3).CAST_TO_BOOL(), "(table2.col3 + table2.col3)::boolean") +} + +func TestExpressionCAST_TO_INTEGER(t *testing.T) { + assertExpressionSerialize(t, table2Col3.CAST_TO_INTEGER(), "table2.col3::integer") +} + +func TestExpressionCAST_TO_DOUBLE(t *testing.T) { + assertExpressionSerialize(t, table2Col3.CAST_TO_DOUBLE(), "table2.col3::double precision") +} + +func TestExpressionCAST_TO_TEXT(t *testing.T) { + assertExpressionSerialize(t, table2Col3.CAST_TO_TEXT(), "table2.col3::text") +} + +func TestExpressionCAST_TO_DATE(t *testing.T) { + assertExpressionSerialize(t, table2Col3.CAST_TO_DATE(), "table2.col3::date") +} + +func TestExpressionCAST_TO_TIME(t *testing.T) { + assertExpressionSerialize(t, table2Col3.CAST_TO_TIME(), "table2.col3::time without time zone") +} + +func TestExpressionCAST_TO_TIMEZ(t *testing.T) { + assertExpressionSerialize(t, table2Col3.CAST_TO_TIMEZ(), "table2.col3::time with time zone") +} + +func TestExpressionCAST_TO_TIMESTAMP(t *testing.T) { + assertExpressionSerialize(t, table2Col3.CAST_TO_TIMESTAMP(), "table2.col3::timestamp without time zone") +} + +func TestExpressionCAST_TO_TIMESTAMPZ(t *testing.T) { + assertExpressionSerialize(t, table2Col3.CAST_TO_TIMESTAMPZ(), "table2.col3::timestamp with time zone") +} diff --git a/sqlbuilder/literal_expression.go b/sqlbuilder/literal_expression.go index cd462a9..4269d1d 100644 --- a/sqlbuilder/literal_expression.go +++ b/sqlbuilder/literal_expression.go @@ -88,14 +88,14 @@ type timeLiteral struct { literalExpression } -func Time(hour, minute, second, milliseconds int) timeExpression { +func Time(hour, minute, second, milliseconds int) TimeExpression { timeLiteral := timeLiteral{} timeStr := fmt.Sprintf("%02d:%02d:%02d.%03d", hour, minute, second, milliseconds) timeLiteral.literalExpression = *Literal(timeStr) timeLiteral.timeInterfaceImpl.parent = &timeLiteral - return &timeLiteral + return timeLiteral.CAST_TO_TIME() } //---------------------------------------------------// @@ -104,14 +104,14 @@ type timezLiteral struct { literalExpression } -func Timez(hour, minute, second, milliseconds, timezone int) timezExpression { +func Timez(hour, minute, second, milliseconds, timezone int) TimezExpression { timezLiteral := timezLiteral{} timeStr := fmt.Sprintf("%02d:%02d:%02d.%03d %+03d", hour, minute, second, milliseconds, timezone) timezLiteral.literalExpression = *Literal(timeStr) timezLiteral.timezInterfaceImpl.parent = &timezLiteral - return &timezLiteral + return timezLiteral.CAST_TO_TIMEZ() } //---------------------------------------------------// @@ -127,7 +127,7 @@ func Timestamp(year, month, day, hour, minute, second, milliseconds int) Timesta timestampLiteral.timestampInterfaceImpl.parent = ×tampLiteral - return ×tampLiteral + return timestampLiteral.CAST_TO_TIMESTAMP() } //---------------------------------------------------// @@ -145,7 +145,7 @@ func Timestampz(year, month, day, hour, minute, second, milliseconds, timezone i timestampzLiteral.timestampzInterfaceImpl.parent = ×tampzLiteral - return ×tampzLiteral + return timestampzLiteral.CAST_TO_TIMESTAMPZ() } //---------------------------------------------------// @@ -161,5 +161,5 @@ func Date(year, month, day int) DateExpression { dateLiteral.literalExpression = *Literal(timeStr) dateLiteral.dateInterfaceImpl.parent = &dateLiteral - return &dateLiteral + return dateLiteral.CAST_TO_DATE() } diff --git a/sqlbuilder/time_expression.go b/sqlbuilder/time_expression.go index 1990374..d4c4e10 100644 --- a/sqlbuilder/time_expression.go +++ b/sqlbuilder/time_expression.go @@ -1,52 +1,52 @@ package sqlbuilder -type timeExpression interface { +type TimeExpression interface { expression - EQ(rhs timeExpression) BoolExpression - NOT_EQ(rhs timeExpression) BoolExpression - IS_DISTINCT_FROM(rhs timeExpression) BoolExpression - IS_NOT_DISTINCT_FROM(rhs timeExpression) BoolExpression + EQ(rhs TimeExpression) BoolExpression + NOT_EQ(rhs TimeExpression) BoolExpression + IS_DISTINCT_FROM(rhs TimeExpression) BoolExpression + IS_NOT_DISTINCT_FROM(rhs TimeExpression) BoolExpression - LT(rhs timeExpression) BoolExpression - LT_EQ(rhs timeExpression) BoolExpression - GT(rhs timeExpression) BoolExpression - GT_EQ(rhs timeExpression) BoolExpression + LT(rhs TimeExpression) BoolExpression + LT_EQ(rhs TimeExpression) BoolExpression + GT(rhs TimeExpression) BoolExpression + GT_EQ(rhs TimeExpression) BoolExpression } type timeInterfaceImpl struct { - parent timeExpression + parent TimeExpression } -func (t *timeInterfaceImpl) EQ(rhs timeExpression) BoolExpression { +func (t *timeInterfaceImpl) EQ(rhs TimeExpression) BoolExpression { return EQ(t.parent, rhs) } -func (t *timeInterfaceImpl) NOT_EQ(rhs timeExpression) BoolExpression { +func (t *timeInterfaceImpl) NOT_EQ(rhs TimeExpression) BoolExpression { return NOT_EQ(t.parent, rhs) } -func (t *timeInterfaceImpl) IS_DISTINCT_FROM(rhs timeExpression) BoolExpression { +func (t *timeInterfaceImpl) IS_DISTINCT_FROM(rhs TimeExpression) BoolExpression { return IS_DISTINCT_FROM(t.parent, rhs) } -func (t *timeInterfaceImpl) IS_NOT_DISTINCT_FROM(rhs timeExpression) BoolExpression { +func (t *timeInterfaceImpl) IS_NOT_DISTINCT_FROM(rhs TimeExpression) BoolExpression { return IS_NOT_DISTINCT_FROM(t.parent, rhs) } -func (t *timeInterfaceImpl) LT(rhs timeExpression) BoolExpression { +func (t *timeInterfaceImpl) LT(rhs TimeExpression) BoolExpression { return LT(t.parent, rhs) } -func (t *timeInterfaceImpl) LT_EQ(rhs timeExpression) BoolExpression { +func (t *timeInterfaceImpl) LT_EQ(rhs TimeExpression) BoolExpression { return LT_EQ(t.parent, rhs) } -func (t *timeInterfaceImpl) GT(rhs timeExpression) BoolExpression { +func (t *timeInterfaceImpl) GT(rhs TimeExpression) BoolExpression { return GT(t.parent, rhs) } -func (t *timeInterfaceImpl) GT_EQ(rhs timeExpression) BoolExpression { +func (t *timeInterfaceImpl) GT_EQ(rhs TimeExpression) BoolExpression { return GT_EQ(t.parent, rhs) } @@ -58,7 +58,7 @@ type prefixTimeExpression struct { prefixOpExpression } -func newPrefixTimeExpression(operator string, expression expression) timeExpression { +func newPrefixTimeExpression(operator string, expression expression) TimeExpression { timeExpr := prefixTimeExpression{} timeExpr.prefixOpExpression = newPrefixExpression(expression, operator) diff --git a/sqlbuilder/timez_expression.go b/sqlbuilder/timez_expression.go index dbb38c6..f8cd72a 100644 --- a/sqlbuilder/timez_expression.go +++ b/sqlbuilder/timez_expression.go @@ -1,52 +1,52 @@ package sqlbuilder -type timezExpression interface { +type TimezExpression interface { expression - EQ(rhs timezExpression) BoolExpression - NOT_EQ(rhs timezExpression) BoolExpression - IS_DISTINCT_FROM(rhs timezExpression) BoolExpression - IS_NOT_DISTINCT_FROM(rhs timezExpression) BoolExpression + EQ(rhs TimezExpression) BoolExpression + NOT_EQ(rhs TimezExpression) BoolExpression + IS_DISTINCT_FROM(rhs TimezExpression) BoolExpression + IS_NOT_DISTINCT_FROM(rhs TimezExpression) BoolExpression - LT(rhs timezExpression) BoolExpression - LT_EQ(rhs timezExpression) BoolExpression - GT(rhs timezExpression) BoolExpression - GT_EQ(rhs timezExpression) BoolExpression + LT(rhs TimezExpression) BoolExpression + LT_EQ(rhs TimezExpression) BoolExpression + GT(rhs TimezExpression) BoolExpression + GT_EQ(rhs TimezExpression) BoolExpression } type timezInterfaceImpl struct { - parent timezExpression + parent TimezExpression } -func (t *timezInterfaceImpl) EQ(rhs timezExpression) BoolExpression { +func (t *timezInterfaceImpl) EQ(rhs TimezExpression) BoolExpression { return EQ(t.parent, rhs) } -func (t *timezInterfaceImpl) NOT_EQ(rhs timezExpression) BoolExpression { +func (t *timezInterfaceImpl) NOT_EQ(rhs TimezExpression) BoolExpression { return NOT_EQ(t.parent, rhs) } -func (t *timezInterfaceImpl) IS_DISTINCT_FROM(rhs timezExpression) BoolExpression { +func (t *timezInterfaceImpl) IS_DISTINCT_FROM(rhs TimezExpression) BoolExpression { return IS_DISTINCT_FROM(t.parent, rhs) } -func (t *timezInterfaceImpl) IS_NOT_DISTINCT_FROM(rhs timezExpression) BoolExpression { +func (t *timezInterfaceImpl) IS_NOT_DISTINCT_FROM(rhs TimezExpression) BoolExpression { return IS_NOT_DISTINCT_FROM(t.parent, rhs) } -func (t *timezInterfaceImpl) LT(rhs timezExpression) BoolExpression { +func (t *timezInterfaceImpl) LT(rhs TimezExpression) BoolExpression { return LT(t.parent, rhs) } -func (t *timezInterfaceImpl) LT_EQ(rhs timezExpression) BoolExpression { +func (t *timezInterfaceImpl) LT_EQ(rhs TimezExpression) BoolExpression { return LT_EQ(t.parent, rhs) } -func (t *timezInterfaceImpl) GT(rhs timezExpression) BoolExpression { +func (t *timezInterfaceImpl) GT(rhs TimezExpression) BoolExpression { return GT(t.parent, rhs) } -func (t *timezInterfaceImpl) GT_EQ(rhs timezExpression) BoolExpression { +func (t *timezInterfaceImpl) GT_EQ(rhs TimezExpression) BoolExpression { return GT_EQ(t.parent, rhs) } @@ -58,7 +58,7 @@ type prefixTimezExpression struct { prefixOpExpression } -func newPrefixTimezExpression(operator string, expression expression) timezExpression { +func newPrefixTimezExpression(operator string, expression expression) TimezExpression { timeExpr := prefixTimezExpression{} timeExpr.prefixOpExpression = newPrefixExpression(expression, operator) diff --git a/tests/types_test.go b/tests/types_test.go index de43514..ffe1d41 100644 --- a/tests/types_test.go +++ b/tests/types_test.go @@ -30,6 +30,16 @@ func TestExpressionOperators(t *testing.T) { AllTypes.Integer.IS_NULL(), AllTypes.Timestamp.IS_NOT_NULL(), + String("TRUE").CAST_TO_BOOL(), + String("111").CAST_TO_INTEGER(), + String("11.23").CAST_TO_DOUBLE(), + Int(234).CAST_TO_TEXT(), + String("1/8/1999").CAST_TO_DATE(), + String("04:05:06.789").CAST_TO_TIME(), + String("04:05:06 PST").CAST_TO_TIMEZ(), + String("1999-01-08 04:05:06").CAST_TO_TIMESTAMP(), + String("January 8 04:05:06 1999 PST").CAST_TO_TIMESTAMPZ(), + TO_CHAR(AllTypes.Timestamp, String("HH12:MI:SS")), TO_CHAR(AllTypes.Integer, String("999")), TO_CHAR(AllTypes.DoublePrecision, String("999D9")),