|
7 | 7 | import uuid
|
8 | 8 | import warnings
|
9 | 9 | from contextlib import contextmanager
|
10 |
| -from functools import reduce |
| 10 | +from functools import partial, reduce |
11 | 11 | from multiprocessing import cpu_count
|
12 | 12 |
|
13 | 13 | try:
|
14 | 14 | from concurrent.futures import ThreadPoolExecutor
|
15 | 15 | except ImportError:
|
16 | 16 | ThreadPoolExecutor = None
|
17 | 17 |
|
| 18 | +try: |
| 19 | + from collections import UserList |
| 20 | +except ImportError: |
| 21 | + from UserList import UserList |
| 22 | + |
18 | 23 | import psycopg2
|
19 | 24 | from psycopg2 import sql
|
20 | 25 |
|
@@ -670,21 +675,78 @@ def get_depending_views(cr, table, column):
|
670 | 675 | return cr.fetchall()
|
671 | 676 |
|
672 | 677 |
|
| 678 | +class ColumnList(UserList, sql.Composable): |
| 679 | + """ |
| 680 | + Encapsulate a list of elements that represent column names. |
| 681 | + The resulting object can be rendered as string with leading/trailing comma or an alias. |
| 682 | +
|
| 683 | + Examples: |
| 684 | + ``` |
| 685 | + >>> columns = ColumnList(["id", "field_Yx"], ["id", '"field_Yx"']) |
| 686 | +
|
| 687 | + >>> columns.using(alias="t").as_string(cr._cnx) |
| 688 | + '"t"."id", "t"."field_Yx"' |
| 689 | +
|
| 690 | + >>> columns.using(leading_comma=True).as_string(cr._cnx) |
| 691 | + ', "id", "field_Yx"' |
| 692 | +
|
| 693 | + >>> util.format(cr, "SELECT {} t.name FROM table t", columns.using(alias="t", trailing_comma=True)) |
| 694 | + 'SELECT "t"."id", "t"."field_Yx", t.name FROM table t' |
| 695 | + ``` |
| 696 | + """ |
| 697 | + |
| 698 | + def __init__(self, list_=(), quoted=()): |
| 699 | + assert len(list_) == len(quoted) |
| 700 | + self._unquoted_columns = list(list_) |
| 701 | + super(ColumnList, self).__init__(quoted) |
| 702 | + self._leading_comma = False |
| 703 | + self._trailing_comma = False |
| 704 | + self._alias = None |
| 705 | + |
| 706 | + def using(self, leading_comma=False, trailing_comma=False, alias=None): |
| 707 | + if self._leading_comma is leading_comma and self._trailing_comma is trailing_comma and self._alias == alias: |
| 708 | + return self |
| 709 | + new = ColumnList(self._unquoted_columns, self.data) |
| 710 | + new._leading_comma = leading_comma |
| 711 | + new._trailing_comma = trailing_comma |
| 712 | + new._alias = alias |
| 713 | + return new |
| 714 | + |
| 715 | + def as_string(self, context): |
| 716 | + head = sql.SQL(", " if self._leading_comma and self else "") |
| 717 | + tail = sql.SQL("," if self._trailing_comma and self else "") |
| 718 | + |
| 719 | + if not self._alias: |
| 720 | + builder = sql.Identifier |
| 721 | + elif hasattr(sql.Identifier, "strings"): |
| 722 | + builder = partial(sql.Identifier, self._alias) |
| 723 | + else: |
| 724 | + # older psycopg2 versions, doesn't support passing multiple strings to the constructor |
| 725 | + builder = lambda elem: sql.SQL(".").join(sql.Identifier(self._alias) + sql.Identifier(elem)) |
| 726 | + |
| 727 | + body = sql.SQL(", ").join(builder(elem) for elem in self._unquoted_columns) |
| 728 | + return sql.Composed([head, body, tail]).as_string(context) |
| 729 | + |
| 730 | + def iter_unquoted(self): |
| 731 | + return iter(self._unquoted_columns) |
| 732 | + |
| 733 | + |
673 | 734 | def get_columns(cr, table, ignore=("id",)):
|
674 | 735 | """return the list of columns in table (minus ignored ones)"""
|
675 | 736 | _validate_table(table)
|
676 | 737 |
|
677 | 738 | cr.execute(
|
678 | 739 | """
|
679 |
| - SELECT quote_ident(column_name) |
| 740 | + SELECT coalesce(array_agg(column_name::varchar ORDER BY column_name), ARRAY[]::varchar[]), |
| 741 | + coalesce(array_agg(quote_ident(column_name) ORDER BY column_name), ARRAY[]::varchar[]) |
680 | 742 | FROM information_schema.columns
|
681 | 743 | WHERE table_schema = 'public'
|
682 | 744 | AND table_name = %s
|
683 | 745 | AND column_name != ALL(%s)
|
684 | 746 | """,
|
685 | 747 | [table, list(ignore)],
|
686 | 748 | )
|
687 |
| - return [c for c, in cr.fetchall()] |
| 749 | + return ColumnList(*cr.fetchone()) |
688 | 750 |
|
689 | 751 |
|
690 | 752 | def rename_table(cr, old_table, new_table, remove_constraints=True):
|
|
0 commit comments