chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,16 @@
|
||||
PG_CONNECTION_STRING="postgres://postgres:postgres@localhost:55432/postgres"
|
||||
MYSQL_CONNECTION_STRING="mysql://root:mysql@127.0.0.1:33306/drizzle"
|
||||
SINGLESTORE_CONNECTION_STRING="singlestore://root:singlestore@localhost:3306/drizzle"
|
||||
PLANETSCALE_CONNECTION_STRING=
|
||||
TIDB_CONNECTION_STRING=
|
||||
NEON_HTTP_CONNECTION_STRING=postgres://postgres:postgres@db.localtest.me:5432/postgres
|
||||
NEON_SERVERLESS_CONNECTION_STRING=postgres://postgres:postgres@localhost:5445/postgres
|
||||
LIBSQL_URL="file:local.db"
|
||||
LIBSQL_AUTH_TOKEN="ey..." # For Turso only
|
||||
LIBSQL_REMOTE_URL="libsql://..."
|
||||
LIBSQL_REMOTE_TOKEN="ey..."
|
||||
AWS_DATA_API_DB=
|
||||
AWS_DATA_API_SECRET_ARN=
|
||||
AWS_DATA_API_RESOURCE_ARN=
|
||||
AWS_TEST_PROFILE=
|
||||
VERCEL_CONNECTION_STRING= # For driver-init, utils/is-config tests. Must not be local DB - breaks with Vercel drivers
|
||||
@@ -0,0 +1,6 @@
|
||||
.env
|
||||
*.db
|
||||
trace
|
||||
tests/imports/imports.cjs
|
||||
tests/imports/imports.mjs
|
||||
.sst
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"@xata.io/cli": { "latest": "0.15.10", "compatibility": [{ "range": ">=0.0.0" }] },
|
||||
"@xata.io/client": { "latest": "0.29.3", "compatibility": [{ "range": ">=0.0.0" }] }
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"databaseURL": "https://Andrii-Sherman-s-workspace-2r5ujp.us-east-1.xata.sh/db/integration-tests",
|
||||
"codegen": {
|
||||
"output": "tests/xata/xata.ts"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
services:
|
||||
postgres:
|
||||
image: 'postgres:latest'
|
||||
environment:
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_PASSWORD: postgres
|
||||
POSTGRES_DB: postgres
|
||||
ports:
|
||||
- '5432:5441'
|
||||
healthcheck:
|
||||
test: ['CMD-SHELL', 'pg_isready -U postgres']
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
neon-proxy:
|
||||
image: ghcr.io/timowilhelm/local-neon-http-proxy:main
|
||||
environment:
|
||||
- PG_CONNECTION_STRING=postgres://postgres:postgres@postgres:5432/postgres
|
||||
ports:
|
||||
- '4444:4444'
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
pg_proxy:
|
||||
image: ghcr.io/neondatabase/wsproxy:latest
|
||||
environment:
|
||||
APPEND_PORT: 'postgres:5432'
|
||||
ALLOW_ADDR_REGEX: '.*'
|
||||
LOG_TRAFFIC: 'true'
|
||||
ports:
|
||||
- '5446:80'
|
||||
depends_on:
|
||||
- postgres
|
||||
@@ -0,0 +1,7 @@
|
||||
CREATE TABLE `userstest` (
|
||||
`id` serial PRIMARY KEY,
|
||||
`name` text NOT NULL,
|
||||
`verified` boolean NOT NULL DEFAULT false,
|
||||
`jsonb` json,
|
||||
`created_at` timestamp NOT NULL DEFAULT now()
|
||||
);
|
||||
@@ -0,0 +1,60 @@
|
||||
{
|
||||
"version": "5",
|
||||
"dialect": "mysql",
|
||||
"id": "8e8c8378-0496-40f6-88e3-98aab8282b1f",
|
||||
"prevId": "00000000-0000-0000-0000-000000000000",
|
||||
"tables": {
|
||||
"userstest": {
|
||||
"name": "userstest",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "serial",
|
||||
"primaryKey": true,
|
||||
"notNull": false,
|
||||
"autoincrement": true
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"verified": {
|
||||
"name": "verified",
|
||||
"type": "boolean",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"jsonb": {
|
||||
"name": "jsonb",
|
||||
"type": "json",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()",
|
||||
"autoincrement": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {}
|
||||
}
|
||||
},
|
||||
"schemas": {},
|
||||
"_meta": {
|
||||
"schemas": {},
|
||||
"tables": {},
|
||||
"columns": {}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"version": "5",
|
||||
"dialect": "mysql",
|
||||
"entries": [
|
||||
{
|
||||
"idx": 0,
|
||||
"version": "5",
|
||||
"when": 1680270921944,
|
||||
"tag": "0000_nostalgic_carnage",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
CREATE TABLE `userstest` (
|
||||
`id` serial PRIMARY KEY,
|
||||
`name` text NOT NULL,
|
||||
`verified` boolean NOT NULL DEFAULT false,
|
||||
`jsonb` json,
|
||||
`created_at` timestamp NOT NULL DEFAULT now()
|
||||
);
|
||||
@@ -0,0 +1,5 @@
|
||||
CREATE TABLE `users12` (
|
||||
`id` serial AUTO_INCREMENT PRIMARY KEY NOT NULL,
|
||||
`name` text NOT NULL,
|
||||
`email` text NOT NULL
|
||||
);
|
||||
@@ -0,0 +1,60 @@
|
||||
{
|
||||
"version": "5",
|
||||
"dialect": "mysql",
|
||||
"id": "8e8c8378-0496-40f6-88e3-98aab8282b1f",
|
||||
"prevId": "00000000-0000-0000-0000-000000000000",
|
||||
"tables": {
|
||||
"userstest": {
|
||||
"name": "userstest",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "serial",
|
||||
"primaryKey": true,
|
||||
"notNull": false,
|
||||
"autoincrement": true
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"verified": {
|
||||
"name": "verified",
|
||||
"type": "boolean",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"jsonb": {
|
||||
"name": "jsonb",
|
||||
"type": "json",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()",
|
||||
"autoincrement": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {}
|
||||
}
|
||||
},
|
||||
"schemas": {},
|
||||
"_meta": {
|
||||
"schemas": {},
|
||||
"tables": {},
|
||||
"columns": {}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
{
|
||||
"version": "5",
|
||||
"dialect": "mysql",
|
||||
"id": "47362df0-c353-4bd1-8107-fcc36f0e61bd",
|
||||
"prevId": "8e8c8378-0496-40f6-88e3-98aab8282b1f",
|
||||
"tables": {
|
||||
"userstest": {
|
||||
"name": "userstest",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "serial",
|
||||
"primaryKey": true,
|
||||
"notNull": false,
|
||||
"autoincrement": true
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"verified": {
|
||||
"name": "verified",
|
||||
"type": "boolean",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"jsonb": {
|
||||
"name": "jsonb",
|
||||
"type": "json",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()",
|
||||
"autoincrement": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {}
|
||||
},
|
||||
"users12": {
|
||||
"name": "users12",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "serial",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": true
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"email": {
|
||||
"name": "email",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"my_unique_index": {
|
||||
"name": "my_unique_index",
|
||||
"columns": ["name"],
|
||||
"isUnique": true,
|
||||
"using": "btree"
|
||||
}
|
||||
},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {}
|
||||
}
|
||||
},
|
||||
"schemas": {},
|
||||
"_meta": {
|
||||
"schemas": {},
|
||||
"tables": {},
|
||||
"columns": {}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"version": "5",
|
||||
"dialect": "mysql",
|
||||
"entries": [
|
||||
{
|
||||
"idx": 0,
|
||||
"version": "5",
|
||||
"when": 1680270921944,
|
||||
"tag": "0000_nostalgic_carnage",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 1,
|
||||
"version": "5",
|
||||
"when": 1680270921945,
|
||||
"tag": "0001_test",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
CREATE TABLE `cities_migration` (
|
||||
`id` int,
|
||||
`fullname_name` text,
|
||||
`state` text
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE `users_migration` (
|
||||
`id` int PRIMARY KEY NOT NULL,
|
||||
`full_name` text,
|
||||
`phone` int,
|
||||
`invited_by` int,
|
||||
`city_id` int,
|
||||
`date` timestamp DEFAULT (now())
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE `users12` (
|
||||
`id` serial AUTO_INCREMENT PRIMARY KEY NOT NULL,
|
||||
`name` text NOT NULL,
|
||||
`email` text NOT NULL
|
||||
);
|
||||
@@ -0,0 +1,132 @@
|
||||
{
|
||||
"version": "5",
|
||||
"dialect": "mysql",
|
||||
"id": "8e8c8378-0496-40f6-88e3-98aab8282b1f",
|
||||
"prevId": "00000000-0000-0000-0000-000000000000",
|
||||
"tables": {
|
||||
"cities_migration": {
|
||||
"name": "cities_migration",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "int",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"fullname_name": {
|
||||
"name": "fullname_name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"state": {
|
||||
"name": "state",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {}
|
||||
},
|
||||
"users_migration": {
|
||||
"name": "users_migration",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "int",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"full_name": {
|
||||
"name": "full_name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"phone": {
|
||||
"name": "phone",
|
||||
"type": "int",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"invited_by": {
|
||||
"name": "invited_by",
|
||||
"type": "int",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"city_id": {
|
||||
"name": "city_id",
|
||||
"type": "int",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"date": {
|
||||
"name": "date",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false,
|
||||
"default": "(now())"
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {}
|
||||
},
|
||||
"users12": {
|
||||
"name": "users12",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "serial",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": true
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"email": {
|
||||
"name": "email",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"my_unique_index": {
|
||||
"name": "my_unique_index",
|
||||
"columns": [
|
||||
"name"
|
||||
],
|
||||
"isUnique": true,
|
||||
"using": "btree"
|
||||
}
|
||||
},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {}
|
||||
}
|
||||
},
|
||||
"schemas": {},
|
||||
"_meta": {
|
||||
"schemas": {},
|
||||
"tables": {},
|
||||
"columns": {}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"version": "5",
|
||||
"dialect": "mysql",
|
||||
"entries": [
|
||||
{
|
||||
"idx": 0,
|
||||
"version": "5",
|
||||
"when": 1680270921944,
|
||||
"tag": "0000_nostalgic_carnage",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
CREATE TABLE "users" (
|
||||
id serial PRIMARY KEY,
|
||||
name text NOT NULL,
|
||||
verified boolean NOT NULL DEFAULT false,
|
||||
jsonb jsonb,
|
||||
created_at timestamptz NOT NULL DEFAULT now()
|
||||
);
|
||||
@@ -0,0 +1,56 @@
|
||||
{
|
||||
"version": "5",
|
||||
"dialect": "pg",
|
||||
"id": "cb1644bb-c5da-465a-8d70-f63d81e34514",
|
||||
"prevId": "00000000-0000-0000-0000-000000000000",
|
||||
"tables": {
|
||||
"users": {
|
||||
"name": "users",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "serial",
|
||||
"primaryKey": true,
|
||||
"notNull": false
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"verified": {
|
||||
"name": "verified",
|
||||
"type": "boolean",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": false
|
||||
},
|
||||
"jsonb": {
|
||||
"name": "jsonb",
|
||||
"type": "jsonb",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamptz",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {}
|
||||
}
|
||||
},
|
||||
"enums": {},
|
||||
"schemas": {},
|
||||
"_meta": {
|
||||
"schemas": {},
|
||||
"tables": {},
|
||||
"columns": {}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"version": "5",
|
||||
"dialect": "pg",
|
||||
"entries": [
|
||||
{
|
||||
"idx": 0,
|
||||
"version": "5",
|
||||
"when": 1680271923328,
|
||||
"tag": "0000_puzzling_flatman",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
CREATE TABLE "users" (
|
||||
id serial PRIMARY KEY,
|
||||
name text NOT NULL,
|
||||
verified boolean NOT NULL DEFAULT false,
|
||||
jsonb jsonb,
|
||||
created_at timestamptz NOT NULL DEFAULT now()
|
||||
);
|
||||
@@ -0,0 +1,5 @@
|
||||
CREATE TABLE "users12" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"name" text NOT NULL,
|
||||
"email" text NOT NULL
|
||||
);
|
||||
@@ -0,0 +1,56 @@
|
||||
{
|
||||
"version": "5",
|
||||
"dialect": "pg",
|
||||
"id": "cb1644bb-c5da-465a-8d70-f63d81e34514",
|
||||
"prevId": "00000000-0000-0000-0000-000000000000",
|
||||
"tables": {
|
||||
"users": {
|
||||
"name": "users",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "serial",
|
||||
"primaryKey": true,
|
||||
"notNull": false
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"verified": {
|
||||
"name": "verified",
|
||||
"type": "boolean",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": false
|
||||
},
|
||||
"jsonb": {
|
||||
"name": "jsonb",
|
||||
"type": "jsonb",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamptz",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {}
|
||||
}
|
||||
},
|
||||
"enums": {},
|
||||
"schemas": {},
|
||||
"_meta": {
|
||||
"schemas": {},
|
||||
"tables": {},
|
||||
"columns": {}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
{
|
||||
"version": "5",
|
||||
"dialect": "pg",
|
||||
"id": "f2a88b25-f2da-4973-879e-60b57f24e7b9",
|
||||
"prevId": "cb1644bb-c5da-465a-8d70-f63d81e34514",
|
||||
"tables": {
|
||||
"users": {
|
||||
"name": "users",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "serial",
|
||||
"primaryKey": true,
|
||||
"notNull": false
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"verified": {
|
||||
"name": "verified",
|
||||
"type": "boolean",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": false
|
||||
},
|
||||
"jsonb": {
|
||||
"name": "jsonb",
|
||||
"type": "jsonb",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamptz",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {}
|
||||
}
|
||||
},
|
||||
"enums": {},
|
||||
"schemas": {},
|
||||
"_meta": {
|
||||
"schemas": {},
|
||||
"tables": {},
|
||||
"columns": {}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"version": "5",
|
||||
"dialect": "pg",
|
||||
"entries": [
|
||||
{
|
||||
"idx": 0,
|
||||
"version": "5",
|
||||
"when": 1680271923328,
|
||||
"tag": "0000_puzzling_flatman",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 1,
|
||||
"version": "5",
|
||||
"when": 1680271923329,
|
||||
"tag": "0001_test",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
CREATE TABLE IF NOT EXISTS "all_columns" (
|
||||
"smallint" smallint,
|
||||
"smallint_def" smallint DEFAULT 10,
|
||||
"integer" integer,
|
||||
"integer_def" integer DEFAULT 10,
|
||||
"numeric" numeric,
|
||||
"numeric3" numeric,
|
||||
"numeric4" numeric(7, 7),
|
||||
"numeridef" numeric DEFAULT '100',
|
||||
"bigint" bigint,
|
||||
"bigintdef" bigint DEFAULT 100,
|
||||
"boolean" boolean,
|
||||
"boolean_def" boolean DEFAULT true,
|
||||
"text" text,
|
||||
"textdef" text DEFAULT 'text',
|
||||
"varchar" varchar,
|
||||
"varchardef" varchar DEFAULT 'text',
|
||||
"serial" serial NOT NULL,
|
||||
"bigserial" bigserial NOT NULL,
|
||||
"decimal" numeric(100, 2),
|
||||
"decimaldef" numeric(100, 2) DEFAULT '100.0',
|
||||
"doublePrecision" double precision,
|
||||
"doublePrecisiondef" double precision DEFAULT 100,
|
||||
"real" real,
|
||||
"realdef" real DEFAULT 100,
|
||||
"time2" time(6) with time zone,
|
||||
"timedefnow" time DEFAULT now(),
|
||||
"timestamp" timestamp,
|
||||
"timestamp2" timestamp (6) with time zone,
|
||||
"timestamp3" timestamp with time zone,
|
||||
"timestamp4" timestamp (4),
|
||||
"timestampdef" timestamp DEFAULT now(),
|
||||
"date" date,
|
||||
"datedef" date DEFAULT now(),
|
||||
"interval" interval,
|
||||
"intervaldef" interval DEFAULT '10 days'
|
||||
);
|
||||
@@ -0,0 +1,5 @@
|
||||
CREATE TABLE IF NOT EXISTS "users12" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"name" text NOT NULL,
|
||||
"email" text NOT NULL
|
||||
);
|
||||
@@ -0,0 +1,275 @@
|
||||
{
|
||||
"version": "5",
|
||||
"dialect": "pg",
|
||||
"id": "cb1644bb-c5da-465a-8d70-f63d81e34514",
|
||||
"prevId": "00000000-0000-0000-0000-000000000000",
|
||||
"tables": {
|
||||
"all_columns": {
|
||||
"name": "all_columns",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"smallint": {
|
||||
"name": "smallint",
|
||||
"type": "smallint",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"smallint_def": {
|
||||
"name": "smallint_def",
|
||||
"type": "smallint",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"default": 10
|
||||
},
|
||||
"integer": {
|
||||
"name": "integer",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"integer_def": {
|
||||
"name": "integer_def",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"default": 10
|
||||
},
|
||||
"numeric": {
|
||||
"name": "numeric",
|
||||
"type": "numeric",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"numeric3": {
|
||||
"name": "numeric3",
|
||||
"type": "numeric",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"numeric4": {
|
||||
"name": "numeric4",
|
||||
"type": "numeric(7, 7)",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"numeridef": {
|
||||
"name": "numeridef",
|
||||
"type": "numeric",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"default": "'100'"
|
||||
},
|
||||
"bigint": {
|
||||
"name": "bigint",
|
||||
"type": "bigint",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"bigintdef": {
|
||||
"name": "bigintdef",
|
||||
"type": "bigint",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"default": 100
|
||||
},
|
||||
"boolean": {
|
||||
"name": "boolean",
|
||||
"type": "boolean",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"boolean_def": {
|
||||
"name": "boolean_def",
|
||||
"type": "boolean",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"default": true
|
||||
},
|
||||
"text": {
|
||||
"name": "text",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"textdef": {
|
||||
"name": "textdef",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"default": "'text'"
|
||||
},
|
||||
"varchar": {
|
||||
"name": "varchar",
|
||||
"type": "varchar",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"varchardef": {
|
||||
"name": "varchardef",
|
||||
"type": "varchar",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"default": "'text'"
|
||||
},
|
||||
"serial": {
|
||||
"name": "serial",
|
||||
"type": "serial",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"bigserial": {
|
||||
"name": "bigserial",
|
||||
"type": "bigserial",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"decimal": {
|
||||
"name": "decimal",
|
||||
"type": "numeric(100, 2)",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"decimaldef": {
|
||||
"name": "decimaldef",
|
||||
"type": "numeric(100, 2)",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"default": "'100.0'"
|
||||
},
|
||||
"doublePrecision": {
|
||||
"name": "doublePrecision",
|
||||
"type": "double precision",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"doublePrecisiondef": {
|
||||
"name": "doublePrecisiondef",
|
||||
"type": "double precision",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"default": 100
|
||||
},
|
||||
"real": {
|
||||
"name": "real",
|
||||
"type": "real",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"realdef": {
|
||||
"name": "realdef",
|
||||
"type": "real",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"default": 100
|
||||
},
|
||||
"time2": {
|
||||
"name": "time2",
|
||||
"type": "time(6) with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"timedefnow": {
|
||||
"name": "timedefnow",
|
||||
"type": "time",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"default": "now()"
|
||||
},
|
||||
"timestamp": {
|
||||
"name": "timestamp",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"timestamp2": {
|
||||
"name": "timestamp2",
|
||||
"type": "timestamp (6) with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"timestamp3": {
|
||||
"name": "timestamp3",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"timestamp4": {
|
||||
"name": "timestamp4",
|
||||
"type": "timestamp (4)",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"timestampdef": {
|
||||
"name": "timestampdef",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"default": "now()"
|
||||
},
|
||||
"date": {
|
||||
"name": "date",
|
||||
"type": "date",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"datedef": {
|
||||
"name": "datedef",
|
||||
"type": "date",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"default": "now()"
|
||||
},
|
||||
"interval": {
|
||||
"name": "interval",
|
||||
"type": "interval",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"intervaldef": {
|
||||
"name": "intervaldef",
|
||||
"type": "interval",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"default": "'10 days'"
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {}
|
||||
},
|
||||
"users12": {
|
||||
"name": "users12",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "serial",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"email": {
|
||||
"name": "email",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {}
|
||||
}
|
||||
},
|
||||
"enums": {},
|
||||
"schemas": {},
|
||||
"_meta": {
|
||||
"schemas": {},
|
||||
"tables": {},
|
||||
"columns": {}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"version": "5",
|
||||
"dialect": "pg",
|
||||
"entries": [
|
||||
{
|
||||
"idx": 0,
|
||||
"version": "5",
|
||||
"when": 1680271923328,
|
||||
"tag": "0000_puzzling_flatman",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 1,
|
||||
"version": "5",
|
||||
"when": 1680271923329,
|
||||
"tag": "0001_test",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
CREATE TABLE `drizzle_tests_cities_migration` (
|
||||
`id` int,
|
||||
`fullname_name` text,
|
||||
`state` text
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE `drizzle_tests_users_migration` (
|
||||
`id` int PRIMARY KEY NOT NULL,
|
||||
`full_name` text,
|
||||
`phone` int,
|
||||
`invited_by` int,
|
||||
`city_id` int,
|
||||
`date` timestamp DEFAULT (now())
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE `drizzle_tests_users12` (
|
||||
`id` serial AUTO_INCREMENT PRIMARY KEY NOT NULL,
|
||||
`name` text NOT NULL,
|
||||
`email` text NOT NULL
|
||||
);
|
||||
@@ -0,0 +1,132 @@
|
||||
{
|
||||
"version": "5",
|
||||
"dialect": "mysql",
|
||||
"id": "8e8c8378-0496-40f6-88e3-98aab8282b1f",
|
||||
"prevId": "00000000-0000-0000-0000-000000000000",
|
||||
"tables": {
|
||||
"drizzle_tests_cities_migration": {
|
||||
"name": "drizzle_tests_cities_migration",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "int",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"fullname_name": {
|
||||
"name": "fullname_name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"state": {
|
||||
"name": "state",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {}
|
||||
},
|
||||
"drizzle_tests_users_migration": {
|
||||
"name": "drizzle_tests_users_migration",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "int",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"full_name": {
|
||||
"name": "full_name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"phone": {
|
||||
"name": "phone",
|
||||
"type": "int",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"invited_by": {
|
||||
"name": "invited_by",
|
||||
"type": "int",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"city_id": {
|
||||
"name": "city_id",
|
||||
"type": "int",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"date": {
|
||||
"name": "date",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false,
|
||||
"default": "(now())"
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {}
|
||||
},
|
||||
"drizzle_tests_users12": {
|
||||
"name": "drizzle_tests_users12",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "serial",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": true
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"email": {
|
||||
"name": "email",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"my_unique_index": {
|
||||
"name": "my_unique_index",
|
||||
"columns": [
|
||||
"name"
|
||||
],
|
||||
"isUnique": true,
|
||||
"using": "btree"
|
||||
}
|
||||
},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {}
|
||||
}
|
||||
},
|
||||
"schemas": {},
|
||||
"_meta": {
|
||||
"schemas": {},
|
||||
"tables": {},
|
||||
"columns": {}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"version": "5",
|
||||
"dialect": "mysql",
|
||||
"entries": [
|
||||
{
|
||||
"idx": 0,
|
||||
"version": "5",
|
||||
"when": 1680270921944,
|
||||
"tag": "0000_nostalgic_carnage",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
CREATE TABLE `cities_migration` (
|
||||
`id` int,
|
||||
`fullname_name` text,
|
||||
`state` text
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE `users_migration` (
|
||||
`id` int PRIMARY KEY NOT NULL,
|
||||
`full_name` text,
|
||||
`phone` int,
|
||||
`invited_by` int,
|
||||
`city_id` int,
|
||||
`date` timestamp DEFAULT now()
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE `users12` (
|
||||
`id` serial AUTO_INCREMENT PRIMARY KEY NOT NULL,
|
||||
`name` text NOT NULL,
|
||||
`email` text NOT NULL
|
||||
);
|
||||
@@ -0,0 +1,132 @@
|
||||
{
|
||||
"version": "1",
|
||||
"dialect": "singlestore",
|
||||
"id": "8e8c8378-0496-40f6-88e3-98aab8282b1f",
|
||||
"prevId": "00000000-0000-0000-0000-000000000000",
|
||||
"tables": {
|
||||
"cities_migration": {
|
||||
"name": "cities_migration",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "int",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"fullname_name": {
|
||||
"name": "fullname_name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"state": {
|
||||
"name": "state",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {}
|
||||
},
|
||||
"users_migration": {
|
||||
"name": "users_migration",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "int",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"full_name": {
|
||||
"name": "full_name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"phone": {
|
||||
"name": "phone",
|
||||
"type": "int",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"invited_by": {
|
||||
"name": "invited_by",
|
||||
"type": "int",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"city_id": {
|
||||
"name": "city_id",
|
||||
"type": "int",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"date": {
|
||||
"name": "date",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {}
|
||||
},
|
||||
"users12": {
|
||||
"name": "users12",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "serial",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": true
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"email": {
|
||||
"name": "email",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"my_unique_index": {
|
||||
"name": "my_unique_index",
|
||||
"columns": [
|
||||
"name"
|
||||
],
|
||||
"isUnique": true,
|
||||
"using": "btree"
|
||||
}
|
||||
},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {}
|
||||
}
|
||||
},
|
||||
"schemas": {},
|
||||
"_meta": {
|
||||
"schemas": {},
|
||||
"tables": {},
|
||||
"columns": {}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"version": "1",
|
||||
"dialect": "singlestore",
|
||||
"entries": [
|
||||
{
|
||||
"idx": 0,
|
||||
"version": "1",
|
||||
"when": 1680270921944,
|
||||
"tag": "0000_nostalgic_carnage",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
CREATE TABLE another_users (
|
||||
`id` integer PRIMARY KEY NOT NULL,
|
||||
`name` text NOT NULL,
|
||||
`email` text NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE users12 (
|
||||
`id` integer PRIMARY KEY NOT NULL,
|
||||
`name` text NOT NULL,
|
||||
`email` text NOT NULL
|
||||
);
|
||||
@@ -0,0 +1,123 @@
|
||||
{
|
||||
"version": "5",
|
||||
"dialect": "sqlite",
|
||||
"id": "b6793a90-b553-4a5d-9d2f-b48e56ef2a2a",
|
||||
"prevId": "00000000-0000-0000-0000-000000000000",
|
||||
"tables": {
|
||||
"cities": {
|
||||
"name": "cities",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"fullname_name": {
|
||||
"name": "fullname_name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"state": {
|
||||
"name": "state",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {
|
||||
"cities_id_fullname_name_pk": {
|
||||
"columns": [
|
||||
"id",
|
||||
"fullname_name"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"test_get": {
|
||||
"name": "test_get",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "integer",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"full_name": {
|
||||
"name": "full_name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {}
|
||||
},
|
||||
"users": {
|
||||
"name": "users",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "integer",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"full_name": {
|
||||
"name": "full_name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"phone": {
|
||||
"name": "phone",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"invited_by": {
|
||||
"name": "invited_by",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"city_id": {
|
||||
"name": "city_id",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"users_city_id_cities_id_fk": {
|
||||
"name": "users_city_id_cities_id_fk",
|
||||
"tableFrom": "users",
|
||||
"tableTo": "cities",
|
||||
"columnsFrom": [
|
||||
"city_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
]
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {}
|
||||
}
|
||||
},
|
||||
"enums": {},
|
||||
"_meta": {
|
||||
"schemas": {},
|
||||
"tables": {},
|
||||
"columns": {}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"version": "5",
|
||||
"dialect": "sqlite",
|
||||
"entries": [
|
||||
{
|
||||
"idx": 0,
|
||||
"version": "5",
|
||||
"when": 1680202256473,
|
||||
"tag": "0000_fancy_bug",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
require('dotenv/config');
|
||||
const Database = require('better-sqlite3');
|
||||
const { drizzle } = require('drizzle-orm/better-sqlite3');
|
||||
const { sqlite: schema } = require('./schema.cjs');
|
||||
import { describe, expect } from 'vitest';
|
||||
|
||||
describe('better-sqlite3', async (it) => {
|
||||
it('drizzle()', async () => {
|
||||
const db = drizzle();
|
||||
|
||||
await db.$client.exec('SELECT 1;');
|
||||
|
||||
await db.$client.close();
|
||||
});
|
||||
|
||||
it('drizzle(string)', async () => {
|
||||
const db = drizzle(':memory:');
|
||||
|
||||
await db.$client.exec('SELECT 1;');
|
||||
|
||||
await db.$client.close();
|
||||
});
|
||||
|
||||
it('drizzle(string, config)', async () => {
|
||||
const db = drizzle(':memory:', {
|
||||
schema,
|
||||
});
|
||||
|
||||
await db.$client.exec('SELECT 1;');
|
||||
|
||||
await db.$client.close();
|
||||
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
});
|
||||
|
||||
it('drizzle({connection: string, ...config})', async () => {
|
||||
const db = drizzle({
|
||||
connection: ':memory:',
|
||||
schema,
|
||||
});
|
||||
|
||||
await db.$client.exec('SELECT 1;');
|
||||
|
||||
await db.$client.close();
|
||||
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
});
|
||||
|
||||
it('drizzle({connection: params, ...config})', async () => {
|
||||
const db = drizzle({
|
||||
connection: {
|
||||
source: ':memory:',
|
||||
},
|
||||
schema,
|
||||
});
|
||||
|
||||
await db.$client.exec('SELECT 1;');
|
||||
|
||||
await db.$client.close();
|
||||
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
});
|
||||
|
||||
it('drizzle({connection: {}, ...config})', async () => {
|
||||
const db = drizzle({
|
||||
connection: {},
|
||||
schema,
|
||||
});
|
||||
|
||||
await db.$client.exec('SELECT 1;');
|
||||
|
||||
await db.$client.close();
|
||||
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
});
|
||||
|
||||
it('drizzle({...config})', async () => {
|
||||
const db = drizzle({
|
||||
schema,
|
||||
});
|
||||
|
||||
await db.$client.exec('SELECT 1;');
|
||||
|
||||
await db.$client.close();
|
||||
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
});
|
||||
|
||||
it('drizzle(client)', async () => {
|
||||
const client = new Database(':memory:');
|
||||
const db = drizzle(client);
|
||||
|
||||
await db.$client.exec('SELECT 1;');
|
||||
|
||||
await db.$client.close();
|
||||
});
|
||||
|
||||
it('drizzle(client, config)', async () => {
|
||||
const client = new Database(':memory:');
|
||||
const db = drizzle(client, {
|
||||
schema,
|
||||
});
|
||||
|
||||
await db.$client.exec('SELECT 1;');
|
||||
|
||||
await db.$client.close();
|
||||
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
});
|
||||
|
||||
it('drizzle({client, ...config})', async () => {
|
||||
const client = new Database(':memory:');
|
||||
const db = drizzle({
|
||||
client,
|
||||
schema,
|
||||
});
|
||||
|
||||
await db.$client.exec('SELECT 1;');
|
||||
|
||||
await db.$client.close();
|
||||
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,97 @@
|
||||
require('dotenv/config');
|
||||
const { createClient } = require('@libsql/client');
|
||||
const { drizzle } = require('drizzle-orm/libsql');
|
||||
const { sqlite: schema } = require('./schema.cjs');
|
||||
import { describe, expect } from 'vitest';
|
||||
|
||||
describe('libsql', async (it) => {
|
||||
it('drizzle(string)', async () => {
|
||||
const db = drizzle(':memory:');
|
||||
|
||||
await db.$client.execute('SELECT 1;');
|
||||
|
||||
await db.$client.close();
|
||||
});
|
||||
|
||||
it('drizzle(string, config)', async () => {
|
||||
const db = drizzle(':memory:', {
|
||||
schema,
|
||||
});
|
||||
|
||||
await db.$client.execute('SELECT 1;');
|
||||
|
||||
await db.$client.close();
|
||||
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
});
|
||||
|
||||
it('drizzle({connection: string, ...config})', async () => {
|
||||
const db = drizzle({
|
||||
connection: ':memory:',
|
||||
schema,
|
||||
});
|
||||
|
||||
await db.$client.execute('SELECT 1;');
|
||||
|
||||
await db.$client.close();
|
||||
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
});
|
||||
|
||||
it('drizzle({connection: params, ...config})', async () => {
|
||||
const db = drizzle({
|
||||
connection: {
|
||||
url: ':memory:',
|
||||
},
|
||||
schema,
|
||||
});
|
||||
|
||||
await db.$client.execute('SELECT 1;');
|
||||
|
||||
await db.$client.close();
|
||||
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
});
|
||||
|
||||
it('drizzle(client)', async () => {
|
||||
const client = createClient({
|
||||
url: ':memory:',
|
||||
});
|
||||
const db = drizzle(client);
|
||||
|
||||
await db.$client.execute('SELECT 1;');
|
||||
|
||||
await db.$client.close();
|
||||
});
|
||||
|
||||
it('drizzle(client, config)', async () => {
|
||||
const client = createClient({
|
||||
url: ':memory:',
|
||||
});
|
||||
const db = drizzle(client, {
|
||||
schema,
|
||||
});
|
||||
|
||||
await db.$client.execute('SELECT 1;');
|
||||
|
||||
await db.$client.close();
|
||||
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
});
|
||||
|
||||
it('drizzle({client, ...config})', async () => {
|
||||
const client = createClient({
|
||||
url: ':memory:',
|
||||
});
|
||||
const db = drizzle({
|
||||
client,
|
||||
schema,
|
||||
});
|
||||
|
||||
await db.$client.execute('SELECT 1;');
|
||||
|
||||
await db.$client.close();
|
||||
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,153 @@
|
||||
require('dotenv/config');
|
||||
const { drizzle } = require('drizzle-orm/mysql2');
|
||||
const { createPool, createConnection, Connection } = require('mysql2');
|
||||
const { mysql: schema } = require('./schema.cjs');
|
||||
import { describe, expect } from 'vitest';
|
||||
|
||||
if (!process.env['MYSQL_CONNECTION_STRING']) {
|
||||
throw new Error('MYSQL_CONNECTION_STRING is not defined');
|
||||
}
|
||||
|
||||
describe('mysql2', async (it) => {
|
||||
it('drizzle(string)', async () => {
|
||||
const db = drizzle(
|
||||
process.env['MYSQL_CONNECTION_STRING'],
|
||||
);
|
||||
|
||||
await db.$client.execute(`SELECT 1`);
|
||||
|
||||
expect(db.$client.getConnection).not.toStrictEqual(undefined);
|
||||
});
|
||||
|
||||
it('drizzle(string, config)', async () => {
|
||||
const db = drizzle(
|
||||
process.env['MYSQL_CONNECTION_STRING'],
|
||||
{
|
||||
schema,
|
||||
mode: 'default',
|
||||
},
|
||||
);
|
||||
await db.$client.execute('SELECT 1;');
|
||||
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
expect(db.$client.getConnection).not.toStrictEqual(undefined);
|
||||
});
|
||||
|
||||
it('drizzle({connection: string, ...config})', async () => {
|
||||
const db = drizzle({
|
||||
connection: process.env['MYSQL_CONNECTION_STRING'],
|
||||
schema,
|
||||
mode: 'default',
|
||||
});
|
||||
|
||||
await db.$client.execute('SELECT 1;');
|
||||
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
expect(db.$client.getConnection).not.toStrictEqual(undefined);
|
||||
});
|
||||
|
||||
it('drizzle({connection: params, ...config})', async () => {
|
||||
const db = drizzle({
|
||||
connection: {
|
||||
uri: process.env['MYSQL_CONNECTION_STRING'],
|
||||
},
|
||||
schema,
|
||||
mode: 'default',
|
||||
});
|
||||
|
||||
await db.$client.execute('SELECT 1;');
|
||||
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
expect(db.$client.getConnection).not.toStrictEqual(undefined);
|
||||
});
|
||||
|
||||
it('drizzle(client)', async () => {
|
||||
const client = createPool({
|
||||
uri: process.env['MYSQL_CONNECTION_STRING'],
|
||||
});
|
||||
|
||||
const db = drizzle(client);
|
||||
|
||||
await db.$client.execute('SELECT 1;');
|
||||
|
||||
expect(db.$client.getConnection).not.toStrictEqual(undefined);
|
||||
});
|
||||
|
||||
it('drizzle(client, config)', async () => {
|
||||
const client = createPool({
|
||||
uri: process.env['MYSQL_CONNECTION_STRING'],
|
||||
});
|
||||
const db = drizzle(client, {
|
||||
schema,
|
||||
mode: 'default',
|
||||
});
|
||||
|
||||
await db.$client.execute('SELECT 1;');
|
||||
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
expect(db.$client.getConnection).not.toStrictEqual(undefined);
|
||||
});
|
||||
|
||||
it('drizzle({client, ...config})', async () => {
|
||||
const client = createPool({
|
||||
uri: process.env['MYSQL_CONNECTION_STRING'],
|
||||
});
|
||||
|
||||
const db = drizzle({
|
||||
client,
|
||||
schema,
|
||||
mode: 'default',
|
||||
});
|
||||
|
||||
await db.$client.execute('SELECT 1;');
|
||||
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
expect(db.$client.getConnection).not.toStrictEqual(undefined);
|
||||
});
|
||||
});
|
||||
|
||||
describe('mysql2:connection', async (it) => {
|
||||
it('drizzle(client)', async () => {
|
||||
const client = createConnection({
|
||||
uri: process.env['MYSQL_CONNECTION_STRING'],
|
||||
});
|
||||
|
||||
const db = drizzle(client);
|
||||
|
||||
await db.$client.execute('SELECT 1;');
|
||||
|
||||
expect(db.$client.getConnection).toStrictEqual(undefined);
|
||||
});
|
||||
|
||||
it('drizzle(client, config)', async () => {
|
||||
const client = createConnection({
|
||||
uri: process.env['MYSQL_CONNECTION_STRING'],
|
||||
});
|
||||
const db = drizzle(client, {
|
||||
schema,
|
||||
mode: 'default',
|
||||
});
|
||||
|
||||
await db.$client.execute('SELECT 1;');
|
||||
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
expect(db.$client.getConnection).toStrictEqual(undefined);
|
||||
});
|
||||
|
||||
it('drizzle({client, ...config})', async () => {
|
||||
const client = createConnection({
|
||||
uri: process.env['MYSQL_CONNECTION_STRING'],
|
||||
});
|
||||
|
||||
const db = drizzle({
|
||||
client,
|
||||
schema,
|
||||
mode: 'default',
|
||||
});
|
||||
|
||||
await db.$client.execute('SELECT 1;');
|
||||
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
expect(db.$client.getConnection).toStrictEqual(undefined);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,92 @@
|
||||
require('dotenv/config');
|
||||
const { neon: pg } = require('@neondatabase/serverless');
|
||||
const { drizzle } = require('drizzle-orm/neon-http');
|
||||
const { pg: schema } = require('./schema.cjs');
|
||||
import { describe, expect } from 'vitest';
|
||||
|
||||
if (!process.env['NEON_CONNECTION_STRING']) {
|
||||
throw new Error('NEON_CONNECTION_STRING is not defined');
|
||||
}
|
||||
|
||||
describe('neon-http', async (it) => {
|
||||
it('drizzle(string)', async () => {
|
||||
const db = drizzle(
|
||||
process.env['NEON_CONNECTION_STRING'],
|
||||
);
|
||||
|
||||
await db.$client('SELECT 1;');
|
||||
});
|
||||
|
||||
it('drizzle(string, config)', async () => {
|
||||
const db = drizzle(
|
||||
process.env['NEON_CONNECTION_STRING'],
|
||||
{
|
||||
schema,
|
||||
},
|
||||
);
|
||||
|
||||
await db.$client('SELECT 1;');
|
||||
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
});
|
||||
|
||||
it('drizzle({connection: string, ...config})', async () => {
|
||||
const db = drizzle({
|
||||
connection: process.env['NEON_CONNECTION_STRING'],
|
||||
schema,
|
||||
});
|
||||
|
||||
await db.$client('SELECT 1;');
|
||||
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
});
|
||||
|
||||
it('drizzle({connection: params, ...config})', async () => {
|
||||
const db = drizzle({
|
||||
connection: {
|
||||
connectionString: process.env['NEON_CONNECTION_STRING'],
|
||||
},
|
||||
schema,
|
||||
});
|
||||
|
||||
await db.$client('SELECT 1;');
|
||||
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
});
|
||||
|
||||
it('drizzle(client)', async () => {
|
||||
const client = pg(
|
||||
process.env['NEON_CONNECTION_STRING'],
|
||||
);
|
||||
const db = drizzle(client);
|
||||
|
||||
await db.$client('SELECT 1;');
|
||||
});
|
||||
|
||||
it('drizzle(client, config)', async () => {
|
||||
const client = pg(
|
||||
process.env['NEON_CONNECTION_STRING'],
|
||||
);
|
||||
const db = drizzle(client, {
|
||||
schema,
|
||||
});
|
||||
|
||||
await db.$client('SELECT 1;');
|
||||
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
});
|
||||
|
||||
it('drizzle({client, ...config})', async () => {
|
||||
const client = pg(
|
||||
process.env['NEON_CONNECTION_STRING'],
|
||||
);
|
||||
const db = drizzle({
|
||||
client,
|
||||
schema,
|
||||
});
|
||||
|
||||
await db.$client('SELECT 1;');
|
||||
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,211 @@
|
||||
require('dotenv/config');
|
||||
const { neonConfig, Pool, Client } = require('@neondatabase/serverless');
|
||||
const { drizzle } = require('drizzle-orm/neon-serverless');
|
||||
const { pg: schema } = require('./schema.cjs');
|
||||
const ws = require('ws');
|
||||
import { describe, expect } from 'vitest';
|
||||
|
||||
neonConfig.webSocketConstructor = ws;
|
||||
|
||||
if (!process.env['NEON_CONNECTION_STRING']) {
|
||||
throw new Error('NEON_CONNECTION_STRING is not defined');
|
||||
}
|
||||
|
||||
describe('neon-ws', async (it) => {
|
||||
it('drizzle(string)', async () => {
|
||||
const db = drizzle(
|
||||
process.env['NEON_CONNECTION_STRING'],
|
||||
);
|
||||
|
||||
await db.$client.query('SELECT 1;');
|
||||
});
|
||||
|
||||
it('drizzle(string, config)', async () => {
|
||||
const db = drizzle(
|
||||
process.env['NEON_CONNECTION_STRING'],
|
||||
{
|
||||
schema,
|
||||
},
|
||||
);
|
||||
|
||||
await db.$client.query('SELECT 1;');
|
||||
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
expect(db.$client).toBeInstanceOf(Pool);
|
||||
});
|
||||
|
||||
it('drizzle({connection: string, ...config})', async () => {
|
||||
const db = drizzle({
|
||||
connection: process.env['NEON_CONNECTION_STRING'],
|
||||
schema,
|
||||
});
|
||||
|
||||
await db.$client.query('SELECT 1;');
|
||||
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
expect(db.$client).toBeInstanceOf(Pool);
|
||||
});
|
||||
|
||||
it('drizzle({connection: params, ...config})', async () => {
|
||||
const db = drizzle({
|
||||
connection: {
|
||||
connectionString: process.env['NEON_CONNECTION_STRING'],
|
||||
},
|
||||
schema,
|
||||
});
|
||||
|
||||
await db.$client.query('SELECT 1;');
|
||||
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
expect(db.$client).toBeInstanceOf(Pool);
|
||||
});
|
||||
|
||||
it('drizzle(client)', async () => {
|
||||
const client = new Pool({
|
||||
connectionString: process.env['NEON_CONNECTION_STRING'],
|
||||
});
|
||||
const db = drizzle(client);
|
||||
|
||||
await db.$client.query('SELECT 1;');
|
||||
|
||||
expect(db.$client).toBeInstanceOf(Pool);
|
||||
});
|
||||
|
||||
it('drizzle(client, config)', async () => {
|
||||
const client = new Pool({
|
||||
connectionString: process.env['NEON_CONNECTION_STRING'],
|
||||
});
|
||||
const db = drizzle(client, {
|
||||
schema,
|
||||
});
|
||||
|
||||
await db.$client.query('SELECT 1;');
|
||||
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
expect(db.$client).toBeInstanceOf(Pool);
|
||||
});
|
||||
|
||||
it('drizzle({client, ...config})', async () => {
|
||||
const client = new Pool({
|
||||
connectionString: process.env['NEON_CONNECTION_STRING'],
|
||||
});
|
||||
const db = drizzle({
|
||||
client,
|
||||
schema,
|
||||
});
|
||||
|
||||
await db.$client.query('SELECT 1;');
|
||||
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
expect(db.$client).toBeInstanceOf(Pool);
|
||||
});
|
||||
});
|
||||
|
||||
describe('neon-ws:Client', async (it) => {
|
||||
it('drizzle(client)', async () => {
|
||||
const client = new Client({
|
||||
connectionString: process.env['NEON_CONNECTION_STRING'],
|
||||
});
|
||||
|
||||
await client.connect();
|
||||
|
||||
const db = drizzle(client);
|
||||
|
||||
await db.$client.query('SELECT 1;');
|
||||
|
||||
expect(db.$client).toBeInstanceOf(Client);
|
||||
expect(db.$client).not.toBeInstanceOf(Pool);
|
||||
});
|
||||
|
||||
it('drizzle(client, config)', async () => {
|
||||
const client = new Client({
|
||||
connectionString: process.env['NEON_CONNECTION_STRING'],
|
||||
});
|
||||
const db = drizzle(client, {
|
||||
schema,
|
||||
});
|
||||
|
||||
await client.connect();
|
||||
|
||||
await db.$client.query('SELECT 1;');
|
||||
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
expect(db.$client).toBeInstanceOf(Client);
|
||||
expect(db.$client).not.toBeInstanceOf(Pool);
|
||||
});
|
||||
|
||||
it('drizzle({client, ...config})', async () => {
|
||||
const client = new Client({
|
||||
connectionString: process.env['NEON_CONNECTION_STRING'],
|
||||
});
|
||||
const db = drizzle({
|
||||
client,
|
||||
schema,
|
||||
});
|
||||
|
||||
await client.connect();
|
||||
|
||||
await db.$client.query('SELECT 1;');
|
||||
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
expect(db.$client).toBeInstanceOf(Client);
|
||||
expect(db.$client).not.toBeInstanceOf(Pool);
|
||||
});
|
||||
});
|
||||
|
||||
describe('neon-ws:PoolClient', async (it) => {
|
||||
it('drizzle(client)', async () => {
|
||||
const pool = new Pool({
|
||||
connectionString: process.env['NEON_CONNECTION_STRING'],
|
||||
});
|
||||
const client = await pool.connect();
|
||||
|
||||
const db = drizzle(client);
|
||||
|
||||
await db.$client.query('SELECT 1;');
|
||||
|
||||
client.release();
|
||||
|
||||
expect(db.$client).toBeInstanceOf(Client);
|
||||
expect(db.$client).not.toBeInstanceOf(Pool);
|
||||
});
|
||||
|
||||
it('drizzle(client, config)', async () => {
|
||||
const pool = new Pool({
|
||||
connectionString: process.env['NEON_CONNECTION_STRING'],
|
||||
});
|
||||
const client = await pool.connect();
|
||||
|
||||
const db = drizzle(client, {
|
||||
schema,
|
||||
});
|
||||
|
||||
await db.$client.query('SELECT 1;');
|
||||
|
||||
client.release();
|
||||
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
expect(db.$client).toBeInstanceOf(Client);
|
||||
expect(db.$client).not.toBeInstanceOf(Pool);
|
||||
});
|
||||
|
||||
it('drizzle({client, ...config})', async () => {
|
||||
const pool = new Pool({
|
||||
connectionString: process.env['NEON_CONNECTION_STRING'],
|
||||
});
|
||||
const client = await pool.connect();
|
||||
|
||||
const db = drizzle({
|
||||
client,
|
||||
schema,
|
||||
});
|
||||
|
||||
await db.$client.query('SELECT 1;');
|
||||
|
||||
client.release();
|
||||
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
expect(db.$client).toBeInstanceOf(Client);
|
||||
expect(db.$client).not.toBeInstanceOf(Pool);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,201 @@
|
||||
require('dotenv/config');
|
||||
const { drizzle } = require('drizzle-orm/node-postgres');
|
||||
const pg = require('pg');
|
||||
const { pg: schema } = require('./schema.cjs');
|
||||
import { describe, expect } from 'vitest';
|
||||
|
||||
const Pool = pg.Pool;
|
||||
const Client = pg.Client;
|
||||
|
||||
if (!process.env['PG_CONNECTION_STRING']) {
|
||||
throw new Error('PG_CONNECTION_STRING is not defined');
|
||||
}
|
||||
|
||||
describe('node-pg', async (it) => {
|
||||
it('drizzle(string)', async () => {
|
||||
const db = drizzle(process.env['PG_CONNECTION_STRING']);
|
||||
|
||||
await db.$client.query('SELECT 1;');
|
||||
|
||||
expect(db.$client).toBeInstanceOf(Pool);
|
||||
});
|
||||
|
||||
it('drizzle(string, config)', async () => {
|
||||
const db = drizzle(process.env['PG_CONNECTION_STRING'], {
|
||||
schema,
|
||||
});
|
||||
|
||||
await db.$client.query('SELECT 1;');
|
||||
|
||||
expect(db.$client).toBeInstanceOf(Pool);
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
});
|
||||
|
||||
it('drizzle({connection: string, ...config})', async () => {
|
||||
const db = drizzle({
|
||||
connection: process.env['PG_CONNECTION_STRING'],
|
||||
schema,
|
||||
});
|
||||
|
||||
await db.$client.query('SELECT 1;');
|
||||
|
||||
expect(db.$client).toBeInstanceOf(Pool);
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
});
|
||||
|
||||
it('drizzle({connection: params, ...config})', async () => {
|
||||
const db = drizzle({
|
||||
connection: {
|
||||
connectionString: process.env['PG_CONNECTION_STRING'],
|
||||
},
|
||||
schema,
|
||||
});
|
||||
|
||||
await db.$client.query('SELECT 1;');
|
||||
|
||||
expect(db.$client).toBeInstanceOf(Pool);
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
});
|
||||
|
||||
it('drizzle(client)', async () => {
|
||||
const client = new Pool({
|
||||
connectionString: process.env['PG_CONNECTION_STRING'],
|
||||
});
|
||||
const db = drizzle(client);
|
||||
|
||||
await db.$client.query('SELECT 1;');
|
||||
|
||||
expect(db.$client).toBeInstanceOf(Pool);
|
||||
});
|
||||
|
||||
it('drizzle(client, config)', async () => {
|
||||
const client = new Pool({
|
||||
connectionString: process.env['PG_CONNECTION_STRING'],
|
||||
});
|
||||
const db = drizzle(client, {
|
||||
schema,
|
||||
});
|
||||
|
||||
await db.$client.query('SELECT 1;');
|
||||
|
||||
expect(db.$client).toBeInstanceOf(Pool);
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
});
|
||||
|
||||
it('drizzle({client, ...config})', async () => {
|
||||
const client = new Pool({
|
||||
connectionString: process.env['PG_CONNECTION_STRING'],
|
||||
});
|
||||
const db = drizzle({
|
||||
client,
|
||||
schema,
|
||||
});
|
||||
|
||||
await db.$client.query('SELECT 1;');
|
||||
|
||||
expect(db.$client).toBeInstanceOf(Pool);
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
});
|
||||
});
|
||||
|
||||
describe('node-pg:Client', async (it) => {
|
||||
it('drizzle(client)', async () => {
|
||||
const client = new Client({
|
||||
connectionString: process.env['PG_CONNECTION_STRING'],
|
||||
});
|
||||
const db = drizzle(client);
|
||||
|
||||
await client.connect();
|
||||
|
||||
await db.$client.query('SELECT 1;');
|
||||
|
||||
expect(db.$client).not.toBeInstanceOf(Pool);
|
||||
expect(db.$client).toBeInstanceOf(Client);
|
||||
});
|
||||
|
||||
it('drizzle(client, config)', async () => {
|
||||
const client = new Client({
|
||||
connectionString: process.env['PG_CONNECTION_STRING'],
|
||||
});
|
||||
const db = drizzle(client, {
|
||||
schema,
|
||||
});
|
||||
|
||||
await client.connect();
|
||||
|
||||
await db.$client.query('SELECT 1;');
|
||||
|
||||
expect(db.$client).not.toBeInstanceOf(Pool);
|
||||
expect(db.$client).toBeInstanceOf(Client);
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
});
|
||||
|
||||
it('drizzle({client, ...config})', async () => {
|
||||
const client = new Client({
|
||||
connectionString: process.env['PG_CONNECTION_STRING'],
|
||||
});
|
||||
const db = drizzle({
|
||||
client,
|
||||
schema,
|
||||
});
|
||||
|
||||
await client.connect();
|
||||
|
||||
await db.$client.query('SELECT 1;');
|
||||
|
||||
expect(db.$client).not.toBeInstanceOf(Pool);
|
||||
expect(db.$client).toBeInstanceOf(Client);
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
});
|
||||
});
|
||||
|
||||
describe('node-pg:PoolClient', async (it) => {
|
||||
it('drizzle(client)', async () => {
|
||||
const pool = new Pool({
|
||||
connectionString: process.env['PG_CONNECTION_STRING'],
|
||||
});
|
||||
const client = await pool.connect();
|
||||
const db = drizzle(client);
|
||||
|
||||
await db.$client.query('SELECT 1;');
|
||||
client.release();
|
||||
|
||||
expect(db.$client).not.toBeInstanceOf(Pool);
|
||||
expect(db.$client).toBeInstanceOf(Client);
|
||||
});
|
||||
|
||||
it('drizzle(client, config)', async () => {
|
||||
const pool = new Pool({
|
||||
connectionString: process.env['PG_CONNECTION_STRING'],
|
||||
});
|
||||
const client = await pool.connect();
|
||||
const db = drizzle(client, {
|
||||
schema,
|
||||
});
|
||||
|
||||
await db.$client.query('SELECT 1;');
|
||||
client.release();
|
||||
|
||||
expect(db.$client).not.toBeInstanceOf(Pool);
|
||||
expect(db.$client).toBeInstanceOf(Client);
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
});
|
||||
|
||||
it('drizzle({client, ...config})', async () => {
|
||||
const pool = new Pool({
|
||||
connectionString: process.env['PG_CONNECTION_STRING'],
|
||||
});
|
||||
const client = await pool.connect();
|
||||
const db = drizzle({
|
||||
client,
|
||||
schema,
|
||||
});
|
||||
|
||||
await db.$client.query('SELECT 1;');
|
||||
client.release();
|
||||
|
||||
expect(db.$client).not.toBeInstanceOf(Pool);
|
||||
expect(db.$client).toBeInstanceOf(Client);
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,88 @@
|
||||
require('dotenv/config');
|
||||
const { drizzle } = require('drizzle-orm/pglite');
|
||||
const { pg: schema } = require('./schema.cjs');
|
||||
const { PGlite: Database } = require('@electric-sql/pglite');
|
||||
import { describe, expect } from 'vitest';
|
||||
|
||||
describe('pglite', async (it) => {
|
||||
it('drizzle()', async () => {
|
||||
const db = drizzle();
|
||||
|
||||
await db.$client.exec('SELECT 1;');
|
||||
await db.$client.close();
|
||||
});
|
||||
|
||||
it('drizzle(string)', async () => {
|
||||
const db = drizzle('memory://');
|
||||
|
||||
await db.$client.exec('SELECT 1;');
|
||||
await db.$client.close();
|
||||
});
|
||||
|
||||
it('drizzle(string, config)', async () => {
|
||||
const db = drizzle('memory://', {
|
||||
schema,
|
||||
});
|
||||
|
||||
await db.$client.exec('SELECT 1;');
|
||||
await db.$client.close();
|
||||
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
});
|
||||
|
||||
it('drizzle({connection: {}, ...config})', async () => {
|
||||
const db = drizzle({
|
||||
connection: {},
|
||||
schema,
|
||||
});
|
||||
|
||||
await db.$client.exec('SELECT 1;');
|
||||
await db.$client.close();
|
||||
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
});
|
||||
|
||||
it('drizzle({...config})', async () => {
|
||||
const db = drizzle({
|
||||
schema,
|
||||
});
|
||||
|
||||
await db.$client.exec('SELECT 1;');
|
||||
await db.$client.close();
|
||||
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
});
|
||||
|
||||
it('drizzle(client)', async () => {
|
||||
const client = new Database('memory://');
|
||||
const db = drizzle(client);
|
||||
|
||||
await db.$client.exec('SELECT 1;');
|
||||
await db.$client.close();
|
||||
});
|
||||
|
||||
it('drizzle(client, config)', async () => {
|
||||
const client = new Database('memory://');
|
||||
const db = drizzle(client, {
|
||||
schema,
|
||||
});
|
||||
|
||||
await db.$client.exec('SELECT 1;');
|
||||
await db.$client.close();
|
||||
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
});
|
||||
|
||||
it('drizzle({client, ...config})', async () => {
|
||||
const client = new Database('memory://');
|
||||
const db = drizzle({
|
||||
client,
|
||||
schema,
|
||||
});
|
||||
|
||||
await db.$client.exec('SELECT 1;');
|
||||
await db.$client.close();
|
||||
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,101 @@
|
||||
require('dotenv/config');
|
||||
const { Client } = require('@planetscale/database');
|
||||
const { drizzle } = require('drizzle-orm/planetscale-serverless');
|
||||
const { mysql: schema } = require('./schema.cjs');
|
||||
import { describe, expect } from 'vitest';
|
||||
|
||||
if (!process.env['PLANETSCALE_CONNECTION_STRING']) {
|
||||
throw new Error('PLANETSCALE_CONNECTION_STRING is not defined');
|
||||
}
|
||||
|
||||
describe('planetscale', async (it) => {
|
||||
it('drizzle(string)', async () => {
|
||||
const db = drizzle(
|
||||
process.env['PLANETSCALE_CONNECTION_STRING'],
|
||||
);
|
||||
|
||||
await db.$client.execute('SELECT 1;');
|
||||
|
||||
expect(db.$client).toBeInstanceOf(Client);
|
||||
});
|
||||
|
||||
it('drizzle(string, config)', async () => {
|
||||
const db = drizzle(
|
||||
process.env['PLANETSCALE_CONNECTION_STRING'],
|
||||
{
|
||||
schema,
|
||||
},
|
||||
);
|
||||
|
||||
await db.$client.execute('SELECT 1;');
|
||||
|
||||
expect(db.$client).toBeInstanceOf(Client);
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
});
|
||||
|
||||
it('drizzle({connection: string, ...config})', async () => {
|
||||
const db = drizzle({
|
||||
connection: process.env['PLANETSCALE_CONNECTION_STRING'],
|
||||
schema,
|
||||
});
|
||||
|
||||
await db.$client.execute('SELECT 1;');
|
||||
|
||||
expect(db.$client).toBeInstanceOf(Client);
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
});
|
||||
|
||||
it('drizzle({connection: params, ...config})', async () => {
|
||||
const db = drizzle({
|
||||
connection: {
|
||||
url: process.env['PLANETSCALE_CONNECTION_STRING'],
|
||||
},
|
||||
schema,
|
||||
});
|
||||
|
||||
await db.$client.execute('SELECT 1;');
|
||||
|
||||
expect(db.$client).toBeInstanceOf(Client);
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
});
|
||||
|
||||
it('drizzle(client)', async () => {
|
||||
const client = new Client({
|
||||
url: process.env['PLANETSCALE_CONNECTION_STRING'],
|
||||
});
|
||||
const db = drizzle(client);
|
||||
|
||||
await db.$client.execute('SELECT 1;');
|
||||
|
||||
expect(db.$client).toBeInstanceOf(Client);
|
||||
});
|
||||
|
||||
it('drizzle(client, config)', async () => {
|
||||
const client = new Client({
|
||||
url: process.env['PLANETSCALE_CONNECTION_STRING'],
|
||||
});
|
||||
const db = drizzle(client, {
|
||||
schema,
|
||||
});
|
||||
|
||||
await db.$client.execute('SELECT 1;');
|
||||
|
||||
expect(db.$client).toBeInstanceOf(Client);
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
});
|
||||
|
||||
it('drizzle({client, ...config})', async () => {
|
||||
const client = new Client({
|
||||
url: process.env['PLANETSCALE_CONNECTION_STRING'],
|
||||
});
|
||||
const db = drizzle({
|
||||
client,
|
||||
schema,
|
||||
});
|
||||
|
||||
await db.$client.execute('SELECT 1;');
|
||||
|
||||
expect(db.$client).toBeInstanceOf(Client);
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,81 @@
|
||||
require('dotenv/config');
|
||||
const { drizzle } = require('drizzle-orm/postgres-js');
|
||||
const pg = require('postgres');
|
||||
const { pg: schema } = require('./schema.cjs');
|
||||
import { describe, expect } from 'vitest';
|
||||
|
||||
if (!process.env['PG_CONNECTION_STRING']) {
|
||||
throw new Error('PG_CONNECTION_STRING is not defined');
|
||||
}
|
||||
|
||||
describe('postgres-js', async (it) => {
|
||||
it('drizzle(string)', async () => {
|
||||
const db = drizzle(process.env['PG_CONNECTION_STRING']);
|
||||
|
||||
await db.$client.unsafe('SELECT 1;');
|
||||
});
|
||||
|
||||
it('drizzle(string, config)', async () => {
|
||||
const db = drizzle(process.env['PG_CONNECTION_STRING'], {
|
||||
schema,
|
||||
});
|
||||
|
||||
await db.$client.unsafe('SELECT 1;');
|
||||
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
});
|
||||
|
||||
it('drizzle({connection: string, ...config})', async () => {
|
||||
const db = drizzle({
|
||||
connection: process.env['PG_CONNECTION_STRING'],
|
||||
schema,
|
||||
});
|
||||
|
||||
await db.$client.unsafe('SELECT 1;');
|
||||
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
});
|
||||
|
||||
it('drizzle({connection: params, ...config})', async () => {
|
||||
const db = drizzle({
|
||||
connection: {
|
||||
url: process.env['PG_CONNECTION_STRING'],
|
||||
},
|
||||
schema,
|
||||
});
|
||||
|
||||
await db.$client.unsafe('SELECT 1;');
|
||||
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
});
|
||||
|
||||
it('drizzle(client)', async () => {
|
||||
const client = pg(process.env['PG_CONNECTION_STRING']);
|
||||
const db = drizzle(client);
|
||||
|
||||
await db.$client.unsafe('SELECT 1;');
|
||||
});
|
||||
|
||||
it('drizzle(client, config)', async () => {
|
||||
const client = pg(process.env['PG_CONNECTION_STRING']);
|
||||
const db = drizzle(client, {
|
||||
schema,
|
||||
});
|
||||
|
||||
await db.$client.unsafe('SELECT 1;');
|
||||
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
});
|
||||
|
||||
it('drizzle({client, ...config})', async () => {
|
||||
const client = pg(process.env['PG_CONNECTION_STRING']);
|
||||
const db = drizzle({
|
||||
client,
|
||||
schema,
|
||||
});
|
||||
|
||||
await db.$client.unsafe('SELECT 1;');
|
||||
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,21 @@
|
||||
const { int: mysqlInt, mysqlTable } = require('drizzle-orm/mysql-core');
|
||||
const { integer: pgInt, pgTable } = require('drizzle-orm/pg-core');
|
||||
const { integer: sqliteInt, sqliteTable } = require('drizzle-orm/sqlite-core');
|
||||
|
||||
module.exports.sqlite = {
|
||||
User: sqliteTable('test', {
|
||||
id: sqliteInt('id').primaryKey().notNull(),
|
||||
}),
|
||||
};
|
||||
|
||||
module.exports.pg = {
|
||||
User: pgTable('test', {
|
||||
id: pgInt('id').primaryKey().notNull(),
|
||||
}),
|
||||
};
|
||||
|
||||
module.exports.mysql = {
|
||||
User: mysqlTable('test', {
|
||||
id: mysqlInt('id').primaryKey().notNull(),
|
||||
}),
|
||||
};
|
||||
@@ -0,0 +1,88 @@
|
||||
require('dotenv/config');
|
||||
const { connect } = require('@tidbcloud/serverless');
|
||||
const { drizzle } = require('drizzle-orm/tidb-serverless');
|
||||
const { mysql: schema } = require('./schema.cjs');
|
||||
import { describe, expect } from 'vitest';
|
||||
|
||||
if (!process.env['TIDB_CONNECTION_STRING']) {
|
||||
throw new Error('TIDB_CONNECTION_STRING is not defined');
|
||||
}
|
||||
|
||||
describe('tidb', async (it) => {
|
||||
it('drizzle(string)', async () => {
|
||||
const db = drizzle(
|
||||
process.env['TIDB_CONNECTION_STRING'],
|
||||
);
|
||||
|
||||
await db.$client.execute(`SELECT 1`);
|
||||
});
|
||||
|
||||
it('drizzle(string, config)', async () => {
|
||||
const db = drizzle(
|
||||
process.env['TIDB_CONNECTION_STRING'],
|
||||
{
|
||||
schema,
|
||||
},
|
||||
);
|
||||
|
||||
await db.$client.execute('SELECT 1;');
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
});
|
||||
|
||||
it('drizzle({connection: string, ...config})', async () => {
|
||||
const db = drizzle({
|
||||
connection: process.env['TIDB_CONNECTION_STRING'],
|
||||
schema,
|
||||
});
|
||||
|
||||
await db.$client.execute('SELECT 1;');
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
});
|
||||
|
||||
it('drizzle({connection: params, ...config})', async () => {
|
||||
const db = drizzle({
|
||||
connection: {
|
||||
url: process.env['TIDB_CONNECTION_STRING'],
|
||||
},
|
||||
schema,
|
||||
});
|
||||
|
||||
await db.$client.execute('SELECT 1;');
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
});
|
||||
|
||||
it('drizzle(client)', async () => {
|
||||
const client = connect({
|
||||
url: process.env['TIDB_CONNECTION_STRING'],
|
||||
});
|
||||
|
||||
const db = drizzle(client);
|
||||
|
||||
await db.$client.execute('SELECT 1;');
|
||||
});
|
||||
|
||||
it('drizzle(client, config)', async () => {
|
||||
const client = connect({
|
||||
url: process.env['TIDB_CONNECTION_STRING'],
|
||||
});
|
||||
const db = drizzle(client, {
|
||||
schema,
|
||||
});
|
||||
|
||||
await db.$client.execute('SELECT 1;');
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
});
|
||||
|
||||
it('drizzle({client, ...config})', async () => {
|
||||
const client = connect({
|
||||
url: process.env['TIDB_CONNECTION_STRING'],
|
||||
});
|
||||
const db = drizzle({
|
||||
client,
|
||||
schema,
|
||||
});
|
||||
|
||||
await db.$client.execute('SELECT 1;');
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,229 @@
|
||||
require('dotenv/config');
|
||||
const vc = require('@vercel/postgres');
|
||||
const { drizzle } = require('drizzle-orm/vercel-postgres');
|
||||
const { pg: schema } = require('./schema.cjs');
|
||||
import { describe, expect } from 'vitest';
|
||||
const { sql, createClient, createPool } = vc;
|
||||
|
||||
const Pool = vc.VercelPool;
|
||||
const Client = vc.VercelClient;
|
||||
|
||||
if (!process.env['VERCEL_CONNECTION_STRING']) {
|
||||
throw new Error('VERCEL_CONNECTION_STRING is not defined');
|
||||
}
|
||||
|
||||
// Used for non-pooled connection
|
||||
if (!process.env['NEON_CONNECTION_STRING']) {
|
||||
throw new Error('NEON_CONNECTION_STRING is not defined');
|
||||
}
|
||||
process.env['POSTGRES_URL'] = process.env['VERCEL_CONNECTION_STRING'];
|
||||
|
||||
describe('vercel:sql', async (it) => {
|
||||
it('drizzle()', async () => {
|
||||
const db = drizzle();
|
||||
|
||||
await sql.connect();
|
||||
|
||||
await db.$client.query('SELECT 1;');
|
||||
|
||||
expect(db.$client).toBeTypeOf('function');
|
||||
});
|
||||
|
||||
it('drizzle(client)', async () => {
|
||||
const db = drizzle(sql);
|
||||
|
||||
await db.$client.query('SELECT 1;');
|
||||
|
||||
expect(db.$client).toBeTypeOf('function');
|
||||
});
|
||||
|
||||
it('drizzle(client, config)', async () => {
|
||||
const db = drizzle(sql, {
|
||||
schema,
|
||||
});
|
||||
|
||||
await db.$client.query('SELECT 1;');
|
||||
|
||||
expect(db.$client).toBeTypeOf('function');
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
});
|
||||
|
||||
it('drizzle({client, ...config})', async () => {
|
||||
const db = drizzle({
|
||||
client: sql,
|
||||
schema,
|
||||
});
|
||||
|
||||
await db.$client.query('SELECT 1;');
|
||||
|
||||
expect(db.$client).toBeTypeOf('function');
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
});
|
||||
|
||||
it('drizzle({...config})', async () => {
|
||||
const db = drizzle({
|
||||
schema,
|
||||
});
|
||||
|
||||
await db.$client.query('SELECT 1;');
|
||||
|
||||
expect(db.$client).toBeTypeOf('function');
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
});
|
||||
});
|
||||
|
||||
describe('vercel:Pool', async (it) => {
|
||||
it('drizzle(client)', async () => {
|
||||
const client = createPool({
|
||||
connectionString: process.env['VERCEL_CONNECTION_STRING'],
|
||||
});
|
||||
const db = drizzle(client);
|
||||
|
||||
await db.$client.query('SELECT 1;');
|
||||
|
||||
expect(db.$client).not.toBeTypeOf('function');
|
||||
expect(db.$client).toBeInstanceOf(Pool);
|
||||
});
|
||||
|
||||
it('drizzle(client, config)', async () => {
|
||||
const client = createPool({
|
||||
connectionString: process.env['VERCEL_CONNECTION_STRING'],
|
||||
});
|
||||
const db = drizzle(client, {
|
||||
schema,
|
||||
});
|
||||
|
||||
await db.$client.query('SELECT 1;');
|
||||
|
||||
expect(db.$client).not.toBeTypeOf('function');
|
||||
expect(db.$client).toBeInstanceOf(Pool);
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
});
|
||||
|
||||
it('drizzle({client, ...config})', async () => {
|
||||
const client = createPool({
|
||||
connectionString: process.env['VERCEL_CONNECTION_STRING'],
|
||||
});
|
||||
const db = drizzle({
|
||||
client: client,
|
||||
schema,
|
||||
});
|
||||
|
||||
await db.$client.query('SELECT 1;');
|
||||
|
||||
expect(db.$client).not.toBeTypeOf('function');
|
||||
expect(db.$client).toBeInstanceOf(Pool);
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
});
|
||||
});
|
||||
|
||||
describe('vercel:Client', async (it) => {
|
||||
it('drizzle(client)', async () => {
|
||||
const client = createClient({
|
||||
connectionString: process.env['NEON_CONNECTION_STRING'],
|
||||
});
|
||||
const db = drizzle(client);
|
||||
|
||||
await client.connect();
|
||||
|
||||
await db.$client.query('SELECT 1;');
|
||||
|
||||
expect(db.$client).not.toBeTypeOf('function');
|
||||
expect(db.$client).not.toBeInstanceOf(Pool);
|
||||
expect(db.$client).toBeInstanceOf(Client);
|
||||
});
|
||||
|
||||
it('drizzle(client, config)', async () => {
|
||||
const client = createClient({
|
||||
connectionString: process.env['NEON_CONNECTION_STRING'],
|
||||
});
|
||||
const db = drizzle(client, {
|
||||
schema,
|
||||
});
|
||||
|
||||
await client.connect();
|
||||
|
||||
await db.$client.query('SELECT 1;');
|
||||
|
||||
expect(db.$client).not.toBeTypeOf('function');
|
||||
expect(db.$client).not.toBeInstanceOf(Pool);
|
||||
expect(db.$client).toBeInstanceOf(Client);
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
});
|
||||
|
||||
it('drizzle({client, ...config})', async () => {
|
||||
const client = createClient({
|
||||
connectionString: process.env['NEON_CONNECTION_STRING'],
|
||||
});
|
||||
const db = drizzle({
|
||||
client: client,
|
||||
schema,
|
||||
});
|
||||
|
||||
await client.connect();
|
||||
|
||||
await db.$client.query('SELECT 1;');
|
||||
|
||||
expect(db.$client).not.toBeTypeOf('function');
|
||||
expect(db.$client).not.toBeInstanceOf(Pool);
|
||||
expect(db.$client).toBeInstanceOf(Client);
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
});
|
||||
});
|
||||
|
||||
describe('vercel:PoolClient', async (it) => {
|
||||
it('drizzle(client)', async () => {
|
||||
const pool = createPool({
|
||||
connectionString: process.env['VERCEL_CONNECTION_STRING'],
|
||||
});
|
||||
const client = await pool.connect();
|
||||
|
||||
const db = drizzle(client);
|
||||
|
||||
await db.$client.query('SELECT 1;');
|
||||
client.release();
|
||||
|
||||
expect(db.$client).not.toBeTypeOf('function');
|
||||
expect(db.$client).not.toBeInstanceOf(Pool);
|
||||
expect(db.$client).toBeInstanceOf(Client);
|
||||
});
|
||||
|
||||
it('drizzle(client, config)', async () => {
|
||||
const pool = createPool({
|
||||
connectionString: process.env['VERCEL_CONNECTION_STRING'],
|
||||
});
|
||||
const client = await pool.connect();
|
||||
|
||||
const db = drizzle(client, {
|
||||
schema,
|
||||
});
|
||||
|
||||
await db.$client.query('SELECT 1;');
|
||||
client.release();
|
||||
|
||||
expect(db.$client).not.toBeTypeOf('function');
|
||||
expect(db.$client).not.toBeInstanceOf(Pool);
|
||||
expect(db.$client).toBeInstanceOf(Client);
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
});
|
||||
|
||||
it('drizzle({client, ...config})', async () => {
|
||||
const pool = createPool({
|
||||
connectionString: process.env['VERCEL_CONNECTION_STRING'],
|
||||
});
|
||||
const client = await pool.connect();
|
||||
|
||||
const db = drizzle({
|
||||
client: client,
|
||||
schema,
|
||||
});
|
||||
|
||||
await db.$client.query('SELECT 1;');
|
||||
client.release();
|
||||
|
||||
expect(db.$client).not.toBeTypeOf('function');
|
||||
expect(db.$client).not.toBeInstanceOf(Pool);
|
||||
expect(db.$client).toBeInstanceOf(Client);
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,124 @@
|
||||
import 'dotenv/config';
|
||||
import Database from 'better-sqlite3';
|
||||
import { drizzle } from 'drizzle-orm/better-sqlite3';
|
||||
import { describe, expect } from 'vitest';
|
||||
import { sqlite as schema } from './schema.mjs';
|
||||
|
||||
describe('better-sqlite3', async (it) => {
|
||||
it('drizzle()', async () => {
|
||||
const db = drizzle();
|
||||
|
||||
await db.$client.exec('SELECT 1;');
|
||||
|
||||
await db.$client.close();
|
||||
});
|
||||
|
||||
it('drizzle(string)', async () => {
|
||||
const db = drizzle(':memory:');
|
||||
|
||||
await db.$client.exec('SELECT 1;');
|
||||
|
||||
await db.$client.close();
|
||||
});
|
||||
|
||||
it('drizzle(string, config)', async () => {
|
||||
const db = drizzle(':memory:', {
|
||||
schema,
|
||||
});
|
||||
|
||||
await db.$client.exec('SELECT 1;');
|
||||
|
||||
await db.$client.close();
|
||||
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
});
|
||||
|
||||
it('drizzle({connection: string, ...config})', async () => {
|
||||
const db = drizzle({
|
||||
connection: ':memory:',
|
||||
schema,
|
||||
});
|
||||
|
||||
await db.$client.exec('SELECT 1;');
|
||||
|
||||
await db.$client.close();
|
||||
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
});
|
||||
|
||||
it('drizzle({connection: params, ...config})', async () => {
|
||||
const db = drizzle({
|
||||
connection: {
|
||||
source: ':memory:',
|
||||
},
|
||||
schema,
|
||||
});
|
||||
|
||||
await db.$client.exec('SELECT 1;');
|
||||
|
||||
await db.$client.close();
|
||||
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
});
|
||||
|
||||
it('drizzle({connection: {}, ...config})', async () => {
|
||||
const db = drizzle({
|
||||
connection: {},
|
||||
schema,
|
||||
});
|
||||
|
||||
await db.$client.exec('SELECT 1;');
|
||||
|
||||
await db.$client.close();
|
||||
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
});
|
||||
|
||||
it('drizzle({...config})', async () => {
|
||||
const db = drizzle({
|
||||
schema,
|
||||
});
|
||||
|
||||
await db.$client.exec('SELECT 1;');
|
||||
|
||||
await db.$client.close();
|
||||
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
});
|
||||
|
||||
it('drizzle(client)', async () => {
|
||||
const client = new Database(':memory:');
|
||||
const db = drizzle(client);
|
||||
|
||||
await db.$client.exec('SELECT 1;');
|
||||
|
||||
await db.$client.close();
|
||||
});
|
||||
|
||||
it('drizzle(client, config)', async () => {
|
||||
const client = new Database(':memory:');
|
||||
const db = drizzle(client, {
|
||||
schema,
|
||||
});
|
||||
|
||||
await db.$client.exec('SELECT 1;');
|
||||
|
||||
await db.$client.close();
|
||||
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
});
|
||||
|
||||
it('drizzle({client, ...config})', async () => {
|
||||
const client = new Database(':memory:');
|
||||
const db = drizzle({
|
||||
client,
|
||||
schema,
|
||||
});
|
||||
|
||||
await db.$client.exec('SELECT 1;');
|
||||
|
||||
await db.$client.close();
|
||||
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,97 @@
|
||||
import 'dotenv/config';
|
||||
import { createClient } from '@libsql/client';
|
||||
import { drizzle } from 'drizzle-orm/libsql';
|
||||
import { describe, expect } from 'vitest';
|
||||
import { sqlite as schema } from './schema.mjs';
|
||||
|
||||
describe('libsql', async (it) => {
|
||||
it('drizzle(string)', async () => {
|
||||
const db = drizzle(':memory:');
|
||||
|
||||
await db.$client.execute('SELECT 1;');
|
||||
|
||||
await db.$client.close();
|
||||
});
|
||||
|
||||
it('drizzle(string, config)', async () => {
|
||||
const db = drizzle(':memory:', {
|
||||
schema,
|
||||
});
|
||||
|
||||
await db.$client.execute('SELECT 1;');
|
||||
|
||||
await db.$client.close();
|
||||
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
});
|
||||
|
||||
it('drizzle({connection: string, ...config})', async () => {
|
||||
const db = drizzle({
|
||||
connection: ':memory:',
|
||||
schema,
|
||||
});
|
||||
|
||||
await db.$client.execute('SELECT 1;');
|
||||
|
||||
await db.$client.close();
|
||||
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
});
|
||||
|
||||
it('drizzle({connection: params, ...config})', async () => {
|
||||
const db = drizzle({
|
||||
connection: {
|
||||
url: ':memory:',
|
||||
},
|
||||
schema,
|
||||
});
|
||||
|
||||
await db.$client.execute('SELECT 1;');
|
||||
|
||||
await db.$client.close();
|
||||
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
});
|
||||
|
||||
it('drizzle(client)', async () => {
|
||||
const client = createClient({
|
||||
url: ':memory:',
|
||||
});
|
||||
const db = drizzle(client);
|
||||
|
||||
await db.$client.execute('SELECT 1;');
|
||||
|
||||
await db.$client.close();
|
||||
});
|
||||
|
||||
it('drizzle(client, config)', async () => {
|
||||
const client = createClient({
|
||||
url: ':memory:',
|
||||
});
|
||||
const db = drizzle(client, {
|
||||
schema,
|
||||
});
|
||||
|
||||
await db.$client.execute('SELECT 1;');
|
||||
|
||||
await db.$client.close();
|
||||
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
});
|
||||
|
||||
it('drizzle({client, ...config})', async () => {
|
||||
const client = createClient({
|
||||
url: ':memory:',
|
||||
});
|
||||
const db = drizzle({
|
||||
client,
|
||||
schema,
|
||||
});
|
||||
|
||||
await db.$client.execute('SELECT 1;');
|
||||
|
||||
await db.$client.close();
|
||||
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,153 @@
|
||||
import 'dotenv/config';
|
||||
import { drizzle } from 'drizzle-orm/mysql2';
|
||||
import { Connection, createConnection, createPool } from 'mysql2';
|
||||
import { describe, expect } from 'vitest';
|
||||
import { mysql as schema } from './schema.mjs';
|
||||
|
||||
if (!process.env['MYSQL_CONNECTION_STRING']) {
|
||||
throw new Error('MYSQL_CONNECTION_STRING is not defined');
|
||||
}
|
||||
|
||||
describe('mysql2', async (it) => {
|
||||
it('drizzle(string)', async () => {
|
||||
const db = drizzle(
|
||||
process.env['MYSQL_CONNECTION_STRING'],
|
||||
);
|
||||
|
||||
await db.$client.execute(`SELECT 1`);
|
||||
|
||||
expect(db.$client.getConnection).not.toStrictEqual(undefined);
|
||||
});
|
||||
|
||||
it('drizzle(string, config)', async () => {
|
||||
const db = drizzle(
|
||||
process.env['MYSQL_CONNECTION_STRING'],
|
||||
{
|
||||
schema,
|
||||
mode: 'default',
|
||||
},
|
||||
);
|
||||
await db.$client.execute('SELECT 1;');
|
||||
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
expect(db.$client.getConnection).not.toStrictEqual(undefined);
|
||||
});
|
||||
|
||||
it('drizzle({connection: string, ...config})', async () => {
|
||||
const db = drizzle({
|
||||
connection: process.env['MYSQL_CONNECTION_STRING'],
|
||||
schema,
|
||||
mode: 'default',
|
||||
});
|
||||
|
||||
await db.$client.execute('SELECT 1;');
|
||||
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
expect(db.$client.getConnection).not.toStrictEqual(undefined);
|
||||
});
|
||||
|
||||
it('drizzle({connection: params, ...config})', async () => {
|
||||
const db = drizzle({
|
||||
connection: {
|
||||
uri: process.env['MYSQL_CONNECTION_STRING'],
|
||||
},
|
||||
schema,
|
||||
mode: 'default',
|
||||
});
|
||||
|
||||
await db.$client.execute('SELECT 1;');
|
||||
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
expect(db.$client.getConnection).not.toStrictEqual(undefined);
|
||||
});
|
||||
|
||||
it('drizzle(client)', async () => {
|
||||
const client = createPool({
|
||||
uri: process.env['MYSQL_CONNECTION_STRING'],
|
||||
});
|
||||
|
||||
const db = drizzle(client);
|
||||
|
||||
await db.$client.execute('SELECT 1;');
|
||||
|
||||
expect(db.$client.getConnection).not.toStrictEqual(undefined);
|
||||
});
|
||||
|
||||
it('drizzle(client, config)', async () => {
|
||||
const client = createPool({
|
||||
uri: process.env['MYSQL_CONNECTION_STRING'],
|
||||
});
|
||||
const db = drizzle(client, {
|
||||
schema,
|
||||
mode: 'default',
|
||||
});
|
||||
|
||||
await db.$client.execute('SELECT 1;');
|
||||
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
expect(db.$client.getConnection).not.toStrictEqual(undefined);
|
||||
});
|
||||
|
||||
it('drizzle({client, ...config})', async () => {
|
||||
const client = createPool({
|
||||
uri: process.env['MYSQL_CONNECTION_STRING'],
|
||||
});
|
||||
|
||||
const db = drizzle({
|
||||
client,
|
||||
schema,
|
||||
mode: 'default',
|
||||
});
|
||||
|
||||
await db.$client.execute('SELECT 1;');
|
||||
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
expect(db.$client.getConnection).not.toStrictEqual(undefined);
|
||||
});
|
||||
});
|
||||
|
||||
describe('mysql2:connection', async (it) => {
|
||||
it('drizzle(client)', async () => {
|
||||
const client = createConnection({
|
||||
uri: process.env['MYSQL_CONNECTION_STRING'],
|
||||
});
|
||||
|
||||
const db = drizzle(client);
|
||||
|
||||
await db.$client.execute('SELECT 1;');
|
||||
|
||||
expect(db.$client.getConnection).toStrictEqual(undefined);
|
||||
});
|
||||
|
||||
it('drizzle(client, config)', async () => {
|
||||
const client = createConnection({
|
||||
uri: process.env['MYSQL_CONNECTION_STRING'],
|
||||
});
|
||||
const db = drizzle(client, {
|
||||
schema,
|
||||
mode: 'default',
|
||||
});
|
||||
|
||||
await db.$client.execute('SELECT 1;');
|
||||
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
expect(db.$client.getConnection).toStrictEqual(undefined);
|
||||
});
|
||||
|
||||
it('drizzle({client, ...config})', async () => {
|
||||
const client = createConnection({
|
||||
uri: process.env['MYSQL_CONNECTION_STRING'],
|
||||
});
|
||||
|
||||
const db = drizzle({
|
||||
client,
|
||||
schema,
|
||||
mode: 'default',
|
||||
});
|
||||
|
||||
await db.$client.execute('SELECT 1;');
|
||||
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
expect(db.$client.getConnection).toStrictEqual(undefined);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,92 @@
|
||||
import 'dotenv/config';
|
||||
import { neon as pg } from '@neondatabase/serverless';
|
||||
import { drizzle } from 'drizzle-orm/neon-http';
|
||||
import { describe, expect } from 'vitest';
|
||||
import { pg as schema } from './schema.mjs';
|
||||
|
||||
if (!process.env['NEON_CONNECTION_STRING']) {
|
||||
throw new Error('NEON_CONNECTION_STRING is not defined');
|
||||
}
|
||||
|
||||
describe('neon-http', async (it) => {
|
||||
it('drizzle(string)', async () => {
|
||||
const db = drizzle(
|
||||
process.env['NEON_CONNECTION_STRING'],
|
||||
);
|
||||
|
||||
await db.$client('SELECT 1;');
|
||||
});
|
||||
|
||||
it('drizzle(string, config)', async () => {
|
||||
const db = drizzle(
|
||||
process.env['NEON_CONNECTION_STRING'],
|
||||
{
|
||||
schema,
|
||||
},
|
||||
);
|
||||
|
||||
await db.$client('SELECT 1;');
|
||||
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
});
|
||||
|
||||
it('drizzle({connection: string, ...config})', async () => {
|
||||
const db = drizzle({
|
||||
connection: process.env['NEON_CONNECTION_STRING'],
|
||||
schema,
|
||||
});
|
||||
|
||||
await db.$client('SELECT 1;');
|
||||
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
});
|
||||
|
||||
it('drizzle({connection: params, ...config})', async () => {
|
||||
const db = drizzle({
|
||||
connection: {
|
||||
connectionString: process.env['NEON_CONNECTION_STRING'],
|
||||
},
|
||||
schema,
|
||||
});
|
||||
|
||||
await db.$client('SELECT 1;');
|
||||
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
});
|
||||
|
||||
it('drizzle(client)', async () => {
|
||||
const client = pg(
|
||||
process.env['NEON_CONNECTION_STRING'],
|
||||
);
|
||||
const db = drizzle(client);
|
||||
|
||||
await db.$client('SELECT 1;');
|
||||
});
|
||||
|
||||
it('drizzle(client, config)', async () => {
|
||||
const client = pg(
|
||||
process.env['NEON_CONNECTION_STRING'],
|
||||
);
|
||||
const db = drizzle(client, {
|
||||
schema,
|
||||
});
|
||||
|
||||
await db.$client('SELECT 1;');
|
||||
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
});
|
||||
|
||||
it('drizzle({client, ...config})', async () => {
|
||||
const client = pg(
|
||||
process.env['NEON_CONNECTION_STRING'],
|
||||
);
|
||||
const db = drizzle({
|
||||
client,
|
||||
schema,
|
||||
});
|
||||
|
||||
await db.$client('SELECT 1;');
|
||||
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,211 @@
|
||||
import 'dotenv/config';
|
||||
import { Client, neonConfig, Pool } from '@neondatabase/serverless';
|
||||
import { drizzle } from 'drizzle-orm/neon-serverless';
|
||||
import { describe, expect } from 'vitest';
|
||||
import ws from 'ws';
|
||||
import { pg as schema } from './schema.mjs';
|
||||
|
||||
neonConfig.webSocketConstructor = ws;
|
||||
|
||||
if (!process.env['NEON_CONNECTION_STRING']) {
|
||||
throw new Error('NEON_CONNECTION_STRING is not defined');
|
||||
}
|
||||
|
||||
describe('neon-ws', async (it) => {
|
||||
it('drizzle(string)', async () => {
|
||||
const db = drizzle(
|
||||
process.env['NEON_CONNECTION_STRING'],
|
||||
);
|
||||
|
||||
await db.$client.query('SELECT 1;');
|
||||
});
|
||||
|
||||
it('drizzle(string, config)', async () => {
|
||||
const db = drizzle(
|
||||
process.env['NEON_CONNECTION_STRING'],
|
||||
{
|
||||
schema,
|
||||
},
|
||||
);
|
||||
|
||||
await db.$client.query('SELECT 1;');
|
||||
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
expect(db.$client).toBeInstanceOf(Pool);
|
||||
});
|
||||
|
||||
it('drizzle({connection: string, ...config})', async () => {
|
||||
const db = drizzle({
|
||||
connection: process.env['NEON_CONNECTION_STRING'],
|
||||
schema,
|
||||
});
|
||||
|
||||
await db.$client.query('SELECT 1;');
|
||||
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
expect(db.$client).toBeInstanceOf(Pool);
|
||||
});
|
||||
|
||||
it('drizzle({connection: params, ...config})', async () => {
|
||||
const db = drizzle({
|
||||
connection: {
|
||||
connectionString: process.env['NEON_CONNECTION_STRING'],
|
||||
},
|
||||
schema,
|
||||
});
|
||||
|
||||
await db.$client.query('SELECT 1;');
|
||||
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
expect(db.$client).toBeInstanceOf(Pool);
|
||||
});
|
||||
|
||||
it('drizzle(client)', async () => {
|
||||
const client = new Pool({
|
||||
connectionString: process.env['NEON_CONNECTION_STRING'],
|
||||
});
|
||||
const db = drizzle(client);
|
||||
|
||||
await db.$client.query('SELECT 1;');
|
||||
|
||||
expect(db.$client).toBeInstanceOf(Pool);
|
||||
});
|
||||
|
||||
it('drizzle(client, config)', async () => {
|
||||
const client = new Pool({
|
||||
connectionString: process.env['NEON_CONNECTION_STRING'],
|
||||
});
|
||||
const db = drizzle(client, {
|
||||
schema,
|
||||
});
|
||||
|
||||
await db.$client.query('SELECT 1;');
|
||||
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
expect(db.$client).toBeInstanceOf(Pool);
|
||||
});
|
||||
|
||||
it('drizzle({client, ...config})', async () => {
|
||||
const client = new Pool({
|
||||
connectionString: process.env['NEON_CONNECTION_STRING'],
|
||||
});
|
||||
const db = drizzle({
|
||||
client,
|
||||
schema,
|
||||
});
|
||||
|
||||
await db.$client.query('SELECT 1;');
|
||||
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
expect(db.$client).toBeInstanceOf(Pool);
|
||||
});
|
||||
});
|
||||
|
||||
describe('neon-ws:Client', async (it) => {
|
||||
it('drizzle(client)', async () => {
|
||||
const client = new Client({
|
||||
connectionString: process.env['NEON_CONNECTION_STRING'],
|
||||
});
|
||||
|
||||
await client.connect();
|
||||
|
||||
const db = drizzle(client);
|
||||
|
||||
await db.$client.query('SELECT 1;');
|
||||
|
||||
expect(db.$client).toBeInstanceOf(Client);
|
||||
expect(db.$client).not.toBeInstanceOf(Pool);
|
||||
});
|
||||
|
||||
it('drizzle(client, config)', async () => {
|
||||
const client = new Client({
|
||||
connectionString: process.env['NEON_CONNECTION_STRING'],
|
||||
});
|
||||
const db = drizzle(client, {
|
||||
schema,
|
||||
});
|
||||
|
||||
await client.connect();
|
||||
|
||||
await db.$client.query('SELECT 1;');
|
||||
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
expect(db.$client).toBeInstanceOf(Client);
|
||||
expect(db.$client).not.toBeInstanceOf(Pool);
|
||||
});
|
||||
|
||||
it('drizzle({client, ...config})', async () => {
|
||||
const client = new Client({
|
||||
connectionString: process.env['NEON_CONNECTION_STRING'],
|
||||
});
|
||||
const db = drizzle({
|
||||
client,
|
||||
schema,
|
||||
});
|
||||
|
||||
await client.connect();
|
||||
|
||||
await db.$client.query('SELECT 1;');
|
||||
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
expect(db.$client).toBeInstanceOf(Client);
|
||||
expect(db.$client).not.toBeInstanceOf(Pool);
|
||||
});
|
||||
});
|
||||
|
||||
describe('neon-ws:PoolClient', async (it) => {
|
||||
it('drizzle(client)', async () => {
|
||||
const pool = new Pool({
|
||||
connectionString: process.env['NEON_CONNECTION_STRING'],
|
||||
});
|
||||
const client = await pool.connect();
|
||||
|
||||
const db = drizzle(client);
|
||||
|
||||
await db.$client.query('SELECT 1;');
|
||||
|
||||
client.release();
|
||||
|
||||
expect(db.$client).toBeInstanceOf(Client);
|
||||
expect(db.$client).not.toBeInstanceOf(Pool);
|
||||
});
|
||||
|
||||
it('drizzle(client, config)', async () => {
|
||||
const pool = new Pool({
|
||||
connectionString: process.env['NEON_CONNECTION_STRING'],
|
||||
});
|
||||
const client = await pool.connect();
|
||||
|
||||
const db = drizzle(client, {
|
||||
schema,
|
||||
});
|
||||
|
||||
await db.$client.query('SELECT 1;');
|
||||
|
||||
client.release();
|
||||
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
expect(db.$client).toBeInstanceOf(Client);
|
||||
expect(db.$client).not.toBeInstanceOf(Pool);
|
||||
});
|
||||
|
||||
it('drizzle({client, ...config})', async () => {
|
||||
const pool = new Pool({
|
||||
connectionString: process.env['NEON_CONNECTION_STRING'],
|
||||
});
|
||||
const client = await pool.connect();
|
||||
|
||||
const db = drizzle({
|
||||
client,
|
||||
schema,
|
||||
});
|
||||
|
||||
await db.$client.query('SELECT 1;');
|
||||
|
||||
client.release();
|
||||
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
expect(db.$client).toBeInstanceOf(Client);
|
||||
expect(db.$client).not.toBeInstanceOf(Pool);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,201 @@
|
||||
import 'dotenv/config';
|
||||
import { drizzle } from 'drizzle-orm/node-postgres';
|
||||
import pg from 'pg';
|
||||
import { describe, expect } from 'vitest';
|
||||
import { pg as schema } from './schema.mjs';
|
||||
|
||||
const Pool = pg.Pool;
|
||||
const Client = pg.Client;
|
||||
|
||||
if (!process.env['PG_CONNECTION_STRING']) {
|
||||
throw new Error('PG_CONNECTION_STRING is not defined');
|
||||
}
|
||||
|
||||
describe('node-pg', async (it) => {
|
||||
it('drizzle(string)', async () => {
|
||||
const db = drizzle(process.env['PG_CONNECTION_STRING']);
|
||||
|
||||
await db.$client.query('SELECT 1;');
|
||||
|
||||
expect(db.$client).toBeInstanceOf(Pool);
|
||||
});
|
||||
|
||||
it('drizzle(string, config)', async () => {
|
||||
const db = drizzle(process.env['PG_CONNECTION_STRING'], {
|
||||
schema,
|
||||
});
|
||||
|
||||
await db.$client.query('SELECT 1;');
|
||||
|
||||
expect(db.$client).toBeInstanceOf(Pool);
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
});
|
||||
|
||||
it('drizzle({connection: string, ...config})', async () => {
|
||||
const db = drizzle({
|
||||
connection: process.env['PG_CONNECTION_STRING'],
|
||||
schema,
|
||||
});
|
||||
|
||||
await db.$client.query('SELECT 1;');
|
||||
|
||||
expect(db.$client).toBeInstanceOf(Pool);
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
});
|
||||
|
||||
it('drizzle({connection: params, ...config})', async () => {
|
||||
const db = drizzle({
|
||||
connection: {
|
||||
connectionString: process.env['PG_CONNECTION_STRING'],
|
||||
},
|
||||
schema,
|
||||
});
|
||||
|
||||
await db.$client.query('SELECT 1;');
|
||||
|
||||
expect(db.$client).toBeInstanceOf(Pool);
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
});
|
||||
|
||||
it('drizzle(client)', async () => {
|
||||
const client = new Pool({
|
||||
connectionString: process.env['PG_CONNECTION_STRING'],
|
||||
});
|
||||
const db = drizzle(client);
|
||||
|
||||
await db.$client.query('SELECT 1;');
|
||||
|
||||
expect(db.$client).toBeInstanceOf(Pool);
|
||||
});
|
||||
|
||||
it('drizzle(client, config)', async () => {
|
||||
const client = new Pool({
|
||||
connectionString: process.env['PG_CONNECTION_STRING'],
|
||||
});
|
||||
const db = drizzle(client, {
|
||||
schema,
|
||||
});
|
||||
|
||||
await db.$client.query('SELECT 1;');
|
||||
|
||||
expect(db.$client).toBeInstanceOf(Pool);
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
});
|
||||
|
||||
it('drizzle({client, ...config})', async () => {
|
||||
const client = new Pool({
|
||||
connectionString: process.env['PG_CONNECTION_STRING'],
|
||||
});
|
||||
const db = drizzle({
|
||||
client,
|
||||
schema,
|
||||
});
|
||||
|
||||
await db.$client.query('SELECT 1;');
|
||||
|
||||
expect(db.$client).toBeInstanceOf(Pool);
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
});
|
||||
});
|
||||
|
||||
describe('node-pg:Client', async (it) => {
|
||||
it('drizzle(client)', async () => {
|
||||
const client = new Client({
|
||||
connectionString: process.env['PG_CONNECTION_STRING'],
|
||||
});
|
||||
const db = drizzle(client);
|
||||
|
||||
await client.connect();
|
||||
|
||||
await db.$client.query('SELECT 1;');
|
||||
|
||||
expect(db.$client).not.toBeInstanceOf(Pool);
|
||||
expect(db.$client).toBeInstanceOf(Client);
|
||||
});
|
||||
|
||||
it('drizzle(client, config)', async () => {
|
||||
const client = new Client({
|
||||
connectionString: process.env['PG_CONNECTION_STRING'],
|
||||
});
|
||||
const db = drizzle(client, {
|
||||
schema,
|
||||
});
|
||||
|
||||
await client.connect();
|
||||
|
||||
await db.$client.query('SELECT 1;');
|
||||
|
||||
expect(db.$client).not.toBeInstanceOf(Pool);
|
||||
expect(db.$client).toBeInstanceOf(Client);
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
});
|
||||
|
||||
it('drizzle({client, ...config})', async () => {
|
||||
const client = new Client({
|
||||
connectionString: process.env['PG_CONNECTION_STRING'],
|
||||
});
|
||||
const db = drizzle({
|
||||
client,
|
||||
schema,
|
||||
});
|
||||
|
||||
await client.connect();
|
||||
|
||||
await db.$client.query('SELECT 1;');
|
||||
|
||||
expect(db.$client).not.toBeInstanceOf(Pool);
|
||||
expect(db.$client).toBeInstanceOf(Client);
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
});
|
||||
});
|
||||
|
||||
describe('node-pg:PoolClient', async (it) => {
|
||||
it('drizzle(client)', async () => {
|
||||
const pool = new Pool({
|
||||
connectionString: process.env['PG_CONNECTION_STRING'],
|
||||
});
|
||||
const client = await pool.connect();
|
||||
const db = drizzle(client);
|
||||
|
||||
await db.$client.query('SELECT 1;');
|
||||
client.release();
|
||||
|
||||
expect(db.$client).not.toBeInstanceOf(Pool);
|
||||
expect(db.$client).toBeInstanceOf(Client);
|
||||
});
|
||||
|
||||
it('drizzle(client, config)', async () => {
|
||||
const pool = new Pool({
|
||||
connectionString: process.env['PG_CONNECTION_STRING'],
|
||||
});
|
||||
const client = await pool.connect();
|
||||
const db = drizzle(client, {
|
||||
schema,
|
||||
});
|
||||
|
||||
await db.$client.query('SELECT 1;');
|
||||
client.release();
|
||||
|
||||
expect(db.$client).not.toBeInstanceOf(Pool);
|
||||
expect(db.$client).toBeInstanceOf(Client);
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
});
|
||||
|
||||
it('drizzle({client, ...config})', async () => {
|
||||
const pool = new Pool({
|
||||
connectionString: process.env['PG_CONNECTION_STRING'],
|
||||
});
|
||||
const client = await pool.connect();
|
||||
const db = drizzle({
|
||||
client,
|
||||
schema,
|
||||
});
|
||||
|
||||
await db.$client.query('SELECT 1;');
|
||||
client.release();
|
||||
|
||||
expect(db.$client).not.toBeInstanceOf(Pool);
|
||||
expect(db.$client).toBeInstanceOf(Client);
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,88 @@
|
||||
import 'dotenv/config';
|
||||
import { PGlite as Database } from '@electric-sql/pglite';
|
||||
import { drizzle } from 'drizzle-orm/pglite';
|
||||
import { describe, expect } from 'vitest';
|
||||
import { pg as schema } from './schema.mjs';
|
||||
|
||||
describe('pglite', async (it) => {
|
||||
it('drizzle()', async () => {
|
||||
const db = drizzle();
|
||||
|
||||
await db.$client.exec('SELECT 1;');
|
||||
await db.$client.close();
|
||||
});
|
||||
|
||||
it('drizzle(string)', async () => {
|
||||
const db = drizzle('memory://');
|
||||
|
||||
await db.$client.exec('SELECT 1;');
|
||||
await db.$client.close();
|
||||
});
|
||||
|
||||
it('drizzle(string, config)', async () => {
|
||||
const db = drizzle('memory://', {
|
||||
schema,
|
||||
});
|
||||
|
||||
await db.$client.exec('SELECT 1;');
|
||||
await db.$client.close();
|
||||
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
});
|
||||
|
||||
it('drizzle({connection: {}, ...config})', async () => {
|
||||
const db = drizzle({
|
||||
connection: {},
|
||||
schema,
|
||||
});
|
||||
|
||||
await db.$client.exec('SELECT 1;');
|
||||
await db.$client.close();
|
||||
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
});
|
||||
|
||||
it('drizzle({...config})', async () => {
|
||||
const db = drizzle({
|
||||
schema,
|
||||
});
|
||||
|
||||
await db.$client.exec('SELECT 1;');
|
||||
await db.$client.close();
|
||||
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
});
|
||||
|
||||
it('drizzle(client)', async () => {
|
||||
const client = new Database('memory://');
|
||||
const db = drizzle(client);
|
||||
|
||||
await db.$client.exec('SELECT 1;');
|
||||
await db.$client.close();
|
||||
});
|
||||
|
||||
it('drizzle(client, config)', async () => {
|
||||
const client = new Database('memory://');
|
||||
const db = drizzle(client, {
|
||||
schema,
|
||||
});
|
||||
|
||||
await db.$client.exec('SELECT 1;');
|
||||
await db.$client.close();
|
||||
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
});
|
||||
|
||||
it('drizzle({client, ...config})', async () => {
|
||||
const client = new Database('memory://');
|
||||
const db = drizzle({
|
||||
client,
|
||||
schema,
|
||||
});
|
||||
|
||||
await db.$client.exec('SELECT 1;');
|
||||
await db.$client.close();
|
||||
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,101 @@
|
||||
import 'dotenv/config';
|
||||
import { Client } from '@planetscale/database';
|
||||
import { drizzle } from 'drizzle-orm/planetscale-serverless';
|
||||
import { describe, expect } from 'vitest';
|
||||
import { mysql as schema } from './schema.mjs';
|
||||
|
||||
if (!process.env['PLANETSCALE_CONNECTION_STRING']) {
|
||||
throw new Error('PLANETSCALE_CONNECTION_STRING is not defined');
|
||||
}
|
||||
|
||||
describe('planetscale', async (it) => {
|
||||
it('drizzle(string)', async () => {
|
||||
const db = drizzle(
|
||||
process.env['PLANETSCALE_CONNECTION_STRING'],
|
||||
);
|
||||
|
||||
await db.$client.execute('SELECT 1;');
|
||||
|
||||
expect(db.$client).toBeInstanceOf(Client);
|
||||
});
|
||||
|
||||
it('drizzle(string, config)', async () => {
|
||||
const db = drizzle(
|
||||
process.env['PLANETSCALE_CONNECTION_STRING'],
|
||||
{
|
||||
schema,
|
||||
},
|
||||
);
|
||||
|
||||
await db.$client.execute('SELECT 1;');
|
||||
|
||||
expect(db.$client).toBeInstanceOf(Client);
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
});
|
||||
|
||||
it('drizzle({connection: string, ...config})', async () => {
|
||||
const db = drizzle({
|
||||
connection: process.env['PLANETSCALE_CONNECTION_STRING'],
|
||||
schema,
|
||||
});
|
||||
|
||||
await db.$client.execute('SELECT 1;');
|
||||
|
||||
expect(db.$client).toBeInstanceOf(Client);
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
});
|
||||
|
||||
it('drizzle({connection: params, ...config})', async () => {
|
||||
const db = drizzle({
|
||||
connection: {
|
||||
url: process.env['PLANETSCALE_CONNECTION_STRING'],
|
||||
},
|
||||
schema,
|
||||
});
|
||||
|
||||
await db.$client.execute('SELECT 1;');
|
||||
|
||||
expect(db.$client).toBeInstanceOf(Client);
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
});
|
||||
|
||||
it('drizzle(client)', async () => {
|
||||
const client = new Client({
|
||||
url: process.env['PLANETSCALE_CONNECTION_STRING'],
|
||||
});
|
||||
const db = drizzle(client);
|
||||
|
||||
await db.$client.execute('SELECT 1;');
|
||||
|
||||
expect(db.$client).toBeInstanceOf(Client);
|
||||
});
|
||||
|
||||
it('drizzle(client, config)', async () => {
|
||||
const client = new Client({
|
||||
url: process.env['PLANETSCALE_CONNECTION_STRING'],
|
||||
});
|
||||
const db = drizzle(client, {
|
||||
schema,
|
||||
});
|
||||
|
||||
await db.$client.execute('SELECT 1;');
|
||||
|
||||
expect(db.$client).toBeInstanceOf(Client);
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
});
|
||||
|
||||
it('drizzle({client, ...config})', async () => {
|
||||
const client = new Client({
|
||||
url: process.env['PLANETSCALE_CONNECTION_STRING'],
|
||||
});
|
||||
const db = drizzle({
|
||||
client,
|
||||
schema,
|
||||
});
|
||||
|
||||
await db.$client.execute('SELECT 1;');
|
||||
|
||||
expect(db.$client).toBeInstanceOf(Client);
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,81 @@
|
||||
import 'dotenv/config';
|
||||
import { drizzle } from 'drizzle-orm/postgres-js';
|
||||
import pg from 'postgres';
|
||||
import { describe, expect } from 'vitest';
|
||||
import { pg as schema } from './schema.mjs';
|
||||
|
||||
if (!process.env['PG_CONNECTION_STRING']) {
|
||||
throw new Error('PG_CONNECTION_STRING is not defined');
|
||||
}
|
||||
|
||||
describe('postgres-js', async (it) => {
|
||||
it('drizzle(string)', async () => {
|
||||
const db = drizzle(process.env['PG_CONNECTION_STRING']);
|
||||
|
||||
await db.$client.unsafe('SELECT 1;');
|
||||
});
|
||||
|
||||
it('drizzle(string, config)', async () => {
|
||||
const db = drizzle(process.env['PG_CONNECTION_STRING'], {
|
||||
schema,
|
||||
});
|
||||
|
||||
await db.$client.unsafe('SELECT 1;');
|
||||
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
});
|
||||
|
||||
it('drizzle({connection: string, ...config})', async () => {
|
||||
const db = drizzle({
|
||||
connection: process.env['PG_CONNECTION_STRING'],
|
||||
schema,
|
||||
});
|
||||
|
||||
await db.$client.unsafe('SELECT 1;');
|
||||
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
});
|
||||
|
||||
it('drizzle({connection: params, ...config})', async () => {
|
||||
const db = drizzle({
|
||||
connection: {
|
||||
url: process.env['PG_CONNECTION_STRING'],
|
||||
},
|
||||
schema,
|
||||
});
|
||||
|
||||
await db.$client.unsafe('SELECT 1;');
|
||||
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
});
|
||||
|
||||
it('drizzle(client)', async () => {
|
||||
const client = pg(process.env['PG_CONNECTION_STRING']);
|
||||
const db = drizzle(client);
|
||||
|
||||
await db.$client.unsafe('SELECT 1;');
|
||||
});
|
||||
|
||||
it('drizzle(client, config)', async () => {
|
||||
const client = pg(process.env['PG_CONNECTION_STRING']);
|
||||
const db = drizzle(client, {
|
||||
schema,
|
||||
});
|
||||
|
||||
await db.$client.unsafe('SELECT 1;');
|
||||
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
});
|
||||
|
||||
it('drizzle({client, ...config})', async () => {
|
||||
const client = pg(process.env['PG_CONNECTION_STRING']);
|
||||
const db = drizzle({
|
||||
client,
|
||||
schema,
|
||||
});
|
||||
|
||||
await db.$client.unsafe('SELECT 1;');
|
||||
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,21 @@
|
||||
import { int as mysqlInt, mysqlTable } from 'drizzle-orm/mysql-core';
|
||||
import { integer as pgInt, pgTable } from 'drizzle-orm/pg-core';
|
||||
import { integer as sqliteInt, sqliteTable } from 'drizzle-orm/sqlite-core';
|
||||
|
||||
export const sqlite = {
|
||||
User: sqliteTable('test', {
|
||||
id: sqliteInt('id').primaryKey().notNull(),
|
||||
}),
|
||||
};
|
||||
|
||||
export const pg = {
|
||||
User: pgTable('test', {
|
||||
id: pgInt('id').primaryKey().notNull(),
|
||||
}),
|
||||
};
|
||||
|
||||
export const mysql = {
|
||||
User: mysqlTable('test', {
|
||||
id: mysqlInt('id').primaryKey().notNull(),
|
||||
}),
|
||||
};
|
||||
@@ -0,0 +1,88 @@
|
||||
import 'dotenv/config';
|
||||
import { connect } from '@tidbcloud/serverless';
|
||||
import { drizzle } from 'drizzle-orm/tidb-serverless';
|
||||
import { describe, expect } from 'vitest';
|
||||
import { mysql as schema } from './schema.mjs';
|
||||
|
||||
if (!process.env['TIDB_CONNECTION_STRING']) {
|
||||
throw new Error('TIDB_CONNECTION_STRING is not defined');
|
||||
}
|
||||
|
||||
describe('tidb', async (it) => {
|
||||
it('drizzle(string)', async () => {
|
||||
const db = drizzle(
|
||||
process.env['TIDB_CONNECTION_STRING'],
|
||||
);
|
||||
|
||||
await db.$client.execute(`SELECT 1`);
|
||||
});
|
||||
|
||||
it('drizzle(string, config)', async () => {
|
||||
const db = drizzle(
|
||||
process.env['TIDB_CONNECTION_STRING'],
|
||||
{
|
||||
schema,
|
||||
},
|
||||
);
|
||||
|
||||
await db.$client.execute('SELECT 1;');
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
});
|
||||
|
||||
it('drizzle({connection: string, ...config})', async () => {
|
||||
const db = drizzle({
|
||||
connection: process.env['TIDB_CONNECTION_STRING'],
|
||||
schema,
|
||||
});
|
||||
|
||||
await db.$client.execute('SELECT 1;');
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
});
|
||||
|
||||
it('drizzle({connection: params, ...config})', async () => {
|
||||
const db = drizzle({
|
||||
connection: {
|
||||
url: process.env['TIDB_CONNECTION_STRING'],
|
||||
},
|
||||
schema,
|
||||
});
|
||||
|
||||
await db.$client.execute('SELECT 1;');
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
});
|
||||
|
||||
it('drizzle(client)', async () => {
|
||||
const client = connect({
|
||||
url: process.env['TIDB_CONNECTION_STRING'],
|
||||
});
|
||||
|
||||
const db = drizzle(client);
|
||||
|
||||
await db.$client.execute('SELECT 1;');
|
||||
});
|
||||
|
||||
it('drizzle(client, config)', async () => {
|
||||
const client = connect({
|
||||
url: process.env['TIDB_CONNECTION_STRING'],
|
||||
});
|
||||
const db = drizzle(client, {
|
||||
schema,
|
||||
});
|
||||
|
||||
await db.$client.execute('SELECT 1;');
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
});
|
||||
|
||||
it('drizzle({client, ...config})', async () => {
|
||||
const client = connect({
|
||||
url: process.env['TIDB_CONNECTION_STRING'],
|
||||
});
|
||||
const db = drizzle({
|
||||
client,
|
||||
schema,
|
||||
});
|
||||
|
||||
await db.$client.execute('SELECT 1;');
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,229 @@
|
||||
import 'dotenv/config';
|
||||
import { createClient, createPool, sql, VercelClient, VercelPool } from '@vercel/postgres';
|
||||
import { drizzle } from 'drizzle-orm/vercel-postgres';
|
||||
import { describe, expect } from 'vitest';
|
||||
import { pg as schema } from './schema.mjs';
|
||||
|
||||
const Pool = VercelPool;
|
||||
const Client = VercelClient;
|
||||
|
||||
if (!process.env['VERCEL_CONNECTION_STRING']) {
|
||||
throw new Error('VERCEL_CONNECTION_STRING is not defined');
|
||||
}
|
||||
|
||||
// Used for non-pooled connection
|
||||
if (!process.env['NEON_CONNECTION_STRING']) {
|
||||
throw new Error('NEON_CONNECTION_STRING is not defined');
|
||||
}
|
||||
|
||||
process.env['POSTGRES_URL'] = process.env['VERCEL_CONNECTION_STRING'];
|
||||
|
||||
describe('vercel:sql', async (it) => {
|
||||
it('drizzle()', async () => {
|
||||
const db = drizzle();
|
||||
|
||||
await sql.connect();
|
||||
|
||||
await db.$client.query('SELECT 1;');
|
||||
|
||||
expect(db.$client).toBeTypeOf('function');
|
||||
});
|
||||
|
||||
it('drizzle(client)', async () => {
|
||||
const db = drizzle(sql);
|
||||
|
||||
await db.$client.query('SELECT 1;');
|
||||
|
||||
expect(db.$client).toBeTypeOf('function');
|
||||
});
|
||||
|
||||
it('drizzle(client, config)', async () => {
|
||||
const db = drizzle(sql, {
|
||||
schema,
|
||||
});
|
||||
|
||||
await db.$client.query('SELECT 1;');
|
||||
|
||||
expect(db.$client).toBeTypeOf('function');
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
});
|
||||
|
||||
it('drizzle({client, ...config})', async () => {
|
||||
const db = drizzle({
|
||||
client: sql,
|
||||
schema,
|
||||
});
|
||||
|
||||
await db.$client.query('SELECT 1;');
|
||||
|
||||
expect(db.$client).toBeTypeOf('function');
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
});
|
||||
|
||||
it('drizzle({...config})', async () => {
|
||||
const db = drizzle({
|
||||
schema,
|
||||
});
|
||||
|
||||
await db.$client.query('SELECT 1;');
|
||||
|
||||
expect(db.$client).toBeTypeOf('function');
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
});
|
||||
});
|
||||
|
||||
describe('vercel:Pool', async (it) => {
|
||||
it('drizzle(client)', async () => {
|
||||
const client = createPool({
|
||||
connectionString: process.env['VERCEL_CONNECTION_STRING'],
|
||||
});
|
||||
const db = drizzle(client);
|
||||
|
||||
await db.$client.query('SELECT 1;');
|
||||
|
||||
expect(db.$client).not.toBeTypeOf('function');
|
||||
expect(db.$client).toBeInstanceOf(Pool);
|
||||
});
|
||||
|
||||
it('drizzle(client, config)', async () => {
|
||||
const client = createPool({
|
||||
connectionString: process.env['VERCEL_CONNECTION_STRING'],
|
||||
});
|
||||
const db = drizzle(client, {
|
||||
schema,
|
||||
});
|
||||
|
||||
await db.$client.query('SELECT 1;');
|
||||
|
||||
expect(db.$client).not.toBeTypeOf('function');
|
||||
expect(db.$client).toBeInstanceOf(Pool);
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
});
|
||||
|
||||
it('drizzle({client, ...config})', async () => {
|
||||
const client = createPool({
|
||||
connectionString: process.env['VERCEL_CONNECTION_STRING'],
|
||||
});
|
||||
const db = drizzle({
|
||||
client: client,
|
||||
schema,
|
||||
});
|
||||
|
||||
await db.$client.query('SELECT 1;');
|
||||
|
||||
expect(db.$client).not.toBeTypeOf('function');
|
||||
expect(db.$client).toBeInstanceOf(Pool);
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
});
|
||||
});
|
||||
|
||||
describe('vercel:Client', async (it) => {
|
||||
it('drizzle(client)', async () => {
|
||||
const client = createClient({
|
||||
connectionString: process.env['NEON_CONNECTION_STRING'],
|
||||
});
|
||||
const db = drizzle(client);
|
||||
|
||||
await client.connect();
|
||||
|
||||
await db.$client.query('SELECT 1;');
|
||||
|
||||
expect(db.$client).not.toBeTypeOf('function');
|
||||
expect(db.$client).not.toBeInstanceOf(Pool);
|
||||
expect(db.$client).toBeInstanceOf(Client);
|
||||
});
|
||||
|
||||
it('drizzle(client, config)', async () => {
|
||||
const client = createClient({
|
||||
connectionString: process.env['NEON_CONNECTION_STRING'],
|
||||
});
|
||||
const db = drizzle(client, {
|
||||
schema,
|
||||
});
|
||||
|
||||
await client.connect();
|
||||
|
||||
await db.$client.query('SELECT 1;');
|
||||
|
||||
expect(db.$client).not.toBeTypeOf('function');
|
||||
expect(db.$client).not.toBeInstanceOf(Pool);
|
||||
expect(db.$client).toBeInstanceOf(Client);
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
});
|
||||
|
||||
it('drizzle({client, ...config})', async () => {
|
||||
const client = createClient({
|
||||
connectionString: process.env['NEON_CONNECTION_STRING'],
|
||||
});
|
||||
const db = drizzle({
|
||||
client: client,
|
||||
schema,
|
||||
});
|
||||
|
||||
await client.connect();
|
||||
|
||||
await db.$client.query('SELECT 1;');
|
||||
|
||||
expect(db.$client).not.toBeTypeOf('function');
|
||||
expect(db.$client).not.toBeInstanceOf(Pool);
|
||||
expect(db.$client).toBeInstanceOf(Client);
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
});
|
||||
});
|
||||
|
||||
describe('vercel:PoolClient', async (it) => {
|
||||
it('drizzle(client)', async () => {
|
||||
const pool = createPool({
|
||||
connectionString: process.env['VERCEL_CONNECTION_STRING'],
|
||||
});
|
||||
const client = await pool.connect();
|
||||
|
||||
const db = drizzle(client);
|
||||
|
||||
await db.$client.query('SELECT 1;');
|
||||
client.release();
|
||||
|
||||
expect(db.$client).not.toBeTypeOf('function');
|
||||
expect(db.$client).not.toBeInstanceOf(Pool);
|
||||
expect(db.$client).toBeInstanceOf(Client);
|
||||
});
|
||||
|
||||
it('drizzle(client, config)', async () => {
|
||||
const pool = createPool({
|
||||
connectionString: process.env['VERCEL_CONNECTION_STRING'],
|
||||
});
|
||||
const client = await pool.connect();
|
||||
|
||||
const db = drizzle(client, {
|
||||
schema,
|
||||
});
|
||||
|
||||
await db.$client.query('SELECT 1;');
|
||||
client.release();
|
||||
|
||||
expect(db.$client).not.toBeTypeOf('function');
|
||||
expect(db.$client).not.toBeInstanceOf(Pool);
|
||||
expect(db.$client).toBeInstanceOf(Client);
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
});
|
||||
|
||||
it('drizzle({client, ...config})', async () => {
|
||||
const pool = createPool({
|
||||
connectionString: process.env['VERCEL_CONNECTION_STRING'],
|
||||
});
|
||||
const client = await pool.connect();
|
||||
|
||||
const db = drizzle({
|
||||
client: client,
|
||||
schema,
|
||||
});
|
||||
|
||||
await db.$client.query('SELECT 1;');
|
||||
client.release();
|
||||
|
||||
expect(db.$client).not.toBeTypeOf('function');
|
||||
expect(db.$client).not.toBeInstanceOf(Pool);
|
||||
expect(db.$client).toBeInstanceOf(Client);
|
||||
expect(db.query.User).not.toStrictEqual(undefined);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,81 @@
|
||||
{
|
||||
"name": "integration-tests",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"test:types": "tsc && cd type-tests/join-nodenext && tsc",
|
||||
"test": "pnpm test:vitest",
|
||||
"test:vitest": "vitest run --pass-with-no-tests",
|
||||
"test:esm": "node tests/imports.test.mjs && node tests/imports.test.cjs",
|
||||
"test:data-api": "sst shell vitest run tests/pg/awsdatapi.test.ts"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "Drizzle Team",
|
||||
"license": "Apache-2.0",
|
||||
"private": true,
|
||||
"devDependencies": {
|
||||
"@cloudflare/workers-types": "^4.20241004.0",
|
||||
"@libsql/client": "^0.10.0",
|
||||
"@neondatabase/serverless": "0.10.0",
|
||||
"@originjs/vite-plugin-commonjs": "^1.0.3",
|
||||
"@paralleldrive/cuid2": "^2.2.2",
|
||||
"@types/async-retry": "^1.4.8",
|
||||
"@types/better-sqlite3": "^7.6.4",
|
||||
"@types/dockerode": "^3.3.18",
|
||||
"@types/node": "^20.2.5",
|
||||
"@types/pg": "^8.10.1",
|
||||
"@types/sql.js": "^1.4.4",
|
||||
"@types/uuid": "^9.0.1",
|
||||
"@types/ws": "^8.5.10",
|
||||
"@upstash/redis": "^1.34.3",
|
||||
"@vitest/ui": "^1.6.0",
|
||||
"ava": "^5.3.0",
|
||||
"cross-env": "^7.0.3",
|
||||
"import-in-the-middle": "^1.13.1",
|
||||
"keyv": "^5.2.3",
|
||||
"ts-node": "^10.9.2",
|
||||
"tsx": "^4.14.0",
|
||||
"vite-tsconfig-paths": "^4.3.2",
|
||||
"zx": "^8.3.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-rds-data": "^3.549.0",
|
||||
"@aws-sdk/credential-providers": "^3.549.0",
|
||||
"@electric-sql/pglite": "0.2.12",
|
||||
"@libsql/client": "^0.10.0",
|
||||
"@miniflare/d1": "^2.14.4",
|
||||
"@miniflare/shared": "^2.14.4",
|
||||
"@netlify/db": "^0.4.0",
|
||||
"@planetscale/database": "^1.16.0",
|
||||
"@prisma/client": "5.14.0",
|
||||
"@tidbcloud/serverless": "^0.1.1",
|
||||
"@typescript/analyze-trace": "^0.10.0",
|
||||
"@vercel/postgres": "^0.8.0",
|
||||
"@xata.io/client": "^0.29.3",
|
||||
"async-retry": "^1.3.3",
|
||||
"better-sqlite3": "11.9.1",
|
||||
"dockerode": "^4.0.6",
|
||||
"dotenv": "^16.1.4",
|
||||
"drizzle-prisma-generator": "^0.1.2",
|
||||
"drizzle-seed": "workspace:../drizzle-seed/dist",
|
||||
"drizzle-typebox": "workspace:../drizzle-typebox/dist",
|
||||
"drizzle-valibot": "workspace:../drizzle-valibot/dist",
|
||||
"drizzle-zod": "workspace:../drizzle-zod/dist",
|
||||
"gel": "^2.0.0",
|
||||
"get-port": "^7.0.0",
|
||||
"mysql2": "^3.14.1",
|
||||
"pg": "^8.11.0",
|
||||
"postgres": "^3.3.5",
|
||||
"prisma": "5.14.0",
|
||||
"source-map-support": "^0.5.21",
|
||||
"sql.js": "^1.8.0",
|
||||
"sqlite3": "^5.1.4",
|
||||
"sst": "^3.14.24",
|
||||
"uuid": "^9.0.0",
|
||||
"uvu": "^0.5.6",
|
||||
"vitest": "^3.1.3",
|
||||
"ws": "^8.18.2",
|
||||
"zod": "^3.20.2"
|
||||
}
|
||||
}
|
||||
Vendored
+2
@@ -0,0 +1,2 @@
|
||||
// eslint-disable-next-line @typescript-eslint/triple-slash-reference
|
||||
/// <reference path="./.sst/types.generated.ts" />
|
||||
@@ -0,0 +1,20 @@
|
||||
// eslint-disable-next-line @typescript-eslint/triple-slash-reference
|
||||
/// <reference path="./.sst/platform/config.d.ts" />
|
||||
|
||||
export default $config({
|
||||
app(_input) {
|
||||
return {
|
||||
name: 'awsdataapi',
|
||||
removal: 'remove',
|
||||
home: 'aws',
|
||||
};
|
||||
},
|
||||
async run() {
|
||||
new sst.aws.Postgres('Postgres', {
|
||||
scaling: {
|
||||
min: '0.5 ACU',
|
||||
max: '1 ACU',
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,545 @@
|
||||
/* eslint-disable unicorn/no-empty-file */
|
||||
// import 'dotenv/config';
|
||||
|
||||
// import { RDSDataClient } from '@aws-sdk/client-rds-data';
|
||||
// import { fromIni } from '@aws-sdk/credential-providers';
|
||||
// import type { TestFn } from 'ava';
|
||||
// import anyTest from 'ava';
|
||||
// import * as dotenv from 'dotenv';
|
||||
// import { name, sql } from 'drizzle-orm';
|
||||
// import type { AwsDataApiPgDatabase } from 'drizzle-orm/aws-data-api/pg';
|
||||
// import { drizzle } from 'drizzle-orm/aws-data-api/pg';
|
||||
// import {
|
||||
// bigint,
|
||||
// bigserial,
|
||||
// boolean,
|
||||
// date,
|
||||
// decimal,
|
||||
// doublePrecision,
|
||||
// integer,
|
||||
// json,
|
||||
// jsonb,
|
||||
// numeric,
|
||||
// pgEnum,
|
||||
// pgTable,
|
||||
// real,
|
||||
// serial,
|
||||
// smallint,
|
||||
// text,
|
||||
// time,
|
||||
// timestamp,
|
||||
// varchar,
|
||||
// } from 'drizzle-orm/pg-core';
|
||||
|
||||
// dotenv.config();
|
||||
|
||||
// export const allColumns = pgTable('all_columns', {
|
||||
// sm: smallint('smallint'),
|
||||
// smdef: smallint('smallint_def').default(10),
|
||||
// int: integer('integer'),
|
||||
// intdef: integer('integer_def').default(10),
|
||||
// numeric: numeric('numeric'),
|
||||
// numeric2: numeric('numeric2', { precision: 5 }),
|
||||
// numeric3: numeric('numeric3', { scale: 2 }),
|
||||
// numeric4: numeric('numeric4', { precision: 5, scale: 2 }),
|
||||
// numericdef: numeric('numeridef').default('100'),
|
||||
// bigint: bigint('bigint', { mode: 'number' }),
|
||||
// bigintdef: bigint('bigintdef', { mode: 'number' }).default(100),
|
||||
// bool: boolean('boolean'),
|
||||
// booldef: boolean('boolean_def').default(true),
|
||||
// text: text('text'),
|
||||
// textdef: text('textdef').default('text'),
|
||||
// varchar: varchar('varchar'),
|
||||
// varchardef: varchar('varchardef').default('text'),
|
||||
// serial: serial('serial'),
|
||||
// bigserial: bigserial('bigserial', { mode: 'number' }),
|
||||
// decimal: decimal('decimal', { precision: 100, scale: 2 }),
|
||||
// decimaldef: decimal('decimaldef', { precision: 100, scale: 2 }).default('100.0'),
|
||||
// doublePrecision: doublePrecision('doublePrecision'),
|
||||
// doublePrecisiondef: doublePrecision('doublePrecisiondef').default(100),
|
||||
// real: real('real'),
|
||||
// realdef: real('realdef').default(100),
|
||||
// json: json('json').$type<{ attr: string }>(),
|
||||
// jsondef: json('jsondef').$type<{ attr: string }>().default({ attr: 'value' }),
|
||||
// jsonb: jsonb('jsonb').$type<{ attr: string }>(),
|
||||
// jsonbdef: jsonb('jsonbdef').$type<{ attr: string }>().default({ attr: 'value' }),
|
||||
// time: time('time'),
|
||||
// time2: time('time2', { precision: 6, withTimezone: true }),
|
||||
// timedefnow: time('timedefnow').defaultNow(),
|
||||
// timestamp: timestamp('timestamp'),
|
||||
// timestamp2: timestamp('timestamp2', { precision: 6, withTimezone: true }),
|
||||
// timestamp3: timestamp('timestamp3', { withTimezone: true }),
|
||||
// timestamp4: timestamp('timestamp4', { precision: 4 }),
|
||||
// timestampdef: timestamp('timestampdef').defaultNow(),
|
||||
// date: date('date', { mode: 'date' }),
|
||||
// datedef: date('datedef').defaultNow(),
|
||||
// });
|
||||
|
||||
// interface Context {
|
||||
// db: AwsDataApiPgDatabase;
|
||||
// row: typeof allColumns.$inferSelect;
|
||||
// }
|
||||
|
||||
// const test = anyTest as TestFn<Context>;
|
||||
|
||||
// test.before(async (t) => {
|
||||
// const ctx = t.context;
|
||||
// const database = process.env['AWS_DATA_API_DB']!;
|
||||
// const secretArn = process.env['AWS_DATA_API_SECRET_ARN']!;
|
||||
// const resourceArn = process.env['AWS_DATA_API_RESOURCE_ARN']!;
|
||||
|
||||
// const rdsClient = new RDSDataClient({
|
||||
// credentials: fromIni({ profile: process.env['AWS_TEST_PROFILE'] }),
|
||||
// region: 'us-east-1',
|
||||
// });
|
||||
|
||||
// ctx.db = drizzle(rdsClient, {
|
||||
// database,
|
||||
// secretArn,
|
||||
// resourceArn,
|
||||
// // logger: new DefaultLogger(),
|
||||
// });
|
||||
|
||||
// await ctx.db.execute(sql`
|
||||
// CREATE TABLE IF NOT EXISTS "all_columns" (
|
||||
// "smallint" smallint,
|
||||
// "smallint_def" smallint DEFAULT 10,
|
||||
// "integer" integer,
|
||||
// "integer_def" integer DEFAULT 10,
|
||||
// "numeric" numeric,
|
||||
// "numeric2" numeric(5),
|
||||
// "numeric3" numeric,
|
||||
// "numeric4" numeric(5, 2),
|
||||
// "numeridef" numeric DEFAULT '100',
|
||||
// "bigint" bigint,
|
||||
// "bigintdef" bigint DEFAULT 100,
|
||||
// "boolean" boolean,
|
||||
// "boolean_def" boolean DEFAULT true,
|
||||
// "text" text,
|
||||
// "textdef" text DEFAULT 'text',
|
||||
// "varchar" varchar,
|
||||
// "varchardef" varchar DEFAULT 'text',
|
||||
// "serial" serial,
|
||||
// "bigserial" bigserial,
|
||||
// "decimal" numeric(100, 2),
|
||||
// "decimaldef" numeric(100, 2) DEFAULT '100.0',
|
||||
// "doublePrecision" double precision,
|
||||
// "doublePrecisiondef" double precision DEFAULT 100,
|
||||
// "real" real,
|
||||
// "realdef" real DEFAULT 100,
|
||||
// "json" json,
|
||||
// "jsondef" json DEFAULT '{"attr":"value"}'::json,
|
||||
// "jsonb" jsonb,
|
||||
// "jsonbdef" jsonb DEFAULT '{"attr":"value"}'::jsonb,
|
||||
// "time" time,
|
||||
// "time2" time,
|
||||
// "timedefnow" time DEFAULT now(),
|
||||
// "timestamp" timestamp,
|
||||
// "timestamp2" timestamp (6) with time zone,
|
||||
// "timestamp3" timestamp with time zone,
|
||||
// "timestamp4" timestamp (4),
|
||||
// "timestampdef" timestamp DEFAULT now(),
|
||||
// "date" date,
|
||||
// "datedef" date DEFAULT now()
|
||||
// )
|
||||
// `);
|
||||
|
||||
// const now = new Date();
|
||||
|
||||
// await ctx.db.insert(allColumns).values({
|
||||
// sm: 12,
|
||||
// int: 22,
|
||||
// numeric: '1.1',
|
||||
// numeric2: '123.45',
|
||||
// numeric3: '123.45',
|
||||
// numeric4: '123.45',
|
||||
// bigint: 1578,
|
||||
// bool: true,
|
||||
// text: 'inserted_text',
|
||||
// varchar: 'inserted_varchar',
|
||||
// serial: 44,
|
||||
// bigserial: 63473487,
|
||||
// decimal: '100.1',
|
||||
// doublePrecision: 7384.34,
|
||||
// real: 73849.11,
|
||||
// json: { attr: 'hellohello' },
|
||||
// jsonb: { attr: 'hellohello' },
|
||||
// time: '11:12:00',
|
||||
// time2: '11:12:00',
|
||||
// timestamp: now,
|
||||
// timestamp2: now,
|
||||
// timestamp3: now,
|
||||
// timestamp4: now,
|
||||
// date: now,
|
||||
// // interval: '10 days'
|
||||
// });
|
||||
|
||||
// const resultRows = await ctx.db.select().from(allColumns);
|
||||
// t.is(resultRows.length, 1);
|
||||
|
||||
// const row = resultRows[0]!;
|
||||
// ctx.row = row;
|
||||
// });
|
||||
|
||||
// test.serial('[small] serial type', async (t) => {
|
||||
// const { row } = t.context;
|
||||
|
||||
// t.assert(typeof row.sm === 'number');
|
||||
// t.is(row.sm, 12);
|
||||
// });
|
||||
|
||||
// test.serial('[small serial] type with default', async (t) => {
|
||||
// const { row } = t.context;
|
||||
|
||||
// t.assert(typeof row.sm === 'number');
|
||||
// t.is(row.smdef, 10);
|
||||
// });
|
||||
|
||||
// test.serial('[int] type', async (t) => {
|
||||
// const { row } = t.context;
|
||||
|
||||
// t.assert(typeof row.int === 'number');
|
||||
// t.is(row.int, 22);
|
||||
// });
|
||||
|
||||
// test.serial('[int] type with default', async (t) => {
|
||||
// const { row } = t.context;
|
||||
|
||||
// t.assert(typeof row.intdef === 'number');
|
||||
// t.is(row.intdef, 10);
|
||||
// });
|
||||
|
||||
// test.serial('[numeric] type', async (t) => {
|
||||
// const { row } = t.context;
|
||||
|
||||
// t.assert(typeof row.int === 'number');
|
||||
// t.is(row.int, 22);
|
||||
// });
|
||||
|
||||
// test.serial('[numeric(precision)] type', async (t) => {
|
||||
// const { row } = t.context;
|
||||
|
||||
// t.assert(typeof row.int === 'number');
|
||||
// t.is(row.int, 22);
|
||||
// });
|
||||
|
||||
// test.serial('[numeric(scale)] type', async (t) => {
|
||||
// const { row } = t.context;
|
||||
|
||||
// t.assert(typeof row.int === 'number');
|
||||
// t.is(row.int, 22);
|
||||
// });
|
||||
|
||||
// test.serial('[numeric(precision, scale)] type', async (t) => {
|
||||
// const { row } = t.context;
|
||||
|
||||
// t.assert(typeof row.int === 'number');
|
||||
// t.is(row.int, 22);
|
||||
// });
|
||||
|
||||
// test.serial('[numeric] type with default', async (t) => {
|
||||
// const { row } = t.context;
|
||||
|
||||
// t.assert(typeof row.int === 'number');
|
||||
// t.is(row.int, 22);
|
||||
// });
|
||||
|
||||
// test.serial('[bigint] type', async (t) => {
|
||||
// const { row } = t.context;
|
||||
|
||||
// t.assert(typeof row.bigint === 'number');
|
||||
// t.is(row.bigint, 1578);
|
||||
// });
|
||||
|
||||
// test.serial('[bigint] type with default', async (t) => {
|
||||
// const { row } = t.context;
|
||||
|
||||
// t.assert(typeof row.bigintdef === 'number');
|
||||
// t.is(row.bigintdef, 100);
|
||||
// });
|
||||
|
||||
// test.serial('[boolean] type', async (t) => {
|
||||
// const { row } = t.context;
|
||||
|
||||
// t.assert(typeof row.bool === 'boolean');
|
||||
// t.is(row.bool, true);
|
||||
// });
|
||||
|
||||
// test.serial('[boolean] type with default', async (t) => {
|
||||
// const { row } = t.context;
|
||||
|
||||
// t.assert(typeof row.booldef === 'boolean');
|
||||
// t.is(row.booldef, true);
|
||||
// });
|
||||
|
||||
// test.serial('[text] type', async (t) => {
|
||||
// const { row } = t.context;
|
||||
|
||||
// t.assert(typeof row.text === 'string');
|
||||
// t.is(row.text, 'inserted_text');
|
||||
// });
|
||||
|
||||
// test.serial('[text] type with default', async (t) => {
|
||||
// const { row } = t.context;
|
||||
|
||||
// t.assert(typeof row.textdef === 'string');
|
||||
// t.is(row.textdef, 'text');
|
||||
// });
|
||||
|
||||
// test.serial('[varchar] type', async (t) => {
|
||||
// const { row } = t.context;
|
||||
|
||||
// t.assert(typeof row.varchar === 'string');
|
||||
// t.is(row.varchar, 'inserted_varchar');
|
||||
// });
|
||||
|
||||
// test.serial('[varchar] type with default', async (t) => {
|
||||
// const { row } = t.context;
|
||||
|
||||
// t.assert(typeof row.varchardef === 'string');
|
||||
// t.is(row.varchardef, 'text');
|
||||
// });
|
||||
|
||||
// test.serial('[serial] type', async (t) => {
|
||||
// const { row } = t.context;
|
||||
|
||||
// t.assert(typeof row.serial === 'number');
|
||||
// t.is(row.serial, 44);
|
||||
// });
|
||||
|
||||
// test.serial('[bigserial] type', async (t) => {
|
||||
// const { row } = t.context;
|
||||
|
||||
// t.assert(typeof row.bigserial === 'number');
|
||||
// t.is(row.bigserial, 63473487);
|
||||
// });
|
||||
|
||||
// test.serial('[decimal] type', async (t) => {
|
||||
// const { row } = t.context;
|
||||
|
||||
// t.assert(typeof row.decimal === 'string');
|
||||
// t.is(row.decimal, '100.10');
|
||||
// });
|
||||
|
||||
// test.serial('[decimal] type with default', async (t) => {
|
||||
// const { row } = t.context;
|
||||
|
||||
// t.assert(typeof row.decimaldef === 'string');
|
||||
// t.is(row.decimaldef, '100.00');
|
||||
// });
|
||||
|
||||
// test.serial('[double precision] type', async (t) => {
|
||||
// const { row } = t.context;
|
||||
|
||||
// t.assert(typeof row.doublePrecision === 'number');
|
||||
// t.is(row.doublePrecision, 7384.34);
|
||||
// });
|
||||
|
||||
// test.serial('[double precision] type with default', async (t) => {
|
||||
// const { row } = t.context;
|
||||
|
||||
// t.assert(typeof row.doublePrecisiondef === 'number');
|
||||
// t.is(row.doublePrecisiondef, 100);
|
||||
// });
|
||||
|
||||
// test.serial('[real] type', async (t) => {
|
||||
// const { row } = t.context;
|
||||
|
||||
// t.assert(typeof row.real === 'number');
|
||||
// t.is(row.real, 73849.11);
|
||||
// });
|
||||
|
||||
// test.serial('[real] type with default', async (t) => {
|
||||
// const { row } = t.context;
|
||||
|
||||
// t.assert(typeof row.realdef === 'number');
|
||||
// t.is(row.realdef, 100);
|
||||
// });
|
||||
|
||||
// test.serial('[json] type', async (t) => {
|
||||
// const { row } = t.context;
|
||||
|
||||
// t.assert(typeof row.json?.attr === 'string');
|
||||
// t.deepEqual(row.json, { attr: 'hellohello' });
|
||||
// });
|
||||
|
||||
// test.serial('[json] type with default', async (t) => {
|
||||
// const { row } = t.context;
|
||||
|
||||
// t.assert(typeof row.jsondef?.attr === 'string');
|
||||
// t.deepEqual(row.jsondef, { attr: 'value' });
|
||||
// });
|
||||
|
||||
// test.serial('[jsonb] type', async (t) => {
|
||||
// const { row } = t.context;
|
||||
|
||||
// t.assert(typeof row.jsonb?.attr === 'string');
|
||||
// t.deepEqual(row.jsonb, { attr: 'hellohello' });
|
||||
// });
|
||||
|
||||
// test.serial('[jsonb] type with default', async (t) => {
|
||||
// const { row } = t.context;
|
||||
|
||||
// t.assert(typeof row.jsonbdef?.attr === 'string');
|
||||
// t.deepEqual(row.jsonbdef, { attr: 'value' });
|
||||
// });
|
||||
|
||||
// test.serial('[time] type', async (t) => {
|
||||
// const { row } = t.context;
|
||||
|
||||
// t.assert(typeof row.time === 'string');
|
||||
// t.assert(typeof row.time2 === 'string');
|
||||
// t.assert(typeof row.timedefnow === 'string');
|
||||
// });
|
||||
|
||||
// test.serial('[timestamp] type with default', async (t) => {
|
||||
// const { row } = t.context;
|
||||
|
||||
// t.assert(row.timestamp instanceof Date); // eslint-disable-line no-instanceof/no-instanceof
|
||||
// t.assert(row.timestamp2 instanceof Date); // eslint-disable-line no-instanceof/no-instanceof
|
||||
// t.assert(row.timestamp3 instanceof Date); // eslint-disable-line no-instanceof/no-instanceof
|
||||
// t.assert(row.timestamp4 instanceof Date); // eslint-disable-line no-instanceof/no-instanceof
|
||||
// t.assert(row.timestampdef instanceof Date); // eslint-disable-line no-instanceof/no-instanceof
|
||||
// });
|
||||
|
||||
// test.serial('[date] type with default', async (t) => {
|
||||
// const { row } = t.context;
|
||||
|
||||
// t.assert(row.date instanceof Date); // eslint-disable-line no-instanceof/no-instanceof
|
||||
// t.assert(typeof row.datedef === 'string');
|
||||
// });
|
||||
|
||||
// test.serial('select from enum', async (t) => {
|
||||
// const { db } = t.context;
|
||||
|
||||
// const muscleEnum = pgEnum('muscle', [
|
||||
// 'abdominals',
|
||||
// 'hamstrings',
|
||||
// 'adductors',
|
||||
// 'quadriceps',
|
||||
// 'biceps',
|
||||
// 'shoulders',
|
||||
// 'chest',
|
||||
// 'middle_back',
|
||||
// 'calves',
|
||||
// 'glutes',
|
||||
// 'lower_back',
|
||||
// 'lats',
|
||||
// 'triceps',
|
||||
// 'traps',
|
||||
// 'forearms',
|
||||
// 'neck',
|
||||
// 'abductors',
|
||||
// ]);
|
||||
|
||||
// const forceEnum = pgEnum('force', ['isometric', 'isotonic', 'isokinetic']);
|
||||
|
||||
// const levelEnum = pgEnum('level', ['beginner', 'intermediate', 'advanced']);
|
||||
|
||||
// const mechanicEnum = pgEnum('mechanic', ['compound', 'isolation']);
|
||||
|
||||
// const equipmentEnum = pgEnum('equipment', ['barbell', 'dumbbell', 'bodyweight', 'machine', 'cable', 'kettlebell']);
|
||||
|
||||
// const categoryEnum = pgEnum('category', ['upper_body', 'lower_body', 'full_body']);
|
||||
|
||||
// const exercises = pgTable('exercises', {
|
||||
// id: serial('id').primaryKey(),
|
||||
// name: varchar('name').notNull(),
|
||||
// force: forceEnum('force'),
|
||||
// level: levelEnum('level'),
|
||||
// mechanic: mechanicEnum('mechanic'),
|
||||
// equipment: equipmentEnum('equipment'),
|
||||
// instructions: text('instructions'),
|
||||
// category: categoryEnum('category'),
|
||||
// primaryMuscles: muscleEnum('primary_muscles').array(),
|
||||
// secondaryMuscles: muscleEnum('secondary_muscles').array(),
|
||||
// createdAt: timestamp('created_at').notNull().default(sql`now()`),
|
||||
// updatedAt: timestamp('updated_at').notNull().default(sql`now()`),
|
||||
// });
|
||||
|
||||
// await db.execute(sql`drop table if exists ${exercises}`);
|
||||
// await db.execute(sql`drop type if exists ${name(muscleEnum.enumName)}`);
|
||||
// await db.execute(sql`drop type if exists ${name(forceEnum.enumName)}`);
|
||||
// await db.execute(sql`drop type if exists ${name(levelEnum.enumName)}`);
|
||||
// await db.execute(sql`drop type if exists ${name(mechanicEnum.enumName)}`);
|
||||
// await db.execute(sql`drop type if exists ${name(equipmentEnum.enumName)}`);
|
||||
// await db.execute(sql`drop type if exists ${name(categoryEnum.enumName)}`);
|
||||
|
||||
// await db.execute(
|
||||
// sql`create type ${
|
||||
// name(muscleEnum.enumName)
|
||||
// } as enum ('abdominals', 'hamstrings', 'adductors', 'quadriceps', 'biceps', 'shoulders', 'chest', 'middle_back', 'calves', 'glutes', 'lower_back', 'lats', 'triceps', 'traps', 'forearms', 'neck', 'abductors')`,
|
||||
// );
|
||||
// await db.execute(sql`create type ${name(forceEnum.enumName)} as enum ('isometric', 'isotonic', 'isokinetic')`);
|
||||
// await db.execute(sql`create type ${name(levelEnum.enumName)} as enum ('beginner', 'intermediate', 'advanced')`);
|
||||
// await db.execute(sql`create type ${name(mechanicEnum.enumName)} as enum ('compound', 'isolation')`);
|
||||
// await db.execute(
|
||||
// sql`create type ${
|
||||
// name(equipmentEnum.enumName)
|
||||
// } as enum ('barbell', 'dumbbell', 'bodyweight', 'machine', 'cable', 'kettlebell')`,
|
||||
// );
|
||||
// await db.execute(sql`create type ${name(categoryEnum.enumName)} as enum ('upper_body', 'lower_body', 'full_body')`);
|
||||
// await db.execute(sql`
|
||||
// create table ${exercises} (
|
||||
// id serial primary key,
|
||||
// name varchar not null,
|
||||
// force force,
|
||||
// level level,
|
||||
// mechanic mechanic,
|
||||
// equipment equipment,
|
||||
// instructions text,
|
||||
// category category,
|
||||
// primary_muscles muscle[],
|
||||
// secondary_muscles muscle[],
|
||||
// created_at timestamp not null default now(),
|
||||
// updated_at timestamp not null default now()
|
||||
// )
|
||||
// `);
|
||||
|
||||
// await db.insert(exercises).values({
|
||||
// name: 'Bench Press',
|
||||
// force: 'isotonic',
|
||||
// level: 'beginner',
|
||||
// mechanic: 'compound',
|
||||
// equipment: 'barbell',
|
||||
// instructions:
|
||||
// 'Lie on your back on a flat bench. Grasp the barbell with an overhand grip, slightly wider than shoulder width. Unrack the barbell and hold it over you with your arms locked. Lower the barbell to your chest. Press the barbell back to the starting position.',
|
||||
// category: 'upper_body',
|
||||
// primaryMuscles: ['chest', 'triceps'],
|
||||
// secondaryMuscles: ['shoulders', 'traps'],
|
||||
// });
|
||||
|
||||
// const result = await db.select().from(exercises);
|
||||
|
||||
// t.deepEqual(result, [
|
||||
// {
|
||||
// id: 1,
|
||||
// name: 'Bench Press',
|
||||
// force: 'isotonic',
|
||||
// level: 'beginner',
|
||||
// mechanic: 'compound',
|
||||
// equipment: 'barbell',
|
||||
// instructions:
|
||||
// 'Lie on your back on a flat bench. Grasp the barbell with an overhand grip, slightly wider than shoulder width. Unrack the barbell and hold it over you with your arms locked. Lower the barbell to your chest. Press the barbell back to the starting position.',
|
||||
// category: 'upper_body',
|
||||
// primaryMuscles: ['chest', 'triceps'],
|
||||
// secondaryMuscles: ['shoulders', 'traps'],
|
||||
// createdAt: result[0]!.createdAt,
|
||||
// updatedAt: result[0]!.updatedAt,
|
||||
// },
|
||||
// ]);
|
||||
|
||||
// await db.execute(sql`drop table ${exercises}`);
|
||||
// await db.execute(sql`drop type ${name(muscleEnum.enumName)}`);
|
||||
// await db.execute(sql`drop type ${name(forceEnum.enumName)}`);
|
||||
// await db.execute(sql`drop type ${name(levelEnum.enumName)}`);
|
||||
// await db.execute(sql`drop type ${name(mechanicEnum.enumName)}`);
|
||||
// await db.execute(sql`drop type ${name(equipmentEnum.enumName)}`);
|
||||
// await db.execute(sql`drop type ${name(categoryEnum.enumName)}`);
|
||||
// });
|
||||
|
||||
// test.after.always(async (t) => {
|
||||
// const ctx = t.context;
|
||||
|
||||
// await ctx.db.execute(sql`drop table "all_columns"`);
|
||||
// });
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,281 @@
|
||||
/// <reference types="bun-types" />
|
||||
import { Database } from 'bun:sqlite';
|
||||
import { DefaultLogger, sql } from 'drizzle-orm';
|
||||
import type { BunSQLiteDatabase } from 'drizzle-orm/bun-sqlite';
|
||||
import { drizzle } from 'drizzle-orm/bun-sqlite';
|
||||
import { integer, real, sqliteTable, text } from 'drizzle-orm/sqlite-core';
|
||||
import { suite } from 'uvu';
|
||||
|
||||
const order = sqliteTable('Order', {
|
||||
id: integer('Id'),
|
||||
customerId: text('CustomerId'),
|
||||
employeeId: integer('EmployeeId'),
|
||||
orderDate: text('OrderDate'),
|
||||
requiredDate: text('RequiredDate'),
|
||||
shippedDate: text('ShippedDate'),
|
||||
shipVia: integer('ShipVia'),
|
||||
freight: real('Freight'),
|
||||
shipName: text('ShipName'),
|
||||
shipAddress: text('ShipAddress'),
|
||||
shipCity: text('ShipCity'),
|
||||
shipRegion: text('ShipRegion'),
|
||||
shipPostalCode: text('ShipPostalCode'),
|
||||
shipCountry: text('ShipCountry'),
|
||||
});
|
||||
|
||||
interface Context {
|
||||
db: BunSQLiteDatabase;
|
||||
}
|
||||
|
||||
const test = suite<Context>('sqlite-bun');
|
||||
|
||||
test.before((ctx) => {
|
||||
try {
|
||||
const dbPath = process.env['SQLITE_DB_PATH'] ?? ':memory:';
|
||||
|
||||
const client = new Database(dbPath);
|
||||
ctx.db = drizzle(client, { logger: new DefaultLogger() });
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
});
|
||||
|
||||
// test.before.each((ctx) => {
|
||||
// try {
|
||||
// const { db } = ctx;
|
||||
|
||||
// db.run(sql`drop table if exists ${usersTable}`);
|
||||
// db.run(sql`
|
||||
// create table ${usersTable} (
|
||||
// id integer primary key,
|
||||
// name text not null,
|
||||
// verified integer not null default 0,
|
||||
// json blob,
|
||||
// created_at text not null default (strftime('%s', 'now'))
|
||||
// )`);
|
||||
// } catch (e) {
|
||||
// console.error(e);
|
||||
// }
|
||||
// });
|
||||
|
||||
test('select', (ctx) => {
|
||||
const { db } = ctx;
|
||||
|
||||
// TODO: convert to normalniy test
|
||||
console.log(db.select().from(order).where(sql`"Order"."ShipCountry" = "Germany"`).all()[0]);
|
||||
console.log(db.select().from(order).where(sql`"Order"."ShipCountry" = ${'Germany'}`).prepare().all()[0]);
|
||||
});
|
||||
|
||||
test.run();
|
||||
|
||||
// test.serial('select partial', (t) => {
|
||||
// const { db } = t.context;
|
||||
|
||||
// db.insert(usersTable).values({ name: 'John' }).execute();
|
||||
// const result = db.select({ name: usersTable.name }).from(usersTable).execute();
|
||||
|
||||
// t.deepEqual(result, [{ name: 'John' }]);
|
||||
// });
|
||||
|
||||
// test.serial('insert with auto increment', (t) => {
|
||||
// const { db } = t.context;
|
||||
|
||||
// db.insert(usersTable).values(
|
||||
// { name: 'John' },
|
||||
// { name: 'Jane' },
|
||||
// { name: 'George' },
|
||||
// { name: 'Austin' },
|
||||
// ).execute();
|
||||
// const result = db.select({ id: usersTable.id, name: usersTable.name }).from(usersTable).execute();
|
||||
|
||||
// t.deepEqual(result, [
|
||||
// { id: 1, name: 'John' },
|
||||
// { id: 2, name: 'Jane' },
|
||||
// { id: 3, name: 'George' },
|
||||
// { id: 4, name: 'Austin' },
|
||||
// ]);
|
||||
// });
|
||||
|
||||
// test.serial('insert with default values', (t) => {
|
||||
// const { db } = t.context;
|
||||
|
||||
// db.insert(usersTable).values({ name: 'John' }).execute();
|
||||
// const result = db.select({
|
||||
// id: usersTable.id,
|
||||
// name: usersTable.name,
|
||||
// verified: usersTable.verified,
|
||||
// }).from(usersTable).execute();
|
||||
|
||||
// t.deepEqual(result, [{ id: 1, name: 'John', verified: 0 }]);
|
||||
// });
|
||||
|
||||
// test.serial('insert with overridden default values', (t) => {
|
||||
// const { db } = t.context;
|
||||
|
||||
// db.insert(usersTable).values({ name: 'John', verified: 1 }).execute();
|
||||
// const result = db.select({
|
||||
// id: usersTable.id,
|
||||
// name: usersTable.name,
|
||||
// verified: usersTable.verified,
|
||||
// }).from(usersTable).execute();
|
||||
|
||||
// t.deepEqual(result, [{ id: 1, name: 'John', verified: 1 }]);
|
||||
// });
|
||||
|
||||
// test.serial('update with returning all fields', (t) => {
|
||||
// const { db } = t.context;
|
||||
|
||||
// const now = Date.now();
|
||||
|
||||
// db.insert(usersTable).values({ name: 'John' }).execute();
|
||||
// const users = db.update(usersTable).set({ name: 'Jane' }).where(eq(usersTable.name, 'John')).returning().execute();
|
||||
|
||||
// t.assert(users[0]!.createdAt instanceof Date);
|
||||
// t.assert(Math.abs(users[0]!.createdAt.getTime() - now) < 100);
|
||||
// t.deepEqual(users, [{ id: 1, name: 'Jane', verified: 0, json: null, createdAt: users[0]!.createdAt }]);
|
||||
// });
|
||||
|
||||
// test.serial('update with returning partial', (t) => {
|
||||
// const { db } = t.context;
|
||||
|
||||
// db.insert(usersTable).values({ name: 'John' }).execute();
|
||||
// const users = db.update(usersTable).set({ name: 'Jane' }).where(eq(usersTable.name, 'John')).returning({
|
||||
// id: usersTable.id,
|
||||
// name: usersTable.name,
|
||||
// }).execute();
|
||||
|
||||
// t.deepEqual(users, [{ id: 1, name: 'Jane' }]);
|
||||
// });
|
||||
|
||||
// test.serial('delete with returning all fields', (t) => {
|
||||
// const { db } = t.context;
|
||||
|
||||
// const now = Date.now();
|
||||
|
||||
// db.insert(usersTable).values({ name: 'John' }).execute();
|
||||
// const users = db.delete(usersTable).where(eq(usersTable.name, 'John')).returning().execute();
|
||||
|
||||
// t.assert(users[0]!.createdAt instanceof Date);
|
||||
// t.assert(Math.abs(users[0]!.createdAt.getTime() - now) < 100);
|
||||
// t.deepEqual(users, [{ id: 1, name: 'John', verified: 0, json: null, createdAt: users[0]!.createdAt }]);
|
||||
// });
|
||||
|
||||
// test.serial('delete with returning partial', (t) => {
|
||||
// const { db } = t.context;
|
||||
|
||||
// db.insert(usersTable).values({ name: 'John' }).execute();
|
||||
// const users = db.delete(usersTable).where(eq(usersTable.name, 'John')).returning({
|
||||
// id: usersTable.id,
|
||||
// name: usersTable.name,
|
||||
// }).execute();
|
||||
|
||||
// t.deepEqual(users, [{ id: 1, name: 'John' }]);
|
||||
// });
|
||||
|
||||
// test.serial('insert + select', (t) => {
|
||||
// const { db } = t.context;
|
||||
|
||||
// db.insert(usersTable).values({ name: 'John' }).execute();
|
||||
// const result = db.select({ id: usersTable.id, name: usersTable.name }).from(usersTable).execute();
|
||||
|
||||
// t.deepEqual(result, [{ id: 1, name: 'John' }]);
|
||||
|
||||
// db.insert(usersTable).values({ name: 'Jane' }).execute();
|
||||
// const result2 = db.select({ id: usersTable.id, name: usersTable.name }).from(usersTable).execute();
|
||||
|
||||
// t.deepEqual(result2, [{ id: 1, name: 'John' }, { id: 2, name: 'Jane' }]);
|
||||
// });
|
||||
|
||||
// test.serial('json insert', (t) => {
|
||||
// const { db } = t.context;
|
||||
|
||||
// db.insert(usersTable).values({ name: 'John', json: ['foo', 'bar'] }).execute();
|
||||
// const result = db.select({
|
||||
// id: usersTable.id,
|
||||
// name: usersTable.name,
|
||||
// json: usersTable.json,
|
||||
// }).from(usersTable).execute();
|
||||
|
||||
// t.deepEqual(result, [{ id: 1, name: 'John', json: ['foo', 'bar'] }]);
|
||||
// });
|
||||
|
||||
// test.serial('insert many', (t) => {
|
||||
// const { db } = t.context;
|
||||
|
||||
// db.insert(usersTable).values(
|
||||
// { name: 'John' },
|
||||
// { name: 'Bruce', json: ['foo', 'bar'] },
|
||||
// { name: 'Jane' },
|
||||
// { name: 'Austin', verified: 1 },
|
||||
// ).execute();
|
||||
// const result = db.select({
|
||||
// id: usersTable.id,
|
||||
// name: usersTable.name,
|
||||
// json: usersTable.json,
|
||||
// verified: usersTable.verified,
|
||||
// }).from(usersTable).execute();
|
||||
|
||||
// t.deepEqual(result, [
|
||||
// { id: 1, name: 'John', json: null, verified: 0 },
|
||||
// { id: 2, name: 'Bruce', json: ['foo', 'bar'], verified: 0 },
|
||||
// { id: 3, name: 'Jane', json: null, verified: 0 },
|
||||
// { id: 4, name: 'Austin', json: null, verified: 1 },
|
||||
// ]);
|
||||
// });
|
||||
|
||||
// test.serial('insert many with returning', (t) => {
|
||||
// const { db } = t.context;
|
||||
|
||||
// const result = db.insert(usersTable).values(
|
||||
// { name: 'John' },
|
||||
// { name: 'Bruce', json: ['foo', 'bar'] },
|
||||
// { name: 'Jane' },
|
||||
// { name: 'Austin', verified: 1 },
|
||||
// )
|
||||
// .returning({
|
||||
// id: usersTable.id,
|
||||
// name: usersTable.name,
|
||||
// json: usersTable.json,
|
||||
// verified: usersTable.verified,
|
||||
// })
|
||||
// .execute();
|
||||
|
||||
// t.deepEqual(result, [
|
||||
// { id: 1, name: 'John', json: null, verified: 0 },
|
||||
// { id: 2, name: 'Bruce', json: ['foo', 'bar'], verified: 0 },
|
||||
// { id: 3, name: 'Jane', json: null, verified: 0 },
|
||||
// { id: 4, name: 'Austin', json: null, verified: 1 },
|
||||
// ]);
|
||||
// });
|
||||
|
||||
// test.serial('join with alias', (t) => {
|
||||
// const { db } = t.context;
|
||||
// const customerAlias = alias(usersTable, 'customer');
|
||||
|
||||
// db.insert(usersTable).values([{ id: 10, name: 'Ivan' }, { id: 11, name: 'Hans' }]).execute();
|
||||
// const result = db
|
||||
// .select().from(usersTable)
|
||||
// .fields({ id: usersTable.id, name: usersTable.name })
|
||||
// .leftJoin(customerAlias, eq(customerAlias.id, 11), { id: customerAlias.id, name: customerAlias.name })
|
||||
// .where(eq(usersTable.id, 10))
|
||||
// .execute();
|
||||
|
||||
// t.deepEqual(result, [{
|
||||
// users: { id: 10, name: 'Ivan' },
|
||||
// customer: { id: 11, name: 'Hans' },
|
||||
// }]);
|
||||
// });
|
||||
|
||||
// test('insert with spaces', (t) => {
|
||||
// const { db } = t.context;
|
||||
|
||||
// db.insert(usersTable).values({ name: sql`'Jo h n'` }).execute();
|
||||
// const result = db.select({ id: usersTable.id, name: usersTable.name }).from(usersTable).execute();
|
||||
|
||||
// t.deepEqual(result, [{ id: 1, name: 'Jo h n' }]);
|
||||
// });
|
||||
|
||||
// test.after.always((t) => {
|
||||
// const ctx = t.context;
|
||||
// ctx.client?.close();
|
||||
// });
|
||||
@@ -0,0 +1,283 @@
|
||||
import { Database } from 'bun:sqlite';
|
||||
import { beforeAll, beforeEach, expect, test } from 'bun:test';
|
||||
import { sql } from 'drizzle-orm';
|
||||
import type { BunSQLiteDatabase } from 'drizzle-orm/bun-sqlite';
|
||||
import { drizzle } from 'drizzle-orm/bun-sqlite';
|
||||
import { blob, integer, sqliteTable, text } from 'drizzle-orm/sqlite-core';
|
||||
|
||||
const usersTable = sqliteTable('users', {
|
||||
id: integer('id').primaryKey(),
|
||||
name: text('name').notNull(),
|
||||
verified: integer('verified').notNull().default(0),
|
||||
json: blob('json', { mode: 'json' }).$type<string[]>(),
|
||||
bigInt: blob('big_int', { mode: 'bigint' }),
|
||||
createdAt: integer('created_at', { mode: 'timestamp_ms' }).notNull().default(sql`strftime('%s', 'now')`),
|
||||
});
|
||||
|
||||
let db: BunSQLiteDatabase;
|
||||
|
||||
beforeAll(async () => {
|
||||
try {
|
||||
const dbPath = process.env['SQLITE_DB_PATH'] ?? ':memory:';
|
||||
|
||||
const client = new Database(dbPath);
|
||||
db = drizzle(client);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
try {
|
||||
db.run(sql`drop table if exists ${usersTable}`);
|
||||
db.run(sql`
|
||||
create table ${usersTable} (
|
||||
id integer primary key,
|
||||
name text not null,
|
||||
verified integer not null default 0,
|
||||
json blob,
|
||||
big_int blob,
|
||||
created_at integer not null default (strftime('%s', 'now'))
|
||||
)
|
||||
`);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
});
|
||||
|
||||
test.skip('select large integer', () => {
|
||||
const a = 1667476703000;
|
||||
const res = db.all<{ a: number }>(sql`select ${sql.raw(String(a))} as a`);
|
||||
const result = res[0]!;
|
||||
expect(result.a).toEqual(a);
|
||||
});
|
||||
|
||||
test('select all fields', () => {
|
||||
const now = Date.now();
|
||||
|
||||
db.insert(usersTable).values({ name: 'John' }).run();
|
||||
const result = db.select().from(usersTable).all()[0]!;
|
||||
|
||||
expect(result.createdAt).toBeInstanceOf(Date);
|
||||
expect(Math.abs(result.createdAt.getTime() - now)).toBeLessThan(100);
|
||||
expect(result).toEqual({ id: 1, name: 'John', verified: 0, json: null, createdAt: result.createdAt, bigInt: null });
|
||||
});
|
||||
|
||||
test('select bigint', () => {
|
||||
db.insert(usersTable).values({ name: 'John', bigInt: BigInt(100) }).run();
|
||||
const result = db.select({ bigInt: usersTable.bigInt }).from(usersTable).all()[0]!;
|
||||
|
||||
expect(result).toEqual({ bigInt: BigInt(100) });
|
||||
});
|
||||
|
||||
// test.serial('select partial', (t) => {
|
||||
// const { db } = t.context;
|
||||
|
||||
// db.insert(usersTable).values({ name: 'John' }).execute();
|
||||
// const result = db.select({ name: usersTable.name }).from(usersTable).execute();
|
||||
|
||||
// t.deepEqual(result, [{ name: 'John' }]);
|
||||
// });
|
||||
|
||||
// test.serial('insert with auto increment', (t) => {
|
||||
// const { db } = t.context;
|
||||
|
||||
// db.insert(usersTable).values(
|
||||
// { name: 'John' },
|
||||
// { name: 'Jane' },
|
||||
// { name: 'George' },
|
||||
// { name: 'Austin' },
|
||||
// ).execute();
|
||||
// const result = db.select({ id: usersTable.id, name: usersTable.name }).from(usersTable).execute();
|
||||
|
||||
// t.deepEqual(result, [
|
||||
// { id: 1, name: 'John' },
|
||||
// { id: 2, name: 'Jane' },
|
||||
// { id: 3, name: 'George' },
|
||||
// { id: 4, name: 'Austin' },
|
||||
// ]);
|
||||
// });
|
||||
|
||||
// test.serial('insert with default values', (t) => {
|
||||
// const { db } = t.context;
|
||||
|
||||
// db.insert(usersTable).values({ name: 'John' }).execute();
|
||||
// const result = db.select({
|
||||
// id: usersTable.id,
|
||||
// name: usersTable.name,
|
||||
// verified: usersTable.verified,
|
||||
// }).from(usersTable).execute();
|
||||
|
||||
// t.deepEqual(result, [{ id: 1, name: 'John', verified: 0 }]);
|
||||
// });
|
||||
|
||||
// test.serial('insert with overridden default values', (t) => {
|
||||
// const { db } = t.context;
|
||||
|
||||
// db.insert(usersTable).values({ name: 'John', verified: 1 }).execute();
|
||||
// const result = db.select({
|
||||
// id: usersTable.id,
|
||||
// name: usersTable.name,
|
||||
// verified: usersTable.verified,
|
||||
// }).from(usersTable).execute();
|
||||
|
||||
// t.deepEqual(result, [{ id: 1, name: 'John', verified: 1 }]);
|
||||
// });
|
||||
|
||||
// test.serial('update with returning all fields', (t) => {
|
||||
// const { db } = t.context;
|
||||
|
||||
// const now = Date.now();
|
||||
|
||||
// db.insert(usersTable).values({ name: 'John' }).execute();
|
||||
// const users = db.update(usersTable).set({ name: 'Jane' }).where(eq(usersTable.name, 'John')).returning().execute();
|
||||
|
||||
// t.assert(users[0]!.createdAt instanceof Date);
|
||||
// t.assert(Math.abs(users[0]!.createdAt.getTime() - now) < 100);
|
||||
// t.deepEqual(users, [{ id: 1, name: 'Jane', verified: 0, json: null, createdAt: users[0]!.createdAt }]);
|
||||
// });
|
||||
|
||||
// test.serial('update with returning partial', (t) => {
|
||||
// const { db } = t.context;
|
||||
|
||||
// db.insert(usersTable).values({ name: 'John' }).execute();
|
||||
// const users = db.update(usersTable).set({ name: 'Jane' }).where(eq(usersTable.name, 'John')).returning({
|
||||
// id: usersTable.id,
|
||||
// name: usersTable.name,
|
||||
// }).execute();
|
||||
|
||||
// t.deepEqual(users, [{ id: 1, name: 'Jane' }]);
|
||||
// });
|
||||
|
||||
// test.serial('delete with returning all fields', (t) => {
|
||||
// const { db } = t.context;
|
||||
|
||||
// const now = Date.now();
|
||||
|
||||
// db.insert(usersTable).values({ name: 'John' }).execute();
|
||||
// const users = db.delete(usersTable).where(eq(usersTable.name, 'John')).returning().execute();
|
||||
|
||||
// t.assert(users[0]!.createdAt instanceof Date);
|
||||
// t.assert(Math.abs(users[0]!.createdAt.getTime() - now) < 100);
|
||||
// t.deepEqual(users, [{ id: 1, name: 'John', verified: 0, json: null, createdAt: users[0]!.createdAt }]);
|
||||
// });
|
||||
|
||||
// test.serial('delete with returning partial', (t) => {
|
||||
// const { db } = t.context;
|
||||
|
||||
// db.insert(usersTable).values({ name: 'John' }).execute();
|
||||
// const users = db.delete(usersTable).where(eq(usersTable.name, 'John')).returning({
|
||||
// id: usersTable.id,
|
||||
// name: usersTable.name,
|
||||
// }).execute();
|
||||
|
||||
// t.deepEqual(users, [{ id: 1, name: 'John' }]);
|
||||
// });
|
||||
|
||||
// test.serial('insert + select', (t) => {
|
||||
// const { db } = t.context;
|
||||
|
||||
// db.insert(usersTable).values({ name: 'John' }).execute();
|
||||
// const result = db.select({ id: usersTable.id, name: usersTable.name }).from(usersTable).execute();
|
||||
|
||||
// t.deepEqual(result, [{ id: 1, name: 'John' }]);
|
||||
|
||||
// db.insert(usersTable).values({ name: 'Jane' }).execute();
|
||||
// const result2 = db.select({ id: usersTable.id, name: usersTable.name }).from(usersTable).execute();
|
||||
|
||||
// t.deepEqual(result2, [{ id: 1, name: 'John' }, { id: 2, name: 'Jane' }]);
|
||||
// });
|
||||
|
||||
// test.serial('json insert', (t) => {
|
||||
// const { db } = t.context;
|
||||
|
||||
// db.insert(usersTable).values({ name: 'John', json: ['foo', 'bar'] }).execute();
|
||||
// const result = db.select({
|
||||
// id: usersTable.id,
|
||||
// name: usersTable.name,
|
||||
// json: usersTable.json,
|
||||
// }).from(usersTable).execute();
|
||||
|
||||
// t.deepEqual(result, [{ id: 1, name: 'John', json: ['foo', 'bar'] }]);
|
||||
// });
|
||||
|
||||
// test.serial('insert many', (t) => {
|
||||
// const { db } = t.context;
|
||||
|
||||
// db.insert(usersTable).values(
|
||||
// { name: 'John' },
|
||||
// { name: 'Bruce', json: ['foo', 'bar'] },
|
||||
// { name: 'Jane' },
|
||||
// { name: 'Austin', verified: 1 },
|
||||
// ).execute();
|
||||
// const result = db.select({
|
||||
// id: usersTable.id,
|
||||
// name: usersTable.name,
|
||||
// json: usersTable.json,
|
||||
// verified: usersTable.verified,
|
||||
// }).from(usersTable).execute();
|
||||
|
||||
// t.deepEqual(result, [
|
||||
// { id: 1, name: 'John', json: null, verified: 0 },
|
||||
// { id: 2, name: 'Bruce', json: ['foo', 'bar'], verified: 0 },
|
||||
// { id: 3, name: 'Jane', json: null, verified: 0 },
|
||||
// { id: 4, name: 'Austin', json: null, verified: 1 },
|
||||
// ]);
|
||||
// });
|
||||
|
||||
// test.serial('insert many with returning', (t) => {
|
||||
// const { db } = t.context;
|
||||
|
||||
// const result = db.insert(usersTable).values(
|
||||
// { name: 'John' },
|
||||
// { name: 'Bruce', json: ['foo', 'bar'] },
|
||||
// { name: 'Jane' },
|
||||
// { name: 'Austin', verified: 1 },
|
||||
// )
|
||||
// .returning({
|
||||
// id: usersTable.id,
|
||||
// name: usersTable.name,
|
||||
// json: usersTable.json,
|
||||
// verified: usersTable.verified,
|
||||
// })
|
||||
// .execute();
|
||||
|
||||
// t.deepEqual(result, [
|
||||
// { id: 1, name: 'John', json: null, verified: 0 },
|
||||
// { id: 2, name: 'Bruce', json: ['foo', 'bar'], verified: 0 },
|
||||
// { id: 3, name: 'Jane', json: null, verified: 0 },
|
||||
// { id: 4, name: 'Austin', json: null, verified: 1 },
|
||||
// ]);
|
||||
// });
|
||||
|
||||
// test.serial('join with alias', (t) => {
|
||||
// const { db } = t.context;
|
||||
// const customerAlias = alias(usersTable, 'customer');
|
||||
|
||||
// db.insert(usersTable).values([{ id: 10, name: 'Ivan' }, { id: 11, name: 'Hans' }]).execute();
|
||||
// const result = db
|
||||
// .select().from(usersTable)
|
||||
// .fields({ id: usersTable.id, name: usersTable.name })
|
||||
// .leftJoin(customerAlias, eq(customerAlias.id, 11), { id: customerAlias.id, name: customerAlias.name })
|
||||
// .where(eq(usersTable.id, 10))
|
||||
// .execute();
|
||||
|
||||
// t.deepEqual(result, [{
|
||||
// users: { id: 10, name: 'Ivan' },
|
||||
// customer: { id: 11, name: 'Hans' },
|
||||
// }]);
|
||||
// });
|
||||
|
||||
// test('insert with spaces', (t) => {
|
||||
// const { db } = t.context;
|
||||
|
||||
// db.insert(usersTable).values({ name: sql`'Jo h n'` }).execute();
|
||||
// const result = db.select({ id: usersTable.id, name: usersTable.name }).from(usersTable).execute();
|
||||
|
||||
// t.deepEqual(result, [{ id: 1, name: 'Jo h n' }]);
|
||||
// });
|
||||
|
||||
// test.after.always((t) => {
|
||||
// const ctx = t.context;
|
||||
// ctx.client?.close();
|
||||
// });
|
||||
@@ -0,0 +1,9 @@
|
||||
import { beforeEach } from 'vitest';
|
||||
|
||||
export function skipTests(names: string[]) {
|
||||
beforeEach((ctx) => {
|
||||
if (ctx.task.suite?.name === 'common' && names.includes(ctx.task.name)) {
|
||||
ctx.skip();
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
import Docker from 'dockerode';
|
||||
import { sql } from 'drizzle-orm';
|
||||
import type { NodePgDatabase } from 'drizzle-orm/node-postgres';
|
||||
import { drizzle } from 'drizzle-orm/node-postgres';
|
||||
import { bigserial, geometry, line, pgTable, point } from 'drizzle-orm/pg-core';
|
||||
import getPort from 'get-port';
|
||||
import pg from 'pg';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import { afterAll, beforeAll, beforeEach, expect, test } from 'vitest';
|
||||
|
||||
const { Client } = pg;
|
||||
|
||||
const ENABLE_LOGGING = false;
|
||||
|
||||
let pgContainer: Docker.Container;
|
||||
let docker: Docker;
|
||||
let client: pg.Client;
|
||||
let db: NodePgDatabase;
|
||||
|
||||
async function createDockerDB(): Promise<string> {
|
||||
const inDocker = (docker = new Docker());
|
||||
const port = await getPort({ port: 5432 });
|
||||
const image = 'postgis/postgis:16-3.4';
|
||||
|
||||
const pullStream = await docker.pull(image);
|
||||
await new Promise((resolve, reject) =>
|
||||
inDocker.modem.followProgress(pullStream, (err) => (err ? reject(err) : resolve(err)))
|
||||
);
|
||||
|
||||
pgContainer = await docker.createContainer({
|
||||
Image: image,
|
||||
Env: ['POSTGRES_PASSWORD=postgres', 'POSTGRES_USER=postgres', 'POSTGRES_DB=postgres'],
|
||||
name: `drizzle-integration-tests-${uuid()}`,
|
||||
HostConfig: {
|
||||
AutoRemove: true,
|
||||
PortBindings: {
|
||||
'5432/tcp': [{ HostPort: `${port}` }],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await pgContainer.start();
|
||||
|
||||
return `postgres://postgres:postgres@localhost:${port}/postgres`;
|
||||
}
|
||||
|
||||
beforeAll(async () => {
|
||||
const connectionString = process.env['PG_POSTGIS_CONNECTION_STRING'] ?? (await createDockerDB());
|
||||
|
||||
const sleep = 1000;
|
||||
let timeLeft = 20000;
|
||||
let connected = false;
|
||||
let lastError: unknown | undefined;
|
||||
do {
|
||||
try {
|
||||
client = new Client(connectionString);
|
||||
await client.connect();
|
||||
connected = true;
|
||||
break;
|
||||
} catch (e) {
|
||||
lastError = e;
|
||||
await new Promise((resolve) => setTimeout(resolve, sleep));
|
||||
timeLeft -= sleep;
|
||||
}
|
||||
} while (timeLeft > 0);
|
||||
if (!connected) {
|
||||
console.error('Cannot connect to Postgres');
|
||||
await client?.end().catch(console.error);
|
||||
await pgContainer?.stop().catch(console.error);
|
||||
throw lastError;
|
||||
}
|
||||
db = drizzle(client, { logger: ENABLE_LOGGING });
|
||||
|
||||
await db.execute(sql`CREATE EXTENSION IF NOT EXISTS postgis;`);
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await client?.end().catch(console.error);
|
||||
await pgContainer?.stop().catch(console.error);
|
||||
});
|
||||
|
||||
const items = pgTable('items', {
|
||||
id: bigserial('id', { mode: 'number' }).primaryKey(),
|
||||
point: point('point'),
|
||||
pointObj: point('point_xy', { mode: 'xy' }),
|
||||
line: line('line'),
|
||||
lineObj: line('line_abc', { mode: 'abc' }),
|
||||
geo: geometry('geo', { type: 'point' }),
|
||||
geoObj: geometry('geo_obj', { type: 'point', mode: 'xy' }),
|
||||
geoSrid: geometry('geo_options', { type: 'point', mode: 'xy', srid: 4000 }),
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await db.execute(sql`drop table if exists items cascade`);
|
||||
await db.execute(sql`
|
||||
CREATE TABLE items (
|
||||
id bigserial PRIMARY KEY,
|
||||
"point" point,
|
||||
"point_xy" point,
|
||||
"line" line,
|
||||
"line_abc" line,
|
||||
"geo" geometry(point),
|
||||
"geo_obj" geometry(point),
|
||||
"geo_options" geometry(point,4000)
|
||||
);
|
||||
`);
|
||||
});
|
||||
|
||||
test('insert + select', async () => {
|
||||
const insertedValues = await db.insert(items).values([{
|
||||
point: [1, 2],
|
||||
pointObj: { x: 1, y: 2 },
|
||||
line: [1, 2, 3],
|
||||
lineObj: { a: 1, b: 2, c: 3 },
|
||||
geo: [1, 2],
|
||||
geoObj: { x: 1, y: 2 },
|
||||
geoSrid: { x: 1, y: 2 },
|
||||
}]).returning();
|
||||
|
||||
const response = await db.select().from(items);
|
||||
|
||||
expect(insertedValues).toStrictEqual([{
|
||||
id: 1,
|
||||
point: [1, 2],
|
||||
pointObj: { x: 1, y: 2 },
|
||||
line: [1, 2, 3],
|
||||
lineObj: { a: 1, b: 2, c: 3 },
|
||||
geo: [1, 2],
|
||||
geoObj: { x: 1, y: 2 },
|
||||
geoSrid: { x: 1, y: 2 },
|
||||
}]);
|
||||
|
||||
expect(response).toStrictEqual([{
|
||||
id: 1,
|
||||
point: [1, 2],
|
||||
pointObj: { x: 1, y: 2 },
|
||||
line: [1, 2, 3],
|
||||
lineObj: { a: 1, b: 2, c: 3 },
|
||||
geo: [1, 2],
|
||||
geoObj: { x: 1, y: 2 },
|
||||
geoSrid: { x: 1, y: 2 },
|
||||
}]);
|
||||
});
|
||||
@@ -0,0 +1,145 @@
|
||||
import Docker from 'dockerode';
|
||||
import { sql } from 'drizzle-orm';
|
||||
import { bigserial, geometry, line, pgTable, point } from 'drizzle-orm/pg-core';
|
||||
import { drizzle, type PostgresJsDatabase } from 'drizzle-orm/postgres-js';
|
||||
import getPort from 'get-port';
|
||||
import postgres, { type Sql } from 'postgres';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import { afterAll, beforeAll, beforeEach, expect, test } from 'vitest';
|
||||
|
||||
const ENABLE_LOGGING = false;
|
||||
|
||||
let pgContainer: Docker.Container;
|
||||
let docker: Docker;
|
||||
let client: Sql;
|
||||
let db: PostgresJsDatabase;
|
||||
|
||||
async function createDockerDB(): Promise<string> {
|
||||
const inDocker = (docker = new Docker());
|
||||
const port = await getPort({ port: 5432 });
|
||||
const image = 'postgis/postgis:16-3.4';
|
||||
|
||||
const pullStream = await docker.pull(image);
|
||||
await new Promise((resolve, reject) =>
|
||||
inDocker.modem.followProgress(pullStream, (err) => (err ? reject(err) : resolve(err)))
|
||||
);
|
||||
|
||||
pgContainer = await docker.createContainer({
|
||||
Image: image,
|
||||
Env: ['POSTGRES_PASSWORD=postgres', 'POSTGRES_USER=postgres', 'POSTGRES_DB=postgres'],
|
||||
name: `drizzle-integration-tests-${uuid()}`,
|
||||
HostConfig: {
|
||||
AutoRemove: true,
|
||||
PortBindings: {
|
||||
'5432/tcp': [{ HostPort: `${port}` }],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await pgContainer.start();
|
||||
|
||||
return `postgres://postgres:postgres@localhost:${port}/postgres`;
|
||||
}
|
||||
|
||||
beforeAll(async () => {
|
||||
const connectionString = process.env['PG_POSTGIS_CONNECTION_STRING'] ?? (await createDockerDB());
|
||||
|
||||
const sleep = 1000;
|
||||
let timeLeft = 20000;
|
||||
let connected = false;
|
||||
let lastError: unknown | undefined;
|
||||
do {
|
||||
try {
|
||||
client = postgres(connectionString, {
|
||||
max: 1,
|
||||
onnotice: () => {
|
||||
// disable notices
|
||||
},
|
||||
});
|
||||
await client`select 1`;
|
||||
connected = true;
|
||||
break;
|
||||
} catch (e) {
|
||||
lastError = e;
|
||||
await new Promise((resolve) => setTimeout(resolve, sleep));
|
||||
timeLeft -= sleep;
|
||||
}
|
||||
} while (timeLeft > 0);
|
||||
if (!connected) {
|
||||
console.error('Cannot connect to Postgres');
|
||||
await client?.end().catch(console.error);
|
||||
await pgContainer?.stop().catch(console.error);
|
||||
throw lastError;
|
||||
}
|
||||
db = drizzle(client, { logger: ENABLE_LOGGING });
|
||||
|
||||
await db.execute(sql`CREATE EXTENSION IF NOT EXISTS postgis;`);
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await client?.end().catch(console.error);
|
||||
await pgContainer?.stop().catch(console.error);
|
||||
});
|
||||
|
||||
const items = pgTable('items', {
|
||||
id: bigserial('id', { mode: 'number' }).primaryKey(),
|
||||
point: point('point'),
|
||||
pointObj: point('point_xy', { mode: 'xy' }),
|
||||
line: line('line'),
|
||||
lineObj: line('line_abc', { mode: 'abc' }),
|
||||
geo: geometry('geo', { type: 'point' }),
|
||||
geoObj: geometry('geo_obj', { type: 'point', mode: 'xy' }),
|
||||
geoSrid: geometry('geo_options', { type: 'point', mode: 'xy', srid: 4000 }),
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await db.execute(sql`drop table if exists items cascade`);
|
||||
await db.execute(sql`
|
||||
CREATE TABLE items (
|
||||
id bigserial PRIMARY KEY,
|
||||
"point" point,
|
||||
"point_xy" point,
|
||||
"line" line,
|
||||
"line_abc" line,
|
||||
"geo" geometry(point),
|
||||
"geo_obj" geometry(point),
|
||||
"geo_options" geometry(point,4000)
|
||||
);
|
||||
`);
|
||||
});
|
||||
|
||||
test('insert + select', async () => {
|
||||
const insertedValues = await db.insert(items).values([{
|
||||
point: [1, 2],
|
||||
pointObj: { x: 1, y: 2 },
|
||||
line: [1, 2, 3],
|
||||
lineObj: { a: 1, b: 2, c: 3 },
|
||||
geo: [1, 2],
|
||||
geoObj: { x: 1, y: 2 },
|
||||
geoSrid: { x: 1, y: 2 },
|
||||
}]).returning();
|
||||
|
||||
const response = await db.select().from(items);
|
||||
|
||||
expect(insertedValues).toStrictEqual([{
|
||||
id: 1,
|
||||
point: [1, 2],
|
||||
pointObj: { x: 1, y: 2 },
|
||||
line: [1, 2, 3],
|
||||
lineObj: { a: 1, b: 2, c: 3 },
|
||||
geo: [1, 2],
|
||||
geoObj: { x: 1, y: 2 },
|
||||
geoSrid: { x: 1, y: 2 },
|
||||
}]);
|
||||
|
||||
expect(response).toStrictEqual([{
|
||||
id: 1,
|
||||
point: [1, 2],
|
||||
pointObj: { x: 1, y: 2 },
|
||||
line: [1, 2, 3],
|
||||
lineObj: { a: 1, b: 2, c: 3 },
|
||||
geo: [1, 2],
|
||||
geoObj: { x: 1, y: 2 },
|
||||
geoSrid: { x: 1, y: 2 },
|
||||
}]);
|
||||
});
|
||||
@@ -0,0 +1,381 @@
|
||||
import Docker from 'dockerode';
|
||||
import { eq, hammingDistance, jaccardDistance, l2Distance, not, sql } from 'drizzle-orm';
|
||||
import type { NodePgDatabase } from 'drizzle-orm/node-postgres';
|
||||
import { drizzle } from 'drizzle-orm/node-postgres';
|
||||
import { bigserial, bit, halfvec, pgTable, sparsevec, vector } from 'drizzle-orm/pg-core';
|
||||
import getPort from 'get-port';
|
||||
import pg from 'pg';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import { afterAll, beforeAll, beforeEach, expect, test } from 'vitest';
|
||||
|
||||
const { Client } = pg;
|
||||
|
||||
const ENABLE_LOGGING = false;
|
||||
|
||||
let pgContainer: Docker.Container;
|
||||
let docker: Docker;
|
||||
let client: pg.Client;
|
||||
let db: NodePgDatabase;
|
||||
|
||||
async function createDockerDB(): Promise<string> {
|
||||
const inDocker = (docker = new Docker());
|
||||
const port = await getPort({ port: 5432 });
|
||||
const image = 'pgvector/pgvector:pg16';
|
||||
|
||||
const pullStream = await docker.pull(image);
|
||||
await new Promise((resolve, reject) =>
|
||||
inDocker.modem.followProgress(pullStream, (err) => (err ? reject(err) : resolve(err)))
|
||||
);
|
||||
|
||||
pgContainer = await docker.createContainer({
|
||||
Image: image,
|
||||
Env: ['POSTGRES_PASSWORD=postgres', 'POSTGRES_USER=postgres', 'POSTGRES_DB=postgres'],
|
||||
name: `drizzle-integration-tests-${uuid()}`,
|
||||
HostConfig: {
|
||||
AutoRemove: true,
|
||||
PortBindings: {
|
||||
'5432/tcp': [{ HostPort: `${port}` }],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await pgContainer.start();
|
||||
|
||||
return `postgres://postgres:postgres@localhost:${port}/postgres`;
|
||||
}
|
||||
|
||||
beforeAll(async () => {
|
||||
const connectionString = process.env['PG_VECTOR_CONNECTION_STRING'] ?? (await createDockerDB());
|
||||
|
||||
const sleep = 250;
|
||||
let timeLeft = 5000;
|
||||
let connected = false;
|
||||
let lastError: unknown | undefined;
|
||||
do {
|
||||
try {
|
||||
client = new Client(connectionString);
|
||||
await client.connect();
|
||||
connected = true;
|
||||
break;
|
||||
} catch (e) {
|
||||
lastError = e;
|
||||
await new Promise((resolve) => setTimeout(resolve, sleep));
|
||||
timeLeft -= sleep;
|
||||
}
|
||||
} while (timeLeft > 0);
|
||||
if (!connected) {
|
||||
console.error('Cannot connect to Postgres');
|
||||
await client?.end().catch(console.error);
|
||||
await pgContainer?.stop().catch(console.error);
|
||||
throw lastError;
|
||||
}
|
||||
db = drizzle(client, { logger: ENABLE_LOGGING });
|
||||
|
||||
await db.execute(sql`CREATE EXTENSION IF NOT EXISTS vector;`);
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await client?.end().catch(console.error);
|
||||
await pgContainer?.stop().catch(console.error);
|
||||
});
|
||||
|
||||
const items = pgTable('items', {
|
||||
id: bigserial('id', { mode: 'number' }).primaryKey(),
|
||||
vector: vector('vector', { dimensions: 3 }),
|
||||
bit: bit('bit', { dimensions: 3 }),
|
||||
halfvec: halfvec('halfvec', { dimensions: 3 }),
|
||||
sparsevec: sparsevec('sparsevec', { dimensions: 5 }),
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await db.execute(sql`drop table if exists items cascade`);
|
||||
await db.execute(sql`
|
||||
CREATE TABLE items (
|
||||
id bigserial PRIMARY KEY,
|
||||
"vector" vector(3),
|
||||
"bit" bit(3),
|
||||
"halfvec" halfvec(3),
|
||||
"sparsevec" sparsevec(5)
|
||||
);
|
||||
`);
|
||||
});
|
||||
|
||||
test('insert + partial select', async () => {
|
||||
const insertedValues = await db.insert(items).values([{
|
||||
vector: [3, 1, 2],
|
||||
bit: '000',
|
||||
halfvec: [1, 2, 3],
|
||||
sparsevec: '{1:1,3:2,5:3}/5',
|
||||
}]).returning();
|
||||
|
||||
const query = db.select({ distance: l2Distance(items.vector, [3, 1, 2]) }).from(items);
|
||||
|
||||
expect(query.toSQL()).toStrictEqual({ sql: 'select "vector" <-> $1 from "items"', params: ['[3,1,2]'] });
|
||||
|
||||
const response = await query;
|
||||
|
||||
expect(insertedValues).toStrictEqual([{
|
||||
id: 1,
|
||||
vector: [3, 1, 2],
|
||||
bit: '000',
|
||||
halfvec: [1, 2, 3],
|
||||
sparsevec: '{1:1,3:2,5:3}/5',
|
||||
}]);
|
||||
|
||||
expect(response).toStrictEqual([{ distance: 0 }]);
|
||||
});
|
||||
|
||||
// SELECT * FROM items WHERE embedding <-> '[3,1,2]' < 5;
|
||||
test('insert + complex where', async () => {
|
||||
const insertedValues = await db.insert(items).values([{
|
||||
vector: [3, 1, 2],
|
||||
bit: '000',
|
||||
halfvec: [1, 2, 3],
|
||||
sparsevec: '{1:1,3:2,5:3}/5',
|
||||
}]).returning();
|
||||
|
||||
const query = db.select().from(items)
|
||||
.where(sql`${l2Distance(items.vector, [3, 1, 2])} < ${5}`)
|
||||
.limit(5);
|
||||
|
||||
expect(query.toSQL()).toStrictEqual({
|
||||
sql:
|
||||
'select "id", "vector", "bit", "halfvec", "sparsevec" from "items" where "items"."vector" <-> $1 < $2 limit $3',
|
||||
params: ['[3,1,2]', 5, 5],
|
||||
});
|
||||
const res = await query;
|
||||
|
||||
expect(insertedValues).toStrictEqual([{
|
||||
id: 1,
|
||||
vector: [3, 1, 2],
|
||||
bit: '000',
|
||||
halfvec: [1, 2, 3],
|
||||
sparsevec: '{1:1,3:2,5:3}/5',
|
||||
}]);
|
||||
|
||||
expect(res).toStrictEqual([
|
||||
{
|
||||
id: 1,
|
||||
vector: [3, 1, 2],
|
||||
bit: '000',
|
||||
halfvec: [1, 2, 3],
|
||||
sparsevec: '{1:1,3:2,5:3}/5',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
// SELECT * FROM items WHERE id != 1 ORDER BY embedding <-> (SELECT embedding FROM items WHERE id = 1) LIMIT 5;
|
||||
test('insert + order by subquery', async () => {
|
||||
const insertedValues = await db.insert(items).values([{
|
||||
vector: [3, 1, 2],
|
||||
bit: '000',
|
||||
halfvec: [1, 2, 3],
|
||||
sparsevec: '{1:1,3:2,5:3}/5',
|
||||
}, {
|
||||
vector: [3, 1, 2],
|
||||
bit: '000',
|
||||
halfvec: [1, 2, 3],
|
||||
sparsevec: '{1:1,3:2,5:3}/5',
|
||||
}]).returning();
|
||||
|
||||
const subquery = db.select({ vector: items.vector }).from(items).where(eq(items.id, 1));
|
||||
|
||||
const query = db.select().from(items)
|
||||
.where(not(eq(items.id, 1)))
|
||||
.orderBy(l2Distance(items.vector, subquery))
|
||||
.limit(5);
|
||||
|
||||
expect(query.toSQL()).toStrictEqual({
|
||||
sql:
|
||||
'select "id", "vector", "bit", "halfvec", "sparsevec" from "items" where not "items"."id" = $1 order by "items"."vector" <-> (select "vector" from "items" where "items"."id" = $2) limit $3',
|
||||
params: [1, 1, 5],
|
||||
});
|
||||
const res = await query;
|
||||
|
||||
expect(insertedValues).toStrictEqual([{
|
||||
id: 1,
|
||||
vector: [3, 1, 2],
|
||||
bit: '000',
|
||||
halfvec: [1, 2, 3],
|
||||
sparsevec: '{1:1,3:2,5:3}/5',
|
||||
}, {
|
||||
id: 2,
|
||||
vector: [3, 1, 2],
|
||||
bit: '000',
|
||||
halfvec: [1, 2, 3],
|
||||
sparsevec: '{1:1,3:2,5:3}/5',
|
||||
}]);
|
||||
|
||||
expect(res).toStrictEqual([
|
||||
{
|
||||
id: 2,
|
||||
vector: [3, 1, 2],
|
||||
bit: '000',
|
||||
halfvec: [1, 2, 3],
|
||||
sparsevec: '{1:1,3:2,5:3}/5',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test('insert + select order by jaccard distance', async () => {
|
||||
const insertedValues = await db.insert(items).values({
|
||||
vector: [1, 2, 3],
|
||||
bit: '000',
|
||||
halfvec: [1, 2, 3],
|
||||
sparsevec: '{1:1,3:2,5:3}/5',
|
||||
}).returning();
|
||||
|
||||
const bitQuery = db.select().from(items).orderBy(jaccardDistance(items.bit, '101')).limit(5);
|
||||
|
||||
expect(bitQuery.toSQL()).toStrictEqual({
|
||||
params: [
|
||||
'101',
|
||||
5,
|
||||
],
|
||||
sql: 'select "id", "vector", "bit", "halfvec", "sparsevec" from "items" order by "items"."bit" <%> $1 limit $2',
|
||||
});
|
||||
|
||||
expect(insertedValues).toStrictEqual([{
|
||||
id: 1,
|
||||
vector: [1, 2, 3],
|
||||
bit: '000',
|
||||
halfvec: [1, 2, 3],
|
||||
sparsevec: '{1:1,3:2,5:3}/5',
|
||||
}]);
|
||||
|
||||
const response = await bitQuery;
|
||||
|
||||
expect(response).toStrictEqual([{
|
||||
id: 1,
|
||||
vector: [1, 2, 3],
|
||||
bit: '000',
|
||||
halfvec: [1, 2, 3],
|
||||
sparsevec: '{1:1,3:2,5:3}/5',
|
||||
}]);
|
||||
});
|
||||
|
||||
test('insert + select order by hamming distance', async () => {
|
||||
const insertedValues = await db.insert(items).values({
|
||||
vector: [1, 2, 3],
|
||||
bit: '000',
|
||||
halfvec: [1, 2, 3],
|
||||
sparsevec: '{1:1,3:2,5:3}/5',
|
||||
}).returning();
|
||||
|
||||
const bitQuery = db.select().from(items).orderBy(hammingDistance(items.bit, '101')).limit(5);
|
||||
|
||||
expect(bitQuery.toSQL()).toStrictEqual({
|
||||
params: [
|
||||
'101',
|
||||
5,
|
||||
],
|
||||
sql: 'select "id", "vector", "bit", "halfvec", "sparsevec" from "items" order by "items"."bit" <~> $1 limit $2',
|
||||
});
|
||||
|
||||
expect(insertedValues).toStrictEqual([{
|
||||
id: 1,
|
||||
vector: [1, 2, 3],
|
||||
bit: '000',
|
||||
halfvec: [1, 2, 3],
|
||||
sparsevec: '{1:1,3:2,5:3}/5',
|
||||
}]);
|
||||
|
||||
const response = await bitQuery;
|
||||
|
||||
expect(response).toStrictEqual([{
|
||||
id: 1,
|
||||
vector: [1, 2, 3],
|
||||
bit: '000',
|
||||
halfvec: [1, 2, 3],
|
||||
sparsevec: '{1:1,3:2,5:3}/5',
|
||||
}]);
|
||||
});
|
||||
|
||||
test('insert + select order by l2 distance', async () => {
|
||||
const insertedValues = await db.insert(items).values({
|
||||
vector: [1, 2, 3],
|
||||
bit: '000',
|
||||
halfvec: [1, 2, 3],
|
||||
sparsevec: '{1:1,3:2,5:3}/5',
|
||||
}).returning();
|
||||
|
||||
const queryVector = db.select().from(items).orderBy(l2Distance(items.vector, [3, 1, 2])).limit(5);
|
||||
const queryHalfvec = db.select().from(items).orderBy(l2Distance(items.halfvec, [3, 1, 2])).limit(5);
|
||||
const querySparsevec = db.select().from(items).orderBy(l2Distance(items.sparsevec, '{1:3,3:1,5:2}/5')).limit(5);
|
||||
|
||||
expect(queryVector.toSQL()).toStrictEqual({
|
||||
params: [
|
||||
'[3,1,2]',
|
||||
5,
|
||||
],
|
||||
sql: 'select "id", "vector", "bit", "halfvec", "sparsevec" from "items" order by "items"."vector" <-> $1 limit $2',
|
||||
});
|
||||
|
||||
expect(queryHalfvec.toSQL()).toStrictEqual({
|
||||
params: [
|
||||
'[3,1,2]',
|
||||
5,
|
||||
],
|
||||
sql: 'select "id", "vector", "bit", "halfvec", "sparsevec" from "items" order by "items"."halfvec" <-> $1 limit $2',
|
||||
});
|
||||
|
||||
expect(querySparsevec.toSQL()).toStrictEqual({
|
||||
params: [
|
||||
'{1:3,3:1,5:2}/5',
|
||||
5,
|
||||
],
|
||||
sql:
|
||||
'select "id", "vector", "bit", "halfvec", "sparsevec" from "items" order by "items"."sparsevec" <-> $1 limit $2',
|
||||
});
|
||||
|
||||
const vectorRes = await queryVector;
|
||||
const halfvecRes = await queryHalfvec;
|
||||
const sparsevecRes = await querySparsevec;
|
||||
|
||||
expect(insertedValues).toStrictEqual([{
|
||||
id: 1,
|
||||
vector: [1, 2, 3],
|
||||
bit: '000',
|
||||
halfvec: [1, 2, 3],
|
||||
sparsevec: '{1:1,3:2,5:3}/5',
|
||||
}]);
|
||||
|
||||
const expectedResponse = [{
|
||||
id: 1,
|
||||
vector: [1, 2, 3],
|
||||
bit: '000',
|
||||
halfvec: [1, 2, 3],
|
||||
sparsevec: '{1:1,3:2,5:3}/5',
|
||||
}];
|
||||
|
||||
expect(vectorRes).toStrictEqual(expectedResponse);
|
||||
expect(halfvecRes).toStrictEqual(expectedResponse);
|
||||
expect(sparsevecRes).toStrictEqual(expectedResponse);
|
||||
});
|
||||
|
||||
test('select + insert all vectors', async () => {
|
||||
const insertedValues = await db.insert(items).values({
|
||||
vector: [1, 2, 3],
|
||||
bit: '000',
|
||||
halfvec: [1, 2, 3],
|
||||
sparsevec: '{1:1,3:2,5:3}/5',
|
||||
}).returning();
|
||||
|
||||
const response = await db.select().from(items);
|
||||
|
||||
expect(insertedValues).toStrictEqual([{
|
||||
id: 1,
|
||||
vector: [1, 2, 3],
|
||||
bit: '000',
|
||||
halfvec: [1, 2, 3],
|
||||
sparsevec: '{1:1,3:2,5:3}/5',
|
||||
}]);
|
||||
|
||||
expect(response).toStrictEqual([{
|
||||
id: 1,
|
||||
vector: [1, 2, 3],
|
||||
bit: '000',
|
||||
halfvec: [1, 2, 3],
|
||||
sparsevec: '{1:1,3:2,5:3}/5',
|
||||
}]);
|
||||
});
|
||||
@@ -0,0 +1,383 @@
|
||||
import Docker from 'dockerode';
|
||||
import { eq, hammingDistance, jaccardDistance, l2Distance, not, sql } from 'drizzle-orm';
|
||||
import { bigserial, bit, halfvec, pgTable, sparsevec, vector } from 'drizzle-orm/pg-core';
|
||||
import { drizzle, type PostgresJsDatabase } from 'drizzle-orm/postgres-js';
|
||||
import getPort from 'get-port';
|
||||
import postgres, { type Sql } from 'postgres';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import { afterAll, beforeAll, beforeEach, expect, test } from 'vitest';
|
||||
|
||||
const ENABLE_LOGGING = false;
|
||||
|
||||
let pgContainer: Docker.Container;
|
||||
let docker: Docker;
|
||||
let client: Sql;
|
||||
let db: PostgresJsDatabase;
|
||||
|
||||
async function createDockerDB(): Promise<string> {
|
||||
const inDocker = (docker = new Docker());
|
||||
const port = await getPort({ port: 5432 });
|
||||
const image = 'pgvector/pgvector:pg16';
|
||||
|
||||
const pullStream = await docker.pull(image);
|
||||
await new Promise((resolve, reject) =>
|
||||
inDocker.modem.followProgress(pullStream, (err) => (err ? reject(err) : resolve(err)))
|
||||
);
|
||||
|
||||
pgContainer = await docker.createContainer({
|
||||
Image: image,
|
||||
Env: ['POSTGRES_PASSWORD=postgres', 'POSTGRES_USER=postgres', 'POSTGRES_DB=postgres'],
|
||||
name: `drizzle-integration-tests-${uuid()}`,
|
||||
HostConfig: {
|
||||
AutoRemove: true,
|
||||
PortBindings: {
|
||||
'5432/tcp': [{ HostPort: `${port}` }],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await pgContainer.start();
|
||||
|
||||
return `postgres://postgres:postgres@localhost:${port}/postgres`;
|
||||
}
|
||||
|
||||
beforeAll(async () => {
|
||||
const connectionString = process.env['PG_VECTOR_CONNECTION_STRING'] ?? (await createDockerDB());
|
||||
|
||||
const sleep = 250;
|
||||
let timeLeft = 5000;
|
||||
let connected = false;
|
||||
let lastError: unknown | undefined;
|
||||
do {
|
||||
try {
|
||||
client = postgres(connectionString, {
|
||||
max: 1,
|
||||
onnotice: () => {
|
||||
// disable notices
|
||||
},
|
||||
});
|
||||
await client`select 1`;
|
||||
connected = true;
|
||||
break;
|
||||
} catch (e) {
|
||||
lastError = e;
|
||||
await new Promise((resolve) => setTimeout(resolve, sleep));
|
||||
timeLeft -= sleep;
|
||||
}
|
||||
} while (timeLeft > 0);
|
||||
if (!connected) {
|
||||
console.error('Cannot connect to Postgres');
|
||||
await client?.end().catch(console.error);
|
||||
await pgContainer?.stop().catch(console.error);
|
||||
throw lastError;
|
||||
}
|
||||
db = drizzle(client, { logger: ENABLE_LOGGING });
|
||||
|
||||
await db.execute(sql`CREATE EXTENSION IF NOT EXISTS vector;`);
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await client?.end().catch(console.error);
|
||||
await pgContainer?.stop().catch(console.error);
|
||||
});
|
||||
|
||||
const items = pgTable('items', {
|
||||
id: bigserial('id', { mode: 'number' }).primaryKey(),
|
||||
vector: vector('vector', { dimensions: 3 }),
|
||||
bit: bit('bit', { dimensions: 3 }),
|
||||
halfvec: halfvec('halfvec', { dimensions: 3 }),
|
||||
sparsevec: sparsevec('sparsevec', { dimensions: 5 }),
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await db.execute(sql`drop table if exists items cascade`);
|
||||
await db.execute(sql`
|
||||
CREATE TABLE items (
|
||||
id bigserial PRIMARY KEY,
|
||||
"vector" vector(3),
|
||||
"bit" bit(3),
|
||||
"halfvec" halfvec(3),
|
||||
"sparsevec" sparsevec(5)
|
||||
);
|
||||
`);
|
||||
});
|
||||
|
||||
test('insert + partial select', async () => {
|
||||
const insertedValues = await db.insert(items).values([{
|
||||
vector: [3, 1, 2],
|
||||
bit: '000',
|
||||
halfvec: [1, 2, 3],
|
||||
sparsevec: '{1:1,3:2,5:3}/5',
|
||||
}]).returning();
|
||||
|
||||
const query = db.select({ distance: l2Distance(items.vector, [3, 1, 2]) }).from(items);
|
||||
|
||||
expect(query.toSQL()).toStrictEqual({ sql: 'select "vector" <-> $1 from "items"', params: ['[3,1,2]'] });
|
||||
|
||||
const response = await query;
|
||||
|
||||
expect(insertedValues).toStrictEqual([{
|
||||
id: 1,
|
||||
vector: [3, 1, 2],
|
||||
bit: '000',
|
||||
halfvec: [1, 2, 3],
|
||||
sparsevec: '{1:1,3:2,5:3}/5',
|
||||
}]);
|
||||
|
||||
expect(response).toStrictEqual([{ distance: 0 }]);
|
||||
});
|
||||
|
||||
// SELECT * FROM items WHERE embedding <-> '[3,1,2]' < 5;
|
||||
test('insert + complex where', async () => {
|
||||
const insertedValues = await db.insert(items).values([{
|
||||
vector: [3, 1, 2],
|
||||
bit: '000',
|
||||
halfvec: [1, 2, 3],
|
||||
sparsevec: '{1:1,3:2,5:3}/5',
|
||||
}]).returning();
|
||||
|
||||
const query = db.select().from(items)
|
||||
.where(sql`${l2Distance(items.vector, [3, 1, 2])} < ${5}`)
|
||||
.limit(5);
|
||||
|
||||
expect(query.toSQL()).toStrictEqual({
|
||||
sql:
|
||||
'select "id", "vector", "bit", "halfvec", "sparsevec" from "items" where "items"."vector" <-> $1 < $2 limit $3',
|
||||
params: ['[3,1,2]', 5, 5],
|
||||
});
|
||||
const res = await query;
|
||||
|
||||
expect(insertedValues).toStrictEqual([{
|
||||
id: 1,
|
||||
vector: [3, 1, 2],
|
||||
bit: '000',
|
||||
halfvec: [1, 2, 3],
|
||||
sparsevec: '{1:1,3:2,5:3}/5',
|
||||
}]);
|
||||
|
||||
expect(res).toStrictEqual([
|
||||
{
|
||||
id: 1,
|
||||
vector: [3, 1, 2],
|
||||
bit: '000',
|
||||
halfvec: [1, 2, 3],
|
||||
sparsevec: '{1:1,3:2,5:3}/5',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
// SELECT * FROM items WHERE id != 1 ORDER BY embedding <-> (SELECT embedding FROM items WHERE id = 1) LIMIT 5;
|
||||
test('insert + order by subquery', async () => {
|
||||
const insertedValues = await db.insert(items).values([{
|
||||
vector: [3, 1, 2],
|
||||
bit: '000',
|
||||
halfvec: [1, 2, 3],
|
||||
sparsevec: '{1:1,3:2,5:3}/5',
|
||||
}, {
|
||||
vector: [3, 1, 2],
|
||||
bit: '000',
|
||||
halfvec: [1, 2, 3],
|
||||
sparsevec: '{1:1,3:2,5:3}/5',
|
||||
}]).returning();
|
||||
|
||||
const subquery = db.select({ vector: items.vector }).from(items).where(eq(items.id, 1));
|
||||
|
||||
const query = db.select().from(items)
|
||||
.where(not(eq(items.id, 1)))
|
||||
.orderBy(l2Distance(items.vector, subquery))
|
||||
.limit(5);
|
||||
|
||||
expect(query.toSQL()).toStrictEqual({
|
||||
sql:
|
||||
'select "id", "vector", "bit", "halfvec", "sparsevec" from "items" where not "items"."id" = $1 order by "items"."vector" <-> (select "vector" from "items" where "items"."id" = $2) limit $3',
|
||||
params: [1, 1, 5],
|
||||
});
|
||||
const res = await query;
|
||||
|
||||
expect(insertedValues).toStrictEqual([{
|
||||
id: 1,
|
||||
vector: [3, 1, 2],
|
||||
bit: '000',
|
||||
halfvec: [1, 2, 3],
|
||||
sparsevec: '{1:1,3:2,5:3}/5',
|
||||
}, {
|
||||
id: 2,
|
||||
vector: [3, 1, 2],
|
||||
bit: '000',
|
||||
halfvec: [1, 2, 3],
|
||||
sparsevec: '{1:1,3:2,5:3}/5',
|
||||
}]);
|
||||
|
||||
expect(res).toStrictEqual([
|
||||
{
|
||||
id: 2,
|
||||
vector: [3, 1, 2],
|
||||
bit: '000',
|
||||
halfvec: [1, 2, 3],
|
||||
sparsevec: '{1:1,3:2,5:3}/5',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test('insert + select order by jaccard distance', async () => {
|
||||
const insertedValues = await db.insert(items).values({
|
||||
vector: [1, 2, 3],
|
||||
bit: '000',
|
||||
halfvec: [1, 2, 3],
|
||||
sparsevec: '{1:1,3:2,5:3}/5',
|
||||
}).returning();
|
||||
|
||||
const bitQuery = db.select().from(items).orderBy(jaccardDistance(items.bit, '101')).limit(5);
|
||||
|
||||
expect(bitQuery.toSQL()).toStrictEqual({
|
||||
params: [
|
||||
'101',
|
||||
5,
|
||||
],
|
||||
sql: 'select "id", "vector", "bit", "halfvec", "sparsevec" from "items" order by "items"."bit" <%> $1 limit $2',
|
||||
});
|
||||
|
||||
expect(insertedValues).toStrictEqual([{
|
||||
id: 1,
|
||||
vector: [1, 2, 3],
|
||||
bit: '000',
|
||||
halfvec: [1, 2, 3],
|
||||
sparsevec: '{1:1,3:2,5:3}/5',
|
||||
}]);
|
||||
|
||||
const response = await bitQuery;
|
||||
|
||||
expect(response).toStrictEqual([{
|
||||
id: 1,
|
||||
vector: [1, 2, 3],
|
||||
bit: '000',
|
||||
halfvec: [1, 2, 3],
|
||||
sparsevec: '{1:1,3:2,5:3}/5',
|
||||
}]);
|
||||
});
|
||||
|
||||
test('insert + select order by hamming distance', async () => {
|
||||
const insertedValues = await db.insert(items).values({
|
||||
vector: [1, 2, 3],
|
||||
bit: '000',
|
||||
halfvec: [1, 2, 3],
|
||||
sparsevec: '{1:1,3:2,5:3}/5',
|
||||
}).returning();
|
||||
|
||||
const bitQuery = db.select().from(items).orderBy(hammingDistance(items.bit, '101')).limit(5);
|
||||
|
||||
expect(bitQuery.toSQL()).toStrictEqual({
|
||||
params: [
|
||||
'101',
|
||||
5,
|
||||
],
|
||||
sql: 'select "id", "vector", "bit", "halfvec", "sparsevec" from "items" order by "items"."bit" <~> $1 limit $2',
|
||||
});
|
||||
|
||||
expect(insertedValues).toStrictEqual([{
|
||||
id: 1,
|
||||
vector: [1, 2, 3],
|
||||
bit: '000',
|
||||
halfvec: [1, 2, 3],
|
||||
sparsevec: '{1:1,3:2,5:3}/5',
|
||||
}]);
|
||||
|
||||
const response = await bitQuery;
|
||||
|
||||
expect(response).toStrictEqual([{
|
||||
id: 1,
|
||||
vector: [1, 2, 3],
|
||||
bit: '000',
|
||||
halfvec: [1, 2, 3],
|
||||
sparsevec: '{1:1,3:2,5:3}/5',
|
||||
}]);
|
||||
});
|
||||
|
||||
test('insert + select order by l2 distance', async () => {
|
||||
const insertedValues = await db.insert(items).values({
|
||||
vector: [1, 2, 3],
|
||||
bit: '000',
|
||||
halfvec: [1, 2, 3],
|
||||
sparsevec: '{1:1,3:2,5:3}/5',
|
||||
}).returning();
|
||||
|
||||
const queryVector = db.select().from(items).orderBy(l2Distance(items.vector, [3, 1, 2])).limit(5);
|
||||
const queryHalfvec = db.select().from(items).orderBy(l2Distance(items.halfvec, [3, 1, 2])).limit(5);
|
||||
const querySparsevec = db.select().from(items).orderBy(l2Distance(items.sparsevec, '{1:3,3:1,5:2}/5')).limit(5);
|
||||
|
||||
expect(queryVector.toSQL()).toStrictEqual({
|
||||
params: [
|
||||
'[3,1,2]',
|
||||
5,
|
||||
],
|
||||
sql: 'select "id", "vector", "bit", "halfvec", "sparsevec" from "items" order by "items"."vector" <-> $1 limit $2',
|
||||
});
|
||||
|
||||
expect(queryHalfvec.toSQL()).toStrictEqual({
|
||||
params: [
|
||||
'[3,1,2]',
|
||||
5,
|
||||
],
|
||||
sql: 'select "id", "vector", "bit", "halfvec", "sparsevec" from "items" order by "items"."halfvec" <-> $1 limit $2',
|
||||
});
|
||||
|
||||
expect(querySparsevec.toSQL()).toStrictEqual({
|
||||
params: [
|
||||
'{1:3,3:1,5:2}/5',
|
||||
5,
|
||||
],
|
||||
sql:
|
||||
'select "id", "vector", "bit", "halfvec", "sparsevec" from "items" order by "items"."sparsevec" <-> $1 limit $2',
|
||||
});
|
||||
|
||||
const vectorRes = await queryVector;
|
||||
const halfvecRes = await queryHalfvec;
|
||||
const sparsevecRes = await querySparsevec;
|
||||
|
||||
expect(insertedValues).toStrictEqual([{
|
||||
id: 1,
|
||||
vector: [1, 2, 3],
|
||||
bit: '000',
|
||||
halfvec: [1, 2, 3],
|
||||
sparsevec: '{1:1,3:2,5:3}/5',
|
||||
}]);
|
||||
|
||||
const expectedResponse = [{
|
||||
id: 1,
|
||||
vector: [1, 2, 3],
|
||||
bit: '000',
|
||||
halfvec: [1, 2, 3],
|
||||
sparsevec: '{1:1,3:2,5:3}/5',
|
||||
}];
|
||||
|
||||
expect(vectorRes).toStrictEqual(expectedResponse);
|
||||
expect(halfvecRes).toStrictEqual(expectedResponse);
|
||||
expect(sparsevecRes).toStrictEqual(expectedResponse);
|
||||
});
|
||||
|
||||
test('select + insert all vectors', async () => {
|
||||
const insertedValues = await db.insert(items).values({
|
||||
vector: [1, 2, 3],
|
||||
bit: '000',
|
||||
halfvec: [1, 2, 3],
|
||||
sparsevec: '{1:1,3:2,5:3}/5',
|
||||
}).returning();
|
||||
|
||||
const response = await db.select().from(items);
|
||||
|
||||
expect(insertedValues).toStrictEqual([{
|
||||
id: 1,
|
||||
vector: [1, 2, 3],
|
||||
bit: '000',
|
||||
halfvec: [1, 2, 3],
|
||||
sparsevec: '{1:1,3:2,5:3}/5',
|
||||
}]);
|
||||
|
||||
expect(response).toStrictEqual([{
|
||||
id: 1,
|
||||
vector: [1, 2, 3],
|
||||
bit: '000',
|
||||
halfvec: [1, 2, 3],
|
||||
sparsevec: '{1:1,3:2,5:3}/5',
|
||||
}]);
|
||||
});
|
||||
@@ -0,0 +1,73 @@
|
||||
import { getTableName, is, Table } from 'drizzle-orm';
|
||||
import type { MutationOption } from 'drizzle-orm/cache/core';
|
||||
import { Cache } from 'drizzle-orm/cache/core';
|
||||
import type { CacheConfig } from 'drizzle-orm/cache/core/types';
|
||||
import Keyv from 'keyv';
|
||||
|
||||
// eslint-disable-next-line drizzle-internal/require-entity-kind
|
||||
export class TestGlobalCache extends Cache {
|
||||
private globalTtl: number = 1000;
|
||||
private usedTablesPerKey: Record<string, string[]> = {};
|
||||
|
||||
constructor(private kv: Keyv = new Keyv()) {
|
||||
super();
|
||||
}
|
||||
|
||||
override strategy(): 'explicit' | 'all' {
|
||||
return 'all';
|
||||
}
|
||||
override async get(key: string, _tables: string[], _isTag: boolean): Promise<any[] | undefined> {
|
||||
const res = await this.kv.get(key) ?? undefined;
|
||||
return res;
|
||||
}
|
||||
override async put(
|
||||
key: string,
|
||||
response: any,
|
||||
tables: string[],
|
||||
isTag: boolean,
|
||||
config?: CacheConfig,
|
||||
): Promise<void> {
|
||||
await this.kv.set(key, response, config ? config.ex : this.globalTtl);
|
||||
for (const table of tables) {
|
||||
const keys = this.usedTablesPerKey[table];
|
||||
if (keys === undefined) {
|
||||
this.usedTablesPerKey[table] = [key];
|
||||
} else {
|
||||
keys.push(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
override async onMutate(params: MutationOption): Promise<void> {
|
||||
const tagsArray = params.tags ? Array.isArray(params.tags) ? params.tags : [params.tags] : [];
|
||||
const tablesArray = params.tables ? Array.isArray(params.tables) ? params.tables : [params.tables] : [];
|
||||
|
||||
const keysToDelete = new Set<string>();
|
||||
|
||||
for (const table of tablesArray) {
|
||||
const tableName = is(table, Table) ? getTableName(table) : table as string;
|
||||
const keys = this.usedTablesPerKey[tableName] ?? [];
|
||||
for (const key of keys) keysToDelete.add(key);
|
||||
}
|
||||
|
||||
if (keysToDelete.size > 0 || tagsArray.length > 0) {
|
||||
for (const tag of tagsArray) {
|
||||
await this.kv.delete(tag);
|
||||
}
|
||||
|
||||
for (const key of keysToDelete) {
|
||||
await this.kv.delete(key);
|
||||
for (const table of tablesArray) {
|
||||
const tableName = is(table, Table) ? getTableName(table) : table as string;
|
||||
this.usedTablesPerKey[tableName] = [];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line drizzle-internal/require-entity-kind
|
||||
export class TestCache extends TestGlobalCache {
|
||||
override strategy(): 'explicit' | 'all' {
|
||||
return 'explicit';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import Docker from 'dockerode';
|
||||
import getPort from 'get-port';
|
||||
import { v4 as uuidV4 } from 'uuid';
|
||||
import 'zx/globals';
|
||||
|
||||
export async function createDockerDB(): Promise<{ connectionString: string; container: Docker.Container }> {
|
||||
const docker = new Docker();
|
||||
const port = await getPort({ port: 5656 });
|
||||
const image = 'geldata/gel:6';
|
||||
|
||||
const pullStream = await docker.pull(image);
|
||||
await new Promise((resolve, reject) =>
|
||||
docker.modem.followProgress(pullStream, (err) => (err ? reject(err) : resolve(err)))
|
||||
);
|
||||
|
||||
const gelContainer = await docker.createContainer({
|
||||
Image: image,
|
||||
Env: [
|
||||
'GEL_CLIENT_SECURITY=insecure_dev_mode',
|
||||
'GEL_SERVER_SECURITY=insecure_dev_mode',
|
||||
'GEL_CLIENT_TLS_SECURITY=no_host_verification',
|
||||
'GEL_SERVER_PASSWORD=password',
|
||||
],
|
||||
name: `drizzle-integration-tests-${uuidV4()}`,
|
||||
HostConfig: {
|
||||
AutoRemove: true,
|
||||
PortBindings: {
|
||||
'5656/tcp': [{ HostPort: `${port}` }],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await gelContainer.start();
|
||||
|
||||
return { connectionString: `gel://admin:password@localhost:${port}/main`, container: gelContainer };
|
||||
}
|
||||
@@ -0,0 +1,679 @@
|
||||
import retry from 'async-retry';
|
||||
import type Docker from 'dockerode';
|
||||
import { asc, eq, sql } from 'drizzle-orm';
|
||||
import { drizzle, type GelJsDatabase } from 'drizzle-orm/gel';
|
||||
import { alias, customType, gelTable, gelTableCreator } from 'drizzle-orm/gel-core';
|
||||
import * as gel from 'gel';
|
||||
import { afterAll, afterEach, beforeAll, beforeEach, expect, test } from 'vitest';
|
||||
import { createDockerDB } from './createInstance';
|
||||
import 'zx/globals';
|
||||
|
||||
$.quiet = true;
|
||||
|
||||
const ENABLE_LOGGING = false;
|
||||
|
||||
let db: GelJsDatabase;
|
||||
let client: gel.Client;
|
||||
let container: Docker.Container | undefined;
|
||||
|
||||
let dsn: string;
|
||||
const tlsSecurity = '--tls-security=insecure';
|
||||
|
||||
function sleep(ms: number) {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
beforeAll(async () => {
|
||||
let connectionString;
|
||||
if (process.env['GEL_CONNECTION_STRING']) {
|
||||
connectionString = process.env['GEL_CONNECTION_STRING'];
|
||||
} else {
|
||||
const { connectionString: conStr, container: contrainerObj } = await createDockerDB();
|
||||
connectionString = conStr;
|
||||
container = contrainerObj;
|
||||
}
|
||||
await sleep(15 * 1000);
|
||||
client = await retry(async () => {
|
||||
client = gel.createClient({ dsn: connectionString, tlsSecurity: 'insecure' });
|
||||
return client;
|
||||
}, {
|
||||
retries: 20,
|
||||
factor: 1,
|
||||
minTimeout: 250,
|
||||
maxTimeout: 250,
|
||||
randomize: false,
|
||||
onRetry() {
|
||||
client?.close();
|
||||
},
|
||||
});
|
||||
db = drizzle(client, { logger: ENABLE_LOGGING });
|
||||
|
||||
dsn = connectionString;
|
||||
await $`gel query "CREATE TYPE default::users_custom {
|
||||
create property id1: int16 {
|
||||
create constraint exclusive;
|
||||
};
|
||||
create required property name: str;
|
||||
create property verified: bool {
|
||||
SET default := false;
|
||||
};
|
||||
create property json: json;
|
||||
};" ${tlsSecurity} --dsn=${dsn}`;
|
||||
|
||||
await $`gel query "CREATE TYPE default::prefixed_users_custom {
|
||||
create property id1: int16 {
|
||||
create constraint exclusive;
|
||||
};
|
||||
create required property name: str;
|
||||
};" ${tlsSecurity} --dsn=${dsn}`;
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await $`gel query "DROP TYPE default::users_custom;" ${tlsSecurity} --dsn=${dsn}`;
|
||||
await $`gel query "DROP TYPE default::prefixed_users_custom;" ${tlsSecurity} --dsn=${dsn}`;
|
||||
|
||||
await client?.close();
|
||||
await container?.stop().catch(console.error);
|
||||
});
|
||||
|
||||
beforeEach((ctx) => {
|
||||
ctx.gel = {
|
||||
db,
|
||||
};
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await $`gel query "DELETE default::users_custom;" ${tlsSecurity} --dsn=${dsn}`;
|
||||
await $`gel query "DELETE default::prefixed_users_custom;" ${tlsSecurity} --dsn=${dsn}`;
|
||||
});
|
||||
|
||||
const customInteger = customType<{ data: number; notNull: false; default: false }>({
|
||||
dataType() {
|
||||
return 'integer';
|
||||
},
|
||||
});
|
||||
|
||||
const customText = customType<{ data: string }>({
|
||||
dataType() {
|
||||
return 'text';
|
||||
},
|
||||
});
|
||||
|
||||
const customBoolean = customType<{ data: boolean }>({
|
||||
dataType() {
|
||||
return 'boolean';
|
||||
},
|
||||
});
|
||||
|
||||
const customJson = <TData>(name: string) =>
|
||||
customType<{ data: TData; driverData: string }>({
|
||||
dataType() {
|
||||
return 'json';
|
||||
},
|
||||
})(name);
|
||||
|
||||
const usersTable = gelTable('users_custom', {
|
||||
id1: customInteger('id1'),
|
||||
name: customText('name').notNull(),
|
||||
verified: customBoolean('verified').notNull().default(false),
|
||||
json: customJson<string[]>('json'),
|
||||
});
|
||||
|
||||
test('select all fields', async (ctx) => {
|
||||
const { db } = ctx.gel;
|
||||
|
||||
await db.insert(usersTable).values({ id1: 1, name: 'John' });
|
||||
const result = await db.select().from(usersTable);
|
||||
|
||||
expect(result).toEqual([{ id1: 1, name: 'John', verified: false, json: null }]);
|
||||
});
|
||||
|
||||
test('select sql', async (ctx) => {
|
||||
const { db } = ctx.gel;
|
||||
|
||||
await db.insert(usersTable).values({ id1: 1, name: 'John' });
|
||||
const users = await db.select({
|
||||
name: sql`upper(${usersTable.name})`,
|
||||
}).from(usersTable);
|
||||
|
||||
expect(users).toEqual([{ name: 'JOHN' }]);
|
||||
});
|
||||
|
||||
test('select typed sql', async (ctx) => {
|
||||
const { db } = ctx.gel;
|
||||
|
||||
await db.insert(usersTable).values({ id1: 1, name: 'John' });
|
||||
const users = await db.select({
|
||||
name: sql<string>`upper(${usersTable.name})`,
|
||||
}).from(usersTable);
|
||||
|
||||
expect(users).toEqual([{ name: 'JOHN' }]);
|
||||
});
|
||||
|
||||
test('insert returning sql', async (ctx) => {
|
||||
const { db } = ctx.gel;
|
||||
|
||||
const users = await db.insert(usersTable).values({ id1: 1, name: 'John' }).returning({
|
||||
name: sql`upper(${usersTable.name})`,
|
||||
});
|
||||
|
||||
expect(users).toEqual([{ name: 'JOHN' }]);
|
||||
});
|
||||
|
||||
test('delete returning sql', async (ctx) => {
|
||||
const { db } = ctx.gel;
|
||||
|
||||
await db.insert(usersTable).values({ id1: 1, name: 'John' });
|
||||
const users = await db.delete(usersTable).where(eq(usersTable.name, 'John')).returning({
|
||||
name: sql`upper(${usersTable.name})`,
|
||||
});
|
||||
|
||||
expect(users).toEqual([{ name: 'JOHN' }]);
|
||||
});
|
||||
|
||||
test('update returning sql', async (ctx) => {
|
||||
const { db } = ctx.gel;
|
||||
|
||||
await db.insert(usersTable).values({ id1: 1, name: 'John' });
|
||||
const users = await db.update(usersTable).set({ name: 'Jane' }).where(eq(usersTable.name, 'John')).returning({
|
||||
name: sql`upper(${usersTable.name})`,
|
||||
});
|
||||
|
||||
expect(users).toEqual([{ name: 'JANE' }]);
|
||||
});
|
||||
|
||||
test('update with returning all fields', async (ctx) => {
|
||||
const { db } = ctx.gel;
|
||||
|
||||
await db.insert(usersTable).values({ id1: 1, name: 'John' });
|
||||
const users = await db.update(usersTable).set({ name: 'Jane' }).where(eq(usersTable.name, 'John')).returning();
|
||||
|
||||
expect(users).toEqual([{ id1: 1, name: 'Jane', verified: false, json: null }]);
|
||||
});
|
||||
|
||||
test('update with returning partial', async (ctx) => {
|
||||
const { db } = ctx.gel;
|
||||
|
||||
await db.insert(usersTable).values({ id1: 1, name: 'John' });
|
||||
const users = await db.update(usersTable).set({ name: 'Jane' }).where(eq(usersTable.name, 'John')).returning({
|
||||
id1: usersTable.id1,
|
||||
name: usersTable.name,
|
||||
});
|
||||
|
||||
expect(users).toEqual([{ id1: 1, name: 'Jane' }]);
|
||||
});
|
||||
|
||||
test('delete with returning all fields', async (ctx) => {
|
||||
const { db } = ctx.gel;
|
||||
|
||||
await db.insert(usersTable).values({ id1: 1, name: 'John' });
|
||||
const users = await db.delete(usersTable).where(eq(usersTable.name, 'John')).returning();
|
||||
|
||||
expect(users).toEqual([{ id1: 1, name: 'John', verified: false, json: null }]);
|
||||
});
|
||||
|
||||
test('delete with returning partial', async (ctx) => {
|
||||
const { db } = ctx.gel;
|
||||
|
||||
await db.insert(usersTable).values({ id1: 1, name: 'John' });
|
||||
const users = await db.delete(usersTable).where(eq(usersTable.name, 'John')).returning({
|
||||
id1: usersTable.id1,
|
||||
name: usersTable.name,
|
||||
});
|
||||
|
||||
expect(users).toEqual([{ id1: 1, name: 'John' }]);
|
||||
});
|
||||
|
||||
test('insert + select', async (ctx) => {
|
||||
const { db } = ctx.gel;
|
||||
|
||||
await db.insert(usersTable).values({ id1: 1, name: 'John' });
|
||||
const result = await db.select().from(usersTable);
|
||||
expect(result).toEqual([{ id1: 1, name: 'John', verified: false, json: null }]);
|
||||
|
||||
await db.insert(usersTable).values({ id1: 2, name: 'Jane' });
|
||||
const result2 = await db.select().from(usersTable);
|
||||
expect(result2).toEqual([
|
||||
{ id1: 1, name: 'John', verified: false, json: null },
|
||||
{ id1: 2, name: 'Jane', verified: false, json: null },
|
||||
]);
|
||||
});
|
||||
|
||||
test('insert with overridden default values', async (ctx) => {
|
||||
const { db } = ctx.gel;
|
||||
|
||||
await db.insert(usersTable).values({ id1: 1, name: 'John', verified: true });
|
||||
const result = await db.select().from(usersTable);
|
||||
|
||||
expect(result).toEqual([{ id1: 1, name: 'John', verified: true, json: null }]);
|
||||
});
|
||||
|
||||
test('insert many', async (ctx) => {
|
||||
const { db } = ctx.gel;
|
||||
|
||||
await db.insert(usersTable).values([
|
||||
{ id1: 1, name: 'John' },
|
||||
{ id1: 2, name: 'Bruce', json: ['foo', 'bar'] },
|
||||
{ id1: 3, name: 'Jane' },
|
||||
{ id1: 4, name: 'Austin', verified: true },
|
||||
]);
|
||||
const result = await db.select({
|
||||
id: usersTable.id1,
|
||||
name: usersTable.name,
|
||||
json: usersTable.json,
|
||||
verified: usersTable.verified,
|
||||
}).from(usersTable);
|
||||
|
||||
expect(result).toEqual([
|
||||
{ id: 1, name: 'John', json: null, verified: false },
|
||||
{ id: 2, name: 'Bruce', json: ['foo', 'bar'], verified: false },
|
||||
{ id: 3, name: 'Jane', json: null, verified: false },
|
||||
{ id: 4, name: 'Austin', json: null, verified: true },
|
||||
]);
|
||||
});
|
||||
|
||||
test('insert many with returning', async (ctx) => {
|
||||
const { db } = ctx.gel;
|
||||
|
||||
const result = await db.insert(usersTable).values([
|
||||
{ id1: 1, name: 'John' },
|
||||
{ id1: 2, name: 'Bruce', json: ['foo', 'bar'] },
|
||||
{ id1: 3, name: 'Jane' },
|
||||
{ id1: 4, name: 'Austin', verified: true },
|
||||
])
|
||||
.returning({
|
||||
id: usersTable.id1,
|
||||
name: usersTable.name,
|
||||
json: usersTable.json,
|
||||
verified: usersTable.verified,
|
||||
});
|
||||
|
||||
expect(result).toEqual([
|
||||
{ id: 1, name: 'John', json: null, verified: false },
|
||||
{ id: 2, name: 'Bruce', json: ['foo', 'bar'], verified: false },
|
||||
{ id: 3, name: 'Jane', json: null, verified: false },
|
||||
{ id: 4, name: 'Austin', json: null, verified: true },
|
||||
]);
|
||||
});
|
||||
|
||||
test('select with group by as field', async (ctx) => {
|
||||
const { db } = ctx.gel;
|
||||
|
||||
await db.insert(usersTable).values([{ id1: 1, name: 'John' }, { id1: 2, name: 'Jane' }, { id1: 3, name: 'Jane' }]);
|
||||
|
||||
const result = await db.select({ name: usersTable.name }).from(usersTable)
|
||||
.groupBy(usersTable.name);
|
||||
|
||||
expect(result).toEqual([{ name: 'Jane' }, { name: 'John' }]);
|
||||
});
|
||||
|
||||
test('select with group by as sql', async (ctx) => {
|
||||
const { db } = ctx.gel;
|
||||
|
||||
await db.insert(usersTable).values([{ id1: 1, name: 'John' }, { id1: 2, name: 'Jane' }, { id1: 3, name: 'Jane' }]);
|
||||
|
||||
const result = await db.select({ name: usersTable.name }).from(usersTable)
|
||||
.groupBy(sql`${usersTable.name}`);
|
||||
|
||||
expect(result).toEqual([{ name: 'Jane' }, { name: 'John' }]);
|
||||
});
|
||||
|
||||
test('select with group by as sql + column', async (ctx) => {
|
||||
const { db } = ctx.gel;
|
||||
|
||||
await db.insert(usersTable).values([{ id1: 1, name: 'John' }, { id1: 2, name: 'Jane' }, { id1: 3, name: 'Jane' }]);
|
||||
|
||||
const result = await db.select({ name: usersTable.name }).from(usersTable)
|
||||
.groupBy(sql`${usersTable.name}`, usersTable.id1);
|
||||
|
||||
expect(result).toEqual([{ name: 'Jane' }, { name: 'John' }, { name: 'Jane' }]);
|
||||
});
|
||||
|
||||
test('select with group by as column + sql', async (ctx) => {
|
||||
const { db } = ctx.gel;
|
||||
|
||||
await db.insert(usersTable).values([{ id1: 1, name: 'John' }, { id1: 2, name: 'Jane' }, { id1: 3, name: 'Jane' }]);
|
||||
|
||||
const result = await db.select({ name: usersTable.name }).from(usersTable)
|
||||
.groupBy(usersTable.id1, sql`${usersTable.name}`);
|
||||
|
||||
expect(result).toEqual([{ name: 'John' }, { name: 'Jane' }, { name: 'Jane' }]);
|
||||
});
|
||||
|
||||
test('select with group by complex query', async (ctx) => {
|
||||
const { db } = ctx.gel;
|
||||
|
||||
await db.insert(usersTable).values([{ id1: 1, name: 'John' }, { id1: 2, name: 'Jane' }, { id1: 3, name: 'Jane' }]);
|
||||
|
||||
const result = await db.select({ name: usersTable.name }).from(usersTable)
|
||||
.groupBy(usersTable.id1, sql`${usersTable.name}`)
|
||||
.orderBy(asc(usersTable.name))
|
||||
.limit(1);
|
||||
|
||||
expect(result).toEqual([{ name: 'Jane' }]);
|
||||
});
|
||||
|
||||
test('build query', async (ctx) => {
|
||||
const { db } = ctx.gel;
|
||||
|
||||
const query = db.select({ id: usersTable.id1, name: usersTable.name }).from(usersTable)
|
||||
.groupBy(usersTable.id1, usersTable.name)
|
||||
.toSQL();
|
||||
|
||||
expect(query).toEqual({
|
||||
sql:
|
||||
'select "users_custom"."id1", "users_custom"."name" from "users_custom" group by "users_custom"."id1", "users_custom"."name"',
|
||||
params: [],
|
||||
});
|
||||
});
|
||||
|
||||
test('insert sql', async (ctx) => {
|
||||
const { db } = ctx.gel;
|
||||
|
||||
await db.insert(usersTable).values({ id1: 1, name: sql`${'John'}` });
|
||||
const result = await db.select({ id: usersTable.id1, name: usersTable.name }).from(usersTable);
|
||||
expect(result).toEqual([{ id: 1, name: 'John' }]);
|
||||
});
|
||||
|
||||
test('partial join with alias', async (ctx) => {
|
||||
const { db } = ctx.gel;
|
||||
const customerAlias = alias(usersTable, 'customer');
|
||||
|
||||
await db.insert(usersTable).values([{ id1: 10, name: 'Ivan' }, { id1: 11, name: 'Hans' }]);
|
||||
const result = await db
|
||||
.select({
|
||||
user: {
|
||||
id: usersTable.id1,
|
||||
name: usersTable.name,
|
||||
},
|
||||
customer: {
|
||||
id: customerAlias.id1,
|
||||
name: customerAlias.name,
|
||||
},
|
||||
}).from(usersTable)
|
||||
.leftJoin(customerAlias, eq(customerAlias.id1, 11))
|
||||
.where(eq(usersTable.id1, 10));
|
||||
|
||||
expect(result).toEqual([{
|
||||
user: { id: 10, name: 'Ivan' },
|
||||
customer: { id: 11, name: 'Hans' },
|
||||
}]);
|
||||
});
|
||||
|
||||
test('full join with alias', async (ctx) => {
|
||||
const { db } = ctx.gel;
|
||||
|
||||
const gelTable = gelTableCreator((name) => `prefixed_${name}`);
|
||||
|
||||
const users = gelTable('users_custom', {
|
||||
id1: customInteger('id1'),
|
||||
name: customText('name').notNull(),
|
||||
});
|
||||
|
||||
const customers = alias(users, 'customer');
|
||||
|
||||
await db.insert(users).values([{ id1: 10, name: 'Ivan' }, { id1: 11, name: 'Hans' }]);
|
||||
const result = await db
|
||||
.select().from(users)
|
||||
.leftJoin(customers, eq(customers.id1, 11))
|
||||
.where(eq(users.id1, 10));
|
||||
|
||||
expect(result).toEqual([{
|
||||
users_custom: {
|
||||
id1: 10,
|
||||
name: 'Ivan',
|
||||
},
|
||||
customer: {
|
||||
id1: 11,
|
||||
name: 'Hans',
|
||||
},
|
||||
}]);
|
||||
});
|
||||
|
||||
test('insert with spaces', async (ctx) => {
|
||||
const { db } = ctx.gel;
|
||||
|
||||
await db.insert(usersTable).values({ id1: 1, name: sql`'Jo h n'` });
|
||||
const result = await db.select({ id: usersTable.id1, name: usersTable.name }).from(usersTable);
|
||||
|
||||
expect(result).toEqual([{ id: 1, name: 'Jo h n' }]);
|
||||
});
|
||||
|
||||
test('prepared statement', async (ctx) => {
|
||||
const { db } = ctx.gel;
|
||||
|
||||
await db.insert(usersTable).values({ id1: 1, name: 'John' });
|
||||
const statement = db.select({
|
||||
id: usersTable.id1,
|
||||
name: usersTable.name,
|
||||
}).from(usersTable)
|
||||
.prepare('statement1');
|
||||
const result = await statement.execute();
|
||||
|
||||
expect(result).toEqual([{ id: 1, name: 'John' }]);
|
||||
});
|
||||
|
||||
test('prepared statement reuse', async (ctx) => {
|
||||
const { db } = ctx.gel;
|
||||
|
||||
const stmt = db.insert(usersTable).values({
|
||||
id1: sql.placeholder('id1'),
|
||||
verified: true,
|
||||
name: sql.placeholder('name'),
|
||||
}).prepare('stmt2');
|
||||
|
||||
for (let i = 1; i < 11; i++) {
|
||||
await stmt.execute({ id1: i, name: `John ${i}` });
|
||||
}
|
||||
|
||||
const result = await db.select({
|
||||
id1: usersTable.id1,
|
||||
name: usersTable.name,
|
||||
verified: usersTable.verified,
|
||||
}).from(usersTable);
|
||||
|
||||
expect(result).toEqual([
|
||||
{ id1: 1, name: 'John 1', verified: true },
|
||||
{ id1: 2, name: 'John 2', verified: true },
|
||||
{ id1: 3, name: 'John 3', verified: true },
|
||||
{ id1: 4, name: 'John 4', verified: true },
|
||||
{ id1: 5, name: 'John 5', verified: true },
|
||||
{ id1: 6, name: 'John 6', verified: true },
|
||||
{ id1: 7, name: 'John 7', verified: true },
|
||||
{ id1: 8, name: 'John 8', verified: true },
|
||||
{ id1: 9, name: 'John 9', verified: true },
|
||||
{ id1: 10, name: 'John 10', verified: true },
|
||||
]);
|
||||
});
|
||||
|
||||
test('prepared statement with placeholder in .where', async (ctx) => {
|
||||
const { db } = ctx.gel;
|
||||
|
||||
await db.insert(usersTable).values({ id1: 1, name: 'John' });
|
||||
const stmt = db.select({
|
||||
id: usersTable.id1,
|
||||
name: usersTable.name,
|
||||
}).from(usersTable)
|
||||
.where(eq(usersTable.id1, sql.placeholder('id')))
|
||||
.prepare('stmt3');
|
||||
const result = await stmt.execute({ id: 1 });
|
||||
|
||||
expect(result).toEqual([{ id: 1, name: 'John' }]);
|
||||
});
|
||||
|
||||
test('prepared statement with placeholder in .limit', async (ctx) => {
|
||||
const { db } = ctx.gel;
|
||||
|
||||
await db.insert(usersTable).values({ id1: 1, name: 'John' });
|
||||
const stmt = db
|
||||
.select({
|
||||
id: usersTable.id1,
|
||||
name: usersTable.name,
|
||||
})
|
||||
.from(usersTable)
|
||||
.where(eq(usersTable.id1, sql.placeholder('id')))
|
||||
.limit(sql.placeholder('limit'))
|
||||
.prepare('stmt_limit');
|
||||
|
||||
const result = await stmt.execute({ id: 1, limit: 1 });
|
||||
|
||||
expect(result).toEqual([{ id: 1, name: 'John' }]);
|
||||
expect(result).toHaveLength(1);
|
||||
});
|
||||
|
||||
test('prepared statement with placeholder in .offset', async (ctx) => {
|
||||
const { db } = ctx.gel;
|
||||
|
||||
await db.insert(usersTable).values([{ id1: 1, name: 'John' }, { id1: 2, name: 'John1' }]);
|
||||
const stmt = db
|
||||
.select({
|
||||
id: usersTable.id1,
|
||||
name: usersTable.name,
|
||||
})
|
||||
.from(usersTable)
|
||||
.offset(sql.placeholder('offset'))
|
||||
.prepare('stmt_offset');
|
||||
|
||||
const result = await stmt.execute({ offset: 1 });
|
||||
|
||||
expect(result).toEqual([{ id: 2, name: 'John1' }]);
|
||||
});
|
||||
|
||||
test('insert via db.execute + select via db.execute', async () => {
|
||||
await db.execute(sql`insert into ${usersTable} (${sql.identifier(usersTable.name.name)}) values (${'John'})`);
|
||||
|
||||
const result = await db.execute<{ id1: number; name: string }>(sql`select id1, name from "users_custom"`);
|
||||
expect(result).toEqual([{ id1: null, name: 'John' }]);
|
||||
});
|
||||
|
||||
test('insert via db.execute + returning', async () => {
|
||||
const inserted = await db.execute<{ id: number; name: string }>(
|
||||
sql`insert into ${usersTable} (${
|
||||
sql.identifier(usersTable.name.name)
|
||||
}) values (${'John'}) returning ${usersTable.id1}, ${usersTable.name}`,
|
||||
);
|
||||
expect(inserted).toEqual([{ id1: null, name: 'John' }]);
|
||||
});
|
||||
|
||||
test('insert via db.execute w/ query builder', async () => {
|
||||
const inserted = await db.execute<Pick<typeof usersTable.$inferSelect, 'id1' | 'name'>>(
|
||||
db.insert(usersTable).values({ id1: 1, name: 'John' }).returning({ id: usersTable.id1, name: usersTable.name }),
|
||||
);
|
||||
expect(inserted).toEqual([{ id1: 1, name: 'John' }]);
|
||||
});
|
||||
|
||||
// TODO on conflict does not work
|
||||
// test.todo('build query insert with onConflict do update', async (ctx) => {
|
||||
// const { db } = ctx.gel;
|
||||
|
||||
// const query = db.insert(usersTable)
|
||||
// .values({ id1: 1, name: 'John', jsonb: ['foo', 'bar'] })
|
||||
// .onConflictDoUpdate({ target: usersTable.id1, set: { name: 'John1' } })
|
||||
// .toSQL();
|
||||
|
||||
// expect(query).toEqual({
|
||||
// sql:
|
||||
// 'insert into "users" ("id", "name", "verified", "jsonb", "created_at") values (default, $1, default, $2, default) on conflict ("id") do update set "name" = $3',
|
||||
// params: ['John', '["foo","bar"]', 'John1'],
|
||||
// });
|
||||
// });
|
||||
|
||||
// // TODO on conflict does not work
|
||||
// test.todo('build query insert with onConflict do update / multiple columns', async (ctx) => {
|
||||
// const { db } = ctx.gel;
|
||||
|
||||
// const query = db.insert(usersTable)
|
||||
// .values({ id1: 1, name: 'John', jsonb: ['foo', 'bar'] })
|
||||
// .onConflictDoUpdate({ target: [usersTable.id1, usersTable.name], set: { name: 'John1' } })
|
||||
// .toSQL();
|
||||
|
||||
// expect(query).toEqual({
|
||||
// sql:
|
||||
// 'insert into "users" ("id", "name", "verified", "jsonb", "created_at") values (default, $1, default, $2, default) on conflict ("id","name") do update set "name" = $3',
|
||||
// params: ['John', '["foo","bar"]', 'John1'],
|
||||
// });
|
||||
// });
|
||||
|
||||
// // TODO on conflict does not work
|
||||
// test.todo('build query insert with onConflict do nothing', async (ctx) => {
|
||||
// const { db } = ctx.gel;
|
||||
|
||||
// const query = db.insert(usersTable)
|
||||
// .values({ id1: 1, name: 'John' })
|
||||
// .onConflictDoNothing()
|
||||
// .toSQL();
|
||||
|
||||
// expect(query).toEqual({
|
||||
// sql: 'insert into "users" ("id1", "name", "verified") values ($1, $2, default) on conflict do nothing',
|
||||
// params: [1, 'John'],
|
||||
// });
|
||||
// });
|
||||
|
||||
// // TODO on conflict does not work
|
||||
// test.todo('build query insert with onConflict do nothing + target', async (ctx) => {
|
||||
// const { db } = ctx.gel;
|
||||
|
||||
// const query = db.insert(usersTable)
|
||||
// .values({ id1: 1, name: 'John' })
|
||||
// .onConflictDoNothing({ target: usersTable.id1 })
|
||||
// .toSQL();
|
||||
|
||||
// expect(query).toEqual({
|
||||
// sql: 'insert into "users" ("id1", "name", "verified") values ($1, $2, default) on conflict ("id1") do nothing',
|
||||
// params: [1, 'John'],
|
||||
// });
|
||||
// });
|
||||
|
||||
// // TODO on conflict does not work
|
||||
// test.todo('insert with onConflict do update', async (ctx) => {
|
||||
// const { db } = ctx.gel;
|
||||
|
||||
// await db.insert(usersTable)
|
||||
// .values({ id1: 1, name: 'John' });
|
||||
|
||||
// await db.insert(usersTable)
|
||||
// .values({ id1: 1, name: 'John' })
|
||||
// .onConflictDoUpdate({ target: usersTable.id1, set: { name: 'John1' } });
|
||||
|
||||
// const res = await db.select({ id: usersTable.id1, name: usersTable.name }).from(usersTable).where(
|
||||
// eq(usersTable.id1, 1),
|
||||
// );
|
||||
|
||||
// expect(res).toEqual([{ id: 1, name: 'John1' }]);
|
||||
// });
|
||||
|
||||
// // TODO on conflict does not work
|
||||
// test.todo('insert with onConflict do nothing', async (ctx) => {
|
||||
// const { db } = ctx.gel;
|
||||
|
||||
// await db.insert(usersTable)
|
||||
// .values({ id1: 1, name: 'John' });
|
||||
|
||||
// await db.insert(usersTable)
|
||||
// .values({ id1: 1, name: 'John' })
|
||||
// .onConflictDoNothing();
|
||||
|
||||
// const res = await db.select({ id: usersTable.id1, name: usersTable.name }).from(usersTable).where(
|
||||
// eq(usersTable.id1, 1),
|
||||
// );
|
||||
|
||||
// expect(res).toEqual([{ id: 1, name: 'John' }]);
|
||||
// });
|
||||
|
||||
// // TODO on conflict does not work
|
||||
// test.todo('insert with onConflict do nothing + target', async (ctx) => {
|
||||
// const { db } = ctx.gel;
|
||||
|
||||
// await db.insert(usersTable)
|
||||
// .values({ id1: 1, name: 'John' });
|
||||
|
||||
// await db.insert(usersTable)
|
||||
// .values({ id1: 1, name: 'John' })
|
||||
// .onConflictDoNothing({ target: usersTable.id1 });
|
||||
|
||||
// const res = await db.select({ id: usersTable.id1, name: usersTable.name }).from(usersTable).where(
|
||||
// eq(usersTable.id1, 1),
|
||||
// );
|
||||
|
||||
// expect(res).toEqual([{ id: 1, name: 'John' }]);
|
||||
// });
|
||||
@@ -0,0 +1,160 @@
|
||||
import retry from 'async-retry';
|
||||
import type Docker from 'dockerode';
|
||||
import { relations, sql } from 'drizzle-orm';
|
||||
import { drizzle, type GelJsDatabase } from 'drizzle-orm/gel';
|
||||
import { foreignKey, gelSchema, gelTable, text, timestamptz, uniqueIndex, uuid } from 'drizzle-orm/gel-core';
|
||||
import createClient, { type Client } from 'gel';
|
||||
import { afterAll, afterEach, beforeAll, describe, expect, test } from 'vitest';
|
||||
import 'zx/globals';
|
||||
import { createDockerDB } from './createInstance';
|
||||
|
||||
$.quiet = true;
|
||||
|
||||
const ENABLE_LOGGING = false;
|
||||
|
||||
export const extauth = gelSchema('ext::auth');
|
||||
|
||||
export const identityInExtauth = extauth.table('Identity', {
|
||||
id: uuid().default(sql`uuid_generate_v4()`).primaryKey().notNull(),
|
||||
createdAt: timestamptz('created_at').default(sql`(clock_timestamp())`).notNull(),
|
||||
issuer: text().notNull(),
|
||||
modifiedAt: timestamptz('modified_at').notNull(),
|
||||
subject: text().notNull(),
|
||||
}, (table) => [
|
||||
uniqueIndex('6bc2dd19-bce4-5810-bb1b-7007afe97a11;schemaconstr').using(
|
||||
'btree',
|
||||
table.id.asc().nullsLast().op('uuid_ops'),
|
||||
),
|
||||
]);
|
||||
|
||||
export const user = gelTable('User', {
|
||||
id: uuid().default(sql`uuid_generate_v4()`).primaryKey().notNull(),
|
||||
email: text().notNull(),
|
||||
identityId: uuid('identity_id').notNull(),
|
||||
username: text().notNull(),
|
||||
}, (table) => [
|
||||
uniqueIndex('d504514c-26a7-11f0-b836-81aa188c0abe;schemaconstr').using(
|
||||
'btree',
|
||||
table.id.asc().nullsLast().op('uuid_ops'),
|
||||
),
|
||||
foreignKey({
|
||||
columns: [table.identityId],
|
||||
foreignColumns: [identityInExtauth.id],
|
||||
name: 'User_fk_identity',
|
||||
}),
|
||||
]);
|
||||
|
||||
export const userRelations = relations(user, ({ one }) => ({
|
||||
identity: one(identityInExtauth, { references: [identityInExtauth.id], fields: [user.identityId] }),
|
||||
}));
|
||||
|
||||
const schema = { user, identityInExtauth, userRelations };
|
||||
|
||||
let client: Client;
|
||||
let db: GelJsDatabase<typeof schema>;
|
||||
const tlsSecurity: string = 'insecure';
|
||||
let dsn: string;
|
||||
let container: Docker.Container | undefined;
|
||||
|
||||
function sleep(ms: number) {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
beforeAll(async () => {
|
||||
let connectionString;
|
||||
if (process.env['GEL_CONNECTION_STRING']) {
|
||||
connectionString = process.env['GEL_CONNECTION_STRING'];
|
||||
} else {
|
||||
const { connectionString: conStr, container: contrainerObj } = await createDockerDB();
|
||||
connectionString = conStr;
|
||||
container = contrainerObj;
|
||||
}
|
||||
await sleep(15 * 1000);
|
||||
client = await retry(() => {
|
||||
client = createClient({ dsn: connectionString, tlsSecurity: 'insecure' });
|
||||
return client;
|
||||
}, {
|
||||
retries: 20,
|
||||
factor: 1,
|
||||
minTimeout: 250,
|
||||
maxTimeout: 250,
|
||||
randomize: false,
|
||||
onRetry() {
|
||||
client?.close();
|
||||
},
|
||||
});
|
||||
db = drizzle(client, { logger: ENABLE_LOGGING, schema: { user, identityInExtauth, userRelations } });
|
||||
|
||||
dsn = connectionString;
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await client?.close().catch(console.error);
|
||||
await container?.stop().catch(console.error);
|
||||
});
|
||||
|
||||
describe('extensions tests group', async () => {
|
||||
beforeAll(async () => {
|
||||
await $`gel query 'CREATE EXTENSION pgcrypto VERSION "1.3";
|
||||
CREATE EXTENSION auth VERSION "1.0";
|
||||
CREATE TYPE default::User {
|
||||
CREATE REQUIRED LINK identity: ext::auth::Identity;
|
||||
CREATE REQUIRED PROPERTY email: std::str;
|
||||
CREATE REQUIRED PROPERTY username: std::str;
|
||||
};
|
||||
CREATE GLOBAL default::current_user := (std::assert_single((SELECT
|
||||
default::User {
|
||||
id,
|
||||
username,
|
||||
email
|
||||
}
|
||||
FILTER
|
||||
(.identity = GLOBAL ext::auth::ClientTokenIdentity)
|
||||
)));' --tls-security=${tlsSecurity} --dsn=${dsn}`;
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await $`gel query "DELETE default::User;" --tls-security=${tlsSecurity} --dsn=${dsn}`;
|
||||
});
|
||||
|
||||
test('check that you can query from ext::auth schema in gel', async () => {
|
||||
const [response] = await db.insert(identityInExtauth).values({
|
||||
issuer: 'issuer',
|
||||
subject: 'subject',
|
||||
modifiedAt: new Date(),
|
||||
}).returning();
|
||||
await db.insert(user).values({ identityId: response!.id, username: 'username', email: 'email' });
|
||||
|
||||
const userResponse = await db.select().from(user);
|
||||
const authResponse = await db.select().from(identityInExtauth);
|
||||
const relationsResponse = await db.query.user.findMany({
|
||||
columns: {
|
||||
id: false,
|
||||
identityId: false,
|
||||
},
|
||||
with: {
|
||||
identity: {
|
||||
columns: {
|
||||
id: false,
|
||||
modifiedAt: false,
|
||||
createdAt: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(relationsResponse).toStrictEqual([{
|
||||
email: 'email',
|
||||
identity: { issuer: 'issuer', subject: 'subject' },
|
||||
username: 'username',
|
||||
}]);
|
||||
expect(userResponse.length).toBe(1);
|
||||
expect(authResponse.length).toBe(1);
|
||||
|
||||
expect(userResponse[0]!.username).toBe('username');
|
||||
expect(userResponse[0]!.email).toBe('email');
|
||||
|
||||
expect(authResponse[0]!.issuer).toBe('issuer');
|
||||
expect(authResponse[0]!.subject).toBe('subject');
|
||||
});
|
||||
});
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,73 @@
|
||||
import { afterAll, expect, it } from 'vitest';
|
||||
import 'zx/globals';
|
||||
import * as fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
$.verbose = false;
|
||||
|
||||
const IMPORTS_FOLDER = 'tests/imports/files';
|
||||
|
||||
const folderPath = '../drizzle-orm/dist/package.json';
|
||||
const pj = JSON.parse(fs.readFileSync(folderPath, 'utf8'));
|
||||
|
||||
if (!fs.existsSync(IMPORTS_FOLDER)) {
|
||||
fs.mkdirSync(IMPORTS_FOLDER);
|
||||
}
|
||||
|
||||
it('dynamic imports check for CommonJS', async () => {
|
||||
const promises: ProcessPromise[] = [];
|
||||
for (const [i, key] of Object.keys(pj['exports']).entries()) {
|
||||
const o1 = path.join('drizzle-orm', key);
|
||||
if (
|
||||
o1.startsWith('drizzle-orm/bun-sqlite') || o1.startsWith('drizzle-orm/pglite')
|
||||
|| o1.startsWith('drizzle-orm/expo-sqlite') || o1.startsWith('drizzle-orm/libsql/wasm')
|
||||
|| o1.startsWith('drizzle-orm/bun-sql')
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
fs.writeFileSync(`${IMPORTS_FOLDER}/imports_${i}.cjs`, 'requ');
|
||||
fs.appendFileSync(`${IMPORTS_FOLDER}/imports_${i}.cjs`, 'ire("' + o1 + '");\n', {});
|
||||
|
||||
// fs.writeFileSync(`${IMPORTS_FOLDER}/imports_${i}.mjs`, 'imp');
|
||||
// fs.appendFileSync(`${IMPORTS_FOLDER}/imports_${i}.mjs`, 'ort "' + o1 + '"\n', {});
|
||||
|
||||
promises.push(
|
||||
$`node ${IMPORTS_FOLDER}/imports_${i}.cjs`.nothrow(),
|
||||
// $`node ${IMPORTS_FOLDER}/imports_${i}.mjs`.nothrow(),
|
||||
);
|
||||
}
|
||||
const results = await Promise.all(promises);
|
||||
|
||||
for (const result of results) {
|
||||
expect(result.exitCode, result.message).toBe(0);
|
||||
}
|
||||
});
|
||||
|
||||
it('dynamic imports check for ESM', async () => {
|
||||
const promises: ProcessPromise[] = [];
|
||||
for (const [i, key] of Object.keys(pj['exports']).entries()) {
|
||||
const o1 = path.join('drizzle-orm', key);
|
||||
if (
|
||||
o1.startsWith('drizzle-orm/bun-sqlite') || o1.startsWith('drizzle-orm/expo-sqlite')
|
||||
|| o1.startsWith('drizzle-orm/bun-sql')
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
fs.writeFileSync(`${IMPORTS_FOLDER}/imports_${i}.mjs`, 'imp');
|
||||
fs.appendFileSync(`${IMPORTS_FOLDER}/imports_${i}.mjs`, 'ort "' + o1 + '"\n', {});
|
||||
promises.push(
|
||||
$`node ${IMPORTS_FOLDER}/imports_${i}.mjs`.nothrow(),
|
||||
$`node --import import-in-the-middle/hook.mjs ${IMPORTS_FOLDER}/imports_${i}.mjs`.nothrow(),
|
||||
);
|
||||
}
|
||||
|
||||
const results = await Promise.all(promises);
|
||||
|
||||
for (const result of results) {
|
||||
expect(result.exitCode, result.message).toBe(0);
|
||||
}
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
fs.rmdirSync(IMPORTS_FOLDER, { recursive: true });
|
||||
});
|
||||
@@ -0,0 +1,205 @@
|
||||
import 'dotenv/config';
|
||||
|
||||
import type { TestFn } from 'ava';
|
||||
import anyTest from 'ava';
|
||||
import Docker from 'dockerode';
|
||||
import { DefaultLogger, sql } from 'drizzle-orm';
|
||||
import { boolean, json, mysqlTable, serial, text, timestamp, varchar } from 'drizzle-orm/mysql-core';
|
||||
import type { MySql2Database } from 'drizzle-orm/mysql2';
|
||||
import { drizzle } from 'drizzle-orm/mysql2';
|
||||
import getPort from 'get-port';
|
||||
import * as mysql from 'mysql2/promise';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
|
||||
const ENABLE_LOGGING = false;
|
||||
|
||||
const usersTable = mysqlTable('userstest', {
|
||||
id: serial('id').primaryKey(),
|
||||
name: text('name').notNull(),
|
||||
verified: boolean('verified').notNull().default(false),
|
||||
jsonb: json('jsonb').$type<string[]>(),
|
||||
createdAt: timestamp('created_at', { fsp: 2 }).notNull().defaultNow(),
|
||||
});
|
||||
|
||||
interface Context {
|
||||
docker: Docker;
|
||||
mysqlContainer: Docker.Container;
|
||||
db: MySql2Database;
|
||||
client: mysql.Connection;
|
||||
}
|
||||
|
||||
const test = anyTest as TestFn<Context>;
|
||||
|
||||
async function createDockerDB(ctx: Context): Promise<string> {
|
||||
const docker = (ctx.docker = new Docker());
|
||||
const port = await getPort({ port: 3306 });
|
||||
const image = 'mysql:8';
|
||||
|
||||
const pullStream = await docker.pull(image);
|
||||
await new Promise((resolve, reject) =>
|
||||
docker.modem.followProgress(pullStream, (err) => (err ? reject(err) : resolve(err)))
|
||||
);
|
||||
|
||||
ctx.mysqlContainer = await docker.createContainer({
|
||||
Image: image,
|
||||
Env: ['MYSQL_ROOT_PASSWORD=mysql', 'MYSQL_DATABASE=drizzle'],
|
||||
name: `drizzle-integration-tests-${uuid()}`,
|
||||
HostConfig: {
|
||||
AutoRemove: true,
|
||||
PortBindings: {
|
||||
'3306/tcp': [{ HostPort: `${port}` }],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await ctx.mysqlContainer.start();
|
||||
|
||||
return `mysql://root:mysql@127.0.0.1:${port}/drizzle`;
|
||||
}
|
||||
|
||||
test.before(async (t) => {
|
||||
const ctx = t.context;
|
||||
const connectionString = process.env['MYSQL_CONNECTION_STRING'] ?? await createDockerDB(ctx);
|
||||
|
||||
const sleep = 1000;
|
||||
let timeLeft = 20000;
|
||||
let connected = false;
|
||||
let lastError: unknown | undefined;
|
||||
do {
|
||||
try {
|
||||
ctx.client = await mysql.createConnection(connectionString);
|
||||
await ctx.client.connect();
|
||||
connected = true;
|
||||
break;
|
||||
} catch (e) {
|
||||
lastError = e;
|
||||
await new Promise((resolve) => setTimeout(resolve, sleep));
|
||||
timeLeft -= sleep;
|
||||
}
|
||||
} while (timeLeft > 0);
|
||||
if (!connected) {
|
||||
console.error('Cannot connect to MySQL');
|
||||
await ctx.client?.end().catch(console.error);
|
||||
await ctx.mysqlContainer?.stop().catch(console.error);
|
||||
throw lastError;
|
||||
}
|
||||
ctx.db = drizzle(ctx.client, { logger: ENABLE_LOGGING ? new DefaultLogger() : undefined });
|
||||
});
|
||||
|
||||
test.after.always(async (t) => {
|
||||
const ctx = t.context;
|
||||
await ctx.client?.end().catch(console.error);
|
||||
await ctx.mysqlContainer?.stop().catch(console.error);
|
||||
});
|
||||
|
||||
test.beforeEach(async (t) => {
|
||||
const ctx = t.context;
|
||||
await ctx.db.execute(sql`drop table if exists \`userstest\``);
|
||||
await ctx.db.execute(sql`drop table if exists \`users2\``);
|
||||
await ctx.db.execute(sql`drop table if exists \`cities\``);
|
||||
|
||||
await ctx.db.execute(
|
||||
sql`
|
||||
create table \`userstest\` (
|
||||
\`id\` serial primary key,
|
||||
\`name\` text not null,
|
||||
\`verified\` boolean not null default false,
|
||||
\`jsonb\` json,
|
||||
\`created_at\` timestamp not null default now()
|
||||
)
|
||||
`,
|
||||
);
|
||||
|
||||
await ctx.db.execute(
|
||||
sql`
|
||||
create table \`users2\` (
|
||||
\`id\` serial primary key,
|
||||
\`name\` text not null,
|
||||
\`city_id\` int references \`cities\`(\`id\`)
|
||||
)
|
||||
`,
|
||||
);
|
||||
|
||||
await ctx.db.execute(
|
||||
sql`
|
||||
create table \`cities\` (
|
||||
\`id\` serial primary key,
|
||||
\`name\` text not null
|
||||
)
|
||||
`,
|
||||
);
|
||||
});
|
||||
|
||||
async function setupReturningFunctionsTest(db: MySql2Database) {
|
||||
await db.execute(sql`drop table if exists \`users_default_fn\``);
|
||||
await db.execute(
|
||||
sql`
|
||||
create table \`users_default_fn\` (
|
||||
\`id\` varchar(256) primary key,
|
||||
\`name\` text not null
|
||||
);
|
||||
`,
|
||||
);
|
||||
}
|
||||
|
||||
test.serial('insert $returningId: serail as id', async (t) => {
|
||||
const { db } = t.context;
|
||||
|
||||
const result = await db.insert(usersTable).values({ name: 'John' }).$returningId();
|
||||
// ^?
|
||||
t.deepEqual(result, [{ id: 1 }]);
|
||||
});
|
||||
|
||||
test.serial('insert $returningId: serail as id, batch insert', async (t) => {
|
||||
const { db } = t.context;
|
||||
|
||||
const result = await db.insert(usersTable).values([{ name: 'John' }, { name: 'John1' }]).$returningId();
|
||||
// ^?
|
||||
t.deepEqual(result, [{ id: 1 }, { id: 2 }]);
|
||||
});
|
||||
|
||||
test.serial('insert $returningId: $default as primary key', async (t) => {
|
||||
const { db } = t.context;
|
||||
|
||||
const uniqueKeys = ['ao865jf3mcmkfkk8o5ri495z', 'dyqs529eom0iczo2efxzbcut'];
|
||||
let iterator = 0;
|
||||
|
||||
const usersTableDefFn = mysqlTable('users_default_fn', {
|
||||
customId: varchar('id', { length: 256 }).primaryKey().$defaultFn(() => {
|
||||
const value = uniqueKeys[iterator]!;
|
||||
iterator++;
|
||||
return value;
|
||||
}),
|
||||
name: text('name').notNull(),
|
||||
});
|
||||
|
||||
await setupReturningFunctionsTest(db);
|
||||
|
||||
const result = await db.insert(usersTableDefFn).values([{ name: 'John' }, { name: 'John1' }])
|
||||
// ^?
|
||||
.$returningId();
|
||||
t.deepEqual(result, [{ customId: 'ao865jf3mcmkfkk8o5ri495z' }, { customId: 'dyqs529eom0iczo2efxzbcut' }]);
|
||||
});
|
||||
|
||||
test.serial('insert $returningId: $default as primary key with value', async (t) => {
|
||||
const { db } = t.context;
|
||||
|
||||
const uniqueKeys = ['ao865jf3mcmkfkk8o5ri495z', 'dyqs529eom0iczo2efxzbcut'];
|
||||
let iterator = 0;
|
||||
|
||||
const usersTableDefFn = mysqlTable('users_default_fn', {
|
||||
customId: varchar('id', { length: 256 }).primaryKey().$defaultFn(() => {
|
||||
const value = uniqueKeys[iterator]!;
|
||||
iterator++;
|
||||
return value;
|
||||
}),
|
||||
name: text('name').notNull(),
|
||||
});
|
||||
|
||||
await setupReturningFunctionsTest(db);
|
||||
|
||||
const result = await db.insert(usersTableDefFn).values([{ name: 'John', customId: 'test' }, { name: 'John1' }])
|
||||
// ^?
|
||||
.$returningId();
|
||||
t.deepEqual(result, [{ customId: 'test' }, { customId: 'ao865jf3mcmkfkk8o5ri495z' }]);
|
||||
});
|
||||
@@ -0,0 +1,379 @@
|
||||
import { eq, getTableName, is, sql, Table } from 'drizzle-orm';
|
||||
import type { MutationOption } from 'drizzle-orm/cache/core';
|
||||
import { Cache } from 'drizzle-orm/cache/core';
|
||||
import type { CacheConfig } from 'drizzle-orm/cache/core/types';
|
||||
import type { MySqlDatabase } from 'drizzle-orm/mysql-core';
|
||||
import { alias, boolean, int, json, mysqlTable, serial, text, timestamp } from 'drizzle-orm/mysql-core';
|
||||
import Keyv from 'keyv';
|
||||
import { beforeEach, describe, expect, test, vi } from 'vitest';
|
||||
|
||||
// eslint-disable-next-line drizzle-internal/require-entity-kind
|
||||
export class TestGlobalCache extends Cache {
|
||||
private globalTtl: number = 1000;
|
||||
private usedTablesPerKey: Record<string, string[]> = {};
|
||||
|
||||
constructor(private kv: Keyv = new Keyv()) {
|
||||
super();
|
||||
}
|
||||
|
||||
override strategy(): 'explicit' | 'all' {
|
||||
return 'all';
|
||||
}
|
||||
override async get(key: string, _tables: string[], _isTag: boolean): Promise<any[] | undefined> {
|
||||
const res = await this.kv.get(key) ?? undefined;
|
||||
return res;
|
||||
}
|
||||
override async put(
|
||||
key: string,
|
||||
response: any,
|
||||
tables: string[],
|
||||
isTag: boolean,
|
||||
config?: CacheConfig,
|
||||
): Promise<void> {
|
||||
await this.kv.set(key, response, config ? config.ex : this.globalTtl);
|
||||
for (const table of tables) {
|
||||
const keys = this.usedTablesPerKey[table];
|
||||
if (keys === undefined) {
|
||||
this.usedTablesPerKey[table] = [key];
|
||||
} else {
|
||||
keys.push(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
override async onMutate(params: MutationOption): Promise<void> {
|
||||
const tagsArray = params.tags ? Array.isArray(params.tags) ? params.tags : [params.tags] : [];
|
||||
const tablesArray = params.tables ? Array.isArray(params.tables) ? params.tables : [params.tables] : [];
|
||||
|
||||
const keysToDelete = new Set<string>();
|
||||
|
||||
for (const table of tablesArray) {
|
||||
const tableName = is(table, Table) ? getTableName(table) : table as string;
|
||||
const keys = this.usedTablesPerKey[tableName] ?? [];
|
||||
for (const key of keys) keysToDelete.add(key);
|
||||
}
|
||||
|
||||
if (keysToDelete.size > 0 || tagsArray.length > 0) {
|
||||
for (const tag of tagsArray) {
|
||||
await this.kv.delete(tag);
|
||||
}
|
||||
|
||||
for (const key of keysToDelete) {
|
||||
await this.kv.delete(key);
|
||||
for (const table of tablesArray) {
|
||||
const tableName = is(table, Table) ? getTableName(table) : table as string;
|
||||
this.usedTablesPerKey[tableName] = [];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line drizzle-internal/require-entity-kind
|
||||
export class TestCache extends TestGlobalCache {
|
||||
override strategy(): 'explicit' | 'all' {
|
||||
return 'explicit';
|
||||
}
|
||||
}
|
||||
|
||||
declare module 'vitest' {
|
||||
interface TestContext {
|
||||
cachedMySQL: {
|
||||
db: MySqlDatabase<any, any>;
|
||||
dbGlobalCached: MySqlDatabase<any, any>;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const usersTable = mysqlTable('users', {
|
||||
id: serial('id').primaryKey(),
|
||||
name: text('name').notNull(),
|
||||
verified: boolean('verified').notNull().default(false),
|
||||
jsonb: json('jsonb').$type<string[]>(),
|
||||
createdAt: timestamp('created_at', { fsp: 2 }).notNull().defaultNow(),
|
||||
});
|
||||
|
||||
const postsTable = mysqlTable('posts', {
|
||||
id: serial().primaryKey(),
|
||||
description: text().notNull(),
|
||||
userId: int('city_id').references(() => usersTable.id),
|
||||
});
|
||||
|
||||
export function tests() {
|
||||
describe('common_cache', () => {
|
||||
beforeEach(async (ctx) => {
|
||||
const { db, dbGlobalCached } = ctx.cachedMySQL;
|
||||
await db.execute(sql`drop table if exists users`);
|
||||
await db.execute(sql`drop table if exists posts`);
|
||||
await db.$cache?.invalidate({ tables: 'users' });
|
||||
await dbGlobalCached.$cache?.invalidate({ tables: 'users' });
|
||||
// public users
|
||||
await db.execute(
|
||||
sql`
|
||||
create table users (
|
||||
id serial primary key,
|
||||
name text not null,
|
||||
verified boolean not null default false,
|
||||
jsonb json,
|
||||
created_at timestamp not null default now()
|
||||
)
|
||||
`,
|
||||
);
|
||||
await db.execute(
|
||||
sql`
|
||||
create table posts (
|
||||
id serial primary key,
|
||||
description text not null,
|
||||
user_id int
|
||||
)
|
||||
`,
|
||||
);
|
||||
});
|
||||
|
||||
test('test force invalidate', async (ctx) => {
|
||||
const { db } = ctx.cachedMySQL;
|
||||
|
||||
const spyInvalidate = vi.spyOn(db.$cache, 'invalidate');
|
||||
await db.$cache?.invalidate({ tables: 'users' });
|
||||
expect(spyInvalidate).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test('default global config - no cache should be hit', async (ctx) => {
|
||||
const { db } = ctx.cachedMySQL;
|
||||
|
||||
// @ts-expect-error
|
||||
const spyPut = vi.spyOn(db.$cache, 'put');
|
||||
// @ts-expect-error
|
||||
const spyGet = vi.spyOn(db.$cache, 'get');
|
||||
// @ts-expect-error
|
||||
const spyInvalidate = vi.spyOn(db.$cache, 'onMutate');
|
||||
|
||||
await db.select().from(usersTable);
|
||||
|
||||
expect(spyPut).toHaveBeenCalledTimes(0);
|
||||
expect(spyGet).toHaveBeenCalledTimes(0);
|
||||
expect(spyInvalidate).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
test('default global config + enable cache on select: get, put', async (ctx) => {
|
||||
const { db } = ctx.cachedMySQL;
|
||||
|
||||
// @ts-expect-error
|
||||
const spyPut = vi.spyOn(db.$cache, 'put');
|
||||
// @ts-expect-error
|
||||
const spyGet = vi.spyOn(db.$cache, 'get');
|
||||
// @ts-expect-error
|
||||
const spyInvalidate = vi.spyOn(db.$cache, 'onMutate');
|
||||
|
||||
await db.select().from(usersTable).$withCache();
|
||||
|
||||
expect(spyPut).toHaveBeenCalledTimes(1);
|
||||
expect(spyGet).toHaveBeenCalledTimes(1);
|
||||
expect(spyInvalidate).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
test('default global config + enable cache on select + write: get, put, onMutate', async (ctx) => {
|
||||
const { db } = ctx.cachedMySQL;
|
||||
|
||||
// @ts-expect-error
|
||||
const spyPut = vi.spyOn(db.$cache, 'put');
|
||||
// @ts-expect-error
|
||||
const spyGet = vi.spyOn(db.$cache, 'get');
|
||||
// @ts-expect-error
|
||||
const spyInvalidate = vi.spyOn(db.$cache, 'onMutate');
|
||||
|
||||
await db.select().from(usersTable).$withCache({ config: { ex: 1 } });
|
||||
|
||||
expect(spyPut).toHaveBeenCalledTimes(1);
|
||||
expect(spyGet).toHaveBeenCalledTimes(1);
|
||||
expect(spyInvalidate).toHaveBeenCalledTimes(0);
|
||||
|
||||
spyPut.mockClear();
|
||||
spyGet.mockClear();
|
||||
spyInvalidate.mockClear();
|
||||
|
||||
await db.insert(usersTable).values({ name: 'John' });
|
||||
|
||||
expect(spyPut).toHaveBeenCalledTimes(0);
|
||||
expect(spyGet).toHaveBeenCalledTimes(0);
|
||||
expect(spyInvalidate).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test('default global config + enable cache on select + disable invalidate: get, put', async (ctx) => {
|
||||
const { db } = ctx.cachedMySQL;
|
||||
|
||||
// @ts-expect-error
|
||||
const spyPut = vi.spyOn(db.$cache, 'put');
|
||||
// @ts-expect-error
|
||||
const spyGet = vi.spyOn(db.$cache, 'get');
|
||||
// @ts-expect-error
|
||||
const spyInvalidate = vi.spyOn(db.$cache, 'onMutate');
|
||||
|
||||
await db.select().from(usersTable).$withCache({ tag: 'custom', autoInvalidate: false, config: { ex: 1 } });
|
||||
|
||||
expect(spyPut).toHaveBeenCalledTimes(1);
|
||||
expect(spyGet).toHaveBeenCalledTimes(1);
|
||||
expect(spyInvalidate).toHaveBeenCalledTimes(0);
|
||||
|
||||
await db.insert(usersTable).values({ name: 'John' });
|
||||
|
||||
// invalidate force
|
||||
await db.$cache?.invalidate({ tags: ['custom'] });
|
||||
});
|
||||
|
||||
test('global: true + disable cache', async (ctx) => {
|
||||
const { dbGlobalCached: db } = ctx.cachedMySQL;
|
||||
|
||||
// @ts-expect-error
|
||||
const spyPut = vi.spyOn(db.$cache, 'put');
|
||||
// @ts-expect-error
|
||||
const spyGet = vi.spyOn(db.$cache, 'get');
|
||||
// @ts-expect-error
|
||||
const spyInvalidate = vi.spyOn(db.$cache, 'onMutate');
|
||||
|
||||
await db.select().from(usersTable).$withCache(false);
|
||||
|
||||
expect(spyPut).toHaveBeenCalledTimes(0);
|
||||
expect(spyGet).toHaveBeenCalledTimes(0);
|
||||
expect(spyInvalidate).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
test('global: true - cache should be hit', async (ctx) => {
|
||||
const { dbGlobalCached: db } = ctx.cachedMySQL;
|
||||
|
||||
// @ts-expect-error
|
||||
const spyPut = vi.spyOn(db.$cache, 'put');
|
||||
// @ts-expect-error
|
||||
const spyGet = vi.spyOn(db.$cache, 'get');
|
||||
// @ts-expect-error
|
||||
const spyInvalidate = vi.spyOn(db.$cache, 'onMutate');
|
||||
|
||||
await db.select().from(usersTable);
|
||||
|
||||
expect(spyPut).toHaveBeenCalledTimes(1);
|
||||
expect(spyGet).toHaveBeenCalledTimes(1);
|
||||
expect(spyInvalidate).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
test('global: true - cache: false on select - no cache hit', async (ctx) => {
|
||||
const { dbGlobalCached: db } = ctx.cachedMySQL;
|
||||
|
||||
// @ts-expect-error
|
||||
const spyPut = vi.spyOn(db.$cache, 'put');
|
||||
// @ts-expect-error
|
||||
const spyGet = vi.spyOn(db.$cache, 'get');
|
||||
// @ts-expect-error
|
||||
const spyInvalidate = vi.spyOn(db.$cache, 'onMutate');
|
||||
|
||||
await db.select().from(usersTable).$withCache(false);
|
||||
|
||||
expect(spyPut).toHaveBeenCalledTimes(0);
|
||||
expect(spyGet).toHaveBeenCalledTimes(0);
|
||||
expect(spyInvalidate).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
test('global: true - disable invalidate - cache hit + no invalidate', async (ctx) => {
|
||||
const { dbGlobalCached: db } = ctx.cachedMySQL;
|
||||
|
||||
// @ts-expect-error
|
||||
const spyPut = vi.spyOn(db.$cache, 'put');
|
||||
// @ts-expect-error
|
||||
const spyGet = vi.spyOn(db.$cache, 'get');
|
||||
// @ts-expect-error
|
||||
const spyInvalidate = vi.spyOn(db.$cache, 'onMutate');
|
||||
|
||||
await db.select().from(usersTable).$withCache({ autoInvalidate: false });
|
||||
|
||||
expect(spyPut).toHaveBeenCalledTimes(1);
|
||||
expect(spyGet).toHaveBeenCalledTimes(1);
|
||||
expect(spyInvalidate).toHaveBeenCalledTimes(0);
|
||||
|
||||
spyPut.mockClear();
|
||||
spyGet.mockClear();
|
||||
spyInvalidate.mockClear();
|
||||
|
||||
await db.insert(usersTable).values({ name: 'John' });
|
||||
|
||||
expect(spyPut).toHaveBeenCalledTimes(0);
|
||||
expect(spyGet).toHaveBeenCalledTimes(0);
|
||||
expect(spyInvalidate).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test('global: true - with custom tag', async (ctx) => {
|
||||
const { dbGlobalCached: db } = ctx.cachedMySQL;
|
||||
|
||||
// @ts-expect-error
|
||||
const spyPut = vi.spyOn(db.$cache, 'put');
|
||||
// @ts-expect-error
|
||||
const spyGet = vi.spyOn(db.$cache, 'get');
|
||||
// @ts-expect-error
|
||||
const spyInvalidate = vi.spyOn(db.$cache, 'onMutate');
|
||||
|
||||
await db.select().from(usersTable).$withCache({ tag: 'custom', autoInvalidate: false });
|
||||
|
||||
expect(spyPut).toHaveBeenCalledTimes(1);
|
||||
expect(spyGet).toHaveBeenCalledTimes(1);
|
||||
expect(spyInvalidate).toHaveBeenCalledTimes(0);
|
||||
|
||||
await db.insert(usersTable).values({ name: 'John' });
|
||||
|
||||
// invalidate force
|
||||
await db.$cache?.invalidate({ tags: ['custom'] });
|
||||
});
|
||||
|
||||
// check select used tables
|
||||
test('check simple select used tables', (ctx) => {
|
||||
const { db } = ctx.cachedMySQL;
|
||||
|
||||
// @ts-expect-error
|
||||
expect(db.select().from(usersTable).getUsedTables()).toStrictEqual(['users']);
|
||||
// @ts-expect-error
|
||||
expect(db.select().from(sql`${usersTable}`).getUsedTables()).toStrictEqual(['users']);
|
||||
});
|
||||
// check select+join used tables
|
||||
test('select+join', (ctx) => {
|
||||
const { db } = ctx.cachedMySQL;
|
||||
|
||||
// @ts-expect-error
|
||||
expect(db.select().from(usersTable).leftJoin(postsTable, eq(usersTable.id, postsTable.userId)).getUsedTables())
|
||||
.toStrictEqual(['users', 'posts']);
|
||||
expect(
|
||||
// @ts-expect-error
|
||||
db.select().from(sql`${usersTable}`).leftJoin(postsTable, eq(usersTable.id, postsTable.userId)).getUsedTables(),
|
||||
).toStrictEqual(['users', 'posts']);
|
||||
});
|
||||
// check select+2join used tables
|
||||
test('select+2joins', (ctx) => {
|
||||
const { db } = ctx.cachedMySQL;
|
||||
|
||||
expect(
|
||||
db.select().from(usersTable).leftJoin(
|
||||
postsTable,
|
||||
eq(usersTable.id, postsTable.userId),
|
||||
).leftJoin(
|
||||
alias(postsTable, 'post2'),
|
||||
eq(usersTable.id, postsTable.userId),
|
||||
)
|
||||
// @ts-expect-error
|
||||
.getUsedTables(),
|
||||
)
|
||||
.toStrictEqual(['users', 'posts']);
|
||||
expect(
|
||||
db.select().from(sql`${usersTable}`).leftJoin(postsTable, eq(usersTable.id, postsTable.userId)).leftJoin(
|
||||
alias(postsTable, 'post2'),
|
||||
eq(usersTable.id, postsTable.userId),
|
||||
// @ts-expect-error
|
||||
).getUsedTables(),
|
||||
).toStrictEqual(['users', 'posts']);
|
||||
});
|
||||
// select subquery used tables
|
||||
test('select+join', (ctx) => {
|
||||
const { db } = ctx.cachedMySQL;
|
||||
|
||||
const sq = db.select().from(usersTable).where(eq(usersTable.id, 42)).as('sq');
|
||||
db.select().from(sq);
|
||||
|
||||
// @ts-expect-error
|
||||
expect(db.select().from(sq).getUsedTables()).toStrictEqual(['users']);
|
||||
});
|
||||
});
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,817 @@
|
||||
import retry from 'async-retry';
|
||||
import type Docker from 'dockerode';
|
||||
import { asc, eq, Name, sql } from 'drizzle-orm';
|
||||
import {
|
||||
alias,
|
||||
binary,
|
||||
customType,
|
||||
date,
|
||||
datetime,
|
||||
mysqlEnum,
|
||||
mysqlTable,
|
||||
mysqlTableCreator,
|
||||
serial,
|
||||
text,
|
||||
time,
|
||||
varchar,
|
||||
year,
|
||||
} from 'drizzle-orm/mysql-core';
|
||||
import type { MySql2Database } from 'drizzle-orm/mysql2';
|
||||
import { drizzle } from 'drizzle-orm/mysql2';
|
||||
import { migrate } from 'drizzle-orm/mysql2/migrator';
|
||||
import * as mysql from 'mysql2/promise';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import { afterAll, beforeAll, beforeEach, expect, test } from 'vitest';
|
||||
import { toLocalDate } from '~/utils';
|
||||
import { createDockerDB } from './mysql-common';
|
||||
|
||||
const ENABLE_LOGGING = false;
|
||||
|
||||
let db: MySql2Database;
|
||||
let client: mysql.Connection;
|
||||
let container: Docker.Container | undefined;
|
||||
|
||||
beforeAll(async () => {
|
||||
let connectionString;
|
||||
if (process.env['MYSQL_CONNECTION_STRING']) {
|
||||
connectionString = process.env['MYSQL_CONNECTION_STRING'];
|
||||
} else {
|
||||
const { connectionString: conStr, container: contrainerObj } = await createDockerDB();
|
||||
connectionString = conStr;
|
||||
container = contrainerObj;
|
||||
}
|
||||
client = await retry(async () => {
|
||||
client = await mysql.createConnection(connectionString);
|
||||
await client.connect();
|
||||
return client;
|
||||
}, {
|
||||
retries: 20,
|
||||
factor: 1,
|
||||
minTimeout: 250,
|
||||
maxTimeout: 250,
|
||||
randomize: false,
|
||||
onRetry() {
|
||||
client?.end();
|
||||
},
|
||||
});
|
||||
db = drizzle(client, { logger: ENABLE_LOGGING });
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await client?.end();
|
||||
await container?.stop().catch(console.error);
|
||||
});
|
||||
|
||||
beforeEach((ctx) => {
|
||||
ctx.mysql = {
|
||||
db,
|
||||
};
|
||||
});
|
||||
|
||||
const customSerial = customType<{ data: number; notNull: true; default: true }>({
|
||||
dataType() {
|
||||
return 'serial';
|
||||
},
|
||||
});
|
||||
|
||||
const customText = customType<{ data: string }>({
|
||||
dataType() {
|
||||
return 'text';
|
||||
},
|
||||
});
|
||||
|
||||
const customBoolean = customType<{ data: boolean }>({
|
||||
dataType() {
|
||||
return 'boolean';
|
||||
},
|
||||
fromDriver(value) {
|
||||
if (typeof value === 'boolean') {
|
||||
return value;
|
||||
}
|
||||
return value === 1;
|
||||
},
|
||||
});
|
||||
|
||||
const customJson = <TData>(name: string) =>
|
||||
customType<{ data: TData; driverData: string }>({
|
||||
dataType() {
|
||||
return 'json';
|
||||
},
|
||||
toDriver(value: TData): string {
|
||||
return JSON.stringify(value);
|
||||
},
|
||||
})(name);
|
||||
|
||||
const customTimestamp = customType<
|
||||
{ data: Date; driverData: string; config: { fsp: number } }
|
||||
>({
|
||||
dataType(config) {
|
||||
const precision = config?.fsp === undefined ? '' : ` (${config.fsp})`;
|
||||
return `timestamp${precision}`;
|
||||
},
|
||||
fromDriver(value: string): Date {
|
||||
return new Date(value);
|
||||
},
|
||||
});
|
||||
|
||||
const customBinary = customType<{ data: string; driverData: Buffer; config: { length: number } }>({
|
||||
dataType(config) {
|
||||
return config?.length === undefined
|
||||
? `binary`
|
||||
: `binary(${config.length})`;
|
||||
},
|
||||
|
||||
toDriver(value) {
|
||||
return sql`UNHEX(${value})`;
|
||||
},
|
||||
|
||||
fromDriver(value) {
|
||||
return value.toString('hex');
|
||||
},
|
||||
});
|
||||
|
||||
const usersTable = mysqlTable('userstest', {
|
||||
id: customSerial('id').primaryKey(),
|
||||
name: customText('name').notNull(),
|
||||
verified: customBoolean('verified').notNull().default(false),
|
||||
jsonb: customJson<string[]>('jsonb'),
|
||||
createdAt: customTimestamp('created_at', { fsp: 2 }).notNull().default(sql`now()`),
|
||||
});
|
||||
|
||||
const datesTable = mysqlTable('datestable', {
|
||||
date: date('date'),
|
||||
dateAsString: date('date_as_string', { mode: 'string' }),
|
||||
time: time('time', { fsp: 1 }),
|
||||
datetime: datetime('datetime', { fsp: 2 }),
|
||||
datetimeAsString: datetime('datetime_as_string', { fsp: 2, mode: 'string' }),
|
||||
year: year('year'),
|
||||
});
|
||||
|
||||
export const testTable = mysqlTable('test_table', {
|
||||
id: customBinary('id', { length: 16 }).primaryKey(),
|
||||
sqlId: binary('sql_id', { length: 16 }),
|
||||
rawId: varchar('raw_id', { length: 64 }),
|
||||
});
|
||||
|
||||
const usersMigratorTable = mysqlTable('users12', {
|
||||
id: serial('id').primaryKey(),
|
||||
name: text('name').notNull(),
|
||||
email: text('email').notNull(),
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await db.execute(sql`drop table if exists \`userstest\``);
|
||||
await db.execute(sql`drop table if exists \`datestable\``);
|
||||
await db.execute(sql`drop table if exists \`test_table\``);
|
||||
// await ctx.db.execute(sql`create schema public`);
|
||||
await db.execute(
|
||||
sql`
|
||||
create table \`userstest\` (
|
||||
\`id\` serial primary key,
|
||||
\`name\` text not null,
|
||||
\`verified\` boolean not null default false,
|
||||
\`jsonb\` json,
|
||||
\`created_at\` timestamp not null default now()
|
||||
)
|
||||
`,
|
||||
);
|
||||
|
||||
await db.execute(
|
||||
sql`
|
||||
create table \`datestable\` (
|
||||
\`date\` date,
|
||||
\`date_as_string\` date,
|
||||
\`time\` time,
|
||||
\`datetime\` datetime,
|
||||
\`datetime_as_string\` datetime,
|
||||
\`year\` year
|
||||
)
|
||||
`,
|
||||
);
|
||||
|
||||
await db.execute(
|
||||
sql`
|
||||
create table \`test_table\` (
|
||||
\`id\` binary(16) primary key,
|
||||
\`sql_id\` binary(16),
|
||||
\`raw_id\` varchar(64)
|
||||
)
|
||||
`,
|
||||
);
|
||||
});
|
||||
|
||||
test('select all fields', async (ctx) => {
|
||||
const { db } = ctx.mysql;
|
||||
|
||||
await db.insert(usersTable).values({ name: 'John' });
|
||||
const result = await db.select().from(usersTable);
|
||||
|
||||
expect(result[0]!.createdAt).toBeInstanceOf(Date);
|
||||
// not timezone based timestamp, thats why it should not work here
|
||||
// t.assert(Math.abs(result[0]!.createdAt.getTime() - now) < 2000);
|
||||
expect(result).toEqual([{ id: 1, name: 'John', verified: false, jsonb: null, createdAt: result[0]!.createdAt }]);
|
||||
});
|
||||
|
||||
test('select sql', async (ctx) => {
|
||||
const { db } = ctx.mysql;
|
||||
|
||||
await db.insert(usersTable).values({ name: 'John' });
|
||||
const users = await db.select({
|
||||
name: sql`upper(${usersTable.name})`,
|
||||
}).from(usersTable);
|
||||
|
||||
expect(users).toEqual([{ name: 'JOHN' }]);
|
||||
});
|
||||
|
||||
test('select typed sql', async (ctx) => {
|
||||
const { db } = ctx.mysql;
|
||||
|
||||
await db.insert(usersTable).values({ name: 'John' });
|
||||
const users = await db.select({
|
||||
name: sql<string>`upper(${usersTable.name})`,
|
||||
}).from(usersTable);
|
||||
|
||||
expect(users).toEqual([{ name: 'JOHN' }]);
|
||||
});
|
||||
|
||||
test('insert returning sql', async (ctx) => {
|
||||
const { db } = ctx.mysql;
|
||||
|
||||
const [result, _] = await db.insert(usersTable).values({ name: 'John' });
|
||||
|
||||
expect(result.insertId).toBe(1);
|
||||
});
|
||||
|
||||
test('delete returning sql', async (ctx) => {
|
||||
const { db } = ctx.mysql;
|
||||
|
||||
await db.insert(usersTable).values({ name: 'John' });
|
||||
const users = await db.delete(usersTable).where(eq(usersTable.name, 'John'));
|
||||
|
||||
expect(users[0].affectedRows).toBe(1);
|
||||
});
|
||||
|
||||
test('update returning sql', async (ctx) => {
|
||||
const { db } = ctx.mysql;
|
||||
|
||||
await db.insert(usersTable).values({ name: 'John' });
|
||||
const users = await db.update(usersTable).set({ name: 'Jane' }).where(eq(usersTable.name, 'John'));
|
||||
|
||||
expect(users[0].changedRows).toBe(1);
|
||||
});
|
||||
|
||||
test('update with returning all fields', async (ctx) => {
|
||||
const { db } = ctx.mysql;
|
||||
|
||||
await db.insert(usersTable).values({ name: 'John' });
|
||||
const updatedUsers = await db.update(usersTable).set({ name: 'Jane' }).where(eq(usersTable.name, 'John'));
|
||||
|
||||
const users = await db.select().from(usersTable).where(eq(usersTable.id, 1));
|
||||
|
||||
expect(updatedUsers[0].changedRows).toBe(1);
|
||||
|
||||
expect(users[0]!.createdAt).toBeInstanceOf(Date);
|
||||
// not timezone based timestamp, thats why it should not work here
|
||||
// t.assert(Math.abs(users[0]!.createdAt.getTime() - now) < 2000);
|
||||
expect(users).toEqual([{ id: 1, name: 'Jane', verified: false, jsonb: null, createdAt: users[0]!.createdAt }]);
|
||||
});
|
||||
|
||||
test('update with returning partial', async (ctx) => {
|
||||
const { db } = ctx.mysql;
|
||||
|
||||
await db.insert(usersTable).values({ name: 'John' });
|
||||
const updatedUsers = await db.update(usersTable).set({ name: 'Jane' }).where(eq(usersTable.name, 'John'));
|
||||
|
||||
const users = await db.select({ id: usersTable.id, name: usersTable.name }).from(usersTable).where(
|
||||
eq(usersTable.id, 1),
|
||||
);
|
||||
|
||||
expect(updatedUsers[0].changedRows).toBe(1);
|
||||
|
||||
expect(users).toEqual([{ id: 1, name: 'Jane' }]);
|
||||
});
|
||||
|
||||
test('delete with returning all fields', async (ctx) => {
|
||||
const { db } = ctx.mysql;
|
||||
|
||||
await db.insert(usersTable).values({ name: 'John' });
|
||||
const deletedUser = await db.delete(usersTable).where(eq(usersTable.name, 'John'));
|
||||
|
||||
expect(deletedUser[0].affectedRows).toBe(1);
|
||||
});
|
||||
|
||||
test('delete with returning partial', async (ctx) => {
|
||||
const { db } = ctx.mysql;
|
||||
|
||||
await db.insert(usersTable).values({ name: 'John' });
|
||||
const deletedUser = await db.delete(usersTable).where(eq(usersTable.name, 'John'));
|
||||
|
||||
expect(deletedUser[0].affectedRows).toBe(1);
|
||||
});
|
||||
|
||||
test('insert + select', async (ctx) => {
|
||||
const { db } = ctx.mysql;
|
||||
|
||||
await db.insert(usersTable).values({ name: 'John' });
|
||||
const result = await db.select().from(usersTable);
|
||||
expect(result).toEqual([{ id: 1, name: 'John', verified: false, jsonb: null, createdAt: result[0]!.createdAt }]);
|
||||
|
||||
await db.insert(usersTable).values({ name: 'Jane' });
|
||||
const result2 = await db.select().from(usersTable);
|
||||
expect(result2).toEqual([
|
||||
{ id: 1, name: 'John', verified: false, jsonb: null, createdAt: result2[0]!.createdAt },
|
||||
{ id: 2, name: 'Jane', verified: false, jsonb: null, createdAt: result2[1]!.createdAt },
|
||||
]);
|
||||
});
|
||||
|
||||
test('json insert', async (ctx) => {
|
||||
const { db } = ctx.mysql;
|
||||
|
||||
await db.insert(usersTable).values({ name: 'John', jsonb: ['foo', 'bar'] });
|
||||
const result = await db.select({
|
||||
id: usersTable.id,
|
||||
name: usersTable.name,
|
||||
jsonb: usersTable.jsonb,
|
||||
}).from(usersTable);
|
||||
|
||||
expect(result).toEqual([{ id: 1, name: 'John', jsonb: ['foo', 'bar'] }]);
|
||||
});
|
||||
|
||||
test('insert with overridden default values', async (ctx) => {
|
||||
const { db } = ctx.mysql;
|
||||
|
||||
await db.insert(usersTable).values({ name: 'John', verified: true });
|
||||
const result = await db.select().from(usersTable);
|
||||
|
||||
expect(result).toEqual([{ id: 1, name: 'John', verified: true, jsonb: null, createdAt: result[0]!.createdAt }]);
|
||||
});
|
||||
|
||||
test('insert many', async (ctx) => {
|
||||
const { db } = ctx.mysql;
|
||||
|
||||
await db.insert(usersTable).values([
|
||||
{ name: 'John' },
|
||||
{ name: 'Bruce', jsonb: ['foo', 'bar'] },
|
||||
{ name: 'Jane' },
|
||||
{ name: 'Austin', verified: true },
|
||||
]);
|
||||
const result = await db.select({
|
||||
id: usersTable.id,
|
||||
name: usersTable.name,
|
||||
jsonb: usersTable.jsonb,
|
||||
verified: usersTable.verified,
|
||||
}).from(usersTable);
|
||||
|
||||
expect(result).toEqual([
|
||||
{ id: 1, name: 'John', jsonb: null, verified: false },
|
||||
{ id: 2, name: 'Bruce', jsonb: ['foo', 'bar'], verified: false },
|
||||
{ id: 3, name: 'Jane', jsonb: null, verified: false },
|
||||
{ id: 4, name: 'Austin', jsonb: null, verified: true },
|
||||
]);
|
||||
});
|
||||
|
||||
test('insert many with returning', async (ctx) => {
|
||||
const { db } = ctx.mysql;
|
||||
|
||||
const result = await db.insert(usersTable).values([
|
||||
{ name: 'John' },
|
||||
{ name: 'Bruce', jsonb: ['foo', 'bar'] },
|
||||
{ name: 'Jane' },
|
||||
{ name: 'Austin', verified: true },
|
||||
]);
|
||||
|
||||
expect(result[0].affectedRows).toBe(4);
|
||||
});
|
||||
|
||||
test('select with group by as field', async (ctx) => {
|
||||
const { db } = ctx.mysql;
|
||||
|
||||
await db.insert(usersTable).values([{ name: 'John' }, { name: 'Jane' }, { name: 'Jane' }]);
|
||||
|
||||
const result = await db.select({ name: usersTable.name }).from(usersTable)
|
||||
.groupBy(usersTable.name);
|
||||
|
||||
expect(result).toEqual([{ name: 'John' }, { name: 'Jane' }]);
|
||||
});
|
||||
|
||||
test('select with group by as sql', async (ctx) => {
|
||||
const { db } = ctx.mysql;
|
||||
|
||||
await db.insert(usersTable).values([{ name: 'John' }, { name: 'Jane' }, { name: 'Jane' }]);
|
||||
|
||||
const result = await db.select({ name: usersTable.name }).from(usersTable)
|
||||
.groupBy(sql`${usersTable.name}`);
|
||||
|
||||
expect(result).toEqual([{ name: 'John' }, { name: 'Jane' }]);
|
||||
});
|
||||
|
||||
test('select with group by as sql + column', async (ctx) => {
|
||||
const { db } = ctx.mysql;
|
||||
|
||||
await db.insert(usersTable).values([{ name: 'John' }, { name: 'Jane' }, { name: 'Jane' }]);
|
||||
|
||||
const result = await db.select({ name: usersTable.name }).from(usersTable)
|
||||
.groupBy(sql`${usersTable.name}`, usersTable.id);
|
||||
|
||||
expect(result).toEqual([{ name: 'John' }, { name: 'Jane' }, { name: 'Jane' }]);
|
||||
});
|
||||
|
||||
test('select with group by as column + sql', async (ctx) => {
|
||||
const { db } = ctx.mysql;
|
||||
|
||||
await db.insert(usersTable).values([{ name: 'John' }, { name: 'Jane' }, { name: 'Jane' }]);
|
||||
|
||||
const result = await db.select({ name: usersTable.name }).from(usersTable)
|
||||
.groupBy(usersTable.id, sql`${usersTable.name}`);
|
||||
|
||||
expect(result).toEqual([{ name: 'John' }, { name: 'Jane' }, { name: 'Jane' }]);
|
||||
});
|
||||
|
||||
test('select with group by complex query', async (ctx) => {
|
||||
const { db } = ctx.mysql;
|
||||
|
||||
await db.insert(usersTable).values([{ name: 'John' }, { name: 'Jane' }, { name: 'Jane' }]);
|
||||
|
||||
const result = await db.select({ name: usersTable.name }).from(usersTable)
|
||||
.groupBy(usersTable.id, sql`${usersTable.name}`)
|
||||
.orderBy(asc(usersTable.name))
|
||||
.limit(1);
|
||||
|
||||
expect(result).toEqual([{ name: 'Jane' }]);
|
||||
});
|
||||
|
||||
test('build query', async (ctx) => {
|
||||
const { db } = ctx.mysql;
|
||||
|
||||
const query = db.select({ id: usersTable.id, name: usersTable.name }).from(usersTable)
|
||||
.groupBy(usersTable.id, usersTable.name)
|
||||
.toSQL();
|
||||
|
||||
expect(query).toEqual({
|
||||
sql: `select \`id\`, \`name\` from \`userstest\` group by \`userstest\`.\`id\`, \`userstest\`.\`name\``,
|
||||
params: [],
|
||||
});
|
||||
});
|
||||
|
||||
test('build query insert with onDuplicate', async (ctx) => {
|
||||
const { db } = ctx.mysql;
|
||||
|
||||
const query = db.insert(usersTable)
|
||||
.values({ name: 'John', jsonb: ['foo', 'bar'] })
|
||||
.onDuplicateKeyUpdate({ set: { name: 'John1' } })
|
||||
.toSQL();
|
||||
|
||||
expect(query).toEqual({
|
||||
sql:
|
||||
'insert into `userstest` (`id`, `name`, `verified`, `jsonb`, `created_at`) values (default, ?, default, ?, default) on duplicate key update `name` = ?',
|
||||
params: ['John', '["foo","bar"]', 'John1'],
|
||||
});
|
||||
});
|
||||
|
||||
test('insert with onDuplicate', async (ctx) => {
|
||||
const { db } = ctx.mysql;
|
||||
|
||||
await db.insert(usersTable)
|
||||
.values({ name: 'John' });
|
||||
|
||||
await db.insert(usersTable)
|
||||
.values({ id: 1, name: 'John' })
|
||||
.onDuplicateKeyUpdate({ set: { name: 'John1' } });
|
||||
|
||||
const res = await db.select({ id: usersTable.id, name: usersTable.name }).from(usersTable).where(
|
||||
eq(usersTable.id, 1),
|
||||
);
|
||||
|
||||
expect(res).toEqual([{ id: 1, name: 'John1' }]);
|
||||
});
|
||||
|
||||
test('insert conflict', async (ctx) => {
|
||||
const { db } = ctx.mysql;
|
||||
|
||||
await db.insert(usersTable)
|
||||
.values({ name: 'John' });
|
||||
|
||||
await expect((async () => {
|
||||
db.insert(usersTable).values({ id: 1, name: 'John1' });
|
||||
})()).resolves.not.toThrowError();
|
||||
});
|
||||
|
||||
test('insert conflict with ignore', async (ctx) => {
|
||||
const { db } = ctx.mysql;
|
||||
|
||||
await db.insert(usersTable)
|
||||
.values({ name: 'John' });
|
||||
|
||||
await db.insert(usersTable)
|
||||
.ignore()
|
||||
.values({ id: 1, name: 'John1' });
|
||||
|
||||
const res = await db.select({ id: usersTable.id, name: usersTable.name }).from(usersTable).where(
|
||||
eq(usersTable.id, 1),
|
||||
);
|
||||
|
||||
expect(res).toEqual([{ id: 1, name: 'John' }]);
|
||||
});
|
||||
|
||||
test('insert sql', async (ctx) => {
|
||||
const { db } = ctx.mysql;
|
||||
|
||||
await db.insert(usersTable).values({ name: sql`${'John'}` });
|
||||
const result = await db.select({ id: usersTable.id, name: usersTable.name }).from(usersTable);
|
||||
expect(result).toEqual([{ id: 1, name: 'John' }]);
|
||||
});
|
||||
|
||||
test('partial join with alias', async (ctx) => {
|
||||
const { db } = ctx.mysql;
|
||||
const customerAlias = alias(usersTable, 'customer');
|
||||
|
||||
await db.insert(usersTable).values([{ id: 10, name: 'Ivan' }, { id: 11, name: 'Hans' }]);
|
||||
const result = await db
|
||||
.select({
|
||||
user: {
|
||||
id: usersTable.id,
|
||||
name: usersTable.name,
|
||||
},
|
||||
customer: {
|
||||
id: customerAlias.id,
|
||||
name: customerAlias.name,
|
||||
},
|
||||
}).from(usersTable)
|
||||
.leftJoin(customerAlias, eq(customerAlias.id, 11))
|
||||
.where(eq(usersTable.id, 10));
|
||||
|
||||
expect(result).toEqual([{
|
||||
user: { id: 10, name: 'Ivan' },
|
||||
customer: { id: 11, name: 'Hans' },
|
||||
}]);
|
||||
});
|
||||
|
||||
test('full join with alias', async (ctx) => {
|
||||
const { db } = ctx.mysql;
|
||||
|
||||
const mysqlTable = mysqlTableCreator((name) => `prefixed_${name}`);
|
||||
|
||||
const users = mysqlTable('users', {
|
||||
id: serial('id').primaryKey(),
|
||||
name: text('name').notNull(),
|
||||
});
|
||||
|
||||
await db.execute(sql`drop table if exists ${users}`);
|
||||
await db.execute(sql`create table ${users} (id serial primary key, name text not null)`);
|
||||
|
||||
const customers = alias(users, 'customer');
|
||||
|
||||
await db.insert(users).values([{ id: 10, name: 'Ivan' }, { id: 11, name: 'Hans' }]);
|
||||
const result = await db
|
||||
.select().from(users)
|
||||
.leftJoin(customers, eq(customers.id, 11))
|
||||
.where(eq(users.id, 10));
|
||||
|
||||
expect(result).toEqual([{
|
||||
users: {
|
||||
id: 10,
|
||||
name: 'Ivan',
|
||||
},
|
||||
customer: {
|
||||
id: 11,
|
||||
name: 'Hans',
|
||||
},
|
||||
}]);
|
||||
|
||||
await db.execute(sql`drop table ${users}`);
|
||||
});
|
||||
|
||||
test('select from alias', async (ctx) => {
|
||||
const { db } = ctx.mysql;
|
||||
|
||||
const mysqlTable = mysqlTableCreator((name) => `prefixed_${name}`);
|
||||
|
||||
const users = mysqlTable('users', {
|
||||
id: serial('id').primaryKey(),
|
||||
name: text('name').notNull(),
|
||||
});
|
||||
|
||||
await db.execute(sql`drop table if exists ${users}`);
|
||||
await db.execute(sql`create table ${users} (id serial primary key, name text not null)`);
|
||||
|
||||
const user = alias(users, 'user');
|
||||
const customers = alias(users, 'customer');
|
||||
|
||||
await db.insert(users).values([{ id: 10, name: 'Ivan' }, { id: 11, name: 'Hans' }]);
|
||||
const result = await db
|
||||
.select()
|
||||
.from(user)
|
||||
.leftJoin(customers, eq(customers.id, 11))
|
||||
.where(eq(user.id, 10));
|
||||
|
||||
expect(result).toEqual([{
|
||||
user: {
|
||||
id: 10,
|
||||
name: 'Ivan',
|
||||
},
|
||||
customer: {
|
||||
id: 11,
|
||||
name: 'Hans',
|
||||
},
|
||||
}]);
|
||||
|
||||
await db.execute(sql`drop table ${users}`);
|
||||
});
|
||||
|
||||
test('insert with spaces', async (ctx) => {
|
||||
const { db } = ctx.mysql;
|
||||
|
||||
await db.insert(usersTable).values({ name: sql`'Jo h n'` });
|
||||
const result = await db.select({ id: usersTable.id, name: usersTable.name }).from(usersTable);
|
||||
|
||||
expect(result).toEqual([{ id: 1, name: 'Jo h n' }]);
|
||||
});
|
||||
|
||||
test('prepared statement', async (ctx) => {
|
||||
const { db } = ctx.mysql;
|
||||
|
||||
await db.insert(usersTable).values({ name: 'John' });
|
||||
const statement = db.select({
|
||||
id: usersTable.id,
|
||||
name: usersTable.name,
|
||||
}).from(usersTable)
|
||||
.prepare();
|
||||
const result = await statement.execute();
|
||||
|
||||
expect(result).toEqual([{ id: 1, name: 'John' }]);
|
||||
});
|
||||
|
||||
test('prepared statement reuse', async (ctx) => {
|
||||
const { db } = ctx.mysql;
|
||||
|
||||
const stmt = db.insert(usersTable).values({
|
||||
verified: true,
|
||||
name: sql.placeholder('name'),
|
||||
}).prepare();
|
||||
|
||||
for (let i = 0; i < 10; i++) {
|
||||
await stmt.execute({ name: `John ${i}` });
|
||||
}
|
||||
|
||||
const result = await db.select({
|
||||
id: usersTable.id,
|
||||
name: usersTable.name,
|
||||
verified: usersTable.verified,
|
||||
}).from(usersTable);
|
||||
|
||||
expect(result).toEqual([
|
||||
{ id: 1, name: 'John 0', verified: true },
|
||||
{ id: 2, name: 'John 1', verified: true },
|
||||
{ id: 3, name: 'John 2', verified: true },
|
||||
{ id: 4, name: 'John 3', verified: true },
|
||||
{ id: 5, name: 'John 4', verified: true },
|
||||
{ id: 6, name: 'John 5', verified: true },
|
||||
{ id: 7, name: 'John 6', verified: true },
|
||||
{ id: 8, name: 'John 7', verified: true },
|
||||
{ id: 9, name: 'John 8', verified: true },
|
||||
{ id: 10, name: 'John 9', verified: true },
|
||||
]);
|
||||
});
|
||||
|
||||
test('prepared statement with placeholder in .where', async (ctx) => {
|
||||
const { db } = ctx.mysql;
|
||||
|
||||
await db.insert(usersTable).values({ name: 'John' });
|
||||
const stmt = db.select({
|
||||
id: usersTable.id,
|
||||
name: usersTable.name,
|
||||
}).from(usersTable)
|
||||
.where(eq(usersTable.id, sql.placeholder('id')))
|
||||
.prepare();
|
||||
const result = await stmt.execute({ id: 1 });
|
||||
|
||||
expect(result).toEqual([{ id: 1, name: 'John' }]);
|
||||
});
|
||||
|
||||
test('migrator', async (ctx) => {
|
||||
const { db } = ctx.mysql;
|
||||
|
||||
await db.execute(sql`drop table if exists cities_migration`);
|
||||
await db.execute(sql`drop table if exists users_migration`);
|
||||
await db.execute(sql`drop table if exists users12`);
|
||||
await db.execute(sql`drop table if exists __drizzle_migrations`);
|
||||
|
||||
await migrate(db, { migrationsFolder: './drizzle2/mysql' });
|
||||
|
||||
await db.insert(usersMigratorTable).values({ name: 'John', email: 'email' });
|
||||
|
||||
const result = await db.select().from(usersMigratorTable);
|
||||
|
||||
expect(result).toEqual([{ id: 1, name: 'John', email: 'email' }]);
|
||||
|
||||
await db.execute(sql`drop table cities_migration`);
|
||||
await db.execute(sql`drop table users_migration`);
|
||||
await db.execute(sql`drop table users12`);
|
||||
await db.execute(sql`drop table __drizzle_migrations`);
|
||||
});
|
||||
|
||||
test('insert via db.execute + select via db.execute', async (ctx) => {
|
||||
const { db } = ctx.mysql;
|
||||
|
||||
await db.execute(sql`insert into ${usersTable} (${new Name(usersTable.name.name)}) values (${'John'})`);
|
||||
|
||||
const result = await db.execute<{ id: number; name: string }>(sql`select id, name from ${usersTable}`);
|
||||
expect(result[0]).toEqual([{ id: 1, name: 'John' }]);
|
||||
});
|
||||
|
||||
test('insert via db.execute w/ query builder', async (ctx) => {
|
||||
const { db } = ctx.mysql;
|
||||
|
||||
const inserted = await db.execute(
|
||||
db.insert(usersTable).values({ name: 'John' }),
|
||||
);
|
||||
expect(inserted[0].affectedRows).toBe(1);
|
||||
});
|
||||
|
||||
test('insert + select all possible dates', async (ctx) => {
|
||||
const { db } = ctx.mysql;
|
||||
|
||||
const date = new Date('2022-11-11');
|
||||
|
||||
await db.insert(datesTable).values({
|
||||
date: date,
|
||||
dateAsString: '2022-11-11',
|
||||
time: '12:12:12',
|
||||
datetime: date,
|
||||
year: 22,
|
||||
datetimeAsString: '2022-11-11 12:12:12',
|
||||
});
|
||||
|
||||
const res = await db.select().from(datesTable);
|
||||
|
||||
expect(res[0]?.date).toBeInstanceOf(Date);
|
||||
expect(res[0]?.datetime).toBeInstanceOf(Date);
|
||||
expect(res[0]?.dateAsString).toBeTypeOf('string');
|
||||
expect(res[0]?.datetimeAsString).toBeTypeOf('string');
|
||||
|
||||
expect(res).toEqual([{
|
||||
date: toLocalDate(new Date('2022-11-11')),
|
||||
dateAsString: '2022-11-11',
|
||||
time: '12:12:12',
|
||||
datetime: new Date('2022-11-11'),
|
||||
year: 2022,
|
||||
datetimeAsString: '2022-11-11 12:12:12',
|
||||
}]);
|
||||
});
|
||||
|
||||
const tableWithEnums = mysqlTable('enums_test_case', {
|
||||
id: serial('id').primaryKey(),
|
||||
enum1: mysqlEnum('enum1', ['a', 'b', 'c']).notNull(),
|
||||
enum2: mysqlEnum('enum2', ['a', 'b', 'c']).default('a'),
|
||||
enum3: mysqlEnum('enum3', ['a', 'b', 'c']).notNull().default('b'),
|
||||
});
|
||||
|
||||
test('Mysql enum test case #1', async (ctx) => {
|
||||
const { db } = ctx.mysql;
|
||||
|
||||
await db.execute(sql`drop table if exists \`enums_test_case\``);
|
||||
|
||||
await db.execute(sql`
|
||||
create table \`enums_test_case\` (
|
||||
\`id\` serial primary key,
|
||||
\`enum1\` ENUM('a', 'b', 'c') not null,
|
||||
\`enum2\` ENUM('a', 'b', 'c') default 'a',
|
||||
\`enum3\` ENUM('a', 'b', 'c') not null default 'b'
|
||||
)
|
||||
`);
|
||||
|
||||
await db.insert(tableWithEnums).values([
|
||||
{ id: 1, enum1: 'a', enum2: 'b', enum3: 'c' },
|
||||
{ id: 2, enum1: 'a', enum3: 'c' },
|
||||
{ id: 3, enum1: 'a' },
|
||||
]);
|
||||
|
||||
const res = await db.select().from(tableWithEnums);
|
||||
|
||||
await db.execute(sql`drop table \`enums_test_case\``);
|
||||
|
||||
expect(res).toEqual([
|
||||
{ id: 1, enum1: 'a', enum2: 'b', enum3: 'c' },
|
||||
{ id: 2, enum1: 'a', enum2: 'a', enum3: 'c' },
|
||||
{ id: 3, enum1: 'a', enum2: 'a', enum3: 'b' },
|
||||
]);
|
||||
});
|
||||
|
||||
test('custom binary', async (ctx) => {
|
||||
const { db } = ctx.mysql;
|
||||
|
||||
const id = uuid().replace(/-/g, '');
|
||||
await db.insert(testTable).values({
|
||||
id,
|
||||
sqlId: sql`UNHEX(${id})`,
|
||||
rawId: id,
|
||||
});
|
||||
|
||||
const res = await db.select().from(testTable);
|
||||
|
||||
expect(res).toEqual([{
|
||||
id,
|
||||
sqlId: Buffer.from(id, 'hex').toString(),
|
||||
rawId: id,
|
||||
}]);
|
||||
});
|
||||
@@ -0,0 +1,88 @@
|
||||
import { Client } from '@planetscale/database';
|
||||
import type { PlanetScaleDatabase } from 'drizzle-orm/planetscale-serverless';
|
||||
import { drizzle } from 'drizzle-orm/planetscale-serverless';
|
||||
import { beforeAll, beforeEach } from 'vitest';
|
||||
import { skipTests } from '~/common';
|
||||
import { tests } from './mysql-common';
|
||||
import { TestCache, TestGlobalCache, tests as cacheTests } from './mysql-common-cache';
|
||||
|
||||
const ENABLE_LOGGING = false;
|
||||
|
||||
let db: PlanetScaleDatabase;
|
||||
let dbGlobalCached: PlanetScaleDatabase;
|
||||
let cachedDb: PlanetScaleDatabase;
|
||||
|
||||
beforeAll(async () => {
|
||||
const client = new Client({ url: process.env['PLANETSCALE_CONNECTION_STRING']! });
|
||||
db = drizzle(client, { logger: ENABLE_LOGGING });
|
||||
cachedDb = drizzle(client, { logger: ENABLE_LOGGING, cache: new TestCache() });
|
||||
dbGlobalCached = drizzle(client, { logger: ENABLE_LOGGING, cache: new TestGlobalCache() });
|
||||
});
|
||||
|
||||
beforeEach((ctx) => {
|
||||
ctx.mysql = {
|
||||
db,
|
||||
};
|
||||
ctx.cachedMySQL = {
|
||||
db: cachedDb,
|
||||
dbGlobalCached,
|
||||
};
|
||||
});
|
||||
|
||||
skipTests([
|
||||
'mySchema :: view',
|
||||
'mySchema :: select from tables with same name from different schema using alias',
|
||||
'mySchema :: prepared statement with placeholder in .where',
|
||||
'mySchema :: insert with spaces',
|
||||
'mySchema :: select with group by as column + sql',
|
||||
'mySchema :: select with group by as field',
|
||||
'mySchema :: insert many',
|
||||
'mySchema :: insert with overridden default values',
|
||||
'mySchema :: insert + select',
|
||||
'mySchema :: delete with returning all fields',
|
||||
'mySchema :: update with returning partial',
|
||||
'mySchema :: delete returning sql',
|
||||
'mySchema :: insert returning sql',
|
||||
'mySchema :: select typed sql',
|
||||
'mySchema :: select sql',
|
||||
'mySchema :: select all fields',
|
||||
'test $onUpdateFn and $onUpdate works updating',
|
||||
'test $onUpdateFn and $onUpdate works as $default',
|
||||
'set operations (mixed all) as function with subquery',
|
||||
'set operations (mixed) from query builder',
|
||||
'set operations (except all) as function',
|
||||
'set operations (except all) from query builder',
|
||||
'set operations (except) as function',
|
||||
'set operations (except) from query builder',
|
||||
'set operations (intersect all) as function',
|
||||
'set operations (intersect all) from query builder',
|
||||
'set operations (intersect) as function',
|
||||
'set operations (intersect) from query builder',
|
||||
'select iterator w/ prepared statement',
|
||||
'select iterator',
|
||||
'subquery with view',
|
||||
'join on aliased sql from with clause',
|
||||
'with ... delete',
|
||||
'with ... update',
|
||||
'with ... select',
|
||||
|
||||
// to redefine in this file
|
||||
'utc config for datetime',
|
||||
'transaction',
|
||||
'transaction with options (set isolationLevel)',
|
||||
'having',
|
||||
'select count()',
|
||||
'insert via db.execute w/ query builder',
|
||||
'insert via db.execute + select via db.execute',
|
||||
'insert many with returning',
|
||||
'delete with returning partial',
|
||||
'delete with returning all fields',
|
||||
'update with returning partial',
|
||||
'update with returning all fields',
|
||||
'update returning sql',
|
||||
'delete returning sql',
|
||||
'insert returning sql',
|
||||
]);
|
||||
|
||||
tests('planetscale');
|
||||
cacheTests();
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,139 @@
|
||||
import retry from 'async-retry';
|
||||
import type { MySqlRemoteDatabase } from 'drizzle-orm/mysql-proxy';
|
||||
import { drizzle as proxyDrizzle } from 'drizzle-orm/mysql-proxy';
|
||||
import * as mysql from 'mysql2/promise';
|
||||
import { afterAll, beforeAll, beforeEach } from 'vitest';
|
||||
import { skipTests } from '~/common';
|
||||
import { createDockerDB, tests } from './mysql-common';
|
||||
|
||||
const ENABLE_LOGGING = false;
|
||||
|
||||
// eslint-disable-next-line drizzle-internal/require-entity-kind
|
||||
class ServerSimulator {
|
||||
constructor(private db: mysql.Connection) {}
|
||||
|
||||
async query(sql: string, params: any[], method: 'all' | 'execute') {
|
||||
if (method === 'all') {
|
||||
try {
|
||||
const result = await this.db.query({
|
||||
sql,
|
||||
values: params,
|
||||
rowsAsArray: true,
|
||||
typeCast: function(field: any, next: any) {
|
||||
if (field.type === 'TIMESTAMP' || field.type === 'DATETIME' || field.type === 'DATE') {
|
||||
return field.string();
|
||||
}
|
||||
return next();
|
||||
},
|
||||
});
|
||||
|
||||
return { data: result[0] as any };
|
||||
} catch (e: any) {
|
||||
return { error: e };
|
||||
}
|
||||
} else if (method === 'execute') {
|
||||
try {
|
||||
const result = await this.db.query({
|
||||
sql,
|
||||
values: params,
|
||||
typeCast: function(field: any, next: any) {
|
||||
if (field.type === 'TIMESTAMP' || field.type === 'DATETIME' || field.type === 'DATE') {
|
||||
return field.string();
|
||||
}
|
||||
return next();
|
||||
},
|
||||
});
|
||||
|
||||
return { data: result as any };
|
||||
} catch (e: any) {
|
||||
return { error: e };
|
||||
}
|
||||
} else {
|
||||
return { error: 'Unknown method value' };
|
||||
}
|
||||
}
|
||||
|
||||
async migrations(queries: string[]) {
|
||||
await this.db.query('START TRANSACTION');
|
||||
try {
|
||||
for (const query of queries) {
|
||||
await this.db.query(query);
|
||||
}
|
||||
await this.db.query('COMMIT');
|
||||
} catch (e) {
|
||||
await this.db.query('ROLLBACK');
|
||||
throw e;
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
let db: MySqlRemoteDatabase;
|
||||
let client: mysql.Connection;
|
||||
let serverSimulator: ServerSimulator;
|
||||
|
||||
beforeAll(async () => {
|
||||
let connectionString;
|
||||
if (process.env['MYSQL_CONNECTION_STRING']) {
|
||||
connectionString = process.env['MYSQL_CONNECTION_STRING'];
|
||||
} else {
|
||||
const { connectionString: conStr } = await createDockerDB();
|
||||
connectionString = conStr;
|
||||
}
|
||||
client = await retry(async () => {
|
||||
client = await mysql.createConnection({
|
||||
uri: connectionString,
|
||||
supportBigNumbers: true,
|
||||
});
|
||||
await client.connect();
|
||||
return client;
|
||||
}, {
|
||||
retries: 20,
|
||||
factor: 1,
|
||||
minTimeout: 250,
|
||||
maxTimeout: 250,
|
||||
randomize: false,
|
||||
onRetry() {
|
||||
client?.end();
|
||||
},
|
||||
});
|
||||
serverSimulator = new ServerSimulator(client);
|
||||
db = proxyDrizzle(async (sql, params, method) => {
|
||||
try {
|
||||
const response = await serverSimulator.query(sql, params, method);
|
||||
|
||||
if (response.error !== undefined) {
|
||||
throw response.error;
|
||||
}
|
||||
|
||||
return { rows: response.data };
|
||||
} catch (e: any) {
|
||||
console.error('Error from mysql proxy server:', e.message);
|
||||
throw e;
|
||||
}
|
||||
}, { logger: ENABLE_LOGGING });
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await client?.end();
|
||||
});
|
||||
|
||||
beforeEach((ctx) => {
|
||||
ctx.mysql = {
|
||||
db,
|
||||
};
|
||||
});
|
||||
|
||||
skipTests([
|
||||
'select iterator w/ prepared statement',
|
||||
'select iterator',
|
||||
'nested transaction rollback',
|
||||
'nested transaction',
|
||||
'transaction rollback',
|
||||
'transaction',
|
||||
'transaction with options (set isolationLevel)',
|
||||
'migrator',
|
||||
]);
|
||||
|
||||
tests();
|
||||
@@ -0,0 +1,61 @@
|
||||
import retry from 'async-retry';
|
||||
import type { MySql2Database } from 'drizzle-orm/mysql2';
|
||||
import { drizzle } from 'drizzle-orm/mysql2';
|
||||
import * as mysql from 'mysql2/promise';
|
||||
import { afterAll, beforeAll, beforeEach } from 'vitest';
|
||||
import { createDockerDB, tests } from './mysql-common';
|
||||
import { TestCache, TestGlobalCache, tests as cacheTests } from './mysql-common-cache';
|
||||
|
||||
const ENABLE_LOGGING = false;
|
||||
|
||||
let db: MySql2Database;
|
||||
let dbGlobalCached: MySql2Database;
|
||||
let cachedDb: MySql2Database;
|
||||
let client: mysql.Connection;
|
||||
|
||||
beforeAll(async () => {
|
||||
let connectionString;
|
||||
if (process.env['MYSQL_CONNECTION_STRING']) {
|
||||
connectionString = process.env['MYSQL_CONNECTION_STRING'];
|
||||
} else {
|
||||
const { connectionString: conStr } = await createDockerDB();
|
||||
connectionString = conStr;
|
||||
}
|
||||
client = await retry(async () => {
|
||||
client = await mysql.createConnection({
|
||||
uri: connectionString!,
|
||||
supportBigNumbers: true,
|
||||
});
|
||||
await client.connect();
|
||||
return client;
|
||||
}, {
|
||||
retries: 20,
|
||||
factor: 1,
|
||||
minTimeout: 250,
|
||||
maxTimeout: 250,
|
||||
randomize: false,
|
||||
onRetry() {
|
||||
client?.end();
|
||||
},
|
||||
});
|
||||
db = drizzle(client, { logger: ENABLE_LOGGING });
|
||||
cachedDb = drizzle(client, { logger: ENABLE_LOGGING, cache: new TestCache() });
|
||||
dbGlobalCached = drizzle(client, { logger: ENABLE_LOGGING, cache: new TestGlobalCache() });
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await client?.end();
|
||||
});
|
||||
|
||||
beforeEach((ctx) => {
|
||||
ctx.mysql = {
|
||||
db,
|
||||
};
|
||||
ctx.cachedMySQL = {
|
||||
db: cachedDb,
|
||||
dbGlobalCached,
|
||||
};
|
||||
});
|
||||
|
||||
cacheTests();
|
||||
tests();
|
||||
@@ -0,0 +1,76 @@
|
||||
import 'dotenv/config';
|
||||
|
||||
import { connect } from '@tidbcloud/serverless';
|
||||
import type { TiDBServerlessDatabase } from 'drizzle-orm/tidb-serverless';
|
||||
import { drizzle } from 'drizzle-orm/tidb-serverless';
|
||||
import { beforeAll, beforeEach } from 'vitest';
|
||||
import { skipTests } from '~/common.ts';
|
||||
import { tests } from './mysql-common.ts';
|
||||
|
||||
const ENABLE_LOGGING = false;
|
||||
|
||||
let db: TiDBServerlessDatabase;
|
||||
|
||||
beforeAll(async () => {
|
||||
const connectionString = process.env['TIDB_CONNECTION_STRING'];
|
||||
if (!connectionString) {
|
||||
throw new Error('TIDB_CONNECTION_STRING is not set');
|
||||
}
|
||||
|
||||
const client = connect({ url: connectionString });
|
||||
db = drizzle(client!, { logger: ENABLE_LOGGING });
|
||||
});
|
||||
|
||||
beforeEach((ctx) => {
|
||||
ctx.mysql = {
|
||||
db,
|
||||
};
|
||||
});
|
||||
|
||||
skipTests([
|
||||
'mySchema :: select with group by as field',
|
||||
'mySchema :: delete with returning all fields',
|
||||
'mySchema :: update with returning partial',
|
||||
'mySchema :: delete returning sql',
|
||||
'mySchema :: insert returning sql',
|
||||
'test $onUpdateFn and $onUpdate works updating',
|
||||
'set operations (mixed all) as function with subquery',
|
||||
'set operations (union) from query builder with subquery',
|
||||
'join on aliased sql from with clause',
|
||||
'join on aliased sql from select',
|
||||
'select from raw sql with joins',
|
||||
'select from raw sql',
|
||||
'having',
|
||||
'select count()',
|
||||
'with ... select',
|
||||
'insert via db.execute w/ query builder',
|
||||
'insert via db.execute + select via db.execute',
|
||||
'select with group by as sql',
|
||||
'select with group by as field',
|
||||
'insert many with returning',
|
||||
'delete with returning partial',
|
||||
'delete with returning all fields',
|
||||
'update with returning partial',
|
||||
'update with returning all fields',
|
||||
'update returning sql',
|
||||
'delete returning sql',
|
||||
'insert returning sql',
|
||||
|
||||
// not supported
|
||||
'set operations (except all) as function',
|
||||
'set operations (except all) from query builder',
|
||||
'set operations (intersect all) as function',
|
||||
'set operations (intersect all) from query builder',
|
||||
'set operations (union all) as function',
|
||||
'tc config for datetime',
|
||||
'select iterator w/ prepared statement',
|
||||
'select iterator',
|
||||
'transaction',
|
||||
'transaction with options (set isolationLevel)',
|
||||
'Insert all defaults in multiple rows',
|
||||
'Insert all defaults in 1 row',
|
||||
'$default with empty array',
|
||||
'utc config for datetime',
|
||||
]);
|
||||
|
||||
tests();
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,69 @@
|
||||
import { neon, type NeonQueryFunction } from '@neondatabase/serverless';
|
||||
import { drizzle, type NeonHttpDatabase } from 'drizzle-orm/neon-http';
|
||||
import { beforeAll, beforeEach, expect, test } from 'vitest';
|
||||
import {
|
||||
commentLikesConfig,
|
||||
commentsConfig,
|
||||
commentsTable,
|
||||
groupsConfig,
|
||||
groupsTable,
|
||||
postsConfig,
|
||||
postsTable,
|
||||
usersConfig,
|
||||
usersTable,
|
||||
usersToGroupsConfig,
|
||||
usersToGroupsTable,
|
||||
} from './neon-http-batch';
|
||||
import { TestCache, TestGlobalCache } from './pg-common-cache';
|
||||
|
||||
const ENABLE_LOGGING = false;
|
||||
|
||||
export const schema = {
|
||||
usersTable,
|
||||
postsTable,
|
||||
commentsTable,
|
||||
usersToGroupsTable,
|
||||
groupsTable,
|
||||
commentLikesConfig,
|
||||
commentsConfig,
|
||||
postsConfig,
|
||||
usersToGroupsConfig,
|
||||
groupsConfig,
|
||||
usersConfig,
|
||||
};
|
||||
|
||||
let db: NeonHttpDatabase<typeof schema>;
|
||||
let client: NeonQueryFunction<false, true>;
|
||||
let dbGlobalCached: NeonHttpDatabase;
|
||||
let cachedDb: NeonHttpDatabase;
|
||||
|
||||
beforeAll(async () => {
|
||||
const connectionString = process.env['NEON_HTTP_CONNECTION_STRING'];
|
||||
if (!connectionString) {
|
||||
throw new Error('NEON_HTTP_CONNECTION_STRING is not defined');
|
||||
}
|
||||
client = neon(connectionString);
|
||||
db = drizzle(client, { schema, logger: ENABLE_LOGGING });
|
||||
cachedDb = drizzle(client, {
|
||||
logger: ENABLE_LOGGING,
|
||||
cache: new TestCache(),
|
||||
});
|
||||
dbGlobalCached = drizzle(client, {
|
||||
logger: ENABLE_LOGGING,
|
||||
cache: new TestGlobalCache(),
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach((ctx) => {
|
||||
ctx.neonPg = {
|
||||
db,
|
||||
};
|
||||
ctx.cachedPg = {
|
||||
db: cachedDb,
|
||||
dbGlobalCached,
|
||||
};
|
||||
});
|
||||
|
||||
test('skip', async () => {
|
||||
expect(1).toBe(1);
|
||||
});
|
||||
@@ -0,0 +1,556 @@
|
||||
import Docker from 'dockerode';
|
||||
import type { InferSelectModel } from 'drizzle-orm';
|
||||
import { eq, relations, sql } from 'drizzle-orm';
|
||||
import type { NeonHttpQueryResult } from 'drizzle-orm/neon-http';
|
||||
import { integer, pgTable, primaryKey, serial, text, timestamp } from 'drizzle-orm/pg-core';
|
||||
import type { AnyPgColumn } from 'drizzle-orm/pg-core';
|
||||
import getPort from 'get-port';
|
||||
import { v4 as uuidV4 } from 'uuid';
|
||||
import { afterAll, beforeEach, describe, expect, expectTypeOf, test } from 'vitest';
|
||||
|
||||
export const usersTable = pgTable('users', {
|
||||
id: serial('id').primaryKey(),
|
||||
name: text('name').notNull(),
|
||||
verified: integer('verified').notNull().default(0),
|
||||
invitedBy: integer('invited_by').references((): AnyPgColumn => usersTable.id),
|
||||
});
|
||||
export const usersConfig = relations(usersTable, ({ one, many }) => ({
|
||||
invitee: one(usersTable, {
|
||||
fields: [usersTable.invitedBy],
|
||||
references: [usersTable.id],
|
||||
}),
|
||||
usersToGroups: many(usersToGroupsTable),
|
||||
posts: many(postsTable),
|
||||
}));
|
||||
|
||||
export const groupsTable = pgTable('groups', {
|
||||
id: serial('id').primaryKey(),
|
||||
name: text('name').notNull(),
|
||||
description: text('description'),
|
||||
});
|
||||
export const groupsConfig = relations(groupsTable, ({ many }) => ({
|
||||
usersToGroups: many(usersToGroupsTable),
|
||||
}));
|
||||
|
||||
export const usersToGroupsTable = pgTable(
|
||||
'users_to_groups',
|
||||
{
|
||||
id: serial('id'),
|
||||
userId: integer('user_id').notNull().references(() => usersTable.id),
|
||||
groupId: integer('group_id').notNull().references(() => groupsTable.id),
|
||||
},
|
||||
(t) => ({
|
||||
pk: primaryKey({ columns: [t.userId, t.groupId] }),
|
||||
}),
|
||||
);
|
||||
export const usersToGroupsConfig = relations(usersToGroupsTable, ({ one }) => ({
|
||||
group: one(groupsTable, {
|
||||
fields: [usersToGroupsTable.groupId],
|
||||
references: [groupsTable.id],
|
||||
}),
|
||||
user: one(usersTable, {
|
||||
fields: [usersToGroupsTable.userId],
|
||||
references: [usersTable.id],
|
||||
}),
|
||||
}));
|
||||
|
||||
export const postsTable = pgTable('posts', {
|
||||
id: serial('id').primaryKey(),
|
||||
content: text('content').notNull(),
|
||||
ownerId: integer('owner_id').references(() => usersTable.id),
|
||||
createdAt: timestamp('created_at').notNull().defaultNow(),
|
||||
});
|
||||
export const postsConfig = relations(postsTable, ({ one, many }) => ({
|
||||
author: one(usersTable, {
|
||||
fields: [postsTable.ownerId],
|
||||
references: [usersTable.id],
|
||||
}),
|
||||
comments: many(commentsTable),
|
||||
}));
|
||||
|
||||
export const commentsTable = pgTable('comments', {
|
||||
id: serial('id').primaryKey(),
|
||||
content: text('content').notNull(),
|
||||
creator: integer('creator').references(() => usersTable.id),
|
||||
postId: integer('post_id').references(() => postsTable.id),
|
||||
createdAt: timestamp('created_at').notNull().defaultNow(),
|
||||
});
|
||||
export const commentsConfig = relations(commentsTable, ({ one, many }) => ({
|
||||
post: one(postsTable, {
|
||||
fields: [commentsTable.postId],
|
||||
references: [postsTable.id],
|
||||
}),
|
||||
author: one(usersTable, {
|
||||
fields: [commentsTable.creator],
|
||||
references: [usersTable.id],
|
||||
}),
|
||||
likes: many(commentLikesTable),
|
||||
}));
|
||||
|
||||
export const commentLikesTable = pgTable('comment_likes', {
|
||||
id: serial('id').primaryKey(),
|
||||
creator: integer('creator').references(() => usersTable.id),
|
||||
commentId: integer('comment_id').references(() => commentsTable.id),
|
||||
createdAt: timestamp('created_at').notNull().defaultNow(),
|
||||
});
|
||||
export const commentLikesConfig = relations(commentLikesTable, ({ one }) => ({
|
||||
comment: one(commentsTable, {
|
||||
fields: [commentLikesTable.commentId],
|
||||
references: [commentsTable.id],
|
||||
}),
|
||||
author: one(usersTable, {
|
||||
fields: [commentLikesTable.creator],
|
||||
references: [usersTable.id],
|
||||
}),
|
||||
}));
|
||||
|
||||
let pgContainer: Docker.Container;
|
||||
export async function createDockerDB(): Promise<string> {
|
||||
const docker = new Docker();
|
||||
const port = await getPort({ port: 5432 });
|
||||
const image = 'postgres:14';
|
||||
|
||||
const pullStream = await docker.pull(image);
|
||||
await new Promise((resolve, reject) =>
|
||||
docker.modem.followProgress(pullStream, (err) => (err ? reject(err) : resolve(err)))
|
||||
);
|
||||
|
||||
pgContainer = await docker.createContainer({
|
||||
Image: image,
|
||||
Env: ['POSTGRES_PASSWORD=postgres', 'POSTGRES_USER=postgres', 'POSTGRES_DB=postgres'],
|
||||
name: `drizzle-integration-tests-${uuidV4()}`,
|
||||
HostConfig: {
|
||||
AutoRemove: true,
|
||||
PortBindings: {
|
||||
'5432/tcp': [{ HostPort: `${port}` }],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await pgContainer.start();
|
||||
|
||||
return `postgres://postgres:postgres@localhost:${port}/postgres`;
|
||||
}
|
||||
|
||||
afterAll(async () => {
|
||||
await pgContainer?.stop().catch(console.error);
|
||||
});
|
||||
|
||||
export function tests() {
|
||||
describe('common', () => {
|
||||
beforeEach(async (ctx) => {
|
||||
const { db } = ctx.pg;
|
||||
await db.execute(sql`drop schema if exists public cascade`);
|
||||
await db.execute(sql`drop schema if exists mySchema cascade`);
|
||||
|
||||
await db.execute(
|
||||
sql`
|
||||
create table users (
|
||||
id serial primary key,
|
||||
name text not null,
|
||||
verified int not null default 0,
|
||||
invited_by int references users(id)
|
||||
)
|
||||
`,
|
||||
);
|
||||
await db.execute(
|
||||
sql`
|
||||
create table groups (
|
||||
id serial primary key,
|
||||
name text not null,
|
||||
description text
|
||||
)
|
||||
`,
|
||||
);
|
||||
await db.execute(
|
||||
sql`
|
||||
create table users_to_groups (
|
||||
id serial,
|
||||
user_id int not null references users(id),
|
||||
group_id int not null references groups(id),
|
||||
primary key (user_id, group_id)
|
||||
)
|
||||
`,
|
||||
);
|
||||
await db.execute(
|
||||
sql`
|
||||
create table posts (
|
||||
id serial primary key,
|
||||
content text not null,
|
||||
owner_id int references users(id),
|
||||
created_at timestamp not null default now()
|
||||
)
|
||||
`,
|
||||
);
|
||||
await db.execute(
|
||||
sql`
|
||||
create table comments (
|
||||
id serial primary key,
|
||||
content text not null,
|
||||
creator int references users(id),
|
||||
post_id int references posts(id),
|
||||
created_at timestamp not null default now()
|
||||
)
|
||||
`,
|
||||
);
|
||||
await db.execute(
|
||||
sql`
|
||||
create table comment_likes (
|
||||
id serial primary key,
|
||||
creator int references users(id),
|
||||
comment_id int references comments(id),
|
||||
created_at timestamp not null default now()
|
||||
)
|
||||
`,
|
||||
);
|
||||
});
|
||||
|
||||
test('batch api example', async (ctx) => {
|
||||
const { db } = ctx.neonPg;
|
||||
|
||||
const batchResponse = await db.batch([
|
||||
db.insert(usersTable).values({ id: 1, name: 'John' }).returning({
|
||||
id: usersTable.id,
|
||||
invitedBy: usersTable.invitedBy,
|
||||
}),
|
||||
db.insert(usersTable).values({ id: 2, name: 'Dan' }),
|
||||
db.select().from(usersTable),
|
||||
]);
|
||||
|
||||
expectTypeOf(batchResponse).toEqualTypeOf<[
|
||||
{
|
||||
id: number;
|
||||
invitedBy: number | null;
|
||||
}[],
|
||||
NeonHttpQueryResult<never>,
|
||||
{
|
||||
id: number;
|
||||
name: string;
|
||||
verified: number;
|
||||
invitedBy: number | null;
|
||||
}[],
|
||||
]>();
|
||||
|
||||
expect(batchResponse.length).eq(3);
|
||||
|
||||
expect(batchResponse[0]).toEqual([{
|
||||
id: 1,
|
||||
invitedBy: null,
|
||||
}]);
|
||||
|
||||
expect(batchResponse[1]).toMatchObject({ rows: [], rowCount: 1 });
|
||||
|
||||
expect(batchResponse[2]).toEqual([
|
||||
{ id: 1, name: 'John', verified: 0, invitedBy: null },
|
||||
{ id: 2, name: 'Dan', verified: 0, invitedBy: null },
|
||||
]);
|
||||
});
|
||||
|
||||
// batch api only relational many
|
||||
test('insert + findMany', async (ctx) => {
|
||||
const { db } = ctx.neonPg;
|
||||
|
||||
const batchResponse = await db.batch([
|
||||
db.insert(usersTable).values({ id: 1, name: 'John' }).returning({ id: usersTable.id }),
|
||||
db.insert(usersTable).values({ id: 2, name: 'Dan' }),
|
||||
db.query.usersTable.findMany({}),
|
||||
]);
|
||||
|
||||
expectTypeOf(batchResponse).toEqualTypeOf<[
|
||||
{
|
||||
id: number;
|
||||
}[],
|
||||
NeonHttpQueryResult<never>,
|
||||
{
|
||||
id: number;
|
||||
name: string;
|
||||
verified: number;
|
||||
invitedBy: number | null;
|
||||
}[],
|
||||
]>();
|
||||
|
||||
expect(batchResponse.length).eq(3);
|
||||
|
||||
expect(batchResponse[0]).toEqual([{
|
||||
id: 1,
|
||||
}]);
|
||||
|
||||
expect(batchResponse[1]).toMatchObject({ rows: [], rowCount: 1 });
|
||||
|
||||
expect(batchResponse[2]).toEqual([
|
||||
{ id: 1, name: 'John', verified: 0, invitedBy: null },
|
||||
{ id: 2, name: 'Dan', verified: 0, invitedBy: null },
|
||||
]);
|
||||
});
|
||||
|
||||
// batch api relational many + one
|
||||
test('insert + findMany + findFirst', async (ctx) => {
|
||||
const { db } = ctx.neonPg;
|
||||
|
||||
const batchResponse = await db.batch([
|
||||
db.insert(usersTable).values({ id: 1, name: 'John' }).returning({ id: usersTable.id }),
|
||||
db.insert(usersTable).values({ id: 2, name: 'Dan' }),
|
||||
db.query.usersTable.findMany({}),
|
||||
db.query.usersTable.findFirst({}),
|
||||
]);
|
||||
|
||||
expectTypeOf(batchResponse).toEqualTypeOf<[
|
||||
{
|
||||
id: number;
|
||||
}[],
|
||||
NeonHttpQueryResult<never>,
|
||||
{
|
||||
id: number;
|
||||
name: string;
|
||||
verified: number;
|
||||
invitedBy: number | null;
|
||||
}[],
|
||||
{
|
||||
id: number;
|
||||
name: string;
|
||||
verified: number;
|
||||
invitedBy: number | null;
|
||||
} | undefined,
|
||||
]>();
|
||||
|
||||
expect(batchResponse.length).eq(4);
|
||||
|
||||
expect(batchResponse[0]).toEqual([{
|
||||
id: 1,
|
||||
}]);
|
||||
|
||||
expect(batchResponse[1]).toMatchObject({ rows: [], rowCount: 1 });
|
||||
|
||||
expect(batchResponse[2]).toEqual([
|
||||
{ id: 1, name: 'John', verified: 0, invitedBy: null },
|
||||
{ id: 2, name: 'Dan', verified: 0, invitedBy: null },
|
||||
]);
|
||||
|
||||
expect(batchResponse[3]).toEqual(
|
||||
{ id: 1, name: 'John', verified: 0, invitedBy: null },
|
||||
);
|
||||
});
|
||||
|
||||
test('insert + db.execute', async (ctx) => {
|
||||
const { db } = ctx.neonPg;
|
||||
|
||||
const batchResponse = await db.batch([
|
||||
db.insert(usersTable).values({ id: 1, name: 'John' }).returning({ id: usersTable.id }),
|
||||
db.execute(sql`insert into users (id, name) values (2, 'Dan')`),
|
||||
]);
|
||||
|
||||
expectTypeOf(batchResponse).toEqualTypeOf<[
|
||||
{
|
||||
id: number;
|
||||
}[],
|
||||
NeonHttpQueryResult<Record<string, unknown>>,
|
||||
]>();
|
||||
|
||||
expect(batchResponse.length).eq(2);
|
||||
|
||||
expect(batchResponse[0]).toEqual([{
|
||||
id: 1,
|
||||
}]);
|
||||
|
||||
expect(batchResponse[1]).toMatchObject({ rowAsArray: false, rows: [], rowCount: 1 });
|
||||
});
|
||||
|
||||
// batch api combined rqb + raw call
|
||||
test('insert + findManyWith + db.all', async (ctx) => {
|
||||
const { db } = ctx.neonPg;
|
||||
|
||||
const batchResponse = await db.batch([
|
||||
db.insert(usersTable).values({ id: 1, name: 'John' }).returning({ id: usersTable.id }),
|
||||
db.insert(usersTable).values({ id: 2, name: 'Dan' }),
|
||||
db.query.usersTable.findMany({}),
|
||||
db.execute<typeof usersTable.$inferSelect>(sql`select * from users`),
|
||||
]);
|
||||
|
||||
expectTypeOf(batchResponse).toEqualTypeOf<[
|
||||
{
|
||||
id: number;
|
||||
}[],
|
||||
NeonHttpQueryResult<never>,
|
||||
{
|
||||
id: number;
|
||||
name: string;
|
||||
verified: number;
|
||||
invitedBy: number | null;
|
||||
}[],
|
||||
NeonHttpQueryResult<{
|
||||
id: number;
|
||||
name: string;
|
||||
verified: number;
|
||||
invitedBy: number | null;
|
||||
}>,
|
||||
]>();
|
||||
|
||||
expect(batchResponse.length).eq(4);
|
||||
|
||||
expect(batchResponse[0]).toEqual([{
|
||||
id: 1,
|
||||
}]);
|
||||
|
||||
expect(batchResponse[1]).toMatchObject({ rowAsArray: true, rows: [], rowCount: 1 });
|
||||
|
||||
expect(batchResponse[2]).toEqual([
|
||||
{ id: 1, name: 'John', verified: 0, invitedBy: null },
|
||||
{ id: 2, name: 'Dan', verified: 0, invitedBy: null },
|
||||
]);
|
||||
|
||||
expect(batchResponse[3]).toMatchObject({
|
||||
rows: [
|
||||
{ id: 1, name: 'John', verified: 0, invited_by: null },
|
||||
{ id: 2, name: 'Dan', verified: 0, invited_by: null },
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
// batch api for insert + update + select
|
||||
test('insert + update + select + select partial', async (ctx) => {
|
||||
const { db } = ctx.neonPg;
|
||||
|
||||
const batchResponse = await db.batch([
|
||||
db.insert(usersTable).values({ id: 1, name: 'John' }).returning({ id: usersTable.id }),
|
||||
db.update(usersTable).set({ name: 'Dan' }).where(eq(usersTable.id, 1)),
|
||||
db.query.usersTable.findMany({}),
|
||||
db.select().from(usersTable).where(eq(usersTable.id, 1)),
|
||||
db.select({ id: usersTable.id, invitedBy: usersTable.invitedBy }).from(usersTable),
|
||||
]);
|
||||
|
||||
expectTypeOf(batchResponse).toEqualTypeOf<[
|
||||
{
|
||||
id: number;
|
||||
}[],
|
||||
NeonHttpQueryResult<never>,
|
||||
{
|
||||
id: number;
|
||||
name: string;
|
||||
verified: number;
|
||||
invitedBy: number | null;
|
||||
}[],
|
||||
{
|
||||
id: number;
|
||||
name: string;
|
||||
verified: number;
|
||||
invitedBy: number | null;
|
||||
}[],
|
||||
{
|
||||
id: number;
|
||||
invitedBy: number | null;
|
||||
}[],
|
||||
]>();
|
||||
|
||||
expect(batchResponse.length).eq(5);
|
||||
|
||||
expect(batchResponse[0]).toEqual([{
|
||||
id: 1,
|
||||
}]);
|
||||
|
||||
expect(batchResponse[1]).toMatchObject({ rows: [], rowCount: 1 });
|
||||
|
||||
expect(batchResponse[2]).toEqual([
|
||||
{ id: 1, name: 'Dan', verified: 0, invitedBy: null },
|
||||
]);
|
||||
|
||||
expect(batchResponse[3]).toEqual([
|
||||
{ id: 1, name: 'Dan', verified: 0, invitedBy: null },
|
||||
]);
|
||||
|
||||
expect(batchResponse[4]).toEqual([
|
||||
{ id: 1, invitedBy: null },
|
||||
]);
|
||||
});
|
||||
|
||||
// batch api for insert + delete + select
|
||||
test('insert + delete + select + select partial', async (ctx) => {
|
||||
const { db } = ctx.neonPg;
|
||||
|
||||
const batchResponse = await db.batch([
|
||||
db.insert(usersTable).values({ id: 1, name: 'John' }).returning({ id: usersTable.id }),
|
||||
db.insert(usersTable).values({ id: 2, name: 'Dan' }),
|
||||
db.delete(usersTable).where(eq(usersTable.id, 1)).returning({
|
||||
id: usersTable.id,
|
||||
invitedBy: usersTable.invitedBy,
|
||||
}),
|
||||
db.query.usersTable.findFirst({
|
||||
columns: {
|
||||
id: true,
|
||||
invitedBy: true,
|
||||
},
|
||||
}),
|
||||
]);
|
||||
|
||||
expectTypeOf(batchResponse).toEqualTypeOf<[
|
||||
{
|
||||
id: number;
|
||||
}[],
|
||||
NeonHttpQueryResult<never>,
|
||||
{
|
||||
id: number;
|
||||
invitedBy: number | null;
|
||||
}[],
|
||||
{
|
||||
id: number;
|
||||
invitedBy: number | null;
|
||||
} | undefined,
|
||||
]>();
|
||||
|
||||
expect(batchResponse.length).eq(4);
|
||||
|
||||
expect(batchResponse[0]).toEqual([{
|
||||
id: 1,
|
||||
}]);
|
||||
|
||||
expect(batchResponse[1]).toMatchObject({ rows: [], rowCount: 1 });
|
||||
|
||||
expect(batchResponse[2]).toEqual([
|
||||
{ id: 1, invitedBy: null },
|
||||
]);
|
||||
|
||||
expect(batchResponse[3]).toEqual(
|
||||
{ id: 2, invitedBy: null },
|
||||
);
|
||||
});
|
||||
|
||||
test('select raw', async (ctx) => {
|
||||
const { db } = ctx.neonPg;
|
||||
|
||||
await db.insert(usersTable).values([{ id: 1, name: 'John' }, { id: 2, name: 'Dan' }]);
|
||||
const batchResponse = await db.batch([
|
||||
db.execute<InferSelectModel<typeof usersTable, { dbColumnNames: true }>>(sql`select * from users`),
|
||||
db.execute<InferSelectModel<typeof usersTable, { dbColumnNames: true }>>(sql`select * from users where id = 1`),
|
||||
]);
|
||||
|
||||
expectTypeOf(batchResponse).toEqualTypeOf<[
|
||||
NeonHttpQueryResult<{
|
||||
id: number;
|
||||
name: string;
|
||||
verified: number;
|
||||
invited_by: number | null;
|
||||
}>,
|
||||
NeonHttpQueryResult<{
|
||||
id: number;
|
||||
name: string;
|
||||
verified: number;
|
||||
invited_by: number | null;
|
||||
}>,
|
||||
]>();
|
||||
|
||||
expect(batchResponse.length).eq(2);
|
||||
|
||||
expect(batchResponse[0]).toMatchObject({
|
||||
rows: [
|
||||
{ id: 1, name: 'John', verified: 0, invited_by: null },
|
||||
{ id: 2, name: 'Dan', verified: 0, invited_by: null },
|
||||
],
|
||||
});
|
||||
|
||||
expect(batchResponse[1]).toMatchObject({
|
||||
rows: [
|
||||
{ id: 1, name: 'John', verified: 0, invited_by: null },
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,789 @@
|
||||
import { neon, neonConfig, type NeonQueryFunction } from '@neondatabase/serverless';
|
||||
import { eq, sql } from 'drizzle-orm';
|
||||
import { drizzle, type NeonHttpDatabase } from 'drizzle-orm/neon-http';
|
||||
import { migrate } from 'drizzle-orm/neon-http/migrator';
|
||||
import { pgMaterializedView, pgTable, serial, timestamp } from 'drizzle-orm/pg-core';
|
||||
import { beforeAll, beforeEach, describe, expect, test, vi } from 'vitest';
|
||||
import { skipTests } from '~/common';
|
||||
import { randomString } from '~/utils';
|
||||
import { tests, usersMigratorTable, usersTable } from './pg-common';
|
||||
import { TestCache, TestGlobalCache, tests as cacheTests } from './pg-common-cache';
|
||||
|
||||
const ENABLE_LOGGING = false;
|
||||
|
||||
let db: NeonHttpDatabase;
|
||||
let dbGlobalCached: NeonHttpDatabase;
|
||||
let cachedDb: NeonHttpDatabase;
|
||||
|
||||
beforeAll(async () => {
|
||||
const connectionString = process.env['NEON_HTTP_CONNECTION_STRING'];
|
||||
if (!connectionString) {
|
||||
throw new Error('NEON_CONNECTION_STRING is not defined');
|
||||
}
|
||||
|
||||
neonConfig.fetchEndpoint = (host) => {
|
||||
const [protocol, port] = host === 'db.localtest.me' ? ['http', 4444] : ['https', 443];
|
||||
return `${protocol}://${host}:${port}/sql`;
|
||||
};
|
||||
const client = neon(connectionString);
|
||||
db = drizzle(client, { logger: ENABLE_LOGGING });
|
||||
cachedDb = drizzle(client, {
|
||||
logger: ENABLE_LOGGING,
|
||||
cache: new TestCache(),
|
||||
});
|
||||
dbGlobalCached = drizzle(client, {
|
||||
logger: ENABLE_LOGGING,
|
||||
cache: new TestGlobalCache(),
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach((ctx) => {
|
||||
ctx.pg = {
|
||||
db,
|
||||
};
|
||||
ctx.cachedPg = {
|
||||
db: cachedDb,
|
||||
dbGlobalCached,
|
||||
};
|
||||
});
|
||||
|
||||
test('migrator : default migration strategy', async () => {
|
||||
await db.execute(sql`drop table if exists all_columns`);
|
||||
await db.execute(sql`drop table if exists users12`);
|
||||
await db.execute(sql`drop table if exists "drizzle"."__drizzle_migrations"`);
|
||||
|
||||
await migrate(db, { migrationsFolder: './drizzle2/pg' });
|
||||
|
||||
await db.insert(usersMigratorTable).values({ name: 'John', email: 'email' });
|
||||
|
||||
const result = await db.select().from(usersMigratorTable);
|
||||
|
||||
expect(result).toEqual([{ id: 1, name: 'John', email: 'email' }]);
|
||||
|
||||
await db.execute(sql`drop table all_columns`);
|
||||
await db.execute(sql`drop table users12`);
|
||||
await db.execute(sql`drop table "drizzle"."__drizzle_migrations"`);
|
||||
});
|
||||
|
||||
test('migrator : migrate with custom schema', async () => {
|
||||
await db.execute(sql`drop table if exists all_columns`);
|
||||
await db.execute(sql`drop table if exists users12`);
|
||||
await db.execute(sql`drop table if exists "drizzle"."__drizzle_migrations"`);
|
||||
|
||||
await migrate(db, { migrationsFolder: './drizzle2/pg', migrationsSchema: 'custom_migrations' });
|
||||
|
||||
// test if the custom migrations table was created
|
||||
const { rowCount } = await db.execute(sql`select * from custom_migrations."__drizzle_migrations";`);
|
||||
expect(rowCount && rowCount > 0).toBeTruthy();
|
||||
|
||||
// test if the migrated table are working as expected
|
||||
await db.insert(usersMigratorTable).values({ name: 'John', email: 'email' });
|
||||
const result = await db.select().from(usersMigratorTable);
|
||||
expect(result).toEqual([{ id: 1, name: 'John', email: 'email' }]);
|
||||
|
||||
await db.execute(sql`drop table all_columns`);
|
||||
await db.execute(sql`drop table users12`);
|
||||
await db.execute(sql`drop table custom_migrations."__drizzle_migrations"`);
|
||||
});
|
||||
|
||||
test('migrator : migrate with custom table', async () => {
|
||||
const customTable = randomString();
|
||||
await db.execute(sql`drop table if exists all_columns`);
|
||||
await db.execute(sql`drop table if exists users12`);
|
||||
await db.execute(sql`drop table if exists "drizzle"."__drizzle_migrations"`);
|
||||
|
||||
await migrate(db, { migrationsFolder: './drizzle2/pg', migrationsTable: customTable });
|
||||
|
||||
// test if the custom migrations table was created
|
||||
const { rowCount } = await db.execute(sql`select * from "drizzle".${sql.identifier(customTable)};`);
|
||||
expect(rowCount && rowCount > 0).toBeTruthy();
|
||||
|
||||
// test if the migrated table are working as expected
|
||||
await db.insert(usersMigratorTable).values({ name: 'John', email: 'email' });
|
||||
const result = await db.select().from(usersMigratorTable);
|
||||
expect(result).toEqual([{ id: 1, name: 'John', email: 'email' }]);
|
||||
|
||||
await db.execute(sql`drop table all_columns`);
|
||||
await db.execute(sql`drop table users12`);
|
||||
await db.execute(sql`drop table "drizzle".${sql.identifier(customTable)}`);
|
||||
});
|
||||
|
||||
test('migrator : migrate with custom table and custom schema', async () => {
|
||||
const customTable = randomString();
|
||||
await db.execute(sql`drop table if exists all_columns`);
|
||||
await db.execute(sql`drop table if exists users12`);
|
||||
await db.execute(sql`drop table if exists "drizzle"."__drizzle_migrations"`);
|
||||
|
||||
await migrate(db, {
|
||||
migrationsFolder: './drizzle2/pg',
|
||||
migrationsTable: customTable,
|
||||
migrationsSchema: 'custom_migrations',
|
||||
});
|
||||
|
||||
// test if the custom migrations table was created
|
||||
const { rowCount } = await db.execute(
|
||||
sql`select * from custom_migrations.${sql.identifier(customTable)};`,
|
||||
);
|
||||
expect(rowCount && rowCount > 0).toBeTruthy();
|
||||
|
||||
// test if the migrated table are working as expected
|
||||
await db.insert(usersMigratorTable).values({ name: 'John', email: 'email' });
|
||||
const result = await db.select().from(usersMigratorTable);
|
||||
expect(result).toEqual([{ id: 1, name: 'John', email: 'email' }]);
|
||||
|
||||
await db.execute(sql`drop table all_columns`);
|
||||
await db.execute(sql`drop table users12`);
|
||||
await db.execute(sql`drop table custom_migrations.${sql.identifier(customTable)}`);
|
||||
});
|
||||
|
||||
test('all date and time columns without timezone first case mode string', async () => {
|
||||
const table = pgTable('all_columns', {
|
||||
id: serial('id').primaryKey(),
|
||||
timestamp: timestamp('timestamp_string', { mode: 'string', precision: 6 }).notNull(),
|
||||
});
|
||||
|
||||
await db.execute(sql`drop table if exists ${table}`);
|
||||
|
||||
await db.execute(sql`
|
||||
create table ${table} (
|
||||
id serial primary key,
|
||||
timestamp_string timestamp(6) not null
|
||||
)
|
||||
`);
|
||||
|
||||
// 1. Insert date in string format without timezone in it
|
||||
await db.insert(table).values([
|
||||
{ timestamp: '2022-01-01 02:00:00.123456' },
|
||||
]);
|
||||
|
||||
// 2, Select in string format and check that values are the same
|
||||
const result = await db.select().from(table);
|
||||
|
||||
expect(result).toEqual([{ id: 1, timestamp: '2022-01-01 02:00:00.123456' }]);
|
||||
|
||||
// 3. Select as raw query and check that values are the same
|
||||
const result2 = await db.execute<{
|
||||
id: number;
|
||||
timestamp_string: string;
|
||||
}>(sql`select * from ${table}`);
|
||||
|
||||
expect(result2.rows).toEqual([{ id: 1, timestamp_string: '2022-01-01 02:00:00.123456' }]);
|
||||
|
||||
await db.execute(sql`drop table if exists ${table}`);
|
||||
});
|
||||
|
||||
test('all date and time columns without timezone second case mode string', async () => {
|
||||
const table = pgTable('all_columns', {
|
||||
id: serial('id').primaryKey(),
|
||||
timestamp: timestamp('timestamp_string', { mode: 'string', precision: 6 }).notNull(),
|
||||
});
|
||||
|
||||
await db.execute(sql`drop table if exists ${table}`);
|
||||
|
||||
await db.execute(sql`
|
||||
create table ${table} (
|
||||
id serial primary key,
|
||||
timestamp_string timestamp(6) not null
|
||||
)
|
||||
`);
|
||||
|
||||
// 1. Insert date in string format with timezone in it
|
||||
await db.insert(table).values([
|
||||
{ timestamp: '2022-01-01T02:00:00.123456-02' },
|
||||
]);
|
||||
|
||||
// 2, Select as raw query and check that values are the same
|
||||
const result = await db.execute<{
|
||||
id: number;
|
||||
timestamp_string: string;
|
||||
}>(sql`select * from ${table}`);
|
||||
|
||||
expect(result.rows).toEqual([{ id: 1, timestamp_string: '2022-01-01 02:00:00.123456' }]);
|
||||
|
||||
await db.execute(sql`drop table if exists ${table}`);
|
||||
});
|
||||
|
||||
test('all date and time columns without timezone third case mode date', async () => {
|
||||
const table = pgTable('all_columns', {
|
||||
id: serial('id').primaryKey(),
|
||||
timestamp: timestamp('timestamp_string', { mode: 'date', precision: 3 }).notNull(),
|
||||
});
|
||||
|
||||
await db.execute(sql`drop table if exists ${table}`);
|
||||
|
||||
await db.execute(sql`
|
||||
create table ${table} (
|
||||
id serial primary key,
|
||||
timestamp_string timestamp(3) not null
|
||||
)
|
||||
`);
|
||||
|
||||
const insertedDate = new Date('2022-01-01 20:00:00.123+04');
|
||||
|
||||
// 1. Insert date as new date
|
||||
await db.insert(table).values([
|
||||
{ timestamp: insertedDate },
|
||||
]);
|
||||
|
||||
// 2, Select as raw query as string
|
||||
const result = await db.execute<{
|
||||
id: number;
|
||||
timestamp_string: string;
|
||||
}>(sql`select * from ${table}`);
|
||||
|
||||
// 3. Compare both dates using orm mapping - Need to add 'Z' to tell JS that it is UTC
|
||||
expect(new Date(result.rows[0]!.timestamp_string + 'Z').getTime()).toBe(insertedDate.getTime());
|
||||
|
||||
await db.execute(sql`drop table if exists ${table}`);
|
||||
});
|
||||
|
||||
test('test mode string for timestamp with timezone', async () => {
|
||||
const table = pgTable('all_columns', {
|
||||
id: serial('id').primaryKey(),
|
||||
timestamp: timestamp('timestamp_string', { mode: 'string', withTimezone: true, precision: 6 }).notNull(),
|
||||
});
|
||||
|
||||
await db.execute(sql`drop table if exists ${table}`);
|
||||
|
||||
await db.execute(sql`
|
||||
create table ${table} (
|
||||
id serial primary key,
|
||||
timestamp_string timestamp(6) with time zone not null
|
||||
)
|
||||
`);
|
||||
|
||||
const timestampString = '2022-01-01 00:00:00.123456-0200';
|
||||
|
||||
// 1. Insert date in string format with timezone in it
|
||||
await db.insert(table).values([
|
||||
{ timestamp: timestampString },
|
||||
]);
|
||||
|
||||
// 2. Select date in string format and check that the values are the same
|
||||
const result = await db.select().from(table);
|
||||
|
||||
// 2.1 Notice that postgres will return the date in UTC, but it is exactly the same
|
||||
expect(result).toEqual([{ id: 1, timestamp: '2022-01-01 02:00:00.123456+00' }]);
|
||||
|
||||
// 3. Select as raw query and checke that values are the same
|
||||
const result2 = await db.execute<{
|
||||
id: number;
|
||||
timestamp_string: string;
|
||||
}>(sql`select * from ${table}`);
|
||||
|
||||
// 3.1 Notice that postgres will return the date in UTC, but it is exactlt the same
|
||||
expect(result2.rows).toEqual([{ id: 1, timestamp_string: '2022-01-01 02:00:00.123456+00' }]);
|
||||
|
||||
await db.execute(sql`drop table if exists ${table}`);
|
||||
});
|
||||
|
||||
test('test mode date for timestamp with timezone', async () => {
|
||||
const table = pgTable('all_columns', {
|
||||
id: serial('id').primaryKey(),
|
||||
timestamp: timestamp('timestamp_string', { mode: 'date', withTimezone: true, precision: 3 }).notNull(),
|
||||
});
|
||||
|
||||
await db.execute(sql`drop table if exists ${table}`);
|
||||
|
||||
await db.execute(sql`
|
||||
create table ${table} (
|
||||
id serial primary key,
|
||||
timestamp_string timestamp(3) with time zone not null
|
||||
)
|
||||
`);
|
||||
|
||||
const timestampString = new Date('2022-01-01 00:00:00.456-0200');
|
||||
|
||||
// 1. Insert date in string format with timezone in it
|
||||
await db.insert(table).values([
|
||||
{ timestamp: timestampString },
|
||||
]);
|
||||
|
||||
// 2. Select date in string format and check that the values are the same
|
||||
const result = await db.select().from(table);
|
||||
|
||||
// 2.1 Notice that postgres will return the date in UTC, but it is exactly the same
|
||||
expect(result).toEqual([{ id: 1, timestamp: timestampString }]);
|
||||
|
||||
// 3. Select as raw query and checke that values are the same
|
||||
const result2 = await db.execute<{
|
||||
id: number;
|
||||
timestamp_string: string;
|
||||
}>(sql`select * from ${table}`);
|
||||
|
||||
// 3.1 Notice that postgres will return the date in UTC, but it is exactlt the same
|
||||
expect(result2.rows).toEqual([{ id: 1, timestamp_string: '2022-01-01 02:00:00.456+00' }]);
|
||||
|
||||
await db.execute(sql`drop table if exists ${table}`);
|
||||
});
|
||||
|
||||
test('test mode string for timestamp with timezone in UTC timezone', async () => {
|
||||
// get current timezone from db
|
||||
const timezone = await db.execute<{ TimeZone: string }>(sql`show timezone`);
|
||||
|
||||
// set timezone to UTC
|
||||
await db.execute(sql`set time zone 'UTC'`);
|
||||
|
||||
const table = pgTable('all_columns', {
|
||||
id: serial('id').primaryKey(),
|
||||
timestamp: timestamp('timestamp_string', { mode: 'string', withTimezone: true, precision: 6 }).notNull(),
|
||||
});
|
||||
|
||||
await db.execute(sql`drop table if exists ${table}`);
|
||||
|
||||
await db.execute(sql`
|
||||
create table ${table} (
|
||||
id serial primary key,
|
||||
timestamp_string timestamp(6) with time zone not null
|
||||
)
|
||||
`);
|
||||
|
||||
const timestampString = '2022-01-01 00:00:00.123456-0200';
|
||||
|
||||
// 1. Insert date in string format with timezone in it
|
||||
await db.insert(table).values([
|
||||
{ timestamp: timestampString },
|
||||
]);
|
||||
|
||||
// 2. Select date in string format and check that the values are the same
|
||||
const result = await db.select().from(table);
|
||||
|
||||
// 2.1 Notice that postgres will return the date in UTC, but it is exactly the same
|
||||
expect(result).toEqual([{ id: 1, timestamp: '2022-01-01 02:00:00.123456+00' }]);
|
||||
|
||||
// 3. Select as raw query and checke that values are the same
|
||||
const result2 = await db.execute<{
|
||||
id: number;
|
||||
timestamp_string: string;
|
||||
}>(sql`select * from ${table}`);
|
||||
|
||||
// 3.1 Notice that postgres will return the date in UTC, but it is exactlt the same
|
||||
expect(result2.rows).toEqual([{ id: 1, timestamp_string: '2022-01-01 02:00:00.123456+00' }]);
|
||||
|
||||
await db.execute(sql`set time zone '${sql.raw(timezone.rows[0]!.TimeZone)}'`);
|
||||
|
||||
await db.execute(sql`drop table if exists ${table}`);
|
||||
});
|
||||
|
||||
test.skip('test mode string for timestamp with timezone in different timezone', async () => {
|
||||
// get current timezone from db
|
||||
const timezone = await db.execute<{ TimeZone: string }>(sql`show timezone`);
|
||||
|
||||
// set timezone to HST (UTC - 10)
|
||||
await db.execute(sql`set time zone 'HST'`);
|
||||
|
||||
const table = pgTable('all_columns', {
|
||||
id: serial('id').primaryKey(),
|
||||
timestamp: timestamp('timestamp_string', { mode: 'string', withTimezone: true, precision: 6 }).notNull(),
|
||||
});
|
||||
|
||||
await db.execute(sql`drop table if exists ${table}`);
|
||||
|
||||
await db.execute(sql`
|
||||
create table ${table} (
|
||||
id serial primary key,
|
||||
timestamp_string timestamp(6) with time zone not null
|
||||
)
|
||||
`);
|
||||
|
||||
const timestampString = '2022-01-01 00:00:00.123456-1000';
|
||||
|
||||
// 1. Insert date in string format with timezone in it
|
||||
await db.insert(table).values([
|
||||
{ timestamp: timestampString },
|
||||
]);
|
||||
|
||||
// 2. Select date in string format and check that the values are the same
|
||||
const result = await db.select().from(table);
|
||||
|
||||
expect(result).toEqual([{ id: 1, timestamp: '2022-01-01 00:00:00.123456-10' }]);
|
||||
|
||||
// 3. Select as raw query and checke that values are the same
|
||||
const result2 = await db.execute<{
|
||||
id: number;
|
||||
timestamp_string: string;
|
||||
}>(sql`select * from ${table}`);
|
||||
|
||||
expect(result2.rows).toEqual([{ id: 1, timestamp_string: '2022-01-01 00:00:00.123456+00' }]);
|
||||
|
||||
await db.execute(sql`set time zone '${sql.raw(timezone.rows[0]!.TimeZone)}'`);
|
||||
|
||||
await db.execute(sql`drop table if exists ${table}`);
|
||||
});
|
||||
|
||||
skipTests([
|
||||
'migrator : default migration strategy',
|
||||
'migrator : migrate with custom schema',
|
||||
'migrator : migrate with custom table',
|
||||
'migrator : migrate with custom table and custom schema',
|
||||
'insert via db.execute + select via db.execute',
|
||||
'insert via db.execute + returning',
|
||||
'insert via db.execute w/ query builder',
|
||||
'all date and time columns without timezone first case mode string',
|
||||
'all date and time columns without timezone third case mode date',
|
||||
'test mode string for timestamp with timezone',
|
||||
'test mode date for timestamp with timezone',
|
||||
'test mode string for timestamp with timezone in UTC timezone',
|
||||
'nested transaction rollback',
|
||||
'transaction rollback',
|
||||
'nested transaction',
|
||||
'transaction',
|
||||
'timestamp timezone',
|
||||
'test $onUpdateFn and $onUpdate works as $default',
|
||||
]);
|
||||
tests();
|
||||
cacheTests();
|
||||
|
||||
beforeEach(async () => {
|
||||
await db.execute(sql`drop schema if exists public cascade`);
|
||||
await db.execute(sql`create schema public`);
|
||||
await db.execute(
|
||||
sql`
|
||||
create table users (
|
||||
id serial primary key,
|
||||
name text not null,
|
||||
verified boolean not null default false,
|
||||
jsonb jsonb,
|
||||
created_at timestamptz not null default now()
|
||||
)
|
||||
`,
|
||||
);
|
||||
});
|
||||
|
||||
test('insert via db.execute + select via db.execute', async () => {
|
||||
await db.execute(
|
||||
sql`insert into ${usersTable} (${sql.identifier(usersTable.name.name)}) values (${'John'})`,
|
||||
);
|
||||
|
||||
const result = await db.execute<{ id: number; name: string }>(
|
||||
sql`select id, name from "users"`,
|
||||
);
|
||||
expect(result.rows).toEqual([{ id: 1, name: 'John' }]);
|
||||
});
|
||||
|
||||
test('insert via db.execute + returning', async () => {
|
||||
const inserted = await db.execute<{ id: number; name: string }>(
|
||||
sql`insert into ${usersTable} (${
|
||||
sql.identifier(
|
||||
usersTable.name.name,
|
||||
)
|
||||
}) values (${'John'}) returning ${usersTable.id}, ${usersTable.name}`,
|
||||
);
|
||||
expect(inserted.rows).toEqual([{ id: 1, name: 'John' }]);
|
||||
});
|
||||
|
||||
test('insert via db.execute w/ query builder', async () => {
|
||||
const inserted = await db.execute<Pick<typeof usersTable.$inferSelect, 'id' | 'name'>>(
|
||||
db
|
||||
.insert(usersTable)
|
||||
.values({ name: 'John' })
|
||||
.returning({ id: usersTable.id, name: usersTable.name }),
|
||||
);
|
||||
expect(inserted.rows).toEqual([{ id: 1, name: 'John' }]);
|
||||
});
|
||||
|
||||
describe('$withAuth tests', (it) => {
|
||||
const client = vi.fn();
|
||||
const db = drizzle({
|
||||
client: client as any as NeonQueryFunction<any, any>,
|
||||
schema: {
|
||||
usersTable,
|
||||
},
|
||||
});
|
||||
|
||||
it('$count', async () => {
|
||||
await db.$withAuth('$count').$count(usersTable).catch(() => null);
|
||||
|
||||
expect(client.mock.lastCall?.[2]).toStrictEqual({ arrayMode: false, fullResults: true, authToken: '$count' });
|
||||
});
|
||||
|
||||
it('delete', async () => {
|
||||
await db.$withAuth('delete').delete(usersTable).catch(() => null);
|
||||
|
||||
expect(client.mock.lastCall?.[2]).toStrictEqual({ arrayMode: false, fullResults: true, authToken: 'delete' });
|
||||
});
|
||||
|
||||
it('select', async () => {
|
||||
await db.$withAuth('select').select().from(usersTable).catch(() => null);
|
||||
|
||||
expect(client.mock.lastCall?.[2]).toStrictEqual({ arrayMode: true, fullResults: true, authToken: 'select' });
|
||||
});
|
||||
|
||||
it('selectDistinct', async () => {
|
||||
await db.$withAuth('selectDistinct').selectDistinct().from(usersTable).catch(() => null);
|
||||
|
||||
expect(client.mock.lastCall?.[2]).toStrictEqual({
|
||||
arrayMode: true,
|
||||
fullResults: true,
|
||||
authToken: 'selectDistinct',
|
||||
});
|
||||
});
|
||||
|
||||
it('selectDistinctOn', async () => {
|
||||
await db.$withAuth('selectDistinctOn').selectDistinctOn([usersTable.name]).from(usersTable).catch(() => null);
|
||||
|
||||
expect(client.mock.lastCall?.[2]).toStrictEqual({
|
||||
arrayMode: true,
|
||||
fullResults: true,
|
||||
authToken: 'selectDistinctOn',
|
||||
});
|
||||
});
|
||||
|
||||
it('update', async () => {
|
||||
await db.$withAuth('update').update(usersTable).set({
|
||||
name: 'CHANGED',
|
||||
}).where(eq(usersTable.name, 'TARGET')).catch(() => null);
|
||||
|
||||
expect(client.mock.lastCall?.[2]).toStrictEqual({ arrayMode: false, fullResults: true, authToken: 'update' });
|
||||
});
|
||||
|
||||
it('insert', async () => {
|
||||
await db.$withAuth('insert').insert(usersTable).values({
|
||||
name: 'WITHAUTHUSER',
|
||||
}).catch(() => null);
|
||||
|
||||
expect(client.mock.lastCall?.[2]).toStrictEqual({ arrayMode: false, fullResults: true, authToken: 'insert' });
|
||||
});
|
||||
|
||||
it('with', async () => {
|
||||
await db.$withAuth('with').with(db.$with('WITH').as((qb) => qb.select().from(usersTable))).select().from(usersTable)
|
||||
.catch(() => null);
|
||||
|
||||
expect(client.mock.lastCall?.[2]).toStrictEqual({ arrayMode: true, fullResults: true, authToken: 'with' });
|
||||
});
|
||||
|
||||
it('rqb', async () => {
|
||||
await db.$withAuth('rqb').query.usersTable.findFirst().catch(() => null);
|
||||
|
||||
expect(client.mock.lastCall?.[2]).toStrictEqual({ arrayMode: true, fullResults: true, authToken: 'rqb' });
|
||||
});
|
||||
|
||||
it('exec', async () => {
|
||||
await db.$withAuth('exec').execute(`SELECT 1`).catch(() => null);
|
||||
|
||||
expect(client.mock.lastCall?.[2]).toStrictEqual({ arrayMode: false, fullResults: true, authToken: 'exec' });
|
||||
});
|
||||
|
||||
it('prepared', async () => {
|
||||
const prep = db.$withAuth('prepared').select().from(usersTable).prepare('withAuthPrepared');
|
||||
|
||||
await prep.execute().catch(() => null);
|
||||
|
||||
expect(client.mock.lastCall?.[2]).toStrictEqual({ arrayMode: true, fullResults: true, authToken: 'prepared' });
|
||||
});
|
||||
|
||||
it('refreshMaterializedView', async () => {
|
||||
const johns = pgMaterializedView('johns')
|
||||
.as((qb) => qb.select().from(usersTable).where(eq(usersTable.name, 'John')));
|
||||
|
||||
await db.$withAuth('refreshMaterializedView').refreshMaterializedView(johns);
|
||||
|
||||
expect(client.mock.lastCall?.[2]).toStrictEqual({
|
||||
arrayMode: false,
|
||||
fullResults: true,
|
||||
authToken: 'refreshMaterializedView',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('$withAuth callback tests', (it) => {
|
||||
const client = vi.fn();
|
||||
const db = drizzle({
|
||||
client: client as any as NeonQueryFunction<any, any>,
|
||||
schema: {
|
||||
usersTable,
|
||||
},
|
||||
});
|
||||
const auth = (token: string) => () => token;
|
||||
|
||||
it('$count', async () => {
|
||||
await db.$withAuth(auth('$count')).$count(usersTable).catch(() => null);
|
||||
|
||||
expect(client.mock.lastCall?.[2]['authToken']()).toStrictEqual('$count');
|
||||
});
|
||||
|
||||
it('delete', async () => {
|
||||
await db.$withAuth(auth('delete')).delete(usersTable).catch(() => null);
|
||||
|
||||
expect(client.mock.lastCall?.[2]['authToken']()).toStrictEqual('delete');
|
||||
});
|
||||
|
||||
it('select', async () => {
|
||||
await db.$withAuth(auth('select')).select().from(usersTable).catch(() => null);
|
||||
|
||||
expect(client.mock.lastCall?.[2]['authToken']()).toStrictEqual('select');
|
||||
});
|
||||
|
||||
it('selectDistinct', async () => {
|
||||
await db.$withAuth(auth('selectDistinct')).selectDistinct().from(usersTable).catch(() => null);
|
||||
|
||||
expect(client.mock.lastCall?.[2]['authToken']()).toStrictEqual('selectDistinct');
|
||||
});
|
||||
|
||||
it('selectDistinctOn', async () => {
|
||||
await db.$withAuth(auth('selectDistinctOn')).selectDistinctOn([usersTable.name]).from(usersTable).catch(() => null);
|
||||
|
||||
expect(client.mock.lastCall?.[2]['authToken']()).toStrictEqual('selectDistinctOn');
|
||||
});
|
||||
|
||||
it('update', async () => {
|
||||
await db.$withAuth(auth('update')).update(usersTable).set({
|
||||
name: 'CHANGED',
|
||||
}).where(eq(usersTable.name, 'TARGET')).catch(() => null);
|
||||
|
||||
expect(client.mock.lastCall?.[2]['authToken']()).toStrictEqual('update');
|
||||
});
|
||||
|
||||
it('insert', async () => {
|
||||
await db.$withAuth(auth('insert')).insert(usersTable).values({
|
||||
name: 'WITHAUTHUSER',
|
||||
}).catch(() => null);
|
||||
|
||||
expect(client.mock.lastCall?.[2]['authToken']()).toStrictEqual('insert');
|
||||
});
|
||||
|
||||
it('with', async () => {
|
||||
await db.$withAuth(auth('with')).with(db.$with('WITH').as((qb) => qb.select().from(usersTable))).select().from(
|
||||
usersTable,
|
||||
)
|
||||
.catch(() => null);
|
||||
|
||||
expect(client.mock.lastCall?.[2]['authToken']()).toStrictEqual('with');
|
||||
});
|
||||
|
||||
it('rqb', async () => {
|
||||
await db.$withAuth(auth('rqb')).query.usersTable.findFirst().catch(() => null);
|
||||
|
||||
expect(client.mock.lastCall?.[2]['authToken']()).toStrictEqual('rqb');
|
||||
});
|
||||
|
||||
it('exec', async () => {
|
||||
await db.$withAuth(auth('exec')).execute(`SELECT 1`).catch(() => null);
|
||||
|
||||
expect(client.mock.lastCall?.[2]['authToken']()).toStrictEqual('exec');
|
||||
});
|
||||
|
||||
it('prepared', async () => {
|
||||
const prep = db.$withAuth(auth('prepared')).select().from(usersTable).prepare('withAuthPrepared');
|
||||
|
||||
await prep.execute().catch(() => null);
|
||||
|
||||
expect(client.mock.lastCall?.[2]['authToken']()).toStrictEqual('prepared');
|
||||
});
|
||||
|
||||
it('refreshMaterializedView', async () => {
|
||||
const johns = pgMaterializedView('johns')
|
||||
.as((qb) => qb.select().from(usersTable).where(eq(usersTable.name, 'John')));
|
||||
|
||||
await db.$withAuth(auth('refreshMaterializedView')).refreshMaterializedView(johns);
|
||||
|
||||
expect(client.mock.lastCall?.[2]['authToken']()).toStrictEqual('refreshMaterializedView');
|
||||
});
|
||||
});
|
||||
|
||||
describe('$withAuth async callback tests', (it) => {
|
||||
const client = vi.fn();
|
||||
const db = drizzle({
|
||||
client: client as any as NeonQueryFunction<any, any>,
|
||||
schema: {
|
||||
usersTable,
|
||||
},
|
||||
});
|
||||
const auth = (token: string) => async () => token;
|
||||
|
||||
it('$count', async () => {
|
||||
await db.$withAuth(auth('$count')).$count(usersTable).catch(() => null);
|
||||
|
||||
expect(client.mock.lastCall?.[2]['authToken']()).toBeInstanceOf(Promise);
|
||||
expect(await client.mock.lastCall?.[2]['authToken']()).toStrictEqual('$count');
|
||||
});
|
||||
|
||||
it('delete', async () => {
|
||||
await db.$withAuth(auth('delete')).delete(usersTable).catch(() => null);
|
||||
|
||||
expect(client.mock.lastCall?.[2]['authToken']()).toBeInstanceOf(Promise);
|
||||
expect(await client.mock.lastCall?.[2]['authToken']()).toStrictEqual('delete');
|
||||
});
|
||||
|
||||
it('select', async () => {
|
||||
await db.$withAuth(auth('select')).select().from(usersTable).catch(() => null);
|
||||
|
||||
expect(client.mock.lastCall?.[2]['authToken']()).toBeInstanceOf(Promise);
|
||||
expect(await client.mock.lastCall?.[2]['authToken']()).toStrictEqual('select');
|
||||
});
|
||||
|
||||
it('selectDistinct', async () => {
|
||||
await db.$withAuth(auth('selectDistinct')).selectDistinct().from(usersTable).catch(() => null);
|
||||
|
||||
expect(client.mock.lastCall?.[2]['authToken']()).toBeInstanceOf(Promise);
|
||||
expect(await client.mock.lastCall?.[2]['authToken']()).toStrictEqual('selectDistinct');
|
||||
});
|
||||
|
||||
it('selectDistinctOn', async () => {
|
||||
await db.$withAuth(auth('selectDistinctOn')).selectDistinctOn([usersTable.name]).from(usersTable).catch(() => null);
|
||||
|
||||
expect(client.mock.lastCall?.[2]['authToken']()).toBeInstanceOf(Promise);
|
||||
expect(await client.mock.lastCall?.[2]['authToken']()).toStrictEqual('selectDistinctOn');
|
||||
});
|
||||
|
||||
it('update', async () => {
|
||||
await db.$withAuth(auth('update')).update(usersTable).set({
|
||||
name: 'CHANGED',
|
||||
}).where(eq(usersTable.name, 'TARGET')).catch(() => null);
|
||||
|
||||
expect(client.mock.lastCall?.[2]['authToken']()).toBeInstanceOf(Promise);
|
||||
expect(await client.mock.lastCall?.[2]['authToken']()).toStrictEqual('update');
|
||||
});
|
||||
|
||||
it('insert', async () => {
|
||||
await db.$withAuth(auth('insert')).insert(usersTable).values({
|
||||
name: 'WITHAUTHUSER',
|
||||
}).catch(() => null);
|
||||
|
||||
expect(client.mock.lastCall?.[2]['authToken']()).toBeInstanceOf(Promise);
|
||||
expect(await client.mock.lastCall?.[2]['authToken']()).toStrictEqual('insert');
|
||||
});
|
||||
|
||||
it('with', async () => {
|
||||
await db.$withAuth(auth('with')).with(db.$with('WITH').as((qb) => qb.select().from(usersTable))).select().from(
|
||||
usersTable,
|
||||
)
|
||||
.catch(() => null);
|
||||
|
||||
expect(client.mock.lastCall?.[2]['authToken']()).toBeInstanceOf(Promise);
|
||||
expect(await client.mock.lastCall?.[2]['authToken']()).toStrictEqual('with');
|
||||
});
|
||||
|
||||
it('rqb', async () => {
|
||||
await db.$withAuth(auth('rqb')).query.usersTable.findFirst().catch(() => null);
|
||||
|
||||
expect(client.mock.lastCall?.[2]['authToken']()).toBeInstanceOf(Promise);
|
||||
expect(await client.mock.lastCall?.[2]['authToken']()).toStrictEqual('rqb');
|
||||
});
|
||||
|
||||
it('exec', async () => {
|
||||
await db.$withAuth(auth('exec')).execute(`SELECT 1`).catch(() => null);
|
||||
|
||||
expect(client.mock.lastCall?.[2]['authToken']()).toBeInstanceOf(Promise);
|
||||
expect(await client.mock.lastCall?.[2]['authToken']()).toStrictEqual('exec');
|
||||
});
|
||||
|
||||
it('prepared', async () => {
|
||||
const prep = db.$withAuth(auth('prepared')).select().from(usersTable).prepare('withAuthPrepared');
|
||||
|
||||
await prep.execute().catch(() => null);
|
||||
|
||||
expect(client.mock.lastCall?.[2]['authToken']()).toBeInstanceOf(Promise);
|
||||
expect(await client.mock.lastCall?.[2]['authToken']()).toStrictEqual('prepared');
|
||||
});
|
||||
|
||||
it('refreshMaterializedView', async () => {
|
||||
const johns = pgMaterializedView('johns')
|
||||
.as((qb) => qb.select().from(usersTable).where(eq(usersTable.name, 'John')));
|
||||
|
||||
await db.$withAuth(auth('refreshMaterializedView')).refreshMaterializedView(johns);
|
||||
|
||||
expect(client.mock.lastCall?.[2]['authToken']()).toBeInstanceOf(Promise);
|
||||
expect(await client.mock.lastCall?.[2]['authToken']()).toStrictEqual('refreshMaterializedView');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,585 @@
|
||||
import { neonConfig, Pool } from '@neondatabase/serverless';
|
||||
import { eq, sql } from 'drizzle-orm';
|
||||
import { drizzle, type NeonDatabase } from 'drizzle-orm/neon-serverless';
|
||||
import { migrate } from 'drizzle-orm/neon-serverless/migrator';
|
||||
import { pgTable, serial, timestamp } from 'drizzle-orm/pg-core';
|
||||
import { afterAll, beforeAll, beforeEach, expect, test } from 'vitest';
|
||||
import ws from 'ws';
|
||||
import { skipTests } from '~/common';
|
||||
import { randomString } from '~/utils';
|
||||
import { mySchema, tests, usersMigratorTable, usersMySchemaTable, usersTable } from './pg-common';
|
||||
import { TestCache, TestGlobalCache, tests as cacheTests } from './pg-common-cache';
|
||||
|
||||
const ENABLE_LOGGING = false;
|
||||
|
||||
let db: NeonDatabase;
|
||||
let dbGlobalCached: NeonDatabase;
|
||||
let cachedDb: NeonDatabase;
|
||||
let client: Pool;
|
||||
|
||||
neonConfig.wsProxy = (host) => `${host}:5446/v1`;
|
||||
neonConfig.useSecureWebSocket = false;
|
||||
neonConfig.pipelineTLS = false;
|
||||
neonConfig.pipelineConnect = false;
|
||||
neonConfig.webSocketConstructor = ws;
|
||||
|
||||
beforeAll(async () => {
|
||||
const connectionString = process.env['NEON_SERVERLESS_CONNECTION_STRING'];
|
||||
if (!connectionString) {
|
||||
throw new Error('NEON_SERVERLESS_CONNECTION_STRING is not defined');
|
||||
}
|
||||
|
||||
client = new Pool({ connectionString });
|
||||
db = drizzle(client, { logger: ENABLE_LOGGING });
|
||||
cachedDb = drizzle(client, {
|
||||
logger: ENABLE_LOGGING,
|
||||
cache: new TestCache(),
|
||||
});
|
||||
dbGlobalCached = drizzle(client, {
|
||||
logger: ENABLE_LOGGING,
|
||||
cache: new TestGlobalCache(),
|
||||
});
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await client?.end();
|
||||
});
|
||||
|
||||
beforeEach((ctx) => {
|
||||
ctx.pg = {
|
||||
db,
|
||||
};
|
||||
ctx.cachedPg = {
|
||||
db: cachedDb,
|
||||
dbGlobalCached,
|
||||
};
|
||||
});
|
||||
|
||||
test('migrator : default migration strategy', async () => {
|
||||
await db.execute(sql`drop table if exists all_columns`);
|
||||
await db.execute(sql`drop table if exists users12`);
|
||||
await db.execute(sql`drop table if exists "drizzle"."__drizzle_migrations"`);
|
||||
|
||||
await migrate(db, { migrationsFolder: './drizzle2/pg' });
|
||||
|
||||
await db.insert(usersMigratorTable).values({ name: 'John', email: 'email' });
|
||||
|
||||
const result = await db.select().from(usersMigratorTable);
|
||||
|
||||
expect(result).toEqual([{ id: 1, name: 'John', email: 'email' }]);
|
||||
|
||||
await db.execute(sql`drop table all_columns`);
|
||||
await db.execute(sql`drop table users12`);
|
||||
await db.execute(sql`drop table "drizzle"."__drizzle_migrations"`);
|
||||
});
|
||||
|
||||
test('migrator : migrate with custom schema', async () => {
|
||||
await db.execute(sql`drop table if exists all_columns`);
|
||||
await db.execute(sql`drop table if exists users12`);
|
||||
await db.execute(sql`drop table if exists "drizzle"."__drizzle_migrations"`);
|
||||
|
||||
await migrate(db, { migrationsFolder: './drizzle2/pg', migrationsSchema: 'custom_migrations' });
|
||||
|
||||
// test if the custom migrations table was created
|
||||
const { rowCount } = await db.execute(sql`select * from custom_migrations."__drizzle_migrations";`);
|
||||
expect(rowCount && rowCount > 0).toBeTruthy();
|
||||
|
||||
// test if the migrated table are working as expected
|
||||
await db.insert(usersMigratorTable).values({ name: 'John', email: 'email' });
|
||||
const result = await db.select().from(usersMigratorTable);
|
||||
expect(result).toEqual([{ id: 1, name: 'John', email: 'email' }]);
|
||||
|
||||
await db.execute(sql`drop table all_columns`);
|
||||
await db.execute(sql`drop table users12`);
|
||||
await db.execute(sql`drop table custom_migrations."__drizzle_migrations"`);
|
||||
});
|
||||
|
||||
test('migrator : migrate with custom table', async () => {
|
||||
const customTable = randomString();
|
||||
await db.execute(sql`drop table if exists all_columns`);
|
||||
await db.execute(sql`drop table if exists users12`);
|
||||
await db.execute(sql`drop table if exists "drizzle"."__drizzle_migrations"`);
|
||||
|
||||
await migrate(db, { migrationsFolder: './drizzle2/pg', migrationsTable: customTable });
|
||||
|
||||
// test if the custom migrations table was created
|
||||
const { rowCount } = await db.execute(sql`select * from "drizzle".${sql.identifier(customTable)};`);
|
||||
expect(rowCount && rowCount > 0).toBeTruthy();
|
||||
|
||||
// test if the migrated table are working as expected
|
||||
await db.insert(usersMigratorTable).values({ name: 'John', email: 'email' });
|
||||
const result = await db.select().from(usersMigratorTable);
|
||||
expect(result).toEqual([{ id: 1, name: 'John', email: 'email' }]);
|
||||
|
||||
await db.execute(sql`drop table all_columns`);
|
||||
await db.execute(sql`drop table users12`);
|
||||
await db.execute(sql`drop table "drizzle".${sql.identifier(customTable)}`);
|
||||
});
|
||||
|
||||
test('migrator : migrate with custom table and custom schema', async () => {
|
||||
const customTable = randomString();
|
||||
await db.execute(sql`drop table if exists all_columns`);
|
||||
await db.execute(sql`drop table if exists users12`);
|
||||
await db.execute(sql`drop table if exists "drizzle"."__drizzle_migrations"`);
|
||||
|
||||
await migrate(db, {
|
||||
migrationsFolder: './drizzle2/pg',
|
||||
migrationsTable: customTable,
|
||||
migrationsSchema: 'custom_migrations',
|
||||
});
|
||||
|
||||
// test if the custom migrations table was created
|
||||
const { rowCount } = await db.execute(
|
||||
sql`select * from custom_migrations.${sql.identifier(customTable)};`,
|
||||
);
|
||||
expect(rowCount && rowCount > 0).toBeTruthy();
|
||||
|
||||
// test if the migrated table are working as expected
|
||||
await db.insert(usersMigratorTable).values({ name: 'John', email: 'email' });
|
||||
const result = await db.select().from(usersMigratorTable);
|
||||
expect(result).toEqual([{ id: 1, name: 'John', email: 'email' }]);
|
||||
|
||||
await db.execute(sql`drop table all_columns`);
|
||||
await db.execute(sql`drop table users12`);
|
||||
await db.execute(sql`drop table custom_migrations.${sql.identifier(customTable)}`);
|
||||
});
|
||||
|
||||
test('all date and time columns without timezone first case mode string', async () => {
|
||||
const table = pgTable('all_columns', {
|
||||
id: serial('id').primaryKey(),
|
||||
timestamp: timestamp('timestamp_string', { mode: 'string', precision: 6 }).notNull(),
|
||||
});
|
||||
|
||||
await db.execute(sql`drop table if exists ${table}`);
|
||||
|
||||
await db.execute(sql`
|
||||
create table ${table} (
|
||||
id serial primary key,
|
||||
timestamp_string timestamp(6) not null
|
||||
)
|
||||
`);
|
||||
|
||||
// 1. Insert date in string format without timezone in it
|
||||
await db.insert(table).values([
|
||||
{ timestamp: '2022-01-01 02:00:00.123456' },
|
||||
]);
|
||||
|
||||
// 2, Select in string format and check that values are the same
|
||||
const result = await db.select().from(table);
|
||||
|
||||
expect(result).toEqual([{ id: 1, timestamp: '2022-01-01 02:00:00.123456' }]);
|
||||
|
||||
// 3. Select as raw query and check that values are the same
|
||||
const result2 = await db.execute<{
|
||||
id: number;
|
||||
timestamp_string: string;
|
||||
}>(sql`select * from ${table}`);
|
||||
|
||||
expect(result2.rows).toEqual([{ id: 1, timestamp_string: '2022-01-01 02:00:00.123456' }]);
|
||||
|
||||
await db.execute(sql`drop table if exists ${table}`);
|
||||
});
|
||||
|
||||
test('all date and time columns without timezone second case mode string', async () => {
|
||||
const table = pgTable('all_columns', {
|
||||
id: serial('id').primaryKey(),
|
||||
timestamp: timestamp('timestamp_string', { mode: 'string', precision: 6 }).notNull(),
|
||||
});
|
||||
|
||||
await db.execute(sql`drop table if exists ${table}`);
|
||||
|
||||
await db.execute(sql`
|
||||
create table ${table} (
|
||||
id serial primary key,
|
||||
timestamp_string timestamp(6) not null
|
||||
)
|
||||
`);
|
||||
|
||||
// 1. Insert date in string format with timezone in it
|
||||
await db.insert(table).values([
|
||||
{ timestamp: '2022-01-01T02:00:00.123456-02' },
|
||||
]);
|
||||
|
||||
// 2, Select as raw query and check that values are the same
|
||||
const result = await db.execute<{
|
||||
id: number;
|
||||
timestamp_string: string;
|
||||
}>(sql`select * from ${table}`);
|
||||
|
||||
expect(result.rows).toEqual([{ id: 1, timestamp_string: '2022-01-01 02:00:00.123456' }]);
|
||||
|
||||
await db.execute(sql`drop table if exists ${table}`);
|
||||
});
|
||||
|
||||
test('all date and time columns without timezone third case mode date', async () => {
|
||||
const table = pgTable('all_columns', {
|
||||
id: serial('id').primaryKey(),
|
||||
timestamp: timestamp('timestamp_string', { mode: 'date', precision: 3 }).notNull(),
|
||||
});
|
||||
|
||||
await db.execute(sql`drop table if exists ${table}`);
|
||||
|
||||
await db.execute(sql`
|
||||
create table ${table} (
|
||||
id serial primary key,
|
||||
timestamp_string timestamp(3) not null
|
||||
)
|
||||
`);
|
||||
|
||||
const insertedDate = new Date('2022-01-01 20:00:00.123+04');
|
||||
|
||||
// 1. Insert date as new date
|
||||
await db.insert(table).values([
|
||||
{ timestamp: insertedDate },
|
||||
]);
|
||||
|
||||
// 2, Select as raw query as string
|
||||
const result = await db.execute<{
|
||||
id: number;
|
||||
timestamp_string: string;
|
||||
}>(sql`select * from ${table}`);
|
||||
|
||||
// 3. Compare both dates using orm mapping - Need to add 'Z' to tell JS that it is UTC
|
||||
expect(new Date(result.rows[0]!.timestamp_string + 'Z').getTime()).toBe(insertedDate.getTime());
|
||||
|
||||
await db.execute(sql`drop table if exists ${table}`);
|
||||
});
|
||||
|
||||
test('test mode string for timestamp with timezone', async () => {
|
||||
const table = pgTable('all_columns', {
|
||||
id: serial('id').primaryKey(),
|
||||
timestamp: timestamp('timestamp_string', { mode: 'string', withTimezone: true, precision: 6 }).notNull(),
|
||||
});
|
||||
|
||||
await db.execute(sql`drop table if exists ${table}`);
|
||||
|
||||
await db.execute(sql`
|
||||
create table ${table} (
|
||||
id serial primary key,
|
||||
timestamp_string timestamp(6) with time zone not null
|
||||
)
|
||||
`);
|
||||
|
||||
const timestampString = '2022-01-01 00:00:00.123456-0200';
|
||||
|
||||
// 1. Insert date in string format with timezone in it
|
||||
await db.insert(table).values([
|
||||
{ timestamp: timestampString },
|
||||
]);
|
||||
|
||||
// 2. Select date in string format and check that the values are the same
|
||||
const result = await db.select().from(table);
|
||||
|
||||
// 2.1 Notice that postgres will return the date in UTC, but it is exactly the same
|
||||
expect(result).toEqual([{ id: 1, timestamp: '2022-01-01 02:00:00.123456+00' }]);
|
||||
|
||||
// 3. Select as raw query and checke that values are the same
|
||||
const result2 = await db.execute<{
|
||||
id: number;
|
||||
timestamp_string: string;
|
||||
}>(sql`select * from ${table}`);
|
||||
|
||||
// 3.1 Notice that postgres will return the date in UTC, but it is exactlt the same
|
||||
expect(result2.rows).toEqual([{ id: 1, timestamp_string: '2022-01-01 02:00:00.123456+00' }]);
|
||||
|
||||
await db.execute(sql`drop table if exists ${table}`);
|
||||
});
|
||||
|
||||
test('test mode date for timestamp with timezone', async () => {
|
||||
const table = pgTable('all_columns', {
|
||||
id: serial('id').primaryKey(),
|
||||
timestamp: timestamp('timestamp_string', { mode: 'date', withTimezone: true, precision: 3 }).notNull(),
|
||||
});
|
||||
|
||||
await db.execute(sql`drop table if exists ${table}`);
|
||||
|
||||
await db.execute(sql`
|
||||
create table ${table} (
|
||||
id serial primary key,
|
||||
timestamp_string timestamp(3) with time zone not null
|
||||
)
|
||||
`);
|
||||
|
||||
const timestampString = new Date('2022-01-01 00:00:00.456-0200');
|
||||
|
||||
// 1. Insert date in string format with timezone in it
|
||||
await db.insert(table).values([
|
||||
{ timestamp: timestampString },
|
||||
]);
|
||||
|
||||
// 2. Select date in string format and check that the values are the same
|
||||
const result = await db.select().from(table);
|
||||
|
||||
// 2.1 Notice that postgres will return the date in UTC, but it is exactly the same
|
||||
expect(result).toEqual([{ id: 1, timestamp: timestampString }]);
|
||||
|
||||
// 3. Select as raw query and checke that values are the same
|
||||
const result2 = await db.execute<{
|
||||
id: number;
|
||||
timestamp_string: string;
|
||||
}>(sql`select * from ${table}`);
|
||||
|
||||
// 3.1 Notice that postgres will return the date in UTC, but it is exactlt the same
|
||||
expect(result2.rows).toEqual([{ id: 1, timestamp_string: '2022-01-01 02:00:00.456+00' }]);
|
||||
|
||||
await db.execute(sql`drop table if exists ${table}`);
|
||||
});
|
||||
|
||||
test('test mode string for timestamp with timezone in UTC timezone', async () => {
|
||||
// get current timezone from db
|
||||
const timezone = await db.execute<{ TimeZone: string }>(sql`show timezone`);
|
||||
|
||||
// set timezone to UTC
|
||||
await db.execute(sql`set time zone 'UTC'`);
|
||||
|
||||
const table = pgTable('all_columns', {
|
||||
id: serial('id').primaryKey(),
|
||||
timestamp: timestamp('timestamp_string', { mode: 'string', withTimezone: true, precision: 6 }).notNull(),
|
||||
});
|
||||
|
||||
await db.execute(sql`drop table if exists ${table}`);
|
||||
|
||||
await db.execute(sql`
|
||||
create table ${table} (
|
||||
id serial primary key,
|
||||
timestamp_string timestamp(6) with time zone not null
|
||||
)
|
||||
`);
|
||||
|
||||
const timestampString = '2022-01-01 00:00:00.123456-0200';
|
||||
|
||||
// 1. Insert date in string format with timezone in it
|
||||
await db.insert(table).values([
|
||||
{ timestamp: timestampString },
|
||||
]);
|
||||
|
||||
// 2. Select date in string format and check that the values are the same
|
||||
const result = await db.select().from(table);
|
||||
|
||||
// 2.1 Notice that postgres will return the date in UTC, but it is exactly the same
|
||||
expect(result).toEqual([{ id: 1, timestamp: '2022-01-01 02:00:00.123456+00' }]);
|
||||
|
||||
// 3. Select as raw query and checke that values are the same
|
||||
const result2 = await db.execute<{
|
||||
id: number;
|
||||
timestamp_string: string;
|
||||
}>(sql`select * from ${table}`);
|
||||
|
||||
// 3.1 Notice that postgres will return the date in UTC, but it is exactlt the same
|
||||
expect(result2.rows).toEqual([{ id: 1, timestamp_string: '2022-01-01 02:00:00.123456+00' }]);
|
||||
|
||||
await db.execute(sql`set time zone '${sql.raw(timezone.rows[0]!.TimeZone)}'`);
|
||||
|
||||
await db.execute(sql`drop table if exists ${table}`);
|
||||
});
|
||||
|
||||
test.skip('test mode string for timestamp with timezone in different timezone', async () => {
|
||||
// get current timezone from db
|
||||
const timezone = await db.execute<{ TimeZone: string }>(sql`show timezone`);
|
||||
|
||||
// set timezone to HST (UTC - 10)
|
||||
await db.execute(sql`set time zone 'HST'`);
|
||||
|
||||
const table = pgTable('all_columns', {
|
||||
id: serial('id').primaryKey(),
|
||||
timestamp: timestamp('timestamp_string', { mode: 'string', withTimezone: true, precision: 6 }).notNull(),
|
||||
});
|
||||
|
||||
await db.execute(sql`drop table if exists ${table}`);
|
||||
|
||||
await db.execute(sql`
|
||||
create table ${table} (
|
||||
id serial primary key,
|
||||
timestamp_string timestamp(6) with time zone not null
|
||||
)
|
||||
`);
|
||||
|
||||
const timestampString = '2022-01-01 00:00:00.123456-1000';
|
||||
|
||||
// 1. Insert date in string format with timezone in it
|
||||
await db.insert(table).values([
|
||||
{ timestamp: timestampString },
|
||||
]);
|
||||
|
||||
// 2. Select date in string format and check that the values are the same
|
||||
const result = await db.select().from(table);
|
||||
|
||||
expect(result).toEqual([{ id: 1, timestamp: '2022-01-01 00:00:00.123456-10' }]);
|
||||
|
||||
// 3. Select as raw query and checke that values are the same
|
||||
const result2 = await db.execute<{
|
||||
id: number;
|
||||
timestamp_string: string;
|
||||
}>(sql`select * from ${table}`);
|
||||
|
||||
expect(result2.rows).toEqual([{ id: 1, timestamp_string: '2022-01-01 00:00:00.123456+00' }]);
|
||||
|
||||
await db.execute(sql`set time zone '${sql.raw(timezone.rows[0]!.TimeZone)}'`);
|
||||
|
||||
await db.execute(sql`drop table if exists ${table}`);
|
||||
});
|
||||
|
||||
test('select all fields', async (ctx) => {
|
||||
const { db } = ctx.pg;
|
||||
|
||||
const now = Date.now();
|
||||
|
||||
await db.insert(usersTable).values({ name: 'John' });
|
||||
const result = await db.select().from(usersTable);
|
||||
|
||||
expect(result[0]!.createdAt).toBeInstanceOf(Date);
|
||||
expect(Math.abs(result[0]!.createdAt.getTime() - now)).toBeLessThan(3000);
|
||||
expect(result).toEqual([{ id: 1, name: 'John', verified: false, jsonb: null, createdAt: result[0]!.createdAt }]);
|
||||
});
|
||||
|
||||
test('update with returning all fields', async (ctx) => {
|
||||
const { db } = ctx.pg;
|
||||
|
||||
const now = Date.now();
|
||||
|
||||
await db.insert(usersTable).values({ name: 'John' });
|
||||
const users = await db
|
||||
.update(usersTable)
|
||||
.set({ name: 'Jane' })
|
||||
.where(eq(usersTable.name, 'John'))
|
||||
.returning();
|
||||
|
||||
expect(users[0]!.createdAt).toBeInstanceOf(Date);
|
||||
expect(Math.abs(users[0]!.createdAt.getTime() - now)).toBeLessThan(3000);
|
||||
expect(users).toEqual([
|
||||
{ id: 1, name: 'Jane', verified: false, jsonb: null, createdAt: users[0]!.createdAt },
|
||||
]);
|
||||
});
|
||||
|
||||
test('delete with returning all fields', async (ctx) => {
|
||||
const { db } = ctx.pg;
|
||||
|
||||
const now = Date.now();
|
||||
|
||||
await db.insert(usersTable).values({ name: 'John' });
|
||||
const users = await db.delete(usersTable).where(eq(usersTable.name, 'John')).returning();
|
||||
|
||||
expect(users[0]!.createdAt).toBeInstanceOf(Date);
|
||||
expect(Math.abs(users[0]!.createdAt.getTime() - now)).toBeLessThan(3000);
|
||||
expect(users).toEqual([
|
||||
{ id: 1, name: 'John', verified: false, jsonb: null, createdAt: users[0]!.createdAt },
|
||||
]);
|
||||
});
|
||||
|
||||
test('mySchema :: select all fields', async (ctx) => {
|
||||
const { db } = ctx.pg;
|
||||
|
||||
const now = Date.now();
|
||||
|
||||
await db.insert(usersMySchemaTable).values({ name: 'John' });
|
||||
const result = await db.select().from(usersMySchemaTable);
|
||||
|
||||
expect(result[0]!.createdAt).toBeInstanceOf(Date);
|
||||
expect(Math.abs(result[0]!.createdAt.getTime() - now)).toBeLessThan(3000);
|
||||
expect(result).toEqual([{ id: 1, name: 'John', verified: false, jsonb: null, createdAt: result[0]!.createdAt }]);
|
||||
});
|
||||
|
||||
test('mySchema :: delete with returning all fields', async (ctx) => {
|
||||
const { db } = ctx.pg;
|
||||
|
||||
const now = Date.now();
|
||||
|
||||
await db.insert(usersMySchemaTable).values({ name: 'John' });
|
||||
const users = await db.delete(usersMySchemaTable).where(eq(usersMySchemaTable.name, 'John')).returning();
|
||||
|
||||
expect(users[0]!.createdAt).toBeInstanceOf(Date);
|
||||
expect(Math.abs(users[0]!.createdAt.getTime() - now)).toBeLessThan(3000);
|
||||
expect(users).toEqual([{ id: 1, name: 'John', verified: false, jsonb: null, createdAt: users[0]!.createdAt }]);
|
||||
});
|
||||
|
||||
skipTests([
|
||||
'migrator : default migration strategy',
|
||||
'migrator : migrate with custom schema',
|
||||
'migrator : migrate with custom table',
|
||||
'migrator : migrate with custom table and custom schema',
|
||||
'insert via db.execute + select via db.execute',
|
||||
'insert via db.execute + returning',
|
||||
'insert via db.execute w/ query builder',
|
||||
'all date and time columns without timezone first case mode string',
|
||||
'all date and time columns without timezone third case mode date',
|
||||
'test mode string for timestamp with timezone',
|
||||
'test mode date for timestamp with timezone',
|
||||
'test mode string for timestamp with timezone in UTC timezone',
|
||||
'nested transaction rollback',
|
||||
'transaction rollback',
|
||||
'nested transaction',
|
||||
'transaction',
|
||||
'timestamp timezone',
|
||||
'test $onUpdateFn and $onUpdate works as $default',
|
||||
'select all fields',
|
||||
'update with returning all fields',
|
||||
'delete with returning all fields',
|
||||
'mySchema :: select all fields',
|
||||
'mySchema :: delete with returning all fields',
|
||||
]);
|
||||
tests();
|
||||
cacheTests();
|
||||
|
||||
beforeEach(async () => {
|
||||
await db.execute(sql`drop schema if exists public cascade`);
|
||||
await db.execute(sql`drop schema if exists ${mySchema} cascade`);
|
||||
|
||||
await db.execute(sql`create schema public`);
|
||||
await db.execute(sql`create schema ${mySchema}`);
|
||||
|
||||
await db.execute(
|
||||
sql`
|
||||
create table users (
|
||||
id serial primary key,
|
||||
name text not null,
|
||||
verified boolean not null default false,
|
||||
jsonb jsonb,
|
||||
created_at timestamptz not null default now()
|
||||
)
|
||||
`,
|
||||
);
|
||||
|
||||
await db.execute(
|
||||
sql`
|
||||
create table ${usersMySchemaTable} (
|
||||
id serial primary key,
|
||||
name text not null,
|
||||
verified boolean not null default false,
|
||||
jsonb jsonb,
|
||||
created_at timestamptz not null default now()
|
||||
)
|
||||
`,
|
||||
);
|
||||
});
|
||||
|
||||
test('insert via db.execute + select via db.execute', async () => {
|
||||
await db.execute(
|
||||
sql`insert into ${usersTable} (${sql.identifier(usersTable.name.name)}) values (${'John'})`,
|
||||
);
|
||||
|
||||
const result = await db.execute<{ id: number; name: string }>(
|
||||
sql`select id, name from "users"`,
|
||||
);
|
||||
expect(result.rows).toEqual([{ id: 1, name: 'John' }]);
|
||||
});
|
||||
|
||||
test('insert via db.execute + returning', async () => {
|
||||
const inserted = await db.execute<{ id: number; name: string }>(
|
||||
sql`insert into ${usersTable} (${
|
||||
sql.identifier(
|
||||
usersTable.name.name,
|
||||
)
|
||||
}) values (${'John'}) returning ${usersTable.id}, ${usersTable.name}`,
|
||||
);
|
||||
expect(inserted.rows).toEqual([{ id: 1, name: 'John' }]);
|
||||
});
|
||||
|
||||
test('insert via db.execute w/ query builder', async () => {
|
||||
const inserted = await db.execute<Pick<typeof usersTable.$inferSelect, 'id' | 'name'>>(
|
||||
db
|
||||
.insert(usersTable)
|
||||
.values({ name: 'John' })
|
||||
.returning({ id: usersTable.id, name: usersTable.name }),
|
||||
);
|
||||
expect(inserted.rows).toEqual([{ id: 1, name: 'John' }]);
|
||||
});
|
||||
@@ -0,0 +1,458 @@
|
||||
import { sql } from 'drizzle-orm';
|
||||
import { drizzle, type NetlifyDbDatabase } from 'drizzle-orm/netlify-db';
|
||||
import { migrate } from 'drizzle-orm/netlify-db/migrator';
|
||||
import { pgTable, serial, timestamp } from 'drizzle-orm/pg-core';
|
||||
import { beforeAll, beforeEach, expect, test } from 'vitest';
|
||||
import { skipTests } from '~/common';
|
||||
import { randomString } from '~/utils';
|
||||
import { tests, usersMigratorTable, usersTable } from './pg-common';
|
||||
import { TestCache, TestGlobalCache, tests as cacheTests } from './pg-common-cache';
|
||||
|
||||
const ENABLE_LOGGING = false;
|
||||
|
||||
let db: NetlifyDbDatabase;
|
||||
let dbGlobalCached: NetlifyDbDatabase;
|
||||
let cachedDb: NetlifyDbDatabase;
|
||||
|
||||
beforeAll(async () => {
|
||||
const connectionString = process.env['NETLIFY_DB_URL'];
|
||||
if (!connectionString) {
|
||||
throw new Error('NETLIFY_DB_URL is not defined');
|
||||
}
|
||||
|
||||
db = drizzle(connectionString, { logger: ENABLE_LOGGING });
|
||||
cachedDb = drizzle(connectionString, {
|
||||
logger: ENABLE_LOGGING,
|
||||
cache: new TestCache(),
|
||||
});
|
||||
dbGlobalCached = drizzle(connectionString, {
|
||||
logger: ENABLE_LOGGING,
|
||||
cache: new TestGlobalCache(),
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach((ctx) => {
|
||||
ctx.pg = {
|
||||
db,
|
||||
};
|
||||
ctx.cachedPg = {
|
||||
db: cachedDb,
|
||||
dbGlobalCached,
|
||||
};
|
||||
});
|
||||
|
||||
test('migrator : default migration strategy', async () => {
|
||||
await db.execute(sql`drop table if exists all_columns`);
|
||||
await db.execute(sql`drop table if exists users12`);
|
||||
await db.execute(sql`drop table if exists "drizzle"."__drizzle_migrations"`);
|
||||
|
||||
await migrate(db, { migrationsFolder: './drizzle2/pg' });
|
||||
|
||||
await db.insert(usersMigratorTable).values({ name: 'John', email: 'email' });
|
||||
|
||||
const result = await db.select().from(usersMigratorTable);
|
||||
|
||||
expect(result).toEqual([{ id: 1, name: 'John', email: 'email' }]);
|
||||
|
||||
await db.execute(sql`drop table all_columns`);
|
||||
await db.execute(sql`drop table users12`);
|
||||
await db.execute(sql`drop table "drizzle"."__drizzle_migrations"`);
|
||||
});
|
||||
|
||||
test('migrator : migrate with custom schema', async () => {
|
||||
await db.execute(sql`drop table if exists all_columns`);
|
||||
await db.execute(sql`drop table if exists users12`);
|
||||
await db.execute(sql`drop table if exists "drizzle"."__drizzle_migrations"`);
|
||||
|
||||
await migrate(db, { migrationsFolder: './drizzle2/pg', migrationsSchema: 'custom_migrations' });
|
||||
|
||||
// test if the custom migrations table was created
|
||||
const { rowCount } = await db.execute(sql`select * from custom_migrations."__drizzle_migrations";`);
|
||||
expect(rowCount && rowCount > 0).toBeTruthy();
|
||||
|
||||
// test if the migrated table are working as expected
|
||||
await db.insert(usersMigratorTable).values({ name: 'John', email: 'email' });
|
||||
const result = await db.select().from(usersMigratorTable);
|
||||
expect(result).toEqual([{ id: 1, name: 'John', email: 'email' }]);
|
||||
|
||||
await db.execute(sql`drop table all_columns`);
|
||||
await db.execute(sql`drop table users12`);
|
||||
await db.execute(sql`drop table custom_migrations."__drizzle_migrations"`);
|
||||
});
|
||||
|
||||
test('migrator : migrate with custom table', async () => {
|
||||
const customTable = randomString();
|
||||
await db.execute(sql`drop table if exists all_columns`);
|
||||
await db.execute(sql`drop table if exists users12`);
|
||||
await db.execute(sql`drop table if exists "drizzle"."__drizzle_migrations"`);
|
||||
|
||||
await migrate(db, { migrationsFolder: './drizzle2/pg', migrationsTable: customTable });
|
||||
|
||||
// test if the custom migrations table was created
|
||||
const { rowCount } = await db.execute(sql`select * from "drizzle".${sql.identifier(customTable)};`);
|
||||
expect(rowCount && rowCount > 0).toBeTruthy();
|
||||
|
||||
// test if the migrated table are working as expected
|
||||
await db.insert(usersMigratorTable).values({ name: 'John', email: 'email' });
|
||||
const result = await db.select().from(usersMigratorTable);
|
||||
expect(result).toEqual([{ id: 1, name: 'John', email: 'email' }]);
|
||||
|
||||
await db.execute(sql`drop table all_columns`);
|
||||
await db.execute(sql`drop table users12`);
|
||||
await db.execute(sql`drop table "drizzle".${sql.identifier(customTable)}`);
|
||||
});
|
||||
|
||||
test('migrator : migrate with custom table and custom schema', async () => {
|
||||
const customTable = randomString();
|
||||
await db.execute(sql`drop table if exists all_columns`);
|
||||
await db.execute(sql`drop table if exists users12`);
|
||||
await db.execute(sql`drop table if exists "drizzle"."__drizzle_migrations"`);
|
||||
|
||||
await migrate(db, {
|
||||
migrationsFolder: './drizzle2/pg',
|
||||
migrationsTable: customTable,
|
||||
migrationsSchema: 'custom_migrations',
|
||||
});
|
||||
|
||||
// test if the custom migrations table was created
|
||||
const { rowCount } = await db.execute(
|
||||
sql`select * from custom_migrations.${sql.identifier(customTable)};`,
|
||||
);
|
||||
expect(rowCount && rowCount > 0).toBeTruthy();
|
||||
|
||||
// test if the migrated table are working as expected
|
||||
await db.insert(usersMigratorTable).values({ name: 'John', email: 'email' });
|
||||
const result = await db.select().from(usersMigratorTable);
|
||||
expect(result).toEqual([{ id: 1, name: 'John', email: 'email' }]);
|
||||
|
||||
await db.execute(sql`drop table all_columns`);
|
||||
await db.execute(sql`drop table users12`);
|
||||
await db.execute(sql`drop table custom_migrations.${sql.identifier(customTable)}`);
|
||||
});
|
||||
|
||||
test('all date and time columns without timezone first case mode string', async () => {
|
||||
const table = pgTable('all_columns', {
|
||||
id: serial('id').primaryKey(),
|
||||
timestamp: timestamp('timestamp_string', { mode: 'string', precision: 6 }).notNull(),
|
||||
});
|
||||
|
||||
await db.execute(sql`drop table if exists ${table}`);
|
||||
|
||||
await db.execute(sql`
|
||||
create table ${table} (
|
||||
id serial primary key,
|
||||
timestamp_string timestamp(6) not null
|
||||
)
|
||||
`);
|
||||
|
||||
// 1. Insert date in string format without timezone in it
|
||||
await db.insert(table).values([
|
||||
{ timestamp: '2022-01-01 02:00:00.123456' },
|
||||
]);
|
||||
|
||||
// 2, Select in string format and check that values are the same
|
||||
const result = await db.select().from(table);
|
||||
|
||||
expect(result).toEqual([{ id: 1, timestamp: '2022-01-01 02:00:00.123456' }]);
|
||||
|
||||
// 3. Select as raw query and check that values are the same
|
||||
const result2 = await db.execute<{
|
||||
id: number;
|
||||
timestamp_string: string;
|
||||
}>(sql`select * from ${table}`);
|
||||
|
||||
expect(result2.rows).toEqual([{ id: 1, timestamp_string: '2022-01-01 02:00:00.123456' }]);
|
||||
|
||||
await db.execute(sql`drop table if exists ${table}`);
|
||||
});
|
||||
|
||||
test('all date and time columns without timezone second case mode string', async () => {
|
||||
const table = pgTable('all_columns', {
|
||||
id: serial('id').primaryKey(),
|
||||
timestamp: timestamp('timestamp_string', { mode: 'string', precision: 6 }).notNull(),
|
||||
});
|
||||
|
||||
await db.execute(sql`drop table if exists ${table}`);
|
||||
|
||||
await db.execute(sql`
|
||||
create table ${table} (
|
||||
id serial primary key,
|
||||
timestamp_string timestamp(6) not null
|
||||
)
|
||||
`);
|
||||
|
||||
// 1. Insert date in string format with timezone in it
|
||||
await db.insert(table).values([
|
||||
{ timestamp: '2022-01-01T02:00:00.123456-02' },
|
||||
]);
|
||||
|
||||
// 2, Select as raw query and check that values are the same
|
||||
const result = await db.execute<{
|
||||
id: number;
|
||||
timestamp_string: string;
|
||||
}>(sql`select * from ${table}`);
|
||||
|
||||
expect(result.rows).toEqual([{ id: 1, timestamp_string: '2022-01-01 02:00:00.123456' }]);
|
||||
|
||||
await db.execute(sql`drop table if exists ${table}`);
|
||||
});
|
||||
|
||||
test('all date and time columns without timezone third case mode date', async () => {
|
||||
const table = pgTable('all_columns', {
|
||||
id: serial('id').primaryKey(),
|
||||
timestamp: timestamp('timestamp_string', { mode: 'date', precision: 3 }).notNull(),
|
||||
});
|
||||
|
||||
await db.execute(sql`drop table if exists ${table}`);
|
||||
|
||||
await db.execute(sql`
|
||||
create table ${table} (
|
||||
id serial primary key,
|
||||
timestamp_string timestamp(3) not null
|
||||
)
|
||||
`);
|
||||
|
||||
const insertedDate = new Date('2022-01-01 20:00:00.123+04');
|
||||
|
||||
// 1. Insert date as new date
|
||||
await db.insert(table).values([
|
||||
{ timestamp: insertedDate },
|
||||
]);
|
||||
|
||||
// 2, Select as raw query as string
|
||||
const result = await db.execute<{
|
||||
id: number;
|
||||
timestamp_string: string;
|
||||
}>(sql`select * from ${table}`);
|
||||
|
||||
// 3. Compare both dates using orm mapping - Need to add 'Z' to tell JS that it is UTC
|
||||
expect(new Date(result.rows[0]!.timestamp_string + 'Z').getTime()).toBe(insertedDate.getTime());
|
||||
|
||||
await db.execute(sql`drop table if exists ${table}`);
|
||||
});
|
||||
|
||||
test('test mode string for timestamp with timezone', async () => {
|
||||
const table = pgTable('all_columns', {
|
||||
id: serial('id').primaryKey(),
|
||||
timestamp: timestamp('timestamp_string', { mode: 'string', withTimezone: true, precision: 6 }).notNull(),
|
||||
});
|
||||
|
||||
await db.execute(sql`drop table if exists ${table}`);
|
||||
|
||||
await db.execute(sql`
|
||||
create table ${table} (
|
||||
id serial primary key,
|
||||
timestamp_string timestamp(6) with time zone not null
|
||||
)
|
||||
`);
|
||||
|
||||
const timestampString = '2022-01-01 00:00:00.123456-0200';
|
||||
|
||||
// 1. Insert date in string format with timezone in it
|
||||
await db.insert(table).values([
|
||||
{ timestamp: timestampString },
|
||||
]);
|
||||
|
||||
// 2. Select date in string format and check that the values are the same
|
||||
const result = await db.select().from(table);
|
||||
|
||||
// 2.1 Notice that postgres will return the date in UTC, but it is exactly the same
|
||||
expect(result).toEqual([{ id: 1, timestamp: '2022-01-01 02:00:00.123456+00' }]);
|
||||
|
||||
// 3. Select as raw query and checke that values are the same
|
||||
const result2 = await db.execute<{
|
||||
id: number;
|
||||
timestamp_string: string;
|
||||
}>(sql`select * from ${table}`);
|
||||
|
||||
// 3.1 Notice that postgres will return the date in UTC, but it is exactlt the same
|
||||
expect(result2.rows).toEqual([{ id: 1, timestamp_string: '2022-01-01 02:00:00.123456+00' }]);
|
||||
|
||||
await db.execute(sql`drop table if exists ${table}`);
|
||||
});
|
||||
|
||||
test('test mode date for timestamp with timezone', async () => {
|
||||
const table = pgTable('all_columns', {
|
||||
id: serial('id').primaryKey(),
|
||||
timestamp: timestamp('timestamp_string', { mode: 'date', withTimezone: true, precision: 3 }).notNull(),
|
||||
});
|
||||
|
||||
await db.execute(sql`drop table if exists ${table}`);
|
||||
|
||||
await db.execute(sql`
|
||||
create table ${table} (
|
||||
id serial primary key,
|
||||
timestamp_string timestamp(3) with time zone not null
|
||||
)
|
||||
`);
|
||||
|
||||
const timestampString = new Date('2022-01-01 00:00:00.456-0200');
|
||||
|
||||
// 1. Insert date in string format with timezone in it
|
||||
await db.insert(table).values([
|
||||
{ timestamp: timestampString },
|
||||
]);
|
||||
|
||||
// 2. Select date in string format and check that the values are the same
|
||||
const result = await db.select().from(table);
|
||||
|
||||
// 2.1 Notice that postgres will return the date in UTC, but it is exactly the same
|
||||
expect(result).toEqual([{ id: 1, timestamp: timestampString }]);
|
||||
|
||||
// 3. Select as raw query and checke that values are the same
|
||||
const result2 = await db.execute<{
|
||||
id: number;
|
||||
timestamp_string: string;
|
||||
}>(sql`select * from ${table}`);
|
||||
|
||||
// 3.1 Notice that postgres will return the date in UTC, but it is exactlt the same
|
||||
expect(result2.rows).toEqual([{ id: 1, timestamp_string: '2022-01-01 02:00:00.456+00' }]);
|
||||
|
||||
await db.execute(sql`drop table if exists ${table}`);
|
||||
});
|
||||
|
||||
test('test mode string for timestamp with timezone in UTC timezone', async () => {
|
||||
// get current timezone from db
|
||||
const timezone = await db.execute<{ TimeZone: string }>(sql`show timezone`);
|
||||
|
||||
// set timezone to UTC
|
||||
await db.execute(sql`set time zone 'UTC'`);
|
||||
|
||||
const table = pgTable('all_columns', {
|
||||
id: serial('id').primaryKey(),
|
||||
timestamp: timestamp('timestamp_string', { mode: 'string', withTimezone: true, precision: 6 }).notNull(),
|
||||
});
|
||||
|
||||
await db.execute(sql`drop table if exists ${table}`);
|
||||
|
||||
await db.execute(sql`
|
||||
create table ${table} (
|
||||
id serial primary key,
|
||||
timestamp_string timestamp(6) with time zone not null
|
||||
)
|
||||
`);
|
||||
|
||||
const timestampString = '2022-01-01 00:00:00.123456-0200';
|
||||
|
||||
// 1. Insert date in string format with timezone in it
|
||||
await db.insert(table).values([
|
||||
{ timestamp: timestampString },
|
||||
]);
|
||||
|
||||
// 2. Select date in string format and check that the values are the same
|
||||
const result = await db.select().from(table);
|
||||
|
||||
// 2.1 Notice that postgres will return the date in UTC, but it is exactly the same
|
||||
expect(result).toEqual([{ id: 1, timestamp: '2022-01-01 02:00:00.123456+00' }]);
|
||||
|
||||
// 3. Select as raw query and checke that values are the same
|
||||
const result2 = await db.execute<{
|
||||
id: number;
|
||||
timestamp_string: string;
|
||||
}>(sql`select * from ${table}`);
|
||||
|
||||
// 3.1 Notice that postgres will return the date in UTC, but it is exactlt the same
|
||||
expect(result2.rows).toEqual([{ id: 1, timestamp_string: '2022-01-01 02:00:00.123456+00' }]);
|
||||
|
||||
await db.execute(sql`set time zone '${sql.raw(timezone.rows[0]!.TimeZone)}'`);
|
||||
|
||||
await db.execute(sql`drop table if exists ${table}`);
|
||||
});
|
||||
|
||||
test.skip('test mode string for timestamp with timezone in different timezone', async () => {
|
||||
// get current timezone from db
|
||||
const timezone = await db.execute<{ TimeZone: string }>(sql`show timezone`);
|
||||
|
||||
// set timezone to HST (UTC - 10)
|
||||
await db.execute(sql`set time zone 'HST'`);
|
||||
|
||||
const table = pgTable('all_columns', {
|
||||
id: serial('id').primaryKey(),
|
||||
timestamp: timestamp('timestamp_string', { mode: 'string', withTimezone: true, precision: 6 }).notNull(),
|
||||
});
|
||||
|
||||
await db.execute(sql`drop table if exists ${table}`);
|
||||
|
||||
await db.execute(sql`
|
||||
create table ${table} (
|
||||
id serial primary key,
|
||||
timestamp_string timestamp(6) with time zone not null
|
||||
)
|
||||
`);
|
||||
|
||||
const timestampString = '2022-01-01 00:00:00.123456-1000';
|
||||
|
||||
// 1. Insert date in string format with timezone in it
|
||||
await db.insert(table).values([
|
||||
{ timestamp: timestampString },
|
||||
]);
|
||||
|
||||
// 2. Select date in string format and check that the values are the same
|
||||
const result = await db.select().from(table);
|
||||
|
||||
expect(result).toEqual([{ id: 1, timestamp: '2022-01-01 00:00:00.123456-10' }]);
|
||||
|
||||
// 3. Select as raw query and checke that values are the same
|
||||
const result2 = await db.execute<{
|
||||
id: number;
|
||||
timestamp_string: string;
|
||||
}>(sql`select * from ${table}`);
|
||||
|
||||
expect(result2.rows).toEqual([{ id: 1, timestamp_string: '2022-01-01 00:00:00.123456+00' }]);
|
||||
|
||||
await db.execute(sql`set time zone '${sql.raw(timezone.rows[0]!.TimeZone)}'`);
|
||||
|
||||
await db.execute(sql`drop table if exists ${table}`);
|
||||
});
|
||||
|
||||
skipTests([]);
|
||||
tests();
|
||||
cacheTests();
|
||||
|
||||
beforeEach(async () => {
|
||||
await db.execute(sql`drop schema if exists public cascade`);
|
||||
await db.execute(sql`create schema public`);
|
||||
await db.execute(
|
||||
sql`
|
||||
create table users (
|
||||
id serial primary key,
|
||||
name text not null,
|
||||
verified boolean not null default false,
|
||||
jsonb jsonb,
|
||||
created_at timestamptz not null default now()
|
||||
)
|
||||
`,
|
||||
);
|
||||
});
|
||||
|
||||
test('insert via db.execute + select via db.execute', async () => {
|
||||
await db.execute(
|
||||
sql`insert into ${usersTable} (${sql.identifier(usersTable.name.name)}) values (${'John'})`,
|
||||
);
|
||||
|
||||
const result = await db.execute<{ id: number; name: string }>(
|
||||
sql`select id, name from "users"`,
|
||||
);
|
||||
expect(result.rows).toEqual([{ id: 1, name: 'John' }]);
|
||||
});
|
||||
|
||||
test('insert via db.execute + returning', async () => {
|
||||
const inserted = await db.execute<{ id: number; name: string }>(
|
||||
sql`insert into ${usersTable} (${
|
||||
sql.identifier(
|
||||
usersTable.name.name,
|
||||
)
|
||||
}) values (${'John'}) returning ${usersTable.id}, ${usersTable.name}`,
|
||||
);
|
||||
expect(inserted.rows).toEqual([{ id: 1, name: 'John' }]);
|
||||
});
|
||||
|
||||
test('insert via db.execute w/ query builder', async () => {
|
||||
const inserted = await db.execute<Pick<typeof usersTable.$inferSelect, 'id' | 'name'>>(
|
||||
db
|
||||
.insert(usersTable)
|
||||
.values({ name: 'John' })
|
||||
.returning({ id: usersTable.id, name: usersTable.name }),
|
||||
);
|
||||
expect(inserted.rows).toEqual([{ id: 1, name: 'John' }]);
|
||||
});
|
||||
@@ -0,0 +1,492 @@
|
||||
import retry from 'async-retry';
|
||||
import { sql } from 'drizzle-orm';
|
||||
import type { NodePgDatabase } from 'drizzle-orm/node-postgres';
|
||||
import { drizzle } from 'drizzle-orm/node-postgres';
|
||||
import { migrate } from 'drizzle-orm/node-postgres/migrator';
|
||||
import { pgTable, serial, timestamp } from 'drizzle-orm/pg-core';
|
||||
import { Client } from 'pg';
|
||||
import { afterAll, beforeAll, beforeEach, expect, test } from 'vitest';
|
||||
import { skipTests } from '~/common';
|
||||
import { randomString } from '~/utils';
|
||||
import { createDockerDB, tests, usersMigratorTable, usersTable } from './pg-common';
|
||||
import { TestCache, TestGlobalCache, tests as cacheTests } from './pg-common-cache';
|
||||
|
||||
const ENABLE_LOGGING = false;
|
||||
|
||||
let db: NodePgDatabase;
|
||||
let client: Client;
|
||||
let dbGlobalCached: NodePgDatabase;
|
||||
let cachedDb: NodePgDatabase;
|
||||
|
||||
beforeAll(async () => {
|
||||
let connectionString;
|
||||
if (process.env['PG_CONNECTION_STRING']) {
|
||||
connectionString = process.env['PG_CONNECTION_STRING'];
|
||||
} else {
|
||||
const { connectionString: conStr } = await createDockerDB();
|
||||
connectionString = conStr;
|
||||
}
|
||||
client = await retry(async () => {
|
||||
client = new Client(connectionString);
|
||||
await client.connect();
|
||||
return client;
|
||||
}, {
|
||||
retries: 20,
|
||||
factor: 1,
|
||||
minTimeout: 250,
|
||||
maxTimeout: 250,
|
||||
randomize: false,
|
||||
onRetry() {
|
||||
client?.end();
|
||||
},
|
||||
});
|
||||
db = drizzle(client, { logger: ENABLE_LOGGING });
|
||||
cachedDb = drizzle(client, { logger: ENABLE_LOGGING, cache: new TestCache() });
|
||||
dbGlobalCached = drizzle(client, { logger: ENABLE_LOGGING, cache: new TestGlobalCache() });
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await client?.end();
|
||||
});
|
||||
|
||||
beforeEach((ctx) => {
|
||||
ctx.pg = {
|
||||
db,
|
||||
};
|
||||
ctx.cachedPg = {
|
||||
db: cachedDb,
|
||||
dbGlobalCached,
|
||||
};
|
||||
});
|
||||
|
||||
test('migrator : default migration strategy', async () => {
|
||||
await db.execute(sql`drop table if exists all_columns`);
|
||||
await db.execute(sql`drop table if exists users12`);
|
||||
await db.execute(sql`drop table if exists "drizzle"."__drizzle_migrations"`);
|
||||
|
||||
await migrate(db, { migrationsFolder: './drizzle2/pg' });
|
||||
|
||||
await db.insert(usersMigratorTable).values({ name: 'John', email: 'email' });
|
||||
|
||||
const result = await db.select().from(usersMigratorTable);
|
||||
|
||||
expect(result).toEqual([{ id: 1, name: 'John', email: 'email' }]);
|
||||
|
||||
await db.execute(sql`drop table all_columns`);
|
||||
await db.execute(sql`drop table users12`);
|
||||
await db.execute(sql`drop table "drizzle"."__drizzle_migrations"`);
|
||||
});
|
||||
|
||||
test('migrator : migrate with custom schema', async () => {
|
||||
const customSchema = randomString();
|
||||
await db.execute(sql`drop table if exists all_columns`);
|
||||
await db.execute(sql`drop table if exists users12`);
|
||||
await db.execute(sql`drop table if exists "drizzle"."__drizzle_migrations"`);
|
||||
|
||||
await migrate(db, { migrationsFolder: './drizzle2/pg', migrationsSchema: customSchema });
|
||||
|
||||
// test if the custom migrations table was created
|
||||
const { rowCount } = await db.execute(sql`select * from ${sql.identifier(customSchema)}."__drizzle_migrations";`);
|
||||
expect(rowCount && rowCount > 0).toBeTruthy();
|
||||
|
||||
// test if the migrated table are working as expected
|
||||
await db.insert(usersMigratorTable).values({ name: 'John', email: 'email' });
|
||||
const result = await db.select().from(usersMigratorTable);
|
||||
expect(result).toEqual([{ id: 1, name: 'John', email: 'email' }]);
|
||||
|
||||
await db.execute(sql`drop table all_columns`);
|
||||
await db.execute(sql`drop table users12`);
|
||||
await db.execute(sql`drop table ${sql.identifier(customSchema)}."__drizzle_migrations"`);
|
||||
});
|
||||
|
||||
test('migrator : migrate with custom table', async () => {
|
||||
const customTable = randomString();
|
||||
await db.execute(sql`drop table if exists all_columns`);
|
||||
await db.execute(sql`drop table if exists users12`);
|
||||
await db.execute(sql`drop table if exists "drizzle"."__drizzle_migrations"`);
|
||||
|
||||
await migrate(db, { migrationsFolder: './drizzle2/pg', migrationsTable: customTable });
|
||||
|
||||
// test if the custom migrations table was created
|
||||
const { rowCount } = await db.execute(sql`select * from "drizzle".${sql.identifier(customTable)};`);
|
||||
expect(rowCount && rowCount > 0).toBeTruthy();
|
||||
|
||||
// test if the migrated table are working as expected
|
||||
await db.insert(usersMigratorTable).values({ name: 'John', email: 'email' });
|
||||
const result = await db.select().from(usersMigratorTable);
|
||||
expect(result).toEqual([{ id: 1, name: 'John', email: 'email' }]);
|
||||
|
||||
await db.execute(sql`drop table all_columns`);
|
||||
await db.execute(sql`drop table users12`);
|
||||
await db.execute(sql`drop table "drizzle".${sql.identifier(customTable)}`);
|
||||
});
|
||||
|
||||
test('migrator : migrate with custom table and custom schema', async () => {
|
||||
const customTable = randomString();
|
||||
const customSchema = randomString();
|
||||
await db.execute(sql`drop table if exists all_columns`);
|
||||
await db.execute(sql`drop table if exists users12`);
|
||||
await db.execute(sql`drop table if exists "drizzle"."__drizzle_migrations"`);
|
||||
|
||||
await migrate(db, {
|
||||
migrationsFolder: './drizzle2/pg',
|
||||
migrationsTable: customTable,
|
||||
migrationsSchema: customSchema,
|
||||
});
|
||||
|
||||
// test if the custom migrations table was created
|
||||
const { rowCount } = await db.execute(
|
||||
sql`select * from ${sql.identifier(customSchema)}.${sql.identifier(customTable)};`,
|
||||
);
|
||||
expect(rowCount && rowCount > 0).toBeTruthy();
|
||||
|
||||
// test if the migrated table are working as expected
|
||||
await db.insert(usersMigratorTable).values({ name: 'John', email: 'email' });
|
||||
const result = await db.select().from(usersMigratorTable);
|
||||
expect(result).toEqual([{ id: 1, name: 'John', email: 'email' }]);
|
||||
|
||||
await db.execute(sql`drop table all_columns`);
|
||||
await db.execute(sql`drop table users12`);
|
||||
await db.execute(sql`drop table ${sql.identifier(customSchema)}.${sql.identifier(customTable)}`);
|
||||
});
|
||||
|
||||
test('all date and time columns without timezone first case mode string', async () => {
|
||||
const table = pgTable('all_columns', {
|
||||
id: serial('id').primaryKey(),
|
||||
timestamp: timestamp('timestamp_string', { mode: 'string', precision: 6 }).notNull(),
|
||||
});
|
||||
|
||||
await db.execute(sql`drop table if exists ${table}`);
|
||||
|
||||
await db.execute(sql`
|
||||
create table ${table} (
|
||||
id serial primary key,
|
||||
timestamp_string timestamp(6) not null
|
||||
)
|
||||
`);
|
||||
|
||||
// 1. Insert date in string format without timezone in it
|
||||
await db.insert(table).values([
|
||||
{ timestamp: '2022-01-01 02:00:00.123456' },
|
||||
]);
|
||||
|
||||
// 2, Select in string format and check that values are the same
|
||||
const result = await db.select().from(table);
|
||||
|
||||
expect(result).toEqual([{ id: 1, timestamp: '2022-01-01 02:00:00.123456' }]);
|
||||
|
||||
// 3. Select as raw query and check that values are the same
|
||||
const result2 = await db.execute<{
|
||||
id: number;
|
||||
timestamp_string: string;
|
||||
}>(sql`select * from ${table}`);
|
||||
|
||||
expect(result2.rows).toEqual([{ id: 1, timestamp_string: '2022-01-01 02:00:00.123456' }]);
|
||||
|
||||
await db.execute(sql`drop table if exists ${table}`);
|
||||
});
|
||||
|
||||
test('all date and time columns without timezone second case mode string', async () => {
|
||||
const table = pgTable('all_columns', {
|
||||
id: serial('id').primaryKey(),
|
||||
timestamp: timestamp('timestamp_string', { mode: 'string', precision: 6 }).notNull(),
|
||||
});
|
||||
|
||||
await db.execute(sql`drop table if exists ${table}`);
|
||||
|
||||
await db.execute(sql`
|
||||
create table ${table} (
|
||||
id serial primary key,
|
||||
timestamp_string timestamp(6) not null
|
||||
)
|
||||
`);
|
||||
|
||||
// 1. Insert date in string format with timezone in it
|
||||
await db.insert(table).values([
|
||||
{ timestamp: '2022-01-01T02:00:00.123456-02' },
|
||||
]);
|
||||
|
||||
// 2, Select as raw query and check that values are the same
|
||||
const result = await db.execute<{
|
||||
id: number;
|
||||
timestamp_string: string;
|
||||
}>(sql`select * from ${table}`);
|
||||
|
||||
expect(result.rows).toEqual([{ id: 1, timestamp_string: '2022-01-01 02:00:00.123456' }]);
|
||||
|
||||
await db.execute(sql`drop table if exists ${table}`);
|
||||
});
|
||||
|
||||
test('all date and time columns without timezone third case mode date', async () => {
|
||||
const table = pgTable('all_columns', {
|
||||
id: serial('id').primaryKey(),
|
||||
timestamp: timestamp('timestamp_string', { mode: 'date', precision: 3 }).notNull(),
|
||||
});
|
||||
|
||||
await db.execute(sql`drop table if exists ${table}`);
|
||||
|
||||
await db.execute(sql`
|
||||
create table ${table} (
|
||||
id serial primary key,
|
||||
timestamp_string timestamp(3) not null
|
||||
)
|
||||
`);
|
||||
|
||||
const insertedDate = new Date('2022-01-01 20:00:00.123+04');
|
||||
|
||||
// 1. Insert date as new date
|
||||
await db.insert(table).values([
|
||||
{ timestamp: insertedDate },
|
||||
]);
|
||||
|
||||
// 2, Select as raw query as string
|
||||
const result = await db.execute<{
|
||||
id: number;
|
||||
timestamp_string: string;
|
||||
}>(sql`select * from ${table}`);
|
||||
|
||||
// 3. Compare both dates using orm mapping - Need to add 'Z' to tell JS that it is UTC
|
||||
expect(new Date(result.rows[0]!.timestamp_string + 'Z').getTime()).toBe(insertedDate.getTime());
|
||||
|
||||
await db.execute(sql`drop table if exists ${table}`);
|
||||
});
|
||||
|
||||
test('test mode string for timestamp with timezone', async () => {
|
||||
const table = pgTable('all_columns', {
|
||||
id: serial('id').primaryKey(),
|
||||
timestamp: timestamp('timestamp_string', { mode: 'string', withTimezone: true, precision: 6 }).notNull(),
|
||||
});
|
||||
|
||||
await db.execute(sql`drop table if exists ${table}`);
|
||||
|
||||
await db.execute(sql`
|
||||
create table ${table} (
|
||||
id serial primary key,
|
||||
timestamp_string timestamp(6) with time zone not null
|
||||
)
|
||||
`);
|
||||
|
||||
const timestampString = '2022-01-01 00:00:00.123456-0200';
|
||||
|
||||
// 1. Insert date in string format with timezone in it
|
||||
await db.insert(table).values([
|
||||
{ timestamp: timestampString },
|
||||
]);
|
||||
|
||||
// 2. Select date in string format and check that the values are the same
|
||||
const result = await db.select().from(table);
|
||||
|
||||
// 2.1 Notice that postgres will return the date in UTC, but it is exactly the same
|
||||
expect(result).toEqual([{ id: 1, timestamp: '2022-01-01 02:00:00.123456+00' }]);
|
||||
|
||||
// 3. Select as raw query and checke that values are the same
|
||||
const result2 = await db.execute<{
|
||||
id: number;
|
||||
timestamp_string: string;
|
||||
}>(sql`select * from ${table}`);
|
||||
|
||||
// 3.1 Notice that postgres will return the date in UTC, but it is exactlt the same
|
||||
expect(result2.rows).toEqual([{ id: 1, timestamp_string: '2022-01-01 02:00:00.123456+00' }]);
|
||||
|
||||
await db.execute(sql`drop table if exists ${table}`);
|
||||
});
|
||||
|
||||
test('test mode date for timestamp with timezone', async () => {
|
||||
const table = pgTable('all_columns', {
|
||||
id: serial('id').primaryKey(),
|
||||
timestamp: timestamp('timestamp_string', { mode: 'date', withTimezone: true, precision: 3 }).notNull(),
|
||||
});
|
||||
|
||||
await db.execute(sql`drop table if exists ${table}`);
|
||||
|
||||
await db.execute(sql`
|
||||
create table ${table} (
|
||||
id serial primary key,
|
||||
timestamp_string timestamp(3) with time zone not null
|
||||
)
|
||||
`);
|
||||
|
||||
const timestampString = new Date('2022-01-01 00:00:00.456-0200');
|
||||
|
||||
// 1. Insert date in string format with timezone in it
|
||||
await db.insert(table).values([
|
||||
{ timestamp: timestampString },
|
||||
]);
|
||||
|
||||
// 2. Select date in string format and check that the values are the same
|
||||
const result = await db.select().from(table);
|
||||
|
||||
// 2.1 Notice that postgres will return the date in UTC, but it is exactly the same
|
||||
expect(result).toEqual([{ id: 1, timestamp: timestampString }]);
|
||||
|
||||
// 3. Select as raw query and checke that values are the same
|
||||
const result2 = await db.execute<{
|
||||
id: number;
|
||||
timestamp_string: string;
|
||||
}>(sql`select * from ${table}`);
|
||||
|
||||
// 3.1 Notice that postgres will return the date in UTC, but it is exactlt the same
|
||||
expect(result2.rows).toEqual([{ id: 1, timestamp_string: '2022-01-01 02:00:00.456+00' }]);
|
||||
|
||||
await db.execute(sql`drop table if exists ${table}`);
|
||||
});
|
||||
|
||||
test('test mode string for timestamp with timezone in UTC timezone', async () => {
|
||||
// get current timezone from db
|
||||
const timezone = await db.execute<{ TimeZone: string }>(sql`show timezone`);
|
||||
|
||||
// set timezone to UTC
|
||||
await db.execute(sql`set time zone 'UTC'`);
|
||||
|
||||
const table = pgTable('all_columns', {
|
||||
id: serial('id').primaryKey(),
|
||||
timestamp: timestamp('timestamp_string', { mode: 'string', withTimezone: true, precision: 6 }).notNull(),
|
||||
});
|
||||
|
||||
await db.execute(sql`drop table if exists ${table}`);
|
||||
|
||||
await db.execute(sql`
|
||||
create table ${table} (
|
||||
id serial primary key,
|
||||
timestamp_string timestamp(6) with time zone not null
|
||||
)
|
||||
`);
|
||||
|
||||
const timestampString = '2022-01-01 00:00:00.123456-0200';
|
||||
|
||||
// 1. Insert date in string format with timezone in it
|
||||
await db.insert(table).values([
|
||||
{ timestamp: timestampString },
|
||||
]);
|
||||
|
||||
// 2. Select date in string format and check that the values are the same
|
||||
const result = await db.select().from(table);
|
||||
|
||||
// 2.1 Notice that postgres will return the date in UTC, but it is exactly the same
|
||||
expect(result).toEqual([{ id: 1, timestamp: '2022-01-01 02:00:00.123456+00' }]);
|
||||
|
||||
// 3. Select as raw query and checke that values are the same
|
||||
const result2 = await db.execute<{
|
||||
id: number;
|
||||
timestamp_string: string;
|
||||
}>(sql`select * from ${table}`);
|
||||
|
||||
// 3.1 Notice that postgres will return the date in UTC, but it is exactlt the same
|
||||
expect(result2.rows).toEqual([{ id: 1, timestamp_string: '2022-01-01 02:00:00.123456+00' }]);
|
||||
|
||||
await db.execute(sql`set time zone '${sql.raw(timezone.rows[0]!.TimeZone)}'`);
|
||||
|
||||
await db.execute(sql`drop table if exists ${table}`);
|
||||
});
|
||||
|
||||
test('test mode string for timestamp with timezone in different timezone', async () => {
|
||||
// get current timezone from db
|
||||
const timezone = await db.execute<{ TimeZone: string }>(sql`show timezone`);
|
||||
|
||||
// set timezone to HST (UTC - 10)
|
||||
await db.execute(sql`set time zone '-10'`);
|
||||
|
||||
const table = pgTable('all_columns', {
|
||||
id: serial('id').primaryKey(),
|
||||
timestamp: timestamp('timestamp_string', { mode: 'string', withTimezone: true, precision: 6 }).notNull(),
|
||||
});
|
||||
|
||||
await db.execute(sql`drop table if exists ${table}`);
|
||||
|
||||
await db.execute(sql`
|
||||
create table ${table} (
|
||||
id serial primary key,
|
||||
timestamp_string timestamp(6) with time zone not null
|
||||
)
|
||||
`);
|
||||
|
||||
const timestampString = '2022-01-01 00:00:00.123456-1000';
|
||||
|
||||
// 1. Insert date in string format with timezone in it
|
||||
await db.insert(table).values([
|
||||
{ timestamp: timestampString },
|
||||
]);
|
||||
|
||||
// 2. Select date in string format and check that the values are the same
|
||||
const result = await db.select().from(table);
|
||||
|
||||
expect(result).toEqual([{ id: 1, timestamp: '2022-01-01 00:00:00.123456-10' }]);
|
||||
|
||||
// 3. Select as raw query and checke that values are the same
|
||||
const result2 = await db.execute<{
|
||||
id: number;
|
||||
timestamp_string: string;
|
||||
}>(sql`select * from ${table}`);
|
||||
|
||||
expect(result2.rows).toEqual([{ id: 1, timestamp_string: '2022-01-01 00:00:00.123456-10' }]);
|
||||
|
||||
await db.execute(sql`set time zone '${sql.raw(timezone.rows[0]!.TimeZone)}'`);
|
||||
|
||||
await db.execute(sql`drop table if exists ${table}`);
|
||||
});
|
||||
|
||||
skipTests([
|
||||
'migrator : default migration strategy',
|
||||
'migrator : migrate with custom schema',
|
||||
'migrator : migrate with custom table',
|
||||
'migrator : migrate with custom table and custom schema',
|
||||
'insert via db.execute + select via db.execute',
|
||||
'insert via db.execute + returning',
|
||||
'insert via db.execute w/ query builder',
|
||||
'all date and time columns without timezone first case mode string',
|
||||
'all date and time columns without timezone third case mode date',
|
||||
'test mode string for timestamp with timezone',
|
||||
'test mode date for timestamp with timezone',
|
||||
'test mode string for timestamp with timezone in UTC timezone',
|
||||
'test mode string for timestamp with timezone in different timezone',
|
||||
]);
|
||||
tests();
|
||||
cacheTests();
|
||||
|
||||
beforeEach(async () => {
|
||||
await db.execute(sql`drop schema if exists public cascade`);
|
||||
await db.execute(sql`create schema public`);
|
||||
await db.execute(
|
||||
sql`
|
||||
create table users (
|
||||
id serial primary key,
|
||||
name text not null,
|
||||
verified boolean not null default false,
|
||||
jsonb jsonb,
|
||||
created_at timestamptz not null default now()
|
||||
)
|
||||
`,
|
||||
);
|
||||
});
|
||||
|
||||
test('insert via db.execute + select via db.execute', async () => {
|
||||
await db.execute(
|
||||
sql`insert into ${usersTable} (${sql.identifier(usersTable.name.name)}) values (${'John'})`,
|
||||
);
|
||||
|
||||
const result = await db.execute<{ id: number; name: string }>(
|
||||
sql`select id, name from "users"`,
|
||||
);
|
||||
expect(result.rows).toEqual([{ id: 1, name: 'John' }]);
|
||||
});
|
||||
|
||||
test('insert via db.execute + returning', async () => {
|
||||
const inserted = await db.execute<{ id: number; name: string }>(
|
||||
sql`insert into ${usersTable} (${
|
||||
sql.identifier(
|
||||
usersTable.name.name,
|
||||
)
|
||||
}) values (${'John'}) returning ${usersTable.id}, ${usersTable.name}`,
|
||||
);
|
||||
expect(inserted.rows).toEqual([{ id: 1, name: 'John' }]);
|
||||
});
|
||||
|
||||
test('insert via db.execute w/ query builder', async () => {
|
||||
const inserted = await db.execute<Pick<typeof usersTable.$inferSelect, 'id' | 'name'>>(
|
||||
db
|
||||
.insert(usersTable)
|
||||
.values({ name: 'John' })
|
||||
.returning({ id: usersTable.id, name: usersTable.name }),
|
||||
);
|
||||
expect(inserted.rows).toEqual([{ id: 1, name: 'John' }]);
|
||||
});
|
||||
@@ -0,0 +1,400 @@
|
||||
import type Docker from 'dockerode';
|
||||
import { eq, getTableName, is, sql, Table } from 'drizzle-orm';
|
||||
import type { MutationOption } from 'drizzle-orm/cache/core';
|
||||
import { Cache } from 'drizzle-orm/cache/core';
|
||||
import type { CacheConfig } from 'drizzle-orm/cache/core/types';
|
||||
import type { PgDatabase, PgQueryResultHKT } from 'drizzle-orm/pg-core';
|
||||
import { alias, boolean, integer, jsonb, pgTable, serial, text, timestamp } from 'drizzle-orm/pg-core';
|
||||
import Keyv from 'keyv';
|
||||
import { afterAll, beforeEach, describe, expect, test, vi } from 'vitest';
|
||||
|
||||
// eslint-disable-next-line drizzle-internal/require-entity-kind
|
||||
export class TestGlobalCache extends Cache {
|
||||
private globalTtl: number = 1000;
|
||||
private usedTablesPerKey: Record<string, string[]> = {};
|
||||
|
||||
constructor(private kv: Keyv = new Keyv()) {
|
||||
super();
|
||||
}
|
||||
|
||||
override strategy(): 'explicit' | 'all' {
|
||||
return 'all';
|
||||
}
|
||||
override async get(key: string, _tables: string[], _isTag: boolean): Promise<any[] | undefined> {
|
||||
const res = await this.kv.get(key) ?? undefined;
|
||||
return res;
|
||||
}
|
||||
override async put(
|
||||
key: string,
|
||||
response: any,
|
||||
tables: string[],
|
||||
isTag: boolean,
|
||||
config?: CacheConfig,
|
||||
): Promise<void> {
|
||||
await this.kv.set(key, response, config ? config.ex : this.globalTtl);
|
||||
for (const table of tables) {
|
||||
const keys = this.usedTablesPerKey[table];
|
||||
if (keys === undefined) {
|
||||
this.usedTablesPerKey[table] = [key];
|
||||
} else {
|
||||
keys.push(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
override async onMutate(params: MutationOption): Promise<void> {
|
||||
const tagsArray = params.tags ? Array.isArray(params.tags) ? params.tags : [params.tags] : [];
|
||||
const tablesArray = params.tables ? Array.isArray(params.tables) ? params.tables : [params.tables] : [];
|
||||
|
||||
const keysToDelete = new Set<string>();
|
||||
|
||||
for (const table of tablesArray) {
|
||||
const tableName = is(table, Table) ? getTableName(table) : table as string;
|
||||
const keys = this.usedTablesPerKey[tableName] ?? [];
|
||||
for (const key of keys) keysToDelete.add(key);
|
||||
}
|
||||
|
||||
if (keysToDelete.size > 0 || tagsArray.length > 0) {
|
||||
for (const tag of tagsArray) {
|
||||
await this.kv.delete(tag);
|
||||
}
|
||||
|
||||
for (const key of keysToDelete) {
|
||||
await this.kv.delete(key);
|
||||
for (const table of tablesArray) {
|
||||
const tableName = is(table, Table) ? getTableName(table) : table as string;
|
||||
this.usedTablesPerKey[tableName] = [];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line drizzle-internal/require-entity-kind
|
||||
export class TestCache extends TestGlobalCache {
|
||||
override strategy(): 'explicit' | 'all' {
|
||||
return 'explicit';
|
||||
}
|
||||
}
|
||||
|
||||
declare module 'vitest' {
|
||||
interface TestContext {
|
||||
cachedPg: {
|
||||
db: PgDatabase<PgQueryResultHKT>;
|
||||
dbGlobalCached: PgDatabase<PgQueryResultHKT>;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const usersTable = pgTable('users', {
|
||||
id: serial().primaryKey(),
|
||||
name: text().notNull(),
|
||||
verified: boolean().notNull().default(false),
|
||||
jsonb: jsonb().$type<string[]>(),
|
||||
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
|
||||
});
|
||||
|
||||
const postsTable = pgTable('posts', {
|
||||
id: serial().primaryKey(),
|
||||
description: text().notNull(),
|
||||
userId: integer('city_id').references(() => usersTable.id),
|
||||
});
|
||||
|
||||
let pgContainer: Docker.Container;
|
||||
|
||||
afterAll(async () => {
|
||||
await pgContainer?.stop().catch(console.error);
|
||||
});
|
||||
|
||||
export function tests() {
|
||||
describe('common', () => {
|
||||
beforeEach(async (ctx) => {
|
||||
const { db, dbGlobalCached } = ctx.cachedPg;
|
||||
await db.execute(sql`drop schema if exists public cascade`);
|
||||
await db.$cache?.invalidate({ tables: 'users' });
|
||||
await dbGlobalCached.$cache?.invalidate({ tables: 'users' });
|
||||
await db.execute(sql`create schema public`);
|
||||
// public users
|
||||
await db.execute(
|
||||
sql`
|
||||
create table users (
|
||||
id serial primary key,
|
||||
name text not null,
|
||||
verified boolean not null default false,
|
||||
jsonb jsonb,
|
||||
created_at timestamptz not null default now()
|
||||
)
|
||||
`,
|
||||
);
|
||||
});
|
||||
|
||||
test('test force invalidate', async (ctx) => {
|
||||
const { db } = ctx.cachedPg;
|
||||
|
||||
const spyInvalidate = vi.spyOn(db.$cache, 'invalidate');
|
||||
await db.$cache?.invalidate({ tables: 'users' });
|
||||
expect(spyInvalidate).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test('default global config - no cache should be hit', async (ctx) => {
|
||||
const { db } = ctx.cachedPg;
|
||||
|
||||
// @ts-expect-error
|
||||
const spyPut = vi.spyOn(db.$cache, 'put');
|
||||
// @ts-expect-error
|
||||
const spyGet = vi.spyOn(db.$cache, 'get');
|
||||
// @ts-expect-error
|
||||
const spyInvalidate = vi.spyOn(db.$cache, 'onMutate');
|
||||
|
||||
await db.select().from(usersTable);
|
||||
|
||||
expect(spyPut).toHaveBeenCalledTimes(0);
|
||||
expect(spyGet).toHaveBeenCalledTimes(0);
|
||||
expect(spyInvalidate).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
test('default global config + enable cache on select: get, put', async (ctx) => {
|
||||
const { db } = ctx.cachedPg;
|
||||
|
||||
// @ts-expect-error
|
||||
const spyPut = vi.spyOn(db.$cache, 'put');
|
||||
// @ts-expect-error
|
||||
const spyGet = vi.spyOn(db.$cache, 'get');
|
||||
// @ts-expect-error
|
||||
const spyInvalidate = vi.spyOn(db.$cache, 'onMutate');
|
||||
|
||||
await db.select().from(usersTable).$withCache();
|
||||
|
||||
expect(spyPut).toHaveBeenCalledTimes(1);
|
||||
expect(spyGet).toHaveBeenCalledTimes(1);
|
||||
expect(spyInvalidate).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
test('default global config + enable cache on select + write: get, put, onMutate', async (ctx) => {
|
||||
const { db } = ctx.cachedPg;
|
||||
|
||||
// @ts-expect-error
|
||||
const spyPut = vi.spyOn(db.$cache, 'put');
|
||||
// @ts-expect-error
|
||||
const spyGet = vi.spyOn(db.$cache, 'get');
|
||||
// @ts-expect-error
|
||||
const spyInvalidate = vi.spyOn(db.$cache, 'onMutate');
|
||||
|
||||
await db.select().from(usersTable).$withCache({ config: { ex: 1 } });
|
||||
|
||||
expect(spyPut).toHaveBeenCalledTimes(1);
|
||||
expect(spyGet).toHaveBeenCalledTimes(1);
|
||||
expect(spyInvalidate).toHaveBeenCalledTimes(0);
|
||||
|
||||
spyPut.mockClear();
|
||||
spyGet.mockClear();
|
||||
spyInvalidate.mockClear();
|
||||
|
||||
await db.insert(usersTable).values({ name: 'John' });
|
||||
|
||||
expect(spyPut).toHaveBeenCalledTimes(0);
|
||||
expect(spyGet).toHaveBeenCalledTimes(0);
|
||||
expect(spyInvalidate).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test('default global config + enable cache on select + disable invalidate: get, put', async (ctx) => {
|
||||
const { db } = ctx.cachedPg;
|
||||
|
||||
// @ts-expect-error
|
||||
const spyPut = vi.spyOn(db.$cache, 'put');
|
||||
// @ts-expect-error
|
||||
const spyGet = vi.spyOn(db.$cache, 'get');
|
||||
// @ts-expect-error
|
||||
const spyInvalidate = vi.spyOn(db.$cache, 'onMutate');
|
||||
|
||||
await db.select().from(usersTable).$withCache({ tag: 'custom', autoInvalidate: false, config: { ex: 1 } });
|
||||
|
||||
expect(spyPut).toHaveBeenCalledTimes(1);
|
||||
expect(spyGet).toHaveBeenCalledTimes(1);
|
||||
expect(spyInvalidate).toHaveBeenCalledTimes(0);
|
||||
|
||||
await db.insert(usersTable).values({ name: 'John' });
|
||||
|
||||
// invalidate force
|
||||
await db.$cache?.invalidate({ tags: ['custom'] });
|
||||
});
|
||||
|
||||
test('global: true + disable cache', async (ctx) => {
|
||||
const { dbGlobalCached: db } = ctx.cachedPg;
|
||||
|
||||
// @ts-expect-error
|
||||
const spyPut = vi.spyOn(db.$cache, 'put');
|
||||
// @ts-expect-error
|
||||
const spyGet = vi.spyOn(db.$cache, 'get');
|
||||
// @ts-expect-error
|
||||
const spyInvalidate = vi.spyOn(db.$cache, 'onMutate');
|
||||
|
||||
await db.select().from(usersTable).$withCache(false);
|
||||
|
||||
expect(spyPut).toHaveBeenCalledTimes(0);
|
||||
expect(spyGet).toHaveBeenCalledTimes(0);
|
||||
expect(spyInvalidate).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
test('global: true - cache should be hit', async (ctx) => {
|
||||
const { dbGlobalCached: db } = ctx.cachedPg;
|
||||
|
||||
// @ts-expect-error
|
||||
const spyPut = vi.spyOn(db.$cache, 'put');
|
||||
// @ts-expect-error
|
||||
const spyGet = vi.spyOn(db.$cache, 'get');
|
||||
// @ts-expect-error
|
||||
const spyInvalidate = vi.spyOn(db.$cache, 'onMutate');
|
||||
|
||||
await db.select().from(usersTable);
|
||||
|
||||
expect(spyPut).toHaveBeenCalledTimes(1);
|
||||
expect(spyGet).toHaveBeenCalledTimes(1);
|
||||
expect(spyInvalidate).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
test('global: true - cache: false on select - no cache hit', async (ctx) => {
|
||||
const { dbGlobalCached: db } = ctx.cachedPg;
|
||||
|
||||
// @ts-expect-error
|
||||
const spyPut = vi.spyOn(db.$cache, 'put');
|
||||
// @ts-expect-error
|
||||
const spyGet = vi.spyOn(db.$cache, 'get');
|
||||
// @ts-expect-error
|
||||
const spyInvalidate = vi.spyOn(db.$cache, 'onMutate');
|
||||
|
||||
await db.select().from(usersTable).$withCache(false);
|
||||
|
||||
expect(spyPut).toHaveBeenCalledTimes(0);
|
||||
expect(spyGet).toHaveBeenCalledTimes(0);
|
||||
expect(spyInvalidate).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
test('global: true - disable invalidate - cache hit + no invalidate', async (ctx) => {
|
||||
const { dbGlobalCached: db } = ctx.cachedPg;
|
||||
|
||||
// @ts-expect-error
|
||||
const spyPut = vi.spyOn(db.$cache, 'put');
|
||||
// @ts-expect-error
|
||||
const spyGet = vi.spyOn(db.$cache, 'get');
|
||||
// @ts-expect-error
|
||||
const spyInvalidate = vi.spyOn(db.$cache, 'onMutate');
|
||||
|
||||
await db.select().from(usersTable).$withCache({ autoInvalidate: false });
|
||||
|
||||
expect(spyPut).toHaveBeenCalledTimes(1);
|
||||
expect(spyGet).toHaveBeenCalledTimes(1);
|
||||
expect(spyInvalidate).toHaveBeenCalledTimes(0);
|
||||
|
||||
spyPut.mockClear();
|
||||
spyGet.mockClear();
|
||||
spyInvalidate.mockClear();
|
||||
|
||||
await db.insert(usersTable).values({ name: 'John' });
|
||||
|
||||
expect(spyPut).toHaveBeenCalledTimes(0);
|
||||
expect(spyGet).toHaveBeenCalledTimes(0);
|
||||
expect(spyInvalidate).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test('global: true - with custom tag', async (ctx) => {
|
||||
const { dbGlobalCached: db } = ctx.cachedPg;
|
||||
|
||||
// @ts-expect-error
|
||||
const spyPut = vi.spyOn(db.$cache, 'put');
|
||||
// @ts-expect-error
|
||||
const spyGet = vi.spyOn(db.$cache, 'get');
|
||||
// @ts-expect-error
|
||||
const spyInvalidate = vi.spyOn(db.$cache, 'onMutate');
|
||||
|
||||
await db.select().from(usersTable).$withCache({ tag: 'custom', autoInvalidate: false });
|
||||
|
||||
expect(spyPut).toHaveBeenCalledTimes(1);
|
||||
expect(spyGet).toHaveBeenCalledTimes(1);
|
||||
expect(spyInvalidate).toHaveBeenCalledTimes(0);
|
||||
|
||||
await db.insert(usersTable).values({ name: 'John' });
|
||||
|
||||
// invalidate force
|
||||
await db.$cache?.invalidate({ tags: ['custom'] });
|
||||
});
|
||||
|
||||
test('global: true - with custom tag + with autoinvalidate', async (ctx) => {
|
||||
const { dbGlobalCached: db } = ctx.cachedPg;
|
||||
|
||||
// @ts-expect-error
|
||||
const spyPut = vi.spyOn(db.$cache, 'put');
|
||||
// @ts-expect-error
|
||||
const spyGet = vi.spyOn(db.$cache, 'get');
|
||||
// @ts-expect-error
|
||||
const spyInvalidate = vi.spyOn(db.$cache, 'onMutate');
|
||||
|
||||
await db.select().from(usersTable).$withCache({ tag: 'custom' });
|
||||
|
||||
expect(spyPut).toHaveBeenCalledTimes(1);
|
||||
expect(spyGet).toHaveBeenCalledTimes(1);
|
||||
|
||||
await db.insert(usersTable).values({ name: 'John' });
|
||||
|
||||
expect(spyInvalidate).toHaveBeenCalledTimes(1);
|
||||
|
||||
// invalidate force
|
||||
await db.$cache?.invalidate({ tags: ['custom'] });
|
||||
});
|
||||
|
||||
// check select used tables
|
||||
test('check simple select used tables', (ctx) => {
|
||||
const { db } = ctx.cachedPg;
|
||||
|
||||
// @ts-expect-error
|
||||
expect(db.select().from(usersTable).getUsedTables()).toStrictEqual(['users']);
|
||||
// @ts-expect-error
|
||||
expect(db.select().from(sql`${usersTable}`).getUsedTables()).toStrictEqual(['users']);
|
||||
});
|
||||
// check select+join used tables
|
||||
test('select+join', (ctx) => {
|
||||
const { db } = ctx.cachedPg;
|
||||
|
||||
// @ts-expect-error
|
||||
expect(db.select().from(usersTable).leftJoin(postsTable, eq(usersTable.id, postsTable.userId)).getUsedTables())
|
||||
.toStrictEqual(['users', 'posts']);
|
||||
expect(
|
||||
// @ts-expect-error
|
||||
db.select().from(sql`${usersTable}`).leftJoin(postsTable, eq(usersTable.id, postsTable.userId)).getUsedTables(),
|
||||
).toStrictEqual(['users', 'posts']);
|
||||
});
|
||||
// check select+2join used tables
|
||||
test('select+2joins', (ctx) => {
|
||||
const { db } = ctx.cachedPg;
|
||||
|
||||
expect(
|
||||
db.select().from(usersTable).leftJoin(
|
||||
postsTable,
|
||||
eq(usersTable.id, postsTable.userId),
|
||||
).leftJoin(
|
||||
alias(postsTable, 'post2'),
|
||||
eq(usersTable.id, postsTable.userId),
|
||||
)
|
||||
// @ts-expect-error
|
||||
.getUsedTables(),
|
||||
)
|
||||
.toStrictEqual(['users', 'posts']);
|
||||
expect(
|
||||
db.select().from(sql`${usersTable}`).leftJoin(postsTable, eq(usersTable.id, postsTable.userId)).leftJoin(
|
||||
alias(postsTable, 'post2'),
|
||||
eq(usersTable.id, postsTable.userId),
|
||||
// @ts-expect-error
|
||||
).getUsedTables(),
|
||||
).toStrictEqual(['users', 'posts']);
|
||||
});
|
||||
// select subquery used tables
|
||||
test('select+join', (ctx) => {
|
||||
const { db } = ctx.cachedPg;
|
||||
|
||||
const sq = db.select().from(usersTable).where(eq(usersTable.id, 42)).as('sq');
|
||||
db.select().from(sq);
|
||||
|
||||
// @ts-expect-error
|
||||
expect(db.select().from(sq).getUsedTables()).toStrictEqual(['users']);
|
||||
});
|
||||
});
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,798 @@
|
||||
import retry from 'async-retry';
|
||||
import type Docker from 'dockerode';
|
||||
import { asc, eq, sql } from 'drizzle-orm';
|
||||
import type { NodePgDatabase } from 'drizzle-orm/node-postgres';
|
||||
import { drizzle } from 'drizzle-orm/node-postgres';
|
||||
import { migrate } from 'drizzle-orm/node-postgres/migrator';
|
||||
import { alias, customType, pgTable, pgTableCreator, serial, text } from 'drizzle-orm/pg-core';
|
||||
import { Client } from 'pg';
|
||||
import { afterAll, beforeAll, beforeEach, expect, test } from 'vitest';
|
||||
import { randomString } from '~/utils';
|
||||
import { createDockerDB } from './pg-common';
|
||||
|
||||
const ENABLE_LOGGING = false;
|
||||
|
||||
let db: NodePgDatabase;
|
||||
let client: Client;
|
||||
let container: Docker.Container | undefined;
|
||||
|
||||
beforeAll(async () => {
|
||||
let connectionString;
|
||||
if (process.env['PG_CONNECTION_STRING']) {
|
||||
connectionString = process.env['PG_CONNECTION_STRING'];
|
||||
} else {
|
||||
const { connectionString: conStr, container: contrainerObj } = await createDockerDB();
|
||||
connectionString = conStr;
|
||||
container = contrainerObj;
|
||||
}
|
||||
client = await retry(async () => {
|
||||
client = new Client(connectionString);
|
||||
await client.connect();
|
||||
return client;
|
||||
}, {
|
||||
retries: 20,
|
||||
factor: 1,
|
||||
minTimeout: 250,
|
||||
maxTimeout: 250,
|
||||
randomize: false,
|
||||
onRetry() {
|
||||
client?.end();
|
||||
},
|
||||
});
|
||||
db = drizzle(client, { logger: ENABLE_LOGGING });
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await client?.end();
|
||||
await container?.stop().catch(console.error);
|
||||
});
|
||||
|
||||
beforeEach((ctx) => {
|
||||
ctx.pg = {
|
||||
db,
|
||||
};
|
||||
});
|
||||
|
||||
const customSerial = customType<{ data: number; notNull: true; default: true }>({
|
||||
dataType() {
|
||||
return 'serial';
|
||||
},
|
||||
});
|
||||
|
||||
const customText = customType<{ data: string }>({
|
||||
dataType() {
|
||||
return 'text';
|
||||
},
|
||||
});
|
||||
|
||||
const customBoolean = customType<{ data: boolean }>({
|
||||
dataType() {
|
||||
return 'boolean';
|
||||
},
|
||||
});
|
||||
|
||||
const customJsonb = <TData>(name: string) =>
|
||||
customType<{ data: TData; driverData: string }>({
|
||||
dataType() {
|
||||
return 'jsonb';
|
||||
},
|
||||
toDriver(value: TData): string {
|
||||
return JSON.stringify(value);
|
||||
},
|
||||
})(name);
|
||||
|
||||
const customTimestamp = customType<
|
||||
{ data: Date; driverData: string; config: { withTimezone: boolean; precision?: number } }
|
||||
>({
|
||||
dataType(config) {
|
||||
const precision = config?.precision === undefined ? '' : ` (${config.precision})`;
|
||||
return `timestamp${precision}${config?.withTimezone ? ' with time zone' : ''}`;
|
||||
},
|
||||
fromDriver(value: string): Date {
|
||||
return new Date(value);
|
||||
},
|
||||
});
|
||||
|
||||
const usersTable = pgTable('users', {
|
||||
id: customSerial('id').primaryKey(),
|
||||
name: customText('name').notNull(),
|
||||
verified: customBoolean('verified').notNull().default(false),
|
||||
jsonb: customJsonb<string[]>('jsonb'),
|
||||
createdAt: customTimestamp('created_at', { withTimezone: true }).notNull().default(sql`now()`),
|
||||
});
|
||||
|
||||
const usersMigratorTable = pgTable('users12', {
|
||||
id: serial('id').primaryKey(),
|
||||
name: text('name').notNull(),
|
||||
email: text('email').notNull(),
|
||||
});
|
||||
|
||||
beforeEach(async (ctx) => {
|
||||
const { db } = ctx.pg;
|
||||
await db.execute(sql`drop schema if exists public cascade`);
|
||||
await db.execute(sql`create schema public`);
|
||||
await db.execute(
|
||||
sql`
|
||||
create table users (
|
||||
id serial primary key,
|
||||
name text not null,
|
||||
verified boolean not null default false,
|
||||
jsonb jsonb,
|
||||
created_at timestamptz not null default now()
|
||||
)
|
||||
`,
|
||||
);
|
||||
});
|
||||
|
||||
test('select all fields', async (ctx) => {
|
||||
const { db } = ctx.pg;
|
||||
|
||||
const now = Date.now();
|
||||
|
||||
await db.insert(usersTable).values({ name: 'John' });
|
||||
const result = await db.select().from(usersTable);
|
||||
|
||||
expect(result[0]!.createdAt).toBeInstanceOf(Date);
|
||||
expect(Math.abs(result[0]!.createdAt.getTime() - now)).toBeLessThan(100);
|
||||
expect(result).toEqual([{ id: 1, name: 'John', verified: false, jsonb: null, createdAt: result[0]!.createdAt }]);
|
||||
});
|
||||
|
||||
test('select sql', async (ctx) => {
|
||||
const { db } = ctx.pg;
|
||||
|
||||
await db.insert(usersTable).values({ name: 'John' });
|
||||
const users = await db.select({
|
||||
name: sql`upper(${usersTable.name})`,
|
||||
}).from(usersTable);
|
||||
|
||||
expect(users).toEqual([{ name: 'JOHN' }]);
|
||||
});
|
||||
|
||||
test('select typed sql', async (ctx) => {
|
||||
const { db } = ctx.pg;
|
||||
|
||||
await db.insert(usersTable).values({ name: 'John' });
|
||||
const users = await db.select({
|
||||
name: sql<string>`upper(${usersTable.name})`,
|
||||
}).from(usersTable);
|
||||
|
||||
expect(users).toEqual([{ name: 'JOHN' }]);
|
||||
});
|
||||
|
||||
test('insert returning sql', async (ctx) => {
|
||||
const { db } = ctx.pg;
|
||||
|
||||
const users = await db.insert(usersTable).values({ name: 'John' }).returning({
|
||||
name: sql`upper(${usersTable.name})`,
|
||||
});
|
||||
|
||||
expect(users).toEqual([{ name: 'JOHN' }]);
|
||||
});
|
||||
|
||||
test('delete returning sql', async (ctx) => {
|
||||
const { db } = ctx.pg;
|
||||
|
||||
await db.insert(usersTable).values({ name: 'John' });
|
||||
const users = await db.delete(usersTable).where(eq(usersTable.name, 'John')).returning({
|
||||
name: sql`upper(${usersTable.name})`,
|
||||
});
|
||||
|
||||
expect(users).toEqual([{ name: 'JOHN' }]);
|
||||
});
|
||||
|
||||
test('update returning sql', async (ctx) => {
|
||||
const { db } = ctx.pg;
|
||||
|
||||
await db.insert(usersTable).values({ name: 'John' });
|
||||
const users = await db.update(usersTable).set({ name: 'Jane' }).where(eq(usersTable.name, 'John')).returning({
|
||||
name: sql`upper(${usersTable.name})`,
|
||||
});
|
||||
|
||||
expect(users).toEqual([{ name: 'JANE' }]);
|
||||
});
|
||||
|
||||
test('update with returning all fields', async (ctx) => {
|
||||
const { db } = ctx.pg;
|
||||
|
||||
const now = Date.now();
|
||||
|
||||
await db.insert(usersTable).values({ name: 'John' });
|
||||
const users = await db.update(usersTable).set({ name: 'Jane' }).where(eq(usersTable.name, 'John')).returning();
|
||||
|
||||
expect(users[0]!.createdAt).toBeInstanceOf(Date);
|
||||
expect(Math.abs(users[0]!.createdAt.getTime() - now)).toBeLessThan(100);
|
||||
expect(users).toEqual([{ id: 1, name: 'Jane', verified: false, jsonb: null, createdAt: users[0]!.createdAt }]);
|
||||
});
|
||||
|
||||
test('update with returning partial', async (ctx) => {
|
||||
const { db } = ctx.pg;
|
||||
|
||||
await db.insert(usersTable).values({ name: 'John' });
|
||||
const users = await db.update(usersTable).set({ name: 'Jane' }).where(eq(usersTable.name, 'John')).returning({
|
||||
id: usersTable.id,
|
||||
name: usersTable.name,
|
||||
});
|
||||
|
||||
expect(users).toEqual([{ id: 1, name: 'Jane' }]);
|
||||
});
|
||||
|
||||
test('delete with returning all fields', async (ctx) => {
|
||||
const { db } = ctx.pg;
|
||||
|
||||
const now = Date.now();
|
||||
|
||||
await db.insert(usersTable).values({ name: 'John' });
|
||||
const users = await db.delete(usersTable).where(eq(usersTable.name, 'John')).returning();
|
||||
|
||||
expect(users[0]!.createdAt).toBeInstanceOf(Date);
|
||||
expect(Math.abs(users[0]!.createdAt.getTime() - now)).toBeLessThan(100);
|
||||
expect(users).toEqual([{ id: 1, name: 'John', verified: false, jsonb: null, createdAt: users[0]!.createdAt }]);
|
||||
});
|
||||
|
||||
test('delete with returning partial', async (ctx) => {
|
||||
const { db } = ctx.pg;
|
||||
|
||||
await db.insert(usersTable).values({ name: 'John' });
|
||||
const users = await db.delete(usersTable).where(eq(usersTable.name, 'John')).returning({
|
||||
id: usersTable.id,
|
||||
name: usersTable.name,
|
||||
});
|
||||
|
||||
expect(users).toEqual([{ id: 1, name: 'John' }]);
|
||||
});
|
||||
|
||||
test('insert + select', async (ctx) => {
|
||||
const { db } = ctx.pg;
|
||||
|
||||
await db.insert(usersTable).values({ name: 'John' });
|
||||
const result = await db.select().from(usersTable);
|
||||
expect(result).toEqual([{ id: 1, name: 'John', verified: false, jsonb: null, createdAt: result[0]!.createdAt }]);
|
||||
|
||||
await db.insert(usersTable).values({ name: 'Jane' });
|
||||
const result2 = await db.select().from(usersTable);
|
||||
expect(result2).toEqual([
|
||||
{ id: 1, name: 'John', verified: false, jsonb: null, createdAt: result2[0]!.createdAt },
|
||||
{ id: 2, name: 'Jane', verified: false, jsonb: null, createdAt: result2[1]!.createdAt },
|
||||
]);
|
||||
});
|
||||
|
||||
test('json insert', async (ctx) => {
|
||||
const { db } = ctx.pg;
|
||||
|
||||
await db.insert(usersTable).values({ name: 'John', jsonb: ['foo', 'bar'] });
|
||||
const result = await db.select({
|
||||
id: usersTable.id,
|
||||
name: usersTable.name,
|
||||
jsonb: usersTable.jsonb,
|
||||
}).from(usersTable);
|
||||
|
||||
expect(result).toEqual([{ id: 1, name: 'John', jsonb: ['foo', 'bar'] }]);
|
||||
});
|
||||
|
||||
test('insert with overridden default values', async (ctx) => {
|
||||
const { db } = ctx.pg;
|
||||
|
||||
await db.insert(usersTable).values({ name: 'John', verified: true });
|
||||
const result = await db.select().from(usersTable);
|
||||
|
||||
expect(result).toEqual([{ id: 1, name: 'John', verified: true, jsonb: null, createdAt: result[0]!.createdAt }]);
|
||||
});
|
||||
|
||||
test('insert many', async (ctx) => {
|
||||
const { db } = ctx.pg;
|
||||
|
||||
await db.insert(usersTable).values([
|
||||
{ name: 'John' },
|
||||
{ name: 'Bruce', jsonb: ['foo', 'bar'] },
|
||||
{ name: 'Jane' },
|
||||
{ name: 'Austin', verified: true },
|
||||
]);
|
||||
const result = await db.select({
|
||||
id: usersTable.id,
|
||||
name: usersTable.name,
|
||||
jsonb: usersTable.jsonb,
|
||||
verified: usersTable.verified,
|
||||
}).from(usersTable);
|
||||
|
||||
expect(result).toEqual([
|
||||
{ id: 1, name: 'John', jsonb: null, verified: false },
|
||||
{ id: 2, name: 'Bruce', jsonb: ['foo', 'bar'], verified: false },
|
||||
{ id: 3, name: 'Jane', jsonb: null, verified: false },
|
||||
{ id: 4, name: 'Austin', jsonb: null, verified: true },
|
||||
]);
|
||||
});
|
||||
|
||||
test('insert many with returning', async (ctx) => {
|
||||
const { db } = ctx.pg;
|
||||
|
||||
const result = await db.insert(usersTable).values([
|
||||
{ name: 'John' },
|
||||
{ name: 'Bruce', jsonb: ['foo', 'bar'] },
|
||||
{ name: 'Jane' },
|
||||
{ name: 'Austin', verified: true },
|
||||
])
|
||||
.returning({
|
||||
id: usersTable.id,
|
||||
name: usersTable.name,
|
||||
jsonb: usersTable.jsonb,
|
||||
verified: usersTable.verified,
|
||||
});
|
||||
|
||||
expect(result).toEqual([
|
||||
{ id: 1, name: 'John', jsonb: null, verified: false },
|
||||
{ id: 2, name: 'Bruce', jsonb: ['foo', 'bar'], verified: false },
|
||||
{ id: 3, name: 'Jane', jsonb: null, verified: false },
|
||||
{ id: 4, name: 'Austin', jsonb: null, verified: true },
|
||||
]);
|
||||
});
|
||||
|
||||
test('select with group by as field', async (ctx) => {
|
||||
const { db } = ctx.pg;
|
||||
|
||||
await db.insert(usersTable).values([{ name: 'John' }, { name: 'Jane' }, { name: 'Jane' }]);
|
||||
|
||||
const result = await db.select({ name: usersTable.name }).from(usersTable)
|
||||
.groupBy(usersTable.name);
|
||||
|
||||
expect(result).toEqual([{ name: 'Jane' }, { name: 'John' }]);
|
||||
});
|
||||
|
||||
test('select with group by as sql', async (ctx) => {
|
||||
const { db } = ctx.pg;
|
||||
|
||||
await db.insert(usersTable).values([{ name: 'John' }, { name: 'Jane' }, { name: 'Jane' }]);
|
||||
|
||||
const result = await db.select({ name: usersTable.name }).from(usersTable)
|
||||
.groupBy(sql`${usersTable.name}`);
|
||||
|
||||
expect(result).toEqual([{ name: 'Jane' }, { name: 'John' }]);
|
||||
});
|
||||
|
||||
test('select with group by as sql + column', async (ctx) => {
|
||||
const { db } = ctx.pg;
|
||||
|
||||
await db.insert(usersTable).values([{ name: 'John' }, { name: 'Jane' }, { name: 'Jane' }]);
|
||||
|
||||
const result = await db.select({ name: usersTable.name }).from(usersTable)
|
||||
.groupBy(sql`${usersTable.name}`, usersTable.id);
|
||||
|
||||
expect(result).toEqual([{ name: 'Jane' }, { name: 'Jane' }, { name: 'John' }]);
|
||||
});
|
||||
|
||||
test('select with group by as column + sql', async (ctx) => {
|
||||
const { db } = ctx.pg;
|
||||
|
||||
await db.insert(usersTable).values([{ name: 'John' }, { name: 'Jane' }, { name: 'Jane' }]);
|
||||
|
||||
const result = await db.select({ name: usersTable.name }).from(usersTable)
|
||||
.groupBy(usersTable.id, sql`${usersTable.name}`);
|
||||
|
||||
expect(result).toEqual([{ name: 'Jane' }, { name: 'Jane' }, { name: 'John' }]);
|
||||
});
|
||||
|
||||
test('select with group by complex query', async (ctx) => {
|
||||
const { db } = ctx.pg;
|
||||
|
||||
await db.insert(usersTable).values([{ name: 'John' }, { name: 'Jane' }, { name: 'Jane' }]);
|
||||
|
||||
const result = await db.select({ name: usersTable.name }).from(usersTable)
|
||||
.groupBy(usersTable.id, sql`${usersTable.name}`)
|
||||
.orderBy(asc(usersTable.name))
|
||||
.limit(1);
|
||||
|
||||
expect(result).toEqual([{ name: 'Jane' }]);
|
||||
});
|
||||
|
||||
test('build query', async (ctx) => {
|
||||
const { db } = ctx.pg;
|
||||
|
||||
const query = db.select({ id: usersTable.id, name: usersTable.name }).from(usersTable)
|
||||
.groupBy(usersTable.id, usersTable.name)
|
||||
.toSQL();
|
||||
|
||||
expect(query).toEqual({
|
||||
sql: 'select "id", "name" from "users" group by "users"."id", "users"."name"',
|
||||
params: [],
|
||||
});
|
||||
});
|
||||
|
||||
test('insert sql', async (ctx) => {
|
||||
const { db } = ctx.pg;
|
||||
|
||||
await db.insert(usersTable).values({ name: sql`${'John'}` });
|
||||
const result = await db.select({ id: usersTable.id, name: usersTable.name }).from(usersTable);
|
||||
expect(result).toEqual([{ id: 1, name: 'John' }]);
|
||||
});
|
||||
|
||||
test('partial join with alias', async (ctx) => {
|
||||
const { db } = ctx.pg;
|
||||
const customerAlias = alias(usersTable, 'customer');
|
||||
|
||||
await db.insert(usersTable).values([{ id: 10, name: 'Ivan' }, { id: 11, name: 'Hans' }]);
|
||||
const result = await db
|
||||
.select({
|
||||
user: {
|
||||
id: usersTable.id,
|
||||
name: usersTable.name,
|
||||
},
|
||||
customer: {
|
||||
id: customerAlias.id,
|
||||
name: customerAlias.name,
|
||||
},
|
||||
}).from(usersTable)
|
||||
.leftJoin(customerAlias, eq(customerAlias.id, 11))
|
||||
.where(eq(usersTable.id, 10));
|
||||
|
||||
expect(result).toEqual([{
|
||||
user: { id: 10, name: 'Ivan' },
|
||||
customer: { id: 11, name: 'Hans' },
|
||||
}]);
|
||||
});
|
||||
|
||||
test('full join with alias', async (ctx) => {
|
||||
const { db } = ctx.pg;
|
||||
|
||||
const pgTable = pgTableCreator((name) => `prefixed_${name}`);
|
||||
|
||||
const users = pgTable('users', {
|
||||
id: serial('id').primaryKey(),
|
||||
name: text('name').notNull(),
|
||||
});
|
||||
|
||||
await db.execute(sql`drop table if exists ${users}`);
|
||||
await db.execute(sql`create table ${users} (id serial primary key, name text not null)`);
|
||||
|
||||
const customers = alias(users, 'customer');
|
||||
|
||||
await db.insert(users).values([{ id: 10, name: 'Ivan' }, { id: 11, name: 'Hans' }]);
|
||||
const result = await db
|
||||
.select().from(users)
|
||||
.leftJoin(customers, eq(customers.id, 11))
|
||||
.where(eq(users.id, 10));
|
||||
|
||||
expect(result).toEqual([{
|
||||
users: {
|
||||
id: 10,
|
||||
name: 'Ivan',
|
||||
},
|
||||
customer: {
|
||||
id: 11,
|
||||
name: 'Hans',
|
||||
},
|
||||
}]);
|
||||
|
||||
await db.execute(sql`drop table ${users}`);
|
||||
});
|
||||
|
||||
test('insert with spaces', async (ctx) => {
|
||||
const { db } = ctx.pg;
|
||||
|
||||
await db.insert(usersTable).values({ name: sql`'Jo h n'` });
|
||||
const result = await db.select({ id: usersTable.id, name: usersTable.name }).from(usersTable);
|
||||
|
||||
expect(result).toEqual([{ id: 1, name: 'Jo h n' }]);
|
||||
});
|
||||
|
||||
test('prepared statement', async (ctx) => {
|
||||
const { db } = ctx.pg;
|
||||
|
||||
await db.insert(usersTable).values({ name: 'John' });
|
||||
const statement = db.select({
|
||||
id: usersTable.id,
|
||||
name: usersTable.name,
|
||||
}).from(usersTable)
|
||||
.prepare('statement1');
|
||||
const result = await statement.execute();
|
||||
|
||||
expect(result).toEqual([{ id: 1, name: 'John' }]);
|
||||
});
|
||||
|
||||
test('prepared statement reuse', async (ctx) => {
|
||||
const { db } = ctx.pg;
|
||||
|
||||
const stmt = db.insert(usersTable).values({
|
||||
verified: true,
|
||||
name: sql.placeholder('name'),
|
||||
}).prepare('stmt2');
|
||||
|
||||
for (let i = 0; i < 10; i++) {
|
||||
await stmt.execute({ name: `John ${i}` });
|
||||
}
|
||||
|
||||
const result = await db.select({
|
||||
id: usersTable.id,
|
||||
name: usersTable.name,
|
||||
verified: usersTable.verified,
|
||||
}).from(usersTable);
|
||||
|
||||
expect(result).toEqual([
|
||||
{ id: 1, name: 'John 0', verified: true },
|
||||
{ id: 2, name: 'John 1', verified: true },
|
||||
{ id: 3, name: 'John 2', verified: true },
|
||||
{ id: 4, name: 'John 3', verified: true },
|
||||
{ id: 5, name: 'John 4', verified: true },
|
||||
{ id: 6, name: 'John 5', verified: true },
|
||||
{ id: 7, name: 'John 6', verified: true },
|
||||
{ id: 8, name: 'John 7', verified: true },
|
||||
{ id: 9, name: 'John 8', verified: true },
|
||||
{ id: 10, name: 'John 9', verified: true },
|
||||
]);
|
||||
});
|
||||
|
||||
test('prepared statement with placeholder in .where', async (ctx) => {
|
||||
const { db } = ctx.pg;
|
||||
|
||||
await db.insert(usersTable).values({ name: 'John' });
|
||||
const stmt = db.select({
|
||||
id: usersTable.id,
|
||||
name: usersTable.name,
|
||||
}).from(usersTable)
|
||||
.where(eq(usersTable.id, sql.placeholder('id')))
|
||||
.prepare('stmt3');
|
||||
const result = await stmt.execute({ id: 1 });
|
||||
|
||||
expect(result).toEqual([{ id: 1, name: 'John' }]);
|
||||
});
|
||||
|
||||
test('prepared statement with placeholder in .limit', async (ctx) => {
|
||||
const { db } = ctx.pg;
|
||||
|
||||
await db.insert(usersTable).values({ name: 'John' });
|
||||
const stmt = db
|
||||
.select({
|
||||
id: usersTable.id,
|
||||
name: usersTable.name,
|
||||
})
|
||||
.from(usersTable)
|
||||
.where(eq(usersTable.id, sql.placeholder('id')))
|
||||
.limit(sql.placeholder('limit'))
|
||||
.prepare('stmt_limit');
|
||||
|
||||
const result = await stmt.execute({ id: 1, limit: 1 });
|
||||
|
||||
expect(result).toEqual([{ id: 1, name: 'John' }]);
|
||||
expect(result).toHaveLength(1);
|
||||
});
|
||||
|
||||
test('prepared statement with placeholder in .offset', async (ctx) => {
|
||||
const { db } = ctx.pg;
|
||||
|
||||
await db.insert(usersTable).values([{ name: 'John' }, { name: 'John1' }]);
|
||||
const stmt = db
|
||||
.select({
|
||||
id: usersTable.id,
|
||||
name: usersTable.name,
|
||||
})
|
||||
.from(usersTable)
|
||||
.offset(sql.placeholder('offset'))
|
||||
.prepare('stmt_offset');
|
||||
|
||||
const result = await stmt.execute({ offset: 1 });
|
||||
|
||||
expect(result).toEqual([{ id: 2, name: 'John1' }]);
|
||||
});
|
||||
|
||||
test('migrator : default migration strategy', async () => {
|
||||
await db.execute(sql`drop table if exists all_columns`);
|
||||
await db.execute(sql`drop table if exists users12`);
|
||||
await db.execute(sql`drop table if exists "drizzle"."__drizzle_migrations"`);
|
||||
|
||||
await migrate(db, { migrationsFolder: './drizzle2/pg' });
|
||||
|
||||
await db.insert(usersMigratorTable).values({ name: 'John', email: 'email' });
|
||||
|
||||
const result = await db.select().from(usersMigratorTable);
|
||||
|
||||
expect(result).toEqual([{ id: 1, name: 'John', email: 'email' }]);
|
||||
|
||||
await db.execute(sql`drop table all_columns`);
|
||||
await db.execute(sql`drop table users12`);
|
||||
await db.execute(sql`drop table "drizzle"."__drizzle_migrations"`);
|
||||
});
|
||||
|
||||
test('migrator : migrate with custom schema', async () => {
|
||||
const customSchema = randomString();
|
||||
await db.execute(sql`drop table if exists all_columns`);
|
||||
await db.execute(sql`drop table if exists users12`);
|
||||
await db.execute(sql`drop table if exists "drizzle"."__drizzle_migrations"`);
|
||||
|
||||
await migrate(db, { migrationsFolder: './drizzle2/pg', migrationsSchema: customSchema });
|
||||
|
||||
// test if the custom migrations table was created
|
||||
const { rowCount } = await db.execute(sql`select * from ${sql.identifier(customSchema)}."__drizzle_migrations";`);
|
||||
expect(rowCount! > 0).toBeTruthy();
|
||||
|
||||
// test if the migrated table are working as expected
|
||||
await db.insert(usersMigratorTable).values({ name: 'John', email: 'email' });
|
||||
const result = await db.select().from(usersMigratorTable);
|
||||
expect(result).toEqual([{ id: 1, name: 'John', email: 'email' }]);
|
||||
|
||||
await db.execute(sql`drop table all_columns`);
|
||||
await db.execute(sql`drop table users12`);
|
||||
await db.execute(sql`drop table ${sql.identifier(customSchema)}."__drizzle_migrations"`);
|
||||
});
|
||||
|
||||
test('migrator : migrate with custom table', async () => {
|
||||
const customTable = randomString();
|
||||
await db.execute(sql`drop table if exists all_columns`);
|
||||
await db.execute(sql`drop table if exists users12`);
|
||||
await db.execute(sql`drop table if exists "drizzle"."__drizzle_migrations"`);
|
||||
|
||||
await migrate(db, { migrationsFolder: './drizzle2/pg', migrationsTable: customTable });
|
||||
|
||||
// test if the custom migrations table was created
|
||||
const { rowCount } = await db.execute(sql`select * from "drizzle".${sql.identifier(customTable)};`);
|
||||
expect(rowCount! > 0).toBeTruthy();
|
||||
|
||||
// test if the migrated table are working as expected
|
||||
await db.insert(usersMigratorTable).values({ name: 'John', email: 'email' });
|
||||
const result = await db.select().from(usersMigratorTable);
|
||||
expect(result).toEqual([{ id: 1, name: 'John', email: 'email' }]);
|
||||
|
||||
await db.execute(sql`drop table all_columns`);
|
||||
await db.execute(sql`drop table users12`);
|
||||
await db.execute(sql`drop table "drizzle".${sql.identifier(customTable)}`);
|
||||
});
|
||||
|
||||
test('migrator : migrate with custom table and custom schema', async () => {
|
||||
const customTable = randomString();
|
||||
const customSchema = randomString();
|
||||
await db.execute(sql`drop table if exists all_columns`);
|
||||
await db.execute(sql`drop table if exists users12`);
|
||||
await db.execute(sql`drop table if exists "drizzle"."__drizzle_migrations"`);
|
||||
|
||||
await migrate(db, {
|
||||
migrationsFolder: './drizzle2/pg',
|
||||
migrationsTable: customTable,
|
||||
migrationsSchema: customSchema,
|
||||
});
|
||||
|
||||
// test if the custom migrations table was created
|
||||
const { rowCount } = await db.execute(
|
||||
sql`select * from ${sql.identifier(customSchema)}.${sql.identifier(customTable)};`,
|
||||
);
|
||||
expect(rowCount! > 0).toBeTruthy();
|
||||
|
||||
// test if the migrated table are working as expected
|
||||
await db.insert(usersMigratorTable).values({ name: 'John', email: 'email' });
|
||||
const result = await db.select().from(usersMigratorTable);
|
||||
expect(result).toEqual([{ id: 1, name: 'John', email: 'email' }]);
|
||||
|
||||
await db.execute(sql`drop table all_columns`);
|
||||
await db.execute(sql`drop table users12`);
|
||||
await db.execute(sql`drop table ${sql.identifier(customSchema)}.${sql.identifier(customTable)}`);
|
||||
});
|
||||
|
||||
test('insert via db.execute + select via db.execute', async () => {
|
||||
await db.execute(sql`insert into ${usersTable} (${sql.identifier(usersTable.name.name)}) values (${'John'})`);
|
||||
|
||||
const result = await db.execute<{ id: number; name: string }>(sql`select id, name from "users"`);
|
||||
expect(result.rows).toEqual([{ id: 1, name: 'John' }]);
|
||||
});
|
||||
|
||||
test('insert via db.execute + returning', async () => {
|
||||
const inserted = await db.execute<{ id: number; name: string }>(
|
||||
sql`insert into ${usersTable} (${
|
||||
sql.identifier(usersTable.name.name)
|
||||
}) values (${'John'}) returning ${usersTable.id}, ${usersTable.name}`,
|
||||
);
|
||||
expect(inserted.rows).toEqual([{ id: 1, name: 'John' }]);
|
||||
});
|
||||
|
||||
test('insert via db.execute w/ query builder', async () => {
|
||||
const inserted = await db.execute<Pick<typeof usersTable.$inferSelect, 'id' | 'name'>>(
|
||||
db.insert(usersTable).values({ name: 'John' }).returning({ id: usersTable.id, name: usersTable.name }),
|
||||
);
|
||||
expect(inserted.rows).toEqual([{ id: 1, name: 'John' }]);
|
||||
});
|
||||
|
||||
test('build query insert with onConflict do update', async (ctx) => {
|
||||
const { db } = ctx.pg;
|
||||
|
||||
const query = db.insert(usersTable)
|
||||
.values({ name: 'John', jsonb: ['foo', 'bar'] })
|
||||
.onConflictDoUpdate({ target: usersTable.id, set: { name: 'John1' } })
|
||||
.toSQL();
|
||||
|
||||
expect(query).toEqual({
|
||||
sql:
|
||||
'insert into "users" ("id", "name", "verified", "jsonb", "created_at") values (default, $1, default, $2, default) on conflict ("id") do update set "name" = $3',
|
||||
params: ['John', '["foo","bar"]', 'John1'],
|
||||
});
|
||||
});
|
||||
|
||||
test('build query insert with onConflict do update / multiple columns', async (ctx) => {
|
||||
const { db } = ctx.pg;
|
||||
|
||||
const query = db.insert(usersTable)
|
||||
.values({ name: 'John', jsonb: ['foo', 'bar'] })
|
||||
.onConflictDoUpdate({ target: [usersTable.id, usersTable.name], set: { name: 'John1' } })
|
||||
.toSQL();
|
||||
|
||||
expect(query).toEqual({
|
||||
sql:
|
||||
'insert into "users" ("id", "name", "verified", "jsonb", "created_at") values (default, $1, default, $2, default) on conflict ("id","name") do update set "name" = $3',
|
||||
params: ['John', '["foo","bar"]', 'John1'],
|
||||
});
|
||||
});
|
||||
|
||||
test('build query insert with onConflict do nothing', async (ctx) => {
|
||||
const { db } = ctx.pg;
|
||||
|
||||
const query = db.insert(usersTable)
|
||||
.values({ name: 'John', jsonb: ['foo', 'bar'] })
|
||||
.onConflictDoNothing()
|
||||
.toSQL();
|
||||
|
||||
expect(query).toEqual({
|
||||
sql:
|
||||
'insert into "users" ("id", "name", "verified", "jsonb", "created_at") values (default, $1, default, $2, default) on conflict do nothing',
|
||||
params: ['John', '["foo","bar"]'],
|
||||
});
|
||||
});
|
||||
|
||||
test('build query insert with onConflict do nothing + target', async (ctx) => {
|
||||
const { db } = ctx.pg;
|
||||
|
||||
const query = db.insert(usersTable)
|
||||
.values({ name: 'John', jsonb: ['foo', 'bar'] })
|
||||
.onConflictDoNothing({ target: usersTable.id })
|
||||
.toSQL();
|
||||
|
||||
expect(query).toEqual({
|
||||
sql:
|
||||
'insert into "users" ("id", "name", "verified", "jsonb", "created_at") values (default, $1, default, $2, default) on conflict ("id") do nothing',
|
||||
params: ['John', '["foo","bar"]'],
|
||||
});
|
||||
});
|
||||
|
||||
test('insert with onConflict do update', async (ctx) => {
|
||||
const { db } = ctx.pg;
|
||||
|
||||
await db.insert(usersTable)
|
||||
.values({ name: 'John' });
|
||||
|
||||
await db.insert(usersTable)
|
||||
.values({ id: 1, name: 'John' })
|
||||
.onConflictDoUpdate({ target: usersTable.id, set: { name: 'John1' } });
|
||||
|
||||
const res = await db.select({ id: usersTable.id, name: usersTable.name }).from(usersTable).where(
|
||||
eq(usersTable.id, 1),
|
||||
);
|
||||
|
||||
expect(res).toEqual([{ id: 1, name: 'John1' }]);
|
||||
});
|
||||
|
||||
test('insert with onConflict do nothing', async (ctx) => {
|
||||
const { db } = ctx.pg;
|
||||
|
||||
await db.insert(usersTable)
|
||||
.values({ name: 'John' });
|
||||
|
||||
await db.insert(usersTable)
|
||||
.values({ id: 1, name: 'John' })
|
||||
.onConflictDoNothing();
|
||||
|
||||
const res = await db.select({ id: usersTable.id, name: usersTable.name }).from(usersTable).where(
|
||||
eq(usersTable.id, 1),
|
||||
);
|
||||
|
||||
expect(res).toEqual([{ id: 1, name: 'John' }]);
|
||||
});
|
||||
|
||||
test('insert with onConflict do nothing + target', async (ctx) => {
|
||||
const { db } = ctx.pg;
|
||||
|
||||
await db.insert(usersTable)
|
||||
.values({ name: 'John' });
|
||||
|
||||
await db.insert(usersTable)
|
||||
.values({ id: 1, name: 'John' })
|
||||
.onConflictDoNothing({ target: usersTable.id });
|
||||
|
||||
const res = await db.select({ id: usersTable.id, name: usersTable.name }).from(usersTable).where(
|
||||
eq(usersTable.id, 1),
|
||||
);
|
||||
|
||||
expect(res).toEqual([{ id: 1, name: 'John' }]);
|
||||
});
|
||||
@@ -0,0 +1,512 @@
|
||||
import retry from 'async-retry';
|
||||
import { sql } from 'drizzle-orm';
|
||||
import { pgTable, serial, timestamp } from 'drizzle-orm/pg-core';
|
||||
import type { PgRemoteDatabase } from 'drizzle-orm/pg-proxy';
|
||||
import { drizzle as proxyDrizzle } from 'drizzle-orm/pg-proxy';
|
||||
import { migrate } from 'drizzle-orm/pg-proxy/migrator';
|
||||
import * as pg from 'pg';
|
||||
import { afterAll, beforeAll, beforeEach, expect, test } from 'vitest';
|
||||
import { skipTests } from '~/common';
|
||||
import { createDockerDB, tests, usersMigratorTable, usersTable } from './pg-common';
|
||||
import { TestCache, TestGlobalCache, tests as cacheTests } from './pg-common-cache';
|
||||
|
||||
// eslint-disable-next-line drizzle-internal/require-entity-kind
|
||||
class ServerSimulator {
|
||||
constructor(private db: pg.Client) {
|
||||
const { types } = pg;
|
||||
|
||||
types.setTypeParser(types.builtins.TIMESTAMPTZ, (val) => val);
|
||||
types.setTypeParser(types.builtins.TIMESTAMP, (val) => val);
|
||||
types.setTypeParser(types.builtins.DATE, (val) => val);
|
||||
types.setTypeParser(types.builtins.INTERVAL, (val) => val);
|
||||
types.setTypeParser(1231, (val) => val);
|
||||
types.setTypeParser(1115, (val) => val);
|
||||
types.setTypeParser(1185, (val) => val);
|
||||
types.setTypeParser(1187, (val) => val);
|
||||
types.setTypeParser(1182, (val) => val);
|
||||
}
|
||||
|
||||
async query(sql: string, params: any[], method: 'all' | 'execute') {
|
||||
if (method === 'all') {
|
||||
try {
|
||||
const result = await this.db.query({
|
||||
text: sql,
|
||||
values: params,
|
||||
rowMode: 'array',
|
||||
});
|
||||
|
||||
return { data: result.rows as any };
|
||||
} catch (e: any) {
|
||||
return { error: e };
|
||||
}
|
||||
} else if (method === 'execute') {
|
||||
try {
|
||||
const result = await this.db.query({
|
||||
text: sql,
|
||||
values: params,
|
||||
});
|
||||
|
||||
return { data: result.rows as any };
|
||||
} catch (e: any) {
|
||||
return { error: e };
|
||||
}
|
||||
} else {
|
||||
return { error: 'Unknown method value' };
|
||||
}
|
||||
}
|
||||
|
||||
async migrations(queries: string[]) {
|
||||
await this.db.query('BEGIN');
|
||||
try {
|
||||
for (const query of queries) {
|
||||
await this.db.query(query);
|
||||
}
|
||||
await this.db.query('COMMIT');
|
||||
} catch (e) {
|
||||
await this.db.query('ROLLBACK');
|
||||
throw e;
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
const ENABLE_LOGGING = false;
|
||||
|
||||
let db: PgRemoteDatabase;
|
||||
let dbGlobalCached: PgRemoteDatabase;
|
||||
let cachedDb: PgRemoteDatabase;
|
||||
let client: pg.Client;
|
||||
let serverSimulator: ServerSimulator;
|
||||
|
||||
beforeAll(async () => {
|
||||
let connectionString;
|
||||
if (process.env['PG_CONNECTION_STRING']) {
|
||||
connectionString = process.env['PG_CONNECTION_STRING'];
|
||||
} else {
|
||||
const { connectionString: conStr } = await createDockerDB();
|
||||
connectionString = conStr;
|
||||
}
|
||||
client = await retry(async () => {
|
||||
client = new pg.Client(connectionString);
|
||||
await client.connect();
|
||||
return client;
|
||||
}, {
|
||||
retries: 20,
|
||||
factor: 1,
|
||||
minTimeout: 250,
|
||||
maxTimeout: 250,
|
||||
randomize: false,
|
||||
onRetry() {
|
||||
client?.end();
|
||||
},
|
||||
});
|
||||
serverSimulator = new ServerSimulator(client);
|
||||
const proxyHandler = async (sql: string, params: any[], method: any) => {
|
||||
try {
|
||||
const response = await serverSimulator.query(sql, params, method);
|
||||
|
||||
if (response.error !== undefined) {
|
||||
throw response.error;
|
||||
}
|
||||
|
||||
return { rows: response.data };
|
||||
} catch (e: any) {
|
||||
console.error('Error from pg proxy server:', e.message);
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
db = proxyDrizzle(proxyHandler, {
|
||||
logger: ENABLE_LOGGING,
|
||||
});
|
||||
|
||||
cachedDb = proxyDrizzle(proxyHandler, { logger: ENABLE_LOGGING, cache: new TestCache() });
|
||||
dbGlobalCached = proxyDrizzle(proxyHandler, { logger: ENABLE_LOGGING, cache: new TestGlobalCache() });
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await client?.end();
|
||||
});
|
||||
|
||||
beforeEach((ctx) => {
|
||||
ctx.pg = {
|
||||
db,
|
||||
};
|
||||
ctx.cachedPg = {
|
||||
db: cachedDb,
|
||||
dbGlobalCached,
|
||||
};
|
||||
});
|
||||
|
||||
test('migrator : default migration strategy', async () => {
|
||||
await db.execute(sql`drop table if exists all_columns`);
|
||||
await db.execute(sql`drop table if exists users12`);
|
||||
await db.execute(sql`drop table if exists "drizzle"."__drizzle_migrations"`);
|
||||
|
||||
// './drizzle2/pg-proxy/first' ??
|
||||
await migrate(db, async (queries) => {
|
||||
try {
|
||||
await serverSimulator.migrations(queries);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
throw new Error('Proxy server cannot run migrations');
|
||||
}
|
||||
}, { migrationsFolder: './drizzle2/pg' });
|
||||
|
||||
await db.insert(usersMigratorTable).values({ name: 'John', email: 'email' });
|
||||
|
||||
const result = await db.select().from(usersMigratorTable);
|
||||
|
||||
expect(result).toEqual([{ id: 1, name: 'John', email: 'email' }]);
|
||||
|
||||
await db.execute(sql`drop table all_columns`);
|
||||
await db.execute(sql`drop table users12`);
|
||||
await db.execute(sql`drop table "drizzle"."__drizzle_migrations"`);
|
||||
});
|
||||
|
||||
test('all date and time columns without timezone first case mode string', async () => {
|
||||
const table = pgTable('all_columns', {
|
||||
id: serial('id').primaryKey(),
|
||||
timestamp: timestamp('timestamp_string', { mode: 'string', precision: 6 }).notNull(),
|
||||
});
|
||||
|
||||
await db.execute(sql`drop table if exists ${table}`);
|
||||
|
||||
await db.execute(sql`
|
||||
create table ${table} (
|
||||
id serial primary key,
|
||||
timestamp_string timestamp(6) not null
|
||||
)
|
||||
`);
|
||||
|
||||
// 1. Insert date in string format without timezone in it
|
||||
await db.insert(table).values([
|
||||
{ timestamp: '2022-01-01 02:00:00.123456' },
|
||||
]);
|
||||
|
||||
// 2, Select in string format and check that values are the same
|
||||
const result = await db.select().from(table);
|
||||
|
||||
expect(result).toEqual([{ id: 1, timestamp: '2022-01-01 02:00:00.123456' }]);
|
||||
|
||||
// 3. Select as raw query and check that values are the same
|
||||
const result2 = await db.execute<{
|
||||
id: number;
|
||||
timestamp_string: string;
|
||||
}>(sql`select * from ${table}`);
|
||||
|
||||
expect(result2).toEqual([{ id: 1, timestamp_string: '2022-01-01 02:00:00.123456' }]);
|
||||
|
||||
await db.execute(sql`drop table if exists ${table}`);
|
||||
});
|
||||
|
||||
test('all date and time columns without timezone second case mode string', async () => {
|
||||
const table = pgTable('all_columns', {
|
||||
id: serial('id').primaryKey(),
|
||||
timestamp: timestamp('timestamp_string', { mode: 'string', precision: 6 }).notNull(),
|
||||
});
|
||||
|
||||
await db.execute(sql`drop table if exists ${table}`);
|
||||
|
||||
await db.execute(sql`
|
||||
create table ${table} (
|
||||
id serial primary key,
|
||||
timestamp_string timestamp(6) not null
|
||||
)
|
||||
`);
|
||||
|
||||
// 1. Insert date in string format with timezone in it
|
||||
await db.insert(table).values([
|
||||
{ timestamp: '2022-01-01T02:00:00.123456-02' },
|
||||
]);
|
||||
|
||||
// 2, Select as raw query and check that values are the same
|
||||
const result = await db.execute<{
|
||||
id: number;
|
||||
timestamp_string: string;
|
||||
}>(sql`select * from ${table}`);
|
||||
|
||||
expect(result).toEqual([{ id: 1, timestamp_string: '2022-01-01 02:00:00.123456' }]);
|
||||
|
||||
await db.execute(sql`drop table if exists ${table}`);
|
||||
});
|
||||
|
||||
test('all date and time columns without timezone third case mode date', async () => {
|
||||
const table = pgTable('all_columns', {
|
||||
id: serial('id').primaryKey(),
|
||||
timestamp: timestamp('timestamp_string', { mode: 'date', precision: 3 }).notNull(),
|
||||
});
|
||||
|
||||
await db.execute(sql`drop table if exists ${table}`);
|
||||
|
||||
await db.execute(sql`
|
||||
create table ${table} (
|
||||
id serial primary key,
|
||||
timestamp_string timestamp(3) not null
|
||||
)
|
||||
`);
|
||||
|
||||
const insertedDate = new Date('2022-01-01 20:00:00.123+04');
|
||||
|
||||
// 1. Insert date as new date
|
||||
await db.insert(table).values([
|
||||
{ timestamp: insertedDate },
|
||||
]);
|
||||
|
||||
// 2, Select as raw query as string
|
||||
const result = await db.execute<{
|
||||
id: number;
|
||||
timestamp_string: string;
|
||||
}>(sql`select * from ${table}`);
|
||||
|
||||
// 3. Compare both dates using orm mapping - Need to add 'Z' to tell JS that it is UTC
|
||||
expect(new Date(result[0]!.timestamp_string + 'Z').getTime()).toBe(insertedDate.getTime());
|
||||
|
||||
await db.execute(sql`drop table if exists ${table}`);
|
||||
});
|
||||
|
||||
test('test mode string for timestamp with timezone', async () => {
|
||||
const table = pgTable('all_columns', {
|
||||
id: serial('id').primaryKey(),
|
||||
timestamp: timestamp('timestamp_string', { mode: 'string', withTimezone: true, precision: 6 }).notNull(),
|
||||
});
|
||||
|
||||
await db.execute(sql`drop table if exists ${table}`);
|
||||
|
||||
await db.execute(sql`
|
||||
create table ${table} (
|
||||
id serial primary key,
|
||||
timestamp_string timestamp(6) with time zone not null
|
||||
)
|
||||
`);
|
||||
|
||||
const timestampString = '2022-01-01 00:00:00.123456-0200';
|
||||
|
||||
// 1. Insert date in string format with timezone in it
|
||||
await db.insert(table).values([
|
||||
{ timestamp: timestampString },
|
||||
]);
|
||||
|
||||
// 2. Select date in string format and check that the values are the same
|
||||
const result = await db.select().from(table);
|
||||
|
||||
// 2.1 Notice that postgres will return the date in UTC, but it is exactly the same
|
||||
expect(result).toEqual([{ id: 1, timestamp: '2022-01-01 02:00:00.123456+00' }]);
|
||||
|
||||
// 3. Select as raw query and checke that values are the same
|
||||
const result2 = await db.execute<{
|
||||
id: number;
|
||||
timestamp_string: string;
|
||||
}>(sql`select * from ${table}`);
|
||||
|
||||
// 3.1 Notice that postgres will return the date in UTC, but it is exactlt the same
|
||||
expect(result2).toEqual([{ id: 1, timestamp_string: '2022-01-01 02:00:00.123456+00' }]);
|
||||
|
||||
await db.execute(sql`drop table if exists ${table}`);
|
||||
});
|
||||
|
||||
test('test mode date for timestamp with timezone', async () => {
|
||||
const table = pgTable('all_columns', {
|
||||
id: serial('id').primaryKey(),
|
||||
timestamp: timestamp('timestamp_string', { mode: 'date', withTimezone: true, precision: 3 }).notNull(),
|
||||
});
|
||||
|
||||
await db.execute(sql`drop table if exists ${table}`);
|
||||
|
||||
await db.execute(sql`
|
||||
create table ${table} (
|
||||
id serial primary key,
|
||||
timestamp_string timestamp(3) with time zone not null
|
||||
)
|
||||
`);
|
||||
|
||||
const timestampString = new Date('2022-01-01 00:00:00.456-0200');
|
||||
|
||||
// 1. Insert date in string format with timezone in it
|
||||
await db.insert(table).values([
|
||||
{ timestamp: timestampString },
|
||||
]);
|
||||
|
||||
// 2. Select date in string format and check that the values are the same
|
||||
const result = await db.select().from(table);
|
||||
|
||||
// 2.1 Notice that postgres will return the date in UTC, but it is exactly the same
|
||||
expect(result).toEqual([{ id: 1, timestamp: timestampString }]);
|
||||
|
||||
// 3. Select as raw query and checke that values are the same
|
||||
const result2 = await db.execute<{
|
||||
id: number;
|
||||
timestamp_string: string;
|
||||
}>(sql`select * from ${table}`);
|
||||
|
||||
// 3.1 Notice that postgres will return the date in UTC, but it is exactlt the same
|
||||
expect(result2).toEqual([{ id: 1, timestamp_string: '2022-01-01 02:00:00.456+00' }]);
|
||||
|
||||
await db.execute(sql`drop table if exists ${table}`);
|
||||
});
|
||||
|
||||
test('test mode string for timestamp with timezone in UTC timezone', async () => {
|
||||
// get current timezone from db
|
||||
const timezone = await db.execute<{ TimeZone: string }>(sql`show timezone`);
|
||||
|
||||
// set timezone to UTC
|
||||
await db.execute(sql`set time zone 'UTC'`);
|
||||
|
||||
const table = pgTable('all_columns', {
|
||||
id: serial('id').primaryKey(),
|
||||
timestamp: timestamp('timestamp_string', { mode: 'string', withTimezone: true, precision: 6 }).notNull(),
|
||||
});
|
||||
|
||||
await db.execute(sql`drop table if exists ${table}`);
|
||||
|
||||
await db.execute(sql`
|
||||
create table ${table} (
|
||||
id serial primary key,
|
||||
timestamp_string timestamp(6) with time zone not null
|
||||
)
|
||||
`);
|
||||
|
||||
const timestampString = '2022-01-01 00:00:00.123456-0200';
|
||||
|
||||
// 1. Insert date in string format with timezone in it
|
||||
await db.insert(table).values([
|
||||
{ timestamp: timestampString },
|
||||
]);
|
||||
|
||||
// 2. Select date in string format and check that the values are the same
|
||||
const result = await db.select().from(table);
|
||||
|
||||
// 2.1 Notice that postgres will return the date in UTC, but it is exactly the same
|
||||
expect(result).toEqual([{ id: 1, timestamp: '2022-01-01 02:00:00.123456+00' }]);
|
||||
|
||||
// 3. Select as raw query and checke that values are the same
|
||||
const result2 = await db.execute<{
|
||||
id: number;
|
||||
timestamp_string: string;
|
||||
}>(sql`select * from ${table}`);
|
||||
|
||||
// 3.1 Notice that postgres will return the date in UTC, but it is exactlt the same
|
||||
expect(result2).toEqual([{ id: 1, timestamp_string: '2022-01-01 02:00:00.123456+00' }]);
|
||||
|
||||
await db.execute(sql`set time zone '${sql.raw(timezone[0]!.TimeZone)}'`);
|
||||
|
||||
await db.execute(sql`drop table if exists ${table}`);
|
||||
});
|
||||
|
||||
test('test mode string for timestamp with timezone in different timezone', async () => {
|
||||
// get current timezone from db
|
||||
const timezone = await db.execute<{ TimeZone: string }>(sql`show timezone`);
|
||||
|
||||
// set timezone to HST (UTC - 10)
|
||||
await db.execute(sql`set time zone '-10'`);
|
||||
|
||||
const table = pgTable('all_columns', {
|
||||
id: serial('id').primaryKey(),
|
||||
timestamp: timestamp('timestamp_string', { mode: 'string', withTimezone: true, precision: 6 }).notNull(),
|
||||
});
|
||||
|
||||
await db.execute(sql`drop table if exists ${table}`);
|
||||
|
||||
await db.execute(sql`
|
||||
create table ${table} (
|
||||
id serial primary key,
|
||||
timestamp_string timestamp(6) with time zone not null
|
||||
)
|
||||
`);
|
||||
|
||||
const timestampString = '2022-01-01 00:00:00.123456-1000';
|
||||
|
||||
// 1. Insert date in string format with timezone in it
|
||||
await db.insert(table).values([
|
||||
{ timestamp: timestampString },
|
||||
]);
|
||||
|
||||
// 2. Select date in string format and check that the values are the same
|
||||
const result = await db.select().from(table);
|
||||
|
||||
expect(result).toEqual([{ id: 1, timestamp: '2022-01-01 00:00:00.123456-10' }]);
|
||||
|
||||
// 3. Select as raw query and checke that values are the same
|
||||
const result2 = await db.execute<{
|
||||
id: number;
|
||||
timestamp_string: string;
|
||||
}>(sql`select * from ${table}`);
|
||||
|
||||
expect(result2).toEqual([{ id: 1, timestamp_string: '2022-01-01 00:00:00.123456-10' }]);
|
||||
|
||||
await db.execute(sql`set time zone '${sql.raw(timezone[0]!.TimeZone)}'`);
|
||||
|
||||
await db.execute(sql`drop table if exists ${table}`);
|
||||
});
|
||||
|
||||
skipTests([
|
||||
'migrator : default migration strategy',
|
||||
'migrator : migrate with custom schema',
|
||||
'migrator : migrate with custom table',
|
||||
'migrator : migrate with custom table and custom schema',
|
||||
'insert via db.execute + select via db.execute',
|
||||
'insert via db.execute + returning',
|
||||
'insert via db.execute w/ query builder',
|
||||
'all date and time columns without timezone first case mode string',
|
||||
'all date and time columns without timezone third case mode date',
|
||||
'test mode string for timestamp with timezone',
|
||||
'test mode date for timestamp with timezone',
|
||||
'test mode string for timestamp with timezone in UTC timezone',
|
||||
'test mode string for timestamp with timezone in different timezone',
|
||||
'transaction',
|
||||
'transaction rollback',
|
||||
'nested transaction',
|
||||
'nested transaction rollback',
|
||||
'test $onUpdateFn and $onUpdate works updating',
|
||||
]);
|
||||
|
||||
beforeEach(async () => {
|
||||
await db.execute(sql`drop schema if exists public cascade`);
|
||||
await db.execute(sql`create schema public`);
|
||||
await db.execute(
|
||||
sql`
|
||||
create table users (
|
||||
id serial primary key,
|
||||
name text not null,
|
||||
verified boolean not null default false,
|
||||
jsonb jsonb,
|
||||
created_at timestamptz not null default now()
|
||||
)
|
||||
`,
|
||||
);
|
||||
});
|
||||
|
||||
test('insert via db.execute + select via db.execute', async () => {
|
||||
await db.execute(
|
||||
sql`insert into ${usersTable} (${sql.identifier(usersTable.name.name)}) values (${'John'})`,
|
||||
);
|
||||
|
||||
const result = await db.execute<{ id: number; name: string }>(
|
||||
sql`select id, name from "users"`,
|
||||
);
|
||||
expect(result).toEqual([{ id: 1, name: 'John' }]);
|
||||
});
|
||||
|
||||
test('insert via db.execute + returning', async () => {
|
||||
const inserted = await db.execute<{ id: number; name: string }>(
|
||||
sql`insert into ${usersTable} (${
|
||||
sql.identifier(
|
||||
usersTable.name.name,
|
||||
)
|
||||
}) values (${'John'}) returning ${usersTable.id}, ${usersTable.name}`,
|
||||
);
|
||||
expect(inserted).toEqual([{ id: 1, name: 'John' }]);
|
||||
});
|
||||
|
||||
test('insert via db.execute w/ query builder', async () => {
|
||||
const inserted = await db.execute<Pick<typeof usersTable.$inferSelect, 'id' | 'name'>>(
|
||||
db
|
||||
.insert(usersTable)
|
||||
.values({ name: 'John' })
|
||||
.returning({ id: usersTable.id, name: usersTable.name }),
|
||||
);
|
||||
expect(inserted).toEqual([{ id: 1, name: 'John' }]);
|
||||
});
|
||||
|
||||
tests();
|
||||
cacheTests();
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user