Hello. I have a question adding an entity to metad...
# advice-metadata-modeling
p
Hello. I have a question adding an entity to metadata-model. I’m using datahub v0.8.32 version. I added a new entity(DatabaseQuery) to the datahub. DatabaseQuery entity have ‘DatabaseQueryKey’ aspect and ‘DatabaseQueryProperties’ aspect. And DatabaseQueryKey composed of ‘databaseQueryId’. I edited and added the following files. • metadata-models/src/main/pegasus/com/linkedin/metadata/key/DatabaseQueryKey.pdl • metadata-models/src/main/pegasus/com/linkedin/databaseQuery/DatabaseQueryProperties.pdl • metadata-models/src/main/resources/entity-registry.yml • datahub-graphql-core/src/main/resources/entity.graphql • li-utils/src/main/javaPegasus/com/linkedin/common/urn/DatabaseQueryUrn.java • metadata-utils/src/main/java/com/linkedin/metadata/Constants.java • datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/types/databasequery/mappers/DatabaseQueryPropertiesMapper.java • datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/types/databasequery/mappers/DatabaseQueryMapper.java • datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/types/databasequery/DatabaseQueryType.java • datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/GmsGraphQLEngine.java I added the metadata related to the DatabaseQuery to datahub. And I ran it, and there were no errors. However, when I query GraphQL on page(localhost:9002/api/graphiql), an error occurs.
Copy code
------query 1
{
  databaseQuery(urn:"urn:li:databaseQuery:test1") {
    databaseQueryId
  }
}

------result 1
{
  "errors": [
    {
      "message": "The field at path '/databaseQuery/databaseQueryId' was declared as a non null type, but the code involved in retrieving data has wrongly returned a null value.  The graphql specification requires that the parent field be set to null, or if that is non nullable that it bubble up null to its parent and so on. The non-nullable type is 'String' within parent type 'DatabaseQuery'",
      "path": [
        "databaseQuery",
        "databaseQueryId"
      ],
      "extensions": {
        "classification": "NullValueInNonNullableField"
      }
    }
  ],
  "data": {
    "databaseQuery": null
  }
}

------query 2
query {
  search(input: {type: DATABASE_QUERY, query: "test1"}) {
    total
    searchResults {
      entity {
        urn
      }
    }
  }
}

------result 2
{
  "errors": [
    {
      "message": "The field at path '/search/searchResults[0]/entity' was declared as a non null type, but the code involved in retrieving data has wrongly returned a null value.  The graphql specification requires that the parent field be set to null, or if that is non nullable that it bubble up null to its parent and so on. The non-nullable type is 'Entity' within parent type 'SearchResult'",
      "path": [
        "search",
        "searchResults",
        0,
        "entity"
      ],
      "extensions": {
        "classification": "NullValueInNonNullableField"
      }
    }
  ],
  "data": {
    "search": null
  }
}
And when I query like this, it came out well.
Copy code
------query 3
{
  databaseQuery(urn:"urn:li:databaseQuery:test1") {
    databaseQueryProperties {
      name
    }
  }
}

------result 3
{
  "data": {
    "databaseQuery": {
      "databaseQueryProperties": {
        "name": "TEST1"
      }
    }
  }
}

------query 4
query {
  search(input: {type: DATABASE_QUERY, query: "test1"}) {
    total
    searchResults {
      matchedFields {
        name
        value
      }
    }
  }
}

------result 3
{
  "data": {
    "search": {
      "total": 1,
      "searchResults": [
        {
          "matchedFields": [
            {
              "name": "databaseQueryId",
              "value": "test1"
            }
          ]
        }
      ]
    }
  }
}
The code below is part of the code I modified. DatabaseQueryKey.pdl
Copy code
namespace com.linkedin.metadata.key

/**
 * Key for Database Query
 */
