Пример #1
0
    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)
Пример #2
0
    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
Пример #3
0
    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()
Пример #4
0
    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
Пример #5
0
    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
Пример #6
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)))
Пример #7
0
    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)
Пример #8
0
    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)
Пример #9
0
    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()
Пример #10
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, 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
Пример #11
0
    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)
Пример #12
0
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)
Пример #13
0
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
Пример #14
0
    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()
Пример #15
0
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)
Пример #16
0
 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
Пример #17
0
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)
Пример #18
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)
Пример #19
0
    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()
Пример #20
0
 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
Пример #21
0
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
Пример #22
0
    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)
Пример #23
0
    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
Пример #24
0
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
Пример #25
0
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)
Пример #26
0
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)
Пример #27
0
 def __get__(self, obj, model):
     """ :rtype: ModelQuerySet """
     if model.__abstract__:
         raise CQLEngineException(
             'cannot execute queries against abstract models')
     return SimpleQuerySet(obj)
Пример #28
0
 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)
Пример #30
0
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)