Query the most recent item in a collection

I’m currently migrating a database to fauna, one of my collections has a notificationtime field, which is of type Time. I’ve uploaded this data with the JS client without errors, but I’m now having trouble querying this collection and sorting by that field. There are many times in my app where I’d just like to grab the most recent notification.

I’ve created an index on this collection like so:

CreateIndex({
  name: "milonEventsByTime",
  source: Collection("VehicleMilonData"),
  terms: [{ field: ["data", "notificationtime"] }, { field: ["ref"] }]
})

I’ve also added this resolver to my datamodel:

allMilonEventsSortedByTime: [VehicleMilonData] @resolver(name: "milonEventsByTime", paginated: true)

And finally, have created this UDF:

Query(
  Lambda(
    ["size", "after", "before"],
    Let(
      {
        match: Match(Index("milonEventsByTime")),
        page: If(
          Equals(Var("before"), null),
          If(
            Equals(Var("after"), null),
            Paginate(Var("match"), { size: Var("size") }),
            Paginate(Var("match"), { after: Var("after"), size: Var("size") })
          ),
          Paginate(Var("match"), { before: Var("before"), size: Var("size") })
        )
      },
      Map(Var("page"), Lambda("values", Get(Select(4, Var("values")))))
    )
  )
)

But when querying the dataset with that function I get no results:

I’ve followed the various indexing and sorting pages in the docs and in this forum, but I’m really struggling to get any further from here and would love some advice.

Indexes are automatically sorted by values. That means you have to use notificationtime in the values of the index instead of the terms. You can then just get the first element from the index results e.g. by Paginate with size 1 and/or Get on the index match to get the first element.

Keep in mind that values also represent the return fields of the index which requires you to add a reference in case you need get the document itself or other values in case you want direct access to these values without getting the document. Just make sure you place notificationtime as the first value and it will be sorted by notificaitontime first.

@databrecht thanks for this! that’s sorted it out.

I’m now struggling slightly with the next step: a query that will return the most recent notification for a given owner.

getMilonEventsSortedByTimeByUser(userId: String!): [VehicleMilonData] @resolver(name: "milonEventsByTimeByUser", paginated: true)

I’m currently trying to use the same index, just with a different UDF that includes the userId in the args lambda array. Runningnthe query gives me a "Integer expected, String provided." Error. From here I’m not sure where to go!

Hello, can you show us the Index you created then milonEventsByTime ?
What do you have as returned values ? And how you changed the query with userId ?

1 Like

@n44ps here is the index for milonEventsByTime

await client.query(
    q.CreateIndex({
      name: "milonEventsByTime",
      source: q.Collection("VehicleMilonData"),
      values: [
        { field: ["data", "notificationtime"], reverse: true },
        { field: ["ref"] }
      ]
    })
  );

And here are my returned values (which is what I want)

I already have a getMilonEventsByUserId that returns all the events of an associated user. this was quite easy to set up with Fauna. I’d now like to have a version of that query which will return the events from a user, but sorted by time. The last query below is what I now want.

type VehicleMilonData {
  userId: String!
  notificationtime: Time!
}

getMilonEventsByUserId(UserId: String!): [MilonData]
getMilonEventsSortedByTime: [eMilonData] @resolver(name: "milonEventsByTime", paginated: true)
getMilonEventsSortedByTimeByUserId(UserId: String!): [MilonData] @resolver(name: "milonEventsByTimeByUserId", paginated: true)

I’m not sure how to structure the new UDF for this. I know I need it to accept an extra argument for userId, but beyond that I keep running into errors

I’ve gotten a bit closer to what I think is the solution.

I’ve created a new index for the query:

{
  name: "milonEventsByUserIdSortedByTime",
  serialized: true,
  source: "MilonData",
  terms: [
    {
      field: ["data", "userId"]
    }
  ],
  values: [
    {
      field: ["data", "notificationtime"],
      reverse: true
    },
    {
      field: ["ref"]
    }
  ]
}

I’ve also written a new UDF:

Query(
  Lambda(
    ["size", "after", "before", "userId"],
    Let(
      {
        match: Match(
          Index("milonEventsByUserIdSortedByTime"),
          Var("userId")
        ),
        page: If(
          Equals(Var("before"), null),
          If(
            Equals(Var("after"), null),
            Paginate(Var("match"), { size: Var("size") }),
            Paginate(Var("match"), { after: Var("after"), size: Var("size") })
          ),
          Paginate(Var("match"), { before: Var("before"), size: Var("size") })
        )
      },
      Map(Var("page"), Lambda("values", Get(Select(1, Var("values")))))
    )
  )
)

I can verify that the index and UDF work fine, as calling this function in the Fauna shell gives me exactly what I want:

Call(Function("milonEventsByUserIdSortedByTime"), [5, 0, 0, String("5f57505d040e545b00")])

However I’m still having problems with graphql. My query in the model is:

getMilonEventsByUserIdSortedByTime(userId: String!): [VehicleMilonData] @resolver(name: "milonEventsByUserIdSortedByTime", paginated: true)

And when trying to use it like so:

query {
  getMilonEventsByuserIdSortedByTime(userId: "5f57505d040e545b00") {
    data {
      notificationtime
    }
  }
}

I get an error: "Integer expected, String provided.",

I’m not sure why running this query in graphql gives that error?

UPDATE: I’ve fixed it! the Lambda in my UDF needed to be reordered to be Lambda( ["userId", "size", "after", "before"]

1 Like