diff --git a/Documentation/Books/Makefile b/Documentation/Books/Makefile index 9e5c860971..1015734647 100644 --- a/Documentation/Books/Makefile +++ b/Documentation/Books/Makefile @@ -205,7 +205,7 @@ check-docublocks: grep -v '.*#.*:.*' \ >> /tmp/rawindoc.txt cat /tmp/rawindoc.txt | sed -e "s;.*ck ;;" -e "s;.*ne ;;" |sort -u > /tmp/indoc.txt - grep -R '^/// @startDocuBlock' ../DocuBlocks --include "*.md" --include "*.mdpp" |grep -v aardvark > /tmp/rawinprog.txt + grep -R '^@startDocuBlock' ../DocuBlocks --include "*.md" --include "*.mdpp" |grep -v aardvark > /tmp/rawinprog.txt # searching the Inline docublocks needs some more blacklisting: grep -R '@startDocuBlockInline' --include "*.h" --include "*.cpp" --include "*.js" --include "*.mdpp" . |\ grep -v ppbook |\ @@ -264,9 +264,9 @@ build-books: make build-books-keep-md NAME=AQL make build-books-keep-md NAME=HTTP - make ppbook-check-html-link NAME=Users - make ppbook-check-html-link NAME=AQL - make ppbook-check-html-link NAME=HTTP + #make ppbook-check-html-link NAME=Users + #make ppbook-check-html-link NAME=AQL + #make ppbook-check-html-link NAME=HTTP make check-docublocks diff --git a/UnitTests/Basics/json-test.cpp b/UnitTests/Basics/json-test.cpp index e134bb5468..5d557dfee9 100644 --- a/UnitTests/Basics/json-test.cpp +++ b/UnitTests/Basics/json-test.cpp @@ -293,7 +293,7 @@ BOOST_AUTO_TEST_CASE (tst_json_string_utf8_1) { BOOST_CHECK_EQUAL(true, TRI_IsStringJson(json)); STRINGIFY - BOOST_CHECK_EQUAL("\"\\uCF54\\uB9AC\\uC544\\uB2F7\\uCEF4 \\uBA54\\uC77C\\uC54C\\uB9AC\\uBBF8 \\uC11C\\uBE44\\uC2A4 \\uC911\\uB2E8\\uC548\\uB0B4 [\\uC548\\uB0B4] \\uAC1C\\uC778\\uC815\\uBCF4\\uCDE8\\uAE09\\uBC29\\uCE68 \\uBCC0\\uACBD \\uC548\\uB0B4 \\uD68C\\uC0AC\\uC18C\\uAC1C | \\uAD11\\uACE0\\uC548\\uB0B4 | \\uC81C\\uD734\\uC548\\uB0B4 | \\uAC1C\\uC778\\uC815\\uBCF4\\uCDE8\\uAE09\\uBC29\\uCE68 | \\uCCAD\\uC18C\\uB144\\uBCF4\\uD638\\uC815\\uCC45 | \\uC2A4\\uD338\\uBC29\\uC9C0\\uC815\\uCC45 | \\uC0AC\\uC774\\uBC84\\uACE0\\uAC1D\\uC13C\\uD130 | \\uC57D\\uAD00\\uC548\\uB0B4 | \\uC774\\uBA54\\uC77C \\uBB34\\uB2E8\\uC218\\uC9D1\\uAC70\\uBD80 | \\uC11C\\uBE44\\uC2A4 \\uC804\\uCCB4\\uBCF4\\uAE30\"", STRING_VALUE); + BOOST_CHECK_EQUAL("\"코리아닷컴 메일알리미 서비스 중단안내 [안내] 개인정보취급방침 변경 안내 회사소개 | 광고안내 | 제휴안내 | 개인정보취급방침 | 청소년보호정책 | 스팸방지정책 | 사이버고객센터 | 약관안내 | 이메일 무단수집거부 | 서비스 전체보기\"", STRING_VALUE); FREE_JSON FREE_BUFFER @@ -310,7 +310,7 @@ BOOST_AUTO_TEST_CASE (tst_json_string_utf8_2) { TRI_json_t* json = TRI_CreateStringCopyJson(TRI_UNKNOWN_MEM_ZONE, value, strlen(value)); STRINGIFY - BOOST_CHECK_EQUAL("\"\\u00E4\\u00F6\\u00FC\\u00DF\\u00C4\\u00D6\\u00DC\\u20AC\\u00B5\"", STRING_VALUE); + BOOST_CHECK_EQUAL("\"äöüßÄÖÜ€µ\"", STRING_VALUE); FREE_JSON FREE_BUFFER @@ -327,7 +327,7 @@ BOOST_AUTO_TEST_CASE (tst_json_string_utf8_3) { TRI_json_t* json = TRI_CreateStringCopyJson(TRI_UNKNOWN_MEM_ZONE, value, strlen(value)); STRINGIFY - BOOST_CHECK_EQUAL("\"a\\uD835\\uDEE2\"", STRING_VALUE); + BOOST_CHECK_EQUAL("\"a𝛢\"", STRING_VALUE); FREE_JSON FREE_BUFFER @@ -509,7 +509,7 @@ BOOST_AUTO_TEST_CASE (tst_json_array_keys_utf8) { TRI_Insert3ObjectJson(TRI_UNKNOWN_MEM_ZONE, json, "мадридского", TRI_CreateNumberJson(TRI_UNKNOWN_MEM_ZONE, 4)); STRINGIFY - BOOST_CHECK_EQUAL("{\"\\u00E4\\u00F6\\u00FC\\u00C4\\u00D6\\u00DC\\u00DF\":1,\"\\uCF54\\uB9AC\\uC544\\uB2F7\\uCEF4\":2,\"\\u30B8\\u30E3\\u30D1\\u30F3\":3,\"\\u043C\\u0430\\u0434\\u0440\\u0438\\u0434\\u0441\\u043A\\u043E\\u0433\\u043E\":4}", STRING_VALUE); + BOOST_CHECK_EQUAL("{\"äöüÄÖÜß\":1,\"코리아닷컴\":2,\"ジャパン\":3,\"мадридского\":4}", STRING_VALUE); FREE_JSON FREE_BUFFER diff --git a/arangod/Aql/AstNode.cpp b/arangod/Aql/AstNode.cpp index 72e5d0012f..5c2d2d334e 100644 --- a/arangod/Aql/AstNode.cpp +++ b/arangod/Aql/AstNode.cpp @@ -2219,70 +2219,59 @@ void AstNode::stringify(arangodb::basics::StringBuffer* buffer, bool verbose, THROW_ARANGO_EXCEPTION(TRI_ERROR_NOT_IMPLEMENTED); } - if (verbose || n > 0) { - if (verbose || n > 1) { - buffer->appendChar('['); + buffer->appendChar('['); + for (size_t i = 0; i < n; ++i) { + if (i > 0) { + buffer->appendChar(','); } - for (size_t i = 0; i < n; ++i) { - if (i > 0) { - buffer->appendChar(','); - } - AstNode* member = getMember(i); - if (member != nullptr) { - member->stringify(buffer, verbose, failIfLong); - } - } - if (verbose || n > 1) { - buffer->appendChar(']'); + AstNode* member = getMember(i); + if (member != nullptr) { + member->stringify(buffer, verbose, failIfLong); } } + buffer->appendChar(']'); return; } if (type == NODE_TYPE_OBJECT) { // must be JavaScript-compatible! - if (verbose) { - buffer->appendChar('{'); - size_t const n = numMembers(); + buffer->appendChar('{'); + size_t const n = numMembers(); - if (failIfLong && n > TooLongThreshold) { - // intentionally do not stringify this node because the output would be - // too long - THROW_ARANGO_EXCEPTION(TRI_ERROR_NOT_IMPLEMENTED); - } - - for (size_t i = 0; i < n; ++i) { - if (i > 0) { - buffer->appendChar(','); - } - - AstNode* member = getMember(i); - - if (member->type == NODE_TYPE_OBJECT_ELEMENT) { - TRI_ASSERT(member->numMembers() == 1); - - buffer->appendChar('"'); - buffer->appendJsonEncoded(member->getStringValue(), - member->getStringLength()); - buffer->appendText(TRI_CHAR_LENGTH_PAIR("\":")); - - member->getMember(0)->stringify(buffer, verbose, failIfLong); - } else if (member->type == NODE_TYPE_CALCULATED_OBJECT_ELEMENT) { - TRI_ASSERT(member->numMembers() == 2); - - buffer->appendText(TRI_CHAR_LENGTH_PAIR("$[")); - member->getMember(0)->stringify(buffer, verbose, failIfLong); - buffer->appendText(TRI_CHAR_LENGTH_PAIR("]:")); - member->getMember(1)->stringify(buffer, verbose, failIfLong); - } else { - TRI_ASSERT(false); - } - } - buffer->appendChar('}'); - } else { - buffer->appendText(TRI_CHAR_LENGTH_PAIR("[object Object]")); + if (failIfLong && n > TooLongThreshold) { + // intentionally do not stringify this node because the output would be + // too long + THROW_ARANGO_EXCEPTION(TRI_ERROR_NOT_IMPLEMENTED); } + + for (size_t i = 0; i < n; ++i) { + if (i > 0) { + buffer->appendChar(','); + } + + AstNode* member = getMember(i); + + if (member->type == NODE_TYPE_OBJECT_ELEMENT) { + TRI_ASSERT(member->numMembers() == 1); + + buffer->appendJsonEncoded(member->getStringValue(), + member->getStringLength()); + buffer->appendChar(':'); + + member->getMember(0)->stringify(buffer, verbose, failIfLong); + } else if (member->type == NODE_TYPE_CALCULATED_OBJECT_ELEMENT) { + TRI_ASSERT(member->numMembers() == 2); + + buffer->appendText(TRI_CHAR_LENGTH_PAIR("$[")); + member->getMember(0)->stringify(buffer, verbose, failIfLong); + buffer->appendText(TRI_CHAR_LENGTH_PAIR("]:")); + member->getMember(1)->stringify(buffer, verbose, failIfLong); + } else { + TRI_ASSERT(false); + } + } + buffer->appendChar('}'); return; } @@ -2809,9 +2798,7 @@ void AstNode::appendValue(arangodb::basics::StringBuffer* buffer) const { } case VALUE_TYPE_STRING: { - buffer->appendChar('"'); buffer->appendJsonEncoded(value.value._string, value.length); - buffer->appendChar('"'); break; } diff --git a/arangod/Aql/Executor.cpp b/arangod/Aql/Executor.cpp index 623f4fdcca..fb970b1bf5 100644 --- a/arangod/Aql/Executor.cpp +++ b/arangod/Aql/Executor.cpp @@ -150,7 +150,7 @@ std::unordered_map const Executor::FunctionNames{ {"SUBSTRING", Function("SUBSTRING", "AQL_SUBSTRING", "s,n|n", true, true, false, true, true)}, {"CONTAINS", Function("CONTAINS", "AQL_CONTAINS", "s,s|b", true, true, - false, true, true)}, + false, true, true, &Functions::Contains)}, {"LIKE", Function("LIKE", "AQL_LIKE", "s,r|b", true, true, false, true, true, &Functions::Like)}, {"LEFT", @@ -858,16 +858,12 @@ void Executor::generateCodeExpression(AstNode const* node) { void Executor::generateCodeString(char const* value, size_t length) { TRI_ASSERT(value != nullptr); - _buffer->appendChar('"'); _buffer->appendJsonEncoded(value, length); - _buffer->appendChar('"'); } /// @brief generates code for a string value void Executor::generateCodeString(std::string const& value) { - _buffer->appendChar('"'); _buffer->appendJsonEncoded(value.c_str(), value.size()); - _buffer->appendChar('"'); } /// @brief generate JavaScript code for an array diff --git a/arangod/Aql/Functions.cpp b/arangod/Aql/Functions.cpp index 303f254e5b..701d2c4e9c 100644 --- a/arangod/Aql/Functions.cpp +++ b/arangod/Aql/Functions.cpp @@ -60,6 +60,13 @@ using namespace arangodb::aql; thread_local std::unordered_map* RegexCache = nullptr; +/// @brief convert a number value into an AqlValue +static AqlValue NumberValue(arangodb::AqlTransaction* trx, int value) { + TransactionBuilderLeaser builder(trx); + builder->add(VPackValue(value)); + return AqlValue(builder.get()); +} + /// @brief convert a number value into an AqlValue static AqlValue NumberValue(arangodb::AqlTransaction* trx, double value) { if (std::isnan(value) || !std::isfinite(value) || value == HUGE_VAL || value == -HUGE_VAL) { @@ -337,21 +344,9 @@ void Functions::Stringify(arangodb::AqlTransaction* trx, return; } - if (slice.isArray()) { - bool first = true; - for (auto const& sub : VPackArrayIterator(slice, true)) { - if (!first) { - buffer.push_back(','); - } else { - first = false; - } - Stringify(trx, buffer, sub); - } - return; - } - - if (slice.isObject()) { - buffer.append("[object Object]"); + if (slice.isObject() || slice.isArray()) { + VPackDumper dumper(&buffer, trx->transactionContext()->getVPackOptions()); + dumper.dump(slice); return; } @@ -1156,6 +1151,62 @@ AqlValue Functions::Nth(arangodb::aql::Query* query, return value.at(index, mustDestroy, true); } +/// @brief function CONTAINS +AqlValue Functions::Contains(arangodb::aql::Query* query, + arangodb::AqlTransaction* trx, + VPackFunctionParameters const& parameters) { + ValidateParameters(parameters, "CONTAINS", 2, 3); + + AqlValue value = ExtractFunctionParameterValue(trx, parameters, 0); + AqlValue search = ExtractFunctionParameterValue(trx, parameters, 1); + AqlValue returnIndex = ExtractFunctionParameterValue(trx, parameters, 2); + + int result = -1; // default is "not found" + { + StringBufferLeaser buffer(trx); + arangodb::basics::VPackStringBufferAdapter adapter(buffer->stringBuffer()); + + AppendAsString(trx, adapter, value); + size_t const valueLength = buffer->length(); + + size_t const searchOffset = buffer->length(); + AppendAsString(trx, adapter, search); + size_t const searchLength = buffer->length() - valueLength; + + if (searchLength > 0) { + char const* found = static_cast(memmem(buffer->c_str(), valueLength, buffer->c_str() + searchOffset, searchLength)); + + if (found != nullptr) { + // find offset into string + int bytePosition = static_cast(found - buffer->c_str()); + char const* p = buffer->c_str(); + int pos = 0; + while (pos < bytePosition) { + unsigned char c = static_cast(*p); + if (c < 128) { + ++pos; + } else if (c < 224) { + pos += 2; + } else if (c < 240) { + pos += 3; + } else if (c < 248) { + pos += 4; + } + } + result = pos; + } + } + } + + if (returnIndex.toBoolean()) { + // return numeric value + return NumberValue(trx, result); + } + + // return boolean + return AqlValue(result != -1); +} + /// @brief function CONCAT AqlValue Functions::Concat(arangodb::aql::Query* query, arangodb::AqlTransaction* trx, @@ -1172,21 +1223,8 @@ AqlValue Functions::Concat(arangodb::aql::Query* query, continue; } - if (member.isArray()) { - // append each member individually - AqlValueMaterializer materializer(trx); - VPackSlice slice = materializer.slice(member, false); - for (auto const& sub : VPackArrayIterator(slice, true)) { - if (sub.isNone() || sub.isNull()) { - continue; - } - - Stringify(trx, adapter, sub); - } - } else { - // convert member to a string and append - AppendAsString(trx, adapter, member); - } + // convert member to a string and append + AppendAsString(trx, adapter, member); } size_t length = buffer->length(); diff --git a/arangod/Aql/Functions.h b/arangod/Aql/Functions.h index 590a2e36f0..ecbbaf86ac 100644 --- a/arangod/Aql/Functions.h +++ b/arangod/Aql/Functions.h @@ -89,6 +89,8 @@ struct Functions { VPackFunctionParameters const&); static AqlValue Nth(arangodb::aql::Query*, arangodb::AqlTransaction*, VPackFunctionParameters const&); + static AqlValue Contains(arangodb::aql::Query*, arangodb::AqlTransaction*, + VPackFunctionParameters const&); static AqlValue Concat(arangodb::aql::Query*, arangodb::AqlTransaction*, VPackFunctionParameters const&); static AqlValue Like(arangodb::aql::Query*, arangodb::AqlTransaction*, diff --git a/arangod/Indexes/RocksDBFeature.cpp b/arangod/Indexes/RocksDBFeature.cpp index 953f1cb10f..d4781050d4 100644 --- a/arangod/Indexes/RocksDBFeature.cpp +++ b/arangod/Indexes/RocksDBFeature.cpp @@ -152,6 +152,8 @@ RocksDBFeature* RocksDBFeature::instance() { } int RocksDBFeature::syncWal() { +#ifndef _WIN32 + // SyncWAL() always reports a "not implemented" error on Windows if (Instance == nullptr || !Instance->isEnabled()) { return TRI_ERROR_NO_ERROR; } @@ -164,7 +166,7 @@ int RocksDBFeature::syncWal() { LOG(ERR) << "error syncing rocksdb WAL: " << status.ToString(); return TRI_ERROR_INTERNAL; } - +#endif return TRI_ERROR_NO_ERROR; } diff --git a/arangod/V8Server/v8-collection.cpp b/arangod/V8Server/v8-collection.cpp index 5ffdd49972..667bc19030 100644 --- a/arangod/V8Server/v8-collection.cpp +++ b/arangod/V8Server/v8-collection.cpp @@ -1317,7 +1317,6 @@ static void JS_PropertiesVocbaseCol( } TRI_document_collection_t* document = collection->_collection; - TRI_collection_t* base = document; // check if we want to change some parameters if (0 < args.Length()) { @@ -1337,18 +1336,18 @@ static void JS_PropertiesVocbaseCol( // only work under the lock WRITE_LOCKER(writeLocker, document->_infoLock); - if (base->_info.isVolatile() && + if (document->_info.isVolatile() && arangodb::basics::VelocyPackHelper::getBooleanValue( - slice, "waitForSync", base->_info.waitForSync())) { + slice, "waitForSync", document->_info.waitForSync())) { ReleaseCollection(collection); // the combination of waitForSync and isVolatile makes no sense TRI_V8_THROW_EXCEPTION_PARAMETER( "volatile collections do not support the waitForSync option"); } - if (base->_info.isVolatile() != + if (document->_info.isVolatile() != arangodb::basics::VelocyPackHelper::getBooleanValue( - slice, "isVolatile", base->_info.isVolatile())) { + slice, "isVolatile", document->_info.isVolatile())) { TRI_V8_THROW_EXCEPTION_PARAMETER( "isVolatile option cannot be changed at runtime"); } @@ -1363,11 +1362,11 @@ static void JS_PropertiesVocbaseCol( "indexBuckets must be a two-power between 1 and 1024"); } - } // Leave the scope and free the JOURNAL lock + } // Leave the scope and free the lock // try to write new parameter to file - bool doSync = base->_vocbase->_settings.forceSyncProperties; - res = base->updateCollectionInfo(base->_vocbase, slice, doSync); + bool doSync = document->_vocbase->_settings.forceSyncProperties; + res = document->updateCollectionInfo(document->_vocbase, slice, doSync); if (res != TRI_ERROR_NO_ERROR) { ReleaseCollection(collection); @@ -1377,7 +1376,7 @@ static void JS_PropertiesVocbaseCol( try { VPackBuilder infoBuilder; infoBuilder.openObject(); - TRI_CreateVelocyPackCollectionInfo(base->_info, infoBuilder); + TRI_CreateVelocyPackCollectionInfo(document->_info, infoBuilder); infoBuilder.close(); // now log the property changes @@ -1411,12 +1410,12 @@ static void JS_PropertiesVocbaseCol( TRI_GET_GLOBAL_STRING(IsSystemKey); TRI_GET_GLOBAL_STRING(IsVolatileKey); TRI_GET_GLOBAL_STRING(JournalSizeKey); - result->Set(DoCompactKey, v8::Boolean::New(isolate, base->_info.doCompact())); - result->Set(IsSystemKey, v8::Boolean::New(isolate, base->_info.isSystem())); + result->Set(DoCompactKey, v8::Boolean::New(isolate, document->_info.doCompact())); + result->Set(IsSystemKey, v8::Boolean::New(isolate, document->_info.isSystem())); result->Set(IsVolatileKey, - v8::Boolean::New(isolate, base->_info.isVolatile())); + v8::Boolean::New(isolate, document->_info.isVolatile())); result->Set(JournalSizeKey, - v8::Number::New(isolate, base->_info.maximalSize())); + v8::Number::New(isolate, document->_info.maximalSize())); result->Set(TRI_V8_ASCII_STRING("indexBuckets"), v8::Number::New(isolate, document->_info.indexBuckets())); @@ -1434,7 +1433,7 @@ static void JS_PropertiesVocbaseCol( } TRI_GET_GLOBAL_STRING(WaitForSyncKey); result->Set(WaitForSyncKey, - v8::Boolean::New(isolate, base->_info.waitForSync())); + v8::Boolean::New(isolate, document->_info.waitForSync())); ReleaseCollection(collection); TRI_V8_RETURN(result); @@ -1458,11 +1457,11 @@ static int RenameGraphCollections(v8::Isolate* isolate, v8::HandleScope scope(isolate); StringBuffer buffer(TRI_UNKNOWN_MEM_ZONE); - buffer.appendText("require('@arangodb/general-graph')._renameCollection(\""); + buffer.appendText("require('@arangodb/general-graph')._renameCollection("); buffer.appendJsonEncoded(oldName.c_str(), oldName.size()); - buffer.appendText("\", \""); + buffer.appendChar(','); buffer.appendJsonEncoded(newName.c_str(), newName.size()); - buffer.appendText("\");"); + buffer.appendText(");"); TRI_ExecuteJavaScriptString( isolate, isolate->GetCurrentContext(), diff --git a/arangosh/Import/ImportHelper.cpp b/arangosh/Import/ImportHelper.cpp index 3b684baa88..e1c2249015 100644 --- a/arangosh/Import/ImportHelper.cpp +++ b/arangosh/Import/ImportHelper.cpp @@ -476,9 +476,7 @@ void ImportHelper::addField(char const* field, size_t fieldLength, size_t row, if (row == 0 || escaped) { // head line or escaped value - _lineBuffer.appendChar('"'); - _lineBuffer.appendJsonEncoded(field); - _lineBuffer.appendChar('"'); + _lineBuffer.appendJsonEncoded(field, fieldLength); return; } @@ -514,9 +512,7 @@ void ImportHelper::addField(char const* field, size_t fieldLength, size_t row, _lineBuffer.appendInteger(num); } catch (...) { // conversion failed - _lineBuffer.appendChar('"'); - _lineBuffer.appendJsonEncoded(field); - _lineBuffer.appendChar('"'); + _lineBuffer.appendJsonEncoded(field, fieldLength); } } else if (IsDecimal(field, fieldLength)) { // double value @@ -539,9 +535,7 @@ void ImportHelper::addField(char const* field, size_t fieldLength, size_t row, _lineBuffer.appendText(field, fieldLength); _lineBuffer.appendChar('"'); } else { - _lineBuffer.appendChar('"'); - _lineBuffer.appendJsonEncoded(field); - _lineBuffer.appendChar('"'); + _lineBuffer.appendJsonEncoded(field, fieldLength); } } diff --git a/js/server/modules/@arangodb/aql.js b/js/server/modules/@arangodb/aql.js index 361eec9c41..e41f6eb58f 100644 --- a/js/server/modules/@arangodb/aql.js +++ b/js/server/modules/@arangodb/aql.js @@ -2122,7 +2122,7 @@ function ARITHMETIC_MODULUS (lhs, rhs) { function AQL_CONCAT () { 'use strict'; - var result = '', i, j; + var result = '', i; for (i = 0; i < arguments.length; ++i) { var element = arguments[i]; @@ -2130,16 +2130,7 @@ function AQL_CONCAT () { if (weight === TYPEWEIGHT_NULL) { continue; } - else if (weight === TYPEWEIGHT_ARRAY) { - for (j = 0; j < element.length; ++j) { - if (TYPEWEIGHT(element[j]) !== TYPEWEIGHT_NULL) { - result += AQL_TO_STRING(element[j]); - } - } - } - else { - result += AQL_TO_STRING(element); - } + result += AQL_TO_STRING(element); } return result; @@ -2161,28 +2152,13 @@ function AQL_CONCAT_SEPARATOR () { if (i > 0 && weight === TYPEWEIGHT_NULL) { continue; } - if (i === 0) { separator = AQL_TO_STRING(element); - continue; - } - else if (found) { - result += separator; - } - - if (weight === TYPEWEIGHT_ARRAY) { - found = false; - for (j = 0; j < element.length; ++j) { - if (TYPEWEIGHT(element[j]) !== TYPEWEIGHT_NULL) { - if (found) { - result += separator; - } - result += AQL_TO_STRING(element[j]); - found = true; - } - } } else { + if (found) { + result += separator; + } result += AQL_TO_STRING(element); found = true; } @@ -2718,9 +2694,10 @@ function AQL_TO_STRING (value) { case TYPEWEIGHT_STRING: return value; case TYPEWEIGHT_NUMBER: + return value.toString(); case TYPEWEIGHT_ARRAY: case TYPEWEIGHT_OBJECT: - return value.toString(); + return JSON.stringify(value); } } diff --git a/js/server/tests/aql/aql-dynamic-attributes.js b/js/server/tests/aql/aql-dynamic-attributes.js index f2d1c9fdf2..5237a96e3f 100644 --- a/js/server/tests/aql/aql-dynamic-attributes.js +++ b/js/server/tests/aql/aql-dynamic-attributes.js @@ -73,8 +73,8 @@ function ahuacatlDynamicAttributesTestSuite () { //////////////////////////////////////////////////////////////////////////////// testDynamicAttributesNonStringNames : function () { - var expected = { "true" : 12, "false" : 15, "123" : 21, "-42.5" : 99, "" : 117, "1,2,3,4" : 131, "[object Object]" : 149 }; - var query = "{ [ PASSTHRU(null) ] : 7, [ PASSTHRU(true) ] : 12, [ PASSTHRU(false) ] : 15, [ PASSTHRU(123) ] : 21, [ PASSTHRU(-42.5) ] : 99, [ PASSTHRU([ ]) ] : 117, [ PASSTHRU([ 1, 2, 3, 4 ]) ] : 131, [ PASSTHRU({ a: 1 }) ] : 149 }"; + var expected = { "" : 7, "true" : 12, "false" : 15, "123" : 21, "-42.5" : 99, "[]" : 117, "[1]" : 121, "[1,2,3,4]" : 131, "{\"a\":1}" : 149 }; + var query = "{ [ PASSTHRU(null) ] : 7, [ PASSTHRU(true) ] : 12, [ PASSTHRU(false) ] : 15, [ PASSTHRU(123) ] : 21, [ PASSTHRU(-42.5) ] : 99, [ PASSTHRU([ ]) ] : 117, [ PASSTHRU([ 1 ]) ] : 121, [ PASSTHRU([ 1, 2, 3, 4 ]) ] : 131, [ PASSTHRU({ a: 1 }) ] : 149 }"; checkResult(query, expected); }, diff --git a/js/server/tests/aql/aql-functions-string.js b/js/server/tests/aql/aql-functions-string.js index 7f425489e8..a5cd00e4ee 100644 --- a/js/server/tests/aql/aql-functions-string.js +++ b/js/server/tests/aql/aql-functions-string.js @@ -288,8 +288,12 @@ function ahuacatlStringFunctionsTestSuite () { assertEqual([ -1 ], getQueryResults(buildQuery(i, "\"test\", \"test2\", \"test3\""))); assertEqual([ false ], getQueryResults(buildQuery(i, "null, null"))); assertEqual([ true ], getQueryResults(buildQuery(i, "4, 4"))); - assertEqual([ true ], getQueryResults(buildQuery(i, "{ }, { }"))); - assertEqual([ false ], getQueryResults(buildQuery(i, "[ ], [ ]"))); + assertEqual([ true ], getQueryResults(buildQuery(i, "{}, {}"))); + assertEqual([ true ], getQueryResults(buildQuery(i, "{a:1,b:2}, {a:1,b:2}"))); + assertEqual([ true ], getQueryResults(buildQuery(i, "[], []"))); + assertEqual([ true ], getQueryResults(buildQuery(i, "[1,2,3], [1,2,3]"))); + assertEqual([ true ], getQueryResults(buildQuery(i, "[1,2], [1,2]"))); + assertEqual([ true ], getQueryResults(buildQuery(i, "[1,2,3], 2"))); assertEqual([ false ], getQueryResults(buildQuery(i, "null, \"yes\""))); assertEqual([ false ], getQueryResults(buildQuery(i, "3, null"))); } @@ -460,8 +464,10 @@ function ahuacatlStringFunctionsTestSuite () { assertEqual([ "" ], getQueryResults("RETURN LEFT(null, 2)")); assertEqual([ "tr" ], getQueryResults("RETURN LEFT(true, 2)")); assertEqual([ "4" ], getQueryResults("RETURN LEFT(4, 2)")); - assertEqual([ "" ], getQueryResults("RETURN LEFT([ ], 2)")); - assertEqual([ "[o" ], getQueryResults("RETURN LEFT({ }, 2)")); + assertEqual([ "[]" ], getQueryResults("RETURN LEFT([ ], 2)")); + assertEqual([ "[" ], getQueryResults("RETURN LEFT([ ], 1)")); + assertEqual([ "{}" ], getQueryResults("RETURN LEFT({ }, 2)")); + assertEqual([ "{" ], getQueryResults("RETURN LEFT({ }, 1)")); assertEqual([ "" ], getQueryResults("RETURN LEFT('foo', null)")); assertEqual([ "f" ], getQueryResults("RETURN LEFT('foo', true)")); assertEqual([ "" ], getQueryResults("RETURN LEFT('foo', 'bar')")); @@ -493,8 +499,10 @@ function ahuacatlStringFunctionsTestSuite () { assertEqual([ "" ], getQueryResults("RETURN RIGHT(null, 2)")); assertEqual([ "ue" ], getQueryResults("RETURN RIGHT(true, 2)")); assertEqual([ "4" ], getQueryResults("RETURN RIGHT(4, 2)")); - assertEqual([ "" ], getQueryResults("RETURN RIGHT([ ], 2)")); - assertEqual([ "t]" ], getQueryResults("RETURN RIGHT({ }, 2)")); + assertEqual([ "[]" ], getQueryResults("RETURN RIGHT([ ], 2)")); + assertEqual([ "]" ], getQueryResults("RETURN RIGHT([ ], 1)")); + assertEqual([ "{}" ], getQueryResults("RETURN RIGHT({ }, 2)")); + assertEqual([ "}" ], getQueryResults("RETURN RIGHT({ }, 1)")); assertEqual([ "" ], getQueryResults("RETURN RIGHT('foo', null)")); assertEqual([ "o" ], getQueryResults("RETURN RIGHT('foo', true)")); assertEqual([ "" ], getQueryResults("RETURN RIGHT('foo', 'bar')")); @@ -672,13 +680,13 @@ function ahuacatlStringFunctionsTestSuite () { assertEqual([ "" ], getQueryResults("RETURN TRIM(null)")); assertEqual([ "true" ], getQueryResults("RETURN TRIM(true)")); assertEqual([ "4" ], getQueryResults("RETURN TRIM(4)")); - assertEqual([ "" ], getQueryResults("RETURN TRIM([ ])")); - assertEqual([ "[object Object]" ], getQueryResults("RETURN TRIM({ })")); + assertEqual([ "[]" ], getQueryResults("RETURN TRIM([ ])")); + assertEqual([ "{}" ], getQueryResults("RETURN TRIM({ })")); assertEqual([ "foo" ], getQueryResults("RETURN TRIM('foo', null)")); assertEqual([ "foo" ], getQueryResults("RETURN TRIM('foo', true)")); assertEqual([ "foo" ], getQueryResults("RETURN TRIM('foo', 'bar')")); assertEqual([ "foo" ], getQueryResults("RETURN TRIM('foo', [ ])")); - assertEqual([ "f" ], getQueryResults("RETURN TRIM('foo', { })")); // { } = "[object Object]" + assertEqual([ "foo" ], getQueryResults("RETURN TRIM('foo', { })")); assertEqual([ "foo" ], getQueryResults("RETURN TRIM('foo', -1)")); assertEqual([ "foo" ], getQueryResults("RETURN TRIM('foo', -1.5)")); assertEqual([ "foo" ], getQueryResults("RETURN TRIM('foo', 3)")); @@ -836,12 +844,15 @@ function ahuacatlStringFunctionsTestSuite () { assertEqual([ -1 ], getQueryResults("RETURN FIND_FIRST(null, 'foo')")); assertEqual([ -1 ], getQueryResults("RETURN FIND_FIRST(true, 'foo')")); assertEqual([ -1 ], getQueryResults("RETURN FIND_FIRST(4, 'foo')")); + assertEqual([ -1 ], getQueryResults("RETURN FIND_FIRST([], 'foo')")); assertEqual([ -1 ], getQueryResults("RETURN FIND_FIRST([ ], 'foo')")); + assertEqual([ -1 ], getQueryResults("RETURN FIND_FIRST({}, 'foo')")); assertEqual([ -1 ], getQueryResults("RETURN FIND_FIRST({ }, 'foo')")); assertEqual([ 0 ], getQueryResults("RETURN FIND_FIRST('foo', null)")); assertEqual([ 0 ], getQueryResults("RETURN FIND_FIRST('foo', '')")); assertEqual([ -1 ], getQueryResults("RETURN FIND_FIRST('foo', true)")); - assertEqual([ 0 ], getQueryResults("RETURN FIND_FIRST('foo', [ ])")); + assertEqual([ -1 ], getQueryResults("RETURN FIND_FIRST('foo', [])")); + assertEqual([ -1 ], getQueryResults("RETURN FIND_FIRST('foo', [1,2])")); assertEqual([ -1 ], getQueryResults("RETURN FIND_FIRST('foo', { })")); assertEqual([ -1 ], getQueryResults("RETURN FIND_FIRST('foo', -1)")); assertEqual([ -1 ], getQueryResults("RETURN FIND_FIRST('foo', -1.5)")); @@ -948,7 +959,7 @@ function ahuacatlStringFunctionsTestSuite () { assertEqual([ 3 ], getQueryResults("RETURN FIND_LAST('foo', null)")); assertEqual([ 3 ], getQueryResults("RETURN FIND_LAST('foo', '')")); assertEqual([ -1 ], getQueryResults("RETURN FIND_LAST('foo', true)")); - assertEqual([ 3 ], getQueryResults("RETURN FIND_LAST('foo', [ ])")); + assertEqual([ -1 ], getQueryResults("RETURN FIND_LAST('foo', [ ])")); assertEqual([ -1 ], getQueryResults("RETURN FIND_LAST('foo', { })")); assertEqual([ -1 ], getQueryResults("RETURN FIND_LAST('foo', -1)")); assertEqual([ -1 ], getQueryResults("RETURN FIND_LAST('foo', -1.5)")); @@ -986,7 +997,7 @@ function ahuacatlStringFunctionsTestSuite () { testConcatList : function () { var expected = [ "theQuickBrownアボカドJumps名称について" ]; - var actual = getQueryResults("FOR r IN [ 1 ] return CONCAT([ 'the', 'Quick', '', null, 'Brown', null, 'アボカド', 'Jumps', '名称について' ])"); + var actual = getQueryResults("FOR r IN [ 1 ] return CONCAT('the', 'Quick', '', null, 'Brown', null, 'アボカド', 'Jumps', '名称について' )"); assertEqual(expected, actual); }, @@ -998,12 +1009,12 @@ function ahuacatlStringFunctionsTestSuite () { assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_NUMBER_MISMATCH.code, "RETURN CONCAT()"); assertEqual([ "yestrue" ], getQueryResults("RETURN CONCAT(\"yes\", true)")); assertEqual([ "yes4" ], getQueryResults("RETURN CONCAT(\"yes\", 4)")); - assertEqual([ "yes" ], getQueryResults("RETURN CONCAT(\"yes\", [ ])")); - assertEqual([ "yes[object Object]" ], getQueryResults("RETURN CONCAT(\"yes\", { })")); + assertEqual([ "yes[]" ], getQueryResults("RETURN CONCAT(\"yes\", [ ])")); + assertEqual([ "yes{}" ], getQueryResults("RETURN CONCAT(\"yes\", { })")); assertEqual([ "trueyes" ], getQueryResults("RETURN CONCAT(true, \"yes\")")); assertEqual([ "4yes" ], getQueryResults("RETURN CONCAT(4, \"yes\")")); - assertEqual([ "yes" ], getQueryResults("RETURN CONCAT([ ], \"yes\")")); - assertEqual([ "[object Object]yes" ], getQueryResults("RETURN CONCAT({ }, \"yes\")")); + assertEqual([ "[]yes" ], getQueryResults("RETURN CONCAT([ ], \"yes\")")); + assertEqual([ "{}yes" ], getQueryResults("RETURN CONCAT({ }, \"yes\")")); }, //////////////////////////////////////////////////////////////////////////////// @@ -1032,7 +1043,7 @@ function ahuacatlStringFunctionsTestSuite () { testConcatCxxList : function () { var expected = [ "theQuickBrownアボカドJumps名称について" ]; - var actual = getQueryResults("FOR r IN [ 1 ] return NOOPT(CONCAT([ 'the', 'Quick', '', null, 'Brown', null, 'アボカド', 'Jumps', '名称について' ]))"); + var actual = getQueryResults("FOR r IN [ 1 ] return NOOPT(CONCAT('the', 'Quick', '', null, 'Brown', null, 'アボカド', 'Jumps', '名称について'))"); assertEqual(expected, actual); }, @@ -1044,12 +1055,15 @@ function ahuacatlStringFunctionsTestSuite () { assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_NUMBER_MISMATCH.code, "RETURN NOOPT(CONCAT())"); assertEqual([ "yestrue" ], getQueryResults("RETURN NOOPT(CONCAT(\"yes\", true))")); assertEqual([ "yes4" ], getQueryResults("RETURN NOOPT(CONCAT(\"yes\", 4))")); - assertEqual([ "yes" ], getQueryResults("RETURN NOOPT(CONCAT(\"yes\", [ ]))")); - assertEqual([ "yes[object Object]" ], getQueryResults("RETURN NOOPT(CONCAT(\"yes\", { }))")); + assertEqual([ "yes[]" ], getQueryResults("RETURN NOOPT(CONCAT(\"yes\", [ ]))")); + assertEqual([ "yes{}" ], getQueryResults("RETURN NOOPT(CONCAT(\"yes\", { }))")); assertEqual([ "trueyes" ], getQueryResults("RETURN NOOPT(CONCAT(true, \"yes\"))")); assertEqual([ "4yes" ], getQueryResults("RETURN NOOPT(CONCAT(4, \"yes\"))")); - assertEqual([ "yes" ], getQueryResults("RETURN NOOPT(CONCAT([ ], \"yes\"))")); - assertEqual([ "[object Object]yes" ], getQueryResults("RETURN NOOPT(CONCAT({ }, \"yes\"))")); + assertEqual([ "[]yes" ], getQueryResults("RETURN NOOPT(CONCAT([ ], \"yes\"))")); + assertEqual([ "[1,2,3]yes" ], getQueryResults("RETURN NOOPT(CONCAT([ 1,2,3], \"yes\"))")); + assertEqual([ "[1,2,3]yes" ], getQueryResults("RETURN NOOPT(CONCAT([ 1 , 2, 3 ], \"yes\"))")); + assertEqual([ "{}yes" ], getQueryResults("RETURN NOOPT(CONCAT({ }, \"yes\"))")); + assertEqual([ "{\"a\":\"foo\",\"b\":2}yes" ], getQueryResults("RETURN NOOPT(CONCAT({ a: \"foo\", b: 2 }, \"yes\"))")); }, //////////////////////////////////////////////////////////////////////////////// @@ -1077,7 +1091,7 @@ function ahuacatlStringFunctionsTestSuite () { //////////////////////////////////////////////////////////////////////////////// testConcatSeparatorList1 : function () { - var expected = [ "the,Quick,Brown,Fox,Jumps,higher,than,you" ]; + var expected = [ "[\"the\",\"Quick\",null,\"Brown\",null,\"Fox\",\"Jumps\"],higher,[\"than\",\"you\"]" ]; var actual = getQueryResults("FOR r IN [ 1 ] return CONCAT_SEPARATOR(',', [ 'the', 'Quick', null, 'Brown', null, 'Fox', 'Jumps' ], 'higher', [ 'than', 'you' ])"); assertEqual(expected, actual); }, @@ -1087,11 +1101,21 @@ function ahuacatlStringFunctionsTestSuite () { //////////////////////////////////////////////////////////////////////////////// testConcatSeparatorList2 : function () { - var expected = [ "the*/*/Quick*/*/Brown*/*/*/*/Fox*/*/Jumps*/*/higher*/*/than*/*/you" ]; + var expected = [ "[\"the\",\"Quick\",null,\"Brown\",\"\",\"Fox\",\"Jumps\"]*/*/[]*/*/higher*/*/[\"than\",\"you\"]" ]; var actual = getQueryResults("FOR r IN [ 1 ] return CONCAT_SEPARATOR('*/*/', [ 'the', 'Quick', null, 'Brown', '', 'Fox', 'Jumps' ], [ ], 'higher', [ 'than', 'you' ])"); assertEqual(expected, actual); }, +//////////////////////////////////////////////////////////////////////////////// +/// @brief test concat_separator function +//////////////////////////////////////////////////////////////////////////////// + + testConcatSeparatorList3 : function () { + var expected = [ "the*/*/Quick*/*/Brown*/*/*/*/Fox*/*/Jumps*/*/[]*/*/higher*/*/[\"than\",\"you\"]" ]; + var actual = getQueryResults("FOR r IN [ 1 ] return CONCAT_SEPARATOR('*/*/', 'the', 'Quick', null, 'Brown', '', 'Fox', 'Jumps', [ ], 'higher', [ 'than', 'you' ])"); + assertEqual(expected, actual); + }, + //////////////////////////////////////////////////////////////////////////////// /// @brief test concat_separator function //////////////////////////////////////////////////////////////////////////////// @@ -1102,16 +1126,17 @@ function ahuacatlStringFunctionsTestSuite () { assertEqual([ "yesyes" ], getQueryResults("RETURN CONCAT_SEPARATOR(null, \"yes\", \"yes\")")); assertEqual([ "yestrueyes" ], getQueryResults("RETURN CONCAT_SEPARATOR(true, \"yes\", \"yes\")")); assertEqual([ "yes4yes" ], getQueryResults("RETURN CONCAT_SEPARATOR(4, \"yes\", \"yes\")")); - assertEqual([ "yesyes" ], getQueryResults("RETURN CONCAT_SEPARATOR([ ], \"yes\", \"yes\")")); - assertEqual([ "yes[object Object]yes" ], getQueryResults("RETURN CONCAT_SEPARATOR({ }, \"yes\", \"yes\")")); + assertEqual([ "yes[]yes" ], getQueryResults("RETURN CONCAT_SEPARATOR([ ], \"yes\", \"yes\")")); + assertEqual([ "yes{}yes" ], getQueryResults("RETURN CONCAT_SEPARATOR({ }, \"yes\", \"yes\")")); assertEqual([ "trueyesyes" ], getQueryResults("RETURN CONCAT_SEPARATOR(\"yes\", true, \"yes\")")); assertEqual([ "4yesyes" ], getQueryResults("RETURN CONCAT_SEPARATOR(\"yes\", 4, \"yes\")")); - assertEqual([ "yes" ], getQueryResults("RETURN CONCAT_SEPARATOR(\"yes\", [ ], \"yes\")")); - assertEqual([ "[object Object]yesyes" ], getQueryResults("RETURN CONCAT_SEPARATOR(\"yes\", { }, \"yes\")")); + assertEqual([ "[]yesyes" ], getQueryResults("RETURN CONCAT_SEPARATOR(\"yes\", [ ], \"yes\")")); + assertEqual([ "{}yesyes" ], getQueryResults("RETURN CONCAT_SEPARATOR(\"yes\", { }, \"yes\")")); assertEqual([ "yesyestrue" ], getQueryResults("RETURN CONCAT_SEPARATOR(\"yes\", \"yes\", true)")); assertEqual([ "yesyes4" ], getQueryResults("RETURN CONCAT_SEPARATOR(\"yes\", \"yes\", 4)")); - assertEqual([ "yesyes" ], getQueryResults("RETURN CONCAT_SEPARATOR(\"yes\", \"yes\", [ ])")); - assertEqual([ "yesyes[object Object]" ], getQueryResults("RETURN CONCAT_SEPARATOR(\"yes\", \"yes\", { })")); + assertEqual([ "yesyes[]" ], getQueryResults("RETURN CONCAT_SEPARATOR(\"yes\", \"yes\", [ ])")); + assertEqual([ "yesyes[1,2,3]" ], getQueryResults("RETURN CONCAT_SEPARATOR(\"yes\", \"yes\", [ 1,2,3 ])")); + assertEqual([ "yesyes{}" ], getQueryResults("RETURN CONCAT_SEPARATOR(\"yes\", \"yes\", { })")); }, //////////////////////////////////////////////////////////////////////////////// @@ -1154,8 +1179,12 @@ function ahuacatlStringFunctionsTestSuite () { assertEqual([ 0 ], getQueryResults("RETURN CHAR_LENGTH(null)")); assertEqual([ 4 ], getQueryResults("RETURN CHAR_LENGTH(true)")); assertEqual([ 1 ], getQueryResults("RETURN CHAR_LENGTH(3)")); - assertEqual([ 0 ], getQueryResults("RETURN CHAR_LENGTH([ ])")); - assertEqual([ 15 ], getQueryResults("RETURN CHAR_LENGTH({ })")); + assertEqual([ 2 ], getQueryResults("RETURN CHAR_LENGTH([ ])")); + assertEqual([ 7 ], getQueryResults("RETURN CHAR_LENGTH([ 1, 2, 3 ])")); + assertEqual([ 2 ], getQueryResults("RETURN CHAR_LENGTH({ })")); + assertEqual([ 7 ], getQueryResults("RETURN CHAR_LENGTH({ a:1 })")); + assertEqual([ 11 ], getQueryResults("RETURN CHAR_LENGTH({ a: \"foo\" })")); + assertEqual([ 13 ], getQueryResults("RETURN CHAR_LENGTH({ a:1, b: 2 })")); }, //////////////////////////////////////////////////////////////////////////////// @@ -1198,8 +1227,11 @@ function ahuacatlStringFunctionsTestSuite () { assertEqual([ "" ], getQueryResults("RETURN LOWER(null)")); assertEqual([ "true" ], getQueryResults("RETURN LOWER(true)")); assertEqual([ "3" ], getQueryResults("RETURN LOWER(3)")); - assertEqual([ "" ], getQueryResults("RETURN LOWER([])")); - assertEqual([ "[object object]" ], getQueryResults("RETURN LOWER({})")); + assertEqual([ "[]" ], getQueryResults("RETURN LOWER([])")); + assertEqual([ "[1,2,3]" ], getQueryResults("RETURN LOWER([1,2,3])")); + assertEqual([ "{}" ], getQueryResults("RETURN LOWER({})")); + assertEqual([ "{\"a\":1,\"b\":2}" ], getQueryResults("RETURN LOWER({A:1,b:2})")); + assertEqual([ "{\"a\":1,\"a\":2,\"b\":3}" ], getQueryResults("RETURN LOWER({A:1,a:2,b:3})")); }, //////////////////////////////////////////////////////////////////////////////// @@ -1232,8 +1264,10 @@ function ahuacatlStringFunctionsTestSuite () { assertEqual([ "" ], getQueryResults("RETURN UPPER(null)")); assertEqual([ "TRUE" ], getQueryResults("RETURN UPPER(true)")); assertEqual([ "3" ], getQueryResults("RETURN UPPER(3)")); - assertEqual([ "" ], getQueryResults("RETURN UPPER([])")); - assertEqual([ "[OBJECT OBJECT]" ], getQueryResults("RETURN UPPER({})")); + assertEqual([ "[]" ], getQueryResults("RETURN UPPER([])")); + assertEqual([ "[1,2,3]" ], getQueryResults("RETURN UPPER([1,2,3])")); + assertEqual([ "{}" ], getQueryResults("RETURN UPPER({})")); + assertEqual([ "{\"A\":1,\"B\":2}" ], getQueryResults("RETURN UPPER({a:1, b:2})")); }, //////////////////////////////////////////////////////////////////////////////// @@ -1309,8 +1343,10 @@ function ahuacatlStringFunctionsTestSuite () { assertEqual([ "" ], getQueryResults(buildQuery(i, "null, 1"))); assertEqual([ "true" ], getQueryResults(buildQuery(i, "true, 0"))); assertEqual([ "3" ], getQueryResults(buildQuery(i, "3, 0"))); - assertEqual([ "" ], getQueryResults(buildQuery(i, "[ ], 0"))); - assertEqual([ "[object Object]" ], getQueryResults(buildQuery(i, "{ }, 0"))); + assertEqual([ "[]" ], getQueryResults(buildQuery(i, "[ ], 0"))); + assertEqual([ "[1,2,3]" ], getQueryResults(buildQuery(i, "[ 1, 2, 3 ], 0"))); + assertEqual([ "2,3]" ], getQueryResults(buildQuery(i, "[ 1, 2, 3 ], 3"))); + assertEqual([ "{}" ], getQueryResults(buildQuery(i, "{ }, 0"))); assertEqual([ "" ], getQueryResults(buildQuery(i, "\"yes\", null, 0"))); assertEqual([ "" ], getQueryResults(buildQuery(i, "\"yes\", true, 0"))); assertEqual([ "" ], getQueryResults(buildQuery(i, "\"yes\", \"yes\", 0"))); @@ -1444,8 +1480,13 @@ function ahuacatlStringFunctionsTestSuite () { assertEqual([ "c4ca4238a0b923820dcc509a6f75849b" ], getQueryResults("RETURN MD5(1)")); assertEqual([ "6bb61e3b7bce0931da574d19d1d82c88" ], getQueryResults("RETURN MD5(-1)")); assertEqual([ "d41d8cd98f00b204e9800998ecf8427e" ], getQueryResults("RETURN MD5(null)")); + assertEqual([ "d751713988987e9331980363e24189ce" ], getQueryResults("RETURN MD5([])")); + assertEqual([ "8d5162ca104fa7e79fe80fd92bb657fb" ], getQueryResults("RETURN MD5([0])")); assertEqual([ "35dba5d75538a9bbe0b4da4422759a0e" ], getQueryResults("RETURN MD5('[1]')")); - assertEqual([ "1441a7909c087dbbe7ce59881b9df8b9" ], getQueryResults("RETURN MD5({ })")); + assertEqual([ "f79408e5ca998cd53faf44af31e6eb45" ], getQueryResults("RETURN MD5([1,2])")); + assertEqual([ "99914b932bd37a50b983c5e7c90ae93b" ], getQueryResults("RETURN MD5({ })")); + assertEqual([ "99914b932bd37a50b983c5e7c90ae93b" ], getQueryResults("RETURN MD5({})")); + assertEqual([ "608de49a4600dbb5b173492759792e4a" ], getQueryResults("RETURN MD5({a:1,b:2})")); }, //////////////////////////////////////////////////////////////////////////////// @@ -1499,7 +1540,7 @@ function ahuacatlStringFunctionsTestSuite () { assertEqual([ "6bb61e3b7bce0931da574d19d1d82c88" ], getQueryResults("RETURN NOOPT(MD5(-1))")); assertEqual([ "d41d8cd98f00b204e9800998ecf8427e" ], getQueryResults("RETURN NOOPT(MD5(null))")); assertEqual([ "35dba5d75538a9bbe0b4da4422759a0e" ], getQueryResults("RETURN NOOPT(MD5('[1]'))")); - assertEqual([ "1441a7909c087dbbe7ce59881b9df8b9" ], getQueryResults("RETURN NOOPT(MD5({ }))")); + assertEqual([ "99914b932bd37a50b983c5e7c90ae93b" ], getQueryResults("RETURN NOOPT(MD5({}))")); assertEqual([ "c7cb8c1df686c0219d540849efe3bce3" ], getQueryResults("RETURN NOOPT(MD5('[1,2,4,7,11,16,22,29,37,46,56,67,79,92,106,121,137,154,172,191,211,232,254,277,301,326,352,379,407,436,466,497,529,562,596,631,667,704,742,781,821,862,904,947,991,1036,1082,1129,1177,1226,1276,1327,1379,1432,1486,1541,1597,1654,1712,1771,1831,1892,1954,2017,2081,2146,2212,2279,2347,2416,2486,2557,2629,2702,2776,2851,2927,3004,3082,3161,3241,3322,3404,3487,3571,3656,3742,3829,3917,4006,4096,4187,4279,4372,4466,4561,4657,4754,4852,4951]'))")); }, diff --git a/js/server/tests/aql/aql-operators.js b/js/server/tests/aql/aql-operators.js index 46297526f1..28f8fcf221 100644 --- a/js/server/tests/aql/aql-operators.js +++ b/js/server/tests/aql/aql-operators.js @@ -679,23 +679,23 @@ function ahuacatlOperatorsTestSuite () { {ex: "1 ", val: "'1 '"}, {ex: "0", val: "'0'"}, {ex: "-1", val: "'-1'"}, - {ex: "", val: "[ ]"}, - {ex: "0", val: "[ 0 ]"}, - {ex: "0,1", val: "[ 0, 1 ]"}, - {ex: "1,2", val: "[ 1, 2 ]"}, - {ex: "-1,0", val: "[ -1, 0 ]"}, - {ex: "0,1,1,2,9,4", val: "[ 0, 1, [1, 2], [ [ 9, 4 ] ] ]"}, - {ex: "[object Object]", val: "[ { } ]"}, - {ex: "0,1,[object Object]", val: "[ 0, 1, { } ]"}, - {ex: "[object Object],[object Object]", val: "[ { }, { } ]"}, - {ex: "", val: "['']"}, - {ex: "false", val: "[ false ]"}, - {ex: "true", val: "[ true ]"}, - {ex: "[object Object]", val: "{ }"}, - {ex: "[object Object]", val: "{ 'a' : true }"}, - {ex: "[object Object]", val: "{ 'a' : true, 'b' : 0 }"}, - {ex: "[object Object]", val: "{ 'a' : { }, 'b' : { } }"}, - {ex: "[object Object]", val: "{ 'a' : [ ], 'b' : [ ] }"} + {ex: "[]", val: "[ ]"}, + {ex: "[0]", val: "[ 0 ]"}, + {ex: "[0,1]", val: "[ 0, 1 ]"}, + {ex: "[1,2]", val: "[ 1, 2 ]"}, + {ex: "[-1,0]", val: "[ -1, 0 ]"}, + {ex: "[0,1,[1,2],[[9,4]]]", val: "[ 0, 1, [1, 2], [ [ 9, 4 ] ] ]"}, + {ex: "[{}]", val: "[ { } ]"}, + {ex: "[0,1,{}]", val: "[ 0, 1, { } ]"}, + {ex: "[{},{}]", val: "[ { }, { } ]"}, + {ex: "[\"\"]", val: "['']"}, + {ex: "[false]", val: "[ false ]"}, + {ex: "[true]", val: "[ true ]"}, + {ex: "{}", val: "{ }"}, + {ex: "{\"a\":true}", val: "{ 'a' : true }"}, + {ex: "{\"a\":true,\"b\":0}", val: "{ 'a' : true, 'b' : 0 }"}, + {ex: "{\"a\":{},\"b\":{}}", val: "{ 'a' : { }, 'b' : { } }"}, + {ex: "{\"a\":[],\"b\":[]}", val: "{ 'a' : [ ], 'b' : [ ] }"} ]; values.forEach(function(v) { var q = `RETURN TO_STRING(${v.val})`; @@ -4126,22 +4126,22 @@ function ahuacatlOperatorsTestSuite () { assertEqual("1", aql.AQL_CONCAT(undefined, 1)); assertEqual("2", aql.AQL_CONCAT(undefined, 2)); assertEqual("-1", aql.AQL_CONCAT(undefined, -1)); - assertEqual("", aql.AQL_CONCAT(undefined, [ ])); - assertEqual("1", aql.AQL_CONCAT(undefined, [ 1 ])); - assertEqual("12", aql.AQL_CONCAT(undefined, [ 1, 2 ])); - assertEqual("[object Object]", aql.AQL_CONCAT(undefined, { })); - assertEqual("[object Object]", aql.AQL_CONCAT(undefined, { 'a' : 0 })); + assertEqual("[]", aql.AQL_CONCAT(undefined, [ ])); + assertEqual("[1]", aql.AQL_CONCAT(undefined, [ 1 ])); + assertEqual("[1,2]", aql.AQL_CONCAT(undefined, [ 1, 2 ])); + assertEqual("{}", aql.AQL_CONCAT(undefined, { })); + assertEqual("{\"a\":0}", aql.AQL_CONCAT(undefined, { 'a' : 0 })); assertEqual("false", aql.AQL_CONCAT(false, undefined)); assertEqual("true", aql.AQL_CONCAT(true, undefined)); assertEqual("0", aql.AQL_CONCAT(0, undefined)); assertEqual("1", aql.AQL_CONCAT(1, undefined)); assertEqual("2", aql.AQL_CONCAT(2, undefined)); assertEqual("-1", aql.AQL_CONCAT(-1, undefined)); - assertEqual("", aql.AQL_CONCAT([ ], undefined)); - assertEqual("1", aql.AQL_CONCAT([ 1 ], undefined)); - assertEqual("12", aql.AQL_CONCAT([ 1, 2 ], undefined)); - assertEqual("[object Object]", aql.AQL_CONCAT({ }, undefined)); - assertEqual("[object Object]", aql.AQL_CONCAT({ 'a' : 0 }, undefined)); + assertEqual("[]", aql.AQL_CONCAT([ ], undefined)); + assertEqual("[1]", aql.AQL_CONCAT([ 1 ], undefined)); + assertEqual("[1,2]", aql.AQL_CONCAT([ 1, 2 ], undefined)); + assertEqual("{}", aql.AQL_CONCAT({ }, undefined)); + assertEqual("{\"a\":0}", aql.AQL_CONCAT({ 'a' : 0 }, undefined)); assertEqual("1", aql.AQL_CONCAT(1, NaN)); assertEqual("1", aql.AQL_CONCAT(1, null)); assertEqual("1false", aql.AQL_CONCAT(1, false)); @@ -4151,10 +4151,10 @@ function ahuacatlOperatorsTestSuite () { assertEqual("10", aql.AQL_CONCAT(1, '0')); assertEqual("11", aql.AQL_CONCAT(1, '1')); assertEqual("1a", aql.AQL_CONCAT(1, 'a')); - assertEqual("1", aql.AQL_CONCAT(1, [ ])); - assertEqual("10", aql.AQL_CONCAT(1, [ 0 ])); - assertEqual("1[object Object]", aql.AQL_CONCAT(1, { })); - assertEqual("1[object Object]", aql.AQL_CONCAT(1, { 'a' : 0 })); + assertEqual("1[]", aql.AQL_CONCAT(1, [ ])); + assertEqual("1[0]", aql.AQL_CONCAT(1, [ 0 ])); + assertEqual("1{}", aql.AQL_CONCAT(1, { })); + assertEqual("1{\"a\":0}", aql.AQL_CONCAT(1, { 'a' : 0 })); assertEqual("1", aql.AQL_CONCAT(NaN, 1)); assertEqual("1", aql.AQL_CONCAT(null, 1)); assertEqual("false1", aql.AQL_CONCAT(false, 1)); @@ -4164,10 +4164,10 @@ function ahuacatlOperatorsTestSuite () { assertEqual("01", aql.AQL_CONCAT('0', 1)); assertEqual("11", aql.AQL_CONCAT('1', 1)); assertEqual("a1", aql.AQL_CONCAT('a', 1)); - assertEqual("1", aql.AQL_CONCAT([ ], 1)); - assertEqual("01", aql.AQL_CONCAT([ 0 ], 1)); - assertEqual("[object Object]1", aql.AQL_CONCAT({ }, 1)); - assertEqual("[object Object]1", aql.AQL_CONCAT({ 'a' : 0 }, 1)); + assertEqual("[]1", aql.AQL_CONCAT([ ], 1)); + assertEqual("[0]1", aql.AQL_CONCAT([ 0 ], 1)); + assertEqual("{}1", aql.AQL_CONCAT({ }, 1)); + assertEqual("{\"a\":0}1", aql.AQL_CONCAT({ 'a' : 0 }, 1)); assertEqual("10", aql.AQL_CONCAT(1, 0)); assertEqual("1000", aql.AQL_CONCAT(100, 0)); assertEqual("-10", aql.AQL_CONCAT(-1, 0)); @@ -4175,12 +4175,13 @@ function ahuacatlOperatorsTestSuite () { assertEqual("00", aql.AQL_CONCAT(0, 0)); assertEqual("false", aql.AQL_CONCAT('', false)); assertEqual("true", aql.AQL_CONCAT('', true)); - assertEqual("", aql.AQL_CONCAT('', [ ])); - assertEqual("[object Object]", aql.AQL_CONCAT('', { })); + assertEqual("[]", aql.AQL_CONCAT('', [ ])); + assertEqual("{}", aql.AQL_CONCAT('', { })); assertEqual("afalse", aql.AQL_CONCAT('a', false)); assertEqual("atrue", aql.AQL_CONCAT('a', true)); - assertEqual("a", aql.AQL_CONCAT('a', [ ])); - assertEqual("a[object Object]", aql.AQL_CONCAT('a', { })); + assertEqual("a[]", aql.AQL_CONCAT('a', [ ])); + assertEqual("a{}", aql.AQL_CONCAT('a', { })); + assertEqual("a{\"foo\":\"bar\"}", aql.AQL_CONCAT('a', { foo: "bar" })); }, //////////////////////////////////////////////////////////////////////////////// diff --git a/lib/Basics/StringBuffer.cpp b/lib/Basics/StringBuffer.cpp index 378b7db6ad..34e8349246 100644 --- a/lib/Basics/StringBuffer.cpp +++ b/lib/Basics/StringBuffer.cpp @@ -27,50 +27,6 @@ #include "Basics/fpconv.h" #include "Zip/zip.h" -//////////////////////////////////////////////////////////////////////////////// -/// @brief escape tables -//////////////////////////////////////////////////////////////////////////////// - -#define Z16 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 - -//////////////////////////////////////////////////////////////////////////////// -/// @brief escape values for characters used in JSON string printing -/// use when forward slashes need no escaping -//////////////////////////////////////////////////////////////////////////////// - -static char const JsonEscapeTableWithoutSlash[256] = { - // 0 1 2 3 4 5 6 7 8 9 A B C D E F - 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'b', 't', 'n', - 'u', 'f', 'r', 'u', 'u', // 00 - 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', - 'u', 'u', 'u', 'u', 'u', // 10 - 0, 0, '"', 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, // 20 - Z16, Z16, // 30~4F - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, '\\', 0, 0, 0, // 50 - Z16, Z16, Z16, Z16, Z16, Z16, Z16, Z16, Z16, Z16 // 60~FF -}; - -//////////////////////////////////////////////////////////////////////////////// -/// @brief escape values for characters used in JSON string printing -/// use when forward slashes need escaping -//////////////////////////////////////////////////////////////////////////////// - -static char const JsonEscapeTableWithSlash[256] = { - // 0 1 2 3 4 5 6 7 8 9 A B C D E F - 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'b', 't', 'n', - 'u', 'f', 'r', 'u', 'u', // 00 - 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', - 'u', 'u', 'u', 'u', 'u', // 10 - 0, 0, '"', 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, '/', // 20 - Z16, Z16, // 30~4F - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, '\\', 0, 0, 0, // 50 - Z16, Z16, Z16, Z16, Z16, Z16, Z16, Z16, Z16, Z16 // 60~FF -}; - //////////////////////////////////////////////////////////////////////////////// /// @brief append a character without check //////////////////////////////////////////////////////////////////////////////// @@ -136,266 +92,6 @@ static int AppendString(TRI_string_buffer_t* self, char const* str, return TRI_ERROR_NO_ERROR; } -//////////////////////////////////////////////////////////////////////////////// -/// @brief escapes UTF-8 range U+0000 to U+007F -//////////////////////////////////////////////////////////////////////////////// - -static void EscapeUtf8Range0000T007F(TRI_string_buffer_t* self, - char const** src) { - uint8_t c = (uint8_t) * (*src); - - uint16_t i1 = (((uint16_t)c) & 0xF0) >> 4; - uint16_t i2 = (((uint16_t)c) & 0x0F); - - AppendString(self, "00", 2); - AppendChar(self, (i1 < 10) ? ('0' + i1) : ('A' + i1 - 10)); - AppendChar(self, (i2 < 10) ? ('0' + i2) : ('A' + i2 - 10)); -} - -//////////////////////////////////////////////////////////////////////////////// -/// @brief escapes UTF-8 range U+0080 to U+07FF -//////////////////////////////////////////////////////////////////////////////// - -static void EscapeUtf8Range0080T07FF(TRI_string_buffer_t* self, - char const** src) { - uint8_t c = (uint8_t) * ((*src) + 0); - uint8_t d = (uint8_t) * ((*src) + 1); - - // correct UTF-8 - if ((d & 0xC0) == 0x80) { - uint16_t n = ((c & 0x1F) << 6) | (d & 0x3F); - TRI_ASSERT(n >= 128); - - uint16_t i1 = (n & 0xF000) >> 12; - uint16_t i2 = (n & 0x0F00) >> 8; - uint16_t i3 = (n & 0x00F0) >> 4; - uint16_t i4 = (n & 0x000F); - - AppendChar(self, '\\'); - AppendChar(self, 'u'); - - AppendChar(self, (i1 < 10) ? ('0' + i1) : ('A' + i1 - 10)); - AppendChar(self, (i2 < 10) ? ('0' + i2) : ('A' + i2 - 10)); - AppendChar(self, (i3 < 10) ? ('0' + i3) : ('A' + i3 - 10)); - AppendChar(self, (i4 < 10) ? ('0' + i4) : ('A' + i4 - 10)); - - (*src) += 1; - } - - // corrupted UTF-8 - else { - AppendChar(self, *(*src)); - } -} - -//////////////////////////////////////////////////////////////////////////////// -/// @brief escapes UTF-8 range U+0800 to U+D7FF and U+E000 to U+FFFF -//////////////////////////////////////////////////////////////////////////////// - -static void EscapeUtf8Range0800TFFFF(TRI_string_buffer_t* self, - char const** src) { - uint8_t c = (uint8_t) * ((*src) + 0); - uint8_t d = (uint8_t) * ((*src) + 1); - uint8_t e = (uint8_t) * ((*src) + 2); - - // correct UTF-8 (3-byte sequence UTF-8 1110xxxx 10xxxxxx) - if ((d & 0xC0) == 0x80 && (e & 0xC0) == 0x80) { - uint16_t n = ((c & 0x0F) << 12) | ((d & 0x3F) << 6) | (e & 0x3F); - - TRI_ASSERT(n >= 2048 && (n < 55296 || n > 57343)); - - uint16_t i1 = (n & 0xF000) >> 12; - uint16_t i2 = (n & 0x0F00) >> 8; - uint16_t i3 = (n & 0x00F0) >> 4; - uint16_t i4 = (n & 0x000F); - - AppendChar(self, '\\'); - AppendChar(self, 'u'); - - AppendChar(self, (i1 < 10) ? ('0' + i1) : ('A' + i1 - 10)); - AppendChar(self, (i2 < 10) ? ('0' + i2) : ('A' + i2 - 10)); - AppendChar(self, (i3 < 10) ? ('0' + i3) : ('A' + i3 - 10)); - AppendChar(self, (i4 < 10) ? ('0' + i4) : ('A' + i4 - 10)); - - (*src) += 2; - } - - // corrupted UTF-8 - else { - AppendChar(self, *(*src)); - } -} - -//////////////////////////////////////////////////////////////////////////////// -/// @brief escapes UTF-8 range U+10000 to U+10FFFF -//////////////////////////////////////////////////////////////////////////////// - -static void EscapeUtf8Range10000T10FFFF(TRI_string_buffer_t* self, - char const** src) { - uint8_t c = (uint8_t) * ((*src) + 0); - uint8_t d = (uint8_t) * ((*src) + 1); - uint8_t e = (uint8_t) * ((*src) + 2); - uint8_t f = (uint8_t) * ((*src) + 3); - - // correct UTF-8 (4-byte sequence UTF-8 1110xxxx 10xxxxxx 10xxxxxx) - if ((d & 0xC0) == 0x80 && (e & 0xC0) == 0x80 && (f & 0xC0) == 0x80) { - uint32_t n = ((c & 0x0F) << 18) | ((d & 0x3F) << 12) | ((e & 0x3F) << 6) | - (f & 0x3F); - TRI_ASSERT(n >= 65536 && n <= 1114111); - - // construct the surrogate pairs - n -= 0x10000; - - uint32_t s1 = ((n & 0xFFC00) >> 10) + 0xD800; - uint32_t s2 = (n & 0x3FF) + 0xDC00; - - // encode high surrogate - uint16_t i1 = (s1 & 0xF000) >> 12; - uint16_t i2 = (s1 & 0x0F00) >> 8; - uint16_t i3 = (s1 & 0x00F0) >> 4; - uint16_t i4 = (s1 & 0x000F); - - AppendChar(self, '\\'); - AppendChar(self, 'u'); - - AppendChar(self, (i1 < 10) ? ('0' + i1) : ('A' + i1 - 10)); - AppendChar(self, (i2 < 10) ? ('0' + i2) : ('A' + i2 - 10)); - AppendChar(self, (i3 < 10) ? ('0' + i3) : ('A' + i3 - 10)); - AppendChar(self, (i4 < 10) ? ('0' + i4) : ('A' + i4 - 10)); - - // encode low surrogate - i1 = (s2 & 0xF000) >> 12; - i2 = (s2 & 0x0F00) >> 8; - i3 = (s2 & 0x00F0) >> 4; - i4 = (s2 & 0x000F); - - AppendChar(self, '\\'); - AppendChar(self, 'u'); - - AppendChar(self, (i1 < 10) ? ('0' + i1) : ('A' + i1 - 10)); - AppendChar(self, (i2 < 10) ? ('0' + i2) : ('A' + i2 - 10)); - AppendChar(self, (i3 < 10) ? ('0' + i3) : ('A' + i3 - 10)); - AppendChar(self, (i4 < 10) ? ('0' + i4) : ('A' + i4 - 10)); - - // advance src - (*src) += 3; - } - - // corrupted UTF-8 - else { - AppendChar(self, *(*src)); - } -} - -//////////////////////////////////////////////////////////////////////////////// -/// @brief appends characters but json-encode the string -//////////////////////////////////////////////////////////////////////////////// - -static int AppendJsonEncodedValue(TRI_string_buffer_t* self, char const*& ptr, - bool escapeSlash) { - int res = Reserve(self, 2); - - if (res != TRI_ERROR_NO_ERROR) { - return res; - } - - // next character as unsigned char - uint8_t c = (uint8_t)*ptr; - - // character is in the normal latin1 range - if ((c & 0x80) == 0) { - // special character, escape - char esc; - if (escapeSlash) { - esc = JsonEscapeTableWithSlash[c]; - } else { - esc = JsonEscapeTableWithoutSlash[c]; - } - - if (esc) { - AppendChar(self, '\\'); - AppendChar(self, esc); - - if (esc == 'u') { - // unicode output, must allocate more memory - res = Reserve(self, 4); - - if (res != TRI_ERROR_NO_ERROR) { - return res; - } - - EscapeUtf8Range0000T007F(self, &ptr); - } - } - - // normal latin1 - else { - AppendChar(self, *ptr); - } - } - - // unicode range 0080 - 07ff (2-byte sequence UTF-8) - else if ((c & 0xE0) == 0xC0) { - // hopefully correct UTF-8 - if (*(ptr + 1) != '\0') { - res = Reserve(self, 6); - - if (res != TRI_ERROR_NO_ERROR) { - return res; - } - EscapeUtf8Range0080T07FF(self, &ptr); - } - - // corrupted UTF-8 - else { - AppendChar(self, *ptr); - } - } - - // unicode range 0800 - ffff (3-byte sequence UTF-8) - else if ((c & 0xF0) == 0xE0) { - // hopefully correct UTF-8 - if (*(ptr + 1) != '\0' && *(ptr + 2) != '\0') { - res = Reserve(self, 6); - - if (res != TRI_ERROR_NO_ERROR) { - return res; - } - EscapeUtf8Range0800TFFFF(self, &ptr); - } - - // corrupted UTF-8 - else { - AppendChar(self, *ptr); - } - } - - // unicode range 10000 - 10ffff (4-byte sequence UTF-8) - else if ((c & 0xF8) == 0xF0) { - // hopefully correct UTF-8 - if (*(ptr + 1) != '\0' && *(ptr + 2) != '\0' && *(ptr + 3) != '\0') { - res = Reserve(self, 12); - - if (res != TRI_ERROR_NO_ERROR) { - return res; - } - EscapeUtf8Range10000T10FFFF(self, &ptr); - } - - // corrupted UTF-8 - else { - AppendChar(self, *ptr); - } - } - - // unicode range above 10ffff -- NOT IMPLEMENTED - else { - AppendChar(self, *ptr); - } - - return TRI_ERROR_NO_ERROR; -} - //////////////////////////////////////////////////////////////////////////////// /// @brief create a new string buffer and initialize it //////////////////////////////////////////////////////////////////////////////// @@ -803,25 +499,112 @@ int TRI_AppendString2StringBuffer(TRI_string_buffer_t* self, char const* str, return AppendString(self, str, len); } -//////////////////////////////////////////////////////////////////////////////// -/// @brief appends characters but json-encode the null-terminated string -//////////////////////////////////////////////////////////////////////////////// +int AppendJsonEncoded(TRI_string_buffer_t* self, char const* src, + size_t length, bool escapeForwardSlashes) { + static char const EscapeTable[256] = { + // 0 1 2 3 4 5 6 7 8 9 A B C D E + // F + 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'b', 't', 'n', 'u', 'f', 'r', + 'u', + 'u', // 00 + 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', + 'u', + 'u', // 10 + 0, 0, '"', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, + '/', // 20 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, + 0, // 30~4F + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + '\\', 0, 0, 0, // 50 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, + 0, // 60~FF + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0}; -int TRI_AppendJsonEncodedStringStringBuffer(TRI_string_buffer_t* self, - char const* src, bool escapeSlash) { - char const* ptr = src; + // reserve enough room for the whole string at once + int res = Reserve(self, 6 * length + 2); - while (*ptr != '\0') { - int res = AppendJsonEncodedValue(self, ptr, escapeSlash); + if (res != TRI_ERROR_NO_ERROR) { + return res; + } + + AppendChar(self, '"'); - if (res != TRI_ERROR_NO_ERROR) { - return res; + uint8_t const* p = reinterpret_cast(src); + uint8_t const* e = p + length; + while (p < e) { + uint8_t c = *p; + + if ((c & 0x80) == 0) { + // check for control characters + char esc = EscapeTable[c]; + + if (esc) { + if (c != '/' || escapeForwardSlashes) { + // escape forward slashes only when requested + AppendChar(self, '\\'); + } + AppendChar(self, static_cast(esc)); + + if (esc == 'u') { + uint16_t i1 = (((uint16_t)c) & 0xf0) >> 4; + uint16_t i2 = (((uint16_t)c) & 0x0f); + + AppendChar(self, '0'); + AppendChar(self, '0'); + AppendChar(self, static_cast((i1 < 10) ? ('0' + i1) : ('A' + i1 - 10))); + AppendChar(self, static_cast((i2 < 10) ? ('0' + i2) : ('A' + i2 - 10))); + } + } else { + AppendChar(self, static_cast(c)); + } + } else if ((c & 0xe0) == 0xc0) { + // two-byte sequence + if (p + 1 >= e) { + return TRI_ERROR_INTERNAL; + } + + memcpy(self->_current, reinterpret_cast(p), 2); + self->_current += 2; + ++p; + } else if ((c & 0xf0) == 0xe0) { + // three-byte sequence + if (p + 2 >= e) { + return TRI_ERROR_INTERNAL; + } + + memcpy(self->_current, reinterpret_cast(p), 3); + self->_current += 3; + p += 2; + } else if ((c & 0xf8) == 0xf0) { + // four-byte sequence + if (p + 3 >= e) { + return TRI_ERROR_INTERNAL; + } + + memcpy(self->_current, reinterpret_cast(p), 4); + self->_current += 4; + p += 3; } - ++ptr; + ++p; } - return TRI_ERROR_NO_ERROR; + AppendChar(self, '"'); + return TRI_ERROR_NO_ERROR; } //////////////////////////////////////////////////////////////////////////////// @@ -831,20 +614,7 @@ int TRI_AppendJsonEncodedStringStringBuffer(TRI_string_buffer_t* self, int TRI_AppendJsonEncodedStringStringBuffer(TRI_string_buffer_t* self, char const* src, size_t length, bool escapeSlash) { - char const* ptr = src; - char const* end = src + length; - - while (ptr < end) { - int res = AppendJsonEncodedValue(self, ptr, escapeSlash); - - if (res != TRI_ERROR_NO_ERROR) { - return res; - } - - ++ptr; - } - - return TRI_ERROR_NO_ERROR; + return AppendJsonEncoded(self, src, length, escapeSlash); } //////////////////////////////////////////////////////////////////////////////// diff --git a/lib/Basics/StringBuffer.h b/lib/Basics/StringBuffer.h index 8659a847b4..6f6438c77a 100644 --- a/lib/Basics/StringBuffer.h +++ b/lib/Basics/StringBuffer.h @@ -229,13 +229,6 @@ static inline void TRI_AppendStringUnsafeStringBuffer(TRI_string_buffer_t* self, self->_current += str.size(); } -//////////////////////////////////////////////////////////////////////////////// -/// @brief appends characters but json-encode the null-terminated string -//////////////////////////////////////////////////////////////////////////////// - -int TRI_AppendJsonEncodedStringStringBuffer(TRI_string_buffer_t* self, - char const* str, bool); - //////////////////////////////////////////////////////////////////////////////// /// @brief appends characters but json-encode the string //////////////////////////////////////////////////////////////////////////////// @@ -791,15 +784,6 @@ class StringBuffer { /// @brief appends as json-encoded ////////////////////////////////////////////////////////////////////////////// - StringBuffer& appendJsonEncoded(char const* str) { - TRI_AppendJsonEncodedStringStringBuffer(&_buffer, str, true); - return *this; - } - - ////////////////////////////////////////////////////////////////////////////// - /// @brief appends as json-encoded - ////////////////////////////////////////////////////////////////////////////// - StringBuffer& appendJsonEncoded(char const* str, size_t length) { TRI_AppendJsonEncodedStringStringBuffer(&_buffer, str, length, true); return *this; diff --git a/lib/Basics/json.cpp b/lib/Basics/json.cpp index eea4d81ae2..9fec1b619e 100644 --- a/lib/Basics/json.cpp +++ b/lib/Basics/json.cpp @@ -78,27 +78,17 @@ static int StringifyJson(TRI_memory_zone_t* zone, TRI_string_buffer_t* buffer, case TRI_JSON_STRING: case TRI_JSON_STRING_REFERENCE: { - res = TRI_AppendCharStringBuffer(buffer, '\"'); - - if (res != TRI_ERROR_NO_ERROR) { - return res; - } - if (object->_value._string.length > 0) { // optimisation for the empty string res = TRI_AppendJsonEncodedStringStringBuffer( buffer, object->_value._string.data, object->_value._string.length - 1, false); - - if (res != TRI_ERROR_NO_ERROR) { - return TRI_ERROR_OUT_OF_MEMORY; - } + } else { + res = TRI_AppendString2StringBuffer(buffer, "\"\"", 2); } - - res = TRI_AppendCharStringBuffer(buffer, '\"'); - + if (res != TRI_ERROR_NO_ERROR) { - return res; + return TRI_ERROR_OUT_OF_MEMORY; } break; diff --git a/lib/Basics/system-functions.cpp b/lib/Basics/system-functions.cpp index e96fdc62f3..1587b95a41 100644 --- a/lib/Basics/system-functions.cpp +++ b/lib/Basics/system-functions.cpp @@ -46,6 +46,41 @@ void* memrchr(void const* block, int c, size_t size) { #endif +//////////////////////////////////////////////////////////////////////////////// +/// @brief memmem +//////////////////////////////////////////////////////////////////////////////// + +#ifdef _WIN32 + +void* xmemmem(void const* haystack, size_t haystackLength, + void const* needle, size_t needleLength) { + if (haystackLength == 0 || + needleLength == 0 || + haystackLength < needleLength) { + return nullptr; + } + + char const* n = static_cast(needle); + + if (needleLength == 1) { + return memchr(const_cast(haystack), static_cast(*n), haystackLength); + } + + char const* current = static_cast(haystack); + char const* end = static_cast(haystack) + haystackLength - needleLength; + + for (; current <= end; ++current) { + if (*current == *n && + memcmp(needle, current, needleLength) == 0) { + return const_cast(static_cast(current)); + } + } + + return nullptr; +} + +#endif + //////////////////////////////////////////////////////////////////////////////// /// @brief get the time of day //////////////////////////////////////////////////////////////////////////////// diff --git a/lib/Basics/system-functions.h b/lib/Basics/system-functions.h index 20c068710e..0da91a37de 100644 --- a/lib/Basics/system-functions.h +++ b/lib/Basics/system-functions.h @@ -36,6 +36,14 @@ void* memrchr(void const* block, int c, size_t size); #endif +//////////////////////////////////////////////////////////////////////////////// +/// @brief memmem +//////////////////////////////////////////////////////////////////////////////// + +#ifdef _WIN32 +void* memmem(void const* haystack, size_t haystackLength, void const* needle, size_t needleLength); +#endif + //////////////////////////////////////////////////////////////////////////////// /// @brief get the time of day ////////////////////////////////////////////////////////////////////////////////