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

[Bug]: Validating entity relationships throws unhandled exception #2184

Closed
1 task done
Aniruddh25 opened this issue Apr 23, 2024 · 7 comments · Fixed by #2202
Closed
1 task done

[Bug]: Validating entity relationships throws unhandled exception #2184

Aniruddh25 opened this issue Apr 23, 2024 · 7 comments · Fixed by #2202
Assignees
Labels
bug Something isn't working
Milestone

Comments

@Aniruddh25
Copy link
Contributor

Aniruddh25 commented Apr 23, 2024

What happened?

Cannot figure out what's wrong with this sample config. Doing dab validate causes an Unhandled Exception with the stack trace as provided in the log output.

{
  "$schema": "https://dataapibuilder.azureedge.net/schemas/v0.5.34/dab.draft.schema.json",
  "data-source": {
    "database-type": "mssql",
    "options": {
      "set-session-context": false
    },
    "connection-string": "Server=XXXXX;Persist Security Info=False;User ID=<USERHERE>;Password=<PWD HERE> ;MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;"
  },
  "runtime": {
    "rest": {
      "enabled": true,
      "path": "/api"
    },
    "graphql": {
      "allow-introspection": true,
      "enabled": true,
      "path": "/graphql"
    },
    "host": {
      "mode": "development",
      "cors": {
        "origins": [],
        "allow-credentials": false
      },
      "authentication": {
        "provider": "StaticWebApps"
      }
    }
  },
  "entities": {
    "Publisher": {
      "source": {
        "object": "publishers",
        "type": "table",
        "key-fields": ["id"]
      },
      "graphql": {
        "enabled": true,
        "type": {
          "singular": "Publisher",
          "plural": "Publishers"
        }
      },
      "rest": {
        "enabled": true
      },
      "permissions": [
        {
          "role": "anonymous",
          "actions": [
            {
              "action": "read"
            }
          ]
        },
        {
          "role": "authenticated",
          "actions": [
            {
              "action": "create"
            },
            {
              "action": "read"
            },
            {
              "action": "update"
            },
            {
              "action": "delete"
            }
          ]
        },
        {
          "role": "policy_tester_01",
          "actions": [
            {
              "action": "read",
              "fields": {
                "exclude": [],
                "include": [
                  "*"
                ]
              },
              "policy": {
                "database": "@item.id eq 1940"
              }
            },
            {
              "action": "update",
              "fields": {
                "exclude": [],
                "include": [
                  "*"
                ]
              }
            },
            {
              "action": "create"
            },
            {
              "action": "delete"
            }
          ]
        },
        {
          "role": "policy_tester_02",
          "actions": [
            {
              "action": "read",
              "fields": {
                "exclude": [],
                "include": [
                  "*"
                ]
              },
              "policy": {
                "database": "@item.id ne 1940"
              }
            },
            {
              "action": "update",
              "fields": {
                "exclude": [],
                "include": [
                  "*"
                ]
              }
            },
            {
              "action": "create"
            },
            {
              "action": "delete"
            }
          ]
        },
        {
          "role": "policy_tester_03",
          "actions": [
            {
              "action": "read",
              "fields": {
                "exclude": [],
                "include": [
                  "*"
                ]
              },
              "policy": {
                "database": "@item.id ne 1940"
              }
            },
            {
              "action": "update",
              "fields": {
                "exclude": [],
                "include": [
                  "*"
                ]
              }
            },
            {
              "action": "create"
            },
            {
              "action": "delete"
            }
          ]
        },
        {
          "role": "policy_tester_04",
          "actions": [
            {
              "action": "read",
              "fields": {
                "exclude": [],
                "include": [
                  "*"
                ]
              },
              "policy": {
                "database": "@item.id eq 1940"
              }
            },
            {
              "action": "update",
              "fields": {
                "exclude": [],
                "include": [
                  "*"
                ]
              }
            },
            {
              "action": "create"
            },
            {
              "action": "delete"
            }
          ]
        },
        {
          "role": "policy_tester_06",
          "actions": [
            {
              "action": "read",
              "fields": {
                "exclude": [],
                "include": [
                  "*"
                ]
              },
              "policy": {
                "database": "@item.id eq 1940"
              }
            },
            {
              "action": "update",
              "fields": {
                "exclude": [],
                "include": [
                  "*"
                ]
              }
            },
            {
              "action": "create"
            },
            {
              "action": "delete"
            }
          ]
        },
        {
          "role": "database_policy_tester",
          "actions": [
            {
              "action": "create",
              "policy": {
                "database": "@item.name ne 'New publisher'"
              }
            },
            {
              "action": "update",
              "policy": {
                "database": "@item.id ne 1234"
              }
            },
            {
              "action": "read",
              "policy": {
                "database": "@item.id ne 1234 or @item.id gt 1940"
              }
            }
          ]
        }
      ],
      "relationships": {
        "books": {
          "cardinality": "many",
          "target.entity": "Book",
          "source.fields": ["id"],
          "target.fields": ["publisher_id"],
          "linking.source.fields": [],
          "linking.target.fields": []
        }
      }
    },
    "Book": {
      "source": {
        "object": "books",
        "type": "table",
        "key-fields": ["id"]
      },
      "graphql": {
        "enabled": true,
        "type": {
          "singular": "book",
          "plural": "books"
        }
      },
      "rest": {
        "enabled": true
      },
      "permissions": [
        {
          "role": "anonymous",
          "actions": [
            {
              "action": "create"
            },
            {
              "action": "read"
            },
            {
              "action": "update"
            },
            {
              "action": "delete"
            }
          ]
        },
        {
          "role": "authenticated",
          "actions": [
            {
              "action": "create"
            },
            {
              "action": "read"
            },
            {
              "action": "update"
            },
            {
              "action": "delete"
            }
          ]
        },
        {
          "role": "policy_tester_01",
          "actions": [
            {
              "action": "read",
              "fields": {
                "exclude": [],
                "include": [
                  "*"
                ]
              },
              "policy": {
                "database": "@item.title eq 'Policy-Test-01'"
              }
            },
            {
              "action": "update",
              "fields": {
                "exclude": [],
                "include": [
                  "*"
                ]
              }
            },
            {
              "action": "create"
            },
            {
              "action": "delete"
            }
          ]
        },
        {
          "role": "policy_tester_02",
          "actions": [
            {
              "action": "read",
              "fields": {
                "exclude": [],
                "include": [
                  "*"
                ]
              },
              "policy": {
                "database": "@item.title ne 'Policy-Test-01'"
              }
            },
            {
              "action": "update",
              "fields": {
                "exclude": [],
                "include": [
                  "*"
                ]
              }
            },
            {
              "action": "create"
            },
            {
              "action": "delete"
            }
          ]
        },
        {
          "role": "policy_tester_03",
          "actions": [
            {
              "action": "read",
              "fields": {
                "exclude": [],
                "include": [
                  "*"
                ]
              },
              "policy": {
                "database": "@item.title eq 'Policy-Test-01'"
              }
            },
            {
              "action": "update",
              "fields": {
                "exclude": [],
                "include": [
                  "*"
                ]
              }
            },
            {
              "action": "create"
            },
            {
              "action": "delete"
            }
          ]
        },
        {
          "role": "policy_tester_04",
          "actions": [
            {
              "action": "read",
              "fields": {
                "exclude": [],
                "include": [
                  "*"
                ]
              },
              "policy": {
                "database": "@item.title ne 'Policy-Test-01'"
              }
            },
            {
              "action": "update",
              "fields": {
                "exclude": [],
                "include": [
                  "*"
                ]
              }
            },
            {
              "action": "create"
            },
            {
              "action": "delete"
            }
          ]
        },
        {
          "role": "policy_tester_05",
          "actions": [
            {
              "action": "read",
              "fields": {
                "exclude": [],
                "include": [
                  "*"
                ]
              },
              "policy": {
                "database": "@item.id ne 9"
              }
            },
            {
              "action": "update",
              "fields": {
                "exclude": [],
                "include": [
                  "*"
                ]
              }
            },
            {
              "action": "create"
            },
            {
              "action": "delete"
            }
          ]
        },
        {
          "role": "policy_tester_06",
          "actions": [
            {
              "action": "read",
              "fields": {
                "exclude": [],
                "include": [
                  "*"
                ]
              },
              "policy": {
                "database": "@item.id ne 10"
              }
            },
            {
              "action": "create"
            },
            {
              "action": "delete"
            },
            {
              "action": "update",
              "fields": {
                "exclude": [],
                "include": [
                  "*"
                ]
              }
            }
          ]
        },
        {
          "role": "policy_tester_07",
          "actions": [
            {
              "action": "delete",
              "fields": {
                "exclude": [],
                "include": [
                  "*"
                ]
              },
              "policy": {
                "database": "@item.id ne 9"
              }
            },
            {
              "action": "read",
              "fields": {
                "exclude": [],
                "include": [
                  "*"
                ]
              }
            },
            {
              "action": "update",
              "fields": {
                "exclude": [],
                "include": [
                  "*"
                ]
              },
              "policy": {
                "database": "@item.id ne 9"
              }
            },
            {
              "action": "create"
            }
          ]
        },
        {
          "role": "policy_tester_08",
          "actions": [
            {
              "action": "read",
              "fields": {
                "exclude": [],
                "include": [
                  "*"
                ]
              }
            },
            {
              "action": "delete",
              "fields": {
                "exclude": [],
                "include": [
                  "*"
                ]
              },
              "policy": {
                "database": "@item.id eq 9"
              }
            },
            {
              "action": "update",
              "fields": {
                "exclude": [],
                "include": [
                  "*"
                ]
              },
              "policy": {
                "database": "@item.id eq 9"
              }
            },
            {
              "action": "create"
            }
          ]
        },
        {
          "role": "test_role_with_noread",
          "actions": [
            {
              "action": "create"
            },
            {
              "action": "update"
            },
            {
              "action": "delete"
            }
          ]
        },
        {
          "role": "test_role_with_excluded_fields",
          "actions": [
            {
              "action": "read",
              "fields": {
                "exclude": [
                  "publisher_id"
                ]
              }
            },
            {
              "action": "create"
            },
            {
              "action": "update"
            },
            {
              "action": "delete"
            }
          ]
        },
        {
          "role": "test_role_with_policy_excluded_fields",
          "actions": [
            {
              "action": "read",
              "fields": {
                "exclude": [
                  "publisher_id"
                ]
              },
              "policy": {
                "database": "@item.title ne 'Test'"
              }
            },
            {
              "action": "create"
            },
            {
              "action": "update"
            },
            {
              "action": "delete"
            }
          ]
        }
      ],
      "mappings": {
        "id": "id",
        "title": "title"
      },
      "relationships": {
        "publishers": {
          "cardinality": "one",
          "target.entity": "Publisher",
          "source.fields": ["publisher_id"],
          "target.fields": ["id"],
          "linking.source.fields": [],
          "linking.target.fields": []
        }
      }
    }

  }
}

