From 7a88aaac0c060a4beef236c2c3aa325599d55413 Mon Sep 17 00:00:00 2001 From: Markus Pesch Date: Sun, 21 Mar 2021 18:47:14 +0100 Subject: [PATCH] refac: use embed instead of go-bindata, secure closing of transactions --- Makefile | 40 +- cli/imp/imp.go | 74 +- cli/sensor/sensor.go | 56 +- cli/temperature/temperature.go | 6 +- go.mod | 8 +- go.sum | 56 +- pkg/daemon/daemon.go | 14 +- pkg/repository/db/db.go | 134 -- pkg/repository/db/postgres.go | 951 ------------- pkg/repository/db/sqlite.go | 939 ------------ .../postgres/ddl/1_table_devices.down.sql | 0 .../postgres/ddl/1_table_devices.up.sql | 0 .../postgres/ddl/2_table_sensors.down.sql | 0 .../postgres/ddl/2_table_sensors.up.sql | 0 .../postgres/ddl/3_table_humidites.down.sql | 0 .../postgres/ddl/3_table_humidites.up.sql | 0 .../postgres/ddl/4_table_pressures.down.sql | 0 .../postgres/ddl/4_table_pressures.up.sql | 0 .../ddl/5_table_temperatures.down.sql | 0 .../postgres/ddl/5_table_temperatures.up.sql | 0 ...onstraint_fk_humidities_sensor_id.down.sql | 0 ..._constraint_fk_humidities_sensor_id.up.sql | 0 ...constraint_fk_pressures_sensor_id.down.sql | 0 ...7_constraint_fk_pressures_sensor_id.up.sql | 0 .../dml/deleteDeviceByID.sql} | 0 .../postgres/dml/deleteDeviceByName.sql | 2 + .../dml/deleteSensorByID.sql} | 0 .../postgres/dml/deleteSensorByName.sql | 2 + .../{db => }/postgres/dml/insertDevice.sql | 0 .../{db => }/postgres/dml/insertHumidity.sql | 0 .../postgres/dml/insertOrUpdateDevice.sql | 0 .../postgres/dml/insertOrUpdateHumidity.sql | 0 .../postgres/dml/insertOrUpdatePressure.sql | 0 .../postgres/dml/insertOrUpdateSensor.sql | 0 .../dml/insertOrUpdateTemperature.sql | 0 .../{db => }/postgres/dml/insertPressure.sql | 0 .../{db => }/postgres/dml/insertSensor.sql | 0 .../postgres/dml/insertTemperature.sql | 0 .../dml/selectDeviceByID.sql} | 0 .../postgres/dml/selectDeviceByName.sql | 9 + .../{db => }/postgres/dml/selectDevices.sql | 0 .../postgres/dml/selectHumidities.sql | 0 .../dml/selectHumidityByID.sql} | 0 .../dml/selectPressureByID.sql} | 0 .../{db => }/postgres/dml/selectPressures.sql | 0 .../dml/selectSensorByID.sql} | 0 .../{db => }/postgres/dml/selectSensors.sql | 0 .../postgres/dml/selectSensorsByDeviceID.sql | 18 + .../postgres/dml/selectSensorsByModel.sql | 18 + .../postgres/dml/selectSensorsByName.sql | 18 + .../dml/selectTemperatureByID.sql} | 0 .../postgres/dml/selectTemperatures.sql | 0 .../{db => }/postgres/dml/updateDevice.sql | 0 .../{db => }/postgres/dml/updateSensor.sql | 0 pkg/repository/postgres/postgres.go | 1206 ++++++++++++++++ pkg/repository/repository.go | 629 +++++---- pkg/repository/repository_test.go | 169 +-- .../sqlite3/ddl/1_table_devices.down.sql | 0 .../sqlite3/ddl/1_table_devices.up.sql | 0 .../sqlite3/ddl/2_table_sensors.down.sql | 0 .../sqlite3/ddl/2_table_sensors.up.sql | 0 .../sqlite3/ddl/3_table_humidities.down.sql | 0 .../sqlite3/ddl/3_table_humidities.up.sql | 0 .../sqlite3/ddl/4_table_pressures.down.sql | 0 .../sqlite3/ddl/4_table_pressures.up.sql | 0 .../sqlite3/ddl/5_table_temperatures.down.sql | 0 .../sqlite3/ddl/5_table_temperatures.up.sql | 0 .../dml/deleteDeviceByID.sql} | 0 .../sqlite3/dml/deleteDeviceByName.sql | 2 + .../dml/deleteSensorByID.sql} | 0 .../sqlite3/dml/deleteSensorByName.sql | 2 + .../{db => }/sqlite3/dml/insertDevice.sql | 0 .../{db => }/sqlite3/dml/insertHumidity.sql | 0 .../sqlite3/dml/insertOrUpdateDevice.sql | 0 .../sqlite3/dml/insertOrUpdateHumidity.sql | 0 .../sqlite3/dml/insertOrUpdatePressure.sql | 0 .../sqlite3/dml/insertOrUpdateSensor.sql | 0 .../sqlite3/dml/insertOrUpdateTemperature.sql | 0 .../{db => }/sqlite3/dml/insertPressure.sql | 0 .../{db => }/sqlite3/dml/insertSensor.sql | 0 .../sqlite3/dml/insertTemperature.sql | 0 .../dml/selectDeviceByID.sql} | 0 .../sqlite3/dml/selectDeviceByName.sql | 9 + .../{db => }/sqlite3/dml/selectDevices.sql | 0 .../{db => }/sqlite3/dml/selectHumidities.sql | 0 .../dml/selectHumidityByID.sql} | 0 .../dml/selectPressureByID.sql} | 0 .../{db => }/sqlite3/dml/selectPressures.sql | 0 .../dml/selectSensorByID.sql} | 0 .../{db => }/sqlite3/dml/selectSensors.sql | 0 .../sqlite3/dml/selectSensorsByDeviceID.sql | 18 + .../sqlite3/dml/selectSensorsByModel.sql | 18 + .../sqlite3/dml/selectSensorsByName.sql | 18 + .../dml/selectTemperatureByID.sql} | 0 .../sqlite3/dml/selectTemperatures.sql | 0 .../{db => }/sqlite3/dml/updateDevice.sql | 0 .../{db => }/sqlite3/dml/updateSensor.sql | 0 pkg/repository/sqlite3/sqlite.go | 1256 +++++++++++++++++ 98 files changed, 3147 insertions(+), 2525 deletions(-) delete mode 100644 pkg/repository/db/db.go delete mode 100644 pkg/repository/db/postgres.go delete mode 100644 pkg/repository/db/sqlite.go rename pkg/repository/{db => }/postgres/ddl/1_table_devices.down.sql (100%) rename pkg/repository/{db => }/postgres/ddl/1_table_devices.up.sql (100%) rename pkg/repository/{db => }/postgres/ddl/2_table_sensors.down.sql (100%) rename pkg/repository/{db => }/postgres/ddl/2_table_sensors.up.sql (100%) rename pkg/repository/{db => }/postgres/ddl/3_table_humidites.down.sql (100%) rename pkg/repository/{db => }/postgres/ddl/3_table_humidites.up.sql (100%) rename pkg/repository/{db => }/postgres/ddl/4_table_pressures.down.sql (100%) rename pkg/repository/{db => }/postgres/ddl/4_table_pressures.up.sql (100%) rename pkg/repository/{db => }/postgres/ddl/5_table_temperatures.down.sql (100%) rename pkg/repository/{db => }/postgres/ddl/5_table_temperatures.up.sql (100%) rename pkg/repository/{db => }/postgres/ddl/6_constraint_fk_humidities_sensor_id.down.sql (100%) rename pkg/repository/{db => }/postgres/ddl/6_constraint_fk_humidities_sensor_id.up.sql (100%) rename pkg/repository/{db => }/postgres/ddl/7_constraint_fk_pressures_sensor_id.down.sql (100%) rename pkg/repository/{db => }/postgres/ddl/7_constraint_fk_pressures_sensor_id.up.sql (100%) rename pkg/repository/{db/postgres/dml/deleteDevice.sql => postgres/dml/deleteDeviceByID.sql} (100%) create mode 100644 pkg/repository/postgres/dml/deleteDeviceByName.sql rename pkg/repository/{db/postgres/dml/deleteSensor.sql => postgres/dml/deleteSensorByID.sql} (100%) create mode 100644 pkg/repository/postgres/dml/deleteSensorByName.sql rename pkg/repository/{db => }/postgres/dml/insertDevice.sql (100%) rename pkg/repository/{db => }/postgres/dml/insertHumidity.sql (100%) rename pkg/repository/{db => }/postgres/dml/insertOrUpdateDevice.sql (100%) rename pkg/repository/{db => }/postgres/dml/insertOrUpdateHumidity.sql (100%) rename pkg/repository/{db => }/postgres/dml/insertOrUpdatePressure.sql (100%) rename pkg/repository/{db => }/postgres/dml/insertOrUpdateSensor.sql (100%) rename pkg/repository/{db => }/postgres/dml/insertOrUpdateTemperature.sql (100%) rename pkg/repository/{db => }/postgres/dml/insertPressure.sql (100%) rename pkg/repository/{db => }/postgres/dml/insertSensor.sql (100%) rename pkg/repository/{db => }/postgres/dml/insertTemperature.sql (100%) rename pkg/repository/{db/postgres/dml/selectDevice.sql => postgres/dml/selectDeviceByID.sql} (100%) create mode 100644 pkg/repository/postgres/dml/selectDeviceByName.sql rename pkg/repository/{db => }/postgres/dml/selectDevices.sql (100%) rename pkg/repository/{db => }/postgres/dml/selectHumidities.sql (100%) rename pkg/repository/{db/postgres/dml/selectHumidity.sql => postgres/dml/selectHumidityByID.sql} (100%) rename pkg/repository/{db/postgres/dml/selectPressure.sql => postgres/dml/selectPressureByID.sql} (100%) rename pkg/repository/{db => }/postgres/dml/selectPressures.sql (100%) rename pkg/repository/{db/postgres/dml/selectSensor.sql => postgres/dml/selectSensorByID.sql} (100%) rename pkg/repository/{db => }/postgres/dml/selectSensors.sql (100%) create mode 100644 pkg/repository/postgres/dml/selectSensorsByDeviceID.sql create mode 100644 pkg/repository/postgres/dml/selectSensorsByModel.sql create mode 100644 pkg/repository/postgres/dml/selectSensorsByName.sql rename pkg/repository/{db/postgres/dml/selectTemperature.sql => postgres/dml/selectTemperatureByID.sql} (100%) rename pkg/repository/{db => }/postgres/dml/selectTemperatures.sql (100%) rename pkg/repository/{db => }/postgres/dml/updateDevice.sql (100%) rename pkg/repository/{db => }/postgres/dml/updateSensor.sql (100%) create mode 100644 pkg/repository/postgres/postgres.go rename pkg/repository/{db => }/sqlite3/ddl/1_table_devices.down.sql (100%) rename pkg/repository/{db => }/sqlite3/ddl/1_table_devices.up.sql (100%) rename pkg/repository/{db => }/sqlite3/ddl/2_table_sensors.down.sql (100%) rename pkg/repository/{db => }/sqlite3/ddl/2_table_sensors.up.sql (100%) rename pkg/repository/{db => }/sqlite3/ddl/3_table_humidities.down.sql (100%) rename pkg/repository/{db => }/sqlite3/ddl/3_table_humidities.up.sql (100%) rename pkg/repository/{db => }/sqlite3/ddl/4_table_pressures.down.sql (100%) rename pkg/repository/{db => }/sqlite3/ddl/4_table_pressures.up.sql (100%) rename pkg/repository/{db => }/sqlite3/ddl/5_table_temperatures.down.sql (100%) rename pkg/repository/{db => }/sqlite3/ddl/5_table_temperatures.up.sql (100%) rename pkg/repository/{db/sqlite3/dml/deleteDevice.sql => sqlite3/dml/deleteDeviceByID.sql} (100%) create mode 100644 pkg/repository/sqlite3/dml/deleteDeviceByName.sql rename pkg/repository/{db/sqlite3/dml/deleteSensor.sql => sqlite3/dml/deleteSensorByID.sql} (100%) create mode 100644 pkg/repository/sqlite3/dml/deleteSensorByName.sql rename pkg/repository/{db => }/sqlite3/dml/insertDevice.sql (100%) rename pkg/repository/{db => }/sqlite3/dml/insertHumidity.sql (100%) rename pkg/repository/{db => }/sqlite3/dml/insertOrUpdateDevice.sql (100%) rename pkg/repository/{db => }/sqlite3/dml/insertOrUpdateHumidity.sql (100%) rename pkg/repository/{db => }/sqlite3/dml/insertOrUpdatePressure.sql (100%) rename pkg/repository/{db => }/sqlite3/dml/insertOrUpdateSensor.sql (100%) rename pkg/repository/{db => }/sqlite3/dml/insertOrUpdateTemperature.sql (100%) rename pkg/repository/{db => }/sqlite3/dml/insertPressure.sql (100%) rename pkg/repository/{db => }/sqlite3/dml/insertSensor.sql (100%) rename pkg/repository/{db => }/sqlite3/dml/insertTemperature.sql (100%) rename pkg/repository/{db/sqlite3/dml/selectDevice.sql => sqlite3/dml/selectDeviceByID.sql} (100%) create mode 100644 pkg/repository/sqlite3/dml/selectDeviceByName.sql rename pkg/repository/{db => }/sqlite3/dml/selectDevices.sql (100%) rename pkg/repository/{db => }/sqlite3/dml/selectHumidities.sql (100%) rename pkg/repository/{db/sqlite3/dml/selectHumidity.sql => sqlite3/dml/selectHumidityByID.sql} (100%) rename pkg/repository/{db/sqlite3/dml/selectPressure.sql => sqlite3/dml/selectPressureByID.sql} (100%) rename pkg/repository/{db => }/sqlite3/dml/selectPressures.sql (100%) rename pkg/repository/{db/sqlite3/dml/selectSensor.sql => sqlite3/dml/selectSensorByID.sql} (100%) rename pkg/repository/{db => }/sqlite3/dml/selectSensors.sql (100%) create mode 100644 pkg/repository/sqlite3/dml/selectSensorsByDeviceID.sql create mode 100644 pkg/repository/sqlite3/dml/selectSensorsByModel.sql create mode 100644 pkg/repository/sqlite3/dml/selectSensorsByName.sql rename pkg/repository/{db/sqlite3/dml/selectTemperature.sql => sqlite3/dml/selectTemperatureByID.sql} (100%) rename pkg/repository/{db => }/sqlite3/dml/selectTemperatures.sql (100%) rename pkg/repository/{db => }/sqlite3/dml/updateDevice.sql (100%) rename pkg/repository/{db => }/sqlite3/dml/updateSensor.sql (100%) create mode 100644 pkg/repository/sqlite3/sqlite.go diff --git a/Makefile b/Makefile index e526561..e1a5ca8 100644 --- a/Makefile +++ b/Makefile @@ -30,7 +30,7 @@ EXECUTABLE_TARGETS:= \ ${EXECUTABLE}: bin/tmp/${EXECUTABLE} -bin/linux/amd64/${EXECUTABLE}: bindata +bin/linux/amd64/${EXECUTABLE}: CGO_ENABLED=1 \ GOOS=linux \ GOARCH=amd64 \ @@ -38,7 +38,7 @@ bin/linux/amd64/${EXECUTABLE}: bindata GOPRIVATE=${GOPRIVATE} \ go build -ldflags "-X main.version=${VERSION:v%=%}" -o ${@} -bin/linux/arm/5/${EXECUTABLE}: bindata +bin/linux/arm/5/${EXECUTABLE}: CGO_ENABLED=1 \ GOOS=linux \ GOARCH=arm \ @@ -47,7 +47,7 @@ bin/linux/arm/5/${EXECUTABLE}: bindata GOPRIVATE=${GOPRIVATE} \ go build -ldflags "-X main.version=${VERSION:v%=%}" -o ${@} -bin/linux/arm/7/${EXECUTABLE}: bindata +bin/linux/arm/7/${EXECUTABLE}: CC=arm-linux-gnueabihf-gcc \ CGO_ENABLED=1 \ GOOS=linux \ @@ -57,46 +57,22 @@ bin/linux/arm/7/${EXECUTABLE}: bindata GOPRIVATE=${GOPRIVATE} \ go build -ldflags "-X main.version=${VERSION:v%=%}" -o ${@} -bin/tmp/${EXECUTABLE}: bindata +bin/tmp/${EXECUTABLE}: CGO_ENABLED=1 \ GOPROXY=${GOPROXY} \ GOPRIVATE=${GOPRIVATE} \ go build -ldflags "-X main.version=${VERSION:v%=%}" -o ${@} -# GO-BINDATA -# ============================================================================== -BINDATA_TARGETS := \ - pkg/repository/db/postgres/ddl/bindata.go \ - pkg/repository/db/postgres/dml/bindata.go \ - pkg/repository/db/sqlite3/ddl/bindata.go \ - pkg/repository/db/sqlite3/dml/bindata.go - -PHONY+=bindata -bindata: clean ${BINDATA_TARGETS} - -pkg/repository/db/postgres/ddl/bindata.go: - go-bindata -pkg postgresddl -prefix ${@:%/bindata.go=%} -o ${@} ${@:%/bindata.go=%}/* - -pkg/repository/db/postgres/dml/bindata.go: - go-bindata -pkg postgresdml -prefix ${@:%/bindata.go=%} -o ${@} ${@:%/bindata.go=%}/* - -pkg/repository/db/sqlite3/ddl/bindata.go: - go-bindata -pkg sqlite3ddl -prefix ${@:%/bindata.go=%} -o ${@} ${@:%/bindata.go=%}/* - -pkg/repository/db/sqlite3/dml/bindata.go: - go-bindata -pkg sqlite3dml -prefix ${@:%/bindata.go=%} -o ${@} ${@:%/bindata.go=%}/* - # CLEAN # ============================================================================== PHONY+=clean clean: - rm --force ${BINDATA_TARGETS} || true rm --force --recursive bin/ || true # TEST # ============================================================================== PHONY+=test/unit -test/unit: bindata +test/unit: go test -v -race -coverprofile=coverage.txt -covermode=atomic -timeout 600s -count=1 ./pkg/... PHONY+=test/integration @@ -147,12 +123,6 @@ PHONY+=${EXECUTABLE_TARGETS:%=container-run/%} ${EXECUTABLE_TARGETS:%=container-run/%}: $(MAKE) container-run COMMAND=${@:container-run/%=%} -# CONTAINER STEPS - GO-BINDATA -# ============================================================================== -PHONY+=container-run/bindata -container-run/bindata: - $(MAKE) container-run COMMAND=${@:container-run/%=%} - # CONTAINER STEPS - CLEAN # ============================================================================== PHONY+=container-run/clean diff --git a/cli/imp/imp.go b/cli/imp/imp.go index f207816..34d0ca4 100644 --- a/cli/imp/imp.go +++ b/cli/imp/imp.go @@ -1,12 +1,6 @@ package imp import ( - "fmt" - "net/url" - - "git.cryptic.systems/volker.raschek/flucky/pkg/config" - "git.cryptic.systems/volker.raschek/flucky/pkg/repository" - "git.cryptic.systems/volker.raschek/go-logger" "github.com/spf13/cobra" ) @@ -35,47 +29,47 @@ func InitCmd(cmd *cobra.Command) error { } func importSources(cmd *cobra.Command, args []string) error { - configFile, err := cmd.Flags().GetString("config") - if err != nil { - return fmt.Errorf("No config file defined") - } + // configFile, err := cmd.Flags().GetString("config") + // if err != nil { + // return fmt.Errorf("No config file defined") + // } - cnf, err := config.Read(configFile) - if err != nil { - return err - } + // cnf, err := config.Read(configFile) + // if err != nil { + // return err + // } - destURL, err := url.Parse(cnf.DSN) - if err != nil { - return err - } + // destURL, err := url.Parse(cnf.DSN) + // if err != nil { + // return err + // } - logLevelString, err := cmd.Flags().GetString("loglevel") - if err != nil { - return err - } + // logLevelString, err := cmd.Flags().GetString("loglevel") + // if err != nil { + // return err + // } - logLevel, err := logger.ParseLogLevel(logLevelString) - if err != nil { - return err - } + // logLevel, err := logger.ParseLogLevel(logLevelString) + // if err != nil { + // return err + // } - flogger := logger.NewLogger(logLevel) + // flogger := logger.NewLogger(logLevel) - sourceURL, err := url.Parse(args[0]) - if err != nil { - return fmt.Errorf("Failed to parse source url: %w", err) - } + // sourceURL, err := url.Parse(args[0]) + // if err != nil { + // return fmt.Errorf("Failed to parse source url: %w", err) + // } - err = repository.Import(sourceURL, destURL, flogger, repository.OptImport{ - Sensors: importSensors, - Humidities: importHumidities, - Pressures: importPressures, - Temperatures: importTemperatures, - }) - if err != nil { - return fmt.Errorf("Failed to import: %w", err) - } + // err = repository.Import(sourceURL, destURL, flogger, repository.OptImport{ + // Sensors: importSensors, + // Humidities: importHumidities, + // Pressures: importPressures, + // Temperatures: importTemperatures, + // }) + // if err != nil { + // return fmt.Errorf("Failed to import: %w", err) + // } return nil } diff --git a/cli/sensor/sensor.go b/cli/sensor/sensor.go index a6caaff..ed7e4a3 100644 --- a/cli/sensor/sensor.go +++ b/cli/sensor/sensor.go @@ -1,6 +1,7 @@ package sensor import ( + "context" "fmt" "net/url" "os" @@ -184,7 +185,7 @@ func addSensor(cmd *cobra.Command, args []string) error { } // add sensor entry to list - err = repo.AddSensors(sensor) + err = repo.AddSensors(context.Background()) if err != nil { return err } @@ -227,7 +228,21 @@ func disableSensor(cmd *cobra.Command, args []string) error { return err } - return repo.DisableSensorsByNames(args...) + s, err := repo.GetSensorsByNames(context.Background(), args...) + if err != nil { + return err + } + + for i := range s { + s[i].Enabled = false + } + + err = repo.UpdateSensors(context.Background(), s...) + if err != nil { + return err + } + + return nil } func enableSensor(cmd *cobra.Command, args []string) error { @@ -259,7 +274,21 @@ func enableSensor(cmd *cobra.Command, args []string) error { return err } - return repo.EnableSensorsByNames(args...) + s, err := repo.GetSensorsByNames(context.Background(), args...) + if err != nil { + return err + } + + for i := range s { + s[i].Enabled = true + } + + err = repo.UpdateSensors(context.Background(), s...) + if err != nil { + return err + } + + return nil } func listSensors(cmd *cobra.Command, args []string) error { @@ -292,7 +321,7 @@ func listSensors(cmd *cobra.Command, args []string) error { } // add sensor entry to list - sensors, err := repo.GetSensorsByDeviceID(cnf.DeviceID) + sensors, err := repo.GetSensorsByDeviceIDs(context.Background(), cnf.DeviceID) if err != nil { return err } @@ -334,7 +363,7 @@ func removeSensor(cmd *cobra.Command, args []string) error { return err } - return repo.RemoveSensorsByNames(args...) + return repo.RemoveSensorsByNames(context.Background(), args...) } func renameSensor(cmd *cobra.Command, args []string) error { @@ -366,5 +395,20 @@ func renameSensor(cmd *cobra.Command, args []string) error { return err } - return repo.RenameSensors(args[0], args[1]) + s, err := repo.GetSensorsByNames(context.Background(), args[0]) + if err != nil { + return err + } + + for i := range s { + s[i].Name = args[1] + } + + err = repo.UpdateSensors(context.Background(), s...) + if err != nil { + return err + } + + return nil + } diff --git a/cli/temperature/temperature.go b/cli/temperature/temperature.go index d5eebfe..35a3cbc 100644 --- a/cli/temperature/temperature.go +++ b/cli/temperature/temperature.go @@ -72,7 +72,9 @@ func readTemperature(cmd *cobra.Command, args []string) error { return err } - sensorTypes, err := repo.GetSensors() + ctx := context.Background() + + sensorTypes, err := repo.GetSensors(ctx) if err != nil { return err } @@ -123,7 +125,7 @@ LOOP: } if persist { - err = repo.AddMeasuredValues(measuredValues...) + err = repo.AddMeasuredValues(ctx, measuredValues...) if err != nil { return err } diff --git a/go.mod b/go.mod index 580583a..4c9f86e 100644 --- a/go.mod +++ b/go.mod @@ -1,22 +1,20 @@ module git.cryptic.systems/volker.raschek/flucky -go 1.14 +go 1.16 require ( git.cryptic.systems/volker.raschek/dockerutils v0.2.0 git.cryptic.systems/volker.raschek/go-dht v0.1.2 git.cryptic.systems/volker.raschek/go-logger v0.1.0 github.com/Masterminds/semver v1.5.0 - github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6 // indirect github.com/d2r2/go-bsbmp v0.0.0-20190515110334-3b4b3aea8375 github.com/d2r2/go-i2c v0.0.0-20191123181816-73a8a799d6bc github.com/d2r2/go-logger v0.0.0-20181221090742-9998a510495e - github.com/golang-migrate/migrate/v4 v4.14.1 + github.com/golang-migrate/migrate/v4 v4.14.2-0.20201125065321-a53e6fc42574 + github.com/johejo/golang-migrate-extra v0.0.0-20210217013041-51a992e50d16 github.com/lib/pq v1.9.0 github.com/mattn/go-sqlite3 v2.0.3+incompatible github.com/satori/go.uuid v1.2.0 github.com/spf13/cobra v1.1.1 github.com/stretchr/testify v1.7.0 - github.com/ugorji/go v1.1.4 // indirect - github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77 // indirect ) diff --git a/go.sum b/go.sum index 2fe7da6..609b0ef 100644 --- a/go.sum +++ b/go.sum @@ -34,8 +34,6 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -git.cryptic.systems/volker.raschek/dockerutils v0.1.0 h1:NjE5BVS0k9XoRj8qZOHNvpjb1+C8Zr4sK/g4Yjw5Of4= -git.cryptic.systems/volker.raschek/dockerutils v0.1.0/go.mod h1:PikYsmSANKHEKJSD2pIY9gPqMpiQh1ufCrU/s/v2I5Q= git.cryptic.systems/volker.raschek/dockerutils v0.2.0 h1:HJhAfHoyKHOt76T5PxeLvpoA0FCiGu50u4GRwGLGVJs= git.cryptic.systems/volker.raschek/dockerutils v0.2.0/go.mod h1:c4ZZpD2unnzwr7qHDpVdCmxLGOSnJagli7xNGgTBhk0= git.cryptic.systems/volker.raschek/go-dht v0.1.2 h1:kGmfpaVUETQhSELCIrKXMjKwuUhQkRUz/7VbLYiTRJA= @@ -49,9 +47,6 @@ github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym github.com/ClickHouse/clickhouse-go v1.3.12/go.mod h1:EaI/sW7Azgz9UATzd5ZdZHRUhHgv5+JMS9NSr2smCJI= github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= -github.com/Microsoft/go-winio v0.4.14 h1:+hMXMk01us9KgxGb7ftKQt2Xpf5hH/yky+TDA+qxleU= -github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= -github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5 h1:ygIc8M6trr62pF5DucadTWGdEB4mEyvzi0e2nbcmcyA= github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw= github.com/Microsoft/go-winio v0.4.16 h1:FtSW/jqD+l4ba5iPBj9CODVtgfYAD8w2wS923g/cFDk= github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= @@ -60,7 +55,6 @@ github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuy github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/apache/arrow/go/arrow v0.0.0-20200601151325-b2287a20f230/go.mod h1:QNYViu/X0HXDHw7m3KXzWSVXIbfUvJqBFe6Gj8/pYA0= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= -github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/aws/aws-sdk-go v1.17.7/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= @@ -84,14 +78,11 @@ github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnht github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= github.com/cockroachdb/cockroach-go v0.0.0-20190925194419-606b3d062051/go.mod h1:XGLbWH/ujMcbPbhZq52Nv6UrCghb1yGn//133kEsvDk= github.com/containerd/containerd v1.4.0/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= -github.com/containerd/containerd v1.4.1 h1:pASeJT3R3YyVn+94qEPk0SnU1OQ20Jd/T+SPKy9xehY= github.com/containerd/containerd v1.4.1/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/containerd/containerd v1.4.3 h1:ijQT13JedHSHrQGWFcGEwzcNKrAGIiZ+jSD5QQG07SY= github.com/containerd/containerd v1.4.3/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= -github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= -github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= @@ -106,19 +97,16 @@ github.com/d2r2/go-i2c v0.0.0-20191123181816-73a8a799d6bc h1:HLRSIWzUGMLCq4ldt0W github.com/d2r2/go-i2c v0.0.0-20191123181816-73a8a799d6bc/go.mod h1:AwxDPnsgIpy47jbGXZHA9Rv7pDkOJvQbezPuK1Y+nNk= github.com/d2r2/go-logger v0.0.0-20181221090742-9998a510495e h1:ZG3JBA6rPRl0xxQ+nNSfO7tor8w+CNCTs05DNJQYbLM= github.com/d2r2/go-logger v0.0.0-20181221090742-9998a510495e/go.mod h1:oA+9PUt8F1aKJ6o4YU1T120i7sgo1T6/1LWEEBy0BSs= -github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/denisenkom/go-mssqldb v0.0.0-20200620013148-b91950f658ec/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= +github.com/dhui/dktest v0.3.3 h1:DBuH/9GFaWbDRa42qsut/hbQu+srAQ0rPWnUoiGX7CA= github.com/dhui/dktest v0.3.3/go.mod h1:EML9sP4sqJELHn4jV7B0TY8oF6077nk83/tz7M56jcQ= github.com/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BUmsJpcB+cRlLU7cSug= github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v1.13.1 h1:IkZjBSIc8hBjLpqeAbeE5mca5mNgeatLHBy3GO78BWo= -github.com/docker/docker v1.13.1/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/docker v17.12.0-ce-rc1.0.20200618181300-9dc6525e6118+incompatible h1:iWPIG7pWIsCwT6ZtHnTUpoVMnete7O/pzd9HFE3+tn8= github.com/docker/docker v17.12.0-ce-rc1.0.20200618181300-9dc6525e6118+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker v20.10.2+incompatible h1:vFgEHPqWBTp4pTjdLwjAA4bSo3gvIGOYwuJTlEjVBCw= github.com/docker/docker v20.10.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= @@ -149,14 +137,11 @@ github.com/gobuffalo/here v0.6.0/go.mod h1:wAG085dHOYqUpf+Ap+WOdrPTp5IYcDAs/x7PL github.com/gocql/gocql v0.0.0-20190301043612-f6df8288f9b4/go.mod h1:4Fw1eo5iaEhDUs8XyuhSVCVy52Jq3L+/3GJgYkwc+/0= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= -github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls= github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang-migrate/migrate v1.3.2 h1:QAlFV1QF9zdkzy/jujlBVkVu+L/+k18cg8tuY1/4JDY= -github.com/golang-migrate/migrate v3.5.4+incompatible h1:R7OzwvCJTCgwapPCiX6DyBiu2czIUMDCB118gFTKTUA= -github.com/golang-migrate/migrate/v4 v4.14.1 h1:qmRd/rNGjM1r3Ve5gHd5ZplytrD02UcItYNxJ3iUHHE= -github.com/golang-migrate/migrate/v4 v4.14.1/go.mod h1:l7Ks0Au6fYHuUIxUhQ0rcVX1uLlJg54C/VvW7tvxSz0= +github.com/golang-migrate/migrate/v4 v4.14.2-0.20201125065321-a53e6fc42574 h1:YEVMe8861NCZxTMzTI5BCobpwGpt1Md6D8v00jDc68w= +github.com/golang-migrate/migrate/v4 v4.14.2-0.20201125065321-a53e6fc42574/go.mod h1:l7Ks0Au6fYHuUIxUhQ0rcVX1uLlJg54C/VvW7tvxSz0= github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -197,8 +182,8 @@ github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.1 h1:JFrFEBb2xKufg6XkJsJr+WbKb4FQlURi5RUcBveYu9k= github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4 h1:L8R9j+yAqZuZjsqh/z+F1NCffTKKLShY6zXTItVIZ8M= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= @@ -221,10 +206,9 @@ github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51 github.com/gorilla/handlers v1.4.2/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ= github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= -github.com/gorilla/mux v1.7.4 h1:VuZ8uybHlWmqV03+zRzdwKL4tUnIp1MAQtp1mIFE1bc= github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= -github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= @@ -283,6 +267,8 @@ github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0f github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks= +github.com/johejo/golang-migrate-extra v0.0.0-20210217013041-51a992e50d16 h1:sDjlyV4OzJYR2ZdLAAKZwV6N/CcX60xbGnPZzKJOZ50= +github.com/johejo/golang-migrate-extra v0.0.0-20210217013041-51a992e50d16/go.mod h1:lzH77MbyyahK7YO90wGRb65i9xLSoy2fD0dUSm23yMs= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= @@ -310,11 +296,9 @@ github.com/ktrysmt/go-bitbucket v0.6.4/go.mod h1:9u0v3hsd2rqCHRIpbir1oP7F58uo5dq github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lib/pq v1.8.0 h1:9xohqzkUwzR4Ga4ivdTcawVS89YSDVxXMa3xJX3cGzg= github.com/lib/pq v1.8.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lib/pq v1.9.0 h1:L8nSXQQzAYByakOFMTwpjRoHsMJklur4Gi59b6VivR8= github.com/lib/pq v1.9.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/markbates/pkger v0.15.1/go.mod h1:0JoVlrol20BSywW79rN3kdFFsE5xYM+rSCQDXbLhiuI= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= @@ -337,6 +321,7 @@ github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0Qu github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v0.0.0-20180220230111-00c29f56e238/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/moby/term v0.0.0-20201216013528-df9cb8a40635 h1:rzf0wL0CHVc8CEsgyygG0Mn9CNCCPZqOPaz8RiiHYQk= github.com/moby/term v0.0.0-20201216013528-df9cb8a40635/go.mod h1:FBS0z0QWA44HXygs7VXDUOGoN/1TV3RuWkLO04am3wc= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= @@ -359,9 +344,7 @@ github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FI github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4/go.mod h1:4OwLy04Bl9Ef3GJJCoec+30X3LQs/0/m4HFRt/2LUSA= -github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -405,41 +388,31 @@ github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4k github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cobra v1.0.0 h1:6m/oheQuQ13N9ks4hubMG6BnvwOeaJrqSPLahSnczz8= -github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= github.com/spf13/cobra v1.1.1 h1:KfztREH0tPxJJ+geloSLaAkaPkr4ki2Er5quFV1TDo4= github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= -github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= -github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/testify v1.2.0/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/tidwall/pretty v0.0.0-20180105212114-65a9db5fad51/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= -github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= github.com/xanzy/go-gitlab v0.15.0/go.mod h1:8zdQa/ri1dfn8eS3Ir1SyfvOKlw7WBJ8DVThkpGiXrs= github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I= github.com/xdg/stringprep v1.0.0/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= -github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -516,8 +489,6 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190522155817-f3200d17e092 h1:4QSRKanuywn15aTZvI/mIDEgPQpswuFndXpOj3rKEco= -golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -540,8 +511,6 @@ golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81R golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201029221708-28c70e62bb1d/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201031054903-ff519b6c9102 h1:42cLlJJdEh+ySyeUUbEQ5bsTiq8voBeTuweGVkY6Puw= -golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210119194325-5f4716e94777 h1:003p0dJM77cxMSyCPFphvZf/Y5/NXf5fzg6ufd1/Oew= golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/oauth2 v0.0.0-20180227000427-d7d64896b5ff/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -575,7 +544,6 @@ golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b h1:ag/x1USPSsqHud38I9BAC88qdNLDHHtQ4mlgQIZPPNA= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -602,9 +570,7 @@ golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200826173525-f9321e4c35a6/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f h1:+Nyd8tzPX9R7BWHguqsrbFdRx3WQ/1ib8I44HXV5yTA= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201029080932-201ba4db2418 h1:HlFl4V6pEMziuLXyRkm5BIYq1y1GAbb02pRlWvI54OM= golang.org/x/sys v0.0.0-20201029080932-201ba4db2418/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -618,8 +584,8 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e h1:EHBhcS0mlXEAVwNyO2dLfjToGsyY4j24pTs2ScHnX7s= golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20201208040808-7e3f01d25324 h1:Hir2P/De0WpUhtrKGGjvSb2YxUgyZ7EFOSLIcSSpiwE= golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -739,7 +705,6 @@ google.golang.org/genproto v0.0.0-20201030142918-24207fddd1c3 h1:sg8vLDNIxFPHTch google.golang.org/genproto v0.0.0-20201030142918-24207fddd1c3/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= -google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= @@ -751,7 +716,6 @@ google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3Iji google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.33.1 h1:DGeFlSan2f+WEtCERJ4J9GJWk15TxUi8QGagfI87Xyc= google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= google.golang.org/grpc v1.35.0 h1:TwIQcH3es+MojMVojxxfQ3l3OF2KzlRxML2xZq0kRo8= google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= @@ -779,7 +743,6 @@ gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= @@ -789,6 +752,7 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= +gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0= gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/pkg/daemon/daemon.go b/pkg/daemon/daemon.go index 4d0ffbf..f63b613 100644 --- a/pkg/daemon/daemon.go +++ b/pkg/daemon/daemon.go @@ -27,8 +27,10 @@ func Start(cnf *config.Config, cachedEntries uint, flogger logger.Logger) error return err } + ctx := context.Background() + // Add - repoDevice, err := repo.GetDevice(cnf.DeviceID) + repoDevice, err := repo.GetDeviceByID(ctx, cnf.DeviceID) switch { case err != nil: return err @@ -39,7 +41,7 @@ func Start(cnf *config.Config, cachedEntries uint, flogger logger.Logger) error return err } - err = repo.AddDevices(&types.Device{ + err = repo.AddDevices(ctx, &types.Device{ ID: cnf.DeviceID, Name: hostname, }) @@ -47,13 +49,13 @@ func Start(cnf *config.Config, cachedEntries uint, flogger logger.Logger) error return err } - repoDevice, err = repo.GetDevice(cnf.DeviceID) + repoDevice, err = repo.GetDeviceByID(ctx, cnf.DeviceID) if err != nil { return err } } - repoSensors, err := repo.GetSensorsByDeviceID(repoDevice.ID) + repoSensors, err := repo.GetSensorsByDeviceIDs(ctx, repoDevice.ID) switch { case err != nil: return err @@ -110,7 +112,7 @@ func Start(cnf *config.Config, cachedEntries uint, flogger logger.Logger) error if cap(measuredValues) == len(measuredValues) { flogger.Debug("Flush cache with %v values", len(measuredValues)) - err := repo.AddMeasuredValues(measuredValues...) + err := repo.AddMeasuredValues(ctx, measuredValues...) if err != nil { flogger.Error("%v", err) } @@ -124,7 +126,7 @@ func Start(cnf *config.Config, cachedEntries uint, flogger logger.Logger) error flogger.Debug("Flush cache with %v remaining values", len(measuredValues)) - err := repo.AddMeasuredValues(measuredValues...) + err := repo.AddMeasuredValues(ctx, measuredValues...) if err != nil { flogger.Error("%v", err) } diff --git a/pkg/repository/db/db.go b/pkg/repository/db/db.go deleted file mode 100644 index 3f78c4e..0000000 --- a/pkg/repository/db/db.go +++ /dev/null @@ -1,134 +0,0 @@ -package db - -import ( - "context" - "database/sql" - "fmt" - "net/url" - "os" - "path/filepath" - - postgresdml "git.cryptic.systems/volker.raschek/flucky/pkg/repository/db/postgres/dml" - sqlite3dml "git.cryptic.systems/volker.raschek/flucky/pkg/repository/db/sqlite3/dml" - "git.cryptic.systems/volker.raschek/flucky/pkg/types" - "git.cryptic.systems/volker.raschek/go-logger" -) - -// Database is a general interface for a database backend like postgres, oracle -// or sqlite -type Database interface { - Close() error - DeleteDevices(ctx context.Context, deviceIDs ...string) error - DeleteSensors(ctx context.Context, sensorIDs ...string) error - InsertDevices(ctx context.Context, devices ...*types.Device) error - InsertOrUpdateDevices(ctx context.Context, devices ...*types.Device) error - InsertMeasuredValues(ctx context.Context, measuredValues ...*types.MeasuredValue) error - InsertOrUpdateMeasuredValues(ctx context.Context, measuredValues ...*types.MeasuredValue) error - InsertSensors(ctx context.Context, sensors ...*types.Sensor) error - InsertOrUpdateSensors(ctx context.Context, sensors ...*types.Sensor) error - Migrate(ctx context.Context) error - SelectDevice(ctx context.Context, deviceID string) (*types.Device, error) - SelectDevices(ctx context.Context) ([]*types.Device, error) - SelectHumidity(ctx context.Context, id string) (*types.MeasuredValue, error) - SelectHumidities(ctx context.Context) ([]*types.MeasuredValue, error) - SelectPressure(ctx context.Context, id string) (*types.MeasuredValue, error) - SelectPressures(ctx context.Context) ([]*types.MeasuredValue, error) - SelectSensor(ctx context.Context, sensorID string) (*types.Sensor, error) - SelectSensors(ctx context.Context) ([]*types.Sensor, error) - SelectTemperature(ctx context.Context, id string) (*types.MeasuredValue, error) - SelectTemperatures(ctx context.Context) ([]*types.MeasuredValue, error) - UpdateDevices(ctx context.Context, devices ...*types.Device) error - UpdateSensors(ctx context.Context, sensors ...*types.Sensor) error -} - -// New returns a new database backend interface -func New(databaseURL *url.URL, flogger logger.Logger) (Database, error) { - - // Check of nil pointer - for _, parameter := range []interface{}{ - databaseURL, - flogger, - } { - if parameter == nil { - return nil, fmt.Errorf("Parameter does not be nil") - } - } - - var ( - database Database - err error - ) - - switch databaseURL.Scheme { - case "postgres": - - // postgres://[user]:[password]@[host]:[port]/[path]?[query] - newDBO, err := sql.Open(databaseURL.Scheme, databaseURL.String()) - if err != nil { - return nil, err - } - - queries := make(map[string]string, 0) - for _, assetName := range postgresdml.AssetNames() { - body, err := postgresdml.Asset(assetName) - if err != nil { - return nil, fmt.Errorf("Failed to load asset %v, %w", assetName, err) - } - queries[assetName] = string(body) - } - - database = &Postgres{ - databaseURL: databaseURL, - dbo: newDBO, - flogger: flogger, - queries: queries, - } - - case "sqlite3": - - // Create directory if not exist - if _, err := os.Stat(filepath.Dir(databaseURL.Path)); os.IsNotExist(err) { - err := os.MkdirAll(filepath.Dir(databaseURL.Path), 0755) - if err != nil { - return nil, err - } - } - - // enable foreign keys - values := databaseURL.Query() - values.Set("_foreign_keys", "on") - - customRawURL := fmt.Sprintf("file://%v?%v", databaseURL.Path, values.Encode()) - - sqlDB, err := sql.Open("sqlite3", customRawURL) - if err != nil { - return nil, err - } - - queries := make(map[string]string, 0) - for _, assetName := range sqlite3dml.AssetNames() { - body, err := sqlite3dml.Asset(assetName) - if err != nil { - return nil, fmt.Errorf("Failed to load asset %v, %w", assetName, err) - } - queries[assetName] = string(body) - } - - database = &SQLite{ - databaseURL: databaseURL, - dbo: sqlDB, - flogger: flogger, - queries: queries, - } - default: - return nil, fmt.Errorf("Unsupported database scheme: %v", databaseURL.Scheme) - } - - // Initialize database scheme if not exists - err = database.Migrate(context.Background()) - if err != nil { - return nil, err - } - - return database, nil -} diff --git a/pkg/repository/db/postgres.go b/pkg/repository/db/postgres.go deleted file mode 100644 index 88d8c12..0000000 --- a/pkg/repository/db/postgres.go +++ /dev/null @@ -1,951 +0,0 @@ -package db - -import ( - "context" - "database/sql" - "errors" - "fmt" - "net/url" - "time" - - postgresddl "git.cryptic.systems/volker.raschek/flucky/pkg/repository/db/postgres/ddl" - "git.cryptic.systems/volker.raschek/flucky/pkg/types" - "git.cryptic.systems/volker.raschek/go-logger" - "github.com/golang-migrate/migrate/v4" - bindata "github.com/golang-migrate/migrate/v4/source/go_bindata" -) - -// Postgres implementation -type Postgres struct { - databaseURL *url.URL - dbo *sql.DB - flogger logger.Logger - queries map[string]string -} - -// Close closes the database and prevents new queries from starting. Close then -// waits for all queries that have started processing on the server to finish. -func (postgres *Postgres) Close() error { - return postgres.dbo.Close() -} - -// DeleteDevices from the database -func (postgres *Postgres) DeleteDevices(ctx context.Context, deviceIDs ...string) error { - queryFile := "deleteDevice.sql" - query, present := postgres.queries[queryFile] - if !present { - return fmt.Errorf("Postgres-Backend: File %v not found", queryFile) - } - - tx, err := postgres.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: false}) - if err != nil { - return fmt.Errorf("Failed to begin new transaction: %v", err) - } - - stmt, err := tx.Prepare(query) - if err != nil { - return fmt.Errorf("Failed to prepare statement: %v", err) - } - defer stmt.Close() - - for _, deviceID := range deviceIDs { - _, err = stmt.Exec(deviceID) - if err != nil { - tx.Rollback() - return err - } - } - - return tx.Commit() -} - -// DeleteSensors from the database -func (postgres *Postgres) DeleteSensors(ctx context.Context, sensorIDs ...string) error { - queryFile := "deleteSensor.sql" - query, present := postgres.queries[queryFile] - if !present { - return fmt.Errorf("Postgres-Backend: File %v not found", queryFile) - } - - tx, err := postgres.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: false}) - if err != nil { - return fmt.Errorf("Failed to begin new transaction: %v", err) - } - - stmt, err := tx.Prepare(query) - if err != nil { - return fmt.Errorf("Failed to prepare statement: %v", err) - } - defer stmt.Close() - - for _, sensorID := range sensorIDs { - _, err = stmt.Exec(sensorID) - if err != nil { - tx.Rollback() - return err - } - } - - return tx.Commit() -} - -// InsertDevices into the database -func (postgres *Postgres) InsertDevices(ctx context.Context, devices ...*types.Device) error { - tx, err := postgres.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: false}) - if err != nil { - return fmt.Errorf("Failed to begin new transaction: %v", err) - } - - err = postgres.insertDevices(tx, devices...) - if err != nil { - return err - } - - return tx.Commit() -} - -func (postgres *Postgres) insertDevices(tx *sql.Tx, devices ...*types.Device) error { - queryFile := "insertDevice.sql" - query, present := postgres.queries[queryFile] - if !present { - return fmt.Errorf("Postgres-Backend: File %v not found", queryFile) - } - - stmt, err := tx.Prepare(query) - if err != nil { - return fmt.Errorf("Failed to prepare statement: %v", err) - } - defer stmt.Close() - - for _, device := range devices { - - if device.CreationDate.Equal(time.Time{}) { - device.CreationDate = time.Now() - } - - _, err = stmt.Exec( - &device.ID, - &device.Name, - &device.Location, - &device.CreationDate, - &device.UpdateDate, - ) - if err != nil { - tx.Rollback() - return fmt.Errorf("Failed to execute statement: %v", err) - } - } - - return nil -} - -// InsertMeasuredValues into the database -func (postgres *Postgres) InsertMeasuredValues(ctx context.Context, measuredValues ...*types.MeasuredValue) error { - splittedMeasuredValues := make(map[types.MeasuredValueType][]*types.MeasuredValue, 0) - - for _, measuredValue := range measuredValues { - if _, ok := splittedMeasuredValues[measuredValue.ValueType]; !ok { - splittedMeasuredValues[measuredValue.ValueType] = make([]*types.MeasuredValue, 0) - } - splittedMeasuredValues[measuredValue.ValueType] = append(splittedMeasuredValues[measuredValue.ValueType], measuredValue) - } - - tx, err := postgres.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: false}) - if err != nil { - return fmt.Errorf("Failed to begin new transaction: %v", err) - } - - // General insert function - insert := func(tx *sql.Tx, queryFile string, measuredValues []*types.MeasuredValue) error { - query, present := postgres.queries[queryFile] - if !present { - return fmt.Errorf("Postgres-Backend: File %v not found", queryFile) - } - - stmt, err := tx.Prepare(query) - if err != nil { - return fmt.Errorf("Failed to prepare statement: %v", err) - } - defer stmt.Close() - - for _, measuredValue := range measuredValues { - - if measuredValue.CreationDate.Equal(time.Time{}) { - measuredValue.CreationDate = time.Now() - } - - _, err := stmt.Exec( - &measuredValue.ID, - &measuredValue.Value, - &measuredValue.Date, - &measuredValue.SensorID, - &measuredValue.CreationDate, - &measuredValue.UpdateDate, - ) - - if err != nil { - return fmt.Errorf("Failed to execute statement: %v", err) - } - } - - return nil - } - - for measuredValueType, measuredValues := range splittedMeasuredValues { - var queryFile string - - switch measuredValueType { - case types.Humidity: - queryFile = "insertHumidity.sql" - case types.Pressure: - queryFile = "insertPressure.sql" - case types.Temperature: - queryFile = "insertTemperature.sql" - default: - tx.Rollback() - return fmt.Errorf("Measured value type %v not supported", measuredValueType) - } - - err := insert(tx, queryFile, measuredValues) - if err != nil { - tx.Rollback() - return err - } - } - - return tx.Commit() -} - -func (postgres *Postgres) InsertOrUpdateDevices(ctx context.Context, devices ...*types.Device) error { - queryFile := "insertOrUpdateDevice.sql" - query, present := postgres.queries[queryFile] - if !present { - return fmt.Errorf("Postgres-Backend: File %v not found", queryFile) - } - - tx, err := postgres.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: false}) - if err != nil { - return fmt.Errorf("Failed to begin new transaction: %v", err) - } - - for _, device := range devices { - - if device.CreationDate.Equal(time.Time{}) { - device.CreationDate = time.Now() - } - - _, err = tx.Exec( - query, - &device.ID, - &device.Name, - &device.Location, - &device.CreationDate, - &device.UpdateDate, - ) - if err != nil { - tx.Rollback() - return fmt.Errorf("Failed to execute statement: %v", err) - } - } - - return tx.Commit() -} - -// InsertOrUpdateMeasuredValues into the database -func (postgres *Postgres) InsertOrUpdateMeasuredValues(ctx context.Context, measuredValues ...*types.MeasuredValue) error { - splittedMeasuredValues := make(map[types.MeasuredValueType][]*types.MeasuredValue, 0) - - for _, measuredValue := range measuredValues { - if _, ok := splittedMeasuredValues[measuredValue.ValueType]; !ok { - splittedMeasuredValues[measuredValue.ValueType] = make([]*types.MeasuredValue, 0) - } - splittedMeasuredValues[measuredValue.ValueType] = append(splittedMeasuredValues[measuredValue.ValueType], measuredValue) - } - - tx, err := postgres.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: false}) - if err != nil { - return fmt.Errorf("Failed to begin new transaction: %v", err) - } - - // General insert function - insert := func(tx *sql.Tx, queryFile string, measuredValues []*types.MeasuredValue) error { - query, present := postgres.queries[queryFile] - if !present { - return fmt.Errorf("Postgres-Backend: File %v not found", queryFile) - } - - stmt, err := tx.Prepare(query) - if err != nil { - return fmt.Errorf("Failed to prepare statement: %v", err) - } - defer stmt.Close() - - for _, measuredValue := range measuredValues { - - if measuredValue.CreationDate.Equal(time.Time{}) { - measuredValue.CreationDate = time.Now() - } - - _, err := stmt.Exec( - &measuredValue.ID, - &measuredValue.Value, - &measuredValue.Date, - &measuredValue.SensorID, - &measuredValue.CreationDate, - &measuredValue.UpdateDate, - ) - - if err != nil { - return fmt.Errorf("Failed to execute statement: %v", err) - } - } - - return nil - } - - for measuredValueType, measuredValues := range splittedMeasuredValues { - var queryFile string - - switch measuredValueType { - case types.Humidity: - queryFile = "insertOrUpdateHumidity.sql" - case types.Pressure: - queryFile = "insertOrUpdatePressure.sql" - case types.Temperature: - queryFile = "insertOrUpdateTemperature.sql" - default: - tx.Rollback() - return fmt.Errorf("Measured value type %v not supported", measuredValueType) - } - - err := insert(tx, queryFile, measuredValues) - if err != nil { - tx.Rollback() - return err - } - } - - return tx.Commit() -} - -// InsertOrUpdateSensors into the database -func (postgres *Postgres) InsertOrUpdateSensors(ctx context.Context, sensors ...*types.Sensor) error { - queryFile := "insertOrUpdateSensor.sql" - query, present := postgres.queries[queryFile] - if !present { - return fmt.Errorf("Postgres-Backend: File %v not found", queryFile) - } - - tx, err := postgres.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: false}) - if err != nil { - return fmt.Errorf("Failed to begin new transaction: %v", err) - } - - stmt, err := tx.Prepare(query) - if err != nil { - return fmt.Errorf("Failed to prepare statement: %v", err) - } - defer stmt.Close() - - for _, sensor := range sensors { - - if sensor.CreationDate.Equal(time.Time{}) { - sensor.CreationDate = time.Now() - } - - _, err = stmt.Exec( - &sensor.ID, - &sensor.Name, - &sensor.Location, - &sensor.WireID, - &sensor.I2CBus, - &sensor.I2CAddress, - &sensor.GPIONumber, - &sensor.Model, - &sensor.Enabled, - &sensor.TickDuration, - &sensor.DeviceID, - &sensor.CreationDate, - &sensor.UpdateDate, - ) - if err != nil { - tx.Rollback() - return fmt.Errorf("Failed to execute statement: %v", err) - } - } - - return tx.Commit() -} - -// InsertSensors into the database -func (postgres *Postgres) InsertSensors(ctx context.Context, sensors ...*types.Sensor) error { - queryFile := "insertSensor.sql" - query, present := postgres.queries[queryFile] - if !present { - return fmt.Errorf("Postgres-Backend: File %v not found", queryFile) - } - - tx, err := postgres.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: false}) - if err != nil { - return fmt.Errorf("Failed to begin new transaction: %v", err) - } - - stmt, err := tx.Prepare(query) - if err != nil { - return fmt.Errorf("Failed to prepare statement: %v", err) - } - defer stmt.Close() - - for _, sensor := range sensors { - - if sensor.CreationDate.Equal(time.Time{}) { - sensor.CreationDate = time.Now() - } - - _, err = stmt.Exec( - &sensor.ID, - &sensor.Name, - &sensor.Location, - &sensor.WireID, - &sensor.I2CBus, - &sensor.I2CAddress, - &sensor.GPIONumber, - &sensor.Model, - &sensor.Enabled, - &sensor.TickDuration, - &sensor.DeviceID, - &sensor.CreationDate, - &sensor.UpdateDate, - ) - if err != nil { - tx.Rollback() - return fmt.Errorf("Failed to execute statement: %v", err) - } - } - - return tx.Commit() -} - -// Migrate creates all required tables if not exist -func (postgres *Postgres) Migrate(ctx context.Context) error { - assetSource := bindata.Resource(postgresddl.AssetNames(), func(query string) ([]byte, error) { - return postgresddl.Asset(query) - }) - - sourceDriver, err := bindata.WithInstance(assetSource) - if err != nil { - return err - } - - m, err := migrate.NewWithSourceInstance("bindata", sourceDriver, postgres.databaseURL.String()) - if err != nil { - return err - } - - err = m.Up() - switch { - case errors.Is(err, migrate.ErrNoChange): - return nil - default: - return err - } -} - -// SelectDevice from database -func (postgres *Postgres) SelectDevice(ctx context.Context, id string) (*types.Device, error) { - queryFile := "selectDevice.sql" - query, present := postgres.queries[queryFile] - if !present { - return nil, fmt.Errorf("Postgres-Backend: File %v not found", queryFile) - } - - tx, err := postgres.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: true}) - if err != nil { - return nil, fmt.Errorf("Failed to begin new transaction: %v", err) - } - - devices, err := postgres.selectDevices(tx, query, id) - if err != nil { - return nil, err - } - - err = tx.Commit() - if err != nil { - return nil, err - } - - if len(devices) == 0 { - return nil, nil - } - - return devices[0], nil -} - -// SelectDevices from the database -func (postgres *Postgres) SelectDevices(ctx context.Context) ([]*types.Device, error) { - queryFile := "selectDevices.sql" - query, present := postgres.queries[queryFile] - if !present { - return nil, fmt.Errorf("Postgres-Backend: File %v not found", queryFile) - } - - tx, err := postgres.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: true}) - if err != nil { - return nil, fmt.Errorf("Failed to begin new transaction: %v", err) - } - - devices, err := postgres.selectDevices(tx, query) - if err != nil { - return nil, err - } - - err = tx.Commit() - if err != nil { - return nil, fmt.Errorf("Failed to commit transaction: %v", err) - } - - return devices, nil -} - -func (postgres *Postgres) selectDevices(tx *sql.Tx, query string, args ...interface{}) ([]*types.Device, error) { - stmt, err := tx.Prepare(query) - if err != nil { - return nil, fmt.Errorf("Failed to prepare statement: %v", err) - } - defer stmt.Close() - - rows, err := stmt.Query(args...) - if err != nil { - return nil, fmt.Errorf("Failed to query statement: %v", err) - } - - devices := make([]*types.Device, 0) - for rows.Next() { - device := new(types.Device) - err = rows.Scan( - &device.ID, - &device.Name, - &device.Location, - &device.CreationDate, - &device.UpdateDate, - ) - if err != nil { - return nil, fmt.Errorf("Failed to scan row: %v", err) - } - devices = append(devices, device) - } - - return devices, nil -} - -// SelectHumidity returns humidity from the database -func (postgres *Postgres) SelectHumidity(ctx context.Context, id string) (*types.MeasuredValue, error) { - queryFile := "selectHumidity.sql" - query, present := postgres.queries[queryFile] - if !present { - return nil, fmt.Errorf("Postgres-Backend: File %v not found", queryFile) - } - - tx, err := postgres.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: true}) - if err != nil { - return nil, err - } - - measuredValues, err := postgres.selectMeasuredValue(tx, query, id) - if err != nil { - return nil, err - } - - err = tx.Commit() - if err != nil { - return nil, err - } - - if measuredValues == nil { - return nil, nil - } - - for _, measuredValue := range measuredValues { - measuredValue.ValueType = types.Humidity - } - - return measuredValues[0], nil -} - -// SelectHumidities returns humidities from the database -func (postgres *Postgres) SelectHumidities(ctx context.Context) ([]*types.MeasuredValue, error) { - queryFile := "selectHumidities.sql" - query, present := postgres.queries[queryFile] - if !present { - return nil, fmt.Errorf("Postgres-Backend: File %v not found", queryFile) - } - - tx, err := postgres.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: true}) - if err != nil { - return nil, err - } - - measuredValues, err := postgres.selectMeasuredValue(tx, query) - if err != nil { - return nil, err - } - - err = tx.Commit() - if err != nil { - return nil, err - } - - for _, measuredValue := range measuredValues { - measuredValue.ValueType = types.Humidity - } - - return measuredValues, nil -} - -func (postgres *Postgres) selectMeasuredValue(tx *sql.Tx, query string, args ...interface{}) ([]*types.MeasuredValue, error) { - stmt, err := tx.Prepare(query) - if err != nil { - tx.Rollback() - return nil, err - } - defer stmt.Close() - - rows, err := stmt.Query(args...) - if err != nil { - tx.Rollback() - return nil, err - } - - measuredValues := make([]*types.MeasuredValue, 0) - for rows.Next() { - measuredValue := new(types.MeasuredValue) - err := rows.Scan( - &measuredValue.ID, - &measuredValue.Value, - &measuredValue.Date, - &measuredValue.SensorID, - &measuredValue.CreationDate, - &measuredValue.UpdateDate, - ) - - if err != nil { - tx.Rollback() - return nil, err - } - - measuredValues = append(measuredValues, measuredValue) - } - - return measuredValues, nil -} - -// SelectPressure returns pressure from the database -func (postgres *Postgres) SelectPressure(ctx context.Context, id string) (*types.MeasuredValue, error) { - queryFile := "selectPressure.sql" - query, present := postgres.queries[queryFile] - if !present { - return nil, fmt.Errorf("Postgres-Backend: File %v not found", queryFile) - } - - tx, err := postgres.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: true}) - if err != nil { - return nil, err - } - - measuredValues, err := postgres.selectMeasuredValue(tx, query, id) - if err != nil { - return nil, err - } - - err = tx.Commit() - if err != nil { - return nil, err - } - - if measuredValues == nil { - return nil, nil - } - - for _, measuredValue := range measuredValues { - measuredValue.ValueType = types.Pressure - } - - return measuredValues[0], nil -} - -// SelectPressures returns pressure from the database -func (postgres *Postgres) SelectPressures(ctx context.Context) ([]*types.MeasuredValue, error) { - queryFile := "selectPressures.sql" - query, present := postgres.queries[queryFile] - if !present { - return nil, fmt.Errorf("Postgres-Backend: File %v not found", queryFile) - } - - tx, err := postgres.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: true}) - if err != nil { - return nil, err - } - - measuredValues, err := postgres.selectMeasuredValue(tx, query) - if err != nil { - return nil, err - } - - err = tx.Commit() - if err != nil { - return nil, err - } - - for _, measuredValue := range measuredValues { - measuredValue.ValueType = types.Pressure - } - - return measuredValues, nil -} - -// SelectSensor from database -func (postgres *Postgres) SelectSensor(ctx context.Context, id string) (*types.Sensor, error) { - queryFile := "selectSensor.sql" - query, present := postgres.queries[queryFile] - if !present { - return nil, fmt.Errorf("Postgres-Backend: File %v not found", queryFile) - } - - tx, err := postgres.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: true}) - if err != nil { - return nil, fmt.Errorf("Failed to begin new transaction: %v", err) - } - - sensors, err := postgres.selectSensors(tx, query, id) - if err != nil { - return nil, err - } - - err = tx.Commit() - if err != nil { - return nil, fmt.Errorf("Failed to commit transaction: %v", err) - } - - if len(sensors) == 0 { - return nil, nil - } - - return sensors[0], nil -} - -// SelectSensors from the database -func (postgres *Postgres) SelectSensors(ctx context.Context) ([]*types.Sensor, error) { - queryFile := "selectSensors.sql" - query, present := postgres.queries[queryFile] - if !present { - return nil, fmt.Errorf("Postgres-Backend: File %v not found", queryFile) - } - - tx, err := postgres.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: true}) - if err != nil { - return nil, fmt.Errorf("Failed to begin new transaction: %v", err) - } - - sensors, err := postgres.selectSensors(tx, query) - if err != nil { - return nil, err - } - - err = tx.Commit() - if err != nil { - return nil, fmt.Errorf("Failed to commit transaction: %v", err) - } - - return sensors, nil -} - -func (postgres *Postgres) selectSensors(tx *sql.Tx, query string, args ...interface{}) ([]*types.Sensor, error) { - stmt, err := tx.Prepare(query) - if err != nil { - return nil, fmt.Errorf("Failed to prepare statement: %v", err) - } - defer stmt.Close() - - rows, err := stmt.Query(args...) - if err != nil { - return nil, fmt.Errorf("Failed to query statement: %v", err) - } - - sensors := make([]*types.Sensor, 0) - for rows.Next() { - sensor := new(types.Sensor) - err = rows.Scan( - &sensor.ID, - &sensor.Name, - &sensor.Location, - &sensor.WireID, - &sensor.I2CBus, - &sensor.I2CAddress, - &sensor.GPIONumber, - &sensor.Model, - &sensor.Enabled, - &sensor.TickDuration, - &sensor.DeviceID, - &sensor.CreationDate, - &sensor.UpdateDate, - ) - - if err != nil { - return nil, fmt.Errorf("Failed to scan row: %v", err) - } - sensors = append(sensors, sensor) - } - - return sensors, nil -} - -// SelectTemperature returns temperatures from the database -func (postgres *Postgres) SelectTemperature(ctx context.Context, id string) (*types.MeasuredValue, error) { - queryFile := "selectTemperature.sql" - query, present := postgres.queries[queryFile] - if !present { - return nil, fmt.Errorf("Postgres-Backend: File %v not found", queryFile) - } - - tx, err := postgres.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: true}) - if err != nil { - return nil, err - } - - measuredValues, err := postgres.selectMeasuredValue(tx, query, id) - if err != nil { - return nil, err - } - - err = tx.Commit() - if err != nil { - return nil, err - } - - if measuredValues == nil { - return nil, nil - } - - for _, measuredValue := range measuredValues { - measuredValue.ValueType = types.Temperature - } - - return measuredValues[0], nil -} - -// SelectTemperatures returns temperatures from the database -func (postgres *Postgres) SelectTemperatures(ctx context.Context) ([]*types.MeasuredValue, error) { - queryFile := "selectTemperatures.sql" - query, present := postgres.queries[queryFile] - if !present { - return nil, fmt.Errorf("Postgres-Backend: File %v not found", queryFile) - } - - tx, err := postgres.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: true}) - if err != nil { - return nil, err - } - - measuredValues, err := postgres.selectMeasuredValue(tx, query) - if err != nil { - return nil, err - } - - err = tx.Commit() - if err != nil { - return nil, err - } - - for _, measuredValue := range measuredValues { - measuredValue.ValueType = types.Temperature - } - - return measuredValues, nil -} - -// UpdateDevices updates a device in the database -func (postgres *Postgres) UpdateDevices(ctx context.Context, devices ...*types.Device) error { - queryFile := "updateDevice.sql" - query, present := postgres.queries[queryFile] - if !present { - return fmt.Errorf("Postgres-Backend: File %v not found", queryFile) - } - - tx, err := postgres.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: false}) - if err != nil { - return err - } - - stmt, err := tx.Prepare(query) - if err != nil { - tx.Rollback() - return err - } - defer stmt.Close() - - for _, device := range devices { - now := time.Now() - device.UpdateDate = &now - - _, err := stmt.Exec( - &device.Name, - &device.Location, - &device.CreationDate, - &device.UpdateDate, - &device.ID, - ) - if err != nil { - tx.Rollback() - return err - } - } - - return tx.Commit() -} - -// UpdateSensors updates a sensor in the database -func (postgres *Postgres) UpdateSensors(ctx context.Context, sensors ...*types.Sensor) error { - queryFile := "updateSensor.sql" - query, present := postgres.queries[queryFile] - if !present { - return fmt.Errorf("Postgres-Backend: File %v not found", queryFile) - } - - tx, err := postgres.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: false}) - if err != nil { - return err - } - - stmt, err := tx.Prepare(query) - if err != nil { - tx.Rollback() - return err - } - defer stmt.Close() - - for _, sensor := range sensors { - now := time.Now() - sensor.UpdateDate = &now - _, err := stmt.Exec( - &sensor.Name, - &sensor.Location, - &sensor.WireID, - &sensor.I2CBus, - &sensor.I2CAddress, - &sensor.GPIONumber, - &sensor.Model, - &sensor.Enabled, - &sensor.TickDuration, - &sensor.DeviceID, - &sensor.CreationDate, - &sensor.UpdateDate, - &sensor.ID, - ) - if err != nil { - tx.Rollback() - return err - } - } - - return tx.Commit() -} diff --git a/pkg/repository/db/sqlite.go b/pkg/repository/db/sqlite.go deleted file mode 100644 index 514e417..0000000 --- a/pkg/repository/db/sqlite.go +++ /dev/null @@ -1,939 +0,0 @@ -package db - -import ( - "context" - "database/sql" - "errors" - "fmt" - "net/url" - - sqlite3ddl "git.cryptic.systems/volker.raschek/flucky/pkg/repository/db/sqlite3/ddl" - "git.cryptic.systems/volker.raschek/flucky/pkg/types" - "git.cryptic.systems/volker.raschek/go-logger" - "github.com/golang-migrate/migrate/v4" - bindata "github.com/golang-migrate/migrate/v4/source/go_bindata" -) - -// SQLite implementation -type SQLite struct { - databaseURL *url.URL - dbo *sql.DB - flogger logger.Logger - queries map[string]string -} - -// Close closes the database and prevents new queries from starting. Close then -// waits for all queries that have started processing on the server to finish. -func (sqlite *SQLite) Close() error { - return sqlite.dbo.Close() -} - -// DeleteDevices from the database -func (sqlite *SQLite) DeleteDevices(ctx context.Context, deviceIDs ...string) error { - queryFile := "deleteDevice.sql" - query, present := sqlite.queries[queryFile] - if !present { - return fmt.Errorf("SQLite-Backend: File %v not found", queryFile) - } - - tx, err := sqlite.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: false}) - if err != nil { - return fmt.Errorf("Failed to begin new transaction: %v", err) - } - - stmt, err := tx.Prepare(query) - if err != nil { - return fmt.Errorf("Failed to prepare statement: %v", err) - } - defer stmt.Close() - - for _, deviceID := range deviceIDs { - _, err = stmt.Exec(deviceID) - if err != nil { - tx.Rollback() - return err - } - } - - return tx.Commit() -} - -// DeleteSensors from the database -func (sqlite *SQLite) DeleteSensors(ctx context.Context, sensorIDs ...string) error { - queryFile := "deleteSensor.sql" - query, present := sqlite.queries[queryFile] - if !present { - return fmt.Errorf("SQLite-Backend: File %v not found", queryFile) - } - - tx, err := sqlite.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: false}) - if err != nil { - return fmt.Errorf("Failed to begin new transaction: %v", err) - } - - stmt, err := tx.Prepare(query) - if err != nil { - return fmt.Errorf("Failed to prepare statement: %v", err) - } - defer stmt.Close() - - for _, sensorID := range sensorIDs { - _, err = stmt.Exec(sensorID) - if err != nil { - tx.Rollback() - return err - } - } - - return tx.Commit() -} - -func (sqlite *SQLite) ExistDevice(ctx context.Context, deviceID string) (bool, error) { - queryFile := "existDevice.sql" - query, present := sqlite.queries[queryFile] - if !present { - return false, fmt.Errorf("SQLite-Backend: File %v not found", queryFile) - } - - tx, err := sqlite.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: false}) - if err != nil { - return false, fmt.Errorf("Failed to begin new transaction: %v", err) - } - - stmt, err := tx.Prepare(query) - if err != nil { - return false, fmt.Errorf("Failed to prepare statement: %v", err) - } - defer stmt.Close() - - rows, err := stmt.Query() - if err != nil { - return false, fmt.Errorf("Failed to query statement: %v", err) - } - defer rows.Close() - - var t bool - for rows.Next() { - rows.Scan(&t) - } - - return t, nil -} - -func (sqlite *SQLite) ExistDevices(ctx context.Context, deviceIDs ...string) (map[string]bool, error) { - return nil, nil -} - -// InsertDevices into the database -func (sqlite *SQLite) InsertDevices(ctx context.Context, devices ...*types.Device) error { - queryFile := "insertDevice.sql" - query, present := sqlite.queries[queryFile] - if !present { - return fmt.Errorf("SQLite-Backend: File %v not found", queryFile) - } - - tx, err := sqlite.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: false}) - if err != nil { - return fmt.Errorf("Failed to begin new transaction: %v", err) - } - - stmt, err := tx.Prepare(query) - if err != nil { - return fmt.Errorf("Failed to prepare statement: %v", err) - } - defer stmt.Close() - - for _, device := range devices { - _, err = stmt.Exec(&device.ID, &device.Name, &device.Location, &device.CreationDate, &device.UpdateDate) - if err != nil { - tx.Rollback() - return fmt.Errorf("Failed to execute statement: %v", err) - } - } - - return tx.Commit() -} - -// InsertMeasuredValues into the database -func (sqlite *SQLite) InsertMeasuredValues(ctx context.Context, measuredValues ...*types.MeasuredValue) error { - splittedMeasuredValues := make(map[types.MeasuredValueType][]*types.MeasuredValue, 0) - - for _, measuredValue := range measuredValues { - if _, ok := splittedMeasuredValues[measuredValue.ValueType]; !ok { - splittedMeasuredValues[measuredValue.ValueType] = make([]*types.MeasuredValue, 0) - } - splittedMeasuredValues[measuredValue.ValueType] = append(splittedMeasuredValues[measuredValue.ValueType], measuredValue) - } - - tx, err := sqlite.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: false}) - if err != nil { - return fmt.Errorf("Failed to begin new transaction: %v", err) - } - - // General insert function - insert := func(tx *sql.Tx, queryFile string, measuredValues []*types.MeasuredValue) error { - query, present := sqlite.queries[queryFile] - if !present { - return fmt.Errorf("SQLite-Backend: File %v not found", queryFile) - } - - stmt, err := tx.Prepare(query) - if err != nil { - return fmt.Errorf("Failed to prepare statement: %v", err) - } - defer stmt.Close() - - for _, measuredValue := range measuredValues { - _, err := stmt.Exec( - &measuredValue.ID, - &measuredValue.Value, - &measuredValue.Date, - &measuredValue.SensorID, - &measuredValue.CreationDate, - &measuredValue.UpdateDate, - ) - - if err != nil { - return fmt.Errorf("Failed to execute statement: %v", err) - } - } - - return nil - } - - for measuredValueType, measuredValues := range splittedMeasuredValues { - var queryFile string - - switch measuredValueType { - case types.Humidity: - queryFile = "insertHumidity.sql" - case types.Pressure: - queryFile = "insertPressure.sql" - case types.Temperature: - queryFile = "insertTemperature.sql" - default: - tx.Rollback() - return fmt.Errorf("Measured value type %v not supported", measuredValueType) - } - - err := insert(tx, queryFile, measuredValues) - if err != nil { - tx.Rollback() - return err - } - } - - return tx.Commit() -} - -// InsertOrUpdateMeasuredValues into the database -func (sqlite *SQLite) InsertOrUpdateMeasuredValues(ctx context.Context, measuredValues ...*types.MeasuredValue) error { - splittedMeasuredValues := make(map[types.MeasuredValueType][]*types.MeasuredValue, 0) - - for _, measuredValue := range measuredValues { - if _, ok := splittedMeasuredValues[measuredValue.ValueType]; !ok { - splittedMeasuredValues[measuredValue.ValueType] = make([]*types.MeasuredValue, 0) - } - splittedMeasuredValues[measuredValue.ValueType] = append(splittedMeasuredValues[measuredValue.ValueType], measuredValue) - } - - tx, err := sqlite.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: false}) - if err != nil { - return fmt.Errorf("Failed to begin new transaction: %v", err) - } - - // General insert function - insert := func(tx *sql.Tx, queryFile string, measuredValues []*types.MeasuredValue) error { - query, present := sqlite.queries[queryFile] - if !present { - return fmt.Errorf("SQLite-Backend: File %v not found", queryFile) - } - - stmt, err := tx.Prepare(query) - if err != nil { - return fmt.Errorf("Failed to prepare statement: %v", err) - } - defer stmt.Close() - - for _, measuredValue := range measuredValues { - _, err := stmt.Exec( - &measuredValue.ID, - &measuredValue.Value, - &measuredValue.Date, - &measuredValue.SensorID, - &measuredValue.CreationDate, - &measuredValue.UpdateDate, - ) - - if err != nil { - return fmt.Errorf("Failed to execute statement: %v", err) - } - } - - return nil - } - - for measuredValueType, measuredValues := range splittedMeasuredValues { - var queryFile string - - switch measuredValueType { - case types.Humidity: - queryFile = "insertOrUpdateHumidity.sql" - case types.Pressure: - queryFile = "insertOrUpdatePressure.sql" - case types.Temperature: - queryFile = "insertOrUpdateTemperature.sql" - default: - tx.Rollback() - return fmt.Errorf("Measured value type %v not supported", measuredValueType) - } - - err := insert(tx, queryFile, measuredValues) - if err != nil { - tx.Rollback() - return err - } - } - - return tx.Commit() -} - -// InsertOrUpdateDevices into the database -func (sqlite *SQLite) InsertOrUpdateDevices(ctx context.Context, devices ...*types.Device) error { - queryFile := "insertOrUpdateDevice.sql" - query, present := sqlite.queries[queryFile] - if !present { - return fmt.Errorf("SQLite-Backend: File %v not found", queryFile) - } - - tx, err := sqlite.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: false}) - if err != nil { - return fmt.Errorf("Failed to begin new transaction: %v", err) - } - - stmt, err := tx.Prepare(query) - if err != nil { - return fmt.Errorf("Failed to prepare statement: %v", err) - } - defer stmt.Close() - - for _, device := range devices { - _, err = stmt.Exec(&device.ID, &device.Name, &device.Location, &device.CreationDate, &device.UpdateDate) - if err != nil { - tx.Rollback() - return fmt.Errorf("Failed to execute statement: %v", err) - } - } - - return tx.Commit() -} - -// InsertOrUpdateSensors into the database -func (sqlite *SQLite) InsertOrUpdateSensors(ctx context.Context, sensors ...*types.Sensor) error { - queryFile := "insertOrUpdateSensor.sql" - query, present := sqlite.queries[queryFile] - if !present { - return fmt.Errorf("SQLite-Backend: File %v not found", queryFile) - } - - tx, err := sqlite.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: false}) - if err != nil { - return fmt.Errorf("Failed to begin new transaction: %v", err) - } - - stmt, err := tx.Prepare(query) - if err != nil { - return fmt.Errorf("Failed to prepare statement: %v", err) - } - defer stmt.Close() - - for _, sensor := range sensors { - _, err = stmt.Exec( - &sensor.ID, - &sensor.Name, - &sensor.Location, - &sensor.WireID, - &sensor.I2CBus, - &sensor.I2CAddress, - &sensor.GPIONumber, - &sensor.Model, - &sensor.Enabled, - &sensor.TickDuration, - &sensor.DeviceID, - &sensor.CreationDate, - &sensor.UpdateDate, - ) - if err != nil { - tx.Rollback() - return fmt.Errorf("Failed to execute statement: %v", err) - } - } - - return tx.Commit() -} - -// InsertSensors into the database -func (sqlite *SQLite) InsertSensors(ctx context.Context, sensors ...*types.Sensor) error { - queryFile := "insertSensor.sql" - query, present := sqlite.queries[queryFile] - if !present { - return fmt.Errorf("SQLite-Backend: File %v not found", queryFile) - } - - tx, err := sqlite.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: false}) - if err != nil { - return fmt.Errorf("Failed to begin new transaction: %v", err) - } - - stmt, err := tx.Prepare(query) - if err != nil { - return fmt.Errorf("Failed to prepare statement: %v", err) - } - defer stmt.Close() - - for _, sensor := range sensors { - _, err = stmt.Exec( - &sensor.ID, - &sensor.Name, - &sensor.Location, - &sensor.WireID, - &sensor.I2CBus, - &sensor.I2CAddress, - &sensor.GPIONumber, - &sensor.Model, - &sensor.Enabled, - &sensor.TickDuration, - &sensor.DeviceID, - &sensor.CreationDate, - &sensor.UpdateDate, - ) - if err != nil { - tx.Rollback() - return fmt.Errorf("Failed to execute statement: %v", err) - } - } - - return tx.Commit() -} - -// Migrate creates all required tables if not exist -func (sqlite *SQLite) Migrate(ctx context.Context) error { - assetSource := bindata.Resource(sqlite3ddl.AssetNames(), func(query string) ([]byte, error) { - return sqlite3ddl.Asset(query) - }) - - sourceDriver, err := bindata.WithInstance(assetSource) - if err != nil { - return err - } - - m, err := migrate.NewWithSourceInstance("bindata", sourceDriver, sqlite.databaseURL.String()) - if err != nil { - return err - } - - err = m.Up() - switch { - case errors.Is(err, migrate.ErrNoChange): - return nil - default: - return err - } -} - -// SelectDevice from database -func (sqlite *SQLite) SelectDevice(ctx context.Context, id string) (*types.Device, error) { - queryFile := "selectDevice.sql" - query, present := sqlite.queries[queryFile] - if !present { - return nil, fmt.Errorf("SQLite-Backend: File %v not found", queryFile) - } - - tx, err := sqlite.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: true}) - if err != nil { - return nil, fmt.Errorf("Failed to begin new transaction: %v", err) - } - - devices, err := sqlite.selectDevices(tx, query, id) - if err != nil { - return nil, err - } - - err = tx.Commit() - if err != nil { - return nil, err - } - - if len(devices) == 0 { - return nil, nil - } - - return devices[0], nil -} - -// SelectDevices from the database -func (sqlite *SQLite) SelectDevices(ctx context.Context) ([]*types.Device, error) { - queryFile := "selectDevices.sql" - query, present := sqlite.queries[queryFile] - if !present { - return nil, fmt.Errorf("SQLite-Backend: File %v not found", queryFile) - } - - tx, err := sqlite.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: true}) - if err != nil { - return nil, fmt.Errorf("Failed to begin new transaction: %v", err) - } - - devices, err := sqlite.selectDevices(tx, query) - if err != nil { - return nil, err - } - - err = tx.Commit() - if err != nil { - return nil, fmt.Errorf("Failed to commit transaction: %v", err) - } - - return devices, nil -} - -func (sqlite *SQLite) selectDevices(tx *sql.Tx, query string, args ...interface{}) ([]*types.Device, error) { - stmt, err := tx.Prepare(query) - if err != nil { - return nil, fmt.Errorf("Failed to prepare statement: %v", err) - } - defer stmt.Close() - - rows, err := stmt.Query(args...) - if err != nil { - return nil, fmt.Errorf("Failed to query statement: %v", err) - } - defer rows.Close() - - devices := make([]*types.Device, 0) - for rows.Next() { - device := new(types.Device) - err = rows.Scan( - &device.ID, - &device.Name, - &device.Location, - &device.CreationDate, - &device.UpdateDate, - ) - if err != nil { - return nil, fmt.Errorf("Failed to scan row: %v", err) - } - devices = append(devices, device) - } - - return devices, nil -} - -// SelectHumidity returns humidity from the database -func (sqlite *SQLite) SelectHumidity(ctx context.Context, id string) (*types.MeasuredValue, error) { - queryFile := "selectHumidity.sql" - query, present := sqlite.queries[queryFile] - if !present { - return nil, fmt.Errorf("SQLite-Backend: File %v not found", queryFile) - } - - tx, err := sqlite.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: true}) - if err != nil { - return nil, err - } - - measuredValues, err := sqlite.selectMeasuredValue(tx, query, id) - if err != nil { - return nil, err - } - - err = tx.Commit() - if err != nil { - return nil, err - } - - if measuredValues == nil { - return nil, nil - } - - for _, measuredValue := range measuredValues { - measuredValue.ValueType = types.Humidity - } - - return measuredValues[0], nil -} - -// SelectHumidities returns humidities from the database -func (sqlite *SQLite) SelectHumidities(ctx context.Context) ([]*types.MeasuredValue, error) { - queryFile := "selectHumidities.sql" - query, present := sqlite.queries[queryFile] - if !present { - return nil, fmt.Errorf("SQLite-Backend: File %v not found", queryFile) - } - - tx, err := sqlite.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: true}) - if err != nil { - return nil, err - } - - measuredValues, err := sqlite.selectMeasuredValue(tx, query) - if err != nil { - return nil, err - } - - err = tx.Commit() - if err != nil { - return nil, err - } - - for _, measuredValue := range measuredValues { - measuredValue.ValueType = types.Humidity - } - - return measuredValues, nil -} - -func (sqlite *SQLite) selectMeasuredValue(tx *sql.Tx, query string, args ...interface{}) ([]*types.MeasuredValue, error) { - stmt, err := tx.Prepare(query) - if err != nil { - tx.Rollback() - return nil, err - } - defer stmt.Close() - - rows, err := stmt.Query(args...) - if err != nil { - tx.Rollback() - return nil, err - } - defer rows.Close() - - measuredValues := make([]*types.MeasuredValue, 0) - for rows.Next() { - measuredValue := new(types.MeasuredValue) - err := rows.Scan( - &measuredValue.ID, - &measuredValue.Value, - &measuredValue.Date, - &measuredValue.SensorID, - &measuredValue.CreationDate, - &measuredValue.UpdateDate, - ) - - if err != nil { - tx.Rollback() - return nil, err - } - - measuredValues = append(measuredValues, measuredValue) - } - - return measuredValues, nil -} - -// SelectPressure returns pressure from the database -func (sqlite *SQLite) SelectPressure(ctx context.Context, id string) (*types.MeasuredValue, error) { - queryFile := "selectPressure.sql" - query, present := sqlite.queries[queryFile] - if !present { - return nil, fmt.Errorf("SQLite-Backend: File %v not found", queryFile) - } - - tx, err := sqlite.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: true}) - if err != nil { - return nil, err - } - - measuredValues, err := sqlite.selectMeasuredValue(tx, query, id) - if err != nil { - return nil, err - } - - err = tx.Commit() - if err != nil { - return nil, err - } - - if measuredValues == nil { - return nil, nil - } - - for _, measuredValue := range measuredValues { - measuredValue.ValueType = types.Pressure - } - - return measuredValues[0], nil -} - -// SelectPressures returns pressure from the database -func (sqlite *SQLite) SelectPressures(ctx context.Context) ([]*types.MeasuredValue, error) { - queryFile := "selectPressures.sql" - query, present := sqlite.queries[queryFile] - if !present { - return nil, fmt.Errorf("SQLite-Backend: File %v not found", queryFile) - } - - tx, err := sqlite.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: true}) - if err != nil { - return nil, err - } - - measuredValues, err := sqlite.selectMeasuredValue(tx, query) - if err != nil { - return nil, err - } - - err = tx.Commit() - if err != nil { - return nil, err - } - - for _, measuredValue := range measuredValues { - measuredValue.ValueType = types.Pressure - } - - return measuredValues, nil -} - -// SelectSensor from database -func (sqlite *SQLite) SelectSensor(ctx context.Context, id string) (*types.Sensor, error) { - queryFile := "selectSensor.sql" - query, present := sqlite.queries[queryFile] - if !present { - return nil, fmt.Errorf("SQLite-Backend: File %v not found", queryFile) - } - - tx, err := sqlite.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: true}) - if err != nil { - return nil, fmt.Errorf("Failed to begin new transaction: %v", err) - } - - sensors, err := sqlite.selectSensors(tx, query, id) - if err != nil { - return nil, err - } - - err = tx.Commit() - if err != nil { - return nil, fmt.Errorf("Failed to commit transaction: %v", err) - } - - if len(sensors) == 0 { - return nil, nil - } - - return sensors[0], nil -} - -// SelectSensors from the database -func (sqlite *SQLite) SelectSensors(ctx context.Context) ([]*types.Sensor, error) { - queryFile := "selectSensors.sql" - query, present := sqlite.queries[queryFile] - if !present { - return nil, fmt.Errorf("SQLite-Backend: File %v not found", queryFile) - } - - tx, err := sqlite.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: true}) - if err != nil { - return nil, fmt.Errorf("Failed to begin new transaction: %v", err) - } - - sensors, err := sqlite.selectSensors(tx, query) - if err != nil { - return nil, err - } - - err = tx.Commit() - if err != nil { - return nil, fmt.Errorf("Failed to commit transaction: %v", err) - } - - return sensors, nil -} - -func (sqlite *SQLite) selectSensors(tx *sql.Tx, query string, args ...interface{}) ([]*types.Sensor, error) { - stmt, err := tx.Prepare(query) - if err != nil { - return nil, fmt.Errorf("Failed to prepare statement: %v", err) - } - defer stmt.Close() - - rows, err := stmt.Query(args...) - if err != nil { - return nil, fmt.Errorf("Failed to query statement: %v", err) - } - defer rows.Close() - - sensors := make([]*types.Sensor, 0) - for rows.Next() { - sensor := new(types.Sensor) - err = rows.Scan( - &sensor.ID, - &sensor.Name, - &sensor.Location, - &sensor.WireID, - &sensor.I2CBus, - &sensor.I2CAddress, - &sensor.GPIONumber, - &sensor.Model, - &sensor.Enabled, - &sensor.TickDuration, - &sensor.DeviceID, - &sensor.CreationDate, - &sensor.UpdateDate, - ) - - if err != nil { - return nil, fmt.Errorf("Failed to scan row: %v", err) - } - sensors = append(sensors, sensor) - } - - return sensors, nil -} - -// SelectTemperature returns temperatures from the database -func (sqlite *SQLite) SelectTemperature(ctx context.Context, id string) (*types.MeasuredValue, error) { - queryFile := "selectTemperature.sql" - query, present := sqlite.queries[queryFile] - if !present { - return nil, fmt.Errorf("SQLite-Backend: File %v not found", queryFile) - } - - tx, err := sqlite.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: true}) - if err != nil { - return nil, err - } - - measuredValues, err := sqlite.selectMeasuredValue(tx, query, id) - if err != nil { - return nil, err - } - - err = tx.Commit() - if err != nil { - return nil, err - } - - if measuredValues == nil { - return nil, nil - } - - for _, measuredValue := range measuredValues { - measuredValue.ValueType = types.Temperature - } - - return measuredValues[0], nil -} - -// SelectTemperatures returns temperatures from the database -func (sqlite *SQLite) SelectTemperatures(ctx context.Context) ([]*types.MeasuredValue, error) { - queryFile := "selectTemperatures.sql" - query, present := sqlite.queries[queryFile] - if !present { - return nil, fmt.Errorf("SQLite-Backend: File %v not found", queryFile) - } - - tx, err := sqlite.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: true}) - if err != nil { - return nil, err - } - - measuredValues, err := sqlite.selectMeasuredValue(tx, query) - if err != nil { - return nil, err - } - - err = tx.Commit() - if err != nil { - return nil, err - } - - for _, measuredValue := range measuredValues { - measuredValue.ValueType = types.Temperature - } - - return measuredValues, nil -} - -// UpdateDevices updates a device in the database -func (sqlite *SQLite) UpdateDevices(ctx context.Context, devices ...*types.Device) error { - queryFile := "updateDevice.sql" - query, present := sqlite.queries[queryFile] - if !present { - return fmt.Errorf("SQLite-Backend: File %v not found", queryFile) - } - - tx, err := sqlite.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: false}) - if err != nil { - return err - } - - stmt, err := tx.Prepare(query) - if err != nil { - tx.Rollback() - return err - } - defer stmt.Close() - - for _, device := range devices { - _, err := stmt.Exec( - &device.Name, - &device.Location, - &device.CreationDate, - &device.UpdateDate, - &device.ID, - ) - if err != nil { - tx.Rollback() - return err - } - } - - return tx.Commit() -} - -// UpdateSensors updates a sensor in the database -func (sqlite *SQLite) UpdateSensors(ctx context.Context, sensors ...*types.Sensor) error { - queryFile := "updateSensor.sql" - query, present := sqlite.queries[queryFile] - if !present { - return fmt.Errorf("SQLite-Backend: File %v not found", queryFile) - } - - tx, err := sqlite.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: false}) - if err != nil { - return err - } - - stmt, err := tx.Prepare(query) - if err != nil { - tx.Rollback() - return err - } - defer stmt.Close() - - for _, sensor := range sensors { - _, err := stmt.Exec( - &sensor.Name, - &sensor.Location, - &sensor.WireID, - &sensor.I2CBus, - &sensor.I2CAddress, - &sensor.GPIONumber, - &sensor.Model, - &sensor.Enabled, - &sensor.TickDuration, - &sensor.DeviceID, - &sensor.CreationDate, - &sensor.UpdateDate, - &sensor.ID, - ) - if err != nil { - tx.Rollback() - return err - } - } - - return tx.Commit() -} diff --git a/pkg/repository/db/postgres/ddl/1_table_devices.down.sql b/pkg/repository/postgres/ddl/1_table_devices.down.sql similarity index 100% rename from pkg/repository/db/postgres/ddl/1_table_devices.down.sql rename to pkg/repository/postgres/ddl/1_table_devices.down.sql diff --git a/pkg/repository/db/postgres/ddl/1_table_devices.up.sql b/pkg/repository/postgres/ddl/1_table_devices.up.sql similarity index 100% rename from pkg/repository/db/postgres/ddl/1_table_devices.up.sql rename to pkg/repository/postgres/ddl/1_table_devices.up.sql diff --git a/pkg/repository/db/postgres/ddl/2_table_sensors.down.sql b/pkg/repository/postgres/ddl/2_table_sensors.down.sql similarity index 100% rename from pkg/repository/db/postgres/ddl/2_table_sensors.down.sql rename to pkg/repository/postgres/ddl/2_table_sensors.down.sql diff --git a/pkg/repository/db/postgres/ddl/2_table_sensors.up.sql b/pkg/repository/postgres/ddl/2_table_sensors.up.sql similarity index 100% rename from pkg/repository/db/postgres/ddl/2_table_sensors.up.sql rename to pkg/repository/postgres/ddl/2_table_sensors.up.sql diff --git a/pkg/repository/db/postgres/ddl/3_table_humidites.down.sql b/pkg/repository/postgres/ddl/3_table_humidites.down.sql similarity index 100% rename from pkg/repository/db/postgres/ddl/3_table_humidites.down.sql rename to pkg/repository/postgres/ddl/3_table_humidites.down.sql diff --git a/pkg/repository/db/postgres/ddl/3_table_humidites.up.sql b/pkg/repository/postgres/ddl/3_table_humidites.up.sql similarity index 100% rename from pkg/repository/db/postgres/ddl/3_table_humidites.up.sql rename to pkg/repository/postgres/ddl/3_table_humidites.up.sql diff --git a/pkg/repository/db/postgres/ddl/4_table_pressures.down.sql b/pkg/repository/postgres/ddl/4_table_pressures.down.sql similarity index 100% rename from pkg/repository/db/postgres/ddl/4_table_pressures.down.sql rename to pkg/repository/postgres/ddl/4_table_pressures.down.sql diff --git a/pkg/repository/db/postgres/ddl/4_table_pressures.up.sql b/pkg/repository/postgres/ddl/4_table_pressures.up.sql similarity index 100% rename from pkg/repository/db/postgres/ddl/4_table_pressures.up.sql rename to pkg/repository/postgres/ddl/4_table_pressures.up.sql diff --git a/pkg/repository/db/postgres/ddl/5_table_temperatures.down.sql b/pkg/repository/postgres/ddl/5_table_temperatures.down.sql similarity index 100% rename from pkg/repository/db/postgres/ddl/5_table_temperatures.down.sql rename to pkg/repository/postgres/ddl/5_table_temperatures.down.sql diff --git a/pkg/repository/db/postgres/ddl/5_table_temperatures.up.sql b/pkg/repository/postgres/ddl/5_table_temperatures.up.sql similarity index 100% rename from pkg/repository/db/postgres/ddl/5_table_temperatures.up.sql rename to pkg/repository/postgres/ddl/5_table_temperatures.up.sql diff --git a/pkg/repository/db/postgres/ddl/6_constraint_fk_humidities_sensor_id.down.sql b/pkg/repository/postgres/ddl/6_constraint_fk_humidities_sensor_id.down.sql similarity index 100% rename from pkg/repository/db/postgres/ddl/6_constraint_fk_humidities_sensor_id.down.sql rename to pkg/repository/postgres/ddl/6_constraint_fk_humidities_sensor_id.down.sql diff --git a/pkg/repository/db/postgres/ddl/6_constraint_fk_humidities_sensor_id.up.sql b/pkg/repository/postgres/ddl/6_constraint_fk_humidities_sensor_id.up.sql similarity index 100% rename from pkg/repository/db/postgres/ddl/6_constraint_fk_humidities_sensor_id.up.sql rename to pkg/repository/postgres/ddl/6_constraint_fk_humidities_sensor_id.up.sql diff --git a/pkg/repository/db/postgres/ddl/7_constraint_fk_pressures_sensor_id.down.sql b/pkg/repository/postgres/ddl/7_constraint_fk_pressures_sensor_id.down.sql similarity index 100% rename from pkg/repository/db/postgres/ddl/7_constraint_fk_pressures_sensor_id.down.sql rename to pkg/repository/postgres/ddl/7_constraint_fk_pressures_sensor_id.down.sql diff --git a/pkg/repository/db/postgres/ddl/7_constraint_fk_pressures_sensor_id.up.sql b/pkg/repository/postgres/ddl/7_constraint_fk_pressures_sensor_id.up.sql similarity index 100% rename from pkg/repository/db/postgres/ddl/7_constraint_fk_pressures_sensor_id.up.sql rename to pkg/repository/postgres/ddl/7_constraint_fk_pressures_sensor_id.up.sql diff --git a/pkg/repository/db/postgres/dml/deleteDevice.sql b/pkg/repository/postgres/dml/deleteDeviceByID.sql similarity index 100% rename from pkg/repository/db/postgres/dml/deleteDevice.sql rename to pkg/repository/postgres/dml/deleteDeviceByID.sql diff --git a/pkg/repository/postgres/dml/deleteDeviceByName.sql b/pkg/repository/postgres/dml/deleteDeviceByName.sql new file mode 100644 index 0000000..7b5ac8b --- /dev/null +++ b/pkg/repository/postgres/dml/deleteDeviceByName.sql @@ -0,0 +1,2 @@ +DELETE FROM devices +WHERE device_name = $1; \ No newline at end of file diff --git a/pkg/repository/db/postgres/dml/deleteSensor.sql b/pkg/repository/postgres/dml/deleteSensorByID.sql similarity index 100% rename from pkg/repository/db/postgres/dml/deleteSensor.sql rename to pkg/repository/postgres/dml/deleteSensorByID.sql diff --git a/pkg/repository/postgres/dml/deleteSensorByName.sql b/pkg/repository/postgres/dml/deleteSensorByName.sql new file mode 100644 index 0000000..b6a7d6f --- /dev/null +++ b/pkg/repository/postgres/dml/deleteSensorByName.sql @@ -0,0 +1,2 @@ +DELETE FROM sensors +WHERE sensor_name = $1; \ No newline at end of file diff --git a/pkg/repository/db/postgres/dml/insertDevice.sql b/pkg/repository/postgres/dml/insertDevice.sql similarity index 100% rename from pkg/repository/db/postgres/dml/insertDevice.sql rename to pkg/repository/postgres/dml/insertDevice.sql diff --git a/pkg/repository/db/postgres/dml/insertHumidity.sql b/pkg/repository/postgres/dml/insertHumidity.sql similarity index 100% rename from pkg/repository/db/postgres/dml/insertHumidity.sql rename to pkg/repository/postgres/dml/insertHumidity.sql diff --git a/pkg/repository/db/postgres/dml/insertOrUpdateDevice.sql b/pkg/repository/postgres/dml/insertOrUpdateDevice.sql similarity index 100% rename from pkg/repository/db/postgres/dml/insertOrUpdateDevice.sql rename to pkg/repository/postgres/dml/insertOrUpdateDevice.sql diff --git a/pkg/repository/db/postgres/dml/insertOrUpdateHumidity.sql b/pkg/repository/postgres/dml/insertOrUpdateHumidity.sql similarity index 100% rename from pkg/repository/db/postgres/dml/insertOrUpdateHumidity.sql rename to pkg/repository/postgres/dml/insertOrUpdateHumidity.sql diff --git a/pkg/repository/db/postgres/dml/insertOrUpdatePressure.sql b/pkg/repository/postgres/dml/insertOrUpdatePressure.sql similarity index 100% rename from pkg/repository/db/postgres/dml/insertOrUpdatePressure.sql rename to pkg/repository/postgres/dml/insertOrUpdatePressure.sql diff --git a/pkg/repository/db/postgres/dml/insertOrUpdateSensor.sql b/pkg/repository/postgres/dml/insertOrUpdateSensor.sql similarity index 100% rename from pkg/repository/db/postgres/dml/insertOrUpdateSensor.sql rename to pkg/repository/postgres/dml/insertOrUpdateSensor.sql diff --git a/pkg/repository/db/postgres/dml/insertOrUpdateTemperature.sql b/pkg/repository/postgres/dml/insertOrUpdateTemperature.sql similarity index 100% rename from pkg/repository/db/postgres/dml/insertOrUpdateTemperature.sql rename to pkg/repository/postgres/dml/insertOrUpdateTemperature.sql diff --git a/pkg/repository/db/postgres/dml/insertPressure.sql b/pkg/repository/postgres/dml/insertPressure.sql similarity index 100% rename from pkg/repository/db/postgres/dml/insertPressure.sql rename to pkg/repository/postgres/dml/insertPressure.sql diff --git a/pkg/repository/db/postgres/dml/insertSensor.sql b/pkg/repository/postgres/dml/insertSensor.sql similarity index 100% rename from pkg/repository/db/postgres/dml/insertSensor.sql rename to pkg/repository/postgres/dml/insertSensor.sql diff --git a/pkg/repository/db/postgres/dml/insertTemperature.sql b/pkg/repository/postgres/dml/insertTemperature.sql similarity index 100% rename from pkg/repository/db/postgres/dml/insertTemperature.sql rename to pkg/repository/postgres/dml/insertTemperature.sql diff --git a/pkg/repository/db/postgres/dml/selectDevice.sql b/pkg/repository/postgres/dml/selectDeviceByID.sql similarity index 100% rename from pkg/repository/db/postgres/dml/selectDevice.sql rename to pkg/repository/postgres/dml/selectDeviceByID.sql diff --git a/pkg/repository/postgres/dml/selectDeviceByName.sql b/pkg/repository/postgres/dml/selectDeviceByName.sql new file mode 100644 index 0000000..2000d5d --- /dev/null +++ b/pkg/repository/postgres/dml/selectDeviceByName.sql @@ -0,0 +1,9 @@ +SELECT + device_id, + device_name, + device_location, + creation_date, + update_date +FROM + devices +WHERE device_name = $1; \ No newline at end of file diff --git a/pkg/repository/db/postgres/dml/selectDevices.sql b/pkg/repository/postgres/dml/selectDevices.sql similarity index 100% rename from pkg/repository/db/postgres/dml/selectDevices.sql rename to pkg/repository/postgres/dml/selectDevices.sql diff --git a/pkg/repository/db/postgres/dml/selectHumidities.sql b/pkg/repository/postgres/dml/selectHumidities.sql similarity index 100% rename from pkg/repository/db/postgres/dml/selectHumidities.sql rename to pkg/repository/postgres/dml/selectHumidities.sql diff --git a/pkg/repository/db/postgres/dml/selectHumidity.sql b/pkg/repository/postgres/dml/selectHumidityByID.sql similarity index 100% rename from pkg/repository/db/postgres/dml/selectHumidity.sql rename to pkg/repository/postgres/dml/selectHumidityByID.sql diff --git a/pkg/repository/db/postgres/dml/selectPressure.sql b/pkg/repository/postgres/dml/selectPressureByID.sql similarity index 100% rename from pkg/repository/db/postgres/dml/selectPressure.sql rename to pkg/repository/postgres/dml/selectPressureByID.sql diff --git a/pkg/repository/db/postgres/dml/selectPressures.sql b/pkg/repository/postgres/dml/selectPressures.sql similarity index 100% rename from pkg/repository/db/postgres/dml/selectPressures.sql rename to pkg/repository/postgres/dml/selectPressures.sql diff --git a/pkg/repository/db/postgres/dml/selectSensor.sql b/pkg/repository/postgres/dml/selectSensorByID.sql similarity index 100% rename from pkg/repository/db/postgres/dml/selectSensor.sql rename to pkg/repository/postgres/dml/selectSensorByID.sql diff --git a/pkg/repository/db/postgres/dml/selectSensors.sql b/pkg/repository/postgres/dml/selectSensors.sql similarity index 100% rename from pkg/repository/db/postgres/dml/selectSensors.sql rename to pkg/repository/postgres/dml/selectSensors.sql diff --git a/pkg/repository/postgres/dml/selectSensorsByDeviceID.sql b/pkg/repository/postgres/dml/selectSensorsByDeviceID.sql new file mode 100644 index 0000000..2638cff --- /dev/null +++ b/pkg/repository/postgres/dml/selectSensorsByDeviceID.sql @@ -0,0 +1,18 @@ +SELECT + sensor_id, + sensor_name, + sensor_location, + wire_id, + i2c_bus, + i2c_address, + gpio_number, + sensor_model, + sensor_enabled, + tick_duration, + device_id, + creation_date, + update_date +FROM + sensors +WHERE + device_id = $1; \ No newline at end of file diff --git a/pkg/repository/postgres/dml/selectSensorsByModel.sql b/pkg/repository/postgres/dml/selectSensorsByModel.sql new file mode 100644 index 0000000..23b83ff --- /dev/null +++ b/pkg/repository/postgres/dml/selectSensorsByModel.sql @@ -0,0 +1,18 @@ +SELECT + sensor_id, + sensor_name, + sensor_location, + wire_id, + i2c_bus, + i2c_address, + gpio_number, + sensor_model, + sensor_enabled, + tick_duration, + device_id, + creation_date, + update_date +FROM + sensors +WHERE + sensor_model = $1; \ No newline at end of file diff --git a/pkg/repository/postgres/dml/selectSensorsByName.sql b/pkg/repository/postgres/dml/selectSensorsByName.sql new file mode 100644 index 0000000..3263cb9 --- /dev/null +++ b/pkg/repository/postgres/dml/selectSensorsByName.sql @@ -0,0 +1,18 @@ +SELECT + sensor_id, + sensor_name, + sensor_location, + wire_id, + i2c_bus, + i2c_address, + gpio_number, + sensor_model, + sensor_enabled, + tick_duration, + device_id, + creation_date, + update_date +FROM + sensors +WHERE + sensor_name = $1; \ No newline at end of file diff --git a/pkg/repository/db/postgres/dml/selectTemperature.sql b/pkg/repository/postgres/dml/selectTemperatureByID.sql similarity index 100% rename from pkg/repository/db/postgres/dml/selectTemperature.sql rename to pkg/repository/postgres/dml/selectTemperatureByID.sql diff --git a/pkg/repository/db/postgres/dml/selectTemperatures.sql b/pkg/repository/postgres/dml/selectTemperatures.sql similarity index 100% rename from pkg/repository/db/postgres/dml/selectTemperatures.sql rename to pkg/repository/postgres/dml/selectTemperatures.sql diff --git a/pkg/repository/db/postgres/dml/updateDevice.sql b/pkg/repository/postgres/dml/updateDevice.sql similarity index 100% rename from pkg/repository/db/postgres/dml/updateDevice.sql rename to pkg/repository/postgres/dml/updateDevice.sql diff --git a/pkg/repository/db/postgres/dml/updateSensor.sql b/pkg/repository/postgres/dml/updateSensor.sql similarity index 100% rename from pkg/repository/db/postgres/dml/updateSensor.sql rename to pkg/repository/postgres/dml/updateSensor.sql diff --git a/pkg/repository/postgres/postgres.go b/pkg/repository/postgres/postgres.go new file mode 100644 index 0000000..23d3fb5 --- /dev/null +++ b/pkg/repository/postgres/postgres.go @@ -0,0 +1,1206 @@ +package postgres + +import ( + "context" + "database/sql" + "embed" + "errors" + "fmt" + "net/url" + "time" + + "git.cryptic.systems/volker.raschek/flucky/pkg/types" + "git.cryptic.systems/volker.raschek/go-logger" + "github.com/golang-migrate/migrate/v4" + "github.com/johejo/golang-migrate-extra/source/iofs" +) + +//go:embed ddl/*.sql +var postgresDDLAssets embed.FS + +//go:embed dml/*.sql +var postgresDMLAssets embed.FS + +const ( + postgresDDLAssetPath = "ddl" + postgresDMLAssetPath = "dml" + + deleteDeviceByIDSQLFile = postgresDMLAssetPath + "/deleteDeviceByID.sql" + deleteDeviceByNameSQLFile = postgresDMLAssetPath + "/deleteDeviceByName.sql" + + deleteSensorByIDSQLFile = postgresDMLAssetPath + "/deleteSensorByID.sql" + deleteSensorByNameSQLFile = postgresDMLAssetPath + "/deleteSensorByName.sql" + + insertDeviceSQLFile = postgresDMLAssetPath + "/insertDevice.sql" + insertSensorSQLFile = postgresDMLAssetPath + "/insertSensor.sql" + insertHumiditySQLFile = postgresDMLAssetPath + "/insertHumidity.sql" + insertPressureSQLFile = postgresDMLAssetPath + "/insertPressure.sql" + insertTemperatureSQLFile = postgresDMLAssetPath + "/insertTemperature.sql" + + insertOrUpdateDeviceSQLFile = postgresDMLAssetPath + "/insertOrUpdateDevice.sql" + insertOrUpdateSensorSQLFile = postgresDMLAssetPath + "/insertOrUpdateSensor.sql" + insertOrUpdateHumiditySQLFile = postgresDMLAssetPath + "/insertOrUpdateHumidity.sql" + insertOrUpdatePressureSQLFile = postgresDMLAssetPath + "/insertOrUpdatePressure.sql" + insertOrUpdateTemperatureSQLFile = postgresDMLAssetPath + "/insertOrUpdateTemperature.sql" + + selectDeviceByIDSQLFile = postgresDMLAssetPath + "/selectDeviceByID.sql" + selectDeviceByNameSQLFile = postgresDMLAssetPath + "/selectDeviceByName.sql" + selectDevicesSQLFile = postgresDMLAssetPath + "/selectDevices.sql" + + selectSensorByIDSQLFile = postgresDMLAssetPath + "/selectSensorByID.sql" + selectSensorsSQLFile = postgresDMLAssetPath + "/selectSensors.sql" + selectSensorsByDeviceIDSQLFile = postgresDMLAssetPath + "/selectSensorsByDeviceID.sql" + selectSensorsByModelSQLFile = postgresDMLAssetPath + "/selectSensorsByModel.sql" + selectSensorsByNameSQLFile = postgresDMLAssetPath + "/selectSensorsByName.sql" + + selectHumidityByIDSQLFile = postgresDMLAssetPath + "/selectHumidityByID.sql" + selectHumiditiesSQLFile = postgresDMLAssetPath + "/selectHumidities.sql" + + selectPressureByIDSQLFile = postgresDMLAssetPath + "/selectPressureByID.sql" + selectPressuresSQLFile = postgresDMLAssetPath + "/selectPressures.sql" + + selectTemperatureByIDSQLFile = postgresDMLAssetPath + "/selectTemperatureByID.sql" + selectTemperaturesSQLFile = postgresDMLAssetPath + "/selectTemperatures.sql" + + updateDeviceSQLFile = postgresDMLAssetPath + "/updateDevice.sql" + updateSensorSQLFile = postgresDMLAssetPath + "/updateSensor.sql" +) + +type Opts struct { + DatabaseURL *url.URL + Logger logger.Logger +} + +func (o *Opts) Validate() error { + for k, v := range map[string]interface{}{ + "DatabaseURL": o.DatabaseURL, + "Logger": o.Logger, + } { + if v == nil { + return fmt.Errorf("%v does not be empty or nil", k) + } + } + + if o.DatabaseURL.Scheme != "postgres" { + return fmt.Errorf("Unsupported database scheme %v for database backend postgres", o.DatabaseURL.Scheme) + } + + return nil +} + +func New(opts Opts) (*Postgres, error) { + if err := opts.Validate(); err != nil { + return nil, err + } + + sqlDBO, err := sql.Open(opts.DatabaseURL.Scheme, opts.DatabaseURL.String()) + if err != nil { + return nil, err + } + + return &Postgres{ + databaseURL: opts.DatabaseURL, + dbo: sqlDBO, + flogger: opts.Logger, + }, nil +} + +// Postgres implementation +type Postgres struct { + databaseURL *url.URL + dbo *sql.DB + flogger logger.Logger +} + +// Close closes the database and prevents new queries from starting. Close then +// waits for all queries that have started processing on the server to finish. +func (postgres *Postgres) Close() error { + return postgres.dbo.Close() +} + +// RemoveDevices from the database +func (postgres *Postgres) RemoveDevicesByIDs(ctx context.Context, deviceIDs ...string) error { + f, err := postgresDMLAssets.ReadFile(deleteDeviceByIDSQLFile) + if err != nil { + return fmt.Errorf("Failed to read file %v: %w", deleteDeviceByIDSQLFile, err) + } + + tx, err := postgres.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: false}) + if err != nil { + return fmt.Errorf("Failed to begin new transaction: %v", err) + } + defer tx.Rollback() + + stmt, err := tx.Prepare(string(f)) + 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 { + return err + } + } + + return tx.Commit() +} + +// RemoveDevices from the database +func (postgres *Postgres) RemoveDevicesByNames(ctx context.Context, names ...string) error { + f, err := postgresDMLAssets.ReadFile(deleteDeviceByNameSQLFile) + if err != nil { + return fmt.Errorf("Failed to read file %v: %w", deleteDeviceByNameSQLFile, err) + } + + tx, err := postgres.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: false}) + if err != nil { + return fmt.Errorf("Failed to begin new transaction: %v", err) + } + defer tx.Rollback() + + stmt, err := tx.Prepare(string(f)) + if err != nil { + return fmt.Errorf("Failed to prepare statement: %v", err) + } + defer stmt.Close() + + for _, deviceID := range names { + _, err = stmt.Exec(deviceID) + if err != nil { + return err + } + } + + return tx.Commit() +} + +// RemoveSensors from the database +func (postgres *Postgres) RemoveSensorsByIDs(ctx context.Context, sensorIDs ...string) error { + f, err := postgresDMLAssets.ReadFile(deleteSensorByIDSQLFile) + if err != nil { + return fmt.Errorf("Failed to read file %v: %w", deleteSensorByIDSQLFile, err) + } + + tx, err := postgres.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: false}) + if err != nil { + return fmt.Errorf("Failed to begin new transaction: %v", err) + } + defer tx.Rollback() + + stmt, err := tx.Prepare(string(f)) + 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 { + return err + } + } + + return tx.Commit() +} + +// RemoveSensors from the database +func (postgres *Postgres) RemoveSensorsByNames(ctx context.Context, sensorIDs ...string) error { + f, err := postgresDMLAssets.ReadFile(deleteSensorByNameSQLFile) + if err != nil { + return fmt.Errorf("Failed to read file %v: %w", deleteSensorByNameSQLFile, err) + } + + tx, err := postgres.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: false}) + if err != nil { + return fmt.Errorf("Failed to begin new transaction: %v", err) + } + defer tx.Rollback() + + stmt, err := tx.Prepare(string(f)) + 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 { + return err + } + } + + return tx.Commit() +} + +// AddDevices into the database +func (postgres *Postgres) AddDevices(ctx context.Context, devices ...*types.Device) error { + tx, err := postgres.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: false}) + if err != nil { + return fmt.Errorf("Failed to begin new transaction: %v", err) + } + defer tx.Rollback() + + err = postgres.insertDevices(tx, devices...) + if err != nil { + return err + } + + return tx.Commit() +} + +func (postgres *Postgres) insertDevices(tx *sql.Tx, devices ...*types.Device) error { + f, err := postgresDMLAssets.ReadFile(insertDeviceSQLFile) + if err != nil { + return fmt.Errorf("Failed to read file %v: %w", insertDeviceSQLFile, err) + } + + stmt, err := tx.Prepare(string(f)) + if err != nil { + return fmt.Errorf("Failed to prepare statement: %v", err) + } + defer stmt.Close() + + for _, device := range devices { + + if device.CreationDate.Equal(time.Time{}) { + device.CreationDate = time.Now() + } + + _, err = stmt.Exec( + &device.ID, + &device.Name, + &device.Location, + &device.CreationDate, + &device.UpdateDate, + ) + if err != nil { + return fmt.Errorf("Failed to execute statement: %v", err) + } + } + + return nil +} + +// AddMeasuredValues into the database +func (postgres *Postgres) AddMeasuredValues(ctx context.Context, measuredValues ...*types.MeasuredValue) error { + splittedMeasuredValues := make(map[types.MeasuredValueType][]*types.MeasuredValue, 0) + + for _, measuredValue := range measuredValues { + if _, ok := splittedMeasuredValues[measuredValue.ValueType]; !ok { + splittedMeasuredValues[measuredValue.ValueType] = make([]*types.MeasuredValue, 0) + } + splittedMeasuredValues[measuredValue.ValueType] = append(splittedMeasuredValues[measuredValue.ValueType], measuredValue) + } + + tx, err := postgres.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: false}) + if err != nil { + return fmt.Errorf("Failed to begin new transaction: %v", err) + } + defer tx.Rollback() + + // General insert function + insert := func(tx *sql.Tx, queryFile string, measuredValues []*types.MeasuredValue) error { + f, err := postgresDMLAssets.ReadFile(queryFile) + if err != nil { + return fmt.Errorf("Failed to read file %v: %w", queryFile, err) + } + + stmt, err := tx.Prepare(string(f)) + if err != nil { + return fmt.Errorf("Failed to prepare statement: %v", err) + } + defer stmt.Close() + + for _, measuredValue := range measuredValues { + + if measuredValue.CreationDate.Equal(time.Time{}) { + measuredValue.CreationDate = time.Now() + } + + _, err := stmt.Exec( + &measuredValue.ID, + &measuredValue.Value, + &measuredValue.Date, + &measuredValue.SensorID, + &measuredValue.CreationDate, + &measuredValue.UpdateDate, + ) + + if err != nil { + return fmt.Errorf("Failed to execute statement: %v", err) + } + } + + return nil + } + + for measuredValueType, measuredValues := range splittedMeasuredValues { + var queryFile string + + switch measuredValueType { + case types.Humidity: + queryFile = insertHumiditySQLFile + case types.Pressure: + queryFile = insertPressureSQLFile + case types.Temperature: + queryFile = insertTemperatureSQLFile + default: + return fmt.Errorf("Measured value type %v not supported", measuredValueType) + } + + err := insert(tx, queryFile, measuredValues) + if err != nil { + return err + } + } + + return tx.Commit() +} + +func (postgres *Postgres) AddOrUpdateDevices(ctx context.Context, devices ...*types.Device) error { + f, err := postgresDMLAssets.ReadFile(insertOrUpdateDeviceSQLFile) + if err != nil { + return fmt.Errorf("Failed to read file %v: %w", insertOrUpdateDeviceSQLFile, err) + } + + tx, err := postgres.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: false}) + if err != nil { + return fmt.Errorf("Failed to begin new transaction: %v", err) + } + defer tx.Rollback() + + stmt, err := tx.Prepare(string(f)) + if err != nil { + return fmt.Errorf("Failed to prepare statement: %w", err) + } + defer stmt.Close() + + for _, device := range devices { + + if device.CreationDate.Equal(time.Time{}) { + device.CreationDate = time.Now() + } + + _, err = stmt.Exec( + &device.ID, + &device.Name, + &device.Location, + &device.CreationDate, + &device.UpdateDate, + ) + if err != nil { + return fmt.Errorf("Failed to execute statement: %v", err) + } + } + + return tx.Commit() +} + +// AddOrUpdateMeasuredValues into the database +func (postgres *Postgres) AddOrUpdateMeasuredValues(ctx context.Context, measuredValues ...*types.MeasuredValue) error { + splittedMeasuredValues := make(map[types.MeasuredValueType][]*types.MeasuredValue, 0) + + for _, measuredValue := range measuredValues { + if _, ok := splittedMeasuredValues[measuredValue.ValueType]; !ok { + splittedMeasuredValues[measuredValue.ValueType] = make([]*types.MeasuredValue, 0) + } + splittedMeasuredValues[measuredValue.ValueType] = append(splittedMeasuredValues[measuredValue.ValueType], measuredValue) + } + + tx, err := postgres.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: false}) + if err != nil { + return fmt.Errorf("Failed to begin new transaction: %v", err) + } + defer tx.Rollback() + + // General insert function + insert := func(tx *sql.Tx, queryFile string, measuredValues []*types.MeasuredValue) error { + f, err := postgresDMLAssets.ReadFile(queryFile) + if err != nil { + return fmt.Errorf("Failed to read file %v: %w", queryFile, err) + } + + stmt, err := tx.Prepare(string(f)) + if err != nil { + return fmt.Errorf("Failed to prepare statement: %v", err) + } + defer stmt.Close() + + for _, measuredValue := range measuredValues { + + if measuredValue.CreationDate.Equal(time.Time{}) { + measuredValue.CreationDate = time.Now() + } + + _, err := stmt.Exec( + &measuredValue.ID, + &measuredValue.Value, + &measuredValue.Date, + &measuredValue.SensorID, + &measuredValue.CreationDate, + &measuredValue.UpdateDate, + ) + + if err != nil { + return fmt.Errorf("Failed to execute statement: %v", err) + } + } + + return nil + } + + for measuredValueType, measuredValues := range splittedMeasuredValues { + var queryFile string + + switch measuredValueType { + case types.Humidity: + queryFile = insertOrUpdateHumiditySQLFile + case types.Pressure: + queryFile = insertOrUpdatePressureSQLFile + case types.Temperature: + queryFile = insertOrUpdateTemperatureSQLFile + default: + return fmt.Errorf("Measured value type %v not supported", measuredValueType) + } + + err := insert(tx, queryFile, measuredValues) + if err != nil { + return err + } + } + + return tx.Commit() +} + +// AddOrUpdateSensors into the database +func (postgres *Postgres) AddOrUpdateSensors(ctx context.Context, sensors ...*types.Sensor) error { + f, err := postgresDMLAssets.ReadFile(insertOrUpdateSensorSQLFile) + if err != nil { + return fmt.Errorf("Failed to read file %v: %w", insertOrUpdateSensorSQLFile, err) + } + + tx, err := postgres.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: false}) + if err != nil { + return fmt.Errorf("Failed to begin new transaction: %v", err) + } + defer tx.Rollback() + + stmt, err := tx.Prepare(string(f)) + if err != nil { + return fmt.Errorf("Failed to prepare statement: %v", err) + } + defer stmt.Close() + + for _, sensor := range sensors { + + if sensor.CreationDate.Equal(time.Time{}) { + sensor.CreationDate = time.Now() + } + + _, err = stmt.Exec( + &sensor.ID, + &sensor.Name, + &sensor.Location, + &sensor.WireID, + &sensor.I2CBus, + &sensor.I2CAddress, + &sensor.GPIONumber, + &sensor.Model, + &sensor.Enabled, + &sensor.TickDuration, + &sensor.DeviceID, + &sensor.CreationDate, + &sensor.UpdateDate, + ) + if err != nil { + return fmt.Errorf("Failed to execute statement: %v", err) + } + } + + return tx.Commit() +} + +// AddSensors into the database +func (postgres *Postgres) AddSensors(ctx context.Context, sensors ...*types.Sensor) error { + f, err := postgresDMLAssets.ReadFile(insertSensorSQLFile) + if err != nil { + return fmt.Errorf("Failed to read file %v: %w", insertSensorSQLFile, err) + } + + tx, err := postgres.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: false}) + if err != nil { + return fmt.Errorf("Failed to begin new transaction: %v", err) + } + defer tx.Rollback() + + stmt, err := tx.Prepare(string(f)) + if err != nil { + return fmt.Errorf("Failed to prepare statement: %v", err) + } + defer stmt.Close() + + for _, sensor := range sensors { + + if sensor.CreationDate.Equal(time.Time{}) { + sensor.CreationDate = time.Now() + } + + _, err = stmt.Exec( + &sensor.ID, + &sensor.Name, + &sensor.Location, + &sensor.WireID, + &sensor.I2CBus, + &sensor.I2CAddress, + &sensor.GPIONumber, + &sensor.Model, + &sensor.Enabled, + &sensor.TickDuration, + &sensor.DeviceID, + &sensor.CreationDate, + &sensor.UpdateDate, + ) + if err != nil { + return fmt.Errorf("Failed to execute statement: %v", err) + } + } + + return tx.Commit() +} + +// Migrate creates all required tables if not exist +func (postgres *Postgres) Migrate(ctx context.Context) error { + sourceDriver, err := iofs.New(postgresDDLAssets, postgresDDLAssetPath) + if err != nil { + return err + } + + m, err := migrate.NewWithSourceInstance("iofs", sourceDriver, postgres.databaseURL.String()) + if err != nil { + return err + } + + err = m.Up() + switch { + case errors.Is(err, migrate.ErrNoChange): + return nil + default: + return err + } +} + +// GetDevice from database +func (postgres *Postgres) GetDeviceByID(ctx context.Context, id string) (*types.Device, error) { + f, err := postgresDMLAssets.ReadFile(selectDeviceByIDSQLFile) + if err != nil { + return nil, fmt.Errorf("Failed to read file %v: %w", selectDeviceByIDSQLFile, err) + } + + tx, err := postgres.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: true}) + if err != nil { + return nil, fmt.Errorf("Failed to begin new transaction: %v", err) + } + defer tx.Rollback() + + devices, err := postgres.selectDevices(tx, string(f), id) + if err != nil { + return nil, err + } + + err = tx.Commit() + if err != nil { + return nil, err + } + + if len(devices) == 0 { + return nil, nil + } + + return devices[0], nil +} + +// GetDevice from database +func (postgres *Postgres) GetDeviceByName(ctx context.Context, name string) (*types.Device, error) { + f, err := postgresDMLAssets.ReadFile(selectDeviceByNameSQLFile) + if err != nil { + return nil, fmt.Errorf("Failed to read file %v: %w", selectDeviceByNameSQLFile, err) + } + + tx, err := postgres.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: true}) + if err != nil { + return nil, fmt.Errorf("Failed to begin new transaction: %v", err) + } + defer tx.Rollback() + + devices, err := postgres.selectDevices(tx, string(f), name) + if err != nil { + return nil, err + } + + err = tx.Commit() + if err != nil { + return nil, err + } + + if len(devices) == 0 { + return nil, nil + } + + return devices[0], nil +} + +// GetDevices from the database +func (postgres *Postgres) GetDevices(ctx context.Context) ([]*types.Device, error) { + f, err := postgresDMLAssets.ReadFile(selectDevicesSQLFile) + if err != nil { + return nil, fmt.Errorf("Failed to read file %v: %w", selectDevicesSQLFile, err) + } + + tx, err := postgres.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: true}) + if err != nil { + return nil, fmt.Errorf("Failed to begin new transaction: %v", err) + } + defer tx.Rollback() + + devices, err := postgres.selectDevices(tx, string(f)) + if err != nil { + return nil, err + } + + err = tx.Commit() + if err != nil { + return nil, fmt.Errorf("Failed to commit transaction: %v", err) + } + + return devices, nil +} + +func (postgres *Postgres) selectDevices(tx *sql.Tx, query string, args ...interface{}) ([]*types.Device, error) { + stmt, err := tx.Prepare(query) + if err != nil { + return nil, fmt.Errorf("Failed to prepare statement: %v", err) + } + defer stmt.Close() + + rows, err := stmt.Query(args...) + if err != nil { + return nil, fmt.Errorf("Failed to query statement: %v", err) + } + + devices := make([]*types.Device, 0) + for rows.Next() { + device := new(types.Device) + err = rows.Scan( + &device.ID, + &device.Name, + &device.Location, + &device.CreationDate, + &device.UpdateDate, + ) + if err != nil { + return nil, fmt.Errorf("Failed to scan row: %v", err) + } + devices = append(devices, device) + } + + return devices, nil +} + +// GetHumidity returns humidity from the database +func (postgres *Postgres) GetHumidityByID(ctx context.Context, id string) (*types.MeasuredValue, error) { + f, err := postgresDMLAssets.ReadFile(selectHumidityByIDSQLFile) + if err != nil { + return nil, fmt.Errorf("Failed to read file %v: %w", selectHumidityByIDSQLFile, err) + } + + tx, err := postgres.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: true}) + if err != nil { + return nil, err + } + defer tx.Rollback() + + measuredValues, err := postgres.selectMeasuredValue(tx, string(f), id) + if err != nil { + return nil, err + } + + err = tx.Commit() + if err != nil { + return nil, err + } + + if measuredValues == nil { + return nil, nil + } + + for _, measuredValue := range measuredValues { + measuredValue.ValueType = types.Humidity + } + + return measuredValues[0], nil +} + +// GetHumidities returns humidities from the database +func (postgres *Postgres) GetHumidities(ctx context.Context) ([]*types.MeasuredValue, error) { + f, err := postgresDMLAssets.ReadFile(selectHumiditiesSQLFile) + if err != nil { + return nil, fmt.Errorf("Failed to read file %v: %w", selectHumiditiesSQLFile, err) + } + + tx, err := postgres.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: true}) + if err != nil { + return nil, err + } + defer tx.Rollback() + + measuredValues, err := postgres.selectMeasuredValue(tx, string(f)) + if err != nil { + return nil, err + } + + err = tx.Commit() + if err != nil { + return nil, err + } + + for _, measuredValue := range measuredValues { + measuredValue.ValueType = types.Humidity + } + + return measuredValues, nil +} + +func (postgres *Postgres) selectMeasuredValue(tx *sql.Tx, query string, args ...interface{}) ([]*types.MeasuredValue, error) { + stmt, err := tx.Prepare(query) + if err != nil { + return nil, err + } + defer stmt.Close() + + rows, err := stmt.Query(args...) + if err != nil { + return nil, err + } + + measuredValues := make([]*types.MeasuredValue, 0) + for rows.Next() { + measuredValue := new(types.MeasuredValue) + err := rows.Scan( + &measuredValue.ID, + &measuredValue.Value, + &measuredValue.Date, + &measuredValue.SensorID, + &measuredValue.CreationDate, + &measuredValue.UpdateDate, + ) + + if err != nil { + return nil, err + } + + measuredValues = append(measuredValues, measuredValue) + } + + return measuredValues, nil +} + +// GetPressure returns pressure from the database +func (postgres *Postgres) GetPressureByID(ctx context.Context, id string) (*types.MeasuredValue, error) { + f, err := postgresDMLAssets.ReadFile(selectPressureByIDSQLFile) + if err != nil { + return nil, fmt.Errorf("Failed to read file %v: %w", selectPressureByIDSQLFile, err) + } + + tx, err := postgres.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: true}) + if err != nil { + return nil, err + } + defer tx.Rollback() + + measuredValues, err := postgres.selectMeasuredValue(tx, string(f), id) + if err != nil { + return nil, err + } + + err = tx.Commit() + if err != nil { + return nil, err + } + + if measuredValues == nil { + return nil, nil + } + + for _, measuredValue := range measuredValues { + measuredValue.ValueType = types.Pressure + } + + return measuredValues[0], nil +} + +// GetPressures returns pressure from the database +func (postgres *Postgres) GetPressures(ctx context.Context) ([]*types.MeasuredValue, error) { + f, err := postgresDMLAssets.ReadFile(selectPressuresSQLFile) + if err != nil { + return nil, fmt.Errorf("Failed to read file %v: %w", selectPressuresSQLFile, err) + } + + tx, err := postgres.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: true}) + if err != nil { + return nil, err + } + defer tx.Rollback() + + measuredValues, err := postgres.selectMeasuredValue(tx, string(f)) + if err != nil { + return nil, err + } + + err = tx.Commit() + if err != nil { + return nil, err + } + + for _, measuredValue := range measuredValues { + measuredValue.ValueType = types.Pressure + } + + return measuredValues, nil +} + +// GetSensor from database +func (postgres *Postgres) GetSensorByID(ctx context.Context, id string) (*types.Sensor, error) { + f, err := postgresDMLAssets.ReadFile(selectSensorByIDSQLFile) + if err != nil { + return nil, fmt.Errorf("Failed to read file %v: %w", selectSensorByIDSQLFile, err) + } + + tx, err := postgres.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: true}) + if err != nil { + return nil, fmt.Errorf("Failed to begin new transaction: %v", err) + } + defer tx.Rollback() + + sensors, err := postgres.selectSensors(tx, string(f), id) + if err != nil { + return nil, err + } + + err = tx.Commit() + if err != nil { + return nil, fmt.Errorf("Failed to commit transaction: %v", err) + } + + if len(sensors) == 0 { + return nil, nil + } + + return sensors[0], nil +} + +// GetSensors from the database +func (postgres *Postgres) GetSensors(ctx context.Context) ([]*types.Sensor, error) { + f, err := postgresDMLAssets.ReadFile(selectSensorsSQLFile) + if err != nil { + return nil, fmt.Errorf("Failed to read file %v: %w", selectSensorsSQLFile, err) + } + + tx, err := postgres.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: true}) + if err != nil { + return nil, fmt.Errorf("Failed to begin new transaction: %v", err) + } + defer tx.Rollback() + + sensors, err := postgres.selectSensors(tx, string(f)) + 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 +} + +// GetSensorsByModels from the database +func (postgres *Postgres) GetSensorsByDeviceIDs(ctx context.Context, deviceIDs ...string) ([]*types.Sensor, error) { + f, err := postgresDMLAssets.ReadFile(selectSensorsByDeviceIDSQLFile) + if err != nil { + return nil, fmt.Errorf("Failed to read file %v: %w", selectSensorsByDeviceIDSQLFile, err) + } + + tx, err := postgres.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: true}) + if err != nil { + return nil, fmt.Errorf("Failed to begin new transaction: %v", err) + } + defer tx.Rollback() + + cachedSensors := make([]*types.Sensor, 0) + for i := range deviceIDs { + sensors, err := postgres.selectSensors(tx, string(f), deviceIDs[i]) + if err != nil { + return nil, err + } + + cachedSensors = append(cachedSensors, sensors...) + } + + err = tx.Commit() + if err != nil { + return nil, fmt.Errorf("Failed to commit transaction: %v", err) + } + + return cachedSensors, nil +} + +// GetSensorsByModel from the database +func (postgres *Postgres) GetSensorsByModels(ctx context.Context, sensorModels ...string) ([]*types.Sensor, error) { + f, err := postgresDMLAssets.ReadFile(selectSensorsByModelSQLFile) + if err != nil { + return nil, fmt.Errorf("Failed to read file %v: %w", selectSensorsByModelSQLFile, err) + } + + tx, err := postgres.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: true}) + if err != nil { + return nil, fmt.Errorf("Failed to begin new transaction: %v", err) + } + + cachedSensors := make([]*types.Sensor, 0) + for i := range sensorModels { + sensors, err := postgres.selectSensors(tx, string(f), sensorModels[i]) + if err != nil { + return nil, err + } + + cachedSensors = append(cachedSensors, sensors...) + } + + err = tx.Commit() + if err != nil { + return nil, fmt.Errorf("Failed to commit transaction: %v", err) + } + + return cachedSensors, nil +} + +// GetSensorsByModel from the database +func (postgres *Postgres) GetSensorsByNames(ctx context.Context, sensorNames ...string) ([]*types.Sensor, error) { + f, err := postgresDMLAssets.ReadFile(selectSensorsByNameSQLFile) + if err != nil { + return nil, fmt.Errorf("Failed to read file %v: %w", selectSensorsByNameSQLFile, err) + } + + tx, err := postgres.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: true}) + if err != nil { + return nil, fmt.Errorf("Failed to begin new transaction: %v", err) + } + defer tx.Rollback() + + cachedSensors := make([]*types.Sensor, 0) + for i := range sensorNames { + sensors, err := postgres.selectSensors(tx, string(f), sensorNames[i]) + if err != nil { + return nil, err + } + + cachedSensors = append(cachedSensors, sensors...) + } + + err = tx.Commit() + if err != nil { + return nil, fmt.Errorf("Failed to commit transaction: %v", err) + } + + return cachedSensors, 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 +} + +// GetTemperature returns temperatures from the database +func (postgres *Postgres) GetTemperatureByID(ctx context.Context, id string) (*types.MeasuredValue, error) { + f, err := postgresDMLAssets.ReadFile(selectTemperatureByIDSQLFile) + if err != nil { + return nil, fmt.Errorf("Failed to read file %v: %w", selectTemperatureByIDSQLFile, err) + } + + tx, err := postgres.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: true}) + if err != nil { + return nil, err + } + defer tx.Rollback() + + measuredValues, err := postgres.selectMeasuredValue(tx, string(f), id) + if err != nil { + return nil, err + } + + err = tx.Commit() + if err != nil { + return nil, err + } + + if measuredValues == nil { + return nil, nil + } + + for _, measuredValue := range measuredValues { + measuredValue.ValueType = types.Temperature + } + + return measuredValues[0], nil +} + +// GetTemperatures returns temperatures from the database +func (postgres *Postgres) GetTemperatures(ctx context.Context) ([]*types.MeasuredValue, error) { + f, err := postgresDMLAssets.ReadFile(selectTemperaturesSQLFile) + if err != nil { + return nil, fmt.Errorf("Failed to read file %v: %w", selectTemperaturesSQLFile, err) + } + + tx, err := postgres.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: true}) + if err != nil { + return nil, err + } + defer tx.Rollback() + + measuredValues, err := postgres.selectMeasuredValue(tx, string(f)) + if err != nil { + return nil, err + } + + err = tx.Commit() + if err != nil { + return nil, err + } + + for _, measuredValue := range measuredValues { + measuredValue.ValueType = types.Temperature + } + + return measuredValues, nil +} + +// UpdateDevices updates a device in the database +func (postgres *Postgres) UpdateDevices(ctx context.Context, devices ...*types.Device) error { + f, err := postgresDMLAssets.ReadFile(updateDeviceSQLFile) + if err != nil { + return fmt.Errorf("Failed to read file %v: %w", updateDeviceSQLFile, err) + } + + tx, err := postgres.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: false}) + if err != nil { + return err + } + defer tx.Rollback() + + stmt, err := tx.Prepare(string(f)) + if err != nil { + 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 { + return err + } + } + + return tx.Commit() +} + +// UpdateSensors updates a sensor in the database +func (postgres *Postgres) UpdateSensors(ctx context.Context, sensors ...*types.Sensor) error { + f, err := postgresDMLAssets.ReadFile(updateSensorSQLFile) + if err != nil { + return fmt.Errorf("Failed to read file %v: %w", updateSensorSQLFile, err) + } + + tx, err := postgres.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: false}) + if err != nil { + return err + } + defer tx.Rollback() + + stmt, err := tx.Prepare(string(f)) + if err != nil { + 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 { + return err + } + } + + return tx.Commit() +} diff --git a/pkg/repository/repository.go b/pkg/repository/repository.go index 68395b3..bf399ac 100644 --- a/pkg/repository/repository.go +++ b/pkg/repository/repository.go @@ -4,339 +4,412 @@ import ( "context" "fmt" "net/url" - "strings" - "git.cryptic.systems/volker.raschek/flucky/pkg/repository/db" + "git.cryptic.systems/volker.raschek/flucky/pkg/repository/postgres" + "git.cryptic.systems/volker.raschek/flucky/pkg/repository/sqlite3" "git.cryptic.systems/volker.raschek/flucky/pkg/types" "git.cryptic.systems/volker.raschek/go-logger" ) +type Repository interface { + AddDevices(ctx context.Context, devices ...*types.Device) error + AddMeasuredValues(ctx context.Context, measuredValues ...*types.MeasuredValue) error + AddOrUpdateDevices(ctx context.Context, devices ...*types.Device) error + AddOrUpdateMeasuredValues(ctx context.Context, measuredValues ...*types.MeasuredValue) error + AddOrUpdateSensors(ctx context.Context, sensors ...*types.Sensor) error + AddSensors(ctx context.Context, sensors ...*types.Sensor) error + Close() error + GetDeviceByID(ctx context.Context, deviceID string) (*types.Device, error) + GetDeviceByName(ctx context.Context, name string) (*types.Device, error) + GetDevices(ctx context.Context) ([]*types.Device, error) + GetHumidities(ctx context.Context) ([]*types.MeasuredValue, error) + GetHumidityByID(ctx context.Context, id string) (*types.MeasuredValue, error) + GetPressureByID(ctx context.Context, id string) (*types.MeasuredValue, error) + GetPressures(ctx context.Context) ([]*types.MeasuredValue, error) + GetSensorByID(ctx context.Context, sensorID string) (*types.Sensor, error) + GetSensorsByDeviceIDs(ctx context.Context, deviceIDs ...string) ([]*types.Sensor, error) + GetSensorsByModels(ctx context.Context, sensorModels ...string) ([]*types.Sensor, error) + GetSensorsByNames(ctx context.Context, sensorModels ...string) ([]*types.Sensor, error) + GetSensors(ctx context.Context) ([]*types.Sensor, error) + GetTemperatureByID(ctx context.Context, id string) (*types.MeasuredValue, error) + GetTemperatures(ctx context.Context) ([]*types.MeasuredValue, error) + Migrate(ctx context.Context) error + RemoveDevicesByIDs(ctx context.Context, deviceIDs ...string) error + RemoveDevicesByNames(ctx context.Context, names ...string) error + RemoveSensorsByIDs(ctx context.Context, sensorIDs ...string) error + RemoveSensorsByNames(ctx context.Context, names ...string) error + UpdateDevices(ctx context.Context, devices ...*types.Device) error + UpdateSensors(ctx context.Context, sensors ...*types.Sensor) error +} + +// New returns a new database backend interface +func New(databaseURL *url.URL, flogger logger.Logger) (Repository, error) { + switch databaseURL.Scheme { + case "postgres": + repo, err := postgres.New(postgres.Opts{ + DatabaseURL: databaseURL, + Logger: flogger, + }) + if err != nil { + return nil, err + } + + // Initialize database scheme if not exists + err = repo.Migrate(context.Background()) + if err != nil { + return nil, err + } + + return repo, nil + + case "sqlite3": + repo, err := sqlite3.New(sqlite3.Opts{ + DatabaseURL: databaseURL, + Logger: flogger, + }) + if err != nil { + return nil, err + } + + // Initialize database scheme if not exists + err = repo.Migrate(context.Background()) + if err != nil { + return nil, err + } + + return repo, nil + + default: + return nil, fmt.Errorf("Unsupported repository scheme: %v", databaseURL.Scheme) + } +} + // Repository represent a repository where all devices, sensors and measured // values are stored. -type Repository struct { - database db.Database -} +// type Repository struct { +// database db.Database +// } -// AddDevices to the repository -func (repo *Repository) AddDevices(devices ...*types.Device) error { - return repo.database.InsertDevices(context.Background(), devices...) -} +// // AddDevices to the repository +// func (repo *Repository) AddDevices(devices ...*types.Device) error { +// return repo.database.InsertDevices(context.Background(), devices...) +// } -// AddMeasuredValues to the repository -func (repo *Repository) AddMeasuredValues(measuredValues ...*types.MeasuredValue) error { - return repo.database.InsertMeasuredValues(context.Background(), measuredValues...) -} +// // AddMeasuredValues to the repository +// func (repo *Repository) AddMeasuredValues(measuredValues ...*types.MeasuredValue) error { +// return repo.database.InsertMeasuredValues(context.Background(), measuredValues...) +// } -// AddOrUpdateDevices to the repository -func (repo *Repository) AddOrUpdateDevices(devices ...*types.Device) error { - return repo.database.InsertOrUpdateDevices(context.Background(), devices...) -} +// // AddOrUpdateDevices to the repository +// func (repo *Repository) AddOrUpdateDevices(devices ...*types.Device) error { +// return repo.database.InsertOrUpdateDevices(context.Background(), devices...) +// } -// AddOrUpdateMeasuredValues to the repository -func (repo *Repository) AddOrUpdateMeasuredValues(measuredValues ...*types.MeasuredValue) error { - return repo.database.InsertOrUpdateMeasuredValues(context.Background(), measuredValues...) -} +// // AddOrUpdateMeasuredValues to the repository +// func (repo *Repository) AddOrUpdateMeasuredValues(measuredValues ...*types.MeasuredValue) error { +// return repo.database.InsertOrUpdateMeasuredValues(context.Background(), measuredValues...) +// } -// AddOrUpdateSensors to the repository -func (repo *Repository) AddOrUpdateSensors(sensors ...*types.Sensor) error { - return repo.database.InsertOrUpdateSensors(context.Background(), sensors...) -} +// // AddOrUpdateSensors to the repository +// func (repo *Repository) AddOrUpdateSensors(sensors ...*types.Sensor) error { +// return repo.database.InsertOrUpdateSensors(context.Background(), sensors...) +// } -// AddSensors to the repository -func (repo *Repository) AddSensors(sensors ...*types.Sensor) error { - return repo.database.InsertSensors(context.Background(), sensors...) -} +// // AddSensors to the repository +// func (repo *Repository) AddSensors(sensors ...*types.Sensor) error { +// return repo.database.InsertSensors(context.Background(), sensors...) +// } -// Close closes the repository and prevents new queries from starting. Close -// then waits for all queries that have started processing on the server to -// finish. -func (repo *Repository) Close() error { - return repo.database.Close() -} +// // Close closes the repository and prevents new queries from starting. Close +// // then waits for all queries that have started processing on the server to +// // finish. +// func (repo *Repository) Close() error { +// return repo.database.Close() +// } -// DisableSensorsByNames disable all sensors which match bei their name -func (repo *Repository) DisableSensorsByNames(sensorNames ...string) error { - sensors, err := repo.GetSensors() - if err != nil { - return err - } +// // DisableSensorsByNames disable all sensors which match bei their name +// func (repo *Repository) DisableSensorsByNames(sensorNames ...string) error { +// sensors, err := repo.GetSensors() +// if err != nil { +// return err +// } - matchedSensors := make([]*types.Sensor, 0) - for _, sensor := range sensors { - for _, sensorName := range sensorNames { - if strings.Compare(sensor.Name, sensorName) == 0 { - sensor.Enabled = false - matchedSensors = append(matchedSensors, sensor) - } - } - } +// matchedSensors := make([]*types.Sensor, 0) +// for _, sensor := range sensors { +// for _, sensorName := range sensorNames { +// if strings.Compare(sensor.Name, sensorName) == 0 { +// sensor.Enabled = false +// matchedSensors = append(matchedSensors, sensor) +// } +// } +// } - return repo.UpdateSensors(matchedSensors...) -} +// return repo.UpdateSensors(matchedSensors...) +// } -// EnableSensorsByNames enable all sensors which match bei their name -func (repo *Repository) EnableSensorsByNames(sensorNames ...string) error { - sensors, err := repo.GetSensors() - if err != nil { - return err - } +// // EnableSensorsByNames enable all sensors which match bei their name +// func (repo *Repository) EnableSensorsByNames(sensorNames ...string) error { +// sensors, err := repo.GetSensors() +// if err != nil { +// return err +// } - matchedSensors := make([]*types.Sensor, 0) - for _, sensor := range sensors { - for _, sensorName := range sensorNames { - if strings.Compare(sensor.Name, sensorName) == 0 { - sensor.Enabled = true - matchedSensors = append(matchedSensors, sensor) - } - } - } +// matchedSensors := make([]*types.Sensor, 0) +// for _, sensor := range sensors { +// for _, sensorName := range sensorNames { +// if strings.Compare(sensor.Name, sensorName) == 0 { +// sensor.Enabled = true +// matchedSensors = append(matchedSensors, sensor) +// } +// } +// } - return repo.UpdateSensors(matchedSensors...) -} +// return repo.UpdateSensors(matchedSensors...) +// } -// GetDevice returns a device by his id. If no device has been found, the -// function returns nil. -func (repo *Repository) GetDevice(deviceID string) (*types.Device, error) { - return repo.database.SelectDevice(context.Background(), deviceID) -} +// // GetDevice returns a device by his id. If no device has been found, the +// // function returns nil. +// func (repo *Repository) GetDevice(deviceID string) (*types.Device, error) { +// return repo.database.SelectDevice(context.Background(), deviceID) +// } -// GetDevices returns all devices. If no devices has been found, the function -// returns nil. -func (repo *Repository) GetDevices() ([]*types.Device, error) { - return repo.database.SelectDevices(context.Background()) -} +// // GetDevices returns all devices. If no devices has been found, the function +// // returns nil. +// func (repo *Repository) GetDevices() ([]*types.Device, error) { +// return repo.database.SelectDevices(context.Background()) +// } -// GetHumidity returns a humidity value by id -func (repo *Repository) GetHumidity(humidityID string) (*types.MeasuredValue, error) { - return repo.database.SelectHumidity(context.Background(), humidityID) -} +// // GetHumidity returns a humidity value by id +// func (repo *Repository) GetHumidity(humidityID string) (*types.MeasuredValue, error) { +// return repo.database.SelectHumidity(context.Background(), humidityID) +// } -// GetPressure returns a pressure value by id -func (repo *Repository) GetPressure(pressureID string) (*types.MeasuredValue, error) { - return repo.database.SelectPressure(context.Background(), pressureID) -} +// // GetPressure returns a pressure value by id +// func (repo *Repository) GetPressure(pressureID string) (*types.MeasuredValue, error) { +// return repo.database.SelectPressure(context.Background(), pressureID) +// } -// GetSensor returns a sensor by his id. If no sensor has been found, the -// function returns nil. -func (repo *Repository) GetSensor(sensorID string) (*types.Sensor, error) { - return repo.database.SelectSensor(context.Background(), sensorID) -} +// // GetSensor returns a sensor by his id. If no sensor has been found, the +// // function returns nil. +// func (repo *Repository) GetSensor(sensorID string) (*types.Sensor, error) { +// return repo.database.SelectSensor(context.Background(), sensorID) +// } -// GetSensors returns all sensors. If no sensors has been found, the function -// returns nil. -func (repo *Repository) GetSensors(models ...string) ([]*types.Sensor, error) { - sensors, err := repo.database.SelectSensors(context.Background()) - switch { - case err != nil: - return nil, err - case len(models) > 0: - cachedSensors := make([]*types.Sensor, 0) - LOOP: - for i := range sensors { - for j := range models { - if strings.ToLower(sensors[i].Model) == strings.ToLower(models[j]) { - cachedSensors = append(cachedSensors, sensors[i]) - continue LOOP - } - } - } - return cachedSensors, nil - case len(models) <= 0: - fallthrough - default: - return sensors, err - } -} +// // GetSensors returns all sensors. If no sensors has been found, the function +// // returns nil. +// func (repo *Repository) GetSensors(models ...string) ([]*types.Sensor, error) { +// sensors, err := repo.database.SelectSensors(context.Background()) +// switch { +// case err != nil: +// return nil, err +// case len(models) > 0: +// cachedSensors := make([]*types.Sensor, 0) +// LOOP: +// for i := range sensors { +// for j := range models { +// if strings.ToLower(sensors[i].Model) == strings.ToLower(models[j]) { +// cachedSensors = append(cachedSensors, sensors[i]) +// continue LOOP +// } +// } +// } +// return cachedSensors, nil +// case len(models) <= 0: +// fallthrough +// default: +// return sensors, err +// } +// } -// GetSensorsByDeviceID returns all sensors by a device id. If no sensor has -// been found by the id, the function returns nil. -func (repo *Repository) GetSensorsByDeviceID(deviceID string) ([]*types.Sensor, error) { - cachedSensors := make([]*types.Sensor, 0) - sensors, err := repo.GetSensors() - if err != nil { - return nil, err - } +// // GetSensorsByDeviceID returns all sensors by a device id. If no sensor has +// // been found by the id, the function returns nil. +// func (repo *Repository) GetSensorsByDeviceID(deviceID string) ([]*types.Sensor, error) { +// cachedSensors := make([]*types.Sensor, 0) +// sensors, err := repo.GetSensors() +// if err != nil { +// return nil, err +// } - for _, sensor := range sensors { - if strings.Compare(sensor.DeviceID, deviceID) == 0 { - cachedSensors = append(cachedSensors, sensor) - } - } +// for _, sensor := range sensors { +// if strings.Compare(sensor.DeviceID, deviceID) == 0 { +// cachedSensors = append(cachedSensors, sensor) +// } +// } - return cachedSensors, nil -} +// return cachedSensors, nil +// } -// GetTemperature returns a temperature value by id -func (repo *Repository) GetTemperature(pressureID string) (*types.MeasuredValue, error) { - return repo.database.SelectTemperature(context.Background(), pressureID) -} +// // GetTemperature returns a temperature value by id +// func (repo *Repository) GetTemperature(pressureID string) (*types.MeasuredValue, error) { +// return repo.database.SelectTemperature(context.Background(), pressureID) +// } -// RemoveDevices removes devices by their ids from the repository. Additional -// all sensors and measured values, which are in relation with the device -// respectively the sensors will also be deleted. -func (repo *Repository) RemoveDevices(deviceIDs ...string) error { - return repo.database.DeleteDevices(context.Background(), deviceIDs...) -} +// // RemoveDevices removes devices by their ids from the repository. Additional +// // all sensors and measured values, which are in relation with the device +// // respectively the sensors will also be deleted. +// func (repo *Repository) RemoveDevices(deviceIDs ...string) error { +// return repo.database.DeleteDevices(context.Background(), deviceIDs...) +// } -// RenameSensors all sensors which match by their current name to the new name -func (repo *Repository) RenameSensors(oldName string, newName string) error { - sensors, err := repo.GetSensors() - if err != nil { - return err - } +// // RenameSensors all sensors which match by their current name to the new name +// func (repo *Repository) RenameSensors(oldName string, newName string) error { +// sensors, err := repo.GetSensors() +// if err != nil { +// return err +// } - matchedSensors := make([]*types.Sensor, 0) - for _, sensor := range sensors { - if strings.Compare(sensor.Name, oldName) == 0 { - sensor.Name = newName - matchedSensors = append(matchedSensors, sensor) - } - } +// matchedSensors := make([]*types.Sensor, 0) +// for _, sensor := range sensors { +// if strings.Compare(sensor.Name, oldName) == 0 { +// sensor.Name = newName +// matchedSensors = append(matchedSensors, sensor) +// } +// } - return repo.UpdateSensors(matchedSensors...) -} +// return repo.UpdateSensors(matchedSensors...) +// } -// RemoveSensors removes sensors by their ids from the repository. Additional -// all measured values, which are in relation with the sensor will also be -// deleted. -func (repo *Repository) RemoveSensors(sensorIDs ...string) error { - return repo.database.DeleteSensors(context.Background(), sensorIDs...) -} +// // RemoveSensors removes sensors by their ids from the repository. Additional +// // all measured values, which are in relation with the sensor will also be +// // deleted. +// func (repo *Repository) RemoveSensors(sensorIDs ...string) error { +// return repo.database.DeleteSensors(context.Background(), sensorIDs...) +// } -// RemoveSensorsByNames removes all sensors which match bei their name -func (repo *Repository) RemoveSensorsByNames(sensorNames ...string) error { - sensors, err := repo.GetSensors() - if err != nil { - return err - } +// // RemoveSensorsByNames removes all sensors which match bei their name +// func (repo *Repository) RemoveSensorsByNames(sensorNames ...string) error { +// sensors, err := repo.GetSensors() +// if err != nil { +// return err +// } - matchedSensorIDs := make([]string, 0) - for _, sensor := range sensors { - for _, sensorName := range sensorNames { - if strings.Compare(sensor.Name, sensorName) == 0 { - matchedSensorIDs = append(matchedSensorIDs, sensor.ID) - } - } - } +// matchedSensorIDs := make([]string, 0) +// for _, sensor := range sensors { +// for _, sensorName := range sensorNames { +// if strings.Compare(sensor.Name, sensorName) == 0 { +// matchedSensorIDs = append(matchedSensorIDs, sensor.ID) +// } +// } +// } - return repo.RemoveSensors(matchedSensorIDs...) -} +// return repo.RemoveSensors(matchedSensorIDs...) +// } -// UpdateDevices update devices which are stored into the repository by their -// id. The id of the device can not be updated. -func (repo *Repository) UpdateDevices(devices ...*types.Device) error { - return repo.database.UpdateDevices(context.Background(), devices...) -} +// // UpdateDevices update devices which are stored into the repository by their +// // id. The id of the device can not be updated. +// func (repo *Repository) UpdateDevices(devices ...*types.Device) error { +// return repo.database.UpdateDevices(context.Background(), devices...) +// } -// UpdateSensors update sensors which are stored into the repository by their -// id. The id of the sensor can not be updated. -func (repo *Repository) UpdateSensors(sensors ...*types.Sensor) error { - return repo.database.UpdateSensors(context.Background(), sensors...) -} +// // UpdateSensors update sensors which are stored into the repository by their +// // id. The id of the sensor can not be updated. +// func (repo *Repository) UpdateSensors(sensors ...*types.Sensor) error { +// return repo.database.UpdateSensors(context.Background(), sensors...) +// } -// New returns a new repository based on the data source name (dsn) -func New(dsnURL *url.URL, flogger logger.Logger) (*Repository, error) { - database, err := db.New(dsnURL, flogger) - if err != nil { - return nil, err - } +// // New returns a new repository based on the data source name (dsn) +// func New(dsnURL *url.URL, flogger logger.Logger) (*Repository, error) { +// database, err := db.New(dsnURL, flogger) +// if err != nil { +// return nil, err +// } - return &Repository{ - database: database, - }, nil -} +// return &Repository{ +// database: database, +// }, nil +// } -type OptImport struct { - Sensors bool - Humidities bool - Pressures bool - Temperatures bool -} +// type OptImport struct { +// Sensors bool +// Humidities bool +// Pressures bool +// Temperatures bool +// } -func Import(sourceDSNURL *url.URL, destDNSURL *url.URL, flogger logger.Logger, optImport OptImport) error { +// func Import(sourceDSNURL *url.URL, destDNSURL *url.URL, flogger logger.Logger, optImport OptImport) error { - importMap := map[string]bool{ - "humidities": optImport.Humidities, - "pressures": optImport.Pressures, - "temperatures": optImport.Temperatures, - } +// importMap := map[string]bool{ +// "humidities": optImport.Humidities, +// "pressures": optImport.Pressures, +// "temperatures": optImport.Temperatures, +// } - // enable sensors if one measured value is enabled - if !optImport.Sensors { - for key, value := range importMap { - if value { - flogger.Info("Enable import option sensors. It's required as foreign key for %v", key) - optImport.Sensors = true - } - } - } +// // enable sensors if one measured value is enabled +// if !optImport.Sensors { +// for key, value := range importMap { +// if value { +// flogger.Info("Enable import option sensors. It's required as foreign key for %v", key) +// optImport.Sensors = true +// } +// } +// } - // Initialize source repository - sourceRepo, err := New(sourceDSNURL, flogger) - if err != nil { - return fmt.Errorf("Failed to open the source repo: %w", err) - } - defer sourceRepo.Close() +// // Initialize source repository +// sourceRepo, err := New(sourceDSNURL, flogger) +// if err != nil { +// return fmt.Errorf("Failed to open the source repo: %w", err) +// } +// defer sourceRepo.Close() - // Initialize destination repository - destRepo, err := New(destDNSURL, flogger) - if err != nil { - return fmt.Errorf("Failed to open the destination repo: %w", err) - } - defer destRepo.Close() +// // Initialize destination repository +// destRepo, err := New(destDNSURL, flogger) +// if err != nil { +// return fmt.Errorf("Failed to open the destination repo: %w", err) +// } +// defer destRepo.Close() - // AddOrUpdate: Devices - ctx := context.Background() - devices, err := sourceRepo.database.SelectDevices(ctx) - if err != nil { - return fmt.Errorf("Failed to fetch devices from source repo: %w", err) - } +// // AddOrUpdate: Devices +// ctx := context.Background() +// devices, err := sourceRepo.database.SelectDevices(ctx) +// if err != nil { +// return fmt.Errorf("Failed to fetch devices from source repo: %w", err) +// } - flogger.Debug("Found %v devices", len(devices)) +// flogger.Debug("Found %v devices", len(devices)) - for i := range devices { - err := destRepo.AddOrUpdateDevices(devices[i]) - if err != nil { - return fmt.Errorf("Failed to add or update device %v into dest repo: %w", devices[i].Name, err) - } - } +// for i := range devices { +// err := destRepo.AddOrUpdateDevices(devices[i]) +// if err != nil { +// return fmt.Errorf("Failed to add or update device %v into dest repo: %w", devices[i].Name, err) +// } +// } - // AddOrUpdate: Sensors - if optImport.Sensors { - sensors, err := sourceRepo.database.SelectSensors(ctx) - if err != nil { - return fmt.Errorf("Failed to fetch sensors from source repo: %w", err) - } +// // AddOrUpdate: Sensors +// if optImport.Sensors { +// sensors, err := sourceRepo.database.SelectSensors(ctx) +// if err != nil { +// return fmt.Errorf("Failed to fetch sensors from source repo: %w", err) +// } - flogger.Debug("Found %v sensors", len(sensors)) +// flogger.Debug("Found %v sensors", len(sensors)) - for i := range sensors { - err := destRepo.AddOrUpdateSensors(sensors[i]) - if err != nil { - return fmt.Errorf("Failed to add or update sensor %v into dest repo: %w", sensors[i].Name, err) - } - } - } +// for i := range sensors { +// err := destRepo.AddOrUpdateSensors(sensors[i]) +// if err != nil { +// return fmt.Errorf("Failed to add or update sensor %v into dest repo: %w", sensors[i].Name, err) +// } +// } +// } - for key, f := range map[string]func(context.Context) ([]*types.MeasuredValue, error){ - "humidities": sourceRepo.database.SelectHumidities, - "pressures": sourceRepo.database.SelectPressures, - "temperatures": sourceRepo.database.SelectTemperatures, - } { - if importMap[key] { - measuredValues, err := f(ctx) - if err != nil { - return fmt.Errorf("Failed to select %v from source repo: %w", key, err) - } +// for key, f := range map[string]func(context.Context) ([]*types.MeasuredValue, error){ +// "humidities": sourceRepo.database.SelectHumidities, +// "pressures": sourceRepo.database.SelectPressures, +// "temperatures": sourceRepo.database.SelectTemperatures, +// } { +// if importMap[key] { +// measuredValues, err := f(ctx) +// if err != nil { +// return fmt.Errorf("Failed to select %v from source repo: %w", key, err) +// } - flogger.Debug("Found %v %v values", len(measuredValues), key) +// flogger.Debug("Found %v %v values", len(measuredValues), key) - err = destRepo.AddOrUpdateMeasuredValues(measuredValues...) - if err != nil { - return fmt.Errorf("Failed to add or update %v into dest repo: %w", key, err) - } - } - } +// err = destRepo.AddOrUpdateMeasuredValues(measuredValues...) +// if err != nil { +// return fmt.Errorf("Failed to add or update %v into dest repo: %w", key, err) +// } +// } +// } - return nil -} +// return nil +// } diff --git a/pkg/repository/repository_test.go b/pkg/repository/repository_test.go index 7593512..2c84c88 100644 --- a/pkg/repository/repository_test.go +++ b/pkg/repository/repository_test.go @@ -31,15 +31,13 @@ func TestPostgresBackend(t *testing.T) { require.NoError(err) rand.Seed(time.Now().Unix()) - postgresHostPort := rand.Intn(10024-1024) + 1024 postgresDBPasswort := "postgres" postgresContainerID, err := dockerClient.NewBuilder("postgres:13-alpine"). - Port(fmt.Sprintf("%v:5432/tcp", postgresHostPort)). - Pull(). AddEnv("PGTZ", "Europe/Berlin"). AddEnv("POSTGRES_PASSWORD", postgresDBPasswort). AddEnv("TZ", "Europe/Berlin"). Mount("/etc/localtime", "/etc/localtime"). + Pull(). Start(context.Background()) cleanup := func() { dockerClient.ContainerRemoveByIDs(context.Background(), postgresContainerID) @@ -49,8 +47,12 @@ func TestPostgresBackend(t *testing.T) { time.Sleep(time.Second * 10) + // inspect container to get his container ip + cjson, err := dockerClient.ContainerInspect(context.Background(), postgresContainerID) + require.NoError(err) + // postgres://[user]:[password]@[host]:[port]/[path]?[query] - dsnURL, err := url.Parse(fmt.Sprintf("postgres://postgres:%v@127.0.0.1:%v?sslmode=disable", postgresDBPasswort, postgresHostPort)) + dsnURL, err := url.Parse(fmt.Sprintf("postgres://postgres:%v@%v:5432?sslmode=disable", postgresDBPasswort, cjson.NetworkSettings.IPAddress)) require.NoError(err) repo, err := repository.New(dsnURL, logger.NewLogger(logger.LogLevelDebug)) @@ -75,7 +77,8 @@ func TestSQLiteBackend(t *testing.T) { testBackend(t, repo) } -func testBackend(t *testing.T, repo *repository.Repository) { +func testBackend(t *testing.T, repo repository.Repository) { + ctx := context.Background() require := require.New(t) location := uuid.NewV4().String() expectedDevices := []*types.Device{ @@ -94,33 +97,50 @@ func testBackend(t *testing.T, repo *repository.Repository) { } // Test: AddDevice - err := repo.AddDevices(expectedDevices...) + err := repo.AddDevices(ctx, expectedDevices...) require.NoError(err) // Test: GetDevices - devices, err := repo.GetDevices() + devices, err := repo.GetDevices(ctx) require.NoError(err) require.Len(devices, len(expectedDevices)) require.JSONEq(jsonEncoder(expectedDevices), jsonEncoder(devices)) - // Test: GetDevice - device, err := repo.GetDevice(expectedDevices[0].ID) + // Test: GetDeviceByID + device, err := repo.GetDeviceByID(ctx, expectedDevices[0].ID) require.NoError(err) require.JSONEq(jsonEncoder(expectedDevices[0]), jsonEncoder(device)) - // Test: RemoveDevice - err = repo.RemoveDevices(expectedDevices[0].ID) + // Test: GetDeviceByName + device, err = repo.GetDeviceByName(ctx, expectedDevices[0].Name) + require.NoError(err) + require.JSONEq(jsonEncoder(expectedDevices[0]), jsonEncoder(device)) + + // Test: RemoveDevicesByIDs + err = repo.RemoveDevicesByIDs(ctx, expectedDevices[0].ID) require.NoError(err) - devices, err = repo.GetDevices() + devices, err = repo.GetDevices(ctx) require.NoError(err) require.Len(devices, 1) - device, err = repo.GetDevice(expectedDevices[0].ID) + device, err = repo.GetDeviceByID(ctx, expectedDevices[0].ID) require.NoError(err) require.Nil(device) - err = repo.AddDevices(expectedDevices[0]) + // Test: RemoveDevicesByNames + err = repo.RemoveDevicesByNames(ctx, expectedDevices[0].Name) + require.NoError(err) + + devices, err = repo.GetDevices(ctx) + require.NoError(err) + require.Len(devices, 1) + + device, err = repo.GetDeviceByID(ctx, expectedDevices[0].ID) + require.NoError(err) + require.Nil(device) + + err = repo.AddDevices(ctx, expectedDevices[0]) require.NoError(err) // Test: Update Devices @@ -132,10 +152,10 @@ func testBackend(t *testing.T, repo *repository.Repository) { CreationDate: *timeNow(require), } - err = repo.UpdateDevices(expectedDevice) + err = repo.UpdateDevices(ctx, expectedDevice) require.NoError(err) - device, err = repo.GetDevice(expectedDevice.ID) + device, err = repo.GetDeviceByID(ctx, expectedDevice.ID) require.NoError(err) require.NotEmpty(device) require.Equal(expectedDevice.ID, device.ID) @@ -150,12 +170,12 @@ func testBackend(t *testing.T, repo *repository.Repository) { Location: &location, CreationDate: *timeNow(require), } - err = repo.AddOrUpdateDevices(expectedDevice) + err = repo.AddOrUpdateDevices(ctx, expectedDevice) require.NoError(err) // time.Sleep(time.Minute * 10) - device, err = repo.GetDevice(expectedDevice.ID) + device, err = repo.GetDeviceByID(ctx, expectedDevice.ID) require.NoError(err) require.NotEmpty(device) require.Equal(expectedDevice.ID, device.ID) @@ -169,10 +189,10 @@ func testBackend(t *testing.T, repo *repository.Repository) { Location: &location, CreationDate: *timeNow(require), } - err = repo.AddOrUpdateDevices(expectedDevice) + err = repo.AddOrUpdateDevices(ctx, expectedDevice) require.NoError(err) - device, err = repo.GetDevice(expectedDevice.ID) + device, err = repo.GetDeviceByID(ctx, expectedDevice.ID) require.NoError(err) require.NotEmpty(device) require.Equal(expectedDevice.ID, device.ID) @@ -222,97 +242,80 @@ func testBackend(t *testing.T, repo *repository.Repository) { ) // Test: AddSensors - err = repo.AddSensors(expectedSensors...) + err = repo.AddSensors(ctx, expectedSensors...) require.NoError(err) // Test: GetSensors - sensors, err := repo.GetSensors() + sensors, err := repo.GetSensors(ctx) require.NoError(err) require.Len(sensors, len(expectedSensors)) - sensors, err = repo.GetSensors("BME280") - require.NoError(err) - require.Len(sensors, 1) - require.JSONEq(jsonEncoder(expectedSensors[2]), jsonEncoder(sensors[0])) - - sensors, err = repo.GetSensors("DS18B20") - require.NoError(err) - require.Len(sensors, 1) - require.JSONEq(jsonEncoder(expectedSensors[1]), jsonEncoder(sensors[0])) - - sensors, err = repo.GetSensors("DHT11") + // Test: GetSensorsByNames + sensors, err = repo.GetSensorsByNames(ctx, "01fbdbe9-cebf-42ed-8065-bf4882ccf76b") require.NoError(err) require.Len(sensors, 1) require.JSONEq(jsonEncoder(expectedSensors[0]), jsonEncoder(sensors[0])) - sensors, err = repo.GetSensors("DHT11", "DS18B20") + // Test: GetSensorsByModels + sensors, err = repo.GetSensorsByModels(ctx, "BME280") + require.NoError(err) + require.Len(sensors, 1) + require.JSONEq(jsonEncoder(expectedSensors[2]), jsonEncoder(sensors[0])) + + sensors, err = repo.GetSensorsByModels(ctx, "DS18B20") + require.NoError(err) + require.Len(sensors, 1) + require.JSONEq(jsonEncoder(expectedSensors[1]), jsonEncoder(sensors[0])) + + sensors, err = repo.GetSensorsByModels(ctx, "DHT11") + require.NoError(err) + require.Len(sensors, 1) + require.JSONEq(jsonEncoder(expectedSensors[0]), jsonEncoder(sensors[0])) + + sensors, err = repo.GetSensorsByModels(ctx, "DHT11", "DS18B20") require.NoError(err) require.Len(sensors, 2) require.JSONEq(jsonEncoder(expectedSensors[0:2]), jsonEncoder(sensors[0:2])) - // Test: GetSensor - sensor, err := repo.GetSensor(expectedSensors[0].ID) + // Test: GetSensorByID + sensor, err := repo.GetSensorByID(ctx, expectedSensors[0].ID) require.NoError(err) require.JSONEq(jsonEncoder(expectedSensors[0]), jsonEncoder(sensor)) // Test: GetSensorsByDeviceID - sensors, err = repo.GetSensorsByDeviceID("ec0be3ab-d26d-4f9b-a96e-23ae5c577f8f") + sensors, err = repo.GetSensorsByDeviceIDs(ctx, "ec0be3ab-d26d-4f9b-a96e-23ae5c577f8f") require.NoError(err) require.Len(sensors, 2) require.JSONEq(jsonEncoder(expectedSensors[0:2]), jsonEncoder(sensors)) - // Test: RemoveSensors - err = repo.RemoveSensors(expectedSensors[0].ID) + // Test: RemoveSensorsByIDs + err = repo.RemoveSensorsByIDs(ctx, expectedSensors[0].ID) require.NoError(err) - sensors, err = repo.GetSensors() + sensors, err = repo.GetSensors(ctx) require.NoError(err) require.Len(sensors, 2) - sensors, err = repo.GetSensorsByDeviceID("ec0be3ab-d26d-4f9b-a96e-23ae5c577f8f") + sensors, err = repo.GetSensorsByDeviceIDs(ctx, "ec0be3ab-d26d-4f9b-a96e-23ae5c577f8f") require.NoError(err) require.Len(sensors, 1) - sensor, err = repo.GetSensor(expectedSensors[0].ID) + sensor, err = repo.GetSensorByID(ctx, expectedSensors[0].ID) require.NoError(err) require.Nil(sensor) // Test: RemoveSensorsByNames - err = repo.RemoveSensorsByNames(expectedSensors[1].Name) + err = repo.RemoveSensorsByNames(ctx, expectedSensors[1].Name) require.NoError(err) - sensors, err = repo.GetSensors() + sensors, err = repo.GetSensors(ctx) require.NoError(err) require.Len(sensors, 1) - sensor, err = repo.GetSensor(expectedSensors[1].ID) + sensor, err = repo.GetSensorByID(ctx, expectedSensors[1].ID) require.NoError(err) require.Nil(sensor) - // Test: RenameSensor - err = repo.RenameSensors(expectedSensors[2].Name, "Hello") - require.NoError(err) - - sensor, err = repo.GetSensor(expectedSensors[2].ID) - require.Equal("Hello", sensor.Name) - require.NotNil(sensor) - - // Test: DisableSensorsByNames - err = repo.DisableSensorsByNames("Hello") - require.NoError(err) - - sensor, err = repo.GetSensor(expectedSensors[2].ID) - require.False(sensor.Enabled) - require.NotNil(sensor) - - // Test: EnableSensorsByName - err = repo.EnableSensorsByNames("Hello") - require.NoError(err) - - sensor, err = repo.GetSensor(expectedSensors[2].ID) - require.True(sensor.Enabled) - require.NotNil(sensor) - // Test: UpdateSensors expectedSensor := &types.Sensor{ ID: "8c74397f-8e60-4c9d-960d-3197747cef9a", @@ -327,10 +330,10 @@ func testBackend(t *testing.T, repo *repository.Repository) { CreationDate: *timeNow(require), } - err = repo.UpdateSensors(expectedSensor) + err = repo.UpdateSensors(ctx, expectedSensor) require.NoError(err) - sensor, err = repo.GetSensor(expectedSensor.ID) + sensor, err = repo.GetSensorByID(ctx, expectedSensor.ID) require.NoError(err) require.NotNil(sensor) // require.JSONEq(jsonEncoder(expectedSensor), jsonEncoder(sensor)) @@ -349,10 +352,10 @@ func testBackend(t *testing.T, repo *repository.Repository) { CreationDate: *timeNow(require), } - err = repo.AddOrUpdateSensors(expectedSensor) + err = repo.AddOrUpdateSensors(ctx, expectedSensor) require.NoError(err) - sensor, err = repo.GetSensor(expectedSensor.ID) + sensor, err = repo.GetSensorByID(ctx, expectedSensor.ID) require.NoError(err) require.NotEmpty(sensor) require.Equal(expectedSensor.ID, sensor.ID) @@ -378,10 +381,10 @@ func testBackend(t *testing.T, repo *repository.Repository) { CreationDate: *timeNow(require), } - err = repo.AddOrUpdateSensors(expectedSensor) + err = repo.AddOrUpdateSensors(ctx, expectedSensor) require.NoError(err) - sensor, err = repo.GetSensor(expectedSensor.ID) + sensor, err = repo.GetSensorByID(ctx, expectedSensor.ID) require.NoError(err) require.NotEmpty(sensor) require.Equal(expectedSensor.ID, sensor.ID) @@ -427,7 +430,7 @@ func testBackend(t *testing.T, repo *repository.Repository) { ) // Test: AddMeasuredValues - err = repo.AddMeasuredValues(expectedMeasuredValues...) + err = repo.AddMeasuredValues(ctx, expectedMeasuredValues...) require.NoError(err) for i := range expectedMeasuredValues { @@ -439,15 +442,15 @@ func testBackend(t *testing.T, repo *repository.Repository) { switch expectedMeasuredValues[i].ValueType { case types.Humidity: - measuredValue, err = repo.GetHumidity(expectedMeasuredValues[i].ID) + measuredValue, err = repo.GetHumidityByID(ctx, expectedMeasuredValues[i].ID) require.NoError(err) require.NotNil(measuredValue) case types.Pressure: - measuredValue, err = repo.GetPressure(expectedMeasuredValues[i].ID) + measuredValue, err = repo.GetPressureByID(ctx, expectedMeasuredValues[i].ID) require.NoError(err) require.NotNil(measuredValue) case types.Temperature: - measuredValue, err = repo.GetTemperature(expectedMeasuredValues[i].ID) + measuredValue, err = repo.GetTemperatureByID(ctx, expectedMeasuredValues[i].ID) require.NoError(err) require.NotNil(measuredValue) } @@ -489,7 +492,7 @@ func testBackend(t *testing.T, repo *repository.Repository) { }, } - err = repo.AddOrUpdateMeasuredValues(expectedMeasuredValues...) + err = repo.AddOrUpdateMeasuredValues(ctx, expectedMeasuredValues...) require.NoError(err) for i := range expectedMeasuredValues { @@ -501,15 +504,15 @@ func testBackend(t *testing.T, repo *repository.Repository) { switch expectedMeasuredValues[i].ValueType { case types.Humidity: - measuredValue, err = repo.GetHumidity(expectedMeasuredValues[i].ID) + measuredValue, err = repo.GetHumidityByID(ctx, expectedMeasuredValues[i].ID) require.NoError(err) require.NotNil(measuredValue) case types.Pressure: - measuredValue, err = repo.GetPressure(expectedMeasuredValues[i].ID) + measuredValue, err = repo.GetPressureByID(ctx, expectedMeasuredValues[i].ID) require.NoError(err) require.NotNil(measuredValue) case types.Temperature: - measuredValue, err = repo.GetTemperature(expectedMeasuredValues[i].ID) + measuredValue, err = repo.GetTemperatureByID(ctx, expectedMeasuredValues[i].ID) require.NoError(err) require.NotNil(measuredValue) } diff --git a/pkg/repository/db/sqlite3/ddl/1_table_devices.down.sql b/pkg/repository/sqlite3/ddl/1_table_devices.down.sql similarity index 100% rename from pkg/repository/db/sqlite3/ddl/1_table_devices.down.sql rename to pkg/repository/sqlite3/ddl/1_table_devices.down.sql diff --git a/pkg/repository/db/sqlite3/ddl/1_table_devices.up.sql b/pkg/repository/sqlite3/ddl/1_table_devices.up.sql similarity index 100% rename from pkg/repository/db/sqlite3/ddl/1_table_devices.up.sql rename to pkg/repository/sqlite3/ddl/1_table_devices.up.sql diff --git a/pkg/repository/db/sqlite3/ddl/2_table_sensors.down.sql b/pkg/repository/sqlite3/ddl/2_table_sensors.down.sql similarity index 100% rename from pkg/repository/db/sqlite3/ddl/2_table_sensors.down.sql rename to pkg/repository/sqlite3/ddl/2_table_sensors.down.sql diff --git a/pkg/repository/db/sqlite3/ddl/2_table_sensors.up.sql b/pkg/repository/sqlite3/ddl/2_table_sensors.up.sql similarity index 100% rename from pkg/repository/db/sqlite3/ddl/2_table_sensors.up.sql rename to pkg/repository/sqlite3/ddl/2_table_sensors.up.sql diff --git a/pkg/repository/db/sqlite3/ddl/3_table_humidities.down.sql b/pkg/repository/sqlite3/ddl/3_table_humidities.down.sql similarity index 100% rename from pkg/repository/db/sqlite3/ddl/3_table_humidities.down.sql rename to pkg/repository/sqlite3/ddl/3_table_humidities.down.sql diff --git a/pkg/repository/db/sqlite3/ddl/3_table_humidities.up.sql b/pkg/repository/sqlite3/ddl/3_table_humidities.up.sql similarity index 100% rename from pkg/repository/db/sqlite3/ddl/3_table_humidities.up.sql rename to pkg/repository/sqlite3/ddl/3_table_humidities.up.sql diff --git a/pkg/repository/db/sqlite3/ddl/4_table_pressures.down.sql b/pkg/repository/sqlite3/ddl/4_table_pressures.down.sql similarity index 100% rename from pkg/repository/db/sqlite3/ddl/4_table_pressures.down.sql rename to pkg/repository/sqlite3/ddl/4_table_pressures.down.sql diff --git a/pkg/repository/db/sqlite3/ddl/4_table_pressures.up.sql b/pkg/repository/sqlite3/ddl/4_table_pressures.up.sql similarity index 100% rename from pkg/repository/db/sqlite3/ddl/4_table_pressures.up.sql rename to pkg/repository/sqlite3/ddl/4_table_pressures.up.sql diff --git a/pkg/repository/db/sqlite3/ddl/5_table_temperatures.down.sql b/pkg/repository/sqlite3/ddl/5_table_temperatures.down.sql similarity index 100% rename from pkg/repository/db/sqlite3/ddl/5_table_temperatures.down.sql rename to pkg/repository/sqlite3/ddl/5_table_temperatures.down.sql diff --git a/pkg/repository/db/sqlite3/ddl/5_table_temperatures.up.sql b/pkg/repository/sqlite3/ddl/5_table_temperatures.up.sql similarity index 100% rename from pkg/repository/db/sqlite3/ddl/5_table_temperatures.up.sql rename to pkg/repository/sqlite3/ddl/5_table_temperatures.up.sql diff --git a/pkg/repository/db/sqlite3/dml/deleteDevice.sql b/pkg/repository/sqlite3/dml/deleteDeviceByID.sql similarity index 100% rename from pkg/repository/db/sqlite3/dml/deleteDevice.sql rename to pkg/repository/sqlite3/dml/deleteDeviceByID.sql diff --git a/pkg/repository/sqlite3/dml/deleteDeviceByName.sql b/pkg/repository/sqlite3/dml/deleteDeviceByName.sql new file mode 100644 index 0000000..7b5ac8b --- /dev/null +++ b/pkg/repository/sqlite3/dml/deleteDeviceByName.sql @@ -0,0 +1,2 @@ +DELETE FROM devices +WHERE device_name = $1; \ No newline at end of file diff --git a/pkg/repository/db/sqlite3/dml/deleteSensor.sql b/pkg/repository/sqlite3/dml/deleteSensorByID.sql similarity index 100% rename from pkg/repository/db/sqlite3/dml/deleteSensor.sql rename to pkg/repository/sqlite3/dml/deleteSensorByID.sql diff --git a/pkg/repository/sqlite3/dml/deleteSensorByName.sql b/pkg/repository/sqlite3/dml/deleteSensorByName.sql new file mode 100644 index 0000000..b6a7d6f --- /dev/null +++ b/pkg/repository/sqlite3/dml/deleteSensorByName.sql @@ -0,0 +1,2 @@ +DELETE FROM sensors +WHERE sensor_name = $1; \ No newline at end of file diff --git a/pkg/repository/db/sqlite3/dml/insertDevice.sql b/pkg/repository/sqlite3/dml/insertDevice.sql similarity index 100% rename from pkg/repository/db/sqlite3/dml/insertDevice.sql rename to pkg/repository/sqlite3/dml/insertDevice.sql diff --git a/pkg/repository/db/sqlite3/dml/insertHumidity.sql b/pkg/repository/sqlite3/dml/insertHumidity.sql similarity index 100% rename from pkg/repository/db/sqlite3/dml/insertHumidity.sql rename to pkg/repository/sqlite3/dml/insertHumidity.sql diff --git a/pkg/repository/db/sqlite3/dml/insertOrUpdateDevice.sql b/pkg/repository/sqlite3/dml/insertOrUpdateDevice.sql similarity index 100% rename from pkg/repository/db/sqlite3/dml/insertOrUpdateDevice.sql rename to pkg/repository/sqlite3/dml/insertOrUpdateDevice.sql diff --git a/pkg/repository/db/sqlite3/dml/insertOrUpdateHumidity.sql b/pkg/repository/sqlite3/dml/insertOrUpdateHumidity.sql similarity index 100% rename from pkg/repository/db/sqlite3/dml/insertOrUpdateHumidity.sql rename to pkg/repository/sqlite3/dml/insertOrUpdateHumidity.sql diff --git a/pkg/repository/db/sqlite3/dml/insertOrUpdatePressure.sql b/pkg/repository/sqlite3/dml/insertOrUpdatePressure.sql similarity index 100% rename from pkg/repository/db/sqlite3/dml/insertOrUpdatePressure.sql rename to pkg/repository/sqlite3/dml/insertOrUpdatePressure.sql diff --git a/pkg/repository/db/sqlite3/dml/insertOrUpdateSensor.sql b/pkg/repository/sqlite3/dml/insertOrUpdateSensor.sql similarity index 100% rename from pkg/repository/db/sqlite3/dml/insertOrUpdateSensor.sql rename to pkg/repository/sqlite3/dml/insertOrUpdateSensor.sql diff --git a/pkg/repository/db/sqlite3/dml/insertOrUpdateTemperature.sql b/pkg/repository/sqlite3/dml/insertOrUpdateTemperature.sql similarity index 100% rename from pkg/repository/db/sqlite3/dml/insertOrUpdateTemperature.sql rename to pkg/repository/sqlite3/dml/insertOrUpdateTemperature.sql diff --git a/pkg/repository/db/sqlite3/dml/insertPressure.sql b/pkg/repository/sqlite3/dml/insertPressure.sql similarity index 100% rename from pkg/repository/db/sqlite3/dml/insertPressure.sql rename to pkg/repository/sqlite3/dml/insertPressure.sql diff --git a/pkg/repository/db/sqlite3/dml/insertSensor.sql b/pkg/repository/sqlite3/dml/insertSensor.sql similarity index 100% rename from pkg/repository/db/sqlite3/dml/insertSensor.sql rename to pkg/repository/sqlite3/dml/insertSensor.sql diff --git a/pkg/repository/db/sqlite3/dml/insertTemperature.sql b/pkg/repository/sqlite3/dml/insertTemperature.sql similarity index 100% rename from pkg/repository/db/sqlite3/dml/insertTemperature.sql rename to pkg/repository/sqlite3/dml/insertTemperature.sql diff --git a/pkg/repository/db/sqlite3/dml/selectDevice.sql b/pkg/repository/sqlite3/dml/selectDeviceByID.sql similarity index 100% rename from pkg/repository/db/sqlite3/dml/selectDevice.sql rename to pkg/repository/sqlite3/dml/selectDeviceByID.sql diff --git a/pkg/repository/sqlite3/dml/selectDeviceByName.sql b/pkg/repository/sqlite3/dml/selectDeviceByName.sql new file mode 100644 index 0000000..2000d5d --- /dev/null +++ b/pkg/repository/sqlite3/dml/selectDeviceByName.sql @@ -0,0 +1,9 @@ +SELECT + device_id, + device_name, + device_location, + creation_date, + update_date +FROM + devices +WHERE device_name = $1; \ No newline at end of file diff --git a/pkg/repository/db/sqlite3/dml/selectDevices.sql b/pkg/repository/sqlite3/dml/selectDevices.sql similarity index 100% rename from pkg/repository/db/sqlite3/dml/selectDevices.sql rename to pkg/repository/sqlite3/dml/selectDevices.sql diff --git a/pkg/repository/db/sqlite3/dml/selectHumidities.sql b/pkg/repository/sqlite3/dml/selectHumidities.sql similarity index 100% rename from pkg/repository/db/sqlite3/dml/selectHumidities.sql rename to pkg/repository/sqlite3/dml/selectHumidities.sql diff --git a/pkg/repository/db/sqlite3/dml/selectHumidity.sql b/pkg/repository/sqlite3/dml/selectHumidityByID.sql similarity index 100% rename from pkg/repository/db/sqlite3/dml/selectHumidity.sql rename to pkg/repository/sqlite3/dml/selectHumidityByID.sql diff --git a/pkg/repository/db/sqlite3/dml/selectPressure.sql b/pkg/repository/sqlite3/dml/selectPressureByID.sql similarity index 100% rename from pkg/repository/db/sqlite3/dml/selectPressure.sql rename to pkg/repository/sqlite3/dml/selectPressureByID.sql diff --git a/pkg/repository/db/sqlite3/dml/selectPressures.sql b/pkg/repository/sqlite3/dml/selectPressures.sql similarity index 100% rename from pkg/repository/db/sqlite3/dml/selectPressures.sql rename to pkg/repository/sqlite3/dml/selectPressures.sql diff --git a/pkg/repository/db/sqlite3/dml/selectSensor.sql b/pkg/repository/sqlite3/dml/selectSensorByID.sql similarity index 100% rename from pkg/repository/db/sqlite3/dml/selectSensor.sql rename to pkg/repository/sqlite3/dml/selectSensorByID.sql diff --git a/pkg/repository/db/sqlite3/dml/selectSensors.sql b/pkg/repository/sqlite3/dml/selectSensors.sql similarity index 100% rename from pkg/repository/db/sqlite3/dml/selectSensors.sql rename to pkg/repository/sqlite3/dml/selectSensors.sql diff --git a/pkg/repository/sqlite3/dml/selectSensorsByDeviceID.sql b/pkg/repository/sqlite3/dml/selectSensorsByDeviceID.sql new file mode 100644 index 0000000..2638cff --- /dev/null +++ b/pkg/repository/sqlite3/dml/selectSensorsByDeviceID.sql @@ -0,0 +1,18 @@ +SELECT + sensor_id, + sensor_name, + sensor_location, + wire_id, + i2c_bus, + i2c_address, + gpio_number, + sensor_model, + sensor_enabled, + tick_duration, + device_id, + creation_date, + update_date +FROM + sensors +WHERE + device_id = $1; \ No newline at end of file diff --git a/pkg/repository/sqlite3/dml/selectSensorsByModel.sql b/pkg/repository/sqlite3/dml/selectSensorsByModel.sql new file mode 100644 index 0000000..23b83ff --- /dev/null +++ b/pkg/repository/sqlite3/dml/selectSensorsByModel.sql @@ -0,0 +1,18 @@ +SELECT + sensor_id, + sensor_name, + sensor_location, + wire_id, + i2c_bus, + i2c_address, + gpio_number, + sensor_model, + sensor_enabled, + tick_duration, + device_id, + creation_date, + update_date +FROM + sensors +WHERE + sensor_model = $1; \ No newline at end of file diff --git a/pkg/repository/sqlite3/dml/selectSensorsByName.sql b/pkg/repository/sqlite3/dml/selectSensorsByName.sql new file mode 100644 index 0000000..3263cb9 --- /dev/null +++ b/pkg/repository/sqlite3/dml/selectSensorsByName.sql @@ -0,0 +1,18 @@ +SELECT + sensor_id, + sensor_name, + sensor_location, + wire_id, + i2c_bus, + i2c_address, + gpio_number, + sensor_model, + sensor_enabled, + tick_duration, + device_id, + creation_date, + update_date +FROM + sensors +WHERE + sensor_name = $1; \ No newline at end of file diff --git a/pkg/repository/db/sqlite3/dml/selectTemperature.sql b/pkg/repository/sqlite3/dml/selectTemperatureByID.sql similarity index 100% rename from pkg/repository/db/sqlite3/dml/selectTemperature.sql rename to pkg/repository/sqlite3/dml/selectTemperatureByID.sql diff --git a/pkg/repository/db/sqlite3/dml/selectTemperatures.sql b/pkg/repository/sqlite3/dml/selectTemperatures.sql similarity index 100% rename from pkg/repository/db/sqlite3/dml/selectTemperatures.sql rename to pkg/repository/sqlite3/dml/selectTemperatures.sql diff --git a/pkg/repository/db/sqlite3/dml/updateDevice.sql b/pkg/repository/sqlite3/dml/updateDevice.sql similarity index 100% rename from pkg/repository/db/sqlite3/dml/updateDevice.sql rename to pkg/repository/sqlite3/dml/updateDevice.sql diff --git a/pkg/repository/db/sqlite3/dml/updateSensor.sql b/pkg/repository/sqlite3/dml/updateSensor.sql similarity index 100% rename from pkg/repository/db/sqlite3/dml/updateSensor.sql rename to pkg/repository/sqlite3/dml/updateSensor.sql diff --git a/pkg/repository/sqlite3/sqlite.go b/pkg/repository/sqlite3/sqlite.go new file mode 100644 index 0000000..1392b1f --- /dev/null +++ b/pkg/repository/sqlite3/sqlite.go @@ -0,0 +1,1256 @@ +package sqlite3 + +import ( + "context" + "database/sql" + "embed" + "errors" + "fmt" + "net/url" + "os" + "path/filepath" + "sync" + + "git.cryptic.systems/volker.raschek/flucky/pkg/types" + "git.cryptic.systems/volker.raschek/go-logger" + "github.com/golang-migrate/migrate/v4" + "github.com/johejo/golang-migrate-extra/source/iofs" +) + +//go:embed ddl/*.sql +var sqlite3DDLAssets embed.FS + +//go:embed dml/*.sql +var sqlite3DMLAssets embed.FS + +const ( + sqlite3DDLAssetPath = "ddl" + sqlite3DMLAssetPath = "dml" + + deleteDeviceByIDSQLFile = sqlite3DMLAssetPath + "/deleteDeviceByID.sql" + deleteDeviceByNameSQLFile = sqlite3DMLAssetPath + "/deleteDeviceByName.sql" + + deleteSensorByIDSQLFile = sqlite3DMLAssetPath + "/deleteSensorByID.sql" + deleteSensorByNameSQLFile = sqlite3DMLAssetPath + "/deleteSensorByName.sql" + + insertDeviceSQLFile = sqlite3DMLAssetPath + "/insertDevice.sql" + insertSensorSQLFile = sqlite3DMLAssetPath + "/insertSensor.sql" + insertHumiditySQLFile = sqlite3DMLAssetPath + "/insertHumidity.sql" + insertPressureSQLFile = sqlite3DMLAssetPath + "/insertPressure.sql" + insertTemperatureSQLFile = sqlite3DMLAssetPath + "/insertTemperature.sql" + + insertOrUpdateDeviceSQLFile = sqlite3DMLAssetPath + "/insertOrUpdateDevice.sql" + insertOrUpdateSensorSQLFile = sqlite3DMLAssetPath + "/insertOrUpdateSensor.sql" + insertOrUpdateHumiditySQLFile = sqlite3DMLAssetPath + "/insertOrUpdateHumidity.sql" + insertOrUpdatePressureSQLFile = sqlite3DMLAssetPath + "/insertOrUpdatePressure.sql" + insertOrUpdateTemperatureSQLFile = sqlite3DMLAssetPath + "/insertOrUpdateTemperature.sql" + + selectDeviceByIDSQLFile = sqlite3DMLAssetPath + "/selectDeviceByID.sql" + selectDeviceByNameSQLFile = sqlite3DMLAssetPath + "/selectDeviceByName.sql" + selectDevicesSQLFile = sqlite3DMLAssetPath + "/selectDevices.sql" + + selectSensorByIDSQLFile = sqlite3DMLAssetPath + "/selectSensorByID.sql" + selectSensorsSQLFile = sqlite3DMLAssetPath + "/selectSensors.sql" + selectSensorsByDeviceIDSQLFile = sqlite3DMLAssetPath + "/selectSensorsByDeviceID.sql" + selectSensorsByModelSQLFile = sqlite3DMLAssetPath + "/selectSensorsByModel.sql" + selectSensorsByNameSQLFile = sqlite3DMLAssetPath + "/selectSensorsByName.sql" + + selectHumidityByIDSQLFile = sqlite3DMLAssetPath + "/selectHumidityByID.sql" + selectHumiditiesSQLFile = sqlite3DMLAssetPath + "/selectHumidities.sql" + + selectPressureByIDSQLFile = sqlite3DMLAssetPath + "/selectPressureByID.sql" + selectPressuresSQLFile = sqlite3DMLAssetPath + "/selectPressures.sql" + + selectTemperatureByIDSQLFile = sqlite3DMLAssetPath + "/selectTemperatureByID.sql" + selectTemperaturesSQLFile = sqlite3DMLAssetPath + "/selectTemperatures.sql" + + updateDeviceSQLFile = sqlite3DMLAssetPath + "/updateDevice.sql" + updateSensorSQLFile = sqlite3DMLAssetPath + "/updateSensor.sql" +) + +type Opts struct { + DatabaseURL *url.URL + Logger logger.Logger +} + +func (o *Opts) Validate() error { + for k, v := range map[string]interface{}{ + "DatabaseURL": o.DatabaseURL, + "Logger": o.Logger, + } { + if v == nil { + return fmt.Errorf("%v does not be empty or nil", k) + } + } + + if o.DatabaseURL.Scheme != "sqlite3" { + return fmt.Errorf("Unsupported database scheme %v for database backend sqlite3", o.DatabaseURL.Scheme) + } + + return nil +} + +func New(opts Opts) (*SQLite, error) { + if err := opts.Validate(); err != nil { + return nil, err + } + + // Create directory if not exist + if _, err := os.Stat(filepath.Dir(opts.DatabaseURL.Path)); os.IsNotExist(err) { + err := os.MkdirAll(filepath.Dir(opts.DatabaseURL.Path), 0755) + if err != nil { + return nil, err + } + } + + // enable foreign keys + values := opts.DatabaseURL.Query() + values.Set("_foreign_keys", "on") + + customRawURL := fmt.Sprintf("file://%v?%v", opts.DatabaseURL.Path, values.Encode()) + + sqlDBO, err := sql.Open(opts.DatabaseURL.Scheme, customRawURL) + if err != nil { + return nil, err + } + + return &SQLite{ + databaseURL: opts.DatabaseURL, + dbo: sqlDBO, + flogger: opts.Logger, + mutex: new(sync.Mutex), + }, nil +} + +// SQLite implementation +type SQLite struct { + databaseURL *url.URL + dbo *sql.DB + flogger logger.Logger + mutex *sync.Mutex +} + +// Close closes the database and prevents new queries from starting. Close then +// waits for all queries that have started processing on the server to finish. +func (sqlite *SQLite) Close() error { + sqlite.mutex.Lock() + defer sqlite.mutex.Unlock() + return sqlite.dbo.Close() +} + +// RemoveDevices from the database +func (sqlite *SQLite) RemoveDevicesByIDs(ctx context.Context, deviceIDs ...string) error { + sqlite.mutex.Lock() + defer sqlite.mutex.Unlock() + + f, err := sqlite3DMLAssets.ReadFile(deleteDeviceByIDSQLFile) + if err != nil { + return fmt.Errorf("Failed to read file %v: %w", deleteDeviceByIDSQLFile, err) + } + + tx, err := sqlite.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: false}) + if err != nil { + return fmt.Errorf("Failed to begin new transaction: %v", err) + } + defer tx.Rollback() + + stmt, err := tx.Prepare(string(f)) + 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 { + return err + } + } + + return tx.Commit() +} + +// RemoveDevices from the database +func (sqlite *SQLite) RemoveDevicesByNames(ctx context.Context, names ...string) error { + sqlite.mutex.Lock() + defer sqlite.mutex.Unlock() + + f, err := sqlite3DMLAssets.ReadFile(deleteDeviceByNameSQLFile) + if err != nil { + return fmt.Errorf("Failed to read file %v: %w", deleteDeviceByNameSQLFile, err) + } + + tx, err := sqlite.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: false}) + if err != nil { + return fmt.Errorf("Failed to begin new transaction: %v", err) + } + defer tx.Rollback() + + stmt, err := tx.Prepare(string(f)) + if err != nil { + return fmt.Errorf("Failed to prepare statement: %v", err) + } + defer stmt.Close() + + for _, deviceID := range names { + _, err = stmt.Exec(deviceID) + if err != nil { + return err + } + } + + return tx.Commit() +} + +// RemoveSensors from the database +func (sqlite *SQLite) RemoveSensorsByIDs(ctx context.Context, sensorIDs ...string) error { + sqlite.mutex.Lock() + defer sqlite.mutex.Unlock() + + f, err := sqlite3DMLAssets.ReadFile(deleteSensorByIDSQLFile) + if err != nil { + return fmt.Errorf("Failed to read file %v: %w", deleteSensorByIDSQLFile, err) + } + + tx, err := sqlite.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: false}) + if err != nil { + return fmt.Errorf("Failed to begin new transaction: %v", err) + } + defer tx.Rollback() + + stmt, err := tx.Prepare(string(f)) + 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 { + return err + } + } + + return tx.Commit() +} + +// RemoveSensors from the database +func (sqlite *SQLite) RemoveSensorsByNames(ctx context.Context, names ...string) error { + sqlite.mutex.Lock() + defer sqlite.mutex.Unlock() + + f, err := sqlite3DMLAssets.ReadFile(deleteSensorByNameSQLFile) + if err != nil { + return fmt.Errorf("Failed to read file %v: %w", deleteSensorByNameSQLFile, err) + } + + tx, err := sqlite.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: false}) + if err != nil { + return fmt.Errorf("Failed to begin new transaction: %v", err) + } + defer tx.Rollback() + + stmt, err := tx.Prepare(string(f)) + if err != nil { + return fmt.Errorf("Failed to prepare statement: %v", err) + } + defer stmt.Close() + + for _, sensorID := range names { + _, err = stmt.Exec(sensorID) + if err != nil { + return err + } + } + + return tx.Commit() +} + +// AddDevices into the database +func (sqlite *SQLite) AddDevices(ctx context.Context, devices ...*types.Device) error { + sqlite.mutex.Lock() + defer sqlite.mutex.Unlock() + + f, err := sqlite3DMLAssets.ReadFile(insertDeviceSQLFile) + if err != nil { + return fmt.Errorf("Failed to read file %v: %w", insertDeviceSQLFile, err) + } + + tx, err := sqlite.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: false}) + if err != nil { + return fmt.Errorf("Failed to begin new transaction: %v", err) + } + defer tx.Rollback() + + stmt, err := tx.Prepare(string(f)) + if err != nil { + return fmt.Errorf("Failed to prepare statement: %v", err) + } + defer stmt.Close() + + for _, device := range devices { + _, err = stmt.Exec(&device.ID, &device.Name, &device.Location, &device.CreationDate, &device.UpdateDate) + if err != nil { + return fmt.Errorf("Failed to execute statement: %v", err) + } + } + + return tx.Commit() +} + +// AddMeasuredValues into the database +func (sqlite *SQLite) AddMeasuredValues(ctx context.Context, measuredValues ...*types.MeasuredValue) error { + sqlite.mutex.Lock() + defer sqlite.mutex.Unlock() + + splittedMeasuredValues := make(map[types.MeasuredValueType][]*types.MeasuredValue, 0) + + for _, measuredValue := range measuredValues { + if _, ok := splittedMeasuredValues[measuredValue.ValueType]; !ok { + splittedMeasuredValues[measuredValue.ValueType] = make([]*types.MeasuredValue, 0) + } + splittedMeasuredValues[measuredValue.ValueType] = append(splittedMeasuredValues[measuredValue.ValueType], measuredValue) + } + + tx, err := sqlite.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: false}) + if err != nil { + return fmt.Errorf("Failed to begin new transaction: %v", err) + } + defer tx.Rollback() + + // General insert function + insert := func(tx *sql.Tx, queryFile string, measuredValues []*types.MeasuredValue) error { + f, err := sqlite3DMLAssets.ReadFile(queryFile) + if err != nil { + return fmt.Errorf("Failed to read file %v: %w", deleteDeviceByIDSQLFile, err) + } + + stmt, err := tx.Prepare(string(f)) + if err != nil { + return fmt.Errorf("Failed to prepare statement: %v", err) + } + defer stmt.Close() + + for _, measuredValue := range measuredValues { + _, err := stmt.Exec( + &measuredValue.ID, + &measuredValue.Value, + &measuredValue.Date, + &measuredValue.SensorID, + &measuredValue.CreationDate, + &measuredValue.UpdateDate, + ) + + if err != nil { + return fmt.Errorf("Failed to execute statement: %v", err) + } + } + + return nil + } + + for measuredValueType, measuredValues := range splittedMeasuredValues { + var queryFile string + + switch measuredValueType { + case types.Humidity: + queryFile = insertHumiditySQLFile + case types.Pressure: + queryFile = insertPressureSQLFile + case types.Temperature: + queryFile = insertTemperatureSQLFile + default: + return fmt.Errorf("Measured value type %v not supported", measuredValueType) + } + + err := insert(tx, queryFile, measuredValues) + if err != nil { + return err + } + } + + return tx.Commit() +} + +// AddOrUpdateMeasuredValues into the database +func (sqlite *SQLite) AddOrUpdateMeasuredValues(ctx context.Context, measuredValues ...*types.MeasuredValue) error { + sqlite.mutex.Lock() + defer sqlite.mutex.Unlock() + + splittedMeasuredValues := make(map[types.MeasuredValueType][]*types.MeasuredValue, 0) + + for _, measuredValue := range measuredValues { + if _, ok := splittedMeasuredValues[measuredValue.ValueType]; !ok { + splittedMeasuredValues[measuredValue.ValueType] = make([]*types.MeasuredValue, 0) + } + splittedMeasuredValues[measuredValue.ValueType] = append(splittedMeasuredValues[measuredValue.ValueType], measuredValue) + } + + tx, err := sqlite.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: false}) + if err != nil { + return fmt.Errorf("Failed to begin new transaction: %v", err) + } + defer tx.Rollback() + + // General insert function + insert := func(tx *sql.Tx, queryFile string, measuredValues []*types.MeasuredValue) error { + f, err := sqlite3DMLAssets.ReadFile(queryFile) + if err != nil { + return fmt.Errorf("Failed to read file %v: %w", queryFile, err) + } + + stmt, err := tx.Prepare(string(f)) + if err != nil { + return fmt.Errorf("Failed to prepare statement: %v", err) + } + defer stmt.Close() + + for _, measuredValue := range measuredValues { + _, err := stmt.Exec( + &measuredValue.ID, + &measuredValue.Value, + &measuredValue.Date, + &measuredValue.SensorID, + &measuredValue.CreationDate, + &measuredValue.UpdateDate, + ) + + if err != nil { + return fmt.Errorf("Failed to execute statement: %v", err) + } + } + + return nil + } + + for measuredValueType, measuredValues := range splittedMeasuredValues { + var queryFile string + + switch measuredValueType { + case types.Humidity: + queryFile = insertOrUpdateHumiditySQLFile + case types.Pressure: + queryFile = insertOrUpdatePressureSQLFile + case types.Temperature: + queryFile = insertOrUpdateTemperatureSQLFile + default: + return fmt.Errorf("Measured value type %v not supported", measuredValueType) + } + + err := insert(tx, queryFile, measuredValues) + if err != nil { + return err + } + } + + return tx.Commit() +} + +// AddOrUpdateDevices into the database +func (sqlite *SQLite) AddOrUpdateDevices(ctx context.Context, devices ...*types.Device) error { + sqlite.mutex.Lock() + defer sqlite.mutex.Unlock() + + f, err := sqlite3DMLAssets.ReadFile(insertOrUpdateDeviceSQLFile) + if err != nil { + return fmt.Errorf("Failed to read file %v: %w", insertOrUpdateDeviceSQLFile, err) + } + + tx, err := sqlite.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: false}) + if err != nil { + return fmt.Errorf("Failed to begin new transaction: %v", err) + } + defer tx.Rollback() + + stmt, err := tx.Prepare(string(f)) + if err != nil { + return fmt.Errorf("Failed to prepare statement: %v", err) + } + defer stmt.Close() + + for _, device := range devices { + _, err = stmt.Exec(&device.ID, &device.Name, &device.Location, &device.CreationDate, &device.UpdateDate) + if err != nil { + return fmt.Errorf("Failed to execute statement: %v", err) + } + } + + return tx.Commit() +} + +// AddOrUpdateSensors into the database +func (sqlite *SQLite) AddOrUpdateSensors(ctx context.Context, sensors ...*types.Sensor) error { + sqlite.mutex.Lock() + defer sqlite.mutex.Unlock() + + f, err := sqlite3DMLAssets.ReadFile(insertOrUpdateSensorSQLFile) + if err != nil { + return fmt.Errorf("Failed to read file %v: %w", insertOrUpdateSensorSQLFile, err) + } + + tx, err := sqlite.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: false}) + if err != nil { + return fmt.Errorf("Failed to begin new transaction: %v", err) + } + defer tx.Rollback() + + stmt, err := tx.Prepare(string(f)) + if err != nil { + return fmt.Errorf("Failed to prepare statement: %v", err) + } + defer stmt.Close() + + for _, sensor := range sensors { + _, err = stmt.Exec( + &sensor.ID, + &sensor.Name, + &sensor.Location, + &sensor.WireID, + &sensor.I2CBus, + &sensor.I2CAddress, + &sensor.GPIONumber, + &sensor.Model, + &sensor.Enabled, + &sensor.TickDuration, + &sensor.DeviceID, + &sensor.CreationDate, + &sensor.UpdateDate, + ) + if err != nil { + return fmt.Errorf("Failed to execute statement: %v", err) + } + } + + return tx.Commit() +} + +// AddSensors into the database +func (sqlite *SQLite) AddSensors(ctx context.Context, sensors ...*types.Sensor) error { + sqlite.mutex.Lock() + defer sqlite.mutex.Unlock() + + f, err := sqlite3DMLAssets.ReadFile(insertSensorSQLFile) + if err != nil { + return fmt.Errorf("Failed to read file %v: %w", insertSensorSQLFile, err) + } + + tx, err := sqlite.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: false}) + if err != nil { + return fmt.Errorf("Failed to begin new transaction: %v", err) + } + defer tx.Rollback() + + stmt, err := tx.Prepare(string(f)) + if err != nil { + return fmt.Errorf("Failed to prepare statement: %v", err) + } + defer stmt.Close() + + for _, sensor := range sensors { + _, err = stmt.Exec( + &sensor.ID, + &sensor.Name, + &sensor.Location, + &sensor.WireID, + &sensor.I2CBus, + &sensor.I2CAddress, + &sensor.GPIONumber, + &sensor.Model, + &sensor.Enabled, + &sensor.TickDuration, + &sensor.DeviceID, + &sensor.CreationDate, + &sensor.UpdateDate, + ) + if err != nil { + return fmt.Errorf("Failed to execute statement: %v", err) + } + } + + return tx.Commit() +} + +// Migrate creates all required tables if not exist +func (sqlite *SQLite) Migrate(ctx context.Context) error { + sqlite.mutex.Lock() + defer sqlite.mutex.Unlock() + + sourceDriver, err := iofs.New(sqlite3DDLAssets, sqlite3DDLAssetPath) + if err != nil { + return err + } + + m, err := migrate.NewWithSourceInstance("iofs", sourceDriver, sqlite.databaseURL.String()) + if err != nil { + return err + } + + err = m.Up() + switch { + case errors.Is(err, migrate.ErrNoChange): + return nil + default: + return err + } +} + +// GetDevice from database +func (sqlite *SQLite) GetDeviceByID(ctx context.Context, id string) (*types.Device, error) { + sqlite.mutex.Lock() + defer sqlite.mutex.Unlock() + + f, err := sqlite3DMLAssets.ReadFile(selectDeviceByIDSQLFile) + if err != nil { + return nil, fmt.Errorf("Failed to read file %v: %w", selectDeviceByIDSQLFile, err) + } + + tx, err := sqlite.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: true}) + if err != nil { + return nil, fmt.Errorf("Failed to begin new transaction: %v", err) + } + defer tx.Rollback() + + devices, err := sqlite.selectDevices(tx, string(f), id) + if err != nil { + return nil, err + } + + err = tx.Commit() + if err != nil { + return nil, err + } + + if len(devices) == 0 { + return nil, nil + } + + return devices[0], nil +} + +// GetDevice from database +func (sqlite *SQLite) GetDeviceByName(ctx context.Context, name string) (*types.Device, error) { + sqlite.mutex.Lock() + defer sqlite.mutex.Unlock() + + f, err := sqlite3DMLAssets.ReadFile(selectDeviceByNameSQLFile) + if err != nil { + return nil, fmt.Errorf("Failed to read file %v: %w", selectDeviceByNameSQLFile, err) + } + + tx, err := sqlite.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: true}) + if err != nil { + return nil, fmt.Errorf("Failed to begin new transaction: %v", err) + } + defer tx.Rollback() + + devices, err := sqlite.selectDevices(tx, string(f), name) + if err != nil { + return nil, err + } + + err = tx.Commit() + if err != nil { + return nil, err + } + + if len(devices) == 0 { + return nil, nil + } + + return devices[0], nil +} + +// GetDevices from the database +func (sqlite *SQLite) GetDevices(ctx context.Context) ([]*types.Device, error) { + sqlite.mutex.Lock() + defer sqlite.mutex.Unlock() + + f, err := sqlite3DMLAssets.ReadFile(selectDevicesSQLFile) + if err != nil { + return nil, fmt.Errorf("Failed to read file %v: %w", selectDevicesSQLFile, err) + } + + tx, err := sqlite.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: true}) + if err != nil { + return nil, fmt.Errorf("Failed to begin new transaction: %v", err) + } + defer tx.Rollback() + + devices, err := sqlite.selectDevices(tx, string(f)) + if err != nil { + return nil, err + } + + err = tx.Commit() + if err != nil { + return nil, fmt.Errorf("Failed to commit transaction: %v", err) + } + + return devices, nil +} + +func (sqlite *SQLite) selectDevices(tx *sql.Tx, query string, args ...interface{}) ([]*types.Device, error) { + stmt, err := tx.Prepare(query) + if err != nil { + return nil, fmt.Errorf("Failed to prepare statement: %v", err) + } + defer stmt.Close() + + rows, err := stmt.Query(args...) + if err != nil { + return nil, fmt.Errorf("Failed to query statement: %v", err) + } + defer rows.Close() + + devices := make([]*types.Device, 0) + for rows.Next() { + device := new(types.Device) + err = rows.Scan( + &device.ID, + &device.Name, + &device.Location, + &device.CreationDate, + &device.UpdateDate, + ) + if err != nil { + return nil, fmt.Errorf("Failed to scan row: %v", err) + } + devices = append(devices, device) + } + + return devices, nil +} + +// GetHumidity returns humidity from the database +func (sqlite *SQLite) GetHumidityByID(ctx context.Context, id string) (*types.MeasuredValue, error) { + sqlite.mutex.Lock() + defer sqlite.mutex.Unlock() + + f, err := sqlite3DMLAssets.ReadFile(selectHumidityByIDSQLFile) + if err != nil { + return nil, fmt.Errorf("Failed to read file %v: %w", selectHumidityByIDSQLFile, err) + } + + tx, err := sqlite.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: true}) + if err != nil { + return nil, err + } + defer tx.Rollback() + + measuredValues, err := sqlite.selectMeasuredValue(tx, string(f), id) + if err != nil { + return nil, err + } + + err = tx.Commit() + if err != nil { + return nil, err + } + + if measuredValues == nil { + return nil, nil + } + + for _, measuredValue := range measuredValues { + measuredValue.ValueType = types.Humidity + } + + return measuredValues[0], nil +} + +// GetHumidities returns humidities from the database +func (sqlite *SQLite) GetHumidities(ctx context.Context) ([]*types.MeasuredValue, error) { + sqlite.mutex.Lock() + defer sqlite.mutex.Unlock() + + f, err := sqlite3DMLAssets.ReadFile(selectHumiditiesSQLFile) + if err != nil { + return nil, fmt.Errorf("Failed to read file %v: %w", selectHumiditiesSQLFile, err) + } + + tx, err := sqlite.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: true}) + if err != nil { + return nil, err + } + defer tx.Rollback() + + measuredValues, err := sqlite.selectMeasuredValue(tx, string(f)) + if err != nil { + return nil, err + } + + err = tx.Commit() + if err != nil { + return nil, err + } + + for _, measuredValue := range measuredValues { + measuredValue.ValueType = types.Humidity + } + + return measuredValues, nil +} + +func (sqlite *SQLite) selectMeasuredValue(tx *sql.Tx, query string, args ...interface{}) ([]*types.MeasuredValue, error) { + stmt, err := tx.Prepare(query) + if err != nil { + return nil, err + } + defer stmt.Close() + + rows, err := stmt.Query(args...) + if err != nil { + return nil, err + } + defer rows.Close() + + measuredValues := make([]*types.MeasuredValue, 0) + for rows.Next() { + measuredValue := new(types.MeasuredValue) + err := rows.Scan( + &measuredValue.ID, + &measuredValue.Value, + &measuredValue.Date, + &measuredValue.SensorID, + &measuredValue.CreationDate, + &measuredValue.UpdateDate, + ) + + if err != nil { + return nil, err + } + + measuredValues = append(measuredValues, measuredValue) + } + + return measuredValues, nil +} + +// GetPressure returns pressure from the database +func (sqlite *SQLite) GetPressureByID(ctx context.Context, id string) (*types.MeasuredValue, error) { + sqlite.mutex.Lock() + defer sqlite.mutex.Unlock() + + f, err := sqlite3DMLAssets.ReadFile(selectPressureByIDSQLFile) + if err != nil { + return nil, fmt.Errorf("Failed to read file %v: %w", selectPressureByIDSQLFile, err) + } + + tx, err := sqlite.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: true}) + if err != nil { + return nil, err + } + defer tx.Rollback() + + measuredValues, err := sqlite.selectMeasuredValue(tx, string(f), id) + if err != nil { + return nil, err + } + + err = tx.Commit() + if err != nil { + return nil, err + } + + if measuredValues == nil { + return nil, nil + } + + for _, measuredValue := range measuredValues { + measuredValue.ValueType = types.Pressure + } + + return measuredValues[0], nil +} + +// GetPressures returns pressure from the database +func (sqlite *SQLite) GetPressures(ctx context.Context) ([]*types.MeasuredValue, error) { + sqlite.mutex.Lock() + defer sqlite.mutex.Unlock() + + f, err := sqlite3DMLAssets.ReadFile(selectPressuresSQLFile) + if err != nil { + return nil, fmt.Errorf("Failed to read file %v: %w", deleteDeviceByIDSQLFile, err) + } + + tx, err := sqlite.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: true}) + if err != nil { + return nil, err + } + defer tx.Rollback() + + measuredValues, err := sqlite.selectMeasuredValue(tx, string(f)) + if err != nil { + return nil, err + } + + err = tx.Commit() + if err != nil { + return nil, err + } + + for _, measuredValue := range measuredValues { + measuredValue.ValueType = types.Pressure + } + + return measuredValues, nil +} + +// GetSensor from database +func (sqlite *SQLite) GetSensorByID(ctx context.Context, id string) (*types.Sensor, error) { + sqlite.mutex.Lock() + defer sqlite.mutex.Unlock() + + f, err := sqlite3DMLAssets.ReadFile(selectSensorByIDSQLFile) + if err != nil { + return nil, fmt.Errorf("Failed to read file %v: %w", selectSensorByIDSQLFile, err) + } + + tx, err := sqlite.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: true}) + if err != nil { + return nil, fmt.Errorf("Failed to begin new transaction: %v", err) + } + defer tx.Rollback() + + sensors, err := sqlite.selectSensors(tx, string(f), id) + if err != nil { + return nil, err + } + + err = tx.Commit() + if err != nil { + return nil, fmt.Errorf("Failed to commit transaction: %v", err) + } + + if len(sensors) == 0 { + return nil, nil + } + + return sensors[0], nil +} + +// GetSensors from the database +func (sqlite *SQLite) GetSensors(ctx context.Context) ([]*types.Sensor, error) { + sqlite.mutex.Lock() + defer sqlite.mutex.Unlock() + + f, err := sqlite3DMLAssets.ReadFile(selectSensorsSQLFile) + if err != nil { + return nil, fmt.Errorf("Failed to read file %v: %w", selectSensorsSQLFile, err) + } + + tx, err := sqlite.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: true}) + if err != nil { + return nil, fmt.Errorf("Failed to begin new transaction: %v", err) + } + defer tx.Rollback() + + sensors, err := sqlite.selectSensors(tx, string(f)) + 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 +} + +// GetSensorsByModels from the database +func (sqlite *SQLite) GetSensorsByDeviceIDs(ctx context.Context, deviceIDs ...string) ([]*types.Sensor, error) { + sqlite.mutex.Lock() + defer sqlite.mutex.Unlock() + + f, err := sqlite3DMLAssets.ReadFile(selectSensorsByDeviceIDSQLFile) + if err != nil { + return nil, fmt.Errorf("Failed to read file %v: %w", selectSensorsByDeviceIDSQLFile, err) + } + + tx, err := sqlite.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: true}) + if err != nil { + return nil, fmt.Errorf("Failed to begin new transaction: %v", err) + } + defer tx.Rollback() + + cachedSensors := make([]*types.Sensor, 0) + for i := range deviceIDs { + sensors, err := sqlite.selectSensors(tx, string(f), deviceIDs[i]) + if err != nil { + return nil, err + } + + cachedSensors = append(cachedSensors, sensors...) + } + + err = tx.Commit() + if err != nil { + return nil, fmt.Errorf("Failed to commit transaction: %v", err) + } + + return cachedSensors, nil +} + +// GetSensorsByModels from the database +func (sqlite *SQLite) GetSensorsByModels(ctx context.Context, sensorModels ...string) ([]*types.Sensor, error) { + sqlite.mutex.Lock() + defer sqlite.mutex.Unlock() + + f, err := sqlite3DMLAssets.ReadFile(selectSensorsByModelSQLFile) + if err != nil { + return nil, fmt.Errorf("Failed to read file %v: %w", selectSensorsByModelSQLFile, err) + } + + tx, err := sqlite.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: true}) + if err != nil { + return nil, fmt.Errorf("Failed to begin new transaction: %v", err) + } + defer tx.Rollback() + + cachedSensors := make([]*types.Sensor, 0) + for i := range sensorModels { + sensors, err := sqlite.selectSensors(tx, string(f), sensorModels[i]) + if err != nil { + return nil, err + } + + cachedSensors = append(cachedSensors, sensors...) + } + + err = tx.Commit() + if err != nil { + return nil, fmt.Errorf("Failed to commit transaction: %v", err) + } + + return cachedSensors, nil +} + +// GetSensorsByModels from the database +func (sqlite *SQLite) GetSensorsByNames(ctx context.Context, sensorNames ...string) ([]*types.Sensor, error) { + sqlite.mutex.Lock() + defer sqlite.mutex.Unlock() + + f, err := sqlite3DMLAssets.ReadFile(selectSensorsByNameSQLFile) + if err != nil { + return nil, fmt.Errorf("Failed to read file %v: %w", selectSensorsByNameSQLFile, err) + } + + tx, err := sqlite.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: true}) + if err != nil { + return nil, fmt.Errorf("Failed to begin new transaction: %v", err) + } + defer tx.Rollback() + + cachedSensors := make([]*types.Sensor, 0) + for i := range sensorNames { + sensors, err := sqlite.selectSensors(tx, string(f), sensorNames[i]) + if err != nil { + return nil, err + } + + cachedSensors = append(cachedSensors, sensors...) + } + + err = tx.Commit() + if err != nil { + return nil, fmt.Errorf("Failed to commit transaction: %v", err) + } + + return cachedSensors, nil +} + +func (sqlite *SQLite) selectSensors(tx *sql.Tx, query string, args ...interface{}) ([]*types.Sensor, error) { + stmt, err := tx.Prepare(query) + if err != nil { + return nil, fmt.Errorf("Failed to prepare statement: %v", err) + } + defer stmt.Close() + + rows, err := stmt.Query(args...) + if err != nil { + return nil, fmt.Errorf("Failed to query statement: %v", err) + } + defer rows.Close() + + sensors := make([]*types.Sensor, 0) + for rows.Next() { + sensor := new(types.Sensor) + err = rows.Scan( + &sensor.ID, + &sensor.Name, + &sensor.Location, + &sensor.WireID, + &sensor.I2CBus, + &sensor.I2CAddress, + &sensor.GPIONumber, + &sensor.Model, + &sensor.Enabled, + &sensor.TickDuration, + &sensor.DeviceID, + &sensor.CreationDate, + &sensor.UpdateDate, + ) + + if err != nil { + return nil, fmt.Errorf("Failed to scan row: %v", err) + } + sensors = append(sensors, sensor) + } + + return sensors, nil +} + +// GetTemperature returns temperatures from the database +func (sqlite *SQLite) GetTemperatureByID(ctx context.Context, id string) (*types.MeasuredValue, error) { + sqlite.mutex.Lock() + defer sqlite.mutex.Unlock() + + f, err := sqlite3DMLAssets.ReadFile(selectTemperatureByIDSQLFile) + if err != nil { + return nil, fmt.Errorf("Failed to read file %v: %w", selectTemperatureByIDSQLFile, err) + } + + tx, err := sqlite.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: true}) + if err != nil { + return nil, err + } + defer tx.Rollback() + + measuredValues, err := sqlite.selectMeasuredValue(tx, string(f), id) + if err != nil { + return nil, err + } + + err = tx.Commit() + if err != nil { + return nil, err + } + + if measuredValues == nil { + return nil, nil + } + + for _, measuredValue := range measuredValues { + measuredValue.ValueType = types.Temperature + } + + return measuredValues[0], nil +} + +// GetTemperatures returns temperatures from the database +func (sqlite *SQLite) GetTemperatures(ctx context.Context) ([]*types.MeasuredValue, error) { + sqlite.mutex.Lock() + defer sqlite.mutex.Unlock() + + f, err := sqlite3DMLAssets.ReadFile(selectTemperaturesSQLFile) + if err != nil { + return nil, fmt.Errorf("Failed to read file %v: %w", selectTemperaturesSQLFile, err) + } + + tx, err := sqlite.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: true}) + if err != nil { + return nil, err + } + defer tx.Rollback() + + measuredValues, err := sqlite.selectMeasuredValue(tx, string(f)) + if err != nil { + return nil, err + } + + err = tx.Commit() + if err != nil { + return nil, err + } + + for _, measuredValue := range measuredValues { + measuredValue.ValueType = types.Temperature + } + + return measuredValues, nil +} + +// UpdateDevices updates a device in the database +func (sqlite *SQLite) UpdateDevices(ctx context.Context, devices ...*types.Device) error { + sqlite.mutex.Lock() + defer sqlite.mutex.Unlock() + + f, err := sqlite3DMLAssets.ReadFile(updateDeviceSQLFile) + if err != nil { + return fmt.Errorf("Failed to read file %v: %w", updateDeviceSQLFile, err) + } + + tx, err := sqlite.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: false}) + if err != nil { + return err + } + defer tx.Rollback() + + stmt, err := tx.Prepare(string(f)) + if err != nil { + return err + } + defer stmt.Close() + + for _, device := range devices { + _, err := stmt.Exec( + &device.Name, + &device.Location, + &device.CreationDate, + &device.UpdateDate, + &device.ID, + ) + if err != nil { + return err + } + } + + return tx.Commit() +} + +// UpdateSensors updates a sensor in the database +func (sqlite *SQLite) UpdateSensors(ctx context.Context, sensors ...*types.Sensor) error { + sqlite.mutex.Lock() + defer sqlite.mutex.Unlock() + + f, err := sqlite3DMLAssets.ReadFile(updateSensorSQLFile) + if err != nil { + return fmt.Errorf("Failed to read file %v: %w", updateSensorSQLFile, err) + } + + tx, err := sqlite.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: false}) + if err != nil { + return err + } + defer tx.Rollback() + + stmt, err := tx.Prepare(string(f)) + if err != nil { + return err + } + defer stmt.Close() + + for _, sensor := range sensors { + _, err := stmt.Exec( + &sensor.Name, + &sensor.Location, + &sensor.WireID, + &sensor.I2CBus, + &sensor.I2CAddress, + &sensor.GPIONumber, + &sensor.Model, + &sensor.Enabled, + &sensor.TickDuration, + &sensor.DeviceID, + &sensor.CreationDate, + &sensor.UpdateDate, + &sensor.ID, + ) + if err != nil { + return err + } + } + + return tx.Commit() +}