fix(pkg/daemon): save measured values into postgres database if defined

This commit is contained in:
2019-09-04 13:37:50 +02:00
parent 6223f4e79b
commit 04bc3baffe
60 changed files with 680 additions and 378 deletions

39
pkg/storage/db/db.go Normal file
View File

@ -0,0 +1,39 @@
package db
import (
"database/sql"
"fmt"
"github.com/go-flucky/flucky/pkg/config"
"github.com/go-flucky/flucky/pkg/logger"
_ "github.com/lib/pq"
)
var (
flogger logger.Logger
)
func init() {
flogger = logger.NewSilentLogger()
}
func New(databaseSettings *config.DatabaseSettings) (Database, error) {
connStr := fmt.Sprintf("%v://%v:%v@%v:%v/%v?sslmode=disable", databaseSettings.Vendor.String(), databaseSettings.User, databaseSettings.Password, databaseSettings.Host, databaseSettings.Port, databaseSettings.Database)
newDBO, err := sql.Open(databaseSettings.Vendor.String(), connStr)
if err != nil {
return nil, err
}
switch databaseSettings.Vendor {
case config.VendorPostgreSQL:
return &Postgres{
dbo: newDBO,
}, nil
default:
return nil, fmt.Errorf("Unknown Database Type")
}
}
func SetLogger(logger logger.Logger) {
flogger = logger
}

17
pkg/storage/db/errors.go Normal file
View File

@ -0,0 +1,17 @@
package db
import (
"errors"
)
var (
errorBeginTransaction = errors.New("Can not start new transaction")
errorGetAsset = errors.New("Can not get asset from go-bindata")
errorRowNotFound = errors.New("Can not find row by given ID")
errorPrepareStatement = errors.New("Can not prepare sql statement")
errorRollbackTransaction = errors.New("Can not rollback transaction")
errorScanRow = errors.New("Can not scan row")
errorStatementExecute = errors.New("Can not execute statement")
errorStatementQuery = errors.New("Can not query statement")
errorUnknownMeasuredValueType = errors.New("Unknown measured value type")
)

View File

@ -0,0 +1,43 @@
package db
import (
"context"
"github.com/Masterminds/semver"
"github.com/go-flucky/flucky/pkg/types"
)
type Database interface {
// Close DB Connction
Close() error
// Schema
Schema(ctx context.Context, version *semver.Version) error
// Delete
DeleteDevices(ctx context.Context, devices []*types.Device) error
DeleteMeasuredValues(ctx context.Context, measuredValues []*types.MeasuredValue) error
DeleteSensors(ctx context.Context, sensors []*types.Sensor) error
// Insert
InsertDevices(ctx context.Context, devices []*types.Device) error
InsertMeasuredValues(ctx context.Context, measuredValues []*types.MeasuredValue) error
InsertSensors(ctx context.Context, sensors []*types.Sensor) error
// Select
SelectDeviceByID(ctx context.Context, id string) (*types.Device, error)
SelectHumidities(ctx context.Context) ([]*types.MeasuredValue, error)
SelectHumidityByID(ctx context.Context, id string) (*types.MeasuredValue, error)
SelectMeasuredValuesByIDAndType(ctx context.Context, id string, valueType types.MeasuredValueType) (*types.MeasuredValue, error)
SelectPressures(ctx context.Context) ([]*types.MeasuredValue, error)
SelectPressureByID(ctx context.Context, id string) (*types.MeasuredValue, error)
SelectSensorByID(ctx context.Context, id string) (*types.Sensor, error)
SelectTemperatures(ctx context.Context) ([]*types.MeasuredValue, error)
SelectTemperatureByID(ctx context.Context, id string) (*types.MeasuredValue, error)
// Update
UpdateDevices(ctx context.Context, devices []*types.Device) error
UpdateMeasuredValues(ctx context.Context, measuredValues []*types.MeasuredValue) error
UpdateSensors(ctx context.Context, sensots []*types.Sensor) error
}

632
pkg/storage/db/postgres.go Normal file
View File

