2018-08-26 13:15:48 +02:00
|
|
|
//
|
|
|
|
// Copyright (c) 2018
|
|
|
|
// Mainflux
|
|
|
|
//
|
|
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
//
|
|
|
|
|
2018-05-15 17:13:09 +02:00
|
|
|
package postgres
|
|
|
|
|
|
|
|
import (
|
|
|
|
"database/sql"
|
|
|
|
"fmt"
|
|
|
|
|
2018-11-28 15:58:48 +01:00
|
|
|
"github.com/lib/pq" // required for DB access
|
2018-05-15 17:13:09 +02:00
|
|
|
"github.com/mainflux/mainflux/logger"
|
|
|
|
"github.com/mainflux/mainflux/things"
|
|
|
|
)
|
|
|
|
|
|
|
|
var _ things.ThingRepository = (*thingRepository)(nil)
|
|
|
|
|
|
|
|
type thingRepository struct {
|
|
|
|
db *sql.DB
|
|
|
|
log logger.Logger
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewThingRepository instantiates a PostgreSQL implementation of thing
|
|
|
|
// repository.
|
|
|
|
func NewThingRepository(db *sql.DB, log logger.Logger) things.ThingRepository {
|
|
|
|
return &thingRepository{db: db, log: log}
|
|
|
|
}
|
|
|
|
|
2018-12-05 13:09:25 +01:00
|
|
|
func (tr thingRepository) Save(thing things.Thing) (string, error) {
|
|
|
|
q := `INSERT INTO things (id, owner, type, name, key, metadata) VALUES ($1, $2, $3, $4, $5, $6) RETURNING id`
|
2018-05-16 14:28:41 +02:00
|
|
|
|
2018-11-28 15:58:48 +01:00
|
|
|
metadata := thing.Metadata
|
|
|
|
if metadata == "" {
|
|
|
|
metadata = "{}"
|
|
|
|
}
|
|
|
|
|
2018-12-05 13:09:25 +01:00
|
|
|
_, err := tr.db.Exec(q, thing.ID, thing.Owner, thing.Type, thing.Name, thing.Key, metadata)
|
2018-11-28 15:58:48 +01:00
|
|
|
if err != nil {
|
|
|
|
pqErr, ok := err.(*pq.Error)
|
|
|
|
if ok && errInvalid == pqErr.Code.Name() {
|
2018-12-05 13:09:25 +01:00
|
|
|
return "", things.ErrMalformedEntity
|
2018-11-28 15:58:48 +01:00
|
|
|
}
|
|
|
|
|
2018-12-05 13:09:25 +01:00
|
|
|
return "", err
|
2018-05-16 14:28:41 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return thing.ID, nil
|
2018-05-15 17:13:09 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func (tr thingRepository) Update(thing things.Thing) error {
|
2018-08-17 16:20:35 +02:00
|
|
|
q := `UPDATE things SET name = $1, metadata = $2 WHERE owner = $3 AND id = $4;`
|
2018-05-15 17:13:09 +02:00
|
|
|
|
2018-11-28 15:58:48 +01:00
|
|
|
metadata := thing.Metadata
|
|
|
|
if metadata == "" {
|
|
|
|
metadata = "{}"
|
|
|
|
}
|
|
|
|
|
|
|
|
res, err := tr.db.Exec(q, thing.Name, metadata, thing.Owner, thing.ID)
|
2018-05-15 17:13:09 +02:00
|
|
|
if err != nil {
|
2018-11-28 15:58:48 +01:00
|
|
|
pqErr, ok := err.(*pq.Error)
|
|
|
|
if ok && errInvalid == pqErr.Code.Name() {
|
|
|
|
return things.ErrMalformedEntity
|
|
|
|
}
|
|
|
|
|
2018-05-15 17:13:09 +02:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
cnt, err := res.RowsAffected()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if cnt == 0 {
|
|
|
|
return things.ErrNotFound
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2018-12-05 13:09:25 +01:00
|
|
|
func (tr thingRepository) RetrieveByID(owner, id string) (things.Thing, error) {
|
2018-08-17 16:20:35 +02:00
|
|
|
q := `SELECT name, type, key, metadata FROM things WHERE id = $1 AND owner = $2`
|
2018-05-15 17:13:09 +02:00
|
|
|
thing := things.Thing{ID: id, Owner: owner}
|
|
|
|
err := tr.db.
|
|
|
|
QueryRow(q, id, owner).
|
2018-08-17 16:20:35 +02:00
|
|
|
Scan(&thing.Name, &thing.Type, &thing.Key, &thing.Metadata)
|
2018-05-15 17:13:09 +02:00
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
empty := things.Thing{}
|
|
|
|
if err == sql.ErrNoRows {
|
|
|
|
return empty, things.ErrNotFound
|
|
|
|
}
|
|
|
|
return empty, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return thing, nil
|
|
|
|
}
|
|
|
|
|
2018-12-05 13:09:25 +01:00
|
|
|
func (tr thingRepository) RetrieveByKey(key string) (string, error) {
|
2018-05-17 20:17:02 +02:00
|
|
|
q := `SELECT id FROM things WHERE key = $1`
|
2018-12-05 13:09:25 +01:00
|
|
|
var id string
|
2018-05-17 20:17:02 +02:00
|
|
|
if err := tr.db.QueryRow(q, key).Scan(&id); err != nil {
|
|
|
|
if err == sql.ErrNoRows {
|
2018-12-05 13:09:25 +01:00
|
|
|
return "", things.ErrNotFound
|
2018-05-17 20:17:02 +02:00
|
|
|
}
|
2018-12-05 13:09:25 +01:00
|
|
|
return "", err
|
2018-05-17 20:17:02 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return id, nil
|
|
|
|
}
|
|
|
|
|
2019-01-08 11:53:24 +01:00
|
|
|
func (tr thingRepository) RetrieveAll(owner string, offset, limit uint64) things.ThingsPage {
|
2018-08-17 16:20:35 +02:00
|
|
|
q := `SELECT id, name, type, key, metadata FROM things WHERE owner = $1 ORDER BY id LIMIT $2 OFFSET $3`
|
2018-05-15 17:13:09 +02:00
|
|
|
items := []things.Thing{}
|
|
|
|
|
|
|
|
rows, err := tr.db.Query(q, owner, limit, offset)
|
|
|
|
if err != nil {
|
|
|
|
tr.log.Error(fmt.Sprintf("Failed to retrieve things due to %s", err))
|
2019-01-08 11:53:24 +01:00
|
|
|
return things.ThingsPage{}
|
2018-05-15 17:13:09 +02:00
|
|
|
}
|
|
|
|
defer rows.Close()
|
|
|
|
|
|
|
|
for rows.Next() {
|
|
|
|
c := things.Thing{Owner: owner}
|
2018-08-17 16:20:35 +02:00
|
|
|
if err = rows.Scan(&c.ID, &c.Name, &c.Type, &c.Key, &c.Metadata); err != nil {
|
2018-05-15 17:13:09 +02:00
|
|
|
tr.log.Error(fmt.Sprintf("Failed to read retrieved thing due to %s", err))
|
2019-01-08 11:53:24 +01:00
|
|
|
return things.ThingsPage{}
|
2018-05-15 17:13:09 +02:00
|
|
|
}
|
|
|
|
items = append(items, c)
|
|
|
|
}
|
|
|
|
|
2019-01-08 11:53:24 +01:00
|
|
|
q = `SELECT COUNT(*) FROM things WHERE owner = $1`
|
|
|
|
|
|
|
|
var total uint64
|
|
|
|
if err := tr.db.QueryRow(q, owner).Scan(&total); err != nil {
|
|
|
|
tr.log.Error(fmt.Sprintf("Failed to count things due to %s", err))
|
|
|
|
return things.ThingsPage{}
|
|
|
|
}
|
|
|
|
|
|
|
|
page := things.ThingsPage{
|
|
|
|
Things: items,
|
|
|
|
PageMetadata: things.PageMetadata{
|
|
|
|
Total: total,
|
|
|
|
Offset: offset,
|
|
|
|
Limit: limit,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
return page
|
|
|
|
}
|
|
|
|
|
|
|
|
func (tr thingRepository) RetrieveByChannel(owner, channel string, offset, limit uint64) things.ThingsPage {
|
2019-01-15 18:51:06 +01:00
|
|
|
q := `SELECT id, type, name, key, metadata
|
2019-01-08 11:53:24 +01:00
|
|
|
FROM things th
|
|
|
|
INNER JOIN connections co
|
|
|
|
ON th.id = co.thing_id
|
|
|
|
WHERE th.owner = $1 AND co.channel_id = $2
|
|
|
|
ORDER BY th.id
|
|
|
|
LIMIT $3
|
|
|
|
OFFSET $4`
|
|
|
|
items := []things.Thing{}
|
|
|
|
|
|
|
|
rows, err := tr.db.Query(q, owner, channel, limit, offset)
|
|
|
|
if err != nil {
|
|
|
|
tr.log.Error(fmt.Sprintf("Failed to retrieve things due to %s", err))
|
|
|
|
return things.ThingsPage{}
|
|
|
|
}
|
|
|
|
defer rows.Close()
|
|
|
|
|
|
|
|
for rows.Next() {
|
|
|
|
t := things.Thing{Owner: owner}
|
2019-01-15 18:51:06 +01:00
|
|
|
if err := rows.Scan(&t.ID, &t.Type, &t.Name, &t.Key, &t.Metadata); err != nil {
|
2019-01-08 11:53:24 +01:00
|
|
|
tr.log.Error(fmt.Sprintf("Failed to read retrieved thing due to %s", err))
|
|
|
|
return things.ThingsPage{}
|
|
|
|
}
|
|
|
|
items = append(items, t)
|
|
|
|
}
|
|
|
|
|
|
|
|
q = `SELECT COUNT(*)
|
|
|
|
FROM things th
|
|
|
|
INNER JOIN connections co
|
|
|
|
ON th.id = co.thing_id
|
|
|
|
WHERE th.owner = $1 AND co.channel_id = $2`
|
|
|
|
|
|
|
|
var total uint64
|
|
|
|
if err := tr.db.QueryRow(q, owner, channel).Scan(&total); err != nil {
|
|
|
|
tr.log.Error(fmt.Sprintf("Failed to count things due to %s", err))
|
|
|
|
return things.ThingsPage{}
|
|
|
|
}
|
|
|
|
|
|
|
|
return things.ThingsPage{
|
|
|
|
Things: items,
|
|
|
|
PageMetadata: things.PageMetadata{
|
|
|
|
Total: total,
|
|
|
|
Offset: offset,
|
|
|
|
Limit: limit,
|
|
|
|
},
|
|
|
|
}
|
2018-05-15 17:13:09 +02:00
|
|
|
}
|
|
|
|
|
2018-12-05 13:09:25 +01:00
|
|
|
func (tr thingRepository) Remove(owner, id string) error {
|
2018-05-15 17:13:09 +02:00
|
|
|
q := `DELETE FROM things WHERE id = $1 AND owner = $2`
|
|
|
|
tr.db.Exec(q, id, owner)
|
|
|
|
return nil
|
|
|
|
}
|