Skip to content

Commit 4e6b6f8

Browse files
craig[bot]fqazidt
committed
154382: workload/schemachanger: add randomized testing for TRUNCATE r=rafiss a=fqazi Previously, the randomized schema changer workload had no coverage for TRUNCATE. This patch adds support for testing TRUNCATE in the randomized workload with multiple tables and with / without CASSCADE. Note: This patch also fixes the pgcode generated by TRUNCATE when foreign key refrences exist, since previously we returned an internal error. Postgres generates a FeatureNotSupported pgcode. Fixes: #154045 Release note: None 154455: cmd/roachtest: prevent macOS host from idle-sleeping during test r=dt a=dt These tests run for 20mins+ so it is common to step away to make coffee or something while they are running. Unfortunately this can mean the host goes to sleep, failing the test. To avoid this we can invoke the macOS utility 'caffeinate' with '-i' to prevent only machine idle sleep (but not display sleep/lock), and with the roachtest process, so it will inhibit sleep until the roachtest process exits. Release note: none. Epic: none. Co-authored-by: Faizan Qazi <[email protected]> Co-authored-by: David Taylor <[email protected]>
3 parents 9f71acc + 9decebf + d988acb commit 4e6b6f8

File tree

8 files changed

+145
-2
lines changed

8 files changed

+145
-2
lines changed

