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

Add guide for using SafeQL and extensions to help unsupported features. #5419

Merged
merged 34 commits into from
Nov 22, 2023
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
8a415c1
add guide for GIS support
jharrell Nov 2, 2023
2bf1da1
Apply suggestions from code review
jharrell Nov 2, 2023
f522587
Merge branch 'main' into jharrell/issue5407
jharrell Nov 3, 2023
86a5e7a
Merge branch 'main' into jharrell/issue5407
jharrell Nov 6, 2023
917378e
Updates to structure
jharrell Nov 6, 2023
32c7a3a
Update content/300-guides/500-other/900-advanced-database-tasks/06-ty…
jharrell Nov 6, 2023
7edc4f4
Apply suggestions from code review
jharrell Nov 6, 2023
c7eab15
Apply suggestions from code review
jharrell Nov 7, 2023
f66ec88
PR feedback
jharrell Nov 7, 2023
5449afb
Update content/300-guides/500-other/900-advanced-database-tasks/06-ty…
jharrell Nov 7, 2023
7dfbcbb
Merge branch 'main' into jharrell/issue5407
jharrell Nov 7, 2023
8b45930
Apply suggestions from code review
jharrell Nov 8, 2023
983fcba
expand article
jharrell Nov 8, 2023
8749586
Update spellcheck.yml
jharrell Nov 8, 2023
fea51d1
typo
jharrell Nov 8, 2023
89b1278
Merge branch 'jharrell/issue5407' of github.com:prisma/docs into jhar…
jharrell Nov 8, 2023
710e72c
Merge branch 'main' into jharrell/issue5407
ruheni Nov 8, 2023
17b85e6
Merge branch 'main' into jharrell/issue5407
jharrell Nov 8, 2023
bfd9811
Cover SafeQL usage within a client extension
jharrell Nov 8, 2023
97e0b19
Merge branch 'main' into jharrell/issue5407
ruheni Nov 9, 2023
eced3dc
Apply suggestions from code review
jharrell Nov 9, 2023
4c4f79e
Update content/300-guides/500-other/900-advanced-database-tasks/06-ty…
nikolasburk Nov 15, 2023
4b95cc8
update safeql guide
nikolasburk Nov 21, 2023
516c18f
Merge branch 'main' into jharrell/issue5407
nikolasburk Nov 21, 2023
001f178
Update content/300-guides/500-other/900-advanced-database-tasks/06-ty…
nikolasburk Nov 22, 2023
34f59df
Update content/300-guides/500-other/900-advanced-database-tasks/06-ty…
nikolasburk Nov 22, 2023
9b101cb
Update content/300-guides/500-other/900-advanced-database-tasks/06-ty…
nikolasburk Nov 22, 2023
8be143b
Update content/300-guides/500-other/900-advanced-database-tasks/06-ty…
nikolasburk Nov 22, 2023
3b539d0
Update content/300-guides/500-other/900-advanced-database-tasks/06-ty…
nikolasburk Nov 22, 2023
a99d827
Update content/300-guides/500-other/900-advanced-database-tasks/06-ty…
nikolasburk Nov 22, 2023
246a375
Update content/300-guides/500-other/900-advanced-database-tasks/06-ty…
nikolasburk Nov 22, 2023
6ec2ad2
Update content/300-guides/500-other/900-advanced-database-tasks/06-ty…
nikolasburk Nov 22, 2023
5e39403
Update content/300-guides/500-other/900-advanced-database-tasks/06-ty…
nikolasburk Nov 22, 2023
fcab560
Update content/300-guides/500-other/900-advanced-database-tasks/06-ty…
nikolasburk Nov 22, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
---
title: 'Using SafeQL with unsupported GIS features'
metaTitle: 'Using SafeQL with unsupported GIS features (Guide)'
jharrell marked this conversation as resolved.
Show resolved Hide resolved
metaDescription: 'Learn how to use SafeQL and Prisma Client extensions to work around features not natively supported by Prisma, such as PostGIS.'
---

## Overview

