gas-stack/doc/sqlite-schema-rules-and-linter.md

4.2 KiB

SQLite Schema Rules and Linter

The sqlite_lint subcommand enforces some rules that the GAS stack considers best-practices.

All checks are enabled by default. Disabling checks isn't recommended; many GAS stack methodologies assume your schema is designed in accordance with these rules, and will be less effective if you don't follow them.

Currently the only way to disable them is setting an environment variable with the check name in capitals prefixed with INPUT_, e.g., INPUT_REQUIRE_NOT_NULL=false disables the require_not_null check.

INPUT_REQUIRE_NOT_NULL=false gas sqlite_lint <path/to/schema.sql>  # `require_not_null` check will be skipped

Running the linter

gas sqlite_lint <path/to/schema.sql>

Available Checks

This is a list of currently available checks.

require_not_null

Enforce that all columns should be marked as not null, unless they are foreign keys.

Explanation:

  • Nulls are a common source of unexpected bugs, because they're usually an invalid state but often get created by mistake (e.g., you forgot to set a value). Explicitly disabling nulls prevents such mistakes.
  • If the "natural zero value" is a valid value in your application and you explicitly need to distinguish it from "missing data", use an has_xyz or is_xyz_valid flag of some kind, rather than a nullable field.
    • This is usually unnecessary, because the natural zero-values 0 and "" (empty string) are usually sufficient to indicate "no value". This is called a "sentinel value", or "in-band null value", because you don't need a special data type (null) to declare absence of data.
  • Foreign keys are exempt in this check, because null is a special value the integrity checker uses to say "this row has no related item".

require_strict

Enforce that all tables should be marked as strict.

Explanation:

  • By default, SQLite tables are very loose with what values they accept, and don't enforce any type checking. "Strict" disables this "looseness", and enforces that inserted values match the stated type of the column.
  • "Strict" tables also limit to a small number of column types: int, integer, real, text, blob or any.
    • To represent dates / times, use Unix epoch times in milliseconds, and convert to formatted dates (and timezones) only when displaying the value to a user. This is the most portable and least bug-prone method to handle dates.

See more about "strict" tables in SQLite's documentation: https://sqlite.org/stricttables.html

forbid_int_type

Enforce that all columns should use integer type instead of int.

Explanation:

  • This is an extension of "strict" tables, which allow two redundant integer types, integer and int. This check standardizes the types further, permitting only integer.

require_explicit_primary_key

Enforce that all tables must have an explicitly declared primary key.

Explanation:

  • All tables need to have a primary key for storage reasons, so if you don't declare one, SQLite will auto-generate a hidden "rowid" column. Making it explicit (rowid or otherwise) improves schema readability.

See more about the special behavior of rowid in SQLite's documentation: https://sqlite.org/lang_createtable.html#rowid

require_explicit_rowid

Enforce that any table that's not declared without rowid has an explicit rowid integer primary key column.

Explanation:

  • In SQLite, all tables implicitly have a rowid column unless they are declared without rowid. Making it explicit improves schema readability.

See more about the without rowid modifier in SQLite's documentation: https://sqlite.org/withoutrowid.html

forbid_rowid_on_without_rowid_table

Enforce that without rowid tables don't have a rowid column.

Explanation:

  • This is pretty self explanatory. You can technically give a without rowid table a rowid column. But don't.

require_indexes_for_foreign_keys

Enforce that columns referenced by foreign keys must have indexes.

Explanation:

  • Foreign keys are usually used for joins. Joining on un-indexed columns is very slow. Ensuring that all foreign-key-referenced columns have indexes will greatly improve the performance of database operations.