Example #1
0
    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
Example #2
0
    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
Example #3
0
    def __get__(self, obj, model):
        """ :rtype: ModelQuerySet """
        if model.__abstract__:
            raise CQLEngineException(
                "cannot execute queries against abstract models")
        queryset = ModelQuerySet(model)

        return queryset
Example #4
0
 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 = []
Example #5
0
    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
Example #6
0
 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))
Example #7
0
 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)
Example #8
0
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)
Example #9
0
    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
Example #10
0
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)))
Example #11
0
    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
Example #12
0
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)
Example #13
0
 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)