@Aspect = {
  "name": "databaseQueryKey"
}
record DatabaseQueryKey {
  /**
   * Database Query ID
   */
  @Searchable = {
    "boostScore": 10.0,
    "enableAutocomplete": true,
    "fieldName": "id",
    "fieldType": "TEXT_PARTIAL"
  }
  databaseQueryId: string
}
entity-registry.yml
Copy code
- name: databaseQuery
  doc: Database Query represents a query and related information for requesting data from a data source. ex) Sql, Sparql, Graphql etc.
  keyAspect: databaseQueryKey
  aspects:
    - databaseQueryProperties
entity.graphql
Copy code
type DatabaseQuery implements EntityWithRelationships & Entity {
    """
    A primary key of the Database Query
    """
    urn: String!

    """
    A standard Entity Type
    """
    type: EntityType!

    """
    Unique guid for Database Query
    """
    databaseQueryId: String!

    """
    An additional set of read only properties
    """
    databaseQueryProperties: DatabaseQueryProperties
}

enum EntityType {
	"""
	The Database Query Entity
	"""
	DATABASE_QUERY
	
	etc…
}
DatabaseQueryUrn.java
Copy code
package com.linkedin.common.urn;

import com.linkedin.data.template.Custom;
import com.linkedin.data.template.DirectCoercer;
import com.linkedin.data.template.TemplateOutputCastException;
import java.net.URISyntaxException;

public final class DatabaseQueryUrn extends Urn {
    public static final String ENTITY_TYPE = "databaseQuery";

    private final String _databaseQueryId;

    public DatabaseQueryUrn(String databaseQueryId) {
        super(ENTITY_TYPE, TupleKey.createWithOneKeyPart(databaseQueryId));
        this._databaseQueryId = databaseQueryId;
    }

    private DatabaseQueryUrn(TupleKey entityKey, String databaseQueryId) {
        super("li", "databaseQuery", entityKey);
        this._databaseQueryId = databaseQueryId;
    }

    public String getDatabaseQueryIdEntity() {
        return _databaseQueryId;
    }

    public static DatabaseQueryUrn createFromString(String rawUrn) throws URISyntaxException {
        return createFromUrn(Urn.createFromString(rawUrn));
    }

    private static DatabaseQueryUrn deodeUrn(String databaseQueryId) throws Exception {
        return new DatabaseQueryUrn(TupleKey.create(new Object[]{databaseQueryId}), databaseQueryId);
    }

    public static DatabaseQueryUrn createFromUrn(Urn urn) throws URISyntaxException {
        if (!"li".equals(urn.getNamespace())) {
            throw new URISyntaxException(urn.toString(), "Urn namespace type should be 'li'.");
        } else if (!ENTITY_TYPE.equals(urn.getEntityType())) {
            throw new URISyntaxException(urn.toString(), "Urn entity type should be 'test.");
        } else {
            TupleKey key = urn.getEntityKey();
            if (key.size() != 1) {
                throw new URISyntaxException(urn.toString(), "Invalid number of Keys.");
            } else {
                try {
                    return deodeUrn((String)key.getAs(0, String.class));
                    // return new DatabaseQueryUrn((String)key.getAs(0, String.class));
                } catch (Exception e) {
                    throw new URISyntaxException(urn.toString(), "Invalid URN Parameter: '"+e.getMessage());
                }
            }
        }
    }

    public static DatabaseQueryUrn deserialize(String rawUrn) throws URISyntaxException {
        return createFromString(rawUrn);
    }

    static {
        Custom.registerCoercer(new DirectCoercer<DatabaseQueryUrn>() {
            public Object coerceInput(DatabaseQueryUrn object) throws ClassCastException {
                return object.toString();
            }

            public DatabaseQueryUrn coerceOutput(Object object) throws TemplateOutputCastException {
                try {
                    return DatabaseQueryUrn.createFromString((String)object);
                } catch (URISyntaxException e) {
                    throw new TemplateOutputCastException("Invalid URN syntax: " + e.getMessage(), e);
                }
            }
        }, DatabaseQueryUrn.class);
    }
}
DatabaseQueryMapper.java
Copy code
public class DatabaseQueryMapper implements ModelMapper<EntityResponse, DatabaseQuery> {
    public static final DatabaseQueryMapper INSTANCE = new DatabaseQueryMapper();

