Commits

Shantanu Kumar committed 3912a3c

add :read-only key in spec, throw WriteNotAllowedException when :read-only true, add assoc-xxx functions for context-free spec key/values

  • Participants
  • Parent commits af2425a

Comments (0)

Files changed (2)

 * Default spec as a static var `default-dbspec`
 * Convenience macros/functions for context-free resources
 * Additional keys in spec
+  * :read-only    boolean (that ensures DB writes can be controlled)
   * :show-sql-fn  fn (that prints out the SQL)
+* Introduce WriteNotAllowedException for situations when write-operations cannot
+  be executed
 * [TODO] More introspection fns
 
 

File src/main/clj/org/bituf/clj_dbspec.clj

   "DB-Spec related functions and vars to provide only the building blocks.
   Usually only libraries would use this."
   (:import
-    (java.util List)
-    (java.sql  Connection DatabaseMetaData DriverManager ResultSet)
-    (javax.sql DataSource))
+    (java.util            List)
+    (java.sql             Connection DatabaseMetaData DriverManager ResultSet)
+    (javax.sql            DataSource)
+    (org.bituf.clj_dbspec WriteNotAllowedException))
   (:require
     [clojure.string :as sr]
     [clojure.pprint :as pp]
 
 
 (def ^{:doc "Clj-DBSpec version (only major and minor)"}
-      version 0.1)
+      version [0 2])
 
 
 (defn ^Connection make-connection
    :dbmetadata  (array-map)
    :catalog     nil
    :schema      nil
+   :read-only   false
    :show-sql    true
    :show-sql-fn (fn [^String sql] (println sql))
    :clj-to-db   (fn [iden]
                   1. Catalog name - SHOULD be converted using db-iden
     :schema      (Clojure form - String, Keyword etc.; default nil)
                   1. Schema name - SHOULD be converted using db-iden
-    :show-sql    (Booleanl default true)
+    :read-only   (Boolean default false)
+                  1. If true, all READ operations should execute as usual and
+                     all WRITE operations should throw WriteNotAllowedException.
+                  2. I false, both READ and WRITE operations execute fine
+    :show-sql    (Boolean default true)
                   1. If true, SQL statements should be printed.
     :clj-to-db   (function, default: to string, replace '-' => '_')
                   1. Dictates how are identifiers converted from Clojure to
   "Convenience macro to bind *dbspec* to `spec` merged into *dbspec* and
   execute body of code in that context."
   [spec & body]
-  `(binding [*dbspec* (into (or *dbspec* {}) spec)]
+  `(binding [*dbspec* (into (or *dbspec* {}) ~spec)]
      ~@body))
 
 
+(defn value
+  "Assuming that (1) a function is not a value, (2) and that `v` can either be
+  a no-arg function that returns value, or a value itself, return value."
+  [v]
+  (if (fn? v) (v)
+    v))
+
+
 ;; --- Resource that do not require context-building or cleanup ---
 
 
 
 
 (defn assoc-datasource
-  "Add :datasource object to the spec. Example usage:
+  "Add :datasource object to the spec. `ds` can be a DataSource object or a
+  no-arg function that returns one.
+  Example usage:
   ;; assume: `ds` is a javax.sql.DataSource object
   (def spec (assoc-datasource {} ds))
   (with-dbspec spec
     ;; code that operates on the datasource
     ..body..)"
-  [spec ^DataSource ds] {:post [(map? %)]
-                         :pre  [(instance? DataSource ds)]}
-  (assoc-kvmap spec {:datasource ds
+  [spec ds] {:post [(map? %)]
+             :pre  [(map? spec) (or (instance? DataSource ds) (fn? ds))]}
+  (assoc-kvmap spec {:datasource (value ds)
                      :connection nil}))
 
 
+(defn assoc-readonly
+  "Add :read-only flag to the spec. `flag` can be either a boolean value or a
+  no-arg function that returns one."
+  ([spec flag] {:post [(map? %)]
+                :pre  [(map? spec)]}
+    (assoc-kvmap spec {:read-only (value flag)}))
+  ([spec] {:post [(map? %)]
+           :pre  [(map? spec)]}
+    (assoc-readonly spec)))
+
+
+(defn read-only?
+  "Return true if spec is in read-only mode, false otherwise."
+  ([spec] {:pre [(map? spec)]}
+    (if (:read-only spec) true
+      false))
+  ([] (read-only? *dbspec*)))
+
+
+(defn verify-writable
+  "Return true if spec is NOT in :read-only mode, throw WriteNotAllowedException
+  otherwise."
+  ([spec] {:pre [(map? spec)]}
+    (or (not (read-only? spec))
+      (throw (WriteNotAllowedException.
+               (format "Spec is in READ-ONLY mode: %s" (pp/pprint spec))))))
+  ([]
+    (verify-writable *dbspec*)))
+
+
 ;; --- Resources with context-building (and possibly cleanup) ---
 
 
     f))
 
 
+(defn wrap-read-only
+  "Add a :read-only flag to the spec and excute f in that context. `flag` is
+  either a no-arg function that returns boolean flag, or is a boolean flag.
+  `f` is the application function to be wrapped."
+  [flag f] {:post [(fn? %)]
+            :pre  [(fn? f)]}
+  (let [b (if (fn? flag) (flag)
+            (or (and flag true) false))]
+    (wrap-dbspec {:read-only b}
+      f)))
+
+
 (defn wrap-datasource-conn
   "Middleware to associate a new :connection object in *dbspec*. Assuming `f`
   is a fn that may read *dbspec*, return fn that binds :connection in *dbspec*
 (declare dbmeta)
 
 (defmethod make-dbspec DataSource [ds]
-  (array-map
-    :datasource ds
-    :dbmetadata ((wrap-datasource-conn ds
-                   #(dbmeta (:connection *dbspec*))))))
+  {:datasource ds})
 
 
 (defmethod make-dbspec Connection [conn]
-  (array-map
-    :connection conn
-    :dbmetadata (dbmeta conn)))
+  {:connection conn})
 
 
 ;; ----- Clojure/Database identifier conversion -----