Skip to content

Commit 2aff402

Browse files
craig[bot]jeffswenson
andcommitted
Merge #148024
148024: workload/rand: refactor schema handling for reuse r=jeffswenson a=jeffswenson The rand workload has logic for loading a table from a cluster and adapting the randgen package for use with a gosql client. This change extracts that logic into a reusable `LoadTable` function. The utility will be used by LDR testing that generates high rates of conflict for random schemas. Release note: none Informs: CRDB-44094 Co-authored-by: Jeff Swenson <[email protected]>
2 parents dad0ad0 + d488286 commit 2aff402

File tree

4 files changed

+286
-218
lines changed

4 files changed

+286
-218
lines changed

pkg/workload/rand/BUILD.bazel

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

33
go_library(
44
name = "rand",
5-
srcs = ["rand.go"],
5+
srcs = [
6+
"rand.go",
7+
"schema.go",
8+
],
69
importpath = "github.com/cockroachdb/cockroach/pkg/workload/rand",
710
visibility = ["//visibility:public"],
811
deps = [

pkg/workload/rand/rand.go

Lines changed: 23 additions & 214 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,10 @@ import (
1010
"context"
1111
gosql "database/sql"
1212
"database/sql/driver"
13-
"encoding/hex"
1413
"fmt"
1514
"math/rand"
16-
"reflect"
1715
"strings"
1816

19-
"github.com/cockroachdb/cockroach/pkg/geo"
2017
"github.com/cockroachdb/cockroach/pkg/geo/geopb"
2118
"github.com/cockroachdb/cockroach/pkg/sql/randgen"
2219
"github.com/cockroachdb/cockroach/pkg/sql/sem/tree"
@@ -26,7 +23,6 @@ import (
2623
"github.com/cockroachdb/cockroach/pkg/workload/histogram"
2724
"github.com/cockroachdb/errors"
2825
"github.com/lib/pq"
29-
"github.com/lib/pq/oid"
3026
"github.com/spf13/pflag"
3127
)
3228

@@ -108,41 +104,6 @@ func (w *random) Tables() []workload.Table {
108104
return tables
109105
}
110106

111-
type col struct {
112-
name string
113-
dataType *types.T
114-
dataPrecision int
115-
dataScale int
116-
cdefault gosql.NullString
117-
isNullable bool
118-
isComputed bool
119-
}
120-
121-
// typeForOid returns the *types.T struct associated with the given
122-
// OID. Note that for columns of type `BIT` and `CHAR`, the width is
123-
// not recorded on the T struct itself; instead, we query the
124-
// `information_schema.columns` view to get that information. When the
125-
// `character_maximum_length` column is NULL, it means the column has
126-
// variable width and we set the width of the type to 0, which will
127-
// cause the random data generator to generate data with random width.
128-
func typeForOid(db *gosql.DB, typeOid oid.Oid, tableName, columnName string) (*types.T, error) {
129-
datumType := *types.OidToType[typeOid]
130-
if typeOid == oid.T_bit || typeOid == oid.T_char {
131-
var width int32
132-
if err := db.QueryRow(
133-
`SELECT IFNULL(character_maximum_length, 0)
134-
FROM information_schema.columns
135-
WHERE table_name = $1 AND column_name = $2`,
136-
tableName, columnName).Scan(&width); err != nil {
137-
return nil, err
138-
}
139-
140-
datumType.InternalType.Width = width
141-
}
142-
143-
return &datumType, nil
144-
}
145-
146107
// Ops implements the Opser interface.
147108
func (w *random) Ops(
148109
ctx context.Context, urls []string, reg *histogram.Registry,
@@ -160,93 +121,23 @@ func (w *random) Ops(
160121
tableName = w.Tables()[0].Name
161122
}
162123

163-
var relid int
164-
sqlName := tree.Name(tableName)
165-
if err := db.QueryRow("SELECT $1::REGCLASS::OID", sqlName.String()).Scan(&relid); err != nil {
166-
return workload.QueryLoad{}, err
167-
}
168-
169-
rows, err := db.Query(
170-
`
171-
SELECT attname, atttypid, adsrc, NOT attnotnull, attgenerated != ''
172-
FROM pg_catalog.pg_attribute
173-
LEFT JOIN pg_catalog.pg_attrdef
174-
ON attrelid=adrelid AND attnum=adnum
175-
WHERE attrelid=$1`, relid)
124+
table, err := LoadTable(db, tableName)
176125
if err != nil {
177126
return workload.QueryLoad{}, err
178127
}
179-
defer func() { retErr = errors.CombineErrors(retErr, rows.Close()) }()
180-
var cols []col
181-
var numCols = 0
182-
183-
for rows.Next() {
184-
var c col
185-
c.dataPrecision = 0
186-
c.dataScale = 0
187-
188-
var typOid int
189-
if err := rows.Scan(&c.name, &typOid, &c.cdefault, &c.isNullable, &c.isComputed); err != nil {
190-
return workload.QueryLoad{}, err
191-
}
192-
c.dataType, err = typeForOid(db, oid.Oid(typOid), tableName, c.name)
193-
if err != nil {
194-
return workload.QueryLoad{}, err
195-
}
196-
if c.cdefault.String == "unique_rowid()" { // skip
197-
continue
198-
}
199-
if strings.HasPrefix(c.cdefault.String, "uuid_v4()") { // skip
200-
continue
201-
}
202-
cols = append(cols, c)
203-
numCols++
204-
}
205-
206-
if numCols == 0 {
207-
return workload.QueryLoad{}, errors.New("no columns detected")
208-
}
209-
210-
if err = rows.Err(); err != nil {
211-
return workload.QueryLoad{}, err
212-
}
213128

214129
// insert on conflict requires the primary key. check information_schema if not specified on the command line
215130
if strings.HasPrefix(w.method, "ioc") && w.primaryKey == "" {
216-
rows, err := db.Query(
217-
`
218-
SELECT a.attname
219-
FROM pg_index i
220-
JOIN pg_attribute a ON a.attrelid = i.indrelid
221-
AND a.attnum = ANY(i.indkey)
222-
WHERE i.indrelid = $1
223-
AND i.indisprimary`, relid)
224-
if err != nil {
225-
return workload.QueryLoad{}, err
226-
}
227-
defer func() { retErr = errors.CombineErrors(retErr, rows.Close()) }()
228-
for rows.Next() {
229-
var colname string
230-
231-
if err := rows.Scan(&colname); err != nil {
232-
return workload.QueryLoad{}, err
233-
}
234-
if w.primaryKey != "" {
235-
w.primaryKey += "," + tree.NameString(colname)
236-
} else {
237-
w.primaryKey += tree.NameString(colname)
238-
}
131+
if len(table.PrimaryKey) == 0 {
132+
return workload.QueryLoad{}, errors.New(
133+
"insert on conflict requires primary key to be specified via -primary if the table does " +
134+
"not have primary key")
239135
}
240-
if err = rows.Err(); err != nil {
241-
return workload.QueryLoad{}, err
136+
var primaryKey []string
137+
for _, i := range table.PrimaryKey {
138+
primaryKey = append(primaryKey, table.Cols[i].Name)
242139
}
243-
}
244-
245-
if strings.HasPrefix(w.method, "ioc") && w.primaryKey == "" {
246-
err := errors.New(
247-
"insert on conflict requires primary key to be specified via -primary if the table does " +
248-
"not have primary key")
249-
return workload.QueryLoad{}, err
140+
w.primaryKey = strings.Join(primaryKey, ",")
250141
}
251142

252143
var dmlMethod string
@@ -265,19 +156,19 @@ AND i.indisprimary`, relid)
265156
case "ioc-update":
266157
dmlMethod = "insert"
267158
dmlSuffix.WriteString(fmt.Sprintf(" on conflict (%s) do update set ", w.primaryKey))
268-
for i, c := range cols {
159+
for i, c := range table.Cols {
269160
if i > 0 {
270161
dmlSuffix.WriteString(",")
271162
}
272-
dmlSuffix.WriteString(fmt.Sprintf("%s=EXCLUDED.%s", tree.NameString(c.name), tree.NameString(c.name)))
163+
dmlSuffix.WriteString(fmt.Sprintf("%s=EXCLUDED.%s", tree.NameString(c.Name), tree.NameString(c.Name)))
273164
}
274165
default:
275166
return workload.QueryLoad{}, errors.Errorf("%s DML method not valid", w.primaryKey)
276167
}
277168

278-
var nonComputedCols []col
279-
for _, c := range cols {
280-
if !c.isComputed {
169+
var nonComputedCols []Col
170+
for _, c := range table.Cols {
171+
if !c.IsComputed {
281172
nonComputedCols = append(nonComputedCols, c)
282173
}
283174
}
@@ -287,7 +178,7 @@ AND i.indisprimary`, relid)
287178
if i > 0 {
288179
buf.WriteString(",")
289180
}
290-
buf.WriteString(tree.NameString(c.name))
181+
buf.WriteString(tree.NameString(c.Name))
291182
}
292183
buf.WriteString(`) VALUES `)
293184

@@ -320,7 +211,7 @@ AND i.indisprimary`, relid)
320211
config: w,
321212
hists: reg.GetHandle(),
322213
db: db,
323-
cols: nonComputedCols,
214+
table: &table,
324215
rng: rand.New(rand.NewSource(RandomSeed.Seed() + int64(i))),
325216
writeStmt: writeStmt,
326217
}
@@ -333,7 +224,7 @@ type randOp struct {
333224
config *random
334225
hists *histogram.Histograms
335226
db *gosql.DB
336-
cols []col
227+
table *Table
337228
rng *rand.Rand
338229
writeStmt *gosql.Stmt
339230
}
@@ -365,104 +256,22 @@ func (sa sqlArray) Value() (driver.Value, error) {
365256
return pq.Array(sa.array).Value()
366257
}
367258

368-
// DatumToGoSQL converts a datum to a Go type.
369-
func DatumToGoSQL(d tree.Datum) (interface{}, error) {
370-
d = tree.UnwrapDOidWrapper(d)
371-
if d == tree.DNull {
372-
return nil, nil
373-
}
374-
switch d := d.(type) {
375-
case *tree.DBool:
376-
return bool(*d), nil
377-
case *tree.DString:
378-
return string(*d), nil
379-
case *tree.DBytes:
380-
return fmt.Sprintf(`x'%s'`, hex.EncodeToString([]byte(*d))), nil
381-
case *tree.DDate, *tree.DTime:
382-
return tree.AsStringWithFlags(d, tree.FmtBareStrings), nil
383-
case *tree.DTimestamp:
384-
return d.Time, nil
385-
case *tree.DTimestampTZ:
386-
return d.Time, nil
387-
case *tree.DInterval:
388-
return d.Duration.String(), nil
389-
case *tree.DBitArray:
390-
return tree.AsStringWithFlags(d, tree.FmtBareStrings), nil
391-
case *tree.DInt:
392-
return int64(*d), nil
393-
case *tree.DOid:
394-
return uint32(d.Oid), nil
395-
case *tree.DFloat:
396-
return float64(*d), nil
397-
case *tree.DDecimal:
398-
// use string representation here since randgen might generate
399-
// decimals that don't fit into a float64
400-
return d.String(), nil
401-
case *tree.DArray:
402-
arr := make([]interface{}, len(d.Array))
403-
for i := range d.Array {
404-
elt, err := DatumToGoSQL(d.Array[i])
405-
if err != nil {
406-
return nil, err
407-
}
408-
if elt == nil {
409-
elt = nullVal{}
410-
}
411-
arr[i] = elt
412-
}
413-
return sqlArray{arr, d.ParamTyp}, nil
414-
case *tree.DUuid:
415-
return d.UUID, nil
416-
case *tree.DIPAddr:
417-
return d.IPAddr.String(), nil
418-
case *tree.DJSON:
419-
return d.JSON.String(), nil
420-
case *tree.DJsonpath:
421-
return d.String(), nil
422-
case *tree.DTimeTZ:
423-
return d.TimeTZ.String(), nil
424-
case *tree.DBox2D:
425-
return d.CartesianBoundingBox.Repr(), nil
426-
case *tree.DGeography:
427-
return geo.SpatialObjectToEWKT(d.Geography.SpatialObject(), 2)
428-
case *tree.DGeometry:
429-
return geo.SpatialObjectToEWKT(d.Geometry.SpatialObject(), 2)
430-
case *tree.DPGLSN:
431-
return d.LSN.String(), nil
432-
case *tree.DTSQuery:
433-
return d.String(), nil
434-
case *tree.DTSVector:
435-
return d.String(), nil
436-
case *tree.DPGVector:
437-
return d.String(), nil
438-
}
439-
return nil, errors.Errorf("unhandled datum type: %s", reflect.TypeOf(d))
440-
}
441-
442259
type nullVal struct{}
443260

444261
func (nullVal) Value() (driver.Value, error) {
445262
return nil, nil
446263
}
447264

448265
func (o *randOp) run(ctx context.Context) (err error) {
449-
params := make([]interface{}, len(o.cols)*o.config.batchSize)
450-
k := 0 // index into params
266+
params := make([]interface{}, 0, len(o.table.Cols)*o.config.batchSize)
451267
for j := 0; j < o.config.batchSize; j++ {
452-
for _, c := range o.cols {
453-
nullPct := 0
454-
if c.isNullable && o.config.nullPct > 0 {
455-
nullPct = 100 / o.config.nullPct
456-
}
457-
d := randgen.RandDatumWithNullChance(o.rng, c.dataType, nullPct, /* nullChance */
458-
false /* favorCommonData */, false /* targetColumnIsUnique */)
459-
params[k], err = DatumToGoSQL(d)
460-
if err != nil {
461-
return err
462-
}
463-
k++
268+
row, err := o.table.RandomRow(o.rng, o.config.nullPct)
269+
if err != nil {
270+
return err
464271
}
272+
params = append(params, row...)
465273
}
274+
466275
start := timeutil.Now()
467276
_, err = o.writeStmt.ExecContext(ctx, params...)
468277
if o.hists != nil {

pkg/workload/rand/rand_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -90,13 +90,13 @@ func TestRandRun(t *testing.T) {
9090
writeStmt, err := db.Prepare(stmt)
9191
require.NoError(t, err)
9292

93-
dataType, err := typeForOid(db, typeT.InternalType.Oid, tblName, colName)
93+
table, err := LoadTable(db, tblName)
9494
require.NoError(t, err)
95-
cols := []col{{name: colName, dataType: dataType}}
95+
9696
op := randOp{
9797
config: &random{batchSize: 1},
9898
db: db,
99-
cols: cols,
99+
table: &table,
100100
rng: rng,
101101
writeStmt: writeStmt,
102102
}

0 commit comments

Comments
 (0)