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

"Gather" plugin example: treating unique indexes similarly to unique constraints #2054

Open
MarioSimou opened this issue May 9, 2024 · 1 comment

Comments

@MarioSimou
Copy link
Contributor

MarioSimou commented May 9, 2024

Summary

PostGraphile recognizes columns as unique only if they have a unique constraint and not when a unique index is created

While this is not a direct problem with postgraphile, the solution below can help those that use postgraphile in conjunction with prisma. As mentioned here, prisma doesn't set a unique constraint on a column using the @unique directive. Instead, it only creates a unique index. Consequently, postgraphile doesn't recognize those columns as unique, which causes a few issues.

Steps to reproduce

Add the @unique directive in a prisma schema. Also, run the migration against the database.

Expected results

Columns marked with @unique directive to be considered unique by postgraphile.

Actual results

Columns marked with @unique directive are ignored.

Possible Solution

Create this plugin and include it in your graphile config.

// uniqueIndexesPlugin.ts
import type {PgResourceUnique, PgCodecAttributes, PgResourceOptions} from 'postgraphile/@dataplan/pg'
import type {PgAttribute} from 'postgraphile/graphile-build-pg/pg-introspection'

const createUnique = <TAttributes extends PgCodecAttributes = PgCodecAttributes>(
  isPrimary: boolean,
  attributes: PgAttribute[],
  description?: string
): PgResourceUnique<TAttributes> => {
  const names = attributes.map(attribute => attribute.attname)
  const tags = attributes.reduce((tags, attribute) => ({...tags, ...attribute.getTags()}), {})

  return {
    isPrimary,
    attributes: names,
    description,
    extensions: {tags},
  }
}

const isPgAttribute = (attribute: PgAttribute | null): attribute is PgAttribute =>
  Boolean(attribute && attribute._type === 'PgAttribute')

const createUniquesLookup = (options: PgResourceOptions) => {
  const createUniqueId = (unique: PgResourceUnique) => unique.attributes.join('-')

  const hash =
    options.uniques?.reduce<Record<string, true>>(
      (uniques, unique) => ({
        ...uniques,
        [createUniqueId(unique)]: true,
      }),
      {}
    ) ?? {}

  return (unique: PgResourceUnique) => Boolean(hash[createUniqueId(unique)])
}

export const UniqueIndexesPlugin: GraphileConfig.Plugin = {
  name: 'UniqueIndexesPlugin',
  version: '0.0.1',
  gather: {
    hooks: {
      async pgTables_PgResourceOptions(_, event) {
        const relkinds = ['r']
        const {pgClass, resourceOptions} = event

        if (!relkinds.includes(pgClass.relkind)) {
          return
        }
        if (!resourceOptions.uniques) {
          return
        }

        const indexes = pgClass.getIndexes().filter(idx => idx.indisunique)
        const uniquesToAdd: PgResourceUnique[] = []
        const lookup = createUniquesLookup(resourceOptions)

        for (const idx of indexes) {
          const attributes = idx.getKeys()

          if (!attributes.every(isPgAttribute)) {
            continue
          }

          const isPrimary = idx.indisprimary ?? false

          const unique = createUnique(isPrimary, attributes)

          if (lookup(unique)) {
            continue
          }

          uniquesToAdd.push(unique)
        }

        resourceOptions.uniques = [...resourceOptions.uniques, ...uniquesToAdd]
      },
    },
  },
}
@benjie benjie changed the title Postgraphile recognizes columns as unique only if they have a unique constraint and not when a unique index is created "Gather" plugin example: treating unique indexes similarly to unique constraints May 9, 2024
@benjie
Copy link
Member

benjie commented May 9, 2024

Thanks for filling out the example from Discord and submitting it as an example for others! We really appreciate it 🙌

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Status: 📝 Docs Improvements
Development

No branches or pull requests

2 participants