Skip to content

Commit ca966e1

Browse files
Docs about table definitions. (pointfreeco#55)
* Docs about table definitions. * Update DefiningYourSchema.md --------- Co-authored-by: Stephen Celis <[email protected]>
1 parent c4644ac commit ca966e1

File tree

1 file changed

+133
-0
lines changed

1 file changed

+133
-0
lines changed

Sources/StructuredQueriesCore/Documentation.docc/Articles/DefiningYourSchema.md

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)