Implement update join (#2383) (#2385)

backport #2383

Reviewed-on: https://gitea.com/xorm/xorm/pulls/2383

Reviewed-on: https://gitea.com/xorm/xorm/pulls/2385
This commit is contained in:
Lunny Xiao 2023-12-30 10:50:02 +00:00
parent cc28d99161
commit 0398dee813
3 changed files with 171 additions and 47 deletions

View File

@ -34,13 +34,7 @@ func (statement *Statement) writeJoins(w *builder.BytesWriter) error {
return nil return nil
} }
func (statement *Statement) writeJoin(buf *builder.BytesWriter, join join) error { func (statement *Statement) writeJoinTable(buf *builder.BytesWriter, join join) error {
// write join operator
if _, err := fmt.Fprint(buf, " ", join.op, " JOIN"); err != nil {
return err
}
// write join table or subquery
switch tp := join.table.(type) { switch tp := join.table.(type) {
case builder.Builder: case builder.Builder:
if _, err := fmt.Fprintf(buf, " ("); err != nil { if _, err := fmt.Fprintf(buf, " ("); err != nil {
@ -87,6 +81,19 @@ func (statement *Statement) writeJoin(buf *builder.BytesWriter, join join) error
return err return err
} }
} }
return nil
}
func (statement *Statement) writeJoin(buf *builder.BytesWriter, join join) error {
// write join operator
if _, err := fmt.Fprint(buf, " ", join.op, " JOIN"); err != nil {
return err
}
// write join table or subquery
if err := statement.writeJoinTable(buf, join); err != nil {
return err
}
// write on condition // write on condition
if _, err := fmt.Fprint(buf, " ON "); err != nil { if _, err := fmt.Fprint(buf, " ON "); err != nil {
@ -109,3 +116,14 @@ func (statement *Statement) writeJoin(buf *builder.BytesWriter, join join) error
return nil return nil
} }
func (statement *Statement) convertJoinCondition(join join) (builder.Cond, error) {
switch condTp := join.condition.(type) {
case string:
return builder.Expr(statement.ReplaceQuote(condTp), join.args...), nil
case builder.Cond:
return condTp, nil
default:
return nil, fmt.Errorf("unsupported join condition type: %v", condTp)
}
}

View File

@ -341,13 +341,51 @@ func (statement *Statement) writeUpdateTableName(updateWriter *builder.BytesWrit
} }
} }
func (statement *Statement) writeUpdateFrom(updateWriter *builder.BytesWriter) error { func (statement *Statement) writeUpdateFrom(updateWriter *builder.BytesWriter) (builder.Cond, error) {
if statement.dialect.URI().DBType != schemas.MSSQL || statement.TableAlias == "" { if statement.dialect.URI().DBType == schemas.MSSQL {
return nil if _, err := fmt.Fprint(updateWriter, " FROM"); err != nil {
return nil, err
}
if _, err := fmt.Fprint(updateWriter, " ", statement.quote(statement.TableName())); err != nil {
return nil, err
}
if statement.TableAlias != "" {
if _, err := fmt.Fprint(updateWriter, " ", statement.TableAlias); err != nil {
return nil, err
}
}
} }
_, err := fmt.Fprint(updateWriter, " FROM ", statement.quote(statement.TableName()), " ", statement.TableAlias) if len(statement.joins) == 0 {
return err return builder.NewCond(), nil
}
if statement.dialect.URI().DBType != schemas.MSSQL {
if _, err := fmt.Fprint(updateWriter, " FROM"); err != nil {
return nil, err
}
}
cond := builder.NewCond()
for i, join := range statement.joins {
if statement.dialect.URI().DBType == schemas.MSSQL || i > 0 {
if _, err := fmt.Fprint(updateWriter, ","); err != nil {
return nil, err
}
}
if err := statement.writeJoinTable(updateWriter, join); err != nil {
return nil, err
}
joinCond, err := statement.convertJoinCondition(join)
if err != nil {
return nil, err
}
cond = cond.And(joinCond)
}
return cond, nil
} }
func (statement *Statement) writeWhereOrAnd(updateWriter *builder.BytesWriter, hasConditions bool) error { func (statement *Statement) writeWhereOrAnd(updateWriter *builder.BytesWriter, hasConditions bool) error {
@ -555,8 +593,18 @@ func (statement *Statement) writeSetColumns(colNames []string, args []interface{
return err return err
} }
} }
if _, err := fmt.Fprint(w, colName); err != nil { if statement.dialect.URI().DBType != schemas.SQLITE && statement.dialect.URI().DBType != schemas.POSTGRES && len(statement.joins) > 0 {
return err tbName := statement.TableAlias
if tbName == "" {
tbName = statement.TableName()
}
if _, err := fmt.Fprint(w, tbName, ".", colName); err != nil {
return err
}
} else {
if _, err := fmt.Fprint(w, colName); err != nil {
return err
}
} }
} }
w.Append(args...) w.Append(args...)
@ -565,6 +613,12 @@ func (statement *Statement) writeSetColumns(colNames []string, args []interface{
} }
func (statement *Statement) writeUpdateSets(w *builder.BytesWriter, v reflect.Value, colNames []string, args []interface{}) error { func (statement *Statement) writeUpdateSets(w *builder.BytesWriter, v reflect.Value, colNames []string, args []interface{}) error {
// write set
if _, err := fmt.Fprint(w, " SET "); err != nil {
return err
}
previousLen := w.Len()
if err := statement.writeSetColumns(colNames, args)(w); err != nil { if err := statement.writeSetColumns(colNames, args)(w); err != nil {
return err return err
} }
@ -588,12 +642,51 @@ func (statement *Statement) writeUpdateSets(w *builder.BytesWriter, v reflect.Va
if err := statement.writeVersionIncrSet(w, v, setNumber > 0); err != nil { if err := statement.writeVersionIncrSet(w, v, setNumber > 0); err != nil {
return err return err
} }
// if no columns to be updated, return error
if previousLen == w.Len() {
return ErrNoColumnsTobeUpdated
}
return nil return nil
} }
var ErrNoColumnsTobeUpdated = errors.New("no columns found to be updated") var ErrNoColumnsTobeUpdated = errors.New("no columns found to be updated")
func (statement *Statement) WriteUpdate(updateWriter *builder.BytesWriter, cond builder.Cond, v reflect.Value, colNames []string, args []interface{}) error { func (statement *Statement) WriteUpdate(updateWriter *builder.BytesWriter, cond builder.Cond, v reflect.Value, colNames []string, args []interface{}) error {
switch statement.dialect.URI().DBType {
case schemas.MYSQL:
return statement.writeUpdateMySQL(updateWriter, cond, v, colNames, args)
case schemas.MSSQL:
return statement.writeUpdateMSSQL(updateWriter, cond, v, colNames, args)
default:
return statement.writeUpdateCommon(updateWriter, cond, v, colNames, args)
}
}
func (statement *Statement) writeUpdateMySQL(updateWriter *builder.BytesWriter, cond builder.Cond, v reflect.Value, colNames []string, args []interface{}) error {
if _, err := fmt.Fprintf(updateWriter, "UPDATE"); err != nil {
return err
}
if err := statement.writeUpdateTableName(updateWriter); err != nil {
return err
}
if err := statement.writeJoins(updateWriter); err != nil {
return err
}
if err := statement.writeUpdateSets(updateWriter, v, colNames, args); err != nil {
return err
}
// write where
if err := statement.writeWhereCond(updateWriter, cond); err != nil {
return err
}
if err := statement.writeOrderBys(updateWriter); err != nil {
return err
}
return statement.writeUpdateLimit(updateWriter, cond)
}
func (statement *Statement) writeUpdateMSSQL(updateWriter *builder.BytesWriter, cond builder.Cond, v reflect.Value, colNames []string, args []interface{}) error {
if _, err := fmt.Fprintf(updateWriter, "UPDATE"); err != nil { if _, err := fmt.Fprintf(updateWriter, "UPDATE"); err != nil {
return err return err
} }
@ -606,47 +699,56 @@ func (statement *Statement) WriteUpdate(updateWriter *builder.BytesWriter, cond
return err return err
} }
// write set if err := statement.writeUpdateSets(updateWriter, v, colNames, args); err != nil {
if _, err := fmt.Fprint(updateWriter, " SET "); err != nil { return err
}
// write from
joinConds, err := statement.writeUpdateFrom(updateWriter)
if err != nil {
return err
}
table := statement.RefTable
if statement.HasOrderBy() && table != nil && len(table.PrimaryKeys) == 1 {
} else {
// write where
if err := statement.writeWhereCond(updateWriter, cond.And(joinConds)); err != nil {
return err
}
}
return statement.writeUpdateLimit(updateWriter, cond.And(joinConds))
}
// writeUpdateCommon write update sql for non mysql && non mssql
func (statement *Statement) writeUpdateCommon(updateWriter *builder.BytesWriter, cond builder.Cond, v reflect.Value, colNames []string, args []interface{}) error {
if _, err := fmt.Fprintf(updateWriter, "UPDATE"); err != nil {
return err
}
if err := statement.writeUpdateTop(updateWriter); err != nil {
return err
}
if err := statement.writeUpdateTableName(updateWriter); err != nil {
return err return err
} }
previousLen := updateWriter.Len()
if err := statement.writeUpdateSets(updateWriter, v, colNames, args); err != nil { if err := statement.writeUpdateSets(updateWriter, v, colNames, args); err != nil {
return err return err
} }
// if no columns to be updated, return error
if previousLen == updateWriter.Len() {
return ErrNoColumnsTobeUpdated
}
// write from // write from
if err := statement.writeUpdateFrom(updateWriter); err != nil { joinConds, err := statement.writeUpdateFrom(updateWriter)
if err != nil {
return err return err
} }
if statement.dialect.URI().DBType == schemas.MSSQL { // write where
table := statement.RefTable if err := statement.writeWhereCond(updateWriter, cond.And(joinConds)); err != nil {
if statement.HasOrderBy() && table != nil && len(table.PrimaryKeys) == 1 { return err
} else {
// write where
if err := statement.writeWhereCond(updateWriter, cond); err != nil {
return err
}
}
} else {
// write where
if err := statement.writeWhereCond(updateWriter, cond); err != nil {
return err
}
} }
if statement.dialect.URI().DBType == schemas.MYSQL { return statement.writeUpdateLimit(updateWriter, cond.And(joinConds))
if err := statement.writeOrderBys(updateWriter); err != nil {
return err
}
}
return statement.writeUpdateLimit(updateWriter, cond)
} }

