couchbase startkey_docid and complex key - couchdb

The call below with the matching result set returns a result that does not start with the doc id a4a6cf44-8a82-494a-a2b9-f6a3ec629f17. As the result set below demonstrates the top 3 keys are identical, but startkey_docid has no effect.
Just a couple questions around this
Will startkey_docid work correctly with a complex key? (This apparently works for couchdb)
If yes to the above, is the source of the issue the use of dateToArray(doc.created) in our view?
Is this a bug?
View:
function (doc, meta)
{
if(meta.type == "json" && doc.type == "POST")
{
emit([doc.category, dateToArray(doc.created), doc.visibility], null);
}
}
Call:
?startkey=["auto",[2013,10,10,23,12,0],"EVERYONE"]&endkey=["auto",[2013,12,11,23,12,0],"EVERYONE"]&startkey_docid=a4a6cf44-8a82-494a-a2b9-f6a3ec629f17
Result:
{
total_rows: 20,
rows: [{
id: "a4a6cf44-8a82",
key: ["auto", [2013, 11, 8, 1, 17, 46], "EVERYONE"],
value: null
}, {
id: "a4a6cf44-8a82-494a-a2b9",
key: ["auto", [2013, 11, 8, 1, 17, 46], "EVERYONE"],
value: null
}, {
id: "a4a6cf44-8a82-494a-a2b9-f6a3ec629f17",
key: ["auto", [2013, 11, 8, 1, 17, 46], "EVERYONE"],
value: null
}, {
id: "41070cfc-a85c-424c-9b87-fce0616c77c1",
key: ["auto", [
2013, 11, 11, 20, 28, 21], "EVERYONE"],
value: null
}, {
id: "bb275e3c-54da-4e85-8cc3-21defff4e278",
key: ["auto", [
2013, 11, 13, 1, 41, 7], "EVERYONE"],
value: null
}]
}
Example data set. post_id is the document id.
{
type: "POST",
post_id: "a4a6cf44-8a82",
visibility: "EVERYONE",
userID: "<user_id>",
title: "Some title 1",
category: "auto",
description: "",
created: "2013-11-07 17:17:46 -0800",
modified: "2013-11-07 17:17:46 -0800"
}, {
type: "POST",
post_id: "a4a6cf44-8a82-494a-a2b9",
visibility: "EVERYONE",
userID: "<user_id>",
title: "Some title 2",
category: "auto",
description: "",
created: "2013-11-07 17:17:46 -0800",
modified: "2013-11-07 17:17:46 -0800"
}, {
type: "POST",
post_id: "a4a6cf44-8a82-494a-a2b9-f6a3ec629f17",
visibility: "EVERYONE",
userID: "<user_id>",
title: "Some title 3",
category: "auto",
description: "",
created: "2013-11-07 17:17:46 -0800",
modified: "2013-11-07 17:17:46 -0800"
}, {
type: "POST",
post_id: "41070cfc-a85c-424c-9b87-fce0616c77c1",
visibility: "EVERYONE",
userID: "<user_id>",
title: "Some title 4",
category: "auto",
description: "",
created: "2013-11-11 12:28:21 -0800",
modified: "2013-11-11 12:28:21 -0800"
}, {
type: "POST",
post_id: "bb275e3c-54da-4e85-8cc3-21defff4e278",
visibility: "EVERYONE",
userID: "<user_id>",
title: "Some title 5",
category: "auto",
description: "",
created: "2013-11-12 17:41:07 -0800",
modified: "2013-11-12 17:41:07 -0800"
}

