-
Notifications
You must be signed in to change notification settings - Fork 3.8k
/
logic.go
4411 lines (4050 loc) · 145 KB
/
logic.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// Copyright 2015 The Cockroach Authors.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
package logictest
import (
"bufio"
"bytes"
"context"
"crypto/md5"
gosql "database/sql"
"flag"
"fmt"
"math"
"math/rand"
"net"
"net/url"
"os"
"path"
"path/filepath"
"reflect"
"regexp"
"runtime"
"runtime/debug"
"runtime/trace"
"sort"
"strconv"
"strings"
"testing"
"text/tabwriter"
"time"
"unicode/utf8"
"github.com/cockroachdb/cockroach-go/v2/testserver"
"github.com/cockroachdb/cockroach/pkg/base"
"github.com/cockroachdb/cockroach/pkg/build/bazel"
_ "github.com/cockroachdb/cockroach/pkg/cloud/externalconn/providers" // imported to register ExternalConnection providers
"github.com/cockroachdb/cockroach/pkg/clusterversion"
"github.com/cockroachdb/cockroach/pkg/col/coldata"
"github.com/cockroachdb/cockroach/pkg/kv/kvclient/rangefeed"
"github.com/cockroachdb/cockroach/pkg/kv/kvserver"
"github.com/cockroachdb/cockroach/pkg/kv/kvserver/kvserverbase"
"github.com/cockroachdb/cockroach/pkg/roachpb"
"github.com/cockroachdb/cockroach/pkg/security/username"
"github.com/cockroachdb/cockroach/pkg/server"
"github.com/cockroachdb/cockroach/pkg/settings/cluster"
"github.com/cockroachdb/cockroach/pkg/sql"
"github.com/cockroachdb/cockroach/pkg/sql/execinfra"
"github.com/cockroachdb/cockroach/pkg/sql/logictest/logictestbase"
"github.com/cockroachdb/cockroach/pkg/sql/parser"
"github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgcode"
"github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgerror"
"github.com/cockroachdb/cockroach/pkg/sql/randgen"
"github.com/cockroachdb/cockroach/pkg/sql/rowinfra"
"github.com/cockroachdb/cockroach/pkg/sql/schemachanger/corpus"
"github.com/cockroachdb/cockroach/pkg/sql/schemachanger/scexec"
"github.com/cockroachdb/cockroach/pkg/sql/schemachanger/scplan"
"github.com/cockroachdb/cockroach/pkg/sql/sem/eval"
"github.com/cockroachdb/cockroach/pkg/sql/sem/tree"
"github.com/cockroachdb/cockroach/pkg/sql/sessiondatapb"
"github.com/cockroachdb/cockroach/pkg/sql/sqlstats"
"github.com/cockroachdb/cockroach/pkg/sql/stats"
"github.com/cockroachdb/cockroach/pkg/testutils"
"github.com/cockroachdb/cockroach/pkg/testutils/floatcmp"
"github.com/cockroachdb/cockroach/pkg/testutils/physicalplanutils"
"github.com/cockroachdb/cockroach/pkg/testutils/serverutils"
"github.com/cockroachdb/cockroach/pkg/testutils/skip"
"github.com/cockroachdb/cockroach/pkg/testutils/sqlutils"
"github.com/cockroachdb/cockroach/pkg/upgrade/upgradebase"
"github.com/cockroachdb/cockroach/pkg/util"
"github.com/cockroachdb/cockroach/pkg/util/binfetcher"
"github.com/cockroachdb/cockroach/pkg/util/envutil"
"github.com/cockroachdb/cockroach/pkg/util/log"
"github.com/cockroachdb/cockroach/pkg/util/randutil"
"github.com/cockroachdb/cockroach/pkg/util/timeutil"
"github.com/cockroachdb/cockroach/pkg/util/tracing"
"github.com/cockroachdb/errors"
"github.com/kr/pretty"
"github.com/lib/pq"
"github.com/pmezard/go-difflib/difflib"
"github.com/stretchr/testify/require"
)
// This file is home to TestLogic, a general-purpose engine for
// running SQL logic tests.
//
// TestLogic implements the infrastructure that runs end-to-end tests
// against CockroachDB's SQL layer. It is typically used to run
// CockroachDB's own tests (stored in the `testdata` directory) during
// development and CI, and a subset of SQLite's "Sqllogictest" during
// nightly CI runs. However, any test input can be specified via
// command-line flags (see below).
//
// In a nutshell, TestLogic reads one or more test input files
// containing sequences of SQL statements and queries. Each input file
// is meant to test a feature group. The reason why tests can/should
// be split across multiple files is that each test input file gets
// its own fresh, empty database.
//
// Input files for unit testing are stored alongside the source code
// in the `testdata` subdirectory. The input files for the larger
// TestSqlLiteLogic tests are stored in a separate repository.
//
// The test input is expressed using a domain-specific language, called
// Test-Script, defined by SQLite's "Sqllogictest". The official home
// of Sqllogictest and Test-Script is
// https://www.sqlite.org/sqllogictest/
//
// (the TestSqlLiteLogic test uses a fork of the Sqllogictest test files;
// its input files are hosted at https://github.com/cockroachdb/sqllogictest)
//
// Test-Script is line-oriented. It supports both statements which
// generate no result rows, and queries that produce result rows. The
// result of queries can be checked either using an explicit reference
// output in the test file, or using the expected row count and a hash
// of the expected output. A test can also check for expected column
// names for query results, or expected errors.
//
//
// ###########################################
// TEST CONFIGURATION DIRECTIVES
// ###########################################
//
// Logic tests can start with a directive as follows:
//
// # LogicTest: local fakedist
//
// This directive lists configurations; the test is run once in each
// configuration (in separate subtests). The configurations are defined by
// LogicTestConfigs. If the directive is missing, the test is run in the
// default configuration.
//
// The directive also supports blocklists, i.e. running all specified
// configurations apart from a blocklisted configuration:
//
// # LogicTest: default-configs !3node-tenant
//
// If a blocklist is specified without an accompanying configuration, the
// default config is assumed. i.e., the following directive is equivalent to the
// one above:
//
// # LogicTest: !3node-tenant
//
// An issue can optionally be specified as the reason for blocking a given
// configuration:
//
// # LogicTest: !3node-tenant(<issue number>)
//
// A link to the issue will be printed out if the -print-blocklist-issues flag
// is specified.
//
// There is a special directive '!metamorphic-batch-sizes' that adjusts the
// server to force the usage of production values related for some constants,
// mostly related to batch sizes, that might change via metamorphic testing.
//
//
// ###########################################################
// TENANT CLUSTER SETTING OVERRIDE OPTION DIRECTIVES
// ###########################################################
//
// Test files can also contain tenant cluster setting override directives around
// the beginning of the file. These directives can be used to configure tenant
// cluster setting overrides during setup. This can be useful for altering
// tenant read-only settings for configurations that run their tests as
// secondary tenants (eg. 3node-tenant). While these directives apply to all
// configurations under which the test will be run, it's only really meaningful
// when the test runs as a secondary tenant; the configuration has no effect if
// the test is run as the system tenant.
//
// The directives line looks like:
// # tenant-cluster-setting-override-opt: opt1 opt2
//
// The options are:
// - allow-zone-configs-for-secondary-tenants: If specified, secondary tenants
// are allowed to alter their zone configurations.
// - allow-multi-region-abstractions-for-secondary-tenants: If specified,
// secondary tenants are allowed to make use of multi-region abstractions.
// - allow-split-at-for-secondary-tenants: If specified,
// secondary tenants are allowed to run ALTER TABLE ... SPLIT AT.
//
// ###########################################
// CLUSTER OPTION DIRECTIVES
// ###########################################
//
// Besides test configuration directives, test files can also contain cluster
// option directives around the beginning of the file (these directive can
// before or appear after the configuration ones). These directives affect the
// settings of the cluster created for the test, across all the configurations
// that the test will run under.
//
// The directives line looks like:
// # cluster-opt: opt1 opt2
//
// The options are:
// - disable-span-config: If specified, the span configs infrastructure will be
// disabled.
// - tracing-off: If specified, tracing defaults to being turned off. This is
// used to override the environment, which may ask for tracing to be on by
// default.
//
//
// ###########################################
// LogicTest language
// ###########################################
//
// The Test-Script language is extended here for use with CockroachDB. The
// supported directives are:
//
// - statement ok
// Runs the statement that follows and expects success. For
// example:
// statement ok
// CREATE TABLE kv (k INT PRIMARY KEY, v INT)
//
// - statement notice <regexp>
// Like "statement ok" but expects a notice that matches the given regexp.
//
// - statement count N
// Like "statement ok" but expect a final RowsAffected count of N.
// example:
// statement count 2
// INSERT INTO kv VALUES (1,2), (2,3)
//
// - statement error <regexp>
// Runs the statement that follows and expects an
// error that matches the given regexp.
//
// - statement async <name> <options>
// Runs a statement asynchronously, marking it as a pending
// statement with a unique name, to be completed and validated later with
// "awaitstatement". This is intended for use with statements that may block,
// such as those contending on locks. Other statement options described
// above are supported, though the statement may not be in a "repeat".
// Incomplete pending statements will result in an error on test completion.
// Note that as the statement will be run asynchronously, subsequent queries
// that depend on the state of the statement should be run with the "retry"
// option to ensure deterministic test results.
//
// - awaitstatement <name>
// Completes a pending statement with the provided name, validating its
// results as expected per the given options to "statement async <name>...".
//
// - copy,copy-error
// Runs a COPY FROM STDIN statement, because of the separate data chunk it requires
// special logictest support. Format is:
// copy
// COPY <table> FROM STDIN;
// <blankline>
// COPY DATA
// ----
// <NUMROWS>
//
// copy-error is just like copy but an error is expected and results should be error
// string.
//
// - query <typestring> <options> <label>
// Runs the query that follows and verifies the results (specified after the
// query and a ---- separator). Example:
// query I
// SELECT 1, 2
// ----
// 1 2
//
// The type string specifies the number of columns and their types:
// - T for text; also used for various types which get converted
// to string (arrays, timestamps, etc.).
// - I for integer
// - F for floating point (matches 15 significant decimal digits,
// https://www.postgresql.org/docs/9.0/datatype-numeric.html)
// - R for decimal
// - B for boolean
// - O for oid
// - _ to include the column header, but ignore the column results.
// This is useful to verify that a column exists when the results are
// non-deterministic and to avoid projecting all other columns (for
// example `SHOW RANGES FROM TABLE`). A "_" placeholder is written in
// place of actual results.
//
// Options are comma separated strings from the following:
// - nosort (default)
// - rowsort: sorts both the returned and the expected rows assuming one
// white-space separated word per column.
// - valuesort: sorts all values on all rows as one big set of
// strings (for both the returned and the expected rows).
// - partialsort(x,y,..): performs a partial sort on both the
// returned and the expected results, preserving the relative
// order of rows that differ on the specified columns
// (1-indexed); for results that are expected to be already
// ordered according to these columns. See partialSort() for
// more information.
// - colnames: column names are verified (the expected column names
// are the first line in the expected results).
// - retry: if the expected results do not match the actual results, the
// test will be retried with exponential backoff up to some maximum
// duration. If the test succeeds at any time during that period, it
// is considered successful. Otherwise, it is a failure. See
// testutils.SucceedsSoon for more information. If run with the
// -rewrite flag, inserts a 2s sleep before executing the query
// once.
// - async: runs the query asynchronously, marking it as a pending
// query using the label parameter as a unique name, to be completed
// and validated later with "awaitquery". This is intended for use
// with queries that may block, such as those contending on locks.
// It is supported with other options, with the exception of "retry",
// and may not be in a "repeat". Note that as the query will be run
// asynchronously, subsequent queries that depend on the state of
// the query should be run with the "retry" option to ensure
// deterministic test results.
// - kvtrace: runs the query and compares against the results of the
// kv operations trace of the query. kvtrace optionally accepts
// arguments of the form kvtrace(op,op,...). Op is one of
// the accepted k/v arguments such as 'CPut', 'Scan' etc. It
// also accepts arguments of the form 'prefix=...'. For example,
// if kvtrace(CPut,Del,prefix=/Table/54,prefix=/Table/55), the
// results will be filtered to contain messages starting with
// CPut /Table/54, CPut /Table/55, Del /Table/54, Del /Table/55.
// Cannot be combined with noticetrace.
// - noticetrace: runs the query and compares only the notices that
// appear. Cannot be combined with kvtrace.
// - nodeidx=N: runs the query on node N of the cluster.
//
// The label is optional. If specified, the test runner stores a hash
// of the results of the query under the given label. If the label is
// reused, the test runner verifies that the results are the
// same. This can be used to verify that two or more queries in the
// same test script that are logically equivalent always generate the
// same output. If a label is provided, expected results don't need to
// be provided (in which case there should be no ---- separator).
//
// - query error <regexp>
// Runs the query that follows and expects an error
// that matches the given regexp.
//
// - awaitquery <name>
// Completes a pending query with the provided name, validating its
// results as expected per the given options to "query ... async ... <label>".
//
// - repeat <number>
// It causes the following `statement` or `query` to be repeated the given
// number of times. For example:
// repeat 50
// statement ok
// INSERT INTO T VALUES ((SELECT MAX(k+1) FROM T))
//
// - let $varname
// Executes the query that follows (expecting a single result) and remembers
// the result (as a string) for later use. Any `$varname` occurrences in
// subsequent statements or queries are replaced with the result. The
// variable name must start with a letter, and subsequent characters must be
// letters, digits, or underscores. Example:
// let $foo
// SELECT MAX(v) FROM kv
//
// statement ok
// SELECT * FROM kv WHERE v = $foo
//
// - sleep <duration>
// Introduces a sleep period. Example: sleep 2s
//
// - user <username> [nodeidx=N]
// Changes the user for subsequent statements or queries.
// If nodeidx is specified, this user will connect to the node
// in the cluster with index N (note this is 0-indexed, while
// node IDs themselves are 1-indexed).
//
// - upgrade N
// When using a cockroach-go/testserver logictest, upgrades the node at
// index N to the version specified by the logictest config.
//
// A "host-cluster-" prefix can be prepended to the user, which will force
// the user session to be against the host cluster (useful for multi-tenant
// configurations).
//
// - skipif <mysql/mssql/postgresql/cockroachdb/config CONFIG [ISSUE]>
// Skips the following `statement` or `query` if the argument is
// postgresql, cockroachdb, or a config matching the currently
// running configuration.
//
// - onlyif <mysql/mssql/postgresql/cockroachdb/config CONFIG [ISSUE]>
// Skips the following `statement` or `query` if the argument is not
// postgresql, cockroachdb, or a config matching the currently
// running configuration.
//
// - traceon <file>
// Enables tracing to the given file.
//
// - traceoff
// Stops tracing.
//
// - subtest <testname>
// Defines the start of a subtest. The subtest is any number of statements
// that occur after this command until the end of file or the next subtest
// command.
//
// - retry
// Specifies that the next occurrence of a statement or query directive
// (including those which expect errors), will be retried for a fixed
// duration until the test passes, or the alloted time has elapsed.
// This is similar to the retry option of the query directive.
//
// The overall architecture of TestLogic is as follows:
//
// - TestLogic() selects the input files and instantiates
// a `logicTest` object for each input file.
//
// - logicTest.run() sets up a new database.
// - logicTest.processTestFile() runs all tests from that input file.
//
// - each individual test in an input file is instantiated either as a
// logicStatement or logicQuery object. These are then processed by
// either logicTest.execStatement() or logicTest.execQuery().
//
// TestLogic has three main parameter groups:
//
// - Which input files are processed.
// - How and when to stop when tests fail.
// - Which results are reported.
//
// The parameters are typically set using the TESTFLAGS `make`
// parameter, as in:
//
// make test PKG=./pkg/sql TESTS=TestLogic TESTFLAGS='....'
//
// Input file selection:
//
// -d <glob> selects all files matching <glob>. This can mix and
// match wildcards (*/?) or groups like {a,b,c}.
//
// -bigtest enable the long-running SqlLiteLogic test, which uses files from
// CockroachDB's fork of Sqllogictest.
//
// Configuration:
//
// -config name[,name2,...] customizes the test cluster configuration for test
// files that lack LogicTest directives; must be one
// of `LogicTestConfigs`.
// Example:
// -config local,fakedist
//
// Error mode:
//
// -max-errors N stop testing after N errors have been
// encountered. Default 1. Set to 0 for no limit.
//
// -allow-prepare-fail
// tolerate / ignore errors that occur during query
// preparation. With -allow-prepare-fail you can
// indicate that it is OK as long as the database
// reports early to the client that it does not support
// a given query. Errors are still reported if queries
// fail during execution only or if a statement fails.
//
// -flex-types tolerate when a result column is produced with a
// different numeric type than the one expected by the
// test. This enables reusing tests designed for
// databases with slightly different typing semantics.
//
// Test output:
//
// -v (or -test.v if the test is compiled as a standalone
// binary). Go `testing`'s `verbose` flag.
//
// The output generated by the following flags is suppressed unless
// either -v is given or a test fails.
//
// -show-sql show SQL statements/queries immediately before they
// are tested. This can be useful for example when
// troubleshooting errors that cause the database/test
// to stop before the test completes. When -show-sql
// is set, individual test results are annoted with
// either "OK" (test passed as expected), "XFAIL"
// (expected failure, test failed as expected), or
// "FAIL" to indicate an unexpected/undesired test
// failure.
//
// -error-summary produces a report organized by error message
// of all queries that have caused that error. Useful
// with -allow-prepare-fail and/or -flex-types.
//
// -full-messages by default -error-summary shortens error messages
// and queries so they fit in a moderately large
// terminal screen. With this parameter, the
// full text of errors and queries is printed.
//
// Suggested use:
//
// - For validation testing: just -d or -bigtest.
// - For compatibility testing: add -allow-prepare-fail -flex-types.
// - For troubleshooting / analysis: add -v -show-sql -error-summary.
var (
resultsRE = regexp.MustCompile(`^(\d+)\s+values?\s+hashing\s+to\s+([0-9A-Fa-f]+)$`)
noticeRE = regexp.MustCompile(`^statement\s+notice\s+(.*)$`)
errorRE = regexp.MustCompile(`^(?:statement|query)\s+error\s+(?:pgcode\s+([[:alnum:]]+)\s+)?(.*)$`)
varRE = regexp.MustCompile(`\$[a-zA-Z][a-zA-Z_0-9]*`)
// Bigtest is a flag which should be set if the long-running sqlite logic tests should be run.
Bigtest = flag.Bool("bigtest", false, "enable the long-running SqlLiteLogic test")
// Testing mode
maxErrs = flag.Int(
"max-errors", 1,
"stop processing input files after this number of errors (set to 0 for no limit)",
)
allowPrepareFail = flag.Bool(
"allow-prepare-fail", false, "tolerate unexpected errors when preparing a query",
)
flexTypes = flag.Bool(
"flex-types", false,
"do not fail when a test expects a column of a numeric type but the query provides another type",
)
// Output parameters
showSQL = flag.Bool("show-sql", false,
"print the individual SQL statement/queries before processing",
)
showDiff = flag.Bool("show-diff", false, "generate a diff for expectation mismatches when possible")
printErrorSummary = flag.Bool("error-summary", false,
"print a per-error summary of failing queries at the end of testing, "+
"when -allow-prepare-fail is set",
)
fullMessages = flag.Bool("full-messages", false,
"do not shorten the error or SQL strings when printing the summary for -allow-prepare-fail "+
"or -flex-types.",
)
rewriteResultsInTestfiles = flag.Bool(
"rewrite", false,
"ignore the expected results and rewrite the test files with the actual results from this "+
"run. Used to update tests when a change affects many cases; please verify the testfile "+
"diffs carefully!",
)
rewriteSQL = flag.Bool(
"rewrite-sql", false,
"pretty-reformat the SQL queries. Only use this incidentally when importing new tests. "+
"beware! some tests INTEND to use non-formatted SQL queries (e.g. case sensitivity). "+
"do not bluntly apply!",
)
sqlfmtLen = flag.Int("line-length", tree.DefaultPrettyCfg().LineWidth,
"target line length when using -rewrite-sql")
disableOptRuleProbability = flag.Float64(
"disable-opt-rule-probability", 0,
"disable transformation rules in the cost-based optimizer with the given probability.")
optimizerCostPerturbation = flag.Float64(
"optimizer-cost-perturbation", 0,
"randomly perturb the estimated cost of each expression in the query tree by at most the "+
"given fraction for the purpose of creating alternate query plans in the optimizer.")
saveDeclarativeCorpus = flag.String(
"declarative-corpus", "",
"enables generation and storage of a declarative schema changer corpus",
)
// globalMVCCRangeTombstone will write a global MVCC range tombstone across
// the entire user keyspace during cluster bootstrapping. This should not
// semantically affect the test data written above it, but will activate MVCC
// range tombstone code paths in the storage layer for testing.
globalMVCCRangeTombstone = util.ConstantWithMetamorphicTestBool(
"logictest-global-mvcc-range-tombstone", false)
// useMVCCRangeTombstonesForPointDeletes will use point-sized MVCC range
// tombstones for point deletions, on a best-effort basis. These should be
// indistinguishable to a KV client, but activate MVCC range tombstone
// code paths in the storage/KV layer, for testing. This may result in
// incorrect MVCC stats for RangeKey* fields in rare cases, due to point
// writes not holding appropriate latches for range key stats update.
useMVCCRangeTombstonesForPointDeletes = util.ConstantWithMetamorphicTestBool(
"logictest-use-mvcc-range-tombstones-for-point-deletes", false)
// BackupRestoreProbability is the environment variable for `3node-backup` config.
backupRestoreProbability = envutil.EnvOrDefaultFloat64("COCKROACH_LOGIC_TEST_BACKUP_RESTORE_PROBABILITY", 0.0)
)
const queryRewritePlaceholderPrefix = "__async_query_rewrite_placeholder"
// logicStatement represents a single statement test in Test-Script.
type logicStatement struct {
// file and line number of the test.
pos string
// SQL string to be sent to the database.
sql string
// expected notice, if any.
expectNotice string
// expected error, if any. "" indicates the statement should
// succeed.
expectErr string
// expected pgcode for the error, if any. "" indicates the
// test does not check the pgwire error code.
expectErrCode string
// expected rows affected count. -1 to avoid testing this.
expectCount int64
// if this statement is to run asynchronously, and become a pendingStatement.
expectAsync bool
// the name key to use for the pendingStatement.
statementName string
}
// pendingExecResult represents the asynchronous result of a logicStatement
// run against the DB, as well as the final SQL used in execution.
type pendingExecResult struct {
execSQL string
res gosql.Result
err error
}
// pendingStatement encapsulates a logicStatement that is expected to block and
// as such is run in a separate goroutine, as well as the channel on which to
// receive the results of the statement execution.
type pendingStatement struct {
logicStatement
// The channel on which to receive the execution results, when completed.
resultChan chan pendingExecResult
}
// pendingQueryResult represents the asynchronous result of a logicQuery
// run against the DB, including any returned rows.
type pendingQueryResult struct {
rows *gosql.Rows
err error
}
// pendingQuery encapsulates a logicQuery that is expected to block and
// as such is run in a separate goroutine, as well as the channel on which to
// receive the results of the query execution.
type pendingQuery struct {
logicQuery
// The channel on which to receive the query results, when completed.
resultChan chan pendingQueryResult
}
// readSQL reads the lines of a SQL statement or query until the first blank
// line or (optionally) a "----" separator, and sets stmt.sql.
//
// If a separator is found, returns separator=true. If a separator is found when
// it is not expected, returns an error.
func (ls *logicStatement) readSQL(
t *logicTest, s *logictestbase.LineScanner, allowSeparator bool,
) (separator bool, _ error) {
if err := t.maybeBackupRestore(t.rng, t.cfg); err != nil {
return false, err
}
var buf bytes.Buffer
hasVars := false
for s.Scan() {
line := s.Text()
if !*rewriteSQL {
t.emit(line)
}
substLine := t.substituteVars(line)
if line != substLine {
hasVars = true
line = substLine
}
if line == "" {
break
}
if line == "----" {
separator = true
if ls.expectNotice != "" {
return false, errors.Errorf(
"%s: invalid ---- separator after a statement expecting a notice: %s",
ls.pos, ls.expectNotice,
)
}
if ls.expectErr != "" {
return false, errors.Errorf(
"%s: invalid ---- separator after a statement or query expecting an error: %s",
ls.pos, ls.expectErr,
)
}
if !allowSeparator {
return false, errors.Errorf("%s: unexpected ---- separator", ls.pos)
}
break
}
fmt.Fprintln(&buf, line)
}
ls.sql = strings.TrimSpace(buf.String())
if *rewriteSQL {
if !hasVars {
newSyntax, err := func(inSql string) (string, error) {
// Can't rewrite the SQL otherwise because the vars make it invalid.
stmtList, err := parser.Parse(inSql)
if err != nil {
if ls.expectErr != "" {
// Maybe a parse error was expected. Simply do not rewrite.
return inSql, nil
}
return "", errors.Wrapf(err, "%s: error while parsing SQL for reformat:\n%s", ls.pos, ls.sql)
}
var newSyntax bytes.Buffer
pcfg := tree.DefaultPrettyCfg()
pcfg.LineWidth = *sqlfmtLen
pcfg.Simplify = false
pcfg.UseTabs = false
for i := range stmtList {
if i > 0 {
fmt.Fprintln(&newSyntax, ";")
}
fmt.Fprint(&newSyntax, pcfg.Pretty(stmtList[i].AST))
}
return newSyntax.String(), nil
}(ls.sql)
if err != nil {
return false, err
}
ls.sql = newSyntax
}
t.emit(ls.sql)
if separator {
t.emit("----")
} else {
t.emit("")
}
}
return separator, nil
}
// logicSorter sorts result rows (or not) depending on Test-Script's
// sorting option for a "query" test. See the implementation of the
// "query" directive below for details.
type logicSorter func(numCols int, values []string)
type rowSorter struct {
numCols int
numRows int
values []string
}
func (r rowSorter) row(i int) []string {
return r.values[i*r.numCols : (i+1)*r.numCols]
}
func (r rowSorter) Len() int {
return r.numRows
}
func (r rowSorter) Less(i, j int) bool {
a := r.row(i)
b := r.row(j)
for k := range a {
if a[k] < b[k] {
return true
}
if a[k] > b[k] {
return false
}
}
return false
}
func (r rowSorter) Swap(i, j int) {
a := r.row(i)
b := r.row(j)
for i := range a {
a[i], b[i] = b[i], a[i]
}
}
func rowSort(numCols int, values []string) {
sort.Sort(rowSorter{
numCols: numCols,
numRows: len(values) / numCols,
values: values,
})
}
func valueSort(numCols int, values []string) {
sort.Strings(values)
}
// partialSort rearranges consecutive rows that have the same values on a
// certain set of columns (orderedCols).
//
// More specifically: rows are partitioned into groups of consecutive rows that
// have the same values for columns orderedCols. Inside each group, the rows are
// sorted. The relative order of any two rows that differ on orderedCols is
// preserved.
//
// This is useful when comparing results for a statement that guarantees a
// partial, but not a total order. Consider:
//
// SELECT a, b FROM ab ORDER BY a
//
// Some possible outputs for the same data:
//
// 1 2 1 5 1 2
// 1 5 1 4 1 4
// 1 4 or 1 2 or 1 5
// 2 3 2 2 2 3
// 2 2 2 3 2 2
//
// After a partialSort with orderedCols = {0} all become:
//
// 1 2
// 1 4
// 1 5
// 2 2
// 2 3
//
// An incorrect output like:
//
// 1 5 1 2
// 1 2 1 5
// 2 3 becomes: 2 2
// 2 2 2 3
// 1 4 1 4
//
// and it is detected as different.
func partialSort(numCols int, orderedCols []int, values []string) {
// We use rowSorter here only as a container.
c := rowSorter{
numCols: numCols,
numRows: len(values) / numCols,
values: values,
}
// Sort the group of rows [rowStart, rowEnd).
sortGroup := func(rowStart, rowEnd int) {
sort.Sort(rowSorter{
numCols: numCols,
numRows: rowEnd - rowStart,
values: values[rowStart*numCols : rowEnd*numCols],
})
}
groupStart := 0
for rIdx := 1; rIdx < c.numRows; rIdx++ {
// See if this row belongs in the group with the previous row.
row := c.row(rIdx)
start := c.row(groupStart)
differs := false
for _, i := range orderedCols {
if start[i] != row[i] {
differs = true
break
}
}
if differs {
// Sort the group and start a new group with just this row in it.
sortGroup(groupStart, rIdx)
groupStart = rIdx
}
}
sortGroup(groupStart, c.numRows)
}
// logicQuery represents a single query test in Test-Script.
type logicQuery struct {
logicStatement
// colTypes indicates the expected result column types.
colTypes string
// colNames controls the inclusion of column names in the query result.
colNames bool
// some tests require the output to match modulo sorting.
sorter logicSorter
// expectedErr and expectedErrCode are as in logicStatement.
// if set, the results are cross-checked against previous queries with the
// same label.
label string
checkResults bool
// valsPerLine is the number of values included in each line of the expected
// results. This can either be 1, or else it must match the number of expected
// columns (i.e. len(colTypes)).
valsPerLine int
// expectedResults indicates the expected sequence of text words
// when flattening a query's results.
expectedResults []string
// expectedResultsRaw is the same as expectedResults, but
// retaining the original formatting (whitespace, indentation) as
// the test input file. This is used for pretty-printing unexpected
// results.
expectedResultsRaw []string
// expectedHash indicates the expected hash of all result rows
// combined. "" indicates hash checking is disabled.
expectedHash string
// expectedValues indicates the number of rows expected when
// expectedHash is set.
expectedValues int
// kvtrace indicates that we're comparing the output of a kv trace.
kvtrace bool
// kvOpTypes can be used only when kvtrace is true. It contains
// the particular operation types to filter on, such as CPut or Del.
kvOpTypes []string
keyPrefixFilters []string
// nodeIdx determines which node on the cluster to execute a query on for the given query.
nodeIdx int
// noticetrace indicates we're comparing the output of a notice trace.
noticetrace bool
// rawOpts are the query options, before parsing. Used to display in error
// messages.
rawOpts string
// roundFloatsInStringsSigFigs specifies the number of significant figures
// to round floats embedded in strings to where zero means do not round.
roundFloatsInStringsSigFigs int
}
var allowedKVOpTypes = []string{
"CPut",
"Put",
"InitPut",
"Del",
"DelRange",
"ClearRange",
"Get",
"Scan",
}
func isAllowedKVOp(op string) bool {
for _, s := range allowedKVOpTypes {
if op == s {
return true
}
}
return false
}
// logicTest executes the test cases specified in a file. The file format is
// taken from the sqllogictest tool
// (http://www.sqlite.org/sqllogictest/doc/trunk/about.wiki) with various
// extensions to allow specifying errors and additional options. See
// https://github.com/gregrahn/sqllogictest/ for a github mirror of the
// sqllogictest source.
type logicTest struct {
rootT *testing.T
subtestT *testing.T
rng *rand.Rand
cfg logictestbase.TestClusterConfig
// serverArgs are the parameters used to create a cluster for this test.
// They are persisted since a cluster can be recreated throughout the
// lifetime of the test and we should create all clusters with the same
// arguments.
serverArgs *TestServerArgs
// clusterOpts are the options used to create a cluster for this test.
// They are persisted since a cluster can be recreated throughout the
// lifetime of the test and we should create all clusters with the same
// arguments.
clusterOpts []clusterOpt
// knobOpts are the options used to create testing knobs.
knobOpts []knobOpt
// tenantClusterSettingOverrideOpts are the options used by the host cluster
// to configure tenant setting overrides during setup. They're persisted here
// because a cluster can be recreated throughout the lifetime of a test, and
// we should override tenant settings each time this happens.
tenantClusterSettingOverrideOpts []tenantClusterSettingOverrideOpt
// cluster is the test cluster against which we are testing. This cluster
// may be reset during the lifetime of the test.
cluster serverutils.TestClusterInterface
// testserverCluster is the testserver cluster. This uses real binaries.
testserverCluster testserver.TestServer
// sharedIODir is the ExternalIO directory that is shared between all clusters
// created in the same logicTest. It is populated during setup() of the logic
// test.
sharedIODir string
// the index of the node (within the cluster) against which we run the test
// statements.
nodeIdx int
// If this test uses a SQL tenant server, this is its address. In this case,
// all clients are created against this tenant.
tenantAddrs []string
// map of built clients. Needs to be persisted so that we can
// re-use them and close them all on exit.
clients map[string]*gosql.DB
// client currently in use. This can change during processing
// of a test input file when encountering the "user" directive.
// see setUser() for details.
user string
db *gosql.DB
// clusterCleanupFuncs contains the cleanup methods that are specific to a
// cluster. These will be called during cluster tear-down. Note that 1 logic
// test may reset its cluster throughout a test. Cleanup methods that should
// be stored here include PGUrl connections for the users for a cluster.
clusterCleanupFuncs []func()
// testCleanupFuncs are cleanup methods that are only called when closing a
// test (rather than a specific cluster). One test may reset a cluster with a
// new one, but keep some shared resources across the entire test. An example
// would be an IO directory used throughout the test.
testCleanupFuncs []func()
// progress holds the number of statements executed so far.
progress int
// failures holds the number of tests failed so far, when
// -try-harder is set.