Retuning complex tree like structures in NEO4J without duplication - node.js

I have a method getPhotosInBucket which is to return all photos where a [:IN] relationship exists in a given bucket where each photo has its own set of relationships to Meta nodes that each contain properties corresponding to a different version of the image eg. small, medium, thumbnail etc. To make things even more complicated, each photo also has a creator and each creator has a thumbnail which like a photo has multiple Meta nodes related to it.
Below is a cut down version of what would be returned. In this example there is only one user who created the bucket and created each photo but in the real world user James could have a bucket containing 100 different images each with their own creator, thumbnail and creator thumbnail.
The query used to return this data is a little verbose and was just to demonstrate my structure a little bit and generate a visual graph:
MATCH (album:Asset:Album:Bucket {name: 'Bucketjamesprivate'})
MATCH (album)<-[:CREATED]-(creator)
OPTIONAL MATCH (album)<-[:THUMBNAIL]-(albumThumb)<-[:META]-(albumThumbMeta)
OPTIONAL MATCH (creator)<-[:THUMBNAIL]-(creatorThumb)
OPTIONAL MATCH (creatorThumb)<-[:META]-(creatorThumbMeta)
OPTIONAL MATCH (album)<-[:IN]-(photos)<-[:META]-(meta)
OPTIONAL MATCH (photos)<-[:CREATED]-(photoOwner)<-[:THUMBNAIL]-(photoThumbnail)<-[:META]-(photoThumbnailMeta)
RETURN DISTINCT album, albumThumb, albumThumbMeta, creator, creatorThumb, photos, meta, photoOwner, photoThumbnailMeta
This ends up returning the following:
As you can see, James created 1 bucket, 1 bucket thumbnail with 3 meta nodes, 2 photos IN the bucket each with 3 meta nodes and finally he has a thumbnail with 3 meta nodes.
The actual number of rows returned is 54 which can only grow exponentially when I have even just a handful of photos in a bucket to return so perhaps there is a more performant way of doing this.
I have tried using the collect method but it introduces some strange duplication which I'm sure is expected but I don't understand it well enough to know why.
The object I would like my method to return in the end would be something like this:
{
album: {
name: 'etc',
foo: 'bar',
bar: 'foo'
},
albumThumb: [
{
type: 'small',
src: 'www.foo.com/small'
},
{
type: 'medium',
src: 'www.foo.com/medium'
}
],
creator: {
name: 'James',
foo: 'bar'
},
creatorThumb: [
{
type: 'small',
src: 'www.foo.com/small'
},
{
type: 'medium',
src: 'www.foo.com/medium'
}
],
photos: [
{
photo: {
name: 'Photo 1',
date: '112432543636'
},
meta: [
{
type: 'small',
src: 'www.foo.com/small'
},
{
type: 'medium',
src: 'www.foo.com/medium'
}
],
creator: {
name: 'James',
foo: 'bar'
},
creatorThumb: [
{
type: 'small',
src: 'www.foo.com/small'
},
{
type: 'medium',
src: 'www.foo.com/medium'
}
]
},
{
photo: {
name: 'Photo 2',
date: '112432543636'
},
meta: [
{
type: 'small',
src: 'www.foo.com/small'
},
{
type: 'medium',
src: 'www.foo.com/medium'
}
],
creator: {
name: 'James',
foo: 'bar'
},
creatorThumb: [
{
type: 'small',
src: 'www.foo.com/small'
},
{
type: 'medium',
src: 'www.foo.com/medium'
}
]
}
]
}
Be it a photo in a bucket or a photo that is a thumbnail of some other node, there will only be a handful of meta nodes for each photo.
I'd also like to allow the client on the front end to paginate the photos, is there a way I can LIMIT and SKIP the photos IN the bucket?
Should I approach this differently and make 2 separate transactions? One to get the album, albumThumb, creator, creatorThumb and another to get the photos and associated thumbs?