Okay, this is an old thread, but for reference sake, I'll post my findings and try to explain this functionality the best I can.
Example
Given a database of over 9,000 movie documents, each containing attributes like title, year, rating, genre, etc., define a view MovieIndexView with index name MovieIndex for this database that emits the following complex (but rather simple) key for filtering movies by rating, and sorting by year:
function (doc) {
emit([doc.rating, doc.year], doc);
}
The view can be queried with sorted rows using techniques similar to the way the OP has shown:
- Request -
GET /movies_db/_design/MovieIndex/_view/MovieIndexView?
startkey=["R",{}]
&endkey=["R",0]
&descending=true
&limit=6
- Response -
{
"total_rows": 9411,
"offset": 1,
"rows": [
{
"id": "2802144",
"key": [
"R",
2014
],
"value": {
"_id": "2802144",
"_rev": "1-924e12ba1f1144e3a453bbd8978acc5c",
"title": "Kingsman: The Secret Service",
"year": 2014,
"rating": "R",
"runtime": "129 min",
"genre": [
"Action",
"Adventure",
"Comedy"
],
"director": "Matthew Vaughn"
}
},
<...>
{
"id": "2278388",
"key": [
"R",
2014
],
"value": {
"_id": "2278388",
"_rev": "1-c38b7f5eb43abfd59fb8514277290e46",
"title": "The Grand Budapest Hotel",
"year": 2014,
"rating": "R",
"runtime": "99 min",
"genre": [
"Adventure",
"Comedy",
"Drama"
],
"director": "Wes Anderson"
}
}
]
}
Note we only pulled the first 6 documents by setting the limit variable (I pasted only the first and last relevant items).
Pagination
A first shot at pagination for the next sequence of rows could be straight forward. Simply set the startkey of the next request to the key value of the last item in the previous set, while keeping the value for endkey unchanged:
startkey=["R",2014]
&endkey=["R",0]
&descending=true
&limit=6
Do you see a problem with this approach? This new start key fails to specify which of the multiple movie items with the same value for startkey (movies with the same rating, that came out the same year) the response should start with. While there is nothing syntactically wrong with the request, you will notice that the returned rows will not start with the next sequential item we want, but rather with another arbitrary item in the database with a matching value for key.
This is where startkey_docid comes in handy:
- Request -
GET /movies_db/_design/MovieIndexView/_view/MovieIndex?
startkey=["R",2014]
&endkey=["R",0]
&descending=true
&limit=6
&startkey_docid=2278388
This request will return rows starting with the correct item in the sequence, providing the best means of paginating the rows from the view.
The Takeaway
A startkey_docid must be used together with a relevant startkey for your pagination to work.
I hope this helps anyone who may be busting their heads on this subtle feature.
Sources
CouchDB Official Docs: http://docs.couchdb.org/en/latest/couchapp/views/collation.html
Interesting thread exchange: http://grokbase.com/t/couchdb/user/091defx51x/sort-by-date-and-find-by-key
Similar SO topic: https://stackoverflow.com/questions/10142850/need-a-couchdb-trick-to-sort-by-date-and-filter-by-group#=

You can find more information on how to use startkey_docid properly here: http://blog.couchbase.com/startkeydocid-behaviour

Related

SuiteScript TypeError: Cannot read property "firstname" from undefined

