rxdb
Version: 
A local-first realtime NoSQL Database for JavaScript applications - https://rxdb.info/
284 lines (278 loc) • 9.79 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.changeIndexableStringByOneQuantum = changeIndexableStringByOneQuantum;
exports.getIndexMeta = getIndexMeta;
exports.getIndexStringLength = getIndexStringLength;
exports.getIndexableStringMonad = getIndexableStringMonad;
exports.getNumberIndexString = getNumberIndexString;
exports.getPrimaryKeyFromIndexableString = getPrimaryKeyFromIndexableString;
exports.getStartIndexStringFromLowerBound = getStartIndexStringFromLowerBound;
exports.getStartIndexStringFromUpperBound = getStartIndexStringFromUpperBound;
exports.getStringLengthOfIndexNumber = getStringLengthOfIndexNumber;
var _rxSchemaHelper = require("./rx-schema-helper.js");
var _index = require("./plugins/utils/index.js");
var _queryPlanner = require("./query-planner.js");
/**
 * For some RxStorage implementations,
 * we need to use our custom crafted indexes
 * so we can easily iterate over them. And sort plain arrays of document data.
 *
 * We really often have to craft an index string for a given document.
 * Performance of everything in this file is very important
 * which is why the code sometimes looks strange.
 * Run performance tests before and after you touch anything here!
 */
/**
 * Prepare all relevant information
 * outside of the returned function
 * from getIndexableStringMonad()
 * to save performance when the returned
 * function is called many times.
 */
function getIndexMeta(schema, index) {
  var fieldNameProperties = index.map(fieldName => {
    var schemaPart = (0, _rxSchemaHelper.getSchemaByObjectPath)(schema, fieldName);
    if (!schemaPart) {
      throw new Error('not in schema: ' + fieldName);
    }
    var type = schemaPart.type;
    var parsedLengths;
    if (type === 'number' || type === 'integer') {
      parsedLengths = getStringLengthOfIndexNumber(schemaPart);
    }
    var getValue = (0, _index.objectPathMonad)(fieldName);
    var maxLength = schemaPart.maxLength ? schemaPart.maxLength : 0;
    var getIndexStringPart;
    if (type === 'string') {
      getIndexStringPart = docData => {
        var fieldValue = getValue(docData);
        if (!fieldValue) {
          fieldValue = '';
        }
        return fieldValue.padEnd(maxLength, ' ');
      };
    } else if (type === 'boolean') {
      getIndexStringPart = docData => {
        var fieldValue = getValue(docData);
        return fieldValue ? '1' : '0';
      };
    } else {
      // number
      getIndexStringPart = docData => {
        var fieldValue = getValue(docData);
        return getNumberIndexString(parsedLengths, fieldValue);
      };
    }
    var ret = {
      fieldName,
      schemaPart,
      parsedLengths,
      getValue,
      getIndexStringPart
    };
    return ret;
  });
  return fieldNameProperties;
}
/**
 * Crafts an indexable string that can be used
 * to check if a document would be sorted below or above
 * another documents, dependent on the index values.
 * @monad for better performance
 *
 * IMPORTANT: Performance is really important here
 * which is why we code so 'strange'.
 * Always run performance tests when you want to
 * change something in this method.
 */
