refac: use embed instead of go-bindata, secure closing of transactions

This commit is contained in:
2021-03-21 18:47:14 +01:00
parent 59db7cfc85
commit 7a88aaac0c
98 changed files with 3147 additions and 2525 deletions

View File

@ -27,8 +27,10 @@ func Start(cnf *config.Config, cachedEntries uint, flogger logger.Logger) error
return err
}
ctx := context.Background()
// Add
repoDevice, err := repo.GetDevice(cnf.DeviceID)
repoDevice, err := repo.GetDeviceByID(ctx, cnf.DeviceID)
switch {
case err != nil:
return err
@ -39,7 +41,7 @@ func Start(cnf *config.Config, cachedEntries uint, flogger logger.Logger) error
return err
}
err = repo.AddDevices(&types.Device{
err = repo.AddDevices(ctx, &types.Device{
ID: cnf.DeviceID,
Name: hostname,
})
@ -47,13 +49,13 @@ func Start(cnf *config.Config, cachedEntries uint, flogger logger.Logger) error
return err
}
repoDevice, err = repo.GetDevice(cnf.DeviceID)
repoDevice, err = repo.GetDeviceByID(ctx, cnf.DeviceID)
if err != nil {
return err
}
}
repoSensors, err := repo.GetSensorsByDeviceID(repoDevice.ID)
repoSensors, err := repo.GetSensorsByDeviceIDs(ctx, repoDevice.ID)
switch {
case err != nil:
return err
@ -110,7 +112,7 @@ func Start(cnf *config.Config, cachedEntries uint, flogger logger.Logger) error
if cap(measuredValues) == len(measuredValues) {
flogger.Debug("Flush cache with %v values", len(measuredValues))
err := repo.AddMeasuredValues(measuredValues...)
err := repo.AddMeasuredValues(ctx, measuredValues...)
if err != nil {
flogger.Error("%v", err)
}
@ -124,7 +126,7 @@ func Start(cnf *config.Config, cachedEntries uint, flogger logger.Logger) error
flogger.Debug("Flush cache with %v remaining values", len(measuredValues))
err := repo.AddMeasuredValues(measuredValues...)
err := repo.AddMeasuredValues(ctx, measuredValues...)
if err != nil {
flogger.Error("%v", err)
}

View File

@ -1,134 +0,0 @@
package db
import (
"context"
"database/sql"
"fmt"
"net/url"
"os"
"path/filepath"
postgresdml "git.cryptic.systems/volker.raschek/flucky/pkg/repository/db/postgres/dml"
sqlite3dml "git.cryptic.systems/volker.raschek/flucky/pkg/repository/db/sqlite3/dml"
"git.cryptic.systems/volker.raschek/flucky/pkg/types"
"git.cryptic.systems/volker.raschek/go-logger"
)
// Database is a general interface for a database backend like postgres, oracle
// or sqlite
type Database interface {
Close() error
DeleteDevices(ctx context.Context, deviceIDs ...string) error
DeleteSensors(ctx context.Context, sensorIDs ...string) error
InsertDevices(ctx context.Context, devices ...*types.Device) error
InsertOrUpdateDevices(ctx context.Context, devices ...*types.Device) error
InsertMeasuredValues(ctx context.Context, measuredValues ...*types.MeasuredValue) error
InsertOrUpdateMeasuredValues(ctx context.Context, measuredValues ...*types.MeasuredValue) error
InsertSensors(ctx context.Context, sensors ...*types.Sensor) error
InsertOrUpdateSensors(ctx context.Context, sensors ...*types.Sensor) error
Migrate(ctx context.Context) error
SelectDevice(ctx context.Context, deviceID string) (*types.Device, error)
SelectDevices(ctx context.Context) ([]*types.Device, error)
SelectHumidity(ctx context.Context, id string) (*types.MeasuredValue, error)
SelectHumidities(ctx context.Context) ([]*types.MeasuredValue, error)
SelectPressure(ctx context.Context, id string) (*types.MeasuredValue, error)
SelectPressures(ctx context.Context) ([]*types.MeasuredValue, error)
SelectSensor(ctx context.Context, sensorID string) (*types.Sensor, error)
SelectSensors(ctx context.Context) ([]*types.Sensor, error)
SelectTemperature(ctx context.Context, id string) (*types.MeasuredValue, error)
SelectTemperatures(ctx context.Context) ([]*types.MeasuredValue, error)
UpdateDevices(ctx context.Context, devices ...*types.Device) error
UpdateSensors(ctx context.Context, sensors ...*types.Sensor) error
}
// New returns a new database backend interface
func New(databaseURL *url.URL, flogger logger.Logger) (Database, error) {
// Check of nil pointer
for _, parameter := range []interface{}{
databaseURL,
flogger,
} {
if parameter == nil {
return nil, fmt.Errorf("Parameter does not be nil")
}
}
var (
database Database
err error
)
switch databaseURL.Scheme {
case "postgres":
// postgres://[user]:[password]@[host]:[port]/[path]?[query]
newDBO, err := sql.Open(databaseURL.Scheme, databaseURL.String())
if err != nil {
return nil, err
}
queries := make(map[string]string, 0)
for _, assetName := range postgresdml.AssetNames() {
body, err := postgresdml.Asset(assetName)
if err != nil {
return nil, fmt.Errorf("Failed to load asset %v, %w", assetName, err)
}
queries[assetName] = string(body)
}
database = &Postgres{
databaseURL: databaseURL,
dbo: newDBO,
flogger: flogger,
queries: queries,
}
case "sqlite3":
// Create directory if not exist
if _, err := os.Stat(filepath.Dir(databaseURL.Path)); os.IsNotExist(err) {
err := os.MkdirAll(filepath.Dir(databaseURL.Path), 0755)
if err != nil {
return nil, err
}
}
// enable foreign keys
values := databaseURL.Query()
values.Set("_foreign_keys", "on")
customRawURL := fmt.Sprintf("file://%v?%v", databaseURL.Path, values.Encode())
sqlDB, err := sql.Open("sqlite3", customRawURL)
if err != nil {
return nil, err
}
queries := make(map[string]string, 0)
for _, assetName := range sqlite3dml.AssetNames() {
body, err := sqlite3dml.Asset(assetName)
if err != nil {
return nil, fmt.Errorf("Failed to load asset %v, %w", assetName, err)
}
queries[assetName] = string(body)
}
database = &SQLite{
databaseURL: databaseURL,
dbo: sqlDB,
flogger: flogger,
queries: queries,
}
default:
return nil, fmt.Errorf("Unsupported database scheme: %v", databaseURL.Scheme)
}
// Initialize database scheme if not exists
err = database.Migrate(context.Background())
if err != nil {
return nil, err
}
return database, nil
}

View File

@ -1,951 +0,0 @@
package db
import (
"context"
"database/sql"
"errors"
"fmt"
"net/url"
"time"
postgresddl "git.cryptic.systems/volker.raschek/flucky/pkg/repository/db/postgres/ddl"
"git.cryptic.systems/volker.raschek/flucky/pkg/types"
"git.cryptic.systems/volker.raschek/go-logger"
"github.com/golang-migrate/migrate/v4"
bindata "github.com/golang-migrate/migrate/v4/source/go_bindata"
)
// Postgres implementation
type Postgres struct {
databaseURL *url.URL
dbo *sql.DB
flogger logger.Logger
queries map[string]string
}
// Close closes the database and prevents new queries from starting. Close then
// waits for all queries that have started processing on the server to finish.
func (postgres *Postgres) Close() error {
return postgres.dbo.Close()
}
// DeleteDevices from the database
func (postgres *Postgres) DeleteDevices(ctx context.Context, deviceIDs ...string) error {
queryFile := "deleteDevice.sql"
query, present := postgres.queries[queryFile]
if !present {
return fmt.Errorf("Postgres-Backend: File %v not found", queryFile)
}
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()
for _, deviceID := range deviceIDs {
_, err = stmt.Exec(deviceID)
if err != nil {
tx.Rollback()
return err
}
}
return tx.Commit()
}
// DeleteSensors from the database
func (postgres *Postgres) DeleteSensors(ctx context.Context, sensorIDs ...string) error {
queryFile := "deleteSensor.sql"
query, present := postgres.queries[queryFile]
if !present {
return fmt.Errorf("Postgres-Backend: File %v not found", queryFile)
}
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()
for _, sensorID := range sensorIDs {
_, err = stmt.Exec(sensorID)
if err != nil {
tx.Rollback()
return err
}
}
return tx.Commit()
}
// InsertDevices into the database
func (postgres *Postgres) InsertDevices(ctx context.Context, devices ...*types.Device) error {
tx, err := postgres.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: false})
if err != nil {
return fmt.Errorf("Failed to begin new transaction: %v", err)
}
err = postgres.insertDevices(tx, devices...)
if err != nil {
return err
}
return tx.Commit()
}
func (postgres *Postgres) insertDevices(tx *sql.Tx, devices ...*types.Device) error {
queryFile := "insertDevice.sql"
query, present := postgres.queries[queryFile]
if !present {
return fmt.Errorf("Postgres-Backend: File %v not found", queryFile)
}
stmt, err := tx.Prepare(query)
if err != nil {
return fmt.Errorf("Failed to prepare statement: %v", err)
}
defer stmt.Close()
for _, device := range devices {
if device.CreationDate.Equal(time.Time{}) {
device.CreationDate = time.Now()
}
_, err = stmt.Exec(
&device.ID,
&device.Name,
&device.Location,
&device.CreationDate,
&device.UpdateDate,
)
if err != nil {
tx.Rollback()
return fmt.Errorf("Failed to execute statement: %v", err)
}
}
return nil
}
// InsertMeasuredValues into the database
func (postgres *Postgres) InsertMeasuredValues(ctx context.Context, measuredValues ...*types.MeasuredValue) error {
splittedMeasuredValues := make(map[types.MeasuredValueType][]*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, queryFile string, measuredValues []*types.MeasuredValue) error {
query, present := postgres.queries[queryFile]
if !present {
return fmt.Errorf("Postgres-Backend: File %v not found", queryFile)
}
stmt, err := tx.Prepare(query)
if err != nil {
return fmt.Errorf("Failed to prepare statement: %v", err)
}
defer stmt.Close()
for _, measuredValue := range measuredValues {
if measuredValue.CreationDate.Equal(time.Time{}) {
measuredValue.CreationDate = time.Now()
}
_, err := stmt.Exec(
&measuredValue.ID,
&measuredValue.Value,
&measuredValue.Date,
&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 queryFile string
switch measuredValueType {
case types.Humidity:
queryFile = "insertHumidity.sql"
case types.Pressure:
queryFile = "insertPressure.sql"
case types.Temperature:
queryFile = "insertTemperature.sql"
default:
tx.Rollback()
return fmt.Errorf("Measured value type %v not supported", measuredValueType)
}
err := insert(tx, queryFile, measuredValues)
if err != nil {
tx.Rollback()
return err
}
}
return tx.Commit()
}
func (postgres *Postgres) InsertOrUpdateDevices(ctx context.Context, devices ...*types.Device) error {
queryFile := "insertOrUpdateDevice.sql"
query, present := postgres.queries[queryFile]
if !present {
return fmt.Errorf("Postgres-Backend: File %v not found", queryFile)
}
tx, err := postgres.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: false})
if err != nil {
return fmt.Errorf("Failed to begin new transaction: %v", err)
}
for _, device := range devices {
if device.CreationDate.Equal(time.Time{}) {
device.CreationDate = time.Now()
}
_, err = tx.Exec(
query,
&device.ID,
&device.Name,
&device.Location,
&device.CreationDate,
&device.UpdateDate,
)
if err != nil {
tx.Rollback()
return fmt.Errorf("Failed to execute statement: %v", err)
}
}
return tx.Commit()
}
// InsertOrUpdateMeasuredValues into the database
func (postgres *Postgres) InsertOrUpdateMeasuredValues(ctx context.Context, measuredValues ...*types.MeasuredValue) error {
splittedMeasuredValues := make(map[types.MeasuredValueType][]*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, queryFile string, measuredValues []*types.MeasuredValue) error {
query, present := postgres.queries[queryFile]
if !present {
return fmt.Errorf("Postgres-Backend: File %v not found", queryFile)
}
stmt, err := tx.Prepare(query)
if err != nil {
return fmt.Errorf("Failed to prepare statement: %v", err)
}
defer stmt.Close()
for _, measuredValue := range measuredValues {
if measuredValue.CreationDate.Equal(time.Time{}) {
measuredValue.CreationDate = time.Now()
}
_, err := stmt.Exec(
&measuredValue.ID,
&measuredValue.Value,
&measuredValue.Date,
&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 queryFile string
switch measuredValueType {
case types.Humidity:
queryFile = "insertOrUpdateHumidity.sql"
case types.Pressure:
queryFile = "insertOrUpdatePressure.sql"
case types.Temperature:
queryFile = "insertOrUpdateTemperature.sql"
default:
tx.Rollback()
return fmt.Errorf("Measured value type %v not supported", measuredValueType)
}
err := insert(tx, queryFile, measuredValues)
if err != nil {
tx.Rollback()
return err
}
}
return tx.Commit()
}
// InsertOrUpdateSensors into the database
func (postgres *Postgres) InsertOrUpdateSensors(ctx context.Context, sensors ...*types.Sensor) error {
queryFile := "insertOrUpdateSensor.sql"
query, present := postgres.queries[queryFile]
if !present {
return fmt.Errorf("Postgres-Backend: File %v not found", queryFile)
}
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()
for _, sensor := range sensors {
if sensor.CreationDate.Equal(time.Time{}) {
sensor.CreationDate = time.Now()
}
_, err = stmt.Exec(
&sensor.ID,
&sensor.Name,
&sensor.Location,
&sensor.WireID,
&sensor.I2CBus,
&sensor.I2CAddress,
&sensor.GPIONumber,
&sensor.Model,
&sensor.Enabled,
&sensor.TickDuration,
&sensor.DeviceID,
&sensor.CreationDate,
&sensor.UpdateDate,
)
if err != nil {
tx.Rollback()
return fmt.Errorf("Failed to execute statement: %v", err)
}
}
return tx.Commit()
}
// InsertSensors into the database
func (postgres *Postgres) InsertSensors(ctx context.Context, sensors ...*types.Sensor) error {
queryFile := "insertSensor.sql"
query, present := postgres.queries[queryFile]
if !present {
return fmt.Errorf("Postgres-Backend: File %v not found", queryFile)
}
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()
for _, sensor := range sensors {
if sensor.CreationDate.Equal(time.Time{}) {
sensor.CreationDate = time.Now()
}
_, err = stmt.Exec(
&sensor.ID,
&sensor.Name,
&sensor.Location,
&sensor.WireID,
&sensor.I2CBus,
&sensor.I2CAddress,
&sensor.GPIONumber,
&sensor.Model,
&sensor.Enabled,
&sensor.TickDuration,
&sensor.DeviceID,
&sensor.CreationDate,
&sensor.UpdateDate,
)
if err != nil {
tx.Rollback()
return fmt.Errorf("Failed to execute statement: %v", err)
}
}
return tx.Commit()
}
// Migrate creates all required tables if not exist
func (postgres *Postgres) Migrate(ctx context.Context) error {
assetSource := bindata.Resource(postgresddl.AssetNames(), func(query string) ([]byte, error) {
return postgresddl.Asset(query)
})
sourceDriver, err := bindata.WithInstance(assetSource)
if err != nil {
return err
}
m, err := migrate.NewWithSourceInstance("bindata", sourceDriver, postgres.databaseURL.String())
if err != nil {
return err
}
err = m.Up()
switch {
case errors.Is(err, migrate.ErrNoChange):
return nil
default:
return err
}
}
// SelectDevice from database
func (postgres *Postgres) SelectDevice(ctx context.Context, id string) (*types.Device, error) {
queryFile := "selectDevice.sql"
query, present := postgres.queries[queryFile]
if !present {
return nil, fmt.Errorf("Postgres-Backend: File %v not found", queryFile)
}
tx, err := postgres.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: true})
if err != nil {
return nil, fmt.Errorf("Failed to begin new transaction: %v", err)
}
devices, err := postgres.selectDevices(tx, query, id)
if err != nil {
return nil, err
}
err = tx.Commit()
if err != nil {
return nil, err
}
if len(devices) == 0 {
return nil, nil
}
return devices[0], nil
}
// SelectDevices from the database
func (postgres *Postgres) SelectDevices(ctx context.Context) ([]*types.Device, error) {
queryFile := "selectDevices.sql"
query, present := postgres.queries[queryFile]
if !present {
return nil, fmt.Errorf("Postgres-Backend: File %v not found", queryFile)
}
tx, err := postgres.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: true})
if err != nil {
return nil, fmt.Errorf("Failed to begin new transaction: %v", err)
}
devices, err := postgres.selectDevices(tx, query)
if err != nil {
return nil, err
}
err = tx.Commit()
if err != nil {
return nil, fmt.Errorf("Failed to commit transaction: %v", err)
}
return devices, nil
}
func (postgres *Postgres) selectDevices(tx *sql.Tx, query string, args ...interface{}) ([]*types.Device, error) {
stmt, err := tx.Prepare(query)
if err != nil {
return nil, fmt.Errorf("Failed to prepare statement: %v", err)
}
defer stmt.Close()
rows, err := stmt.Query(args...)
if err != nil {
return nil, fmt.Errorf("Failed to query statement: %v", err)
}
devices := make([]*types.Device, 0)
for rows.Next() {
device := new(types.Device)
err = rows.Scan(
&device.ID,
&device.Name,
&device.Location,
&device.CreationDate,
&device.UpdateDate,
)
if err != nil {
return nil, fmt.Errorf("Failed to scan row: %v", err)
}
devices = append(devices, device)
}
return devices, nil
}
// SelectHumidity returns humidity from the database
func (postgres *Postgres) SelectHumidity(ctx context.Context, id string) (*types.MeasuredValue, error) {
queryFile := "selectHumidity.sql"
query, present := postgres.queries[queryFile]
if !present {
return nil, fmt.Errorf("Postgres-Backend: File %v not found", queryFile)
}
tx, err := postgres.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: true})
if err != nil {
return nil, err
}
measuredValues, err := postgres.selectMeasuredValue(tx, query, id)
if err != nil {
return nil, err
}
err = tx.Commit()
if err != nil {
return nil, err
}
if measuredValues == nil {
return nil, nil
}
for _, measuredValue := range measuredValues {
measuredValue.ValueType = types.Humidity
}
return measuredValues[0], nil
}
// SelectHumidities returns humidities from the database
func (postgres *Postgres) SelectHumidities(ctx context.Context) ([]*types.MeasuredValue, error) {
queryFile := "selectHumidities.sql"
query, present := postgres.queries[queryFile]
if !present {
return nil, fmt.Errorf("Postgres-Backend: File %v not found", queryFile)
}
tx, err := postgres.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: true})
if err != nil {
return nil, err
}
measuredValues, err := postgres.selectMeasuredValue(tx, query)
if err != nil {
return nil, err
}
err = tx.Commit()
if err != nil {
return nil, err
}
for _, measuredValue := range measuredValues {
measuredValue.ValueType = types.Humidity
}
return measuredValues, nil
}
func (postgres *Postgres) selectMeasuredValue(tx *sql.Tx, query string, args ...interface{}) ([]*types.MeasuredValue, error) {
stmt, err := tx.Prepare(query)
if err != nil {
tx.Rollback()
return nil, err
}
defer stmt.Close()
rows, err := stmt.Query(args...)
if err != nil {
tx.Rollback()
return nil, err
}
measuredValues := make([]*types.MeasuredValue, 0)
for rows.Next() {
measuredValue := new(types.MeasuredValue)
err := rows.Scan(
&measuredValue.ID,
&measuredValue.Value,
&measuredValue.Date,
&measuredValue.SensorID,
&measuredValue.CreationDate,
&measuredValue.UpdateDate,
)
if err != nil {
tx.Rollback()
return nil, err
}
measuredValues = append(measuredValues, measuredValue)
}
return measuredValues, nil
}
// SelectPressure returns pressure from the database
func (postgres *Postgres) SelectPressure(ctx context.Context, id string) (*types.MeasuredValue, error) {
queryFile := "selectPressure.sql"
query, present := postgres.queries[queryFile]
if !present {
return nil, fmt.Errorf("Postgres-Backend: File %v not found", queryFile)
}
tx, err := postgres.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: true})
if err != nil {
return nil, err
}
measuredValues, err := postgres.selectMeasuredValue(tx, query, id)
if err != nil {
return nil, err
}
err = tx.Commit()
if err != nil {
return nil, err
}
if measuredValues == nil {
return nil, nil
}
for _, measuredValue := range measuredValues {
measuredValue.ValueType = types.Pressure
}
return measuredValues[0], nil
}
// SelectPressures returns pressure from the database
func (postgres *Postgres) SelectPressures(ctx context.Context) ([]*types.MeasuredValue, error) {
queryFile := "selectPressures.sql"
query, present := postgres.queries[queryFile]
if !present {
return nil, fmt.Errorf("Postgres-Backend: File %v not found", queryFile)
}
tx, err := postgres.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: true})
if err != nil {
return nil, err
}
measuredValues, err := postgres.selectMeasuredValue(tx, query)
if err != nil {
return nil, err
}
err = tx.Commit()
if err != nil {
return nil, err
}
for _, measuredValue := range measuredValues {
measuredValue.ValueType = types.Pressure
}
return measuredValues, nil
}
// SelectSensor from database
func (postgres *Postgres) SelectSensor(ctx context.Context, id string) (*types.Sensor, error) {
queryFile := "selectSensor.sql"
query, present := postgres.queries[queryFile]
if !present {
return nil, fmt.Errorf("Postgres-Backend: File %v not found", queryFile)
}
tx, err := postgres.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: true})
if err != nil {
return nil, fmt.Errorf("Failed to begin new transaction: %v", err)
}
sensors, err := postgres.selectSensors(tx, query, id)
if err != nil {
return nil, err
}
err = tx.Commit()
if err != nil {
return nil, fmt.Errorf("Failed to commit transaction: %v", err)
}
if len(sensors) == 0 {
return nil, nil
}
return sensors[0], nil
}
// SelectSensors from the database
func (postgres *Postgres) SelectSensors(ctx context.Context) ([]*types.Sensor, error) {
queryFile := "selectSensors.sql"
query, present := postgres.queries[queryFile]
if !present {
return nil, fmt.Errorf("Postgres-Backend: File %v not found", queryFile)
}
tx, err := postgres.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: true})
if err != nil {
return nil, fmt.Errorf("Failed to begin new transaction: %v", err)
}
sensors, err := postgres.selectSensors(tx, query)
if err != nil {
return nil, err
}
err = tx.Commit()
if err != nil {
return nil, fmt.Errorf("Failed to commit transaction: %v", err)
}
return sensors, nil
}
func (postgres *Postgres) selectSensors(tx *sql.Tx, query string, args ...interface{}) ([]*types.Sensor, error) {
stmt, err := tx.Prepare(query)
if err != nil {
return nil, fmt.Errorf("Failed to prepare statement: %v", err)
}
defer stmt.Close()
rows, err := stmt.Query(args...)
if err != nil {
return nil, fmt.Errorf("Failed to query statement: %v", err)
}
sensors := make([]*types.Sensor, 0)
for rows.Next() {
sensor := new(types.Sensor)
err = rows.Scan(
&sensor.ID,
&sensor.Name,
&sensor.Location,
&sensor.WireID,
&sensor.I2CBus,
&sensor.I2CAddress,
&sensor.GPIONumber,
&sensor.Model,
&sensor.Enabled,
&sensor.TickDuration,
&sensor.DeviceID,
&sensor.CreationDate,
&sensor.UpdateDate,
)
if err != nil {
return nil, fmt.Errorf("Failed to scan row: %v", err)
}
sensors = append(sensors, sensor)
}
return sensors, nil
}
// SelectTemperature returns temperatures from the database
func (postgres *Postgres) SelectTemperature(ctx context.Context, id string) (*types.MeasuredValue, error) {
queryFile := "selectTemperature.sql"
query, present := postgres.queries[queryFile]
if !present {
return nil, fmt.Errorf("Postgres-Backend: File %v not found", queryFile)
}
tx, err := postgres.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: true})
if err != nil {
return nil, err
}
measuredValues, err := postgres.selectMeasuredValue(tx, query, id)
if err != nil {
return nil, err
}
err = tx.Commit()
if err != nil {
return nil, err
}
if measuredValues == nil {
return nil, nil
}
for _, measuredValue := range measuredValues {
measuredValue.ValueType = types.Temperature
}
return measuredValues[0], nil
}
// SelectTemperatures returns temperatures from the database
func (postgres *Postgres) SelectTemperatures(ctx context.Context) ([]*types.MeasuredValue, error) {
queryFile := "selectTemperatures.sql"
query, present := postgres.queries[queryFile]
if !present {
return nil, fmt.Errorf("Postgres-Backend: File %v not found", queryFile)
}
tx, err := postgres.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: true})
if err != nil {
return nil, err
}
measuredValues, err := postgres.selectMeasuredValue(tx, query)
if err != nil {
return nil, err
}
err = tx.Commit()
if err != nil {
return nil, err
}
for _, measuredValue := range measuredValues {
measuredValue.ValueType = types.Temperature
}
return measuredValues, nil
}
// UpdateDevices updates a device in the database
func (postgres *Postgres) UpdateDevices(ctx context.Context, devices ...*types.Device) error {
queryFile := "updateDevice.sql"
query, present := postgres.queries[queryFile]
if !present {
return fmt.Errorf("Postgres-Backend: File %v not found", queryFile)
}
tx, err := postgres.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: false})
if err != nil {
return err
}
stmt, err := tx.Prepare(query)
if err != nil {
tx.Rollback()
return err
}
defer stmt.Close()
for _, device := range devices {
now := time.Now()
device.UpdateDate = &now
_, err := stmt.Exec(
&device.Name,
&device.Location,
&device.CreationDate,
&device.UpdateDate,
&device.ID,
)
if err != nil {
tx.Rollback()
return err
}
}
return tx.Commit()
}
// UpdateSensors updates a sensor in the database
func (postgres *Postgres) UpdateSensors(ctx context.Context, sensors ...*types.Sensor) error {
queryFile := "updateSensor.sql"
query, present := postgres.queries[queryFile]
if !present {
return fmt.Errorf("Postgres-Backend: File %v not found", queryFile)
}
tx, err := postgres.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: false})
if err != nil {
return err
}
stmt, err := tx.Prepare(query)
if err != nil {
tx.Rollback()
return err
}
defer stmt.Close()
for _, sensor := range sensors {
now := time.Now()
sensor.UpdateDate = &now
_, err := stmt.Exec(
&sensor.Name,
&sensor.Location,
&sensor.WireID,
&sensor.I2CBus,
&sensor.I2CAddress,
&sensor.GPIONumber,
&sensor.Model,
&sensor.Enabled,
&sensor.TickDuration,
&sensor.DeviceID,
&sensor.CreationDate,
&sensor.UpdateDate,
&sensor.ID,
)
if err != nil {
tx.Rollback()
return err
}
}
return tx.Commit()
}

