diff --git a/pydal/adapters/base.py b/pydal/adapters/base.py index d4e21ac03..c5e384283 100644 --- a/pydal/adapters/base.py +++ b/pydal/adapters/base.py @@ -288,10 +288,18 @@ def _parse( #: otherwise we set the value in extras else: #: fields[j] may be None if only 'colnames' was specified in db.executesql() - f_itype, ftype = ( - fields[j] and [fields[j]._itype, fields[j].type] or [None, None] - ) + field = fields[j] + f_itype, ftype = (field and [field._itype, field.type] or [None, None]) value = self.parse_value(value, f_itype, ftype, blob_decode) + # for aliased fields use the aliased name + if isinstance(field, Expression) and field.op == self.dialect._as: + colname = field.second + # if the alias is a tablename.fieldname add the column to the table + if field.tablename: + if field.tablename not in new_row: + new_row[field.tablename] = self.db.Row() + new_row[field.tablename][colname] = value + continue extras[colname] = value if not fields[j]: new_row[colname] = value @@ -300,7 +308,7 @@ def _parse( if new_column_match is not None: new_column_name = new_column_match.group(1) new_row[new_column_name] = value - #: add extras if needed (eg. operations results) + #: add extra if not empty if extras: new_row["_extra"] = extras #: add virtuals diff --git a/pydal/objects.py b/pydal/objects.py index 6339ee134..500a9f588 100644 --- a/pydal/objects.py +++ b/pydal/objects.py @@ -1483,12 +1483,23 @@ def __new__(cls, *args, **kwargs): new_cls = super(Expression, cls).__new__(cls) return new_cls - def __init__(self, db, op, first=None, second=None, type=None, **optional_args): + def __init__( + self, + db, + op, + first=None, + second=None, + type=None, + tablename=None, + **optional_args, + ): self.db = db self.op = op self.first = first self.second = second self._table = getattr(first, "_table", None) + # this used to place an expression with alias inside a table + self.tablename = tablename if not type and first and hasattr(first, "type"): self.type = first.type else: @@ -1592,6 +1603,11 @@ def __getitem__(self, i): return self[i : i + 1] def __str__(self): + if self.op == self._dialect._as: + return self.second + return str(self.db._adapter.expand(self, self.type)) + + def __repr__(self): return str(self.db._adapter.expand(self, self.type)) def __or__(self, other): # for use in sortby @@ -1733,12 +1749,11 @@ def contains(self, value, all=False, case_sensitive=False): ) def with_alias(self, alias): - return Expression(self.db, self._dialect._as, self, alias, self.type) - - @property - def alias(self): - if self.op == self._dialect._as: - return self.second + if "." in alias: + tablename, alias = alias.split(".", 1) + else: + tablename = None + return Expression(self.db, self._dialect._as, self, alias, self.type, tablename) # GIS expressions @@ -3359,10 +3374,16 @@ def __getslice__(self, a, b): ) def __getitem__(self, i): + """ + This extract a row from a set of rows and tries to compactify + - if there are columns from more than one table, it returns a dict of dicts + - if there are columns for a single table it returns a single dict + - if there are extra columns (including aliased) and a single table, adds the extra columns to the one table + """ if isinstance(i, slice): return self.__getslice__(i.start, i.stop) row = self.records[i] - keys = list(row.keys()) + keys = list(row) if self.compact and len(keys) == 1 and keys[0] != "_extra": return row[keys[0]] return row @@ -3604,7 +3625,7 @@ def render(self, i=None, fields=None): representer in DAL instance" ) row = copy.deepcopy(self.records[i]) - keys = list(row.keys()) + keys = list(row) if not fields: fields = [f for f in self.fields if isinstance(f, Field) and f.represent] for field in fields: @@ -3676,7 +3697,7 @@ def __next__(self): # in # # normally accomplished by Rows.__get_item__ - keys = list(row.keys()) + keys = list(row) if len(keys) == 1 and keys[0] != "_extra": row = row[keys[0]] return row