GithubHelp home page GithubHelp logo

adrinalin4ik / nestjs-graphql-tools Goto Github PK

View Code? Open in Web Editor NEW
65.0 3.0 6.0 2.24 MB

NestJS Graphql Tools is a flexible solution that provides a bunch of decorators for solving problems like n+1 request, filtering, sorting, pagination, polymorphic relation, graphql field extraction. It is fully based on decorators. To use it you can just add a decorator to your resolver.

License: GNU General Public License v3.0

JavaScript 0.75% TypeScript 99.25%
auto dataloader graphql nestjs polymorphic query resolver typeorm decorator hasura

nestjs-graphql-tools's Introduction

NestJS Graphql tools Logo

NestJS graphql Tools

NestJS Graphql automation library for building performant API

NPM Version Package License NPM Downloads

Description

The library allows to build efficient graphql API helping overcome n+1 problem and building hasura-like search interface with the minimum dependencies.

Introduction

With the library you will be able to build queries like that easily, using decorators and having full controll over everything.

{
  users(
    where: {
      id: { in: [1,2,3,4] }
      task_title: { like: "%Task%" }
    }
    order_by: {email: ASC, created_at: DESC}
    paginate: {page: 1, per_page: 10}
  ) {
    id
    fname
    lname
    email
    tasks(order_by: {id: ASC_NULLS_LAST}) {
      id
      title
    }
  }
}

Overview

Installation

npm i nestjs-graphql-tools
or
yarn add nestjs-graphql-tools

Data Loader n+1 problem solver

Loader usage guide

  1. Decorate your resolver with @GraphqlLoader()
  2. Add @Loader() parameter as a first parameter
  3. @Loader will return you LoaderData interface which includes ids of entities and helpers for constructing sutable object for graphql
One to many example
@Resolver(() => UserObjectType) 
export class UserResolver {

  @ResolveField(() => TaskObjectType)
  @GraphqlLoader()
  async tasks(
    @Loader() loader: LoaderData<TaskObjectType, number>,
    @Args('story_points') story_points: number, // custom search arg
  ) {
    const tasks = await getRepository(Task).find({
      where: {
        assignee_id: In<number>(loader.ids) // assignee_id is foreign key from Task to User table
        story_points
      }
    });

    return loader.helpers.mapOneToManyRelation(tasks, loader.ids, 'assignee_id'); // this helper will construct an object like { <assignee_id>: Task }. Graphql expects this shape.
  }
}
Many to one relation
@Resolver(() => TaskObjectType)
export class TaskResolver {

  constructor(
    @InjectRepository(User) public readonly userRepository: Repository<User>
  ) {}

  @ResolveField(() => UserObjectType)
  @GraphqlLoader({
    foreignKey: 'assignee_id' // Here we're providing foreigh key. Decorator gather all the keys from parent and provide it in loader.ids
  })
  async assignee(
    @Loader() loader: LoaderData<TaskObjectType, number>,
    @Filter(() => UserObjectType) filter: Brackets,
  ) {
    const qb = this.userRepository.createQueryBuilder('u')
      .where(filter)
      .andWhere({
        id: In(loader.ids) // Here will be assigne_ids
      })
    const users = await qb.getMany();
    return loader.helpers.mapManyToOneRelation(users, loader.ids); // This helper provide the shape {assignee_id: User}
  }
}

Polymorphic relations

@GraphqlLoader decorator provides ability to preload polymorphic relations

Usage

To be able to use it you need to decorate your resolver with @GraphqlLoader decorator. Decorator has parameter which allows to specify fields which needs to be gathered for polymorphic relation.

@GraphqlLoader({
  polymorphic: {
    idField: 'description_id', // Name of polymorphic id attribute of the parent model
    typeField: 'description_type' // Name of polymorphic type attribute of the parent model
  }
})

