Limitations & Gotchas

SQLite

Chapter 9 · Limitations & Gotchas

Eight chapters have made a fair case for SQLite's own real strengths. This chapter covers its genuine limitations with the same honesty — closing out sqlite1-1's own roadmap entirely.

No User/Permission System — A Structural Consequence, Not an Oversight

sqlite1-1's own "no server" model means there's genuinely nothing analogous to MySQL's user accounts or Postgres's own roles — there's no server process to authenticate against in the first place, so the entire concept of database-level user permissions has no place to live in SQLite's own architecture.

In practice, access control for a SQLite database is entirely a matter of operating-system file permissions (who can read or write the actual .db file) plus whatever the application itself chooses to implement. This is a genuinely different security model, not simply a lesser one — but it does mean the application and its deployment take on responsibility that MySQL/Postgres would otherwise absorb at the database layer itself. This is exactly what makes sqlite1-7's own "database-level per-user access control" disqualifying criterion structurally true, not just an arbitrary rule.

Historically Limited ALTER TABLE Support

SQLite's ALTER TABLE has real, historical, genuine limitations compared to MySQL/Postgres. For a long time, it could only rename a table, add a column, or rename a column — it could not drop a column, change a column's type, or add/remove constraints directly.

SQLite 3.35 (2021) added DROP COLUMN support — a real, recent improvement, worth naming honestly, echoing sqlite1-3's own STRICT-tables-as-a-recent-fix pattern.

For anything still beyond what ALTER TABLE supports, the classic, genuinely accepted SQLite idiom — not a hack — is: create a new table with the desired schema, copy the data over, drop the old table, and rename the new one into place.

No Procedural Language Equivalent to PL/pgSQL

postgres1-8 covered PL/pgSQL (and Postgres's own pluggable multi-language extensibility); MySQL has its own stored-procedure dialect. SQLite has neither. It does support triggers, but trigger bodies are limited to ordinary SQL statements — not a full procedural language with variables, loops, or control flow.

This is a genuine, structural limitation rather than just "SQLite being simpler." Embedding logic inside the database engine itself doesn't fit SQLite's own architecture naturally, since the entire point of SQLite is that the application already has direct, in-process access to the data. Most logic that would live in a stored procedure in MySQL/Postgres simply lives in ordinary application code instead when using SQLite — a genuinely different, not necessarily worse, division of responsibility.

Type Affinity Gotchas, Revisited

sqlite1-3's own warn-box already covered this in depth: mixed storage classes in one column can produce surprising sort and comparison results, since SQLite orders storage classes in a fixed sequence rather than comparing mixed values as one type. Worth restating briefly here as this chapter closes the loop on it, per sqlite1-1's own roadmap.

A Few Other Honest, Smaller Limitations

  • Historically limited RIGHT JOIN/FULL OUTER JOIN support — modern SQLite has added support for both, worth an accurate, current note rather than an outdated claim.
  • No native network/inet types the way postgres1-3 covered for Postgres.
  • Database size is practically capped by available disk, though the theoretical limit (around 281 TB) is enormous — not an actual practical concern for the vast majority of real use cases.
A single file means a single point of physical failure
Since the entire database lives in one file, per sqlite1-1's own "a database is a file" material, damage to that one file — a bad disk sector, a filesystem bug, non-atomic writes on a network filesystem — can affect the whole database at once. A client-server system typically has corruption contained or recoverable via replicas, per postgres1-11's own replication material; SQLite's own single-file model has no built-in equivalent. This is a genuinely different kind of risk than anything else in this chapter, and it's especially relevant to the "SQLite as a production server-side database" pattern from sqlite1-6 — real backups (covered lightly via sqlite1-1's own file-copy material) remain genuinely important here.
sqlite1-1's own roadmap, fully closed
This is the last item from sqlite1-1's own roadmap table. sqlite1-10 is the capstone, bringing every chapter's own material together into one real, working project.

Hands-On Exercises

Exercise 1

Explain why SQLite has no user/permission system, tying your answer to sqlite1-1's own "no server" architecture, and explain what fills that gap instead.

📄 View solution
Exercise 2

Explain SQLite's historical ALTER TABLE limitations and describe the "create new table, copy data, drop old, rename" workaround pattern, and explain why SQLite 3.35's DROP COLUMN addition is a genuine, recent improvement worth naming honestly.

📄 View solution
Exercise 3

Using this chapter's own warn-box, explain the single-file corruption risk and why it's a genuinely different kind of risk than a client-server database with replication (postgres1-11) would typically face — tie your answer back to sqlite1-1's own "database is a file" material.

📄 View solution

Chapter 9 Quick Reference

  • No user/permission system — a structural consequence of having no server process to authenticate against; OS file permissions + application logic fill the gap
  • ALTER TABLE historically limited (rename table/add column/rename column only); DROP COLUMN added in 3.35 (2021); "new table, copy, drop, rename" is the standard, accepted idiom for anything else
  • No PL/pgSQL/stored-procedure equivalent — trigger bodies are plain SQL only; logic lives in application code instead
  • Type affinity gotchas from sqlite1-3 revisited — mixed storage classes, fixed sort order
  • Smaller notes: RIGHT/FULL OUTER JOIN now supported in modern SQLite, no native network types, a huge but real practical size ceiling
  • Single-file model means single-point physical-failure risk — no built-in replica recovery the way postgres1-11 covers; real backups matter
  • Next chapter: Capstone — Building a Local-First CLI Tool With SQLite