Models Read Data
Introduction
Models represent the entities — such as tables, views, or collections — in your data source, defining the structure and how the data can be accessed.
Lifecycle
If you haven't already, have a run through our Quickstart guide to set up your Hasura DDN instance. In the sections below, we'll guide you through how to interact with your metadata and shape your API to your liking!
Creating a model
To query data from your API, you'll first need to create a model that represents that data.
- PostgreSQL
- MongoDB
- ClickHouse
Via a new table or view
CREATE TABLE public.users (
id serial PRIMARY KEY,
name text NOT NULL
);
INSERT INTO public.users (name)
VALUES
('Alice Johnson'),
('Bob Smith'),
('Charlie Davis');
ddn connector introspect <connector_name>
ddn model add <connector_name> users
Via an existing table or view
If you already have a table present, you can add it by introspecting your data source and referencing it by name.
ddn connector introspect <connector_name>
ddn model add <connector_name> users
Via a new collection or view
db.createCollection("users");
db.users.insertMany([{ name: "Alice" }, { name: "Bob" }, { name: "Eve" }]);
ddn connector introspect <connector_name>
ddn model add <connector_name> users
Via an existing collection or view
If you already have a collection with data present, you can add it by introspecting your data source and referencing it by name.
ddn connector introspect <connector_name>
ddn model add <connector_name> users
Via a new table or view
CREATE TABLE users (
id UInt32,
name String
)
ENGINE = MergeTree()
ORDER BY id;
INSERT INTO users (id, name) VALUES
(1, 'Alice Johnson'),
(2, 'Bob Smith'),
(3, 'Charlie Davis');
ddn connector introspect <connector_name>
ddn model add <connector_name> users
Via an existing table or view
If you already have a table present, you can add it by introspecting your data source and referencing it by name.
ddn connector introspect <connector_name>
ddn model add <connector_name> users
ddn supergraph build local
ddn run docker-start
ddn console --local
query {
users {
id
name
}
}
{
"data": {
"users": [
{
"id": 1,
"screamedName": "Alice Johnson"
},
{
"id": 2,
"screamedName": "Bob Smith"
},
{
"id": 3,
"screamedName": "Charlie Davis"
}
]
}
}
Extending a model
Models can be extended, allowing for transformation and enrichment of the data.
Via a native query
- PostgreSQL
- MongoDB
- ClickHouse
Within your connector's directory, you can add a new file with a .sql
extension to define a native query.
mkdir -p <my_subgraph>/connector/<connector_name>/native_operations/queries/
-- native_operations/queries/user_by_name_between.sql
SELECT *
FROM users
WHERE name LIKE '%' || {{name}} || '%'
AND name > {{lower_bound}}
AND name < {{upper_bound}}
ddn connector plugin --connector <my_subgraph>/connector/<my_connector>/connector.yaml -- \
native-operation create --operation-path native_operations/queries/user_by_name_between.sql --kind query
ddn connector introspect <connector_name>
ddn model update <connector_name> "*"
Within your connector's directory, you can add a new JSON configuration file to define a native query.
mkdir -p <my_subgraph>/connector/<connector_name>/native_queries/
// native_queries/user_by_name_between.json
{
"name": "userByName",
"representation": "collection",
"description": "Look up a user by a regular expression match on name",
"inputCollection": "users",
"arguments": {
"name": { "type": { "scalar": "string" } },
"lower_bound": { "type": { "scalar": "string" } },
"upper_bound": { "type": { "scalar": "string" } }
},
"resultDocumentType": "UserByName",
"objectTypes": {
"UserByName": {
"fields": {
"_id": { "type": { "scalar": "objectId" } },
"name": { "type": { "scalar": "string" } }
}
}
},
"pipeline": [
{
"$match": {
"$and": [
{ "name": { "$regex": "{{ name }}" } },
{ "name": { "$gt": "{{ lower_bound }}" } },
{ "name": { "$lt": "{{ upper_bound }}" } }
]
}
}
]
}
ddn connector introspect <connector_name>
ddn model update <connector_name> "*"
Within your connector's directory, you can add a new SQL configuration file to define a native query.
mkdir -p <my_subgraph>/connector/<connector_name>/queries/
// queries/UsersByName.sql
SELECT *
FROM "default"."users"
WHERE "users"."name" = {name: String}
Note this uses the ClickHouse parameter syntax
// configuration.json
{
"tables": {},
"queries": {
"UserByName": {
"exposed_as": "collection",
"file": "queries/UserByName.sql",
"return_type": {
"kind": "definition",
"columns": {
"id": "Int32",
"name": "String"
}
}
}
}
}
ddn connector introspect <connector_name>
ddn model add <connector_name> UserByName
Via a lambda connector
Lambda connectors allow you to expose custom business logic via your API. You can also create relationships between an existing model and this business logic to expand the data a model can return.
We support lambda connectors for TypeScript, Python, and Go.
- TypeScript
- Python
- Go
ddn connector init my_ts -i
/**
* @readonly
*/
export function nameToUpperCase(name: string): string {
return name.toUpperCase();
}
ddn connector introspect my_ts
ddn command add my_ts nameToUpperCase
ddn connector init my_python -i
from hasura_ndc import start
from hasura_ndc.function_connector import FunctionConnector
connector = FunctionConnector()
@connector.register_query
def name_to_upper_case(name: str) -> str:
return name.upper()
if __name__ == "__main__":
start(connector)
ddn connector introspect my_python
ddn command add my_python name_to_upper_case
ddn connector init my_go -i
package functions
import (
"context"
"fmt"
"hasura-ndc.dev/ndc-go/types"
"strings"
)
// NameArguments defines the input arguments for the function
type NameArguments struct {
Name string `json:"name"` // required argument
}
// NameResult defines the output result for the function
type NameResult string
// FunctionNameToUpperCase converts a name string to uppercase
func FunctionNameToUpperCase(ctx context.Context, state *types.State, arguments *NameArguments) (*NameResult, error) {
if arguments.Name == "" {
return nil, fmt.Errorf("name cannot be empty")
}
upperCaseName := NameResult(strings.ToUpper(arguments.Name))
return &upperCaseName, nil
}
ddn connector introspect my_go
ddn command add my_go nameToUpperCase
---
# e.g., inside users.hml
kind: Relationship
version: v1
definition:
name: screamedName
sourceType: Users
target:
command:
name: NameToUpperCase
subgraph: app
mapping:
- source:
fieldPath:
- fieldName: name
target:
argument:
argumentName: name
description: The user's name returned in all caps.
ddn supergraph build local
ddn run docker-start
ddn console --local
query {
users {
id
screamedName
}
}
{
"data": {
"users": [
{
"id": 1,
"screamedName": "ALICE JOHNSON"
},
{
"id": 2,
"screamedName": "BOB SMITH"
},
{
"id": 3,
"screamedName": "CHARLIE DAVIS"
}
]
}
}
Updating a model
Your underlying data source may change over time. You can update your model to reflect these changes.
You'll need to update the mapping of your model to the data source by updating the DataConnectorLink object.
ddn connector-link update <connector_name> --subgraph <path_to_subgraph.yaml>
ddn model update <connector_name> users
Deleting a model
ddn model remove users
Reference
You can learn more about models in the metadata reference docs.