View File

@ -1,939 +0,0 @@
package db
import (
"context"
"database/sql"
"errors"
"fmt"
"net/url"
sqlite3ddl "git.cryptic.systems/volker.raschek/flucky/pkg/repository/db/sqlite3/ddl"
"git.cryptic.systems/volker.raschek/flucky/pkg/types"
"git.cryptic.systems/volker.raschek/go-logger"
"github.com/golang-migrate/migrate/v4"
bindata "github.com/golang-migrate/migrate/v4/source/go_bindata"
)
// SQLite implementation
type SQLite struct {
databaseURL *url.URL
dbo *sql.DB
flogger logger.Logger
queries map[string]string
}
// Close closes the database and prevents new queries from starting. Close then
// waits for all queries that have started processing on the server to finish.
func (sqlite *SQLite) Close() error {
return sqlite.dbo.Close()
}
// DeleteDevices from the database
func (sqlite *SQLite) DeleteDevices(ctx context.Context, deviceIDs ...string) error {
queryFile := "deleteDevice.sql"
query, present := sqlite.queries[queryFile]
if !present {
return fmt.Errorf("SQLite-Backend: File %v not found", queryFile)
}
tx, err := sqlite.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()
for _, deviceID := range deviceIDs {
_, err = stmt.Exec(deviceID)
if err != nil {
tx.Rollback()
return err
}
}
return tx.Commit()
}
// DeleteSensors from the database
func (sqlite *SQLite) DeleteSensors(ctx context.Context, sensorIDs ...string) error {
queryFile := "deleteSensor.sql"
query, present := sqlite.queries[queryFile]
if !present {
return fmt.Errorf("SQLite-Backend: File %v not found", queryFile)
}
tx, err := sqlite.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()
for _, sensorID := range sensorIDs {
_, err = stmt.Exec(sensorID)
if err != nil {
tx.Rollback()
return err
}
}
return tx.Commit()
}
func (sqlite *SQLite) ExistDevice(ctx context.Context, deviceID string) (bool, error) {
queryFile := "existDevice.sql"
query, present := sqlite.queries[queryFile]
if !present {
return false, fmt.Errorf("SQLite-Backend: File %v not found", queryFile)
}
tx, err := sqlite.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: false})
if err != nil {
return false, fmt.Errorf("Failed to begin new transaction: %v", err)
}
stmt, err := tx.Prepare(query)
if err != nil {
return false, fmt.Errorf("Failed to prepare statement: %v", err)
}
defer stmt.Close()
rows, err := stmt.Query()
if err != nil {
return false, fmt.Errorf("Failed to query statement: %v", err)
}
defer rows.Close()
var t bool
for rows.Next() {
rows.Scan(&t)
}
return t, nil
}
func (sqlite *SQLite) ExistDevices(ctx context.Context, deviceIDs ...string) (map[string]bool, error) {
return nil, nil
}
// InsertDevices into the database
func (sqlite *SQLite) InsertDevices(ctx context.Context, devices ...*types.Device) error {
queryFile := "insertDevice.sql"
query, present := sqlite.queries[queryFile]
if !present {
return fmt.Errorf("SQLite-Backend: File %v not found", queryFile)
}
tx, err := sqlite.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()
for _, device := range devices {
_, err = stmt.Exec(&device.ID, &device.Name, &device.Location, &device.CreationDate, &device.UpdateDate)
if err != nil {
tx.Rollback()
return fmt.Errorf("Failed to execute statement: %v", err)
}
}
return tx.Commit()
}
// InsertMeasuredValues into the database
func (sqlite *SQLite) InsertMeasuredValues(ctx context.Context, measuredValues ...*types.MeasuredValue) error {
splittedMeasuredValues := make(map[types.MeasuredValueType][]*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 := sqlite.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, queryFile string, measuredValues []*types.MeasuredValue) error {
query, present := sqlite.queries[queryFile]
if !present {
return fmt.Errorf("SQLite-Backend: File %v not found", queryFile)
}
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.Date,
&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 queryFile string
switch measuredValueType {
case types.Humidity:
queryFile = "insertHumidity.sql"
case types.Pressure:
queryFile = "insertPressure.sql"
case types.Temperature:
queryFile = "insertTemperature.sql"
default:
tx.Rollback()
return fmt.Errorf("Measured value type %v not supported", measuredValueType)
}
err := insert(tx, queryFile, measuredValues)
if err != nil {
tx.Rollback()
return err
}
}
return tx.Commit()
}
// InsertOrUpdateMeasuredValues into the database
func (sqlite *SQLite) InsertOrUpdateMeasuredValues(ctx context.Context, measuredValues ...*types.MeasuredValue) error {
splittedMeasuredValues := make(map[types.MeasuredValueType][]*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 := sqlite.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, queryFile string, measuredValues []*types.MeasuredValue) error {
query, present := sqlite.queries[queryFile]
if !present {
return fmt.Errorf("SQLite-Backend: File %v not found", queryFile)
}
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.Date,
&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 queryFile string
switch measuredValueType {
case types.Humidity:
queryFile = "insertOrUpdateHumidity.sql"
case types.Pressure:
queryFile = "insertOrUpdatePressure.sql"
case types.Temperature:
queryFile = "insertOrUpdateTemperature.sql"
default:
tx.Rollback()
return fmt.Errorf("Measured value type %v not supported", measuredValueType)
}
err := insert(tx, queryFile, measuredValues)
if err != nil {
tx.Rollback()
return err
}
}
return tx.Commit()
}
// InsertOrUpdateDevices into the database
func (sqlite *SQLite) InsertOrUpdateDevices(ctx context.Context, devices ...*types.Device) error {
queryFile := "insertOrUpdateDevice.sql"
query, present := sqlite.queries[queryFile]
if !present {
return fmt.Errorf("SQLite-Backend: File %v not found", queryFile)
}
tx, err := sqlite.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()
for _, device := range devices {
_, err = stmt.Exec(&device.ID, &device.Name, &device.Location, &device.CreationDate, &device.UpdateDate)
if err != nil {
tx.Rollback()
return fmt.Errorf("Failed to execute statement: %v", err)
}
}
return tx.Commit()
}
// InsertOrUpdateSensors into the database
func (sqlite *SQLite) InsertOrUpdateSensors(ctx context.Context, sensors ...*types.Sensor) error {
queryFile := "insertOrUpdateSensor.sql"
query, present := sqlite.queries[queryFile]
if !present {
return fmt.Errorf("SQLite-Backend: File %v not found", queryFile)
}
tx, err := sqlite.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()
for _, sensor := range sensors {
_, err = stmt.Exec(
&sensor.ID,
&sensor.Name,
&sensor.Location,
&sensor.WireID,
&sensor.I2CBus,
&sensor.I2CAddress,
&sensor.GPIONumber,
&sensor.Model,
&sensor.Enabled,
&sensor.TickDuration,
&sensor.DeviceID,
&sensor.CreationDate,
&sensor.UpdateDate,
)
if err != nil {
tx.Rollback()
return fmt.Errorf("Failed to execute statement: %v", err)
}
}
return tx.Commit()
}
// InsertSensors into the database
func (sqlite *SQLite) InsertSensors(ctx context.Context, sensors ...*types.Sensor) error {
queryFile := "insertSensor.sql"
query, present := sqlite.queries[queryFile]
if !present {
return fmt.Errorf("SQLite-Backend: File %v not found", queryFile)
}
tx, err := sqlite.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()
for _, sensor := range sensors {
_, err = stmt.Exec(
&sensor.ID,
&sensor.Name,
&sensor.Location,
&sensor.WireID,
&sensor.I2CBus,
&sensor.I2CAddress,
&sensor.GPIONumber,
&sensor.Model,
&sensor.Enabled,
&sensor.TickDuration,
&sensor.DeviceID,
&sensor.CreationDate,
&sensor.UpdateDate,
)
if err != nil {
tx.Rollback()
return fmt.Errorf("Failed to execute statement: %v", err)
}
}
return tx.Commit()
}
// Migrate creates all required tables if not exist
func (sqlite *SQLite) Migrate(ctx context.Context) error {
assetSource := bindata.Resource(sqlite3ddl.AssetNames(), func(query string) ([]byte, error) {
return sqlite3ddl.Asset(query)
})
sourceDriver, err := bindata.WithInstance(assetSource)
if err != nil {
return err
}
m, err := migrate.NewWithSourceInstance("bindata", sourceDriver, sqlite.databaseURL.String())
if err != nil {
return err
}
err = m.Up()
switch {
case errors.Is(err, migrate.ErrNoChange):
return nil
default:
return err
}
}
// SelectDevice from database
func (sqlite *SQLite) SelectDevice(ctx context.Context, id string) (*types.Device, error) {
queryFile := "selectDevice.sql"
query, present := sqlite.queries[queryFile]
if !present {
return nil, fmt.Errorf("SQLite-Backend: File %v not found", queryFile)
}
tx, err := sqlite.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: true})
if err != nil {
return nil, fmt.Errorf("Failed to begin new transaction: %v", err)
}
devices, err := sqlite.selectDevices(tx, query, id)
if err != nil {
return nil, err
}
err = tx.Commit()
if err != nil {
return nil, err
}
if len(devices) == 0 {
return nil, nil
}
return devices[0], nil
}
// SelectDevices from the database
func (sqlite *SQLite) SelectDevices(ctx context.Context) ([]*types.Device, error) {
queryFile := "selectDevices.sql"
query, present := sqlite.queries[queryFile]
if !present {
return nil, fmt.Errorf("SQLite-Backend: File %v not found", queryFile)
}
tx, err := sqlite.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: true})
if err != nil {
return nil, fmt.Errorf("Failed to begin new transaction: %v", err)
}
devices, err := sqlite.selectDevices(tx, query)
if err != nil {
return nil, err
}
err = tx.Commit()
if err != nil {
return nil, fmt.Errorf("Failed to commit transaction: %v", err)
}
return devices, nil
}
func (sqlite *SQLite) selectDevices(tx *sql.Tx, query string, args ...interface{}) ([]*types.Device, error) {
stmt, err := tx.Prepare(query)
if err != nil {
return nil, fmt.Errorf("Failed to prepare statement: %v", err)
}
defer stmt.Close()
rows, err := stmt.Query(args...)
if err != nil {
return nil, fmt.Errorf("Failed to query statement: %v", err)
}
defer rows.Close()
devices := make([]*types.Device, 0)
for rows.Next() {
device := new(types.Device)
err = rows.Scan(
&device.ID,
&device.Name,
&device.Location,
&device.CreationDate,
&device.UpdateDate,
)
if err != nil {
return nil, fmt.Errorf("Failed to scan row: %v", err)
}
devices = append(devices, device)
}
return devices, nil
}
// SelectHumidity returns humidity from the database
func (sqlite *SQLite) SelectHumidity(ctx context.Context, id string) (*types.MeasuredValue, error) {
queryFile := "selectHumidity.sql"
query, present := sqlite.queries[queryFile]
if !present {
return nil, fmt.Errorf("SQLite-Backend: File %v not found", queryFile)
}
tx, err := sqlite.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: true})
if err != nil {
return nil, err
}
measuredValues, err := sqlite.selectMeasuredValue(tx, query, id)
if err != nil {
return nil, err
}
err = tx.Commit()
if err != nil {
return nil, err
}
if measuredValues == nil {
return nil, nil
}
for _, measuredValue := range measuredValues {
measuredValue.ValueType = types.Humidity
}
return measuredValues[0], nil
}
// SelectHumidities returns humidities from the database
func (sqlite *SQLite) SelectHumidities(ctx context.Context) ([]*types.MeasuredValue, error) {
queryFile := "selectHumidities.sql"
query, present := sqlite.queries[queryFile]
if !present {
return nil, fmt.Errorf("SQLite-Backend: File %v not found", queryFile)
}
tx, err := sqlite.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: true})
if err != nil {
return nil, err
}
measuredValues, err := sqlite.selectMeasuredValue(tx, query)
if err != nil {
return nil, err
}
err = tx.Commit()
if err != nil {
return nil, err
}
for _, measuredValue := range measuredValues {
measuredValue.ValueType = types.Humidity
}
return measuredValues, nil
}
func (sqlite *SQLite) selectMeasuredValue(tx *sql.Tx, query string, args ...interface{}) ([]*types.MeasuredValue, error) {
stmt, err := tx.Prepare(query)
if err != nil {
tx.Rollback()
return nil, err
}
defer stmt.Close()
rows, err := stmt.Query(args...)
if err != nil {
tx.Rollback()
return nil, err
}
defer rows.Close()
measuredValues := make([]*types.MeasuredValue, 0)
for rows.Next() {
measuredValue := new(types.MeasuredValue)
err := rows.Scan(
&measuredValue.ID,
&measuredValue.Value,
&measuredValue.Date,
&measuredValue.SensorID,
&measuredValue.CreationDate,
&measuredValue.UpdateDate,
)
if err != nil {
tx.Rollback()
return nil, err
}
measuredValues = append(measuredValues, measuredValue)
}
return measuredValues, nil
}
// SelectPressure returns pressure from the database
func (sqlite *SQLite) SelectPressure(ctx context.Context, id string) (*types.MeasuredValue, error) {
queryFile := "selectPressure.sql"
query, present := sqlite.queries[queryFile]
if !present {
return nil, fmt.Errorf("SQLite-Backend: File %v not found", queryFile)
}
tx, err := sqlite.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: true})
if err != nil {
return nil, err
}
measuredValues, err := sqlite.selectMeasuredValue(tx, query, id)
if err != nil {
return nil, err
}
err = tx.Commit()
if err != nil {
return nil, err
}
if measuredValues == nil {
return nil, nil
}
for _, measuredValue := range measuredValues {
measuredValue.ValueType = types.Pressure
}
return measuredValues[0], nil
}
// SelectPressures returns pressure from the database
func (sqlite *SQLite) SelectPressures(ctx context.Context) ([]*types.MeasuredValue, error) {
queryFile := "selectPressures.sql"
query, present := sqlite.queries[queryFile]
if !present {
return nil, fmt.Errorf("SQLite-Backend: File %v not found", queryFile)
}
tx, err := sqlite.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: true})
if err != nil {
return nil, err
}
measuredValues, err := sqlite.selectMeasuredValue(tx, query)
if err != nil {
return nil, err
}
err = tx.Commit()
if err != nil {
return nil, err
}
for _, measuredValue := range measuredValues {
measuredValue.ValueType = types.Pressure
}
return measuredValues, nil
}
// SelectSensor from database
func (sqlite *SQLite) SelectSensor(ctx context.Context, id string) (*types.Sensor, error) {
queryFile := "selectSensor.sql"
query, present := sqlite.queries[queryFile]
if !present {
return nil, fmt.Errorf("SQLite-Backend: File %v not found", queryFile)
}
tx, err := sqlite.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: true})
if err != nil {
return nil, fmt.Errorf("Failed to begin new transaction: %v", err)
}
sensors, err := sqlite.selectSensors(tx, query, id)
if err != nil {
return nil, err
}
err = tx.Commit()
if err != nil {
return nil, fmt.Errorf("Failed to commit transaction: %v", err)
}
if len(sensors) == 0 {
return nil, nil
}
return sensors[0], nil
}
// SelectSensors from the database
func (sqlite *SQLite) SelectSensors(ctx context.Context) ([]*types.Sensor, error) {
queryFile := "selectSensors.sql"
query, present := sqlite.queries[queryFile]
if !present {
return nil, fmt.Errorf("SQLite-Backend: File %v not found", queryFile)
}
tx, err := sqlite.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: true})
if err != nil {
return nil, fmt.Errorf("Failed to begin new transaction: %v", err)
}
sensors, err := sqlite.selectSensors(tx, query)
if err != nil {
return nil, err
}
err = tx.Commit()
if err != nil {
return nil, fmt.Errorf("Failed to commit transaction: %v", err)
}
return sensors, nil
}
func (sqlite *SQLite) selectSensors(tx *sql.Tx, query string, args ...interface{}) ([]*types.Sensor, error) {
stmt, err := tx.Prepare(query)
if err != nil {
return nil, fmt.Errorf("Failed to prepare statement: %v", err)
}
defer stmt.Close()
rows, err := stmt.Query(args...)
if err != nil {
return nil, fmt.Errorf("Failed to query statement: %v", err)
}
defer rows.Close()
sensors := make([]*types.Sensor, 0)
for rows.Next() {
sensor := new(types.Sensor)
err = rows.Scan(
&sensor.ID,
&sensor.Name,
&sensor.Location,
&sensor.WireID,
&sensor.I2CBus,
&sensor.I2CAddress,
&sensor.GPIONumber,
&sensor.Model,
&sensor.Enabled,
&sensor.TickDuration,
&sensor.DeviceID,
&sensor.CreationDate,
&sensor.UpdateDate,
)
if err != nil {
return nil, fmt.Errorf("Failed to scan row: %v", err)
}
sensors = append(sensors, sensor)
}
return sensors, nil
}
// SelectTemperature returns temperatures from the database
func (sqlite *SQLite) SelectTemperature(ctx context.Context, id string) (*types.MeasuredValue, error) {
queryFile := "selectTemperature.sql"
query, present := sqlite.queries[queryFile]
if !present {
return nil, fmt.Errorf("SQLite-Backend: File %v not found", queryFile)
}
tx, err := sqlite.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: true})
if err != nil {
return nil, err
}
measuredValues, err := sqlite.selectMeasuredValue(tx, query, id)
if err != nil {
return nil, err
}
err = tx.Commit()
if err != nil {
return nil, err
}
if measuredValues == nil {
return nil, nil
}
for _, measuredValue := range measuredValues {
measuredValue.ValueType = types.Temperature
}
return measuredValues[0], nil
}
// SelectTemperatures returns temperatures from the database
func (sqlite *SQLite) SelectTemperatures(ctx context.Context) ([]*types.MeasuredValue, error) {
queryFile := "selectTemperatures.sql"
query, present := sqlite.queries[queryFile]
if !present {
return nil, fmt.Errorf("SQLite-Backend: File %v not found", queryFile)
}
tx, err := sqlite.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: true})
if err != nil {
return nil, err
}
measuredValues, err := sqlite.selectMeasuredValue(tx, query)
if err != nil {
return nil, err
}
err = tx.Commit()
if err != nil {
return nil, err
}
for _, measuredValue := range measuredValues {
measuredValue.ValueType = types.Temperature
}
return measuredValues, nil
}
// UpdateDevices updates a device in the database
func (sqlite *SQLite) UpdateDevices(ctx context.Context, devices ...*types.Device) error {
queryFile := "updateDevice.sql"
query, present := sqlite.queries[queryFile]
if !present {
return fmt.Errorf("SQLite-Backend: File %v not found", queryFile)
}
tx, err := sqlite.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: false})
if err != nil {
return err
}
stmt, err := tx.Prepare(query)
if err != nil {
tx.Rollback()
return err
}
defer stmt.Close()
for _, device := range devices {
_, err := stmt.Exec(
&device.Name,
&device.Location,
&device.CreationDate,
&device.UpdateDate,
&device.ID,
)
if err != nil {
tx.Rollback()
return err
}
}
return tx.Commit()
}
// UpdateSensors updates a sensor in the database
func (sqlite *SQLite) UpdateSensors(ctx context.Context, sensors ...*types.Sensor) error {
queryFile := "updateSensor.sql"
query, present := sqlite.queries[queryFile]
if !present {
return fmt.Errorf("SQLite-Backend: File %v not found", queryFile)
}
tx, err := sqlite.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: false})
if err != nil {
return err
}
stmt, err := tx.Prepare(query)
if err != nil {
tx.Rollback()
return err
}
defer stmt.Close()
for _, sensor := range sensors {
_, err := stmt.Exec(
&sensor.Name,
&sensor.Location,
&sensor.WireID,
&sensor.I2CBus,
&sensor.I2CAddress,
&sensor.GPIONumber,
&sensor.Model,
&sensor.Enabled,
&sensor.TickDuration,
&sensor.DeviceID,
&sensor.CreationDate,
&sensor.UpdateDate,
&sensor.ID,
)
if err != nil {
tx.Rollback()
return err
}
}
return tx.Commit()
}

