Merge pull request #463 from go-jet/columnlist-set
Add support for assigning one ColumnList to another in INSERT and UPDATE queries.
This commit is contained in:
commit
a3fc2d8832
8 changed files with 171 additions and 45 deletions
|
|
@ -1,3 +1,3 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
const version = "v2.12.0"
|
const version = "v2.13.0"
|
||||||
|
|
|
||||||
|
|
@ -1,27 +1,20 @@
|
||||||
package jet
|
package jet
|
||||||
|
|
||||||
// ColumnAssigment is interface wrapper around column assigment
|
// ColumnAssigment is interface wrapper around column assignment
|
||||||
type ColumnAssigment interface {
|
type ColumnAssigment interface {
|
||||||
Serializer
|
Serializer
|
||||||
isColumnAssigment()
|
isColumnAssignment()
|
||||||
}
|
}
|
||||||
|
|
||||||
type columnAssigmentImpl struct {
|
type columnAssigmentImpl struct {
|
||||||
column ColumnSerializer
|
column ColumnSerializer
|
||||||
expression Expression
|
toAssign Serializer
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewColumnAssignment(serializer ColumnSerializer, expression Expression) ColumnAssigment {
|
func (a columnAssigmentImpl) isColumnAssignment() {}
|
||||||
return &columnAssigmentImpl{
|
|
||||||
column: serializer,
|
|
||||||
expression: expression,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a columnAssigmentImpl) isColumnAssigment() {}
|
|
||||||
|
|
||||||
func (a columnAssigmentImpl) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) {
|
func (a columnAssigmentImpl) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) {
|
||||||
a.column.serialize(statement, out, ShortName.WithFallTrough(options)...)
|
a.column.serialize(statement, out, ShortName.WithFallTrough(options)...)
|
||||||
out.WriteString("=")
|
out.WriteString("=")
|
||||||
a.expression.serialize(statement, out, FallTrough(options)...)
|
a.toAssign.serialize(statement, out, FallTrough(options)...)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,60 @@
|
||||||
package jet
|
package jet
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
// ColumnList is a helper type to support list of columns as single projection
|
// ColumnList is a helper type to support list of columns as single projection
|
||||||
type ColumnList []ColumnExpression
|
type ColumnList []ColumnExpression
|
||||||
|
|
||||||
// SET creates column assigment for each column in column list. expression should be created by ROW function
|
func (cl ColumnList) isExpressionOrColumnList() {}
|
||||||
|
|
||||||
|
// SET creates a column assignment from the current ColumnList using the provided expression.
|
||||||
|
// This assignment can be used in INSERT queries (e.g., to set columns on conflict) or in UPDATE queries
|
||||||
|
// (e.g., to assign new values to columns).
|
||||||
//
|
//
|
||||||
// Link.UPDATE().
|
// The expression can be:
|
||||||
// SET(Link.MutableColumns.SET(ROW(String("github.com"), Bool(false))).
|
// - Another ColumnList: It must have the same length as the current ColumnList and each column must match by name
|
||||||
// WHERE(Link.ID.EQ(Int(0)))
|
// - A ROW expression containing values.
|
||||||
func (cl ColumnList) SET(expression Expression) ColumnAssigment {
|
// - A SELECT statement that returns a matching column list structure.
|
||||||
|
//
|
||||||
|
// Examples:
|
||||||
|
//
|
||||||
|
// Link.AllColumns.SET(ROW(String("github.com"), Bool(false)))
|
||||||
|
//
|
||||||
|
// Link.MutableColumns.SET(Link.EXCLUDED.MutableColumns)
|
||||||
|
//
|
||||||
|
// Link.MutableColumns.SET(
|
||||||
|
// SELECT(Link.MutableColumns).
|
||||||
|
// FROM(Link).
|
||||||
|
// WHERE(Link.ID.EQ(Int(200))),
|
||||||
|
// )
|
||||||
|
func (cl ColumnList) SET(toAssignExp expressionOrColumnList) ColumnAssigment {
|
||||||
|
|
||||||
|
if toAssign, ok := toAssignExp.(ColumnList); ok {
|
||||||
|
if len(cl) != len(toAssign) {
|
||||||
|
panic(fmt.Sprintf("jet: column list length mismatch: expected %d columns, got %d", len(cl), len(toAssign)))
|
||||||
|
}
|
||||||
|
|
||||||
|
var ret columnListAssigment
|
||||||
|
|
||||||
|
for i, column := range cl {
|
||||||
|
if column.Name() != toAssign[i].Name() {
|
||||||
|
panic(fmt.Sprintf("jet: column name mismatch at index %d: expected column '%s', got '%s'",
|
||||||
|
i, column.Name(), toAssign[i].Name(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = append(ret, columnAssigmentImpl{
|
||||||
|
column: column,
|
||||||
|
toAssign: toAssign[i],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
return columnAssigmentImpl{
|
return columnAssigmentImpl{
|
||||||
column: cl,
|
column: cl,
|
||||||
expression: expression,
|
toAssign: toAssignExp,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
21
internal/jet/column_list_assigment.go
Normal file
21
internal/jet/column_list_assigment.go
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
package jet
|
||||||
|
|
||||||
|
type expressionOrColumnList interface {
|
||||||
|
Serializer
|
||||||
|
isExpressionOrColumnList()
|
||||||
|
}
|
||||||
|
|
||||||
|
type columnListAssigment []ColumnAssigment
|
||||||
|
|
||||||
|
func (c columnListAssigment) isColumnAssignment() {}
|
||||||
|
|
||||||
|
func (c columnListAssigment) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) {
|
||||||
|
for i, columnAssigment := range c {
|
||||||
|
if i > 0 {
|
||||||
|
out.WriteString(",")
|
||||||
|
out.NewLine()
|
||||||
|
}
|
||||||
|
|
||||||
|
columnAssigment.serialize(statement, out, options...)
|
||||||
|
}
|
||||||
|
}
|
||||||
25
internal/jet/column_list_test.go
Normal file
25
internal/jet/column_list_test.go
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
package jet
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestColumnList_SET(t *testing.T) {
|
||||||
|
columnList1 := ColumnList{IntegerColumn("id"), StringColumn("Name"), BoolColumn("active")}
|
||||||
|
columnList2 := ColumnList{IntegerColumn("id"), StringColumn("Name"), BoolColumn("active")}
|
||||||
|
|
||||||
|
columnList1.SET(columnList2)
|
||||||
|
|
||||||
|
columnList3 := ColumnList{IntegerColumn("id"), StringColumn("Name")}
|
||||||
|
|
||||||
|
require.PanicsWithValue(t, "jet: column list length mismatch: expected 2 columns, got 3", func() {
|
||||||
|
columnList3.SET(columnList1)
|
||||||
|
})
|
||||||
|
|
||||||
|
columnList4 := ColumnList{IntegerColumn("id"), StringColumn("FullName"), BoolColumn("active")}
|
||||||
|
|
||||||
|
require.PanicsWithValue(t, "jet: column name mismatch at index 1: expected column 'Name', got 'FullName'", func() {
|
||||||
|
columnList1.SET(columnList4)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
@ -28,8 +28,8 @@ func (i *boolColumnImpl) From(subQuery SelectTable) ColumnBool {
|
||||||
|
|
||||||
func (i *boolColumnImpl) SET(boolExp BoolExpression) ColumnAssigment {
|
func (i *boolColumnImpl) SET(boolExp BoolExpression) ColumnAssigment {
|
||||||
return columnAssigmentImpl{
|
return columnAssigmentImpl{
|
||||||
column: i,
|
column: i,
|
||||||
expression: boolExp,
|
toAssign: boolExp,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -72,8 +72,8 @@ func (i *floatColumnImpl) From(subQuery SelectTable) ColumnFloat {
|
||||||
|
|
||||||
func (i *floatColumnImpl) SET(floatExp FloatExpression) ColumnAssigment {
|
func (i *floatColumnImpl) SET(floatExp FloatExpression) ColumnAssigment {
|
||||||
return columnAssigmentImpl{
|
return columnAssigmentImpl{
|
||||||
column: i,
|
column: i,
|
||||||
expression: floatExp,
|
toAssign: floatExp,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -117,8 +117,8 @@ func (i *integerColumnImpl) From(subQuery SelectTable) ColumnInteger {
|
||||||
|
|
||||||
func (i *integerColumnImpl) SET(intExp IntegerExpression) ColumnAssigment {
|
func (i *integerColumnImpl) SET(intExp IntegerExpression) ColumnAssigment {
|
||||||
return columnAssigmentImpl{
|
return columnAssigmentImpl{
|
||||||
column: i,
|
column: i,
|
||||||
expression: intExp,
|
toAssign: intExp,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -163,8 +163,8 @@ func (i *stringColumnImpl) From(subQuery SelectTable) ColumnString {
|
||||||
|
|
||||||
func (i *stringColumnImpl) SET(stringExp StringExpression) ColumnAssigment {
|
func (i *stringColumnImpl) SET(stringExp StringExpression) ColumnAssigment {
|
||||||
return columnAssigmentImpl{
|
return columnAssigmentImpl{
|
||||||
column: i,
|
column: i,
|
||||||
expression: stringExp,
|
toAssign: stringExp,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -208,8 +208,8 @@ func (i *blobColumnImpl) From(subQuery SelectTable) ColumnBlob {
|
||||||
|
|
||||||
func (i *blobColumnImpl) SET(blobExp BlobExpression) ColumnAssigment {
|
func (i *blobColumnImpl) SET(blobExp BlobExpression) ColumnAssigment {
|
||||||
return columnAssigmentImpl{
|
return columnAssigmentImpl{
|
||||||
column: i,
|
column: i,
|
||||||
expression: blobExp,
|
toAssign: blobExp,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -252,8 +252,8 @@ func (i *timeColumnImpl) From(subQuery SelectTable) ColumnTime {
|
||||||
|
|
||||||
func (i *timeColumnImpl) SET(timeExp TimeExpression) ColumnAssigment {
|
func (i *timeColumnImpl) SET(timeExp TimeExpression) ColumnAssigment {
|
||||||
return columnAssigmentImpl{
|
return columnAssigmentImpl{
|
||||||
column: i,
|
column: i,
|
||||||
expression: timeExp,
|
toAssign: timeExp,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -295,8 +295,8 @@ func (i *timezColumnImpl) From(subQuery SelectTable) ColumnTimez {
|
||||||
|
|
||||||
func (i *timezColumnImpl) SET(timezExp TimezExpression) ColumnAssigment {
|
func (i *timezColumnImpl) SET(timezExp TimezExpression) ColumnAssigment {
|
||||||
return columnAssigmentImpl{
|
return columnAssigmentImpl{
|
||||||
column: i,
|
column: i,
|
||||||
expression: timezExp,
|
toAssign: timezExp,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -339,8 +339,8 @@ func (i *timestampColumnImpl) From(subQuery SelectTable) ColumnTimestamp {
|
||||||
|
|
||||||
func (i *timestampColumnImpl) SET(timestampExp TimestampExpression) ColumnAssigment {
|
func (i *timestampColumnImpl) SET(timestampExp TimestampExpression) ColumnAssigment {
|
||||||
return columnAssigmentImpl{
|
return columnAssigmentImpl{
|
||||||
column: i,
|
column: i,
|
||||||
expression: timestampExp,
|
toAssign: timestampExp,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -383,8 +383,8 @@ func (i *timestampzColumnImpl) From(subQuery SelectTable) ColumnTimestampz {
|
||||||
|
|
||||||
func (i *timestampzColumnImpl) SET(timestampzExp TimestampzExpression) ColumnAssigment {
|
func (i *timestampzColumnImpl) SET(timestampzExp TimestampzExpression) ColumnAssigment {
|
||||||
return columnAssigmentImpl{
|
return columnAssigmentImpl{
|
||||||
column: i,
|
column: i,
|
||||||
expression: timestampzExp,
|
toAssign: timestampzExp,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -427,8 +427,8 @@ func (i *dateColumnImpl) From(subQuery SelectTable) ColumnDate {
|
||||||
|
|
||||||
func (i *dateColumnImpl) SET(dateExp DateExpression) ColumnAssigment {
|
func (i *dateColumnImpl) SET(dateExp DateExpression) ColumnAssigment {
|
||||||
return columnAssigmentImpl{
|
return columnAssigmentImpl{
|
||||||
column: i,
|
column: i,
|
||||||
expression: dateExp,
|
toAssign: dateExp,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -460,8 +460,8 @@ type intervalColumnImpl struct {
|
||||||
|
|
||||||
func (i *intervalColumnImpl) SET(intervalExp IntervalExpression) ColumnAssigment {
|
func (i *intervalColumnImpl) SET(intervalExp IntervalExpression) ColumnAssigment {
|
||||||
return columnAssigmentImpl{
|
return columnAssigmentImpl{
|
||||||
column: i,
|
column: i,
|
||||||
expression: intervalExp,
|
toAssign: intervalExp,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -516,8 +516,8 @@ func (i *rangeColumnImpl[T]) From(subQuery SelectTable) ColumnRange[T] {
|
||||||
|
|
||||||
func (i *rangeColumnImpl[T]) SET(rangeExp Range[T]) ColumnAssigment {
|
func (i *rangeColumnImpl[T]) SET(rangeExp Range[T]) ColumnAssigment {
|
||||||
return columnAssigmentImpl{
|
return columnAssigmentImpl{
|
||||||
column: i,
|
column: i,
|
||||||
expression: rangeExp,
|
toAssign: rangeExp,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ type Expression interface {
|
||||||
Projection
|
Projection
|
||||||
GroupByClause
|
GroupByClause
|
||||||
OrderByClause
|
OrderByClause
|
||||||
|
expressionOrColumnList
|
||||||
|
|
||||||
serializeForJsonValue(statement StatementType, out *SQLBuilder)
|
serializeForJsonValue(statement StatementType, out *SQLBuilder)
|
||||||
setRoot(root Expression)
|
setRoot(root Expression)
|
||||||
|
|
@ -37,6 +38,8 @@ type ExpressionInterfaceImpl struct {
|
||||||
Root Expression
|
Root Expression
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (e *ExpressionInterfaceImpl) isExpressionOrColumnList() {}
|
||||||
|
|
||||||
func (e *ExpressionInterfaceImpl) setRoot(root Expression) {
|
func (e *ExpressionInterfaceImpl) setRoot(root Expression) {
|
||||||
e.Root = root
|
e.Root = root
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -196,6 +196,47 @@ RETURNING link.id AS "link.id",
|
||||||
testutils.AssertExecAndRollback(t, stmt, db, 2)
|
testutils.AssertExecAndRollback(t, stmt, db, 2)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("do update column list", func(t *testing.T) {
|
||||||
|
stmt := Link.INSERT().
|
||||||
|
VALUES(1, "http://www.postgresqltutorial.com", "PostgreSQL Tutorial", DEFAULT).
|
||||||
|
ON_CONFLICT(Link.ID).DO_UPDATE(
|
||||||
|
SET(
|
||||||
|
Link.MutableColumns.SET(Link.EXCLUDED.MutableColumns),
|
||||||
|
),
|
||||||
|
).RETURNING(Link.AllColumns)
|
||||||
|
|
||||||
|
testutils.AssertDebugStatementSql(t, stmt, `
|
||||||
|
INSERT INTO test_sample.link
|
||||||
|
VALUES (1, 'http://www.postgresqltutorial.com', 'PostgreSQL Tutorial', DEFAULT)
|
||||||
|
ON CONFLICT (id) DO UPDATE
|
||||||
|
SET url = excluded.url,
|
||||||
|
name = excluded.name,
|
||||||
|
description = excluded.description
|
||||||
|
RETURNING link.id AS "link.id",
|
||||||
|
link.url AS "link.url",
|
||||||
|
link.name AS "link.name",
|
||||||
|
link.description AS "link.description";
|
||||||
|
`)
|
||||||
|
|
||||||
|
testutils.ExecuteInTxAndRollback(t, db, func(tx qrm.DB) {
|
||||||
|
var dest []model.Link
|
||||||
|
|
||||||
|
err := stmt.QueryContext(ctx, tx, &dest)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
testutils.AssertJSON(t, dest, `
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"ID": 1,
|
||||||
|
"URL": "http://www.postgresqltutorial.com",
|
||||||
|
"Name": "PostgreSQL Tutorial",
|
||||||
|
"Description": null
|
||||||
|
}
|
||||||
|
]
|
||||||
|
`)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
t.Run("do update complex", func(t *testing.T) {
|
t.Run("do update complex", func(t *testing.T) {
|
||||||
skipForCockroachDB(t) // does not support ROW
|
skipForCockroachDB(t) // does not support ROW
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue