Here's my query
query Q_BUY_SELL{
description: "Select filtered Orders "
statement:
SELECT namespace.Order
WHERE ((((orderType != _$filterType AND orderStatus == _$filterStatus) AND bidTokenPrice == _$bidTokenPrice) AND orderer != _$orderer) AND property == _$property )
}
And here's how i'm using it
return query('Q_BUY_SELL', {
filterStatus: 'PENDING',
filterType: 'SELL',
bidTokenPrice: 10,
orderer:'resource:com.contrachain.User#o1',
property:'resource:com.contrachain.Property#p2'
})
.then(function (assets) {
console.log(assets);
// Some alterations to assets
Here's the only Asset in my db which i wasn't expecting in result because of the 'orderer' field. (See orderer != _$orderer in query)
{
"$class": "com.contrachain.Order",
"orderId": "Thu Feb 22 2018 15:57:05 GMT+0530 (IST)-30",
"orderType": "BUY",
"orderStatus": "PENDING",
"bidTokenPrice": 10,
"tokenQuantity": 30,
"orderTime": "2018-02-22T10:27:05.089Z",
"orderer": "resource:com.contrachain.User#o1",
"property": "resource:com.contrachain.Property#p2"
}
But it's still there in the response in console.
TLDR; I have 5 conditions(1,2,3,4,5) in the query Q_BUY_SELL out of which (1,5) are working fine but the 2nd, 3rd and 4th conditions are not being applied to the results.
I feel silly posting this question as the problem seems trivial, but i've been stuck with this for a while now and need some external perspective to identify what i'm missing here.
**UPDATE: Relevant part of the Models **
asset Property identified by propertyId {
o String propertyId
--> User owner
}
asset Order identified by orderId {
o String orderId
o OrderType orderType
o OrderStatus orderStatus default = 'PENDING'
o Double bidTokenPrice
o Double tokenQuantity
o DateTime orderTime
--> User orderer
--> Property property
}
abstract participant Account identified by emailId {
o String emailId
o String name default = ''
o DateTime joiningDate
o Boolean isActive default=false
}
participant User extends Account {
o Double balanceINR default=0.0
}
transaction PlaceOrder {
o OrderType orderType
o Double bidTokenPrice
o Double tokenQuantity
o DateTime orderTime
--> User orderer
--> Property property
}
enum OrderType {
o BUY
o SELL
}
enum OrderStatus {
o PENDING
o SUCCESSFUL
o VOID
}
its difficult to replicate without the model. But I suggest to 'pare' it back to 4 criteria in your query to begin with (so - remove the property resource comparison for example) and see if it does/doesn't return the orderer (as you wrote). In any case - I would ALSO create a second record so the query does return 'something' that IS EXPECTED to be a match (and hopefully omits the record that shouldn't match), for testing - just so you can see that the query returns an orderer matching your criteria etc etc - bur first try see if the query works with 4 criteria including orderer check. What I'm suggesting is to see if there's a breakage in the aggregation of criteria (or not).
an example of building queries in a transaction and parsing is shown here FYI -> https://github.com/hyperledger/composer/blob/master/packages/composer-tests-functional/systest/data/transactions.queries.js
just to say I don't see the problems you're seeing for multiple criteria - I've tried 5 and six, mixing up - all worked fine?. Perhaps you can give feedback.
I went and tested some queries as follows with parameterised query with criteria (essentially the same as you did, for a different business network) - in online playground (So I create the queries.qry file then call the query in my TP function ( FYI that and the sample function code are at the bottom):
Numbers(below) represent left-side 'fields' in query definition (these fields are shown in the query below (ordinal, left to right)) - my 'data' is at the bottom - essentially I always (except for one test) 'miss out' on a record with tradingSymbol == "1"
query myQryTest {
description: "Select all commodities"
statement:
SELECT org.acme.trading.Commodity
WHERE (tradingSymbol != _$tradingSymbol AND (description == _$description OR description == _$description) AND mainExchange == _$mainExchange AND quantity == _$quantity AND owner == _$owner )
}
}
RESULTS:
(1 AND 2 AND 3 AND 4 AND 5) - ie all with '==' comparisons.(so 1st criteria would changed to be tradingSymbol == "1" above -just for this one test only). All worked fine. 1 record.
(!= 1 AND 2 AND 3 AND 4 AND = 5) - so one negation in criteria. Worked fine. Got the results I wanted (2 records - see my sample data below)
(!=1 AND (2 = "2" OR 2 == "3") AND 3 AND 4 AND 5) - 6 criteria, as shown above - one in parentheses (where field match is an OR). Worked fine. Got the right results. I changed the description in record 3 to be "4" and I get one record,
{
"$class": "org.acme.trading.Commodity",
"tradingSymbol": "1",
"description": "2",
"mainExchange": "3",
"quantity": 4,
"owner": "resource:org.acme.trading.Trader#1"
}
{
"$class": "org.acme.trading.Commodity",
"tradingSymbol": "2",
"description": "2",
"mainExchange": "3",
"quantity": 4,
"owner": "resource:org.acme.trading.Trader#1"
}
{
"$class": "org.acme.trading.Commodity",
"tradingSymbol": "3",
"description": "2",
"mainExchange": "3",
"quantity": 4,
"owner": "resource:org.acme.trading.Trader#1"
}
you can try this out for yourself - you can try this out with the trade-network from online playground https://composer-playground.mybluemix.net/ (deploy trade-network sample) and create a Trader trader 1 record in the Participant.
/**
* Try access elements of an array of B assets
* #param {org.acme.trading.Qry} qry - function to access A elements (and B assets)
* #transaction
*/
function myQryfunc(qry) {
return query('myQryTest', {
"tradingSymbol": "1",
"description": "2",
"mainExchange": "3",
"quantity": 4,
"owner": "resource:org.acme.trading.Trader#1"
})
.then(function (results) {
var promises = [];
for (var n = 0; n < results.length; n++) {
var asset = results[n];
console.log('Query result is ' + (n+1) + ', object is ' + asset);
console.log('Asset identifier is ' + asset.getIdentifier());
}
});
}
Model definition for my Qry transaction (to call it in playground) is
transaction Qry {
--> Commodity commodity
}
Related
I currently have three collections that need to be routed into one endpoint. I want to get the Course collection sort it, then from that course, I have to use nested subqueries to fetch a random review(there could be multiple tied to the same course) and also get the related user.
User{
name:
_id:User/4638
key: ...}
Review{
_from: User/4638
_to: Course/489
date: ....}
Course{
_id: Course/489
title: ...}
The issue I'm having is fetching the user based on the review. I've tried MERGE, but that seems to limit the query to one use when there should be multiple. Below is the current output using LET.
"course": {
"_key": "789",
"_id": "Courses/789",
"_rev": "_ebjuy62---",
"courseTitle": "Pandas Essential Training",
"mostRecentCost": 15.99,
"hours": 20,
"averageRating": 5
},
"review": [
{
"_key": "543729",
"_id": "Reviews/543729",
"_from": "Users/PGBJ38",
"_to": "Courses/789",
"_rev": "_ebOrt9u---",
"rating": 2
}
],
"user": []
},
Here is the current LET subquery method I'm using. I was wondering if there was anyway to pass or maybe nest the subqueries so that user can read review. Currently I try to pass the LET var but that isn't read in the output since a blank array is shown.
FOR c IN Courses
SORT c.averageRating DESC
LIMIT 3
LET rev = (FOR r IN Reviews
FILTER c._id == r._to
SORT RAND()
LIMIT 1
RETURN r)
LET use = (FOR u IN Users
FILTER rev._from == u._id
RETURN u)
RETURN {course: c, review: rev, user: use}`
The result of the first LET query, rev, is an array with one element. You can re-write the complete query two ways:
Set rev to the first element of the LET query result:
FOR c IN Courses
SORT c.averageRating DESC
LIMIT 3
LET rev = (FOR r IN Reviews
FILTER c._id == r._to
SORT RAND()
LIMIT 1
RETURN r)[0]
LET use = (FOR u IN Users
FILTER rev._from == u._id
RETURN u)
RETURN {course: c, review: rev, user: use}
I use this variant in my own projects.
Access the first elememt og rev in the second LET query:
FOR c IN Courses
SORT c.averageRating DESC
LIMIT 3
LET rev = (FOR r IN Reviews
FILTER c._id == r._to
SORT RAND()
LIMIT 1
RETURN r)
LET use = (FOR u IN Users
FILTER rev[0]._from == u._id
RETURN u)
RETURN {course: c, review: rev, user: use}
This is untested, the syntax might need slight changes. And you have to look at cases where there aren't any reviews - I can't say how this behaves in that case from the top of my head.
How can CosmosDB Query the values of the properties within a dynamic JSON?
The app allows storing a JSON as a set of custom properties for an object. They are serialized and stored in CosmosDb. For example, here are two entries:
{
"id": "ade9f2d6-fff6-4993-8473-a2af40f071f4",
...
"Properties": {
"fn": "Ernest",
"ln": "Hemingway",
"a_book": "The Old Man and the Sea"
},
...
}
and
{
"id": "23cb9d4c-da56-40ec-9fbe-7f5178a92a4f",
...
"Properties": {
"First Name": "Salvador",
"Last Name": "Dali",
"Period": "Surrealism"
},
...
}
How can the query be structured so that it searches in the values of Properties?
I’m looking for something that doesn’t involve the name of the
sub-propety, like SELECT * FROM c WHERE
some_function_here(c.Properties, ‘Ernest’)
Maybe I get your idea that you want to filter the documents by the value of the Properties, not the name. If so , you could use UDF in cosmos db.
sample udf:
function query(Properties,filedValue){
for(var k in Properties){
if(Properties[k] == filedValue)
return true;
}
return false;
}
sample query:
SELECT c.id FROM c where udf.query(c.Properties,'Ernest')
output:
Just summary here, Ovi's udf function like:
function QueryProperties (Properties, filedValue) {
for (var k in Properties) {
if (Properties[k] && Properties[k].toString().toUpperCase().includes(filedValue.toString().toUpperCase()))
return true;
return false;
}
Both of the following syntax's will work.
SELECT * FROM c where c.Properties["First Name"] = 'Salvador'
SELECT * FROM c where c.Properties.fn = 'Ernest'
Is the below query supported in Azure DocumentDB? It returns no documents.
Variables values at runtime:
1. collectionLink = "<link for my collection>"
2. feedOptions = new FeedOptions { MaxItemCount = 2 }
3. name = "chris"
client.CreateDocumentQuery<T>(collectionLink, feedOptions).Where(m => (m.Status == "Foo" && (m.Black.Name == null || m.Black.Name != name) && (m.White.Name == null || m.White.Name != name)));
I have tested with simpler queries, such as the below, which both return results I expect.
client.CreateDocumentQuery<T>(collectionLink, feedOptions).Where(m => m.Status == "Foo");
client.CreateDocumentQuery<T>(collectionLink, feedOptions).Where(m => m.Status == "Foo").Where(m => m.Size == 19);
Lastly, I've ensured there are documents which meet the problematic query's filter criteria:
{
"id": "1992db52-c9c6-4496-aaaa-f8cb83a8c6b0",
"status": "Foo",
"size": 19,
"black": {
"name": "charlie"
},
"white": {},
}
Thanks.
Turns out the "m.White.Name == null || m.White.Name != name" check is problematic because the Name field does not exist on the document in the DB.
When the document is edited to the following, the query returns it. Notice the explicit null value for Name field.
{
"id": "1992db52-c9c6-4496-aaaa-f8cb83a8c6b0",
"status": "Foo",
"size": 19,
"black": {
"name": "charlie"
},
"white": {
"name": null
},
}
The query can be written to handle missing properties using DocumentDB UDFs as follows. DocumentDB uses JavaScript's semantics, and an explicit null is different from a missing property ("undefined") in JavaScript. To check for explicit null is simple (== null like your query), but to query for a field that may or may not exist in DocumentDB, you must first create a UDF for ISDEFINED:
function ISDEFINED(doc, prop) {
return doc[prop] !== undefined;
}
And then use it in a DocumentDB query like:
client.CreateDocumentQuery<T>(
collectionLink,
"SELECT * FROM docs m WHERE m.Status == "Foo" AND (ISDEFINED(m.white, "name") OR m.white.name != name)");
Hope this helps. Note that since != and UDFs both require scans, it's a good idea for performance/scale to always use them only within queries that have other filters.
I have a mongoDB collection and an item in the collection looks like below:
{
"_id": "52f535b56268a019bd11cc2a",
"description": "Some description",
"entry_date": "2014-02-07T19:36:21.430Z",
"last_update": "2014-02-07T19:36:21.430Z",
"r": "samestring",
"s": "samestring"
}
Dates are ISODate objects.
This query returns items correctly
db.myCollection.find({$where : "this.entry_date < this.last_update"});
Below query returns nothing (I expect that it returns the above item):
db.myCollection.find({$where : "this.entry_date == this.last_update"});
And this query returns all items (I expected again it returns the above item):
db.myCollection.find({$where :"this.r == this.s"});
What am I doing wrong?
Thanks!!
---EDIT----
So I tried to test with a small data like below:
> db.myCollection.find({},{ _id: 0, start_date: 1, end_date: 1});
{
"start_date" : ISODate("2014-02-07T19:36:21.430Z"),
"end_date" : ISODate("2014-02-07T19:36:21.430Z")
}
{
"start_date" : ISODate("2014-02-07T19:36:21.430Z"),
"end_date" : ISODate("2014-02-07T22:39:02.114Z")
}
It didn't work for Date as you can see:
> db.myCollection.find(
{$where: "Date(this.start_date) == Date(this.end_date)"},
{ _id: 0, start_date: 1, end_date: 1 }
);
{
"start_date" : ISODate("2014-02-07T19:36:21.430Z"),
"end_date" : ISODate("2014-02-07T19:36:21.430Z")
}
{
"start_date" : ISODate("2014-02-07T19:36:21.430Z"),
"end_date" : ISODate("2014-02-07T22:39:02.114Z")
}
Works for string values:
> db.myCollection.find({$where: "this.title == this.description"},{ _id: 0, title: 1 });
{ "title" : "samedescription" }
You have to be really careful when comparing dates in JavaScript - use valueOf() or getTime():
> db.myCollection.find({$where: "this.start_date.getTime() == this.end_date.getTime()"});
{ "_id" : ObjectId("52f5b7e316d795f0a076fbdf"), "description" : "a description", "title" : "a title", "start_date" : ISODate("2014-02-07T19:36:21.430Z"), "end_date" : ISODate("2014-02-07T19:36:21.430Z") }
Here is why your other queries didn't work.
db.myCollection.find({$where: "Date(this.start_date) == Date(this.end_date)"});
This didn't work because you didn't use new when initializing the dates. This generally has hilarious results with all dates being equal to each other:
> Date(2014,2,8) == Date(1941,12,7)
true
> Date(2000,1,1) == Date(1995,2,8)
true
But even if you properly instantiate the date using new, you still get hilarious results when comparing dates using ==, as demonstrated in this gist:
var dateValue = 504001800000; // Saturday, December 21st, 1985 at 3:30am EST
var date1 = new Date(dateValue);
var date2 = new Date(dateValue);
console.log(date1 == date2); // false (different instances)
console.log(date1 === date2); // false (different instances)
console.log(date1 > date2); // false (date1 is not later than date2)
console.log(date1 < date2); // false (date1 is not earlier than date2)
console.log(date1 >= date2); // true (rofl)
console.log(date1 <= date2); // true (ahahahaha)
As for your other query:
It didn't work if I consider them as strings either:
db.myCollection.find({$where: "this.start_date == this.end_date"});
You're not actually comparing them as strings, you're comparing ISODate objects, which is how they're stored. For ISODate, similar to Date, the == operator will return false unless you're comparing the exact same instance. Using getTime should work, however, which is what I did up above.
Hopefully, none of this makes any sense whatsoever, because if it does, I'm worried about your sanity.
--EDITED--
You were looking for the $where operator. Your query must be in a valid JSON notation and outside of this operator there is no other access to that kind of raw JavaScript notation.
Dates
finally:
{$where: "this.start_date.valueOf() == this.end_date.valueOf()"}
Also be careful not to run into reserved words and other traps.
Be very careful to read the documentation on this operator and make sure you absolutely need it. It will slow things down considerably as your find will scan the entire collection. It cannot use an index.
Let's say I have two types of docs with one referencing the other, e.g. "orders" and "order_replies" the later one having a field "ref" which contains an ID of an order document.
For my validate_doc_update function I want a user only to be able to change an order_reply document if he is the author of the original order.
How can I get the "author" field from the order with the ID newDoc.ref within the function?
Here's a part of my validation function so far:
if(userCtx.roles.indexOf('employee') >= 0) {
// [...] other doc types
if (newDoc.type == 'order_reply') {
//newDoc.ref.author is not the proper approach
require((userCtx.name == newDoc.ref.author), "this is not your order reply!");
}
}
Put the author's ID in the order's ID. For example:
The order: { "_id": "order/jack/1", ... }
An order's reply: { "ref": "order/jack/1", ... }
So you can check with:
if (userCtx.roles.indexOf('employee') !== -1) {
if (newDoc.type === 'order_reply') {
require(userCtx.name === newDoc.ref.split('/', 3)[1], "this is not your order reply!");
}
}
If you have multiple authors, use "order/author1/author2/.../authorN/ordernum" as _id of the order and check with:
var parts = newDoc.ref.split('/'),
authors = parts.slice(1, -1);
require(authors.indexOf(userCtx.name) !== -1, "this is not your order reply!");
UPDATE: Due to bug COUCHDB-1229, use of "/" in doc._id may cause problems. Depending on your use-case, it may be better to use another separator, e.g. ":".