Version

0.12.0-rc

What database are you using?

Azure SQL

What hosting model are you using?

Local (including CLI)

Which API approach are you accessing DAB through?

No response

Relevant log output

Information: Validating entity relationships.
Unhandled exception. System.AggregateException: One or more errors occurred. (Value cannot be null. (Parameter 'key'))
 ---> System.ArgumentNullException: Value cannot be null. (Parameter 'key')
   at System.Collections.Generic.Dictionary`2.FindValue(TKey key)
   at System.Collections.Generic.Dictionary`2.TryGetValue(TKey key, TValue& value)
   at Azure.DataApiBuilder.Core.Services.SqlMetadataProvider`3.TryGetBackingColumn(String entityName, String field, String& name) in /_/src/Core/Services/MetadataProviders/SqlMetadataProvider.cs:line 220
   at Azure.DataApiBuilder.Core.Configurations.RuntimeConfigValidator.GetFieldsNotBackedByColumnsInDB(List`1 invalidColumns, String[] fields, String entityName, ISqlMetadataProvider sqlMetadataProvider) in /_/src/Core/Configurations/RuntimeConfigValidator.cs:line 1093
   at Azure.DataApiBuilder.Core.Configurations.RuntimeConfigValidator.ValidateRelationshipsInConfig(RuntimeConfig runtimeConfig, IMetadataProviderFactory sqlMetadataProviderFactory) in /_/src/Core/Configurations/RuntimeConfigValidator.cs:line 875
   at Azure.DataApiBuilder.Core.Configurations.RuntimeConfigValidator.ValidateEntitiesMetadata(RuntimeConfig runtimeConfig, ILoggerFactory loggerFactory) in /_/src/Core/Configurations/RuntimeConfigValidator.cs:line 232      
   at Azure.DataApiBuilder.Core.Configurations.RuntimeConfigValidator.TryValidateConfig(String configFilePath, ILoggerFactory loggerFactory) in /_/src/Core/Configurations/RuntimeConfigValidator.cs:line 169
   --- End of inner exception stack trace ---
   at System.Threading.Tasks.Task.ThrowIfExceptional(Boolean includeTaskCanceledExceptions)
   at System.Threading.Tasks.Task`1.GetResultCore(Boolean waitCompletionNotification)
   at System.Threading.Tasks.Task`1.get_Result()
   at Cli.ConfigGenerator.IsConfigValid(ValidateOptions options, FileSystemRuntimeConfigLoader loader, IFileSystem fileSystem) in /_/src/Cli/ConfigGenerator.cs:line 1132
   at Cli.Commands.ValidateOptions.Handler(ILogger logger, FileSystemRuntimeConfigLoader loader, IFileSystem fileSystem) in /_/src/Cli/Commands/ValidateOptions.cs:line 31
   at Cli.Program.<>c__DisplayClass2_0.<Execute>b__5(ValidateOptions options) in /_/src/Cli/Program.cs:line 67
   at CommandLine.ParserResultExtensions.MapResult[T1,T2,T3,T4,T5,T6,T7,TResult](ParserResult`1 result, Func`2 parsedFunc1, Func`2 parsedFunc2, Func`2 parsedFunc3, Func`2 parsedFunc4, Func`2 parsedFunc5, Func`2 parsedFunc6, Func`2 parsedFunc7, Func`2 notParsedFunc)
   at Cli.Program.Execute(String[] args, ILogger cliLogger, IFileSystem fileSystem, FileSystemRuntimeConfigLoader loader) in /_/src/Cli/Program.cs:line 61
   at Cli.Program.Main(String[] args) in /_/src/Cli/Program.cs:line 41

Code of Conduct

  • I agree to follow this project's Code of Conduct
@Aniruddh25 Aniruddh25 added bug Something isn't working triage issues to be triaged labels Apr 23, 2024
@Aniruddh25 Aniruddh25 added this to the 1.1rc milestone Apr 23, 2024
@seantleonard
Copy link
Contributor

Does "$schema": "https://dataapibuilder.azureedge.net/schemas/v0.5.34/dab.draft.schema.json", not cause issues with JSON validation given that the version used is 0.5.34 instead of "$schema": "https://github.com/Azure/data-api-builder/releases/download/v0.12.0-rc/dab.draft.schema.json",

@seantleonard
Copy link
Contributor

I get a different stack trace:

Information: Validating entity relationships.
Unhandled exception. System.AggregateException: One or more errors occurred. (The given key 'Publisher' was not present in the dictionary.)
---> System.Collections.Generic.KeyNotFoundException: The given key 'Publisher' was not present in the dictionary.
at System.Collections.Generic.Dictionary2.get_Item(TKey key) at Azure.DataApiBuilder.Core.Services.SqlMetadataProvider3.TryGetBackingColumn(String entityName, String field, String& name) in //src/Core/Services/MetadataProviders/SqlMetadataProvider.cs:line 220
at Azure.DataApiBuilder.Core.Configurations.RuntimeConfigValidator.GetFieldsNotBackedByColumnsInDB(List`1 invalidColumns, String[] fields, String entityName, ISqlMetadataProvider sqlMetadataProvider) in /
/src/Core/Configurations/RuntimeConfigValidator.cs:line 1093
at Azure.DataApiBuilder.Core.Configurations.RuntimeConfigValidator.ValidateRelationshipsInConfig(RuntimeConfig runtimeConfig, IMetadataProviderFactory sqlMetadataProviderFactory) in //src/Core/Configurations/RuntimeConfigValidator.cs:line 875
at Azure.DataApiBuilder.Core.Configurations.RuntimeConfigValidator.ValidateEntitiesMetadata(RuntimeConfig runtimeConfig, ILoggerFactory loggerFactory) in /
/src/Core/Configurations/RuntimeConfigValidator.cs:line 232
at Azure.DataApiBuilder.Core.Configurations.RuntimeConfigValidator.TryValidateConfig(String configFilePath, ILoggerFactory loggerFactory) in /_/src/Core/Configurations/RuntimeConfigValidator.cs:line 169

