Skip to content

Commit ceb320e

Browse files
craig[bot]rafiss
andcommitted
159204: jobs: move JobSchedulerEnv helper function r=rafiss a=rafiss Move this simple function to avoid a dependency cycle in an upcoming commit. Epic: None Release note: None 159227: schemachanger: fix EXPLAIN output of storage param operations r=rafiss a=rafiss Since the protobuf was embedded into the scop for mutating storage parameters, it was not being rendered properly in EXPLAIN output. Fix it by making an explicit field instead. informs #155990 Release note: None Co-authored-by: Rafi Shamim <[email protected]>
3 parents 7be8922 + 7004788 + a119b32 commit ceb320e

File tree

20 files changed

+260
-59
lines changed

20 files changed

+260
-59
lines changed

pkg/backup/alter_backup_schedule.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ func loadSchedules(
4949
}
5050

5151
execCfg := p.ExecCfg()
52-
env := sql.JobSchedulerEnv(execCfg.JobsKnobs())
52+
env := jobs.JobSchedulerEnv(execCfg.JobsKnobs())
5353
schedules := jobs.ScheduledJobTxn(p.InternalSQLTxn())
5454
schedule, err := schedules.Load(ctx, env, scheduleID)
5555
if err != nil {
@@ -434,7 +434,7 @@ func processFullBackupRecurrence(
434434
return s, nil
435435
}
436436

437-
env := sql.JobSchedulerEnv(p.ExecCfg().JobsKnobs())
437+
env := jobs.JobSchedulerEnv(p.ExecCfg().JobsKnobs())
438438
scheduledJobs := jobs.ScheduledJobTxn(p.InternalSQLTxn())
439439
if fullBackupAlways {
440440
if s.incJob == nil {
@@ -535,7 +535,7 @@ func validateFullIncrementalFrequencies(p sql.PlanHookState, s scheduleDetails)
535535
if s.incJob == nil {
536536
return nil
537537
}
538-
env := sql.JobSchedulerEnv(p.ExecCfg().JobsKnobs())
538+
env := jobs.JobSchedulerEnv(p.ExecCfg().JobsKnobs())
539539
now := env.Now()
540540

541541
fullFreq, err := frequencyFromCron(now, s.fullJob.ScheduleExpr())
@@ -594,7 +594,7 @@ func processInto(p sql.PlanHookState, spec *alterBackupScheduleSpec, s scheduleD
594594
// so we can unpause incrementals. This mirrors the behavior of
595595
// CREATE SCHEDULE FOR BACKUP.
596596
if !incPaused {
597-
env := sql.JobSchedulerEnv(p.ExecCfg().JobsKnobs())
597+
env := jobs.JobSchedulerEnv(p.ExecCfg().JobsKnobs())
598598
s.fullJob.SetNextRun(env.Now())
599599
}
600600

@@ -608,7 +608,7 @@ func processNextRunNow(
608608
return nil
609609
}
610610

611-
env := sql.JobSchedulerEnv(p.ExecCfg().JobsKnobs())
611+
env := jobs.JobSchedulerEnv(p.ExecCfg().JobsKnobs())
612612

613613
// Trigger the full schedule, unless there is an inc schedule and the user did
614614
// not explicitly specify the full.

pkg/backup/create_scheduled_backup.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ func doCreateBackupSchedules(
180180
}
181181
}
182182

183-
env := sql.JobSchedulerEnv(p.ExecCfg().JobsKnobs())
183+
env := jobs.JobSchedulerEnv(p.ExecCfg().JobsKnobs())
184184

185185
// Evaluate incremental and full recurrence.
186186
incRecurrence, err := schedulebase.ComputeScheduleRecurrence(env.Now(), eval.recurrence)

pkg/backup/restore_job.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2922,7 +2922,7 @@ func (r *restoreResumer) dropDescriptors(
29222922
// immediately.
29232923
dropTime := int64(1)
29242924
scheduledJobs := jobs.ScheduledJobTxn(txn)
2925-
env := sql.JobSchedulerEnv(r.execCfg.JobsKnobs())
2925+
env := jobs.JobSchedulerEnv(r.execCfg.JobsKnobs())
29262926
for i := range mutableTables {
29272927
tableToDrop := mutableTables[i]
29282928
tablesToGC = append(tablesToGC, tableToDrop.ID)

pkg/ccl/changefeedccl/scheduled_changefeed.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -560,7 +560,7 @@ func doCreateChangefeedSchedule(
560560
resultsCh chan<- tree.Datums,
561561
) error {
562562

563-
env := sql.JobSchedulerEnv(p.ExecCfg().JobsKnobs())
563+
env := jobs.JobSchedulerEnv(p.ExecCfg().JobsKnobs())
564564

565565
if knobs, ok := p.ExecCfg().DistSQLSrv.TestingKnobs.JobsTestingKnobs.(*jobs.TestingKnobs); ok {
566566
if knobs.JobSchedulerEnv != nil {

pkg/jobs/scheduled_job.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,14 @@ func NewScheduledJob(env scheduledjobs.JobSchedulerEnv) *ScheduledJob {
6868
}
6969
}
7070

71+
// JobSchedulerEnv returns JobSchedulerEnv.
72+
func JobSchedulerEnv(knobs *TestingKnobs) scheduledjobs.JobSchedulerEnv {
73+
if knobs != nil && knobs.JobSchedulerEnv != nil {
74+
return knobs.JobSchedulerEnv
75+
}
76+
return scheduledjobs.ProdJobSchedulerEnv
77+
}
78+
7179
// scheduledJobNotFoundError is returned from load when the scheduled job does
7280
// not exist.
7381
type scheduledJobNotFoundError struct {

pkg/sql/GEMINI.md

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
# SQL Package Development Guide
2+
3+
## Schema Changers: Legacy vs Declarative
4+
5+
CockroachDB has two schema change systems: the legacy schema changer and the newer declarative schema changer. Understanding their differences is crucial for DDL development work.
6+
7+
### Architectural Overview
8+
9+
**Legacy Schema Changer** (`/pkg/sql/schema_changer.go`):
10+
- **Hard-coded state transitions**: Uses fixed sequences of descriptor mutations stored in the `mutations` slice
11+
- **Imperative approach**: Logic written procedurally with specific code paths for each DDL operation
12+
- **Limited state model**: Uses states like `DELETE_ONLY`, `WRITE_ONLY`, `BACKFILL_ONLY` from `DescriptorMutation`
13+
- **Job type**: Uses `SCHEMA_CHANGE` and `TYPEDESC_SCHEMA_CHANGE` job types
14+
15+
**Declarative Schema Changer** (`/pkg/sql/schemachanger/`):
16+
- **Element-based modeling**: Schema changes modeled as elements (columns, indexes, constraints) with target statuses
17+
- **Declarative planning**: Uses rules and dependency graphs to generate execution plans
18+
- **State stored in descriptors**: Uses `declarative_schema_changer_state` field instead of `mutations` slice
19+
- **Job type**: Uses `NEW_SCHEMA_CHANGE` job type
20+
21+
### State Management Differences
22+
23+
**Legacy Schema Changer:**
24+
```go
25+
// Uses DescriptorMutation in mutations slice
26+
type DescriptorMutation struct {
27+
State DescriptorMutation_State // DELETE_ONLY, WRITE_ONLY, etc.
28+
Direction DescriptorMutation_Direction // ADD, DROP
29+
// ... specific column/index descriptors
30+
}
31+
```
32+
33+
**Declarative Schema Changer:**
34+
```proto
35+
// Uses element model with target statuses
36+
message Target {
37+
ElementProto element_proto = 1;
38+
Status target_status = 3; // PUBLIC, ABSENT, TRANSIENT_ABSENT
39+
}
40+
```
41+
42+
### Element Model (Declarative)
43+
44+
Elements are defined in `/pkg/sql/schemachanger/scpb/elements.proto`:
45+
- **Table-level**: `Table`, `TableComment`, `TableData`
46+
- **Column elements**: `Column`, `ColumnName`, `ColumnType`, `ColumnDefaultExpression`
47+
- **Index elements**: `PrimaryIndex`, `SecondaryIndex`, `IndexColumn`
48+
- **Constraint elements**: `CheckConstraint`, `ForeignKeyConstraint`
49+
50+
Status transitions: `ABSENT` -> `DELETE_ONLY` -> `WRITE_ONLY` -> `BACKFILL_ONLY` -> `VALIDATED` -> `PUBLIC`
51+
52+
### Planning and Execution
53+
54+
**Legacy Schema Changer:**
55+
- Planning and execution tightly coupled
56+
- Each DDL statement has custom imperative logic
57+
- State transitions are hard-coded sequences
58+
- Difficult to handle complex multi-statement transactions
59+
60+
**Declarative Schema Changer:**
61+
- Clear separation between planning and execution phases
62+
- Uses dependency graphs and rules (`/pkg/sql/schemachanger/scplan/internal/rules/`)
63+
- Can handle complex multi-statement transactions declaratively
64+
- Better composition of multiple DDL operations
65+
66+
### Error Handling and Rollback
67+
68+
**Legacy Schema Changer:**
69+
- Known rollback issues, especially with data loss during failed multi-operation schema changes
70+
- Example: dropping columns alongside adding indexes can permanently lose data on rollback
71+
72+
**Declarative Schema Changer:**
73+
- Designed for correct rollbacks by reversing target statuses
74+
- Uses `OnFailOrCancel` method to revert by flipping directions (ADD becomes DROP)
75+
- Better handling of complex failure scenarios
76+
77+
### Configuration
78+
79+
Enable declarative schema changer via:
80+
```sql
81+
-- Session level
82+
SET use_declarative_schema_changer = 'on'; -- Options: 'off', 'on', 'unsafe', 'unsafe_always'
83+
84+
-- Cluster-wide default
85+
SET CLUSTER SETTING sql.defaults.use_declarative_schema_changer = 'on';
86+
```
87+
88+
### Development Patterns
89+
90+
**Adding New DDL Operations:**
91+
92+
*Legacy Schema Changer:*
93+
- Add case statements in main schema changer loop
94+
- Implement specific state transition logic
95+
- Hard-code mutation sequences
96+
97+
*Declarative Schema Changer:*
98+
- Define new elements in `elements.proto`
99+
- Add rules for state transitions in `scplan/internal/rules/`
100+
- Implement operations in `scop/` and execution in `scexec/`
101+
- More modular and composable approach
102+
103+
### Code Organization
104+
105+
**Legacy Schema Changer:**
106+
- `/pkg/sql/schema_changer.go` - Main implementation
107+
- `/pkg/sql/schema_changer_state.go` - State management
108+
- `/pkg/sql/type_change.go` - Type changes
109+
110+
**Declarative Schema Changer:**
111+
- `scpb/` - Protocol buffer definitions for elements and state
112+
- `scbuild/` - Building targets from DDL ASTs
113+
- `scplan/` - Planning state transitions and dependencies
114+
- `scexec/` - Executing operations
115+
- `scop/` - Operation definitions
116+
- `screl/` - Relational model for elements
117+
118+
### When to Use Each
119+
120+
**Legacy Schema Changer:**
121+
- Still required for some DDL operations not yet implemented in declarative changer
122+
- May be faster for simple, single-operation schema changes
123+
- More predictable performance due to hard-coded paths
124+
125+
**Declarative Schema Changer:**
126+
- Better for complex multi-statement transactions
127+
- More overhead in planning phase but better execution for complex scenarios
128+
- Required for future transactional schema changes
129+
- Better correctness guarantees and rollback handling
130+
131+
### Migration Path
132+
133+
CockroachDB is gradually migrating from legacy to declarative:
134+
1. **Coexistence period**: Both schema changers run simultaneously
135+
2. **Feature-by-feature migration**: DDL operations migrated individually
136+
3. **Version compatibility**: Old jobs must continue during upgrades
137+
4. **Extensive testing**: Test coverage in `schemachanger_test.go` and related files
138+
139+
### Key Benefits of Declarative Approach
140+
141+
1. **Correctness**: Better handling of complex scenarios and rollbacks
142+
2. **Extensibility**: Easier to add new DDL operations
143+
3. **Composability**: Can handle multiple DDL operations in single transaction
144+
4. **Testing**: Better separation of concerns allows more targeted testing
145+
5. **Future-ready**: Foundation for transactional schema changes
146+
147+
The declarative schema changer represents a significant architectural improvement addressing fundamental limitations of the legacy approach, particularly around correctness, extensibility, and complex transaction handling.

pkg/sql/alter_table.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2161,7 +2161,7 @@ func handleTTLStorageParamChange(
21612161

21622162
// Update cron schedule if required.
21632163
if before.DeletionCron != after.DeletionCron {
2164-
env := JobSchedulerEnv(params.ExecCfg().JobsKnobs())
2164+
env := jobs.JobSchedulerEnv(params.ExecCfg().JobsKnobs())
21652165
schedules := jobs.ScheduledJobTxn(params.p.InternalSQLTxn())
21662166
s, err := schedules.Load(
21672167
params.ctx,

pkg/sql/catalog/externalcatalog/external_catalog.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ func DropIngestedExternalCatalog(
197197
// immediately.
198198
dropTime := int64(1)
199199
scheduledJobs := jobs.ScheduledJobTxn(txn)
200-
env := sql.JobSchedulerEnv(execCfg.JobsKnobs())
200+
env := jobs.JobSchedulerEnv(execCfg.JobsKnobs())
201201
for i := range mutableTables {
202202
tableToDrop := mutableTables[i]
203203
tablesToGC = append(tablesToGC, tableToDrop.ID)

pkg/sql/check.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -736,7 +736,7 @@ func (p *planner) validateTTLScheduledJobInTable(
736736
ttl := tableDesc.GetRowLevelTTL()
737737

738738
execCfg := p.ExecCfg()
739-
env := JobSchedulerEnv(execCfg.JobsKnobs())
739+
env := jobs.JobSchedulerEnv(execCfg.JobsKnobs())
740740

741741
wrapError := func(origErr error) error {
742742
return errors.WithHintf(

pkg/sql/control_schedules.go

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -43,17 +43,9 @@ func collectTelemetry(command tree.ScheduleCommand) {
4343
}
4444
}
4545

46-
// JobSchedulerEnv returns JobSchedulerEnv.
47-
func JobSchedulerEnv(knobs *jobs.TestingKnobs) scheduledjobs.JobSchedulerEnv {
48-
if knobs != nil && knobs.JobSchedulerEnv != nil {
49-
return knobs.JobSchedulerEnv
50-
}
51-
return scheduledjobs.ProdJobSchedulerEnv
52-
}
53-
5446
// loadSchedule loads schedule information as the node user.
5547
func loadSchedule(params runParams, scheduleID tree.Datum) (*jobs.ScheduledJob, error) {
56-
env := JobSchedulerEnv(params.ExecCfg().JobsKnobs())
48+
env := jobs.JobSchedulerEnv(params.ExecCfg().JobsKnobs())
5749
schedule := jobs.NewScheduledJob(env)
5850

5951
// Load schedule expression. This is needed for resume command, but we
@@ -89,7 +81,7 @@ func loadSchedule(params runParams, scheduleID tree.Datum) (*jobs.ScheduledJob,
8981
func DeleteSchedule(
9082
ctx context.Context, execCfg *ExecutorConfig, txn isql.Txn, scheduleID jobspb.ScheduleID,
9183
) error {
92-
env := JobSchedulerEnv(execCfg.JobsKnobs())
84+
env := jobs.JobSchedulerEnv(execCfg.JobsKnobs())
9385
_, err := txn.ExecEx(
9486
ctx,
9587
"delete-schedule",
@@ -168,7 +160,7 @@ func (n *controlSchedulesNode) startExec(params runParams) error {
168160
if schedule.IsPaused() {
169161
err = errors.Newf("cannot execute a paused schedule; use RESUME SCHEDULE instead")
170162
} else {
171-
env := JobSchedulerEnv(params.ExecCfg().JobsKnobs())
163+
env := jobs.JobSchedulerEnv(params.ExecCfg().JobsKnobs())
172164
schedule.SetNextRun(env.Now())
173165
err = jobs.ScheduledJobTxn(params.p.InternalSQLTxn()).
174166
Update(params.ctx, schedule)

0 commit comments

Comments
 (0)