Skip to content

Commit 09fa488

Browse files
Cockroach upgrade - autocommit_before_ddl (#1348)
* upgrade crdb to the latest regular release * remove cockroach_upgrade branch from CI * settings applied to the current transaction only * make changes to transaction and atomic * remove postgres password * `autocommit_before_ddl` experiments * arg -> kwarg * tweak docs * fix linter error * fix test * Update tests.yaml * more test fixes --------- Co-authored-by: sinisaos <[email protected]>
1 parent d830e6e commit 09fa488

File tree

6 files changed

+86
-20
lines changed

6 files changed

+86
-20
lines changed

.github/workflows/tests.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ jobs:
142142
strategy:
143143
matrix:
144144
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
145-
cockroachdb-version: ["v24.1.0"]
145+
cockroachdb-version: ["v25.4.3"]
146146
steps:
147147
- uses: actions/checkout@v3
148148
- name: Set up Python ${{ matrix.python-version }}

piccolo/apps/migrations/auto/migration_manager.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
from piccolo.columns import Column, column_types
1919
from piccolo.columns.column_types import ForeignKey, Serial
2020
from piccolo.engine import engine_finder
21+
from piccolo.engine.cockroach import CockroachTransaction
2122
from piccolo.query import Query
2223
from piccolo.query.base import DDL
2324
from piccolo.query.constraints import get_fk_constraint_name
@@ -984,7 +985,11 @@ async def run(self, backwards: bool = False):
984985
engine.transaction()
985986
if self.wrap_in_transaction
986987
else SkippedTransaction()
987-
):
988+
) as transaction:
989+
if isinstance(transaction, CockroachTransaction):
990+
# To enable DDL rollbacks in CockroachDB.
991+
await transaction.autocommit_before_ddl(enabled=False)
992+
988993
if not self.preview:
989994
if direction == "backwards":
990995
raw_list = self.raw_backwards

piccolo/engine/cockroach.py

Lines changed: 65 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,57 @@
11
from __future__ import annotations
22

33
from collections.abc import Sequence
4-
from typing import Any, Optional
4+
from typing import Any, Optional, cast
55

6-
from piccolo.utils.lazy_loader import LazyLoader
7-
from piccolo.utils.warnings import Level, colored_warning
6+
from .postgres import Atomic, PostgresEngine, PostgresTransaction
87

9-
from .postgres import PostgresEngine
108

11-
asyncpg = LazyLoader("asyncpg", globals(), "asyncpg")
9+
class CockroachAtomic(Atomic):
10+
11+
__slots__ = ("autocommit_before_ddl",)
12+
13+
def __init__(
14+
self,
15+
engine: CockroachEngine,
16+
autocommit_before_ddl: Optional[bool] = False,
17+
):
18+
"""
19+
:param autocommit_before_ddl:
20+
Defaults to ``False`` to prevent automatic DDL commits
21+
in transactions (preventing rollbacks). Applies only to the current
22+
transaction and is automatically reverted when the transaction
23+
commits or is rolled back.
24+
25+
Usage::
26+
27+
# Defaults to ``False`` (``autocommit_before_ddl = off``)
28+
transaction = engine.atomic()
29+
transaction.add(Foo.create_table())
30+
31+
# If we want to set ``autocommit_before_ddl = on``,
32+
# which is the default Cockroach session setting.
33+
transaction = engine.atomic(autocommit_before_ddl=True)
34+
transaction.add(Foo.create_table())
35+
36+
"""
37+
super().__init__(engine)
38+
self.autocommit_before_ddl = autocommit_before_ddl
39+
40+
async def setup_transaction(self, transaction: PostgresTransaction):
41+
if self.autocommit_before_ddl is not None:
42+
transaction = cast(CockroachTransaction, transaction)
43+
await transaction.autocommit_before_ddl(
44+
enabled=self.autocommit_before_ddl
45+
)
46+
47+
48+
class CockroachTransaction(PostgresTransaction):
49+
50+
async def autocommit_before_ddl(self, enabled: bool = True):
51+
value = "on" if enabled else "off"
52+
await self.connection.execute(
53+
f"SET LOCAL autocommit_before_ddl = {value}"
54+
)
1255

1356

1457
class CockroachEngine(PostgresEngine):
@@ -35,15 +78,20 @@ def __init__(
3578
self.engine_type = "cockroach"
3679
self.min_version_number = 0
3780

38-
async def prep_database(self):
39-
try:
40-
await self._run_in_new_connection(
41-
"SET CLUSTER SETTING sql.defaults.experimental_alter_column_type.enabled = true;" # noqa: E501
42-
)
43-
except asyncpg.exceptions.InsufficientPrivilegeError:
44-
colored_warning(
45-
"=> Unable to set up Cockroach DB "
46-
"functionality may not behave as expected. Make sure "
47-
"your database user has permission to set cluster options.",
48-
level=Level.medium,
49-
)
81+
def atomic(
82+
self,
83+
autocommit_before_ddl: Optional[bool] = False,
84+
) -> CockroachAtomic:
85+
return CockroachAtomic(
86+
engine=self,
87+
autocommit_before_ddl=autocommit_before_ddl,
88+
)
89+
90+
def transaction(
91+
self,
92+
allow_nested: bool = True,
93+
) -> CockroachTransaction:
94+
return CockroachTransaction(
95+
engine=self,
96+
allow_nested=allow_nested,
97+
)

piccolo/engine/postgres.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,11 +113,15 @@ def __init__(self, engine: PostgresEngine):
113113
def add(self, *query: Union[Query, DDL]):
114114
self.queries += list(query)
115115

116+
async def setup_transaction(self, transaction: PostgresTransaction):
117+
pass
118+
116119
async def run(self):
117120
from piccolo.query.methods.objects import Create, GetOrCreate
118121

119122
try:
120-
async with self.engine.transaction():
123+
async with self.engine.transaction() as transaction:
124+
await self.setup_transaction(transaction=transaction)
121125
for query in self.queries:
122126
if isinstance(query, (Query, DDL, Create, GetOrCreate)):
123127
await query.run()

piccolo/testing/test_case.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from unittest import IsolatedAsyncioTestCase, TestCase
55

66
from piccolo.engine import Engine, engine_finder
7+
from piccolo.engine.cockroach import CockroachTransaction
78
from piccolo.table import (
89
Table,
910
create_db_tables,
@@ -112,9 +113,13 @@ async def asyncSetUp(self) -> None:
112113
db = self.db or engine_finder()
113114
assert db is not None
114115
self.transaction = db.transaction()
116+
115117
# This is only available in Python 3.11 and above:
116118
await self.enterAsyncContext(cm=self.transaction) # type: ignore
117119

120+
if isinstance(self.transaction, CockroachTransaction):
121+
await self.transaction.autocommit_before_ddl(False)
122+
118123
async def asyncTearDown(self):
119124
await super().asyncTearDown()
120125
await self.transaction.rollback()

tests/engine/test_transaction.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import pytest
66

7+
from piccolo.engine.cockroach import CockroachTransaction
78
from piccolo.engine.sqlite import SQLiteEngine, TransactionType
89
from piccolo.table import drop_db_tables_sync
910
from piccolo.utils.sync import run_sync
@@ -132,6 +133,9 @@ def test_manual_rollback(self):
132133

133134
async def run_transaction():
134135
async with Band._meta.db.transaction() as transaction:
136+
if isinstance(transaction, CockroachTransaction):
137+
await transaction.autocommit_before_ddl(enabled=False)
138+
135139
await Manager.create_table()
136140
await transaction.rollback()
137141

0 commit comments

Comments
 (0)