1
0
forked from xorm/xorm
xorm/find.go

321 lines
8.1 KiB
Go

// Copyright 2016 The Xorm Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package xorm
import (
"database/sql"
"errors"
"reflect"
"strings"
"xorm.io/builder"
"xorm.io/xorm/v2/internal/convert"
"xorm.io/xorm/v2/internal/utils"
"xorm.io/xorm/v2/schemas"
)
const (
tpStruct = iota
tpNonStruct
)
// Find retrieve records from table, condiBeans's non-empty fields
// are conditions. beans could be []Struct, []*Struct, map[int64]Struct
// map[int64]*Struct
func (session *Session) Find(rowsSlicePtr any, condiBean ...any) error {
if session.isAutoClose {
defer session.Close()
}
return session.find(rowsSlicePtr, condiBean...)
}
// FindAndCount find the results and also return the counts
func (session *Session) FindAndCount(rowsSlicePtr any, condiBean ...any) (int64, error) {
if session.isAutoClose {
defer session.Close()
}
session.autoResetStatement = false
err := session.find(rowsSlicePtr, condiBean...)
if err != nil {
return 0, err
}
sliceValue := reflect.Indirect(reflect.ValueOf(rowsSlicePtr))
if sliceValue.Kind() != reflect.Slice && sliceValue.Kind() != reflect.Map {
return 0, errors.New("needs a pointer to a slice or a map")
}
sliceElementType := sliceValue.Type().Elem()
if sliceElementType.Kind() == reflect.Ptr {
sliceElementType = sliceElementType.Elem()
}
session.autoResetStatement = true
if session.statement.SelectStr != "" {
session.statement.SelectStr = ""
}
if len(session.statement.ColumnMap) > 0 && !session.statement.IsDistinct {
session.statement.ColumnMap = []string{}
}
session.statement.ResetOrderBy()
if session.statement.LimitN != nil {
session.statement.LimitN = nil
}
if session.statement.Start > 0 {
session.statement.Start = 0
}
// session has stored the conditions so we use `unscoped` to avoid duplicated condition.
if sliceElementType.Kind() == reflect.Struct {
return session.Unscoped().Count(reflect.New(sliceElementType).Interface())
}
return session.Unscoped().Count()
}
func (session *Session) find(rowsSlicePtr any, condiBean ...any) error {
defer session.resetStatement()
if session.statement.LastError != nil {
return session.statement.LastError
}
sliceValue := reflect.Indirect(reflect.ValueOf(rowsSlicePtr))
isSlice := sliceValue.Kind() == reflect.Slice
isMap := sliceValue.Kind() == reflect.Map
if !isSlice && !isMap {
return errors.New("needs a pointer to a slice or a map")
}
sliceElementType := sliceValue.Type().Elem()
tp := tpStruct
if session.statement.RefTable == nil {
if sliceElementType.Kind() == reflect.Ptr {
if sliceElementType.Elem().Kind() == reflect.Struct {
pv := reflect.New(sliceElementType.Elem())
if err := session.statement.SetRefValue(pv); err != nil {
return err
}
} else {
tp = tpNonStruct
}
} else if sliceElementType.Kind() == reflect.Struct {
pv := reflect.New(sliceElementType)
if err := session.statement.SetRefValue(pv); err != nil {
return err
}
} else {
tp = tpNonStruct
}
}
var (
table = session.statement.RefTable
addedTableName = session.statement.NeedTableName()
autoCond builder.Cond
)
if tp == tpStruct {
if !session.statement.NoAutoCondition && len(condiBean) > 0 {
condTable, err := session.engine.tagParser.Parse(reflect.ValueOf(condiBean[0]))
if err != nil {
return err
}
autoCond, err = session.statement.BuildConds(condTable, condiBean[0], true, true, false, true, addedTableName)
if err != nil {
return err
}
} else {
if col := table.DeletedColumn(); col != nil && !session.statement.GetUnscoped() { // tag "deleted" is enabled
autoCond = session.statement.CondDeleted(col)
}
}
}
// if it's a map with Cols but primary key not in column list, we still need the primary key
if isMap && !session.statement.ColumnMap.IsEmpty() {
for _, k := range session.statement.RefTable.PrimaryKeys {
session.statement.ColumnMap.Add(k)
}
}
sqlStr, args, err := session.statement.GenFindSQL(autoCond)
if err != nil {
return err
}
return session.noCacheFind(table, sliceValue, sqlStr, args...)
}
type QueryedField struct {
FieldName string
LowerFieldName string
ColumnType *sql.ColumnType
TempIndex int
ColumnSchema *schemas.Column
}
type ColumnsSchema struct {
Fields []*QueryedField
FieldNames []string
Types []*sql.ColumnType
}
func (columnsSchema *ColumnsSchema) ParseTableSchema(table *schemas.Table) {
for _, field := range columnsSchema.Fields {
field.ColumnSchema = table.GetColumnIdx(field.FieldName, field.TempIndex)
}
}
func ParseColumnsSchema(fieldNames []string, types []*sql.ColumnType, table *schemas.Table) *ColumnsSchema {
var columnsSchema ColumnsSchema
fields := make([]*QueryedField, 0, len(fieldNames))
for i, fieldName := range fieldNames {
field := &QueryedField{
FieldName: fieldName,
LowerFieldName: strings.ToLower(fieldName),
ColumnType: types[i],
}
fields = append(fields, field)
}
columnsSchema.Fields = fields
tempMap := make(map[string]int)
for _, field := range fields {
var idx int
var ok bool
if idx, ok = tempMap[field.LowerFieldName]; !ok {
idx = 0
} else {
idx++
}
tempMap[field.LowerFieldName] = idx
field.TempIndex = idx
}
if table != nil {
columnsSchema.ParseTableSchema(table)
}
return &columnsSchema
}
func (session *Session) noCacheFind(table *schemas.Table, containerValue reflect.Value, sqlStr string, args ...any) error {
elemType := containerValue.Type().Elem()
var isPointer bool
if elemType.Kind() == reflect.Ptr {
isPointer = true
elemType = elemType.Elem()
}
if elemType.Kind() == reflect.Ptr {
return errors.New("pointer to pointer is not supported")
}
rows, err := session.queryRows(sqlStr, args...)
if err != nil {
return err
}
defer rows.Close()
fields, err := rows.Columns()
if err != nil {
return err
}
types, err := rows.ColumnTypes()
if err != nil {
return err
}
newElemFunc := func(fields []string) reflect.Value {
return utils.New(elemType, len(fields), len(fields))
}
var containerValueSetFunc func(*reflect.Value, schemas.PK) error
if containerValue.Kind() == reflect.Slice {
containerValueSetFunc = func(newValue *reflect.Value, pk schemas.PK) error {
if isPointer {
containerValue.Set(reflect.Append(containerValue, newValue.Elem().Addr()))
} else {
containerValue.Set(reflect.Append(containerValue, newValue.Elem()))
}
return nil
}
} else {
keyType := containerValue.Type().Key()
if len(table.PrimaryKeys) == 0 {
return errors.New("don't support multiple primary key's map has non-slice key type")
}
if len(table.PrimaryKeys) > 1 && keyType.Kind() != reflect.Slice {
return errors.New("don't support multiple primary key's map has non-slice key type")
}
containerValueSetFunc = func(newValue *reflect.Value, pk schemas.PK) error {
keyValue := reflect.New(keyType)
cols := table.PKColumns()
if len(cols) == 1 {
if err := convert.AssignValue(keyValue, pk[0]); err != nil {
return err
}
} else {
keyValue.Set(reflect.ValueOf(&pk))
}
if isPointer {
containerValue.SetMapIndex(keyValue.Elem(), newValue.Elem().Addr())
} else {
containerValue.SetMapIndex(keyValue.Elem(), newValue.Elem())
}
return nil
}
}
if elemType.Kind() == reflect.Struct {
newValue := newElemFunc(fields)
tb, err := session.engine.tagParser.ParseWithCache(newValue)
if err != nil {
return err
}
columnsSchema := ParseColumnsSchema(fields, types, tb)
err = session.rows2Beans(rows, columnsSchema, fields, types, tb, newElemFunc, containerValueSetFunc)
rows.Close()
if err != nil {
return err
}
return session.executeProcessors()
}
for rows.Next() {
newValue := newElemFunc(fields)
bean := newValue.Interface()
switch elemType.Kind() {
case reflect.Slice:
err = session.getSlice(rows, types, fields, bean)
case reflect.Map:
err = session.getMap(rows, types, fields, bean)
default:
err = rows.Scan(bean)
}
if err != nil {
return err
}
if err := containerValueSetFunc(&newValue, nil); err != nil {
return err
}
}
return rows.Err()
}