Skip to content

Commit 0acad28

Browse files
committed
draft
1 parent 4e667e5 commit 0acad28

File tree

4 files changed

+111
-0
lines changed

4 files changed

+111
-0
lines changed

pkg/BUILD.bazel

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ ALL_TESTS = [
4848
"//pkg/ccl/cloudccl/externalconn:externalconn_test",
4949
"//pkg/ccl/cloudccl/gcp:gcp_test",
5050
"//pkg/ccl/jwtauthccl:jwtauthccl_test",
51+
"//pkg/ccl/kvccl/kvtenantccl/upgradeinterlockccl:upgradeinterlockccl_test",
5152
"//pkg/ccl/ldapccl:ldapccl_test",
5253
"//pkg/ccl/logictestccl/tests/3node-tenant-multiregion:3node-tenant-multiregion_test",
5354
"//pkg/ccl/logictestccl/tests/3node-tenant:3node-tenant_test",
@@ -1018,6 +1019,7 @@ GO_TARGETS = [
10181019
"//pkg/ccl/gssapiccl:gssapiccl",
10191020
"//pkg/ccl/jwtauthccl:jwtauthccl",
10201021
"//pkg/ccl/jwtauthccl:jwtauthccl_test",
1022+
"//pkg/ccl/kvccl/kvtenantccl/upgradeinterlockccl:upgradeinterlockccl_test",
10211023
"//pkg/ccl/ldapccl:ldapccl",
10221024
"//pkg/ccl/ldapccl:ldapccl_test",
10231025
"//pkg/ccl/logictestccl/tests/3node-tenant-multiregion:3node-tenant-multiregion_test",

pkg/sql/explain_bundle.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import (
2828
"github.com/cockroachdb/cockroach/pkg/sql/opt/cat"
2929
"github.com/cockroachdb/cockroach/pkg/sql/opt/exec/explain"
3030
"github.com/cockroachdb/cockroach/pkg/sql/opt/memo"
31+
"github.com/cockroachdb/cockroach/pkg/sql/parserutils"
3132
"github.com/cockroachdb/cockroach/pkg/sql/sem/catconstants"
3233
"github.com/cockroachdb/cockroach/pkg/sql/sem/catid"
3334
"github.com/cockroachdb/cockroach/pkg/sql/sem/tree"
@@ -556,6 +557,52 @@ var stmtBundleIncludeAllFKReferences = settings.RegisterBoolSetting(
556557
false,
557558
)
558559

560+
func (c *stmtEnvCollector) getStatementHints(
561+
ctx context.Context, stmt string, sv *settings.Values,
562+
) (donorSqls []string, err error) {
563+
fingerprintFlags := tree.FmtFlags(tree.QueryFormattingForFingerprintsMask.Get(
564+
sv,
565+
))
566+
fingerprint, err := parserutils.FingerprintStatement(stmt, fingerprintFlags)
567+
if err != nil {
568+
return donorSqls, err
569+
}
570+
571+
query := `SELECT crdb_internal.pb_to_json('hintpb.StatementHintUnion', hint)->'injectHints'->>'donorSql' AS donor_sql FROM system.statement_hints WHERE fingerprint = $1 ORDER BY "created_at"`
572+
573+
rows, err := c.ie.QueryBufferedEx(ctx, "stmtEnvCollector", nil /* txn */, c.ieo, query, fingerprint)
574+
if err != nil {
575+
return donorSqls, err
576+
}
577+
578+
donorSqls = make([]string, len(rows))
579+
for i, row := range rows {
580+
if len(row) == 0 {
581+
return nil, errors.AssertionFailedf("expect not nil row")
582+
}
583+
strDatum, ok := row[0].(*tree.DString)
584+
if !ok {
585+
return nil, errors.AssertionFailedf("expect row to be a string")
586+
}
587+
donorSqls[i] = strDatum.String()
588+
}
589+
return donorSqls, nil
590+
}
591+
592+
func (c *stmtEnvCollector) PrintStmtHints(
593+
ctx context.Context, stmt string, sv *settings.Values, w io.Writer,
594+
) error {
595+
donorSqls, err := c.getStatementHints(ctx, stmt, sv)
596+
if err != nil {
597+
return err
598+
}
599+
for _, donorSql := range donorSqls {
600+
// donorSql has been quoted.
601+
fmt.Fprintf(w, "SELECT information_schema.crdb_rewrite_inline_hints('%s', %s);\n", stmt, donorSql)
602+
}
603+
return err
604+
}
605+
559606
// stmtBundleStatsFileRE is a regex that matches all "complex" characters that
560607
// might not be safe when used in file names on some systems.
561608
var stmtBundleStatsFileRE = regexp.MustCompile(`[^a-zA-Z0-9_.\-]`)
@@ -1007,6 +1054,11 @@ func (b *stmtBundleBuilder) addEnv(ctx context.Context) {
10071054
b.printError(fmt.Sprintf("-- error getting schema for view %s: %v", views[i].FQString(), err), &buf)
10081055
}
10091056
}
1057+
1058+
if err := c.PrintStmtHints(ctx, b.stmt, b.sv, &buf); err != nil {
1059+
b.printError(fmt.Sprintf("-- error getting statement hints: %v", err), &buf)
1060+
}
1061+
10101062
if buf.Len() == 0 {
10111063
buf.WriteString("-- there were no objects used in this query\n")
10121064
}

pkg/sql/explain_bundle_test.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1178,6 +1178,44 @@ CREATE TABLE users(id UUID DEFAULT gen_random_uuid() PRIMARY KEY, promo_id INT R
11781178
)
11791179
})
11801180

