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

Expose discrete range types as their canonicalized value, omitting inclusive #2272

Open
3 of 6 tasks
the-sun-will-rise-tomorrow opened this issue Dec 6, 2024 · 1 comment
Assignees

Comments

@the-sun-will-rise-tomorrow

Feature description

Currently, discrete range types such as daterange are represented as:

type DateRange {
  start: DateRangeBound
  end: DateRangeBound
}

type DateRangeBound {
  value: Date!
  inclusive: Bool!
}

For discrete range types, we can convert them to their canonical representation, so that the start of the range is always inclusive and the end always exclusive. Then, we can simplify the representation to just

type DateRange {
  start: Date # always inclusive
  end: Date   # always exclusive
}

Motivating example

We have a lot of date ranges in our schema, and would prefer to avoid pushing the requirement to deal with the inclusive flag to clients.

Breaking changes

Yes, this would be a breaking change and should probably be optional (maybe using a smart tag).

Supporting development

I [tick all that apply]:

  • am interested in building this feature myself
  • am interested in collaborating on building this feature
  • am willing to help testing this feature before it's released
  • am willing to write a test-driven test suite for this feature (before it exists)
  • am a Graphile sponsor ❤️
  • have an active support or consultancy contract with Graphile
@github-project-automation github-project-automation bot moved this to 🌳 Triage in V5.0.0 Dec 6, 2024
@benjie
Copy link
Member

benjie commented Dec 6, 2024

Neat!

Here's an example of a plugin that adds a codec, and registers the GraphQL types for it:

https://github.com/graphile/crystal/blob/main/graphile-build/graphile-build-pg/src/plugins/PgLtreePlugin.ts

If you can use the existing codec, you may be able to get away with just this bit:

schema: {
hooks: {
init(_, build) {
const codec = build.input.pgRegistry.pgCodecs.ltree;
if (codec) {
const ltreeTypeName = build.inflection.builtin("LTree");
build.registerScalarType(
ltreeTypeName,
{ pgCodec: codec },
() => ({
description:
"Represents an `ltree` hierarchical label tree as outlined in https://www.postgresql.org/docs/current/ltree.html",
// TODO: specifiedByURL: https://postgraphile.org/scalars/ltree
}),
'Adding "LTree" scalar type from PgLtreePlugin.',
);
build.setGraphQLTypeForPgCodec(codec, "output", ltreeTypeName);
build.setGraphQLTypeForPgCodec(codec, "input", ltreeTypeName);
}
return _;
},
},

because as the PostgreSQL docs note:

The built-in range types int4range, int8range, and daterange all use a canonical form that includes the lower bound and excludes the upper bound; that is, [).

Here's where we register the range types:

if (!build.getTypeMetaByName(rangeTypeName)) {
build.registerObjectType(
rangeTypeName,
{
isPgRangeType: true,
pgCodec: codec,
},
() => ({
description: build.wrapDescription(
`A range of \`${underlyingOutputTypeName}\`.`,
"type",
),
fields: () => ({
start: {
description: build.wrapDescription(
"The starting bound of our range.",
"field",
),
type: build.getOutputTypeByName(rangeBoundTypeName!),
},
end: {
description: build.wrapDescription(
"The ending bound of our range.",
"field",
),
type: build.getOutputTypeByName(rangeBoundTypeName!),
},
}),
}),
"PgCodecsPlugin enum range output",
);
}
if (!build.getTypeMetaByName(rangeInputTypeName)) {
build.registerInputObjectType(
rangeInputTypeName,
{
isPgRangeInputType: true,
pgCodec: codec,
},
() => ({
description: build.wrapDescription(
`A range of \`${underlyingInputTypeName}\`.`,
"type",
),
fields: () => ({
start: {
description: build.wrapDescription(
"The starting bound of our range.",
"field",
),
type: build.getInputTypeByName(
rangeBoundInputTypeName,
),
},
end: {
description: build.wrapDescription(
"The ending bound of our range.",
"field",
),
type: build.getInputTypeByName(
rangeBoundInputTypeName,
),
},
}),
}),
"PgCodecsPlugin enum range input",
);
}
build.setGraphQLTypeForPgCodec(codec, "output", rangeTypeName);
build.setGraphQLTypeForPgCodec(
codec,
"input",
rangeInputTypeName,
);

As you can see, we check to see if the type already exists and only register it if it doesn't - so you can register the type ahead of time and it should Just Work ™️.

You should be able to write this as a plugin in your own codebase; and once you have it working we can look at merging it into core. Please let me know how you get on!

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

No branches or pull requests

2 participants