Commits

Shantanu Kumar committed a7e2870

clean up Clojure/DB identifier conversion,
rename identifier conversion functions,
add DB introspection functions

  • Participants
  • Parent commits 474f43e

Comments (0)

Files changed (3)

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

   (.getConnection ds))
 
 
-;; ----- Utility fns for converting Clojure/Database identifiers -----
-
-
-(defrecord DBIdentSpec [replace-map transform-fn])
-
-
-(defmacro make-dbidentspec-with
-  "Return an instance of DBIdentSpec.
-  Arguments:
-    replace-map  (map) of String match-tokens and String replacement-tokens
-    transform-fn (fn)  that accepts one String argument and returns a form
-                       as appropriate (for use in SQL or Clojure)
-  Example:
-    (make-dbidentspec {\"-\" nil} s
-      (clojure.string/lower-case s))"
-  [replace-map string-name & transform-fnbody]
-  `(assert (map? ~replace-map))
-  `(DBIdentSpec. ~replace-map
-     (fn [^String ~string-name] ~@transform-fnbody)))
-
-
-(defn transform-name
-  "Convert given 'str-name' using the transformation function in 'ident-spec'
-  after replacing all occurrences as per the replacement map."
-  [^String str-name ^DBIdentSpec ident-spec]
-  (let [xform-fn (:transform-fn ident-spec)]
-    (xform-fn
-      (in/replace-all str-name ident-spec))))
-
-
 ;; ----- The Db-Spec definition (data structure) -----
 
 
     :connection  (java.sql.Connection, default: nil)
                   1. If the connection is not taken from a datasource you SHOULD
                      include a cached :dbmetadata value while re-binding.
-    :dbmetadata  (map, default: empty map)
-                  1. This is usually result of get-dbmetadata function.
+    :dbmeta      (map, default: empty map)
+                  1. This is usually result of dbmeta function.
     :clj-to-db   (DBIdentSpec, default: to lower-case string, replace '-' => '_')
                   1. Dictates how are identifiers converted from Clojure to
                      the Database.
                  :datasource nil
                  :connection nil
                  :dbmetadata (array-map)
-                 :clj-to-db  (make-dbidentspec-with {"-" "_" ; hyphen to underscore
-                                                    } s
-                               (sr/lower-case s))
-                 :db-to-clj  (make-dbidentspec-with {"_" "-" ; underscore to hyphen
-                                                    } s
-                               (keyword (sr/lower-case s)))))
+                 :clj-to-db  (fn [iden]
+                               (if (string? iden) iden
+                                 (apply str (replace {\- \_}
+                                              (in/as-string iden)))))
+                 :db-to-clj  (fn [^String iden]
+                               (keyword (apply str (replace {\_ \-}
+                                                     (sr/lower-case iden)))))))
 
 
 ;; ----- Convenience macros for binding values into DB-Spec -----
   class)
 
 
-(declare get-dbmetadata)
+(declare dbmeta)
 
 (defmethod make-dbspec DataSource [ds]
   (array-map
     :datasource ds
     :dbmetadata (with-datasource-connection ds
-                  (get-dbmetadata (:connection *dbspec*)))))
+                  (dbmeta (:connection *dbspec*)))))
 
 
 (defmethod make-dbspec Connection [conn]
   (array-map
     :connection conn
-    :dbmetadata (get-dbmetadata conn)))
+    :dbmetadata (dbmeta conn)))
 
 
 ;; ----- Clojure/Database identifier conversion -----
 
 