This decorator will aggregate all types and provide ids for each type. All aggregated types will be aveilable in @Loader decorator. It has attribute which called `polymorphicTypes.

PolmorphicTypes attribute shape

[
  {
    type: string | number
    ids: string[] | number[]
  }
]
Example
// Parent class
// task.resolver.ts
@Resolver(() => TaskObjectType)
export class TaskResolver {
  constructor(
    @InjectRepository(Task) public readonly taskRepository: Repository<Task>,
    @InjectRepository(Description) public readonly descriptionRepository: Repository<Description>
  ) {}

  @ResolveField(() => [DescriptionObjectType])
  @GraphqlLoader()
  async descriptions(
    @Loader() loader: LoaderData<TaskObjectType, number>,
    @SelectedUnionTypes({ 
      nestedPolymorphicResolverName: 'descriptionable',
    }) selectedUnions: SelectedUnionTypesResult // <-- This decorator will gather and provide selected union types. NestedPolymorphicResolverName argument allows to specify where specifically it should gather the fields
  ) {
    // Mapping graphql types to the database types
    const selectedTypes = Array.from(selectedUnions.types.keys()).map(type => { 
      switch (type) {
        case DescriptionTextObjectType.name:
          return DescriptionType.Text;
        case DescriptionChecklistObjectType.name:
          return DescriptionType.Checklist;
      }
    });

    const qb = this.descriptionRepository.createQueryBuilder('d')
      .andWhere({
        task_id: In(loader.ids),
        description_type: In(selectedTypes) // finding only selected types
      })
    
    const descriptions = await qb.getMany();
    return loader.helpers.mapOneToManyRelation(descriptions, loader.ids, 'task_id');
  }
}


// Polymorphic resolver
// description.resolver.ts
@Resolver(() => DescriptionObjectType)
export class DescriptionResolver {
  constructor(
    @InjectRepository(DescriptionText) public readonly descriptionTextRepository: Repository<DescriptionText>,
    @InjectRepository(DescriptionChecklist) public readonly descriptionChecklistRepository: Repository<DescriptionChecklist>,
  ) {}
  
  @ResolveField(() => [DescriptionableUnion], { nullable: true })
  @GraphqlLoader({ // <-- We will load description_id field of parent model to the ids and description_type field to the type
    polymorphic: {
      idField: 'description_id',
      typeField: 'description_type'
    }
  })
  async descriptionable(
    @Loader() loader: PolymorphicLoaderData<[DescriptionText | DescriptionChecklist], number, DescriptionType>, // <-- It will return aggregated polymorphicTypes
    @SelectedUnionTypes() types: SelectedUnionTypesResult // <-- It will extract from the query and return selected union types
  ) {
    const results = []; // <-- We need to gather all entities to the single array

    for (const item of loader.polimorphicTypes) {
      switch(item.descriminator) {
        case DescriptionType.Text:
          const textDescriptions = await this.descriptionTextRepository.createQueryBuilder()
          .select(types.getFields(DescriptionTextObjectType))
          .where({
            id: In(item.ids)
          })
          .getRawMany();

          results.push({ descriminator: DescriptionType.Text, entities: textDescriptions })

          break;
        case DescriptionType.Checklist:
          const checklistDescriptions = await this.descriptionChecklistRepository.createQueryBuilder()
          .select(types.getFields(DescriptionChecklistObjectType))
          .where({
            id: In(item.ids)
          })
          .getRawMany();

          results.push({ descriminator: DescriptionType.Checklist, entities: checklistDescriptions })
          
          break;
        default: break;
      }
    }
    return loader.helpers.mapOneToManyPolymorphicRelation(results, loader.ids); // <-- This helper will change shape of responce to the shape which is sutable for graphql
  }
}

You can find complete example in src/descriptions folder

Filters

Filter is giving ability to filter out entities by the condition. Condition looks similar to hasura interface using operators eq, neq, gt, gte, lt, lte, in, like, notlike, between, notbetween, null. By default it generates filter based on provided model. It supports only first level of the tables hierachy. If you need to search in depth you can declare custom filters (example 3).

Basic example 1

{
  users(where: {id: {eq: 1}}) {
    id
  }
}

Basic example 2

{
  users(
    where: {
      and: [
        {
          email: {like: "yahoo.com"}
        }
        {
          email: {like: "google.com"}
        }
      ],
      or: {
        id: {
          between: [1,2,3]
        }
      }
    }
  ) {
    id
  }
}

Filter usage guide

  1. Add @Filter() parameter with type of FilterArgs
  2. @Filter() will return typeorm compatible condition which you can use in your query builder.
@Query with filters
@Resolver(() => UserObjectType)
export class UserResolver {
  constructor(
    @InjectRepository(Task) public readonly taskRepository: Repository<Task>,
    @InjectRepository(User) public readonly userRepository: Repository<User>
  ) {}

  @Query(() => [UserObjectType])
  users(
    @Filter(() => UserObjectType) filter: FilterArgs, // It will return  typeorm condition
    @Args('task_title', {nullable: true}) taskTitle: string, // You can add custom additional filter if needed
  ) {
    const qb = this.userRepository.createQueryBuilder('u')
      .leftJoin('task', 't', 't.assignee_id = u.id')
      .where(filter)
      .distinct();

      if (taskTitle) { // mixed filters
        qb.andWhere(`t.title ilike :title`, { title: `%${taskTitle}%` })
      }

    return qb.getMany()
  }
}
@ResolveField with filter
@Resolver(() => UserObjectType)
export class UserResolver {
  constructor(@InjectRepository(Task) public readonly taskRepository: Repository<Task>) {}

  @ResolveField(() => TaskObjectType)
  @GraphqlLoader()
  async tasks(
    @Loader() loader: LoaderData<TaskObjectType, number>,
    @Filter(() => TaskObjectType) filter: FilterArgs,
  ) {
    const qb = this.taskRepository.createQueryBuilder()
    .where(filter)
    .andWhere({
      assignee_id: In<number>(loader.ids)
    });

    const tasks = await qb.getMany();
    
    return loader.helpers.mapOneToManyRelation(tasks, loader.ids, 'assignee_id');
  }
}
Custom filters
@InputType()
export class UserFilterInputType {
  @FilterField(() => String, { sqlExp: 't.title'})
  task_title: string;

  @FilterField(() => String, { sqlExp: 't.story_points'})
  task_story_points: number;
  
  @FilterField(() => String, { sqlExp: 'concat(u.fname, \' \', u.lname)'})
  full_name: string;
}

// Resolver
@Resolver(() => UserObjectType)
export class UserResolver {
  constructor(
    @InjectRepository(Task) public readonly taskRepository: Repository<Task>,
    @InjectRepository(StoryModel) public readonly storyRepository: Repository<StoryModel>,
    @InjectRepository(User) public readonly userRepository: Repository<User>
  ) {}

  @Query(() => [UserObjectType])
  users(
    @Filter(() => [UserObjectType, UserFilterInputType]) filter: FilterArgs, // <-- Object model and Filter model. It is possible to provide only one model or more that 2.
    @Sorting(() => UserObjectType, { sqlAlias: 'u' }) sorting: SortArgs<UserObjectType>
  ) {
    const qb = this.userRepository.createQueryBuilder('u')
      .leftJoin('task', 't', 't.assignee_id = u.id')
      .where(filter)
      .orderBy(sorting);

    return qb.getMany()
  }
}

You can also exclude some fields from the DTO filter. Read Exclusions.

Sorting

The library provides ability to make sorting. It supports all types of sorting. [ASC/DESC] [NULLS FIRST/LAST]

Basic example

{
  users(
    order_by: {
      id: ASC_NULLS_LAST
    }
  ) {
    id
  }
}
@Resolver(() => TaskObjectType)
export class TaskResolver {
  constructor(@InjectRepository(Task) public readonly taskRepository: Repository<Task>) {}

  @Query(() => [TaskObjectType])
  async tasks(
    /* SqlAlias is an ptional argument. Allows to provide alias in case if you have many tables joined. In current case it doesn't required */
    @Sorting(() => TaskObjectType, { sqlAlias: 't' }) sorting: SortArgs<TaskObjectType>
  ) {
    const qb = this.taskRepository.createQueryBuilder('t')
      .orderBy(sorting);
    return qb.getMany();
  }
}

Custom sorting fields

// sorting.dto.ts
@InputType()
export class UserSortingInputType {
  @SortingField({sqlExp: 't.story_points'})
  task_story_points: number;
}

// user.resolver.ts
@Resolver(() => UserObjectType)
export class UserResolver {
  constructor(
    @InjectRepository(Task) public readonly taskRepository: Repository<Task>,
    @InjectRepository(StoryModel) public readonly storyRepository: Repository<StoryModel>,
    @InjectRepository(User) public readonly userRepository: Repository<User>
  ) {}

  @Query(() => [UserObjectType])
  users(
    /* SqlAlias is an optional argument. You can provide alias in case if you have many tables joined.
    Object model and Sorting model. Ability to provide 1+ model. It accepts both Object and Sorting models. Next model in array extends previous model overriding fields with the same names.
    */
    @Sorting(() => [UserObjectType, UserSortingInputType], { sqlAlias: 'u' }) sorting: SortArgs<UserObjectType>
  ) {
    const qb = this.userRepository.createQueryBuilder('u')
      .leftJoin('task', 't', 't.assignee_id = u.id')
      .orderBy(sorting)
      .distinct();

    return qb.getMany()
  }
}

You can also exclude some fields from the sorting DTO. Read Exclusions.

Exclusions

Sometimes you don't want to provide filters/sorting by all the fields in the dto. There's a couple decorators that can help with it @FilterField({exclude: true}) and @SortingField({exclude: true})

Exclude field from filters and sortings

@ObjectType()
export class User {
  @Field(() => String)
  fname: string;

  @Field(() => String)
  @FilterField({exclude: true})
  @SortingField({exclude: true})
  mname: string;

  @Field(() => String)
  lname: string;
}

export class UserResolver {
  @Query(() => [UserObjectType])
  users(
    @Filter(() => [UserObjectType], {sqlAlias: 'u'}) filter: FilterArgs,
    @Sorting(() => [UserObjectType], { sqlAlias: 'u' }) sorting: SortArgs<UserObjectType>
  ) {
    const qb = this.userRepository.createQueryBuilder('u')
      .where(filter)
      .orderBy(sorting);

    return qb.getMany()
  }
}

Now, if you try to build a query with the sorting an filtering by mname you'll get an error, because there's not such field in the graphql schema definition for sorting and filtering.

Pagination

The library provides parameter decorator @Paginator() for the pagination. This decorator returns object like that

{
  page: number,
  per_page: number
}

@Query with pagination

@Resolver(() => TaskObjectType)
export class TaskResolver {
  constructor(@InjectRepository(Task) public readonly taskRepository: Repository<Task>) {}

  @Query(() => [TaskObjectType])
  async tasks(
   @Paginator() paginator: PaginatorArgs,
  ) {
    const qb = this.taskRepository.createQueryBuilder('t');
    
    if (paginator) {
      qb.offset(paginator.page * paginator.per_page).limit(paginator.per_page)
    }

    return qb.getMany();
  }
}

Field extraction

The library allows to gather only requested field from the query and provides it as an array to the parameter variable.

Basic example

Simple graphql query

{
  tasks {
    id
    title
  }
}

Resolver

@Resolver(() => TaskObjectType)
export class TaskResolver {
  constructor(@InjectRepository(Task) public readonly taskRepository: Repository<Task>) {}

  @Query(() => [TaskObjectType])
  async tasks(
   @Filter(() => TaskObjectType) filter: FilterArgs,
   @SelectedFields({sqlAlias: 't'}) selectedFields: SelectedFieldsResult // Requested fields will be here. sqlAlias is optional thing. It useful in case if you're using alias in query builder
  ) {
    const res = await this.taskRepository.createQueryBuilder('t')
      .select(selectedFields.fieldsData.fieldsString) // fieldsString return array of strings
      .where(filter)
      .getMany();
    return res;
  }
}

The query will generate typeorm request with only requested fields

SELECT "t"."id" AS "t_id", "t"."title" AS "t_title" FROM "task" "t"

Base models and inheritance

In order to make base model with common attributes it is required to decorate base model with the @InheritedModel() decorator. You can find usage of it in base.dto.ts file inside src folder.

How to inherit DTO from base class

@ObjectType()
@InheritedModel() // <-- Make inheritance possible. If you not decorate object with this decorator, you will not see these properties in "where" and sorting statements
export class BaseDTO {
  @Field(() => Int)
  id: number;

  // Timestamps
  @Field(() => Date)
  created_at: Date;

  @Field(() => Date)
  updated_at: Date;
}

Federation

Basic support of federation already in place. Just add to your method with @ResolveReference() one more decorator @GraphqlLoader()

Example

This examples is the reference to official example https://github.com/nestjs/nest/tree/master/sample/31-graphql-federation-code-first. Clone https://github.com/nestjs/nest/tree/master/sample/31-graphql-federation-code-first (download specific directory with https://download-directory.github.io/ or with chrome extention https://chrome.google.com/webstore/detail/gitzip-for-github/ffabmkklhbepgcgfonabamgnfafbdlkn)

  1. Annotate method resolveReference of users-application/src/users/users.resolver.ts
// users-application/src/users/users.resolver.ts
@ResolveReference()
@GraphqlLoader()
async resolveReference(
   @Loader() loader: LoaderData<User, number>,
) {
 const ids = loader.ids;
 const users = this.usersService.findByIds(ids);
 return loader.helpers.mapManyToOneRelation(users, loader.ids, 'id')
}
  1. Add method findByIds to users-application/src/users/users.service.ts
// users-application/src/users/users.service.ts
@Injectable()
export class UsersService {
  private users: User[] = [
    { id: 1, name: 'John Doe' },
    { id: 2, name: 'Richard Roe' },
  ];

  findByIds(idsList: number[]): User[] {
    return this.users.filter((user) => idsList.some(id => Number(id) === user.id));
  }
}
  1. Install dependencies of 3 projects : npm ci in gateway, posts-application, users-application.

  2. Run all projects in order :

    • cd users-application && npm run start
    • cd posts-application && npm run start
    • cd gateway && npm run start
  3. Go to localhost:3001/graphql and send graphql request to gateway

{
  posts {
    id
    title
    authorId
    user {
      id
      name
    }
  }
}

Additional options

Options are ENV variables that you can provide to configurate the lib

  • FILTER_OPERATION_PREFIX - Operation prefix. You can make hasura-like prefix for where operators like _eq, _neq, etc. Example FILTER_OPERATION_PREFIX=\_

More examples

You can find more examples in the src folder

FAQ

  1. Q: Let's say you have many joins and some tables has duplicated fields like name or title. A: In order not to break filters you need to provide sqlAlias that matches alias of the main model of the query. There plenty examples in the code in in readme.
  2. Q:The same example where you have a model with many joins and you want to provide ability to sort or filter by joined field. A: you can create custom filter with ability to provide sql alias that they will use. Check out filtering section, there a couple examples with custom fields.
  3. Q: The error: QueryFailedError: for SELECT DISTINCT, ORDER BY expressions must appear in select list. A To avoid this error add sorted field to selected fields. In most of the time it might happen in case you're using custom fields for sorting.

Contribution

If you want to contribute please create new PR with good description.

How to run the project:

  1. Create a database
createdb -h localhost -U postgres nestjs_graphql_tools_development_public;
  1. Fill out database config in config/default.json
  2. Run dev server
yarn install
yarn start:dev

On the first run, server will seed up the database with testing dataset.

  1. Reach out http://localhost:3000/graphql

License

NestJS Graphql tools is GNU GPLv3 licensed.

nestjs-graphql-tools's People

Contributors

adrinalin4ik avatar dependabot[bot] avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

nestjs-graphql-tools's Issues

Camelcase properties get generated as lowercase in the filter output

Describe the bug
Camelcase properties get generated as lowercase in the filter output e.g adminHome -> adminhome

To Reproduce
Steps to reproduce the behavior:

  1. Create an Entity and ObjectType with camel cased property e.g adminHome
  2. Pass the filter as a where prop to the entity repository
  3. Error like adminhome is not a valid column should throw

Expected behavior
Should work just like the non-camel cased columns.

Desktop (please complete the following information):

  • Mac OS Sonoma
  • Chrome

Use library along with global pipes

In the release 0.7.17, when we follow the readme adding @filter() parameter with type of Brackets from typeorm library, an error is throw: where.whereFactory is not a function.

Changing the type Brackets into any solve the problems.

Sorting by multiple fields is passed in the wrong order

Describe the bug
I pass to GraphQL in the parameter the first sort by name and the second parameter by id - however, regardless of my order in the resolver, it comes first id then name

To Reproduce
1 Set variables
{
"orderBy":
{
"date_start": "ASC",
"id":"ASC",
},
}

  1. Query
    news(order_by: $orderBy) {
    count
    data {
    id
    name
    date_start
    }
    }

  2. Resolver param
    @Sorting(() => NewsModel, {}) sorting: SortArgs< NewsModel >,

  3. Log
    console.log('sorting', sorting);

  4. See error
    sorting { id: 'ASC', date_start: 'ASC' }

Expected behavior
sorting { date_start: 'ASC', id: 'ASC' }

Screenshots
2023-11-20_19-43-44
2023-11-20_19-35-02 (2)

Version:
"nestjs-graphql-tools": "^0.8.4",

Use dataloader with @ResolveReference

First of all, specials thanks for this awesome library.

Describe the bug
With Graphql Federation, you use @ResolveReference to resolve a reference to an entity.
Unfortunately, @GraphqlLoader() and @loader() annotations doesn't work with @ResolveReference.
I ran into this error :

error {
  message: "Cannot read properties of undefined (reading 'path')",
  locations: [ { line: 1, column: 34 } ],
  path: [ '_entities', 0 ],
  extensions: {
    code: 'INTERNAL_SERVER_ERROR',
    stacktrace: [
      "TypeError: Cannot read properties of undefined (reading 'path')",
      '    at UsersResolver.descriptor.value (/work/users-application/node_modules/nestjs-graphql-tools/lib/loader.ts:125:51)',
      '    at /work/users-application/node_modules/@nestjs/core/helpers/external-context-creator.js:67:33',
      '    at processTicksAndRejections (node:internal/process/task_queues:96:5)',
      '    at async target (/work/users-application/node_modules/@nestjs/core/helpers/external-context-creator.js:74:28)'
    ]
  }
}

It seems to be related to line 96 in loader.ts , where ctx.getArgs() have different shape with graphql federation :

  const args = ctx.getArgs();
  const { req } = args[2];
  const info = args[3];

On federated graph, req is in args[1] and info in args[2] .
Theses updates solved my issue :

export const Loader = createParamDecorator((_data: unknown, ctx: ExecutionContext) => {
   const args = ctx.getArgs();
   let req;
   let info;
   if(args.length < 4) {
       req = args[1].req;
       info = args[2]
   } else {
       req = args[2].req;
       info = args[3]
   }
return {
....

To Reproduce
Steps to reproduce the behavior:

  @ResolveReference()
  @GraphqlLoader()
  async resolveReference(
      @Loader() loader: LoaderData<User, number>,
      @Parent() reference: { __typename: string; id: number },
  ) {
    const ids = loader.ids;
    const users = this.usersService.findByIds(ids);
    return loader.helpers.mapManyToOneRelation(users, loader.ids, 'id')
  }
  • add method findByIds to users-application/src/users/users.service.ts :
  findByIds(idsList: number[]): User[] {
    return this.users.filter((user) => idsList.some(id => Number(id) === user.id));
  }
  • install dependencies of 3 projects : npm ci in gateway, posts-application, users-application
  • run all projects in order :
    • cd users-application && npm run start
    • cd posts-application && npm run start
    • cd gateway && npm run start
  • send graphql request to gateway
{
  posts {
    id
    title
    authorId
    user {
      id
      name
    }
  }
}

Expected behavior
We expect no error and result return from the gateway

Screenshots
If applicable, add screenshots to help explain your problem.

Desktop (please complete the following information):

  • OS: Ubuntu 18
  • Browser chrome
  • Version 116.0.5845.179

Additional context
we can use also fieldName to search if this is a FederatedGraph by looking info.fieldName='_entities' too, or testing info.path.prev === undefined

Add genergic type for `LoaderData`.`parent`

Is your feature request related to a problem? Please describe.
Right now we have LoaderData.parent as any

export interface LoaderData<DtoType, IdType> {
  parent: any;
  // ...
}

Describe the solution you'd like
I propose to add the ability to specify the type without braking changes

export interface LoaderData<DtoType, IdType, ParentType extends object = any> {
  parent: ParentType;
}

Resolving Ambiguity: Handling Column Ambiguity Error in Multi-Table Product Query with Status Filter

Describe the bug
Suppose we have tree table product, category, and product type tables in all three tables containing field name status. So while I fetch products with category and product type using setFindOption with relation category and product type and apply filter. In this case, if the user tries to fetch the products by status using a filter they got the error of column status is ambiguous. The reason behind this error is that filter variables do not attach any table preference in query building so that is why the SQL database cause an error.

image

Mysql error

Describe the bug
I receive: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '::varchar ilike '%Task%'::varchar))' when I use Like filter

To Reproduce
Steps to reproduce the behavior:

@Query(() => [CategoryType], { nullable: 'itemsAndList' })
  @GraphqlFilter()
  async allCategories(
    @Filter(() => CategoryType) filter: Brackets,
    @Paginator() paginator: PaginatorArgs,
  ): Promise<CategoryType[]> {
    const qb = this.categoriesRepository.createQueryBuilder('c').where(filter);
    console.log(filter);
    if (paginator) {
      qb.offset(paginator.page * paginator.per_page).limit(paginator.per_page);
    }
    return qb.getMany();
  }

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.