[OCaml] High Intensity Training Online
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.
lib/persistence/migration_data.ml
@@ -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
@@ -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;