-(defn ^String clj-to-dbident
-  "Given presumably a Clojure DB entity (database, table, column, column type)
-  name, convert to String and apply transformations for use in SQL statements."
+(defn ^String db-iden
+  "Convert Clojure form to database identifier (schema/table/column name etc.)"
   [clj-form]
-  (transform-name
-    (in/as-string clj-form) (:clj-to-db *dbspec*)))
+  ((:clj-to-db *dbspec*) clj-form))
 
 
-(defn db-to-cljident
-  "Given presumably a String DB entity (database, table, column, column type)
-  name, apply transformations on it for use in Clojure environment."
-  [^String db-ident]
-  (transform-name
-    (in/as-string db-ident) (:db-to-clj *dbspec*)))
+(defn clj-iden
+  "Convert database identifier (schema/table/column name etc.) to Clojure form."
+  [^String db-form]
+  ((:db-to-clj *dbspec*) db-form))
 
 
 ;; ----- Result-set and Metadata functions -----
 
 
 (defprotocol IRow
-  (asVec [this] "Return the vector representation")
-  (asMap [this] "Return the map representation"))
+  (labels [this] "Return a vector of labels in the order they exist in the row")
+  (asVec  [this] "Return a vector of values in the order they exist in the row")
+  (asMap  [this] "Return the map representation"))
 
 
 ;; A row is a sequence of column values. It also behaves as a map - you can
 ;; access column values using integer index (like vector) or using the Clojure
 ;; form of the column label (like a map).
 ;; Note: Rows may have duplicate column titles (but index is always unique).
