Expand typed SQL DSL

This commit is contained in:
pavel 2026-08-28 00:02:32 +02:00
commit 314c8fb207
17 changed files with 1082 additions and 498 deletions

View file

@ -205,7 +205,7 @@ func TestGenerateSQLInsertDoUpdate(t *testing.T) {
fun upsertAccount(row: AccountRow): GotlinSQLQuery {
return sql.insert<AccountRow>(row)
.onConflict { it.id }
.doUpdate { excluded -> set(AccountRow.balance, excluded.balance) }
.doUpdate { excluded -> AccountRow.balance = excluded.balance }
.build()
}
`)
@ -223,8 +223,8 @@ fun upsert(row: BalanceRow): GotlinSQLQuery {
return sql.insert<BalanceRow>(row)
.onConflict { listOf(it.tenantId, it.id) }
.doUpdate { excluded ->
set(BalanceRow.amount, excluded.amount)
set(BalanceRow.pending, excluded.pending)
BalanceRow.amount = excluded.amount
BalanceRow.pending = excluded.pending
}
.build()
}
@ -234,6 +234,50 @@ fun upsert(row: BalanceRow): GotlinSQLQuery {
}
}
func TestGenerateSQLJoinsAliasesGroupingOffsetAndAggregates(t *testing.T) {
code := compileSQL(t, accountRowSource+`
@table("customers")
data class CustomerRow(@id var id: String, var name: String)
data class AccountSummary(var customerId: String, var customerName: String, var total: Double, var entries: Long)
fun summaries(minimum: Double): GotlinSQLQuery {
return sql.from<AccountRow>()
.alias("account")
.leftJoin<CustomerRow>("customer") { account, customer -> account.customerId == customer.id }
.select { account, customer -> AccountSummary(account.customerId, customer.name, sum(account.balance), count()) }
.where { account, customer -> account.balance > minimum }
.groupBy { account, customer -> listOf(account.customerId, customer.name) }
.having { account, customer -> sum(account.balance) > minimum }
.orderByDescending { account, customer -> sum(account.balance) }
.limit(10)
.offset(20)
.build()
}
`)
want := `SQL: "SELECT account.customer_id, customer.name, SUM(account.balance), COUNT(*) FROM accounts AS account LEFT JOIN customers AS customer ON account.customer_id = customer.id WHERE account.balance > $1 GROUP BY account.customer_id, customer.name HAVING SUM(account.balance) > $2 ORDER BY SUM(account.balance) DESC LIMIT 10 OFFSET 20"`
if !strings.Contains(code, want) {
t.Fatalf("generated Go missing %q:\n%s", want, code)
}
if !strings.Contains(code, `Args: []any{minimum, minimum}`) {
t.Fatalf("aggregate arguments missing:\n%s", code)
}
}
func TestGenerateSQLBulkInsert(t *testing.T) {
code := compileSQL(t, accountRowSource+`
fun insertAccounts(rows: List<AccountRow>): GotlinSQLQuery = sql.insertAll<AccountRow>(rows).build()
`)
for _, want := range []string{
`gotlinSQLBulkInsert[AccountRow](rows, "accounts", []string{"id", "customer_id", "kind", "balance"}`,
`func(row *AccountRow) []any { return []any{row.Id, row.CustomerId, row.AccountType, row.Balance} }`,
`func gotlinSQLBulkInsert[T any]`,
`panic("bulk insert requires at least one row")`,
} {
if !strings.Contains(code, want) {
t.Fatalf("generated Go missing %q:\n%s", want, code)
}
}
}
func TestRejectInvalidSQLQueries(t *testing.T) {
tests := []struct {
name string
@ -282,12 +326,12 @@ func TestRejectInvalidSQLQueries(t *testing.T) {
},
{
name: "unknown update target",
src: accountRowSource + `fun query(row: AccountRow): GotlinSQLQuery { return sql.insert<AccountRow>(row).onConflict { it.id }.doUpdate { excluded -> set(AccountRow.missing, excluded.balance) }.build() }`,
src: accountRowSource + `fun query(row: AccountRow): GotlinSQLQuery { return sql.insert<AccountRow>(row).onConflict { it.id }.doUpdate { excluded -> AccountRow.missing = excluded.balance }.build() }`,
want: "has no field missing",
},
{
name: "incompatible update fields",
src: accountRowSource + `fun query(row: AccountRow): GotlinSQLQuery { return sql.insert<AccountRow>(row).onConflict { it.id }.doUpdate { excluded -> set(AccountRow.balance, excluded.accountType) }.build() }`,
src: accountRowSource + `fun query(row: AccountRow): GotlinSQLQuery { return sql.insert<AccountRow>(row).onConflict { it.id }.doUpdate { excluded -> AccountRow.balance = excluded.accountType }.build() }`,
want: "has type Double",
},
{
@ -338,6 +382,32 @@ fun query(row: OtherRow): GotlinSQLQuery { return sql.insert<AccountRow>(row).on
src: accountRowSource + `fun query(row: AccountRow, pool: Any, ctx: Any): List<AccountRow> { return sql.insert<AccountRow>(row).fetch(pool, ctx).unwrap() }`,
want: "fetch() is only supported for sql.from",
},
{
name: "duplicate join alias",
src: accountRowSource + `
@table("customers") data class CustomerRow(@id var id: String)
fun query(): GotlinSQLQuery = sql.from<AccountRow>().alias("row").join<CustomerRow>("row") { account, customer -> account.customerId == customer.id }.build()
`,
want: `duplicate SQL alias "row"`,
},
{
name: "having without grouping",
src: accountRowSource + `fun query(): GotlinSQLQuery = sql.from<AccountRow>().having { count() > 0 }.build()`,
want: "after groupBy",
},
{
name: "negative offset",
src: accountRowSource + `fun query(): GotlinSQLQuery = sql.from<AccountRow>().offset(-1).build()`,
want: "offset() requires a non-negative Int",
},
{
name: "bulk insert wrong element type",
src: accountRowSource + `
@table("other") data class OtherRow(@id var id: String)
fun query(rows: List<OtherRow>): GotlinSQLQuery = sql.insertAll<AccountRow>(rows).build()
`,
want: "requires List<AccountRow>",
},
}
for _, test := range tests {