View File

@ -1471,7 +1471,6 @@ func TestNilFromDB(t *testing.T) {
assert.NotNil(t, tt4.Field1.cb) assert.NotNil(t, tt4.Field1.cb)
} }
/*
func TestUpdateWithJoin(t *testing.T) { func TestUpdateWithJoin(t *testing.T) {
type TestUpdateWithJoin struct { type TestUpdateWithJoin struct {
Id int64 Id int64
@ -1494,10 +1493,15 @@ func TestUpdateWithJoin(t *testing.T) {
_, err = testEngine.Insert(&TestUpdateWithJoin{ExtId: b.Id, Name: "test"}) _, err = testEngine.Insert(&TestUpdateWithJoin{ExtId: b.Id, Name: "test"})
assert.NoError(t, err) assert.NoError(t, err)
_, err = testEngine.Table("test_update_with_join").
Join("INNER", "test_update_with_join2 AS b", "test_update_with_join.ext_id = b.id").
Where("b.`name` = ?", "test").
Update(&TestUpdateWithJoin{Name: "test2"})
assert.NoError(t, err)
_, err = testEngine.Table("test_update_with_join"). _, err = testEngine.Table("test_update_with_join").
Join("INNER", "test_update_with_join2", "test_update_with_join.ext_id = test_update_with_join2.id"). Join("INNER", "test_update_with_join2", "test_update_with_join.ext_id = test_update_with_join2.id").
Where("test_update_with_join2.name = ?", "test"). Where("test_update_with_join2.`name` = ?", "test").
Update(&TestUpdateWithJoin{Name: "test2"}) Update(&TestUpdateWithJoin{Name: "test2"})
assert.NoError(t, err) assert.NoError(t, err)
} }
*/