View File

@ -0,0 +1,2 @@
DELETE FROM devices
WHERE device_name = $1;

View File

@ -0,0 +1,2 @@
DELETE FROM sensors
WHERE sensor_name = $1;

View File

@ -0,0 +1,9 @@
SELECT
device_id,
device_name,
device_location,
creation_date,
update_date
FROM
devices
WHERE device_name = $1;

View File

@ -0,0 +1,18 @@
SELECT
sensor_id,
sensor_name,
sensor_location,
wire_id,
i2c_bus,
i2c_address,
gpio_number,
sensor_model,
sensor_enabled,
tick_duration,
device_id,
creation_date,
update_date
FROM
sensors
WHERE
device_id = $1;

View File

@ -0,0 +1,18 @@
SELECT
sensor_id,
sensor_name,
sensor_location,
wire_id,
i2c_bus,
i2c_address,
gpio_number,
sensor_model,
sensor_enabled,
tick_duration,
device_id,
creation_date,
update_date
FROM
sensors
WHERE
sensor_model = $1;

View File

@ -0,0 +1,18 @@
SELECT
sensor_id,
sensor_name,
sensor_location,
wire_id,
i2c_bus,
i2c_address,
gpio_number,
sensor_model,
sensor_enabled,
tick_duration,
device_id,
creation_date,
update_date
FROM
sensors
WHERE
sensor_name = $1;

