//////////////////////////////////////////////////////////////////////////////// /// @brief V8-vocbase queries /// /// @file /// /// DISCLAIMER /// /// Copyright 2004-2012 triAGENS GmbH, Cologne, Germany /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. /// You may obtain a copy of the License at /// /// http://www.apache.org/licenses/LICENSE-2.0 /// /// Unless required by applicable law or agreed to in writing, software /// distributed under the License is distributed on an "AS IS" BASIS, /// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. /// See the License for the specific language governing permissions and /// limitations under the License. /// /// Copyright holder is triAGENS GmbH, Cologne, Germany /// /// @author Dr. Frank Celler /// @author Copyright 2011-2012, triAGENS GmbH, Cologne, Germany //////////////////////////////////////////////////////////////////////////////// #include #include "v8-query.h" #include "BasicsC/logging.h" #include "HashIndex/hashindex.h" #include "SkipLists/skiplistIndex.h" #include "V8/v8-conv.h" #include "V8/v8-utils.h" #include "V8Server/v8-vocbase.h" #include "VocBase/edge-collection.h" // ----------------------------------------------------------------------------- // --SECTION-- HELPER FUNCTIONS // ----------------------------------------------------------------------------- // ----------------------------------------------------------------------------- // --SECTION-- private types // ----------------------------------------------------------------------------- //////////////////////////////////////////////////////////////////////////////// /// @addtogroup VocBase /// @{ //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// /// @brief geo coordinate container, also containing the distance //////////////////////////////////////////////////////////////////////////////// typedef struct { double _distance; void const* _data; } geo_coordinate_distance_t; //////////////////////////////////////////////////////////////////////////////// /// @brief query types //////////////////////////////////////////////////////////////////////////////// typedef enum { QUERY_EXAMPLE, QUERY_CONDITION } query_t; //////////////////////////////////////////////////////////////////////////////// /// @} //////////////////////////////////////////////////////////////////////////////// // ----------------------------------------------------------------------------- // --SECTION-- private functions // ----------------------------------------------------------------------------- //////////////////////////////////////////////////////////////////////////////// /// @addtogroup VocBase /// @{ //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// /// @brief extracts skip and limit //////////////////////////////////////////////////////////////////////////////// static void ExtractSkipAndLimit (v8::Arguments const& argv, size_t pos, TRI_voc_ssize_t& skip, TRI_voc_size_t& limit) { skip = TRI_QRY_NO_SKIP; limit = TRI_QRY_NO_LIMIT; if (pos < (size_t) argv.Length() && ! argv[pos]->IsNull()) { skip = (TRI_voc_size_t) TRI_ObjectToDouble(argv[pos]); } if (pos + 1 < (size_t) argv.Length() && ! argv[pos + 1]->IsNull()) { limit = (TRI_voc_ssize_t) TRI_ObjectToDouble(argv[pos + 1]); } } //////////////////////////////////////////////////////////////////////////////// /// @brief calculates slice //////////////////////////////////////////////////////////////////////////////// static void CalculateSkipLimitSlice (size_t length, TRI_voc_ssize_t skip, TRI_voc_size_t limit, size_t& s, size_t& e) { s = 0; e = length; // skip from the beginning if (0 < skip) { s = skip; if (e < s) { s = e; } } // skip from the end else if (skip < 0) { skip = -skip; if ((size_t) skip < e) { s = e - skip; } } // apply limit if (s + limit < e) { e = s + limit; } } //////////////////////////////////////////////////////////////////////////////// /// @brief cleans up the example object //////////////////////////////////////////////////////////////////////////////// static void CleanupExampleObject (TRI_shaper_t* shaper, size_t n, TRI_shape_pid_t* pids, TRI_shaped_json_t** values) { // clean shaped json objects for (size_t j = 0; j < n; ++j) { TRI_FreeShapedJson(shaper, values[j]); } TRI_Free(TRI_UNKNOWN_MEM_ZONE, values); if (pids != 0) { TRI_Free(TRI_UNKNOWN_MEM_ZONE, pids); } } //////////////////////////////////////////////////////////////////////////////// /// @brief sets up the example object //////////////////////////////////////////////////////////////////////////////// static int SetupExampleObject (v8::Handle example, TRI_shaper_t* shaper, size_t& n, TRI_shape_pid_t*& pids, TRI_shaped_json_t**& values, v8::Handle* err) { // get own properties of example v8::Handle names = example->GetOwnPropertyNames(); n = names->Length(); // setup storage pids = (TRI_shape_pid_t*) TRI_Allocate(TRI_UNKNOWN_MEM_ZONE, n * sizeof(TRI_shape_pid_t), false); // TODO: memory allocation might fail values = (TRI_shaped_json_t**) TRI_Allocate(TRI_UNKNOWN_MEM_ZONE, n * sizeof(TRI_shaped_json_t*), false); // TODO: memory allocation might fail // convert for (size_t i = 0; i < n; ++i) { v8::Handle key = names->Get(i); v8::Handle val = example->Get(key); TRI_Utf8ValueNFC keyStr(TRI_UNKNOWN_MEM_ZONE, key); if (*keyStr != 0) { pids[i] = shaper->findAttributePathByName(shaper, *keyStr); values[i] = TRI_ShapedJsonV8Object(val, shaper); } if (*keyStr == 0 || pids[i] == 0 || values[i] == 0) { CleanupExampleObject(shaper, i, pids, values); if (*keyStr == 0) { *err = TRI_CreateErrorObject(TRI_ERROR_BAD_PARAMETER, "cannot convert attribute path to UTF8"); return TRI_ERROR_BAD_PARAMETER; } else if (pids[i] == 0) { *err = TRI_CreateErrorObject(TRI_ERROR_BAD_PARAMETER, "cannot convert to attribute path"); return TRI_ERROR_BAD_PARAMETER; } else { *err = TRI_CreateErrorObject(TRI_ERROR_BAD_PARAMETER, "cannot convert value to JSON"); return TRI_ERROR_BAD_PARAMETER; } assert(false); } } return TRI_ERROR_NO_ERROR; } //////////////////////////////////////////////////////////////////////////////// /// @brief sets up the skiplist operator for a skiplist condition query //////////////////////////////////////////////////////////////////////////////// static TRI_index_operator_t* SetupConditionsSkiplist (TRI_index_t* idx, TRI_shaper_t* shaper, v8::Handle conditions) { TRI_index_operator_t* lastOperator = 0; TRI_json_t* parameters = TRI_CreateListJson(TRI_UNKNOWN_MEM_ZONE); size_t numEq = 0; size_t lastNonEq = 0; if (parameters == 0) { return 0; } // iterate over all index fields for (size_t i = 1; i <= idx->_fields._length; ++i) { v8::Handle key = v8::String::New(idx->_fields._buffer[i - 1]); if (!conditions->HasOwnProperty(key)) { break; } v8::Handle fieldConditions = conditions->Get(key); if (!fieldConditions->IsArray()) { // wrong data type for field conditions break; } // iterator over all conditions v8::Handle values = v8::Handle::Cast(fieldConditions); for (uint32_t j = 0; j < values->Length(); ++j) { v8::Handle fieldCondition = values->Get(j); if (!fieldCondition->IsArray()) { // wrong data type for single condition goto MEM_ERROR; } v8::Handle condition = v8::Handle::Cast(fieldCondition); if (condition->Length() != 2) { // wrong number of values in single condition goto MEM_ERROR; } v8::Handle op = condition->Get(0); v8::Handle value = condition->Get(1); if (!op->IsString()) { // wrong operator type goto MEM_ERROR; } TRI_json_t* json = TRI_JsonObject(value); if (!json) { goto MEM_ERROR; } std::string opValue = TRI_ObjectToString(op); if (opValue == "==") { // equality comparison if (lastNonEq > 0) { goto MEM_ERROR; } TRI_PushBack3ListJson(TRI_UNKNOWN_MEM_ZONE, parameters, json); // creation of equality operator is deferred until it is finally needed ++numEq; break; } else { if (lastNonEq > 0 && lastNonEq != i) { // if we already had a range condition and a previous field, we cannot continue // because the skiplist interface does not support such queries goto MEM_ERROR; } TRI_index_operator_type_e opType; if (opValue == ">") { opType = TRI_GT_INDEX_OPERATOR; } else if (opValue == ">=") { opType = TRI_GE_INDEX_OPERATOR; } else if (opValue == "<") { opType = TRI_LT_INDEX_OPERATOR; } else if (opValue == "<=") { opType = TRI_LE_INDEX_OPERATOR; } else { // wrong operator type goto MEM_ERROR; } lastNonEq = i; TRI_json_t* cloned = TRI_CopyJson(TRI_UNKNOWN_MEM_ZONE, parameters); if (cloned == 0) { goto MEM_ERROR; } TRI_PushBack3ListJson(TRI_UNKNOWN_MEM_ZONE, cloned, json); if (numEq) { // create equality operator if one is in queue TRI_json_t* clonedParams = TRI_CopyJson(TRI_UNKNOWN_MEM_ZONE, parameters); if (clonedParams == 0) { TRI_FreeJson(TRI_UNKNOWN_MEM_ZONE, cloned); goto MEM_ERROR; } lastOperator = TRI_CreateIndexOperator(TRI_EQ_INDEX_OPERATOR, NULL, NULL, clonedParams, shaper, NULL, clonedParams->_value._objects._length, NULL); numEq = 0; } TRI_index_operator_t* current = 0; // create the operator for the current condition current = TRI_CreateIndexOperator(opType, NULL, NULL, cloned, shaper, NULL, cloned->_value._objects._length, NULL); if (current == 0) { TRI_FreeJson(TRI_UNKNOWN_MEM_ZONE, cloned); goto MEM_ERROR; } if (lastOperator == 0) { lastOperator = current; } else { // merge the current operator with previous operators using logical AND TRI_index_operator_t* newOperator = TRI_CreateIndexOperator(TRI_AND_INDEX_OPERATOR, lastOperator, current, NULL, shaper, NULL, 2, NULL); if (newOperator == 0) { TRI_FreeIndexOperator(current); goto MEM_ERROR; } else { lastOperator = newOperator; } } } } } if (numEq) { // create equality operator if one is in queue assert(lastOperator == 0); assert(lastNonEq == 0); TRI_json_t* clonedParams = TRI_CopyJson(TRI_UNKNOWN_MEM_ZONE, parameters); if (clonedParams == 0) { goto MEM_ERROR; } lastOperator = TRI_CreateIndexOperator(TRI_EQ_INDEX_OPERATOR, NULL, NULL, clonedParams, shaper, NULL, clonedParams->_value._objects._length, NULL); } TRI_FreeJson(TRI_UNKNOWN_MEM_ZONE, parameters); return lastOperator; MEM_ERROR: TRI_FreeJson(TRI_UNKNOWN_MEM_ZONE, parameters); if (lastOperator == 0) { TRI_FreeIndexOperator(lastOperator); } return 0; } //////////////////////////////////////////////////////////////////////////////// /// @brief sets up the bitarray operator for a bitarray condition query //////////////////////////////////////////////////////////////////////////////// static TRI_json_t* SetupBitarrayAttributeValuesHelper (TRI_index_t* idx, v8::Handle attributeValues) { TRI_json_t* parameters = TRI_CreateListJson(TRI_UNKNOWN_MEM_ZONE); // ........................................................................ // No memory, no problem // ........................................................................ if (parameters == 0) { return 0; } // ........................................................................ // Client mucked something up? // ........................................................................ if (!attributeValues->IsObject()) { TRI_FreeJson(TRI_UNKNOWN_MEM_ZONE, parameters); return 0; } // ........................................................................ // Observe that the client can have sent any number of parameters which // do not match the list of attributes defined in the index. // These parameters are IGNORED -- no error is reported. // ........................................................................ for (size_t i = 0; i < idx->_fields._length; ++i) { v8::Handle key = v8::String::New(idx->_fields._buffer[i]); TRI_json_t* json; // ...................................................................... // The client may have sent values for all of the Attributes or for // a subset of them. If the value for an Attribute is missing, then we // assume that the client wishes to IGNORE the value of that Attribute. // In the later case, we add the json object 'TRI_JSON_UNUSED' to // indicate that this attribute is to be ignored. Notice that it is // possible to ignore all the attributes defined as part of the index. // ...................................................................... if (attributeValues->HasOwnProperty(key)) { // .................................................................... // for this index attribute, there is such an attribute given as a // as a parameter by the client -- determine the value (or values) // of this attribute parameter and store it for later use in the // lookup // .................................................................... v8::Handle value = attributeValues->Get(key); json = TRI_JsonObject(value); // .................................................................... // special case: if client sent {"x":[],...}, then we wrap this up // as {"x":[ [] ],...}. // .................................................................... if (json->_type == TRI_JSON_LIST) { if (json->_value._objects._length == 0) { TRI_json_t emptyList; emptyList._type = TRI_JSON_LIST; TRI_InitVector(&(emptyList._value._objects), TRI_UNKNOWN_MEM_ZONE, sizeof(TRI_json_t)); TRI_PushBack2ListJson(json, &emptyList); } } } else { // .................................................................... // for this index attribute we can not locate it in the list of parameters // sent to us by the client. Assign it an 'unused' (perhaps should be // renamed to 'unknown' or 'undefined'). // .................................................................... json = (TRI_json_t*) TRI_Allocate(TRI_UNKNOWN_MEM_ZONE, sizeof(TRI_json_t), true); json->_type = TRI_JSON_UNUSED; } // ...................................................................... // Check and ensure we have a json object defined before we store it. // ...................................................................... if (json == 0) { TRI_FreeJson(TRI_UNKNOWN_MEM_ZONE, parameters); return 0; } // ...................................................................... // store it in an list json object -- eventually wil be stored as part // of the index operator. // ...................................................................... TRI_PushBack3ListJson(TRI_UNKNOWN_MEM_ZONE, parameters, json); } return parameters; } static TRI_index_operator_t* SetupConditionsBitarrayHelper (TRI_index_t* idx, TRI_shaper_t* shaper, v8::Handle condition) { v8::Handle value; TRI_index_operator_type_e operatorType; TRI_index_operator_t* indexOperator = 0; // ........................................................................ // Check the various operator conditions // ........................................................................ // ........................................................................ // Check for an 'AND' condition. The following are acceptable: '&', '&&' 'and' // ........................................................................ if (condition->HasOwnProperty(v8::String::New("&"))) { operatorType = TRI_AND_INDEX_OPERATOR; value = condition->Get(v8::String::New("&")); } else if (condition->HasOwnProperty(v8::String::New("&&"))) { operatorType = TRI_AND_INDEX_OPERATOR; value = condition->Get(v8::String::New("&&")); } else if (condition->HasOwnProperty(v8::String::New("and"))) { operatorType = TRI_AND_INDEX_OPERATOR; value = condition->Get(v8::String::New("and")); } // ........................................................................ // Check for an 'OR' condition. The following are acceptable: '|', '||' 'or' // ........................................................................ else if (condition->HasOwnProperty(v8::String::New("|"))) { value = condition->Get(v8::String::New("|")); operatorType = TRI_OR_INDEX_OPERATOR; } else if (condition->HasOwnProperty(v8::String::New("||"))) { value = condition->Get(v8::String::New("||")); operatorType = TRI_OR_INDEX_OPERATOR; } else if (condition->HasOwnProperty(v8::String::New("or"))) { value = condition->Get(v8::String::New("or")); operatorType = TRI_OR_INDEX_OPERATOR; } // ........................................................................ // Check for an 'NOT' condition. The following are acceptable: '!', 'not' // ........................................................................ else if (condition->HasOwnProperty(v8::String::New("!"))) { value = condition->Get(v8::String::New("!")); operatorType = TRI_NOT_INDEX_OPERATOR; } else if (condition->HasOwnProperty(v8::String::New("not"))) { value = condition->Get(v8::String::New("not")); operatorType = TRI_NOT_INDEX_OPERATOR; } // ........................................................................ // Check for an 'EQUAL' condition. The following are acceptable: '=', '==', 'eq' // ........................................................................ else if (condition->HasOwnProperty(v8::String::New("=="))) { value = condition->Get(v8::String::New("==")); operatorType = TRI_EQ_INDEX_OPERATOR; } else if (condition->HasOwnProperty(v8::String::New("="))) { value = condition->Get(v8::String::New("=")); operatorType = TRI_EQ_INDEX_OPERATOR; } else if (condition->HasOwnProperty(v8::String::New("eq"))) { value = condition->Get(v8::String::New("eq")); operatorType = TRI_EQ_INDEX_OPERATOR; } // ........................................................................ // Check for an 'NOT EQUAL' condition. The following are acceptable: '!=', '<>, 'ne' // ........................................................................ else if (condition->HasOwnProperty(v8::String::New("!="))) { value = condition->Get(v8::String::New("!=")); operatorType = TRI_NE_INDEX_OPERATOR; } else if (condition->HasOwnProperty(v8::String::New("<>"))) { value = condition->Get(v8::String::New("<>")); operatorType = TRI_NE_INDEX_OPERATOR; } else if (condition->HasOwnProperty(v8::String::New("ne"))) { value = condition->Get(v8::String::New("ne")); operatorType = TRI_NE_INDEX_OPERATOR; } // ........................................................................ // Check for an 'LESS THAN OR EQUAL' condition. The following are acceptable: '<=', 'le' // ........................................................................ else if (condition->HasOwnProperty(v8::String::New("<="))) { value = condition->Get(v8::String::New("<=")); operatorType = TRI_LE_INDEX_OPERATOR; } else if (condition->HasOwnProperty(v8::String::New("le"))) { value = condition->Get(v8::String::New("le")); operatorType = TRI_LE_INDEX_OPERATOR; } // ........................................................................ // Check for an 'LESS THAN ' condition. The following are acceptable: '<', 'lt' // ........................................................................ else if (condition->HasOwnProperty(v8::String::New("<"))) { value = condition->Get(v8::String::New("<")); operatorType = TRI_LT_INDEX_OPERATOR; } else if (condition->HasOwnProperty(v8::String::New("lt"))) { value = condition->Get(v8::String::New("lt")); operatorType = TRI_LT_INDEX_OPERATOR; } // ........................................................................ // Check for an 'GREATER THAN OR EQUAL' condition. The following are acceptable: '>=', 'ge' // ........................................................................ else if (condition->HasOwnProperty(v8::String::New(">="))) { value = condition->Get(v8::String::New(">=")); operatorType = TRI_GE_INDEX_OPERATOR; } else if (condition->HasOwnProperty(v8::String::New("ge"))) { value = condition->Get(v8::String::New("ge")); operatorType = TRI_GE_INDEX_OPERATOR; } // ........................................................................ // Check for an 'GREATER THAN ' condition. The following are acceptable: '>', 'gt' // ........................................................................ else if (condition->HasOwnProperty(v8::String::New(">"))) { value = condition->Get(v8::String::New(">")); operatorType = TRI_GT_INDEX_OPERATOR; } else if (condition->HasOwnProperty(v8::String::New("gt"))) { value = condition->Get(v8::String::New("gt")); operatorType = TRI_GT_INDEX_OPERATOR; } // ........................................................................ // We received an invalid condition. Most likely we are really expressing // a condition {"x":1} which should be BY_EXAMPLE rather than BY_CONDITION // ........................................................................ else { // invalid operator index condition return 0; } // ........................................................................ // Since we have a valid condition, act upon it // may require recursion // ........................................................................ switch (operatorType) { case TRI_AND_INDEX_OPERATOR: case TRI_OR_INDEX_OPERATOR: { // .................................................................... // For both the 'AND' and 'OR' index operators, we require an array // with 2 elements for the value of the condition object. E.g. we // expect: {"&": [{"x":0},{"x":1}]} <-- this is a special "and" call // see the ensureBitarray doc for // more information. // More common is // expect: {"or": [{"x":0},{"x":1}]} <-- which means return all docs // where attribute "x" has the // value of 0 or 1. // To have "x" = 0 or "x" = 1 or "x" = 2 we expect: // {"or":[{"x":0},{"or":[{"x":1},{"x":2}]}]} or any valid iteration // of this. TODO: shortcut this with the "list" index operator // .................................................................... // .................................................................... // wrong data type for this condition -- we require [leftOperation,rightOperation] // .................................................................... if (!value->IsArray()) { return 0; } v8::Handle andValues = v8::Handle::Cast(value); // .................................................................... // Check the length of the array to ensure that it is exactly 2 // .................................................................... if (andValues->Length() != 2) { return 0; } v8::Handle leftValue = andValues->Get(0); v8::Handle rightValue = andValues->Get(1); if (!leftValue->IsObject() || !rightValue->IsObject()) { return 0; } v8::Handle leftObject = v8::Handle::Cast(leftValue); v8::Handle rightObject = v8::Handle::Cast(rightValue); // .................................................................... // recurse the left and right operators // .................................................................... TRI_index_operator_t* leftOp = SetupConditionsBitarrayHelper(idx, shaper, leftObject); TRI_index_operator_t* rightOp = SetupConditionsBitarrayHelper(idx, shaper, rightObject); if (leftOp == 0 || rightOp == 0) { TRI_FreeIndexOperator(leftOp); TRI_FreeIndexOperator(rightOp); return 0; } indexOperator = TRI_CreateIndexOperator(operatorType, leftOp, rightOp, NULL, shaper, NULL, 0, NULL); break; } case TRI_NOT_INDEX_OPERATOR: { // .................................................................... // wrong data type for this condition -- we require {...} which becomes // the left object for not operator. // .................................................................... if (!value->IsObject()) { return 0; } v8::Handle leftObject = v8::Handle::Cast(value); // .................................................................... // recurse the left and only operator // .................................................................... TRI_index_operator_t* leftOp = SetupConditionsBitarrayHelper(idx, shaper, leftObject); if (leftOp == 0) { return 0; } indexOperator = TRI_CreateIndexOperator(operatorType, leftOp, NULL, NULL, shaper, NULL, 0, NULL); break; } case TRI_EQ_INDEX_OPERATOR: case TRI_NE_INDEX_OPERATOR: case TRI_LE_INDEX_OPERATOR: case TRI_LT_INDEX_OPERATOR: case TRI_GE_INDEX_OPERATOR: case TRI_GT_INDEX_OPERATOR: { v8::Handle leftObject = v8::Handle::Cast(value); TRI_json_t* parameters = SetupBitarrayAttributeValuesHelper(idx, leftObject); if (parameters == 0) { return 0; } indexOperator = TRI_CreateIndexOperator(operatorType, NULL, NULL, parameters, shaper, NULL, parameters->_value._objects._length, NULL); break; } default: { return 0; } } // end of switch (operatorType) return indexOperator; } static TRI_index_operator_t* SetupConditionsBitarray (TRI_index_t* idx, TRI_shaper_t* shaper, v8::Handle condition) { TRI_index_operator_t* indexOperator = SetupConditionsBitarrayHelper(idx, shaper, condition); return indexOperator; } //////////////////////////////////////////////////////////////////////////////// /// @brief sets up the skiplist operator for a skiplist example query /// /// this will set up a JSON container with the example values as a list /// at the end, one skiplist equality operator is created for the entire list //////////////////////////////////////////////////////////////////////////////// static TRI_index_operator_t* SetupExampleSkiplist (TRI_index_t* idx, TRI_shaper_t* shaper, v8::Handle example) { TRI_json_t* parameters = TRI_CreateListJson(TRI_UNKNOWN_MEM_ZONE); if (parameters == 0) { return 0; } for (size_t i = 0; i < idx->_fields._length; ++i) { v8::Handle key = v8::String::New(idx->_fields._buffer[i]); if (!example->HasOwnProperty(key)) { break; } v8::Handle value = example->Get(key); TRI_json_t* json = TRI_JsonObject(value); if (!json) { TRI_FreeJson(TRI_UNKNOWN_MEM_ZONE, parameters); return 0; } TRI_PushBack3ListJson(TRI_UNKNOWN_MEM_ZONE, parameters, json); } if (parameters->_value._objects._length > 0) { // example means equality comparisons only return TRI_CreateIndexOperator(TRI_EQ_INDEX_OPERATOR, NULL, NULL, parameters, shaper, NULL, parameters->_value._objects._length, NULL); } TRI_FreeJson(TRI_UNKNOWN_MEM_ZONE, parameters); return 0; } //////////////////////////////////////////////////////////////////////////////// /// @brief creates an index operator for a bitarray example query /// /// this will set up a JSON container with the example values as a list /// at the end, one skiplist equality operator is created for the entire list //////////////////////////////////////////////////////////////////////////////// static TRI_index_operator_t* SetupExampleBitarray (TRI_index_t* idx, TRI_shaper_t* shaper, v8::Handle example) { TRI_json_t* parameters = SetupBitarrayAttributeValuesHelper(idx, example); if (parameters == 0) { return 0; } // for an example query, we can only assume equality operator is required. return TRI_CreateIndexOperator(TRI_EQ_INDEX_OPERATOR, NULL, NULL, parameters, shaper, NULL, parameters->_value._objects._length, NULL); } //////////////////////////////////////////////////////////////////////////////// /// @brief sets up the example object for hash index //////////////////////////////////////////////////////////////////////////////// static int SetupExampleObjectIndex (TRI_hash_index_t* hashIndex, v8::Handle example, TRI_shaper_t* shaper, size_t& n, TRI_shaped_json_t**& values, v8::Handle* err) { // extract attribute paths n = hashIndex->_paths._length; // setup storage values = (TRI_shaped_json_t**) TRI_Allocate(TRI_UNKNOWN_MEM_ZONE, n * sizeof(TRI_shaped_json_t*), false); // TODO: memory allocation might fail // convert for (size_t i = 0; i < n; ++i) { TRI_shape_pid_t pid = * (TRI_shape_pid_t*) TRI_AtVector(&hashIndex->_paths, i); char const* name = TRI_AttributeNameShapePid(shaper, pid); if (name == NULL) { CleanupExampleObject(shaper, i, 0, values); *err = TRI_CreateErrorObject(TRI_ERROR_INTERNAL, "shaper failed"); return TRI_ERROR_BAD_PARAMETER; } v8::Handle key = v8::String::New(name); if (example->HasOwnProperty(key)) { v8::Handle val = example->Get(key); values[i] = TRI_ShapedJsonV8Object(val, shaper); } else { values[i] = TRI_ShapedJsonV8Object(v8::Null(), shaper); } if (values[i] == 0) { CleanupExampleObject(shaper, i, 0, values); *err = TRI_CreateErrorObject(TRI_ERROR_BAD_PARAMETER, "cannot convert value to JSON"); return TRI_ERROR_BAD_PARAMETER; } } return TRI_ERROR_NO_ERROR; } //////////////////////////////////////////////////////////////////////////////// /// @brief execute a skiplist query (by condition or by example) //////////////////////////////////////////////////////////////////////////////// static v8::Handle ExecuteSkiplistQuery (v8::Arguments const& argv, std::string const& signature, const query_t type, const bool lock) { v8::HandleScope scope; // extract and use the simple collection v8::Handle err; TRI_vocbase_col_t const* collection; TRI_document_collection_t* document = 0; if (lock) { document = TRI_ExtractAndUseSimpleCollection(argv, collection, &err); } else { document = TRI_ExtractSimpleCollection(argv, collection, &err); } if (document == 0) { return scope.Close(v8::ThrowException(err)); } TRI_primary_collection_t* primary = &document->base; // expecting index, example, skip, and limit if (argv.Length() < 2) { if (lock) { TRI_ReleaseCollection(collection); } std::string usage("Usage: "); usage += signature; return scope.Close(v8::ThrowException( TRI_CreateErrorObject(TRI_ERROR_BAD_PARAMETER, usage))); } if (! argv[1]->IsObject()) { if (lock) { TRI_ReleaseCollection(collection); } std::string msg; if (type == QUERY_EXAMPLE) { msg = " must be an object"; } else { msg = " must be an object"; } return scope.Close(v8::ThrowException( TRI_CreateErrorObject(TRI_ERROR_BAD_PARAMETER, msg))); } TRI_shaper_t* shaper = document->base._shaper; // extract skip and limit TRI_voc_ssize_t skip; TRI_voc_size_t limit; ExtractSkipAndLimit(argv, 2, skip, limit); // setup result v8::Handle result = v8::Object::New(); v8::Handle documents = v8::Array::New(); result->Set(v8::String::New("documents"), documents); // ............................................................................. // inside a read transaction // ............................................................................. if (lock) { primary->beginRead(primary); } // extract the index TRI_index_t* idx = TRI_LookupIndexByHandle(document->base.base._vocbase, collection, argv[0], false, &err); if (idx == 0) { primary->endRead(primary); if (lock) { TRI_ReleaseCollection(collection); } return scope.Close(v8::ThrowException(err)); } if (idx->_type != TRI_IDX_TYPE_SKIPLIST_INDEX) { if (lock) { primary->endRead(primary); TRI_ReleaseCollection(collection); } return scope.Close(v8::ThrowException(TRI_CreateErrorObject(TRI_ERROR_BAD_PARAMETER, "index must be a skiplist index"))); } TRI_index_operator_t* skiplistOperator; v8::Handle values = argv[1]->ToObject(); if (type == QUERY_EXAMPLE) { skiplistOperator = SetupExampleSkiplist(idx, shaper, values); } else { skiplistOperator = SetupConditionsSkiplist(idx, shaper, values); } if (! skiplistOperator) { if (lock) { primary->endRead(primary); TRI_ReleaseCollection(collection); } return scope.Close(v8::ThrowException(TRI_CreateErrorObject(TRI_ERROR_BAD_PARAMETER, "setting up skiplist operator failed"))); } TRI_skiplist_iterator_t* skiplistIterator = TRI_LookupSkiplistIndex(idx, skiplistOperator); TRI_barrier_t* barrier = 0; TRI_voc_ssize_t total = 0; TRI_voc_size_t count = 0; bool error = false; while (true) { SkiplistIndexElement* indexElement = (SkiplistIndexElement*) skiplistIterator->_next(skiplistIterator); if (indexElement == NULL) { break; } ++total; if (total > skip && count < limit) { if (barrier == 0) { barrier = TRI_CreateBarrierElement(&document->base._barrierList); } // TODO: barrier might be 0 v8::Handle doc = TRI_WrapShapedJson(collection, (TRI_doc_mptr_t const*) indexElement->data, barrier); if (doc.IsEmpty()) { // error error = true; break; } else { documents->Set(count, doc); ++count; } } } if (lock) { primary->endRead(primary); } // ............................................................................. // outside a write transaction // ............................................................................. // free data allocated by skiplist index result TRI_FreeSkiplistIterator(skiplistIterator); result->Set(v8::String::New("total"), v8::Number::New((double) total)); result->Set(v8::String::New("count"), v8::Number::New(count)); if (lock) { TRI_ReleaseCollection(collection); } if (error) { scope.Close(result); } return scope.Close(result); } //////////////////////////////////////////////////////////////////////////////// /// @brief execute a bitarray index query (by condition or by example) //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// // Example of a filter associated with an interator //////////////////////////////////////////////////////////////////////////////// static bool BitarrayFilterExample(TRI_index_iterator_t* indexIterator) { BitarrayIndexElement* indexElement; TRI_bitarray_index_t* baIndex; indexElement = (BitarrayIndexElement*) indexIterator->_next(indexIterator); if (indexElement == NULL) { return false; } baIndex = (TRI_bitarray_index_t*) indexIterator->_index; if (baIndex == NULL) { return false; } /* doc = (TRI_doc_mptr_t*) indexElement->data; */ // .......................................................................... // Now perform any additional filter operations you require on the doc // using baIndex which you now have access to. // .......................................................................... return true; } static v8::Handle ExecuteBitarrayQuery (v8::Arguments const& argv, std::string const& signature, const query_t type, const bool lock) { v8::HandleScope scope; v8::Handle err; const TRI_vocbase_col_t* collection; TRI_voc_ssize_t skip; TRI_voc_size_t limit; // ........................................................................... // extract and use the simple collection // ........................................................................... TRI_document_collection_t* document = 0; if (lock) { document = TRI_ExtractAndUseSimpleCollection(argv, collection, &err); } else { document = TRI_ExtractSimpleCollection(argv, collection, &err); } if (document == 0) { return scope.Close(v8::ThrowException(err)); } TRI_primary_collection_t* primary = &document->base; // ........................................................................... // Check the parameters, expecting index, example, skip, and limit // e.g. ("110597/962565", {"x":1}, null, null) // ........................................................................... if (argv.Length() < 2) { if (lock) { TRI_ReleaseCollection(collection); } std::string usage("Usage: "); usage += signature; return scope.Close(v8::ThrowException(TRI_CreateErrorObject(TRI_ERROR_BAD_PARAMETER,usage))); } // ........................................................................... // Check that the second parameter is an associative array (json object) // ........................................................................... if (! argv[1]->IsObject()) { if (lock) { TRI_ReleaseCollection(collection); } std::string msg; if (type == QUERY_EXAMPLE) { msg = " must be an object"; } else { msg = " must be an object"; } return scope.Close(v8::ThrowException(TRI_CreateErrorObject(TRI_ERROR_BAD_PARAMETER, msg))); } TRI_shaper_t* shaper = document->base._shaper; // ............................................................................. // extract skip and limit // ............................................................................. ExtractSkipAndLimit(argv, 2, skip, limit); // ............................................................................. // Create the json object result which stores documents located // ............................................................................. v8::Handle result = v8::Object::New(); // ............................................................................. // Create the array to store documents located // ............................................................................. v8::Handle documents = v8::Array::New(); result->Set(v8::String::New("documents"), documents); // ............................................................................. // inside a read transaction // ............................................................................. if (lock) { primary->beginRead(primary); } // ............................................................................. // extract the index // ............................................................................. TRI_index_t* idx = TRI_LookupIndexByHandle(document->base.base._vocbase, collection, argv[0], false, &err); if (idx == 0) { primary->endRead(primary); if (lock) { TRI_ReleaseCollection(collection); } return scope.Close(v8::ThrowException(err)); } if (idx->_type != TRI_IDX_TYPE_BITARRAY_INDEX) { if (lock) { primary->endRead(primary); TRI_ReleaseCollection(collection); } return scope.Close(v8::ThrowException(TRI_CreateErrorObject(TRI_ERROR_BAD_PARAMETER, "index must be a skiplist index"))); } TRI_index_operator_t* indexOperator; v8::Handle values = argv[1]->ToObject(); if (type == QUERY_EXAMPLE) { indexOperator = SetupExampleBitarray(idx, shaper, values); } else { indexOperator = SetupConditionsBitarray(idx, shaper, values); } if (indexOperator == 0) { // something wrong if (lock) { primary->endRead(primary); TRI_ReleaseCollection(collection); } return scope.Close(v8::ThrowException(TRI_CreateErrorObject(TRI_ERROR_BAD_PARAMETER, "setting up bitarray index operator failed"))); } // ............................................................................. // attempt to locate the documents // ............................................................................. TRI_index_iterator_t* indexIterator = TRI_LookupBitarrayIndex(idx, indexOperator, BitarrayFilterExample); // ............................................................................. // Take care of the case where the index iterator is returned as NULL -- may // occur when some catastrophic error occurs. // ............................................................................. TRI_barrier_t* barrier = 0; TRI_voc_ssize_t total = 0; TRI_voc_size_t count = 0; bool error = false; if (indexIterator != NULL) { while (true) { TRI_doc_mptr_t* data = (TRI_doc_mptr_t*) indexIterator->_next(indexIterator); if (data == NULL) { break; } ++total; if (total > skip && count < limit) { if (barrier == 0) { barrier = TRI_CreateBarrierElement(&document->base._barrierList); } // TODO: barrier might be 0 v8::Handle doc = TRI_WrapShapedJson(collection, data, barrier); if (doc.IsEmpty()) { // error error = true; break; } else { documents->Set(count, doc); ++count; } } } // free data allocated by index result TRI_FreeIndexIterator(indexIterator); } else { LOG_WARNING("index iterator returned with a NULL value in ExecuteBitarrayQuery"); // return an empty list } if (lock) { primary->endRead(primary); } // ............................................................................. // outside a write transaction // ............................................................................. result->Set(v8::String::New("total"), v8::Number::New((double) total)); result->Set(v8::String::New("count"), v8::Number::New(count)); if (lock) { TRI_ReleaseCollection(collection); } if (error) { return scope.Close(v8::Null()); } return scope.Close(result); } //////////////////////////////////////////////////////////////////////////////// /// @brief sorts geo coordinates //////////////////////////////////////////////////////////////////////////////// static int CompareGeoCoordinateDistance (geo_coordinate_distance_t* left, geo_coordinate_distance_t* right) { if (left->_distance < right->_distance) { return -1; } else if (left->_distance > right->_distance) { return 1; } else { return 0; } } #define FSRT_NAME SortGeoCoordinates #define FSRT_NAM2 SortGeoCoordinatesTmp #define FSRT_TYPE geo_coordinate_distance_t #define FSRT_COMP(l,r,s) CompareGeoCoordinateDistance(l,r) uint32_t FSRT_Rand = 0; static uint32_t RandomGeoCoordinateDistance (void) { return (FSRT_Rand = FSRT_Rand * 31415 + 27818); } #define FSRT__RAND \ ((fs_b) + FSRT__UNIT * (RandomGeoCoordinateDistance() % FSRT__DIST(fs_e,fs_b,FSRT__SIZE))) #include "BasicsC/fsrt.inc" #include "strings.h" //////////////////////////////////////////////////////////////////////////////// /// @brief creates a geo result //////////////////////////////////////////////////////////////////////////////// static void StoreGeoResult (TRI_vocbase_col_t const* collection, GeoCoordinates* cors, v8::Handle& documents, v8::Handle& distances) { GeoCoordinate* end; GeoCoordinate* ptr; double* dtr; geo_coordinate_distance_t* gnd; geo_coordinate_distance_t* gtr; geo_coordinate_distance_t* tmp; size_t n; uint32_t i; TRI_barrier_t* barrier; // sort the result n = cors->length; if (n == 0) { GeoIndex_CoordinatesFree(cors); return; } gtr = (tmp = (geo_coordinate_distance_t*) TRI_Allocate(TRI_UNKNOWN_MEM_ZONE, sizeof(geo_coordinate_distance_t) * n, false)); gnd = tmp + n; ptr = cors->coordinates; end = cors->coordinates + n; dtr = cors->distances; for (; ptr < end; ++ptr, ++dtr, ++gtr) { gtr->_distance = *dtr; gtr->_data = ptr->data; } GeoIndex_CoordinatesFree(cors); SortGeoCoordinates(tmp, gnd); barrier = TRI_CreateBarrierElement(&((TRI_primary_collection_t*) collection->_collection)->_barrierList); // TODO: barrier might be 0 // copy the documents for (gtr = tmp, i = 0; gtr < gnd; ++gtr, ++i) { documents->Set(i, TRI_WrapShapedJson(collection, (TRI_doc_mptr_t const*) gtr->_data, barrier)); distances->Set(i, v8::Number::New(gtr->_distance)); } TRI_Free(TRI_UNKNOWN_MEM_ZONE, tmp); } //////////////////////////////////////////////////////////////////////////////// /// @} //////////////////////////////////////////////////////////////////////////////// // ----------------------------------------------------------------------------- // --SECTION-- QUERY FUNCTIONS // ----------------------------------------------------------------------------- // ----------------------------------------------------------------------------- // --SECTION-- private functions // ----------------------------------------------------------------------------- //////////////////////////////////////////////////////////////////////////////// /// @addtogroup VocBase /// @{ //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// /// @brief looks up edges for given direction //////////////////////////////////////////////////////////////////////////////// static v8::Handle EdgesQuery (TRI_edge_direction_e direction, v8::Arguments const& argv) { v8::HandleScope scope; // extract and use the simple collection v8::Handle err; TRI_vocbase_col_t const* collection; TRI_document_collection_t* document = TRI_ExtractAndUseSimpleCollection(argv, collection, &err); if (document == 0) { return scope.Close(v8::ThrowException(err)); } TRI_primary_collection_t* primary = &document->base; if (collection->_type != TRI_COL_TYPE_EDGE) { TRI_ReleaseCollection(collection); return scope.Close(v8::ThrowException( TRI_CreateErrorObject(TRI_ERROR_ARANGO_COLLECTION_TYPE_INVALID, "invalid collection type for edge query"))); } // first and only argument schould be a list of document idenfifier if (argv.Length() != 1) { TRI_ReleaseCollection(collection); switch (direction) { case TRI_EDGE_IN: return scope.Close(v8::ThrowException( TRI_CreateErrorObject(TRI_ERROR_BAD_PARAMETER, "usage: inEdges()"))); case TRI_EDGE_OUT: return scope.Close(v8::ThrowException( TRI_CreateErrorObject(TRI_ERROR_BAD_PARAMETER, "usage: outEdges()"))); case TRI_EDGE_ANY: default: { return scope.Close(v8::ThrowException( TRI_CreateErrorObject(TRI_ERROR_BAD_PARAMETER, "usage: edges()"))); } } } // setup result v8::Handle documents = v8::Array::New(); // ............................................................................. // inside a read transaction // ............................................................................. primary->beginRead(primary); TRI_barrier_t* barrier = 0; uint32_t count = 0; bool error = false; // argument is a list of vertices if (argv[0]->IsArray()) { v8::Handle vertices = v8::Handle::Cast(argv[0]); uint32_t len = vertices->Length(); for (uint32_t i = 0; i < len; ++i) { TRI_vector_pointer_t edges; TRI_voc_cid_t cid; TRI_voc_rid_t rid; TRI_voc_key_t key = 0; TRI_vocbase_col_t const* vertexCollection = 0; v8::Handle errMsg = TRI_ParseDocumentOrDocumentHandle(collection->_vocbase, vertexCollection, key, rid, true, vertices->Get(i)); if (! errMsg.IsEmpty()) { if (vertexCollection != 0) { TRI_ReleaseCollection(vertexCollection); } if (key) { TRI_FreeString(TRI_CORE_MEM_ZONE, key); key = 0; } continue; } cid = vertexCollection->_cid; TRI_ReleaseCollection(vertexCollection); edges = TRI_LookupEdgesDocumentCollection(document, direction, cid, key); if (key) TRI_FreeString(TRI_CORE_MEM_ZONE, key); for (size_t j = 0; j < edges._length; ++j) { if (barrier == 0) { barrier = TRI_CreateBarrierElement(&document->base._barrierList); } // TODO: barrier might be 0 v8::Handle doc = TRI_WrapShapedJson(collection, (TRI_doc_mptr_t const*) edges._buffer[j], barrier); if (doc.IsEmpty()) { // error error = true; break; } else { documents->Set(count, doc); ++count; } } TRI_DestroyVectorPointer(&edges); } } // argument is a single vertex else { TRI_vector_pointer_t edges; TRI_voc_cid_t cid; TRI_voc_rid_t rid; TRI_voc_key_t key = 0; TRI_vocbase_col_t const* vertexCollection = 0; v8::Handle errMsg = TRI_ParseDocumentOrDocumentHandle(collection->_vocbase, vertexCollection, key, rid, true, argv[0]); if (! errMsg.IsEmpty()) { if (vertexCollection != 0) { TRI_ReleaseCollection(vertexCollection); } primary->endRead(primary); if (key) { TRI_FreeString(TRI_CORE_MEM_ZONE, key); } TRI_ReleaseCollection(collection); return scope.Close(v8::ThrowException(errMsg)); } cid = vertexCollection->_cid; TRI_ReleaseCollection(vertexCollection); edges = TRI_LookupEdgesDocumentCollection(document, direction, cid, key); if (key) TRI_FreeString(TRI_CORE_MEM_ZONE, key); for (size_t j = 0; j < edges._length; ++j) { if (barrier == 0) { barrier = TRI_CreateBarrierElement(&document->base._barrierList); } // TODO: barrier might be 0 v8::Handle doc = TRI_WrapShapedJson(collection, (TRI_doc_mptr_t const*) edges._buffer[j], barrier); if (doc.IsEmpty()) { // error error = true; break; } else { documents->Set(count, doc); ++count; } } TRI_DestroyVectorPointer(&edges); } primary->endRead(primary); // ............................................................................. // outside a write transaction // ............................................................................. TRI_ReleaseCollection(collection); if (error) { return scope.Close(v8::Null()); } return scope.Close(documents); } //////////////////////////////////////////////////////////////////////////////// /// @} //////////////////////////////////////////////////////////////////////////////// // ----------------------------------------------------------------------------- // --SECTION-- javascript functions // ----------------------------------------------------------------------------- //////////////////////////////////////////////////////////////////////////////// /// @addtogroup VocBase /// @{ //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// /// @brief executes an ALL query, without any locking /// /// the caller must ensure all relevant locks are acquired and freed //////////////////////////////////////////////////////////////////////////////// static v8::Handle AllQuery (TRI_document_collection_t* document, TRI_vocbase_col_t const* collection, v8::Arguments const& argv) { v8::HandleScope scope; // expecting two arguments if (argv.Length() != 2) { return scope.Close(v8::ThrowException( TRI_CreateErrorObject(TRI_ERROR_BAD_PARAMETER, "usage: ALL(, )"))); } // extract skip and limit TRI_voc_ssize_t skip; TRI_voc_size_t limit; TRI_primary_collection_t* primary = &document->base; ExtractSkipAndLimit(argv, 0, skip, limit); // setup result v8::Handle result = v8::Object::New(); v8::Handle documents = v8::Array::New(); result->Set(v8::String::New("documents"), documents); size_t total = primary->_primaryIndex._nrUsed; uint32_t count = 0; bool error = false; if (0 < total && 0 < limit) { TRI_barrier_t* barrier = 0; void** beg = primary->_primaryIndex._table; void** end = beg + primary->_primaryIndex._nrAlloc; void** ptr = beg; // skip from the beginning if (0 < skip) { for (; ptr < end && 0 < skip; ++ptr) { if (*ptr) { TRI_doc_mptr_t const* d = (TRI_doc_mptr_t const*) *ptr; if (d->_validTo == 0) { --skip; } } } } // skip from the end else if (skip < 0) { ptr = end - 1; for (; beg <= ptr; --ptr) { if (*ptr) { TRI_doc_mptr_t const* d = (TRI_doc_mptr_t const*) *ptr; if (d->_validTo == 0) { ++skip; if (skip == 0) { break; } } } } if (ptr < beg) { ptr = beg; } } // limit for (; ptr < end && count < limit; ++ptr) { if (*ptr) { TRI_doc_mptr_t const* d = (TRI_doc_mptr_t const*) *ptr; if (d->_validTo == 0) { if (barrier == 0) { barrier = TRI_CreateBarrierElement(&document->base._barrierList); } // TODO: barrier might be 0 v8::Handle doc = TRI_WrapShapedJson(collection, d, barrier); if (doc.IsEmpty()) { // error error = true; break; } else { documents->Set(count, doc); ++count; } } } } } result->Set(v8::String::New("total"), v8::Number::New((double) total)); result->Set(v8::String::New("count"), v8::Number::New(count)); if (error) { return scope.Close(v8::Null()); } return scope.Close(result); } //////////////////////////////////////////////////////////////////////////////// /// @brief selects all elements, acquiring all required locks //////////////////////////////////////////////////////////////////////////////// static v8::Handle JS_AllQuery (v8::Arguments const& argv) { v8::HandleScope scope; // extract and use the simple collection v8::Handle err; TRI_vocbase_col_t const* collection; TRI_document_collection_t* document = TRI_ExtractAndUseSimpleCollection(argv, collection, &err); if (document == 0) { return scope.Close(v8::ThrowException(err)); } // ............................................................................. // inside a read transaction // ............................................................................. TRI_primary_collection_t* primary = &document->base; primary->beginRead(primary); v8::Handle result = AllQuery(document, collection, argv); primary->endRead(primary); // ............................................................................. // outside a write transaction // ............................................................................. TRI_ReleaseCollection(collection); return scope.Close(result); } //////////////////////////////////////////////////////////////////////////////// /// @brief selects all elements, without acquiring any locks /// /// It is the callers responsibility to acquire and free the required locks //////////////////////////////////////////////////////////////////////////////// static v8::Handle JS_AllNLQuery (v8::Arguments const& argv) { v8::HandleScope scope; // extract and use the simple collection v8::Handle err; TRI_vocbase_col_t const* collection; TRI_document_collection_t* document = TRI_ExtractSimpleCollection(argv, collection, &err); if (document == 0) { return scope.Close(v8::ThrowException(err)); } v8::Handle result = AllQuery(document, collection, argv); return scope.Close(result); } //////////////////////////////////////////////////////////////////////////////// /// @brief selects elements by example (not using any index) //////////////////////////////////////////////////////////////////////////////// static v8::Handle JS_ByExampleQuery (v8::Arguments const& argv) { v8::HandleScope scope; // extract and use the simple collection v8::Handle err; TRI_vocbase_col_t const* collection; TRI_document_collection_t* document = TRI_ExtractAndUseSimpleCollection(argv, collection, &err); if (document == 0) { return scope.Close(v8::ThrowException(err)); } TRI_shaper_t* shaper = document->base._shaper; // expecting example, skip, limit if (argv.Length() < 1) { TRI_ReleaseCollection(collection); return scope.Close(v8::ThrowException( TRI_CreateErrorObject(TRI_ERROR_BAD_PARAMETER, "usage: BY_EXAMPLE(, , )"))); } // extract the example if (! argv[0]->IsObject()) { TRI_ReleaseCollection(collection); return scope.Close(v8::ThrowException( TRI_CreateErrorObject(TRI_ERROR_BAD_PARAMETER, " must be an object"))); } v8::Handle example = argv[0]->ToObject(); // extract skip and limit TRI_voc_ssize_t skip; TRI_voc_size_t limit; ExtractSkipAndLimit(argv, 1, skip, limit); // extract sub-documents TRI_shape_pid_t* pids; TRI_shaped_json_t** values; size_t n; int res = SetupExampleObject(example, shaper, n, pids, values, &err); if (res != TRI_ERROR_NO_ERROR) { TRI_ReleaseCollection(collection); return scope.Close(v8::ThrowException(err)); } // setup result v8::Handle result = v8::Object::New(); v8::Handle documents = v8::Array::New(); result->Set(v8::String::New("documents"), documents); // ............................................................................. // inside a read transaction // ............................................................................. TRI_primary_collection_t* primary = &document->base; TRI_doc_operation_context_t context; TRI_InitReadContextPrimaryCollection(&context, primary); primary->beginRead(primary); // find documents by example TRI_vector_t filtered = TRI_SelectByExample(&context, n, pids, values); // convert to list of shaped jsons size_t total = filtered._length; size_t count = 0; bool error = false; if (0 < total) { size_t s; size_t e; CalculateSkipLimitSlice(filtered._length, skip, limit, s, e); if (s < e) { // only go in here if something has to be done, otherwise barrier memory might be lost TRI_barrier_t* barrier = TRI_CreateBarrierElement(&primary->_barrierList); // TODO: barrier might be 0 for (size_t j = s; j < e; ++j) { TRI_doc_mptr_t* mptr = (TRI_doc_mptr_t*) TRI_AtVector(&filtered, j); v8::Handle doc = TRI_WrapShapedJson(collection, mptr, barrier); if (doc.IsEmpty()) { // error error = true; break; } else { documents->Set(count, doc); ++count; } } } } TRI_DestroyVector(&filtered); primary->endRead(primary); // ............................................................................. // outside a write transaction // ............................................................................. result->Set(v8::String::New("total"), v8::Number::New((double) total)); result->Set(v8::String::New("count"), v8::Number::New(count)); CleanupExampleObject(shaper, n, pids, values); TRI_ReleaseCollection(collection); if (error) { return scope.Close(v8::Null()); } return scope.Close(result); } //////////////////////////////////////////////////////////////////////////////// /// @brief selects elements by example using a hash index /// /// It is the callers responsibility to acquire and free the required locks //////////////////////////////////////////////////////////////////////////////// static v8::Handle ByExampleHashIndexQuery (TRI_document_collection_t* document, TRI_vocbase_col_t const* collection, v8::Handle* err, v8::Arguments const& argv) { v8::HandleScope scope; // expecting index, example, skip, and limit if (argv.Length() < 2) { return scope.Close(v8::ThrowException( TRI_CreateErrorObject(TRI_ERROR_BAD_PARAMETER, "usage: BY_EXAMPLE_HASH(, , , )"))); } // extract the example if (! argv[1]->IsObject()) { return scope.Close(v8::ThrowException( TRI_CreateErrorObject(TRI_ERROR_BAD_PARAMETER, " must be an object"))); } v8::Handle example = argv[1]->ToObject(); // extract skip and limit TRI_voc_ssize_t skip; TRI_voc_size_t limit; ExtractSkipAndLimit(argv, 2, skip, limit); // setup result v8::Handle result = v8::Object::New(); v8::Handle documents = v8::Array::New(); result->Set(v8::String::New("documents"), documents); // extract the index TRI_index_t* idx = TRI_LookupIndexByHandle(document->base.base._vocbase, collection, argv[0], false, err); if (idx == 0) { return scope.Close(v8::ThrowException(*err)); } if (idx->_type != TRI_IDX_TYPE_HASH_INDEX) { return scope.Close(v8::ThrowException(TRI_CreateErrorObject(TRI_ERROR_BAD_PARAMETER, "index must be a hash index"))); } TRI_hash_index_t* hashIndex = (TRI_hash_index_t*) idx; // convert the example (index is locked by beginRead) size_t n; TRI_shaped_json_t** values; TRI_shaper_t* shaper = document->base._shaper; int res = SetupExampleObjectIndex(hashIndex, example, shaper, n, values, err); if (res != TRI_ERROR_NO_ERROR) { return scope.Close(v8::ThrowException(*err)); } // find the matches TRI_hash_index_elements_t* list = TRI_LookupShapedJsonHashIndex(idx, values); // convert result size_t total = list->_numElements; size_t count = 0; bool error = false; if (0 < total) { size_t s; size_t e; CalculateSkipLimitSlice(total, skip, limit, s, e); if (s < e) { TRI_barrier_t* barrier = TRI_CreateBarrierElement(&document->base._barrierList); // TODO: barrier might be 0 for (size_t i = s; i < e; ++i) { v8::Handle doc = TRI_WrapShapedJson(collection, (TRI_doc_mptr_t const*) list->_elements[i].data, barrier); if (doc.IsEmpty()) { // error error = true; break; } else { documents->Set(count, doc); ++count; } } } } // free data allocated by hash index result TRI_FreeResultHashIndex(idx, list); result->Set(v8::String::New("total"), v8::Number::New((double) total)); result->Set(v8::String::New("count"), v8::Number::New(count)); CleanupExampleObject(shaper, n, 0, values); if (error) { return scope.Close(v8::Null()); } return scope.Close(result); } //////////////////////////////////////////////////////////////////////////////// /// @brief selects elements by example using a hash index //////////////////////////////////////////////////////////////////////////////// static v8::Handle JS_ByExampleHashIndex (v8::Arguments const& argv) { v8::HandleScope scope; // extract and use the simple collection v8::Handle err; TRI_vocbase_col_t const* collection; TRI_document_collection_t* document = TRI_ExtractAndUseSimpleCollection(argv, collection, &err); if (document == 0) { return scope.Close(v8::ThrowException(err)); } // ............................................................................. // inside a read transaction // ............................................................................. TRI_primary_collection_t* primary = &document->base; primary->beginRead(primary); v8::Handle result = ByExampleHashIndexQuery(document, collection, &err, argv); primary->endRead(primary); // ............................................................................. // outside a write transaction // ............................................................................. TRI_ReleaseCollection(collection); return scope.Close(result); } //////////////////////////////////////////////////////////////////////////////// /// @brief selects elements by example using a hash index /// /// It is the callers responsibility to acquire and free the required locks //////////////////////////////////////////////////////////////////////////////// static v8::Handle JS_ByExampleNLHashIndex (v8::Arguments const& argv) { v8::HandleScope scope; // extract and use the simple collection v8::Handle err; TRI_vocbase_col_t const* collection; TRI_document_collection_t* document = TRI_ExtractSimpleCollection(argv, collection, &err); if (document == 0) { return scope.Close(v8::ThrowException(err)); } v8::Handle result = ByExampleHashIndexQuery(document, collection, &err, argv); return scope.Close(result); } //////////////////////////////////////////////////////////////////////////////// /// @brief selects elements by condition using a skiplist index //////////////////////////////////////////////////////////////////////////////// static v8::Handle JS_ByConditionSkiplist (v8::Arguments const& argv) { std::string signature("BY_CONDITION_SKIPLIST(, , , )"); return ExecuteSkiplistQuery(argv, signature, QUERY_CONDITION, true); } //////////////////////////////////////////////////////////////////////////////// /// @brief selects elements by condition using a skiplist index //////////////////////////////////////////////////////////////////////////////// static v8::Handle JS_ByConditionNLSkiplist (v8::Arguments const& argv) { std::string signature("BY_CONDITION_SKIPLIST_NL(, , , )"); return ExecuteSkiplistQuery(argv, signature, QUERY_CONDITION, false); } //////////////////////////////////////////////////////////////////////////////// /// @brief selects elements by example using a skiplist index //////////////////////////////////////////////////////////////////////////////// static v8::Handle JS_ByExampleSkiplist (v8::Arguments const& argv) { std::string signature("BY_EXAMPLE_SKIPLIST(, , , )"); return ExecuteSkiplistQuery(argv, signature, QUERY_EXAMPLE, true); } //////////////////////////////////////////////////////////////////////////////// /// @brief selects elements by example using a skiplist index //////////////////////////////////////////////////////////////////////////////// static v8::Handle JS_ByExampleNLSkiplist (v8::Arguments const& argv) { std::string signature("BY_EXAMPLE_SKIPLIST_NL(, , , )"); return ExecuteSkiplistQuery(argv, signature, QUERY_EXAMPLE, false); } //////////////////////////////////////////////////////////////////////////////// /// @brief selects elements by example using a bitarray index //////////////////////////////////////////////////////////////////////////////// static v8::Handle JS_ByExampleBitarray (v8::Arguments const& argv) { std::string signature("BY_EXAMPLE_BITARRAY(, , , )"); return ExecuteBitarrayQuery(argv, signature, QUERY_EXAMPLE, true); } static v8::Handle JS_ByExampleNLBitarray (v8::Arguments const& argv) { std::string signature("BY_EXAMPLE_BITARRAYNL(, , , )"); return ExecuteBitarrayQuery(argv, signature, QUERY_EXAMPLE, false); } static v8::Handle JS_ByConditionBitarray (v8::Arguments const& argv) { std::string signature("BY_CONDITION_BITARRAY(, , , )"); return ExecuteBitarrayQuery(argv, signature, QUERY_CONDITION, true); } static v8::Handle JS_ByConditionNLBitarray (v8::Arguments const& argv) { std::string signature("BY_CONDITION_BITARRAY_NL(, , , )"); return ExecuteBitarrayQuery(argv, signature, QUERY_CONDITION, false); } //////////////////////////////////////////////////////////////////////////////// /// @brief selects all edges for a set of vertices /// /// @FUN{@FA{edge-collection}.edges(@FA{vertex})} /// /// The @FN{edges} operator finds all edges starting from (outbound) or ending /// in (inbound) @FA{vertex}. /// /// @FUN{@FA{edge-collection}.edges(@FA{vertices})} /// /// The @FN{edges} operator finds all edges starting from (outbound) or ending /// in (inbound) a document from @FA{vertices}, which must a list of documents /// or document handles. /// /// @EXAMPLES /// /// @verbinclude shell-edge-edges //////////////////////////////////////////////////////////////////////////////// static v8::Handle JS_EdgesQuery (v8::Arguments const& argv) { return EdgesQuery(TRI_EDGE_ANY, argv); } //////////////////////////////////////////////////////////////////////////////// /// @brief selects all inbound edges /// /// @FUN{@FA{edge-collection}.inEdges(@FA{vertex})} /// /// The @FN{edges} operator finds all edges ending in (inbound) @FA{vertex}. /// /// @FUN{@FA{edge-collection}.inEdges(@FA{vertices})} /// /// The @FN{edges} operator finds all edges ending in (inbound) a document from /// @FA{vertices}, which must a list of documents or document handles. /// /// @EXAMPLES /// /// @verbinclude shell-edge-in-edges //////////////////////////////////////////////////////////////////////////////// static v8::Handle JS_InEdgesQuery (v8::Arguments const& argv) { return EdgesQuery(TRI_EDGE_IN, argv); } //////////////////////////////////////////////////////////////////////////////// /// @brief selects points near a given coordinate /// /// the caller must ensure all relevant locks are acquired and freed //////////////////////////////////////////////////////////////////////////////// static v8::Handle NearQuery (TRI_document_collection_t* document, TRI_vocbase_col_t const* collection, v8::Handle* err, v8::Arguments const& argv) { v8::HandleScope scope; // expect: NEAR(, , , ) if (argv.Length() != 4) { return scope.Close(v8::ThrowException( TRI_CreateErrorObject(TRI_ERROR_BAD_PARAMETER, "usage: NEAR(, , , )"))); } // extract the index TRI_index_t* idx = TRI_LookupIndexByHandle(document->base.base._vocbase, collection, argv[0], false, err); if (idx == 0) { return scope.Close(v8::ThrowException(*err)); } if (idx->_type != TRI_IDX_TYPE_GEO1_INDEX && idx->_type != TRI_IDX_TYPE_GEO2_INDEX) { return scope.Close(v8::ThrowException(TRI_CreateErrorObject(TRI_ERROR_BAD_PARAMETER, "index must be a geo-index"))); } // extract latitude and longitude double latitude = TRI_ObjectToDouble(argv[1]); double longitude = TRI_ObjectToDouble(argv[2]); // extract the limit TRI_voc_ssize_t limit = (TRI_voc_ssize_t) TRI_ObjectToDouble(argv[3]); // setup result v8::Handle result = v8::Object::New(); v8::Handle documents = v8::Array::New(); result->Set(v8::String::New("documents"), documents); v8::Handle distances = v8::Array::New(); result->Set(v8::String::New("distances"), distances); GeoCoordinates* cors = TRI_NearestGeoIndex(idx, latitude, longitude, limit); if (cors != 0) { StoreGeoResult(collection, cors, documents, distances); } return scope.Close(result); } //////////////////////////////////////////////////////////////////////////////// /// @brief selects points near a given coordinate //////////////////////////////////////////////////////////////////////////////// static v8::Handle JS_NearQuery (v8::Arguments const& argv) { v8::HandleScope scope; // extract and use the simple collection v8::Handle err; TRI_vocbase_col_t const* collection; TRI_document_collection_t* document = TRI_ExtractAndUseSimpleCollection(argv, collection, &err); if (document == 0) { return scope.Close(v8::ThrowException(err)); } // ............................................................................. // inside a read transaction // ............................................................................. TRI_primary_collection_t* primary = &document->base; primary->beginRead(primary); v8::Handle result = NearQuery(document, collection, &err, argv); primary->endRead(primary); // ............................................................................. // outside a write transaction // ............................................................................. TRI_ReleaseCollection(collection); return scope.Close(result); } //////////////////////////////////////////////////////////////////////////////// /// @brief selects points near a given coordinate /// /// It is the callers responsibility to acquire and free the required locks //////////////////////////////////////////////////////////////////////////////// static v8::Handle JS_NearNLQuery (v8::Arguments const& argv) { v8::HandleScope scope; // extract and use the simple collection v8::Handle err; TRI_vocbase_col_t const* collection; TRI_document_collection_t* document = TRI_ExtractSimpleCollection(argv, collection, &err); if (document == 0) { return scope.Close(v8::ThrowException(err)); } v8::Handle result = NearQuery(document, collection, &err, argv); return scope.Close(result); } //////////////////////////////////////////////////////////////////////////////// /// @brief selects all outbound edges /// /// @FUN{@FA{edge-collection}.outEdges(@FA{vertex})} /// /// The @FN{edges} operator finds all edges starting from (outbound) /// @FA{vertices}. /// /// @FUN{@FA{edge-collection}.outEdges(@FA{vertices})} /// /// The @FN{edges} operator finds all edges starting from (outbound) a document /// from @FA{vertices}, which must a list of documents or document handles. /// /// @EXAMPLES /// /// @verbinclude shell-edge-out-edges //////////////////////////////////////////////////////////////////////////////// static v8::Handle JS_OutEdgesQuery (v8::Arguments const& argv) { return EdgesQuery(TRI_EDGE_OUT, argv); } //////////////////////////////////////////////////////////////////////////////// /// @brief selects points within a given radius /// /// the caller must ensure all relevant locks are acquired and freed //////////////////////////////////////////////////////////////////////////////// static v8::Handle WithinQuery (TRI_document_collection_t* document, TRI_vocbase_col_t const* collection, v8::Handle* err, v8::Arguments const& argv) { v8::HandleScope scope; // expect: WITHIN(, , , ) if (argv.Length() != 4) { return scope.Close(v8::ThrowException( TRI_CreateErrorObject(TRI_ERROR_BAD_PARAMETER, "usage: WITHIN(, , , )"))); } // extract the index TRI_index_t* idx = TRI_LookupIndexByHandle(document->base.base._vocbase, collection, argv[0], false, err); if (idx == 0) { return scope.Close(v8::ThrowException(*err)); } if (idx->_type != TRI_IDX_TYPE_GEO1_INDEX && idx->_type != TRI_IDX_TYPE_GEO2_INDEX) { return scope.Close(v8::ThrowException(TRI_CreateErrorObject(TRI_ERROR_BAD_PARAMETER, "index must be a geo-index"))); } // extract latitude and longitude double latitude = TRI_ObjectToDouble(argv[1]); double longitude = TRI_ObjectToDouble(argv[2]); // extract the limit double radius = TRI_ObjectToDouble(argv[3]); // setup result v8::Handle result = v8::Object::New(); v8::Handle documents = v8::Array::New(); result->Set(v8::String::New("documents"), documents); v8::Handle distances = v8::Array::New(); result->Set(v8::String::New("distances"), distances); GeoCoordinates* cors = TRI_WithinGeoIndex(idx, latitude, longitude, radius); if (cors != 0) { StoreGeoResult(collection, cors, documents, distances); } return scope.Close(result); } //////////////////////////////////////////////////////////////////////////////// /// @brief selects points within a given radius //////////////////////////////////////////////////////////////////////////////// static v8::Handle JS_WithinQuery (v8::Arguments const& argv) { v8::HandleScope scope; // extract and use the simple collection v8::Handle err; TRI_vocbase_col_t const* collection; TRI_document_collection_t* document = TRI_ExtractAndUseSimpleCollection(argv, collection, &err); if (document == 0) { return scope.Close(v8::ThrowException(err)); } // ............................................................................. // inside a read transaction // ............................................................................. TRI_primary_collection_t* primary = &document->base; primary->beginRead(primary); v8::Handle result = WithinQuery(document, collection, &err, argv); primary->endRead(primary); // ............................................................................. // outside a write transaction // ............................................................................. TRI_ReleaseCollection(collection); return scope.Close(result); } //////////////////////////////////////////////////////////////////////////////// /// @brief selects points within a given radius /// /// It is the callers responsibility to acquire and free the required locks //////////////////////////////////////////////////////////////////////////////// static v8::Handle JS_WithinNLQuery (v8::Arguments const& argv) { v8::HandleScope scope; // extract the simple collection v8::Handle err; TRI_vocbase_col_t const* collection; TRI_document_collection_t* document = TRI_ExtractSimpleCollection(argv, collection, &err); if (document == 0) { return scope.Close(v8::ThrowException(err)); } v8::Handle result = WithinQuery(document, collection, &err, argv); return scope.Close(result); } //////////////////////////////////////////////////////////////////////////////// /// @} //////////////////////////////////////////////////////////////////////////////// // ----------------------------------------------------------------------------- // --SECTION-- MODULE // ----------------------------------------------------------------------------- // ----------------------------------------------------------------------------- // --SECTION-- public functions // ----------------------------------------------------------------------------- //////////////////////////////////////////////////////////////////////////////// /// @addtogroup VocBase /// @{ //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// /// @brief creates the query functions //////////////////////////////////////////////////////////////////////////////// void TRI_InitV8Queries (v8::Handle context) { v8::HandleScope scope; v8::Handle rt; v8::Isolate* isolate = v8::Isolate::GetCurrent(); TRI_v8_global_t* v8g = (TRI_v8_global_t*) isolate->GetData(); assert(v8g != 0); // ............................................................................. // generate the TRI_vocbase_col_t template // ............................................................................. rt = v8g->VocbaseColTempl; // the _NL functions are the same as their unsuffixed counterparts, just without any locking TRI_AddMethodVocbase(rt, "ALL", JS_AllQuery); TRI_AddMethodVocbase(rt, "ALL_NL", JS_AllNLQuery, true); TRI_AddMethodVocbase(rt, "BY_CONDITION_BITARRAY", JS_ByConditionBitarray); TRI_AddMethodVocbase(rt, "BY_CONDITION_BITARRAY_NL", JS_ByConditionNLBitarray, true); TRI_AddMethodVocbase(rt, "BY_CONDITION_SKIPLIST", JS_ByConditionSkiplist); TRI_AddMethodVocbase(rt, "BY_CONDITION_SKIPLIST_NL", JS_ByConditionNLSkiplist, true); TRI_AddMethodVocbase(rt, "BY_EXAMPLE", JS_ByExampleQuery); TRI_AddMethodVocbase(rt, "BY_EXAMPLE_BITARRAY", JS_ByExampleBitarray); TRI_AddMethodVocbase(rt, "BY_EXAMPLE_BITARRAY_NL", JS_ByExampleNLBitarray, true); TRI_AddMethodVocbase(rt, "BY_EXAMPLE_HASH", JS_ByExampleHashIndex); TRI_AddMethodVocbase(rt, "BY_EXAMPLE_HASH_NL", JS_ByExampleNLHashIndex, true); TRI_AddMethodVocbase(rt, "BY_EXAMPLE_SKIPLIST", JS_ByExampleSkiplist); TRI_AddMethodVocbase(rt, "BY_EXAMPLE_SKIPLIST_NL", JS_ByExampleNLSkiplist, true); TRI_AddMethodVocbase(rt, "edges", JS_EdgesQuery); TRI_AddMethodVocbase(rt, "inEdges", JS_InEdgesQuery); TRI_AddMethodVocbase(rt, "NEAR", JS_NearQuery); TRI_AddMethodVocbase(rt, "NEARL_NL", JS_NearNLQuery, true); TRI_AddMethodVocbase(rt, "outEdges", JS_OutEdgesQuery); TRI_AddMethodVocbase(rt, "WITHIN", JS_WithinQuery); TRI_AddMethodVocbase(rt, "WITHIN_NL", JS_WithinNLQuery, true); } //////////////////////////////////////////////////////////////////////////////// /// @} //////////////////////////////////////////////////////////////////////////////// // Local Variables: // mode: outline-minor // outline-regexp: "^\\(/// @brief\\|/// {@inheritDoc}\\|/// @addtogroup\\|/// @page\\|// --SECTION--\\|/// @\\}\\)" // End: