PKGBUILD/pkg/storage/storage.go
Markus Pesch fb8d4dd5eb
fix: new implementation
changes:
- Remove cli
  Some cli commands are not complete tested and are deprecated.

- Daemon
  - Old version has a very bad implementation of how to verify, if the
    device or the sensors are in the database insert. The current
    implementation can be improved but this one is betten then the old
    one.
  - Remove complete the cache store implementation. Use a normal array
    and query the length and capacity to determine how the array cache
    must be cleaned.

- Type
  Remove unused types and functions
2020-05-03 14:09:22 +02:00

241 lines
6.9 KiB
Go

package storage
import (
"context"
"database/sql"
"fmt"
"net/url"
"path/filepath"
_ "github.com/lib/pq"
"github.com/volker-raschek/flucky/pkg/types"
"github.com/volker-raschek/go-logger/pkg/logger"
)
// 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)
}
var (
postgresAssetPath = "pkg/storage/postgres"
)
// 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)
for _, measuredValue := range measuredValues {
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)
}
// 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)
}
query := string(queryBytes)
stmt, err := tx.Prepare(query)
if err != nil {
return fmt.Errorf("Failed to prepare statement: %v", err)
}
defer stmt.Close()
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)
}
}
return nil
}
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)
}
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)
}
stmt, err := tx.Prepare(query)
if err != nil {
return fmt.Errorf("Failed to prepare statement: %v", err)
}
defer stmt.Close()
_, 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()
}
// 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)
}
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
}
// 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)
}
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
}
// 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 {
case "postgres":
newDBO, err := sql.Open(storageEndpointURL.Scheme, storageEndpointURL.String())
if err != nil {
return nil, err
}
return &Postgres{
dbo: newDBO,
flogger: flogger,
}, nil
default:
return nil, fmt.Errorf("Unsupported database scheme: %v", storageEndpointURL.Scheme)
}
}