Storage: Show history+trash using the list command (#99009)
Co-authored-by: Stephanie Hingtgen <stephanie.hingtgen@grafana.com>
This commit is contained in:
co-authored by
Stephanie Hingtgen
parent
67252dfa46
commit
356b32008b
@@ -34,9 +34,10 @@ type Backend interface {
|
||||
}
|
||||
|
||||
type BackendOptions struct {
|
||||
DBProvider db.DBProvider
|
||||
Tracer trace.Tracer
|
||||
PollingInterval time.Duration
|
||||
DBProvider db.DBProvider
|
||||
Tracer trace.Tracer
|
||||
PollingInterval time.Duration
|
||||
SkipDataMigration bool
|
||||
}
|
||||
|
||||
func NewBackend(opts BackendOptions) (Backend, error) {
|
||||
@@ -53,12 +54,13 @@ func NewBackend(opts BackendOptions) (Backend, error) {
|
||||
pollingInterval = defaultPollingInterval
|
||||
}
|
||||
return &backend{
|
||||
done: ctx.Done(),
|
||||
cancel: cancel,
|
||||
log: log.New("sql-resource-server"),
|
||||
tracer: opts.Tracer,
|
||||
dbProvider: opts.DBProvider,
|
||||
pollingInterval: pollingInterval,
|
||||
done: ctx.Done(),
|
||||
cancel: cancel,
|
||||
log: log.New("sql-resource-server"),
|
||||
tracer: opts.Tracer,
|
||||
dbProvider: opts.DBProvider,
|
||||
pollingInterval: pollingInterval,
|
||||
skipDataMigration: opts.SkipDataMigration,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -74,9 +76,10 @@ type backend struct {
|
||||
tracer trace.Tracer
|
||||
|
||||
// database
|
||||
dbProvider db.DBProvider
|
||||
db db.DB
|
||||
dialect sqltemplate.Dialect
|
||||
dbProvider db.DBProvider
|
||||
db db.DB
|
||||
dialect sqltemplate.Dialect
|
||||
skipDataMigration bool
|
||||
|
||||
// watch streaming
|
||||
//stream chan *resource.WatchEvent
|
||||
@@ -103,6 +106,12 @@ func (b *backend) initLocked(ctx context.Context) error {
|
||||
return fmt.Errorf("no dialect for driver %q", driverName)
|
||||
}
|
||||
|
||||
// Process any data manipulation migrations
|
||||
err = b.runStartupDataMigrations(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return b.db.PingContext(ctx)
|
||||
}
|
||||
|
||||
@@ -477,13 +486,17 @@ func (b *backend) ReadResource(ctx context.Context, req *resource.ReadRequest) *
|
||||
}
|
||||
|
||||
func (b *backend) ListIterator(ctx context.Context, req *resource.ListRequest, cb func(resource.ListIterator) error) (int64, error) {
|
||||
_, span := b.tracer.Start(ctx, tracePrefix+"List")
|
||||
ctx, span := b.tracer.Start(ctx, tracePrefix+"List")
|
||||
defer span.End()
|
||||
|
||||
if req.Options == nil || req.Options.Key.Group == "" || req.Options.Key.Resource == "" {
|
||||
return 0, fmt.Errorf("missing group or resource")
|
||||
}
|
||||
|
||||
if req.Source != resource.ListRequest_STORE {
|
||||
return b.getHistory(ctx, req, cb)
|
||||
}
|
||||
|
||||
// TODO: think about how to handler VersionMatch. We should be able to use latest for the first page (only).
|
||||
|
||||
// TODO: add support for RemainingItemCount
|
||||
@@ -647,6 +660,48 @@ func (b *backend) listAtRevision(ctx context.Context, req *resource.ListRequest,
|
||||
return iter.listRV, err
|
||||
}
|
||||
|
||||
// listLatest fetches the resources from the resource table.
|
||||
func (b *backend) getHistory(ctx context.Context, req *resource.ListRequest, cb func(resource.ListIterator) error) (int64, error) {
|
||||
listReq := sqlGetHistoryRequest{
|
||||
SQLTemplate: sqltemplate.New(b.dialect),
|
||||
Key: req.Options.Key,
|
||||
Trash: req.Source == resource.ListRequest_TRASH,
|
||||
}
|
||||
|
||||
iter := &listIter{}
|
||||
if req.NextPageToken != "" {
|
||||
continueToken, err := GetContinueToken(req.NextPageToken)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("get continue token: %w", err)
|
||||
}
|
||||
listReq.StartRV = continueToken.ResourceVersion
|
||||
}
|
||||
|
||||
err := b.db.WithTx(ctx, ReadCommittedRO, func(ctx context.Context, tx db.Tx) error {
|
||||
var err error
|
||||
iter.listRV, err = fetchLatestRV(ctx, tx, b.dialect, req.Options.Key.Group, req.Options.Key.Resource)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rows, err := dbutil.QueryRows(ctx, tx, sqlResourceHistoryGet, listReq)
|
||||
if rows != nil {
|
||||
defer func() {
|
||||
if err := rows.Close(); err != nil {
|
||||
b.log.Warn("listLatest error closing rows", "error", err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
iter.rows = rows
|
||||
return cb(iter)
|
||||
})
|
||||
return iter.listRV, err
|
||||
}
|
||||
|
||||
func (b *backend) WatchWriteEvents(ctx context.Context) (<-chan *resource.WrittenEvent, error) {
|
||||
// Get the latest RV
|
||||
since, err := b.listLatestRVs(ctx)
|
||||
|
||||
@@ -62,7 +62,10 @@ func setupBackendTest(t *testing.T) (testBackend, context.Context) {
|
||||
|
||||
ctx := testutil.NewDefaultTestContext(t)
|
||||
dbp := test.NewDBProviderMatchWords(t)
|
||||
b, err := NewBackend(BackendOptions{DBProvider: dbp})
|
||||
b, err := NewBackend(BackendOptions{
|
||||
DBProvider: dbp,
|
||||
SkipDataMigration: true, // Calling migrations makes startup SQL calls (avoid the mock)
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, b)
|
||||
|
||||
@@ -109,7 +112,7 @@ func TestBackend_Init(t *testing.T) {
|
||||
|
||||
ctx := testutil.NewDefaultTestContext(t)
|
||||
dbp := test.NewDBProviderWithPing(t)
|
||||
b, err := NewBackend(BackendOptions{DBProvider: dbp})
|
||||
b, err := NewBackend(BackendOptions{DBProvider: dbp, SkipDataMigration: true})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, b)
|
||||
|
||||
@@ -166,7 +169,7 @@ func TestBackend_Init(t *testing.T) {
|
||||
|
||||
ctx := testutil.NewDefaultTestContext(t)
|
||||
dbp := test.NewDBProviderWithPing(t)
|
||||
b, err := NewBackend(BackendOptions{DBProvider: dbp})
|
||||
b, err := NewBackend(BackendOptions{DBProvider: dbp, SkipDataMigration: true})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, dbp.DB)
|
||||
|
||||
@@ -182,7 +185,7 @@ func TestBackend_IsHealthy(t *testing.T) {
|
||||
|
||||
ctx := testutil.NewDefaultTestContext(t)
|
||||
dbp := test.NewDBProviderWithPing(t)
|
||||
b, err := NewBackend(BackendOptions{DBProvider: dbp})
|
||||
b, err := NewBackend(BackendOptions{DBProvider: dbp, SkipDataMigration: true})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, dbp.DB)
|
||||
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
SELECT
|
||||
{{ .Ident "guid" }},
|
||||
{{ .Ident "value" }},
|
||||
{{ .Ident "group" }},
|
||||
{{ .Ident "resource" }},
|
||||
{{ .Ident "previous_resource_version" }}
|
||||
FROM {{ .Ident "resource_history" }}
|
||||
WHERE {{ .Ident "action" }} = 3
|
||||
AND {{ .Ident "value" }} LIKE {{ .Arg .MarkerQuery }};
|
||||
@@ -0,0 +1,5 @@
|
||||
SELECT {{ .Ident "value" }}
|
||||
FROM {{ .Ident "resource_history" }}
|
||||
WHERE {{ .Ident "group" }} = {{ .Arg .Group }}
|
||||
AND {{ .Ident "resource" }} = {{ .Arg .Resource }}
|
||||
AND {{ .Ident "resource_version" }} = {{ .Arg .RV }};
|
||||
@@ -0,0 +1,4 @@
|
||||
UPDATE {{ .Ident "resource_history" }}
|
||||
SET {{ .Ident "value" }} = {{ .Arg .Value }}
|
||||
WHERE {{ .Ident "guid" }} = {{ .Arg .GUID }}
|
||||
;
|
||||
@@ -0,0 +1,4 @@
|
||||
DELETE FROM {{ .Ident "resource_history" }}
|
||||
WHERE 1 = 1
|
||||
AND {{ .Ident "guid" }} = {{ .Arg .GUID }}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
SELECT
|
||||
{{ .Ident "resource_version" }},
|
||||
{{ .Ident "namespace" }},
|
||||
{{ .Ident "name" }},
|
||||
{{ .Ident "folder" }},
|
||||
{{ .Ident "value" }}
|
||||
FROM {{ .Ident "resource_history" }}
|
||||
WHERE 1 = 1
|
||||
AND {{ .Ident "namespace" }} = {{ .Arg .Key.Namespace }}
|
||||
AND {{ .Ident "group" }} = {{ .Arg .Key.Group }}
|
||||
AND {{ .Ident "resource" }} = {{ .Arg .Key.Resource }}
|
||||
{{ if .Key.Name }}
|
||||
AND {{ .Ident "name" }} = {{ .Arg .Key.Name }}
|
||||
{{ end }}
|
||||
{{ if .Trash }}
|
||||
AND {{ .Ident "action" }} = 3
|
||||
{{ end }}
|
||||
{{ if (gt .StartRV 0) }}
|
||||
AND {{ .Ident "resource_version" }} > {{ .Arg .StartRV }}
|
||||
{{ end }}
|
||||
ORDER BY resource_version DESC
|
||||
@@ -0,0 +1,133 @@
|
||||
package sql
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
|
||||
"github.com/grafana/grafana/pkg/apimachinery/utils"
|
||||
"github.com/grafana/grafana/pkg/storage/unified/sql/db"
|
||||
"github.com/grafana/grafana/pkg/storage/unified/sql/dbutil"
|
||||
"github.com/grafana/grafana/pkg/storage/unified/sql/sqltemplate"
|
||||
)
|
||||
|
||||
// This runs functions before the server is returned as healthy
|
||||
func (b *backend) runStartupDataMigrations(ctx context.Context) error {
|
||||
if b.skipDataMigration {
|
||||
return nil
|
||||
}
|
||||
|
||||
type migrateRow struct {
|
||||
GUID string
|
||||
Marker *unstructured.Unstructured
|
||||
Group string
|
||||
Resource string
|
||||
PreviousRV int64
|
||||
}
|
||||
|
||||
// Migrate DeletedMarker to regular resource
|
||||
err := b.db.WithTx(ctx, ReadCommitted, func(ctx context.Context, tx db.Tx) error {
|
||||
req := &sqlMigrationQueryRequest{
|
||||
SQLTemplate: sqltemplate.New(b.dialect),
|
||||
MarkerQuery: `{"kind":"DeletedMarker"%`,
|
||||
}
|
||||
|
||||
// 1. Find rows with the existing deletion marker
|
||||
rows, err := dbutil.QueryRows(ctx, tx, sqlMigratorGetDeletionMarkers, req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
migrateRows := make([]migrateRow, 0)
|
||||
for rows.Next() {
|
||||
item := migrateRow{Marker: &unstructured.Unstructured{}}
|
||||
err = rows.Scan(&item.GUID, &req.Value, &item.Group, &item.Resource, &item.PreviousRV)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = item.Marker.UnmarshalJSON([]byte(req.Value))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
migrateRows = append(migrateRows, item)
|
||||
}
|
||||
err = rows.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, item := range migrateRows {
|
||||
// 2. Load the previous value referenced by that marker
|
||||
req := &sqlMigrationQueryRequest{
|
||||
SQLTemplate: sqltemplate.New(b.dialect),
|
||||
Group: item.Group,
|
||||
Resource: item.Resource,
|
||||
RV: item.PreviousRV,
|
||||
GUID: item.GUID,
|
||||
}
|
||||
rows, err = dbutil.QueryRows(ctx, tx, sqlMigratorGetValueFromRV, req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if rows.Next() {
|
||||
err = rows.Scan(&req.Value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
err = rows.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.Reset()
|
||||
|
||||
if len(req.Value) > 0 {
|
||||
previous := &unstructured.Unstructured{}
|
||||
err = previous.UnmarshalJSON([]byte(req.Value))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 3. Prepare a new payload
|
||||
metaMarker, _ := utils.MetaAccessor(item.Marker)
|
||||
metaPrev, _ := utils.MetaAccessor(previous)
|
||||
metaPrev.SetDeletionTimestamp(metaMarker.GetDeletionTimestamp())
|
||||
metaPrev.SetFinalizers(nil)
|
||||
metaPrev.SetManagedFields(nil)
|
||||
metaPrev.SetGeneration(utils.DeletedGeneration)
|
||||
metaPrev.SetAnnotation(utils.AnnoKeyKubectlLastAppliedConfig, "") // clears it
|
||||
ts, _ := metaMarker.GetUpdatedTimestamp()
|
||||
if ts != nil {
|
||||
metaPrev.SetUpdatedTimestamp(ts)
|
||||
}
|
||||
buff, err := previous.MarshalJSON()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.Value = string(buff)
|
||||
|
||||
// 4. Update the SQL row with this new value
|
||||
b.log.Info("Migrating DeletedMarker", "guid", req.GUID, "group", req.Group, "resource", req.Resource)
|
||||
_, err = dbutil.Exec(ctx, tx, sqlMigratorUpdateValueWithGUID, req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
// 5. If the previous version is missing, we delete it -- there is nothing to help us restore anyway
|
||||
b.log.Warn("Removing orphan deletion marker", "guid", req.GUID, "group", req.Group, "resource", req.Resource)
|
||||
_, err = dbutil.Exec(ctx, tx, sqlResourceHistoryDelete, &sqlResourceHistoryDeleteRequest{
|
||||
SQLTemplate: sqltemplate.New(b.dialect),
|
||||
GUID: req.GUID,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return err
|
||||
}
|
||||
@@ -42,6 +42,8 @@ var (
|
||||
sqlResoureceHistoryUpdateUid = mustTemplate("resource_history_update_uid.sql")
|
||||
sqlResourceHistoryInsert = mustTemplate("resource_history_insert.sql")
|
||||
sqlResourceHistoryPoll = mustTemplate("resource_history_poll.sql")
|
||||
sqlResourceHistoryGet = mustTemplate("resource_history_get.sql")
|
||||
sqlResourceHistoryDelete = mustTemplate("resource_history_delete.sql")
|
||||
|
||||
// sqlResourceLabelsInsert = mustTemplate("resource_labels_insert.sql")
|
||||
sqlResourceVersionGet = mustTemplate("resource_version_get.sql")
|
||||
@@ -51,6 +53,10 @@ var (
|
||||
|
||||
sqlResourceBlobInsert = mustTemplate("resource_blob_insert.sql")
|
||||
sqlResourceBlobQuery = mustTemplate("resource_blob_query.sql")
|
||||
|
||||
sqlMigratorGetDeletionMarkers = mustTemplate("migrator_get_deletion_markers.sql")
|
||||
sqlMigratorGetValueFromRV = mustTemplate("migrator_get_value_from_rv.sql")
|
||||
sqlMigratorUpdateValueWithGUID = mustTemplate("migrator_update_value_with_guid.sql")
|
||||
)
|
||||
|
||||
// TxOptions.
|
||||
@@ -197,6 +203,27 @@ func (r sqlResourceHistoryListRequest) Results() (*resource.ResourceWrapper, err
|
||||
}, nil
|
||||
}
|
||||
|
||||
type sqlResourceHistoryDeleteRequest struct {
|
||||
sqltemplate.SQLTemplate
|
||||
GUID string
|
||||
// TODO, add other constraints
|
||||
}
|
||||
|
||||
func (r *sqlResourceHistoryDeleteRequest) Validate() error {
|
||||
return nil // TODO
|
||||
}
|
||||
|
||||
type sqlGetHistoryRequest struct {
|
||||
sqltemplate.SQLTemplate
|
||||
Key *resource.ResourceKey
|
||||
Trash bool // only deleted items
|
||||
StartRV int64 // from NextPageToken
|
||||
}
|
||||
|
||||
func (r sqlGetHistoryRequest) Validate() error {
|
||||
return nil // TODO
|
||||
}
|
||||
|
||||
// update resource history
|
||||
|
||||
type sqlResourceHistoryUpdateRequest struct {
|
||||
@@ -303,3 +330,19 @@ func (r *sqlResourceVersionListRequest) Results() (*groupResourceVersion, error)
|
||||
x := *r.groupResourceVersion
|
||||
return &x, nil
|
||||
}
|
||||
|
||||
// This holds all the variables used in migration queries
|
||||
|
||||
type sqlMigrationQueryRequest struct {
|
||||
sqltemplate.SQLTemplate
|
||||
MarkerQuery string //
|
||||
Group string
|
||||
Resource string
|
||||
RV int64
|
||||
GUID string
|
||||
Value string
|
||||
}
|
||||
|
||||
func (r sqlMigrationQueryRequest) Validate() error {
|
||||
return nil // TODO
|
||||
}
|
||||
|
||||
@@ -207,6 +207,46 @@ func TestUnifiedStorageQueries(t *testing.T) {
|
||||
},
|
||||
},
|
||||
|
||||
sqlResourceHistoryGet: {
|
||||
{
|
||||
Name: "read object history",
|
||||
Data: &sqlGetHistoryRequest{
|
||||
SQLTemplate: mocks.NewTestingSQLTemplate(),
|
||||
Key: &resource.ResourceKey{
|
||||
Namespace: "nn",
|
||||
Group: "gg",
|
||||
Resource: "rr",
|
||||
Name: "name",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "read trash",
|
||||
Data: &sqlGetHistoryRequest{
|
||||
SQLTemplate: mocks.NewTestingSQLTemplate(),
|
||||
Key: &resource.ResourceKey{
|
||||
Namespace: "nn",
|
||||
Group: "gg",
|
||||
Resource: "rr",
|
||||
},
|
||||
Trash: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "read trash second page",
|
||||
Data: &sqlGetHistoryRequest{
|
||||
SQLTemplate: mocks.NewTestingSQLTemplate(),
|
||||
Key: &resource.ResourceKey{
|
||||
Namespace: "nn",
|
||||
Group: "gg",
|
||||
Resource: "rr",
|
||||
},
|
||||
Trash: true,
|
||||
StartRV: 123456,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
sqlResourceVersionGet: {
|
||||
{
|
||||
Name: "single path",
|
||||
@@ -317,5 +357,44 @@ func TestUnifiedStorageQueries(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
sqlResourceHistoryDelete: {
|
||||
{
|
||||
Name: "guid",
|
||||
Data: &sqlResourceHistoryDeleteRequest{
|
||||
SQLTemplate: mocks.NewTestingSQLTemplate(),
|
||||
GUID: `xxxx`,
|
||||
},
|
||||
},
|
||||
},
|
||||
sqlMigratorGetDeletionMarkers: {
|
||||
{
|
||||
Name: "list",
|
||||
Data: &sqlMigrationQueryRequest{
|
||||
SQLTemplate: mocks.NewTestingSQLTemplate(),
|
||||
MarkerQuery: `{"kind":"DeletedMarker"%`,
|
||||
},
|
||||
},
|
||||
},
|
||||
sqlMigratorGetValueFromRV: {
|
||||
{
|
||||
Name: "get",
|
||||
Data: &sqlMigrationQueryRequest{
|
||||
SQLTemplate: mocks.NewTestingSQLTemplate(),
|
||||
Group: "ggg",
|
||||
Resource: "rrr",
|
||||
RV: 1234,
|
||||
},
|
||||
},
|
||||
},
|
||||
sqlMigratorUpdateValueWithGUID: {
|
||||
{
|
||||
Name: "update",
|
||||
Data: &sqlMigrationQueryRequest{
|
||||
SQLTemplate: mocks.NewTestingSQLTemplate(),
|
||||
GUID: "ggggg",
|
||||
Value: "{new value}",
|
||||
},
|
||||
},
|
||||
},
|
||||
}})
|
||||
}
|
||||
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
SELECT
|
||||
`guid`,
|
||||
`value`,
|
||||
`group`,
|
||||
`resource`,
|
||||
`previous_resource_version`
|
||||
FROM `resource_history`
|
||||
WHERE `action` = 3
|
||||
AND `value` LIKE '{"kind":"DeletedMarker"%';
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
SELECT `value`
|
||||
FROM `resource_history`
|
||||
WHERE `group` = 'ggg'
|
||||
AND `resource` = 'rrr'
|
||||
AND `resource_version` = 1234;
|
||||
Vendored
Executable
+4
@@ -0,0 +1,4 @@
|
||||
UPDATE `resource_history`
|
||||
SET `value` = '{new value}'
|
||||
WHERE `guid` = 'ggggg'
|
||||
;
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
DELETE FROM `resource_history`
|
||||
WHERE 1 = 1
|
||||
AND `guid` = 'xxxx'
|
||||
Vendored
Executable
+13
@@ -0,0 +1,13 @@
|
||||
SELECT
|
||||
`resource_version`,
|
||||
`namespace`,
|
||||
`name`,
|
||||
`folder`,
|
||||
`value`
|
||||
FROM `resource_history`
|
||||
WHERE 1 = 1
|
||||
AND `namespace` = 'nn'
|
||||
AND `group` = 'gg'
|
||||
AND `resource` = 'rr'
|
||||
AND `name` = 'name'
|
||||
ORDER BY resource_version DESC
|
||||
Vendored
Executable
+14
@@ -0,0 +1,14 @@
|
||||
SELECT
|
||||
`resource_version`,
|
||||
`namespace`,
|
||||
`name`,
|
||||
`folder`,
|
||||
`value`
|
||||
FROM `resource_history`
|
||||
WHERE 1 = 1
|
||||
AND `namespace` = 'nn'
|
||||
AND `group` = 'gg'
|
||||
AND `resource` = 'rr'
|
||||
AND `action` = 3
|
||||
AND `resource_version` > 123456
|
||||
ORDER BY resource_version DESC
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
SELECT
|
||||
`resource_version`,
|
||||
`namespace`,
|
||||
`name`,
|
||||
`folder`,
|
||||
`value`
|
||||
FROM `resource_history`
|
||||
WHERE 1 = 1
|
||||
AND `namespace` = 'nn'
|
||||
AND `group` = 'gg'
|
||||
AND `resource` = 'rr'
|
||||
AND `action` = 3
|
||||
ORDER BY resource_version DESC
|
||||
Vendored
Executable
+9
@@ -0,0 +1,9 @@
|
||||
SELECT
|
||||
"guid",
|
||||
"value",
|
||||
"group",
|
||||
"resource",
|
||||
"previous_resource_version"
|
||||
FROM "resource_history"
|
||||
WHERE "action" = 3
|
||||
AND "value" LIKE '{"kind":"DeletedMarker"%';
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
SELECT "value"
|
||||
FROM "resource_history"
|
||||
WHERE "group" = 'ggg'
|
||||
AND "resource" = 'rrr'
|
||||
AND "resource_version" = 1234;
|
||||
Vendored
Executable
+4
@@ -0,0 +1,4 @@
|
||||
UPDATE "resource_history"
|
||||
SET "value" = '{new value}'
|
||||
WHERE "guid" = 'ggggg'
|
||||
;
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
DELETE FROM "resource_history"
|
||||
WHERE 1 = 1
|
||||
AND "guid" = 'xxxx'
|
||||
Vendored
Executable
+13
@@ -0,0 +1,13 @@
|
||||
SELECT
|
||||
"resource_version",
|
||||
"namespace",
|
||||
"name",
|
||||
"folder",
|
||||
"value"
|
||||
FROM "resource_history"
|
||||
WHERE 1 = 1
|
||||
AND "namespace" = 'nn'
|
||||
AND "group" = 'gg'
|
||||
AND "resource" = 'rr'
|
||||
AND "name" = 'name'
|
||||
ORDER BY resource_version DESC
|
||||
Vendored
Executable
+14
@@ -0,0 +1,14 @@
|
||||
SELECT
|
||||
"resource_version",
|
||||
"namespace",
|
||||
"name",
|
||||
"folder",
|
||||
"value"
|
||||
FROM "resource_history"
|
||||
WHERE 1 = 1
|
||||
AND "namespace" = 'nn'
|
||||
AND "group" = 'gg'
|
||||
AND "resource" = 'rr'
|
||||
AND "action" = 3
|
||||
AND "resource_version" > 123456
|
||||
ORDER BY resource_version DESC
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
SELECT
|
||||
"resource_version",
|
||||
"namespace",
|
||||
"name",
|
||||
"folder",
|
||||
"value"
|
||||
FROM "resource_history"
|
||||
WHERE 1 = 1
|
||||
AND "namespace" = 'nn'
|
||||
AND "group" = 'gg'
|
||||
AND "resource" = 'rr'
|
||||
AND "action" = 3
|
||||
ORDER BY resource_version DESC
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
SELECT
|
||||
"guid",
|
||||
"value",
|
||||
"group",
|
||||
"resource",
|
||||
"previous_resource_version"
|
||||
FROM "resource_history"
|
||||
WHERE "action" = 3
|
||||
AND "value" LIKE '{"kind":"DeletedMarker"%';
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
SELECT "value"
|
||||
FROM "resource_history"
|
||||
WHERE "group" = 'ggg'
|
||||
AND "resource" = 'rrr'
|
||||
AND "resource_version" = 1234;
|
||||
Vendored
Executable
+4
@@ -0,0 +1,4 @@
|
||||
UPDATE "resource_history"
|
||||
SET "value" = '{new value}'
|
||||
WHERE "guid" = 'ggggg'
|
||||
;
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
DELETE FROM "resource_history"
|
||||
WHERE 1 = 1
|
||||
AND "guid" = 'xxxx'
|
||||
Vendored
Executable
+13
@@ -0,0 +1,13 @@
|
||||
SELECT
|
||||
"resource_version",
|
||||
"namespace",
|
||||
"name",
|
||||
"folder",
|
||||
"value"
|
||||
FROM "resource_history"
|
||||
WHERE 1 = 1
|
||||
AND "namespace" = 'nn'
|
||||
AND "group" = 'gg'
|
||||
AND "resource" = 'rr'
|
||||
AND "name" = 'name'
|
||||
ORDER BY resource_version DESC
|
||||
Vendored
Executable
+14
@@ -0,0 +1,14 @@
|
||||
SELECT
|
||||
"resource_version",
|
||||
"namespace",
|
||||
"name",
|
||||
"folder",
|
||||
"value"
|
||||
FROM "resource_history"
|
||||
WHERE 1 = 1
|
||||
AND "namespace" = 'nn'
|
||||
AND "group" = 'gg'
|
||||
AND "resource" = 'rr'
|
||||
AND "action" = 3
|
||||
AND "resource_version" > 123456
|
||||
ORDER BY resource_version DESC
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
SELECT
|
||||
"resource_version",
|
||||
"namespace",
|
||||
"name",
|
||||
"folder",
|
||||
"value"
|
||||
FROM "resource_history"
|
||||
WHERE 1 = 1
|
||||
AND "namespace" = 'nn'
|
||||
AND "group" = 'gg'
|
||||
AND "resource" = 'rr'
|
||||
AND "action" = 3
|
||||
ORDER BY resource_version DESC
|
||||
Reference in New Issue
Block a user