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? π€Hey @jussi.mononen I believe the issue here is that within-date-range? needs to be fully qualified, like my.ns/within-date-range?
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"
Ah! Of course! Thank you!
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!Have you looked at https://docs.xtdb.com/drivers/clojure/codox/xtdb.api.html#var-template ?
@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 :)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.@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}}))