Skip to content

Commit

Permalink
Nulls not distinct option (#770)
Browse files Browse the repository at this point in the history
* feat: added option nulls not distinct to addUniqueConstraint method, updated docs

* chore: cleanup

* chore: reverted site changes

* fix: use builder for unique constraint

* fix: unit test

* added 'nulls not distinct' modifier to addColumn

* feat: added 'nulls not distinct' to create index

* test: added unit test for alter table, add column

* feat: added nulls not distinct option for alter table, add unique constraint

* chore: cleanup

* chore: cleanup

* test: added typing checks for alter table and nullsNotDistinct

* Update src/schema/unique-constraint-builder.ts

Co-authored-by: Igal Klebanov <[email protected]>

* Update src/query-compiler/default-query-compiler.ts

Co-authored-by: Igal Klebanov <[email protected]>

* fix: pull request comments

* fix: moved quote for consistency with another unit test

* test: added positive unit tests, fixed formatting

---------

Co-authored-by: Igal Klebanov <[email protected]>
  • Loading branch information
Alex Vershinin and igalklebanov authored Nov 21, 2023
1 parent 31c5258 commit 4e40aa8
Show file tree
Hide file tree
Showing 13 changed files with 452 additions and 9 deletions.
1 change: 1 addition & 0 deletions src/operation-node/column-definition-node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export interface ColumnDefinitionNode extends OperationNode {
readonly unsigned?: boolean
readonly frontModifiers?: ReadonlyArray<OperationNode>
readonly endModifiers?: ReadonlyArray<OperationNode>
readonly nullsNotDistinct?: boolean
}

/**
Expand Down
1 change: 1 addition & 0 deletions src/operation-node/create-index-node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export interface CreateIndexNode extends OperationNode {
readonly using?: RawNode
readonly ifNotExists?: boolean
readonly where?: WhereNode
readonly nullsNotDistinct?: boolean
}

/**
Expand Down
3 changes: 3 additions & 0 deletions src/operation-node/operation-node-transformer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -433,6 +433,7 @@ export class OperationNodeTransformer {
generated: this.transformNode(node.generated),
frontModifiers: this.transformNodeList(node.frontModifiers),
endModifiers: this.transformNodeList(node.endModifiers),
nullsNotDistinct: node.nullsNotDistinct,
})
}

Expand Down Expand Up @@ -549,6 +550,7 @@ export class OperationNodeTransformer {
using: this.transformNode(node.using),
ifNotExists: node.ifNotExists,
where: this.transformNode(node.where),
nullsNotDistinct: node.nullsNotDistinct,
})
}

Expand Down Expand Up @@ -586,6 +588,7 @@ export class OperationNodeTransformer {
kind: 'UniqueConstraintNode',
columns: this.transformNodeList(node.columns),
name: this.transformNode(node.name),
nullsNotDistinct: node.nullsNotDistinct,
})
}

Expand Down
23 changes: 22 additions & 1 deletion src/operation-node/unique-constraint-node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,14 @@ export interface UniqueConstraintNode extends OperationNode {
readonly kind: 'UniqueConstraintNode'
readonly columns: ReadonlyArray<ColumnNode>
readonly name?: IdentifierNode
readonly nullsNotDistinct?: boolean
}

export type UniqueConstraintNodeProps = Omit<
Partial<UniqueConstraintNode>,
'kind'
>

/**
* @internal
*/
Expand All @@ -17,11 +23,26 @@ export const UniqueConstraintNode = freeze({
return node.kind === 'UniqueConstraintNode'
},

create(columns: string[], constraintName?: string): UniqueConstraintNode {
create(
columns: string[],
constraintName?: string,
nullsNotDistinct?: boolean
): UniqueConstraintNode {
return freeze({
kind: 'UniqueConstraintNode',
columns: freeze(columns.map(ColumnNode.create)),
name: constraintName ? IdentifierNode.create(constraintName) : undefined,
nullsNotDistinct,
})
},

cloneWith(
node: UniqueConstraintNode,
props: UniqueConstraintNodeProps
): UniqueConstraintNode {
return freeze({
...node,
...props,
})
},
})
16 changes: 15 additions & 1 deletion src/query-compiler/default-query-compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -604,6 +604,10 @@ export class DefaultQueryCompiler
this.append(' unique')
}

