Merge remote-tracking branch 'upstream/master' into stmt-cache2

# Conflicts:
#	tests/postgres/alltypes_test.go
#	tests/postgres/northwind_test.go
#	tests/postgres/sample_test.go
#	tests/postgres/update_test.go
#	tests/sqlite/insert_test.go
#	tests/sqlite/main_test.go
#	tests/sqlite/sample_test.go
#	tests/sqlite/update_test.go
This commit is contained in:
go-jet 2024-10-19 14:01:55 +02:00
commit 4bb9775134
97 changed files with 2306 additions and 537 deletions

View file

@ -1,6 +1,7 @@
package mysql
import (
"github.com/go-jet/jet/v2/internal/utils/ptr"
"github.com/shopspring/decimal"
"github.com/stretchr/testify/require"
"strings"
@ -96,18 +97,18 @@ func TestExpressionOperators(t *testing.T) {
SELECT all_types.'integer' IS NULL AS "result.is_null",
all_types.date_ptr IS NOT NULL AS "result.is_not_null",
(all_types.small_int_ptr IN (?, ?)) AS "result.in",
(all_types.small_int_ptr IN (
(all_types.small_int_ptr IN ((
SELECT all_types.'integer' AS "all_types.integer"
FROM test_sample.all_types
)) AS "result.in_select",
))) AS "result.in_select",
(CURRENT_USER()) AS "result.raw",
(? + COALESCE(all_types.small_int_ptr, 0) + ?) AS "result.raw_arg",
(? + all_types.integer + ? + ? + ? + ?) AS "result.raw_arg2",
(all_types.small_int_ptr NOT IN (?, ?, NULL)) AS "result.not_in",
(all_types.small_int_ptr NOT IN (
(all_types.small_int_ptr NOT IN ((
SELECT all_types.'integer' AS "all_types.integer"
FROM test_sample.all_types
)) AS "result.not_in_select"
))) AS "result.not_in_select"
FROM test_sample.all_types
LIMIT ?;
`, "'", "`", -1), int64(11), int64(22), 78, 56, 11, 22, 11, 33, 44, int64(11), int64(22), int64(2))
@ -1067,7 +1068,7 @@ func TestAllTypesInsertOnDuplicateKeyUpdate(t *testing.T) {
var toInsert = model.AllTypes{
Boolean: false,
BooleanPtr: testutils.BoolPtr(true),
BooleanPtr: ptr.Of(true),
TinyInt: 1,
UTinyInt: 2,
SmallInt: 3,
@ -1078,53 +1079,53 @@ var toInsert = model.AllTypes{
UInteger: 8,
BigInt: 9,
UBigInt: 1122334455,
TinyIntPtr: testutils.Int8Ptr(11),
UTinyIntPtr: testutils.UInt8Ptr(22),
SmallIntPtr: testutils.Int16Ptr(33),
USmallIntPtr: testutils.UInt16Ptr(44),
MediumIntPtr: testutils.Int32Ptr(55),
UMediumIntPtr: testutils.UInt32Ptr(66),
IntegerPtr: testutils.Int32Ptr(77),
UIntegerPtr: testutils.UInt32Ptr(88),
BigIntPtr: testutils.Int64Ptr(99),
UBigIntPtr: testutils.UInt64Ptr(111),
TinyIntPtr: ptr.Of(int8(11)),
UTinyIntPtr: ptr.Of(uint8(22)),
SmallIntPtr: ptr.Of(int16(33)),
USmallIntPtr: ptr.Of(uint16(44)),
MediumIntPtr: ptr.Of(int32(55)),
UMediumIntPtr: ptr.Of(uint32(66)),
IntegerPtr: ptr.Of(int32(77)),
UIntegerPtr: ptr.Of(uint32(88)),
BigIntPtr: ptr.Of(int64(99)),
UBigIntPtr: ptr.Of(uint64(111)),
Decimal: 11.22,
DecimalPtr: testutils.Float64Ptr(33.44),
DecimalPtr: ptr.Of(33.44),
Numeric: 55.66,
NumericPtr: testutils.Float64Ptr(77.88),
NumericPtr: ptr.Of(77.88),
Float: 99.00,
FloatPtr: testutils.Float64Ptr(11.22),
FloatPtr: ptr.Of(11.22),
Double: 33.44,
DoublePtr: testutils.Float64Ptr(55.66),
DoublePtr: ptr.Of(55.66),
Real: 77.88,
RealPtr: testutils.Float64Ptr(99.00),
RealPtr: ptr.Of(99.00),
Bit: "1",
BitPtr: testutils.StringPtr("0"),
BitPtr: ptr.Of("0"),
Time: time.Date(1, 1, 1, 10, 11, 12, 100, &time.Location{}),
TimePtr: testutils.TimePtr(time.Date(1, 1, 1, 10, 11, 12, 100, time.UTC)),
TimePtr: ptr.Of(time.Date(1, 1, 1, 10, 11, 12, 100, time.UTC)),
Date: time.Now(),
DatePtr: testutils.TimePtr(time.Now()),
DatePtr: ptr.Of(time.Now()),
DateTime: time.Now(),
DateTimePtr: testutils.TimePtr(time.Now()),
DateTimePtr: ptr.Of(time.Now()),
Timestamp: time.Now(),
//TimestampPtr: testutils.TimePtr(time.Now()), // TODO: build fails for MariaDB
Year: 2000,
YearPtr: testutils.Int16Ptr(2001),
YearPtr: ptr.Of(int16(2001)),
Char: "abcd",
CharPtr: testutils.StringPtr("absd"),
CharPtr: ptr.Of("absd"),
VarChar: "abcd",
VarCharPtr: testutils.StringPtr("absd"),
VarCharPtr: ptr.Of("absd"),
Binary: []byte("1010"),
BinaryPtr: testutils.ByteArrayPtr([]byte("100001")),
BinaryPtr: ptr.Of([]byte("100001")),
VarBinary: []byte("1010"),
VarBinaryPtr: testutils.ByteArrayPtr([]byte("100001")),
VarBinaryPtr: ptr.Of([]byte("100001")),
Blob: []byte("large file"),
BlobPtr: testutils.ByteArrayPtr([]byte("very large file")),
BlobPtr: ptr.Of([]byte("very large file")),
Text: "some text",
TextPtr: testutils.StringPtr("text"),
TextPtr: ptr.Of("text"),
Enum: model.AllTypesEnum_Value1,
JSON: "{}",
JSONPtr: testutils.StringPtr(`{"a": 1}`),
JSONPtr: ptr.Of(`{"a": 1}`),
}
var allTypesJson = `
@ -1358,17 +1359,17 @@ func TestExactDecimals(t *testing.T) {
Floats: model.Floats{
// overwritten by wrapped(floats) scope
Numeric: 0.1,
NumericPtr: testutils.Float64Ptr(0.1),
NumericPtr: ptr.Of(0.1),
Decimal: 0.1,
DecimalPtr: testutils.Float64Ptr(0.1),
DecimalPtr: ptr.Of(0.1),
// not overwritten
Float: 0.2,
FloatPtr: testutils.Float64Ptr(0.22),
FloatPtr: ptr.Of(0.22),
Double: 0.3,
DoublePtr: testutils.Float64Ptr(0.33),
DoublePtr: ptr.Of(0.33),
Real: 0.4,
RealPtr: testutils.Float64Ptr(0.44),
RealPtr: ptr.Of(0.44),
},
Numeric: decimal.RequireFromString("12.35"),
NumericPtr: decimal.RequireFromString("56.79"),
@ -1403,3 +1404,34 @@ VALUES ('91.23', '45.67', '12.35', '56.79', 0.2, 0.22, 0.3, 0.33, 0.4, 0.44);
require.Equal(t, 45.67, *result.Floats.DecimalPtr)
})
}
func TestRowExpression(t *testing.T) {
now := time.Now()
nowAddHour := time.Now().Add(time.Hour)
stmt := SELECT(
ROW(Bool(false), DateT(now)).EQ(ROW(Bool(true), DateT(now))),
ROW(Bool(false), DateT(now)).NOT_EQ(ROW(Bool(true), DateT(now))),
ROW(TimestampT(nowAddHour), String("txt")).IS_DISTINCT_FROM(RowExp(Raw("row(NOW(), 'png')"))),
ROW(TimestampT(now), DateTimeT(nowAddHour)).GT(ROW(TimestampT(now), DateTimeT(now))),
ROW(DateTimeT(nowAddHour), Int(1)).GT_EQ(ROW(DateTimeT(now), Int(2))),
ROW(TimestampT(now), DateTimeT(nowAddHour)).LT(ROW(TimestampT(now), DateTimeT(now))),
ROW(DateTimeT(nowAddHour), Float(1.22)).LT_EQ(ROW(DateTimeT(now), Float(2.33))),
)
//fmt.Println(stmt.Sql())
//fmt.Println(stmt.DebugSql())
testutils.AssertStatementSql(t, stmt, `
SELECT ROW(?, CAST(? AS DATE)) = ROW(?, CAST(? AS DATE)),
ROW(?, CAST(? AS DATE)) != ROW(?, CAST(? AS DATE)),
NOT(ROW(TIMESTAMP(?), ?) <=> (row(NOW(), 'png'))),
ROW(TIMESTAMP(?), CAST(? AS DATETIME)) > ROW(TIMESTAMP(?), CAST(? AS DATETIME)),
ROW(CAST(? AS DATETIME), ?) >= ROW(CAST(? AS DATETIME), ?),
ROW(TIMESTAMP(?), CAST(? AS DATETIME)) < ROW(TIMESTAMP(?), CAST(? AS DATETIME)),
ROW(CAST(? AS DATETIME), ?) <= ROW(CAST(? AS DATETIME), ?);
`)
err := stmt.Query(db, &struct{}{})
require.NoError(t, err)
}

View file

@ -8,8 +8,11 @@ import (
"github.com/stretchr/testify/require"
"github.com/go-jet/jet/v2/generator/metadata"
"github.com/go-jet/jet/v2/generator/mysql"
"github.com/go-jet/jet/v2/generator/template"
"github.com/go-jet/jet/v2/internal/testutils"
mysql2 "github.com/go-jet/jet/v2/mysql"
"github.com/go-jet/jet/v2/tests/dbconfig"
)
@ -39,6 +42,39 @@ func TestGenerator(t *testing.T) {
require.NoError(t, err)
}
func TestGenerator_TableMetadata(t *testing.T) {
var schema metadata.Schema
err := mysql.Generate(genTestDir3, dbConnection("dvds"),
template.Default(mysql2.Dialect).UseSchema(func(m metadata.Schema) template.Schema {
schema = m
return template.DefaultSchema(m)
}))
require.NoError(t, err)
// Spot check the actor table and assert that the emitted
// properties are as expected.
var got metadata.Table
for _, table := range schema.TablesMetaData {
if table.Name == "actor" {
got = table
}
}
want := metadata.Table{
Name: "actor",
Columns: []metadata.Column{
{Name: "actor_id", IsPrimaryKey: true, IsNullable: false, IsGenerated: false, HasDefault: false, DataType: metadata.DataType{Name: "smallint", Kind: "base", IsUnsigned: true}, Comment: ""},
{Name: "first_name", IsPrimaryKey: false, IsNullable: false, IsGenerated: false, HasDefault: false, DataType: metadata.DataType{Name: "varchar", Kind: "base", IsUnsigned: false}, Comment: ""},
{Name: "last_name", IsPrimaryKey: false, IsNullable: false, IsGenerated: false, HasDefault: false, DataType: metadata.DataType{Name: "varchar", Kind: "base", IsUnsigned: false}, Comment: ""},
{Name: "last_update", IsPrimaryKey: false, IsNullable: false, IsGenerated: false, HasDefault: true, DataType: metadata.DataType{Name: "timestamp", Kind: "base", IsUnsigned: false}, Comment: ""},
},
}
require.Equal(t, want, got)
err = os.RemoveAll(genTestDirRoot)
require.NoError(t, err)
}
func TestCmdGenerator(t *testing.T) {
err := os.RemoveAll(genTestDir3)
require.NoError(t, err)

View file

@ -3,10 +3,12 @@ package mysql
import (
"context"
"github.com/go-jet/jet/v2/internal/testutils"
"github.com/go-jet/jet/v2/internal/utils/ptr"
. "github.com/go-jet/jet/v2/mysql"
"github.com/go-jet/jet/v2/qrm"
"github.com/go-jet/jet/v2/tests/.gentestdata/mysql/test_sample/model"
. "github.com/go-jet/jet/v2/tests/.gentestdata/mysql/test_sample/table"
"github.com/stretchr/testify/require"
"math/rand"
"testing"
@ -300,7 +302,7 @@ func TestInsertOnDuplicateKeyUpdateNEW(t *testing.T) {
ID: randId,
URL: "https://www.yahoo.com",
Name: "Yahoo",
Description: testutils.StringPtr("web portal and search engine"),
Description: ptr.Of("web portal and search engine"),
},
}).AS_NEW().
ON_DUPLICATE_KEY_UPDATE(
@ -337,7 +339,7 @@ ON DUPLICATE KEY UPDATE id = (link.id + ?),
ID: randId + 11,
URL: "https://www.yahoo.com",
Name: "Yahoo",
Description: testutils.StringPtr("web portal and search engine"),
Description: ptr.Of("web portal and search engine"),
})
})
}

View file

@ -6,12 +6,9 @@ import (
jetmysql "github.com/go-jet/jet/v2/mysql"
"github.com/go-jet/jet/v2/postgres"
"github.com/go-jet/jet/v2/tests/dbconfig"
"github.com/stretchr/testify/require"
"math/rand"
"runtime"
"time"
_ "github.com/go-sql-driver/mysql"
"github.com/stretchr/testify/require"
"runtime"
"github.com/pkg/profile"
"os"
@ -33,7 +30,6 @@ func sourceIsMariaDB() bool {
}
func TestMain(m *testing.M) {
rand.Seed(time.Now().Unix())
defer profile.Start().Stop()
var err error
@ -101,3 +97,9 @@ func skipForMariaDB(t *testing.T) {
t.SkipNow()
}
}
func onlyMariaDB(t *testing.T) {
if !sourceIsMariaDB() {
t.SkipNow()
}
}

347
tests/mysql/values_test.go Normal file
View file

@ -0,0 +1,347 @@
package mysql
import (
"github.com/go-jet/jet/v2/internal/testutils"
"github.com/stretchr/testify/require"
"strings"
"testing"
"time"
. "github.com/go-jet/jet/v2/mysql"
"github.com/go-jet/jet/v2/tests/.gentestdata/mysql/dvds/model"
. "github.com/go-jet/jet/v2/tests/.gentestdata/mysql/dvds/table"
)
func TestVALUES(t *testing.T) {
skipForMariaDB(t)
valuesTable := VALUES(
ROW(Int32(1), Int32(2), Float(4.666), Bool(false), String("txt")),
ROW(Int32(11).ADD(Int32(2)), Int32(22), Float(33.222), Bool(true), String("png")),
ROW(Int32(11), Int32(22), Float(33.222), Bool(true), NULL),
).AS("values_table")
stmt := SELECT(
valuesTable.AllColumns(),
).FROM(
valuesTable,
)
testutils.AssertStatementSql(t, stmt, `
SELECT values_table.column_0 AS "column_0",
values_table.column_1 AS "column_1",
values_table.column_2 AS "column_2",
values_table.column_3 AS "column_3",
values_table.column_4 AS "column_4"
FROM (
VALUES ROW(?, ?, ?, ?, ?),
ROW(? + ?, ?, ?, ?, ?),
ROW(?, ?, ?, ?, NULL)
) AS values_table;
`)
var dest []struct {
Column0 int
Column1 int
Column2 float32
Column3 bool
Column4 *string
}
err := stmt.Query(db, &dest)
require.NoError(t, err)
testutils.AssertJSON(t, dest, `
[
{
"Column0": 1,
"Column1": 2,
"Column2": 4.666,
"Column3": false,
"Column4": "txt"
},
{
"Column0": 13,
"Column1": 22,
"Column2": 33.222,
"Column3": true,
"Column4": "png"
},
{
"Column0": 11,
"Column1": 22,
"Column2": 33.222,
"Column3": true,
"Column4": null
}
]
`)
}
func TestVALUES_Join(t *testing.T) {
skipForMariaDB(t)
title := StringColumn("title")
releaseYear := IntegerColumn("ReleaseYear")
rentalRate := FloatColumn("rental_rate")
lastUpdate := Timestamp(2007, time.February, 11, 12, 0, 0)
films := VALUES(
ROW(String("Chamber Italian"), Int64(117), Int32(2005), Float(5.82), lastUpdate),
ROW(String("Grosse Wonderful"), Int64(49), Int32(2004), Float(6.242), lastUpdate.ADD(INTERVAL(1, HOUR))),
ROW(String("Airport Pollock"), Int64(54), Int32(2001), Float(7.22), NULL),
ROW(String("Bright Encounters"), Int64(73), Int32(2002), Float(8.25), NULL),
ROW(String("Academy Dinosaur"), Int64(83), Int32(2010), Float(9.22), lastUpdate.SUB(INTERVAL(2, MINUTE))),
).AS("film_values",
title, IntegerColumn("length"), releaseYear, rentalRate, TimestampColumn("last_update"))
stmt := SELECT(
Film.AllColumns,
films.AllColumns(),
).FROM(
Film.
INNER_JOIN(films, title.EQ(Film.Title)),
).WHERE(AND(
Film.ReleaseYear.GT(releaseYear),
Film.RentalRate.LT(rentalRate),
)).ORDER_BY(
title,
)
testutils.AssertDebugStatementSql(t, stmt, strings.ReplaceAll(`
SELECT film.film_id AS "film.film_id",
film.title AS "film.title",
film.description AS "film.description",
film.release_year AS "film.release_year",
film.language_id AS "film.language_id",
film.original_language_id AS "film.original_language_id",
film.rental_duration AS "film.rental_duration",
film.rental_rate AS "film.rental_rate",
film.length AS "film.length",
film.replacement_cost AS "film.replacement_cost",
film.rating AS "film.rating",
film.special_features AS "film.special_features",
film.last_update AS "film.last_update",
film_values.title AS "title",
film_values.length AS "length",
film_values.''ReleaseYear'' AS "ReleaseYear",
film_values.rental_rate AS "rental_rate",
film_values.last_update AS "last_update"
FROM dvds.film
INNER JOIN (
VALUES ROW('Chamber Italian', 117, 2005, 5.82, TIMESTAMP('2007-02-11 12:00:00')),
ROW('Grosse Wonderful', 49, 2004, 6.242, TIMESTAMP('2007-02-11 12:00:00') + INTERVAL 1 HOUR),
ROW('Airport Pollock', 54, 2001, 7.22, NULL),
ROW('Bright Encounters', 73, 2002, 8.25, NULL),
ROW('Academy Dinosaur', 83, 2010, 9.22, TIMESTAMP('2007-02-11 12:00:00') - INTERVAL 2 MINUTE)
) AS film_values (title, length, ''ReleaseYear'', rental_rate, last_update) ON (film_values.title = film.title)
WHERE (
(film.release_year > film_values.''ReleaseYear'')
AND (film.rental_rate < film_values.rental_rate)
)
ORDER BY film_values.title;
`, "''", "`"))
var dest []struct {
Film model.Film
Title string
Length int
ReleaseYear int
RentalRate float32
LastUpdate *time.Time
}
err := stmt.Query(db, &dest)
require.NoError(t, err)
require.Len(t, dest, 4)
testutils.AssertJSON(t, dest[0:2], `
[
{
"Film": {
"FilmID": 8,
"Title": "AIRPORT POLLOCK",
"Description": "A Epic Tale of a Moose And a Girl who must Confront a Monkey in Ancient India",
"ReleaseYear": 2006,
"LanguageID": 1,
"OriginalLanguageID": null,
"RentalDuration": 6,
"RentalRate": 4.99,
"Length": 54,
"ReplacementCost": 15.99,
"Rating": "R",
"SpecialFeatures": "Trailers",
"LastUpdate": "2006-02-15T05:03:42Z"
},
"Title": "Airport Pollock",
"Length": 54,
"ReleaseYear": 2001,
"RentalRate": 7.22,
"LastUpdate": null
},
{
"Film": {
"FilmID": 98,
"Title": "BRIGHT ENCOUNTERS",
"Description": "A Fateful Yarn of a Lumberjack And a Feminist who must Conquer a Student in A Jet Boat",
"ReleaseYear": 2006,
"LanguageID": 1,
"OriginalLanguageID": null,
"RentalDuration": 4,
"RentalRate": 4.99,
"Length": 73,
"ReplacementCost": 12.99,
"Rating": "PG-13",
"SpecialFeatures": "Trailers",
"LastUpdate": "2006-02-15T05:03:42Z"
},
"Title": "Bright Encounters",
"Length": 73,
"ReleaseYear": 2002,
"RentalRate": 8.25,
"LastUpdate": null
}
]
`)
}
func TestVALUES_CTE_Update(t *testing.T) {
skipForMariaDB(t)
paymentID := IntegerColumn("payment_id")
increase := FloatColumn("increase")
paymentsToUpdate := CTE("values_cte", paymentID, increase)
stmt := WITH(
paymentsToUpdate.AS(
VALUES(
ROW(Int32(204), Float(1.21)),
ROW(Int32(207), Float(1.02)),
ROW(Int32(200), Float(1.34)),
ROW(Int32(203), Float(1.72)),
),
),
)(
Payment.INNER_JOIN(paymentsToUpdate, paymentID.EQ(Payment.PaymentID)).
UPDATE().
SET(
Payment.Amount.SET(Payment.Amount.MUL(increase)),
).WHERE(Bool(true)),
)
testutils.AssertStatementSql(t, stmt, `
WITH values_cte (payment_id, increase) AS (
VALUES ROW(?, ?),
ROW(?, ?),
ROW(?, ?),
ROW(?, ?)
)
UPDATE dvds.payment
INNER JOIN values_cte ON (values_cte.payment_id = payment.payment_id)
SET amount = (payment.amount * values_cte.increase)
WHERE ?;
`)
testutils.AssertExecAndRollback(t, stmt, db, 4)
}
func TestVALUES_MariaDB(t *testing.T) {
onlyMariaDB(t) // mariadb won't accept values rows if all the elements are placeholders, so we have to use raw statement
paymentID := IntegerColumn("payment_id")
increase := FloatColumn("increase")
paymentsToUpdate := CTE("values_cte", paymentID, increase)
stmt := WITH(
paymentsToUpdate.AS(
RawStatement(`
VALUES (204, 1.21),
(207, 1.02),
(200, 1.34),
(203, 1.72)
`),
),
)(
SELECT(
Payment.AllColumns,
paymentsToUpdate.AllColumns(),
).FROM(
Payment.
INNER_JOIN(paymentsToUpdate, paymentID.EQ(Payment.PaymentID)),
).WHERE(
increase.GT(Float(1.03)),
).ORDER_BY(
increase,
),
)
testutils.AssertStatementSql(t, stmt, `
WITH values_cte (payment_id, increase) AS (
VALUES (204, 1.21),
(207, 1.02),
(200, 1.34),
(203, 1.72)
)
SELECT payment.payment_id AS "payment.payment_id",
payment.customer_id AS "payment.customer_id",
payment.staff_id AS "payment.staff_id",
payment.rental_id AS "payment.rental_id",
payment.amount AS "payment.amount",
payment.payment_date AS "payment.payment_date",
payment.last_update AS "payment.last_update",
values_cte.payment_id AS "payment_id",
values_cte.increase AS "increase"
FROM dvds.payment
INNER JOIN values_cte ON (values_cte.payment_id = payment.payment_id)
WHERE values_cte.increase > ?
ORDER BY values_cte.increase;
`)
var dest []struct {
model.Payment
Increase float64
}
err := stmt.Query(db, &dest)
require.NoError(t, err)
testutils.AssertJSON(t, dest, `
[
{
"PaymentID": 204,
"CustomerID": 7,
"StaffID": 1,
"RentalID": 13476,
"Amount": 2.99,
"PaymentDate": "2005-08-20T01:06:04Z",
"LastUpdate": "2006-02-15T22:12:31Z",
"Increase": 1.21
},
{
"PaymentID": 200,
"CustomerID": 7,
"StaffID": 2,
"RentalID": 11542,
"Amount": 7.99,
"PaymentDate": "2005-08-17T00:51:32Z",
"LastUpdate": "2006-02-15T22:12:31Z",
"Increase": 1.34
},
{
"PaymentID": 203,
"CustomerID": 7,
"StaffID": 2,
"RentalID": 13373,
"Amount": 2.99,
"PaymentDate": "2005-08-19T21:23:31Z",
"LastUpdate": "2006-02-15T22:12:31Z",
"Increase": 1.72
}
]
`)
}

View file

@ -164,10 +164,10 @@ WITH payments_to_delete AS (
WHERE payment.amount < 0.5
)
DELETE FROM dvds.payment
WHERE payment.payment_id IN (
WHERE payment.payment_id IN ((
SELECT payments_to_delete.''payment.payment_id'' AS "payment.payment_id"
FROM payments_to_delete
);
));
`, "''", "`"))
tx, err := db.Begin()