Skip to content
This repository was archived by the owner on Sep 2, 2024. It is now read-only.

Commit a1a7162

Browse files
authored
Merge pull request #68 from VladPetriv/feat/add-count-function
feat(stores/Count): implement count function
2 parents 1b113ca + e72fd8b commit a1a7162

File tree

9 files changed

+265
-0
lines changed

9 files changed

+265
-0
lines changed

database/memory/count.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package memory
2+
3+
import (
4+
"github.com/staticbackendhq/core/model"
5+
)
6+
7+
func (m *Memory) Count(auth model.Auth, dbName, col string, filter map[string]interface{}) (int64, error) {
8+
list, err := all[map[string]any](m, dbName, col)
9+
if err != nil {
10+
return -1, err
11+
}
12+
13+
list = secureRead(auth, col, list)
14+
15+
filtered := filterByClauses(list, filter)
16+
17+
return int64(len(filtered)), nil
18+
}

database/memory/count_test.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package memory
2+
3+
import (
4+
"testing"
5+
)
6+
7+
func TestCount(t *testing.T) {
8+
task1 := newTask("task_with_filter", false)
9+
_, err := datastore.CreateDocument(adminAuth, confDBName, colName, task1)
10+
if err != nil {
11+
t.Fatal(err)
12+
}
13+
14+
task2 := newTask("task_with_filter", false)
15+
_, err = datastore.CreateDocument(adminAuth, confDBName, colName, task2)
16+
if err != nil {
17+
t.Fatal(err)
18+
}
19+
20+
var clauses [][]interface{}
21+
22+
clauses = append(clauses, []interface{}{"title", "=", "task_with_filter"})
23+
24+
filters, err := datastore.ParseQuery(clauses)
25+
if err != nil {
26+
t.Fatal(err)
27+
}
28+
29+
count, err := datastore.Count(adminAuth, confDBName, colName, filters)
30+
if err != nil {
31+
t.Fatal(err)
32+
}
33+
34+
if count != 2 {
35+
t.Fatalf("expected 2 got %v", count)
36+
}
37+
}
38+
39+
func TestCountWIthNoFilters(t *testing.T) {
40+
task1 := newTask("tasknof", false)
41+
_, err := datastore.CreateDocument(adminAuth, confDBName, colName, task1)
42+
if err != nil {
43+
t.Fatal(err)
44+
}
45+
46+
count, err := datastore.Count(adminAuth, confDBName, colName, nil)
47+
if err != nil {
48+
t.Fatal(err)
49+
}
50+
51+
// Here we expect this count because after running all tests total count of documents will be 16 + 3
52+
if count != 19 {
53+
t.Fatalf("expected 19 got %v", count)
54+
}
55+
}

database/mongo/count.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package mongo
2+
3+
import (
4+
"github.com/staticbackendhq/core/model"
5+
)
6+
7+
func (mg *Mongo) Count(auth model.Auth, dbName, col string, filter map[string]interface{}) (count int64, err error) {
8+
db := mg.Client.Database(dbName)
9+
10+
acctID, userID, err := parseObjectID(auth)
11+
if err != nil {
12+
return
13+
}
14+
15+
secureRead(acctID, userID, auth.Role, col, filter)
16+
17+
count, err = db.Collection(model.CleanCollectionName(col)).CountDocuments(mg.Ctx, filter)
18+
if err != nil {
19+
return -1, err
20+
}
21+
22+
return count, nil
23+
}

database/mongo/count_test.go

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package mongo
2+
3+
import "testing"
4+
5+
func TestCount(t *testing.T) {
6+
task1 := newTask("task_with_filter", false)
7+
_, err := datastore.CreateDocument(adminAuth, confDBName, colName, task1)
8+
if err != nil {
9+
t.Fatal(err)
10+
}
11+
12+
task2 := newTask("task_with_filter", false)
13+
_, err = datastore.CreateDocument(adminAuth, confDBName, colName, task2)
14+
if err != nil {
15+
t.Fatal(err)
16+
}
17+
18+
var clauses [][]interface{}
19+
20+
clauses = append(clauses, []interface{}{"title", "=", "task_with_filter"})
21+
22+
filters, err := datastore.ParseQuery(clauses)
23+
if err != nil {
24+
t.Fatal(err)
25+
}
26+
27+
count, err := datastore.Count(adminAuth, confDBName, colName, filters)
28+
if err != nil {
29+
t.Fatal(err)
30+
}
31+
32+
if count != 2 {
33+
t.Fatalf("expected 2 got %v", count)
34+
}
35+
}
36+
37+
func TestCountWithNoFilters(t *testing.T) {
38+
task1 := newTask("should be in list", false)
39+
_, err := datastore.CreateDocument(adminAuth, confDBName, colName, task1)
40+
if err != nil {
41+
t.Fatal(err)
42+
}
43+
44+
count, err := datastore.Count(adminAuth, confDBName, colName, nil)
45+
if err != nil {
46+
t.Fatal(err)
47+
}
48+
49+
// Here we expect this count because after running all tests total count of documents will be 16 + 3
50+
if count != 19 {
51+
t.Fatalf("expected 19 got %v", count)
52+
}
53+
}

