add LeftJoinOnField/InnerJoinOnField/InnerJoinOnField/FieldsPrefix/FieldsExPrefix for package gdb

This commit is contained in:
John Guo
2021-10-29 15:12:31 +08:00
parent a6a8d787e4
commit 6192d32501
14 changed files with 304 additions and 76 deletions

View File

@ -408,6 +408,7 @@ type formatWhereInput struct {
OmitEmpty bool
Schema string
Table string
Prefix string // Field prefix, eg: "user.", "order.".
}
// formatWhere formats where statement and its arguments for `Where` and `Having` statements.
@ -436,6 +437,7 @@ func formatWhere(db DB, in formatWhereInput) (newWhere string, newArgs []interfa
Args: newArgs,
Key: key,
Value: value,
Prefix: in.Prefix,
})
}
@ -462,6 +464,7 @@ func formatWhere(db DB, in formatWhereInput) (newWhere string, newArgs []interfa
Key: ketStr,
Value: value,
OmitEmpty: in.OmitEmpty,
Prefix: in.Prefix,
})
return true
})
@ -494,6 +497,7 @@ func formatWhere(db DB, in formatWhereInput) (newWhere string, newArgs []interfa
Key: foundKey,
Value: foundValue,
OmitEmpty: in.OmitEmpty,
Prefix: in.Prefix,
})
}
}
@ -501,18 +505,31 @@ func formatWhere(db DB, in formatWhereInput) (newWhere string, newArgs []interfa
default:
// Usually a string.
var (
i = 0
whereStr = gconv.String(in.Where)
)
// Is `whereStr` a field name which composed as a key-value condition?
// Eg:
// Where("id", []int{}).All() -> SELECT xxx FROM xxx WHERE 0=1
// Where("name", "").All() -> SELECT xxx FROM xxx WHERE `name`=''
// OmitEmpty().Where("id", []int{}).All() -> SELECT xxx FROM xxx
// OmitEmpty().("name", "").All() -> SELECT xxx FROM xxx
if in.OmitEmpty && len(in.Args) == 1 && gstr.Count(whereStr, "?") == 0 && utils.IsEmpty(in.Args[0]) {
// Where("id", 1)
// Where("id", g.Slice{1,2,3})
if gregex.IsMatchString(regularFieldNameWithoutDotRegPattern, whereStr) && len(in.Args) == 1 {
newArgs = formatWhereKeyValue(formatWhereKeyValueInput{
Db: db,
Buffer: buffer,
Args: newArgs,
Key: whereStr,
Value: in.Args[0],
OmitEmpty: in.OmitEmpty,
Prefix: in.Prefix,
})
in.Args = in.Args[:0]
break
}
// Regular string and parameter place holder handling.
// Eg:
// Where("id in(?) and name=?", g.Slice{1,2,3}, "john")
var (
i = 0
)
for {
if i >= len(in.Args) {
break
@ -612,6 +629,7 @@ type formatWhereKeyValueInput struct {
Key string
Value interface{}
OmitEmpty bool
Prefix string // Field prefix, eg: "user.", "order.".
}
// formatWhereKeyValue handles each key-value pair of the parameter map.
@ -628,6 +646,9 @@ func formatWhereKeyValue(in formatWhereKeyValueInput) (newArgs []interface{}) {
if in.OmitEmpty && holderCount == 0 && gutil.IsEmpty(in.Value) {
return in.Args
}
if in.Prefix != "" && !gstr.Contains(quotedKey, ".") {
quotedKey = in.Prefix + "." + quotedKey
}
if in.Buffer.Len() > 0 {
in.Buffer.WriteString(" AND ")
}
@ -664,7 +685,7 @@ func formatWhereKeyValue(in formatWhereKeyValueInput) (newArgs []interface{}) {
in.Buffer.WriteString(quotedKey)
}
} else {
// It also supports "LIKE" statement, which we considers it an operator.
// It also supports "LIKE" statement, which we consider it an operator.
quotedKey = gstr.Trim(quotedKey)
if gstr.Pos(quotedKey, "?") == -1 {
like := " LIKE"

View File

@ -334,6 +334,12 @@ func (m *Model) Page(page, limit int) *Model {
//
// The parameter `limit1` specifies whether limits querying only one record if m.limit is not set.
func (m *Model) formatCondition(limit1 bool, isCountStatement bool) (conditionWhere string, conditionExtra string, conditionArgs []interface{}) {
var (
prefix = ""
)
if gstr.Contains(m.tables, " JOIN ") {
prefix = m.db.GetCore().QuoteWord(m.tablesInit)
}
if len(m.whereHolder) > 0 {
for _, v := range m.whereHolder {
switch v.Operator {
@ -346,6 +352,7 @@ func (m *Model) formatCondition(limit1 bool, isCountStatement bool) (conditionWh
OmitEmpty: m.option&optionOmitEmptyWhere > 0,
Schema: m.schema,
Table: m.tables,
Prefix: prefix,
})
if len(newWhere) > 0 {
conditionWhere = newWhere
@ -363,6 +370,7 @@ func (m *Model) formatCondition(limit1 bool, isCountStatement bool) (conditionWh
OmitEmpty: m.option&optionOmitEmptyWhere > 0,
Schema: m.schema,
Table: m.tables,
Prefix: prefix,
})
if len(newWhere) > 0 {
if len(conditionWhere) == 0 {
@ -383,6 +391,7 @@ func (m *Model) formatCondition(limit1 bool, isCountStatement bool) (conditionWh
OmitEmpty: m.option&optionOmitEmptyWhere > 0,
Schema: m.schema,
Table: m.tables,
Prefix: prefix,
})
if len(newWhere) > 0 {
if len(conditionWhere) == 0 {
@ -430,6 +439,7 @@ func (m *Model) formatCondition(limit1 bool, isCountStatement bool) (conditionWh
OmitEmpty: m.option&optionOmitEmptyWhere > 0,
Schema: m.schema,
Table: m.tables,
Prefix: prefix,
})
if len(havingStr) > 0 {
conditionExtra += " HAVING " + havingStr

View File

@ -17,6 +17,12 @@ import (
// Fields appends `fieldNamesOrMapStruct` to the operation fields of the model, multiple fields joined using char ','.
// The parameter `fieldNamesOrMapStruct` can be type of string/map/*map/struct/*struct.
//
// Eg:
// Fields("id", "name", "age")
// Fields([]string{"id", "name", "age"})
// Fields(map[string]interface{}{"id":1, "name":"john", "age":18})
// Fields(User{ Id: 1, Name: "john", Age: 18})
func (m *Model) Fields(fieldNamesOrMapStruct ...interface{}) *Model {
length := len(fieldNamesOrMapStruct)
if length == 0 {
@ -52,10 +58,21 @@ func (m *Model) Fields(fieldNamesOrMapStruct ...interface{}) *Model {
return m
}
// FieldsPrefix performs as function Fields but add extra prefix for each field.
func (m *Model) FieldsPrefix(prefix string, fieldNamesOrMapStruct ...interface{}) *Model {
model := m.Fields(fieldNamesOrMapStruct...)
array := gstr.SplitAndTrim(model.fields, ",")
gstr.PrefixArray(array, prefix+".")
model.fields = gstr.Join(array, ",")
return model
}
// FieldsEx appends `fieldNamesOrMapStruct` to the excluded operation fields of the model,
// multiple fields joined using char ','.
// Note that this function supports only single table operations.
// The parameter `fieldNamesOrMapStruct` can be type of string/map/*map/struct/*struct.
//
// Also see Fields.
func (m *Model) FieldsEx(fieldNamesOrMapStruct ...interface{}) *Model {
length := len(fieldNamesOrMapStruct)
if length == 0 {
@ -80,6 +97,15 @@ func (m *Model) FieldsEx(fieldNamesOrMapStruct ...interface{}) *Model {
return m
}
// FieldsExPrefix performs as function FieldsEx but add extra prefix for each field.
func (m *Model) FieldsExPrefix(prefix string, fieldNamesOrMapStruct ...interface{}) *Model {
model := m.FieldsEx(fieldNamesOrMapStruct...)
array := gstr.SplitAndTrim(model.fieldsEx, ",")
gstr.PrefixArray(array, prefix+".")
model.fieldsEx = gstr.Join(array, ",")
return model
}
// FieldCount formats and appends commonly used field `COUNT(column)` to the select fields of model.
func (m *Model) FieldCount(column string, as ...string) *Model {
asStr := ""

View File

@ -25,40 +25,93 @@ func isSubQuery(s string) bool {
// LeftJoin does "LEFT JOIN ... ON ..." statement on the model.
// The parameter `table` can be joined table and its joined condition,
// and also with its alias name, like:
// Table("user").LeftJoin("user_detail", "user_detail.uid=user.uid")
// Table("user", "u").LeftJoin("user_detail", "ud", "ud.uid=u.uid")
// Table("user", "u").LeftJoin("SELECT xxx FROM xxx AS a", "a.uid=u.uid")
// and also with its alias name.
//
// Eg:
// Model("user").LeftJoin("user_detail", "user_detail.uid=user.uid")
// Model("user", "u").LeftJoin("user_detail", "ud", "ud.uid=u.uid")
// Model("user", "u").LeftJoin("SELECT xxx FROM xxx AS a", "a.uid=u.uid")
func (m *Model) LeftJoin(table ...string) *Model {
return m.doJoin("LEFT", table...)
}
// RightJoin does "RIGHT JOIN ... ON ..." statement on the model.
// The parameter `table` can be joined table and its joined condition,
// and also with its alias name, like:
// Table("user").RightJoin("user_detail", "user_detail.uid=user.uid")
// Table("user", "u").RightJoin("user_detail", "ud", "ud.uid=u.uid")
// Table("user", "u").RightJoin("SELECT xxx FROM xxx AS a", "a.uid=u.uid")
// and also with its alias name.
//
// Eg:
// Model("user").RightJoin("user_detail", "user_detail.uid=user.uid")
// Model("user", "u").RightJoin("user_detail", "ud", "ud.uid=u.uid")
// Model("user", "u").RightJoin("SELECT xxx FROM xxx AS a", "a.uid=u.uid")
func (m *Model) RightJoin(table ...string) *Model {
return m.doJoin("RIGHT", table...)
}
// InnerJoin does "INNER JOIN ... ON ..." statement on the model.
// The parameter `table` can be joined table and its joined condition,
// and also with its alias name, like:
// Table("user").InnerJoin("user_detail", "user_detail.uid=user.uid")
// Table("user", "u").InnerJoin("user_detail", "ud", "ud.uid=u.uid")
// Table("user", "u").InnerJoin("SELECT xxx FROM xxx AS a", "a.uid=u.uid")
// and also with its alias name
//
// Eg:
// Model("user").InnerJoin("user_detail", "user_detail.uid=user.uid")
// Model("user", "u").InnerJoin("user_detail", "ud", "ud.uid=u.uid")
// Model("user", "u").InnerJoin("SELECT xxx FROM xxx AS a", "a.uid=u.uid")
func (m *Model) InnerJoin(table ...string) *Model {
return m.doJoin("INNER", table...)
}
// LeftJoinOnField performs as LeftJoin, but it joins both tables with the same field name.
//
// Eg:
// Model("order").LeftJoinOnField("user", "user_id")
// Model("order").LeftJoinOnField("product", "product_id")
func (m *Model) LeftJoinOnField(table, field string) *Model {
return m.doJoin("LEFT", table, fmt.Sprintf(
`%s.%s=%s.%s`,
m.tables,
m.db.GetCore().QuoteWord(field),
m.db.GetCore().QuoteWord(table),
m.db.GetCore().QuoteWord(field),
))
}
// RightJoinOnField performs as RightJoin, but it joins both tables with the same field name.
//
// Eg:
// Model("order").InnerJoinOnField("user", "user_id")
// Model("order").InnerJoinOnField("product", "product_id")
func (m *Model) RightJoinOnField(table, field string) *Model {
return m.doJoin("RIGHT", table, fmt.Sprintf(
`%s.%s=%s.%s`,
m.tables,
m.db.GetCore().QuoteWord(field),
m.db.GetCore().QuoteWord(table),
m.db.GetCore().QuoteWord(field),
))
}
// InnerJoinOnField performs as InnerJoin, but it joins both tables with the same field name.
//
// Eg:
// Model("order").InnerJoinOnField("user", "user_id")
// Model("order").InnerJoinOnField("product", "product_id")
func (m *Model) InnerJoinOnField(table, field string) *Model {
return m.doJoin("INNER", table, fmt.Sprintf(
`%s.%s=%s.%s`,
m.tables,
m.db.GetCore().QuoteWord(field),
m.db.GetCore().QuoteWord(table),
m.db.GetCore().QuoteWord(field),
))
}
// doJoin does "LEFT/RIGHT/INNER JOIN ... ON ..." statement on the model.
// The parameter `table` can be joined table and its joined condition,
// and also with its alias name, like:
// Model("user").InnerJoin("user_detail", "user_detail.uid=user.uid")
// Model("user", "u").InnerJoin("user_detail", "ud", "ud.uid=u.uid")
// Model("user", "u").InnerJoin("SELECT xxx FROM xxx AS a", "a.uid=u.uid")
// and also with its alias name.
//
// Eg:
// Model("user").InnerJoin("user_detail", "user_detail.uid=user.uid")
// Model("user", "u").InnerJoin("user_detail", "ud", "ud.uid=u.uid")
// Model("user", "u").InnerJoin("SELECT xxx FROM xxx AS a", "a.uid=u.uid")
// Related issues:
// https://github.com/gogf/gf/issues/1024
func (m *Model) doJoin(operator string, table ...string) *Model {

View File

@ -30,10 +30,10 @@ func (tx *TX) Schema(schema string) *Schema {
}
}
// Table creates and returns a new ORM model.
// Model creates and returns a new ORM model.
// The parameter `tables` can be more than one table names, like :
// "user", "user u", "user, user_detail", "user u, user_detail ud"
func (s *Schema) Table(table string) *Model {
func (s *Schema) Model(table string) *Model {
var m *Model
if s.tx != nil {
m = s.tx.Model(table)
@ -51,9 +51,3 @@ func (s *Schema) Table(table string) *Model {
m.schema = s.schema
return m
}
// Model is alias of Core.Table.
// See Core.Table.
func (s *Schema) Model(table string) *Model {
return s.Table(table)
}

View File

@ -2308,31 +2308,31 @@ func Test_Model_Schema2(t *testing.T) {
}()
// Schema.
gtest.C(t, func(t *gtest.T) {
v, err := db.Schema(TestSchema1).Table(table).Value("nickname", "id=2")
v, err := db.Schema(TestSchema1).Model(table).Value("nickname", "id=2")
t.AssertNil(err)
t.Assert(v.String(), "name_2")
r, err := db.Schema(TestSchema1).Table(table).Update(g.Map{"nickname": "name_200"}, "id=2")
r, err := db.Schema(TestSchema1).Model(table).Update(g.Map{"nickname": "name_200"}, "id=2")
t.AssertNil(err)
n, _ := r.RowsAffected()
t.Assert(n, 1)
v, err = db.Schema(TestSchema1).Table(table).Value("nickname", "id=2")
v, err = db.Schema(TestSchema1).Model(table).Value("nickname", "id=2")
t.AssertNil(err)
t.Assert(v.String(), "name_200")
v, err = db.Schema(TestSchema2).Table(table).Value("nickname", "id=2")
v, err = db.Schema(TestSchema2).Model(table).Value("nickname", "id=2")
t.AssertNil(err)
t.Assert(v.String(), "name_2")
v, err = db.Schema(TestSchema1).Table(table).Value("nickname", "id=2")
v, err = db.Schema(TestSchema1).Model(table).Value("nickname", "id=2")
t.AssertNil(err)
t.Assert(v.String(), "name_200")
})
// Schema.
gtest.C(t, func(t *gtest.T) {
i := 1000
_, err := db.Schema(TestSchema1).Table(table).Insert(g.Map{
_, err := db.Schema(TestSchema1).Model(table).Insert(g.Map{
"id": i,
"passport": fmt.Sprintf(`user_%d`, i),
"password": fmt.Sprintf(`pass_%d`, i),
@ -2342,11 +2342,11 @@ func Test_Model_Schema2(t *testing.T) {
})
t.AssertNil(err)
v, err := db.Schema(TestSchema1).Table(table).Value("nickname", "id=?", i)
v, err := db.Schema(TestSchema1).Model(table).Value("nickname", "id=?", i)
t.AssertNil(err)
t.Assert(v.String(), "name_1000")
v, err = db.Schema(TestSchema2).Table(table).Value("nickname", "id=?", i)
v, err = db.Schema(TestSchema2).Model(table).Value("nickname", "id=?", i)
t.AssertNil(err)
t.Assert(v.String(), "")
})

View File

@ -0,0 +1,86 @@
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package gdb_test
import (
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gtime"
"github.com/gogf/gf/v2/test/gtest"
"testing"
)
func Test_Model_LeftJoinOnField(t *testing.T) {
var (
table1 = gtime.TimestampNanoStr() + "_table1"
table2 = gtime.TimestampNanoStr() + "_table2"
)
createInitTable(table1)
defer dropTable(table1)
createInitTable(table2)
defer dropTable(table2)
db.SetDebug(true)
gtest.C(t, func(t *gtest.T) {
r, err := db.Model(table1).
FieldsPrefix(table1, "*").
LeftJoinOnField(table2, "id").
Where("id", g.Slice{1, 2}).
Order("id asc").All()
t.AssertNil(err)
t.Assert(len(r), 2)
t.Assert(r[0]["id"], "1")
t.Assert(r[1]["id"], "2")
})
}
func Test_Model_RightJoinOnField(t *testing.T) {
var (
table1 = gtime.TimestampNanoStr() + "_table1"
table2 = gtime.TimestampNanoStr() + "_table2"
)
createInitTable(table1)
defer dropTable(table1)
createInitTable(table2)
defer dropTable(table2)
db.SetDebug(true)
gtest.C(t, func(t *gtest.T) {
r, err := db.Model(table1).
FieldsPrefix(table1, "*").
RightJoinOnField(table2, "id").
Where("id", g.Slice{1, 2}).
Order("id asc").All()
t.AssertNil(err)
t.Assert(len(r), 2)
t.Assert(r[0]["id"], "1")
t.Assert(r[1]["id"], "2")
})
}
func Test_Model_InnerJoinOnField(t *testing.T) {
var (
table1 = gtime.TimestampNanoStr() + "_table1"
table2 = gtime.TimestampNanoStr() + "_table2"
)
createInitTable(table1)
defer dropTable(table1)
createInitTable(table2)
defer dropTable(table2)
db.SetDebug(true)
gtest.C(t, func(t *gtest.T) {
r, err := db.Model(table1).
FieldsPrefix(table1, "*").
InnerJoinOnField(table2, "id").
Where("id", g.Slice{1, 2}).
Order("id asc").All()
t.AssertNil(err)
t.Assert(len(r), 2)
t.Assert(r[0]["id"], "1")
t.Assert(r[1]["id"], "2")
})
}