if (node.nullsNotDistinct) {
this.append(' nulls not distinct')
}

if (node.primaryKey) {
this.append(' primary key')
}
Expand Down Expand Up @@ -828,6 +832,10 @@ export class DefaultQueryCompiler
this.append(')')
}

if (node.nullsNotDistinct) {
this.append(' nulls not distinct')
}

if (node.where) {
this.append(' ')
this.visitNode(node.where)
Expand Down Expand Up @@ -898,7 +906,13 @@ export class DefaultQueryCompiler
this.append(' ')
}

this.append('unique (')
this.append('unique')

if (node.nullsNotDistinct) {
this.append(' nulls not distinct')
}

this.append(' (')
this.compileList(node.columns)
this.append(')')
}
Expand Down
15 changes: 13 additions & 2 deletions src/schema/alter-table-builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ import { AlterTableExecutor } from './alter-table-executor.js'
import { AlterTableAddForeignKeyConstraintBuilder } from './alter-table-add-foreign-key-constraint-builder.js'
import { AlterTableDropConstraintBuilder } from './alter-table-drop-constraint-builder.js'
import { PrimaryConstraintNode } from '../operation-node/primary-constraint-node.js'
import {
UniqueConstraintNodeBuilder,
UniqueConstraintNodeBuilderCallback,
} from './unique-constraint-builder.js'

/**
* This builder can be used to create a `alter table` query.
Expand Down Expand Up @@ -155,13 +159,20 @@ export class AlterTableBuilder implements ColumnAlteringInterface {
*/
addUniqueConstraint(
constraintName: string,
columns: string[]
columns: string[],
build: UniqueConstraintNodeBuilderCallback = noop
): AlterTableExecutor {
const uniqueConstraintBuilder = build(
new UniqueConstraintNodeBuilder(
UniqueConstraintNode.create(columns, constraintName)
)
)

return new AlterTableExecutor({
...this.#props,
node: AlterTableNode.cloneWithTableProps(this.#props.node, {
addConstraint: AddConstraintNode.create(
UniqueConstraintNode.create(columns, constraintName)
uniqueConstraintBuilder.toOperationNode()
),
}),
})
Expand Down
30 changes: 30 additions & 0 deletions src/schema/column-definition-builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,36 @@ export class ColumnDefinitionBuilder implements OperationNodeSource {
)
}

/**
* Adds `nulls not distinct` specifier.
* Should be used with `unique` constraint.
*
* This only works on some dialects like PostgreSQL.
*
* ### Examples
*
* ```ts
* db.schema.createTable('person')
* .addColumn('id', 'integer', col => col.primaryKey())
* .addColumn('first_name', 'varchar(30)', col => col.unique().nullsNotDistinct())
* .execute()
* ```
*
* The generated SQL (PostgreSQL):
*
* ```sql
* create table "person" (
* "id" integer primary key,
* "first_name" varchar(30) unique nulls not distinct
* )
* ```
*/
nullsNotDistinct(): ColumnDefinitionBuilder {
return new ColumnDefinitionBuilder(
ColumnDefinitionNode.cloneWith(this.#node, { nullsNotDistinct: true })
)
}

/**
* This can be used to add any additional SQL to the end of the column definition.
*
Expand Down
31 changes: 31 additions & 0 deletions src/schema/create-index-builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,37 @@ export class CreateIndexBuilder<C = never>
})
}

/**
* Adds `nulls not distinct` specifier to index.
* This only works on some dialects like PostgreSQL.
*
* ### Examples
*
* ```ts
* db.schema.createIndex('person_first_name_index')
* .on('person')
* .column('first_name')
* .nullsNotDistinct()
* .execute()
* ```
*
* The generated SQL (PostgreSQL):
*
* ```sql
* create index "person_first_name_index"
* on "test" ("first_name")
* nulls not distinct;
* ```
*/
nullsNotDistinct(): CreateIndexBuilder<C> {
return new CreateIndexBuilder({
...this.#props,
node: CreateIndexNode.cloneWith(this.#props.node, {
nullsNotDistinct: true,
}),
})
}

