indexstorage.js 27.4 KB
Newer Older
1 2 3 4 5
/*
* Copyright 2013, Nexedi SA
* Released under the LGPL license.
* http://www.gnu.org/licenses/lgpl.html
*/
Sven Franck's avatar
Sven Franck committed
6 7
/*jslint indent: 2, maxlen: 80, sloppy: true, nomen: true */
/*global jIO: true, localStorage: true, setTimeout: true */
8 9 10 11 12 13 14 15 16 17
/**
 * JIO Index Storage.
 * Manages indexes for specified storages.
 * Description:
 * {
 *     "type": "index",
 *     "indices": [
 *        {"indexA",["field_A"]},
 *        {"indexAB",["field_A","field_B"]}
 *     ],
18 19 20 21
 *     "field_types": {
 *        "field_A": "dateTime",
 *        "field_B": "string"
 *      },
22 23 24 25 26 27 28
 *     "storage": [
 *         <sub storage description>,
 *         ...
 *     ]
 * }
 * Index file will contain
 * {
29
 *   "_id": "app-name_indices.json",
30 31 32 33
 *   "indexA":
 *      "fieldA": {
 *        "keyword_abc": ["some_id","some_other_id",...]
 *      }
34 35
 *   },
 *   "indexAB": {
36 37 38 39
 *     "fieldA": {
 *       "keyword_abc": ["some_id"]
 *      },
 *     "fieldB": {
40
 *       "keyword_def": ["some_id"]
41
*      }
42 43
 *   }
 * }
44 45 46 47 48 49 50 51 52 53 54 55
 * NOTES:
 * It may be difficult to "un-sort" multi-field indices, like
 * indexAB, because all keywords will be listed regrardless
 * of underlying field, so an index on author and year would produce
 * two entries per record like:
 * 
 * "William Shakespeare":["id_Romeo_and_Juliet", "id_Othello"],
 * "1591":["id_Romeo_and_Juliet"],
 * "1603":["id_Othello"]
 * 
 * So for direct lookups, this should be convient, but for other types
 * of queries, it depends
56
 */