@seantleonard
Copy link
Contributor

This issue looks like it stems from collections in the metadataprovider not being fully populated at time of validation:
image

@seantleonard
Copy link
Contributor

this was not caught presumably because it doesn't occur on dab start and only appears during dab validate

@seantleonard seantleonard removed the triage issues to be triaged label Apr 30, 2024
@seantleonard
Copy link
Contributor

seantleonard commented May 2, 2024

This seems to be caused by SqlMetadataProvider::GenerateExposedToBackingColumnMapsForEntities() being outside the scope of _isValidateOnly code.

Image

@seantleonard
Copy link
Contributor

The above comment holds true when the dab-config.json you provide to dab validate --config "path" has an invalid connection string. Because dab validate accumulates all errors instead of halting at first error, it attempts to find out everything wrong with the config. That won't work when dab can't connect to the database and proceed to validate your config against db metadata.

@Aniruddh25 , can you confirm whether your config had a valid connection string or if you were using an environment variable placeholder for your connection string?

Agreed that the error message isn't helpful so one path forward here is:

  • halt validation when the connection string is invalid/can't connect to DB. When DAB can't connect to DB, DAB can't confirm whether config is correct. (@abhishekkumams , thoughts?

@aaronburtle
Copy link
Contributor

The above comment holds true when the dab-config.json you provide to dab validate --config "path" has an invalid connection string. Because dab validate accumulates all errors instead of halting at first error, it attempts to find out everything wrong with the config. That won't work when dab can't connect to the database and proceed to validate your config against db metadata.