/**
* Specifies the table for the index.
*/
Expand Down
22 changes: 19 additions & 3 deletions src/schema/create-table-builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ import { CheckConstraintNode } from '../operation-node/check-constraint-node.js'
import { parseTable } from '../parser/table-parser.js'
import { parseOnCommitAction } from '../parser/on-commit-action-parse.js'
import { Expression } from '../expression/expression.js'
import {
UniqueConstraintNodeBuilder,
UniqueConstraintNodeBuilderCallback,
} from './unique-constraint-builder.js'
import { parseExpression } from '../parser/expression-parser.js'

/**
Expand Down Expand Up @@ -104,7 +108,7 @@ export class CreateTableBuilder<TB extends string, C extends string = never>
* ```
*
* With this method, it's once again good to remember that Kysely just builds the
* query and doesn't provide the same API for all databses. For example, some
* query and doesn't provide the same API for all databases. For example, some
* databases like older MySQL don't support the `references` statement in the
* column definition. Instead foreign key constraints need to be defined in the
* `create table` query. See the next example:
Expand Down Expand Up @@ -186,16 +190,28 @@ export class CreateTableBuilder<TB extends string, C extends string = never>
* ```ts
* addUniqueConstraint('first_name_last_name_unique', ['first_name', 'last_name'])
* ```
*
* In dialects such as PostgreSQL you can specify `nulls not distinct` as follows:
* ```ts
* addUniqueConstraint('first_name_last_name_unique', ['first_name', 'last_name'], (builder) => builder.nullsNotDistinct())
* ```
*/
addUniqueConstraint(
constraintName: string,
columns: C[]
columns: C[],
build: UniqueConstraintNodeBuilderCallback = noop
): CreateTableBuilder<TB, C> {
const uniqueConstraintBuilder = build(
new UniqueConstraintNodeBuilder(
UniqueConstraintNode.create(columns, constraintName)
)
)

return new CreateTableBuilder({
...this.#props,
node: CreateTableNode.cloneWithConstraint(
this.#props.node,
UniqueConstraintNode.create(columns, constraintName)
uniqueConstraintBuilder.toOperationNode()
),
})
}
Expand Down
35 changes: 35 additions & 0 deletions src/schema/unique-constraint-builder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { OperationNodeSource } from '../operation-node/operation-node-source.js'
import { UniqueConstraintNode } from '../operation-node/unique-constraint-node.js'
import { preventAwait } from '../util/prevent-await.js'

export class UniqueConstraintNodeBuilder implements OperationNodeSource {
readonly #node: UniqueConstraintNode

constructor(node: UniqueConstraintNode) {
this.#node = node
}

toOperationNode(): UniqueConstraintNode {
return this.#node
}

/**
* Adds `nulls not distinct` to the unique constraint definition
*
* Supported by PostgreSQL dialect only
*/
nullsNotDistinct(): UniqueConstraintNodeBuilder {
return new UniqueConstraintNodeBuilder(
UniqueConstraintNode.cloneWith(this.#node, { nullsNotDistinct: true })
)
}
}

preventAwait(
UniqueConstraintNodeBuilder,
"don't await UniqueConstraintNodeBuilder instances directly."
)

export type UniqueConstraintNodeBuilderCallback = (
builder: UniqueConstraintNodeBuilder
) => UniqueConstraintNodeBuilder
Loading

1 comment on commit 4e40aa8

@vercel
Copy link

@vercel vercel bot commented on 4e40aa8 Nov 21, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

kysely – ./

www.kysely.dev
kysely.dev
kysely-kysely-team.vercel.app
kysely-git-master-kysely-team.vercel.app

Please sign in to comment.