Skip to content

Commit 20b30a1

Browse files
committed
PYTHON-337: case sensitive support for column family name
1 parent e9c55a1 commit 20b30a1

6 files changed

Lines changed: 105 additions & 3 deletions

File tree

cassandra/cqlengine/management.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ def sync_table(model):
194194
if table.indexes.get(index_name):
195195
continue
196196

197-
qs = ['CREATE INDEX {0}'.format(index_name)]
197+
qs = ['CREATE INDEX {0}'.format(metadata.protect_name(index_name))]
198198
qs += ['ON {0}'.format(cf_name)]
199199
qs += ['("{0}")'.format(column.db_field_name)]
200200
qs = ' '.join(qs)

cassandra/cqlengine/models.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
import logging
1616
import re
1717
import six
18-
import warnings
18+
from warnings import warn
1919

2020
from cassandra.cqlengine import CQLEngineException, ValidationError
2121
from cassandra.cqlengine import columns
@@ -308,6 +308,8 @@ class MultipleObjectsReturned(_MultipleObjectsReturned):
308308

309309
__table_name__ = None
310310

311+
__table_name_case_sensitive__ = False
312+
311313
__keyspace__ = None
312314

313315
__discriminator_value__ = None
@@ -492,7 +494,14 @@ def column_family_name(cls, include_keyspace=True):
492494
def _raw_column_family_name(cls):
493495
if not cls._table_name:
494496
if cls.__table_name__:
495-
cls._table_name = cls.__table_name__.lower()
497+
if cls.__table_name_case_sensitive__:
498+
cls._table_name = cls.__table_name__
499+
else:
500+
table_name = cls.__table_name__.lower()
501+
if cls.__table_name__ != table_name:
502+
warn(("Model __table_name__ will be case sensitive by default in 4.0. "
503+
"You should fix the __table_name__ value of the '{0}' model.").format(cls.__name__))
504+
cls._table_name = table_name
496505
else:
497506
if cls._is_polymorphic and not cls._is_polymorphic_base:
498507
cls._table_name = cls._polymorphic_base._raw_column_family_name()
@@ -924,6 +933,11 @@ class Model(BaseModel):
924933
*Optional.* Sets the name of the CQL table for this model. If left blank, the table name will be the name of the model, with it's module name as it's prefix. Manually defined table names are not inherited.
925934
"""
926935

936+
__table_name_case_sensitive__ = False
937+
"""
938+
*Optional.* By default, __table_name__ is case insensitive. Set this to True if you want to preserve the case sensitivity.
939+
"""
940+
927941
__keyspace__ = None
928942
"""
929943
Sets the name of the keyspace used by this model.

docs/api/cassandra/cqlengine/models.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ Model
2828

2929
.. autoattribute:: __table_name__
3030

31+
.. autoattribute:: __table_name_case_sensitive__
32+
3133
.. autoattribute:: __keyspace__
3234

3335
.. _ttl-change:

tests/integration/cqlengine/management/test_management.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,49 @@ def test_sync_table_works_with_primary_keys_only_tables(self):
235235
self.assertIn('SizeTieredCompactionStrategy', table_meta.as_cql_query())
236236

237237

238+
class IndexModel(Model):
239+
240+
__table_name__ = 'index_model'
241+
first_key = columns.UUID(primary_key=True)
242+
second_key = columns.Text(index=True)
243+
244+
245+
class IndexCaseSensitiveModel(Model):
246+
247+
__table_name__ = 'IndexModel'
248+
__table_name_case_sensitive__ = True
249+
first_key = columns.UUID(primary_key=True)
250+
second_key = columns.Text(index=True)
251+
252+
253+
class IndexTests(BaseCassEngTestCase):
254+
255+
def setUp(self):
256+
drop_table(IndexModel)
257+
258+
def test_sync_index(self):
259+
260+
sync_table(IndexModel)
261+
table_meta = management._get_table_metadata(IndexModel)
262+
self.assertIn("index_index_model_second_key", table_meta.indexes)
263+
264+
# index already exists
265+
sync_table(IndexModel)
266+
table_meta = management._get_table_metadata(IndexModel)
267+
self.assertIn("index_index_model_second_key", table_meta.indexes)
268+
269+
def test_sync_index_case_sensitive(self):
270+
271+
sync_table(IndexCaseSensitiveModel)
272+
table_meta = management._get_table_metadata(IndexCaseSensitiveModel)
273+
self.assertIn("index_IndexModel_second_key", table_meta.indexes)
274+
275+
# index already exists
276+
sync_table(IndexCaseSensitiveModel)
277+
table_meta = management._get_table_metadata(IndexCaseSensitiveModel)
278+
self.assertIn("index_IndexModel_second_key", table_meta.indexes)
279+
280+
238281
class NonModelFailureTest(BaseCassEngTestCase):
239282
class FakeModel(object):
240283
pass