@ -0,0 +1,632 @@
package db
import (
"context"
"database/sql"
"fmt"
"path/filepath"
"sort"
"strings"
"github.com/Masterminds/semver"
"github.com/go-flucky/flucky/pkg/types"
_ "github.com/lib/pq"
)
var (
postgresAssetPath = "pkg/storage/db/sql/psql"
)
type Postgres struct {
dbo *sql.DB
}
func (p *Postgres) Close() error {
return p.dbo.Close()
}
// Schema create or upgrade database schema to the version of the flucky binary
func (p *Postgres) Schema(ctx context.Context, version *semver.Version) error {
schemaFunc := func(ctx context.Context, fromVersion *semver.Version, toVersion *semver.Version) error {
assetPath := fmt.Sprintf("%v/schema", postgresAssetPath)
sqlAssetFiles, err := AssetDir(assetPath)
if err != nil {
return fmt.Errorf("Can not restore asset directory %v: %v", assetPath, err)
}
postgreSQLVersionChanges := make(map[*semver.Version]string, 0)
postgreSQLVersions := make([]*semver.Version, len(sqlAssetFiles))
for i, sqlAssetFile := range sqlAssetFiles {
fileSemVersion, err := semver.NewVersion(strings.ReplaceAll(sqlAssetFile, ".sql", ""))
if err != nil {
return fmt.Errorf("Can not create semantic version from file asset %v: %v", sqlAssetFile, err)
}
postgreSQLVersionChanges[fileSemVersion] = sqlAssetFile
postgreSQLVersions[i] = fileSemVersion
}
sort.Sort(semver.Collection(postgreSQLVersions))
for i, postgreSQLVersion := range postgreSQLVersions {
if fromVersion != nil {
if postgreSQLVersion.LessThan(fromVersion) || postgreSQLVersion.Equal(fromVersion) {
flogger.Debug("SKIP: PostgreSQL schema version '%v' is less or eqal then the local version changes '%v'", postgreSQLVersion.String(), fromVersion.String())
continue
}
}
asset := postgreSQLVersionChanges[postgreSQLVersion]
queryBytes, err := Asset(filepath.Join(assetPath, asset))
if err != nil {
return fmt.Errorf("Can not restore asset %v, %v", asset, err)
}
query := string(queryBytes)
if _, err := p.dbo.ExecContext(ctx, query); err != nil {
return fmt.Errorf("%v: %v", errorStatementExecute, err)
}
if i == 0 {
if err := p.InsertInfo(ctx, "version", postgreSQLVersion.String()); err != nil {
return fmt.Errorf("Can not insert version %v into info table: %v", postgreSQLVersion.String(), err)
}
} else {
if err := p.UpdateInfo(ctx, "version", postgreSQLVersion.String()); err != nil {
return fmt.Errorf("Can not update version %v into info table: %v", postgreSQLVersion.String(), err)
}
}
}
return nil
}
dbVersion, err := p.SelectInfo(ctx, "version")
if err != nil {
// can not select version from database, maybe the schema is not initialize
// create db schema for the current flucky version
return schemaFunc(ctx, nil, version)
} else {
fromVersion, err := semver.NewVersion(dbVersion)
if err != nil {
return fmt.Errorf("Can not create semantic version from database entry %v: %v", dbVersion, err)
}
return schemaFunc(ctx, fromVersion, version)
}
}
func (p *Postgres) DeleteDevices(ctx context.Context, devices []*types.Device) error {
asset := fmt.Sprintf("%v/deleteDevice.sql", postgresAssetPath)
queryBytes, err := Asset(asset)
if err != nil {
return fmt.Errorf("%v: %v", errorGetAsset, err)
}
query := string(queryBytes)
stmt, err := p.dbo.PrepareContext(ctx, query)
if err != nil {
return fmt.Errorf("%v: %v", errorPrepareStatement, err)
}
defer stmt.Close()
for _, device := range devices {
_, err := stmt.ExecContext(ctx, &device.DeviceID)
if err != nil {
return fmt.Errorf("%v: %v", errorStatementExecute, err)
}
}
return nil
}
func (p *Postgres) DeleteSensors(ctx context.Context, sensors []*types.Sensor) error {
asset := fmt.Sprintf("%v/deleteSensor.sql", postgresAssetPath)
queryBytes, err := Asset(asset)
if err != nil {
return fmt.Errorf("%v: %v", errorGetAsset, err)
}
query := string(queryBytes)
stmt, err := p.dbo.PrepareContext(ctx, query)
if err != nil {
return fmt.Errorf("%v: %v", errorPrepareStatement, err)
}
defer stmt.Close()
for _, sensor := range sensors {
_, err := stmt.ExecContext(ctx, &sensor.SensorID)
if err != nil {
return fmt.Errorf("%v: %v", errorStatementExecute, err)
}
}
return nil
}
func (p *Postgres) DeleteMeasuredValues(ctx context.Context, measuredValues []*types.MeasuredValue) error {
deleteMeasuredValue := func(ctx context.Context, query string, measuredValues []*types.MeasuredValue) error {
stmt, err := p.dbo.PrepareContext(ctx, query)
if err != nil {
return fmt.Errorf("%v: %v", errorPrepareStatement, err)
}
defer stmt.Close()
for _, measuredValue := range measuredValues {
_, err := stmt.ExecContext(ctx, &measuredValue.ID)
if err != nil {
return fmt.Errorf("%v: %v", errorStatementExecute, err)
}
}
return nil
}
sortedMeasuredValueTypes := make(map[types.MeasuredValueType][]*types.MeasuredValue)
for _, measuredValue := range measuredValues {
if _, ok := sortedMeasuredValueTypes[measuredValue.ValueType]; !ok {
sortedMeasuredValueTypes[measuredValue.ValueType] = make([]*types.MeasuredValue, 0)
}
sortedMeasuredValueTypes[measuredValue.ValueType] = append(sortedMeasuredValueTypes[measuredValue.ValueType], measuredValue)
}
assetFunc := func(queryFile string) (string, error) {
queryBytes, err := Asset(queryFile)
if err != nil {
return "", fmt.Errorf("%v: %v", errorGetAsset, err)
}
return string(queryBytes), nil
}
for measuredValueType, sortedMeasuredValues := range sortedMeasuredValueTypes {
switch measuredValueType {
case types.MeasuredValueTypeHumidity:
query, err := assetFunc(fmt.Sprintf("%v/deleteHumidity.sql", postgresAssetPath))
if err != nil {
return err
}
if err := deleteMeasuredValue(ctx, query, sortedMeasuredValues); err != nil {
return err
}
case types.MeasuredValueTypePressure:
query, err := assetFunc(fmt.Sprintf("%v/deletePressure.sql", postgresAssetPath))
if err != nil {
return err
}
if err := deleteMeasuredValue(ctx, query, sortedMeasuredValues); err != nil {
return err
}
case types.MeasuredValueTypeTemperature:
query, err := assetFunc(fmt.Sprintf("%v/deleteTemperature.sql", postgresAssetPath))
if err != nil {
return err
}
if err := deleteMeasuredValue(ctx, query, sortedMeasuredValues); err != nil {
return err
}
}
}
return nil
}
func (p *Postgres) InsertDevices(ctx context.Context, devices []*types.Device) error {
asset := fmt.Sprintf("%v/insertDevice.sql", postgresAssetPath)
queryBytes, err := Asset(asset)
if err != nil {
return fmt.Errorf("%v: %v", errorGetAsset, err)
}
query := string(queryBytes)
stmt, err := p.dbo.PrepareContext(ctx, query)
if err != nil {
return fmt.Errorf("%v: %v", errorPrepareStatement, err)
}
defer stmt.Close()
for _, device := range devices {
_, err := stmt.ExecContext(ctx, &device.DeviceID, &device.DeviceName, &device.DeviceLocation, &device.CreationDate)
if err != nil {
return fmt.Errorf("%v: %v", errorStatementExecute, err)
}
}
return nil
}
func (p *Postgres) InsertInfo(ctx context.Context, key string, value string) error {
asset := fmt.Sprintf("%v/insertInfo.sql", postgresAssetPath)
queryBytes, err := Asset(asset)
if err != nil {
return fmt.Errorf("%v: %v", errorGetAsset, err)
}
query := string(queryBytes)
stmt, err := p.dbo.PrepareContext(ctx, query)
if err != nil {
return fmt.Errorf("%v: %v", errorPrepareStatement, err)
}
defer stmt.Close()
_, err = stmt.ExecContext(ctx, key, value)
if err != nil {
return fmt.Errorf("%v: %v", errorStatementExecute, err)
}
return nil
}
func (p *Postgres) InsertMeasuredValues(ctx context.Context, measuredValues []*types.MeasuredValue) error {
sortedMeasuredValueTypes := make(map[types.MeasuredValueType][]*types.MeasuredValue)
for _, measuredValue := range measuredValues {
if _, ok := sortedMeasuredValueTypes[measuredValue.ValueType]; !ok {
sortedMeasuredValueTypes[measuredValue.ValueType] = make([]*types.MeasuredValue, 0)
}
sortedMeasuredValueTypes[measuredValue.ValueType] = append(sortedMeasuredValueTypes[measuredValue.ValueType], measuredValue)
}
for measuredValueType, sortedMeasuredValues := range sortedMeasuredValueTypes {
switch measuredValueType {
case types.MeasuredValueTypeHumidity:
if err := p.insertHumidity(ctx, sortedMeasuredValues); err != nil {
return err
}
case types.MeasuredValueTypePressure:
if err := p.insertPressure(ctx, sortedMeasuredValues); err != nil {
return err
}
case types.MeasuredValueTypeTemperature:
if err := p.insertTemperature(ctx, sortedMeasuredValues); err != nil {
return err
}
}
}
return nil
}
func (p *Postgres) insertHumidity(ctx context.Context, measuredValues []*types.MeasuredValue) error {
asset := fmt.Sprintf("%v/insertHumidity.sql", postgresAssetPath)
queryBytes, err := Asset(asset)
if err != nil {
return fmt.Errorf("%v: %v", errorGetAsset, err)
}
query := string(queryBytes)
stmt, err := p.dbo.PrepareContext(ctx, query)
if err != nil {
return fmt.Errorf("%v: %v", errorPrepareStatement, err)
}
defer stmt.Close()
for _, measuredValue := range measuredValues {
if measuredValue.ValueType != types.MeasuredValueTypeHumidity {
continue
}
_, err := stmt.ExecContext(ctx, &measuredValue.ID, &measuredValue.Value, &measuredValue.FromDate, &measuredValue.TillDate, &measuredValue.SensorID, &measuredValue.CreationDate, &measuredValue.UpdateDate)
if err != nil {
return fmt.Errorf("%v: %v", errorStatementExecute, err)
}
}
return nil
}
func (p *Postgres) insertPressure(ctx context.Context, measuredValues []*types.MeasuredValue) error {
asset := fmt.Sprintf("%v/insertPressure.sql", postgresAssetPath)
queryBytes, err := Asset(asset)
if err != nil {
return fmt.Errorf("%v: %v", errorGetAsset, err)
}
query := string(queryBytes)
stmt, err := p.dbo.PrepareContext(ctx, query)
if err != nil {
return fmt.Errorf("%v: %v", errorPrepareStatement, err)
}
defer stmt.Close()
for _, measuredValue := range measuredValues {
if measuredValue.ValueType != types.MeasuredValueTypePressure {
continue
}
_, err := stmt.ExecContext(ctx, &measuredValue.ID, &measuredValue.Value, &measuredValue.FromDate, &measuredValue.TillDate, &measuredValue.SensorID, &measuredValue.CreationDate, &measuredValue.UpdateDate)
if err != nil {
return fmt.Errorf("%v: %v", errorStatementExecute, err)
}
}
return nil
}
func (p *Postgres) insertTemperature(ctx context.Context, measuredValues []*types.MeasuredValue) error {
asset := fmt.Sprintf("%v/insertTemperature.sql", postgresAssetPath)
queryBytes, err := Asset(asset)
if err != nil {
return fmt.Errorf("%v: %v", errorGetAsset, err)
}
query := string(queryBytes)
stmt, err := p.dbo.PrepareContext(ctx, query)
if err != nil {
return fmt.Errorf("%v: %v", errorPrepareStatement, err)
}
defer stmt.Close()
for _, measuredValue := range measuredValues {
if measuredValue.ValueType != types.MeasuredValueTypeTemperature {
continue
}
_, err := stmt.ExecContext(ctx, &measuredValue.ID, &measuredValue.Value, &measuredValue.FromDate, &measuredValue.TillDate, &measuredValue.SensorID, &measuredValue.CreationDate, &measuredValue.UpdateDate)
if err != nil {
return fmt.Errorf("%v: %v", errorStatementExecute, err)
}
}
return nil
}
func (p *Postgres) InsertSensors(ctx context.Context, sensors []*types.Sensor) error {
asset := fmt.Sprintf("%v/insertSensor.sql", postgresAssetPath)
queryBytes, err := Asset(asset)
if err != nil {
return fmt.Errorf("%v: %v", errorGetAsset, err)
}
query := string(queryBytes)
stmt, err := p.dbo.PrepareContext(ctx, query)
if err != nil {
return fmt.Errorf("%v: %v", errorPrepareStatement, err)
}
defer stmt.Close()
for _, sensor := range sensors {
_, err := stmt.ExecContext(ctx, &sensor.SensorID, &sensor.SensorName, &sensor.SensorLocation, &sensor.WireID, &sensor.I2CBus, &sensor.I2CAddress, &sensor.GPIONumber, &sensor.SensorModel, &sensor.SensorEnabled, &sensor.DeviceID, &sensor.CreationDate)
if err != nil {
return fmt.Errorf("%v: %v", errorStatementExecute, err)
}
}
return nil
}
func (p *Postgres) SelectDeviceByID(ctx context.Context, id string) (*types.Device, error) {
asset := fmt.Sprintf("%v/selectDeviceByID.sql", postgresAssetPath)
queryBytes, err := Asset(asset)
if err != nil {
return nil, fmt.Errorf("%v: %v", errorGetAsset, err)
}
query := string(queryBytes)
stmt, err := p.dbo.PrepareContext(ctx, query)
if err != nil {
return nil, fmt.Errorf("%v: %v", errorPrepareStatement, err)
}
row := stmt.QueryRowContext(ctx, id)
if row == nil {
return nil, errorRowNotFound
}
device := new(types.Device)
err = row.Scan(&device.DeviceID, &device.DeviceName, &device.DeviceLocation, &device.CreationDate)
if err != nil {
return nil, fmt.Errorf("%v: %v", errorScanRow, err)
}
return device, nil
}
func (p *Postgres) SelectInfo(ctx context.Context, key string) (string, error) {
asset := fmt.Sprintf("%v/selectInfo.sql", postgresAssetPath)
queryBytes, err := Asset(asset)
if err != nil {
return "", fmt.Errorf("%v: %v", errorGetAsset, err)
}
query := string(queryBytes)
stmt, err := p.dbo.PrepareContext(ctx, query)
if err != nil {
return "", fmt.Errorf("%v: %v", errorPrepareStatement, err)
}
row := stmt.QueryRowContext(ctx, key)
if row == nil {
return "", errorRowNotFound
}
value := ""
err = row.Scan(&value)
if err != nil {
return "", fmt.Errorf("%v: %v", errorScanRow, err)
}
return value, nil
}
func (p *Postgres) SelectHumidities(ctx context.Context) ([]*types.MeasuredValue, error) {
queryFile := fmt.Sprintf("%v/selectHumidities.sql", postgresAssetPath)
measuredValues, err := p.selectMeasuredValues(ctx, types.MeasuredValueTypeHumidity, queryFile, nil)
if err != nil {
return nil, err
}
return measuredValues, nil
}
func (p *Postgres) SelectHumidityByID(ctx context.Context, id string) (*types.MeasuredValue, error) {
queryFile := fmt.Sprintf("%v/selectHumidityByID.sql", postgresAssetPath)
args := []interface{}{id}
measuredValues, err := p.selectMeasuredValues(ctx, types.MeasuredValueTypeHumidity, queryFile, args)
if err != nil {
return nil, err
}
if len(measuredValues) == 0 {
return nil, fmt.Errorf("%v: %v", errorRowNotFound, id)
}
return measuredValues[0], nil
}
func (p *Postgres) SelectMeasuredValuesByIDAndType(ctx context.Context, id string, valueType types.MeasuredValueType) (*types.MeasuredValue, error) {
switch valueType {
case types.MeasuredValueTypeHumidity:
return p.SelectHumidityByID(ctx, id)
case types.MeasuredValueTypePressure:
return p.SelectPressureByID(ctx, id)
case types.MeasuredValueTypeTemperature:
return p.SelectTemperatureByID(ctx, id)
default:
return nil, fmt.Errorf("%v: %v", errorUnknownMeasuredValueType, valueType)
}
}
func (p *Postgres) SelectPressures(ctx context.Context) ([]*types.MeasuredValue, error) {
queryFile := fmt.Sprintf("%v/selectPressures.sql", postgresAssetPath)
measuredValues, err := p.selectMeasuredValues(ctx, types.MeasuredValueTypePressure, queryFile, nil)
if err != nil {
return nil, err
}
return measuredValues, nil
}
func (p *Postgres) SelectPressureByID(ctx context.Context, id string) (*types.MeasuredValue, error) {
queryFile := fmt.Sprintf("%v/selectPressureByID.sql", postgresAssetPath)
args := []interface{}{id}
measuredValues, err := p.selectMeasuredValues(ctx, types.MeasuredValueTypePressure, queryFile, args)
if err != nil {
return nil, err
}
if len(measuredValues) == 0 {
return nil, fmt.Errorf("%v: %v", errorRowNotFound, id)
}
return measuredValues[0], nil
}
func (p *Postgres) SelectSensorByID(ctx context.Context, id string) (*types.Sensor, error) {
asset := fmt.Sprintf("%v/selectSensorByID.sql", postgresAssetPath)
queryBytes, err := Asset(asset)
if err != nil {
return nil, fmt.Errorf("%v: %v", errorGetAsset, err)
}
query := string(queryBytes)
stmt, err := p.dbo.PrepareContext(ctx, query)
if err != nil {
return nil, fmt.Errorf("%v: %v", errorPrepareStatement, err)
}
row := stmt.QueryRowContext(ctx, id)
if row == nil {
return nil, errorRowNotFound
}
sensor := new(types.Sensor)
err = row.Scan(&sensor.SensorID, &sensor.SensorName, &sensor.SensorLocation, &sensor.WireID, &sensor.I2CBus, &sensor.I2CAddress, &sensor.GPIONumber, &sensor.SensorModel, &sensor.SensorEnabled, &sensor.DeviceID, &sensor.CreationDate)
if err != nil {
return nil, fmt.Errorf("%v: %v", errorScanRow, err)
}
return sensor, nil
}
func (p *Postgres) SelectTemperatures(ctx context.Context) ([]*types.MeasuredValue, error) {
queryFile := fmt.Sprintf("%v/selectTemperatures.sql", postgresAssetPath)
measuredValues, err := p.selectMeasuredValues(ctx, types.MeasuredValueTypeTemperature, queryFile, nil)
if err != nil {
return nil, err
}
return measuredValues, nil
}
func (p *Postgres) SelectTemperatureByID(ctx context.Context, id string) (*types.MeasuredValue, error) {
queryFile := fmt.Sprintf("%v/selectTemperatureByID.sql", postgresAssetPath)
args := []interface{}{id}
measuredValues, err := p.selectMeasuredValues(ctx, types.MeasuredValueTypeTemperature, queryFile, args)
if err != nil {
return nil, err
}
if len(measuredValues) == 0 {
return nil, fmt.Errorf("%v: %v", errorRowNotFound, id)
}
return measuredValues[0], nil
}
func (p *Postgres) selectMeasuredValues(ctx context.Context, measuredValueType types.MeasuredValueType, queryFile string, queryArgs []interface{}) ([]*types.MeasuredValue, error) {
queryBytes, err := Asset(queryFile)
if err != nil {
return nil, fmt.Errorf("%v: %v", errorGetAsset, err)
}
query := string(queryBytes)
stmt, err := p.dbo.PrepareContext(ctx, query)
if err != nil {
return nil, fmt.Errorf("%v: %v", errorPrepareStatement, err)
}
rows, err := stmt.QueryContext(ctx, queryArgs...)
if err != nil {
return nil, fmt.Errorf("%v: %v", errorStatementQuery, err)
}
measuredValues := make([]*types.MeasuredValue, 0)
for rows.Next() {
measuredValue := new(types.MeasuredValue)
measuredValue.ValueType = measuredValueType
rows.Scan(&measuredValue.ID, &measuredValue.Value, &measuredValue.FromDate, &measuredValue.TillDate, &measuredValue.SensorID, &measuredValue.CreationDate, &measuredValue.UpdateDate)
measuredValues = append(measuredValues, measuredValue)
}
return measuredValues, nil
}
func (p *Postgres) UpdateDevices(ctx context.Context, devices []*types.Device) error {
return nil
}
func (p *Postgres) UpdateInfo(ctx context.Context, key string, value string) error {
asset := fmt.Sprintf("%v/updateInfo.sql", postgresAssetPath)
queryBytes, err := Asset(asset)
if err != nil {
return fmt.Errorf("%v: %v", errorGetAsset, err)
}
query := string(queryBytes)
stmt, err := p.dbo.PrepareContext(ctx, query)
if err != nil {
return fmt.Errorf("%v: %v", errorPrepareStatement, err)
}
defer stmt.Close()
_, err = stmt.ExecContext(ctx, key, value)
if err != nil {
return fmt.Errorf("%v: %v", errorStatementExecute, err)
}
return nil
}
func (p *Postgres) UpdateMeasuredValues(ctx context.Context, measuredValues []*types.MeasuredValue) error {
return nil
}
func (p *Postgres) UpdateSensors(ctx context.Context, sensots []*types.Sensor) error {
return nil
}

