Search code examples
phpelasticsearchtags

Can i add tags to a "deeper" key in an Elastic Search document?


i have products with tags, and tags are inside tagtypes.

this is a sample document that i added to the index

 {
        "_index" : "products",
        "_type" : "_doc",
        "_id" : "1219",
        "_score" : 1.0,
        "_source" : {
          "id" : "1219",
          "product_no" : "26426492261",
          "merchant_id" : 11,
          "name" : "Apple »Magic Keyboard für das 12,9\" iPad Pro (4. Generation)« iPad-Tastatur",
          "category" : "Technik>Multimedia>Zubehör>Tastatur>iPad Tastatur",
          "deep_link" : "https://foo",
          "short_description" : null,
          "long_description" : "Apple:",
          "brand" : "Apple",
          "merchant_image_url" : "http://something",
          "tagtypes" : [
            [
              {
                "Memory" : [ ]
              }
            ]
          ]
        }
      },

That tagtype "Memory" is dynamically created while indexing the products.

I tried to add tags to that key

 //attach tags also to ES
                        $params = [
                            'index' => 'products',
                            'id' => $product['_id'],
                            'body' => [
                                'script' => [
                                    'source' => 'if (!ctx._source.tagtypes.'.$tagType->name.'.contains(params.tag)) { ctx._source.tagtypes.'.$tagType->name.'.add(params.tag) }',
                                    'lang' => 'painless',
                                    'params' => [
                                        'tag' => $tag->value
                                    ]
                                ]
                            ]

                        ];

But i receive an error like

{"error":{"root_cause":[{"type":"illegal_argument_exception","reason":"failed to execute script"}],"type":"illegal_argument_exception","reason":"failed to execute script","caused_by":{"type":"script_exception","reason":"runtime error","script_stack":["if (!ctx._source.tagtypes[\"Memory\"].contains(params.tag)) { "," ^---- HERE"],"script":"if (!ctx._source.tagtypes[\"Memory\"].contains(params.tag)) { ctx._source.tagtypes[\"Memory\"].add(params.tag) }","lang":"painless","position":{"offset":16,"start":0,"end":60},"caused_by":{"type":"wrong_method_type_exception","reason":"cannot convert MethodHandle(List,int)int to (Object,String)String"}}},"status":400}

Could anyone help me with that. I couldnt find any documentation about it, as the examples are often too basic.

Is it generally possible to save to "deeper keys" like this ? Or can i just create "tags" as simple list (without any depth)

Thanks in advance Adrian!


Solution

  • Your field tagtypes is an array of arrays of objects which themselves contain one-key arrays.

    When you're dealing with such "deep" structures, you'll need some form of iteration to update them.

    For loops are a good place start but they often lead to java.util.ConcurrentModificationExceptions. So it's easier to work with temporary copies of data and then replace the corresponding _source attribute when done with the iterations:

    {
      "query": {
        "match_all": {}
      },
      "script": {
        "source": """
          if (ctx._source.tagtypes == null) { return; }
          
          def originalTagtypes = ctx._source.tagtypes;
          def newTagtypes = [];
          
          for (outerGroup in originalTagtypes) {
            // keep what we've got
            newTagtypes.addAll(outerGroup);
            
            // group already present?
            def atLeastOneGroupContainsTag = outerGroup.stream().anyMatch(tagGroup -> tagGroup.containsKey(params.tag));
            
            // if not, add it as a hashmap of one single empty list
            if (!atLeastOneGroupContainsTag) {
              Map m = new HashMap();
              m.put(params.tag, []);
              newTagtypes.add(m);
            } 
          }
          
          ctx._source.tagtypes = [newTagtypes];
        """,
        "lang": "painless",
        "params": {
          "tag": "CPU"
        }
      }
    }
    

    which'll end up updating the tagtypes like so:

    {
     ...
      "tagtypes" : [
        [
          {
            "Memory" : [ ]
          },
          {
            "CPU" : [ ]        <---
          }
        ]
      ],
      ...
    }
    

    You're right when you say that the documentation examples are too basic. Shameless plug: I recently published a handbook that aims to address exactly that. You'll find lots non-trivial scripting examples to gain a better understanding of the Painless scripting language.