SQL: Add sql template test helper (#91953)

This commit is contained in:
Ryan McKinley 2024-08-16 14:36:56 +03:00 committed by GitHub
parent ac72098248
commit d9cabe5e14
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
131 changed files with 1377 additions and 911 deletions

View File

@ -29,7 +29,7 @@ var (
) )
type sqlQuery struct { type sqlQuery struct {
*sqltemplate.SQLTemplate sqltemplate.SQLTemplateIface
Query *DashboardQuery Query *DashboardQuery
} }

View File

@ -1,167 +1,69 @@
package legacy package legacy
import ( import (
"embed"
"os"
"path/filepath"
"testing" "testing"
"text/template" "text/template"
"github.com/google/go-cmp/cmp" "github.com/grafana/grafana/pkg/storage/unified/sql/sqltemplate/mocks"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/storage/unified/sql/sqltemplate"
) )
//go:embed testdata/*
var testdataFS embed.FS
func testdata(t *testing.T, filename string) []byte {
t.Helper()
b, err := testdataFS.ReadFile(`testdata/` + filename)
if err != nil {
writeTestData(filename, "<empty>")
assert.Fail(t, "missing test file")
}
return b
}
func writeTestData(filename, value string) {
_ = os.WriteFile(filepath.Join("testdata", filename), []byte(value), 0777)
}
func TestQueries(t *testing.T) { func TestQueries(t *testing.T) {
t.Parallel() mocks.CheckQuerySnapshots(t, mocks.TemplateTestSetup{
RootDir: "testdata",
// Check each dialect Templates: map[*template.Template][]mocks.TemplateTestCase{
dialects := []sqltemplate.Dialect{ sqlQueryDashboards: {
sqltemplate.MySQL, {
sqltemplate.SQLite, Name: "history_uid",
sqltemplate.PostgreSQL, Data: &sqlQuery{
} SQLTemplateIface: mocks.NewTestingSQLTemplate(),
Query: &DashboardQuery{
// Each template has one or more test cases, each identified with a OrgID: 2,
// descriptive name (e.g. "happy path", "error twiddling the frobb"). Each UID: "UUU",
// of them will test that for the same input data they must produce a result },
// that will depend on the Dialect. Expected queries should be defined in
// separate files in the testdata directory. This improves the testing
// experience by separating test data from test code, since mixing both
// tends to make it more difficult to reason about what is being done,
// especially as we want testing code to scale and make it easy to add
// tests.
type (
testCase = struct {
Name string
// Data should be the struct passed to the template.
Data sqltemplate.SQLTemplateIface
}
)
// Define tests cases. Most templates are trivial and testing that they
// generate correct code for a single Dialect is fine, since the one thing
// that always changes is how SQL placeholder arguments are passed (most
// Dialects use `?` while PostgreSQL uses `$1`, `$2`, etc.), and that is
// something that should be tested in the Dialect implementation instead of
// here. We will ask to have at least one test per SQL template, and we will
// lean to test MySQL. Templates containing branching (conditionals, loops,
// etc.) should be exercised at least once in each of their branches.
//
// NOTE: in the Data field, make sure to have pointers populated to simulate
// data is set as it would be in a real request. The data being correctly
// populated in each case should be tested in integration tests, where the
// data will actually flow to and from a real database. In this tests we
// only care about producing the correct SQL.
testCases := map[*template.Template][]*testCase{
sqlQueryDashboards: {
{
Name: "history_uid",
Data: &sqlQuery{
SQLTemplate: new(sqltemplate.SQLTemplate),
Query: &DashboardQuery{
OrgID: 2,
UID: "UUU",
}, },
}, },
}, {
{ Name: "history_uid_at_version",
Name: "history_uid_at_version", Data: &sqlQuery{
Data: &sqlQuery{ SQLTemplateIface: mocks.NewTestingSQLTemplate(),
SQLTemplate: new(sqltemplate.SQLTemplate), Query: &DashboardQuery{
Query: &DashboardQuery{ OrgID: 2,
OrgID: 2, UID: "UUU",
UID: "UUU", Version: 3,
Version: 3, },
}, },
}, },
}, {
{ Name: "history_uid_second_page",
Name: "history_uid_second_page", Data: &sqlQuery{
Data: &sqlQuery{ SQLTemplateIface: mocks.NewTestingSQLTemplate(),
SQLTemplate: new(sqltemplate.SQLTemplate), Query: &DashboardQuery{
Query: &DashboardQuery{ OrgID: 2,
OrgID: 2, UID: "UUU",
UID: "UUU", LastID: 7,
LastID: 7, },
}, },
}, },
}, {
{ Name: "dashboard",
Name: "dashboard", Data: &sqlQuery{
Data: &sqlQuery{ SQLTemplateIface: mocks.NewTestingSQLTemplate(),
SQLTemplate: new(sqltemplate.SQLTemplate), Query: &DashboardQuery{
Query: &DashboardQuery{ OrgID: 2,
OrgID: 2, },
}, },
}, },
}, {
{ Name: "dashboard_next_page",
Name: "dashboard_next_page", Data: &sqlQuery{
Data: &sqlQuery{ SQLTemplateIface: mocks.NewTestingSQLTemplate(),
SQLTemplate: new(sqltemplate.SQLTemplate), Query: &DashboardQuery{
Query: &DashboardQuery{ OrgID: 2,
OrgID: 2, LastID: 22,
LastID: 22, },
}, },
}, },
}, },
}, },
} })
// Execute test cases
for tmpl, tcs := range testCases {
t.Run(tmpl.Name(), func(t *testing.T) {
t.Parallel()
for _, tc := range tcs {
t.Run(tc.Name, func(t *testing.T) {
t.Parallel()
for _, dialect := range dialects {
filename := dialect.DialectName() + "__" + tc.Name + ".sql"
t.Run(filename, func(t *testing.T) {
// not parallel because we're sharing tc.Data, not
// worth it deep cloning
expectedQuery := string(testdata(t, filename))
//expectedQuery := sqltemplate.FormatSQL(rawQuery)
tc.Data.SetDialect(dialect)
err := tc.Data.Validate()
require.NoError(t, err)
got, err := sqltemplate.Execute(tmpl, tc.Data)
require.NoError(t, err)
got = sqltemplate.RemoveEmptyLines(got)
if diff := cmp.Diff(expectedQuery, got); diff != "" {
writeTestData(filename, got)
t.Errorf("%s: %s", tc.Name, diff)
}
})
}
})
}
})
}
} }

View File

@ -92,8 +92,8 @@ func (a *dashboardSqlAccess) getRows(ctx context.Context, query *DashboardQuery)
} }
req := sqlQuery{ req := sqlQuery{
SQLTemplate: sqltemplate.New(a.dialect), SQLTemplateIface: sqltemplate.New(a.dialect),
Query: query, Query: query,
} }
tmpl := sqlQueryDashboards tmpl := sqlQueryDashboards

View File

@ -14,5 +14,5 @@ SELECT
LEFT OUTER JOIN `user` AS created_user ON dashboard.created_by = created_user.id LEFT OUTER JOIN `user` AS created_user ON dashboard.created_by = created_user.id
LEFT OUTER JOIN `user` AS updated_user ON dashboard.updated_by = updated_user.id LEFT OUTER JOIN `user` AS updated_user ON dashboard.updated_by = updated_user.id
WHERE dashboard.is_folder = false WHERE dashboard.is_folder = false
AND dashboard.org_id = ? AND dashboard.org_id = 2
ORDER BY dashboard.id DESC ORDER BY dashboard.id DESC

View File

@ -14,7 +14,7 @@ SELECT
LEFT OUTER JOIN `user` AS created_user ON dashboard.created_by = created_user.id LEFT OUTER JOIN `user` AS created_user ON dashboard.created_by = created_user.id
LEFT OUTER JOIN `user` AS updated_user ON dashboard.updated_by = updated_user.id LEFT OUTER JOIN `user` AS updated_user ON dashboard.updated_by = updated_user.id
WHERE dashboard.is_folder = false WHERE dashboard.is_folder = false
AND dashboard.org_id = ? AND dashboard.org_id = 2
AND dashboard.id > ? AND dashboard.id > 22
AND dashboard.deleted IS NULL AND dashboard.deleted IS NULL
ORDER BY dashboard.id DESC ORDER BY dashboard.id DESC

View File

@ -0,0 +1,19 @@
SELECT
dashboard.org_id, dashboard.id,
dashboard.uid, dashboard.folder_uid,
dashboard.deleted, plugin_id,
dashboard_provisioning.name as origin_name,
dashboard_provisioning.external_id as origin_path,
dashboard_provisioning.check_sum as origin_key,
dashboard_provisioning.updated as origin_ts,
dashboard.created, created_user.uid as created_by, dashboard.created_by as created_by_id,
dashboard.updated, updated_user.uid as updated_by, dashboard.updated_by as updated_by_id,
dashboard.version, '' as message, dashboard.data
FROM dashboard
LEFT OUTER JOIN dashboard_provisioning ON dashboard.id = dashboard_provisioning.dashboard_id
LEFT OUTER JOIN `user` AS created_user ON dashboard.created_by = created_user.id
LEFT OUTER JOIN `user` AS updated_user ON dashboard.updated_by = updated_user.id
WHERE dashboard.is_folder = false
AND dashboard.org_id = 2
AND dashboard.uid = 'UUU'
ORDER BY dashboard.id DESC

View File

@ -15,6 +15,6 @@ SELECT
LEFT OUTER JOIN `user` AS created_user ON dashboard.created_by = created_user.id LEFT OUTER JOIN `user` AS created_user ON dashboard.created_by = created_user.id
LEFT OUTER JOIN `user` AS updated_user ON dashboard.updated_by = updated_user.id LEFT OUTER JOIN `user` AS updated_user ON dashboard.updated_by = updated_user.id
WHERE dashboard.is_folder = false WHERE dashboard.is_folder = false
AND dashboard.org_id = ? AND dashboard.org_id = 2
AND dashboard_version.version = ? AND dashboard_version.version = 3
ORDER BY dashboard_version.version DESC ORDER BY dashboard_version.version DESC

View File

@ -0,0 +1,20 @@
SELECT
dashboard.org_id, dashboard.id,
dashboard.uid, dashboard.folder_uid,
dashboard.deleted, plugin_id,
dashboard_provisioning.name as origin_name,
dashboard_provisioning.external_id as origin_path,
dashboard_provisioning.check_sum as origin_key,
dashboard_provisioning.updated as origin_ts,
dashboard.created, created_user.uid as created_by, dashboard.created_by as created_by_id,
dashboard.updated, updated_user.uid as updated_by, dashboard.updated_by as updated_by_id,
dashboard.version, '' as message, dashboard.data
FROM dashboard
LEFT OUTER JOIN dashboard_provisioning ON dashboard.id = dashboard_provisioning.dashboard_id
LEFT OUTER JOIN `user` AS created_user ON dashboard.created_by = created_user.id
LEFT OUTER JOIN `user` AS updated_user ON dashboard.updated_by = updated_user.id
WHERE dashboard.is_folder = false
AND dashboard.org_id = 2
AND dashboard.uid = 'UUU'
AND dashboard.deleted IS NULL
ORDER BY dashboard.id DESC

View File

@ -14,6 +14,5 @@ SELECT
LEFT OUTER JOIN `user` AS created_user ON dashboard.created_by = created_user.id LEFT OUTER JOIN `user` AS created_user ON dashboard.created_by = created_user.id
LEFT OUTER JOIN `user` AS updated_user ON dashboard.updated_by = updated_user.id LEFT OUTER JOIN `user` AS updated_user ON dashboard.updated_by = updated_user.id
WHERE dashboard.is_folder = false WHERE dashboard.is_folder = false
AND dashboard.org_id = ? AND dashboard.org_id = 2
AND dashboard.uid = ?
ORDER BY dashboard.id DESC ORDER BY dashboard.id DESC

View File

@ -14,7 +14,7 @@ SELECT
LEFT OUTER JOIN `user` AS created_user ON dashboard.created_by = created_user.id LEFT OUTER JOIN `user` AS created_user ON dashboard.created_by = created_user.id
LEFT OUTER JOIN `user` AS updated_user ON dashboard.updated_by = updated_user.id LEFT OUTER JOIN `user` AS updated_user ON dashboard.updated_by = updated_user.id
WHERE dashboard.is_folder = false WHERE dashboard.is_folder = false
AND dashboard.org_id = ? AND dashboard.org_id = 2
AND dashboard.uid = ? AND dashboard.id > 22
AND dashboard.deleted IS NULL AND dashboard.deleted IS NULL
ORDER BY dashboard.id DESC ORDER BY dashboard.id DESC

View File