View File

@ -0,0 +1,300 @@
package db_test
import (
"context"
"strings"
"testing"
"github.com/Masterminds/semver"
"github.com/go-flucky/flucky/pkg/config"
"github.com/go-flucky/flucky/pkg/storage/db"
"github.com/go-flucky/flucky/pkg/types"
"github.com/go-flucky/flucky/test/goldenfiles"
"github.com/stretchr/testify/require"
)
type test struct {
Name string
Test func(*testing.T)
}
var (
database db.Database
postgresContainerImage string = "docker.io/postgres/postgres"
postgresSettings = &config.DatabaseSettings{
Vendor: config.VendorPostgreSQL,
Host: "localhost",
Port: "5432",
User: "postgres",
Password: "postgres",
Database: "postgres",
}
goldenDevicesFilePath string = "test/goldenfiles/json/goldenDevices.json"
goldenSensorsFilePath string = "test/goldenfiles/json/goldenSensors.json"
goldenMeasuredValuesFilePath string = "test/goldenfiles/json/goldenMeasuredValues.json"
goldenPressuresFilePath string = "test/goldenfiles/json/goldenPressures.json"
goldenHumiditiesFilePath string = "test/goldenfiles/json/goldenHumidities.json"
goldenTemperaturesFilePath string = "test/goldenfiles/json/goldenTemperatures.json"
goldenDevices []*types.Device
goldenSensors []*types.Sensor
goldenMeasuredValues []*types.MeasuredValue
goldenPressures []*types.MeasuredValue
goldenHumidites []*types.MeasuredValue
goldenTemperatures []*types.MeasuredValue
)
func load(t *testing.T) {
require := require.New(t)
d, err := goldenfiles.GetGoldenDevices(goldenDevicesFilePath)
require.NoError(err)
goldenDevices = d
s, err := goldenfiles.GetGoldenSensors(goldenSensorsFilePath)
require.NoError(err)
goldenSensors = s
hum, err := goldenfiles.GetGoldenMeasuredValues(goldenHumiditiesFilePath)
require.NoError(err)
goldenHumidites = hum
mv, err := goldenfiles.GetGoldenMeasuredValues(goldenMeasuredValuesFilePath)
require.NoError(err)
goldenMeasuredValues = mv
pres, err := goldenfiles.GetGoldenMeasuredValues(goldenPressuresFilePath)
require.NoError(err)
goldenPressures = pres
temp, err := goldenfiles.GetGoldenMeasuredValues(goldenTemperaturesFilePath)
require.NoError(err)
goldenTemperatures = temp
}
func TestPostgres(t *testing.T) {
require := require.New(t)
load(t)
db, err := db.New(postgresSettings)
database = db
require.Nil(err)
tests := []*test{
&test{
Name: "schema",
Test: testSchemaCreate,
},
&test{
Name: "insertDevices",
Test: testInsertDevices,
},
&test{
Name: "insertSensors",
Test: testInsertSensors,
},
&test{
Name: "insertHumidity",
Test: testInsertHumidity,
},
&test{
Name: "insertPressure",
Test: testInsertPressure,
},
&test{
Name: "insertTemperatures",
Test: testInsertTemperatures,
},
&test{
Name: "deleteHumidities",
Test: testDeleteHumidity,
},
&test{
Name: "deletePressures",
Test: testDeletePressures,
},
&test{
Name: "deleteTemperatures",
Test: testDeleteTemperatures,
},
&test{
Name: "insertMeasuredValues",
Test: testInsertMeasuredValues,
},
&test{
Name: "deleteMeasuredValues",
Test: testDeleteMeasuredValues,
},
&test{
Name: "deleteSensors",
Test: testDeleteSensors,
},
&test{
Name: "deleteDevices",
Test: testDeleteDevices,
},
}
for _, test := range tests {
t.Run(test.Name, test.Test)
}
}
func testSchemaCreate(t *testing.T) {
require := require.New(t)
homePath := "pkg/storage/db/sql/psql/schema"
sqlAssetFiles, err := db.AssetDir(homePath)
require.NoError(err)
ctx := context.Background()
for _, sqlAssetFile := range sqlAssetFiles {
fromVersion, err := semver.NewVersion(strings.ReplaceAll(sqlAssetFile, ".sql", ""))
require.NoError(err)
err = database.Schema(ctx, fromVersion)
require.NoError(err)
}
}
func testInsertDevices(t *testing.T) {
require := require.New(t)
ctx := context.Background()
err := database.InsertDevices(ctx, goldenDevices)
require.NoError(err)
for _, goldenDevice := range goldenDevices {
testDevice, err := database.SelectDeviceByID(ctx, goldenDevice.DeviceID)
require.NoError(err)
goldenfiles.CompareMeasuredValues(t, goldenDevice, testDevice)
}
}
func testInsertSensors(t *testing.T) {
require := require.New(t)
ctx := context.Background()
err := database.InsertSensors(ctx, goldenSensors)
require.NoError(err)
for _, goldenSensor := range goldenSensors {
testSensor, err := database.SelectSensorByID(ctx, goldenSensor.SensorID)
require.NoError(err)
goldenfiles.CompareMeasuredValues(t, goldenSensor, testSensor)
}
}
func testInsertHumidity(t *testing.T) {
require := require.New(t)
ctx := context.Background()
err := database.InsertMeasuredValues(ctx, goldenHumidites)
require.NoError(err)
for _, goldenHumidity := range goldenHumidites {
testHumidity, err := database.SelectHumidityByID(ctx, goldenHumidity.ID)
require.NoError(err)
goldenfiles.CompareMeasuredValues(t, []*types.MeasuredValue{testHumidity}, []*types.MeasuredValue{testHumidity})
}
}
func testInsertMeasuredValues(t *testing.T) {
require := require.New(t)
ctx := context.Background()
err := database.InsertMeasuredValues(ctx, goldenMeasuredValues)
require.NoError(err)
for _, goldenMeasuredValue := range goldenMeasuredValues {
testMeasuredValue, err := database.SelectMeasuredValuesByIDAndType(ctx, goldenMeasuredValue.ID, goldenMeasuredValue.ValueType)
require.NoError(err)
goldenfiles.CompareMeasuredValues(t, []*types.MeasuredValue{goldenMeasuredValue}, []*types.MeasuredValue{testMeasuredValue})
}
}
func testInsertPressure(t *testing.T) {
require := require.New(t)
ctx := context.Background()
err := database.InsertMeasuredValues(ctx, goldenPressures)
require.NoError(err)
for _, goldenPressure := range goldenPressures {
testPressure, err := database.SelectPressureByID(ctx, goldenPressure.ID)
require.NoError(err)
goldenfiles.CompareMeasuredValues(t, []*types.MeasuredValue{testPressure}, []*types.MeasuredValue{testPressure})
}
}
func testInsertTemperatures(t *testing.T) {
require := require.New(t)
ctx := context.Background()
err := database.InsertMeasuredValues(ctx, goldenTemperatures)
require.NoError(err)
for _, goldenTemperature := range goldenTemperatures {
testTemperature, err := database.SelectTemperatureByID(ctx, goldenTemperature.ID)
require.NoError(err)
goldenfiles.CompareMeasuredValues(t, []*types.MeasuredValue{goldenTemperature}, []*types.MeasuredValue{testTemperature})
}
}
func testDeleteDevices(t *testing.T) {
require := require.New(t)
ctx := context.Background()
err := database.DeleteDevices(ctx, goldenDevices)
require.NoError(err)
for _, goldenDevice := range goldenDevices {
_, err := database.SelectDeviceByID(ctx, goldenDevice.DeviceID)
require.Error(err)
}
}
func testDeleteSensors(t *testing.T) {
require := require.New(t)
ctx := context.Background()
err := database.DeleteSensors(ctx, goldenSensors)
require.NoError(err)
for _, goldenSensor := range goldenSensors {
_, err := database.SelectDeviceByID(ctx, goldenSensor.SensorID)
require.Error(err)
}
}
func testDeleteHumidity(t *testing.T) {
require := require.New(t)
ctx := context.Background()
err := database.DeleteMeasuredValues(ctx, goldenHumidites)
require.NoError(err)
for _, goldenHumidity := range goldenHumidites {
_, err := database.SelectHumidityByID(ctx, goldenHumidity.ID)
require.Error(err)
}
}
func testDeleteMeasuredValues(t *testing.T) {
require := require.New(t)
ctx := context.Background()
err := database.DeleteMeasuredValues(ctx, goldenMeasuredValues)
require.NoError(err)
for _, goldenMeasuredValue := range goldenMeasuredValues {
_, err := database.SelectPressureByID(ctx, goldenMeasuredValue.ID)
require.Error(err)
}
}
func testDeletePressures(t *testing.T) {
require := require.New(t)
ctx := context.Background()
err := database.DeleteMeasuredValues(ctx, goldenPressures)
require.NoError(err)
for _, goldenPressure := range goldenPressures {
_, err := database.SelectPressureByID(ctx, goldenPressure.ID)
require.Error(err)
}
}
func testDeleteTemperatures(t *testing.T) {
require := require.New(t)
ctx := context.Background()
err := database.DeleteMeasuredValues(ctx, goldenTemperatures)
require.NoError(err)
for _, goldenTemperature := range goldenTemperatures {
_, err := database.SelectTemperatureByID(ctx, goldenTemperature.ID)
require.Error(err)
}
}

