Missing rules var '%' in :in

Hello Logseq community!

I’m trying to build a framework for using Logseq as a database for applications of different kinds. I’m starting to learn how web API works, and I’ve faced an issue.

I have a quite simple request for fetching everything with a type of “foo”:

{
  "method": "logseq.DB.datascriptQuery",
  "args": [
    "[:find (pull ?b [*]) :where [property ?b :type \"foo\"]]"
  ]
}

But in the response I get an error:

{
  "error": "Missing rules var '%' in :in"
}

The query above works perfectly fine from Logseq page.

What’s interesting, another request, with more complex query works totally fine:

{
  "method": "logseq.DB.datascriptQuery",
  "args": [
    "[:find (pull ?b [*]) :where [?b :block/marker ?marker] [(contains? #{\"TODO\" \"DOING\" \"NOW\" \"LATER\" \"WAITING\"} ?marker)] (not [?b :block/scheduled ?d]) (not [?b :block/deadline ?d])]"
  ]
}

Do I miss something or it can be a bug?

Welcome.

This expression:

[property ?b :type \"foo\"]

should be within parentheses instead of brackets:

(property ?b :type \"foo\")
1 Like

Hi @mentaloid !
Nope, still the same error

Looks like a bug. Try replacing the whole parenthesis with its equivalent:

  • simplified:
    [?b :block/properties ?prop] [(get ?prop :type) ?v] [(= ?v \"foo\")]
  • exact: rules

Thanks a lot!
However I’ve noticed another thing - if my property value is a reference:

type:: [[foo]]

then neither [?b :block/properties ?prop] [(get ?prop :type) ?v] [(= ?v \"foo\")] nor [?b :block/properties ?prop] [(get ?prop :type) ?v] [(= ?v \"[[foo]]\")] will find that block. Can it be a different bug or it’s expected behaviour?

It is expected behaviour. The brackets form an inline reference, which can be checked with [?v :block/name \"foo\"]

For reference the return value is a clojure set. You need to use contains in that case.
I.e. [(contains? ?v "foo")]
(I hope I got the syntax right :sweat_smile:)

In logseq it works:

#+BEGIN_QUERY
{:title [:b "test"]
 :query [:find (pull ?b [*])
  :where
   [?b :block/properties ?prop] 
   [(get ?prop :type) ?v]
   [(contains? ?v "foo")]
 ]
}
#+END_QUERY

Does that work?! ?v isn’t an database identity but a clojure set. At least when you would return it. :thinking:

In logseq it doesn’t work.
Here’s what ?v is:

@Siferiax you are right! Thank you!

@mentaloid hmm, still returns nothing

  • [(= ?v #{"foo"})] returns only exact values (not always desirable)
  • [(contains? ?v "foo")] returns partial values too (not always desirable)

The problem of both is that they work not on the actual reference, but on its local string representation. This means that they fail when the string isn’t identical (e.g. [[Foo]] instead of [[foo]] or an alias etc.), no matter that the reference is still valid. We need a way to turn the reference into its database identity.

Possible, though a bit complicated.
I use it in my own graph though.
Basically the simple query syntax can handle both an actual reference ([[foo]]) and plain text (foo)
However it doesn’t allow you to grab the value. So you need to work backwards as it were.

(has-page-property ?p :type) ; performance making sure we start with only appropriate pages
[?b :block/page ?p] ; blocks on the pages
[?b :block/refs ?t] ; only blocks that reference something 
[?t :block/original-name ?type] ; get the name of the page that is referenced
(page-property ?p :type ?type) ; check that this name is used for the property.

As for capitalization, that’s always going to be an issue unless we use a case insensitive regex.

Edit: may not be exactly what you mean, but it does use the actual reference

You may improve performance by replacing the simple query syntax with its equivalent (from here) and removing the repetitions, as :has-page-property is a code-subset of :page-property .

It does use the actual reference (even for alternative cases and aliases), but it doesn’t ensure that this reference is a value of the given property (or any property for that matter), instead of a reference in the main body of the block. So this could work only if some conventions were followed.

Ah! Thanks for that link, that helps.

It does, with that last line.
Though given the link you shared, this would effectively be the same:

[?p :block/properties ?prop]
[(get ?prop :type) ?value]
[?b :block/page ?p]
[?b :block/refs ?t]
[?t :block/original-name ?type]
(or 
  [(= ?value ?type)] ; plain text
  [(contains? ?value ?type)] ; actual reference
)

Basically the query does the following checks.

  1. we have a page with a property named type
  2. we have a block on that page with a reference
  3. the page referenced in the block is the same as the value for the page property type.

Instead of the page property we can also use [?b :block/properties ?prop] for finding any block on the page with said property. And then that same block needs to have the property value and the reference with the same page.
In that case it doesn’t matter whether the property is a block or page property.

The only downside here is that it assumes the original-name for a page is used. So if my page is Name and I make a block with type:: name it will not work. Unless we say type always uses a reference (comma separated properties in the config), then it might work?

  • both = and contains? check against a specific string instead of an actual reference
    • all the results match a valid string
      • false-positives are excluded
  • but a unique database identity may correspond to more than one string
    • examples:
      • different cases of letters
      • aliases
    • some valid references don’t match the specific string
      • false-negatives are not excluded
      • the user has:
        • no guarantee that nothing is missing
        • no clue when something is missing
      • proper conventions could prevent those
  • potential solutions:
    • references in property values to either:
      • be saved as database identities
        • that would break many existing queries
      • be automatically saved with the original name
        • after all, they are mere values
    • a way to query for backlinks directly
      • that would solve other cases as well
1 Like