@@ -10,13 +10,10 @@ import (
10
10
"context"
11
11
gosql "database/sql"
12
12
"database/sql/driver"
13
- "encoding/hex"
14
13
"fmt"
15
14
"math/rand"
16
- "reflect"
17
15
"strings"
18
16
19
- "github.com/cockroachdb/cockroach/pkg/geo"
20
17
"github.com/cockroachdb/cockroach/pkg/geo/geopb"
21
18
"github.com/cockroachdb/cockroach/pkg/sql/randgen"
22
19
"github.com/cockroachdb/cockroach/pkg/sql/sem/tree"
@@ -26,7 +23,6 @@ import (
26
23
"github.com/cockroachdb/cockroach/pkg/workload/histogram"
27
24
"github.com/cockroachdb/errors"
28
25
"github.com/lib/pq"
29
- "github.com/lib/pq/oid"
30
26
"github.com/spf13/pflag"
31
27
)
32
28
@@ -108,41 +104,6 @@ func (w *random) Tables() []workload.Table {
108
104
return tables
109
105
}
110
106
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
-
146
107
// Ops implements the Opser interface.
147
108
func (w * random ) Ops (
148
109
ctx context.Context , urls []string , reg * histogram.Registry ,
@@ -160,93 +121,23 @@ func (w *random) Ops(
160
121
tableName = w .Tables ()[0 ].Name
161
122
}
162
123
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 )
176
125
if err != nil {
177
126
return workload.QueryLoad {}, err
178
127
}
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
- }
213
128
214
129
// insert on conflict requires the primary key. check information_schema if not specified on the command line
215
130
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" )
239
135
}
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 )
242
139
}
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 , "," )
250
141
}
251
142
252
143
var dmlMethod string
@@ -265,19 +156,19 @@ AND i.indisprimary`, relid)
265
156
case "ioc-update" :
266
157
dmlMethod = "insert"
267
158
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 {
269
160
if i > 0 {
270
161
dmlSuffix .WriteString ("," )
271
162
}
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 )))
273
164
}
274
165
default :
275
166
return workload.QueryLoad {}, errors .Errorf ("%s DML method not valid" , w .primaryKey )
276
167
}
277
168
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 {
281
172
nonComputedCols = append (nonComputedCols , c )
282
173
}
283
174
}
@@ -287,7 +178,7 @@ AND i.indisprimary`, relid)
287
178
if i > 0 {
288
179
buf .WriteString ("," )
289
180
}
290
- buf .WriteString (tree .NameString (c .name ))
181
+ buf .WriteString (tree .NameString (c .Name ))
291
182
}
292
183
buf .WriteString (`) VALUES ` )
293
184
@@ -320,7 +211,7 @@ AND i.indisprimary`, relid)
320
211
config : w ,
321
212
hists : reg .GetHandle (),
322
213
db : db ,
323
- cols : nonComputedCols ,
214
+ table : & table ,
324
215
rng : rand .New (rand .NewSource (RandomSeed .Seed () + int64 (i ))),
325
216
writeStmt : writeStmt ,
326
217
}
@@ -333,7 +224,7 @@ type randOp struct {
333
224
config * random
334
225
hists * histogram.Histograms
335
226
db * gosql.DB
336
- cols [] col
227
+ table * Table
337
228
rng * rand.Rand
338
229
writeStmt * gosql.Stmt
339
230
}
@@ -365,104 +256,22 @@ func (sa sqlArray) Value() (driver.Value, error) {
365
256
return pq .Array (sa .array ).Value ()
366
257
}
367
258
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
-
442
259
type nullVal struct {}
443
260
444
261
func (nullVal ) Value () (driver.Value , error ) {
445
262
return nil , nil
446
263
}
447
264
448
265
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 )
451
267
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
464
271
}
272
+ params = append (params , row ... )
465
273
}
274
+
466
275
start := timeutil .Now ()
467
276
_ , err = o .writeStmt .ExecContext (ctx , params ... )
468
277
if o .hists != nil {
0 commit comments