init - add project files

This commit is contained in:
2025-03-06 23:54:11 -05:00
commit e724ff1120
1363 changed files with 897467 additions and 0 deletions

View File

@@ -0,0 +1,132 @@
package postgres
import (
"fmt"
"strconv"
"github.com/go-jet/jet/v2/internal/jet"
)
type cast struct {
jet.Cast
}
// CAST function converts an expr (of any type) into later specified datatype.
func CAST(expr Expression) *cast {
ret := &cast{}
ret.Cast = jet.NewCastImpl(expr)
return ret
}
// AS casts expression as castType
func (b *cast) AS(castType string) Expression {
return b.Cast.AS(castType)
}
// AS_BOOL casts expression as bool type
func (b *cast) AS_BOOL() BoolExpression {
return BoolExp(b.AS("boolean"))
}
// AS_SMALLINT casts expression as smallint type
func (b *cast) AS_SMALLINT() IntegerExpression {
return IntExp(b.AS("smallint"))
}
// AS_INTEGER casts expression AS integer type
func (b *cast) AS_INTEGER() IntegerExpression {
return IntExp(b.AS("integer"))
}
// AS_BIGINT casts expression AS bigint type
func (b *cast) AS_BIGINT() IntegerExpression {
return IntExp(b.AS("bigint"))
}
// AS_NUMERIC casts expression as numeric type, using precision and optionally scale
func (b *cast) AS_NUMERIC(precisionAndScale ...int) FloatExpression {
var castArgs string
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 FloatExp(b.AS("numeric" + castArgs))
}
// AS_REAL casts expression AS real type
func (b *cast) AS_REAL() FloatExpression {
return FloatExp(b.AS("real"))
}
// AS_DOUBLE casts expression AS double precision type
func (b *cast) AS_DOUBLE() FloatExpression {
return FloatExp(b.AS("double precision"))
}
// AS_TEXT casts expression AS text type
func (b *cast) AS_TEXT() StringExpression {
return StringExp(b.AS("text"))
}
// AS_CHAR casts expression AS a character type
func (b *cast) AS_CHAR(length ...int) StringExpression {
if len(length) > 0 {
return StringExp(b.AS("char(" + strconv.Itoa(length[0]) + ")"))
}
return StringExp(b.AS("char"))
}
// AS_VARCHAR casts expression AS a character varying type
func (b *cast) AS_VARCHAR(length ...int) StringExpression {
if len(length) > 0 {
return StringExp(b.AS("varchar(" + strconv.Itoa(length[0]) + ")"))
}
return StringExp(b.AS("varchar"))
}
// AS_DATE casts expression AS date type
func (b *cast) AS_DATE() DateExpression {
return DateExp(b.AS("date"))
}
// AS_DECIMAL casts expression AS date type
func (b *cast) AS_DECIMAL() FloatExpression {
return FloatExp(b.AS("decimal"))
}
// AS_BYTEA casts expression AS text type
func (b *cast) AS_BYTEA() StringExpression {
return StringExp(b.AS("bytea"))
}
// AS_TIME casts expression AS date type
func (b *cast) AS_TIME() TimeExpression {
return TimeExp(b.AS("time without time zone"))
}
// AS_TIMEZ casts expression AS time with time timezone type
func (b *cast) AS_TIMEZ() TimezExpression {
return TimezExp(b.AS("time with time zone"))
}
// AS_TIMESTAMP casts expression AS timestamp type
func (b *cast) AS_TIMESTAMP() TimestampExpression {
return TimestampExp(b.AS("timestamp without time zone"))
}
// AS_TIMESTAMPZ casts expression AS timestamp with timezone type
func (b *cast) AS_TIMESTAMPZ() TimestampzExpression {
return TimestampzExp(b.AS("timestamp with time zone"))
}
// AS_INTERVAL casts expression AS interval type
func (b *cast) AS_INTERVAL() IntervalExpression {
return IntervalExp(b.AS("interval"))
}

View File

@@ -0,0 +1,71 @@
package postgres
import (
"testing"
)
func TestExpressionCAST_AS(t *testing.T) {
assertSerialize(t, CAST(Int(11)).AS("text"), `$1::text`, int64(11))
}
func TestExpressionCAST_AS_BOOL(t *testing.T) {
assertSerialize(t, CAST(Int(1)).AS_BOOL(), "$1::boolean", int64(1))
assertSerialize(t, CAST(table2Col3).AS_BOOL(), "table2.col3::boolean")
assertSerialize(t, CAST(table2Col3.ADD(table2Col3)).AS_BOOL(), "(table2.col3 + table2.col3)::boolean")
}
func TestExpressionCAST_AS_SMALLINT(t *testing.T) {
assertSerialize(t, CAST(table2Col3).AS_SMALLINT(), "table2.col3::smallint")
}
func TestExpressionCAST_AS_INTEGER(t *testing.T) {
assertSerialize(t, CAST(table2Col3).AS_INTEGER(), "table2.col3::integer")
}
func TestExpressionCAST_AS_BIGINT(t *testing.T) {
assertSerialize(t, CAST(table2Col3).AS_BIGINT(), "table2.col3::bigint")
}
func TestExpressionCAST_AS_NUMERIC(t *testing.T) {
assertSerialize(t, CAST(table2Col3).AS_NUMERIC(11, 11), "table2.col3::numeric(11, 11)")
assertSerialize(t, CAST(table2Col3).AS_NUMERIC(11), "table2.col3::numeric(11)")
}
func TestExpressionCAST_AS_REAL(t *testing.T) {
assertSerialize(t, CAST(table2Col3).AS_REAL(), "table2.col3::real")
}
func TestExpressionCAST_AS_DOUBLE(t *testing.T) {
assertSerialize(t, CAST(table2Col3).AS_DOUBLE(), "table2.col3::double precision")
}
func TestExpressionCAST_AS_TEXT(t *testing.T) {
assertSerialize(t, CAST(table2Col3).AS_TEXT(), "table2.col3::text")
}
func TestExpressionCAST_AS_DATE(t *testing.T) {
assertSerialize(t, CAST(table2Col3).AS_DATE(), "table2.col3::date")
}
func TestExpressionCAST_AS_TIME(t *testing.T) {
assertSerialize(t, CAST(table2Col3).AS_TIME(), "table2.col3::time without time zone")
}
func TestExpressionCAST_AS_TIMEZ(t *testing.T) {
assertSerialize(t, CAST(table2Col3).AS_TIMEZ(), "table2.col3::time with time zone")
}
func TestExpressionCAST_AS_TIMESTAMP(t *testing.T) {
assertSerialize(t, CAST(table2Col3).AS_TIMESTAMP(), "table2.col3::timestamp without time zone")
}
func TestExpressionCAST_AS_TIMESTAMPZ(t *testing.T) {
assertSerialize(t, CAST(table2Col3).AS_TIMESTAMPZ(), "table2.col3::timestamp with time zone")
}
func TestExpressionCAST_AS_INTERVAL(t *testing.T) {
assertSerialize(t, CAST(table2ColTimez).AS_INTERVAL(), "table2.col_timez::interval")
assertSerialize(t, CAST(Time(20, 11, 10)).AS_INTERVAL(), "$1::time without time zone::interval", "20:11:10")
assertSerialize(t, table2ColDate.SUB(CAST(Time(20, 11, 10)).AS_INTERVAL()),
"(table2.col_date - $1::time without time zone::interval)", "20:11:10")
}

View File

@@ -0,0 +1,70 @@
package postgres
import (
"github.com/go-jet/jet/v2/internal/jet"
"github.com/go-jet/jet/v2/internal/utils/is"
)
type onConflict interface {
ON_CONSTRAINT(name string) conflictTarget
WHERE(indexPredicate BoolExpression) conflictTarget
conflictTarget
}
type conflictTarget interface {
DO_NOTHING() InsertStatement
DO_UPDATE(action conflictAction) InsertStatement
}
type onConflictClause struct {
insertStatement InsertStatement
constraint string
indexExpressions []jet.ColumnExpression
whereClause jet.ClauseWhere
do jet.Serializer
}
func (o *onConflictClause) ON_CONSTRAINT(name string) conflictTarget {
o.constraint = name
return o
}
func (o *onConflictClause) WHERE(indexPredicate BoolExpression) conflictTarget {
o.whereClause.Condition = indexPredicate
return o
}
func (o *onConflictClause) DO_NOTHING() InsertStatement {
o.do = jet.Keyword("DO NOTHING")
return o.insertStatement
}
func (o *onConflictClause) DO_UPDATE(action conflictAction) InsertStatement {
o.do = action
return o.insertStatement
}
func (o *onConflictClause) Serialize(statementType jet.StatementType, out *jet.SQLBuilder, options ...jet.SerializeOption) {
if is.Nil(o.do) {
return
}
out.NewLine()
out.WriteString("ON CONFLICT")
if len(o.indexExpressions) > 0 {
out.WriteString("(")
jet.SerializeColumnExpressions(o.indexExpressions, statementType, out, jet.ShortName)
out.WriteString(")")
}
if o.constraint != "" {
out.WriteString("ON CONSTRAINT")
out.WriteString(o.constraint)
}
o.whereClause.Serialize(statementType, out, jet.SkipNewLine, jet.ShortName)
out.IncreaseIdent(7)
jet.Serialize(o.do, statementType, out)
out.DecreaseIdent(7)
}

View File

@@ -0,0 +1,36 @@
package postgres
import "testing"
func TestOnConflict(t *testing.T) {
assertClauseSerialize(t, &onConflictClause{}, "")
onConflict := &onConflictClause{}
onConflict.DO_NOTHING()
assertClauseSerialize(t, onConflict, `
ON CONFLICT DO NOTHING`)
onConflict = &onConflictClause{indexExpressions: ColumnList{table1ColBool}}
onConflict.DO_NOTHING()
assertClauseSerialize(t, onConflict, `
ON CONFLICT (col_bool) DO NOTHING`)
onConflict = &onConflictClause{indexExpressions: ColumnList{table1ColBool}}
onConflict.ON_CONSTRAINT("table_pkey").DO_NOTHING()
assertClauseSerialize(t, onConflict, `
ON CONFLICT (col_bool) ON CONSTRAINT table_pkey DO NOTHING`)
onConflict = &onConflictClause{indexExpressions: ColumnList{table1ColBool, table2ColFloat}}
onConflict.WHERE(table2ColFloat.ADD(table1ColInt).GT(table1ColFloat)).
DO_UPDATE(
SET(table1ColBool.SET(Bool(true)),
table1ColInt.SET(Int(11))).
WHERE(table2ColFloat.GT(Float(11.1))),
)
assertClauseSerialize(t, onConflict, `
ON CONFLICT (col_bool, col_float) WHERE (col_float + col_int) > col_float DO UPDATE
SET col_bool = $1::boolean,
col_int = $2
WHERE table2.col_float > $3`)
}

View File

@@ -0,0 +1,140 @@
package postgres
import (
"github.com/go-jet/jet/v2/internal/jet"
)
// Column is common column interface for all types of columns.
type Column = jet.ColumnExpression
// ColumnList function returns list of columns that be used as projection or column list for UPDATE and INSERT statement.
type ColumnList = jet.ColumnList
// ColumnBool is interface for SQL boolean columns.
type ColumnBool = jet.ColumnBool
// BoolColumn creates named bool column.
var BoolColumn = jet.BoolColumn
// ColumnString is interface for SQL text, character, character varying
// bytea, uuid columns and enums types.
type ColumnString = jet.ColumnString
// StringColumn creates named string column.
var StringColumn = jet.StringColumn
// ColumnInteger is interface for SQL smallint, integer, bigint columns.
type ColumnInteger = jet.ColumnInteger
// IntegerColumn creates named integer column.
var IntegerColumn = jet.IntegerColumn
// ColumnFloat is interface for SQL real, numeric, decimal or double precision column.
type ColumnFloat = jet.ColumnFloat
// FloatColumn creates named float column.
var FloatColumn = jet.FloatColumn
// ColumnDate is interface of SQL date columns.
type ColumnDate = jet.ColumnDate
// DateColumn creates named date column.
var DateColumn = jet.DateColumn
// ColumnTime is interface for SQL time column.
type ColumnTime = jet.ColumnTime
// TimeColumn creates named time column
var TimeColumn = jet.TimeColumn
// ColumnTimez is interface of SQL time with time zone columns.
type ColumnTimez = jet.ColumnTimez
// TimezColumn creates named time with time zone column.
var TimezColumn = jet.TimezColumn
// ColumnTimestamp is interface of SQL timestamp columns.
type ColumnTimestamp = jet.ColumnTimestamp
// TimestampColumn creates named timestamp column
var TimestampColumn = jet.TimestampColumn
// ColumnTimestampz is interface of SQL timestamp with timezone columns.
type ColumnTimestampz = jet.ColumnTimestampz
// TimestampzColumn creates named timestamp with time zone column.
var TimestampzColumn = jet.TimestampzColumn
// ColumnDateRange is interface of SQL date range column
type ColumnDateRange = jet.ColumnRange[DateExpression]
// DateRangeColumn creates named range with range column
var DateRangeColumn = jet.RangeColumn[DateExpression]
// ColumnNumericRange is interface of SQL numeric range column
type ColumnNumericRange = jet.ColumnRange[NumericExpression]
// NumericRangeColumn creates named range with range column
var NumericRangeColumn = jet.RangeColumn[NumericExpression]
// ColumnTimestampRange is interface of SQL timestamp range column
type ColumnTimestampRange = jet.ColumnRange[TimestampExpression]
// TimestampRangeColumn creates named range with range column
var TimestampRangeColumn = jet.RangeColumn[TimestampExpression]
// ColumnTimestampzRange is interface of SQL timestamp range column
type ColumnTimestampzRange = jet.ColumnRange[TimestampzExpression]
// TimestampzRangeColumn creates named range with range column
var TimestampzRangeColumn = jet.RangeColumn[TimestampzExpression]
// ColumnInt4Range is interface of SQL int4 range column
type ColumnInt4Range jet.ColumnRange[jet.Int4Expression]
// Int4RangeColumn creates named range with range column
var Int4RangeColumn = jet.RangeColumn[jet.Int4Expression]
// ColumnInt8Range is interface of SQL int8 range column
type ColumnInt8Range jet.ColumnRange[jet.Int8Expression]
// Int8RangeColumn creates named range with range column
var Int8RangeColumn = jet.RangeColumn[jet.Int8Expression]
//------------------------------------------------------//
// ColumnInterval is interface of PostgreSQL interval columns.
type ColumnInterval interface {
IntervalExpression
jet.Column
From(subQuery SelectTable) ColumnInterval
SET(intervalExp IntervalExpression) ColumnAssigment
}
//------------------------------------------------------//
type intervalColumnImpl struct {
jet.ColumnExpressionImpl
intervalInterfaceImpl
}
func (i *intervalColumnImpl) SET(intervalExp IntervalExpression) ColumnAssigment {
return jet.NewColumnAssignment(i, intervalExp)
}
func (i *intervalColumnImpl) From(subQuery SelectTable) ColumnInterval {
newIntervalColumn := IntervalColumn(i.Name())
jet.SetTableName(newIntervalColumn, i.TableName())
jet.SetSubQuery(newIntervalColumn, subQuery)
return newIntervalColumn
}
// IntervalColumn creates named interval column.
func IntervalColumn(name string) ColumnInterval {
intervalColumn := &intervalColumnImpl{}
intervalColumn.ColumnExpressionImpl = jet.NewColumnImpl(name, "", intervalColumn)
intervalColumn.intervalInterfaceImpl.parent = intervalColumn
return intervalColumn
}

View File

@@ -0,0 +1,20 @@
package postgres
import (
"testing"
)
func TestNewIntervalColumn(t *testing.T) {
subQuery := SELECT(Int(1)).AsTable("sub_query")
subQueryIntervalColumn := IntervalColumn("col_interval").From(subQuery)
assertSerialize(t, subQueryIntervalColumn, `sub_query.col_interval`)
assertSerialize(t, subQueryIntervalColumn.EQ(INTERVAL(2, HOUR, 10, MINUTE)),
`(sub_query.col_interval = INTERVAL '2 HOUR 10 MINUTE')`)
assertProjectionSerialize(t, subQueryIntervalColumn, `sub_query.col_interval AS "col_interval"`)
subQueryIntervalColumn2 := table1ColInterval.From(subQuery)
assertSerialize(t, subQueryIntervalColumn2, `sub_query."table1.col_interval"`)
assertSerialize(t, subQueryIntervalColumn2.EQ(INTERVAL(1, DAY)), `(sub_query."table1.col_interval" = INTERVAL '1 DAY')`)
assertProjectionSerialize(t, subQueryIntervalColumn2, `sub_query."table1.col_interval" AS "table1.col_interval"`)
}

View File

@@ -0,0 +1,30 @@
package postgres
import "github.com/go-jet/jet/v2/internal/jet"
type conflictAction interface {
jet.Serializer
WHERE(condition BoolExpression) conflictAction
}
// SET creates conflict action for ON_CONFLICT clause
func SET(assigments ...ColumnAssigment) conflictAction {
conflictAction := updateConflictActionImpl{}
conflictAction.doUpdate = jet.KeywordClause{Keyword: "DO UPDATE"}
conflictAction.Serializer = jet.NewSerializerClauseImpl(&conflictAction.doUpdate, &conflictAction.set, &conflictAction.where)
conflictAction.set = assigments
return &conflictAction
}
type updateConflictActionImpl struct {
jet.Serializer
doUpdate jet.KeywordClause
set jet.SetClauseNew
where jet.ClauseWhere
}
func (u *updateConflictActionImpl) WHERE(condition BoolExpression) conflictAction {
u.where.Condition = condition
return u
}

