[#2309] added query by filter record helpers

This commit is contained in:
Gani Georgiev
2023-05-31 11:47:16 +03:00
parent 0fb92720f8
commit ddca49ba16
5 changed files with 285 additions and 5 deletions
+89
View File
@@ -1,6 +1,7 @@
package daos
import (
"database/sql"
"errors"
"fmt"
"strings"
@@ -8,8 +9,10 @@ import (
"github.com/pocketbase/dbx"
"github.com/pocketbase/pocketbase/models"
"github.com/pocketbase/pocketbase/models/schema"
"github.com/pocketbase/pocketbase/resolvers"
"github.com/pocketbase/pocketbase/tools/inflector"
"github.com/pocketbase/pocketbase/tools/list"
"github.com/pocketbase/pocketbase/tools/search"
"github.com/pocketbase/pocketbase/tools/security"
"github.com/pocketbase/pocketbase/tools/types"
"github.com/spf13/cast"
@@ -218,6 +221,92 @@ func (dao *Dao) FindFirstRecordByData(
return record, nil
}
// FindRecordsByFilter returns limit number of records matching the
// provided string filter.
//
// The sort argument is optional and can be empty string OR the same format
// used in the web APIs, eg. "-created,title".
//
// If the limit argument is <= 0, no limit is applied to the query and
// all matching records are returned.
//
// Example:
//
// dao.FindRecordsByFilter("posts", "title ~ 'lorem ipsum' && visible = true", "-created", 10)
func (dao *Dao) FindRecordsByFilter(
collectionNameOrId string,
filter string,
sort string,
limit int,
) ([]*models.Record, error) {
collection, err := dao.FindCollectionByNameOrId(collectionNameOrId)
if err != nil {
return nil, err
}
q := dao.RecordQuery(collection)
// build a fields resolver and attach the generated conditions to the query
// ---
resolver := resolvers.NewRecordFieldResolver(
dao,
collection, // the base collection
nil, // no request data
true, // allow searching hidden/protected fields like "email"
)
expr, err := search.FilterData(filter).BuildExpr(resolver)
if err != nil || expr == nil {
return nil, errors.New("invalid or empty filter expression")
}
q.AndWhere(expr)
if sort != "" {
for _, sortField := range search.ParseSortFromString(sort) {
expr, err := sortField.BuildExpr(resolver)
if err != nil {
return nil, err
}
if expr != "" {
q.AndOrderBy(expr)
}
}
}
resolver.UpdateQuery(q) // attaches any adhoc joins and aliases
// ---
if limit > 0 {
q.Limit(int64(limit))
}
records := []*models.Record{}
if err := q.All(&records); err != nil {
return nil, err
}
return records, nil
}
// FindFirstRecordByFilter returns the first available record matching the provided filter.
//
// Example:
//
// dao.FindFirstRecordByFilter("posts", "slug='test'")
func (dao *Dao) FindFirstRecordByFilter(collectionNameOrId string, filter string) (*models.Record, error) {
result, err := dao.FindRecordsByFilter(collectionNameOrId, filter, "", 1)
if err != nil {
return nil, err
}
if len(result) == 0 {
return nil, sql.ErrNoRows
}
return result[0], nil
}
// IsRecordValueUnique checks if the provided key-value pair is a unique Record value.
//
// For correctness, if the collection is "auth" and the key is "username",
+177
View File
@@ -405,6 +405,183 @@ func TestFindFirstRecordByData(t *testing.T) {
}
}
func TestFindRecordsByFilter(t *testing.T) {
app, _ := tests.NewTestApp()
defer app.Cleanup()
scenarios := []struct {
name string
collectionIdOrName string
filter string
sort string
limit int
expectError bool
expectRecordIds []string
}{
{
"missing collection",
"missing",
"id != ''",
"",
0,
true,
nil,
},
{
"missing filter",
"demo2",
"",
"",
0,
true,
nil,
},
{
"invalid filter",
"demo2",
"someMissingField > 1",
"",
0,
true,
nil,
},
{
"simple filter",
"demo2",
"id != ''",
"",
0,
false,
[]string{
"llvuca81nly1qls",
"achvryl401bhse3",
"0yxhwia2amd8gec",
},
},
{
"multi-condition filter with sort",
"demo2",
"id != '' && active=true",
"-created,title",
-1, // should behave the same as 0
false,
[]string{
"0yxhwia2amd8gec",
"achvryl401bhse3",
},
},
{
"with limit",
"demo2",
"id != ''",
"title",
2,
false,
[]string{
"llvuca81nly1qls",
"achvryl401bhse3",
},
},
}
for _, s := range scenarios {
records, err := app.Dao().FindRecordsByFilter(
s.collectionIdOrName,
s.filter,
s.sort,
s.limit,
)
hasErr := err != nil
if hasErr != s.expectError {
t.Errorf("[%s] Expected hasErr to be %v, got %v (%v)", s.name, s.expectError, hasErr, err)
continue
}
if hasErr {
continue
}
if len(records) != len(s.expectRecordIds) {
t.Errorf("[%s] Expected %d records, got %d", s.name, len(s.expectRecordIds), len(records))
continue
}
for i, id := range s.expectRecordIds {
if id != records[i].Id {
t.Errorf("[%s] Expected record with id %q, got %q at index %d", s.name, id, records[i].Id, i)
}
}
}
}
func TestFindFirstRecordByFilter(t *testing.T) {
app, _ := tests.NewTestApp()
defer app.Cleanup()
scenarios := []struct {
name string
collectionIdOrName string
filter string
expectError bool
expectRecordId string
}{
{
"missing collection",
"missing",
"id != ''",
true,
"",
},
{
"missing filter",
"demo2",
"",
true,
"",
},
{
"invalid filter",
"demo2",
"someMissingField > 1",
true,
"",
},
{
"valid filter but no matches",
"demo2",
"id = 'test'",
true,
"",
},
{
"valid filter and multiple matches",
"demo2",
"id != ''",
false,
"llvuca81nly1qls",
},
}
for _, s := range scenarios {
record, err := app.Dao().FindFirstRecordByFilter(s.collectionIdOrName, s.filter)
hasErr := err != nil
if hasErr != s.expectError {
t.Errorf("[%s] Expected hasErr to be %v, got %v (%v)", s.name, s.expectError, hasErr, err)
continue
}
if hasErr {
continue
}
if record.Id != s.expectRecordId {
t.Errorf("[%s] Expected record with id %q, got %q", s.name, s.expectRecordId, record.Id)
}
}
}
func TestIsRecordValueUnique(t *testing.T) {
app, _ := tests.NewTestApp()
defer app.Cleanup()