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

Nested alias returning null #21826

Open
wants to merge 7 commits into
base: main
Choose a base branch
from

Conversation

balles
Copy link

@balles balles commented Mar 12, 2024

Scope

Fixes #18802.

In some cases using aliases in nested fields will return null, even if data is available. Problem is that inside the recursive function parseFields the alias field on the query is overwritten.

Take the following example:

Given a schema with this nested collections:

root_collection 
↳ first_level_a
     ↳ second_level_a
↳ first_level_b
Complete example schema

{
  "data": {
    "version": 1,
    "directus": "10.10.0",
    "vendor": "postgres",
    "collections": [
      {
        "collection": "first_level_a",
        "meta": {
          "accountability": "all",
          "archive_app_filter": true,
          "archive_field": null,
          "archive_value": null,
          "collapse": "open",
          "collection": "first_level_a",
          "color": null,
          "display_template": null,
          "group": "root_collection",
          "hidden": false,
          "icon": null,
          "item_duplication_fields": null,
          "note": null,
          "preview_url": null,
          "singleton": false,
          "sort": 1,
          "sort_field": null,
          "translations": null,
          "unarchive_value": null,
          "versioning": false
        },
        "schema": {
          "name": "first_level_a"
        }
      },
      {
        "collection": "first_level_b",
        "meta": {
          "accountability": "all",
          "archive_app_filter": true,
          "archive_field": null,
          "archive_value": null,
          "collapse": "open",
          "collection": "first_level_b",
          "color": null,
          "display_template": null,
          "group": "root_collection",
          "hidden": false,
          "icon": null,
          "item_duplication_fields": null,
          "note": null,
          "preview_url": null,
          "singleton": false,
          "sort": 2,
          "sort_field": null,
          "translations": null,
          "unarchive_value": null,
          "versioning": false
        },
        "schema": {
          "name": "first_level_b"
        }
      },
      {
        "collection": "root_collection",
        "meta": {
          "accountability": "all",
          "archive_app_filter": true,
          "archive_field": null,
          "archive_value": null,
          "collapse": "open",
          "collection": "root_collection",
          "color": null,
          "display_template": null,
          "group": null,
          "hidden": false,
          "icon": null,
          "item_duplication_fields": null,
          "note": null,
          "preview_url": null,
          "singleton": false,
          "sort": 1,
          "sort_field": null,
          "translations": null,
          "unarchive_value": null,
          "versioning": false
        },
        "schema": {
          "name": "root_collection"
        }
      },
      {
        "collection": "second_level_a",
        "meta": {
          "accountability": "all",
          "archive_app_filter": true,
          "archive_field": null,
          "archive_value": null,
          "collapse": "open",
          "collection": "second_level_a",
          "color": null,
          "display_template": null,
          "group": "first_level_a",
          "hidden": false,
          "icon": null,
          "item_duplication_fields": null,
          "note": null,
          "preview_url": null,
          "singleton": false,
          "sort": 1,
          "sort_field": null,
          "translations": null,
          "unarchive_value": null,
          "versioning": false
        },
        "schema": {
          "name": "second_level_a"
        }
      }
    ],
    "fields": [
      {
        "collection": "first_level_a",
        "field": "id",
        "type": "integer",
        "meta": {
          "collection": "first_level_a",
          "conditions": null,
          "display": null,
          "display_options": null,
          "field": "id",
          "group": null,
          "hidden": true,
          "interface": null,
          "note": null,
          "options": null,
          "readonly": false,
          "required": false,
          "sort": 1,
          "special": null,
          "translations": null,
          "validation": null,
          "validation_message": null,
          "width": "full"
        },
        "schema": {
          "name": "id",
          "table": "first_level_a",
          "data_type": "integer",
          "default_value": "nextval('first_level_a_id_seq'::regclass)",
          "max_length": null,
          "numeric_precision": 32,
          "numeric_scale": 0,
          "is_nullable": false,
          "is_unique": true,
          "is_primary_key": true,
          "is_generated": false,
          "generation_expression": null,
          "has_auto_increment": true,
          "foreign_key_table": null,
          "foreign_key_column": null
        }
      },
      {
        "collection": "first_level_a",
        "field": "second_level_a",
        "type": "integer",
        "meta": {
          "collection": "first_level_a",
          "conditions": null,
          "display": null,
          "display_options": null,
          "field": "second_level_a",
          "group": null,
          "hidden": false,
          "interface": "select-dropdown-m2o",
          "note": null,
          "options": null,
          "readonly": false,
          "required": false,
          "sort": 2,
          "special": [
            "m2o"
          ],
          "translations": null,
          "validation": null,
          "validation_message": null,
          "width": "full"
        },
        "schema": {
          "name": "second_level_a",
          "table": "first_level_a",
          "data_type": "integer",
          "default_value": null,
          "max_length": null,
          "numeric_precision": 32,
          "numeric_scale": 0,
          "is_nullable": true,
          "is_unique": false,
          "is_primary_key": false,
          "is_generated": false,
          "generation_expression": null,
          "has_auto_increment": false,
          "foreign_key_table": "second_level_a",
          "foreign_key_column": "id"
        }
      },
      {
        "collection": "first_level_b",
        "field": "id",
        "type": "integer",
        "meta": {
          "collection": "first_level_b",
          "conditions": null,
          "display": null,
          "display_options": null,
          "field": "id",
          "group": null,
          "hidden": true,
          "interface": null,
          "note": null,
          "options": null,
          "readonly": false,
          "required": false,
          "sort": 1,
          "special": null,
          "translations": null,
          "validation": null,
          "validation_message": null,
          "width": "full"
        },
        "schema": {
          "name": "id",
          "table": "first_level_b",
          "data_type": "integer",
          "default_value": "nextval('first_level_b_id_seq'::regclass)",
          "max_length": null,
          "numeric_precision": 32,
          "numeric_scale": 0,
          "is_nullable": false,
          "is_unique": true,
          "is_primary_key": true,
          "is_generated": false,
          "generation_expression": null,
          "has_auto_increment": true,
          "foreign_key_table": null,
          "foreign_key_column": null
        }
      },
      {
        "collection": "root_collection",
        "field": "id",
        "type": "integer",
        "meta": {
          "collection": "root_collection",
          "conditions": null,
          "display": null,
          "display_options": null,
          "field": "id",
          "group": null,
          "hidden": true,
          "interface": "input",
          "note": null,
          "options": null,
          "readonly": true,
          "required": false,
          "sort": 1,
          "special": null,
          "translations": null,
          "validation": null,
          "validation_message": null,
          "width": "full"
        },
        "schema": {
          "name": "id",
          "table": "root_collection",
          "data_type": "integer",
          "default_value": "nextval('root_collection_id_seq'::regclass)",
          "max_length": null,
          "numeric_precision": 32,
          "numeric_scale": 0,
          "is_nullable": false,
          "is_unique": true,
          "is_primary_key": true,
          "is_generated": false,
          "generation_expression": null,
          "has_auto_increment": true,
          "foreign_key_table": null,
          "foreign_key_column": null
        }
      },
      {
        "collection": "root_collection",
        "field": "first_level_b",
        "type": "integer",
        "meta": {
          "collection": "root_collection",
          "conditions": null,
          "display": null,
          "display_options": null,
          "field": "first_level_b",
          "group": null,
          "hidden": false,
          "interface": "select-dropdown-m2o",
          "note": null,
          "options": null,
          "readonly": false,
          "required": false,
          "sort": 3,
          "special": [
            "m2o"
          ],
          "translations": null,
          "validation": null,
          "validation_message": null,
          "width": "full"
        },
        "schema": {
          "name": "first_level_b",
          "table": "root_collection",
          "data_type": "integer",
          "default_value": null,
          "max_length": null,
          "numeric_precision": 32,
          "numeric_scale": 0,
          "is_nullable": true,
          "is_unique": false,
          "is_primary_key": false,
          "is_generated": false,
          "generation_expression": null,
          "has_auto_increment": false,
          "foreign_key_table": "first_level_b",
          "foreign_key_column": "id"
        }
      },
      {
        "collection": "root_collection",
        "field": "first_level_a",
        "type": "integer",
        "meta": {
          "collection": "root_collection",
          "conditions": null,
          "display": null,
          "display_options": null,
          "field": "first_level_a",
          "group": null,
          "hidden": false,
          "interface": "select-dropdown-m2o",
          "note": null,
          "options": null,
          "readonly": false,
          "required": false,
          "sort": 2,
          "special": [
            "m2o"
          ],
          "translations": null,
          "validation": null,
          "validation_message": null,
          "width": "full"
        },
        "schema": {
          "name": "first_level_a",
          "table": "root_collection",
          "data_type": "integer",
          "default_value": null,
          "max_length": null,
          "numeric_precision": 32,
          "numeric_scale": 0,
          "is_nullable": true,
          "is_unique": false,
          "is_primary_key": false,
          "is_generated": false,
          "generation_expression": null,
          "has_auto_increment": false,
          "foreign_key_table": "first_level_a",
          "foreign_key_column": "id"
        }
      },
      {
        "collection": "second_level_a",
        "field": "id",
        "type": "integer",
        "meta": {
          "collection": "second_level_a",
          "conditions": null,
          "display": null,
          "display_options": null,
          "field": "id",
          "group": null,
          "hidden": true,
          "interface": null,
          "note": null,
          "options": null,
          "readonly": false,
          "required": false,
          "sort": 1,
          "special": null,
          "translations": null,
          "validation": null,
          "validation_message": null,
          "width": "full"
        },
        "schema": {
          "name": "id",
          "table": "second_level_a",
          "data_type": "integer",
          "default_value": "nextval('second_level_a_id_seq'::regclass)",
          "max_length": null,
          "numeric_precision": 32,
          "numeric_scale": 0,
          "is_nullable": false,
          "is_unique": true,
          "is_primary_key": true,
          "is_generated": false,
          "generation_expression": null,
          "has_auto_increment": true,
          "foreign_key_table": null,
          "foreign_key_column": null
        }
      },
      {
        "collection": "second_level_a",
        "field": "field_to_alias",
        "type": "string",
        "meta": {
          "collection": "second_level_a",
          "conditions": null,
          "display": null,
          "display_options": null,
          "field": "field_to_alias",
          "group": null,
          "hidden": false,
          "interface": "input",
          "note": null,
          "options": null,
          "readonly": false,
          "required": false,
          "sort": 2,
          "special": null,
          "translations": null,
          "validation": null,
          "validation_message": null,
          "width": "full"
        },
        "schema": {
          "name": "field_to_alias",
          "table": "second_level_a",
          "data_type": "character varying",
          "default_value": null,
          "max_length": 255,
          "numeric_precision": null,
          "numeric_scale": null,
          "is_nullable": true,
          "is_unique": false,
          "is_primary_key": false,
          "is_generated": false,
          "generation_expression": null,
          "has_auto_increment": false,
          "foreign_key_table": null,
          "foreign_key_column": null
        }
      }
    ],
    "relations": [
      {
        "collection": "first_level_a",
        "field": "second_level_a",
        "related_collection": "second_level_a",
        "meta": {
          "junction_field": null,
          "many_collection": "first_level_a",
          "many_field": "second_level_a",
          "one_allowed_collections": null,
          "one_collection": "second_level_a",
          "one_collection_field": null,
          "one_deselect_action": "nullify",
          "one_field": null,
          "sort_field": null
        },
        "schema": {
          "table": "first_level_a",
          "column": "second_level_a",
          "foreign_key_table": "second_level_a",
          "foreign_key_column": "id",
          "constraint_name": "first_level_a_second_level_a_foreign",
          "on_update": "NO ACTION",
          "on_delete": "SET NULL"
        }
      },
      {
        "collection": "root_collection",
        "field": "first_level_b",
        "related_collection": "first_level_b",
        "meta": {
          "junction_field": null,
          "many_collection": "root_collection",
          "many_field": "first_level_b",
          "one_allowed_collections": null,
          "one_collection": "first_level_b",
          "one_collection_field": null,
          "one_deselect_action": "nullify",
          "one_field": null,
          "sort_field": null
        },
        "schema": {
          "table": "root_collection",
          "column": "first_level_b",
          "foreign_key_table": "first_level_b",
          "foreign_key_column": "id",
          "constraint_name": "root_collection_first_level_b_foreign",
          "on_update": "NO ACTION",
          "on_delete": "SET NULL"
        }
      },
      {
        "collection": "root_collection",
        "field": "first_level_a",
        "related_collection": "first_level_a",
        "meta": {
          "junction_field": null,
          "many_collection": "root_collection",
          "many_field": "first_level_a",
          "one_allowed_collections": null,
          "one_collection": "first_level_a",
          "one_collection_field": null,
          "one_deselect_action": "nullify",
          "one_field": null,
          "sort_field": null
        },
        "schema": {
          "table": "root_collection",
          "column": "first_level_a",
          "foreign_key_table": "first_level_a",
          "foreign_key_column": "id",
          "constraint_name": "root_collection_first_level_a_foreign",
          "on_update": "NO ACTION",
          "on_delete": "SET NULL"
        }
      }
    ]
  }
}

