Skip to content

Commit 33e74cf

Browse files
committed
Compiler: Add CrateIdentifierPreparer
By using this component of the SQLAlchemy dialect compiler, it can define CrateDB's reserved words to be quoted properly when building SQL statements. This allows to quote reserved words like `object` properly, for example when used as column names.
1 parent 877ebaa commit 33e74cf

File tree

4 files changed

+67
-1
lines changed

4 files changed

+67
-1
lines changed

CHANGES.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33

44
## Unreleased
55
- Added/reactivated documentation as `sqlalchemy-cratedb`
6+
- Added `CrateIdentifierPreparer`, in order to quote reserved words
7+
like `object` properly, for example when used as column names.
68

79
## 2024/06/13 0.37.0
810
- Added support for CrateDB's [FLOAT_VECTOR] data type and its accompanying

src/sqlalchemy_cratedb/compiler.py

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

2626
import sqlalchemy as sa
2727
from sqlalchemy.dialects.postgresql.base import PGCompiler
28+
from sqlalchemy.dialects.postgresql.base import RESERVED_WORDS as POSTGRESQL_RESERVED_WORDS
2829
from sqlalchemy.sql import compiler
2930
from sqlalchemy.types import String
3031
from .type.geo import Geopoint, Geoshape
@@ -323,3 +324,36 @@ def for_update_clause(self, select, **kw):
323324
warnings.warn("CrateDB does not support the 'INSERT ... FOR UPDATE' clause, "
324325
"it will be omitted when generating SQL statements.")
325326
return ''
327+
328+
329+
# TODO: There are certainly more to add here than just `object`?
330+
CRATEDB_RESERVED_WORDS = ["object"]
331+
332+
333+
class CrateIdentifierPreparer(sa.sql.compiler.IdentifierPreparer):
334+
"""
335+
Define CrateDB's reserved words to be quoted properly.
336+
"""
337+
reserved_words = set(list(POSTGRESQL_RESERVED_WORDS) + CRATEDB_RESERVED_WORDS)
338+
339+
def _unquote_identifier(self, value):
340+
if value[0] == self.initial_quote:
341+
value = value[1:-1].replace(
342+
self.escape_to_quote, self.escape_quote
343+
)
344+
return value
345+
346+
def format_type(self, type_, use_schema=True):
347+
if not type_.name:
348+
raise sa.exc.CompileError("Type requires a name.")
349+
350+
name = self.quote(type_.name)
351+
effective_schema = self.schema_for_object(type_)
352+
353+
if (
354+
not self.omit_schema
355+
and use_schema
356+
and effective_schema is not None
357+
):
358+
name = self.quote_schema(effective_schema) + "." + name
359+
return name

src/sqlalchemy_cratedb/dialect.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@
2929

3030
from .compiler import (
3131
CrateTypeCompiler,
32-
CrateDDLCompiler
32+
CrateDDLCompiler,
33+
CrateIdentifierPreparer,
3334
)
3435
from crate.client.exceptions import TimezoneUnawareException
3536
from .sa_version import SA_VERSION, SA_1_4, SA_2_0
@@ -174,6 +175,7 @@ class CrateDialect(default.DefaultDialect):
174175
statement_compiler = statement_compiler
175176
ddl_compiler = CrateDDLCompiler
176177
type_compiler = CrateTypeCompiler
178+
preparer = CrateIdentifierPreparer
177179
use_insertmanyvalues = True
178180
use_insertmanyvalues_wo_returning = True
179181
supports_multivalues_insert = True

tests/compiler_test.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -432,3 +432,31 @@ class FooBar(Base):
432432
self.assertIsSubclass(w[-1].category, UserWarning)
433433
self.assertIn("CrateDB does not support unique constraints, "
434434
"they will be omitted when generating DDL statements.", str(w[-1].message))
435+
436+
def test_ddl_with_reserved_words(self):
437+
"""
438+
Verify the CrateDB dialect properly ignores unique key constraints.
439+
"""
440+
441+
Base = declarative_base(metadata=self.metadata)
442+
443+
class FooBar(Base):
444+
"""The entity."""
445+
446+
__tablename__ = "foobar"
447+
448+
id = sa.Column(sa.Integer, primary_key=True)
449+
array = sa.Column(sa.String)
450+
object = sa.Column(sa.String)
451+
452+
# Verify SQL DDL statement.
453+
self.metadata.create_all(self.engine, tables=[FooBar.__table__], checkfirst=False)
454+
self.assertEqual(self.executed_statement, dedent("""
455+
CREATE TABLE testdrive.foobar (
456+
\tid INT NOT NULL,
457+
\t"array" STRING,
458+
\t"object" STRING,
459+
\tPRIMARY KEY (id)
460+
)
461+
462+
""")) # noqa: W291, W293

0 commit comments

Comments
 (0)