View File

@@ -0,0 +1,51 @@
package postgres
import "github.com/go-jet/jet/v2/internal/jet"
// DeleteStatement is interface for PostgreSQL DELETE statement
type DeleteStatement interface {
jet.SerializerStatement
USING(tables ...ReadableTable) DeleteStatement
WHERE(expression BoolExpression) DeleteStatement
RETURNING(projections ...jet.Projection) DeleteStatement
}
type deleteStatementImpl struct {
jet.SerializerStatement
Delete jet.ClauseDelete
Using jet.ClauseFrom
Where jet.ClauseWhere
Returning jet.ClauseReturning
}
func newDeleteStatement(table WritableTable) DeleteStatement {
newDelete := &deleteStatementImpl{}
newDelete.SerializerStatement = jet.NewStatementImpl(Dialect, jet.DeleteStatementType, newDelete,
&newDelete.Delete,
&newDelete.Using,
&newDelete.Where,
&newDelete.Returning)
newDelete.Delete.Table = table
newDelete.Using.Name = "USING"
newDelete.Where.Mandatory = true
return newDelete
}
func (d *deleteStatementImpl) USING(tables ...ReadableTable) DeleteStatement {
d.Using.Tables = readableTablesToSerializerList(tables)
return d
}
func (d *deleteStatementImpl) WHERE(expression BoolExpression) DeleteStatement {
d.Where.Condition = expression
return d
}
func (d *deleteStatementImpl) RETURNING(projections ...jet.Projection) DeleteStatement {
d.Returning.ProjectionList = projections
return d
}

View File

@@ -0,0 +1,25 @@
package postgres
import (
"testing"
)
func TestDeleteUnconditionally(t *testing.T) {
assertStatementSqlErr(t, table1.DELETE(), `jet: WHERE clause not set`)
assertStatementSqlErr(t, table1.DELETE().WHERE(nil), `jet: WHERE clause not set`)
}
func TestDeleteWithWhere(t *testing.T) {
assertStatementSql(t, table1.DELETE().WHERE(table1Col1.EQ(Int(1))), `
DELETE FROM db.table1
WHERE table1.col1 = $1;
`, int64(1))
}
func TestDeleteWithWhereAndReturning(t *testing.T) {
assertStatementSql(t, table1.DELETE().WHERE(table1Col1.EQ(Int(1))).RETURNING(table1Col1), `
DELETE FROM db.table1
WHERE table1.col1 = $1
RETURNING table1.col1 AS "table1.col1";
`, int64(1))
}

View File

@@ -0,0 +1,193 @@
package postgres
import (
"fmt"
"github.com/go-jet/jet/v2/internal/jet"
"strconv"
)
// Dialect is implementation of postgres dialect for SQL Builder serialisation.
var Dialect = newDialect()
func newDialect() jet.Dialect {
operatorSerializeOverrides := map[string]jet.SerializeOverride{}
operatorSerializeOverrides[jet.StringRegexpLikeOperator] = postgresREGEXPLIKEoperator
operatorSerializeOverrides[jet.StringNotRegexpLikeOperator] = postgresNOTREGEXPLIKEoperator
operatorSerializeOverrides["CAST"] = postgresCAST
dialectParams := jet.DialectParams{
Name: "PostgreSQL",
PackageName: "postgres",
OperatorSerializeOverrides: operatorSerializeOverrides,
AliasQuoteChar: '"',
IdentifierQuoteChar: '"',
ArgumentPlaceholder: func(ord int) string {
return "$" + strconv.Itoa(ord)
},
ReservedWords: reservedWords,
ValuesDefaultColumnName: func(index int) string {
return fmt.Sprintf("column%d", index+1)
},
}
return jet.NewDialect(dialectParams)
}
func postgresCAST(expressions ...jet.Serializer) jet.SerializerFunc {
return func(statement jet.StatementType, out *jet.SQLBuilder, options ...jet.SerializeOption) {
if len(expressions) < 2 {
panic("jet: invalid number of expressions for operator")
}
expression := expressions[0]
litExpr, ok := expressions[1].(jet.LiteralExpression)
if !ok {
panic("jet: cast invalid cast type")
}
castType, ok := litExpr.Value().(string)
if !ok {
panic("jet: cast type is not string")
}
jet.Serialize(expression, statement, out, options...)
out.WriteString("::" + castType)
}
}
func postgresREGEXPLIKEoperator(expressions ...jet.Serializer) jet.SerializerFunc {
return func(statement jet.StatementType, out *jet.SQLBuilder, options ...jet.SerializeOption) {
if len(expressions) < 2 {
panic("jet: invalid number of expressions for operator")
}
jet.Serialize(expressions[0], statement, out, options...)
caseSensitive := false
if len(expressions) >= 3 {
if stringLiteral, ok := expressions[2].(jet.LiteralExpression); ok {
caseSensitive = stringLiteral.Value().(bool)
}
}
if caseSensitive {
out.WriteString("~")
} else {
out.WriteString("~*")
}
jet.Serialize(expressions[1], statement, out, options...)
}
}
func postgresNOTREGEXPLIKEoperator(expressions ...jet.Serializer) jet.SerializerFunc {
return func(statement jet.StatementType, out *jet.SQLBuilder, options ...jet.SerializeOption) {
if len(expressions) < 2 {
panic("jet: invalid number of expressions for operator")
}
jet.Serialize(expressions[0], statement, out, options...)
caseSensitive := false
if len(expressions) >= 3 {
if stringLiteral, ok := expressions[2].(jet.LiteralExpression); ok {
caseSensitive = stringLiteral.Value().(bool)
}
}
if caseSensitive {
out.WriteString("!~")
} else {
out.WriteString("!~*")
}
jet.Serialize(expressions[1], statement, out, options...)
}
}
var reservedWords = []string{
"ALL",
"ANALYSE",
"ANALYZE",
"AND",
"ANY",
"ARRAY",
"AS",
"ASC",
"ASYMMETRIC",
"BOTH",
"CASE",
"CAST",
"CHECK",
"COLLATE",
"COLUMN",
"CONSTRAINT",
"CREATE",
"CURRENT_CATALOG",
"CURRENT_DATE",
"CURRENT_ROLE",
"CURRENT_TIME",
"CURRENT_TIMESTAMP",
"CURRENT_USER",
"DEFAULT",
"DEFERRABLE",
"DESC",
"DISTINCT",
"DO",
"ELSE",
"END",
"EXCEPT",
"FALSE",
"FETCH",
"FOR",
"FOREIGN",
"FROM",
"GRANT",
"GROUP",
"HAVING",
"IN",
"INITIALLY",
"INTERSECT",
"INTO",
"LATERAL",
"LEADING",
"LIMIT",
"LOCALTIME",
"LOCALTIMESTAMP",
"NOT",
"NULL",
"OFFSET",
"ON",
"ONLY",
"OR",
"ORDER",
"PLACING",
"PRIMARY",
"REFERENCES",
"RETURNING",
"RIGHT",
"SELECT",
"SESSION_USER",
"SOME",
"SYMMETRIC",
"TABLE",
"THEN",
"TO",
"TRAILING",
"TRUE",
"UNION",
"UNIQUE",
"USER",
"USING",
"VARIADIC",
"WHEN",
"WHERE",
"WINDOW",
"WITH",
}

View File

@@ -0,0 +1,88 @@
package postgres
import "testing"
func TestString_REGEXP_LIKE_operator(t *testing.T) {
assertSerialize(t, table3StrCol.REGEXP_LIKE(table2ColStr), "(table3.col2 ~* table2.col_str)")
assertSerialize(t, table3StrCol.REGEXP_LIKE(String("JOHN")), "(table3.col2 ~* $1::text)", "JOHN")
assertSerialize(t, table3StrCol.REGEXP_LIKE(String("JOHN"), false), "(table3.col2 ~* $1::text)", "JOHN")
assertSerialize(t, table3StrCol.REGEXP_LIKE(String("JOHN"), true), "(table3.col2 ~ $1::text)", "JOHN")
}
func TestString_NOT_REGEXP_LIKE_operator(t *testing.T) {
assertSerialize(t, table3StrCol.NOT_REGEXP_LIKE(table2ColStr), "(table3.col2 !~* table2.col_str)")
assertSerialize(t, table3StrCol.NOT_REGEXP_LIKE(String("JOHN")), "(table3.col2 !~* $1::text)", "JOHN")
assertSerialize(t, table3StrCol.NOT_REGEXP_LIKE(String("JOHN"), false), "(table3.col2 !~* $1::text)", "JOHN")
assertSerialize(t, table3StrCol.NOT_REGEXP_LIKE(String("JOHN"), true), "(table3.col2 !~ $1::text)", "JOHN")
}
func TestExists(t *testing.T) {
assertSerialize(t, EXISTS(
table2.
SELECT(Int(1)).
WHERE(table1Col1.EQ(table2Col3)),
),
`(EXISTS (
SELECT $1
FROM db.table2
WHERE table1.col1 = table2.col3
))`, int64(1))
assertSerialize(t, EXISTS(
SELECT(Int(1)),
).EQ(Bool(true)),
`((EXISTS (
SELECT $1
)) = $2::boolean)`, int64(1), true)
assertProjectionSerialize(t, EXISTS(
SELECT(Int(1)),
).AS("exists"),
`(EXISTS (
SELECT $1
)) AS "exists"`, int64(1))
}
func TestIN(t *testing.T) {
assertSerialize(t, Float(1.11).IN(table1.SELECT(table1Col1)),
`($1 IN ((
SELECT table1.col1 AS "table1.col1"
FROM db.table1
)))`, float64(1.11))
assertSerialize(t, ROW(Int(12), table1Col1).IN(table2.SELECT(table2Col3, table3Col1)),
`(ROW($1, table1.col1) IN ((
SELECT table2.col3 AS "table2.col3",
table3.col1 AS "table3.col1"
FROM db.table2
)))`, int64(12))
}
func TestNOT_IN(t *testing.T) {
assertSerialize(t, Float(1.11).NOT_IN(table1.SELECT(table1Col1)),
`($1 NOT IN ((
SELECT table1.col1 AS "table1.col1"
FROM db.table1
)))`, float64(1.11))
assertSerialize(t, ROW(Int(12), table1Col1).NOT_IN(table2.SELECT(table2Col3, table3Col1)),
`(ROW($1, table1.col1) NOT IN ((
SELECT table2.col3 AS "table2.col3",
table3.col1 AS "table3.col1"
FROM db.table2
)))`, int64(12))
}
func TestReservedWordEscaped(t *testing.T) {
var table1ColUser = IntervalColumn("user")
var table1ColVariadic = IntervalColumn("VARIADIC")
var table1ColProcedure = IntervalColumn("procedure")
_ = NewTable("db", "table1", "", table1ColUser, table1ColVariadic, table1ColProcedure)
assertSerialize(t, table1ColUser, `table1."user"`)
assertSerialize(t, table1ColVariadic, `table1."VARIADIC"`)
assertSerialize(t, table1ColProcedure, `table1.procedure`)
}

View File

@@ -0,0 +1,161 @@
package postgres
import "github.com/go-jet/jet/v2/internal/jet"
// Expression is common interface for all expressions.
// Can be Bool, Int, Float, String, Date, Time, Timez, Timestamp or Timestampz expressions.
type Expression = jet.Expression
// BoolExpression interface
type BoolExpression = jet.BoolExpression
// StringExpression interface
type StringExpression = jet.StringExpression
// NumericExpression interface
type NumericExpression = jet.NumericExpression
// IntegerExpression interface
type IntegerExpression = jet.IntegerExpression
// FloatExpression is interface
type FloatExpression = jet.FloatExpression
// TimeExpression interface
type TimeExpression = jet.TimeExpression
// TimezExpression interface for 'time with time zone' types
type TimezExpression = jet.TimezExpression
// DateExpression is interface for date types
type DateExpression = jet.DateExpression
// TimestampExpression interface
type TimestampExpression = jet.TimestampExpression
// TimestampzExpression interface
type TimestampzExpression = jet.TimestampzExpression
// RowExpression interface
type RowExpression = jet.RowExpression
// DateRange Expression interface
type DateRange = jet.Range[DateExpression]
// TimestampRange Expression interface
type TimestampRange = jet.Range[TimestampExpression]
// TimestampzRange Expression interface
type TimestampzRange = jet.Range[TimestampzExpression]
// NumericRange Expression interface
type NumericRange = jet.Range[NumericExpression]
// Int4Range Expression interface
type Int4Range = jet.Range[IntegerExpression]
// Int8Range Expression interface
type Int8Range = jet.Range[IntegerExpression]
// BoolExp is bool expression wrapper around arbitrary expression.
// Allows go compiler to see any expression as bool expression.
// Does not add sql cast to generated sql builder output.
var BoolExp = jet.BoolExp
// IntExp is int expression wrapper around arbitrary expression.
// Allows go compiler to see any expression as int expression.
// Does not add sql cast to generated sql builder output.
var IntExp = jet.IntExp
// FloatExp is date expression wrapper around arbitrary expression.
// Allows go compiler to see any expression as float expression.
// Does not add sql cast to generated sql builder output.
var FloatExp = jet.FloatExp
// TimeExp is time expression wrapper around arbitrary expression.
// Allows go compiler to see any expression as time expression.
// Does not add sql cast to generated sql builder output.
var TimeExp = jet.TimeExp
// StringExp is string expression wrapper around arbitrary expression.
// Allows go compiler to see any expression as string expression.
// Does not add sql cast to generated sql builder output.
var StringExp = jet.StringExp
// TimezExp is time with time zone expression wrapper around arbitrary expression.
// Allows go compiler to see any expression as time with time zone expression.
// Does not add sql cast to generated sql builder output.
var TimezExp = jet.TimezExp
// DateExp is date expression wrapper around arbitrary expression.
// Allows go compiler to see any expression as date expression.
// Does not add sql cast to generated sql builder output.
var DateExp = jet.DateExp
// TimestampExp is timestamp expression wrapper around arbitrary expression.
// Allows go compiler to see any expression as timestamp expression.
// Does not add sql cast to generated sql builder output.
var TimestampExp = jet.TimestampExp
// TimestampzExp is timestamp with time zone expression wrapper around arbitrary expression.
// Allows go compiler to see any expression as timestamp with time zone expression.
// Does not add sql cast to generated sql builder output.
var TimestampzExp = jet.TimestampzExp
// RowExp serves as a wrapper for an arbitrary expression, treating it as a row expression.
// This enables the Go compiler to interpret any expression as a row expression
// Note: This does not modify the generated SQL builder output by adding a SQL CAST operation.
var RowExp = jet.RowExp
// RangeExp is range expression wrapper around arbitrary expression.
// Allows go compiler to see any expression as range expression.
// Does not add sql cast to generated sql builder output.
var (
Int4RangeExp = jet.Int4RangeExp
Int8RangeExp = jet.Int8RangeExp
NumRangeExp = jet.NumRangeExp
DateRangeExp = jet.DateRangeExp
TsRangeExp = jet.TsRangeExp
TstzRangeExp = jet.TstzRangeExp
)
// CustomExpression is used to define custom expressions.
var CustomExpression = jet.CustomExpression
// Token is used to define custom token in a custom expression.
type Token = jet.Token
// RawArgs is type used to pass optional arguments to Raw method
type RawArgs = map[string]interface{}
// Raw can be used for any unsupported functions, operators or expressions.
// For example: Raw("current_database()")
// Raw helper methods for each of the postgres types
var (
Raw = jet.Raw
RawBool = jet.RawBool
RawInt = jet.RawInt
RawFloat = jet.RawFloat
RawString = jet.RawString
RawTime = jet.RawTime
RawTimez = jet.RawTimez
RawTimestamp = jet.RawTimestamp
RawTimestampz = jet.RawTimestampz
RawDate = jet.RawDate
RawNumRange = jet.RawRange[jet.NumericExpression]
RawInt4Range = jet.RawRange[jet.Int4Expression]
RawInt8Range = jet.RawRange[jet.Int8Expression]
RawTimestampRange = jet.RawRange[jet.TimestampExpression]
RawTimestampzRange = jet.RawRange[jet.TimestampzExpression]
RawDateRange = jet.RawRange[jet.DateExpression]
)
// Func can be used to call custom or unsupported database functions.
var Func = jet.Func
// NewEnumValue creates new named enum value
var NewEnumValue = jet.NewEnumValue
// BinaryOperator can be used to use custom or unsupported operators that take two operands.
var BinaryOperator = jet.BinaryOperator

View File