@ -0,0 +1,19 @@
SELECT
dashboard.org_id, dashboard.id,
dashboard.uid, dashboard.folder_uid,
dashboard.deleted, plugin_id,
dashboard_provisioning.name as origin_name,
dashboard_provisioning.external_id as origin_path,
dashboard_provisioning.check_sum as origin_key,
dashboard_provisioning.updated as origin_ts,
dashboard.created, created_user.uid as created_by, dashboard.created_by as created_by_id,
dashboard.updated, updated_user.uid as updated_by, dashboard.updated_by as updated_by_id,
dashboard.version, '' as message, dashboard.data
FROM dashboard
LEFT OUTER JOIN dashboard_provisioning ON dashboard.id = dashboard_provisioning.dashboard_id
LEFT OUTER JOIN `user` AS created_user ON dashboard.created_by = created_user.id
LEFT OUTER JOIN `user` AS updated_user ON dashboard.updated_by = updated_user.id
WHERE dashboard.is_folder = false
AND dashboard.org_id = 2
AND dashboard.uid = 'UUU'
ORDER BY dashboard.id DESC

View File

@ -0,0 +1,20 @@
SELECT
dashboard.org_id, dashboard.id,
dashboard.uid, dashboard.folder_uid,
dashboard.deleted, plugin_id,
dashboard_provisioning.name as origin_name,
dashboard_provisioning.external_id as origin_path,
dashboard_provisioning.check_sum as origin_key,
dashboard_provisioning.updated as origin_ts,
dashboard.created, created_user.uid as created_by, dashboard.created_by as created_by_id,
dashboard_version.created, updated_user.uid as updated_by,updated_user.id as created_by_id,
dashboard_version.version, dashboard_version.message, dashboard_version.data
FROM dashboard
LEFT OUTER JOIN dashboard_version ON dashboard.id = dashboard_version.dashboard_id
LEFT OUTER JOIN dashboard_provisioning ON dashboard.id = dashboard_provisioning.dashboard_id
LEFT OUTER JOIN `user` AS created_user ON dashboard.created_by = created_user.id
LEFT OUTER JOIN `user` AS updated_user ON dashboard.updated_by = updated_user.id
WHERE dashboard.is_folder = false
AND dashboard.org_id = 2
AND dashboard_version.version = 3
ORDER BY dashboard_version.version DESC

View File

@ -0,0 +1,20 @@
SELECT
dashboard.org_id, dashboard.id,
dashboard.uid, dashboard.folder_uid,
dashboard.deleted, plugin_id,
dashboard_provisioning.name as origin_name,
dashboard_provisioning.external_id as origin_path,
dashboard_provisioning.check_sum as origin_key,
dashboard_provisioning.updated as origin_ts,
dashboard.created, created_user.uid as created_by, dashboard.created_by as created_by_id,
dashboard.updated, updated_user.uid as updated_by, dashboard.updated_by as updated_by_id,
dashboard.version, '' as message, dashboard.data
FROM dashboard
LEFT OUTER JOIN dashboard_provisioning ON dashboard.id = dashboard_provisioning.dashboard_id
LEFT OUTER JOIN `user` AS created_user ON dashboard.created_by = created_user.id
LEFT OUTER JOIN `user` AS updated_user ON dashboard.updated_by = updated_user.id
WHERE dashboard.is_folder = false
AND dashboard.org_id = 2
AND dashboard.uid = 'UUU'
AND dashboard.deleted IS NULL
ORDER BY dashboard.id DESC

View File

@ -14,5 +14,5 @@ SELECT
LEFT OUTER JOIN "user" AS created_user ON dashboard.created_by = created_user.id LEFT OUTER JOIN "user" AS created_user ON dashboard.created_by = created_user.id
LEFT OUTER JOIN "user" AS updated_user ON dashboard.updated_by = updated_user.id LEFT OUTER JOIN "user" AS updated_user ON dashboard.updated_by = updated_user.id
WHERE dashboard.is_folder = false WHERE dashboard.is_folder = false
AND dashboard.org_id = ? AND dashboard.org_id = 2
ORDER BY dashboard.id DESC ORDER BY dashboard.id DESC

View File

@ -14,7 +14,7 @@ SELECT
LEFT OUTER JOIN "user" AS created_user ON dashboard.created_by = created_user.id LEFT OUTER JOIN "user" AS created_user ON dashboard.created_by = created_user.id
LEFT OUTER JOIN "user" AS updated_user ON dashboard.updated_by = updated_user.id LEFT OUTER JOIN "user" AS updated_user ON dashboard.updated_by = updated_user.id
WHERE dashboard.is_folder = false WHERE dashboard.is_folder = false
AND dashboard.org_id = ? AND dashboard.org_id = 2
AND dashboard.id > ? AND dashboard.id > 22
AND dashboard.deleted IS NULL AND dashboard.deleted IS NULL
ORDER BY dashboard.id DESC ORDER BY dashboard.id DESC

View File

@ -0,0 +1,19 @@
SELECT
dashboard.org_id, dashboard.id,
dashboard.uid, dashboard.folder_uid,
dashboard.deleted, plugin_id,
dashboard_provisioning.name as origin_name,
dashboard_provisioning.external_id as origin_path,
dashboard_provisioning.check_sum as origin_key,
dashboard_provisioning.updated as origin_ts,
dashboard.created, created_user.uid as created_by, dashboard.created_by as created_by_id,
dashboard.updated, updated_user.uid as updated_by, dashboard.updated_by as updated_by_id,
dashboard.version, '' as message, dashboard.data
FROM dashboard
LEFT OUTER JOIN dashboard_provisioning ON dashboard.id = dashboard_provisioning.dashboard_id
LEFT OUTER JOIN "user" AS created_user ON dashboard.created_by = created_user.id
LEFT OUTER JOIN "user" AS updated_user ON dashboard.updated_by = updated_user.id
WHERE dashboard.is_folder = false
AND dashboard.org_id = 2
AND dashboard.uid = 'UUU'
ORDER BY dashboard.id DESC

View File

@ -15,6 +15,6 @@ SELECT
LEFT OUTER JOIN "user" AS created_user ON dashboard.created_by = created_user.id LEFT OUTER JOIN "user" AS created_user ON dashboard.created_by = created_user.id
LEFT OUTER JOIN "user" AS updated_user ON dashboard.updated_by = updated_user.id LEFT OUTER JOIN "user" AS updated_user ON dashboard.updated_by = updated_user.id
WHERE dashboard.is_folder = false WHERE dashboard.is_folder = false
AND dashboard.org_id = ? AND dashboard.org_id = 2
AND dashboard_version.version = ? AND dashboard_version.version = 3
ORDER BY dashboard_version.version DESC ORDER BY dashboard_version.version DESC

View File

@ -0,0 +1,20 @@
SELECT
dashboard.org_id, dashboard.id,
dashboard.uid, dashboard.folder_uid,
dashboard.deleted, plugin_id,
dashboard_provisioning.name as origin_name,
dashboard_provisioning.external_id as origin_path,
dashboard_provisioning.check_sum as origin_key,
dashboard_provisioning.updated as origin_ts,
dashboard.created, created_user.uid as created_by, dashboard.created_by as created_by_id,
dashboard.updated, updated_user.uid as updated_by, dashboard.updated_by as updated_by_id,
dashboard.version, '' as message, dashboard.data
FROM dashboard
LEFT OUTER JOIN dashboard_provisioning ON dashboard.id = dashboard_provisioning.dashboard_id
LEFT OUTER JOIN "user" AS created_user ON dashboard.created_by = created_user.id
LEFT OUTER JOIN "user" AS updated_user ON dashboard.updated_by = updated_user.id
WHERE dashboard.is_folder = false
AND dashboard.org_id = 2
AND dashboard.uid = 'UUU'
AND dashboard.deleted IS NULL
ORDER BY dashboard.id DESC

View File

@ -14,5 +14,5 @@ SELECT
LEFT OUTER JOIN "user" AS created_user ON dashboard.created_by = created_user.id LEFT OUTER JOIN "user" AS created_user ON dashboard.created_by = created_user.id
LEFT OUTER JOIN "user" AS updated_user ON dashboard.updated_by = updated_user.id LEFT OUTER JOIN "user" AS updated_user ON dashboard.updated_by = updated_user.id
WHERE dashboard.is_folder = false WHERE dashboard.is_folder = false
AND dashboard.org_id = $1 AND dashboard.org_id = 2
ORDER BY dashboard.id DESC ORDER BY dashboard.id DESC

View File

@ -14,7 +14,7 @@ SELECT
LEFT OUTER JOIN "user" AS created_user ON dashboard.created_by = created_user.id LEFT OUTER JOIN "user" AS created_user ON dashboard.created_by = created_user.id
LEFT OUTER JOIN "user" AS updated_user ON dashboard.updated_by = updated_user.id LEFT OUTER JOIN "user" AS updated_user ON dashboard.updated_by = updated_user.id
WHERE dashboard.is_folder = false WHERE dashboard.is_folder = false
AND dashboard.org_id = ? AND dashboard.org_id = 2
AND dashboard.uid = ? AND dashboard.id > 22
AND dashboard.deleted IS NULL AND dashboard.deleted IS NULL
ORDER BY dashboard.id DESC ORDER BY dashboard.id DESC

View File

@ -0,0 +1,19 @@
SELECT
dashboard.org_id, dashboard.id,
dashboard.uid, dashboard.folder_uid,
dashboard.deleted, plugin_id,
dashboard_provisioning.name as origin_name,
dashboard_provisioning.external_id as origin_path,
dashboard_provisioning.check_sum as origin_key,
dashboard_provisioning.updated as origin_ts,
dashboard.created, created_user.uid as created_by, dashboard.created_by as created_by_id,
dashboard.updated, updated_user.uid as updated_by, dashboard.updated_by as updated_by_id,
dashboard.version, '' as message, dashboard.data
FROM dashboard
LEFT OUTER JOIN dashboard_provisioning ON dashboard.id = dashboard_provisioning.dashboard_id
LEFT OUTER JOIN "user" AS created_user ON dashboard.created_by = created_user.id
LEFT OUTER JOIN "user" AS updated_user ON dashboard.updated_by = updated_user.id
WHERE dashboard.is_folder = false
AND dashboard.org_id = 2
AND dashboard.uid = 'UUU'
ORDER BY dashboard.id DESC

View File

@ -15,6 +15,6 @@ SELECT
LEFT OUTER JOIN "user" AS created_user ON dashboard.created_by = created_user.id LEFT OUTER JOIN "user" AS created_user ON dashboard.created_by = created_user.id
LEFT OUTER JOIN "user" AS updated_user ON dashboard.updated_by = updated_user.id LEFT OUTER JOIN "user" AS updated_user ON dashboard.updated_by = updated_user.id
WHERE dashboard.is_folder = false WHERE dashboard.is_folder = false
AND dashboard.org_id = $1 AND dashboard.org_id = 2
AND dashboard_version.version = $2 AND dashboard_version.version = 3
ORDER BY dashboard_version.version DESC ORDER BY dashboard_version.version DESC

View File

@ -0,0 +1,20 @@
SELECT
dashboard.org_id, dashboard.id,
dashboard.uid, dashboard.folder_uid,
dashboard.deleted, plugin_id,
dashboard_provisioning.name as origin_name,
dashboard_provisioning.external_id as origin_path,
dashboard_provisioning.check_sum as origin_key,
dashboard_provisioning.updated as origin_ts,
dashboard.created, created_user.uid as created_by, dashboard.created_by as created_by_id,
dashboard.updated, updated_user.uid as updated_by, dashboard.updated_by as updated_by_id,
dashboard.version, '' as message, dashboard.data
FROM dashboard
LEFT OUTER JOIN dashboard_provisioning ON dashboard.id = dashboard_provisioning.dashboard_id
LEFT OUTER JOIN "user" AS created_user ON dashboard.created_by = created_user.id
LEFT OUTER JOIN "user" AS updated_user ON dashboard.updated_by = updated_user.id
WHERE dashboard.is_folder = false
AND dashboard.org_id = 2
AND dashboard.uid = 'UUU'
AND dashboard.deleted IS NULL
ORDER BY dashboard.id DESC

View File

@ -14,6 +14,5 @@ SELECT
LEFT OUTER JOIN "user" AS created_user ON dashboard.created_by = created_user.id LEFT OUTER JOIN "user" AS created_user ON dashboard.created_by = created_user.id
LEFT OUTER JOIN "user" AS updated_user ON dashboard.updated_by = updated_user.id LEFT OUTER JOIN "user" AS updated_user ON dashboard.updated_by = updated_user.id
WHERE dashboard.is_folder = false WHERE dashboard.is_folder = false
AND dashboard.org_id = ? AND dashboard.org_id = 2
AND dashboard.uid = ?
ORDER BY dashboard.id DESC ORDER BY dashboard.id DESC

View File

@ -14,7 +14,7 @@ SELECT
LEFT OUTER JOIN "user" AS created_user ON dashboard.created_by = created_user.id LEFT OUTER JOIN "user" AS created_user ON dashboard.created_by = created_user.id
LEFT OUTER JOIN "user" AS updated_user ON dashboard.updated_by = updated_user.id LEFT OUTER JOIN "user" AS updated_user ON dashboard.updated_by = updated_user.id
WHERE dashboard.is_folder = false WHERE dashboard.is_folder = false
AND dashboard.org_id = $1 AND dashboard.org_id = 2
AND dashboard.id > $2 AND dashboard.id > 22
AND dashboard.deleted IS NULL AND dashboard.deleted IS NULL
ORDER BY dashboard.id DESC ORDER BY dashboard.id DESC

View File

