Source: search.js

/**
 * Searcher Class
 * @class Searcher
 * @param {string} recordType    The string representing the record type
 * @param {string} batchSize     The string representing the batch size
 * @param {string} lowerBound    The string representing the lower bound internalid
 * @param {array}  searchFilters The array containing search filter params
 * @param {array}  searchColumns The array containing search column params
 * @return {Upserter} A new instance of Searcher
 */
this.Searcher = (function() {

  /** @constructor */
  function Searcher(recordType, batchSize, lowerBound, searchFilters, searchColumns) {
    this.SEARCH_FILTER_NAME_KEY     = 'name';
    this.SEARCH_FILTER_OPERATOR_KEY = 'operator';
    this.SEARCH_FILTER_VALUE_KEY    = 'value';
    this.SEARCH_FILTER_FORMULA_KEY  = 'formula';

    this.SEARCH_COLUMN_NAME_KEY = 'name';
    this.SEARCH_COLUMN_JOIN_KEY = 'join';
    this.SEARCH_COLUMN_SORT_KEY = 'sort';

    this.SEARCH_FORMULA_FIELD_KEY      = 'field';
    this.SEARCH_FORMULA_VALUES_KEY     = 'values';
    this.SEARCH_FORMULA_COMPARISON_KEY = 'comparison';
    this.SEARCH_FORMULA_JOIN_KEY       = 'join';

    this.recordType            = recordType;
    this.originalBatchSize     = batchSize;
    this.originalLowerBound    = lowerBound;
    this.lowerBound            = lowerBound;
    this.lowerBoundFilterIndex = 0;
    this.rawSearchFilters      = searchFilters;
    this.rawSearchColumns      = searchColumns;
    this.searchFilters         = [];
    this.searchColumns         = [];
    this.results               = [];

    intBatchSize = parseInt(this.originalBatchSize);
    if((intBatchSize % 1000) != 0) {
      intBatchSize = intBatchSize + (1000 - (intBatchSize % 1000));
    }
    this.batchSize = intBatchSize;

    this.createSearchFilters();
    this.generateLowerBoundFilter();
    this.createSearchColumns();
    this.generateSortColumn();
  }

  /**
   * Iterates over `rawSearchFilters` calling generating an nlobjSearchFilter
   * from each
   *
   * @method
   * @memberof Searcher
   * @return {null}
   */
  Searcher.prototype.createSearchFilters = function() {
    for(index in this.rawSearchFilters) {
      searchFilterData   = this.rawSearchFilters[index];
      searchFilterObject = this.getSearchFilterObject(searchFilterData);

      if(searchFilterData.hasOwnProperty(this.SEARCH_FILTER_FORMULA_KEY)) {
        formulaData = searchFilterData[this.SEARCH_FILTER_FORMULA_KEY];
        formula = this.generateFormula(formulaData);
        this.setFormula(searchFilterObject, formula);
      }

      this.searchFilters.push(searchFilterObject);
    }
    this.lowerBoundFilterIndex = this.searchFilters.length;
  }

  /**
   * Generates a SQL function filter from the given params
   *
   * @method
   * @param {object} formulaData The object repesenting the formula params
   * @memberof Searcher
   * @return {String} The string representing the SQL function
   */
  Searcher.prototype.generateFormula = function(formulaData) {
    field      = formulaData[this.SEARCH_FORMULA_FIELD_KEY];
    values     = formulaData[this.SEARCH_FORMULA_VALUES_KEY];
    comparison = formulaData[this.SEARCH_FORMULA_COMPARISON_KEY];
    join       = formulaData[this.SEARCH_FORMULA_JOIN_KEY];

    formula  = "CASE WHEN (";
    segments = [];
    for(index in values) {
      value = values[index];
      formulaSegment = this.buildFormulaSegment(field, comparison, value);
      segments.push(formulaSegment);
    }
    formula += segments.join(' ' + join + ' ');
    formula += ") THEN 1 ELSE 0 END";

    return formula;
  }

  /**
   * Generates a single comparison segment for a SQL formula
   *
   * @method
   * @param {string} field      The String repesenting the field
   * @param {string} comparison The String repesenting the comparison operator
   * @param {string} value      The String repesenting the value
   * @memberof Searcher
   * @return {String} The string representing the SQL function
   */
  Searcher.prototype.buildFormulaSegment = function(field, comparison, value) {
    return "{" + field + "} " + comparison + " '" + value + "'";
  }

  /**
   * Assigns a given SQL formula to a search filter
   *
   * @method
   * @param {nlobjSearchFilter} searchFilterObject An nlobjSearchFilter instance
   * @param {string}            formulaString      The String representing the SQL formula
   * @memberof Searcher
   * @return {String} The string representing the SQL function
   */
  Searcher.prototype.setFormula = function(searchFilterObject, formulaString) {
    searchFilterObject.setFormula(formulaString);
  }

  /**
   * Generates an nlobjSearchFilter to start the search at the lower bound,
   * then assigns that filter to the list of all filters
   *
   * @method
   * @memberof Searcher
   * @return {null}
   */
  Searcher.prototype.generateLowerBoundFilter = function() {
    searchFilterData = {};
    searchFilterData[this.SEARCH_FILTER_NAME_KEY]     = 'internalidnumber';
    searchFilterData[this.SEARCH_FILTER_OPERATOR_KEY] = 'greaterthan';
    searchFilterData[this.SEARCH_FILTER_VALUE_KEY]    = this.lowerBound;

    lowerBoundFilterObject = this.getSearchFilterObject(searchFilterData)
    this.searchFilters[this.lowerBoundFilterIndex] = lowerBoundFilterObject;
  }

  /**
   * Generates an nlobjSearchFilter with the given params
   *
   * @method
   * @param {object} searchFilterData The object representing the search filter params
   * @memberof Searcher
   * @return {nlobjSearchFilter} The generated search filter
   */
  Searcher.prototype.getSearchFilterObject = function(searchFilterData) {
    name     = searchFilterData[this.SEARCH_FILTER_NAME_KEY];
    operator = searchFilterData[this.SEARCH_FILTER_OPERATOR_KEY];
    value    = searchFilterData[this.SEARCH_FILTER_VALUE_KEY];
    filter = NetsuiteToolkit.searchFilter(name, null, operator, value);
    return filter;
  }

  /**
   * Generates an nlobjSearchColumn for each set of params in rawSearchColumns
   * and appends the generated filters to the search column list
   *
   * @method
   * @memberof Searcher
   * @return {null}
   */
  Searcher.prototype.createSearchColumns = function() {
    for(index in this.rawSearchColumns) {
      searchColumnData   = this.rawSearchColumns[index];
      searchColumnObject = this.getSearchColumnObject(searchColumnData);
      this.searchColumns.push(searchColumnObject);
    }
  }

  /**
   * Generates a search column to sort by internalid and appends it to the
   * list of all search columns
   *
   * @method
   * @memberof Searcher
   * @return {null}
   */
  Searcher.prototype.generateSortColumn = function() {
    sortColumn = NetsuiteToolkit.searchColumn('internalid', null);
    sortColumn.setSort();
    this.searchColumns.push(sortColumn);
  }

  /**
   * Generates an nlobjSearchColumn with the given params
   *
   * @method
   * @param {object} searchColumnData The object representing the search column params
   * @memberof Searcher
   * @return {nlobjSearchColumn} The generated search column
   */
  Searcher.prototype.getSearchColumnObject = function(searchColumnData) {
    name = searchColumnData[this.SEARCH_COLUMN_NAME_KEY];
    join = searchColumnData[this.SEARCH_COLUMN_JOIN_KEY];
    column = NetsuiteToolkit.searchColumn(name, join);
    return column;
  }

  /**
   * Sets the sort mastering of the given column
   *
   * @method
   * @param {nlobjSearchcolumn} A search column
   * @memberof Searcher
   * @return {null}
   */
  Searcher.prototype.setSortColumn = function(sortColumnObject) {
    sortColumnObject.setSort();
  }

  /**
   * Loop to orchestrate the entire search process
   *
   * @method
   * @memberof Searcher
   * @return {null}
   */
  Searcher.prototype.executeSearch = function() {
    while(true) {
      try {
        done = this.searchIteration();
        if(done) { break; }
      } catch(exception) {
        formattedException = NetsuiteToolkit.formatReply(null, null, exception);
        this.results = formattedException;
        break;
      }
    }
  }

  /**
   * Requests a block of results from Netsuite, updates the context and
   * accumulates the results to the result list
   *
   * @method
   * @memberof Searcher
   * @return {null}
   */
  Searcher.prototype.searchIteration = function() {
    resultsBlock = this.getSearchResults();
    this.appendResults(resultsBlock);
    done = this.isExecutionDone(resultsBlock);

    if(!done) {
      this.updateBoundAndFilter(resultsBlock);
    }

    return done;
  }

  /**
   * Requests a block of results from Netsuite, updates the context and
   *
   * @method
   * @param {Array} resultsBlock The array containing a set of results from Netsuite
   * @memberof Searcher
   * @return {boolean} The boolean representing successful completion
   */
  Searcher.prototype.isExecutionDone = function(resultsBlock) {
    if(!resultsBlock) { return true }
    allResultsFound = resultsBlock.length != 1000;
    batchSizeMet    = this.results.length >= this.batchSize;
    return allResultsFound || batchSizeMet;
  }

  /**
   * Fetches a new set of results from Netsuite and accumulates them onto the result list
   *
   * @method
   * @memberof Searcher
   * @return {Array} The array of results fetched from Netsuite
   */
  Searcher.prototype.getSearchResults = function() {
    results = NetsuiteToolkit.searchRecord(this.recordType, null,
                                           this.searchFilters,
                                           this.searchColumns);
    results = [].concat(results);
    results = results.filter(function(result) { return(result != null) });
    return results;
  }

  /**
   * Updates the context of the search iteration using the given result set
   *
   * @method
   * @param {Array} resultsBlock The array containing a set of results from Netsuite
   * @memberof Searcher
   * @return {null}
   */
  Searcher.prototype.updateBoundAndFilter = function(resultsBlock) {
    newLowerBound   = this.extractLowerBound(resultsBlock);
    this.lowerBound = newLowerBound;
    this.generateLowerBoundFilter();
  }

  /**
   * Retrieves the list of the last record from the given result set
   *
   * @method
   * @param {Array} resultsBlock The array containing a set of results from Netsuite
   * @memberof Searcher
   * @return {Number} The number representing the id of the last-fetched record
   */
  Searcher.prototype.extractLowerBound = function(resultsBlock) {
    resultRow = resultsBlock[resultsBlock.length - 1];
    return resultRow.getId();
  }

  /**
   * Concatenates the given result set onto the result list
   *
   * @method
   * @param {Array} resultsBlock The array containing a set of results from Netsuite
   * @memberof Searcher
   * @return {null}
   */
  Searcher.prototype.appendResults = function(resultsBlock) {
    this.results = this.results.concat(resultsBlock);
  }

  /**
   * Generates a list of params given in the HTTP request
   *
   * @method
   * @memberof Searcher
   * @return {object} The object representing the params from the HTTP request
   */
  Searcher.prototype.getParams = function() {
    params = {};
    params['record_type']    = this.recordType;
    params['batch_size']     = this.originalBatchSize;
    params['lower_bound']    = this.originalLowerBound;
    params['search_filters'] = this.rawSearchFilters;
    params['search_columns'] = this.rawSearchColumns;
    return params;
  }

  /**
   * Generates a formatted reply containing the results of the search execution
   *
   * @method
   * @memberof Searcher
   * @return {object} The object representing formatted reply
   */
  Searcher.prototype.reply = function() {
    return NetsuiteToolkit.formatReply(this.getParams(), this.results);
  }

  return Searcher;

})();

/** @namespace global */

/**
 * The script function that Netsuite will call to execute the Search process
 * 
 * @method
 * @param {object} request The object representing the HTTP request body
 * @memberof global
 * @return {object} The formatted results of the request
 */
var searchPostHandler = function(request) {
  searcher = new Searcher(request['record_type'], request['batch_size'], request['lower_bound'],
                          request['search_filters'], request['search_columns']);
  searcher.executeSearch();
  return searcher.reply();
}