Skip to content

Commit 48cae8d

Browse files
authored
Fix AttributeError in CrateCompiler and add test (#278)
1 parent d4b5ae9 commit 48cae8d

3 files changed

Lines changed: 27 additions & 6 deletions

File tree

CHANGES.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
# Changelog
22

3+
## Unreleased
4+
- Compiler: Fixed `AttributeError: 'CrateCompilerSA20' object has no attribute
5+
'visit_on_conflict_do_update'` by forwarding calls to
6+
`PGCompiler.visit_on_conflict_do_update`
7+
38
## 2026/06/17 0.43.0
49
- Types: Improved support for FLOAT type, converging to FLOAT vs. DOUBLE
510
- Types: Added method `ObjectArray.as_generic` for better reverse type lookups
@@ -10,9 +15,6 @@
1015
newly introduced `sqltypes.{DOUBLE,DOUBLE_PRECISION}` types
1116
- Compiler: Made `CREATE INDEX` a no-op, only emitting `SELECT 1`, because CrateDB
1217
does not support that statement
13-
- Compiler: Fixed `AttributeError: 'CrateCompilerSA20' object has no attribute
14-
'visit_on_conflict_do_update'` by forwarding calls to
15-
`PGCompiler.visit_on_conflict_do_update`
1618
- Dialect: Added methods concerned with isolation levels as no-ops
1719
- Types: Started emulating PostgreSQL's `JSON(B)` types using CrateDB's `OBJECT`
1820
- Dialect: now uses `paramstyle = "pyformat"` supported in crate-python 2.2.1.

src/sqlalchemy_cratedb/compiler.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -213,9 +213,6 @@ def visit_create_index(self, create, **kw) -> str:
213213

214214

215215
class CrateTypeCompiler(compiler.GenericTypeCompiler):
216-
visit_on_conflict_do_update = PGCompiler.visit_on_conflict_do_update
217-
_on_conflict_target = PGCompiler._on_conflict_target
218-
219216
def visit_string(self, type_, **kw):
220217
return "STRING"
221218

@@ -297,6 +294,9 @@ def visit_JSONB(self, type_, **kw):
297294

298295

299296
class CrateCompiler(compiler.SQLCompiler):
297+
visit_on_conflict_do_update = PGCompiler.visit_on_conflict_do_update
298+
_on_conflict_target = PGCompiler._on_conflict_target
299+
300300
def visit_getitem_binary(self, binary, operator, **kw):
301301
return "{0}['{1}']".format(self.process(binary.left, **kw), binary.right.value)
302302

tests/compiler_test.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525

2626
import sqlalchemy as sa
2727
from crate.client.cursor import Cursor
28+
from sqlalchemy.dialects.postgresql import insert as pg_insert
2829
from sqlalchemy.sql import Update, text
2930

3031
from sqlalchemy_cratedb.compiler import crate_before_execute
@@ -332,6 +333,24 @@ def test_for_update(self):
332333
str(w[-1].message),
333334
)
334335

336+
@skipIf(
337+
SA_VERSION < SA_2_0,
338+
"on_conflict_do_update is only exercised via CrateCompilerSA20",
339+
)
340+
def test_insert_on_conflict_do_update(self):
341+
"""
342+
Verify that INSERT ... ON CONFLICT DO UPDATE compiles without
343+
an error.
344+
"""
345+
stmt = pg_insert(self.mytable).values(name="foo", data={"x": 1})
346+
upsert = stmt.on_conflict_do_update(
347+
index_elements=["name"],
348+
set_={"data": {"x": 2}},
349+
)
350+
sql = str(upsert.compile(bind=self.crate_engine))
351+
self.assertIn("ON CONFLICT (name)", sql)
352+
self.assertIn("DO UPDATE SET data =", sql)
353+
335354

336355
FakeCursor = MagicMock(name="FakeCursor", spec=Cursor)
337356

0 commit comments

Comments
 (0)