xtdb

jussi 2024-06-12T08:10:24.786619Z

Hi, running into an error when trying to use args in a custom predicate function, using XTDB v1.

(xt/q (xt/db db)
      '{:find [(pull e [*])]
        :where [[e :type type]
                [e :date date]
                [(within-date-range? date from-date to-date)]]
        :in [[type ...] from-date to-date]}
      types from-date to-date)
Running this results in
xtdb.IllegalArgumentException: Clause refers to unknown variable: within-date-range? {:pred {:pred-fn within-date-range?, :args [date from-date to-date]}}
    data: #:xtdb.error{:error-type :illegal-argument,
                       :error-key :clause-unknown-var,
                       :message
                       "Clause refers to unknown variable: within-date-range? {:pred {:pred-fn within-date-range?, :args [date from-date to-date]}}"}
I kind of assumed that args introduced in :in binding would be available to predicate functions. I must be missing something? πŸ€”

refset 2024-06-12T09:41:31.141579Z

Hey @jussi.mononen I believe the issue here is that within-date-range? needs to be fully qualified, like my.ns/within-date-range?

refset 2024-06-12T09:42:10.150789Z

because it can't resolve it as a function, it is assuming it is a variable hence why the error is "Clause refers to unknown variable"

jussi 2024-06-12T09:48:23.550499Z

Ah! Of course! Thank you!

πŸ™ 1
Stoating 2024-06-12T20:04:25.138369Z

Hello, I am currently playing with XTDB v2.0 and am interested in using XTQL as exclusively as possible. While looking online, I found many various queries/transactions which use specific data, but had difficulty finding any functions/macros which use XTQL and pass values into a transaction. As a result, I have created some macros which do this, but I wonder if this is intended use. Here is an example update transaction which allows a user to update their first name, last name, and email for a given id.

(defmacro update-user
    [db user]
    (let [first-name (user :first-name)
          last-name  (user :last-name)
          email      (user :email)
          id         (user :xt/id)]
      `(xt/submit-tx ~db '[[:update {:table :users
                                     :bind [{:xt/id ~'$xt/id}]
                                     :set {:first-name ~first-name
                                           :last-name ~last-name
                                           :email ~email}}
                            {:xt/id ~id}]])))
Is this going to be problematic, or is this just peachy? Thanks!

seancorfield 2024-06-12T20:40:17.624019Z

Have you looked at https://docs.xtdb.com/drivers/clojure/codox/xtdb.api.html#var-template ?

❀️ 1
βž• 1
Stoating 2024-06-12T21:11:42.627669Z

@seancorfield aaah. Nice. How did I miss that? Now:

(defn update-user
    [user]
    (let [first-name (user :first-name)
          last-name  (user :last-name)
          email      (user :email)
          id         (user :xt/id)]
      (xt/template [[:update {:table :users
                              :bind [{:xt/id $xt/id}]
                              :set {:first-name ~first-name
                                    :last-name ~last-name
                                    :email ~email}}
                     {:xt/id ~id}]])))
Can be called so:
(xt/submit-tx db (update-user {:first-name "John"
                               :last-name "Doe"
                               :email "john.doe@gmail.com"
                               :xt/id #uuid "d5943ed2-bef1-483d-8eb7-2be3e38d7e16"}))
Thanks :)

jarohen 2024-06-13T05:41:29.020749Z

While you can use template for this, it was more intended for cases when the wider structure of the query is dynamic, rather than param interpolation - I think in this case you should be able to provide first-name et al as params as well (like you have with $xt/id) template, for comparison, would be used if you didn't know whether you want to set each field or not until runtime (e.g. based on user-provided flags) try this?

(defn update-user
  [db user]
  (xt/submit-tx db
                [[:update '{:table :users
                            :bind [{:xt/id $xt/id}]
                            :set {:first-name $first-name
                                  :last-name $last-name
                                  :email $email}}
                  (select-keys user [:xt/id :first-name :last-name :email])]]))
Using parameters in this way also has the additional performance benefit that the query itself remains constant, so we can cache the query plan - when the arguments are inlined into the query itself, we'll need to plan a different query for each set of arguments.

πŸ‘πŸ» 1
❀️ 1
Stoating 2024-06-13T06:49:59.877669Z

@jarohen Thank you for the insight. That works like a charm and fits nicely with the other queries. My mistake was initially quoting the very beginning of the transaction which led to the 'need' for a macro. '[[:update <-- now we need to get the user inside this thing... vs. '{:table <-- the quote is scoped correctly and allows us to use the passed arguments without any shenanigans For posterity, here are some toy functions which pass values into a query using the :args keyword.

(defn get-user-by-first-name-1
  [db first-name]
  (xt/q db '(from :users [* {:first-name $first-name}])
        {:args {:first-name first-name}}))

(defn get-user-by-first-name-2
  [db first-name]
  (xt/q db '(-> (from :users [*] {:first-name $first-name})
                (where (= first-name $first-name)))
        {:args {:first-name first-name}}))

(defn exclude-users-by-first-name
  [db first-name]
  (xt/q db '(-> (from :users [*] {:first-name $first-name})
                (where (not (= first-name $first-name))))
        {:args {:first-name first-name}}))

πŸ™ 1
☺️ 1