Skip to content

Commit

Permalink
SQLAlchemy: Add support for 'autogenerate_uuid' in column creation
Browse files Browse the repository at this point in the history
  • Loading branch information
surister committed Sep 17, 2023
1 parent c276159 commit 8790924
Show file tree
Hide file tree
Showing 4 changed files with 53 additions and 0 deletions.
1 change: 1 addition & 0 deletions CHANGES.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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`_
Expand Down
3 changes: 3 additions & 0 deletions docs/sqlalchemy.rst
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,7 @@ system <sa:orm_declarative_mapping>`:
... 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']
Expand All @@ -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::

Expand Down
16 changes: 16 additions & 0 deletions src/crate/client/sqlalchemy/compiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
33 changes: 33 additions & 0 deletions src/crate/client/sqlalchemy/tests/create_table_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

0 comments on commit 8790924

Please sign in to comment.