@@ -0,0 +1,79 @@
package postgres
import (
"testing"
"time"
"github.com/stretchr/testify/require"
)
func TestRaw(t *testing.T) {
assertSerialize(t, Raw("current_database()"), "(current_database())")
assertDebugSerialize(t, Raw("current_database()"), "(current_database())")
assertSerialize(t, Raw(":first_arg + table.colInt + :second_arg", RawArgs{":first_arg": 11, ":second_arg": 22}),
"($1 + table.colInt + $2)", 11, 22)
assertDebugSerialize(t, Raw(":first_arg + table.colInt + :second_arg", RawArgs{":first_arg": 11, ":second_arg": 22}),
"(11 + table.colInt + 22)")
assertSerialize(t,
Int(700).ADD(RawInt(":first_arg + table.colInt + :second_arg", RawArgs{":first_arg": 11, ":second_arg": 22})),
"($1 + ($2 + table.colInt + $3))",
int64(700), 11, 22)
assertDebugSerialize(t,
Int(700).ADD(RawInt(":first_arg + table.colInt + :second_arg", RawArgs{":first_arg": 11, ":second_arg": 22})),
"(700 + (11 + table.colInt + 22))")
}
func TestDuplicateArguments(t *testing.T) {
assertSerialize(t, Raw(":arg + table.colInt + :arg", RawArgs{":arg": 11}),
"($1 + table.colInt + $1)", 11)
assertDebugSerialize(t, Raw(":arg + table.colInt + :arg", RawArgs{":arg": 11}),
"(11 + table.colInt + 11)")
assertSerialize(t, Raw("#age + table.colInt + #year + #age + #year + 11", RawArgs{"#age": 11, "#year": 2000}),
"($1 + table.colInt + $2 + $1 + $2 + 11)", 11, 2000)
assertDebugSerialize(t, Raw("#age + table.colInt + #year + #age + #year + 11", RawArgs{"#age": 11, "#year": 2000}),
"(11 + table.colInt + 2000 + 11 + 2000 + 11)")
assertSerialize(t, Raw("#1 + all_types.integer + #2 + #1 + #2 + #3 + #4",
RawArgs{"#1": 11, "#2": 22, "#3": 33, "#4": 44}),
`($1 + all_types.integer + $2 + $1 + $2 + $3 + $4)`, 11, 22, 33, 44)
assertDebugSerialize(t, Raw("#1 + all_types.integer + #2 + #1 + #2 + #3 + #4",
RawArgs{"#1": 11, "#2": 22, "#3": 33, "#4": 44}),
`(11 + all_types.integer + 22 + 11 + 22 + 33 + 44)`)
}
func TestRawInvalidArguments(t *testing.T) {
defer func() {
r := recover()
require.Equal(t, "jet: named argument 'first_arg' does not appear in raw query", r)
}()
assertSerialize(t, Raw("table.colInt + :second_arg", RawArgs{
"first_arg": 11,
"second_arg": 22,
}), "(table.colInt + $1)", 22)
}
func TestRawHelperMethods(t *testing.T) {
assertSerialize(t, RawBool("table.colInt < :float", RawArgs{":float": 11.22}).IS_FALSE(),
"(table.colInt < $1) IS FALSE", 11.22)
assertSerialize(t, RawFloat("table.colInt + :float", RawArgs{":float": 11.22}).EQ(Float(3.14)),
"((table.colInt + $1) = $2)", 11.22, 3.14)
assertSerialize(t, RawString("table.colStr || str", RawArgs{"str": "doe"}).EQ(String("john doe")),
"((table.colStr || $1) = $2::text)", "doe", "john doe")
now := time.Now()
assertSerialize(t, RawTime("table.colTime").EQ(TimeT(now)),
"((table.colTime) = $1::time without time zone)", now)
assertSerialize(t, RawTimez("table.colTime").EQ(TimezT(now)),
"((table.colTime) = $1::time with time zone)", now)
assertSerialize(t, RawTimestamp("table.colTimestamp").EQ(TimestampT(now)),
"((table.colTimestamp) = $1::timestamp without time zone)", now)
assertSerialize(t, RawTimestampz("table.colTimestampz").EQ(TimestampzT(now)),
"((table.colTimestampz) = $1::timestamp with time zone)", now)
assertSerialize(t, RawDate("table.colDate").EQ(DateT(now)),
"((table.colDate) = $1::date)", now)
}

View File

@@ -0,0 +1,474 @@
package postgres
import (
"github.com/go-jet/jet/v2/internal/jet"
)
// This functions can be used, instead of its method counterparts, to have a better indentation of a complex condition
// in the Go code and in the generated SQL.
var (
// AND function adds AND operator between expressions.
AND = jet.AND
// OR function adds OR operator between expressions.
OR = jet.OR
)
// ROW function is used to create a tuple value that consists of a set of expressions or column values.
func ROW(expressions ...Expression) RowExpression {
return jet.ROW(Dialect, expressions...)
}
// ------------------ Mathematical functions ---------------//
// ABSf calculates absolute value from float expression
var ABSf = jet.ABSf
// ABSi calculates absolute value from int expression
var ABSi = jet.ABSi
// POW calculates power of base with exponent
var POW = jet.POW
// POWER calculates power of base with exponent
var POWER = jet.POWER
// SQRT calculates square root of numeric expression
var SQRT = jet.SQRT
// CBRT calculates cube root of numeric expression
var CBRT = jet.CBRT
// CEIL calculates ceil of float expression
var CEIL = jet.CEIL
// FLOOR calculates floor of float expression
var FLOOR = jet.FLOOR
// ROUND calculates round of a float expressions with optional precision
var ROUND = jet.ROUND
// SIGN returns sign of float expression
var SIGN = jet.SIGN
// TRUNC calculates trunc of float expression with optional precision
var TRUNC = jet.TRUNC
// LN calculates natural algorithm of float expression
var LN = jet.LN
// LOG calculates logarithm of float expression
var LOG = jet.LOG
// ----------------- Aggregate functions -------------------//
// AVG is aggregate function used to calculate avg value from numeric expression
var AVG = jet.AVG
// BIT_AND is aggregate function used to calculates the bitwise AND of all non-null input values, or null if none.
var BIT_AND = jet.BIT_AND
// BIT_OR is aggregate function used to calculates the bitwise OR of all non-null input values, or null if none.
var BIT_OR = jet.BIT_OR
// BOOL_AND is aggregate function. Returns true if all input values are true, otherwise false
var BOOL_AND = jet.BOOL_AND
// BOOL_OR is aggregate function. Returns true if at least one input value is true, otherwise false
var BOOL_OR = jet.BOOL_OR
// COUNT is aggregate function. Returns number of input rows for which the value of expression is not null.
var COUNT = jet.COUNT
// EVERY is aggregate function. Returns true if all input values are true, otherwise false
var EVERY = jet.EVERY
// MAX is aggregate function. Returns maximum value of expression across all input values
var MAX = jet.MAX
// MAXf is aggregate function. Returns maximum value of float expression across all input values
var MAXf = jet.MAXf
// MAXi is aggregate function. Returns maximum value of int expression across all input values
var MAXi = jet.MAXi
// MIN is aggregate function. Returns minimum value of expression across all input values.
var MIN = jet.MIN
// MINf is aggregate function. Returns minimum value of float expression across all input values
var MINf = jet.MINf
// MINi is aggregate function. Returns minimum value of int expression across all input values
var MINi = jet.MINi
// SUM is aggregate function. Returns sum of all expressions
var SUM = jet.SUM
// SUMf is aggregate function. Returns sum of expression across all float expressions
var SUMf = jet.SUMf
// SUMi is aggregate function. Returns sum of expression across all integer expression.
var SUMi = jet.SUMi
// -------------------- Window functions -----------------------//
// ROW_NUMBER returns number of the current row within its partition, counting from 1
var ROW_NUMBER = jet.ROW_NUMBER
// RANK of the current row with gaps; same as row_number of its first peer
var RANK = jet.RANK
// DENSE_RANK returns rank of the current row without gaps; this function counts peer groups
var DENSE_RANK = jet.DENSE_RANK
// PERCENT_RANK calculates relative rank of the current row: (rank - 1) / (total partition rows - 1)
var PERCENT_RANK = jet.PERCENT_RANK
// CUME_DIST calculates cumulative distribution: (number of partition rows preceding or peer with current row) / total partition rows
var CUME_DIST = jet.CUME_DIST
// NTILE returns integer ranging from 1 to the argument value, dividing the partition as equally as possible
var NTILE = jet.NTILE
// LAG returns value evaluated at the row that is offset rows before the current row within the partition;
// if there is no such row, instead return default (which must be of the same type as value).
// Both offset and default are evaluated with respect to the current row.
// If omitted, offset defaults to 1 and default to null
var LAG = jet.LAG
// LEAD returns value evaluated at the row that is offset rows after the current row within the partition;
// if there is no such row, instead return default (which must be of the same type as value).
// Both offset and default are evaluated with respect to the current row.
// If omitted, offset defaults to 1 and default to null
var LEAD = jet.LEAD
// FIRST_VALUE returns value evaluated at the row that is the first row of the window frame
var FIRST_VALUE = jet.FIRST_VALUE
// LAST_VALUE returns value evaluated at the row that is the last row of the window frame
var LAST_VALUE = jet.LAST_VALUE
// NTH_VALUE returns value evaluated at the row that is the nth row of the window frame (counting from 1); null if no such row
var NTH_VALUE = jet.NTH_VALUE
//--------------------- String functions ------------------//
// BIT_LENGTH returns number of bits in string expression
var BIT_LENGTH = jet.BIT_LENGTH
// CHAR_LENGTH returns number of characters in string expression
var CHAR_LENGTH = jet.CHAR_LENGTH
// OCTET_LENGTH returns number of bytes in string expression
var OCTET_LENGTH = jet.OCTET_LENGTH
// LOWER returns string expression in lower case
var LOWER = jet.LOWER
// UPPER returns string expression in upper case
var UPPER = jet.UPPER
// BTRIM removes the longest string consisting only of characters
// in characters (a space by default) from the start and end of string
var BTRIM = jet.BTRIM
// LTRIM removes the longest string containing only characters
// from characters (a space by default) from the start of string
var LTRIM = jet.LTRIM
// RTRIM removes the longest string containing only characters
// from characters (a space by default) from the end of string
var RTRIM = jet.RTRIM
// CHR returns character with the given code.
var CHR = jet.CHR
// CONCAT adds two or more expressions together
var CONCAT = func(expressions ...Expression) StringExpression {
return jet.CONCAT(explicitLiteralCasts(expressions...)...)
}
// CONCAT_WS adds two or more expressions together with a separator.
func CONCAT_WS(separator Expression, expressions ...Expression) StringExpression {
return jet.CONCAT_WS(explicitLiteralCast(separator), explicitLiteralCasts(expressions...)...)
}
// CONVERT converts string to dest_encoding. The original encoding is
// specified by src_encoding. The string must be valid in this encoding.
var CONVERT = jet.CONVERT
// CONVERT_FROM converts string to the database encoding. The original
// encoding is specified by src_encoding. The string must be valid in this encoding.
var CONVERT_FROM = jet.CONVERT_FROM
// CONVERT_TO converts string to dest_encoding.
var CONVERT_TO = jet.CONVERT_TO
// ENCODE encodes binary data into a textual representation.
// Supported formats are: base64, hex, escape. escape converts zero bytes and
// high-bit-set bytes to octal sequences (\nnn) and doubles backslashes.
var ENCODE = jet.ENCODE
// DECODE decodes binary data from textual representation in string.
// Options for format are same as in encode.
var DECODE = jet.DECODE
// FORMAT formats a number to a format like "#,###,###.##", rounded to a specified number of decimal places, then it returns the result as a string.
func FORMAT(formatStr StringExpression, formatArgs ...Expression) StringExpression {
return jet.FORMAT(formatStr, explicitLiteralCasts(formatArgs...)...)
}
// INITCAP converts the first letter of each word to upper case
// and the rest to lower case. Words are sequences of alphanumeric
// characters separated by non-alphanumeric characters.
var INITCAP = jet.INITCAP
// LEFT returns first n characters in the string.
// When n is negative, return all but last |n| characters.
var LEFT = jet.LEFT
// RIGHT returns last n characters in the string.
// When n is negative, return all but first |n| characters.
var RIGHT = jet.RIGHT
// LENGTH returns number of characters in string with a given encoding
var LENGTH = jet.LENGTH
// LPAD fills up the string to length length by prepending the characters
// fill (a space by default). If the string is already longer than length
// then it is truncated (on the right).
var LPAD = jet.LPAD
// RPAD fills up the string to length length by appending the characters
// fill (a space by default). If the string is already longer than length then it is truncated.
var RPAD = jet.RPAD
// MD5 calculates the MD5 hash of string, returning the result in hexadecimal
var MD5 = jet.MD5
// REPEAT repeats string the specified number of times
var REPEAT = jet.REPEAT
// REPLACE replaces all occurrences in string of substring from with substring to
var REPLACE = jet.REPLACE
// REVERSE returns reversed string.
var REVERSE = jet.REVERSE
// STRPOS returns location of specified substring (same as position(substring in string),
// but note the reversed argument order)
var STRPOS = jet.STRPOS
// SUBSTR extracts substring
var SUBSTR = jet.SUBSTR
// TO_ASCII convert string to ASCII from another encoding
var TO_ASCII = jet.TO_ASCII
// TO_HEX converts number to its equivalent hexadecimal representation
var TO_HEX = jet.TO_HEX
//----------Data Type Formatting Functions ----------------------//
// LOWER_BOUND returns range expressions lower bound
func LOWER_BOUND[T Expression](expression jet.Range[T]) T {
return jet.LOWER_BOUND[T](expression)
}
// UPPER_BOUND returns range expressions upper bound
func UPPER_BOUND[T Expression](expression jet.Range[T]) T {
return jet.UPPER_BOUND[T](expression)
}
//----------Data Type Formatting Functions ----------------------//
// TO_CHAR converts expression to string with format
var TO_CHAR = jet.TO_CHAR
// TO_DATE converts string to date using format
var TO_DATE = jet.TO_DATE
// TO_NUMBER converts string to numeric using format
var TO_NUMBER = jet.TO_NUMBER
// TO_TIMESTAMP converts string to time stamp with time zone using format
var TO_TIMESTAMP = jet.TO_TIMESTAMP
//----------------- Date/Time Functions and Operators ------------//
// Additional time unit types for EXTRACT function
const (
DOW unit = MILLENNIUM + 1 + iota
DOY
EPOCH
ISODOW
ISOYEAR
JULIAN
QUARTER
TIMEZONE
TIMEZONE_HOUR
TIMEZONE_MINUTE
)
// EXTRACT function retrieves subfields such as year or hour from date/time values
//
// EXTRACT(DAY, User.CreatedAt)
func EXTRACT(field unit, from Expression) FloatExpression {
return FloatExp(jet.EXTRACT(unitToString(field), from))
}
// CURRENT_DATE returns current date
var CURRENT_DATE = jet.CURRENT_DATE
// CURRENT_TIME returns current time with time zone
var CURRENT_TIME = jet.CURRENT_TIME
// CURRENT_TIMESTAMP returns current timestamp with time zone
var CURRENT_TIMESTAMP = jet.CURRENT_TIMESTAMP
// LOCALTIME returns local time of day using optional precision
var LOCALTIME = jet.LOCALTIME
// LOCALTIMESTAMP returns current date and time using optional precision
var LOCALTIMESTAMP = jet.LOCALTIMESTAMP
// NOW returns current date and time
var NOW = jet.NOW
// DATE_TRUNC returns the truncated date and time using optional time zone.
// Use TimestampzExp if you need timestamp with time zone and IntervalExp if you need interval.
func DATE_TRUNC(field unit, source Expression, timezone ...string) TimestampExpression {
if len(timezone) > 0 {
return jet.NewTimestampFunc("DATE_TRUNC", jet.FixedLiteral(unitToString(field)), source, jet.FixedLiteral(timezone[0]))
}
return jet.NewTimestampFunc("DATE_TRUNC", jet.FixedLiteral(unitToString(field)), source)
}
// GENERATE_SERIES generates a series of values from start to stop, with a step size of step.
func GENERATE_SERIES(start Expression, stop Expression, step ...Expression) Expression {
if len(step) > 0 {
return jet.NewFunc("GENERATE_SERIES", []Expression{start, stop, step[0]}, nil)
}
return jet.NewFunc("GENERATE_SERIES", []Expression{start, stop}, nil)
}
// --------------- Conditional Expressions Functions -------------//
// COALESCE function returns the first of its arguments that is not null.
var COALESCE = jet.COALESCE
// NULLIF function returns a null value if value1 equals value2; otherwise it returns value1.
var NULLIF = jet.NULLIF
// GREATEST selects the largest value from a list of expressions
var GREATEST = jet.GREATEST
// LEAST selects the smallest value from a list of expressions
var LEAST = jet.LEAST
// EXISTS checks for existence of the rows in subQuery
var EXISTS = jet.EXISTS
// CASE create CASE operator with optional list of expressions
var CASE = jet.CASE
func explicitLiteralCasts(expressions ...Expression) []jet.Expression {
ret := []jet.Expression{}
for _, exp := range expressions {
ret = append(ret, explicitLiteralCast(exp))
}
return ret
}
func explicitLiteralCast(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
}
// MODE computes the most frequent value of the aggregated argument
var MODE = jet.MODE
// PERCENTILE_CONT computes a value corresponding to the specified fraction within the ordered set of
// aggregated argument values. This will interpolate between adjacent input items if needed.
func PERCENTILE_CONT(fraction FloatExpression) *jet.OrderSetAggregateFunc {
return jet.PERCENTILE_CONT(castFloatLiteral(fraction))
}
// PERCENTILE_DISC computes the first value within the ordered set of aggregated argument values whose position
// in the ordering equals or exceeds the specified fraction. The aggregated argument must be of a sortable type.
func PERCENTILE_DISC(fraction FloatExpression) *jet.OrderSetAggregateFunc {
return jet.PERCENTILE_DISC(castFloatLiteral(fraction))
}
func castFloatLiteral(fraction FloatExpression) FloatExpression {
if _, ok := fraction.(jet.LiteralExpression); ok {
return CAST(fraction).AS_DOUBLE() // to make postgres aware of the type
}
return fraction
}
// ----------------- Group By operators --------------------------//
// GROUPING_SETS operator allows grouping of the rows in a table by multiple sets of columns(or expressions) in a single query.
// This can be useful when we want to analyze data by different combinations of columns, without having to write separate
// queries for each combination. GROUPING_SETS sets of columns are constructed with WRAP method.
//
// GROUPING_SETS(
// WRAP(Inventory.FilmID, Inventory.StoreID),
// WRAP(),
// ),
var GROUPING_SETS = jet.GROUPING_SETS
// WRAP surrounds a list of expressions or columns with parentheses, producing new row: (expression1, expression2, ...)
// The construct (a, b) is normally recognized in expressions as a row constructor. WRAP and ROW methods behave exactly the same,
// except when used in GROUPING_SETS and VALUES. In these contexts, WRAP must be used instead of ROW.
func WRAP(expressions ...Expression) RowExpression {
return jet.WRAP(Dialect, expressions...)
}
// ROLLUP operator is used with the GROUP BY clause to generate all prefixes of a group of columns including the empty list.
// It creates extra rows in the result set that represent the subtotal values for each combination of columns.
var ROLLUP = jet.ROLLUP
// CUBE operator is used with the GROUP BY clause to generate subtotals for all possible combinations of a group of columns.
// It creates extra rows in the result set that represent the subtotal values for each combination of columns.
var CUBE = jet.CUBE
// GROUPING function is used to identify which columns are included in a grouping set or a subtotal row. It takes as input
// the name of a column and returns 1 if the column is not included in the current grouping set, and 0 otherwise.
// It can be also used with multiple parameters to check if a set of columns is included in the current grouping set. The result
// of the GROUPING function would then be an integer bit mask having 1s for the arguments which have GROUPING(argument) as 1.
var GROUPING = jet.GROUPING
// range constructor functions
var (
// DATE_RANGE constructor function to create a date range
DATE_RANGE = jet.DateRange
// NUM_RANGE constructor function to create a numeric range
NUM_RANGE = jet.NumRange
// TS_RANGE constructor function to create a timestamp range
TS_RANGE = jet.TsRange
// TSTZ_RANGE constructor function to create a timestampz range
TSTZ_RANGE = jet.TstzRange
// INT4_RANGE constructor function to create a int4 range
INT4_RANGE = jet.Int4Range
// INT8_RANGE constructor function to create a int8 range
INT8_RANGE = jet.Int8Range
)

