Skip to content

Commit 60785f0

Browse files
committed
sqlstats: add datadriven tests for sql stats
Adds a new datadriven test for sql stats. This datadriven test makes it easy to test expected outputs of sql stats in a deterministic fashion, but not depending on non deterministic fields such as latencies or bytes written. Instead, it simply checks that certain values are greater than 0 or not. The commands on this test support specifying db and app-name args to allow for deterministic results of statement_statistics and transaction_statistics outputs. Epic: None Release note: None
1 parent 5d42f80 commit 60785f0

File tree

7 files changed

+291
-1
lines changed

7 files changed

+291
-1
lines changed

pkg/BUILD.bazel

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -663,6 +663,7 @@ ALL_TESTS = [
663663
"//pkg/sql/sqlstats/sslocal:sslocal_test",
664664
"//pkg/sql/sqlstats/ssmemstorage:ssmemstorage_test",
665665
"//pkg/sql/sqlstats/ssremote:ssremote_test",
666+
"//pkg/sql/sqlstats:sqlstats_test",
666667
"//pkg/sql/sqltestutils:sqltestutils_test",
667668
"//pkg/sql/stats:stats_test",
668669
"//pkg/sql/stmtdiagnostics:stmtdiagnostics_test",
@@ -2417,6 +2418,7 @@ GO_TARGETS = [
24172418
"//pkg/sql/sqlstats/ssremote:ssremote",
24182419
"//pkg/sql/sqlstats/ssremote:ssremote_test",
24192420
"//pkg/sql/sqlstats:sqlstats",
2421+
"//pkg/sql/sqlstats:sqlstats_test",
24202422
"//pkg/sql/sqltelemetry:sqltelemetry",
24212423
"//pkg/sql/sqltestutils:sqltestutils",
24222424
"//pkg/sql/sqltestutils:sqltestutils_test",

pkg/sql/sqlstats/BUILD.bazel

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
load("@io_bazel_rules_go//go:def.bzl", "go_library")
1+
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
22

33
go_library(
44
name = "sqlstats",
@@ -32,3 +32,26 @@ go_library(
3232
"@com_github_cockroachdb_redact//:redact",
3333
],
3434
)
35+
36+
go_test(
37+
name = "sqlstats_test",
38+
srcs = [
39+
"main_test.go",
40+
"sqlstats_test.go",
41+
],
42+
data = glob(["testdata/**"]),
43+
deps = [
44+
":sqlstats",
45+
"//pkg/base",
46+
"//pkg/security/securityassets",
47+
"//pkg/security/securitytest",
48+
"//pkg/server",
49+
"//pkg/testutils/datapathutils",
50+
"//pkg/testutils/serverutils",
51+
"//pkg/testutils/sqlutils",
52+
"//pkg/testutils/testcluster",
53+
"//pkg/util/leaktest",
54+
"//pkg/util/log",
55+
"@com_github_cockroachdb_datadriven//:datadriven",
56+
],
57+
)

pkg/sql/sqlstats/main_test.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// Copyright 2025 The Cockroach Authors.
2+
//
3+
// Use of this software is governed by the CockroachDB Software License
4+
// included in the /LICENSE file.
5+
6+
package sqlstats_test
7+
8+
import (
9+
"os"
10+
"testing"
11+
12+
"github.com/cockroachdb/cockroach/pkg/security/securityassets"
13+
"github.com/cockroachdb/cockroach/pkg/security/securitytest"
14+
"github.com/cockroachdb/cockroach/pkg/server"
15+
"github.com/cockroachdb/cockroach/pkg/testutils/serverutils"
16+
"github.com/cockroachdb/cockroach/pkg/testutils/testcluster"
17+
)
18+
19+
func TestMain(m *testing.M) {
20+
securityassets.SetLoader(securitytest.EmbeddedAssets)
21+
serverutils.InitTestServerFactory(server.TestServerFactory)
22+
serverutils.InitTestClusterFactory(testcluster.TestClusterFactory)
23+
os.Exit(m.Run())
24+
}

pkg/sql/sqlstats/sqlstats_test.go

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
// Copyright 2025 The Cockroach Authors.
2+
//
3+
// Use of this software is governed by the CockroachDB Software License
4+
// included in the /LICENSE file.
5+
6+
package sqlstats_test
7+
8+
import (
9+
"context"
10+
"fmt"
11+
"strings"
12+
"testing"
13+
14+
"github.com/cockroachdb/cockroach/pkg/base"
15+
"github.com/cockroachdb/cockroach/pkg/sql/sqlstats"
16+
"github.com/cockroachdb/cockroach/pkg/testutils/datapathutils"
17+
"github.com/cockroachdb/cockroach/pkg/testutils/serverutils"
18+
"github.com/cockroachdb/cockroach/pkg/testutils/sqlutils"
19+
"github.com/cockroachdb/cockroach/pkg/util/leaktest"
20+
"github.com/cockroachdb/cockroach/pkg/util/log"
21+
"github.com/cockroachdb/datadriven"
22+
)
23+
24+
func TestDataDrivenTest(t *testing.T) {
25+
defer leaktest.AfterTest(t)()
26+
defer log.Scope(t).Close(t)
27+
28+
datadriven.Walk(t, datapathutils.TestDataPath(t), func(t *testing.T, path string) {
29+
s, conn, _ := serverutils.StartServer(t, base.TestServerArgs{
30+
Knobs: base.TestingKnobs{
31+
SQLStatsKnobs: &sqlstats.TestingKnobs{
32+
SynchronousSQLStats: true,
33+
},
34+
},
35+
})
36+
37+
defer s.Stopper().Stop(context.Background())
38+
statsRunner := sqlutils.MakeSQLRunner(conn)
39+
datadriven.RunTest(t, path, func(t *testing.T, d *datadriven.TestData) string {
40+
switch d.Cmd {
41+
case "exec-sql":
42+
sqlConn := s.SQLConn(t)
43+
defer sqlConn.Close()
44+
execSqlRunner := sqlutils.MakeSQLRunner(sqlConn)
45+
var appName, dbName string
46+
if d.HasArg("db") {
47+
d.ScanArgs(t, "db", &dbName)
48+
execSqlRunner.Exec(t, fmt.Sprintf("CREATE DATABASE IF NOT EXISTS %s", dbName))
49+
execSqlRunner.Exec(t, fmt.Sprintf("USE %s", dbName))
50+
} else {
51+
t.Fatalf("db arg is required")
52+
}
53+
if d.HasArg("app-name") {
54+
d.ScanArgs(t, "app-name", &appName)
55+
execSqlRunner.Exec(t, "SET APPLICATION_NAME = $1", appName)
56+
}
57+
execSqlRunner.Exec(t, d.Input)
58+
return ""
59+
case "show-stats":
60+
var appName, dbName string
61+
if d.HasArg("app-name") {
62+
d.ScanArgs(t, "app-name", &appName)
63+
}
64+
if d.HasArg("db") {
65+
d.ScanArgs(t, "db", &dbName)
66+
}
67+
return GetStats(t, statsRunner, appName, dbName)
68+
case "show-txn-stats":
69+
var appName, dbName string
70+
if d.HasArg("app-name") {
71+
d.ScanArgs(t, "app-name", &appName)
72+
}
73+
if d.HasArg("db") {
74+
d.ScanArgs(t, "db", &dbName)
75+
}
76+
return GetTxnStats(t, statsRunner, appName, dbName)
77+
default:
78+
t.Fatalf("unexpected cmd %s", d.Cmd)
79+
}
80+
return ""
81+
})
82+
})
83+
}
84+
85+
func GetStats(t *testing.T, conn *sqlutils.SQLRunner, appName string, dbName string) string {
86+
t.Helper()
87+
query := ` SELECT row_to_json(s) FROM (
88+
SELECT
89+
encode(fingerprint_id, 'hex') AS fingerprint_id,
90+
encode(transaction_fingerprint_id, 'hex') AS transaction_fingerprint_id,
91+
plan_hash != '\x0000000000000000' AS plan_hash_set,
92+
metadata ->> 'query' AS query,
93+
metadata ->> 'querySummary' AS summary,
94+
metadata -> 'distsql' AS plan_distributed,
95+
metadata -> 'implicitTxn' AS plan_implicit_txn,
96+
metadata -> 'vec' AS plan_vectorized,
97+
metadata -> 'fullScan' AS plan_full_scan,
98+
statistics -> 'statistics'->> 'cnt' AS count,
99+
statistics -> 'statistics'-> 'nodes' AS nodes,
100+
statistics -> 'statistics' -> 'sqlType' AS sql_type,
101+
(statistics -> 'statistics' -> 'parseLat' -> 'mean')::FLOAT > 0 AS parse_lat_not_zero,
102+
(statistics -> 'statistics' -> 'planLat' -> 'mean')::FLOAT > 0 AS plan_lat_not_zero,
103+
(statistics -> 'statistics' -> 'runLat' -> 'mean')::FLOAT > 0 AS run_lat_not_zero,
104+
(statistics -> 'statistics' -> 'svcLat' -> 'mean')::FLOAT > 0 AS svc_lat_not_zero
105+
FROM crdb_internal.statement_statistics
106+
WHERE app_name = $1
107+
AND metadata->>'db' = $2
108+
ORDER BY fingerprint_id) s
109+
`
110+
res := conn.QueryStr(t, query, appName, dbName)
111+
rows := make([]string, len(res))
112+
for rowIdx := range res {
113+
rows[rowIdx] = strings.Join(res[rowIdx], ",")
114+
}
115+
return strings.Join(rows, "\n")
116+
}
117+
118+
func GetTxnStats(t *testing.T, conn *sqlutils.SQLRunner, appName string, dbName string) string {
119+
query := ` SELECT row_to_json(s) FROM (
120+
SELECT
121+
encode(fingerprint_id, 'hex') AS txn_fingerprint_id,
122+
metadata->'stmtFingerprintIDs' AS statement_fingerprint_ids,
123+
(statistics -> 'statistics' -> 'commitLat' -> 'mean')::FLOAT > 0 AS commit_lat_not_zero,
124+
(statistics -> 'statistics' -> 'svcLat' -> 'mean')::FLOAT > 0 AS svc_lat_not_zero
125+
FROM crdb_internal.transaction_statistics ts
126+
WHERE ts.fingerprint_id in (
127+
SELECT DISTINCT ss.transaction_fingerprint_id
128+
FROM crdb_internal.statement_statistics ss
129+
WHERE app_name = $1
130+
AND metadata->>'db' = $2
131+
)
132+
ORDER BY fingerprint_id) s
133+
`
134+
res := conn.QueryStr(t, query, appName, dbName)
135+
rows := make([]string, len(res))
136+
for rowIdx := range res {
137+
rows[rowIdx] = strings.Join(res[rowIdx], ",")
138+
}
139+
return strings.Join(rows, "\n")
140+
}

pkg/sql/sqlstats/testdata/plpgsql

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
exec-sql db=udf_test app-name=plpgsql
2+
CREATE TABLE IF NOT EXISTS test(a int);
3+
CREATE OR REPLACE PROCEDURE insert_incremental(n INT)
4+
LANGUAGE plpgsql
5+
AS $$
6+
DECLARE
7+
i INT := 0;
8+
next_val INT;
9+
BEGIN
10+
WHILE i < n LOOP
11+
SELECT COALESCE(MAX(a), 0) + 1 INTO next_val FROM test;
12+
INSERT INTO test(a) VALUES (next_val);
13+
i := i + 1;
14+
END LOOP;
15+
END;
16+
$$;
17+
CALL insert_incremental(5);
18+
----
19+
20+
show-stats db=udf_test app-name=plpgsql
21+
----
22+
{"count": "1", "fingerprint_id": "11c4e5400aa05722", "nodes": [], "parse_lat_not_zero": true, "plan_distributed": false, "plan_full_scan": false, "plan_hash_set": true, "plan_implicit_txn": true, "plan_lat_not_zero": true, "plan_vectorized": true, "query": "CREATE OR REPLACE PROCEDURE insert_incremental(n INT8)\n\tLANGUAGE plpgsql\n\tAS $$_$$", "run_lat_not_zero": true, "sql_type": "TypeDDL", "summary": "CREATE OR REPLACE PROCEDURE insert_incremental(n INT8)\n\tLANGUAGE plpgsql\n\tAS $$_$$", "svc_lat_not_zero": true, "transaction_fingerprint_id": "3013cc07fc915da5"}
23+
{"count": "1", "fingerprint_id": "a82856550b801042", "nodes": [], "parse_lat_not_zero": true, "plan_distributed": false, "plan_full_scan": false, "plan_hash_set": true, "plan_implicit_txn": true, "plan_lat_not_zero": true, "plan_vectorized": true, "query": "CALL insert_incremental(_)", "run_lat_not_zero": true, "sql_type": "TypeTCL", "summary": "CALL insert_incremental(_)", "svc_lat_not_zero": true, "transaction_fingerprint_id": "3013cc07fc915da5"}
24+
{"count": "1", "fingerprint_id": "f592c1a0850da0bf", "nodes": [1], "parse_lat_not_zero": true, "plan_distributed": false, "plan_full_scan": false, "plan_hash_set": true, "plan_implicit_txn": true, "plan_lat_not_zero": true, "plan_vectorized": true, "query": "CREATE TABLE IF NOT EXISTS test (a INT8)", "run_lat_not_zero": true, "sql_type": "TypeDDL", "summary": "CREATE TABLE IF NOT EXISTS test (a INT8)", "svc_lat_not_zero": true, "transaction_fingerprint_id": "5af17cec030c1760"}

pkg/sql/sqlstats/testdata/query

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
exec-sql db=random_db
2+
CREATE TABLE users (id INT PRIMARY KEY, name STRING);
3+
INSERT INTO users VALUES (1, 'Alice'), (2, 'Bob');
4+
SELECT * FROM users;
5+
----
6+
7+
# Sanity check shouldn't return anything
8+
show-stats app-name=random db=random_db
9+
----
10+
11+
show-stats db=random_db
12+
----
13+
{"count": "1", "fingerprint_id": "269c5d2c33dec958", "nodes": [1], "parse_lat_not_zero": true, "plan_distributed": false, "plan_full_scan": false, "plan_hash_set": true, "plan_implicit_txn": true, "plan_lat_not_zero": true, "plan_vectorized": true, "query": "INSERT INTO users VALUES (_, __more__), (__more__)", "run_lat_not_zero": true, "sql_type": "TypeDML", "summary": "INSERT INTO users", "svc_lat_not_zero": true, "transaction_fingerprint_id": "205354584637cfcb"}
14+
{"count": "1", "fingerprint_id": "2cdc86177e8b242b", "nodes": [1], "parse_lat_not_zero": true, "plan_distributed": false, "plan_full_scan": false, "plan_hash_set": true, "plan_implicit_txn": true, "plan_lat_not_zero": true, "plan_vectorized": true, "query": "SET database = random_db", "run_lat_not_zero": true, "sql_type": "TypeDML", "summary": "SET database = random_db", "svc_lat_not_zero": true, "transaction_fingerprint_id": "83bf3b5bf88a93f4"}
15+
{"count": "1", "fingerprint_id": "e6733d9c37147d22", "nodes": [1], "parse_lat_not_zero": true, "plan_distributed": false, "plan_full_scan": true, "plan_hash_set": true, "plan_implicit_txn": true, "plan_lat_not_zero": true, "plan_vectorized": true, "query": "SELECT * FROM users", "run_lat_not_zero": true, "sql_type": "TypeDML", "summary": "SELECT * FROM users", "svc_lat_not_zero": true, "transaction_fingerprint_id": "205354584637cfcb"}
16+
{"count": "1", "fingerprint_id": "ec1bc23e5d2925f6", "nodes": [1], "parse_lat_not_zero": true, "plan_distributed": false, "plan_full_scan": false, "plan_hash_set": true, "plan_implicit_txn": true, "plan_lat_not_zero": true, "plan_vectorized": true, "query": "CREATE TABLE users (id INT8 PRIMARY KEY, name STRING)", "run_lat_not_zero": true, "sql_type": "TypeDDL", "summary": "CREATE TABLE users (id INT8 PRIMARY KEY, name STRING)", "svc_lat_not_zero": true, "transaction_fingerprint_id": "205354584637cfcb"}
17+
18+
exec-sql db=random_db app-name=transactions
19+
BEGIN;
20+
INSERT INTO users VALUES (3, 'Charlie');
21+
INSERT INTO users VALUES (4, 'Diana');
22+
COMMIT;
23+
----
24+
25+
exec-sql db=random_db app-name=transactions
26+
BEGIN;
27+
INSERT INTO users VALUES (5, 'Eve');
28+
INSERT INTO users VALUES (6, 'Frank');
29+
COMMIT;
30+
----
31+
32+
show-stats db=random_db app-name=transactions
33+
----
34+
{"count": "4", "fingerprint_id": "5fe99858726d3688", "nodes": [1], "parse_lat_not_zero": true, "plan_distributed": false, "plan_full_scan": false, "plan_hash_set": true, "plan_implicit_txn": false, "plan_lat_not_zero": true, "plan_vectorized": true, "query": "INSERT INTO users VALUES (_, __more__)", "run_lat_not_zero": true, "sql_type": "TypeDML", "summary": "INSERT INTO users", "svc_lat_not_zero": true, "transaction_fingerprint_id": "78d7c1c32632f05d"}
35+
36+
show-txn-stats db=random_db app-name=transactions
37+
----
38+
{"commit_lat_not_zero": true, "statement_fingerprint_ids": ["5fe99858726d3688", "5fe99858726d3688"], "svc_lat_not_zero": true, "txn_fingerprint_id": "78d7c1c32632f05d"}

pkg/sql/sqlstats/testdata/udf

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
exec-sql db=udf_test app-name=udf
2+
CREATE TABLE IF NOT EXISTS test(a int);
3+
CREATE FUNCTION random_int()
4+
RETURNS INT
5+
LANGUAGE SQL
6+
AS $$
7+
SELECT floor(random() * 100)::INT
8+
$$
9+
VOLATILE;
10+
SELECT random_int();
11+
CREATE FUNCTION do_something()
12+
RETURNS INT
13+
LANGUAGE SQL
14+
AS $$
15+
INSERT INTO test(select random_int());
16+
SELECT max(a) FROM test;
17+
$$
18+
VOLATILE;
19+
select do_something();
20+
----
21+
22+
show-stats db=udf_test app-name=udf
23+
----
24+
{"count": "1", "fingerprint_id": "8be77c892ac9ad38", "nodes": [], "parse_lat_not_zero": true, "plan_distributed": false, "plan_full_scan": false, "plan_hash_set": true, "plan_implicit_txn": true, "plan_lat_not_zero": true, "plan_vectorized": true, "query": "CREATE FUNCTION do_something()\n\tRETURNS INT8\n\tLANGUAGE SQL\n\tVOLATILE\n\tAS $$_$$", "run_lat_not_zero": true, "sql_type": "TypeDDL", "summary": "CREATE FUNCTION do_something()\n\tRETURNS INT8\n\tLANGUAGE SQL\n\tVOLATILE\n\tAS $$_$$", "svc_lat_not_zero": true, "transaction_fingerprint_id": "7a2be5f0c7083376"}
25+
{"count": "1", "fingerprint_id": "af9bcc145f0d85f3", "nodes": [1], "parse_lat_not_zero": true, "plan_distributed": false, "plan_full_scan": false, "plan_hash_set": true, "plan_implicit_txn": true, "plan_lat_not_zero": true, "plan_vectorized": true, "query": "SELECT do_something()", "run_lat_not_zero": true, "sql_type": "TypeDML", "summary": "SELECT do_something()", "svc_lat_not_zero": true, "transaction_fingerprint_id": "7a2be5f0c7083376"}
26+
{"count": "1", "fingerprint_id": "bf1896e478760878", "nodes": [1], "parse_lat_not_zero": true, "plan_distributed": false, "plan_full_scan": false, "plan_hash_set": true, "plan_implicit_txn": true, "plan_lat_not_zero": true, "plan_vectorized": true, "query": "SELECT random_int()", "run_lat_not_zero": true, "sql_type": "TypeDML", "summary": "SELECT random_int()", "svc_lat_not_zero": true, "transaction_fingerprint_id": "3af8b4d3f0ded8e0"}
27+
{"count": "1", "fingerprint_id": "f56262fdc29506d7", "nodes": [], "parse_lat_not_zero": true, "plan_distributed": false, "plan_full_scan": false, "plan_hash_set": true, "plan_implicit_txn": true, "plan_lat_not_zero": true, "plan_vectorized": true, "query": "CREATE FUNCTION random_int()\n\tRETURNS INT8\n\tLANGUAGE SQL\n\tVOLATILE\n\tAS $$_$$", "run_lat_not_zero": true, "sql_type": "TypeDDL", "summary": "CREATE FUNCTION random_int()\n\tRETURNS INT8\n\tLANGUAGE SQL\n\tVOLATILE\n\tAS $$_$$", "svc_lat_not_zero": true, "transaction_fingerprint_id": "3af8b4d3f0ded8e0"}
28+
{"count": "1", "fingerprint_id": "f592c1a0850da0bf", "nodes": [1], "parse_lat_not_zero": true, "plan_distributed": false, "plan_full_scan": false, "plan_hash_set": true, "plan_implicit_txn": true, "plan_lat_not_zero": true, "plan_vectorized": true, "query": "CREATE TABLE IF NOT EXISTS test (a INT8)", "run_lat_not_zero": true, "sql_type": "TypeDDL", "summary": "CREATE TABLE IF NOT EXISTS test (a INT8)", "svc_lat_not_zero": true, "transaction_fingerprint_id": "5af17cec030c1760"}
29+
30+
exec-sql db=udf_test app-name=udf_transactions
31+
BEGIN;
32+
select do_something();
33+
select do_something();
34+
COMMIT;
35+
----
36+
37+
show-stats db=udf_test app-name=udf_transactions
38+
----
39+
{"count": "2", "fingerprint_id": "af9bcc145f0d85ff", "nodes": [1], "parse_lat_not_zero": true, "plan_distributed": false, "plan_full_scan": false, "plan_hash_set": true, "plan_implicit_txn": false, "plan_lat_not_zero": true, "plan_vectorized": true, "query": "SELECT do_something()", "run_lat_not_zero": true, "sql_type": "TypeDML", "summary": "SELECT do_something()", "svc_lat_not_zero": true, "transaction_fingerprint_id": "1dc175ec90b4a99f"}

0 commit comments

Comments
 (0)