function getIndexableStringMonad(schema, index) {
  var fieldNameProperties = getIndexMeta(schema, index);
  var fieldNamePropertiesAmount = fieldNameProperties.length;
  var indexPartsFunctions = fieldNameProperties.map(r => r.getIndexStringPart);
  /**
   * @hotPath Performance of this function is very critical!
   */
  var ret = function (docData) {
    var str = '';
    for (var i = 0; i < fieldNamePropertiesAmount; ++i) {
      str += indexPartsFunctions[i](docData);
    }
    return str;
  };
  return ret;
}
function getStringLengthOfIndexNumber(schemaPart) {
  var minimum = Math.floor(schemaPart.minimum);
  var maximum = Math.ceil(schemaPart.maximum);
  var multipleOf = schemaPart.multipleOf;
  var valueSpan = maximum - minimum;
  var nonDecimals = valueSpan.toString().length;
  var multipleOfParts = multipleOf.toString().split('.');
  var decimals = 0;
  if (multipleOfParts.length > 1) {
    decimals = multipleOfParts[1].length;
  }
  return {
    minimum,
    maximum,
    nonDecimals,
    decimals,
    roundedMinimum: minimum
  };
}
function getIndexStringLength(schema, index) {
  var fieldNameProperties = getIndexMeta(schema, index);
  var length = 0;
  fieldNameProperties.forEach(props => {
    var schemaPart = props.schemaPart;
    var type = schemaPart.type;
    if (type === 'string') {
      length += schemaPart.maxLength;
    } else if (type === 'boolean') {
      length += 1;
    } else {
      var parsedLengths = props.parsedLengths;
      length = length + parsedLengths.nonDecimals + parsedLengths.decimals;
    }
  });
  return length;
}
function getPrimaryKeyFromIndexableString(indexableString, primaryKeyLength) {
  var paddedPrimaryKey = indexableString.slice(primaryKeyLength * -1);
  // we can safely trim here because the primary key is not allowed to start or end with a space char.
  var primaryKey = paddedPrimaryKey.trim();
  return primaryKey;
}
function getNumberIndexString(parsedLengths, fieldValue) {
  /**
   * Ensure that the given value is in the boundaries
   * of the schema, otherwise it would create a broken index string.
   * This can happen for example if you have a minimum of 0
   * and run a query like
   * selector {
   *  numField: { $gt: -1000 }
   * }
   */
  if (typeof fieldValue === 'undefined') {
    fieldValue = 0;
  }
  if (fieldValue < parsedLengths.minimum) {
    fieldValue = parsedLengths.minimum;
  }
  if (fieldValue > parsedLengths.maximum) {
    fieldValue = parsedLengths.maximum;
  }
  var nonDecimalsValueAsString = (Math.floor(fieldValue) - parsedLengths.roundedMinimum).toString();
  var str = nonDecimalsValueAsString.padStart(parsedLengths.nonDecimals, '0');
  if (parsedLengths.decimals > 0) {
    var splitByDecimalPoint = fieldValue.toString().split('.');
    var decimalValueAsString = splitByDecimalPoint.length > 1 ? splitByDecimalPoint[1] : '0';
    str += decimalValueAsString.padEnd(parsedLengths.decimals, '0');
  }
  return str;
}
function getStartIndexStringFromLowerBound(schema, index, lowerBound) {
  var str = '';
  index.forEach((fieldName, idx) => {
    var schemaPart = (0, _rxSchemaHelper.getSchemaByObjectPath)(schema, fieldName);
    var bound = lowerBound[idx];
    var type = schemaPart.type;
    switch (type) {
      case 'string':
        var maxLength = (0, _index.ensureNotFalsy)(schemaPart.maxLength, 'maxLength not set');
        if (typeof bound === 'string') {
          str += bound.padEnd(maxLength, ' ');
        } else {
          // str += ''.padStart(maxLength, inclusiveStart ? ' ' : INDEX_MAX);
          str += ''.padEnd(maxLength, ' ');
        }
        break;
      case 'boolean':
        if (bound === null) {
          str += '0';
        } else if (bound === _queryPlanner.INDEX_MIN) {
          str += '0';
        } else if (bound === _queryPlanner.INDEX_MAX) {
          str += '1';
        } else {
          var boolToStr = bound ? '1' : '0';
          str += boolToStr;
        }
        break;
      case 'number':
      case 'integer':
        var parsedLengths = getStringLengthOfIndexNumber(schemaPart);
        if (bound === null || bound === _queryPlanner.INDEX_MIN) {
          var fillChar = '0';
          str += fillChar.repeat(parsedLengths.nonDecimals + parsedLengths.decimals);
        } else if (bound === _queryPlanner.INDEX_MAX) {
          str += getNumberIndexString(parsedLengths, parsedLengths.maximum);
        } else {
          var add = getNumberIndexString(parsedLengths, bound);
          str += add;
        }
        break;
      default:
        throw new Error('unknown index type ' + type);
    }
  });
  return str;
}
function getStartIndexStringFromUpperBound(schema, index, upperBound) {
  var str = '';
  index.forEach((fieldName, idx) => {
    var schemaPart = (0, _rxSchemaHelper.getSchemaByObjectPath)(schema, fieldName);
    var bound = upperBound[idx];
    var type = schemaPart.type;
    switch (type) {
      case 'string':
        var maxLength = (0, _index.ensureNotFalsy)(schemaPart.maxLength, 'maxLength not set');
        if (typeof bound === 'string' && bound !== _queryPlanner.INDEX_MAX) {
          str += bound.padEnd(maxLength, ' ');
        } else if (bound === _queryPlanner.INDEX_MIN) {
          str += ''.padEnd(maxLength, ' ');
        } else {
          str += ''.padEnd(maxLength, _queryPlanner.INDEX_MAX);
        }
        break;
      case 'boolean':
        if (bound === null) {
          str += '1';
        } else {
          var boolToStr = bound ? '1' : '0';
          str += boolToStr;
        }
        break;
      case 'number':
      case 'integer':
        var parsedLengths = getStringLengthOfIndexNumber(schemaPart);
        if (bound === null || bound === _queryPlanner.INDEX_MAX) {
          var fillChar = '9';
          str += fillChar.repeat(parsedLengths.nonDecimals + parsedLengths.decimals);
        } else if (bound === _queryPlanner.INDEX_MIN) {
          var _fillChar = '0';
          str += _fillChar.repeat(parsedLengths.nonDecimals + parsedLengths.decimals);
        } else {
          str += getNumberIndexString(parsedLengths, bound);
        }
        break;
      default:
        throw new Error('unknown index type ' + type);
    }
  });
  return str;
}
/**
 * Used in storages where it is not possible
 * to define inclusiveEnd/inclusiveStart
 */
function changeIndexableStringByOneQuantum(str, direction) {
  var lastChar = str.slice(-1);
  var charCode = lastChar.charCodeAt(0);
  charCode = charCode + direction;
  var withoutLastChar = str.slice(0, -1);
  return withoutLastChar + String.fromCharCode(charCode);
}
//# sourceMappingURL=custom-index.js.map