Skip to content

Commit 012c257

Browse files
hammerheadamotl
andcommitted
Support disabling indexing in SQLAlchemy ORM column definitions
Co-authored-by: Andreas Motl <[email protected]>
1 parent de7d66d commit 012c257

File tree

4 files changed

+52
-16
lines changed

4 files changed

+52
-16
lines changed

CHANGES.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ Changes for crate
55
Unreleased
66
==========
77

8+
- SQLAlchemy: Added support for ``crate_index`` and ``nullable`` attributes in
9+
ORM column definitions.
810

911
2022/12/02 0.28.0
1012
=================

docs/sqlalchemy.rst

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -181,9 +181,9 @@ system`_::
181181
... }
182182
...
183183
... id = sa.Column(sa.String, primary_key=True, default=gen_key)
184-
... name = sa.Column(sa.String)
184+
... name = sa.Column(sa.String, crate_index=False)
185185
... name_normalized = sa.Column(sa.String, sa.Computed("lower(name)"))
186-
... quote = sa.Column(sa.String)
186+
... quote = sa.Column(sa.String, nullable=False)
187187
... details = sa.Column(types.Object)
188188
... more_details = sa.Column(types.ObjectArray)
189189
... name_ft = sa.Column(sa.String)
@@ -201,6 +201,8 @@ In this example, we:
201201
- Use the ``gen_key`` function to provide a default value for the ``id`` column
202202
(which is also the primary key)
203203
- Use standard SQLAlchemy types for the ``id``, ``name``, and ``quote`` columns
204+
- Use ``nullable=False`` to define a ``NOT NULL`` constraint
205+
- Disable indexing of the ``name`` column using ``crate_index=False``
204206
- Define a computed column ``name_normalized`` (based on ``name``) that
205207
translates into a generated column
206208
- Use the `Object`_ extension type for the ``details`` column
@@ -250,7 +252,7 @@ A table schema like this
250252
.. code-block:: sql
251253
252254
CREATE TABLE "doc"."logs" (
253-
"ts" TIMESTAMP WITH TIME ZONE,
255+
"ts" TIMESTAMP WITH TIME ZONE NOT NULL,
254256
"level" TEXT,
255257
"message" TEXT
256258
)

src/crate/client/sqlalchemy/compiler.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
import sqlalchemy as sa
2626
from sqlalchemy.dialects.postgresql.base import PGCompiler
2727
from sqlalchemy.sql import compiler, crud, selectable
28-
from .types import MutableDict
28+
from .types import MutableDict, _Craty, Geopoint, Geoshape
2929
from .sa_version import SA_VERSION, SA_1_4
3030

3131

@@ -119,6 +119,15 @@ def get_column_specification(self, column, **kwargs):
119119
"Primary key columns cannot be nullable"
120120
)
121121

122+
if column.dialect_options['crate'].get('index') is False:
123+
if isinstance(column.type, (Geopoint, Geoshape, _Craty)):
124+
raise sa.exc.CompileError(
125+
"Disabling indexing is not supported for column "
126+
"types OBJECT, GEO_POINT, and GEO_SHAPE"
127+
)
128+
129+
colspec += " INDEX OFF"
130+
122131
return colspec
123132

124133
def visit_computed_column(self, generated):

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

Lines changed: 35 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
import sqlalchemy as sa
2323
from sqlalchemy.ext.declarative import declarative_base
2424

25-
from crate.client.sqlalchemy.types import Object, ObjectArray
25+
from crate.client.sqlalchemy.types import Object, ObjectArray, Geopoint
2626
from crate.client.cursor import Cursor
2727

2828
from unittest import TestCase
@@ -41,7 +41,7 @@ def setUp(self):
4141
self.engine = sa.create_engine('crate://')
4242
self.Base = declarative_base(bind=self.engine)
4343

44-
def test_create_table_with_basic_types(self):
44+
def test_table_basic_types(self):
4545
class User(self.Base):
4646
__tablename__ = 'users'
4747
string_col = sa.Column(sa.String, primary_key=True)
@@ -69,7 +69,7 @@ class User(self.Base):
6969
'\n\tPRIMARY KEY (string_col)\n)\n\n'),
7070
())
7171

72-
def test_with_obj_column(self):
72+
def test_column_obj(self):
7373
class DummyTable(self.Base):
7474
__tablename__ = 'dummy'
7575
pk = sa.Column(sa.String, primary_key=True)
@@ -80,7 +80,7 @@ class DummyTable(self.Base):
8080
'\n\tPRIMARY KEY (pk)\n)\n\n'),
8181
())
8282

83-
def test_with_clustered_by(self):
83+
def test_table_clustered_by(self):
8484
class DummyTable(self.Base):
8585
__tablename__ = 't'
8686
__table_args__ = {
@@ -97,7 +97,7 @@ class DummyTable(self.Base):
9797
') CLUSTERED BY (p)\n\n'),
9898
())
9999

100-
def test_with_computed_column(self):
100+
def test_column_computed(self):
101101
class DummyTable(self.Base):
102102
__tablename__ = 't'
103103
ts = sa.Column(sa.BigInteger, primary_key=True)
@@ -111,15 +111,15 @@ class DummyTable(self.Base):
111111
')\n\n'),
112112
())
113113

114-
def test_with_virtual_computed_column(self):
114+
def test_column_computed_virtual(self):
115115
class DummyTable(self.Base):
116116
__tablename__ = 't'
117117
ts = sa.Column(sa.BigInteger, primary_key=True)
118118
p = sa.Column(sa.BigInteger, sa.Computed("date_trunc('day', ts)", persisted=False))
119119
with self.assertRaises(sa.exc.CompileError):
120120
self.Base.metadata.create_all()
121121

122-
def test_with_partitioned_by(self):
122+
def test_table_partitioned_by(self):
123123
class DummyTable(self.Base):
124124
__tablename__ = 't'
125125
__table_args__ = {
@@ -137,7 +137,7 @@ class DummyTable(self.Base):
137137
') PARTITIONED BY (p)\n\n'),
138138
())
139139

140-
def test_with_number_of_shards_and_replicas(self):
140+
def test_table_number_of_shards_and_replicas(self):
141141
class DummyTable(self.Base):
142142
__tablename__ = 't'
143143
__table_args__ = {
@@ -154,7 +154,7 @@ class DummyTable(self.Base):
154154
') CLUSTERED INTO 3 SHARDS WITH (NUMBER_OF_REPLICAS = 2)\n\n'),
155155
())
156156

157-
def test_with_clustered_by_and_number_of_shards(self):
157+
def test_table_clustered_by_and_number_of_shards(self):
158158
class DummyTable(self.Base):
159159
__tablename__ = 't'
160160
__table_args__ = {
@@ -172,7 +172,7 @@ class DummyTable(self.Base):
172172
') CLUSTERED BY (p) INTO 3 SHARDS\n\n'),
173173
())
174174

175-
def test_table_with_object_array(self):
175+
def test_column_object_array(self):
176176
class DummyTable(self.Base):
177177
__tablename__ = 't'
178178
pk = sa.Column(sa.String, primary_key=True)
@@ -185,7 +185,7 @@ class DummyTable(self.Base):
185185
'tags ARRAY(OBJECT), \n\t'
186186
'PRIMARY KEY (pk)\n)\n\n'), ())
187187

188-
def test_table_with_nullable(self):
188+
def test_column_nullable(self):
189189
class DummyTable(self.Base):
190190
__tablename__ = 't'
191191
pk = sa.Column(sa.String, primary_key=True)
@@ -200,9 +200,32 @@ class DummyTable(self.Base):
200200
'b INT NOT NULL, \n\t'
201201
'PRIMARY KEY (pk)\n)\n\n'), ())
202202

203-
def test_with_pk_nullable(self):
203+
def test_column_pk_nullable(self):
204204
class DummyTable(self.Base):
205205
__tablename__ = 't'
206206
pk = sa.Column(sa.String, primary_key=True, nullable=True)
207207
with self.assertRaises(sa.exc.CompileError):
208208
self.Base.metadata.create_all()
209+
210+
def test_column_crate_index(self):
211+
class DummyTable(self.Base):
212+
__tablename__ = 't'
213+
pk = sa.Column(sa.String, primary_key=True)
214+
a = sa.Column(sa.Integer, crate_index=False)
215+
b = sa.Column(sa.Integer, crate_index=True)
216+
217+
self.Base.metadata.create_all()
218+
fake_cursor.execute.assert_called_with(
219+
('\nCREATE TABLE t (\n\t'
220+
'pk STRING NOT NULL, \n\t'
221+
'a INT INDEX OFF, \n\t'
222+
'b INT, \n\t'
223+
'PRIMARY KEY (pk)\n)\n\n'), ())
224+
225+
def test_column_geopoint_without_index(self):
226+
class DummyTable(self.Base):
227+
__tablename__ = 't'
228+
pk = sa.Column(sa.String, primary_key=True)
229+
a = sa.Column(Geopoint, crate_index=False)
230+
with self.assertRaises(sa.exc.CompileError):
231+
self.Base.metadata.create_all()

0 commit comments

Comments
 (0)