@@ -1624,79 +1624,101 @@ def create_id_sequence(cr, table, set_as_default=True):
1624
1624
)
1625
1625
1626
1626
1627
- def update_table_from_dict (cr , table , mapping , key_col = "id" , bucket_size = DEFAULT_BUCKET_SIZE ):
1627
+ def update_table_from_dict (cr , table , columns , mapping , key_col = "id" , bucket_size = DEFAULT_BUCKET_SIZE ):
1628
1628
"""
1629
- Update table's rows based on mapping.
1629
+ Update table based on mapping.
1630
1630
1631
- Efficiently updates rows in a table by mapping an identifier column (`key_col`) value to the new values for the provided set of columns.
1631
+ Each `mapping` entry defines the new values for the specified `columns` for the row(s) whose `key_col` value matches the key.
1632
+
1633
+ .. important::
1634
+
1635
+ `columns` can be either a string or a list of strings. Crucially the values of the provided `mapping` must have the same type
1636
+ and, if a list, the same **dimensionality** and **order**. Otherwise:
1637
+
1638
+ .. code-block:: python
1639
+
1640
+ columns = ["int_col", "text_col"]
1641
+ mapping = {
1642
+ 1: [123, "foo", True], # third value is ignored
1643
+ 2: [456], # text_col is set to NULL
1644
+ 3: ["bar", 789], # will attempt to set `int_col` to "bar" and `text_col` to 789
1645
+ }
1632
1646
1633
1647
.. example::
1634
1648
1635
1649
.. code-block:: python
1636
1650
1651
+ # single column update
1637
1652
util.update_table_from_dict(
1638
1653
cr,
1639
1654
"account_move",
1655
+ "always_tax_eligible",
1640
1656
{
1641
- 1: {"closing_return_id": 2, "always_tax_eligible": True} ,
1642
- 2: {"closing_return_id": 3, "always_tax_eligible": False} ,
1657
+ 1: True,
1658
+ 2: False,
1643
1659
},
1644
1660
)
1645
1661
1646
- :param str table: the table to update
1647
- :param dict[any, dict[str, any]] mapping: mapping of `key_col` identifiers to maps of column names to their new value
1648
-
1649
- .. example::
1650
-
1651
- .. code-block:: python
1652
-
1653
- mapping = {
1654
- 1: {"col1": 123, "col2": "foo"},
1655
- 2: {"col1": 456, "col2": "bar"},
1656
- }
1657
-
1658
- .. warning::
1659
-
1660
- All maps should have the exact same set of keys (column names). The following
1661
- example would behave unpredictably:
1662
-
1663
- .. code-block:: python
1662
+ # multi-column update
1663
+ util.update_table_from_dict(
1664
+ cr,
1665
+ "account_move",
1666
+ ["closing_return_id", "always_tax_eligible"],
1667
+ {
1668
+ 1: [2, True],
1669
+ 2: [3, False],
1670
+ },
1671
+ )
1664
1672
1665
- # WRONG
1666
- mapping = {
1667
- 1: {"col1": 123, "col2": "foo"},
1668
- 2: {"col1": 456},
1669
- }
1673
+ .. warning::
1670
1674
1671
- Either resulting in `col2` updates being ignored or setting it to NULL for row 2 .
1675
+ As a side effect, the cursor may be committed .
1672
1676
1673
- :param str key_col: The column to match the key against (`id` by default)
1677
+ :param str table: database's table to perform the update of
1678
+ :param str | list[str] columns: table's columns to update
1679
+ :param dict[any, any | list[any]] mapping: matches values of `key_col` to the new `columns` values
1680
+ :param str key_col: column to match against keys of `mapping`
1674
1681
:param int bucket_size: maximum number of rows to update per single query
1675
1682
"""
1676
- if not mapping :
1677
- return
1678
-
1679
1683
_validate_table (table )
1684
+ if not columns or not mapping :
1685
+ return
1680
1686
1681
- column_names = list (next (iter (mapping .values ())).keys ())
1682
- query = cr .mogrify (
1683
- format_query (
1687
+ if isinstance (columns , str ):
1688
+ query = format_query (
1684
1689
cr ,
1685
1690
"""
1686
1691
UPDATE {table} t
1687
- SET ({columns_list}) = ROW({values_list})
1688
- FROM JSONB_EACH(%%s) m
1692
+ SET {col} = m.value::{col_type}
1693
+ FROM JSONB_EACH_TEXT(%s) m
1694
+ WHERE t.{key_col}::text = m.key
1695
+ """ ,
1696
+ table = table ,
1697
+ col = ColumnList .from_unquoted (cr , [columns ]),
1698
+ col_type = column_type (cr , table , columns ),
1699
+ key_col = key_col ,
1700
+ )
1701
+ else :
1702
+ query = format_query (
1703
+ cr ,
1704
+ """
1705
+ UPDATE {table} t
1706
+ SET ({cols}) = ROW({cols_values})
1707
+ FROM JSONB_EACH(%s) m
1689
1708
WHERE t.{key_col}::varchar = m.key
1690
1709
""" ,
1691
1710
table = table ,
1692
- columns_list = ColumnList .from_unquoted (cr , column_names ),
1693
- values_list = sql .SQL (", " ).join (
1694
- sql .SQL ("(m.value->>%s)::{}" ).format (sql .SQL (column_type (cr , table , col ))) for col in column_names
1711
+ cols = ColumnList .from_unquoted (cr , columns ),
1712
+ cols_values = SQLStr (
1713
+ ", " .join (
1714
+ "(m.value->>{:d})::{}" .format (
1715
+ col_idx , sql .Identifier (column_type (cr , table , col_name )).as_string (cr ._cnx )
1716
+ )
1717
+ for col_idx , col_name in enumerate (columns )
1718
+ )
1695
1719
),
1696
1720
key_col = key_col ,
1697
- ),
1698
- column_names ,
1699
- )
1721
+ )
1700
1722
1701
1723
if len (mapping ) <= 1.1 * bucket_size :
1702
1724
cr .execute (query , [json .dumps (mapping )])
0 commit comments