I have a problem that's not making any sense to me. I have created a custom search and I am using the results from that search to addSelectOptions to a select field I have added.
However when trying to simply access a value within a nested object I am getting the error message: TypeError: Cannot read property "firstname" from undefined.
Here is the code:
var sfPlayersSearch = search
.create({
id: 'customsearch_pm_sf_players_search',
title: 'SF Players Search',
type: search.Type.EMPLOYEE,
columns: [
'entityid',
'firstname',
'lastname',
'custentity_pm_ws_sf_player',
],
filters: ['custentity_pm_ws_sf_player', 'is', 'true'],
})
.run()
.getRange(0, 100);
log.debug({ title: 'SF Players', details: sfPlayersSearch });
var player1ProxyField = form.addField({
id: 'custpage_pm_ws_sf_player_1_proxy',
label: 'Player 1 Proxy',
type: ui.FieldType.SELECT,
});
var player2ProxyField = form.addField({
id: 'custpage_pm_ws_sf_player_2_proxy',
label: 'Player 2 Proxy',
type: ui.FieldType.SELECT,
});
for (var i = 0; i < sfPlayersSearch.length; i++) {
log.debug({title: 'Result', details: sfPlayersSearch[i].values.firstname});
player1ProxyField.addSelectOption({ value: sfPlayersSearch[i], text: sfPlayersSearch[i].id });
}
JSON Object:
[
[
{
"recordType": "employee",
"id": "8",
"values": {
"entityid": "Artur X",
"firstname": "Artur",
"lastname": "X",
"custentity_pm_ws_sf_player": true
}
},
{
"recordType": "employee",
"id": "50",
"values": {
"entityid": "Darryl X",
"firstname": "Darryl",
"lastname": "X",
"custentity_pm_ws_sf_player": true
}
},
{
"recordType": "employee",
"id": "1983",
"values": {
"entityid": "Douglas X",
"firstname": "Douglas",
"lastname": "X",
"custentity_pm_ws_sf_player": true
}
},
{
"recordType": "employee",
"id": "86477",
"values": {
"entityid": "Paul X",
"firstname": "Paul",
"lastname": "X",
"custentity_pm_ws_sf_player": true
}
}
]
]
Any help greatly appreciated. I have tried doing .values || {}.firstname and this returns no error, but also no result.
Search.run().getRange() returns an array of Result objects. These are what you are iterating through in your for block. The Result object does not include values but rather includes methods for access the values getValue()) and text (getText()) of each result.
Change
log.debug({title: 'Result', details: sfPlayersSearch[i].values.firstname});.
to
log.debug({title: 'Result', details: sfPlayersSearch[i].getValue('firstname')});
While #krypton 's answer is correct for what you are doing you may find it useful to use Netsuite's JSONified version of search objects like what you see in your log.
If you were writing this as a library file where you might want to work in a Map/Reduce you can normalize the search result like
.getRange(0, 100)
.map(function(sr){
return JSON.parse(JSON.stringify(sr));
});
Now your original code for values.firstname would work.
Fun fact. If you are using SS 2.1 you can use let', const, and arrow functions:
const players = search.create()...
.getRange(0, 100)
.map(sr=>(JSON.parse(JSON.stringify(sr)));

MongoDB - findOne with nested subdocuments and projection

I am currently using the code below in node.js to find and return data on various nesting levels from a mongo database. I'd like to add another layer of nesting (as mentioned in #3).
Collection:
[
{
"title": "Category A",
"link": "a",
"items": [
{
"title": "Item C",
"link": "a-c",
"series": [
{
"title": "Item C X",
"link": "a-c-x"
},
{
"title": "Item C Y",
"link": "a-c-y"
},
]
},
{
"title": "Item D",
"link": "a-d"
}
]
},
{
"title": "Category B",
"link": "b"
}
]
The query:
const doc = await ... .findOne(
{
$or: [
{ link: id },
{ "items.link": id },
{ "items.series.link": id }
],
},
{
projection: {
_id: 0,
title: 1,
link: 1,
items: { $elemMatch: { link: id } },
},
}
);
Intended results:
(works) if link of the document is matched,
(works) there should only be an object with the title and link returned
e.g.
value of id variable: "a"
expected query result: { title: "Category A", link: "a"}
(works) if items.link of subdocument is matched,
(works) it should be the same as above + an additional element in the items array returned.
e.g.
value of id variable: "a-c"
expected query result: { title: "Category A", link: "a", items: [{ title: "Item C", link: "a-c" }]}
(works) if items.series.link of sub-subdocument is matched
(struggling with this) it should return the same as in 2. + an additional element inside the matched items.series
e.g.
value of id variable: "a-c-y"
expected query result: { title: "Category A", link: "a", items: [{ title: "Item C", link: "a-c", series: [{ title: "Item C Y", link: "a-c-y" }]}]}
current query result: The whole Category A document with all sub-documents
Questions:
a.) How do I modify the projection to return the expected output in #3 as well?
b.) Is the approach above sound in terms of reading speed from a denormalized structure? I figured there'd probably need to be indexes on link, items.link and items.series.link as they are all completely unique in the document, but maybe there is a way to achieve the above goal with a completely different approach?
Ended up with going half-way via mongodb and get the full item for both - when the item link is matched and the series link is matched:
projection: {
_id: 0,
title: 1,
link: 1,
items: { $elemMatch: { $or: [
{ link: id },
{"series.link": id }
]}},
}
After that javascript filters the series array to see if the series is matched:
doc?.items?.[0]?.series?.find(item => item.link === id)
if the js is truthy (returns an object) we matched a series, if there is a doc, but the js is falsy we matched an item result.
Although not a full mongodb solution and there is definitely room for improvement the above seems to achieve the end goal to be able to distinguish between category, item and series results.

No value passed to the slots but Original Value is passed in the bot

I am using Amazon lex with AWS lambda as a validation Codehook. When I was trying to pass a value with a response card dynamically generated, it is showing null value in the AppointmentTime Slot but the original value is showing value in slotDetails.
Here is the request sent through lex:
{
"messageVersion": "1.0",
"invocationSource": "DialogCodeHook",
"userId": "prwna44b91sbr4w7d2pwwva59anaqzqx",
"sessionAttributes": {
"store_id": "26",
"address": "Quark Atrium, A‐45, Phase VIII Extension,Industrial Focal Point,Sahibzada Ajit Singh Nagar, Punjab 160071,India",
"closingTime": "19:00:00",
"city": "Mohali",
"phone": "9718409751",
"bookingDateTime": "2018-05-27T21:25:46+05:30",
"openingTime": "10:00:00",
"state": "PB",
"email": "bhuvnesh.kumar#sourcefuse.com",
"zip_code": "44545"
},
"requestAttributes": null,
"bot": {
"name": "ScheduleRide",
"alias": "$LATEST",
"version": "$LATEST"
},
"outputDialogMode": "Text",
"currentIntent": {
"name": "BookAppointment",
"slots": {
"CustomerAgreement": null,
"DropLocationPrompt": "yes",
"PickupAddress": "Unnamed Road, Industrial Area, Sector 74, Sahibzada Ajit Singh Nagar, Punjab 140308, India",
"RequiredService": null,
"AppointmentTime": null,
"DropAddress": null,
"PhoneNumber": null,
"AppointmentDate": "2018-05-28"
},
"slotDetails": {
"CustomerAgreement": {
"resolutions": [],
"originalValue": null
},
"DropLocationPrompt": {
"resolutions": [
{
"value": "yes"
}
],
"originalValue": "yes"
},
"PickupAddress": {
"resolutions": [],
"originalValue": "Unnamed Road, Industrial Area, Sector 74, Sahibzada Ajit Singh Nagar, Punjab 140308, India"
},
"RequiredService": {
"resolutions": [],
"originalValue": null
},
"AppointmentTime": {
"resolutions": [
{
"value": "00:00"
},
{
"value": "12:00"
}
],
"originalValue": "14:00:00"
},
"DropAddress": {
"resolutions": [],
"originalValue": null
},
"PhoneNumber": {
"resolutions": [],
"originalValue": null
},
"AppointmentDate": {
"resolutions": [],
"originalValue": "28 may"
}
},
"confirmationStatus": "None"
},
"inputTranscript": "14:00:00"
}
Here is the request passed to the lex, see the AppointmentTime in the slots object, it is showing null and in the slotDetails object, it is showing value. Here is the array of buttons which i pass from the response card object:
[ { text: '1:00 PM', value: '13:00:00' },
{ text: '1:30 PM', value: '13:30:00' },
{ text: '2:00 PM', value: '14:00:00' },
{ text: '2:30 PM', value: '14:30:00' },
{ text: '3:00 PM', value: '15:00:00' },
{ text: '3:30 PM', value: '15:30:00' },
{ text: '4:00 PM', value: '16:00:00' },
{ text: '4:30 PM', value: '16:30:00' },
{ text: '5:00 PM', value: '17:00:00' },
{ text: '5:30 PM', value: '17:30:00' },
{ text: '6:00 PM', value: '18:00:00' },
{ text: '6:30 PM', value: '18:30:00' } ]
Here is the function of buildResponseCard:
function buildResponseCard(title, subTitle, options){
let buttons = null;
console.log(options);
let genericAttachments = [];
if (options != null){
buttons = [];
for(let i = 0; i < (options.length); i++){
buttons.push(options[i]);
if(i%3 === 2){
genericAttachments.push({
title,
subTitle,
buttons
});
buttons = [];
}
}
}
return {
version: 1,
contentType: "application/vnd.amazonaws.card.generic",
genericAttachments
};
}
The reason I want AppointmentTime slot value on slot object to be shown on is that whenever the value in the slot is invalid through validation, I will set it to null and will callback it through lambda function and i will not be able to callback the slotDetails object in the response.
Can anyone tell me how can i achieve this?
For the AMAZON.TIME built-in slot:
When a user enters an ambiguous time, Amazon Lex uses the slotDetails attribute of a Lambda event to pass resolutions for the ambiguous times to your Lambda function. ... In this case, the value in the slots map is null, and the slotDetails entity contains the two possible resolutions of the time.
Your input of "14:00:00" is not understood as an unambiguous time, and Lex tries to resolve it to either noon or midnight, which is obviously wrong. This is probably because the Lex Time slot is expecting only hours and minutes (hh:mm) but not seconds (hh:mm:ss).
So if it is possible for you, one solution is to remove the seconds digits from your time values. For example:
[ { text: '1:00 PM', value: '13:00' }, ... ]
However, if you must have the seconds in the time value, then you can simply find the time the user selected from the inputTranscript value and do your validation of it from there.
var userInput = event.inputTranscript;
EDIT:
Another option is to ignore Lex's suggested resolutions and get the slot-details original-value. Like this:
var timeValue = event.currentIntent.slotDetails.AppointmentTime.originalValue;
And then set the AppointmentTime slot yourself. Like this:
event.currentIntent.slots.AppointmentTime = timeValue;