database/persister.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,4 +142,6 @@ type Persister interface {
142142
DeleteFile(dbName, fileID string) error
143143
// ListAllFiles lists all file
144144
ListAllFiles(dbName, accountID string) ([]model.File, error)
145+
// Count returns the numbers of entries in a collection based on optional filters
146+
Count(auth model.Auth, dbName, col string, filters map[string]interface{}) (int64, error)
145147
}

database/postgresql/count.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package postgresql
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/staticbackendhq/core/model"
7+
)
8+
9+
func (pg *PostgreSQL) Count(auth model.Auth, dbName, col string, filters map[string]interface{}) (count int64, err error) {
10+
where := secureRead(auth, col)
11+
where = applyFilter(where, filters)
12+
13+
query := fmt.Sprintf(`
14+
SELECT COUNT(*)
15+
FROM %s.%s
16+
%s;
17+
`, dbName, model.CleanCollectionName(col), where)
18+
19+
err = pg.DB.QueryRow(query, auth.AccountID, auth.UserID).Scan(&count)
20+
if err != nil {
21+
return -1, err
22+
}
23+
24+
return count, nil
25+
}

database/postgresql/count_test.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package postgresql
2+
3+
import (
4+
"testing"
5+
)
6+
7+
func TestCount(t *testing.T) {
8+
task1 := newTask("task_with_filter", false)
9+
_, err := datastore.CreateDocument(adminAuth, confDBName, colName, task1)
10+
if err != nil {
11+
t.Fatal(err)
12+
}
13+
14+
task2 := newTask("task_with_filter", false)
15+
_, err = datastore.CreateDocument(adminAuth, confDBName, colName, task2)
16+
if err != nil {
17+
t.Fatal(err)
18+
}
19+
20+
var clauses [][]interface{}
21+
22+
clauses = append(clauses, []interface{}{"title", "=", "task_with_filter"})
23+
24+
filters, err := datastore.ParseQuery(clauses)
25+
if err != nil {
26+
t.Fatal(err)
27+
}
28+
count, err := datastore.Count(adminAuth, confDBName, colName, filters)
29+
if err != nil {
30+
t.Fatal(err)
31+
}
32+
33+
if count != 2 {
34+
t.Fatalf("expected 2 got %v", count)
35+
}
36+
}
37+
38+
func TestCountWithNoFilter(t *testing.T) {
39+
task1 := newTask("task1", false)
40+
_, err := datastore.CreateDocument(adminAuth, confDBName, colName, task1)
41+
if err != nil {
42+
t.Fatal(err)
43+
}
44+
45+
count, err := datastore.Count(adminAuth, confDBName, colName, nil)
46+
if err != nil {
47+
t.Fatal(err)
48+
}
49+
50+
// Here we expect this count because after running all tests total count of documents will be 16 + 3
51+
if count != 19 {
52+
t.Fatalf("expected 19 got %v", count)
53+
}
54+
}

db.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,40 @@ func (database *Database) list(w http.ResponseWriter, r *http.Request) {
136136
respond(w, http.StatusOK, result)
137137
}
138138

139+
func (database *Database) count(w http.ResponseWriter, r *http.Request) {
140+
var clauses [][]interface{}
141+
142+
if err := json.NewDecoder(r.Body).Decode(&clauses); err != nil {
143+
// Here we don't return an error because filters are optional
144+
database.log.Error().Err(err).Msg("error parsing body")
145+
}
146+
147+
filter, err := backend.DB.ParseQuery(clauses)
148+
if err != nil {
149+
// Here we don't return an error because filters are optional
150+
database.log.Error().Err(err).Msg("error parsing query")
151+
}
152+
153+
conf, auth, err := middleware.Extract(r, true)
154+
if err != nil {
155+
database.log.Error().Err(err).Msg("error extracting conf and auth")
156+
http.Error(w, err.Error(), http.StatusBadRequest)
157+
158+
return
159+
}
160+
161+
col := getURLPart(r.URL.Path, 3)
162+
163+
result, err := backend.DB.Count(auth, conf.Name, col, filter)
164+
if err != nil {
165+
http.Error(w, err.Error(), http.StatusInternalServerError)
166+
167+
return
168+
}
169+
170+
respond(w, http.StatusOK, map[string]int64{"count": result})
171+
}
172+
139173
func (database *Database) get(w http.ResponseWriter, r *http.Request) {
140174
conf, auth, err := middleware.Extract(r, true)
141175
if err != nil {

server.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,7 @@ func Start(c config.AppConfig, log *logger.Logger) {
142142

143143
// database routes
144144
http.Handle("/db/", middleware.Chain(http.HandlerFunc(database.dbreq), stdAuth...))
145+
http.Handle("/db/count/", middleware.Chain(http.HandlerFunc(database.count), stdAuth...))
145146
http.Handle("/query/", middleware.Chain(http.HandlerFunc(database.query), stdAuth...))
146147
http.Handle("/inc/", middleware.Chain(http.HandlerFunc(database.increase), stdAuth...))
147148
http.Handle("/sudoquery/", middleware.Chain(http.HandlerFunc(database.query), stdRoot...))

0 commit comments

Comments
 (0)