I'm sure there's a simple way but how do I skip an...
# suitescript
m
I'm sure there's a simple way but how do I skip an iteration of a map function in a map/reduce script? Do I need to
break
? Netsuite makes it a pain to test these.
r
Just return an empty object. return {} Map stage will still trigger, but based on your condition it will not test any of he logic below and just go to next stage if any is there.
m
do I have to return an object or can I just use a stand-alone
return
statement?
r
Never tried it. Just test it out.
a
umm I think the standard practice would be to filter that result out at the get input stage?
m
At the input stage I'm returning a saved search of journal entries. For some reason Netsuite specifically made Mainline: True not work for journal entries... so in the map stage I'm trying to skip the iteration if the journal entry has already been parsed
So for journal entries there seems to be no way to make sure my saved search is only returning unique journal ids
a
you should be able to use the map reduce in the map you set the key as the JE internal id and in then in the reduce all the line data for that JE will be in the same reduce instance
alternatively you can preprocess the the serach results in the get input stage, actually run your search there, and loop over the results and push results to a new array only if that value doesn't already exist in the arrary and then return that new array to get your limited map instances
m
Yes, my question was about how to skip the extra lines in the Map function. I'm trying to skip the duplicates there
In the map stage I'm taking the journal entries, pulling each one into memory (ideally uniquely), looping through the journal's lines, and finding the lines I care about
a
yeah i think the problem you're going to run into is the map instances aren't aware of each other... so I'm not sure how the one map instance will know that the other map instance is already processing that JE
m
Definitely possible, I made a global array variable that i am pushing values into from the map function, and about to test to see if the global function is accessible in each instance of the map, but you're right I wouldn't be surprised if each iteration of the map function only has an empty array despite me defining it 'globally'
a
map instances can't see globals
m
or some partially filled out array for whatever that virtual machine is processing
Fair enough... seemed a little redundant to pre-process data before the map function but I suppose that's my next step
a
you can use globals in MRs but only if you work withthem in summary and get input stage
m
Hmm okay, thanks for the info Anthony
a
you could maybe add a custom checkbox to the JE? and check it when you're done with that JE, and then have each map instance check for that checkbox?
m
that seems like a lot more work/disruptive than just running the search pre-emptively in the inputdata
a
true
m
it's only a few thousand lines and i'm only checking/storing the id value
I just need to figure out how to structure the data for the map function, was letting netsuite figure it out from the serach directly
a
gotcha... yeah then just do it up front in the get input would probably be easiest
m
i'm guessing it's an array of hashes or something?
a
oh you need column data? figured you'd just load the JE record?
m
well I mean how does the data have to be structured at the end of the getinputdata for netsuite to figure out how to iterate over it in the map function?
a
it just needs to be something it can iterate over.... so an array should be fine i think?
m
okay cool
a
maybe it needs to be an array of objects? so you'd do {id:x}
m
yeah that was what i was wondering
for map it says- • Parses each row of data into a key/value pair. One pair (key/value) is passed per function invocation. If this stage is skipped, the reduce stage is required. Data may be processed in parallel in this stage. and it mentions that it's passing in a JSON string...
a
an array should be fine, but idk how you'd reference it, so I'd do an array of objects
its what the getinput stage returns, is what you care about
m
their example shows-
Copy code
function getInputData() {
return "the quick brown fox \njumps over the lazy dog.".split('\n'); }
// After the getInputData function is executed, the system creates the following
// key/value pairs: //
// key: 0, value: 'the quick brown fox'
// key: 1, value: 'jumps over the lazy dog.'
a
oh so it just makes the index the key
yeah that works too i gues
m
yeah i guess so
yep, just gotta tweak some stuff
a
👍
good luck
m
Hmm I think that worked but it's erroring out somewhere else, do you know how to get these errors displayed? Make it fail on error
a
I'm not sure what you mean... if there's an error it did fail? if one map errors you want them ALL to error?
m
I think if it error's right now it essentially fails silently, I would like to actually see the erorr logged to the execution log, reading up on how to add that
a
wrap it a try/catch and log the error in the catch
b
m
Hmm yeah a try/catch could work
Anyone know why a
reduce
stage would not be run? I've got
Copy code
return {
      getInputData: getInputData,
      map: map,
      reduce: reduce,
      summarize: summarize
    };
and then
Copy code
function reduce(context) {
      log.audit('hit reduce')
      log.audit('reduce stage context', context)

      parsedContext = JSON.parse(context)
....
but not seeing any logging. In my summary context I see
{"type":"mapreduce.ReduceSummary","dateCreated":"2023-10-16T18:12:14.818Z","seconds":0,"usage":0,"concurrency":1,"yields":0}
It just doesn't seem to be running reduce at all
In my map I am logging context and I believe I'm properly writing key/value pairs, e.g.-
{"type":"mapreduce.MapContext","isRestarted":false,"executionNo":1,"key":"5","value":"4327348"}
I'm adding a try/catch to both map and reduce with logging... still nothing. Didn't hit any of the
catch
errors but reduce still doesn't seem to run
b
m
Yeah, aren't I doing that?
and then showing that it's being done with the execution log screenshot
b
where do you use it
m
I didn't include that code sorry, but the results of that code is shown in my screenshot there, all the MAP STAGE CONTEXT lines
That section of code looks like-
Copy code
// Passing the required data for reduce stage
          context.write({ key: customerId, value: currency });
        }

      } catch (e) {
        log.error('hit map catch', e)
      }

      log.audit('MAP STAGE CONTEXT', context)
    }
