protected List<ValueEval> fetchValuesWithFullScan(
      IDataSet set, Map<Integer, Object> where, int columnIndex) {

    List<ValueEval> found = new ArrayList<>();

    for (IDsRow row : set) {
      boolean allFieldsMatch = true;
      int allFieldsPresent = where.size();

      for (Entry<Integer, Object> whereColumn : where.entrySet()) {
        IDsCell cell = row.getCell(whereColumn.getKey());

        if (cell != null) {
          allFieldsPresent--;
          Object extValue = coerceValueTo(whereColumn.getValue());
          /* Such a strange conversion because of Number types - everything is Double in POI */
          Object intValue = coerceValueTo(valueToValueEval(cell.getValue().get()));

          if (!intValue.equals(extValue)) {
            allFieldsMatch = false;
            break;
          }
        }
      }

      if (allFieldsPresent == 0 && allFieldsMatch) {
        found.add(valueToValueEval(row.getCell(columnIndex).getValue().get()));
        break; // collecting only the first matching record according to product owner requirements
      }
    }

    return found;
  }
  @Override
  public ValueEval evaluate(ValueEval[] args, OperationEvaluationContext ec) {

    log.debug("In evaluate() of DSLOOKUP function. Args = {}", Arrays.toString(args));

    if (args.length < 4 || args.length % 2 != 0) {
      log.warn(
          "The number of input arguments of DSLOOKUP function should be even and no less than 4.");
      return ErrorEval.VALUE_INVALID;
    }
    if (!(args[0] instanceof StringValueEval) && !(args[0] instanceof RefEval)) {
      log.warn(
          "The first input argument of DSLOOKUP function should be a string (or a reference to a cell) with a dataset name.");
      return ErrorEval.VALUE_INVALID;
    }
    if (!(args[args.length - 1] instanceof StringValueEval)
        && !(args[args.length - 1] instanceof RefEval)) {
      log.warn(
          "The last input argument of DSLOOKUP function should be a string (or a reference to a cell) with a name of a column which values should be returned.");
      return ErrorEval.VALUE_INVALID;
    }

    String datasetName;
    try {
      datasetName =
          (String) coerceValueTo(getSingleValue(args[0], ec.getRowIndex(), ec.getColumnIndex()));
    } catch (EvaluationException e) {
      log.error(String.format("Cannot get the value of DataSet name: %s", args[0]), e);
      return ErrorEval.VALUE_INVALID;
    }

    String columnName;
    try {
      columnName =
          (String)
              coerceValueTo(
                  getSingleValue(args[args.length - 1], ec.getRowIndex(), ec.getColumnIndex()));
    } catch (EvaluationException e) {
      log.error(
          String.format("Cannot get the value of target column name: %s", args[args.length - 1]),
          e);
      return ErrorEval.VALUE_INVALID;
    }

    Map<Object, ValueEval> pairs = new HashMap<>();

    for (int i = 1; i < args.length - 1; i += 2) {

      if (!(args[i] instanceof StringEval) && !(args[i] instanceof RefEval)) {
        log.warn(
            "The {}th input argument in DSLOOKUP function should be a string (or a reference to a cell) with a name of a condition field",
            i);
        return ErrorEval.VALUE_INVALID;
      }

      try {
        String key =
            (String) coerceValueTo(getSingleValue(args[i], ec.getRowIndex(), ec.getColumnIndex()));
        ValueEval val = getSingleValue(args[i + 1], ec.getRowIndex(), ec.getColumnIndex());
        pairs.put(key, val);
      } catch (EvaluationException e) {
        log.error(String.format("Cannot get the value of matcher column: %s", args[i]), e);
        return ErrorEval.VALUE_INVALID;
      }
    }

    DataSetAccessor dataSets =
        (DataSetAccessor) ec.getCustomEvaluationContext().get(DataSetAccessor.class);
    if (dataSets == null) {
      dataSets = this.external.getDataSetAccessor();
    }

    IDataSet dataSet;
    try {
      dataSet = dataSets.get(datasetName);
    } catch (Exception e) {
      log.error(
          "The DataSet with name = {} cannot be found\retrived from DataSet storage.", datasetName);
      return ErrorEval.NA;
    }

    if (dataSet == null) {
      DataModelAccessor dataModels =
          (DataModelAccessor) ec.getCustomEvaluationContext().get(DataModelAccessor.class);
      if (dataModels == null) {
        dataModels = this.external.getDataModelAccessor();
      }

      dataSet = Converters.toDataSet(dataModels.get(datasetName));
    }

    if (dataSet == null) {
      log.error(
          "The DataSet with name = {} cannot found in DataSet/DataModel storage.", datasetName);
      return ErrorEval.NA;
    }

    Iterator<IDsRow> rowrator = dataSet.iterator();
    if (!rowrator.hasNext()) {
      log.warn("The spreadsheet shoud have at least 2 rows to run DSLOOKUP function");
      return ErrorEval.VALUE_INVALID;
    }

    int columnIndex = -1;
    IDsRow titleRow = rowrator.next();
    Map<Integer, Object> indexToValue = new HashMap<>();

    for (IDsCell cell : titleRow) {
      ICellValue value = cell.getValue();

      if (pairs.containsKey(value.get())) {
        indexToValue.put(cell.index(), pairs.get(value.get()));
      }

      if (columnName.equals(value.get())) {
        columnIndex = cell.index();
      }
    }

    if (columnIndex < 0) {
      log.warn("No such column to retreive value from is found: {}.", columnName);
      return ErrorEval.VALUE_INVALID;
    }

    if (indexToValue.isEmpty()) {
      log.warn("No filter columns are found.");
      return ErrorEval.VALUE_INVALID;
    }

    DsLookupParameters parameters =
        new DsLookupParameters(dataSet.getName(), indexToValue, columnIndex);
    List<ValueEval> fetchedValues = fetchValuesWithOptimisations(parameters, ec);

    if (fetchedValues == null) {
      fetchedValues = fetchValuesWithFullScan(dataSet, indexToValue, columnIndex);

      updateOptimisationsCache(parameters, dataSet, fetchedValues, ec);
    }

    // This is per PO decision: DSLOOKUP should return only one value - first found.
    return fetchedValues.isEmpty() ? ErrorEval.NA : fetchedValues.get(0);
  }