View File

@@ -0,0 +1,36 @@
package postgres
import (
"testing"
)
func TestROW(t *testing.T) {
assertSerialize(t, ROW(SELECT(Int(1))), `ROW((
SELECT $1
))`)
assertSerialize(t, ROW(Int(1), SELECT(Int(2)), Float(11.11)), `ROW($1, (
SELECT $2
), $3)`)
}
func TestDATE_TRUNC(t *testing.T) {
assertSerialize(t, DATE_TRUNC(YEAR, NOW()), "DATE_TRUNC('YEAR', NOW())")
assertSerialize(
t,
DATE_TRUNC(DAY, NOW().ADD(INTERVAL(1, HOUR)), "Australia/Sydney"),
"DATE_TRUNC('DAY', NOW() + INTERVAL '1 HOUR', 'Australia/Sydney')",
)
}
func TestGENERATE_SERIES(t *testing.T) {
assertSerialize(
t,
GENERATE_SERIES(NOW(), NOW().ADD(INTERVAL(10, DAY))),
"GENERATE_SERIES(NOW(), NOW() + INTERVAL '10 DAY')",
)
assertSerialize(
t,
GENERATE_SERIES(NOW(), NOW().ADD(INTERVAL(10, DAY)), INTERVAL(2, DAY)),
"GENERATE_SERIES(NOW(), NOW() + INTERVAL '10 DAY', INTERVAL '2 DAY')",
)
}

View File

@@ -0,0 +1,77 @@
package postgres
import "github.com/go-jet/jet/v2/internal/jet"
// InsertStatement is interface for SQL INSERT statements
type InsertStatement interface {
jet.SerializerStatement
// Insert row of values
VALUES(value interface{}, values ...interface{}) InsertStatement
// Insert row of values, where value for each column is extracted from filed of structure data.
// If data is not struct or there is no field for every column selected, this method will panic.
MODEL(data interface{}) InsertStatement
MODELS(data interface{}) InsertStatement
QUERY(selectStatement SelectStatement) InsertStatement
ON_CONFLICT(indexExpressions ...jet.ColumnExpression) onConflict
RETURNING(projections ...Projection) InsertStatement
}
func newInsertStatement(table WritableTable, columns []jet.Column) InsertStatement {
newInsert := &insertStatementImpl{}
newInsert.SerializerStatement = jet.NewStatementImpl(Dialect, jet.InsertStatementType, newInsert,
&newInsert.Insert,
&newInsert.ValuesQuery,
&newInsert.OnConflict,
&newInsert.Returning,
)
newInsert.Insert.Table = table
newInsert.Insert.Columns = columns
return newInsert
}
type insertStatementImpl struct {
jet.SerializerStatement
Insert jet.ClauseInsert
ValuesQuery jet.ClauseValuesQuery
Returning jet.ClauseReturning
OnConflict onConflictClause
}
func (i *insertStatementImpl) VALUES(value interface{}, values ...interface{}) InsertStatement {
i.ValuesQuery.Rows = append(i.ValuesQuery.Rows, jet.UnwindRowFromValues(value, values))
return i
}
func (i *insertStatementImpl) MODEL(data interface{}) InsertStatement {
i.ValuesQuery.Rows = append(i.ValuesQuery.Rows, jet.UnwindRowFromModel(i.Insert.GetColumns(), data))
return i
}
func (i *insertStatementImpl) MODELS(data interface{}) InsertStatement {
i.ValuesQuery.Rows = append(i.ValuesQuery.Rows, jet.UnwindRowsFromModels(i.Insert.GetColumns(), data)...)
return i
}
func (i *insertStatementImpl) RETURNING(projections ...jet.Projection) InsertStatement {
i.Returning.ProjectionList = projections
return i
}
func (i *insertStatementImpl) QUERY(selectStatement SelectStatement) InsertStatement {
i.ValuesQuery.Query = selectStatement
return i
}
func (i *insertStatementImpl) ON_CONFLICT(indexExpressions ...jet.ColumnExpression) onConflict {
i.OnConflict = onConflictClause{
insertStatement: i,
indexExpressions: indexExpressions,
}
return &i.OnConflict
}

View File

@@ -0,0 +1,201 @@
package postgres
import (
"github.com/stretchr/testify/require"
"testing"
"time"
)
func TestInvalidInsert(t *testing.T) {
assertStatementSqlErr(t, table1.INSERT(nil).VALUES(1), "jet: nil column in columns list")
}
func TestInsertNilValue(t *testing.T) {
assertStatementSql(t, table1.INSERT(table1Col1).VALUES(nil), `
INSERT INTO db.table1 (col1)
VALUES ($1);
`, nil)
}
func TestInsertSingleValue(t *testing.T) {
assertStatementSql(t, table1.INSERT(table1Col1).VALUES(1), `
INSERT INTO db.table1 (col1)
VALUES ($1);
`, int(1))
}
func TestInsertWithColumnList(t *testing.T) {
columnList := ColumnList{table3ColInt, table3StrCol}
assertStatementSql(t, table3.INSERT(columnList).VALUES(1, 3), `
INSERT INTO db.table3 (col_int, col2)
VALUES ($1, $2);
`, 1, 3)
}
func TestInsertDate(t *testing.T) {
date := time.Date(1999, 1, 2, 3, 4, 5, 0, time.UTC)
assertStatementSql(t, table1.INSERT(table1ColTime).VALUES(date), `
INSERT INTO db.table1 (col_time)
VALUES ($1);
`, date)
}
func TestInsertMultipleValues(t *testing.T) {
assertStatementSql(t, table1.INSERT(table1Col1, table1ColFloat, table1ColBool).VALUES(1, 2, 3), `
INSERT INTO db.table1 (col1, col_float, col_bool)
VALUES ($1, $2, $3);
`, 1, 2, 3)
}
func TestInsertMultipleRows(t *testing.T) {
stmt := table1.INSERT(table1Col1, table1ColFloat).
VALUES(1, 2).
VALUES(11, 22).
VALUES(111, 222)
assertStatementSql(t, stmt, `
INSERT INTO db.table1 (col1, col_float)
VALUES ($1, $2),
($3, $4),
($5, $6);
`, 1, 2, 11, 22, 111, 222)
}
func TestInsertValuesFromModel(t *testing.T) {
type Table1Model struct {
Col1 *int
ColFloat float64
}
one := 1
toInsert := Table1Model{
Col1: &one,
ColFloat: 1.11,
}
stmt := table1.INSERT(table1Col1, table1ColFloat).
MODEL(toInsert).
MODEL(&toInsert)
expectedSQL := `
INSERT INTO db.table1 (col1, col_float)
VALUES ($1, $2),
($3, $4);
`
assertStatementSql(t, stmt, expectedSQL, 1, float64(1.11), 1, float64(1.11))
}
func TestInsertValuesFromModelColumnMismatch(t *testing.T) {
defer func() {
r := recover()
require.Equal(t, r, "missing struct field for column : col1")
}()
type Table1Model struct {
Col1Prim int
Col2 string
}
newData := Table1Model{
Col1Prim: 1,
Col2: "one",
}
table1.
INSERT(table1Col1, table1ColFloat).
MODEL(newData)
}
func TestInsertFromNonStructModel(t *testing.T) {
defer func() {
r := recover()
require.Equal(t, r, "jet: data has to be a struct")
}()
table2.INSERT(table2ColInt).MODEL([]int{})
}
func TestInsertQuery(t *testing.T) {
stmt := table1.INSERT(table1Col1).
QUERY(table1.SELECT(table1Col1))
var expectedSQL = `
INSERT INTO db.table1 (col1) (
SELECT table1.col1 AS "table1.col1"
FROM db.table1
);
`
assertStatementSql(t, stmt, expectedSQL)
}
func TestInsertDefaultValue(t *testing.T) {
stmt := table1.INSERT(table1Col1, table1ColFloat).
VALUES(DEFAULT, "two")
var expectedSQL = `
INSERT INTO db.table1 (col1, col_float)
VALUES (DEFAULT, $1);
`
assertStatementSql(t, stmt, expectedSQL, "two")
}
func TestInsert_ON_CONFLICT(t *testing.T) {
stmt := table1.INSERT(table1Col1, table1ColBool).
VALUES("one", "two").
VALUES("1", "2").
VALUES("theta", "beta").
ON_CONFLICT(table1ColBool).WHERE(table1ColBool.IS_NOT_FALSE()).
DO_UPDATE(
SET(table1ColBool.SET(Bool(true)),
table2ColInt.SET(Int(1)),
ColumnList{table1Col1, table1ColBool}.SET(ROW(Int(2), String("two"))),
).WHERE(table1Col1.GT(Int(2))),
).
RETURNING(table1Col1, table1ColBool)
assertDebugStatementSql(t, stmt, `
INSERT INTO db.table1 (col1, col_bool)
VALUES ('one', 'two'),
('1', '2'),
('theta', 'beta')
ON CONFLICT (col_bool) WHERE col_bool IS NOT FALSE DO UPDATE
SET col_bool = TRUE::boolean,
col_int = 1,
(col1, col_bool) = ROW(2, 'two'::text)
WHERE table1.col1 > 2
RETURNING table1.col1 AS "table1.col1",
table1.col_bool AS "table1.col_bool";
`)
}
func TestInsert_ON_CONFLICT_ON_CONSTRAINT(t *testing.T) {
stmt := table1.INSERT(table1Col1, table1ColBool).
VALUES("one", "two").
VALUES("1", "2").
ON_CONFLICT().ON_CONSTRAINT("idk_primary_key").
DO_UPDATE(
SET(table1ColBool.SET(Bool(false)),
table2ColInt.SET(Int(1)),
ColumnList{table1Col1, table1ColBool}.SET(ROW(Int(2), String("two"))),
).WHERE(table1Col1.GT(Int(2)))).
RETURNING(table1Col1, table1ColBool)
assertDebugStatementSql(t, stmt, `
INSERT INTO db.table1 (col1, col_bool)
VALUES ('one', 'two'),
('1', '2')
ON CONFLICT ON CONSTRAINT idk_primary_key DO UPDATE
SET col_bool = FALSE::boolean,
col_int = 1,
(col1, col_bool) = ROW(2, 'two'::text)
WHERE table1.col1 > 2
RETURNING table1.col1 AS "table1.col1",
table1.col_bool AS "table1.col_bool";
`)
}

View File

@@ -0,0 +1,257 @@
package postgres
import (
"fmt"
"github.com/go-jet/jet/v2/internal/jet"
"github.com/go-jet/jet/v2/internal/utils/datetime"
"strconv"
"strings"
"time"
)
type quantityAndUnit = float64
type unit = float64
// Interval unit types
const (
YEAR unit = 123456789 + iota
MONTH
WEEK
DAY
HOUR
MINUTE
SECOND
MILLISECOND
MICROSECOND
DECADE
CENTURY
MILLENNIUM
)
// IntervalExpression is representation of postgres INTERVAL
type IntervalExpression interface {
jet.IsInterval
jet.Expression
EQ(rhs IntervalExpression) BoolExpression
NOT_EQ(rhs IntervalExpression) BoolExpression
IS_DISTINCT_FROM(rhs IntervalExpression) BoolExpression
IS_NOT_DISTINCT_FROM(rhs IntervalExpression) BoolExpression
LT(rhs IntervalExpression) BoolExpression
LT_EQ(rhs IntervalExpression) BoolExpression
GT(rhs IntervalExpression) BoolExpression
GT_EQ(rhs IntervalExpression) BoolExpression
BETWEEN(min, max IntervalExpression) BoolExpression
NOT_BETWEEN(min, max IntervalExpression) BoolExpression
ADD(rhs IntervalExpression) IntervalExpression
SUB(rhs IntervalExpression) IntervalExpression
MUL(rhs NumericExpression) IntervalExpression
DIV(rhs NumericExpression) IntervalExpression
}
type intervalInterfaceImpl struct {
jet.IsIntervalImpl
parent IntervalExpression
}
func (i *intervalInterfaceImpl) EQ(rhs IntervalExpression) BoolExpression {
return jet.Eq(i.parent, rhs)
}
func (i *intervalInterfaceImpl) NOT_EQ(rhs IntervalExpression) BoolExpression {
return jet.NotEq(i.parent, rhs)
}
func (i *intervalInterfaceImpl) IS_DISTINCT_FROM(rhs IntervalExpression) BoolExpression {
return jet.IsDistinctFrom(i.parent, rhs)
}
func (i *intervalInterfaceImpl) IS_NOT_DISTINCT_FROM(rhs IntervalExpression) BoolExpression {
return jet.IsNotDistinctFrom(i.parent, rhs)
}
func (i *intervalInterfaceImpl) LT(rhs IntervalExpression) BoolExpression {
return jet.Lt(i.parent, rhs)
}
func (i *intervalInterfaceImpl) LT_EQ(rhs IntervalExpression) BoolExpression {
return jet.LtEq(i.parent, rhs)
}
func (i *intervalInterfaceImpl) GT(rhs IntervalExpression) BoolExpression {
return jet.Gt(i.parent, rhs)
}
func (i *intervalInterfaceImpl) GT_EQ(rhs IntervalExpression) BoolExpression {
return jet.GtEq(i.parent, rhs)
}
func (i *intervalInterfaceImpl) BETWEEN(min, max IntervalExpression) BoolExpression {
return jet.NewBetweenOperatorExpression(i.parent, min, max, false)
}
func (i *intervalInterfaceImpl) NOT_BETWEEN(min, max IntervalExpression) BoolExpression {
return jet.NewBetweenOperatorExpression(i.parent, min, max, true)
}
func (i *intervalInterfaceImpl) ADD(rhs IntervalExpression) IntervalExpression {
return IntervalExp(jet.Add(i.parent, rhs))
}
func (i *intervalInterfaceImpl) SUB(rhs IntervalExpression) IntervalExpression {
return IntervalExp(jet.Sub(i.parent, rhs))
}
func (i *intervalInterfaceImpl) MUL(rhs NumericExpression) IntervalExpression {
return IntervalExp(jet.Mul(i.parent, rhs))
}
func (i *intervalInterfaceImpl) DIV(rhs NumericExpression) IntervalExpression {
return IntervalExp(jet.Div(i.parent, rhs))
}
type intervalExpression struct {
jet.Expression
intervalInterfaceImpl
}
// INTERVAL creates new interval expression from the list of quantity-unit pairs.
//
// INTERVAL(1, DAY, 3, MINUTE)
func INTERVAL(quantityAndUnit ...quantityAndUnit) IntervalExpression {
quantityAndUnitLen := len(quantityAndUnit)
if quantityAndUnitLen == 0 || quantityAndUnitLen%2 != 0 {
panic("jet: invalid number of quantity and unit fields")
}
var fields []string
for i := 0; i < len(quantityAndUnit); i += 2 {
quantity := strconv.FormatFloat(quantityAndUnit[i], 'f', -1, 64)
unitString := unitToString(quantityAndUnit[i+1])
fields = append(fields, quantity+" "+unitString)
}
intervalStr := fmt.Sprintf("INTERVAL '%s'", strings.Join(fields, " "))
newInterval := &intervalExpression{}
newInterval.Expression = jet.RawWithParent(intervalStr, newInterval)
newInterval.intervalInterfaceImpl.parent = newInterval
return newInterval
}
// INTERVALd creates interval expression from time.Duration
func INTERVALd(duration time.Duration) IntervalExpression {
days, hours, minutes, seconds, microseconds := datetime.ExtractTimeComponents(duration)
var quantityAndUnits []quantityAndUnit
if days > 0 {
quantityAndUnits = append(quantityAndUnits, quantityAndUnit(days))
quantityAndUnits = append(quantityAndUnits, DAY)
}
if hours > 0 {
quantityAndUnits = append(quantityAndUnits, quantityAndUnit(hours))
quantityAndUnits = append(quantityAndUnits, HOUR)
}
if minutes > 0 {
quantityAndUnits = append(quantityAndUnits, quantityAndUnit(minutes))
quantityAndUnits = append(quantityAndUnits, MINUTE)
}
if seconds > 0 {
quantityAndUnits = append(quantityAndUnits, quantityAndUnit(seconds))
quantityAndUnits = append(quantityAndUnits, SECOND)
}
if microseconds > 0 {
quantityAndUnits = append(quantityAndUnits, quantityAndUnit(microseconds))
quantityAndUnits = append(quantityAndUnits, MICROSECOND)
}
if len(quantityAndUnits) == 0 {
return INTERVAL(0, MICROSECOND)
}
return INTERVAL(quantityAndUnits...)
}
func unitToString(unit quantityAndUnit) string {
switch unit {
case YEAR:
return "YEAR"
case MONTH:
return "MONTH"
case WEEK:
return "WEEK"
case DAY:
return "DAY"
case HOUR:
return "HOUR"
case MINUTE:
return "MINUTE"
case SECOND:
return "SECOND"
case MILLISECOND:
return "MILLISECOND"
case MICROSECOND:
return "MICROSECOND"
case DECADE:
return "DECADE"
case CENTURY:
return "CENTURY"
case MILLENNIUM:
return "MILLENNIUM"
// additional field units for EXTRACT function
case DOW:
return "DOW"
case DOY:
return "DOY"
case EPOCH:
return "EPOCH"
case ISODOW:
return "ISODOW"
case ISOYEAR:
return "ISOYEAR"
case JULIAN:
return "JULIAN"
case QUARTER:
return "QUARTER"
case TIMEZONE:
return "TIMEZONE"
case TIMEZONE_HOUR:
return "TIMEZONE_HOUR"
case TIMEZONE_MINUTE:
return "TIMEZONE_MINUTE"
default:
panic("jet: invalid INTERVAL unit type")
}
}
//---------------------------------------------------//
type intervalWrapper struct {
intervalInterfaceImpl
Expression
}
func newIntervalExpressionWrap(expression Expression) IntervalExpression {
intervalWrap := &intervalWrapper{Expression: expression}
intervalWrap.intervalInterfaceImpl.parent = intervalWrap
return intervalWrap
}
// IntervalExp is interval expression wrapper around arbitrary expression.
// Allows go compiler to see any expression as interval expression.
// Does not add sql cast to generated sql builder output.
func IntervalExp(expression Expression) IntervalExpression {
return newIntervalExpressionWrap(expression)
}

