dbt-labs / dbt-labs/dbt-adapters
feat(redshift): add profile option to drop relations without CASCADE
- Dominant language
- Python
- Stars
- 233
- Forks
- 362
- Avg merge
- 3d 22h
- Merged PRs (30d)
- 9
Description
## Summary
Add a profile credentials option (`drop_without_cascade` or similar) to omit `CASCADE` from `DROP TABLE/VIEW/MATERIALIZED VIEW` statements in dbt-redshift.
## Background
All three dbt-redshift drop macros unconditionally append `CASCADE`:
**`macros/relations/table/drop.sql`** (line 2):
```sql
drop table if exists {{ relation }} cascade
```
**`macros/relations/view/drop.sql`** (line 2):
```sql
drop view if exists {{ relation }} cascade
```
**`macros/relations/materialized_view/drop.sql`** (line 2):
```sql
drop materialized view if exists {{ relation }} cascade
```
`CASCADE` instructs Redshift to automatically drop all dependent objects (views, etc.). On clusters with many objects, Redshift must walk the full dependency graph to resolve each `DROP ... CASCADE`, which is expensive. For users who exclusively use **unbound views** (or have no downstream views at all), there are no dependents to cascade to — the overhead is pure waste.
Additionally, `CASCADE` is the root cause of the concurrent-transaction race condition that requires the global thread lock (see related issue). Removing `CASCADE` also opens the door to removing the lock.
## Proposed Change
Add a boolean profile credential field `drop_without_cascade` (default `False`) that, when `True`, emits `DROP ... RESTRICT` (or simply omits `CASCADE`) instead.
### Files to change
**`dbt-redshift/src/dbt/adapters/redshift/connections.py`**
Add to `RedshiftCredentials` dataclass (~line 190):
```python
# Drop behavior
drop_without_cascade: bool = False
```
Add to `_connection_keys()` (~line 228):
```python
"drop_without_cascade",
```
**`macros/relations/table/drop.sql`**:
```sql
{%- macro redshift__drop_table(relation) -%}
drop table if exists {{ relation }} {% if not adapter.config.credentials.drop_without_cascade %}cascade{% endif %}
{%- endmacro -%}
```
**`macros/relations/view/drop.sql`**:
```sql
{%- macro redshift__drop_view(relation) -%}
drop view if exists {{ relation }} {% if not adapter.config.credentials.drop_without_cascade %}cascade{% endif %}
{%- endmacro -%}
```
**`macros/relations/materialized_view/drop.sql`**:
```sql
{% macro redshift__drop_materialized_view(relation) -%}
drop materialized view if exists {{ relation }} {% if not adapter.config.credentials.drop_without_cascade %}cascade{% endif %}
{%- endmacro %}
```
> **Note:** The exact mechanism for passing credentials into Jinja macros should follow the existing pattern used by `redshift__use_show_apis()` in `macros/metadata/helpers.sql`. Alternatively this could be implemented as a Python-level override of the drop macros using `execute()` directly in `impl.py`.
## Safety
- Default is `False` — no change in behavior for existing users.
- Safe only for projects that don't have dependent views downstream of dropped relations (e.g., projects using only unbound views, or where dbt manages all dependencies).
- Must be documented with a clear warning: if a dependent object exists and `CASCADE` is omitted, Redshift will raise an error rather than silently dropping it.
- Pairs naturally with `drop_without_lock` (see related issue): users who set both get maximum DROP performance with minimal risk when their project has no downstream view dependencies.
## Related
- Related to the `drop_without_lock` feature request — the thread lock exists specifically because of `CASCADE` race conditions.
Contributor guide
Assessment
This issue has not been assessed yet.