Skip to content

Commit 0c2aa0f

Browse files
committed
feat(delete): add using for postgres database
1 parent 4483721 commit 0c2aa0f

3 files changed

Lines changed: 83 additions & 1 deletion

File tree

delete.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ const (
1313
deleteMarkerInit injectionMarker = iota
1414
deleteMarkerAfterWith
1515
deleteMarkerAfterDeleteFrom
16+
deleteMarkerAfterUsing
1617
deleteMarkerAfterWhere
1718
deleteMarkerAfterOrderBy
1819
deleteMarkerAfterLimit
@@ -69,6 +70,7 @@ type DeleteBuilder struct {
6970
cteBuilder *CTEBuilder
7071

7172
tables []string
73+
usingTables []string
7274
orderByCols []string
7375
order string
7476
limitVar string
@@ -102,6 +104,18 @@ func (db *DeleteBuilder) DeleteFrom(table ...string) *DeleteBuilder {
102104
return db
103105
}
104106

107+
// Using sets the USING clause in DELETE.
108+
// It's a PostgreSQL extension that allows referencing other tables in the WHERE clause,
109+
// similar to a JOIN.
110+
//
111+
// db.DeleteFrom("orders").Using("customers").Where("orders.customer_id = customers.id", "customers.status = 'inactive'")
112+
// // Generates: DELETE FROM orders USING customers WHERE orders.customer_id = customers.id AND customers.status = 'inactive'
113+
func (db *DeleteBuilder) Using(table ...string) *DeleteBuilder {
114+
db.usingTables = table
115+
db.marker = deleteMarkerAfterUsing
116+
return db
117+
}
118+
105119
// TableNames returns all table names in this DELETE statement.
106120
func (db *DeleteBuilder) TableNames() []string {
107121
var additionalTableNames []string
@@ -264,6 +278,13 @@ func (db *DeleteBuilder) BuildWithFlavor(flavor Flavor, initialArg ...interface{
264278
db.injection.WriteTo(buf, insertMarkerAfterReturning)
265279
}
266280

281+
if len(db.usingTables) > 0 {
282+
buf.WriteLeadingString("USING ")
283+
buf.WriteStrings(db.usingTables, ", ")
284+
}
285+
286+
db.injection.WriteTo(buf, deleteMarkerAfterUsing)
287+
267288
if db.WhereClause != nil {
268289
db.whereClauseProxy.WhereClause = db.WhereClause
269290
defer func() {

delete_test.go

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,67 @@ func TestDeleteBuilderReturning(t *testing.T) {
194194
a.Equal("WITH temp_user AS (SELECT id FROM inactive_users) DELETE FROM user, temp_user WHERE user.id IN (SELECT id FROM temp_user) RETURNING id, deleted_at", sql)
195195
}
196196

197+
func ExampleDeleteBuilder_Using() {
198+
db := NewDeleteBuilder()
199+
db.DeleteFrom("orders")
200+
db.Using("customers")
201+
db.Where(
202+
"orders.customer_id = customers.id",
203+
db.Equal("customers.status", "inactive"),
204+
)
205+
206+
sql, args := db.BuildWithFlavor(PostgreSQL)
207+
fmt.Println(sql)
208+
fmt.Println(args)
209+
210+
// Output:
211+
// DELETE FROM orders USING customers WHERE orders.customer_id = customers.id AND customers.status = $1
212+
// [inactive]
213+
}
214+
215+
func TestDeleteBuilderUsing(t *testing.T) {
216+
a := assert.New(t)
217+
218+
// Single USING table
219+
db := NewDeleteBuilder()
220+
db.DeleteFrom("orders")
221+
db.Using("customers")
222+
db.Where("orders.customer_id = customers.id")
223+
224+
sql, _ := db.BuildWithFlavor(PostgreSQL)
225+
a.Equal("DELETE FROM orders USING customers WHERE orders.customer_id = customers.id", sql)
226+
227+
// Multiple USING tables
228+
db2 := NewDeleteBuilder()
229+
db2.DeleteFrom("orders")
230+
db2.Using("customers", "products")
231+
db2.Where("orders.customer_id = customers.id", "orders.product_id = products.id")
232+
233+
sql, _ = db2.BuildWithFlavor(PostgreSQL)
234+
a.Equal("DELETE FROM orders USING customers, products WHERE orders.customer_id = customers.id AND orders.product_id = products.id", sql)
235+
236+
// USING with RETURNING
237+
db3 := NewDeleteBuilder()
238+
db3.DeleteFrom("orders")
239+
db3.Using("customers")
240+
db3.Where(db3.Equal("customers.id", 42))
241+
db3.Returning("orders.id")
242+
243+
sql, args := db3.BuildWithFlavor(PostgreSQL)
244+
a.Equal("DELETE FROM orders USING customers WHERE customers.id = $1 RETURNING orders.id", sql)
245+
a.Equal([]interface{}{42}, args)
246+
247+
// SQL injection after USING
248+
db4 := NewDeleteBuilder()
249+
db4.DeleteFrom("orders")
250+
db4.Using("customers")
251+
db4.SQL("/* after using */")
252+
db4.Where("orders.customer_id = customers.id")
253+
254+
sql, _ = db4.BuildWithFlavor(PostgreSQL)
255+
a.Equal("DELETE FROM orders USING customers /* after using */ WHERE orders.customer_id = customers.id", sql)
256+
}
257+
197258
func TestDeleteBuilderClone(t *testing.T) {
198259
a := assert.New(t)
199260
cte := With(

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
module github.com/huandu/go-sqlbuilder
1+
module github.com/webdaad/go-sqlbuilder
22

33
go 1.18
44

0 commit comments

Comments
 (0)