Giter Site home page Giter Site logo

prisma / prisma Goto Github PK

View Code? Open in Web Editor NEW
37.2K 233.0 1.4K 109.61 MB

Next-generation ORM for Node.js & TypeScript | PostgreSQL, MySQL, MariaDB, SQL Server, SQLite, MongoDB and CockroachDB

Home Page: https://www.prisma.io

License: Apache License 2.0

TypeScript 98.51% JavaScript 1.37% Shell 0.06% TSQL 0.01% Dockerfile 0.06% Batchfile 0.01%
prisma orm database typescript javascript nodejs mariadb mysql postgresql sqlserver

prisma's People

Contributors

0xflotus avatar 2color avatar aqrln avatar danstarns avatar druue avatar ejoebstl avatar errorname avatar garrensmith avatar janpio avatar jasonkuhrt avatar jkomyno avatar jolg42 avatar kuldar avatar matthewmueller avatar miguelff avatar millsp avatar nikolasburk avatar pantharshit00 avatar prisma-bot avatar rafaelkr avatar renovate-bot avatar renovate[bot] avatar sampolahtinen avatar schickling avatar sevinf avatar steebchen avatar timsuchanek avatar tomhoule avatar weakky avatar williamluke4 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

prisma's Issues

Support โ€œUnit of Workโ€ pattern

Problem

Whenever performing operations on objects in your application which get directly persisted to the database can involve:

  • slow performance when a lot of operations are made (lots of small database calls)
  • complexity dealing with potential read inconsistencies during a long transaction

Solution

A solution to this problem is referred to as a โ€œUnit of Workโ€ which proposes to keep track of changes done within a transaction that can affect the database, and taking care of figuring out the changes which need to be done at the database level as a result.

Details

See RFC

See also internal notes

Photon generation fails for a datamodel with back relations

model Todo {
  id: Int @id
  text: String
  author: Owner @relation(name: "AuthorTodo")
  delegatedTo: Owner? @relation(name: "DelegatedToTodo")
}

model Owner {
  id: Int @id
  name: String?
  todos: Todo[] @relation(name: "AuthorTodo")
}

Fails with the following trace:

$ [todo-matrix]$ yarn prisma2 generate
yarn run v1.15.2
$ /Users/divyendusingh/Documents/prisma/support/feature/todo-matrix/node_modules/.bin/prisma2 generate

Generating Photon to /Users/divyendusingh/Documents/prisma/support/feature/todo-matrix/node_modules/@generated/photon
Engine path: /Users/divyendusingh/Documents/prisma/support/feature/todo-matrix/node_modules/@prisma/prototype/runtime/prisma
/Users/divyendusingh/Documents/prisma/support/feature/todo-matrix/node_modules/@prisma/prototype/build/index.js:17018
                    throw new Engine_1.PhotonError(`Error in query engine: ` + this.errorLogs, undefined, undefined, this.errorLogs);
                    ^