@ -0,0 +1,19 @@
SELECT
dashboard.org_id, dashboard.id,
dashboard.uid, dashboard.folder_uid,
dashboard.deleted, plugin_id,
dashboard_provisioning.name as origin_name,
dashboard_provisioning.external_id as origin_path,
dashboard_provisioning.check_sum as origin_key,
dashboard_provisioning.updated as origin_ts,
dashboard.created, created_user.uid as created_by, dashboard.created_by as created_by_id,
dashboard.updated, updated_user.uid as updated_by, dashboard.updated_by as updated_by_id,
dashboard.version, '' as message, dashboard.data
FROM dashboard
LEFT OUTER JOIN dashboard_provisioning ON dashboard.id = dashboard_provisioning.dashboard_id
LEFT OUTER JOIN "user" AS created_user ON dashboard.created_by = created_user.id
LEFT OUTER JOIN "user" AS updated_user ON dashboard.updated_by = updated_user.id
WHERE dashboard.is_folder = false
AND dashboard.org_id = 2
AND dashboard.uid = 'UUU'
ORDER BY dashboard.id DESC

View File

@ -0,0 +1,20 @@
SELECT
dashboard.org_id, dashboard.id,
dashboard.uid, dashboard.folder_uid,
dashboard.deleted, plugin_id,
dashboard_provisioning.name as origin_name,
dashboard_provisioning.external_id as origin_path,
dashboard_provisioning.check_sum as origin_key,
dashboard_provisioning.updated as origin_ts,
dashboard.created, created_user.uid as created_by, dashboard.created_by as created_by_id,
dashboard_version.created, updated_user.uid as updated_by,updated_user.id as created_by_id,
dashboard_version.version, dashboard_version.message, dashboard_version.data
FROM dashboard
LEFT OUTER JOIN dashboard_version ON dashboard.id = dashboard_version.dashboard_id
LEFT OUTER JOIN dashboard_provisioning ON dashboard.id = dashboard_provisioning.dashboard_id
LEFT OUTER JOIN "user" AS created_user ON dashboard.created_by = created_user.id
LEFT OUTER JOIN "user" AS updated_user ON dashboard.updated_by = updated_user.id
WHERE dashboard.is_folder = false
AND dashboard.org_id = 2
AND dashboard_version.version = 3
ORDER BY dashboard_version.version DESC

View File

@ -0,0 +1,20 @@
SELECT
dashboard.org_id, dashboard.id,
dashboard.uid, dashboard.folder_uid,
dashboard.deleted, plugin_id,
dashboard_provisioning.name as origin_name,
dashboard_provisioning.external_id as origin_path,
dashboard_provisioning.check_sum as origin_key,
dashboard_provisioning.updated as origin_ts,
dashboard.created, created_user.uid as created_by, dashboard.created_by as created_by_id,
dashboard.updated, updated_user.uid as updated_by, dashboard.updated_by as updated_by_id,
dashboard.version, '' as message, dashboard.data
FROM dashboard
LEFT OUTER JOIN dashboard_provisioning ON dashboard.id = dashboard_provisioning.dashboard_id
LEFT OUTER JOIN "user" AS created_user ON dashboard.created_by = created_user.id
LEFT OUTER JOIN "user" AS updated_user ON dashboard.updated_by = updated_user.id
WHERE dashboard.is_folder = false
AND dashboard.org_id = 2
AND dashboard.uid = 'UUU'
AND dashboard.deleted IS NULL
ORDER BY dashboard.id DESC

View File

@ -14,6 +14,5 @@ SELECT
LEFT OUTER JOIN "user" AS created_user ON dashboard.created_by = created_user.id LEFT OUTER JOIN "user" AS created_user ON dashboard.created_by = created_user.id
LEFT OUTER JOIN "user" AS updated_user ON dashboard.updated_by = updated_user.id LEFT OUTER JOIN "user" AS updated_user ON dashboard.updated_by = updated_user.id
WHERE dashboard.is_folder = false WHERE dashboard.is_folder = false
AND dashboard.org_id = $1 AND dashboard.org_id = 2
AND dashboard.uid = $2
ORDER BY dashboard.id DESC ORDER BY dashboard.id DESC

View File

@ -14,7 +14,7 @@ SELECT
LEFT OUTER JOIN "user" AS created_user ON dashboard.created_by = created_user.id LEFT OUTER JOIN "user" AS created_user ON dashboard.created_by = created_user.id
LEFT OUTER JOIN "user" AS updated_user ON dashboard.updated_by = updated_user.id LEFT OUTER JOIN "user" AS updated_user ON dashboard.updated_by = updated_user.id
WHERE dashboard.is_folder = false WHERE dashboard.is_folder = false
AND dashboard.org_id = $1 AND dashboard.org_id = 2
AND dashboard.uid = $2 AND dashboard.id > 22
AND dashboard.deleted IS NULL AND dashboard.deleted IS NULL
ORDER BY dashboard.id DESC ORDER BY dashboard.id DESC

View File

@ -0,0 +1,19 @@
SELECT
dashboard.org_id, dashboard.id,
dashboard.uid, dashboard.folder_uid,
dashboard.deleted, plugin_id,
dashboard_provisioning.name as origin_name,
dashboard_provisioning.external_id as origin_path,
dashboard_provisioning.check_sum as origin_key,
dashboard_provisioning.updated as origin_ts,
dashboard.created, created_user.uid as created_by, dashboard.created_by as created_by_id,
dashboard.updated, updated_user.uid as updated_by, dashboard.updated_by as updated_by_id,
dashboard.version, '' as message, dashboard.data
FROM dashboard
LEFT OUTER JOIN dashboard_provisioning ON dashboard.id = dashboard_provisioning.dashboard_id
LEFT OUTER JOIN "user" AS created_user ON dashboard.created_by = created_user.id
LEFT OUTER JOIN "user" AS updated_user ON dashboard.updated_by = updated_user.id
WHERE dashboard.is_folder = false
AND dashboard.org_id = 2
AND dashboard.uid = 'UUU'
ORDER BY dashboard.id DESC

View File

@ -0,0 +1,20 @@
SELECT
dashboard.org_id, dashboard.id,
dashboard.uid, dashboard.folder_uid,
dashboard.deleted, plugin_id,
dashboard_provisioning.name as origin_name,
dashboard_provisioning.external_id as origin_path,
dashboard_provisioning.check_sum as origin_key,
dashboard_provisioning.updated as origin_ts,
dashboard.created, created_user.uid as created_by, dashboard.created_by as created_by_id,
dashboard_version.created, updated_user.uid as updated_by,updated_user.id as created_by_id,
dashboard_version.version, dashboard_version.message, dashboard_version.data
FROM dashboard
LEFT OUTER JOIN dashboard_version ON dashboard.id = dashboard_version.dashboard_id
LEFT OUTER JOIN dashboard_provisioning ON dashboard.id = dashboard_provisioning.dashboard_id
LEFT OUTER JOIN "user" AS created_user ON dashboard.created_by = created_user.id
LEFT OUTER JOIN "user" AS updated_user ON dashboard.updated_by = updated_user.id
WHERE dashboard.is_folder = false
AND dashboard.org_id = 2
AND dashboard_version.version = 3
ORDER BY dashboard_version.version DESC

View File

@ -0,0 +1,20 @@
SELECT
dashboard.org_id, dashboard.id,
dashboard.uid, dashboard.folder_uid,
dashboard.deleted, plugin_id,
dashboard_provisioning.name as origin_name,
dashboard_provisioning.external_id as origin_path,
dashboard_provisioning.check_sum as origin_key,
dashboard_provisioning.updated as origin_ts,
dashboard.created, created_user.uid as created_by, dashboard.created_by as created_by_id,
dashboard.updated, updated_user.uid as updated_by, dashboard.updated_by as updated_by_id,
dashboard.version, '' as message, dashboard.data
FROM dashboard
LEFT OUTER JOIN dashboard_provisioning ON dashboard.id = dashboard_provisioning.dashboard_id
LEFT OUTER JOIN "user" AS created_user ON dashboard.created_by = created_user.id
LEFT OUTER JOIN "user" AS updated_user ON dashboard.updated_by = updated_user.id
WHERE dashboard.is_folder = false
AND dashboard.org_id = 2
AND dashboard.uid = 'UUU'
AND dashboard.deleted IS NULL
ORDER BY dashboard.id DESC

View File

@ -55,8 +55,8 @@ func (s *legacySQLStore) ListTeams(ctx context.Context, ns claims.NamespaceInfo,
} }
req := sqlQueryListTeams{ req := sqlQueryListTeams{
SQLTemplate: sqltemplate.New(s.dialect), SQLTemplateIface: sqltemplate.New(s.dialect),
Query: &query, Query: &query,
} }
rawQuery, err := sqltemplate.Execute(sqlQueryTeams, req) rawQuery, err := sqltemplate.Execute(sqlQueryTeams, req)
@ -117,8 +117,8 @@ func (s *legacySQLStore) ListUsers(ctx context.Context, ns claims.NamespaceInfo,
} }
return s.queryUsers(ctx, sqlQueryUsers, sqlQueryListUsers{ return s.queryUsers(ctx, sqlQueryUsers, sqlQueryListUsers{
SQLTemplate: sqltemplate.New(s.dialect), SQLTemplateIface: sqltemplate.New(s.dialect),
Query: &query, Query: &query,
}, limit, query.UID != "") }, limit, query.UID != "")
} }
@ -180,7 +180,7 @@ func (s *legacySQLStore) GetDisplay(ctx context.Context, ns claims.NamespaceInfo
} }
return s.queryUsers(ctx, sqlQueryDisplay, sqlQueryGetDisplay{ return s.queryUsers(ctx, sqlQueryDisplay, sqlQueryGetDisplay{
SQLTemplate: sqltemplate.New(s.dialect), SQLTemplateIface: sqltemplate.New(s.dialect),
Query: &query, Query: &query,
}, 10000, false) }, 10000, false)
} }

View File

@ -31,7 +31,7 @@ var (
) )
type sqlQueryListUsers struct { type sqlQueryListUsers struct {
*sqltemplate.SQLTemplate sqltemplate.SQLTemplateIface
Query *ListUserQuery Query *ListUserQuery
} }
@ -40,7 +40,7 @@ func (r sqlQueryListUsers) Validate() error {
} }
type sqlQueryListTeams struct { type sqlQueryListTeams struct {
*sqltemplate.SQLTemplate sqltemplate.SQLTemplateIface
Query *ListTeamQuery Query *ListTeamQuery
} }
@ -49,7 +49,7 @@ func (r sqlQueryListTeams) Validate() error {
} }
type sqlQueryGetDisplay struct { type sqlQueryGetDisplay struct {
*sqltemplate.SQLTemplate sqltemplate.SQLTemplateIface
Query *GetUserDisplayQuery Query *GetUserDisplayQuery
} }

View File

