Real World Haskell - Cassandra Part 1 - Connecting to Cassandra

Posted on July 30, 2016

I use Cassandra at work and was wondering what a complete solution for connecting to it with Haskell would look like. The following will function mainly as a cookbook for connecting to Cassandra but is also littered with the real world sharp edges (and their workarounds) you sometimes have to deal with in Real World Haskell development.

I go to Hackage and type in “Cassandra” and these results are returned:

    cassandra-thrift library
    thrift bindings to the cassandra database
    library Last uploaded on Apr 21, 2013

    cassandra-cql library
    Haskell client for Cassandra's CQL protocol
    bsd3, library Last uploaded on Jul 30, 2015

    cql-io library
    Cassandra CQL client.
    library, mpl Last uploaded on Jun 17

    hscassandra library
    cassandra database interface
    bsd3, library Last uploaded on Mar 17, 2011

    cql library
    Cassandra CQL binary protocol.
    library Last uploaded on Jun 17

    cassy library
    A high level driver for the Cassandra datastore
    bsd3, library Last uploaded on Sep 2, 2014

    Quelea library
    Programming with Eventual Consistency over Cassandra.
    bsd3, library Last uploaded on Dec 13

    Thrift library
    Haskell bindings for the Apache Thrift RPC system
    library Last uploaded on Apr 12, 2013

I don’t particularly want the cassandra-thrift library bindings, so I’ll try out the cassandra-cql library.

/tmp λ stack new test-cassandra-cql simple
Downloading template "simple" to create project "test-cassandra-cql" in test-cassandra-cql/ ...

The following parameters were needed by the template but not provided: author-email, author-name, category, copyright, github-username
You can provide them in /home/cody/.stack/config.yaml, like this:
templates:
  params:
    author-email: value
    author-name: value
    category: value
    copyright: value
    github-username: value
Or you can pass each one as parameters like this:
stack new test-cassandra-cql simple -p "author-email:value" -p "author-name:value" -p "category:value" -p "copyright:value" -p "github-username:value"

Looking for .cabal or package.yaml files to use to init the project.
Using cabal packages:
- test-cassandra-cql/test-cassandra-cql.cabal

Selecting the best among 8 snapshots...

* Matches lts-6.9

Selected resolver: lts-6.9
Initialising configuration using resolver: lts-6.9
Total number of user packages considered: 1
Writing configuration to file: test-cassandra-cql/stack.yaml
All done.

Modify the test-cassandra-cql.cabal file to look like this:

name:                test-cassandra-cql
version:             0.1.0.0
synopsis:            Simple project template from stack
description:         Please see README.md
homepage:            https://github.com/githubuser/test-cassandra-cql#readme
license:             BSD3
license-file:        LICENSE
author:              Author name here
maintainer:          example@example.com
copyright:           2016 Author name here
category:            Web
build-type:          Simple
cabal-version:       >=1.10

executable test-cassandra-cql
  hs-source-dirs:      src
  main-is:             Main.hs
  default-language:    Haskell2010
  build-depends:       base >= 4.7 && < 5,
                       cassandra-cql

Stackage doesn’t yet have cassandra-cql so it’ll fail when we try to build. I’ll run stack solver --update-config followed by stack build first. I also didn’t yet have cabal-install installed so I’ll add on stack install cabal-install:

/tmp/test-cassandra-cql λ stack install cabal-install && stack solver --update-config && stack build
Cabal-1.22.8.0: download
Cabal-1.22.8.0: configure
Cabal-1.22.8.0: build
Cabal-1.22.8.0: copy/register
cabal-install-1.22.9.0: download
cabal-install-1.22.9.0: configure
cabal-install-1.22.9.0: build
cabal-install-1.22.9.0: copy/register
Completed 2 action(s).
Copying from /home/cody/.stack/snapshots/x86_64-linux/lts-6.9/7.10.3/bin/cabal to /home/cody/.local/bin/cabal

Copied executables to /home/cody/.local/bin:
- cabal
Using configuration file: stack.yaml
Using cabal packages:
- test-cassandra-cql.cabal

Using resolver: lts-6.9
Using compiler: ghc-7.10.3
Asking cabal to calculate a build plan...
Trying with packages from lts-6.9 as hard constraints...
Successfully determined a build plan with 1 external dependencies.

The following changes will be made to stack.yaml:
* Resolver is lts-6.9
* Dependencies to be added
    extra-deps:
    - cassandra-cql-0.5.0.2

Updated stack.yaml
hslogger-1.2.10: using precompiled package
Decimal-0.4.2: using precompiled package
network-info-0.2.0.8: using precompiled package
resource-pool-0.2.3.2: using precompiled package
uuid-types-1.0.3: using precompiled package
uuid-1.3.12: using precompiled package
cassandra-cql-0.5.0.2: download
cassandra-cql-0.5.0.2: configure
cassandra-cql-0.5.0.2: build
cassandra-cql-0.5.0.2: copy/register
test-cassandra-cql-0.1.0.0: configure
Configuring test-cassandra-cql-0.1.0.0...
test-cassandra-cql-0.1.0.0: build
Preprocessing executable 'test-cassandra-cql' for
test-cassandra-cql-0.1.0.0...
[1 of 1] Compiling Main             ( src/Main.hs, .stack-work/dist/x86_64-linux/Cabal-1.22.5.0/build/test-cassandra-cql/test-cassandra-cql-tmp/Main.o )
Linking .stack-work/dist/x86_64-linux/Cabal-1.22.5.0/build/test-cassandra-cql/test-cassandra-cql ...
test-cassandra-cql-0.1.0.0: copy/register
Installing executable(s) in
/tmp/test-cassandra-cql/.stack-work/install/x86_64-linux/lts-6.9/7.10.3/bin
Completed 8 action(s).
/tmp/test-cassandra-cql λ 

Cool, now it’s installed and we are ready to write some code. Let’s be lazy and peruse the github link I found on cassandra-cql’s Hackage page. At the github page I see something like this:

 	Database/Cassandra 	executeTrans now takes consistency as a parameter. 	7 months ago
	tests 	executeTrans now takes consistency as a parameter. 	7 months ago
	.gitignore 	Use Data.Pool for connection pooling. 	2 years ago
	.travis.yml 	Lower casing language name in travis script. 	a year ago
	LICENSE 	Checkpoint: It installs and more stuff works. Results not typed yet. 	3 years ago
	README.md 	Edits to readme. 	a year ago
	Setup.hs 	Add Setup.hs 	3 years ago
	cassandra-cql.cabal 	Fixed map serialization 	11 months ago
	changelog.md 	ver 0.5.0.2 change: fix incorrect upper bound on base package. 	a year ago

