I'm using MarkLogic v8.
I am trying to apply a container constraint on a structured query to return only documents with value x in element c (nested within elements a and b).
queryBuilder.containerConstraint() takes a parameter for an option name and a StructuredQueryDefinition. My option looks like this:
<options xmlns='http://marklogic.com/appservices/search'>
<constraint name='language'>
<element name=\"name\" ns=\"\"/>
</constraint>
</options>
"name" is the name of the innermost element (c) containing the value I want to reference against. Is this how the option should be constructed, or should 'name' instead be the name of the outermost element?
How should the StructuredQueryDefinition (that is accepted as a parameter by containerConstraint()) be constructed? Should I be writing raw XML, or are there contruction methods to be passed in?
Is there a better way to do this? I already have a working Term search, I just need to be able to filter by a property set inside the document.
I think I found an answer:
Option was as follows:
<search:options
xmlns:search='http://marklogic.com/appservices/search'>
<search:constraint name='language'>
<search:word>
<search:element name='name' ns=''/>
</search:word>
</search:constraint>
</search:options>
Then called the option in a Word Constraint:
queryBuilder.wordConstraint("language", MY_LANGUAGE)
This appears to do what I wanted it to.
Related
I would like to have a query that looks at the name of its parent and then will navigate to the folder with the same sitename underneath the content folder and show the items underneath it in the multilist.
Basically the structure in the content tree looks as followed:
sitecore
content
Sitename1
(items that needs to be showed in the multilist)
Sitename2
(items that needs to be showed in the multilist)
medialibrary
Sitename1
PDF1 (with multiList with search)
Sitename2
PDF2 (with multiList with search)
Trouble starts when I would like to start comparing the "name" of the relative parent to the "name" of the child of the absolute path. In Xpath it would probably go something like it is described within: Compare attribute values using Xpath
In this case I had the following query up until now:
/sitecore/content/*[ancestor-or-self::*[##templateid='{Template Id of Sitename}']=##name and ##templateid='{Template Id of Sitename}']
This query returns Sitename1 and Sitename2.
Funny thing is that if I replace "ancestor-or-se.." or "##name" part bij "Sitename1", like so:
/sitecore/content/*['Sitename1'=##name and ##templateid='{Template Id of Sitename}']
..and..
/sitecore/content/*[./ancestor-or-self::*[##templateid='{Template Id of Sitename}']='DSW' and ##templateid='{Template Id of Sitename}']
I get the wanted result: Sitename1.
Btw I'm using the build-in xpath builder for now, before it paste the query into the "multilist with search."
Any help would be appreciated.
Edit:
I think I found out that when I start the relative query (the "./ancestor::..." part) it actually is relative to the item where I ended up with the absolute query. So I should have the following query:
./ancestor-or-self::*[##templateid='{Template Id of Sitename}' and ##name=ancestor::*[##templateid='{Template Id of root item aka "sitecore"}']//*[##templateid={Template Id of Sitename}]]
Here I get the error "Object must be of type String," which is probably because of the following part of the previous query:
##name=ancestor::*[##templateid='{Template Id of root item aka "sitecore"}']//*[##templateid={Template Id of Sitename}]
The right part of this doesn't solve to a string. So the question remains, how to extract pure the string out of a sitecore item using sitecore xpath in order to be able to make a comparison.
I figured out that Sitecore doesn't support subqueries at least for fast queries, I think same applies to normal ones (see also: "Subqueries are not supported" in here ). Which now lead me to using simple code where I perform two queries. A very simple way to do it is to inherit from IDatasource (in the sitecore.buckets.dll), you will need to write "code:{fullpath to class}, assemblyname.dll" (See also: here)
I would like to 'upsert' a document in DynamoDB. That is, I would like to specify a key, and a set of field/value pairs. If no document exists with that key, I want one created with that key and the key/value pairs I specified. If a document exists with that key, I want the fields I specified to be set to the values specified (if those fields did not exist before, then they should be added). Any other, unspecified fields on the existing document should be left alone.
It seems I can do this pretty well with the UpdateItem call, when the field/value pairs I am setting are all top-level fields. If I have nested structures, UpdateItem will work to set the nested fields, as long as the structure exists. In other words, if my existing document has "foo": {}, then I can set "foo.bar": 42 successfully.
However, I don't seem to be able to set "foo.bar": 42 if there is no foo object already (like in the case where there is no document with the specified field at all, and my 'upsert' is behaving as an 'insert'.
I found a discussion on the AWS forums from a few years ago which seems to imply that what I want to do cannot be done, but I'm hoping this has changed recently, or maybe someone knows of a way to do it?
UpdateItem behaves like an "upsert" operation: The item is updated if it exists in the table, but if not, a new item is added (inserted).
http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/SQLtoNoSQL.UpdateData.html
That ("foo.bar": 42) can be achieved using the below query:
table.update_item(Key = {'Id' : id},
UpdateExpression = 'SET foo = :value1',
ExpressionAttributeValues = {':value1': {'bar' : 42}}
)
Hope this helps :)
I found this UpdateItem limitation (top level vs nested attributes) frustrating as well. Eventually I came across this answer and was able to work around the problem: https://stackoverflow.com/a/43136029/431296
It requires two UpdateItem calls (possibly more depending on level of nesting?). I only needed a single level, so this is how I did it:
Update the item using an attribute_exists condition to create the top level attribute as an empty map if it doesn't already exist. This will work if the entire item is missing or if it exists and has other pre-existing attributes you don't want to lose.
Then do the 2nd level update item to update the nested value. As long as the parent exists (ex: an empty map in my case) it works great.
I got the impression you weren't using python, but here's the python code to accomplish the upsert of a nested attribute in an item like this:
{
"partition_key": "key",
"top_level_attribute": {
"nested_attribute": "value"
}
}
python boto3 code:
def upsert_nested_item(self, partition_key, top_level_attribute_name, nested_attribute_name, nested_item_value):
try:
self.table.update_item(
Key={'partition_key': partition_key},
ExpressionAttributeNames={f'#{top_level_attribute_name}': top_level_attribute_name},
ExpressionAttributeValues={':empty': {}},
ConditionExpression=f'attribute_not_exists(#{top_level_attribute_name})',
UpdateExpression=f'SET #{top_level_attribute_name} = :empty',
)
except self.DYNAMODB.meta.client.exceptions.ConditionalCheckFailedException:
pass
self.table.update_item(
Key={'partition_key': partition_key},
ExpressionAttributeNames={
f'#{top_level_attribute_name}': top_level_attribute_name,
f'#{nested_attribute_name}': nested_attribute_name
},
ExpressionAttributeValues={f':{top_level_attribute_name}': nested_item_value},
UpdateExpression=f'SET #{top_level_attribute_name}.#{nested_attribute_name} = :{top_level_attribute_name}',
)
I wanted to fetch the document which have the particular element attribute value.
So, I tried the cts:element-attribute-value-query but I didn't get any result. But the same element attribute value, I am able to get using cts:element-attribute-range-query.
Here the sample snippet used.
let $s-query := cts:element-attribute-range-query(xs:QName("tit:title"),xs:QName("name"),"=",
"SampleTitle",
("collation=http://marklogic.com/collation/codepoint"))
let $s-query := cts:element-attribute-value-query(xs:QName("tit:title"),xs:QName("name"),
"SampleTitle",
())
return cts:search(fn:doc(),($s-query))
The problem with range-query is it needs the range index. I have hundreds of DB's in multiple hosts. I need to create range indexes on each DB.
What could be the problem with attribute-value-query?
I found the issue with a couple of research.
Actually the result document is a french language document. It has the structure as follows. This is a sample.
<doc xml:lang="fr:CA" xmlns:tit="title">
<tit:title name="SampleTitle"/>
</doc>
The cts:element-attribute-value-query is a language dependent query. To get the french language results, then language needs to be mentioned in the option as follows.
cts:element-attribute-value-query(xs:QName("tit:title"),xs:QName("name"), "SampleTitle",("lang=fr"))
But cts:element-attribute-range-query don't require the language option.
Thanks for the effort.
I have some initial conditions that are specified by functions of (x,y,z).
I would like to programmatically define a field whose values are a function of (x,y,z). Can this be done as part of field construction, rather than looping over cells/faces and setting each value individually?
Further, can I set the internal field and boundary values in a straightforward manner?
You might want to use #codeStream directive to enter the generating code directly in the field defining dictionary, see official documentation.
Also you might want to look at extensions such as groovyBC, funkySetFields or swak4Foam.
I have a lot of objects with unique IDs. Every object can have several labels associated to it, like this:
123: ['a', 'hello']
456: ['dsajdaskldjs']
789: (no labels associated yet)
I'm not planning to store all objects in DynamoDB, only these sets of labels. So it would make sense to add labels like that:
find a record with (id = needed_id)
if there is one, and it has a set named label_set, add a label to this set
if there is no record with such id, or the existing record doesn't have an attribute named label_set, create a record and an attribute, and initialize the attribute with a set consisting of the label
if I used sets of numbers, I could use just ADD operation of UPDATE command. This command does exactly what I described. However, this does not work with sets of strings:
If no item matches the specified primary key:
ADD— Creates an item with supplied primary key and number (or set of numbers) for the attribute value. Not valid for a string type.
so I have to use a PUT operation with Expected set to {"label_set":{"Exists":false}}, followed (in case it fails) by an ADD operation. These are two operations, and it kinda sucks (since you pay per operation, the costs of this will be 2 times more than they could be).
This limitations seems really weird to me. Why are something what works with numbers sets would not work with string sets? Maybe I'm doing something wrong.
Using many records like (123, 'a'), (123, 'hello') instead of one record per object with a set is not a solutions: I want to get all the values from the set at once, without any scans.
I use string sets from the Java SDK the way you describe all the time and it works for me. Perhaps it has changed? I basically follow the pattern in this doc:
http://docs.amazonwebservices.com/amazondynamodb/latest/developerguide/API_UpdateItem.html
ADD— Only use the add action for numbers or if the target attribute is
a set (including string sets). ADD does not work if the target
attribute is a single string value or a scalar binary value. The
specified value is added to a numeric value (incrementing or
decrementing the existing numeric value) or added as an additional
value in a string set. If a set of values is specified, the values are
added to the existing set. For example if the original set is [1,2]
and supplied value is [3], then after the add operation the set is
[1,2,3], not [4,5]. An error occurs if an Add action is specified for
a set attribute and the attribute type specified does not match the
existing set type.
If you use ADD for an attribute that does not exist, the attribute and
its values are added to the item.
When your set is empty, it means the attribute isn't present. You can still ADD to it. In fact, a pattern that I've found useful is to simply ADD without even checking for the item. If it doesn't exist, it will create a new item using the specified key and create the attribute set with the value(s) I am adding. If the item exists but the attribute doesn't, it creates the attribute set and adds the value(s). If they both exist, it just adds the value(s).
The only piece that caught me up at first was that the value I had to add was a SS (String set) even if it was only one string value. From DynamoDB's perspective, you are always merging sets, even if the existing set is an empty set (missing) or the new set only contains one value.
IMO, from the way you've described your intent, you would be better off not specifying an existing condition at all. You are having to do two steps because you are enforcing two different situations but you are trying to perform the same action in both. So might as well just blindly add the label and let DynamoDB handle the rest.
Maybe you could: (pseudo code)
try:
add_with_update_item(hash_key=42, "label")
except:
element = new Element(hash_key=42, labels=["label"])
element.save()
With this graceful recovery approach, you need 1 call in the general case, 2 otherwise.
You are unable to use sets to do what you want because Dynamo Db doesn't support empty sets. I would suggest just using a string with a custom schema and building the set from that yourself.
To avoid two operations, you can add a "ConditionExpression" to your item.
For example, add this field/value to your item:
"ConditionExpression": "attribute_not_exists(RecordID) and attribute_not_exists(label_set)"
Source documentation.
Edit: I found a really good guide about how to use the conditional statements