i've got tons of redundant logging at this point
I'm going to try to hard code some context writing-
Copy code
log.audit('MAP STAGE CONTEXT', context)
      context.write({key: 'test', value: 'test'});
Hmm yeah hard coding some values seemed to finally get me to hit reduce.. I wonder why it wasn't before. Maybe the value needs to be in an array otherwise
b
you havent shared enough code, but whatever block your context.write is in isnt running
or is erroring out with an uncatchable error, in which case you havent implemented Logging Errors
m
The map context in its entirety is-
Copy code
function map(context) {
      try {
        var searchResult = JSON.parse(context.value);
        // log.audit('map stage searchResult', searchResult)

        // Load the record and loop through all the lines to find customer/currency
        var journalRecord = record.load({ type: 'journalentry', id: searchResult })
        var currency = journalRecord.getValue('currency');

        var lineCount = journalRecord.getLineCount({sublistId: 'line'});
        for (var i = 0; i < lineCount; i++) {
          var account = journalRecord.getSublistValue({sublistId: 'line', fieldId: 'account', line: i});
          if (account != expenseAccountId) continue;

          var customerId = journalRecord.getSublistValue({sublistId: 'line', fieldId: 'entity', line: i});

          // Passing the required data for reduce stage
          context.write({ key: customerId, value: currency });
        }

      } catch (e) {
        log.error('hit map catch', e)
      }

      log.audit('MAP STAGE CONTEXT', context)
}
and the netsuite logs seemed to indicate that I was writing to the context with-
Audit	MAP STAGE CONTEXT	10/16/2023	11:43 am	-System-	{"type":"mapreduce.MapContext","isRestarted":false,"executionNo":1,"key":"5","value":"4327348"}
but yeah for some reason it doesn't seem to be writing to context properly, I just don't know why unless I can't write to the context in a for loop?
b
the likely explanation is that the account is not equal to the expenseAccountId
m
If that was the case how would I be getting that log line? Showing that it is logging distinct key/value pairs
b
continue is not the same as break
m
context.write({ key: customerId, value: currency });
is below the continue and
log.audit('MAP STAGE CONTEXT', context)
is later showing
{"type":"mapreduce.MapContext","isRestarted":false,"executionNo":1,"key":"5","value":"4327348"}
b
and regardless, its in a loop
either option would only affects the loop
m
so I can't write to context in a loop?
b
your code logs the map stage context regardless of the results of the loop
it tells you nothing about if context.write has actually run
m
I'm not quite understanding, since it seems to be set (written?) to the context from that log statement
b
the keys and values of the map stage are not the keys and values of the reduce stage
finish reading the documentation on mapContext.write
m
I'm not following, my code seems to mirror exactly the example there and I don't see any additional information in any of the linked docs
b
does your code ever call mapContext.write
m
??? yes i copied and pasted it many times didn't I?
I think every example of my code I've pasted has included that, sorry if I'm misunderstanding but it has
Copy code
context.write({ key: customerId, value: currency });
that runs multiple times per map in a for loop
b
you have made no such check to see if that line is ever reached
m
So you're still saying that
if (account != expenseAccountId) continue;
is skipping every iteration of the line despite it being logged as part of the mapContext later in the map function?
i'll tweak to
Copy code
if (account != expenseAccountId) continue;
          log.audit('after expense account', account)
b
you are using the value of the log
Copy code
{
  "type": "mapreduce.MapContext",
  "isRestarted": false,
  "executionNo": 1,
  "key": "5",
  "value": "4327348"
}
to see a key and value, and you are assuming that has a relation to the key and value that was written
it has no such relation, you could log it in the beginning of the map function and it would log the same thing
m
Yes, since I don't know how else that key/value would be set to context other than my context.write call?
Okay so you're saying those are the initial values
Gotcha, and not what I'm setting via .write
I wasn't understanding that, thanks
r
Just return. Unless you write to map context a key and response then it won’t send any data to reduce or summarize.
I don’t see you writing to mapContext which you need to do in order for it to pass data to reduce/summary stage. context.write({ key: context.value[i], value: 1 }); https://docs.oracle.com/en/cloud/saas/netsuite/ns-online-help/section_4413275809.html
m
Thanks Ryan, I did have context.write but the problem was that I was trying to compare to a global variable earlier in the script which obviously wasn't being set as expected, causing all the iterations to
continue
prior to hitting my context.write
r
Ah, yeah, everything is unique and must be passed meaning no global variables between stages.
303 Views