PHP MongoDB\Client updateOne add new field to existing document array - php

I'm struggling to update a document and add new elements (fields) into it's existing array without losing the array elements on update.
This is my code which inserts a new document into the collection if it doesn't already exist (upsert):
$updateResult = $collection->findOneAndUpdate(
[
'recording-id' => $out['recording']->id
],
['$set' => [
'release' => [
'id' => $out['release']->id,
'title' => $out['release']->title,
'date' => $out['release']->date,
'country' => $out['release']->country
],
'artist' => [
'id' => $out['artist']->id,
'name' => $out['artist']->name,
],
'recording' => [
'id' => $out['recording']->id,
'title' => $out['recording']->title,
'score' => $out['recording']->score,
'length' => $out['recording']->length,
'release-count' => count($out['recording']->releases),
],
'release-group' => [
'id' => $out['release-group']['id'],
'title' => $out['release-group']['title'],
'first-release-date'=>$out['release-group']['first-release-date'],
'primary-type' => $out['release-group']['primary-type'],
'musicbrainz' => $out['release-group']['musicbrainz'],
'url-rels' => $out['release-group']['url-rels'],
'coverart' => $out['release-group']['coverart']
],
'execution' => [
'firstfind' => $out['execution']->time
]
]
],
['upsert' => true,
'projection' =>
[
'_id' => 0,
'release' => 1,
'artist' => 1,
'recording' => 1,
'release-group' => 1,
'execution' => 1
],
'returnDocument' => MongoDB\Operation\FindOneAndUpdate::RETURN_DOCUMENT_AFTER,
]
);
So now I have an existing document in a collection:
{
"_id" : ObjectId("5d1a6aaf5ecc8001ee858f6c"),
"recording-id" : "d0d439f9-5324-4728-8706-2da39adb89c5",
"artist" : {
"id" : "9d97b077-b28d-4ba8-a3d9-c71926e3b2b6",
"name" : "Gordon Lightfoot"
},
"recording" : {
"id" : "d0d439f9-5324-4728-8706-2da39adb89c5",
"title" : "Sundown",
"score" : 100,
"length" : 184000,
"release-count" : 2
},
"release" : {
"id" : "0c008d76-2bc9-44a3-854b-0a08cde89337",
"title" : "All Live",
"date" : "2012-04-24",
"country" : "CA"
},
"release-group" : {
"id" : "0a5d5f33-8e9d-4fa4-b622-a95e4218a3c4",
"title" : "All Live",
"first-release-date" : "2012-04-24",
"primary-type" : "Album",
"musicbrainz" : "https://musicbrainz.org/release-group/0a5d5f33-8e9d-4fa4-b622-a95e4218a3c4",
"url-rels" : "https://musicbrainz.org/ws/2/release-group/0a5d5f33-8e9d-4fa4-b622-a95e4218a3c4?inc=url-rels&fmt=json",
"coverart" : null
}
}
Now, I would like to update this document, and add new fields into the arrays. The new fields are to be added to certain fields.
Here is the code doing that:
$collection = (new MongoDB\Client)->stream->musicbrainz;
$updateResult = $collection->updateOne(
[
'recording-id' => $out['recording']['id']
],
['$addToSet' => [
'artist' => [
'wikiQiD' => $out['artist']['qid'],
'wiki-extract' => $out['artist']['wiki-extract'],
'wiki-pageid' => $out['artist']['pageid'],
],
'release-group' => [
'wikiQiD' => $out['release-group']['qid'],
'wiki-extract' => $out['release-group']['wiki-extract']
]
]
],
[
'upsert' => true,
'returnDocument' => MongoDB\Operation\FindOneAndUpdate::RETURN_DOCUMENT_AFTER,
]
);
I've noticed there's "$addToSet" and "$push" commands, and could use assistance with what the difference is between these two commands.
If the field is absent in the document to update, $push adds the array
field with the value as its element.
The $addToSet operator adds a value to an array unless the value is
already present, in which case $addToSet does nothing to that array.
I did some googling, and reading of the MongoDB/Client UpdateOne function, but can't seem to find a way to append these fields to the existing arrays.
The error I'm getting is:
Fatal error: Uncaught MongoDB\Driver\Exception\BulkWriteException: The field 'artist' must be an array but is of type object in document {_id: ObjectId('5d1a6aaf5ecc8001ee858f6c')} in ...
I know the following:
It could be my document, as it's not a proper array that Fatal error is complaining about.
It could be my `findOneAndUpdate' formatting, and I'm not doing that correctly.
It could be both and I have it all wrong from the very start.
Any insight or constructive criticism is appreciated, just refrain from flames, pls.

Here's the working code I finally use to get it do what I want it to.
It's a simple matter of just setting your field names and values and mongo will update the found record replacing it with the array sent to it. No need to push or anything.
function firstFindMongoUpdate($out) {
$collection = (new MongoDB\Client)->stream->musicbrainz;
$updateResult = $collection->findOneAndUpdate(
[
'recording-id' => $out['recording']->id
],
['$set' => [
'query' => $out['query'],
'release' => [
'id' => $out['release']->id,
'title' => $out['release']->title,
'date' => $out['release']->date,
'country' => $out['release']->country,
'label' => $out['release']->label,
],
'artist' => [
'id' => $out['artist']->id,
'name' => $out['artist']->name,
'wiki' => $out['artist']->wiki,
],
'recording' => [
'id' => $out['recording']->id,
'title' => $out['recording']->title, // sometimes contains apostophe ie; Bill‘s Love (option ] key)
'score' => $out['recording']->score,
'length' => $out['recording']->length,
'release-count' => count($out['recording']->releases)
],
'release-group' => [
'id' => $out['release-group']['id'],
'title' => $out['release-group']['title'],
'first-release-date'=>$out['release-group']['first-release-date'],
'primary-type' => $out['release-group']['primary-type'],
'musicbrainz' => $out['release-group']['musicbrainz'],
'url-rels' => $out['release-group']['url-rels'],
'coverart' => $out['release-group']['coverart'],
'wiki' => $out['release-group']['wiki']
],
'execution' => [
'artistQuery' => $out['execution']->artistQuery,
'recordingQuery'=> $out['execution']->recordingQuery,
'time' => $out['execution']->time
]
]
],
['upsert' => true,
'projection' =>
[
'_id' => 1,
'query' => 1,
'release' => 1,
'artist' => 1,
'recording' => 1,
'release-group' => 1,
'execution' => 1
],
'returnDocument' => MongoDB\Operation\FindOneAndUpdate::RETURN_DOCUMENT_AFTER,
]
);

Related

Is it possible to use specific index in Mongodb using PHP?

Can somebody tell me please if is possible to force Mongodb to use specific index from PHP script? I have something like this:
$mongoPipeline = [
[
'$match' => [
'global_campaign_id' => ['$in' => $campaigns_ids]
]
],
[
'$group' => [
'_id' => [
'global_campaign_id' => '$global_campaign_id',
'device_id' => '$device_id',
'partner_id' => '$partner_id',
],
'date_last' => ['$max' => '$date_created'],
'partner_id' => ['$first' => '$server'],
'campaign_id' => ['$first' => '$global_campaign_id'],
'device_id' => ['$first' => '$device_id']
]
]
];
$options = [
'allowDiskUse' => true,
'maxTimeMS' => 1000 * 60 * 1,
'explain' => true,
];
$cursor = $this->mongoDb->{$collection}->aggregate($pipeline, $options);
Is it possible to add index to options object? If I use explain it seems it does not use an index prepared for this query. It is compoud index:
global_campaign_id: 1, device_id: 1, partner_id: 1, date_created: 1

MongoDB filter query results by $lookup array

MongoDB 3.4
I have a project and project_permission collections. The project_permission collection contains permissions to the projects for some users. A single user can have multiple different permissions to the project.
[
'$lookup' => [
'from' => ProjectPermission::collectionName(),
'localField' => '_id',
'foreignField' => 'project_id',
'as' => 'project_permissions'
]
],
[
'$project' => [
// ... irrelevant fields here
'permissions' => '$project_permissions'
]
],
this is how the project query results looks like without filtering:
// other project results
// ... other fields
'permissions' => [
0 => [
'_id' => '5d2873aafa873b2b7c000fad'
'project_id' => '56a9e5c5d18cacc72a485839'
'user_id' => '562f6bfc05dfe9570fb6e427'
'permission' => 'read'
'created_at' => 1562932138
'updated_at' => 1562932139
]
1 => [
'_id' => '5d2879fdfa873b2b7c000fbd'
'project_id' => '56a9e5c5d18cacc72a485839'
'user_id' => '562f6bfc05dfe9570fb6e427'
'permission' => 'write'
'created_at' => 1562932139
'updated_at' => 1562932140
]
2 => [
'_id' => '5db960b5fa873b1604005e8e'
'project_id' => '56a9e5c5d18cacc72a485839'
'user_id' => '582b30dd1e634e6362e1b504'
'permission' => 'write'
'created_at' => 1572430005
'updated_at' => 1572430005
]
]
What I would like to achieve is to return with only those projects where the client - who requested the query - has a specific permission to the project, for example write.
The way I tried it:
pipeline: [
0 => [
'$match' => [
// not related to the problem
]
]
1 => [
'$match' => [
'$and' => [
0 => [
'shared_permissions' => [
'$eq' => true
]
]
1 => [
'$or' => [
0 => [
'project_permissions' => [
'$exists' => true
'$ne' => []
]
]
1 => [
'owner_id' => [
'$ne' => MongoDB\BSON\ObjectId#1
(
[oid] => '582b30dd1e634e6362e1b504'
)
]
]
]
]
]
]
]
2 => [
'$lookup' => [
'from' => 'project_permission'
'localField' => '_id'
'foreignField' => 'project_id'
'as' => 'project_permissions'
]
]
3 => [
'$project' => [
// more not important fields here
'shared_permissions' => 1
'permissions' => [
'$map' => [
'input' => [
'$filter' => [
'input' => '$project_permissions'
'as' => 'project_permission'
'cond' => [
'$and' => [
0 => [
'$eq' => ['$$project_permission.user_id', MongoDB\BSON\ObjectId#1
(
[oid] => '582b30dd1e634e6362e1b504'
)
]
1 => [
'$eq' => ['$$project_permission.permission', 'write']
]
]
]
]
]
'as' => 'project_permission'
'in' => [
'user_id' => '$$project_permission.user_id'
'permission' => '$$project_permission.permission'
]
]
]
]
]
]
For this I almost get the correct response:
[
0 => [
'_id' => '56a9e5c5d18cacc72a485839'
'short_id' => 3
'title' => 'Modified title'
'owner_id' => '562f692a05dfe9560fb6e428'
'updated_at' => 1572435428
'owner_name' => 'Borat Sagdiyev'
'shared_permissions' => true
'permissions' => [
0 => [
'user_id' => '582b30dd1e634e6362e1b504'
'permission' => 'write'
]
]
]
1 => []
]
The problem with this is that empty array, where the result was filtered out - and it wouldn't be a problem if the empty array wouldn't be in the result, because if I use the pagination, then it says two results, instead of one. And we know that in the worst case we would get back an array of empty arrays only.
So what I would like to achieve is this last example results without empty arrays in a way where the pagination will be fine with it too.
ps.: unwind is not an option, because of some structural conventions.
Any ideas?
To perform an equality match between a field from the input documents with a field from the documents of the “joined” collection, the $lookup stage has the following syntax:
{
$lookup:
{
from: <collection to join>,
localField: <field from the input documents>,
foreignField: <field from the documents of the "from" collection>,
as: <output array field>
}
}

Implement suggest function of elasticsearch-php client in my API

I am trying to implement the suggest function of elasticsearch-php client in my API to suggest people some already existing problems.
I have made index for my problems
'index' => 'newproblemindex',
'body' => [
'settings' => [
'number_of_shards' => 3,
'number_of_replicas' => 2
],
'mappings' => [
'newproblems' => [ // type of index
'_source' => [
'enabled' => true
],
'properties' => [
'title' => [
'type' => 'text',
'analyzer' => 'standard'
],
'description' => [
'type' => 'text',
'analyzer' => 'standard'
], 'suggest' => [
'type' => 'completion'
]
]
]
]
]
But I am unable to find which param fields to use to implement suggest function
'index' => 'newproblemindex',
'body' => [
'try' => [
'text' => $request->search_key,
'completion' => [ 'text' => 'suggest' ]
]
],
I am using laravel and taking search_key as request param but I am getting "invalid_type_name_exception" and when I tried to give the type name, it is again giving me some error.
"suggest" => [
"song-suggest" => [
"prefix" => $request->search_key,
"completion" => ["field" => "suggest"]
]
]
I am getting error "suggest is not a valid param ". Please help
and Thanks in advance.

How do you define a Cassandra CollectionMap nested in a UDT with duoshuo's PHP client library?

I have a CollectionSet<UDT> where UDT contains a CollectionMap<int,boolean>. I have not been able to find any documentation or example of how to define this when creating a new Cassandra\Type\CollectionSet for inserting into the table. There is a great example with a CollectionList (found here) which is like this:
// CollectionSet<UDT>, where UDT contains: Int, Text, Boolean,
// CollectionList<Text>, CollectionList<UDT>
new Cassandra\Type\CollectionSet([
[
'id' => 1,
'name' => 'string',
'active' => true,
'friends' => ['string1', 'string2', 'string3'],
'drinks' => [['qty' => 5, 'brand' => 'Pepsi'], ['qty' => 3, 'brand' => 'Coke']]
],[
'id' => 2,
'name' => 'string',
'active' => false,
'friends' => ['string4', 'string5', 'string6'],
'drinks' => []
]
], [
[
'type' => Cassandra\Type\Base::UDT,
'definition' => [
'id' => Cassandra\Type\Base::INT,
'name' => Cassandra\Type\Base::VARCHAR,
'active' => Cassandra\Type\Base::BOOLEAN,
'friends' => [
'type' => Cassandra\Type\Base::COLLECTION_LIST,
'value' => Cassandra\Type\Base::VARCHAR
],
'drinks' => [
'type' => Cassandra\Type\Base::COLLECTION_LIST,
'value' => [
'type' => Cassandra\Type\Base::UDT,
'typeMap' => [
'qty' => Cassandra\Type\Base::INT,
'brand' => Cassandra\Type\Base::VARCHAR
]
]
]
]
]
]);
I've tried using the above example with several variations to accommodate the CollectionMap but nothing is working. My last attempt was this
new Cassandra\Type\CollectionSet($udt_array, [[
'type'=>Cassandra\Type\Base::UDT,
'definition' => [
'map_name' => [
'type' => Cassandra\Type\Base::COLLECTION_MAP,
'value' => [
Cassandra\Type\Base::INT,
Cassandra\Type\Base::BOOLEAN
]
]
]
]])
which gives the error Caught exception: Since v0.7, collection types should have \"definition\" directive. I've also tried using 'definition' instead of 'value'. I'm running out of ideas, any help would be greatly appreciated.
Use "definition" instead of "value". I tried this before but apparently I was doing something else wrong because this worked.
new Cassandra\Type\CollectionSet($udt_array, [[
'type'=>Cassandra\Type\Base::UDT,
'definition' => [
'map_name' => [
'type' => Cassandra\Type\Base::COLLECTION_MAP,
'definition' => [
Cassandra\Type\Base::INT,
Cassandra\Type\Base::BOOLEAN
]
]
]
]])

How do i highlight my result in elasticsearch?

I am not able to highlight my result, which part of my query is wrong?
PHPClient for elasticsearch throws exception on execution.
$query = [
"query" => [
"filtered" => [
"query" => [
"bool" => [
"should" => [
[
'query_string' => [
'fields' => [
'Title.title^4',
'Title.ngrams_front^2',
'Title.ngrams_back'
],
'defaultOperator' => 'or',
'query' => $paramsObj->q
]
],
[
'query_string' => [
'auto_generate_phrase_queries' => 0,
'enable_position_increments' => false,
'fields' => [
'Title.title',
'Address',
'keys'
],
'query' => $paramsObj->q,
'use_dis_max' => false,
'boost' => 2
]
],
[
'fuzzy' => [
'Title.title' => [
'value' => $paramsObj->q,
'boost' => 1,
'min_similarity' => 0.5,
'max_expansions' => 20,
'prefix_length' => 0
]
]
]
]
]
],
"filter" => $filters
]
],
"highlight" => [
"fields" => [
'Title.title' => [ "pre_tags" => "<em>", "post_tags" => "</em>" ]
]
]
];
First i tried highlighting at filtered level, then i googled and found out i need to do at query level at top of filtered level, so i did but still it throws exception.
Fatal error: Uncaught exception 'Guzzle\Http\Exception\ClientErrorResponseException'
If at all anyone can help, kindly help.
Try something like this:
$query = array(
'query' => array(
'bool' => array(
'should' => array(
'fuzzy' => array(
'name' => array(
'value' => $serachstring,
'boost' => 1,
'min_similarity' => 0.5,
'max_expansions' => 20,
'prefix_length' => 0
),
),
// ...
)
),
),
'highlight' => array(
"pre_tags" => "<em>",
"post_tags" => "</em>",
'fields' => array(
'name' => (object) array()
)
),
);

Categories