pkg/cmd/roachtest/roachtestflags/flags.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -497,6 +497,14 @@ var (
497497
Always collect artifacts during test teardown, even if the test did not
498498
time out or fail.`,
499499
})
500+
501+
Caffeinate bool = true
502+
_ = registerRunFlag(&Caffeinate, FlagInfo{
503+
Name: "caffeinate",
504+
Usage: `
505+
On Darwin, prevent the system from sleeping while roachtest is running
506+
by invoking caffeinate -i -w <pid>. Default is true.`,
507+
})
500508
)
501509

502510
// The flags below override the final cluster configuration. They have no

pkg/cmd/roachtest/run.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"math/rand"
1212
"net/http"
1313
"os"
14+
"os/exec"
1415
"os/signal"
1516
"os/user"
1617
"path/filepath"
@@ -52,6 +53,21 @@ const (
5253
// runTests is the main function for the run and bench commands.
5354
// Assumes initRunFlagsBinariesAndLibraries was called.
5455
func runTests(register func(registry.Registry), filter *registry.TestFilter) error {
56+
// On Darwin, start caffeinate to prevent the system from sleeping.
57+
if runtime.GOOS == "darwin" && roachtestflags.Caffeinate {
58+
pid := os.Getpid()
59+
cmd := exec.Command("caffeinate", "-i", "-w", strconv.Itoa(pid))
60+
if err := cmd.Start(); err != nil {
61+
fmt.Fprintf(os.Stderr, "warning: failed to start caffeinate: %v\n", err)
62+
} else {
63+
defer func() {
64+
if cmd.Process != nil {
65+
_ = cmd.Process.Kill()
66+
}
67+
}()
68+
}
69+
}
70+
5571
globalSeed := randutil.NewPseudoSeed()
5672
if globalSeedEnv := os.Getenv("ROACHTEST_GLOBAL_SEED"); globalSeedEnv != "" {
5773
if parsed, err := strconv.ParseInt(globalSeedEnv, 0, 64); err == nil {

pkg/sql/schemachanger/scbuild/internal/scbuildstmt/truncate.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ package scbuildstmt
88
import (
99
"github.com/cockroachdb/cockroach/pkg/sql/catalog"
1010
"github.com/cockroachdb/cockroach/pkg/sql/catalog/descpb"
11+
"github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgcode"
12+
"github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgerror"
1113
"github.com/cockroachdb/cockroach/pkg/sql/privilege"
1214
"github.com/cockroachdb/cockroach/pkg/sql/schemachanger/scerrors"
1315
"github.com/cockroachdb/cockroach/pkg/sql/schemachanger/scpb"
@@ -51,7 +53,13 @@ func Truncate(b BuildCtx, stmt *tree.Truncate) {
5153
if stmt.DropBehavior != tree.DropCascade {
5254
name := b.QueryByID(tableID).FilterNamespace().MustGetOneElement()
5355
refName := refElts.FilterNamespace().MustGetZeroOrOneElement()
54-
panic(errors.Errorf("%q is %s table %q", name.Name, "referenced by foreign key from", refName.Name))
56+
// The error code returned here matches PGSQL, even though the CASCADE
57+
// operation is supported there with TRUNCATE as well.
58+
panic(errors.WithHint(pgerror.Newf(pgcode.FeatureNotSupported,
59+
"%q is %s table %q", name.Name,
60+
"referenced by foreign key from",
61+
refName.Name),
62+
"truncate dependent tables at the same time or specify the CASCADE option"))
5563
}
5664
if !tablesToTruncate.Contains(referencingTableID) {
5765
tablesToTruncate.Add(referencingTableID)

pkg/sql/truncate.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ import (
1818
"github.com/cockroachdb/cockroach/pkg/sql/catalog/catalogkeys"
1919
"github.com/cockroachdb/cockroach/pkg/sql/catalog/descpb"
2020
"github.com/cockroachdb/cockroach/pkg/sql/catalog/tabledesc"
21+
"github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgcode"
22+
"github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgerror"
2123
"github.com/cockroachdb/cockroach/pkg/sql/privilege"
2224
"github.com/cockroachdb/cockroach/pkg/sql/schemachanger/scerrors"
2325
"github.com/cockroachdb/cockroach/pkg/sql/sem/tree"
@@ -89,7 +91,10 @@ func (t *truncateNode) startExec(params runParams) error {
8991
}
9092

9193
if n.DropBehavior != tree.DropCascade {
92-
return errors.Errorf("%q is %s table %q", tableDesc.Name, msg, other.Name)
94+
// The error code returned here matches PGSQL, even though the CASCADE
95+
// operation is supported there with TRUNCATE as well.
96+
return errors.WithHintf(pgerror.Newf(pgcode.FeatureNotSupported, "%q is %s table %q", tableDesc.Name, msg, other.Name),
97+
"truncate dependent tables at the same time or specify the CASCADE option")
9398
}
9499
if err := p.CheckPrivilege(ctx, other, privilege.DROP); err != nil {
95100
return err

pkg/workload/schemachange/error_screening.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1794,3 +1794,38 @@ func (og *operationGenerator) tableHasForeignKeyMutation(
17941794
AND (m->'constraint'->>'foreign_key') IS NOT NULL
17951795
);`, tableName)
17961796
}
1797+
1798+
// getTableForeignKeyReferences returns a list of tables that reference
1799+
// the specified table via foreign key references.
1800+
func (og *operationGenerator) getTableForeignKeyReferences(
1801+
ctx context.Context, tx pgx.Tx, tableName *tree.TableName,
1802+
) ([]tree.TableName, error) {
1803+
rows, err := tx.Query(ctx,
1804+
`WITH fk_refs AS (
1805+
SELECT conrelid FROM pg_constraint WHERE
1806+
confrelid = $1::REGCLASS AND
1807+
conrelid <> $1::REGCLASS
1808+
)
1809+
SELECT
1810+
n.nspname as schema_name, c.relname AS object_name
1811+
FROM fk_refs AS f
1812+
JOIN pg_class AS c ON c.oid = f.conrelid
1813+
JOIN pg_namespace AS n ON c.relnamespace = n.oid
1814+
`,
1815+
tableName.String())
1816+
if err != nil {
1817+
return nil, err
1818+
}
1819+
defer rows.Close()
1820+
var result []tree.TableName
1821+
for rows.Next() {
1822+
var table, schema string
1823+
err = rows.Scan(&schema, &table)
1824+
if err != nil {
1825+
return nil, err
1826+
}
1827+
name := tree.MakeTableNameWithSchema(tableName.CatalogName, tree.Name(schema), tree.Name(table))
1828+
result = append(result, name)
1829+
}
1830+
return result, rows.Err()
1831+
}

