chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 12:10:05 +08:00
commit d37d8d293b
1388 changed files with 484182 additions and 0 deletions
+297
View File
@@ -0,0 +1,297 @@
# Common way of defining custom types
> [!NOTE]
> For more advanced documentation about defining custom data types in PostgreSQL and MySQL, please check [`custom-types.md`](custom-types.md).
## Examples
Best way to see, how customType definition is working - is to check how existing data types in postgres and mysql could be defined using `customType` function from Drizzle ORM
### Postgres Data Types using `node-postgres` driver
---
#### **Serial**
```typescript
const customSerial = customType<{ data: number; notNull: true; default: true }>(
{
dataType() {
return 'serial';
},
},
);
```
#### **Text**
```typescript
const customText = customType<{ data: string }>({
dataType() {
return 'text';
},
});
```
#### **Boolean**
```typescript
const customBoolean = customType<{ data: boolean }>({
dataType() {
return 'boolean';
},
});
```
#### **Jsonb**
```typescript
const customJsonb = <TData>(name: string) =>
customType<{ data: TData; driverData: string }>({
dataType() {
return 'jsonb';
},
toDriver(value: TData): string {
return JSON.stringify(value);
},
})(name);
```
#### **Timestamp**
```typescript
const customTimestamp = customType<
{
data: Date;
driverData: string;
config: { withTimezone: boolean; precision?: number };
}
>({
dataType(config) {
const precision = typeof config.precision !== 'undefined'
? ` (${config.precision})`
: '';
return `timestamp${precision}${
config.withTimezone ? ' with time zone' : ''
}`;
},
fromDriver(value: string): Date {
return new Date(value);
},
});
```
#### Usage for all types will be same as defined functions in Drizzle ORM
```typescript
const usersTable = pgTable('users', {
id: customSerial('id').primaryKey(),
name: customText('name').notNull(),
verified: customBoolean('verified').notNull().default(false),
jsonb: customJsonb<string[]>('jsonb'),
createdAt: customTimestamp('created_at', { withTimezone: true }).notNull()
.default(sql`now()`),
});
```
### MySql Data Types using `mysql2` driver
---
#### **Serial**
```typescript
const customSerial = customType<{ data: number; notNull: true; default: true }>(
{
dataType() {
return 'serial';
},
},
);
```
#### **Text**
```typescript
const customText = customType<{ data: string }>({
dataType() {
return 'text';
},
});
```
#### **Boolean**
```typescript
const customBoolean = customType<{ data: boolean }>({
dataType() {
return 'boolean';
},
fromDriver(value) {
if (typeof value === 'boolean') {
return value;
}
return value === 1;
},
});
```
#### **Json**
```typescript
const customJson = <TData>(name: string) =>
customType<{ data: TData; driverData: string }>({
dataType() {
return 'json';
},
toDriver(value: TData): string {
return JSON.stringify(value);
},
})(name);
```
#### **Timestamp**
```typescript
const customTimestamp = customType<
{ data: Date; driverData: string; config: { fsp: number } }
>({
dataType(config) {
const precision = typeof config.fsp !== 'undefined'
? ` (${config.fsp})`
: '';
return `timestamp${precision}`;
},
fromDriver(value: string): Date {
return new Date(value);
},
});
```
#### Usage for all types will be same as defined functions in Drizzle ORM
```typescript
const usersTable = mysqlTable('userstest', {
id: customSerial('id').primaryKey(),
name: customText('name').notNull(),
verified: customBoolean('verified').notNull().default(false),
jsonb: customJson<string[]>('jsonb'),
createdAt: customTimestamp('created_at', { fsp: 2 }).notNull().default(
sql`now()`,
),
});
```
You can check ts-doc for types and param definition
````typescript
export type CustomTypeValues = {
/**
* Required type for custom column, that will infer proper type model
*
* Examples:
*
* If you want your column to be `string` type after selecting/or on inserting - use `data: string`. Like `text`, `varchar`
*
* If you want your column to be `number` type after selecting/or on inserting - use `data: number`. Like `integer`
*/
data: unknown;
/**
* Type helper, that represents what type database driver is accepting for specific database data type
*/
driverData?: unknown;
/**
* What config type should be used for {@link CustomTypeParams} `dataType` generation
*/
config?: unknown;
/**
* Whether the config argument should be required or not
* @default false
*/
configRequired?: boolean;
/**
* If your custom data type should be notNull by default you can use `notNull: true`
*
* @example
* const customSerial = customType<{ data: number, notNull: true, default: true }>({
* dataType() {
* return 'serial';
* },
* });
*/
notNull?: boolean;
/**
* If your custom data type has default you can use `default: true`
*
* @example
* const customSerial = customType<{ data: number, notNull: true, default: true }>({
* dataType() {
* return 'serial';
* },
* });
*/
default?: boolean;
};
export interface CustomTypeParams<T extends Partial<CustomTypeValues>> {
/**
* Database data type string representation, that is used for migrations
* @example
* ```
* `jsonb`, `text`
* ```
*
* If database data type needs additional params you can use them from `config` param
* @example
* ```
* `varchar(256)`, `numeric(2,3)`
* ```
*
* To make `config` be of specific type please use config generic in {@link CustomTypeValues}
*
* @example
* Usage example
* ```
* dataType() {
* return 'boolean';
* },
* ```
* Or
* ```
* dataType(config) {
* return typeof config.length !== 'undefined' ? `varchar(${config.length})` : `varchar`;
* }
* ```
*/
dataType: (config: T['config'] | (Equal<T['configRequired'], true> extends true ? never : undefined)) => string;
/**
* Optional mapping function, between user input and driver
* @example
* For example, when using jsonb we need to map JS/TS object to string before writing to database
* ```
* toDriver(value: TData): string {
* return JSON.stringify(value);
* }
* ```
*/
toDriver?: (value: T['data']) => T['driverData'] | SQL;
/**
* Optional mapping function, that is responsible for data mapping from database to JS/TS code
* @example
* For example, when using timestamp we need to map string Date representation to JS Date
* ```
* fromDriver(value: string): Date {
* return new Date(value);
* },
* ```
*/
fromDriver?: (value: T['driverData']) => T['data'];
}
````
+214
View File
@@ -0,0 +1,214 @@
# How to define custom types
Drizzle ORM has a big set of predefined column types for different SQL databases. But still there are additional types that are not supported by Drizzle ORM (yet). That could be native pg types or extension types
Here are some instructions on how to create and use your own types with Drizzle ORM
---
## Abstract view on column builder pattern in Drizzle ORM
Each type creation should use 2 classes:
- `ColumnBuilder` - class, that is responsible for generating whole set of needed fields for column creation
- `Column` - class, that is representing Columns itself, that is used in query generation, migration mapping, etc.
Each module has it's own class, representing `ColumnBuilder` or `Column`:
- For `pg` -> `PgColumnBuilder` and `PgColumn`
- For `mysql` -> `MySqlColumnBuilder` and `MySqlColumn`
- For `sqlite` -> `SQLiteColumnBuilder` and `SQLiteColumn`
### Builder class explanation - (postgresql text data type example)
- Builder class is responsible for storing TS return type for specific database datatype and override build function to return ready to use column in table
- `TData` - extends return type for column. Current example will infer string type for current datatype used in schema definition
```typescript
export class PgTextBuilder<TData extends string = string>
extends PgColumnBuilder<
ColumnBuilderConfig<{ data: TData; driverParam: string }>
>
{
build<TTableName extends string>(
table: AnyPgTable<{ name: TTableName }>,
): PgText<TTableName, TData> {
return new PgText(table, this.config);
}
}
```
> [!WARNING]
> `$pgColumnBuilderBrand` should be changed and be equal to class name for new data type builder
### Column class explanation - (postgresql text data type example)
---
Column class has set of types/functions, that could be overridden to get needed behavior for custom type
- `TData` - extends return type for column. Current example will infer string type for current datatype used in schema definition
- `getSQLType()` - function, that shows datatype name in database and will be used in migration generation
- `mapFromDriverValue()` - interceptor between database and select query execution. If you want to modify/map/change value for specific data type, it could be done here
#### Usage example for jsonb type
```typescript
override mapToDriverValue(value: TData): string {
return JSON.stringify(value);
}
```
- `mapToDriverValue` - interceptor between user input for insert/update queries and database query. If you want to modify/map/change value for specific data type, it could be done here
#### Usage example for int type
```typescript
override mapFromDriverValue(value: number | string): number {
if (typeof value === 'string') {
return parseInt(value);
}
return value;
}
```
#### Column class example
```typescript
export class PgText<TTableName extends string, TData extends string>
extends PgColumn<ColumnConfig<{ tableName: TTableName; data: TData; driverParam: string }>> {
constructor(table: AnyPgTable<{ name: TTableName }>, builder: PgTextBuilder<TData>['config']) {
super(table, builder);
}
getSQLType(): string {
return 'text';
}
override mapFromDriverValue(value: string): TData {
return value as TData
}
override mapToDriverValue(value: TData): string {
return value
}
}
```
> [!WARNING]
> `$pgColumnBrand` should be changed and be equal to class name for new data type
### Full text data type for PostgreSQL example
For more postgres data type examples you could check [here](/drizzle-orm/src/pg-core/columns)
```typescript
import { ColumnConfig, ColumnBuilderConfig } from 'drizzle-orm';
import { AnyPgTable } from 'drizzle-orm/pg-core';
import { PgColumn, PgColumnBuilder } from './common';
export class PgTextBuilder<TData extends string = string>
extends PgColumnBuilder<
ColumnBuilderConfig<{ data: TData; driverParam: string }>
>
{
build<TTableName extends string>(
table: AnyPgTable<{ name: TTableName }>,
): PgText<TTableName, TData> {
return new PgText(table, this.config);
}
}
export class PgText<TTableName extends string, TData extends string>
extends PgColumn<
ColumnConfig<{ tableName: TTableName; data: TData; driverParam: string }>
>
{
constructor(
table: AnyPgTable<{ name: TTableName }>,
builder: PgTextBuilder<TData>['config'],
) {
super(table, builder);
}
getSQLType(): string {
return 'text';
}
}
export function text<T extends string = string>(
name: string,
): PgTextBuilder<T> {
return new PgTextBuilder(name);
}
```
## Custom data type example
> [!NOTE]
> We will check example on pg module, but current pattern applies to all dialects, that are currently supported by Drizzle ORM
### Setting up CITEXT datatype
> [!NOTE]
> This type is available only with extensions and used for example, just to show how you could setup any data type you want. Extension support will come soon
### CITEXT data type example
```typescript
export class PgCITextBuilder<TData extends string = string> extends PgColumnBuilder<
PgColumnBuilderHKT,
ColumnBuilderConfig<{ data: TData; driverParam: string }>
> {
protected $pgColumnBuilderBrand: string = 'PgCITextBuilder';
build<TTableName extends string>(table: AnyPgTable<{ name: TTableName }>): PgCIText<TTableName, TData> {
return new PgCIText(table, this.config);
}
}
export class PgCIText<TTableName extends string, TData extends string>
extends PgColumn<PgColumnHKT, ColumnConfig<{ tableName: TTableName; data: TData; driverParam: string }>>
{
constructor(table: AnyPgTable<{ name: TTableName }>, builder: PgCITextBuilder<TData>['config']) {
super(table, builder);
}
getSQLType(): string {
return 'citext';
}
}
export function citext<T extends string = string>(name: string): PgCITextBuilder<T> {
return new PgCITextBuilder(name);
}
```
#### Usage example
```typescript
const table = pgTable('table', {
id: integer('id').primaryKey(),
ciname: citext('ciname')
})
```
## Contributing by adding new custom types in Drizzle ORM
You could add your created custom data types to Drizzle ORM, so everyone can use it.
Each data type should be placed in separate file in `columns` folder and PR open with tag `new-data-type:pg` | `new-data-type:sqlite` | `new-data-type:mysql`
For more Contribution information - please check [CONTRIBUTING.md](../CONTRIBUTING.md)
+251
View File
@@ -0,0 +1,251 @@
# Drizzle ORM - Joins
As with other parts of Drizzle ORM, the joins syntax is a balance between the SQL-likeness and type safety.
Here's an example of how a common "one-to-many" relationship can be modelled.
```typescript
const users = pgTable('users', {
id: serial('id').primaryKey(),
firstName: text('first_name').notNull(),
lastName: text('last_name'),
cityId: int('city_id').references(() => cities.id),
});
const cities = pgTable('cities', {
id: serial('id').primaryKey(),
name: text('name').notNull(),
});
```
Now, let's select all cities with all users that live in that city.
This is how you'd write it in raw SQL:
```sql
select
cities.id as city_id,
cities.name as city_name,
users.id as user_id,
users.first_name,
users.last_name
from cities
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({
cityId: cities.id,
cityName: cities.name,
userId: users.id,
firstName: users.firstName,
lastName: users.lastName,
})
.from(cities)
.leftJoin(users, eq(users.cityId, cities.id));
```
`rows` will have the following type:
```typescript
{
cityId: number;
cityName: string;
userId: number | null;
firstName: string | null;
lastName: string | null;
}[]
```
As you can see, all the joined columns have been nullified. This might do the trick if you're using joins to form a single row of results, but in our case we have two separate entities in our row - a city and a user.
It might not be very convenient to check every field for nullability separately (or, even worse, just add an `!` after every field to "make compiler happy"). It would be much more useful if you could somehow run a single check
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 of `.select()`:**
```typescript
const rows = await db
.select({
cityId: cities.id,
cityName: cities.name,
user: {
id: users.id,
firstName: users.firstName,
lastName: users.lastName,
},
})
.from(cities)
.leftJoin(users, eq(users.cityId, cities.id));
```
In that case, the ORM will use dark TypeScript magic (as if it wasn't already) and figure out that you have a nested object where all the fields belong to the same table. So, the `rows` type will now look like this:
```typescript
{
cityId: number;
cityName: string;
user: {
id: number;
firstName: string;
lastName: string | null;
} | null;
}
```
This is much more convenient! Now, you can just do a single check for `row.user !== null`, and all the user fields will become available.
---
Note that you can group any fields in a nested object however you like, but the single check optimization will only be applied to a certain nested object if all its fields belong to the same table.
So, for example, you can group the city fields, too:
```typescript
.select({
city: {
id: cities.id,
name: cities.name,
},
user: {
id: users.id,
firstName: users.firstName,
lastName: users.lastName,
},
})
```
And the result type will look like this:
```typescript
{
city: {
id: number;
name: string;
};
user: {
id: number;
firstName: string;
lastName: string | null;
} | null;
}
```
---
If you just need all the fields from all the tables you're selecting and joining, you can simply omit the argument of the `.select()` method altogether:
```typescript
const rows = await db.select().from(cities).leftJoin(users, eq(users.cityId, cities.id));
```
> [!NOTE]
> In this case, the Drizzle table/column names will be used as the keys in the result object.
```typescript
{
cities: {
id: number;
name: string;
};
users: {
id: number;
firstName: string;
lastName: string | null;
cityId: number | null;
} | null;
}[]
```
---
There are cases where you'd want to select all the fields from one table, but pick fields from others. In that case, instead of listing all the table fields, you can just pass a table:
```typescript
.select({
cities, // shorthand for "cities: cities", the key can be anything
user: {
firstName: users.firstName,
},
})
```
```typescript
{
cities: {
id: number;
name: string;
};
user: {
firstName: string;
} | null;
}
```
---
But what happens if you group columns from multiple tables in the same nested object? Nothing, really - they will still be all individually nullable, just grouped under the same object (as you might expect!):
```typescript
.select({
id: cities.id,
cityAndUser: {
cityName: cities.name,
userId: users.id,
firstName: users.firstName,
lastName: users.lastName,
}
})
```
```typescript
{
id: number;
cityAndUser: {
cityName: string;
userId: number | null;
firstName: string | null;
lastName: string | null;
};
}
```
## Aggregating results
OK, so you have obtained all the cities and the users for every city. But what you **really** wanted is a **list** of users for every city, and what you currently have is an array of `city-user?` combinations. So, how do you transform it?
That's the neat part - you can do that however you'd like! No hand-holding here.
For example, one of the ways to do that would be `Array.reduce()`:
```typescript
import { InferModel } from 'drizzle-orm';
type User = InferModel<typeof users>;
type City = InferModel<typeof cities>;
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[] }>>(
(acc, row) => {
const city = row.city;
const user = row.user;
if (!acc[city.id]) {
acc[city.id] = { city, users: [] };
}
if (user) {
acc[city.id].users.push(user);
}
return acc;
},
{},
);
```
+32
View File
@@ -0,0 +1,32 @@
# Table introspect API
## Get table information
```ts
import { pgTable, getTableConfig } from 'drizzle-orm/pg-core';
const table = pgTable(...);
const {
columns,
indexes,
foreignKeys,
checks,
primaryKeys,
name,
schema,
} = getTableConfig(table);
```
## Get table columns map
```ts
import { pgTable, getTableColumns } from 'drizzle-orm/pg-core';
const table = pgTable('table', {
id: integer('id').primaryKey(),
name: text('name'),
});
const columns/*: { id: ..., name: ... } */ = getTableColumns(table);
```