package repository import ( "context" "database/sql" "errors" "fmt" "net/url" "os" "path/filepath" "sync" "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" "github.com/golang-migrate/migrate/v4" "github.com/johejo/golang-migrate-extra/source/iofs" ) type SQLiteOpts struct { DatabaseURL *url.URL Logger logger.Logger } func (o *SQLiteOpts) 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 NewSQLite(opts SQLiteOpts) (Repository, 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) { /* #nosec */ 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 } // AddDevices into the database func (d *SQLite) AddDevices(ctx context.Context, devices ...*types.Device) error { d.mutex.Lock() defer d.mutex.Unlock() tx, err := d.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: false}) if err != nil { return fmt.Errorf("Failed to begin new transaction: %v", err) } defer func() { _ = tx.Rollback() }() err = d.insertDevices(tx, devices...) if err != nil { return err } return tx.Commit() } func (d *SQLite) insertDevices(tx *sql.Tx, devices ...*types.Device) error { stmt, err := tx.Prepare(sqlite3.InsertDeviceSQL) if err != nil { return fmt.Errorf("Failed to prepare statement: %v", err) } defer func() { _ = 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 nil } // AddOrUpdateDevices into the database func (d *SQLite) AddOrUpdateDevices(ctx context.Context, devices ...*types.Device) error { d.mutex.Lock() defer d.mutex.Unlock() tx, err := d.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: false}) if err != nil { return fmt.Errorf("Failed to begin new transaction: %v", err) } defer func() { _ = tx.Rollback() }() err = d.insertOrUpdateDevices(tx, devices...) if err != nil { return err } return tx.Commit() } func (d *SQLite) insertOrUpdateDevices(tx *sql.Tx, devices ...*types.Device) error { stmt, err := tx.Prepare(sqlite3.InsertOrUpdateDeviceSQL) if err != nil { return fmt.Errorf("Failed to prepare statement: %v", err) } defer func() { _ = 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 nil } // AddMeasuredValues into the database func (d *SQLite) AddMeasuredValues(ctx context.Context, measuredValues ...*types.MeasuredValue) error { d.mutex.Lock() defer d.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 := d.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: false}) if err != nil { return fmt.Errorf("Failed to begin new transaction: %v", err) } defer func() { _ = tx.Rollback() }() for measuredValueType, measuredValues := range splittedMeasuredValues { var query string switch measuredValueType { case types.Humidity: query = sqlite3.InsertHumiditySQL case types.Pressure: query = sqlite3.InsertPressureSQL case types.Temperature: query = sqlite3.InsertTemperatureSQL default: return fmt.Errorf("Measured value type %v not supported", measuredValueType) } err := d.insertMeasuredValues(tx, query, measuredValues...) if err != nil { return err } } return tx.Commit() } func (d *SQLite) insertMeasuredValues(tx *sql.Tx, query string, measuredValues ...*types.MeasuredValue) error { stmt, err := tx.Prepare(query) if err != nil { return fmt.Errorf("Failed to prepare statement: %v", err) } defer func() { _ = 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 } // AddOrUpdateMeasuredValues into the database func (d *SQLite) AddOrUpdateMeasuredValues(ctx context.Context, measuredValues ...*types.MeasuredValue) error { d.mutex.Lock() defer d.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 := d.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: false}) if err != nil { return fmt.Errorf("Failed to begin new transaction: %v", err) } defer func() { _ = tx.Rollback() }() for measuredValueType, measuredValues := range splittedMeasuredValues { var query string switch measuredValueType { case types.Humidity: query = sqlite3.InsertOrUpdateHumiditySQL case types.Pressure: query = sqlite3.InsertOrUpdatePressureSQL case types.Temperature: query = sqlite3.InsertOrUpdateTemperatureSQL default: return fmt.Errorf("Measured value type %v not supported", measuredValueType) } err := d.insertOrUpdateMeasuredValues(tx, query, measuredValues...) if err != nil { return err } } return tx.Commit() } func (d *SQLite) insertOrUpdateMeasuredValues(tx *sql.Tx, query string, measuredValues ...*types.MeasuredValue) error { stmt, err := tx.Prepare(query) if err != nil { return fmt.Errorf("Failed to prepare statement: %v", err) } defer func() { _ = 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 } // AddSensors into the database func (d *SQLite) AddSensors(ctx context.Context, sensors ...*types.Sensor) error { d.mutex.Lock() defer d.mutex.Unlock() tx, err := d.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: false}) if err != nil { return fmt.Errorf("Failed to begin new transaction: %v", err) } defer func() { _ = tx.Rollback() }() stmt, err := tx.Prepare(sqlite3.InsertSensorSQL) if err != nil { return fmt.Errorf("Failed to prepare statement: %v", err) } defer func() { _ = 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() } // AddOrUpdateSensors into the database func (d *SQLite) AddOrUpdateSensors(ctx context.Context, sensors ...*types.Sensor) error { d.mutex.Lock() defer d.mutex.Unlock() tx, err := d.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: false}) if err != nil { return fmt.Errorf("Failed to begin new transaction: %v", err) } defer func() { _ = tx.Rollback() }() err = d.insertOrUpdateSensors(tx, sensors...) if err != nil { return err } return tx.Commit() } func (d *SQLite) insertOrUpdateSensors(tx *sql.Tx, sensors ...*types.Sensor) error { stmt, err := tx.Prepare(sqlite3.InsertOrUpdateSensorSQL) if err != nil { return fmt.Errorf("Failed to prepare statement: %v", err) } defer func() { _ = 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 nil } // 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 (d *SQLite) Close() error { d.mutex.Lock() defer d.mutex.Unlock() return d.dbo.Close() } // Migrate creates all required tables if not exist func (d *SQLite) Migrate(ctx context.Context) error { d.mutex.Lock() defer d.mutex.Unlock() sourceDriver, err := iofs.New(sqlite3.DDLAssets, sqlite3.DDLAssetPath) if err != nil { return err } m, err := migrate.NewWithSourceInstance("iofs", sourceDriver, d.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 (d *SQLite) GetDeviceByID(ctx context.Context, id string) (*types.Device, error) { d.mutex.Lock() defer d.mutex.Unlock() tx, err := d.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: true}) if err != nil { return nil, fmt.Errorf("Failed to begin new transaction: %v", err) } defer func() { _ = tx.Rollback() }() devices, err := d.selectDevices(tx, sqlite3.SelectDeviceByIDSQL, 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 (d *SQLite) GetDeviceByName(ctx context.Context, name string) (*types.Device, error) { d.mutex.Lock() defer d.mutex.Unlock() tx, err := d.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: true}) if err != nil { return nil, fmt.Errorf("Failed to begin new transaction: %v", err) } defer func() { _ = tx.Rollback() }() devices, err := d.selectDevices(tx, sqlite3.SelectDeviceByNameSQL, 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 (d *SQLite) GetDevices(ctx context.Context) ([]*types.Device, error) { d.mutex.Lock() defer d.mutex.Unlock() tx, err := d.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: true}) if err != nil { return nil, fmt.Errorf("Failed to begin new transaction: %v", err) } defer func() { _ = tx.Rollback() }() devices, err := d.selectDevices(tx, sqlite3.SelectDevicesSQL) 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 (d *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 func() { _ = stmt.Close() }() rows, err := stmt.Query(args...) if err != nil { return nil, fmt.Errorf("Failed to query statement: %v", err) } defer func() { _ = 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) } if err := rows.Err(); err != nil { return nil, fmt.Errorf("failed to scan rows: %v", err) } return devices, nil } // GetHumidity returns humidity from the database func (d *SQLite) GetHumidityByID(ctx context.Context, id string) (*types.MeasuredValue, error) { d.mutex.Lock() defer d.mutex.Unlock() tx, err := d.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: true}) if err != nil { return nil, err } defer func() { _ = tx.Rollback() }() measuredValues, err := d.selectMeasuredValue(tx, sqlite3.SelectHumidityByIDSQL, 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 (d *SQLite) GetHumidities(ctx context.Context) ([]*types.MeasuredValue, error) { d.mutex.Lock() defer d.mutex.Unlock() tx, err := d.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: true}) if err != nil { return nil, err } defer func() { _ = tx.Rollback() }() measuredValues, err := d.selectMeasuredValue(tx, sqlite3.SelectHumiditiesSQL) 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 (d *SQLite) selectMeasuredValue(tx *sql.Tx, query string, args ...interface{}) ([]*types.MeasuredValue, error) { stmt, err := tx.Prepare(query) if err != nil { return nil, err } defer func() { _ = stmt.Close() }() rows, err := stmt.Query(args...) if err != nil { return nil, err } defer func() { _ = 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) } if err := rows.Err(); err != nil { return nil, fmt.Errorf("failed to scan rows: %v", err) } return measuredValues, nil } // GetPressure returns pressure from the database func (d *SQLite) GetPressureByID(ctx context.Context, id string) (*types.MeasuredValue, error) { d.mutex.Lock() defer d.mutex.Unlock() tx, err := d.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: true}) if err != nil { return nil, err } defer func() { _ = tx.Rollback() }() measuredValues, err := d.selectMeasuredValue(tx, sqlite3.SelectPressureByIDSQL, 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 (d *SQLite) GetPressures(ctx context.Context) ([]*types.MeasuredValue, error) { d.mutex.Lock() defer d.mutex.Unlock() tx, err := d.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: true}) if err != nil { return nil, err } defer func() { _ = tx.Rollback() }() measuredValues, err := d.selectMeasuredValue(tx, sqlite3.SelectPressuresSQL) 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 (d *SQLite) GetSensorByID(ctx context.Context, id string) (*types.Sensor, error) { d.mutex.Lock() defer d.mutex.Unlock() tx, err := d.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: true}) if err != nil { return nil, fmt.Errorf("Failed to begin new transaction: %v", err) } defer func() { _ = tx.Rollback() }() sensors, err := d.selectSensors(tx, sqlite3.SelectSensorByIDSQL, 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 (d *SQLite) GetSensors(ctx context.Context) ([]*types.Sensor, error) { d.mutex.Lock() defer d.mutex.Unlock() tx, err := d.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: true}) if err != nil { return nil, fmt.Errorf("Failed to begin new transaction: %v", err) } defer func() { _ = tx.Rollback() }() sensors, err := d.selectSensors(tx, sqlite3.SelectSensorsSQL) 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 (d *SQLite) GetSensorsByDeviceIDs(ctx context.Context, deviceIDs ...string) ([]*types.Sensor, error) { d.mutex.Lock() defer d.mutex.Unlock() tx, err := d.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: true}) if err != nil { return nil, fmt.Errorf("Failed to begin new transaction: %v", err) } defer func() { _ = tx.Rollback() }() cachedSensors := make([]*types.Sensor, 0) for i := range deviceIDs { sensors, err := d.selectSensors(tx, sqlite3.SelectSensorsByDeviceIDSQL, 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 (d *SQLite) GetSensorsByModels(ctx context.Context, sensorModels ...string) ([]*types.Sensor, error) { d.mutex.Lock() defer d.mutex.Unlock() tx, err := d.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: true}) if err != nil { return nil, fmt.Errorf("Failed to begin new transaction: %v", err) } defer func() { _ = tx.Rollback() }() cachedSensors := make([]*types.Sensor, 0) for i := range sensorModels { sensors, err := d.selectSensors(tx, sqlite3.SelectSensorsByModelSQL, 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 (d *SQLite) GetSensorsByNames(ctx context.Context, sensorNames ...string) ([]*types.Sensor, error) { d.mutex.Lock() defer d.mutex.Unlock() tx, err := d.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: true}) if err != nil { return nil, fmt.Errorf("Failed to begin new transaction: %v", err) } defer func() { _ = tx.Rollback() }() cachedSensors := make([]*types.Sensor, 0) for i := range sensorNames { sensors, err := d.selectSensors(tx, sqlite3.SelectSensorsByNameSQL, 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 (d *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 func() { _ = stmt.Close() }() rows, err := stmt.Query(args...) if err != nil { return nil, fmt.Errorf("Failed to query statement: %v", err) } defer func() { _ = 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) } if err := rows.Err(); err != nil { return nil, fmt.Errorf("failed to scan rows: %v", err) } return sensors, nil } // GetTemperature returns temperatures from the database func (d *SQLite) GetTemperatureByID(ctx context.Context, id string) (*types.MeasuredValue, error) { d.mutex.Lock() defer d.mutex.Unlock() tx, err := d.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: true}) if err != nil { return nil, err } defer func() { _ = tx.Rollback() }() measuredValues, err := d.selectMeasuredValue(tx, sqlite3.SelectTemperatureByIDSQL, 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 (d *SQLite) GetTemperatures(ctx context.Context) ([]*types.MeasuredValue, error) { d.mutex.Lock() defer d.mutex.Unlock() tx, err := d.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: true}) if err != nil { return nil, err } defer func() { _ = tx.Rollback() }() measuredValues, err := d.selectMeasuredValue(tx, sqlite3.SelectTemperaturesSQL) 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 } // Import imports devices, sensors and all measured values from a source // repository. Existing entries will be updated. func (d *SQLite) Import(ctx context.Context, src Repository) error { d.mutex.Lock() defer d.mutex.Unlock() tx, err := d.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: false}) if err != nil { return err } defer func() { _ = tx.Rollback() }() devices, err := src.GetDevices(ctx) if err != nil { return err } err = d.insertOrUpdateDevices(tx, devices...) if err != nil { return err } sensors, err := src.GetSensors(ctx) if err != nil { return err } err = d.insertOrUpdateSensors(tx, sensors...) if err != nil { return err } for _, value := range []struct { f func(ctx context.Context) ([]*types.MeasuredValue, error) query string }{ {f: src.GetHumidities, query: postgres.InsertOrUpdateHumiditySQL}, {f: src.GetPressures, query: postgres.InsertOrUpdatePressureSQL}, {f: src.GetTemperatures, query: postgres.InsertOrUpdateTemperatureSQL}, } { measuredValues, err := value.f(ctx) if err != nil { return err } err = d.insertOrUpdateMeasuredValues(tx, value.query, measuredValues...) if err != nil { return err } } return tx.Commit() } // RemoveDevices from the database func (d *SQLite) RemoveDevicesByIDs(ctx context.Context, deviceIDs ...string) error { d.mutex.Lock() defer d.mutex.Unlock() tx, err := d.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: false}) if err != nil { return fmt.Errorf("Failed to begin new transaction: %v", err) } defer func() { _ = tx.Rollback() }() stmt, err := tx.Prepare(sqlite3.DeleteDeviceByIDSQL) if err != nil { return fmt.Errorf("Failed to prepare statement: %v", err) } defer func() { _ = stmt.Close() }() for _, deviceID := range deviceIDs { _, err = stmt.Exec(deviceID) if err != nil { return err } } return tx.Commit() } // RemoveDevices from the database func (d *SQLite) RemoveDevicesByNames(ctx context.Context, names ...string) error { d.mutex.Lock() defer d.mutex.Unlock() tx, err := d.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: false}) if err != nil { return fmt.Errorf("Failed to begin new transaction: %v", err) } defer func() { _ = tx.Rollback() }() stmt, err := tx.Prepare(sqlite3.DeleteDeviceByNameSQL) if err != nil { return fmt.Errorf("Failed to prepare statement: %v", err) } defer func() { _ = stmt.Close() }() for _, deviceID := range names { _, err = stmt.Exec(deviceID) if err != nil { return err } } return tx.Commit() } // RemoveSensors from the database func (d *SQLite) RemoveSensorsByIDs(ctx context.Context, sensorIDs ...string) error { d.mutex.Lock() defer d.mutex.Unlock() tx, err := d.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: false}) if err != nil { return fmt.Errorf("Failed to begin new transaction: %v", err) } defer func() { _ = tx.Rollback() }() stmt, err := tx.Prepare(sqlite3.DeleteSensorByIDSQL) if err != nil { return fmt.Errorf("Failed to prepare statement: %v", err) } defer func() { _ = stmt.Close() }() for _, sensorID := range sensorIDs { _, err = stmt.Exec(sensorID) if err != nil { return err } } return tx.Commit() } // RemoveSensors from the database func (d *SQLite) RemoveSensorsByNames(ctx context.Context, names ...string) error { d.mutex.Lock() defer d.mutex.Unlock() tx, err := d.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: false}) if err != nil { return fmt.Errorf("Failed to begin new transaction: %v", err) } defer func() { _ = tx.Rollback() }() stmt, err := tx.Prepare(sqlite3.DeleteSensorByNameSQL) if err != nil { return fmt.Errorf("Failed to prepare statement: %v", err) } defer func() { _ = stmt.Close() }() for _, sensorID := range names { _, err = stmt.Exec(sensorID) if err != nil { return err } } return tx.Commit() } // UpdateDevices updates a device in the database func (d *SQLite) UpdateDevices(ctx context.Context, devices ...*types.Device) error { d.mutex.Lock() defer d.mutex.Unlock() tx, err := d.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: false}) if err != nil { return err } defer func() { _ = tx.Rollback() }() stmt, err := tx.Prepare(sqlite3.UpdateDeviceSQL) if err != nil { return err } defer func() { _ = 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 (d *SQLite) UpdateSensors(ctx context.Context, sensors ...*types.Sensor) error { d.mutex.Lock() defer d.mutex.Unlock() tx, err := d.dbo.BeginTx(ctx, &sql.TxOptions{ReadOnly: false}) if err != nil { return err } defer func() { _ = tx.Rollback() }() stmt, err := tx.Prepare(sqlite3.UpdateSensorSQL) if err != nil { return err } defer func() { _ = 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() }