View File

@@ -0,0 +1,84 @@
package postgres
import (
"testing"
"time"
)
func TestINTERVAL(t *testing.T) {
assertSerialize(t, INTERVAL(1, YEAR), "INTERVAL '1 YEAR'")
assertSerialize(t, INTERVAL(1, MONTH), "INTERVAL '1 MONTH'")
assertSerialize(t, INTERVAL(1, WEEK), "INTERVAL '1 WEEK'")
assertSerialize(t, INTERVAL(1, DAY), "INTERVAL '1 DAY'")
assertSerialize(t, INTERVAL(1, HOUR), "INTERVAL '1 HOUR'")
assertSerialize(t, INTERVAL(1, MINUTE), "INTERVAL '1 MINUTE'")
assertSerialize(t, INTERVAL(1, SECOND), "INTERVAL '1 SECOND'")
assertSerialize(t, INTERVAL(1, MILLISECOND), "INTERVAL '1 MILLISECOND'")
assertSerialize(t, INTERVAL(1, MICROSECOND), "INTERVAL '1 MICROSECOND'")
assertSerialize(t, INTERVAL(1, DECADE), "INTERVAL '1 DECADE'")
assertSerialize(t, INTERVAL(1, CENTURY), "INTERVAL '1 CENTURY'")
assertSerialize(t, INTERVAL(1, MILLENNIUM), "INTERVAL '1 MILLENNIUM'")
assertSerialize(t, INTERVAL(1, YEAR, 10, MONTH), "INTERVAL '1 YEAR 10 MONTH'")
assertSerialize(t, INTERVAL(1, YEAR, 10, MONTH, 20, DAY), "INTERVAL '1 YEAR 10 MONTH 20 DAY'")
assertSerialize(t, INTERVAL(1, YEAR, 10, MONTH, 20, DAY, 3, HOUR), "INTERVAL '1 YEAR 10 MONTH 20 DAY 3 HOUR'")
assertSerialize(t, INTERVAL(1, YEAR).IS_NOT_NULL(), "INTERVAL '1 YEAR' IS NOT NULL")
assertProjectionSerialize(t, INTERVAL(1, YEAR).AS("one year"), `INTERVAL '1 YEAR' AS "one year"`)
f := 5.2
assertSerialize(t, INTERVAL(f, YEAR), "INTERVAL '5.2 YEAR'")
}
func TestINTERVALd(t *testing.T) {
assertSerialize(t, INTERVALd(0), "INTERVAL '0 MICROSECOND'")
assertSerialize(t, INTERVALd(1*time.Microsecond), "INTERVAL '1 MICROSECOND'")
assertSerialize(t, INTERVALd(1*time.Millisecond), "INTERVAL '1000 MICROSECOND'")
assertSerialize(t, INTERVALd(1*time.Second), "INTERVAL '1 SECOND'")
assertSerialize(t, INTERVALd(1*time.Minute), "INTERVAL '1 MINUTE'")
assertSerialize(t, INTERVALd(1*time.Hour), "INTERVAL '1 HOUR'")
assertSerialize(t, INTERVALd(24*time.Hour), "INTERVAL '1 DAY'")
assertSerialize(t, INTERVALd(24*time.Hour+2*time.Hour+3*time.Minute+4*time.Second+5*time.Microsecond),
"INTERVAL '1 DAY 2 HOUR 3 MINUTE 4 SECOND 5 MICROSECOND'")
}
func TestINTERVAL_InvalidParams(t *testing.T) {
assertPanicErr(t, func() { INTERVAL() }, "jet: invalid number of quantity and unit fields")
assertPanicErr(t, func() { INTERVAL(1) }, "jet: invalid number of quantity and unit fields")
assertPanicErr(t, func() { INTERVAL(1, 2) }, "jet: invalid INTERVAL unit type")
}
func TestDateTimeIntervalArithmetic(t *testing.T) {
assertSerialize(t, table2ColDate.ADD(INTERVAL(1, HOUR)), "(table2.col_date + INTERVAL '1 HOUR')")
assertSerialize(t, table2ColDate.SUB(INTERVAL(1, HOUR)), "(table2.col_date - INTERVAL '1 HOUR')")
assertSerialize(t, table2ColTime.ADD(INTERVAL(1, HOUR)), "(table2.col_time + INTERVAL '1 HOUR')")
assertSerialize(t, table2ColTime.SUB(INTERVAL(1, HOUR)), "(table2.col_time - INTERVAL '1 HOUR')")
assertSerialize(t, table2ColTimez.ADD(INTERVAL(1, HOUR)), "(table2.col_timez + INTERVAL '1 HOUR')")
assertSerialize(t, table2ColTimez.SUB(INTERVAL(1, HOUR)), "(table2.col_timez - INTERVAL '1 HOUR')")
assertSerialize(t, table2ColTimestamp.ADD(INTERVAL(1, HOUR)), "(table2.col_timestamp + INTERVAL '1 HOUR')")
assertSerialize(t, table2ColTimestamp.SUB(INTERVAL(1, HOUR)), "(table2.col_timestamp - INTERVAL '1 HOUR')")
assertSerialize(t, table2ColTimestampz.ADD(INTERVAL(1, HOUR)), "(table2.col_timestampz + INTERVAL '1 HOUR')")
assertSerialize(t, table2ColTimestampz.SUB(INTERVAL(1, HOUR)), "(table2.col_timestampz - INTERVAL '1 HOUR')")
}
func TestIntervalExpressionMethods(t *testing.T) {
assertSerialize(t, table1ColInterval.EQ(table2ColInterval), "(table1.col_interval = table2.col_interval)")
assertSerialize(t, table1ColInterval.EQ(INTERVAL(10, SECOND)), "(table1.col_interval = INTERVAL '10 SECOND')")
assertSerialize(t, table1ColInterval.EQ(INTERVALd(11*time.Minute)), "(table1.col_interval = INTERVAL '11 MINUTE')")
assertSerialize(t, table1ColInterval.EQ(INTERVALd(11*time.Minute)).EQ(Bool(false)),
"((table1.col_interval = INTERVAL '11 MINUTE') = $1::boolean)", false)
assertSerialize(t, table1ColInterval.NOT_EQ(table2ColInterval), "(table1.col_interval != table2.col_interval)")
assertSerialize(t, table1ColInterval.IS_DISTINCT_FROM(table2ColInterval), "(table1.col_interval IS DISTINCT FROM table2.col_interval)")
assertSerialize(t, table1ColInterval.IS_NOT_DISTINCT_FROM(table2ColInterval), "(table1.col_interval IS NOT DISTINCT FROM table2.col_interval)")
assertSerialize(t, table1ColInterval.LT(table2ColInterval), "(table1.col_interval < table2.col_interval)")
assertSerialize(t, table1ColInterval.LT_EQ(table2ColInterval), "(table1.col_interval <= table2.col_interval)")
assertSerialize(t, table1ColInterval.GT(table2ColInterval), "(table1.col_interval > table2.col_interval)")
assertSerialize(t, table1ColInterval.GT_EQ(table2ColInterval), "(table1.col_interval >= table2.col_interval)")
assertSerialize(t, table1ColInterval.ADD(table2ColInterval), "(table1.col_interval + table2.col_interval)")
assertSerialize(t, table1ColInterval.SUB(table2ColInterval), "(table1.col_interval - table2.col_interval)")
assertSerialize(t, table1ColInterval.MUL(table2ColInt), "(table1.col_interval * table2.col_int)")
assertSerialize(t, table1ColInterval.MUL(table2ColFloat), "(table1.col_interval * table2.col_float)")
assertSerialize(t, table1ColInterval.DIV(table2ColInt), "(table1.col_interval / table2.col_int)")
assertSerialize(t, table1ColInterval.DIV(table2ColFloat), "(table1.col_interval / table2.col_float)")
}

View File

@@ -0,0 +1,19 @@
package postgres
import "github.com/go-jet/jet/v2/internal/jet"
const (
// DEFAULT is jet equivalent of SQL DEFAULT
DEFAULT = jet.DEFAULT
)
var (
// NULL is jet equivalent of SQL NULL
NULL = jet.NULL
// STAR is jet equivalent of SQL *
STAR = jet.STAR
// PLUS_INFINITY is jet equivalent for sql infinity
PLUS_INFINITY = jet.PLUS_INFINITY
// MINUS_INFINITY is jet equivalent for sql -infinity
MINUS_INFINITY = jet.MINUS_INFINITY
)

View File

@@ -0,0 +1,24 @@
package postgres
import "github.com/go-jet/jet/v2/internal/jet"
// LATERAL derived tables constructor from select statement
func LATERAL(selectStmt SelectStatement) lateralImpl {
return lateralImpl{
selectStmt: selectStmt,
}
}
type lateralImpl struct {
selectStmt SelectStatement
}
func (l lateralImpl) AS(alias string) SelectTable {
subQuery := &selectTableImpl{
SelectTable: jet.NewLateral(l.selectStmt, alias),
}
subQuery.readableTableInterfaceImpl.parent = subQuery
return subQuery
}

View File

@@ -0,0 +1,14 @@
package postgres
import "testing"
func TestLATERAL(t *testing.T) {
assertSerialize(t,
LATERAL(
SELECT(Int(1)),
).AS("lat1"),
`LATERAL (
SELECT $1
) AS lat1`)
}

View File

@@ -0,0 +1,182 @@
package postgres
import (
"time"
"github.com/go-jet/jet/v2/internal/jet"
)
// Bool is boolean literal constructor
func Bool(value bool) BoolExpression {
return CAST(jet.Bool(value)).AS_BOOL()
}
// Int is constructor for 64 bit signed integer expressions literals.
var Int = jet.Int
// Int8 is constructor for 8 bit signed integer expressions literals.
func Int8(value int8) IntegerExpression {
return CAST(jet.Int8(value)).AS_SMALLINT()
}
// Int16 is constructor for 16 bit signed integer expressions literals.
func Int16(value int16) IntegerExpression {
return CAST(jet.Int16(value)).AS_SMALLINT()
}
// Int32 is constructor for 32 bit signed integer expressions literals.
func Int32(value int32) IntegerExpression {
return CAST(jet.Int32(value)).AS_INTEGER()
}
// Int64 is constructor for 64 bit signed integer expressions literals.
func Int64(value int64) IntegerExpression {
return CAST(jet.Int(value)).AS_BIGINT()
}
// Uint8 is constructor for 8 bit unsigned integer expressions literals.
func Uint8(value uint8) IntegerExpression {
return CAST(jet.Uint8(value)).AS_SMALLINT()
}
// Uint16 is constructor for 16 bit unsigned integer expressions literals.
func Uint16(value uint16) IntegerExpression {
return CAST(jet.Uint16(value)).AS_INTEGER()
}
// Uint32 is constructor for 32 bit unsigned integer expressions literals.
func Uint32(value uint32) IntegerExpression {
return CAST(jet.Uint32(value)).AS_BIGINT()
}
// Float creates new float literal expression
var Float = jet.Float
// Real is placeholder constructor for 32-bit float literals
func Real(value float32) FloatExpression {
return CAST(jet.Literal(value)).AS_REAL()
}
// Double is placeholder constructor for 64-bit float literals
func Double(value float64) FloatExpression {
return CAST(jet.Literal(value)).AS_DOUBLE()
}
// Decimal creates new float literal expression
var Decimal = jet.Decimal
// String is a parameter constructor for the PostgreSQL text type. Using the `Text` constructor is
// generally preferable.
//
// WARNING: String always applies a `text` type cast, which can be problematic if a parameter is compared
// to a `character` column, as this may prevent index usage. In such cases, consider using the Char
// constructor instead. See also other PostgreSQL-specific constructors: Text, Char, and VarChar.
func String(value string) StringExpression {
return CAST(jet.String(value)).AS_TEXT()
}
// Text is a parameter constructor for the PostgreSQL text type. This constructor also adds an
// explicit placeholder type cast to text in the generated query, such as `$3::text`.
// Example usage:
//
// Text("English")
func Text(value string) StringExpression {
return CAST(jet.Literal(value)).AS_TEXT()
}
// Char is a parameter constructor for the PostgreSQL character type. This constructor also adds an
// explicit placeholder type cast to text in the generated query, such as `$3::char(30)`.
// Example usage:
//
// Char(20)("English")
func Char(length ...int) func(value string) StringExpression {
return func(value string) StringExpression {
return CAST(StringExp(jet.Literal(value))).AS_CHAR(length...)
}
}
// VarChar is a parameter constructor for the PostgreSQL character varying type. This constructor
// also adds an explicit placeholder type cast to text in the generated query, such as `$3::varchar(30)`.
// Example usage:
//
// VarChar(20)("English")
// VarChar()("English")
func VarChar(length ...int) func(value string) StringExpression {
return func(value string) StringExpression {
return CAST(StringExp(jet.Literal(value))).AS_VARCHAR(length...)
}
}
// Json creates new json literal expression
func Json(value interface{}) StringExpression {
switch value.(type) {
case string, []byte:
default:
panic("Bytea parameter value has to be of the type string or []byte")
}
return StringExp(CAST(jet.Literal(value)).AS("json"))
}
// UUID is a helper function to create string literal expression from uuid object
// value can be any uuid type with a String method
var UUID = jet.UUID
// Bytea creates new bytea literal expression
func Bytea(value interface{}) StringExpression {
switch value.(type) {
case string, []byte:
default:
panic("Bytea parameter value has to be of the type string or []byte")
}
return CAST(jet.Literal(value)).AS_BYTEA()
}
// Date creates new date literal expression
func Date(year int, month time.Month, day int) DateExpression {
return CAST(jet.Date(year, month, day)).AS_DATE()
}
// DateT creates new date literal expression from time.Time object
func DateT(t time.Time) DateExpression {
return CAST(jet.DateT(t)).AS_DATE()
}
// Time creates new time literal expression
func Time(hour, minute, second int, nanoseconds ...time.Duration) TimeExpression {
return CAST(jet.Time(hour, minute, second, nanoseconds...)).AS_TIME()
}
// TimeT creates new time literal expression from time.Time object
func TimeT(t time.Time) TimeExpression {
return CAST(jet.TimeT(t)).AS_TIME()
}
// Timez creates new time with time zone literal expression
func Timez(hour, minute, second int, milliseconds time.Duration, timezone string) TimezExpression {
return CAST(jet.Timez(hour, minute, second, milliseconds, timezone)).AS_TIMEZ()
}
// TimezT creates new time with time zone literal expression from time.Time object
func TimezT(t time.Time) TimezExpression {
return CAST(jet.TimezT(t)).AS_TIMEZ()
}
// Timestamp creates new timestamp literal expression
func Timestamp(year int, month time.Month, day, hour, minute, second int, milliseconds ...time.Duration) TimestampExpression {
return CAST(jet.Timestamp(year, month, day, hour, minute, second, milliseconds...)).AS_TIMESTAMP()
}
// TimestampT creates new timestamp literal expression from time.Time object
func TimestampT(t time.Time) TimestampExpression {
return CAST(jet.TimestampT(t)).AS_TIMESTAMP()
}
// Timestampz creates new timestamp with time zone literal expression
func Timestampz(year int, month time.Month, day, hour, minute, second int, milliseconds time.Duration, timezone string) TimestampzExpression {
return CAST(jet.Timestampz(year, month, day, hour, minute, second, milliseconds, timezone)).AS_TIMESTAMPZ()
}
// TimestampzT creates new timestamp literal expression from time.Time object
func TimestampzT(t time.Time) TimestampzExpression {
return CAST(jet.TimestampzT(t)).AS_TIMESTAMPZ()
}

