graphile-contrib / postgraphile-plugin-connection-filter

Filtering on PostGraphile connections
MIT License
286 stars 32 forks source link

Always add ...Exists field on virtual foreign key constraints #195

Open NicolasMahe opened 1 year ago

NicolasMahe commented 1 year ago

Hi everyone,

This PR forces the addition of the ...Exists field on virtual foreign key constraints.

As virtual foreign key constraints are not applied on the database level, it's possible to end up with empty connections on the GraphQL API when adding virtual foreign key constraints with smart tags.

The PgConnectionArgFilterForwardRelationsPlugin was only adding the ...Exists field if one of the columns used in the foreign key constraint is nullable. This is very good for database foreign keys, but not enough when adding virtual foreign keys to expose these connections on the GraphQL API, as the connection can still be null.


Here is the diff this PR produces on a simplified GraphQL schema:

type Ownership {
  """Reads a single `Asset` that is related to this `Ownership`."""
  asset: Asset
  assetId: String!
}

type Asset {
  """Reads and enables pagination through a set of `Ownership`."""
  ownerships(
    """Read all values in the set after (below) this cursor."""
    after: Cursor

    """Read all values in the set before (above) this cursor."""
    before: Cursor

    """
    A condition to be used in determining which values should be returned by the collection.
    """
    condition: OwnershipCondition

    """
    A filter to be used in determining which values should be returned by the collection.
    """
    filter: OwnershipFilter

    """Only read the first `n` values of the set."""
    first: Int

    """Only read the last `n` values of the set."""
    last: Int

    """
    Skip the first `n` values from our `after` cursor, an alternative to cursor
    based pagination. May not be used with `last`.
    """
    offset: Int

    """The method to use when ordering `Ownership`."""
    orderBy: [OwnershipsOrderBy!] = [PRIMARY_KEY_ASC]
  ): OwnershipsConnection!
}

input OwnershipFilter {
  """Filter by the object’s `asset` relation."""
  asset: AssetFilter

+  """A related `asset` exists."""
+  assetExists: Boolean

  """Filter by the object’s `assetId` field."""
  assetId: StringFilter
}

And the virtual foreign key constraint is defined as:

export default makeJSONPgSmartTagsPlugin({
  version: 1,
  config: {
    class: {
      Ownership: {
        tags: {
          foreignKey: [
            '("assetId") references "Asset" ("id")|@fieldName asset|@foreignFieldName ownerships',
          ],
        },
      },
    },
  },
})