Skip to content

Commit 2dd637d

Browse files
committed
Address Adam's feedback
1 parent e08d75d commit 2dd637d

5 files changed

Lines changed: 62 additions & 48 deletions

File tree

main/MigrationTool.hs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,10 +71,11 @@ readApiFile file = fmap (parseAPIWithChangelog file (0,0)) (readFile file)
7171
data ChangeTag = None
7272
deriving (Read, Show)
7373

74-
customMigrations :: CustomMigrations JS.Object JS.Value ChangeTag ChangeTag ChangeTag
74+
customMigrations :: CustomMigrations JS.Object JS.Value ChangeTag ChangeTag ChangeTag ChangeTag
7575
customMigrations = CustomMigrations (nope JS.Object) (const noSchemaChanges)
7676
(nope id) (const noSchemaChanges)
7777
(nope id)
78+
(nope id)
7879
where
7980
nope toVal _ v = Left (CustomMigrationError "No custom migrations defined" (toVal v))
8081

src/Data/API/Changes.hs

Lines changed: 45 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -94,11 +94,11 @@ import Safe
9494
-- custom migration tags in the changelog, as generated by
9595
-- 'generateMigrationKind'.
9696

97-
migrateDataDump :: (Read db, Read rec, Read fld)
97+
migrateDataDump :: (Read db, Read rec, Read alt, Read fld)
9898
=> (API, Version) -- ^ Starting schema and version
9999
-> (API, VersionExtra) -- ^ Ending schema and version
100100
-> APIChangelog -- ^ Log of changes, containing both versions
101-
-> CustomMigrations JS.Object JS.Value db rec fld -- ^ Custom migration functions
101+
-> CustomMigrations JS.Object JS.Value db rec alt fld -- ^ Custom migration functions
102102
-> TypeName -- ^ Name of the dataset's type
103103
-> DataChecks -- ^ How thoroughly to validate changes
104104
-> JS.Value -- ^ Dataset to be migrated
@@ -110,11 +110,11 @@ migrateDataDump startApi endApi changelog custom root chks db = do
110110
db' <- applyChangesToDatabase root custom' db changes ?!? uncurry ValueError
111111
return (db', warnings)
112112

113-
migrateDataDump' :: (Read db, Read rec, Read fld)
113+
migrateDataDump' :: (Read db, Read rec, Read alt, Read fld)
114114
=> (API, Version) -- ^ Starting schema and version
115115
-> (API, VersionExtra) -- ^ Ending schema and version
116116
-> APIChangelog -- ^ Log of changes, containing both versions
117-
-> CustomMigrations Record Value db rec fld -- ^ Custom migration functions
117+
-> CustomMigrations Record Value db rec alt fld -- ^ Custom migration functions
118118
-> TypeName -- ^ Name of the dataset's type
119119
-> DataChecks -- ^ How thoroughly to validate changes
120120
-> Value.Value -- ^ Dataset to be migrated
@@ -129,36 +129,41 @@ migrateDataDump' startApi endApi changelog custom root chks db = do
129129

130130

131131
-- | Custom migrations used in the changelog must be implemented in
132-
-- Haskell, and supplied in this record. There are three kinds:
132+
-- Haskell, and supplied in this record. There are four kinds:
133133
--
134134
-- * Whole-database migrations, which may arbitrarily change the API
135135
-- schema and the data to match;
136136
--
137-
-- * Type migrations, which may change the schema of a single type; and
137+
-- * Type migrations, which may change the schema of a single type;
138+
--
139+
-- * Union alternative migrations, which may change the type of a
140+
-- single alternative within a union (with the new type specified in
141+
-- the changelog); and
138142
--
139143
-- * Single field migrations, which may change only the type of the
140144
-- field (with the new type specified in the changelog).
141145
--
142146
-- For database and type migrations, if the schema is unchanged, the
143147
-- corresponding function should return 'Nothing'.
144148
--
145-
-- The @db@, @ty@ and @fld@ parameters should be instantiated with
146-
-- the enumeration types generated by 'generateMigrationKinds', which
147-
-- correspond to the exact set of custom migration tags used in the
148-
-- changelog.
149-
data CustomMigrations o v db ty fld = CustomMigrations
149+
-- The @db@, @ty@, @alt@ and @fld@ parameters should be instantiated
150+
-- with the enumeration types generated by 'generateMigrationKinds',
151+
-- which correspond to the exact set of custom migration tags used in
152+
-- the changelog.
153+
data CustomMigrations o v db ty alt fld = CustomMigrations
150154
{ databaseMigration :: db -> o -> Either ValueError o
151155
, databaseMigrationSchema :: db -> NormAPI -> Either ApplyFailure (Maybe NormAPI)
152156
, typeMigration :: ty -> v -> Either ValueError v
153157
, typeMigrationSchema :: ty -> NormTypeDecl -> Either ApplyFailure (Maybe NormTypeDecl)
158+
, unionAltMigration :: alt -> v -> Either ValueError v
154159
, fieldMigration :: fld -> v -> Either ValueError v }
155160

156-
type CustomMigrationsTagged o v = CustomMigrations o v MigrationTag MigrationTag MigrationTag
161+
type CustomMigrationsTagged o v = CustomMigrations o v MigrationTag MigrationTag MigrationTag MigrationTag
157162

158-
readCustomMigrations :: (Read db, Read ty, Read fld)
159-
=> CustomMigrations o v db ty fld -> CustomMigrationsTagged o v
160-
readCustomMigrations (CustomMigrations db dbs r rs f) =
161-
CustomMigrations (db . read) (dbs . read) (r . read) (rs . read) (f . read)
163+
readCustomMigrations :: (Read db, Read ty, Read alt, Read fld)
164+
=> CustomMigrations o v db ty alt fld -> CustomMigrationsTagged o v
165+
readCustomMigrations (CustomMigrations db dbs r rs a f) =
166+
CustomMigrations (db . read) (dbs . read) (r . read) (rs . read) (a . read) (f . read)
162167

163168
-- | Lift a custom record migration to work on arbitrary values
164169
mkRecordMigration :: (JS.Object -> Either ValueError JS.Object)
@@ -244,23 +249,23 @@ isChangelogOrdered changelog =
244249

245250

246251
-- | Sets of custom migration tags in the changelog for
247-
-- whole-database, single-record and single-field migrations
248-
changelogTags :: APIChangelog -> (Set MigrationTag, Set MigrationTag, Set MigrationTag)
249-
changelogTags (ChangesStart _) = (Set.empty, Set.empty, Set.empty)
252+
-- whole-database, single-type, union-alternative and single-field migrations
253+
changelogTags :: APIChangelog -> (Set MigrationTag, Set MigrationTag, Set MigrationTag, Set MigrationTag)
254+
changelogTags (ChangesStart _) = (Set.empty, Set.empty, Set.empty, Set.empty)
250255
changelogTags (ChangesUpTo _ cs older) =
251-
unions3 (map changeTags cs) `union3` changelogTags older
256+
unions4 (map changeTags cs) `union4` changelogTags older
252257
where
253-
union3 (a, b, c) (x, y, z) = (a `Set.union` x, b `Set.union` y, c `Set.union` z)
254-
unions3 xyzs = (Set.unions xs, Set.unions ys, Set.unions zs)
255-
where (xs, ys, zs) = unzip3 xyzs
258+
union4 (a, b, c, d) (x, y, z, w) = (a `Set.union` x, b `Set.union` y, c `Set.union` z, d `Set.union` w)
259+
unions4 xyzws = (Set.unions xs, Set.unions ys, Set.unions zs, Set.unions ws)
260+
where (xs, ys, zs, ws) = unzip4 xyzws
256261

257262
-- | Sets of custom migration tags in a single change
258-
changeTags :: APIChange -> (Set MigrationTag, Set MigrationTag, Set MigrationTag)
259-
changeTags (ChChangeField _ _ _ t) = (Set.empty, Set.empty, Set.singleton t)
260-
changeTags (ChChangeUnionAlt _ _ _ t) = (Set.empty, Set.singleton t, Set.empty)
261-
changeTags (ChCustomType _ t) = (Set.empty, Set.singleton t, Set.empty)
262-
changeTags (ChCustomAll t) = (Set.singleton t, Set.empty, Set.empty)
263-
changeTags _ = (Set.empty, Set.empty, Set.empty)
263+
changeTags :: APIChange -> (Set MigrationTag, Set MigrationTag, Set MigrationTag, Set MigrationTag)
264+
changeTags (ChChangeField _ _ _ t) = (Set.empty, Set.empty, Set.empty, Set.singleton t)
265+
changeTags (ChChangeUnionAlt _ _ _ t) = (Set.empty, Set.empty, Set.singleton t, Set.empty)
266+
changeTags (ChCustomType _ t) = (Set.empty, Set.singleton t, Set.empty, Set.empty)
267+
changeTags (ChCustomAll t) = (Set.singleton t, Set.empty, Set.empty, Set.empty)
268+
changeTags _ = (Set.empty, Set.empty, Set.empty, Set.empty)
264269

265270

266271
--------------------------------
@@ -306,11 +311,11 @@ findUpdatePos tname api = Map.alter (Just . UpdateHere) tname $
306311

307312
-- | Check that a changelog adequately describes how to migrate from
308313
-- one version to another.
309-
validateChanges :: (Read db, Read rec, Read fld)
314+
validateChanges :: (Read db, Read rec, Read alt, Read fld)
310315
=> (API, Version) -- ^ Starting schema and version
311316
-> (API, VersionExtra) -- ^ Ending schema and version
312317
-> APIChangelog -- ^ Changelog to be validated
313-
-> CustomMigrations o v db rec fld -- ^ Custom migration functions
318+
-> CustomMigrations o v db rec alt fld -- ^ Custom migration functions
314319
-> TypeName -- ^ Name of the dataset's type
315320
-> DataChecks -- ^ How thoroughly to validate changes
316321
-> Either ValidateFailure [ValidateWarning]
@@ -620,7 +625,7 @@ applyChangeToData (ChRenameUnionAlt _ fname fname') _ = withObject $ \un p ->
620625
applyChangeToData (ChChangeUnionAlt _ fname _ftype tag) custom = withObject $ \un p ->
621626
case matchSingletonObject un of
622627
Just (k, r) | k == _FieldName fname -> do
623-
r' <- liftMigration (typeMigration custom tag) r p
628+
r' <- liftMigration (unionAltMigration custom tag) r p
624629
return $ singletonObject (_FieldName fname) r'
625630
_ -> return un
626631

@@ -738,7 +743,7 @@ applyChangeToData' _ (ChRenameUnionAlt _ fname fname') _ v p = do
738743
applyChangeToData' _ (ChChangeUnionAlt _ fname _ftype tag) custom v p = do
739744
(fn, v') <- expectUnion v p
740745
if fn == fname
741-
then Union fn <$!> liftMigration (typeMigration custom tag) v' (inField fn:p)
746+
then Union fn <$!> liftMigration (unionAltMigration custom tag) v' (inField fn:p)
742747
else pure v
743748

744749
applyChangeToData' _ (ChRenameEnumVal _ fname fname') _ v p = do
@@ -887,17 +892,21 @@ type Decode t = JS.Value -> Either [(JSONError, Position)] t
887892

888893
-- | Generate enumeration datatypes corresponding to the custom
889894
-- migrations used in an API migration changelog.
890-
generateMigrationKinds :: APIChangelog -> String -> String -> String -> Q [Dec]
891-
generateMigrationKinds changes all_nm rec_nm fld_nm = do
895+
generateMigrationKinds :: APIChangelog -> String -> String -> String -> String -> Q [Dec]
896+
generateMigrationKinds changes all_nm rec_nm alt_nm fld_nm = do
892897
guardNoDups (all_tags `Set.intersection` rec_tags)
898+
guardNoDups (all_tags `Set.intersection` alt_tags)
893899
guardNoDups (all_tags `Set.intersection` fld_tags)
900+
guardNoDups (rec_tags `Set.intersection` alt_tags)
894901
guardNoDups (rec_tags `Set.intersection` fld_tags)
902+
guardNoDups (alt_tags `Set.intersection` fld_tags)
895903

896904
return [ mkDataD [] (mkName all_nm) [] (cons all_nm all_tags) derivs
897905
, mkDataD [] (mkName rec_nm) [] (cons rec_nm rec_tags) derivs
906+
, mkDataD [] (mkName alt_nm) [] (cons alt_nm alt_tags) derivs
898907
, mkDataD [] (mkName fld_nm) [] (cons fld_nm fld_tags) derivs ]
899908
where
900-
(all_tags, rec_tags, fld_tags) = changelogTags changes
909+
(all_tags, rec_tags, alt_tags, fld_tags) = changelogTags changes
901910

902911
guardNoDups xs
903912
| Set.null xs = return ()

src/Data/API/Tutorial.hs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -301,13 +301,14 @@ These types should then be used to create a 'CustomMigrations' record,
301301
which describes how to transform the data (and 'API', if appropriate)
302302
for each custom migration. For example,
303303
304-
> $(generateMigrationKinds myChangelog "DatabaseMigration" "TypeMigration" "FieldMigration")
304+
> $(generateMigrationKinds myChangelog "DatabaseMigration" "TypeMigration" "UnionAltMigration" "FieldMigration")
305305
306306
with the changelog fragment above would give
307307
308-
> data DatabaseMigration = MigrateWholeDatabase | ...
309-
> data TypeMigration = MigrateWidgetType | ...
310-
> data FieldMigration = MigrateFooField | ...
308+
> data DatabaseMigration = MigrateWholeDatabase | ...
309+
> data TypeMigration = MigrateWidgetType | ...
310+
> data UnionAltMigration = ...
311+
> data FieldMigration = MigrateFooField | ...
311312
312313
Calls to 'migrateDataDump' should include a suitable
313314
'CustomMigrations' record, which includes functions to perform the

tests/Data/API/Test/Migration.hs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ import qualified Data.HashMap.Strict as HMap
4040
#endif
4141

4242

43-
$(generateMigrationKinds changelog "TestDatabaseMigration" "TestRecordMigration" "TestFieldMigration")
43+
$(generateMigrationKinds changelog "TestDatabaseMigration" "TestRecordMigration" "TestUnionAltMigration" "TestFieldMigration")
4444

4545

4646
-- Test of a whole-database migration: copy data between tables
@@ -121,18 +121,20 @@ testFieldMigration' ConvertBinaryToString (Value.Bytes bs) = return (Value.Strin
121121
testFieldMigration' ConvertBinaryToString v = Left $ CustomMigrationError "bad data" (JS.toJSON v)
122122

123123

124-
testMigration :: CustomMigrations JS.Object JS.Value TestDatabaseMigration TestRecordMigration TestFieldMigration
124+
testMigration :: CustomMigrations JS.Object JS.Value TestDatabaseMigration TestRecordMigration TestUnionAltMigration TestFieldMigration
125125
testMigration = CustomMigrations testDatabaseMigration
126126
testDatabaseMigrationSchema
127127
testRecordMigration
128128
testRecordMigrationSchema
129+
(\ _ -> noDataChanges)
129130
testFieldMigration
130131

131-
testMigration' :: CustomMigrations Value.Record Value.Value TestDatabaseMigration TestRecordMigration TestFieldMigration
132+
testMigration' :: CustomMigrations Value.Record Value.Value TestDatabaseMigration TestRecordMigration TestUnionAltMigration TestFieldMigration
132133
testMigration' = CustomMigrations testDatabaseMigration'
133134
testDatabaseMigrationSchema
134135
testRecordMigration'
135136
testRecordMigrationSchema
137+
(\ _ -> noDataChanges)
136138
testFieldMigration'
137139

138140

tests/Data/API/Test/UnionMigration.hs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ import Data.API.Test.UnionMigrationData
2727

2828

2929
-- Generate migration enums from changelog
30-
$(generateMigrationKinds typeSwapChangelog "TypeSwapDbMigration" "TypeSwapRecordMigration" "TypeSwapFieldMigration")
30+
$(generateMigrationKinds typeSwapChangelog "TypeSwapDbMigration" "TypeSwapRecordMigration" "TypeSwapUnionAltMigration" "TypeSwapFieldMigration")
3131

3232

3333
-- -----------------------------------------------------------------------------
@@ -41,7 +41,7 @@ $(generateMigrationKinds typeSwapChangelog "TypeSwapDbMigration" "TypeSwapRecord
4141
--
4242
-- This is a type migration because we're transforming the entire inner value
4343
-- of the union alternative from one type to another.
44-
migratePersonV1ToV2 :: TypeSwapRecordMigration -> JS.Value -> Either ValueError JS.Value
44+
migratePersonV1ToV2 :: TypeSwapUnionAltMigration -> JS.Value -> Either ValueError JS.Value
4545
migratePersonV1ToV2 MigratePersonV1ToV2 (JS.Object obj) = do
4646
nameVal <- lookupKey "name" obj ?! CustomMigrationError "missing 'name' field" (JS.Object obj)
4747
case nameVal of
@@ -53,12 +53,13 @@ migratePersonV1ToV2 MigratePersonV1ToV2 v =
5353
Left $ CustomMigrationError "expected object for PersonV1" v
5454

5555

56-
typeSwapMigration :: CustomMigrations JS.Object JS.Value TypeSwapDbMigration TypeSwapRecordMigration TypeSwapFieldMigration
56+
typeSwapMigration :: CustomMigrations JS.Object JS.Value TypeSwapDbMigration TypeSwapRecordMigration TypeSwapUnionAltMigration TypeSwapFieldMigration
5757
typeSwapMigration = CustomMigrations
5858
{ databaseMigration = \ _ -> noDataChanges
5959
, databaseMigrationSchema = \ _ -> noSchemaChanges
60-
, typeMigration = migratePersonV1ToV2
60+
, typeMigration = \ _ -> noDataChanges
6161
, typeMigrationSchema = \ _ -> noSchemaChanges
62+
, unionAltMigration = migratePersonV1ToV2
6263
, fieldMigration = \ _ -> noDataChanges
6364
}
6465

0 commit comments

Comments
 (0)