   [node]
   (-> (doto (ClientConfig. "" "")
         (.setReconnectOnConnectionLoss true))
-      (ClientFactory/createClient)
-      (doto
-        (.createConnection (name node)))))
+        (ClientFactory/createClient)
+        (doto
+          (.createConnection (name node)))))
+
+(defn volt-table->map
+  "Converts a VoltDB table to a data structure like
+
+  {:status status-code
+   :schema [{:column_name VoltType, ...}]
+   :rows [{:k1 v1, :k2 v2}, ...]}"
+  [^VoltTable t]
+  (let [column-count (.getColumnCount t)
+        column-names (loop [i     0
+                            cols  (transient [])]
+                       (if (= i column-count)
+                         (persistent! cols)
+                         (recur (inc i)
+                                (conj! cols (keyword (.getColumnName t i))))))
+        basis        (apply create-struct column-names)
+        column-types (loop [i 0
+                            types (transient [])]
+                       (if (= i column-count)
+                         (persistent! types)
+                         (recur (inc i)
+                                (conj! types (.getColumnType t i)))))
+        row          (doto (.cloneRow t)
+                       (.resetRowPosition))]
+  {:status (.getStatusCode t)
+   :schema (apply struct basis column-types)
+   :rows (loop [rows (transient [])]
+           (if (.advanceRow row)
+             (let [cols (object-array column-count)]
+               (loop [j 0]
+                 (when (< j column-count)
+                   (aset cols j (.get row j (nth column-types j)))
+                   (recur (inc j))))
+               (recur (conj! rows (clojure.lang.PersistentStructMap/construct
+                                    basis
+                                    (seq cols)))))
+             ; Done
+             (persistent! rows)))}))
+
+(defn call!
+  "Call a stored procedure and returns a seq of VoltTable results."
+  [client procedure & args]
+  (let [res (.callProcedure client procedure (into-array Object args))]
+    ; Docs claim callProcedure will throw, but tutorial checks anyway so ???
+    (assert (= (.getStatus res) ClientResponse/SUCCESS))
+    (map volt-table->map (.getResults res))))
+
+(defn ad-hoc!
+  "Run an ad-hoc SQL stored procedure."
+  [client & args]
+  (apply call! client "@AdHoc" args))
 
 (defn client
   "A single-register client."

  (:require [jepsen [core       :as jepsen]
                    [db         :as db]
                    [control    :as c :refer [|]]
                    [client     :as client]
                    [tests      :as tests]]
            [jepsen.os.debian     :as debian]
            [jepsen.control.util  :as cu]
            [clojure.string :as str]
            [clojure.data.xml :as xml]
            [clojure.tools.logging :refer [info warn]])
  (:import (org.voltdb.client Client
                              ClientConfig
                              ClientFactory)))

(def username "voltdb")
(def base-dir "/opt/voltdb")

         :name    "voltdb"
         :os      debian/os
         :client  (client)
         :db      (db url)
         :model   (model/cas-register 0)
         :checker (checker/compose {:linear checker/linearizable
                                    :perf   (checker/perf)})
         :nemesis (nemesis/partition-random-halves)
         :generator (->> (gen/mix [r w cas])
                         (gen/stagger 1)
                         (gen/nemesis
                           (gen/seq (cycle [(gen/sleep 30)
                                            {:type :info :f :start}
                                            (gen/sleep 30)
                                            {:type :info :f :stop}])))
                         (gen/time-limit 200))))
