Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/improve-views' into update-valid…
Browse files Browse the repository at this point in the history
…ators
L-Mario564 committed Nov 14, 2024

Verified

This commit was signed with the committer’s verified signature.
2 parents 1fc52ca + d91df79 commit aa76163
Showing 8 changed files with 189 additions and 18 deletions.
5 changes: 2 additions & 3 deletions drizzle-orm/src/mysql-core/view.ts
Original file line number Diff line number Diff line change
@@ -7,7 +7,6 @@ import type { ColumnsSelection, SQL } from '~/sql/sql.ts';
import { getTableColumns } from '~/utils.ts';
import type { MySqlColumn, MySqlColumnBuilderBase } from './columns/index.ts';
import { QueryBuilder } from './query-builders/query-builder.ts';
import type { SelectedFields } from './query-builders/select.types.ts';
import { mysqlTable } from './table.ts';
import { MySqlViewBase } from './view-base.ts';
import { MySqlViewConfig } from './view-common.ts';
@@ -58,7 +57,7 @@ export class ViewBuilderCore<TConfig extends { name: string; columns?: unknown }
export class ViewBuilder<TName extends string = string> extends ViewBuilderCore<{ name: TName }> {
static override readonly [entityKind]: string = 'MySqlViewBuilder';

as<TSelectedFields extends SelectedFields>(
as<TSelectedFields extends ColumnsSelection>(
qb: TypedQueryBuilder<TSelectedFields> | ((qb: QueryBuilder) => TypedQueryBuilder<TSelectedFields>),
): MySqlViewWithSelection<TName, false, AddAliasToSelection<TSelectedFields, TName, 'mysql'>> {
if (typeof qb === 'function') {
@@ -160,7 +159,7 @@ export class MySqlView<
config: {
name: TName;
schema: string | undefined;
selectedFields: SelectedFields;
selectedFields: ColumnsSelection;
query: SQL | undefined;
};
}) {
9 changes: 4 additions & 5 deletions drizzle-orm/src/pg-core/view.ts
Original file line number Diff line number Diff line change
@@ -8,7 +8,6 @@ import { getTableColumns } from '~/utils.ts';
import type { RequireAtLeastOne } from '~/utils.ts';
import type { PgColumn, PgColumnBuilderBase } from './columns/common.ts';
import { QueryBuilder } from './query-builders/query-builder.ts';
import type { SelectedFields } from './query-builders/select.types.ts';
import { pgTable } from './table.ts';
import { PgViewBase } from './view-base.ts';
import { PgViewConfig } from './view-common.ts';
@@ -45,7 +44,7 @@ export class DefaultViewBuilderCore<TConfig extends { name: string; columns?: un
export class ViewBuilder<TName extends string = string> extends DefaultViewBuilderCore<{ name: TName }> {
static override readonly [entityKind]: string = 'PgViewBuilder';

as<TSelectedFields extends SelectedFields>(
as<TSelectedFields extends ColumnsSelection>(
qb: TypedQueryBuilder<TSelectedFields> | ((qb: QueryBuilder) => TypedQueryBuilder<TSelectedFields>),
): PgViewWithSelection<TName, false, AddAliasToSelection<TSelectedFields, TName, 'pg'>> {
if (typeof qb === 'function') {
@@ -198,7 +197,7 @@ export class MaterializedViewBuilder<TName extends string = string>
{
static override readonly [entityKind]: string = 'PgMaterializedViewBuilder';

as<TSelectedFields extends SelectedFields>(
as<TSelectedFields extends ColumnsSelection>(
qb: TypedQueryBuilder<TSelectedFields> | ((qb: QueryBuilder) => TypedQueryBuilder<TSelectedFields>),
): PgMaterializedViewWithSelection<TName, false, AddAliasToSelection<TSelectedFields, TName, 'pg'>> {
if (typeof qb === 'function') {
@@ -317,7 +316,7 @@ export class PgView<
config: {
name: TName;
schema: string | undefined;
selectedFields: SelectedFields;
selectedFields: ColumnsSelection;
query: SQL | undefined;
};
}) {
@@ -362,7 +361,7 @@ export class PgMaterializedView<
config: {
name: TName;
schema: string | undefined;
selectedFields: SelectedFields;
selectedFields: ColumnsSelection;
query: SQL | undefined;
};
}) {
2 changes: 2 additions & 0 deletions drizzle-orm/src/query-builders/select.types.ts
Original file line number Diff line number Diff line change
@@ -93,6 +93,7 @@ export type AddAliasToSelection<
: {
[Key in keyof TSelection]: TSelection[Key] extends Column
? ChangeColumnTableName<TSelection[Key], TAlias, TDialect>
: TSelection[Key] extends Table ? AddAliasToSelection<TSelection[Key]['_']['columns'], TAlias, TDialect>
: TSelection[Key] extends SQL | SQL.Aliased ? TSelection[Key]
: TSelection[Key] extends ColumnsSelection ? MapColumnsToTableAlias<TSelection[Key], TAlias, TDialect>
: never;
@@ -120,6 +121,7 @@ export type BuildSubquerySelection<
[Key in keyof TSelection]: TSelection[Key] extends SQL
? DrizzleTypeError<'You cannot reference this field without assigning it an alias first - use `.as(<alias>)`'>
: TSelection[Key] extends SQL.Aliased ? TSelection[Key]
: TSelection[Key] extends Table ? BuildSubquerySelection<TSelection[Key]['_']['columns'], TNullability>
: TSelection[Key] extends Column
? ApplyNullabilityToColumn<TSelection[Key], TNullability[TSelection[Key]['_']['tableName']]>
: TSelection[Key] extends ColumnsSelection ? BuildSubquerySelection<TSelection[Key], TNullability>
17 changes: 14 additions & 3 deletions drizzle-orm/src/sql/sql.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import type { CasingCache } from '~/casing.ts';
import { entityKind, is } from '~/entity.ts';
import type { SelectedFields } from '~/operations.ts';
import { isPgEnum } from '~/pg-core/columns/enum.ts';
import type { SelectResult } from '~/query-builders/select.types.ts';
import { Subquery } from '~/subquery.ts';
import { tracer } from '~/tracing.ts';
import type { Assume, Equal } from '~/utils.ts';
import { ViewBaseConfig } from '~/view-common.ts';
import type { AnyColumn } from '../column.ts';
import { Column } from '../column.ts';
@@ -629,17 +630,19 @@ export abstract class View<
name: TName;
originalName: TName;
schema: string | undefined;
selectedFields: SelectedFields<AnyColumn, Table>;
selectedFields: ColumnsSelection;
isExisting: TExisting;
query: TExisting extends true ? undefined : SQL;
isAlias: boolean;
};

declare readonly $inferSelect: InferSelectViewModel<View<Assume<TName, string>, TExisting, TSelection>>;

constructor(
{ name, schema, selectedFields, query }: {
name: TName;
schema: string | undefined;
selectedFields: SelectedFields<AnyColumn, Table>;
selectedFields: ColumnsSelection;
query: SQL | undefined;
},
) {
@@ -659,6 +662,14 @@ export abstract class View<
}
}

export type InferSelectViewModel<TView extends View> =
Equal<TView['_']['selectedFields'], { [x: string]: unknown }> extends true ? { [x: string]: unknown }
: SelectResult<
TView['_']['selectedFields'],
'single',
Record<TView['_']['name'], 'not-null'>
>;

// Defined separately from the Column class to resolve circular dependency
Column.prototype.getSQL = function() {
return new SQL([this]);
5 changes: 2 additions & 3 deletions drizzle-orm/src/sqlite-core/view.ts
Original file line number Diff line number Diff line change
@@ -7,7 +7,6 @@ import type { ColumnsSelection, SQL } from '~/sql/sql.ts';
import { getTableColumns } from '~/utils.ts';
import type { SQLiteColumn, SQLiteColumnBuilderBase } from './columns/common.ts';
import { QueryBuilder } from './query-builders/query-builder.ts';
import type { SelectedFields } from './query-builders/select.types.ts';
import { sqliteTable } from './table.ts';
import { SQLiteViewBase } from './view-base.ts';

@@ -38,7 +37,7 @@ export class ViewBuilderCore<
export class ViewBuilder<TName extends string = string> extends ViewBuilderCore<{ name: TName }> {
static override readonly [entityKind]: string = 'SQLiteViewBuilder';

as<TSelection extends SelectedFields>(
as<TSelection extends ColumnsSelection>(
qb: TypedQueryBuilder<TSelection> | ((qb: QueryBuilder) => TypedQueryBuilder<TSelection>),
): SQLiteViewWithSelection<TName, false, AddAliasToSelection<TSelection, TName, 'sqlite'>> {
if (typeof qb === 'function') {
@@ -135,7 +134,7 @@ export class SQLiteView<
config: {
name: TName;
schema: string | undefined;
selectedFields: SelectedFields;
selectedFields: ColumnsSelection;
query: SQL | undefined;
};
}) {
49 changes: 47 additions & 2 deletions drizzle-orm/type-tests/mysql/select.ts
Original file line number Diff line number Diff line change
@@ -22,11 +22,19 @@ import {
or,
} from '~/expressions.ts';
import { alias } from '~/mysql-core/alias.ts';
import { param, sql } from '~/sql/sql.ts';
import { type InferSelectViewModel, param, sql } from '~/sql/sql.ts';

import type { Equal } from 'type-tests/utils.ts';
import { Expect } from 'type-tests/utils.ts';
import { type MySqlSelect, type MySqlSelectQueryBuilder, QueryBuilder } from '~/mysql-core/index.ts';
import {
int,
type MySqlSelect,
type MySqlSelectQueryBuilder,
mysqlTable,
mysqlView,
QueryBuilder,
text,
} from '~/mysql-core/index.ts';
import { db } from './db.ts';
import { cities, classes, newYorkers, users } from './tables.ts';

@@ -604,3 +612,40 @@ await db
// @ts-expect-error method was already called
.for('update');
}

{
const table1 = mysqlTable('table1', {
id: int().primaryKey(),
name: text().notNull(),
});
const table2 = mysqlTable('table2', {
id: int().primaryKey(),
age: int().notNull(),
});
const table3 = mysqlTable('table3', {
id: int().primaryKey(),
phone: text().notNull(),
});
const view = mysqlView('view').as((qb) =>
qb.select({
table: table1,
column: table2.age,
nested: {
column: table3.phone,
},
}).from(table1).innerJoin(table2, sql``).leftJoin(table3, sql``)
);
const result = await db.select().from(view);

Expect<
Equal<typeof result, {
table: typeof table1.$inferSelect;
column: number;
nested: {
column: string | null;
};
}[]>
>;
Expect<Equal<typeof result, typeof view.$inferSelect[]>>;
Expect<Equal<typeof result, InferSelectViewModel<typeof view>[]>>;
}
78 changes: 77 additions & 1 deletion drizzle-orm/type-tests/pg/select.ts
Original file line number Diff line number Diff line change
@@ -31,13 +31,15 @@ import { alias } from '~/pg-core/alias.ts';
import {
boolean,
integer,
pgMaterializedView,
type PgSelect,
type PgSelectQueryBuilder,
pgTable,
pgView,
QueryBuilder,
text,
} from '~/pg-core/index.ts';
import { type SQL, sql } from '~/sql/sql.ts';
import { type InferSelectViewModel, type SQL, sql } from '~/sql/sql.ts';

import { db } from './db.ts';
import { cities, classes, newYorkers, newYorkers2, users } from './tables.ts';
@@ -1156,3 +1158,77 @@ await db
),
);
}

{
const table1 = pgTable('table1', {
id: integer().primaryKey(),
name: text().notNull(),
});
const table2 = pgTable('table2', {
id: integer().primaryKey(),
age: integer().notNull(),
});
const table3 = pgTable('table3', {
id: integer().primaryKey(),
phone: text().notNull(),
});
const view = pgView('view').as((qb) =>
qb.select({
table: table1,
column: table2.age,
nested: {
column: table3.phone,
},
}).from(table1).innerJoin(table2, sql``).leftJoin(table3, sql``)
);
const result = await db.select().from(view);

Expect<
Equal<typeof result, {
table: typeof table1.$inferSelect;
column: number;
nested: {
column: string | null;
};
}[]>
>;
Expect<Equal<typeof result, typeof view.$inferSelect[]>>;
Expect<Equal<typeof result, InferSelectViewModel<typeof view>[]>>;
}

{
const table1 = pgTable('table1', {
id: integer().primaryKey(),
name: text().notNull(),
});
const table2 = pgTable('table2', {
id: integer().primaryKey(),
age: integer().notNull(),
});
const table3 = pgTable('table3', {
id: integer().primaryKey(),
phone: text().notNull(),
});
const view = pgMaterializedView('view').as((qb) =>
qb.select({
table: table1,
column: table2.age,
nested: {
column: table3.phone,
},
}).from(table1).innerJoin(table2, sql``).leftJoin(table3, sql``)
);
const result = await db.select().from(view);

Expect<
Equal<typeof result, {
table: typeof table1.$inferSelect;
column: number;
nested: {
column: string | null;
};
}[]>
>;
Expect<Equal<typeof result, typeof view.$inferSelect[]>>;
Expect<Equal<typeof result, InferSelectViewModel<typeof view>[]>>;
}
42 changes: 41 additions & 1 deletion drizzle-orm/type-tests/sqlite/select.ts
Original file line number Diff line number Diff line change
@@ -21,12 +21,15 @@ import {
notLike,
or,
} from '~/expressions.ts';
import { param, sql } from '~/sql/sql.ts';
import { type InferSelectViewModel, param, sql } from '~/sql/sql.ts';
import { alias } from '~/sqlite-core/alias.ts';

import type { Equal } from 'type-tests/utils.ts';
import { Expect } from 'type-tests/utils.ts';
import { integer, text } from '~/sqlite-core/index.ts';
import type { SQLiteSelect, SQLiteSelectQueryBuilder } from '~/sqlite-core/query-builders/select.types.ts';
import { sqliteTable } from '~/sqlite-core/table.ts';
import { sqliteView } from '~/sqlite-core/view.ts';
import { db } from './db.ts';
import { cities, classes, newYorkers, users } from './tables.ts';

@@ -579,3 +582,40 @@ Expect<
// @ts-expect-error method was already called
.offset(10);
}

{
const table1 = sqliteTable('table1', {
id: integer().primaryKey(),
name: text().notNull(),
});
const table2 = sqliteTable('table2', {
id: integer().primaryKey(),
age: integer().notNull(),
});
const table3 = sqliteTable('table3', {
id: integer().primaryKey(),
phone: text().notNull(),
});
const view = sqliteView('view').as((qb) =>
qb.select({
table: table1,
column: table2.age,
nested: {
column: table3.phone,
},
}).from(table1).innerJoin(table2, sql``).leftJoin(table3, sql``)
);
const result = await db.select().from(view);

Expect<
Equal<typeof result, {
table: typeof table1.$inferSelect;
column: number;
nested: {
column: string | null;
};
}[]>
>;
Expect<Equal<typeof result, typeof view.$inferSelect[]>>;
Expect<Equal<typeof result, InferSelectViewModel<typeof view>[]>>;
}

0 comments on commit aa76163

Please sign in to comment.