info:To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
---
# Adding foreign key constraint to an existing column
# Add a foreign key constraint to an existing column
Foreign keys help ensure consistency between related database tables. The current database review process **always** encourages you to add [foreign keys](../foreign_keys.md) when creating tables that reference records from other tables.
Foreign keys ensure consistency between related database tables. The current database review process **always** encourages you to add [foreign keys](../foreign_keys.md) when creating tables that reference records from other tables.
Starting with Rails version 4, Rails includes migration helpers to add foreign key constraints to database tables. Before Rails 4, the only way for ensuring some level of consistency was the [`dependent`](https://guides.rubyonrails.org/association_basics.html#options-for-belongs-to-dependent) option within the association definition. Ensuring data consistency on the application level could fail in some unfortunate cases, so we might end up with inconsistent data in the table. This is mostly affecting older tables, where we simply didn't have the framework support to ensure consistency on the database level. These data inconsistencies can easily cause unexpected application behavior or bugs.
Starting with Rails version 4, Rails includes migration helpers to add foreign key constraints
to database tables. Before Rails 4, the only way for ensuring some level of consistency was the
option in the association definition. Ensuring data consistency on the application level could fail
in some unfortunate cases, so we might end up with inconsistent data in the table. This mostly affects
older tables, where we didn't have the framework support to ensure consistency on the database level.
These data inconsistencies can cause unexpected application behavior or bugs.
Adding a foreign key to an existing database column requires database structure changes and potential data changes. In case the table is in use, we should always assume that there is inconsistent data.
...
...
@@ -45,7 +51,7 @@ class Email < ActiveRecord::Base
end
```
Problem: when the user is removed, the email records related to the removed user will stay in the `emails` table:
Problem: when the user is removed, the email records related to the removed user stays in the `emails` table:
```ruby
user=User.find(1)
...
...
@@ -83,11 +89,13 @@ Avoid using the `add_foreign_key` constraint more than once per migration file,
#### Data migration to fix existing records
The approach here depends on the data volume and the cleanup strategy. If we can easily find "invalid" records by doing a simple database query and the record count is not that high, then the data migration can be executed within a Rails migration.
The approach here depends on the data volume and the cleanup strategy. If we can find "invalid"
records by doing a database query and the record count is not high, then the data migration can
be executed in a Rails migration.
In case the data volume is higher (>1000 records), it's better to create a background migration. If unsure, please contact the database team for advice.
Example for cleaning up records in the `emails` table within a database migration:
Example for cleaning up records in the `emails` table in a database migration:
@@ -6,7 +6,10 @@ info: To determine the technical writer assigned to the Stage/Group associated w
# Constraints naming conventions
The most common option is to let Rails pick the name for database constraints and indexes or let PostgreSQL use the defaults (when applicable). However, when needing to define custom names in Rails or working in Go applications where no ORM is used, it is important to follow strict naming conventions to improve consistency and discoverability.
The most common option is to let Rails pick the name for database constraints and indexes or let
PostgreSQL use the defaults (when applicable). However, when defining custom names in Rails, or
working in Go applications where no ORM is used, it is important to follow strict naming conventions
to improve consistency and discoverability.
The table below describes the naming conventions for custom PostgreSQL constraints.
The intent is not to retroactively change names in existing databases but rather ensure consistency of future changes.
@@ -36,7 +36,8 @@ Keyset pagination works without any configuration for simple ActiveRecord querie
- Order by one column.
- Order by two columns, where the last column is the primary key.
The library can detect nullable and non-distinct columns and based on these, it will add extra ordering using the primary key. This is necessary because keyset pagination expects distinct order by values:
The library detects nullable and non-distinct columns and based on these, adds extra ordering
using the primary key. This is necessary because keyset pagination expects distinct order by values:
```ruby
Project.order(:created_at).keyset_paginate.records# ORDER BY created_at, id
...
...
@@ -79,7 +80,7 @@ cursor = paginator.cursor_for_next_page # encoded column attributes for the next
paginator=Project.order(:name).keyset_paginate(cursor: cursor).records# loading the next page
```
Since keyset pagination does not support page numbers, we are restricted to go to the following pages:
Because keyset pagination does not support page numbers, we are restricted to go to the following pages:
- Next page
- Previous page
...
...
@@ -111,7 +112,8 @@ In the HAML file, we can render the records:
The performance of the keyset pagination depends on the database index configuration and the number of columns we use in the `ORDER BY` clause.
In case we order by the primary key (`id`), then the generated queries will be efficient since the primary key is covered by a database index.
In case we order by the primary key (`id`), then the generated queries are efficient because
the primary key is covered by a database index.
When two or more columns are used in the `ORDER BY` clause, it's advised to check the generated database query and make sure that the correct index configuration is used. More information can be found on the [pagination guideline page](pagination_guidelines.md#index-coverage).
...
...
@@ -149,7 +151,9 @@ puts paginator2.records.to_a # UNION query
## Complex order configuration
Common `ORDER BY` configurations will be handled by the `keyset_paginate` method automatically so no manual configuration is needed. There are a few edge cases where order object configuration is necessary:
Common `ORDER BY` configurations are handled by the `keyset_paginate` method automatically
so no manual configuration is needed. There are a few edge cases where order object
The `keyset_paginate` method raises an error because the order value on the query is a custom SQL string and not an [`Arel`](https://www.rubydoc.info/gems/arel) AST node. The keyset library cannot automatically infer configuration values from these kinds of queries.
To make keyset pagination work, we need to configure custom order objects, to do so, we need to collect information about the order columns:
To make keyset pagination work, we must configure custom order objects, to do so, we must
collect information about the order columns:
-`relative_position` can have duplicated values since no unique index is present.
-`relative_position` can have null values because we don't have a not null constraint on the column. For this, we need to determine where will we see NULL values, at the beginning of the resultset or the end (`NULLS LAST`).
- Keyset pagination requires distinct order columns, so we'll need to add the primary key (`id`) to make the order distinct.
- Jumping to the last page and paginating backwards actually reverses the `ORDER BY` clause. For this, we'll need to provide the reversed `ORDER BY` clause.
-`relative_position` can have duplicated values because no unique index is present.
-`relative_position` can have null values because we don't have a not null constraint on the column. For this, we must determine where we see NULL values, at the beginning of the result set, or the end (`NULLS LAST`).
- Keyset pagination requires distinct order columns, so we must add the primary key (`id`) to make the order distinct.
- Jumping to the last page and paginating backwards actually reverses the `ORDER BY` clause. For this, we must provide the reversed `ORDER BY` clause.
Example:
...
...
@@ -206,7 +211,8 @@ scope.keyset_paginate.records # works
### Function-based ordering
In the following example, we multiply the `id` by 10 and ordering by that value. Since the `id` column is unique, we need to define only one column:
In the following example, we multiply the `id` by 10 and order by that value. Because the `id`
column is unique, we define only one column:
```ruby
order=Gitlab::Pagination::Keyset::Order.build([
...
...
@@ -233,7 +239,8 @@ The `add_to_projections` flag tells the paginator to expose the column expressio
### `iid` based ordering
When ordering issues, the database ensures that we'll have distinct `iid` values within a project. Ordering by one column is enough to make the pagination work if the `project_id` filter is present:
When ordering issues, the database ensures that we have distinct `iid` values in a project.
Ordering by one column is enough to make the pagination work if the `project_id` filter is present: