Database changes checklist
Write migration
When you write the migration, leave the template in place.
Up migration
- Number each action of your up migration with a comment (
UP 1,UP 2, ...) - Up migrations should include a migration path for existing data
- We prefer non-nullable columns -- if your column is nullable, add a comment explaining why
- New columns must include reasonable defaults (hardcoded or derived from existing data)
- Do not use
.enum()-- we prefer virtual enums (TEXT columns that are validated in business logic). See https://internal-docs.dreambound.com/engineering/practices/enums. Also add a Schema and GraphQLEnum export in /models/enums. - Array types should be implemented like
table.specificType("columnName", "text[]").defaultTo("{}").notNullable()
Down migration
- Each UP action must be paired, in reverse, with an UNDO action
- If you do not have a paired action, you must leave the comment and explain why
/**
* UNDO 2
* We don't need to add back the `snacks` table because I ate all the snacks
*/
Some lessons we have learned the hard way:
- Making something non-null? If a nullable column becomes non-null, this migration will fail. This is especially hard to test if it happens during a down migration. You will need a plan for what to do with these values.
Verify down migration
Up and down migrations should be exactly reversible in schema or you will cause other developers a lot of pain.
When writing your down migration, always reference what is in the database (i.e. the ground truth), not a migration.
For each of these checks,
- Run your migration UP
- Copy the result
- Run your migration DOWN, then UP
- Copy the result -- This should be identical (ordering differences OK)
Verify columns and types
Are columns and types exactly the same?
SELECT
column_name,
column_default,
is_nullable,
data_type,
character_maximum_length,
datetime_precision,
udt_name
FROM information_schema.columns
WHERE table_name = '<TABLE NAME>';
Verify indexes
Are indexes exactly the same? Be sure to check this because indexes are sometimes non-obvious.
SELECT
tablename,
indexname,
indexdef
FROM pg_indexes
WHERE tablename = '<TABLE NAME>';
If renaming a column, make sure you also rename any associated indexes. These are not automatically renamed!
Verify foreign keys and constraints
SELECT
conrelid::regclass table_name,
conname foreign_key,
pg_get_constraintdef(oid) constraint
FROM pg_constraint
WHERE conrelid::regclass::text = '<TABLE NAME>';
Importing constants
Don't import enums/constants, redefine them in the migration so that if they change, the migration doesn't break.
Migration sanity checks
- Run your migration up, then down, then up.
Objection and GraphQL
A new column must be enforced in 3 places:
- On the Objection model, its TypeScript type
- On the Objection model, its JSONSchema definition
- GraphQL enum / scalar
Is your new column a JSON column? If so, reconsider and ensure this makes sense -- JSON columns are harder to type and validate, especially if they are nullable.
- Add it to
jsonAttributesarray. If you do not do this, Objection won't parse data correctly. - If your column contains multiple specific data points, they should be independently resolved in GraphQL (not as a generic JSON blob).
Implementations
- TypeScript types
- JSONSchema definition (in
jsonSchema)- Include if new columns should be required
- Filters
- Do we need to filter on this, internally or on GraphQL? Add relevant filters to
filters.
- Do we need to filter on this, internally or on GraphQL? Add relevant filters to
- Sortable
- Do we need to sort on this? Add column to
sortable.
- Do we need to sort on this? Add column to
Sanity checks
- Test that the column resolves correctly.
- Does this column need particular authorization rules that are different from its parent table?
Database seeder
- Implement at least 3 examples of this column.
- Prefer using a dynamic seeder (
faker) instead of hard-coding values. - If the column is nullable, implement a null example as well.
Airtable
- Does this column need to get sent to Airtable? This is usually Yes. File a ticket to implement this.
Front-ends
- As a rule of thumb (with occasional exceptions), all columns should be accessible and editable from at least the Admin Portal.
- File tickets to implement view/list, add, edit, and delete.