Value variable causes "eq expects atleast 1 argument"

NOTE: I’m using the GraphQL issue tag because I couldn’t find a DQL one

Report a DGraph Bug

What edition and version of Dgraph are you using?

Edition:

  • SlashGraphQL
  • Dgraph (community edition/Dgraph Cloud)

If you are using the community edition or enterprise edition of Dgraph, please list the version:

Dgraph Version
$ dgraph version
 
Dgraph version   : v23.0.1
Dgraph codename  : dgraph
Dgraph SHA-256   : 1d9145cf378b4e97b5f6cefd55069973c65c36f07b8e05a24b68b8ff5c5e74a4
Commit SHA-1     : 3de01e4
Commit timestamp : 2023-07-03 14:35:26 +0530
Branch           : HEAD
Go version       : go1.19.10
jemalloc enabled : true

Have you tried reproducing the issue with the latest release?

Yes

Steps to reproduce the issue (paste the query/schema if possible)

Schema

<endTime>: datetime @index(hour) .
<startTime>: datetime @index(hour) .
<state>: string @index(hash) .
type <ScheduleItem> {
	endTime
	startTime
  state
}

Mutation

{
  set {
    <_:ScheduleItem1> <dgraph.type> "ScheduleItem" .
    <_:ScheduleItem1> <endTime> "2023-07-28T12:00:00Z" .
    <_:ScheduleItem1> <startTime> "2023-07-28T08:00:00Z" .
    <_:ScheduleItem1> <state> "inactive" .

    <_:ScheduleItem2> <dgraph.type> "ScheduleItem" .
    <_:ScheduleItem2> <endTime> "2023-07-28T20:00:00Z" .
    <_:ScheduleItem2> <startTime> "2023-07-28T16:00:00Z" .
    <_:ScheduleItem2> <state> "inactive" .
  }
}

Query

{
  var(func: type(ScheduleItem), first: 1) @filter(eq(state, "active")) {
    endTime as endTime
  }

  NextConsecutiveScheduleItem(func: type(ScheduleItem), first: 1) @filter(
    eq(startTime, val(endTime))
    and type(ScheduleItem)
    and eq(state, "scheduled")
  ) {
    uid
  }
}

Expected behaviour and actual result.

Actual

The actual result is an error that a value was not provided.

{
  "name": "t",
  "url": "https://dgraph-alpha-ermine.001.001agents.com/query?timeout=20s",
  "errors": [
    {
      "message": ": eq expects atleast 1 argument.",
      "extensions": {
        "code": "ErrorInvalidRequest"
      }
    }
  ]
}

Expected

I expect the query not to throw an error and instead use a null (or equivalent) value and return an empty result.

{
  "data": {
    "NextConsecutiveScheduleItem": []
  },
}

Works if the first query block returns data

Mutation

  upsert {
    query {
      u as var(func: type(ScheduleItem), first: 1) @filter(eq(state, "inactive"))
    }

    mutation {
      set {
        uid(u) <state> "active" .
      }
    }
  }
1 Like

No, you can’t use val(...) functions like that at all.

From the docs: Value Variables - Query language

Value variables store scalar values. Value variables are a map from the UIDs of the enclosing block to the corresponding values.

It therefore only makes sense to use the values from a value variable in a context that matches the same UIDs - if used in a block matching different UIDs the value variable is undefined.

You give it the alias, NextConsecutiveScheduleItem but it is the same node between both queries which is redundant. Right?!? The (func: type(ScheduleItem), first: 1) is going to return the same node in both blocks. The @filter(...) is checked after this root and pagination is performed.

so doing val(endTime) is saying is this endTime which is mapped to this same node, the same value for this same node. That is illogical to do, but the reason for the error is because the mapped predicate is empty.

Now I wish there was a way to do what you want, but I don’t think so. Some things are just better in SQL :frowning:

But for a consolation, here is how to do this in SQL:

SELECT TOP 1
  id
FROM
  SceduleItems AS NextConsecutibeScheduleItem