-(deftype Row [colvalue-vec colvalue-map conv-fn]
+(deftype Row [collabel-vec colvalue-vec colvalue-map conv-fn]
   clojure.lang.ILookup
-    (valAt [this key not-found] (conv-fn key not-found))
-    (valAt [this key]           (.valAt this key nil))
+    (valAt  [this key not-found] (conv-fn key not-found))
+    (valAt  [this key]           (.valAt this key nil))
+  clojure.lang.IFn
+    (invoke [this key not-found] (.valAt this key not-found))
+    (invoke [this key]           (.valAt this key nil))
   IRow
-    (asVec [this] colvalue-vec)
-    (asMap [this] colvalue-map)
+    (labels [this] collabel-vec)
+    (asVec  [this] colvalue-vec)
+    (asMap  [this] colvalue-map)
   Object
     (toString [this] (with-out-str
+                       (println collabel-vec)
                        (println colvalue-vec)
                        (println colvalue-map)))
     (hashCode [this] (.hashCode (.toString this)))
-    (equals [this that] (and (instance? Row that)
-                          (= colvalue-vec (.asVec ^Row that))
-                          (= colvalue-map (.asMap ^Row that)))))
+    (equals   [this that] (and (instance? Row that)
+                            (= colvalue-vec (.asVec ^Row that))
+                            (= colvalue-map (.asMap ^Row that)))))
 
 
 (defn row?
 
 (defn ^Row make-row
   "Create an instance of Row type."
-  ([colvalue-vec colvalue-map conv-fn]
-    (Row. colvalue-vec colvalue-map conv-fn))
-  ([colvalue-vec colvalue-map]
-    (make-row colvalue-vec colvalue-map
+  ([collabel-vec colvalue-vec colvalue-map conv-fn]
+    (Row. collabel-vec colvalue-vec colvalue-map conv-fn))
+  ([collabel-vec colvalue-vec colvalue-map]
+    (make-row collabel-vec colvalue-vec colvalue-map
       (fn [key not-found]
         (cond
           (contains? colvalue-vec key) (colvalue-vec key)
 
 ;; Enable pretty printing
 (defmethod pp/simple-dispatch Row [^Row x]
-  ;(pp/pprint (.asVec x))
   (pp/pprint (.asMap x)))
 
 
-(defn alt-resultset-seq
+(defn row-seq
   "Create and return a lazy sequence of Row instances corresponding to
   the rows in the java.sql.ResultSet rs
   Note: This function is a modified version of clojure.core/resultset-seq and
         solves the following issues:
    1. Maintains order of columns in a row (resultset-seq maintains order too)
-   2. All columns can be accessed using corresponding integer index
-   3. Duplicate column titles (and hence values) can exist in a row
-   4. Lets you define how to convert column label to Clojure form"
+   2. Preserves original column titles in the order they exist in the resultset
+   3. All columns can be accessed using corresponding integer index (zero based)
+   4. Duplicate column titles (and hence values) can exist in a row
+   5. Lets you define how to convert column label to Clojure form"
   [^ResultSet rs]
   (let [rsmeta     (. rs (getMetaData))
         idxs       (range 1 (inc (. rsmeta (getColumnCount))))
-        orig-keys  (distinct (map (fn [i] (. rsmeta (getColumnLabel i))) idxs))
-        keys       (map db-to-cljident orig-keys)
+        labels     (map (fn [i] (. rsmeta (getColumnLabel i))) idxs)
+        keys       (map clj-iden (distinct labels))
         row-struct (apply create-struct keys)
         create-row (fn [colvalue-seq]
-                     (make-row (into [] colvalue-seq)
+                     (make-row (into [] labels) (into [] colvalue-seq)
                        (reduce into (struct row-struct)
                          (map (fn [n ^String k] {n (.getObject rs k)})
-                           keys orig-keys))))
+                           keys labels))))
         row-values (fn [] (map (fn [^Integer i] (. rs (getObject i))) idxs))
         rows (fn thisfn []
                (when (. rs (next))
     (rows)))
 
 
-(defn get-dbmetadata
-  "Return a map of metadata.
+(defn colvalue-seq
+  "Return column values as a seq from rows for the given column-key."
+  [rows column-key]
+  (map #(get % column-key) rows))
+
+
+(defn dbmeta
+  "Return a map of database metadata.
   See also: http://download.oracle.com/javase/6/docs/api/java/sql/DatabaseMetaData.html"
   [^Connection conn]
   (let [metadata ^DatabaseMetaData (.getMetaData conn)]
       :sup-catalogs-in-proccalls         (.supportsCatalogsInProcedureCalls       metadata)
       :sup-catalogs-in-tabledefs         (.supportsCatalogsInTableDefinitions     metadata)
       ;; (sequence of maps) assorted ResultSet attributes
-      :client-info-properties (into [] (in/maybe-val (alt-resultset-seq
-                                                       (.getClientInfoProperties  metadata))))
-      :table-types            (into [] (alt-resultset-seq (.getTableTypes         metadata)))
-      :type-info              (into [] (alt-resultset-seq (.getTypeInfo           metadata))))))
+      :table-types (into [] (row-seq (.getTableTypes metadata)))
+      :type-info   (into [] (row-seq (.getTypeInfo   metadata))))))
 
 
 ;; ----- Database introspection -----
 
 
+(defn ^DatabaseMetaData get-dbmeta
+  "Return DatabaseMetaData object from Connection conn. Not to be confused with
+  dbmeta function that returns a map."
+  [^Connection conn]
+  (.getMetaData conn))
+
+
 (defn get-catalogs
   "Return a vector of catalogs in the database, where each catalog information
   is a map with column :table-cat (depending on you 'db-to-clj' configuration)
   See also:
   http://j.mp/eNWOa8 (Java 6 API, class DatabaseMetaData, method getCatalogs)"
   [^DatabaseMetaData dm]
-  (into [] (alt-resultset-seq (.getCatalogs dm))))
+  (into [] (row-seq (.getCatalogs dm))))
 
 
 (defn get-schemas
   See also:
   http://j.mp/gSlOtD (Java 6 API, class DatabaseMetaData, method getSchemas)"
   [^DatabaseMetaData dm]
-  (into [] (alt-resultset-seq (.getSchemas dm))))
+  (into [] (row-seq (.getSchemas dm))))
 
 
 (defn get-tables
   "Return a vector of table descriptions in database. By default include only
   all tables in current database/catalog/schema. Depending on the 'db-to-clj'
   configuration, each table description has the following columns:
-   :table-cat                 - String: table catalog (may be nil)
-   :table-schem               - String: table schema (may be nil)
-   :table-name                - String: table name
-   :table-type                - String: table type. Typical types are
+   :table-cat                 String => table catalog (may be nil)
+   :table-schem               String => table schema (may be nil)
+   :table-name                String => table name
+   :table-type                String => table type. Typical types are
                                         \"TABLE\", \"VIEW\", \"SYSTEM TABLE\",
                                         \"GLOBAL TEMPORARY\", \"LOCAL TEMPORARY\",
                                         \"ALIAS\", \"SYNONYM\".
-   :remarks                   - String: explanatory comment on the table
-   :type-cat                  - String: the types catalog (may be nil)
-   :type-schem                - String: the types schema (may be nil)
-   :type-name                 - String: type name (may be nil)
-   :self-referencing-col-name - String: name of the designated \"identifier\"
+   :remarks                   String => explanatory comment on the table
+   :type-cat                  String => the types catalog (may be nil)
+   :type-schem                String => the types schema (may be nil)
+   :type-name                 String => type name (may be nil)
+   :self-referencing-col-name String => name of the designated \"identifier\"
                                         column of a typed table (may be nil)
-   :ref-generation            - String: specifies how values in SELF_REFERENCING_COL_NAME
+   :ref-generation            String => specifies how values in :self-referencing-col-name
                                         are created. Values are \"SYSTEM\",
                                         \"USER\", \"DERIVED\". (may be nil)
-   :sql                       - String: the SQL/DDL used to create it (may be nil)
+   :sql                       String => the SQL/DDL used to create it (may be nil)
   Arguments:
    dm (java.sql.DatabaseMetaData)
   Optional arguments:
                             a table name pattern; must match the table name as
                             it is stored in the database; nil selects all
    :types          (String) a list of table types, which must be from the list
-                            of table types returned from function get-dbmetadata
+                            of table types returned from function dbmeta
                             (key :table-types, typical values listed below);
                             nil returns all types
                              \"TABLE\", \"VIEW\", \"SYSTEM TABLE\",
   http://j.mp/dUeYXT (Java 6 API, class DatabaseMetaData, method getTables)"
   [^DatabaseMetaData dm
    & {:keys [catalog
-             schema-pattern schema
-             table-pattern  table
+             schema-pattern
+             table-pattern
              types]
       :or {catalog        nil
-           schema-pattern nil schema nil
-           table-pattern  nil table  nil
+           schema-pattern nil
+           table-pattern  nil
            types          (into-array
                             String ["TABLE"])}
       :as opt}]
   {:pre [(clojure.set/subset? (set (keys opt))
            #{:catalog
-             :schema-pattern :schema
-             :table-pattern  :table
+             :schema-pattern
+             :table-pattern
              :types})]}
-  (let [rs (.getTables dm ^String catalog ^String (or schema-pattern schema)
-             ^String (or table-pattern table)
+  (let [rs (.getTables dm
+             ^String catalog       ^String schema-pattern
+             ^String table-pattern
              ^"[Ljava.lang.String;" (#(or (and (coll? %) (into-array String %))
                                         %) types))]
-    (into [] (alt-resultset-seq rs))))
+    (into [] (row-seq rs))))
+
+
+(defn table-names
+  "Return table names from the collection returned by get-tables fn."
+  [rows]
+  (let [tname (clj-iden "TABLE_NAME")]
+    (into [] (colvalue-seq rows tname))))
+
+
+(defn get-columns
+  "Retrieve a description of table columns available in the specified catalog.
+  Only column descriptions matching the catalog, schema, table and column name
+  criteria are returned. They are ordered by:
+    TABLE_CAT,TABLE_SCHEM, TABLE_NAME, and ORDINAL_POSITION.
+  Each column description has the following columns:
+    :table-cat          String => table catalog (may be null)
+    :table-schem        String => table schema (may be null)
+    :table-name         String => table name
+    :column-name        String => column name
+    :data-type             int => SQL type from java.sql.Types
+    :type-name          String => Data source dependent type name, for a User-Defined-Type
+                                  the type name is fully qualified
+    :column-size           int => column size.
+                                  For numeric data, this is the maximum precision.
+                                  For character data, this is the length in characters.
+                                  For datetime datatypes, this is the length in
+                                   characters of the String representation
+                                   (assuming the maximum allowed precision of the
+                                   fractional seconds component).
+                                  For binary data, this is the length in bytes.
+                                  For the ROWID datatype, this is the length in bytes.
+                                  Nil is returned for data types where the column
+                                   size is not applicable.
+    :buffer-length    (unused)
+    :decimal-digits        int => the number of fractional digits. Nil is
+                                  returned for data types where :decimal-digits
+                                  is not applicable.
+    :num-prec-radix        int => Radix (typically either 10 or 2)
+    :nullable              int => is NULL allowed (see following values)
+                                    columnNoNulls - might not allow NULL values
+                                    columnNullable - definitely allows NULL values
+                                    columnNullableUnknown - nullability unknown
+    :remarks            String => comment describing column (may be nil)
+    :column-def         String => default value for the column, which should be
+                                  interpreted as a string when the value is
+                                  enclosed in single quotes (may be nil)
+    :sql-data-type         int => unused
+    :sql-datetime-sub      int => unused
+    :char-octet-length     int => for char types the maximum number of bytes in
+                                  the column
+    :ordinal-position      int => index of column in table (starting at 1)
+    :is-nullable-string String => ISO rules are used to determine the nullability
+                                  for a column.
+                                    YES --- if the parameter can include NULLs
+                                    NO --- if the parameter cannot include NULLs
+                                    empty string --- if the nullability for the
+                                                     parameter is unknown
+    :scope-catalog      String => catalog of table that is the scope of a reference
+                                  attribute (nil if :data-type isn't REF)
+    :scope-schema       String => schema of table that is the scope of a reference
+                                  attribute (nil if the :data-type isn't REF)
+    :scope-table        String => table name that this the scope of a reference
+                                  attribure (nil if the :data-type isn't REF)
+    :source-data-type    short => source type of a distinct type or user-generated
+                                  Ref type, SQL type from java.sql.Types (nil if
+                                  :data-type isn't DISTINCT or user-generated REF)
+    :is-autoincrement   String => Indicates whether this column is auto incremented
+                                    YES --- if the column is auto incremented
+                                    NO --- if the column is not auto incremented
+                                    empty string --- if it cannot be determined
+                                                     whether the column is auto
+                                                     incremented
+  Arguments:
+   dm (java.sql.DatabaseMetaData)
+  Optional arguments:
+   :catalog        (String) a catalog name; must match the catalog name as it is
+                            stored in the database; \"\" retrieves those without
+                            a catalog; nil means that the catalog name should not
+                            be used to narrow the search
+   :schema-pattern (String) a schema name pattern; must match the schema name as
+                            it is stored in the database; \"\" retrieves those
+                            without a schema; nil means that the schema name
+                            should not be used to narrow the search
+   :table-pattern  (String) a table name pattern; must match the table name as
+                            it is stored in the database
+   :column-pattern (String) a column name pattern; must match the column name as
+                            it is stored in the database
+  See also:
+   http://j.mp/fap5kl (Java 6 API, class DatabaseMetaData, method getColumns)"
+  [^DatabaseMetaData dm
+   & {:keys [catalog
+             schema-pattern
+             table-pattern
+             column-pattern]
+      :or {catalog nil
+           schema-pattern nil
+           table-pattern  nil
+           column-pattern nil}
+      :as opt}]
+  {:pre [(clojure.set/subset? (set (keys opt))
+           #{:catalog
+             :schema-pattern
+             :table-pattern
+             :column-pattern})]}
+  (let [rs (.getColumns dm
+             ^String catalog       ^String schema-pattern
+             ^String table-pattern ^String column-pattern)]
+    (into [] (row-seq rs))))
+
+
+(defn get-column-privileges
+  "Retrieve a description of the access rights for a table's columns. Only
+  privileges matching the column name criteria are returned. They are ordered by
+  :column-name and :privilege.
+  Each privilige description has the following columns:
+    :table-cat    String => table catalog (may be nil)
+    :table-schem  String => table schema (may be nil)
+    :table-name   String => table name
+    :column-name  String => column name
+    :grantor      String => grantor of access (may be nil)
+    :grantee      String => grantee of access
+    :privilege    String => name of access (SELECT, INSERT, UPDATE, REFRENCES, ...)
+    :is-grantable String => \"YES\" if grantee is permitted to grant to others;
+                            \"NO\" if not; nil if unknown
+  Arguments:
+   dm (java.sql.DatabaseMetaData)
+  Optional arguments:
+   :catalog        (String) a catalog name; must match the catalog name as it is
+                            stored in the database; \"\" retrieves those without
+                            a catalog; nil means that the catalog name should not
+                            be used to narrow the search
+   :schema         (String) a schema name; must match the schema name as it is
+                            stored in the database; \"\" retrieves those without
+                            a schema; nil means that the schema name should not
+                            be used to narrow the search
+   :table          (String) a table name; must match the table name as it is
+                            stored in the database
+   :column-pattern (String) a column name pattern; must match the column name as
+                            it is stored in the database
+  See also:
+   http://j.mp/hmaOI4 (Java 6 API, class DatabaseMetaData, method getColumnPrivileges)"
+  [^DatabaseMetaData dm
+   & {:keys [catalog
+             schema
+             table
+             column-pattern]
+      :or {catalog nil
+           schema  nil
+           table   nil
+           column-pattern nil}
+      :as opt}]
+  {:pre [(clojure.set/subset? (set (keys opt))
+           #{:catalog
+             :schema
+             :table
+             :column-pattern})]}
+  (let [rs (.getColumnPrivileges dm
+             ^String catalog ^String schema
+             ^String table   ^String column-pattern)]
+    (into [] (row-seq rs))))
+
+
+(defn get-crossref
+  "Given a parent-table and a foreign-table, retrieve a description of the
+  foreign key columns in the foreign-table that reference the primary key or the
+  columns representing a unique constraint of the parent-table (could be the
+  same or a different table). The number of columns returned from parent-table
+  must match the number of columns that make up the foreign key. They are
+  ordered by
+    FKTABLE_CAT, FKTABLE_SCHEM, FKTABLE_NAME, and KEY_SEQ.
+
+  Each foreign key column description has the following columns:
+  
+  :pktable-cat   String => parent key table catalog (may be nil)
+  :pktable-schem String => parent key table schema (may be nil)
+  :pktable-name  String => parent key table name
+  :pkcolumn-name String => parent key column name
+  :fktable-cat   String => foreign key table catalog being exported (may be nil)
+  :fktable-schem String => foreign key table schema being exported (may be nil)
+  :fktable-name  String => foreign key table name being exported
+  :fkcolumn-name String => foreign key column name being exported
+  :key-seq       short  => sequence number within foreign key;
+                           value 1 represents the 1st column of the foreign key,
+                           value 2 represents the 2nd column of the foreign key
+  :update-rule    short => What happens to foreign key when parent key is updated:
+                           (below are static fields in java.sql.DatabaseMetaData)
+                            importedKeyNoAction   - do not allow update of parent
+                                                    key if it has been imported
+                            importedKeyCascade    - change imported key to agree
+                                                    with parent key update
+                            importedKeySetNull    - change imported key to NULL if
+                                                    its parent key has been updated
+                            importedKeySetDefault - change imported key to default
+                                                    values if its parent key has
+                                                    been updated
+                            importedKeyRestrict   - same as importedKeyNoAction
+                                                    (for ODBC 2.x compatibility)
+  :delete-rule    short => What happens to the foreign key when parent key is deleted:
+                           (below are static fields in java.sql.DatabaseMetaData)
+                            importedKeyNoAction   - do not allow delete of parent key
+                                                    if it has been imported
+                            importedKeyCascade    - delete rows that import a deleted key
+                            importedKeySetNull    - change imported key to NULL if its
+                                                    primary key has been deleted
+                            importedKeyRestrict   - same as importedKeyNoAction
+                                                    (for ODBC 2.x compatibility)
+                            importedKeySetDefault - change imported key to default if
+                                                    its parent key has been deleted
+  :fk-name       String => foreign key name (may be null)
+  :pk-name       String => parent key name (may be null)
+  :deferrability  short => can the evaluation of foreign key constraints be deferred until commit
+                           (below are static fields in java.sql.DatabaseMetaData)
+                            importedKeyInitiallyDeferred  - see SQL92 for definition
+                            importedKeyInitiallyImmediate - see SQL92 for definition
+                            importedKeyNotDeferrable      - see SQL92 for definition
+  Arguments:
+   dm            (java.sql.DatabaseMetaData)
+   parent-table  (String) the name of the table that exports the key; must match
+                          the table name as it is stored in the database
+   foreign-table (String) the name of the table that imports the key; must match
+                          the table name as it is stored in the database
+  Optional arguments:
+   :parent-catalog  (String) a catalog name; must match the catalog name as it is
+                             stored in the database; \"\" retrieves those without
+                             a catalog; nil (default) means drop catalog name from
+                             the selection criteria
+   :parent-schema   (String) a schema name; must match the schema name as it is
+                             stored in the database; \"\" retrieves those without
+                             a schema; nil (default) means drop schema name from
+                             the selection criteria
+   :foreign-catalog (String) a catalog name; must match the catalog name as it is
+                             stored in the database; \"\" retrieves those without
+                             a catalog; nil (default) means drop catalog name from
+                             the selection criteria
+   :foreign-schema  (String) a schema name; must match the schema name as it is
+                             stored in the database; \"\" retrieves those without
+                             a schema; nil (default) means drop schema name from
+                             the selection criteria
+  See also:
+   http://j.mp/h6bM4u (Java 6 API, class DatabaseMetaData, method getCrossReference)"
+  [^DatabaseMetaData dm ^String parent-table ^String foreign-table
+   & {:keys [parent-catalog
+             parent-schema
+             foreign-catalog
+             foreign-schema]
+      :or {parent-catalog  nil
+           parent-schema   nil
+           foreign-catalog nil
+           foreign-schema  nil}
+      :as opt}]
+  {:pre [(clojure.set/subset? (set (keys opt))
+           #{:parent-catalog
+             :parent-schema
+             :foreign-catalog
+             :foreign-schema})]}
+  (let [rs (.getCrossReference dm
+             ^String parent-catalog  ^String parent-schema  parent-table
+             ^String foreign-catalog ^String foreign-schema foreign-table)]
+    (into [] (row-seq rs))))

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

   [x]
   (if (or (keyword? x) (symbol? x)) (name x)
     (str x)))
-
-
-(defn replace-all
-  "Replace all string tokens in specified 'string-name' by lookig up the
-  replacement map in the specified 'ident-spec'."
-  [^String str-name ^DBIdentSpec ident-spec]
-  (reduce (fn [s [m r]] ; s=string, m=match, r=replacement
-            (sr/replace s (re-pattern m) r))
-    str-name
-    (:replace-map ident-spec)))
-
-

File src/test/clj/org/bituf/test_clj_dbspec.clj

   (testing "Make connection with username/password"
     (is (instance? Connection (spec/make-connection driver db-url2 "sa" ""))))
   (testing "Metadata"
-    (is (map? (echo (spec/get-dbmetadata
+    (is (map? (echo (spec/dbmeta
                       (spec/make-connection driver db-url3 "sa" ""))))))
   (testing "Make datasource"
     (is (instance? DataSource (spec/make-datasource driver db-url4)))))
           (exec-query (:connection spec/*dbspec*)))))))
 
 
-(deftest test-clj-to-dbident
+(deftest test-iden-conversion
   (testing "Convert Clojure identifier to database identifier"
-    (is (= (spec/clj-to-dbident :Hello-Morris) "hello_morris"))))
-
-
-(deftest test-db-to-cljident
+    (is (= (spec/db-iden :Hello-Morris) "Hello_Morris")))
   (testing "Convert database identifier to Clojure identifier"
-    (is (= (spec/db-to-cljident "Hello_Morris") :hello-morris))))
+    (is (= (spec/clj-iden "Hello_Morris") :hello-morris))))
 
 
 (deftest test-db-introspect
 (defn test-ns-hook []
   (test-make-connection)
   (test-with-datasource-connection)
-  (test-clj-to-dbident)
-  (test-db-to-cljident)
+  (test-iden-conversion)
   (test-db-introspect))