From 87909247329e7436454c693183581b634a8fa25b Mon Sep 17 00:00:00 2001 From: surister Date: Sun, 17 Sep 2023 19:49:46 +0200 Subject: [PATCH] SQLAlchemy: Add support for 'autogenerate_uuid' in column creation --- CHANGES.txt | 1 + docs/sqlalchemy.rst | 3 ++ src/crate/client/sqlalchemy/compiler.py | 16 +++++++++ .../sqlalchemy/tests/create_table_test.py | 33 +++++++++++++++++++ 4 files changed, 53 insertions(+) diff --git a/CHANGES.txt b/CHANGES.txt index 2ffe8765..b9534fbb 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -5,6 +5,7 @@ Changes for crate Unreleased ========== +- Add support for CrateDB's gen_random_text_uuid in SQLAlchemy column generation ORM - Properly handle Python-native UUID types in SQL parameters - SQLAlchemy: Fix handling URL parameters ``timeout`` and ``pool_size`` - Permit installation with urllib3 v2, see also `urllib3 v2.0 roadmap`_ diff --git a/docs/sqlalchemy.rst b/docs/sqlalchemy.rst index c31396ab..798ba9f6 100644 --- a/docs/sqlalchemy.rst +++ b/docs/sqlalchemy.rst @@ -207,6 +207,7 @@ system `: ... quote_ft = sa.Column(sa.String) ... even_more_details = sa.Column(sa.String, crate_columnstore=False) ... created_at = sa.Column(sa.DateTime, server_default=sa.func.now()) + ... uuid = sa.Column(sa.String, crate_autogenerate_uuid=True) ... ... __mapper_args__ = { ... 'exclude_properties': ['name_ft', 'quote_ft'] @@ -230,6 +231,8 @@ In this example, we: the mapping (so SQLAlchemy doesn't try to update them as if they were columns) - Disable the columnstore of the ``even_more_details`` column using ``crate_columnstore=False`` - Add a ``created_at`` column whose default value is set by CrateDB's ``now()`` function. +- Add a ``uuid`` column whose default value is generated by CrateDB's ``gen_random_text_uuid`` + can also be used with ``primary_key=True`` as an alternative of ``default=gen_key`` .. TIP:: diff --git a/src/crate/client/sqlalchemy/compiler.py b/src/crate/client/sqlalchemy/compiler.py index 3ae7a7cb..feefb772 100644 --- a/src/crate/client/sqlalchemy/compiler.py +++ b/src/crate/client/sqlalchemy/compiler.py @@ -116,6 +116,22 @@ def get_column_specification(self, column, **kwargs): if column.computed is not None: colspec += " " + self.process(column.computed) + if column.dialect_options['crate'].get('autogenerate_uuid'): + if not isinstance(column.type, (sa.String, sa.Text)): + raise sa.exc.CompileError( + "Auto generate uuid is only supported for column" + "types STRING and TEXT" + ) + if default is not None: + raise sa.exc.CompileError( + "Can only have one default value but server_default" + " and crate_generate_uuid are set" + ) + + colspec += " DEFAULT gen_random_text_uuid()" + + # nullable should go after 'DEFAULT' query modifications, otherwise + # invalid statements will be generated, ex: `x STRING NOT NULL DEFAULT 'value'` if column.nullable is False: colspec += " NOT NULL" elif column.nullable and column.primary_key: diff --git a/src/crate/client/sqlalchemy/tests/create_table_test.py b/src/crate/client/sqlalchemy/tests/create_table_test.py index 4c6072aa..4b1e738b 100644 --- a/src/crate/client/sqlalchemy/tests/create_table_test.py +++ b/src/crate/client/sqlalchemy/tests/create_table_test.py @@ -311,3 +311,36 @@ class DummyTable(self.Base): 'pk STRING NOT NULL, \n\t' 'answer INT DEFAULT 42, \n\t' 'PRIMARY KEY (pk)\n)\n\n'), ()) + + def test_column_autogenerate_uuid(self): + class DummyTable(self.Base): + __tablename__ = 't' + pk = sa.Column(sa.String, primary_key=True, crate_autogenerate_uuid=True) + other = sa.Column(sa.Boolean) + + self.Base.metadata.create_all(bind=self.engine) + fake_cursor.execute.assert_called_with( + '\nCREATE TABLE t (\n\t' + 'pk STRING DEFAULT gen_random_text_uuid() NOT NULL, \n\t' + 'other BOOLEAN, \n\tPRIMARY KEY (pk)\n)\n\n', () + ) + + def test_column_autogenerate_uuid_correct_type(self): + class DummyTable(self.Base): + __tablename__ = 't' + pk = sa.Column(sa.Integer, primary_key=True, crate_autogenerate_uuid=True) + other = sa.Column(sa.Boolean) + + with self.assertRaises(sa.exc.CompileError): + self.Base.metadata.create_all(bind=self.engine) + + def test_column_autogenerate_uuid_clashes_with_server_default(self): + class DummyTable(self.Base): + __tablename__ = 't' + pk = sa.Column(sa.String, primary_key=True, + crate_autogenerate_uuid=True, + server_default='value') + other = sa.Column(sa.Boolean) + + with self.assertRaises(sa.exc.CompileError): + self.Base.metadata.create_all(bind=self.engine)