Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/data-annotations/retention-policies.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ public class ApiRequestLog
}
```

> :warning: **Note:** Due to a known bug in TimescaleDB ([#9446](https://github.com/timescale/timescaledb/issues/9446)), `alter_job` fails when used with `DropCreatedBefore` policies. The library works around this by skipping the `alter_job` call for `DropCreatedBefore` policies. As a result, job scheduling parameters (`ScheduleInterval`, `MaxRuntime`, `MaxRetries`, `RetryPeriod`) are accepted by the API but have no effect at the database level when `DropCreatedBefore` is used.
> :warning: **Note:** Customizing job scheduling parameters (`ScheduleInterval`, `MaxRuntime`, `MaxRetries`, `RetryPeriod`) on a `DropCreatedBefore` policy requires **TimescaleDB 2.26.3 or later**. Earlier versions contain a bug in `alter_job` that prevents these settings from being applied for `drop_created_before` policies. Policies using `DropAfter` are not affected and work on all supported TimescaleDB versions.

## Complete Example

Expand Down
2 changes: 1 addition & 1 deletion docs/fluent-api/retention-policies.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ public class ApiRequestLogConfiguration : IEntityTypeConfiguration<ApiRequestLog
}
```

> :warning: **Note:** Due to a known bug in TimescaleDB ([#9446](https://github.com/timescale/timescaledb/issues/9446)), `alter_job` fails when used with `drop_created_before` policies. The library works around this by skipping the `alter_job` call for `drop_created_before` policies. As a result, job scheduling parameters (`scheduleInterval`, `maxRuntime`, `maxRetries`, `retryPeriod`) are accepted by the API but have no effect at the database level when `dropCreatedBefore` is used.
> :warning: **Note:** Customizing job scheduling parameters (`scheduleInterval`, `maxRuntime`, `maxRetries`, `retryPeriod`) on a `dropCreatedBefore` policy requires **TimescaleDB 2.26.3 or later**. Earlier versions contain a bug in `alter_job` that prevents these settings from being applied for `drop_created_before` policies. Policies using `dropAfter` are not affected and work on all supported TimescaleDB versions.

## Complete Example

Expand Down
20 changes: 0 additions & 20 deletions src/Eftdb/Generators/RetentionPolicyOperationGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -96,16 +96,6 @@ private static List<string> BuildAlterJobClauses(AddRetentionPolicyOperation ope
{
List<string> clauses = [];

// alter_job fails for drop_created_before retention policies.
// TimescaleDB's policy_retention_check expects drop_after in the job config JSONB
// but finds drop_created_before instead. Workaround: avoid alter_job for
// drop_created_before policies, or recreate the policy entirely.
// TODO: Remove this when a fix has been applied to TimescaleDB.
if (!string.IsNullOrEmpty(operation.DropCreatedBefore))
{
return clauses;
}

if (!string.IsNullOrWhiteSpace(operation.ScheduleInterval))
clauses.Add($"schedule_interval => INTERVAL '{operation.ScheduleInterval}'");

Expand All @@ -125,16 +115,6 @@ private static List<string> BuildAlterJobClauses(AlterRetentionPolicyOperation o
{
List<string> clauses = [];

// alter_job fails for drop_created_before retention policies.
// TimescaleDB's policy_retention_check expects drop_after in the job config JSONB
// but finds drop_created_before instead. Workaround: avoid alter_job for
// drop_created_before policies, or recreate the policy entirely.
// TODO: Remove this when a fix has been applied to TimescaleDB.
if (!string.IsNullOrEmpty(operation.DropCreatedBefore))
{
return clauses;
}

if (!string.IsNullOrWhiteSpace(operation.ScheduleInterval) && operation.ScheduleInterval != operation.OldScheduleInterval)
clauses.Add($"schedule_interval => INTERVAL '{operation.ScheduleInterval}'");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,10 @@ public void Generate_Add_DropAfter_with_minimal_config_creates_only_add_policy_s

#endregion

#region Generate_Add_DropCreatedBefore_creates_add_policy_without_alter_job
#region Generate_Add_DropCreatedBefore_with_job_settings_creates_add_and_alter_job_sql

[Fact]
public void Generate_Add_DropCreatedBefore_creates_add_policy_without_alter_job()
public void Generate_Add_DropCreatedBefore_with_job_settings_creates_add_and_alter_job_sql()
{
// Arrange
AddRetentionPolicyOperation operation = new()
Expand All @@ -62,10 +62,11 @@ public void Generate_Add_DropCreatedBefore_creates_add_policy_without_alter_job(
MaxRetries = 5
};

// DropCreatedBefore policies must not emit alter_job due to TimescaleDB bug #9446.
// Job settings are intentionally ignored.
string expected = @".Sql(@""
SELECT add_retention_policy('public.""""TestTable""""', drop_created_before => INTERVAL '30 days');
SELECT alter_job(job_id, schedule_interval => INTERVAL '1 day', max_retries => 5)
FROM timescaledb_information.jobs
WHERE proc_name = 'policy_retention' AND hypertable_schema = 'public' AND hypertable_name = 'TestTable';
"")";

// Act
Expand Down Expand Up @@ -138,10 +139,10 @@ FROM timescaledb_information.jobs

#endregion

#region Generate_Add_DropCreatedBefore_with_job_settings_still_omits_alter_job
#region Generate_Add_DropCreatedBefore_with_all_job_settings_creates_add_and_alter_job_sql

[Fact]
public void Generate_Add_DropCreatedBefore_with_job_settings_still_omits_alter_job()
public void Generate_Add_DropCreatedBefore_with_all_job_settings_creates_add_and_alter_job_sql()
{
// Arrange
AddRetentionPolicyOperation operation = new()
Expand All @@ -155,10 +156,11 @@ public void Generate_Add_DropCreatedBefore_with_job_settings_still_omits_alter_j
RetryPeriod = "10 minutes"
};

// Even with all job settings specified, alter_job is omitted for DropCreatedBefore
// due to TimescaleDB bug #9446 workaround.
string expected = @".Sql(@""
SELECT add_retention_policy('public.""""TestTable""""', drop_created_before => INTERVAL '30 days');
SELECT alter_job(job_id, schedule_interval => INTERVAL '2 days', max_runtime => INTERVAL '1 hour', max_retries => 5, retry_period => INTERVAL '10 minutes')
FROM timescaledb_information.jobs
WHERE proc_name = 'policy_retention' AND hypertable_schema = 'public' AND hypertable_name = 'TestTable';
"")";

// Act
Expand Down Expand Up @@ -240,10 +242,10 @@ FROM timescaledb_information.jobs

#endregion

#region Generate_Alter_changed_to_DropCreatedBefore_creates_remove_and_add_without_alter_job
#region Generate_Alter_changed_to_DropCreatedBefore_creates_remove_add_and_alter_job_sql

[Fact]
public void Generate_Alter_changed_to_DropCreatedBefore_creates_remove_and_add_without_alter_job()
public void Generate_Alter_changed_to_DropCreatedBefore_creates_remove_add_and_alter_job_sql()
{
// Arrange
AlterRetentionPolicyOperation operation = new()
Expand All @@ -258,11 +260,13 @@ public void Generate_Alter_changed_to_DropCreatedBefore_creates_remove_and_add_w
OldScheduleInterval = "1 day"
};

// During Alter recreation, alter_job is still emitted to reapply existing job settings.
// The DropCreatedBefore workaround only applies to the Add path.
// During recreation, alter_job is emitted to reapply the final-state job settings
string expected = @".Sql(@""
SELECT remove_retention_policy('public.""""TestTable""""', if_exists => true);
SELECT add_retention_policy('public.""""TestTable""""', drop_created_before => INTERVAL '30 days');
SELECT alter_job(job_id, schedule_interval => INTERVAL '1 day')
FROM timescaledb_information.jobs
WHERE proc_name = 'policy_retention' AND hypertable_schema = 'public' AND hypertable_name = 'TestTable';
"")";

// Act
Expand Down Expand Up @@ -400,10 +404,10 @@ FROM timescaledb_information.jobs

#endregion

#region Generate_Alter_DropCreatedBefore_job_settings_change_skips_alter_job
#region Generate_Alter_DropCreatedBefore_only_job_settings_change_emits_alter_job

[Fact]
public void Generate_Alter_DropCreatedBefore_job_settings_change_skips_alter_job()
public void Generate_Alter_DropCreatedBefore_only_job_settings_change_emits_alter_job()
{
// Arrange
AlterRetentionPolicyOperation operation = new()
Expand All @@ -420,12 +424,17 @@ public void Generate_Alter_DropCreatedBefore_job_settings_change_skips_alter_job
OldScheduleInterval = "1 day"
};

// No recreation needed, but alter_job is skipped for DropCreatedBefore due to TimescaleDB bug #9446.
RetentionPolicyOperationGenerator generator = new(true);
List<string> result = generator.Generate(operation);
string expected = @".Sql(@""
SELECT alter_job(job_id, schedule_interval => INTERVAL '2 days')
FROM timescaledb_information.jobs
WHERE proc_name = 'policy_retention' AND hypertable_schema = 'public' AND hypertable_name = 'TestTable';
"")";

// Act
string result = GetGeneratedCode(operation);

// Assert
Assert.Empty(result);
Assert.Equal(SqlHelper.NormalizeSql(expected), SqlHelper.NormalizeSql(result));
}

#endregion
Expand Down
Loading
Loading