def build_select_expression(self) -> Tuple[sqlalchemy.sql.select, List[str]]: """ Main entry point from outside (after proper initialization). Extracts columns list to fetch, construct all required joins for select related, then applies all conditional and sort clauses. Returns ready to run query with all joins and clauses. :return: ready to run query with all joins and clauses. :rtype: sqlalchemy.sql.selectable.Select """ self_related_fields = self.model_cls.own_table_columns( model=self.model_cls, excludable=self.excludable, use_alias=True, ) self.columns = self.model_cls.Meta.alias_manager.prefixed_columns( "", self.table, self_related_fields ) self.apply_order_bys_for_primary_model() if self._pagination_query_required(): self.select_from = self._build_pagination_subquery() else: self.select_from = self.table related_models = group_related_list(self._select_related) for related in related_models: remainder = None if isinstance(related_models, dict) and related_models[related]: remainder = related_models[related] sql_join = SqlJoin( used_aliases=self.used_aliases, select_from=self.select_from, columns=self.columns, excludable=self.excludable, order_columns=self.order_columns, sorted_orders=self.sorted_orders, main_model=self.model_cls, relation_name=related, relation_str=related, related_models=remainder, ) ( self.used_aliases, self.select_from, self.columns, self.sorted_orders, ) = sql_join.build_join() expr = sqlalchemy.sql.select(self.columns) expr = expr.select_from(self.select_from) expr = self._apply_expression_modifiers(expr) # print("\n", expr.compile(compile_kwargs={"literal_binds": True})) self._reset_query_parameters() return expr
def test_group_related_list(): given = [ "friends__least_favourite_game", "least_favourite_game", "friends", "favourite_game", "friends__favourite_game", ] expected = { "least_favourite_game": [], "favourite_game": [], "friends": ["favourite_game", "least_favourite_game"], } assert group_related_list(given) == expected
def from_row( # noqa CCR001 cls: Type[T], row: sqlalchemy.engine.ResultProxy, select_related: List = None, related_models: Any = None, previous_model: Type[T] = None, source_model: Type[T] = None, related_name: str = None, fields: Optional[Union[Dict, Set]] = None, exclude_fields: Optional[Union[Dict, Set]] = None, current_relation_str: str = None, ) -> Optional[T]: """ Model method to convert raw sql row from database into ormar.Model instance. Traverses nested models if they were specified in select_related for query. Called recurrently and returns model instance if it's present in the row. Note that it's processing one row at a time, so if there are duplicates of parent row that needs to be joined/combined (like parent row in sql join with 2+ child rows) instances populated in this method are later combined in the QuerySet. Other method working directly on raw database results is in prefetch_query, where rows are populated in a different way as they do not have nested models in result. :param current_relation_str: name of the relation field :type current_relation_str: str :param source_model: model on which relation was defined :type source_model: Type[Model] :param row: raw result row from the database :type row: sqlalchemy.engine.result.ResultProxy :param select_related: list of names of related models fetched from database :type select_related: List :param related_models: list or dict of related models :type related_models: Union[List, Dict] :param previous_model: internal param for nested models to specify table_prefix :type previous_model: Model class :param related_name: internal parameter - name of current nested model :type related_name: str :param fields: fields and related model fields to include if provided only those are included :type fields: Optional[Union[Dict, Set]] :param exclude_fields: fields and related model fields to exclude excludes the fields even if they are provided in fields :type exclude_fields: Optional[Union[Dict, Set]] :return: returns model if model is populated from database :rtype: Optional[Model] """ item: Dict[str, Any] = {} select_related = select_related or [] related_models = related_models or [] table_prefix = "" if select_related: source_model = cls related_models = group_related_list(select_related) rel_name2 = related_name if (previous_model and related_name and issubclass(previous_model.Meta.model_fields[related_name], ManyToManyField)): through_field = previous_model.Meta.model_fields[related_name] if (through_field.self_reference and related_name == through_field.self_reference_primary): rel_name2 = through_field.default_source_field_name( ) # type: ignore else: rel_name2 = through_field.default_target_field_name( ) # type: ignore previous_model = through_field.through # type: ignore if previous_model and rel_name2: if current_relation_str and "__" in current_relation_str and source_model: table_prefix = cls.Meta.alias_manager.resolve_relation_alias( from_model=source_model, relation_name=current_relation_str) if not table_prefix: table_prefix = cls.Meta.alias_manager.resolve_relation_alias( from_model=previous_model, relation_name=rel_name2) item = cls.populate_nested_models_from_row( item=item, row=row, related_models=related_models, fields=fields, exclude_fields=exclude_fields, current_relation_str=current_relation_str, source_model=source_model, ) item = cls.extract_prefixed_table_columns( item=item, row=row, table_prefix=table_prefix, fields=fields, exclude_fields=exclude_fields, ) instance: Optional[T] = None if item.get(cls.Meta.pkname, None) is not None: item["__excluded__"] = cls.get_names_to_exclude( fields=fields, exclude_fields=exclude_fields) instance = cls(**item) instance.set_save_status(True) return instance
def from_row( # noqa: CFQ002 cls, row: sqlalchemy.engine.ResultProxy, source_model: Type["Model"], select_related: List = None, related_models: Any = None, related_field: Type["ForeignKeyField"] = None, excludable: ExcludableItems = None, current_relation_str: str = "", proxy_source_model: Optional[Type["Model"]] = None, used_prefixes: List[str] = None, ) -> Optional["Model"]: """ Model method to convert raw sql row from database into ormar.Model instance. Traverses nested models if they were specified in select_related for query. Called recurrently and returns model instance if it's present in the row. Note that it's processing one row at a time, so if there are duplicates of parent row that needs to be joined/combined (like parent row in sql join with 2+ child rows) instances populated in this method are later combined in the QuerySet. Other method working directly on raw database results is in prefetch_query, where rows are populated in a different way as they do not have nested models in result. :param used_prefixes: list of already extracted prefixes :type used_prefixes: List[str] :param proxy_source_model: source model from which querysetproxy is constructed :type proxy_source_model: Optional[Type["ModelRow"]] :param excludable: structure of fields to include and exclude :type excludable: ExcludableItems :param current_relation_str: name of the relation field :type current_relation_str: str :param source_model: model on which relation was defined :type source_model: Type[Model] :param row: raw result row from the database :type row: sqlalchemy.engine.result.ResultProxy :param select_related: list of names of related models fetched from database :type select_related: List :param related_models: list or dict of related models :type related_models: Union[List, Dict] :param related_field: field with relation declaration :type related_field: Type[ForeignKeyField] :return: returns model if model is populated from database :rtype: Optional[Model] """ item: Dict[str, Any] = {} select_related = select_related or [] related_models = related_models or [] table_prefix = "" used_prefixes = used_prefixes if used_prefixes is not None else [] excludable = excludable or ExcludableItems() if select_related: related_models = group_related_list(select_related) if related_field: if related_field.is_multi: previous_model = related_field.through else: previous_model = related_field.owner table_prefix = cls.Meta.alias_manager.resolve_relation_alias( from_model=previous_model, relation_name=related_field.name) if not table_prefix or table_prefix in used_prefixes: manager = cls.Meta.alias_manager table_prefix = manager.resolve_relation_alias_after_complex( source_model=source_model, relation_str=current_relation_str, relation_field=related_field, ) used_prefixes.append(table_prefix) item = cls._populate_nested_models_from_row( item=item, row=row, related_models=related_models, excludable=excludable, current_relation_str=current_relation_str, source_model=source_model, # type: ignore proxy_source_model=proxy_source_model, # type: ignore table_prefix=table_prefix, used_prefixes=used_prefixes, ) item = cls.extract_prefixed_table_columns(item=item, row=row, table_prefix=table_prefix, excludable=excludable) instance: Optional["Model"] = None if item.get(cls.Meta.pkname, None) is not None: item["__excluded__"] = cls.get_names_to_exclude( excludable=excludable, alias=table_prefix) instance = cast("Model", cls(**item)) instance.set_save_status(True) return instance