WHERE
  NextConsecutibeScheduleItem.state='scheduled'
  AND NextConsecutibeScheduleItem.startTime IN (
    SELECT TOP 1
     activeItem.endTime 
    FROM
      ScheduleItems AS activeItem
    WHERE
      activeItem.state='active'
  )

You give it the alias, NextConsecutiveScheduleItem but it is the same node between both queries which is redundant. Right?!? The (func: type(ScheduleItem), first: 1) is going to return the same node in both blocks.

The first: 1 in the second query block is a typo. It’s supposed to query all inactive Schedule Items and select the one that matches the endTime of the active schedule item. Here’s an updated query:

{
  var(func: type(ScheduleItem), first: 1) @filter(eq(state, "active")) {
    endTime as endTime
  }

  NextConsecutiveScheduleItem(func: type(ScheduleItem)) @filter(
    eq(startTime, val(endTime))
    and type(ScheduleItem)
    and eq(state, "inactive")
  ) {
    uid
  }
}

That said, I still think this is a bug. It seems that data should not determine whether or not a query returns a valid result. A query should be valid or invalid regardless of the data.

It therefore only makes sense to use the values from a value variable in a context that matches the same UIDs - if used in a block matching different UIDs the value variable is undefined.

Value variables just went down a notch in my book. In light of this, I don’t think it’s possible to do what I’m trying to do with value variables.

Is there another way to accomplish what I’m trying to accomplish in a single DGraph query, essentially querying one node based on the value of a predicate of another node?

1 Like

I put the eq/val part into func and it seems to work:

{
  var(func: type(ScheduleItem), first: 1) @filter(eq(state, "active")) {
    endTime as endTime
  }

  NextConsecutiveScheduleItem(func: eq(startTime, val(endTime))) @filter(
    type(ScheduleItem)
    and eq(state, "inactive")
  ) {
    uid
  }
}
1 Like

@ppp225,

Thanks for this workaround! While it’s not ideal and I still consider there to be a bug, this works for my use case. Value variables must be evaluated after query blocks are executed when used in the top level func().

1 Like

Wait a second… does this actually work?? Are you sure this is not just returning the same node? But thinking about it… it might just work. In the root function, it doesn’t limit the universe of nodes yet. So this would just have the value? Unless the value is still linked to the uid of the associated predicate. Man, I really wish stuff like this was better explained in the docs.

I still consider there to be a bug

I feel like the errors are helpful in catching errors with data, when doing operations on value variables. Because usually you want to calculate some (usually numerical) data, and without errors on null values it would be problematic.

Wait a second… does this actually work??

Yup! Dgraph is quite magical sometimes! ;D

It also works with 2 nodes returned from the first query. It seems to work like this (in the root func):

endTimeVAR
uid -> endTime1
uid -> endTime2

val(endTimeVAR) -> [endTime1,endTime2]
uid(endTimeVAR) -> [uid1,uid2]

Full query with 2 results for reference (I added some more data)

{
  varr(func: type(ScheduleItem), first: 2) @filter(eq(state, "inactive")) {
    uid
    endTimeVAR as endTime
  }

  NextConsecutiveScheduleItem(func: eq(startTime, val(endTimeVAR))) @filter(
    type(ScheduleItem)
    and eq(state, "inactive")
  ) {
    uid
    startTime
  }

  ByUID(func: uid(endTimeVAR)) @filter(
    type(ScheduleItem)
    and eq(state, "inactive")
  ) {
    uid
  }
}
  "data": {
    "varr": [
      {
        "uid": "0x15f91",
        "endTime": "2023-07-28T12:00:00Z"
      },
      {
        "uid": "0x15f92",
        "endTime": "2023-07-28T20:00:00Z"
      }
    ],
    "NextConsecutiveScheduleItem": [
      {
        "uid": "0x15f93",
        "startTime": "2023-07-28T12:00:00Z"
      },
      {
        "uid": "0x15f94",
        "startTime": "2023-07-28T12:00:00Z"
      },
      {
        "uid": "0x186a1",
        "startTime": "2023-07-28T20:00:00Z"
      }
    ],
    "ByUID": [
      {
        "uid": "0x15f91"
      },
      {
        "uid": "0x15f92"
      }
    ]
  }
1 Like