influxdb/notebooks/service.go

150 lines
3.7 KiB
Go

package notebooks
import (
"context"
"database/sql"
"errors"
"time"
"github.com/influxdata/influxdb/v2"
"github.com/influxdata/influxdb/v2/kit/platform"
"github.com/influxdata/influxdb/v2/snowflake"
"github.com/influxdata/influxdb/v2/sqlite"
)
var _ influxdb.NotebookService = (*Service)(nil)
type Service struct {
store *sqlite.SqlStore
idGenerator platform.IDGenerator
}
func NewService(store *sqlite.SqlStore) *Service {
return &Service{
store: store,
idGenerator: snowflake.NewIDGenerator(),
}
}
func (s *Service) GetNotebook(ctx context.Context, id platform.ID) (*influxdb.Notebook, error) {
var n influxdb.Notebook
query := `
SELECT id, org_id, name, spec, created_at, updated_at
FROM notebooks WHERE id = $1`
if err := s.store.DB.GetContext(ctx, &n, query, id); err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, influxdb.ErrNotebookNotFound
}
return nil, err
}
return &n, nil
}
// CreateNotebook creates a notebook. Note that this and all "write" operations on the database need to use the Mutex lock,
// since sqlite can only handle 1 concurrent write operation at a time.
func (s *Service) CreateNotebook(ctx context.Context, create *influxdb.NotebookReqBody) (*influxdb.Notebook, error) {
s.store.Mu.Lock()
defer s.store.Mu.Unlock()
nowTime := time.Now().UTC()
n := influxdb.Notebook{
ID: s.idGenerator.ID(),
OrgID: create.OrgID,
Name: create.Name,
Spec: create.Spec,
CreatedAt: nowTime,
UpdatedAt: nowTime,
}
query := `
INSERT INTO notebooks (id, org_id, name, spec, created_at, updated_at)
VALUES (:id, :org_id, :name, :spec, :created_at, :updated_at)`
_, err := s.store.DB.NamedExecContext(ctx, query, &n)
if err != nil {
return nil, err
}
// Ideally, the create query would use "RETURNING" in order to avoid making a separate query.
// Unfortunately this breaks the scanning of values into the result struct, so we have to make a separate
// SELECT request to return the result from the database.
return s.GetNotebook(ctx, n.ID)
}
// UpdateNotebook updates a notebook.
func (s *Service) UpdateNotebook(ctx context.Context, id platform.ID, update *influxdb.NotebookReqBody) (*influxdb.Notebook, error) {
s.store.Mu.Lock()
defer s.store.Mu.Unlock()
nowTime := time.Now().UTC()
n := influxdb.Notebook{
ID: id,
OrgID: update.OrgID,
Name: update.Name,
Spec: update.Spec,
UpdatedAt: nowTime,
}
query := `
UPDATE notebooks SET org_id = :org_id, name = :name, spec = :spec, updated_at = :updated_at
WHERE id = :id`
_, err := s.store.DB.NamedExecContext(ctx, query, &n)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, influxdb.ErrNotebookNotFound
}
return nil, err
}
return s.GetNotebook(ctx, n.ID)
}
// DeleteNotebook deletes a notebook.
func (s *Service) DeleteNotebook(ctx context.Context, id platform.ID) error {
s.store.Mu.Lock()
defer s.store.Mu.Unlock()
query := `
DELETE FROM notebooks
WHERE id = $1`
res, err := s.store.DB.ExecContext(ctx, query, id.String())
if err != nil {
return err
}
r, err := res.RowsAffected()
if err != nil {
return err
}
if r == 0 {
return influxdb.ErrNotebookNotFound
}
return nil
}
// ListNotebooks lists notebooks matching the provided filter. Currently, only org_id is used in the filter.
// Future uses may support pagination via this filter as well.
func (s *Service) ListNotebooks(ctx context.Context, filter influxdb.NotebookListFilter) ([]*influxdb.Notebook, error) {
ns := []*influxdb.Notebook{}
query := `
SELECT id, org_id, name, spec, created_at, updated_at
FROM notebooks
WHERE org_id = $1`
if err := s.store.DB.SelectContext(ctx, &ns, query, filter.OrgID); err != nil {
return nil, err
}
return ns, nil
}