I have a bit more time now, let me give it a shot ;)
MATCH (album:Asset:Album:Bucket {name: 'Bucketjamesprivate'})
MATCH (album)<-[:CREATED]-(creator)
OPTIONAL MATCH (album)<-[:THUMBNAIL]-(albumThumb)<-[:META]-(albumThumbMeta)
WITH
album,
collect({src: albumThumb.src, type: albumThumbMeta.type}) AS albumThumbs,
creator
OPTIONAL MATCH (creator)<-[:THUMBNAIL]-(creatorThumb)<-[:META]-(creatorThumbMeta)
WITH
album,
albumThumbs,
creator,
collect({src: creatorThumb.src, type: creatorThumbMeta.type}) AS creatorThumbs
OPTIONAL MATCH (album)<-[:IN]-(photo)<-[:META]-(photoMeta)
OPTIONAL MATCH
(photo)<-[:CREATED]-(photoOwner)<-[:THUMBNAIL]-(ownerThumb)
<-[:META]-(ownerThumbMeta)
WITH
album,
albumThumbs,
creator,
creatorThumbs,
photo,
collect({src: photo.src, type: photoMeta.type}) AS photoMeta,
photoOwner,
collect({src: ownerThumb.src, type: ownerThumbMeta.type}) AS ownerThumbs
RETURN
album,
albumThumbs,
creator,
creatorThumbs,
collect({
photo: photo,
meta: photoMeta,
owner: photoOwner,
ownerThumbs: ownerThumbs}) AS photos
Hopefully that will do it for you, or at least get you close enough!
This is my CREATE statement, BTW, in case anybody want to give it a shot:
CREATE
(bucket:Bucket {name: 'Bucketjamesprivate'})<-[:CREATED]-(james:Person {name: 'James'}),
(p1:Photo)-[:IN]->(bucket),
(p1)<-[:CREATED]-(james),
(p1)<-[:META]-(:Meta {type: 'small'}),
(p1)<-[:META]-(:Meta {type: 'medium'}),
(p1)<-[:META]-(:Meta {type: 'small_sq'}),
(p2:Photo)-[:IN]->(bucket),
(p2)<-[:CREATED]-(james),
(p2)<-[:META]-(:Meta {type: 'small'}),
(p2)<-[:META]-(:Meta {type: 'medium'}),
(p2)<-[:META]-(:Meta {type: 'small_sq'}),
(bucket_thumb:Thumbnail)-[:THUMBNAIL]->(bucket),
(bucket_thumb)<-[:CREATED]-(james),
(bucket_thumb)<-[:META]-(:Meta {type: 'small'}),
(bucket_thumb)<-[:META]-(:Meta {type: 'medium'}),
(bucket_thumb)<-[:META]-(:Meta {type: 'small_sq'}),
(james_thumb:Thumbnail)-[:THUMBNAIL]->(james),
(james_thumb)<-[:CREATED]-(james),
(james_thumb)<-[:META]-(:Meta {type: 'small'}),
(james_thumb)<-[:META]-(:Meta {type: 'medium'}),
(james_thumb)<-[:META]-(:Meta {type: 'small_sq'})

Related

Nodejs Create BinaryRow Data Structure with Raw Data with JSON string saved in Redis Client?

I am reading off from a Redis cache which has the user data with 'JSON string',
However in the Nodejs application, it sends the data as in the below format,
I want to create the exact same structure from the Redis JSON string, I am struggling to understand how to re-create the 'BinaryRow' in here.
if I do
util.inspect(user_info)
the final output needs to be like below.
user: [
BinaryRow {
user_id: 7558073,
country_id: 191,
city_id: 1975002,
name: 'iphone',
birth: 1980-09-25T18:30:00.000Z,
mode: 'active',
gender: 'M'
}
],
country: [ BinaryRow { country_title: 'Australia' } ],
city: [ BinaryRow { city_title: 'Gampahas' } ],
photo: [
BinaryRow {
photo_id: 100813,
visible: 'Y',
ref: 'ssss'
}
]

AJV JsonSchema validator reports seemingly incorrect error

I'm using AJV to validate a HTTP request payload against a schema. However, I see an error reported that I was not expecting. This is a code example to demonstrate the issue:
const schema = {
type: 'array',
minItems: 1,
items: {
anyOf: [
{
type: 'object',
properties: {
op: {
enum: ['replace']
},
path: {
type: 'string',
pattern: '/data/to/foo/bar',
},
value: {
type: 'string',
},
},
},{
type: 'object',
properties: {
op: {
enum: ['replace']
},
path: {
type: 'string',
pattern: '/data/to/baz',
},
value: {
type: 'object',
required: ['foo', 'bar'],
properties: {
foo: {
type: 'string',
},
bar: {
type: 'string',
},
}
}
}
}
],
},
}
const validator = new ajv()
const compiledValidator = validator.compile(schema)
const data = [
{ // this object should pass
op: 'replace',
path: '/data/to/foo/bar',
value: 'foo',
},
{ // this object should fail in the `value` mismatch (missing required attribute)
op: 'replace',
path: '/data/to/baz',
value: {
foo: 'bar',
},
},
]
compiledValidator(data)
console.log(compiledValidator.errors)
The schema defines a number of objects to which an incoming list of data objects should match. The first data item matches the schema (first item schema), however the second data item misses a required attribute (bar) in the value object.
When I run the above code I get the following output:
[
{
instancePath: '/1/path',
schemaPath: '#/items/anyOf/0/properties/path/pattern',
keyword: 'pattern',
params: { pattern: '/data/to/foo/bar' },
message: 'must match pattern "/data/to/foo/bar"'
},
{
instancePath: '/1/value',
schemaPath: '#/items/anyOf/1/properties/value/required',
keyword: 'required',
params: { missingProperty: 'bar' },
message: "must have required property 'bar'"
},
{
instancePath: '/1',
schemaPath: '#/items/anyOf',
keyword: 'anyOf',
params: {},
message: 'must match a schema in anyOf'
}
]
I understand the 2nd and the 3rd (last) errors. However, The first error seems to indicate that the path doesn't match path requirements of the first item schema. It is true that the 2nd data item doesn't match the 1st schema item but I don't seem to understand how it is relevant. I would assume that the error would be focused around the value, not the path since it matches on the path schemas.
Is there a way to get the error reporting more focused around the errors that matter?
There is no way for the evaluator to know whether you intended the first "anyOf" subschema to match or the second, so the most useful thing to do is to show you all the errors.
It can be confusing because you don't need to resolve all the errors, just some of them, which is why some implementations also offer a heirarchical error format to make it more easy to see relationships like this. Maybe if you request that ajv implement more of these error formats, it will happen :)
You can see that all of the errors pertain to the second item in the data by looking at the instancePath for each error, which all start with /1. This is the location within the data that generated the error.
So let's look at the second item and compare it against the schema.
{ // this object should fail in the `value` mismatch (missing required attribute)
op: 'replace',
path: '/data/to/baz',
value: {
foo: 'bar',
},
}
The schema says that an item should either (from the anyOf) have
path: '/data/to/foo/bar' and value: { type: 'string' }, or
path: '/data/to/baz' and value: { required: [ 'foo', 'bar' ] }
The reported errors are:
The first case fails because path is wrong.
The second case fails because /value/bar is not present.
The last error is just the anyOf reporting that none of the options passed.