When we run the following example code, we will end up with firstLevelB = null, which is incorrect!
It should return something like firstLevelB = { id: 1 }.

import { createDirectus, graphql, staticToken } from "@directus/sdk";

const url = "your-directus-api-url";
const token = "your-token";

const client = createDirectus(url).with(staticToken(token)).with(graphql());

const INSERT_TEST_DATA = `
mutation createDataSet {
    create_root_collection_items(
      data: { first_level_a: { second_level_a: { field_to_alias: "this will be shown" } }, first_level_b: {} }
    ) {
      id
    }
  }
`;

const SHOW_BUG_QUERY = `
query showBugQuery {
    rootCollection: root_collection {
      id
      firstLevelA: first_level_a {
        id
        secondLevelA: second_level_a {
          id
          fieldToAlias: field_to_alias
        }
      }
      firstLevelB: first_level_b {
        id
      }
    }
  }
`;

const main = async () => {
  await client.query(INSERT_TEST_DATA);
  const result = await client.query(SHOW_BUG_QUERY);
  console.log(JSON.stringify(result, null, 2)); 

};

main().catch((err) => {
  console.error(JSON.stringify(err));
});

If we move the firstLevelB in the query above the firstLevelA, it will actually return the correct value. This is caused by the bug this PR tries to tackle.

What's changed:

  • add unit test for issue
  • use and pass local variables alias instead of query.alias
  • removed unnecessary async/ awaits

Potential Risks / Drawbacks

  • I didn't found a test for the convertWildcards function, so the changes are untested if those aren't tested in the blackbox testing

Review Notes / Questions

  • you can checkout the first commit to see the unit test failing. If you run the test you can see where the error is happening

Copy link

changeset-bot bot commented Mar 12, 2024

🦋 Changeset detected

Latest commit: f5fb3a1

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 2 packages
Name Type
@directus/api Patch
directus Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@paescuj paescuj self-assigned this Apr 2, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: 🆕 Needs Triage
Development

Successfully merging this pull request may close these issues.

Graphql returning null when using Aliases
2 participants