PKGBUILD/pkg/repository/db/sqlite.go
markus 366dccde12
fix: use go-migrate pkg to init or update db schema
Instead to implement own logic how the database scheme should be updated
or migrated to a newer or older version flucky use now instead the
go-migrate package.
2021-01-30 15:44:21 +01:00

940 lines
22 KiB
Go

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()
}