View File

@@ -0,0 +1,105 @@
package postgres
import (
"math"
"testing"
"time"
)
func TestBool(t *testing.T) {
assertSerialize(t, Bool(false), `$1::boolean`, false)
}
func TestInt(t *testing.T) {
assertSerialize(t, Int(11), `$1`, int64(11))
}
func TestInt8(t *testing.T) {
val := int8(math.MinInt8)
assertSerialize(t, Int8(val), `$1::smallint`, val)
}
func TestInt16(t *testing.T) {
val := int16(math.MinInt16)
assertSerialize(t, Int16(val), `$1::smallint`, val)
}
func TestInt32(t *testing.T) {
val := int32(math.MinInt32)
assertSerialize(t, Int32(val), `$1::integer`, val)
}
func TestInt64(t *testing.T) {
val := int64(math.MinInt64)
assertSerialize(t, Int64(val), `$1::bigint`, val)
}
func TestUint8(t *testing.T) {
val := uint8(math.MaxUint8)
assertSerialize(t, Uint8(val), `$1::smallint`, val)
}
func TestUint16(t *testing.T) {
val := uint16(math.MaxUint16)
assertSerialize(t, Uint16(val), `$1::integer`, val)
}
func TestUint32(t *testing.T) {
val := uint32(math.MaxUint32)
assertSerialize(t, Uint32(val), `$1::bigint`, val)
}
func TestFloat(t *testing.T) {
assertSerialize(t, Float(12.34), `$1`, float64(12.34))
assertSerialize(t, Real(12.34), `$1::real`, float32(12.34))
assertSerialize(t, Double(12.34), `$1::double precision`, float64(12.34))
}
func TestString(t *testing.T) {
assertSerialize(t, String("Some text"), `$1::text`, "Some text")
assertSerialize(t, Text("Some text"), `$1::text`, "Some text")
assertSerialize(t, Char(20)("John Doe"), `$1::char(20)`, "John Doe")
assertSerialize(t, Char()("John Doe"), `$1::char`, "John Doe")
assertSerialize(t, VarChar(20)("John Doe"), `$1::varchar(20)`, "John Doe")
assertSerialize(t, VarChar()("John Doe"), `$1::varchar`, "John Doe")
}
func TestBytea(t *testing.T) {
assertSerialize(t, Bytea("Some text"), `$1::bytea`, "Some text")
assertSerialize(t, Bytea([]byte("Some byte array")), `$1::bytea`, []byte("Some byte array"))
}
func TestJson(t *testing.T) {
assertSerialize(t, Json("{\"key\": \"value\"}"), `$1::json`, "{\"key\": \"value\"}")
assertSerialize(t, Json([]byte("{\"key\": \"value\"}")), `$1::json`, []byte("{\"key\": \"value\"}"))
}
func TestDate(t *testing.T) {
assertSerialize(t, Date(2014, time.January, 2), `$1::date`, "2014-01-02")
assertSerialize(t, DateT(time.Now()), `$1::date`)
}
func TestTime(t *testing.T) {
assertSerialize(t, Time(10, 15, 30), `$1::time without time zone`, "10:15:30")
assertSerialize(t, TimeT(time.Now()), `$1::time without time zone`)
}
func TestTimez(t *testing.T) {
assertSerialize(t, Timez(10, 15, 30, 0, "UTC"),
`$1::time with time zone`, "10:15:30 UTC")
assertSerialize(t, TimezT(time.Now()), `$1::time with time zone`)
}
func TestTimestamp(t *testing.T) {
assertSerialize(t, Timestamp(2010, time.March, 30, 10, 15, 30),
`$1::timestamp without time zone`, "2010-03-30 10:15:30")
assertSerialize(t, TimestampT(time.Now()), `$1::timestamp without time zone`)
}
func TestTimestampz(t *testing.T) {
assertSerialize(t, Timestampz(2010, time.March, 30, 10, 15, 30, 0, "UTC"),
`$1::timestamp with time zone`, "2010-03-30 10:15:30 UTC")
assertSerialize(t, TimestampzT(time.Now()), `$1::timestamp with time zone`)
}

View File

@@ -0,0 +1,56 @@
package postgres
import "github.com/go-jet/jet/v2/internal/jet"
// TableLockMode is a type of possible SQL table lock
type TableLockMode string
// Lock types for LockStatement.
const (
LOCK_ACCESS_SHARE TableLockMode = "ACCESS SHARE"
LOCK_ROW_SHARE TableLockMode = "ROW SHARE"
LOCK_ROW_EXCLUSIVE TableLockMode = "ROW EXCLUSIVE"
LOCK_SHARE_UPDATE_EXCLUSIVE TableLockMode = "SHARE UPDATE EXCLUSIVE"
LOCK_SHARE TableLockMode = "SHARE"
LOCK_SHARE_ROW_EXCLUSIVE TableLockMode = "SHARE ROW EXCLUSIVE"
LOCK_EXCLUSIVE TableLockMode = "EXCLUSIVE"
LOCK_ACCESS_EXCLUSIVE TableLockMode = "ACCESS EXCLUSIVE"
)
// LockStatement is interface for MySQL LOCK tables
type LockStatement interface {
Statement
IN(lockMode TableLockMode) LockStatement
NOWAIT() LockStatement
}
// LOCK creates LockStatement from list of tables
func LOCK(tables ...jet.SerializerTable) LockStatement {
newLock := &lockStatementImpl{}
newLock.SerializerStatement = jet.NewStatementImpl(Dialect, jet.LockStatementType, newLock,
&newLock.StatementBegin, &newLock.In, &newLock.NoWait)
newLock.StatementBegin.Name = "LOCK TABLE"
newLock.StatementBegin.Tables = tables
newLock.NoWait.Name = "NOWAIT"
return newLock
}
type lockStatementImpl struct {
jet.SerializerStatement
StatementBegin jet.ClauseStatementBegin
In jet.ClauseIn
NoWait jet.ClauseOptional
}
func (l *lockStatementImpl) IN(lockMode TableLockMode) LockStatement {
l.In.LockMode = string(lockMode)
return l
}
func (l *lockStatementImpl) NOWAIT() LockStatement {
l.NoWait.Show = true
return l
}

View File

@@ -0,0 +1,32 @@
package postgres
import (
"testing"
)
func TestLockTable(t *testing.T) {
assertStatementSql(t, table1.LOCK().IN(LOCK_ACCESS_SHARE), `
LOCK TABLE db.table1 IN ACCESS SHARE MODE;
`)
assertStatementSql(t, table1.LOCK().IN(LOCK_ROW_SHARE), `
LOCK TABLE db.table1 IN ROW SHARE MODE;
`)
assertStatementSql(t, table1.LOCK().IN(LOCK_ROW_EXCLUSIVE), `
LOCK TABLE db.table1 IN ROW EXCLUSIVE MODE;
`)
assertStatementSql(t, table1.LOCK().IN(LOCK_SHARE_UPDATE_EXCLUSIVE), `
LOCK TABLE db.table1 IN SHARE UPDATE EXCLUSIVE MODE;
`)
assertStatementSql(t, table1.LOCK().IN(LOCK_SHARE), `
LOCK TABLE db.table1 IN SHARE MODE;
`)
assertStatementSql(t, table1.LOCK().IN(LOCK_SHARE_ROW_EXCLUSIVE), `
LOCK TABLE db.table1 IN SHARE ROW EXCLUSIVE MODE;
`)
assertStatementSql(t, table1.LOCK().IN(LOCK_EXCLUSIVE), `
LOCK TABLE db.table1 IN EXCLUSIVE MODE;
`)
assertStatementSql(t, table1.LOCK().IN(LOCK_ACCESS_EXCLUSIVE).NOWAIT(), `
LOCK TABLE db.table1 IN ACCESS EXCLUSIVE MODE NOWAIT;
`)
}

View File

@@ -0,0 +1,12 @@
package postgres
import "github.com/go-jet/jet/v2/internal/jet"
// NOT returns negation of bool expression result
var NOT = jet.NOT
// BIT_NOT inverts every bit in integer expression result
var BIT_NOT = jet.BIT_NOT
// DISTINCT operator can be used to return distinct values of expr
var DISTINCT = jet.DISTINCT

View File

@@ -0,0 +1,231 @@
package postgres
import (
"math"
"github.com/go-jet/jet/v2/internal/jet"
)
// RowLock is interface for SELECT statement row lock types
type RowLock = jet.RowLock
// Row lock types
var (
UPDATE = jet.NewRowLock("UPDATE")
NO_KEY_UPDATE = jet.NewRowLock("NO KEY UPDATE")
SHARE = jet.NewRowLock("SHARE")
KEY_SHARE = jet.NewRowLock("KEY SHARE")
)
// Window function clauses
var (
PARTITION_BY = jet.PARTITION_BY
ORDER_BY = jet.ORDER_BY
UNBOUNDED = int64(math.MaxInt64)
CURRENT_ROW = jet.CURRENT_ROW
)
// PRECEDING window frame clause
func PRECEDING(offset int64) jet.FrameExtent {
return jet.PRECEDING(toJetFrameOffset(offset))
}
// FOLLOWING window frame clause
func FOLLOWING(offset int64) jet.FrameExtent {
return jet.FOLLOWING(toJetFrameOffset(offset))
}
// Window definition reference
var Window = jet.WindowName
// SelectStatement is interface for PostgreSQL SELECT statement
type SelectStatement interface {
Statement
jet.HasProjections
Expression
DISTINCT(on ...jet.ColumnExpression) SelectStatement
FROM(tables ...ReadableTable) SelectStatement
WHERE(expression BoolExpression) SelectStatement
GROUP_BY(groupByClauses ...GroupByClause) SelectStatement
HAVING(boolExpression BoolExpression) SelectStatement
WINDOW(name string) windowExpand
ORDER_BY(orderByClauses ...OrderByClause) SelectStatement
LIMIT(limit int64) SelectStatement
OFFSET(offset int64) SelectStatement
// OFFSET_e can be used when an integer expression is needed as offset, otherwise OFFSET can be used
OFFSET_e(offset IntegerExpression) SelectStatement
FETCH_FIRST(count IntegerExpression) fetchExpand
FOR(lock RowLock) SelectStatement
UNION(rhs SelectStatement) setStatement
UNION_ALL(rhs SelectStatement) setStatement
INTERSECT(rhs SelectStatement) setStatement
INTERSECT_ALL(rhs SelectStatement) setStatement
EXCEPT(rhs SelectStatement) setStatement
EXCEPT_ALL(rhs SelectStatement) setStatement
AsTable(alias string) SelectTable
}
// SELECT creates new SelectStatement with list of projections
func SELECT(projection Projection, projections ...Projection) SelectStatement {
return newSelectStatement(nil, append([]Projection{projection}, projections...))
}
func newSelectStatement(table ReadableTable, projections []Projection) SelectStatement {
newSelect := &selectStatementImpl{}
newSelect.ExpressionStatement = jet.NewExpressionStatementImpl(Dialect, jet.SelectStatementType, newSelect,
&newSelect.Select,
&newSelect.From,
&newSelect.Where,
&newSelect.GroupBy,
&newSelect.Having,
&newSelect.Window,
&newSelect.OrderBy,
&newSelect.Limit,
&newSelect.Offset,
&newSelect.Fetch,
&newSelect.For)
newSelect.Select.ProjectionList = projections
if table != nil {
newSelect.From.Tables = []jet.Serializer{table}
}
newSelect.Limit.Count = -1
newSelect.setOperatorsImpl.parent = newSelect
return newSelect
}
type selectStatementImpl struct {
jet.ExpressionStatement
setOperatorsImpl
Select jet.ClauseSelect
From jet.ClauseFrom
Where jet.ClauseWhere
GroupBy jet.ClauseGroupBy
Having jet.ClauseHaving
Window jet.ClauseWindow
OrderBy jet.ClauseOrderBy
Limit jet.ClauseLimit
Offset jet.ClauseOffset
Fetch jet.ClauseFetch
For jet.ClauseFor
}
func (s *selectStatementImpl) DISTINCT(on ...jet.ColumnExpression) SelectStatement {
s.Select.Distinct = true
s.Select.DistinctOnColumns = on
return s
}
func (s *selectStatementImpl) FROM(tables ...ReadableTable) SelectStatement {
s.From.Tables = readableTablesToSerializerList(tables)
return s
}
func (s *selectStatementImpl) WHERE(condition BoolExpression) SelectStatement {
s.Where.Condition = condition
return s
}
func (s *selectStatementImpl) GROUP_BY(groupByClauses ...GroupByClause) SelectStatement {
s.GroupBy.List = groupByClauses
return s
}
func (s *selectStatementImpl) HAVING(boolExpression BoolExpression) SelectStatement {
s.Having.Condition = boolExpression
return s
}
func (s *selectStatementImpl) WINDOW(name string) windowExpand {
s.Window.Definitions = append(s.Window.Definitions, jet.WindowDefinition{Name: name})
return windowExpand{selectStatement: s}
}
func (s *selectStatementImpl) ORDER_BY(orderByClauses ...OrderByClause) SelectStatement {
s.OrderBy.List = orderByClauses
return s
}
func (s *selectStatementImpl) LIMIT(limit int64) SelectStatement {
s.Limit.Count = limit
return s
}
func (s *selectStatementImpl) OFFSET(offset int64) SelectStatement {
s.Offset.Count = Int(offset)
return s
}
func (s *selectStatementImpl) OFFSET_e(offset IntegerExpression) SelectStatement {
s.Offset.Count = offset
return s
}
func (s *selectStatementImpl) FETCH_FIRST(count IntegerExpression) fetchExpand {
s.Fetch.Count = count
return fetchExpand{
selectStatement: s,
}
}
func (s *selectStatementImpl) FOR(lock RowLock) SelectStatement {
s.For.Lock = lock
return s
}
func (s *selectStatementImpl) AsTable(alias string) SelectTable {
return newSelectTable(s, alias, nil)
}
//-----------------------------------------------------
type windowExpand struct {
selectStatement *selectStatementImpl
}
func (w windowExpand) AS(window ...jet.Window) SelectStatement {
if len(window) == 0 {
return w.selectStatement
}
windowsDefinition := w.selectStatement.Window.Definitions
windowsDefinition[len(windowsDefinition)-1].Window = window[0]
return w.selectStatement
}
func toJetFrameOffset(offset int64) jet.Serializer {
if offset == UNBOUNDED {
return jet.UNBOUNDED
}
return jet.FixedLiteral(offset)
}
func readableTablesToSerializerList(tables []ReadableTable) []jet.Serializer {
var ret []jet.Serializer
for _, table := range tables {
ret = append(ret, table)
}
return ret
}
type fetchExpand struct {
selectStatement *selectStatementImpl
}
func (f fetchExpand) ROWS_ONLY() SelectStatement {
f.selectStatement.Fetch.WithTies = false
return f.selectStatement
}
func (f fetchExpand) ROWS_WITH_TIES() SelectStatement {
f.selectStatement.Fetch.WithTies = true
return f.selectStatement
}

View File