tests/integration/cqlengine/model/test_class_construction.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,7 @@ class NoKeyspace(Model):
217217

218218
self.assertEqual(len(warn), 0)
219219

220+
220221
class TestManualTableNaming(BaseCassEngTestCase):
221222

222223
class RenamedTest(Model):
@@ -230,6 +231,31 @@ def test_proper_table_naming(self):
230231
assert self.RenamedTest.column_family_name(include_keyspace=False) == 'manual_name'
231232
assert self.RenamedTest.column_family_name(include_keyspace=True) == 'whatever.manual_name'
232233

234+
235+
class TestManualTableNamingCaseSensitive(BaseCassEngTestCase):
236+
237+
class RenamedCaseInsensitiveTest(Model):
238+
__keyspace__ = 'whatever'
239+
__table_name__ = 'Manual_Name'
240+
241+
id = columns.UUID(primary_key=True)
242+
243+
class RenamedCaseSensitiveTest(Model):
244+
__keyspace__ = 'whatever'
245+
__table_name__ = 'Manual_Name'
246+
__table_name_case_sensitive__ = True
247+
248+
id = columns.UUID(primary_key=True)
249+
250+
def test_proper_table_naming_case_insensitive(self):
251+
self.assertEqual(self.RenamedCaseInsensitiveTest.column_family_name(include_keyspace=False), 'manual_name')
252+
self.assertEqual(self.RenamedCaseInsensitiveTest.column_family_name(include_keyspace=True), 'whatever.manual_name')
253+
254+
def test_proper_table_naming_case_sensitive(self):
255+
self.assertEqual(self.RenamedCaseSensitiveTest.column_family_name(include_keyspace=False), '"Manual_Name"')
256+
self.assertEqual(self.RenamedCaseSensitiveTest.column_family_name(include_keyspace=True), 'whatever."Manual_Name"')
257+
258+
233259
class AbstractModel(Model):
234260
__abstract__ = True
235261

tests/integration/cqlengine/model/test_model.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,23 @@ class TestModel(Model):
126126
# .. but we can still get the bare CF name
127127
self.assertEqual(TestModel.column_family_name(include_keyspace=False), "test_model")
128128

129+
def test_column_family_case_sensitive(self):
130+
class TestModel(Model):
131+
__table_name__ = 'TestModel'
132+
__table_name_case_sensitive__ = True
133+
134+
k = columns.Integer(primary_key=True)
135+
136+
self.assertEqual(TestModel.column_family_name(), '%s."TestModel"' % (models.DEFAULT_KEYSPACE,))
137+
138+
TestModel.__keyspace__ = "my_test_keyspace"
139+
self.assertEqual(TestModel.column_family_name(), '%s."TestModel"' % (TestModel.__keyspace__,))
140+
141+
del TestModel.__keyspace__
142+
with patch('cassandra.cqlengine.models.DEFAULT_KEYSPACE', None):
143+
self.assertRaises(CQLEngineException, TestModel.column_family_name)
144+
self.assertEqual(TestModel.column_family_name(include_keyspace=False), '"TestModel"')
145+
129146

130147
class BuiltInAttributeConflictTest(unittest.TestCase):
131148
"""tests Model definitions that conflict with built-in attributes/methods"""

0 commit comments

Comments
 (0)