xtdb 2025-07-24

Is this a bug or a misunderstanding of the xtql query syntax ?

(ns scratchpad.xtql-binding-spec-bug
  "Minimal reproduction: XTQL $ parameters in binding specs don't filter results
   
   Bug: When using $ parameters in from binding specs like {:xt/id $param},
   the constraint is ignored and all rows are returned."
  (:require [xtdb.node :as xtn]
            [xtdb.api :as xt]))

(defn minimal-repro []
  (with-open [node (xtn/start-node {})]
    ;; Setup: Insert 3 employees
    (xt/submit-tx node
      [[:put-docs :employees {:xt/id "emp1" :name "Alice"}]
       [:put-docs :employees {:xt/id "emp2" :name "Bob"}]
       [:put-docs :employees {:xt/id "emp3" :name "Charlie"}]])
    
    (Thread/sleep 100)
    
    (println "\n=== XTQL Binding Spec Parameter Bug ===\n")
    
    ;; Expected: Only return Bob (emp2)
    ;; Actual: Returns ALL employees
    (println "Query: '(from :employees [{:xt/id $uid :name name}])")
    (println "Args:  {:args {:uid \"emp2\"}}")
    (println "\nExpected result: [{:name \"Bob\"}]")
    (println "\nActual result:")
    (let [result (xt/q node 
                      '(from :employees [{:xt/id $uid :name name}])
                      {:args {:uid "emp2"}})]
      (doseq [row result]
        (println " " row)))
    
    (println "\nāŒ BUG: Returns all 3 employees instead of filtering by :xt/id = \"emp2\"")
    
    ;; Show that function syntax works correctly
    (println "\n\nFor comparison, function syntax works correctly:")
    (println "Query: ['(fn [uid] (from :employees [{:xt/id uid :name name}])) \"emp2\"]")
    (println "Result:" (xt/q node 
                            ['(fn [uid] 
                                (from :employees [{:xt/id uid :name name}]))
                             "emp2"]))
    (println "āœ… Correctly returns only Bob")))

(comment
  (minimal-repro)
  
  ;; The issue appears to be that $ parameters in binding specs
  ;; are treated as output bindings rather than filter constraints
  
  ;; This explains why we see :uid in the output with the parameter value
  )
=== XTQL Binding Spec Parameter Bug ===

Query: '(from :employees [{:xt/id $uid :name name}])
Args:  {:args {:uid "emp2"}}

Expected result: [{:name "Bob"}]

Actual result:
  {#clerk/unreadable-edn (keyword  uid) emp3, :name Charlie}
  {#clerk/unreadable-edn (keyword  uid) emp1, :name Alice}
  {#clerk/unreadable-edn (keyword  uid) emp2, :name Bob}

āŒ BUG: Returns all 3 employees instead of filtering by :xt/id = "emp2"


For comparison, function syntax works correctly:
Query: ['(fn [uid] (from :employees [{:xt/id uid :name name}])) "emp2"]
Result: [{:name Bob}]
āœ… Correctly returns only Bob

The XTQL behavior changed here as part of bringing it into SQL so I think you have to use the fn form now for XTQL parameters.
yep - this was part of https://github.com/xtdb/xtdb/releases/tag/v2.0.0-beta8. AFAICT we updated the docs for this but would be grateful to be pointed at any we've missed šŸ™‚

@seancorfield Can you point me to a sample app using honeysql with XTDB 2, specifically one embedding XTQL in it?

The XTQL behavior changed here as part of bringing it into SQL so I think you have to use the fn form now for XTQL parameters. Perhaps the documentation hasn't caught up with that change in some places?

šŸ‘ 1

Perhaps worth noting that you can now use next.jdbc to interact with XTDB as if it were PostgreSQL, and still embed XTQL into SQL expressions if you want. And you can use HoneySQL to build SQL queries for next.jdbc, including embedding XTQL. The Clojure API is not really useful now, IMO.

I'll take the suggestion, I wanted to avoid bringing other deps and experiment with unify to get a datalog like experience.