- Get Postgres
# Ubuntu/Debian
$ sudo apt install postgresql
$ sudo systemctl start [email protected]
# Arch
$ sudo pacman -Syu postgresql
$ sudo systemctl start postgresql.service
$ sudo mkdir -p /var/lib/postgres/data
$ sudo chown postgres:postgres -R /var/lib/postgres/data
$ sudo -u postgres initdb /var/lib/postgres/data
$ sudo systemctl start postgresql.service
- Get comfortable
git clone https://github.com/duijf/boring-software.git
cd boring-software/code
stack setup
stack build
- Set up the cluster roles and permissions
$ sudo -u postgres psql
postgres=# create role "test" with login password 'test';
postgres=# create database "test";
postgres=# grant all privileges on database "test" to "test";
postgres=# \q
$ psql -U test test
password:
test=> create table foo (id bigserial primary key);
test=> drop table foo;
-
Find the docs for
ConnectInfo
. Try to make a value of this type. -
Try to connect to Postgres from Haskell using you rown
ConnectInfo
. Use the credentials from the DB setup. -
Find out what functions from
postgresql-simple
allow you to operate on aConnection
. Read the sigs forquery
andexecute
. -
Find out how to get a
Query
type. -
Find out what function has type
Bytestring -> Query
-
Write a function
executeSqlFile :: Connection -> FilePath -> IO ()
-
Think of how we'd want to represent the structure of the
schemactl
tables in Haskell types things in the Database. Find them indb/000_bootstrap.sql.up
. -
Create Haskell types for these.
-
See if you can get
FromRow
andToRow
instances for your custom types. -
Write a function
markActiveRevision :: Pg.Connection -> ? -> IO ()
which marks a revision as active in the DB. You might need to create a type for this. -
Write a function
getActiveRevision :: Pg.Connection -> IO ?
which marks a revision as active in the DB. You might need to re-use your type from the previous section.
-
Write an implementation for
getMigrationsToRun
to yield the migrations to run based on the currentRev
from the database. What cases can't this function handle at this point? -
Does the type for
getMigrationsToRun
function make sense? If not, what could it be instead? -
Does the type for
Migration
still make sense? If not, what could it be instead? -
If you want, change the types and fix the compile errors. You can also wait with this if you're unsure what/if there is a problem with these types.
-
Think of a CLI. Implement it. Keep the implementation simple. Try to see if you can use
System.Environment.getArgs
and a hand written parser.What is the difference between the types
parseArgs :: IO (Maybe EventType)
andparseArgs :: [String] -> Maybe EventType
? How do they differ in their use? -
File parsing time! See if you can read (a simple version of) that index file! Take a look at
Data.Attoparsec.ByteString.Char8
for parsing things.It's fine if your initial parser breaks on whitespace or comments.
-
Get your program to run the migrations from the
schemactl-index
file.
-
What information do you need if you want to support Downgrades? Change the program to support it. Hint: you probably need to change the types of
Migration
,markActiveRevision
, and possibly your parser. -
Add support for reading the connection settings from a config file. Can you use JSON for this? Hint: a small solution can include
Data.Aeson
,GHC.Generics
, andDeriveGeneric
. -
Add support for specifying the amount of migrations to run on during an upgrade or downgrade. Make people pass it on the CLI.
-
Add support for whitespace and/or comments in your index file parser.
-
Add more validation logic to the
getMigrationsToRun
function (for interesting mistakes humans could make). Add a--strict
mode to toggle these validations.Some ideas: duplicate migrations, warn/error on changes to migration files after they have been applied the fact. Warn/error when users change the order of migrations in the file based on the
schemactl_events
table. -
Your CLI code is probably getting a bit convoluted at this point, especially if you want to handle common idioms wherer the order does not matter. See if you can change your hand-written parser to one based on
optparse-applicative
. -
Are you happy with your error handling code? ('Yes' is fine; trust your instinct). If not, research alternatives and try them out until you're happy.
-
What other things aren't you happy with? See if you can find solutions to the things that bother you.
-
Add support for making connections to multiple databases. Add these to your config file. Allow the user to specify which database to run migrations against in the config file.
-
Read passwords/secrets from another source like environment variables or maybe some encrypted file? (Hint for the latter: See if you can use
fernet
) -
Add support for making connections over SSH tunnels. Research your own libs and write an implementation. Add this as an option in the config file.
-
Since we now have SSH tunnels: add support for locking before running migrations. Add a
break-lock
command to your CLI.