Skip to content

Commit

Permalink
implement ColumnType and remove db.generated
Browse files Browse the repository at this point in the history
  • Loading branch information
koskimas committed Jan 2, 2022
1 parent 0ca3b01 commit a5e8c00
Show file tree
Hide file tree
Showing 26 changed files with 374 additions and 218 deletions.
68 changes: 53 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,34 +65,61 @@ All you need to do is define an interface for each table in the database and pas
interfaces to the `Kysely` constructor:

```ts
import { Kysely, PostgresDialect } from 'kysely'
import {
Kysely,
PostgresDialect,
Generated,
ColumnType,
Selectable,
Insertable,
Updateable,
} from 'kysely'

interface PersonTable {
// Columns that are generated by the database should be marked
// using the `Generated` type. This way they are automatically
// made optional in inserts.
id: Generated<number>

interface Person {
id: number
first_name: string
// If the column is nullable in the db, make its type nullable.
// Don't use optional properties.
last_name: string | null
gender: 'male' | 'female' | 'other'

// If the column is nullable in the database, make its type nullable.
// Don't use optional properties. Optionality is always determined
// automatically by Kysely.
last_name: string | null

// You can specity a type for each operation (select, insert and update)
// using the `ColumnType<SelectType, InsertType, UpdateType>` wrapper.
// Here we define a column `modified_at` that is selected as a `Date`,
// can optionally be provided as a `string` in inserts and can never
// be updated:
modified_at: ColumnType<Date, string | undefined, never>
}

interface Pet {
id: number
// You can extract the select, insert and update interfaces like this
// if you want (you don't need to):
type Person = Selectable<PersonTable>
type InsertablePerson = Insertable<PersonTable>
type UpdateablePerson = Updateable<PersonTable>

interface PetTable {
id: Generated<number>
name: string
owner_id: number
species: 'dog' | 'cat'
}

interface Movie {
id: string
interface MovieTable {
id: Generated<string>
stars: number
}

// Keys of this interface are table names.
interface Database {
person: Person
pet: Pet
movie: Movie
person: PersonTable
pet: PetTable
movie: MovieTable
}

// You'd create one of these when you start your app.
Expand All @@ -101,15 +128,26 @@ const db = new Kysely<Database>({
dialect: new PostgresDialect({
host: 'localhost',
database: 'kysely_test',
})
}),
})

async function demo() {
const { id } = await db
.insertInto('person')
.values({ first_name: 'Jennifer', gender: 'female' })
.returning('id')
.executeTakeFirstOrThrow()

await db
.insertInto('pet')
.values({ name: 'Catto', species: 'cat', owner_id: id })
.execute()

const person = await db
.selectFrom('person')
.innerJoin('pet', 'pet.owner_id', 'person.id')
.select(['first_name', 'pet.name as pet_name'])
.where('person.id', '=', 1)
.where('person.id', '=', id)
.executeTakeFirst()

if (person) {
Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ export * from './operation-node/with-node.js'

export * from './dialect/database-introspector.js'

export * from './util/column-type.js'
export * from './util/compilable.js'
export * from './util/log.js'
export {
Expand Down
32 changes: 0 additions & 32 deletions src/kysely.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ import { MigrationModule } from './migration/migration.js'
import { QueryExecutor } from './query-executor/query-executor.js'
import { QueryCreator } from './query-creator.js'
import { KyselyPlugin } from './plugin/kysely-plugin.js'
import { GeneratedPlaceholder } from './util/type-utils.js'
import { GENERATED_PLACEHOLDER } from './util/generated-placeholder.js'
import { DefaultQueryExecutor } from './query-executor/default-query-executor.js'
import { DatabaseIntrospector } from './dialect/database-introspector.js'
import { freeze, isObject } from './util/object-utils.js'
Expand Down Expand Up @@ -135,34 +133,6 @@ export class Kysely<DB> extends QueryCreator<DB> {
return this.#props.dialect.createIntrospector(this.withoutPlugins())
}

/**
* A value to be used in place of columns that are generated in the database
* when inserting rows.
*
* ### Examples
*
* In this example the `Person` table has non-null properties `id` and `created_at`
* which are both automatically genereted by the database. Since their types are
* `number` and `string` respectively instead of `number | null` and `string | null`
* the `values` method requires you to give a value for them. the `generated`
* placeholder can be used in these cases.
*
* ```ts
* await db.insertInto('person')
* .values({
* id: db.generated,
* created_at: db.generated,
* first_name: 'Jennifer',
* last_name: 'Aniston',
* gender: 'female'
* })
* .execute()
* ```
*/
get generated(): GeneratedPlaceholder {
return GENERATED_PLACEHOLDER
}

/**
* Returns a {@link FunctionBuilder} that can be used to write type safe function
* calls.
Expand Down Expand Up @@ -215,7 +185,6 @@ export class Kysely<DB> extends QueryCreator<DB> {
* const catto = await db.transaction().execute(async (trx) => {
* const jennifer = await trx.insertInto('person')
* .values({
* id: db.generated,
* first_name: 'Jennifer',
* last_name: 'Aniston',
* })
Expand All @@ -226,7 +195,6 @@ export class Kysely<DB> extends QueryCreator<DB> {
*
* return await trx.insertInto('pet')
* .values({
* id: db.generated,
* user_id: jennifer.id,
* name: 'Catto',
* species: 'cat'
Expand Down
27 changes: 18 additions & 9 deletions src/parser/insert-values-parser.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,34 @@
import { ColumnNode } from '../operation-node/column-node.js'
import { PrimitiveValueListNode } from '../operation-node/primitive-value-list-node.js'
import { ValueListNode } from '../operation-node/value-list-node.js'
import { GeneratedPlaceholder } from '../util/type-utils.js'
import { isGeneratedPlaceholder } from '../util/generated-placeholder.js'
import { freeze, isPrimitive, PrimitiveValue } from '../util/object-utils.js'
import { ParseContext } from './parse-context.js'
import { parseValueExpression, ValueExpression } from './value-parser.js'
import { ValuesNode } from '../operation-node/values-node.js'
import {
NonNullableInsertKeys,
NullableInsertKeys,
InsertType,
} from '../util/column-type.js'

export type InsertObject<DB, TB extends keyof DB> = {
[C in keyof DB[TB]]: InsertValueExpression<DB, TB, DB[TB][C]>
[C in NonNullableInsertKeys<DB[TB]>]: ValueExpression<
DB,
TB,
InsertType<DB[TB][C]>
>
} & {
[C in NullableInsertKeys<DB[TB]>]?: ValueExpression<
DB,
TB,
InsertType<DB[TB][C]>
>
}

export type InsertObjectOrList<DB, TB extends keyof DB> =
| InsertObject<DB, TB>
| ReadonlyArray<InsertObject<DB, TB>>

type InsertValueExpression<DB, TB extends keyof DB, T> =
| ValueExpression<DB, TB, T>
| GeneratedPlaceholder

export function parseInsertObjectOrList(
ctx: ParseContext,
args: InsertObjectOrList<any, any>
Expand Down Expand Up @@ -48,7 +57,7 @@ function parseColumnNamesAndIndexes(
const cols = Object.keys(row)

for (const col of cols) {
if (!columns.has(col) && !isGeneratedPlaceholder(row[col])) {
if (!columns.has(col)) {
columns.set(col, columns.size)
}
}
Expand All @@ -72,7 +81,7 @@ function parseRowValues(
const columnIdx = columns.get(col)
const value = row[col]

if (columnIdx !== undefined && !isGeneratedPlaceholder(value)) {
if (columnIdx !== undefined) {
rowValues[columnIdx] = value
}
}
Expand Down
3 changes: 2 additions & 1 deletion src/parser/reference-parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
DynamicReferenceBuilder,
isDynamicReferenceBuilder,
} from '../dynamic/dynamic-reference-builder.js'
import { SelectType } from '../util/column-type.js'

export type ReferenceExpression<DB, TB extends keyof DB> =
| StringReference<DB, TB>
Expand All @@ -44,7 +45,7 @@ export type ExtractTypeFromReferenceExpression<
TB extends keyof DB,
RE
> = RE extends string
? ExtractTypeFromStringReference<DB, TB, RE>
? SelectType<ExtractTypeFromStringReference<DB, TB, RE>>
: RE extends RawBuilder<infer O>
? O
: RE extends (qb: any) => RawBuilder<infer O>
Expand Down
17 changes: 10 additions & 7 deletions src/parser/select-parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import {
AnyColumn,
AnyColumnWithTable,
ExtractColumnType,
RowType,
ValueType,
} from '../util/type-utils.js'
import { parseAliasedStringReference } from './reference-parser.js'
Expand All @@ -24,6 +23,7 @@ import {
AliasedComplexExpression,
parseAliasedComplexExpression,
} from './complex-expression-parser.js'
import { Selectable, SelectType } from '../util/column-type.js'

export type SelectExpression<DB, TB extends keyof DB> =
| AnyAliasedColumnWithTable<DB, TB>
Expand Down Expand Up @@ -56,14 +56,11 @@ export type SelectAllQueryBuilder<
TB extends keyof DB,
O,
S extends keyof DB
> = SelectQueryBuilder<DB, TB, O & RowType<DB, S>>
> = SelectQueryBuilder<DB, TB, O & AllSelection<DB, S>>

export type Selection<DB, TB extends keyof DB, SE> = {
[A in ExtractAliasFromSelectExpression<SE>]: ExtractTypeFromSelectExpression<
DB,
TB,
SE,
A
[A in ExtractAliasFromSelectExpression<SE>]: SelectType<
ExtractTypeFromSelectExpression<DB, TB, SE, A>
>
}

Expand Down Expand Up @@ -172,6 +169,12 @@ type ExtractTypeFromStringSelectExpression<
: never
: never

type AllSelection<DB, TB extends keyof DB> = Selectable<{
[C in AnyColumn<DB, TB>]: {
[T in TB]: C extends keyof DB[T] ? DB[T][C] : never
}[TB]
}>

export function parseSelectExpressionOrList(
ctx: ParseContext,
selection: SelectExpressionOrList<any, any>
Expand Down
3 changes: 2 additions & 1 deletion src/parser/update-set-parser.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { ColumnNode } from '../operation-node/column-node.js'
import { ColumnUpdateNode } from '../operation-node/column-update-node.js'
import { UpdateKeys, UpdateType } from '../util/column-type.js'
import { ParseContext } from './parse-context.js'
import { parseValueExpression, ValueExpression } from './value-parser.js'

export type MutationObject<DB, TB extends keyof DB> = {
[C in keyof DB[TB]]?: ValueExpression<DB, TB, DB[TB][C]>
[C in UpdateKeys<DB[TB]>]?: ValueExpression<DB, TB, UpdateType<DB[TB][C]>>
}

export function parseUpdateObject(
Expand Down
Loading

0 comments on commit a5e8c00

Please sign in to comment.