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 }