Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 39 additions & 7 deletions cassandra/cqlengine/columns.py
Original file line number Diff line number Diff line change
Expand Up @@ -837,7 +837,30 @@ def to_database(self, value):


class BaseContainerColumn(BaseCollectionColumn):
pass
"""
Base class for container columns (Set, List, Map).

Supports optional freezing for immutable collections.
"""

frozen = False
"""
bool flag, indicates this collection should be frozen (immutable).
Frozen collections use FULL indexes instead of VALUES indexes.
"""

def __init__(self, types, frozen=False, **kwargs):
"""
:param types: a sequence of sub types in this collection
:param frozen: if True, the collection will be frozen (immutable)
"""
self.frozen = frozen
super(BaseContainerColumn, self).__init__(types, **kwargs)

def _apply_frozen(self):
"""Apply frozen wrapper to db_type if frozen=True."""
if self.frozen:
self._freeze_db_type()


class Set(BaseContainerColumn):
Expand All @@ -849,18 +872,21 @@ class Set(BaseContainerColumn):

_python_type_hashable = False

def __init__(self, value_type, strict=True, default=set, **kwargs):
def __init__(self, value_type, strict=True, default=set, frozen=False, **kwargs):
"""
:param value_type: a column class indicating the types of the value
:param strict: sets whether non set values will be coerced to set
type on validation, or raise a validation error, defaults to True
:param frozen: if True, the collection will be frozen (immutable) and
use FULL indexes instead of VALUES indexes
"""
self.strict = strict
super(Set, self).__init__((value_type,), default=default, **kwargs)
super(Set, self).__init__((value_type,), frozen=frozen, default=default, **kwargs)
self.value_col = self.types[0]
if not self.value_col._python_type_hashable:
raise ValidationError("Cannot create a Set with unhashable value type (see PYTHON-494)")
self.db_type = 'set<{0}>'.format(self.value_col.db_type)
self._apply_frozen()

def validate(self, value):
val = super(Set, self).validate(value)
Expand Down Expand Up @@ -899,13 +925,16 @@ class List(BaseContainerColumn):

_python_type_hashable = False

def __init__(self, value_type, default=list, **kwargs):
def __init__(self, value_type, default=list, frozen=False, **kwargs):
"""
:param value_type: a column class indicating the types of the value
:param frozen: if True, the collection will be frozen (immutable) and
use FULL indexes instead of VALUES indexes
"""
super(List, self).__init__((value_type,), default=default, **kwargs)
super(List, self).__init__((value_type,), frozen=frozen, default=default, **kwargs)
self.value_col = self.types[0]
self.db_type = 'list<{0}>'.format(self.value_col.db_type)
self._apply_frozen()

def validate(self, value):
val = super(List, self).validate(value)
Expand Down Expand Up @@ -937,19 +966,22 @@ class Map(BaseContainerColumn):

_python_type_hashable = False

def __init__(self, key_type, value_type, default=dict, **kwargs):
def __init__(self, key_type, value_type, default=dict, frozen=False, **kwargs):
"""
:param key_type: a column class indicating the types of the key
:param value_type: a column class indicating the types of the value
:param frozen: if True, the collection will be frozen (immutable) and
use FULL indexes instead of VALUES indexes
"""
super(Map, self).__init__((key_type, value_type), default=default, **kwargs)
super(Map, self).__init__((key_type, value_type), frozen=frozen, default=default, **kwargs)
self.key_col = self.types[0]
self.value_col = self.types[1]

if not self.key_col._python_type_hashable:
raise ValidationError("Cannot create a Map with unhashable key type (see PYTHON-494)")

self.db_type = 'map<{0}, {1}>'.format(self.key_col.db_type, self.value_col.db_type)
self._apply_frozen()

def validate(self, value):
val = super(Map, self).validate(value)
Expand Down
6 changes: 5 additions & 1 deletion cassandra/cqlengine/management.py
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,11 @@ def _sync_table(model, connection=None):

qs = ['CREATE INDEX']
qs += ['ON {0}'.format(cf_name)]
qs += ['("{0}")'.format(column.db_field_name)]
# Use FULL index for frozen collections, VALUES index (implicit) for non-frozen
if isinstance(column, columns.BaseContainerColumn) and column.frozen:
qs += ['(FULL("{0}"))'.format(column.db_field_name)]
else:
qs += ['("{0}")'.format(column.db_field_name)]
qs = ' '.join(qs)
execute(qs, connection=connection)

Expand Down
43 changes: 42 additions & 1 deletion tests/unit/cqlengine/test_columns.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

import unittest

from cassandra.cqlengine.columns import Column
from cassandra.cqlengine.columns import Column, List, Set, Map, Text, Integer


class ColumnTest(unittest.TestCase):
Expand Down Expand Up @@ -66,3 +66,44 @@ def test_hash(self):
c0 = Column()
assert id(c0) == c0.__hash__()


class FrozenCollectionTest(unittest.TestCase):
"""Test frozen parameter for collection columns (List, Set, Map)."""

def test_list_default_not_frozen(self):
col = List(Text)
assert col.frozen is False
assert col.db_type == 'list<text>'

def test_list_frozen_true(self):
col = List(Text, frozen=True)
assert col.frozen is True
assert col.db_type == 'frozen<list<text>>'

def test_set_default_not_frozen(self):
col = Set(Text)
assert col.frozen is False
assert col.db_type == 'set<text>'

def test_set_frozen_true(self):
col = Set(Text, frozen=True)
assert col.frozen is True
assert col.db_type == 'frozen<set<text>>'

def test_map_default_not_frozen(self):
col = Map(Text, Integer)
assert col.frozen is False
assert col.db_type == 'map<text, int>'

def test_map_frozen_true(self):
col = Map(Text, Integer, frozen=True)
assert col.frozen is True
assert col.db_type == 'frozen<map<text, int>>'

def test_frozen_with_index(self):
"""Test that frozen collections can be created with index=True."""
col = List(Text, frozen=True, index=True)
assert col.frozen is True
assert col.index is True
assert col.db_type == 'frozen<list<text>>'

Loading