diff --git a/.github/workflows/basic-workflow.yml b/.github/workflows/basic-workflow.yml index 5eb6796..4a426c1 100644 --- a/.github/workflows/basic-workflow.yml +++ b/.github/workflows/basic-workflow.yml @@ -6,16 +6,20 @@ on: module: type: string required: true - description: 'The name of sample project.' + description: "The name of sample project." language: type: string required: true - description: 'The programming language used by the sample project.' + description: "The programming language used by the sample project." with_oceanbase_container: type: boolean required: false - description: 'Whether to use a pre-deployed OceanBase container in CI workflow.' + description: "Whether to use a pre-deployed OceanBase container in CI workflow." default: true + oceanbase_image_tag: + type: string + required: false + description: "The tag of OceanBase CE Docker image." concurrency: group: basic-ci-${{ github.event.pull_request.number || github.ref }}-${{ inputs.module }} @@ -31,14 +35,14 @@ jobs: if: ${{ inputs.language == 'golang' }} uses: actions/setup-go@v4 with: - go-version: '1.20' + go-version: "1.20" - name: Setup Java env if: ${{ inputs.language == 'java' }} uses: actions/setup-java@v4 with: - java-version: '8' - distribution: 'zulu' - cache: 'maven' + java-version: "8" + distribution: "zulu" + cache: "maven" - name: Setup Python env if: ${{ inputs.language == 'python' }} uses: actions/setup-python@v4 @@ -54,7 +58,8 @@ jobs: if: ${{ inputs.with_oceanbase_container }} uses: oceanbase/setup-oceanbase-ce@v1 with: - network: 'host' + network: "host" + image_tag: ${{ inputs.oceanbase_image_tag || 'latest' }} - name: Run sample for ${{ inputs.module }} run: | cd ${{ inputs.language }}/${{ inputs.module }} || exit diff --git a/.github/workflows/javascript.yml b/.github/workflows/javascript.yml index 1fd7ab5..3b708f9 100644 --- a/.github/workflows/javascript.yml +++ b/.github/workflows/javascript.yml @@ -3,22 +3,26 @@ name: JavaScript CI on: push: paths: - - '.github/workflows/javascript.yml' - - 'javascript/**' + - ".github/workflows/javascript.yml" + - "javascript/**" pull_request: paths: - - '.github/workflows/javascript.yml' - - 'javascript/**' + - ".github/workflows/javascript.yml" + - "javascript/**" jobs: ci: strategy: matrix: module: - - name: 'mysql2' + - name: "mysql2" with_oceanbase_container: true + - name: "prisma" + with_oceanbase_container: true + oceanbase_image_tag: "4.2.2" uses: ./.github/workflows/basic-workflow.yml with: - language: 'javascript' + language: "javascript" module: ${{ matrix.module.name }} with_oceanbase_container: ${{ matrix.module.with_oceanbase_container }} + oceanbase_image_tag: ${{ matrix.module.oceanbase_image_tag }} diff --git a/javascript/prisma/.env b/javascript/prisma/.env new file mode 100644 index 0000000..d480e98 --- /dev/null +++ b/javascript/prisma/.env @@ -0,0 +1 @@ +DATABASE_URL="mysql://root:@127.0.0.1:2881/test?prefer_socket=false&a=.psdb.cloud" diff --git a/javascript/prisma/.gitignore b/javascript/prisma/.gitignore new file mode 100644 index 0000000..3c3629e --- /dev/null +++ b/javascript/prisma/.gitignore @@ -0,0 +1 @@ +node_modules diff --git a/javascript/prisma/README-CN.md b/javascript/prisma/README-CN.md new file mode 100644 index 0000000..1446351 --- /dev/null +++ b/javascript/prisma/README-CN.md @@ -0,0 +1,114 @@ +# Prisma 连接 OceanBase 指南 + +[English](README.md) | 简体中文 + +本文介绍如何通过 [Prisma](https://www.prisma.io) 连接 [OceanBase](https://www.oceanbase.com) 数据库。 + +## 准备工作 + +确保 Nodejs 和 npm 已经安装。 + +## 项目配置 + +拉取项目并进入相应目录: + +```bash +git clone git@github.com:oceanbase/ob-samples.git +cd javascript/prisma +``` + +全局配置环境变量 (原因详见 https://open.oceanbase.com/blog/15137753618): + +```bash +export PRISMA_ENGINES_MIRROR=https://oceanbase-prisma-builds.s3.ap-southeast-1.amazonaws.com +export BINARY_DOWNLOAD_VERSION=96fa66f2f130d66795d9f79dd431c678a9c7104e +``` + +安装依赖 (注意 `prisma` 和 `@prisma/client` 版本在 `^5.20.0` 及以上): + +```bash +npm install +``` + +修改 `.env` 中的数据库连接串,格式如下。注意需要设置 `prefer_socket=false`,以避免和 OceanBase 建立连接时报错。 + +```bash +DATABASE_URL="mysql://root:@127.0.0.1:2881/test?prefer_socket=false&a=.psdb.cloud" +``` + +执行以下命令,将 `prisma/schema.prisma` 中定义的 `User`、`Post` 和 `Profile` 模型同步到数据库中: + +```bash +npx prisma migrate dev --name init +``` + +```sql +mysql> show tables; ++--------------------+ +| Tables_in_test | ++--------------------+ +| _prisma_migrations | +| posts | +| profiles | +| users | ++--------------------+ +4 rows in set (0.02 sec) +``` + +执行 `index.ts`: + +```bash +npx ts-node index.ts +``` + +输出以下内容,说明执行成功: + +```bash +[ + { + id: 1, + email: 'alice@oceanbase.com', + name: 'Alice', + posts: [ + { + id: 1, + createdAt: 2024-10-31T04:33:45.535Z, + updatedAt: 2024-10-31T04:33:45.535Z, + title: 'Hello World', + content: null, + published: false, + authorId: 1 + } + ], + profile: { id: 1, bio: 'I like turtles', userId: 1 } + } +] +``` + +查看对应的 `users`、`posts` 和 `profiles` 表,数据已正常插入: + +```bash +mysql> select * from users; ++----+---------------------+-------+ +| id | email | name | ++----+---------------------+-------+ +| 1 | alice@oceanbase.com | Alice | ++----+---------------------+-------+ +1 row in set (0.01 sec) + +mysql> select * from posts; ++----+-------------------------+-------------------------+-------------+---------+-----------+----------+ +| id | createdAt | updatedAt | title | content | published | authorId | ++----+-------------------------+-------------------------+-------------+---------+-----------+----------+ +| 1 | 2024-10-31 04:33:45.535 | 2024-10-31 04:33:45.535 | Hello World | NULL | 0 | 1 | ++----+-------------------------+-------------------------+-------------+---------+-----------+----------+ +1 row in set (0.01 sec) + +mysql> select * from profiles; ++----+----------------+--------+ +| id | bio | userId | ++----+----------------+--------+ +| 1 | I like turtles | 1 | ++----+----------------+--------+ +1 row in set (0.01 sec) +``` diff --git a/javascript/prisma/README.md b/javascript/prisma/README.md new file mode 100644 index 0000000..a158b07 --- /dev/null +++ b/javascript/prisma/README.md @@ -0,0 +1,114 @@ +# Connect to OceanBase with Prisma + +English | [简体中文](README-CN.md) + +This document describes how to connect to [OceanBase](https://www.oceanbase.com) with [Prisma](https://www.prisma.io). + +## Preparation + +Make sure `Node.js` and `npm` are installed. + +## Configuration + +Clone the project and navigate to the appropriate directory: + +```bash +git clone git@github.com:oceanbase/ob-samples.git +cd javascript/prisma +``` + +Set the global environment variables (for reasons see https://open.oceanbase.com/blog/15137753618): + +```bash +export PRISMA_ENGINES_MIRROR=https://oceanbase-prisma-builds.s3.ap-southeast-1.amazonaws.com +export BINARY_DOWNLOAD_VERSION=96fa66f2f130d66795d9f79dd431c678a9c7104e +``` + +Install dependencies (note that the versions of `prisma` and `@prisma/client` should be `^5.20.0` or higher): + +```bash +npm install +``` + +Modify the connection string in the `.env` file, formatted as follows. Note that `prefer_socket=false` must be set to avoid errors when connecting to OceanBase. + +```bash +DATABASE_URL="mysql://root:@127.0.0.1:2881/test?prefer_socket=false&a=.psdb.cloud" +``` + +Execute the following command to synchronize the `User`, `Post` and `Profile` data models defined in `prisma/schema.prisma` to the database: + +```bash +npx prisma migrate dev --name init +``` + +```sql +mysql> show tables; ++--------------------+ +| Tables_in_test | ++--------------------+ +| _prisma_migrations | +| posts | +| profiles | +| users | ++--------------------+ +4 rows in set (0.02 sec) +``` + +Execute `index.ts`: + +```bash +npx ts-node index.ts +``` + +The output should be as follows, indicating successful execution: + +```bash +[ + { + id: 1, + email: 'alice@oceanbase.com', + name: 'Alice', + posts: [ + { + id: 1, + createdAt: 2024-10-31T04:33:45.535Z, + updatedAt: 2024-10-31T04:33:45.535Z, + title: 'Hello World', + content: null, + published: false, + authorId: 1 + } + ], + profile: { id: 1, bio: 'I like turtles', userId: 1 } + } +] +``` + +Check the corresponding `users`, `posts` and `profiles` tables and the data has been inserted: + +```bash +mysql> select * from users; ++----+---------------------+-------+ +| id | email | name | ++----+---------------------+-------+ +| 1 | alice@oceanbase.com | Alice | ++----+---------------------+-------+ +1 row in set (0.01 sec) + +mysql> select * from posts; ++----+-------------------------+-------------------------+-------------+---------+-----------+----------+ +| id | createdAt | updatedAt | title | content | published | authorId | ++----+-------------------------+-------------------------+-------------+---------+-----------+----------+ +| 1 | 2024-10-31 04:33:45.535 | 2024-10-31 04:33:45.535 | Hello World | NULL | 0 | 1 | ++----+-------------------------+-------------------------+-------------+---------+-----------+----------+ +1 row in set (0.01 sec) + +mysql> select * from profiles; ++----+----------------+--------+ +| id | bio | userId | ++----+----------------+--------+ +| 1 | I like turtles | 1 | ++----+----------------+--------+ +1 row in set (0.01 sec) +``` diff --git a/javascript/prisma/index.ts b/javascript/prisma/index.ts new file mode 100644 index 0000000..0fee963 --- /dev/null +++ b/javascript/prisma/index.ts @@ -0,0 +1,36 @@ +import { PrismaClient } from "@prisma/client"; + +const prisma = new PrismaClient(); + +async function main() { + await prisma.user.create({ + data: { + name: "Alice", + email: "alice@oceanbase.com", + posts: { + create: { title: "Hello World" }, + }, + profile: { + create: { bio: "I like turtles" }, + }, + }, + }); + + const allUsers = await prisma.user.findMany({ + include: { + posts: true, + profile: true, + }, + }); + console.dir(allUsers, { depth: null }); +} + +main() + .then(async () => { + await prisma.$disconnect(); + }) + .catch(async (e) => { + console.error(e); + await prisma.$disconnect(); + process.exit(1); + }); diff --git a/javascript/prisma/package.json b/javascript/prisma/package.json new file mode 100644 index 0000000..892d8c9 --- /dev/null +++ b/javascript/prisma/package.json @@ -0,0 +1,21 @@ +{ + "name": "prisma", + "version": "1.0.0", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "description": "", + "devDependencies": { + "@types/node": "^22.8.4", + "prisma": "^5.21.1", + "ts-node": "^10.9.2", + "typescript": "^5.6.3" + }, + "dependencies": { + "@prisma/client": "^5.21.1" + } +} diff --git a/javascript/prisma/prisma/schema.prisma b/javascript/prisma/prisma/schema.prisma new file mode 100644 index 0000000..48d15c4 --- /dev/null +++ b/javascript/prisma/prisma/schema.prisma @@ -0,0 +1,43 @@ +// This is your Prisma schema file, +// learn more about it in the docs: https://pris.ly/d/prisma-schema + +generator client { + provider = "prisma-client-js" +} + +datasource db { + provider = "mysql" + url = env("DATABASE_URL") +} + +model Post { + id Int @id @default(autoincrement()) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + title String @db.VarChar(255) + content String? + published Boolean @default(false) + author User @relation(fields: [authorId], references: [id]) + authorId Int + + @@map("posts") +} + +model Profile { + id Int @id @default(autoincrement()) + bio String? + user User @relation(fields: [userId], references: [id]) + userId Int @unique + + @@map("profiles") +} + +model User { + id Int @id @default(autoincrement()) + email String @unique + name String? + posts Post[] + profile Profile? + + @@map("users") +} diff --git a/javascript/prisma/run.sh b/javascript/prisma/run.sh new file mode 100644 index 0000000..5677928 --- /dev/null +++ b/javascript/prisma/run.sh @@ -0,0 +1,5 @@ +export PRISMA_ENGINES_MIRROR=https://oceanbase-prisma-builds.s3.ap-southeast-1.amazonaws.com +export BINARY_DOWNLOAD_VERSION=96fa66f2f130d66795d9f79dd431c678a9c7104e +npm install +npx prisma migrate dev --name init +npx ts-node index.ts diff --git a/javascript/prisma/tsconfig.json b/javascript/prisma/tsconfig.json new file mode 100644 index 0000000..56a8ab8 --- /dev/null +++ b/javascript/prisma/tsconfig.json @@ -0,0 +1,110 @@ +{ + "compilerOptions": { + /* Visit https://aka.ms/tsconfig to read more about this file */ + + /* Projects */ + // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ + // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ + // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ + // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ + // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ + // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ + + /* Language and Environment */ + "target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ + // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ + // "jsx": "preserve", /* Specify what JSX code is generated. */ + // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */ + // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ + // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ + // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ + // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ + // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ + // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ + // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ + // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ + + /* Modules */ + "module": "commonjs", /* Specify what module code is generated. */ + // "rootDir": "./", /* Specify the root folder within your source files. */ + // "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */ + // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ + // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ + // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ + // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ + // "types": [], /* Specify type package names to be included without being referenced in a source file. */ + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ + // "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */ + // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */ + // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */ + // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */ + // "noUncheckedSideEffectImports": true, /* Check side effect imports. */ + // "resolveJsonModule": true, /* Enable importing .json files. */ + // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */ + // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ + + /* JavaScript Support */ + // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ + // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ + // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ + + /* Emit */ + // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ + // "declarationMap": true, /* Create sourcemaps for d.ts files. */ + // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ + // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ + // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ + // "noEmit": true, /* Disable emitting files from a compilation. */ + // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ + // "outDir": "./", /* Specify an output folder for all emitted files. */ + // "removeComments": true, /* Disable emitting comments. */ + // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ + // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ + // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ + // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ + // "newLine": "crlf", /* Set the newline character for emitting files. */ + // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ + // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ + // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ + // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ + // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ + + /* Interop Constraints */ + // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ + // "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */ + // "isolatedDeclarations": true, /* Require sufficient annotation on exports so other tools can trivially generate declaration files. */ + // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ + "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ + // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ + "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ + + /* Type Checking */ + "strict": true, /* Enable all strict type-checking options. */ + // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ + // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ + // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ + // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ + // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ + // "strictBuiltinIteratorReturn": true, /* Built-in iterators are instantiated with a 'TReturn' type of 'undefined' instead of 'any'. */ + // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ + // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ + // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ + // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ + // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ + // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ + // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ + // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ + // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ + // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ + // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ + // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ + // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ + + /* Completeness */ + // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ + "skipLibCheck": true /* Skip type checking all .d.ts files. */ + } +}