Skip to content

Commit

Permalink
Run db push against programmatic Prisma API and remove faulty optimis…
Browse files Browse the repository at this point in the history
…ation (#5059)

* Prisma experimenting

* Experimenting

* WIP

* Experimenting

* Add --skip-generate

* Convert it to a preconstruct package

* Experimenting

* Fix a thing

* Fix a thing

* WIP

* Add _runPrismaCmd back for now

* Try a thing

* add an await

* Test a thing

* Remove the skip

* Test a thing

* Hopefully fix things?

* Revert some changes

* Revert more things

* Another thing

* Add a newline

* Cleanup and run in parallel

* Fix things

* Test perf things

* Try a thing

* Fix things

* Add changesets

* Fix some things
  • Loading branch information
emmatown authored Mar 12, 2021
1 parent fbc6d6d commit b3c4a75
Show file tree
Hide file tree
Showing 10 changed files with 183 additions and 37 deletions.
5 changes: 5 additions & 0 deletions .changeset/empty-garlics-begin.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@keystone-next/adapter-prisma-legacy': patch
---

Replaced usage of prisma cli when in `migrationMode: 'prototype'` with programmatic calls to `@prisma/migrate` to improve performance
5 changes: 5 additions & 0 deletions .changeset/poor-plums-try.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@keystone-next/adapter-prisma-legacy': patch
---

Removed faulty optimisation that caused migrations to not be run if the prisma client directory and the prisma schema already existed
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@
"packages/fields-wysiwyg-tinymce",
"packages/fields-color",
"packages/fields",
"packages/adapter-prisma",
"packages-next/*",
"design-system/packages/*"
],
Expand Down
3 changes: 0 additions & 3 deletions packages/adapter-prisma/index.js

This file was deleted.

