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

docs: add Prisma sample app #1968

Draft
wants to merge 5 commits into
base: postgresql-dialect
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 5 additions & 0 deletions .github/workflows/samples.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,11 @@ jobs:
run: |
npm install
npm start
- name: Run Prisma Sample tests
working-directory: ./samples/nodejs/prisma-sample-app
run: |
npm install
npm start
ruby-samples:
runs-on: ubuntu-latest
steps:
Expand Down
11 changes: 11 additions & 0 deletions samples/nodejs/prisma-sample-app/.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Environment variables declared in this file are automatically made available to Prisma.
# See the documentation for more detail: https://pris.ly/d/prisma-schema#accessing-environment-variables-from-the-schema

# Point this URL to PGAdapter and your database.
# The sample overrides this value to point to a PGAdapter+Emulator instance that is started
# by the sample application. See index.ts for more details.
#
# Note that the '?options=-c%20spanner.well_known_client=prisma' at the end of the connection string
# is required for PGAdapter to recognize the connection as coming from Prisma. PGAdapter will then
# add specific query replacements for Prisma.
DATABASE_URL="postgresql://localhost:5432/prisma-sample?options=-c%20spanner.well_known_client=prisma"
168 changes: 168 additions & 0 deletions samples/nodejs/prisma-sample-app/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
# PGAdapter and Prisma

