diff --git a/.eslintignore b/.eslintignore
index 94ac4254b..d88c5d722 100644
--- a/.eslintignore
+++ b/.eslintignore
@@ -5,3 +5,4 @@ examples
**/*.js
**/*.mjs
**/*.cjs
+**/playground
diff --git a/.eslintrc.yaml b/.eslintrc.yaml
index 7af1bd426..bc71e00bc 100644
--- a/.eslintrc.yaml
+++ b/.eslintrc.yaml
@@ -16,6 +16,7 @@ rules:
- error
- disallowTypeAnnotations: false
fixStyle: separate-type-imports
+ '@typescript-eslint/no-import-type-side-effects': 'error'
import/no-cycle: error
import/no-self-import: error
import/no-empty-named-blocks: error
@@ -66,3 +67,4 @@ rules:
'@typescript-eslint/ban-ts-comment': 'off'
'@typescript-eslint/no-empty-interface': 'off'
'@typescript-eslint/no-unsafe-declaration-merging': 'off'
+ 'no-inner-declarations': 'off'
diff --git a/.github/workflows/release-feature-branch.yaml b/.github/workflows/release-feature-branch.yaml
index a588079f2..3126e994a 100644
--- a/.github/workflows/release-feature-branch.yaml
+++ b/.github/workflows/release-feature-branch.yaml
@@ -79,11 +79,35 @@ jobs:
- name: Install dependencies
run: pnpm install
+ - name: Check preconditions
+ id: checks
+ shell: bash
+ working-directory: ${{ matrix.package }}
+ run: |
+ old_version="$(jq -r .version package.json)"
+ version="$old_version-$(git rev-parse --short HEAD)"
+ npm version $version
+ tag="${{ github.ref_name }}"
+ is_version_published="$(npm view ${{ matrix.package }} versions --json | jq -r '.[] | select(. == "'$version'") | . == "'$version'"')"
+
+ if [[ "$is_version_published" == "true" ]]; then
+ echo "\`${{ matrix.package }}@$version\` already published, adding tag \`$tag\`" >> $GITHUB_STEP_SUMMARY
+ npm dist-tag add ${{ matrix.package }}@$version $tag
+ else
+ {
+ echo "version=$version"
+ echo "tag=$tag"
+ echo "has_new_release=true"
+ } >> $GITHUB_OUTPUT
+ fi
+
- name: Build
+ if: steps.checks.outputs.has_new_release == 'true'
run: |
pnpm build
- name: Run tests
+ if: steps.checks.outputs.has_new_release == 'true'
env:
PG_CONNECTION_STRING: postgres://postgres:postgres@localhost:5432/drizzle
MYSQL_CONNECTION_STRING: mysql://root:root@localhost:3306/drizzle
@@ -99,28 +123,37 @@ jobs:
pnpm test --filter ${{ matrix.package }}
fi
+ - name: Pack
+ if: steps.checks.outputs.has_new_release == 'true'
+ working-directory: ${{ matrix.package }}
+ shell: bash
+ env:
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_ACCESS_TOKEN }}
+ run: |
+ npm run pack
+
+ - name: Run @arethetypeswrong/cli
+ if: steps.checks.outputs.has_new_release == 'true'
+ working-directory: ${{ matrix.package }}
+ shell: bash
+ env:
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_ACCESS_TOKEN }}
+ run: |
+ pnpm attw package.tgz
+
- name: Publish
- if: github.event_name == 'push'
+ if: github.event_name == 'push' && steps.checks.outputs.has_new_release == 'true'
run: |
- tag="${{ github.ref_name }}"
- old_version="$(jq -r .version package.json)"
- version="$old_version-$(git rev-parse --short HEAD)"
- is_version_published="$(npm view ${{ matrix.package }} versions --json | jq -r '.[] | select(. == "'$version'") | . == "'$version'"')"
+ tag="${{ steps.checks.outputs.tag }}"
+ version="${{ steps.checks.outputs.version }}"
- if [[ "$is_version_published" == "true" ]]; then
- echo "Version $version already published, adding tag $tag"
- npm dist-tag add ${{ matrix.package }}@$version $tag
- else
- echo "Publishing ${{ matrix.package }}@$tag using version $version"
- (cd dist && npm version $version)
- npm run pack
- npm run publish -- --tag $tag
- fi
+ echo "Publishing ${{ matrix.package }}@$tag using version $version"
+ npm run publish -- --tag $tag
echo "npm: \`${{ matrix.package }}@$tag | ${{ matrix.package }}@$version\`" >> $GITHUB_STEP_SUMMARY
# Post release message to Discord
- curl -X POST -H "Content-Type: application/json" -d "{\"embeds\": [{\"title\": \"New \`${{ matrix.package }}\` release! 🎉\", \"url\": \"https://www.npmjs.com/package/${{ matrix.package }}/v/$version\", \"color\": \"12907856\", \"fields\": [{\"name\": \"Version\", \"value\": \"\`$version\`\"}, {\"name\": \"Tag\", \"value\": \"\`$tag\`\"}]}]}" ${{ secrets.DISCORD_DEV_RELEASE_WEBHOOK_URL }}
+ # curl -X POST -H "Content-Type: application/json" -d "{\"embeds\": [{\"title\": \"New \`${{ matrix.package }}\` release! 🎉\", \"url\": \"https://www.npmjs.com/package/${{ matrix.package }}/v/$version\", \"color\": \"12907856\", \"fields\": [{\"name\": \"Version\", \"value\": \"\`$version\`\"}, {\"name\": \"Tag\", \"value\": \"\`$tag\`\"}]}]}" ${{ secrets.DISCORD_DEV_RELEASE_WEBHOOK_URL }}
working-directory: ${{ matrix.package }}
shell: bash
env:
diff --git a/.github/workflows/release-latest.yaml b/.github/workflows/release-latest.yaml
index c49c56707..3153adb32 100644
--- a/.github/workflows/release-latest.yaml
+++ b/.github/workflows/release-latest.yaml
@@ -79,8 +79,12 @@ jobs:
run: |
latest="$(npm view --json ${{ matrix.package }} dist-tags.latest | jq -r)"
version="$(jq -r .version package.json)"
+ is_version_published="$(npm view ${{ matrix.package }} versions --json | jq -r '.[] | select(. == "'$version'") | . == "'$version'"')"
- if [[ "$latest" != "$version" ]]; then
+ if [[ "$is_version_published" == "true" ]]; then
+ echo "\`${{ matrix.package }}@$version\` already published, adding tag \`latest\`" >> $GITHUB_STEP_SUMMARY
+ npm dist-tag add ${{ matrix.package }}@$version latest
+ elif [[ "$latest" != "$version" ]]; then
echo "Latest: $latest"
echo "Current: $version"
@@ -119,6 +123,24 @@ jobs:
pnpm test --filter ${{ matrix.package }}
fi
+ - name: Pack
+ if: steps.checks.outputs.has_new_release == 'true'
+ working-directory: ${{ matrix.package }}
+ shell: bash
+ env:
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_ACCESS_TOKEN }}
+ run: |
+ npm run pack
+
+ - name: Run @arethetypeswrong/cli
+ if: steps.checks.outputs.has_new_release == 'true'
+ working-directory: ${{ matrix.package }}
+ shell: bash
+ env:
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_ACCESS_TOKEN }}
+ run: |
+ pnpm attw package.tgz
+
- name: Publish
if: steps.checks.outputs.has_new_release == 'true'
working-directory: ${{ matrix.package }}
@@ -127,21 +149,14 @@ jobs:
NODE_AUTH_TOKEN: ${{ secrets.NPM_ACCESS_TOKEN }}
run: |
version="${{ steps.checks.outputs.version }}"
- is_version_published="$(npm view ${{ matrix.package }} versions --json | jq -r '.[] | select(. == "'$version'") | . == "'$version'"')"
- if [[ "$is_version_published" == "true" ]]; then
- echo "Version $version already published, adding tag $tag"
- npm dist-tag add ${{ matrix.package }}@$version latest
- else
- echo echo "Publishing ${{ matrix.package }}@$version"
- npm run pack
- npm run publish
- fi
+ echo "Publishing ${{ matrix.package }}@$version"
+ npm run publish
echo "npm: \`+ ${{ matrix.package }}@$version\`" >> $GITHUB_STEP_SUMMARY
# Post release message to Discord
- curl -X POST -H "Content-Type: application/json" -d "{\"embeds\": [{\"title\": \"New \`${{ matrix.package }}\` release! 🎉\", \"url\": \"https://www.npmjs.com/package/${{ matrix.package }}\", \"color\": \"12907856\", \"fields\": [{\"name\": \"Tag\", \"value\": \"\`$tag\`\"}]}]}" ${{ secrets.DISCORD_RELEASE_WEBHOOK_URL }}
+ # curl -X POST -H "Content-Type: application/json" -d "{\"embeds\": [{\"title\": \"New \`${{ matrix.package }}\` release! 🎉\", \"url\": \"https://www.npmjs.com/package/${{ matrix.package }}\", \"color\": \"12907856\", \"fields\": [{\"name\": \"Tag\", \"value\": \"\`$tag\`\"}]}]}" ${{ secrets.DISCORD_RELEASE_WEBHOOK_URL }}
- name: Create GitHub release for ORM package
uses: actions/github-script@v6
diff --git a/changelogs/drizzle-orm/0.28.6.md b/changelogs/drizzle-orm/0.28.6.md
new file mode 100644
index 000000000..7a894c698
--- /dev/null
+++ b/changelogs/drizzle-orm/0.28.6.md
@@ -0,0 +1,140 @@
+## Changes
+
+> **Note**:
+> MySQL `datetime` with `mode: 'date'` will now store dates in UTC strings and retrieve data in UTC as well to align with MySQL behavior for `datetime`. If you need a different behavior and want to handle `datetime` mapping in a different way, please use `mode: 'string'` or [Custom Types](https://orm.drizzle.team/docs/custom-types) implementation
+
+Check [Fix Datetime mapping for MySQL](https://github.com/drizzle-team/drizzle-orm/pull/1082) for implementation details
+
+## New Features
+
+### 🎉 `LibSQL` batch api support
+
+Reference: https://docs.turso.tech/reference/client-access/javascript-typescript-sdk#execute-a-batch-of-statements
+
+Batch API usage example:
+
+```ts
+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,
+ ),
+]);
+```
+
+Type for `batchResponse` in this example would be:
+
+```ts
+type BatchResponse = [
+ {
+ id: number;
+ }[],
+ ResultSet,
+ {
+ id: number;
+ name: string;
+ verified: number;
+ invitedBy: number | null;
+ }[],
+ {
+ id: number;
+ name: string;
+ verified: number;
+ invitedBy: number | null;
+ }[],
+ {
+ id: number;
+ invitedBy: number | null;
+ }[],
+];
+```
+
+All possible builders that can be used inside `db.batch`:
+
+```ts
+`db.all()`,
+`db.get()`,
+`db.values()`,
+`db.run()`,
+`db.query.
.findMany()`,
+`db.query..findFirst()`,
+`db.select()...`,
+`db.update()...`,
+`db.delete()...`,
+`db.insert()...`,
+```
+
+More usage examples here: [integration-tests/tests/libsql-batch.test.ts](https://github.com/drizzle-team/drizzle-orm/pull/1161/files#diff-17253895532e520545027dd48dcdbac2d69a5a49d594974e6d55d7502f89b838R248) and in [docs](https://orm.drizzle.team/docs/batch-api)
+
+### 🎉 Add json mode for text in SQLite
+
+Example
+
+```ts
+const test = sqliteTable('test', {
+ dataTyped: text('data_typed', { mode: 'json' }).$type<{ a: 1 }>().notNull(),
+});
+```
+
+### 🎉 Add `.toSQL()` to Relational Query API calls
+
+Example
+
+```ts
+const query = db.query.usersTable.findFirst().toSQL();
+```
+
+### 🎉 Added new PostgreSQL operators for Arrays - thanks @L-Mario564
+
+List of operators and usage examples
+`arrayContains`, `arrayContained`, `arrayOverlaps`
+
+```ts
+const contains = await db.select({ id: posts.id }).from(posts)
+ .where(arrayContains(posts.tags, ['Typescript', 'ORM']));
+
+const contained = await db.select({ id: posts.id }).from(posts)
+ .where(arrayContained(posts.tags, ['Typescript', 'ORM']));
+
+const overlaps = await db.select({ id: posts.id }).from(posts)
+ .where(arrayOverlaps(posts.tags, ['Typescript', 'ORM']));
+
+const withSubQuery = await db.select({ id: posts.id }).from(posts)
+ .where(arrayContains(
+ posts.tags,
+ db.select({ tags: posts.tags }).from(posts).where(eq(posts.id, 1)),
+ ));
+```
+
+### 🎉 Add more SQL operators for where filter function in Relational Queries - thanks @cayter!
+
+**Before**
+
+```ts
+import { inArray } from "drizzle-orm/pg-core";
+
+await db.users.findFirst({
+ where: (table, _) => inArray(table.id, [ ... ])
+})
+```
+
+**After**
+
+```ts
+await db.users.findFirst({
+ where: (table, { inArray }) => inArray(table.id, [ ... ])
+})
+```
+
+## Bug Fixes
+
+- 🐛 [Correct where in on conflict in sqlite](https://github.com/drizzle-team/drizzle-orm/pull/1076) - Thanks @hanssonduck!
+- 🐛 [Fix libsql/client type import](https://github.com/drizzle-team/drizzle-orm/pull/1122) - Thanks @luisfvieirasilva!
+- 🐛 [Fix: raw sql query not being mapped properly on RDS](https://github.com/drizzle-team/drizzle-orm/pull/1071) - Thanks @boian-ivanov
+- 🐛 [Fix Datetime mapping for MySQL](https://github.com/drizzle-team/drizzle-orm/pull/1082) - thanks @Angelelz
+- 🐛 [Fix smallserial generating as serial](https://github.com/drizzle-team/drizzle-orm/pull/1127) - thanks @L-Mario564
diff --git a/changelogs/drizzle-orm/0.29.0.md b/changelogs/drizzle-orm/0.29.0.md
new file mode 100644
index 000000000..18a24e502
--- /dev/null
+++ b/changelogs/drizzle-orm/0.29.0.md
@@ -0,0 +1,321 @@
+> Drizzle ORM version `0.29.0` will require a minimum Drizzle Kit version of `0.20.0`, and vice versa. Therefore, when upgrading to a newer version of Drizzle ORM, you will also need to upgrade Drizzle Kit. This may result in some breaking changes throughout the versions, especially if you need to upgrade Drizzle Kit and your Drizzle ORM version is older than `<0.28.0`
+
+## New Features
+
+### 🎉 MySQL `unsigned` option for bigint
+
+You can now specify `bigint unsigned` type
+
+```ts
+const table = mysqlTable('table', {
+ id: bigint('id', { mode: 'number', unsigned: true }),
+});
+```
+
+Read more in [docs](https://orm.drizzle.team/docs/column-types/mysql#bigint)
+
+### 🎉 Improved query builder types
+
+Starting from `0.29.0` by default, as all the query builders in Drizzle try to conform to SQL as much as possible, you can only invoke most of the methods once. For example, in a SELECT statement there might only be one WHERE clause, so you can only invoke .where() once:
+
+```ts
+const query = db
+ .select()
+ .from(users)
+ .where(eq(users.id, 1))
+ .where(eq(users.name, 'John')); // ❌ Type error - where() can only be invoked once
+```
+
+This behavior is useful for conventional query building, i.e. when you create the whole query at once. However, it becomes a problem when you want to build a query dynamically, i.e. if you have a shared function that takes a query builder and enhances it. To solve this problem, Drizzle provides a special 'dynamic' mode for query builders, which removes the restriction of invoking methods only once. To enable it, you need to call .$dynamic() on a query builder.
+
+Let's see how it works by implementing a simple withPagination function that adds LIMIT and OFFSET clauses to a query based on the provided page number and an optional page size:
+
+```ts
+function withPagination(
+ qb: T,
+ page: number,
+ pageSize: number = 10,
+) {
+ return qb.limit(pageSize).offset(page * pageSize);
+}
+
+const query = db.select().from(users).where(eq(users.id, 1));
+withPagination(query, 1); // ❌ Type error - the query builder is not in dynamic mode
+
+const dynamicQuery = query.$dynamic();
+withPagination(dynamicQuery, 1); // ✅ OK
+```
+
+Note that the withPagination function is generic, which allows you to modify the result type of the query builder inside it, for example by adding a join:
+
+```ts
+function withFriends(qb: T) {
+ return qb.leftJoin(friends, eq(friends.userId, users.id));
+}
+
+let query = db.select().from(users).where(eq(users.id, 1)).$dynamic();
+query = withFriends(query);
+```
+
+Read more in [docs](https://orm.drizzle.team/docs/dynamic-query-building)
+
+### 🎉 Possibility to specify name for primary keys and foreign keys
+
+There is an issue when constraint names exceed the 64-character limit of the database. This causes the database engine to truncate the name, potentially leading to issues. Starting from `0.29.0`, you have the option to specify custom names for both `primaryKey()` and `foreignKey()`. We have also deprecated the old `primaryKey()` syntax, which can still be used but will be removed in future releases
+
+```ts
+const table = pgTable('table', {
+ id: integer('id'),
+ name: text('name'),
+}, (table) => ({
+ cpk: primaryKey({ name: 'composite_key', columns: [table.id, table.name] }),
+ cfk: foreignKey({
+ name: 'fkName',
+ columns: [table.id],
+ foreignColumns: [table.name],
+ }),
+}));
+```
+
+Read more in [docs](https://orm.drizzle.team/docs/indexes-constraints#composite-primary-key)
+
+### 🎉 Read Replicas Support
+
+You can now use the Drizzle `withReplica` function to specify different database connections for read replicas and the main instance for write operations. By default, `withReplicas` will use a random read replica for read operations and the main instance for all other data modification operations. You can also specify custom logic for choosing which read replica connection to use. You have the freedom to make any weighted, custom decision for that. Here are some usage examples:
+
+```ts
+const primaryDb = drizzle(client);
+const read1 = drizzle(client);
+const read2 = drizzle(client);
+
+const db = withReplicas(primaryDb, [read1, read2]);
+
+// read from primary
+db.$primary.select().from(usersTable);
+
+// read from either read1 connection or read2 connection
+db.select().from(usersTable)
+
+// use primary database for delete operation
+db.delete(usersTable).where(eq(usersTable.id, 1))
+```
+
+Implementation example of custom logic for selecting read replicas, where the first replica has a 70% chance of being chosen, and the second replica has a 30% chance of being chosen. Note that you can implement any type of random selection for read replicas
+
+```ts
+const db = withReplicas(primaryDb, [read1, read2], (replicas) => {
+ const weight = [0.7, 0.3];
+ let cumulativeProbability = 0;
+ const rand = Math.random();
+
+ for (const [i, replica] of replicas.entries()) {
+ cumulativeProbability += weight[i]!;
+ if (rand < cumulativeProbability) return replica;
+ }
+ return replicas[0]!
+});
+```
+
+`withReplicas` function is available for all dialects in Drizzle ORM
+
+Read more in [docs](https://orm.drizzle.team/docs/read-replicas)
+
+### 🎉 Set operators support (UNION, UNION ALL, INTERSECT, INTERSECT ALL, EXCEPT, EXCEPT ALL)
+
+Huge thanks to @Angelelz for the significant contribution he made, from API discussions to proper type checks and runtime logic, along with an extensive set of tests. This greatly assisted us in delivering this feature in this release
+
+Usage examples:
+All set operators can be used in a two ways: `import approach` or `builder approach`
+
+##### Import approach
+```ts
+import { union } from 'drizzle-orm/pg-core'
+
+const allUsersQuery = db.select().from(users);
+const allCustomersQuery = db.select().from(customers);
+
+const result = await union(allUsersQuery, allCustomersQuery)
+```
+
+##### Builder approach
+```ts
+const result = await db.select().from(users).union(db.select().from(customers));
+```
+
+Read more in [docs](https://orm.drizzle.team/docs/set-operations)
+
+### 🎉 New MySQL Proxy Driver
+
+A new driver has been released, allowing you to create your own implementation for an HTTP driver using a MySQL database. You can find usage examples in the `./examples/mysql-proxy` folder
+
+You need to implement two endpoints on your server that will be used for queries and migrations(Migrate endpoint is optional and only if you want to use drizzle migrations). Both the server and driver implementation are up to you, so you are not restricted in any way. You can add custom mappings, logging, and much more
+
+You can find both server and driver implementation examples in the `./examples/mysql-proxy` folder
+
+```ts
+// Driver
+import axios from 'axios';
+import { eq } from 'drizzle-orm/expressions';
+import { drizzle } from 'drizzle-orm/mysql-proxy';
+import { migrate } from 'drizzle-orm/mysql-proxy/migrator';
+import { cities, users } from './schema';
+
+async function main() {
+ const db = drizzle(async (sql, params, method) => {
+ try {
+ const rows = await axios.post(`${process.env.REMOTE_DRIVER}/query`, {
+ sql,
+ params,
+ method,
+ });
+
+ return { rows: rows.data };
+ } catch (e: any) {
+ console.error('Error from pg proxy server:', e.response.data);
+ return { rows: [] };
+ }
+ });
+
+ await migrate(db, async (queries) => {
+ try {
+ await axios.post(`${process.env.REMOTE_DRIVER}/migrate`, { queries });
+ } catch (e) {
+ console.log(e);
+ throw new Error('Proxy server cannot run migrations');
+ }
+ }, { migrationsFolder: 'drizzle' });
+
+ await db.insert(cities).values({ id: 1, name: 'name' });
+
+ await db.insert(users).values({
+ id: 1,
+ name: 'name',
+ email: 'email',
+ cityId: 1,
+ });
+
+ const usersToCityResponse = await db.select().from(users).leftJoin(
+ cities,
+ eq(users.cityId, cities.id),
+ );
+}
+```
+
+### 🎉 New PostgreSQL Proxy Driver
+
+Same as MySQL you can now implement your own http driver for PostgreSQL database. You can find usage examples in the `./examples/pg-proxy` folder
+
+You need to implement two endpoints on your server that will be used for queries and migrations (Migrate endpoint is optional and only if you want to use drizzle migrations). Both the server and driver implementation are up to you, so you are not restricted in any way. You can add custom mappings, logging, and much more
+
+You can find both server and driver implementation examples in the `./examples/pg-proxy` folder
+
+```ts
+import axios from 'axios';
+import { eq } from 'drizzle-orm/expressions';
+import { drizzle } from 'drizzle-orm/pg-proxy';
+import { migrate } from 'drizzle-orm/pg-proxy/migrator';
+import { cities, users } from './schema';
+
+async function main() {
+ const db = drizzle(async (sql, params, method) => {
+ try {
+ const rows = await axios.post(`${process.env.REMOTE_DRIVER}/query`, { sql, params, method });
+
+ return { rows: rows.data };
+ } catch (e: any) {
+ console.error('Error from pg proxy server:', e.response.data);
+ return { rows: [] };
+ }
+ });
+
+ await migrate(db, async (queries) => {
+ try {
+ await axios.post(`${process.env.REMOTE_DRIVER}/query`, { queries });
+ } catch (e) {
+ console.log(e);
+ throw new Error('Proxy server cannot run migrations');
+ }
+ }, { migrationsFolder: 'drizzle' });
+
+ const insertedCity = await db.insert(cities).values({ id: 1, name: 'name' }).returning();
+ const insertedUser = await db.insert(users).values({ id: 1, name: 'name', email: 'email', cityId: 1 });
+ const usersToCityResponse = await db.select().from(users).leftJoin(cities, eq(users.cityId, cities.id));
+}
+```
+
+### 🎉 `D1` Batch API support
+
+Reference: https://developers.cloudflare.com/d1/platform/client-api/#dbbatch
+
+Batch API usage example:
+
+```ts
+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,
+ ),
+]);
+```
+
+Type for `batchResponse` in this example would be:
+
+```ts
+type BatchResponse = [
+ {
+ id: number;
+ }[],
+ D1Result,
+ {
+ id: number;
+ name: string;
+ verified: number;
+ invitedBy: number | null;
+ }[],
+ {
+ id: number;
+ name: string;
+ verified: number;
+ invitedBy: number | null;
+ }[],
+ {
+ id: number;
+ invitedBy: number | null;
+ }[],
+];
+```
+
+All possible builders that can be used inside `db.batch`:
+
+```ts
+`db.all()`,
+`db.get()`,
+`db.values()`,
+`db.run()`,
+`db.query..findMany()`,
+`db.query..findFirst()`,
+`db.select()...`,
+`db.update()...`,
+`db.delete()...`,
+`db.insert()...`,
+```
+
+More usage examples here: [integration-tests/tests/d1-batch.test.ts](https://github.com/drizzle-team/drizzle-orm/blob/beta/integration-tests/tests/d1-batch.test.ts) and in [docs](https://orm.drizzle.team/docs/batch-api)
+
+---
+## Drizzle Kit 0.20.0
+
+1. New way to define drizzle.config using `defineConfig` function
+2. Possibility to access Cloudflare D1 with Drizzle Studio using wrangler.toml file
+3. Drizzle Studio is migrating to https://local.drizzle.studio/
+4. `bigint unsigned` support
+5. `primaryKeys` and `foreignKeys` now can have custom names
+6. Environment variables are now automatically fetched
+7. Some bug fixes and improvements
+
+You can read more about drizzle-kit updates [here](https://github.com/drizzle-team/drizzle-kit-mirror/releases/tag/v0.20.0)
\ No newline at end of file
diff --git a/drizzle-orm/package.json b/drizzle-orm/package.json
index e9fed1334..6d2613d75 100644
--- a/drizzle-orm/package.json
+++ b/drizzle-orm/package.json
@@ -1,6 +1,6 @@
{
"name": "drizzle-orm",
- "version": "0.28.5",
+ "version": "0.29.0",
"description": "Drizzle ORM package for SQL databases",
"type": "module",
"scripts": {
@@ -12,7 +12,7 @@
"publish": "npm publish package.tgz"
},
"main": "./index.cjs",
- "module": "./index.mjs",
+ "module": "./index.js",
"types": "./index.d.ts",
"sideEffects": false,
"publishConfig": {
@@ -124,15 +124,12 @@
},
"devDependencies": {
"@aws-sdk/client-rds-data": "^3.344.0",
- "@cloudflare/workers-types": "^4.20230518.0",
+ "@cloudflare/workers-types": "^4.20230904.0",
"@libsql/client": "^0.1.6",
"@neondatabase/serverless": "^0.4.24",
"@opentelemetry/api": "^1.4.1",
"@originjs/vite-plugin-commonjs": "^1.0.3",
"@planetscale/database": "^1.7.0",
- "@rollup/plugin-json": "^6.0.0",
- "@rollup/plugin-replace": "^5.0.2",
- "@rollup/plugin-typescript": "^11.1.1",
"@types/better-sqlite3": "^7.6.4",
"@types/node": "^20.2.5",
"@types/pg": "^8.10.1",
@@ -141,16 +138,11 @@
"better-sqlite3": "^8.4.0",
"bun-types": "^0.6.6",
"cpy": "^10.1.0",
- "cpy-cli": "^5.0.0",
"knex": "^2.4.2",
"kysely": "^0.25.0",
"mysql2": "^3.3.3",
"pg": "^8.11.0",
"postgres": "^3.3.5",
- "rimraf": "^5.0.0",
- "rollup": "^3.27.2",
- "rollup-plugin-dts": "^5.3.1",
- "rollup-plugin-typescript2": "^0.35.0",
"sql.js": "^1.8.0",
"sqlite3": "^5.1.2",
"tslib": "^2.5.2",
diff --git a/drizzle-orm/rollup.cjs.config.ts b/drizzle-orm/rollup.cjs.config.ts
deleted file mode 100644
index 52f7b959e..000000000
--- a/drizzle-orm/rollup.cjs.config.ts
+++ /dev/null
@@ -1,39 +0,0 @@
-import json from '@rollup/plugin-json';
-import replace from '@rollup/plugin-replace';
-import typescript from '@rollup/plugin-typescript';
-import { defineConfig } from 'rollup';
-
-import { entries, external } from './rollup.common';
-
-export default defineConfig([
- {
- input: entries.reduce>((acc, entry) => {
- const from = 'src/' + entry + '.ts';
- const to = entry;
- acc[to] = from;
- return acc;
- }, {}),
- output: {
- format: 'cjs',
- dir: 'dist.new',
- entryFileNames: '[name].cjs',
- chunkFileNames: '[name]-[hash].cjs',
- sourcemap: true,
- },
- external,
- plugins: [
- replace({
- 'await import': 'require',
- preventAssignment: true,
- }),
- json({
- preferConst: true,
- }),
- typescript({
- tsconfig: 'tsconfig.cjs.json',
- outputToFilesystem: true,
- incremental: false,
- }),
- ],
- },
-]);
diff --git a/drizzle-orm/rollup.common.ts b/drizzle-orm/rollup.common.ts
deleted file mode 100644
index 741c1c38f..000000000
--- a/drizzle-orm/rollup.common.ts
+++ /dev/null
@@ -1,59 +0,0 @@
-export const entries = [
- 'index',
- 'aws-data-api/pg/index',
- 'aws-data-api/pg/migrator',
- 'better-sqlite3/index',
- 'better-sqlite3/migrator',
- 'bun-sqlite/index',
- 'bun-sqlite/migrator',
- 'd1/index',
- 'd1/migrator',
- 'vercel-postgres/migrator',
- 'vercel-postgres/index',
- 'knex/index',
- 'kysely/index',
- 'libsql/index',
- 'libsql/migrator',
- 'mysql-core/index',
- 'mysql2/index',
- 'mysql2/migrator',
- 'neon-serverless/index',
- 'neon-serverless/migrator',
- 'neon-http/index',
- 'neon-http/migrator',
- 'node-postgres/index',
- 'node-postgres/migrator',
- 'pg-core/index',
- 'planetscale-serverless/index',
- 'planetscale-serverless/migrator',
- 'postgres-js/index',
- 'postgres-js/migrator',
- 'sql-js/index',
- 'sql-js/migrator',
- 'sqlite-core/index',
- 'sqlite-proxy/index',
- 'sqlite-proxy/migrator',
- 'migrator',
- 'version',
-];
-
-export const external = [
- /^node:/,
- '@aws-sdk/client-rds-data',
- '@cloudflare/workers-types',
- '@libsql/client',
- '@neondatabase/serverless',
- '@planetscale/database',
- 'better-sqlite3',
- 'pg',
- 'sql.js',
- 'knex',
- 'kysely',
- 'mysql2',
- 'mysql2/promise',
- 'postgres',
- 'sqlite3',
- 'bun:sqlite',
- '@opentelemetry/api',
- '@vercel/postgres',
-];
diff --git a/drizzle-orm/rollup.esm.config.ts b/drizzle-orm/rollup.esm.config.ts
deleted file mode 100644
index 4b3a04910..000000000
--- a/drizzle-orm/rollup.esm.config.ts
+++ /dev/null
@@ -1,34 +0,0 @@
-import json from '@rollup/plugin-json';
-import typescript from '@rollup/plugin-typescript';
-import { defineConfig } from 'rollup';
-
-import { entries, external } from './rollup.common';
-
-export default defineConfig([
- {
- input: entries.reduce>((acc, entry) => {
- const from = 'src/' + entry + '.ts';
- const to = entry;
- acc[to] = from;
- return acc;
- }, {}),
- output: {
- format: 'esm',
- dir: 'dist.new',
- entryFileNames: '[name].mjs',
- chunkFileNames: '[name]-[hash].mjs',
- sourcemap: true,
- },
- external,
- plugins: [
- json({
- preferConst: true,
- }),
- typescript({
- tsconfig: 'tsconfig.esm.json',
- outputToFilesystem: true,
- incremental: false,
- }),
- ],
- },
-]);
diff --git a/drizzle-orm/scripts/build.ts b/drizzle-orm/scripts/build.ts
index e5cf56a94..058a719b9 100755
--- a/drizzle-orm/scripts/build.ts
+++ b/drizzle-orm/scripts/build.ts
@@ -2,10 +2,10 @@
import 'zx/globals';
import cpy from 'cpy';
-import { entries } from '../rollup.common';
+async function updateAndCopyPackageJson() {
+ const pkg = await fs.readJSON('package.json');
-function updateAndCopyPackageJson() {
- const pkg = fs.readJSONSync('package.json');
+ const entries = await glob('src/**/*.ts');
pkg.exports = entries.reduce<
Record
>(
- (acc, entry) => {
+ (acc, rawEntry) => {
+ const entry = rawEntry.match(/src\/(.*)\.ts/)![1]!;
const exportsEntry = entry === 'index' ? '.' : './' + entry.replace(/\/index$/, '');
- const importEntry = `./${entry}.mjs`;
+ const importEntry = `./${entry}.js`;
const requireEntry = `./${entry}.cjs`;
acc[exportsEntry] = {
import: {
- types: `./${entry}.d.mts`,
+ types: `./${entry}.d.ts`,
default: importEntry,
},
require: {
@@ -42,21 +43,34 @@ function updateAndCopyPackageJson() {
{},
);
- fs.writeJSONSync('dist.new/package.json', pkg, { spaces: 2 });
+ await fs.writeJSON('dist.new/package.json', pkg, { spaces: 2 });
}
-await $`rollup --config rollup.cjs.config.ts --configPlugin typescript`;
-await $`rollup --config rollup.esm.config.ts --configPlugin typescript`;
-await $`rimraf dist-dts && tsc -p tsconfig.dts.json && resolve-tspaths -p tsconfig.dts.json --out dist-dts`;
-await cpy('dist-dts/**/*.d.ts', 'dist.new', {
- rename: (basename) => basename.replace(/\.d\.ts$/, '.d.mts'),
-});
-await cpy('dist-dts/**/*.d.ts', 'dist.new', {
- rename: (basename) => basename.replace(/\.d\.ts$/, '.d.cts'),
-});
-await cpy('dist-dts/**/*.d.ts', 'dist.new');
-fs.removeSync('dist-dts');
-fs.copySync('../README.md', 'dist.new/README.md');
-updateAndCopyPackageJson();
-fs.removeSync('dist');
-fs.renameSync('dist.new', 'dist');
+await fs.remove('dist.new');
+
+await Promise.all([
+ (async () => {
+ await $`tsup`;
+ })(),
+ (async () => {
+ await $`tsc -p tsconfig.dts.json`;
+ await cpy('dist-dts/**/*.d.ts', 'dist.new', {
+ rename: (basename) => basename.replace(/\.d\.ts$/, '.d.cts'),
+ });
+ await cpy('dist-dts/**/*.d.ts', 'dist.new', {
+ rename: (basename) => basename.replace(/\.d\.ts$/, '.d.ts'),
+ });
+ })(),
+]);
+
+await Promise.all([
+ $`tsup src/version.ts --no-config --dts --format esm --outDir dist.new`,
+ $`tsup src/version.ts --no-config --dts --format cjs --outDir dist.new`,
+]);
+
+await $`scripts/fix-imports.ts`;
+
+await fs.copy('../README.md', 'dist.new/README.md');
+await updateAndCopyPackageJson();
+await fs.remove('dist');
+await fs.rename('dist.new', 'dist');
diff --git a/drizzle-orm/scripts/fix-imports.ts b/drizzle-orm/scripts/fix-imports.ts
new file mode 100755
index 000000000..f2035eeda
--- /dev/null
+++ b/drizzle-orm/scripts/fix-imports.ts
@@ -0,0 +1,89 @@
+#!/usr/bin/env -S pnpm tsx
+import 'zx/globals';
+
+import path from 'node:path';
+import { parse, print, visit } from 'recast';
+import parser from 'recast/parsers/typescript';
+
+function resolvePathAlias(importPath: string, file: string) {
+ if (importPath.startsWith('~/')) {
+ const relativePath = path.relative(path.dirname(file), path.resolve('dist.new', importPath.slice(2)));
+ importPath = relativePath.startsWith('.') ? relativePath : './' + relativePath;
+ }
+
+ return importPath;
+}
+
+function fixImportPath(importPath: string, file: string, ext: string) {
+ importPath = resolvePathAlias(importPath, file);
+
+ if (!/\..*\.(js|ts)$/.test(importPath)) {
+ return importPath;
+ }
+
+ return importPath.replace(/\.(js|ts)$/, ext);
+}
+
+const cjsFiles = await glob('dist.new/**/*.{cjs,d.cts}');
+
+await Promise.all(cjsFiles.map(async (file) => {
+ const code = parse(await fs.readFile(file, 'utf8'), { parser });
+
+ visit(code, {
+ visitImportDeclaration(path) {
+ path.value.source.value = fixImportPath(path.value.source.value, file, '.cjs');
+ this.traverse(path);
+ },
+ visitExportAllDeclaration(path) {
+ path.value.source.value = fixImportPath(path.value.source.value, file, '.cjs');
+ this.traverse(path);
+ },
+ visitExportNamedDeclaration(path) {
+ if (path.value.source) {
+ path.value.source.value = fixImportPath(path.value.source.value, file, '.cjs');
+ }
+ this.traverse(path);
+ },
+ visitCallExpression(path) {
+ if (path.value.callee.type === 'Identifier' && path.value.callee.name === 'require') {
+ path.value.arguments[0].value = fixImportPath(path.value.arguments[0].value, file, '.cjs');
+ }
+ this.traverse(path);
+ },
+ visitTSImportType(path) {
+ path.value.argument.value = resolvePathAlias(path.value.argument.value, file);
+ this.traverse(path);
+ },
+ });
+
+ await fs.writeFile(file, print(code).code);
+}));
+
+const esmFiles = await glob('dist.new/**/*.{js,d.ts}');
+
+await Promise.all(esmFiles.map(async (file) => {
+ const code = parse(await fs.readFile(file, 'utf8'), { parser });
+
+ visit(code, {
+ visitImportDeclaration(path) {
+ path.value.source.value = fixImportPath(path.value.source.value, file, '.js');
+ this.traverse(path);
+ },
+ visitExportAllDeclaration(path) {
+ path.value.source.value = fixImportPath(path.value.source.value, file, '.js');
+ this.traverse(path);
+ },
+ visitExportNamedDeclaration(path) {
+ if (path.value.source) {
+ path.value.source.value = fixImportPath(path.value.source.value, file, '.js');
+ }
+ this.traverse(path);
+ },
+ visitTSImportType(path) {
+ path.value.argument.value = fixImportPath(path.value.argument.value, file, '.js');
+ this.traverse(path);
+ },
+ });
+
+ await fs.writeFile(file, print(code).code);
+}));
diff --git a/drizzle-orm/src/alias.ts b/drizzle-orm/src/alias.ts
index e570921d2..ecbc2dc56 100644
--- a/drizzle-orm/src/alias.ts
+++ b/drizzle-orm/src/alias.ts
@@ -2,9 +2,10 @@ import type { AnyColumn } from './column.ts';
import { Column } from './column.ts';
import { entityKind, is } from './entity.ts';
import type { Relation } from './relations.ts';
-import { SQL, sql } from './sql/index.ts';
+import type { View} from './sql/sql.ts';
+import { SQL, sql } from './sql/sql.ts';
import { Table } from './table.ts';
-import { type View, ViewBaseConfig } from './view.ts';
+import { ViewBaseConfig } from './view-common.ts';
export class ColumnAliasProxyHandler implements ProxyHandler {
static readonly [entityKind]: string = 'ColumnAliasProxyHandler';
diff --git a/drizzle-orm/src/aws-data-api/common/index.ts b/drizzle-orm/src/aws-data-api/common/index.ts
index 805011941..9a657ba6c 100644
--- a/drizzle-orm/src/aws-data-api/common/index.ts
+++ b/drizzle-orm/src/aws-data-api/common/index.ts
@@ -1,6 +1,6 @@
import type { Field } from '@aws-sdk/client-rds-data';
import { TypeHint } from '@aws-sdk/client-rds-data';
-import type { QueryTypingsValue } from '~/sql/index.ts';
+import type { QueryTypingsValue } from '~/sql/sql.ts';
export function getValueFromDataApi(field: Field) {
if (field.stringValue !== undefined) {
diff --git a/drizzle-orm/src/aws-data-api/pg/driver.ts b/drizzle-orm/src/aws-data-api/pg/driver.ts
index dac77b54d..e5acbe939 100644
--- a/drizzle-orm/src/aws-data-api/pg/driver.ts
+++ b/drizzle-orm/src/aws-data-api/pg/driver.ts
@@ -9,7 +9,7 @@ import {
type RelationalSchemaConfig,
type TablesRelationalConfig,
} from '~/relations.ts';
-import { type DrizzleConfig } from '~/utils.ts';
+import type { DrizzleConfig } from '~/utils.ts';
import type { AwsDataApiClient, AwsDataApiPgQueryResultHKT } from './session.ts';
import { AwsDataApiSession } from './session.ts';
diff --git a/drizzle-orm/src/aws-data-api/pg/session.ts b/drizzle-orm/src/aws-data-api/pg/session.ts
index 97d3dd3c0..53519e6b9 100644
--- a/drizzle-orm/src/aws-data-api/pg/session.ts
+++ b/drizzle-orm/src/aws-data-api/pg/session.ts
@@ -1,4 +1,4 @@
-import type { ExecuteStatementCommandOutput, Field, RDSDataClient } from '@aws-sdk/client-rds-data';
+import type { ColumnMetadata, ExecuteStatementCommandOutput, Field, RDSDataClient } from '@aws-sdk/client-rds-data';
import {
BeginTransactionCommand,
CommitTransactionCommand,
@@ -17,8 +17,8 @@ import {
type QueryResultHKT,
} from '~/pg-core/index.ts';
import type { SelectedFieldsOrdered } from '~/pg-core/query-builders/select.types.ts';
-import { type RelationalSchemaConfig, type TablesRelationalConfig } from '~/relations.ts';
-import { fillPlaceholders, type Query, type QueryTypingsValue, type SQL, sql } from '~/sql/index.ts';
+import type { RelationalSchemaConfig, TablesRelationalConfig } from '~/relations.ts';
+import { fillPlaceholders, type QueryTypingsValue, type QueryWithTypings, type SQL, sql } from '~/sql/sql.ts';
import { mapResultRow } from '~/utils.ts';
import { getValueFromDataApi, toValueParam } from '../common/index.ts';
@@ -48,6 +48,7 @@ export class AwsDataApiPreparedQuery extends Prep
resourceArn: options.resourceArn,
database: options.database,
transactionId,
+ includeResultMetadata: !fields && !customResultMapper,
});
}
@@ -80,6 +81,9 @@ export class AwsDataApiPreparedQuery extends Prep
const { fields, rawQuery, client, customResultMapper } = this;
if (!fields && !customResultMapper) {
const result = await client.send(rawQuery);
+ if (result.columnMetadata && result.columnMetadata.length > 0) {
+ return this.mapResultRows(result.records ?? [], result.columnMetadata);
+ }
return result.records ?? [];
}
@@ -89,6 +93,18 @@ export class AwsDataApiPreparedQuery extends Prep
return row.map((field: Field) => getValueFromDataApi(field));
});
}
+
+ /** @internal */
+ mapResultRows(records: Field[][], columnMetadata: ColumnMetadata[]) {
+ return records.map((record) => {
+ const row: Record = {};
+ for (const [index, field] of record.entries()) {
+ const { name } = columnMetadata[index]!;
+ row[name ?? index] = getValueFromDataApi(field); // not what to default if name is undefined
+ }
+ return row;
+ });
+ }
}
export interface AwsDataApiSessionOptions {
@@ -131,7 +147,7 @@ export class AwsDataApiSession<
}
prepareQuery(
- query: Query,
+ query: QueryWithTypings,
fields: SelectedFieldsOrdered | undefined,
transactionId?: string,
customResultMapper?: (rows: unknown[][]) => T['execute'],
diff --git a/drizzle-orm/src/batch.ts b/drizzle-orm/src/batch.ts
new file mode 100644
index 000000000..0931440a5
--- /dev/null
+++ b/drizzle-orm/src/batch.ts
@@ -0,0 +1,8 @@
+import type { Dialect } from './column-builder.ts';
+import type { RunnableQuery } from './runnable-query.ts';
+
+export type BatchItem = RunnableQuery;
+
+export type BatchResponse = {
+ [K in keyof T]: T[K]['_']['result'];
+};
diff --git a/drizzle-orm/src/better-sqlite3/driver.ts b/drizzle-orm/src/better-sqlite3/driver.ts
index ac2c7db0a..728586e57 100644
--- a/drizzle-orm/src/better-sqlite3/driver.ts
+++ b/drizzle-orm/src/better-sqlite3/driver.ts
@@ -8,7 +8,7 @@ import {
} from '~/relations.ts';
import { BaseSQLiteDatabase } from '~/sqlite-core/db.ts';
import { SQLiteSyncDialect } from '~/sqlite-core/dialect.ts';
-import { type DrizzleConfig } from '~/utils.ts';
+import type { DrizzleConfig } from '~/utils.ts';
import { BetterSQLiteSession } from './session.ts';
export type BetterSQLite3Database<
diff --git a/drizzle-orm/src/better-sqlite3/session.ts b/drizzle-orm/src/better-sqlite3/session.ts
index 74be6830b..20d1612c2 100644
--- a/drizzle-orm/src/better-sqlite3/session.ts
+++ b/drizzle-orm/src/better-sqlite3/session.ts
@@ -2,15 +2,15 @@ import type { Database, RunResult, Statement } from 'better-sqlite3';
import { entityKind } from '~/entity.ts';
import type { Logger } from '~/logger.ts';
import { NoopLogger } from '~/logger.ts';
-import { type RelationalSchemaConfig, type TablesRelationalConfig } from '~/relations.ts';
-import { fillPlaceholders, type Query, sql } from '~/sql/index.ts';
+import type { RelationalSchemaConfig, TablesRelationalConfig } from '~/relations.ts';
+import { fillPlaceholders, type Query, sql } from '~/sql/sql.ts';
import type { SQLiteSyncDialect } from '~/sqlite-core/dialect.ts';
import { SQLiteTransaction } from '~/sqlite-core/index.ts';
import type { SelectedFieldsOrdered } from '~/sqlite-core/query-builders/select.types.ts';
import {
- PreparedQuery as PreparedQueryBase,
type PreparedQueryConfig as PreparedQueryConfigBase,
type SQLiteExecuteMethod,
+ SQLitePreparedQuery as PreparedQueryBase,
SQLiteSession,
type SQLiteTransactionConfig,
} from '~/sqlite-core/session.ts';
@@ -47,7 +47,7 @@ export class BetterSQLiteSession<
customResultMapper?: (rows: unknown[][]) => unknown,
): PreparedQuery {
const stmt = this.client.prepare(query.sql);
- return new PreparedQuery(stmt, query.sql, query.params, this.logger, fields, executeMethod, customResultMapper);
+ return new PreparedQuery(stmt, query, this.logger, fields, executeMethod, customResultMapper);
}
override transaction(
@@ -88,27 +88,26 @@ export class PreparedQuery
constructor(
private stmt: Statement,
- private queryString: string,
- private params: unknown[],
+ query: Query,
private logger: Logger,
private fields: SelectedFieldsOrdered | undefined,
executeMethod: SQLiteExecuteMethod,
private customResultMapper?: (rows: unknown[][]) => unknown,
) {
- super('sync', executeMethod);
+ super('sync', executeMethod, query);
}
run(placeholderValues?: Record): RunResult {
- const params = fillPlaceholders(this.params, placeholderValues ?? {});
- this.logger.logQuery(this.queryString, params);
+ const params = fillPlaceholders(this.query.params, placeholderValues ?? {});
+ this.logger.logQuery(this.query.sql, params);
return this.stmt.run(...params);
}
all(placeholderValues?: Record): T['all'] {
- const { fields, joinsNotNullableMap, queryString, logger, stmt, customResultMapper } = this;
+ const { fields, joinsNotNullableMap, query, logger, stmt, customResultMapper } = this;
if (!fields && !customResultMapper) {
- const params = fillPlaceholders(this.params, placeholderValues ?? {});
- logger.logQuery(queryString, params);
+ const params = fillPlaceholders(query.params, placeholderValues ?? {});
+ logger.logQuery(query.sql, params);
return stmt.all(...params);
}
@@ -120,8 +119,8 @@ export class PreparedQuery
}
get(placeholderValues?: Record): T['get'] {
- const params = fillPlaceholders(this.params, placeholderValues ?? {});
- this.logger.logQuery(this.queryString, params);
+ const params = fillPlaceholders(this.query.params, placeholderValues ?? {});
+ this.logger.logQuery(this.query.sql, params);
const { fields, stmt, joinsNotNullableMap, customResultMapper } = this;
if (!fields && !customResultMapper) {
@@ -142,8 +141,8 @@ export class PreparedQuery
}
values(placeholderValues?: Record): T['values'] {
- const params = fillPlaceholders(this.params, placeholderValues ?? {});
- this.logger.logQuery(this.queryString, params);
+ const params = fillPlaceholders(this.query.params, placeholderValues ?? {});
+ this.logger.logQuery(this.query.sql, params);
return this.stmt.raw().all(...params) as T['values'];
}
}
diff --git a/drizzle-orm/src/bun-sqlite/driver.ts b/drizzle-orm/src/bun-sqlite/driver.ts
index 36d500748..0d196ff03 100644
--- a/drizzle-orm/src/bun-sqlite/driver.ts
+++ b/drizzle-orm/src/bun-sqlite/driver.ts
@@ -10,7 +10,7 @@ import {
} from '~/relations.ts';
import { BaseSQLiteDatabase } from '~/sqlite-core/db.ts';
import { SQLiteSyncDialect } from '~/sqlite-core/dialect.ts';
-import { type DrizzleConfig } from '~/utils.ts';
+import type { DrizzleConfig } from '~/utils.ts';
import { SQLiteBunSession } from './session.ts';
export type BunSQLiteDatabase<
diff --git a/drizzle-orm/src/bun-sqlite/session.ts b/drizzle-orm/src/bun-sqlite/session.ts
index 816b1ab06..612350a47 100644
--- a/drizzle-orm/src/bun-sqlite/session.ts
+++ b/drizzle-orm/src/bun-sqlite/session.ts
@@ -4,8 +4,8 @@ import type { Database, Statement as BunStatement } from 'bun:sqlite';
import { entityKind } from '~/entity.ts';
import type { Logger } from '~/logger.ts';
import { NoopLogger } from '~/logger.ts';
-import { type RelationalSchemaConfig, type TablesRelationalConfig } from '~/relations.ts';
-import { fillPlaceholders, type Query, sql } from '~/sql/index.ts';
+import type { RelationalSchemaConfig, TablesRelationalConfig } from '~/relations.ts';
+import { fillPlaceholders, type Query, sql } from '~/sql/sql.ts';
import type { SQLiteSyncDialect } from '~/sqlite-core/dialect.ts';
import { SQLiteTransaction } from '~/sqlite-core/index.ts';
import type { SelectedFieldsOrdered } from '~/sqlite-core/query-builders/select.types.ts';
@@ -14,7 +14,7 @@ import type {
SQLiteExecuteMethod,
SQLiteTransactionConfig,
} from '~/sqlite-core/session.ts';
-import { PreparedQuery as PreparedQueryBase, SQLiteSession } from '~/sqlite-core/session.ts';
+import { SQLitePreparedQuery as PreparedQueryBase, SQLiteSession } from '~/sqlite-core/session.ts';
import { mapResultRow } from '~/utils.ts';
export interface SQLiteBunSessionOptions {
@@ -53,7 +53,7 @@ export class SQLiteBunSession<
customResultMapper?: (rows: unknown[][]) => unknown,
): PreparedQuery {
const stmt = this.client.prepare(query.sql);
- return new PreparedQuery(stmt, query.sql, query.params, this.logger, fields, executeMethod, customResultMapper);
+ return new PreparedQuery(stmt, query, this.logger, fields, executeMethod, customResultMapper);
}
override transaction(
@@ -98,27 +98,26 @@ export class PreparedQuery
constructor(
private stmt: Statement,
- private queryString: string,
- private params: unknown[],
+ query: Query,
private logger: Logger,
private fields: SelectedFieldsOrdered | undefined,
executeMethod: SQLiteExecuteMethod,
private customResultMapper?: (rows: unknown[][]) => unknown,
) {
- super('sync', executeMethod);
+ super('sync', executeMethod, query);
}
run(placeholderValues?: Record): void {
- const params = fillPlaceholders(this.params, placeholderValues ?? {});
- this.logger.logQuery(this.queryString, params);
+ const params = fillPlaceholders(this.query.params, placeholderValues ?? {});
+ this.logger.logQuery(this.query.sql, params);
return this.stmt.run(...params);
}
all(placeholderValues?: Record): T['all'] {
- const { fields, queryString, logger, joinsNotNullableMap, stmt, customResultMapper } = this;
+ const { fields, query, logger, joinsNotNullableMap, stmt, customResultMapper } = this;
if (!fields && !customResultMapper) {
- const params = fillPlaceholders(this.params, placeholderValues ?? {});
- logger.logQuery(queryString, params);
+ const params = fillPlaceholders(query.params, placeholderValues ?? {});
+ logger.logQuery(query.sql, params);
return stmt.all(...params);
}
@@ -132,8 +131,8 @@ export class PreparedQuery
}
get(placeholderValues?: Record): T['get'] {
- const params = fillPlaceholders(this.params, placeholderValues ?? {});
- this.logger.logQuery(this.queryString, params);
+ const params = fillPlaceholders(this.query.params, placeholderValues ?? {});
+ this.logger.logQuery(this.query.sql, params);
const row = this.stmt.get(...params);
if (!row) {
@@ -153,8 +152,8 @@ export class PreparedQuery
}
values(placeholderValues?: Record): T['values'] {
- const params = fillPlaceholders(this.params, placeholderValues ?? {});
- this.logger.logQuery(this.queryString, params);
+ const params = fillPlaceholders(this.query.params, placeholderValues ?? {});
+ this.logger.logQuery(this.query.sql, params);
return this.stmt.values(...params);
}
}
diff --git a/drizzle-orm/src/column-builder.ts b/drizzle-orm/src/column-builder.ts
index 5adce6e73..7ef9b6d14 100644
--- a/drizzle-orm/src/column-builder.ts
+++ b/drizzle-orm/src/column-builder.ts
@@ -2,7 +2,7 @@ import { entityKind } from '~/entity.ts';
import type { Column } from './column.ts';
import type { MySqlColumn } from './mysql-core/index.ts';
import type { PgColumn } from './pg-core/index.ts';
-import type { SQL } from './sql/index.ts';
+import type { SQL } from './sql/sql.ts';
import type { SQLiteColumn } from './sqlite-core/index.ts';
import type { Simplify } from './utils.ts';
diff --git a/drizzle-orm/src/column.ts b/drizzle-orm/src/column.ts
index 836d32700..deacc073a 100644
--- a/drizzle-orm/src/column.ts
+++ b/drizzle-orm/src/column.ts
@@ -1,6 +1,6 @@
import type { ColumnBuilderBaseConfig, ColumnBuilderRuntimeConfig, ColumnDataType } from './column-builder.ts';
import { entityKind } from './entity.ts';
-import type { DriverValueMapper, SQL, SQLWrapper } from './sql/index.ts';
+import type { DriverValueMapper, SQL, SQLWrapper } from './sql/sql.ts';
import type { Table } from './table.ts';
import type { Update } from './utils.ts';
@@ -38,7 +38,9 @@ export interface Column<
TRuntimeConfig extends object = object,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
TTypeConfig extends object = object,
-> extends DriverValueMapper, SQLWrapper {}
+> extends DriverValueMapper, SQLWrapper {
+ // SQLWrapper runtime implementation is defined in 'sql/sql.ts'
+}
/*
`Column` only accepts a full `ColumnConfig` as its generic.
To infer parts of the config, use `AnyColumn` that accepts a partial config.
diff --git a/drizzle-orm/src/d1/driver.ts b/drizzle-orm/src/d1/driver.ts
index e4f32109e..0cbd0a0cc 100644
--- a/drizzle-orm/src/d1/driver.ts
+++ b/drizzle-orm/src/d1/driver.ts
@@ -1,20 +1,33 @@
///
-
+import type { BatchItem, BatchResponse } from '~/batch.ts';
+import { entityKind } from '~/entity.ts';
import { DefaultLogger } from '~/logger.ts';
import {
createTableRelationsHelpers,
extractTablesRelationalConfig,
+ type ExtractTablesWithRelations,
type RelationalSchemaConfig,
type TablesRelationalConfig,
} from '~/relations.ts';
import { BaseSQLiteDatabase } from '~/sqlite-core/db.ts';
import { SQLiteAsyncDialect } from '~/sqlite-core/dialect.ts';
-import { type DrizzleConfig } from '~/utils.ts';
+import type { DrizzleConfig } from '~/utils.ts';
import { SQLiteD1Session } from './session.ts';
-export type DrizzleD1Database<
+export class DrizzleD1Database<
TSchema extends Record = Record,
-> = BaseSQLiteDatabase<'async', D1Result, TSchema>;
+> extends BaseSQLiteDatabase<'async', D1Result, TSchema> {
+ static readonly [entityKind]: string = 'LibSQLDatabase';
+
+ /** @internal */
+ declare readonly session: SQLiteD1Session>;
+
+ async batch, T extends Readonly<[U, ...U[]]>>(
+ batch: T,
+ ): Promise> {
+ return this.session.batch(batch) as Promise>;
+ }
+}
export function drizzle = Record>(
client: D1Database,
@@ -42,5 +55,5 @@ export function drizzle = Record;
+ return new DrizzleD1Database('async', dialect, session, schema) as DrizzleD1Database;
}
diff --git a/drizzle-orm/src/d1/session.ts b/drizzle-orm/src/d1/session.ts
index d45731764..da2040ec7 100644
--- a/drizzle-orm/src/d1/session.ts
+++ b/drizzle-orm/src/d1/session.ts
@@ -1,20 +1,21 @@
///
+import type { BatchItem } from '~/batch.ts';
import { entityKind } from '~/entity.ts';
import type { Logger } from '~/logger.ts';
import { NoopLogger } from '~/logger.ts';
-import { type RelationalSchemaConfig, type TablesRelationalConfig } from '~/relations.ts';
-import { type Query, sql } from '~/sql/index.ts';
-import { fillPlaceholders } from '~/sql/index.ts';
+import type { RelationalSchemaConfig, TablesRelationalConfig } from '~/relations.ts';
+import type { PreparedQuery } from '~/session.ts';
+import { type Query, sql, fillPlaceholders } from '~/sql/sql.ts';
import type { SQLiteAsyncDialect } from '~/sqlite-core/dialect.ts';
import { SQLiteTransaction } from '~/sqlite-core/index.ts';
import type { SelectedFieldsOrdered } from '~/sqlite-core/query-builders/select.types.ts';
-import {
- type PreparedQueryConfig as PreparedQueryConfigBase,
- type SQLiteExecuteMethod,
- type SQLiteTransactionConfig,
+import type {
+ PreparedQueryConfig as PreparedQueryConfigBase,
+ SQLiteExecuteMethod,
+ SQLiteTransactionConfig,
} from '~/sqlite-core/session.ts';
-import { PreparedQuery as PreparedQueryBase, SQLiteSession } from '~/sqlite-core/session.ts';
+import { SQLitePreparedQuery, SQLiteSession } from '~/sqlite-core/session.ts';
import { mapResultRow } from '~/utils.ts';
export interface SQLiteD1SessionOptions {
@@ -46,9 +47,123 @@ export class SQLiteD1Session<
fields: SelectedFieldsOrdered | undefined,
executeMethod: SQLiteExecuteMethod,
customResultMapper?: (rows: unknown[][]) => unknown,
- ): PreparedQuery {
+ ): D1PreparedQuery {
const stmt = this.client.prepare(query.sql);
- return new PreparedQuery(stmt, query.sql, query.params, this.logger, fields, executeMethod, customResultMapper);
+ return new D1PreparedQuery(stmt, query, this.logger, fields, executeMethod, customResultMapper);
+ }
+
+ /*override */ async batch>(queries: T) {
+ const preparedQueries: PreparedQuery[] = [];
+ const builtQueries: D1PreparedStatement[] = [];
+
+ for (const query of queries) {
+ const preparedQuery = query.prepare();
+ const builtQuery = preparedQuery.getQuery();
+ preparedQueries.push(preparedQuery);
+ if (builtQuery.params.length > 0) {
+ builtQueries.push((preparedQuery as D1PreparedQuery).stmt.bind(...builtQuery.params));
+ } else {
+ const builtQuery = preparedQuery.getQuery();
+ builtQueries.push(
+ this.client.prepare(builtQuery.sql).bind(...builtQuery.params),
+ );
+ }
+ }
+
+ // const queryToType: (
+ // | { mode: 'all' }
+ // | {
+ // mode: 'all_mapped';
+ // config: { fields: SelectedFieldsOrdered; joinsNotNullableMap?: Record };
+ // }
+ // | { mode: 'get' }
+ // | { mode: 'values' }
+ // | { mode: 'raw' }
+ // | { mode: 'rqb'; mapper: any }
+ // )[] = [];
+
+ // const builtQueries: D1PreparedStatement[] = queries.map((query) => {
+ // if (is(query, SQLiteSelectBase)) {
+ // const prepared = query.prepare() as D1PreparedQuery;
+ // prepared.fields === undefined
+ // ? queryToType.push({ mode: 'all' })
+ // : queryToType.push({
+ // mode: 'all_mapped',
+ // config: { fields: prepared.fields, joinsNotNullableMap: prepared.joinsNotNullableMap },
+ // });
+ // return prepared.stmt.bind(...prepared.params);
+ // } else if (
+ // is(query, SQLiteInsertBase) || is(query, SQLiteUpdateBase)
+ // || is(query, SQLiteDeleteBase)
+ // ) {
+ // const prepared = query.prepare() as D1PreparedQuery;
+ // queryToType.push(
+ // query.config.returning
+ // ? {
+ // mode: 'all_mapped',
+ // config: { fields: query.config.returning },
+ // }
+ // : { mode: 'raw' },
+ // );
+ // return prepared.stmt.bind(...prepared.params);
+ // } else if (is(query, SQLiteRaw)) {
+ // const builtQuery = this.dialect.sqlToQuery(query.getSQL());
+ // queryToType.push(
+ // query.config.action === 'run' ? { mode: 'raw' } : { mode: query.config.action },
+ // );
+ // return this.client.prepare(builtQuery.sql).bind(...builtQuery.params);
+ // } else if (is(query, SQLiteRelationalQuery)) {
+ // const preparedRqb = query.prepare() as D1PreparedQuery;
+ // queryToType.push({ mode: 'rqb', mapper: preparedRqb.customResultMapper });
+ // return preparedRqb.stmt.bind(...preparedRqb.params);
+ // }
+ // throw new DrizzleError({ message: 'You can use only drizzle queries in D1 batch API' });
+ // });
+
+ const batchResults = await this.client.batch(builtQueries);
+ return batchResults.map((result, i) => preparedQueries[i]!.mapResult(result, true));
+
+ // const res = this.client.batch(builtQueries).then((stmt) =>
+ // stmt.map(({ results }, index) => {
+ // const action = queryToType[index]!;
+ // if (action.mode === 'all') {
+ // return results;
+ // }
+ // if (action.mode === 'all_mapped') {
+ // const mappedRows = this.d1ToRawMapping(results);
+ // return mappedRows!.map((row) => {
+ // return mapResultRow(
+ // action.config.fields,
+ // row,
+ // action.config.joinsNotNullableMap,
+ // );
+ // });
+ // }
+ // if (action.mode === 'get') {
+ // return results![0] as unknown[];
+ // }
+ // if (action.mode === 'values') {
+ // return this.d1ToRawMapping(results);
+ // }
+ // if (action.mode === 'raw') {
+ // return stmt[index];
+ // }
+ // return action.mapper(this.d1ToRawMapping(results));
+ // })
+ // );
+ // return res;
+ }
+
+ override extractRawAllValueFromBatchResult(_result: unknown): unknown {
+ return (_result as D1Result).results;
+ }
+
+ override extractRawGetValueFromBatchResult(result: unknown): unknown {
+ return (result as D1Result).results[0];
+ }
+
+ override extractRawValuesValueFromBatchResult(result: unknown): unknown {
+ return d1ToRawMapping((result as D1Result).results);
}
override async transaction(
@@ -89,51 +204,89 @@ export class D1Transaction<
}
}
-export class PreparedQuery extends PreparedQueryBase<
+/**
+ * This function was taken from the D1 implementation: https://github.com/cloudflare/workerd/blob/4aae9f4c7ae30a59a88ca868c4aff88bda85c956/src/cloudflare/internal/d1-api.ts#L287
+ * It may cause issues with duplicated column names in join queries, which should be fixed on the D1 side.
+ * @param results
+ * @returns
+ */
+function d1ToRawMapping(results: any) {
+ const rows: unknown[][] = [];
+ for (const row of results) {
+ const entry = Object.keys(row).map((k) => row[k]);
+ rows.push(entry);
+ }
+ return rows;
+}
+
+export class D1PreparedQuery extends SQLitePreparedQuery<
{ type: 'async'; run: D1Result; all: T['all']; get: T['get']; values: T['values']; execute: T['execute'] }
> {
static readonly [entityKind]: string = 'D1PreparedQuery';
+ /** @internal */
+ customResultMapper?: (rows: unknown[][], mapColumnValue?: (value: unknown) => unknown) => unknown;
+
+ /** @internal */
+ fields?: SelectedFieldsOrdered;
+
+ /** @internal */
+ stmt: D1PreparedStatement;
+
constructor(
- private stmt: D1PreparedStatement,
- private queryString: string,
- private params: unknown[],
+ stmt: D1PreparedStatement,
+ query: Query,
private logger: Logger,
- private fields: SelectedFieldsOrdered | undefined,
+ fields: SelectedFieldsOrdered | undefined,
executeMethod: SQLiteExecuteMethod,
- private customResultMapper?: (rows: unknown[][]) => unknown,
+ customResultMapper?: (rows: unknown[][]) => unknown,
) {
- super('async', executeMethod);
+ super('async', executeMethod, query);
+ this.customResultMapper = customResultMapper;
+ this.fields = fields;
+ this.stmt = stmt;
}
run(placeholderValues?: Record): Promise {
- const params = fillPlaceholders(this.params, placeholderValues ?? {});
- this.logger.logQuery(this.queryString, params);
+ const params = fillPlaceholders(this.query.params, placeholderValues ?? {});
+ this.logger.logQuery(this.query.sql, params);
return this.stmt.bind(...params).run();
}
async all(placeholderValues?: Record): Promise {
- const { fields, joinsNotNullableMap, queryString, logger, stmt, customResultMapper } = this;
+ const { fields, query, logger, stmt, customResultMapper } = this;
if (!fields && !customResultMapper) {
- const params = fillPlaceholders(this.params, placeholderValues ?? {});
- logger.logQuery(queryString, params);
- return stmt.bind(...params).all().then(({ results }) => results!);
+ const params = fillPlaceholders(query.params, placeholderValues ?? {});
+ logger.logQuery(query.sql, params);
+ return stmt.bind(...params).all().then(({ results }) => this.mapAllResult(results!));
}
const rows = await this.values(placeholderValues);
- if (customResultMapper) {
- return customResultMapper(rows) as T['all'];
+ return this.mapAllResult(rows);
+ }
+
+ override mapAllResult(rows: unknown, isFromBatch?: boolean): unknown {
+ if (isFromBatch) {
+ rows = d1ToRawMapping((rows as D1Result).results);
+ }
+
+ if (!this.fields && !this.customResultMapper) {
+ return rows;
+ }
+
+ if (this.customResultMapper) {
+ return this.customResultMapper(rows as unknown[][]);
}
- return rows.map((row) => mapResultRow(fields!, row, joinsNotNullableMap));
+ return (rows as unknown[][]).map((row) => mapResultRow(this.fields!, row, this.joinsNotNullableMap));
}
async get(placeholderValues?: Record): Promise {
- const { fields, joinsNotNullableMap, queryString, logger, stmt, customResultMapper } = this;
+ const { fields, joinsNotNullableMap, query, logger, stmt, customResultMapper } = this;
if (!fields && !customResultMapper) {
- const params = fillPlaceholders(this.params, placeholderValues ?? {});
- logger.logQuery(queryString, params);
+ const params = fillPlaceholders(query.params, placeholderValues ?? {});
+ logger.logQuery(query.sql, params);
return stmt.bind(...params).all().then(({ results }) => results![0]);
}
@@ -150,9 +303,25 @@ export class PreparedQuery
return mapResultRow(fields!, rows[0], joinsNotNullableMap);
}
+ override mapGetResult(result: unknown, isFromBatch?: boolean): unknown {
+ if (isFromBatch) {
+ result = d1ToRawMapping((result as D1Result).results)[0];
+ }
+
+ if (!this.fields && !this.customResultMapper) {
+ return result;
+ }
+
+ if (this.customResultMapper) {
+ return this.customResultMapper([result as unknown[]]) as T['all'];
+ }
+
+ return mapResultRow(this.fields!, result as unknown[], this.joinsNotNullableMap);
+ }
+
values(placeholderValues?: Record): Promise {
- const params = fillPlaceholders(this.params, placeholderValues ?? {});
- this.logger.logQuery(this.queryString, params);
+ const params = fillPlaceholders(this.query.params, placeholderValues ?? {});
+ this.logger.logQuery(this.query.sql, params);
return this.stmt.bind(...params).raw();
}
}
diff --git a/drizzle-orm/src/errors.ts b/drizzle-orm/src/errors.ts
index 151264c68..ede6e0a59 100644
--- a/drizzle-orm/src/errors.ts
+++ b/drizzle-orm/src/errors.ts
@@ -3,15 +3,10 @@ import { entityKind } from '~/entity.ts';
export class DrizzleError extends Error {
static readonly [entityKind]: string = 'DrizzleError';
- constructor(message: string) {
+ constructor({ message, cause }: { message?: string; cause?: unknown }) {
super(message);
this.name = 'DrizzleError';
- }
-
- static wrap(error: unknown, message?: string): DrizzleError {
- return error instanceof Error // eslint-disable-line no-instanceof/no-instanceof
- ? new DrizzleError(message ? `${message}: ${error.message}` : error.message)
- : new DrizzleError(message ?? String(error));
+ this.cause = cause;
}
}
@@ -19,6 +14,6 @@ export class TransactionRollbackError extends DrizzleError {
static readonly [entityKind]: string = 'TransactionRollbackError';
constructor() {
- super('Rollback');
+ super({ message: 'Rollback' });
}
}
diff --git a/drizzle-orm/src/index.ts b/drizzle-orm/src/index.ts
index 2eb9a58ca..bc72260b9 100644
--- a/drizzle-orm/src/index.ts
+++ b/drizzle-orm/src/index.ts
@@ -12,4 +12,4 @@ export * from './sql/index.ts';
export * from './subquery.ts';
export * from './table.ts';
export * from './utils.ts';
-export * from './view.ts';
+export * from './view-common.ts';
diff --git a/drizzle-orm/src/libsql/driver.ts b/drizzle-orm/src/libsql/driver.ts
index 2d47a52a8..3acff2893 100644
--- a/drizzle-orm/src/libsql/driver.ts
+++ b/drizzle-orm/src/libsql/driver.ts
@@ -1,19 +1,33 @@
import type { Client, ResultSet } from '@libsql/client';
+import type { BatchItem, BatchResponse } from '~/batch.ts';
+import { entityKind } from '~/entity.ts';
import { DefaultLogger } from '~/logger.ts';
import {
createTableRelationsHelpers,
extractTablesRelationalConfig,
+ type ExtractTablesWithRelations,
type RelationalSchemaConfig,
type TablesRelationalConfig,
} from '~/relations.ts';
import { BaseSQLiteDatabase } from '~/sqlite-core/db.ts';
import { SQLiteAsyncDialect } from '~/sqlite-core/dialect.ts';
-import { type DrizzleConfig } from '~/utils.ts';
+import type { DrizzleConfig } from '~/utils.ts';
import { LibSQLSession } from './session.ts';
-export type LibSQLDatabase<
+export class LibSQLDatabase<
TSchema extends Record = Record,
-> = BaseSQLiteDatabase<'async', ResultSet, TSchema>;
+> extends BaseSQLiteDatabase<'async', ResultSet, TSchema> {
+ static readonly [entityKind]: string = 'LibSQLDatabase';
+
+ /** @internal */
+ declare readonly session: LibSQLSession>;
+
+ async batch, T extends Readonly<[U, ...U[]]>>(
+ batch: T,
+ ): Promise> {
+ return this.session.batch(batch) as Promise>;
+ }
+}
export function drizzle<
TSchema extends Record = Record,
@@ -40,5 +54,5 @@ export function drizzle<
}
const session = new LibSQLSession(client, dialect, schema, { logger }, undefined);
- return new BaseSQLiteDatabase('async', dialect, session, schema) as LibSQLDatabase;
+ return new LibSQLDatabase('async', dialect, session, schema) as LibSQLDatabase;
}
diff --git a/drizzle-orm/src/libsql/session.ts b/drizzle-orm/src/libsql/session.ts
index 210cd6429..76155f041 100644
--- a/drizzle-orm/src/libsql/session.ts
+++ b/drizzle-orm/src/libsql/session.ts
@@ -1,18 +1,20 @@
import type { Client, InArgs, InStatement, ResultSet, Transaction } from '@libsql/client';
+import type { BatchItem as BatchItem } from '~/batch.ts';
import { entityKind } from '~/entity.ts';
import type { Logger } from '~/logger.ts';
import { NoopLogger } from '~/logger.ts';
-import { type RelationalSchemaConfig, type TablesRelationalConfig } from '~/relations.ts';
-import { fillPlaceholders, type Query, type SQL, sql } from '~/sql/index.ts';
+import type { RelationalSchemaConfig, TablesRelationalConfig } from '~/relations.ts';
+import type { PreparedQuery } from '~/session.ts';
+import { fillPlaceholders, type Query, sql } from '~/sql/sql.ts';
import type { SQLiteAsyncDialect } from '~/sqlite-core/dialect.ts';
import { SQLiteTransaction } from '~/sqlite-core/index.ts';
import type { SelectedFieldsOrdered } from '~/sqlite-core/query-builders/select.types.ts';
-import {
- type PreparedQueryConfig as PreparedQueryConfigBase,
- type SQLiteExecuteMethod,
- type SQLiteTransactionConfig,
+import type {
+ PreparedQueryConfig as PreparedQueryConfigBase,
+ SQLiteExecuteMethod,
+ SQLiteTransactionConfig,
} from '~/sqlite-core/session.ts';
-import { PreparedQuery as PreparedQueryBase, SQLiteSession } from '~/sqlite-core/session.ts';
+import { SQLitePreparedQuery, SQLiteSession } from '~/sqlite-core/session.ts';
import { mapResultRow } from '~/utils.ts';
export interface LibSQLSessionOptions {
@@ -45,11 +47,10 @@ export class LibSQLSession<
fields: SelectedFieldsOrdered | undefined,
executeMethod: SQLiteExecuteMethod,
customResultMapper?: (rows: unknown[][]) => unknown,
- ): PreparedQuery {
- return new PreparedQuery(
+ ): LibSQLPreparedQuery {
+ return new LibSQLPreparedQuery(
this.client,
- query.sql,
- query.params,
+ query,
this.logger,
fields,
this.tx,
@@ -58,12 +59,19 @@ export class LibSQLSession<
);
}
- /*override */ batch(queries: SQL[]): Promise {
- const builtQueries: InStatement[] = queries.map((query) => {
- const builtQuery = this.dialect.sqlToQuery(query);
- return { sql: builtQuery.sql, args: builtQuery.params as InArgs };
- });
- return this.client.batch(builtQueries);
+ async batch[] | readonly BatchItem<'sqlite'>[]>(queries: T) {
+ const preparedQueries: PreparedQuery[] = [];
+ const builtQueries: InStatement[] = [];
+
+ for (const query of queries) {
+ const preparedQuery = query.prepare();
+ const builtQuery = preparedQuery.getQuery();
+ preparedQueries.push(preparedQuery);
+ builtQueries.push({ sql: builtQuery.sql, args: builtQuery.params as InArgs });
+ }
+
+ const batchResults = await this.client.batch(builtQueries);
+ return batchResults.map((result, i) => preparedQueries[i]!.mapResult(result, true));
}
override async transaction(
@@ -83,6 +91,18 @@ export class LibSQLSession<
throw err;
}
}
+
+ override extractRawAllValueFromBatchResult(result: unknown): unknown {
+ return (result as ResultSet).rows;
+ }
+
+ override extractRawGetValueFromBatchResult(result: unknown): unknown {
+ return (result as ResultSet).rows[0];
+ }
+
+ override extractRawValuesValueFromBatchResult(result: unknown): unknown {
+ return (result as ResultSet).rows;
+ }
}
export class LibSQLTransaction<
@@ -106,85 +126,115 @@ export class LibSQLTransaction<
}
}
-export class PreparedQuery extends PreparedQueryBase<
+export class LibSQLPreparedQuery extends SQLitePreparedQuery<
{ type: 'async'; run: ResultSet; all: T['all']; get: T['get']; values: T['values']; execute: T['execute'] }
> {
static readonly [entityKind]: string = 'LibSQLPreparedQuery';
constructor(
private client: Client,
- private queryString: string,
- private params: unknown[],
+ query: Query,
private logger: Logger,
- private fields: SelectedFieldsOrdered | undefined,
+ /** @internal */ public fields: SelectedFieldsOrdered | undefined,
private tx: Transaction | undefined,
executeMethod: SQLiteExecuteMethod,
- private customResultMapper?: (rows: unknown[][], mapColumnValue?: (value: unknown) => unknown) => unknown,
+ /** @internal */ public customResultMapper?: (
+ rows: unknown[][],
+ mapColumnValue?: (value: unknown) => unknown,
+ ) => unknown,
) {
- super('async', executeMethod);
+ super('async', executeMethod, query);
+ this.customResultMapper = customResultMapper;
+ this.fields = fields;
}
run(placeholderValues?: Record): Promise {
- const params = fillPlaceholders(this.params, placeholderValues ?? {});
- this.logger.logQuery(this.queryString, params);
- const stmt: InStatement = { sql: this.queryString, args: params as InArgs };
+ const params = fillPlaceholders(this.query.params, placeholderValues ?? {});
+ this.logger.logQuery(this.query.sql, params);
+ const stmt: InStatement = { sql: this.query.sql, args: params as InArgs };
return this.tx ? this.tx.execute(stmt) : this.client.execute(stmt);
}
async all(placeholderValues?: Record): Promise {
- const { fields, joinsNotNullableMap, logger, queryString, tx, client, customResultMapper } = this;
+ const { fields, logger, query, tx, client, customResultMapper } = this;
if (!fields && !customResultMapper) {
- const params = fillPlaceholders(this.params, placeholderValues ?? {});
- logger.logQuery(queryString, params);
- const stmt: InStatement = { sql: queryString, args: params as InArgs };
- return (tx ? tx.execute(stmt) : client.execute(stmt)).then(({ rows }) => rows.map((row) => normalizeRow(row)));
+ const params = fillPlaceholders(query.params, placeholderValues ?? {});
+ logger.logQuery(query.sql, params);
+ const stmt: InStatement = { sql: query.sql, args: params as InArgs };
+ return (tx ? tx.execute(stmt) : client.execute(stmt)).then(({ rows }) => this.mapAllResult(rows));
}
const rows = await this.values(placeholderValues) as unknown[][];
- if (customResultMapper) {
- return customResultMapper(rows, normalizeFieldValue) as T['all'];
+ return this.mapAllResult(rows);
+ }
+
+ override mapAllResult(rows: unknown, isFromBatch?: boolean): unknown {
+ if (isFromBatch) {
+ rows = (rows as ResultSet).rows;
+ }
+
+ if (!this.fields && !this.customResultMapper) {
+ return (rows as unknown[]).map((row) => normalizeRow(row));
}
- return rows.map((row) => {
+ if (this.customResultMapper) {
+ return this.customResultMapper(rows as unknown[][], normalizeFieldValue) as T['all'];
+ }
+
+ return (rows as unknown[]).map((row) => {
return mapResultRow(
- fields!,
+ this.fields!,
Array.prototype.slice.call(row).map((v) => normalizeFieldValue(v)),
- joinsNotNullableMap,
+ this.joinsNotNullableMap,
);
});
}
async get(placeholderValues?: Record): Promise {
- const { fields, joinsNotNullableMap, logger, queryString, tx, client, customResultMapper } = this;
+ const { fields, logger, query, tx, client, customResultMapper } = this;
if (!fields && !customResultMapper) {
- const params = fillPlaceholders(this.params, placeholderValues ?? {});
- logger.logQuery(queryString, params);
- const stmt: InStatement = { sql: queryString, args: params as InArgs };
- return (tx ? tx.execute(stmt) : client.execute(stmt)).then(({ rows }) => normalizeRow(rows[0]));
+ const params = fillPlaceholders(query.params, placeholderValues ?? {});
+ logger.logQuery(query.sql, params);
+ const stmt: InStatement = { sql: query.sql, args: params as InArgs };
+ return (tx ? tx.execute(stmt) : client.execute(stmt)).then(({ rows }) => this.mapGetResult(rows));
}
const rows = await this.values(placeholderValues) as unknown[][];
- if (!rows[0]) {
+ return this.mapGetResult(rows);
+ }
+
+ override mapGetResult(rows: unknown, isFromBatch?: boolean): unknown {
+ if (isFromBatch) {
+ rows = (rows as ResultSet).rows;
+ }
+
+ const row = (rows as unknown[])[0];
+
+ if (!this.fields && !this.customResultMapper) {
+ return normalizeRow(row);
+ }
+
+ if (!row) {
return undefined;
}
- if (customResultMapper) {
- return customResultMapper(rows, normalizeFieldValue) as T['get'];
+ if (this.customResultMapper) {
+ return this.customResultMapper(rows as unknown[][], normalizeFieldValue) as T['get'];
}
return mapResultRow(
- fields!,
- Array.prototype.slice.call(rows[0]).map((v) => normalizeFieldValue(v)),
- joinsNotNullableMap,
+ this.fields!,
+ Array.prototype.slice.call(row).map((v) => normalizeFieldValue(v)),
+ this.joinsNotNullableMap,
);
}
values(placeholderValues?: Record): Promise {
- const params = fillPlaceholders(this.params, placeholderValues ?? {});
- this.logger.logQuery(this.queryString, params);
- const stmt: InStatement = { sql: this.queryString, args: params as InArgs };
+ const params = fillPlaceholders(this.query.params, placeholderValues ?? {});
+ this.logger.logQuery(this.query.sql, params);
+ const stmt: InStatement = { sql: this.query.sql, args: params as InArgs };
return (this.tx ? this.tx.execute(stmt) : this.client.execute(stmt)).then(({ rows }) => rows) as Promise<
T['values']
>;
@@ -205,7 +255,7 @@ function normalizeRow(obj: any) {
}
function normalizeFieldValue(value: unknown) {
- if (value instanceof ArrayBuffer) { // eslint-disable-line no-instanceof/no-instanceof
+ if (typeof ArrayBuffer !== 'undefined' && value instanceof ArrayBuffer) { // eslint-disable-line no-instanceof/no-instanceof
if (typeof Buffer !== 'undefined') {
if (!(value instanceof Buffer)) { // eslint-disable-line no-instanceof/no-instanceof
return Buffer.from(value);
diff --git a/drizzle-orm/src/mysql-core/alias.ts b/drizzle-orm/src/mysql-core/alias.ts
index 08d70f035..8320c5533 100644
--- a/drizzle-orm/src/mysql-core/alias.ts
+++ b/drizzle-orm/src/mysql-core/alias.ts
@@ -1,7 +1,7 @@
import { TableAliasProxyHandler } from '~/alias.ts';
import type { BuildAliasTable } from './query-builders/select.types.ts';
import type { MySqlTable } from './table.ts';
-import { type MySqlViewBase } from './view.ts';
+import type { MySqlViewBase } from './view-base.ts';
export function alias(
table: TTable,
diff --git a/drizzle-orm/src/mysql-core/checks.ts b/drizzle-orm/src/mysql-core/checks.ts
index fa76b4537..af9a29f6a 100644
--- a/drizzle-orm/src/mysql-core/checks.ts
+++ b/drizzle-orm/src/mysql-core/checks.ts
@@ -1,5 +1,5 @@
import { entityKind } from '~/entity.ts';
-import type { SQL } from '~/sql/index.ts';
+import type { SQL } from '~/sql/sql.ts';
import type { MySqlTable } from './table.ts';
export class CheckBuilder {
diff --git a/drizzle-orm/src/mysql-core/columns/bigint.ts b/drizzle-orm/src/mysql-core/columns/bigint.ts
index d87195868..c80770d22 100644
--- a/drizzle-orm/src/mysql-core/columns/bigint.ts
+++ b/drizzle-orm/src/mysql-core/columns/bigint.ts
@@ -14,12 +14,13 @@ export type MySqlBigInt53BuilderInitial = MySqlBigInt53Bui
}>;
export class MySqlBigInt53Builder>
- extends MySqlColumnBuilderWithAutoIncrement
+ extends MySqlColumnBuilderWithAutoIncrement
{
static readonly [entityKind]: string = 'MySqlBigInt53Builder';
- constructor(name: T['name']) {
+ constructor(name: T['name'], unsigned: boolean = false) {
super(name, 'number', 'MySqlBigInt53');
+ this.config.unsigned = unsigned;
}
/** @internal */
@@ -34,12 +35,12 @@ export class MySqlBigInt53Builder>
- extends MySqlColumnWithAutoIncrement
+ extends MySqlColumnWithAutoIncrement
{
static readonly [entityKind]: string = 'MySqlBigInt53';
getSQLType(): string {
- return 'bigint';
+ return `bigint${this.config.unsigned ? ' unsigned' : ''}`;
}
override mapFromDriverValue(value: number | string): number {
@@ -60,12 +61,13 @@ export type MySqlBigInt64BuilderInitial = MySqlBigInt64Bui
}>;
export class MySqlBigInt64Builder>
- extends MySqlColumnBuilderWithAutoIncrement
+ extends MySqlColumnBuilderWithAutoIncrement
{
static readonly [entityKind]: string = 'MySqlBigInt64Builder';
- constructor(name: T['name']) {
+ constructor(name: T['name'], unsigned: boolean = false) {
super(name, 'bigint', 'MySqlBigInt64');
+ this.config.unsigned = unsigned;
}
/** @internal */
@@ -80,12 +82,12 @@ export class MySqlBigInt64Builder>
- extends MySqlColumnWithAutoIncrement
+ extends MySqlColumnWithAutoIncrement
{
static readonly [entityKind]: string = 'MySqlBigInt64';
getSQLType(): string {
- return 'bigint';
+ return `bigint${this.config.unsigned ? ' unsigned' : ''}`;
}
// eslint-disable-next-line unicorn/prefer-native-coercion-functions
@@ -96,6 +98,7 @@ export class MySqlBigInt64
interface MySqlBigIntConfig {
mode: T;
+ unsigned?: boolean;
}
export function bigint(
@@ -104,7 +107,7 @@ export function bigint : MySqlBigInt64BuilderInitial;
export function bigint(name: string, config: MySqlBigIntConfig) {
if (config.mode === 'number') {
- return new MySqlBigInt53Builder(name);
+ return new MySqlBigInt53Builder(name, config.unsigned);
}
- return new MySqlBigInt64Builder(name);
+ return new MySqlBigInt64Builder(name, config.unsigned);
}
diff --git a/drizzle-orm/src/mysql-core/columns/char.ts b/drizzle-orm/src/mysql-core/columns/char.ts
index 78b845e71..5466ec046 100644
--- a/drizzle-orm/src/mysql-core/columns/char.ts
+++ b/drizzle-orm/src/mysql-core/columns/char.ts
@@ -2,7 +2,7 @@ import type { ColumnBuilderBaseConfig, ColumnBuilderRuntimeConfig, MakeColumnCon
import type { ColumnBaseConfig } from '~/column.ts';
import { entityKind } from '~/entity.ts';
import type { AnyMySqlTable } from '~/mysql-core/table.ts';
-import { type Writable } from '~/utils.ts';
+import type { Writable } from '~/utils.ts';
import { MySqlColumn, MySqlColumnBuilder } from './common.ts';
export type MySqlCharBuilderInitial = MySqlCharBuilder<{
diff --git a/drizzle-orm/src/mysql-core/columns/common.ts b/drizzle-orm/src/mysql-core/columns/common.ts
index 2f1ee2fce..fe518cebb 100644
--- a/drizzle-orm/src/mysql-core/columns/common.ts
+++ b/drizzle-orm/src/mysql-core/columns/common.ts
@@ -14,7 +14,7 @@ import { entityKind } from '~/entity.ts';
import type { ForeignKey, UpdateDeleteAction } from '~/mysql-core/foreign-keys.ts';
import { ForeignKeyBuilder } from '~/mysql-core/foreign-keys.ts';
import type { AnyMySqlTable, MySqlTable } from '~/mysql-core/table.ts';
-import { type Update } from '~/utils.ts';
+import type { Update } from '~/utils.ts';
import { uniqueKeyName } from '../unique-constraint.ts';
export interface ReferenceConfig {
diff --git a/drizzle-orm/src/mysql-core/columns/custom.ts b/drizzle-orm/src/mysql-core/columns/custom.ts
index c67904e38..135bc8c09 100644
--- a/drizzle-orm/src/mysql-core/columns/custom.ts
+++ b/drizzle-orm/src/mysql-core/columns/custom.ts
@@ -2,8 +2,8 @@ import type { ColumnBuilderBaseConfig, ColumnBuilderRuntimeConfig, MakeColumnCon
import type { ColumnBaseConfig } from '~/column.ts';
import { entityKind } from '~/entity.ts';
import type { AnyMySqlTable } from '~/mysql-core/table.ts';
-import type { SQL } from '~/sql/index.ts';
-import { type Equal } from '~/utils.ts';
+import type { SQL } from '~/sql/sql.ts';
+import type { Equal } from '~/utils.ts';
import { MySqlColumn, MySqlColumnBuilder } from './common.ts';
export type ConvertCustomConfig> =
diff --git a/drizzle-orm/src/mysql-core/columns/date.common.ts b/drizzle-orm/src/mysql-core/columns/date.common.ts
index 9800a3d90..3fd8aa612 100644
--- a/drizzle-orm/src/mysql-core/columns/date.common.ts
+++ b/drizzle-orm/src/mysql-core/columns/date.common.ts
@@ -6,7 +6,7 @@ import type {
} from '~/column-builder.ts';
import type { ColumnBaseConfig } from '~/column.ts';
import { entityKind } from '~/entity.ts';
-import { sql } from '~/sql/index.ts';
+import { sql } from '~/sql/sql.ts';
import { MySqlColumn, MySqlColumnBuilder } from './common.ts';
export interface MySqlDateColumnBaseConfig {
diff --git a/drizzle-orm/src/mysql-core/columns/date.ts b/drizzle-orm/src/mysql-core/columns/date.ts
index 396e23d60..c51971829 100644
--- a/drizzle-orm/src/mysql-core/columns/date.ts
+++ b/drizzle-orm/src/mysql-core/columns/date.ts
@@ -2,7 +2,7 @@ import type { ColumnBuilderBaseConfig, ColumnBuilderRuntimeConfig, MakeColumnCon
import type { ColumnBaseConfig } from '~/column.ts';
import { entityKind } from '~/entity.ts';
import type { AnyMySqlTable } from '~/mysql-core/table.ts';
-import { type Equal } from '~/utils.ts';
+import type { Equal } from '~/utils.ts';
import { MySqlColumn, MySqlColumnBuilder } from './common.ts';
export type MySqlDateBuilderInitial = MySqlDateBuilder<{
diff --git a/drizzle-orm/src/mysql-core/columns/datetime.ts b/drizzle-orm/src/mysql-core/columns/datetime.ts
index 801801969..cfe9ce0b7 100644
--- a/drizzle-orm/src/mysql-core/columns/datetime.ts
+++ b/drizzle-orm/src/mysql-core/columns/datetime.ts
@@ -2,7 +2,7 @@ import type { ColumnBuilderBaseConfig, ColumnBuilderRuntimeConfig, MakeColumnCon
import type { ColumnBaseConfig } from '~/column.ts';
import { entityKind } from '~/entity.ts';
import type { AnyMySqlTable } from '~/mysql-core/table.ts';
-import { type Equal } from '~/utils.ts';
+import type { Equal } from '~/utils.ts';
import { MySqlColumn, MySqlColumnBuilder } from './common.ts';
export type MySqlDateTimeBuilderInitial = MySqlDateTimeBuilder<{
@@ -53,8 +53,12 @@ export class MySqlDateTime>
return `datetime${precision}`;
}
+ override mapToDriverValue(value: Date): unknown {
+ return value.toISOString().replace('T', ' ').replace('Z', '');
+ }
+
override mapFromDriverValue(value: string): Date {
- return new Date(value);
+ return new Date(value.replace(' ', 'T') + 'Z');
}
}
diff --git a/drizzle-orm/src/mysql-core/columns/enum.ts b/drizzle-orm/src/mysql-core/columns/enum.ts
index 48221f868..a7d5399ed 100644
--- a/drizzle-orm/src/mysql-core/columns/enum.ts
+++ b/drizzle-orm/src/mysql-core/columns/enum.ts
@@ -2,7 +2,7 @@ import type { ColumnBuilderBaseConfig, ColumnBuilderRuntimeConfig, MakeColumnCon
import type { ColumnBaseConfig } from '~/column.ts';
import { entityKind } from '~/entity.ts';
import type { AnyMySqlTable } from '~/mysql-core/table.ts';
-import { type Writable } from '~/utils.ts';
+import type { Writable } from '~/utils.ts';
import { MySqlColumn, MySqlColumnBuilder } from './common.ts';
export type MySqlEnumColumnBuilderInitial =
diff --git a/drizzle-orm/src/mysql-core/columns/int.ts b/drizzle-orm/src/mysql-core/columns/int.ts
index a3bdf5377..4fa1bb936 100644
--- a/drizzle-orm/src/mysql-core/columns/int.ts
+++ b/drizzle-orm/src/mysql-core/columns/int.ts
@@ -14,12 +14,13 @@ export type MySqlIntBuilderInitial = MySqlIntBuilder<{
}>;
export class MySqlIntBuilder>
- extends MySqlColumnBuilderWithAutoIncrement
+ extends MySqlColumnBuilderWithAutoIncrement
{
static readonly [entityKind]: string = 'MySqlIntBuilder';
- constructor(name: T['name']) {
+ constructor(name: T['name'], config?: MySqlIntConfig) {
super(name, 'number', 'MySqlInt');
+ this.config.unsigned = config ? config.unsigned : false;
}
/** @internal */
@@ -30,11 +31,13 @@ export class MySqlIntBuilder> extends MySqlColumnWithAutoIncrement {
+export class MySqlInt>
+ extends MySqlColumnWithAutoIncrement
+{
static readonly [entityKind]: string = 'MySqlInt';
getSQLType(): string {
- return 'int';
+ return `int${this.config.unsigned ? ' unsigned' : ''}`;
}
override mapFromDriverValue(value: number | string): number {
@@ -45,6 +48,10 @@ export class MySqlInt> extends
}
}
-export function int(name: TName): MySqlIntBuilderInitial {
- return new MySqlIntBuilder(name);
+export interface MySqlIntConfig {
+ unsigned?: boolean;
+}
+
+export function int(name: TName, config?: MySqlIntConfig): MySqlIntBuilderInitial {
+ return new MySqlIntBuilder(name, config);
}
diff --git a/drizzle-orm/src/mysql-core/columns/mediumint.ts b/drizzle-orm/src/mysql-core/columns/mediumint.ts
index e17e2f368..9a9277fe0 100644
--- a/drizzle-orm/src/mysql-core/columns/mediumint.ts
+++ b/drizzle-orm/src/mysql-core/columns/mediumint.ts
@@ -3,6 +3,7 @@ import type { ColumnBaseConfig } from '~/column.ts';
import { entityKind } from '~/entity.ts';
import type { AnyMySqlTable } from '~/mysql-core/table.ts';
import { MySqlColumnBuilderWithAutoIncrement, MySqlColumnWithAutoIncrement } from './common.ts';
+import type { MySqlIntConfig } from './int.ts';
export type MySqlMediumIntBuilderInitial = MySqlMediumIntBuilder<{
name: TName;
@@ -14,12 +15,13 @@ export type MySqlMediumIntBuilderInitial = MySqlMediumIntB
}>;
export class MySqlMediumIntBuilder>
- extends MySqlColumnBuilderWithAutoIncrement
+ extends MySqlColumnBuilderWithAutoIncrement
{
static readonly [entityKind]: string = 'MySqlMediumIntBuilder';
- constructor(name: T['name']) {
+ constructor(name: T['name'], config?: MySqlIntConfig) {
super(name, 'number', 'MySqlMediumInt');
+ this.config.unsigned = config ? config.unsigned : false;
}
/** @internal */
@@ -34,12 +36,12 @@ export class MySqlMediumIntBuilder>
- extends MySqlColumnWithAutoIncrement
+ extends MySqlColumnWithAutoIncrement
{
static readonly [entityKind]: string = 'MySqlMediumInt';
getSQLType(): string {
- return 'mediumint';
+ return `mediumint${this.config.unsigned ? ' unsigned' : ''}`;
}
override mapFromDriverValue(value: number | string): number {
@@ -50,6 +52,9 @@ export class MySqlMediumInt(name: TName): MySqlMediumIntBuilderInitial {
- return new MySqlMediumIntBuilder(name);
+export function mediumint(
+ name: TName,
+ config?: MySqlIntConfig,
+): MySqlMediumIntBuilderInitial {
+ return new MySqlMediumIntBuilder(name, config);
}
diff --git a/drizzle-orm/src/mysql-core/columns/smallint.ts b/drizzle-orm/src/mysql-core/columns/smallint.ts
index e1723dbd0..e4653f5dd 100644
--- a/drizzle-orm/src/mysql-core/columns/smallint.ts
+++ b/drizzle-orm/src/mysql-core/columns/smallint.ts
@@ -3,6 +3,7 @@ import type { ColumnBaseConfig } from '~/column.ts';
import { entityKind } from '~/entity.ts';
import type { AnyMySqlTable } from '~/mysql-core/table.ts';
import { MySqlColumnBuilderWithAutoIncrement, MySqlColumnWithAutoIncrement } from './common.ts';
+import type { MySqlIntConfig } from './int.ts';
export type MySqlSmallIntBuilderInitial = MySqlSmallIntBuilder<{
name: TName;
@@ -14,12 +15,13 @@ export type MySqlSmallIntBuilderInitial = MySqlSmallIntBui
}>;
export class MySqlSmallIntBuilder>
- extends MySqlColumnBuilderWithAutoIncrement
+ extends MySqlColumnBuilderWithAutoIncrement
{
static readonly [entityKind]: string = 'MySqlSmallIntBuilder';
- constructor(name: T['name']) {
+ constructor(name: T['name'], config?: MySqlIntConfig) {
super(name, 'number', 'MySqlSmallInt');
+ this.config.unsigned = config ? config.unsigned : false;
}
/** @internal */
@@ -34,12 +36,12 @@ export class MySqlSmallIntBuilder>
- extends MySqlColumnWithAutoIncrement
+ extends MySqlColumnWithAutoIncrement
{
static readonly [entityKind]: string = 'MySqlSmallInt';
getSQLType(): string {
- return 'smallint';
+ return `smallint${this.config.unsigned ? ' unsigned' : ''}`;
}
override mapFromDriverValue(value: number | string): number {
@@ -50,6 +52,9 @@ export class MySqlSmallInt
}
}
-export function smallint(name: TName): MySqlSmallIntBuilderInitial {
- return new MySqlSmallIntBuilder(name);
+export function smallint(
+ name: TName,
+ config?: MySqlIntConfig,
+): MySqlSmallIntBuilderInitial {
+ return new MySqlSmallIntBuilder(name, config);
}
diff --git a/drizzle-orm/src/mysql-core/columns/text.ts b/drizzle-orm/src/mysql-core/columns/text.ts
index 9853532d4..8a4a30822 100644
--- a/drizzle-orm/src/mysql-core/columns/text.ts
+++ b/drizzle-orm/src/mysql-core/columns/text.ts
@@ -2,7 +2,7 @@ import type { ColumnBuilderBaseConfig, ColumnBuilderRuntimeConfig, MakeColumnCon
import type { ColumnBaseConfig } from '~/column.ts';
import { entityKind } from '~/entity.ts';
import type { AnyMySqlTable } from '~/mysql-core/table.ts';
-import { type Writable } from '~/utils.ts';
+import type { Writable } from '~/utils.ts';
import { MySqlColumn, MySqlColumnBuilder } from './common.ts';
export type MySqlTextColumnType = 'tinytext' | 'text' | 'mediumtext' | 'longtext';
diff --git a/drizzle-orm/src/mysql-core/columns/timestamp.ts b/drizzle-orm/src/mysql-core/columns/timestamp.ts
index aed1ad04a..3b6df80d3 100644
--- a/drizzle-orm/src/mysql-core/columns/timestamp.ts
+++ b/drizzle-orm/src/mysql-core/columns/timestamp.ts
@@ -2,7 +2,7 @@ import type { ColumnBuilderBaseConfig, ColumnBuilderRuntimeConfig, MakeColumnCon
import type { ColumnBaseConfig } from '~/column.ts';
import { entityKind } from '~/entity.ts';
import type { AnyMySqlTable } from '~/mysql-core/table.ts';
-import { type Equal } from '~/utils.ts';
+import type { Equal } from '~/utils.ts';
import { MySqlDateBaseColumn, MySqlDateColumnBaseBuilder } from './date.common.ts';
export type MySqlTimestampBuilderInitial = MySqlTimestampBuilder<{
diff --git a/drizzle-orm/src/mysql-core/columns/tinyint.ts b/drizzle-orm/src/mysql-core/columns/tinyint.ts
index 1fdef4e61..35a68cbd2 100644
--- a/drizzle-orm/src/mysql-core/columns/tinyint.ts
+++ b/drizzle-orm/src/mysql-core/columns/tinyint.ts
@@ -3,6 +3,7 @@ import type { ColumnBaseConfig } from '~/column.ts';
import { entityKind } from '~/entity.ts';
import type { AnyMySqlTable } from '~/mysql-core/table.ts';
import { MySqlColumnBuilderWithAutoIncrement, MySqlColumnWithAutoIncrement } from './common.ts';
+import type { MySqlIntConfig } from './int.ts';
export type MySqlTinyIntBuilderInitial = MySqlTinyIntBuilder<{
name: TName;
@@ -14,12 +15,13 @@ export type MySqlTinyIntBuilderInitial = MySqlTinyIntBuild
}>;
export class MySqlTinyIntBuilder>
- extends MySqlColumnBuilderWithAutoIncrement
+ extends MySqlColumnBuilderWithAutoIncrement
{
static readonly [entityKind]: string = 'MySqlTinyIntBuilder';
- constructor(name: T['name']) {
+ constructor(name: T['name'], config?: MySqlIntConfig) {
super(name, 'number', 'MySqlTinyInt');
+ this.config.unsigned = config ? config.unsigned : false;
}
/** @internal */
@@ -34,12 +36,12 @@ export class MySqlTinyIntBuilder>
- extends MySqlColumnWithAutoIncrement
+ extends MySqlColumnWithAutoIncrement
{
static readonly [entityKind]: string = 'MySqlTinyInt';
getSQLType(): string {
- return 'tinyint';
+ return `tinyint${this.config.unsigned ? ' unsigned' : ''}`;
}
override mapFromDriverValue(value: number | string): number {
@@ -50,6 +52,6 @@ export class MySqlTinyInt>
}
}
-export function tinyint(name: TName): MySqlTinyIntBuilderInitial {
- return new MySqlTinyIntBuilder(name);
+export function tinyint(name: TName, config?: MySqlIntConfig): MySqlTinyIntBuilderInitial {
+ return new MySqlTinyIntBuilder(name, config);
}
diff --git a/drizzle-orm/src/mysql-core/columns/varchar.ts b/drizzle-orm/src/mysql-core/columns/varchar.ts
index 99c52db77..7db55563f 100644
--- a/drizzle-orm/src/mysql-core/columns/varchar.ts
+++ b/drizzle-orm/src/mysql-core/columns/varchar.ts
@@ -2,7 +2,7 @@ import type { ColumnBuilderBaseConfig, ColumnBuilderRuntimeConfig, MakeColumnCon
import type { ColumnBaseConfig } from '~/column.ts';
import { entityKind } from '~/entity.ts';
import type { AnyMySqlTable } from '~/mysql-core/table.ts';
-import { type Writable } from '~/utils.ts';
+import type { Writable } from '~/utils.ts';
import { MySqlColumn, MySqlColumnBuilder } from './common.ts';
export type MySqlVarCharBuilderInitial = MySqlVarCharBuilder<
diff --git a/drizzle-orm/src/mysql-core/db.ts b/drizzle-orm/src/mysql-core/db.ts
index 3c79f9d86..e3a07cee3 100644
--- a/drizzle-orm/src/mysql-core/db.ts
+++ b/drizzle-orm/src/mysql-core/db.ts
@@ -1,18 +1,12 @@
import type { ResultSetHeader } from 'mysql2/promise';
import { entityKind } from '~/entity.ts';
import type { TypedQueryBuilder } from '~/query-builders/query-builder.ts';
-import {
- type ExtractTablesWithRelations,
- type RelationalSchemaConfig,
- type TablesRelationalConfig,
-} from '~/relations.ts';
-import type { SQLWrapper } from '~/sql/index.ts';
-import { SelectionProxyHandler, WithSubquery } from '~/subquery.ts';
-import { type DrizzleTypeError } from '~/utils.ts';
-import { type ColumnsSelection } from '~/view.ts';
+import type { ExtractTablesWithRelations, RelationalSchemaConfig, TablesRelationalConfig } from '~/relations.ts';
+import type { ColumnsSelection, SQLWrapper } from '~/sql/sql.ts';
+import type { DrizzleTypeError } from '~/utils.ts';
import type { MySqlDialect } from './dialect.ts';
import {
- MySqlDelete,
+ MySqlDeleteBase,
MySqlInsertBuilder,
MySqlSelectBuilder,
MySqlUpdateBuilder,
@@ -31,6 +25,8 @@ import type {
} from './session.ts';
import type { WithSubqueryWithSelection } from './subquery.ts';
import type { MySqlTable } from './table.ts';
+import { WithSubquery } from '~/subquery.ts';
+import { SelectionProxyHandler } from '~/selection-proxy.ts';
export class MySqlDatabase<
TQueryResult extends QueryResultHKT,
@@ -84,7 +80,7 @@ export class MySqlDatabase<
return {
as(
qb: TypedQueryBuilder | ((qb: QueryBuilder) => TypedQueryBuilder),
- ): WithSubqueryWithSelection {
+ ): WithSubqueryWithSelection {
if (typeof qb === 'function') {
qb = qb(new QueryBuilder());
}
@@ -92,7 +88,7 @@ export class MySqlDatabase<
return new Proxy(
new WithSubquery(qb.getSQL(), qb.getSelectedFields() as SelectedFields, alias, true),
new SelectionProxyHandler({ alias, sqlAliasedBehavior: 'alias', sqlBehavior: 'error' }),
- ) as WithSubqueryWithSelection;
+ ) as WithSubqueryWithSelection;
},
};
}
@@ -159,8 +155,8 @@ export class MySqlDatabase<
return new MySqlInsertBuilder(table, this.session, this.dialect);
}
- delete(table: TTable): MySqlDelete {
- return new MySqlDelete(table, this.session, this.dialect);
+ delete(table: TTable): MySqlDeleteBase {
+ return new MySqlDeleteBase(table, this.session, this.dialect);
}
execute(
@@ -179,3 +175,55 @@ export class MySqlDatabase<
return this.session.transaction(transaction, config);
}
}
+
+export type MySQLWithReplicas = Q & { $primary: Q };
+
+export const withReplicas = <
+ HKT extends QueryResultHKT,
+ TPreparedQueryHKT extends PreparedQueryHKTBase,
+ TFullSchema extends Record,
+ TSchema extends TablesRelationalConfig,
+ Q extends MySqlDatabase<
+ HKT,
+ TPreparedQueryHKT,
+ TFullSchema,
+ TSchema extends Record ? ExtractTablesWithRelations : TSchema
+ >,
+>(
+ primary: Q,
+ replicas: [Q, ...Q[]],
+ getReplica: (replicas: Q[]) => Q = () => replicas[Math.floor(Math.random() * replicas.length)]!,
+): MySQLWithReplicas => {
+ const select: Q['select'] = (...args: any) => getReplica(replicas).select(args);
+ const selectDistinct: Q['selectDistinct'] = (...args: any) => getReplica(replicas).selectDistinct(args);
+ const $with: Q['with'] = (...args: any) => getReplica(replicas).with(args);
+
+ const update: Q['update'] = (...args: any) => primary.update(args);
+ const insert: Q['insert'] = (...args: any) => primary.insert(args);
+ const $delete: Q['delete'] = (...args: any) => primary.delete(args);
+ const execute: Q['execute'] = (...args: any) => primary.execute(args);
+ const transaction: Q['transaction'] = (...args: any) => primary.transaction(args);
+
+ return new Proxy(
+ {
+ ...primary,
+ update,
+ insert,
+ delete: $delete,
+ execute,
+ transaction,
+ $primary: primary,
+ select,
+ selectDistinct,
+ with: $with,
+ },
+ {
+ get(target, prop, _receiver) {
+ if (prop === 'query') {
+ return getReplica(replicas).query;
+ }
+ return target[prop as keyof typeof target];
+ },
+ },
+ );
+};
diff --git a/drizzle-orm/src/mysql-core/dialect.ts b/drizzle-orm/src/mysql-core/dialect.ts
index 46dbd2d5e..34d5bf907 100644
--- a/drizzle-orm/src/mysql-core/dialect.ts
+++ b/drizzle-orm/src/mysql-core/dialect.ts
@@ -14,28 +14,19 @@ import {
type TableRelationalConfig,
type TablesRelationalConfig,
} from '~/relations.ts';
-import { and, eq, Param, type Query, SQL, sql, type SQLChunk } from '~/sql/index.ts';
+import { Param, type QueryWithTypings, SQL, sql, type SQLChunk, View } from '~/sql/sql.ts';
import { Subquery, SubqueryConfig } from '~/subquery.ts';
import { getTableName, Table } from '~/table.ts';
import { orderSelectedFields, type UpdateSet } from '~/utils.ts';
-import { View, ViewBaseConfig } from '~/view.ts';
-import { DrizzleError } from '../index.ts';
+import { DrizzleError, type Name, ViewBaseConfig, and, eq } from '../index.ts';
import { MySqlColumn } from './columns/common.ts';
import type { MySqlDeleteConfig } from './query-builders/delete.ts';
import type { MySqlInsertConfig } from './query-builders/insert.ts';
-import type { Join, MySqlSelectConfig, SelectedFieldsOrdered } from './query-builders/select.types.ts';
+import type { MySqlSelectConfig, MySqlSelectJoinConfig, SelectedFieldsOrdered } from './query-builders/select.types.ts';
import type { MySqlUpdateConfig } from './query-builders/update.ts';
import type { MySqlSession } from './session.ts';
import { MySqlTable } from './table.ts';
-import { MySqlViewBase } from './view.ts';
-
-// TODO find out how to use all/values. Seems like I need those functions
-// Build project
-// copy runtime tests to be sure it's working
-
-// Add mysql to drizzle-kit
-
-// Add Planetscale Driver and create example repo
+import { MySqlViewBase } from './view-base.ts';
export class MySqlDialect {
static readonly [entityKind]: string = 'MySqlDialect';
@@ -204,6 +195,7 @@ export class MySqlDialect {
offset,
lockingClause,
distinct,
+ setOperators,
}: MySqlSelectConfig,
): SQL {
const fieldsList = fieldsFlat ?? orderSelectedFields(fields);
@@ -331,7 +323,75 @@ export class MySqlDialect {
}
}
- return sql`${withSql}select${distinctSql} ${selection} from ${tableSql}${joinsSql}${whereSql}${groupBySql}${havingSql}${orderBySql}${limitSql}${offsetSql}${lockingClausesSql}`;
+ const finalQuery =
+ sql`${withSql}select${distinctSql} ${selection} from ${tableSql}${joinsSql}${whereSql}${groupBySql}${havingSql}${orderBySql}${limitSql}${offsetSql}${lockingClausesSql}`;
+
+ if (setOperators.length > 0) {
+ return this.buildSetOperations(finalQuery, setOperators);
+ }
+
+ return finalQuery;
+ }
+
+ buildSetOperations(leftSelect: SQL, setOperators: MySqlSelectConfig['setOperators']): SQL {
+ const [setOperator, ...rest] = setOperators;
+
+ if (!setOperator) {
+ throw new Error('Cannot pass undefined values to any set operator');
+ }
+
+ if (rest.length === 0) {
+ return this.buildSetOperationQuery({ leftSelect, setOperator });
+ }
+
+ // Some recursive magic here
+ return this.buildSetOperations(
+ this.buildSetOperationQuery({ leftSelect, setOperator }),
+ rest,
+ );
+ }
+
+ buildSetOperationQuery({
+ leftSelect,
+ setOperator: { type, isAll, rightSelect, limit, orderBy, offset },
+ }: { leftSelect: SQL; setOperator: MySqlSelectConfig['setOperators'][number] }): SQL {
+ const leftChunk = sql`(${leftSelect.getSQL()}) `;
+ const rightChunk = sql`(${rightSelect.getSQL()})`;
+
+ let orderBySql;
+ if (orderBy && orderBy.length > 0) {
+ const orderByValues: (SQL | Name)[] = [];
+
+ // The next bit is necessary because the sql operator replaces ${table.column} with `table`.`column`
+ // which is invalid MySql syntax, Table from one of the SELECTs cannot be used in global ORDER clause
+ for (const orderByUnit of orderBy) {
+ if (is(orderByUnit, MySqlColumn)) {
+ orderByValues.push(sql.identifier(orderByUnit.name));
+ } else if (is(orderByUnit, SQL)) {
+ for (let i = 0; i < orderByUnit.queryChunks.length; i++) {
+ const chunk = orderByUnit.queryChunks[i];
+
+ if (is(chunk, MySqlColumn)) {
+ orderByUnit.queryChunks[i] = sql.identifier(chunk.name);
+ }
+ }
+
+ orderByValues.push(sql`${orderByUnit}`);
+ } else {
+ orderByValues.push(sql`${orderByUnit}`);
+ }
+ }
+
+ orderBySql = sql` order by ${sql.join(orderByValues, sql`, `)} `;
+ }
+
+ const limitSql = limit ? sql` limit ${limit}` : undefined;
+
+ const operatorChunk = sql.raw(`${type} ${isAll ? 'all ' : ''}`);
+
+ const offsetSql = offset ? sql` offset ${offset}` : undefined;
+
+ return sql`${leftChunk}${operatorChunk}${rightChunk}${orderBySql}${limitSql}${offsetSql}`;
}
buildInsertQuery({ table, values, ignore, onConflict }: MySqlInsertConfig): SQL {
@@ -374,7 +434,7 @@ export class MySqlDialect {
return sql`insert${ignoreSql} into ${table} ${insertOrder} values ${valuesSql}${onConflictSql}`;
}
- sqlToQuery(sql: SQL): Query {
+ sqlToQuery(sql: SQL): QueryWithTypings {
return sql.toQuery({
escapeName: this.escapeName,
escapeParam: this.escapeParam,
@@ -405,7 +465,7 @@ export class MySqlDialect {
}): BuildRelationalQueryResult {
let selection: BuildRelationalQueryResult['selection'] = [];
let limit, offset, orderBy: MySqlSelectConfig['orderBy'], where;
- const joins: Join[] = [];
+ const joins: MySqlSelectJoinConfig[] = [];
if (config === true) {
const selectionEntries = Object.entries(tableConfig.columns);
@@ -578,7 +638,7 @@ export class MySqlDialect {
}
if (selection.length === 0) {
- throw new DrizzleError(`No fields selected for table "${tableConfig.tsName}" ("${tableAlias}")`);
+ throw new DrizzleError({ message: `No fields selected for table "${tableConfig.tsName}" ("${tableAlias}")` });
}
let result;
@@ -631,6 +691,7 @@ export class MySqlDialect {
where,
limit,
offset,
+ setOperators: [],
});
where = undefined;
@@ -653,6 +714,7 @@ export class MySqlDialect {
limit,
offset,
orderBy,
+ setOperators: [],
});
} else {
result = this.buildSelectQuery({
@@ -667,6 +729,7 @@ export class MySqlDialect {
limit,
offset,
orderBy,
+ setOperators: [],
});
}
@@ -869,9 +932,10 @@ export class MySqlDialect {
}
if (selection.length === 0) {
- throw new DrizzleError(
- `No fields selected for table "${tableConfig.tsName}" ("${tableAlias}"). You need to have at least one item in "columns", "with" or "extras". If you need to select all columns, omit the "columns" key or set it to undefined.`,
- );
+ throw new DrizzleError({
+ message:
+ `No fields selected for table "${tableConfig.tsName}" ("${tableAlias}"). You need to have at least one item in "columns", "with" or "extras". If you need to select all columns, omit the "columns" key or set it to undefined.`,
+ });
}
let result;
@@ -920,6 +984,7 @@ export class MySqlDialect {
where,
limit,
offset,
+ setOperators: [],
});
where = undefined;
@@ -941,6 +1006,7 @@ export class MySqlDialect {
limit,
offset,
orderBy,
+ setOperators: [],
});
} else {
result = this.buildSelectQuery({
@@ -954,6 +1020,7 @@ export class MySqlDialect {
limit,
offset,
orderBy,
+ setOperators: [],
});
}
diff --git a/drizzle-orm/src/mysql-core/expressions.ts b/drizzle-orm/src/mysql-core/expressions.ts
index 76db28551..a61f77786 100644
--- a/drizzle-orm/src/mysql-core/expressions.ts
+++ b/drizzle-orm/src/mysql-core/expressions.ts
@@ -1,6 +1,6 @@
import { bindIfParam } from '~/expressions.ts';
-import type { Placeholder, SQL, SQLChunk, SQLWrapper } from '~/sql/index.ts';
-import { sql } from '~/sql/index.ts';
+import type { Placeholder, SQL, SQLChunk, SQLWrapper } from '~/sql/sql.ts';
+import { sql } from '~/sql/sql.ts';
import type { MySqlColumn } from './columns/index.ts';
export * from '~/expressions.ts';
diff --git a/drizzle-orm/src/mysql-core/foreign-keys.ts b/drizzle-orm/src/mysql-core/foreign-keys.ts
index 8f9abdae8..957e1f15c 100644
--- a/drizzle-orm/src/mysql-core/foreign-keys.ts
+++ b/drizzle-orm/src/mysql-core/foreign-keys.ts
@@ -5,6 +5,7 @@ import { MySqlTable } from './table.ts';
export type UpdateDeleteAction = 'cascade' | 'restrict' | 'no action' | 'set null' | 'set default';
export type Reference = () => {
+ readonly name?: string;
readonly columns: MySqlColumn[];
readonly foreignTable: MySqlTable;
readonly foreignColumns: MySqlColumn[];
@@ -24,6 +25,7 @@ export class ForeignKeyBuilder {
constructor(
config: () => {
+ name?: string;
columns: MySqlColumn[];
foreignColumns: MySqlColumn[];
},
@@ -33,8 +35,8 @@ export class ForeignKeyBuilder {
} | undefined,
) {
this.reference = () => {
- const { columns, foreignColumns } = config();
- return { columns, foreignTable: foreignColumns[0]!.table as MySqlTable, foreignColumns };
+ const { name, columns, foreignColumns } = config();
+ return { name, columns, foreignTable: foreignColumns[0]!.table as MySqlTable, foreignColumns };
};
if (actions) {
this._onUpdate = actions.onUpdate;
@@ -74,7 +76,7 @@ export class ForeignKey {
}
getName(): string {
- const { columns, foreignColumns } = this.reference();
+ const { name, columns, foreignColumns } = this.reference();
const columnNames = columns.map((column) => column.name);
const foreignColumnNames = foreignColumns.map((column) => column.name);
const chunks = [
@@ -83,7 +85,7 @@ export class ForeignKey {
foreignColumns[0]!.table[MySqlTable.Symbol.Name],
...foreignColumnNames,
];
- return `${chunks.join('_')}_fk`;
+ return name ?? `${chunks.join('_')}_fk`;
}
}
@@ -105,13 +107,15 @@ export function foreignKey<
TColumns extends [AnyMySqlColumn<{ tableName: TTableName }>, ...AnyMySqlColumn<{ tableName: TTableName }>[]],
>(
config: {
+ name?: string;
columns: TColumns;
foreignColumns: ColumnsWithTable;
},
): ForeignKeyBuilder {
function mappedConfig() {
- const { columns, foreignColumns } = config;
+ const { name, columns, foreignColumns } = config;
return {
+ name,
columns,
foreignColumns,
};
diff --git a/drizzle-orm/src/mysql-core/index.ts b/drizzle-orm/src/mysql-core/index.ts
index 7120e0c5c..204e0af3c 100644
--- a/drizzle-orm/src/mysql-core/index.ts
+++ b/drizzle-orm/src/mysql-core/index.ts
@@ -13,4 +13,5 @@ export * from './subquery.ts';
export * from './table.ts';
export * from './unique-constraint.ts';
export * from './utils.ts';
+export * from './view-common.ts';
export * from './view.ts';
diff --git a/drizzle-orm/src/mysql-core/indexes.ts b/drizzle-orm/src/mysql-core/indexes.ts
index 8a6139e1e..5b73b1d30 100644
--- a/drizzle-orm/src/mysql-core/indexes.ts
+++ b/drizzle-orm/src/mysql-core/indexes.ts
@@ -1,5 +1,5 @@
import { entityKind } from '~/entity.ts';
-import type { SQL } from '~/sql/index.ts';
+import type { SQL } from '~/sql/sql.ts';
import type { AnyMySqlColumn, MySqlColumn } from './columns/index.ts';
import type { MySqlTable } from './table.ts';
diff --git a/drizzle-orm/src/mysql-core/primary-keys.ts b/drizzle-orm/src/mysql-core/primary-keys.ts
index 26bd3edca..014cbd8c0 100644
--- a/drizzle-orm/src/mysql-core/primary-keys.ts
+++ b/drizzle-orm/src/mysql-core/primary-keys.ts
@@ -4,9 +4,22 @@ import { MySqlTable } from './table.ts';
export function primaryKey<
TTableName extends string,
+ TColumn extends AnyMySqlColumn<{ tableName: TTableName }>,
TColumns extends AnyMySqlColumn<{ tableName: TTableName }>[],
->(...columns: TColumns): PrimaryKeyBuilder {
- return new PrimaryKeyBuilder(columns);
+>(config: { name?: string; columns: [TColumn, ...TColumns] }): PrimaryKeyBuilder;
+/**
+ * @deprecated: Please use primaryKey({ columns: [] }) instead of this function
+ * @param columns
+ */
+export function primaryKey<
+ TTableName extends string,
+ TColumns extends AnyMySqlColumn<{ tableName: TTableName }>[],
+>(...columns: TColumns): PrimaryKeyBuilder;
+export function primaryKey(...config: any) {
+ if (config[0].columns) {
+ return new PrimaryKeyBuilder(config[0].columns, config[0].name);
+ }
+ return new PrimaryKeyBuilder(config);
}
export class PrimaryKeyBuilder {
@@ -15,15 +28,20 @@ export class PrimaryKeyBuilder {
/** @internal */
columns: MySqlColumn[];
+ /** @internal */
+ name?: string;
+
constructor(
columns: MySqlColumn[],
+ name?: string,
) {
this.columns = columns;
+ this.name = name;
}
/** @internal */
build(table: MySqlTable): PrimaryKey {
- return new PrimaryKey(table, this.columns);
+ return new PrimaryKey(table, this.columns, this.name);
}
}
@@ -31,12 +49,15 @@ export class PrimaryKey {
static readonly [entityKind]: string = 'MySqlPrimaryKey';
readonly columns: MySqlColumn[];
+ readonly name?: string;
- constructor(readonly table: MySqlTable, columns: MySqlColumn[]) {
+ constructor(readonly table: MySqlTable, columns: MySqlColumn[], name?: string) {
this.columns = columns;
+ this.name = name;
}
getName(): string {
- return `${this.table[MySqlTable.Symbol.Name]}_${this.columns.map((column) => column.name).join('_')}_pk`;
+ return this.name
+ ?? `${this.table[MySqlTable.Symbol.Name]}_${this.columns.map((column) => column.name).join('_')}_pk`;
}
}
diff --git a/drizzle-orm/src/mysql-core/query-builders/delete.ts b/drizzle-orm/src/mysql-core/query-builders/delete.ts
index 7ec773f82..33588fd16 100644
--- a/drizzle-orm/src/mysql-core/query-builders/delete.ts
+++ b/drizzle-orm/src/mysql-core/query-builders/delete.ts
@@ -1,6 +1,7 @@
import { entityKind } from '~/entity.ts';
import type { MySqlDialect } from '~/mysql-core/dialect.ts';
import type {
+ AnyQueryResultHKT,
MySqlSession,
PreparedQueryConfig,
PreparedQueryHKTBase,
@@ -10,28 +11,78 @@ import type {
} from '~/mysql-core/session.ts';
import type { MySqlTable } from '~/mysql-core/table.ts';
import { QueryPromise } from '~/query-promise.ts';
-import type { Query, SQL, SQLWrapper } from '~/sql/index.ts';
+import type { Query, SQL, SQLWrapper } from '~/sql/sql.ts';
import type { SelectedFieldsOrdered } from './select.types.ts';
+export type MySqlDeleteWithout<
+ T extends AnyMySqlDeleteBase,
+ TDynamic extends boolean,
+ K extends keyof T & string,
+> = TDynamic extends true ? T
+ : Omit<
+ MySqlDeleteBase<
+ T['_']['table'],
+ T['_']['queryResult'],
+ T['_']['preparedQueryHKT'],
+ TDynamic,
+ T['_']['excludedMethods'] | K
+ >,
+ T['_']['excludedMethods'] | K
+ >;
+
+export type MySqlDelete<
+ TTable extends MySqlTable = MySqlTable,
+ TQueryResult extends QueryResultHKT = AnyQueryResultHKT,
+ TPreparedQueryHKT extends PreparedQueryHKTBase = PreparedQueryHKTBase,
+> = MySqlDeleteBase;
+
export interface MySqlDeleteConfig {
where?: SQL | undefined;
table: MySqlTable;
returning?: SelectedFieldsOrdered;
}
-// eslint-disable-next-line @typescript-eslint/no-empty-interface
-export interface MySqlDelete<
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
+export type MySqlDeletePrepare = PreparedQueryKind<
+ T['_']['preparedQueryHKT'],
+ PreparedQueryConfig & {
+ execute: QueryResultKind;
+ iterator: never;
+ },
+ true
+>;
+
+type MySqlDeleteDynamic = MySqlDelete<
+ T['_']['table'],
+ T['_']['queryResult'],
+ T['_']['preparedQueryHKT']
+>;
+
+type AnyMySqlDeleteBase = MySqlDeleteBase;
+
+export interface MySqlDeleteBase<
TTable extends MySqlTable,
TQueryResult extends QueryResultHKT,
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
TPreparedQueryHKT extends PreparedQueryHKTBase,
-> extends QueryPromise> {}
+ TDynamic extends boolean = false,
+ TExcludedMethods extends string = never,
+> extends QueryPromise> {
+ readonly _: {
+ readonly table: TTable;
+ readonly queryResult: TQueryResult;
+ readonly preparedQueryHKT: TPreparedQueryHKT;
+ readonly dynamic: TDynamic;
+ readonly excludedMethods: TExcludedMethods;
+ };
+}
-export class MySqlDelete<
+export class MySqlDeleteBase<
TTable extends MySqlTable,
TQueryResult extends QueryResultHKT,
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
TPreparedQueryHKT extends PreparedQueryHKTBase,
+ TDynamic extends boolean = false,
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ TExcludedMethods extends string = never,
> extends QueryPromise> implements SQLWrapper {
static readonly [entityKind]: string = 'MySqlDelete';
@@ -46,11 +97,9 @@ export class MySqlDelete<
this.config = { table };
}
- where(
- where: SQL | undefined,
- ): Omit {
+ where(where: SQL | undefined): MySqlDeleteWithout {
this.config.where = where;
- return this;
+ return this as any;
}
/** @internal */
@@ -58,23 +107,16 @@ export class MySqlDelete<
return this.dialect.buildDeleteQuery(this.config);
}
- toSQL(): { sql: Query['sql']; params: Query['params'] } {
+ toSQL(): Query {
const { typings: _typings, ...rest } = this.dialect.sqlToQuery(this.getSQL());
return rest;
}
- prepare() {
+ prepare(): MySqlDeletePrepare {
return this.session.prepareQuery(
this.dialect.sqlToQuery(this.getSQL()),
this.config.returning,
- ) as PreparedQueryKind<
- TPreparedQueryHKT,
- PreparedQueryConfig & {
- execute: QueryResultKind;
- iterator: never;
- },
- true
- >;
+ ) as MySqlDeletePrepare;
}
override execute: ReturnType['execute'] = (placeholderValues) => {
@@ -89,4 +131,8 @@ export class MySqlDelete<
};
iterator = this.createIterator();
+
+ $dynamic(): MySqlDeleteDynamic {
+ return this as any;
+ }
}
diff --git a/drizzle-orm/src/mysql-core/query-builders/insert.ts b/drizzle-orm/src/mysql-core/query-builders/insert.ts
index f34b46011..56bf18af4 100644
--- a/drizzle-orm/src/mysql-core/query-builders/insert.ts
+++ b/drizzle-orm/src/mysql-core/query-builders/insert.ts
@@ -1,6 +1,7 @@
import { entityKind, is } from '~/entity.ts';
import type { MySqlDialect } from '~/mysql-core/dialect.ts';
import type {
+ AnyQueryResultHKT,
MySqlSession,
PreparedQueryConfig,
PreparedQueryHKTBase,
@@ -10,8 +11,8 @@ import type {
} from '~/mysql-core/session.ts';
import type { MySqlTable } from '~/mysql-core/table.ts';
import { QueryPromise } from '~/query-promise.ts';
-import type { Placeholder, Query, SQLWrapper } from '~/sql/index.ts';
-import { Param, SQL, sql } from '~/sql/index.ts';
+import type { Placeholder, Query, SQLWrapper } from '~/sql/sql.ts';
+import { Param, SQL, sql } from '~/sql/sql.ts';
import { Table } from '~/table.ts';
import { mapUpdateSet, type ValidateShape } from '~/utils.ts';
import type { MySqlUpdateSetSource } from './update.ts';
@@ -51,15 +52,15 @@ export class MySqlInsertBuilder<
return this;
}
- values(
+ values>(
value: ValidateShape>,
- ): MySqlInsert;
- values(
+ ): MySqlInsertBase;
+ values>(
values: ValidateShape>[],
- ): MySqlInsert;
- values(
+ ): MySqlInsertBase;
+ values>(
values: ValidateShape> | ValidateShape>[],
- ): MySqlInsert {
+ ): MySqlInsertBase {
values = Array.isArray(values) ? values : [values];
if (values.length === 0) {
throw new Error('values() must be called with at least one value');
@@ -74,21 +75,75 @@ export class MySqlInsertBuilder<
return result;
});
- return new MySqlInsert(this.table, mappedValues, this.shouldIgnore, this.session, this.dialect);
+ return new MySqlInsertBase(this.table, mappedValues, this.shouldIgnore, this.session, this.dialect);
}
}
-export interface MySqlInsert<
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
+export type MySqlInsertWithout =
+ TDynamic extends true ? T
+ : Omit<
+ MySqlInsertBase<
+ T['_']['table'],
+ T['_']['queryResult'],
+ T['_']['preparedQueryHKT'],
+ TDynamic,
+ T['_']['excludedMethods'] | K
+ >,
+ T['_']['excludedMethods'] | K
+ >;
+
+export type MySqlInsertDynamic = MySqlInsert<
+ T['_']['table'],
+ T['_']['queryResult'],
+ T['_']['preparedQueryHKT']
+>;
+
+export type MySqlInsertPrepare = PreparedQueryKind<
+ T['_']['preparedQueryHKT'],
+ PreparedQueryConfig & {
+ execute: QueryResultKind;
+ iterator: never;
+ },
+ true
+>;
+
+export type MySqlInsertOnDuplicateKeyUpdateConfig = {
+ set: MySqlUpdateSetSource;
+};
+
+export type MySqlInsert<
+ TTable extends MySqlTable = MySqlTable,
+ TQueryResult extends QueryResultHKT = AnyQueryResultHKT,
+ TPreparedQueryHKT extends PreparedQueryHKTBase = PreparedQueryHKTBase,
+> = MySqlInsertBase;
+
+export type AnyMySqlInsert = MySqlInsertBase;
+
+export interface MySqlInsertBase<
TTable extends MySqlTable,
TQueryResult extends QueryResultHKT,
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
TPreparedQueryHKT extends PreparedQueryHKTBase,
-> extends QueryPromise>, SQLWrapper {}
-export class MySqlInsert<
+ TDynamic extends boolean = false,
+ TExcludedMethods extends string = never,
+> extends QueryPromise>, SQLWrapper {
+ readonly _: {
+ readonly table: TTable;
+ readonly queryResult: TQueryResult;
+ readonly preparedQueryHKT: TPreparedQueryHKT;
+ readonly dynamic: TDynamic;
+ readonly excludedMethods: TExcludedMethods;
+ };
+}
+
+export class MySqlInsertBase<
TTable extends MySqlTable,
TQueryResult extends QueryResultHKT,
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
TPreparedQueryHKT extends PreparedQueryHKTBase,
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ TDynamic extends boolean = false,
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ TExcludedMethods extends string = never,
> extends QueryPromise> implements SQLWrapper {
static readonly [entityKind]: string = 'MySqlInsert';
@@ -107,13 +162,12 @@ export class MySqlInsert<
this.config = { table, values, ignore };
}
- onDuplicateKeyUpdate(config: {
- // target?: IndexColumn | IndexColumn[];
- set: MySqlUpdateSetSource;
- }): this {
+ onDuplicateKeyUpdate(
+ config: MySqlInsertOnDuplicateKeyUpdateConfig,
+ ): MySqlInsertWithout {
const setSql = this.dialect.buildUpdateSet(this.config.table, mapUpdateSet(this.config.table, config.set));
this.config.onConflict = sql`update ${setSql}`;
- return this;
+ return this as any;
}
/** @internal */
@@ -121,23 +175,16 @@ export class MySqlInsert<
return this.dialect.buildInsertQuery(this.config);
}
- toSQL(): { sql: Query['sql']; params: Query['params'] } {
+ toSQL(): Query {
const { typings: _typings, ...rest } = this.dialect.sqlToQuery(this.getSQL());
return rest;
}
- prepare() {
+ prepare(): MySqlInsertPrepare {
return this.session.prepareQuery(
this.dialect.sqlToQuery(this.getSQL()),
undefined,
- ) as PreparedQueryKind<
- TPreparedQueryHKT,
- PreparedQueryConfig & {
- execute: QueryResultKind;
- iterator: never;
- },
- true
- >;
+ ) as MySqlInsertPrepare;
}
override execute: ReturnType['execute'] = (placeholderValues) => {
@@ -152,4 +199,8 @@ export class MySqlInsert<
};
iterator = this.createIterator();
+
+ $dynamic(): MySqlInsertDynamic {
+ return this as any;
+ }
}
diff --git a/drizzle-orm/src/mysql-core/query-builders/query-builder.ts b/drizzle-orm/src/mysql-core/query-builders/query-builder.ts
index b91ce61f5..9cb5ca9e2 100644
--- a/drizzle-orm/src/mysql-core/query-builders/query-builder.ts
+++ b/drizzle-orm/src/mysql-core/query-builders/query-builder.ts
@@ -2,10 +2,11 @@ import { entityKind } from '~/entity.ts';
import { MySqlDialect } from '~/mysql-core/dialect.ts';
import type { WithSubqueryWithSelection } from '~/mysql-core/subquery.ts';
import type { TypedQueryBuilder } from '~/query-builders/query-builder.ts';
-import { SelectionProxyHandler, WithSubquery } from '~/subquery.ts';
-import { type ColumnsSelection } from '~/view.ts';
import { MySqlSelectBuilder } from './select.ts';
import type { SelectedFields } from './select.types.ts';
+import { WithSubquery } from '~/subquery.ts';
+import { SelectionProxyHandler } from '~/selection-proxy.ts';
+import type { ColumnsSelection } from '~/sql/sql.ts';
export class QueryBuilder {
static readonly [entityKind]: string = 'MySqlQueryBuilder';
@@ -18,7 +19,7 @@ export class QueryBuilder {
return {
as(
qb: TypedQueryBuilder | ((qb: QueryBuilder) => TypedQueryBuilder),
- ): WithSubqueryWithSelection {
+ ): WithSubqueryWithSelection {
if (typeof qb === 'function') {
qb = qb(queryBuilder);
}
@@ -26,7 +27,7 @@ export class QueryBuilder {
return new Proxy(
new WithSubquery(qb.getSQL(), qb.getSelectedFields() as SelectedFields, alias, true),
new SelectionProxyHandler({ alias, sqlAliasedBehavior: 'alias', sqlBehavior: 'error' }),
- ) as WithSubqueryWithSelection