graphile / crystal

🔮 Graphile's Crystal Monorepo; home to Grafast, PostGraphile, pg-introspection, pg-sql2 and much more!
https://graphile.org/
Other
12.62k stars 571 forks source link

Queries that return anonymous types do not respect optional parameters (with pgStrictFunctions = true) #2216

Closed nathanielgranor closed 2 weeks ago

nathanielgranor commented 1 month ago

Summary

PostGraphile documentation (and confirmed by Benjie in Discord) states that

If you'd like PostGraphile to treat all function arguments as required (non-null) unless they have a default then you can use the graphileBuildOptions.pgStrictFunctions = true setting. This is similar to marking the function as STRICT but with the subtle difference that arguments with defaults may be specified as NULL without necessitating that the function returns null. With this setting enabled, arguments without default value will be set mandatory while arguments with default value will be optional. For example: CREATE FUNCTION foo(a int, b int, c int = 0, d int = null)... would give a mutation foo(a: Int!, b: Int!, c: Int, d: Int).

But when a custom query returns an anonymous type, all parameters are marked required even if the postgresql definition includes a default value.

Steps to reproduce

  1. Running Postgraphile 5.0.0-beta.32
  2. Make sure to set graphileBuildOptions.pgStrictFunctions = true
  3. To test that pgStrictFunctions is working, create:
    create or replace function toy_function_1 (
    a int,
    b int,
    c int default null
    )
    returns int
    stable
    security invoker
    begin
    atomic
    select a+b
    ;
    end;
  4. Compile to GraphQL and note c is an optional parameter for toyFunction1 as expected.
  5. Now create:
    create type toy_response as (
    numadd int,
    numsub int
    );
    create or replace function toy_function_2 (
    a int,
    b int,
    c int default null
    )
    returns toy_response
    stable
    security invoker
    begin
    atomic
    select a+b,a-b
    ;
    end;
  6. Compile to GraphQL and note that c is still an optional parameter for toyFunction2 as expected.
  7. Finally, add the following and compile:
    create or replace function toy_function_3 (
    a int,
    b int,
    c int default null
    )
    returns table (
    numsum int,
    numdiff int
              )
    stable
    security invoker
    begin
    atomic
    select a+b, a-b
    ;
    end;

Expected results

Expect parameter c to be option in toyFunction3.

Actual results

Parameter c is required in toyFunction3:

"""Reads and enables pagination through a set of `ToyFunction3Record`."""
  toyFunction3(
    a: Int!
    b: Int!
    c: Int!

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

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

    """Read all values in the set after (below) this cursor."""
    after: Cursor

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

Additional context

Discussed in Discord Forum: https://discord.com/channels/489127045289476126/1296934601884700769

Possible Solution