@@ -24,6 +24,7 @@ that represent those database definitions.
2424 * [ Default representations for dates and UUIDs] ( #Default-representations-for-dates-and-UUIDs )
2525* [ Primary keyed tables] ( #Primary-keyed-tables )
2626* [ Ephemeral columns] ( #Ephemeral-columns )
27+ * [ Table definition tools] ( #Table-definition-tools )
2728
2829### Defining a table
2930
@@ -399,6 +400,138 @@ struct Book {
399400}
400401```
401402
403+ ### Table definition tools
404+
405+ This library does not come with any tools for actually constructing table definition queries,
406+ such as `CREATE TABLE`, `ALTER TABLE`, and so on. That is, there are no APIs for performing the
407+ following kinds of queries:
408+
409+ @Row {
410+ @Column {
411+ ```swift
412+ Reminder.createTable()
413+ // ⚠️ Theoretical API that does
414+ // not actually exist.
415+ ```
416+ }
417+ @Column {
418+ ```sql
419+ CREATE TABLE " reminders" (
420+ " id" INTEGER PRIMARY KEY AUTOINCREMENT,
421+ " title" TEXT NOT NULL,
422+ " isCompleted" INTEGER NOT NULL DEFAULT 0
423+ )
424+ ```
425+ }
426+ }
427+
428+ In fact, we recommend all changes to the schema of your database be executed as SQL strings using
429+ the [`#sql` macro](<doc:SafeSQLStrings>):
430+
431+ ```swift
432+ #sql(
433+ " " "
434+ CREATE TABLE "reminders " (
435+ " id" INTEGER PRIMARY KEY AUTOINCREMENT,
436+ " title" TEXT NOT NULL ,
437+ " isCompleted" INTEGER NOT NULL DEFAULT 0
438+ )
439+ " " "
440+ )
441+ ```
442+
443+ It may seem strange for us to recommend using SQL strings when the library provides such an
444+ expansive assortment of tools that make SQL more expressive, type-safe, and schema-safe. But there
445+ is a very good reason for this.
446+
447+ Through the lifetime of an application you will perform many migrations on your schema. You will
448+ add/remove tables, add/remove columns, add/remove indicies, add/remove constraints, and more.
449+ Each of these alterations to the schema make a snapshot of your entire database's schema that
450+ is frozen in that moment of time. Once a migration has been shipped and run on a user's device
451+ it should never be edited again. Therefore it is not appropriate to use the statically known
452+ symbols exposed by `@Table` to alter your database.
453+
454+ As a concrete example, suppose we _did_ have table definition tools. This would mean creating a
455+ table could be as simple as this:
456+
457+ ```swift
458+ @Table struct Reminder {
459+ let id: Int
460+ var name = " "
461+ }
462+
463+ migrator.migrate(" Create ' reminders' table" ) { db in
464+ // ⚠️ Theoretical 'createTable' API. Does not actually exist.
465+ try Reminder.createTable().execute(db)
466+ }
467+ ```
468+
469+ When your app is launched for the first time it will run this migration and make a record of it
470+ being run so that it is not ever run again.
471+
472+ But then a few days later you decide that you prefer `title` to `name` for the `Reminder` type,
473+ and so you hope that you can just rename the project, fix any compilation errors, and add a new
474+ migration:
475+
476+ ```diff
477+ @Table struct Reminder {
478+ let id: Int
479+ - var name = " "
480+ + var title = " "
481+ }
482+
483+ migrator.migrate(" Create ' reminders' table" ) { db in
484+ // ⚠️ Theoretical 'createTable' API. Does not actually exist.
485+ try Reminder.createTable().execute(db)
486+ }
487+ +migrator.migrate(" Rename ' name' to ' title' " ) { db in
488+ + // ⚠️ Theoretical 'rename(from:)' API. Does not actually exist.
489+ + try Reminder.title.rename(from: " name" ).execute(db)
490+ +}
491+ ```
492+
493+ Now when the app launches it rename the column in the database, and make a record that the migration
494+ has been run so that it is not ever run again.
495+
496+ This will work just fine for all users that have previously run the first migration. But any new
497+ users that run the whole suite of migrations at once will have the following SQL statements
498+ executed:
499+
500+ ```sql
501+ CREATE TABLE " reminders" (
502+ " id" INTEGER,
503+ " title" TEXT
504+ );
505+ ALTER TABLE " reminders" RENAME COLUMN " name" TO " title" ;
506+ ```
507+
508+ The second SQL statement fails because there is no " name" column. And the reason this is happening
509+ is because `Reminder.createTable()` must use the most current version of the schema where the field
510+ is " title" , not " name." This violates the principle that migrations should be snapshots of your
511+ database's schema frozen in time and should never be edited after shipping to your users. A side
512+ effect of violating this principle is that we now generate invalid SQL and run the risk of breaking
513+ our users' app.
514+
515+ If it worries you to write SQL strings by hand, then fear not! For a few reasons:
516+
517+ * Although this library aims to provide type-safe and schema-safe tools for writing SQL, it is
518+ not a goal to make it so that you _never_ write SQL strings. SQL is an amazing language that has
519+ stood the test of time, and you will be a better engineer for being able to write it from
520+ scratch. And sometimes, such as the case with table definitions, it is necessary to write SQL
521+ strings.
522+
523+ * It may seem dangerous to write SQL strings. After all, aren't they susceptible to SQL injection
524+ attacks and typos? The `#sql` macro protects you against any SQL injection attacks, and provides
525+ some basic linting to make sure your SQL is roughly correct. And typos are not common in table
526+ definition statements since an unexpect database schema is a very visible bug in your
527+ application, as opposed to a small part of a `SELECT` statement that is only run every once in
528+ awhile in your app.
529+
530+ So, we hope that you will consider it a _benefit_ that your application's schema will be defined and
531+ maintained as simple SQL strings. It's a simple format that everyone familiar with SQLite will
532+ understand, and it makes your application most resillient to the ever growing changes and demands on
533+ your application.
534+
402535## Topics
403536
404537### Schema
0 commit comments