Skip to content

Commit

Permalink
Merge pull request #2119 from drizzle-team/aws-data-api-fixes
Browse files Browse the repository at this point in the history
AWS Data API fixes
  • Loading branch information
dankochetov authored Apr 7, 2024
2 parents ab9feb7 + 9b8a94f commit a7c90b8
Show file tree
Hide file tree
Showing 35 changed files with 7,681 additions and 2,393 deletions.
30 changes: 22 additions & 8 deletions changelogs/drizzle-orm/0.30.5.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,25 @@
- 🎉 Added custom schema support to enums in Postgres:
## New Features

```ts
import { pgSchema } from 'drizzle-orm/pg-core';
### 🎉 `$onUpdate` functionality for PostgreSQL, MySQL and SQLite

const mySchema = pgSchema('mySchema');
const colors = mySchema.enum('colors', ['red', 'green', 'blue']);
```
Adds a dynamic update value to the column.
The function will be called when the row is updated, and the returned value will be used as the column value if none is provided.
If no `default` (or `$defaultFn`) value is provided, the function will be called when the row is inserted as well, and the returned value will be used as the column value.

- 🐛 Split `where` clause in Postgres `.onConflictDoUpdate` method into `setWhere` and `targetWhere` clauses, to support both `where` cases in `on conflict ...` clause (#1628, #1302)
- 🐛 Fix query generation for `where` clause in Postgres `.onConflictDoNothing` method, as it was placed in a wrong spot (#1628)
> Note: This value does not affect the `drizzle-kit` behavior, it is only used at runtime in `drizzle-orm`.
```ts
const usersOnUpdate = pgTable('users_on_update', {
id: serial('id').primaryKey(),
name: text('name').notNull(),
updateCounter: integer('update_counter').default(sql`1`).$onUpdateFn(() => sql`update_counter + 1`),
updatedAt: timestamp('updated_at', { mode: 'date', precision: 3 }).$onUpdate(() => new Date()),
alwaysNull: text('always_null').$type<string | null>().$onUpdate(() => null),
});
```

## Fixes

- [BUG]: insertions on columns with the smallserial datatype are not optional - #1848

Thanks @Angelelz and @gabrielDonnantuoni!
27 changes: 27 additions & 0 deletions changelogs/drizzle-orm/0.30.6.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
## New Features

### 🎉 PGlite driver Support

PGlite is a WASM Postgres build packaged into a TypeScript client library that enables you to run Postgres in the browser, Node.js and Bun, with no need to install any other dependencies. It is only 2.6mb gzipped.

It can be used as an ephemeral in-memory database, or with persistence either to the file system (Node/Bun) or indexedDB (Browser).

Unlike previous "Postgres in the browser" projects, PGlite does not use a Linux virtual machine - it is simply Postgres in WASM.

Usage Example
```ts
import { PGlite } from '@electric-sql/pglite';
import { drizzle } from 'drizzle-orm/pglite';

// In-memory Postgres
const client = new PGlite();
const db = drizzle(client);

await db.select().from(users);
```
---
There are currently 2 limitations, that should be fixed on Pglite side:

- [Attempting to refresh a materialised view throws error](https://github.com/electric-sql/pglite/issues/63)

- [Attempting to SET TIME ZONE throws error](https://github.com/electric-sql/pglite/issues/62)
4 changes: 4 additions & 0 deletions changelogs/drizzle-orm/0.30.7.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
## Bug fixes

- Add mappings for `@vercel/postgres` package
- Fix interval mapping for `neon` drivers - #1542
11 changes: 11 additions & 0 deletions changelogs/drizzle-orm/0.30.8.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
- 🎉 Added custom schema support to enums in Postgres:

```ts
import { pgSchema } from 'drizzle-orm/pg-core';

const mySchema = pgSchema('mySchema');
const colors = mySchema.enum('colors', ['red', 'green', 'blue']);
```

- 🐛 Split `where` clause in Postgres `.onConflictDoUpdate` method into `setWhere` and `targetWhere` clauses, to support both `where` cases in `on conflict ...` clause (#1628, #1302)
- 🐛 Fix query generation for `where` clause in Postgres `.onConflictDoNothing` method, as it was placed in a wrong spot (#1628)
3 changes: 2 additions & 1 deletion dprint.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@
"**/drizzle2/**/meta",
"**/*snapshot.json",
"**/_journal.json",
"**/tsup.config*.mjs"
"**/tsup.config*.mjs",
"**/.sst"
],
"plugins": [
"https://plugins.dprint.dev/typescript-0.83.0.wasm",
Expand Down
13 changes: 9 additions & 4 deletions drizzle-orm/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "drizzle-orm",
"version": "0.30.5",
"version": "0.30.7",
"description": "Drizzle ORM package for SQL databases",
"type": "module",
"scripts": {
Expand Down Expand Up @@ -45,6 +45,7 @@
"peerDependencies": {
"@aws-sdk/client-rds-data": ">=3",
"@cloudflare/workers-types": ">=3",
"@electric-sql/pglite": ">=0.1.1",
"@libsql/client": "*",
"@neondatabase/serverless": ">=0.1",
"@op-engineering/op-sqlite": ">=2",
Expand All @@ -54,7 +55,7 @@
"@types/pg": "*",
"@types/react": ">=18",
"@types/sql.js": "*",
"@vercel/postgres": "*",
"@vercel/postgres": ">=0.8.0",
"@xata.io/client": "*",
"better-sqlite3": ">=7",
"bun-types": "*",
Expand Down Expand Up @@ -140,11 +141,15 @@
},
"@types/react": {
"optional": true
},
"@electric-sql/pglite": {
"optional": true
}
},
"devDependencies": {
"@aws-sdk/client-rds-data": "^3.344.0",
"@aws-sdk/client-rds-data": "^3.549.0",
"@cloudflare/workers-types": "^4.20230904.0",
"@electric-sql/pglite": "^0.1.1",
"@libsql/client": "^0.5.6",
"@neondatabase/serverless": "^0.9.0",
"@op-engineering/op-sqlite": "^2.0.16",
Expand All @@ -156,7 +161,7 @@
"@types/pg": "^8.10.1",
"@types/react": "^18.2.45",
"@types/sql.js": "^1.4.4",
"@vercel/postgres": "^0.3.0",
"@vercel/postgres": "^0.8.0",
"@xata.io/client": "^0.29.3",
"better-sqlite3": "^8.4.0",
"bun-types": "^0.6.6",
Expand Down
16 changes: 13 additions & 3 deletions drizzle-orm/src/aws-data-api/pg/driver.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
import { entityKind } from '~/entity.ts';
import type { SQLWrapper } from '~/index.ts';
import type { Logger } from '~/logger.ts';
import { DefaultLogger } from '~/logger.ts';
import { PgDatabase } from '~/pg-core/db.ts';
import { PgDialect } from '~/pg-core/dialect.ts';
import type { PgRaw } from '~/pg-core/query-builders/raw.ts';
import {
createTableRelationsHelpers,
extractTablesRelationalConfig,
type RelationalSchemaConfig,
type TablesRelationalConfig,
} from '~/relations.ts';
import type { DrizzleConfig } from '~/utils.ts';
import type { AwsDataApiClient, AwsDataApiPgQueryResultHKT } from './session.ts';
import type { AwsDataApiClient, AwsDataApiPgQueryResult, AwsDataApiPgQueryResultHKT } from './session.ts';
import { AwsDataApiSession } from './session.ts';

export interface PgDriverOptions {
Expand All @@ -28,9 +30,17 @@ export interface DrizzleAwsDataApiPgConfig<
secretArn: string;
}

export type AwsDataApiPgDatabase<
export class AwsDataApiPgDatabase<
TSchema extends Record<string, unknown> = Record<string, never>,
> = PgDatabase<AwsDataApiPgQueryResultHKT, TSchema>;
> extends PgDatabase<AwsDataApiPgQueryResultHKT, TSchema> {
static readonly [entityKind]: string = 'AwsDataApiPgDatabase';

override execute<
TRow extends Record<string, unknown> = Record<string, unknown>,
>(query: SQLWrapper): PgRaw<AwsDataApiPgQueryResult<TRow>> {
return super.execute(query);
}
}

export class AwsPgDialect extends PgDialect {
static readonly [entityKind]: string = 'AwsPgDialect';
Expand Down
51 changes: 33 additions & 18 deletions drizzle-orm/src/aws-data-api/pg/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,13 +56,13 @@ export class AwsDataApiPreparedQuery<T extends PreparedQueryConfig> extends PgPr
async execute(placeholderValues: Record<string, unknown> | undefined = {}): Promise<T['execute']> {
const { fields, joinsNotNullableMap, customResultMapper } = this;

const rows = await this.values(placeholderValues) as unknown[][];
const result = await this.values(placeholderValues) as AwsDataApiPgQueryResult<unknown[]>;
if (!fields && !customResultMapper) {
return rows as T['execute'];
return result as T['execute'];
}
return customResultMapper
? customResultMapper(rows)
: rows.map((row) => mapResultRow<T['execute']>(fields!, row, joinsNotNullableMap));
? customResultMapper(result.rows!)
: result.rows!.map((row) => mapResultRow<T['execute']>(fields!, row, joinsNotNullableMap));
}

all(placeholderValues?: Record<string, unknown> | undefined): Promise<T['all']> {
Expand All @@ -83,16 +83,24 @@ export class AwsDataApiPreparedQuery<T extends PreparedQueryConfig> extends PgPr
if (!fields && !customResultMapper) {
const result = await client.send(rawQuery);
if (result.columnMetadata && result.columnMetadata.length > 0) {
return this.mapResultRows(result.records ?? [], result.columnMetadata);
const rows = this.mapResultRows(result.records ?? [], result.columnMetadata);
return {
...result,
rows,
};
}
return result.records ?? [];
return result;
}

const result = await client.send(rawQuery);
const rows = result.records?.map((row) => {
return row.map((field) => getValueFromDataApi(field));
}) ?? [];

return result.records?.map((row: any) => {
return row.map((field: Field) => getValueFromDataApi(field));
});
return {
...result,
rows,
};
}

/** @internal */
Expand Down Expand Up @@ -155,9 +163,10 @@ export class AwsDataApiSession<
prepareQuery<T extends PreparedQueryConfig = PreparedQueryConfig>(
query: QueryWithTypings,
fields: SelectedFieldsOrdered | undefined,
transactionId: string | undefined,
name: string | undefined,
isResponseInArrayMode: boolean,
customResultMapper?: (rows: unknown[][]) => T['execute'],
transactionId?: string,
): PgPreparedQuery<T> {
return new AwsDataApiPreparedQuery(
this.client,
Expand All @@ -166,7 +175,7 @@ export class AwsDataApiSession<
query.typings ?? [],
this.options,
fields,
transactionId,
transactionId ?? this.transactionId,
isResponseInArrayMode,
customResultMapper,
);
Expand All @@ -176,8 +185,10 @@ export class AwsDataApiSession<
return this.prepareQuery<PreparedQueryConfig & { execute: T }>(
this.dialect.sqlToQuery(query),
undefined,
this.transactionId,
undefined,
false,
undefined,
this.transactionId,
).execute();
}

Expand Down Expand Up @@ -208,21 +219,25 @@ export class AwsDataApiTransaction<
> extends PgTransaction<AwsDataApiPgQueryResultHKT, TFullSchema, TSchema> {
static readonly [entityKind]: string = 'AwsDataApiTransaction';

override transaction<T>(transaction: (tx: AwsDataApiTransaction<TFullSchema, TSchema>) => Promise<T>): Promise<T> {
override async transaction<T>(
transaction: (tx: AwsDataApiTransaction<TFullSchema, TSchema>) => Promise<T>,
): Promise<T> {
const savepointName = `sp${this.nestedIndex + 1}`;
const tx = new AwsDataApiTransaction(this.dialect, this.session, this.schema, this.nestedIndex + 1);
this.session.execute(sql`savepoint ${savepointName}`);
await this.session.execute(sql.raw(`savepoint ${savepointName}`));
try {
const result = transaction(tx);
this.session.execute(sql`release savepoint ${savepointName}`);
const result = await transaction(tx);
await this.session.execute(sql.raw(`release savepoint ${savepointName}`));
return result;
} catch (e) {
this.session.execute(sql`rollback to savepoint ${savepointName}`);
await this.session.execute(sql.raw(`rollback to savepoint ${savepointName}`));
throw e;
}
}
}

export type AwsDataApiPgQueryResult<T> = ExecuteStatementCommandOutput & { rows: T[] };

export interface AwsDataApiPgQueryResultHKT extends QueryResultHKT {
type: ExecuteStatementCommandOutput;
type: AwsDataApiPgQueryResult<any>;
}
21 changes: 21 additions & 0 deletions drizzle-orm/src/column-builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ export type ColumnBuilderRuntimeConfig<TData, TRuntimeConfig extends object = ob
notNull: boolean;
default: TData | SQL | undefined;
defaultFn: (() => TData | SQL) | undefined;
onUpdateFn: (() => TData | SQL) | undefined;
hasDefault: boolean;
primaryKey: boolean;
isUnique: boolean;
Expand Down Expand Up @@ -192,6 +193,26 @@ export abstract class ColumnBuilder<
*/
$default = this.$defaultFn;

/**
* Adds a dynamic update value to the column.
* The function will be called when the row is updated, and the returned value will be used as the column value if none is provided.
* If no `default` (or `$defaultFn`) value is provided, the function will be called when the row is inserted as well, and the returned value will be used as the column value.
*
* **Note:** This value does not affect the `drizzle-kit` behavior, it is only used at runtime in `drizzle-orm`.
*/
$onUpdateFn(
fn: () => (this['_'] extends { $type: infer U } ? U : this['_']['data']) | SQL,
): HasDefault<this> {
this.config.onUpdateFn = fn;
this.config.hasDefault = true;
return this as HasDefault<this>;
}

/**
* Alias for {@link $onUpdateFn}.
*/
$onUpdate = this.$onUpdateFn;

/**
* Adds a `primary key` clause to the column definition. This implicitly makes the column `not null`.
*
Expand Down
2 changes: 2 additions & 0 deletions drizzle-orm/src/column.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ export abstract class Column<
readonly notNull: boolean;
readonly default: T['data'] | SQL | undefined;
readonly defaultFn: (() => T['data'] | SQL) | undefined;
readonly onUpdateFn: (() => T['data'] | SQL) | undefined;
readonly hasDefault: boolean;
readonly isUnique: boolean;
readonly uniqueName: string | undefined;
Expand All @@ -79,6 +80,7 @@ export abstract class Column<
this.notNull = config.notNull;
this.default = config.default;
this.defaultFn = config.defaultFn;
this.onUpdateFn = config.onUpdateFn;
this.hasDefault = config.hasDefault;
this.primary = config.primaryKey;
this.isUnique = config.isUnique;
Expand Down
35 changes: 22 additions & 13 deletions drizzle-orm/src/mysql-core/dialect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,20 +110,24 @@ export class MySqlDialect {
}

buildUpdateSet(table: MySqlTable, set: UpdateSet): SQL {
const setEntries = Object.entries(set);

const setSize = setEntries.length;
return sql.join(
setEntries
.flatMap(([colName, value], i): SQL[] => {
const col: MySqlColumn = table[Table.Symbol.Columns][colName]!;
const res = sql`${sql.identifier(col.name)} = ${value}`;
if (i < setSize - 1) {
return [res, sql.raw(', ')];
}
return [res];
}),
const tableColumns = table[Table.Symbol.Columns];

const columnNames = Object.keys(tableColumns).filter((colName) =>
set[colName] !== undefined || tableColumns[colName]?.onUpdateFn !== undefined
);

const setSize = columnNames.length;
return sql.join(columnNames.flatMap((colName, i) => {
const col = tableColumns[colName]!;

const value = set[colName] ?? sql.param(col.onUpdateFn!(), col);
const res = sql`${sql.identifier(col.name)} = ${value}`;

if (i < setSize - 1) {
return [res, sql.raw(', ')];
}
return [res];
}));
}

buildUpdateQuery({ table, set, where, returning, withList }: MySqlUpdateConfig): SQL {
Expand Down Expand Up @@ -423,6 +427,11 @@ export class MySqlDialect {
const defaultFnResult = col.defaultFn();
const defaultValue = is(defaultFnResult, SQL) ? defaultFnResult : sql.param(defaultFnResult, col);
valueList.push(defaultValue);
// eslint-disable-next-line unicorn/no-negated-condition
} else if (!col.default && col.onUpdateFn !== undefined) {
const onUpdateFnResult = col.onUpdateFn();
const newValue = is(onUpdateFnResult, SQL) ? onUpdateFnResult : sql.param(onUpdateFnResult, col);
valueList.push(newValue);
} else {
valueList.push(sql`default`);
}
Expand Down
Loading

0 comments on commit a7c90b8

Please sign in to comment.