Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Composite primary keys generating an incorrect schema #267

Open
ValentinVignal opened this issue May 28, 2024 · 1 comment
Open

Composite primary keys generating an incorrect schema #267

ValentinVignal opened this issue May 28, 2024 · 1 comment
Labels
bug Something isn't working

Comments

@ValentinVignal
Copy link
Contributor

Describe the bug

I'm using an entity with composite primary keys. Those primary keys are not used in the generated schema for the read-one, update-one, and delete-one queries/mutations.

Have you read the Contributing Guidelines?

Yes.

To Reproduce

Follow the installation for the example: https://tripss.github.io/nestjs-query/docs/introduction/example

Use this entity:

import { ID, ObjectType } from '@nestjs/graphql';
import { FilterableField, IDField } from '@ptc-org/nestjs-query-graphql';
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';

@Entity()
@ObjectType('TodoItem')
export class TodoItemEntity {
  @IDField(() => ID)
  @PrimaryGeneratedColumn()
  firstId!: string;

  @IDField(() => ID)
  @PrimaryGeneratedColumn()
  secondId!: string;

  @FilterableField()
  @Column()
  title!: string;
}

And this module:

import { NestjsQueryGraphQLModule } from '@ptc-org/nestjs-query-graphql';
import { NestjsQueryTypeOrmModule } from '@ptc-org/nestjs-query-typeorm';
import { Module } from '@nestjs/common';
import { TodoItemEntity } from './todo-item.entity';

@Module({
  imports: [
    NestjsQueryGraphQLModule.forFeature({
      // import the NestjsQueryTypeOrmModule to register the entity with typeorm
      // and provide a QueryService
      imports: [NestjsQueryTypeOrmModule.forFeature([TodoItemEntity])],
      resolvers: [
        {
          EntityClass: TodoItemEntity,
          DTOClass: TodoItemEntity,
        },
      ],
    }),
  ],
})
export class TodoItemModule {}

Expected behavior

The schema generated is using id: ID! for the read-one, update-one, and delete-one queries/mutations.

Generated schema
type TodoItem {
  firstId: ID!
  secondId: ID!
  title: String!
}

type DeleteManyResponse {
  # The number of records deleted.
  deletedCount: Int!
}

type TodoItemDeleteResponse {
  firstId: ID
  secondId: ID
  title: String
}

type UpdateManyResponse {
  # The number of records updated.
  updatedCount: Int!
}

type TodoItemEdge {
  # The node containing the TodoItem
  node: TodoItem!

  # Cursor for this node.
  cursor: ConnectionCursor!
}

# Cursor for paging through collections
scalar ConnectionCursor

type PageInfo {
  # true if paging forward and there are more records.
  hasNextPage: Boolean

  # true if paging backwards and there are more records.
  hasPreviousPage: Boolean

  # The cursor of the first returned record.
  startCursor: ConnectionCursor

  # The cursor of the last returned record.
  endCursor: ConnectionCursor
}

type TodoItemConnection {
  # Paging information
  pageInfo: PageInfo!

  # Array of edges.
  edges: [TodoItemEdge!]!
}

type Query {
  todoItem(
    # The id of the record to find.
    id: ID!
  ): TodoItem!
  todoItems(
    # Limit or page results.
    paging: CursorPaging! = { first: 10 }

    # Specify to filter the records returned.
    filter: TodoItemFilter! = {}

    # Specify to sort results.
    sorting: [TodoItemSort!]! = []
  ): TodoItemConnection!
}

input CursorPaging {
  # Paginate before opaque cursor
  before: ConnectionCursor

  # Paginate after opaque cursor
  after: ConnectionCursor

  # Paginate first
  first: Int

  # Paginate last
  last: Int
}

input TodoItemFilter {
  and: [TodoItemFilter!]
  or: [TodoItemFilter!]
  firstId: IDFilterComparison
  secondId: IDFilterComparison
  title: StringFieldComparison
}

input IDFilterComparison {
  is: Boolean
  isNot: Boolean
  eq: ID
  neq: ID
  gt: ID
  gte: ID
  lt: ID
  lte: ID
  like: ID
  notLike: ID
  iLike: ID
  notILike: ID
  in: [ID!]
  notIn: [ID!]
}

