Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Support for bulk_update #4

Closed
Arisophy opened this issue Mar 12, 2021 · 2 comments
Closed

Support for bulk_update #4

Arisophy opened this issue Mar 12, 2021 · 2 comments

Comments

@Arisophy
Copy link
Owner

CPkQuerySet.bulk_update fails.
Need to change UpdateQuery To CPkUpdateQuery

    def update(self, **kwargs):
        """
        Update all elements in the current QuerySet, setting all the given
        fields to the appropriate values.
        """
        self._not_support_combined_queries('update')
        assert not self.query.is_sliced, \
            "Cannot update a query once a slice has been taken."
        self._for_write = True
        query = self.query.chain(sql.UpdateQuery)
        query.add_update_values(kwargs)
        # Clear any annotations so that they won't be present in subqueries.
        query.annotations = {}
        with transaction.mark_for_rollback_on_error(using=self.db):
            rows = query.get_compiler(self.db).execute_sql(CURSOR)
        self._result_cache = None
        return rows
    update.alters_data = True
@Arisophy
Copy link
Owner Author

Add "chain" code to change UpdateQuery to CpkUpdateQuery.

cpkquery.py

class CPkQueryMixin():
:
    ###########################
    # override
    ###########################

    def chain(self, klass=None):
        cpk_klass = klass
        if klass == Query:
            cpk_klass = CPkQuery
        elif klass == UpdateQuery:
            cpk_klass = CPkUpdateQuery
        elif klass == DeleteQuery:
            cpk_klass = CPkDeleteQuery

        return super().chain(klass=cpk_klass)

This is not the main problem. The problem is "pk=obj.pk" in When class.

query.py

class QuerySet:
:
    def bulk_update(self, objs, fields, batch_size=None):
        """
        Update the given fields in each of the given objects in the database.
        """
        if batch_size is not None and batch_size < 0:
            raise ValueError('Batch size must be a positive integer.')
        if not fields:
            raise ValueError('Field names must be given to bulk_update().')
        objs = tuple(objs)
        if any(obj.pk is None for obj in objs):
            raise ValueError('All bulk_update() objects must have a primary key set.')
        fields = [self.model._meta.get_field(name) for name in fields]
        if any(not f.concrete or f.many_to_many for f in fields):
            raise ValueError('bulk_update() can only be used with concrete fields.')
        if any(f.primary_key for f in fields):
            raise ValueError('bulk_update() cannot be used with primary key fields.')
        if not objs:
            return
        # PK is used twice in the resulting update query, once in the filter
        # and once in the WHEN. Each field will also have one CAST.
        max_batch_size = connections[self.db].ops.bulk_batch_size(['pk', 'pk'] + fields, objs)
        batch_size = min(batch_size, max_batch_size) if batch_size else max_batch_size
        requires_casting = connections[self.db].features.requires_casted_case_in_updates
        batches = (objs[i:i + batch_size] for i in range(0, len(objs), batch_size))
        updates = []
        for batch_objs in batches:
            update_kwargs = {}
            for field in fields:
                when_statements = []
                for obj in batch_objs:
                    attr = getattr(obj, field.attname)
                    if not isinstance(attr, Expression):
                        attr = Value(attr, output_field=field)
                    when_statements.append(When(pk=obj.pk, then=attr))   # This is the problem!! Need to treate CompositePK
                case_statement = Case(*when_statements, output_field=field)
                if requires_casting:
                    case_statement = Cast(case_statement, output_field=field)
                update_kwargs[field.attname] = case_statement
            updates.append(([obj.pk for obj in batch_objs], update_kwargs))
        with transaction.atomic(using=self.db, savepoint=False):
            for pks, update_kwargs in updates:
                self.filter(pk__in=pks).update(**update_kwargs)
    bulk_update.alters_data = True

@Arisophy
Copy link
Owner Author

Release v1.0.2

1. Override QuerySet.bulk_update to change pk lookup for When class

cpkquery.py

class CPkQuerySet(QuerySet):
    ###########################
    # override
    ###########################
:
    def bulk_update(self, objs, fields, batch_size=None):
:
                    # CHANGE S
                    #when_statements.append(When(pk=obj.pk, then=attr))
                    lookups = obj.get_pk_lookups()
                    when_statements.append(When(**lookups, then=attr))
                    # CHANGE E
:

2. Add get_pk_lookups method to CpkModel

cpkmodel.py

class CPkModelMixin:
:
    def get_pk_lookups(self):
        if self.has_compositepk:
            keys = self.pkeys
            vals = self.pkvals
            return { key.attname:val for key, val in zip(keys, vals)}
        else:
            return { 'pk':self.pk }
:

# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant