mirror of
https://github.com/hanzoai/sqlbuilder.git
synced 2026-10-01 00:37:42 +00:00
- go.mod module path -> github.com/hanzoai/sqlbuilder (package name kept: sqlbuilder) - Rename exported Flavor constant ClickHouse -> Datastore (same iota ordinal 6, behavior byte-identical): const decl, all case labels (Interpolate/Quote/ PrepareInsertIgnore/args/select/union), Flavor.String() literal "ClickHouse" -> "Datastore", and the name<->flavor test map. - Rename unexported dialect helper clickhouseInterpolate -> datastoreInterpolate. - Rename example ExampleInsertBuilder_insertIgnore_clickhouse -> _datastore. - Update tests + README flavor list to Datastore. - Keep upstream MIT LICENSE; add NOTICE recording the fork + rename. Dialect SQL output unchanged; only the identifier is renamed.
378 lines
11 KiB
Go
378 lines
11 KiB
Go
// Copyright 2018 Huan Du. All rights reserved.
|
|
// Licensed under the MIT license that can be found in the LICENSE file.
|
|
|
|
package sqlbuilder
|
|
|
|
import (
|
|
"fmt"
|
|
"testing"
|
|
|
|
"github.com/huandu/go-assert"
|
|
)
|
|
|
|
func ExampleUpdate() {
|
|
sql := Update("demo.user").
|
|
Set(
|
|
"visited = visited + 1",
|
|
).
|
|
Where(
|
|
"id = 1234",
|
|
).
|
|
String()
|
|
|
|
fmt.Println(sql)
|
|
|
|
// Output:
|
|
// UPDATE demo.user SET visited = visited + 1 WHERE id = 1234
|
|
}
|
|
|
|
func ExampleUpdateBuilder() {
|
|
ub := NewUpdateBuilder()
|
|
ub.Update("demo.user")
|
|
ub.Set(
|
|
ub.Assign("type", "sys"),
|
|
ub.Incr("credit"),
|
|
"modified_at = UNIX_TIMESTAMP(NOW())", // It's allowed to write arbitrary SQL.
|
|
)
|
|
ub.Where(
|
|
ub.GreaterThan("id", 1234),
|
|
ub.Like("name", "%Du"),
|
|
ub.Or(
|
|
ub.IsNull("id_card"),
|
|
ub.In("status", 1, 2, 5),
|
|
),
|
|
"modified_at > created_at + "+ub.Var(86400), // It's allowed to write arbitrary SQL.
|
|
)
|
|
ub.OrderByAsc("id")
|
|
|
|
sql, args := ub.Build()
|
|
fmt.Println(sql)
|
|
fmt.Println(args)
|
|
|
|
// Output:
|
|
// UPDATE demo.user SET type = ?, credit = credit + 1, modified_at = UNIX_TIMESTAMP(NOW()) WHERE id > ? AND name LIKE ? AND (id_card IS NULL OR status IN (?, ?, ?)) AND modified_at > created_at + ? ORDER BY id ASC
|
|
// [sys 1234 %Du 1 2 5 86400]
|
|
}
|
|
|
|
func TestUpdateAssignments(t *testing.T) {
|
|
a := assert.New(t)
|
|
cases := map[string]func(ub *UpdateBuilder) string{
|
|
"f = f + 1|[]": func(ub *UpdateBuilder) string { return ub.Incr("f") },
|
|
"f = f - 1|[]": func(ub *UpdateBuilder) string { return ub.Decr("f") },
|
|
"f = f + $1|[123]": func(ub *UpdateBuilder) string { return ub.Add("f", 123) },
|
|
"f = f - $1|[123]": func(ub *UpdateBuilder) string { return ub.Sub("f", 123) },
|
|
"f = f * $1|[123]": func(ub *UpdateBuilder) string { return ub.Mul("f", 123) },
|
|
"f = f / $1|[123]": func(ub *UpdateBuilder) string { return ub.Div("f", 123) },
|
|
}
|
|
|
|
for expected, f := range cases {
|
|
ub := NewUpdateBuilder()
|
|
s := f(ub)
|
|
ub.Set(s)
|
|
_, args := ub.Build()
|
|
actual := fmt.Sprintf("%v|%v", s, args)
|
|
|
|
a.Equal(actual, expected)
|
|
}
|
|
}
|
|
|
|
func ExampleUpdateBuilder_SetMore() {
|
|
ub := NewUpdateBuilder()
|
|
ub.Update("demo.user")
|
|
ub.Set(
|
|
ub.Assign("type", "sys"),
|
|
ub.Incr("credit"),
|
|
)
|
|
ub.SetMore(
|
|
"modified_at = UNIX_TIMESTAMP(NOW())", // It's allowed to write arbitrary SQL.
|
|
)
|
|
|
|
sql, args := ub.Build()
|
|
fmt.Println(sql)
|
|
fmt.Println(args)
|
|
|
|
// Output:
|
|
// UPDATE demo.user SET type = ?, credit = credit + 1, modified_at = UNIX_TIMESTAMP(NOW())
|
|
// [sys]
|
|
}
|
|
|
|
func ExampleUpdateBuilder_SQL() {
|
|
ub := NewUpdateBuilder()
|
|
ub.SQL("/* before */")
|
|
ub.Update("demo.user")
|
|
ub.SQL("/* after update */")
|
|
ub.Set(
|
|
ub.Assign("type", "sys"),
|
|
)
|
|
ub.SQL("/* after set */")
|
|
ub.OrderByDesc("id")
|
|
ub.SQL("/* after order by */")
|
|
ub.Limit(10)
|
|
ub.SQL("/* after limit */")
|
|
|
|
sql := ub.String()
|
|
fmt.Println(sql)
|
|
|
|
// Output:
|
|
// /* before */ UPDATE demo.user /* after update */ SET type = ? /* after set */ ORDER BY id DESC /* after order by */ LIMIT ? /* after limit */
|
|
}
|
|
|
|
func ExampleUpdateBuilder_NumAssignment() {
|
|
ub := NewUpdateBuilder()
|
|
ub.Update("demo.user")
|
|
ub.Set(
|
|
ub.Assign("type", "sys"),
|
|
ub.Incr("credit"),
|
|
"modified_at = UNIX_TIMESTAMP(NOW())",
|
|
)
|
|
|
|
// Count the number of assignments.
|
|
fmt.Println(ub.NumAssignment())
|
|
|
|
// Output:
|
|
// 3
|
|
}
|
|
|
|
func ExampleUpdateBuilder_With() {
|
|
sql := With(
|
|
CTETable("users").As(
|
|
Select("id", "name").From("users").Where("prime IS NOT NULL"),
|
|
),
|
|
).Update("orders").Set(
|
|
"orders.transport_fee = 0",
|
|
).Where(
|
|
"users.id = orders.user_id",
|
|
).String()
|
|
|
|
fmt.Println(sql)
|
|
|
|
// Output:
|
|
// WITH users AS (SELECT id, name FROM users WHERE prime IS NOT NULL) UPDATE orders, users SET orders.transport_fee = 0 WHERE users.id = orders.user_id
|
|
}
|
|
|
|
func TestUpdateBuilderGetFlavor(t *testing.T) {
|
|
a := assert.New(t)
|
|
ub := newUpdateBuilder()
|
|
|
|
ub.SetFlavor(PostgreSQL)
|
|
flavor := ub.Flavor()
|
|
a.Equal(PostgreSQL, flavor)
|
|
|
|
ubClick := Datastore.NewUpdateBuilder()
|
|
flavor = ubClick.Flavor()
|
|
a.Equal(Datastore, flavor)
|
|
}
|
|
|
|
func ExampleUpdateBuilder_Returning() {
|
|
ub := NewUpdateBuilder()
|
|
ub.Update("user")
|
|
ub.Set(ub.Assign("name", "Huan Du"))
|
|
ub.Where(ub.Equal("id", 123))
|
|
ub.Returning("id", "updated_at")
|
|
|
|
sql, args := ub.BuildWithFlavor(PostgreSQL)
|
|
fmt.Println(sql)
|
|
fmt.Println(args)
|
|
|
|
// Output:
|
|
// UPDATE user SET name = $1 WHERE id = $2 RETURNING id, updated_at
|
|
// [Huan Du 123]
|
|
}
|
|
|
|
func TestUpdateBuilderReturning(t *testing.T) {
|
|
a := assert.New(t)
|
|
ub := NewUpdateBuilder()
|
|
ub.Update("user")
|
|
ub.Set(ub.Assign("name", "Huan Du"))
|
|
ub.Where(ub.Equal("id", 123))
|
|
ub.Returning("id", "updated_at")
|
|
|
|
sql, _ := ub.BuildWithFlavor(MySQL)
|
|
a.Equal("UPDATE user SET name = ? WHERE id = ?", sql)
|
|
|
|
sql, _ = ub.BuildWithFlavor(PostgreSQL)
|
|
a.Equal("UPDATE user SET name = $1 WHERE id = $2 RETURNING id, updated_at", sql)
|
|
|
|
sql, _ = ub.BuildWithFlavor(SQLite)
|
|
a.Equal("UPDATE user SET name = ? WHERE id = ? RETURNING id, updated_at", sql)
|
|
|
|
sql, _ = ub.BuildWithFlavor(SQLServer)
|
|
a.Equal("UPDATE user SET name = @p1 OUTPUT INSERTED.id, INSERTED.updated_at WHERE id = @p2", sql)
|
|
|
|
sql, _ = ub.BuildWithFlavor(CQL)
|
|
a.Equal("UPDATE user SET name = ? WHERE id = ?", sql)
|
|
|
|
sql, _ = ub.BuildWithFlavor(Datastore)
|
|
a.Equal("UPDATE user SET name = ? WHERE id = ?", sql)
|
|
|
|
sql, _ = ub.BuildWithFlavor(Presto)
|
|
a.Equal("UPDATE user SET name = ? WHERE id = ?", sql)
|
|
|
|
// Test with no returning columns
|
|
ub2 := NewUpdateBuilder()
|
|
ub2.Update("user")
|
|
ub2.Set(ub2.Assign("name", "Test"))
|
|
ub2.Where(ub2.Equal("id", 1))
|
|
ub2.Returning() // Empty returning
|
|
|
|
sql, _ = ub2.BuildWithFlavor(PostgreSQL)
|
|
a.Equal("UPDATE user SET name = $1 WHERE id = $2", sql)
|
|
|
|
// Test with single column
|
|
ub3 := NewUpdateBuilder()
|
|
ub3.Update("user")
|
|
ub3.Set(ub3.Assign("name", "Test"))
|
|
ub3.Where(ub3.Equal("id", 1))
|
|
ub3.Returning("id")
|
|
|
|
sql, _ = ub3.BuildWithFlavor(PostgreSQL)
|
|
a.Equal("UPDATE user SET name = $1 WHERE id = $2 RETURNING id", sql)
|
|
|
|
// Test with ORDER BY and LIMIT
|
|
ub4 := NewUpdateBuilder()
|
|
ub4.Update("user")
|
|
ub4.Set(ub4.Assign("name", "Test"))
|
|
ub4.Where(ub4.Equal("status", 1))
|
|
ub4.OrderBy("id").Asc()
|
|
ub4.Limit(5)
|
|
ub4.Returning("id", "name")
|
|
|
|
sql, _ = ub4.BuildWithFlavor(PostgreSQL)
|
|
a.Equal("UPDATE user SET name = $1 WHERE status = $2 ORDER BY id ASC LIMIT $3 RETURNING id, name", sql)
|
|
|
|
// Test chaining
|
|
ub5 := NewUpdateBuilder().Update("user").Set("status = 1").Returning("id").Returning("name", "updated_at")
|
|
sql, _ = ub5.BuildWithFlavor(PostgreSQL)
|
|
a.Equal("UPDATE user SET status = 1 RETURNING name, updated_at", sql) // Last Returning call overwrites
|
|
|
|
// Test SQL injection after RETURNING
|
|
ub6 := NewUpdateBuilder()
|
|
ub6.Update("user")
|
|
ub6.Set(ub6.Assign("name", "Test"))
|
|
ub6.Where(ub6.Equal("id", 1))
|
|
ub6.Returning("id", "name")
|
|
ub6.SQL("/* comment after returning */")
|
|
|
|
sql, _ = ub6.BuildWithFlavor(PostgreSQL)
|
|
a.Equal("UPDATE user SET name = $1 WHERE id = $2 RETURNING id, name /* comment after returning */", sql)
|
|
|
|
// Test with CTE (WITH clause)
|
|
cte := With(CTETable("temp_user").As(Select("id").From("active_users")))
|
|
ub7 := cte.Update("user")
|
|
ub7.Set(ub7.Assign("status", "active"))
|
|
ub7.Where("user.id IN (SELECT id FROM temp_user)")
|
|
ub7.Returning("id", "status")
|
|
|
|
sql, _ = ub7.BuildWithFlavor(PostgreSQL)
|
|
a.Equal("WITH temp_user AS (SELECT id FROM active_users) UPDATE user SET status = $1 FROM temp_user WHERE user.id IN (SELECT id FROM temp_user) RETURNING id, status", sql)
|
|
}
|
|
|
|
func TestUpdateBuilderClone(t *testing.T) {
|
|
a := assert.New(t)
|
|
cte := With(
|
|
CTETable("vip").As(Select("user_id").From("vip_users")),
|
|
)
|
|
ub := cte.Update("orders").Set("discount = 1").Where("orders.user_id = vip.user_id").OrderBy("orders.id").Desc().Limit(2).Returning("orders.id")
|
|
|
|
clone := ub.Clone()
|
|
s1, args1 := ub.BuildWithFlavor(PostgreSQL)
|
|
s2, args2 := clone.BuildWithFlavor(PostgreSQL)
|
|
a.Equal(s1, s2)
|
|
a.Equal(args1, args2)
|
|
|
|
clone.Asc().Limit(5)
|
|
a.NotEqual(ub.String(), clone.String())
|
|
}
|
|
|
|
func TestUpdateBuilderFrom(t *testing.T) {
|
|
a := assert.New(t)
|
|
ub := NewUpdateBuilder()
|
|
ub.Update("user")
|
|
ub.Set(ub.Assign("name", "Huan Du"))
|
|
ub.From("person")
|
|
ub.Where(ub.Equal("id", 123))
|
|
|
|
sql, _ := ub.BuildWithFlavor(MySQL)
|
|
a.Equal("UPDATE user SET name = ? WHERE id = ?", sql)
|
|
|
|
sql, _ = ub.BuildWithFlavor(PostgreSQL)
|
|
a.Equal("UPDATE user SET name = $1 FROM person WHERE id = $2", sql)
|
|
|
|
sql, _ = ub.BuildWithFlavor(SQLite)
|
|
a.Equal("UPDATE user SET name = ? FROM person WHERE id = ?", sql)
|
|
|
|
sql, _ = ub.BuildWithFlavor(SQLServer)
|
|
a.Equal("UPDATE user SET name = @p1 FROM person WHERE id = @p2", sql)
|
|
|
|
sql, _ = ub.BuildWithFlavor(CQL)
|
|
a.Equal("UPDATE user SET name = ? WHERE id = ?", sql)
|
|
|
|
sql, _ = ub.BuildWithFlavor(Datastore)
|
|
a.Equal("UPDATE user SET name = ? WHERE id = ?", sql)
|
|
|
|
sql, _ = ub.BuildWithFlavor(Presto)
|
|
a.Equal("UPDATE user SET name = ? WHERE id = ?", sql)
|
|
|
|
// Test with no from
|
|
ub2 := NewUpdateBuilder()
|
|
ub2.Update("user")
|
|
ub2.Set(ub2.Assign("name", "Test"))
|
|
ub2.From()
|
|
ub2.Where(ub2.Equal("id", 1))
|
|
|
|
sql, _ = ub2.BuildWithFlavor(PostgreSQL)
|
|
a.Equal("UPDATE user SET name = $1 WHERE id = $2", sql)
|
|
|
|
// Test with multiple from tables
|
|
ub3 := NewUpdateBuilder()
|
|
ub3.Update("user")
|
|
ub3.Set(ub3.Assign("name", "Test"))
|
|
ub3.From("person", "company")
|
|
ub3.Where(ub3.Equal("id", 1))
|
|
|
|
sql, _ = ub3.BuildWithFlavor(PostgreSQL)
|
|
a.Equal("UPDATE user SET name = $1 FROM person, company WHERE id = $2", sql)
|
|
|
|
// Test chaining
|
|
ub5 := NewUpdateBuilder().Update("user").Set("status = 1").From("person").From("company")
|
|
sql, _ = ub5.BuildWithFlavor(PostgreSQL)
|
|
a.Equal("UPDATE user SET status = 1 FROM company", sql) // Last From call overwrites
|
|
|
|
// Test SQL injection after FROM
|
|
ub6 := NewUpdateBuilder()
|
|
ub6.Update("user")
|
|
ub6.Set(ub6.Assign("name", "Test"))
|
|
ub6.From("person")
|
|
ub6.SQL("/* comment after from */")
|
|
ub6.Where(ub6.Equal("id", 1))
|
|
|
|
sql, _ = ub6.BuildWithFlavor(PostgreSQL)
|
|
a.Equal("UPDATE user SET name = $1 FROM person /* comment after from */ WHERE id = $2", sql)
|
|
|
|
// Test with CTE (WITH clause)
|
|
cte := With(CTETable("temp_user").As(Select("id").From("active_users")))
|
|
ub7 := cte.Update("user")
|
|
ub7.Set(ub7.Assign("status", "active"))
|
|
ub7.From("person")
|
|
ub7.Where("user.id IN (SELECT id FROM temp_user)")
|
|
|
|
sql, _ = ub7.BuildWithFlavor(PostgreSQL)
|
|
a.Equal("WITH temp_user AS (SELECT id FROM active_users) UPDATE user SET status = $1 FROM temp_user, person WHERE user.id IN (SELECT id FROM temp_user)", sql)
|
|
|
|
// Test with SQLServer Returning
|
|
ub8 := ub.Clone().Returning("id", "name")
|
|
sql, _ = ub8.BuildWithFlavor(SQLServer)
|
|
a.Equal("UPDATE user SET name = @p1 OUTPUT INSERTED.id, INSERTED.name FROM person WHERE id = @p2", sql)
|
|
|
|
// Test with SQL injection after WHERE
|
|
ub9 := NewUpdateBuilder()
|
|
ub9.Update("user")
|
|
ub9.Set(ub9.Assign("name", "Test"))
|
|
ub9.From("person")
|
|
ub9.Where("user.id = person.id")
|
|
ub9.SQL("/* comment after where */")
|
|
|
|
sql, _ = ub9.BuildWithFlavor(PostgreSQL)
|
|
a.Equal("UPDATE user SET name = $1 FROM person WHERE user.id = person.id /* comment after where */", sql)
|
|
}
|