@@ -0,0 +1,142 @@
package postgres
import (
"testing"
)
func TestInvalidSelect(t *testing.T) {
assertStatementSqlErr(t, SELECT(nil), "jet: Projection is nil")
}
func TestSelectColumnList(t *testing.T) {
columnList := ColumnList{table2ColInt, table2ColFloat, table3ColInt}
assertStatementSql(t, SELECT(columnList).FROM(table2), `
SELECT table2.col_int AS "table2.col_int",
table2.col_float AS "table2.col_float",
table3.col_int AS "table3.col_int"
FROM db.table2;
`)
}
func TestSelectLiterals(t *testing.T) {
assertStatementSql(t, SELECT(Int(1), Float(2.2), Bool(false)).FROM(table1), `
SELECT $1,
$2,
$3::boolean
FROM db.table1;
`, int64(1), 2.2, false)
}
func TestSelectDistinct(t *testing.T) {
assertStatementSql(t, SELECT(table1ColBool).DISTINCT().FROM(table1), `
SELECT DISTINCT table1.col_bool AS "table1.col_bool"
FROM db.table1;
`)
}
func TestSelectFrom(t *testing.T) {
assertStatementSql(t, SELECT(table1ColInt, table2ColFloat).FROM(table1), `
SELECT table1.col_int AS "table1.col_int",
table2.col_float AS "table2.col_float"
FROM db.table1;
`)
assertStatementSql(t, SELECT(table1ColInt, table2ColFloat).FROM(table1.INNER_JOIN(table2, table1ColInt.EQ(table2ColInt))), `
SELECT table1.col_int AS "table1.col_int",
table2.col_float AS "table2.col_float"
FROM db.table1
INNER JOIN db.table2 ON (table1.col_int = table2.col_int);
`)
assertStatementSql(t, table1.INNER_JOIN(table2, table1ColInt.EQ(table2ColInt)).SELECT(table1ColInt, table2ColFloat), `
SELECT table1.col_int AS "table1.col_int",
table2.col_float AS "table2.col_float"
FROM db.table1
INNER JOIN db.table2 ON (table1.col_int = table2.col_int);
`)
}
func TestSelectWhere(t *testing.T) {
assertStatementSql(t, SELECT(table1ColInt).FROM(table1).WHERE(Bool(true)), `
SELECT table1.col_int AS "table1.col_int"
FROM db.table1
WHERE $1::boolean;
`, true)
assertStatementSql(t, SELECT(table1ColInt).FROM(table1).WHERE(table1ColInt.GT_EQ(Int(10))), `
SELECT table1.col_int AS "table1.col_int"
FROM db.table1
WHERE table1.col_int >= $1;
`, int64(10))
}
func TestSelectGroupBy(t *testing.T) {
assertStatementSql(t, SELECT(table2ColInt).FROM(table2).GROUP_BY(table2ColFloat), `
SELECT table2.col_int AS "table2.col_int"
FROM db.table2
GROUP BY table2.col_float;
`)
}
func TestSelectHaving(t *testing.T) {
assertStatementSql(t, SELECT(table3ColInt).FROM(table3).HAVING(table1ColBool.EQ(Bool(true))), `
SELECT table3.col_int AS "table3.col_int"
FROM db.table3
HAVING table1.col_bool = $1::boolean;
`, true)
}
func TestSelectOrderBy(t *testing.T) {
assertStatementSql(t, SELECT(table2ColFloat).FROM(table2).ORDER_BY(table2ColInt.DESC()), `
SELECT table2.col_float AS "table2.col_float"
FROM db.table2
ORDER BY table2.col_int DESC;
`)
assertStatementSql(t, SELECT(table2ColFloat).FROM(table2).ORDER_BY(table2ColInt.DESC(), table2ColInt.ASC()), `
SELECT table2.col_float AS "table2.col_float"
FROM db.table2
ORDER BY table2.col_int DESC, table2.col_int ASC;
`)
}
func TestSelectLimitOffset(t *testing.T) {
assertStatementSql(t, SELECT(table2ColInt).FROM(table2).LIMIT(10), `
SELECT table2.col_int AS "table2.col_int"
FROM db.table2
LIMIT $1;
`, int64(10))
assertStatementSql(t, SELECT(table2ColInt).FROM(table2).LIMIT(10).OFFSET(2), `
SELECT table2.col_int AS "table2.col_int"
FROM db.table2
LIMIT $1
OFFSET $2;
`, int64(10), int64(2))
}
func TestSelectLock(t *testing.T) {
assertStatementSql(t, SELECT(table1ColBool).FROM(table1).FOR(UPDATE()), `
SELECT table1.col_bool AS "table1.col_bool"
FROM db.table1
FOR UPDATE;
`)
assertStatementSql(t, SELECT(table1ColBool).FROM(table1).FOR(SHARE().NOWAIT()), `
SELECT table1.col_bool AS "table1.col_bool"
FROM db.table1
FOR SHARE NOWAIT;
`)
assertStatementSql(t, SELECT(table1ColBool).FROM(table1).FOR(KEY_SHARE().NOWAIT()), `
SELECT table1.col_bool AS "table1.col_bool"
FROM db.table1
FOR KEY SHARE NOWAIT;
`)
assertStatementSql(t, SELECT(table1ColBool).FROM(table1).FOR(NO_KEY_UPDATE().SKIP_LOCKED()), `
SELECT table1.col_bool AS "table1.col_bool"
FROM db.table1
FOR NO KEY UPDATE SKIP LOCKED;
`)
assertStatementSql(t, SELECT(table1ColBool).FROM(table1).FOR(UPDATE().OF(table1, table2).NOWAIT()), `
SELECT table1.col_bool AS "table1.col_bool"
FROM db.table1
FOR UPDATE OF table1, table2 NOWAIT;
`)
}

View File

@@ -0,0 +1,24 @@
package postgres
import "github.com/go-jet/jet/v2/internal/jet"
// SelectTable is interface for postgres temporary tables like sub-queries, VALUES, CTEs etc...
type SelectTable interface {
readableTable
jet.SelectTable
}
type selectTableImpl struct {
jet.SelectTable
readableTableInterfaceImpl
}
func newSelectTable(serializerWithProjections jet.SerializerHasProjections, alias string, columnAliases []jet.ColumnExpression) SelectTable {
subQuery := &selectTableImpl{
SelectTable: jet.NewSelectTable(serializerWithProjections, alias, columnAliases),
}
subQuery.readableTableInterfaceImpl.parent = subQuery
return subQuery
}

View File

@@ -0,0 +1,150 @@
package postgres
import "github.com/go-jet/jet/v2/internal/jet"
// UNION effectively appends the result of sub-queries(select statements) into single query.
// It eliminates duplicate rows from its result.
func UNION(lhs, rhs jet.SerializerStatement, selects ...jet.SerializerStatement) setStatement {
return newSetStatementImpl(union, false, toSelectList(lhs, rhs, selects...))
}
// UNION_ALL effectively appends the result of sub-queries(select statements) into single query.
// It does not eliminates duplicate rows from its result.
func UNION_ALL(lhs, rhs jet.SerializerStatement, selects ...jet.SerializerStatement) setStatement {
return newSetStatementImpl(union, true, toSelectList(lhs, rhs, selects...))
}
// INTERSECT returns all rows that are in query results.
// It eliminates duplicate rows from its result.
func INTERSECT(lhs, rhs jet.SerializerStatement, selects ...jet.SerializerStatement) setStatement {
return newSetStatementImpl(intersect, false, toSelectList(lhs, rhs, selects...))
}
// INTERSECT_ALL returns all rows that are in query results.
// It does not eliminates duplicate rows from its result.
func INTERSECT_ALL(lhs, rhs jet.SerializerStatement, selects ...jet.SerializerStatement) setStatement {
return newSetStatementImpl(intersect, true, toSelectList(lhs, rhs, selects...))
}
// EXCEPT returns all rows that are in the result of query lhs but not in the result of query rhs.
// It eliminates duplicate rows from its result.
func EXCEPT(lhs, rhs jet.SerializerStatement) setStatement {
return newSetStatementImpl(except, false, toSelectList(lhs, rhs))
}
// EXCEPT_ALL returns all rows that are in the result of query lhs but not in the result of query rhs.
// It does not eliminates duplicate rows from its result.
func EXCEPT_ALL(lhs, rhs jet.SerializerStatement) setStatement {
return newSetStatementImpl(except, true, toSelectList(lhs, rhs))
}
type setStatement interface {
setOperators
ORDER_BY(orderByClauses ...OrderByClause) setStatement
LIMIT(limit int64) setStatement
OFFSET(offset int64) setStatement
// OFFSET_e can be used when an integer expression is needed as offset, otherwise OFFSET can be used
OFFSET_e(offset IntegerExpression) setStatement
AsTable(alias string) SelectTable
}
type setOperators interface {
Statement
jet.HasProjections
Expression
UNION(rhs SelectStatement) setStatement
UNION_ALL(rhs SelectStatement) setStatement
INTERSECT(rhs SelectStatement) setStatement
INTERSECT_ALL(rhs SelectStatement) setStatement
EXCEPT(rhs SelectStatement) setStatement
EXCEPT_ALL(rhs SelectStatement) setStatement
}
type setOperatorsImpl struct {
parent setOperators
}
func (s *setOperatorsImpl) UNION(rhs SelectStatement) setStatement {
return UNION(s.parent, rhs)
}
func (s *setOperatorsImpl) UNION_ALL(rhs SelectStatement) setStatement {
return UNION_ALL(s.parent, rhs)
}
func (s *setOperatorsImpl) INTERSECT(rhs SelectStatement) setStatement {
return INTERSECT(s.parent, rhs)
}
func (s *setOperatorsImpl) INTERSECT_ALL(rhs SelectStatement) setStatement {
return INTERSECT_ALL(s.parent, rhs)
}
func (s *setOperatorsImpl) EXCEPT(rhs SelectStatement) setStatement {
return EXCEPT(s.parent, rhs)
}
func (s *setOperatorsImpl) EXCEPT_ALL(rhs SelectStatement) setStatement {
return EXCEPT_ALL(s.parent, rhs)
}
type setStatementImpl struct {
jet.ExpressionStatement
setOperatorsImpl
setOperator jet.ClauseSetStmtOperator
}
func newSetStatementImpl(operator string, all bool, selects []jet.SerializerStatement) setStatement {
newSetStatement := &setStatementImpl{}
newSetStatement.ExpressionStatement = jet.NewExpressionStatementImpl(Dialect, jet.SetStatementType, newSetStatement,
&newSetStatement.setOperator)
newSetStatement.setOperator.Operator = operator
newSetStatement.setOperator.All = all
newSetStatement.setOperator.Selects = selects
newSetStatement.setOperator.Limit.Count = -1
newSetStatement.setOperatorsImpl.parent = newSetStatement
return newSetStatement
}
func (s *setStatementImpl) ORDER_BY(orderByClauses ...OrderByClause) setStatement {
s.setOperator.OrderBy.List = orderByClauses
return s
}
func (s *setStatementImpl) LIMIT(limit int64) setStatement {
s.setOperator.Limit.Count = limit
return s
}
func (s *setStatementImpl) OFFSET(offset int64) setStatement {
s.setOperator.Offset.Count = Int(offset)
return s
}
func (s *setStatementImpl) OFFSET_e(offset IntegerExpression) setStatement {
s.setOperator.Offset.Count = offset
return s
}
func (s *setStatementImpl) AsTable(alias string) SelectTable {
return newSelectTable(s, alias, nil)
}
const (
union = "UNION"
intersect = "INTERSECT"
except = "EXCEPT"
)
func toSelectList(lhs, rhs jet.SerializerStatement, selects ...jet.SerializerStatement) []jet.SerializerStatement {
return append([]jet.SerializerStatement{lhs, rhs}, selects...)
}

View File

@@ -0,0 +1,81 @@
package postgres
import (
"testing"
)
func TestSelectSets(t *testing.T) {
select1 := SELECT(table1ColBool).FROM(table1)
select2 := SELECT(table2ColBool).FROM(table2)
assertStatementSql(t, select1.UNION(select2), `
(
SELECT table1.col_bool AS "table1.col_bool"
FROM db.table1
)
UNION
(
SELECT table2.col_bool AS "table2.col_bool"
FROM db.table2
);
`)
assertStatementSql(t, select1.UNION_ALL(select2), `
(
SELECT table1.col_bool AS "table1.col_bool"
FROM db.table1
)
UNION ALL
(
SELECT table2.col_bool AS "table2.col_bool"
FROM db.table2
);
`)
assertStatementSql(t, select1.INTERSECT(select2), `
(
SELECT table1.col_bool AS "table1.col_bool"
FROM db.table1
)
INTERSECT
(
SELECT table2.col_bool AS "table2.col_bool"
FROM db.table2
);
`)
assertStatementSql(t, select1.INTERSECT_ALL(select2), `
(
SELECT table1.col_bool AS "table1.col_bool"
FROM db.table1
)
INTERSECT ALL
(
SELECT table2.col_bool AS "table2.col_bool"
FROM db.table2
);
`)
assertStatementSql(t, select1.EXCEPT(select2), `
(
SELECT table1.col_bool AS "table1.col_bool"
FROM db.table1
)
EXCEPT
(
SELECT table2.col_bool AS "table2.col_bool"
FROM db.table2
);
`)
assertStatementSql(t, select1.EXCEPT_ALL(select2), `
(
SELECT table1.col_bool AS "table1.col_bool"
FROM db.table1
)
EXCEPT ALL
(
SELECT table2.col_bool AS "table2.col_bool"
FROM db.table2
);
`)
}

View File

@@ -0,0 +1,10 @@
package postgres
import (
"github.com/go-jet/jet/v2/internal/jet"
)
// RawStatement creates new sql statements from raw query and optional map of named arguments
func RawStatement(rawQuery string, namedArguments ...RawArgs) jet.SerializerStatement {
return jet.RawStatement(Dialect, rawQuery, namedArguments...)
}

View File

@@ -0,0 +1,137 @@
package postgres
import "github.com/go-jet/jet/v2/internal/jet"
// Table is interface for MySQL tables
type Table interface {
readableTable
writableTable
jet.SerializerTable
}
type readableTable interface {
// Generates a select query on the current tableName.
SELECT(projection Projection, projections ...Projection) SelectStatement
// Creates a inner join tableName Expression using onCondition.
INNER_JOIN(table ReadableTable, onCondition BoolExpression) ReadableTable
// Creates a left join tableName Expression using onCondition.
LEFT_JOIN(table ReadableTable, onCondition BoolExpression) ReadableTable
// Creates a right join tableName Expression using onCondition.
RIGHT_JOIN(table ReadableTable, onCondition BoolExpression) ReadableTable
// Creates a full join tableName Expression using onCondition.
FULL_JOIN(table ReadableTable, onCondition BoolExpression) ReadableTable
// Creates a cross join tableName Expression using onCondition.
CROSS_JOIN(table ReadableTable) ReadableTable
}
type writableTable interface {
INSERT(columns ...jet.Column) InsertStatement
UPDATE(columns ...jet.Column) UpdateStatement
DELETE() DeleteStatement
LOCK() LockStatement
}
// ReadableTable interface
type ReadableTable interface {
readableTable
jet.Serializer
}
// WritableTable interface
type WritableTable interface {
jet.Table
writableTable
jet.Serializer
}
type readableTableInterfaceImpl struct {
parent ReadableTable
}
// Generates a select query on the current tableName.
func (r readableTableInterfaceImpl) SELECT(projection1 Projection, projections ...Projection) SelectStatement {
return newSelectStatement(r.parent, append([]Projection{projection1}, projections...))
}
// Creates a inner join tableName Expression using onCondition.
func (r readableTableInterfaceImpl) INNER_JOIN(table ReadableTable, onCondition BoolExpression) ReadableTable {
return newJoinTable(r.parent, table, jet.InnerJoin, onCondition)
}
// Creates a left join tableName Expression using onCondition.
func (r readableTableInterfaceImpl) LEFT_JOIN(table ReadableTable, onCondition BoolExpression) ReadableTable {
return newJoinTable(r.parent, table, jet.LeftJoin, onCondition)
}
// Creates a right join tableName Expression using onCondition.
func (r readableTableInterfaceImpl) RIGHT_JOIN(table ReadableTable, onCondition BoolExpression) ReadableTable {
return newJoinTable(r.parent, table, jet.RightJoin, onCondition)
}
func (r readableTableInterfaceImpl) FULL_JOIN(table ReadableTable, onCondition BoolExpression) ReadableTable {
return newJoinTable(r.parent, table, jet.FullJoin, onCondition)
}
func (r readableTableInterfaceImpl) CROSS_JOIN(table ReadableTable) ReadableTable {
return newJoinTable(r.parent, table, jet.CrossJoin, nil)
}
type writableTableInterfaceImpl struct {
parent WritableTable
}
func (w *writableTableInterfaceImpl) INSERT(columns ...jet.Column) InsertStatement {
return newInsertStatement(w.parent, jet.UnwidColumnList(columns))
}
func (w *writableTableInterfaceImpl) UPDATE(columns ...jet.Column) UpdateStatement {
return newUpdateStatement(w.parent, jet.UnwidColumnList(columns))
}
func (w *writableTableInterfaceImpl) DELETE() DeleteStatement {
return newDeleteStatement(w.parent)
}
func (w *writableTableInterfaceImpl) LOCK() LockStatement {
return LOCK(w.parent)
}
type tableImpl struct {
readableTableInterfaceImpl
writableTableInterfaceImpl
jet.SerializerTable
}
// NewTable creates new table with schema Name, table Name and list of columns
func NewTable(schemaName, name, alias string, columns ...jet.ColumnExpression) Table {
t := &tableImpl{
SerializerTable: jet.NewTable(schemaName, name, alias, columns...),
}
t.readableTableInterfaceImpl.parent = t
t.writableTableInterfaceImpl.parent = t
return t
}
type joinTable struct {
readableTableInterfaceImpl
jet.JoinTable
}
func newJoinTable(lhs jet.Serializer, rhs jet.Serializer, joinType jet.JoinType, onCondition BoolExpression) ReadableTable {
newJoinTable := &joinTable{
JoinTable: jet.NewJoinTable(lhs, rhs, joinType, onCondition),
}
newJoinTable.readableTableInterfaceImpl.parent = newJoinTable
return newJoinTable
}

View File