This page explains how to use Prisma Client extensions and [SafeQL](https://safeql.dev) to create custom, type-safe Prisma Client queries and type-safe raw SQL queries when working with geographical data. Our example will be using [PostGIS](https://postgis.net/) and PostgreSQL, but is applicable to other features and database engines.
jharrell marked this conversation as resolved.
Show resolved Hide resolved
nikolasburk marked this conversation as resolved.
Show resolved Hide resolved

## What is SafeQL?

[SafeQL](https://safeql.dev/) allows for advanced linting and type safety within raw SQL queries. After setup, SafeQL works with Prisma `$queryRaw` and `$executeRaw` to provide type safety when raw queries are required. Please note that SafeQL runs as an ESLint plugin and is configured using ESLint rules. This guide doesn't cover setting up ESLint and we will assume that you already having it running in your project.
jharrell marked this conversation as resolved.
Show resolved Hide resolved

## Prerequisites

To follow along, you will be expected to have:

- A [PostgreSQL](https://www.postgresql.org/) database with PostGIS installed.
jharrell marked this conversation as resolved.
Show resolved Hide resolved
- Prisma set up in your project
- ESLint set up in your project

## Geographic data support in Prisma

At the time of writing, Prisma does not support working with geographic data, specifically using PostGIS.
jharrell marked this conversation as resolved.
Show resolved Hide resolved

A model that has geographic data columns will be stored using the [`Unsupported`](/reference/api-reference/prisma-schema-reference#unsupported) data type. Fields with `Unsupported` types are present in the generated Prisma Client. A model with a required `Unsupported` type does not expose write operations such as `create`, and `update`.
nikolasburk marked this conversation as resolved.
Show resolved Hide resolved
nikolasburk marked this conversation as resolved.
Show resolved Hide resolved

Prisma supports write operations on models with a required `Unsupported` field using `$queryRaw` and `$executeRaw`. You can use Prisma Client extensions and SafeQL to improve the type-safety when working with geographical data in raw queries.

## 1. Set up Prisma for use with PostGIS

If you haven't already, enable the `postgresqlExtensions` Preview feature and add the `postgis` PostgreSQL extension in your Prisma schema:

```prisma highlight=3,8;add
jharrell marked this conversation as resolved.
Show resolved Hide resolved
generator client {
provider = "prisma-client-js"
previewFeatures = ["postgresqlExtensions"]
}

datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
extensions = [postgis]
}
```

<Admonition type="warning">

If you are not using a hosted database provider, you will likely need to install the `postgis` extension. Refer to [PostGIS's docs](http://postgis.net/documentation/getting_started/#installing-postgis) to learn more about how to get started with PostGIS.
nikolasburk marked this conversation as resolved.
Show resolved Hide resolved

If you're using Docker Compose, you can use the following snippet to set up a PostgreSQL database that has PostGIS installed:

```yaml
version: '3.6'
services:
pgDB:
image: postgis/postgis:13-3.1-alpine
restart: always
ports:
- '5432:5432'
volumes:
- db_data:/var/lib/postgresql/data
environment:
POSTGRES_PASSWORD: password
POSTGRES_DB: geoexample
volumes:
db_data:
```

</Admonition>

Next, create a migration using the `prisma migrate dev` command to enable the extension. The output of the migration file should look like the following:
nikolasburk marked this conversation as resolved.
Show resolved Hide resolved

```sql

-- CreateExtension
CREATE EXTENSION IF NOT EXISTS "postgis";

```

Make sure that the migration is applied by running `prisma migrate status`.

## 2. Create a new model that uses a geographic data column

Add a new model with a column with a `geography` data type once the migration is applied. For this guide, we'll use a model called `PointOfInterest`.

```prisma
model PointOfInterest {
id Int @id @default(autoincrement())
name String
location Unsupported("geography(Point, 4326)")
}
```

You'll notice that the `location` field uses an `Unsupported` type. This means that we lose a lot of the benefits of Prisma when working with `PointOfInterest`. We'll be using [SafeQL](https://safeql.dev/) to fix this.
nikolasburk marked this conversation as resolved.
Show resolved Hide resolved

Run the `prisma migrate dev` command to create a migration and the table `PointOfInterest` table in your database.
nikolasburk marked this conversation as resolved.
Show resolved Hide resolved

nikolasburk marked this conversation as resolved.
Show resolved Hide resolved
## 3. Integrate SafeQL

SafeQL is easily integrated with Prisma in order to lint `$queryRaw` and `$executeRaw` Prisma calls. You can reference [SafeQL's integration guide](https://safeql.dev/compatibility/prisma.html) or follow the steps below.

### Install the <inlinecode>@ts-safeql/eslint-plugin</inlinecode> npm package

```terminal
npm install -D @ts-safeql/eslint-plugin
```

This ESLint plugin is what will allow for queries to be linted.

### Add <inlinecode>@ts-safeql/eslint-plugin</inlinecode> to your ESLint plugins

You should now add `@ts-safeql/eslint-plugin` to your list of ESLint plugins. In our example we are using an `.eslintrc.js` file, but this can be applied to [any way that you configure ESLint](https://eslint.org/docs/latest/use/configure/)
jharrell marked this conversation as resolved.
Show resolved Hide resolved

```js file=.eslintrc.js highlight=3
/** @type {import('eslint').Linter.Config} */
module.exports = {
"plugins": [..., "@ts-safeql/eslint-plugin"],
...
}
```

### Add <inlinecode>@ts-safeql/check-sql</inlinecode> rules

Now setup rules so that SafeQL will mark invalid SQL queries as ESLint errors

```js file=.eslintrc.js highlight=4-22;add
/** @type {import('eslint').Linter.Config} */
module.exports = {
plugins: [..., '@ts-safeql/eslint-plugin'],
rules: {
'@ts-safeql/check-sql': [
'error',
{
connections: [
{
// The migrations path:
migrationsDir: './prisma/migrations',
targets: [
// This makes `prisma.$queryRaw` and `prisma.$executeRaw` commands linted
{ tag: 'prisma.+($queryRaw|$executeRaw)', transform: '{type}[]' },
// This makes `client.$queryRaw` and `client.$executeRaw` commands linted
{ tag: 'client.+($queryRaw|$executeRaw)', transform: '{type}[]' },
nikolasburk marked this conversation as resolved.
Show resolved Hide resolved
],
},
],
},
],
},
}
```

### Connect to your database

Finally, set up a `connectionUrl` for SafeQL so that it can read the state of your database, including columns for each table.

Our example relies on `dotenv` to get the same connection string that is used by Prisma. We recommend this in order to keep your database URL out of version control.
nikolasburk marked this conversation as resolved.
Show resolved Hide resolved

```js file=.eslintrc.js highlight=1,6-9,16;add
require('dotenv').config()

/** @type {import('eslint').Linter.Config} */
module.exports = {
plugins: ['@ts-safeql/eslint-plugin'],
// exclude `parserOptions` if you are not using TypeScript
parserOptions: {
project: './tsconfig.json',
},
rules: {
'@ts-safeql/check-sql': [
'error',
{
connections: [
{
connectionUrl: process.env.DATABASE_URL,
// The migrations path:
migrationsDir: './prisma/migrations',
targets: [
// what you would like SafeQL to lint. This makes `prisma.$queryRaw` and `prisma.$executeRaw`
// commands linted
{ tag: 'prisma.+($queryRaw|$executeRaw)', transform: '{type}[]' },
nikolasburk marked this conversation as resolved.
Show resolved Hide resolved
],
},
],
},
],
},
}
```

## 4. Creating an extension for geographic data

Now let's make a Prisma Client extension in order to query this model. We will be making an extension that finds the closest points of interest to a given longitude and latitude.

```ts file=lib/prisma.ts
<!-- prettier-ignore-start -->
nikolasburk marked this conversation as resolved.
Show resolved Hide resolved
import { PrismaClient } from '@prisma/client'

const prisma = new PrismaClient().$extends((client) => {
return client.$extends({
model: {
pointOfInterest: {
async findClosestPoints(latitude: number, longitude: number, limit: number) {
return client.$queryRaw<{ id: number; name: string; location: string }[]>`SELECT * FROM "PointOfInterest" ORDER BY ST_DistanceSphere(location::geometry, ST_MakePoint(${longitude}, ${latitude})) LIMIT ${limit}`
},
nikolasburk marked this conversation as resolved.
Show resolved Hide resolved
},
},
})
})
nikolasburk marked this conversation as resolved.
Show resolved Hide resolved

export { prisma }
<!-- prettier-ignore-end -->
```

Now, you can use our Prisma Client as normal to find close points of interest to a given longitude and latitude using the custom method created on the `PointOfInterest` model.

```ts file=app.ts
<!-- prettier-ignore-start -->
const closestPointOfInterest = await prisma.pointOfInterest.findClosestPoints(33.942791, -118.410042, 1)
nikolasburk marked this conversation as resolved.
Show resolved Hide resolved
<!-- prettier-ignore-end -->
```

We also have the benefit of SafeQL to add extra type safety to our raw queries. For example, if we removed the cast to `geometry` for `location`, we would get the linting error:
nikolasburk marked this conversation as resolved.
Show resolved Hide resolved

```terminal
error Invalid Query: function st_distancesphere(geography, geometry) does not exist @ts-safeql/check-sql
```

Which would let us know to check our function signature again.
jharrell marked this conversation as resolved.
Show resolved Hide resolved

## Conclusion

While Prisma does not natively support geographic data like PostGIS, SafeQL and Prisma Client extensions give back some type-safety to applications that use these data types. This could be implemented further, by taking the above ideas and applying them to allow for mutations against underlying data, all while having typed guarantees thanks the the SafeQL ESLint plugin.
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
title: Advanced features using raw SQL
metaTitle: Advanced features using raw SQL (Guides)
metaDescription: Learn how to use raw SQL and Prisma Client extensions to cover areas not natively supported by Prisma.
toc: false
---

<TopBlock>

There are certain database features that are not natively supported by Prisma. In these cases, it's possible to use [Prisma Client extensions](/concepts/components/prisma-client/client-extensions) and tools like [SafeQL](https://safeql.dev/) in order to keep the great developer experience of Prisma when working with raw SQL.

</TopBlock>

## In this section

<Subsections />
Loading