Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/clojure.yml
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ jobs:

services:
postgres:
image: ghcr.io/instantdb/postgresql:postgresql-16-pg-hint-plan
image: ghcr.io/instantdb/postgresql:postgresql-17-pg-hint-plan
ports:
- 5432:5432
env:
Expand Down
6 changes: 3 additions & 3 deletions server/dev-postgres/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
FROM postgres:16
FROM postgres:17

RUN apt-get update && \
apt-get install -y \
build-essential \
postgresql-server-dev-16 \
postgresql-16-pg-hint-plan \
postgresql-server-dev-17 \
postgresql-17-pg-hint-plan \
wget && \
mkdir build && cd build && \
wget https://github.com/eulerto/wal2json/archive/refs/tags/wal2json_2_6.tar.gz && \
Expand Down
2 changes: 1 addition & 1 deletion server/dev-postgres/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@ github-login:

build-and-push-image:
docker buildx build --platform linux/amd64,linux/arm64 --push \
-t ghcr.io/instantdb/postgresql:postgresql-16-pg-hint-plan .
-t ghcr.io/instantdb/postgresql:postgresql-17-pg-hint-plan .
37 changes: 29 additions & 8 deletions server/src/instant/db/model/triple.clj
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,13 @@

(def value-lookup-error-prefix "missing-lookup-value")

