Parse a version tagged data type and then migrate it to the desired type. Any serialized value has been extended by the return type can be parsed.
Modulesafecopy-0.10.4.3Haskell2010
Data.SafeCopy
SafeCopy extends the parsing and serialization capabilities of Data.Serialize to include nested version control. Nested version control means that you can change the definition and binary format of a type nested deep within other types without problems.
Migration
Consider this scenario. You want to store your contact list on disk and so write the following code:
type Name = String
type Address = String
data Contacts = Contacts [(Name, Address)]
instance SafeCopy Contacts where
putCopy (Contacts list) = contain $ safePut list
getCopy = contain $ Contacts <$> safeGet
At this point, everything is fine. You get the awesome speed of Data.Serialize together with Haskell's ease of use. However, things quickly take a U-turn for the worse when you realize that you want to keep phone numbers as well as names and addresses. Being the experienced coder that you are, you see that using a 3-tuple isn't very pretty and you'd rather use a record. At first you fear that this change in structure will invalidate all your old data. Those fears are quickly quelled, though, when you remember how nifty SafeCopy is. With renewed enthusiasm, you set out and write the following code:
type Name = String
type Address = String
type Phone = String
{- We rename our old Contacts structure -}
data Contacts_v0 = Contacts_v0 [(Name, Address)]
instance SafeCopy Contacts_v0 where
putCopy (Contacts_v0 list) = contain $ safePut list
getCopy = contain $ Contacts_v0 <$> safeGet
data Contact = Contact { name :: Name
, address :: Address
, phone :: Phone }
instance SafeCopy Contact where
putCopy Contact{..} = contain $ do safePut name; safePut address; safePut phone
getCopy = contain $ Contact <$> safeGet <*> safeGet <*> safeGet
data Contacts = Contacts [Contact]
instance SafeCopy Contacts where
version = 2
kind = extension
putCopy (Contacts contacts) = contain $ safePut contacts
getCopy = contain $ Contacts <$> safeGet
{- Here the magic happens: -}
instance Migrate Contacts where
type MigrateFrom Contacts = Contacts_v0
migrate (Contacts_v0 contacts) = Contacts [ Contact{ name = name
, address = address
, phone = "" }
| (name, address) <- contacts ]
With this, you reflect on your code and you are happy. You feel confident in the safety of
your data and you know you can remove Contacts_v0 once you no longer wish to support
that legacy format.
Retiring Migrations
There may come a time when you have to remove Contacts_v0.
Perhaps it uses types you want to remove from your build to
decrease its size. Perhaps it has constraints such as Enum which
are imposed on your new Contacts type via the SafeCopy
instance.
In any case, if you are using safecopy incombination with
acid-state, some care must be taken when removing Contacts_v0.
The following steps must be taken to add a new type and remove the old:
Add the migration as described above.
Run the server with the new migration on all important data. This will cause the type to be modified in the running program.
Restart the server with the new migration on all important data. This causes checkpoints to be written that only contain the new type.
Remove the old type from your source code, changing the
kindof the new type fromextensiontobase. Build and deploy.
If you omit any of these steps it is a certainty that you will proceed happily with your development thinking all is grand, and then when you go to deploy your live system the migration will fail.
- 7 types
- 2 classes
- 16 values
- Packagesafecopy-0.10.4.3
- Exports25
- LanguageHaskell2010
- LicenceLicenseRef-PublicDomain
- SourceSafeCopy.hs
Serialize a data type by first writing out its version tag. This is much simpler than the corresponding safeGet since previous versions don't come into play.
The centerpiece of this library. Defines a version for a data type together with how it should be serialized/parsed.
Users should define instances of SafeCopy for their types even though getCopy and putCopy can't be used directly. To serialize/parse a data type using SafeCopy, see safeGet and safePut.
Methods
version :: Version aThe version of the type.
Only used as a key so it must be unique (this is checked at run-time) but doesn't have to be sequential or continuous.
The default version is '0'.
kind :: Kind agetCopy :: Contained (Get a)This method defines how a value should be parsed without also worrying about writing out the version tag. This function cannot be used directly. One should use safeGet, instead.
putCopy :: a -> Contained PutThis method defines how a value should be parsed without worrying about previous versions or migrations. This function cannot be used directly. One should use 'safePut, instead.
objectProfile :: Profile aVersion profile.
errorTypeName :: Proxy a -> StringThe name of the type. This is only used in error message strings.
Instances64SafeCopy, …
SafeCopy ByteStringDefined in safecopy-0.10.4.3 · Data.SafeCopy.Instances · orphanSafeCopy ByteStringDefined in safecopy-0.10.4.3 · Data.SafeCopy.Instances · orphanSafeCopy IntSetDefined in safecopy-0.10.4.3 · Data.SafeCopy.Instances · orphanSafeCopy IntegerDefined in safecopy-0.10.4.3 · Data.SafeCopy.Instances · orphanSafeCopy NaturalDefined in safecopy-0.10.4.3 · Data.SafeCopy.Instances · orphanSafeCopy Int16Defined in safecopy-0.10.4.3 · Data.SafeCopy.Instances · orphanSafeCopy Int32Defined in safecopy-0.10.4.3 · Data.SafeCopy.Instances · orphanSafeCopy Int64Defined in safecopy-0.10.4.3 · Data.SafeCopy.Instances · orphanSafeCopy Int8Defined in safecopy-0.10.4.3 · Data.SafeCopy.Instances · orphanSafeCopy BoolDefined in safecopy-0.10.4.3 · Data.SafeCopy.Instances · orphanSafeCopy CharDefined in safecopy-0.10.4.3 · Data.SafeCopy.Instances · orphanSafeCopy DoubleDefined in safecopy-0.10.4.3 · Data.SafeCopy.Instances · orphanSafeCopy FloatDefined in safecopy-0.10.4.3 · Data.SafeCopy.Instances · orphanSafeCopy IntDefined in safecopy-0.10.4.3 · Data.SafeCopy.Instances · orphanSafeCopy OrderingDefined in safecopy-0.10.4.3 · Data.SafeCopy.Instances · orphanSafeCopy WordDefined in safecopy-0.10.4.3 · Data.SafeCopy.Instances · orphanSafeCopy Word16Defined in safecopy-0.10.4.3 · Data.SafeCopy.Instances · orphanSafeCopy Word32Defined in safecopy-0.10.4.3 · Data.SafeCopy.Instances · orphanSafeCopy Word64Defined in safecopy-0.10.4.3 · Data.SafeCopy.Instances · orphanSafeCopy Word8Defined in safecopy-0.10.4.3 · Data.SafeCopy.Instances · orphanSafeCopy CalendarTimeDefined in safecopy-0.10.4.3 · Data.SafeCopy.Instances · orphanSafeCopy ClockTimeDefined in safecopy-0.10.4.3 · Data.SafeCopy.Instances · orphanSafeCopy DayDefined in safecopy-0.10.4.3 · Data.SafeCopy.Instances · orphanSafeCopy MonthDefined in safecopy-0.10.4.3 · Data.SafeCopy.Instances · orphanSafeCopy TimeDiffDefined in safecopy-0.10.4.3 · Data.SafeCopy.Instances · orphanSafeCopy CerealDouble040Defined in safecopy-0.10.4.3 · Data.SafeCopy.InstancesSafeCopy CerealFloat040Defined in safecopy-0.10.4.3 · Data.SafeCopy.InstancesSafeCopy TextDefined in safecopy-0.10.4.3 · Data.SafeCopy.Instances · orphanSafeCopy TextDefined in safecopy-0.10.4.3 · Data.SafeCopy.Instances · orphanSafeCopy DayDefined in safecopy-0.10.4.3 · Data.SafeCopy.Instances · orphanSafeCopy AbsoluteTimeDefined in safecopy-0.10.4.3 · Data.SafeCopy.Instances · orphanSafeCopy DiffTimeDefined in safecopy-0.10.4.3 · Data.SafeCopy.Instances · orphanSafeCopy NominalDiffTimeDefined in safecopy-0.10.4.3 · Data.SafeCopy.Instances · orphanSafeCopy UTCTimeDefined in safecopy-0.10.4.3 · Data.SafeCopy.Instances · orphanSafeCopy UniversalTimeDefined in safecopy-0.10.4.3 · Data.SafeCopy.Instances · orphanSafeCopy LocalTimeDefined in safecopy-0.10.4.3 · Data.SafeCopy.Instances · orphanSafeCopy TimeOfDayDefined in safecopy-0.10.4.3 · Data.SafeCopy.Instances · orphanSafeCopy TimeZoneDefined in safecopy-0.10.4.3 · Data.SafeCopy.Instances · orphanSafeCopy ZonedTimeDefined in safecopy-0.10.4.3 · Data.SafeCopy.Instances · orphanSafeCopy ()Defined in safecopy-0.10.4.3 · Data.SafeCopy.Instances · orphanSafeCopy a => SafeCopy (IntMap a)Defined in safecopy-0.10.4.3 · Data.SafeCopy.Instances · orphanSafeCopy a => SafeCopy (Seq a)Defined in safecopy-0.10.4.3 · Data.SafeCopy.Instances · orphanSafeCopy a => SafeCopy (Tree a)Defined in safecopy-0.10.4.3 · Data.SafeCopy.Instances · orphanSafeCopy a => SafeCopy (NonEmpty a)Defined in safecopy-0.10.4.3 · Data.SafeCopy.Instances · orphanSafeCopy a => SafeCopy (Maybe a)Defined in safecopy-0.10.4.3 · Data.SafeCopy.Instances · orphanSafeCopy a => SafeCopy (Prim a)Defined in safecopy-0.10.4.3 · Data.SafeCopy.Instances · orphanSafeCopy a => SafeCopy (Vector a)Defined in safecopy-0.10.4.3 · Data.SafeCopy.Instances · orphanSafeCopy a => SafeCopy [a]Defined in safecopy-0.10.4.3 · Data.SafeCopy.Instances · orphan(Integral a, SafeCopy a) => SafeCopy (Ratio a)Defined in safecopy-0.10.4.3 · Data.SafeCopy.Instances · orphan(SafeCopy a, Ord a) => SafeCopy (Set a)Defined in safecopy-0.10.4.3 · Data.SafeCopy.Instances · orphan(SafeCopy a, Storable a) => SafeCopy (Vector a)Defined in safecopy-0.10.4.3 · Data.SafeCopy.Instances · orphan(SafeCopy a, Prim a) => SafeCopy (Vector a)Defined in safecopy-0.10.4.3 · Data.SafeCopy.Instances · orphan(SafeCopy a, Unbox a) => SafeCopy (Vector a)Defined in safecopy-0.10.4.3 · Data.SafeCopy.Instances · orphan(IArray UArray e, Ix i, SafeCopy e, SafeCopy i) => SafeCopy (UArray i e)Defined in safecopy-0.10.4.3 · Data.SafeCopy.Instances · orphan(HasResolution a, Fractional (Fixed a), Typeable a) => SafeCopy (Fixed a)Defined in safecopy-0.10.4.3 · Data.SafeCopy.Instances · orphan(Ix i, SafeCopy e, SafeCopy i) => SafeCopy (Array i e)Defined in safecopy-0.10.4.3 · Data.SafeCopy.Instances · orphan(SafeCopy a, SafeCopy b) => SafeCopy (Either a b)Defined in safecopy-0.10.4.3 · Data.SafeCopy.Instances · orphan(SafeCopy a, SafeCopy b) => SafeCopy (a, b)Defined in safecopy-0.10.4.3 · Data.SafeCopy.Instances · orphan(SafeCopy a, SafeCopy b, Ord a) => SafeCopy (Map a b)Defined in safecopy-0.10.4.3 · Data.SafeCopy.Instances · orphan(SafeCopy a, SafeCopy b, SafeCopy c) => SafeCopy (a, b, c)Defined in safecopy-0.10.4.3 · Data.SafeCopy.Instances · orphan(SafeCopy a, SafeCopy b, SafeCopy c, SafeCopy d) => SafeCopy (a, b, c, d)Defined in safecopy-0.10.4.3 · Data.SafeCopy.Instances · orphan(SafeCopy a, SafeCopy b, SafeCopy c, SafeCopy d, SafeCopy e) => SafeCopy (a, b, c, d, e)Defined in safecopy-0.10.4.3 · Data.SafeCopy.Instances · orphan(SafeCopy a, SafeCopy b, SafeCopy c, SafeCopy d, SafeCopy e, SafeCopy f) => SafeCopy (a, b, c, d, e, f)Defined in safecopy-0.10.4.3 · Data.SafeCopy.Instances · orphan(SafeCopy a, SafeCopy b, SafeCopy c, SafeCopy d, SafeCopy e, SafeCopy f, SafeCopy g) => SafeCopy (a, b, c, d, e, f, g)Defined in safecopy-0.10.4.3 · Data.SafeCopy.Instances · orphan
Wrapper for data that was saved without a version tag.
Constructors
PrimgetPrimitive :: a
The central mechanism for dealing with version control.
This type class specifies what data migrations can happen and how they happen.
Associated types
type family MigrateFrom aThis is the type we're extending. Each type capable of migration can only extend one other type.
Methods
migrate :: MigrateFrom a -> aThis method specifies how to migrate from the older type to the newer one. It will never be necessary to use this function manually as it all taken care of internally in the library.
The kind of a data type determines how it is tagged (if at all).
Primitives kinds (see primitive) are not tagged with a version id and hence cannot be extended later.
Extensions (see extension) tell the system that there exists a previous version of the data type which should be migrated if needed.
There is also a default kind which is neither primitive nor an extension of a previous type.
The extension kind lets the system know that there is at least one previous version of this type. A given data type can only extend a single other data type. However, it is perfectly fine to build chains of extensions. The migrations between each step is handled automatically.
The extended_extension kind lets the system know that there is at least one previous and one future version of this type.
The extended_base kind lets the system know that there is at least one future version of this type.
The default kind. Does not extend any type.
Place a value in an unbreakable container.
A simple numeric version id.
Instances5Eq, Num, Read, Show, Serialize
Eq (Version a)Defined in safecopy-0.10.4.3 · Data.SafeCopy.SafeCopyNum (Version a)Defined in safecopy-0.10.4.3 · Data.SafeCopy.SafeCopyRead (Version a)Defined in safecopy-0.10.4.3 · Data.SafeCopy.SafeCopyShow (Version a)Defined in safecopy-0.10.4.3 · Data.SafeCopy.SafeCopySerialize (Version a)Defined in safecopy-0.10.4.3 · Data.SafeCopy.SafeCopy
Template haskell functions
6 declarationsDerive an instance of SafeCopy.
When serializing, we put a Word8 describing the
constructor (if the data type has more than one
constructor). For each type used in the constructor, we
call getSafePut (which immediately serializes the version
of the type). Then, for each field in the constructor, we
use one of the put functions obtained in the last step.
For example, given the data type and the declaration below
data T0 b = T0 b Int
deriveSafeCopy 1 'base ''T0
we generate
instance (SafeCopy a, SafeCopy b) =>
SafeCopy (T0 b) where
putCopy (T0 arg1 arg2) = contain $ do put_b <- getSafePut
put_Int <- getSafePut
put_b arg1
put_Int arg2
return ()
getCopy = contain $ do get_b <- getSafeGet
get_Int <- getSafeGet
return T0 <*> get_b <*> get_Int
version = 1
kind = base
And, should we create another data type as a newer version of T0, such as
data T a b = C a a | D b Int
deriveSafeCopy 2 'extension ''T
instance SafeCopy b => Migrate (T a b) where
type MigrateFrom (T a b) = T0 b
migrate (T0 b i) = D b i
we generate
instance (SafeCopy a, SafeCopy b) =>
SafeCopy (T a b) where
putCopy (C arg1 arg2) = contain $ do putWord8 0
put_a <- getSafePut
put_a arg1
put_a arg2
return ()
putCopy (D arg1 arg2) = contain $ do putWord8 1
put_b <- getSafePut
put_Int <- getSafePut
put_b arg1
put_Int arg2
return ()
getCopy = contain $ do tag <- getWord8
case tag of
0 -> do get_a <- getSafeGet
return C <*> get_a <*> get_a
1 -> do get_b <- getSafeGet
get_Int <- getSafeGet
return D <*> get_b <*> get_Int
_ -> fail $ "Could not identify tag \"" ++
show tag ++ "\" for type Main.T " ++
"that has only 2 constructors. " ++
"Maybe your data is corrupted?"
version = 2
kind = extension
Note that by using getSafePut, we saved 4 bytes in the case
of the C constructor. For D and T0, we didn't save
anything. The instance derived by this function always use
at most the same space as those generated by
deriveSafeCopySimple, but never more (as we don't call
getSafePut/getSafeGet for types that aren't needed).
Note that you may use deriveSafeCopySimple with one version of your data type and deriveSafeCopy in another version without any problems.
Derive an instance of SafeCopy. The instance derived by this function is simpler than the one derived by deriveSafeCopy in that we always use safePut and safeGet (instead of getSafePut and getSafeGet).
When serializing, we put a Word8 describing the
constructor (if the data type has more than one constructor)
and, for each field of the constructor, we use safePut.
For example, given the data type and the declaration below
data T a b = C a a | D b Int
deriveSafeCopySimple 1 'base ''T
we generate
instance (SafeCopy a, SafeCopy b) =>
SafeCopy (T a b) where
putCopy (C arg1 arg2) = contain $ do putWord8 0
safePut arg1
safePut arg2
return ()
putCopy (D arg1 arg2) = contain $ do putWord8 1
safePut arg1
safePut arg2
return ()
getCopy = contain $ do tag <- getWord8
case tag of
0 -> do return C <*> safeGet <*> safeGet
1 -> do return D <*> safeGet <*> safeGet
_ -> fail $ "Could not identify tag \"" ++
show tag ++ "\" for type Main.T " ++
"that has only 2 constructors. " ++
"Maybe your data is corrupted?"
version = 1
kind = base
Using this simpler instance means that you may spend more bytes when serializing data. On the other hand, it is more straightforward and may match any other format you used in the past.
Note that you may use deriveSafeCopy with one version of your data type and deriveSafeCopySimple in another version without any problems.
Derive an instance of SafeCopy. The instance derived by
this function should be compatible with the instance derived
by the module Happstack.Data.SerializeTH of the
happstack-data package. The instances use only safePut
and safeGet (as do the instances created by
deriveSafeCopySimple), but we also always write a Word8
tag, even if the data type isn't a sum type.
For example, given the data type and the declaration below
data T0 b = T0 b Int
deriveSafeCopy 1 'base ''T0
we generate
instance (SafeCopy a, SafeCopy b) =>
SafeCopy (T0 b) where
putCopy (T0 arg1 arg2) = contain $ do putWord8 0
safePut arg1
safePut arg2
return ()
getCopy = contain $ do tag <- getWord8
case tag of
0 -> do return T0 <*> safeGet <*> safeGet
_ -> fail $ "Could not identify tag \"" ++
show tag ++ "\" for type Main.T0 " ++
"that has only 1 constructors. " ++
"Maybe your data is corrupted?"
version = 1
kind = base
This instance always consumes at least the same space as deriveSafeCopy or deriveSafeCopySimple, but may use more because of the useless tag. So we recomend using it only if you really need to read a previous version in this format, and not for newer versions.
Note that you may use deriveSafeCopy with one version of your data type and deriveSafeCopyHappstackData in another version without any problems.
Rarely used functions
3 declarationsParse a version tag and return the corresponding migrated parser. This is useful when you can prove that multiple values have the same version. See getSafePut.
Serialize the version tag and return the associated putter. This is useful when serializing multiple values with the same version. See getSafeGet.