Indexes: Search by Boolean? - faunadb

I'm having some trouble with FaunaDB Indexes. FQL is quite powerful but the docs seem to be limited (for now) to only a few examples/use cases. (Searching by String)
I have a collection of Orders, with a few fields: status, id, client, material and date.
My goal is to search/filter for orders depending on their Status, OPEN OR CLOSED (Boolean true/false).
Here is the Index I created:
CreateIndex({
name: "orders_all_by_open_asc",
unique: false,
serialized: true,
source: Collection("orders"),
terms: [{ field: ["data", "status"] }],
values: [
{ field: ["data", "unique_id"] },
{ field: ["data", "client"] },
{ field: ["data", "material"] },
{ field: ["data", "date"] }
]
}
So with this Index, I want to specify either TRUE or FALSE and get all corresponding orders, including their data (fields).
I'm having two problems:
When I pass TRUE OR FALSE using the Javascript Driver, nothing is returned :( Is it possible to search by Booleans at all, or only by String/Number?
Here is my Query (in FQL, using the Shell):
Match(Index("orders_all_by_open_asc"), true)
And unfortunately, nothing is returned. I'm probably doing this wrong.
Second (slightly unrelated) question. When I create an Index and specify a bunch of Values, it seems the data returned is in Array format, with only the values, not the Fields. An example:
[
1001,
"client1",
"concrete",
"2021-04-13T00:00:00.000Z",
],
[
1002,
"client2",
"wood",
"2021-04-13T00:00:00.000Z",
]
This format is bad for me, because my front-end expects receiving an Object with the Fields as a key and the Values as properties. Example:
data:
{
unique_id : 1001,
client : "client1",
material : "concrete",
date: "2021-04-13T00:00:00.000Z"
},
{
unique_id : 1002,
client : "client2",
material : "wood",
date: "2021-04-13T00:00:00.000Z"
},
etc..
Is there any way to get the Field as well as the Value when using Index values, or will it always return an Array (and not an object)?
Could I use a Lambda or something for this?
I do have another Query that uses Map and Lambda to good effect, and returns the entire document, including the Ref and Data fields:
Map(
Paginate(
Match(Index("orders_by_date"), date),
),
Lambda('item', Get(Var('item')))
)
This works very nicely but unfortunately, it also performs one Get request per Document returned and that seems very inefficient.
This new Index I'm wanting to build, to filter by Order Status, will be used to return hundreds of Orders, hundreds of times a day. So I'm trying to keep it as efficient as possible, but if it can only return an Array it won't be useful.
Thanks in advance!! Indexes are great but hard to grasp, so any insight will be appreciated.

You didn't show us exactly what you have done, so here's an example that shows that filtering on boolean values does work using the index you created as-is:
> CreateCollection({ name: "orders" })
{
ref: Collection("orders"),
ts: 1618350087320000,
history_days: 30,
name: 'orders'
}
> Create(Collection("orders"), { data: {
unique_id: 1,
client: "me",
material: "stone",
date: Now(),
status: true
}})
{
ref: Ref(Collection("orders"), "295794155241603584"),
ts: 1618350138800000,
data: {
unique_id: 1,
client: 'me',
material: 'stone',
date: Time("2021-04-13T21:42:18.784Z"),
status: true
}
}
> Create(Collection("orders"), { data: {
unique_id: 2,
client: "you",
material: "muslin",
date: Now(),
status: false
}})
{
ref: Ref(Collection("orders"), "295794180038328832"),
ts: 1618350162440000,
data: {
unique_id: 2,
client: 'you',
material: 'muslin',
date: Time("2021-04-13T21:42:42.437Z"),
status: false
}
}
> CreateIndex({
name: "orders_all_by_open_asc",
unique: false,
serialized: true,
source: Collection("orders"),
terms: [{ field: ["data", "status"] }],
values: [
{ field: ["data", "unique_id"] },
{ field: ["data", "client"] },
{ field: ["data", "material"] },
{ field: ["data", "date"] }
]
})
{
ref: Index("orders_all_by_open_asc"),
ts: 1618350185940000,
active: true,
serialized: true,
name: 'orders_all_by_open_asc',
unique: false,
source: Collection("orders"),
terms: [ { field: [ 'data', 'status' ] } ],
values: [
{ field: [ 'data', 'unique_id' ] },
{ field: [ 'data', 'client' ] },
{ field: [ 'data', 'material' ] },
{ field: [ 'data', 'date' ] }
],
partitions: 1
}
> Paginate(Match(Index("orders_all_by_open_asc"), true))
{ data: [ [ 1, 'me', 'stone', Time("2021-04-13T21:42:18.784Z") ] ] }
> Paginate(Match(Index("orders_all_by_open_asc"), false))
{ data: [ [ 2, 'you', 'muslin', Time("2021-04-13T21:42:42.437Z") ] ] }
It's a little more work, but you can compose whatever return format that you like:
> Map(
Paginate(Match(Index("orders_all_by_open_asc"), false)),
Lambda(
["unique_id", "client", "material", "date"],
{
unique_id: Var("unique_id"),
client: Var("client"),
material: Var("material"),
date: Var("date"),
}
)
)
{
data: [
{
unique_id: 2,
client: 'you',
material: 'muslin',
date: Time("2021-04-13T21:42:42.437Z")
}
]
}
It's still an array of results, but each result is now an object with the appropriate field names.

Not too familiar with FQL, but I am somewhat familiar with SQL languages. Essentially, database languages usually treat all of your values as strings until they don't need to anymore. Instead, your query should use the string definition that FQL is expecting. I believe it should be OPEN or CLOSED in your case. You can simply have an if statement in java to determine whether to search for "OPEN" or "CLOSED".
To answer your second question, I don't know for FQL, but if that is what is returned, then your approach with a lamda seems to be fine. Not much else you can do about it from your end other than hope that you get a different way to get entries in API form somewhere in the future. At the end of the day, an O(n) operation in this context is not too bad, and only having to return a hundred or so orders shouldn't be the most painful thing in the world.
If you are truly worried about this, you can break up the request into portions, so you return only the first 100, then when frontend wants the next set, you send the next 100. You can cache the results too to make it very fast from the front-end perspective.

Another suggestion, maybe I am wrong and failed at searching the docs, but I will post anyway just in case it's helpful.
My index was failing to return objects, example data here is the client field:
"data": {
"status": "LIVRAISON",
"open": true,
"unique_id": 1001,
"client": {
"name": "TEST1",
"contact_name": "Bob",
"email": "bob#client.com",
"phone": "555-555-5555"
Here, the client field returned as null even though it was specified in the Index.
From reading the docs, here: https://docs.fauna.com/fauna/current/api/fql/indexes?lang=javascript#value
In the Value Objects section, I was able to understand that for Objects, the Index Field must be defined as an Array, one for each Object key. Example for my data:
{ field: ['data', 'client', 'name'] },
{ field: ['data', 'client', 'contact_name'] },
{ field: ['data', 'client', 'email'] },
{ field: ['data', 'client', 'phone'] },
This was slightly confusing, because my beginner brain expected that defining the 'client' field would simply return the entire object, like so:
{ field: ['data', 'client'] },
The only part about this in the docs was this sentence: The field ["data", "address", "street"] refers to the street field contained in an address object within the document’s data object.
This is enough information, but maybe it would deserve its own section, with a longer example? Of course the simple sentence works, but with a sub-section called 'Adding Objects to Fields' or something, this would make it extra-clear.
Hoping my moments of confusion will help out. Loving FaunaDB so far, keep up the great work :)

Related

Faunadb create index on child item field

I'm trying to get an index on a field of a child item in my document. The data is this:
[
{
"ref": Ref(Collection("ColA"), "111111111111111111"),
"ts":1659241462840000,
"data":{
"name":"Test a",
"members":[
{
"userId":"1",
"roles":[
"admin"
]
}
]
}
},
{
"ref": Ref(Collection("ColA"), "111111111111111112"),
"ts":1659241462840000,
"data":{
"name":"Test b",
"members":[
{
"userId":"1",
"roles":[
"admin"
]
},
{
"userId":"2",
"roles":[
"read-only"
]
}
]
}
},
{
"ref": Ref(Collection("ColA"), "111111111111111113"),
"ts":1659241462840000,
"data":{
"name":"Test c",
"members":[
{
"userId":"2",
"roles":[
"admin"
]
}
]
}
}
]
Trying to using data.members.userId as term in the index. This only gives back one result when I use the index with the filter value '1'
Then I tried to create the index as following:
CreateIndex({
name: 'spaces_member_ids',
source: {
collection: Collection("ColA"),
fields: {
members: Query(
Lambda(
"ColA",
Select(["data", "members", "userId"], Var("ColA"), '')
)
),
},
},
terms: [
{ binding: "members" },
],
values: [
{ field: "data.name" },
{ field: "ref" },
]
})
But that gives no results when I use the index with the filter value '1' Both times I expect to get two items back (Test a and Test b).
Anyone knows how to create an index that gived back all the data of ColA filtered on field 'userId' in the 'members' array?
The problem is that there is no userId field as a direct descendant of the members array.
For background, Fauna index entries can only contain scalar values. Objects are not indexed at all. For arrays, one index entry is created per scalar value in the array. If you attempt to index multiple array fields, the number of index entries produced is the Cartesian product of the items in all indexed arrays.
If you create your index like so:
CreateIndex({
name: 'spaces_member_ids',
source: Collection("ColA"),
terms: [
{ field: ["data", "members", 0, "userId"] },
],
values: [
{ field: ["data", "name"] },
{ field: "ref" },
]
})
Then you'll be able to search for userId values that appear in the first item in the members array.
If you need to create index entries for all userId values from each ColA document, then your binding approach is close, but it needs to provide an array.
CreateIndex({
name: "spaces_member_ids",
source: {
collection: Collection("ColA"),
fields: {
members: Query(
Lambda(
"ColA",
Map(
Select(["data", "members"], Var("ColA"), []),
Lambda(
"member",
Select(["userId"], Var("member"), "")
)
)
)
),
},
},
terms: [
{ binding: "members" },
],
values: [
{ field: ["data", "name"] },
{ field: "ref" },
]
})
The notable changes that I made are:
Within the binding, Map is used to iterate on the members field in the document.
Simply returning the userId field value within a Map is sufficient to return an array of userId values.
Corrected the syntax in the values definition. Fauna indexes don't process dot notation.

How to show/hide fields in the admin UI depending on the value from another field with KeystoneJS Next?

I have a Category model set in schema.ts as follows:
Category: list({
fields: {
name: text(),
type: select({
options: [
{ label: "MultipleChoice", value: "MultipleChoice" },
{ label: "Range", value: "Range" },
],
defaultValue: "...",
isRequired: true,
isUnique: true,
ui: { displayMode: "segmented-control" },
}),
from: integer(),
to: integer(),
options: text()
},
})
And this renders these components in the admin UI:
I'd like to show from and to fields only when Range is selected (hiding options field) and the other way around when MultipleChoice is selected. Is there a way to achieve that with Keystone Next?
I also tried another approach splitting the category types in different models and then relate them somehow with the Category model, but I'm not sure how to do so. Something like:
CategoryRange: list({
ui: {
isHidden: true,
},
fields: {
from: integer(),
to: integer(),
},
}),
CategoryMultipleChoice: list({
ui: {
isHidden: true,
},
fields: {
options: text(),
},
})
Conditional fields were supported in Keystone 4. They haven't been brought forward to Keystone 6 (aka. "Next") yet but they're on the roadmap. I'd expect support for them to arrive in the next few months.
Right now, in Keystone 6, probably the best you could do would be to create a custom field type that collected the "type" and "from/to" fields together. This would let you define an interface in the Admin UI that implemented whatever presentation rules you liked.

How do I index a value which is a #relation in a document in FaunaDB?

Is it possible to create an index of a value from a #relation type in FaunaDB? Here is the schema but I just cannot figure out how to create an index for what would be the data.testing.status value.
type TestType {
testing: Testing!
}
type Testing {
status: PaymentStatus!
testType: [TestType!] #relation
}
enum PaymentStatus {
PAID
UNPAID
}
I don't know if the enum is causing an issue? I can't find any documentation on this.
Here is the query:
Map(
Paginate(Match(Index("certificate_remittance_by_remittance"), "UNPAID")),
Lambda("ref", Get(Var("ref")))
)
and the relevant document data:
"ref": Ref(Collection("Certificate"), "302119834927235593"),
"ts": 1624382777140000,
"data": {
"remittance": Ref(Collection("Remittance"), "302119834830766601"),
}
and remittance document:
{
"ref": Ref(Collection("Remittance"), "302119834830766601"),
"ts": 1624382777140000,
"data": {
"status": "UNPAID",
"chequeNumber": "",
"remittanceOwed": 245,
"remittanceAmount": 245
}
}
Certainly.
The enum portion of your schema isn't causing an issue. If it were, you would not have been able to import the schema successfully.
With your provided schema, note that the GraphQL API has already created a relationship index for you:
Get(Index("testType_testing_by_testing"))
{
ref: Index("testType_testing_by_testing"),
ts: 1624315929200000,
active: true,
serialized: true,
name: "testType_testing_by_testing",
source: Collection("TestType"),
data: {
gql: {
ts: Time("2021-06-21T22:52:08.969203Z")
}
},
terms: [
{
field: ["data", "testing"]
}
],
unique: false,
partitions: 1
}
To add an index for the TestType collection, on the status field, you'd run:
CreateIndex({
name: "TestType_by_status",
source: Collection("TestType"),
terms: [
{ field: ["data", "status"] },
]
})

Object property is filled when shown but undefined when accessed. Nodejs Sequelize

I am performing this raw sql query
SELECT postId, users.id as userId,users.firstName,users.lastName,users.avatar,COUNT(postId) as
numOfLikes,body
FROM posts
INNER JOIN likes ON likes.postId = posts.id
INNER JOIN users ON users.id = posts.userId
GROUP BY postId
ORDER BY postId DESC
through nodeJs sequelize ORM
Posts.findAll({
attributes: ['id','body','createdAt', [db.fn('count', db.col('likes.postId')), 'numOfLikes']],
include: [{ attributes: [], model: Likes,required:true, },{model:Users,required:true}],
group: ['id'],
order: [['id', 'DESC']]
})
I receive everything as it should be but cannot access numOfLikes object property (undefined)
{
"id": 18,
"body": "This show was organized.",
"createdAt": "2021-03-06T23:55:44.000Z",
"numOfLikes": 5,
"user": {
"id": 73,
"firstName": "Paolo",
"lastName": "Jovovic",
"email": "dzonnna#gmail.com",
"email_verified": "1",
"password": "$2b$10$6fLwPfuLP8Jfp7em0iqBm.YhznDut8AWOmUPynqecfd9YMvZBMaXq",
"google_id": null,
"avatar": "1_aZF6_EToO4T3ZeHXfgF-Vg.png",
"role": "0",
"createdAt": "2021-02-28T22:30:42.000Z",
"updatedAt": "2021-03-01T23:59:21.000Z"
}
}
I had this same issue - I was selecting certain attributes and the query was working correctly.
const books = await Book.findAll({
where: {status_id: [status[0].id, status[1].id]},
attributes: ["id", "cover", "title", "status_id", "created_at"],
order: [["_id", "DESC"]],
})
the object showed the value but when i tried to access it, it was always undefined.
dataValues: {
id: '9beb341c-a0fa-489d-8a32-c9ec28f0ab16',
cover: 'cover_500.jpg',
title: 'birth',
status_id: '8a5b8c46-b5ac-477a-b1c5-ca2210399e6c',
created_at: 2022-10-16T02:10:32.000Z
},
I should point out here that i've changed the name of createAt in the model
createdAt: {
field: "created_at",
type: Sequelize.DATE,
},
when trying to access the value with book.create_at or book.createAt both come back as undefined even though they are clearly on the object. this is a very strange behavior. since they are in the datavalues they should be accessible.
when I remove the attributes selection column and just return everything for the row, it magically works.
so the issue is either,
I changed the name in the model and there is some kind of issue / error in sequalize
There is just some kind of issue / error with sequalize access the datavalue
either way, you can grab the whole row and only return / build your object out of the columns you desire.
I switched the attributes to
attributes: ["id", "cover", "title", "status_id", "createdAt"],
and it is now letting me access the field with createdAt but still not created_at so the issue appears to be the renaming of the field along with the selection of attributes.
hope this helps someone until they can fix this.
Posts.findAll({
attributes: ['id', 'body', 'createdAt', [db.fn('count', db.col('likes.postId')), 'numOfLikes']],
include: [{ attributes: [], model: Likes, required: true, }, { model: Users, required: true }],
group: ['id'],
order: [['id', 'DESC']]
}).then(postList => {
postList = JSON.parse(JSON.stringify(postList)) // This is important
postList.forEach(post => {
// access post
console.log(post, post.users)
});
})

How to select all documents where one date field is after another date field in FaunaDB

I have a very simple collection with documents that look like this:
{
...
latestEdit: Time(...),
lastPublished: Time(...)
}
I would like to query all documents that have a latestEdit time that's after lastPublished time.
I find FQL to be very different to SQL and I'm finding the transition quite hard.
Any help much appreciated.
Fauna's FQL is not declarative, so you have to construct the appropriate indexes and queries to help you solve problems like this.
Fauna indexes have a feature called "bindings", which allow you to provide a user-defined function that can compute a value based on document values. The binding lets us index the computed value by itself (rather than having to index on latestEdit or lastPublished). Here's what that might look like:
CreateIndex({
name: "edit_after_published",
source: {
collection: Collection("test"),
fields: {
needsPublish: Query(
Lambda(
"doc",
Let(
{
latestEdit: Select(["data", "latestEdit"], Var("doc")),
lastPublished: Select(["data", "lastPublished"], Var("doc")),
},
If(
GT(Var("latestEdit"), Var("lastPublished")),
true,
false
)
)
)
)
}
},
terms: [ { binding: "needsPublish" } ]
})
You can see that we define a binding called needsPublish. The binding uses Let to define named values for the two document fields that we want to compare, and then the If statement checks to see if the latestEdit value is greather than lastPublished value: when it is we return true, otherwise we return false. Then, the binding is used in the index's terms definition, which defines the fields that we want to be able to search on.
I created sample documents in a collection called test, like so:
> Create(Collection("test"), { data: { name: "first", latestEdit: Now(), lastPublished: TimeSubtract(Now(), 1, "day") }})
{
ref: Ref(Collection("test"), "306026106743423488"),
ts: 1628108088190000,
data: {
name: 'first',
latestEdit: Time("2021-08-04T20:14:48.121Z"),
lastPublished: Time("2021-08-03T20:14:48.121Z")
}
}
> Create(Collection("test"), { data: { name: "second", lastPublished: Now(), latestEdit: TimeSubtract(Now(), 1, "day") }})
{
ref: Ref(Collection("test"), "306026150784664064"),
ts: 1628108130150000,
data: {
name: 'second',
lastPublished: Time("2021-08-04T20:15:30.148Z"),
latestEdit: Time("2021-08-03T20:15:30.148Z")
}
}
The first document subtracts one day from lastPublished and the second document subtracts one day from latestEdit, to test both conditions of the binding.
Then we can query for all documents where needsPublish results in true:
> Map(Paginate(Match(Index("edit_after_published"), true)), Lambda("X", Get(Var("X"))))
{
data: [
{
ref: Ref(Collection("test"), "306026106743423488"),
ts: 1628108088190000,
data: {
name: 'first',
latestEdit: Time("2021-08-04T20:14:48.121Z"),
lastPublished: Time("2021-08-03T20:14:48.121Z")
}
}
]
}
And we can also query for all documents where needsPublish is false:
> Map(Paginate(Match(Index("edit_after_published"), false)), Lambda("X", Get(Var("X"))))
{
data: [
{
ref: Ref(Collection("test"), "306026150784664064"),
ts: 1628108130150000,
data: {
name: 'second',
lastPublished: Time("2021-08-04T20:15:30.148Z"),
latestEdit: Time("2021-08-03T20:15:30.148Z")
}
}
]
}