fix: add postgres backend

changes:
- Add postgres backend
- Modified or added table attributes.
  UpdateDate, ForeignKeys, Booleans
- Fix test for sqlite and postgres. Compare json instead the struct.
This commit is contained in:
2020-06-01 22:36:57 +02:00
parent a1c28a0a2e
commit 4931c63c10
46 changed files with 822 additions and 245 deletions

View File

@ -4,32 +4,27 @@ import (
"context"
"database/sql"
"fmt"
"path/filepath"
"time"
_ "github.com/lib/pq"
"github.com/volker-raschek/flucky/pkg/types"
"github.com/volker-raschek/go-logger/pkg/logger"
)
var (
postgresAssetPath = "pkg/storage/postgres"
)
// Postgres implementation
type Postgres struct {
dbo *sql.DB
flogger logger.Logger
queries map[string]string
}
// DeleteDevices from the database
func (postgres *Postgres) DeleteDevices(ctx context.Context, deviceIDs ...string) error {
asset := filepath.Join(postgresAssetPath, "deleteDevice.sql")
queryBytes, err := Asset(asset)
if err != nil {
return fmt.Errorf("Failed to load asset %v: %v", asset, err)
queryFile := "deleteDevice.sql"
query, present := postgres.queries[queryFile]
if !present {
return fmt.Errorf("SQLite-Backend: File %v not found", queryFile)
}
query := string(queryBytes)
tx, err := postgres.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: false})
if err != nil {
@ -55,12 +50,11 @@ func (postgres *Postgres) DeleteDevices(ctx context.Context, deviceIDs ...string
// DeleteSensors from the database
func (postgres *Postgres) DeleteSensors(ctx context.Context, sensorIDs ...string) error {
asset := filepath.Join(postgresAssetPath, "deleteSensor.sql")
queryBytes, err := Asset(asset)
if err != nil {
return fmt.Errorf("Failed to load asset %v: %v", asset, err)
queryFile := "deleteSensor.sql"
query, present := postgres.queries[queryFile]
if !present {
return fmt.Errorf("SQLite-Backend: File %v not found", queryFile)
}
query := string(queryBytes)
tx, err := postgres.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: false})
if err != nil {
@ -86,12 +80,11 @@ func (postgres *Postgres) DeleteSensors(ctx context.Context, sensorIDs ...string
// InsertDevices into the database
func (postgres *Postgres) InsertDevices(ctx context.Context, devices ...*types.Device) error {
asset := filepath.Join(postgresAssetPath, "insertDevice.sql")
queryBytes, err := Asset(asset)
if err != nil {
return fmt.Errorf("Failed to load asset %v: %v", asset, err)
queryFile := "insertDevice.sql"
query, present := postgres.queries[queryFile]
if !present {
return fmt.Errorf("SQLite-Backend: File %v not found", queryFile)
}
query := string(queryBytes)
tx, err := postgres.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: false})
if err != nil {
@ -105,7 +98,7 @@ func (postgres *Postgres) InsertDevices(ctx context.Context, devices ...*types.D
defer stmt.Close()
for _, device := range devices {
_, err = stmt.Exec(&device.ID, &device.Name, &device.Location, &device.CreationDate)
_, 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)
@ -132,12 +125,11 @@ func (postgres *Postgres) InsertMeasuredValues(ctx context.Context, measuredValu
}
// General insert function
insert := func(tx *sql.Tx, asset string, measuredValues []*types.MeasuredValue) error {
queryBytes, err := Asset(asset)
if err != nil {
return fmt.Errorf("Failed to load asset %v: %v", asset, err)
insert := func(tx *sql.Tx, queryFile string, measuredValues []*types.MeasuredValue) error {
query, present := postgres.queries[queryFile]
if !present {
return fmt.Errorf("SQLite-Backend: File %v not found", queryFile)
}
query := string(queryBytes)
stmt, err := tx.Prepare(query)
if err != nil {
@ -146,7 +138,15 @@ func (postgres *Postgres) InsertMeasuredValues(ctx context.Context, measuredValu
defer stmt.Close()
for _, measuredValue := range measuredValues {
_, err := stmt.Exec(&measuredValue.ID, &measuredValue.Value, &measuredValue.SensorID, &measuredValue.CreationDate)
_, 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)
}
@ -156,21 +156,21 @@ func (postgres *Postgres) InsertMeasuredValues(ctx context.Context, measuredValu
}
for measuredValueType, measuredValues := range splittedMeasuredValues {
var asset string
var queryFile string
switch measuredValueType {
case "humidity":
asset = filepath.Join(postgresAssetPath, "insertHumidity.sql")
queryFile = "insertHumidity.sql"
case "pressure":
asset = filepath.Join(postgresAssetPath, "insertPressure.sql")
queryFile = "insertPressure.sql"
case "temperature":
asset = filepath.Join(postgresAssetPath, "insertTemperature.sql")
queryFile = "insertTemperature.sql"
default:
tx.Rollback()
return fmt.Errorf("Measured value type %v not supported", measuredValueType)
}
err := insert(tx, asset, measuredValues)
err := insert(tx, queryFile, measuredValues)
if err != nil {
tx.Rollback()
return err
@ -182,12 +182,11 @@ func (postgres *Postgres) InsertMeasuredValues(ctx context.Context, measuredValu
// InsertSensors into the database
func (postgres *Postgres) InsertSensors(ctx context.Context, sensors ...*types.Sensor) error {
asset := filepath.Join(postgresAssetPath, "insertSensor.sql")
queryBytes, err := Asset(asset)
if err != nil {
return fmt.Errorf("Failed to load asset %v: %v", asset, err)
queryFile := "insertSensor.sql"
query, present := postgres.queries[queryFile]
if !present {
return fmt.Errorf("SQLite-Backend: File %v not found", queryFile)
}
query := string(queryBytes)
tx, err := postgres.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: false})
if err != nil {
@ -201,7 +200,21 @@ func (postgres *Postgres) InsertSensors(ctx context.Context, sensors ...*types.S
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.DeviceID, &sensor.CreationDate)
_, 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)
@ -211,75 +224,69 @@ func (postgres *Postgres) InsertSensors(ctx context.Context, sensors ...*types.S
return tx.Commit()
}
// Scheme creates all required tables if not exist
func (postgres *Postgres) Scheme(ctx context.Context) error {
for _, query := range []string{
postgres.queries["createTableDevices.sql"],
postgres.queries["createTableSensors.sql"],
postgres.queries["createTableHumidites.sql"],
postgres.queries["createTablePressures.sql"],
postgres.queries["createTableTemperatures.sql"],
} {
_, err := postgres.dbo.ExecContext(ctx, query)
if err != nil {
return err
}
}
return nil
}
// SelectDevice from database
func (postgres *Postgres) SelectDevice(ctx context.Context, id string) (*types.Device, error) {
asset := filepath.Join(postgresAssetPath, "selectDevice.sql")
queryBytes, err := Asset(asset)
if err != nil {
return nil, fmt.Errorf("Failed to load asset %v: %v", asset, err)
queryFile := "selectDevice.sql"
query, present := postgres.queries[queryFile]
if !present {
return nil, fmt.Errorf("SQLite-Backend: File %v not found", queryFile)
}
query := string(queryBytes)
tx, err := postgres.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: true})
if err != nil {
return nil, fmt.Errorf("Failed to begin new transaction: %v", err)
}
stmt, err := tx.Prepare(query)
devices, err := postgres.selectDevices(tx, query, id)
if err != nil {
return nil, fmt.Errorf("Failed to prepare statement: %v", err)
}
defer stmt.Close()
row := stmt.QueryRow(id)
device := new(types.Device)
err = row.Scan(&device.ID, &device.Name, &device.Location, &device.CreationDate)
if err != nil {
return nil, fmt.Errorf("Failed to scan row: %v", err)
return nil, err
}
err = tx.Commit()
if err != nil {
return nil, fmt.Errorf("Failed to commit transaction: %v", err)
return nil, err
}
return device, nil
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) {
asset := filepath.Join(postgresAssetPath, "selectDevices.sql")
queryBytes, err := Asset(asset)
if err != nil {
return nil, fmt.Errorf("Failed to load asset %v: %v", asset, err)
queryFile := "selectDevices.sql"
query, present := postgres.queries[queryFile]
if !present {
return nil, fmt.Errorf("SQLite-Backend: File %v not found", queryFile)
}
query := string(queryBytes)
tx, err := postgres.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: true})
if err != nil {
return nil, fmt.Errorf("Failed to begin new transaction: %v", err)
}
stmt, err := tx.Prepare(query)
devices, err := postgres.selectDevices(tx, query)
if err != nil {
return nil, fmt.Errorf("Failed to prepare statement: %v", err)
}
defer stmt.Close()
rows, err := stmt.Query()
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)
if err != nil {
return nil, fmt.Errorf("Failed to scan row: %v", err)
}
devices = append(devices, device)
return nil, err
}
err = tx.Commit()
@ -290,32 +297,218 @@ func (postgres *Postgres) SelectDevices(ctx context.Context) ([]*types.Device, e
return devices, nil
}
// SelectSensor from database
func (postgres *Postgres) SelectSensor(ctx context.Context, id string) (*types.Sensor, error) {
asset := filepath.Join(postgresAssetPath, "selectSensor.sql")
queryBytes, err := Asset(asset)
if err != nil {
return nil, fmt.Errorf("Failed to load asset %v: %v", asset, err)
}
query := string(queryBytes)
tx, err := postgres.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: true})
if err != nil {
return nil, fmt.Errorf("Failed to begin new transaction: %v", err)
}
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()
row := stmt.QueryRow(id)
sensor := new(types.Sensor)
err = row.Scan(&sensor.ID, &sensor.Name, &sensor.Location, &sensor.WireID, &sensor.I2CBus, &sensor.I2CAddress, &sensor.GPIONumber, &sensor.Model, &sensor.Enabled, &sensor.DeviceID, &sensor.CreationDate)
rows, err := stmt.Query(args...)
if err != nil {
return nil, fmt.Errorf("Failed to scan row: %v", err)
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("SQLite-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 = "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("SQLite-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, nil)
if err != nil {
return nil, err
}
err = tx.Commit()
if err != nil {
return nil, err
}
for _, measuredValue := range measuredValues {
measuredValue.ValueType = "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.SensorID,
&measuredValue.Date,
&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("SQLite-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 = "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("SQLite-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, nil)
if err != nil {
return nil, err
}
err = tx.Commit()
if err != nil {
return nil, err
}
for _, measuredValue := range measuredValues {
measuredValue.ValueType = "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("SQLite-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()
@ -323,42 +516,29 @@ func (postgres *Postgres) SelectSensor(ctx context.Context, id string) (*types.S
return nil, fmt.Errorf("Failed to commit transaction: %v", err)
}
return sensor, nil
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) {
asset := filepath.Join(postgresAssetPath, "selectSensors.sql")
queryBytes, err := Asset(asset)
if err != nil {
return nil, fmt.Errorf("Failed to load asset %v: %v", asset, err)
queryFile := "selectSensors.sql"
query, present := postgres.queries[queryFile]
if !present {
return nil, fmt.Errorf("SQLite-Backend: File %v not found", queryFile)
}
query := string(queryBytes)
tx, err := postgres.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: true})
if err != nil {
return nil, fmt.Errorf("Failed to begin new transaction: %v", err)
}
stmt, err := tx.Prepare(query)
sensors, err := postgres.selectSensors(tx, query)
if err != nil {
return nil, fmt.Errorf("Failed to prepare statement: %v", err)
}
defer stmt.Close()
rows, err := stmt.Query()
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.DeviceID, &sensor.CreationDate)
if err != nil {
return nil, fmt.Errorf("Failed to scan row: %v", err)
}
sensors = append(sensors, sensor)
return nil, err
}
err = tx.Commit()
@ -369,12 +549,193 @@ func (postgres *Postgres) SelectSensors(ctx context.Context) ([]*types.Sensor, e
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("SQLite-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 = "temperatures"
}
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("SQLite-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, nil)
if err != nil {
return nil, err
}
err = tx.Commit()
if err != nil {
return nil, err
}
for _, measuredValue := range measuredValues {
measuredValue.ValueType = "temperatures"
}
return measuredValues, nil
}
// UpdateDevices updates a device in the database
func (postgres *Postgres) UpdateDevices(ctx context.Context, devices ...*types.Device) error {
return nil
queryFile := "updateDevice.sql"
query, present := postgres.queries[queryFile]
if !present {
return fmt.Errorf("SQLite-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, sensor ...*types.Sensor) error {
return nil
func (postgres *Postgres) UpdateSensors(ctx context.Context, sensors ...*types.Sensor) error {
queryFile := "updateSensor.sql"
query, present := postgres.queries[queryFile]
if !present {
return fmt.Errorf("SQLite-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()
}