Skip to content

Commit 4c62ecf

Browse files
JanLikaramotl
authored andcommitted
Implement server_default for SA columns
CrateDB's SQLAlchemy dialect now handles the `server_default` when generating table DDL. Fix #454.
1 parent 7b86801 commit 4c62ecf

File tree

4 files changed

+60
-2
lines changed

4 files changed

+60
-2
lines changed

CHANGES.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ Unreleased
1616
SQLAlchemy 2.0 by adding the new ``insert_returning`` and ``update_returning`` flags
1717
in the CrateDB dialect.
1818

19+
- SQLAlchemy DDL: Allow setting ``server_default`` on columns to enable server-generated defaults.
1920

2021
2023/03/30 0.31.0
2122
=================

docs/sqlalchemy.rst

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,7 @@ system <sa:orm_declarative_mapping>`:
206206
... name_ft = sa.Column(sa.String)
207207
... quote_ft = sa.Column(sa.String)
208208
... even_more_details = sa.Column(sa.String, crate_columnstore=False)
209+
... created_at = sa.Column(sa.DateTime, server_default=sa.func.now())
209210
...
210211
... __mapper_args__ = {
211212
... 'exclude_properties': ['name_ft', 'quote_ft']
@@ -221,13 +222,14 @@ In this example, we:
221222
- Use standard SQLAlchemy types for the ``id``, ``name``, and ``quote`` columns
222223
- Use ``nullable=False`` to define a ``NOT NULL`` constraint
223224
- Disable indexing of the ``name`` column using ``crate_index=False``
224-
- Disable the columnstore of the ``even_more_details`` column using ``crate_columnstore=False``
225225
- Define a computed column ``name_normalized`` (based on ``name``) that
226226
translates into a generated column
227227
- Use the `Object`_ extension type for the ``details`` column
228228
- Use the `ObjectArray`_ extension type for the ``more_details`` column
229229
- Set up the ``name_ft`` and ``quote_ft`` fulltext indexes, but exclude them from
230230
the mapping (so SQLAlchemy doesn't try to update them as if they were columns)
231+
- Disable the columnstore of the ``even_more_details`` column using ``crate_columnstore=False``
232+
- Add a ``created_at`` column whose default value is set by CrateDB's ``now()`` function.
231233

232234
.. TIP::
233235

src/crate/client/sqlalchemy/compiler.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,10 @@ class CrateDDLCompiler(compiler.DDLCompiler):
108108
def get_column_specification(self, column, **kwargs):
109109
colspec = self.preparer.format_column(column) + " " + \
110110
self.dialect.type_compiler.process(column.type)
111-
# TODO: once supported add default here
111+
112+
default = self.get_column_default_string(column)
113+
if default is not None:
114+
colspec += " DEFAULT " + default
112115

113116
if column.computed is not None:
114117
colspec += " " + self.process(column.computed)

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

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,3 +259,55 @@ class DummyTable(self.Base):
259259

260260
with self.assertRaises(sa.exc.CompileError):
261261
self.Base.metadata.create_all(bind=self.engine)
262+
263+
def test_column_server_default_text_func(self):
264+
class DummyTable(self.Base):
265+
__tablename__ = 't'
266+
pk = sa.Column(sa.String, primary_key=True)
267+
a = sa.Column(sa.DateTime, server_default=sa.text("now()"))
268+
269+
self.Base.metadata.create_all(bind=self.engine)
270+
fake_cursor.execute.assert_called_with(
271+
('\nCREATE TABLE t (\n\t'
272+
'pk STRING NOT NULL, \n\t'
273+
'a TIMESTAMP DEFAULT now(), \n\t'
274+
'PRIMARY KEY (pk)\n)\n\n'), ())
275+
276+
def test_column_server_default_string(self):
277+
class DummyTable(self.Base):
278+
__tablename__ = 't'
279+
pk = sa.Column(sa.String, primary_key=True)
280+
a = sa.Column(sa.String, server_default="Zaphod")
281+
282+
self.Base.metadata.create_all(bind=self.engine)
283+
fake_cursor.execute.assert_called_with(
284+
('\nCREATE TABLE t (\n\t'
285+
'pk STRING NOT NULL, \n\t'
286+
'a STRING DEFAULT \'Zaphod\', \n\t'
287+
'PRIMARY KEY (pk)\n)\n\n'), ())
288+
289+
def test_column_server_default_func(self):
290+
class DummyTable(self.Base):
291+
__tablename__ = 't'
292+
pk = sa.Column(sa.String, primary_key=True)
293+
a = sa.Column(sa.DateTime, server_default=sa.func.now())
294+
295+
self.Base.metadata.create_all(bind=self.engine)
296+
fake_cursor.execute.assert_called_with(
297+
('\nCREATE TABLE t (\n\t'
298+
'pk STRING NOT NULL, \n\t'
299+
'a TIMESTAMP DEFAULT now(), \n\t'
300+
'PRIMARY KEY (pk)\n)\n\n'), ())
301+
302+
def test_column_server_default_text_constant(self):
303+
class DummyTable(self.Base):
304+
__tablename__ = 't'
305+
pk = sa.Column(sa.String, primary_key=True)
306+
answer = sa.Column(sa.Integer, server_default=sa.text("42"))
307+
308+
self.Base.metadata.create_all(bind=self.engine)
309+
fake_cursor.execute.assert_called_with(
310+
('\nCREATE TABLE t (\n\t'
311+
'pk STRING NOT NULL, \n\t'
312+
'answer INT DEFAULT 42, \n\t'
313+
'PRIMARY KEY (pk)\n)\n\n'), ())

0 commit comments

Comments
 (0)