Allow time functions (Now, TimeAdd, TimeSubtract, Time, etc.) on indexes

I have the following index:

q.CreateIndex({
  name,
  source: {
    collection: q.Collection('userTokens'),
    fields: {
      isActive: q.Query(
        q.Lambda(
          ['document'],
          q.If(
            q.LT(q.Now(), q.Select(['data', 'expiresAt'], q.Var('document'))),
            1,
            0
          )
        )
      )
    }
  },
  terms: [
    { field: ['data', 'userId'] },
    { field: ['data', 'token'] },
    { binding: 'isActive' }
  ]
});

For some weird resons this:

q.If(
  q.LT(q.Now(), q.Select(['data', 'expiresAt'], q.Var('document'))),
  1,
  0
)

results to 0 even though it should be 1, resulting to instance not found errors, but when I tried that on the shell, it actually gave me back the expected output.

If(
  LT(
    Now(),
    Time("2021-01-20T14:43:41.419689Z")
  ),
  1,
  0
)

It may have something to do with the index itself but I can’t find what it is, it can’t be the lambda because as far as I know, this:

q.Lambda(
  ['document'],

is the same as this:

q.Lambda(
  'document',

Now I see why, it’s because of this Indexes | Fauna Documentation apparently NOW cannot be used in an index. I think this will be a limitation and will have a negative impact on how we create and use indexes…

I tried using Time('now') but also resulted to the same problem.

Hi @aprilmintacpineda ,

Your goal is to pass to the index

userId, token, isActive

where isActive is 0 or 1 and get back the reference for the document, is it correct?

Luigi

Yes, that is correct. The isActive is a binding and is determined by checking if a particular date has already past, which I can see that is only possible by using Now but that’s not allowed to be used in indexes.

Hi @aprilmintacpineda ,

as a simple workaround, you can create an index like this:

CreateIndex({
  name: "refByIdToken",
  source: Collection('userTokens'),
  terms: [
    { field: ['data', 'userId'] },
    { field: ['data', 'token'] }
  ],
  values: [
    { field: ['data', 'expiresAt'] },
    { field: ['ref'] }
  ]
})

and a function:

CreateFunction(
  {
    name: 'getUser',
    body: Query(
      Lambda(['userId','token','isActive'],
        Let(
          {
            document: Get(Match('refByIdToken',[Var('userId'),Var('token')])),
            expired: If(LT(Now(),Select(['data','expiresAt'],Var('document'))),1,0) 
          },
          If(Equals(Var('isActive'),Var('expired')),Var('document'),"Token not valid")
        )
      )
    )
  }
)
and then call the function:

Call(Function(“getUser”),[‘test1’,‘abcde’,1])
{ ref: Ref(Collection(“userTokens”), “287691618317763079”),
ts: 1610622957440000,
data:
{ userId: ‘test1’,
token: ‘abcde’,
expiresAt: Time(“2021-01-20T14:43:41.419689Z”) } }

Call(Function(“getUser”),[‘test1’,‘abcde’,0])
‘Token not valid’

or

Call(Function(“getUser”),[‘test2’,‘fghij’,0])
{ ref: Ref(Collection(“userTokens”), “287691672105517575”),
ts: 1610623008876000,
data:
{ userId: ‘test2’,
token: ‘fghij’,
expiresAt: Time(“2021-01-10T14:43:41.419689Z”) } }

Call(Function(“getUser”),[‘test2’,‘fghij’,1])
‘Token not valid’


Hope this helps.

Luigi

That gave me an idea of what I can do as a workaround, but I think that would be inefficient, it would be more efficient to add this functionality to indexes, what I will have to do is this:

This should return a list of tokens generated by the user.

  1. Get all tokens.
  2. Filter out the expired documents.
  3. Return the filtered documents.

Which I believe will do quite a lot of computes compared to doing it on indexes.

I will give this a try though, thanks for the suggestion!

This works but it’s a disaster to the pricing since to achieve what I want to achieve I will have to paginate, then do map, then filter which is a disaster with the compute pricing, I tried using reduce but it doesn’t work for the sets returned by paginate. On the other hand, if time functions were allowed in the indexes it would have been easier, and no need to do multiple loops.

Here’s what my function looks like

q.CreateFunction({
  name: 'getActiveTokensByUserId',
  body: q.Query(
    q.Lambda(
      ['userId', 'nextToken'],
      q.Filter(
        q.Map(
          q.Paginate(
            q.Match(
              q.Index('userTokens'),
              q.Var('userId')
            ),
            {
              size: 20,
              after: q.If(
                q.Equals(q.Var('nextToken'), ''),
                [],
                q.Var('nextToken')
              )
            }
          ),
          q.Lambda(['ref'], q.Get(q.Var('ref')))
        ),
        q.Lambda(
          ['document'],
          q.LT(
            q.Now(),
            q.Time(
              q.Select(['data', 'expiresAt'], q.Var('document'))
            )
          )
        )
      )
    )
  )
})

Additionally, for this part:

q.If(
  q.Equals(q.Var('nextToken'), ''),
  [],
  q.Var('nextToken')
)

it would be good if there’s a isFalsy function,

q.If(
  q.isFalsy(q.Var('nextToken')), // null, undefined, empty string, 0
  [],
  q.Var('nextToken')
)