Skip to content

Commit 5d60ddf

Browse files
committed
Support computed columns in SQLAlchemy ORM
1 parent ab6f03a commit 5d60ddf

4 files changed

Lines changed: 42 additions & 1 deletion

File tree

CHANGES.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ Unreleased
1111
that SQL statement clauses like ``LIMIT -1`` could have been generated. Both
1212
PostgreSQL and CrateDB only accept ``LIMIT ALL`` instead.
1313

14+
- Added support for computed columns in the SQLAlchemy ORM
1415

1516
2022/10/10 0.27.2
1617
=================

docs/sqlalchemy.rst

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,9 +179,10 @@ system`_::
179179
...
180180
... id = sa.Column(sa.String, primary_key=True, default=gen_key)
181181
... name = sa.Column(sa.String)
182+
... name_normalized = sa.Column(sa.String, sa.Computed("lower(name)"))
182183
... quote = sa.Column(sa.String)
183184
... details = sa.Column(types.Object)
184-
... more_details = sa.Column(ObjectArray)
185+
... more_details = sa.Column(types.ObjectArray)
185186
... name_ft = sa.Column(sa.String)
186187
... quote_ft = sa.Column(sa.String)
187188
...
@@ -197,6 +198,8 @@ In this example, we:
197198
- Use the ``gen_key`` function to provide a default value for the ``id`` column
198199
(which is also the primary key)
199200
- Use standard SQLAlchemy types for the ``id``, ``name``, and ``quote`` columns
201+
- Define a computed column ``name_normalized`` (based on ``name``) that
202+
translates into a generated column
200203
- Use the `Object`_ extension type for the ``details`` column
201204
- Use the `ObjectArray`_ extension type for the ``more_details`` column
202205
- Set up the ``name_ft`` and ``quote_ft`` fulltext indexes, but exclude them from

src/crate/client/sqlalchemy/compiler.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,8 +108,23 @@ def get_column_specification(self, column, **kwargs):
108108
colspec = self.preparer.format_column(column) + " " + \
109109
self.dialect.type_compiler.process(column.type)
110110
# TODO: once supported add default / NOT NULL here
111+
112+
if column.computed is not None:
113+
colspec += " " + self.process(column.computed)
114+
111115
return colspec
112116

117+
def visit_computed_column(self, generated):
118+
if generated.persisted is False:
119+
raise sa.exc.CompileError(
120+
"Virtual computed columns are not supported, set "
121+
"'persisted' to None or True"
122+
)
123+
124+
return "GENERATED ALWAYS AS (%s)" % self.sql_compiler.process(
125+
generated.sqltext, include_table=False, literal_binds=True
126+
)
127+
113128
def post_create_table(self, table):
114129
special_options = ''
115130
clustered_options = defaultdict(str)

src/crate/client/sqlalchemy/tests/create_table_test.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,28 @@ class DummyTable(self.Base):
9797
') CLUSTERED BY (p)\n\n'),
9898
())
9999

100+
def test_with_computed_column(self):
101+
class DummyTable(self.Base):
102+
__tablename__ = 't'
103+
ts = sa.Column(sa.BigInteger, primary_key=True)
104+
p = sa.Column(sa.BigInteger, sa.Computed("date_trunc('day', ts)"))
105+
self.Base.metadata.create_all()
106+
fake_cursor.execute.assert_called_with(
107+
('\nCREATE TABLE t (\n\t'
108+
'ts LONG, \n\t'
109+
'p LONG GENERATED ALWAYS AS (date_trunc(\'day\', ts)), \n\t'
110+
'PRIMARY KEY (ts)\n'
111+
')\n\n'),
112+
())
113+
114+
def test_with_virtual_computed_column(self):
115+
class DummyTable(self.Base):
116+
__tablename__ = 't'
117+
ts = sa.Column(sa.BigInteger, primary_key=True)
118+
p = sa.Column(sa.BigInteger, sa.Computed("date_trunc('day', ts)", persisted=False))
119+
with self.assertRaises(sa.exc.CompileError):
120+
self.Base.metadata.create_all()
121+
100122
def test_with_partitioned_by(self):
101123
class DummyTable(self.Base):
102124
__tablename__ = 't'

0 commit comments

Comments
 (0)