From 1d9cf515cd8059c2581c2876904456d01331c4e5 Mon Sep 17 00:00:00 2001 From: Jan Likar Date: Fri, 26 May 2023 23:38:10 +0200 Subject: [PATCH] Implement server_default for SA columns CrateDB's SQLAlchemy dialect now handles the `server_default` when generating table DDL. Fix #454. --- docs/sqlalchemy.rst | 2 + src/crate/client/sqlalchemy/compiler.py | 5 +- .../sqlalchemy/tests/create_table_test.py | 52 +++++++++++++++++++ 3 files changed, 58 insertions(+), 1 deletion(-) diff --git a/docs/sqlalchemy.rst b/docs/sqlalchemy.rst index 00bf5d00..40238140 100644 --- a/docs/sqlalchemy.rst +++ b/docs/sqlalchemy.rst @@ -205,6 +205,7 @@ system `: ... 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'] @@ -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:: diff --git a/src/crate/client/sqlalchemy/compiler.py b/src/crate/client/sqlalchemy/compiler.py index 7e6dad7d..29b695df 100644 --- a/src/crate/client/sqlalchemy/compiler.py +++ b/src/crate/client/sqlalchemy/compiler.py @@ -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) diff --git a/src/crate/client/sqlalchemy/tests/create_table_test.py b/src/crate/client/sqlalchemy/tests/create_table_test.py index 7eca2628..b2c9d4f1 100644 --- a/src/crate/client/sqlalchemy/tests/create_table_test.py +++ b/src/crate/client/sqlalchemy/tests/create_table_test.py @@ -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_text_func(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 TEXT 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_text_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'), ())