View File

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

View File

@ -0,0 +1,2 @@
DELETE FROM humidities
WHERE humidity_id = $1;

View File

@ -0,0 +1,2 @@
DELETE FROM pressures
WHERE pressure_id = $1;

View File

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

View File

@ -0,0 +1,2 @@
DELETE FROM temperatures
WHERE temperature_id = $1;

View File

@ -0,0 +1,7 @@
INSERT INTO devices (
device_id,
device_name,
device_location,
creation_date
)
VALUES ($1, $2, $3, $4);

View File

@ -0,0 +1,10 @@
INSERT INTO humidities (
humidity_id,
humidity_value,
humidity_from_date,
humidity_till_date,
sensor_id,
creation_date,
update_date
)
VALUES ($1, $2, $3, $4, $5, $6, $7);

View File

@ -0,0 +1,2 @@
INSERT INTO info (key, value)
VALUES ($1, $2);

View File

@ -0,0 +1,10 @@
INSERT INTO pressures (
pressure_id,
pressure_value,
pressure_from_date,
pressure_till_date,
sensor_id,
creation_date,
update_date
)
VALUES ($1, $2, $3, $4, $5, $6, $7);

View File

@ -0,0 +1,14 @@
INSERT INTO sensors (
sensor_id,
sensor_name,
sensor_location,
wire_id,
i2c_bus,
i2c_address,
gpio_number,
sensor_model,
sensor_enabled,
device_id,
creation_date
)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11);