File diff suppressed because it is too large Load Diff

View File

@ -4,339 +4,412 @@ import (
"context"
"fmt"
"net/url"
"strings"
"git.cryptic.systems/volker.raschek/flucky/pkg/repository/db"
"git.cryptic.systems/volker.raschek/flucky/pkg/repository/postgres"
"git.cryptic.systems/volker.raschek/flucky/pkg/repository/sqlite3"
"git.cryptic.systems/volker.raschek/flucky/pkg/types"
"git.cryptic.systems/volker.raschek/go-logger"
)
type Repository interface {
AddDevices(ctx context.Context, devices ...*types.Device) error
AddMeasuredValues(ctx context.Context, measuredValues ...*types.MeasuredValue) error
AddOrUpdateDevices(ctx context.Context, devices ...*types.Device) error
AddOrUpdateMeasuredValues(ctx context.Context, measuredValues ...*types.MeasuredValue) error
AddOrUpdateSensors(ctx context.Context, sensors ...*types.Sensor) error
AddSensors(ctx context.Context, sensors ...*types.Sensor) error
Close() error
GetDeviceByID(ctx context.Context, deviceID string) (*types.Device, error)
GetDeviceByName(ctx context.Context, name string) (*types.Device, error)
GetDevices(ctx context.Context) ([]*types.Device, error)
GetHumidities(ctx context.Context) ([]*types.MeasuredValue, error)
GetHumidityByID(ctx context.Context, id string) (*types.MeasuredValue, error)
GetPressureByID(ctx context.Context, id string) (*types.MeasuredValue, error)
GetPressures(ctx context.Context) ([]*types.MeasuredValue, error)
GetSensorByID(ctx context.Context, sensorID string) (*types.Sensor, error)
GetSensorsByDeviceIDs(ctx context.Context, deviceIDs ...string) ([]*types.Sensor, error)
GetSensorsByModels(ctx context.Context, sensorModels ...string) ([]*types.Sensor, error)
GetSensorsByNames(ctx context.Context, sensorModels ...string) ([]*types.Sensor, error)
GetSensors(ctx context.Context) ([]*types.Sensor, error)
GetTemperatureByID(ctx context.Context, id string) (*types.MeasuredValue, error)
GetTemperatures(ctx context.Context) ([]*types.MeasuredValue, error)
Migrate(ctx context.Context) error
RemoveDevicesByIDs(ctx context.Context, deviceIDs ...string) error
RemoveDevicesByNames(ctx context.Context, names ...string) error
RemoveSensorsByIDs(ctx context.Context, sensorIDs ...string) error
RemoveSensorsByNames(ctx context.Context, names ...string) error
UpdateDevices(ctx context.Context, devices ...*types.Device) error
UpdateSensors(ctx context.Context, sensors ...*types.Sensor) error
}
// New returns a new database backend interface
func New(databaseURL *url.URL, flogger logger.Logger) (Repository, error) {
switch databaseURL.Scheme {
case "postgres":
repo, err := postgres.New(postgres.Opts{
DatabaseURL: databaseURL,
Logger: flogger,
})
if err != nil {
return nil, err
}
// Initialize database scheme if not exists
err = repo.Migrate(context.Background())
if err != nil {
return nil, err
}
return repo, nil
case "sqlite3":
repo, err := sqlite3.New(sqlite3.Opts{
DatabaseURL: databaseURL,
Logger: flogger,
})
if err != nil {
return nil, err
}
// Initialize database scheme if not exists
err = repo.Migrate(context.Background())
if err != nil {
return nil, err
}
return repo, nil
default:
return nil, fmt.Errorf("Unsupported repository scheme: %v", databaseURL.Scheme)
}
}
// Repository represent a repository where all devices, sensors and measured
// values are stored.
type Repository struct {
database db.Database
}
// type Repository struct {
// database db.Database
// }
// AddDevices to the repository
func (repo *Repository) AddDevices(devices ...*types.Device) error {
return repo.database.InsertDevices(context.Background(), devices...)
}
// // AddDevices to the repository
// func (repo *Repository) AddDevices(devices ...*types.Device) error {
// return repo.database.InsertDevices(context.Background(), devices...)
// }
// AddMeasuredValues to the repository
func (repo *Repository) AddMeasuredValues(measuredValues ...*types.MeasuredValue) error {
return repo.database.InsertMeasuredValues(context.Background(), measuredValues...)
}
// // AddMeasuredValues to the repository
// func (repo *Repository) AddMeasuredValues(measuredValues ...*types.MeasuredValue) error {
// return repo.database.InsertMeasuredValues(context.Background(), measuredValues...)
// }
// AddOrUpdateDevices to the repository
func (repo *Repository) AddOrUpdateDevices(devices ...*types.Device) error {
return repo.database.InsertOrUpdateDevices(context.Background(), devices...)
}
// // AddOrUpdateDevices to the repository
// func (repo *Repository) AddOrUpdateDevices(devices ...*types.Device) error {
// return repo.database.InsertOrUpdateDevices(context.Background(), devices...)
// }
// AddOrUpdateMeasuredValues to the repository
func (repo *Repository) AddOrUpdateMeasuredValues(measuredValues ...*types.MeasuredValue) error {
return repo.database.InsertOrUpdateMeasuredValues(context.Background(), measuredValues...)
}
// // AddOrUpdateMeasuredValues to the repository
// func (repo *Repository) AddOrUpdateMeasuredValues(measuredValues ...*types.MeasuredValue) error {
// return repo.database.InsertOrUpdateMeasuredValues(context.Background(), measuredValues...)
// }
// AddOrUpdateSensors to the repository
func (repo *Repository) AddOrUpdateSensors(sensors ...*types.Sensor) error {
return repo.database.InsertOrUpdateSensors(context.Background(), sensors...)
}
// // AddOrUpdateSensors to the repository
// func (repo *Repository) AddOrUpdateSensors(sensors ...*types.Sensor) error {
// return repo.database.InsertOrUpdateSensors(context.Background(), sensors...)
// }
// AddSensors to the repository
func (repo *Repository) AddSensors(sensors ...*types.Sensor) error {
return repo.database.InsertSensors(context.Background(), sensors...)
}
// // AddSensors to the repository
// func (repo *Repository) AddSensors(sensors ...*types.Sensor) error {
// return repo.database.InsertSensors(context.Background(), sensors...)
// }
// Close closes the repository and prevents new queries from starting. Close
// then waits for all queries that have started processing on the server to
// finish.
func (repo *Repository) Close() error {
return repo.database.Close()
}
// // Close closes the repository and prevents new queries from starting. Close
// // then waits for all queries that have started processing on the server to
// // finish.
// func (repo *Repository) Close() error {
// return repo.database.Close()
// }
// DisableSensorsByNames disable all sensors which match bei their name
func (repo *Repository) DisableSensorsByNames(sensorNames ...string) error {
sensors, err := repo.GetSensors()
if err != nil {
return err
}
// // DisableSensorsByNames disable all sensors which match bei their name
// func (repo *Repository) DisableSensorsByNames(sensorNames ...string) error {
// sensors, err := repo.GetSensors()
// if err != nil {
// return err
// }
matchedSensors := make([]*types.Sensor, 0)
for _, sensor := range sensors {
for _, sensorName := range sensorNames {
if strings.Compare(sensor.Name, sensorName) == 0 {
sensor.Enabled = false
matchedSensors = append(matchedSensors, sensor)
}
}
}
// matchedSensors := make([]*types.Sensor, 0)
// for _, sensor := range sensors {
// for _, sensorName := range sensorNames {
// if strings.Compare(sensor.Name, sensorName) == 0 {
// sensor.Enabled = false
// matchedSensors = append(matchedSensors, sensor)
// }
// }
// }
return repo.UpdateSensors(matchedSensors...)
}
// return repo.UpdateSensors(matchedSensors...)
// }
// EnableSensorsByNames enable all sensors which match bei their name
func (repo *Repository) EnableSensorsByNames(sensorNames ...string) error {
sensors, err := repo.GetSensors()
if err != nil {
return err
}
// // EnableSensorsByNames enable all sensors which match bei their name
// func (repo *Repository) EnableSensorsByNames(sensorNames ...string) error {
// sensors, err := repo.GetSensors()
// if err != nil {
// return err
// }
matchedSensors := make([]*types.Sensor, 0)
for _, sensor := range sensors {
for _, sensorName := range sensorNames {
if strings.Compare(sensor.Name, sensorName) == 0 {
sensor.Enabled = true
matchedSensors = append(matchedSensors, sensor)
}
}
}
// matchedSensors := make([]*types.Sensor, 0)
// for _, sensor := range sensors {
// for _, sensorName := range sensorNames {
// if strings.Compare(sensor.Name, sensorName) == 0 {
// sensor.Enabled = true
// matchedSensors = append(matchedSensors, sensor)
// }
// }
// }
return repo.UpdateSensors(matchedSensors...)
}
// return repo.UpdateSensors(matchedSensors...)
// }
// GetDevice returns a device by his id. If no device has been found, the
// function returns nil.
func (repo *Repository) GetDevice(deviceID string) (*types.Device, error) {
return repo.database.SelectDevice(context.Background(), deviceID)
}
// // GetDevice returns a device by his id. If no device has been found, the
// // function returns nil.
// func (repo *Repository) GetDevice(deviceID string) (*types.Device, error) {
// return repo.database.SelectDevice(context.Background(), deviceID)
// }
// GetDevices returns all devices. If no devices has been found, the function
// returns nil.
func (repo *Repository) GetDevices() ([]*types.Device, error) {
return repo.database.SelectDevices(context.Background())
}
// // GetDevices returns all devices. If no devices has been found, the function
// // returns nil.
// func (repo *Repository) GetDevices() ([]*types.Device, error) {
// return repo.database.SelectDevices(context.Background())
// }
// GetHumidity returns a humidity value by id
func (repo *Repository) GetHumidity(humidityID string) (*types.MeasuredValue, error) {
return repo.database.SelectHumidity(context.Background(), humidityID)
}
// // GetHumidity returns a humidity value by id
// func (repo *Repository) GetHumidity(humidityID string) (*types.MeasuredValue, error) {
// return repo.database.SelectHumidity(context.Background(), humidityID)
// }
// GetPressure returns a pressure value by id
func (repo *Repository) GetPressure(pressureID string) (*types.MeasuredValue, error) {
return repo.database.SelectPressure(context.Background(), pressureID)
}
// // GetPressure returns a pressure value by id
// func (repo *Repository) GetPressure(pressureID string) (*types.MeasuredValue, error) {
// return repo.database.SelectPressure(context.Background(), pressureID)
// }
// GetSensor returns a sensor by his id. If no sensor has been found, the
// function returns nil.
func (repo *Repository) GetSensor(sensorID string) (*types.Sensor, error) {
return repo.database.SelectSensor(context.Background(), sensorID)
}
// // GetSensor returns a sensor by his id. If no sensor has been found, the
// // function returns nil.
// func (repo *Repository) GetSensor(sensorID string) (*types.Sensor, error) {
// return repo.database.SelectSensor(context.Background(), sensorID)
// }
// GetSensors returns all sensors. If no sensors has been found, the function
// returns nil.
func (repo *Repository) GetSensors(models ...string) ([]*types.Sensor, error) {
sensors, err := repo.database.SelectSensors(context.Background())
switch {
case err != nil:
return nil, err
case len(models) > 0:
cachedSensors := make([]*types.Sensor, 0)
LOOP:
for i := range sensors {
for j := range models {
if strings.ToLower(sensors[i].Model) == strings.ToLower(models[j]) {
cachedSensors = append(cachedSensors, sensors[i])
continue LOOP
}
}
}
return cachedSensors, nil
case len(models) <= 0:
fallthrough
default:
return sensors, err
}
}
// // GetSensors returns all sensors. If no sensors has been found, the function
// // returns nil.
// func (repo *Repository) GetSensors(models ...string) ([]*types.Sensor, error) {
// sensors, err := repo.database.SelectSensors(context.Background())
// switch {
// case err != nil:
// return nil, err
// case len(models) > 0:
// cachedSensors := make([]*types.Sensor, 0)
// LOOP:
// for i := range sensors {
// for j := range models {
// if strings.ToLower(sensors[i].Model) == strings.ToLower(models[j]) {
// cachedSensors = append(cachedSensors, sensors[i])
// continue LOOP
// }
// }
// }
// return cachedSensors, nil
// case len(models) <= 0:
// fallthrough
// default:
// return sensors, err
// }
// }
// GetSensorsByDeviceID returns all sensors by a device id. If no sensor has
// been found by the id, the function returns nil.
func (repo *Repository) GetSensorsByDeviceID(deviceID string) ([]*types.Sensor, error) {
cachedSensors := make([]*types.Sensor, 0)
sensors, err := repo.GetSensors()
if err != nil {
return nil, err
}
// // GetSensorsByDeviceID returns all sensors by a device id. If no sensor has
// // been found by the id, the function returns nil.
// func (repo *Repository) GetSensorsByDeviceID(deviceID string) ([]*types.Sensor, error) {
// cachedSensors := make([]*types.Sensor, 0)
// sensors, err := repo.GetSensors()
// if err != nil {
// return nil, err
// }
for _, sensor := range sensors {
if strings.Compare(sensor.DeviceID, deviceID) == 0 {
cachedSensors = append(cachedSensors, sensor)
}
}
// for _, sensor := range sensors {
// if strings.Compare(sensor.DeviceID, deviceID) == 0 {
// cachedSensors = append(cachedSensors, sensor)
// }
// }
return cachedSensors, nil
}
// return cachedSensors, nil
// }
// GetTemperature returns a temperature value by id
func (repo *Repository) GetTemperature(pressureID string) (*types.MeasuredValue, error) {
return repo.database.SelectTemperature(context.Background(), pressureID)
}
// // GetTemperature returns a temperature value by id
// func (repo *Repository) GetTemperature(pressureID string) (*types.MeasuredValue, error) {
// return repo.database.SelectTemperature(context.Background(), pressureID)
// }
// RemoveDevices removes devices by their ids from the repository. Additional
// all sensors and measured values, which are in relation with the device
// respectively the sensors will also be deleted.
func (repo *Repository) RemoveDevices(deviceIDs ...string) error {
return repo.database.DeleteDevices(context.Background(), deviceIDs...)
}
// // RemoveDevices removes devices by their ids from the repository. Additional
// // all sensors and measured values, which are in relation with the device
// // respectively the sensors will also be deleted.
// func (repo *Repository) RemoveDevices(deviceIDs ...string) error {
// return repo.database.DeleteDevices(context.Background(), deviceIDs...)
// }
// RenameSensors all sensors which match by their current name to the new name
func (repo *Repository) RenameSensors(oldName string, newName string) error {
sensors, err := repo.GetSensors()
if err != nil {
return err
}
// // RenameSensors all sensors which match by their current name to the new name
// func (repo *Repository) RenameSensors(oldName string, newName string) error {
// sensors, err := repo.GetSensors()
// if err != nil {
// return err
// }
matchedSensors := make([]*types.Sensor, 0)
for _, sensor := range sensors {
if strings.Compare(sensor.Name, oldName) == 0 {
sensor.Name = newName
matchedSensors = append(matchedSensors, sensor)
}
}
// matchedSensors := make([]*types.Sensor, 0)
// for _, sensor := range sensors {
// if strings.Compare(sensor.Name, oldName) == 0 {
// sensor.Name = newName
// matchedSensors = append(matchedSensors, sensor)
// }
// }
return repo.UpdateSensors(matchedSensors...)
}
// return repo.UpdateSensors(matchedSensors...)
// }
// RemoveSensors removes sensors by their ids from the repository. Additional
// all measured values, which are in relation with the sensor will also be
// deleted.
func (repo *Repository) RemoveSensors(sensorIDs ...string) error {
return repo.database.DeleteSensors(context.Background(), sensorIDs...)
}
// // RemoveSensors removes sensors by their ids from the repository. Additional
// // all measured values, which are in relation with the sensor will also be
// // deleted.
// func (repo *Repository) RemoveSensors(sensorIDs ...string) error {
// return repo.database.DeleteSensors(context.Background(), sensorIDs...)
// }
// RemoveSensorsByNames removes all sensors which match bei their name
func (repo *Repository) RemoveSensorsByNames(sensorNames ...string) error {
sensors, err := repo.GetSensors()
if err != nil {
return err
}
// // RemoveSensorsByNames removes all sensors which match bei their name
// func (repo *Repository) RemoveSensorsByNames(sensorNames ...string) error {
// sensors, err := repo.GetSensors()
// if err != nil {
// return err
// }
matchedSensorIDs := make([]string, 0)
for _, sensor := range sensors {
for _, sensorName := range sensorNames {
if strings.Compare(sensor.Name, sensorName) == 0 {
matchedSensorIDs = append(matchedSensorIDs, sensor.ID)
}
}
}
// matchedSensorIDs := make([]string, 0)
// for _, sensor := range sensors {
// for _, sensorName := range sensorNames {
// if strings.Compare(sensor.Name, sensorName) == 0 {
// matchedSensorIDs = append(matchedSensorIDs, sensor.ID)
// }
// }
// }
return repo.RemoveSensors(matchedSensorIDs...)
}
// return repo.RemoveSensors(matchedSensorIDs...)
// }
// UpdateDevices update devices which are stored into the repository by their
// id. The id of the device can not be updated.
func (repo *Repository) UpdateDevices(devices ...*types.Device) error {
return repo.database.UpdateDevices(context.Background(), devices...)
}
// // UpdateDevices update devices which are stored into the repository by their
// // id. The id of the device can not be updated.
// func (repo *Repository) UpdateDevices(devices ...*types.Device) error {
// return repo.database.UpdateDevices(context.Background(), devices...)
// }
// UpdateSensors update sensors which are stored into the repository by their
// id. The id of the sensor can not be updated.
func (repo *Repository) UpdateSensors(sensors ...*types.Sensor) error {
return repo.database.UpdateSensors(context.Background(), sensors...)
}
// // UpdateSensors update sensors which are stored into the repository by their
// // id. The id of the sensor can not be updated.
// func (repo *Repository) UpdateSensors(sensors ...*types.Sensor) error {
// return repo.database.UpdateSensors(context.Background(), sensors...)
// }
// New returns a new repository based on the data source name (dsn)
func New(dsnURL *url.URL, flogger logger.Logger) (*Repository, error) {
database, err := db.New(dsnURL, flogger)
if err != nil {
return nil, err
}
// // New returns a new repository based on the data source name (dsn)
// func New(dsnURL *url.URL, flogger logger.Logger) (*Repository, error) {
// database, err := db.New(dsnURL, flogger)
// if err != nil {
// return nil, err
// }
return &Repository{
database: database,
}, nil
}
// return &Repository{
// database: database,
// }, nil
// }
type OptImport struct {
Sensors bool
Humidities bool
Pressures bool
Temperatures bool
}
// type OptImport struct {
// Sensors bool
// Humidities bool
// Pressures bool
// Temperatures bool
// }
func Import(sourceDSNURL *url.URL, destDNSURL *url.URL, flogger logger.Logger, optImport OptImport) error {
// func Import(sourceDSNURL *url.URL, destDNSURL *url.URL, flogger logger.Logger, optImport OptImport) error {
importMap := map[string]bool{
"humidities": optImport.Humidities,
"pressures": optImport.Pressures,
"temperatures": optImport.Temperatures,
}
// importMap := map[string]bool{
// "humidities": optImport.Humidities,
// "pressures": optImport.Pressures,
// "temperatures": optImport.Temperatures,
// }
// enable sensors if one measured value is enabled
if !optImport.Sensors {
for key, value := range importMap {
if value {
flogger.Info("Enable import option sensors. It's required as foreign key for %v", key)
optImport.Sensors = true
}
}
}
// // enable sensors if one measured value is enabled
// if !optImport.Sensors {
// for key, value := range importMap {
// if value {
// flogger.Info("Enable import option sensors. It's required as foreign key for %v", key)
// optImport.Sensors = true
// }
// }
// }
// Initialize source repository
sourceRepo, err := New(sourceDSNURL, flogger)
if err != nil {
return fmt.Errorf("Failed to open the source repo: %w", err)
}
defer sourceRepo.Close()
// // Initialize source repository
// sourceRepo, err := New(sourceDSNURL, flogger)
// if err != nil {
// return fmt.Errorf("Failed to open the source repo: %w", err)
// }
// defer sourceRepo.Close()
// Initialize destination repository
destRepo, err := New(destDNSURL, flogger)
if err != nil {
return fmt.Errorf("Failed to open the destination repo: %w", err)
}
defer destRepo.Close()
// // Initialize destination repository
// destRepo, err := New(destDNSURL, flogger)
// if err != nil {
// return fmt.Errorf("Failed to open the destination repo: %w", err)
// }
// defer destRepo.Close()
// AddOrUpdate: Devices
ctx := context.Background()
devices, err := sourceRepo.database.SelectDevices(ctx)
if err != nil {
return fmt.Errorf("Failed to fetch devices from source repo: %w", err)
}
// // AddOrUpdate: Devices
// ctx := context.Background()
// devices, err := sourceRepo.database.SelectDevices(ctx)
// if err != nil {
// return fmt.Errorf("Failed to fetch devices from source repo: %w", err)
// }
flogger.Debug("Found %v devices", len(devices))
// flogger.Debug("Found %v devices", len(devices))
for i := range devices {
err := destRepo.AddOrUpdateDevices(devices[i])
if err != nil {
return fmt.Errorf("Failed to add or update device %v into dest repo: %w", devices[i].Name, err)
}
}
// for i := range devices {
// err := destRepo.AddOrUpdateDevices(devices[i])
// if err != nil {
// return fmt.Errorf("Failed to add or update device %v into dest repo: %w", devices[i].Name, err)
// }
// }
// AddOrUpdate: Sensors
if optImport.Sensors {
sensors, err := sourceRepo.database.SelectSensors(ctx)
if err != nil {
return fmt.Errorf("Failed to fetch sensors from source repo: %w", err)
}
// // AddOrUpdate: Sensors
// if optImport.Sensors {
// sensors, err := sourceRepo.database.SelectSensors(ctx)
// if err != nil {
// return fmt.Errorf("Failed to fetch sensors from source repo: %w", err)
// }
flogger.Debug("Found %v sensors", len(sensors))
// flogger.Debug("Found %v sensors", len(sensors))
for i := range sensors {
err := destRepo.AddOrUpdateSensors(sensors[i])
if err != nil {
return fmt.Errorf("Failed to add or update sensor %v into dest repo: %w", sensors[i].Name, err)
}
}
}
// for i := range sensors {
// err := destRepo.AddOrUpdateSensors(sensors[i])
// if err != nil {
// return fmt.Errorf("Failed to add or update sensor %v into dest repo: %w", sensors[i].Name, err)
// }
// }
// }
for key, f := range map[string]func(context.Context) ([]*types.MeasuredValue, error){
"humidities": sourceRepo.database.SelectHumidities,
"pressures": sourceRepo.database.SelectPressures,
"temperatures": sourceRepo.database.SelectTemperatures,
} {
if importMap[key] {
measuredValues, err := f(ctx)
if err != nil {
return fmt.Errorf("Failed to select %v from source repo: %w", key, err)
}
// for key, f := range map[string]func(context.Context) ([]*types.MeasuredValue, error){
// "humidities": sourceRepo.database.SelectHumidities,
// "pressures": sourceRepo.database.SelectPressures,
// "temperatures": sourceRepo.database.SelectTemperatures,
// } {
// if importMap[key] {
// measuredValues, err := f(ctx)
// if err != nil {
// return fmt.Errorf("Failed to select %v from source repo: %w", key, err)
// }
flogger.Debug("Found %v %v values", len(measuredValues), key)
// flogger.Debug("Found %v %v values", len(measuredValues), key)
err = destRepo.AddOrUpdateMeasuredValues(measuredValues...)
if err != nil {
return fmt.Errorf("Failed to add or update %v into dest repo: %w", key, err)
}
}
}
// err = destRepo.AddOrUpdateMeasuredValues(measuredValues...)
// if err != nil {
// return fmt.Errorf("Failed to add or update %v into dest repo: %w", key, err)
// }
// }
// }
return nil
}
// return nil
// }

