|
17 | 17 | from __future__ import annotations
|
18 | 18 |
|
19 | 19 | import contextlib
|
| 20 | +import itertools |
20 | 21 | import os
|
21 | 22 | import re
|
22 | 23 | import sqlite3
|
@@ -580,42 +581,53 @@ def __delattr__(self, key):
|
580 | 581 | # Database interaction (CRUD methods).
|
581 | 582 |
|
582 | 583 | def store(self, fields: Optional[Iterable[str]] = None):
|
583 |
| - """Save the object's metadata into the library database. |
584 |
| - :param fields: the fields to be stored. If not specified, all fields |
585 |
| - will be. |
586 | 584 | """
|
587 |
| - if fields is None: |
588 |
| - fields = self._fields.keys() |
589 |
| - fields = set(fields) - {"id"} |
| 585 | + Save the object's metadata into the library database. |
| 586 | +
|
| 587 | + :param fields: the fields to be stored (default: all of them). |
| 588 | + Only non-flexible fields (excluding `"id"`) can be specified. |
| 589 | + """ |
| 590 | + |
590 | 591 | db = self._check_db()
|
591 | 592 |
|
592 |
| - # Build assignments for query. |
593 |
| - dirty_fields = list(fields & self._dirty) |
594 |
| - self._dirty -= fields |
595 |
| - assignments = ",".join(f"{k}=?" for k in dirty_fields) |
596 |
| - subvars = [self._type(k).to_sql(self[k]) for k in dirty_fields] |
| 593 | + # Extract known sets of keys from the table. |
| 594 | + known_fields = set(self._fields.keys()) |
| 595 | + known_flex_fields = set(self._values_flex.keys()) |
| 596 | + |
| 597 | + # Normalize the 'fields' parameter. |
| 598 | + fields = set(fields or known_fields) - {"id"} |
| 599 | + assert ( |
| 600 | + len(fields & known_flex_fields) == 0 |
| 601 | + ), "`fields` cannot contain flexible fields" |
| 602 | + |
| 603 | + # Compute how various fields were modified. |
| 604 | + dirty_fields = fields & self._dirty |
| 605 | + dirty_flex_fields = known_flex_fields & self._dirty |
| 606 | + removed_flex_fields = self._dirty - fields - known_flex_fields |
597 | 607 |
|
598 | 608 | with db.transaction() as tx:
|
599 |
| - # Main table update. |
600 |
| - if assignments: |
| 609 | + # Update non-flexible fields. |
| 610 | + if dirty_fields: |
| 611 | + # NOTE: the order of iteration of 'dirty_fields' will not change |
| 612 | + # between the two statements, since the set is not modified. |
| 613 | + assignments = ",".join(f"{k}=?" for k in dirty_fields) |
| 614 | + values = [self._type(k).to_sql(self[k]) for k in dirty_fields] |
601 | 615 | query = f"UPDATE {self._table} SET {assignments} WHERE id=?"
|
602 |
| - subvars.append(self.id) |
603 |
| - tx.mutate(query, subvars) |
| 616 | + tx.mutate(query, [*values, self.id]) |
604 | 617 |
|
605 | 618 | # Modified/added flexible attributes.
|
606 |
| - flex_fields = set(self._values_flex.keys()) |
607 |
| - dirty_flex_fields = list(flex_fields & self._dirty) |
608 |
| - self._dirty -= flex_fields |
| 619 | + # TODO: Use the underlying 'executemany()' function here. |
609 | 620 | for key in dirty_flex_fields:
|
610 | 621 | tx.mutate(
|
611 | 622 | f"INSERT INTO {self._flex_table} "
|
612 | 623 | "(entity_id, key, value) "
|
613 |
| - "VALUES (?, ?, ?);", |
| 624 | + "VALUES (?, ?, ?)", |
614 | 625 | (self.id, key, self._values_flex[key]),
|
615 | 626 | )
|
616 | 627 |
|
617 | 628 | # Deleted flexible attributes.
|
618 |
| - for key in self._dirty: |
| 629 | + # TODO: Use the underlying 'executemany()' function here. |
| 630 | + for key in removed_flex_fields: |
619 | 631 | tx.mutate(
|
620 | 632 | f"DELETE FROM {self._flex_table} WHERE entity_id=? AND key=?",
|
621 | 633 | (self.id, key),
|
|
0 commit comments