No examples directory, but tests directories usually have something useful. When I go there I see:

 	.gitignore 	Added script for creating keyspace. 	a year ago
	Main.hs 	Fixed map serialization 	11 months ago
	create_keyspace.cql 	Added script for creating keyspace. 	a year ago
	example-autocreate-keyspace.hs 	remove unused code 	a year ago
	example-trans.hs 	executeTrans now takes consistency as a parameter. 	7 months ago
	example.hs 	Fixing authentication for CQL binary protocol v2. 	a year ago
	test-decimal.hs 	Added PasswordAuthenticator and associated code. 	2 years ago
	test-double.hs 	Added PasswordAuthenticator and associated code. 	2 years ago
	test-float.hs 	Added PasswordAuthenticator and associated code. 	2 years ago
	test-inet.hs 	Added PasswordAuthenticator and associated code. 	2 years ago
	test-list.hs 	Added PasswordAuthenticator and associated code. 	2 years ago
	test-pool.hs 	export newPool' which allows overriding pool configuration defaults 	2 years ago
	test-set.hs 	Added PasswordAuthenticator and associated code. 	2 years ago
	test-timestamp.hs 	Added PasswordAuthenticator and associated code. 	2 years ago
	test-timeuuid.hs 	Added PasswordAuthenticator and associated code. 	2 years ago
	test-varint.hs 	Added PasswordAuthenticator and associated code. 	2 years ago

Ah, there’s an example.hs and here are it’s contents:

{-# LANGUAGE OverloadedStrings, DataKinds #-}

import Database.Cassandra.CQL
import Control.Monad
import Control.Monad.CatchIO
import Control.Monad.Trans (liftIO)
import Data.ByteString.Char8 (ByteString)
import qualified Data.ByteString.Char8 as C
import Data.Text (Text)
import qualified Data.Text as T
import Data.UUID
import System.Random

dropSongs :: Query Schema () ()
dropSongs = "drop table songs"

createSongs :: Query Schema () ()
createSongs = "create table songs (id uuid PRIMARY KEY, title ascii, artist varchar, femaleSinger boolean, timesPlayed int, comment text)"

insertSong :: Query Write (UUID, ByteString, Text, Bool, Int, Maybe Text) ()
insertSong = "insert into songs (id, title, artist, femaleSinger, timesPlayed, comment) values (?, ?, ?, ?, ?, ?)"

getSongs :: Query Rows () (UUID, ByteString, Text, Bool, Int, Maybe Text)
getSongs = "select id, title, artist, femaleSinger, timesPlayed, comment from songs"

getOneSong :: Query Rows UUID (Text, Int)
getOneSong = "select artist, timesPlayed from songs where id=?"

ignoreDropFailure :: Cas () -> Cas ()
ignoreDropFailure code = code `catch` \exc -> case exc of
    ConfigError _ _ -> return ()  -- Ignore the error if the table doesn't exist
    Invalid _ _ -> return ()
    _               -> throw exc

main = do
    -- let auth = Just (PasswordAuthenticator "cassandra" "cassandra")
    let auth = Nothing
    {-
    Assuming a 'test' keyspace already exists. Here's some CQL to create it:
    CREATE KEYSPACE test WITH replication = { 'class' : 'SimpleStrategy', 'replication_factor' : '1' };
    -}
    pool <- newPool [("localhost", "9042")] "test" auth -- servers, keyspace, maybe auth
    runCas pool $ do
        ignoreDropFailure $ liftIO . print =<< executeSchema QUORUM dropSongs ()
        liftIO . print =<< executeSchema QUORUM createSongs ()

        u1 <- liftIO randomIO
        u2 <- liftIO randomIO
        u3 <- liftIO randomIO
        executeWrite QUORUM insertSong (u1, "La Grange", "ZZ Top", False, 2, Nothing)
        executeWrite QUORUM insertSong (u2, "Your Star", "Evanescence", True, 799, Nothing)
        executeWrite QUORUM insertSong (u3, "Angel of Death", "Slayer", False, 50, Just "Singer Tom Araya")

        songs <- executeRows QUORUM getSongs ()
        liftIO $ forM_ songs $ \(uuid, title, artist, female, played, mComment) -> do
            putStrLn ""
            putStrLn $ "id            : "++show uuid
            putStrLn $ "title         : "++C.unpack title
            putStrLn $ "artist        : "++T.unpack artist
            putStrLn $ "female singer : "++show female
            putStrLn $ "times played  : "++show played
            putStrLn $ "comment       : "++show mComment

        liftIO $ putStrLn ""
        liftIO . print =<< executeRow QUORUM getOneSong u2

Replace everying in Main.hs of our project we created with the above. Now we can try stack build again:

test-cassandra-cql-0.1.0.0: configure
Configuring test-cassandra-cql-0.1.0.0...
test-cassandra-cql-0.1.0.0: build
Preprocessing executable 'test-cassandra-cql' for
test-cassandra-cql-0.1.0.0...

/tmp/test-cassandra-cql/src/Main.hs:5:8:
    Could not find module ‘Control.Monad.CatchIO’
    It is a member of the hidden package ‘MonadCatchIO-transformers-0.3.1.3@Monad_Jr5N1jwqWOiDadnjgHIYSh’.
    Perhaps you need to add ‘MonadCatchIO-transformers’ to the build-depends in your .cabal file.
    Use -v to see a list of the files searched for.

/tmp/test-cassandra-cql/src/Main.hs:6:8:
    Could not find module ‘Control.Monad.Trans’
    It is a member of the hidden package ‘monads-tf-0.1.0.3@monad_5M6wZtP1TpiAkFGzni5tIH’.
    Perhaps you need to add ‘monads-tf’ to the build-depends in your .cabal file.
    It is a member of the hidden package ‘mtl-2.2.1@mtl_Aue4leSeVkpKLsfHIV51E8’.
    Perhaps you need to add ‘mtl’ to the build-depends in your .cabal file.
    Use -v to see a list of the files searched for.

/tmp/test-cassandra-cql/src/Main.hs:8:18:
    Could not find module ‘Data.ByteString.Char8’
    It is a member of the hidden package ‘bytestring-0.10.6.0@bytes_6VWy06pWzJq9evDvK2d4w6’.
    Perhaps you need to add ‘bytestring’ to the build-depends in your .cabal file.
    Use -v to see a list of the files searched for.

/tmp/test-cassandra-cql/src/Main.hs:10:18:
    Could not find module ‘Data.Text’
    It is a member of the hidden package ‘text-1.2.2.1@text_HmqVQnZSpjaC156ABqPhne’.
    Perhaps you need to add ‘text’ to the build-depends in your .cabal file.
    Use -v to see a list of the files searched for.

/tmp/test-cassandra-cql/src/Main.hs:11:8:
    Could not find module ‘Data.UUID’
    It is a member of the hidden package ‘uuid-1.3.12@uuid_BjRErtmELFC2d9F8IOzq95’.
    Perhaps you need to add ‘uuid’ to the build-depends in your .cabal file.
    Use -v to see a list of the files searched for.

/tmp/test-cassandra-cql/src/Main.hs:12:8:
    Could not find module ‘System.Random’
    It is a member of the hidden package ‘random-1.1@rando_9Kgekc9yEaLHLNUuw6paWL’.
    Perhaps you need to add ‘random’ to the build-depends in your .cabal file.
    Use -v to see a list of the files searched for.

--  While building package test-cassandra-cql-0.1.0.0 using:
      /home/cody/.stack/setup-exe-cache/x86_64-linux/setup-Simple-Cabal-1.22.5.0-ghc-7.10.3 --builddir=.stack-work/dist/x86_64-linux/Cabal-1.22.5.0 build exe:test-cassandra-cql --ghc-options " -ddump-hi -ddump-to-file"
    Process exited with code: ExitFailure 1

Uh oh, that looks kind of scary. All it’s saying though is that we don’t have those packages listed in the build-depends section of our cabal file which currently looks like:

name:                test-cassandra-cql
version:             0.1.0.0
synopsis:            Simple project template from stack
description:         Please see README.md
homepage:            https://github.com/githubuser/test-cassandra-cql#readme
license:             BSD3
license-file:        LICENSE
author:              Author name here
maintainer:          example@example.com
copyright:           2016 Author name here
category:            Web
build-type:          Simple
cabal-version:       >=1.10

executable test-cassandra-cql
  hs-source-dirs:      src
  main-is:             Main.hs
  default-language:    Haskell2010
  build-depends:       base >= 4.7 && < 5,
                       cassandra-cql,

Let’s update it to include all of those dependencies:

name:                test-cassandra-cql
version:             0.1.0.0
synopsis:            Simple project template from stack
description:         Please see README.md
homepage:            https://github.com/githubuser/test-cassandra-cql#readme
license:             BSD3
license-file:        LICENSE
author:              Author name here
maintainer:          example@example.com
copyright:           2016 Author name here
category:            Web
build-type:          Simple
cabal-version:       >=1.10

executable test-cassandra-cql
  hs-source-dirs:      src
  main-is:             Main.hs
  default-language:    Haskell2010
  build-depends:       base >= 4.7 && < 5,
                       cassandra-cql,
                       MonadCatchIO-transformers,
                       monads-tf,
                       bytestring,
                       text,
                       uuid,
                       random

And then run stack build again:

/tmp/test-cassandra-cql λ stack build 
test-cassandra-cql-0.1.0.0: configure
Configuring test-cassandra-cql-0.1.0.0...
test-cassandra-cql-0.1.0.0: build
Preprocessing executable 'test-cassandra-cql' for
test-cassandra-cql-0.1.0.0...
[1 of 1] Compiling Main             ( src/Main.hs, .stack-work/dist/x86_64-linux/Cabal-1.22.5.0/build/test-cassandra-cql/test-cassandra-cql-tmp/Main.o )
Linking .stack-work/dist/x86_64-linux/Cabal-1.22.5.0/build/test-cassandra-cql/test-cassandra-cql ...
test-cassandra-cql-0.1.0.0: copy/register
Installing executable(s) in
/tmp/test-cassandra-cql/.stack-work/install/x86_64-linux/lts-6.9/7.10.3/bin

It worked! The real question is: Why was I using stack build when next we’re just going to use stack ghci. Ah well, time to use stack ghci and try to run our main function:

/tmp/test-cassandra-cql λ stack ghci 
Using main module: 1. Package `test-cassandra-cql' component exe:test-cassandra-cql with main-is file: /tmp/test-cassandra-cql/src/Main.hs
Configuring GHCi with the following packages: test-cassandra-cql
GHCi, version 7.10.3: http://www.haskell.org/ghc/  :? for help
Ok, modules loaded: none.
[1 of 1] Compiling Main             ( /tmp/test-cassandra-cql/src/Main.hs, interpreted )
Ok, modules loaded: Main.
*Main> main 
failed to create a session due to temporary error (will retry) : NoAvailableServers
failed to create a session due to permanent error (will rethrow) : user interrupt
Interrupted.

Uh oh, we forgot to actually make a Cassandra server available. We can use Docker to quickly do that. If you don’t have Docker installed check out Docker’s installation page. So open up another terminal and type:

docker run -p 9042:9042 -p 9160:9160 -it --name some-cassandra cassandra:latest

It should look something like this:

/tmp/test-cassandra-cql λ docker run -p 9042:9042 -p 9160:9160 -it --name some-cassandra cassandra:latest
Unable to find image 'cassandra:latest' locally
latest: Pulling from library/cassandra
Digest: sha256:201f7a0fd29490032435945ee4985c0aa69eff36b2882e9a6d4b436eb140b1cc
Status: Downloaded newer image for cassandra:latest
INFO  15:57:05 Configuration location: file:/etc/cassandra/cassandra.yaml

... snip (random cassandra logs) ...

INFO  15:57:18 Scheduling approximate time-check task with a precision of 10 milliseconds
INFO  15:57:18 Created default superuser role 'cassandra'

Now if we go back to the other terminal and run our main function let’s see what happens:

*Main> main 
failed to create a session due to temporary error (will retry) : LocalProtocolError "unexpected version 4" "<startup>"
failed to create a session due to permanent error (will rethrow) : user interrupt

Uh oh, it’s an unexpected version. Maybe cassandra-cql doesn’t support latest version of cassandra? Let’s check the docs and the source repo. On the repo’s readme.md it says:

“Haskell client for Cassandra’s CQL binary protocol v2”

I believe the latest Cassandra uses CQL version 3.3, so we’ll need a little older Cassandra.

Let’s check what tags are available on the Docker Cassandra page

    2.1.15, 2.1 (2.1/Dockerfile)
    2.2.7, 2.2, 2 (2.2/Dockerfile)
    3.0.8, 3.0 (3.0/Dockerfile)
    3.7, 3, latest (3.7/Dockerfile)

Hm, let’s guess and try 3.0.8.

/tmp/test-cassandra-cql λ docker rm -f some-cassandra && docker run -p 9042:9042 -p 9160:9160 -it --name some-cassandra cassandra:3.0.8
some-cassandra
INFO  18:07:43 Configuration location: file:/etc/cassandra/cassandra.yaml
INFO  18:07:53 Starting listening for CQL clients on /0.0.0.0:9042 (unencrypted)...

... snip (random Cassandra logs) ...

INFO  18:07:53 Not starting RPC server as requested. Use JMX (StorageService->startRPCServer()) or nodetool (enablethrift) to start it
INFO  18:07:55 Created default superuser role 'cassandra'

Then try running our main function again:

*Main> main 
failed to create a session due to temporary error (will retry) : LocalProtocolError "unexpected version 4" "<startup>"

I’m not sure how CQL versions/Cassandra correspond so I’m just going to try the oldest tag and see if it works.

/tmp/test-cassandra-cql λ docker rm -f some-cassandra && docker run -p 9042:9042 -p 9160:9160 -it --name some-cassandra cassandra:2.1
some-cassandra
Unable to find image 'cassandra:2.1' locally
2.1: Pulling from library/cassandra

357ea8c3d80b: Already exists 
25eb4fd61cd9: Already exists 
a21cf6fac262: Already exists 
... snip (you know the rest by now) ...

Trying to run main again:

*Main> main 
failed to create a session due to permanent error (will rethrow) : Invalid "Keyspace 'test' does not exist" "USE test"
failed to create a session due to permanent error (will rethrow) : user interrupt
Interrupted.

Awesome! We’re almost there, just need to create the keyspace. Can we do that with cassandra-cql? I searched “keyspace” on the cassandra-cql github and one of the results was “tests/example-autocreate-keyspace.hs”. It looks like the example we copy pasted except it auto creates the keyspace. So copy the below into your main.hs:

{-# LANGUAGE OverloadedStrings, DataKinds #-}

import Database.Cassandra.CQL
import Control.Monad
import Control.Monad.CatchIO
import Control.Monad.Trans (liftIO)
import Data.ByteString.Char8 (ByteString)
import qualified Data.ByteString.Char8 as C
import Data.Text (Text)
import qualified Data.Text as T
import Data.UUID
import System.Random

dropSongs :: Query Schema () ()
dropSongs = "drop table songs"

createSongs :: Query Schema () ()
createSongs = "create table songs (id uuid PRIMARY KEY, title ascii, artist varchar, femaleSinger boolean, timesPlayed int, comment text)"

insertSong :: Query Write (UUID, ByteString, Text, Bool, Int, Maybe Text) ()
insertSong = "insert into songs (id, title, artist, femaleSinger, timesPlayed, comment) values (?, ?, ?, ?, ?, ?)"

getSongs :: Query Rows () (UUID, ByteString, Text, Bool, Int, Maybe Text)
getSongs = "select id, title, artist, femaleSinger, timesPlayed, comment from songs"

getOneSong :: Query Rows UUID (Text, Int)
getOneSong = "select artist, timesPlayed from songs where id=?"

ignoreDropFailure :: Cas () -> Cas ()
ignoreDropFailure code = code `catch` \exc -> case exc of
    ConfigError _ _ -> return ()  -- Ignore the error if the table doesn't exist
    Invalid _ _ -> return ()
    _               -> throw exc

main = do
    -- let auth = Just (PasswordAuthenticator "cassandra" "cassandra")
    let auth = Nothing

    -- this config will automatically run keyspace creation cql script during each connection initializationj
    -- suitable for a development purposes

    let ksCfg  = "CREATE KEYSPACE IF NOT EXISTS test1 WITH replication = { 'class' : 'SimpleStrategy', 'replication_factor' : '1' };"
    let poolCfg = (defaultConfig [("localhost", "9042")] "test1" auth){ piKeyspaceConfig = Just ksCfg}

    pool <- newPool' poolCfg -- servers, keyspace, maybe auth
    runCas pool $ do
        ignoreDropFailure $ liftIO . print =<< executeSchema QUORUM dropSongs ()
        liftIO . print =<< executeSchema QUORUM createSongs ()

        u1 <- liftIO randomIO
        u2 <- liftIO randomIO
        u3 <- liftIO randomIO
        executeWrite QUORUM insertSong (u1, "La Grange", "ZZ Top", False, 2, Nothing)
        executeWrite QUORUM insertSong (u2, "Your Star", "Evanescence", True, 799, Nothing)
        executeWrite QUORUM insertSong (u3, "Angel of Death", "Slayer", False, 50, Just "Singer Tom Araya")

        songs <- executeRows QUORUM getSongs ()
        liftIO $ forM_ songs $ \(uuid, title, artist, female, played, mComment) -> do
            putStrLn ""
            putStrLn $ "id            : "++show uuid
            putStrLn $ "title         : "++C.unpack title
            putStrLn $ "artist        : "++T.unpack artist
            putStrLn $ "female singer : "++show female
            putStrLn $ "times played  : "++show played
            putStrLn $ "comment       : "++show mComment

        liftIO $ putStrLn ""
        liftIO . print =<< executeRow QUORUM getOneSong u2

Then we can reload in stack ghci and re-run our main function:

Prelude> :r
[1 of 1] Compiling Main             ( /tmp/test-cassandra-cql/src/Main.hs, interpreted )

/tmp/test-cassandra-cql/src/Main.hs:43:73:
    parse error on input ‘KeyspaceConfig’
Failed, modules loaded: none.
Prelude>  1

I know from experience that at this point it’s probably because the version on github I copied the example from and the version on Hackage differ. Sure enough after checking I see the Hackage version is at 0.5.0.2 and the github version is at 0.6. I’m just going to use the github version, luckily stack makes that pretty easy.

Here is our current stack.yaml:

flags: {}
extra-package-dbs: []
packages:
- '.'
extra-deps:
- cassandra-cql-0.5.0.2
resolver: lts-6.9

I’ve been busy doing Go programming at work and can’t recall the git syntax for Stack, so I’ll refer to this big FAQ page they have. In about 15 seconds I see “I need to use a package (or version of a package) that is not available on Hackage, what should I do?” and then:

To install packages directly from a Git repository, use e.g.:

resolver: lts-2.10
packages:
- location:
    git: https://github.com/githubuser/reponame.git
    commit: somecommitID

So we can update our stack.yaml from above to:

flags: {}
extra-package-dbs: []
packages:
- '.'
- location:
    git: https://github.com/the-real-blackh/cassandra-cql.git
    commit: a34c7448630a25400ad20d42c5faca248f6fe644
resolver: lts-6.9

Then we can restart stack ghci:

Prelude> :r
[1 of 1] Compiling Main             ( /tmp/test-cassandra-cql/src/Main.hs, interpreted )

/tmp/test-cassandra-cql/src/Main.hs:43:73:
    ‘piKeyspaceConfig’ is not a (visible) constructor field name
Failed, modules loaded: none.
Prelude> :q
Leaving GHCi.
/tmp/test-cassandra-cql λ stack ghci
cassandra-cql-0.5.0.2: unregistering
cassandra-cql-0.6: configure
cassandra-cql-0.6: build
cassandra-cql-0.6: copy/register
test-cassandra-cql-0.1.0.0: configure
test-cassandra-cql-0.1.0.0: build
test-cassandra-cql-0.1.0.0: copy/register
Completed 2 action(s).
Using main module: 1. Package `test-cassandra-cql' component exe:test-cassandra-cql with main-is file: /tmp/test-cassandra-cql/src/Main.hs
Configuring GHCi with the following packages: test-cassandra-cql, cassandra-cql
GHCi, version 7.10.3: http://www.haskell.org/ghc/  :? for help

/tmp/test-cassandra-cql/.stack-work/downloaded/MXPl4BU8uauT/Database/Cassandra/CQL.hs:3:23: Warning:
    -XOverlappingInstances is deprecated: instead use per-instance pragmas OVERLAPPING/OVERLAPPABLE/OVERLAPS

/tmp/test-cassandra-cql/.stack-work/downloaded/MXPl4BU8uauT/Database/Cassandra/CQL.hs:169:8:
    Ambiguous module name ‘Control.Monad.Reader’:
      it was found in multiple packages:
      mtl-2.2.1@mtl_Aue4leSeVkpKLsfHIV51E8 monads-tf-0.1.0.3@monad_5M6wZtP1TpiAkFGzni5tIH

/tmp/test-cassandra-cql/.stack-work/downloaded/MXPl4BU8uauT/Database/Cassandra/CQL.hs:170:8:
    Ambiguous module name ‘Control.Monad.State’:
      it was found in multiple packages:
      mtl-2.2.1@mtl_Aue4leSeVkpKLsfHIV51E8 monads-tf-0.1.0.3@monad_5M6wZtP1TpiAkFGzni5tIH

/tmp/test-cassandra-cql/.stack-work/downloaded/MXPl4BU8uauT/Database/Cassandra/CQL.hs:171:18:
    Ambiguous module name ‘Control.Monad.RWS’:
      it was found in multiple packages:
      mtl-2.2.1@mtl_Aue4leSeVkpKLsfHIV51E8 monads-tf-0.1.0.3@monad_5M6wZtP1TpiAkFGzni5tIH

/tmp/test-cassandra-cql/.stack-work/downloaded/MXPl4BU8uauT/Database/Cassandra/CQL.hs:172:18:
    Ambiguous module name ‘Control.Monad.Error’:
      it was found in multiple packages:
      mtl-2.2.1@mtl_Aue4leSeVkpKLsfHIV51E8 monads-tf-0.1.0.3@monad_5M6wZtP1TpiAkFGzni5tIH

/tmp/test-cassandra-cql/.stack-work/downloaded/MXPl4BU8uauT/Database/Cassandra/CQL.hs:173:18:
    Ambiguous module name ‘Control.Monad.Writer’:
      it was found in multiple packages:
      mtl-2.2.1@mtl_Aue4leSeVkpKLsfHIV51E8 monads-tf-0.1.0.3@monad_5M6wZtP1TpiAkFGzni5tIH
Failed, modules loaded: none.

/tmp/test-cassandra-cql/.stack-work/downloaded/MXPl4BU8uauT/Database/Cassandra/CQL.hs:3:23: Warning:
    -XOverlappingInstances is deprecated: instead use per-instance pragmas OVERLAPPING/OVERLAPPABLE/OVERLAPS

/tmp/test-cassandra-cql/.stack-work/downloaded/MXPl4BU8uauT/Database/Cassandra/CQL.hs:169:8:
    Ambiguous module name ‘Control.Monad.Reader’:
      it was found in multiple packages:
      mtl-2.2.1@mtl_Aue4leSeVkpKLsfHIV51E8 monads-tf-0.1.0.3@monad_5M6wZtP1TpiAkFGzni5tIH

/tmp/test-cassandra-cql/.stack-work/downloaded/MXPl4BU8uauT/Database/Cassandra/CQL.hs:170:8:
    Ambiguous module name ‘Control.Monad.State’:
      it was found in multiple packages:
      mtl-2.2.1@mtl_Aue4leSeVkpKLsfHIV51E8 monads-tf-0.1.0.3@monad_5M6wZtP1TpiAkFGzni5tIH

/tmp/test-cassandra-cql/.stack-work/downloaded/MXPl4BU8uauT/Database/Cassandra/CQL.hs:171:18:
    Ambiguous module name ‘Control.Monad.RWS’:
      it was found in multiple packages:
      mtl-2.2.1@mtl_Aue4leSeVkpKLsfHIV51E8 monads-tf-0.1.0.3@monad_5M6wZtP1TpiAkFGzni5tIH

/tmp/test-cassandra-cql/.stack-work/downloaded/MXPl4BU8uauT/Database/Cassandra/CQL.hs:172:18:
    Ambiguous module name ‘Control.Monad.Error’:
      it was found in multiple packages:
      mtl-2.2.1@mtl_Aue4leSeVkpKLsfHIV51E8 monads-tf-0.1.0.3@monad_5M6wZtP1TpiAkFGzni5tIH

/tmp/test-cassandra-cql/.stack-work/downloaded/MXPl4BU8uauT/Database/Cassandra/CQL.hs:173:18:
    Ambiguous module name ‘Control.Monad.Writer’:
      it was found in multiple packages:
      mtl-2.2.1@mtl_Aue4leSeVkpKLsfHIV51E8 monads-tf-0.1.0.3@monad_5M6wZtP1TpiAkFGzni5tIH

/tmp/test-cassandra-cql/src/Main.hs:6:8:
    Ambiguous module name ‘Control.Monad.Trans’:
      it was found in multiple packages:
      mtl-2.2.1@mtl_Aue4leSeVkpKLsfHIV51E8 monads-tf-0.1.0.3@monad_5M6wZtP1TpiAkFGzni5tIH
Failed, modules loaded: none.

<no location info>:
    Could not find module ‘Database.Cassandra.CQL’
    It is a member of the hidden package ‘cassandra-cql-0.6@cassa_FMk92m2DzAC8Q6AfWWp6qA’.

Whoa, it partially worked but then barfed on an error I’ve both not seen before and don’t currently know what it is. I have a hunch it’s something to do with multiple but different versions of cassandra-cql’s dependencies. So I’ll try something quick and easy, the equivalent of “turn it off and then on again”:

/tmp/test-cassandra-cql λ rm -r .stack-work/
/tmp/test-cassandra-cql λ stack build 
cassandra-cql-0.6: configure
cassandra-cql-0.6: build
cassandra-cql-0.6: copy/register
test-cassandra-cql-0.1.0.0: configure
test-cassandra-cql-0.1.0.0: build
test-cassandra-cql-0.1.0.0: copy/register
Completed 2 action(s).
/tmp/test-cassandra-cql λ stack ghci 
Using main module: 1. Package `test-cassandra-cql' component exe:test-cassandra-cql with main-is file: /tmp/test-cassandra-cql/src/Main.hs
Configuring GHCi with the following packages: test-cassandra-cql, cassandra-cql
GHCi, version 7.10.3: http://www.haskell.org/ghc/  :? for help

/tmp/test-cassandra-cql/.stack-work/downloaded/MXPl4BU8uauT/Database/Cassandra/CQL.hs:3:23: Warning:
    -XOverlappingInstances is deprecated: instead use per-instance pragmas OVERLAPPING/OVERLAPPABLE/OVERLAPS

/tmp/test-cassandra-cql/.stack-work/downloaded/MXPl4BU8uauT/Database/Cassandra/CQL.hs:169:8:
    Ambiguous module name ‘Control.Monad.Reader’:
      it was found in multiple packages:
      mtl-2.2.1@mtl_Aue4leSeVkpKLsfHIV51E8 monads-tf-0.1.0.3@monad_5M6wZtP1TpiAkFGzni5tIH

/tmp/test-cassandra-cql/.stack-work/downloaded/MXPl4BU8uauT/Database/Cassandra/CQL.hs:170:8:
    Ambiguous module name ‘Control.Monad.State’:
      it was found in multiple packages:
      mtl-2.2.1@mtl_Aue4leSeVkpKLsfHIV51E8 monads-tf-0.1.0.3@monad_5M6wZtP1TpiAkFGzni5tIH

/tmp/test-cassandra-cql/.stack-work/downloaded/MXPl4BU8uauT/Database/Cassandra/CQL.hs:171:18:
    Ambiguous module name ‘Control.Monad.RWS’:
      it was found in multiple packages:
      mtl-2.2.1@mtl_Aue4leSeVkpKLsfHIV51E8 monads-tf-0.1.0.3@monad_5M6wZtP1TpiAkFGzni5tIH

/tmp/test-cassandra-cql/.stack-work/downloaded/MXPl4BU8uauT/Database/Cassandra/CQL.hs:172:18:
    Ambiguous module name ‘Control.Monad.Error’:
      it was found in multiple packages:
      mtl-2.2.1@mtl_Aue4leSeVkpKLsfHIV51E8 monads-tf-0.1.0.3@monad_5M6wZtP1TpiAkFGzni5tIH

/tmp/test-cassandra-cql/.stack-work/downloaded/MXPl4BU8uauT/Database/Cassandra/CQL.hs:173:18:
    Ambiguous module name ‘Control.Monad.Writer’:
      it was found in multiple packages:
      mtl-2.2.1@mtl_Aue4leSeVkpKLsfHIV51E8 monads-tf-0.1.0.3@monad_5M6wZtP1TpiAkFGzni5tIH
Failed, modules loaded: none.

/tmp/test-cassandra-cql/.stack-work/downloaded/MXPl4BU8uauT/Database/Cassandra/CQL.hs:3:23: Warning:
    -XOverlappingInstances is deprecated: instead use per-instance pragmas OVERLAPPING/OVERLAPPABLE/OVERLAPS

/tmp/test-cassandra-cql/.stack-work/downloaded/MXPl4BU8uauT/Database/Cassandra/CQL.hs:169:8:
    Ambiguous module name ‘Control.Monad.Reader’:
      it was found in multiple packages:
      mtl-2.2.1@mtl_Aue4leSeVkpKLsfHIV51E8 monads-tf-0.1.0.3@monad_5M6wZtP1TpiAkFGzni5tIH

/tmp/test-cassandra-cql/.stack-work/downloaded/MXPl4BU8uauT/Database/Cassandra/CQL.hs:170:8:
    Ambiguous module name ‘Control.Monad.State’:
      it was found in multiple packages:
      mtl-2.2.1@mtl_Aue4leSeVkpKLsfHIV51E8 monads-tf-0.1.0.3@monad_5M6wZtP1TpiAkFGzni5tIH

/tmp/test-cassandra-cql/.stack-work/downloaded/MXPl4BU8uauT/Database/Cassandra/CQL.hs:171:18:
    Ambiguous module name ‘Control.Monad.RWS’:
      it was found in multiple packages:
      mtl-2.2.1@mtl_Aue4leSeVkpKLsfHIV51E8 monads-tf-0.1.0.3@monad_5M6wZtP1TpiAkFGzni5tIH

/tmp/test-cassandra-cql/.stack-work/downloaded/MXPl4BU8uauT/Database/Cassandra/CQL.hs:172:18:
    Ambiguous module name ‘Control.Monad.Error’:
      it was found in multiple packages:
      mtl-2.2.1@mtl_Aue4leSeVkpKLsfHIV51E8 monads-tf-0.1.0.3@monad_5M6wZtP1TpiAkFGzni5tIH

/tmp/test-cassandra-cql/.stack-work/downloaded/MXPl4BU8uauT/Database/Cassandra/CQL.hs:173:18:
    Ambiguous module name ‘Control.Monad.Writer’:
      it was found in multiple packages:
      mtl-2.2.1@mtl_Aue4leSeVkpKLsfHIV51E8 monads-tf-0.1.0.3@monad_5M6wZtP1TpiAkFGzni5tIH

/tmp/test-cassandra-cql/src/Main.hs:6:8:
    Ambiguous module name ‘Control.Monad.Trans’:
      it was found in multiple packages:
      mtl-2.2.1@mtl_Aue4leSeVkpKLsfHIV51E8 monads-tf-0.1.0.3@monad_5M6wZtP1TpiAkFGzni5tIH
Failed, modules loaded: none.

<no location info>:
    Could not find module ‘Database.Cassandra.CQL’
    It is a member of the hidden package ‘cassandra-cql-0.6@cassa_FMk92m2DzAC8Q6AfWWp6qA’.
Prelude> 

After some reflection, figured out that this issue is caused by these libraries being in our Cabal file:

name:                test-cassandra-cql
version:             0.1.0.0
synopsis:            Simple project template from stack
description:         Please see README.md
homepage:            https://github.com/githubuser/test-cassandra-cql#readme
license:             BSD3
license-file:        LICENSE
author:              Author name here
maintainer:          example@example.com
copyright:           2016 Author name here
category:            Web
build-type:          Simple
cabal-version:       >=1.10

executable test-cassandra-cql
  hs-source-dirs:      src
  main-is:             Main.hs
  default-language:    Haskell2010
  build-depends:       base >= 4.7 && < 5,
                       cassandra-cql,
                       MonadCatchIO-transformers,
                       monads-tf,
                       bytestring,
                       text,
                       uuid,
                       random

Update it to:

name:                test-cassandra-cql
version:             0.1.0.0
synopsis:            Simple project template from stack
description:         Please see README.md
homepage:            https://github.com/githubuser/test-cassandra-cql#readme
license:             BSD3
license-file:        LICENSE
author:              Author name here
maintainer:          example@example.com
copyright:           2016 Author name here
category:            Web
build-type:          Simple
cabal-version:       >=1.10

executable test-cassandra-cql
  hs-source-dirs:      src
  main-is:             Main.hs
  default-language:    Haskell2010
  build-depends:       base >= 4.7 && < 5,
                       cassandra-cql,
                       MonadCatchIO-transformers,
                       monads-tf,
                       bytestring,
                       text,
                       uuid,
                       random

Then run stack ghci again and you’ll see those errors have gone away (though we have a lot of warnings):

/tmp/test-cassandra-cql λ stack ghci
test-cassandra-cql-0.1.0.0: build

--  While building package test-cassandra-cql-0.1.0.0 using:
      /home/cody/.stack/setup-exe-cache/x86_64-linux/setup-Simple-Cabal-1.22.5.0-ghc-7.10.3 --builddir=.stack-work/dist/x86_64-linux/Cabal-1.22.5.0 build exe:test-cassandra-cql --ghc-options " -ddump-hi -ddump-to-file"
    Process exited with code: ExitFailure 1
    Logs have been written to: /tmp/test-cassandra-cql/.stack-work/logs/test-cassandra-cql-0.1.0.0.log

    Preprocessing executable 'test-cassandra-cql' for
    test-cassandra-cql-0.1.0.0...
    
    /tmp/test-cassandra-cql/src/Main.hs:5:8:
        Could not find module ‘Control.Monad.CatchIO’
        It is a member of the hidden package ‘MonadCatchIO-transformers-0.3.1.3@Monad_Jr5N1jwqWOiDadnjgHIYSh’.
        Perhaps you need to add ‘MonadCatchIO-transformers’ to the build-depends in your .cabal file.
        Use -v to see a list of the files searched for.
    
    /tmp/test-cassandra-cql/src/Main.hs:6:8:
        Could not find module ‘Control.Monad.Trans’
        It is a member of the hidden package ‘monads-tf-0.1.0.3@monad_5M6wZtP1TpiAkFGzni5tIH’.
        Perhaps you need to add ‘monads-tf’ to the build-depends in your .cabal file.
        It is a member of the hidden package ‘mtl-2.2.1@mtl_Aue4leSeVkpKLsfHIV51E8’.
        Perhaps you need to add ‘mtl’ to the build-depends in your .cabal file.
        Use -v to see a list of the files searched for.
    
    /tmp/test-cassandra-cql/src/Main.hs:8:18:
        Could not find module ‘Data.ByteString.Char8’
        It is a member of the hidden package ‘bytestring-0.10.6.0@bytes_6VWy06pWzJq9evDvK2d4w6’.
        Perhaps you need to add ‘bytestring’ to the build-depends in your .cabal file.
        Use -v to see a list of the files searched for.
    
    /tmp/test-cassandra-cql/src/Main.hs:10:18:
        Could not find module ‘Data.Text’
        It is a member of the hidden package ‘text-1.2.2.1@text_HmqVQnZSpjaC156ABqPhne’.
        Perhaps you need to add ‘text’ to the build-depends in your .cabal file.
        Use -v to see a list of the files searched for.
    
    /tmp/test-cassandra-cql/src/Main.hs:11:8:
        Could not find module ‘Data.UUID’
        It is a member of the hidden package ‘uuid-1.3.12@uuid_BjRErtmELFC2d9F8IOzq95’.
        Perhaps you need to add ‘uuid’ to the build-depends in your .cabal file.
        Use -v to see a list of the files searched for.
Warning: build failed, but optimistically launching GHCi anyway
Using main module: 1. Package `test-cassandra-cql' component exe:test-cassandra-cql with main-is file: /tmp/test-cassandra-cql/src/Main.hs
Configuring GHCi with the following packages: test-cassandra-cql, cassandra-cql
GHCi, version 7.10.3: http://www.haskell.org/ghc/  :? for help

/tmp/test-cassandra-cql/.stack-work/downloaded/MXPl4BU8uauT/Database/Cassandra/CQL.hs:3:23: Warning:
    -XOverlappingInstances is deprecated: instead use per-instance pragmas OVERLAPPING/OVERLAPPABLE/OVERLAPS
[1 of 1] Compiling Database.Cassandra.CQL ( /tmp/test-cassandra-cql/.stack-work/downloaded/MXPl4BU8uauT/Database/Cassandra/CQL.hs, interpreted )

/tmp/test-cassandra-cql/.stack-work/downloaded/MXPl4BU8uauT/Database/Cassandra/CQL.hs:172:1: Warning:
    Module ‘Control.Monad.Error’ is deprecated:
      Use Control.Monad.Except instead

/tmp/test-cassandra-cql/.stack-work/downloaded/MXPl4BU8uauT/Database/Cassandra/CQL.hs:303:29: Warning:
    In the use of type constructor or class ‘Control.Monad.Error.Error’
    (imported from Control.Monad.Error, but defined in transformers-0.4.2.0:Control.Monad.Trans.Error):
    Deprecated: "Use Control.Monad.Trans.Except instead"

/tmp/test-cassandra-cql/.stack-work/downloaded/MXPl4BU8uauT/Database/Cassandra/CQL.hs:303:77: Warning:
    In the use of type constructor or class ‘Control.Monad.Error.ErrorT’
    (imported from Control.Monad.Error, but defined in transformers-0.4.2.0:Control.Monad.Trans.Error):
    Deprecated: "Use Control.Monad.Trans.Except instead"

/tmp/test-cassandra-cql/.stack-work/downloaded/MXPl4BU8uauT/Database/Cassandra/CQL.hs:837:38: Warning:
    Unticked promoted constructor: ‘Schema’.
    Use ‘'Schema’ instead of ‘Schema’.

/tmp/test-cassandra-cql/.stack-work/downloaded/MXPl4BU8uauT/Database/Cassandra/CQL.hs:843:55: Warning:
    Unticked promoted constructor: ‘Rows’.
    Use ‘'Rows’ instead of ‘Rows’.

/tmp/test-cassandra-cql/.stack-work/downloaded/MXPl4BU8uauT/Database/Cassandra/CQL.hs:1788:22: Warning:
    Unticked promoted constructor: ‘Rows’.
    Use ‘'Rows’ instead of ‘Rows’.

/tmp/test-cassandra-cql/.stack-work/downloaded/MXPl4BU8uauT/Database/Cassandra/CQL.hs:1799:23: Warning:
    Unticked promoted constructor: ‘Write’.
    Use ‘'Write’ instead of ‘Write’.

/tmp/test-cassandra-cql/.stack-work/downloaded/MXPl4BU8uauT/Database/Cassandra/CQL.hs:1816:21: Warning:
    Unticked promoted constructor: ‘Rows’.
    Use ‘'Rows’ instead of ‘Rows’.

/tmp/test-cassandra-cql/.stack-work/downloaded/MXPl4BU8uauT/Database/Cassandra/CQL.hs:1823:59: Warning:
    Unticked promoted constructor: ‘Rows’.
    Use ‘'Rows’ instead of ‘Rows’.

/tmp/test-cassandra-cql/.stack-work/downloaded/MXPl4BU8uauT/Database/Cassandra/CQL.hs:1835:23: Warning:
    Unticked promoted constructor: ‘Write’.
    Use ‘'Write’ instead of ‘Write’.

/tmp/test-cassandra-cql/.stack-work/downloaded/MXPl4BU8uauT/Database/Cassandra/CQL.hs:1847:24: Warning:
    Unticked promoted constructor: ‘Schema’.
    Use ‘'Schema’ instead of ‘Schema’.

/tmp/test-cassandra-cql/.stack-work/downloaded/MXPl4BU8uauT/Database/Cassandra/CQL.hs:1859:24: Warning:
    Unticked promoted constructor: ‘Schema’.
    Use ‘'Schema’ instead of ‘Schema’.
Ok, modules loaded: Database.Cassandra.CQL.
[2 of 2] Compiling Main             ( /tmp/test-cassandra-cql/src/Main.hs, interpreted )

/tmp/test-cassandra-cql/src/Main.hs:14:20: Warning:
    Unticked promoted constructor: ‘Schema’.
    Use ‘'Schema’ instead of ‘Schema’.

/tmp/test-cassandra-cql/src/Main.hs:17:22: Warning:
    Unticked promoted constructor: ‘Schema’.
    Use ‘'Schema’ instead of ‘Schema’.

/tmp/test-cassandra-cql/src/Main.hs:20:21: Warning:
    Unticked promoted constructor: ‘Write’.
    Use ‘'Write’ instead of ‘Write’.

/tmp/test-cassandra-cql/src/Main.hs:23:19: Warning:
    Unticked promoted constructor: ‘Rows’.
    Use ‘'Rows’ instead of ‘Rows’.

/tmp/test-cassandra-cql/src/Main.hs:26:21: Warning:
    Unticked promoted constructor: ‘Rows’.
    Use ‘'Rows’ instead of ‘Rows’.
Ok, modules loaded: Database.Cassandra.CQL, Main.
*Main Database.Cassandra.CQL> 

Now, we can try to run our main function again:

*Main Database.Cassandra.CQL> main
(CREATED,Keyspace "TABLE",Table "test1")

id            : 8a33cc5c-5d1c-464f-ab3e-19c491f240d8
title         : Your Star
artist        : Evanescence
female singer : True
times played  : 799
comment       : Nothing

id            : 662f407a-7362-47c8-bc37-b859847a031a
title         : La Grange
artist        : ZZ Top
female singer : False
times played  : 2
comment       : Nothing

id            : 017e4971-88a2-48f1-b7c3-98cde924a6cc
title         : Angel of Death
artist        : Slayer
female singer : False
times played  : 50
comment       : Just "Singer Tom Araya"

Just ("Evanescence",799)

And success!

Part 2 will be a retrospective reflecting on this process and how connecting to Cassandra in Haskell could be made easier for someone who might not have the luxury of time and patience that I had to get this working.