Azure/data-api-builder

[Bug]: Validating entity relationships throws unhandled exception

Aniruddh25 opened this issue · 7 comments

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

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",

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

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

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

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

Image

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?

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.