3 changes: 3 additions & 0 deletions packages/adapter-prisma/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
{
"name": "@keystone-next/adapter-prisma-legacy",
"description": "KeystoneJS Prisma Database Adapter",
"main": "dist/adapter-prisma-legacy.cjs.js",
"module": "dist/adapter-prisma-legacy.esm.js",
"version": "3.3.0",
"author": "The KeystoneJS Development Team",
"license": "MIT",
Expand All @@ -12,6 +14,7 @@
"@keystone-next/keystone-legacy": "^20.0.0",
"@keystone-next/utils-legacy": "^7.0.0",
"@prisma/client": "2.18.0",
"@prisma/migrate": "2.18.0",
"@prisma/sdk": "2.18.0",
"cuid": "^2.1.8",
"prisma": "2.18.0"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
const fs = require('fs');
const path = require('path');
const { execSync } = require('child_process');
const cuid = require('cuid');
const { getGenerators, formatSchema } = require('@prisma/sdk');
const {
import fs from 'fs';
import path from 'path';
import { execSync } from 'child_process';
import cuid from 'cuid';
import { getGenerator, formatSchema } from '@prisma/sdk';
import {
BaseKeystoneAdapter,
BaseListAdapter,
BaseFieldAdapter,
} = require('@keystone-next/keystone-legacy');
const { defaultObj, mapKeys, identity, flatten } = require('@keystone-next/utils-legacy');
} from '@keystone-next/keystone-legacy';
import { defaultObj, mapKeys, identity, flatten } from '@keystone-next/utils-legacy';
// eslint-disable-next-line import/no-unresolved
import { runPrototypeMigrations } from './migrations';

class PrismaAdapter extends BaseKeystoneAdapter {
constructor(config = {}) {
Expand All @@ -34,6 +36,7 @@ class PrismaAdapter extends BaseKeystoneAdapter {
this.schemaPath = path.join(prismaPath, 'schema.prisma');
this.clientPath = path.resolve(`${prismaPath}/${clientDir}`);
this.dbSchemaName = this.getDbSchemaName({ prismaSchema });
this.prismaSchema = prismaSchema;
return { prismaSchema };
}

Expand Down Expand Up @@ -69,6 +72,13 @@ class PrismaAdapter extends BaseKeystoneAdapter {
}

async _connect({ rels }) {
// the adapter was already connected since we have a prisma client
// it may have been disconnected since it was connected though
// so connect but don't regenerate the prisma client
if (this.prisma) {
await this.prisma.$connect();
return;
}
const PrismaClient = await this._getPrismaClient({ rels });
this.prisma = new PrismaClient({
log: this.enableLogging && ['query'],
Expand All @@ -89,31 +99,16 @@ class PrismaAdapter extends BaseKeystoneAdapter {
// If any of our critical directories are missing, or if the schema has changed, then
// we've got things to do.

try {
const existing = fs.readFileSync(this.schemaPath, { encoding: 'utf-8' });
if (existing === prismaSchema && fs.existsSync(this.clientPath)) {
// If they're the same, we're golden
return;
}
} catch (err) {
if (err.code !== 'ENOENT') {
throw err;
}
}

this._writePrismaSchema({ prismaSchema });

// Generate prisma client
await this._generatePrismaClient();

// Run prisma migrations
await this._runMigrations();
// Generate prisma client and run prisma migrations
await Promise.all([this._generatePrismaClient(), this._runMigrations({ prismaSchema })]);
}

async _runMigrations() {
async _runMigrations({ prismaSchema }) {
if (this.migrationMode === 'prototype') {
// Sync the database directly, without generating any migration
this._runPrismaCmd(`db push --accept-data-loss --preview-feature`);
await runPrototypeMigrations(this._url(), prismaSchema, path.resolve(this.schemaPath));
} else if (this.migrationMode === 'createOnly') {
// Generate a migration, but do not apply it
this._runPrismaCmd(`migrate dev --create-only --name keystone-${cuid()} --preview-feature`);
Expand All @@ -136,7 +131,7 @@ class PrismaAdapter extends BaseKeystoneAdapter {
}

async _generatePrismaClient() {
const generator = (await getGenerators({ schemaPath: this.schemaPath }))[0];
const generator = await getGenerator({ schemaPath: this.schemaPath });
await generator.generate();
generator.stop();
}
Expand Down Expand Up @@ -261,7 +256,7 @@ class PrismaAdapter extends BaseKeystoneAdapter {
} else {
// If we're in prototype mode then we need to rebuild the tables after a reset
this._runPrismaCmd(`migrate reset --force --preview-feature`);
this._runPrismaCmd(`db push --accept-data-loss --preview-feature`);
await runPrototypeMigrations(this._url(), this.prismaSchema, path.resolve(this.schemaPath));
}
} else {
this._runPrismaCmd(`migrate reset --force --preview-feature`);
Expand Down Expand Up @@ -656,4 +651,4 @@ class PrismaFieldAdapter extends BaseFieldAdapter {
}
}

module.exports = { PrismaAdapter, PrismaListAdapter, PrismaFieldAdapter };
export { PrismaAdapter, PrismaListAdapter, PrismaFieldAdapter };
1 change: 1 addition & 0 deletions packages/adapter-prisma/src/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { PrismaAdapter, PrismaListAdapter, PrismaFieldAdapter } from './adapter-prisma';
72 changes: 72 additions & 0 deletions packages/adapter-prisma/src/migrations.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { createDatabase, uriToCredentials, DatabaseCredentials } from '@prisma/sdk';
import { Migrate } from '@prisma/migrate';
import path from 'path';

// we don't want to pollute process.env.DATABASE_URL so we're
// setting the env variable _just_ long enough for Migrate to
// read it and then we reset it immediately after.
// Migrate reads the env variables a single time when it starts the child process that it talks to
function runMigrateWithDbUrl<T>(dbUrl: string, cb: () => T): T {
let prevDBURLFromEnv = process.env.DATABASE_URL;
try {
process.env.DATABASE_URL = dbUrl;
return cb();
} finally {
process.env.DATABASE_URL = prevDBURLFromEnv;
}
}

export async function runPrototypeMigrations(dbUrl: string, schema: string, schemaPath: string) {
let before = Date.now();

await ensureDatabaseExists(dbUrl, path.dirname(schemaPath));
let migrate = new Migrate(schemaPath);

let migration = await runMigrateWithDbUrl(dbUrl, () =>
migrate.engine.schemaPush({
// TODO: we probably want to do something like db push does where either there's
// a prompt or an argument needs to be passed to make it force(i.e. lose data)
force: true,
schema,
})
);
migrate.stop();

if (migration.warnings.length === 0 && migration.executedSteps === 0) {
console.info(`✨ The database is already in sync with the Prisma schema.`);
} else {
console.info(
`✨ Your database is now in sync with your schema. Done in ${formatms(Date.now() - before)}`
);
}
}

async function ensureDatabaseExists(dbUrl: string, schemaDir: string) {
// createDatabase will return false when the database already exists
const result = await createDatabase(dbUrl, schemaDir);
if (result && result.exitCode === 0) {
const credentials = uriToCredentials(dbUrl);
console.log(
`✨ ${credentials.type} database "${credentials.database}" created at ${getDbLocation(
credentials
)}`
);
}
// TODO: handle createDatabase returning a failure (prisma's cli does not handle it though so not super worried)
}

function getDbLocation(credentials: DatabaseCredentials): string {
if (credentials.type === 'sqlite') {
return credentials.uri!;
}

return `${credentials.host}${credentials.port === undefined ? '' : `:${credentials.port}`}`;
}

function formatms(ms: number): string {
if (ms < 1000) {
return `${ms}ms`;
}

return (ms / 1000).toFixed(2) + 's';
}
2 changes: 1 addition & 1 deletion packages/adapter-prisma/tests/adapter-prisma.test.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const { PrismaAdapter } = require('../lib/adapter-prisma');
const { PrismaAdapter } = require('..');

global.console = {
error: jest.fn(),
Expand Down
73 changes: 70 additions & 3 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2744,6 +2744,28 @@
dependencies:
"@prisma/debug" "2.18.0"

"@prisma/[email protected]":
version "2.18.0"
resolved "https://registry.yarnpkg.com/@prisma/migrate/-/migrate-2.18.0.tgz#ea170a629073f7ea46af9e47fd6f779759bbcc1b"
integrity sha512-1/SwHe9zYmDENCGjxm73VQTLrJ4MP5zp0Hze5736U/AByMONHjvVMUrctkgXPJgfSU+63kWM4ajN4Ar4m1BbkA==
dependencies:
"@prisma/debug" "2.18.0"
"@prisma/get-platform" "2.18.0"
"@sindresorhus/slugify" "^1.1.0"
diff "4.0.2"
execa "^5.0.0"
global-dirs "^3.0.0"
has-yarn "^2.1.0"
indent-string "^4.0.0"
log-update "^4.0.0"
new-github-issue-url "^0.2.1"
open "^7.0.3"
pkg-up "^3.1.0"
prompts "^2.3.2"
resolve-pkg "^2.0.0"
strip-ansi "^6.0.0"
strip-indent "^3.0.0"

"@prisma/[email protected]":
version "2.18.0"
resolved "https://registry.yarnpkg.com/@prisma/sdk/-/sdk-2.18.0.tgz#7fb6025d807041735fde32a583a7f5e407ff1f37"
Expand Down Expand Up @@ -4257,7 +4279,7 @@ ansi-escapes@^3.0.0, ansi-escapes@^3.2.0:
resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.2.0.tgz#8780b98ff9dbf5638152d1f1fe5c1d7b4442976b"
integrity sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==

ansi-escapes@^4.2.1:
ansi-escapes@^4.2.1, ansi-escapes@^4.3.0:
version "4.3.1"
resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.1.tgz#a5c47cc43181f1f38ffd7076837700d395522a61"
integrity sha512-JWF7ocqNrp8u9oqpgV+wH5ftbt+cfvv+PTjOvKLT3AdYly/LmORARfEVT1iyjwN+4MqE5UmVKoAdIBqeoCHgLA==
Expand Down Expand Up @@ -5743,6 +5765,13 @@ cli-cursor@^2.0.0, cli-cursor@^2.1.0:
dependencies:
restore-cursor "^2.0.0"

cli-cursor@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-3.1.0.tgz#264305a7ae490d1d03bf0c9ba7c925d1753af307"
integrity sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==
dependencies:
restore-cursor "^3.1.0"

cli-table3@~0.6.0:
version "0.6.0"
resolved "https://registry.yarnpkg.com/cli-table3/-/cli-table3-0.6.0.tgz#b7b1bc65ca8e7b5cef9124e13dc2b21e2ce4faee"
Expand Down Expand Up @@ -6804,6 +6833,11 @@ diff-sequences@^26.6.2:
resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-26.6.2.tgz#48ba99157de1923412eed41db6b6d4aa9ca7c0b1"
integrity sha512-Mv/TDa3nZ9sbc5soK+OoA74BsS3mL37yixCvUAQkiuA4Wz6YtwP/K47n2rv2ovzHZvoiQeA5FTQOschKkEwB0Q==

[email protected]:
version "4.0.2"
resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d"
integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==

[email protected]:
version "5.0.0"
resolved "https://registry.yarnpkg.com/diff/-/diff-5.0.0.tgz#7ed6ad76d859d030787ec35855f5b1daf31d852b"
Expand Down Expand Up @@ -9616,7 +9650,7 @@ is-wsl@^1.1.0:
resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d"
integrity sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=

is-wsl@^2.2.0:
is-wsl@^2.1.1, is-wsl@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271"
integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==
Expand Down Expand Up @@ -10688,6 +10722,16 @@ log-update@^2.3.0:
cli-cursor "^2.0.0"
wrap-ansi "^3.0.1"

log-update@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/log-update/-/log-update-4.0.0.tgz#589ecd352471f2a1c0c570287543a64dfd20e0a1"
integrity sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==
dependencies:
ansi-escapes "^4.3.0"
cli-cursor "^3.1.0"
slice-ansi "^4.0.0"
wrap-ansi "^6.2.0"

loglevel@^1.6.7:
version "1.7.1"
resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.7.1.tgz#005fde2f5e6e47068f935ff28573e125ef72f197"
Expand Down Expand Up @@ -11976,6 +12020,14 @@ onetime@^5.1.0, onetime@^5.1.2:
dependencies:
mimic-fn "^2.1.0"

open@^7.0.3:
version "7.4.2"
resolved "https://registry.yarnpkg.com/open/-/open-7.4.2.tgz#b8147e26dcf3e426316c730089fd71edd29c2321"
integrity sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==
dependencies:
is-docker "^2.0.0"
is-wsl "^2.1.1"

optimism@^0.14.0:
version "0.14.0"
resolved "https://registry.yarnpkg.com/optimism/-/optimism-0.14.0.tgz#256fb079a3428585b40a3a8462f907e0abd2fc49"
Expand Down Expand Up @@ -12618,6 +12670,13 @@ pkg-dir@^4.1.0, pkg-dir@^4.2.0:
dependencies:
find-up "^4.0.0"

pkg-up@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/pkg-up/-/pkg-up-3.1.0.tgz#100ec235cc150e4fd42519412596a28512a0def5"
integrity sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==
dependencies:
find-up "^3.0.0"

[email protected]:
version "1.3.6"
resolved "https://registry.yarnpkg.com/platform/-/platform-1.3.6.tgz#48b4ce983164b209c2d45a107adb31f473a6e7a7"
Expand Down Expand Up @@ -12908,7 +12967,7 @@ promise-inflight@^1.0.1:
resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3"
integrity sha1-mEcocL8igTL8vdhoEputEsPAKeM=

prompts@^2.0.1:
prompts@^2.0.1, prompts@^2.3.2:
version "2.4.0"
resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.0.tgz#4aa5de0723a231d1ee9121c40fdf663df73f61d7"
integrity sha512-awZAKrk3vN6CroQukBL+R9051a4R3zCZBlJm/HBfrSZ8iTpYix3VX1vU4mveiLpiwmOJT4wokTF9m6HUk4KqWQ==
Expand Down Expand Up @@ -14029,6 +14088,14 @@ restore-cursor@^2.0.0:
onetime "^2.0.0"
signal-exit "^3.0.2"

restore-cursor@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-3.1.0.tgz#39f67c54b3a7a58cea5236d95cf0034239631f7e"
integrity sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==
dependencies:
onetime "^5.1.0"
signal-exit "^3.0.2"

ret@~0.1.10:
version "0.1.15"
resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc"
Expand Down

1 comment on commit b3c4a75

@vercel
Copy link

@vercel vercel bot commented on b3c4a75 Mar 12, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.