input StringFieldComparison {
  is: Boolean
  isNot: Boolean
  eq: String
  neq: String
  gt: String
  gte: String
  lt: String
  lte: String
  like: String
  notLike: String
  iLike: String
  notILike: String
  in: [String!]
  notIn: [String!]
}

input TodoItemSort {
  field: TodoItemSortFields!
  direction: SortDirection!
  nulls: SortNulls
}

enum TodoItemSortFields {
  firstId
  secondId
  title
}

# Sort Directions
enum SortDirection {
  ASC
  DESC
}

# Sort Nulls Options
enum SortNulls {
  NULLS_FIRST
  NULLS_LAST
}

type Mutation {
  createOneTodoItem(input: CreateOneTodoItemInput!): TodoItem!
  createManyTodoItems(input: CreateManyTodoItemsInput!): [TodoItem!]!
  updateOneTodoItem(input: UpdateOneTodoItemInput!): TodoItem!
  updateManyTodoItems(input: UpdateManyTodoItemsInput!): UpdateManyResponse!
  deleteOneTodoItem(input: DeleteOneTodoItemInput!): TodoItemDeleteResponse!
  deleteManyTodoItems(input: DeleteManyTodoItemsInput!): DeleteManyResponse!
}

input CreateOneTodoItemInput {
  # The record to create
  todoItem: CreateTodoItem!
}

input CreateTodoItem {
  firstId: ID!
  secondId: ID!
  title: String!
}

input CreateManyTodoItemsInput {
  # Array of records to create
  todoItems: [CreateTodoItem!]!
}

input UpdateOneTodoItemInput {
  # The id of the record to update
  id: ID!

  # The update to apply.
  update: UpdateTodoItem!
}

input UpdateTodoItem {
  firstId: ID
  secondId: ID
  title: String
}

input UpdateManyTodoItemsInput {
  # Filter used to find fields to update
  filter: TodoItemUpdateFilter!

  # The update to apply to all records found using the filter
  update: UpdateTodoItem!
}

input TodoItemUpdateFilter {
  and: [TodoItemUpdateFilter!]
  or: [TodoItemUpdateFilter!]
  firstId: IDFilterComparison
  secondId: IDFilterComparison
  title: StringFieldComparison
}

input DeleteOneTodoItemInput {
  # The id of the record to delete.
  id: ID!
}

input DeleteManyTodoItemsInput {
  # Filter to find records to delete
  filter: TodoItemDeleteFilter!
}

input TodoItemDeleteFilter {
  and: [TodoItemDeleteFilter!]
  or: [TodoItemDeleteFilter!]
  firstId: IDFilterComparison
  secondId: IDFilterComparison
  title: StringFieldComparison
}

I would expect something like:

type Query {
  todoItem(
-    # The id of the record to find.
-    id: ID!
+    firstId: ID!
+    secondId: ID!
  ): TodoItem!
}

input UpdateOneTodoItemInput {
-  # The id of the record to update
-  id: ID!
+  firstId: ID!
+  secondId: ID!

  # The update to apply.
  update: UpdateTodoItem!
}

input DeleteOneTodoItemInput {
-  # The id of the record to delete.
-  id: ID!
+  firstId: ID!
+  secondId: ID!
}

Desktop (please complete the following information):

  • Node Version: v22.2.0
  • @ptc-org/nestjs-query-core: ^6.1.0,
  • @ptc-org/nestjs-query-graphql: ^6.1.0,
  • @ptc-org/nestjs-query-typeorm: ^6.1.0,
@ValentinVignal ValentinVignal added the bug Something isn't working label May 28, 2024
@TriPSs
Copy link
Owner

TriPSs commented Jun 4, 2024

Interesting case, if I look at the code everywhere where we use IDField it only expects one field, to change this will be a challenge as we would then go from one known field (id) to 1 or more unknown fields.

Less then ideal solution: mark the second primary field as a required filter field?

@ValentinVignal ValentinVignal changed the title Composite primary keys generating a correct schema Composite primary keys generating an incorrect schema Jun 4, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

2 participants