@Aniruddh25 , can you confirm whether your config had a valid connection string or if you were using an environment variable placeholder for your connection string?

Agreed that the error message isn't helpful so one path forward here is:

  • halt validation when the connection string is invalid/can't connect to DB. When DAB can't connect to DB, DAB can't confirm whether config is correct. (@abhishekkumams , thoughts?

I found the same in my investiation. This only arises when the connection string is invalid. My thought was to add a try catch to the validation steps that rely on the connection string and then some messaging to help explain the problem.

seantleonard pushed a commit that referenced this issue May 9, 2024
… correctly initialized (#2202)

## Why make this change?
Closes #2184
Closes #2180


## What is this change?

When we don't properly initialize the `SqlMetadataProvider`, certain
functions will not behave as intended. This change adds some defensive
programming around this fact so that we have more readable exceptions in
these cases, as well as restructuring the validation to avoid the
potential of such a circumstance.

We refactor the `ValidateEntitiesMetadata` function into 2 functions,
one called `ValidateRelationshipConfigCorrectness` that validates the
relationships in the config without needing to cross reference database
metadata, and leaving the remaining validations that do require such
metadata within the original function. This validation is then handled
by a mix of the initialization of the metadata provider (this init
process does some validation), and the call to `ValidateRelationships`
which happens from within `ValidateEntitiesMetadata` only when the
initialization of the metadata provider resulted in no connection
errors.

Therefore, the normal code path when doing `dab validate` will be

1. Call `ValidateRelationshipConfigCorrectness`
2. Call `ValidateEntitiesMetadata`
    2a. If there were no connection errors Call `ValidateRelationships`



![image](https://github.com/Azure/data-api-builder/assets/93220300/1e2feec5-ac73-4e57-b3eb-01f032f362cf)


## How was this tested?

Manually, against our test suite, and we add a regression test that
looks for the correct error messaging when the config that caused the
error from the finding of the bug is used.

## Sample Request(s)

This can be reprod by using the command `dab validate -c <path to
config>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
3 participants