//////////////////////////////////////////////////////////////////////////////// /// @brief V8-vocbase queries /// /// @file /// /// DISCLAIMER /// /// Copyright 2004-2013 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-2013, triAGENS GmbH, Cologne, Germany //////////////////////////////////////////////////////////////////////////////// #include "v8-query.h" #include "BasicsC/logging.h" #include "BasicsC/random.h" #include "BasicsC/string-buffer.h" #include "GeoIndex/geo-index.h" #include "HashIndex/hash-index.h" #include "FulltextIndex/fulltext-index.h" #include "FulltextIndex/fulltext-result.h" #include "FulltextIndex/fulltext-query.h" #include "SkipLists/skiplistIndex.h" #include "Utils/Barrier.h" #include "Utils/CollectionNameResolver.h" #include "Utils/EmbeddableTransaction.h" #include "Utils/SingleCollectionReadOnlyTransaction.h" #include "Utils/V8TransactionContext.h" #include "V8/v8-globals.h" #include "V8/v8-conv.h" #include "V8/v8-utils.h" #include "V8Server/v8-vocbase.h" #include "VocBase/edge-collection.h" using namespace std; using namespace triagens::basics; using namespace triagens::arango; // ----------------------------------------------------------------------------- // --SECTION-- private defines // ----------------------------------------------------------------------------- //////////////////////////////////////////////////////////////////////////////// /// @addtogroup VocBase /// @{ //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// /// @brief shortcut for read-only transaction class type //////////////////////////////////////////////////////////////////////////////// #define ReadTransactionType SingleCollectionReadOnlyTransaction > //////////////////////////////////////////////////////////////////////////////// /// @brief shortcut to wrap a shaped-json object in a read-only transaction //////////////////////////////////////////////////////////////////////////////// #define WRAP_SHAPED_JSON(...) TRI_WrapShapedJson(__VA_ARGS__) //////////////////////////////////////////////////////////////////////////////// /// @brief free barrier if set //////////////////////////////////////////////////////////////////////////////// #define FREE_BARRIER(barrier) \ if (barrier != 0) { \ TRI_FreeBarrier(barrier); \ barrier = 0; \ } //////////////////////////////////////////////////////////////////////////////// /// @} //////////////////////////////////////////////////////////////////////////////// // ----------------------------------------------------------------------------- // --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 return an empty result set //////////////////////////////////////////////////////////////////////////////// static v8::Handle EmptyResult () { v8::HandleScope scope; v8::Handle result = v8::Object::New(); result->Set(v8::String::New("documents"), v8::Array::New()); result->Set(v8::String::New("total"), v8::Number::New(0)); result->Set(v8::String::New("count"), v8::Number::New(0)); return scope.Close(result); } //////////////////////////////////////////////////////////////////////////////// /// @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() && ! argv[pos]->IsUndefined()) { skip = (TRI_voc_size_t) TRI_ObjectToDouble(argv[pos]); } if (pos + 1 < (size_t) argv.Length() && ! argv[pos + 1]->IsNull() && ! argv[pos + 1]->IsUndefined()) { 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 = (size_t) skip; if (e < s) { s = (size_t) 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) { int64_t sum = (int64_t) s + (int64_t) limit; if (sum < (int64_t) e) { if (sum >= (int64_t) TRI_QRY_NO_LIMIT) { e = TRI_QRY_NO_LIMIT; } else { e = (size_t) sum; } } } } //////////////////////////////////////////////////////////////////////////////// /// @brief cleans up the example object //////////////////////////////////////////////////////////////////////////////// static void CleanupExampleObject (TRI_memory_zone_t* zone, 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(zone, 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 const& 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); if (pids == 0) { // out of memory *err = TRI_CreateErrorObject(TRI_ERROR_OUT_OF_MEMORY); return TRI_ERROR_OUT_OF_MEMORY; } values = (TRI_shaped_json_t**) TRI_Allocate(TRI_UNKNOWN_MEM_ZONE, n * sizeof(TRI_shaped_json_t*), false); if (values == 0) { // out of memory TRI_Free(TRI_UNKNOWN_MEM_ZONE, pids); pids = 0; *err = TRI_CreateErrorObject(TRI_ERROR_OUT_OF_MEMORY); return TRI_ERROR_OUT_OF_MEMORY; } // 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->lookupAttributePathByName(shaper, *keyStr); values[i] = TRI_ShapedJsonV8Object(val, shaper, false, false); if (pids[i] == 0 || values[i] == 0) { // no attribute path found. this means the result will be empty CleanupExampleObject(shaper->_memoryZone, i, pids, values); return TRI_RESULT_ELEMENT_NOT_FOUND; } } if (*keyStr == 0 || pids[i] == 0 || values[i] == 0) { CleanupExampleObject(shaper->_memoryZone, i, pids, values); if (*keyStr == 0) { *err = TRI_CreateErrorObject(TRI_ERROR_BAD_PARAMETER, "cannot convert attribute path to UTF8"); } else { *err = TRI_CreateErrorObject(TRI_ERROR_BAD_PARAMETER, "cannot convert value to JSON"); } return TRI_ERROR_BAD_PARAMETER; } } 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; size_t numEq = 0; size_t lastNonEq = 0; TRI_json_t* parameters = TRI_CreateListJson(TRI_UNKNOWN_MEM_ZONE); 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_ObjectToJson(value); if (json == 0) { goto MEM_ERROR; } std::string opValue = TRI_ObjectToString(op); if (opValue == "==") { // equality comparison if (lastNonEq > 0) { TRI_FreeJson(TRI_UNKNOWN_MEM_ZONE, json); 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 TRI_FreeJson(TRI_UNKNOWN_MEM_ZONE, json); 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 TRI_FreeJson(TRI_UNKNOWN_MEM_ZONE, json); goto MEM_ERROR; } lastNonEq = i; TRI_json_t* cloned = TRI_CopyJson(TRI_UNKNOWN_MEM_ZONE, parameters); if (cloned == 0) { TRI_FreeJson(TRI_UNKNOWN_MEM_ZONE, json); 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; // 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_ObjectToJson(value); if (json == 0) { TRI_FreeJson(TRI_UNKNOWN_MEM_ZONE, parameters); return 0; } // .................................................................... // 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); if (json == 0) { // OOM TRI_FreeJson(TRI_UNKNOWN_MEM_ZONE, parameters); return 0; } json->_type = TRI_JSON_UNUSED; } // ...................................................................... // Check and ensure we have a json object defined before we store it. // ...................................................................... assert(json != 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_ObjectToJson(value); if (json == 0) { 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 destroys the example object for a hash index //////////////////////////////////////////////////////////////////////////////// static void DestroySearchValue (TRI_memory_zone_t* zone, TRI_index_search_value_t& value) { size_t n; n = value._length; for (size_t j = 0; j < n; ++j) { TRI_DestroyShapedJson(zone, &value._values[j]); } TRI_Free(TRI_CORE_MEM_ZONE, value._values); } //////////////////////////////////////////////////////////////////////////////// /// @brief sets up the example object for a hash index //////////////////////////////////////////////////////////////////////////////// static int SetupSearchValue (TRI_vector_t const* paths, v8::Handle example, TRI_shaper_t* shaper, TRI_index_search_value_t& result, v8::Handle* err) { size_t n; // extract attribute paths n = paths->_length; // setup storage result._length = n; result._values = (TRI_shaped_json_t*) TRI_Allocate(TRI_CORE_MEM_ZONE, n * sizeof(TRI_shaped_json_t), true); // convert for (size_t i = 0; i < n; ++i) { TRI_shape_pid_t pid = * (TRI_shape_pid_t*) TRI_AtVector(paths, i); assert(pid != 0); char const* name = TRI_AttributeNameShapePid(shaper, pid); if (name == NULL) { DestroySearchValue(shaper->_memoryZone, result); *err = TRI_CreateErrorObject(TRI_ERROR_INTERNAL, "shaper failed"); return TRI_ERROR_BAD_PARAMETER; } v8::Handle key = v8::String::New(name); int res; if (example->HasOwnProperty(key)) { v8::Handle val = example->Get(key); res = TRI_FillShapedJsonV8Object(val, &result._values[i], shaper, false, false); } else { res = TRI_FillShapedJsonV8Object(v8::Null(), &result._values[i], shaper, false, false); } if (res != TRI_ERROR_NO_ERROR) { DestroySearchValue(shaper->_memoryZone, result); if (res != TRI_RESULT_ELEMENT_NOT_FOUND) { *err = TRI_CreateErrorObject(res, "cannot convert value to JSON"); } return res; } } 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) { v8::HandleScope scope; // expecting index, example, skip, and limit if (argv.Length() < 2) { TRI_V8_EXCEPTION_USAGE(scope, signature.c_str()); } if (! argv[1]->IsObject()) { std::string msg; if (type == QUERY_EXAMPLE) { msg = " must be an object"; } else { msg = " must be an object"; } TRI_V8_TYPE_ERROR(scope, msg.c_str()); } TRI_vocbase_col_t const* col; col = TRI_UnwrapClass(argv.Holder(), TRI_GetVocBaseColType()); if (col == 0) { TRI_V8_EXCEPTION_INTERNAL(scope, "cannot extract collection"); } TRI_SHARDING_COLLECTION_NOT_YET_IMPLEMENTED(scope, col); CollectionNameResolver resolver(col->_vocbase); ReadTransactionType trx(col->_vocbase, resolver, col->_cid); int res = trx.begin(); if (res != TRI_ERROR_NO_ERROR) { TRI_V8_EXCEPTION(scope, res); } v8::Handle err; TRI_primary_collection_t* primary = trx.primaryCollection(); TRI_shaper_t* shaper = primary->_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 // ............................................................................. trx.lockRead(); // extract the index TRI_index_t* idx = TRI_LookupIndexByHandle(col, argv[0], false, &err); if (idx == 0) { return scope.Close(v8::ThrowException(err)); } if (idx->_type != TRI_IDX_TYPE_SKIPLIST_INDEX) { TRI_V8_EXCEPTION(scope, TRI_ERROR_ARANGO_NO_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 == 0) { TRI_V8_EXCEPTION(scope, TRI_ERROR_BAD_PARAMETER); } TRI_skiplist_iterator_t* skiplistIterator = TRI_LookupSkiplistIndex(idx, skiplistOperator); if (skiplistIterator == 0) { int res = TRI_errno(); if (res == TRI_RESULT_ELEMENT_NOT_FOUND) { return scope.Close(EmptyResult()); } TRI_V8_EXCEPTION(scope, TRI_ERROR_ARANGO_NO_INDEX); } TRI_voc_ssize_t total = 0; TRI_voc_size_t count = 0; bool error = false; TRI_barrier_t* barrier = TRI_CreateBarrierElement(&primary->_barrierList); if (barrier == 0) { TRI_FreeSkiplistIterator(skiplistIterator); TRI_V8_EXCEPTION_MEMORY(scope); } assert(barrier != 0); while (true) { TRI_skiplist_index_element_t* indexElement = skiplistIterator->_next(skiplistIterator); if (indexElement == NULL) { break; } ++total; if (total > skip && count < limit) { v8::Handle doc = WRAP_SHAPED_JSON(trx, col->_cid, (TRI_doc_mptr_t const*) indexElement->_document, barrier); if (doc.IsEmpty()) { error = true; break; } else { documents->Set(count, doc); ++count; } } } trx.finish(res); // ............................................................................. // 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 (error) { FREE_BARRIER(barrier); TRI_V8_EXCEPTION_MEMORY(scope); } if (count == 0) { FREE_BARRIER(barrier); } 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) { TRI_doc_mptr_t* indexElement; TRI_bitarray_index_t* baIndex; indexElement = (TRI_doc_mptr_t*) indexIterator->_next(indexIterator); if (indexElement == NULL) { return false; } baIndex = (TRI_bitarray_index_t*) indexIterator->_index; if (baIndex == NULL) { return false; } return true; } static v8::Handle ExecuteBitarrayQuery (v8::Arguments const& argv, std::string const& signature, const query_t type) { v8::HandleScope scope; // ........................................................................... // Check the parameters, expecting index, example, skip, and limit // e.g. ("110597/962565", {"x":1}, null, null) // ........................................................................... if (argv.Length() < 2) { TRI_V8_EXCEPTION_USAGE(scope, signature.c_str()); } // ........................................................................... // Check that the second parameter is an associative array (json object) // ........................................................................... if (! argv[1]->IsObject()) { std::string msg; if (type == QUERY_EXAMPLE) { msg = " must be an object"; } else { msg = " must be an object"; } TRI_V8_EXCEPTION_PARAMETER(scope, msg); } // ............................................................................. // extract skip and limit // ............................................................................. TRI_voc_ssize_t skip; TRI_voc_size_t limit; ExtractSkipAndLimit(argv, 2, skip, limit); TRI_vocbase_col_t const* col; col = TRI_UnwrapClass(argv.Holder(), TRI_GetVocBaseColType()); if (col == 0) { TRI_V8_EXCEPTION_INTERNAL(scope, "cannot extract collection"); } TRI_SHARDING_COLLECTION_NOT_YET_IMPLEMENTED(scope, col); CollectionNameResolver resolver(col->_vocbase); ReadTransactionType trx(col->_vocbase, resolver, col->_cid); int res = trx.begin(); if (res != TRI_ERROR_NO_ERROR) { TRI_V8_EXCEPTION(scope, res); } TRI_primary_collection_t* primary = trx.primaryCollection(); TRI_shaper_t* shaper = primary->_shaper; // ............................................................................. // 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 // ............................................................................. trx.lockRead(); // ............................................................................. // extract the index // ............................................................................. v8::Handle err; TRI_index_t* idx = TRI_LookupIndexByHandle(col, argv[0], false, &err); if (idx == 0) { return scope.Close(v8::ThrowException(err)); } if (idx->_type != TRI_IDX_TYPE_BITARRAY_INDEX) { TRI_V8_EXCEPTION(scope, TRI_ERROR_ARANGO_NO_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 TRI_V8_EXCEPTION(scope, TRI_ERROR_BAD_PARAMETER); } // ............................................................................. // 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_voc_ssize_t total = 0; TRI_voc_size_t count = 0; bool error = false; TRI_barrier_t* barrier = TRI_CreateBarrierElement(&primary->_barrierList); if (barrier == 0) { if (indexIterator != NULL) { TRI_FreeIndexIterator(indexIterator); } TRI_V8_EXCEPTION_MEMORY(scope); } assert(barrier != 0); 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) { v8::Handle doc = WRAP_SHAPED_JSON(trx, col->_cid, data, barrier); if (doc.IsEmpty()) { 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 } trx.finish(res); // ............................................................................. // 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 (error) { FREE_BARRIER(barrier); TRI_V8_EXCEPTION_MEMORY(scope); } if (count == 0) { FREE_BARRIER(barrier); } 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_INSTANCE SortGeo #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 SortGeoFSRT_Rand = 0; static uint32_t SortGeoRandomGenerator (void) { return (SortGeoFSRT_Rand = SortGeoFSRT_Rand * 31415 + 27818); } #define FSRT__RAND ((fs_b) + FSRT__UNIT * (SortGeoRandomGenerator() % FSRT__DIST(fs_e,fs_b,FSRT__SIZE))) #include "BasicsC/fsrt.inc" #undef FSRT__RAND //////////////////////////////////////////////////////////////////////////////// /// @brief creates a geo result //////////////////////////////////////////////////////////////////////////////// static int StoreGeoResult (ReadTransactionType& trx, 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 TRI_ERROR_NO_ERROR; } gtr = (tmp = (geo_coordinate_distance_t*) TRI_Allocate(TRI_UNKNOWN_MEM_ZONE, sizeof(geo_coordinate_distance_t) * n, false)); if (gtr == 0) { GeoIndex_CoordinatesFree(cors); return TRI_ERROR_OUT_OF_MEMORY; } 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); if (barrier == 0) { TRI_Free(TRI_UNKNOWN_MEM_ZONE, tmp); return TRI_ERROR_OUT_OF_MEMORY; } // copy the documents bool error = false; for (gtr = tmp, i = 0; gtr < gnd; ++gtr, ++i) { v8::Handle doc = WRAP_SHAPED_JSON(trx, collection->_cid, (TRI_doc_mptr_t const*) gtr->_data, barrier); if (doc.IsEmpty()) { error = true; break; } documents->Set(i, doc); distances->Set(i, v8::Number::New(gtr->_distance)); } TRI_Free(TRI_UNKNOWN_MEM_ZONE, tmp); if (error) { FREE_BARRIER(barrier); return TRI_ERROR_OUT_OF_MEMORY; } return TRI_ERROR_NO_ERROR; } //////////////////////////////////////////////////////////////////////////////// /// @} //////////////////////////////////////////////////////////////////////////////// // ----------------------------------------------------------------------------- // --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; TRI_vocbase_col_t const* col; col = TRI_UnwrapClass(argv.Holder(), TRI_GetVocBaseColType()); if (col == 0) { TRI_V8_EXCEPTION_INTERNAL(scope, "cannot extract collection"); } TRI_SHARDING_COLLECTION_NOT_YET_IMPLEMENTED(scope, col); if (col->_type != TRI_COL_TYPE_EDGE) { TRI_V8_EXCEPTION(scope, TRI_ERROR_ARANGO_COLLECTION_TYPE_INVALID); } CollectionNameResolver resolver(col->_vocbase); ReadTransactionType trx(col->_vocbase, resolver, col->_cid); int res = trx.begin(); if (res != TRI_ERROR_NO_ERROR) { TRI_V8_EXCEPTION(scope, res); } TRI_primary_collection_t* primary = trx.primaryCollection(); // first and only argument schould be a list of document idenfifier if (argv.Length() != 1) { switch (direction) { case TRI_EDGE_IN: TRI_V8_EXCEPTION_USAGE(scope, "inEdges()"); case TRI_EDGE_OUT: TRI_V8_EXCEPTION_USAGE(scope, "outEdges()"); case TRI_EDGE_ANY: default: { TRI_V8_EXCEPTION_USAGE(scope, "edges()"); } } } // setup result v8::Handle documents = v8::Array::New(); // ............................................................................. // inside a read transaction // ............................................................................. trx.lockRead(); uint32_t count = 0; bool error = false; TRI_barrier_t* barrier = TRI_CreateBarrierElement(&primary->_barrierList); if (barrier == 0) { TRI_V8_EXCEPTION_MEMORY(scope); } // argument is a list of vertices if (argv[0]->IsArray()) { v8::Handle vertices = v8::Handle::Cast(argv[0]); const 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_key_t key = 0; res = TRI_ParseVertex(resolver, cid, key, vertices->Get(i), true); if (res != TRI_ERROR_NO_ERROR) { // error is just ignored continue; } edges = TRI_LookupEdgesDocumentCollection((TRI_document_collection_t*) primary, direction, cid, key); if (key != 0) { TRI_FreeString(TRI_CORE_MEM_ZONE, key); } for (size_t j = 0; j < edges._length; ++j) { v8::Handle doc = WRAP_SHAPED_JSON(trx, col->_cid, (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); if (error) { break; } } } // argument is a single vertex else { TRI_vector_pointer_t edges; TRI_voc_key_t key = 0; TRI_voc_cid_t cid; res = TRI_ParseVertex(resolver, cid, key, argv[0], true); if (res != TRI_ERROR_NO_ERROR) { FREE_BARRIER(barrier); TRI_V8_EXCEPTION(scope, res); } edges = TRI_LookupEdgesDocumentCollection((TRI_document_collection_t*) primary, direction, cid, key); if (key != 0) { TRI_FreeString(TRI_CORE_MEM_ZONE, key); } for (size_t j = 0; j < edges._length; ++j) { v8::Handle doc = WRAP_SHAPED_JSON(trx, col->_cid, (TRI_doc_mptr_t const*) edges._buffer[j], barrier); if (doc.IsEmpty()) { error = true; break; } else { documents->Set(count, doc); ++count; } } TRI_DestroyVectorPointer(&edges); } trx.finish(res); // ............................................................................. // outside a read transaction // ............................................................................. if (error) { FREE_BARRIER(barrier); TRI_V8_EXCEPTION_MEMORY(scope); } if (count == 0) { FREE_BARRIER(barrier); } return scope.Close(documents); } //////////////////////////////////////////////////////////////////////////////// /// @} //////////////////////////////////////////////////////////////////////////////// // ----------------------------------------------------------------------------- // --SECTION-- javascript functions // ----------------------------------------------------------------------------- //////////////////////////////////////////////////////////////////////////////// /// @addtogroup VocBase /// @{ //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// /// @brief selects all documents from a collection //////////////////////////////////////////////////////////////////////////////// static v8::Handle JS_AllQuery (v8::Arguments const& argv) { v8::HandleScope scope; // expecting two arguments if (argv.Length() != 2) { TRI_V8_EXCEPTION_USAGE(scope, "ALL(, )"); } TRI_vocbase_col_t const* col; col = TRI_UnwrapClass(argv.Holder(), TRI_GetVocBaseColType()); if (col == 0) { TRI_V8_EXCEPTION_INTERNAL(scope, "cannot extract collection"); } TRI_SHARDING_COLLECTION_NOT_YET_IMPLEMENTED(scope, col); // extract skip and limit TRI_voc_ssize_t skip; TRI_voc_size_t limit; ExtractSkipAndLimit(argv, 0, skip, limit); TRI_barrier_t* barrier = 0; uint32_t total = 0; vector docs; CollectionNameResolver resolver(col->_vocbase); ReadTransactionType trx(col->_vocbase, resolver, col->_cid); int res = trx.begin(); if (res != TRI_ERROR_NO_ERROR) { TRI_V8_EXCEPTION(scope, res); } res = trx.read(docs, &barrier, skip, limit, &total); res = trx.finish(res); if (res != TRI_ERROR_NO_ERROR) { FREE_BARRIER(barrier); TRI_V8_EXCEPTION(scope, res); } const size_t n = docs.size(); uint32_t count = 0; if (n > 0) { TRI_ASSERT_MAINTAINER(barrier != 0); } // setup result v8::Handle result = v8::Object::New(); v8::Handle documents = v8::Array::New(n); // reserve full capacity in one go result->Set(v8::String::New("documents"), documents); for (size_t i = 0; i < n; ++i) { v8::Handle doc = WRAP_SHAPED_JSON(trx, col->_cid, &docs[i], barrier); if (doc.IsEmpty()) { FREE_BARRIER(barrier); TRI_V8_EXCEPTION_MEMORY(scope); } else { documents->Set(count++, doc); } } result->Set(v8::String::New("total"), v8::Number::New(total)); result->Set(v8::String::New("count"), v8::Number::New(count)); if (count == 0) { FREE_BARRIER(barrier); } return scope.Close(result); } //////////////////////////////////////////////////////////////////////////////// /// @brief selects documents from a collection, using an offset into the /// primary index. this can be used for incremental access //////////////////////////////////////////////////////////////////////////////// static v8::Handle JS_OffsetQuery (v8::Arguments const& argv) { v8::HandleScope scope; // expecting two arguments if (argv.Length() != 4) { TRI_V8_EXCEPTION_USAGE(scope, "OFFSET(, , , )"); } TRI_vocbase_col_t const* col; col = TRI_UnwrapClass(argv.Holder(), TRI_GetVocBaseColType()); if (col == 0) { TRI_V8_EXCEPTION_INTERNAL(scope, "cannot extract collection"); } TRI_SHARDING_COLLECTION_NOT_YET_IMPLEMENTED(scope, col); TRI_voc_size_t internalSkip = (TRI_voc_size_t) TRI_ObjectToDouble(argv[0]); TRI_voc_size_t batchSize = (TRI_voc_size_t) TRI_ObjectToDouble(argv[1]); // extract skip and limit TRI_voc_ssize_t skip; TRI_voc_size_t limit; ExtractSkipAndLimit(argv, 2, skip, limit); TRI_barrier_t* barrier = 0; uint32_t total = 0; vector docs; CollectionNameResolver resolver(col->_vocbase); ReadTransactionType trx(col->_vocbase, resolver, col->_cid); int res = trx.begin(); if (res != TRI_ERROR_NO_ERROR) { TRI_V8_EXCEPTION(scope, res); } res = trx.readOffset(docs, &barrier, internalSkip, batchSize, skip, &total); res = trx.finish(res); if (res != TRI_ERROR_NO_ERROR) { FREE_BARRIER(barrier); TRI_V8_EXCEPTION(scope, res); } const size_t n = docs.size(); uint32_t count = 0; if (n > 0) { TRI_ASSERT_MAINTAINER(barrier != 0); } // setup result v8::Handle result = v8::Object::New(); v8::Handle documents = v8::Array::New(n); // reserve full capacity in one go result->Set(v8::String::New("documents"), documents); for (size_t i = 0; i < n; ++i) { v8::Handle document = WRAP_SHAPED_JSON(trx, col->_cid, &docs[i], barrier); if (document.IsEmpty()) { FREE_BARRIER(barrier); TRI_V8_EXCEPTION_MEMORY(scope); } else { documents->Set(count++, document); } } result->Set(v8::String::New("total"), v8::Number::New(total)); result->Set(v8::String::New("count"), v8::Number::New(count)); result->Set(v8::String::New("skip"), v8::Number::New(internalSkip)); if (count == 0) { FREE_BARRIER(barrier); } return scope.Close(result); } //////////////////////////////////////////////////////////////////////////////// /// @brief selects a random document /// /// @FUN{@FA{collection}.any()} /// /// The @FN{any} method returns a random document from the collection. It returns /// @LIT{null} if the collection is empty. /// /// @EXAMPLES /// /// @code /// arangod> db.example.any() /// { "_id" : "example/222716379559", "_rev" : "222716379559", "Hello" : "World" } /// @endcode //////////////////////////////////////////////////////////////////////////////// static v8::Handle JS_AnyQuery (v8::Arguments const& argv) { v8::HandleScope scope; TRI_vocbase_col_t const* col; col = TRI_UnwrapClass(argv.Holder(), TRI_GetVocBaseColType()); if (col == 0) { TRI_V8_EXCEPTION_INTERNAL(scope, "cannot extract collection"); } TRI_barrier_t* barrier = 0; TRI_doc_mptr_t document; document._data = 0; document._key = 0; CollectionNameResolver resolver(col->_vocbase); ReadTransactionType trx(col->_vocbase, resolver, col->_cid); int res = trx.begin(); if (res != TRI_ERROR_NO_ERROR) { TRI_V8_EXCEPTION(scope, res); } res = trx.readRandom(&document, &barrier); res = trx.finish(res); if (res != TRI_ERROR_NO_ERROR) { FREE_BARRIER(barrier); TRI_V8_EXCEPTION(scope, res); } if (document._data == 0 || document._key == 0) { FREE_BARRIER(barrier); return scope.Close(v8::Null()); } v8::Handle doc = WRAP_SHAPED_JSON(trx, col->_cid, &document, barrier); if (doc.IsEmpty()) { FREE_BARRIER(barrier); TRI_V8_EXCEPTION_MEMORY(scope); } return scope.Close(doc); } //////////////////////////////////////////////////////////////////////////////// /// @brief selects documents by example (not using any index) //////////////////////////////////////////////////////////////////////////////// static v8::Handle JS_ByExampleQuery (v8::Arguments const& argv) { v8::HandleScope scope; // expecting example, skip, limit if (argv.Length() < 1) { TRI_V8_EXCEPTION_USAGE(scope, "BY_EXAMPLE(, , )"); } // extract the example if (! argv[0]->IsObject()) { TRI_V8_TYPE_ERROR(scope, " must be an object"); } TRI_vocbase_col_t const* col; col = TRI_UnwrapClass(argv.Holder(), TRI_GetVocBaseColType()); if (col == 0) { TRI_V8_EXCEPTION_INTERNAL(scope, "cannot extract collection"); } TRI_SHARDING_COLLECTION_NOT_YET_IMPLEMENTED(scope, col); CollectionNameResolver resolver(col->_vocbase); ReadTransactionType trx(col->_vocbase, resolver, col->_cid); int res = trx.begin(); if (res != TRI_ERROR_NO_ERROR) { TRI_V8_EXCEPTION(scope, res); } TRI_primary_collection_t* primary = trx.primaryCollection(); TRI_shaper_t* shaper = primary->_shaper; 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 = 0; size_t n; v8::Handle err; res = SetupExampleObject(example, shaper, n, pids, values, &err); if (res == TRI_RESULT_ELEMENT_NOT_FOUND) { // empty result return scope.Close(EmptyResult()); } if (res != TRI_ERROR_NO_ERROR) { 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 // ............................................................................. trx.lockRead(); // find documents by example TRI_vector_t filtered = TRI_SelectByExample(trx.trxCollection(), n, pids, values); // convert to list of shaped jsons size_t total = filtered._length; size_t count = 0; bool error = false; TRI_barrier_t* barrier = 0; if (total > 0) { barrier = TRI_CreateBarrierElement(&primary->_barrierList); if (barrier == 0) { TRI_DestroyVector(&filtered); TRI_V8_EXCEPTION_MEMORY(scope); } assert(barrier != 0); size_t s, e; CalculateSkipLimitSlice(filtered._length, skip, limit, s, e); for (size_t j = s; j < e; ++j) { TRI_doc_mptr_t* mptr = (TRI_doc_mptr_t*) TRI_AtVector(&filtered, j); v8::Handle doc = WRAP_SHAPED_JSON(trx, col->_cid, mptr, barrier); if (doc.IsEmpty()) { error = true; break; } else { documents->Set(count++, doc); } } } TRI_DestroyVector(&filtered); trx.finish(res); // ............................................................................. // outside a write transaction // ............................................................................. result->Set(v8::String::New("total"), v8::Integer::New(total)); result->Set(v8::String::New("count"), v8::Integer::New(count)); CleanupExampleObject(shaper->_memoryZone, n, pids, values); if (error) { FREE_BARRIER(barrier); TRI_V8_EXCEPTION_MEMORY(scope); } if (count == 0) { FREE_BARRIER(barrier); } return scope.Close(result); } //////////////////////////////////////////////////////////////////////////////// /// @brief selects documents by example using a hash index /// /// It is the callers responsibility to acquire and free the required locks //////////////////////////////////////////////////////////////////////////////// static v8::Handle ByExampleHashIndexQuery (ReadTransactionType& trx, 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) { TRI_V8_EXCEPTION_USAGE(scope, "EXAMPLE_HASH(, , , )"); } // extract the example if (! argv[1]->IsObject()) { TRI_V8_TYPE_ERROR(scope, " 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(collection, argv[0], false, err); if (idx == 0) { return scope.Close(v8::ThrowException(*err)); } if (idx->_type != TRI_IDX_TYPE_HASH_INDEX) { TRI_V8_EXCEPTION(scope, TRI_ERROR_ARANGO_NO_INDEX); } TRI_hash_index_t* hashIndex = (TRI_hash_index_t*) idx; // convert the example (index is locked by lockRead) TRI_index_search_value_t searchValue; TRI_primary_collection_t* primary = trx.primaryCollection(); TRI_shaper_t* shaper = primary->_shaper; int res = SetupSearchValue(&hashIndex->_paths, example, shaper, searchValue, err); if (res != TRI_ERROR_NO_ERROR) { if (res == TRI_RESULT_ELEMENT_NOT_FOUND) { return scope.Close(EmptyResult()); } return scope.Close(v8::ThrowException(*err)); } // find the matches TRI_index_result_t list = TRI_LookupHashIndex(idx, &searchValue); DestroySearchValue(shaper->_memoryZone, searchValue); // convert result size_t total = list._length; size_t count = 0; bool error = false; TRI_barrier_t* barrier = 0; if (0 < total) { barrier = TRI_CreateBarrierElement(&primary->_barrierList); if (barrier == 0) { TRI_DestroyIndexResult(&list); TRI_V8_EXCEPTION_MEMORY(scope); } size_t s, e; CalculateSkipLimitSlice(total, skip, limit, s, e); for (size_t i = s; i < e; ++i) { v8::Handle doc = WRAP_SHAPED_JSON(trx, collection->_cid, list._documents[i], barrier); if (doc.IsEmpty()) { error = true; break; } else { documents->Set(count++, doc); } } } // free data allocated by hash index result TRI_DestroyIndexResult(&list); result->Set(v8::String::New("total"), v8::Number::New((double) total)); result->Set(v8::String::New("count"), v8::Number::New(count)); if (error) { FREE_BARRIER(barrier); TRI_V8_EXCEPTION_MEMORY(scope); } if (count == 0) { FREE_BARRIER(barrier); } return scope.Close(result); } //////////////////////////////////////////////////////////////////////////////// /// @brief selects documents by example using a hash index //////////////////////////////////////////////////////////////////////////////// static v8::Handle JS_ByExampleHashIndex (v8::Arguments const& argv) { v8::HandleScope scope; TRI_vocbase_col_t const* col; col = TRI_UnwrapClass(argv.Holder(), TRI_GetVocBaseColType()); if (col == 0) { TRI_V8_EXCEPTION_INTERNAL(scope, "cannot extract collection"); } TRI_SHARDING_COLLECTION_NOT_YET_IMPLEMENTED(scope, col); CollectionNameResolver resolver(col->_vocbase); ReadTransactionType trx(col->_vocbase, resolver, col->_cid); int res = trx.begin(); if (res != TRI_ERROR_NO_ERROR) { TRI_V8_EXCEPTION(scope, res); } v8::Handle err; // ............................................................................. // inside a read transaction // ............................................................................. trx.lockRead(); v8::Handle result = ByExampleHashIndexQuery(trx, col, &err, argv); trx.finish(res); // ............................................................................. // outside a write transaction // ............................................................................. return scope.Close(result); } //////////////////////////////////////////////////////////////////////////////// /// @brief selects documents by condition using a skiplist index //////////////////////////////////////////////////////////////////////////////// static v8::Handle JS_ByConditionSkiplist (v8::Arguments const& argv) { std::string const signature("BY_CONDITION_SKIPLIST(, , , )"); return ExecuteSkiplistQuery(argv, signature, QUERY_CONDITION); } //////////////////////////////////////////////////////////////////////////////// /// @brief selects documents by example using a skiplist index //////////////////////////////////////////////////////////////////////////////// static v8::Handle JS_ByExampleSkiplist (v8::Arguments const& argv) { std::string const signature("BY_EXAMPLE_SKIPLIST(, , , )"); return ExecuteSkiplistQuery(argv, signature, QUERY_EXAMPLE); } //////////////////////////////////////////////////////////////////////////////// /// @brief selects documents by example using a bitarray index //////////////////////////////////////////////////////////////////////////////// static v8::Handle JS_ByExampleBitarray (v8::Arguments const& argv) { std::string const signature("BY_EXAMPLE_BITARRAY(, , , )"); return ExecuteBitarrayQuery(argv, signature, QUERY_EXAMPLE); } //////////////////////////////////////////////////////////////////////////////// /// @brief selects documents by condition using a bitarray index //////////////////////////////////////////////////////////////////////////////// static v8::Handle JS_ByConditionBitarray (v8::Arguments const& argv) { std::string const signature("BY_CONDITION_BITARRAY(, , , )"); return ExecuteBitarrayQuery(argv, signature, QUERY_CONDITION); } typedef struct collection_checksum_s { TRI_string_buffer_t _buffer; CollectionNameResolver* _resolver; uint32_t _checksum; } collection_checksum_t; //////////////////////////////////////////////////////////////////////////////// /// @brief callback for checksum calculation, WR = with _rid, WD = with data //////////////////////////////////////////////////////////////////////////////// template static bool ChecksumCalculator (TRI_doc_mptr_t const* mptr, TRI_primary_collection_t* primary, void* data) { TRI_df_marker_t const* marker = static_cast(mptr->_data); collection_checksum_t* helper = static_cast(data); uint32_t localCrc; if (marker->_type == TRI_DOC_MARKER_KEY_DOCUMENT) { localCrc = TRI_Crc32HashString(mptr->_key); if (WR) { localCrc += TRI_Crc32HashPointer(&mptr->_rid, sizeof(TRI_voc_rid_t)); } } else if (marker->_type == TRI_DOC_MARKER_KEY_EDGE) { TRI_doc_edge_key_marker_t const* e = (TRI_doc_edge_key_marker_t const*) marker; // must convert _rid, _fromCid, _toCid into strings for portability localCrc = TRI_Crc32HashString(mptr->_key); if (WR) { localCrc += TRI_Crc32HashPointer(&mptr->_rid, sizeof(TRI_voc_rid_t)); } #ifndef TRI_ENABLE_CLUSTER const string extra = helper->_resolver->getCollectionName(e->_toCid) + TRI_DOCUMENT_HANDLE_SEPARATOR_CHR + string(((char*) marker) + e->_offsetToKey) + helper->_resolver->getCollectionName(e->_fromCid) + TRI_DOCUMENT_HANDLE_SEPARATOR_CHR + string(((char*) marker) + e->_offsetFromKey); #else const string extra = helper->_resolver->getCollectionNameCluster(e->_toCid) + TRI_DOCUMENT_HANDLE_SEPARATOR_CHR + string(((char*) marker) + e->_offsetToKey) + helper->_resolver->getCollectionNameCluster(e->_fromCid) + TRI_DOCUMENT_HANDLE_SEPARATOR_CHR + string(((char*) marker) + e->_offsetFromKey); #endif localCrc += TRI_Crc32HashPointer(extra.c_str(), extra.size()); } else { return true; } if (WD) { // with data TRI_doc_document_key_marker_t const* d = (TRI_doc_document_key_marker_t const*) marker; TRI_shaped_json_t shaped; TRI_EXTRACT_SHAPED_JSON_MARKER(shaped, d); TRI_StringifyArrayShapedJson(primary->_shaper, &helper->_buffer, &shaped, false); localCrc += TRI_Crc32HashPointer(TRI_BeginStringBuffer(&helper->_buffer), TRI_LengthStringBuffer(&helper->_buffer)); TRI_ResetStringBuffer(&helper->_buffer); } helper->_checksum += localCrc; return true; } //////////////////////////////////////////////////////////////////////////////// /// @brief calculates a checksum for the data in a collection /// /// @FUN{@FA{collection}.checksum(@FA{withRevisions}, @FA{withData})} /// /// The @FN{checksum} operation calculates a CRC32 checksum of the keys /// contained in collection @FA{collection}. /// /// If the optional argument @FA{withRevisions} is set to @LIT{true}, then the /// revision ids of the documents are also included in the checksumming. /// /// If the optional argument @FA{withData} is set to @LIT{true}, then the /// actual document data is also checksummed. Including the document data in /// checksumming will make the calculation slower, but is more accurate. /// /// Note: this method is not available in a cluster. //////////////////////////////////////////////////////////////////////////////// static v8::Handle JS_ChecksumCollection (v8::Arguments const& argv) { v8::HandleScope scope; #ifdef TRI_ENABLE_CLUSTER if (ServerState::instance()->isCoordinator()) { // renaming a collection in a cluster is unsupported TRI_V8_EXCEPTION(scope, TRI_ERROR_CLUSTER_UNSUPPORTED); } #endif TRI_vocbase_col_t const* col; col = TRI_UnwrapClass(argv.Holder(), TRI_GetVocBaseColType()); if (col == 0) { TRI_V8_EXCEPTION_INTERNAL(scope, "cannot extract collection"); } TRI_SHARDING_COLLECTION_NOT_YET_IMPLEMENTED(scope, col); bool withRevisions = false; if (argv.Length() > 0) { withRevisions = TRI_ObjectToBoolean(argv[0]); } bool withData = false; if (argv.Length() > 1) { withData = TRI_ObjectToBoolean(argv[1]); } CollectionNameResolver resolver(col->_vocbase); ReadTransactionType trx(col->_vocbase, resolver, col->_cid); int res = trx.begin(); if (res != TRI_ERROR_NO_ERROR) { TRI_V8_EXCEPTION(scope, res); } TRI_primary_collection_t* primary = trx.primaryCollection(); Barrier barrier(primary); collection_checksum_t helper; helper._checksum = 0; helper._resolver = &resolver; // ............................................................................. // inside a read transaction // ............................................................................. trx.lockRead(); // get last tick const string rid = StringUtils::itoa(primary->base._info._revision); if (withData) { TRI_InitStringBuffer(&helper._buffer, TRI_CORE_MEM_ZONE); if (withRevisions) { TRI_DocumentIteratorPrimaryCollection(primary, &helper, &ChecksumCalculator); } else { TRI_DocumentIteratorPrimaryCollection(primary, &helper, &ChecksumCalculator); } TRI_DestroyStringBuffer(&helper._buffer); } else { if (withRevisions) { TRI_DocumentIteratorPrimaryCollection(primary, &helper, &ChecksumCalculator); } else { TRI_DocumentIteratorPrimaryCollection(primary, &helper, &ChecksumCalculator); } } trx.finish(res); // ............................................................................. // outside a write transaction // ............................................................................. v8::Handle result = v8::Object::New(); result->Set(v8::String::New("checksum"), v8::Number::New(helper._checksum)); result->Set(v8::String::New("revision"), v8::String::New(rid.c_str(), rid.size())); return scope.Close(result); } //////////////////////////////////////////////////////////////////////////////// /// @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 the n first documents in the collection //////////////////////////////////////////////////////////////////////////////// static v8::Handle JS_FirstQuery (v8::Arguments const& argv) { v8::HandleScope scope; if (argv.Length() > 1) { TRI_V8_EXCEPTION_USAGE(scope, "FIRST()"); } int64_t count = 1; bool returnList = false; // if argument is supplied, we'll return a list - otherwise we simply return the first doc if (argv.Length() == 1) { if (! argv[0]->IsUndefined()) { count = TRI_ObjectToInt64(argv[0]); returnList = true; } } if (count < 1) { TRI_V8_EXCEPTION_PARAMETER(scope, "invalid value for "); } TRI_vocbase_col_t const* col; col = TRI_UnwrapClass(argv.Holder(), TRI_GetVocBaseColType()); if (col == 0) { TRI_V8_EXCEPTION_INTERNAL(scope, "cannot extract collection"); } TRI_barrier_t* barrier = 0; CollectionNameResolver resolver(col->_vocbase); SingleCollectionReadOnlyTransaction > trx(col->_vocbase, resolver, col->_cid); int res = trx.begin(); if (res != TRI_ERROR_NO_ERROR) { TRI_V8_EXCEPTION(scope, res); } barrier = TRI_CreateBarrierElement(&trx.primaryCollection()->_barrierList); if (barrier == 0) { TRI_V8_EXCEPTION_MEMORY(scope); } vector documents; res = trx.readPositional(documents, 0, count); const size_t n = documents.size(); assert(barrier != 0); if (n == 0) { FREE_BARRIER(barrier); } if (returnList) { v8::Handle result = v8::Array::New(n); uint32_t j = 0; for (size_t i = 0; i < n; ++i) { v8::Handle doc = WRAP_SHAPED_JSON(trx, col->_cid, documents[i], barrier); if (doc.IsEmpty()) { // error trx.finish(res); FREE_BARRIER(barrier); TRI_V8_EXCEPTION_MEMORY(scope); } result->Set(j++, doc); } trx.finish(res); return scope.Close(result); } else { if (n == 0) { trx.finish(res); return scope.Close(v8::Null()); } v8::Handle result = WRAP_SHAPED_JSON(trx, col->_cid, documents[0], barrier); trx.finish(res); if (result.IsEmpty()) { FREE_BARRIER(barrier); TRI_V8_EXCEPTION_MEMORY(scope); } return scope.Close(result); } } //////////////////////////////////////////////////////////////////////////////// /// @brief queries the fulltext index /// /// the caller must ensure all relevant locks are acquired and freed //////////////////////////////////////////////////////////////////////////////// static v8::Handle FulltextQuery (ReadTransactionType& trx, TRI_vocbase_col_t const* collection, v8::Handle* err, v8::Arguments const& argv) { v8::HandleScope scope; // expect: FULLTEXT(, ) if (argv.Length() != 2) { TRI_V8_EXCEPTION_USAGE(scope, "FULLTEXT(, )"); } // extract the index TRI_index_t* idx = TRI_LookupIndexByHandle(collection, argv[0], false, err); if (idx == 0) { return scope.Close(v8::ThrowException(*err)); } if (idx->_type != TRI_IDX_TYPE_FULLTEXT_INDEX) { TRI_V8_EXCEPTION(scope, TRI_ERROR_ARANGO_NO_INDEX); } const string queryString = TRI_ObjectToString(argv[1]); bool isSubstringQuery = false; TRI_fulltext_query_t* query = TRI_CreateQueryFulltextIndex(TRI_FULLTEXT_SEARCH_MAX_WORDS); if (! query) { TRI_V8_EXCEPTION_MEMORY(scope); } int res = TRI_ParseQueryFulltextIndex(query, queryString.c_str(), &isSubstringQuery); if (res != TRI_ERROR_NO_ERROR) { TRI_FreeQueryFulltextIndex(query); TRI_V8_EXCEPTION(scope, res); } TRI_fulltext_index_t* fulltextIndex = (TRI_fulltext_index_t*) idx; if (isSubstringQuery && ! fulltextIndex->_indexSubstrings) { TRI_FreeQueryFulltextIndex(query); TRI_V8_EXCEPTION(scope, TRI_ERROR_NOT_IMPLEMENTED); } TRI_fulltext_result_t* queryResult = TRI_QueryFulltextIndex(fulltextIndex->_fulltextIndex, query); if (! queryResult) { TRI_V8_EXCEPTION_INTERNAL(scope, "internal error in fulltext index query"); } TRI_barrier_t* barrier = 0; if (queryResult->_numDocuments > 0) { barrier = TRI_CreateBarrierElement(&((TRI_primary_collection_t*) collection->_collection)->_barrierList); } // setup result v8::Handle result = v8::Object::New(); v8::Handle documents = v8::Array::New(); result->Set(v8::String::New("documents"), documents); bool error = false; for (uint32_t i = 0; i < queryResult->_numDocuments; ++i) { v8::Handle doc = WRAP_SHAPED_JSON(trx, collection->_cid, (TRI_doc_mptr_t const*) queryResult->_documents[i], barrier); if (doc.IsEmpty()) { error = true; break; } documents->Set(i, doc); } TRI_FreeResultFulltextIndex(queryResult); if (error) { FREE_BARRIER(barrier); TRI_V8_EXCEPTION_MEMORY(scope); } return scope.Close(result); } //////////////////////////////////////////////////////////////////////////////// /// @brief queries the fulltext index /// /// @FUN{@FA{collection}.FULLTEXT(@FA{index-handle}, @FA{query})} /// /// The @FN{FULLTEXT} operator performs a fulltext search using the specified /// index and the specified @FA{query}. /// /// @FA{query} must contain a comma-separated list of words to look for. /// Each word can optionally be prefixed with one of the following command /// literals: /// - @LIT{prefix}: perform a prefix-search for the word following /// - @LIT{substring}: perform substring-matching for the word following. This /// option is only supported for fulltext indexes that have been created with /// the @LIT{indexSubstrings} option /// - @LIT{complete}: only match the complete following word (this is the default) /// /// @EXAMPLES /// /// @verbinclude shell-simple-fulltext //////////////////////////////////////////////////////////////////////////////// static v8::Handle JS_FulltextQuery (v8::Arguments const& argv) { v8::HandleScope scope; TRI_vocbase_col_t const* col; col = TRI_UnwrapClass(argv.Holder(), TRI_GetVocBaseColType()); if (col == 0) { TRI_V8_EXCEPTION_INTERNAL(scope, "cannot extract collection"); } TRI_SHARDING_COLLECTION_NOT_YET_IMPLEMENTED(scope, col); CollectionNameResolver resolver(col->_vocbase); ReadTransactionType trx(col->_vocbase, resolver, col->_cid); int res = trx.begin(); if (res != TRI_ERROR_NO_ERROR) { TRI_V8_EXCEPTION(scope, res); } v8::Handle err; // ............................................................................. // inside a read transaction // ............................................................................. trx.lockRead(); v8::Handle result = FulltextQuery(trx, col, &err, argv); trx.finish(res); // ............................................................................. // outside a write transaction // ............................................................................. return scope.Close(result); } //////////////////////////////////////////////////////////////////////////////// /// @brief selects the n last documents in the collection //////////////////////////////////////////////////////////////////////////////// static v8::Handle JS_LastQuery (v8::Arguments const& argv) { v8::HandleScope scope; if (argv.Length() > 1) { TRI_V8_EXCEPTION_USAGE(scope, "LAST()"); } int64_t count = 1; bool returnList = false; // if argument is supplied, we'll return a list - otherwise we simply return the last doc if (argv.Length() == 1) { if (! argv[0]->IsUndefined()) { count = TRI_ObjectToInt64(argv[0]); returnList = true; } } if (count < 1) { TRI_V8_EXCEPTION_PARAMETER(scope, "invalid value for "); } TRI_vocbase_col_t const* col; col = TRI_UnwrapClass(argv.Holder(), TRI_GetVocBaseColType()); if (col == 0) { TRI_V8_EXCEPTION_INTERNAL(scope, "cannot extract collection"); } TRI_SHARDING_COLLECTION_NOT_YET_IMPLEMENTED(scope, col); TRI_barrier_t* barrier = 0; CollectionNameResolver resolver(col->_vocbase); SingleCollectionReadOnlyTransaction > trx(col->_vocbase, resolver, col->_cid); int res = trx.begin(); if (res != TRI_ERROR_NO_ERROR) { TRI_V8_EXCEPTION(scope, res); } barrier = TRI_CreateBarrierElement(&trx.primaryCollection()->_barrierList); if (barrier == 0) { TRI_V8_EXCEPTION_MEMORY(scope); } vector documents; res = trx.readPositional(documents, -1, count); const size_t n = documents.size(); assert(barrier != 0); if (n == 0) { FREE_BARRIER(barrier); } if (returnList) { v8::Handle result = v8::Array::New(n); uint32_t j = 0; for (size_t i = 0; i < n; ++i) { v8::Handle doc = WRAP_SHAPED_JSON(trx, col->_cid, documents[i], barrier); if (doc.IsEmpty()) { // error trx.finish(res); FREE_BARRIER(barrier); TRI_V8_EXCEPTION_MEMORY(scope); } result->Set(j++, doc); } trx.finish(res); return scope.Close(result); } else { if (n == 0) { trx.finish(res); return scope.Close(v8::Null()); } v8::Handle result = WRAP_SHAPED_JSON(trx, col->_cid, documents[0], barrier); trx.finish(res); if (result.IsEmpty()) { FREE_BARRIER(barrier); TRI_V8_EXCEPTION_MEMORY(scope); } return scope.Close(result); } } //////////////////////////////////////////////////////////////////////////////// /// @brief selects points near a given coordinate /// /// the caller must ensure all relevant locks are acquired and freed //////////////////////////////////////////////////////////////////////////////// static v8::Handle NearQuery (ReadTransactionType& trx, TRI_vocbase_col_t const* collection, v8::Handle* err, v8::Arguments const& argv) { v8::HandleScope scope; // expect: NEAR(, , , ) if (argv.Length() != 4) { TRI_V8_EXCEPTION_USAGE(scope, "NEAR(, , , )"); } // extract the index TRI_index_t* idx = TRI_LookupIndexByHandle(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) { TRI_V8_EXCEPTION(scope, TRI_ERROR_ARANGO_NO_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) { int res = StoreGeoResult(trx, collection, cors, documents, distances); if (res != TRI_ERROR_NO_ERROR) { TRI_V8_EXCEPTION(scope, res); } } return scope.Close(result); } //////////////////////////////////////////////////////////////////////////////// /// @brief selects points near a given coordinate //////////////////////////////////////////////////////////////////////////////// static v8::Handle JS_NearQuery (v8::Arguments const& argv) { v8::HandleScope scope; TRI_vocbase_col_t const* col; col = TRI_UnwrapClass(argv.Holder(), TRI_GetVocBaseColType()); if (col == 0) { TRI_V8_EXCEPTION_INTERNAL(scope, "cannot extract collection"); } TRI_SHARDING_COLLECTION_NOT_YET_IMPLEMENTED(scope, col); CollectionNameResolver resolver(col->_vocbase); ReadTransactionType trx(col->_vocbase, resolver, col->_cid); int res = trx.begin(); if (res != TRI_ERROR_NO_ERROR) { TRI_V8_EXCEPTION(scope, res); } v8::Handle err; // ............................................................................. // inside a read transaction // ............................................................................. trx.lockRead(); v8::Handle result = NearQuery(trx, col, &err, argv); trx.finish(res); // ............................................................................. // outside a write transaction // ............................................................................. 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 (ReadTransactionType& trx, TRI_vocbase_col_t const* collection, v8::Handle* err, v8::Arguments const& argv) { v8::HandleScope scope; // expect: WITHIN(, , , ) if (argv.Length() != 4) { TRI_V8_EXCEPTION_USAGE(scope, "WITHIN(, , , )"); } // extract the index TRI_index_t* idx = TRI_LookupIndexByHandle(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) { TRI_V8_EXCEPTION(scope, TRI_ERROR_ARANGO_NO_INDEX); } // extract latitude and longitude double latitude = TRI_ObjectToDouble(argv[1]); double longitude = TRI_ObjectToDouble(argv[2]); // extract the radius 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) { int res = StoreGeoResult(trx, collection, cors, documents, distances); if (res != TRI_ERROR_NO_ERROR) { TRI_V8_EXCEPTION(scope, res); } } return scope.Close(result); } //////////////////////////////////////////////////////////////////////////////// /// @brief selects points within a given radius //////////////////////////////////////////////////////////////////////////////// static v8::Handle JS_WithinQuery (v8::Arguments const& argv) { v8::HandleScope scope; TRI_vocbase_col_t const* col; col = TRI_UnwrapClass(argv.Holder(), TRI_GetVocBaseColType()); if (col == 0) { TRI_V8_EXCEPTION_INTERNAL(scope, "cannot extract collection"); } TRI_SHARDING_COLLECTION_NOT_YET_IMPLEMENTED(scope, col); CollectionNameResolver resolver(col->_vocbase); ReadTransactionType trx(col->_vocbase, resolver, col->_cid); int res = trx.begin(); if (res != TRI_ERROR_NO_ERROR) { TRI_V8_EXCEPTION(scope, res); } v8::Handle err; // ............................................................................. // inside a read transaction // ............................................................................. trx.lockRead(); v8::Handle result = WithinQuery(trx, col, &err, argv); trx.finish(res); // ............................................................................. // outside a write transaction // ............................................................................. 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; TRI_AddMethodVocbase(rt, "ALL", JS_AllQuery, true); TRI_AddMethodVocbase(rt, "ANY", JS_AnyQuery, true); TRI_AddMethodVocbase(rt, "BY_CONDITION_BITARRAY", JS_ByConditionBitarray, true); TRI_AddMethodVocbase(rt, "BY_CONDITION_SKIPLIST", JS_ByConditionSkiplist, true); TRI_AddMethodVocbase(rt, "BY_EXAMPLE", JS_ByExampleQuery, true); TRI_AddMethodVocbase(rt, "BY_EXAMPLE_BITARRAY", JS_ByExampleBitarray, true); TRI_AddMethodVocbase(rt, "BY_EXAMPLE_HASH", JS_ByExampleHashIndex, true); TRI_AddMethodVocbase(rt, "BY_EXAMPLE_SKIPLIST", JS_ByExampleSkiplist, true); TRI_AddMethodVocbase(rt, "checksum", JS_ChecksumCollection); TRI_AddMethodVocbase(rt, "EDGES", JS_EdgesQuery, true); TRI_AddMethodVocbase(rt, "FIRST", JS_FirstQuery, true); TRI_AddMethodVocbase(rt, "FULLTEXT", JS_FulltextQuery, true); TRI_AddMethodVocbase(rt, "INEDGES", JS_InEdgesQuery, true); TRI_AddMethodVocbase(rt, "LAST", JS_LastQuery, true); TRI_AddMethodVocbase(rt, "NEAR", JS_NearQuery, true); // internal method. not intended to be used by end-users TRI_AddMethodVocbase(rt, "OFFSET", JS_OffsetQuery, true); TRI_AddMethodVocbase(rt, "OUTEDGES", JS_OutEdgesQuery, true); TRI_AddMethodVocbase(rt, "WITHIN", JS_WithinQuery); } //////////////////////////////////////////////////////////////////////////////// /// @} //////////////////////////////////////////////////////////////////////////////// // Local Variables: // mode: outline-minor // outline-regexp: "/// @brief\\|/// {@inheritDoc}\\|/// @addtogroup\\|/// @page\\|// --SECTION--\\|/// @\\}" // End: