diff --git a/build.yaml b/build.yaml index 6a395828..b31eac83 100644 --- a/build.yaml +++ b/build.yaml @@ -25,7 +25,7 @@ build: sudo apt-get install -y libev4 libev-dev fi pip install -r test-requirements.txt - + pip install nose-ignore-docstring if [[ $CYTHON == 'CYTHON' ]]; then pip install cython @@ -38,12 +38,12 @@ build: fi echo "==========RUNNING CQLENGINE TESTS==========" - CASSANDRA_VERSION=$CCM_CASSANDRA_VERSION nosetests -s -v --with-xunit --xunit-file=cqle_results.xml tests/integration/cqlengine/ || true + CASSANDRA_VERSION=$CCM_CASSANDRA_VERSION nosetests -s -v --logging-format="[%(levelname)s] %(asctime)s %(thread)d: %(message)s" --with-ignore-docstrings --with-xunit --xunit-file=cqle_results.xml tests/integration/cqlengine/ || true echo "==========RUNNING INTEGRATION TESTS==========" - CASSANDRA_VERSION=$CCM_CASSANDRA_VERSION nosetests -s -v --with-xunit --xunit-file=standard_results.xml tests/integration/standard/ || true + CASSANDRA_VERSION=$CCM_CASSANDRA_VERSION nosetests -s -v --logging-format="[%(levelname)s] %(asctime)s %(thread)d: %(message)s" --with-ignore-docstrings --with-xunit --xunit-file=standard_results.xml tests/integration/standard/ || true echo "==========RUNNING LONG INTEGRATION TESTS==========" - CASSANDRA_VERSION=$CCM_CASSANDRA_VERSION nosetests -s -v --with-xunit --xunit-file=long_results.xml tests/integration/long/ || true + CASSANDRA_VERSION=$CCM_CASSANDRA_VERSION nosetests -s -v --logging-format="[%(levelname)s] %(asctime)s %(thread)d: %(message)s" --with-ignore-docstrings --with-xunit --xunit-file=long_results.xml tests/integration/long/ || true - xunit: - "*_results.xml" \ No newline at end of file diff --git a/tests/integration/__init__.py b/tests/integration/__init__.py index b29aa00f..4509cba1 100644 --- a/tests/integration/__init__.py +++ b/tests/integration/__init__.py @@ -271,6 +271,7 @@ def execute_until_pass(session, query): raise RuntimeError("Failed to execute query after 100 attempts: {0}".format(query)) + def setup_keyspace(ipformat=None): # wait for nodes to startup time.sleep(10) @@ -332,3 +333,149 @@ class UpDownWaiter(object): def wait_for_up(self): self.up_event.wait() + + +class BasicKeyspaceUnitTestCase(unittest.TestCase): + """ + This is basic unit test case that provides various utility methods that can be leveraged for testcase setup and tear + down + """ + @property + def keyspace_name(self): + return self.ks_name + + @property + def class_table_name(self): + return self.ks_name + + @property + def function_table_name(self): + return self._testMethodName.lower() + + @classmethod + def drop_keyspace(cls): + execute_until_pass(cls.session, "DROP KEYSPACE {0}".format(cls.ks_name)) + + @classmethod + def create_keyspace(cls, rf): + ddl = "CREATE KEYSPACE {0} WITH replication = {{'class': 'SimpleStrategy', 'replication_factor': '{1}'}}".format(cls.ks_name, rf) + execute_until_pass(cls.session, ddl) + + @classmethod + def common_setup(cls, rf, create_class_table=False, skip_if_cass_version_less_than=None): + cls.cluster = Cluster(protocol_version=PROTOCOL_VERSION) + cls.session = cls.cluster.connect() + cls.ks_name = cls.__name__.lower() + cls.create_keyspace(rf) + + if create_class_table: + + ddl = ''' + CREATE TABLE {0}.{1} ( + k int PRIMARY KEY, + v int )'''.format(cls.ks_name, cls.ks_name) + execute_until_pass(cls.session, ddl) + + def create_function_table(self): + ddl = ''' + CREATE TABLE {0}.{1} ( + k int PRIMARY KEY, + v int )'''.format(self.keyspace_name, self.function_table_name) + execute_until_pass(self.session, ddl) + + def drop_function_table(self): + ddl = "DROP TABLE {0}.{1} ".format(self.keyspace_name, self.function_table_name) + execute_until_pass(self.session, ddl) + + +class BasicSharedKeyspaceUnitTestCase(BasicKeyspaceUnitTestCase): + """ + This is basic unit test case that can be leveraged to scope a keyspace to a specific test class. + creates a keyspace named after the testclass with a rf of 1. + """ + @classmethod + def setUpClass(cls): + cls.common_setup(1) + + @classmethod + def tearDownClass(cls): + cls.drop_keyspace() + cls.cluster.shutdown() + + +class BasicSharedKeyspaceUnitTestCaseWTable(BasicSharedKeyspaceUnitTestCase): + """ + This is basic unit test case that can be leveraged to scope a keyspace to a specific test class. + creates a keyspace named after the testclass with a rf of 1, and a table named after the class + """ + @classmethod + def setUpClass(self): + self.common_setup(1, True) + + +class BasicSharedKeyspaceUnitTestCaseRF2(BasicSharedKeyspaceUnitTestCase): + """ + This is basic unit test case that can be leveraged to scope a keyspace to a specific test class. + creates a keyspace named after the test class with a rf of 2, and a table named after the class + """ + @classmethod + def setUpClass(self): + self.common_setup(2) + + +class BasicSharedKeyspaceUnitTestCaseWTable(BasicSharedKeyspaceUnitTestCase): + """ + This is basic unit test case that can be leveraged to scope a keyspace to a specific test class. + creates a keyspace named after the testc lass with a rf of 2, and a table named after the class + """ + @classmethod + def setUpClass(self): + self.common_setup(2, True) + + +class BasicSharedKeyspaceUnitTestCaseRF3(BasicSharedKeyspaceUnitTestCase): + """ + This is basic unit test case that can be leveraged to scope a keyspace to a specific test class. + creates a keyspace named after the test class with a rf of 3 + """ + @classmethod + def setUpClass(self): + self.common_setup(3) + + +class BasicSharedKeyspaceUnitTestCaseRF3WTable(BasicSharedKeyspaceUnitTestCase): + """ + This is basic unit test case that can be leveraged to scope a keyspace to a specific test class. + creates a keyspace named after the test class with a rf of 3 and a table named after the class + """ + @classmethod + def setUpClass(self): + self.common_setup(3, True) + + +class BasicSharedKeyspaceUnitTestCaseWFunctionTable(BasicSharedKeyspaceUnitTestCase): + """" + This is basic unit test case that can be leveraged to scope a keyspace to a specific test class. + creates a keyspace named after the test class with a rf of 3 and a table named after the class + the table is scoped to just the unit test and will be removed. + + """ + def setUp(self): + self.create_function_table() + + def tearDown(self): + self.drop_function_table() + + +class BasicSegregatedKeyspaceUnitTestCase(BasicKeyspaceUnitTestCase): + """ + This unit test will create and teardown a keyspace for each individual unit tests. + It has overhead and should only be used with complex unit test were sharing a keyspace will + cause issues. + """ + def setUp(self): + self.common_setup(1) + + def tearDown(self): + self.drop_keyspace() + self.cluster.shutdown() diff --git a/tests/integration/cqlengine/test_ttl.py b/tests/integration/cqlengine/test_ttl.py index e86b2cbf..811fdbad 100644 --- a/tests/integration/cqlengine/test_ttl.py +++ b/tests/integration/cqlengine/test_ttl.py @@ -12,6 +12,12 @@ # See the License for the specific language governing permissions and # limitations under the License. + +try: + import unittest2 as unittest +except ImportError: + import unittest # noqa + from cassandra.cqlengine.management import sync_table, drop_table from tests.integration.cqlengine.base import BaseCassEngTestCase from cassandra.cqlengine.models import Model @@ -19,12 +25,13 @@ from uuid import uuid4 from cassandra.cqlengine import columns import mock from cassandra.cqlengine.connection import get_session +from tests.integration import CASSANDRA_VERSION class TestTTLModel(Model): - id = columns.UUID(primary_key=True, default=lambda:uuid4()) - count = columns.Integer() - text = columns.Text(required=False) + id = columns.UUID(primary_key=True, default=lambda: uuid4()) + count = columns.Integer() + text = columns.Text(required=False) class BaseTTLTest(BaseCassEngTestCase): @@ -42,24 +49,26 @@ class BaseTTLTest(BaseCassEngTestCase): class TestDefaultTTLModel(Model): __options__ = {'default_time_to_live': 20} - id = columns.UUID(primary_key=True, default=lambda:uuid4()) - count = columns.Integer() - text = columns.Text(required=False) + id = columns.UUID(primary_key=True, default=lambda:uuid4()) + count = columns.Integer() + text = columns.Text(required=False) class BaseDefaultTTLTest(BaseCassEngTestCase): @classmethod def setUpClass(cls): - super(BaseDefaultTTLTest, cls).setUpClass() - sync_table(TestDefaultTTLModel) - sync_table(TestTTLModel) + if CASSANDRA_VERSION >= '2.0': + super(BaseDefaultTTLTest, cls).setUpClass() + sync_table(TestDefaultTTLModel) + sync_table(TestTTLModel) @classmethod def tearDownClass(cls): - super(BaseDefaultTTLTest, cls).tearDownClass() - drop_table(TestDefaultTTLModel) - drop_table(TestTTLModel) + if CASSANDRA_VERSION >= '2.0': + super(BaseDefaultTTLTest, cls).tearDownClass() + drop_table(TestDefaultTTLModel) + drop_table(TestTTLModel) class TTLQueryTests(BaseTTLTest): @@ -91,7 +100,6 @@ class TTLModelTests(BaseTTLTest): self.assertTrue(isinstance(qs, TestTTLModel.__queryset__), type(qs)) - class TTLInstanceUpdateTest(BaseTTLTest): def test_update_includes_ttl(self): session = get_session() @@ -109,9 +117,6 @@ class TTLInstanceUpdateTest(BaseTTLTest): model.ttl(60).update(text="goodbye forever") - - - class TTLInstanceTest(BaseTTLTest): def test_instance_is_returned(self): """ @@ -151,6 +156,7 @@ class TTLBlindUpdateTest(BaseTTLTest): self.assertIn("USING TTL", query) +@unittest.skipIf(CASSANDRA_VERSION < '2.0', "default_time_to_Live was introduce in C* 2.0, currently running {0}".format(CASSANDRA_VERSION)) class TTLDefaultTest(BaseDefaultTTLTest): def test_default_ttl_not_set(self): session = get_session() diff --git a/tests/integration/standard/test_cluster.py b/tests/integration/standard/test_cluster.py index 5fe97886..21ceddf4 100644 --- a/tests/integration/standard/test_cluster.py +++ b/tests/integration/standard/test_cluster.py @@ -503,7 +503,7 @@ class ClusterTests(unittest.TestCase): cluster.shutdown() def test_idle_heartbeat(self): - interval = 1 + interval = 2 cluster = Cluster(protocol_version=PROTOCOL_VERSION, idle_heartbeat_interval=interval) if PROTOCOL_VERSION < 3: cluster.set_core_connections_per_host(HostDistance.LOCAL, 1) @@ -520,7 +520,7 @@ class ClusterTests(unittest.TestCase): connection_request_ids[id(c)] = deque(c.request_ids) # copy of request ids # let two heatbeat intervals pass (first one had startup messages in it) - time.sleep(2 * interval + interval/10.) + time.sleep(2 * interval + interval/2) connections = [c for holders in cluster.get_connection_holders() for c in holders.get_connections()] diff --git a/tests/integration/standard/test_metadata.py b/tests/integration/standard/test_metadata.py index 18298952..5ec48452 100644 --- a/tests/integration/standard/test_metadata.py +++ b/tests/integration/standard/test_metadata.py @@ -32,8 +32,10 @@ from cassandra.metadata import (Metadata, KeyspaceMetadata, IndexMetadata, Index get_schema_parser) from cassandra.policies import SimpleConvictionPolicy from cassandra.pool import Host +from cassandra.query import SimpleStatement, ConsistencyLevel -from tests.integration import get_cluster, use_singledc, PROTOCOL_VERSION, get_server_versions, execute_until_pass +from tests.integration import get_cluster, use_singledc, PROTOCOL_VERSION, get_server_versions, execute_until_pass, \ + BasicSharedKeyspaceUnitTestCase, BasicSegregatedKeyspaceUnitTestCase def setup_module(): @@ -307,8 +309,8 @@ class SchemaMetadataTests(unittest.TestCase): statements = [s.strip() for s in statements.split(';')] statements = list(filter(bool, statements)) self.assertEqual(3, len(statements)) - self.assertEqual(d_index, statements[1]) - self.assertEqual(e_index, statements[2]) + self.assertIn(d_index, statements) + self.assertIn(e_index, statements) # make sure indexes are included in KeyspaceMetadata.export_as_string() ksmeta = self.cluster.metadata.keyspaces[self.ksname] @@ -1901,23 +1903,17 @@ class BadMetaTest(unittest.TestCase): self.assertIn("/*\nWarning:", m.export_as_string()) -class MaterializedViewMetadataTest(unittest.TestCase): - - ksname = "materialized_view_test" +class MaterializedViewMetadataTestSimple(BasicSharedKeyspaceUnitTestCase): def setUp(self): if CASS_SERVER_VERSION < (3, 0): raise unittest.SkipTest("Materialized views require Cassandra 3.0+") - - self.cluster = Cluster(protocol_version=PROTOCOL_VERSION) - self.session = self.cluster.connect() - execute_until_pass(self.session, - "CREATE KEYSPACE {0} WITH replication = {{'class': 'SimpleStrategy', 'replication_factor': '1'}}" - .format(self.ksname)) + self.session.execute("CREATE TABLE {0}.{1} (pk int PRIMARY KEY, c int)".format(self.keyspace_name, self.function_table_name)) + self.session.execute("CREATE MATERIALIZED VIEW {0}.mv1 AS SELECT c FROM {0}.{1} WHERE c IS NOT NULL PRIMARY KEY (pk, c)".format(self.keyspace_name, self.function_table_name)) def tearDown(self): - execute_until_pass(self.session, "DROP KEYSPACE {0}".format(self.ksname)) - self.cluster.shutdown() + self.session.execute("DROP MATERIALIZED VIEW {0}.mv1".format(self.keyspace_name)) + self.session.execute("DROP TABLE {0}.{1}".format(self.keyspace_name, self.function_table_name)) def test_materialized_view_metadata_creation(self): """ @@ -1935,14 +1931,11 @@ class MaterializedViewMetadataTest(unittest.TestCase): @test_category metadata """ - self.session.execute("CREATE TABLE {0}.table1 (pk int PRIMARY KEY, c int)".format(self.ksname)) - self.session.execute("CREATE MATERIALIZED VIEW {0}.mv1 AS SELECT c FROM {0}.table1 WHERE c IS NOT NULL PRIMARY KEY (pk, c)".format(self.ksname)) + self.assertIn("mv1", self.cluster.metadata.keyspaces[self.keyspace_name].views) + self.assertIn("mv1", self.cluster.metadata.keyspaces[self.keyspace_name].tables[self.function_table_name].views) - self.assertIn("mv1", self.cluster.metadata.keyspaces[self.ksname].views) - self.assertIn("mv1", self.cluster.metadata.keyspaces[self.ksname].tables["table1"].views) - - self.assertEqual(self.ksname, self.cluster.metadata.keyspaces[self.ksname].tables["table1"].views["mv1"].keyspace_name) - self.assertEqual("table1", self.cluster.metadata.keyspaces[self.ksname].tables["table1"].views["mv1"].base_table_name) + self.assertEqual(self.keyspace_name, self.cluster.metadata.keyspaces[self.keyspace_name].tables[self.function_table_name].views["mv1"].keyspace_name) + self.assertEqual(self.function_table_name, self.cluster.metadata.keyspaces[self.keyspace_name].tables[self.function_table_name].views["mv1"].base_table_name) def test_materialized_view_metadata_alter(self): """ @@ -1959,15 +1952,10 @@ class MaterializedViewMetadataTest(unittest.TestCase): @test_category metadata """ + self.assertIn("SizeTieredCompactionStrategy", self.cluster.metadata.keyspaces[self.keyspace_name].tables[self.function_table_name].views["mv1"].options["compaction"]["class"] ) - self.session.execute("CREATE TABLE {0}.table1 (pk int PRIMARY KEY, c int)".format(self.ksname)) - self.session.execute("CREATE MATERIALIZED VIEW {0}.mv1 AS SELECT c FROM {0}.table1 WHERE c IS NOT NULL PRIMARY KEY (pk, c)".format(self.ksname)) - - self.assertIn("SizeTieredCompactionStrategy", self.cluster.metadata.keyspaces[self.ksname].tables["table1"].views["mv1"].options["compaction"]["class"]) - - self.session.execute("ALTER MATERIALIZED VIEW {0}.mv1 WITH compaction = {{ 'class' : 'LeveledCompactionStrategy' }}".format(self.ksname)) - self.assertIn("LeveledCompactionStrategy", self.cluster.metadata.keyspaces[self.ksname].tables["table1"].views["mv1"].options["compaction"]["class"]) - + self.session.execute("ALTER MATERIALIZED VIEW {0}.mv1 WITH compaction = {{ 'class' : 'LeveledCompactionStrategy' }}".format(self.keyspace_name)) + self.assertIn("LeveledCompactionStrategy", self.cluster.metadata.keyspaces[self.keyspace_name].tables[self.function_table_name].views["mv1"].options["compaction"]["class"]) def test_materialized_view_metadata_drop(self): """ @@ -1985,14 +1973,21 @@ class MaterializedViewMetadataTest(unittest.TestCase): @test_category metadata """ - self.session.execute("CREATE TABLE {0}.table1 (pk int PRIMARY KEY, c int)".format(self.ksname)) - self.session.execute("CREATE MATERIALIZED VIEW {0}.mv1 AS SELECT c FROM {0}.table1 WHERE c IS NOT NULL PRIMARY KEY (pk, c)".format(self.ksname)) - self.session.execute("DROP MATERIALIZED VIEW {0}.mv1".format(self.ksname)) + self.session.execute("DROP MATERIALIZED VIEW {0}.mv1".format(self.keyspace_name)) - self.assertNotIn("mv1", self.cluster.metadata.keyspaces[self.ksname].tables["table1"].views) - self.assertNotIn("mv1", self.cluster.metadata.keyspaces[self.ksname].views) - self.assertDictEqual({}, self.cluster.metadata.keyspaces[self.ksname].tables["table1"].views) - self.assertDictEqual({}, self.cluster.metadata.keyspaces[self.ksname].views) + self.assertNotIn("mv1", self.cluster.metadata.keyspaces[self.keyspace_name].tables[self.function_table_name].views) + self.assertNotIn("mv1", self.cluster.metadata.keyspaces[self.keyspace_name].views) + self.assertDictEqual({}, self.cluster.metadata.keyspaces[self.keyspace_name].tables[self.function_table_name].views) + self.assertDictEqual({}, self.cluster.metadata.keyspaces[self.keyspace_name].views) + + self.session.execute("CREATE MATERIALIZED VIEW {0}.mv1 AS SELECT c FROM {0}.{1} WHERE c IS NOT NULL PRIMARY KEY (pk, c)".format(self.keyspace_name, self.function_table_name)) + + +class MaterializedViewMetadataTestComplex(BasicSegregatedKeyspaceUnitTestCase): + def setUp(self): + if CASS_SERVER_VERSION < (3, 0): + raise unittest.SkipTest("Materialized views require Cassandra 3.0+") + super(MaterializedViewMetadataTestComplex, self).setUp() def test_create_view_metadata(self): """ @@ -2016,7 +2011,7 @@ class MaterializedViewMetadataTest(unittest.TestCase): day INT, score INT, PRIMARY KEY (user, game, year, month, day) - )""".format(self.ksname) + )""".format(self.keyspace_name) self.session.execute(create_table) @@ -2024,11 +2019,11 @@ class MaterializedViewMetadataTest(unittest.TestCase): SELECT game, year, month, score, user, day FROM {0}.scores WHERE game IS NOT NULL AND year IS NOT NULL AND month IS NOT NULL AND score IS NOT NULL AND user IS NOT NULL AND day IS NOT NULL PRIMARY KEY ((game, year, month), score, user, day) - WITH CLUSTERING ORDER BY (score DESC, user ASC, day ASC)""".format(self.ksname) + WITH CLUSTERING ORDER BY (score DESC, user ASC, day ASC)""".format(self.keyspace_name) self.session.execute(create_mv) - score_table = self.cluster.metadata.keyspaces[self.ksname].tables['scores'] - mv = self.cluster.metadata.keyspaces[self.ksname].views['monthlyhigh'] + score_table = self.cluster.metadata.keyspaces[self.keyspace_name].tables['scores'] + mv = self.cluster.metadata.keyspaces[self.keyspace_name].views['monthlyhigh'] self.assertIsNotNone(score_table.views["monthlyhigh"]) self.assertIsNotNone(len(score_table.views), 1) @@ -2056,7 +2051,7 @@ class MaterializedViewMetadataTest(unittest.TestCase): self.assertIsNotNone(score_table.columns['score']) # Validate basic mv information - self.assertEquals(mv.keyspace_name, self.ksname) + self.assertEquals(mv.keyspace_name, self.keyspace_name) self.assertEquals(mv.name, "monthlyhigh") self.assertEquals(mv.base_table_name, "scores") self.assertFalse(mv.include_all_columns) @@ -2110,7 +2105,7 @@ class MaterializedViewMetadataTest(unittest.TestCase): self.assertEquals(day_column.is_static, mv.clustering_key[2].is_static) self.assertEquals(day_column.is_reversed, mv.clustering_key[2].is_reversed) - def test_create_mv_changes(self): + def test_base_table_mv_changes(self): """ test to ensure that materialized view metadata is properly updated with base table changes @@ -2132,39 +2127,53 @@ class MaterializedViewMetadataTest(unittest.TestCase): day INT, score TEXT, PRIMARY KEY (user, game, year, month, day) - )""".format(self.ksname) + )""".format(self.keyspace_name) self.session.execute(create_table) - view_name = 'monthlyhigh' - create_mv = """CREATE MATERIALIZED VIEW {0}.{1} AS + create_mv = """CREATE MATERIALIZED VIEW {0}.monthlyhigh AS SELECT game, year, month, score, user, day FROM {0}.scores WHERE game IS NOT NULL AND year IS NOT NULL AND month IS NOT NULL AND score IS NOT NULL AND user IS NOT NULL AND day IS NOT NULL PRIMARY KEY ((game, year, month), score, user, day) - WITH CLUSTERING ORDER BY (score DESC, user ASC, day ASC)""".format(self.ksname, view_name) + WITH CLUSTERING ORDER BY (score DESC, user ASC, day ASC)""".format(self.keyspace_name) + + create_mv_alltime = """CREATE MATERIALIZED VIEW {0}.alltimehigh AS + SELECT * FROM {0}.scores + WHERE game IS NOT NULL AND score IS NOT NULL AND user IS NOT NULL AND year IS NOT NULL AND month IS NOT NULL AND day IS NOT NULL + PRIMARY KEY (game, score, user, year, month, day) + WITH CLUSTERING ORDER BY (score DESC)""".format(self.keyspace_name) self.session.execute(create_mv) - score_table = self.cluster.metadata.keyspaces[self.ksname].tables['scores'] - self.assertIn(view_name, score_table.views) - self.assertIn(view_name, self.cluster.metadata.keyspaces[self.ksname].views) + self.session.execute(create_mv_alltime) - insert_fouls = """ALTER TABLE {0}.scores ADD fouls INT""".format(self.ksname) + score_table = self.cluster.metadata.keyspaces[self.keyspace_name].tables['scores'] + + self.assertIsNotNone(score_table.views["monthlyhigh"]) + self.assertEqual(len(self.cluster.metadata.keyspaces[self.keyspace_name].views), 2) + + insert_fouls = """ALTER TABLE {0}.scores ADD fouls INT""".format((self.keyspace_name)) self.session.execute(insert_fouls) - self.assertIn(view_name, self.cluster.metadata.keyspaces[self.ksname].views) + self.assertEqual(len(self.cluster.metadata.keyspaces[self.keyspace_name].views), 2) - alter_scores = """ALTER TABLE {0}.scores ALTER score TYPE blob""".format(self.ksname) + alter_scores = """ALTER TABLE {0}.scores ALTER score blob""".format((self.keyspace_name)) - self.session.execute(alter_scores) # ERROR https://issues.apache.org/jira/browse/CASSANDRA-10424 - self.assertIn(view_name, self.cluster.metadata.keyspaces[self.ksname].views) + self.session.execute(alter_scores) + self.assertEqual(len(self.cluster.metadata.keyspaces[self.keyspace_name].views), 2) - score_column = self.cluster.metadata.keyspaces[self.ksname].tables['scores'].columns['score'] + score_column = self.cluster.metadata.keyspaces[self.keyspace_name].tables['scores'].columns['score'] self.assertEquals(score_column.typestring, 'blob') - score_mv_column = self.cluster.metadata.keyspaces[self.ksname].views["monthlyhigh"].columns['score'] + score_mv_column = self.cluster.metadata.keyspaces[self.keyspace_name].views["monthlyhigh"].columns['score'] self.assertEquals(score_mv_column.typestring, 'blob') + mv_alltime = self.cluster.metadata.keyspaces[self.keyspace_name].views["alltimehigh"] + self.assertIn("fouls", mv_alltime.columns) + + mv_alltime_fouls_comumn = self.cluster.metadata.keyspaces[self.keyspace_name].views["alltimehigh"].columns['fouls'] + self.assertEquals(mv_alltime_fouls_comumn.typestring, 'int') + def test_metadata_with_quoted_identifiers(self): """ test to ensure that materialized view metadata is properly constructed when quoted identifiers are used @@ -2184,7 +2193,7 @@ class MaterializedViewMetadataTest(unittest.TestCase): "theKey" int, "the;Clustering" int, "the Value" int, - PRIMARY KEY ("theKey", "the;Clustering"))""".format(self.ksname) + PRIMARY KEY ("theKey", "the;Clustering"))""".format(self.keyspace_name) self.session.execute(create_table) @@ -2192,12 +2201,12 @@ class MaterializedViewMetadataTest(unittest.TestCase): SELECT "theKey", "the;Clustering", "the Value" FROM {0}.t1 WHERE "theKey" IS NOT NULL AND "the;Clustering" IS NOT NULL AND "the Value" IS NOT NULL - PRIMARY KEY ("theKey", "the;Clustering")""".format(self.ksname) + PRIMARY KEY ("theKey", "the;Clustering")""".format(self.keyspace_name) self.session.execute(create_mv) - t1_table = self.cluster.metadata.keyspaces[self.ksname].tables['t1'] - mv = self.cluster.metadata.keyspaces[self.ksname].views['mv1'] + t1_table = self.cluster.metadata.keyspaces[self.keyspace_name].tables['t1'] + mv = self.cluster.metadata.keyspaces[self.keyspace_name].views['mv1'] self.assertIsNotNone(t1_table.views["mv1"]) self.assertIsNotNone(len(t1_table.views), 1) @@ -2216,7 +2225,7 @@ class MaterializedViewMetadataTest(unittest.TestCase): self.assertIsNotNone(t1_table.columns['the Value']) # Validate basic mv information - self.assertEquals(mv.keyspace_name, self.ksname) + self.assertEquals(mv.keyspace_name, self.keyspace_name) self.assertEquals(mv.name, "mv1") self.assertEquals(mv.base_table_name, "t1") self.assertFalse(mv.include_all_columns) diff --git a/tests/integration/standard/test_query.py b/tests/integration/standard/test_query.py index 92ffbf68..77374d27 100644 --- a/tests/integration/standard/test_query.py +++ b/tests/integration/standard/test_query.py @@ -26,13 +26,15 @@ from cassandra.query import (PreparedStatement, BoundStatement, SimpleStatement, from cassandra.cluster import Cluster from cassandra.policies import HostDistance -from tests.integration import use_singledc, PROTOCOL_VERSION +from tests.integration import use_singledc, PROTOCOL_VERSION, BasicSharedKeyspaceUnitTestCase, get_server_versions import re def setup_module(): use_singledc() + global CASS_SERVER_VERSION + CASS_SERVER_VERSION = get_server_versions()[0] class QueryTests(unittest.TestCase): @@ -617,3 +619,163 @@ class BatchStatementDefaultRoutingKeyTests(unittest.TestCase): self.assertIsNotNone(batch.routing_key) self.assertEqual(batch.routing_key, self.prepared.bind((1, 0)).routing_key) + + +class MaterializedViewQueryTest(BasicSharedKeyspaceUnitTestCase): + + def setUp(self): + if CASS_SERVER_VERSION < (3, 0): + raise unittest.SkipTest("Materialized views require Cassandra 3.0+") + + def test_mv_filtering(self): + """ + Test to ensure that cql filtering where clauses are properly supported in the python driver. + + test_mv_filtering Tests that various complex MV where clauses produce the correct results. It also validates that + these results and the grammar is supported appropriately. + + @since 3.0.0 + @jira_ticket PYTHON-399 + @expected_result Materialized view where clauses should produce the appropriate results. + + @test_category materialized_view + """ + create_table = """CREATE TABLE {0}.scores( + user TEXT, + game TEXT, + year INT, + month INT, + day INT, + score INT, + PRIMARY KEY (user, game, year, month, day) + )""".format(self.ksname) + + self.session.execute(create_table) + + create_mv_alltime = """CREATE MATERIALIZED VIEW {0}.alltimehigh AS + SELECT * FROM {0}.scores + WHERE game IS NOT NULL AND score IS NOT NULL AND user IS NOT NULL AND year IS NOT NULL AND month IS NOT NULL AND day IS NOT NULL + PRIMARY KEY (game, score, user, year, month, day) + WITH CLUSTERING ORDER BY (score DESC)""".format(self.ksname) + + create_mv_dailyhigh = """CREATE MATERIALIZED VIEW {0}.dailyhigh AS + SELECT * FROM {0}.scores + WHERE game IS NOT NULL AND year IS NOT NULL AND month IS NOT NULL AND day IS NOT NULL AND score IS NOT NULL AND user IS NOT NULL + PRIMARY KEY ((game, year, month, day), score, user) + WITH CLUSTERING ORDER BY (score DESC)""".format(self.ksname) + + create_mv_monthlyhigh = """CREATE MATERIALIZED VIEW {0}.monthlyhigh AS + SELECT * FROM {0}.scores + WHERE game IS NOT NULL AND year IS NOT NULL AND month IS NOT NULL AND score IS NOT NULL AND user IS NOT NULL AND day IS NOT NULL + PRIMARY KEY ((game, year, month), score, user, day) + WITH CLUSTERING ORDER BY (score DESC)""".format(self.ksname) + + create_mv_filtereduserhigh = """CREATE MATERIALIZED VIEW {0}.filtereduserhigh AS + SELECT * FROM {0}.scores + WHERE user in ('jbellis', 'pcmanus') AND game IS NOT NULL AND score IS NOT NULL AND year is NOT NULL AND day is not NULL and month IS NOT NULL + PRIMARY KEY (game, score, user, year, month, day) + WITH CLUSTERING ORDER BY (score DESC)""".format(self.ksname) + + self.session.execute(create_mv_alltime) + self.session.execute(create_mv_dailyhigh) + self.session.execute(create_mv_monthlyhigh) + self.session.execute(create_mv_filtereduserhigh) + + prepared_insert = self.session.prepare("""INSERT INTO {0}.scores (user, game, year, month, day, score) VALUES (?, ?, ? ,? ,?, ?)""".format(self.ksname)) + + bound = prepared_insert.bind(('pcmanus', 'Coup', 2015, 5, 1, 4000)) + self.session.execute(bound) + bound = prepared_insert.bind(('jbellis', 'Coup', 2015, 5, 3, 1750)) + self.session.execute(bound) + bound = prepared_insert.bind(('yukim', 'Coup', 2015, 5, 3, 2250)) + self.session.execute(bound) + bound = prepared_insert.bind(('tjake', 'Coup', 2015, 5, 3, 500)) + self.session.execute(bound) + bound = prepared_insert.bind(('iamaleksey', 'Coup', 2015, 6, 1, 2500)) + self.session.execute(bound) + bound = prepared_insert.bind(('tjake', 'Coup', 2015, 6, 2, 1000)) + self.session.execute(bound) + bound = prepared_insert.bind(('pcmanus', 'Coup', 2015, 6, 2, 2000)) + self.session.execute(bound) + bound = prepared_insert.bind(('jmckenzie', 'Coup', 2015, 6, 9, 2700)) + self.session.execute(bound) + bound = prepared_insert.bind(('jbellis', 'Coup', 2015, 6, 20, 3500)) + self.session.execute(bound) + bound = prepared_insert.bind(('jbellis', 'Checkers', 2015, 6, 20, 1200)) + self.session.execute(bound) + bound = prepared_insert.bind(('jbellis', 'Chess', 2015, 6, 21, 3500)) + self.session.execute(bound) + bound = prepared_insert.bind(('pcmanus', 'Chess', 2015, 1, 25, 3200)) + self.session.execute(bound) + + # Test simple statement and alltime high filtering + query_statement = SimpleStatement("SELECT * FROM {0}.alltimehigh WHERE game='Coup'".format(self.ksname), + consistency_level=ConsistencyLevel.QUORUM) + results = self.session.execute(query_statement) + self.assertEquals(results[0].game, 'Coup') + self.assertEquals(results[0].year, 2015) + self.assertEquals(results[0].month, 5) + self.assertEquals(results[0].day, 1) + self.assertEquals(results[0].score, 4000) + self.assertEquals(results[0].user, "pcmanus") + + # Test prepared statement and daily high filtering + prepared_query = self.session.prepare("SELECT * FROM {0}.dailyhigh WHERE game=? AND year=? AND month=? and day=?".format(self.ksname).format(self.ksname)) + bound_query = prepared_query.bind(("Coup", 2015, 6, 2)) + results = self.session.execute(bound_query) + self.assertEquals(results[0].game, 'Coup') + self.assertEquals(results[0].year, 2015) + self.assertEquals(results[0].month, 6) + self.assertEquals(results[0].day, 2) + self.assertEquals(results[0].score, 2000) + self.assertEquals(results[0].user, "pcmanus") + + self.assertEquals(results[1].game, 'Coup') + self.assertEquals(results[1].year, 2015) + self.assertEquals(results[1].month, 6) + self.assertEquals(results[1].day, 2) + self.assertEquals(results[1].score, 1000) + self.assertEquals(results[1].user, "tjake") + + # Test montly high range queries + prepared_query = self.session.prepare("SELECT * FROM {0}.monthlyhigh WHERE game=? AND year=? AND month=? and score >= ? and score <= ?".format(self.ksname).format(self.ksname)) + bound_query = prepared_query.bind(("Coup", 2015, 6, 2500, 3500)) + results = self.session.execute(bound_query) + self.assertEquals(results[0].game, 'Coup') + self.assertEquals(results[0].year, 2015) + self.assertEquals(results[0].month, 6) + self.assertEquals(results[0].day, 20) + self.assertEquals(results[0].score, 3500) + self.assertEquals(results[0].user, "jbellis") + + self.assertEquals(results[1].game, 'Coup') + self.assertEquals(results[1].year, 2015) + self.assertEquals(results[1].month, 6) + self.assertEquals(results[1].day, 9) + self.assertEquals(results[1].score, 2700) + self.assertEquals(results[1].user, "jmckenzie") + + self.assertEquals(results[2].game, 'Coup') + self.assertEquals(results[2].year, 2015) + self.assertEquals(results[2].month, 6) + self.assertEquals(results[2].day, 1) + self.assertEquals(results[2].score, 2500) + self.assertEquals(results[2].user, "iamaleksey") + + # Test filtered user high scores + query_statement = SimpleStatement("SELECT * FROM {0}.filtereduserhigh WHERE game='Chess'".format(self.ksname), + consistency_level=ConsistencyLevel.QUORUM) + results = self.session.execute(query_statement) + self.assertEquals(results[0].game, 'Chess') + self.assertEquals(results[0].year, 2015) + self.assertEquals(results[0].month, 6) + self.assertEquals(results[0].day, 21) + self.assertEquals(results[0].score, 3500) + self.assertEquals(results[0].user, "jbellis") + + self.assertEquals(results[1].game, 'Chess') + self.assertEquals(results[1].year, 2015) + self.assertEquals(results[1].month, 1) + self.assertEquals(results[1].day, 25) + self.assertEquals(results[1].score, 3200) + self.assertEquals(results[1].user, "pcmanus") \ No newline at end of file diff --git a/tests/integration/standard/test_row_factories.py b/tests/integration/standard/test_row_factories.py index 4fe5cf39..3c056739 100644 --- a/tests/integration/standard/test_row_factories.py +++ b/tests/integration/standard/test_row_factories.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -from tests.integration import get_server_versions, use_singledc, PROTOCOL_VERSION +from tests.integration import get_server_versions, use_singledc, PROTOCOL_VERSION, BasicSharedKeyspaceUnitTestCaseWFunctionTable try: import unittest2 as unittest @@ -28,45 +28,37 @@ def setup_module(): use_singledc() -class RowFactoryTests(unittest.TestCase): +class RowFactoryTests(BasicSharedKeyspaceUnitTestCaseWFunctionTable): """ Test different row_factories and access code """ - def setUp(self): - self.cluster = Cluster(protocol_version=PROTOCOL_VERSION) - self.session = self.cluster.connect() + super(RowFactoryTests, self).setUp() + self.insert1 = ''' + INSERT INTO {0}.{1} + ( k , v ) + VALUES + ( 1 , 1 ) + '''.format(self.keyspace_name, self.function_table_name) + + self.insert2 = ''' + INSERT INTO {0}.{1} + ( k , v ) + VALUES + ( 2 , 2 ) + '''.format(self.keyspace_name, self.function_table_name) + + self.select = ''' + SELECT * FROM {0}.{1} + '''.format(self.keyspace_name, self.function_table_name) def tearDown(self): - self.cluster.shutdown() - - truncate = ''' - TRUNCATE test3rf.test - ''' - - insert1 = ''' - INSERT INTO test3rf.test - ( k , v ) - VALUES - ( 1 , 1 ) - ''' - - insert2 = ''' - INSERT INTO test3rf.test - ( k , v ) - VALUES - ( 2 , 2 ) - ''' - - select = ''' - SELECT * FROM test3rf.test - ''' + self.drop_function_table() def test_tuple_factory(self): session = self.session session.row_factory = tuple_factory - session.execute(self.truncate) session.execute(self.insert1) session.execute(self.insert2) @@ -87,7 +79,6 @@ class RowFactoryTests(unittest.TestCase): session = self.session session.row_factory = named_tuple_factory - session.execute(self.truncate) session.execute(self.insert1) session.execute(self.insert2) @@ -107,7 +98,6 @@ class RowFactoryTests(unittest.TestCase): session = self.session session.row_factory = dict_factory - session.execute(self.truncate) session.execute(self.insert1) session.execute(self.insert2) @@ -128,7 +118,6 @@ class RowFactoryTests(unittest.TestCase): session = self.session session.row_factory = ordered_dict_factory - session.execute(self.truncate) session.execute(self.insert1) session.execute(self.insert2) @@ -154,7 +143,6 @@ class NamedTupleFactoryAndNumericColNamesTests(unittest.TestCase): def setup_class(cls): cls.cluster = Cluster(protocol_version=PROTOCOL_VERSION) cls.session = cls.cluster.connect() - cls._cass_version, cls._cql_version = get_server_versions() ddl = ''' CREATE TABLE test1rf.table_num_col ( key blob PRIMARY KEY, "626972746864617465" blob )