Skip to content

Commit

Permalink
Merge pull request #1182 from drizzle-team/beta
Browse files Browse the repository at this point in the history
0.28.6 Release
  • Loading branch information
AndriiSherman authored Sep 6, 2023
2 parents cfcdb82 + 6c516c3 commit a7dc7e8
Show file tree
Hide file tree
Showing 19 changed files with 975 additions and 59 deletions.
140 changes: 140 additions & 0 deletions changelogs/drizzle-orm/0.28.6.md
Original file line number Diff line number Diff line change
@@ -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.<table>.findMany()`,
`db.query.<table>.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
2 changes: 1 addition & 1 deletion drizzle-orm/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "drizzle-orm",
"version": "0.28.5",
"version": "0.28.6",
"description": "Drizzle ORM package for SQL databases",
"type": "module",
"scripts": {
Expand Down
53 changes: 50 additions & 3 deletions drizzle-orm/src/libsql/driver.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import type { Client, ResultSet } from '@libsql/client';
import { entityKind } from '~/entity.ts';
import { DefaultLogger } from '~/logger.ts';
import type { SelectResult } from '~/query-builders/select.types.ts';
import {
createTableRelationsHelpers,
extractTablesRelationalConfig,
Expand All @@ -8,12 +10,57 @@ import {
} from '~/relations.ts';
import { BaseSQLiteDatabase } from '~/sqlite-core/db.ts';
import { SQLiteAsyncDialect } from '~/sqlite-core/dialect.ts';
import type {
SQLiteDelete,
SQLiteInsert,
SQLiteSelect,
SQLiteUpdate,
} from '~/sqlite-core/index.ts';
import type { SQLiteRelationalQuery } from '~/sqlite-core/query-builders/query.ts';
import type { SQLiteRaw } from '~/sqlite-core/query-builders/raw.ts';
import { type DrizzleConfig } from '~/utils.ts';
import { LibSQLSession } from './session.ts';

export type LibSQLDatabase<
export type BatchParameters =
| SQLiteUpdate<any, 'async', ResultSet, any>
| SQLiteSelect<any, 'async', ResultSet, any, any>
| SQLiteDelete<any, 'async', ResultSet, any>
| Omit<SQLiteDelete<any, 'async', ResultSet, any>, 'where'>
| Omit<SQLiteUpdate<any, 'async', ResultSet, any>, 'where'>
| SQLiteInsert<any, 'async', ResultSet, any>
| SQLiteRelationalQuery<'async', any>
| SQLiteRaw<any>;

export type BatchResponse<U extends BatchParameters, TQuery extends Readonly<[U, ...U[]]>> = {
[K in keyof TQuery]: TQuery[K] extends
SQLiteSelect<infer _TTable, 'async', infer _TRes, infer TSelection, infer TSelectMode, infer TNullabilityMap>
? SelectResult<TSelection, TSelectMode, TNullabilityMap>[]
: TQuery[K] extends SQLiteUpdate<infer _TTable, 'async', infer _TRunResult, infer _TReturning>
? _TReturning extends undefined ? _TRunResult : _TReturning[]
: TQuery[K] extends Omit<SQLiteUpdate<infer _TTable, 'async', infer _TRunResult, infer _TReturning>, 'where'>
? _TReturning extends undefined ? _TRunResult : _TReturning[]
: TQuery[K] extends SQLiteInsert<infer _TTable, 'async', infer _TRunResult, infer _TReturning>
? _TReturning extends undefined ? _TRunResult : _TReturning[]
: TQuery[K] extends SQLiteDelete<infer _TTable, 'async', infer _TRunResult, infer _TReturning>
? _TReturning extends undefined ? _TRunResult : _TReturning[]
: TQuery[K] extends Omit<SQLiteDelete<infer _TTable, 'async', infer _TRunResult, infer _TReturning>, 'where'>
? _TReturning extends undefined ? _TRunResult : _TReturning[]
: TQuery[K] extends SQLiteRelationalQuery<'async', infer TResult> ? TResult
: TQuery[K] extends SQLiteRaw<infer TResult> ? TResult
: never;
};

export class LibSQLDatabase<
TSchema extends Record<string, unknown> = Record<string, never>,
> = BaseSQLiteDatabase<'async', ResultSet, TSchema>;
> extends BaseSQLiteDatabase<'async', ResultSet, TSchema> {
static readonly [entityKind]: string = 'LibSQLDatabase';

async batch<U extends BatchParameters, T extends Readonly<[U, ...U[]]>>(
batch: T,
): Promise<BatchResponse<U, T>> {
return await (this.session as LibSQLSession<TSchema, any>).batch(batch) as BatchResponse<U, T>;
}
}

export function drizzle<
TSchema extends Record<string, unknown> = Record<string, never>,
Expand All @@ -40,5 +87,5 @@ export function drizzle<
}

const session = new LibSQLSession(client, dialect, schema, { logger }, undefined);
return new BaseSQLiteDatabase('async', dialect, session, schema) as LibSQLDatabase<TSchema>;
return new LibSQLDatabase('async', dialect, session, schema) as LibSQLDatabase<TSchema>;
}
98 changes: 89 additions & 9 deletions drizzle-orm/src/libsql/session.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import type { Client, InArgs, InStatement, ResultSet, Transaction } from '@libsql/client';
import { entityKind } from '~/entity.ts';
import { type Client, type InArgs, type InStatement, type ResultSet, type Transaction } from '@libsql/client';
import { entityKind, is } 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 { fillPlaceholders, type Query, sql } from '~/sql/index.ts';
import type { SQLiteAsyncDialect } from '~/sqlite-core/dialect.ts';
import { SQLiteTransaction } from '~/sqlite-core/index.ts';
import { SQLiteDelete, SQLiteInsert, SQLiteSelect, SQLiteTransaction, SQLiteUpdate } from '~/sqlite-core/index.ts';
import { SQLiteRelationalQuery } from '~/sqlite-core/query-builders/query.ts';
import { SQLiteRaw } from '~/sqlite-core/query-builders/raw.ts';
import type { SelectedFieldsOrdered } from '~/sqlite-core/query-builders/select.types.ts';
import {
type PreparedQueryConfig as PreparedQueryConfigBase,
Expand All @@ -14,6 +16,7 @@ import {
} from '~/sqlite-core/session.ts';
import { PreparedQuery as PreparedQueryBase, SQLiteSession } from '~/sqlite-core/session.ts';
import { mapResultRow } from '~/utils.ts';
import type { BatchParameters } from './driver.ts';

export interface LibSQLSessionOptions {
logger?: Logger;
Expand Down Expand Up @@ -58,12 +61,81 @@ export class LibSQLSession<
);
}