    public static DatabaseQuery map(@Nonnull final EntityResponse entityResponse) {
        return INSTANCE.apply(entityResponse);
    }

    @Override
    public DatabaseQuery apply(EntityResponse entityResponse) {
        final DatabaseQuery result = new DatabaseQuery();
        result.setUrn(entityResponse.getUrn().toString());
        result.setType(EntityType.DATABASE_QUERY);

        EnvelopedAspectMap aspectMap = entityResponse.getAspects();
        MappingHelper<DatabaseQuery> mappingHelper = new MappingHelper<>(aspectMap, result);
        mappingHelper.mapToResult(DATABASE_QUERY_KEY_ASPECT_NAME, this::mapDatabaseQueryKey);
        mappingHelper.mapToResult(DATABASE_QUERY_PROPERTIES_ASPECT_NAME, (databaseQuery, dataMap) ->
                databaseQuery.setDatabaseQueryProperties(DatabaseQueryPropertiesMapper.map(new DatabaseQueryProperties(dataMap))));

        return mappingHelper.getResult();
    }

    private void mapDatabaseQueryKey(@Nonnull DatabaseQuery databaseQuery, @Nonnull DataMap dataMap) {
        final DatabaseQueryKey databaseQueryKey = new DatabaseQueryKey(dataMap);
        databaseQueryKey.setDatabaseQueryId(databaseQueryKey.getDatabaseQueryId());
    }
}
DatabaseQueryType.java
Copy code
public class DatabaseQueryType implements SearchableEntityType<DatabaseQuery>,
                                          BrowsableEntityType<DatabaseQuery> {
    static final Set<String> ASPECTS_TO_RESOLVE = ImmutableSet.of(
            DATABASE_QUERY_KEY_ASPECT_NAME,
            DATABASE_QUERY_PROPERTIES_ASPECT_NAME
    );
    private static final Set<String> FACET_FIELDS = ImmutableSet.of("access");
    private final EntityClient _entityClient;
    public DatabaseQueryType(final EntityClient entityClient) {
        _entityClient = entityClient;
    }

    @Override
    public EntityType type() {
        return EntityType.DATABASE_QUERY;
    }

    @Override
    public Class<DatabaseQuery> objectClass() {
        return DatabaseQuery.class;
    }

    @Override
    public List<DataFetcherResult<DatabaseQuery>> batchLoad(@Nonnull List<String> urnStrs, @Nonnull QueryContext context) throws Exception {
        final List<Urn> urns = urnStrs.stream().map(UrnUtils::getUrn).collect(Collectors.toList());
        try {
            final Map<Urn, EntityResponse> databaseQueryMap = _entityClient.batchGetV2(
                    Constants.DATABASE_QUERY_ENTITY_NAME,
                    new HashSet<>(urns),
                    ASPECTS_TO_RESOLVE,
                    context.getAuthentication()
            );
            final List<EntityResponse> results = new ArrayList<>();

            for (Urn urn : urns) {
                results.add(databaseQueryMap.getOrDefault(urn, null));
            }

            return results.stream()
                    .map(dbQuery -> dbQuery == null ? null : DataFetcherResult.<DatabaseQuery>newResult()
                    .data(DatabaseQueryMapper.map(dbQuery))
                    .build())
                    .collect(Collectors.toList());
        } catch (Exception e) {
            throw new RuntimeException("Failed to batch load DatabaseQuery", e);
        }
    }

    ...skip code

    @Override
    public List<BrowsePath> browsePaths(@Nonnull String urn, @Nonnull QueryContext context) throws Exception {
        final StringArray result = _entityClient.getBrowsePaths(getDatabaseQueryUrn(urn), context.getAuthentication());
        return BrowsePathsMapper.map(result);
    }

    private com.linkedin.common.urn.DatabaseQueryUrn getDatabaseQueryUrn(String urnStr) {
        try {
            return DatabaseQueryUrn.createFromString(urnStr);
        } catch (URISyntaxException e) {
            throw new RuntimeException(String.format("Failed to retrieve databaseQuery %s, invalid urn", urnStr));
        }
    }
}
What am I missing?? Please help me..