Skip to content

Commit

Permalink
Implement server_default for SA columns
Browse files Browse the repository at this point in the history
CrateDB's SQLAlchemy dialect now handles the `server_default` when generating
table DDL.

Fix #454.
  • Loading branch information
JanLikar committed May 26, 2023
1 parent 9032a3f commit a16e476
Show file tree
Hide file tree
Showing 3 changed files with 58 additions and 1 deletion.
2 changes: 2 additions & 0 deletions docs/sqlalchemy.rst
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,7 @@ system <sa:orm_declarative_mapping>`:
... more_details = sa.Column(types.ObjectArray)
... name_ft = sa.Column(sa.String)
... quote_ft = sa.Column(sa.String)
... created_at = sa.Column(sa.DateTime, server_default=sa.func.now())
...
... __mapper_args__ = {
... 'exclude_properties': ['name_ft', 'quote_ft']
Expand All @@ -226,6 +227,7 @@ In this example, we:
- Use the `ObjectArray`_ extension type for the ``more_details`` column
- Set up the ``name_ft`` and ``quote_ft`` fulltext indexes, but exclude them from
the mapping (so SQLAlchemy doesn't try to update them as if they were columns)
- Add a `created_at` column whose default value is set by CrateDB's `now()` function.

.. TIP::

Expand Down
5 changes: 4 additions & 1 deletion src/crate/client/sqlalchemy/compiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,10 @@ class CrateDDLCompiler(compiler.DDLCompiler):
def get_column_specification(self, column, **kwargs):
colspec = self.preparer.format_column(column) + " " + \
self.dialect.type_compiler.process(column.type)
# TODO: once supported add default here

default = self.get_column_default_string(column)
if default is not None:
colspec += " DEFAULT " + default

if column.computed is not None:
colspec += " " + self.process(column.computed)
Expand Down
52 changes: 52 additions & 0 deletions src/crate/client/sqlalchemy/tests/create_table_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -232,3 +232,55 @@ class DummyTable(self.Base):
a = sa.Column(Geopoint, crate_index=False)
with self.assertRaises(sa.exc.CompileError):
self.Base.metadata.create_all(bind=self.engine)

def test_column_server_default(self):
class DummyTable(self.Base):
__tablename__ = 't'
pk = sa.Column(sa.String, primary_key=True)
a = sa.Column(sa.DateTime, server_default=sa.text("now()"))

self.Base.metadata.create_all(bind=self.engine)
fake_cursor.execute.assert_called_with(
('\nCREATE TABLE t (\n\t'
'pk STRING NOT NULL, \n\t'
'a TIMESTAMP DEFAULT now(), \n\t'
'PRIMARY KEY (pk)\n)\n\n'), ())

def test_column_server_default_string(self):
class DummyTable(self.Base):
__tablename__ = 't'
pk = sa.Column(sa.String, primary_key=True)
a = sa.Column(sa.DateTime, server_default="Zaphod")

self.Base.metadata.create_all(bind=self.engine)
fake_cursor.execute.assert_called_with(
('\nCREATE TABLE t (\n\t'
'pk STRING NOT NULL, \n\t'
'a TIMESTAMP DEFAULT \'Zaphod\', \n\t'
'PRIMARY KEY (pk)\n)\n\n'), ())

def test_column_server_default_func(self):
class DummyTable(self.Base):
__tablename__ = 't'
pk = sa.Column(sa.String, primary_key=True)
a = sa.Column(sa.DateTime, server_default=sa.func.now())

self.Base.metadata.create_all(bind=self.engine)
fake_cursor.execute.assert_called_with(
('\nCREATE TABLE t (\n\t'
'pk STRING NOT NULL, \n\t'
'a TIMESTAMP DEFAULT now(), \n\t'
'PRIMARY KEY (pk)\n)\n\n'), ())

def test_column_server_default_constant(self):
class DummyTable(self.Base):
__tablename__ = 't'
pk = sa.Column(sa.String, primary_key=True)
answer = sa.Column(sa.Integer, server_default=sa.text("42"))

self.Base.metadata.create_all(bind=self.engine)
fake_cursor.execute.assert_called_with(
('\nCREATE TABLE t (\n\t'
'pk STRING NOT NULL, \n\t'
'answer INT DEFAULT 42, \n\t'
'PRIMARY KEY (pk)\n)\n\n'), ())

0 comments on commit a16e476

Please sign in to comment.