refactor consolidate migrations into one initial schema

The deployed database is being reset, so the seven incremental migrations -- with their drop-and-recreate churn from schema reshuffles -- have no file left to migrate. Collapse them into a single version-1 'initial schema' migration that creates the final tables directly. Verified a fresh connect creates all ten application tables and records one ledger row. Also name Migration_data's primary type t, the module convention, and re-export it from Migrations as the stable 'migration' name.

Commit
ecf80d4559c20a4e539dadb3de7d1fd29649e677
Author
Marius Peter <dev@marius-peter.com>
Author date
Committer
Marius Peter <dev@marius-peter.com>
Committer date
Changed files
lib/persistence/migration_data.ml
index 990913a4..54955b03 100644..100644
@@ -3,9 +3,14 @@
3 3 database file already carries. {!Migrations} reads this and applies it.
4 4
5 5 Keeping the SQL here, apart from the apply logic, means a schema change edits
6 Removed: one place and never touches OCaml control flow. *)
6 Added: one place and never touches OCaml control flow.
7 7
8 Removed: type migration = { version : int; name : string; statements : string list }
8 Added: The seven incremental migrations that grew this schema were consolidated into
9 Added: a single initial migration when the deployed database was reset: there was no
10 Added: file left to migrate, so the drop-and-recreate churn of the old chain served
11 Added: no purpose. Add the next schema change as version 2. *)
12 Added:
13 Added: type t = { version : int; name : string; statements : string list }
9 14 (** One migration: a version, a human name, and the ordered statements it runs.
10 15 *)
11 16
@@ -53,13 +58,6 @@
53 58 expires_at REAL NOT NULL,
54 59 payload TEXT NOT NULL
55 60 )|};
56 Removed: ];
57 Removed: };
58 Removed: {
59 Removed: version = 2;
60 Removed: name = "subjective feedback";
61 Removed: statements =
62 Removed: [
63 61 {|CREATE TABLE IF NOT EXISTS feedback (
64 62 trainee_id TEXT NOT NULL,
65 63 seq INTEGER NOT NULL,
@@ -68,13 +66,6 @@
68 66 )|};
69 67 {|CREATE INDEX IF NOT EXISTS feedback_by_trainee
70 68 ON feedback (trainee_id, seq DESC)|};
71 Removed: ];
72 Removed: };
73 Removed: {
74 Removed: version = 3;
75 Removed: name = "application feedback";
76 Removed: statements =
77 Removed: [
78 69 {|CREATE TABLE IF NOT EXISTS app_feedback (
79 70 trainee_id TEXT NOT NULL,
80 71 seq INTEGER NOT NULL,
@@ -84,13 +75,6 @@
84 75 )|};
85 76 {|CREATE INDEX IF NOT EXISTS app_feedback_by_trainee
86 77 ON app_feedback (trainee_id, seq DESC)|};
87 Removed: ];
88 Removed: };
89 Removed: {
90 Removed: version = 4;
91 Removed: name = "application feedback votes";
92 Removed: statements =
93 Removed: [
94 78 {|CREATE TABLE IF NOT EXISTS app_feedback_vote (
95 79 feedback_trainee_id TEXT NOT NULL,
96 80 feedback_seq INTEGER NOT NULL,
@@ -99,89 +83,13 @@
99 83 )|};
100 84 {|CREATE INDEX IF NOT EXISTS app_feedback_vote_by_voter
101 85 ON app_feedback_vote (voter_id)|};
102 Removed: ];
103 Removed: };
104 Removed: {
105 Removed: version = 5;
106 Removed: name = "ISO 8601 UTC timestamps";
107 Removed: statements =
108 Removed: [
109 Removed: {|DROP TABLE IF EXISTS app_feedback_vote|};
110 Removed: {|DROP TABLE IF EXISTS app_feedback|};
111 Removed: {|DROP TABLE IF EXISTS feedback|};
112 Removed: {|DROP TABLE IF EXISTS workout|};
113 Removed: {|DROP TABLE IF EXISTS in_progress|};
114 Removed: {|CREATE TABLE IF NOT EXISTS in_progress (
115 Removed: trainee_id TEXT PRIMARY KEY,
116 Removed: encoded TEXT NOT NULL
117 Removed: )|};
118 Removed: {|CREATE TABLE IF NOT EXISTS workout (
119 Removed: id TEXT PRIMARY KEY,
120 Removed: trainee_id TEXT NOT NULL,
121 Removed: seq INTEGER NOT NULL,
122 Removed: encoded TEXT NOT NULL
123 Removed: )|};
124 Removed: {|CREATE INDEX IF NOT EXISTS workout_by_trainee
125 Removed: ON workout (trainee_id, seq DESC)|};
126 Removed: {|CREATE TABLE IF NOT EXISTS feedback (
127 Removed: trainee_id TEXT NOT NULL,
128 Removed: seq INTEGER NOT NULL,
129 Removed: encoded TEXT NOT NULL,
130 Removed: PRIMARY KEY (trainee_id, seq)
131 Removed: )|};
132 Removed: {|CREATE INDEX IF NOT EXISTS feedback_by_trainee
133 Removed: ON feedback (trainee_id, seq DESC)|};
134 Removed: {|CREATE TABLE IF NOT EXISTS app_feedback (
135 Removed: trainee_id TEXT NOT NULL,
136 Removed: seq INTEGER NOT NULL,
137 Removed: submitted_at TEXT NOT NULL,
138 Removed: message TEXT NOT NULL,
139 Removed: PRIMARY KEY (trainee_id, seq)
140 Removed: )|};
141 Removed: {|CREATE INDEX IF NOT EXISTS app_feedback_by_trainee
142 Removed: ON app_feedback (trainee_id, seq DESC)|};
143 Removed: {|CREATE TABLE IF NOT EXISTS app_feedback_vote (
144 Removed: feedback_trainee_id TEXT NOT NULL,
145 Removed: feedback_seq INTEGER NOT NULL,
146 Removed: voter_id TEXT NOT NULL,
147 Removed: PRIMARY KEY (feedback_trainee_id, feedback_seq, voter_id)
148 Removed: )|};
149 Removed: {|CREATE INDEX IF NOT EXISTS app_feedback_vote_by_voter
150 Removed: ON app_feedback_vote (voter_id)|};
151 Removed: ];
152 Removed: };
153 Removed: {
154 Removed: version = 6;
155 Removed: name = "five-point feedback scores";
156 Removed: (* Subjective feedback now encodes levels as scores 1..5, so rows written
157 Removed: with the old below/usual/above codes no longer decode. Feedback is
158 Removed: observed, not authored, so the domain permits dropping it rather than
159 Removed: migrating it. *)
160 Removed: statements =
161 Removed: [
162 Removed: {|DROP TABLE IF EXISTS feedback|};
163 Removed: {|CREATE TABLE IF NOT EXISTS feedback (
164 Removed: trainee_id TEXT NOT NULL,
165 Removed: seq INTEGER NOT NULL,
166 Removed: encoded TEXT NOT NULL,
167 Removed: PRIMARY KEY (trainee_id, seq)
168 Removed: )|};
169 Removed: {|CREATE INDEX IF NOT EXISTS feedback_by_trainee
170 Removed: ON feedback (trainee_id, seq DESC)|};
171 Removed: ];
172 Removed: };
173 Removed: {
174 Removed: version = 7;
175 Removed: name = "workout import batches and promoted records";
176 Removed: (* An import batch is provisional: it holds the raw export source and the
177 Removed: cleaning done so far, encoded as one snapshot blob. A promoted record
178 Removed: is canonical evidence with an independent lifecycle, so it carries no
179 Removed: foreign key to its batch — deleting the batch must never cascade to the
180 Removed: record. The batch's [imported_at] is stored as a column so listing can
181 Removed: order without decoding every blob. A record is keyed by (trainee,
182 Removed: workout) so a re-promotion replaces it. *)
183 Removed: statements =
184 Removed: [
86 Added: (* An import batch is provisional: it holds the raw export source and
87 Added: the cleaning done so far, encoded as one snapshot blob. A promoted
88 Added: record is canonical evidence with an independent lifecycle, so it
89 Added: carries no foreign key to its batch — deleting the batch must never
90 Added: cascade to the record. The batch's [imported_at] is a column so
91 Added: listing can order without decoding every blob. A record is keyed by
92 Added: (trainee, workout) so a re-promotion replaces it. *)
185 93 {|CREATE TABLE IF NOT EXISTS import_batch (
186 94 trainee_id TEXT NOT NULL,
187 95 batch_id TEXT NOT NULL,
lib/persistence/migrations.ml
index 2c1588e8..50beef12 100644..100644
@@ -3,7 +3,7 @@
3 3 logic that runs them: the ledger check that makes applying idempotent, and
4 4 the per-migration transaction. *)
5 5
6 Removed: type migration = Migration_data.migration = {
6 Added: type migration = Migration_data.t = {
7 7 version : int;
8 8 name : string;
9 9 statements : string list;