From 00f04dd498d145a84c48d535634e8b860e77dc11 Mon Sep 17 00:00:00 2001 From: Mitja Zivkovic Date: Wed, 26 Dec 2018 10:05:14 +0100 Subject: [PATCH] Filtering records by exact value of a single field --- api/crm/spec.json | 4 +- api/crm/spec/module.json | 4 +- crm/repository/record.go | 107 ++++++++++++++++++++++++------------- crm/rest/module.go | 2 +- crm/rest/request/module.go | 6 +-- crm/service/record.go | 6 +-- crm/service/record_test.go | 14 ++--- docs/crm/README.md | 2 +- 8 files changed, 90 insertions(+), 55 deletions(-) diff --git a/api/crm/spec.json b/api/crm/spec.json index 7f4cfe2ea..118170a05 100644 --- a/api/crm/spec.json +++ b/api/crm/spec.json @@ -676,10 +676,10 @@ ], "get": [ { + "name": "filter", "type": "string", - "name": "query", "required": false, - "title": "Search query" + "title": "Filtering condition" }, { "name": "page", diff --git a/api/crm/spec/module.json b/api/crm/spec/module.json index 139a43e76..aaa5063b1 100644 --- a/api/crm/spec/module.json +++ b/api/crm/spec/module.json @@ -160,9 +160,9 @@ "Parameters": { "get": [ { - "name": "query", + "name": "filter", "required": false, - "title": "Search query", + "title": "Filtering condition", "type": "string" }, { diff --git a/crm/repository/record.go b/crm/repository/record.go index ea08087db..7071eb2fc 100644 --- a/crm/repository/record.go +++ b/crm/repository/record.go @@ -3,12 +3,13 @@ package repository import ( "context" "encoding/json" - "fmt" "strings" "time" + "github.com/crusttech/crust/crm/repository/ql" "github.com/pkg/errors" "github.com/titpetric/factory" + "gopkg.in/Masterminds/squirrel.v1" "github.com/crusttech/crust/crm/types" ) @@ -20,7 +21,7 @@ type ( FindByID(id uint64) (*types.Record, error) Report(moduleID uint64, metrics, dimensions, filter string) (results interface{}, err error) - Find(moduleID uint64, query string, page int, perPage int, sort string) (*FindResponse, error) + Find(moduleID uint64, filter string, sort string, page int, perPage int) (*FindResponse, error) Create(mod *types.Record) (*types.Record, error) Update(mod *types.Record) (*types.Record, error) @@ -30,7 +31,7 @@ type ( } FindResponseMeta struct { - Query string `json:"query,omitempty"` + Filter string `json:"filter,omitempty"` Page int `json:"page"` PerPage int `json:"perPage"` Count int `json:"count"` @@ -101,7 +102,7 @@ func (r *record) Report(moduleID uint64, metrics, dimensions, filter string) (re } } -func (r *record) Find(moduleID uint64, query string, page int, perPage int, sort string) (*FindResponse, error) { +func (r *record) Find(moduleID uint64, filter string, sort string, page int, perPage int) (*FindResponse, error) { if page < 0 { page = 0 } @@ -116,22 +117,58 @@ func (r *record) Find(moduleID uint64, query string, page int, perPage int, sort } response := &FindResponse{ Meta: FindResponseMeta{ + Filter: filter, Page: page, PerPage: perPage, - Query: query, Sort: sort, }, Records: make([]*types.Record, 0), } - query = "%" + query + "%" + // Create query for fetching and counting records. + query := squirrel. + Select(). + From("crm_record"). + Where("module_id = ?", moduleID). + Where(squirrel.Eq{"deleted_at": nil}) - sqlSelect := "SELECT * FROM crm_record" - sqlCount := "SELECT count(*) FROM crm_record" - sqlWhere := "WHERE module_id=? and deleted_at IS NULL" - sqlOrder := "ORDER BY id DESC" - sqlLimit := fmt.Sprintf("LIMIT %d, %d", page*perPage, perPage) + // Parse filters. + p := ql.NewParser() + p.OnIdent = ql.MakeIdentWrapHandler(jsonWrap, "created_at", "updated_at", "id", "user_id") + where, err := p.ParseExpression(filter) + if err != nil { + return nil, err + } + + // Append filtering to query. + query = query.Where(where) + + // Create count SQL sentences. + count := query.Column(squirrel.Alias(squirrel.Expr("COUNT(*)"), "count")) + + sqlSelect, argsSelect, err := count.ToSql() + if err != nil { + return nil, err + } + + // Execute count query. + if err := r.db().Get(&response.Meta.Count, sqlSelect, argsSelect...); err != nil { + return nil, err + } + + // Return empty response if count of records is zero. + if response.Meta.Count == 0 { + return response, nil + } + + // Create query for fetching records. + query = query. + Column("*"). + Limit(uint64(perPage)). + Offset(uint64(page)) + + // Append Sorting. chuncks := strings.Split(sort, ",") if len(chuncks) > 0 { @@ -195,37 +232,35 @@ func (r *record) Find(moduleID uint64, query string, page int, perPage int, sort orderFields = append(orderFields, field+" "+order) } - sqlOrder = "ORDER BY " + strings.Join(orderFields, ", ") + query = query.OrderBy(orderFields...) } - // One possibility to order by field value without JSON, is query written bellow with FIELD over column names and order by value: - // SELECT * FROM crm_record - // LEFT JOIN crm_record ON crm_record.id = crm_record_column.record_id" - // WHERE column_name in ('name', 'email') - // ORDER BY FIELD(column_name, 'email', 'name'), column_value; + /* + p = ql.NewParser() + p.OnIdent = ql.MakeIdentWrapHandler(jsonWrap, "id", "module_id", "user_id", "created_at", "updated_at") - // Possibility to order with JSON: - // SELECT *, - // JSON_UNQUOTE(JSON_EXTRACT(json, REPLACE(JSON_UNQUOTE(JSON_SEARCH(json, 'all', 'email')), '.name', '.value'))) as emailField - // FROM crm_record - // ORDER by emailField asc; + order, err := p.ParseExpression(sort) + if err != nil { + return nil, err + } - switch true { - case query != "": - sqlWhere = sqlWhere + " AND id in (select distinct record_id from crm_record_column where column_value like ?)" - if err := r.db().Get(&response.Meta.Count, sqlCount+" "+sqlWhere, moduleID, query); err != nil { - return nil, err - } - if err := r.db().Select(&response.Records, sqlSelect+" "+sqlWhere+" "+sqlOrder+" "+sqlLimit, moduleID, query); err != nil { - return nil, err - } - default: - if err := r.db().Get(&response.Meta.Count, sqlCount+" "+sqlWhere, moduleID); err != nil { - return nil, err - } - if err := r.db().Select(&response.Records, sqlSelect+" "+sqlWhere+" "+sqlOrder+" "+sqlLimit, moduleID); err != nil { + sqlOrder, argsOrder, err := order.ToSql() + if err != nil { return nil, err } + query = query.OrderBy(sqlOrder) + */ + + // Create actual fetch SQL sentences. + sqlSelect, argsSelect, err = query.ToSql() + if err != nil { + return nil, err + } + + // Append order args to select args and execute actual query. + // argsSelect = append(argsSelect, argsOrder...) + if err := r.db().Select(&response.Records, sqlSelect, argsSelect...); err != nil { + return nil, err } return response, nil diff --git a/crm/rest/module.go b/crm/rest/module.go index c5e5bf6cf..2fdfbb0e8 100644 --- a/crm/rest/module.go +++ b/crm/rest/module.go @@ -58,7 +58,7 @@ func (s *Module) RecordReport(ctx context.Context, r *request.ModuleRecordReport } func (s *Module) RecordList(ctx context.Context, r *request.ModuleRecordList) (interface{}, error) { - return s.content.With(ctx).Find(r.ModuleID, r.Query, r.Page, r.PerPage, r.Sort) + return s.content.With(ctx).Find(r.ModuleID, r.Filter, r.Page, r.PerPage, r.Sort) } func (s *Module) RecordRead(ctx context.Context, r *request.ModuleRecordRead) (interface{}, error) { diff --git a/crm/rest/request/module.go b/crm/rest/request/module.go index aef5844cc..d98b27937 100644 --- a/crm/rest/request/module.go +++ b/crm/rest/request/module.go @@ -320,7 +320,7 @@ var _ RequestFiller = NewModuleRecordReport() // Module record/list request parameters type ModuleRecordList struct { - Query string + Filter string Page int PerPage int Sort string @@ -358,9 +358,9 @@ func (m *ModuleRecordList) Fill(r *http.Request) (err error) { post[name] = string(param[0]) } - if val, ok := get["query"]; ok { + if val, ok := get["filter"]; ok { - m.Query = val + m.Filter = val } if val, ok := get["page"]; ok { diff --git a/crm/service/record.go b/crm/service/record.go index 0a49463c9..27659f4f9 100644 --- a/crm/service/record.go +++ b/crm/service/record.go @@ -29,7 +29,7 @@ type ( FindByID(recordID uint64) (*types.Record, error) Report(moduleID uint64, metrics, dimensions, filter string) (interface{}, error) - Find(moduleID uint64, query string, page int, perPage int, sort string) (*repository.FindResponse, error) + Find(moduleID uint64, filter string, sort string, page int, perPage int) (*repository.FindResponse, error) Create(record *types.Record) (*types.Record, error) Update(record *types.Record) (*types.Record, error) @@ -68,8 +68,8 @@ func (s *record) Report(moduleID uint64, metrics, dimensions, filter string) (in return s.repository.Report(moduleID, metrics, dimensions, filter) } -func (s *record) Find(moduleID uint64, query string, page int, perPage int, sort string) (*repository.FindResponse, error) { - response, err := s.repository.Find(moduleID, query, page, perPage, sort) +func (s *record) Find(moduleID uint64, filter string, sort string, page int, perPage int) (*repository.FindResponse, error) { + response, err := s.repository.Find(moduleID, filter, sort, page, perPage) if err != nil { return nil, err } diff --git a/crm/service/record_test.go b/crm/service/record_test.go index b443df44a..8ceade5a4 100644 --- a/crm/service/record_test.go +++ b/crm/service/record_test.go @@ -182,7 +182,7 @@ func TestRecord(t *testing.T) { // fetch all records { - mr, err := repository.Find(module.ID, "", 0, 20, "id desc") + mr, err := repository.Find(module.ID, "", "id desc", 0, 20) assert(t, err == nil, "Error when retrieving records: %+v", err) assert(t, len(mr.Records) == 2, "Expected two record, got %d", len(mr.Records)) assert(t, mr.Meta.Count == 2, "Expected Meta.Count == 2, got %d", mr.Meta.Count) @@ -193,7 +193,7 @@ func TestRecord(t *testing.T) { // fetch all records { - mr, err := repository.Find(module.ID, "", 0, 20, "name asc, email desc") + mr, err := repository.Find(module.ID, "", "name asc, email desc", 0, 20) assert(t, err == nil, "Error when retrieving records: %+v", err) assert(t, len(mr.Records) == 2, "Expected two record, got %d", len(mr.Records)) assert(t, mr.Meta.Count == 2, "Expected Meta.Count == 2, got %d", mr.Meta.Count) @@ -204,7 +204,7 @@ func TestRecord(t *testing.T) { // fetch all records { - mr, err := repository.Find(module.ID, "", 0, 20, "created_at desc") + mr, err := repository.Find(module.ID, "", "created_at desc", 0, 20) assert(t, err == nil, "Error when retrieving records: %+v", err) assert(t, len(mr.Records) == 2, "Expected two record, got %d", len(mr.Records)) assert(t, mr.Meta.Count == 2, "Expected Meta.Count == 2, got %d", mr.Meta.Count) @@ -215,19 +215,19 @@ func TestRecord(t *testing.T) { // fetch all records by query { - mr, err := repository.Find(module.ID, "petric", 0, 20, "id desc") + mr, err := repository.Find(module.ID, "name='Tit Petric'", "id desc", 0, 20) assert(t, err == nil, "Error when retrieving records: %+v", err) assert(t, len(mr.Records) == 1, "Expected one record, got %d", len(mr.Records)) assert(t, mr.Meta.Count == 1, "Expected Meta.Count == 1, got %d", mr.Meta.Count) assert(t, mr.Meta.Page == 0, "Expected Meta.Page == 0, got %d", mr.Meta.Page) assert(t, mr.Meta.PerPage == 20, "Expected Meta.PerPage == 20, got %d", mr.Meta.PerPage) - assert(t, mr.Meta.Query == "petric", "Expected Meta.Query == petric, got '%s'", mr.Meta.Query) + assert(t, mr.Meta.Filter == "name='Tit Petric'", "Expected Meta.Filter == name='Tit Petric', got '%s'", mr.Meta.Filter) assert(t, mr.Meta.Sort == "id desc", "Expected Meta.Sort == id desc, got '%s'", mr.Meta.Sort) } // fetch all records by query { - mr, err := repository.Find(module.ID, "niall", 0, 20, "id asc") + mr, err := repository.Find(module.ID, "niall", "id asc", 0, 20) assert(t, err == nil, "Error when retrieving records: %+v", err) assert(t, len(mr.Records) == 0, "Expected no records, got %d", len(mr.Records)) } @@ -243,7 +243,7 @@ func TestRecord(t *testing.T) { // fetch all records { - mr, err := repository.Find(module.ID, "", 0, 20, "") + mr, err := repository.Find(module.ID, "", "", 0, 20) assert(t, err == nil, "Error when retrieving records: %+v", err) assert(t, len(mr.Records) == 0, "Expected no record, got %d", len(mr.Records)) } diff --git a/docs/crm/README.md b/docs/crm/README.md index eb43b1bf2..4b81608b5 100644 --- a/docs/crm/README.md +++ b/docs/crm/README.md @@ -283,7 +283,7 @@ CRM module definitions | Parameter | Type | Method | Description | Default | Required? | | --------- | ---- | ------ | ----------- | ------- | --------- | -| query | string | GET | Search query | N/A | NO | +| filter | string | GET | Filtering condition | N/A | NO | | page | int | GET | Page number (0 based) | N/A | NO | | perPage | int | GET | Returned items per page (default 50) | N/A | NO | | sort | string | GET | Sort field (default id desc) | N/A | NO |