Finding the property by value mongoose

Have a question about finding nested property in mongoose. Can't find the right solution for my issue. Lets say I have Parent with properties: name, child. Same will go for every single child: name, child. Is there a possible way for example to find the value by entering just a name? If the nth children is at nth level with name "Tom" for this moment I have to go like children.name.children.name......
-Parent
-children
-children
-children
My schema looks like:
const TreeSchema = new mongoose.Schema({
name: {
type: "String",
unique: true,
required: true,
},
children: [
{
name: {
type: "String",
},
},
],
date: {
type: Date,
default: Date.now,
},
});
request image
So the issue is that if I have nth child I can't just find him like this. I have to keep going deep every time like children.name : req....
router.post("/:parent/:child", async (req, res) => {
let tree = await Tree.findOne({name : req.params.child})
]);

Boosting results based on a boolean field

I have tons of articles in various stores. Some of these articles are own brand articles and should be ranked higher than other articles in my elasticsearch search results (both ownbrand and non ownbrand should be shown however.)
I already tried different approached with field_value_factor but that doesn't seem to go well with a boolean field.
I also tried the approached solution in Boosting an elasticsearch result based on a boolean field value but that didn't worked well for me. The results with the ownBrand approach were still way lower ranked then a lot of non ownBrand articles.
Index:
schema: {
articleId: { type: 'text' },
brandId: { type: 'text' },
brandName: { type: 'text' },
countryId: { type: 'text' },
description: { type: 'text' },
isOwnBrand: { type: 'boolean' },
stores: { type: 'keyword' },
},
};
Query:
query: {
function_score: {
query: {
bool: {
must: {
multi_match: {
query: searchterm,
fields: ['name^5', 'name.ngram'],
fuzziness: 'auto',
prefix_length: 2,
},
},
filter: [{ term: { stores: storeId } }],
},
},
},
},
};
The result should prioritize fields with isOwnBrand = true at the top while still showing relevant articles with isOwnBrand = false below.
I am a bit lost on how to handle this.
You can use Field Value factor. Below should work fine even on a boolean field as well. try it
{
"query": {
"function_score": {
"query" {...}, #your normal query as in question
#add below after query
"field_value_factor": {
"field": "isOwnBrand",
"factor": 1.2,
"modifier": "sqrt",
"missing": 1
}
}
}
}
One caveat i can think of but haven't tested - since false is 0, above script will score down all documents with false to 0 score, which messes up scoring. You could either make the isOwnBrand a number field and set priority starting 1
OR you could also use script_score

Elasticsearch english language analyzer with nodejs

First time trying to implement elastic search using aws hosted service with nodejs. Referred to the official docs and came up with this:
//1. create index
client.indices.create({ index: 'products' });
//2. mapping
client.indices.putMapping({
index: 'products',
type: 'products',
body: {
properties: {
'product_name': {
'type': 'string',
"analyzer": "english"
},
'product_description': {
'type': 'string',
"analyzer": "english"
}
}
}
});
//3. import documents..
product_name: Testing
//4. search
client.search({
index: 'products',
type: 'products',
q: keywords,
analyzer: 'english',
size: 10
});
Now, if I search for 'Testing' or 'Token Testing', this returns 1 result. But if I test passing 'Test' or 'Tist' as a keyword, seems the analyzer isn't picking up and I get no results.
Updated: Added analyzer: 'english' according to #Val answer, but still no results for 'Test' or 'Tist'.
That's because when using the q parameter, a query_string query is made and the default analyzer of your input string is the standard analyzer.
You have two choices here:
A. Include the field name in your query string:
client.search({
index: 'products',
type: 'products',
q: 'product_name:' + keywords, <--- modify this
size: 10
});
B. Specify english as the query-time analyzer to produce the same tokens as has been previously indexed:
client.search({
index: 'products',
type: 'products',
q: keywords,
analyzer: 'english', <--- or add this
size: 10
});

Resources