/*override */ batch(queries: SQL[]): Promise<ResultSet[]> {
/*override */ batch<U extends BatchParameters, T extends Readonly<[U, ...U[]]>>(queries: T) {
const queryToType: (
| { mode: 'all' }
| {
mode: 'all_mapped';
config: { fields: SelectedFieldsOrdered; joinsNotNullableMap?: Record<string, boolean> };
}
| { mode: 'get' }
| { mode: 'values' }
| { mode: 'raw' }
| { mode: 'rqb'; mapper: any }
)[] = [];

const builtQueries: InStatement[] = queries.map((query) => {
const builtQuery = this.dialect.sqlToQuery(query);
const builtQuery = this.dialect.sqlToQuery(query.getSQL());

if (is(query, SQLiteSelect)) {
// @ts-expect-error
const prepared = query.prepare() as PreparedQuery;
prepared.fields === undefined
? queryToType.push({ mode: 'all' })
: queryToType.push({
mode: 'all_mapped',
config: { fields: prepared.fields, joinsNotNullableMap: prepared.joinsNotNullableMap },
});
} else if (is(query, SQLiteInsert) || is(query, SQLiteUpdate) || is(query, SQLiteDelete)) {
queryToType.push(
// @ts-expect-error
query.config.returning
? {
mode: 'all_mapped',
config: { fields: query.config.returning },
}
: { mode: 'raw' },
);
} else if (is(query, SQLiteRaw)) {
queryToType.push(
query.config.action === 'run' ? { mode: 'raw' } : { mode: query.config.action },
);
} else if (is(query, SQLiteRelationalQuery)) {
const preparedRqb = query.prepare() as PreparedQuery;
queryToType.push({ mode: 'rqb', mapper: preparedRqb.customResultMapper });
}

return { sql: builtQuery.sql, args: builtQuery.params as InArgs };
});
return this.client.batch(builtQueries);

const res = this.client.batch(builtQueries).then((stmt) =>
stmt.map(({ rows }, index) => {
const action = queryToType[index]!;
if (action.mode === 'all') {
return rows.map((row) => normalizeRow(row));
}
if (action.mode === 'all_mapped') {
return rows.map((row) => {
return mapResultRow(
action.config.fields,
Array.prototype.slice.call(row).map((v) => normalizeFieldValue(v)),
action.config.joinsNotNullableMap,
);
});
}
if (action.mode === 'get') {
return normalizeRow(rows[0]);
}
if (action.mode === 'values') {
return rows.map((row) => Object.values(row));
}
if (action.mode === 'raw') {
return stmt[index];
}
return action.mapper(rows, normalizeFieldValue);
})
);
return res;
}

override async transaction<T>(
Expand Down Expand Up @@ -111,17 +183,25 @@ export class PreparedQuery<T extends PreparedQueryConfig = PreparedQueryConfig>
> {
static readonly [entityKind]: string = 'LibSQLPreparedQuery';

/** @internal */
customResultMapper?: (rows: unknown[][], mapColumnValue?: (value: unknown) => unknown) => unknown;

/** @internal */
fields?: SelectedFieldsOrdered;

constructor(
private client: Client,
private queryString: string,
private params: unknown[],
private logger: Logger,
private fields: SelectedFieldsOrdered | undefined,
fields: SelectedFieldsOrdered | undefined,
private tx: Transaction | undefined,
executeMethod: SQLiteExecuteMethod,
private customResultMapper?: (rows: unknown[][], mapColumnValue?: (value: unknown) => unknown) => unknown,
customResultMapper?: (rows: unknown[][], mapColumnValue?: (value: unknown) => unknown) => unknown,
) {
super('async', executeMethod);
this.customResultMapper = customResultMapper;
this.fields = fields;
}

run(placeholderValues?: Record<string, unknown>): Promise<ResultSet> {
Expand Down
Loading

0 comments on commit a7dc7e8

Please sign in to comment.