             to
             unique-key-tables)))
 
-(defn has-lookups?
-  [^Entity entity]
+(s/defn has-lookups?
+  [entity :- Entity]
   (boolean
    (or (not-empty (t/mutable-secondary-tables entity))
        (not-empty (t/mutable-lookup-tables entity)))))
 
-(defn update-secondaries-and-lookups
+(s/defn update-secondaries-and-lookups
   "update non-LWT secondary and lookup entries"
 
-  ([^Session session ^Entity entity old-record updated-record-with-keys]
-   (with-context deferred-context
-     (mlet [stale-secondary-responses (delete-stale-secondaries
-                                       session
-                                       entity
-                                       old-record
-                                       updated-record-with-keys)
-
-            stale-lookup-responses (delete-stale-lookups
-                                    session
-                                    entity
-                                    old-record
-                                    updated-record-with-keys)
-
-            secondary-reponses (upsert-secondaries
-                                session
-                                entity
-                                updated-record-with-keys)
-
-            lookup-responses (upsert-lookups
-                              session
-                              entity
-                              old-record
-                              updated-record-with-keys)]
-
-       (return updated-record-with-keys)))))
-
-(defn upsert*
+  [session :- Session
+   entity :- Entity
+   old-record :- t/MaybeRecordSchema
+   updated-record-with-keys :- t/MaybeRecordSchema]
+  (with-context deferred-context
+    (mlet [stale-secondary-responses (delete-stale-secondaries
+                                      session
+                                      entity
+                                      old-record
+                                      updated-record-with-keys)
+
+           stale-lookup-responses (delete-stale-lookups
+                                   session
+                                   entity
+                                   old-record
+                                   updated-record-with-keys)
+
+           secondary-reponses (upsert-secondaries
+                               session
+                               entity
+                               updated-record-with-keys)
+
+           lookup-responses (upsert-lookups
+                             session
+                             entity
+                             old-record
+                             updated-record-with-keys)]
+
+      (return updated-record-with-keys))))
+
+(s/defschema UpsertOptsSchema
+  (s/maybe
+   {(s/optional-key :if-not-exists) s/Bool
+    (s/optional-key :only-if) [s/Any]
+    (s/optional-key :columns) [(s/one s/Keyword :column) s/Keyword]
+    (s/optional-key :where) [s/Any]
+    (s/optional-key :using) {(s/optional-key :timestamp) s/Int
+                             (s/optional-key :ttl) s/Int}}))
+
+(s/defn upsert*
   "upsert a single instance, upserting primary, secondary, unique-key and
    lookup records as required and deleting stale secondary, unique-key and
    lookup records

      (return
       [:ok record :upserted]))))

(defn stale-secondary-key-value
  [^Entity entity old-record new-record secondary-table]
  (let [key (:key secondary-table)
        old-key-value (k/extract-key-value key old-record)
        new-key-value (k/extract-key-value key new-record)]

                         stale-key-value)
          (return [:ok nil :no-stale-secondary])))))))

(s/defn upsert-secondaries
  [session :- Session
   entity :- Entity
   record :- t/MaybeRecordSchema]
  (combine-responses
   (for [t (t/mutable-secondary-tables entity)]
     (let [k (:key t)]
