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 sqlite support to the prisma adapter #3946

Merged
merged 2 commits into from
Mar 15, 2021
Merged
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
21 changes: 21 additions & 0 deletions .changeset/curly-chefs-invite.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
---
'@keystone-next/website': minor
'@keystone-next/fields': minor
'@keystone-next/fields-document': minor
'@keystone-next/keystone': minor
'@keystone-next/types': minor
'@keystone-next/adapter-prisma-legacy': minor
'@keystone-next/fields-legacy': minor
'@keystone-next/fields-auto-increment-legacy': minor
'@keystone-next/fields-cloudinary-image-legacy': minor
'@keystone-next/fields-color-legacy': minor
'@keystone-next/fields-markdown-legacy': minor
'@keystone-next/fields-oembed-legacy': minor
'@keystone-next/fields-unsplash-legacy': minor
'@keystone-next/fields-wysiwyg-tinymce-legacy': minor
'@keystone-next/keystone-legacy': minor
'@keystone-next/test-utils-legacy': minor
'@keystone-next/api-tests-legacy': minor
---

Added experimental support for Prisma + SQLite as a database adapter.
5 changes: 2 additions & 3 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -112,13 +112,11 @@ jobs:
POSTGRES_DB: test_db
ports:
- 5432:5432
env:
DATABASE_URL: 'postgres://keystone5:k3yst0n3@localhost:5432/test_db'
strategy:
fail-fast: false
matrix:
index: [0, 1, 2, 3, 4, 5, 6, 7, 8]
adapter: ['mongoose', 'knex', 'prisma_postgresql']
adapter: ['mongoose', 'knex', 'prisma_postgresql', 'prisma_sqlite']
steps:
- name: Checkout Repo
uses: actions/checkout@v2
Expand Down Expand Up @@ -163,6 +161,7 @@ jobs:
UNSPLASH_KEY: ${{ secrets.UNSPLASH_KEY}}
UNSPLASH_SECRET: ${{ secrets.UNSPLASH_SECRET}}
GOOGLE_API_KEY: ${{ secrets.GOOGLE_API_KEY }}
DATABASE_URL: ${{ matrix.adapter == 'prisma_sqlite' && 'file:./dev.db' || 'postgres://keystone5:k3yst0n3@localhost:5432/test_db' }}

non-api-tests:
name: Package Unit Tests
Expand Down
66 changes: 64 additions & 2 deletions docs-next/pages/apis/config.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export default config({
session: () => { /* ... */ },
graphql: { /* ... */ },
extendGraphqlSchema: { /* ... */ },
experimental: { /* ... */ },
});
```

Expand Down Expand Up @@ -56,8 +57,9 @@ import type { DatabaseConfig } from '@keystone-next/types';
The `db` config option configures the database used to store data in your Keystone system.
It has a TypeScript type of `DatabaseConfig`.
Keystone supports three different database types; **Prisma**, **PostgreSQL**, and **MongoDB**.
These database types are powered by their corresponding Keystone database adapter; `prisma_postgresql`, `knex`, and `mongoose`.
The `prisma_postgresql` adapter includes support for the **migrate** commands of the Keystone [command line](../guides/cli).
Prisma in turn support both **PostgreSQL** and **SQLite** databases.
These database types are powered by their corresponding Keystone database adapter; `prisma_postgresql`, `prisma_sqlite`, `knex`, and `mongoose`.
The `prisma_postgresql` and `prisma_sqlite` adapters includes support for the **migrate** commands of the Keystone [command line](../guides/cli).

All database adapters require the `url` argument, which defines the connection URL for your database.
They also all have an optional `onConnect` async function, which takes a [`KeystoneContext`](./context) object, and lets perform any actions you might need at startup, such as data seeding.
Expand Down Expand Up @@ -89,6 +91,43 @@ export default config({
});
```

### prisma_sqlite

Support for SQLite with Prisma is still in preview.
To use this option you must also set `{ experimental: { prismaSqlite: true } }`.

Advanced configuration:

- `enableLogging` (default: `false`): Enable logging from the Prisma client.
- `getPrismaPath` (default: `() => '.keystone/prisma'` ): Set the location of the generated Prisma schema and client.

The function for `getPrismaPath` is provided with the generated Prisma schema as a `string` in the `{ prismaSchema }` argument.

```typescript
export default config({
db: {
adapter: 'prisma_sqlite',
url: 'file:./keystone.db',
onConnect: async context => { /* ... */ },
// Optional advanced configuration
enableLogging: true,
getPrismaPath: ({ prismaSchema }) => '.prisma',
},
/* ... */
});
```