View File

@ -31,15 +31,13 @@ func TestPostgresBackend(t *testing.T) {
require.NoError(err)
rand.Seed(time.Now().Unix())
postgresHostPort := rand.Intn(10024-1024) + 1024
postgresDBPasswort := "postgres"
postgresContainerID, err := dockerClient.NewBuilder("postgres:13-alpine").
Port(fmt.Sprintf("%v:5432/tcp", postgresHostPort)).
Pull().
AddEnv("PGTZ", "Europe/Berlin").
AddEnv("POSTGRES_PASSWORD", postgresDBPasswort).
AddEnv("TZ", "Europe/Berlin").
Mount("/etc/localtime", "/etc/localtime").
Pull().
Start(context.Background())
cleanup := func() {
dockerClient.ContainerRemoveByIDs(context.Background(), postgresContainerID)
@ -49,8 +47,12 @@ func TestPostgresBackend(t *testing.T) {
time.Sleep(time.Second * 10)
// inspect container to get his container ip
cjson, err := dockerClient.ContainerInspect(context.Background(), postgresContainerID)
require.NoError(err)
// postgres://[user]:[password]@[host]:[port]/[path]?[query]
dsnURL, err := url.Parse(fmt.Sprintf("postgres://postgres:%v@127.0.0.1:%v?sslmode=disable", postgresDBPasswort, postgresHostPort))
dsnURL, err := url.Parse(fmt.Sprintf("postgres://postgres:%v@%v:5432?sslmode=disable", postgresDBPasswort, cjson.NetworkSettings.IPAddress))
require.NoError(err)
repo, err := repository.New(dsnURL, logger.NewLogger(logger.LogLevelDebug))
@ -75,7 +77,8 @@ func TestSQLiteBackend(t *testing.T) {
testBackend(t, repo)
}
func testBackend(t *testing.T, repo *repository.Repository) {
func testBackend(t *testing.T, repo repository.Repository) {
ctx := context.Background()
require := require.New(t)
location := uuid.NewV4().String()
expectedDevices := []*types.Device{
@ -94,33 +97,50 @@ func testBackend(t *testing.T, repo *repository.Repository) {
}
// Test: AddDevice
err := repo.AddDevices(expectedDevices...)
err := repo.AddDevices(ctx, expectedDevices...)
require.NoError(err)
// Test: GetDevices
devices, err := repo.GetDevices()
devices, err := repo.GetDevices(ctx)
require.NoError(err)
require.Len(devices, len(expectedDevices))
require.JSONEq(jsonEncoder(expectedDevices), jsonEncoder(devices))
// Test: GetDevice
device, err := repo.GetDevice(expectedDevices[0].ID)
// Test: GetDeviceByID
device, err := repo.GetDeviceByID(ctx, expectedDevices[0].ID)
require.NoError(err)
require.JSONEq(jsonEncoder(expectedDevices[0]), jsonEncoder(device))
// Test: RemoveDevice
err = repo.RemoveDevices(expectedDevices[0].ID)
// Test: GetDeviceByName
device, err = repo.GetDeviceByName(ctx, expectedDevices[0].Name)
require.NoError(err)
require.JSONEq(jsonEncoder(expectedDevices[0]), jsonEncoder(device))
// Test: RemoveDevicesByIDs
err = repo.RemoveDevicesByIDs(ctx, expectedDevices[0].ID)
require.NoError(err)
devices, err = repo.GetDevices()
devices, err = repo.GetDevices(ctx)
require.NoError(err)
require.Len(devices, 1)
device, err = repo.GetDevice(expectedDevices[0].ID)
device, err = repo.GetDeviceByID(ctx, expectedDevices[0].ID)
require.NoError(err)
require.Nil(device)
err = repo.AddDevices(expectedDevices[0])
// Test: RemoveDevicesByNames
err = repo.RemoveDevicesByNames(ctx, expectedDevices[0].Name)
require.NoError(err)
devices, err = repo.GetDevices(ctx)
require.NoError(err)
require.Len(devices, 1)
device, err = repo.GetDeviceByID(ctx, expectedDevices[0].ID)
require.NoError(err)
require.Nil(device)
err = repo.AddDevices(ctx, expectedDevices[0])
require.NoError(err)
// Test: Update Devices
@ -132,10 +152,10 @@ func testBackend(t *testing.T, repo *repository.Repository) {
CreationDate: *timeNow(require),
}
err = repo.UpdateDevices(expectedDevice)
err = repo.UpdateDevices(ctx, expectedDevice)
require.NoError(err)
device, err = repo.GetDevice(expectedDevice.ID)
device, err = repo.GetDeviceByID(ctx, expectedDevice.ID)
require.NoError(err)
require.NotEmpty(device)
require.Equal(expectedDevice.ID, device.ID)
@ -150,12 +170,12 @@ func testBackend(t *testing.T, repo *repository.Repository) {
Location: &location,
CreationDate: *timeNow(require),
}
err = repo.AddOrUpdateDevices(expectedDevice)
err = repo.AddOrUpdateDevices(ctx, expectedDevice)
require.NoError(err)
// time.Sleep(time.Minute * 10)
device, err = repo.GetDevice(expectedDevice.ID)
device, err = repo.GetDeviceByID(ctx, expectedDevice.ID)
require.NoError(err)
require.NotEmpty(device)
require.Equal(expectedDevice.ID, device.ID)
@ -169,10 +189,10 @@ func testBackend(t *testing.T, repo *repository.Repository) {
Location: &location,
CreationDate: *timeNow(require),
}
err = repo.AddOrUpdateDevices(expectedDevice)
err = repo.AddOrUpdateDevices(ctx, expectedDevice)
require.NoError(err)
device, err = repo.GetDevice(expectedDevice.ID)
device, err = repo.GetDeviceByID(ctx, expectedDevice.ID)
require.NoError(err)
require.NotEmpty(device)
require.Equal(expectedDevice.ID, device.ID)
@ -222,97 +242,80 @@ func testBackend(t *testing.T, repo *repository.Repository) {
)
// Test: AddSensors
err = repo.AddSensors(expectedSensors...)
err = repo.AddSensors(ctx, expectedSensors...)
require.NoError(err)
// Test: GetSensors
sensors, err := repo.GetSensors()
sensors, err := repo.GetSensors(ctx)
require.NoError(err)
require.Len(sensors, len(expectedSensors))
sensors, err = repo.GetSensors("BME280")
require.NoError(err)
require.Len(sensors, 1)
require.JSONEq(jsonEncoder(expectedSensors[2]), jsonEncoder(sensors[0]))
sensors, err = repo.GetSensors("DS18B20")
require.NoError(err)
require.Len(sensors, 1)
require.JSONEq(jsonEncoder(expectedSensors[1]), jsonEncoder(sensors[0]))
sensors, err = repo.GetSensors("DHT11")
// Test: GetSensorsByNames
sensors, err = repo.GetSensorsByNames(ctx, "01fbdbe9-cebf-42ed-8065-bf4882ccf76b")
require.NoError(err)
require.Len(sensors, 1)
require.JSONEq(jsonEncoder(expectedSensors[0]), jsonEncoder(sensors[0]))
sensors, err = repo.GetSensors("DHT11", "DS18B20")
// Test: GetSensorsByModels
sensors, err = repo.GetSensorsByModels(ctx, "BME280")
require.NoError(err)
require.Len(sensors, 1)
require.JSONEq(jsonEncoder(expectedSensors[2]), jsonEncoder(sensors[0]))
sensors, err = repo.GetSensorsByModels(ctx, "DS18B20")
require.NoError(err)
require.Len(sensors, 1)
require.JSONEq(jsonEncoder(expectedSensors[1]), jsonEncoder(sensors[0]))
sensors, err = repo.GetSensorsByModels(ctx, "DHT11")
require.NoError(err)
require.Len(sensors, 1)
require.JSONEq(jsonEncoder(expectedSensors[0]), jsonEncoder(sensors[0]))
sensors, err = repo.GetSensorsByModels(ctx, "DHT11", "DS18B20")
require.NoError(err)
require.Len(sensors, 2)
require.JSONEq(jsonEncoder(expectedSensors[0:2]), jsonEncoder(sensors[0:2]))
// Test: GetSensor
sensor, err := repo.GetSensor(expectedSensors[0].ID)
// Test: GetSensorByID
sensor, err := repo.GetSensorByID(ctx, expectedSensors[0].ID)
require.NoError(err)
require.JSONEq(jsonEncoder(expectedSensors[0]), jsonEncoder(sensor))
// Test: GetSensorsByDeviceID
sensors, err = repo.GetSensorsByDeviceID("ec0be3ab-d26d-4f9b-a96e-23ae5c577f8f")
sensors, err = repo.GetSensorsByDeviceIDs(ctx, "ec0be3ab-d26d-4f9b-a96e-23ae5c577f8f")
require.NoError(err)
require.Len(sensors, 2)
require.JSONEq(jsonEncoder(expectedSensors[0:2]), jsonEncoder(sensors))
// Test: RemoveSensors
err = repo.RemoveSensors(expectedSensors[0].ID)
// Test: RemoveSensorsByIDs
err = repo.RemoveSensorsByIDs(ctx, expectedSensors[0].ID)
require.NoError(err)
sensors, err = repo.GetSensors()
sensors, err = repo.GetSensors(ctx)
require.NoError(err)
require.Len(sensors, 2)
sensors, err = repo.GetSensorsByDeviceID("ec0be3ab-d26d-4f9b-a96e-23ae5c577f8f")
sensors, err = repo.GetSensorsByDeviceIDs(ctx, "ec0be3ab-d26d-4f9b-a96e-23ae5c577f8f")
require.NoError(err)
require.Len(sensors, 1)
sensor, err = repo.GetSensor(expectedSensors[0].ID)
sensor, err = repo.GetSensorByID(ctx, expectedSensors[0].ID)
require.NoError(err)
require.Nil(sensor)
// Test: RemoveSensorsByNames
err = repo.RemoveSensorsByNames(expectedSensors[1].Name)
err = repo.RemoveSensorsByNames(ctx, expectedSensors[1].Name)
require.NoError(err)
sensors, err = repo.GetSensors()
sensors, err = repo.GetSensors(ctx)
require.NoError(err)
require.Len(sensors, 1)
sensor, err = repo.GetSensor(expectedSensors[1].ID)
sensor, err = repo.GetSensorByID(ctx, expectedSensors[1].ID)
require.NoError(err)
require.Nil(sensor)
// Test: RenameSensor
err = repo.RenameSensors(expectedSensors[2].Name, "Hello")
require.NoError(err)
sensor, err = repo.GetSensor(expectedSensors[2].ID)
require.Equal("Hello", sensor.Name)
require.NotNil(sensor)
// Test: DisableSensorsByNames
err = repo.DisableSensorsByNames("Hello")
require.NoError(err)
sensor, err = repo.GetSensor(expectedSensors[2].ID)
require.False(sensor.Enabled)
require.NotNil(sensor)
// Test: EnableSensorsByName
err = repo.EnableSensorsByNames("Hello")
require.NoError(err)
sensor, err = repo.GetSensor(expectedSensors[2].ID)
require.True(sensor.Enabled)
require.NotNil(sensor)
// Test: UpdateSensors
expectedSensor := &types.Sensor{
ID: "8c74397f-8e60-4c9d-960d-3197747cef9a",
@ -327,10 +330,10 @@ func testBackend(t *testing.T, repo *repository.Repository) {
CreationDate: *timeNow(require),
}
err = repo.UpdateSensors(expectedSensor)
err = repo.UpdateSensors(ctx, expectedSensor)
require.NoError(err)
sensor, err = repo.GetSensor(expectedSensor.ID)
sensor, err = repo.GetSensorByID(ctx, expectedSensor.ID)
require.NoError(err)
require.NotNil(sensor)
// require.JSONEq(jsonEncoder(expectedSensor), jsonEncoder(sensor))
@ -349,10 +352,10 @@ func testBackend(t *testing.T, repo *repository.Repository) {
CreationDate: *timeNow(require),
}
err = repo.AddOrUpdateSensors(expectedSensor)
err = repo.AddOrUpdateSensors(ctx, expectedSensor)
require.NoError(err)
sensor, err = repo.GetSensor(expectedSensor.ID)
sensor, err = repo.GetSensorByID(ctx, expectedSensor.ID)
require.NoError(err)
require.NotEmpty(sensor)
require.Equal(expectedSensor.ID, sensor.ID)
@ -378,10 +381,10 @@ func testBackend(t *testing.T, repo *repository.Repository) {
CreationDate: *timeNow(require),
}
err = repo.AddOrUpdateSensors(expectedSensor)
err = repo.AddOrUpdateSensors(ctx, expectedSensor)
require.NoError(err)
sensor, err = repo.GetSensor(expectedSensor.ID)
sensor, err = repo.GetSensorByID(ctx, expectedSensor.ID)
require.NoError(err)
require.NotEmpty(sensor)
require.Equal(expectedSensor.ID, sensor.ID)
@ -427,7 +430,7 @@ func testBackend(t *testing.T, repo *repository.Repository) {
)
// Test: AddMeasuredValues
err = repo.AddMeasuredValues(expectedMeasuredValues...)
err = repo.AddMeasuredValues(ctx, expectedMeasuredValues...)
require.NoError(err)
for i := range expectedMeasuredValues {
@ -439,15 +442,15 @@ func testBackend(t *testing.T, repo *repository.Repository) {
switch expectedMeasuredValues[i].ValueType {
case types.Humidity:
measuredValue, err = repo.GetHumidity(expectedMeasuredValues[i].ID)
measuredValue, err = repo.GetHumidityByID(ctx, expectedMeasuredValues[i].ID)
require.NoError(err)
require.NotNil(measuredValue)
case types.Pressure:
measuredValue, err = repo.GetPressure(expectedMeasuredValues[i].ID)
measuredValue, err = repo.GetPressureByID(ctx, expectedMeasuredValues[i].ID)
require.NoError(err)
require.NotNil(measuredValue)
case types.Temperature:
measuredValue, err = repo.GetTemperature(expectedMeasuredValues[i].ID)
measuredValue, err = repo.GetTemperatureByID(ctx, expectedMeasuredValues[i].ID)
require.NoError(err)
require.NotNil(measuredValue)
}
@ -489,7 +492,7 @@ func testBackend(t *testing.T, repo *repository.Repository) {
},
}
err = repo.AddOrUpdateMeasuredValues(expectedMeasuredValues...)
err = repo.AddOrUpdateMeasuredValues(ctx, expectedMeasuredValues...)
require.NoError(err)
for i := range expectedMeasuredValues {
@ -501,15 +504,15 @@ func testBackend(t *testing.T, repo *repository.Repository) {
switch expectedMeasuredValues[i].ValueType {
case types.Humidity:
measuredValue, err = repo.GetHumidity(expectedMeasuredValues[i].ID)
measuredValue, err = repo.GetHumidityByID(ctx, expectedMeasuredValues[i].ID)
require.NoError(err)
require.NotNil(measuredValue)
case types.Pressure:
measuredValue, err = repo.GetPressure(expectedMeasuredValues[i].ID)
measuredValue, err = repo.GetPressureByID(ctx, expectedMeasuredValues[i].ID)
require.NoError(err)
require.NotNil(measuredValue)
case types.Temperature:
measuredValue, err = repo.GetTemperature(expectedMeasuredValues[i].ID)
measuredValue, err = repo.GetTemperatureByID(ctx, expectedMeasuredValues[i].ID)
require.NoError(err)
require.NotNil(measuredValue)
}

View File

@ -0,0 +1,2 @@
DELETE FROM devices
WHERE device_name = $1;

View File

@ -0,0 +1,2 @@
DELETE FROM sensors
WHERE sensor_name = $1;

View File

@ -0,0 +1,9 @@
SELECT
device_id,
device_name,
device_location,
creation_date,
update_date
FROM
devices
WHERE device_name = $1;

View File

@ -0,0 +1,18 @@
SELECT
sensor_id,
sensor_name,
sensor_location,
wire_id,
i2c_bus,
i2c_address,
gpio_number,
sensor_model,
sensor_enabled,
tick_duration,
device_id,
creation_date,
update_date
FROM
sensors
WHERE
device_id = $1;

View File

@ -0,0 +1,18 @@
SELECT
sensor_id,
sensor_name,
sensor_location,
wire_id,
i2c_bus,
i2c_address,
gpio_number,
sensor_model,
sensor_enabled,
tick_duration,
device_id,
creation_date,
update_date
FROM
sensors
WHERE
sensor_model = $1;

View File

@ -0,0 +1,18 @@
SELECT
sensor_id,
sensor_name,
sensor_location,
wire_id,
i2c_bus,
i2c_address,
gpio_number,
sensor_model,
sensor_enabled,
tick_duration,
device_id,
creation_date,
update_date
FROM
sensors
WHERE
sensor_name = $1;

File diff suppressed because it is too large Load Diff