|
4 | 4 | [clojure.java.jdbc :as sql] |
5 | 5 | [clojure.string :as str] |
6 | 6 | [clojure.tools.logging :as log] |
| 7 | + [next.jdbc.prepare :refer [SettableParameter]] |
7 | 8 | [puppetlabs.i18n.core :refer [trs tru]] |
8 | 9 | [puppetlabs.kitchensink.core :as kitchensink] |
9 | 10 | [puppetlabs.puppetdb.cheshire :as json] |
10 | 11 | [puppetlabs.puppetdb.honeysql] |
11 | 12 | [puppetlabs.puppetdb.jdbc :as jdbc] |
12 | 13 | [puppetlabs.puppetdb.query.common :refer [bad-query-ex]] |
13 | 14 | [puppetlabs.puppetdb.schema :as pls] |
| 15 | + [puppetlabs.puppetdb.utils :refer [byte-array-class]] |
14 | 16 | [schema.core :as s]) |
15 | 17 | (:import |
16 | | - [java.sql Connection] |
17 | | - [java.util UUID] |
18 | | - [org.postgresql.util PGobject])) |
| 18 | + (java.sql Connection PreparedStatement) |
| 19 | + (java.util HexFormat UUID) |
| 20 | + (org.postgresql.util PGobject) |
| 21 | + (puppetlabs.puppetdb.jdbc PDBBytea VecPDBBytea))) |
| 22 | + |
| 23 | +(def ^:private warn-on-reflection-orig *warn-on-reflection*) |
| 24 | +(set! *warn-on-reflection* true) |
19 | 25 |
|
20 | 26 | ;; SCHEMA |
21 | 27 |
|
22 | 28 | (defn array-to-param |
23 | 29 | [col-type java-type values] |
24 | | - (.createArrayOf ^Connection (:connection (jdbc/db)) |
25 | | - col-type |
26 | | - (into-array java-type values))) |
| 30 | + ;; We create the VecPDBBytea so we can redirect the parameter to |
| 31 | + ;; setArray in the set-parameter specializations. |
| 32 | + (if (= java-type PDBBytea) |
| 33 | + ;; FIXME: change byte-array-class to byte/1 once we require clojure 1.12+ |
| 34 | + (VecPDBBytea. (into-array byte-array-class (map #(.data ^PDBBytea %) values))) |
| 35 | + (.createArrayOf ^Connection (:connection (jdbc/db)) |
| 36 | + col-type (into-array java-type values)))) |
27 | 37 |
|
28 | 38 | (def pg-extension-map |
29 | 39 | "Maps to the table definition in postgres, but only includes some of the |
|
427 | 437 | (sql/with-db-connection [_conn db] |
428 | 438 | (sql/execute! db ["vacuum analyze"] {:transaction? false}))) |
429 | 439 |
|
430 | | -(defn parse-db-hash |
431 | | - [^PGobject db-hash] |
432 | | - (str/replace (.getValue db-hash) "\\x" "")) |
| 440 | +(defn db-hash->hex [^PDBBytea pdbb] |
| 441 | + (.formatHex (HexFormat/of) (.data pdbb))) |
433 | 442 |
|
434 | 443 | (defn parse-db-uuid |
435 | 444 | [^UUID db-uuid] |
|
454 | 463 | (defn bytea-escape [s] |
455 | 464 | (format "\\x%s" s)) |
456 | 465 |
|
457 | | -(defn munge-hash-for-storage |
458 | | - [hash] |
459 | | - (str->pgobject "bytea" (bytea-escape hash))) |
| 466 | +(extend-protocol sql/ISQLParameter |
| 467 | + ;; This became necessary with more recent pgjdbc |
| 468 | + ;; versions (42.7.7 (at least) and newer), because it stopped |
| 469 | + ;; handling a "bytea" PGobject; it started crashing in |
| 470 | + ;; PGBytea/toPGLiteral, which doesn't have a clause to handle the |
| 471 | + ;; escaped String that it ends up with, presumably from the PGobject |
| 472 | + ;; value. The pgjdbc docs specify .setBytes, so do |
| 473 | + ;; that: |
| 474 | + ;; https://jdbc.postgresql.org/documentation/binary-data/ |
| 475 | + ;; https://clojure-doc.org/articles/ecosystem/java_jdbc/using_sql/ |
| 476 | + ;; https://clojure.github.io/java.jdbc/#clojure.java.jdbc/ISQLParameter |
| 477 | + PDBBytea |
| 478 | + (sql/set-parameter [v ^PreparedStatement s i] |
| 479 | + (.setBytes s i (.data v))) |
| 480 | + VecPDBBytea |
| 481 | + (sql/set-parameter [v ^PreparedStatement s i] |
| 482 | + (.setArray s i (.createArrayOf ^Connection (:connection (jdbc/db)) |
| 483 | + "bytea" (.data v))))) |
| 484 | + |
| 485 | +(extend-protocol SettableParameter |
| 486 | + PDBBytea |
| 487 | + (sql/set-parameter [v ^PreparedStatement s i] (.setBytes s i (.data v))) |
| 488 | + VecPDBBytea |
| 489 | + (sql/set-parameter [v ^PreparedStatement s i] |
| 490 | + (.setArray s i (.createArrayOf ^Connection (:connection (jdbc/db)) |
| 491 | + "bytea" (.data v))))) |
| 492 | + |
| 493 | +(defn munge-hash-for-storage [hash] |
| 494 | + ;; Represent hashes as PDBBytea so we can add them to |
| 495 | + ;; PreparedStatments via setBytes via the clojure/next jdbc |
| 496 | + ;; protocols above. It'd be more efficient if eventually pdb just |
| 497 | + ;; used something like PDBBytea or byte[] everywhere, though the |
| 498 | + ;; latter would allow mutation and might raise "ownership" |
| 499 | + ;; questions. The *most* efficient hash that's not just a byte[] |
| 500 | + ;; might be a class with two long members and a short to store the |
| 501 | + ;; 20 bytes (i.e. so that the data would be inline in the class). |
| 502 | + (PDBBytea. (.parseHex (HexFormat/of) hash))) |
460 | 503 |
|
461 | 504 | (defn munge-json-for-storage |
462 | 505 | "Prepare a clojure object for storage depending on db type." |
|
497 | 540 | (log/info (trs "Analyzing small tables")) |
498 | 541 | (apply jdbc/do-commands-outside-txn |
499 | 542 | (map #(str "analyze " %) small-tables))) |
| 543 | + |
| 544 | +(set! *warn-on-reflection* warn-on-reflection-orig) |
0 commit comments