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, ConditionalClause): raise QueryException( "{0} is not a valid query operator".format(operator)) clone._conditional.append(operator) for arg, val in kwargs.items(): if isinstance(val, Token): raise QueryException( "Token() values are not valid in conditionals") col_name, col_op = self._parse_filter_arg(arg) try: column = self.model._get_column(col_name) except KeyError: raise QueryException( "Can't resolve column name: '{0}'".format(col_name)) if isinstance(val, BaseQueryFunction): query_val = val else: query_val = column.to_database(val) operator_class = BaseWhereOperator.get_operator(col_op or "EQ") operator = operator_class() clone._conditional.append( WhereClause(column.db_field_name, operator, query_val)) return clone
def prepare(self): 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 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.set_delete_null_columns(delete_conditionals) 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)) self.statement = statement
def __get__(self, obj, model): """ :rtype: ModelQuerySet """ if model.__abstract__: raise CQLEngineException( "cannot execute queries against abstract models") queryset = ModelQuerySet(model) return queryset
def __init__( self, conn, batch_type=None, timestamp=None, consistency=None, execute_on_exception=False, timeout=TIMEOUT_NOT_SET, ): """ :param conn: Cassandra connection wrapper used to execute the batched queries. :type: cqlengine.connection.Connection object :param batch_type: (optional) One of batch type values available through BatchType enum :type batch_type: str or None :param timestamp: (optional) A datetime or timedelta object with desired timestamp to be applied to the batch conditional. :type timestamp: datetime or timedelta or None :param consistency: (optional) One of consistency values ("ANY", "ONE", "QUORUM" etc) :type consistency: The :class:`.ConsistencyLevel` to be used for the batch query, or None. :param execute_on_exception: (Defaults to False) Indicates that when the Batch instance is used as a context manager the queries accumulated within the context must be executed despite encountering an error within the context. By default, any exception raised from within the context scope will cause the batched queries not to be executed. :type execute_on_exception: bool :param timeout: (optional) Timeout for the entire batch (in seconds), if not specified fallback to default session timeout :type timeout: float or cqlmapper.TIMEOUT_NOT_SET """ self.conn = conn self._executed = False self._context_entered = False self.queries = [] self.batch_type = batch_type if timestamp is not None and not isinstance(timestamp, (datetime, timedelta)): raise CQLEngineException( "timestamp object must be an instance of datetime") self.timestamp = timestamp self.consistency = consistency self._execute_on_exception = execute_on_exception self.timeout = timeout self._callbacks = []
def prepare(self): 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) self.statement = ds
def execute(self, query, *a, **kw): """Adds the given query to the batch.""" if isinstance(query, DMLQuery): if query.statement: self._add_query(query.statement) if query.cleanup_statement: self._add_query(query.cleanup_statement) elif isinstance(query, BaseCQLStatement): batch_statement_types = (InsertStatement, UpdateStatement, DeleteStatement) if not isinstance(query, batch_statement_types): raise CQLEngineException( "Only inserts, updates, and deletes are available in " "batch mode") return self._add_query(query) else: raise ValueError("Unexpected type for query <%s>" % type(query))
def execute(self, query, *a, **kw): if isinstance(query, DMLQuery): if query.statement: self._add_query(query.statement) if query.cleanup_statement: self._add_query(query.cleanup_statement) elif isinstance(query, BaseCQLStatement): batch_statement_types = ( InsertStatement, UpdateStatement, DeleteStatement, ) if not isinstance(query, batch_statement_types): raise CQLEngineException( "Only inserts, updates, and deletes are available in " "batch mode" ) return self._add_query(query)
def sync_type(conn, type_model): """ 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(conn, type_model)
def prepare(self): 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: raise Exception( "'create' and 'save' actions on Counters is not supported. " "Use the 'update' mechanism instead." ) insert = InsertStatement( self.column_family_name, ttl=self._ttl, timestamp=self._timestamp, if_not_exists=self._if_not_exists, ) static_save_only = len(self.instance._clustering_keys) != 0 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(col, getattr(self.instance, name, None)) # set cleanup to delete any nulled columns if not static_save_only: self.set_delete_null_columns() # skip query execution if it's empty # caused by pointless update queries if not insert.is_empty: self.statement = insert
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 filter(self, **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 arg, val in kwargs.items(): col_name, col_op = self._parse_filter_arg(arg) quote_field = True if not isinstance(val, Token): try: column = self.model._get_column(col_name) except KeyError: raise QueryException( "Can't resolve column name: '{0}'".format(col_name) ) else: if col_name != 'pk__token': raise QueryException( "Token() values may only be compared to the " "'pk__token' virtual column" ) column = columns._PartitionKeysToken(self.model) quote_field = False partition_columns = column.partition_columns if len(partition_columns) != len(val.value): raise QueryException( 'Token() received {0} arguments but model has {1} ' '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 elif (isinstance(operator, ContainsOperator) and isinstance(column, (columns.List, columns.Set, columns.Map))): # For ContainsOperator and collections, we query using the # value, not the container query_val = val else: query_val = column.to_database(val) if not col_op: # only equal values should be deferred clone._defer_fields.add(col_name) # map by db field name for substitution in results clone._deferred_values[column.db_field_name] = val clone._where.append( WhereClause( column.db_field_name, operator, query_val, quote_field=quote_field, ) ) return clone
def sync_table(conn, model): """Inspects the model and creates / updates the corresponding table and columns. If `keyspaces` is specified, the table will be synched for all specified keyspaces. Note that the `Model.__keyspace__` is ignored in that case. If `connections` is specified, the table will be synched for all specified connections. Note that the `Model.__connection__` is ignored in that case. If not specified, it will try to get the connection from the Model. 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. :param conn: Cassandra connection wrapper used to execute any CQL statements. :type: cqlengine.ConnectionInterface subclass :param model: Model representing the table you want to sync. :type: cqlmapper.models.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") cluster = conn.cluster cf_name = model.column_family_name() raw_cf_name = model._raw_column_family_name() ks_name = conn.keyspace try: keyspace = cluster.metadata.keyspaces[ks_name] except KeyError: msg = "Keyspace '{0}' for model {1} does not exist." 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(conn, udt, syncd_types) if raw_cf_name not in tables: log.debug( "sync_table creating new table %s in keyspace %s", cf_name, ks_name, ) qs = _get_create_table(model) try: conn.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 for keyspace %s", cf_name, keyspace, ) 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.' ) 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 = ( "Cannot add primary key '{0}' (with db_field '{1}') to " "existing table {2}" ) raise CQLEngineException( msg.format(model_name, db_name, cf_name) ) query = "ALTER TABLE {0} add {1}".format( cf_name, col.get_column_def(), ) conn.execute(query) db_fields_not_in_model = model_fields.symmetric_difference( table_columns ) if db_fields_not_in_model: msg = "Table {0} has fields not referenced by model: {1}" log.info(msg.format(cf_name, db_fields_not_in_model)) _update_options(conn, 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) conn.execute(qs)
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)