Skip to content

Commit

Permalink
Merge pull request #171 from drizzle-team/orm-88-select-from-subquery
Browse files Browse the repository at this point in the history
Implement selecting/joining subquery, replace .select().fields() with .select().from()
  • Loading branch information
dankochetov authored Feb 9, 2023
2 parents cdd79a3 + c0cd0b3 commit 7032b9b
Show file tree
Hide file tree
Showing 61 changed files with 2,410 additions and 1,447 deletions.
14 changes: 8 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,26 +102,28 @@ const updateResult /* : { updated: Date }[] */ = await db.update(users)
.returning({ updated: users.updatedAt });

// Select
const allUsers /* : User[] */ = await db.select(users);
const allUsers /* : User[] */ = await db.select().from(users);

// Select custom fields
const upperCaseNames /* : { id: number; name: string }[] */ = await db.select(users)
.fields({
const upperCaseNames /* : { id: number; name: string }[] */ = await db
.select({
id: users.id,
name: sql`upper(${users.fullName})`.as<string>(),
});
})
.from(users);

// Joins
// You wouldn't BELIEVE how SMART the result type is! 😱
const allUsersWithCities = await db.select(users)
.fields({
const allUsersWithCities = await db
.select({
id: users.id,
name: users.fullName,
city: {
id: cities.id,
name: cities.name,
},
})
.from(users)
.leftJoin(cities, eq(users.cityId, cities.id));

// Delete
Expand Down
17 changes: 17 additions & 0 deletions changelogs/drizzle-orm/0.19.0.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
- Implemented selecting and joining a subquery. Example usage:

```ts
const sq = db
.select({
categoryId: courseCategoriesTable.id,
category: courseCategoriesTable.name,
total: sql`count(${courseCategoriesTable.id})`.as<number>(),
})
.from(courseCategoriesTable)
.groupBy(courseCategoriesTable.id, courseCategoriesTable.name)
.subquery('sq');
```

After that, just use the subquery instead of a table as usual.

- ❗ Replaced `db.select(table).fields({ ... })` syntax with `db.select({ ... }).from(table)` to look more like its SQL counterpart.
29 changes: 16 additions & 13 deletions docs/joins.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,15 @@ left join users on users.city_id = cities.id
And here's how to do the same with Drizzle ORM:

```typescript
const rows = await db.select(cities)
.fields({
cityId: cities.id,
cityName: cities.name,
userId: users.id,
firstName: users.firstName,
lastName: users.lastName,
})
const rows = await db
.select({
cityId: cities.id,
cityName: cities.name,
userId: users.id,
firstName: users.firstName,
lastName: users.lastName,
})
.from(cities)
.leftJoin(users, eq(users.cityId, cities.id));
```

Expand All @@ -65,8 +66,8 @@ to verify that the user was joined and all of its fields are available.
**To achieve that, you can group the fields of a certain table in a nested object inside the `.fields()`:**

```typescript
const rows = await db.select(cities)
.fields({
const rows = await db
.select({
cityId: cities.id,
cityName: cities.name,
user: {
Expand All @@ -75,6 +76,7 @@ const rows = await db.select(cities)
lastName: users.lastName,
},
})
.from(cities)
.leftJoin(users, eq(users.cityId, cities.id));
```

Expand Down Expand Up @@ -134,7 +136,7 @@ And the result type will look like this:
If you just need all the fields from all the tables you're selecting and joining, you can skip the `.fields()` method altogether:

```typescript
const rows = await db.select(cities).leftJoin(users, eq(users.cityId, cities.id));
const rows = await db.select().from(cities).leftJoin(users, eq(users.cityId, cities.id));
```

> **Note**: in this case, the DB table names will be used as the keys in the result object.
Expand Down Expand Up @@ -220,11 +222,12 @@ import { InferModel } from 'drizzle-orm';
type User = InferModel<typeof users>;
type City = InferModel<typeof cities>;

const rows = await db.select(cities)
.fields({
const rows = await db
.select({
city: cities,
user: users,
})
.from(cities)
.leftJoin(users, eq(users.cityId, cities.id));

const result = rows.reduce<Record<number, { city: City; users: User[] }>>(
Expand Down
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.18.0",
"version": "0.19.0",
"description": "Drizzle ORM package for SQL databases",
"scripts": {
"build": "tsc && resolve-tspaths && cp ../README.md package.json dist/",
Expand Down
102 changes: 61 additions & 41 deletions drizzle-orm/src/mysql-core/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ const poolConnection = mysql.createPool({

const db = drizzle(poolConnection);

const allUsers = await db.select(users);
const allUsers = await db.select().from(users);
```

### Connect using mysql2 Client
Expand All @@ -111,7 +111,7 @@ const connection = await mysql.createConnection({

const db = drizzle(connection);

const allUsers = await db.select(users);
const allUsers = await db.select().from(users);
```

### Connect using PlanetScale Serverless client
Expand All @@ -132,7 +132,7 @@ const connection = connect({

const db = drizzle(connection);

const allUsers = await db.select(users);
const allUsers = await db.select().from(users);
```

## Schema declaration
Expand Down Expand Up @@ -193,7 +193,7 @@ const poolConnection = mysql.createPool({

export const db: MySqlDatabase = drizzle(poolConnection);

const result: User[] = await db.select(users);
const result: User[] = await db.select().from(users);

/* type MySqlRawQueryExample is a response from mysql2 driver
type MySqlRawQueryResult = [ResultSetHeader, FieldPacket[]];
Expand Down Expand Up @@ -349,31 +349,48 @@ const users = mysqlTable('users', {

const db = drizzle(...);

await db.select(users);
await db.select(users).where(eq(users.id, 42));
await db.select().from(users);
await db.select().from(users).where(eq(users.id, 42));

// you can combine filters with eq(...) or or(...)
await db.select(users)
.where(and(eq(users.id, 42), eq(users.name, 'Dan')));
// you can combine filters with and(...) / or(...)
await db.select().from(users).where(and(eq(users.id, 42), eq(users.name, 'Dan')));

await db.select(users)
await db.select().from(users)
.where(or(eq(users.id, 42), eq(users.id, 1)));

// partial select
const result = await db.select(users).fields({
const result = await db
.select({
mapped1: users.id,
mapped2: users.name,
});
})
.from(users);
const { mapped1, mapped2 } = result[0];

// limit, offset & order by
await db.select(users).limit(10).offset(10);
await db.select(users).orderBy(asc(users.name));
await db.select(users).orderBy(desc(users.name));
await db.select().from(users).limit(10).offset(10);
await db.select().from(users).orderBy(users.name);
await db.select().from(users).orderBy(desc(users.name));
// you can pass multiple order args
await db.select(users).orderBy(asc(users.name), desc(users.name));
await db.select().from(users).orderBy(asc(users.name), desc(users.name));
```

#### Select from subquery

```typescript
const sq = db.select().from(users).where(eq(users.id, 42)).subquery('sq');
await db.select().from(sq);
```

Subqueries in joins are supported, too:

```typescript
await db.select().from(users).leftJoin(sq, eq(users.id, sq.id));
```

#### List of all filter operators

// list of all filter operators
```typescript
eq(column, value)
eq(column1, column2)
ne(column, value)
Expand Down Expand Up @@ -415,6 +432,7 @@ not(sqlExpression)

and(expressions: SQL[])
or(expressions: SQL[])

```

### Insert
Expand Down Expand Up @@ -497,8 +515,7 @@ const users = mysqlTable('users', {
cityId: int('city_id').references(() => cities.id),
});

const result = db.select(cities)
.leftJoin(users, eq(cities2.id, users2.cityId));
const result = db.select().from(cities).leftJoin(users, eq(cities2.id, users2.cityId));
```

#### Many-to-many
Expand All @@ -520,7 +537,9 @@ const usersToChatGroups = mysqlTable('usersToChatGroups', {
});

// querying user group with id 1 and all the participants(users)
const result = await db.select(usersToChatGroups)
const result = await db
.select()
.from(usersToChatGroups)
.leftJoin(users, eq(usersToChatGroups.userId, users.id))
.leftJoin(chatGroups, eq(usersToChatGroups.groupId, chatGroups.id))
.where(eq(chatGroups.id, 1));
Expand All @@ -539,7 +558,9 @@ export const files = mysqlTable('folders', {
const nestedFiles = alias(files, 'nested_files');

// will return files and folders and nested files for each folder at root dir
const result = await db.select(files)
const result = await db
.select()
.from(files)
.leftJoin(nestedFiles, eq(files.name, nestedFiles.name))
.where(eq(files.parent, '/'));
```
Expand All @@ -548,29 +569,30 @@ const result = await db.select(files)

```typescript
// Select user ID and city ID and name
const result1 = await db.select(cities).fields({
userId: users.id,
cityId: cities.id,
cityName: cities.name,
}).leftJoin(users, eq(users.cityId, cities.id));
const result1 = await db
.select({
userId: users.id,
cityId: cities.id,
cityName: cities.name,
})
.from(cities).leftJoin(users, eq(users.cityId, cities.id));

// Select all fields from users and only id and name from cities
const result2 = await db.select(cities).fields({
// Supports any level of nesting!
user: users,
city: {
id: cities.id,
name: cities.name,
},
}).leftJoin(users, eq(users.cityId, cities.id));
const result2 = await db
.select({
user: users,
city: {
id: cities.id,
name: cities.name,
},
})
.from(cities).leftJoin(users, eq(users.cityId, cities.id));
```

## Prepared statements

```typescript
const query = db.select(users)
.where(eq(users.name, 'Dan'))
.prepare();
const query = db.select().from(users).where(eq(users.name, 'Dan')).prepare();

const result = await query.execute();
```
Expand All @@ -580,9 +602,7 @@ const result = await query.execute();
```typescript
import { placeholder } from 'drizzle-orm/mysql-core';

const query = db.select(users)
.where(eq(users.name, placeholder('name')))
.prepare();
const query = db.select().from(users).where(eq(users.name, placeholder('name'))).prepare();

const result = await query.execute({ name: 'Dan' });
```
Expand Down Expand Up @@ -622,7 +642,7 @@ export const authOtps = mysqlTable('auth_otp', {
id: serial('id').primaryKey(),
phone: varchar('phone', { length: 256 }),
userId: int('user_id').references(() => users.id),
}
});
```

It will generate:
Expand Down
4 changes: 0 additions & 4 deletions drizzle-orm/src/mysql-core/columns/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,10 +85,6 @@ export abstract class MySqlColumn<T extends Partial<ColumnBaseConfig>> extends C
) {
super(table, config);
}

unsafe(): AnyMySqlColumn {
return this as AnyMySqlColumn;
}
}

export type AnyMySqlColumn<TPartial extends Partial<ColumnBaseConfig> = {}> = MySqlColumn<
Expand Down
21 changes: 12 additions & 9 deletions drizzle-orm/src/mysql-core/db.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { ResultSetHeader } from 'mysql2/promise';
import { SQLWrapper } from '~/sql';
import { orderSelectedFields } from '~/utils';
import { MySqlDialect } from './dialect';
import { MySqlDelete, MySqlInsertBuilder, MySqlSelect, MySqlUpdateBuilder } from './query-builders';
import { MySqlDelete, MySqlInsertBuilder, MySqlSelectBuilder, MySqlUpdateBuilder } from './query-builders';
import { SelectFields } from './query-builders/select.types';
import { MySqlSession, QueryResultHKT, QueryResultKind } from './session';
import { AnyMySqlTable, MySqlTable } from './table';
import { AnyMySqlTable } from './table';

export class MySqlDatabase<TQueryResult extends QueryResultHKT, TSession extends MySqlSession> {
constructor(
Expand All @@ -14,24 +14,27 @@ export class MySqlDatabase<TQueryResult extends QueryResultHKT, TSession extends
readonly session: TSession,
) {}

select<TTable extends AnyMySqlTable>(from: TTable): MySqlSelect<TTable> {
const fields = orderSelectedFields(from[MySqlTable.Symbol.Columns]);
return new MySqlSelect(from, fields, this.session, this.dialect);
select(): MySqlSelectBuilder<undefined>;
select<TSelection extends SelectFields>(fields: TSelection): MySqlSelectBuilder<TSelection>;
select(fields?: SelectFields): MySqlSelectBuilder<SelectFields | undefined> {
return new MySqlSelectBuilder(fields ?? undefined, this.session, this.dialect);
}

update<TTable extends AnyMySqlTable>(table: TTable): MySqlUpdateBuilder<TTable,TQueryResult> {
update<TTable extends AnyMySqlTable>(table: TTable): MySqlUpdateBuilder<TTable, TQueryResult> {
return new MySqlUpdateBuilder(table, this.session, this.dialect);
}

insert<TTable extends AnyMySqlTable>(table: TTable): MySqlInsertBuilder<TTable,TQueryResult> {
insert<TTable extends AnyMySqlTable>(table: TTable): MySqlInsertBuilder<TTable, TQueryResult> {
return new MySqlInsertBuilder(table, this.session, this.dialect);
}

delete<TTable extends AnyMySqlTable>(table: TTable): MySqlDelete<TTable, TQueryResult> {
return new MySqlDelete(table, this.session, this.dialect);
}

execute<T extends { [column: string]: any } = ResultSetHeader>(query: SQLWrapper): Promise<QueryResultKind<TQueryResult, T>> {
execute<T extends { [column: string]: any } = ResultSetHeader>(
query: SQLWrapper,
): Promise<QueryResultKind<TQueryResult, T>> {
return this.session.execute(query.getSQL());
}
}
Loading

0 comments on commit 7032b9b

Please sign in to comment.