2019-10-11 10:36:00 +00:00
|
|
|
package storage
|
|
|
|
|
|
|
|
import (
|
2019-12-07 15:53:49 +00:00
|
|
|
"context"
|
2020-05-03 12:04:08 +00:00
|
|
|
"database/sql"
|
2019-12-07 15:53:49 +00:00
|
|
|
"fmt"
|
|
|
|
"net/url"
|
2020-05-03 12:04:08 +00:00
|
|
|
"path/filepath"
|
2019-10-11 10:36:00 +00:00
|
|
|
|
2020-05-03 12:04:08 +00:00
|
|
|
_ "github.com/lib/pq"
|
2020-01-19 12:30:47 +00:00
|
|
|
"github.com/volker-raschek/flucky/pkg/types"
|
2020-05-03 12:04:08 +00:00
|
|
|
"github.com/volker-raschek/go-logger/pkg/logger"
|
2019-10-11 10:36:00 +00:00
|
|
|
)
|
|
|
|
|
2020-05-03 12:04:08 +00:00
|
|
|
// Storage is a general interface for a storage endpoint
|
|
|
|
type Storage interface {
|
|
|
|
InsertDevice(ctx context.Context, device *types.Device) error
|
|
|
|
InsertMeasuredValues(ctx context.Context, measuredValues []*types.MeasuredValue) error
|
|
|
|
InsertSensor(ctx context.Context, sensor *types.Sensor) error
|
|
|
|
SelectDevice(ctx context.Context, id string) (*types.Device, error)
|
|
|
|
SelectSensor(ctx context.Context, id string) (*types.Sensor, error)
|
|
|
|
}
|
2019-10-11 10:36:00 +00:00
|
|
|
|
2020-05-03 12:04:08 +00:00
|
|
|
var (
|
|
|
|
postgresAssetPath = "pkg/storage/postgres"
|
|
|
|
)
|
2019-10-11 10:36:00 +00:00
|
|
|
|
2020-05-03 12:04:08 +00:00
|
|
|
// Postgres implementation
|
|
|
|
type Postgres struct {
|
|
|
|
dbo *sql.DB
|
|
|
|
flogger logger.Logger
|
|
|
|
}
|
|
|
|
|
|
|
|
// InsertDevice into the database
|
|
|
|
func (postgres *Postgres) InsertDevice(ctx context.Context, device *types.Device) error {
|
|
|
|
asset := filepath.Join(postgresAssetPath, "insertDevice.sql")
|
|
|
|
queryBytes, err := Asset(asset)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("Failed to load asset %v: %v", asset, err)
|
|
|
|
}
|
|
|
|
query := string(queryBytes)
|
|
|
|
|
|
|
|
tx, err := postgres.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: false})
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("Failed to begin new transaction: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
stmt, err := tx.Prepare(query)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("Failed to prepare statement: %v", err)
|
|
|
|
}
|
|
|
|
defer stmt.Close()
|
|
|
|
|
|
|
|
_, err = stmt.Exec(&device.ID, &device.Name, &device.Location, &device.CreationDate)
|
|
|
|
if err != nil {
|
|
|
|
tx.Rollback()
|
|
|
|
return fmt.Errorf("Failed to execute statement: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return tx.Commit()
|
|
|
|
}
|
|
|
|
|
|
|
|
// InsertMeasuredValues into the database
|
|
|
|
func (postgres *Postgres) InsertMeasuredValues(ctx context.Context, measuredValues []*types.MeasuredValue) error {
|
|
|
|
splittedMeasuredValues := make(map[string][]*types.MeasuredValue, 0)
|
2019-10-11 10:36:00 +00:00
|
|
|
|
|
|
|
for _, measuredValue := range measuredValues {
|
2020-05-03 12:04:08 +00:00
|
|
|
if _, ok := splittedMeasuredValues[measuredValue.ValueType]; !ok {
|
|
|
|
splittedMeasuredValues[measuredValue.ValueType] = make([]*types.MeasuredValue, 0)
|
|
|
|
}
|
|
|
|
splittedMeasuredValues[measuredValue.ValueType] = append(splittedMeasuredValues[measuredValue.ValueType], measuredValue)
|
|
|
|
}
|
|
|
|
|
|
|
|
tx, err := postgres.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: false})
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("Failed to begin new transaction: %v", err)
|
|
|
|
}
|
2019-12-07 17:57:41 +00:00
|
|
|
|
2020-05-03 12:04:08 +00:00
|
|
|
// General insert function
|
|
|
|
insert := func(tx *sql.Tx, asset string, measuredValues []*types.MeasuredValue) error {
|
|
|
|
queryBytes, err := Asset(asset)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("Failed to load asset %v: %v", asset, err)
|
2019-10-11 10:36:00 +00:00
|
|
|
}
|
2020-05-03 12:04:08 +00:00
|
|
|
query := string(queryBytes)
|
2019-10-11 10:36:00 +00:00
|
|
|
|
2020-05-03 12:04:08 +00:00
|
|
|
stmt, err := tx.Prepare(query)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("Failed to prepare statement: %v", err)
|
2019-10-11 10:36:00 +00:00
|
|
|
}
|
2020-05-03 12:04:08 +00:00
|
|
|
defer stmt.Close()
|
2019-10-11 10:36:00 +00:00
|
|
|
|
2020-05-03 12:04:08 +00:00
|
|
|
for _, measuredValue := range measuredValues {
|
|
|
|
_, err := stmt.Exec(&measuredValue.ID, &measuredValue.Value, &measuredValue.FromDate, &measuredValue.TillDate, &measuredValue.SensorID, &measuredValue.CreationDate, &measuredValue.UpdateDate)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("Failed to execute statement: %v", err)
|
|
|
|
}
|
2019-10-11 10:36:00 +00:00
|
|
|
}
|
2020-05-03 12:04:08 +00:00
|
|
|
|
|
|
|
return nil
|
2019-10-11 10:36:00 +00:00
|
|
|
}
|
|
|
|
|
2020-05-03 12:04:08 +00:00
|
|
|
for measuredValueType, measuredValues := range splittedMeasuredValues {
|
|
|
|
var asset string
|
|
|
|
|
|
|
|
switch measuredValueType {
|
|
|
|
case "humidity":
|
|
|
|
asset = filepath.Join(postgresAssetPath, "insertHumidity.sql")
|
|
|
|
case "pressure":
|
|
|
|
asset = filepath.Join(postgresAssetPath, "insertPressure.sql")
|
|
|
|
case "temperature":
|
|
|
|
asset = filepath.Join(postgresAssetPath, "insertTemperature.sql")
|
|
|
|
default:
|
|
|
|
tx.Rollback()
|
|
|
|
return fmt.Errorf("Measured value type %v not supported", measuredValueType)
|
2019-10-11 10:36:00 +00:00
|
|
|
}
|
2020-05-03 12:04:08 +00:00
|
|
|
|
|
|
|
err := insert(tx, asset, measuredValues)
|
|
|
|
if err != nil {
|
|
|
|
tx.Rollback()
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return tx.Commit()
|
|
|
|
}
|
|
|
|
|
|
|
|
// InsertSensor into the database
|
|
|
|
func (postgres *Postgres) InsertSensor(ctx context.Context, sensor *types.Sensor) error {
|
|
|
|
asset := filepath.Join(postgresAssetPath, "insertSensor.sql")
|
|
|
|
queryBytes, err := Asset(asset)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("Failed to load asset %v: %v", asset, err)
|
|
|
|
}
|
|
|
|
query := string(queryBytes)
|
|
|
|
|
|
|
|
tx, err := postgres.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: false})
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("Failed to begin new transaction: %v", err)
|
2019-10-11 10:36:00 +00:00
|
|
|
}
|
|
|
|
|
2020-05-03 12:04:08 +00:00
|
|
|
stmt, err := tx.Prepare(query)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("Failed to prepare statement: %v", err)
|
|
|
|
}
|
|
|
|
defer stmt.Close()
|
2019-10-11 10:36:00 +00:00
|
|
|
|
2020-05-03 12:04:08 +00:00
|
|
|
_, err = stmt.Exec(&sensor.ID, &sensor.Name, &sensor.Location, &sensor.WireID, &sensor.I2CBus, &sensor.I2CAddress, &sensor.GPIONumber, &sensor.Model, &sensor.Enabled, &sensor.DeviceID, &sensor.CreationDate)
|
|
|
|
if err != nil {
|
|
|
|
tx.Rollback()
|
|
|
|
return fmt.Errorf("Failed to execute statement: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return tx.Commit()
|
2019-10-11 10:36:00 +00:00
|
|
|
}
|
|
|
|
|
2020-05-03 12:04:08 +00:00
|
|
|
// SelectDevice from database
|
|
|
|
func (postgres *Postgres) SelectDevice(ctx context.Context, id string) (*types.Device, error) {
|
|
|
|
asset := filepath.Join(postgresAssetPath, "selectDeviceByID.sql")
|
|
|
|
queryBytes, err := Asset(asset)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("Failed to load asset %v: %v", asset, err)
|
|
|
|
}
|
|
|
|
query := string(queryBytes)
|
|
|
|
|
|
|
|
tx, err := postgres.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: true})
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("Failed to begin new transaction: %v", err)
|
2019-12-07 15:53:49 +00:00
|
|
|
}
|
2020-05-03 12:04:08 +00:00
|
|
|
|
|
|
|
stmt, err := tx.Prepare(query)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("Failed to prepare statement: %v", err)
|
|
|
|
}
|
|
|
|
defer stmt.Close()
|
|
|
|
|
|
|
|
row := stmt.QueryRow(id)
|
|
|
|
|
|
|
|
device := new(types.Device)
|
|
|
|
err = row.Scan(&device.ID, &device.Name, &device.Location, &device.CreationDate)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("Failed to scan row: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return device, nil
|
2019-12-07 15:53:49 +00:00
|
|
|
}
|
|
|
|
|
2020-05-03 12:04:08 +00:00
|
|
|
// SelectSensor from database
|
|
|
|
func (postgres *Postgres) SelectSensor(ctx context.Context, id string) (*types.Sensor, error) {
|
|
|
|
asset := filepath.Join(postgresAssetPath, "selectSensorByID.sql")
|
|
|
|
queryBytes, err := Asset(asset)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("Failed to load asset %v: %v", asset, err)
|
|
|
|
}
|
|
|
|
query := string(queryBytes)
|
|
|
|
|
|
|
|
tx, err := postgres.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: true})
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("Failed to begin new transaction: %v", err)
|
2019-10-11 10:36:00 +00:00
|
|
|
}
|
2020-05-03 12:04:08 +00:00
|
|
|
|
|
|
|
stmt, err := tx.Prepare(query)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("Failed to prepare statement: %v", err)
|
|
|
|
}
|
|
|
|
defer stmt.Close()
|
|
|
|
|
|
|
|
row := stmt.QueryRow(id)
|
|
|
|
|
|
|
|
sensor := new(types.Sensor)
|
|
|
|
err = row.Scan(&sensor.ID, &sensor.Name, &sensor.Location, &sensor.WireID, &sensor.I2CBus, &sensor.I2CAddress, &sensor.GPIONumber, &sensor.Model, &sensor.Enabled, &sensor.DeviceID, &sensor.CreationDate)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("Failed to scan row: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return sensor, nil
|
2019-10-11 10:36:00 +00:00
|
|
|
}
|
2019-12-07 15:53:49 +00:00
|
|
|
|
2020-05-03 12:04:08 +00:00
|
|
|
// New returns a new storage provider
|
|
|
|
func New(storageEndpoint string, flogger logger.Logger) (Storage, error) {
|
|
|
|
storageEndpointURL, err := url.Parse(storageEndpoint)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
switch storageEndpointURL.Scheme {
|
2019-12-07 15:53:49 +00:00
|
|
|
case "postgres":
|
2020-05-03 12:04:08 +00:00
|
|
|
newDBO, err := sql.Open(storageEndpointURL.Scheme, storageEndpointURL.String())
|
2019-12-07 15:53:49 +00:00
|
|
|
if err != nil {
|
2020-05-03 12:04:08 +00:00
|
|
|
return nil, err
|
2019-12-07 15:53:49 +00:00
|
|
|
}
|
2020-01-18 13:40:47 +00:00
|
|
|
|
2020-05-03 12:04:08 +00:00
|
|
|
return &Postgres{
|
|
|
|
dbo: newDBO,
|
|
|
|
flogger: flogger,
|
|
|
|
}, nil
|
|
|
|
default:
|
|
|
|
return nil, fmt.Errorf("Unsupported database scheme: %v", storageEndpointURL.Scheme)
|
2019-12-07 15:53:49 +00:00
|
|
|
}
|
|
|
|
}
|