diff --git a/cmd/psql-gorm-poc/main.go b/cmd/psql-gorm-poc/main.go index a70facca..48d2c964 100644 --- a/cmd/psql-gorm-poc/main.go +++ b/cmd/psql-gorm-poc/main.go @@ -66,7 +66,7 @@ func main() { case float64: theInt = int(v) case int: - theInt = int(v) + theInt = v default: log.Panic().Interface("value", v).Msg("unexpected type in JSON") } diff --git a/internal/manager/persistence/db.go b/internal/manager/persistence/db.go index 0afe5eaa..6da269a2 100644 --- a/internal/manager/persistence/db.go +++ b/internal/manager/persistence/db.go @@ -23,10 +23,11 @@ package persistence import ( "context" - "database/sql" "fmt" "github.com/rs/zerolog/log" + "gorm.io/driver/postgres" + "gorm.io/gorm" _ "modernc.org/sqlite" "gitlab.com/blender/flamenco-ng-poc/internal/manager/job_compilers" @@ -34,30 +35,28 @@ import ( ) // TODO : have this configurable from the CLI. -const dbURI = "flamenco-manager.sqlite" +const dbDSN = "host=localhost user=flamenco password=flamenco dbname=flamenco TimeZone=Europe/Amsterdam" // DB provides the database interface. type DB struct { - sqldb *sql.DB + gormDB *gorm.DB } func OpenDB(ctx context.Context) (*DB, error) { - log.Info().Str("uri", dbURI).Msg("opening database") - return openDB(ctx, dbURI) + return openDB(ctx, dbDSN) } func openDB(ctx context.Context, uri string) (*DB, error) { - sqldb, err := sql.Open("sqlite", uri) - if err != nil { - return nil, fmt.Errorf("unable to open database: %w", err) - } + // TODO: don't log the password. + log.Info().Str("dsn", dbDSN).Msg("opening database") - if err := sqldb.PingContext(ctx); err != nil { - return nil, fmt.Errorf("error accessing database %s: %w", dbURI, err) + gormDB, err := gorm.Open(postgres.Open(dbDSN), &gorm.Config{}) + if err != nil { + log.Panic().Err(err).Msg("failed to connect database") } db := DB{ - sqldb: sqldb, + gormDB: gormDB, } if err := db.migrate(); err != nil { return nil, err @@ -67,95 +66,46 @@ func openDB(ctx context.Context, uri string) (*DB, error) { } func (db *DB) StoreJob(ctx context.Context, authoredJob job_compilers.AuthoredJob) error { - tx, err := db.sqldb.BeginTx(ctx, nil) - if err != nil { - return err - } - defer tx.Rollback() - _, err = tx.ExecContext(ctx, - `INSERT INTO jobs (uuid, name, jobType, priority) VALUES (?, ?, ?, ?)`, - authoredJob.JobID, authoredJob.Name, authoredJob.JobType, authoredJob.Priority, - ) - if err != nil { - return err + dbJob := Job{ + UUID: authoredJob.JobID, + Name: authoredJob.Name, + JobType: authoredJob.JobType, + Priority: int8(authoredJob.Priority), + Settings: JobSettings(authoredJob.Settings), + Metadata: JobMetadata(authoredJob.Metadata), } - for key, value := range authoredJob.Settings { - _, err := tx.ExecContext(ctx, - `INSERT INTO job_settings (job_id, key, value) VALUES (?, ?, ?)`, - authoredJob.JobID, key, value, - ) - if err != nil { - return err - } + tx := db.gormDB.Create(&dbJob) + if tx.Error != nil { + return fmt.Errorf("error storing job: %v", tx.Error) } - for key, value := range authoredJob.Metadata { - _, err := tx.ExecContext(ctx, - `INSERT INTO job_metadata (job_id, key, value) VALUES (?, ?, ?)`, - authoredJob.JobID, key, value, - ) - if err != nil { - return err - } - } - - return tx.Commit() + return nil } func (db *DB) FetchJob(ctx context.Context, jobID string) (*api.Job, error) { - job := api.Job{} - - err := db.sqldb. - QueryRowContext(ctx, `SELECT * FROM jobs j where j.uuid=?`, jobID). - Scan(&job.Id, &job.Name, &job.Type, &job.Priority, &job.Created, &job.Updated) - if err != nil { - return nil, err + dbJob := Job{} + findResult := db.gormDB.First(&dbJob, "uuid = ?", jobID) + if findResult.Error != nil { + return nil, findResult.Error } - var settings api.JobSettings - rows, err := db.sqldb.QueryContext(ctx, "SELECT key, value FROM job_settings WHERE job_id=?", jobID) - if err != nil { - return nil, err - } - defer rows.Close() - for rows.Next() { - var key, value string - if err := rows.Scan(&key, &value); err != nil { - return nil, err - } - settings.AdditionalProperties[key] = value - } - if err := rows.Close(); err != nil { - return nil, err - } - if err := rows.Err(); err != nil { - return nil, err + apiJob := api.Job{ + SubmittedJob: api.SubmittedJob{ + Name: dbJob.Name, + Priority: int(dbJob.Priority), + Type: dbJob.JobType, + }, + + Id: dbJob.UUID, + Created: dbJob.CreatedAt, + Updated: dbJob.UpdatedAt, + Status: api.JobStatus(dbJob.Status), } - var metadata api.JobMetadata - rows, err = db.sqldb.QueryContext(ctx, "SELECT key, value FROM job_metadata WHERE job_id=?", jobID) - if err != nil { - return nil, err - } - defer rows.Close() - for rows.Next() { - var key, value string - if err := rows.Scan(&key, &value); err != nil { - return nil, err - } - metadata.AdditionalProperties[key] = value - } - if err := rows.Close(); err != nil { - return nil, err - } - if err := rows.Err(); err != nil { - return nil, err - } + apiJob.Settings.AdditionalProperties = dbJob.Settings + apiJob.Metadata.AdditionalProperties = dbJob.Metadata - job.Settings = &settings - job.Metadata = &metadata - - return &job, nil + return &apiJob, nil } diff --git a/internal/manager/persistence/db_migration.go b/internal/manager/persistence/db_migration.go index 421d104a..1125508a 100644 --- a/internal/manager/persistence/db_migration.go +++ b/internal/manager/persistence/db_migration.go @@ -21,48 +21,12 @@ package persistence * ***** END GPL LICENSE BLOCK ***** */ import ( - "embed" "fmt" - - "github.com/golang-migrate/migrate/v4" - "github.com/golang-migrate/migrate/v4/database/sqlite" - "github.com/golang-migrate/migrate/v4/source" - "github.com/golang-migrate/migrate/v4/source/iofs" ) -func init() { - source.Register("embedfs", &EmbedFS{}) -} - -type EmbedFS struct { - iofs.PartialDriver -} - -//go:embed migrations/*.sql -var embedFS embed.FS - func (db *DB) migrate() error { - driver, err := sqlite.WithInstance(db.sqldb, &sqlite.Config{}) - if err != nil { - return fmt.Errorf("cannot create migration driver: %w", err) - } - - m, err := migrate.NewWithDatabaseInstance("embedfs://", "sqlite", driver) - if err != nil { - return fmt.Errorf("cannot create migration instance: %w", err) - } - - err = m.Up() - if err != nil && err != migrate.ErrNoChange { - return fmt.Errorf("cannot migrate database: %w", err) + if err := db.gormDB.AutoMigrate(&Job{}); err != nil { + return fmt.Errorf("failed to automigrate database: %v", err) } return nil } - -func (f *EmbedFS) Open(url string) (source.Driver, error) { - nf := &EmbedFS{} - if err := nf.Init(embedFS, "migrations"); err != nil { - return nil, err - } - return nf, nil -} diff --git a/internal/manager/persistence/migrations/000001_create_jobs_table.down.sql b/internal/manager/persistence/migrations/000001_create_jobs_table.down.sql deleted file mode 100644 index 6bffc015..00000000 --- a/internal/manager/persistence/migrations/000001_create_jobs_table.down.sql +++ /dev/null @@ -1 +0,0 @@ -DROP TABLE jobs; diff --git a/internal/manager/persistence/migrations/000001_create_jobs_table.up.sql b/internal/manager/persistence/migrations/000001_create_jobs_table.up.sql deleted file mode 100644 index 919c0999..00000000 --- a/internal/manager/persistence/migrations/000001_create_jobs_table.up.sql +++ /dev/null @@ -1,24 +0,0 @@ -CREATE TABLE jobs ( - uuid CHAR(36) PRIMARY KEY NOT NULL, - name VARCHAR(255) NOT NULL, - jobType VARCHAR(255) NOT NULL, - priority INT NOT NULL, - created TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, - updated TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL -); - -CREATE TABLE job_settings ( - job_id CHAR(36), - key VARCHAR(255), - value TEXT, - FOREIGN KEY(job_id) REFERENCES jobs(uuid) -); -CREATE UNIQUE INDEX job_settings_index ON job_settings(job_id, key); - -CREATE TABLE job_metadata ( - job_id CHAR(36), - key VARCHAR(255), - value TEXT, - FOREIGN KEY(job_id) REFERENCES jobs(uuid) -); -CREATE UNIQUE INDEX job_metadata_index ON job_metadata(job_id, key); diff --git a/internal/manager/persistence/models.go b/internal/manager/persistence/models.go new file mode 100644 index 00000000..28dddcb7 --- /dev/null +++ b/internal/manager/persistence/models.go @@ -0,0 +1,68 @@ +package persistence + +/* ***** BEGIN GPL LICENSE BLOCK ***** + * + * Original Code Copyright (C) 2022 Blender Foundation. + * + * This file is part of Flamenco. + * + * Flamenco is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later + * version. + * + * Flamenco is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with + * Flamenco. If not, see . + * + * ***** END GPL LICENSE BLOCK ***** */ + +import ( + "database/sql/driver" + "encoding/json" + "errors" + + "gorm.io/gorm" +) + +type Job struct { + gorm.Model + UUID string `gorm:"type:char(36)"` + + Name string `gorm:"type:varchar(64)"` + JobType string `gorm:"type:varchar(32)"` + Priority int8 `gorm:"type:smallint"` + Status string `gorm:"type:varchar(32)"` // See JobStatusXxxx consts in openapi_types.gen.go + + Settings JobSettings `gorm:"type:jsonb"` + Metadata JobMetadata `gorm:"type:jsonb"` +} + +type JobSettings map[string]interface{} + +func (js JobSettings) Value() (driver.Value, error) { + return json.Marshal(js) +} +func (js *JobSettings) Scan(value interface{}) error { + b, ok := value.([]byte) + if !ok { + return errors.New("type assertion to []byte failed") + } + return json.Unmarshal(b, &js) +} + +type JobMetadata map[string]string + +func (js JobMetadata) Value() (driver.Value, error) { + return json.Marshal(js) +} +func (js *JobMetadata) Scan(value interface{}) error { + b, ok := value.([]byte) + if !ok { + return errors.New("type assertion to []byte failed") + } + return json.Unmarshal(b, &js) +} diff --git a/internal/manager/persistence/types.go b/internal/manager/persistence/types.go deleted file mode 100644 index 2c0be7a1..00000000 --- a/internal/manager/persistence/types.go +++ /dev/null @@ -1,44 +0,0 @@ -package persistence - -/* ***** BEGIN GPL LICENSE BLOCK ***** - * - * Original Code Copyright (C) 2022 Blender Foundation. - * - * This file is part of Flamenco. - * - * Flamenco is free software: you can redistribute it and/or modify it under - * the terms of the GNU General Public License as published by the Free Software - * Foundation, either version 3 of the License, or (at your option) any later - * version. - * - * Flamenco is distributed in the hope that it will be useful, but WITHOUT ANY - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - * A PARTICULAR PURPOSE. See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along with - * Flamenco. If not, see . - * - * ***** END GPL LICENSE BLOCK ***** */ - -import "time" - -type Job struct { - ID string // UUID - Name string - JobType string - Priority int8 - Created time.Time - Updated time.Time -} - -type JobSetting struct { - JobID string - Key string - Value string -} - -type JobMetadata struct { - JobID string - Key string - Value string -}