View File

@ -0,0 +1,10 @@
INSERT INTO temperatures (
temperature_id,
temperature_value,
temperature_from_date,
temperature_till_date,
sensor_id,
creation_date,
update_date
)
VALUES ($1, $2, $3, $4, $5, $6, $7);

View File

@ -0,0 +1,152 @@
DROP TABLE IF EXISTS devices CASCADE;
DROP TABLE IF EXISTS sensors CASCADE;
DROP TABLE IF EXISTS humidities CASCADE;
DROP TABLE IF EXISTS pressures CASCADE;
DROP TABLE IF EXISTS temperatures CASCADE;
DROP TABLE IF EXISTS info CASCADE;
-- +----------------------------------------+
-- | TABLES |
-- +----------------------------------------+
CREATE TABLE IF NOT EXISTS devices(
device_id CHAR(36) CONSTRAINT pk_devices PRIMARY KEY,
device_name VARCHAR(32) NOT NULL,
device_location VARCHAR(32),
device_last_contact TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL,
creation_date TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS sensors (
sensor_id CHAR(36) CONSTRAINT pk_sensors PRIMARY KEY,
sensor_name VARCHAR(32) NOT NULL,
sensor_location VARCHAR(32) NOT NULL,
wire_id VARCHAR(15),
i2c_bus VARCHAR(255),
i2c_address VARCHAR(12),
gpio_number VARCHAR(6),
sensor_model VARCHAR(16) NOT NULL,
sensor_enabled BOOLEAN DEFAULT TRUE NOT NULL,
sensor_last_contact TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL,
device_id CHAR(36),
creation_date TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL
);
CREATE TABLE IF NOT EXISTS humidities (
humidity_id CHAR(36) CONSTRAINT pk_humidities PRIMARY KEY,
humidity_value NUMERIC(9,3) NOT NULL,
humidity_from_date TIMESTAMP WITH TIME ZONE NOT NULL,
humidity_till_date TIMESTAMP WITH TIME ZONE,
sensor_id CHAR(36),
creation_date TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL,
update_date TIMESTAMP WITH TIME ZONE
);
CREATE TABLE IF NOT EXISTS pressures (
pressure_id CHAR(36) CONSTRAINT pk_pressures PRIMARY KEY,
pressure_value NUMERIC(10,3) NOT NULL,
pressure_from_date TIMESTAMP WITH TIME ZONE NOT NULL,
pressure_till_date TIMESTAMP WITH TIME ZONE,
sensor_id CHAR(36),
creation_date TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL,
update_date TIMESTAMP WITH TIME ZONE
);
CREATE TABLE IF NOT EXISTS temperatures (
temperature_id CHAR(36) CONSTRAINT pk_temperatures PRIMARY KEY,
temperature_value NUMERIC(5,3) NOT NULL,
temperature_from_date TIMESTAMP WITH TIME ZONE NOT NULL,
temperature_till_date TIMESTAMP WITH TIME ZONE,
sensor_id CHAR(36),
creation_date TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL,
update_date TIMESTAMP WITH TIME ZONE
);
CREATE TABLE IF NOT EXISTS info (
key VARCHAR(32) CONSTRAINT pk_info PRIMARY KEY,
value VARCHAR(32) NOT NULL
);
-- +----------------------------------------+
-- | FOREIGN-KEYS |
-- +----------------------------------------+
ALTER TABLE sensors
ADD FOREIGN KEY (device_id)
REFERENCES devices(device_id)
ON DELETE CASCADE
ON UPDATE CASCADE;
ALTER TABLE humidities
ADD FOREIGN KEY (sensor_id)
REFERENCES sensors(sensor_id)
ON DELETE CASCADE
ON UPDATE CASCADE;
ALTER TABLE pressures
ADD FOREIGN KEY (sensor_id)
REFERENCES sensors(sensor_id)
ON DELETE CASCADE
ON UPDATE CASCADE;
ALTER TABLE temperatures
ADD FOREIGN KEY (sensor_id)
REFERENCES sensors(sensor_id)
ON DELETE CASCADE
ON UPDATE CASCADE;
-- +----------------------------------------+
-- | Trigger-Functions |
-- +----------------------------------------+
CREATE OR REPLACE FUNCTION device_last_contact()
RETURNS trigger AS
$BODY$
BEGIN
UPDATE devices
SET device_last_contact = CURRENT_TIMESTAMP
WHERE device_id = NEW.device_id;
RETURN NEW;
END;
$BODY$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION sensor_last_contact()
RETURNS trigger AS
$BODY$
BEGIN
UPDATE sensors
SET sensor_last_contact = CURRENT_TIMESTAMP,
sensor_enabled = true
WHERE sensor_id = NEW.sensor_id;
RETURN NEW;
END;
$BODY$ LANGUAGE plpgsql;
-- +----------------------------------------+
-- | Trigger |
-- +----------------------------------------+
DROP TRIGGER IF EXISTS ai_humidities ON humidities;
DROP TRIGGER IF EXISTS ai_pressure ON pressures;
DROP TRIGGER IF EXISTS ai_temperatures ON temperatures;
CREATE TRIGGER au_sensors
AFTER UPDATE
ON sensors
FOR EACH ROW
EXECUTE PROCEDURE device_last_contact();
CREATE TRIGGER ai_humidities
AFTER INSERT
ON humidities
FOR EACH ROW
EXECUTE PROCEDURE sensor_last_contact();
CREATE TRIGGER ai_pressures
AFTER INSERT
ON pressures
FOR EACH ROW
EXECUTE PROCEDURE sensor_last_contact();
CREATE TRIGGER ai_temperatures
AFTER INSERT
ON temperatures
FOR EACH ROW
EXECUTE PROCEDURE sensor_last_contact();

View File

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

View File

@ -0,0 +1,10 @@
SELECT
humidity_id,
humidity_value,
humidity_from_date,
humidity_till_date,
sensor_id,
creation_date,
update_date
FROM
humidities;

View File

@ -0,0 +1,12 @@
SELECT
humidity_id,
humidity_value,
humidity_from_date,
humidity_till_date,
sensor_id,
creation_date,
update_date
FROM
humidities
WHERE
humidity_id = $1;

View File

@ -0,0 +1,3 @@
SELECT value
FROM info
WHERE key = $1;

View File

@ -0,0 +1,12 @@
SELECT
pressure_id,
pressure_value,
pressure_from_date,
pressure_till_date,
sensor_id,
creation_date,
update_date
FROM
pressures
WHERE
pressure_id = $1;

View File

@ -0,0 +1,10 @@
SELECT
pressure_id,
pressure_value,
pressure_from_date,
pressure_till_date,
sensor_id,
creation_date,
update_date
FROM
pressures;

View File

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

View File

@ -0,0 +1,12 @@
SELECT
temperature_id,
temperature_value,
temperature_from_date,
temperature_till_date,
sensor_id,
creation_date,
update_date
FROM
temperatures
WHERE
temperature_id = $1;

View File

@ -0,0 +1,10 @@
SELECT
temperature_id,
temperature_value,
temperature_from_date,
temperature_till_date,
sensor_id,
creation_date,
update_date
FROM
temperatures;

View File

@ -0,0 +1,3 @@
UPDATE info
SET value = $2
WHERE key = $1;