@ -1,207 +1,109 @@
package legacy package legacy
import ( import (
"embed"
"os"
"path/filepath"
"testing" "testing"
"text/template" "text/template"
"github.com/google/go-cmp/cmp" "github.com/grafana/grafana/pkg/storage/unified/sql/sqltemplate/mocks"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/storage/unified/sql/sqltemplate"
) )
//go:embed testdata/*
var testdataFS embed.FS
func testdata(t *testing.T, filename string) []byte {
t.Helper()
b, err := testdataFS.ReadFile(`testdata/` + filename)
if err != nil {
writeTestData(filename, "<empty>")
assert.Fail(t, "missing test file")
}
return b
}
func writeTestData(filename, value string) {
_ = os.WriteFile(filepath.Join("testdata", filename), []byte(value), 0777)
}
func TestQueries(t *testing.T) { func TestQueries(t *testing.T) {
t.Parallel() mocks.CheckQuerySnapshots(t, mocks.TemplateTestSetup{
RootDir: "testdata",
// Check each dialect Templates: map[*template.Template][]mocks.TemplateTestCase{
dialects := []sqltemplate.Dialect{ sqlQueryTeams: {
sqltemplate.MySQL, {
sqltemplate.SQLite, Name: "teams_uid",
sqltemplate.PostgreSQL, Data: &sqlQueryListTeams{
} SQLTemplateIface: mocks.NewTestingSQLTemplate(),
Query: &ListTeamQuery{
// Each template has one or more test cases, each identified with a UID: "abc",
// descriptive name (e.g. "happy path", "error twiddling the frobb"). Each },
// of them will test that for the same input data they must produce a result },
// that will depend on the Dialect. Expected queries should be defined in },
// separate files in the testdata directory. This improves the testing {
// experience by separating test data from test code, since mixing both Name: "teams_page_1",
// tends to make it more difficult to reason about what is being done, Data: &sqlQueryListTeams{
// especially as we want testing code to scale and make it easy to add SQLTemplateIface: mocks.NewTestingSQLTemplate(),
// tests. Query: &ListTeamQuery{
type ( Limit: 5,
testCase = struct { },
Name string },
},
// Data should be the struct passed to the template. {
Data sqltemplate.SQLTemplateIface Name: "teams_page_2",
} Data: &sqlQueryListTeams{
) SQLTemplateIface: mocks.NewTestingSQLTemplate(),
Query: &ListTeamQuery{
// Define tests cases. Most templates are trivial and testing that they ContinueID: 1,
// generate correct code for a single Dialect is fine, since the one thing Limit: 2,
// that always changes is how SQL placeholder arguments are passed (most },
// Dialects use `?` while PostgreSQL uses `$1`, `$2`, etc.), and that is
// something that should be tested in the Dialect implementation instead of
// here. We will ask to have at least one test per SQL template, and we will
// lean to test MySQL. Templates containing branching (conditionals, loops,
// etc.) should be exercised at least once in each of their branches.
//
// NOTE: in the Data field, make sure to have pointers populated to simulate
// data is set as it would be in a real request. The data being correctly
// populated in each case should be tested in integration tests, where the
// data will actually flow to and from a real database. In this tests we
// only care about producing the correct SQL.
testCases := map[*template.Template][]*testCase{
sqlQueryTeams: {
{
Name: "teams_uid",
Data: &sqlQueryListTeams{
SQLTemplate: new(sqltemplate.SQLTemplate),
Query: &ListTeamQuery{
UID: "abc",
}, },
}, },
}, },
{ sqlQueryUsers: {
Name: "teams_page_1", {
Data: &sqlQueryListTeams{ Name: "users_uid",
SQLTemplate: new(sqltemplate.SQLTemplate), Data: &sqlQueryListUsers{
Query: &ListTeamQuery{ SQLTemplateIface: mocks.NewTestingSQLTemplate(),
Limit: 5, Query: &ListUserQuery{
UID: "abc",
},
},
},
{
Name: "users_page_1",
Data: &sqlQueryListUsers{
SQLTemplateIface: mocks.NewTestingSQLTemplate(),
Query: &ListUserQuery{
Limit: 5,
},
},
},
{
Name: "users_page_2",
Data: &sqlQueryListUsers{
SQLTemplateIface: mocks.NewTestingSQLTemplate(),
Query: &ListUserQuery{
ContinueID: 1,
Limit: 2,
},
}, },
}, },
}, },
{ sqlQueryDisplay: {
Name: "teams_page_2", {
Data: &sqlQueryListTeams{ Name: "display_uids",
SQLTemplate: new(sqltemplate.SQLTemplate), Data: &sqlQueryGetDisplay{
Query: &ListTeamQuery{ SQLTemplateIface: mocks.NewTestingSQLTemplate(),
ContinueID: 1, Query: &GetUserDisplayQuery{
Limit: 2, OrgID: 2,
UIDs: []string{"a", "b"},
},
},
},
{
Name: "display_ids",
Data: &sqlQueryGetDisplay{
SQLTemplateIface: mocks.NewTestingSQLTemplate(),
Query: &GetUserDisplayQuery{
OrgID: 2,
IDs: []int64{1, 2},
},
},
},
{
Name: "display_ids_uids",
Data: &sqlQueryGetDisplay{
SQLTemplateIface: mocks.NewTestingSQLTemplate(),
Query: &GetUserDisplayQuery{
OrgID: 2,
UIDs: []string{"a", "b"},
IDs: []int64{1, 2},
},
}, },
}, },
}, },
}, },
sqlQueryUsers: { })
{
Name: "users_uid",
Data: &sqlQueryListUsers{
SQLTemplate: new(sqltemplate.SQLTemplate),
Query: &ListUserQuery{
UID: "abc",
},
},
},
{
Name: "users_page_1",
Data: &sqlQueryListUsers{
SQLTemplate: new(sqltemplate.SQLTemplate),
Query: &ListUserQuery{
Limit: 5,
},
},
},
{
Name: "users_page_2",
Data: &sqlQueryListUsers{
SQLTemplate: new(sqltemplate.SQLTemplate),
Query: &ListUserQuery{
ContinueID: 1,
Limit: 2,
},
},
},
},
sqlQueryDisplay: {
{
Name: "display_uids",
Data: &sqlQueryGetDisplay{
SQLTemplate: new(sqltemplate.SQLTemplate),
Query: &GetUserDisplayQuery{
OrgID: 2,
UIDs: []string{"a", "b"},
},
},
},
{
Name: "display_ids",
Data: &sqlQueryGetDisplay{
SQLTemplate: new(sqltemplate.SQLTemplate),
Query: &GetUserDisplayQuery{
OrgID: 2,
IDs: []int64{1, 2},
},
},
},
{
Name: "display_ids_uids",
Data: &sqlQueryGetDisplay{
SQLTemplate: new(sqltemplate.SQLTemplate),
Query: &GetUserDisplayQuery{
OrgID: 2,
UIDs: []string{"a", "b"},
IDs: []int64{1, 2},
},
},
},
},
}
// Execute test cases
for tmpl, tcs := range testCases {
t.Run(tmpl.Name(), func(t *testing.T) {
t.Parallel()
for _, tc := range tcs {
t.Run(tc.Name, func(t *testing.T) {
t.Parallel()
for _, dialect := range dialects {
filename := dialect.DialectName() + "__" + tc.Name + ".sql"
t.Run(filename, func(t *testing.T) {
// not parallel because we're sharing tc.Data, not
// worth it deep cloning
expectedQuery := string(testdata(t, filename))
//expectedQuery := sqltemplate.FormatSQL(rawQuery)
tc.Data.SetDialect(dialect)
err := tc.Data.Validate()
require.NoError(t, err)
got, err := sqltemplate.Execute(tmpl, tc.Data)
require.NoError(t, err)
got = sqltemplate.RemoveEmptyLines(got)
if diff := cmp.Diff(expectedQuery, got); diff != "" {
writeTestData(filename, got)
t.Errorf("%s: %s", tc.Name, diff)
}
})
}
})
}
})
}
} }

View File

@ -1,8 +1,8 @@
SELECT org_user.org_id, u.id, u.uid, u.login, u.email, u.name, SELECT org_user.org_id, u.id, u.uid, u.login, u.email, u.name,
u.created, u.updated, u.is_service_account, u.is_disabled, u.is_admin u.created, u.updated, u.is_service_account, u.is_disabled, u.is_admin
FROM `user` as u JOIN org_user ON u.id = org_user.user_id FROM `user` as u JOIN org_user ON u.id = org_user.user_id
WHERE org_user.org_id = ? AND ( 1=2 WHERE org_user.org_id = 2 AND ( 1=2
OR u.id IN (?, ?) OR u.id IN (1, 2)
) )
ORDER BY u.id asc ORDER BY u.id asc
LIMIT 500 LIMIT 500

View File

@ -1,9 +1,9 @@
SELECT org_user.org_id, u.id, u.uid, u.login, u.email, u.name, SELECT org_user.org_id, u.id, u.uid, u.login, u.email, u.name,
u.created, u.updated, u.is_service_account, u.is_disabled, u.is_admin u.created, u.updated, u.is_service_account, u.is_disabled, u.is_admin
FROM `user` as u JOIN org_user ON u.id = org_user.user_id FROM `user` as u JOIN org_user ON u.id = org_user.user_id
WHERE org_user.org_id = ? AND ( 1=2 WHERE org_user.org_id = 2 AND ( 1=2
OR uid IN (?, ?) OR uid IN ('a', 'b')
OR u.id IN (?, ?) OR u.id IN (1, 2)
) )
ORDER BY u.id asc ORDER BY u.id asc
LIMIT 500 LIMIT 500

View File

@ -1,8 +1,8 @@
SELECT org_user.org_id, u.id, u.uid, u.login, u.email, u.name, SELECT org_user.org_id, u.id, u.uid, u.login, u.email, u.name,
u.created, u.updated, u.is_service_account, u.is_disabled, u.is_admin u.created, u.updated, u.is_service_account, u.is_disabled, u.is_admin
FROM `user` as u JOIN org_user ON u.id = org_user.user_id FROM `user` as u JOIN org_user ON u.id = org_user.user_id
WHERE org_user.org_id = ? AND ( 1=2 WHERE org_user.org_id = 2 AND ( 1=2
OR uid IN (?, ?) OR uid IN ('a', 'b')
) )
ORDER BY u.id asc ORDER BY u.id asc
LIMIT 500 LIMIT 500

View File

@ -1,5 +1,5 @@
SELECT id, uid, name, email, created, updated SELECT id, uid, name, email, created, updated
FROM "team" FROM "team"
WHERE org_id = ? WHERE org_id = 0
ORDER BY id asc ORDER BY id asc
LIMIT ? LIMIT 5

View File

@ -1,6 +1,6 @@
SELECT id, uid, name, email, created, updated SELECT id, uid, name, email, created, updated
FROM "team" FROM "team"
WHERE org_id = ? WHERE org_id = 0
AND id > ? AND id > 1
ORDER BY id asc ORDER BY id asc
LIMIT ? LIMIT 2

View File

@ -1,6 +1,6 @@
SELECT id, uid, name, email, created, updated SELECT id, uid, name, email, created, updated
FROM "team" FROM "team"
WHERE org_id = ? WHERE org_id = 0
AND uid = ? AND uid = 'abc'
ORDER BY id asc ORDER BY id asc
LIMIT ? LIMIT 0

View File

@ -1,7 +1,7 @@
SELECT org_user.org_id, u.id, u.uid, u.login, u.email, u.name, SELECT org_user.org_id, u.id, u.uid, u.login, u.email, u.name,
u.created, u.updated, u.is_service_account, u.is_disabled, u.is_admin u.created, u.updated, u.is_service_account, u.is_disabled, u.is_admin
FROM `user` as u JOIN org_user ON u.id = org_user.user_id FROM `user` as u JOIN org_user ON u.id = org_user.user_id
WHERE org_user.org_id = ? WHERE org_user.org_id = 0
AND u.is_service_account = ? AND u.is_service_account = FALSE
ORDER BY u.id asc ORDER BY u.id asc
LIMIT ? LIMIT 5

View File

@ -1,8 +1,8 @@
SELECT org_user.org_id, u.id, u.uid, u.login, u.email, u.name, SELECT org_user.org_id, u.id, u.uid, u.login, u.email, u.name,
u.created, u.updated, u.is_service_account, u.is_disabled, u.is_admin u.created, u.updated, u.is_service_account, u.is_disabled, u.is_admin
FROM `user` as u JOIN org_user ON u.id = org_user.user_id FROM `user` as u JOIN org_user ON u.id = org_user.user_id
WHERE org_user.org_id = ? WHERE org_user.org_id = 0
AND u.is_service_account = ? AND u.is_service_account = FALSE
AND id > ? AND id > 1
ORDER BY u.id asc ORDER BY u.id asc
LIMIT ? LIMIT 2

View File

@ -0,0 +1,8 @@
SELECT org_user.org_id, u.id, u.uid, u.login, u.email, u.name,
u.created, u.updated, u.is_service_account, u.is_disabled, u.is_admin
FROM `user` as u JOIN org_user ON u.id = org_user.user_id
WHERE org_user.org_id = 0
AND u.is_service_account = FALSE
AND uid = 'abc'
ORDER BY u.id asc
LIMIT 0

View File

@ -1,8 +0,0 @@
SELECT org_user.org_id, u.id, u.uid, u.login, u.email, u.name,
u.created, u.updated, u.is_service_account, u.is_disabled, u.is_admin
FROM `user` as u JOIN org_user ON u.id = org_user.user_id
WHERE org_user.org_id = ?
AND u.is_service_account = ?
AND uid = ?
ORDER BY u.id asc
LIMIT ?

View File

@ -1,8 +1,8 @@
SELECT org_user.org_id, u.id, u.uid, u.login, u.email, u.name, SELECT org_user.org_id, u.id, u.uid, u.login, u.email, u.name,
u.created, u.updated, u.is_service_account, u.is_disabled, u.is_admin u.created, u.updated, u.is_service_account, u.is_disabled, u.is_admin
FROM "user" as u JOIN org_user ON u.id = org_user.user_id FROM "user" as u JOIN org_user ON u.id = org_user.user_id
WHERE org_user.org_id = ? AND ( 1=2 WHERE org_user.org_id = 2 AND ( 1=2
OR u.id IN (?, ?) OR u.id IN (1, 2)
) )
ORDER BY u.id asc ORDER BY u.id asc
LIMIT 500 LIMIT 500

View File

@ -1,9 +1,9 @@
SELECT org_user.org_id, u.id, u.uid, u.login, u.email, u.name, SELECT org_user.org_id, u.id, u.uid, u.login, u.email, u.name,
u.created, u.updated, u.is_service_account, u.is_disabled, u.is_admin u.created, u.updated, u.is_service_account, u.is_disabled, u.is_admin
FROM "user" as u JOIN org_user ON u.id = org_user.user_id FROM "user" as u JOIN org_user ON u.id = org_user.user_id
WHERE org_user.org_id = ? AND ( 1=2 WHERE org_user.org_id = 2 AND ( 1=2
OR uid IN (?, ?) OR uid IN ('a', 'b')
OR u.id IN (?, ?) OR u.id IN (1, 2)
) )
ORDER BY u.id asc ORDER BY u.id asc
LIMIT 500 LIMIT 500

View File