pkg/workload/schemachange/operation_generator.go

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5580,3 +5580,67 @@ func (og *operationGenerator) findExistingTrigger(
55805580

55815581
return &triggerWithInfo, nil
55825582
}
5583+
5584+
// truncateTable generates a TRUNCATE TABLE statement.
5585+
func (og *operationGenerator) truncateTable(ctx context.Context, tx pgx.Tx) (*opStmt, error) {
5586+
tbls, err := Collect(ctx, og, tx, pgx.RowTo[string],
5587+
`SELECT quote_ident(schema_name) || '.' || quote_ident(table_name) FROM [SHOW TABLES] WHERE type = 'table'`)
5588+
if err != nil {
5589+
return nil, err
5590+
}
5591+
5592+
const MaxTruncateTables = 8
5593+
stmt, expectedCode, err := Generate[*tree.Truncate](og.params.rng, og.produceError(), []GenerationCase{
5594+
{pgcode.SuccessfulCompletion, `TRUNCATE TABLE {MultipleTableNames}`},
5595+
{pgcode.SuccessfulCompletion, `TRUNCATE TABLE {MultipleTableNames} CASCADE`},
5596+
{pgcode.UndefinedTable, `TRUNCATE TABLE {TableNameDoesNotExist}`},
5597+
{pgcode.UndefinedTable, `TRUNCATE TABLE {TableNameDoesNotExist} CASCADE`},
5598+
}, template.FuncMap{
5599+
"MultipleTableNames": func() (string, error) {
5600+
selectedTables, err := PickBetween(og.params.rng, 1, MaxTruncateTables, tbls)
5601+
if err != nil {
5602+
return "", err
5603+
}
5604+
return strings.Join(selectedTables, ","), nil
5605+
},
5606+
"TableNameDoesNotExist": func() (string, error) {
5607+
return "TableThatDoesNotExist", nil
5608+
},
5609+
})
5610+
if err != nil {
5611+
return nil, err
5612+
}
5613+
op := newOpStmt(stmt, codesWithConditions{
5614+
{expectedCode, true},
5615+
})
5616+
5617+
// If the the TRUNCATE is not cascaded, then the operation can fail
5618+
// if foreign key references exist.
5619+
if expectedCode == pgcode.SuccessfulCompletion &&
5620+
stmt.DropBehavior != tree.DropCascade {
5621+
tableSet := map[string]struct{}{}
5622+
fkSet := map[string]struct{}{}
5623+
// Gather the set of tables handled by this statement, and
5624+
// any foreign keys that reference these tables.
5625+
for _, table := range stmt.Tables {
5626+
tableSet[table.FQString()] = struct{}{}
5627+
fkReferenceTables, err := og.getTableForeignKeyReferences(ctx, tx, &table)
5628+
if err != nil {
5629+
return nil, err
5630+
}
5631+
for _, fk := range fkReferenceTables {
5632+
fkSet[fk.FQString()] = struct{}{}
5633+
}
5634+
}
5635+
// Check if any of the foreign keys that reference these
5636+
// tables are not truncated. If they are, then the TRUNCATE
5637+
// will fail with an error.
5638+
for fk := range fkSet {
5639+
if _, hasTable := tableSet[fk]; !hasTable {
5640+
op.expectedExecErrors.add(pgcode.FeatureNotSupported)
5641+
break
5642+
}
5643+
}
5644+
}
5645+
return op, nil
5646+
}

pkg/workload/schemachange/optype.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ const (
143143
dropTable // DROP TABLE <table>
144144
dropTrigger // DROP TRIGGER <trigger> ON <table>
145145
dropView // DROP VIEW <view>
146+
truncateTable
146147

147148
// Unimplemented operations. TODO(sql-foundations): Audit and/or implement these operations.
148149
// alterDatabaseOwner
@@ -264,6 +265,7 @@ var opFuncs = []func(*operationGenerator, context.Context, pgx.Tx) (*opStmt, err
264265
renameSequence: (*operationGenerator).renameSequence,
265266
renameTable: (*operationGenerator).renameTable,
266267
renameView: (*operationGenerator).renameView,
268+
truncateTable: (*operationGenerator).truncateTable,
267269
}
268270

269271
var opWeights = []int{
@@ -321,6 +323,7 @@ var opWeights = []int{
321323
renameSequence: 1,
322324
renameTable: 1,
323325
renameView: 1,
326+
truncateTable: 1,
324327
}
325328

326329
// This workload will maintain its own list of minimal supported versions for
@@ -358,4 +361,5 @@ var opDeclarativeVersion = map[opType]clusterversion.Key{
358361
dropTable: clusterversion.MinSupported,
359362
dropTrigger: clusterversion.MinSupported,
360363
dropView: clusterversion.MinSupported,
364+
truncateTable: clusterversion.V25_4,
361365
}

pkg/workload/schemachange/optype_string.go

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)