fix: Ensure accurate Total Count and correct Limit=0 handling

This commit finalizes the complex querying logic:
- **Refactor `GetAll` (sqlite.go):** Reworks the internal logic to ensure the total number of matching records (`totalCount`) is calculated via a separate COUNT query *before* applying LIMIT/OFFSET.
- **Corrected Pagination:** Adds an explicit check in `GetAll` to return an empty `items` list when `limit=0`, while still reporting the correct `totalCount`.
- **API Handler Update:** Consumes the accurate `totalCount` returned by the Store layer and includes it in the final API response under the `totalItems` field.
This commit is contained in:
Hadi Mottale 2025-11-22 10:55:23 +03:30
parent 4704d7802b
commit 07cdb55411
3 changed files with 34 additions and 26 deletions

View File

@ -1,3 +1,4 @@
// upupa_dataist_ir/internal/api/handlers.go
package api package api
import ( import (
@ -108,7 +109,7 @@ func (a *API) GetAllRecordsHandler(w http.ResponseWriter, r *http.Request) {
offset = 0 offset = 0
} }
records, err := a.Records.GetAll(collection.ID, finalFilters, orderBy, limit, offset) records, totalItems, err := a.Records.GetAll(collection.ID, finalFilters, orderBy, limit, offset)
if err != nil { if err != nil {
log.Printf("Error retrieving records: %v", err) log.Printf("Error retrieving records: %v", err)
http.Error(w, "Failed to retrieve records", http.StatusInternalServerError) http.Error(w, "Failed to retrieve records", http.StatusInternalServerError)
@ -120,7 +121,7 @@ func (a *API) GetAllRecordsHandler(w http.ResponseWriter, r *http.Request) {
response := map[string]interface{}{ response := map[string]interface{}{
"items": records, "items": records,
"totalItems": len(records), "totalItems": totalItems,
"limit": limit, "limit": limit,
"offset": offset, "offset": offset,
"orderBy": orderBy, "orderBy": orderBy,

View File

@ -1,4 +1,4 @@
// internal/store/repository.go // internal/store/repository
package store package store
import "upupa_dataist_ir/pkg/models" import "upupa_dataist_ir/pkg/models"
@ -10,9 +10,7 @@ type CollectionRepository interface {
type RecordRepository interface { type RecordRepository interface {
CreateRecord(r models.Record) (models.Record, error) CreateRecord(r models.Record) (models.Record, error)
GetAll(collectionID string, filters map[string]interface{}, orderBy string, limit, offset int) ([]models.Record, int, error)
GetAll(collectionID string, filters map[string]interface{}, orderBy string, limit, offset int) ([]models.Record, error)
GetByID(collectionID, recordID string) (models.Record, error) GetByID(collectionID, recordID string) (models.Record, error)
Update(r models.Record) error Update(r models.Record) error
Delete(collectionID, recordID string) error Delete(collectionID, recordID string) error

View File

@ -1,3 +1,4 @@
// upupa_dataist_ir/internal/store/sqlite.go
package store package store
import ( import (
@ -39,9 +40,9 @@ func (s *SQLiteStore) InitSchema() error {
CREATE TABLE IF NOT EXISTS records ( CREATE TABLE IF NOT EXISTS records (
id TEXT PRIMARY KEY, id TEXT PRIMARY KEY,
collection_id TEXT NOT NULL, collection_id TEXT NOT NULL,
created DATETIME DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')), created DATETIME DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
updated DATETIME DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')), updated DATETIME DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
data TEXT NOT NULL, data TEXT NOT NULL,
FOREIGN KEY (collection_id) REFERENCES collections (id) ON DELETE CASCADE FOREIGN KEY (collection_id) REFERENCES collections (id) ON DELETE CASCADE
); );
@ -84,8 +85,8 @@ func (s *SQLiteStore) CreateRecord(r models.Record) (models.Record, error) {
return r, err return r, err
} }
query := `INSERT INTO records query := `INSERT INTO records
(id, collection_id, data) (id, collection_id, data)
VALUES (?, ?, ?)` VALUES (?, ?, ?)`
_, err = s.db.Exec(query, r.ID, r.CollectionID, jsonData) _, err = s.db.Exec(query, r.ID, r.CollectionID, jsonData)
@ -96,15 +97,14 @@ func (s *SQLiteStore) CreateRecord(r models.Record) (models.Record, error) {
return s.GetByID(r.CollectionID, r.ID) return s.GetByID(r.CollectionID, r.ID)
} }
func (s *SQLiteStore) GetAll(collectionID string, filters map[string]interface{}, orderBy string, limit, offset int) ([]models.Record, error) { func (s *SQLiteStore) GetAll(collectionID string, filters map[string]interface{}, orderBy string, limit, offset int) ([]models.Record, int, error) {
baseQuery := `SELECT id, collection_id, created, updated, data FROM records WHERE collection_id = ?` baseQuery := `SELECT id, collection_id, created, updated, data FROM records WHERE collection_id = ?`
args := []interface{}{collectionID} args := []interface{}{collectionID}
filterClause := ""
if len(filters) > 0 { if len(filters) > 0 {
filterClause := ""
for key, value := range filters { for key, value := range filters {
field, op, err := parseFilterKey(key) field, op, err := parseFilterKey(key)
if err != nil { if err != nil {
@ -117,23 +117,32 @@ func (s *SQLiteStore) GetAll(collectionID string, filters map[string]interface{}
filterClause += fmt.Sprintf(" AND %s %s ?", caster, op) filterClause += fmt.Sprintf(" AND %s %s ?", caster, op)
args = append(args, value) args = append(args, value)
} }
baseQuery += filterClause
} }
countQuery := "SELECT COUNT(*) FROM records WHERE collection_id = ?" + filterClause
var totalCount int
err := s.db.QueryRow(countQuery, args...).Scan(&totalCount)
if err != nil {
return nil, 0, err
}
if limit == 0 {
return []models.Record{}, totalCount, nil
}
baseQuery += filterClause
sortClause := " ORDER BY created DESC" sortClause := " ORDER BY created DESC"
if orderBy != "" { if orderBy != "" {
sortClause = buildOrderClause(orderBy) sortClause = buildOrderClause(orderBy)
} }
baseQuery += sortClause baseQuery += sortClause
if limit > 0 { baseQuery += fmt.Sprintf(" LIMIT %d OFFSET %d", limit, offset)
baseQuery += fmt.Sprintf(" LIMIT %d OFFSET %d", limit, offset)
}
rows, err := s.db.Query(baseQuery, args...) rows, err := s.db.Query(baseQuery, args...)
if err != nil { if err != nil {
return nil, err return nil, 0, err
} }
defer rows.Close() defer rows.Close()
@ -147,12 +156,12 @@ func (s *SQLiteStore) GetAll(collectionID string, filters map[string]interface{}
err := rows.Scan(&r.ID, &r.CollectionID, &r.Created, &r.Updated, &dataStr) err := rows.Scan(&r.ID, &r.CollectionID, &r.Created, &r.Updated, &dataStr)
if err != nil { if err != nil {
return nil, err return nil, 0, err
} }
data, err := unmarshalRecordData(dataStr) data, err := unmarshalRecordData(dataStr)
if err != nil { if err != nil {
return nil, err return nil, 0, err
} }
r.Data = data r.Data = data
@ -160,15 +169,15 @@ func (s *SQLiteStore) GetAll(collectionID string, filters map[string]interface{}
} }
if err := rows.Err(); err != nil { if err := rows.Err(); err != nil {
return nil, err return nil, 0, err
} }
return records, nil return records, totalCount, nil
} }
func (s *SQLiteStore) GetByID(collectionID, recordID string) (models.Record, error) { func (s *SQLiteStore) GetByID(collectionID, recordID string) (models.Record, error) {
query := `SELECT id, collection_id, created, updated, data query := `SELECT id, collection_id, created, updated, data
FROM records FROM records
WHERE collection_id = ? AND id = ?` WHERE collection_id = ? AND id = ?`
row := s.db.QueryRow(query, collectionID, recordID) row := s.db.QueryRow(query, collectionID, recordID)