1
0
Fork 0

Merge branch 'devel' of https://github.com/arangodb/arangodb into devel

This commit is contained in:
Jan Steemann 2015-10-27 16:16:57 +01:00
commit da8519b1a2
7 changed files with 273 additions and 45 deletions

View File

@ -307,6 +307,10 @@ The following query will then use the array index:
FILTER 'foobar' IN doc.tags[*].name
RETURN doc
If you store a document having the array which does contain elements not having
the subattributes this document will also be indexed with the value `null`, which
in ArangoDB is equal to attribute not existing.
ArangoDB supports creating array indexes with a single <i>[\*]</i> operator per index
attribute. For example, creating an index as follows is not supported:
@ -322,8 +326,46 @@ value `bar` will be inserted only once:
db.posts.insert({ tags: [ "foobar", "bar", "bar" ] });
```
If an array index is declared unique, the de-duplication of array values will happen before
If an array index is declared **unique**, the de-duplication of array values will happen before
inserting the values into the index, so the above insert operation will not necessarily fail.
It will fail if the index already contains an instance of the `bar` value, but will succeed
if the value `bar` is not already present in the index.
If an array index is declared and you store documents that do not have an array at the specified attribute
this document will not be inserted in the index. Hence the following objects will not be indexed:
```js
db.posts.ensureIndex({ type: "hash", fields: [ "tags[*]" ] });
db.posts.insert({ something: "else" });
db.posts.insert({ tags: null });
db.posts.insert({ tags: "this is no array" });
db.posts.insert({ tags: { content: [1, 2, 3] } });
```
An array index is able to index an explicit `null` value and when queried for it, it will only
return those documents having explicitly `null` stored in the array, it will not return any
documents that do not have the array at all.
```js
db.posts.ensureIndex({ type: "hash", fields: [ "tags[*]" ] });
db.posts.insert({tags: null}) // Will not be indexed
db.posts.insert({tags: []}) // Will not be indexed
db.posts.insert({tags: [null]}); // Will be indexed for null
db.posts.insert({tags: [null, 1, 2]}); // Will be indexed for null, 1 and 2
```
Declaring an array index as **sparse** does not have an effect on the array part of the index,
this in particular means that explicit `null` values are also indexed in the **sparse** version.
If an index is combined from an array and a normal attribute the sparsity will apply for the attribute e.g.:
```js
db.posts.ensureIndex({ type: "hash", fields: [ "tags[*]", "name" ], sparse: true });
db.posts.insert({tags: null, name: "alice"}) // Will not be indexed
db.posts.insert({tags: [], name: "alice"}) // Will not be indexed
db.posts.insert({tags: [1, 2, 3]}) // Will not be indexed
db.posts.insert({tags: [1, 2, 3], name: null}) // Will not be indexed
db.posts.insert({tags: [1, 2, 3], name: "alice"})
// Will be indexed for [1, "alice"], [2, "alice"], [3, "alice"]
db.posts.insert({tags: [null], name: "bob"})
// Will be indexed for [null, "bob"]
```

View File

@ -48,11 +48,18 @@
* https://coreos.com/using-coreos/etcd/
* [Apache 2 License](https://github.com/coreos/etcd/blob/master/LICENSE)
### autotools
* http://www.gnu.org/software/autoconf/autoconf.html
* https://www.gnu.org/software/automake/
* only used to generate code, not part of the distribution
* parts generated are free as-is license
### Bison
* https://www.gnu.org/software/bison/
* only used to generate code, not part of the distribution
* parts used see https://github.com/arangodb/arangodb/blob/devel/arangod/Aql/grammar.cpp#L20
* parts generated use see https://github.com/arangodb/arangodb/blob/devel/arangod/Aql/grammar.cpp#L20
### Flex

View File

@ -568,9 +568,11 @@ bool Index::canUseConditionPart (triagens::aql::AstNode const* access,
return false;
}
/* A sparse index will store null in Array
if (access->isNullValue()) {
return false;
}
*/
}
else if (op->type == triagens::aql::NODE_TYPE_OPERATOR_BINARY_IN &&
access->type == triagens::aql::NODE_TYPE_EXPANSION) {
@ -579,9 +581,11 @@ bool Index::canUseConditionPart (triagens::aql::AstNode const* access,
return false;
}
/* A sparse index will store null in Array
if (other->isNullValue()) {
return false;
}
*/
}
else if (access->type == triagens::aql::NODE_TYPE_ATTRIBUTE_ACCESS) {
// a.b == value OR a.b IN values

View File

@ -310,13 +310,14 @@ void PathBasedIndex::buildIndexValues (TRI_shaped_json_t const* documentShape,
if (! check || shape == nullptr || shapedJson._sid == BasicShapes::TRI_SHAPE_SID_NULL) {
// attribute not, found
bool expandAnywhere = false;
for (size_t k = 0; k < n; ++k) {
size_t k = 0;
for (; k < n; ++k) {
if (_paths[level][k].second) {
expandAnywhere = true;
break;
}
}
if (expandAnywhere && (i < n - 1 || ! check || shape == nullptr ) ) {
if (expandAnywhere && i <= k) {
// We have an array index and we are not evaluating the indexed attribute.
// Do not index this attribute at all
if (level == 0) {

View File

@ -602,10 +602,18 @@
);
if (graph) {
$('.modal-body table').css('border-collapse', 'separate');
var i;
$('.modal-body .spacer').remove();
for (i = 0; i <= this.counter; i++) {
$('#row_fromCollections' + i).show();
$('#row_toCollections' + i).show();
$('#row_newEdgeDefinitions' + i).addClass('first');
$('#row_fromCollections' + i).addClass('middle');
$('#row_toCollections' + i).addClass('last');
$('#row_toCollections' + i).after('<tr id="spacer'+ i +'" class="spacer"></tr>');
}
}
@ -663,6 +671,17 @@
});
window.modalView.undelegateEvents();
window.modalView.delegateEvents(this.events);
var i;
$('.modal-body .spacer').remove();
for (i = 0; i <= this.counter; i++) {
$('#row_fromCollections' + i).show();
$('#row_toCollections' + i).show();
$('#row_newEdgeDefinitions' + i).addClass('first');
$('#row_fromCollections' + i).addClass('middle');
$('#row_toCollections' + i).addClass('last');
$('#row_toCollections' + i).after('<tr id="spacer'+ i +'" class="spacer"></tr>');
}
return;
}
if (id.indexOf("remove_newEdgeDefinitions") !== -1 ) {
@ -670,6 +689,7 @@
$('#row_newEdgeDefinitions' + number).remove();
$('#row_fromCollections' + number).remove();
$('#row_toCollections' + number).remove();
$('#spacer' + number).remove();
}
},

View File

@ -114,6 +114,56 @@
line-height: 17px;
}
tr {
&.spacer {
height: 10px;
}
&.first {
background-color: $c-lightgreen-2-bg;
th:first-child {
border-top-left-radius: 3px;
}
th:last-child {
border-top-right-radius: 3px;
}
}
&.middle {
background-color: $c-lightgreen-2-bg;
padding-left: 10px;
padding-right: 10px;
}
&.last {
background-color: $c-lightgreen-2-bg;
th:first-child {
border-bottom-left-radius: 3px;
}
th:last-child {
border-bottom-right-radius: 3px;
}
}
&.first,
&.middle,
&.last {
th:first-child {
padding-left: 10px;
}
th:last-child {
padding-right: 10px;
}
}
}
th {
%cell-centered {
text-align: center;
@ -267,6 +317,7 @@
border: 0 !important;
border-radius: 3px !important;
box-shadow: 0;
width: 580px;
.fade.in {
top: 12.1% !important;

View File

@ -30,6 +30,7 @@
var jsunity = require("jsunity");
var db = require("org/arangodb").db;
var isCluster = require("org/arangodb/cluster").isCluster();
////////////////////////////////////////////////////////////////////////////////
/// @brief test suite
@ -84,7 +85,7 @@ function arrayIndexSuite () {
"index used for: " + query);
};
var validateResults = function (query, sparse) {
var validateResults = function (query) {
var bindVars = {};
bindVars.tag = "tenth";
@ -113,12 +114,7 @@ function arrayIndexSuite () {
}
bindVars.tag = null;
if (!sparse) {
checkIsOptimizedQuery(query, bindVars);
}
else {
validateIndexNotUsed(query, bindVars);
}
checkIsOptimizedQuery(query, bindVars);
actual = AQL_EXECUTE(query, bindVars);
// We check if we found the Arrays with NULL in it
assertNotEqual(-1, actual.json.indexOf("0t"), "Did not find the null array");
@ -316,9 +312,9 @@ function arrayIndexSuite () {
col.save({_key: "noArray", a: "NoArray"});
col.save({_key: "null", a: null});
const query = `FOR x IN ${cName} FILTER @tag IN x.a[*] SORT x._key RETURN x._key`;
validateResults(query, true);
validateResults(query);
const orQuery = `FOR x IN ${cName} FILTER @tag1 IN x.a[*] || @tag2 IN x.a[*] SORT x._key RETURN x._key`;
validateResultsOr(orQuery, true);
validateResultsOr(orQuery);
},
testSkiplistPlainArray : function () {
@ -429,9 +425,9 @@ function arrayIndexSuite () {
col.save({_key: "noArray", a: "NoArray"});
col.save({_key: "null", a: null});
const query = `FOR x IN ${cName} FILTER @tag IN x.a[*] SORT x._key RETURN x._key`;
validateResults(query, true);
validateResults(query);
const orQuery = `FOR x IN ${cName} FILTER @tag1 IN x.a[*] || @tag2 IN x.a[*] SORT x._key RETURN x._key`;
validateResultsOr(orQuery, true);
validateResultsOr(orQuery);
}
};
@ -447,15 +443,17 @@ function arrayIndexNonArraySuite () {
var allIndexes = col.getIndexes(true);
assertEqual(allIndexes.length, 2, "We have more than one index!");
var idx = allIndexes[1];
switch (idx.type) {
case "hash":
assertEqual(idx.figures.totalUsed, count);
break;
case "skiplist":
assertEqual(idx.figures.nrUsed, count);
break;
default:
assertTrue(false, "Unexpected index type");
if (! isCluster) {
switch (idx.type) {
case "hash":
assertEqual(idx.figures.totalUsed, count);
break;
case "skiplist":
assertEqual(idx.figures.nrUsed, count);
break;
default:
assertTrue(false, "Unexpected index type");
}
}
};
@ -616,13 +614,16 @@ function arrayIndexNonArraySuite () {
col.save({ a: [], b: 1, c: 1 }); // Empty Array. no indexing
checkElementsInIndex(inserted);
col.save({ a: [1, 2, 3, 3, 2, 1] }); // a does not have any nested value. Handled equal to a: []
col.save({ a: [1, 2, 3, 3, 2, 1] }); // a does not have any nested value. Index as one null
inserted += 1;
checkElementsInIndex(inserted);
col.save({ a: [1, 2, 3, 3, 2, 1], b: 1 }); // a does not have any nested value. Handled equal to a: []
col.save({ a: [1, 2, 3, 3, 2, 1], b: 1 }); // a does not have any nested value. Index as one null
inserted += 1;
checkElementsInIndex(inserted);
col.save({ a: [1, 2, 3, 3, 2, 1], b: 1, c: 1 }); // a does not have any nested value. Handled equal to a: []
col.save({_key: "null1", a: [1, 2, 3, 3, 2, 1], b: 1, c: 1 }); // a does not have any nested value. Index as one null
inserted += 1;
checkElementsInIndex(inserted);
col.save({ a: [{d: 1}, {d: 2}, {d: 3}, {d: 3}, {d: 2}, {d: 1}] });
@ -637,23 +638,24 @@ function arrayIndexNonArraySuite () {
inserted += 3; // We index b: 1, c: 1 and 3 values for a[*].d
checkElementsInIndex(inserted);
col.save({_key: "null1", a: [{d: null}, {d: "a"}, {d: "b"}, {d: "c"}, {d: "b"}, {d: "a"}, {d: null}] });
col.save({_key: "null2", a: [{d: null}, {d: "a"}, {d: "b"}, {d: "c"}, {d: "b"}, {d: "a"}, {d: null}] });
inserted += 4;
checkElementsInIndex(inserted); // b: null a: "a", "b", "c", null c:null
col.save({_key: "null2", a: [{d: null}, {d: "a"}, {d: "b"}, {d: "c"}, {d: "b"}, {d: "a"}, {d: null}], b: 1 });
col.save({_key: "null3", a: [{d: null}, {d: "a"}, {d: "b"}, {d: "c"}, {d: "b"}, {d: "a"}, {d: null}], b: 1 });
inserted += 4;
checkElementsInIndex(inserted);
col.save({_key: "null3", a: [{d: null}, {d: "a"}, {d: "b"}, {d: "c"}, {d: "b"}, {d: "a"}, {d: null}], b: 1, c: 1 });
col.save({_key: "null4", a: [{d: null}, {d: "a"}, {d: "b"}, {d: "c"}, {d: "b"}, {d: "a"}, {d: null}], b: 1, c: 1 });
inserted += 4;
checkElementsInIndex(inserted);
const query = `FOR x IN ${cName} FILTER @tag IN x.a[*].d && 1 == x.b && 1 == x.c SORT x._key RETURN x._key`;
var actual = AQL_EXECUTE(query, { tag : null }).json;
// We expect that we can only find the Array that has stored exactly the null value
assertEqual(actual.length, 1);
assertEqual(actual[0], "null3");
assertEqual(actual.length, 2);
assertEqual(actual[0], "null1");
assertEqual(actual[1], "null4");
},
testHashIndexSubAttributeArray : function () {
@ -667,6 +669,66 @@ function arrayIndexNonArraySuite () {
// Do not find anything
},
testHashIndexMultiArray : function () {
col.ensureHashIndex("a[*]", "b[*]");
col.save({a: [1, 2, 3]}); // Do not index
checkElementsInIndex(0);
col.save({a: [1, 2, 3], b: null}); // Do not index
checkElementsInIndex(0);
col.save({a: [1, 2, 3], b: "this is no array"}); // Do not index
checkElementsInIndex(0);
col.save({a: "this is no array", b: ["a", "b", "c"]}); // Do not index
checkElementsInIndex(0);
col.save({a: [1, 2, null, null, 2, 1], b: ["a", "b", null, "b", "a"]});
checkElementsInIndex(9); // 3*3 many combinations
const query = `FOR x IN ${cName} FILTER @tag IN x.a[*] && @tag IN x.b[*] SORT x._key RETURN x._key`;
var actual = AQL_EXECUTE(query, { tag : null }).json;
assertEqual(actual.length, 1);
},
testHashIndexArraySparse : function () {
col.ensureHashIndex("a[*]", "b", {sparse: true});
var inserted = 0;
col.save({a: [1, 2, 3]}); // Do not index, b is not set
checkElementsInIndex(inserted);
col.save({a: [1, 2, 3], b: null}); // Do not index, b is null
checkElementsInIndex(inserted);
col.save({a: [1, 2, 3], b: 1}); // Do index
inserted += 3;
checkElementsInIndex(inserted);
col.save({a: [null, 4], b: 1}); // Do index
inserted += 2;
checkElementsInIndex(inserted);
const query = `FOR x IN ${cName} FILTER null IN x.a[*] && 1 == x.b SORT x._key RETURN x._key`;
// We can use the index for null in SPARSE
var actual = AQL_EXECUTE(query).json;
assertEqual(actual.length, 1);
var plan = AQL_EXPLAIN(query).plan;
var nodeTypes = plan.nodes.map(function(node) {
return node.type;
});
assertNotEqual(-1, nodeTypes.indexOf("IndexNode"));
const query2 = `FOR x IN ${cName} FILTER null IN x.a[*] SORT x._key RETURN x._key`;
plan = AQL_EXPLAIN(query2).plan;
nodeTypes = plan.nodes.map(function(node) {
return node.type;
});
// Cannot use the index for sub attribute a
assertEqual(-1, nodeTypes.indexOf("IndexNode"));
},
testSkiplistSingleAttribute : function () {
col.ensureSkiplist("a[*]");
col.save({}); // a is not set
@ -876,16 +938,16 @@ function arrayIndexNonArraySuite () {
checkElementsInIndex(inserted);
col.save({ a: [1, 2, 3, 3, 2, 1] });
inserted += 1; // We index b: null But a does not have any nested value. Handled equal to a: []
inserted += 1; // We index b: null But a does not have any nested value. Index one for null
checkElementsInIndex(inserted);
col.save({ a: [1, 2, 3, 3, 2, 1], b: 1 }); // b: 1 a: 1,2,3 c: null
inserted += 1; // We index b: 1 But a does not have any nested value. Handled equal to a: []
col.save({ _key: "null1", a: [1, 2, 3, 3, 2, 1], b: 1 }); // b: 1 a: 1,2,3 c: null
inserted += 1; // We index b: 1 But a does not have any nested value. Index one for null
insertedB += 1;
checkElementsInIndex(inserted);
col.save({ a: [1, 2, 3, 3, 2, 1], b: 1, c: 1 });
inserted += 1; // We index b: 1, c: 1 But a does not have any nested value. Handled equal to a: []
col.save({ _key: "null2", a: [1, 2, 3, 3, 2, 1], b: 1, c: 1 });
inserted += 1; // We index b: 1, c: 1 But a does not have any nested value. Index one for null
insertedB += 1;
checkElementsInIndex(inserted);
@ -903,26 +965,29 @@ function arrayIndexNonArraySuite () {
insertedB += 3;
checkElementsInIndex(inserted);
col.save({_key: "null1", a: [{d: null}, {d: "a"}, {d: "b"}, {d: "c"}, {d: "b"}, {d: "a"}, {d: null}] });
col.save({_key: "null3", a: [{d: null}, {d: "a"}, {d: "b"}, {d: "c"}, {d: "b"}, {d: "a"}, {d: null}] });
inserted += 4;
checkElementsInIndex(inserted); // b: null a: "a", "b", "c", null c:null
col.save({_key: "null2", a: [{d: null}, {d: "a"}, {d: "b"}, {d: "c"}, {d: "b"}, {d: "a"}, {d: null}], b: 1 });
col.save({_key: "null4", a: [{d: null}, {d: "a"}, {d: "b"}, {d: "c"}, {d: "b"}, {d: "a"}, {d: null}], b: 1 });
inserted += 4;
insertedB += 4;
checkElementsInIndex(inserted);
col.save({_key: "null3", a: [{d: null}, {d: "a"}, {d: "b"}, {d: "c"}, {d: "b"}, {d: "a"}, {d: null}], b: 1, c: 1 });
col.save({_key: "null5", a: [{d: null}, {d: "a"}, {d: "b"}, {d: "c"}, {d: "b"}, {d: "a"}, {d: null}], b: 1, c: 1 });
inserted += 4;
insertedB += 4;
checkElementsInIndex(inserted);
const query = `FOR x IN ${cName} FILTER @tag IN x.a[*].d && 1 == x.b SORT x._key RETURN x._key`;
var actual = AQL_EXECUTE(query, { tag : null }).json;
// We expect that we can only find the Array that has stored exactly the null value
assertEqual(actual.length, 2);
assertEqual(actual[0], "null2");
assertEqual(actual[1], "null3");
// We expect that we can only find the array that stores exactly the null value
// And the arrays that do not have the sub attribute.
assertEqual(actual.length, 4);
assertEqual(actual[0], "null1");
assertEqual(actual[1], "null2");
assertEqual(actual[2], "null4");
assertEqual(actual[3], "null5");
const query2 = `FOR x IN ${cName} FILTER 1 == x.b RETURN x._key`;
actual = AQL_EXECUTE(query2).json;
@ -942,7 +1007,45 @@ function arrayIndexNonArraySuite () {
var actual = AQL_EXECUTE(query, { tag : null }).json;
assertEqual(actual.length, 0);
// Do not find anything
}
},
testSkiplistIndexArraySparse : function () {
col.ensureSkiplist("a[*]", "b", {sparse: true});
var inserted = 0;
col.save({a: [1, 2, 3]}); // Do not index, b is not set
checkElementsInIndex(inserted);
col.save({a: [1, 2, 3], b: null}); // Do not index, b is null
checkElementsInIndex(inserted);
col.save({a: [1, 2, 3], b: 1}); // Do index
inserted += 3;
checkElementsInIndex(inserted);
col.save({a: [null, 4], b: 1}); // Do index
inserted += 2;
checkElementsInIndex(inserted);
const query = `FOR x IN ${cName} FILTER null IN x.a[*] && 1 == x.b SORT x._key RETURN x._key`;
// We can use the index for null in SPARSE
var actual = AQL_EXECUTE(query).json;
assertEqual(actual.length, 1);
var plan = AQL_EXPLAIN(query).plan;
var nodeTypes = plan.nodes.map(function(node) {
return node.type;
});
assertNotEqual(-1, nodeTypes.indexOf("IndexNode"));
const query2 = `FOR x IN ${cName} FILTER null IN x.a[*] SORT x._key RETURN x._key`;
plan = AQL_EXPLAIN(query2).plan;
nodeTypes = plan.nodes.map(function(node) {
return node.type;
});
// Cannot use the index for sub attribute a
assertEqual(-1, nodeTypes.indexOf("IndexNode"));
},
};