PGAdapter has experimental support for [Prisma](https://prisma.io/). This document shows
how to use this sample application, and lists the limitations when working with `Prisma` with PGAdapter.

The [src/sample.ts](src/sample.ts) file contains a sample application using `Prisma` with PGAdapter.
Use this as a reference for features of `Prisma` that are supported with PGAdapter.
This sample assumes that the reader is familiar with `Prisma`, and
it is not intended as a tutorial for how to use `Prisma` in general.

__NOTE__: In order for all features that are mentioned in this document to work, it is important
that your connection string contains the following addition: `?options=-c%20spanner.well_known_client=prisma`.
That will ensure that PGAdapter recognizes the connecting client as Prisma. This ensures that
PGAdapter can apply specific query translations for Prisma.
See [this environment file](.env) for a full example.


## Experimental Support
Experimental Support means that `Prisma` can be used with Cloud Spanner PostgreSQL databases, but
with limitations. Applications that have been developed with `Prisma` for PostgreSQL will require
modifications before they can be used with Cloud Spanner PostgreSQL databases. It is possible to
develop new applications using `Prisma` with Cloud Spanner PostgreSQL databases, as long as the
limitations are taken into account. These applications will also work with PostgreSQL without
modifications.

See [Limitations](#limitations) for a full list of limitations when working with `Prisma`.

## Start PGAdapter
You must start PGAdapter before you can run the sample. The following command shows how to start PGAdapter using the
pre-built Docker image. See [Running PGAdapter](../../../README.md#usage) for more information on other options for how
to run PGAdapter.

```shell
export GOOGLE_APPLICATION_CREDENTIALS=/path/to/credentials.json
docker pull gcr.io/cloud-spanner-pg-adapter/pgadapter
docker run \
-d -p 5432:5432 \
-v ${GOOGLE_APPLICATION_CREDENTIALS}:/credentials.json:ro \
-v /tmp:/tmp \
gcr.io/cloud-spanner-pg-adapter/pgadapter \
-p my-project -i my-instance \
-c /credentials.json \
-x
```

## Configuration

Modify the .env file in this directory, so it corresponds to your local setup:
1. Modify the `DATABASE_URL` and `STALE_READ_DATABASE_URL` variables to point to your PGAdapter
instance and your database. Do not modify or remove the
`?sslmode=disable&options=-c%20spanner.well_known_client=prisma` sections.
2. Modify the `SHADOW_DATABASE_URL` to point to a local PostgreSQL instance. This is required, as
Cloud Spanner cannot be used as a shadow database. See also https://www.prisma.io/docs/concepts/components/prisma-migrate/shadow-database#cloud-hosted-shadow-databases-must-be-created-manually

## Creating the Sample Data Model

The sample data model can be created using `Prisma` migrations. Run the following command to create
the sample data model:

```shell
npx prisma migrate deploy
```

## Running the Sample Application

Run the sample application with the following command:

```shell
npm run start
```

## Data Types

See the [prisma/schema.prisma](prisma/schema.prisma) file for an example mapping for all data types.

Cloud Spanner supports the following data types in combination with `Prisma`.

| PostgreSQL Type | Prisma type |
|----------------------------------------|-------------------|
| boolean | Boolean |
| bigint / int8 | BigInt |
| varchar | String |
| text | String |
| float8 / double precision | Float |
| numeric | Decimal |
| timestamptz / timestamp with time zone | DateTime |
| date | DateTime @db.Date |
| bytea | Bytes |
| jsonb | Json |


## Limitations
The following limitations are currently known:

| Limitation | Workaround |
|------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Migrations | Cloud Spanner does not support the full PostgreSQL DDL dialect, and also not all `pg_catalog` tables and functions. PGAdapter contains translations for the most commonly used features that are not supported. It is possible to use Prisma Migrations with PGAdapter, but some migrations are likely to require manual modifications. |
| Shadow Database | Cloud Spanner cannot be used as a shadow database. Instead, you need to set up a real PostgreSQL database and use that as the shadow database. See https://www.prisma.io/docs/concepts/components/prisma-migrate/shadow-database#cloud-hosted-shadow-databases-must-be-created-manually |
| Advisory locks | Advisory locks are not supported. Prisma will try to use this during migrations. These commands will be ignored by PGAdapter. |
| Generated primary keys | Auto-generated primary keys are not supported. Do not add the `@default(autoincrement())` annotation to your model definitions. |
| Upsert | Upsert is not supported. |

### Migrations
Prisma Migrations make extensive use of `pg_catalog` tables and functions. Not all of these are supported
by Cloud Spanner. PGAdapter contains replacement queries for the most commonly used `pg_catalog`
features. This makes it possible to use Prisma Migrations with PGAdapter, but some migration
commands could still fail. Please feel free to open an issue in the GitHub repository if you run
into any such issues.

An alternative way to use Prisma Migrations with Cloud Spanner PostgreSQL is to execute the
migration commands on an open-source PostgreSQL database. See [migrations.md](migrations.md) for
more information.

The DDL that is generated by a Prisma Migration command can be modified manually. This is sometimes
needed to add features that are not supported by Prisma, or if you want to add specific Cloud Spanner
features to your data model. This sample application for example creates an `INTERLEAVED` table.
It can also be necessary if Prisma generates DDL statements that are not supported by Cloud Spanner.

See also:
- https://www.prisma.io/docs/concepts/components/prisma-migrate/migrate-development-production#customizing-migrations
- https://cloud.google.com/spanner/docs/reference/postgresql/data-definition-language#extensions_to

### Shadow Database
Prisma Migrations use a shadow database for generating and verifying new migrations. Cloud Spanner
cannot be used for this purpose. Instead, you need to set up a separate PostgreSQL database that can
be used as the shadow database.

See also https://www.prisma.io/docs/concepts/components/prisma-migrate/shadow-database#cloud-hosted-shadow-databases-must-be-created-manually.

You can also use an open-source PostgreSQL database to execute all Prisma migrations command. See
[migrations.md](migrations.md) for more information.

### Advisory Locks
Prisma Migrations will use [advisory locks during migrations](https://www.prisma.io/docs/concepts/components/prisma-migrate/migrate-development-production#advisory-locking).
Cloud Spanner and PGAdapter do not support advisory locks. The advisory lock statements will
therefore be ignored and have no effect.

### Generated Primary Keys
Generated primary keys are not supported and should be replaced with primary key definitions that
are manually assigned. See https://cloud.google.com/spanner/docs/schema-design#primary-key-prevent-hotspots
for more information on choosing a good primary key. This sample uses UUIDs that are generated by the
application for primary keys.

```typescript
model Singer {
id String @id
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
firstName String
lastName String
/// `fullName` is generated by the database and should not be set.
fullName String?
active Boolean
albums Album[]
Concert Concert[]

@@index(lastName)
}
```

### Generated Columns
Generated columns can be used, but these are not supported by Prisma Migrations. You need to manually
modify the generated migration script to create a generated column. This sample uses a generated
column for the `fullName` property of `Singer`. See [migration.sql](prisma/migrations/20230608163206_init/migration.sql)
for an example.

### Upsert
`Upsert` statements are not supported by Cloud Spanner and should not be used.
22 changes: 22 additions & 0 deletions samples/nodejs/prisma-sample-app/migrations.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Prisma Migrations with PGAdapter

Prisma migration commands, other than `prisma migration deploy`, are not supported on Cloud Spanner
PostgreSQL, as they use `pg_catalog` tables and other features of PostgreSQL that are not supported
by Cloud Spanner.

You can however use a local PostgreSQL database and execute Prisma migration commands on that
database instead.

## Empty PostgreSQL Database

1. Create an empty PostgreSQL database, either on a local server or in a Docker container.
2. Modify the `.env` file to point to this local database instead of Cloud Spanner.
3. Execute `npx prisma migrate deploy` to deploy all your migrations to this database.
4. Make any changes to your Prisma code and execute `prisma migrate dev --name my_migration_name` to
generate the migration.
5. Save the migration script to your code repository. Make any manual changes that might be needed,
for example if Prisma generated DDL statements that are not supported by Cloud Spanner. It is
recommended to try the DDL statements manually on a Cloud Spanner database before committing them
to your code repository.
6. Modify the `.env` file to point to Cloud Spanner again and apply the latest migration(s) with
`npx prisma migrate deploy`.
23 changes: 23 additions & 0 deletions samples/nodejs/prisma-sample-app/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"name": "prisma-sample-app",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"start": "ts-node src/index.ts",
"test": "ts-node src/sample_test.ts"
},
"keywords": [],
"author": "",
"devDependencies": {
"@types/node": "^18.11.18",
"prisma": "^5.15.0",
"ts-node": "^10.9.1",
"typescript": "^4.9.4"
},
"dependencies": {
"@prisma/client": "^5.15.0",
"yargs": "^17.5.1",
"testcontainers": "^10.7.1"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
-- CreateTable
CREATE TABLE "Singer" (
"id" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"firstName" TEXT NOT NULL,
"lastName" TEXT NOT NULL,
"fullName" TEXT generated always as (CASE WHEN "firstName" IS NULL THEN "lastName"
WHEN "lastName" IS NULL THEN "firstName"
ELSE "firstName" || ' ' || "lastName" END) stored,
"active" BOOLEAN NOT NULL,

CONSTRAINT "Singer_pkey" PRIMARY KEY ("id")
);

-- CreateTable
CREATE TABLE "Album" (
"id" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"title" TEXT NOT NULL,
"marketingBudget" DECIMAL(65,30),
"releaseDate" DATE,
"coverPicture" BYTEA,
"singerId" TEXT NOT NULL,

CONSTRAINT "Album_pkey" PRIMARY KEY ("id")
);

-- CreateTable
-- Note that the definition of this table has been manually modified so it is interleaved in the
-- table Album. This is a Cloud Spanner-specific extension to open source PostgreSQL.
CREATE TABLE "Track" (
"id" TEXT NOT NULL,
"trackNumber" BIGINT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"title" TEXT NOT NULL,
"sampleRate" DOUBLE PRECISION,

CONSTRAINT "Track_pkey" PRIMARY KEY ("id","trackNumber")
) INTERLEAVE IN PARENT "Album" ON DELETE CASCADE;

-- CreateTable
CREATE TABLE "Venue" (
"id" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"name" TEXT NOT NULL,
"description" JSONB NOT NULL,

CONSTRAINT "Venue_pkey" PRIMARY KEY ("id")
);

-- CreateTable
CREATE TABLE "Concert" (
"id" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"name" TEXT NOT NULL,
"venueId" TEXT NOT NULL,
"singerId" TEXT NOT NULL,
"startTime" TIMESTAMPTZ NOT NULL,
"endTime" TIMESTAMPTZ NOT NULL,

CONSTRAINT "Concert_pkey" PRIMARY KEY ("id"),
constraint chk_end_time_after_start_time check ("endTime" > "startTime")
);

-- CreateIndex
CREATE INDEX "Singer_lastName_idx" ON "Singer"("lastName");

-- CreateIndex
CREATE UNIQUE INDEX "Album_singerId_title_idx" ON "Album"("singerId", "title");

-- CreateIndex
CREATE UNIQUE INDEX "Track_id_title_idx" ON "Track"("id", "title");

-- CreateIndex
CREATE INDEX "Concert_startTime_idx" ON "Concert"("startTime" DESC);

-- AddForeignKey
ALTER TABLE "Album" ADD CONSTRAINT "Album_singerId_fkey" FOREIGN KEY ("singerId") REFERENCES "Singer"("id") ON DELETE RESTRICT ON UPDATE CASCADE;

-- AddForeignKey
ALTER TABLE "Concert" ADD CONSTRAINT "Concert_venueId_fkey" FOREIGN KEY ("venueId") REFERENCES "Venue"("id") ON DELETE RESTRICT ON UPDATE CASCADE;

-- AddForeignKey
ALTER TABLE "Concert" ADD CONSTRAINT "Concert_singerId_fkey" FOREIGN KEY ("singerId") REFERENCES "Singer"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Please do not edit this file manually
# It should be added in your version-control system (i.e. Git)
provider = "postgresql"
Loading
Loading