;; Helper for :when-not-matched in `insert-multi
(def triple-t-values
(zipmap triple-cols
(map (fn [col]
(keyword (str "t." (name col))))
triple-cols)))

(defn insert-multi!
"Given a set of raw triples, we enhance each triple with metadata based on
the triple's underlying attr and then insert these enhanced triples into
Expand Down Expand Up @@ -586,14 +593,28 @@
{:select :* :from :enhanced-triples :where [:not :ea]}

ea-index-inserts
{:insert-into [[:triples triple-cols]
{:select triple-cols
:from :ea-triples-distinct
:order-by [:app-id :entity-id :attr-id :value-md5]}]
:on-conflict [:app-id :entity-id :attr-id {:where [:= :ea true]}]
:do-update-set {:value :excluded.value
:value-md5 :excluded.value-md5}
:returning :*}
(if (not (flags/toggled? :disable-merge-into))
{:merge-into :triples
:using [[:ea-triples-distinct :t]]
:on [:and
[:= :triples.app-id :t.app-id]
[:= :triples.entity-id :t.entity-id]
[:= :triples.attr-id :t.attr-id]]
:when-not-matched {:insert {:values [triple-t-values]}}
:when-matched [[[:= :triples.value-md5 :t.value-md5]
:do-nothing]
{:update {:set {:value :t.value
:value-md5 :t.value-md5}}}]
:returning :triples.*}

{:insert-into [[:triples triple-cols]
{:select triple-cols
:from :ea-triples-distinct
:order-by [:app-id :entity-id :attr-id :value-md5]}]
:on-conflict [:app-id :entity-id :attr-id {:where [:= :ea true]}]
:do-update-set {:value :excluded.value
:value-md5 :excluded.value-md5}
:returning :*})

remaining-inserts
{:insert-into [[:triples triple-cols]
Expand Down
114 changes: 113 additions & 1 deletion server/src/instant/util/hsql.clj
Original file line number Diff line number Diff line change
@@ -1,5 +1,117 @@
(ns instant.util.hsql
(:require [honey.sql :as hsql]))
(:require clojure.string
[honey.sql :as hsql]))

;; ------------------------
;; merge-into custom clause

;; :merge-into

(defn format-matched-cond [expr]
(let [[sql & params] (honey.sql/format-expr expr)]
(when-not (clojure.string/blank? sql)
(into [(str "AND " sql)] params))))

(defn matched-merge-action? [x]
(case x
:do-nothing :do-nothing
:delete :delete
(and (map? x)
(or (:update x)
(:insert x)))))

(defn format-matched-merge-action [x]
(case x
:do-nothing ["DO NOTHING"]
:delete ["DELETE"]
(cond (:update x)
(let [[sql & params] (honey.sql/format-dsl (:update x))]
(into [(str "UPDATE " sql)] params))

(:insert x)
(let [[sql & params] (honey.sql/format-dsl (:insert x))]
(into [(str "INSERT " sql)] params)))))

(defn matched-cases [exp]
(cond
;; just a single action with no condition
(matched-merge-action? exp)
[exp]

;; single action with condition
(and (vector? exp)
(= 2 (count exp))
(not (vector? (first exp)))
(not (matched-merge-action? (first exp)))
(matched-merge-action? (second exp)))
[exp]

;; A list of condition+action
(vector? exp)
exp

:else
(throw (Exception. "Unknown match clauses for :when-matched/:when-not-matched"))))

(honey.sql/register-clause!
:when-not-matched
(fn [_clause exp]
(reduce (fn [[sql & params] expr]
(if (matched-merge-action? expr)
(let [[expr-sql & expr-params] (format-matched-merge-action expr)]
(into [(str sql " WHEN NOT MATCHED THEN " expr-sql)]
(into params expr-params)))
(do
(assert (and (vector? expr)
(matched-merge-action? (second expr)))
"Invalid match clause for :when-not-matched")
(let [[cond-expr action-expr] expr
[case-sql & case-params] (format-matched-cond cond-expr)
[expr-sql & expr-params] (format-matched-merge-action action-expr)]
(into [(str sql " WHEN NOT MATCHED " case-sql " THEN " expr-sql)]
(concat params case-params expr-params))))

))
[""]
(matched-cases exp)))
;; Get behind of :using
:join-by)

(honey.sql/register-clause!
:when-matched
(fn [_clause exp]
(reduce (fn [[sql & params] expr]
(if (matched-merge-action? expr)
(let [[expr-sql & expr-params] (format-matched-merge-action expr)]
(into [(str sql " WHEN MATCHED THEN " expr-sql)]
(into params expr-params)))
(do
(assert (and (vector? expr)
(matched-merge-action? (second expr)))
"Invalid match clause for :when-matched")
(let [[cond-expr action-expr] expr
[case-sql & case-params] (format-matched-cond cond-expr)
[expr-sql & expr-params] (format-matched-merge-action action-expr)]
(into [(str sql " WHEN MATCHED " case-sql " THEN " expr-sql)]
(concat params case-params expr-params))))

))
[""]
(matched-cases exp)))
:when-not-matched)

(honey.sql/register-clause!
:on
(fn [_clause expr]
(let [[sql & params] (honey.sql/format-expr expr)]
(into [(str "ON " sql)] params)))
:when-matched)

(honey.sql/register-clause!
:merge-into
(fn [_clause tbl]
[(str "MERGE INTO " (honey.sql/format-entity tbl))])
:using)

;; ----------------------
;; pg-hints custom clause
Expand Down
21 changes: 21 additions & 0 deletions server/test/instant/jdbc/sql_test.clj
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,27 @@
(sql/select (aurora/conn-pool :read) ["select 1"])
(is (= 0 (count @(:stmts in-progress)))))))

(deftest merge-into
(testing "smoke test"
(is (= ["MERGE INTO test USING a b ON (b.id = test.id) AND (b.v = test.v) WHEN MATCHED AND test.a = b.a THEN DELETE WHEN MATCHED AND test.b = b.b THEN UPDATE SET a = ? WHEN MATCHED AND test.c = b.c THEN INSERT (a) VALUES (?), (?) WHEN MATCHED THEN DO NOTHING WHEN NOT MATCHED THEN DO NOTHING RETURNING \"\".*"
1
2
3]
(honey.sql/format {:merge-into :test
:using [[:a :b]]
:on [:and
[:= :b.id :test.id]
[:= :b.v :test.v]]
:when-matched [[[:= :test.a :b.a]
:delete]
[[:= :test.b :b.b]
{:update {:set {:a 1}}}]
[[:= :test.c :b.c]
{:insert {:values [{:a 2} {:a 3}]}}]
:do-nothing]
:when-not-matched :do-nothing
:returning :.*})))))

(deftest cant-write-on-a-readonly-connection
(is (thrown-with-msg? clojure.lang.ExceptionInfo
#"read-only-sql-transaction"
Expand Down
Loading