Skip to content

Commit

Permalink
initial draft of model query
Browse files Browse the repository at this point in the history
  • Loading branch information
qiangxue committed Jul 13, 2016
1 parent 094c37e commit ff81765
Show file tree
Hide file tree
Showing 5 changed files with 152 additions and 46 deletions.
6 changes: 4 additions & 2 deletions builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,11 +136,13 @@ func (b *BaseBuilder) NewQuery(sql string) *Query {
// The parameters to this method should be the list column names to be selected.
// A column name may have an optional alias name. For example, Select("id", "my_name AS name").
func (b *BaseBuilder) Select(cols ...string) *SelectQuery {
return NewSelectQuery(b.db.Builder, b.executor).Select(cols...)
return NewSelectQuery(b.db.Builder).Select(cols...)
}

// Model returns a new ModelQuery object that can be used to perform model-based DB operations.
// The model passed to this method should be a pointer to a model struct.
func (b *BaseBuilder) Model(model interface{}) *ModelQuery {
return newModelQuery(model)
return newModelQuery(model, b.db.FieldMapper, b.db.Builder)
}

// GeneratePlaceholder generates an anonymous parameter placeholder with the given parameter ID.
Expand Down
21 changes: 13 additions & 8 deletions field.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ func DefaultFieldMapFunc(f string) string {
type fieldInfo struct {
name string
dbName string
isPK bool
path []int
}

Expand All @@ -54,7 +53,7 @@ func newStructValue(model interface{}, mapper FieldMapFunc) *structValue {
if value.Kind() != reflect.Ptr || value.Elem().Kind() != reflect.Struct || value.IsNil() {
return nil
}
t := reflect.TypeOf(model)
t := reflect.TypeOf(model).Elem()
var tableName string
if tm, ok := model.(TableModel); ok {
tableName = tm.TableName()
Expand All @@ -65,28 +64,35 @@ func newStructValue(model interface{}, mapper FieldMapFunc) *structValue {
si := getStructInfo(t, mapper)
return &structValue{
structInfo: si,
value: value,
value: value.Elem(),
tableName: tableName,
}
}

func (s *structValue) pk() map[string]interface{} {
return s.fields(s.pkNames...)
return s.columns(s.pkNames, nil)
}

func (s *structValue) fields(attrs ...string) map[string]interface{} {
func (s *structValue) columns(include, exclude []string) map[string]interface{} {
v := map[string]interface{}{}
if len(attrs) == 0 {
if len(include) == 0 {
for _, fi := range s.nameMap {
v[fi.dbName] = fi.getValue(s.value)
}
} else {
for _, attr := range attrs {
for _, attr := range include {
if fi, ok := s.nameMap[attr]; ok {
v[fi.dbName] = fi.getValue(s.value)
}
}
}
if len(exclude) > 0 {
for _, name := range exclude {
if fi, ok := s.nameMap[name]; ok {
delete(v, fi.dbName)
}
}
}
return v
}

Expand Down Expand Up @@ -179,7 +185,6 @@ func buildStructInfo(si *structInfo, a reflect.Type, path []int, namePrefix, dbN
fi := &fieldInfo{
name: concat(namePrefix, name),
dbName: concat(dbNamePrefix, dbName),
isPK: false,
path: path2,
}
si.nameMap[fi.name] = fi
Expand Down
71 changes: 54 additions & 17 deletions model_query.go
Original file line number Diff line number Diff line change
@@ -1,44 +1,81 @@
package dbx

import "database/sql"
import "errors"

type TableModel interface {
TableName() string
}

type ModelQuery struct {
builder Builder
model *structValue
builder Builder
model *structValue
exclude []string
lastError error
}

func newModelQuery(model interface{}, fieldMapFunc FieldMapFunc, builder Builder) *ModelQuery {
sv := newStructValue(model, fieldMapFunc)
if sv == nil {
// todo: log error
}
return &ModelQuery{
q := &ModelQuery{
builder: builder,
model: sv,
}
if sv == nil {
q.lastError = errors.New("The model must be specified as a pointer to the model struct.")
}
return q
}

func (q *ModelQuery) Insert(attrs ...string) (sql.Result, error) {
func (q *ModelQuery) Exclude(attrs ...string) *ModelQuery {
q.exclude = attrs
return q
}

func (q *ModelQuery) Insert(attrs ...string) error {
if q.lastError != nil {
return q.lastError
}
tableName := q.model.tableName
cols := q.model.fields(attrs...)
// todo: fill in PK, remove nil PK
return q.builder.Insert(tableName, Params(cols)).Execute()
cols := q.model.columns(attrs, q.exclude)
pk := q.model.pk()
ai := "" // auto-incremental column
for pkc := range pk {
if col, ok := cols[pkc]; ok {
if col == nil || col == 0 {
delete(cols, pkc)
ai = pkc
}
}
}

result, err := q.builder.Insert(tableName, Params(cols)).Execute()
if err == nil && ai != "" {
pkValue, err := result.LastInsertId()
if err != nil {
return err
}
q.model.dbNameMap[ai].getField(q.model.value).SetInt(pkValue)
}
return err
}

func (q *ModelQuery) Update(attrs ...string) (sql.Result, error) {
cols := q.model.fields(attrs...)
func (q *ModelQuery) Update(attrs ...string) error {
if q.lastError != nil {
return q.lastError
}
cols := q.model.columns(attrs, q.exclude)
pk := q.model.pk()
for name := range pk {
delete(cols, name)
}
return q.builder.Update(q.model.tableName, Params(cols), HashExp(pk)).Execute()
_, err := q.builder.Update(q.model.tableName, Params(cols), HashExp(pk)).Execute()
return err
}

func (q *ModelQuery) Delete() (sql.Result, error) {
pk := HashExp(q.model.pk())
return q.builder.Delete(q.model.tableName, pk).Execute()
func (q *ModelQuery) Delete() error {
if q.lastError != nil {
return q.lastError
}
pk := q.model.pk()
_, err := q.builder.Delete(q.model.tableName, HashExp(pk)).Execute()
return err
}
76 changes: 70 additions & 6 deletions model_query_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,83 @@ import (
)

type Item struct {
ID int `db:"pk"`
ID int
Name string
}

type Item2 struct {
ID *int `db:"pk"`
Name *string
}

func (m *Item2) TableName() string {
return "item"
}

func TestModelQuery_Insert(t *testing.T) {
db := getPreparedDB()
defer db.Close()

result, err := db.NewQuery("INSERT INTO item (name) VALUES ('test')").Execute()
name := "test"
item := Item{
Name: name,
}
err := db.Model(&item).Insert()
if assert.Nil(t, err) {
assert.Equal(t, 6, item.ID)
}

item2 := Item2{
Name: &name,
}
err = db.Model(&item2).Insert()
if assert.Nil(t, err) && assert.NotNil(t, item2.ID) {
assert.Equal(t, 7, *item2.ID)
}
}

func TestModelQuery_Update(t *testing.T) {
db := getPreparedDB()
defer db.Close()

item := Item{
ID: 2,
Name: "test",
}
err := db.Model(&item).Update()
if assert.Nil(t, err) {
var m Item
db.Select().From("item").Where(HashExp{"ID": 2}).One(&m)
assert.Equal(t, "test", m.Name)
}

id := 3
name := "test2"
item2 := Item2{
ID: &id,
Name: &name,
}
err = db.Model(&item2).Update()
if assert.Nil(t, err) {
var m Item2
db.Select().From("item").Where(HashExp{"ID": 3}).One(&m)
if assert.NotNil(t, m.Name) {
assert.Equal(t, "test2", *m.Name)
}
}
}

func TestModelQuery_Delete(t *testing.T) {
db := getPreparedDB()
defer db.Close()

item := Item{
ID: 2,
}
err := db.Model(&item).Delete()
if assert.Nil(t, err) {
rows, _ := result.RowsAffected()
assert.Equal(t, rows, int64(1), "Result.RowsAffected()")
lastID, _ := result.LastInsertId()
assert.Equal(t, lastID, int64(6), "Result.LastInsertId()")
var m Item
err := db.Select().From("item").Where(HashExp{"ID": 2}).One(&m)
assert.NotNil(t, err)
}
}
24 changes: 11 additions & 13 deletions select.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,7 @@ import (
// SelectQuery represents a DB-agnostic SELECT query.
// It can be built into a DB-specific query by calling the Build() method.
type SelectQuery struct {
builder Builder
executor Executor
builder Builder

selects []string
distinct bool
Expand Down Expand Up @@ -43,18 +42,17 @@ type UnionInfo struct {
}

// NewSelectQuery creates a new SelectQuery instance.
func NewSelectQuery(builder Builder, executor Executor) *SelectQuery {
func NewSelectQuery(builder Builder) *SelectQuery {
return &SelectQuery{
builder: builder,
executor: executor,
selects: []string{},
from: []string{},
join: []JoinInfo{},
orderBy: []string{},
groupBy: []string{},
union: []UnionInfo{},
limit: -1,
params: Params{},
builder: builder,
selects: []string{},
from: []string{},
join: []JoinInfo{},
orderBy: []string{},
groupBy: []string{},
union: []UnionInfo{},
limit: -1,
params: Params{},
}
}

Expand Down

0 comments on commit ff81765

Please sign in to comment.