def query_parameters_from_update(collection, filters, data): where_clauses = where_clauses_from_filters(filters) assignment_clauses = assignment_clauses_from_data(data) statement = UpdateStatement( table=collection, assignments=assignment_clauses, where=where_clauses, if_exists=True ) return str(statement), statement.get_context()
def update_query(table: str, filters: list, data: dict): where_clauses = generate_where_clause_from_filters(filters) assignment_clauses = assignment_clauses_from_data(data) statement = UpdateStatement(table, assignment_clauses, where_clauses, if_exists=True) return str(statement), statement.get_context()
async def async_update(self, **values): if not values: return nulled_columns = set() updated_columns = set() us = UpdateStatement(self.column_family_name, where=self._where, ttl=self._ttl, timestamp=self._timestamp, conditionals=self._conditional, if_exists=self._if_exists) for name, val in values.items(): col_name, col_op = self._parse_filter_arg(name) col = self.model._columns.get(col_name) # check for nonexistant columns if col is None: raise ValidationError( "{0}.{1} has no column named: {2}".format( self.__module__, self.model.__name__, col_name)) # check for primary key update attempts if col.is_primary_key: raise ValidationError( "Cannot apply update to primary key '{0}' for {1}.{2}". format(col_name, self.__module__, self.model.__name__)) if col_op == 'remove' and isinstance(col, columns.Map): if not isinstance(val, set): raise ValidationError( "Cannot apply update operation '{0}' on column '{1}' with value '{2}'. A set is required." .format(col_op, col_name, val)) val = {v: None for v in val} else: # we should not provide default values in this use case. val = col.validate(val) if val is None: nulled_columns.add(col_name) continue us.add_update(col, val, operation=col_op) updated_columns.add(col_name) if us.assignments: await self._async_execute(us) if nulled_columns: delete_conditional = [ condition for condition in self._conditional if condition.field not in updated_columns ] if self._conditional else None ds = DeleteStatement(self.column_family_name, fields=nulled_columns, where=self._where, conditionals=delete_conditional, if_exists=self._if_exists) await self._async_execute(ds)
def test_context_update(self): us = UpdateStatement('table') us.add_assignment(Column(db_field='a'), 'b') us.add_assignment(Column(db_field='c'), 'd') us.add_where(Column(db_field='a'), EqualsOperator(), 'x') us.update_context_id(3) self.assertEqual(six.text_type(us), 'UPDATE table SET "a" = %(4)s, "c" = %(5)s WHERE "a" = %(3)s') self.assertEqual(us.get_context(), {'4': 'b', '5': 'd', '3': 'x'})
def test_context_update(self): us = UpdateStatement('table') us.add_assignment_clause(AssignmentClause('a', 'b')) us.add_assignment_clause(AssignmentClause('c', 'd')) us.add_where_clause(WhereClause('a', EqualsOperator(), 'x')) us.update_context_id(3) self.assertEqual( six.text_type(us), 'UPDATE table SET "a" = %(4)s, "c" = %(5)s WHERE "a" = %(3)s') self.assertEqual(us.get_context(), {'4': 'b', '5': 'd', '3': 'x'})
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, 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)) self._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 test_context_update(self): us = UpdateStatement('table') us.add_assignment_clause(AssignmentClause('a', 'b')) us.add_assignment_clause(AssignmentClause('c', 'd')) us.add_where_clause(WhereClause('a', EqualsOperator(), 'x')) us.update_context_id(3) self.assertEqual(six.text_type(us), 'UPDATE table SET "a" = %(4)s, "c" = %(5)s WHERE "a" = %(3)s') self.assertEqual(us.get_context(), {'4': 'b', '5': 'd', '3': 'x'})
def test_table_rendering(self): """ tests that fields are properly added to the select statement """ us = UpdateStatement('table') self.assertTrue( six.text_type(us).startswith('UPDATE table SET'), six.text_type(us)) self.assertTrue(str(us).startswith('UPDATE table SET'), str(us))
def test_rendering(self): us = UpdateStatement('table') us.add_assignment(Column(db_field='a'), 'b') us.add_assignment(Column(db_field='c'), 'd') us.add_where(Column(db_field='a'), EqualsOperator(), 'x') self.assertEqual(six.text_type(us), 'UPDATE table SET "a" = %(0)s, "c" = %(1)s WHERE "a" = %(2)s', six.text_type(us)) us.add_where(Column(db_field='a'), NotEqualsOperator(), 'y') self.assertEqual(six.text_type(us), 'UPDATE table SET "a" = %(0)s, "c" = %(1)s WHERE "a" = %(2)s AND "a" != %(3)s', six.text_type(us))
def test_insert_statement_execute(self): """ Test to verify the execution of BaseCQLStatements using connection.execute @since 3.10 @jira_ticket PYTHON-505 @expected_result inserts a row in C*, updates the rows and then deletes all the rows using BaseCQLStatements @test_category data_types:object_mapper """ partition = uuid4() cluster = 1 #Verifying insert statement st = InsertStatement(self.table_name) st.add_assignment(Column(db_field='partition'), partition) st.add_assignment(Column(db_field='cluster'), cluster) st.add_assignment(Column(db_field='count'), 1) st.add_assignment(Column(db_field='text'), "text_for_db") st.add_assignment(Column(db_field='text_set'), set(("foo", "bar"))) st.add_assignment(Column(db_field='text_list'), ["foo", "bar"]) st.add_assignment(Column(db_field='text_map'), { "foo": '1', "bar": '2' }) execute(st) self._verify_statement(st) # Verifying update statement where = [ WhereClause('partition', EqualsOperator(), partition), WhereClause('cluster', EqualsOperator(), cluster) ] st = UpdateStatement(self.table_name, where=where) st.add_assignment(Column(db_field='count'), 2) st.add_assignment(Column(db_field='text'), "text_for_db_update") st.add_assignment(Column(db_field='text_set'), set(("foo_update", "bar_update"))) st.add_assignment(Column(db_field='text_list'), ["foo_update", "bar_update"]) st.add_assignment(Column(db_field='text_map'), { "foo": '3', "bar": '4' }) execute(st) self._verify_statement(st) # Verifying delete statement execute(DeleteStatement(self.table_name, where=where)) self.assertEqual(TestQueryUpdateModel.objects.count(), 0)
def test_rendering(self): us = UpdateStatement('table') us.add_assignment_clause(AssignmentClause('a', 'b')) us.add_assignment_clause(AssignmentClause('c', 'd')) us.add_where_clause(WhereClause('a', EqualsOperator(), 'x')) self.assertEqual( six.text_type(us), 'UPDATE table SET "a" = %(0)s, "c" = %(1)s WHERE "a" = %(2)s', six.text_type(us))
def test_update_empty_set_removal_does_not_assign(self): us = UpdateStatement('table') us.add_assignment_clause(SetUpdateClause('a', set(), operation='remove')) self.assertEqual(six.text_type(us), 'UPDATE table SET "a" = "a" - %(0)s')
def update(self, **values): """ Performs an update on the row selected by the queryset. Include values to update in the update like so: .. code-block:: python Model.objects(key=n).update(value='x') Passing in updates for columns which are not part of the model will raise a ValidationError. Per column validation will be performed, but instance level validation will not (i.e., `Model.validate` is not called). This is sometimes referred to as a blind update. For example: .. code-block:: python class User(Model): id = Integer(primary_key=True) name = Text() setup(["localhost"], "test") sync_table(User) u = User.create(id=1, name="jon") User.objects(id=1).update(name="Steve") # sets name to null User.objects(id=1).update(name=None) Also supported is blindly adding and removing elements from container columns, without loading a model instance from Cassandra. Using the syntax `.update(column_name={x, y, z})` will overwrite the contents of the container, like updating a non container column. However, adding `__<operation>` to the end of the keyword arg, makes the update call add or remove items from the collection, without overwriting then entire column. Given the model below, here are the operations that can be performed on the different container columns: .. code-block:: python class Row(Model): row_id = columns.Integer(primary_key=True) set_column = columns.Set(Integer) list_column = columns.List(Integer) map_column = columns.Map(Integer, Integer) :class:`~cqlengine.columns.Set` - `add`: adds the elements of the given set to the column - `remove`: removes the elements of the given set to the column .. code-block:: python # add elements to a set Row.objects(row_id=5).update(set_column__add={6}) # remove elements to a set Row.objects(row_id=5).update(set_column__remove={4}) :class:`~cqlengine.columns.List` - `append`: appends the elements of the given list to the end of the column - `prepend`: prepends the elements of the given list to the beginning of the column .. code-block:: python # append items to a list Row.objects(row_id=5).update(list_column__append=[6, 7]) # prepend items to a list Row.objects(row_id=5).update(list_column__prepend=[1, 2]) :class:`~cqlengine.columns.Map` - `update`: adds the given keys/values to the columns, creating new entries if they didn't exist, and overwriting old ones if they did .. code-block:: python # add items to a map Row.objects(row_id=5).update(map_column__update={1: 2, 3: 4}) """ if not values: return nulled_columns = set() updated_columns = set() us = UpdateStatement(self.column_family_name, where=self._where, ttl=self._ttl, timestamp=self._timestamp, conditionals=self._conditional, if_exists=self._if_exists) for name, val in values.items(): col_name, col_op = self._parse_filter_arg(name) col = self.model._columns.get(col_name) # check for nonexistant columns if col is None: raise ValidationError("{0}.{1} has no column named: {2}".format(self.__module__, self.model.__name__, col_name)) # check for primary key update attempts if col.is_primary_key: raise ValidationError("Cannot apply update to primary key '{0}' for {1}.{2}".format(col_name, self.__module__, self.model.__name__)) # we should not provide default values in this use case. val = col.validate(val) if val is None: nulled_columns.add(col_name) continue us.add_update(col, val, operation=col_op) updated_columns.add(col_name) if us.assignments: self._execute(us) if nulled_columns: delete_conditional = [condition for condition in self._conditional if condition.field not in updated_columns] if self._conditional else None ds = DeleteStatement(self.column_family_name, fields=nulled_columns, where=self._where, conditionals=delete_conditional, if_exists=self._if_exists) self._execute(ds)
def test_update_empty_set_removal_does_not_assign(self): us = UpdateStatement('table') us.add_update(Set(Text, db_field='a'), set(), 'remove') self.assertFalse(us.assignments)
def test_additional_rendering(self): us = UpdateStatement('table', ttl=60) us.add_assignment(Column(db_field='a'), 'b') us.add_where(Column(db_field='a'), EqualsOperator(), 'x') self.assertIn('USING TTL 60', six.text_type(us))
def test_update_list_prepend_with_empty_list(self): us = UpdateStatement('table') us.add_assignment_clause( ListUpdateClause('a', List.Quoter([]), operation='prepend')) self.assertEqual(six.text_type(us), 'UPDATE table SET "a" = %(0)s + "a"')
def test_context(self): us = UpdateStatement('table') us.add_assignment_clause(AssignmentClause('a', 'b')) us.add_assignment_clause(AssignmentClause('c', 'd')) us.add_where_clause(WhereClause('a', EqualsOperator(), 'x')) self.assertEqual(us.get_context(), {'0': 'b', '1': 'd', '2': 'x'})
def test_additional_rendering(self): us = UpdateStatement('table', ttl=60) us.add_assignment_clause(AssignmentClause('a', 'b')) us.add_where_clause(WhereClause('a', EqualsOperator(), 'x')) self.assertIn('USING TTL 60', six.text_type(us))
def update(self, **values): """ Performs an update on the row selected by the queryset. Include values to update in the update like so: .. code-block:: python Model.objects(key=n).update(value='x') Passing in updates for columns which are not part of the model will raise a ValidationError. Per column validation will be performed, but instance level validation will not (i.e., `Model.validate` is not called). This is sometimes referred to as a blind update. For example: .. code-block:: python class User(Model): id = Integer(primary_key=True) name = Text() setup(["localhost"], "test") sync_table(User) u = User.create(id=1, name="jon") User.objects(id=1).update(name="Steve") # sets name to null User.objects(id=1).update(name=None) Also supported is blindly adding and removing elements from container columns, without loading a model instance from Cassandra. Using the syntax `.update(column_name={x, y, z})` will overwrite the contents of the container, like updating a non container column. However, adding `__<operation>` to the end of the keyword arg, makes the update call add or remove items from the collection, without overwriting then entire column. Given the model below, here are the operations that can be performed on the different container columns: .. code-block:: python class Row(Model): row_id = columns.Integer(primary_key=True) set_column = columns.Set(Integer) list_column = columns.List(Integer) map_column = columns.Map(Integer, Integer) :class:`~cqlengine.columns.Set` - `add`: adds the elements of the given set to the column - `remove`: removes the elements of the given set to the column .. code-block:: python # add elements to a set Row.objects(row_id=5).update(set_column__add={6}) # remove elements to a set Row.objects(row_id=5).update(set_column__remove={4}) :class:`~cqlengine.columns.List` - `append`: appends the elements of the given list to the end of the column - `prepend`: prepends the elements of the given list to the beginning of the column .. code-block:: python # append items to a list Row.objects(row_id=5).update(list_column__append=[6, 7]) # prepend items to a list Row.objects(row_id=5).update(list_column__prepend=[1, 2]) :class:`~cqlengine.columns.Map` - `update`: adds the given keys/values to the columns, creating new entries if they didn't exist, and overwriting old ones if they did .. code-block:: python # add items to a map Row.objects(row_id=5).update(map_column__update={1: 2, 3: 4}) """ if not values: return nulled_columns = set() us = UpdateStatement(self.column_family_name, where=self._where, ttl=self._ttl, timestamp=self._timestamp, transactions=self._transaction) for name, val in values.items(): col_name, col_op = self._parse_filter_arg(name) col = self.model._columns.get(col_name) # check for nonexistant columns if col is None: raise ValidationError("{}.{} has no column named: {}".format( self.__module__, self.model.__name__, col_name)) # check for primary key update attempts if col.is_primary_key: raise ValidationError( "Cannot apply update to primary key '{}' for {}.{}".format( col_name, self.__module__, self.model.__name__)) # we should not provide default values in this use case. val = col.validate(val) if val is None: nulled_columns.add(col_name) continue # add the update statements if isinstance(col, columns.Counter): # TODO: implement counter updates raise NotImplementedError elif isinstance(col, (columns.List, columns.Set, columns.Map)): if isinstance(col, columns.List): klass = ListUpdateClause elif isinstance(col, columns.Set): klass = SetUpdateClause elif isinstance(col, columns.Map): klass = MapUpdateClause else: raise RuntimeError us.add_assignment_clause( klass(col_name, col.to_database(val), operation=col_op)) else: us.add_assignment_clause( AssignmentClause(col_name, col.to_database(val))) if us.assignments: self._execute(us) if nulled_columns: ds = DeleteStatement(self.column_family_name, fields=nulled_columns, where=self._where) self._execute(ds)
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 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, 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)) # 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 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 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 test_update_list_append_with_empty_list(self): us = UpdateStatement('table') us.add_assignment_clause(ListUpdateClause('a', [], operation='append')) self.assertEqual(six.text_type(us), 'UPDATE table SET "a" = "a" + %(0)s')
def test_context(self): us = UpdateStatement('table') us.add_assignment(Column(db_field='a'), 'b') us.add_assignment(Column(db_field='c'), 'd') us.add_where(Column(db_field='a'), EqualsOperator(), 'x') self.assertEqual(us.get_context(), {'0': 'b', '1': 'd', '2': 'x'})
def test_rendering(self): us = UpdateStatement('table') us.add_assignment_clause(AssignmentClause('a', 'b')) us.add_assignment_clause(AssignmentClause('c', 'd')) us.add_where_clause(WhereClause('a', EqualsOperator(), 'x')) self.assertEqual(six.text_type(us), 'UPDATE table SET "a" = %(0)s, "c" = %(1)s WHERE "a" = %(2)s', six.text_type(us))
def test_update_set_add(self): us = UpdateStatement('table') us.add_update(Set(Text, db_field='a'), set((1,)), 'add') self.assertEqual(six.text_type(us), 'UPDATE table SET "a" = "a" + %(0)s')
def test_insert_statement_execute(self): """ Test to verify the execution of BaseCQLStatements using connection.execute @since 3.10 @jira_ticket PYTHON-505 @expected_result inserts a row in C*, updates the rows and then deletes all the rows using BaseCQLStatements @test_category data_types:object_mapper """ partition = uuid4() cluster = 1 self._insert_statement(partition, cluster) # Verifying update statement where = [WhereClause('partition', EqualsOperator(), partition), WhereClause('cluster', EqualsOperator(), cluster)] st = UpdateStatement(self.table_name, where=where) st.add_assignment(Column(db_field='count'), 2) st.add_assignment(Column(db_field='text'), "text_for_db_update") st.add_assignment(Column(db_field='text_set'), set(("foo_update", "bar_update"))) st.add_assignment(Column(db_field='text_list'), ["foo_update", "bar_update"]) st.add_assignment(Column(db_field='text_map'), {"foo": '3', "bar": '4'}) execute(st) self._verify_statement(st) # Verifying delete statement execute(DeleteStatement(self.table_name, where=where)) self.assertEqual(TestQueryUpdateModel.objects.count(), 0)
def test_update_list_append_with_empty_list(self): us = UpdateStatement('table') us.add_update(List(Text, db_field='a'), [], 'append') self.assertFalse(us.assignments)
def test_update_set_add(self): us = UpdateStatement('table') us.add_assignment_clause( SetUpdateClause('a', set((1, )), operation='add')) self.assertEqual(six.text_type(us), 'UPDATE table SET "a" = "a" + %(0)s')
def test_update_set_add(self): us = UpdateStatement('table') us.add_update(Set(Text, db_field='a'), set((1, )), 'add') self.assertEqual(six.text_type(us), 'UPDATE table SET "a" = "a" + %(0)s')
def test_update_empty_set_removal_does_not_assign(self): us = UpdateStatement('table') us.add_assignment_clause( SetUpdateClause('a', set(), operation='remove')) self.assertEqual(six.text_type(us), 'UPDATE table SET "a" = "a" - %(0)s')
def test_update_list_prepend_with_empty_list(self): us = UpdateStatement('table') us.add_assignment_clause(ListUpdateClause('a', List.Quoter([]), operation='prepend')) self.assertEqual(six.text_type(us), 'UPDATE table SET "a" = %(0)s + "a"')
def test_update_set_add(self): us = UpdateStatement('table') us.add_assignment_clause(SetUpdateClause('a', set((1,)), operation='add')) self.assertEqual(six.text_type(us), 'UPDATE table SET "a" = "a" + %(0)s')