#### Limitations

The `prisma_sqlite` is not intended to be used in production systems, and has certain limitations:

- `document`: The `document` field type is not supported.
- `decimal`: The `decimal` field type is not supported.
- `timestamp`: The `timestamp` field type only supports times within the range `1970 - 2038`.
- `text`: The `text` field type does not support the advanced filtering operations `contains`, `starts_with`, `ends_with`, or case insensitive filtering.
- `autoincrement`: The `autoincrement` field type can only be used as an `id` field.
- `select`: Using the `dataType: 'enum'` will use a GraphQL `String` type, rather than an `Enum` type.

### knex

Advanced configuration:
Expand Down Expand Up @@ -260,4 +299,27 @@ export default config({

See the [schema extension guide](../guides/schema-extension) for more details on how to use `graphQLSchemaExtension()` to extend your GraphQL API.

## experimental

The following flags allow you to enable features which are still in preview.
These features are not guaranteed to work, and should be used with caution.

```typescript
import { config } from '@keystone-next/keystone/schema';

export default config({
experimental: {
enableNextJsGraphqlApiEndpoint: true,
prismaSqlite: true,
}
/* ... */
});
```

Options:

- `enableNextJsGraphqlApiEndpoint`: (coming soon)
- `prismaSqlite`: Enables the use of SQLite with Prisma.
This flag is required when setting `{ db: { adapter: 'prisma_sqlite' } }`.

export default ({ children }) => <Markdown>{children}</Markdown>;
6 changes: 5 additions & 1 deletion packages-next/fields-document/src/Implementation.js
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,11 @@ export class KnexDocumentInterface extends CommonDocumentInterface(KnexFieldAdap
export class PrismaDocumentInterface extends CommonDocumentInterface(PrismaFieldAdapter) {
constructor() {
super(...arguments);

if (this.listAdapter.parentAdapter.provider === 'sqlite') {
throw new Error(
`PrismaAdapter provider "sqlite" does not support field type "${this.field.constructor.name}"`
);
}
// Error rather than ignoring invalid config
// We totally can index these values, it's just not trivial. See issue #1297
if (this.config.isIndexed) {
Expand Down
1 change: 1 addition & 0 deletions packages-next/fields-document/src/test-fixtures.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export const exampleValue2 = () => [
export const supportsUnique = false;
export const fieldName = 'content';
export const subfieldName = 'document';
export const unSupportedAdapterList = ['prisma_sqlite'];

export const fieldConfig = () => ({ ___validateAndNormalize: x => x });

Expand Down
1 change: 1 addition & 0 deletions packages-next/fields/src/types/text/views/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ export const controller = (
Label({ label, value }) {
return `${label.toLowerCase()}: "${value}"`;
},
// FIXME: Not all of these options will work with prisma_sqlite
types: {
contains_i: {
label: 'Contains',
Expand Down
9 changes: 6 additions & 3 deletions packages-next/keystone/src/lib/applyIdFieldDefaults.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,12 @@ export function applyIdFieldDefaults(config: KeystoneConfig): KeystoneConfig['li
}
let idField =
config.lists[key].idField ??
{ mongoose: mongoId({}), knex: autoIncrement({}), prisma_postgresql: autoIncrement({}) }[
config.db.adapter
];
{
mongoose: mongoId({}),
knex: autoIncrement({}),
prisma_postgresql: autoIncrement({}),
prisma_sqlite: autoIncrement({}),
}[config.db.adapter];
idField = {
...idField,
config: {
Expand Down
13 changes: 13 additions & 0 deletions packages-next/keystone/src/lib/createKeystone.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,19 @@ export function createKeystone(
migrationMode,
prismaClient,
...db,
provider: 'postgresql',
});
} else if (db.adapter === 'prisma_sqlite') {
if (!config.experimental?.prismaSqlite) {
throw new Error(
'SQLite support is still experimental. You must set { experimental: { prismaSqlite: true } } in your config to use this feature.'
);
}
adapter = new PrismaAdapter({
getPrismaPath: () => path.join(dotKeystonePath, 'prisma'),
prismaClient,
...db,
provider: 'sqlite',
});
}
// @ts-ignore The @types/keystonejs__keystone package has the wrong type for KeystoneOptions
Expand Down
7 changes: 7 additions & 0 deletions packages-next/types/src/config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ export type KeystoneConfig = {
experimental?: {
/** Enables nextjs graphql api route mode */
enableNextJsGraphqlApiEndpoint?: boolean;
/** Enable Prisma+SQLite support */
prismaSqlite?: boolean;
};
};

Expand Down Expand Up @@ -65,6 +67,11 @@ export type DatabaseConfig = DatabaseCommon &
getPrismaPath?: (arg: { prismaSchema: any }) => string;
getDbSchemaName?: (arg: { prismaSchema: any }) => string;
}
| {
adapter: 'prisma_sqlite';
enableLogging?: boolean;
getPrismaPath?: (arg: { prismaSchema: any }) => string;
}
| { adapter: 'knex'; dropDatabase?: boolean; schemaName?: string }
| { adapter: 'mongoose'; mongooseOptions?: { mongoUri?: string } & ConnectOptions }
);
Expand Down
14 changes: 12 additions & 2 deletions packages/adapter-prisma/src/adapter-prisma.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ class PrismaAdapter extends BaseKeystoneAdapter {
// TODO: Should we default to 'public' or null?
if (this.provider === 'postgresql') {
return this.dbSchemaName ? `${this.url}?schema=${this.dbSchemaName}` : this.url;
} else if (this.provider === 'sqlite') {
return this.url;
}
}

Expand Down Expand Up @@ -272,6 +274,13 @@ class PrismaAdapter extends BaseKeystoneAdapter {
this._runPrismaCmd(`migrate reset --force --preview-feature`);
await runPrototypeMigrations(this._url(), this.prismaSchema, path.resolve(this.schemaPath));
}
} else if (this.provider === 'sqlite') {
const tables = await this.prisma.$queryRaw(
"SELECT name FROM sqlite_master WHERE type='table';"
);
for (const { name } of tables) {
await this.prisma.$queryRaw(`DELETE FROM "${name}";`);
}
} else {
this._runPrismaCmd(`migrate reset --force --preview-feature`);
}
Expand Down Expand Up @@ -437,11 +446,12 @@ class PrismaListAdapter extends BaseListAdapter {
if (search !== undefined && search !== '' && searchField) {
if (searchField.fieldName === 'Text') {
// FIXME: Think about regex
const mode = this.parentAdapter.provider === 'sqlite' ? undefined : 'insensitive';
if (!ret.where) {
ret.where = { [searchFieldName]: { contains: search, mode: 'insensitive' } };
ret.where = { [searchFieldName]: { contains: search, mode } };
} else {
ret.where = {
AND: [ret.where, { [searchFieldName]: { contains: search, mode: 'insensitive' } }],
AND: [ret.where, { [searchFieldName]: { contains: search, mode } }],
};
}
// const f = escapeRegExp;
Expand Down
6 changes: 5 additions & 1 deletion packages/fields-auto-increment/src/Implementation.js
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,11 @@ export class KnexAutoIncrementInterface extends KnexFieldAdapter {
export class PrismaAutoIncrementInterface extends PrismaFieldAdapter {
constructor() {
super(...arguments);

if (this.listAdapter.parentAdapter.provider === 'sqlite' && !this.field.isPrimaryKey) {
throw new Error(
`PrismaAdapter provider "sqlite" does not support field type "${this.field.constructor.name}"`
);
}
// Default isUnique to true if not specified
this.isUnique = typeof this.config.isUnique === 'undefined' ? true : !!this.config.isUnique;
this.isIndexed = !!this.config.isIndexed && !this.config.isUnique;
Expand Down
2 changes: 1 addition & 1 deletion packages/fields-auto-increment/src/test-fixtures.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export const skipCreateTest = false;
export const skipUpdateTest = true;

// `AutoIncrement` field type is not supported by `mongoose`. So, we need to filter it out while performing `API` tests.
export const unSupportedAdapterList = ['mongoose'];
export const unSupportedAdapterList = ['mongoose', 'prisma_sqlite'];

// Be default, `AutoIncrement` are read-only. But for `isRequired` test purpose, we need to bypass these restrictions.
export const fieldConfig = matrixValue => ({
Expand Down
2 changes: 1 addition & 1 deletion packages/fields-cloudinary-image/src/test-fixtures.skip.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,5 +78,5 @@ export const storedValues = () => [

export const supportedFilters = adapterName => [
'null_equality',
adapterName !== 'prisma_postgresql' && 'in_empty_null',
!['prisma_postgresql', 'prisma_sqlite'].includes(adapterName) && 'in_empty_null',
];
8 changes: 4 additions & 4 deletions packages/fields-color/src/test-fixtures.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,12 @@ export const storedValues = () => [
{ name: 'g', testField: null },
];

export const supportedFilters = () => [
export const supportedFilters = adapterName => [
'null_equality',
'equality',
'equality_case_insensitive',
adapterName !== 'prisma_sqlite' && 'equality_case_insensitive',
'in_empty_null',
'in_value',
'string',
'string_case_insensitive',
adapterName !== 'prisma_sqlite' && 'string',
adapterName !== 'prisma_sqlite' && 'string_case_insensitive',
];
8 changes: 4 additions & 4 deletions packages/fields-markdown/src/test-fixtures.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,12 @@ export const storedValues = () => [
{ name: 'g', testField: null },
];

export const supportedFilters = () => [
export const supportedFilters = adapterName => [
'null_equality',
'equality',
'equality_case_insensitive',
adapterName !== 'prisma_sqlite' && 'equality_case_insensitive',
'in_empty_null',
'in_value',
'string',
'string_case_insensitive',
adapterName !== 'prisma_sqlite' && 'string',
adapterName !== 'prisma_sqlite' && 'string_case_insensitive',
];
6 changes: 5 additions & 1 deletion packages/fields-oembed/src/Implementation.js
Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,11 @@ export class KnexOEmbedInterface extends CommonOEmbedInterface(KnexFieldAdapter)
export class PrismaOEmbedInterface extends CommonOEmbedInterface(PrismaFieldAdapter) {
constructor() {
super(...arguments);

if (this.listAdapter.parentAdapter.provider === 'sqlite') {
throw new Error(
`PrismaAdapter provider "sqlite" does not support field type "${this.field.constructor.name}"`
);
}
// Error rather than ignoring invalid config
// We totally can index these values, it's just not trivial. See issue #1297
if (this.config.isIndexed) {
Expand Down
3 changes: 2 additions & 1 deletion packages/fields-oembed/src/test-fixtures.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export const exampleValue2 = () => 'https://codesandbox.io';
export const supportsUnique = false;
export const fieldName = 'portfolio';
export const subfieldName = 'originalUrl';
export const unSupportedAdapterList = ['prisma_sqlite'];

const iframelyAdapter = new IframelyOEmbedAdapter({
apiKey: process.env.IFRAMELY_API_KEY || 'iframely_api_key',
Expand Down Expand Up @@ -47,5 +48,5 @@ export const storedValues = () => [

export const supportedFilters = adapterName => [
'null_equality',
adapterName !== 'prisma_postgresql' && 'in_empty_null',
!['prisma_postgresql'].includes(adapterName) && 'in_empty_null',
];
2 changes: 1 addition & 1 deletion packages/fields-unsplash/src/test-fixtures.skip.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,5 +49,5 @@ export const storedValues = () => [

export const supportedFilters = adapterName => [
'null_equality',
adapterName !== 'prisma_postgresql' && 'in_empty_null',
!['prisma_postgresql', 'prisma_sqlite'].includes(adapterName) && 'in_empty_null',
];
10 changes: 5 additions & 5 deletions packages/fields-wysiwyg-tinymce/src/test-fixtures.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,12 @@ export const storedValues = () => [
{ name: 'g', content: null },
];

export const supportedFilters = () => [
export const supportedFilters = adapterName => [
'null_equality',
'equality',
'equality_case_insensitive',
adapterName !== 'prisma_sqlite' && 'equality_case_insensitive',
'in_empty_null',
'in_equal',
'string',
'string_case_insensitive',
'in_value',
adapterName !== 'prisma_sqlite' && 'string',
adapterName !== 'prisma_sqlite' && 'string_case_insensitive',
];
6 changes: 5 additions & 1 deletion packages/fields/src/types/DateTime/Implementation.js
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,11 @@ export class KnexDateTimeInterface extends CommonDateTimeInterface(KnexFieldAdap
export class PrismaDateTimeInterface extends CommonDateTimeInterface(PrismaFieldAdapter) {
constructor() {
super(...arguments);

if (this.listAdapter.parentAdapter.provider === 'sqlite') {
throw new Error(
`PrismaAdapter provider "sqlite" does not support field type "${this.field.constructor.name}"`
);
}
this.utcPath = `${this.path}_utc`;
this.offsetPath = `${this.path}_offset`;
this.realKeys = [this.utcPath, this.offsetPath];
Expand Down
1 change: 1 addition & 0 deletions packages/fields/src/types/DateTime/test-fixtures.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export const exampleValue = () => '1990-12-31T12:34:56.789+01:23';
export const exampleValue2 = () => '2000-01-20T00:08:00.000+10:00';
export const supportsUnique = true;
export const fieldName = 'lastOnline';
export const unSupportedAdapterList = ['prisma_sqlite'];

export const getTestFields = () => ({ name: { type: Text }, lastOnline: { type } });

Expand Down
Loading