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 table
pull/88188/head
Diego Augusto Molina 1 year ago committed by GitHub
parent 6744dae806
commit 8b02b6b76a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 1
      go.mod
  2. 3
      go.sum
  3. 59
      pkg/services/store/entity/db/dbimpl/db.go
  4. 54
      pkg/services/store/entity/db/dbimpl/db_test.go
  5. 18
      pkg/services/store/entity/db/dbimpl/dbimpl.go
  6. 14
      pkg/services/store/entity/db/migrations/entity_store_mig.go
  7. 46
      pkg/services/store/entity/db/service.go
  8. 7
      pkg/services/store/entity/sqlstash/data/entity_delete.sql
  9. 35
      pkg/services/store/entity/sqlstash/data/entity_folder_insert.sql
  10. 93
      pkg/services/store/entity/sqlstash/data/entity_insert.sql
  11. 18
      pkg/services/store/entity/sqlstash/data/entity_labels_delete.sql
  12. 29
      pkg/services/store/entity/sqlstash/data/entity_labels_insert.sql
  13. 14
      pkg/services/store/entity/sqlstash/data/entity_list_folder_elements.sql
  14. 78
      pkg/services/store/entity/sqlstash/data/entity_read.sql
  15. 53
      pkg/services/store/entity/sqlstash/data/entity_ref_find.sql
  16. 34
      pkg/services/store/entity/sqlstash/data/entity_update.sql
  17. 7
      pkg/services/store/entity/sqlstash/data/kind_version_inc.sql
  18. 13
      pkg/services/store/entity/sqlstash/data/kind_version_insert.sql
  19. 7
      pkg/services/store/entity/sqlstash/data/kind_version_lock.sql
  20. 164
      pkg/services/store/entity/sqlstash/queries.go

@ -224,6 +224,7 @@ require (
github.com/Azure/go-autorest/tracing v0.6.0 // indirect
github.com/Azure/go-ntlmssp v0.0.0-20220621081337-cb9428e4ac1e // indirect
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.1 // indirect
github.com/DATA-DOG/go-sqlmock v1.5.2 // @grafana/grafana-search-and-storage
github.com/FZambia/eagle v0.1.0 // indirect
github.com/JohnCGriffin/overflow v0.0.0-20211019200055-46fa312c352c // indirect
github.com/Masterminds/goutils v1.1.1 // indirect

@ -1284,6 +1284,8 @@ github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbi
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/Code-Hex/go-generics-cache v1.3.1 h1:i8rLwyhoyhaerr7JpjtYjJZUcCbWOdiYO3fZXLiEC4g=
github.com/Code-Hex/go-generics-cache v1.3.1/go.mod h1:qxcC9kRVrct9rHeiYpFWSoW1vxyillCVzX13KZG8dl4=
github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU=
github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU=
github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
github.com/FZambia/eagle v0.1.0 h1:9gyX6x+xjoIfglgyPTcYm7dvY7FJ93us1QY5De4CyXA=
github.com/FZambia/eagle v0.1.0/go.mod h1:YjGSPVkQTNcVLfzEUQJNgW9ScPR0K4u/Ky0yeFa4oDA=
@ -2459,6 +2461,7 @@ github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/kisielk/sqlstruct v0.0.0-20201105191214-5f3e10d3ab46/go.mod h1:yyMNCyc/Ib3bDTKd379tNMpB/7/H5TjM2Y9QJ5THLbE=
github.com/klauspost/asmfmt v1.3.2 h1:4Ri7ox3EwapiOjCki+hw14RyKk201CN4rzyCJRFLpK4=
github.com/klauspost/asmfmt v1.3.2/go.mod h1:AG8TuvYojzulgDAMCnYn50l/5QV3Bs/tp6j0HLHbNSE=
github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=

@ -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)
})
}

@ -4,6 +4,10 @@ import (
"fmt"
"github.com/dlmiddlecote/sqlstats"
"github.com/jmoiron/sqlx"
"github.com/prometheus/client_golang/prometheus"
"xorm.io/xorm"
"github.com/grafana/grafana/pkg/infra/db"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/infra/tracing"
@ -12,9 +16,6 @@ import (
entitydb "github.com/grafana/grafana/pkg/services/store/entity/db"
"github.com/grafana/grafana/pkg/services/store/entity/db/migrations"
"github.com/grafana/grafana/pkg/setting"
"github.com/jmoiron/sqlx"
"github.com/prometheus/client_golang/prometheus"
"xorm.io/xorm"
)
var _ entitydb.EntityDBInterface = (*EntityDB)(nil)
@ -128,3 +129,14 @@ func (db *EntityDB) GetSession() (*session.SessionDB, error) {
func (db *EntityDB) GetCfg() *setting.Cfg {
return db.cfg
}
func (db *EntityDB) GetDB() (entitydb.DB, error) {
engine, err := db.GetEngine()
if err != nil {
return nil, err
}
ret := NewDB(engine.DB().DB, engine.Dialect().DriverName())
return ret, nil
}

@ -187,6 +187,20 @@ func initEntityTables(mg *migrator.Migrator) string {
},
})
tables = append(tables, migrator.Table{
Name: "kind_version",
Columns: []*migrator.Column{
{Name: "group", Type: migrator.DB_NVarchar, Length: 190, Nullable: false},
{Name: "resource", Type: migrator.DB_NVarchar, Length: 190, Nullable: false},
{Name: "resource_version", Type: migrator.DB_BigInt, Nullable: false},
{Name: "created_at", Type: migrator.DB_BigInt, Nullable: false},
{Name: "updated_at", Type: migrator.DB_BigInt, Nullable: false},
},
Indices: []*migrator.Index{
{Cols: []string{"group", "resource"}, Type: migrator.UniqueIndex},
},
})
// Initialize all tables
for t := range tables {
mg.AddMigration("drop table "+tables[t].Name, migrator.NewDropTableMigration(tables[t].Name))

@ -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…
Cancel
Save