@ -1,8 +1,8 @@
SELECT org_user.org_id, u.id, u.uid, u.login, u.email, u.name, SELECT org_user.org_id, u.id, u.uid, u.login, u.email, u.name,
u.created, u.updated, u.is_service_account, u.is_disabled, u.is_admin u.created, u.updated, u.is_service_account, u.is_disabled, u.is_admin
FROM "user" as u JOIN org_user ON u.id = org_user.user_id FROM "user" as u JOIN org_user ON u.id = org_user.user_id
WHERE org_user.org_id = $1 AND ( 1=2 WHERE org_user.org_id = 2 AND ( 1=2
OR uid IN ($2, $3) OR uid IN ('a', 'b')
) )
ORDER BY u.id asc ORDER BY u.id asc
LIMIT 500 LIMIT 500

View File

@ -1,5 +1,5 @@
SELECT id, uid, name, email, created, updated SELECT id, uid, name, email, created, updated
FROM "team" FROM "team"
WHERE org_id = ? WHERE org_id = 0
ORDER BY id asc ORDER BY id asc
LIMIT ? LIMIT 5

View File

@ -0,0 +1,6 @@
SELECT id, uid, name, email, created, updated
FROM "team"
WHERE org_id = 0
AND id > 1
ORDER BY id asc
LIMIT 2

View File

@ -0,0 +1,6 @@
SELECT id, uid, name, email, created, updated
FROM "team"
WHERE org_id = 0
AND uid = 'abc'
ORDER BY id asc
LIMIT 0

View File

@ -1,7 +1,7 @@
SELECT org_user.org_id, u.id, u.uid, u.login, u.email, u.name, SELECT org_user.org_id, u.id, u.uid, u.login, u.email, u.name,
u.created, u.updated, u.is_service_account, u.is_disabled, u.is_admin u.created, u.updated, u.is_service_account, u.is_disabled, u.is_admin
FROM "user" as u JOIN org_user ON u.id = org_user.user_id FROM "user" as u JOIN org_user ON u.id = org_user.user_id
WHERE org_user.org_id = ? WHERE org_user.org_id = 0
AND u.is_service_account = ? AND u.is_service_account = FALSE
ORDER BY u.id asc ORDER BY u.id asc
LIMIT ? LIMIT 5

View File

@ -1,8 +1,8 @@
SELECT org_user.org_id, u.id, u.uid, u.login, u.email, u.name, SELECT org_user.org_id, u.id, u.uid, u.login, u.email, u.name,
u.created, u.updated, u.is_service_account, u.is_disabled, u.is_admin u.created, u.updated, u.is_service_account, u.is_disabled, u.is_admin
FROM "user" as u JOIN org_user ON u.id = org_user.user_id FROM "user" as u JOIN org_user ON u.id = org_user.user_id
WHERE org_user.org_id = $1 WHERE org_user.org_id = 0
AND u.is_service_account = $2 AND u.is_service_account = FALSE
AND id > $3 AND id > 1
ORDER BY u.id asc ORDER BY u.id asc
LIMIT $4 LIMIT 2

View File

@ -0,0 +1,8 @@
SELECT org_user.org_id, u.id, u.uid, u.login, u.email, u.name,
u.created, u.updated, u.is_service_account, u.is_disabled, u.is_admin
FROM "user" as u JOIN org_user ON u.id = org_user.user_id
WHERE org_user.org_id = 0
AND u.is_service_account = FALSE
AND uid = 'abc'
ORDER BY u.id asc
LIMIT 0

View File

@ -1,9 +0,0 @@
SELECT org_user.org_id, u.id, u.uid, u.login, u.email, u.name,
u.created, u.updated, u.is_service_account, u.is_disabled, u.is_admin
FROM "user" as u JOIN org_user ON u.id = org_user.user_id
WHERE org_user.org_id = $1 AND ( 1=2
OR uid IN ($2, $3)
OR u.id IN ($4, $5)
)
ORDER BY u.id asc
LIMIT 500

View File

@ -1,6 +0,0 @@
SELECT id, uid, name, email, created, updated
FROM "team"
WHERE org_id = $1
AND id > $2
ORDER BY id asc
LIMIT $3

View File

@ -1,6 +0,0 @@
SELECT id, uid, name, email, created, updated
FROM "team"
WHERE org_id = $1
AND uid = $2
ORDER BY id asc
LIMIT $3

View File

@ -1,8 +0,0 @@
SELECT org_user.org_id, u.id, u.uid, u.login, u.email, u.name,
u.created, u.updated, u.is_service_account, u.is_disabled, u.is_admin
FROM "user" as u JOIN org_user ON u.id = org_user.user_id
WHERE org_user.org_id = $1
AND u.is_service_account = $2
AND uid = $3
ORDER BY u.id asc
LIMIT $4

View File

@ -1,8 +1,8 @@
SELECT org_user.org_id, u.id, u.uid, u.login, u.email, u.name, SELECT org_user.org_id, u.id, u.uid, u.login, u.email, u.name,
u.created, u.updated, u.is_service_account, u.is_disabled, u.is_admin u.created, u.updated, u.is_service_account, u.is_disabled, u.is_admin
FROM "user" as u JOIN org_user ON u.id = org_user.user_id FROM "user" as u JOIN org_user ON u.id = org_user.user_id
WHERE org_user.org_id = ? AND ( 1=2 WHERE org_user.org_id = 2 AND ( 1=2
OR uid IN (?, ?) OR u.id IN (1, 2)
) )
ORDER BY u.id asc ORDER BY u.id asc
LIMIT 500 LIMIT 500

View File

@ -0,0 +1,9 @@
SELECT org_user.org_id, u.id, u.uid, u.login, u.email, u.name,
u.created, u.updated, u.is_service_account, u.is_disabled, u.is_admin
FROM "user" as u JOIN org_user ON u.id = org_user.user_id
WHERE org_user.org_id = 2 AND ( 1=2
OR uid IN ('a', 'b')
OR u.id IN (1, 2)
)
ORDER BY u.id asc
LIMIT 500

View File

@ -1,8 +1,8 @@
SELECT org_user.org_id, u.id, u.uid, u.login, u.email, u.name, SELECT org_user.org_id, u.id, u.uid, u.login, u.email, u.name,
u.created, u.updated, u.is_service_account, u.is_disabled, u.is_admin u.created, u.updated, u.is_service_account, u.is_disabled, u.is_admin
FROM "user" as u JOIN org_user ON u.id = org_user.user_id FROM "user" as u JOIN org_user ON u.id = org_user.user_id
WHERE org_user.org_id = $1 AND ( 1=2 WHERE org_user.org_id = 2 AND ( 1=2
OR u.id IN ($2, $3) OR uid IN ('a', 'b')
) )
ORDER BY u.id asc ORDER BY u.id asc
LIMIT 500 LIMIT 500

View File

@ -1,5 +1,5 @@
SELECT id, uid, name, email, created, updated SELECT id, uid, name, email, created, updated
FROM "team" FROM "team"
WHERE org_id = $1 WHERE org_id = 0
ORDER BY id asc ORDER BY id asc
LIMIT $2 LIMIT 5

View File

@ -0,0 +1,6 @@
SELECT id, uid, name, email, created, updated
FROM "team"
WHERE org_id = 0
AND id > 1
ORDER BY id asc
LIMIT 2

View File

@ -0,0 +1,6 @@
SELECT id, uid, name, email, created, updated
FROM "team"
WHERE org_id = 0
AND uid = 'abc'
ORDER BY id asc
LIMIT 0

View File

@ -1,7 +1,7 @@
SELECT org_user.org_id, u.id, u.uid, u.login, u.email, u.name, SELECT org_user.org_id, u.id, u.uid, u.login, u.email, u.name,
u.created, u.updated, u.is_service_account, u.is_disabled, u.is_admin u.created, u.updated, u.is_service_account, u.is_disabled, u.is_admin
FROM "user" as u JOIN org_user ON u.id = org_user.user_id FROM "user" as u JOIN org_user ON u.id = org_user.user_id
WHERE org_user.org_id = $1 WHERE org_user.org_id = 0
AND u.is_service_account = $2 AND u.is_service_account = FALSE
ORDER BY u.id asc ORDER BY u.id asc
LIMIT $3 LIMIT 5

View File

@ -0,0 +1,8 @@
SELECT org_user.org_id, u.id, u.uid, u.login, u.email, u.name,
u.created, u.updated, u.is_service_account, u.is_disabled, u.is_admin
FROM "user" as u JOIN org_user ON u.id = org_user.user_id
WHERE org_user.org_id = 0
AND u.is_service_account = FALSE
AND id > 1
ORDER BY u.id asc
LIMIT 2

View File

@ -0,0 +1,8 @@
SELECT org_user.org_id, u.id, u.uid, u.login, u.email, u.name,
u.created, u.updated, u.is_service_account, u.is_disabled, u.is_admin
FROM "user" as u JOIN org_user ON u.id = org_user.user_id
WHERE org_user.org_id = 0
AND u.is_service_account = FALSE
AND uid = 'abc'
ORDER BY u.id asc
LIMIT 0

View File

@ -1,6 +0,0 @@
SELECT id, uid, name, email, created, updated
FROM "team"
WHERE org_id = ?
AND id > ?
ORDER BY id asc
LIMIT ?

View File

@ -1,6 +0,0 @@
SELECT id, uid, name, email, created, updated
FROM "team"
WHERE org_id = ?
AND uid = ?
ORDER BY id asc
LIMIT ?

View File

@ -1,8 +0,0 @@
SELECT org_user.org_id, u.id, u.uid, u.login, u.email, u.name,
u.created, u.updated, u.is_service_account, u.is_disabled, u.is_admin
FROM "user" as u JOIN org_user ON u.id = org_user.user_id
WHERE org_user.org_id = ?
AND u.is_service_account = ?
AND id > ?
ORDER BY u.id asc
LIMIT ?

View File

@ -1,8 +0,0 @@
SELECT org_user.org_id, u.id, u.uid, u.login, u.email, u.name,
u.created, u.updated, u.is_service_account, u.is_disabled, u.is_admin
FROM "user" as u JOIN org_user ON u.id = org_user.user_id
WHERE org_user.org_id = ?
AND u.is_service_account = ?
AND uid = ?
ORDER BY u.id asc
LIMIT ?

View File