Not able to get authorization on 2checkout

I am trying to authorize orders on the 2checkout sandbox, it was working fine but suddenly it stopped. Now I am always getting:
Payment Authorization Failed: Please verify your information and try
again, or try another payment method.
var tco = new Twocheckout({
sellerId: "1234456688", //on my code I am sending my true seller id
privateKey: "XXXXXXX-XXXXXX-XXXXXX", //on my code I am sending my key
sandbox: true
});
var plan = SubscriptionService.getPlan(req.body.plan);
if(plan) {
var params = {
"merchantOrderId": new Date().valueOf()+"",
"token": req.body.token,
"currency": "USD",
"tangible": "N",
"lineItems": [
{
"name": plan.name,
"price": plan.price,
"type": "product",
"quantity": "1",
"productId": plan.id,
"recurrence": "1 Month",
"duration": "Forever",
"description": ""
}],
"billingAddr": {
"name": req.body.ccName,
"addrLine1": req.body.streetAddress,
"city": req.body.city,
"state": req.body.state,
"zipCode": req.body.zip,
"country": req.body.country,
"email": req.user.email,
"phoneNumber": "5555555555"
}
};
tco.checkout.authorize(params, function (error, data) {
if (error) {
res.send(error);
} else {
res.send(data.response);
}
});
}
}
this is the example of a json I am sending
{ merchantOrderId: '1494967223074',
token: 'ZTFiNmFkMjktZWNmMi00NjlhLWE0MDAtZmJkMGJlYjU5M2Q1',
currency: 'USD',
tangible: 'N',
lineItems:
[ { name: 'pro plan',
price: '149.00',
type: 'product',
quantity: '1',
productId: '002',
recurrence: '1 Month',
duration: 'Forever',
description: '' } ],
billingAddr:
{ name: 'Testing Tester',
addrLine1: '123 Main Street',
city: 'Townsville',
state: 'ohio',
zipCode: '43206',
country: 'USA',
email: 'victor.eloy#landmarkwebteam.com',
phoneNumber: '55555555' } }
If I go to my account >> site management and set demo to true I manage to get authorizations from the sandbox but the orders do not get logged to the sandbox. Previously even when the demo mode was off I managed to get the orders authorized but now I don't know what is happening.
here comes a log from one order:
I have the exact same problem. Just 4 days ago the code was working fine. I'm assuming it's something from 2checkout not from our code..
Only thing I can see is you are attempting to parse
"zipCode": req.body.zip
but you are sending
zipCode: '43206'
I assume this should be parsed as req.body.zipCode

Referance multiple documents Mongoose