Sven Franck's avatar
Sven Franck committed
57
jIO.addStorageType('indexed', function (spec, my) {
58 59

  "use strict";
Sven Franck's avatar
Sven Franck committed
60
  var that, priv = {};
61

Sven Franck's avatar
Sven Franck committed
62
  spec = spec || {};
63
  that = my.basicStorage(spec, my);
Sven Franck's avatar
Sven Franck committed
64

65
  priv.indices = spec.indices;
66
  priv.field_types = spec.field_types;
67 68 69 70
  priv.substorage_key = "sub_storage";
  priv.substorage = spec[priv.substorage_key];
  priv.index_indicator = spec.sub_storage.application_name || "index";
  priv.index_suffix = priv.index_indicator + "_indices.json";
Sven Franck's avatar
Sven Franck committed
71

72
  my.env = my.env || spec.env || {};
Sven Franck's avatar
Sven Franck committed
73

74 75 76 77
  that.specToStore = function () {
    var o = {};
    o[priv.substorage_key] = priv.substorage;
    o.env = my.env;
Sven Franck's avatar
Sven Franck committed
78 79 80
    return o;
  };

81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111
  /**
   * Generate a new uuid
   * @method generateUuid
   * @return {string} The new uuid
   */
  priv.generateUuid = function () {
    var S4 = function () {
      var i, string = Math.floor(
        Math.random() * 0x10000 /* 65536 */
      ).toString(16);
      for (i = string.length; i < 4; i += 1) {
        string = "0" + string;
      }
      return string;
    };
    return S4() + S4() + "-" +
      S4() + "-" +
      S4() + "-" +
      S4() + "-" +
      S4() + S4() + S4();
  };

  /**
  * Get number of elements in object
  * @method getObjectSize
  * @param  {object} obj The object to check
  * @return {number} size The amount of elements in the object
  */
  priv.getObjectSize = function (obj) {
    var size = 0, key;
    for (key in obj) {
Sven Franck's avatar
Sven Franck committed
112 113 114
      if (obj.hasOwnProperty(key)) {
        size += 1;
      }
Sven Franck's avatar
Sven Franck committed
115
    }
116
    return size;
Sven Franck's avatar
Sven Franck committed
117 118
  };

119 120 121 122 123 124 125
  /**
   * Creates an empty indices array
   * @method createEmptyIndexArray
   * @param  {array} indices An array of indices (optional)
   * @return {object} The new index array
   */
  priv.createEmptyIndexArray = function (indices) {
126 127
    var i, k, j = priv.indices.length, new_index,
      new_index_object = {}, new_index_name, new_index_fields;
128 129 130

    if (indices === undefined) {
      for (i = 0; i < j; i += 1) {
131 132 133
        new_index = priv.indices[i];
        new_index_name = new_index.name;
        new_index_fields = new_index.fields;
134
        new_index_object[new_index_name] = {};
135 136 137 138 139

        // loop index fields and add objects to hold value/id pairs
        for (k = 0; k < new_index_fields.length; k += 1) {
          new_index_object[new_index_name][new_index_fields[k]] = {};
        }
140
      }
Sven Franck's avatar
Sven Franck committed
141
    }
142 143 144
    return new_index_object;
  };

145 146 147 148 149 150 151 152 153 154 155 156
  /**
   * Determine if a key/value pair exists in an object by VALUE
   * @method searchObjectByValue
   * @param  {object} indexToSearch The index to search
   * @param  {string} docid The document id to find
   * @param  {string} passback The value that should be returned
   * @return {boolean} true/false
   */
  priv.searchIndexByValue = function (indexToSearch, docid, passback) {
    var key, obj, prop;

    for (key in indexToSearch) {
Sven Franck's avatar
Sven Franck committed
157
      if (indexToSearch.hasOwnProperty(key)) {
158 159 160 161 162 163 164
        obj = indexToSearch[key];
        for (prop in obj) {
          if (obj[prop] === docid) {
            return passback === "bool" ? true : key;
          }
        }
      }
Sven Franck's avatar
Sven Franck committed
165
    }
166
    return false;
Sven Franck's avatar
Sven Franck committed
167
  };
168

169 170 171 172 173 174 175 176
  /**
   * Get element position in array
   * @method getPositionInArray
   * @param  {object} indices The index file
   * @param  {object} indices The index file
   * @returns {number} i Position of element in array
   */
  priv.getPositionInArray = function (element, array) {
177 178
    var i, l = array.length;
    for (i = 0; i < l; i += 1) {
179 180 181 182 183 184 185
      if (array[i] === element) {
        return i;
      }
    }
    return null;
  };

186 187
  /**
   * Find id in indices
188
   * @method isDocidInIndex
189 190 191 192
   * @param  {object} indices The file containing the indeces
   * @param  {object} doc The document which should be added to the index
   * @return {boolean} true/false
   */
193
  priv.isDocidInIndex = function (indices, doc) {
194
    var index, i, j, label, l = priv.indices.length;
195 196 197

    // loop indices
    for (i = 0; i < l; i += 1) {
198 199
      index = {};
      index.reference = priv.indices[i];
200 201 202 203 204 205
      index.reference_size = index.reference.fields.length;
      index.current = indices[index.reference.name];

      for (j = 0; j < index.reference_size; j += 1) {
        label = index.reference.fields[j];
        index.current_size = priv.getObjectSize(index.current[label]);
206

207 208 209 210 211
        // check for existing entries to remove (put-update)
        if (index.current_size > 0) {
          if (priv.searchIndexByValue(index.current[label], doc._id, "bool")) {
            return true;
          }
Sven Franck's avatar
Sven Franck committed
212
        }
Sven Franck's avatar
Sven Franck committed
213 214
      }
    }
215
    return false;
Sven Franck's avatar
Sven Franck committed
216 217 218 219 220 221 222 223 224
  };

  /**
   * Clean up indexes when removing a file
   * @method cleanIndices
   * @param  {object} indices The file containing the indeces
   * @param  {object} doc The document which should be added to the index
   * @return {object} indices The cleaned up file
   */
225
  priv.cleanIndices = function (indices, doc) {
226
    var i, j, k, index, key, label, l = priv.indices.length;
227 228 229 230 231

    // loop indices (indexA, indexAB...)
    for (i = 0; i < l; i += 1) {
      index = {};
      index.reference = priv.indices[i];
232
      index.reference_size = index.reference.fields.length;
Sven Franck's avatar
Sven Franck committed
233
      index.current = indices[index.reference.name];
234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251

      // loop index fields
      for (j = 0; j < index.reference_size; j += 1) {
        label = index.reference.fields[j];
        index.current_size = priv.getObjectSize(index.current[label]);

        // loop field entries
        for (k = 0; k < index.current_size; k += 1) {
          key = priv.searchIndexByValue(index.current[label], doc._id, "key");
          index.result_array = index.current[label][key];
          if (!!key) {
            // if there is more than one docid in the result array,
            // just remove this one and not the whole array
            if (index.result_array.length > 1) {
              index.result_array.splice(k, 1);
            } else {
              delete index.current[label][key];
            }
252 253 254 255 256
          }
        }
      }
    }
    return indices;
Sven Franck's avatar
Sven Franck committed
257
  };
258 259 260 261 262 263
  /**
   * Adds entries to indices
   * @method createEmptyIndexArray
   * @param  {object} indices The file containing the indeces
   * @param  {object} doc The document which should be added to the index
   */
264
  priv.updateIndices = function (indices, doc) {
265
    var i, j, index, value, label, key, l = priv.indices.length;
266 267 268

    // loop indices
    for (i = 0; i < l; i += 1) {
269 270 271
      index = {};
      index.reference = priv.indices[i];
      index.reference_size = index.reference.fields.length;
Sven Franck's avatar
Sven Franck committed
272
      index.current = indices[index.reference.name];
273

274 275
      // build array of values to create entries in index
      for (j = 0; j < index.reference_size; j += 1) {
Sven Franck's avatar
Sven Franck committed
276
        label = index.reference.fields[j];
277
        value = doc[label];
278
        if (value !== undefined) {
279
          index.current_size = priv.getObjectSize(index.current[label]);
280

281
          // check for existing entries to remove (put-update)
282
          if (index.current_size > 0) {
283 284 285 286 287
            key = priv.searchIndexByValue(
              index.current[label],
              doc._id,
              "key"
            );
288
            if (!!key) {
289
              delete index.current[label][key];
290 291
            }
          }
292 293
          if (index.current[label][value] === undefined) {
            index.current[label][value] = [];
294
          }
295 296
          // add a new entry
          index.current[label][value].push(doc._id);
297 298 299 300
        }
      }
    }
    return indices;
Sven Franck's avatar
Sven Franck committed
301 302
  };

303
  /**
304 305
   * Check available indices to find the best one.
   * TODOS: NOT NICE, redo
306
   * @method findBestIndexForQuery
307
   * @param  {object} syntax of query
308 309
   * @returns {object} response The query object constructed from Index file
   */
310 311 312
  priv.findBestIndexForQuery = function (syntax) {
    var i, j, k, l, n, p, o, element, key, block,
      search_ids, use_index = [], select_ids = {}, index, query_param,
313 314 315 316
      current_query, current_query_size;

    // try to parse into object
    if (syntax.query !== undefined) {
317
      current_query = jIO.ComplexQueries.parse(syntax.query);
318 319 320 321
    } else {
      current_query = {};
      current_query_size = 0;
    }
Sven Franck's avatar
Sven Franck committed
322

323 324
    // loop indices
    for (i = 0; i < priv.indices.length; i += 1) {
325 326
      search_ids = [];
      block = false;
327 328 329
      index = {};
      index.reference = priv.indices[i];
      index.reference_size = index.reference.fields.length;
330

331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353
      if (current_query_size !== 0) {
        // rebuild search_ids for iteration
        if (current_query.query_list === undefined) {
          search_ids.push(current_query.id);
        } else {
          for (j = 0; j < current_query.query_list.length; j += 1) {
            if (priv.getPositionInArray(current_query.query_list[j].id,
                search_ids) === null) {
              search_ids.push(current_query.query_list[j].id);
            }
          }
        }

        // loop search ids and find matches in index
        for (k = 0; k < search_ids.length; k += 1) {
          query_param = search_ids[0];
          for (l = 0; l < index.reference_size; l += 1) {
            if (query_param === index.reference.fields[l]) {
              search_ids.splice(
                priv.getPositionInArray(query_param, search_ids),
                1
              );
            }
354 355 356
          }
        }
      }
357

358 359 360 361 362 363 364
      // rebuild select_ids
      for (o = 0; o < syntax.filter.select_list.length; o += 1) {
        element = syntax.filter.select_list[o];
        select_ids[element] = true;
      }

      // search_ids empty  = all needed search fields found on index
365
      if (search_ids.length === 0) {
366 367
        p = priv.getObjectSize(select_ids);
        if (p === 0) {
368 369 370 371 372 373
          use_index.push({
            "name": index.reference.name,
            "search": true,
            "results": false
          });
        } else {
374 375 376 377 378 379 380 381 382 383 384
          for (n = 0; n < index.reference_size; n += 1) {
            delete select_ids[index.reference.fields[n]];
          }
          for (key in select_ids) {
            if (select_ids.hasOwnProperty(key)) {
              use_index.push({
                "name": index.reference.name,
                "search": true,
                "results": false
              });
              block = true;
385 386
            }
          }
387
          if (block === false) {
388 389 390 391 392 393 394 395 396 397 398 399 400 401
            use_index.push({
              "name": index.reference.name,
              "search": true,
              "results": true
            });
          }
        }
      }
    }
    return use_index;
  };

  /**
   * Converts the indices file into an object usable by complex queries
402
   * @method constructQueryObject
403 404 405
   * @param  {object} indices The index file
   * @returns {object} response The query object constructed from Index file
   */
406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473
  priv.constructQueryObject = function (indices, query_syntax) {
    var j, k, l, m, n, use_index, index,
      index_name, field_names, field, key, element,
      query_index, query_object = [], field_name,
      entry;

    // returns index-to-use|can-do-query|can-do-query-and-results
    use_index = priv.findBestIndexForQuery(query_syntax);

    if (use_index.length > 0) {
      for (j = 0; j < use_index.length; j += 1) {
        index = use_index[j];

        // NOTED: the index could be used to:
        // (a) get all document ids matching query
        // (b) get all document ids and results (= run complex query on index)
        // right now, only (b) is supported, because the complex query is
        // a single step process. If it was possible to first get the 
        // relevant document ids, then get the results, the index could be
        // used to do the first step plus use GET on the returned documents
        if (index.search && index.results) {
          index_name = use_index[j].name;
          query_index = indices[index_name];

          // get fieldnames from this index
          for (k = 0; k < priv.indices.length; k += 1) {
            if (priv.indices[k].name === use_index[j].name) {
              field_names = priv.indices[k].fields;
            }
          }
          for (l = 0; l < field_names.length; l += 1) {
            field_name = field_names[l];
            // loop entries for this field name
            field = query_index[field_name];
            for (key in field) {
              if (field.hasOwnProperty(key)) {
                element = field[key];
                // key can be "string" or "number" right now
                if (priv.field_types[field_name] === "number") {
                  key = +key;
                }
                for (m = 0; m < element.length; m += 1) {
                  if (priv.searchIndexByValue(
                      query_object,
                      element[m],
                      "bool"
                    )) {
                    // loop object
                    for (n = 0; n < query_object.length; n += 1) {
                      entry = query_object[n];
                      if (entry.id === element[m]) {
                        entry[field_name] = key;
                      }
                    }
                  } else {
                    entry = {};
                    entry.id = element[m];
                    entry[field_name] = key;
                    query_object.push(entry);
                  }
                }
              }
            }
          }
        }
      }
    }
    return query_object;
Sven Franck's avatar
Sven Franck committed
474
  };
475 476 477 478 479 480 481 482 483
  /**
   * Build the alldocs response from the index file (overriding substorage)
   * @method allDocsResponseFromIndex
   * @param  {object} command The JIO command
   * @param  {boolean} include_docs Whether to also supply the document
   * @param  {object} option The options set for this method
   * @returns {object} response The allDocs response
   */
  priv.allDocsResponseFromIndex = function (indices, include_docs, option) {
Sven Franck's avatar
Sven Franck committed
484
    var i, j, k, m, n = 0, l = priv.indices.length,
485
      index, key, obj, prop, found, file, label,
Sven Franck's avatar
Sven Franck committed
486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508
      unique_count = 0, unique_docids = [], all_doc_response = {},
      success = function (content) {
        file = { value: {} };
        file.id = unique_docids[n];
        file.key = unique_docids[n];
        file.doc = content;
        all_doc_response.rows.push(file);
        // async counter, must be in callback
        n += 1;
        if (n === unique_count) {
          that.success(all_doc_response);
        }
      },
      error = function () {
        that.error({
          "status": 404,
          "statusText": "Not Found",
          "error": "not_found",
          "message": "Cannot find the document",
          "reason": "Cannot get a document from substorage"
        });
        return;
      };
509 510 511 512 513

    // loop indices
    for (i = 0; i < l; i += 1) {
      index = {};
      index.reference = priv.indices[i];
514
      index.reference_size = index.reference.fields.length;
Sven Franck's avatar
Sven Franck committed
515
      index.current = indices[index.reference.name];
516 517

      // a lot of loops, not sure this is the fastest way
518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539
      // loop index fields
      for (j = 0; j < index.reference_size; j += 1) {
        label = index.reference.fields[j];
        index.current_field = index.current[label];
        index.current_size = priv.getObjectSize(index.current_field);

        // loop field id array
        for (j = 0; j < index.current_size; j += 1) {
          for (key in index.current_field) {
            if (index.current_field.hasOwnProperty(key)) {
              obj = index.current_field[key];
              for (prop in obj) {
                if (obj.hasOwnProperty(prop)) {
                  for (k = 0; k < unique_docids.length; k += 1) {
                    if (obj[prop] === unique_docids[k]) {
                      found = true;
                      break;
                    }
                  }
                  if (!found) {
                    unique_docids.push(obj[prop]);
                    unique_count += 1;
Sven Franck's avatar
Sven Franck committed
540 541
                  }
                }
542 543 544 545 546 547
              }
            }
          }
        }
      }
    }
548

549 550 551 552 553 554 555 556 557 558 559
    // construct allDocs response
    all_doc_response.total_rows = unique_count;
    all_doc_response.rows = [];
    for (m = 0; m < unique_count; m += 1) {
      // include_docs
      if (include_docs) {
        that.addJob(
          "get",
          priv.substorage,
          unique_docids[m],
          option,
Sven Franck's avatar
Sven Franck committed
560 561
          success,
          error
562 563 564 565 566 567
        );
      } else {
        file = { value: {} };
        file.id = unique_docids[m];
        file.key = unique_docids[m];
        all_doc_response.rows.push(file);
Sven Franck's avatar
Sven Franck committed
568
        if (m === (unique_count - 1)) {
569 570 571 572 573 574
          return all_doc_response;
        }
      }
    }
  };

575 576 577 578
  /**
   * Post document to substorage and create/update index file(s)
   * @method post
   * @param  {object} command The JIO command
579
   * @param  {string} source The source of the function call
580
   */
581
  priv.postOrPut = function (command, source) {
582
    var f = {}, indices, doc;
583
    doc = command.cloneDoc();
584
    if (typeof doc._id !== "string") {
585 586 587 588 589 590 591
      doc._id = priv.generateUuid();
    }
    f.getIndices = function () {
      var option = command.cloneOption();
      that.addJob(
        "get",
        priv.substorage,
592
        {"_id": priv.index_suffix},
593 594 595 596 597 598 599 600
        option,
        function (response) {
          indices = response;
          f.postDocument("put");
        },
        function (err) {
          switch (err.status) {
          case 404:
601 602 603 604 605 606 607 608 609 610 611
            if (source !== 'PUTATTACHMENT') {
              indices = priv.createEmptyIndexArray();
              f.postDocument("post");
            } else {
              that.error({
                "status": 404,
                "statusText": "Not Found",
                "error": "not found",
                "message": "Document not found",
                "reason": "Document not found"
              });
Sven Franck's avatar
Sven Franck committed
612
              return;
613
            }
614 615 616 617 618 619 620 621 622 623
            break;
          default:
            err.message = "Cannot retrieve index array";
            that.error(err);
            break;
          }
        }
      );
    };
    f.postDocument = function (index_update_method) {
624
      if (priv.isDocidInIndex(indices, doc) && source === 'POST') {
625 626 627 628 629 630 631 632 633
        // POST the document already exists
        that.error({
          "status": 409,
          "statusText": "Conflicts",
          "error": "conflicts",
          "message": "Cannot create a new document",
          "reason": "Document already exists"
        });
        return;
Sven Franck's avatar
Sven Franck committed
634 635 636 637 638 639 640 641 642 643 644 645 646 647 648
      }
      if (source !== 'PUTATTACHMENT') {
        indices = priv.updateIndices(indices, doc);
      }
      that.addJob(
        source === 'PUTATTACHMENT' ? "putAttachment" : "post",
        priv.substorage,
        doc,
        command.cloneOption(),
        function () {
          if (source !== 'PUTATTACHMENT') {
            f.sendIndices(index_update_method);
          } else {
            that.success({
              "ok": true,
649 650
              "id": doc._id,
              "attachment": doc._attachment
Sven Franck's avatar
Sven Franck committed
651 652 653 654 655 656 657
            });
          }
        },
        function (err) {
          switch (err.status) {
          case 409:
            // file already exists
658 659 660 661 662
            if (source !== 'PUTATTACHMENT') {
              f.sendIndices(index_update_method);
            } else {
              that.success({
                "ok": true,
663
                "id": doc._id
664 665
              });
            }
Sven Franck's avatar
Sven Franck committed
666 667 668 669 670
            break;
          default:
            err.message = "Cannot upload document";
            that.error(err);
            break;
671
          }
Sven Franck's avatar
Sven Franck committed
672 673
        }
      );
674 675 676 677 678 679 680 681 682 683 684
    };
    f.sendIndices = function (method) {
      indices._id = priv.index_suffix;
      that.addJob(
        method,
        priv.substorage,
        indices,
        command.cloneOption(),
        function () {
          that.success({
            "ok": true,
685
            "id": doc._id
686 687 688 689 690 691 692 693 694 695
          });
        },
        function (err) {
          // xxx do we try to delete the posted document ?
          err.message = "Cannot save index file";
          that.error(err);
        }
      );
    };
    f.getIndices();
Sven Franck's avatar
Sven Franck committed
696 697
  };

698 699 700 701 702
  /**
   * Update the document metadata and update the index
   * @method put
   * @param  {object} command The JIO command
   */
703
  that.post = function (command) {
704
    priv.postOrPut(command, 'POST');
705
  };
706

707
  /**
708 709 710 711 712
   * Update the document metadata and update the index
   * @method put
   * @param  {object} command The JIO command
   */
  that.put = function (command) {
713
    priv.postOrPut(command, 'PUT');
Sven Franck's avatar
Sven Franck committed
714
  };
715

716
  /**
717 718 719 720 721
   * Add an attachment to a document (no index modification)
   * @method putAttachment
   * @param  {object} command The JIO command
   */
  that.putAttachment = function (command) {
722
    priv.postOrPut(command, 'PUTATTACHMENT');
Sven Franck's avatar
Sven Franck committed
723
  };
724

725
  /**
726
   * Get the document metadata
727 728 729 730
   * @method get
   * @param  {object} command The JIO command
   */
  that.get = function (command) {
731
    that.addJob(
732 733
      "get",
      priv.substorage,
734 735
      command.cloneDoc(),
      command.cloneOption(),
736 737
      function (response) {
        that.success(response);
Sven Franck's avatar
Sven Franck committed
738
      },
739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760
      function (err) {
        that.error(err);
      }
    );
  };

  /**
   * Get the attachment.
   * @method getAttachment
   * @param  {object} command The JIO command
   */
  that.getAttachment = function (command) {
    that.addJob(
      "getAttachment",
      priv.substorage,
      command.cloneDoc(),
      command.cloneOption(),
      function (response) {
        that.success(response);
      },
      function (err) {
        that.error(err);
761 762 763
      }
    );
  };
764 765

  /**
766
   * Remove document - removing documents updates index!.
767
   * @method remove
768 769
   * @param  {object} command The JIO command
   */
770
  that.remove = function (command) {
771 772 773 774 775 776 777 778 779
    var f = {}, indices, doc, docid, option;

    doc = command.cloneDoc();
    option = command.cloneOption();

    f.removeDocument = function (type) {
      that.addJob(
        "remove",
        priv.substorage,
780
        doc,
781 782 783 784
        option,
        function (response) {
          that.success(response);
        },
Sven Franck's avatar
Sven Franck committed
785
        function () {
786 787 788 789 790 791 792 793 794 795 796 797 798 799
          that.error({
            "status": 409,
            "statusText": "Conflict",
            "error": "conflict",
            "message": "Document Update Conflict",
            "reason": "Could not delete document or attachment"
          });
        }
      );
    };
    f.getIndices = function () {
      that.addJob(
        "get",
        priv.substorage,
800
        {"_id": priv.index_suffix},
801 802 803
        option,
        function (response) {
          // if deleting an attachment
Sven Franck's avatar
Sven Franck committed
804 805
          if (typeof command.getAttachmentId() === 'string') {
            f.removeDocument('attachment');
806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824
          } else {
            indices = priv.cleanIndices(response, doc);
            // store update index file
            that.addJob(
              "put",
              priv.substorage,
              indices,
              command.cloneOption(),
              function () {
                // remove actual document
                f.removeDocument('doc');
              },
              function (err) {
                err.message = "Cannot save index file";
                that.error(err);
              }
            );
          }
        },
Sven Franck's avatar
Sven Franck committed
825
        function () {
826 827 828 829 830 831 832
          that.error({
            "status": 404,
            "statusText": "Not Found",
            "error": "not_found",
            "message": "Document index not found, please check document ID",
            "reason": "Incorrect document ID"
          });
Sven Franck's avatar
Sven Franck committed
833
          return;
834 835 836 837
        }
      );
    };
    f.getIndices();
838
  };
839

840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895
  /**
   * Remove document - removing documents updates index!.
   * @method remove
   * @param  {object} command The JIO command
   */
  that.removeAttachment = function (command) {
    var f = {}, indices, doc, docid, option;
    doc = command.cloneDoc();
    option = command.cloneOption();
    f.removeDocument = function (type) {
      that.addJob(
        "removeAttachment",
        priv.substorage,
        doc,
        option,
        that.success,
        that.error
      );
    };
    f.getIndices = function () {
      that.addJob(
        "get",
        priv.substorage,
        {"_id": priv.index_suffix},
        option,
        function (response) {
          // if deleting an attachment
          if (typeof command.getAttachmentId() === 'string') {
            f.removeDocument('attachment');
          } else {
            indices = priv.cleanIndices(response, doc);
            // store update index file
            that.addJob(
              "put",
              priv.substorage,
              indices,
              command.cloneOption(),
              function () {
                // remove actual document
                f.removeDocument('doc');
              },
              function (err) {
                err.message = "Cannot save index file";
                that.error(err);
              }
            );
          }
        },
        function (err) {
          that.error(err);
        }
      );
    };
    f.getIndices();
  };

896 897 898 899
  /**
   * Gets a document list from the substorage
   * Options:
   * - {boolean} include_docs Also retrieve the actual document content.
Sven Franck's avatar
Sven Franck committed
900
   * @method allDocs
901
   * @param  {object} command The JIO command
Sven Franck's avatar
Sven Franck committed
902
   */
903 904 905 906 907 908 909 910 911 912 913 914
  //{
  // "total_rows": 4,
  // "rows": [
  //    {
  //    "id": "otherdoc",
  //    "key": "otherdoc",
  //    "value": {
  //      "rev": "1-3753476B70A49EA4D8C9039E7B04254C"
  //    }
  //  },{...}
  // ]
  //}
Sven Franck's avatar
Sven Franck committed
915
  that.allDocs = function (command) {
916 917
    var f = {}, option, all_docs_response, query_object, query_syntax,
      query_response;
918 919 920 921 922 923
    option = command.cloneOption();

    f.getIndices = function () {
      that.addJob(
        "get",
        priv.substorage,
924
        {"_id": priv.index_suffix},
925 926
        option,
        function (response) {
927 928
          query_syntax = command.getOption('query');
          if (query_syntax !== undefined) {
929 930 931 932 933 934 935 936
            // build complex query object
            query_object = priv.constructQueryObject(response, query_syntax);
            if (query_object.length === 0) {
              that.addJob(
                "allDocs",
                priv.substorage,
                undefined,
                option,
937 938
                that.success,
                that.error
939 940 941 942
              );
            } else {
              // we can use index, run query on index
              query_response =
943
                jIO.ComplexQueries.query(query_syntax, query_object);
944 945
              that.success(query_response);
            }
946
          } else if (command.getOption('include_docs')) {
947 948 949 950
            priv.allDocsResponseFromIndex(response, true, option);
          } else {
            all_docs_response =
              priv.allDocsResponseFromIndex(response, false, option);
Sven Franck's avatar
Sven Franck committed
951
            that.success(all_docs_response);
952 953
          }
        },
954
        that.error
955 956 957 958
      );
    };
    f.getIndices();
  };
Sven Franck's avatar
Sven Franck committed
959
  return that;
960
});