diff --git a/README.md b/README.md index e1c24392..2bc61e76 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,7 @@ Generates GraphQL types, inputs, queries and resolvers directly from SQLAlchemy - [Mapping SQLAlchemy Models](#mapping-sqlalchemy-models) - [Resolver Generation](#resolver-generation) - [Pagination](#pagination) +- [Ordering](#ordering) - [Filtering](#filtering) - [Aggregations](#aggregations) - [Mutations](#mutations) @@ -567,9 +568,11 @@ def farms(self) -> str: Strawchemy supports offset-based pagination out of the box.
-Pagination example: +Pagination examples -Enable pagination on fields: +### Field-Level Pagination + +Enable pagination on specific fields: ```python from strawchemy.schema.pagination import DefaultOffsetPagination @@ -577,10 +580,13 @@ from strawchemy.schema.pagination import DefaultOffsetPagination @strawberry.type class Query: - # Enable pagination with default settings + # Enable pagination with default settings (limit=100, offset=0) users: list[UserType] = strawchemy.field(pagination=True) - # Customize pagination defaults - users_custom_pagination: list[UserType] = strawchemy.field(pagination=DefaultOffsetPagination(limit=20)) + + # Customize pagination defaults for this specific field + users_custom: list[UserType] = strawchemy.field( + pagination=DefaultOffsetPagination(limit=20, offset=10) + ) ``` In your GraphQL queries, you can use the `offset` and `limit` parameters: @@ -594,10 +600,40 @@ In your GraphQL queries, you can use the `offset` and `limit` parameters: } ``` -You can also enable pagination for nested relationships: +### Config-Level Pagination + +Enable pagination globally for all list fields: ```python -@strawchemy.type(User, include="all", child_pagination=True) +from strawchemy import Strawchemy, StrawchemyConfig + +strawchemy = Strawchemy( + StrawchemyConfig( + "postgresql", + pagination="all", # Enable on all list fields + pagination_default_limit=100, # Default limit + pagination_default_offset=0, # Default offset + ) +) + + +@strawchemy.type(User, include="all") +class UserType: + pass + + +@strawberry.type +class Query: + # This field automatically has pagination enabled + users: list[UserType] = strawchemy.field() +``` + +### Type-level pagination + +Enable pagination for nested relationships from a specific type: + +```python +@strawchemy.type(User, include="all", paginate="all") class UserType: pass ``` @@ -619,6 +655,118 @@ Then in your GraphQL queries:
+## Ordering + +Strawchemy provides flexible ordering capabilities for query results. + +
+Ordering examples + +### Field-Level Ordering + +Define ordering inputs and use them on specific fields: + +```python +# Create order by input +@strawchemy.order(User, include="all") +class UserOrderBy: + pass + + +@strawberry.type +class Query: + users: list[UserType] = strawchemy.field(order_by=UserOrderBy) +``` + +Query with ordering: + +```graphql +{ + users(orderBy: [{ name: ASC }, { createdAt: DESC }]) { + id + name + createdAt + } +} +``` + +Available ordering options: + +- `ASC` - Ascending order +- `DESC` - Descending order +- `ASC_NULLS_FIRST` - Ascending with nulls first +- `ASC_NULLS_LAST` - Ascending with nulls last +- `DESC_NULLS_FIRST` - Descending with nulls first +- `DESC_NULLS_LAST` - Descending with nulls last + +### Type-Level Ordering + +Enable ordering automatically on a type: + +```python +@strawchemy.type(User, include="all", order="all") +class UserType: + pass +``` + +This automatically generates and applies an order by input for all fields using this type. + +### Config-Level Ordering + +Enable ordering globally for all list fields: + +```python +from strawchemy import Strawchemy, StrawchemyConfig + +strawchemy = Strawchemy( + StrawchemyConfig( + "postgresql", + order_by="all", # Enable ordering on all list fields + ) +) + + +@strawchemy.type(User, include="all") +class UserType: + pass + + +@strawberry.type +class Query: + # This field automatically has ordering enabled + users: list[UserType] = strawchemy.field() +``` + +With this configuration, all list fields will automatically have an `orderBy` argument without needing to specify it per +field. + +### Nested Relationship Ordering + +Order nested relationships: + +```python +@strawchemy.type(User, include="all", order="all") +class UserType: + pass +``` + +Query with nested ordering: + +```graphql +{ + users(orderBy: [{ name: ASC }]) { + id + name + posts(orderBy: [{ title: ASC }]) { + id + title + } + } +} +``` + +
+ ## Filtering Strawchemy provides powerful filtering capabilities. @@ -1950,18 +2098,20 @@ Configuration is made by passing a `StrawchemyConfig` to the `Strawchemy` instan ### Configuration Options -| Option | Type | Default | Description | -|----------------------------|-------------------------------------------------------------|----------------------------|------------------------------------------------------------------------------------------------------------------------------------------| -| `dialect` | `SupportedDialect` | | Database dialect to use. Supported dialects are "postgresql", "mysql", "sqlite". | -| `session_getter` | `Callable[[Info], Session]` | `default_session_getter` | Function to retrieve SQLAlchemy session from strawberry `Info` object. By default, it retrieves the session from `info.context.session`. | -| `auto_snake_case` | `bool` | `True` | Automatically convert snake cased names to camel case in GraphQL schema. | -| `repository_type` | `type[Repository] \| StrawchemySyncRepository` | `StrawchemySyncRepository` | Repository class to use for auto resolvers. | -| `filter_overrides` | `OrderedDict[tuple[type, ...], type[SQLAlchemyFilterBase]]` | `None` | Override default filters with custom filters. This allows you to provide custom filter implementations for specific column types. | -| `execution_options` | `dict[str, Any]` | `None` | SQLAlchemy execution options for repository operations. These options are passed to the SQLAlchemy `execution_options()` method. | -| `pagination_default_limit` | `int` | `100` | Default pagination limit when `pagination=True`. | -| `pagination` | `bool` | `False` | Enable/disable pagination on list resolvers by default. | -| `default_id_field_name` | `str` | `"id"` | Name for primary key fields arguments on primary key resolvers. | -| `deterministic_ordering` | `bool` | `True` | Force deterministic ordering for list resolvers. | +| Option | Type | Default | Description | +|-----------------------------|-------------------------------------------------------------|----------------------------|------------------------------------------------------------------------------------------------------------------------------------------| +| `dialect` | `SupportedDialect` | | Database dialect to use. Supported dialects are "postgresql", "mysql", "sqlite". | +| `session_getter` | `Callable[[Info], Session]` | `default_session_getter` | Function to retrieve SQLAlchemy session from strawberry `Info` object. By default, it retrieves the session from `info.context.session`. | +| `auto_snake_case` | `bool` | `True` | Automatically convert snake cased names to camel case in GraphQL schema. | +| `repository_type` | `type[Repository] \| StrawchemySyncRepository` | `StrawchemySyncRepository` | Repository class to use for auto resolvers. | +| `filter_overrides` | `OrderedDict[tuple[type, ...], type[SQLAlchemyFilterBase]]` | `None` | Override default filters with custom filters. This allows you to provide custom filter implementations for specific column types. | +| `execution_options` | `dict[str, Any]` | `None` | SQLAlchemy execution options for repository operations. These options are passed to the SQLAlchemy `execution_options()` method. | +| `default_id_field_name` | `str` | `"id"` | Name for primary key fields arguments on primary key resolvers. | +| `deterministic_ordering` | `bool` | `True` | Force deterministic ordering for list resolvers. | +| `pagination` | `Literal["all"] \| None` | `None` | Enable/disable pagination on list resolvers by default. Set to `"all"` to enable pagination on all list fields. | +| `order_by` | `Literal["all"] \| None` | `None` | Enable/disable order by on list resolvers by default. Set to `"all"` to enable ordering on all list fields. | +| `pagination_default_limit` | `int` | `100` | Default pagination limit when `pagination=True`. | +| `pagination_default_offset` | `int` | `0` | Default pagination offset when `pagination=True`. | ### Example @@ -1980,8 +2130,10 @@ strawchemy = Strawchemy( "postgresql", session_getter=get_session_from_context, auto_snake_case=True, - pagination=True, + pagination="all", pagination_default_limit=50, + pagination_default_offset=0, + order_by="all", default_id_field_name="pk", ) ) diff --git a/examples/testapp/testapp/types.py b/examples/testapp/testapp/types.py index 27d9f3d1..f1b6c940 100644 --- a/examples/testapp/testapp/types.py +++ b/examples/testapp/testapp/types.py @@ -20,7 +20,7 @@ class TicketOrder: ... class TicketFilter: ... -@strawchemy.type(Ticket, include="all", filter_input=TicketFilter, order_by=TicketOrder, override=True) +@strawchemy.type(Ticket, include="all", filter_input=TicketFilter, order=TicketOrder, override=True) class TicketType: ... @@ -55,7 +55,7 @@ class ProjectOrder: ... class ProjectFilter: ... -@strawchemy.type(Project, include="all", filter_input=ProjectFilter, order_by=ProjectOrder, override=True) +@strawchemy.type(Project, include="all", filter_input=ProjectFilter, order=ProjectOrder, override=True) class ProjectType: ... diff --git a/src/strawchemy/config/base.py b/src/strawchemy/config/base.py index f8e7b9cd..a555892e 100644 --- a/src/strawchemy/config/base.py +++ b/src/strawchemy/config/base.py @@ -10,6 +10,7 @@ from strawchemy.utils.strawberry import default_session_getter if TYPE_CHECKING: + from strawchemy.dto.types import FieldIterable, IncludeFields from strawchemy.repository.typing import AnySessionGetter, FilterMap from strawchemy.typing import AnyRepositoryType, SupportedDialect @@ -43,14 +44,24 @@ class StrawchemyConfig: """Override default filters with custom filters.""" execution_options: dict[str, Any] | None = None """SQLAlchemy execution options for strawberry operations.""" - pagination_default_limit: int = 100 - """Default pagination limit when `pagination=True`.""" - pagination: bool = False - """Enable/disable pagination on list resolvers.""" default_id_field_name: str = "id" """Name for primary key fields arguments on primary key resolvers.""" deterministic_ordering: bool = True """Force deterministic ordering for list resolvers.""" + include: IncludeFields = "all" + """Globally included fields.""" + exclude: FieldIterable | None = None + """Globally included fields.""" + pagination: IncludeFields | None = None + """Enable/disable pagination on list resolvers.""" + order_by: IncludeFields | None = None + """Enable/disable order by on list resolvers.""" + distinct_on: IncludeFields | None = None + """Enable/disable order by onelist resolvers.""" + pagination_default_limit: int = 100 + """Default pagination limit when `pagination=True`.""" + pagination_default_offset: int = 0 + """Default pagination offset when `pagination=True`.""" inspector: SQLAlchemyGraphQLInspector = field(init=False) diff --git a/src/strawchemy/dto/base.py b/src/strawchemy/dto/base.py index b78f55bb..e73b1403 100644 --- a/src/strawchemy/dto/base.py +++ b/src/strawchemy/dto/base.py @@ -32,7 +32,7 @@ DTOMissing, DTOSkip, DTOUnset, - ExcludeFields, + FieldIterable, IncludeFields, Purpose, PurposeConfig, @@ -392,17 +392,26 @@ def should_exclude_field( explictly_excluded = node.is_root and field.model_field_name in dto_config.exclude explicitly_included = node.is_root and field.model_field_name in dto_config.include - # Exclude fields not present in init if purpose is write - if dto_config.purpose is Purpose.WRITE and not explicitly_included: - explictly_excluded = explictly_excluded or not field.init + globally_excluded = field.model_field_name in dto_config.global_exclude + globally_included = field.model_field_name in dto_config.global_include + if dto_config.include == "all" and not explictly_excluded: - explicitly_included = True + explicitly_included = globally_included = True + + if dto_config.global_include == "all" and not globally_excluded: + globally_included = True excluded = dto_config.purpose not in field.allowed_purposes + + # Exclude fields not present in init if purpose is write + if dto_config.purpose is Purpose.WRITE and not (explicitly_included or globally_included): + excluded = excluded or not field.init + if node.is_root: excluded = excluded or (explictly_excluded or not explicitly_included) else: - excluded = excluded or explictly_excluded + excluded = excluded or (globally_excluded or not globally_included) + return not has_override and excluded def _resolve_basic_type(self, field: DTOFieldDefinition[ModelT, ModelFieldT], dto_config: DTOConfig) -> Any: @@ -626,6 +635,7 @@ def factory( raise_if_no_fields: bool = False, tags: set[str] | None = None, backend_kwargs: dict[str, Any] | None = None, + no_cache: bool = False, **kwargs: Any, ) -> type[DTOBaseT]: """Build a Data transfer object (DTO) from an SQAlchemy model.""" @@ -640,7 +650,7 @@ def factory( if dto_config.scope == "global": self._scoped_dto_names[self._scoped_cache_key(model, dto_config)] = name - if (dto := self._dto_cache.get(cache_key)) or (dto := self._dto_cache.get(scoped_cache_key)): + if not no_cache and ((dto := self._dto_cache.get(cache_key)) or (dto := self._dto_cache.get(scoped_cache_key))): return self.backend.copy(dto, name) if node.is_root else dto dto = self._factory( @@ -685,7 +695,7 @@ def decorator( model: type[ModelT], purpose: Purpose, include: IncludeFields | None = None, - exclude: ExcludeFields | None = None, + exclude: FieldIterable | None = None, partial: bool | None = None, type_map: Mapping[Any, Any] | None = None, aliases: Mapping[str, str] | None = None, diff --git a/src/strawchemy/dto/strawberry.py b/src/strawchemy/dto/strawberry.py index a4c545b5..4ce86910 100644 --- a/src/strawchemy/dto/strawberry.py +++ b/src/strawchemy/dto/strawberry.py @@ -129,7 +129,9 @@ class _Key(Generic[T]): string. """ - separator: str = ":" + __slots__ = ("_key",) + + separator: ClassVar[str] = ":" def __init__(self, components: Sequence[T | str] | str | None = None) -> None: self._key: str = "" diff --git a/src/strawchemy/dto/types.py b/src/strawchemy/dto/types.py index 9722d3c6..07abe4e1 100644 --- a/src/strawchemy/dto/types.py +++ b/src/strawchemy/dto/types.py @@ -5,9 +5,9 @@ import dataclasses from dataclasses import dataclass, field from enum import Enum -from typing import TYPE_CHECKING, Any, Literal, TypeAlias, final, get_type_hints +from typing import TYPE_CHECKING, Any, Literal, TypeAlias, final, get_type_hints, overload -from typing_extensions import override +from typing_extensions import Self, TypeIs, override from strawchemy.utils.annotation import get_annotations @@ -23,15 +23,17 @@ "DTOScope", "DTOSkip", "DTOUnset", - "ExcludeFields", + "FieldIterable", "IncludeFields", "Purpose", "PurposeConfig", + "cast_include_fields", + "is_fields_iterable", ) DTOScope: TypeAlias = Literal["global", "dto"] -IncludeFields: TypeAlias = "list[str] | set[str] | Literal['all']" -ExcludeFields: TypeAlias = "list[str] | set[str]" +FieldIterable: TypeAlias = "list[str] | set[str] | frozenset[str] | tuple[str, ...]" +IncludeFields: TypeAlias = "FieldIterable | Literal['all']" @final @@ -160,8 +162,12 @@ class DTOConfig: """Configure the DTO for "read" or "write" operations.""" include: IncludeFields = field(default_factory=set) """Explicitly include fields from the generated DTO.""" - exclude: ExcludeFields = field(default_factory=set) + global_include: IncludeFields = field(default_factory=set) + """Explicitly include fields from the generated DTO and all its children.""" + exclude: FieldIterable = field(default_factory=set) """Explicitly exclude fields from the generated DTO. Implies `include="all"`.""" + global_exclude: FieldIterable = field(default_factory=set) + """Explicitly exclude fields from the generated DTO and all its children. Implies `global_include="all"`.""" partial: bool | None = None """Make all field optional.""" partial_default: Any = None @@ -182,14 +188,44 @@ def __post_init__(self) -> None: if self.include and self.include != "all" and self.exclude: msg = "When using `exclude` you must set `include='all' or leave it unset`" raise ValueError(msg) + if self.global_include and self.global_include != "all" and self.global_exclude: + msg = "When using `global_exclude` you must set `global_include='all' or leave it unset`" + raise ValueError(msg) + if self.global_exclude: + self.global_include = "all" if self.exclude: self.include = "all" + @classmethod + def from_include(cls, include: IncludeFields | None = None, purpose: Purpose = Purpose.READ) -> Self: + """Create a DTOConfig from an include specification. + + Factory method for creating a DTOConfig with a simplified interface, converting + an `IncludeFields` specification into a complete configuration object. This is + useful for building configs when only the include/exclude specification matters. + + Args: + include: The field inclusion specification. Can be: + - None: Include no fields (converted to empty set) + - "all": Include all fields + - list or set of field names: Include only these specific fields + Defaults to None. + purpose: The purpose of the DTO being configured (READ, WRITE, or COMPLETE). + Defaults to Purpose.READ. + + Returns: + A new DTOConfig instance with the specified include and purpose settings. + All other configuration parameters use their defaults. + """ + return cls(purpose, include=set() if include is None else include) + def copy_with( self, purpose: Purpose | type[DTOUnset] = DTOUnset, include: IncludeFields | None = None, - exclude: ExcludeFields | None = None, + global_include: IncludeFields | None = None, + exclude: FieldIterable | None = None, + global_exclude: FieldIterable | None = None, partial: bool | None | type[DTOUnset] = DTOUnset, unset_sentinel: Any | type[DTOUnset] = DTOUnset, type_overrides: Mapping[Any, Any] | type[DTOUnset] = DTOUnset, @@ -206,11 +242,18 @@ def copy_with( if include is None and exclude is None: include, exclude = self.include, self.exclude else: - include = include or [] - exclude = exclude or [] + include = include or set() + exclude = exclude or set() + if global_include is None and global_exclude is None: + global_include, global_exclude = self.global_include, self.global_exclude + else: + global_include = global_include or set() + global_exclude = global_exclude or set() return DTOConfig( include=include, exclude=exclude, + global_include=global_include, + global_exclude=global_exclude, purpose=self.purpose if purpose is DTOUnset else purpose, partial=self.partial if partial is DTOUnset else partial, unset_sentinel=self.unset_sentinel if unset_sentinel is DTOUnset else unset_sentinel, @@ -265,3 +308,52 @@ def alias(self, name: str) -> str | None: if self.alias_generator is not None: return self.alias_generator(name) return None + + def is_field_included(self, name: str) -> bool: + """Check if a field should be included based on this configuration. + + This method is used during DTO factory operations to determine which fields + from the source model should be included in the generated DTO. + + Args: + name: The field name to check for inclusion. + + Returns: + True if the field should be included based on the include/exclude rules, + False otherwise. + """ + if self.include == "all": + return name not in self.exclude + if self.global_include == "all": + return name not in self.global_exclude + + included = set(self.include) | set(self.global_include) + excluded = set(self.exclude) | set(self.global_exclude) + return name in included and name not in excluded + + +@overload +def cast_include_fields(value: Literal["all"]) -> Literal["all"]: ... + + +@overload +def cast_include_fields(value: frozenset[str] | set[str] | list[str] | tuple[str, ...] | None) -> frozenset[str]: ... + + +def cast_include_fields(value: IncludeFields | None) -> frozenset[str] | Literal["all"]: + match value: + case None: + return frozenset() + case "all": + return "all" + case _: + return frozenset(value) + + +def is_fields_iterable(value: Any) -> TypeIs[IncludeFields | FieldIterable | None]: + """Test the given value is suitable to be used as either `include` or `exclude` in a DTOConfig.""" + if value == "all" or value is None: + return True + if isinstance(value, str): + return False + return isinstance(value, (frozenset, set, list, tuple)) diff --git a/src/strawchemy/dto/utils.py b/src/strawchemy/dto/utils.py index cd53c789..af815036 100644 --- a/src/strawchemy/dto/utils.py +++ b/src/strawchemy/dto/utils.py @@ -19,7 +19,7 @@ DTOConfig, DTOFieldConfig, DTOScope, - ExcludeFields, + FieldIterable, IncludeFields, Purpose, PurposeConfig, @@ -45,7 +45,9 @@ def config( purpose: Purpose, include: IncludeFields | None = None, - exclude: ExcludeFields | None = None, + exclude: FieldIterable | None = None, + global_include: IncludeFields | None = None, + global_exclude: FieldIterable | None = None, partial: bool | None = None, type_map: Mapping[Any, Any] | None = None, aliases: Mapping[str, str] | None = None, @@ -58,6 +60,10 @@ def config( config.exclude = exclude if include: config.include = include + if global_include: + config.global_include = global_include + if global_exclude: + config.global_exclude = global_exclude if type_map: config.type_overrides = type_map if aliases: diff --git a/src/strawchemy/mapper.py b/src/strawchemy/mapper.py index 5769cded..928c2ed1 100644 --- a/src/strawchemy/mapper.py +++ b/src/strawchemy/mapper.py @@ -2,7 +2,7 @@ import dataclasses from functools import cached_property, partial -from typing import TYPE_CHECKING, Any, TypeVar, overload +from typing import TYPE_CHECKING, Any, ClassVar, TypeVar, overload from strawberry.annotation import StrawberryAnnotation from strawberry.schema.config import StrawberryConfig @@ -25,7 +25,7 @@ UpsertConflictFieldsEnumDTOBackend, ) from strawchemy.schema.field import StrawchemyField -from strawchemy.schema.mutation import types +from strawchemy.schema.mutation import types as mutation_types from strawchemy.schema.mutation.field_builder import MutationFieldBuilder from strawchemy.schema.mutation.fields import ( StrawchemyCreateMutationField, @@ -33,7 +33,6 @@ StrawchemyUpdateMutationField, StrawchemyUpsertMutationField, ) -from strawchemy.schema.pagination import DefaultOffsetPagination from strawchemy.utils.registry import StrawberryRegistry if TYPE_CHECKING: @@ -44,7 +43,9 @@ from strawberry.extensions.field_extension import FieldExtension from strawberry.types.arguments import StrawberryArgument + from strawchemy.dto.types import IncludeFields from strawchemy.repository.typing import QueryHookCallable + from strawchemy.schema.pagination import DefaultOffsetPagination from strawchemy.transpiler.hook import QueryHook from strawchemy.typing import AnyRepositoryType, FilterStatementCallable, MappedGraphQLDTO, SupportedDialect from strawchemy.validation.base import ValidationProtocol @@ -53,7 +54,6 @@ T = TypeVar("T", bound="DeclarativeBase") -_TYPES_NS = TYPING_NS | vars(types) __all__ = ("Strawchemy",) @@ -83,6 +83,8 @@ class Strawchemy: pydantic (PydanticMapper): A mapper for generating Pydantic models. """ + _types_namespace: ClassVar[dict[str, Any]] = TYPING_NS | vars(mutation_types) + def __init__( self, config: StrawchemyConfig | SupportedDialect, @@ -134,8 +136,13 @@ def __init__( self.aggregate = partial(self._aggregation_factory.type, mode="aggregate_type") self.upsert_update_fields = self._enum_factory.input self.upsert_conflict_fields = self._upsert_conflict_factory.input - # Initialize mutation field builder - self._mutation_builder = MutationFieldBuilder(self.config, self._annotation_namespace) + self._mutation_builder = MutationFieldBuilder( + config=self.config, + registry_namespace_getter=self._annotation_namespace, + order_by_factory=self._order_by_factory, + filter_factory=self._filter_factory, + distinct_on_factory=self._distinct_on_enum_factory, + ) # Register common types self.registry.register_enum(OrderByEnum, "OrderByEnum") @@ -147,7 +154,7 @@ def _annotation_namespace(self) -> dict[str, Any]: Returns: A dictionary representing the annotation namespace. """ - return self.registry.namespace("object") | _TYPES_NS + return self.registry.namespace("object") | self._types_namespace @cached_property def pydantic(self) -> PydanticMapper: @@ -168,10 +175,10 @@ def field( self, resolver: Any, *, - filter_input: type[BooleanFilterDTO] | None = None, - order_by: type[OrderByDTO] | None = None, - distinct_on: type[EnumDTO] | None = None, + filter_input: type[BooleanFilterDTO] | bool | None = None, + order_by: IncludeFields | type[OrderByDTO] | None = None, pagination: bool | DefaultOffsetPagination | None = None, + distinct_on: IncludeFields | type[EnumDTO] | None = None, arguments: list[StrawberryArgument] | None = None, id_field_name: str | None = None, root_aggregations: bool = False, @@ -196,10 +203,10 @@ def field( def field( self, *, - filter_input: type[BooleanFilterDTO] | None = None, - order_by: type[OrderByDTO] | None = None, - distinct_on: type[EnumDTO] | None = None, + filter_input: type[BooleanFilterDTO] | bool | None = None, + order_by: IncludeFields | type[OrderByDTO] | None = None, pagination: bool | DefaultOffsetPagination | None = None, + distinct_on: IncludeFields | type[EnumDTO] | None = None, arguments: list[StrawberryArgument] | None = None, id_field_name: str | None = None, root_aggregations: bool = False, @@ -224,10 +231,10 @@ def field( self, resolver: Any | None = None, *, - filter_input: type[BooleanFilterDTO] | None = None, - order_by: type[OrderByDTO] | None = None, - distinct_on: type[EnumDTO] | None = None, + filter_input: type[BooleanFilterDTO] | bool | None = None, + order_by: IncludeFields | type[OrderByDTO] | None = None, pagination: bool | DefaultOffsetPagination | None = None, + distinct_on: IncludeFields | type[EnumDTO] | None = None, arguments: list[StrawberryArgument] | None = None, id_field_name: str | None = None, root_aggregations: bool = False, @@ -285,21 +292,13 @@ def field( """ namespace = self._annotation_namespace() type_annotation = StrawberryAnnotation.from_annotation(graphql_type, namespace) if graphql_type else None - repository_type_ = repository_type if repository_type is not None else self.config.repository_type - execution_options_ = execution_options if execution_options is not None else self.config.execution_options - pagination = ( - DefaultOffsetPagination(limit=self.config.pagination_default_limit) if pagination is True else pagination - ) - if pagination is None: - pagination = self.config.pagination - id_field_name = id_field_name or self.config.default_id_field_name field = StrawchemyField( config=self.config, - repository_type=repository_type_, + repository_type=repository_type, root_field=root_field, filter_statement=filter_statement, - execution_options=execution_options_, + execution_options=execution_options, filter_type=filter_input, order_by=order_by, pagination=pagination, @@ -321,6 +320,9 @@ def field( registry_namespace=namespace, description=description, arguments=arguments, + order_by_factory=self._order_by_factory, + filter_factory=self._filter_factory, + distinct_on_factory=self._distinct_on_enum_factory, ) return field(resolver) if resolver else field diff --git a/src/strawchemy/schema/factories/aggregations.py b/src/strawchemy/schema/factories/aggregations.py index a53103a0..ea818784 100644 --- a/src/strawchemy/schema/factories/aggregations.py +++ b/src/strawchemy/schema/factories/aggregations.py @@ -123,6 +123,7 @@ def factory( raise_if_no_fields: bool = False, tags: set[str] | None = None, backend_kwargs: dict[str, Any] | None = None, + no_cache: bool = False, *, function: FunctionInfo | None = None, **kwargs: Any, @@ -137,6 +138,7 @@ def factory( raise_if_no_fields, tags, backend_kwargs, + no_cache, function=function, **kwargs, ) diff --git a/src/strawchemy/schema/factories/base.py b/src/strawchemy/schema/factories/base.py index 8fb7bd99..e89ff2d7 100644 --- a/src/strawchemy/schema/factories/base.py +++ b/src/strawchemy/schema/factories/base.py @@ -16,6 +16,7 @@ import dataclasses from functools import cached_property +from inspect import isclass from typing import TYPE_CHECKING, Any, Literal, Optional, TypeAlias, TypeVar, get_type_hints from sqlalchemy.orm import DeclarativeBase, QueryableAttribute @@ -29,17 +30,17 @@ from strawchemy.dto.strawberry import ( BooleanFilterDTO, DTOKey, + EnumDTO, GraphQLFieldDefinition, MappedStrawberryGraphQLDTO, OrderByDTO, StrawchemyDTOAttributes, UnmappedStrawberryGraphQLDTO, ) -from strawchemy.dto.types import DTOAuto, DTOScope, Purpose +from strawchemy.dto.types import DTOAuto, DTOScope, Purpose, cast_include_fields, is_fields_iterable from strawchemy.dto.utils import config from strawchemy.exceptions import StrawchemyError from strawchemy.instance import MapperModelInstance -from strawchemy.schema.pagination import DefaultOffsetPagination from strawchemy.transpiler import hook from strawchemy.typing import GraphQLDTOT, GraphQLPurpose, GraphQLType, MappedGraphQLDTO from strawchemy.utils.annotation import get_annotations @@ -50,7 +51,8 @@ from strawchemy import Strawchemy from strawchemy.dto.inspectors import SQLAlchemyGraphQLInspector - from strawchemy.dto.types import DTOConfig, ExcludeFields, IncludeFields + from strawchemy.dto.types import DTOConfig, FieldIterable, IncludeFields + from strawchemy.schema.pagination import DefaultOffsetPagination from strawchemy.transpiler.hook import QueryHook from strawchemy.utils.graph import Node from strawchemy.validation.pydantic import MappedPydanticGraphQLDTO @@ -97,9 +99,11 @@ def _type_info( current_node: Node[Relation[Any, GraphQLDTOT], None] | None, override: bool = False, user_defined: bool = False, - child_options: ChildOptions | None = None, + paginate: IncludeFields | None = None, + order: IncludeFields | type[OrderByDTO] | None = None, + distinct_on: IncludeFields | type[EnumDTO] | None = None, + default_pagination: DefaultOffsetPagination | None = None, ) -> RegistryTypeInfo: - child_options = child_options or ChildOptions() graphql_type = self.graphql_type(dto_config) model: type[DeclarativeBase] | None = dto.__dto_model__ if issubclass(dto, MappedStrawberryGraphQLDTO) else None # type: ignore[reportGeneralTypeIssues] default_name = self.root_dto_name(model, dto_config, current_node) if model else dto.__name__ @@ -109,8 +113,10 @@ def _type_info( graphql_type=graphql_type, override=override, user_defined=user_defined, - pagination=DefaultOffsetPagination() if child_options.pagination is True else child_options.pagination, - order_by=child_options.order_by, + pagination=default_pagination, + order=cast_include_fields(order) if is_fields_iterable(order) else order, + distinct_on=cast_include_fields(distinct_on) if is_fields_iterable(distinct_on) else distinct_on, + paginate=cast_include_fields(paginate), scope=dto_config.scope, model=model, exclude_from_scope=dto_config.exclude_from_scope, @@ -130,14 +136,20 @@ def _register_type( directives: Sequence[object] | None = (), override: bool = False, user_defined: bool = False, - child_options: ChildOptions | None = None, + order: IncludeFields | type[OrderByDTO] | None = None, + distinct_on: IncludeFields | type[EnumDTO] | None = None, + paginate: IncludeFields | None = None, + default_pagination: None | DefaultOffsetPagination = None, ) -> type[StrawchemyDTOT]: type_info = self._type_info( dto, dto_config, override=override, user_defined=user_defined, - child_options=child_options, + order=order, + distinct_on=distinct_on, + paginate=paginate, + default_pagination=default_pagination, current_node=current_node, ) self._raise_if_type_conflicts(type_info) @@ -181,7 +193,7 @@ def _config( self, purpose: Purpose, include: IncludeFields | None = None, - exclude: ExcludeFields | None = None, + exclude: FieldIterable | None = None, partial: bool | None = None, type_map: Mapping[Any, Any] | None = None, aliases: Mapping[str, str] | None = None, @@ -193,6 +205,8 @@ def _config( purpose, include=include, exclude=exclude, + global_include=self._mapper.config.include, + global_exclude=self._mapper.config.exclude, partial=partial, type_map=type_map, alias_generator=alias_generator, @@ -207,15 +221,15 @@ def _type_wrapper( *, mode: GraphQLPurpose, include: IncludeFields | None = None, - exclude: ExcludeFields | None = None, + exclude: FieldIterable | None = None, partial: bool | None = None, type_map: Mapping[Any, Any] | None = None, aliases: Mapping[str, str] | None = None, alias_generator: Callable[[str], str] | None = None, - child_pagination: bool | DefaultOffsetPagination = False, - child_order_by: bool = False, + paginate: IncludeFields | None = None, + default_pagination: None | DefaultOffsetPagination = None, filter_input: type[BooleanFilterDTO] | None = None, - order_by: type[OrderByDTO] | None = None, + order: IncludeFields | type[OrderByDTO] | None = None, name: str | None = None, description: str | None = None, directives: Sequence[object] | None = (), @@ -229,6 +243,8 @@ def wrapper(class_: type[Any]) -> type[GraphQLDTOT]: purpose, include=include, exclude=exclude, + global_include=self._mapper.config.include, + global_exclude=self._mapper.config.exclude, partial=partial, type_map=type_map, alias_generator=alias_generator, @@ -247,12 +263,16 @@ def wrapper(class_: type[Any]) -> type[GraphQLDTOT]: override=override, user_defined=True, mode=mode, - child_options=ChildOptions(pagination=child_pagination, order_by=child_order_by), + paginate=self._mapper.config.pagination if paginate is None else paginate, + order=self._mapper.config.order_by if order is None else order, + default_pagination=default_pagination, ) dto.__strawchemy_query_hook__ = query_hook if issubclass(dto, MappedStrawberryGraphQLDTO): - dto.__strawchemy_filter__ = filter_input - dto.__strawchemy_order_by__ = order_by + if isclass(order) and issubclass(order, OrderByDTO): # pyright: ignore[reportUnnecessaryIsInstance] + dto.__strawchemy_order_by__ = order + if isclass(filter_input) and issubclass(filter_input, BooleanFilterDTO): # pyright: ignore[reportUnnecessaryIsInstance] + dto.__strawchemy_filter__ = filter_input dto.__strawchemy_purpose__ = mode return dto @@ -264,7 +284,7 @@ def _input_wrapper( *, mode: GraphQLPurpose, include: IncludeFields | None = None, - exclude: ExcludeFields | None = None, + exclude: FieldIterable | None = None, partial: bool | None = None, type_map: Mapping[Any, Any] | None = None, aliases: Mapping[str, str] | None = None, @@ -278,34 +298,79 @@ def _input_wrapper( **kwargs: Any, ) -> Callable[[type[Any]], type[GraphQLDTOT]]: def wrapper(class_: type[Any]) -> type[GraphQLDTOT]: - dto_config = self._config( - purpose, + return self.make_input( + model=model, + mode=mode, include=include, exclude=exclude, partial=partial, type_map=type_map, - alias_generator=alias_generator, aliases=aliases, - scope=scope, - tags={mode}, - ) - dto = self.factory( - model=model, - dto_config=dto_config, - base=class_, + alias_generator=alias_generator, name=name, description=description, directives=directives, override=override, - user_defined=True, - mode=mode, + purpose=purpose, + scope=scope, + base=class_, **kwargs, ) - dto.__strawchemy_purpose__ = mode - return dto return wrapper + def make_input( + self, + model: type[T], + *, + mode: GraphQLPurpose, + include: IncludeFields | None = None, + exclude: FieldIterable | None = None, + partial: bool | None = None, + type_map: Mapping[Any, Any] | None = None, + aliases: Mapping[str, str] | None = None, + alias_generator: Callable[[str], str] | None = None, + name: str | None = None, + description: str | None = None, + directives: Sequence[object] | None = (), + override: bool = False, + purpose: Purpose = Purpose.WRITE, + scope: DTOScope | None = None, + base: type[Any] | None = None, + user_defined: bool = False, + no_cache: bool = False, + **kwargs: Any, + ) -> type[GraphQLDTOT]: + dto_config = self._config( + purpose, + include=include, + exclude=exclude, + partial=partial, + type_map=type_map, + alias_generator=alias_generator, + aliases=aliases, + scope=scope, + tags={mode}, + ) + if no_cache: + dto_name = name or self.root_dto_name(model, dto_config) + name = self._mapper.registry.uniquify_name(self.graphql_type(dto_config), dto_name) + dto = self.factory( + model=model, + dto_config=dto_config, + base=base, + name=name, + description=description, + directives=directives, + override=override, + user_defined=user_defined, + mode=mode, + no_cache=no_cache, + **kwargs, + ) + dto.__strawchemy_purpose__ = mode + return dto + @cached_property def _namespace(self) -> dict[str, Any]: return vars(strawchemy_typing) | vars(hook) @@ -353,6 +418,7 @@ def factory( raise_if_no_fields: bool = False, tags: set[str] | None = None, backend_kwargs: dict[str, Any] | None = None, + no_cache: bool = False, *, description: str | None = None, directives: Sequence[object] | None = (), @@ -376,6 +442,7 @@ def factory( tags, backend_kwargs=backend_kwargs, field_map=field_map, + no_cache=no_cache, **kwargs, ) if not dto.__strawchemy_field_map__: @@ -431,15 +498,15 @@ def type( model: type[T], *, include: IncludeFields | None = None, - exclude: ExcludeFields | None = None, + exclude: FieldIterable | None = None, partial: bool | None = None, type_map: Mapping[Any, Any] | None = None, aliases: Mapping[str, str] | None = None, alias_generator: Callable[[str], str] | None = None, - child_pagination: bool | DefaultOffsetPagination = False, - child_order_by: bool = False, + paginate: IncludeFields | None = None, + default_pagination: None | DefaultOffsetPagination = None, filter_input: type[BooleanFilterDTO] | None = None, - order_by: type[OrderByDTO] | None = None, + order: IncludeFields | type[OrderByDTO] | None = None, name: str | None = None, description: str | None = None, directives: Sequence[object] | None = (), @@ -457,10 +524,10 @@ def type( type_map=type_map, aliases=aliases, alias_generator=alias_generator, - child_pagination=child_pagination, - child_order_by=child_order_by, + paginate=paginate, + default_pagination=default_pagination, filter_input=filter_input, - order_by=order_by, + order=order, name=name, description=description, directives=directives, @@ -478,7 +545,7 @@ def input( *, mode: GraphQLPurpose, include: IncludeFields | None = None, - exclude: ExcludeFields | None = None, + exclude: FieldIterable | None = None, partial: bool | None = None, type_map: Mapping[Any, Any] | None = None, aliases: Mapping[str, str] | None = None, @@ -521,6 +588,7 @@ def factory( raise_if_no_fields: bool = False, tags: set[str] | None = None, backend_kwargs: dict[str, Any] | None = None, + no_cache: bool = False, *, mode: GraphQLPurpose | None = None, **kwargs: Any, @@ -549,7 +617,7 @@ def input( model: type[T], *, include: IncludeFields | None = None, - exclude: ExcludeFields | None = None, + exclude: FieldIterable | None = None, partial: bool | None = None, type_map: Mapping[Any, Any] | None = None, aliases: Mapping[str, str] | None = None, @@ -582,15 +650,15 @@ def type( self, model: type[T], include: IncludeFields | None = None, - exclude: ExcludeFields | None = None, + exclude: FieldIterable | None = None, partial: bool | None = None, type_map: Mapping[Any, Any] | None = None, aliases: Mapping[str, str] | None = None, alias_generator: Callable[[str], str] | None = None, - child_pagination: bool | DefaultOffsetPagination = False, - child_order_by: bool = False, + paginate: IncludeFields | None = None, + default_pagination: None | DefaultOffsetPagination = None, filter_input: type[BooleanFilterDTO] | None = None, - order_by: type[OrderByDTO] | None = None, + order_by: IncludeFields | type[OrderByDTO] | None = None, name: str | None = None, description: str | None = None, directives: Sequence[object] | None = (), @@ -607,10 +675,10 @@ def type( type_map=type_map, aliases=aliases, alias_generator=alias_generator, - child_pagination=child_pagination, - child_order_by=child_order_by, + paginate=paginate, + default_pagination=default_pagination, filter_input=filter_input, - order_by=order_by, + order=order_by, name=name, description=description, directives=directives, diff --git a/src/strawchemy/schema/factories/enum.py b/src/strawchemy/schema/factories/enum.py index 05d445ab..da30b546 100644 --- a/src/strawchemy/schema/factories/enum.py +++ b/src/strawchemy/schema/factories/enum.py @@ -8,9 +8,10 @@ from sqlalchemy.orm import DeclarativeBase, QueryableAttribute from typing_extensions import override +from strawchemy.dto import config from strawchemy.dto.base import DTOBackend, DTOBase, DTOFactory, DTOFieldDefinition, Relation from strawchemy.dto.strawberry import EnumDTO, GraphQLFieldDefinition -from strawchemy.dto.types import DTOConfig, ExcludeFields, IncludeFields, Purpose +from strawchemy.dto.types import DTOConfig, FieldIterable, IncludeFields, Purpose from strawchemy.utils.text import snake_to_lower_camel_case if TYPE_CHECKING: @@ -141,7 +142,7 @@ def decorator( model: type[DeclarativeBase], purpose: Purpose = Purpose.READ, include: IncludeFields | None = None, - exclude: ExcludeFields | None = None, + exclude: FieldIterable | None = None, partial: bool | None = None, type_map: Mapping[Any, Any] | None = None, aliases: Mapping[str, str] | None = None, @@ -160,11 +161,42 @@ def decorator( **kwargs, ) + def make_input( + self, + model: type[DeclarativeBase], + include: IncludeFields | None = None, + exclude: FieldIterable | None = None, + partial: bool | None = None, + type_map: Mapping[Any, Any] | None = None, + aliases: Mapping[str, str] | None = None, + alias_generator: Callable[[str], str] | None = None, + base: type[Any] | None = None, + name: str | None = None, + no_cache: bool = False, + **kwargs: Any, + ) -> type[EnumDTO]: + return self.factory( + model=model, + dto_config=config( + purpose=Purpose.WRITE, + include=include, + exclude=exclude, + partial=partial, + type_map=type_map, + aliases=aliases, + alias_generator=alias_generator, + ), + base=base, + name=name, + no_cache=no_cache, + **kwargs, + ) + def input( self, model: type[DeclarativeBase], include: IncludeFields | None = None, - exclude: ExcludeFields | None = None, + exclude: FieldIterable | None = None, partial: bool | None = None, type_map: Mapping[Any, Any] | None = None, aliases: Mapping[str, str] | None = None, diff --git a/src/strawchemy/schema/factories/inputs.py b/src/strawchemy/schema/factories/inputs.py index 02869d39..351a8a3e 100644 --- a/src/strawchemy/schema/factories/inputs.py +++ b/src/strawchemy/schema/factories/inputs.py @@ -32,7 +32,7 @@ from strawchemy import Strawchemy from strawchemy.dto.base import DTOBackend, DTOBase, DTOFieldDefinition, ModelFieldT, Relation - from strawchemy.dto.types import ExcludeFields, IncludeFields + from strawchemy.dto.types import FieldIterable, IncludeFields from strawchemy.repository.typing import DeclarativeT from strawchemy.schema.filters import GraphQLFilter from strawchemy.utils.graph import Node @@ -52,7 +52,7 @@ def input( model: type[DeclarativeT], *, include: IncludeFields | None = None, - exclude: ExcludeFields | None = None, + exclude: FieldIterable | None = None, partial: bool | None = None, type_map: Mapping[Any, Any] | None = None, aliases: Mapping[str, str] | None = None, @@ -143,7 +143,9 @@ def iter_field_definitions( if field.uselist and field.related_dto: field.type_ = Union[field.related_dto, None] if aggregate_filters: - aggregation_field = self._aggregation_field(field, dto_config.copy_with(partial_default=UNSET)) + aggregation_field = self._aggregation_field( + field, dto_config.copy_with(partial_default=UNSET, partial=True) + ) field_map[key + aggregation_field.name] = aggregation_field yield aggregation_field else: @@ -172,6 +174,7 @@ def factory( raise_if_no_fields: bool = False, tags: set[str] | None = None, backend_kwargs: dict[str, Any] | None = None, + no_cache: bool = False, *, aggregate_filters: bool = True, **kwargs: Any, @@ -452,6 +455,7 @@ def factory( raise_if_no_fields: bool = False, tags: set[str] | None = None, backend_kwargs: dict[str, Any] | None = None, + no_cache: bool = False, *, aggregate_filters: bool = True, **kwargs: Any, @@ -466,6 +470,7 @@ def factory( raise_if_no_fields, tags, backend_kwargs, + no_cache, aggregate_filters=aggregate_filters, **kwargs, ) diff --git a/src/strawchemy/schema/factories/types.py b/src/strawchemy/schema/factories/types.py index 861325bd..e1a04dd3 100644 --- a/src/strawchemy/schema/factories/types.py +++ b/src/strawchemy/schema/factories/types.py @@ -19,13 +19,13 @@ FunctionFieldDefinition, GraphQLFieldDefinition, MappedStrawberryGraphQLDTO, + OrderByDTO, ) -from strawchemy.dto.types import DTOConfig, DTOMissing, Purpose -from strawchemy.dto.utils import read_all_partial_config, read_partial, write_all_config +from strawchemy.dto.types import DTOConfig, DTOMissing, IncludeFields, Purpose, is_fields_iterable +from strawchemy.dto.utils import read_partial, write_all_config from strawchemy.exceptions import EmptyDTOError from strawchemy.schema.factories import ( AggregationInspector, - ChildOptions, EnumDTOFactory, GraphQLDTOFactory, MappedGraphQLDTOT, @@ -104,27 +104,79 @@ def _aggregation_field( related_dto=dto, ) - def _update_fields( + def _add_fields_arguments( self, dto: type[GraphQLDTOT], base: type[Any] | None, - pagination: bool | DefaultOffsetPagination = False, - order_by: bool = False, + order: IncludeFields | None = None, + paginate: IncludeFields | None = None, + default_pagination: None | DefaultOffsetPagination = None, ) -> type[GraphQLDTOT]: + """Add pagination and ordering arguments to a GraphQL DTO type. + + Enhances a GraphQL Data Transfer Object (DTO) type with pagination and ordering + arguments for relation fields and path filtering for JSON fields. This is a + post-processing step that modifies the DTO type after initial generation to + add query capabilities. + + For each relation field with `uselist=True` (one-to-many relationships): + - If included in the `order` specification: adds an order_by argument + - If included in the `paginate` specification: adds pagination configuration + + For each JSON field: + - Adds a `json_path` argument for path-based filtering + + Args: + dto: The GraphQL DTO type to enhance. Must be a generated strawberry type. + base: Optional base class whose annotations should be merged into the DTO. + If provided, annotations from the base class are added to the final DTO. + order: Field inclusion specification for ordering arguments. Can be: + - None: No ordering arguments added (default) + - "all": Add order_by arguments to all relation fields + - list/set of field names: Add order_by arguments only to named relations + paginate: Field inclusion specification for pagination arguments. Can be: + - None: No pagination arguments added (default) + - "all": Add pagination to all relation fields + - list/set of field names: Add pagination only to named relations + default_pagination: Default pagination configuration to apply when + paginate is enabled. If None, uses default pagination (True). + + Returns: + The modified DTO type with updated __annotations__ and attributes + containing the new pagination and ordering arguments. + """ attributes: dict[str, Any] = {} annotations: dict[str, Any] = {} + order_config = DTOConfig.from_include(order) + paginate_config = DTOConfig.from_include(paginate) for field in dto.__strawchemy_field_map__.values(): + # Add pagination and ordering arguments for relations if field.is_relation and field.uselist: related = Self if field.related_dto is dto else field.related_dto type_annotation = list[related] if related is not None else field.type_ assert field.related_model - order_by_input = None - if order_by: - order_by_input = self._order_by_factory.factory(field.related_model, read_all_partial_config) + order_by_input, pagination = None, False + if order_config.is_field_included(field.model_field_name) or self._mapper.config.order_by: + try: + order_by_input = self._order_by_factory.factory( + field.related_model, + DTOConfig( + Purpose.READ, + partial=True, + include=self._mapper.config.order_by or "all", + global_include=self._mapper.config.order_by or "all", + ), + raise_if_no_fields=True, + ) + except EmptyDTOError: + order_by_input = None + if paginate_config.is_field_included(field.model_field_name): + pagination = default_pagination or True strawberry_field = self._mapper.field(pagination=pagination, order_by=order_by_input, root_field=False) attributes[field.name] = strawberry_field annotations[field.name] = type_annotation + # Add path filtering argument for JSON fields elif ( not field.is_relation and field.has_model_field @@ -152,18 +204,6 @@ def _update_fields( setattr(dto, name, value) return dto - @override - def _cache_key( - self, - model: type[Any], - dto_config: DTOConfig, - node: Node[Relation[Any, MappedGraphQLDTOT], None], - *, - child_options: ChildOptions, - **factory_kwargs: Any, - ) -> Hashable: - return (super()._cache_key(model, dto_config, node, **factory_kwargs), child_options) - @override def dto_name( self, base_name: str, dto_config: DTOConfig, node: Node[Relation[Any, MappedGraphQLDTOT], None] | None = None @@ -207,8 +247,11 @@ def factory( raise_if_no_fields: bool = False, tags: set[str] | None = None, backend_kwargs: dict[str, Any] | None = None, + no_cache: bool = False, *, - child_options: ChildOptions | None = None, + default_pagination: None | DefaultOffsetPagination = None, + order: IncludeFields | type[OrderByDTO] | None = None, + paginate: IncludeFields | None = None, aggregations: bool = True, description: str | None = None, directives: Sequence[object] | None = (), @@ -230,12 +273,19 @@ def factory( aggregations=aggregations if dto_config.purpose is Purpose.READ else False, register_type=False, override=override, - child_options=child_options, + paginate=paginate if paginate == "all" else self._mapper.config.pagination, + order=order if order == "all" else self._mapper.config.order_by, + default_pagination=default_pagination, **kwargs, ) - child_options = child_options or ChildOptions() if self.graphql_type(dto_config) == "object": - dto = self._update_fields(dto, base, pagination=child_options.pagination, order_by=child_options.order_by) + dto = self._add_fields_arguments( + dto, + base, + default_pagination=default_pagination, + order=order if is_fields_iterable(order) else None, + paginate=paginate, + ) if register_type: return self._register_type( dto, @@ -244,7 +294,9 @@ def factory( directives=directives, override=override, user_defined=user_defined, - child_options=child_options, + default_pagination=default_pagination, + order=order, + paginate=paginate, current_node=current_node, ) return dto @@ -322,6 +374,7 @@ def factory( raise_if_no_fields: bool = False, tags: set[str] | None = None, backend_kwargs: dict[str, Any] | None = None, + no_cache: bool = False, *, aggregations: bool = True, **kwargs: Any, @@ -558,12 +611,11 @@ def _cache_key( dto_config: DTOConfig, node: Node[Relation[Any, MappedGraphQLDTOT], None], *, - child_options: ChildOptions, mode: GraphQLPurpose, **factory_kwargs: Any, ) -> Hashable: return ( - super()._cache_key(model, dto_config, node, child_options=child_options, **factory_kwargs), + super()._cache_key(model, dto_config, node, **factory_kwargs), node.root.value.model, mode, ) @@ -666,6 +718,7 @@ def factory( raise_if_no_fields: bool = False, tags: set[str] | None = None, backend_kwargs: dict[str, Any] | None = None, + no_cache: bool = False, *, description: str | None = None, mode: GraphQLPurpose, @@ -683,5 +736,6 @@ def factory( backend_kwargs=backend_kwargs, description=description or self._description(mode), mode=mode, + no_cache=no_cache, **kwargs, ) diff --git a/src/strawchemy/schema/field.py b/src/strawchemy/schema/field.py index 32a21f04..bd5b9e5a 100644 --- a/src/strawchemy/schema/field.py +++ b/src/strawchemy/schema/field.py @@ -14,8 +14,14 @@ from strawchemy.constants import DISTINCT_ON_KEY, FILTER_KEY, LIMIT_KEY, NODES_KEY, OFFSET_KEY, ORDER_BY_KEY from strawchemy.dto.base import MappedDTO -from strawchemy.dto.strawberry import MappedStrawberryGraphQLDTO, StrawchemyDTOAttributes -from strawchemy.dto.types import DTOConfig, Purpose +from strawchemy.dto.strawberry import ( + BooleanFilterDTO, + EnumDTO, + MappedStrawberryGraphQLDTO, + OrderByDTO, + StrawchemyDTOAttributes, +) +from strawchemy.dto.types import DTOConfig, IncludeFields, Purpose from strawchemy.exceptions import StrawchemyFieldError from strawchemy.schema.pagination import DefaultOffsetPagination from strawchemy.utils.annotation import is_type_hint_optional @@ -37,10 +43,11 @@ from strawberry.types.fields.resolver import StrawberryResolver from strawchemy import StrawchemyConfig - from strawchemy.dto.strawberry import BooleanFilterDTO, EnumDTO, OrderByDTO from strawchemy.repository.strawberry import StrawchemyAsyncRepository, StrawchemySyncRepository from strawchemy.repository.strawberry.base import GraphQLResult from strawchemy.repository.typing import QueryHookCallable + from strawchemy.schema.factories import DistinctOnFieldsDTOFactory + from strawchemy.schema.factories.inputs import BooleanFilterDTOFactory, OrderByDTOFactory from strawchemy.typing import ( AnyRepository, AnyRepositoryType, @@ -74,17 +81,20 @@ class StrawchemyField(StrawberryField): def __init__( self, config: StrawchemyConfig, - repository_type: AnyRepositoryType, - filter_type: type[BooleanFilterDTO] | None = None, - order_by: type[OrderByDTO] | None = None, - distinct_on: type[EnumDTO] | None = None, - pagination: bool | DefaultOffsetPagination = False, + order_by_factory: OrderByDTOFactory, + filter_factory: BooleanFilterDTOFactory, + distinct_on_factory: DistinctOnFieldsDTOFactory, + filter_type: type[BooleanFilterDTO] | bool | None = None, + order_by: IncludeFields | type[OrderByDTO] | Literal[False] | None = None, + distinct_on: IncludeFields | type[EnumDTO] | Literal[False] | None = None, + pagination: DefaultOffsetPagination | bool | None = False, + repository_type: AnyRepositoryType | None = None, root_aggregations: bool = False, registry_namespace: dict[str, Any] | None = None, filter_statement: FilterStatementCallable | None = None, query_hook: QueryHookCallable[Any] | Sequence[QueryHookCallable[Any]] | None = None, execution_options: dict[str, Any] | None = None, - id_field_name: str = "id", + id_field_name: str | None = None, arguments: list[StrawberryArgument] | None = None, # Original StrawberryField args python_name: str | None = None, @@ -107,20 +117,22 @@ def __init__( self.registry_namespace = registry_namespace self.is_root_field = root_field self.root_aggregations = root_aggregations - self.distinct_on = distinct_on self.query_hook = query_hook - self.pagination: DefaultOffsetPagination | Literal[False] = ( - DefaultOffsetPagination() if pagination is True else pagination - ) - self.id_field_name = id_field_name + self.id_field_name = config.default_id_field_name if id_field_name is None else id_field_name + + self._pagination = pagination + self._distinct_on = distinct_on self._filter = filter_type self._order_by = order_by self._description = description self._filter_statement = filter_statement - self._execution_options = execution_options + self._execution_options = config.execution_options if execution_options is None else execution_options self._config = config - self._repository_type = repository_type + self._repository_type = config.repository_type if repository_type is None else repository_type + self._order_by_factory = order_by_factory + self._filter_factory = filter_factory + self._distinct_on_factory = distinct_on_factory super().__init__( python_name, @@ -224,18 +236,97 @@ def _is_strawchemy_type( ) @cached_property - def filter(self) -> type[BooleanFilterDTO] | None: + def pagination(self) -> DefaultOffsetPagination | None: + if self._pagination is True or (self._pagination is None and self._config.pagination == "all"): + return DefaultOffsetPagination( + limit=self._config.pagination_default_limit, offset=self._config.pagination_default_offset + ) + + return None if not self._pagination else self._pagination + + @cached_property + def distinct_on(self) -> type[EnumDTO] | None: + if isclass(self._distinct_on) and issubclass(self._distinct_on, EnumDTO): # pyright: ignore[reportUnnecessaryIsInstance] + return self._distinct_on + inner_type = strawberry_contained_user_type(self.type) - if self._filter is None and self._is_strawchemy_type(inner_type): - return inner_type.__strawchemy_filter__ - return self._filter + distinct_on = self._config.distinct_on if self._distinct_on is None else self._distinct_on + + if self._is_strawchemy_type(inner_type) and distinct_on: + config = inner_type.__dto_config__.copy_with( + include=inner_type.__dto_config__.include if distinct_on == "all" else distinct_on + ) + + return self._distinct_on_factory.make_input( + inner_type.__dto_model__, # type: ignore[reportGeneralTypeIssues] + include=config.include, + exclude=config.exclude, + aliases=config.aliases, + alias_generator=config.alias_generator, + type_map=config.type_overrides, + ) + + return None @cached_property def order_by(self) -> type[OrderByDTO] | None: + if isclass(self._order_by) and issubclass(self._order_by, OrderByDTO): # pyright: ignore[reportUnnecessaryIsInstance] + return self._order_by + inner_type = strawberry_contained_user_type(self.type) - if self._order_by is None and self._is_strawchemy_type(inner_type): + order_by = self._config.order_by if self._order_by is None else self._order_by + + if self._is_strawchemy_type(inner_type) and order_by: + config = inner_type.__dto_config__.copy_with( + include=inner_type.__dto_config__.include if order_by == "all" else order_by + ) + + return self._order_by_factory.make_input( + inner_type.__dto_model__, # type: ignore[reportGeneralTypeIssues] + mode="order_by", + include=config.include if order_by == "all" else order_by, + exclude=config.exclude, + aliases=config.aliases, + alias_generator=config.alias_generator, + type_map=config.type_overrides, + no_cache=True, + ) + if ( + self._order_by is None + and self._is_strawchemy_type(inner_type) + and inner_type.__strawchemy_order_by__ is not None + ): return inner_type.__strawchemy_order_by__ - return self._order_by + + return None + + @cached_property + def filter(self) -> type[BooleanFilterDTO] | None: + if isclass(self._filter) and issubclass(self._filter, BooleanFilterDTO): # pyright: ignore[reportUnnecessaryIsInstance] + return self._filter + + inner_type = strawberry_contained_user_type(self.type) + + if self._is_strawchemy_type(inner_type) and self._filter is True: + config = inner_type.__dto_config__ + return self._filter_factory.make_input( + inner_type.__dto_model__, # type: ignore[reportGeneralTypeIssues] + mode="filter", + include=config.include, + exclude=config.exclude, + aliases=config.aliases, + alias_generator=config.alias_generator, + type_map=config.type_overrides, + no_cache=True, + ) + if ( + self._filter is None + and self._is_strawchemy_type(inner_type) + and inner_type.__strawchemy_filter__ is not None + ): + return inner_type.__strawchemy_filter__ + + return None def auto_arguments(self) -> list[StrawberryArgument]: arguments: list[StrawberryArgument] = [] @@ -349,11 +440,14 @@ def __copy__(self) -> Self: root_aggregations=self.root_aggregations, filter_type=self._filter, order_by=self._order_by, - distinct_on=self.distinct_on, + distinct_on=self._distinct_on, pagination=self.pagination, registry_namespace=self.registry_namespace, execution_options=self._execution_options, config=self._config, + order_by_factory=self._order_by_factory, + filter_factory=self._filter_factory, + distinct_on_factory=self._distinct_on_factory, ) new_field._arguments = self._arguments[:] if self._arguments is not None else None # noqa: SLF001 return new_field diff --git a/src/strawchemy/schema/mutation/field_builder.py b/src/strawchemy/schema/mutation/field_builder.py index 72903bb2..f44192f9 100644 --- a/src/strawchemy/schema/mutation/field_builder.py +++ b/src/strawchemy/schema/mutation/field_builder.py @@ -15,6 +15,8 @@ from strawberry.extensions.field_extension import FieldExtension from strawchemy.config.base import StrawchemyConfig + from strawchemy.schema.factories import DistinctOnFieldsDTOFactory + from strawchemy.schema.factories.inputs import BooleanFilterDTOFactory, OrderByDTOFactory from strawchemy.schema.mutation.fields import ( StrawchemyCreateMutationField, StrawchemyDeleteMutationField, @@ -35,6 +37,9 @@ class MutationFieldBuilder: config: StrawchemyConfig registry_namespace_getter: Callable[[], dict[str, Any]] + order_by_factory: OrderByDTOFactory + filter_factory: BooleanFilterDTOFactory + distinct_on_factory: DistinctOnFieldsDTOFactory def build( self, @@ -86,11 +91,10 @@ def build( """ namespace = self.registry_namespace_getter() type_annotation = StrawberryAnnotation.from_annotation(graphql_type, namespace) if graphql_type else None - repository_type_ = repository_type if repository_type is not None else self.config.repository_type field = field_class( config=self.config, - repository_type=repository_type_, + repository_type=repository_type, python_name=None, graphql_name=name, type_annotation=type_annotation, @@ -104,6 +108,9 @@ def build( extensions=extensions or [], registry_namespace=namespace, description=description, + order_by_factory=self.order_by_factory, + filter_factory=self.filter_factory, + distinct_on_factory=self.distinct_on_factory, **field_specific_kwargs, ) return field(resolver) if resolver else field diff --git a/src/strawchemy/utils/registry.py b/src/strawchemy/utils/registry.py index ad060ac8..eb68315e 100644 --- a/src/strawchemy/utils/registry.py +++ b/src/strawchemy/utils/registry.py @@ -30,6 +30,7 @@ from strawberry.types.arguments import StrawberryArgument from strawberry.types.base import WithStrawberryObjectDefinition + from strawchemy.dto.strawberry import EnumDTO, OrderByDTO from strawchemy.dto.types import DTOScope from strawchemy.schema.pagination import DefaultOffsetPagination from strawchemy.typing import GraphQLType, StrawchemyTypeWithStrawberryObjectDefinition @@ -101,8 +102,10 @@ class RegistryTypeInfo: default_name: str | None = None user_defined: bool = False override: bool = False - pagination: DefaultOffsetPagination | Literal[False] = False - order_by: bool = False + pagination: DefaultOffsetPagination | None = None + order: frozenset[str] | Literal["all"] | type[OrderByDTO] = dataclasses.field(default_factory=frozenset) + distinct_on: frozenset[str] | Literal["all"] | type[EnumDTO] = dataclasses.field(default_factory=frozenset) + paginate: frozenset[str] | Literal["all"] = dataclasses.field(default_factory=frozenset) scope: DTOScope | None = None model: type[DeclarativeBase] | None = None tags: frozenset[str] = dataclasses.field(default_factory=frozenset) @@ -110,7 +113,7 @@ class RegistryTypeInfo: @property def scoped_id(self) -> Hashable: - return (self.model, self.graphql_type, self.tags) + return self.model, self.graphql_type, self.tags class StrawberryRegistry: @@ -127,6 +130,7 @@ def __init__(self, strawberry_config: StrawberryConfig) -> None: self._type_map: dict[RegistryTypeInfo, type[Any]] = {} self._names_map: defaultdict[GraphQLType, dict[str, RegistryTypeInfo]] = defaultdict(dict) self._tracked_type_names: defaultdict[GraphQLType, set[str]] = defaultdict(set) + self._unique_names: defaultdict[str, int] = defaultdict(int) def _get_field_type_name( self, @@ -303,6 +307,13 @@ def name_clash(self, type_info: RegistryTypeInfo) -> bool: and not type_info.override ) + def uniquify_name(self, graphql_type: GraphQLType, name: str) -> str: + """Return a type name guaranteed to be unique within the registry.""" + while self.get(graphql_type, name, None): + self._unique_names[name] += 1 + name = f"{name}{self._unique_names[name]}" + return name + @overload def get(self, graphql_type: GraphQLType, name: str, default: _RegistryMissing) -> RegistryTypeInfo: ... diff --git a/src/strawchemy/validation/pydantic.py b/src/strawchemy/validation/pydantic.py index 93426ebd..e6261307 100644 --- a/src/strawchemy/validation/pydantic.py +++ b/src/strawchemy/validation/pydantic.py @@ -24,7 +24,7 @@ from strawchemy import Strawchemy from strawchemy.dto.base import DTOFieldDefinition, MappedDTO, Relation - from strawchemy.dto.types import DTOConfig, ExcludeFields, IncludeFields, Purpose + from strawchemy.dto.types import DTOConfig, FieldIterable, IncludeFields, Purpose from strawchemy.repository.typing import DeclarativeT from strawchemy.typing import GraphQLPurpose from strawchemy.utils.graph import Node @@ -84,7 +84,7 @@ def input( *, mode: GraphQLPurpose, include: IncludeFields | None = None, - exclude: ExcludeFields | None = None, + exclude: FieldIterable | None = None, partial: bool | None = None, type_map: Mapping[Any, Any] | None = None, aliases: Mapping[str, str] | None = None, @@ -109,6 +109,7 @@ def factory( raise_if_no_fields: bool = False, tags: set[str] | None = None, backend_kwargs: dict[str, Any] | None = None, + no_cache: bool = False, *, description: str | None = None, mode: GraphQLPurpose, @@ -123,6 +124,7 @@ def factory( current_node, raise_if_no_fields, tags, + no_cache=no_cache, backend_kwargs=backend_kwargs, description=description or f"{mode.capitalize()} validation type", mode=mode, diff --git a/tests/integration/types/mysql.py b/tests/integration/types/mysql.py index 910e6b98..76e1ab32 100644 --- a/tests/integration/types/mysql.py +++ b/tests/integration/types/mysql.py @@ -151,7 +151,7 @@ class OrderedFruitType: ... class FruitAggregationType: ... -@strawchemy.type(Fruit, include="all", child_pagination=True, child_order_by=True) +@strawchemy.type(Fruit, include="all", paginate="all", order="all") class FruitTypeWithPaginationAndOrderBy: ... @@ -182,7 +182,7 @@ class FruitUpsertConflictFields: ... # Color -@strawchemy.type(Color, include="all", override=True, child_order_by=True) +@strawchemy.type(Color, include="all", override=True, order="all") class ColorType: ... @@ -194,7 +194,7 @@ class ColorOrder: ... class ColorDistinctOn: ... -@strawchemy.type(Color, include="all", child_pagination=True) +@strawchemy.type(Color, include="all", paginate="all") class ColorTypeWithPagination: ... diff --git a/tests/integration/types/postgres.py b/tests/integration/types/postgres.py index 12c688a7..774e04df 100644 --- a/tests/integration/types/postgres.py +++ b/tests/integration/types/postgres.py @@ -159,7 +159,7 @@ class OrderedFruitType: ... class FruitAggregationType: ... -@strawchemy.type(Fruit, include="all", child_pagination=True, child_order_by=True) +@strawchemy.type(Fruit, include="all", paginate="all", order="all") class FruitTypeWithPaginationAndOrderBy: ... @@ -190,7 +190,7 @@ class FruitUpsertConflictFields: ... # Color -@strawchemy.type(Color, include="all", override=True, child_order_by=True) +@strawchemy.type(Color, include="all", override=True, order="all") class ColorType: ... @@ -202,7 +202,7 @@ class ColorOrder: ... class ColorDistinctOn: ... -@strawchemy.type(Color, include="all", child_pagination=True) +@strawchemy.type(Color, include="all", paginate="all") class ColorTypeWithPagination: ... diff --git a/tests/integration/types/sqlite.py b/tests/integration/types/sqlite.py index d090a8e5..75e61eb9 100644 --- a/tests/integration/types/sqlite.py +++ b/tests/integration/types/sqlite.py @@ -150,7 +150,7 @@ class OrderedFruitType: ... class FruitAggregationType: ... -@strawchemy.type(Fruit, include="all", child_pagination=True, child_order_by=True) +@strawchemy.type(Fruit, include="all", paginate="all", order="all") class FruitTypeWithPaginationAndOrderBy: ... @@ -181,7 +181,7 @@ class FruitUpsertConflictFields: ... # Color -@strawchemy.type(Color, include="all", override=True, child_order_by=True) +@strawchemy.type(Color, include="all", override=True, order="all") class ColorType: ... @@ -193,7 +193,7 @@ class ColorOrder: ... class ColorDistinctOn: ... -@strawchemy.type(Color, include="all", child_pagination=True) +@strawchemy.type(Color, include="all", paginate="all") class ColorTypeWithPagination: ... diff --git a/tests/unit/__snapshots__/test_example_app/test_graphql_schema.gql b/tests/unit/__snapshots__/test_example_app/test_graphql_schema.gql index f6714914..60407eeb 100644 --- a/tests/unit/__snapshots__/test_example_app/test_graphql_schema.gql +++ b/tests/unit/__snapshots__/test_example_app/test_graphql_schema.gql @@ -62,16 +62,16 @@ input CustomerAggregateBoolExpSum { } input CustomerAggregateMinMaxDatetimeFieldsOrderBy { - createdAt: OrderByEnum! - updatedAt: OrderByEnum! + createdAt: OrderByEnum + updatedAt: OrderByEnum } input CustomerAggregateMinMaxStringFieldsOrderBy { - name: OrderByEnum! + name: OrderByEnum } input CustomerAggregateNumericFieldsOrderBy { - name: OrderByEnum! + name: OrderByEnum } input CustomerAggregateOrderBy { @@ -328,16 +328,16 @@ input MilestoneAggregateBoolExpSum { } input MilestoneAggregateMinMaxDatetimeFieldsOrderBy { - createdAt: OrderByEnum! - updatedAt: OrderByEnum! + createdAt: OrderByEnum + updatedAt: OrderByEnum } input MilestoneAggregateMinMaxStringFieldsOrderBy { - name: OrderByEnum! + name: OrderByEnum } input MilestoneAggregateNumericFieldsOrderBy { - name: OrderByEnum! + name: OrderByEnum } input MilestoneAggregateOrderBy { @@ -563,16 +563,16 @@ input ProjectAggregateBoolExpSum { } input ProjectAggregateMinMaxDatetimeFieldsOrderBy { - createdAt: OrderByEnum! - updatedAt: OrderByEnum! + createdAt: OrderByEnum + updatedAt: OrderByEnum } input ProjectAggregateMinMaxStringFieldsOrderBy { - name: OrderByEnum! + name: OrderByEnum } input ProjectAggregateNumericFieldsOrderBy { - name: OrderByEnum! + name: OrderByEnum } input ProjectAggregateOrderBy { @@ -798,16 +798,16 @@ input TagAggregateBoolExpSum { } input TagAggregateMinMaxDatetimeFieldsOrderBy { - createdAt: OrderByEnum! - updatedAt: OrderByEnum! + createdAt: OrderByEnum + updatedAt: OrderByEnum } input TagAggregateMinMaxStringFieldsOrderBy { - name: OrderByEnum! + name: OrderByEnum } input TagAggregateNumericFieldsOrderBy { - name: OrderByEnum! + name: OrderByEnum } input TagAggregateOrderBy { @@ -962,16 +962,16 @@ input TicketAggregateBoolExpSum { } input TicketAggregateMinMaxDatetimeFieldsOrderBy { - createdAt: OrderByEnum! - updatedAt: OrderByEnum! + createdAt: OrderByEnum + updatedAt: OrderByEnum } input TicketAggregateMinMaxStringFieldsOrderBy { - name: OrderByEnum! + name: OrderByEnum } input TicketAggregateNumericFieldsOrderBy { - name: OrderByEnum! + name: OrderByEnum } input TicketAggregateOrderBy { diff --git a/tests/unit/dto/test_dto.py b/tests/unit/dto/test_dto.py index 455de40a..1f35819f 100644 --- a/tests/unit/dto/test_dto.py +++ b/tests/unit/dto/test_dto.py @@ -266,3 +266,72 @@ def test_forward_refs_resolved(name: str, sqlalchemy_pydantic_factory: MappedPyd ], } ) + + +# Tests for DTOConfig.from_include() and is_field_included() + + +def test_from_include_with_none() -> None: + """Test that from_include(None) creates a config with empty include set.""" + config = DTOConfig.from_include(None) + assert config.include == set() + assert config.purpose == Purpose.READ + + +def test_from_include_with_all() -> None: + """Test that from_include('all') creates a config with include='all'.""" + config = DTOConfig.from_include("all") + assert config.include == "all" + assert config.purpose == Purpose.READ + + +def test_from_include_with_list() -> None: + """Test that from_include() accepts a list and converts it to the include parameter.""" + config = DTOConfig.from_include(["field1", "field2"]) + assert config.include == ["field1", "field2"] + assert config.purpose == Purpose.READ + + +def test_from_include_with_set() -> None: + """Test that from_include() accepts a set for the include parameter.""" + config = DTOConfig.from_include({"field1", "field2"}) + assert config.include == {"field1", "field2"} + assert config.purpose == Purpose.READ + + +def test_from_include_with_custom_purpose() -> None: + """Test that from_include() accepts a custom purpose.""" + config = DTOConfig.from_include(["field1"], purpose=Purpose.WRITE) + assert config.include == ["field1"] + assert config.purpose == Purpose.WRITE + + +def test_is_field_included_with_all() -> None: + """Test that is_field_included() returns True for any field when include='all'.""" + config = DTOConfig.from_include("all") + assert config.is_field_included("any_field") is True + assert config.is_field_included("another_field") is True + + +def test_is_field_included_with_specific_list() -> None: + """Test that is_field_included() returns True only for listed fields.""" + config = DTOConfig.from_include(["field1", "field2"]) + assert config.is_field_included("field1") is True + assert config.is_field_included("field2") is True + assert config.is_field_included("field3") is False + + +def test_is_field_included_with_empty_include() -> None: + """Test that is_field_included() returns False for all fields when include is empty.""" + config = DTOConfig.from_include(None) + assert config.is_field_included("field1") is False + assert config.is_field_included("any_field") is False + + +def test_is_field_included_with_exclude() -> None: + """Test that excluded fields are properly excluded even when include='all'.""" + config = DTOConfig(Purpose.READ, include="all", exclude={"field2", "field3"}) + assert config.is_field_included("field1") is True + assert config.is_field_included("field2") is False + assert config.is_field_included("field3") is False + assert config.is_field_included("field4") is True diff --git a/tests/unit/mapping/__snapshots__/test_schemas/test_query_schemas[all_fields_order_by].gql b/tests/unit/mapping/__snapshots__/test_schemas/test_query_schemas[all_fields_order_by].gql index efccf7e8..ff732a2b 100644 --- a/tests/unit/mapping/__snapshots__/test_schemas/test_query_schemas[all_fields_order_by].gql +++ b/tests/unit/mapping/__snapshots__/test_schemas/test_query_schemas[all_fields_order_by].gql @@ -1,10 +1,10 @@ ''' input ColorAggregateMinMaxStringFieldsOrderBy { - name: OrderByEnum! + name: OrderByEnum } input ColorAggregateNumericFieldsOrderBy { - name: OrderByEnum! + name: OrderByEnum } input ColorAggregateOrderBy { @@ -48,11 +48,11 @@ type FruitAggregate { } input FruitAggregateMinMaxStringFieldsOrderBy { - name: OrderByEnum! + name: OrderByEnum } input FruitAggregateNumericFieldsOrderBy { - sweetness: OrderByEnum! + sweetness: OrderByEnum } input FruitAggregateOrderBy { diff --git a/tests/unit/mapping/__snapshots__/test_schemas/test_query_schemas[auto_order_by].gql b/tests/unit/mapping/__snapshots__/test_schemas/test_query_schemas[auto_order_by].gql deleted file mode 100644 index 32d165b0..00000000 --- a/tests/unit/mapping/__snapshots__/test_schemas/test_query_schemas[auto_order_by].gql +++ /dev/null @@ -1,339 +0,0 @@ -''' -input ColorAggregateMinMaxStringFieldsOrderBy { - name: OrderByEnum -} - -input ColorAggregateNumericFieldsOrderBy { - name: OrderByEnum -} - -input ColorAggregateOrderBy { - count: OrderByEnum - maxString: ColorAggregateMinMaxStringFieldsOrderBy - minString: ColorAggregateMinMaxStringFieldsOrderBy - sum: ColorAggregateNumericFieldsOrderBy -} - -""" -Boolean expression to compare fields. All fields are combined with logical 'AND'. -""" -input ColorOrderBy { - fruitsAggregate: FruitAggregateOrderBy - fruits: FruitOrderBy - name: OrderByEnum - id: OrderByEnum -} - -"""GraphQL type""" -type ColorType { - fruitsAggregate: FruitAggregate! - - """Fetch objects from the FruitType collection""" - fruits(orderBy: [FruitOrderBy!] = null): [FruitType!]! - name: String! - id: UUID! -} - -"""Aggregation fields""" -type DepartmentAggregate { - count: Int - max: DepartmentMinMaxFields! - min: DepartmentMinMaxFields! - sum: DepartmentSumFields! -} - -input DepartmentAggregateMinMaxStringFieldsOrderBy { - name: OrderByEnum -} - -input DepartmentAggregateNumericFieldsOrderBy { - name: OrderByEnum -} - -input DepartmentAggregateOrderBy { - count: OrderByEnum - maxString: DepartmentAggregateMinMaxStringFieldsOrderBy - minString: DepartmentAggregateMinMaxStringFieldsOrderBy - sum: DepartmentAggregateNumericFieldsOrderBy -} - -"""GraphQL type""" -type DepartmentMinMaxFields { - name: String -} - -""" -Boolean expression to compare fields. All fields are combined with logical 'AND'. -""" -input DepartmentOrderBy { - usersAggregate: UserAggregateOrderBy - users: UserOrderBy - name: OrderByEnum - id: OrderByEnum -} - -"""GraphQL type""" -type DepartmentSumFields { - name: String -} - -"""GraphQL type""" -type DepartmentType { - usersAggregate: UserAggregate! - - """Fetch objects from the UserType collection""" - users(orderBy: [UserOrderBy!] = null): [UserType!]! - name: String - id: UUID! -} - -"""Aggregation fields""" -type FruitAggregate { - avg: FruitNumericFields! - count: Int - max: FruitMinMaxFields! - min: FruitMinMaxFields! - stddevPop: FruitNumericFields! - stddevSamp: FruitNumericFields! - sum: FruitSumFields! - varPop: FruitNumericFields! - varSamp: FruitNumericFields! -} - -input FruitAggregateMinMaxStringFieldsOrderBy { - name: OrderByEnum -} - -input FruitAggregateNumericFieldsOrderBy { - sweetness: OrderByEnum -} - -input FruitAggregateOrderBy { - avg: FruitAggregateNumericFieldsOrderBy - count: OrderByEnum - max: FruitAggregateNumericFieldsOrderBy - maxString: FruitAggregateMinMaxStringFieldsOrderBy - min: FruitAggregateNumericFieldsOrderBy - minString: FruitAggregateMinMaxStringFieldsOrderBy - stddevPop: FruitAggregateNumericFieldsOrderBy - stddevSamp: FruitAggregateNumericFieldsOrderBy - sum: FruitAggregateNumericFieldsOrderBy - varPop: FruitAggregateNumericFieldsOrderBy - varSamp: FruitAggregateNumericFieldsOrderBy -} - -"""GraphQL type""" -type FruitMinMaxFields { - name: String - sweetness: Int -} - -"""GraphQL type""" -type FruitNumericFields { - sweetness: Float -} - -""" -Boolean expression to compare fields. All fields are combined with logical 'AND'. -""" -input FruitOrderBy { - colorAggregate: ColorAggregateOrderBy - color: ColorOrderBy - name: OrderByEnum - colorId: OrderByEnum - sweetness: OrderByEnum - id: OrderByEnum -} - -"""GraphQL type""" -type FruitSumFields { - name: String - sweetness: Int -} - -"""GraphQL type""" -type FruitType { - color: ColorType! - name: String! - colorId: UUID - sweetness: Int! - id: UUID! -} - -"""Aggregation fields""" -type GroupAggregate { - count: Int - max: GroupMinMaxFields! - min: GroupMinMaxFields! - sum: GroupSumFields! -} - -input GroupAggregateMinMaxStringFieldsOrderBy { - name: OrderByEnum -} - -input GroupAggregateNumericFieldsOrderBy { - name: OrderByEnum -} - -input GroupAggregateOrderBy { - count: OrderByEnum - maxString: GroupAggregateMinMaxStringFieldsOrderBy - minString: GroupAggregateMinMaxStringFieldsOrderBy - sum: GroupAggregateNumericFieldsOrderBy -} - -"""GraphQL type""" -type GroupMinMaxFields { - name: String -} - -""" -Boolean expression to compare fields. All fields are combined with logical 'AND'. -""" -input GroupOrderBy { - tagAggregate: TagAggregateOrderBy - tag: TagOrderBy - usersAggregate: UserAggregateOrderBy - users: UserOrderBy - colorAggregate: ColorAggregateOrderBy - color: ColorOrderBy - name: OrderByEnum - tagId: OrderByEnum - colorId: OrderByEnum - id: OrderByEnum -} - -"""GraphQL type""" -type GroupSumFields { - name: String -} - -"""GraphQL type""" -type GroupType { - tag: TagType! - usersAggregate: UserAggregate! - - """Fetch objects from the UserType collection""" - users(orderBy: [UserOrderBy!] = null): [UserType!]! - color: ColorType! - name: String! - tagId: UUID! - colorId: UUID! - id: UUID! -} - -enum OrderByEnum { - ASC - ASC_NULLS_FIRST - ASC_NULLS_LAST - DESC - DESC_NULLS_FIRST - DESC_NULLS_LAST -} - -type Query { - """Fetch objects from the GroupType collection""" - group: [GroupType!]! -} - -input TagAggregateMinMaxStringFieldsOrderBy { - name: OrderByEnum -} - -input TagAggregateNumericFieldsOrderBy { - name: OrderByEnum -} - -input TagAggregateOrderBy { - count: OrderByEnum - maxString: TagAggregateMinMaxStringFieldsOrderBy - minString: TagAggregateMinMaxStringFieldsOrderBy - sum: TagAggregateNumericFieldsOrderBy -} - -""" -Boolean expression to compare fields. All fields are combined with logical 'AND'. -""" -input TagOrderBy { - groupsAggregate: GroupAggregateOrderBy - groups: GroupOrderBy - name: OrderByEnum - id: OrderByEnum -} - -"""GraphQL type""" -type TagType { - groupsAggregate: GroupAggregate! - - """Fetch objects from the GroupType collection""" - groups(orderBy: [GroupOrderBy!] = null): [GroupType!]! - name: String! - id: UUID! -} - -scalar UUID - -"""Aggregation fields""" -type UserAggregate { - count: Int - max: UserMinMaxFields! - min: UserMinMaxFields! - sum: UserSumFields! -} - -input UserAggregateMinMaxStringFieldsOrderBy { - name: OrderByEnum -} - -input UserAggregateNumericFieldsOrderBy { - name: OrderByEnum -} - -input UserAggregateOrderBy { - count: OrderByEnum - maxString: UserAggregateMinMaxStringFieldsOrderBy - minString: UserAggregateMinMaxStringFieldsOrderBy - sum: UserAggregateNumericFieldsOrderBy -} - -"""GraphQL type""" -type UserMinMaxFields { - name: String -} - -""" -Boolean expression to compare fields. All fields are combined with logical 'AND'. -""" -input UserOrderBy { - groupAggregate: GroupAggregateOrderBy - group: GroupOrderBy - tagAggregate: TagAggregateOrderBy - tag: TagOrderBy - departmentsAggregate: DepartmentAggregateOrderBy - departments: DepartmentOrderBy - name: OrderByEnum - groupId: OrderByEnum - tagId: OrderByEnum - id: OrderByEnum -} - -"""GraphQL type""" -type UserSumFields { - name: String -} - -"""GraphQL type""" -type UserType { - group: GroupType! - tag: TagType! - departmentsAggregate: DepartmentAggregate! - - """Fetch objects from the DepartmentType collection""" - departments(orderBy: [DepartmentOrderBy!] = null): [DepartmentType!]! - name: String! - groupId: UUID - tagId: UUID - id: UUID! -} -''' \ No newline at end of file diff --git a/tests/unit/mapping/__snapshots__/test_schemas/test_query_schemas[distinct_config_all].gql b/tests/unit/mapping/__snapshots__/test_schemas/test_query_schemas[distinct_config_all].gql new file mode 100644 index 00000000..e858f495 --- /dev/null +++ b/tests/unit/mapping/__snapshots__/test_schemas/test_query_schemas[distinct_config_all].gql @@ -0,0 +1,69 @@ +''' +enum ColorDistinctOnFields { + name + id +} + +"""GraphQL type""" +type ColorType { + fruitsAggregate: FruitAggregate! + + """Fetch objects from the FruitType collection""" + fruits(distinctOn: [FruitDistinctOnFields!] = null): [FruitType!]! + name: String! + id: UUID! +} + +"""Aggregation fields""" +type FruitAggregate { + avg: FruitNumericFields! + count: Int + max: FruitMinMaxFields! + min: FruitMinMaxFields! + stddevPop: FruitNumericFields! + stddevSamp: FruitNumericFields! + sum: FruitSumFields! + varPop: FruitNumericFields! + varSamp: FruitNumericFields! +} + +enum FruitDistinctOnFields { + name + colorId + sweetness + id +} + +"""GraphQL type""" +type FruitMinMaxFields { + name: String + sweetness: Int +} + +"""GraphQL type""" +type FruitNumericFields { + sweetness: Float +} + +"""GraphQL type""" +type FruitSumFields { + name: String + sweetness: Int +} + +"""GraphQL type""" +type FruitType { + color: ColorType! + name: String! + colorId: UUID + sweetness: Int! + id: UUID! +} + +type Query { + """Fetch objects from the ColorType collection""" + colors(distinctOn: [ColorDistinctOnFields!] = null): [ColorType!]! +} + +scalar UUID +''' \ No newline at end of file diff --git a/tests/unit/mapping/__snapshots__/test_schemas/test_query_schemas[distinct_config_empty].gql b/tests/unit/mapping/__snapshots__/test_schemas/test_query_schemas[distinct_config_empty].gql new file mode 100644 index 00000000..4121398a --- /dev/null +++ b/tests/unit/mapping/__snapshots__/test_schemas/test_query_schemas[distinct_config_empty].gql @@ -0,0 +1,57 @@ +''' +"""GraphQL type""" +type ColorType { + fruitsAggregate: FruitAggregate! + + """Fetch objects from the FruitType collection""" + fruits: [FruitType!]! + name: String! + id: UUID! +} + +"""Aggregation fields""" +type FruitAggregate { + avg: FruitNumericFields! + count: Int + max: FruitMinMaxFields! + min: FruitMinMaxFields! + stddevPop: FruitNumericFields! + stddevSamp: FruitNumericFields! + sum: FruitSumFields! + varPop: FruitNumericFields! + varSamp: FruitNumericFields! +} + +"""GraphQL type""" +type FruitMinMaxFields { + name: String + sweetness: Int +} + +"""GraphQL type""" +type FruitNumericFields { + sweetness: Float +} + +"""GraphQL type""" +type FruitSumFields { + name: String + sweetness: Int +} + +"""GraphQL type""" +type FruitType { + color: ColorType! + name: String! + colorId: UUID + sweetness: Int! + id: UUID! +} + +type Query { + """Fetch objects from the ColorType collection""" + colors: [ColorType!]! +} + +scalar UUID +''' \ No newline at end of file diff --git a/tests/unit/mapping/__snapshots__/test_schemas/test_query_schemas[distinct_config_specific_fields].gql b/tests/unit/mapping/__snapshots__/test_schemas/test_query_schemas[distinct_config_specific_fields].gql new file mode 100644 index 00000000..b0ee37ba --- /dev/null +++ b/tests/unit/mapping/__snapshots__/test_schemas/test_query_schemas[distinct_config_specific_fields].gql @@ -0,0 +1,65 @@ +''' +enum ColorDistinctOnFields { + name +} + +"""GraphQL type""" +type ColorType { + fruitsAggregate: FruitAggregate! + + """Fetch objects from the FruitType collection""" + fruits(distinctOn: [FruitDistinctOnFields!] = null): [FruitType!]! + name: String! + id: UUID! +} + +"""Aggregation fields""" +type FruitAggregate { + avg: FruitNumericFields! + count: Int + max: FruitMinMaxFields! + min: FruitMinMaxFields! + stddevPop: FruitNumericFields! + stddevSamp: FruitNumericFields! + sum: FruitSumFields! + varPop: FruitNumericFields! + varSamp: FruitNumericFields! +} + +enum FruitDistinctOnFields { + name +} + +"""GraphQL type""" +type FruitMinMaxFields { + name: String + sweetness: Int +} + +"""GraphQL type""" +type FruitNumericFields { + sweetness: Float +} + +"""GraphQL type""" +type FruitSumFields { + name: String + sweetness: Int +} + +"""GraphQL type""" +type FruitType { + color: ColorType! + name: String! + colorId: UUID + sweetness: Int! + id: UUID! +} + +type Query { + """Fetch objects from the ColorType collection""" + colors(distinctOn: [ColorDistinctOnFields!] = null): [ColorType!]! +} + +scalar UUID +''' \ No newline at end of file diff --git a/tests/unit/mapping/__snapshots__/test_schemas/test_query_schemas[distinct_config_with_field_override].gql b/tests/unit/mapping/__snapshots__/test_schemas/test_query_schemas[distinct_config_with_field_override].gql new file mode 100644 index 00000000..ca5c2cbb --- /dev/null +++ b/tests/unit/mapping/__snapshots__/test_schemas/test_query_schemas[distinct_config_with_field_override].gql @@ -0,0 +1,68 @@ +''' +enum ColorDistinctOnFields { + name +} + +"""GraphQL type""" +type ColorType { + fruitsAggregate: FruitAggregate! + + """Fetch objects from the FruitType collection""" + fruits(distinctOn: [FruitDistinctOnFields!] = null): [FruitType!]! + name: String! + id: UUID! +} + +"""Aggregation fields""" +type FruitAggregate { + avg: FruitNumericFields! + count: Int + max: FruitMinMaxFields! + min: FruitMinMaxFields! + stddevPop: FruitNumericFields! + stddevSamp: FruitNumericFields! + sum: FruitSumFields! + varPop: FruitNumericFields! + varSamp: FruitNumericFields! +} + +enum FruitDistinctOnFields { + name + colorId + sweetness + id +} + +"""GraphQL type""" +type FruitMinMaxFields { + name: String + sweetness: Int +} + +"""GraphQL type""" +type FruitNumericFields { + sweetness: Float +} + +"""GraphQL type""" +type FruitSumFields { + name: String + sweetness: Int +} + +"""GraphQL type""" +type FruitType { + color: ColorType! + name: String! + colorId: UUID + sweetness: Int! + id: UUID! +} + +type Query { + """Fetch objects from the ColorType collection""" + colors(distinctOn: [ColorDistinctOnFields!] = null): [ColorType!]! +} + +scalar UUID +''' \ No newline at end of file diff --git a/tests/unit/mapping/__snapshots__/test_schemas/test_query_schemas[field_distinct_all].gql b/tests/unit/mapping/__snapshots__/test_schemas/test_query_schemas[field_distinct_all].gql new file mode 100644 index 00000000..2a8310bc --- /dev/null +++ b/tests/unit/mapping/__snapshots__/test_schemas/test_query_schemas[field_distinct_all].gql @@ -0,0 +1,62 @@ +''' +enum ColorDistinctOnFields { + name + id +} + +"""GraphQL type""" +type ColorType { + fruitsAggregate: FruitAggregate! + + """Fetch objects from the FruitType collection""" + fruits: [FruitType!]! + name: String! + id: UUID! +} + +"""Aggregation fields""" +type FruitAggregate { + avg: FruitNumericFields! + count: Int + max: FruitMinMaxFields! + min: FruitMinMaxFields! + stddevPop: FruitNumericFields! + stddevSamp: FruitNumericFields! + sum: FruitSumFields! + varPop: FruitNumericFields! + varSamp: FruitNumericFields! +} + +"""GraphQL type""" +type FruitMinMaxFields { + name: String + sweetness: Int +} + +"""GraphQL type""" +type FruitNumericFields { + sweetness: Float +} + +"""GraphQL type""" +type FruitSumFields { + name: String + sweetness: Int +} + +"""GraphQL type""" +type FruitType { + color: ColorType! + name: String! + colorId: UUID + sweetness: Int! + id: UUID! +} + +type Query { + """Fetch objects from the ColorType collection""" + colors(distinctOn: [ColorDistinctOnFields!] = null): [ColorType!]! +} + +scalar UUID +''' \ No newline at end of file diff --git a/tests/unit/mapping/__snapshots__/test_schemas/test_query_schemas[field_distinct_specific_fields].gql b/tests/unit/mapping/__snapshots__/test_schemas/test_query_schemas[field_distinct_specific_fields].gql new file mode 100644 index 00000000..ef46c7b2 --- /dev/null +++ b/tests/unit/mapping/__snapshots__/test_schemas/test_query_schemas[field_distinct_specific_fields].gql @@ -0,0 +1,61 @@ +''' +enum ColorDistinctOnFields { + name +} + +"""GraphQL type""" +type ColorType { + fruitsAggregate: FruitAggregate! + + """Fetch objects from the FruitType collection""" + fruits: [FruitType!]! + name: String! + id: UUID! +} + +"""Aggregation fields""" +type FruitAggregate { + avg: FruitNumericFields! + count: Int + max: FruitMinMaxFields! + min: FruitMinMaxFields! + stddevPop: FruitNumericFields! + stddevSamp: FruitNumericFields! + sum: FruitSumFields! + varPop: FruitNumericFields! + varSamp: FruitNumericFields! +} + +"""GraphQL type""" +type FruitMinMaxFields { + name: String + sweetness: Int +} + +"""GraphQL type""" +type FruitNumericFields { + sweetness: Float +} + +"""GraphQL type""" +type FruitSumFields { + name: String + sweetness: Int +} + +"""GraphQL type""" +type FruitType { + color: ColorType! + name: String! + colorId: UUID + sweetness: Int! + id: UUID! +} + +type Query { + """Fetch objects from the ColorType collection""" + colors(distinctOn: [ColorDistinctOnFields!] = null): [ColorType!]! +} + +scalar UUID +''' \ No newline at end of file diff --git a/tests/unit/mapping/__snapshots__/test_schemas/test_query_schemas[field_filter_auto_generate].gql b/tests/unit/mapping/__snapshots__/test_schemas/test_query_schemas[field_filter_auto_generate].gql new file mode 100644 index 00000000..68f44ab4 --- /dev/null +++ b/tests/unit/mapping/__snapshots__/test_schemas/test_query_schemas[field_filter_auto_generate].gql @@ -0,0 +1,324 @@ +''' +""" +Boolean expression to compare aggregated fields. All fields are combined with logical 'AND'. +""" +input ColorAggregateBoolExp { + count: ColorAggregateBoolExpCount + maxString: ColorAggregateBoolExpMaxstring + minString: ColorAggregateBoolExpMinstring + sum: ColorAggregateBoolExpSum +} + +"""Boolean expression to compare count aggregation.""" +input ColorAggregateBoolExpCount { + arguments: [ColorCountFields!] = [] + predicate: IntOrderComparison! + distinct: Boolean = false +} + +"""Boolean expression to compare max aggregation.""" +input ColorAggregateBoolExpMaxstring { + arguments: [ColorMinMaxStringFieldsEnum!]! + predicate: TextComparison! + distinct: Boolean = false +} + +"""Boolean expression to compare min aggregation.""" +input ColorAggregateBoolExpMinstring { + arguments: [ColorMinMaxStringFieldsEnum!]! + predicate: TextComparison! + distinct: Boolean = false +} + +"""Boolean expression to compare sum aggregation.""" +input ColorAggregateBoolExpSum { + arguments: [ColorSumFieldsEnum!]! + predicate: FloatOrderComparison! + distinct: Boolean = false +} + +""" +Boolean expression to compare fields. All fields are combined with logical 'AND'. +""" +input ColorBoolExp { + _and: [ColorBoolExp!]! = [] + _or: [ColorBoolExp!]! = [] + _not: ColorBoolExp + fruitsAggregate: FruitAggregateBoolExp + fruits: FruitBoolExp + name: TextComparison + id: UUIDGenericComparison +} + +enum ColorCountFields { + name + id +} + +enum ColorMinMaxStringFieldsEnum { + name +} + +enum ColorSumFieldsEnum { + name +} + +"""GraphQL type""" +type ColorType { + fruitsAggregate: FruitAggregate! + + """Fetch objects from the FruitType collection""" + fruits: [FruitType!]! + name: String! + id: UUID! +} + +""" +Boolean expression to compare fields supporting order comparisons. All fields are combined with logical 'AND' +""" +input FloatOrderComparison { + eq: Float + neq: Float + isNull: Boolean + in: [Float!] + nin: [Float!] + gt: Float + gte: Float + lt: Float + lte: Float +} + +"""Aggregation fields""" +type FruitAggregate { + avg: FruitNumericFields! + count: Int + max: FruitMinMaxFields! + min: FruitMinMaxFields! + stddevPop: FruitNumericFields! + stddevSamp: FruitNumericFields! + sum: FruitSumFields! + varPop: FruitNumericFields! + varSamp: FruitNumericFields! +} + +""" +Boolean expression to compare aggregated fields. All fields are combined with logical 'AND'. +""" +input FruitAggregateBoolExp { + avg: FruitAggregateBoolExpAvg + count: FruitAggregateBoolExpCount + max: FruitAggregateBoolExpMax + maxString: FruitAggregateBoolExpMaxstring + min: FruitAggregateBoolExpMin + minString: FruitAggregateBoolExpMinstring + stddevPop: FruitAggregateBoolExpStddevpop + stddevSamp: FruitAggregateBoolExpStddevsamp + sum: FruitAggregateBoolExpSum + varPop: FruitAggregateBoolExpVarpop + varSamp: FruitAggregateBoolExpVarsamp +} + +"""Boolean expression to compare avg aggregation.""" +input FruitAggregateBoolExpAvg { + arguments: [FruitNumericFieldsEnum!]! + predicate: FloatOrderComparison! + distinct: Boolean = false +} + +"""Boolean expression to compare count aggregation.""" +input FruitAggregateBoolExpCount { + arguments: [FruitCountFields!] = [] + predicate: IntOrderComparison! + distinct: Boolean = false +} + +"""Boolean expression to compare max aggregation.""" +input FruitAggregateBoolExpMax { + arguments: [FruitMinMaxNumericFieldsEnum!]! + predicate: FloatOrderComparison! + distinct: Boolean = false +} + +"""Boolean expression to compare max aggregation.""" +input FruitAggregateBoolExpMaxstring { + arguments: [FruitMinMaxStringFieldsEnum!]! + predicate: TextComparison! + distinct: Boolean = false +} + +"""Boolean expression to compare min aggregation.""" +input FruitAggregateBoolExpMin { + arguments: [FruitMinMaxNumericFieldsEnum!]! + predicate: FloatOrderComparison! + distinct: Boolean = false +} + +"""Boolean expression to compare min aggregation.""" +input FruitAggregateBoolExpMinstring { + arguments: [FruitMinMaxStringFieldsEnum!]! + predicate: TextComparison! + distinct: Boolean = false +} + +"""Boolean expression to compare stddev_pop aggregation.""" +input FruitAggregateBoolExpStddevpop { + arguments: [FruitNumericFieldsEnum!]! + predicate: FloatOrderComparison! + distinct: Boolean = false +} + +"""Boolean expression to compare stddev_samp aggregation.""" +input FruitAggregateBoolExpStddevsamp { + arguments: [FruitNumericFieldsEnum!]! + predicate: FloatOrderComparison! + distinct: Boolean = false +} + +"""Boolean expression to compare sum aggregation.""" +input FruitAggregateBoolExpSum { + arguments: [FruitSumFieldsEnum!]! + predicate: FloatOrderComparison! + distinct: Boolean = false +} + +"""Boolean expression to compare var_pop aggregation.""" +input FruitAggregateBoolExpVarpop { + arguments: [FruitNumericFieldsEnum!]! + predicate: FloatOrderComparison! + distinct: Boolean = false +} + +"""Boolean expression to compare var_samp aggregation.""" +input FruitAggregateBoolExpVarsamp { + arguments: [FruitNumericFieldsEnum!]! + predicate: FloatOrderComparison! + distinct: Boolean = false +} + +""" +Boolean expression to compare fields. All fields are combined with logical 'AND'. +""" +input FruitBoolExp { + _and: [FruitBoolExp!]! = [] + _or: [FruitBoolExp!]! = [] + _not: FruitBoolExp + colorAggregate: ColorAggregateBoolExp + color: ColorBoolExp + name: TextComparison + colorId: UUIDGenericComparison + sweetness: IntOrderComparison + id: UUIDGenericComparison +} + +enum FruitCountFields { + name + colorId + sweetness + id +} + +"""GraphQL type""" +type FruitMinMaxFields { + name: String + sweetness: Int +} + +enum FruitMinMaxNumericFieldsEnum { + sweetness +} + +enum FruitMinMaxStringFieldsEnum { + name +} + +"""GraphQL type""" +type FruitNumericFields { + sweetness: Float +} + +enum FruitNumericFieldsEnum { + sweetness +} + +"""GraphQL type""" +type FruitSumFields { + name: String + sweetness: Int +} + +enum FruitSumFieldsEnum { + name + sweetness +} + +"""GraphQL type""" +type FruitType { + color: ColorType! + name: String! + colorId: UUID + sweetness: Int! + id: UUID! +} + +""" +Boolean expression to compare fields supporting order comparisons. All fields are combined with logical 'AND' +""" +input IntOrderComparison { + eq: Int + neq: Int + isNull: Boolean + in: [Int!] + nin: [Int!] + gt: Int + gte: Int + lt: Int + lte: Int +} + +type Query { + """Fetch objects from the FruitType collection""" + fruits(filter: FruitBoolExp = null): [FruitType!]! +} + +""" +Boolean expression to compare String fields. All fields are combined with logical 'AND' +""" +input TextComparison { + eq: String + neq: String + isNull: Boolean + in: [String!] + nin: [String!] + gt: String + gte: String + lt: String + lte: String + like: String + nlike: String + ilike: String + nilike: String + regexp: String + iregexp: String + nregexp: String + inregexp: String + startswith: String + endswith: String + contains: String + istartswith: String + iendswith: String + icontains: String +} + +scalar UUID + +""" +Boolean expression to compare fields supporting equality comparisons. All fields are combined with logical 'AND' +""" +input UUIDGenericComparison { + eq: UUID + neq: UUID + isNull: Boolean + in: [UUID!] + nin: [UUID!] +} +''' \ No newline at end of file diff --git a/tests/unit/mapping/__snapshots__/test_schemas/test_query_schemas[field_order_by_all].gql b/tests/unit/mapping/__snapshots__/test_schemas/test_query_schemas[field_order_by_all].gql new file mode 100644 index 00000000..7eb68e81 --- /dev/null +++ b/tests/unit/mapping/__snapshots__/test_schemas/test_query_schemas[field_order_by_all].gql @@ -0,0 +1,125 @@ +''' +input ColorAggregateMinMaxStringFieldsOrderBy { + name: OrderByEnum +} + +input ColorAggregateNumericFieldsOrderBy { + name: OrderByEnum +} + +input ColorAggregateOrderBy { + count: OrderByEnum + maxString: ColorAggregateMinMaxStringFieldsOrderBy + minString: ColorAggregateMinMaxStringFieldsOrderBy + sum: ColorAggregateNumericFieldsOrderBy +} + +""" +Boolean expression to compare fields. All fields are combined with logical 'AND'. +""" +input ColorOrderBy { + fruitsAggregate: FruitAggregateOrderBy + fruits: FruitOrderBy + name: OrderByEnum + id: OrderByEnum +} + +"""GraphQL type""" +type ColorType { + fruitsAggregate: FruitAggregate! + + """Fetch objects from the FruitType collection""" + fruits: [FruitType!]! + name: String! + id: UUID! +} + +"""Aggregation fields""" +type FruitAggregate { + avg: FruitNumericFields! + count: Int + max: FruitMinMaxFields! + min: FruitMinMaxFields! + stddevPop: FruitNumericFields! + stddevSamp: FruitNumericFields! + sum: FruitSumFields! + varPop: FruitNumericFields! + varSamp: FruitNumericFields! +} + +input FruitAggregateMinMaxStringFieldsOrderBy { + name: OrderByEnum +} + +input FruitAggregateNumericFieldsOrderBy { + sweetness: OrderByEnum +} + +input FruitAggregateOrderBy { + avg: FruitAggregateNumericFieldsOrderBy + count: OrderByEnum + max: FruitAggregateNumericFieldsOrderBy + maxString: FruitAggregateMinMaxStringFieldsOrderBy + min: FruitAggregateNumericFieldsOrderBy + minString: FruitAggregateMinMaxStringFieldsOrderBy + stddevPop: FruitAggregateNumericFieldsOrderBy + stddevSamp: FruitAggregateNumericFieldsOrderBy + sum: FruitAggregateNumericFieldsOrderBy + varPop: FruitAggregateNumericFieldsOrderBy + varSamp: FruitAggregateNumericFieldsOrderBy +} + +"""GraphQL type""" +type FruitMinMaxFields { + name: String + sweetness: Int +} + +"""GraphQL type""" +type FruitNumericFields { + sweetness: Float +} + +""" +Boolean expression to compare fields. All fields are combined with logical 'AND'. +""" +input FruitOrderBy { + colorAggregate: ColorAggregateOrderBy + color: ColorOrderBy + name: OrderByEnum + colorId: OrderByEnum + sweetness: OrderByEnum + id: OrderByEnum +} + +"""GraphQL type""" +type FruitSumFields { + name: String + sweetness: Int +} + +"""GraphQL type""" +type FruitType { + color: ColorType! + name: String! + colorId: UUID + sweetness: Int! + id: UUID! +} + +enum OrderByEnum { + ASC + ASC_NULLS_FIRST + ASC_NULLS_LAST + DESC + DESC_NULLS_FIRST + DESC_NULLS_LAST +} + +type Query { + """Fetch objects from the FruitType collection""" + fruits(orderBy: [FruitOrderBy!] = null): [FruitType!]! +} + +scalar UUID +''' \ No newline at end of file diff --git a/tests/unit/mapping/__snapshots__/test_schemas/test_query_schemas[field_order_by_specific_fields].gql b/tests/unit/mapping/__snapshots__/test_schemas/test_query_schemas[field_order_by_specific_fields].gql new file mode 100644 index 00000000..80a0def4 --- /dev/null +++ b/tests/unit/mapping/__snapshots__/test_schemas/test_query_schemas[field_order_by_specific_fields].gql @@ -0,0 +1,192 @@ +''' +"""Aggregation fields""" +type ColorAggregate { + count: Int + max: ColorMinMaxFields! + min: ColorMinMaxFields! + sum: ColorSumFields! +} + +input ColorAggregateOrderBy { + count: OrderByEnum +} + +"""GraphQL type""" +type ColorMinMaxFields { + name: String +} + +""" +Boolean expression to compare fields. All fields are combined with logical 'AND'. +""" +input ColorOrderBy { + fruitsAggregate: FruitAggregateOrderBy + fruits: FruitOrderBy + name: OrderByEnum + id: OrderByEnum +} + +"""GraphQL type""" +type ColorSumFields { + name: String +} + +"""GraphQL type""" +type ColorType { + fruitsAggregate: FruitAggregate! + + """Fetch objects from the FruitType collection""" + fruits: [FruitType!]! + name: String! + id: UUID! +} + +""" +Boolean expression to compare fields. All fields are combined with logical 'AND'. +""" +input ContainerOrderBy { + fruitsAggregate: FruitAggregateOrderBy + fruits: FruitOrderBy + vegetablesAggregate: VegetableAggregateOrderBy + vegetables: VegetableOrderBy +} + +"""GraphQL type""" +type ContainerType { + fruitsAggregate: FruitAggregate! + + """Fetch objects from the FruitType collection""" + fruits: [FruitType!]! + vegetablesAggregate: VegetableAggregate! + + """Fetch objects from the VegetableType collection""" + vegetables: [VegetableType!]! + colorsAggregate: ColorAggregate! + + """Fetch objects from the ColorType collection""" + colors: [ColorType!]! + name: String! + id: UUID! +} + +"""Aggregation fields""" +type FruitAggregate { + avg: FruitNumericFields! + count: Int + max: FruitMinMaxFields! + min: FruitMinMaxFields! + stddevPop: FruitNumericFields! + stddevSamp: FruitNumericFields! + sum: FruitSumFields! + varPop: FruitNumericFields! + varSamp: FruitNumericFields! +} + +input FruitAggregateOrderBy { + count: OrderByEnum +} + +"""GraphQL type""" +type FruitMinMaxFields { + name: String + sweetness: Int +} + +"""GraphQL type""" +type FruitNumericFields { + sweetness: Float +} + +""" +Boolean expression to compare fields. All fields are combined with logical 'AND'. +""" +input FruitOrderBy { + colorAggregate: ColorAggregateOrderBy + color: ColorOrderBy + name: OrderByEnum + colorId: OrderByEnum + sweetness: OrderByEnum + id: OrderByEnum +} + +"""GraphQL type""" +type FruitSumFields { + name: String + sweetness: Int +} + +"""GraphQL type""" +type FruitType { + color: ColorType! + name: String! + colorId: UUID + sweetness: Int! + id: UUID! +} + +enum OrderByEnum { + ASC + ASC_NULLS_FIRST + ASC_NULLS_LAST + DESC + DESC_NULLS_FIRST + DESC_NULLS_LAST +} + +type Query { + """Fetch objects from the ContainerType collection""" + containers(orderBy: [ContainerOrderBy!] = null): [ContainerType!]! +} + +scalar UUID + +"""Aggregation fields""" +type VegetableAggregate { + count: Int + max: VegetableMinMaxFields! + min: VegetableMinMaxFields! + sum: VegetableSumFields! +} + +input VegetableAggregateOrderBy { + count: OrderByEnum +} + +enum VegetableFamily { + MUSHROOM + GOURD + CABBAGE + ONION + SEEDS +} + +"""GraphQL type""" +type VegetableMinMaxFields { + name: String + description: String +} + +""" +Boolean expression to compare fields. All fields are combined with logical 'AND'. +""" +input VegetableOrderBy { + family: OrderByEnum + id: OrderByEnum + name: OrderByEnum + description: OrderByEnum +} + +"""GraphQL type""" +type VegetableSumFields { + name: String + description: String +} + +"""GraphQL type""" +type VegetableType { + family: VegetableFamily! + id: UUID! + name: String! + description: String! +} +''' \ No newline at end of file diff --git a/tests/unit/mapping/__snapshots__/test_schemas/test_query_schemas[order_config_all].gql b/tests/unit/mapping/__snapshots__/test_schemas/test_query_schemas[order_config_all].gql new file mode 100644 index 00000000..fd4005ac --- /dev/null +++ b/tests/unit/mapping/__snapshots__/test_schemas/test_query_schemas[order_config_all].gql @@ -0,0 +1,137 @@ +''' +input ColorAggregateMinMaxStringFieldsOrderBy { + name: OrderByEnum +} + +input ColorAggregateNumericFieldsOrderBy { + name: OrderByEnum +} + +input ColorAggregateOrderBy { + count: OrderByEnum + maxString: ColorAggregateMinMaxStringFieldsOrderBy + minString: ColorAggregateMinMaxStringFieldsOrderBy + sum: ColorAggregateNumericFieldsOrderBy +} + +""" +Boolean expression to compare fields. All fields are combined with logical 'AND'. +""" +input ColorOrderBy { + fruitsAggregate: FruitAggregateOrderBy + fruits: FruitOrderBy + name: OrderByEnum + id: OrderByEnum +} + +"""GraphQL type""" +type ColorType { + fruitsAggregate: FruitAggregate! + + """Fetch objects from the FruitType collection""" + fruits(orderBy: [FruitOrderBy!] = null): [FruitType!]! + name: String! + id: UUID! +} + +"""Aggregation fields""" +type FruitAggregate { + avg: FruitNumericFields! + count: Int + max: FruitMinMaxFields! + min: FruitMinMaxFields! + stddevPop: FruitNumericFields! + stddevSamp: FruitNumericFields! + sum: FruitSumFields! + varPop: FruitNumericFields! + varSamp: FruitNumericFields! +} + +input FruitAggregateMinMaxStringFieldsOrderBy { + name: OrderByEnum +} + +input FruitAggregateNumericFieldsOrderBy { + sweetness: OrderByEnum +} + +input FruitAggregateOrderBy { + avg: FruitAggregateNumericFieldsOrderBy + count: OrderByEnum + max: FruitAggregateNumericFieldsOrderBy + maxString: FruitAggregateMinMaxStringFieldsOrderBy + min: FruitAggregateNumericFieldsOrderBy + minString: FruitAggregateMinMaxStringFieldsOrderBy + stddevPop: FruitAggregateNumericFieldsOrderBy + stddevSamp: FruitAggregateNumericFieldsOrderBy + sum: FruitAggregateNumericFieldsOrderBy + varPop: FruitAggregateNumericFieldsOrderBy + varSamp: FruitAggregateNumericFieldsOrderBy +} + +"""GraphQL type""" +type FruitMinMaxFields { + name: String + sweetness: Int +} + +"""GraphQL type""" +type FruitNumericFields { + sweetness: Float +} + +""" +Boolean expression to compare fields. All fields are combined with logical 'AND'. +""" +input FruitOrderBy { + colorAggregate: ColorAggregateOrderBy + color: ColorOrderBy + name: OrderByEnum + colorId: OrderByEnum + sweetness: OrderByEnum + id: OrderByEnum +} + +""" +Boolean expression to compare fields. All fields are combined with logical 'AND'. +""" +input FruitOrderBy1 { + colorAggregate: ColorAggregateOrderBy + color: ColorOrderBy + name: OrderByEnum + colorId: OrderByEnum + sweetness: OrderByEnum + id: OrderByEnum +} + +"""GraphQL type""" +type FruitSumFields { + name: String + sweetness: Int +} + +"""GraphQL type""" +type FruitType { + color: ColorType! + name: String! + colorId: UUID + sweetness: Int! + id: UUID! +} + +enum OrderByEnum { + ASC + ASC_NULLS_FIRST + ASC_NULLS_LAST + DESC + DESC_NULLS_FIRST + DESC_NULLS_LAST +} + +type Query { + """Fetch objects from the FruitType collection""" + fruits(orderBy: [FruitOrderBy1!] = null): [FruitType!]! +} + +scalar UUID +''' \ No newline at end of file diff --git a/tests/unit/mapping/__snapshots__/test_schemas/test_query_schemas[order_config_all_with_field_override].gql b/tests/unit/mapping/__snapshots__/test_schemas/test_query_schemas[order_config_all_with_field_override].gql new file mode 100644 index 00000000..3849a677 --- /dev/null +++ b/tests/unit/mapping/__snapshots__/test_schemas/test_query_schemas[order_config_all_with_field_override].gql @@ -0,0 +1,132 @@ +''' +input ColorAggregateMinMaxStringFieldsOrderBy { + name: OrderByEnum +} + +input ColorAggregateNumericFieldsOrderBy { + name: OrderByEnum +} + +input ColorAggregateOrderBy { + count: OrderByEnum + maxString: ColorAggregateMinMaxStringFieldsOrderBy + minString: ColorAggregateMinMaxStringFieldsOrderBy + sum: ColorAggregateNumericFieldsOrderBy +} + +""" +Boolean expression to compare fields. All fields are combined with logical 'AND'. +""" +input ColorOrderBy { + fruitsAggregate: FruitAggregateOrderBy + fruits: FruitOrderBy + name: OrderByEnum + id: OrderByEnum +} + +"""GraphQL type""" +type ColorType { + fruitsAggregate: FruitAggregate! + + """Fetch objects from the FruitType collection""" + fruits(orderBy: [FruitOrderBy!] = null): [FruitType!]! + name: String! + id: UUID! +} + +"""Aggregation fields""" +type FruitAggregate { + avg: FruitNumericFields! + count: Int + max: FruitMinMaxFields! + min: FruitMinMaxFields! + stddevPop: FruitNumericFields! + stddevSamp: FruitNumericFields! + sum: FruitSumFields! + varPop: FruitNumericFields! + varSamp: FruitNumericFields! +} + +input FruitAggregateMinMaxStringFieldsOrderBy { + name: OrderByEnum +} + +input FruitAggregateNumericFieldsOrderBy { + sweetness: OrderByEnum +} + +input FruitAggregateOrderBy { + avg: FruitAggregateNumericFieldsOrderBy + count: OrderByEnum + max: FruitAggregateNumericFieldsOrderBy + maxString: FruitAggregateMinMaxStringFieldsOrderBy + min: FruitAggregateNumericFieldsOrderBy + minString: FruitAggregateMinMaxStringFieldsOrderBy + stddevPop: FruitAggregateNumericFieldsOrderBy + stddevSamp: FruitAggregateNumericFieldsOrderBy + sum: FruitAggregateNumericFieldsOrderBy + varPop: FruitAggregateNumericFieldsOrderBy + varSamp: FruitAggregateNumericFieldsOrderBy +} + +"""GraphQL type""" +type FruitMinMaxFields { + name: String + sweetness: Int +} + +"""GraphQL type""" +type FruitNumericFields { + sweetness: Float +} + +""" +Boolean expression to compare fields. All fields are combined with logical 'AND'. +""" +input FruitOrderBy { + colorAggregate: ColorAggregateOrderBy + color: ColorOrderBy + name: OrderByEnum + colorId: OrderByEnum + sweetness: OrderByEnum + id: OrderByEnum +} + +""" +Boolean expression to compare fields. All fields are combined with logical 'AND'. +""" +input FruitOrderBy1 { + name: OrderByEnum +} + +"""GraphQL type""" +type FruitSumFields { + name: String + sweetness: Int +} + +"""GraphQL type""" +type FruitType { + color: ColorType! + name: String! + colorId: UUID + sweetness: Int! + id: UUID! +} + +enum OrderByEnum { + ASC + ASC_NULLS_FIRST + ASC_NULLS_LAST + DESC + DESC_NULLS_FIRST + DESC_NULLS_LAST +} + +type Query { + """Fetch objects from the FruitType collection""" + fruits(orderBy: [FruitOrderBy1!] = null): [FruitType!]! +} + +scalar UUID +''' \ No newline at end of file diff --git a/tests/unit/mapping/__snapshots__/test_schemas/test_query_schemas[order_config_empty].gql b/tests/unit/mapping/__snapshots__/test_schemas/test_query_schemas[order_config_empty].gql new file mode 100644 index 00000000..918eed85 --- /dev/null +++ b/tests/unit/mapping/__snapshots__/test_schemas/test_query_schemas[order_config_empty].gql @@ -0,0 +1,57 @@ +''' +"""GraphQL type""" +type ColorType { + fruitsAggregate: FruitAggregate! + + """Fetch objects from the FruitType collection""" + fruits: [FruitType!]! + name: String! + id: UUID! +} + +"""Aggregation fields""" +type FruitAggregate { + avg: FruitNumericFields! + count: Int + max: FruitMinMaxFields! + min: FruitMinMaxFields! + stddevPop: FruitNumericFields! + stddevSamp: FruitNumericFields! + sum: FruitSumFields! + varPop: FruitNumericFields! + varSamp: FruitNumericFields! +} + +"""GraphQL type""" +type FruitMinMaxFields { + name: String + sweetness: Int +} + +"""GraphQL type""" +type FruitNumericFields { + sweetness: Float +} + +"""GraphQL type""" +type FruitSumFields { + name: String + sweetness: Int +} + +"""GraphQL type""" +type FruitType { + color: ColorType! + name: String! + colorId: UUID + sweetness: Int! + id: UUID! +} + +type Query { + """Fetch objects from the FruitType collection""" + fruits: [FruitType!]! +} + +scalar UUID +''' \ No newline at end of file diff --git a/tests/unit/mapping/__snapshots__/test_schemas/test_query_schemas[order_config_empty_with_empty_type_override].gql b/tests/unit/mapping/__snapshots__/test_schemas/test_query_schemas[order_config_empty_with_empty_type_override].gql new file mode 100644 index 00000000..03fdedc2 --- /dev/null +++ b/tests/unit/mapping/__snapshots__/test_schemas/test_query_schemas[order_config_empty_with_empty_type_override].gql @@ -0,0 +1,129 @@ +''' +"""Aggregation fields""" +type ColorAggregate { + count: Int + max: ColorMinMaxFields! + min: ColorMinMaxFields! + sum: ColorSumFields! +} + +"""GraphQL type""" +type ColorMinMaxFields { + name: String +} + +"""GraphQL type""" +type ColorSumFields { + name: String +} + +"""GraphQL type""" +type ColorType { + fruitsAggregate: FruitAggregate! + + """Fetch objects from the FruitType collection""" + fruits: [FruitType!]! + name: String! + id: UUID! +} + +"""GraphQL type""" +type ContainerType { + fruitsAggregate: FruitAggregate! + + """Fetch objects from the FruitType collection""" + fruits: [FruitType!]! + vegetablesAggregate: VegetableAggregate! + + """Fetch objects from the VegetableType collection""" + vegetables: [VegetableType!]! + colorsAggregate: ColorAggregate! + + """Fetch objects from the ColorType collection""" + colors: [ColorType!]! + name: String! + id: UUID! +} + +"""Aggregation fields""" +type FruitAggregate { + avg: FruitNumericFields! + count: Int + max: FruitMinMaxFields! + min: FruitMinMaxFields! + stddevPop: FruitNumericFields! + stddevSamp: FruitNumericFields! + sum: FruitSumFields! + varPop: FruitNumericFields! + varSamp: FruitNumericFields! +} + +"""GraphQL type""" +type FruitMinMaxFields { + name: String + sweetness: Int +} + +"""GraphQL type""" +type FruitNumericFields { + sweetness: Float +} + +"""GraphQL type""" +type FruitSumFields { + name: String + sweetness: Int +} + +"""GraphQL type""" +type FruitType { + color: ColorType! + name: String! + colorId: UUID + sweetness: Int! + id: UUID! +} + +type Query { + """Fetch objects from the ContainerType collection""" + container: [ContainerType!]! +} + +scalar UUID + +"""Aggregation fields""" +type VegetableAggregate { + count: Int + max: VegetableMinMaxFields! + min: VegetableMinMaxFields! + sum: VegetableSumFields! +} + +enum VegetableFamily { + MUSHROOM + GOURD + CABBAGE + ONION + SEEDS +} + +"""GraphQL type""" +type VegetableMinMaxFields { + name: String + description: String +} + +"""GraphQL type""" +type VegetableSumFields { + name: String + description: String +} + +"""GraphQL type""" +type VegetableType { + family: VegetableFamily! + id: UUID! + name: String! + description: String! +} +''' \ No newline at end of file diff --git a/tests/unit/mapping/__snapshots__/test_schemas/test_query_schemas[order_config_specific_fields].gql b/tests/unit/mapping/__snapshots__/test_schemas/test_query_schemas[order_config_specific_fields].gql new file mode 100644 index 00000000..bc92e35d --- /dev/null +++ b/tests/unit/mapping/__snapshots__/test_schemas/test_query_schemas[order_config_specific_fields].gql @@ -0,0 +1,82 @@ +''' +"""GraphQL type""" +type ColorType { + fruitsAggregate: FruitAggregate! + + """Fetch objects from the FruitType collection""" + fruits(orderBy: [FruitOrderBy!] = null): [FruitType!]! + name: String! + id: UUID! +} + +"""Aggregation fields""" +type FruitAggregate { + avg: FruitNumericFields! + count: Int + max: FruitMinMaxFields! + min: FruitMinMaxFields! + stddevPop: FruitNumericFields! + stddevSamp: FruitNumericFields! + sum: FruitSumFields! + varPop: FruitNumericFields! + varSamp: FruitNumericFields! +} + +"""GraphQL type""" +type FruitMinMaxFields { + name: String + sweetness: Int +} + +"""GraphQL type""" +type FruitNumericFields { + sweetness: Float +} + +""" +Boolean expression to compare fields. All fields are combined with logical 'AND'. +""" +input FruitOrderBy { + name: OrderByEnum + sweetness: OrderByEnum +} + +""" +Boolean expression to compare fields. All fields are combined with logical 'AND'. +""" +input FruitOrderBy1 { + name: OrderByEnum + sweetness: OrderByEnum +} + +"""GraphQL type""" +type FruitSumFields { + name: String + sweetness: Int +} + +"""GraphQL type""" +type FruitType { + color: ColorType! + name: String! + colorId: UUID + sweetness: Int! + id: UUID! +} + +enum OrderByEnum { + ASC + ASC_NULLS_FIRST + ASC_NULLS_LAST + DESC + DESC_NULLS_FIRST + DESC_NULLS_LAST +} + +type Query { + """Fetch objects from the FruitType collection""" + fruits(orderBy: [FruitOrderBy1!] = null): [FruitType!]! +} + +scalar UUID +''' \ No newline at end of file diff --git a/tests/unit/mapping/__snapshots__/test_schemas/test_query_schemas[order_config_specific_fields_with_type_override].gql b/tests/unit/mapping/__snapshots__/test_schemas/test_query_schemas[order_config_specific_fields_with_type_override].gql new file mode 100644 index 00000000..b0b04334 --- /dev/null +++ b/tests/unit/mapping/__snapshots__/test_schemas/test_query_schemas[order_config_specific_fields_with_type_override].gql @@ -0,0 +1,207 @@ +''' +"""Aggregation fields""" +type ColorAggregate { + count: Int + max: ColorMinMaxFields! + min: ColorMinMaxFields! + sum: ColorSumFields! +} + +input ColorAggregateMinMaxStringFieldsOrderBy { + name: OrderByEnum +} + +input ColorAggregateNumericFieldsOrderBy { + name: OrderByEnum +} + +input ColorAggregateOrderBy { + count: OrderByEnum + maxString: ColorAggregateMinMaxStringFieldsOrderBy + minString: ColorAggregateMinMaxStringFieldsOrderBy + sum: ColorAggregateNumericFieldsOrderBy +} + +"""GraphQL type""" +type ColorMinMaxFields { + name: String +} + +""" +Boolean expression to compare fields. All fields are combined with logical 'AND'. +""" +input ColorOrderBy { + fruitsAggregate: FruitAggregateOrderBy + fruits: FruitOrderBy + name: OrderByEnum + id: OrderByEnum +} + +"""GraphQL type""" +type ColorSumFields { + name: String +} + +"""GraphQL type""" +type ColorType { + fruitsAggregate: FruitAggregate! + + """Fetch objects from the FruitType collection""" + fruits: [FruitType!]! + name: String! + id: UUID! +} + +"""GraphQL type""" +type ContainerType { + fruitsAggregate: FruitAggregate! + + """Fetch objects from the FruitType collection""" + fruits(orderBy: [FruitOrderBy!] = null): [FruitType!]! + vegetablesAggregate: VegetableAggregate! + + """Fetch objects from the VegetableType collection""" + vegetables(orderBy: [VegetableOrderBy!] = null): [VegetableType!]! + colorsAggregate: ColorAggregate! + + """Fetch objects from the ColorType collection""" + colors: [ColorType!]! + name: String! + id: UUID! +} + +"""Aggregation fields""" +type FruitAggregate { + avg: FruitNumericFields! + count: Int + max: FruitMinMaxFields! + min: FruitMinMaxFields! + stddevPop: FruitNumericFields! + stddevSamp: FruitNumericFields! + sum: FruitSumFields! + varPop: FruitNumericFields! + varSamp: FruitNumericFields! +} + +input FruitAggregateMinMaxStringFieldsOrderBy { + name: OrderByEnum +} + +input FruitAggregateNumericFieldsOrderBy { + sweetness: OrderByEnum +} + +input FruitAggregateOrderBy { + avg: FruitAggregateNumericFieldsOrderBy + count: OrderByEnum + max: FruitAggregateNumericFieldsOrderBy + maxString: FruitAggregateMinMaxStringFieldsOrderBy + min: FruitAggregateNumericFieldsOrderBy + minString: FruitAggregateMinMaxStringFieldsOrderBy + stddevPop: FruitAggregateNumericFieldsOrderBy + stddevSamp: FruitAggregateNumericFieldsOrderBy + sum: FruitAggregateNumericFieldsOrderBy + varPop: FruitAggregateNumericFieldsOrderBy + varSamp: FruitAggregateNumericFieldsOrderBy +} + +"""GraphQL type""" +type FruitMinMaxFields { + name: String + sweetness: Int +} + +"""GraphQL type""" +type FruitNumericFields { + sweetness: Float +} + +""" +Boolean expression to compare fields. All fields are combined with logical 'AND'. +""" +input FruitOrderBy { + colorAggregate: ColorAggregateOrderBy + color: ColorOrderBy + name: OrderByEnum + colorId: OrderByEnum + sweetness: OrderByEnum + id: OrderByEnum +} + +"""GraphQL type""" +type FruitSumFields { + name: String + sweetness: Int +} + +"""GraphQL type""" +type FruitType { + color: ColorType! + name: String! + colorId: UUID + sweetness: Int! + id: UUID! +} + +enum OrderByEnum { + ASC + ASC_NULLS_FIRST + ASC_NULLS_LAST + DESC + DESC_NULLS_FIRST + DESC_NULLS_LAST +} + +type Query { + """Fetch objects from the ContainerType collection""" + container: [ContainerType!]! +} + +scalar UUID + +"""Aggregation fields""" +type VegetableAggregate { + count: Int + max: VegetableMinMaxFields! + min: VegetableMinMaxFields! + sum: VegetableSumFields! +} + +enum VegetableFamily { + MUSHROOM + GOURD + CABBAGE + ONION + SEEDS +} + +"""GraphQL type""" +type VegetableMinMaxFields { + name: String + description: String +} + +""" +Boolean expression to compare fields. All fields are combined with logical 'AND'. +""" +input VegetableOrderBy { + family: OrderByEnum + id: OrderByEnum + name: OrderByEnum + description: OrderByEnum +} + +"""GraphQL type""" +type VegetableSumFields { + name: String + description: String +} + +"""GraphQL type""" +type VegetableType { + family: VegetableFamily! + id: UUID! + name: String! + description: String! +} +''' \ No newline at end of file diff --git a/tests/unit/mapping/__snapshots__/test_schemas/test_query_schemas[order_config_with_field_override].gql b/tests/unit/mapping/__snapshots__/test_schemas/test_query_schemas[order_config_with_field_override].gql new file mode 100644 index 00000000..e5fb8804 --- /dev/null +++ b/tests/unit/mapping/__snapshots__/test_schemas/test_query_schemas[order_config_with_field_override].gql @@ -0,0 +1,80 @@ +''' +"""GraphQL type""" +type ColorType { + fruitsAggregate: FruitAggregate! + + """Fetch objects from the FruitType collection""" + fruits(orderBy: [FruitOrderBy!] = null): [FruitType!]! + name: String! + id: UUID! +} + +"""Aggregation fields""" +type FruitAggregate { + avg: FruitNumericFields! + count: Int + max: FruitMinMaxFields! + min: FruitMinMaxFields! + stddevPop: FruitNumericFields! + stddevSamp: FruitNumericFields! + sum: FruitSumFields! + varPop: FruitNumericFields! + varSamp: FruitNumericFields! +} + +"""GraphQL type""" +type FruitMinMaxFields { + name: String + sweetness: Int +} + +"""GraphQL type""" +type FruitNumericFields { + sweetness: Float +} + +""" +Boolean expression to compare fields. All fields are combined with logical 'AND'. +""" +input FruitOrderBy { + name: OrderByEnum +} + +""" +Boolean expression to compare fields. All fields are combined with logical 'AND'. +""" +input FruitOrderBy1 { + sweetness: OrderByEnum +} + +"""GraphQL type""" +type FruitSumFields { + name: String + sweetness: Int +} + +"""GraphQL type""" +type FruitType { + color: ColorType! + name: String! + colorId: UUID + sweetness: Int! + id: UUID! +} + +enum OrderByEnum { + ASC + ASC_NULLS_FIRST + ASC_NULLS_LAST + DESC + DESC_NULLS_FIRST + DESC_NULLS_LAST +} + +type Query { + """Fetch objects from the FruitType collection""" + fruits(orderBy: [FruitOrderBy1!] = null): [FruitType!]! +} + +scalar UUID +''' \ No newline at end of file diff --git a/tests/unit/mapping/__snapshots__/test_schemas/test_query_schemas[paginate_all_with_default].gql b/tests/unit/mapping/__snapshots__/test_schemas/test_query_schemas[paginate_all_with_default].gql new file mode 100644 index 00000000..1540a4c6 --- /dev/null +++ b/tests/unit/mapping/__snapshots__/test_schemas/test_query_schemas[paginate_all_with_default].gql @@ -0,0 +1,57 @@ +''' +"""GraphQL type""" +type ColorType { + fruitsAggregate: FruitAggregate! + + """Fetch objects from the FruitType collection""" + fruits(limit: Int = 20, offset: Int! = 5): [FruitType!]! + name: String! + id: UUID! +} + +"""Aggregation fields""" +type FruitAggregate { + avg: FruitNumericFields! + count: Int + max: FruitMinMaxFields! + min: FruitMinMaxFields! + stddevPop: FruitNumericFields! + stddevSamp: FruitNumericFields! + sum: FruitSumFields! + varPop: FruitNumericFields! + varSamp: FruitNumericFields! +} + +"""GraphQL type""" +type FruitMinMaxFields { + name: String + sweetness: Int +} + +"""GraphQL type""" +type FruitNumericFields { + sweetness: Float +} + +"""GraphQL type""" +type FruitSumFields { + name: String + sweetness: Int +} + +"""GraphQL type""" +type FruitType { + color: ColorType! + name: String! + colorId: UUID + sweetness: Int! + id: UUID! +} + +type Query { + """Fetch objects from the FruitType collection""" + fruitWithCustomDefault: [FruitType!]! +} + +scalar UUID +''' \ No newline at end of file diff --git a/tests/unit/mapping/__snapshots__/test_schemas/test_query_schemas[paginate_and_order_combined].gql b/tests/unit/mapping/__snapshots__/test_schemas/test_query_schemas[paginate_and_order_combined].gql new file mode 100644 index 00000000..24d5676a --- /dev/null +++ b/tests/unit/mapping/__snapshots__/test_schemas/test_query_schemas[paginate_and_order_combined].gql @@ -0,0 +1,249 @@ +''' +"""Aggregation fields""" +type ColorAggregate { + count: Int + max: ColorMinMaxFields! + min: ColorMinMaxFields! + sum: ColorSumFields! +} + +input ColorAggregateMinMaxStringFieldsOrderBy { + name: OrderByEnum +} + +input ColorAggregateNumericFieldsOrderBy { + name: OrderByEnum +} + +input ColorAggregateOrderBy { + count: OrderByEnum + maxString: ColorAggregateMinMaxStringFieldsOrderBy + minString: ColorAggregateMinMaxStringFieldsOrderBy + sum: ColorAggregateNumericFieldsOrderBy +} + +"""GraphQL type""" +type ColorMinMaxFields { + name: String +} + +""" +Boolean expression to compare fields. All fields are combined with logical 'AND'. +""" +input ColorOrderBy { + fruitsAggregate: FruitAggregateOrderBy + fruits: FruitOrderBy + name: OrderByEnum + id: OrderByEnum +} + +"""GraphQL type""" +type ColorSumFields { + name: String +} + +"""GraphQL type""" +type ColorType { + fruitsAggregate: FruitAggregate! + + """Fetch objects from the FruitType collection""" + fruits: [FruitType!]! + name: String! + id: UUID! +} + +"""GraphQL type""" +type ContainerType1 { + fruitsAggregate: FruitAggregate! + + """Fetch objects from the FruitType collection""" + fruits(limit: Int = 100, offset: Int! = 0): [FruitType!]! + vegetablesAggregate: VegetableAggregate! + + """Fetch objects from the VegetableType collection""" + vegetables(orderBy: [VegetableOrderBy!] = null): [VegetableType!]! + colorsAggregate: ColorAggregate! + + """Fetch objects from the ColorType collection""" + colors: [ColorType!]! + name: String! + id: UUID! +} + +"""GraphQL type""" +type ContainerType2 { + fruitsAggregate: FruitAggregate! + + """Fetch objects from the FruitType collection""" + fruits(limit: Int = 100, offset: Int! = 0, orderBy: [FruitOrderBy!] = null): [FruitType!]! + vegetablesAggregate: VegetableAggregate! + + """Fetch objects from the VegetableType collection""" + vegetables(limit: Int = 100, offset: Int! = 0): [VegetableType!]! + colorsAggregate: ColorAggregate! + + """Fetch objects from the ColorType collection""" + colors: [ColorType!]! + name: String! + id: UUID! +} + +"""GraphQL type""" +type ContainerType3 { + fruitsAggregate: FruitAggregate! + + """Fetch objects from the FruitType collection""" + fruits(limit: Int = 100, offset: Int! = 0, orderBy: [FruitOrderBy!] = null): [FruitType!]! + vegetablesAggregate: VegetableAggregate! + + """Fetch objects from the VegetableType collection""" + vegetables(limit: Int = 100, offset: Int! = 0): [VegetableType!]! + colorsAggregate: ColorAggregate! + + """Fetch objects from the ColorType collection""" + colors(limit: Int = 100, offset: Int! = 0): [ColorType!]! + name: String! + id: UUID! +} + +"""Aggregation fields""" +type FruitAggregate { + avg: FruitNumericFields! + count: Int + max: FruitMinMaxFields! + min: FruitMinMaxFields! + stddevPop: FruitNumericFields! + stddevSamp: FruitNumericFields! + sum: FruitSumFields! + varPop: FruitNumericFields! + varSamp: FruitNumericFields! +} + +input FruitAggregateMinMaxStringFieldsOrderBy { + name: OrderByEnum +} + +input FruitAggregateNumericFieldsOrderBy { + sweetness: OrderByEnum +} + +input FruitAggregateOrderBy { + avg: FruitAggregateNumericFieldsOrderBy + count: OrderByEnum + max: FruitAggregateNumericFieldsOrderBy + maxString: FruitAggregateMinMaxStringFieldsOrderBy + min: FruitAggregateNumericFieldsOrderBy + minString: FruitAggregateMinMaxStringFieldsOrderBy + stddevPop: FruitAggregateNumericFieldsOrderBy + stddevSamp: FruitAggregateNumericFieldsOrderBy + sum: FruitAggregateNumericFieldsOrderBy + varPop: FruitAggregateNumericFieldsOrderBy + varSamp: FruitAggregateNumericFieldsOrderBy +} + +"""GraphQL type""" +type FruitMinMaxFields { + name: String + sweetness: Int +} + +"""GraphQL type""" +type FruitNumericFields { + sweetness: Float +} + +""" +Boolean expression to compare fields. All fields are combined with logical 'AND'. +""" +input FruitOrderBy { + colorAggregate: ColorAggregateOrderBy + color: ColorOrderBy + name: OrderByEnum + colorId: OrderByEnum + sweetness: OrderByEnum + id: OrderByEnum +} + +"""GraphQL type""" +type FruitSumFields { + name: String + sweetness: Int +} + +"""GraphQL type""" +type FruitType { + color: ColorType! + name: String! + colorId: UUID + sweetness: Int! + id: UUID! +} + +enum OrderByEnum { + ASC + ASC_NULLS_FIRST + ASC_NULLS_LAST + DESC + DESC_NULLS_FIRST + DESC_NULLS_LAST +} + +type Query { + """Fetch objects from the ContainerType1 collection""" + container1: [ContainerType1!]! + + """Fetch objects from the ContainerType2 collection""" + container2: [ContainerType2!]! + + """Fetch objects from the ContainerType3 collection""" + container3: [ContainerType3!]! +} + +scalar UUID + +"""Aggregation fields""" +type VegetableAggregate { + count: Int + max: VegetableMinMaxFields! + min: VegetableMinMaxFields! + sum: VegetableSumFields! +} + +enum VegetableFamily { + MUSHROOM + GOURD + CABBAGE + ONION + SEEDS +} + +"""GraphQL type""" +type VegetableMinMaxFields { + name: String + description: String +} + +""" +Boolean expression to compare fields. All fields are combined with logical 'AND'. +""" +input VegetableOrderBy { + family: OrderByEnum + id: OrderByEnum + name: OrderByEnum + description: OrderByEnum +} + +"""GraphQL type""" +type VegetableSumFields { + name: String + description: String +} + +"""GraphQL type""" +type VegetableType { + family: VegetableFamily! + id: UUID! + name: String! + description: String! +} +''' \ No newline at end of file diff --git a/tests/unit/mapping/__snapshots__/test_schemas/test_query_schemas[paginate_empty].gql b/tests/unit/mapping/__snapshots__/test_schemas/test_query_schemas[paginate_empty].gql new file mode 100644 index 00000000..03fdedc2 --- /dev/null +++ b/tests/unit/mapping/__snapshots__/test_schemas/test_query_schemas[paginate_empty].gql @@ -0,0 +1,129 @@ +''' +"""Aggregation fields""" +type ColorAggregate { + count: Int + max: ColorMinMaxFields! + min: ColorMinMaxFields! + sum: ColorSumFields! +} + +"""GraphQL type""" +type ColorMinMaxFields { + name: String +} + +"""GraphQL type""" +type ColorSumFields { + name: String +} + +"""GraphQL type""" +type ColorType { + fruitsAggregate: FruitAggregate! + + """Fetch objects from the FruitType collection""" + fruits: [FruitType!]! + name: String! + id: UUID! +} + +"""GraphQL type""" +type ContainerType { + fruitsAggregate: FruitAggregate! + + """Fetch objects from the FruitType collection""" + fruits: [FruitType!]! + vegetablesAggregate: VegetableAggregate! + + """Fetch objects from the VegetableType collection""" + vegetables: [VegetableType!]! + colorsAggregate: ColorAggregate! + + """Fetch objects from the ColorType collection""" + colors: [ColorType!]! + name: String! + id: UUID! +} + +"""Aggregation fields""" +type FruitAggregate { + avg: FruitNumericFields! + count: Int + max: FruitMinMaxFields! + min: FruitMinMaxFields! + stddevPop: FruitNumericFields! + stddevSamp: FruitNumericFields! + sum: FruitSumFields! + varPop: FruitNumericFields! + varSamp: FruitNumericFields! +} + +"""GraphQL type""" +type FruitMinMaxFields { + name: String + sweetness: Int +} + +"""GraphQL type""" +type FruitNumericFields { + sweetness: Float +} + +"""GraphQL type""" +type FruitSumFields { + name: String + sweetness: Int +} + +"""GraphQL type""" +type FruitType { + color: ColorType! + name: String! + colorId: UUID + sweetness: Int! + id: UUID! +} + +type Query { + """Fetch objects from the ContainerType collection""" + container: [ContainerType!]! +} + +scalar UUID + +"""Aggregation fields""" +type VegetableAggregate { + count: Int + max: VegetableMinMaxFields! + min: VegetableMinMaxFields! + sum: VegetableSumFields! +} + +enum VegetableFamily { + MUSHROOM + GOURD + CABBAGE + ONION + SEEDS +} + +"""GraphQL type""" +type VegetableMinMaxFields { + name: String + description: String +} + +"""GraphQL type""" +type VegetableSumFields { + name: String + description: String +} + +"""GraphQL type""" +type VegetableType { + family: VegetableFamily! + id: UUID! + name: String! + description: String! +} +''' \ No newline at end of file diff --git a/tests/unit/mapping/__snapshots__/test_schemas/test_query_schemas[paginate_specific_fields].gql b/tests/unit/mapping/__snapshots__/test_schemas/test_query_schemas[paginate_specific_fields].gql new file mode 100644 index 00000000..7c9a3730 --- /dev/null +++ b/tests/unit/mapping/__snapshots__/test_schemas/test_query_schemas[paginate_specific_fields].gql @@ -0,0 +1,129 @@ +''' +"""Aggregation fields""" +type ColorAggregate { + count: Int + max: ColorMinMaxFields! + min: ColorMinMaxFields! + sum: ColorSumFields! +} + +"""GraphQL type""" +type ColorMinMaxFields { + name: String +} + +"""GraphQL type""" +type ColorSumFields { + name: String +} + +"""GraphQL type""" +type ColorType { + fruitsAggregate: FruitAggregate! + + """Fetch objects from the FruitType collection""" + fruits: [FruitType!]! + name: String! + id: UUID! +} + +"""GraphQL type""" +type ContainerType { + fruitsAggregate: FruitAggregate! + + """Fetch objects from the FruitType collection""" + fruits(limit: Int = 100, offset: Int! = 0): [FruitType!]! + vegetablesAggregate: VegetableAggregate! + + """Fetch objects from the VegetableType collection""" + vegetables: [VegetableType!]! + colorsAggregate: ColorAggregate! + + """Fetch objects from the ColorType collection""" + colors: [ColorType!]! + name: String! + id: UUID! +} + +"""Aggregation fields""" +type FruitAggregate { + avg: FruitNumericFields! + count: Int + max: FruitMinMaxFields! + min: FruitMinMaxFields! + stddevPop: FruitNumericFields! + stddevSamp: FruitNumericFields! + sum: FruitSumFields! + varPop: FruitNumericFields! + varSamp: FruitNumericFields! +} + +"""GraphQL type""" +type FruitMinMaxFields { + name: String + sweetness: Int +} + +"""GraphQL type""" +type FruitNumericFields { + sweetness: Float +} + +"""GraphQL type""" +type FruitSumFields { + name: String + sweetness: Int +} + +"""GraphQL type""" +type FruitType { + color: ColorType! + name: String! + colorId: UUID + sweetness: Int! + id: UUID! +} + +type Query { + """Fetch objects from the ContainerType collection""" + container: [ContainerType!]! +} + +scalar UUID + +"""Aggregation fields""" +type VegetableAggregate { + count: Int + max: VegetableMinMaxFields! + min: VegetableMinMaxFields! + sum: VegetableSumFields! +} + +enum VegetableFamily { + MUSHROOM + GOURD + CABBAGE + ONION + SEEDS +} + +"""GraphQL type""" +type VegetableMinMaxFields { + name: String + description: String +} + +"""GraphQL type""" +type VegetableSumFields { + name: String + description: String +} + +"""GraphQL type""" +type VegetableType { + family: VegetableFamily! + id: UUID! + name: String! + description: String! +} +''' \ No newline at end of file diff --git a/tests/unit/mapping/__snapshots__/test_schemas/test_query_schemas[pagination_config_default].gql b/tests/unit/mapping/__snapshots__/test_schemas/test_query_schemas[pagination_config_default].gql index 9bf20f23..0b4346cf 100644 --- a/tests/unit/mapping/__snapshots__/test_schemas/test_query_schemas[pagination_config_default].gql +++ b/tests/unit/mapping/__snapshots__/test_schemas/test_query_schemas[pagination_config_default].gql @@ -4,7 +4,7 @@ type ColorType { fruitsAggregate: FruitAggregate! """Fetch objects from the FruitType collection""" - fruits: [FruitType!]! + fruits(limit: Int = 100, offset: Int! = 0): [FruitType!]! name: String! id: UUID! } diff --git a/tests/unit/mapping/__snapshots__/test_schemas/test_query_schemas[pagination_config_empty].gql b/tests/unit/mapping/__snapshots__/test_schemas/test_query_schemas[pagination_config_empty].gql new file mode 100644 index 00000000..03fdedc2 --- /dev/null +++ b/tests/unit/mapping/__snapshots__/test_schemas/test_query_schemas[pagination_config_empty].gql @@ -0,0 +1,129 @@ +''' +"""Aggregation fields""" +type ColorAggregate { + count: Int + max: ColorMinMaxFields! + min: ColorMinMaxFields! + sum: ColorSumFields! +} + +"""GraphQL type""" +type ColorMinMaxFields { + name: String +} + +"""GraphQL type""" +type ColorSumFields { + name: String +} + +"""GraphQL type""" +type ColorType { + fruitsAggregate: FruitAggregate! + + """Fetch objects from the FruitType collection""" + fruits: [FruitType!]! + name: String! + id: UUID! +} + +"""GraphQL type""" +type ContainerType { + fruitsAggregate: FruitAggregate! + + """Fetch objects from the FruitType collection""" + fruits: [FruitType!]! + vegetablesAggregate: VegetableAggregate! + + """Fetch objects from the VegetableType collection""" + vegetables: [VegetableType!]! + colorsAggregate: ColorAggregate! + + """Fetch objects from the ColorType collection""" + colors: [ColorType!]! + name: String! + id: UUID! +} + +"""Aggregation fields""" +type FruitAggregate { + avg: FruitNumericFields! + count: Int + max: FruitMinMaxFields! + min: FruitMinMaxFields! + stddevPop: FruitNumericFields! + stddevSamp: FruitNumericFields! + sum: FruitSumFields! + varPop: FruitNumericFields! + varSamp: FruitNumericFields! +} + +"""GraphQL type""" +type FruitMinMaxFields { + name: String + sweetness: Int +} + +"""GraphQL type""" +type FruitNumericFields { + sweetness: Float +} + +"""GraphQL type""" +type FruitSumFields { + name: String + sweetness: Int +} + +"""GraphQL type""" +type FruitType { + color: ColorType! + name: String! + colorId: UUID + sweetness: Int! + id: UUID! +} + +type Query { + """Fetch objects from the ContainerType collection""" + container: [ContainerType!]! +} + +scalar UUID + +"""Aggregation fields""" +type VegetableAggregate { + count: Int + max: VegetableMinMaxFields! + min: VegetableMinMaxFields! + sum: VegetableSumFields! +} + +enum VegetableFamily { + MUSHROOM + GOURD + CABBAGE + ONION + SEEDS +} + +"""GraphQL type""" +type VegetableMinMaxFields { + name: String + description: String +} + +"""GraphQL type""" +type VegetableSumFields { + name: String + description: String +} + +"""GraphQL type""" +type VegetableType { + family: VegetableFamily! + id: UUID! + name: String! + description: String! +} +''' \ No newline at end of file diff --git a/tests/unit/mapping/__snapshots__/test_schemas/test_query_schemas[pagination_config_empty_with_type_override].gql b/tests/unit/mapping/__snapshots__/test_schemas/test_query_schemas[pagination_config_empty_with_type_override].gql new file mode 100644 index 00000000..7c9a3730 --- /dev/null +++ b/tests/unit/mapping/__snapshots__/test_schemas/test_query_schemas[pagination_config_empty_with_type_override].gql @@ -0,0 +1,129 @@ +''' +"""Aggregation fields""" +type ColorAggregate { + count: Int + max: ColorMinMaxFields! + min: ColorMinMaxFields! + sum: ColorSumFields! +} + +"""GraphQL type""" +type ColorMinMaxFields { + name: String +} + +"""GraphQL type""" +type ColorSumFields { + name: String +} + +"""GraphQL type""" +type ColorType { + fruitsAggregate: FruitAggregate! + + """Fetch objects from the FruitType collection""" + fruits: [FruitType!]! + name: String! + id: UUID! +} + +"""GraphQL type""" +type ContainerType { + fruitsAggregate: FruitAggregate! + + """Fetch objects from the FruitType collection""" + fruits(limit: Int = 100, offset: Int! = 0): [FruitType!]! + vegetablesAggregate: VegetableAggregate! + + """Fetch objects from the VegetableType collection""" + vegetables: [VegetableType!]! + colorsAggregate: ColorAggregate! + + """Fetch objects from the ColorType collection""" + colors: [ColorType!]! + name: String! + id: UUID! +} + +"""Aggregation fields""" +type FruitAggregate { + avg: FruitNumericFields! + count: Int + max: FruitMinMaxFields! + min: FruitMinMaxFields! + stddevPop: FruitNumericFields! + stddevSamp: FruitNumericFields! + sum: FruitSumFields! + varPop: FruitNumericFields! + varSamp: FruitNumericFields! +} + +"""GraphQL type""" +type FruitMinMaxFields { + name: String + sweetness: Int +} + +"""GraphQL type""" +type FruitNumericFields { + sweetness: Float +} + +"""GraphQL type""" +type FruitSumFields { + name: String + sweetness: Int +} + +"""GraphQL type""" +type FruitType { + color: ColorType! + name: String! + colorId: UUID + sweetness: Int! + id: UUID! +} + +type Query { + """Fetch objects from the ContainerType collection""" + container: [ContainerType!]! +} + +scalar UUID + +"""Aggregation fields""" +type VegetableAggregate { + count: Int + max: VegetableMinMaxFields! + min: VegetableMinMaxFields! + sum: VegetableSumFields! +} + +enum VegetableFamily { + MUSHROOM + GOURD + CABBAGE + ONION + SEEDS +} + +"""GraphQL type""" +type VegetableMinMaxFields { + name: String + description: String +} + +"""GraphQL type""" +type VegetableSumFields { + name: String + description: String +} + +"""GraphQL type""" +type VegetableType { + family: VegetableFamily! + id: UUID! + name: String! + description: String! +} +''' \ No newline at end of file diff --git a/tests/unit/mapping/__snapshots__/test_schemas/test_query_schemas[pagination_config_specific_fields].gql b/tests/unit/mapping/__snapshots__/test_schemas/test_query_schemas[pagination_config_specific_fields].gql new file mode 100644 index 00000000..e9c2d7ed --- /dev/null +++ b/tests/unit/mapping/__snapshots__/test_schemas/test_query_schemas[pagination_config_specific_fields].gql @@ -0,0 +1,129 @@ +''' +"""Aggregation fields""" +type ColorAggregate { + count: Int + max: ColorMinMaxFields! + min: ColorMinMaxFields! + sum: ColorSumFields! +} + +"""GraphQL type""" +type ColorMinMaxFields { + name: String +} + +"""GraphQL type""" +type ColorSumFields { + name: String +} + +"""GraphQL type""" +type ColorType { + fruitsAggregate: FruitAggregate! + + """Fetch objects from the FruitType collection""" + fruits(limit: Int = 100, offset: Int! = 0): [FruitType!]! + name: String! + id: UUID! +} + +"""GraphQL type""" +type ContainerType { + fruitsAggregate: FruitAggregate! + + """Fetch objects from the FruitType collection""" + fruits(limit: Int = 100, offset: Int! = 0): [FruitType!]! + vegetablesAggregate: VegetableAggregate! + + """Fetch objects from the VegetableType collection""" + vegetables(limit: Int = 100, offset: Int! = 0): [VegetableType!]! + colorsAggregate: ColorAggregate! + + """Fetch objects from the ColorType collection""" + colors: [ColorType!]! + name: String! + id: UUID! +} + +"""Aggregation fields""" +type FruitAggregate { + avg: FruitNumericFields! + count: Int + max: FruitMinMaxFields! + min: FruitMinMaxFields! + stddevPop: FruitNumericFields! + stddevSamp: FruitNumericFields! + sum: FruitSumFields! + varPop: FruitNumericFields! + varSamp: FruitNumericFields! +} + +"""GraphQL type""" +type FruitMinMaxFields { + name: String + sweetness: Int +} + +"""GraphQL type""" +type FruitNumericFields { + sweetness: Float +} + +"""GraphQL type""" +type FruitSumFields { + name: String + sweetness: Int +} + +"""GraphQL type""" +type FruitType { + color: ColorType! + name: String! + colorId: UUID + sweetness: Int! + id: UUID! +} + +type Query { + """Fetch objects from the ContainerType collection""" + container: [ContainerType!]! +} + +scalar UUID + +"""Aggregation fields""" +type VegetableAggregate { + count: Int + max: VegetableMinMaxFields! + min: VegetableMinMaxFields! + sum: VegetableSumFields! +} + +enum VegetableFamily { + MUSHROOM + GOURD + CABBAGE + ONION + SEEDS +} + +"""GraphQL type""" +type VegetableMinMaxFields { + name: String + description: String +} + +"""GraphQL type""" +type VegetableSumFields { + name: String + description: String +} + +"""GraphQL type""" +type VegetableType { + family: VegetableFamily! + id: UUID! + name: String! + description: String! +} +''' \ No newline at end of file diff --git a/tests/unit/mapping/__snapshots__/test_schemas/test_query_schemas[pagination_config_with_type_override].gql b/tests/unit/mapping/__snapshots__/test_schemas/test_query_schemas[pagination_config_with_type_override].gql new file mode 100644 index 00000000..6bcea719 --- /dev/null +++ b/tests/unit/mapping/__snapshots__/test_schemas/test_query_schemas[pagination_config_with_type_override].gql @@ -0,0 +1,129 @@ +''' +"""Aggregation fields""" +type ColorAggregate { + count: Int + max: ColorMinMaxFields! + min: ColorMinMaxFields! + sum: ColorSumFields! +} + +"""GraphQL type""" +type ColorMinMaxFields { + name: String +} + +"""GraphQL type""" +type ColorSumFields { + name: String +} + +"""GraphQL type""" +type ColorType { + fruitsAggregate: FruitAggregate! + + """Fetch objects from the FruitType collection""" + fruits(limit: Int = 100, offset: Int! = 0): [FruitType!]! + name: String! + id: UUID! +} + +"""GraphQL type""" +type ContainerType { + fruitsAggregate: FruitAggregate! + + """Fetch objects from the FruitType collection""" + fruits: [FruitType!]! + vegetablesAggregate: VegetableAggregate! + + """Fetch objects from the VegetableType collection""" + vegetables(limit: Int = 100, offset: Int! = 0): [VegetableType!]! + colorsAggregate: ColorAggregate! + + """Fetch objects from the ColorType collection""" + colors: [ColorType!]! + name: String! + id: UUID! +} + +"""Aggregation fields""" +type FruitAggregate { + avg: FruitNumericFields! + count: Int + max: FruitMinMaxFields! + min: FruitMinMaxFields! + stddevPop: FruitNumericFields! + stddevSamp: FruitNumericFields! + sum: FruitSumFields! + varPop: FruitNumericFields! + varSamp: FruitNumericFields! +} + +"""GraphQL type""" +type FruitMinMaxFields { + name: String + sweetness: Int +} + +"""GraphQL type""" +type FruitNumericFields { + sweetness: Float +} + +"""GraphQL type""" +type FruitSumFields { + name: String + sweetness: Int +} + +"""GraphQL type""" +type FruitType { + color: ColorType! + name: String! + colorId: UUID + sweetness: Int! + id: UUID! +} + +type Query { + """Fetch objects from the ContainerType collection""" + container: [ContainerType!]! +} + +scalar UUID + +"""Aggregation fields""" +type VegetableAggregate { + count: Int + max: VegetableMinMaxFields! + min: VegetableMinMaxFields! + sum: VegetableSumFields! +} + +enum VegetableFamily { + MUSHROOM + GOURD + CABBAGE + ONION + SEEDS +} + +"""GraphQL type""" +type VegetableMinMaxFields { + name: String + description: String +} + +"""GraphQL type""" +type VegetableSumFields { + name: String + description: String +} + +"""GraphQL type""" +type VegetableType { + family: VegetableFamily! + id: UUID! + name: String! + description: String! +} +''' \ No newline at end of file diff --git a/tests/unit/mapping/__snapshots__/test_schemas/test_query_schemas[pagination_default_offset].gql b/tests/unit/mapping/__snapshots__/test_schemas/test_query_schemas[pagination_default_offset].gql new file mode 100644 index 00000000..01352f38 --- /dev/null +++ b/tests/unit/mapping/__snapshots__/test_schemas/test_query_schemas[pagination_default_offset].gql @@ -0,0 +1,57 @@ +''' +"""GraphQL type""" +type ColorType { + fruitsAggregate: FruitAggregate! + + """Fetch objects from the FruitType collection""" + fruits(limit: Int = 100, offset: Int! = 5): [FruitType!]! + name: String! + id: UUID! +} + +"""Aggregation fields""" +type FruitAggregate { + avg: FruitNumericFields! + count: Int + max: FruitMinMaxFields! + min: FruitMinMaxFields! + stddevPop: FruitNumericFields! + stddevSamp: FruitNumericFields! + sum: FruitSumFields! + varPop: FruitNumericFields! + varSamp: FruitNumericFields! +} + +"""GraphQL type""" +type FruitMinMaxFields { + name: String + sweetness: Int +} + +"""GraphQL type""" +type FruitNumericFields { + sweetness: Float +} + +"""GraphQL type""" +type FruitSumFields { + name: String + sweetness: Int +} + +"""GraphQL type""" +type FruitType { + color: ColorType! + name: String! + colorId: UUID + sweetness: Int! + id: UUID! +} + +type Query { + """Fetch objects from the FruitType collection""" + fruits(limit: Int = 100, offset: Int! = 5): [FruitType!]! +} + +scalar UUID +''' \ No newline at end of file diff --git a/tests/unit/mapping/__snapshots__/test_schemas/test_query_schemas[distinct].gql b/tests/unit/mapping/__snapshots__/test_schemas/test_query_schemas[type_distinct_manual_enum].gql similarity index 100% rename from tests/unit/mapping/__snapshots__/test_schemas/test_query_schemas[distinct].gql rename to tests/unit/mapping/__snapshots__/test_schemas/test_query_schemas[type_distinct_manual_enum].gql diff --git a/tests/unit/mapping/__snapshots__/test_schemas/test_query_schemas[type_order_by_specific_fields].gql b/tests/unit/mapping/__snapshots__/test_schemas/test_query_schemas[type_order_by_specific_fields].gql new file mode 100644 index 00000000..a82cf7c9 --- /dev/null +++ b/tests/unit/mapping/__snapshots__/test_schemas/test_query_schemas[type_order_by_specific_fields].gql @@ -0,0 +1,47 @@ +''' +"""Date (isoformat)""" +scalar Date + +"""Date with time (isoformat)""" +scalar DateTime + +"""Decimal (fixed-point)""" +scalar Decimal + +""" +The `Interval` scalar type represents a duration of time as specified by [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601#Durations). +""" +scalar Interval @specifiedBy(url: "https://en.wikipedia.org/wiki/ISO_8601#Durations") + +""" +The `JSON` scalar type represents JSON values as specified by [ECMA-404](https://ecma-international.org/wp-content/uploads/ECMA-404_2nd_edition_december_2017.pdf). +""" +scalar JSON @specifiedBy(url: "https://ecma-international.org/wp-content/uploads/ECMA-404_2nd_edition_december_2017.pdf") + +type Query { + """Fetch objects from the SQLDataTypesType collection""" + sqlDataTypes: [SQLDataTypesType!]! +} + +"""GraphQL type""" +type SQLDataTypesType { + dateCol: Date! + timeCol: Time! + timeDeltaCol: Interval! + datetimeCol: DateTime! + strCol: String! + intCol: Int! + floatCol: Float! + decimalCol: Decimal! + boolCol: Boolean! + uuidCol: UUID! + dictCol(path: String): JSON + arrayStrCol: [String!]! + id: UUID! +} + +"""Time (isoformat)""" +scalar Time + +scalar UUID +''' \ No newline at end of file diff --git a/tests/unit/mapping/test_schemas.py b/tests/unit/mapping/test_schemas.py index ae1d08f6..74df37e7 100644 --- a/tests/unit/mapping/test_schemas.py +++ b/tests/unit/mapping/test_schemas.py @@ -184,18 +184,49 @@ def test_update_mutation_by_filter_type_not_list_fail() -> None: pytest.param("pagination.pagination_defaults.Query", id="pagination_defaults"), pytest.param("pagination.children_pagination.Query", id="children_pagination"), pytest.param("pagination.children_pagination_defaults.Query", id="children_pagination_defaults"), - pytest.param("pagination.pagination_default_limit.Query", id="pagination_default_limit"), pytest.param("pagination.pagination_config_default.Query", id="pagination_config_default"), + pytest.param("pagination.pagination_default_limit.Query", id="pagination_default_limit"), + pytest.param("pagination.pagination_default_offset.Query", id="pagination_default_offset"), + pytest.param("pagination.paginate_specific_fields.Query", id="paginate_specific_fields"), + pytest.param("pagination.paginate_empty.Query", id="paginate_empty"), + pytest.param("pagination.pagination_config_empty.Query", id="pagination_config_empty"), + pytest.param("pagination.pagination_config_specific_fields.Query", id="pagination_config_specific_fields"), + pytest.param( + "pagination.pagination_config_with_type_override.Query", id="pagination_config_with_type_override" + ), + pytest.param("pagination.paginate_and_order_combined.Query", id="paginate_and_order_combined"), + pytest.param("pagination.paginate_all_with_default.Query", id="paginate_all_with_default"), pytest.param("custom_id_field_name.Query", id="custom_id_field_name"), pytest.param("enums.Query", id="enums"), pytest.param("filters.filters.Query", id="filters"), pytest.param("filters.filters_aggregation.Query", id="aggregation_filters"), pytest.param("filters.type_filter.Query", id="type_filter"), + pytest.param("filters.field_filter_auto_generate.Query", id="field_filter_auto_generate"), pytest.param("order.type_order_by.Query", id="type_order_by"), pytest.param("order.field_order_by.Query", id="field_order_by"), - pytest.param("order.auto_order_by.Query", id="auto_order_by"), + pytest.param("order.field_order_by_all.Query", id="field_order_by_all"), + pytest.param("order.field_order_by_specific_fields.Query", id="field_order_by_specific_fields"), + pytest.param("order.order_config_all.Query", id="order_config_all"), + pytest.param( + "order.order_config_specific_fields_with_type_override.Query", + id="order_config_specific_fields_with_type_override", + ), + pytest.param( + "order.order_config_empty_with_empty_type_override.Query", id="order_config_empty_with_empty_type_override" + ), + pytest.param("order.order_config_all_with_field_override.Query", id="order_config_all_with_field_override"), + pytest.param("order.order_config_specific_fields.Query", id="order_config_specific_fields"), + pytest.param("order.order_config_empty.Query", id="order_config_empty"), + pytest.param("order.order_config_with_field_override.Query", id="order_config_with_field_override"), + pytest.param("order.type_order_by_specific_fields.Query", id="type_order_by_specific_fields"), pytest.param("aggregations.root_aggregations.Query", id="root_aggregations"), - pytest.param("distinct.Query", id="distinct"), + pytest.param("distinct.type_distinct_manual_enum.Query", id="type_distinct_manual_enum"), + pytest.param("distinct.distinct_config_all.Query", id="distinct_config_all"), + pytest.param("distinct.field_distinct_all.Query", id="field_distinct_all"), + pytest.param("distinct.field_distinct_specific_fields.Query", id="field_distinct_specific_fields"), + pytest.param("distinct.distinct_config_with_field_override.Query", id="distinct_config_with_field_override"), + pytest.param("distinct.distinct_config_specific_fields.Query", id="distinct_config_specific_fields"), + pytest.param("distinct.distinct_config_empty.Query", id="distinct_config_empty"), pytest.param("scope.schema_before.Query", id="scope_schema_before"), pytest.param("scope.schema_after.Query", id="scope_schema_after"), pytest.param("scope.schema_in_the_middle.Query", id="scope_schema_in_the_middle"), diff --git a/tests/unit/models.py b/tests/unit/models.py index c2832440..a9580c9d 100644 --- a/tests/unit/models.py +++ b/tests/unit/models.py @@ -197,6 +197,21 @@ class SQLDataTypes(UUIDBase): array_str_col: Mapped[list[str]] = mapped_column(postgresql.ARRAY(Text), default=list) +class Container(UUIDBase): + """Test model with multiple list relationships for testing paginate/order configuration.""" + + __tablename__ = "container" + + name: Mapped[str] + fruits: Mapped[list[Fruit]] = relationship( + "Fruit", primaryjoin="Container.id == foreign(Fruit.color_id)", viewonly=True + ) + vegetables: Mapped[list[Vegetable]] = relationship( + "Vegetable", primaryjoin="Container.id == foreign(Vegetable.id)", viewonly=True + ) + colors: Mapped[list[Color]] = relationship("Color", primaryjoin="Container.id == foreign(Color.id)", viewonly=True) + + # Geo if GEO_INSTALLED: diff --git a/tests/unit/schemas/distinct/__init__.py b/tests/unit/schemas/distinct/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/unit/schemas/distinct/distinct_config_all.py b/tests/unit/schemas/distinct/distinct_config_all.py new file mode 100644 index 00000000..0da45351 --- /dev/null +++ b/tests/unit/schemas/distinct/distinct_config_all.py @@ -0,0 +1,18 @@ +from __future__ import annotations + +import strawberry + +from strawchemy import Strawchemy, StrawchemyConfig +from tests.unit.models import Color + +strawchemy = Strawchemy(StrawchemyConfig("postgresql", distinct_on="all")) + + +@strawchemy.type(Color, include="all") +class ColorType: + pass + + +@strawberry.type +class Query: + colors: list[ColorType] = strawchemy.field() diff --git a/tests/unit/schemas/distinct/distinct_config_empty.py b/tests/unit/schemas/distinct/distinct_config_empty.py new file mode 100644 index 00000000..67d8fea3 --- /dev/null +++ b/tests/unit/schemas/distinct/distinct_config_empty.py @@ -0,0 +1,18 @@ +from __future__ import annotations + +import strawberry + +from strawchemy import Strawchemy, StrawchemyConfig +from tests.unit.models import Color + +strawchemy = Strawchemy(StrawchemyConfig("postgresql", distinct_on=[])) + + +@strawchemy.type(Color, include="all") +class ColorType: + pass + + +@strawberry.type +class Query: + colors: list[ColorType] = strawchemy.field() diff --git a/tests/unit/schemas/distinct/distinct_config_specific_fields.py b/tests/unit/schemas/distinct/distinct_config_specific_fields.py new file mode 100644 index 00000000..3b7431c4 --- /dev/null +++ b/tests/unit/schemas/distinct/distinct_config_specific_fields.py @@ -0,0 +1,18 @@ +from __future__ import annotations + +import strawberry + +from strawchemy import Strawchemy, StrawchemyConfig +from tests.unit.models import Color + +strawchemy = Strawchemy(StrawchemyConfig("postgresql", distinct_on=["name"])) + + +@strawchemy.type(Color, include="all") +class ColorType: + pass + + +@strawberry.type +class Query: + colors: list[ColorType] = strawchemy.field() diff --git a/tests/unit/schemas/distinct/distinct_config_with_field_override.py b/tests/unit/schemas/distinct/distinct_config_with_field_override.py new file mode 100644 index 00000000..7ea7c088 --- /dev/null +++ b/tests/unit/schemas/distinct/distinct_config_with_field_override.py @@ -0,0 +1,18 @@ +from __future__ import annotations + +import strawberry + +from strawchemy import Strawchemy, StrawchemyConfig +from tests.unit.models import Color + +strawchemy = Strawchemy(StrawchemyConfig("postgresql", distinct_on="all")) + + +@strawchemy.type(Color, include="all") +class ColorType: + pass + + +@strawberry.type +class Query: + colors: list[ColorType] = strawchemy.field(distinct_on=["name"]) diff --git a/tests/unit/schemas/distinct/field_distinct_all.py b/tests/unit/schemas/distinct/field_distinct_all.py new file mode 100644 index 00000000..7a4a3acb --- /dev/null +++ b/tests/unit/schemas/distinct/field_distinct_all.py @@ -0,0 +1,18 @@ +from __future__ import annotations + +import strawberry + +from strawchemy import Strawchemy +from tests.unit.models import Color + +strawchemy = Strawchemy("postgresql") + + +@strawchemy.type(Color, include="all") +class ColorType: + pass + + +@strawberry.type +class Query: + colors: list[ColorType] = strawchemy.field(distinct_on="all") diff --git a/tests/unit/schemas/distinct/field_distinct_specific_fields.py b/tests/unit/schemas/distinct/field_distinct_specific_fields.py new file mode 100644 index 00000000..b6e4b242 --- /dev/null +++ b/tests/unit/schemas/distinct/field_distinct_specific_fields.py @@ -0,0 +1,18 @@ +from __future__ import annotations + +import strawberry + +from strawchemy import Strawchemy +from tests.unit.models import Color + +strawchemy = Strawchemy("postgresql") + + +@strawchemy.type(Color, include="all") +class ColorType: + pass + + +@strawberry.type +class Query: + colors: list[ColorType] = strawchemy.field(distinct_on=["name", "hex"]) diff --git a/tests/unit/schemas/distinct.py b/tests/unit/schemas/distinct/type_distinct_manual_enum.py similarity index 100% rename from tests/unit/schemas/distinct.py rename to tests/unit/schemas/distinct/type_distinct_manual_enum.py diff --git a/tests/unit/schemas/filters/field_filter_auto_generate.py b/tests/unit/schemas/filters/field_filter_auto_generate.py new file mode 100644 index 00000000..6f72b2cf --- /dev/null +++ b/tests/unit/schemas/filters/field_filter_auto_generate.py @@ -0,0 +1,18 @@ +from __future__ import annotations + +import strawberry + +from strawchemy import Strawchemy +from tests.unit.models import Fruit + +strawchemy = Strawchemy("postgresql") + + +@strawchemy.type(Fruit, include="all") +class FruitType: + pass + + +@strawberry.type +class Query: + fruits: list[FruitType] = strawchemy.field(filter_input=True) diff --git a/tests/unit/schemas/order/auto_order_by.py b/tests/unit/schemas/order/auto_order_by.py deleted file mode 100644 index a8a96e21..00000000 --- a/tests/unit/schemas/order/auto_order_by.py +++ /dev/null @@ -1,17 +0,0 @@ -from __future__ import annotations - -import strawberry - -from strawchemy import Strawchemy -from tests.unit.models import Group - -strawchemy = Strawchemy("postgresql") - - -@strawchemy.type(Group, include="all", child_order_by=True) -class GroupType: ... - - -@strawberry.type -class Query: - group: list[GroupType] = strawchemy.field() diff --git a/tests/unit/schemas/order/field_order_by_all.py b/tests/unit/schemas/order/field_order_by_all.py new file mode 100644 index 00000000..9aec1336 --- /dev/null +++ b/tests/unit/schemas/order/field_order_by_all.py @@ -0,0 +1,18 @@ +from __future__ import annotations + +import strawberry + +from strawchemy import Strawchemy +from tests.unit.models import Fruit + +strawchemy = Strawchemy("postgresql") + + +@strawchemy.type(Fruit, include="all") +class FruitType: + pass + + +@strawberry.type +class Query: + fruits: list[FruitType] = strawchemy.field(order_by="all") diff --git a/tests/unit/schemas/order/field_order_by_specific_fields.py b/tests/unit/schemas/order/field_order_by_specific_fields.py new file mode 100644 index 00000000..91c9a19a --- /dev/null +++ b/tests/unit/schemas/order/field_order_by_specific_fields.py @@ -0,0 +1,18 @@ +from __future__ import annotations + +import strawberry + +from strawchemy import Strawchemy +from tests.unit.models import Container + +strawchemy = Strawchemy("postgresql") + + +@strawchemy.type(Container, include="all") +class ContainerType: + pass + + +@strawberry.type +class Query: + containers: list[ContainerType] = strawchemy.field(order_by=["fruits", "vegetables"]) diff --git a/tests/unit/schemas/order/order_config_all.py b/tests/unit/schemas/order/order_config_all.py new file mode 100644 index 00000000..abaed7d4 --- /dev/null +++ b/tests/unit/schemas/order/order_config_all.py @@ -0,0 +1,18 @@ +from __future__ import annotations + +import strawberry + +from strawchemy import Strawchemy, StrawchemyConfig +from tests.unit.models import Fruit + +strawchemy = Strawchemy(StrawchemyConfig("postgresql", order_by="all")) + + +@strawchemy.type(Fruit, include="all") +class FruitType: + pass + + +@strawberry.type +class Query: + fruits: list[FruitType] = strawchemy.field() diff --git a/tests/unit/schemas/order/order_config_all_with_field_override.py b/tests/unit/schemas/order/order_config_all_with_field_override.py new file mode 100644 index 00000000..d71294b0 --- /dev/null +++ b/tests/unit/schemas/order/order_config_all_with_field_override.py @@ -0,0 +1,18 @@ +from __future__ import annotations + +import strawberry + +from strawchemy import Strawchemy, StrawchemyConfig +from tests.unit.models import Fruit + +strawchemy = Strawchemy(StrawchemyConfig("postgresql", order_by="all")) + + +@strawchemy.type(Fruit, include="all") +class FruitType: + pass + + +@strawberry.type +class Query: + fruits: list[FruitType] = strawchemy.field(order_by=["name"]) diff --git a/tests/unit/schemas/order/order_config_empty.py b/tests/unit/schemas/order/order_config_empty.py new file mode 100644 index 00000000..aac1b568 --- /dev/null +++ b/tests/unit/schemas/order/order_config_empty.py @@ -0,0 +1,18 @@ +from __future__ import annotations + +import strawberry + +from strawchemy import Strawchemy, StrawchemyConfig +from tests.unit.models import Fruit + +strawchemy = Strawchemy(StrawchemyConfig("postgresql", order_by=[])) + + +@strawchemy.type(Fruit, include="all") +class FruitType: + pass + + +@strawberry.type +class Query: + fruits: list[FruitType] = strawchemy.field() diff --git a/tests/unit/schemas/order/order_config_empty_with_empty_type_override.py b/tests/unit/schemas/order/order_config_empty_with_empty_type_override.py new file mode 100644 index 00000000..11f3cbf3 --- /dev/null +++ b/tests/unit/schemas/order/order_config_empty_with_empty_type_override.py @@ -0,0 +1,18 @@ +from __future__ import annotations + +import strawberry + +from strawchemy import Strawchemy +from tests.unit.models import Container + +strawchemy = Strawchemy("postgresql") + + +@strawchemy.type(Container, include="all", order=[]) +class ContainerType: + pass + + +@strawberry.type +class Query: + container: list[ContainerType] = strawchemy.field() diff --git a/tests/unit/schemas/order/order_config_specific_fields.py b/tests/unit/schemas/order/order_config_specific_fields.py new file mode 100644 index 00000000..b48ab41e --- /dev/null +++ b/tests/unit/schemas/order/order_config_specific_fields.py @@ -0,0 +1,18 @@ +from __future__ import annotations + +import strawberry + +from strawchemy import Strawchemy, StrawchemyConfig +from tests.unit.models import Fruit + +strawchemy = Strawchemy(StrawchemyConfig("postgresql", order_by=["name", "sweetness"])) + + +@strawchemy.type(Fruit, include="all") +class FruitType: + pass + + +@strawberry.type +class Query: + fruits: list[FruitType] = strawchemy.field() diff --git a/tests/unit/schemas/order/order_config_specific_fields_with_type_override.py b/tests/unit/schemas/order/order_config_specific_fields_with_type_override.py new file mode 100644 index 00000000..bb9c5573 --- /dev/null +++ b/tests/unit/schemas/order/order_config_specific_fields_with_type_override.py @@ -0,0 +1,18 @@ +from __future__ import annotations + +import strawberry + +from strawchemy import Strawchemy +from tests.unit.models import Container + +strawchemy = Strawchemy("postgresql") + + +@strawchemy.type(Container, include="all", order=["fruits", "vegetables"]) +class ContainerType: + pass + + +@strawberry.type +class Query: + container: list[ContainerType] = strawchemy.field() diff --git a/tests/unit/schemas/order/order_config_with_field_override.py b/tests/unit/schemas/order/order_config_with_field_override.py new file mode 100644 index 00000000..448c573f --- /dev/null +++ b/tests/unit/schemas/order/order_config_with_field_override.py @@ -0,0 +1,18 @@ +from __future__ import annotations + +import strawberry + +from strawchemy import Strawchemy, StrawchemyConfig +from tests.unit.models import Fruit + +strawchemy = Strawchemy(StrawchemyConfig("postgresql", order_by=["name"])) + + +@strawchemy.type(Fruit, include="all") +class FruitType: + pass + + +@strawberry.type +class Query: + fruits: list[FruitType] = strawchemy.field(order_by=["sweetness"]) diff --git a/tests/unit/schemas/order/type_order_by.py b/tests/unit/schemas/order/type_order_by.py index 022c8828..1fd982da 100644 --- a/tests/unit/schemas/order/type_order_by.py +++ b/tests/unit/schemas/order/type_order_by.py @@ -12,7 +12,7 @@ class SQLDataTypesOrderBy: ... -@strawchemy.type(SQLDataTypes, include="all", order_by=SQLDataTypesOrderBy) +@strawchemy.type(SQLDataTypes, include="all", order=SQLDataTypesOrderBy) class SQLDataTypesType: ... diff --git a/tests/unit/schemas/order/type_order_by_specific_fields.py b/tests/unit/schemas/order/type_order_by_specific_fields.py new file mode 100644 index 00000000..075e6806 --- /dev/null +++ b/tests/unit/schemas/order/type_order_by_specific_fields.py @@ -0,0 +1,17 @@ +from __future__ import annotations + +import strawberry + +from strawchemy import Strawchemy +from tests.unit.models import SQLDataTypes + +strawchemy = Strawchemy("postgresql") + + +@strawchemy.type(SQLDataTypes, include="all", order=["str_col", "int_col"]) +class SQLDataTypesType: ... + + +@strawberry.type +class Query: + sql_data_types: list[SQLDataTypesType] = strawchemy.field() diff --git a/tests/unit/schemas/override/override_argument.py b/tests/unit/schemas/override/override_argument.py index a41b6520..fc76c887 100644 --- a/tests/unit/schemas/override/override_argument.py +++ b/tests/unit/schemas/override/override_argument.py @@ -8,7 +8,7 @@ strawchemy = Strawchemy("postgresql") -@strawchemy.type(Fruit, include="all", child_pagination=True, child_order_by=True) +@strawchemy.type(Fruit, include="all", paginate="all", order="all") class FruitType: name: int diff --git a/tests/unit/schemas/pagination/children_pagination.py b/tests/unit/schemas/pagination/children_pagination.py index 2bcd129d..727e6bd1 100644 --- a/tests/unit/schemas/pagination/children_pagination.py +++ b/tests/unit/schemas/pagination/children_pagination.py @@ -8,7 +8,7 @@ strawchemy = Strawchemy("postgresql") -@strawchemy.type(Fruit, include="all", child_pagination=True) +@strawchemy.type(Fruit, include="all", paginate="all") class FruitType: pass diff --git a/tests/unit/schemas/pagination/children_pagination_defaults.py b/tests/unit/schemas/pagination/children_pagination_defaults.py index 9c81a1c5..3326df77 100644 --- a/tests/unit/schemas/pagination/children_pagination_defaults.py +++ b/tests/unit/schemas/pagination/children_pagination_defaults.py @@ -9,7 +9,7 @@ strawchemy = Strawchemy("postgresql") -@strawchemy.type(Fruit, include="all", child_pagination=DefaultOffsetPagination(limit=10, offset=10)) +@strawchemy.type(Fruit, include="all", paginate="all", default_pagination=DefaultOffsetPagination(limit=10, offset=10)) class FruitType: pass diff --git a/tests/unit/schemas/pagination/paginate_all_with_default.py b/tests/unit/schemas/pagination/paginate_all_with_default.py new file mode 100644 index 00000000..85d70fd1 --- /dev/null +++ b/tests/unit/schemas/pagination/paginate_all_with_default.py @@ -0,0 +1,19 @@ +from __future__ import annotations + +import strawberry + +from strawchemy import Strawchemy +from strawchemy.schema.pagination import DefaultOffsetPagination +from tests.unit.models import Fruit + +strawchemy = Strawchemy("postgresql") + + +@strawchemy.type(Fruit, include="all", paginate="all", default_pagination=DefaultOffsetPagination(limit=20, offset=5)) +class FruitType: + pass + + +@strawberry.type +class Query: + fruit_with_custom_default: list[FruitType] = strawchemy.field() diff --git a/tests/unit/schemas/pagination/paginate_and_order_combined.py b/tests/unit/schemas/pagination/paginate_and_order_combined.py new file mode 100644 index 00000000..42affe2b --- /dev/null +++ b/tests/unit/schemas/pagination/paginate_and_order_combined.py @@ -0,0 +1,33 @@ +from __future__ import annotations + +import strawberry + +from strawchemy import Strawchemy +from tests.unit.models import Container + +strawchemy = Strawchemy("postgresql") + + +# Different fields for paginate vs order +@strawchemy.type(Container, include="all", paginate=["fruits"], order=["vegetables"]) +class ContainerType1: + pass + + +# Overlapping fields +@strawchemy.type(Container, include="all", paginate=["fruits", "vegetables"], order=["fruits"]) +class ContainerType2: + pass + + +# All + specific +@strawchemy.type(Container, include="all", paginate="all", order=["fruits"]) +class ContainerType3: + pass + + +@strawberry.type +class Query: + container1: list[ContainerType1] = strawchemy.field() + container2: list[ContainerType2] = strawchemy.field() + container3: list[ContainerType3] = strawchemy.field() diff --git a/tests/unit/schemas/pagination/paginate_empty.py b/tests/unit/schemas/pagination/paginate_empty.py new file mode 100644 index 00000000..c9d25536 --- /dev/null +++ b/tests/unit/schemas/pagination/paginate_empty.py @@ -0,0 +1,18 @@ +from __future__ import annotations + +import strawberry + +from strawchemy import Strawchemy +from tests.unit.models import Container + +strawchemy = Strawchemy("postgresql") + + +@strawchemy.type(Container, include="all", paginate=[]) +class ContainerType: + pass + + +@strawberry.type +class Query: + container: list[ContainerType] = strawchemy.field() diff --git a/tests/unit/schemas/pagination/paginate_specific_fields.py b/tests/unit/schemas/pagination/paginate_specific_fields.py new file mode 100644 index 00000000..d829b834 --- /dev/null +++ b/tests/unit/schemas/pagination/paginate_specific_fields.py @@ -0,0 +1,18 @@ +from __future__ import annotations + +import strawberry + +from strawchemy import Strawchemy +from tests.unit.models import Container + +strawchemy = Strawchemy("postgresql") + + +@strawchemy.type(Container, include="all", paginate=["fruits"]) +class ContainerType: + pass + + +@strawberry.type +class Query: + container: list[ContainerType] = strawchemy.field() diff --git a/tests/unit/schemas/pagination/pagination_config_default.py b/tests/unit/schemas/pagination/pagination_config_default.py index 46134b59..559b7645 100644 --- a/tests/unit/schemas/pagination/pagination_config_default.py +++ b/tests/unit/schemas/pagination/pagination_config_default.py @@ -5,7 +5,7 @@ from strawchemy import Strawchemy, StrawchemyConfig from tests.unit.models import Fruit -strawchemy = Strawchemy(StrawchemyConfig("postgresql", pagination=True)) +strawchemy = Strawchemy(StrawchemyConfig("postgresql", pagination="all")) @strawchemy.type(Fruit, include="all") diff --git a/tests/unit/schemas/pagination/pagination_config_empty.py b/tests/unit/schemas/pagination/pagination_config_empty.py new file mode 100644 index 00000000..9d219425 --- /dev/null +++ b/tests/unit/schemas/pagination/pagination_config_empty.py @@ -0,0 +1,18 @@ +from __future__ import annotations + +import strawberry + +from strawchemy import Strawchemy, StrawchemyConfig +from tests.unit.models import Container + +strawchemy = Strawchemy(StrawchemyConfig("postgresql", pagination=[])) + + +@strawchemy.type(Container, include="all") +class ContainerType: + pass + + +@strawberry.type +class Query: + container: list[ContainerType] = strawchemy.field() diff --git a/tests/unit/schemas/pagination/pagination_config_empty_with_type_override.py b/tests/unit/schemas/pagination/pagination_config_empty_with_type_override.py new file mode 100644 index 00000000..dfd1c96c --- /dev/null +++ b/tests/unit/schemas/pagination/pagination_config_empty_with_type_override.py @@ -0,0 +1,18 @@ +from __future__ import annotations + +import strawberry + +from strawchemy import Strawchemy, StrawchemyConfig +from tests.unit.models import Container + +strawchemy = Strawchemy(StrawchemyConfig("postgresql", pagination=[])) + + +@strawchemy.type(Container, include="all", paginate=["fruits"]) +class ContainerType: + pass + + +@strawberry.type +class Query: + container: list[ContainerType] = strawchemy.field() diff --git a/tests/unit/schemas/pagination/pagination_config_specific_fields.py b/tests/unit/schemas/pagination/pagination_config_specific_fields.py new file mode 100644 index 00000000..69f7b738 --- /dev/null +++ b/tests/unit/schemas/pagination/pagination_config_specific_fields.py @@ -0,0 +1,18 @@ +from __future__ import annotations + +import strawberry + +from strawchemy import Strawchemy, StrawchemyConfig +from tests.unit.models import Container + +strawchemy = Strawchemy(StrawchemyConfig("postgresql", pagination=["fruits", "vegetables"])) + + +@strawchemy.type(Container, include="all") +class ContainerType: + pass + + +@strawberry.type +class Query: + container: list[ContainerType] = strawchemy.field() diff --git a/tests/unit/schemas/pagination/pagination_config_with_type_override.py b/tests/unit/schemas/pagination/pagination_config_with_type_override.py new file mode 100644 index 00000000..5c557728 --- /dev/null +++ b/tests/unit/schemas/pagination/pagination_config_with_type_override.py @@ -0,0 +1,18 @@ +from __future__ import annotations + +import strawberry + +from strawchemy import Strawchemy, StrawchemyConfig +from tests.unit.models import Container + +strawchemy = Strawchemy(StrawchemyConfig("postgresql", pagination=["fruits"])) + + +@strawchemy.type(Container, include="all", paginate=["vegetables"]) +class ContainerType: + pass + + +@strawberry.type +class Query: + container: list[ContainerType] = strawchemy.field() diff --git a/tests/unit/schemas/pagination/pagination_default_limit.py b/tests/unit/schemas/pagination/pagination_default_limit.py index d815418c..36ac8f21 100644 --- a/tests/unit/schemas/pagination/pagination_default_limit.py +++ b/tests/unit/schemas/pagination/pagination_default_limit.py @@ -8,7 +8,7 @@ strawchemy = Strawchemy(StrawchemyConfig("postgresql", pagination_default_limit=5)) -@strawchemy.type(Fruit, include="all", child_pagination=True) +@strawchemy.type(Fruit, include="all", paginate="all") class FruitType: pass diff --git a/tests/unit/schemas/pagination/pagination_default_offset.py b/tests/unit/schemas/pagination/pagination_default_offset.py new file mode 100644 index 00000000..a0719eb7 --- /dev/null +++ b/tests/unit/schemas/pagination/pagination_default_offset.py @@ -0,0 +1,18 @@ +from __future__ import annotations + +import strawberry + +from strawchemy import Strawchemy, StrawchemyConfig +from tests.unit.models import Fruit + +strawchemy = Strawchemy(StrawchemyConfig("postgresql", pagination_default_offset=5)) + + +@strawchemy.type(Fruit, include="all", paginate="all") +class FruitType: + pass + + +@strawberry.type +class Query: + fruits: list[FruitType] = strawchemy.field(pagination=True)