I'm trying to connect 3 different documents using mongoose (mainly to learn how to use it), I've set up 3 different schemas as follows:
(all of them are in there own files)
const Books = new Schema({
title: { type: String, required: true, unique: true },
description: String,
author: { type: mongoose.Schema.Types.ObjectId, ref: 'Authors' },
stores: [{
available: Number,
store: { type: mongoose.Schema.Types.ObjectId, ref: 'Stores' }
}]
});
exports.Books = mongoose.model('Books', Books);
const Authors = new Schema({
name: { type: String, required: true },
description: String,
born: Date,
died: { type: Date, default: null }
});
exports.Authors = mongoose.model('Authors', Authors);
const Stores = new Schema({
name: { type: String, required: true, unique: true },
description: String,
address: String
});
exports.Stores = mongoose.model('Stores', Stores);
and then I add the following data:
author.name = 'Jonathan Swift';
author.description = 'old satiric bloke';
author.born = new Date(1667, 10, 30);
author.died = new Date(1745, 9, 19);
author.save()
.catch((err) => console.log(err))
.then(() => console.log('author saved'));
store.name = 'Book shop 1';
store.description = 'This is where the cool kids buy all there books!';
store.address = 'La La Land, 123 Combaja street';
store.save()
.catch((err) => console.log(err))
.then(() => console.log('store saved'));
book.title = 'gullivers travels';
book.author = '58a79345711f2350999e0911'; // _id from the author save
book.description = 'A book about a big guy in a small world';
book.stores = [{
available: 8,
store: '58a79345711f2350999e0912' // _id from the store save
}];
book.save()
.catch((err) => console.log(err))
.then(() => console.log('store saved'));
The problem I find is that when I run book.find() it returns:
[
{
"_id": "58a795b8298b50527412815c",
"description": "A book about a big guy in a small world",
"author": {
"_id" : "58a79345711f2350999e0911",
"born" : -9532947600000,
"died" : -7075130400000,
"description" : "old satiric bloke",
"name" : "Jonathan Swift",
"__v" : 0
},
"title": "gullivers travels",
"__v": 0,
"stores": [
{
"available": 8,
"store": "58a79345711f2350999e0912",
"_id": "58a795b8298b50527412815d"
}
]
}
]
what I was expecting/hoping for was to get the entire store as well the same way as with the author, have I missed something or how should I go about this to achieve the expected result?
I've tried populate but without success
In your Books model author & stores.store are reference, you shouldn't have author field populated if you don't use populate().
If you specify directly the _id of your store & author you have just created :
var author = new Authors({
name: 'Jonathan Swift',
description: 'old satiric bloke',
born: new Date(1667, 10, 30),
died: new Date(1745, 9, 19)
});
author.save();
var store = new Stores({
name: 'Book shop 1',
description: 'This is where the cool kids buy all there books!',
address: 'La La Land, 123 Combaja street'
});
store.save();
var book = new Books({
title: 'gullivers travels',
author: author._id, // _id from the author save
description: 'A book about a big guy in a small world',
stores: [{
available: 8,
store: store._id // _id from the store save
}]
})
book.save();
Books.find() gives the expected result :
[
{
"_id": "58a7a2529a8b894656a42e00",
"title": "gullivers travels",
"author": "58a7a2529a8b894656a42dfe",
"description": "A book about a big guy in a small world",
"__v": 0,
"stores": [
{
"available": 8,
"store": "58a7a2529a8b894656a42dff",
"_id": "58a7a2529a8b894656a42e01"
}
]
}
]
If you want stores.store & author populated, use :
Books.find().populate([{path:'author'},{path:'stores.store'}]).exec(function(err, res) {
console.log(JSON.stringify(res, null, 2));
});
It gives as expected author & stores.store both populated :
[
{
"_id": "58a7a2529a8b894656a42e00",
"title": "gullivers travels",
"author": {
"_id": "58a7a2529a8b894656a42dfe",
"name": "Jonathan Swift",
"description": "old satiric bloke",
"born": "1667-11-29T23:00:00.000Z",
"__v": 0,
"died": "1745-10-18T22:00:00.000Z"
},
"description": "A book about a big guy in a small world",
"__v": 0,
"stores": [
{
"available": 8,
"store": {
"_id": "58a7a2529a8b894656a42dff",
"name": "Book shop 1",
"description": "This is where the cool kids buy all there books!",
"address": "La La Land, 123 Combaja street",
"__v": 0
},
"_id": "58a7a2529a8b894656a42e01"
}
]
}
]

Resources