mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-09-11 10:46:25 +03:00
Initial docs for Snowflake native queries
PR-URL: https://github.com/hasura/graphql-engine-mono/pull/10225 Co-authored-by: Rob Dominguez <24390149+robertjdominguez@users.noreply.github.com> GitOrigin-RevId: e72daa4a5f3d53c76ca9ccb22db693120f36d743
This commit is contained in:
parent
114261533b
commit
0b05c6490d
569
docs/docs/schema/snowflake/logical-models.mdx
Normal file
569
docs/docs/schema/snowflake/logical-models.mdx
Normal file
@ -0,0 +1,569 @@
|
||||
---
|
||||
sidebar_label: Logical Models
|
||||
sidebar_position: 4
|
||||
description: Customize the Hasura GraphQL schema with Logical Models
|
||||
keywords:
|
||||
- hasura
|
||||
- docs
|
||||
- snowflake
|
||||
- schema
|
||||
- Logical Models
|
||||
- Native Queries
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
import Thumbnail from '@site/src/components/Thumbnail';
|
||||
|
||||
# Snowflake: Logical Models
|
||||
|
||||
## Introduction
|
||||
|
||||
:::tip Supported from
|
||||
|
||||
Logical Models are supported from `v2.33.0`.
|
||||
|
||||
:::
|
||||
|
||||
Logical Models are a GraphQL representation of database data. They provide an abstraction over the underlying database.
|
||||
|
||||
Logical Models are a new approach from Hasura and are currently used by the
|
||||
[Native Queries](/schema/snowflake/native-queries.mdx) and the
|
||||
GraphQL API for a native database query.
|
||||
|
||||
You can find examples of how to use Logical Models in the
|
||||
[Native Queries](/schema/snowflake/native-queries.mdx) documentation. For a more detailed explanation
|
||||
of Logical Models themselves, read on.
|
||||
|
||||
## Tracking a Logical Model
|
||||
|
||||
<Tabs groupId="user-preference" className="api-tabs">
|
||||
<TabItem value="console" label="Console">
|
||||
|
||||
|
||||
You can create a Logical Model from the "Data" tab of the Console, by clicking on `Native Queries` in the sidebar, then
|
||||
`Add Logical Model`:
|
||||
|
||||
<Thumbnail src="/img/native-queries/logical-model-add.png" alt="Create Logical Model" width="800px" />
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="cli" label="CLI">
|
||||
|
||||
You can create a logical model by adding it to the appropriate database definition in the `metadata > databases > databases.yaml`
|
||||
file:
|
||||
|
||||
```yaml
|
||||
logical_models:
|
||||
- name: "<name>"
|
||||
fields:
|
||||
"<field name>":
|
||||
type: "<Snowflake field type>"
|
||||
nullable: false | true
|
||||
description: "<optional field description>"
|
||||
...
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="api" label="API">
|
||||
|
||||
You create a logical model through the metadata API:
|
||||
|
||||
```http
|
||||
POST /v1/metadata HTTP/1.1
|
||||
Content-Type: application/json
|
||||
X-Hasura-Role: admin
|
||||
|
||||
{
|
||||
"type": "snowflake_track_logical_model",
|
||||
"args": {
|
||||
"source": "default",
|
||||
"name": "<name>",
|
||||
"fields": [
|
||||
{
|
||||
"name": "<field name>",
|
||||
"type": "<Snowflake field type>",
|
||||
"nullable": false | true,
|
||||
"description": "<optional field description>"
|
||||
},
|
||||
...
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
The type of each field can be either a [Snowflake data type](https://docs.snowflake.com/en/sql-reference/intro-summary-data-types)
|
||||
or references to other Logical Models, and each field can be marked as nullable or not,
|
||||
see [LogicalModelType](/api-reference/syntax-defs.mdx#logicalmodeltype).
|
||||
|
||||
For example, we could track a representation of an article as follows:
|
||||
|
||||
<Tabs groupId="user-preference" className="api-tabs">
|
||||
<TabItem value="console" label="Console">
|
||||
|
||||
<Thumbnail
|
||||
src="/img/native-queries/create-logical-model-excerpt-example.png"
|
||||
alt="Create Logical Model"
|
||||
width="800px"
|
||||
/>
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="cli" label="CLI">
|
||||
|
||||
Add the following to the `default` database definition in the `metadata > databases > databases.yaml` file:
|
||||
|
||||
```yaml
|
||||
logical_models:
|
||||
- name: article
|
||||
fields:
|
||||
id:
|
||||
type: integer
|
||||
nullable: false
|
||||
title:
|
||||
type: text
|
||||
nullable: false
|
||||
contents:
|
||||
type: text
|
||||
nullable: false
|
||||
published_date:
|
||||
type: date
|
||||
nullable: true
|
||||
is_published:
|
||||
type: bit
|
||||
nullable: false
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="api" label="API">
|
||||
|
||||
```http
|
||||
POST /v1/metadata HTTP/1.1
|
||||
Content-Type: application/json
|
||||
X-Hasura-Role: admin
|
||||
|
||||
{
|
||||
"type": "snowflake_track_logical_model",
|
||||
"args": {
|
||||
"source": "default",
|
||||
"name": "article",
|
||||
"fields": [
|
||||
{
|
||||
"name": "id",
|
||||
"type":
|
||||
{
|
||||
"scalar": "int"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "title",
|
||||
"type":
|
||||
{
|
||||
"scalar": "text"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "contents",
|
||||
"type":
|
||||
{
|
||||
"scalar": "text"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "published_date",
|
||||
"type":
|
||||
{
|
||||
"scalar": "date",
|
||||
"nullable": true
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "is_published",
|
||||
"type":
|
||||
{
|
||||
"scalar": "bit"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## Untracking a Logical Model
|
||||
|
||||
<Tabs groupId="user-preference" className="api-tabs">
|
||||
<TabItem value="console" label="Console">
|
||||
|
||||
To untrack a Logical Model, click `Remove` next to the Logical Model:
|
||||
|
||||
<Thumbnail src="/img/native-queries/delete-logical-model.png" alt="Delete Logical Model" width="800px" />
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="cli" label="CLI">
|
||||
|
||||
You can remove a Logical Model by removing it from the `metadata > databases > databases.yaml` file.
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="api" label="API">
|
||||
|
||||
You can remove a Logical Model the same way:
|
||||
|
||||
```http
|
||||
POST /v1/metadata HTTP/1.1
|
||||
Content-Type: application/json
|
||||
X-Hasura-Role: admin
|
||||
|
||||
{
|
||||
"type": "snowflake_untrack_logical_model",
|
||||
"args": {
|
||||
"source": "default",
|
||||
"name": "<name>"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
:::info Removing a Logical Model
|
||||
|
||||
Note that you can only remove an **unused** Logical Model; if the model is attached to a Native Query, this operation
|
||||
will fail. You must remove the Native Query first.
|
||||
|
||||
:::
|
||||
|
||||
To untrack the above `article` Logical Model, we would run the following:
|
||||
|
||||
<Tabs groupId="user-preference" className="api-tabs">
|
||||
<TabItem value="console" label="Console">
|
||||
|
||||
<Thumbnail src="/img/native-queries/delete-logical-model.png" alt="Delete Logical Model" width="800px" />
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="cli" label="CLI">
|
||||
|
||||
Remove the above metadata from the `metadata > databases > databases.yaml` file.
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="api" label="API">
|
||||
|
||||
```http
|
||||
POST /v1/metadata HTTP/1.1
|
||||
Content-Type: application/json
|
||||
X-Hasura-Role: admin
|
||||
|
||||
{
|
||||
"type": "snowflake_untrack_logical_model",
|
||||
"args": {
|
||||
"source": "default",
|
||||
"name": "article"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## Referencing other Logical Models {#referencing-other-logical-models}
|
||||
|
||||
Logical Model fields are allowed to refer to other Logical Models, even recursively, allowing nested data types.
|
||||
|
||||
Object or array relationships between Native Queries are an example use of this.
|
||||
|
||||
To elaborate on the `article` example above, we can include authors in the data model:
|
||||
|
||||
<Tabs groupId="user-preference" className="api-tabs">
|
||||
<TabItem value="api" label="API">
|
||||
|
||||
```http
|
||||
POST /v1/metadata HTTP/1.1
|
||||
Content-Type: application/json
|
||||
X-Hasura-Role: admin
|
||||
|
||||
{
|
||||
"type": "bulk_atomic",
|
||||
"args":
|
||||
[
|
||||
{
|
||||
"type": "snowflake_track_logical_model",
|
||||
"args": {
|
||||
"source": "default",
|
||||
"name": "article",
|
||||
"fields": [
|
||||
{
|
||||
"name": "id",
|
||||
"type":
|
||||
{
|
||||
"scalar": "integer"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "title",
|
||||
"type":
|
||||
{
|
||||
"scalar": "text"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "contents",
|
||||
"type":
|
||||
{
|
||||
"scalar": "text"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "author_id",
|
||||
"type":
|
||||
{
|
||||
"scalar": "integer"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "author",
|
||||
"type":
|
||||
{
|
||||
"logical_model": "author",
|
||||
},
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "snowflake_track_logical_model",
|
||||
"args": {
|
||||
"source": "default",
|
||||
"name": "author",
|
||||
"fields": [
|
||||
{
|
||||
"name": "id",
|
||||
"type":
|
||||
{
|
||||
"scalar": "integer"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "name",
|
||||
"type":
|
||||
{
|
||||
"scalar": "text"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "articles",
|
||||
"type":
|
||||
{
|
||||
"array":
|
||||
{
|
||||
"logical_model": "article"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
:::info Wrap calls in `bulk_atomic`
|
||||
|
||||
Tracking the Logical Models one-by-one would fail, since `article` refers to `author`, which is not yet defined.
|
||||
|
||||
Tracking the Logical Models in one atomic operation postpones coherency checks until all models are tracked, which
|
||||
allows for mutual references.
|
||||
|
||||
:::
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## Permissions
|
||||
|
||||
By default, a model has no permissions, and only the admin account will be able to use it. You can enable the model by
|
||||
adding permissions for a given role.
|
||||
|
||||
As Logical Models are currently only used for read-only purposes, you can only add select permissions.
|
||||
|
||||
The permissions consist of a list of columns that the role can access, and a filter that specifies which rows the role
|
||||
can receive.
|
||||
|
||||
<Tabs groupId="user-preference" className="api-tabs">
|
||||
<TabItem value="console" label="Console">
|
||||
|
||||
<Thumbnail src="/img/native-queries/create-permissions.png" alt="Create Logical Model Permissions" width="800px" />
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="cli" label="CLI">
|
||||
|
||||
Columns must be specified, though you can use `"*"` to specify that you want to allow all columns.
|
||||
|
||||
The filter is the same boolean expression syntax as [query filters](/queries/ms-sql-server/query-filters.mdx). To allow
|
||||
all rows to be passed through to the response, you can specify an empty filter, `{}`.
|
||||
|
||||
Add the required permissions to the relevant logical model in `metadata > databases > databases.yaml`:
|
||||
|
||||
```yaml
|
||||
logical_models:
|
||||
- name: "<name>"
|
||||
fields:
|
||||
...
|
||||
select_permissions:
|
||||
- role: "<role name>"
|
||||
permission:
|
||||
columns: "*" | [
|
||||
"column 1",
|
||||
"column 2",
|
||||
...
|
||||
]
|
||||
filter: "<boolean expression>"
|
||||
- ...
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="api" label="API">
|
||||
|
||||
Columns must be specified, though you can use `"*"` to specify that you want to allow all columns.
|
||||
|
||||
The filter is the same boolean expression syntax as [query filters](/queries/ms-sql-server/query-filters.mdx). To allow
|
||||
all rows to be passed through to the response, you can specify an empty filter, `{}`.
|
||||
|
||||
```http
|
||||
POST /v1/metadata HTTP/1.1
|
||||
Content-Type: application/json
|
||||
X-Hasura-Role: admin
|
||||
|
||||
{
|
||||
"type": "snowflake_create_logical_model_select_permission",
|
||||
"args": {
|
||||
"source": "default",
|
||||
"name": "<logical model name>",
|
||||
"role": "<role name>",
|
||||
"permission": {
|
||||
"columns": "*" | [
|
||||
"column 1",
|
||||
"column 2",
|
||||
...
|
||||
],
|
||||
"filter": <boolean expression>
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
For example, to allow access to the `article` Logical Model for the `reader` role, but only for published articles, we
|
||||
could use the following permission to limit the accessible rows to those where `is_published` is `true`, and then hide
|
||||
that column from the list (by specifying all other columns).
|
||||
|
||||
<Tabs groupId="user-preference" className="api-tabs">
|
||||
<TabItem value="console" label="Console">
|
||||
|
||||
<Thumbnail src="/img/native-queries/permissions-with-check.png" alt="Permissions with check" width="800px" />
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="cli" label="CLI">
|
||||
|
||||
Add the required permissions to the relevant logical model in `metadata > databases > databases.yaml`:
|
||||
|
||||
```yaml
|
||||
logical_models:
|
||||
- name: '<name>'
|
||||
fields: ...
|
||||
select_permissions:
|
||||
- role: reader
|
||||
permission:
|
||||
columns:
|
||||
- id
|
||||
- title
|
||||
- contents
|
||||
- date
|
||||
filter:
|
||||
is_published:
|
||||
_eq: true
|
||||
- ...
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="api" label="API">
|
||||
|
||||
```http
|
||||
POST /v1/metadata HTTP/1.1
|
||||
Content-Type: application/json
|
||||
X-Hasura-Role: admin
|
||||
|
||||
{
|
||||
"type": "snowflake_create_logical_model_select_permission",
|
||||
"args": {
|
||||
"source": "default",
|
||||
"name": "article",
|
||||
"role": "reader",
|
||||
"permission": {
|
||||
"columns": [
|
||||
"id",
|
||||
"title",
|
||||
"contents",
|
||||
"date"
|
||||
],
|
||||
"filter": {
|
||||
"is_published": {"_eq": true}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
You can also drop permissions:
|
||||
|
||||
<Tabs groupId="user-preference" className="api-tabs">
|
||||
<TabItem value="console" label="Console">
|
||||
|
||||
<Thumbnail src="/img/native-queries/delete-permissions.png" alt="Delete Permissions" width="800px" />
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="cli" label="CLI">
|
||||
|
||||
Remove the relevant permission from `metadata > databases > databases.yaml`.
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="api" label="API">
|
||||
|
||||
```http
|
||||
POST /v1/metadata HTTP/1.1
|
||||
Content-Type: application/json
|
||||
X-Hasura-Role: admin
|
||||
|
||||
{
|
||||
"type": "snowflake_drop_logical_model_select_permission",
|
||||
"args": {
|
||||
"source": "default",
|
||||
"name": "<logical model name>",
|
||||
"role": "<role name>"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
717
docs/docs/schema/snowflake/native-queries.mdx
Normal file
717
docs/docs/schema/snowflake/native-queries.mdx
Normal file
@ -0,0 +1,717 @@
|
||||
---
|
||||
sidebar_label: Native Queries
|
||||
sidebar_position: 4.1
|
||||
description: Customize the Hasura GraphQL schema with Native Queries
|
||||
keywords:
|
||||
- hasura
|
||||
- docs
|
||||
- snowflake
|
||||
- schema
|
||||
- Logical Models
|
||||
- Native Queries
|
||||
---
|
||||
|
||||
import ProductBadge from '@site/src/components/ProductBadge';
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
import Thumbnail from '@site/src/components/Thumbnail';
|
||||
|
||||
# Snowflake: Native Queries
|
||||
|
||||
<div className="badge-container">
|
||||
<ProductBadge standard pro ee self />
|
||||
</div>
|
||||
|
||||
## What are Native Queries?
|
||||
|
||||
:::tip Supported from
|
||||
|
||||
Native queries are supported from `v2.33.0`.
|
||||
|
||||
:::
|
||||
|
||||
The Hasura GraphQL Engine automatically generates a GraphQL API around database objects. That includes querying,
|
||||
mutating, and subscribing to data changes and events. However, sometimes we need more custom or advanced behavior that
|
||||
doesn’t come out of the box.
|
||||
|
||||
Native Queries allows you to automatically generate a GraphQL API around raw SQL queries, giving you more flexibility
|
||||
and control over your Hasura-generated GraphQL schema. They allow you to harness the full power of SQL within Hasura
|
||||
without the need to create database objects that require DDL privileges.
|
||||
|
||||
You might find Native Queries useful for many reasons:
|
||||
|
||||
- Use the full power of SQL that Hasura might not provide access to through the typical table API, such as `GROUP BY`,
|
||||
window functions, or scalar functions.
|
||||
- Provide custom arguments to the users of your API to greatly expand its flexibility.
|
||||
- Encapsulate sophisticated filtering with a query, allowing your users to provide a single argument rather than having
|
||||
to understand how to manipulate the data.
|
||||
- Work with the advanced features of your database to improve performance.
|
||||
- Write a compatibility layer around tables, making it easier to change your API without breaking existing clients.
|
||||
- Reduce duplication by moving common data manipulation into one place.
|
||||
|
||||
Native Queries are a Cloud and Enterprise feature of Hasura.
|
||||
|
||||
:::info Supported features
|
||||
|
||||
Currently, Hasura aggregations are not supported, but you can write the aggregation yourself as part of the query.
|
||||
|
||||
Relationships will be supported in a future release.
|
||||
|
||||
:::
|
||||
|
||||
## Example: excerpts of articles
|
||||
|
||||
We’ll start with an example. Let’s use this new feature to add some custom functionality to our Hasura API, without
|
||||
needing to define a custom SQL function on the database. If you’d like some reference documentation, scroll down, and
|
||||
also take a look at the [Logical Models documentation](/schema/snowflake/logical-models.mdx).
|
||||
|
||||
Imagine we have some articles in a table, with content kindly donated by [Loripsum.net](https://loripsum.net/):
|
||||
|
||||
```sql
|
||||
CREATE TABLE article(
|
||||
id INTEGER NOT NULL AUTOINCREMENT PRIMARY KEY,
|
||||
title TEXT NOT NULL,
|
||||
date DATE NOT NULL,
|
||||
content TEXT NOT NULL
|
||||
);
|
||||
|
||||
INSERT INTO article(title, date, content) VALUES
|
||||
('You will not believe', '2023-01-01', 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Facillimum id quidem est, inquam. Esse enim, nisi eris, non potes. Oratio me istius philosophi non offendit; Idemne, quod iucunde? Quid est enim aliud esse versutum? Non autem hoc: igitur ne illud quidem.'),
|
||||
('Ten things that', '2023-02-02', 'Illi enim inter se dissentiunt. Sedulo, inquam, faciam. Simus igitur contenti his.'),
|
||||
('Did you know', '2023-03-03', 'Ratio quidem vestra sic cogit. Duo Reges: constructio interrete. An nisi populari fama? Erat enim res aperta. Apparet statim, quae sint officia, quae actiones. Tum mihi Piso: Quid ergo?'),
|
||||
('They just cannot', '2023-04-04', 'Itaque hic ipse iam pridem est reiectus; Quod quidem iam fit etiam in Academia. Negare non possum. Quis non odit sordidos, vanos, leves, futtiles?'),
|
||||
('What on earth', '2023-05-05', 'Venit ad extremum; At certe gravius. Efficiens dici potest. Rhetorice igitur, inquam, nos mavis quam dialectice disputare? Nunc de hominis summo bono quaeritur; Rationis enim perfectio est virtus;');
|
||||
```
|
||||
|
||||
When listing these articles in an index, we probably want to truncate the text to, let’s say, 20 characters. So let’s
|
||||
create a Logical Model representing the excerpted article:
|
||||
|
||||
<Tabs groupId="user-preference" className="api-tabs">
|
||||
<TabItem value="console" label="Console">
|
||||
|
||||
Click on the `Logical Models` tab, and on the `Add Logical Model` button.
|
||||
|
||||
<Thumbnail src="/img/native-queries/logical-model-add.png" alt="Create Logical Model" width="800px" />
|
||||
|
||||
Once the modal is open, fill in the form. Each added field will be returned as a field in the GraphQL schema.
|
||||
|
||||
<Thumbnail
|
||||
src="/img/native-queries/create-logical-model-excerpt-example.png"
|
||||
alt="Create Logical Model"
|
||||
width="800px"
|
||||
/>
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="cli" label="CLI">
|
||||
|
||||
Add the following to the `default` database definition in the `metadata > databases > databases.yaml` file:
|
||||
|
||||
```yaml
|
||||
logical_models:
|
||||
- name: article_excerpt
|
||||
fields:
|
||||
id:
|
||||
type: integer
|
||||
title:
|
||||
type: text
|
||||
date:
|
||||
type: date
|
||||
excerpt:
|
||||
type: text
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="api" label="API">
|
||||
|
||||
```http
|
||||
POST /v1/metadata HTTP/1.1
|
||||
Content-Type: application/json
|
||||
X-Hasura-Role: admin
|
||||
|
||||
{
|
||||
"type": "snowflake_track_logical_model",
|
||||
"args": {
|
||||
"source": "default",
|
||||
"name": "article_excerpt",
|
||||
"fields": [
|
||||
{
|
||||
"name": "id",
|
||||
"type": "integer"
|
||||
},
|
||||
{
|
||||
"name": "title",
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"name": "date",
|
||||
"type": "date"
|
||||
},
|
||||
{
|
||||
"name": "excerpt",
|
||||
"type": "text"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
We can then track a Native Query that takes a single argument, `max_length`, and uses it to truncate the article content
|
||||
(more on arguments below). We use the SQL `substring` function to truncate the function, and the `length` function to
|
||||
decide whether we should use an ellipsis.
|
||||
|
||||
We use `{{max_length}}` to refer to the argument. We need it twice, so we simply use the argument twice.
|
||||
|
||||
<Tabs groupId="user-preference" className="api-tabs">
|
||||
<TabItem value="console" label="Console">
|
||||
|
||||
Click on the `Native Queries` tab and `Create Native Query` button.
|
||||
|
||||
<Thumbnail src="/img/native-queries/native-query-create.png" alt="Create Native Query" width="1146px" />
|
||||
|
||||
Then, fill in the form with the data required:
|
||||
|
||||
| Field | Value |
|
||||
| -------------------------- | ------------------------------- |
|
||||
| Native Query name | `article_with_excerpt` |
|
||||
| Database | The database to be used |
|
||||
| Input parameter | `max_length`, of type `integer` |
|
||||
| The Native Query statement | Value below 👇 |
|
||||
|
||||
```sql
|
||||
SELECT id, title, (substring(content, 1, {{max_length}}) || (CASE WHEN length(content) < {{max_length}} THEN '' else '...' END)) AS excerpt, date
|
||||
FROM article
|
||||
```
|
||||
|
||||
Finally, at the end, add the Query Return Type as the `article_excerpt` Logical Model we created and click `Save`:
|
||||
|
||||
<Thumbnail
|
||||
src="/img/native-queries/create-native-query-excerpt-example.png"
|
||||
alt="Create Native Query Form"
|
||||
width="1146px"
|
||||
/>
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="cli" label="CLI">
|
||||
|
||||
Add the following to the `default` database definition in the `metadata > databases > databases.yaml` file:
|
||||
|
||||
```yaml
|
||||
native_queries:
|
||||
- root_field_name: article_with_excerpt
|
||||
arguments:
|
||||
max_length:
|
||||
type: integer
|
||||
code:
|
||||
SELECT id as "id", title as "title", (substring(content, 1, {{max_length}}) || (CASE WHEN length(content) < {{max_length}} THEN ''
|
||||
else '...' END)) AS "excerpt", date as "date" FROM article
|
||||
returns: article_excerpt
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="api" label="API">
|
||||
|
||||
```http
|
||||
POST /v1/metadata HTTP/1.1
|
||||
Content-Type: application/json
|
||||
X-Hasura-Role: admin
|
||||
|
||||
{
|
||||
"type": "snowflake_track_native_query",
|
||||
"args": {
|
||||
"type": "query",
|
||||
"source": "default",
|
||||
"root_field_name": "article_with_excerpt",
|
||||
"arguments": {
|
||||
"max_length": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"returns": "article_excerpt",
|
||||
"code": "SELECT id, title, (substring(content, 1, {{max_length}}) || (CASE WHEN length(content) < {{max_length}} THEN '' else '...' END)) AS excerpt, date FROM article"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
All that’s left is for us to make a GraphQL query to select some articles with excerpts using our new root field
|
||||
generated in the previous API call. All we need to provide is the date range we’re interested in:
|
||||
|
||||
```graphql
|
||||
query {
|
||||
article_with_excerpt(args: { max_length: 20 }, where: { date: { _gte: "2023-03-01" } }) {
|
||||
id
|
||||
title
|
||||
date
|
||||
excerpt
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
When we run this GraphQL query, we get the following results:
|
||||
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"article_with_excerpt": [
|
||||
{
|
||||
"id": 3,
|
||||
"title": "Did you know",
|
||||
"date": "2023-03-03",
|
||||
"excerpt": "Ratio quidem vestra ..."
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"title": "They just cannot",
|
||||
"date": "2023-04-04",
|
||||
"excerpt": "Itaque hic ipse iam ..."
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"title": "What on earth",
|
||||
"date": "2023-05-05",
|
||||
"excerpt": "Venit ad extremum; A..."
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Creating a Native Query
|
||||
|
||||
:::info All fields in Snowflake are required to use `as "field"` syntax in your native query SQL
|
||||
|
||||
When writing your queries for Snowflake you must use `as "field"` syntax for all returning fields. From the `article` example above:
|
||||
```sql
|
||||
SELECT id as "id", title as "title", (substring(content, 1, {{max_length}}) || (CASE WHEN length(content) < {{max_length}} THEN ''
|
||||
else '...' END)) AS "excerpt", date as "date" FROM article
|
||||
```
|
||||
Notice that each field that we are returning we provide the `as "field"`.
|
||||
|
||||
:::
|
||||
|
||||
### 1. Create a Logical Model
|
||||
|
||||
In order to represent the structure of the data returned by the query, we first create a Logical Model.
|
||||
|
||||
:::info Permissions and Logical Models
|
||||
|
||||
Note that this Logical Model has no attached permissions and therefore will only be available to the admin role. See the
|
||||
[Logical Model documentation](/schema/ms-sql-server/logical-models.mdx) for information on attaching permissions.
|
||||
|
||||
:::
|
||||
|
||||
<Tabs groupId="user-preference" className="api-tabs">
|
||||
<TabItem value="console" label="Console">
|
||||
|
||||
To get started choose `Native Queries` from the sidebar in the `Data` tab:
|
||||
|
||||
<Thumbnail src="/img/native-queries/native-queries-menu.png" alt="Create Logical Model" width="750px" />
|
||||
|
||||
Click on the `Logical Models` tab, and on the `Add Logical Model` button.
|
||||
|
||||
<Thumbnail src="/img/native-queries/logical-model-add.png" alt="Create Logical Model" width="800px" />
|
||||
|
||||
Once the modal is open, fill in the forms with:
|
||||
|
||||
| Field | Value |
|
||||
| ------------------ | ----------------------------- |
|
||||
| Source | The database to be used |
|
||||
| Logical Model Name | The name of the Logical Model |
|
||||
|
||||
Finally, add any optional fields that you would like to be included in the Logical Model and click
|
||||
`Create Logical Model`.
|
||||
|
||||
<Thumbnail src="/img/native-queries/logical-model-modal.png" alt="Create Logical Model" width="800px" />
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="cli" label="CLI">
|
||||
|
||||
You can create a logical model by adding it to the appropriate database definition in the `metadata > databases > databases.yaml`
|
||||
file:
|
||||
|
||||
```yaml
|
||||
logical_models:
|
||||
- name: "<name>"
|
||||
fields:
|
||||
"<field name>":
|
||||
type: "<Snowflake field type>"
|
||||
nullable: false | true
|
||||
description: "<optional field description>"
|
||||
...
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="api" label="API">
|
||||
|
||||
```http
|
||||
POST /v1/metadata HTTP/1.1
|
||||
Content-Type: application/json
|
||||
X-Hasura-Role: admin
|
||||
|
||||
{
|
||||
"type": "snowflake_track_logical_model",
|
||||
"args": {
|
||||
"source": "default",
|
||||
"name": "<name>",
|
||||
"fields": [
|
||||
{
|
||||
"name": "<field name>",
|
||||
"type": "<Snowflake field type>",
|
||||
"nullable": false | true,
|
||||
"description": "<optional field description>"
|
||||
},
|
||||
...
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
### 2. Create a Native Query
|
||||
|
||||
Once the Logical Model is defined, we can use it to define the query:
|
||||
|
||||
<Tabs groupId="user-preference" className="api-tabs">
|
||||
<TabItem value="console" label="Console">
|
||||
|
||||
Click on the `Native Queries` tab and `Create Native Query` button.
|
||||
|
||||
<Thumbnail src="/img/native-queries/native-query-create.png" alt="Create Native Query" width="1146px" />
|
||||
|
||||
Then, fill in the form with the data required:
|
||||
|
||||
| Field | Value |
|
||||
| -------------------------- | ------------------------------- |
|
||||
| Native Query name | `article_with_excerpt` |
|
||||
| Database | The database to be used |
|
||||
| Input parameter | `max_length`, of type `integer` |
|
||||
| The Native Query statement | Your SQL statement. |
|
||||
| Query Return Type | The Logical Model created above |
|
||||
|
||||
Finally, click `Save`.
|
||||
|
||||
<Thumbnail src="/img/native-queries/create-native-query.png" alt="Create Native Query Form" width="1146px" />
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="cli" label="CLI">
|
||||
|
||||
Add the following to the relevant database definition in the `metadata > databases > databases.yaml` file:
|
||||
|
||||
```yaml
|
||||
native_queries:
|
||||
- root_field_name: "<root field name>"
|
||||
arguments:
|
||||
"<argument name>":
|
||||
type: "<PostgreSQL field type>"
|
||||
nullable: false | true
|
||||
description: "<optional field description>"
|
||||
array_relationships:
|
||||
- name: "<relationship name>"
|
||||
using:
|
||||
column_mapping:
|
||||
"<local column>": "<remote column>"
|
||||
remote_native_query: <remote native query name>"
|
||||
object_relationships: <same as array_relationships>
|
||||
description: "<text>"
|
||||
code: "<SQL query>"
|
||||
returns: "<logical model name>"
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="api" label="API">
|
||||
|
||||
```http
|
||||
POST /v1/metadata HTTP/1.1
|
||||
Content-Type: application/json
|
||||
X-Hasura-Role: admin
|
||||
|
||||
{
|
||||
"type": "snowflake_track_native_query",
|
||||
"args": {
|
||||
"source": "default",
|
||||
"root_field_name": "<name>",
|
||||
"type": "query",
|
||||
"arguments": {
|
||||
"<name>": {
|
||||
"type": "<Snowflake field type>",
|
||||
"nullable": false | true,
|
||||
"description": "<optional field description>"
|
||||
}
|
||||
},
|
||||
"array_relationships": [
|
||||
{
|
||||
"name": "<relationship name>",
|
||||
"using": {
|
||||
"column_mapping": {
|
||||
"<local column>": "<remote column>"
|
||||
},
|
||||
"remote_native_query: "<remote native query name>"
|
||||
}
|
||||
}
|
||||
],
|
||||
"object_relationships": <same as array_relationships>,
|
||||
"description": "<text>",
|
||||
"code": "<SQL query>",
|
||||
"returns": "<logical model name>"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
You can use any SQL that you could potentially use in a parameterized query, with the following caveats:
|
||||
|
||||
- The query must currently be a single read-only SQL query.
|
||||
- The query must be a valid standalone query, and not a partial query.
|
||||
- The return type of the query must match with the Logical Model.
|
||||
- The SQL cannot invoke a stored procedure.
|
||||
|
||||
#### Arguments
|
||||
|
||||
The query can take arguments, which are specified in the metadata. These arguments can be used in the SQL using the
|
||||
syntax `{{argument_name}}`. This syntax resembles popular string templating languages such as Mustache, but does not use
|
||||
string interpolation. Instead, it works in exactly the same way as parameterized queries in the database, and so
|
||||
arguments do not need escaping or quoting in the SQL itself. They will be treated as variables of the correct type.
|
||||
|
||||
This does mean that arguments cannot be used for elements of the SQL that deal with structure. For example, you cannot
|
||||
use an argument to specify the name of the table in a `FROM` clause.
|
||||
|
||||
When making a query, the arguments are specified using the `args` parameter of the query root field.
|
||||
|
||||
##### Example: `LIKE` operator
|
||||
|
||||
A commonly used operator is the `LIKE`. When used in a `WHERE` condition, it's usually written with this syntax
|
||||
`WHERE Title LIKE '%word%'`.
|
||||
|
||||
In order to use it with Native Query arguments, you need to use this syntax `LIKE ('%' || {{searchTitle}} || '%')`,
|
||||
where `searchTitle` is the Native Query parameter.
|
||||
|
||||
## Using the Native Query
|
||||
|
||||
You can make a GraphQL request using the specified root field name just as you would any other GraphQL query. When
|
||||
making a query, the arguments are specified using the `args` parameter of the query root field.
|
||||
|
||||
```graphql
|
||||
query {
|
||||
<root field name>(
|
||||
[args: {"<argument name>": <argument value>, ...},]
|
||||
[where: ...,]
|
||||
[order_by: ..., distinct_on: ...,]
|
||||
[limit: ..., offset: ...]
|
||||
) {
|
||||
<field 1>
|
||||
<field 2>
|
||||
...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Query functionality
|
||||
|
||||
Just like tables, Native Queries generate GraphQL types with the ability to further break down the data. You can find
|
||||
more information in the relevant documentation for [filtering](/queries/ms-sql-server/query-filters.mdx),
|
||||
[sorting](/queries/ms-sql-server/sorting.mdx), and [pagination](/queries/ms-sql-server/pagination.mdx).
|
||||
|
||||
Subscriptions (live queries) are also supported as usual.
|
||||
|
||||
## Mutations
|
||||
|
||||
Currently, only read-only queries are supported. All queries are run in a read-only transaction where supported to
|
||||
enforce this constraint.
|
||||
|
||||
A future release will allow mutations to be specified using Native Queries.
|
||||
|
||||
## Permissions
|
||||
|
||||
Native queries will inherit the permissions of the Logical Model that they return. See the
|
||||
[documentation on Logical Models](/schema/ms-sql-server/logical-models.mdx) for an explanation of how to add
|
||||
permissions.
|
||||
|
||||
## Relationships
|
||||
|
||||
Relationships are supported between Native Queries. This is how Native Queries may implement object and array fields of
|
||||
their referenced Logical Model.
|
||||
|
||||
Unlike tables, relationships for a Native Query have to be given as part of tracking the Native Query: The schema of a
|
||||
Native Query is defined by its Logical Model, and the Native Query needs to implement all the fields of the Logical
|
||||
Model in order to be tracked successfully.
|
||||
|
||||
Currently relationships are only supported between Native Queries residing in the same source.
|
||||
|
||||
As an example, consider the following Native Queries which implement the data model of articles and authors given in the
|
||||
section on [Logical Model references](/schema/ms-sql-server/logical-models.mdx#referencing-other-logical-models):
|
||||
|
||||
<Tabs groupId="user-preference" className="api-tabs">
|
||||
<TabItem value="api" label="API">
|
||||
|
||||
```http
|
||||
POST /v1/metadata HTTP/1.1
|
||||
Content-Type: application/json
|
||||
X-Hasura-Role: admin
|
||||
|
||||
{
|
||||
"type": "bulk_atomic",
|
||||
"args": [
|
||||
{
|
||||
"type": "snowflake_track_logical_model",
|
||||
"args": {
|
||||
"description": "",
|
||||
"fields": [
|
||||
{
|
||||
"name": "id",
|
||||
"nullable": false,
|
||||
"type": "integer"
|
||||
},
|
||||
{
|
||||
"name": "title",
|
||||
"nullable": false,
|
||||
"type": "varchar"
|
||||
},
|
||||
{
|
||||
"name": "author_id",
|
||||
"nullable": false,
|
||||
"type": "integer"
|
||||
},
|
||||
{
|
||||
"name": "author",
|
||||
"type": {
|
||||
"logical_model": "author",
|
||||
"nullable": false
|
||||
}
|
||||
}
|
||||
],
|
||||
"name": "article",
|
||||
"source": "snowflake"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "snowflake_track_logical_model",
|
||||
"args": {
|
||||
"description": "",
|
||||
"fields": [
|
||||
{
|
||||
"name": "id",
|
||||
"nullable": false,
|
||||
"type": "integer"
|
||||
},
|
||||
{
|
||||
"name": "name",
|
||||
"nullable": false,
|
||||
"type": "varchar"
|
||||
},
|
||||
{
|
||||
"name": "articles",
|
||||
"type": {
|
||||
"array": {
|
||||
"logical_model": "article"
|
||||
},
|
||||
"nullable": false
|
||||
}
|
||||
}
|
||||
],
|
||||
"name": "author",
|
||||
"source": "snowflake"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "snowflake_track_native_query",
|
||||
"args": {
|
||||
"arguments": {},
|
||||
"array_relationships": [],
|
||||
"code": "SELECT * FROM (VALUES (1, 'Logical Models', 1), (2, 'Native Queries', 2), (3, 'Relationships', 3), (4, 'Graph Relationships', 4), (5, 'Permissions', 5)) as t(\"id\", \"title\", \"author_id\")",
|
||||
"object_relationships": [
|
||||
{
|
||||
"name": "author",
|
||||
"using": {
|
||||
"column_mapping": {
|
||||
"author_id": "id"
|
||||
},
|
||||
"insertion_order": null,
|
||||
"remote_native_query": "author"
|
||||
}
|
||||
}
|
||||
],
|
||||
"returns": "article",
|
||||
"root_field_name": "article",
|
||||
"source": "snowflake",
|
||||
"type": "query"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "snowflake_track_native_query",
|
||||
"args": {
|
||||
"arguments": {},
|
||||
"array_relationships": [
|
||||
{
|
||||
"name": "articles",
|
||||
"using": {
|
||||
"column_mapping": {
|
||||
"id": "author_id"
|
||||
},
|
||||
"insertion_order": null,
|
||||
"remote_native_query": "article"
|
||||
}
|
||||
}
|
||||
],
|
||||
"code": "SELECT * FROM (VALUES (1, 'Tom'), (2, 'Dan'), (3, 'Philip'), (4, 'Gil'), (5, 'Samir')) as t(\"id\", \"name\")",
|
||||
"object_relationships": [],
|
||||
"returns": "author",
|
||||
"root_field_name": "author",
|
||||
"source": "snowflake",
|
||||
"type": "query"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
:::info Wrap calls in `bulk_atomic`
|
||||
|
||||
Similar to Logical Models, tracking the Native Queries one-by-one would fail, since `get_articles` refers to
|
||||
`get_authors`, which is not yet defined.
|
||||
|
||||
Tracking the Native Queries in one atomic operation postpones coherency checks until all models are tracked, which
|
||||
allows for mutual references.
|
||||
|
||||
:::
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="query" label="Query">
|
||||
The Native Queries in this example enable queries like:
|
||||
|
||||
```graphql
|
||||
query {
|
||||
get_authors {
|
||||
name
|
||||
|
||||
short_excerpt: articles(args: { length: 10 }) {
|
||||
title
|
||||
contents
|
||||
}
|
||||
|
||||
long_excerpt: articles(args: { length: 100 }) {
|
||||
title
|
||||
contents
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
Loading…
Reference in New Issue
Block a user