async def async_delete(self): """ Deletes one instance """ if self.instance is None: raise CQLEngineException("DML Query instance attribute is None") ds = DeleteStatement(self.column_family_name, timestamp=self._timestamp, conditionals=self._conditional, if_exists=self._if_exists) for name, col in self.model._primary_keys.items(): val = getattr(self.instance, name) if val is None and not col.partition_key: continue ds.add_where(col, EqualsOperator(), val) await self._async_execute(ds)
def column_family_name(cls, include_keyspace=True): """ Returns the column family name if it's been defined otherwise, it creates it from the module and class name """ cf_name = protect_name(cls._raw_column_family_name()) if include_keyspace: keyspace = cls._get_keyspace() if not keyspace: raise CQLEngineException( "Model keyspace is not set and no default is available. Set model keyspace or setup connection before attempting to generate a query." ) return '{0}.{1}'.format(protect_name(keyspace), cf_name) return cf_name
def save(self): """ Creates / updates a row. This is a blind insert call. All validation and cleaning needs to happen prior to calling this. """ if self.instance is None: raise CQLEngineException("DML Query intance attribute is None") assert type(self.instance) == self.model nulled_fields = set() if self.instance._has_counter or self.instance._can_update(): if self.instance._has_counter: warn( "'create' and 'save' actions on Counters are deprecated. A future version will disallow this. Use the 'update' mechanism instead." ) return self.update() else: insert = InsertStatement(self.column_family_name, ttl=self._ttl, timestamp=self._timestamp, if_not_exists=self._if_not_exists) static_save_only = False if len( self.instance._clustering_keys) == 0 else True for name, col in self.instance._clustering_keys.items(): static_save_only = static_save_only and col._val_is_null( getattr(self.instance, name, None)) for name, col in self.instance._columns.items(): if static_save_only and not col.static and not col.partition_key: continue val = getattr(self.instance, name, None) if col._val_is_null(val): if self.instance._values[name].changed: nulled_fields.add(col.db_field_name) continue insert.add_assignment_clause( AssignmentClause( col.db_field_name, col.to_database(getattr(self.instance, name, None)))) # skip query execution if it's empty # caused by pointless update queries if not insert.is_empty: self._execute(insert) # delete any nulled columns if not static_save_only: self._delete_null_columns()
def count(self): """ Returns the number of rows matched by this query. *Note: This function executes a SELECT COUNT() and has a performance cost on large datasets* """ if self._batch: raise CQLEngineException("Only inserts, updates, and deletes are available in batch mode") if self._count is None: query = self._select_query() query.count = True result = self._execute(query) count_row = result[0].popitem() self._count = count_row[1] return self._count
def __get__(self, obj, model): """ :rtype: ModelQuerySet """ if model.__abstract__: raise CQLEngineException('cannot execute queries against abstract models') queryset = model.__queryset__(model) # if this is a concrete polymorphic model, and the discriminator # key is an indexed column, add a filter clause to only return # logical rows of the proper type if model._is_polymorphic and not model._is_polymorphic_base: name, column = model._discriminator_column_name, model._discriminator_column if column.partition_key or column.index: # look for existing poly types return queryset.filter(**{name: model.__discriminator_value__}) return queryset
def _validate_pk(model, table_meta): model_partition = [c.db_field_name for c in model._partition_keys.values()] meta_partition = [c.name for c in table_meta.partition_key] model_clustering = [c.db_field_name for c in model._clustering_keys.values()] meta_clustering = [c.name for c in table_meta.clustering_key] if model_partition != meta_partition or model_clustering != meta_clustering: def _pk_string(partition, clustering): return "PRIMARY KEY (({0}){1})".format(', '.join(partition), ', ' + ', '.join(clustering) if clustering else '') raise CQLEngineException("Model {0} PRIMARY KEY composition does not match existing table {1}. " "Model: {2}; Table: {3}. " "Update model or drop the table.".format(model, model.column_family_name(), _pk_string(model_partition, model_clustering), _pk_string(meta_partition, meta_clustering)))
def count(self): """ Returns the number of rows matched by this query """ if self._batch: raise CQLEngineException( "Only inserts, updates, and deletes are available in batch mode" ) if self._result_cache is None: query = self._select_query() query.count = True result = self._execute(query) return result[0]['count'] else: return len(self._result_cache)
def delete(self): """ Deletes one instance """ if self.instance is None: raise CQLEngineException("DML Query instance attribute is None") ds = DeleteStatement(self.column_family_name, timestamp=self._timestamp) for name, col in self.model._primary_keys.items(): if (not col.partition_key) and (getattr(self.instance, name) is None): continue ds.add_where_clause( WhereClause(col.db_field_name, EqualsOperator(), col.to_database(getattr(self.instance, name)))) self._execute(ds)
async def _async_execute_query(self): if self._batch: raise CQLEngineException("Only inserts, updates, " "and deletes are available in batch mode") if self._result_cache is None: results = await self._async_execute(self._select_query()) self._result_generator = (i for i in results) self._result_cache = [] self._construct_result = self._maybe_inject_deferred( self._get_result_constructor()) # "DISTINCT COUNT()" is not supported in C* < 2.2, # so we need to materialize all results to get # len() and count() working with DISTINCT queries if self._materialize_results or self._distinct_fields: self._fill_result_cache()
def iff(self, *args, **kwargs): """Adds IF statements to queryset""" if len([x for x in kwargs.values() if x is None]): raise CQLEngineException("None values on iff are not allowed") clone = copy.deepcopy(self) for operator in args: if not isinstance(operator, TransactionClause): raise QueryException( '{} is not a valid query operator'.format(operator)) clone._transaction.append(operator) for col_name, val in kwargs.items(): exists = False try: column = self.model._get_column(col_name) except KeyError: if col_name == 'pk__token': if not isinstance(val, Token): raise QueryException( "Virtual column 'pk__token' may only be compared to Token() values" ) column = columns._PartitionKeysToken(self.model) else: raise QueryException( "Can't resolve column name: '{}'".format(col_name)) if isinstance(val, Token): if col_name != 'pk__token': raise QueryException( "Token() values may only be compared to the 'pk__token' virtual column" ) partition_columns = column.partition_columns if len(partition_columns) != len(val.value): raise QueryException( 'Token() received {} arguments but model has {} partition keys' .format(len(val.value), len(partition_columns))) val.set_columns(partition_columns) if isinstance(val, BaseQueryFunction) or exists is True: query_val = val else: query_val = column.to_database(val) clone._transaction.append(TransactionClause(col_name, query_val)) return clone
def setter(key, limited_to_strategy=None): """ sets key in result, checking if the key is limited to either SizeTiered or Leveled :param key: one of the compaction options, like "bucket_high" :param limited_to_strategy: SizeTieredCompactionStrategy, LeveledCompactionStrategy :return: """ mkey = "__compaction_{}__".format(key) tmp = getattr(model, mkey) if tmp and limited_to_strategy and limited_to_strategy != model.__compaction__: raise CQLEngineException("{} is limited to {}".format( key, limited_to_strategy)) if tmp: # Explicitly cast the values to strings to be able to compare the # values against introspected values from Cassandra. result[key] = str(tmp)
def sync_type(ks_name, type_model, connection=None): """ Inspects the type_model and creates / updates the corresponding type. Note that the attributes removed from the type_model are not deleted on the database (this operation is not supported). They become effectively ignored by (will not show up on) the type_model. **This function should be used with caution, especially in production environments. Take care to execute schema modifications in a single context (i.e. not concurrently with other clients).** *There are plans to guard schema-modifying functions with an environment-driven conditional.* """ if not _allow_schema_modification(): return if not issubclass(type_model, UserType): raise CQLEngineException("Types must be derived from base UserType.") _sync_type(ks_name, type_model, connection=connection)
def execute(query, params=None, consistency_level=None, timeout=NOT_SET, connection=None): conn = get_connection(connection) if not conn.session: raise CQLEngineException("It is required to setup() cqlengine before executing queries") if isinstance(query, SimpleStatement): pass # elif isinstance(query, BaseCQLStatement): params = query.get_context() query = SimpleStatement(str(query), consistency_level=consistency_level, fetch_size=query.fetch_size) elif isinstance(query, six.string_types): query = SimpleStatement(query, consistency_level=consistency_level) log.debug(format_log_context(query.query_string, connection=connection)) result = conn.session.execute(query, params, timeout=timeout) return result
def setup(self): """Setup the connection""" global cluster, session if 'username' in self.cluster_options or 'password' in self.cluster_options: raise CQLEngineException( "Username & Password are now handled by using the native driver's auth_provider" ) if self.lazy_connect: return if 'cloud' in self.cluster_options: if self.hosts: log.warning( "Ignoring hosts %s because a cloud config was provided.", self.hosts) self.cluster = Cluster(**self.cluster_options) else: self.cluster = Cluster(self.hosts, **self.cluster_options) try: self.session = self.cluster.connect() log.debug( format_log_context( "connection initialized with internally created session", connection=self.name)) except NoHostAvailable: if self.retry_connect: log.warning( format_log_context( "connect failed, setting up for re-attempt on first use", connection=self.name)) self.lazy_connect = True raise if DEFAULT_CONNECTION in _connections and _connections[ DEFAULT_CONNECTION] == self: cluster = _connections[DEFAULT_CONNECTION].cluster session = _connections[DEFAULT_CONNECTION].session self.setup_session()
def set_session(s): """ Configures the default connection with a preexisting :class:`cassandra.cluster.Session` Note: the mapper presently requires a Session :attr:`~.row_factory` set to ``dict_factory``. This may be relaxed in the future """ try: conn = get_connection() except CQLEngineException: # no default connection set; initalize one register_connection('default', session=s, default=True) conn = get_connection() else: if conn.session: log.warning( "configuring new default session for cqlengine when one was already set" ) if not any([ s.cluster.profile_manager.default.row_factory is dict_factory and s.cluster._config_mode in [_ConfigMode.PROFILES, _ConfigMode.UNCOMMITTED], s.row_factory is dict_factory and s.cluster._config_mode in [_ConfigMode.LEGACY, _ConfigMode.UNCOMMITTED], ]): raise CQLEngineException( "Failed to initialize: row_factory must be 'dict_factory'") conn.session = s conn.cluster = s.cluster # Set default keyspace from given session's keyspace if conn.session.keyspace: from cassandra.cqlengine import models models.DEFAULT_KEYSPACE = conn.session.keyspace conn.setup_session() log.debug("cqlengine default connection initialized with %s", s)
async def _async_execute(self, statement): connection = self.instance._get_connection( ) if self.instance else self.model._get_connection() if self._batch: if self._batch._connection: if not self._batch._connection_explicit and connection and \ connection != self._batch._connection: raise CQLEngineException( 'BatchQuery queries must be executed ' 'on the same connection') else: # set the BatchQuery connection from the model self._batch._connection = connection return self._batch.add_query(statement) else: results = await _execute_statement(self.model, statement, self._consistency, self._timeout, connection=connection) if self._if_not_exists or self._if_exists or self._conditional: check_applied(results) return results
def set_session(s): """ Configures the global mapper connection with a preexisting :class:`cassandra.cluster.Session` Note: the mapper presently requires a Session :attr:`~.row_factory` set to ``dict_factory``. This may be relaxed in the future """ global cluster, session if session: log.warning( "configuring new connection for cqlengine when one was already set" ) if s.row_factory is not dict_factory: raise CQLEngineException( "Failed to initialize: 'Session.row_factory' must be 'dict_factory'." ) session = s cluster = s.cluster _register_known_types(cluster) log.debug("cqlengine connection initialized with %s", s)
def add_query(self, query): if not isinstance(query, BaseCQLStatement): raise CQLEngineException( 'only BaseCQLStatements can be added to a batch query') self.queries.append(query)
def update(self): """ updates a row. This is a blind update call. All validation and cleaning needs to happen prior to calling this. """ if self.instance is None: raise CQLEngineException("DML Query intance attribute is None") assert type(self.instance) == self.model null_clustering_key = False if len( self.instance._clustering_keys) == 0 else True static_changed_only = True statement = UpdateStatement(self.column_family_name, ttl=self._ttl, timestamp=self._timestamp, transactions=self._transaction) for name, col in self.instance._clustering_keys.items(): null_clustering_key = null_clustering_key and col._val_is_null( getattr(self.instance, name, None)) # get defined fields and their column names for name, col in self.model._columns.items(): # if clustering key is null, don't include non static columns if null_clustering_key and not col.static and not col.partition_key: continue if not col.is_primary_key: val = getattr(self.instance, name, None) val_mgr = self.instance._values[name] # don't update something that is null if val is None: continue # don't update something if it hasn't changed if not val_mgr.changed and not isinstance( col, columns.Counter): continue static_changed_only = static_changed_only and col.static if isinstance(col, (columns.BaseContainerColumn, columns.Counter)): # get appropriate clause if isinstance(col, columns.List): klass = ListUpdateClause elif isinstance(col, columns.Map): klass = MapUpdateClause elif isinstance(col, columns.Set): klass = SetUpdateClause elif isinstance(col, columns.Counter): klass = CounterUpdateClause else: raise RuntimeError # do the stuff clause = klass(col.db_field_name, val, previous=val_mgr.previous_value, column=col) if clause.get_context_size() > 0: statement.add_assignment_clause(clause) else: statement.add_assignment_clause( AssignmentClause(col.db_field_name, col.to_database(val))) if statement.get_context_size() > 0 or self.instance._has_counter: for name, col in self.model._primary_keys.items(): # only include clustering key if clustering key is not null, and non static columns are changed to avoid cql error if (null_clustering_key or static_changed_only) and (not col.partition_key): continue statement.add_where_clause( WhereClause(col.db_field_name, EqualsOperator(), col.to_database(getattr(self.instance, name)))) self._execute(statement) if not null_clustering_key: self._delete_null_columns()
def _inst_batch(self, batch): assert self._timeout is connection.NOT_SET, 'Setting both timeout and batch is not supported' if self._connection: raise CQLEngineException("Cannot specify a connection on model in batch mode.") self._batch = batch return self
def register_connection(name, hosts=None, consistency=None, lazy_connect=False, retry_connect=False, cluster_options=None, default=False, session=None): """ Add a connection to the connection registry. ``hosts`` and ``session`` are mutually exclusive, and ``consistency``, ``lazy_connect``, ``retry_connect``, and ``cluster_options`` only work with ``hosts``. Using ``hosts`` will create a new :class:`cassandra.cluster.Cluster` and :class:`cassandra.cluster.Session`. :param list hosts: list of hosts, (``contact_points`` for :class:`cassandra.cluster.Cluster`). :param int consistency: The default :class:`~.ConsistencyLevel` for the registered connection's new session. Default is the same as :attr:`.Session.default_consistency_level`. For use with ``hosts`` only; will fail when used with ``session``. :param bool lazy_connect: True if should not connect until first use. For use with ``hosts`` only; will fail when used with ``session``. :param bool retry_connect: True if we should retry to connect even if there was a connection failure initially. For use with ``hosts`` only; will fail when used with ``session``. :param dict cluster_options: A dict of options to be used as keyword arguments to :class:`cassandra.cluster.Cluster`. For use with ``hosts`` only; will fail when used with ``session``. :param bool default: If True, set the new connection as the cqlengine default :param Session session: A :class:`cassandra.cluster.Session` to be used in the created connection. """ if name in _connections: log.warning( "Registering connection '{0}' when it already exists.".format( name)) if session is not None: invalid_config_args = (hosts is not None or consistency is not None or lazy_connect is not False or retry_connect is not False or cluster_options is not None) if invalid_config_args: raise CQLEngineException( "Session configuration arguments and 'session' argument are mutually exclusive" ) conn = Connection.from_session(name, session=session) conn.setup_session() else: # use hosts argument if consistency is None: consistency = ConsistencyLevel.LOCAL_ONE conn = Connection(name, hosts=hosts, consistency=consistency, lazy_connect=lazy_connect, retry_connect=retry_connect, cluster_options=cluster_options) conn.setup() _connections[name] = conn if default: set_default_connection(name) return conn
async def async_update(self): """ updates a row. This is a blind update call. All validation and cleaning needs to happen prior to calling this. """ if self.instance is None: raise CQLEngineException("DML Query intance attribute is None") assert type(self.instance) == self.model null_clustering_key = (False if len(self.instance._clustering_keys) == 0 else True) static_changed_only = True statement = UpdateStatement( self.column_family_name, ttl=self._ttl, timestamp=self._timestamp, conditionals=self._conditional, if_exists=self._if_exists, ) for name, col in self.instance._clustering_keys.items(): null_clustering_key = null_clustering_key and col._val_is_null( getattr(self.instance, name, None)) updated_columns = set() # get defined fields and their column names for name, col in self.model._columns.items(): # if clustering key is null, don't include non static columns if (null_clustering_key and not col.static and not col.partition_key): continue if not col.is_primary_key: val = getattr(self.instance, name, None) val_mgr = self.instance._values[name] if val is None: continue if not val_mgr.changed and not isinstance( col, columns.Counter): continue static_changed_only = static_changed_only and col.static statement.add_update(col, val, previous=val_mgr.previous_value) updated_columns.add(col.db_field_name) if statement.assignments: for name, col in self.model._primary_keys.items(): # only include clustering key if clustering key is not null, # and non static columns are changed to avoid cql error if (null_clustering_key or static_changed_only) and (not col.partition_key): continue statement.add_where(col, EqualsOperator(), getattr(self.instance, name)) await self._async_execute(statement) if not null_clustering_key: # remove conditions on fields that have been updated delete_conditionals = ([ condition for condition in self._conditional if condition.field not in updated_columns ] if self._conditional else None) self._delete_null_columns(delete_conditionals)
def filter(self, *args, **kwargs): """ Adds WHERE arguments to the queryset, returning a new queryset See :ref:`retrieving-objects-with-filters` Returns a QuerySet filtered on the keyword arguments """ # add arguments to the where clause filters if len([x for x in kwargs.values() if x is None]): raise CQLEngineException("None values on filter are not allowed") clone = copy.deepcopy(self) for operator in args: if not isinstance(operator, WhereClause): raise QueryException( '{} is not a valid query operator'.format(operator)) clone._where.append(operator) for arg, val in kwargs.items(): col_name, col_op = self._parse_filter_arg(arg) quote_field = True # resolve column and operator try: column = self.model._get_column(col_name) except KeyError: if col_name == 'pk__token': if not isinstance(val, Token): raise QueryException( "Virtual column 'pk__token' may only be compared to Token() values" ) column = columns._PartitionKeysToken(self.model) quote_field = False else: raise QueryException( "Can't resolve column name: '{}'".format(col_name)) if isinstance(val, Token): if col_name != 'pk__token': raise QueryException( "Token() values may only be compared to the 'pk__token' virtual column" ) partition_columns = column.partition_columns if len(partition_columns) != len(val.value): raise QueryException( 'Token() received {} arguments but model has {} partition keys' .format(len(val.value), len(partition_columns))) val.set_columns(partition_columns) # get query operator, or use equals if not supplied operator_class = BaseWhereOperator.get_operator(col_op or 'EQ') operator = operator_class() if isinstance(operator, InOperator): if not isinstance(val, (list, tuple)): raise QueryException( 'IN queries must use a list/tuple value') query_val = [column.to_database(v) for v in val] elif isinstance(val, BaseQueryFunction): query_val = val else: query_val = column.to_database(val) clone._where.append( WhereClause(column.db_field_name, operator, query_val, quote_field=quote_field)) return clone
def get_cluster(connection=None): conn = get_connection(connection) if not conn.cluster: raise CQLEngineException("%s.cluster is not configured. Call one of the setup or default functions first." % __name__) return conn.cluster
def _sync_table(model, connection=None): if not _allow_schema_modification(): return if not issubclass(model, Model): raise CQLEngineException("Models must be derived from base Model.") if model.__abstract__: raise CQLEngineException("cannot create table from abstract model") cf_name = model.column_family_name() raw_cf_name = model._raw_column_family_name() ks_name = model._get_keyspace() connection = connection or model._get_connection() cluster = get_cluster(connection) try: keyspace = cluster.metadata.keyspaces[ks_name] except KeyError: msg = format_log_context("Keyspace '{0}' for model {1} does not exist.", connection=connection) raise CQLEngineException(msg.format(ks_name, model)) tables = keyspace.tables syncd_types = set() for col in model._columns.values(): udts = [] columns.resolve_udts(col, udts) for udt in [u for u in udts if u not in syncd_types]: _sync_type(ks_name, udt, syncd_types, connection=connection) if raw_cf_name not in tables: log.debug(format_log_context("sync_table creating new table %s", keyspace=ks_name, connection=connection), cf_name) qs = _get_create_table(model) try: execute(qs, connection=connection) except CQLEngineException as ex: # 1.2 doesn't return cf names, so we have to examine the exception # and ignore if it says the column family already exists if "Cannot add already existing column family" not in unicode(ex): raise else: log.debug(format_log_context("sync_table checking existing table %s", keyspace=ks_name, connection=connection), cf_name) table_meta = tables[raw_cf_name] _validate_pk(model, table_meta) table_columns = table_meta.columns model_fields = set() for model_name, col in model._columns.items(): db_name = col.db_field_name model_fields.add(db_name) if db_name in table_columns: col_meta = table_columns[db_name] if col_meta.cql_type != col.db_type: msg = format_log_context('Existing table {0} has column "{1}" with a type ({2}) differing from the model type ({3}).' ' Model should be updated.', keyspace=ks_name, connection=connection) msg = msg.format(cf_name, db_name, col_meta.cql_type, col.db_type) warnings.warn(msg) log.warning(msg) continue if col.primary_key or col.primary_key: msg = format_log_context("Cannot add primary key '{0}' (with db_field '{1}') to existing table {2}", keyspace=ks_name, connection=connection) raise CQLEngineException(msg.format(model_name, db_name, cf_name)) query = "ALTER TABLE {0} add {1}".format(cf_name, col.get_column_def()) execute(query, connection=connection) db_fields_not_in_model = model_fields.symmetric_difference(table_columns) if db_fields_not_in_model: msg = format_log_context("Table {0} has fields not referenced by model: {1}", keyspace=ks_name, connection=connection) log.info(msg.format(cf_name, db_fields_not_in_model)) _update_options(model, connection=connection) table = cluster.metadata.keyspaces[ks_name].tables[raw_cf_name] indexes = [c for n, c in model._columns.items() if c.index] # TODO: support multiple indexes in C* 3.0+ for column in indexes: index_name = _get_index_name_by_column(table, column.db_field_name) if index_name: continue qs = ['CREATE INDEX'] qs += ['ON {0}'.format(cf_name)] qs += ['("{0}")'.format(column.db_field_name)] qs = ' '.join(qs) execute(qs, connection=connection)
def sync_table(model): """ Inspects the model and creates / updates the corresponding table and columns. Any User Defined Types used in the table are implicitly synchronized. This function can only add fields that are not part of the primary key. Note that the attributes removed from the model are not deleted on the database. They become effectively ignored by (will not show up on) the model. **This function should be used with caution, especially in production environments. Take care to execute schema modifications in a single context (i.e. not concurrently with other clients).** *There are plans to guard schema-modifying functions with an environment-driven conditional.* """ if not _allow_schema_modification(): return if not issubclass(model, Model): raise CQLEngineException("Models must be derived from base Model.") if model.__abstract__: raise CQLEngineException("cannot create table from abstract model") cf_name = model.column_family_name() raw_cf_name = model._raw_column_family_name() ks_name = model._get_keyspace() cluster = get_cluster() keyspace = cluster.metadata.keyspaces[ks_name] tables = keyspace.tables syncd_types = set() for col in model._columns.values(): udts = [] columns.resolve_udts(col, udts) for udt in [u for u in udts if u not in syncd_types]: _sync_type(ks_name, udt, syncd_types) # check for an existing column family if raw_cf_name not in tables: log.debug("sync_table creating new table %s", cf_name) qs = _get_create_table(model) try: execute(qs) except CQLEngineException as ex: # 1.2 doesn't return cf names, so we have to examine the exception # and ignore if it says the column family already exists if "Cannot add already existing column family" not in unicode(ex): raise else: log.debug("sync_table checking existing table %s", cf_name) # see if we're missing any columns field_names = _get_non_pk_field_names(tables[raw_cf_name]) model_fields = set() # # TODO: does this work with db_name?? for name, col in model._columns.items(): if col.primary_key or col.partition_key: continue # we can't mess with the PK model_fields.add(name) if col.db_field_name in field_names: continue # skip columns already defined # add missing column using the column def query = "ALTER TABLE {0} add {1}".format(cf_name, col.get_column_def()) execute(query) db_fields_not_in_model = model_fields.symmetric_difference(field_names) if db_fields_not_in_model: log.info("Table %s has fields not referenced by model: %s", cf_name, db_fields_not_in_model) _update_options(model) table = cluster.metadata.keyspaces[ks_name].tables[raw_cf_name] indexes = [c for n, c in model._columns.items() if c.index] # TODO: support multiple indexes in C* 3.0+ for column in indexes: index_name = _get_index_name_by_column(table, column.db_field_name) if index_name: continue qs = ['CREATE INDEX'] qs += ['ON {0}'.format(cf_name)] qs += ['("{0}")'.format(column.db_field_name)] qs = ' '.join(qs) execute(qs)
def __get__(self, obj, model): """ :rtype: ModelQuerySet """ if model.__abstract__: raise CQLEngineException( 'cannot execute queries against abstract models') return SimpleQuerySet(obj)
def batch(self, batch_obj): if batch_obj is not None and not isinstance(batch_obj, BatchQuery): raise CQLEngineException( 'batch_obj must be a BatchQuery instance or None') self._batch = batch_obj return self
def test_connection_register_called_second_time(self, connection_mock): settings = self.cassandra_connection.settings_dict CassandraConnection('default', **settings) connection_mock.get_connection.side_effect = CQLEngineException() self.assertFalse(connection_mock.register_connection.called)
def sync_table(model): """ Inspects the model and creates / updates the corresponding table and columns. Any User Defined Types used in the table are implicitly synchronized. This function can only add fields that are not part of the primary key. Note that the attributes removed from the model are not deleted on the database. They become effectively ignored by (will not show up on) the model. **This function should be used with caution, especially in production environments. Take care to execute schema modifications in a single context (i.e. not concurrently with other clients).** *There are plans to guard schema-modifying functions with an environment-driven conditional.* """ if not _allow_schema_modification(): return if not issubclass(model, Model): raise CQLEngineException("Models must be derived from base Model.") if model.__abstract__: raise CQLEngineException("cannot create table from abstract model") cf_name = model.column_family_name() raw_cf_name = model._raw_column_family_name() ks_name = model._get_keyspace() cluster = get_cluster() try: keyspace = cluster.metadata.keyspaces[ks_name] except KeyError: raise CQLEngineException( "Keyspace '{0}' for model {1} does not exist.".format( ks_name, model)) tables = keyspace.tables syncd_types = set() for col in model._columns.values(): udts = [] columns.resolve_udts(col, udts) for udt in [u for u in udts if u not in syncd_types]: _sync_type(ks_name, udt, syncd_types) if raw_cf_name not in tables: log.debug("sync_table creating new table %s", cf_name) qs = _get_create_table(model) try: execute(qs) except CQLEngineException as ex: # 1.2 doesn't return cf names, so we have to examine the exception # and ignore if it says the column family already exists if "Cannot add already existing column family" not in unicode(ex): raise else: log.debug("sync_table checking existing table %s", cf_name) table_meta = tables[raw_cf_name] _validate_pk(model, table_meta) table_columns = table_meta.columns model_fields = set() for model_name, col in model._columns.items(): db_name = col.db_field_name model_fields.add(db_name) if db_name in table_columns: col_meta = table_columns[db_name] if col_meta.cql_type != col.db_type: msg = 'Existing table {0} has column "{1}" with a type ({2}) differing from the model type ({3}).' \ ' Model should be updated.'.format(cf_name, db_name, col_meta.cql_type, col.db_type) warnings.warn(msg) log.warning(msg) continue if col.primary_key or col.primary_key: raise CQLEngineException( "Cannot add primary key '{0}' (with db_field '{1}') to existing table {2}" .format(model_name, db_name, cf_name)) query = "ALTER TABLE {0} add {1}".format(cf_name, col.get_column_def()) execute(query) db_fields_not_in_model = model_fields.symmetric_difference( table_columns) if db_fields_not_in_model: log.info( "Table {0} has fields not referenced by model: {1}".format( cf_name, db_fields_not_in_model)) _update_options(model) table = cluster.metadata.keyspaces[ks_name].tables[raw_cf_name] indexes = [c for n, c in model._columns.items() if c.index] # TODO: support multiple indexes in C* 3.0+ for column in indexes: index_name = _get_index_name_by_column(table, column.db_field_name) if index_name: continue qs = ['CREATE INDEX'] qs += ['ON {0}'.format(cf_name)] qs += ['("{0}")'.format(column.db_field_name)] qs = ' '.join(qs) execute(qs)