PhotonError: Error in query engine: thread 'main' panicked at 'Did not find a relation for those for model Owner and field todo', src/libcore/option.rs:1038:5
note: Run with `RUST_BACKTRACE=1` environment variable to display a backtrace.

    at ChildProcess.<anonymous> (/Users/divyendusingh/Documents/prisma/support/feature/todo-matrix/node_modules/@prisma/prototype/build/index.js:17018:27)
    at ChildProcess.emit (events.js:200:13)
    at Process.ChildProcess._handle.onexit (internal/child_process.js:272:12) {
  query: undefined,
  error: undefined,
  logs: "thread 'main' panicked at 'Did not find a relation for those for " +
    "model Owner and field todo', src/libcore/option.rs:1038:5\nnote: Run " +
    'with `RUST_BACKTRACE=1` environment variable to display a backtrace.\n',
  isPanicked: undefined

`prisma` should list the dev command

The output should list the dev command:

โ—ญ Prisma makes your data easy (https://prisma.io)

Usage

  $ prisma [command]

Commands

      lift   Migrate your datamodel
   convert   Converts a datamodel 1 to datamodel 2
introspect   Get the datamodel of your database
  generate   Generate Photon
       new   Setup Prisma for your app
      seed   Seed data into your database

Examples

  Initialize files for a new Prisma service
  $ prisma new

  Deploy service changes (or new service)
  $ prisma lift

`prisma init` improvements

  • Select / radio buttons should be toggleable via Space instead of enter
  • hide password
  • cycle through options

'prisma2 init' with mysql leads to react error on MySQL8 because of authentication problem

Describe the bug

React will try to recreate this component tree from scratch using the error boundary you provided, App.
Warning: App: Error boundaries should implement getDerivedStateFromError(). In that method, return a state update to display an error message or fallback UI.
The above error occurred in one of your React components:
in Unknown
in div (created by Box)
in Box
in div (created by Box)
in Box
in div (created by Box)
in Box
in Unknown
in div (created by Box)
in Box
in Unknown
in App

React will try to recreate this component tree from scratch using the error boundary you provided, App.
Warning: App: Error boundaries should implement getDerivedStateFromError(). In that method, return a state update to display an error message or fallback UI.

To Reproduce
Steps to reproduce the behavior:

  1. Run 'prisma2 init'
  2. Select Mysql
  3. Enter your data
  4. Select Connect
  5. See error

Expected behavior
The database select screen

Versions (please complete the following information):

  • Connector: MySQL
  • Prisma Server: 0.0.84
  • OS: macOS 10.14.3

`@default` doesnt work for enum

I have this datamodel:

model User {
  id: Int @id
  name: String
  role: Role @default(USER)
  posts: Post[]
}

model Post {
  id: Int @id
  title: String
  author: User
}

enum Role {
  USER
  ADMIN
}

Because role has a default value, it can be omitted in the Photon API call:

import { Photon } from '@generated/photon'

const photon = new Photon()

async function main() {
  await photon.connect()

  const result = await photon.users.create({
    data: { name: 'Niko' }
  })

  console.log(result)
  photon.close()
}

main().catch(e => {
  console.error(e)
  photon.close()
})

When I execute this code, I get the following error:

$ yarn start
yarn run v1.13.0
$ ts-node main.ts
PhotonError: [
  {
    "error": "ConnectorError(NullConstraintViolation { field_name: \"User.role\" })"
  }
]
    at NodeEngine.handleErrors (/Users/nikolasburk/Desktop/prisma2-test/node_modules/@generated/photon/runtime/index.js:1308:15)
    at /Users/nikolasburk/Desktop/prisma2-test/node_modules/@generated/photon/runtime/index.js:1283:37
    at processTicksAndRejections (internal/process/task_queues.js:89:5) {
  query: 'mutation {\n  createUser(data: {\n    ' +
    'name: "Niko"\n  }) {\n    id\n    name\n  }\n' +
    '}',
  error: [
    {
      error: 'ConnectorError(NullConstraintViolation { field_name: "User.role" })'
    }
  ],
  logs: '[query-engine/core/src/builders/mutations/root.rs:179] &model.models() = ' +
    '[\n    Model {\n        name: "User",\n        stable_identifier: "",\n      ' +
    '  is_embedded: false,\n        manifestation: None,\n        fields: ' +
    'OnceCell {\n            once: Once {\n                state: Done\n         ' +
    '   },\n            value: UnsafeCell\n        },\n        ' +
    'internal_data_model: #InternalDataModelWeakRef#\n    },\n    Model {\n      ' +
    '  name: "Post",\n        stable_identifier: "",\n        is_embedded: ' +
    'false,\n        manifestation: None,\n        fields: OnceCell {\n          ' +
    '  once: Once {\n                state: Done\n            },\n            ' +
    'value: UnsafeCell\n        },\n        internal_data_model: ' +
    '#InternalDataModelWeakRef#\n    }\n]\n' +
    '[query-engine/connectors/sql-connector/src/database/sqlite.rs:54] ' +
    'visitor::Sqlite::build(q) = (\n    "INSERT INTO `migration_engine`.`User` ' +
    '(`name`) VALUES (?)",\n    [\n        Text(\n            "Niko"\n        )\n  ' +
    '  ]\n)\n',
  isPanicked: false
}
โœจ  Done in 1.25s.

Support for database seeding

Problem

Seeding a database is a process in which a database (usually empty) is populated with an initial set of data. This is often an automated process that is executed upon the initial setup of an application. The data can be dummy data or necessary data such as an initial administrator account.

Use cases:

  • testing (most common): populate with data required used in automated tests
  • development: populate data useful for development and manual testing
  • production: create data that is required for the app to work in a production environment

Considerations:

  • the seeds tend to be tied to the lifecycle of the schema, evolving the schema will often trigger changes to the seeds
  • the database will sometimes need to be reset and re-seeded, to remove stale/dirty data
  • seeding production is probably a different problem than seeding dev/test environments. Frameworks tend to provide different guidance for these scenarios.

Prior art

Prisma1 has a CLI command seed, which reads the configuration from the prisma.yml file. It can execute a seed script written JS.

Possible Solutions

  • Fixtures - static files containing the data, i.e. CSV, JSON, YAML
  • Seed scripts - code written by the user to populate data using the existing Client API
  • ...

The postinstall command (prisma2 generate) fails on Windows, but works fine on Mac

I installed the SQLite Graphql Boilerplate from the command line and everything works fine until I do the "npm install" which triggers the postinstall script "prisma2 generate".
On Mac I do the exact same with no issues but on Windows 10 I get this error:

Error: write EPIPE
npm ERR! code ELIFECYCLE
npm ERR! errno 1
npm ERR! [email protected] postinstall: 'prisma2 generate'
npm ERR! Exit status 1
npm ERR!
npm ERR! Failed at the [email protected] postinstall script.

[Prisma 2] Upserting User in PostgreSQL causes panick and ConnectorError

Describe the bug
Error dump:

Error: Error in Photon: 
[
  {
    "error": "ConnectorError(QueryError(Error { kind: ToSql, cause: Some(WrongType(Type(Text))) }\n\nstack backtrace:\n   0: failure::backtrace::internal::InternalBacktrace::new::h84e0252f893b7b0e (0x560f176c75f0)\n   1: <failure::backtrace::Backtrace as core::default::Default>::default::hd8f6c25d93ae9c26 (0x560f176c77a0)\n   2: <sql_connector::error::SqlError as core::convert::From<tokio_postgres::error::Error>>::from::h01dd77df417c902b (0x560f170fa307)\n   3: sql_connector::database::postgresql::<impl sql_connector::transactional::Transaction for postgres::transaction::Transaction>::write::h85ba92f2a437f0c3 (0x560f170cee33)\n   4: sql_connector::transactional::Transaction::insert::hd87cbd91efa25ae3 (0x560f16dbb674)\n   5: sql_connector::transactional::mutaction_executor::create::execute::h75940cff21c312d7 (0x560f1710e305)\n   6: sql_connector::transactional::mutaction_executor::<impl connector::database_mutaction_executor::DatabaseMutactionExecutor for sql_connector::database::SqlDatabase<T>>::execute::{{closure}}::create::hba6453f84f903c89 (0x560f17144268)\n   7: <sql_connector::database::postgresql::PostgreSql as sql_connector::transactional::Transactional>::with_transaction::ha343e63fe0096da4 (0x560f16e2500e)\n   8: sql_connector::transactional::mutaction_executor::<impl connector::database_mutaction_executor::DatabaseMutactionExecutor for sql_connector::database::SqlDatabase<T>>::execute::h76670b1c1482e4fd (0x560f16d83343)\n   9: core::executor::write::WriteQueryExecutor::exec_one::h81acc6e0a7d1cf87 (0x560f171e2382)\n  10: core::executor::write::WriteQueryExecutor::execute::h4de4034d22720586 (0x560f171e1c5e)\n  11: core::executor::Executor::exec_all::h019661aa466552b2 (0x560f171e2e45)\n  12: <prisma::req_handlers::graphql::GraphQlRequestHandler as prisma::req_handlers::RequestHandler>::handle::hd901880e604ba7f3 (0x560f16dff33f)\n  13: prisma::http_handler::h574a96a3df5ad7b2 (0x560f16e46da9)\n  14: <F as actix_web::with::FnWith<T,R>>::call_with::h8921e8f777cf5423 (0x560f16e521be)\n  15: <actix_web::with::WithHandlerFut<T,S,R> as futures::future::Future>::poll::hd0405f55c326258c (0x560f16e5702e)\n  16: actix_web::pipeline::PipelineState<S,H>::poll::h05fe08bd6589e38c (0x560f16d5afe3)\n  17: <actix_web::pipeline::Pipeline<S,H> as actix_web::server::handler::HttpHandlerTask>::poll_io::hf988cbba155aa2c2 (0x560f16d49770)\n  18: actix_web::server::h1::Http1Dispatcher<T,H>::poll_handler::h0e2de2e2f0e85c3b (0x560f16dd599c)\n  19: actix_web::server::h1::Http1Dispatcher<T,H>::poll::h7c512f9208c70f65 (0x560f16e15361)\n  20: <actix_web::server::channel::HttpChannel<T,H> as futures::future::Future>::poll::h3fda956b624b7067 (0x560f16e14079)\n  21: <actix_web::server::channel::HttpChannel<T,H> as futures::future::Future>::poll::h3fda956b624b7067 (0x560f16e15011)\n  22: <actix_net::service::and_then::AndThenFuture<A,B> as futures::future::Future>::poll::hb1978ee7b706a005 (0x560f16ea638d)\n  23: futures::future::chain::Chain<A,B,C>::poll::h4dcce436324a17dd (0x560f16ea07a9)\n  24: futures::task_impl::std::set::hb6a916396ab13c6e (0x560f16f8d892)\n  25: futures::task_impl::Spawn<T>::poll_future_notify::ha13909c8cd98d4fe (0x560f16fa8296)\n  26: tokio_current_thread::CurrentRunner::set_spawn::hcab1a55dd9f5caaf (0x560f16f885a4)\n  27: tokio_current_thread::scheduler::Scheduler<U>::tick::h1f8b6bf060538e24 (0x560f16f94cfa)\n  28: tokio_current_thread::Entered<P>::block_on::hd80cde5490457c04 (0x560f16f88f5a)\n  29: std::thread::local::LocalKey<T>::with::h66e6fccf026a2b83 (0x560f16f9f768)\n  30: std::thread::local::LocalKey<T>::with::h7b6b9e8e24d2b1ae (0x560f16fa0598)\n  31: std::thread::local::LocalKey<T>::with::h4efe62e0c4ca0fb4 (0x560f16f9ea5f)\n  32: std::thread::local::LocalKey<T>::with::h1aefc504c0a5187d (0x560f16f9de55)\n  33: tokio::runtime::current_thread::runtime::Runtime::block_on::h573894b7abc600ef (0x560f16f8c7b0)\n  34: std::sys_common::backtrace::__rust_begin_short_backtrace::h027e6ea9ab4c2090 (0x560f16fa5cb7)\n  35: std::panicking::try::do_call::hbc22e41bfc445835 (0x560f16f8ad66)\n  36: __rust_maybe_catch_panic (0x560f176fb18a)\n             at src/libpanic_unwind/lib.rs:87\n  37: core::ops::function::FnOnce::call_once{{vtable.shim}}::hf39704692fc5ef53 (0x560f16fa6556)\n  38: call_once<(),FnBox<()>> (0x560f176e15bf)\n             at /rustc/3c235d5600393dfe6c36eeed34042efad8d4f26e/src/liballoc/boxed.rs:702\n  39: call_once<(),alloc::boxed::Box<FnBox<()>>> (0x560f176fa5a0)\n             at /rustc/3c235d5600393dfe6c36eeed34042efad8d4f26e/src/liballoc/boxed.rs:702\n      start_thread\n             at src/libstd/sys_common/thread.rs:14\n      thread_start\n             at src/libstd/sys/unix/thread.rs:80\n  40: start_thread (0x7fdd3101f57f)\n  41: __clone (0x7fdd30f32f13)\n  42: <unknown> (0x0)))"
  }
]
    at PhotonFetcher._callee$ (/home/jenkins/code/misc/prisma2_test/generated/photon/index.js:35:23)
    at tryCatch (/home/jenkins/code/misc/prisma2_test/node_modules/regenerator-runtime/runtime.js:45:40)
    at Generator.invoke [as _invoke] (/home/jenkins/code/misc/prisma2_test/node_modules/regenerator-runtime/runtime.js:271:22)
    at Generator.prototype.(anonymous function) [as throw] (/home/jenkins/code/misc/prisma2_test/node_modules/regenerator-runtime/runtime.js:97:21)
    at rejected (/home/jenkins/code/misc/prisma2_test/generated/photon/index.js:5:47)
    at processTicksAndRejections (internal/process/task_queues.js:86:5)

To Reproduce
Steps to reproduce the behavior:

  1. Create a basic Prisma 2 project using prisma2 init and configure with PostgreSQL credentials
  2. Create this datamodel:
datasource db {
  provider = "postgres"
  url      = "postgresql://test:test@localhost:5432/test?schema=public"
}

generator js {
  provider = "photonjs"
  output    = "./generated/photon"
}

model User {
  id    String @default(uuid()) @id
  email String @unique
  name  String
}
  1. Run initial deployment of schema / migration
  2. Attempt to query photon.users() - works fine (so we are connected)
  3. Attempt to create User
ctx.photon.users.upsert({
    create: {
        email: '[email protected]',
        name: 'Test Testersson'
    },
    update: {
        name: 'Test Terstersson'
    },
    where: {
        email: '[email protected]'
    }
});
  1. This should cause the provided error mentioned above

Expected behavior
I should be able to upsert a user

Versions (please complete the following information):

  • Connector: Postgres
  • Prisma Server: No server
  • prisma2 CLI: prisma2/0.0.87 (Linux x86_64) node-v11.15.0
  • OS: Arch Linux w/Linux v5.1.9

Zeit binary compatibility

Zeit/now Version 2 runs on AWS lambdas with the ami ami amzn-ami-hvm-2017.03.rc-1.20170327-x86_64-gp2.

A binary compiled on that AMI links against the following binaries:

# ldd migration-engine
linux-vdso.so.1 =>  (0x00007ffd757e8000)
libssl.so.10 => /usr/lib64/libssl.so.10 (0x00007f8fef798000)
libcrypto.so.10 => /lib64/libcrypto.so.10 (0x00007f8fef3b0000)
libdl.so.2 => /lib64/libdl.so.2 (0x00007f8fef1ab000)
librt.so.1 => /lib64/librt.so.1 (0x00007f8feefa3000)
libpthread.so.0 => /lib64/libpthread.so.0 (0x00007f8feed87000)
libgcc_s.so.1 => /lib64/libgcc_s.so.1 (0x00007f8feeb70000)
libc.so.6 => /lib64/libc.so.6 (0x00007f8fee7ac000)
/lib64/ld-linux-x86-64.so.2 (0x000056492d6bf000)
libm.so.6 => /lib64/libm.so.6 (0x00007f8fee4aa000)
libgssapi_krb5.so.2 => /usr/lib64/libgssapi_krb5.so.2 (0x00007f8fee25b000)
libkrb5.so.3 => /usr/lib64/libkrb5.so.3 (0x00007f8fedf74000)
libcom_err.so.2 => /usr/lib64/libcom_err.so.2 (0x00007f8fedd71000)
libk5crypto.so.3 => /usr/lib64/libk5crypto.so.3 (0x00007f8fedb3e000)
libz.so.1 => /lib64/libz.so.1 (0x00007f8fed928000)
libkrb5support.so.0 => /usr/lib64/libkrb5support.so.0 (0x00007f8fed718000)
libkeyutils.so.1 => /lib64/libkeyutils.so.1 (0x00007f8fed515000)
libresolv.so.2 => /lib64/libresolv.so.2 (0x00007f8fed2fb000)
libselinux.so.1 => /usr/lib64/libselinux.so.1 (0x00007f8fed0d9000)

The specific package versions are found here:

openssl-1.0.1k
glibc-2.17

The binary can be built on an instance with the mentioned ami using the following commands:

yum install git curl
git clone https://github.com/prisma/prisma.git
curl https://sh.rustup.rs -sSf | sh
source $HOME/.cargo/env
yum install openssl-devel
yum groupinstall 'Development Tools'
cd prisma/server/prisma-rs
git checkout alpha
cargo build --release

`prisma dev` can't recover from certain rust errors

Datamodel

datasource db {
  provider = "sqlite"
  url      = "file:dev.db"
  default  = true
}

generator photon {
  provider = "photon-js"
  output   = "./generated/photon"
}

model Blog {
  id        Int      @id
  name      String
  viewCount Int
  posts     Post[]
  authors   Author[]
}

model Author {
  id    Int     @id
  name  String?
  posts Post[]
  blog  Blog
}

model Post {
  id    Int      @id
  title String
  tags  String[]
  blog  Blog
  comments Comment[]
}

model Comment {
  id Int @id
  comment String
}

model Comment {
  id Int @id
  comment String
}

Two Comment is definitely an error, but in development often you just want to copy and paste the previous one and override from there. This is probably not an issue with Rust, but rather we need to gracefully recover from these situations in prisma dev. We may either need to automatically restart the rust engine or not kill the rust engine when these sorts of validation errors happen.

Error

thread 'main' panicked at 'Invariant violation: Inserted key Comment twice, this is a bug and invalidates Weak references. ObjectType { name: "Comment", fields: #Fields Cell# }', query-engine/core/src/schema/builders/cache.rs:62:26
note: Run with `RUST_BACKTRACE=1` environment variable to display a backtrace.
/sandbox/node_modules/prisma2/build/index.js:493044
        throw ex;
        ^

SyntaxError: Unexpected end of JSON input
    at JSON.parse (<anonymous>)
    at child.stdout.d (/sandbox/node_modules/prisma2/build/index.js:96278:25)
    at DestroyableTransform.flush [as _flush] (/sandbox/node_modules/prisma2/build/index.js:96293:9)
    at DestroyableTransform.prefinish (/sandbox/node_modules/prisma2/build/index.js:173352:10)
    at DestroyableTransform.emit (events.js:198:13)
    at prefinish (/sandbox/node_modules/prisma2/build/index.js:122896:14)
    at finishMaybe (/sandbox/node_modules/prisma2/build/index.js:122905:5)
    at endWritable (/sandbox/node_modules/prisma2/build/index.js:122918:3)
    at DestroyableTransform.module.exports.3888.Writable.end (/sandbox/node_modules/prisma2/build/index.js:122856:22)
    at Socket.onend (_stream_readable.js:655:10)

/cc @Weakky

Neo4j support

This feature requests serves as a central place to discuss development and progress for the Neo4j connector.

Refactor generator handling

Right now generators are hardcoded and shipped with the prisma2 CLI. We should open up the generator API to let users bring their own generators (e.g. for prisma-test-utils) and nexus-prisma).

Also see: https://gnorm.org

When connecting to existing MySQL, prisma2 init wizard ends at the introspect step and does not select prisma tools ( Photon, Lift ) nor generate any boilerplate.

After successfully introspecting MySQL DB, the init wizard terminates without the select Prisma Tools (Photon, Lift), Language, and Boilerplate steps.

The wizard prints the following message after introspecting:

โœ” Your all set!

  โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

  We created the following files for you:

  newapp/prisma/project.prisma    The datamodel describes your database schema

  โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

  Run the following commands to start developing

  $ cd newapp
  $ prisma2 dev

  Learn more about using Photon and Lift at
  https://www.prisma.io/docs/...

Of course, attempting to run prisma2 dev as directed fails with the following message:

Error
   Error: Error in lift engine for rpc applyMigration:
     [/root/.cargo/git/checkouts/prisma-query-a8c45647247f5d6d/5a9
5ce6/src/connector/mysql.rs:83] self.pool.state() = State {
       connections: 10,
       idle_connections: 10,
   }
   [migration-engine/connectors/sql-migration-connector/src/lib.rs
:273] format!("CREATE SCHEMA IF NOT EXISTS `{}` DEFAULT CHARACTER 
SET latin1;" , &
           self . schema_name) = "CREATE SCHEMA IF NOT EXISTS `tsh
irtify` DEFAULT CHARACTER SET latin1;"
   [/root/.cargo/git/checkouts/prisma-query-a8c45647247f5d6d/5a95c
e6/src/connector/mysql.rs:83] self.pool.state() = State {
       connections: 10,
       idle_connections: 10,
   }
   [migration-engine/connectors/sql-migration-connector/src/sql_mi
gration_persistence.rs:38] m.make_from(barrel_variant) = "CREATE T
ABLE tshirtify._Migration (revision INTEGER NOT NULL AUTO_INCREMEN
T PRIMARY KEY, name TEXT NOT NULL, datamodel TEXT NOT NULL, status
 TEXT NOT NULL, applied INTEGER NOT NULL, rolled_back INTEGER NOT 
NULL, datamodel_steps TEXT NOT NULL, database_migration TEXT NOT N
ULL, errors TEXT NOT NULL, started_at datetime(3) NOT NULL, finish
ed_at datetime(3));"
   [/root/.cargo/git/checkouts/prisma-query-a8c45647247f5d6d/5a95c
e6/src/connector/mysql.rs:83] self.pool.state() = State {
       connections: 10,
       idle_connections: 10,
   }

I hope I'm not missing anything.

API for interactive transactions with dependencies between write-operations

There already is a GitHub issue asking for a way to submit multiple mutations within the same HTTP request where all mutations are executed as one transaction.

However, this feature does not allow for creating a transaction where the mutations depend on each other. Here is an example where the second operation depends on the first:

prisma.transaction(async tx => {
  const user = await tx.createUser({
    name,
  })
  // compute some stuff
  await tx.updateOrder(user.id)
  await tx.commit()
})

This is currently not possible with the Prisma API as it would require having a long-running connection where the results of the operations are sent back and forth between Prisma and the database.

It should be considered whether it is helpful to add such an API to Prisma, or whether abstractions like nested mutations and the requested transactional execution of multiple independent transactions are the better approach for Prisma.

Discuss inflection

Consider backward compatibility / breaking changes (e.g. regarding introspection)

Retrieve pagination information

Problem

Prisma client currently does not provide the ability to retrieve pagination information about a certain query, which prevents developers from accounting for a result setโ€™s size in a variety of cases (display paginated result lists, optimizing batch sizeโ€ฆ)

Proposed solution

Add support for a $withPageInfo() to functions returning collections (findMany ,1-n relations, โ€ฆ) returning pagination information alongside the results.

The pagination information could be described as:

type PageInfo<T> = {
  data: T[] // The query result
  hasNext: boolean // Indication that the cursor can move to the next page
  hasPrev: boolean // Indication that the cursor can move to the previous page
}

Example usage:

const samsPostsWithPageInfo: PageInfo<Post> = await prisma.users
  .findOne('sams-id')
  .posts({ first: 50 })
  .$withPageInfo()
console.log(samsPostsWithPageInfo.hasNext, samsPostsWithPageInfo.hasPrev, samsPostsWithPageInfo.data

For non SQLite databases, the init flow does not ask for language

In the current workflow, CLI always introspects on non-SQLite databases and does not introspect on SQLite.

We used this classification to provide template downloading for SQLite and introspection for other databases.

The introspection workflow currently does not ask for language, so, we need to print files for either TS/JS or not print anything but prisma.project file.

This needs further spec.

Project file layout

I suggest the following file/folder layout for a Prisma project:

prisma/
  migrations/
    20190608154035-base-models/
      datamodel.prisma
      README.md
      steps.json
    lift.lock
  generated/
    photon/
      index.js
      index.d.ts
    .gitignore
  project.prisma

Collection of QueryEngine related errors

Same datamodel/environment as this demo but modified the main.ts file to

import { Photon } from "@generated/photon";

const photon = new Photon();

async function main() {
  await photon.connect();

  const result = await photon.blogs.upsert({
    where: {
      id: 1
    },
    create: {
      name: "Test 1",
      viewCount: 1,
      posts: {
        create: {
          title: "X1"
        }
      }
    },
    update: {
      name: "Test 2",
      viewCount: 2,
      posts: {
        create: {
          title: "X2"
        }
      }
    },
    select: {
      id: true,
      posts: {
        select: {
          id: true
        },
        first: 10
      }
    }
  });

  console.log(result);
  photon.close();
}

main().catch(e => {
  console.error(e);
  photon.close();
});

Running this yields the following error:

$ [p2]$ yarn ts-node main.ts                                                                                                                                     [1/1837]
yarn run v1.15.2
$ /Users/divyendusingh/Documents/prisma/support/feature/p2/node_modules/.bin/ts-node main.ts

{ Error: [
  {
    "error": "ConnectorError(NullConstraintViolation { field_name: \"Blog.name\" })"
  }
]
    at NodeEngine.handleErrors (/Users/divyendusingh/Documents/prisma/support/feature/p2/node_modules/@generated/photon/runtime/index.js:1305:15)
    at response.json.then.result (/Users/divyendusingh/Documents/prisma/support/feature/p2/node_modules/@generated/photon/runtime/index.js:1280:37)
    at process._tickCallback (internal/process/next_tick.js:68:7)
  query:
   'mutation {\n  upsertBlog(\n    where: {\n      id: 1\n    }\n    create: {\n      name: "Test 1"\n      viewCount: 1\n      posts: {\n        create: [\n          {\
n            title: "X1"\n          }\n        ]\n      }\n    }\n    update: {\n      name: "Test 2"\n      viewCount: 2\n      posts: {\n        create: [\n          {
\n            title: "X2"\n          }\n        ]\n      }\n    }\n  ) {\n    id\n    name\n    viewCount\n    posts(first: 10) {\n      id\n      title\n      tags\n
 }\n  }\n}',
  error:
   [ { error:
        'ConnectorError(NullConstraintViolation { field_name: "Blog.name" })' } ],
  logs:
   '[query-engine/core/src/schema/builders/input_type_builder/mod.rs:41] format!("{}{}{}Input" , model_name , input_object_name , f . name) = "PostCreatetagsInput"\n[que
ry-engine/core/src/schema/builders/input_type_builder/mod.rs:41] format!("{}{}{}Input" , model_name , input_object_name , f . name) = "PostUpdatetagsInput"\n[query-engin
e/core/src/schema/builders/input_type_builder/mod.rs:41] format!("{}{}{}Input" , model_name , input_object_name , f . name) = "PostUpdatetagsInput"\n[query-engine/core/s
rc/schema/builders/input_type_builder/mod.rs:41] format!("{}{}{}Input" , model_name , input_object_name , f . name) = "PostCreatetagsInput"\n[query-engine/core/src/schem
a/builders/input_type_builder/mod.rs:41] format!("{}{}{}Input" , model_name , input_object_name , f . name) = "PostUpdatetagsInput"\n[query-engine/core/src/schema/builde
rs/input_type_builder/mod.rs:41] format!("{}{}{}Input" , model_name , input_object_name , f . name) = "PostUpdatetagsInput"\n[query-engine/core/src/builders/mutations/ro
ot.rs:179] &model.models() = [\n    Model {\n        name: "Blog",\n        stable_identifier: "",\n        is_embedded: false,\n        manifestation: None,\n        fi
elds: OnceCell {\n            once: Once {\n                state: Done\n            },\n            value: UnsafeCell\n        },\n        internal_data_model: #Interna
lDataModelWeakRef#\n    },\n    Model {\n        name: "Author",\n        stable_identifier: "",\n        is_embedded: false,\n        manifestation: None,\n        fiel
ds: OnceCell {\n            once: Once {\n                state: Done\n            },\n            value: UnsafeCell\n        },\n        internal_data_model: #InternalD
ataModelWeakRef#\n    },\n    Model {\n        name: "Post",\n        stable_identifier: "",\n        is_embedded: false,\n        manifestation: None,\n        fields:
OnceCell {\n            once: Once {\n                state: Done\n            },\n            value: UnsafeCell\n        },\n        internal_data_model: #InternalDataM
odelWeakRef#\n    }\n]\n[query-engine/connectors/sql-connector/src/database/sqlite.rs:63] visitor::Sqlite::build(q) = (\n    "SELECT `migration_engine`.`Blog`.`id` FROM
`migration_engine`.`Blog` WHERE `migration_engine`.`Blog`.`id` = ?",\n    [\n        Integer(\n            1\n        )\n    ]\n)\n[query-engine/connectors/sql-connector
/src/database/sqlite.rs:54] visitor::Sqlite::build(q) = (\n    "INSERT INTO `migration_engine`.`Blog` DEFAULT VALUES",\n    []\n)\n',
  isPanicked: false }
โœจ  Done in 1.33s.

createdAt with @default(now()) does not work

Failed applyMigration at 2019-06-22T12:57:30.300Z

RPC Input One Line

{"id":1,"jsonrpc":"2.0","method":"applyMigration","params":{"projectInfo":"","force":false,"migrationId":"20190622144536-init","steps":[{"stepType":"CreateModel","name":"User","embedded":false},{"stepType":"CreateModel","name":"Post","embedded":false},{"stepType":"CreateField","model":"User","name":"id","type":{"Base":"String"},"arity":"required","isUnique":true,"id":{"strategy":"Auto","sequence":null},"default":{"Expression":["cuid","String",[]]}},{"stepType":"CreateField","model":"User","name":"email","type":{"Base":"String"},"arity":"required","isUnique":true},{"stepType":"CreateField","model":"User","name":"name","type":{"Base":"String"},"arity":"optional","isUnique":false},{"stepType":"CreateField","model":"User","name":"posts","type":{"Relation":{"to":"Post","to_fields":[],"name":"PostToUser","on_delete":"None"}},"arity":"list","isUnique":false},{"stepType":"CreateField","model":"Post","name":"id","type":{"Base":"String"},"arity":"required","isUnique":true,"id":{"strategy":"Auto","sequence":null},"default":{"Expression":["cuid","String",[]]}},{"stepType":"CreateField","model":"Post","name":"createdAt","type":{"Base":"DateTime"},"arity":"required","isUnique":false,"default":{"Expression":["now","DateTime",[]]}},{"stepType":"CreateField","model":"Post","name":"updatedAt","type":{"Base":"DateTime"},"arity":"required","isUnique":false},{"stepType":"CreateField","model":"Post","name":"published","type":{"Base":"Boolean"},"arity":"required","isUnique":false},{"stepType":"CreateField","model":"Post","name":"title","type":{"Base":"String"},"arity":"required","isUnique":false},{"stepType":"CreateField","model":"Post","name":"content","type":{"Base":"String"},"arity":"optional","isUnique":false},{"stepType":"CreateField","model":"Post","name":"author","type":{"Relation":{"to":"User","to_fields":["id"],"name":"PostToUser","on_delete":"None"}},"arity":"optional","isUnique":false}],"sourceConfig":"datasource db {\n  provider = \"mysql\"\n  url      = \"mysql://root:123456789@localhost:3306/prisma\"\n  default  = true\n}\n\ngenerator photon {\n  provider = \"photonjs\"\n  output   = \"node_modules/@generated/photon\"\n}\n\ngenerator nexus_prisma {\n  provider = \"nexus-prisma\"\n  output   = \"node_modules/@generated/nexus-prisma\"\n}\n\nmodel User {\n  id    String  @default(cuid()) @id @unique\n  email String  @unique\n  name  String?\n  posts Post[]\n}\n\nmodel Post {\n  id        String   @default(cuid()) @id @unique\n  createdAt DateTime @default(now())\n  updatedAt DateTime @updatedAt\n  published Boolean\n  title     String\n  content   String?\n  author    User?\n}"}}

RPC Input Readable

{
  "id": 1,
  "jsonrpc": "2.0",
  "method": "applyMigration",
  "params": {
    "projectInfo": "",
    "force": false,
    "migrationId": "20190622144536-init",
    "steps": [
      {
        "stepType": "CreateModel",
        "name": "User",
        "embedded": false
      },
      {
        "stepType": "CreateModel",
        "name": "Post",
        "embedded": false
      },
      {
        "stepType": "CreateField",
        "model": "User",
        "name": "id",
        "type": {
          "Base": "String"
        },
        "arity": "required",
        "isUnique": true,
        "id": {
          "strategy": "Auto",
          "sequence": null
        },
        "default": {
          "Expression": [
            "cuid",
            "String",
            []
          ]
        }
      },
      {
        "stepType": "CreateField",
        "model": "User",
        "name": "email",
        "type": {
          "Base": "String"
        },
        "arity": "required",
        "isUnique": true
      },
      {
        "stepType": "CreateField",
        "model": "User",
        "name": "name",
        "type": {
          "Base": "String"
        },
        "arity": "optional",
        "isUnique": false
      },
      {
        "stepType": "CreateField",
        "model": "User",
        "name": "posts",
        "type": {
          "Relation": {
            "to": "Post",
            "to_fields": [],
            "name": "PostToUser",
            "on_delete": "None"
          }
        },
        "arity": "list",
        "isUnique": false
      },
      {
        "stepType": "CreateField",
        "model": "Post",
        "name": "id",
        "type": {
          "Base": "String"
        },
        "arity": "required",
        "isUnique": true,
        "id": {
          "strategy": "Auto",
          "sequence": null
        },
        "default": {
          "Expression": [
            "cuid",
            "String",
            []
          ]
        }
      },
      {
        "stepType": "CreateField",
        "model": "Post",
        "name": "createdAt",
        "type": {
          "Base": "DateTime"
        },
        "arity": "required",
        "isUnique": false,
        "default": {
          "Expression": [
            "now",
            "DateTime",
            []
          ]
        }
      },
      {
        "stepType": "CreateField",
        "model": "Post",
        "name": "updatedAt",
        "type": {
          "Base": "DateTime"
        },
        "arity": "required",
        "isUnique": false
      },
      {
        "stepType": "CreateField",
        "model": "Post",
        "name": "published",
        "type": {
          "Base": "Boolean"
        },
        "arity": "required",
        "isUnique": false
      },
      {
        "stepType": "CreateField",
        "model": "Post",
        "name": "title",
        "type": {
          "Base": "String"
        },
        "arity": "required",
        "isUnique": false
      },
      {
        "stepType": "CreateField",
        "model": "Post",
        "name": "content",
        "type": {
          "Base": "String"
        },
        "arity": "optional",
        "isUnique": false
      },
      {
        "stepType": "CreateField",
        "model": "Post",
        "name": "author",
        "type": {
          "Relation": {
            "to": "User",
            "to_fields": [
              "id"
            ],
            "name": "PostToUser",
            "on_delete": "None"
          }
        },
        "arity": "optional",
        "isUnique": false
      }
    ],
    "sourceConfig": "datasource db {\n  provider = \"mysql\"\n  url      = \"mysql://root:123456789@localhost:3306/prisma\"\n  default  = true\n}\n\ngenerator photon {\n  provider = \"photonjs\"\n  output   = \"node_modules/@generated/photon\"\n}\n\ngenerator nexus_prisma {\n  provider = \"nexus-prisma\"\n  output   = \"node_modules/@generated/nexus-prisma\"\n}\n\nmodel User {\n  id    String  @default(cuid()) @id @unique\n  email String  @unique\n  name  String?\n  posts Post[]\n}\n\nmodel Post {\n  id        String   @default(cuid()) @id @unique\n  createdAt DateTime @default(now())\n  updatedAt DateTime @updatedAt\n  published Boolean\n  title     String\n  content   String?\n  author    User?\n}"
  }
}

RPC Response

null

Stack Trace

[/var/root/.cargo/git/checkouts/prisma-query-a8c45647247f5d6d/5a95ce6/src/connector/mysql.rs:83] self.pool.state() = State {
    connections: 10,
    idle_connections: 10
}
[migration-engine/connectors/sql-migration-connector/src/lib.rs:273] format!("CREATE SCHEMA IF NOT EXISTS `{}` DEFAULT CHARACTER SET latin1;" , &
        self . schema_name) = "CREATE SCHEMA IF NOT EXISTS `prisma` DEFAULT CHARACTER SET latin1;"
[/var/root/.cargo/git/checkouts/prisma-query-a8c45647247f5d6d/5a95ce6/src/connector/mysql.rs:83] self.pool.state() = State {
    connections: 10,
    idle_connections: 10
}
[migration-engine/connectors/sql-migration-connector/src/sql_migration_persistence.rs:38] m.make_from(barrel_variant) = "CREATE TABLE prisma._Migration (revision INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY, name TEXT NOT NULL, datamodel TEXT NOT NULL, status TEXT NOT NULL, applied INTEGER NOT NULL, rolled_back INTEGER NOT NULL, datamodel_steps TEXT NOT NULL, database_migration TEXT NOT NULL, errors TEXT NOT NULL, started_at datetime(3) NOT NULL, finished_at datetime(3));"
[/var/root/.cargo/git/checkouts/prisma-query-a8c45647247f5d6d/5a95ce6/src/connector/mysql.rs:83] self.pool.state() = State {
    connections: 10,
    idle_connections: 10
}
[/var/root/.cargo/git/checkouts/prisma-query-a8c45647247f5d6d/5a95ce6/src/connector/mysql.rs:83] self.pool.state() = State {
    connections: 10,
    idle_connections: 10
}
[/var/root/.cargo/git/checkouts/prisma-query-a8c45647247f5d6d/5a95ce6/src/connector/mysql.rs:83] self.pool.state() = State {
    connections: 10,
    idle_connections: 10
}
[/var/root/.cargo/git/checkouts/prisma-query-a8c45647247f5d6d/5a95ce6/src/connector/mysql.rs:158] visitor::Mysql::build(q) = (
    "SELECT `prisma`.`_Migration`.* FROM `prisma`.`_Migration` WHERE `status` = ? ORDER BY `revision` DESC",
    [
        Text(
            "MigrationSuccess"
        )
    ]
)
[/var/root/.cargo/git/checkouts/prisma-query-a8c45647247f5d6d/5a95ce6/src/connector/mysql.rs:83] self.pool.state() = State {
    connections: 10,
    idle_connections: 10
}
[/var/root/.cargo/git/checkouts/prisma-query-a8c45647247f5d6d/5a95ce6/src/connector/mysql.rs:83] self.pool.state() = State {
    connections: 10,
    idle_connections: 10
}
[/var/root/.cargo/git/checkouts/prisma-query-a8c45647247f5d6d/5a95ce6/src/connector/mysql.rs:158] visitor::Mysql::build(q) = (
    "SELECT `prisma`.`_Migration`.* FROM `prisma`.`_Migration` WHERE `status` = ? ORDER BY `revision` DESC",
    [
        Text(
            "MigrationSuccess"
        )
    ]
)
[/var/root/.cargo/git/checkouts/prisma-query-a8c45647247f5d6d/5a95ce6/src/connector/mysql.rs:83] self.pool.state() = State {
    connections: 10,
    idle_connections: 10
}
[/var/root/.cargo/git/checkouts/prisma-query-a8c45647247f5d6d/5a95ce6/src/connector/mysql.rs:83] self.pool.state() = State {
    connections: 10,
    idle_connections: 10
}
[/var/root/.cargo/git/checkouts/prisma-query-a8c45647247f5d6d/5a95ce6/src/connector/mysql.rs:83] self.pool.state() = State {
    connections: 10,
    idle_connections: 10
}
thread 'main' panicked at 'Error accessing result set, column not found: column_default', migration-engine/connectors/sql-migration-connector/src/database_inspector/information_schema.rs:58:33
stack backtrace:
   0: std::sys::unix::backtrace::tracing::imp::unwind_backtrace
   1: std::sys_common::backtrace::_print
   2: std::panicking::default_hook::{{closure}}
   3: std::panicking::default_hook
   4: std::panicking::rust_panic_with_hook
   5: std::panicking::begin_panic
   6: core::ops::function::impls::<impl core::ops::function::FnOnce<A> for &mut F>::call_once
   7: <alloc::vec::Vec<T> as alloc::vec::SpecExtend<T, I>>::from_iter
   8: sql_migration_connector::database_inspector::information_schema::InformationSchema::get_columns
   9: sql_migration_connector::database_inspector::mysql_inspector::MysqlInspector::get_table
  10: <core::iter::adapters::Map<I, F> as core::iter::traits::iterator::Iterator>::fold
  11: <alloc::vec::Vec<T> as alloc::vec::SpecExtend<T, I>>::from_iter
  12: <sql_migration_connector::database_inspector::mysql_inspector::MysqlInspector as sql_migration_connector::database_inspector::DatabaseInspector>::introspect
  13: <sql_migration_connector::sql_database_migration_inferrer::SqlDatabaseMigrationInferrer as migration_connector::database_migration_inferrer::DatabaseMigrationInferrer<sql_migration_connector::sql_migration::SqlMigration>>::infer
  14: migration_core::commands::apply_migration::ApplyMigrationCommand::handle_migration
  15: <migration_core::commands::apply_migration::ApplyMigrationCommand as migration_core::commands::command::MigrationCommand>::execute
  16: <F as jsonrpc_core::calls::RpcMethodSimple>::call
  17: <F as jsonrpc_core::calls::RpcMethod<T>>::call
  18: <futures::future::lazy::Lazy<F, R> as futures::future::Future>::poll
  19: <futures::future::then::Then<A, B, F> as futures::future::Future>::poll
  20: <futures::future::map::Map<A, F> as futures::future::Future>::poll
  21: <futures::future::either::Either<A, B> as futures::future::Future>::poll
  22: futures::task_impl::std::set
  23: <std::thread::local::LocalKey<T>>::with
  24: futures::future::Future::wait
  25: <jsonrpc_core::io::IoHandler<M>>::handle_request_sync
  26: migration_core::rpc_api::RpcApi::handle
  27: migration_engine::main
  28: std::rt::lang_start::{{closure}}
  29: std::panicking::try::do_call
  30: __rust_maybe_catch_panic
  31: std::rt::lang_start_internal
  32: main

Getting start experience not following the spec

In the current GSE post, we mention that multiple files will be generated in the project structure section Expand to learn about the generated project setup. when From scratch option is used in the CLI.

This is because the current CLI implements From scratch differently from other boilerplates by generating the necessary files in memory. However, the spec is to use the example called script for the respective language.

working type definition example

The type definition example

type Numeric Float @pg.numeric(precision: 5, scale: 2)
                   @ms.decimal(precision: 5, scale: 2)

gives me this error: Unexpected token. Expected one of: Start of block ("{").

A working example would be nice. (And also more docs on it in general)

`prisma introspect` should require Prisma file

Historically prisma introspect could be used to create a project. Now this functionality has been move into prisma init. prisma introspect should now only be used for re-introspection use cases.

Please enforce that prisma introspect requires a Prisma file.

Create a Windows Binary

High-level: create a binary for windows and add it to the Table of binaries

There's very likely more low-level work to do here. Might be worth trying: cargo build --target x86_64-pc-windows-gnu to see if we get lucky.

Optimistic Concurrency Control

Problem statement

The current API doesn't allow to implement application-level Optimistic Concurrency Control, which is a pattern commonly implemented by applications with high concurrency needs, to avoid creating a bottleneck on the database, while obtaining certain guarantees about the integrity of the modified data.

Use cases

Imagine data that is bound to a given user profile, which only the said user can modify, or their admin. There are few chances that the same user would try to update the same data at the same time, which makes the use of pessimistic locking or long-running transaction solutions suboptimal. Implementing OCC, in this case, will be likely more performant.

Context about Optimistic Concurrency Control

OCC assumes that multiple transactions can frequently complete without interfering with each other, hence proposes to not block concurrent transactions and require the application to rollback in case of a detected conflict:

This SO answer gives a great summary

Optimistic Concurrency Control implies reading a record, taking note of a version number (other methods to do this involve dates, timestamps or checksums/hashes), and checking that the version hasn't changed before the record gets written back. When writing the record back, one filters the update on the version to make sure it's atomic. (i.e. hasn't been updated between when checking the version and writing the record to the disk) and update the version in one hit.

If the record is dirty (i.e. has a different version to the user's) the transaction should abort and require the process to be restarted.

Pessimistic Concurrency Control

PCC is assuming that data will often be accessed by multiple processes at the same time and proposes to lock data to sequence how these processes access it.

In practice, database vendors like Postgres or MySQL implement a concept of table- or row-level locking, giving control over what can be read and written (depending on the transaction's isolation level):

  • SELECT ... FOR SHARE which leaves the data available to read, but not to update outside the transaction.
  • SELECT ... FOR UPDATE which prevents the selected data from being read or updated outside of the transaction.
  • the NOWAIT variant to fail if a row is locked
  • the SKIP LOCKED variant to skip locked rows
  • ...

This approach comes with risk of deadlocks, which can block the application, so should be considered carefully.

Scope

While we should consider an API design allowing us to evolve towards supporting and controlling both policies, the first iteration should focus on supporting Optimistic Concurrency Control.

Pessimistic Concurrency Control requires a deeper analysis of combinations of transaction isolation levels along with the locking strategy for each record, which makes it significantly more complex.

Solution ideas

The API should offer a way to specify, on update and in $transaction calls, whether to check on a certain field used to implement Optimistic Concurrency Control (version, timestamp...).

Proposal 1

A single attribute defining the locking policy, so that people can implement Optimistic Concurrency Control:

lockingPolicy: {
  optimistic: 'fieldName'
} 

This design would possibly allow defining a pessimistic policy in the future:

lockingPolicy: {
  pessimistic: 'ROW' // or 'TABLE', depends on database vendor
}

Applied example

Imagine users detaining a number of points in a competition. And that points can be updated based on how each user behaves in the application.

model User {
  team        Team
  numPoints   Int
  updatedAt   DateTime @updatedAt
}

The exchange of points needs to be done in a consistent fashion across users to ensure a fair game.

Pseudo code:

// Optimistic
const scored = 2
prisma.$transaction([
	prisma.user.update({
		data: { 
		  numPoints: {
		    decrement: scored
		  } 
		}, 
		where: {
		  id: 'loser-user-id'
		},
		lockingPolicy: {
      optimistic: 'updatedAt'
    } 
  ),
	prisma.user.update({
		data: { 
		  numPoints: {
		    increment: scored
		  } 
		}, 
		where: {
		  id: 'winner-user-id'
		},
		lockingPolicy: {
      optimistic: 'updatedAt'
    } 
  )
])

// Pessimistic (used to proof the API design, not meant for implementation)
const scored = 2
prisma.$transaction([
	prisma.user.update({
		data: { 
		  numPoints: {
		    decrement: scored
		  } 
		}, 
		where: {
		  id: 'loser-user-id'
		},
		lockingPolicy: {
      pessimistic: 'ROW'
    } 
  ),
	prisma.user.update({
		data: { 
		  numPoints: {
		    increment: scored
		  } 
		}, 
		where: {
		  id: 'winner-user-id'
		},
		pessimisticLock: {
      on: 'ROW'
    } 
  )
], {
  isolation: 'REPEATABLE READ'
})

Proposition 2

// Optimistic
const scored = 2
const user = prisma.user.findOne({ where: { id: 'loser-user-id' } })
const controlPoints = user.points

try {
	// Throws if `numPoints` has changed between the fetching and the update
	prisma.user.update({
		data: { 
		  numPoints: {
		    decrement: scored
		  } 
		},
		where: {
		  id: 'loser-user-id'
		},
		if: {
	      numPoints: controlPoints
	    }
	  )
} catch (e) {
  // Consider redoing, or error out.
}

Streaming

for await (const post of prisma.posts().$stream()) {
  console.log(post)
}

const postStreamWithPageInfo = await prisma
  .posts()
  .$stream()
  .$withPageInfo()

for await (const posts of prisma.users
  .findOne('bobs-id')
  .posts({ first: 50 })
  .batch({ batchSize: 100 })) {
  console.log(posts) // 100 posts
}

// Configure streaming chunkSize and fetchThreshold
prisma.posts({ first: 10000 }).$stream({ chunkSize: 100, fetchThreshold: 0.5 /*, tailable: true*/ })

// Buffering
const posts = await prisma
  .posts({ first: 100000 })
  .$stream()
  .toArray()

// Shortcut for count
const userCount = await prisma.users.count({
  where: {
    age: {
      gt: 18,
    },
  },
})

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.