mirror of https://github.com/grafana/grafana
Unified Storage: create kind_version table migration, add SQL and fix db (#87977)
* fix database interfaces * add queries * fix queries * fix linters * add owner to imported go library * remove unused funcs * run go work sync * improve critical section fix in data race fix * fix linters * remove sync * fix typo * improve data embedding * fix linters * fix migration * remove unnecessary comments * fix linters * improve SQL templates readability * remove group_version from kind_version for consistency in History method * add created_at and updated_at columns to kind_version tablepull/88188/head
parent
6744dae806
commit
8b02b6b76a
@ -0,0 +1,59 @@ |
||||
package dbimpl |
||||
|
||||
import ( |
||||
"context" |
||||
"database/sql" |
||||
"fmt" |
||||
|
||||
entitydb "github.com/grafana/grafana/pkg/services/store/entity/db" |
||||
) |
||||
|
||||
func NewDB(d *sql.DB, driverName string) entitydb.DB { |
||||
return sqldb{ |
||||
DB: d, |
||||
driverName: driverName, |
||||
} |
||||
} |
||||
|
||||
type sqldb struct { |
||||
*sql.DB |
||||
driverName string |
||||
} |
||||
|
||||
func (d sqldb) DriverName() string { |
||||
return d.driverName |
||||
} |
||||
|
||||
func (d sqldb) BeginTx(ctx context.Context, opts *sql.TxOptions) (entitydb.Tx, error) { |
||||
t, err := d.DB.BeginTx(ctx, opts) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
return tx{ |
||||
Tx: t, |
||||
}, nil |
||||
} |
||||
|
||||
func (d sqldb) WithTx(ctx context.Context, opts *sql.TxOptions, f entitydb.TxFunc) error { |
||||
t, err := d.BeginTx(ctx, opts) |
||||
if err != nil { |
||||
return fmt.Errorf("begin tx: %w", err) |
||||
} |
||||
|
||||
if err := f(ctx, t); err != nil { |
||||
if rollbackErr := t.Rollback(); rollbackErr != nil { |
||||
return fmt.Errorf("tx err: %w; rollback err: %w", err, rollbackErr) |
||||
} |
||||
return fmt.Errorf("tx err: %w", err) |
||||
} |
||||
|
||||
if err = t.Commit(); err != nil { |
||||
return fmt.Errorf("commit err: %w", err) |
||||
} |
||||
|
||||
return nil |
||||
} |
||||
|
||||
type tx struct { |
||||
*sql.Tx |
||||
} |
@ -0,0 +1,54 @@ |
||||
package dbimpl |
||||
|
||||
import ( |
||||
"context" |
||||
"testing" |
||||
"time" |
||||
|
||||
sqlmock "github.com/DATA-DOG/go-sqlmock" |
||||
"github.com/stretchr/testify/require" |
||||
|
||||
entitydb "github.com/grafana/grafana/pkg/services/store/entity/db" |
||||
) |
||||
|
||||
func newCtx(t *testing.T) context.Context { |
||||
t.Helper() |
||||
|
||||
d, ok := t.Deadline() |
||||
if !ok { |
||||
// provide a default timeout for tests
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) |
||||
t.Cleanup(cancel) |
||||
|
||||
return ctx |
||||
} |
||||
|
||||
ctx, cancel := context.WithDeadline(context.Background(), d) |
||||
t.Cleanup(cancel) |
||||
|
||||
return ctx |
||||
} |
||||
|
||||
func TestDB_WithTx(t *testing.T) { |
||||
t.Parallel() |
||||
|
||||
newTxFunc := func(err error) entitydb.TxFunc { |
||||
return func(context.Context, entitydb.Tx) error { |
||||
return err |
||||
} |
||||
} |
||||
|
||||
t.Run("happy path", func(t *testing.T) { |
||||
t.Parallel() |
||||
|
||||
sqldb, mock, err := sqlmock.New() |
||||
require.NoError(t, err) |
||||
db := NewDB(sqldb, "sqlmock") |
||||
|
||||
mock.ExpectBegin() |
||||
mock.ExpectCommit() |
||||
err = db.WithTx(newCtx(t), nil, newTxFunc(nil)) |
||||
|
||||
require.NoError(t, err) |
||||
}) |
||||
} |
@ -1,16 +1,58 @@ |
||||
package db |
||||
|
||||
import ( |
||||
"context" |
||||
"database/sql" |
||||
|
||||
"xorm.io/xorm" |
||||
|
||||
// "github.com/grafana/grafana/pkg/services/sqlstore"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore/session" |
||||
"github.com/grafana/grafana/pkg/setting" |
||||
) |
||||
|
||||
const ( |
||||
DriverPostgres = "postgres" |
||||
DriverMySQL = "mysql" |
||||
DriverSQLite3 = "sqlite3" |
||||
) |
||||
|
||||
// EntityDBInterface provides access to a database capable of supporting the
|
||||
// Entity Server.
|
||||
type EntityDBInterface interface { |
||||
Init() error |
||||
GetCfg() *setting.Cfg |
||||
GetDB() (DB, error) |
||||
|
||||
// TODO: deprecate.
|
||||
GetSession() (*session.SessionDB, error) |
||||
GetEngine() (*xorm.Engine, error) |
||||
GetCfg() *setting.Cfg |
||||
} |
||||
|
||||
// DB is a thin abstraction on *sql.DB to allow mocking to provide better unit
|
||||
// testing. We purposefully hide database operation methods that would use
|
||||
// context.Background().
|
||||
type DB interface { |
||||
ContextExecer |
||||
BeginTx(context.Context, *sql.TxOptions) (Tx, error) |
||||
WithTx(context.Context, *sql.TxOptions, TxFunc) error |
||||
PingContext(context.Context) error |
||||
Stats() sql.DBStats |
||||
DriverName() string |
||||
} |
||||
|
||||
type TxFunc = func(context.Context, Tx) error |
||||
|
||||
type Tx interface { |
||||
ContextExecer |
||||
Exec(query string, args ...any) (sql.Result, error) |
||||
Query(query string, args ...any) (*sql.Rows, error) |
||||
QueryRow(query string, args ...any) *sql.Row |
||||
Commit() error |
||||
Rollback() error |
||||
} |
||||
|
||||
type ContextExecer interface { |
||||
ExecContext(ctx context.Context, query string, args ...any) (sql.Result, error) |
||||
QueryContext(ctx context.Context, query string, args ...any) (*sql.Rows, error) |
||||
QueryRowContext(ctx context.Context, query string, args ...any) *sql.Row |
||||
} |
||||
|
@ -0,0 +1,7 @@ |
||||
DELETE FROM {{ .Ident "entity" }} |
||||
WHERE 1 = 1 |
||||
AND {{ .Ident "namespace" }} = {{ .Arg .Key.Namespace }} |
||||
AND {{ .Ident "group" }} = {{ .Arg .Key.Group }} |
||||
AND {{ .Ident "resource" }} = {{ .Arg .Key.Resource }} |
||||
AND {{ .Ident "name" }} = {{ .Arg .Key.Name }} |
||||
; |
@ -0,0 +1,35 @@ |
||||
INSERT INTO {{ .Ident "entity_folder" }} |
||||
( |
||||
{{ .Ident "guid" }}, |
||||
{{ .Ident "namespace" }}, |
||||
{{ .Ident "name" }}, |
||||
{{ .Ident "slug_path" }}, |
||||
{{ .Ident "tree" }}, |
||||
{{ .Ident "depth" }}, |
||||
{{ .Ident "lft" }}, |
||||
{{ .Ident "rgt" }}, |
||||
{{ .Ident "detached" }} |
||||
) |
||||
|
||||
VALUES |
||||
{{ $this := . }} |
||||
{{ $addComma := false }} |
||||
{{ range .Items }} |
||||
{{ if $addComma }} |
||||
, |
||||
{{ end }} |
||||
{{ $addComma = true }} |
||||
|
||||
( |
||||
{{ $this.Arg .GUID }}, |
||||
{{ $this.Arg .Namespace }}, |
||||
{{ $this.Arg .UID }}, |
||||
{{ $this.Arg .SlugPath }}, |
||||
{{ $this.Arg .JS }}, |
||||
{{ $this.Arg .Depth }}, |
||||
{{ $this.Arg .Left }}, |
||||
{{ $this.Arg .Right }}, |
||||
{{ $this.Arg .Detached }} |
||||
) |
||||
{{ end }} |
||||
; |
@ -0,0 +1,93 @@ |
||||
INSERT INTO |
||||
|
||||
{{/* Determine which table to insert into */}} |
||||
{{ if .TableEntity }} {{ .Ident "entity" }} |
||||
{{ else }} {{ .Ident "entity_history" }} |
||||
{{ end }} |
||||
|
||||
{{/* Explicitly specify fields that will be set */}} |
||||
( |
||||
{{ .Ident "guid" }}, |
||||
{{ .Ident "resource_version" }}, |
||||
|
||||
{{ .Ident "key" }}, |
||||
|
||||
{{ .Ident "group" }}, |
||||
{{ .Ident "group_version" }}, |
||||
{{ .Ident "resource" }}, |
||||
{{ .Ident "namespace" }}, |
||||
{{ .Ident "name" }}, |
||||
|
||||
{{ .Ident "folder" }}, |
||||
|
||||
{{ .Ident "meta" }}, |
||||
{{ .Ident "body" }}, |
||||
{{ .Ident "status" }}, |
||||
|
||||
{{ .Ident "size" }}, |
||||
{{ .Ident "etag" }}, |
||||
|
||||
{{ .Ident "created_at" }}, |
||||
{{ .Ident "created_by" }}, |
||||
{{ .Ident "updated_at" }}, |
||||
{{ .Ident "updated_by" }}, |
||||
|
||||
{{ .Ident "origin" }}, |
||||
{{ .Ident "origin_key" }}, |
||||
{{ .Ident "origin_ts" }}, |
||||
|
||||
{{ .Ident "title" }}, |
||||
{{ .Ident "slug" }}, |
||||
{{ .Ident "description" }}, |
||||
|
||||
{{ .Ident "message" }}, |
||||
{{ .Ident "labels" }}, |
||||
{{ .Ident "fields" }}, |
||||
{{ .Ident "errors" }}, |
||||
|
||||
{{ .Ident "action" }} |
||||
) |
||||
|
||||
{{/* Provide the values */}} |
||||
VALUES ( |
||||
{{ .Arg .Entity.Guid }}, |
||||
{{ .Arg .Entity.ResourceVersion }}, |
||||
|
||||
{{ .Arg .Entity.Key }}, |
||||
|
||||
{{ .Arg .Entity.Group }}, |
||||
{{ .Arg .Entity.GroupVersion }}, |
||||
{{ .Arg .Entity.Resource }}, |
||||
{{ .Arg .Entity.Namespace }}, |
||||
{{ .Arg .Entity.Name }}, |
||||
|
||||
{{ .Arg .Entity.Folder }}, |
||||
|
||||
{{ .Arg .Entity.Meta }}, |
||||
{{ .Arg .Entity.Body }}, |
||||
{{ .Arg .Entity.Status }}, |
||||
|
||||
{{ .Arg .Entity.Size }}, |
||||
{{ .Arg .Entity.ETag }}, |
||||
|
||||
{{ .Arg .Entity.CreatedAt }}, |
||||
{{ .Arg .Entity.CreatedBy }}, |
||||
{{ .Arg .Entity.UpdatedAt }}, |
||||
{{ .Arg .Entity.UpdatedBy }}, |
||||
|
||||
{{ .Arg .Entity.Origin.Source }}, |
||||
{{ .Arg .Entity.Origin.Key }}, |
||||
{{ .Arg .Entity.Origin.Time }}, |
||||
|
||||
{{ .Arg .Entity.Title }}, |
||||
{{ .Arg .Entity.Slug }}, |
||||
{{ .Arg .Entity.Description }}, |
||||
|
||||
{{ .Arg .Entity.Message }}, |
||||
{{ .Arg .Entity.Labels }}, |
||||
{{ .Arg .Entity.Fields }}, |
||||
{{ .Arg .Entity.Errors }}, |
||||
|
||||
{{ .Arg .Entity.Action }} |
||||
) |
||||
; |
@ -0,0 +1,18 @@ |
||||
DELETE FROM {{ .Ident "entity_labels" }} |
||||
WHERE 1 = 1 |
||||
AND {{ .Ident "guid" }} = {{ .Arg .GUID }} |
||||
{{ if gt (len .KeepLabels) 0 }} |
||||
AND {{ .Ident "label" }} NOT IN ( |
||||
{{ $this := . }} |
||||
{{ $addComma := false }} |
||||
{{ range .KeepLabels }} |
||||
{{ if $addComma }} |
||||
, |
||||
{{ end }} |
||||
{{ $addComma = true }} |
||||
|
||||
{{ $this.Arg . }} |
||||
{{ end }} |
||||
) |
||||
{{ end }} |
||||
; |
@ -0,0 +1,29 @@ |
||||
INSERT INTO {{ .Ident "entity_labels" }} |
||||
( |
||||
{{ .Ident "guid" }}, |
||||
{{ .Ident "label" }}, |
||||
{{ .Ident "value" }} |
||||
) |
||||
|
||||
VALUES |
||||
{{/* |
||||
When we enter the "range" loop the "." will be changed, so we need to |
||||
store the current ".GUID" in a variable to be able to use its value |
||||
*/}} |
||||
{{ $guid := .GUID }} |
||||
|
||||
{{ $this := . }} |
||||
{{ $addComma := false }} |
||||
{{ range $name, $value := .Labels }} |
||||
{{ if $addComma }} |
||||
, |
||||
{{ end }} |
||||
{{ $addComma = true }} |
||||
|
||||
( |
||||
{{ $this.Arg $guid }}, |
||||
{{ $this.Arg $name }}, |
||||
{{ $this.Arg $value }} |
||||
) |
||||
{{ end }} |
||||
; |
@ -0,0 +1,14 @@ |
||||
SELECT |
||||
{{ .Ident "guid" | .Into .FolderInfo.GUID }}, |
||||
{{ .Ident "name" | .Into .FolderInfo.UID }}, |
||||
{{ .Ident "folder" | .Into .FolderInfo.ParentUID }}, |
||||
{{ .Ident "name" | .Into .FolderInfo.Name }}, |
||||
{{ .Ident "slug" | .Into .FolderInfo.Slug }} |
||||
|
||||
FROM {{ .Ident "entity" }} |
||||
|
||||
WHERE 1 = 1 |
||||
AND {{ .Ident "group" }} = {{ .Arg .Group }} |
||||
AND {{ .Ident "resource" }} = {{ .Arg .Resource }} |
||||
AND {{ .Ident "namespace" }} = {{ .Arg .Namespace }} |
||||
; |
@ -0,0 +1,78 @@ |
||||
SELECT |
||||
{{ .Ident "guid" | .Into .Entity.Guid }}, |
||||
{{ .Ident "resource_version" | .Into .Entity.ResourceVersion }}, |
||||
|
||||
{{ .Ident "key" | .Into .Entity.Key }}, |
||||
|
||||
{{ .Ident "group" | .Into .Entity.Group }}, |
||||
{{ .Ident "group_version" | .Into .Entity.GroupVersion }}, |
||||
{{ .Ident "resource" | .Into .Entity.Resource }}, |
||||
{{ .Ident "namespace" | .Into .Entity.Namespace }}, |
||||
{{ .Ident "name" | .Into .Entity.Name }}, |
||||
|
||||
{{ .Ident "folder" | .Into .Entity.Folder }}, |
||||
|
||||
{{ .Ident "meta" | .Into .Entity.Meta }}, |
||||
{{ .Ident "body" | .Into .Entity.Body }}, |
||||
{{ .Ident "status" | .Into .Entity.Status }}, |
||||
|
||||
{{ .Ident "size" | .Into .Entity.Size }}, |
||||
{{ .Ident "etag" | .Into .Entity.ETag }}, |
||||
|
||||
{{ .Ident "created_at" | .Into .Entity.CreatedAt }}, |
||||
{{ .Ident "created_by" | .Into .Entity.CreatedBy }}, |
||||
{{ .Ident "updated_at" | .Into .Entity.UpdatedAt }}, |
||||
{{ .Ident "updated_by" | .Into .Entity.UpdatedBy }}, |
||||
|
||||
{{ .Ident "origin" | .Into .Entity.Origin.Source }}, |
||||
{{ .Ident "origin_key" | .Into .Entity.Origin.Key }}, |
||||
{{ .Ident "origin_ts" | .Into .Entity.Origin.Time }}, |
||||
|
||||
{{ .Ident "title" | .Into .Entity.Title }}, |
||||
{{ .Ident "slug" | .Into .Entity.Slug }}, |
||||
{{ .Ident "description" | .Into .Entity.Description }}, |
||||
|
||||
{{ .Ident "message" | .Into .Entity.Message }}, |
||||
{{ .Ident "labels" | .Into .Entity.Labels }}, |
||||
{{ .Ident "fields" | .Into .Entity.Fields }}, |
||||
{{ .Ident "errors" | .Into .Entity.Errors }}, |
||||
|
||||
{{ .Ident "action" | .Into .Entity.Action }} |
||||
|
||||
FROM |
||||
{{ if gt .ResourceVersion 0 }} |
||||
{{ .Ident "entity_history" }} |
||||
{{ else }} |
||||
{{ .Ident "entity" }} |
||||
{{ end }} |
||||
|
||||
WHERE 1 = 1 |
||||
AND {{ .Ident "namespace" }} = {{ .Arg .Key.Namespace }} |
||||
AND {{ .Ident "group" }} = {{ .Arg .Key.Group }} |
||||
AND {{ .Ident "resource" }} = {{ .Arg .Key.Resource }} |
||||
AND {{ .Ident "name" }} = {{ .Arg .Key.Name }} |
||||
|
||||
{{/* |
||||
Resource versions work like snapshots at the kind level. Thus, a request |
||||
to retrieve a specific resource version should be interpreted as asking |
||||
for a resource as of how it existed at that point in time. This is why we |
||||
request matching entities with at most the provided resource version, and |
||||
return only the one with the highest resource version. In the case of not |
||||
specifying a resource version (i.e. resource version zero), it is |
||||
interpreted as the latest version of the given entity, thus we instead |
||||
query the "entity" table (which holds only the latest version of |
||||
non-deleted entities) and we don't need to specify anything else. The |
||||
"entity" table has a unique constraint on (namespace, group, resource, |
||||
name), so we're guaranteed to have at most one matching row. |
||||
*/}} |
||||
{{ if gt .ResourceVersion 0 }} |
||||
AND {{ .Ident "resource_version" }} <= {{ .Arg .ResourceVersion }} |
||||
|
||||
ORDER BY {{ .Ident "resource_version" }} DESC |
||||
LIMIT 1 |
||||
{{ end }} |
||||
|
||||
{{ if .SelectForUpdate }} |
||||
{{ .SelectFor "UPDATE" }} |
||||
{{ end }} |
||||
; |
@ -0,0 +1,53 @@ |
||||
SELECT |
||||
e.{{ .Ident "guid" | .Into .Entity.Guid }}, |
||||
e.{{ .Ident "resource_version" | .Into .Entity.ResourceVersion }}, |
||||
|
||||
e.{{ .Ident "key" | .Into .Entity.Key }}, |
||||
|
||||
e.{{ .Ident "group" | .Into .Entity.Group }}, |
||||
e.{{ .Ident "group_version" | .Into .Entity.GroupVersion }}, |
||||
e.{{ .Ident "resource" | .Into .Entity.Resource }}, |
||||
e.{{ .Ident "namespace" | .Into .Entity.Namespace }}, |
||||
e.{{ .Ident "name" | .Into .Entity.Name }}, |
||||
|
||||
e.{{ .Ident "folder" | .Into .Entity.Folder }}, |
||||
|
||||
e.{{ .Ident "meta" | .Into .Entity.Meta }}, |
||||
e.{{ .Ident "body" | .Into .Entity.Body }}, |
||||
e.{{ .Ident "status" | .Into .Entity.Status }}, |
||||
|
||||
e.{{ .Ident "size" | .Into .Entity.Size }}, |
||||
e.{{ .Ident "etag" | .Into .Entity.ETag }}, |
||||
|
||||
e.{{ .Ident "created_at" | .Into .Entity.CreatedAt }}, |
||||
e.{{ .Ident "created_by" | .Into .Entity.CreatedBy }}, |
||||
e.{{ .Ident "updated_at" | .Into .Entity.UpdatedAt }}, |
||||
e.{{ .Ident "updated_by" | .Into .Entity.UpdatedBy }}, |
||||
|
||||
e.{{ .Ident "origin" | .Into .Entity.Origin.Source }}, |
||||
e.{{ .Ident "origin_key" | .Into .Entity.Origin.Key }}, |
||||
e.{{ .Ident "origin_ts" | .Into .Entity.Origin.Time }}, |
||||
|
||||
e.{{ .Ident "title" | .Into .Entity.Title }}, |
||||
e.{{ .Ident "slug" | .Into .Entity.Slug }}, |
||||
e.{{ .Ident "description" | .Into .Entity.Description }}, |
||||
|
||||
e.{{ .Ident "message" | .Into .Entity.Message }}, |
||||
e.{{ .Ident "labels" | .Into .Entity.Labels }}, |
||||
e.{{ .Ident "fields" | .Into .Entity.Fields }}, |
||||
e.{{ .Ident "errors" | .Into .Entity.Errors }}, |
||||
|
||||
e.{{ .Ident "action" | .Into .Entity.Action }} |
||||
|
||||
FROM |
||||
{{ .Ident "entity_ref" }} AS r |
||||
INNER JOIN |
||||
{{ .Ident "entity" }} AS e |
||||
ON r.{{ .Ident "guid" }} = e.{{ .Ident "guid" }} |
||||
|
||||
WHERE 1 = 1 |
||||
AND r.{{ .Ident "namespace" }} = {{ .Arg .Request.Namespace }} |
||||
AND r.{{ .Ident "group" }} = {{ .Arg .Request.Group }} |
||||
AND r.{{ .Ident "resource" }} = {{ .Arg .Request.Resource }} |
||||
AND r.{{ .Ident "resolved_to" }} = {{ .Arg .Request.Name }} |
||||
; |
@ -0,0 +1,34 @@ |
||||
UPDATE {{ .Ident "entity" }} SET |
||||
{{ .Ident "resource_version" }} = {{ .Arg .Entity.ResourceVersion }}, |
||||
|
||||
{{ .Ident "group_version" }} = {{ .Arg .Entity.GroupVersion }}, |
||||
|
||||
{{ .Ident "folder" }} = {{ .Arg .Entity.Folder }}, |
||||
|
||||
{{ .Ident "meta" }} = {{ .Arg .Entity.Meta }}, |
||||
{{ .Ident "body" }} = {{ .Arg .Entity.Body }}, |
||||
{{ .Ident "status" }} = {{ .Arg .Entity.Status }}, |
||||
|
||||
{{ .Ident "size" }} = {{ .Arg .Entity.Size }}, |
||||
{{ .Ident "etag" }} = {{ .Arg .Entity.ETag }}, |
||||
|
||||
{{ .Ident "updated_at" }} = {{ .Arg .Entity.UpdatedAt }}, |
||||
{{ .Ident "updated_by" }} = {{ .Arg .Entity.UpdatedBy }}, |
||||
|
||||
{{ .Ident "origin" }} = {{ .Arg .Entity.Origin.Source }}, |
||||
{{ .Ident "origin_key" }} = {{ .Arg .Entity.Origin.Key }}, |
||||
{{ .Ident "origin_ts" }} = {{ .Arg .Entity.Origin.Time }}, |
||||
|
||||
{{ .Ident "title" }} = {{ .Arg .Entity.Title }}, |
||||
{{ .Ident "slug" }} = {{ .Arg .Entity.Slug }}, |
||||
{{ .Ident "description" }} = {{ .Arg .Entity.Description }}, |
||||
|
||||
{{ .Ident "message" }} = {{ .Arg .Entity.Message }}, |
||||
{{ .Ident "labels" }} = {{ .Arg .Entity.Labels }}, |
||||
{{ .Ident "fields" }} = {{ .Arg .Entity.Fields }}, |
||||
{{ .Ident "errors" }} = {{ .Arg .Entity.Errors }}, |
||||
|
||||
{{ .Ident "action" }} = {{ .Arg .Entity.Action }} |
||||
|
||||
WHERE {{ .Ident "guid" }} = {{ .Arg .Entity.Guid }} |
||||
; |
@ -0,0 +1,7 @@ |
||||
UPDATE {{ .Ident "kind_version" }} |
||||
SET {{ .Ident "resource_version" }} = {{ .Arg .ResourceVersion }} + 1 |
||||
WHERE 1 = 1 |
||||
AND {{ .Ident "group" }} = {{ .Arg .Group }} |
||||
AND {{ .Ident "resource" }} = {{ .Arg .Resource }} |
||||
AND {{ .Ident "resource_version" }} = {{ .Arg .ResourceVersion }} |
||||
; |
@ -0,0 +1,13 @@ |
||||
INSERT INTO {{ .Ident "kind_version" }} |
||||
( |
||||
{{ .Ident "group" }}, |
||||
{{ .Ident "resource" }}, |
||||
{{ .Ident "resource_version" }} |
||||
) |
||||
|
||||
VALUES ( |
||||
{{ .Arg .Group }}, |
||||
{{ .Arg .Resource }}, |
||||
1 |
||||
) |
||||
; |
@ -0,0 +1,7 @@ |
||||
SELECT {{ .Ident "resource_version" | .Into .ResourceVersion }} |
||||
FROM {{ .Ident "kind_version" }} |
||||
WHERE 1 = 1 |
||||
AND {{ .Ident "group" }} = {{ .Arg .Group }} |
||||
AND {{ .Ident "resource" }} = {{ .Arg .Resource }} |
||||
{{ .SelectFor "UPDATE" }} |
||||
; |
@ -0,0 +1,164 @@ |
||||
package sqlstash |
||||
|
||||
import ( |
||||
"embed" |
||||
"fmt" |
||||
"text/template" |
||||
|
||||
"github.com/grafana/grafana/pkg/services/store/entity" |
||||
"github.com/grafana/grafana/pkg/services/store/entity/sqlstash/sqltemplate" |
||||
) |
||||
|
||||
// Templates.
|
||||
var ( |
||||
//go:embed data
|
||||
templatesFs embed.FS |
||||
|
||||
// all templates
|
||||
templates = template.Must(template.ParseFS(templatesFs, `data/*.sql`)) |
||||
|
||||
sqlEntityDelete = getTemplate("entity_delete.sql") |
||||
sqlEntityInsert = getTemplate("entity_insert.sql") |
||||
sqlEntityListFolderElements = getTemplate("entity_list_folder_elements.sql") |
||||
sqlEntityUpdate = getTemplate("entity_update.sql") |
||||
sqlEntityRead = getTemplate("entity_read.sql") |
||||
|
||||
sqlEntityFolderInsert = getTemplate("entity_folder_insert.sql") |
||||
|
||||
sqlEntityRefFind = getTemplate("entity_ref_find.sql") |
||||
|
||||
sqlEntityLabelsDelete = getTemplate("entity_labels_delete.sql") |
||||
sqlEntityLabelsInsert = getTemplate("entity_labels_insert.sql") |
||||
|
||||
sqlKindVersionInc = getTemplate("kind_version_inc.sql") |
||||
sqlKindVersionInsert = getTemplate("kind_version_insert.sql") |
||||
sqlKindVersionLock = getTemplate("kind_version_lock.sql") |
||||
) |
||||
|
||||
func getTemplate(filename string) *template.Template { |
||||
if t := templates.Lookup(filename); t != nil { |
||||
return t |
||||
} |
||||
panic(fmt.Sprintf("template file not found: %s", filename)) |
||||
} |
||||
|
||||
type sqlEntityFolderInsertRequest struct { |
||||
*sqltemplate.SQLTemplate |
||||
Items []*sqlEntityFolderInsertRequestItem |
||||
} |
||||
|
||||
type sqlEntityFolderInsertRequestItem struct { |
||||
GUID string |
||||
Namespace string |
||||
UID string |
||||
SlugPath string |
||||
JS string |
||||
Depth int32 |
||||
Left int32 |
||||
Right int32 |
||||
Detached bool |
||||
} |
||||
|
||||
type sqlEntityRefFindRequest struct { |
||||
*sqltemplate.SQLTemplate |
||||
Request *entity.ReferenceRequest |
||||
Entity *withSerialized |
||||
} |
||||
|
||||
type sqlEntityLabelsInsertRequest struct { |
||||
*sqltemplate.SQLTemplate |
||||
GUID string |
||||
Labels map[string]string |
||||
} |
||||
|
||||
type sqlEntityLabelsDeleteRequest struct { |
||||
*sqltemplate.SQLTemplate |
||||
GUID string |
||||
KeepLabels []string |
||||
} |
||||
|
||||
type sqlKindVersionLockRequest struct { |
||||
*sqltemplate.SQLTemplate |
||||
Group string |
||||
GroupVersion string |
||||
Resource string |
||||
ResourceVersion int64 |
||||
} |
||||
|
||||
type sqlKindVersionIncRequest struct { |
||||
*sqltemplate.SQLTemplate |
||||
Group string |
||||
GroupVersion string |
||||
Resource string |
||||
ResourceVersion int64 |
||||
} |
||||
|
||||
type sqlKindVersionInsertRequest struct { |
||||
*sqltemplate.SQLTemplate |
||||
Group string |
||||
GroupVersion string |
||||
Resource string |
||||
} |
||||
|
||||
type sqlEntityListFolderElementsRequest struct { |
||||
*sqltemplate.SQLTemplate |
||||
Group string |
||||
Resource string |
||||
Namespace string |
||||
FolderInfo *folderInfo |
||||
} |
||||
|
||||
type sqlEntityReadRequest struct { |
||||
*sqltemplate.SQLTemplate |
||||
Key *entity.Key |
||||
ResourceVersion int64 |
||||
SelectForUpdate bool |
||||
Entity *withSerialized |
||||
} |
||||
|
||||
type sqlEntityDeleteRequest struct { |
||||
*sqltemplate.SQLTemplate |
||||
Key *entity.Key |
||||
} |
||||
|
||||
type sqlEntityInsertRequest struct { |
||||
*sqltemplate.SQLTemplate |
||||
Entity *withSerialized |
||||
|
||||
// TableEntity, when true, means we will insert into table "entity", and
|
||||
// into table "entity_history" otherwise.
|
||||
TableEntity bool |
||||
} |
||||
|
||||
type sqlEntityUpdateRequest struct { |
||||
*sqltemplate.SQLTemplate |
||||
Entity *withSerialized |
||||
} |
||||
|
||||
// withSerialized provides access to the wire Entiity DTO as well as the
|
||||
// serialized version of some of its fields suitable to be read from or written
|
||||
// to the database.
|
||||
type withSerialized struct { |
||||
*entity.Entity |
||||
|
||||
Labels []byte |
||||
Fields []byte |
||||
Errors []byte |
||||
} |
||||
|
||||
// TODO: remove once we start using these symbols. Prevents `unused` linter
|
||||
// until the next PR.
|
||||
var ( |
||||
_, _, _ = sqlEntityDelete, sqlEntityInsert, sqlEntityListFolderElements |
||||
_, _, _ = sqlEntityUpdate, sqlEntityRead, sqlEntityFolderInsert |
||||
_, _, _ = sqlEntityRefFind, sqlEntityLabelsDelete, sqlEntityLabelsInsert |
||||
_, _, _ = sqlKindVersionInc, sqlKindVersionInsert, sqlKindVersionLock |
||||
_, _ = sqlEntityFolderInsertRequest{}, sqlEntityFolderInsertRequestItem{} |
||||
_, _ = sqlEntityRefFindRequest{}, sqlEntityLabelsInsertRequest{} |
||||
_, _ = sqlEntityLabelsInsertRequest{}, sqlEntityLabelsDeleteRequest{} |
||||
_, _ = sqlKindVersionLockRequest{}, sqlKindVersionIncRequest{} |
||||
_, _ = sqlKindVersionInsertRequest{}, sqlEntityListFolderElementsRequest{} |
||||
_, _ = sqlEntityReadRequest{}, sqlEntityDeleteRequest{} |
||||
_, _ = sqlEntityInsertRequest{}, sqlEntityUpdateRequest{} |
||||
_ = withSerialized{} |
||||
) |
Loading…
Reference in new issue