1181+
t.Run("statement hints", func(t *testing.T) {
1182+
r.Exec(t, "CREATE TABLE table161829(x INT PRIMARY KEY, y INT)")
1183+
r.Exec(t, "CREATE INDEX xy161829 ON table161829 (x, y)")
1184+
r.Exec(t, "CREATE INDEX y161829 ON table161829 (y)")
1185+
r.Exec(t, `SELECT information_schema.crdb_rewrite_inline_hints('SELECT * FROM table161829 WHERE y = 10', 'SELECT * FROM table161829@primary WHERE y = 10')`)
1186+
r.Exec(t, `SELECT information_schema.crdb_rewrite_inline_hints('SELECT * FROM table161829 WHERE y = 10', 'SELECT * FROM table161829@xy161829 WHERE y = 10')`)
1187+
r.Exec(t, `SELECT information_schema.crdb_rewrite_inline_hints('SELECT * FROM table161829 WHERE y = 10', 'SELECT * FROM table161829@y161829 WHERE y = 10')`)
1188+
1189+
rows := r.QueryStr(t, "EXPLAIN ANALYZE (DEBUG) SELECT * FROM table161829 WHERE y = 10")
1190+
checkBundle(
1191+
t, fmt.Sprint(rows), "table161829", func(name, contents string) error {
1192+
if name == "schema.sql" {
1193+
reg := regexp.MustCompile(`crdb_rewrite_inline_hints\(.*\@primary.*\n.*crdb_rewrite_inline_hints.*\@xy161829.*\n.*crdb_rewrite_inline_hints.*\@y161829`)
1194+
if reg.FindString(contents) == "" {
1195+
return errors.Errorf("could not find full crdb_rewrite_inline_hints in schema.sql")
1196+
}
1197+
}
1198+
return nil
1199+
}, false, /* expectErrors */
1200+
base, plans, "distsql.html vec.txt vec-v.txt stats-defaultdb.public.table161829.sql",
1201+
)
1202+
1203+
// Bundle for statements that share the same fingerprint should all see the same hints.
1204+
rows = r.QueryStr(t, "EXPLAIN ANALYZE (DEBUG) SELECT * FROM table161829 WHERE y = 100")
1205+
checkBundle(
1206+
t, fmt.Sprint(rows), "table161829", func(name, contents string) error {
1207+
if name == "schema.sql" {
1208+
reg := regexp.MustCompile(`crdb_rewrite_inline_hints\(.*\@primary.*\n.*crdb_rewrite_inline_hints.*\@xy161829.*\n.*crdb_rewrite_inline_hints.*\@y161829`)
1209+
if reg.FindString(contents) == "" {
1210+
return errors.Errorf("could not find full crdb_rewrite_inline_hints in schema.sql")
1211+
}
1212+
}
1213+
return nil
1214+
}, false, /* expectErrors */
1215+
base, plans, "distsql.html vec.txt vec-v.txt stats-defaultdb.public.table161829.sql",
1216+
)
1217+
})
1218+
11811219
// TODO(yuzefovich): figure out why this test occasionally fails under
11821220
// stress (i.e. it seems that no bundle is collected altogether).
11831221
//t.Run("under different users", func(t *testing.T) {

pkg/sql/parserutils/utils.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,3 +105,22 @@ var ParseQualifiedTableName = func(sql string) (*tree.TableName, error) {
105105
var PLpgSQLParse = func(sql string) (statements.PLpgStatement, error) {
106106
return statements.PLpgStatement{}, errors.AssertionFailedf("sql.DoParserInjection hasn't been called")
107107
}
108+
109+
// FingerprintStatement parses a SQL string and converts it to a statement
110+
// fingerprint. If the input is already a fingerprint, it is returned as-is.
111+
// The optional extraFlags are OR'd into the default FmtHideConstants flags used
112+
// for fingerprinting.
113+
func FingerprintStatement(sql string, extraFlags ...tree.FmtFlags) (string, error) {
114+
stmts, err := Parse(sql)
115+
if err != nil {
116+
return "", pgerror.Wrapf(
117+
err, pgcode.InvalidParameterValue, "could not parse statement",
118+
)
119+
}
120+
if len(stmts) != 1 {
121+
return "", pgerror.Newf(
122+
pgcode.InvalidParameterValue, "could not parse as a single SQL statement",
123+
)
124+
}
125+
return tree.FormatStatementHideConstants(stmts[0].AST, extraFlags...), nil
126+
}

0 commit comments

Comments
 (0)