@@ -0,0 +1,124 @@
package postgres
import (
"testing"
)
func TestJoinNilInputs(t *testing.T) {
assertSerializeErr(t, table2.INNER_JOIN(nil, table1ColBool.EQ(table2ColBool)),
"jet: right hand side of join operation is nil table")
assertSerializeErr(t, table2.INNER_JOIN(table1, nil),
"jet: join condition is nil")
}
func TestINNER_JOIN(t *testing.T) {
assertSerialize(t, table1.
INNER_JOIN(table2, table1ColInt.EQ(table2ColInt)),
`db.table1
INNER JOIN db.table2 ON (table1.col_int = table2.col_int)`)
assertSerialize(t, table1.
INNER_JOIN(table2, table1ColInt.EQ(table2ColInt)).
INNER_JOIN(table3, table1ColInt.EQ(table3ColInt)),
`db.table1
INNER JOIN db.table2 ON (table1.col_int = table2.col_int)
INNER JOIN db.table3 ON (table1.col_int = table3.col_int)`)
assertSerialize(t, table1.
INNER_JOIN(table2, table1ColInt.EQ(Int(1))).
INNER_JOIN(table3, table1ColInt.EQ(Int(2))),
`db.table1
INNER JOIN db.table2 ON (table1.col_int = $1)
INNER JOIN db.table3 ON (table1.col_int = $2)`, int64(1), int64(2))
}
func TestLEFT_JOIN(t *testing.T) {
assertSerialize(t, table1.
LEFT_JOIN(table2, table1ColInt.EQ(table2ColInt)),
`db.table1
LEFT JOIN db.table2 ON (table1.col_int = table2.col_int)`)
assertSerialize(t, table1.
LEFT_JOIN(table2, table1ColInt.EQ(table2ColInt)).
LEFT_JOIN(table3, table1ColInt.EQ(table3ColInt)),
`db.table1
LEFT JOIN db.table2 ON (table1.col_int = table2.col_int)
LEFT JOIN db.table3 ON (table1.col_int = table3.col_int)`)
assertSerialize(t, table1.
LEFT_JOIN(table2, table1ColInt.EQ(Int(1))).
LEFT_JOIN(table3, table1ColInt.EQ(Int(2))),
`db.table1
LEFT JOIN db.table2 ON (table1.col_int = $1)
LEFT JOIN db.table3 ON (table1.col_int = $2)`, int64(1), int64(2))
}
func TestRIGHT_JOIN(t *testing.T) {
assertSerialize(t, table1.
RIGHT_JOIN(table2, table1ColInt.EQ(table2ColInt)),
`db.table1
RIGHT JOIN db.table2 ON (table1.col_int = table2.col_int)`)
assertSerialize(t, table1.
RIGHT_JOIN(table2, table1ColInt.EQ(table2ColInt)).
RIGHT_JOIN(table3, table1ColInt.EQ(table3ColInt)),
`db.table1
RIGHT JOIN db.table2 ON (table1.col_int = table2.col_int)
RIGHT JOIN db.table3 ON (table1.col_int = table3.col_int)`)
assertSerialize(t, table1.
RIGHT_JOIN(table2, table1ColInt.EQ(Int(1))).
RIGHT_JOIN(table3, table1ColInt.EQ(Int(2))),
`db.table1
RIGHT JOIN db.table2 ON (table1.col_int = $1)
RIGHT JOIN db.table3 ON (table1.col_int = $2)`, int64(1), int64(2))
}
func TestFULL_JOIN(t *testing.T) {
assertSerialize(t, table1.
FULL_JOIN(table2, table1ColInt.EQ(table2ColInt)),
`db.table1
FULL JOIN db.table2 ON (table1.col_int = table2.col_int)`)
assertSerialize(t, table1.
FULL_JOIN(table2, table1ColInt.EQ(table2ColInt)).
FULL_JOIN(table3, table1ColInt.EQ(table3ColInt)),
`db.table1
FULL JOIN db.table2 ON (table1.col_int = table2.col_int)
FULL JOIN db.table3 ON (table1.col_int = table3.col_int)`)
assertSerialize(t, table1.
FULL_JOIN(table2, table1ColInt.EQ(Int(1))).
FULL_JOIN(table3, table1ColInt.EQ(Int(2))),
`db.table1
FULL JOIN db.table2 ON (table1.col_int = $1)
FULL JOIN db.table3 ON (table1.col_int = $2)`, int64(1), int64(2))
}
func TestCROSS_JOIN(t *testing.T) {
assertSerialize(t, table1.
CROSS_JOIN(table2),
`db.table1
CROSS JOIN db.table2`)
assertSerialize(t, table1.
CROSS_JOIN(table2).
CROSS_JOIN(table3),
`db.table1
CROSS JOIN db.table2
CROSS JOIN db.table3`)
}
func TestImplicitCROSS_JOIN(t *testing.T) {
assertDebugStatementSql(t,
SELECT(table1Col1, table2Col3).
FROM(table1, table2),
`
SELECT table1.col1 AS "table1.col1",
table2.col3 AS "table2.col3"
FROM db.table1,
db.table2;
`)
assertDebugStatementSql(t,
SELECT(
table1Col1, table2Col3,
).FROM(table1, table2, table3),
`
SELECT table1.col1 AS "table1.col1",
table2.col3 AS "table2.col3"
FROM db.table1,
db.table2,
db.table3;
`)
}

View File

@@ -0,0 +1,37 @@
package postgres
import "github.com/go-jet/jet/v2/internal/jet"
// Statement is common interface for all statements(SELECT, INSERT, UPDATE, DELETE, LOCK)
type Statement = jet.Statement
// Rows wraps sql.Rows type with a support for query result mapping
type Rows = jet.Rows
// Projection is interface for all projection types. Types that can be part of, for instance SELECT clause.
type Projection = jet.Projection
// ProjectionList can be used to create conditional constructed projection list.
type ProjectionList = jet.ProjectionList
// ColumnAssigment is interface wrapper around column assigment
type ColumnAssigment = jet.ColumnAssigment
// PrintableStatement is a statement which sql query can be logged
type PrintableStatement = jet.PrintableStatement
// OrderByClause is the combination of an expression and the wanted ordering to use as input for ORDER BY.
type OrderByClause = jet.OrderByClause
// GroupByClause interface to use as input for GROUP_BY
type GroupByClause = jet.GroupByClause
// SetLogger sets automatic statement logging function
// Deprecated: use SetQueryLogger instead.
var SetLogger = jet.SetLoggerFunc
// SetQueryLogger sets automatic query logging function.
var SetQueryLogger = jet.SetQueryLogger
// QueryInfo contains information about executed query
type QueryInfo = jet.QueryInfo

View File

@@ -0,0 +1,119 @@
package postgres
import (
"github.com/go-jet/jet/v2/internal/jet"
)
// UpdateStatement is interface of SQL UPDATE statement
type UpdateStatement interface {
jet.SerializerStatement
SET(value interface{}, values ...interface{}) UpdateStatement
MODEL(data interface{}) UpdateStatement
FROM(tables ...ReadableTable) UpdateStatement
WHERE(expression BoolExpression) UpdateStatement
RETURNING(projections ...Projection) UpdateStatement
}
type updateStatementImpl struct {
jet.SerializerStatement
Update jet.ClauseUpdate
Set clauseSet
SetNew jet.SetClauseNew
From jet.ClauseFrom
Where jet.ClauseWhere
Returning jet.ClauseReturning
}
func newUpdateStatement(table WritableTable, columns []jet.Column) UpdateStatement {
update := &updateStatementImpl{}
update.SerializerStatement = jet.NewStatementImpl(Dialect, jet.UpdateStatementType, update,
&update.Update,
&update.Set,
&update.SetNew,
&update.From,
&update.Where,
&update.Returning)
update.Update.Table = table
update.Set.Columns = columns
update.Where.Mandatory = true
return update
}
func (u *updateStatementImpl) SET(value interface{}, values ...interface{}) UpdateStatement {
columnAssigment, isColumnAssigment := value.(ColumnAssigment)
if isColumnAssigment {
u.SetNew = []ColumnAssigment{columnAssigment}
for _, value := range values {
u.SetNew = append(u.SetNew, value.(ColumnAssigment))
}
} else {
u.Set.Values = jet.UnwindRowFromValues(value, values)
}
return u
}
func (u *updateStatementImpl) MODEL(data interface{}) UpdateStatement {
u.Set.Values = jet.UnwindRowFromModel(u.Set.Columns, data)
return u
}
func (u *updateStatementImpl) FROM(tables ...ReadableTable) UpdateStatement {
u.From.Tables = readableTablesToSerializerList(tables)
return u
}
func (u *updateStatementImpl) WHERE(expression BoolExpression) UpdateStatement {
u.Where.Condition = expression
return u
}
func (u *updateStatementImpl) RETURNING(projections ...jet.Projection) UpdateStatement {
u.Returning.ProjectionList = projections
return u
}
type clauseSet struct {
Columns []jet.Column
Values []jet.Serializer
}
func (s *clauseSet) Serialize(statementType jet.StatementType, out *jet.SQLBuilder, options ...jet.SerializeOption) {
if len(s.Values) == 0 {
return
}
out.NewLine()
out.WriteString("SET")
if len(s.Columns) == 0 {
panic("jet: no columns selected")
}
if len(s.Columns) > 1 {
out.WriteString("(")
}
jet.SerializeColumnNames(s.Columns, out)
if len(s.Columns) > 1 {
out.WriteString(")")
}
out.WriteString("=")
if len(s.Values) > 1 {
out.WriteString("(")
}
jet.SerializeClauseList(statementType, s.Values, out)
if len(s.Values) > 1 {
out.WriteString(")")
}
}

View File

@@ -0,0 +1,62 @@
package postgres
import (
"fmt"
"testing"
)
func TestUpdateWithOneValue(t *testing.T) {
expectedSQL := `
UPDATE db.table1
SET col_int = $1
WHERE table1.col_int >= $2;
`
stmt := table1.UPDATE(table1ColInt).
SET(1).
WHERE(table1ColInt.GT_EQ(Int(33)))
fmt.Println(stmt.Sql())
assertStatementSql(t, stmt, expectedSQL, 1, int64(33))
}
func TestUpdateWithValues(t *testing.T) {
expectedSQL := `
UPDATE db.table1
SET (col_int, col_float) = ($1, $2)
WHERE table1.col_int >= $3;
`
stmt := table1.UPDATE(table1ColInt, table1ColFloat).
SET(1, 22.2).
WHERE(table1ColInt.GT_EQ(Int(33)))
fmt.Println(stmt.Sql())
assertStatementSql(t, stmt, expectedSQL, 1, 22.2, int64(33))
}
func TestUpdateOneColumnWithSelect(t *testing.T) {
expectedSQL := `
UPDATE db.table1
SET col_float = (
SELECT table1.col_float AS "table1.col_float"
FROM db.table1
)
WHERE table1.col1 = $1
RETURNING table1.col1 AS "table1.col1";
`
stmt := table1.
UPDATE(table1ColFloat).
SET(
table1.SELECT(table1ColFloat),
).
WHERE(table1Col1.EQ(Int(2))).
RETURNING(table1Col1)
assertStatementSql(t, stmt, expectedSQL, int64(2))
}
func TestInvalidInputs(t *testing.T) {
assertStatementSqlErr(t, table1.UPDATE(table1ColInt).SET(1), "jet: WHERE clause not set")
assertStatementSqlErr(t, table1.UPDATE(nil).SET(1), "jet: nil column in columns list")
}

View File

@@ -0,0 +1,83 @@
package postgres
import (
"testing"
"github.com/go-jet/jet/v2/internal/jet"
"github.com/go-jet/jet/v2/internal/testutils"
)
var table1Col1 = IntegerColumn("col1")
var table1ColInt = IntegerColumn("col_int")
var table1ColFloat = FloatColumn("col_float")
var table1ColTime = TimeColumn("col_time")
var table1ColTimez = TimezColumn("col_timez")
var table1ColTimestamp = TimestampColumn("col_timestamp")
var table1ColTimestampz = TimestampzColumn("col_timestampz")
var table1ColBool = BoolColumn("col_bool")
var table1ColDate = DateColumn("col_date")
var table1ColInterval = IntervalColumn("col_interval")
var table1ColRange = Int8RangeColumn("col_range")
var table1 = NewTable(
"db",
"table1",
"",
table1Col1,
table1ColInt,
table1ColFloat,
table1ColTime,
table1ColTimez,
table1ColBool,
table1ColDate,
table1ColTimestamp,
table1ColTimestampz,
table1ColInterval,
table1ColRange,
)
var table2Col3 = IntegerColumn("col3")
var table2Col4 = IntegerColumn("col4")
var table2ColInt = IntegerColumn("col_int")
var table2ColFloat = FloatColumn("col_float")
var table2ColStr = StringColumn("col_str")
var table2ColBool = BoolColumn("col_bool")
var table2ColTime = TimeColumn("col_time")
var table2ColTimez = TimezColumn("col_timez")
var table2ColTimestamp = TimestampColumn("col_timestamp")
var table2ColTimestampz = TimestampzColumn("col_timestampz")
var table2ColDate = DateColumn("col_date")
var table2ColInterval = IntervalColumn("col_interval")
var table2ColRange = Int8RangeColumn("col_range")
var table2 = NewTable("db", "table2", "", table2Col3, table2Col4, table2ColInt, table2ColFloat, table2ColStr, table2ColBool, table2ColTime, table2ColTimez, table2ColDate, table2ColTimestamp, table2ColTimestampz, table2ColInterval, table2ColRange)
var table3Col1 = IntegerColumn("col1")
var table3ColInt = IntegerColumn("col_int")
var table3StrCol = StringColumn("col2")
var table3 = NewTable("db", "table3", "", table3Col1, table3ColInt, table3StrCol)
func assertSerialize(t *testing.T, serializer jet.Serializer, query string, args ...interface{}) {
testutils.AssertSerialize(t, Dialect, serializer, query, args...)
}
func assertDebugSerialize(t *testing.T, serializer jet.Serializer, query string, args ...interface{}) {
testutils.AssertDebugSerialize(t, Dialect, serializer, query, args...)
}
func assertClauseSerialize(t *testing.T, clause jet.Clause, query string, args ...interface{}) {
testutils.AssertClauseSerialize(t, Dialect, clause, query, args...)
}
func assertSerializeErr(t *testing.T, serializer jet.Serializer, errString string) {
testutils.AssertSerializeErr(t, Dialect, serializer, errString)
}
func assertProjectionSerialize(t *testing.T, projection jet.Projection, query string, args ...interface{}) {
testutils.AssertProjectionSerialize(t, Dialect, projection, query, args...)
}
var assertStatementSql = testutils.AssertStatementSql
var assertDebugStatementSql = testutils.AssertDebugStatementSql
var assertStatementSqlErr = testutils.AssertStatementSqlErr
var assertPanicErr = testutils.AssertPanicErr

View File

@@ -0,0 +1,32 @@
package postgres
import "github.com/go-jet/jet/v2/internal/jet"
type values struct {
jet.Values
}
// VALUES is a table value constructor that computes a set of one or more rows as a temporary constant table.
// Each row is defined by the WRAP constructor, which takes one or more expressions.
//
// Example usage:
//
// VALUES(
// WRAP(Int32(204), Real(1.21)),
// WRAP(Int32(207), Real(1.02)),
// )
func VALUES(rows ...RowExpression) values {
return values{Values: jet.Values(rows)}
}
// AS assigns an alias to the temporary VALUES table, allowing it to be referenced
// within SQL FROM clauses, just like a regular table.
// By default, VALUES columns are named `column1`, `column2`, etc... Default column aliasing can be
// overwritten by passing new list of columns.
//
// Example usage:
//
// VALUES(...).AS("film_values", IntegerColumn("length"), TimestampColumn("update_date"))
func (v values) AS(alias string, columns ...Column) SelectTable {
return newSelectTable(v, alias, columns)
}

View File

@@ -0,0 +1,74 @@
package postgres
import "github.com/go-jet/jet/v2/internal/jet"
// CommonTableExpression defines set of interface methods for postgres CTEs
type CommonTableExpression interface {
SelectTable
AS(statement jet.SerializerHasProjections) CommonTableExpression
AS_NOT_MATERIALIZED(statement jet.SerializerStatement) CommonTableExpression
// ALIAS is used to create another alias of the CTE, if a CTE needs to appear multiple times in the main query.
ALIAS(alias string) SelectTable
internalCTE() *jet.CommonTableExpression
}
type commonTableExpression struct {
readableTableInterfaceImpl
jet.CommonTableExpression
}
// WITH function creates new WITH statement from list of common table expressions
func WITH(cte ...CommonTableExpression) func(statement jet.Statement) Statement {
return jet.WITH(Dialect, false, toInternalCTE(cte)...)
}
// WITH_RECURSIVE function creates new WITH RECURSIVE statement from list of common table expressions
func WITH_RECURSIVE(cte ...CommonTableExpression) func(statement jet.Statement) Statement {
return jet.WITH(Dialect, true, toInternalCTE(cte)...)
}
// CTE creates new named commonTableExpression
func CTE(name string, columns ...jet.ColumnExpression) CommonTableExpression {
cte := &commonTableExpression{
readableTableInterfaceImpl: readableTableInterfaceImpl{},
CommonTableExpression: jet.CTE(name, columns...),
}
cte.parent = cte
return cte
}
// AS is used to define a CTE query
func (c *commonTableExpression) AS(statement jet.SerializerHasProjections) CommonTableExpression {
c.CommonTableExpression.Statement = statement
return c
}
// AS_NOT_MATERIALIZED is used to define not materialized CTE query
func (c *commonTableExpression) AS_NOT_MATERIALIZED(statement jet.SerializerStatement) CommonTableExpression {
c.CommonTableExpression.NotMaterialized = true
c.CommonTableExpression.Statement = statement
return c
}
func (c *commonTableExpression) internalCTE() *jet.CommonTableExpression {
return &c.CommonTableExpression
}
// ALIAS is used to create another alias of the CTE, if a CTE needs to appear multiple times in the main query.
func (c *commonTableExpression) ALIAS(name string) SelectTable {
return newSelectTable(c, name, nil)
}
func toInternalCTE(ctes []CommonTableExpression) []*jet.CommonTableExpression {
var ret []*jet.CommonTableExpression
for _, cte := range ctes {
ret = append(ret, cte.internalCTE())
}
return ret
}