diff --git a/Makefile b/Makefile index 0790842..af0c511 100644 --- a/Makefile +++ b/Makefile @@ -105,7 +105,7 @@ all: ${EXECUTABLE_TARGETS} # darwin os bin/darwin/386/${EXECUTABLE}: bindata - CGO_ENABLED=0 \ + CGO_ENABLED=1 \ GOOS=darwin \ GOARCH=386 \ GOPROXY=${GOPROXY} \ @@ -113,7 +113,7 @@ bin/darwin/386/${EXECUTABLE}: bindata go build -ldflags "-X main.version=${VERSION:v%=%}" -o ${@} bin/darwin/amd64/${EXECUTABLE}: bindata - CGO_ENABLED=0 \ + CGO_ENABLED=1 \ GOOS=darwin \ GOARCH=amd64 \ GOPROXY=${GOPROXY} \ @@ -122,7 +122,7 @@ bin/darwin/amd64/${EXECUTABLE}: bindata # freebsd os bin/freebsd/amd64/${EXECUTABLE}: bindata - CGO_ENABLED=0 \ + CGO_ENABLED=1 \ GOOS=freebsd \ GOARCH=amd64 \ GOPROXY=${GOPROXY} \ @@ -131,7 +131,7 @@ bin/freebsd/amd64/${EXECUTABLE}: bindata # linux os bin/linux/386/${EXECUTABLE}: bindata - CGO_ENABLED=0 \ + CGO_ENABLED=1 \ GOOS=linux \ GOARCH=386 \ GOPROXY=${GOPROXY} \ @@ -139,7 +139,7 @@ bin/linux/386/${EXECUTABLE}: bindata go build -ldflags "-X main.version=${VERSION:v%=%}" -o ${@} bin/linux/amd64/${EXECUTABLE}: bindata - CGO_ENABLED=0 \ + CGO_ENABLED=1 \ GOOS=linux \ GOARCH=amd64 \ GOPROXY=${GOPROXY} \ @@ -147,7 +147,7 @@ bin/linux/amd64/${EXECUTABLE}: bindata go build -ldflags "-X main.version=${VERSION:v%=%}" -o ${@} bin/linux/arm/5/${EXECUTABLE}: bindata - CGO_ENABLED=0 \ + CGO_ENABLED=1 \ GOOS=linux \ GOARCH=arm \ GOARM=5 \ @@ -156,7 +156,7 @@ bin/linux/arm/5/${EXECUTABLE}: bindata go build -ldflags "-X main.version=${VERSION:v%=%}" -o ${@} bin/linux/arm/7/${EXECUTABLE}: bindata - CGO_ENABLED=0 \ + CGO_ENABLED=1 \ GOOS=linux \ GOARCH=arm \ GOARM=7 \ @@ -165,7 +165,7 @@ bin/linux/arm/7/${EXECUTABLE}: bindata go build -ldflags "-X main.version=${VERSION:v%=%}" -o ${@} bin/tmp/${EXECUTABLE}: bindata - CGO_ENABLED=0 \ + CGO_ENABLED=1 \ GOPROXY=${GOPROXY} \ GOPRIVATE=${GOPRIVATE} \ go build -ldflags "-X main.version=${VERSION:v%=%}" -o ${@} @@ -173,13 +173,13 @@ bin/tmp/${EXECUTABLE}: bindata # GO-BINDATA # ============================================================================== BINDATA_TARGETS := \ - pkg/storage/bindataSQL.go \ + pkg/repository/db/bindataSQL.go \ PHONY+=bindata bindata: clean ${BINDATA_TARGETS} -pkg/storage/bindataSQL.go: - go-bindata -pkg storage -o ./pkg/storage/bindataSQL.go pkg/storage/postgres/*** +pkg/repository/db/bindataSQL.go: + go-bindata -pkg db -o ./pkg/repository/db/bindataSQL.go pkg/repository/db/postgres/*** pkg/repository/db/sqlite3/*** # TEST # ============================================================================== diff --git a/cli/root.go b/cli/root.go index 3947cb3..fecf145 100644 --- a/cli/root.go +++ b/cli/root.go @@ -83,7 +83,7 @@ func preRunError(cmd *cobra.Command, args []string) error { Name: hostname, CreationDate: postgresTimeStamp, }, - StorageEndpoint: "file:/var/log/flucky/sqlite.db?cache=shared&mode=memory", + StorageEndpoint: "sqlite3:///var/log/flucky/sqlite.db?cache=shared&mode=memory&FKSupport=True", } err = config.Write(&cnf, configFile) diff --git a/cli/sensor/sensor.go b/cli/sensor/sensor.go index 326e5ff..18a753a 100644 --- a/cli/sensor/sensor.go +++ b/cli/sensor/sensor.go @@ -2,13 +2,16 @@ package sensor import ( "fmt" + "net/url" "os" uuid "github.com/satori/go.uuid" "github.com/spf13/cobra" "github.com/volker-raschek/flucky/pkg/cli" "github.com/volker-raschek/flucky/pkg/config" + "github.com/volker-raschek/flucky/pkg/repository" "github.com/volker-raschek/flucky/pkg/types" + "github.com/volker-raschek/go-logger/pkg/logger" ) // InitCmd initialize all sensor subcommands @@ -39,7 +42,7 @@ flucky sensor add --i2c-bus 1 --i2c-address 0x76 wetter-station BME280`, disableSensorCmd := &cobra.Command{ Use: "disable", Short: "Disable Sensor", - Args: cobra.ExactArgs(1), + Args: cobra.MinimumNArgs(1), Example: "flucky sensor disable outdoor", RunE: disableSensor, } @@ -48,7 +51,7 @@ flucky sensor add --i2c-bus 1 --i2c-address 0x76 wetter-station BME280`, Use: "enable", Short: "Enable Sensor", Example: "flucky sensor enable outdoor", - Args: cobra.ExactArgs(1), + Args: cobra.MinimumNArgs(1), RunE: enableSensor, } @@ -64,10 +67,9 @@ flucky sensor add --i2c-bus 1 --i2c-address 0x76 wetter-station BME280`, Short: "Remove Sensor", Aliases: []string{"rm"}, Example: "flucky sensor remove outdoor", - Args: cobra.ExactArgs(1), + Args: cobra.MinimumNArgs(1), RunE: removeSensor, } - removeSensorCmd.Flags().Bool("definitive", true, "Remove the sensor definitive from the backend") renameSensorCmd := &cobra.Command{ Use: "rename", @@ -162,17 +164,34 @@ func addSensor(cmd *cobra.Command, args []string) error { return err } + storageEndpoint, err := url.Parse(cnf.StorageEndpoint) + if err != nil { + return err + } + + // loglevel, err := cmd.Flags().GetString("loglevel") + // if err != nil { + // return fmt.Errorf("No loglevel defined") + // } + + flogger := logger.NewDefaultLogger(logger.LogLevelDebug) + + repo, err := repository.New(storageEndpoint, flogger) + if err != nil { + return err + } + // add sensor entry to list - err = cnf.AddSensor(sensor) + err = repo.AddSensors(sensor) if err != nil { return err } // save new configuration - err = config.Write(cnf, configFile) - if err != nil { - return err - } + // err = config.Write(cnf, configFile) + // if err != nil { + // return err + // } return nil } @@ -189,17 +208,24 @@ func disableSensor(cmd *cobra.Command, args []string) error { return err } - err = cnf.DisableSensor(args[0]) + storageEndpoint, err := url.Parse(cnf.StorageEndpoint) if err != nil { return err } - err = config.Write(cnf, configFile) + // loglevel, err := cmd.Flags().GetString("loglevel") + // if err != nil { + // return fmt.Errorf("No loglevel defined") + // } + + flogger := logger.NewDefaultLogger(logger.LogLevelDebug) + + repo, err := repository.New(storageEndpoint, flogger) if err != nil { return err } - return nil + return repo.DisableSensorsByNames(args...) } func enableSensor(cmd *cobra.Command, args []string) error { @@ -214,16 +240,24 @@ func enableSensor(cmd *cobra.Command, args []string) error { return err } - err = cnf.EnableSensor(args[0]) + storageEndpoint, err := url.Parse(cnf.StorageEndpoint) if err != nil { return err } - err = config.Write(cnf, configFile) + // loglevel, err := cmd.Flags().GetString("loglevel") + // if err != nil { + // return fmt.Errorf("No loglevel defined") + // } + + flogger := logger.NewDefaultLogger(logger.LogLevelDebug) + + repo, err := repository.New(storageEndpoint, flogger) if err != nil { return err } - return nil + + return repo.EnableSensorsByNames(args...) } func listSensors(cmd *cobra.Command, args []string) error { @@ -238,12 +272,30 @@ func listSensors(cmd *cobra.Command, args []string) error { return err } - err = cli.PrintSensors(cnf, os.Stdout) + storageEndpoint, err := url.Parse(cnf.StorageEndpoint) if err != nil { return err } - err = config.Write(cnf, configFile) + // loglevel, err := cmd.Flags().GetString("loglevel") + // if err != nil { + // return fmt.Errorf("No loglevel defined") + // } + + flogger := logger.NewDefaultLogger(logger.LogLevelDebug) + + repo, err := repository.New(storageEndpoint, flogger) + if err != nil { + return err + } + + // add sensor entry to list + sensors, err := repo.GetSensors() + if err != nil { + return err + } + + err = cli.PrintSensors(sensors, os.Stdout) if err != nil { return err } @@ -258,27 +310,29 @@ func removeSensor(cmd *cobra.Command, args []string) error { return fmt.Errorf("No config file defined") } - definitive, err := cmd.Flags().GetBool("definitive") - if err != nil { - return fmt.Errorf("No config file defined") - } - cnf, err := config.Read(configFile) if err != nil { return err } - err = cnf.RemoveSensor(args[0], definitive) + storageEndpoint, err := url.Parse(cnf.StorageEndpoint) if err != nil { return err } - err = config.Write(cnf, configFile) + // loglevel, err := cmd.Flags().GetString("loglevel") + // if err != nil { + // return fmt.Errorf("No loglevel defined") + // } + + flogger := logger.NewDefaultLogger(logger.LogLevelDebug) + + repo, err := repository.New(storageEndpoint, flogger) if err != nil { return err } - return nil + return repo.RemoveSensorsByNames(args...) } func renameSensor(cmd *cobra.Command, args []string) error { @@ -293,15 +347,22 @@ func renameSensor(cmd *cobra.Command, args []string) error { return err } - err = cnf.RenameSensor(args[0], args[1]) + storageEndpoint, err := url.Parse(cnf.StorageEndpoint) if err != nil { return err } - err = config.Write(cnf, configFile) + // loglevel, err := cmd.Flags().GetString("loglevel") + // if err != nil { + // return fmt.Errorf("No loglevel defined") + // } + + flogger := logger.NewDefaultLogger(logger.LogLevelDebug) + + repo, err := repository.New(storageEndpoint, flogger) if err != nil { return err } - return nil + return repo.RenameSensors(args[0], args[1]) } diff --git a/go.mod b/go.mod index a65f227..0d8202f 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,7 @@ require ( github.com/d2r2/go-logger v0.0.0-20181221090742-9998a510495e github.com/go-flucky/go-dht v0.1.1 github.com/lib/pq v1.4.0 + github.com/mattn/go-sqlite3 v1.10.0 github.com/satori/go.uuid v1.2.0 github.com/spf13/cobra v1.0.0 github.com/stretchr/testify v1.5.1 diff --git a/go.sum b/go.sum index 782fac9..3874c88 100644 --- a/go.sum +++ b/go.sum @@ -70,6 +70,11 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/lib/pq v1.4.0 h1:TmtCFbH+Aw0AixwyttznSMQDgbR5Yed/Gg6S8Funrhc= github.com/lib/pq v1.4.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/mattn/go-sqlite3 v1.10.0 h1:jbhqpg7tQe4SupckyijYiy0mJJ/pRyHvXf7JdWK860o= +github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +github.com/mattn/go-sqlite3 v1.13.0 h1:LnJI81JidiW9r7pS/hXe6cFeO5EXNq7KbfvoJLRI69c= +github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U= +github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= diff --git a/pkg/cli/cli.go b/pkg/cli/cli.go index 9c95e7c..8086ca9 100644 --- a/pkg/cli/cli.go +++ b/pkg/cli/cli.go @@ -5,18 +5,18 @@ import ( "io" "text/tabwriter" - "github.com/volker-raschek/flucky/pkg/config" + "github.com/volker-raschek/flucky/pkg/types" ) // PrintSensors displays a list with all configured sensors -func PrintSensors(cnf *config.Config, w io.Writer) error { +func PrintSensors(sensors []*types.Sensor, w io.Writer) error { // declar tabwriter tw := tabwriter.NewWriter(w, 0, 0, 3, ' ', 0) fmt.Fprint(tw, "name\tlocation\ttype\twire-id\ti2c-bus\ti2c-address\tgpio\ttick-duration\tenabled\n") - for _, sensor := range cnf.Sensors { + for _, sensor := range sensors { fmt.Fprintf(tw, "%v\t%v\t%v\t", sensor.Name, sensor.Location, sensor.Model) if sensor.WireID != nil { diff --git a/pkg/config/config.go b/pkg/config/config.go index 8c046ba..039fd7f 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -1,7 +1,6 @@ package config import ( - "context" "fmt" "os" "path/filepath" @@ -10,7 +9,6 @@ import ( "time" "github.com/volker-raschek/flucky/pkg/internal/format" - "github.com/volker-raschek/flucky/pkg/storage" uuid "github.com/satori/go.uuid" "github.com/volker-raschek/flucky/pkg/types" @@ -159,12 +157,7 @@ func (cnf *Config) GetSensorByID(id string) *types.Sensor { // RemoveSensor deletes a sensor by its name or its unique UUID, If definitive // is set to true, the sensor will not only be removed in the configuration file // but also in the backend. -func (cnf *Config) RemoveSensor(name string, definitive bool) error { - - backend, err := storage.New(cnf.StorageEndpoint, nil) - if err != nil { - return err - } +func (cnf *Config) RemoveSensor(name string) error { for i, sensor := range cnf.Sensors { // remove machted name @@ -172,13 +165,6 @@ func (cnf *Config) RemoveSensor(name string, definitive bool) error { sensor.Name == name { cnf.Sensors = append(cnf.Sensors[:i], cnf.Sensors[i+1:]...) - if definitive { - err = backend.RemoveSensorByName(context.Background(), name) - if err != nil { - return err - } - } - return nil } // remove machted uuid @@ -186,13 +172,6 @@ func (cnf *Config) RemoveSensor(name string, definitive bool) error { sensor.ID == name { cnf.Sensors = append(cnf.Sensors[:i], cnf.Sensors[i+1:]...) - if definitive { - err = backend.RemoveSensorByID(context.Background(), name) - if err != nil { - return err - } - } - return nil } } diff --git a/pkg/daemon/daemon.go b/pkg/daemon/daemon.go index 57e6567..862a6f8 100644 --- a/pkg/daemon/daemon.go +++ b/pkg/daemon/daemon.go @@ -2,13 +2,14 @@ package daemon import ( "context" + "net/url" "os" "os/signal" "syscall" "github.com/volker-raschek/flucky/pkg/config" + "github.com/volker-raschek/flucky/pkg/repository/db" "github.com/volker-raschek/flucky/pkg/sensor" - "github.com/volker-raschek/flucky/pkg/storage" "github.com/volker-raschek/flucky/pkg/types" "github.com/volker-raschek/go-logger/pkg/logger" ) @@ -31,7 +32,12 @@ func Start(cnf *config.Config, flogger logger.Logger) error { measuredValueChannel := make(chan *types.MeasuredValue, 0) // load storage endpoint - storageEndpoint, err := storage.New(cnf.StorageEndpoint, flogger) + storageEndpointURL, err := url.Parse(cnf.StorageEndpoint) + if err != nil { + return err + } + + backend, err := db.New(storageEndpointURL, flogger) if err != nil { return err } @@ -43,18 +49,18 @@ func Start(cnf *config.Config, flogger logger.Logger) error { parentCtx := context.Background() // Insert device if not exist - device, _ := storageEndpoint.SelectDevice(parentCtx, cnf.Device.ID) + device, _ := backend.SelectDevice(parentCtx, cnf.Device.ID) if device == nil { - if err := storageEndpoint.InsertDevice(parentCtx, cnf.Device); err != nil { + if err := backend.InsertDevices(parentCtx, cnf.Device); err != nil { return err } } // Insert sensors if not exist for _, cnfSensor := range cnf.Sensors { - sensor, _ := storageEndpoint.SelectSensor(parentCtx, cnfSensor.ID) + sensor, _ := backend.SelectSensor(parentCtx, cnfSensor.ID) if sensor == nil { - if err := storageEndpoint.InsertSensor(parentCtx, cnfSensor); err != nil { + if err := backend.InsertSensors(parentCtx, cnfSensor); err != nil { return err } } @@ -91,7 +97,7 @@ func Start(cnf *config.Config, flogger logger.Logger) error { if cap(measuredValues) == len(measuredValues) { flogger.Debug("Flush cache") - err := storageEndpoint.InsertMeasuredValues(ctx, measuredValues) + err := backend.InsertMeasuredValues(ctx, measuredValues...) if err != nil { flogger.Error("%v", err) } @@ -102,7 +108,7 @@ func Start(cnf *config.Config, flogger logger.Logger) error { cancel() close(measuredValueChannel) - err := storageEndpoint.InsertMeasuredValues(ctx, measuredValues) + err := backend.InsertMeasuredValues(ctx, measuredValues...) if err != nil { flogger.Error("%v", err) } @@ -110,6 +116,4 @@ func Start(cnf *config.Config, flogger logger.Logger) error { break } } - - return nil } diff --git a/pkg/repository/db/db.go b/pkg/repository/db/db.go new file mode 100644 index 0000000..65d7002 --- /dev/null +++ b/pkg/repository/db/db.go @@ -0,0 +1,118 @@ +package db + +import ( + "context" + "database/sql" + "fmt" + "net/url" + "os" + "path/filepath" + "strings" + + "github.com/volker-raschek/flucky/pkg/types" + "github.com/volker-raschek/go-logger/pkg/logger" +) + +// Database is a general interface for a storage endpoint +type Database interface { + DeleteDevices(ctx context.Context, deviceIDs ...string) error + DeleteSensors(ctx context.Context, sensorIDs ...string) error + InsertDevices(ctx context.Context, devices ...*types.Device) error + InsertMeasuredValues(ctx context.Context, measuredValues ...*types.MeasuredValue) error + InsertSensors(ctx context.Context, sensors ...*types.Sensor) error + Scheme(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 storage provider +func New(storageEndpoint *url.URL, flogger logger.Logger) (Database, error) { + + // Check of nil pointer + for _, parameter := range []interface{}{ + storageEndpoint, + flogger, + } { + if parameter == nil { + return nil, fmt.Errorf("Parameter does not be nil") + } + } + + // Load Queryfiles + queries := make(map[string]string, 0) + for _, asset := range AssetNames() { + if !strings.Contains(asset, storageEndpoint.Scheme) { + continue + } + + body, err := Asset(asset) + if err != nil { + return nil, err + } + + queryFile := filepath.Base(asset) + + queries[queryFile] = string(body) + } + + var ( + database Database + err error + ) + + switch storageEndpoint.Scheme { + // case "postgres": + // // postgres://[user]:[password]@[host]:[port]/[path]?[query] + // newDBO, err := sql.Open(storageEndpoint.Scheme, storageEndpoint.String()) + // if err != nil { + // return nil, err + // } + + // return &Postgres{ + // dbo: newDBO, + // flogger: flogger, + // }, nil + + case "sqlite3": + + if _, err := os.Stat(filepath.Dir(storageEndpoint.Path)); os.IsNotExist(err) { + err := os.MkdirAll(filepath.Dir(storageEndpoint.Path), 0755) + if err != nil { + return nil, err + } + } + + // sqlite3:///[path]?[query] flucky dsn + // file:///[path]?[query] sql-lib dsn + newDBO, err := sql.Open(storageEndpoint.Scheme, fmt.Sprintf("file://%v?%v", storageEndpoint.Path, storageEndpoint.RawQuery)) + if err != nil { + return nil, err + } + + database = &SQLite{ + dbo: newDBO, + flogger: flogger, + queries: queries, + } + default: + return nil, fmt.Errorf("Unsupported database scheme: %v", storageEndpoint.Scheme) + } + + err = database.Scheme(context.Background()) + if err != nil { + return nil, err + } + + return database, nil + +} diff --git a/pkg/storage/storage.go b/pkg/repository/db/postgres.go similarity index 58% rename from pkg/storage/storage.go rename to pkg/repository/db/postgres.go index 39644a2..25eef60 100644 --- a/pkg/storage/storage.go +++ b/pkg/repository/db/postgres.go @@ -1,28 +1,17 @@ -package storage +package db import ( "context" "database/sql" "fmt" - "net/url" "path/filepath" _ "github.com/lib/pq" + "github.com/volker-raschek/flucky/pkg/types" "github.com/volker-raschek/go-logger/pkg/logger" ) -// Storage is a general interface for a storage endpoint -type Storage interface { - InsertDevice(ctx context.Context, device *types.Device) error - InsertMeasuredValues(ctx context.Context, measuredValues []*types.MeasuredValue) error - InsertSensor(ctx context.Context, sensor *types.Sensor) error - RemoveSensorByID(ctx context.Context, sensorID string) error - RemoveSensorByName(ctx context.Context, sensorName string) error - SelectDevice(ctx context.Context, id string) (*types.Device, error) - SelectSensor(ctx context.Context, id string) (*types.Sensor, error) -} - var ( postgresAssetPath = "pkg/storage/postgres" ) @@ -33,8 +22,70 @@ type Postgres struct { flogger logger.Logger } -// InsertDevice into the database -func (postgres *Postgres) InsertDevice(ctx context.Context, device *types.Device) error { +// 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) + } + query := string(queryBytes) + + tx, err := postgres.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: false}) + if err != nil { + return fmt.Errorf("Failed to begin new transaction: %v", err) + } + + stmt, err := tx.Prepare(query) + if err != nil { + return fmt.Errorf("Failed to prepare statement: %v", err) + } + defer stmt.Close() + + 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 { + asset := filepath.Join(postgresAssetPath, "deleteSensor.sql") + queryBytes, err := Asset(asset) + if err != nil { + return fmt.Errorf("Failed to load asset %v: %v", asset, err) + } + query := string(queryBytes) + + tx, err := postgres.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: false}) + if err != nil { + return fmt.Errorf("Failed to begin new transaction: %v", err) + } + + stmt, err := tx.Prepare(query) + if err != nil { + return fmt.Errorf("Failed to prepare statement: %v", err) + } + defer stmt.Close() + + 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 { asset := filepath.Join(postgresAssetPath, "insertDevice.sql") queryBytes, err := Asset(asset) if err != nil { @@ -53,17 +104,19 @@ func (postgres *Postgres) InsertDevice(ctx context.Context, device *types.Device } defer stmt.Close() - _, err = stmt.Exec(&device.ID, &device.Name, &device.Location, &device.CreationDate) - if err != nil { - tx.Rollback() - return fmt.Errorf("Failed to execute statement: %v", err) + for _, device := range devices { + _, err = stmt.Exec(&device.ID, &device.Name, &device.Location, &device.CreationDate) + if err != nil { + tx.Rollback() + return fmt.Errorf("Failed to execute statement: %v", err) + } } return tx.Commit() } // InsertMeasuredValues into the database -func (postgres *Postgres) InsertMeasuredValues(ctx context.Context, measuredValues []*types.MeasuredValue) error { +func (postgres *Postgres) InsertMeasuredValues(ctx context.Context, measuredValues ...*types.MeasuredValue) error { splittedMeasuredValues := make(map[string][]*types.MeasuredValue, 0) for _, measuredValue := range measuredValues { @@ -127,8 +180,8 @@ func (postgres *Postgres) InsertMeasuredValues(ctx context.Context, measuredValu return tx.Commit() } -// InsertSensor into the database -func (postgres *Postgres) InsertSensor(ctx context.Context, sensor *types.Sensor) error { +// 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 { @@ -147,68 +200,12 @@ func (postgres *Postgres) InsertSensor(ctx context.Context, sensor *types.Sensor } defer stmt.Close() - _, err = stmt.Exec(&sensor.ID, &sensor.Name, &sensor.Location, &sensor.WireID, &sensor.I2CBus, &sensor.I2CAddress, &sensor.GPIONumber, &sensor.Model, &sensor.Enabled, &sensor.DeviceID, &sensor.CreationDate) - if err != nil { - tx.Rollback() - return fmt.Errorf("Failed to execute statement: %v", err) - } - - return tx.Commit() -} - -// RemoveSensorByID from the database -func (postgres *Postgres) RemoveSensorByID(ctx context.Context, sensorID string) error { - asset := filepath.Join(postgresAssetPath, "removeSensorByID.sql") - queryBytes, err := Asset(asset) - if err != nil { - return fmt.Errorf("Failed to load asset %v: %v", asset, err) - } - query := string(queryBytes) - - tx, err := postgres.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: false}) - if err != nil { - return fmt.Errorf("Failed to begin new transaction: %v", err) - } - - stmt, err := tx.Prepare(query) - if err != nil { - return fmt.Errorf("Failed to prepare statement: %v", err) - } - defer stmt.Close() - - _, err = stmt.Exec(sensorID) - if err != nil { - tx.Rollback() - return err - } - - return tx.Commit() -} - -// RemoveSensorByName from the database -func (postgres *Postgres) RemoveSensorByName(ctx context.Context, sensorID string) error { - asset := filepath.Join(postgresAssetPath, "removeSensorByName.sql") - queryBytes, err := Asset(asset) - if err != nil { - return fmt.Errorf("Failed to load asset %v: %v", asset, err) - } - query := string(queryBytes) - - tx, err := postgres.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: false}) - if err != nil { - return fmt.Errorf("Failed to begin new transaction: %v", err) - } - - stmt, err := tx.Prepare(query) - if err != nil { - return fmt.Errorf("Failed to prepare statement: %v", err) - } - defer stmt.Close() - - _, err = stmt.Exec(sensorID) - if err != nil { - tx.Rollback() - return err + 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) + if err != nil { + tx.Rollback() + return fmt.Errorf("Failed to execute statement: %v", err) + } } return tx.Commit() @@ -216,7 +213,7 @@ func (postgres *Postgres) RemoveSensorByName(ctx context.Context, sensorID strin // SelectDevice from database func (postgres *Postgres) SelectDevice(ctx context.Context, id string) (*types.Device, error) { - asset := filepath.Join(postgresAssetPath, "selectDeviceByID.sql") + 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) @@ -242,12 +239,60 @@ func (postgres *Postgres) SelectDevice(ctx context.Context, id string) (*types.D return nil, fmt.Errorf("Failed to scan row: %v", err) } + err = tx.Commit() + if err != nil { + return nil, fmt.Errorf("Failed to commit transaction: %v", err) + } + return device, 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) + } + query := string(queryBytes) + + tx, err := postgres.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: true}) + if err != nil { + return nil, fmt.Errorf("Failed to begin new transaction: %v", err) + } + + stmt, err := tx.Prepare(query) + if err != nil { + return nil, fmt.Errorf("Failed to prepare statement: %v", err) + } + defer stmt.Close() + + 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) + } + + err = tx.Commit() + if err != nil { + return nil, fmt.Errorf("Failed to commit transaction: %v", err) + } + + return devices, nil +} + // SelectSensor from database func (postgres *Postgres) SelectSensor(ctx context.Context, id string) (*types.Sensor, error) { - asset := filepath.Join(postgresAssetPath, "selectSensorByID.sql") + 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) @@ -273,28 +318,63 @@ func (postgres *Postgres) SelectSensor(ctx context.Context, id string) (*types.S return nil, fmt.Errorf("Failed to scan row: %v", err) } + err = tx.Commit() + if err != nil { + return nil, fmt.Errorf("Failed to commit transaction: %v", err) + } + return sensor, nil } -// New returns a new storage provider -func New(storageEndpoint string, flogger logger.Logger) (Storage, error) { - storageEndpointURL, err := url.Parse(storageEndpoint) +// 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, err + 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) } - switch storageEndpointURL.Scheme { - case "postgres": - newDBO, err := sql.Open(storageEndpointURL.Scheme, storageEndpointURL.String()) + 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() + 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, err + return nil, fmt.Errorf("Failed to scan row: %v", err) } - - return &Postgres{ - dbo: newDBO, - flogger: flogger, - }, nil - default: - return nil, fmt.Errorf("Unsupported database scheme: %v", storageEndpointURL.Scheme) + sensors = append(sensors, sensor) } + + err = tx.Commit() + if err != nil { + return nil, fmt.Errorf("Failed to commit transaction: %v", err) + } + + return sensors, nil +} + +// UpdateDevices updates a device in the database +func (postgres *Postgres) UpdateDevices(ctx context.Context, devices ...*types.Device) error { + return nil +} + +// UpdateSensors updates a sensor in the database +func (postgres *Postgres) UpdateSensors(ctx context.Context, sensor ...*types.Sensor) error { + return nil } diff --git a/pkg/repository/db/postgres/deleteDevice.sql b/pkg/repository/db/postgres/deleteDevice.sql new file mode 100644 index 0000000..22ad7fa --- /dev/null +++ b/pkg/repository/db/postgres/deleteDevice.sql @@ -0,0 +1,2 @@ +DELETE FROM sensors +WHERE device_id = $1; \ No newline at end of file diff --git a/pkg/storage/postgres/removeSensorByID.sql b/pkg/repository/db/postgres/deleteSensor.sql similarity index 100% rename from pkg/storage/postgres/removeSensorByID.sql rename to pkg/repository/db/postgres/deleteSensor.sql diff --git a/pkg/storage/postgres/insertDevice.sql b/pkg/repository/db/postgres/insertDevice.sql similarity index 100% rename from pkg/storage/postgres/insertDevice.sql rename to pkg/repository/db/postgres/insertDevice.sql diff --git a/pkg/storage/postgres/insertHumidity.sql b/pkg/repository/db/postgres/insertHumidity.sql similarity index 100% rename from pkg/storage/postgres/insertHumidity.sql rename to pkg/repository/db/postgres/insertHumidity.sql diff --git a/pkg/storage/postgres/insertPressure.sql b/pkg/repository/db/postgres/insertPressure.sql similarity index 100% rename from pkg/storage/postgres/insertPressure.sql rename to pkg/repository/db/postgres/insertPressure.sql diff --git a/pkg/storage/postgres/insertSensor.sql b/pkg/repository/db/postgres/insertSensor.sql similarity index 100% rename from pkg/storage/postgres/insertSensor.sql rename to pkg/repository/db/postgres/insertSensor.sql diff --git a/pkg/storage/postgres/insertTemperature.sql b/pkg/repository/db/postgres/insertTemperature.sql similarity index 100% rename from pkg/storage/postgres/insertTemperature.sql rename to pkg/repository/db/postgres/insertTemperature.sql diff --git a/pkg/storage/postgres/selectDeviceByID.sql b/pkg/repository/db/postgres/selectDevice.sql similarity index 100% rename from pkg/storage/postgres/selectDeviceByID.sql rename to pkg/repository/db/postgres/selectDevice.sql diff --git a/pkg/repository/db/postgres/selectDevices.sql b/pkg/repository/db/postgres/selectDevices.sql new file mode 100644 index 0000000..81f256e --- /dev/null +++ b/pkg/repository/db/postgres/selectDevices.sql @@ -0,0 +1,7 @@ +SELECT + device_id, + device_name, + device_location, + creation_date +FROM + devices; \ No newline at end of file diff --git a/pkg/storage/postgres/selectSensorByID.sql b/pkg/repository/db/postgres/selectSensor.sql similarity index 100% rename from pkg/storage/postgres/selectSensorByID.sql rename to pkg/repository/db/postgres/selectSensor.sql diff --git a/pkg/repository/db/postgres/selectSensors.sql b/pkg/repository/db/postgres/selectSensors.sql new file mode 100644 index 0000000..0c85bc4 --- /dev/null +++ b/pkg/repository/db/postgres/selectSensors.sql @@ -0,0 +1,14 @@ +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; \ No newline at end of file diff --git a/pkg/repository/db/postgres/updateDevice.sql b/pkg/repository/db/postgres/updateDevice.sql new file mode 100644 index 0000000..997967d --- /dev/null +++ b/pkg/repository/db/postgres/updateDevice.sql @@ -0,0 +1,8 @@ +UPDATE device +SET + device_id = $1, + device_name = $2, + device_location = $3, + creation_date = $4 +WHERE + device_id = $5, \ No newline at end of file diff --git a/pkg/repository/db/postgres/updateSensor.sql b/pkg/repository/db/postgres/updateSensor.sql new file mode 100644 index 0000000..9173cf2 --- /dev/null +++ b/pkg/repository/db/postgres/updateSensor.sql @@ -0,0 +1,15 @@ +UPDATE sensors +SET + sensor_id = $1, + sensor_name = $2, + sensor_location = $3, + wire_id = $4, + i2c_bus = $5, + i2c_address = $6, + gpio_number = $7, + sensor_model = $8, + sensor_enabled = $9, + device_id = $10, + creation_date = $11 +WHERE + sensor_id = $12; \ No newline at end of file diff --git a/pkg/repository/db/sqlite.go b/pkg/repository/db/sqlite.go new file mode 100644 index 0000000..5c42cd0 --- /dev/null +++ b/pkg/repository/db/sqlite.go @@ -0,0 +1,723 @@ +package db + +import ( + "context" + "database/sql" + "fmt" + + _ "github.com/mattn/go-sqlite3" + "github.com/volker-raschek/flucky/pkg/types" + "github.com/volker-raschek/go-logger/pkg/logger" +) + +var ( + sqliteAssetPath = "pkg/storage/sqlite" +) + +// SQLite implementation +type SQLite struct { + dbo *sql.DB + flogger logger.Logger + queries map[string]string +} + +// 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() +} + +// 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) + 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[string][]*types.MeasuredValue, 0) + + for _, measuredValue := range measuredValues { + if _, ok := splittedMeasuredValues[measuredValue.ValueType]; !ok { + splittedMeasuredValues[measuredValue.ValueType] = make([]*types.MeasuredValue, 0) + } + splittedMeasuredValues[measuredValue.ValueType] = append(splittedMeasuredValues[measuredValue.ValueType], measuredValue) + } + + tx, err := 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.FromDate, + &measuredValue.TillDate, + &measuredValue.SensorID, + &measuredValue.CreationDate, + &measuredValue.UpdateDate, + ) + + if err != nil { + return fmt.Errorf("Failed to execute statement: %v", err) + } + } + + return nil + } + + for measuredValueType, measuredValues := range splittedMeasuredValues { + var queryFile string + + switch measuredValueType { + case "humidity": + queryFile = "insertHumidity.sql" + case "pressure": + queryFile = "insertPressure.sql" + case "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() +} + +// 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.DeviceID, + &sensor.CreationDate, + ) + if err != nil { + tx.Rollback() + return fmt.Errorf("Failed to execute statement: %v", err) + } + } + + return tx.Commit() +} + +// Scheme creates all required tables if not exist +func (sqlite *SQLite) Scheme(ctx context.Context) error { + for _, query := range []string{ + sqlite.queries["createTableDevices.sql"], + sqlite.queries["createTableSensors.sql"], + sqlite.queries["createTableHumidites.sql"], + sqlite.queries["createTablePressures.sql"], + sqlite.queries["createTableTemperatures.sql"], + } { + _, err := sqlite.dbo.ExecContext(ctx, query) + if err != nil { + return err + } + } + return nil +} + +// 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 + } + + 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, nil) + 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() + 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 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 = "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, 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 (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 + } + + measuredValues := make([]*types.MeasuredValue, 0) + for rows.Next() { + measuredValues := new(types.MeasuredValue) + err := rows.Scan( + &measuredValues.ID, + &measuredValues.Value, + &measuredValues.FromDate, + &measuredValues.TillDate, + &measuredValues.SensorID, + &measuredValues.CreationDate, + &measuredValues.UpdateDate, + ) + + if err != nil { + tx.Rollback() + return nil, err + } + } + + 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 = "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, 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 (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, nil) + if err != nil { + return nil, err + } + + err = tx.Commit() + if err != nil { + return nil, fmt.Errorf("Failed to commit transaction: %v", err) + } + + 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, nil) + 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() + 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 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 = "temperatures" + } + + 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, 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 (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.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.DeviceID, + &sensor.CreationDate, + &sensor.ID, + ) + if err != nil { + tx.Rollback() + return err + } + } + + return tx.Commit() +} diff --git a/pkg/repository/db/sqlite3/createTableDevices.sql b/pkg/repository/db/sqlite3/createTableDevices.sql new file mode 100644 index 0000000..88b48ee --- /dev/null +++ b/pkg/repository/db/sqlite3/createTableDevices.sql @@ -0,0 +1,6 @@ +CREATE TABLE IF NOT EXISTS devices ( + device_id CHAR(37) PRIMARY KEY, + device_name VARCHAR(64) NOT NULL, + device_location VARCHAR(64), + creation_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL +); \ No newline at end of file diff --git a/pkg/repository/db/sqlite3/createTableHumidities.sql b/pkg/repository/db/sqlite3/createTableHumidities.sql new file mode 100644 index 0000000..a0cd6b4 --- /dev/null +++ b/pkg/repository/db/sqlite3/createTableHumidities.sql @@ -0,0 +1,10 @@ +CREATE TABLE IF NOT EXISTS humidities ( + humidity_id CHAR(37) PRIMARY KEY, + humidity_value NUMERIC(10,3) NOT NULL, + humidity_from_date TIMESTAMP NOT NULL, + humidity_till_date TIMESTAMP, + sensor_id CHAR(37) NOT NULL, + creation_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, + update_date TIMESTAMP, + FOREIGN KEY(sensor_id) REFERENCES sensors(sensor_id) +); \ No newline at end of file diff --git a/pkg/repository/db/sqlite3/createTablePressures.sql b/pkg/repository/db/sqlite3/createTablePressures.sql new file mode 100644 index 0000000..139567a --- /dev/null +++ b/pkg/repository/db/sqlite3/createTablePressures.sql @@ -0,0 +1,10 @@ +CREATE TABLE IF NOT EXISTS pressures ( + pressure_id CHAR(37) PRIMARY KEY, + pressure_value NUMERIC(10,3) NOT NULL, + pressure_from_date TIMESTAMP NOT NULL, + pressure_till_date TIMESTAMP, + sensor_id CHAR(37) NOT NULL, + creation_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, + update_date TIMESTAMP, + FOREIGN KEY(sensor_id) REFERENCES sensors(sensor_id) +); \ No newline at end of file diff --git a/pkg/repository/db/sqlite3/createTableSensors.sql b/pkg/repository/db/sqlite3/createTableSensors.sql new file mode 100644 index 0000000..4c13843 --- /dev/null +++ b/pkg/repository/db/sqlite3/createTableSensors.sql @@ -0,0 +1,14 @@ +CREATE TABLE IF NOT EXISTS sensors ( + sensor_id CHAR(37) PRIMARY KEY, + sensor_name VARCHAR(64) NOT NULL, + sensor_location VARCHAR(64), + wire_id VARCHAR(64), + i2c_bus VARCHAR(255), + i2c_address VARCHAR(12), + gpio_number VARCHAR(6), + sensor_model VARCHAR(16) NOT NULL, + sensor_enabled INTEGER(1) DEFAULT 1 NOT NULL, + device_id CHAR(37) NOT NULL, + creation_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY(device_id) REFERENCES devices(device_id) +); \ No newline at end of file diff --git a/pkg/repository/db/sqlite3/createTableTemperatures.sql b/pkg/repository/db/sqlite3/createTableTemperatures.sql new file mode 100644 index 0000000..1588976 --- /dev/null +++ b/pkg/repository/db/sqlite3/createTableTemperatures.sql @@ -0,0 +1,10 @@ +CREATE TABLE IF NOT EXISTS temperatures ( + temperature_id CHAR(37) PRIMARY KEY, + temperature_value NUMERIC(10,3) NOT NULL, + temperature_from_date TIMESTAMP NOT NULL, + temperature_till_date TIMESTAMP, + sensor_id CHAR(37) NOT NULL, + creation_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, + update_date TIMESTAMP, + FOREIGN KEY(sensor_id) REFERENCES sensors(sensor_id) +); \ No newline at end of file diff --git a/pkg/repository/db/sqlite3/deleteDevice.sql b/pkg/repository/db/sqlite3/deleteDevice.sql new file mode 100644 index 0000000..22ad7fa --- /dev/null +++ b/pkg/repository/db/sqlite3/deleteDevice.sql @@ -0,0 +1,2 @@ +DELETE FROM sensors +WHERE device_id = $1; \ No newline at end of file diff --git a/pkg/repository/db/sqlite3/deleteSensor.sql b/pkg/repository/db/sqlite3/deleteSensor.sql new file mode 100644 index 0000000..dba0c3f --- /dev/null +++ b/pkg/repository/db/sqlite3/deleteSensor.sql @@ -0,0 +1,2 @@ +DELETE FROM sensors +WHERE sensor_id = $1; \ No newline at end of file diff --git a/pkg/repository/db/sqlite3/insertDevice.sql b/pkg/repository/db/sqlite3/insertDevice.sql new file mode 100644 index 0000000..d197f24 --- /dev/null +++ b/pkg/repository/db/sqlite3/insertDevice.sql @@ -0,0 +1,7 @@ +INSERT INTO devices ( + device_id, + device_name, + device_location, + creation_date +) +VALUES ($1, $2, $3, $4); \ No newline at end of file diff --git a/pkg/repository/db/sqlite3/insertHumidity.sql b/pkg/repository/db/sqlite3/insertHumidity.sql new file mode 100644 index 0000000..e7a7041 --- /dev/null +++ b/pkg/repository/db/sqlite3/insertHumidity.sql @@ -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); \ No newline at end of file diff --git a/pkg/repository/db/sqlite3/insertPressure.sql b/pkg/repository/db/sqlite3/insertPressure.sql new file mode 100644 index 0000000..8add4b5 --- /dev/null +++ b/pkg/repository/db/sqlite3/insertPressure.sql @@ -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); \ No newline at end of file diff --git a/pkg/repository/db/sqlite3/insertSensor.sql b/pkg/repository/db/sqlite3/insertSensor.sql new file mode 100644 index 0000000..2aca33a --- /dev/null +++ b/pkg/repository/db/sqlite3/insertSensor.sql @@ -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); \ No newline at end of file diff --git a/pkg/repository/db/sqlite3/insertTemperature.sql b/pkg/repository/db/sqlite3/insertTemperature.sql new file mode 100644 index 0000000..520555a --- /dev/null +++ b/pkg/repository/db/sqlite3/insertTemperature.sql @@ -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); \ No newline at end of file diff --git a/pkg/repository/db/sqlite3/selectDevice.sql b/pkg/repository/db/sqlite3/selectDevice.sql new file mode 100644 index 0000000..a56b927 --- /dev/null +++ b/pkg/repository/db/sqlite3/selectDevice.sql @@ -0,0 +1,8 @@ +SELECT + device_id, + device_name, + device_location, + creation_date +FROM + devices +WHERE device_id = $1; \ No newline at end of file diff --git a/pkg/repository/db/sqlite3/selectDevices.sql b/pkg/repository/db/sqlite3/selectDevices.sql new file mode 100644 index 0000000..81f256e --- /dev/null +++ b/pkg/repository/db/sqlite3/selectDevices.sql @@ -0,0 +1,7 @@ +SELECT + device_id, + device_name, + device_location, + creation_date +FROM + devices; \ No newline at end of file diff --git a/pkg/repository/db/sqlite3/selectHumidities.sql b/pkg/repository/db/sqlite3/selectHumidities.sql new file mode 100644 index 0000000..891dbd7 --- /dev/null +++ b/pkg/repository/db/sqlite3/selectHumidities.sql @@ -0,0 +1,10 @@ +SELECT + humidity_id, + humidity_value, + humidity_from_date, + humidity_till_date, + sensor_id, + creation_date, + update_date +FROM + humidities \ No newline at end of file diff --git a/pkg/repository/db/sqlite3/selectHumidity.sql b/pkg/repository/db/sqlite3/selectHumidity.sql new file mode 100644 index 0000000..e1346cf --- /dev/null +++ b/pkg/repository/db/sqlite3/selectHumidity.sql @@ -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 \ No newline at end of file diff --git a/pkg/repository/db/sqlite3/selectPressure.sql b/pkg/repository/db/sqlite3/selectPressure.sql new file mode 100644 index 0000000..d9e386d --- /dev/null +++ b/pkg/repository/db/sqlite3/selectPressure.sql @@ -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 \ No newline at end of file diff --git a/pkg/repository/db/sqlite3/selectPressures.sql b/pkg/repository/db/sqlite3/selectPressures.sql new file mode 100644 index 0000000..6cf70f9 --- /dev/null +++ b/pkg/repository/db/sqlite3/selectPressures.sql @@ -0,0 +1,10 @@ +SELECT + pressure_id, + pressure_value, + pressure_from_date, + pressure_till_date, + sensor_id, + creation_date, + update_date +FROM + pressures \ No newline at end of file diff --git a/pkg/repository/db/sqlite3/selectSensor.sql b/pkg/repository/db/sqlite3/selectSensor.sql new file mode 100644 index 0000000..0d6f164 --- /dev/null +++ b/pkg/repository/db/sqlite3/selectSensor.sql @@ -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; \ No newline at end of file diff --git a/pkg/repository/db/sqlite3/selectSensors.sql b/pkg/repository/db/sqlite3/selectSensors.sql new file mode 100644 index 0000000..0c85bc4 --- /dev/null +++ b/pkg/repository/db/sqlite3/selectSensors.sql @@ -0,0 +1,14 @@ +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; \ No newline at end of file diff --git a/pkg/repository/db/sqlite3/selectTemperature.sql b/pkg/repository/db/sqlite3/selectTemperature.sql new file mode 100644 index 0000000..a265c05 --- /dev/null +++ b/pkg/repository/db/sqlite3/selectTemperature.sql @@ -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 \ No newline at end of file diff --git a/pkg/repository/db/sqlite3/selectTemperatures.sql b/pkg/repository/db/sqlite3/selectTemperatures.sql new file mode 100644 index 0000000..530f24a --- /dev/null +++ b/pkg/repository/db/sqlite3/selectTemperatures.sql @@ -0,0 +1,10 @@ +SELECT + temperature_id, + temperature_value, + temperature_from_date, + temperature_till_date, + sensor_id, + creation_date, + update_date +FROM + temperatures \ No newline at end of file diff --git a/pkg/repository/db/sqlite3/updateDevice.sql b/pkg/repository/db/sqlite3/updateDevice.sql new file mode 100644 index 0000000..834889f --- /dev/null +++ b/pkg/repository/db/sqlite3/updateDevice.sql @@ -0,0 +1,7 @@ +UPDATE device +SET + device_name = $1, + device_location = $2, + creation_date = $3 +WHERE + device_id = $4, \ No newline at end of file diff --git a/pkg/repository/db/sqlite3/updateSensor.sql b/pkg/repository/db/sqlite3/updateSensor.sql new file mode 100644 index 0000000..44507a6 --- /dev/null +++ b/pkg/repository/db/sqlite3/updateSensor.sql @@ -0,0 +1,14 @@ +UPDATE sensors +SET + sensor_name = $1, + sensor_location = $2, + wire_id = $3, + i2c_bus = $4, + i2c_address = $5, + gpio_number = $6, + sensor_model = $7, + sensor_enabled = $8, + device_id = $9, + creation_date = $10 +WHERE + sensor_id = $11; \ No newline at end of file diff --git a/pkg/repository/repository.go b/pkg/repository/repository.go new file mode 100644 index 0000000..fad3ec8 --- /dev/null +++ b/pkg/repository/repository.go @@ -0,0 +1,189 @@ +package repository + +import ( + "context" + "net/url" + "strings" + + "github.com/volker-raschek/flucky/pkg/repository/db" + "github.com/volker-raschek/flucky/pkg/types" + "github.com/volker-raschek/go-logger/pkg/logger" +) + +// Repository represent a repository where all devices, sensors and measured +// values are stored. +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...) +} + +// AddMeasuredValues to the repository +func (repo *Repository) AddMeasuredValues(measuredValues ...*types.MeasuredValue) error { + return repo.database.InsertMeasuredValues(context.Background(), measuredValues...) +} + +// AddSensors to the repository +func (repo *Repository) AddSensors(sensors ...*types.Sensor) error { + return repo.database.InsertSensors(context.Background(), sensors...) +} + +// 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) + } + } + } + + 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 + } + + 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...) +} + +// 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()) +} + +// 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() ([]*types.Sensor, error) { + return repo.database.SelectSensors(context.Background()) +} + +// 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) + } + } + + return sensors, nil +} + +// 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 + } + + 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...) +} + +// 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 + } + + 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...) +} + +// 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...) +} + +// New returns a new repository based on the storageEndpoint +func New(storageEndpoint *url.URL, flogger logger.Logger) (*Repository, error) { + database, err := db.New(storageEndpoint, flogger) + if err != nil { + return nil, err + } + + return &Repository{ + database: database, + }, nil +} diff --git a/pkg/storage/postgres/removeSensorByName.sql b/pkg/storage/postgres/removeSensorByName.sql deleted file mode 100644 index b6a7d6f..0000000 --- a/pkg/storage/postgres/removeSensorByName.sql +++ /dev/null @@ -1,2 +0,0 @@ -DELETE FROM sensors -WHERE sensor_name = $1; \ No newline at end of file