@ -136,18 +136,18 @@ func (b *backend) create(ctx context.Context, event resource.WriteEvent) (int64,
// 1. Insert into resource // 1. Insert into resource
if _, err := dbutil.Exec(ctx, tx, sqlResourceInsert, sqlResourceRequest{ if _, err := dbutil.Exec(ctx, tx, sqlResourceInsert, sqlResourceRequest{
SQLTemplate: sqltemplate.New(b.dialect), SQLTemplateIface: sqltemplate.New(b.dialect),
WriteEvent: event, WriteEvent: event,
GUID: guid, GUID: guid,
}); err != nil { }); err != nil {
return fmt.Errorf("insert into resource: %w", err) return fmt.Errorf("insert into resource: %w", err)
} }
// 2. Insert into resource history // 2. Insert into resource history
if _, err := dbutil.Exec(ctx, tx, sqlResourceHistoryInsert, sqlResourceRequest{ if _, err := dbutil.Exec(ctx, tx, sqlResourceHistoryInsert, sqlResourceRequest{
SQLTemplate: sqltemplate.New(b.dialect), SQLTemplateIface: sqltemplate.New(b.dialect),
WriteEvent: event, WriteEvent: event,
GUID: guid, GUID: guid,
}); err != nil { }); err != nil {
return fmt.Errorf("insert into resource history: %w", err) return fmt.Errorf("insert into resource history: %w", err)
} }
@ -162,17 +162,17 @@ func (b *backend) create(ctx context.Context, event resource.WriteEvent) (int64,
// 5. Update the RV in both resource and resource_history // 5. Update the RV in both resource and resource_history
if _, err = dbutil.Exec(ctx, tx, sqlResourceHistoryUpdateRV, sqlResourceUpdateRVRequest{ if _, err = dbutil.Exec(ctx, tx, sqlResourceHistoryUpdateRV, sqlResourceUpdateRVRequest{
SQLTemplate: sqltemplate.New(b.dialect), SQLTemplateIface: sqltemplate.New(b.dialect),
GUID: guid, GUID: guid,
ResourceVersion: rv, ResourceVersion: rv,
}); err != nil { }); err != nil {
return fmt.Errorf("update resource_history rv: %w", err) return fmt.Errorf("update resource_history rv: %w", err)
} }
if _, err = dbutil.Exec(ctx, tx, sqlResourceUpdateRV, sqlResourceUpdateRVRequest{ if _, err = dbutil.Exec(ctx, tx, sqlResourceUpdateRV, sqlResourceUpdateRVRequest{
SQLTemplate: sqltemplate.New(b.dialect), SQLTemplateIface: sqltemplate.New(b.dialect),
GUID: guid, GUID: guid,
ResourceVersion: rv, ResourceVersion: rv,
}); err != nil { }); err != nil {
return fmt.Errorf("update resource rv: %w", err) return fmt.Errorf("update resource rv: %w", err)
} }
@ -194,9 +194,9 @@ func (b *backend) update(ctx context.Context, event resource.WriteEvent) (int64,
// 1. Update resource // 1. Update resource
_, err := dbutil.Exec(ctx, tx, sqlResourceUpdate, sqlResourceRequest{ _, err := dbutil.Exec(ctx, tx, sqlResourceUpdate, sqlResourceRequest{
SQLTemplate: sqltemplate.New(b.dialect), SQLTemplateIface: sqltemplate.New(b.dialect),
WriteEvent: event, WriteEvent: event,
GUID: guid, GUID: guid,
}) })
if err != nil { if err != nil {
return fmt.Errorf("initial resource update: %w", err) return fmt.Errorf("initial resource update: %w", err)
@ -204,9 +204,9 @@ func (b *backend) update(ctx context.Context, event resource.WriteEvent) (int64,
// 2. Insert into resource history // 2. Insert into resource history
if _, err := dbutil.Exec(ctx, tx, sqlResourceHistoryInsert, sqlResourceRequest{ if _, err := dbutil.Exec(ctx, tx, sqlResourceHistoryInsert, sqlResourceRequest{
SQLTemplate: sqltemplate.New(b.dialect), SQLTemplateIface: sqltemplate.New(b.dialect),
WriteEvent: event, WriteEvent: event,
GUID: guid, GUID: guid,
}); err != nil { }); err != nil {
return fmt.Errorf("insert into resource history: %w", err) return fmt.Errorf("insert into resource history: %w", err)
} }
@ -221,17 +221,17 @@ func (b *backend) update(ctx context.Context, event resource.WriteEvent) (int64,
// 5. Update the RV in both resource and resource_history // 5. Update the RV in both resource and resource_history
if _, err = dbutil.Exec(ctx, tx, sqlResourceHistoryUpdateRV, sqlResourceUpdateRVRequest{ if _, err = dbutil.Exec(ctx, tx, sqlResourceHistoryUpdateRV, sqlResourceUpdateRVRequest{
SQLTemplate: sqltemplate.New(b.dialect), SQLTemplateIface: sqltemplate.New(b.dialect),
GUID: guid, GUID: guid,
ResourceVersion: rv, ResourceVersion: rv,
}); err != nil { }); err != nil {
return fmt.Errorf("update history rv: %w", err) return fmt.Errorf("update history rv: %w", err)
} }
if _, err = dbutil.Exec(ctx, tx, sqlResourceUpdateRV, sqlResourceUpdateRVRequest{ if _, err = dbutil.Exec(ctx, tx, sqlResourceUpdateRV, sqlResourceUpdateRVRequest{
SQLTemplate: sqltemplate.New(b.dialect), SQLTemplateIface: sqltemplate.New(b.dialect),
GUID: guid, GUID: guid,
ResourceVersion: rv, ResourceVersion: rv,
}); err != nil { }); err != nil {
return fmt.Errorf("update resource rv: %w", err) return fmt.Errorf("update resource rv: %w", err)
} }
@ -254,9 +254,9 @@ func (b *backend) delete(ctx context.Context, event resource.WriteEvent) (int64,
// 1. delete from resource // 1. delete from resource
_, err := dbutil.Exec(ctx, tx, sqlResourceDelete, sqlResourceRequest{ _, err := dbutil.Exec(ctx, tx, sqlResourceDelete, sqlResourceRequest{
SQLTemplate: sqltemplate.New(b.dialect), SQLTemplateIface: sqltemplate.New(b.dialect),
WriteEvent: event, WriteEvent: event,
GUID: guid, GUID: guid,
}) })
if err != nil { if err != nil {
return fmt.Errorf("delete resource: %w", err) return fmt.Errorf("delete resource: %w", err)
@ -264,9 +264,9 @@ func (b *backend) delete(ctx context.Context, event resource.WriteEvent) (int64,
// 2. Add event to resource history // 2. Add event to resource history
if _, err := dbutil.Exec(ctx, tx, sqlResourceHistoryInsert, sqlResourceRequest{ if _, err := dbutil.Exec(ctx, tx, sqlResourceHistoryInsert, sqlResourceRequest{
SQLTemplate: sqltemplate.New(b.dialect), SQLTemplateIface: sqltemplate.New(b.dialect),
WriteEvent: event, WriteEvent: event,
GUID: guid, GUID: guid,
}); err != nil { }); err != nil {
return fmt.Errorf("insert into resource history: %w", err) return fmt.Errorf("insert into resource history: %w", err)
} }
@ -281,9 +281,9 @@ func (b *backend) delete(ctx context.Context, event resource.WriteEvent) (int64,
// 5. Update the RV in resource_history // 5. Update the RV in resource_history
if _, err = dbutil.Exec(ctx, tx, sqlResourceHistoryUpdateRV, sqlResourceUpdateRVRequest{ if _, err = dbutil.Exec(ctx, tx, sqlResourceHistoryUpdateRV, sqlResourceUpdateRVRequest{
SQLTemplate: sqltemplate.New(b.dialect), SQLTemplateIface: sqltemplate.New(b.dialect),
GUID: guid, GUID: guid,
ResourceVersion: rv, ResourceVersion: rv,
}); err != nil { }); err != nil {
return fmt.Errorf("update history rv: %w", err) return fmt.Errorf("update history rv: %w", err)
} }
@ -302,9 +302,9 @@ func (b *backend) ReadResource(ctx context.Context, req *resource.ReadRequest) *
// TODO: validate key ? // TODO: validate key ?
readReq := sqlResourceReadRequest{ readReq := sqlResourceReadRequest{
SQLTemplate: sqltemplate.New(b.dialect), SQLTemplateIface: sqltemplate.New(b.dialect),
Request: req, Request: req,
readResponse: new(readResponse), readResponse: new(readResponse),
} }
sr := sqlResourceRead sr := sqlResourceRead
@ -418,8 +418,8 @@ func (b *backend) listLatest(ctx context.Context, req *resource.ListRequest, cb
} }
listReq := sqlResourceListRequest{ listReq := sqlResourceListRequest{
SQLTemplate: sqltemplate.New(b.dialect), SQLTemplateIface: sqltemplate.New(b.dialect),
Request: new(resource.ListRequest), Request: new(resource.ListRequest),
} }
listReq.Request = proto.Clone(req).(*resource.ListRequest) listReq.Request = proto.Clone(req).(*resource.ListRequest)
@ -467,7 +467,7 @@ func (b *backend) listAtRevision(ctx context.Context, req *resource.ListRequest,
limit = math.MaxInt64 // a limit is required for offset limit = math.MaxInt64 // a limit is required for offset
} }
listReq := sqlResourceHistoryListRequest{ listReq := sqlResourceHistoryListRequest{
SQLTemplate: sqltemplate.New(b.dialect), SQLTemplateIface: sqltemplate.New(b.dialect),
Request: &historyListRequest{ Request: &historyListRequest{
ResourceVersion: iter.listRV, ResourceVersion: iter.listRV,
Limit: limit, Limit: limit,
@ -554,7 +554,7 @@ func (b *backend) poller(ctx context.Context, since groupResourceRV, stream chan
func (b *backend) listLatestRVs(ctx context.Context) (groupResourceRV, error) { func (b *backend) listLatestRVs(ctx context.Context) (groupResourceRV, error) {
since := groupResourceRV{} since := groupResourceRV{}
reqRVs := sqlResourceVersionListRequest{ reqRVs := sqlResourceVersionListRequest{
SQLTemplate: sqltemplate.New(b.dialect), SQLTemplateIface: sqltemplate.New(b.dialect),
groupResourceVersion: new(groupResourceVersion), groupResourceVersion: new(groupResourceVersion),
} }
query, err := sqltemplate.Execute(sqlResourceVersionList, reqRVs) query, err := sqltemplate.Execute(sqlResourceVersionList, reqRVs)
@ -585,11 +585,11 @@ func (b *backend) listLatestRVs(ctx context.Context) (groupResourceRV, error) {
// fetchLatestRV returns the current maximum RV in the resource table // fetchLatestRV returns the current maximum RV in the resource table
func fetchLatestRV(ctx context.Context, x db.ContextExecer, d sqltemplate.Dialect, group, resource string) (int64, error) { func fetchLatestRV(ctx context.Context, x db.ContextExecer, d sqltemplate.Dialect, group, resource string) (int64, error) {
res, err := dbutil.QueryRow(ctx, x, sqlResourceVersionGet, sqlResourceVersionRequest{ res, err := dbutil.QueryRow(ctx, x, sqlResourceVersionGet, sqlResourceVersionRequest{
SQLTemplate: sqltemplate.New(d), SQLTemplateIface: sqltemplate.New(d),
Group: group, Group: group,
Resource: resource, Resource: resource,
ReadOnly: true, ReadOnly: true,
resourceVersion: new(resourceVersion), resourceVersion: new(resourceVersion),
}) })
if errors.Is(err, sql.ErrNoRows) { if errors.Is(err, sql.ErrNoRows) {
return 1, nil return 1, nil
@ -604,7 +604,7 @@ func (b *backend) poll(ctx context.Context, grp string, res string, since int64,
defer span.End() defer span.End()
pollReq := sqlResourceHistoryPollRequest{ pollReq := sqlResourceHistoryPollRequest{
SQLTemplate: sqltemplate.New(b.dialect), SQLTemplateIface: sqltemplate.New(b.dialect),
Resource: res, Resource: res,
Group: grp, Group: grp,
SinceResourceVersion: since, SinceResourceVersion: since,
@ -661,19 +661,19 @@ func resourceVersionAtomicInc(ctx context.Context, x db.ContextExecer, d sqltemp
// TODO: refactor this code to run in a multi-statement transaction in order to minimize the number of round trips. // TODO: refactor this code to run in a multi-statement transaction in order to minimize the number of round trips.
// 1 Lock the row for update // 1 Lock the row for update
rv, err := dbutil.QueryRow(ctx, x, sqlResourceVersionGet, sqlResourceVersionRequest{ rv, err := dbutil.QueryRow(ctx, x, sqlResourceVersionGet, sqlResourceVersionRequest{
SQLTemplate: sqltemplate.New(d), SQLTemplateIface: sqltemplate.New(d),
Group: key.Group, Group: key.Group,
Resource: key.Resource, Resource: key.Resource,
resourceVersion: new(resourceVersion), resourceVersion: new(resourceVersion),
}) })
if errors.Is(err, sql.ErrNoRows) { if errors.Is(err, sql.ErrNoRows) {
// if there wasn't a row associated with the given resource, we create one with // if there wasn't a row associated with the given resource, we create one with
// version 1 // version 1
if _, err = dbutil.Exec(ctx, x, sqlResourceVersionInsert, sqlResourceVersionRequest{ if _, err = dbutil.Exec(ctx, x, sqlResourceVersionInsert, sqlResourceVersionRequest{
SQLTemplate: sqltemplate.New(d), SQLTemplateIface: sqltemplate.New(d),
Group: key.Group, Group: key.Group,
Resource: key.Resource, Resource: key.Resource,
}); err != nil { }); err != nil {
return 0, fmt.Errorf("insert into resource_version: %w", err) return 0, fmt.Errorf("insert into resource_version: %w", err)
} }
@ -687,9 +687,9 @@ func resourceVersionAtomicInc(ctx context.Context, x db.ContextExecer, d sqltemp
// 2. Increment the resource version // 2. Increment the resource version
_, err = dbutil.Exec(ctx, x, sqlResourceVersionInc, sqlResourceVersionRequest{ _, err = dbutil.Exec(ctx, x, sqlResourceVersionInc, sqlResourceVersionRequest{
SQLTemplate: sqltemplate.New(d), SQLTemplateIface: sqltemplate.New(d),
Group: key.Group, Group: key.Group,
Resource: key.Resource, Resource: key.Resource,
resourceVersion: &resourceVersion{ resourceVersion: &resourceVersion{
ResourceVersion: nextRV, ResourceVersion: nextRV,
}, },

View File

@ -58,7 +58,7 @@ var (
) )
type sqlResourceRequest struct { type sqlResourceRequest struct {
*sqltemplate.SQLTemplate sqltemplate.SQLTemplateIface
GUID string GUID string
WriteEvent resource.WriteEvent WriteEvent resource.WriteEvent
} }
@ -80,7 +80,7 @@ func (r *historyPollResponse) Results() (*historyPollResponse, error) {
type groupResourceRV map[string]map[string]int64 type groupResourceRV map[string]map[string]int64
type sqlResourceHistoryPollRequest struct { type sqlResourceHistoryPollRequest struct {
*sqltemplate.SQLTemplate sqltemplate.SQLTemplateIface
Resource string Resource string
Group string Group string
SinceResourceVersion int64 SinceResourceVersion int64
@ -102,7 +102,7 @@ func (r *readResponse) Results() (*readResponse, error) {
} }
type sqlResourceReadRequest struct { type sqlResourceReadRequest struct {
*sqltemplate.SQLTemplate sqltemplate.SQLTemplateIface
Request *resource.ReadRequest Request *resource.ReadRequest
*readResponse *readResponse
} }
@ -113,7 +113,7 @@ func (r sqlResourceReadRequest) Validate() error {
// List // List
type sqlResourceListRequest struct { type sqlResourceListRequest struct {
*sqltemplate.SQLTemplate sqltemplate.SQLTemplateIface
Request *resource.ListRequest Request *resource.ListRequest
} }
@ -126,7 +126,7 @@ type historyListRequest struct {
Options *resource.ListOptions Options *resource.ListOptions
} }
type sqlResourceHistoryListRequest struct { type sqlResourceHistoryListRequest struct {
*sqltemplate.SQLTemplate sqltemplate.SQLTemplateIface
Request *historyListRequest Request *historyListRequest
Response *resource.ResourceWrapper Response *resource.ResourceWrapper
} }
@ -150,7 +150,7 @@ func (r sqlResourceHistoryListRequest) Results() (*resource.ResourceWrapper, err
// update RV // update RV
type sqlResourceUpdateRVRequest struct { type sqlResourceUpdateRVRequest struct {
*sqltemplate.SQLTemplate sqltemplate.SQLTemplateIface
GUID string GUID string
ResourceVersion int64 ResourceVersion int64
} }
@ -174,7 +174,7 @@ func (r *resourceVersion) Results() (*resourceVersion, error) {
} }
type sqlResourceVersionRequest struct { type sqlResourceVersionRequest struct {
*sqltemplate.SQLTemplate sqltemplate.SQLTemplateIface
Group, Resource string Group, Resource string
ReadOnly bool ReadOnly bool
*resourceVersion *resourceVersion
@ -185,7 +185,7 @@ func (r sqlResourceVersionRequest) Validate() error {
} }
type sqlResourceVersionListRequest struct { type sqlResourceVersionListRequest struct {
*sqltemplate.SQLTemplate sqltemplate.SQLTemplateIface
*groupResourceVersion *groupResourceVersion
} }

View File

@ -1,353 +1,184 @@
package sql package sql
import ( import (
"embed"
"testing" "testing"
"text/template" "text/template"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/storage/unified/resource" "github.com/grafana/grafana/pkg/storage/unified/resource"
"github.com/grafana/grafana/pkg/storage/unified/sql/sqltemplate" "github.com/grafana/grafana/pkg/storage/unified/sql/sqltemplate/mocks"
) )
//go:embed testdata/*
var testdataFS embed.FS
func testdata(t *testing.T, filename string) []byte {
t.Helper()
b, err := testdataFS.ReadFile(`testdata/` + filename)
require.NoError(t, err)
return b
}
func TestQueries(t *testing.T) { func TestQueries(t *testing.T) {
t.Parallel() mocks.CheckQuerySnapshots(t, mocks.TemplateTestSetup{
RootDir: "testdata",
// Each template has one or more test cases, each identified with a Templates: map[*template.Template][]mocks.TemplateTestCase{
// descriptive name (e.g. "happy path", "error twiddling the frobb"). Each sqlResourceDelete: {
// of them will test that for the same input data they must produce a result {
// that will depend on the Dialect. Expected queries should be defined in Name: "simple",
// separate files in the testdata directory. This improves the testing Data: &sqlResourceRequest{
// experience by separating test data from test code, since mixing both SQLTemplateIface: mocks.NewTestingSQLTemplate(),
// tends to make it more difficult to reason about what is being done, WriteEvent: resource.WriteEvent{
// especially as we want testing code to scale and make it easy to add
// tests.
type (
// type aliases to make code more semantic and self-documenting
resultSQLFilename = string
dialects = []sqltemplate.Dialect
expected map[resultSQLFilename]dialects
testCase = struct {
Name string
// Data should be the struct passed to the template.
Data sqltemplate.SQLTemplateIface
// Expected maps the filename containing the expected result query
// to the list of dialects that would produce it. For simple
// queries, it is possible that more than one dialect produce the
// same output. The filename is expected to be in the `testdata`
// directory.
Expected expected
}
)
// Define tests cases. Most templates are trivial and testing that they
// generate correct code for a single Dialect is fine, since the one thing
// that always changes is how SQL placeholder arguments are passed (most
// Dialects use `?` while PostgreSQL uses `$1`, `$2`, etc.), and that is
// something that should be tested in the Dialect implementation instead of
// here. We will ask to have at least one test per SQL template, and we will
// lean to test MySQL. Templates containing branching (conditionals, loops,
// etc.) should be exercised at least once in each of their branches.
//
// NOTE: in the Data field, make sure to have pointers populated to simulate
// data is set as it would be in a real request. The data being correctly
// populated in each case should be tested in integration tests, where the
// data will actually flow to and from a real database. In this tests we
// only care about producing the correct SQL.
testCases := map[*template.Template][]*testCase{
sqlResourceDelete: {
{
Name: "single path",
Data: &sqlResourceRequest{
SQLTemplate: new(sqltemplate.SQLTemplate),
WriteEvent: resource.WriteEvent{
Key: &resource.ResourceKey{},
},
},
Expected: expected{
"resource_delete_mysql_sqlite.sql": dialects{
sqltemplate.MySQL,
sqltemplate.SQLite,
},
"resource_delete_postgres.sql": dialects{
sqltemplate.PostgreSQL,
},
},
},
},
sqlResourceInsert: {
{
Name: "insert into resource",
Data: &sqlResourceRequest{
SQLTemplate: new(sqltemplate.SQLTemplate),
WriteEvent: resource.WriteEvent{
Key: &resource.ResourceKey{},
},
},
Expected: expected{
"resource_insert_mysql_sqlite.sql": dialects{
sqltemplate.MySQL,
sqltemplate.SQLite,
},
},
},
},
sqlResourceUpdate: {
{
Name: "single path",
Data: &sqlResourceRequest{
SQLTemplate: new(sqltemplate.SQLTemplate),
WriteEvent: resource.WriteEvent{
Key: &resource.ResourceKey{},
},
},
Expected: expected{
"resource_update_mysql_sqlite.sql": dialects{
sqltemplate.MySQL,
sqltemplate.SQLite,
},
},
},
},
sqlResourceRead: {
{
Name: "without resource version",
Data: &sqlResourceReadRequest{
SQLTemplate: new(sqltemplate.SQLTemplate),
Request: &resource.ReadRequest{
Key: &resource.ResourceKey{},
},
readResponse: new(readResponse),
},
Expected: expected{
"resource_read_mysql_sqlite.sql": dialects{
sqltemplate.MySQL,
sqltemplate.SQLite,
},
},
},
},
sqlResourceList: {
{
Name: "filter on namespace",
Data: &sqlResourceListRequest{
SQLTemplate: new(sqltemplate.SQLTemplate),
Request: &resource.ListRequest{
Limit: 10,
Options: &resource.ListOptions{
Key: &resource.ResourceKey{ Key: &resource.ResourceKey{
Namespace: "ns", Namespace: "nn",
Group: "gg",
Resource: "rr",
Name: "name",
}, },
}, },
}, },
}, },
Expected: expected{ },
"resource_list_mysql_sqlite.sql": dialects{ sqlResourceInsert: {
sqltemplate.MySQL, {
sqltemplate.SQLite, Name: "simple",
Data: &sqlResourceRequest{
SQLTemplateIface: mocks.NewTestingSQLTemplate(),
WriteEvent: resource.WriteEvent{
Key: &resource.ResourceKey{
Namespace: "nn",
Group: "gg",
Resource: "rr",
Name: "name",
},
Type: resource.WatchEvent_ADDED,
PreviousRV: 123,
},
},
},
},
sqlResourceUpdate: {
{
Name: "single path",
Data: &sqlResourceRequest{
SQLTemplateIface: mocks.NewTestingSQLTemplate(),
WriteEvent: resource.WriteEvent{
Key: &resource.ResourceKey{},
},
},
},
},
sqlResourceRead: {
{
Name: "without_resource_version",
Data: &sqlResourceReadRequest{
SQLTemplateIface: mocks.NewTestingSQLTemplate(),
Request: &resource.ReadRequest{
Key: &resource.ResourceKey{},
},
readResponse: new(readResponse),
}, },
}, },
}, },
},
sqlResourceHistoryList: { sqlResourceList: {
{ {
Name: "single path", Name: "filter_on_namespace",
Data: &sqlResourceHistoryListRequest{ Data: &sqlResourceListRequest{
SQLTemplate: new(sqltemplate.SQLTemplate), SQLTemplateIface: mocks.NewTestingSQLTemplate(),
Request: &historyListRequest{ Request: &resource.ListRequest{
Limit: 10, Limit: 10,
Options: &resource.ListOptions{ Options: &resource.ListOptions{
Key: &resource.ResourceKey{ Key: &resource.ResourceKey{
Namespace: "ns", Namespace: "ns",
},
}, },
}, },
}, },
Response: new(resource.ResourceWrapper),
}, },
Expected: expected{ },
"resource_history_list_mysql_sqlite.sql": dialects{
sqltemplate.MySQL, sqlResourceHistoryList: {
sqltemplate.SQLite, {
Name: "single path",
Data: &sqlResourceHistoryListRequest{
SQLTemplateIface: mocks.NewTestingSQLTemplate(),
Request: &historyListRequest{
Limit: 10,
Options: &resource.ListOptions{
Key: &resource.ResourceKey{
Namespace: "ns",
},
},
},
Response: new(resource.ResourceWrapper),
}, },
}, },
}, },
},
sqlResourceUpdateRV: { sqlResourceUpdateRV: {
{ {
Name: "single path", Name: "single path",
Data: &sqlResourceUpdateRVRequest{ Data: &sqlResourceUpdateRVRequest{
SQLTemplate: new(sqltemplate.SQLTemplate), SQLTemplateIface: mocks.NewTestingSQLTemplate(),
},
Expected: expected{
"resource_update_rv_mysql_sqlite.sql": dialects{
sqltemplate.MySQL,
sqltemplate.SQLite,
}, },
}, },
}, },
},
sqlResourceHistoryRead: { sqlResourceHistoryRead: {
{ {
Name: "single path", Name: "single path",
Data: &sqlResourceReadRequest{ Data: &sqlResourceReadRequest{
SQLTemplate: new(sqltemplate.SQLTemplate), SQLTemplateIface: mocks.NewTestingSQLTemplate(),
Request: &resource.ReadRequest{ Request: &resource.ReadRequest{
ResourceVersion: 123, ResourceVersion: 123,
Key: &resource.ResourceKey{}, Key: &resource.ResourceKey{},
}, },
readResponse: new(readResponse), readResponse: new(readResponse),
},
Expected: expected{
"resource_history_read_mysql_sqlite.sql": dialects{
sqltemplate.MySQL,
sqltemplate.SQLite,
}, },
}, },
}, },
},
sqlResourceHistoryUpdateRV: { sqlResourceHistoryUpdateRV: {
{ {
Name: "single path", Name: "single path",
Data: &sqlResourceUpdateRVRequest{ Data: &sqlResourceUpdateRVRequest{
SQLTemplate: new(sqltemplate.SQLTemplate), SQLTemplateIface: mocks.NewTestingSQLTemplate(),
},
Expected: expected{
"resource_history_update_rv_mysql_sqlite.sql": dialects{
sqltemplate.MySQL,
sqltemplate.SQLite,
}, },
}, },
}, },
},
sqlResourceHistoryInsert: { sqlResourceHistoryInsert: {
{ {
Name: "insert into resource_history", Name: "insert into resource_history",
Data: &sqlResourceRequest{ Data: &sqlResourceRequest{
SQLTemplate: new(sqltemplate.SQLTemplate), SQLTemplateIface: mocks.NewTestingSQLTemplate(),
WriteEvent: resource.WriteEvent{ WriteEvent: resource.WriteEvent{
Key: &resource.ResourceKey{}, Key: &resource.ResourceKey{},
}, },
},
Expected: expected{
"resource_history_insert_mysql_sqlite.sql": dialects{
sqltemplate.MySQL,
sqltemplate.SQLite,
}, },
}, },
}, },
},
sqlResourceVersionGet: { sqlResourceVersionGet: {
{ {
Name: "single path", Name: "single path",
Data: &sqlResourceVersionRequest{ Data: &sqlResourceVersionRequest{
SQLTemplate: new(sqltemplate.SQLTemplate), SQLTemplateIface: mocks.NewTestingSQLTemplate(),
resourceVersion: new(resourceVersion), resourceVersion: new(resourceVersion),
ReadOnly: false, ReadOnly: false,
},
Expected: expected{
"resource_version_get_mysql.sql": dialects{
sqltemplate.MySQL,
},
"resource_version_get_sqlite.sql": dialects{
sqltemplate.SQLite,
}, },
}, },
}, },
},
sqlResourceVersionInc: { sqlResourceVersionInc: {
{ {
Name: "increment resource version", Name: "increment resource version",
Data: &sqlResourceVersionRequest{ Data: &sqlResourceVersionRequest{
SQLTemplate: new(sqltemplate.SQLTemplate), SQLTemplateIface: mocks.NewTestingSQLTemplate(),
resourceVersion: &resourceVersion{ resourceVersion: &resourceVersion{
ResourceVersion: 123, ResourceVersion: 123,
}, },
},
Expected: expected{
"resource_version_inc_mysql_sqlite.sql": dialects{
sqltemplate.MySQL,
sqltemplate.SQLite,
}, },
}, },
}, },
},
sqlResourceVersionInsert: { sqlResourceVersionInsert: {
{ {
Name: "single path", Name: "single path",
Data: &sqlResourceVersionRequest{ Data: &sqlResourceVersionRequest{
SQLTemplate: new(sqltemplate.SQLTemplate), SQLTemplateIface: mocks.NewTestingSQLTemplate(),
},
Expected: expected{
"resource_version_insert_mysql_sqlite.sql": dialects{
sqltemplate.MySQL,
sqltemplate.SQLite,
}, },
}, },
}, },
}, }})
}
// Execute test cases
for tmpl, tcs := range testCases {
t.Run(tmpl.Name(), func(t *testing.T) {
t.Parallel()
for _, tc := range tcs {
t.Run(tc.Name, func(t *testing.T) {
t.Parallel()
for filename, ds := range tc.Expected {
t.Run(filename, func(t *testing.T) {
// not parallel because we're sharing tc.Data, not
// worth it deep cloning
rawQuery := string(testdata(t, filename))
expectedQuery := sqltemplate.FormatSQL(rawQuery)
for _, d := range ds {
t.Run(d.DialectName(), func(t *testing.T) {
// not parallel for the same reason
tc.Data.SetDialect(d)
err := tc.Data.Validate()
require.NoError(t, err)
got, err := sqltemplate.Execute(tmpl, tc.Data)
require.NoError(t, err)
got = sqltemplate.FormatSQL(got)
require.Equal(t, expectedQuery, got)
})
}
})
}
})
}
})
}
} }

View File

@ -0,0 +1,147 @@
package mocks
import (
"fmt"
"os"
"path/filepath"
reflect "reflect"
"strings"
"testing"
"text/template"
"github.com/google/go-cmp/cmp"
sqltemplate "github.com/grafana/grafana/pkg/storage/unified/sql/sqltemplate"
"github.com/stretchr/testify/require"
)
func NewTestingSQLTemplate() sqltemplate.SQLTemplateIface {
standard := sqltemplate.New(sqltemplate.MySQL) // dialect gets replaced at each iteration
return &testingSQLTemplate{standard}
}
type testingSQLTemplate struct {
*sqltemplate.SQLTemplate
}
func (t *testingSQLTemplate) Arg(x any) string {
_ = t.SQLTemplate.Arg(x) // discard the output
switch v := reflect.ValueOf(x); {
case v.Kind() == reflect.Bool:
if v.Bool() {
return "TRUE"
}
return "FALSE"
case v.CanInt(), v.CanUint(), v.CanFloat():
_, ok := x.(fmt.Stringer)
if !ok {
return fmt.Sprintf("%v", x)
}
}
return fmt.Sprintf("'%v'", x) // single quotes
}
func (t *testingSQLTemplate) ArgList(slice reflect.Value) (string, error) {
// Copied from upstream Arg
if !slice.IsValid() || slice.Kind() != reflect.Slice {
return "", sqltemplate.ErrInvalidArgList
}
sliceLen := slice.Len()
if sliceLen == 0 {
return "", nil
}
var b strings.Builder
b.Grow(3*sliceLen - 2) // the list will be ?, ?, ?
for i, l := 0, slice.Len(); i < l; i++ {
if i > 0 {
b.WriteString(", ")
}
b.WriteString(t.Arg(slice.Index(i).Interface()))
}
return b.String(), nil
}
type TemplateTestCase struct {
Name string
// Data should be the struct passed to the template.
Data sqltemplate.SQLTemplateIface
}
type TemplateTestSetup struct {
// Where the snapshots can be found
RootDir string
// The template will be run through each dialect
Dialects []sqltemplate.Dialect
// Check a set of templates against example inputs
Templates map[*template.Template][]TemplateTestCase
}
func CheckQuerySnapshots(t *testing.T, setup TemplateTestSetup) {
t.Helper()
t.Parallel()
if len(setup.Dialects) < 1 {
setup.Dialects = []sqltemplate.Dialect{
sqltemplate.MySQL,
sqltemplate.SQLite,
sqltemplate.PostgreSQL,
}
}
for tmpl, cases := range setup.Templates {
t.Run(tmpl.Name(), func(t *testing.T) {
t.Parallel()
tname := strings.TrimSuffix(tmpl.Name(), ".sql")
for _, input := range cases {
t.Run(input.Name, func(t *testing.T) {
t.Parallel()
require.NotPanics(t, func() {
for _, dialect := range setup.Dialects {
t.Run(dialect.DialectName(), func(t *testing.T) {
// not parallel because we're sharing tc.Data,
// but also not worth deep cloning
input.Data.SetDialect(dialect)
err := input.Data.Validate()
require.NoError(t, err)
got, err := sqltemplate.Execute(tmpl, input.Data)
require.NoError(t, err)
clean := sqltemplate.RemoveEmptyLines(got)
update := false
fname := fmt.Sprintf("%s--%s-%s.sql", dialect.DialectName(), tname, input.Name)
fpath := filepath.Join(setup.RootDir, fname)
// We can ignore the gosec G304 because this is only for tests
// nolint:gosec
expect, err := os.ReadFile(fpath)
if err != nil || len(expect) < 1 {
update = true
t.Errorf("missing " + fpath)
} else {
if diff := cmp.Diff(string(expect), clean); diff != "" {
t.Errorf("%s: %s", fname, diff)
update = true
}
}
if update {
_ = os.WriteFile(fpath, []byte(clean), 0777)
}
})
}
})
})
}
})
}
}

View File

@ -11,21 +11,25 @@ import (
var ( var (
ErrValidationNotImplemented = errors.New("validation not implemented") ErrValidationNotImplemented = errors.New("validation not implemented")
ErrSQLTemplateNoSerialize = errors.New("SQLTemplate should not be serialized") ErrSQLTemplateNoSerialize = errors.New("SQLTemplate should not be serialized")
// Make sure SQLTemplate implements the interface
_ SQLTemplateIface = (*SQLTemplate)(nil)
) )
// SQLTemplate provides comprehensive support for SQL templating, handling // SQLTemplate provides comprehensive support for SQL templating, handling
// dialect traits, execution arguments and scanning arguments. // dialect traits, execution arguments and scanning arguments.
type SQLTemplate struct { type SQLTemplate struct {
Dialect Dialect
Args *Args
ScanDest *ScanDest
} }
// New returns a nee *SQLTemplate that will use the given dialect. // New returns a nee *SQLTemplate that will use the given dialect.
func New(d Dialect) *SQLTemplate { func New(d Dialect) *SQLTemplate {
ret := new(SQLTemplate) ret := new(SQLTemplate)
ret.ScanDest = new(ScanDest)
ret.Args = NewArgs(d)
ret.SetDialect(d) ret.SetDialect(d)
return ret return ret
} }

View File

@ -0,0 +1,7 @@
DELETE FROM "resource"
WHERE 1 = 1
AND "namespace" = 'nn'
AND "group" = 'gg'
AND "resource" = 'rr'
AND "name" = 'name'
;

View File

@ -0,0 +1,20 @@
INSERT INTO "resource_history"
(
"guid",
"group",
"resource",
"namespace",
"name",
"value",
"action"
)
VALUES (
'',
'',
'',
'',
'',
'[]',
'UNKNOWN'
)
;

View File

@ -0,0 +1,25 @@
SELECT
kv."resource_version",
kv."namespace",
kv."name",
kv."value"
FROM "resource_history" as kv
INNER JOIN (
SELECT "namespace", "group", "resource", "name", max("resource_version") AS "resource_version"
FROM "resource_history" AS mkv
WHERE 1 = 1
AND "resource_version" <= 0
AND "namespace" = 'ns'
GROUP BY mkv."namespace", mkv."group", mkv."resource", mkv."name"
) AS maxkv
ON
maxkv."resource_version" = kv."resource_version"
AND maxkv."namespace" = kv."namespace"
AND maxkv."group" = kv."group"
AND maxkv."resource" = kv."resource"
AND maxkv."name" = kv."name"
WHERE kv."action" != 3
AND kv."namespace" = 'ns'
ORDER BY kv."namespace" ASC, kv."name" ASC
LIMIT 10 OFFSET 0
;

View File

@ -0,0 +1,13 @@
SELECT
"resource_version",
"value"
FROM "resource_history"
WHERE 1 = 1
AND "namespace" = ''
AND "group" = ''
AND "resource" = ''
AND "name" = ''
AND "resource_version" <= 123
ORDER BY "resource_version" DESC
LIMIT 1
;

View File

@ -0,0 +1,4 @@
UPDATE "resource_history"
SET "resource_version" = 0
WHERE "guid" = ''
;

View File

@ -0,0 +1,20 @@
INSERT INTO "resource"
(
"guid",
"group",
"resource",
"namespace",
"name",
"value",
"action"
)
VALUES (
'',
'gg',
'rr',
'nn',
'name',
'[]',
'ADDED'
)
;

View File

@ -0,0 +1,10 @@
SELECT
"resource_version",
"namespace",
"name",
"value"
FROM "resource"
WHERE 1 = 1
AND "namespace" = 'ns'
ORDER BY "namespace" ASC, "name" ASC
;

View File

@ -0,0 +1,10 @@
SELECT
"resource_version",
"value"
FROM "resource"
WHERE 1 = 1
AND "namespace" = ''
AND "group" = ''
AND "resource" = ''
AND "name" = ''
;

View File

@ -0,0 +1,11 @@
UPDATE "resource"
SET
"guid" = '',
"value" = '[]',
"action" = 'UNKNOWN'
WHERE 1 = 1
AND "group" = ''
AND "resource" = ''
AND "namespace" = ''
AND "name" = ''
;

View File

@ -0,0 +1,4 @@
UPDATE "resource"
SET "resource_version" = 0
WHERE "guid" = ''
;

View File

@ -0,0 +1,8 @@
SELECT
"resource_version"
FROM "resource_version"
WHERE 1 = 1
AND "group" = ''
AND "resource" = ''
FOR UPDATE
;

View File

@ -0,0 +1,7 @@
UPDATE "resource_version"
SET
"resource_version" = 123
WHERE 1 = 1
AND "group" = ''
AND "resource" = ''
;

View File

@ -0,0 +1,12 @@
INSERT INTO "resource_version"
(
"group",
"resource",
"resource_version"
)
VALUES (
'',
'',
1
)
;

View File

@ -0,0 +1,7 @@
DELETE FROM "resource"
WHERE 1 = 1
AND "namespace" = 'nn'
AND "group" = 'gg'
AND "resource" = 'rr'
AND "name" = 'name'
;

View File

@ -0,0 +1,20 @@
INSERT INTO "resource_history"
(
"guid",
"group",
"resource",
"namespace",
"name",
"value",
"action"
)
VALUES (
'',
'',
'',
'',
'',
'[]',
'UNKNOWN'
)
;

View File

@ -0,0 +1,25 @@
SELECT
kv."resource_version",
kv."namespace",
kv."name",
kv."value"
FROM "resource_history" as kv
INNER JOIN (
SELECT "namespace", "group", "resource", "name", max("resource_version") AS "resource_version"
FROM "resource_history" AS mkv
WHERE 1 = 1
AND "resource_version" <= 0
AND "namespace" = 'ns'
GROUP BY mkv."namespace", mkv."group", mkv."resource", mkv."name"
) AS maxkv
ON
maxkv."resource_version" = kv."resource_version"
AND maxkv."namespace" = kv."namespace"
AND maxkv."group" = kv."group"
AND maxkv."resource" = kv."resource"
AND maxkv."name" = kv."name"
WHERE kv."action" != 3
AND kv."namespace" = 'ns'
ORDER BY kv."namespace" ASC, kv."name" ASC
LIMIT 10 OFFSET 0
;

View File

@ -0,0 +1,13 @@
SELECT
"resource_version",
"value"
FROM "resource_history"
WHERE 1 = 1
AND "namespace" = ''
AND "group" = ''
AND "resource" = ''
AND "name" = ''
AND "resource_version" <= 123
ORDER BY "resource_version" DESC
LIMIT 1
;

View File

@ -0,0 +1,4 @@
UPDATE "resource_history"
SET "resource_version" = 0
WHERE "guid" = ''
;

View File

@ -0,0 +1,20 @@
INSERT INTO "resource"
(
"guid",
"group",
"resource",
"namespace",
"name",
"value",
"action"
)
VALUES (
'',
'gg',
'rr',
'nn',
'name',
'[]',
'ADDED'
)
;

View File

@ -0,0 +1,10 @@
SELECT
"resource_version",
"namespace",
"name",
"value"
FROM "resource"
WHERE 1 = 1
AND "namespace" = 'ns'
ORDER BY "namespace" ASC, "name" ASC
;

View File

@ -0,0 +1,10 @@
SELECT
"resource_version",
"value"
FROM "resource"
WHERE 1 = 1
AND "namespace" = ''
AND "group" = ''
AND "resource" = ''
AND "name" = ''
;

View File

@ -0,0 +1,11 @@
UPDATE "resource"
SET
"guid" = '',
"value" = '[]',
"action" = 'UNKNOWN'
WHERE 1 = 1
AND "group" = ''
AND "resource" = ''
AND "namespace" = ''
AND "name" = ''
;

View File

@ -0,0 +1,4 @@
UPDATE "resource"
SET "resource_version" = 0
WHERE "guid" = ''
;

Some files were not shown because too many files have changed in this diff Show More