From f7b80bf656e9d437909057dd3b50cd20d44c1303 Mon Sep 17 00:00:00 2001 From: Wilfried Goesgens Date: Fri, 11 Mar 2016 11:18:28 +0100 Subject: [PATCH 01/20] Add handler for TLS V1.2 as sugested by kurtkincaid in #1771 --- lib/Basics/ssl-helper.cpp | 7 +++++++ lib/Basics/ssl-helper.h | 1 + 2 files changed, 8 insertions(+) diff --git a/lib/Basics/ssl-helper.cpp b/lib/Basics/ssl-helper.cpp index 499d1e707e..48fd2df908 100644 --- a/lib/Basics/ssl-helper.cpp +++ b/lib/Basics/ssl-helper.cpp @@ -59,6 +59,10 @@ SSL_CTX* arangodb::sslContext(protocol_e protocol, std::string const& keyfile) { meth = TLSv1_method(); break; + case TLS_V12: + meth = TLSv1_2_method(); + break; + default: LOG(ERR) << "unknown SSL protocol method"; return nullptr; @@ -104,6 +108,9 @@ std::string arangodb::protocolName(protocol_e protocol) { case TLS_V1: return "TLSv1"; + case TLS_V12: + return "TLSv12"; + default: return "unknown"; } diff --git a/lib/Basics/ssl-helper.h b/lib/Basics/ssl-helper.h index dcf5d33a98..679c89203c 100644 --- a/lib/Basics/ssl-helper.h +++ b/lib/Basics/ssl-helper.h @@ -40,6 +40,7 @@ enum protocol_e { SSL_V23 = 2, SSL_V3 = 3, TLS_V1 = 4, + TLS_V12 = 5, SSL_LAST }; From b29c437b7f93316455f7e1220253a9fe973cb7a3 Mon Sep 17 00:00:00 2001 From: Andreas Streichardt Date: Fri, 11 Mar 2016 18:22:12 +0100 Subject: [PATCH 02/20] Add more error info --- js/server/modules/@arangodb/cluster.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/js/server/modules/@arangodb/cluster.js b/js/server/modules/@arangodb/cluster.js index e10b08b28c..d3d5c49f1c 100644 --- a/js/server/modules/@arangodb/cluster.js +++ b/js/server/modules/@arangodb/cluster.js @@ -85,7 +85,7 @@ function startReadingQuery (endpoint, collName, timeout) { } catch (err) { console.error("startReadingQuery: Bad response body from", - "/_api/query/current", r); + "/_api/query/current", r, JSON.stringify(err)); continue; } for (var i = 0; i < r.length; i++) { @@ -1116,7 +1116,7 @@ function setupReplication () { } } catch (err) { - console.error("Could not set up replication for database ", database); + console.error("Could not set up replication for database ", database, JSON.stringify(err)); ok = false; } } From bc1563650b68a8ecf5a07c9ecd3cbea49ea780db Mon Sep 17 00:00:00 2001 From: jsteemann Date: Sat, 12 Mar 2016 11:47:08 +0100 Subject: [PATCH 03/20] added missing function --- js/apps/system/_admin/aardvark/APP/frontend/src/mode-aql.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/apps/system/_admin/aardvark/APP/frontend/src/mode-aql.js b/js/apps/system/_admin/aardvark/APP/frontend/src/mode-aql.js index 3e43efedd2..8cb624218d 100644 --- a/js/apps/system/_admin/aardvark/APP/frontend/src/mode-aql.js +++ b/js/apps/system/_admin/aardvark/APP/frontend/src/mode-aql.js @@ -106,7 +106,7 @@ var AqlHighlightRules = function() { "date_minute|date_second|date_millisecond|date_dayofyear|date_isoweek|date_leapyear|date_quarter|date_days_in_month|" + "date_add|date_subtract|date_diff|date_compare|date_format|fail|passthru|sleep|not_null|" + "first_list|first_document|parse_identifier|current_user|current_database|" + - "collections|document|union|union_distinct|intersection|flatten|" + + "collections|document|union|union_distinct|intersection|flatten|is_same_collection|" + "ltrim|rtrim|find_first|find_last|split|substitute|md5|sha1|random_token|AQL_LAST_ENTRY)" ); From 601f8c5f006de8837b803e2bb0b95d7fe4b51135 Mon Sep 17 00:00:00 2001 From: Frank Celler Date: Sat, 12 Mar 2016 17:27:47 +0100 Subject: [PATCH 04/20] fixed cleanup --- arangosh/Shell/V8ShellFeature.cpp | 7 +++++++ lib/V8/v8-globals.cpp | 1 + lib/V8/v8-globals.h | 1 + 3 files changed, 9 insertions(+) diff --git a/arangosh/Shell/V8ShellFeature.cpp b/arangosh/Shell/V8ShellFeature.cpp index 2bfb68a681..f90d577102 100644 --- a/arangosh/Shell/V8ShellFeature.cpp +++ b/arangosh/Shell/V8ShellFeature.cpp @@ -137,6 +137,13 @@ void V8ShellFeature::stop() { { v8::Locker locker{_isolate}; v8::Isolate::Scope isolate_scope{_isolate}; + + TRI_v8_global_t* v8g = \ + static_cast(_isolate->GetData(V8DataSlot)); + _isolate->SetData(V8DataSlot, nullptr); + + delete v8g; + _context.Reset(); } diff --git a/lib/V8/v8-globals.cpp b/lib/V8/v8-globals.cpp index b881d7167c..75322e5717 100644 --- a/lib/V8/v8-globals.cpp +++ b/lib/V8/v8-globals.cpp @@ -227,6 +227,7 @@ TRI_v8_global_t* TRI_CreateV8Globals(v8::Isolate* isolate) { TRI_v8_global_t* TRI_GetV8Globals(v8::Isolate* isolate) { TRI_GET_GLOBALS(); + if (v8g == nullptr) { v8g = TRI_CreateV8Globals(isolate); } diff --git a/lib/V8/v8-globals.h b/lib/V8/v8-globals.h index 569af57960..7b63e90f23 100644 --- a/lib/V8/v8-globals.h +++ b/lib/V8/v8-globals.h @@ -50,6 +50,7 @@ static const uint32_t V8DataSlot = 0; #define TRI_V8_TRY_CATCH_BEGIN(isolateVar) \ auto isolateVar = args.GetIsolate(); \ try { + //////////////////////////////////////////////////////////////////////////////// /// @brief macro to terminate a try-catch sequence for V8 callbacks //////////////////////////////////////////////////////////////////////////////// From bb8f603d7f1c89ba10839234c940788f4c78f3fa Mon Sep 17 00:00:00 2001 From: Frank Celler Date: Sat, 12 Mar 2016 23:52:12 +0100 Subject: [PATCH 05/20] free connection --- arangosh/Shell/V8ShellFeature.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/arangosh/Shell/V8ShellFeature.cpp b/arangosh/Shell/V8ShellFeature.cpp index f90d577102..d426bb1888 100644 --- a/arangosh/Shell/V8ShellFeature.cpp +++ b/arangosh/Shell/V8ShellFeature.cpp @@ -281,6 +281,7 @@ int V8ShellFeature::runShell(std::vector const& positionals) { bool promptError; auto v8connection = setup(context, true, positionals, &promptError); + std::unique_ptr guard(v8connection); V8LineEditor v8LineEditor(_isolate, context, "." + _name + ".history"); @@ -407,7 +408,8 @@ bool V8ShellFeature::runScript(std::vector const& files, v8::Context::Scope context_scope{context}; - setup(context, execute, positionals); + auto v8connection = setup(context, execute, positionals); + std::unique_ptr guard(v8connection); bool ok = true; @@ -484,7 +486,8 @@ bool V8ShellFeature::runString(std::vector const& strings, v8::Context::Scope context_scope{context}; - setup(context, true, positionals); + auto v8connection = setup(context, true, positionals); + std::unique_ptr guard(v8connection); bool ok = true; @@ -587,7 +590,8 @@ bool V8ShellFeature::runUnitTests(std::vector const& files, v8::Context::Scope context_scope{context}; - setup(context, true, positionals); + auto v8connection = setup(context, true, positionals); + std::unique_ptr guard(v8connection); bool ok = true; From 47e5069bf75acbb9e196a4f1b0e3fec72d299add Mon Sep 17 00:00:00 2001 From: jsteemann Date: Mon, 14 Mar 2016 09:13:55 +0100 Subject: [PATCH 06/20] do not leak --- arangod/Aql/Functions.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arangod/Aql/Functions.cpp b/arangod/Aql/Functions.cpp index 5a266bcab8..8943f8075d 100644 --- a/arangod/Aql/Functions.cpp +++ b/arangod/Aql/Functions.cpp @@ -2309,7 +2309,7 @@ AqlValue$ Functions::ConcatVPack(arangodb::aql::Query* query, size_t length = buffer.length(); try { std::shared_ptr builder = query->getSharedBuilder(); - std::string res(buffer.steal(), length); + std::string res(buffer.c_str(), length); builder->add(VPackValue(std::move(res))); return AqlValue$(builder.get()); } catch (...) { From c77b919bb724cf2b97a46adb75b41b00598d7abb Mon Sep 17 00:00:00 2001 From: Frank Celler Date: Mon, 14 Mar 2016 19:00:46 +0100 Subject: [PATCH 07/20] added intensity --- lib/ApplicationFeatures/ConsoleFeature.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/ApplicationFeatures/ConsoleFeature.cpp b/lib/ApplicationFeatures/ConsoleFeature.cpp index b5005e6659..64c092f7a5 100644 --- a/lib/ApplicationFeatures/ConsoleFeature.cpp +++ b/lib/ApplicationFeatures/ConsoleFeature.cpp @@ -38,6 +38,7 @@ using namespace arangodb::options; #ifdef _WIN32 static const int FOREGROUND_WHITE = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE; static const int BACKGROUND_WHITE = BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE; +static const int INTENSITY = FOREGROUND_INTENSITY | BACKGROUND_INTENSITY; #endif ConsoleFeature::ConsoleFeature(application_features::ApplicationServer* server) @@ -73,7 +74,7 @@ ConsoleFeature::ConsoleFeature(application_features::ApplicationServer* server) CONSOLE_SCREEN_BUFFER_INFO info; GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &info); - _defaultAttribute = info.wAttributes & (FOREGROUND_INTENSITY | BACKGROUND_INTENSITY); + _defaultAttribute = info.wAttributes & INTENSITY; _defaultColor = info.wAttributes & FOREGROUND_WHITE; _defaultBackground = info.wAttributes & BACKGROUND_WHITE; @@ -210,7 +211,7 @@ void ConsoleFeature::_print(std::string const& s) { case 1: // BOLD case 5: // BLINK - _consoleAttribute = FOREGROUND_INTENSITY; + _consoleAttribute = (_defaultAttribute ^ FOREGROUND_INTENSITY) & INTENSITY; break; case 30: From 1df84976d7df571d3b6ea40d6a489e14d7c519f2 Mon Sep 17 00:00:00 2001 From: Wilfried Goesgens Date: Tue, 15 Mar 2016 00:13:38 +0100 Subject: [PATCH 08/20] as sugested by @MalkmusT in #1780 specify a relative path when installing files, so non-root users can install into their home directories. --- cmake/ArangoDBMacros.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/ArangoDBMacros.cmake b/cmake/ArangoDBMacros.cmake index 96f519c1c5..b460e60a39 100644 --- a/cmake/ArangoDBMacros.cmake +++ b/cmake/ArangoDBMacros.cmake @@ -396,7 +396,7 @@ set(CPACK_PROJECT_CONFIG_FILE "${CMAKE_BINARY_DIR}/CMakeCPackOptions.cmake") install( FILES ${PROJECT_SOURCE_DIR}/Installation/debian/arangodb.init PERMISSIONS OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE - DESTINATION /etc/init.d + DESTINATION etc/init.d RENAME arangodb COMPONENT debian-extras ) From ae8430589f69bf242cd7580400a96596fd5e9f6d Mon Sep 17 00:00:00 2001 From: Frank Celler Date: Tue, 15 Mar 2016 16:54:43 +0100 Subject: [PATCH 09/20] new linenoise-ng --- 3rdParty/linenoise-ng/.clang-format | 4 + 3rdParty/linenoise-ng/.gitignore | 8 + 3rdParty/linenoise-ng/CMakeLists.txt | 39 +- 3rdParty/linenoise-ng/LICENSE | 75 +- 3rdParty/linenoise-ng/README.md | 86 +- 3rdParty/linenoise-ng/appveyor.yml | 11 + 3rdParty/linenoise-ng/include/linenoise.h | 30 +- 3rdParty/linenoise-ng/src/linenoise.cpp | 4731 +++++++++++---------- 3rdParty/linenoise-ng/tst/example.c | 25 +- 9 files changed, 2695 insertions(+), 2314 deletions(-) create mode 100644 3rdParty/linenoise-ng/.clang-format create mode 100644 3rdParty/linenoise-ng/appveyor.yml diff --git a/3rdParty/linenoise-ng/.clang-format b/3rdParty/linenoise-ng/.clang-format new file mode 100644 index 0000000000..387684359d --- /dev/null +++ b/3rdParty/linenoise-ng/.clang-format @@ -0,0 +1,4 @@ +BasedOnStyle: Google +DerivePointerAlignment: false +PointerAlignment: Left +Standard: Cpp11 diff --git a/3rdParty/linenoise-ng/.gitignore b/3rdParty/linenoise-ng/.gitignore index ee0c56f055..e637ed4a31 100644 --- a/3rdParty/linenoise-ng/.gitignore +++ b/3rdParty/linenoise-ng/.gitignore @@ -1,4 +1,12 @@ +CMakeCache.txt +CMakeFiles +Makefile +build +cmake_install.cmake +install_manifest.txt +example linenoise_example +liblinenoise.a *.dSYM history.txt *.o diff --git a/3rdParty/linenoise-ng/CMakeLists.txt b/3rdParty/linenoise-ng/CMakeLists.txt index 37bf513686..1df8007fe0 100755 --- a/3rdParty/linenoise-ng/CMakeLists.txt +++ b/3rdParty/linenoise-ng/CMakeLists.txt @@ -29,27 +29,42 @@ if(CMAKE_COMPILER_IS_GNUCXX) set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} ${BASE_COMPILER_OPTIONS} -O3 -fomit-frame-pointer") set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} ${BASE_COMPILER_OPTIONS} -O3 -g") -elseif(CMAKE_COMPILER_IS_CLANGCXX) +elseif(CMAKE_COMPILER_IS_CLANGCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") + # using regular Clang or AppleClang message(STATUS "Compiler type CLANG: ${CMAKE_CXX_COMPILER}") set(BASE_COMPILER_OPTIONS "-std=c++11 -Wall -Wextra") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${BASE_COMPILER_COMPTIONS}") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${BASE_COMPILER_OPTIONS}") set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} ${BASE_COMPILER_OPTIONS} -O0 -g") set(CMAKE_CXX_FLAGS_MINSIZEREL "${CMAKE_CXX_FLAGS_MINSIZEREL} ${BASE_COMPILER_OPTIONS} -Os") set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} ${BASE_COMPILER_OPTIONS} -O3 -fomit-frame-pointer") set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} ${BASE_COMPILER_OPTIONS} -O3 -g") - -elseif(MSVC) + +elseif(MSVC) message(STATUS "Compiler type MSVC: ${CMAKE_CXX_COMPILER}") add_definitions("-D_CRT_SECURE_NO_WARNINGS=1") - set(${CMAKE_CXX_FLAGS} "${CMAKE_CXX_FLAGS} /MT") - set(${CMAKE_CXX_FLAGS_DEBUG} "${CMAKE_CXX_FLAGS_DEBUG} /MTd") - # hard-coded target platform to x64. does not link otherwise - set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /MACHINE:x64 /SUBSYSTEM:CONSOLE /LTCG /SAFESEH:NO /ignore:4099") + + foreach (flag_var + CMAKE_CXX_FLAGS + CMAKE_CXX_FLAGS_DEBUG + CMAKE_CXX_FLAGS_RELEASE + CMAKE_CXX_FLAGS_MINSIZEREL + CMAKE_CXX_FLAGS_RELWITHDEBINFO) + if (flag_var MATCHES "DEBUG") + set(${flag_var} "${${flag_var}} /MTd") + else () + set(${flag_var} "${${flag_var}} /MT") + endif () + endforeach() + # https://msdn.microsoft.com/en-us/library/aa267384%28VS.60%29.aspx + set(CMAKE_EXE_LINKER_FLAGS_DEBUG "${CMAKE_EXE_LINKER_FLAGS_DEBUG} /INCREMENTAL:NO /SUBSYSTEM:CONSOLE /LTCG /ignore:4099 /NODEFAULTLIB:libc.lib /NODEFAULTLIB:libcmt.lib /NODEFAULTLIB:msvcrt.lib /NODEFAULTLIB:libcd.lib /NODEFAULTLIB:msvcrtd.lib") + set(CMAKE_EXE_LINKER_FLAGS_MINSIZEREL "${CMAKE_EXE_LINKER_FLAGS_MINSIZEREL} /SUBSYSTEM:CONSOLE /ignore:4099 /NODEFAULTLIB:libc.lib /NODEFAULTLIB:libcmtd.lib /NODEFAULTLIB:msvcrt.lib /NODEFAULTLIB:libcd.lib /NODEFAULTLIB:msvcrtd.lib") + set(CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS_RELEASE} /SUBSYSTEM:CONSOLE /ignore:4099 /NODEFAULTLIB:libc.lib /NODEFAULTLIB:libcmtd.lib /NODEFAULTLIB:msvcrt.lib /NODEFAULTLIB:libcd.lib /NODEFAULTLIB:msvcrtd.lib") + set(CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO "${CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO} /SUBSYSTEM:CONSOLE /ignore:4099 /NODEFAULTLIB:libc.lib /NODEFAULTLIB:libcmtd.lib /NODEFAULTLIB:msvcrt.lib /NODEFAULTLIB:libcd.lib /NODEFAULTLIB:msvcrtd.lib") else() # unknown compiler message(STATUS "Compiler type UNKNOWN: ${CMAKE_CXX_COMPILER}") set(BASE_COMPILER_OPTIONS "-std=c++11 -Wall -Wextra") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${BASE_COMPILER_COMPTIONS}") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${BASE_COMPILER_OPTIONS}") set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} ${BASE_COMPILER_OPTIONS} -O0 -g") set(CMAKE_CXX_FLAGS_MINSIZEREL "${CMAKE_CXX_FLAGS_MINSIZEREL} ${BASE_COMPILER_OPTIONS} -Os") set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} ${BASE_COMPILER_OPTIONS} -O3 -fomit-frame-pointer") @@ -68,6 +83,12 @@ add_library( src/wcwidth.cpp ) +# install +install(TARGETS linenoise DESTINATION lib) + +# headers +install(FILES include/linenoise.h DESTINATION include) + # build example add_executable( example diff --git a/3rdParty/linenoise-ng/LICENSE b/3rdParty/linenoise-ng/LICENSE index 18e814865a..b7c58c4458 100644 --- a/3rdParty/linenoise-ng/LICENSE +++ b/3rdParty/linenoise-ng/LICENSE @@ -1,25 +1,66 @@ -Copyright (c) 2010-2014, Salvatore Sanfilippo -Copyright (c) 2010-2013, Pieter Noordhuis +linenoise.cpp +============= + +Copyright (c) 2010, Salvatore Sanfilippo +Copyright (c) 2010, Pieter Noordhuis All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: -* Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of Redis nor the names of its contributors may be used + to endorse or promote products derived from this software without + specific prior written permission. -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +wcwidth.cpp +=========== + +Markus Kuhn -- 2007-05-26 (Unicode 5.0) + +Permission to use, copy, modify, and distribute this software +for any purpose and without fee is hereby granted. The author +disclaims all warranties with regard to this software. + + + +ConvertUTF.cpp +============== + +Copyright 2001-2004 Unicode, Inc. + +Disclaimer + +This source code is provided as is by Unicode, Inc. No claims are +made as to fitness for any particular purpose. No warranties of any +kind are expressed or implied. The recipient agrees to determine +applicability of information provided. If this file has been +purchased on magnetic or optical media from Unicode, Inc., the +sole remedy for any claim will be exchange of defective media +within 90 days of receipt. + +Limitations on Rights to Redistribute This Code + +Unicode, Inc. hereby grants the right to freely use the information +supplied in this file in the creation of products supporting the +Unicode Standard, and to make copies of this file in any form +for internal or external distribution as long as this notice +remains attached. diff --git a/3rdParty/linenoise-ng/README.md b/3rdParty/linenoise-ng/README.md index 82845be896..81898db6de 100644 --- a/3rdParty/linenoise-ng/README.md +++ b/3rdParty/linenoise-ng/README.md @@ -1,23 +1,87 @@ # Linenoise Next Generation -A linenoise implementation based on the work by Salvatore Sanfilippo, -10gen Inc and others. They goal is to create a zero-config, BSD +A small, portable GNU readline replacement for Linux, Windows and +MacOS which is capable of handling UTF-8 characters. Unlike GNU +readline, which is GPL, this library uses a BSD license and can be +used in any kind of program. + +## Origin + +This linenoise implementation is based on the work by +[Salvatore Sanfilippo](https://github.com/antirez/linenoise) and +10gen Inc. The goal is to create a zero-config, BSD licensed, readline replacement usable in Apache2 or BSD licensed programs. -* single and multi line editing mode with the usual key bindings implemented +## Features + +* single-line and multi-line editing mode with the usual key bindings implemented * history handling * completion * BSD license source code * Only uses a subset of VT100 escapes (ANSI.SYS compatible) * UTF8 aware -* support for linux, MacOS and Windows +* support for Linux, MacOS and Windows It deviates from Salvatore's original goal to have a minimal readline replacement for the sake of supporting UTF8 and Windows. It deviates -from 10gen Inc. goal to create a C++ interface, we stick to a pure -C interface. However, the library itself uses C++11 unicode strings -internally. +from 10gen Inc.'s goal to create a C++ interface to linenoise. This +library uses C++ internally, but to the user it provides a pure C +interface that is compatible with the original linenoise API. +C interface. + +## Requirements + +To build this library, you will need a C++11-enabled compiler and +some recent version of CMake. + +## Build instructions + +To build this library on Linux, first create a build directory + +```bash +mkdir -p build +``` + +and then build the library: + +```bash +(cd build && cmake -DCMAKE_BUILD_TYPE=Release .. && make) +``` + +To build and install the library at the default target location, use + +```bash +(cd build && cmake -DCMAKE_BUILD_TYPE=Release .. && make && sudo make install) +``` + +The default installation location can be adjusted by setting the `DESTDIR` +variable when invoking `make install`: + +```bash +(cd build && make DESTDIR=/tmp install) +``` + +To build the library on Windows, use these commands in an MS-DOS command +prompt: + +``` +md build +cd build +``` + +After that, invoke the appropriate command to create the files for your +target environment: + +* 32 bit: `cmake -G "Visual Studio 12 2013" -DCMAKE_BUILD_TYPE=Release ..` +* 64 bit: `cmake -G "Visual Studio 12 2013 Win64" -DCMAKE_BUILD_TYPE=Release ..` + +After that, open the generated file `linenoise.sln` from the `build` +subdirectory with Visual Studio. + + +*note: the following sections of the README.md are from the original +linenoise repository and are partly outdated* ## Can a line editing library be 20k lines of code? @@ -79,12 +143,14 @@ use both in free software and commercial software. * FreeBSD xterm ($TERM = xterm) * ANSI.SYS * Emacs comint mode ($TERM = dumb) + * Windows Please test it everywhere you can and report back! ## Let's push this forward! Patches should be provided in the respect of linenoise sensibility for -small easy to understand code that. They must be submitted under the -Apache2 license using the supplied Apache2 contributor license -agreement. +small and easy to understand code that and the license +restrictions. Extensions must be submitted under a BSD license-style. +A contributor license is required for contributions. + diff --git a/3rdParty/linenoise-ng/appveyor.yml b/3rdParty/linenoise-ng/appveyor.yml new file mode 100644 index 0000000000..acc9a253bb --- /dev/null +++ b/3rdParty/linenoise-ng/appveyor.yml @@ -0,0 +1,11 @@ +version: 1.0.{build} +branches: + only: + - master +configuration: Release +build: +build_script: + - md build + - cd %APPVEYOR_BUILD_FOLDER%\build + - cmake -G "Visual Studio 12 Win64" -DCMAKE_BUILD_TYPE=Release .. + - cmake --build . --config Release diff --git a/3rdParty/linenoise-ng/include/linenoise.h b/3rdParty/linenoise-ng/include/linenoise.h index 7f2b4ebcb2..aac62828a5 100644 --- a/3rdParty/linenoise-ng/include/linenoise.h +++ b/3rdParty/linenoise-ng/include/linenoise.h @@ -44,21 +44,25 @@ extern "C" { #endif - typedef struct linenoiseCompletions linenoiseCompletions; +typedef struct linenoiseCompletions linenoiseCompletions; - typedef void(linenoiseCompletionCallback)(const char*, linenoiseCompletions*); - void linenoiseSetCompletionCallback(linenoiseCompletionCallback* fn); - void linenoiseAddCompletion(linenoiseCompletions* lc, const char* str); +typedef void(linenoiseCompletionCallback)(const char*, linenoiseCompletions*); +void linenoiseSetCompletionCallback(linenoiseCompletionCallback* fn); +void linenoiseAddCompletion(linenoiseCompletions* lc, const char* str); - char* linenoise(const char* prompt); - void linenoisePreloadBuffer(const char* preloadText); - int linenoiseHistoryAdd(const char* line); - int linenoiseHistorySetMaxLen(int len); - int linenoiseHistorySave(const char* filename); - int linenoiseHistoryLoad(const char* filename); - void linenoiseHistoryFree(void); - void linenoiseClearScreen(void); - int linenoiseInstallWindowChangeHandler(void); +char* linenoise(const char* prompt); +void linenoisePreloadBuffer(const char* preloadText); +int linenoiseHistoryAdd(const char* line); +int linenoiseHistorySetMaxLen(int len); +char* linenoiseHistoryLine(int index); +int linenoiseHistorySave(const char* filename); +int linenoiseHistoryLoad(const char* filename); +void linenoiseHistoryFree(void); +void linenoiseClearScreen(void); +void linenoiseSetMultiLine(int ml); +void linenoisePrintKeyCodes(void); +/* the following is extension to the original linenoise API */ +int linenoiseInstallWindowChangeHandler(void); #ifdef __cplusplus } diff --git a/3rdParty/linenoise-ng/src/linenoise.cpp b/3rdParty/linenoise-ng/src/linenoise.cpp index 70fabe4a2a..b2ab8670e0 100644 --- a/3rdParty/linenoise-ng/src/linenoise.cpp +++ b/3rdParty/linenoise-ng/src/linenoise.cpp @@ -130,13 +130,15 @@ using namespace linenoise_ng; typedef unsigned char char8_t; -static ConversionResult copyString8to32 (char32_t* dst, size_t dstSize, size_t& dstCount, const char* src) { +static ConversionResult copyString8to32(char32_t* dst, size_t dstSize, + size_t& dstCount, const char* src) { const UTF8* sourceStart = reinterpret_cast(src); const UTF8* sourceEnd = sourceStart + strlen(src); UTF32* targetStart = reinterpret_cast(dst); UTF32* targetEnd = targetStart + dstSize; - ConversionResult res = ConvertUTF8toUTF32(&sourceStart, sourceEnd, &targetStart, targetEnd, lenientConversion); + ConversionResult res = ConvertUTF8toUTF32( + &sourceStart, sourceEnd, &targetStart, targetEnd, lenientConversion); if (res == conversionOK) { dstCount = targetStart - reinterpret_cast(dst); @@ -144,18 +146,18 @@ static ConversionResult copyString8to32 (char32_t* dst, size_t dstSize, size_t& if (dstCount < dstSize) { *targetStart = 0; } - } else { - dstCount = 0; } return res; } -static ConversionResult copyString8to32 (char32_t* dst, size_t dstSize, size_t& dstCount, const char8_t* src) { - return copyString8to32(dst, dstSize, dstCount, reinterpret_cast(src)); +static ConversionResult copyString8to32(char32_t* dst, size_t dstSize, + size_t& dstCount, const char8_t* src) { + return copyString8to32(dst, dstSize, dstCount, + reinterpret_cast(src)); } -static size_t strlen32 (const char32_t* str) { +static size_t strlen32(const char32_t* str) { const char32_t* ptr = str; while (*ptr) { @@ -165,22 +167,54 @@ static size_t strlen32 (const char32_t* str) { return ptr - str; } -static size_t strlen8 (const char8_t* str) { +static size_t strlen8(const char8_t* str) { return strlen(reinterpret_cast(str)); } -static char8_t* strdup8 (const char* src) { +static char8_t* strdup8(const char* src) { return reinterpret_cast(strdup(src)); } #ifdef _WIN32 -static void copyString32to16(char16_t* dst, size_t dstSize, size_t* dstCount, const char32_t* src, size_t srcSize) { +static const int FOREGROUND_WHITE = + FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE; +static const int BACKGROUND_WHITE = + BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE; +static const int INTENSITY = FOREGROUND_INTENSITY | BACKGROUND_INTENSITY; + +class WinAttributes { + public: + WinAttributes() { + CONSOLE_SCREEN_BUFFER_INFO info; + GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &info); + _defaultAttribute = info.wAttributes & INTENSITY; + _defaultColor = info.wAttributes & FOREGROUND_WHITE; + _defaultBackground = info.wAttributes & BACKGROUND_WHITE; + + _consoleAttribute = _defaultAttribute; + _consoleColor = _defaultColor | _defaultBackground; + } + + public: + int _defaultAttribute; + int _defaultColor; + int _defaultBackground; + + int _consoleAttribute; + int _consoleColor; +}; + +static WinAttributes WIN_ATTR; + +static void copyString32to16(char16_t* dst, size_t dstSize, size_t* dstCount, + const char32_t* src, size_t srcSize) { const UTF32* sourceStart = reinterpret_cast(src); const UTF32* sourceEnd = sourceStart + srcSize; char16_t* targetStart = reinterpret_cast(dst); char16_t* targetEnd = targetStart + dstSize; - ConversionResult res = ConvertUTF32toUTF16(&sourceStart, sourceEnd, &targetStart, targetEnd, lenientConversion); + ConversionResult res = ConvertUTF32toUTF16( + &sourceStart, sourceEnd, &targetStart, targetEnd, lenientConversion); if (res == conversionOK) { *dstCount = targetStart - reinterpret_cast(dst); @@ -192,13 +226,15 @@ static void copyString32to16(char16_t* dst, size_t dstSize, size_t* dstCount, co } #endif -static void copyString32to8 (char* dst, size_t dstSize, size_t* dstCount, const char32_t* src, size_t srcSize) { +static void copyString32to8(char* dst, size_t dstSize, size_t* dstCount, + const char32_t* src, size_t srcSize) { const UTF32* sourceStart = reinterpret_cast(src); const UTF32* sourceEnd = sourceStart + srcSize; UTF8* targetStart = reinterpret_cast(dst); UTF8* targetEnd = targetStart + dstSize; - ConversionResult res = ConvertUTF32toUTF8(&sourceStart, sourceEnd, &targetStart, targetEnd, lenientConversion); + ConversionResult res = ConvertUTF32toUTF8( + &sourceStart, sourceEnd, &targetStart, targetEnd, lenientConversion); if (res == conversionOK) { *dstCount = targetStart - reinterpret_cast(dst); @@ -209,12 +245,12 @@ static void copyString32to8 (char* dst, size_t dstSize, size_t* dstCount, const } } -static void copyString32to8 (char* dst, size_t dstLen, const char32_t* src) { +static void copyString32to8(char* dst, size_t dstLen, const char32_t* src) { size_t dstCount = 0; - copyString32to8 (dst, dstLen, &dstCount, src, strlen32(src)); + copyString32to8(dst, dstLen, &dstCount, src, strlen32(src)); } -static void copyString32 (char32_t* dst, const char32_t* src, size_t len) { +static void copyString32(char32_t* dst, const char32_t* src, size_t len) { while (*src && 1 < len) { *dst++ = *src++; --len; @@ -223,10 +259,10 @@ static void copyString32 (char32_t* dst, const char32_t* src, size_t len) { *dst = 0; } -static int strncmp32 (const char32_t* left, const char32_t* right, size_t len) { +static int strncmp32(const char32_t* left, const char32_t* right, size_t len) { while (0 < len && *left) { if (*left != *right) { - return *left - * right; + return *left - *right; } ++left; @@ -237,20 +273,137 @@ static int strncmp32 (const char32_t* left, const char32_t* right, size_t len) { return 0; } -static int write32 (int fd, char32_t* text32, int len32) { #ifdef _WIN32 - if (_isatty(fd)) { +#include + +static size_t OutputWin(char16_t* text16, char32_t* text32, size_t len32) { + size_t count16 = 0; + + copyString32to16(text16, len32, &count16, text32, len32); + WriteConsoleW(GetStdHandle(STD_OUTPUT_HANDLE), text16, + static_cast(count16), nullptr, nullptr); + + return count16; +} + +static char32_t* HandleEsc(char32_t* p, char32_t* end) { + if (*p == '[') { + int code = 0; + + for (++p; p < end; ++p) { + char32_t c = *p; + + if ('0' <= c && c <= '9') { + code = code * 10 + (c - '0'); + } else if (c == 'm' || c == ';') { + switch (code) { + case 0: + WIN_ATTR._consoleAttribute = WIN_ATTR._defaultAttribute; + WIN_ATTR._consoleColor = + WIN_ATTR._defaultColor | WIN_ATTR._defaultBackground; + break; + + case 1: // BOLD + case 5: // BLINK + WIN_ATTR._consoleAttribute = + (WIN_ATTR._defaultAttribute ^ FOREGROUND_INTENSITY) & INTENSITY; + break; + + case 30: + WIN_ATTR._consoleColor = BACKGROUND_WHITE; + break; + + case 31: + WIN_ATTR._consoleColor = + FOREGROUND_RED | WIN_ATTR._defaultBackground; + break; + + case 32: + WIN_ATTR._consoleColor = + FOREGROUND_GREEN | WIN_ATTR._defaultBackground; + break; + + case 33: + WIN_ATTR._consoleColor = + FOREGROUND_RED | FOREGROUND_GREEN | WIN_ATTR._defaultBackground; + break; + + case 34: + WIN_ATTR._consoleColor = + FOREGROUND_BLUE | WIN_ATTR._defaultBackground; + break; + + case 35: + WIN_ATTR._consoleColor = + FOREGROUND_BLUE | FOREGROUND_RED | WIN_ATTR._defaultBackground; + break; + + case 36: + WIN_ATTR._consoleColor = FOREGROUND_BLUE | FOREGROUND_GREEN | + WIN_ATTR._defaultBackground; + break; + + case 37: + WIN_ATTR._consoleColor = FOREGROUND_GREEN | FOREGROUND_RED | + FOREGROUND_BLUE | + WIN_ATTR._defaultBackground; + break; + } + + code = 0; + } + + if (*p == 'm') { + ++p; + break; + } + } + } else { + ++p; + } + + auto handle = GetStdHandle(STD_OUTPUT_HANDLE); + SetConsoleTextAttribute(handle, + WIN_ATTR._consoleAttribute | WIN_ATTR._consoleColor); + + return p; +} + +static size_t WinWrite32(char16_t* text16, char32_t* text32, size_t len32) { + char32_t* p = text32; + char32_t* q = p; + char32_t* e = text32 + len32; + size_t count16 = 0; + + while (p < e) { + if (*p == 27) { + if (q < p) { + count16 += OutputWin(text16, q, p - q); + } + + q = p = HandleEsc(p + 1, e); + } else { + ++p; + } + } + + if (q < p) { + count16 += OutputWin(text16, q, p - q); + } + + return count16; +} +#endif + +static int write32(int fd, char32_t* text32, int len32) { +#ifdef _WIN32 + if (isatty(fd)) { size_t len16 = 2 * len32 + 1; unique_ptr text16(new char16_t[len16]); - size_t count16 = 0; + size_t count16 = WinWrite32(text16.get(), text32, len32); - copyString32to16(text16.get(), len16, &count16, text32, len32); - - WriteConsoleW(GetStdHandle(STD_OUTPUT_HANDLE), text16.get(), static_cast(count16), nullptr, nullptr); - return static_cast(count16); - } - else { + } else { size_t len8 = 4 * len32 + 1; unique_ptr text8(new char[len8]); size_t count8 = 0; @@ -271,120 +424,93 @@ static int write32 (int fd, char32_t* text32, int len32) { } class Utf32String { - public: - Utf32String () : _length(0) { - _data = new char32_t[1](); + public: + Utf32String() : _length(0) { _data = new char32_t[1](); } + + explicit Utf32String(const char* src) { + size_t len = strlen(src); + _data = new char32_t[len + 1]; + copyString8to32(_data, len + 1, _length, src); + } + + explicit Utf32String(const char8_t* src) { + size_t len = strlen(reinterpret_cast(src)); + _data = new char32_t[len + 1]; + copyString8to32(_data, len + 1, _length, src); + } + + explicit Utf32String(const char32_t* src) { + for (_length = 0; src[_length] != 0; ++_length) { } - explicit - Utf32String (const char* src) { - size_t len = strlen(src); - _data = new char32_t[len + 1]; - copyString8to32(_data, len + 1, _length, src); - } + _data = new char32_t[_length + 1](); + memcpy(_data, src, _length * sizeof(char32_t)); + } - explicit - Utf32String (const char8_t* src) { - size_t len = strlen(reinterpret_cast(src)); - _data = new char32_t[len + 1]; - copyString8to32(_data, len + 1, _length, src); - } + explicit Utf32String(const char32_t* src, int len) : _length(len) { + _data = new char32_t[len + 1](); + memcpy(_data, src, len * sizeof(char32_t)); + } - explicit - Utf32String (const char32_t* src) { - for (_length = 0; src[_length] != 0; ++_length) { - } + explicit Utf32String(int len) : _length(0) { _data = new char32_t[len](); } - _data = new char32_t[_length + 1](); - memcpy(_data, src, _length * sizeof(char32_t)); - } + explicit Utf32String(const Utf32String& that) : _length(that._length) { + _data = new char32_t[_length + 1](); + memcpy(_data, that._data, sizeof(char32_t) * _length); + } - explicit - Utf32String (const char32_t* src, int len) : _length(len) { - _data = new char32_t[len + 1](); - memcpy(_data, src, len * sizeof(char32_t)); - } - - explicit - Utf32String (int len) : _length(0) { - _data = new char32_t[len](); - } - - explicit - Utf32String (const Utf32String& that) : _length(that._length) { - _data = new char32_t[_length + 1](); + Utf32String& operator=(const Utf32String& that) { + if (this != &that) { + delete[] _data; + _data = new char32_t[that._length](); + _length = that._length; memcpy(_data, that._data, sizeof(char32_t) * _length); } - Utf32String& operator= (const Utf32String& that) { - if (this != &that) { - delete[] _data; - _data = new char32_t[that._length](); - _length = that._length; - memcpy(_data, that._data, sizeof(char32_t) * _length); - } + return *this; + } - return *this; + ~Utf32String() { delete[] _data; } + + public: + char32_t* get() const { return _data; } + + size_t length() const { return _length; } + + size_t chars() const { return _length; } + + void initFromBuffer() { + for (_length = 0; _data[_length] != 0; ++_length) { } + } - ~Utf32String () { - delete[] _data; - } + const char32_t& operator[](size_t pos) const { return _data[pos]; } - public: - char32_t* get () const { - return _data; - } + char32_t& operator[](size_t pos) { return _data[pos]; } - size_t length () const { - return _length; - } - - size_t chars () const { - return _length; - } - - void initFromBuffer () { - for (_length = 0; _data[_length] != 0; ++_length) { - } - } - - const char32_t& operator[] (size_t pos) const { - return _data[pos]; - } - - char32_t& operator[] (size_t pos) { - return _data[pos]; - } - - private: - size_t _length; - char32_t* _data; + private: + size_t _length; + char32_t* _data; }; class Utf8String { - Utf8String (const Utf8String&) = delete; - Utf8String& operator= (const Utf8String&) = delete; + Utf8String(const Utf8String&) = delete; + Utf8String& operator=(const Utf8String&) = delete; - public: - explicit - Utf8String (const Utf32String& src) { - size_t len = src.length() * 4 + 1; - _data = new char[len]; - copyString32to8(_data, len, src.get()); - } + public: + explicit Utf8String(const Utf32String& src) { + size_t len = src.length() * 4 + 1; + _data = new char[len]; + copyString32to8(_data, len, src.get()); + } - ~Utf8String () { - delete[] _data; - } + ~Utf8String() { delete[] _data; } - public: - char* get () const { - return _data; - } + public: + char* get() const { return _data; } - private: - char* _data; + private: + char* _data; }; struct linenoiseCompletions { @@ -404,17 +530,19 @@ struct linenoiseCompletions { * @param charCount number of characters in buffer */ namespace linenoise_ng { - int mk_wcwidth(char32_t ucs); +int mk_wcwidth(char32_t ucs); } -static void recomputeCharacterWidths(const char32_t* text, char* widths, int charCount) { - for (int i = 0; i < charCount; ++i) { - widths[i] = mk_wcwidth(text[i]); - } +static void recomputeCharacterWidths(const char32_t* text, char* widths, + int charCount) { + for (int i = 0; i < charCount; ++i) { + widths[i] = mk_wcwidth(text[i]); + } } /** - * Calculate a new screen position given a starting position, screen width and character count + * Calculate a new screen position given a starting position, screen width and + * character count * @param x initial x position (zero-based) * @param y initial y position (zero-based) * @param screenColumns screen column count @@ -422,24 +550,24 @@ static void recomputeCharacterWidths(const char32_t* text, char* widths, int cha * @param xOut returned x position (zero-based) * @param yOut returned y position (zero-based) */ -static void calculateScreenPosition( - int x, int y, int screenColumns, int charCount, int& xOut, int& yOut) { - xOut = x; +static void calculateScreenPosition(int x, int y, int screenColumns, + int charCount, int& xOut, int& yOut) { + xOut = x; + yOut = y; + int charsRemaining = charCount; + while (charsRemaining > 0) { + int charsThisRow = (x + charsRemaining < screenColumns) ? charsRemaining + : screenColumns - x; + xOut = x + charsThisRow; yOut = y; - int charsRemaining = charCount; - while (charsRemaining > 0) { - int charsThisRow = - (x + charsRemaining < screenColumns) ? charsRemaining : screenColumns - x; - xOut = x + charsThisRow; - yOut = y; - charsRemaining -= charsThisRow; - x = 0; - ++y; - } - if (xOut == screenColumns) { // we have to special-case line wrap - xOut = 0; - ++yOut; - } + charsRemaining -= charsThisRow; + x = 0; + ++y; + } + if (xOut == screenColumns) { // we have to special-case line wrap + xOut = 0; + ++yOut; + } } /** @@ -448,129 +576,122 @@ static void calculateScreenPosition( * @param len length of text to calculate */ namespace linenoise_ng { - int mk_wcswidth(const char32_t* pwcs, size_t n); +int mk_wcswidth(const char32_t* pwcs, size_t n); } static int calculateColumnPosition(char32_t* buf32, int len) { - int width = mk_wcswidth(reinterpret_cast(buf32), len); - if (width == -1) - return len; - else - return width; + int width = mk_wcswidth(reinterpret_cast(buf32), len); + if (width == -1) + return len; + else + return width; } static bool isControlChar(char32_t testChar) { - return (testChar < ' ') || // C0 controls - (testChar >= 0x7F && testChar <= 0x9F); // DEL and C1 controls + return (testChar < ' ') || // C0 controls + (testChar >= 0x7F && testChar <= 0x9F); // DEL and C1 controls } -struct PromptBase { // a convenience struct for grouping prompt info - Utf32String promptText; // our copy of the prompt text, edited - char* promptCharWidths; // character widths from mk_wcwidth() - int promptChars; // chars in promptText - int promptBytes; // bytes in promptText - int promptExtraLines; // extra lines (beyond 1) occupied by prompt - int promptIndentation; // column offset to end of prompt - int promptLastLinePosition; // index into promptText where last line begins - int promptPreviousInputLen; // promptChars of previous input line, for clearing - int promptCursorRowOffset; // where the cursor is relative to the start of the prompt - int promptScreenColumns; // width of screen in columns - int promptPreviousLen; // help erasing - int promptErrorCode; // error code (invalid UTF-8) or zero +struct PromptBase { // a convenience struct for grouping prompt info + Utf32String promptText; // our copy of the prompt text, edited + char* promptCharWidths; // character widths from mk_wcwidth() + int promptChars; // chars in promptText + int promptBytes; // bytes in promptText + int promptExtraLines; // extra lines (beyond 1) occupied by prompt + int promptIndentation; // column offset to end of prompt + int promptLastLinePosition; // index into promptText where last line begins + int promptPreviousInputLen; // promptChars of previous input line, for + // clearing + int promptCursorRowOffset; // where the cursor is relative to the start of + // the prompt + int promptScreenColumns; // width of screen in columns + int promptPreviousLen; // help erasing + int promptErrorCode; // error code (invalid UTF-8) or zero - PromptBase() : promptPreviousInputLen(0) {} + PromptBase() : promptPreviousInputLen(0) {} - bool write() { - if (write32(1, promptText.get(), promptBytes) == -1) - return false; + bool write() { + if (write32(1, promptText.get(), promptBytes) == -1) return false; - return true; - } + return true; + } }; struct PromptInfo : public PromptBase { - PromptInfo(const char* textPtr, int columns) { - promptExtraLines = 0; - promptLastLinePosition = 0; - promptPreviousLen = 0; - promptScreenColumns = columns; - Utf32String tempUnicode(textPtr); + PromptInfo(const char* textPtr, int columns) { + promptExtraLines = 0; + promptLastLinePosition = 0; + promptPreviousLen = 0; + promptScreenColumns = columns; + Utf32String tempUnicode(textPtr); - // strip control characters from the prompt -- we do allow newline - char32_t* pIn = tempUnicode.get(); - char32_t* pOut = pIn; + // strip control characters from the prompt -- we do allow newline + char32_t* pIn = tempUnicode.get(); + char32_t* pOut = pIn; - int len = 0; - int x = 0; + int len = 0; + int x = 0; -#ifdef _WIN32 - bool const strip = true; -#else - bool const strip = (isatty(1) != 1); -#endif + bool const strip = (isatty(1) == 0); - while (*pIn) { - char32_t c = *pIn; - if ('\n' == c || !isControlChar(c)) { - *pOut = c; - ++pOut; - ++pIn; - ++len; - if ('\n' == c || ++x >= promptScreenColumns) { - x = 0; - ++promptExtraLines; - promptLastLinePosition = len; - } - } - else if (c == '\x1b') { - if (strip) { - // jump over control chars - ++pIn; - if (*pIn == '[') { - ++pIn; - while (*pIn && ((*pIn == ';') || ((*pIn >= '0' && *pIn <= '9')))) { - ++pIn; - } - if (*pIn == 'm') { - ++pIn; - } - } - } - else { - // copy control chars - *pOut = *pIn; - ++pOut; - ++pIn; - if (*pIn == '[') { - *pOut = *pIn; - ++pOut; - ++pIn; - while (*pIn && ((*pIn == ';') || ((*pIn >= '0' && *pIn <= '9')))) { - *pOut = *pIn; - ++pOut; - ++pIn; - } - if (*pIn == 'm') { - *pOut = *pIn; - ++pOut; - ++pIn; - } - } - } - } - else { - ++pIn; - } + while (*pIn) { + char32_t c = *pIn; + if ('\n' == c || !isControlChar(c)) { + *pOut = c; + ++pOut; + ++pIn; + ++len; + if ('\n' == c || ++x >= promptScreenColumns) { + x = 0; + ++promptExtraLines; + promptLastLinePosition = len; } - *pOut = 0; - promptChars = len; - promptBytes = static_cast(pOut - tempUnicode.get()); - promptText = tempUnicode; - - promptIndentation = len - promptLastLinePosition; - promptCursorRowOffset = promptExtraLines; + } else if (c == '\x1b') { + if (strip) { + // jump over control chars + ++pIn; + if (*pIn == '[') { + ++pIn; + while (*pIn && ((*pIn == ';') || ((*pIn >= '0' && *pIn <= '9')))) { + ++pIn; + } + if (*pIn == 'm') { + ++pIn; + } + } + } else { + // copy control chars + *pOut = *pIn; + ++pOut; + ++pIn; + if (*pIn == '[') { + *pOut = *pIn; + ++pOut; + ++pIn; + while (*pIn && ((*pIn == ';') || ((*pIn >= '0' && *pIn <= '9')))) { + *pOut = *pIn; + ++pOut; + ++pIn; + } + if (*pIn == 'm') { + *pOut = *pIn; + ++pOut; + ++pIn; + } + } + } + } else { + ++pIn; + } } + *pOut = 0; + promptChars = len; + promptBytes = static_cast(pOut - tempUnicode.get()); + promptText = tempUnicode; + promptIndentation = len - promptLastLinePosition; + promptCursorRowOffset = promptExtraLines; + } }; // Used with DynamicPrompt (history search) @@ -578,164 +699,170 @@ struct PromptInfo : public PromptBase { static const Utf32String forwardSearchBasePrompt("(i-search)`"); static const Utf32String reverseSearchBasePrompt("(reverse-i-search)`"); static const Utf32String endSearchBasePrompt("': "); -static Utf32String previousSearchText; // remembered across invocations of linenoise() +static Utf32String + previousSearchText; // remembered across invocations of linenoise() // changing prompt for "(reverse-i-search)`text':" etc. // struct DynamicPrompt : public PromptBase { - Utf32String searchText; // text we are searching for - char* searchCharWidths; // character widths from mk_wcwidth() - int searchTextLen; // chars in searchText - int direction; // current search direction, 1=forward, -1=reverse + Utf32String searchText; // text we are searching for + char* searchCharWidths; // character widths from mk_wcwidth() + int searchTextLen; // chars in searchText + int direction; // current search direction, 1=forward, -1=reverse - DynamicPrompt(PromptBase& pi, int initialDirection) - : searchTextLen(0), direction(initialDirection) { - promptScreenColumns = pi.promptScreenColumns; - promptCursorRowOffset = 0; - Utf32String emptyString(1); - searchText = emptyString; - const Utf32String* basePrompt = - (direction > 0) ? &forwardSearchBasePrompt : &reverseSearchBasePrompt; - size_t promptStartLength = basePrompt->length(); - promptChars = static_cast(promptStartLength + endSearchBasePrompt.length()); - promptBytes = promptChars; - promptLastLinePosition = - promptChars; // TODO fix this, we are asssuming that the history prompt won't wrap (!) - promptPreviousLen = promptChars; - Utf32String tempUnicode(promptChars + 1); - memcpy(tempUnicode.get(), basePrompt->get(), sizeof(char32_t) * promptStartLength); - memcpy(&tempUnicode[promptStartLength], - endSearchBasePrompt.get(), - sizeof(char32_t) * (endSearchBasePrompt.length() + 1)); - tempUnicode.initFromBuffer(); - promptText = tempUnicode; - calculateScreenPosition( - 0, 0, pi.promptScreenColumns, promptChars, promptIndentation, promptExtraLines); - } + DynamicPrompt(PromptBase& pi, int initialDirection) + : searchTextLen(0), direction(initialDirection) { + promptScreenColumns = pi.promptScreenColumns; + promptCursorRowOffset = 0; + Utf32String emptyString(1); + searchText = emptyString; + const Utf32String* basePrompt = + (direction > 0) ? &forwardSearchBasePrompt : &reverseSearchBasePrompt; + size_t promptStartLength = basePrompt->length(); + promptChars = + static_cast(promptStartLength + endSearchBasePrompt.length()); + promptBytes = promptChars; + promptLastLinePosition = promptChars; // TODO fix this, we are asssuming + // that the history prompt won't wrap + // (!) + promptPreviousLen = promptChars; + Utf32String tempUnicode(promptChars + 1); + memcpy(tempUnicode.get(), basePrompt->get(), + sizeof(char32_t) * promptStartLength); + memcpy(&tempUnicode[promptStartLength], endSearchBasePrompt.get(), + sizeof(char32_t) * (endSearchBasePrompt.length() + 1)); + tempUnicode.initFromBuffer(); + promptText = tempUnicode; + calculateScreenPosition(0, 0, pi.promptScreenColumns, promptChars, + promptIndentation, promptExtraLines); + } - void updateSearchPrompt(void) { - const Utf32String* basePrompt = - (direction > 0) ? &forwardSearchBasePrompt : &reverseSearchBasePrompt; - size_t promptStartLength = basePrompt->length(); - promptChars = static_cast(promptStartLength + searchTextLen + endSearchBasePrompt.length()); - promptBytes = promptChars; - Utf32String tempUnicode(promptChars + 1); - memcpy(tempUnicode.get(), basePrompt->get(), sizeof(char32_t) * promptStartLength); - memcpy(&tempUnicode[promptStartLength], searchText.get(), sizeof(char32_t) * searchTextLen); - size_t endIndex = promptStartLength + searchTextLen; - memcpy(&tempUnicode[endIndex], - endSearchBasePrompt.get(), - sizeof(char32_t) * (endSearchBasePrompt.length() + 1)); - tempUnicode.initFromBuffer(); - promptText = tempUnicode; - } + void updateSearchPrompt(void) { + const Utf32String* basePrompt = + (direction > 0) ? &forwardSearchBasePrompt : &reverseSearchBasePrompt; + size_t promptStartLength = basePrompt->length(); + promptChars = static_cast(promptStartLength + searchTextLen + + endSearchBasePrompt.length()); + promptBytes = promptChars; + Utf32String tempUnicode(promptChars + 1); + memcpy(tempUnicode.get(), basePrompt->get(), + sizeof(char32_t) * promptStartLength); + memcpy(&tempUnicode[promptStartLength], searchText.get(), + sizeof(char32_t) * searchTextLen); + size_t endIndex = promptStartLength + searchTextLen; + memcpy(&tempUnicode[endIndex], endSearchBasePrompt.get(), + sizeof(char32_t) * (endSearchBasePrompt.length() + 1)); + tempUnicode.initFromBuffer(); + promptText = tempUnicode; + } - void updateSearchText(const char32_t* textPtr) { - Utf32String tempUnicode(textPtr); - searchTextLen = static_cast(tempUnicode.chars()); - searchText = tempUnicode; - updateSearchPrompt(); - } + void updateSearchText(const char32_t* textPtr) { + Utf32String tempUnicode(textPtr); + searchTextLen = static_cast(tempUnicode.chars()); + searchText = tempUnicode; + updateSearchPrompt(); + } }; class KillRing { - static const int capacity = 10; - int size; - int index; - char indexToSlot[10]; - vector theRing; + static const int capacity = 10; + int size; + int index; + char indexToSlot[10]; + vector theRing; -public: - enum action { actionOther, actionKill, actionYank }; - action lastAction; - size_t lastYankSize; + public: + enum action { actionOther, actionKill, actionYank }; + action lastAction; + size_t lastYankSize; - KillRing() : size(0), index(0), lastAction(actionOther) { - theRing.reserve(capacity); + KillRing() : size(0), index(0), lastAction(actionOther) { + theRing.reserve(capacity); + } + + void kill(const char32_t* text, int textLen, bool forward) { + if (textLen == 0) { + return; } - - void kill(const char32_t* text, int textLen, bool forward) { - if (textLen == 0) { - return; - } - Utf32String killedText(text, textLen); - if (lastAction == actionKill && size > 0) { - int slot = indexToSlot[0]; - int currentLen = static_cast(theRing[slot].length()); - int resultLen = currentLen + textLen; - Utf32String temp(resultLen + 1); - if (forward) { - memcpy(temp.get(), theRing[slot].get(), currentLen * sizeof(char32_t)); - memcpy(&temp[currentLen], killedText.get(), textLen * sizeof(char32_t)); - } else { - memcpy(temp.get(), killedText.get(), textLen * sizeof(char32_t)); - memcpy(&temp[textLen], theRing[slot].get(), currentLen * sizeof(char32_t)); - } - temp[resultLen] = 0; - temp.initFromBuffer(); - theRing[slot] = temp; - } else { - if (size < capacity) { - if (size > 0) { - memmove(&indexToSlot[1], &indexToSlot[0], size); - } - indexToSlot[0] = size; - size++; - theRing.push_back(killedText); - } else { - int slot = indexToSlot[capacity - 1]; - theRing[slot] = killedText; - memmove(&indexToSlot[1], &indexToSlot[0], capacity - 1); - indexToSlot[0] = slot; - } - index = 0; + Utf32String killedText(text, textLen); + if (lastAction == actionKill && size > 0) { + int slot = indexToSlot[0]; + int currentLen = static_cast(theRing[slot].length()); + int resultLen = currentLen + textLen; + Utf32String temp(resultLen + 1); + if (forward) { + memcpy(temp.get(), theRing[slot].get(), currentLen * sizeof(char32_t)); + memcpy(&temp[currentLen], killedText.get(), textLen * sizeof(char32_t)); + } else { + memcpy(temp.get(), killedText.get(), textLen * sizeof(char32_t)); + memcpy(&temp[textLen], theRing[slot].get(), + currentLen * sizeof(char32_t)); + } + temp[resultLen] = 0; + temp.initFromBuffer(); + theRing[slot] = temp; + } else { + if (size < capacity) { + if (size > 0) { + memmove(&indexToSlot[1], &indexToSlot[0], size); } + indexToSlot[0] = size; + size++; + theRing.push_back(killedText); + } else { + int slot = indexToSlot[capacity - 1]; + theRing[slot] = killedText; + memmove(&indexToSlot[1], &indexToSlot[0], capacity - 1); + indexToSlot[0] = slot; + } + index = 0; } + } - Utf32String* yank() { - return (size > 0) ? &theRing[indexToSlot[index]] : 0; - } + Utf32String* yank() { return (size > 0) ? &theRing[indexToSlot[index]] : 0; } - Utf32String* yankPop() { - if (size == 0) { - return 0; - } - ++index; - if (index == size) { - index = 0; - } - return &theRing[indexToSlot[index]]; + Utf32String* yankPop() { + if (size == 0) { + return 0; } + ++index; + if (index == size) { + index = 0; + } + return &theRing[indexToSlot[index]]; + } }; class InputBuffer { - char32_t* buf32; // input buffer - char* charWidths; // character widths from mk_wcwidth() - int buflen; // buffer size in characters - int len; // length of text in input buffer - int pos; // character position in buffer ( 0 <= pos <= len ) + char32_t* buf32; // input buffer + char* charWidths; // character widths from mk_wcwidth() + int buflen; // buffer size in characters + int len; // length of text in input buffer + int pos; // character position in buffer ( 0 <= pos <= len ) - void clearScreen(PromptBase& pi); - int incrementalHistorySearch(PromptBase& pi, int startChar); - int completeLine(PromptBase& pi); - void refreshLine(PromptBase& pi); + void clearScreen(PromptBase& pi); + int incrementalHistorySearch(PromptBase& pi, int startChar); + int completeLine(PromptBase& pi); + void refreshLine(PromptBase& pi); -public: - InputBuffer(char32_t* buffer, char* widthArray, int bufferLen) - : buf32(buffer), charWidths(widthArray), buflen(bufferLen - 1), len(0), pos(0) { - buf32[0] = 0; - } - void preloadBuffer(const char* preloadText) { - size_t ucharCount = 0; - copyString8to32(buf32, buflen + 1, ucharCount, preloadText); - recomputeCharacterWidths(buf32, charWidths, static_cast(ucharCount)); - len = static_cast(ucharCount); - pos = static_cast(ucharCount); - } - int getInputLine(PromptBase& pi); - int length(void) const { - return len; - } + public: + InputBuffer(char32_t* buffer, char* widthArray, int bufferLen) + : buf32(buffer), + charWidths(widthArray), + buflen(bufferLen - 1), + len(0), + pos(0) { + buf32[0] = 0; + } + void preloadBuffer(const char* preloadText) { + size_t ucharCount = 0; + copyString8to32(buf32, buflen + 1, ucharCount, preloadText); + recomputeCharacterWidths(buf32, charWidths, static_cast(ucharCount)); + len = static_cast(ucharCount); + pos = static_cast(ucharCount); + } + int getInputLine(PromptBase& pi); + int length(void) const { return len; } }; // Special codes for keyboard input: @@ -746,24 +873,34 @@ public: // pseudocode, trying to stay out of the way of UTF-8 and international // characters. Here's the general plan. // -// "User input keystrokes" (key chords, whatever) will be encoded as a single value. -// The low 21 bits are reserved for Unicode characters. Popular function-type keys -// get their own codes in the range 0x10200000 to (if needed) 0x1FE00000, currently +// "User input keystrokes" (key chords, whatever) will be encoded as a single +// value. +// The low 21 bits are reserved for Unicode characters. Popular function-type +// keys +// get their own codes in the range 0x10200000 to (if needed) 0x1FE00000, +// currently // just arrow keys, Home, End and Delete. Keypresses with Ctrl get ORed with // 0x20000000, with Alt get ORed with 0x40000000. So, Ctrl+Alt+Home is encoded -// as 0x20000000 + 0x40000000 + 0x10A00000 == 0x70A00000. To keep things complicated, -// the Alt key is equivalent to prefixing the keystroke with ESC, so ESC followed by -// D is treated the same as Alt + D ... we'll just use Emacs terminology and call -// this "Meta". So, we will encode both ESC followed by D and Alt held down while D +// as 0x20000000 + 0x40000000 + 0x10A00000 == 0x70A00000. To keep things +// complicated, +// the Alt key is equivalent to prefixing the keystroke with ESC, so ESC +// followed by +// D is treated the same as Alt + D ... we'll just use Emacs terminology and +// call +// this "Meta". So, we will encode both ESC followed by D and Alt held down +// while D // is pressed the same, as Meta-D, encoded as 0x40000064. // // Here are the definitions of our component constants: // -// Maximum unsigned 32-bit value = 0xFFFFFFFF; // For reference, max 32-bit value -// Highest allocated Unicode char = 0x001FFFFF; // For reference, max Unicode value -static const int META = 0x40000000; // Meta key combination -static const int CTRL = 0x20000000; // Ctrl key combination -// static const int SPECIAL_KEY = 0x10000000; // Common bit for all special keys +// Maximum unsigned 32-bit value = 0xFFFFFFFF; // For reference, max 32-bit +// value +// Highest allocated Unicode char = 0x001FFFFF; // For reference, max +// Unicode value +static const int META = 0x40000000; // Meta key combination +static const int CTRL = 0x20000000; // Ctrl key combination +// static const int SPECIAL_KEY = 0x10000000; // Common bit for all special +// keys static const int UP_ARROW_KEY = 0x10200000; // Special keys static const int DOWN_ARROW_KEY = 0x10400000; static const int RIGHT_ARROW_KEY = 0x10600000; @@ -787,7 +924,7 @@ static struct termios orig_termios; /* in order to restore at exit */ static KillRing killRing; -static int rawmode = 0; /* for atexit() function to check if restore is needed*/ +static int rawmode = 0; /* for atexit() function to check if restore is needed*/ static int atexit_registered = 0; /* register atexit just 1 time */ static int historyMaxLen = LINENOISE_DEFAULT_HISTORY_MAX_LEN; static int historyLen = 0; @@ -795,7 +932,8 @@ static int historyIndex = 0; static char8_t** history = NULL; // used to emulate Windows command prompt on down-arrow after a recall -// we use -2 as our "not set" value because we add 1 to the previous index on down-arrow, +// we use -2 as our "not set" value because we add 1 to the previous index on +// down-arrow, // and zero is a valid index (so -1 is a valid "previous index") static int historyPreviousIndex = -2; static bool historyRecallMostRecent = false; @@ -803,725 +941,700 @@ static bool historyRecallMostRecent = false; static void linenoiseAtExit(void); static bool isUnsupportedTerm(void) { - char* term = getenv("TERM"); - if (term == NULL) - return false; - for (int j = 0; unsupported_term[j]; ++j) - if (!strcasecmp(term, unsupported_term[j])) { - return true; - } - return false; + char* term = getenv("TERM"); + if (term == NULL) return false; + for (int j = 0; unsupported_term[j]; ++j) + if (!strcasecmp(term, unsupported_term[j])) { + return true; + } + return false; } static void beep() { - fprintf(stderr, "\x7"); // ctrl-G == bell/beep - fflush(stderr); + fprintf(stderr, "\x7"); // ctrl-G == bell/beep + fflush(stderr); } void linenoiseHistoryFree(void) { - if (history) { - for (int j = 0; j < historyLen; ++j) - free(history[j]); - historyLen = 0; - free(history); - history = 0; - } + if (history) { + for (int j = 0; j < historyLen; ++j) free(history[j]); + historyLen = 0; + free(history); + history = 0; + } } static int enableRawMode(void) { #ifdef _WIN32 - if (!console_in) { - console_in = GetStdHandle(STD_INPUT_HANDLE); - console_out = GetStdHandle(STD_OUTPUT_HANDLE); + if (!console_in) { + console_in = GetStdHandle(STD_INPUT_HANDLE); + console_out = GetStdHandle(STD_OUTPUT_HANDLE); - GetConsoleMode(console_in, &oldMode); - SetConsoleMode(console_in, - oldMode & ~(ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT | ENABLE_PROCESSED_INPUT)); - } - return 0; + GetConsoleMode(console_in, &oldMode); + SetConsoleMode(console_in, oldMode & + ~(ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT | + ENABLE_PROCESSED_INPUT)); + } + return 0; #else - struct termios raw; + struct termios raw; - if (!isatty(STDIN_FILENO)) - goto fatal; - if (!atexit_registered) { - atexit(linenoiseAtExit); - atexit_registered = 1; - } - if (tcgetattr(0, &orig_termios) == -1) - goto fatal; + if (!isatty(STDIN_FILENO)) goto fatal; + if (!atexit_registered) { + atexit(linenoiseAtExit); + atexit_registered = 1; + } + if (tcgetattr(0, &orig_termios) == -1) goto fatal; - raw = orig_termios; /* modify the original mode */ - /* input modes: no break, no CR to NL, no parity check, no strip char, - * no start/stop output control. */ - raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON); - /* output modes - disable post processing */ - // this is wrong, we don't want raw output, it turns newlines into straight linefeeds - // raw.c_oflag &= ~(OPOST); - /* control modes - set 8 bit chars */ - raw.c_cflag |= (CS8); - /* local modes - echoing off, canonical off, no extended functions, - * no signal chars (^Z,^C) */ - raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG); - /* control chars - set return condition: min number of bytes and timer. - * We want read to return every single byte, without timeout. */ - raw.c_cc[VMIN] = 1; - raw.c_cc[VTIME] = 0; /* 1 byte, no timer */ + raw = orig_termios; /* modify the original mode */ + /* input modes: no break, no CR to NL, no parity check, no strip char, + * no start/stop output control. */ + raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON); + /* output modes - disable post processing */ + // this is wrong, we don't want raw output, it turns newlines into straight + // linefeeds + // raw.c_oflag &= ~(OPOST); + /* control modes - set 8 bit chars */ + raw.c_cflag |= (CS8); + /* local modes - echoing off, canonical off, no extended functions, + * no signal chars (^Z,^C) */ + raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG); + /* control chars - set return condition: min number of bytes and timer. + * We want read to return every single byte, without timeout. */ + raw.c_cc[VMIN] = 1; + raw.c_cc[VTIME] = 0; /* 1 byte, no timer */ - /* put terminal in raw mode after flushing */ - if (tcsetattr(0, TCSADRAIN, &raw) < 0) - goto fatal; - rawmode = 1; - return 0; + /* put terminal in raw mode after flushing */ + if (tcsetattr(0, TCSADRAIN, &raw) < 0) goto fatal; + rawmode = 1; + return 0; fatal: - errno = ENOTTY; - return -1; + errno = ENOTTY; + return -1; #endif } static void disableRawMode(void) { #ifdef _WIN32 - SetConsoleMode(console_in, oldMode); - console_in = 0; - console_out = 0; + SetConsoleMode(console_in, oldMode); + console_in = 0; + console_out = 0; #else - if (rawmode && tcsetattr(0, TCSADRAIN, &orig_termios) != -1) - rawmode = 0; + if (rawmode && tcsetattr(0, TCSADRAIN, &orig_termios) != -1) rawmode = 0; #endif } // At exit we'll try to fix the terminal to the initial conditions -static void linenoiseAtExit(void) { - disableRawMode(); -} +static void linenoiseAtExit(void) { disableRawMode(); } static int getScreenColumns(void) { - int cols; + int cols; #ifdef _WIN32 - CONSOLE_SCREEN_BUFFER_INFO inf; - GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &inf); - cols = inf.dwSize.X; + CONSOLE_SCREEN_BUFFER_INFO inf; + GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &inf); + cols = inf.dwSize.X; #else - struct winsize ws; - cols = (ioctl(1, TIOCGWINSZ, &ws) == -1) ? 80 : ws.ws_col; + struct winsize ws; + cols = (ioctl(1, TIOCGWINSZ, &ws) == -1) ? 80 : ws.ws_col; #endif - // cols is 0 in certain circumstances like inside debugger, which creates further issues - return (cols > 0) ? cols : 80; + // cols is 0 in certain circumstances like inside debugger, which creates + // further issues + return (cols > 0) ? cols : 80; } static int getScreenRows(void) { - int rows; + int rows; #ifdef _WIN32 - CONSOLE_SCREEN_BUFFER_INFO inf; - GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &inf); - rows = 1 + inf.srWindow.Bottom - inf.srWindow.Top; + CONSOLE_SCREEN_BUFFER_INFO inf; + GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &inf); + rows = 1 + inf.srWindow.Bottom - inf.srWindow.Top; #else - struct winsize ws; - rows = (ioctl(1, TIOCGWINSZ, &ws) == -1) ? 24 : ws.ws_row; + struct winsize ws; + rows = (ioctl(1, TIOCGWINSZ, &ws) == -1) ? 24 : ws.ws_row; #endif - return (rows > 0) ? rows : 24; + return (rows > 0) ? rows : 24; } static void setDisplayAttribute(bool enhancedDisplay) { #ifdef _WIN32 - if (enhancedDisplay) { - CONSOLE_SCREEN_BUFFER_INFO inf; - GetConsoleScreenBufferInfo(console_out, &inf); - oldDisplayAttribute = inf.wAttributes; - BYTE oldLowByte = oldDisplayAttribute & 0xFF; - BYTE newLowByte; - switch (oldLowByte) { - case 0x07: - // newLowByte = FOREGROUND_BLUE | FOREGROUND_INTENSITY; // too dim - // newLowByte = FOREGROUND_BLUE; // even dimmer - newLowByte = - FOREGROUND_BLUE | FOREGROUND_GREEN; // most similar to xterm appearance - break; - case 0x70: - newLowByte = BACKGROUND_BLUE | BACKGROUND_INTENSITY; - break; - default: - newLowByte = oldLowByte ^ 0xFF; // default to inverse video - break; - } - inf.wAttributes = (inf.wAttributes & 0xFF00) | newLowByte; - SetConsoleTextAttribute(console_out, inf.wAttributes); - } else { - SetConsoleTextAttribute(console_out, oldDisplayAttribute); + if (enhancedDisplay) { + CONSOLE_SCREEN_BUFFER_INFO inf; + GetConsoleScreenBufferInfo(console_out, &inf); + oldDisplayAttribute = inf.wAttributes; + BYTE oldLowByte = oldDisplayAttribute & 0xFF; + BYTE newLowByte; + switch (oldLowByte) { + case 0x07: + // newLowByte = FOREGROUND_BLUE | FOREGROUND_INTENSITY; // too dim + // newLowByte = FOREGROUND_BLUE; // even dimmer + newLowByte = FOREGROUND_BLUE | + FOREGROUND_GREEN; // most similar to xterm appearance + break; + case 0x70: + newLowByte = BACKGROUND_BLUE | BACKGROUND_INTENSITY; + break; + default: + newLowByte = oldLowByte ^ 0xFF; // default to inverse video + break; } + inf.wAttributes = (inf.wAttributes & 0xFF00) | newLowByte; + SetConsoleTextAttribute(console_out, inf.wAttributes); + } else { + SetConsoleTextAttribute(console_out, oldDisplayAttribute); + } #else - if (enhancedDisplay) { - if (write(1, "\x1b[1;34m", 7) == -1) - return; /* bright blue (visible with both B&W bg) */ - } else { - if (write(1, "\x1b[0m", 4) == -1) - return; /* reset */ - } + if (enhancedDisplay) { + if (write(1, "\x1b[1;34m", 7) == -1) + return; /* bright blue (visible with both B&W bg) */ + } else { + if (write(1, "\x1b[0m", 4) == -1) return; /* reset */ + } #endif } /** - * Display the dynamic incremental search prompt and the current user input line. - * @param pi PromptBase struct holding information about the prompt and our screen position + * Display the dynamic incremental search prompt and the current user input + * line. + * @param pi PromptBase struct holding information about the prompt and our + * screen position * @param buf32 input buffer to be displayed * @param len count of characters in the buffer * @param pos current cursor position within the buffer (0 <= pos <= len) */ static void dynamicRefresh(PromptBase& pi, char32_t* buf32, int len, int pos) { - // calculate the position of the end of the prompt - int xEndOfPrompt, yEndOfPrompt; - calculateScreenPosition( - 0, 0, pi.promptScreenColumns, pi.promptChars, xEndOfPrompt, yEndOfPrompt); - pi.promptIndentation = xEndOfPrompt; + // calculate the position of the end of the prompt + int xEndOfPrompt, yEndOfPrompt; + calculateScreenPosition(0, 0, pi.promptScreenColumns, pi.promptChars, + xEndOfPrompt, yEndOfPrompt); + pi.promptIndentation = xEndOfPrompt; - // calculate the position of the end of the input line - int xEndOfInput, yEndOfInput; - calculateScreenPosition(xEndOfPrompt, - yEndOfPrompt, - pi.promptScreenColumns, - calculateColumnPosition(buf32, len), - xEndOfInput, - yEndOfInput); + // calculate the position of the end of the input line + int xEndOfInput, yEndOfInput; + calculateScreenPosition(xEndOfPrompt, yEndOfPrompt, pi.promptScreenColumns, + calculateColumnPosition(buf32, len), xEndOfInput, + yEndOfInput); - // calculate the desired position of the cursor - int xCursorPos, yCursorPos; - calculateScreenPosition(xEndOfPrompt, - yEndOfPrompt, - pi.promptScreenColumns, - calculateColumnPosition(buf32, pos), - xCursorPos, - yCursorPos); + // calculate the desired position of the cursor + int xCursorPos, yCursorPos; + calculateScreenPosition(xEndOfPrompt, yEndOfPrompt, pi.promptScreenColumns, + calculateColumnPosition(buf32, pos), xCursorPos, + yCursorPos); #ifdef _WIN32 - // position at the start of the prompt, clear to end of previous input - CONSOLE_SCREEN_BUFFER_INFO inf; - GetConsoleScreenBufferInfo(console_out, &inf); - inf.dwCursorPosition.X = 0; - inf.dwCursorPosition.Y -= pi.promptCursorRowOffset /*- pi.promptExtraLines*/; - SetConsoleCursorPosition(console_out, inf.dwCursorPosition); - DWORD count; - FillConsoleOutputCharacterA(console_out, - ' ', - pi.promptPreviousLen + pi.promptPreviousInputLen, - inf.dwCursorPosition, - &count); - pi.promptPreviousLen = pi.promptIndentation; - pi.promptPreviousInputLen = len; + // position at the start of the prompt, clear to end of previous input + CONSOLE_SCREEN_BUFFER_INFO inf; + GetConsoleScreenBufferInfo(console_out, &inf); + inf.dwCursorPosition.X = 0; + inf.dwCursorPosition.Y -= pi.promptCursorRowOffset /*- pi.promptExtraLines*/; + SetConsoleCursorPosition(console_out, inf.dwCursorPosition); + DWORD count; + FillConsoleOutputCharacterA(console_out, ' ', + pi.promptPreviousLen + pi.promptPreviousInputLen, + inf.dwCursorPosition, &count); + pi.promptPreviousLen = pi.promptIndentation; + pi.promptPreviousInputLen = len; - // display the prompt - if (! pi.write()) - return; + // display the prompt + if (!pi.write()) return; - // display the input line - if (write32(1, buf32, len) == -1) - return; + // display the input line + if (write32(1, buf32, len) == -1) return; - // position the cursor - GetConsoleScreenBufferInfo(console_out, &inf); - inf.dwCursorPosition.X = xCursorPos; // 0-based on Win32 - inf.dwCursorPosition.Y -= yEndOfInput - yCursorPos; - SetConsoleCursorPosition(console_out, inf.dwCursorPosition); + // position the cursor + GetConsoleScreenBufferInfo(console_out, &inf); + inf.dwCursorPosition.X = xCursorPos; // 0-based on Win32 + inf.dwCursorPosition.Y -= yEndOfInput - yCursorPos; + SetConsoleCursorPosition(console_out, inf.dwCursorPosition); #else // _WIN32 - char seq[64]; - int cursorRowMovement = pi.promptCursorRowOffset - pi.promptExtraLines; - if (cursorRowMovement > 0) { // move the cursor up as required - snprintf(seq, sizeof seq, "\x1b[%dA", cursorRowMovement); - if (write(1, seq, strlen(seq)) == -1) - return; - } - // position at the start of the prompt, clear to end of screen - snprintf(seq, sizeof seq, "\x1b[1G\x1b[J"); // 1-based on VT100 - if (write(1, seq, strlen(seq)) == -1) - return; + char seq[64]; + int cursorRowMovement = pi.promptCursorRowOffset - pi.promptExtraLines; + if (cursorRowMovement > 0) { // move the cursor up as required + snprintf(seq, sizeof seq, "\x1b[%dA", cursorRowMovement); + if (write(1, seq, strlen(seq)) == -1) return; + } + // position at the start of the prompt, clear to end of screen + snprintf(seq, sizeof seq, "\x1b[1G\x1b[J"); // 1-based on VT100 + if (write(1, seq, strlen(seq)) == -1) return; - // display the prompt - if (! pi.write()) - return; + // display the prompt + if (!pi.write()) return; - // display the input line - if (write32(1, buf32, len) == -1) - return; + // display the input line + if (write32(1, buf32, len) == -1) return; - // we have to generate our own newline on line wrap - if (xEndOfInput == 0 && yEndOfInput > 0) - if (write(1, "\n", 1) == -1) - return; + // we have to generate our own newline on line wrap + if (xEndOfInput == 0 && yEndOfInput > 0) + if (write(1, "\n", 1) == -1) return; - // position the cursor - cursorRowMovement = yEndOfInput - yCursorPos; - if (cursorRowMovement > 0) { // move the cursor up as required - snprintf(seq, sizeof seq, "\x1b[%dA", cursorRowMovement); - if (write(1, seq, strlen(seq)) == -1) - return; - } - // position the cursor within the line - snprintf(seq, sizeof seq, "\x1b[%dG", xCursorPos + 1); // 1-based on VT100 - if (write(1, seq, strlen(seq)) == -1) - return; + // position the cursor + cursorRowMovement = yEndOfInput - yCursorPos; + if (cursorRowMovement > 0) { // move the cursor up as required + snprintf(seq, sizeof seq, "\x1b[%dA", cursorRowMovement); + if (write(1, seq, strlen(seq)) == -1) return; + } + // position the cursor within the line + snprintf(seq, sizeof seq, "\x1b[%dG", xCursorPos + 1); // 1-based on VT100 + if (write(1, seq, strlen(seq)) == -1) return; #endif - pi.promptCursorRowOffset = pi.promptExtraLines + yCursorPos; // remember row for next pass + pi.promptCursorRowOffset = + pi.promptExtraLines + yCursorPos; // remember row for next pass } /** - * Refresh the user's input line: the prompt is already onscreen and is not redrawn here - * @param pi PromptBase struct holding information about the prompt and our screen position + * Refresh the user's input line: the prompt is already onscreen and is not + * redrawn here + * @param pi PromptBase struct holding information about the prompt and our + * screen position */ void InputBuffer::refreshLine(PromptBase& pi) { - // check for a matching brace/bracket/paren, remember its position if found - int highlight = -1; - if (pos < len) { - /* this scans for a brace matching buf32[pos] to highlight */ - int scanDirection = 0; - if (strchr("}])", buf32[pos])) - scanDirection = -1; /* backwards */ - else if (strchr("{[(", buf32[pos])) - scanDirection = 1; /* forwards */ + // check for a matching brace/bracket/paren, remember its position if found + int highlight = -1; + if (pos < len) { + /* this scans for a brace matching buf32[pos] to highlight */ + int scanDirection = 0; + if (strchr("}])", buf32[pos])) + scanDirection = -1; /* backwards */ + else if (strchr("{[(", buf32[pos])) + scanDirection = 1; /* forwards */ - if (scanDirection) { - int unmatched = scanDirection; - for (int i = pos + scanDirection; i >= 0 && i < len; i += scanDirection) { - /* TODO: the right thing when inside a string */ - if (strchr("}])", buf32[i])) - --unmatched; - else if (strchr("{[(", buf32[i])) - ++unmatched; + if (scanDirection) { + int unmatched = scanDirection; + for (int i = pos + scanDirection; i >= 0 && i < len; i += scanDirection) { + /* TODO: the right thing when inside a string */ + if (strchr("}])", buf32[i])) + --unmatched; + else if (strchr("{[(", buf32[i])) + ++unmatched; - if (unmatched == 0) { - highlight = i; - break; - } - } + if (unmatched == 0) { + highlight = i; + break; } + } } + } - // calculate the position of the end of the input line - int xEndOfInput, yEndOfInput; - calculateScreenPosition(pi.promptIndentation, - 0, - pi.promptScreenColumns, - calculateColumnPosition(buf32, len), - xEndOfInput, - yEndOfInput); + // calculate the position of the end of the input line + int xEndOfInput, yEndOfInput; + calculateScreenPosition(pi.promptIndentation, 0, pi.promptScreenColumns, + calculateColumnPosition(buf32, len), xEndOfInput, + yEndOfInput); - // calculate the desired position of the cursor - int xCursorPos, yCursorPos; - calculateScreenPosition(pi.promptIndentation, - 0, - pi.promptScreenColumns, - calculateColumnPosition(buf32, pos), - xCursorPos, - yCursorPos); + // calculate the desired position of the cursor + int xCursorPos, yCursorPos; + calculateScreenPosition(pi.promptIndentation, 0, pi.promptScreenColumns, + calculateColumnPosition(buf32, pos), xCursorPos, + yCursorPos); #ifdef _WIN32 - // position at the end of the prompt, clear to end of previous input - CONSOLE_SCREEN_BUFFER_INFO inf; - GetConsoleScreenBufferInfo(console_out, &inf); - inf.dwCursorPosition.X = pi.promptIndentation; // 0-based on Win32 - inf.dwCursorPosition.Y -= pi.promptCursorRowOffset - pi.promptExtraLines; - SetConsoleCursorPosition(console_out, inf.dwCursorPosition); - DWORD count; - if (len < pi.promptPreviousInputLen) - FillConsoleOutputCharacterA( - console_out, ' ', pi.promptPreviousInputLen, inf.dwCursorPosition, &count); - pi.promptPreviousInputLen = len; + // position at the end of the prompt, clear to end of previous input + CONSOLE_SCREEN_BUFFER_INFO inf; + GetConsoleScreenBufferInfo(console_out, &inf); + inf.dwCursorPosition.X = pi.promptIndentation; // 0-based on Win32 + inf.dwCursorPosition.Y -= pi.promptCursorRowOffset - pi.promptExtraLines; + SetConsoleCursorPosition(console_out, inf.dwCursorPosition); + DWORD count; + if (len < pi.promptPreviousInputLen) + FillConsoleOutputCharacterA(console_out, ' ', pi.promptPreviousInputLen, + inf.dwCursorPosition, &count); + pi.promptPreviousInputLen = len; - // display the input line - if (highlight == -1) { - if (write32(1, buf32, len) == -1) - return; - } else { - if (write32(1, buf32, highlight) == -1) - return; - setDisplayAttribute(true); /* bright blue (visible with both B&W bg) */ - if (write32(1, &buf32[highlight], 1) == -1) - return; - setDisplayAttribute(false); - if (write32(1, buf32 + highlight + 1, len - highlight - 1) == -1) - return; - } + // display the input line + if (highlight == -1) { + if (write32(1, buf32, len) == -1) return; + } else { + if (write32(1, buf32, highlight) == -1) return; + setDisplayAttribute(true); /* bright blue (visible with both B&W bg) */ + if (write32(1, &buf32[highlight], 1) == -1) return; + setDisplayAttribute(false); + if (write32(1, buf32 + highlight + 1, len - highlight - 1) == -1) return; + } - // position the cursor - GetConsoleScreenBufferInfo(console_out, &inf); - inf.dwCursorPosition.X = xCursorPos; // 0-based on Win32 - inf.dwCursorPosition.Y -= yEndOfInput - yCursorPos; - SetConsoleCursorPosition(console_out, inf.dwCursorPosition); + // position the cursor + GetConsoleScreenBufferInfo(console_out, &inf); + inf.dwCursorPosition.X = xCursorPos; // 0-based on Win32 + inf.dwCursorPosition.Y -= yEndOfInput - yCursorPos; + SetConsoleCursorPosition(console_out, inf.dwCursorPosition); #else // _WIN32 - char seq[64]; - int cursorRowMovement = pi.promptCursorRowOffset - pi.promptExtraLines; - if (cursorRowMovement > 0) { // move the cursor up as required - snprintf(seq, sizeof seq, "\x1b[%dA", cursorRowMovement); - if (write(1, seq, strlen(seq)) == -1) - return; - } - // position at the end of the prompt, clear to end of screen - snprintf(seq, sizeof seq, "\x1b[%dG\x1b[J", pi.promptIndentation + 1); // 1-based on VT100 - if (write(1, seq, strlen(seq)) == -1) - return; + char seq[64]; + int cursorRowMovement = pi.promptCursorRowOffset - pi.promptExtraLines; + if (cursorRowMovement > 0) { // move the cursor up as required + snprintf(seq, sizeof seq, "\x1b[%dA", cursorRowMovement); + if (write(1, seq, strlen(seq)) == -1) return; + } + // position at the end of the prompt, clear to end of screen + snprintf(seq, sizeof seq, "\x1b[%dG\x1b[J", + pi.promptIndentation + 1); // 1-based on VT100 + if (write(1, seq, strlen(seq)) == -1) return; - if (highlight == -1) { // write unhighlighted text - if (write32(1, buf32, len) == -1) - return; - } else { // highlight the matching brace/bracket/parenthesis - if (write32(1, buf32, highlight) == -1) - return; - setDisplayAttribute(true); - if (write32(1, &buf32[highlight], 1) == -1) - return; - setDisplayAttribute(false); - if (write32(1, buf32 + highlight + 1, len - highlight - 1) == -1) - return; - } + if (highlight == -1) { // write unhighlighted text + if (write32(1, buf32, len) == -1) return; + } else { // highlight the matching brace/bracket/parenthesis + if (write32(1, buf32, highlight) == -1) return; + setDisplayAttribute(true); + if (write32(1, &buf32[highlight], 1) == -1) return; + setDisplayAttribute(false); + if (write32(1, buf32 + highlight + 1, len - highlight - 1) == -1) return; + } - // we have to generate our own newline on line wrap - if (xEndOfInput == 0 && yEndOfInput > 0) - if (write(1, "\n", 1) == -1) - return; + // we have to generate our own newline on line wrap + if (xEndOfInput == 0 && yEndOfInput > 0) + if (write(1, "\n", 1) == -1) return; - // position the cursor - cursorRowMovement = yEndOfInput - yCursorPos; - if (cursorRowMovement > 0) { // move the cursor up as required - snprintf(seq, sizeof seq, "\x1b[%dA", cursorRowMovement); - if (write(1, seq, strlen(seq)) == -1) - return; - } - // position the cursor within the line - snprintf(seq, sizeof seq, "\x1b[%dG", xCursorPos + 1); // 1-based on VT100 - if (write(1, seq, strlen(seq)) == -1) - return; + // position the cursor + cursorRowMovement = yEndOfInput - yCursorPos; + if (cursorRowMovement > 0) { // move the cursor up as required + snprintf(seq, sizeof seq, "\x1b[%dA", cursorRowMovement); + if (write(1, seq, strlen(seq)) == -1) return; + } + // position the cursor within the line + snprintf(seq, sizeof seq, "\x1b[%dG", xCursorPos + 1); // 1-based on VT100 + if (write(1, seq, strlen(seq)) == -1) return; #endif - pi.promptCursorRowOffset = pi.promptExtraLines + yCursorPos; // remember row for next pass + pi.promptCursorRowOffset = + pi.promptExtraLines + yCursorPos; // remember row for next pass } #ifndef _WIN32 /** - * Read a UTF-8 sequence from the non-Windows keyboard and return the Unicode (char32_t) character it + * Read a UTF-8 sequence from the non-Windows keyboard and return the Unicode + * (char32_t) character it * encodes * * @return char32_t Unicode character */ static char32_t readUnicodeCharacter(void) { - static char8_t utf8String[5]; - static size_t utf8Count = 0; - while (true) { - char8_t c; - if (read(0, &c, 1) <= 0) - return 0; - if (c <= 0x7F) { // short circuit ASCII - utf8Count = 0; - return c; - } else if (utf8Count < sizeof(utf8String) - 1) { - utf8String[utf8Count++] = c; - utf8String[utf8Count] = 0; - char32_t unicodeChar[2]; - size_t ucharCount; - ConversionResult res = copyString8to32(unicodeChar, 2, ucharCount, utf8String); - if (res == conversionOK && ucharCount) { - utf8Count = 0; - return unicodeChar[0]; - } - } else { - utf8Count = 0; // this shouldn't happen: got four bytes but no UTF-8 character - } + static char8_t utf8String[5]; + static size_t utf8Count = 0; + while (true) { + char8_t c; + + /* Continue reading if interrupted by signal. */ + ssize_t nread; + do { + nread = read(0, &c, 1); + } while ((nread == -1) && (errno == EINTR)); + + if (nread <= 0) return 0; + if (c <= 0x7F) { // short circuit ASCII + utf8Count = 0; + return c; + } else if (utf8Count < sizeof(utf8String) - 1) { + utf8String[utf8Count++] = c; + utf8String[utf8Count] = 0; + char32_t unicodeChar[2]; + size_t ucharCount; + ConversionResult res = + copyString8to32(unicodeChar, 2, ucharCount, utf8String); + if (res == conversionOK && ucharCount) { + utf8Count = 0; + return unicodeChar[0]; + } + } else { + utf8Count = + 0; // this shouldn't happen: got four bytes but no UTF-8 character } + } } namespace EscapeSequenceProcessing { // move these out of global namespace -// This chunk of code does parsing of the escape sequences sent by various Linux terminals. +// This chunk of code does parsing of the escape sequences sent by various Linux +// terminals. // -// It handles arrow keys, Home, End and Delete keys by interpreting the sequences sent by -// gnome terminal, xterm, rxvt, konsole, aterm and yakuake including the Alt and Ctrl key +// It handles arrow keys, Home, End and Delete keys by interpreting the +// sequences sent by +// gnome terminal, xterm, rxvt, konsole, aterm and yakuake including the Alt and +// Ctrl key // combinations that are understood by linenoise. // -// The parsing uses tables, a bunch of intermediate dispatch routines and a doDispatch -// loop that reads the tables and sends control to "deeper" routines to continue the -// parsing. The starting call to doDispatch( c, initialDispatch ) will eventually return -// either a character (with optional CTRL and META bits set), or -1 if parsing fails, or +// The parsing uses tables, a bunch of intermediate dispatch routines and a +// doDispatch +// loop that reads the tables and sends control to "deeper" routines to continue +// the +// parsing. The starting call to doDispatch( c, initialDispatch ) will +// eventually return +// either a character (with optional CTRL and META bits set), or -1 if parsing +// fails, or // zero if an attempt to read from the keyboard fails. // -// This is rather sloppy escape sequence processing, since we're not paying attention to what the -// actual TERM is set to and are processing all key sequences for all terminals, but it works with -// the most common keystrokes on the most common terminals. It's intricate, but the nested 'if' -// statements required to do it directly would be worse. This way has the advantage of allowing +// This is rather sloppy escape sequence processing, since we're not paying +// attention to what the +// actual TERM is set to and are processing all key sequences for all terminals, +// but it works with +// the most common keystrokes on the most common terminals. It's intricate, but +// the nested 'if' +// statements required to do it directly would be worse. This way has the +// advantage of allowing // changes and extensions without having to touch a lot of code. -// This is a typedef for the routine called by doDispatch(). It takes the current character -// as input, does any required processing including reading more characters and calling other -// dispatch routines, then eventually returns the final (possibly extended or special) character. +// This is a typedef for the routine called by doDispatch(). It takes the +// current character +// as input, does any required processing including reading more characters and +// calling other +// dispatch routines, then eventually returns the final (possibly extended or +// special) character. // typedef char32_t (*CharacterDispatchRoutine)(char32_t); -// This structure is used by doDispatch() to hold a list of characters to test for and -// a list of routines to call if the character matches. The dispatch routine list is one -// longer than the character list; the final entry is used if no character matches. +// This structure is used by doDispatch() to hold a list of characters to test +// for and +// a list of routines to call if the character matches. The dispatch routine +// list is one +// longer than the character list; the final entry is used if no character +// matches. // struct CharacterDispatch { - unsigned int len; // length of the chars list - const char* chars; // chars to test - CharacterDispatchRoutine* dispatch; // array of routines to call + unsigned int len; // length of the chars list + const char* chars; // chars to test + CharacterDispatchRoutine* dispatch; // array of routines to call }; -// This dispatch routine is given a dispatch table and then farms work out to routines -// listed in the table based on the character it is called with. The dispatch routines can -// read more input characters to decide what should eventually be returned. Eventually, -// a called routine returns either a character or -1 to indicate parsing failure. +// This dispatch routine is given a dispatch table and then farms work out to +// routines +// listed in the table based on the character it is called with. The dispatch +// routines can +// read more input characters to decide what should eventually be returned. +// Eventually, +// a called routine returns either a character or -1 to indicate parsing +// failure. // static char32_t doDispatch(char32_t c, CharacterDispatch& dispatchTable) { - for (unsigned int i = 0; i < dispatchTable.len; ++i) { - if (static_cast(dispatchTable.chars[i]) == c) { - return dispatchTable.dispatch[i](c); - } + for (unsigned int i = 0; i < dispatchTable.len; ++i) { + if (static_cast(dispatchTable.chars[i]) == c) { + return dispatchTable.dispatch[i](c); } - return dispatchTable.dispatch[dispatchTable.len](c); + } + return dispatchTable.dispatch[dispatchTable.len](c); } -static char32_t thisKeyMetaCtrl = 0; // holds pre-set Meta and/or Ctrl modifiers +static char32_t thisKeyMetaCtrl = + 0; // holds pre-set Meta and/or Ctrl modifiers // Final dispatch routines -- return something // -static char32_t normalKeyRoutine(char32_t c) { - return thisKeyMetaCtrl | c; -} +static char32_t normalKeyRoutine(char32_t c) { return thisKeyMetaCtrl | c; } static char32_t upArrowKeyRoutine(char32_t) { - return thisKeyMetaCtrl | UP_ARROW_KEY; + return thisKeyMetaCtrl | UP_ARROW_KEY; } static char32_t downArrowKeyRoutine(char32_t) { - return thisKeyMetaCtrl | DOWN_ARROW_KEY; + return thisKeyMetaCtrl | DOWN_ARROW_KEY; } static char32_t rightArrowKeyRoutine(char32_t) { - return thisKeyMetaCtrl | RIGHT_ARROW_KEY; + return thisKeyMetaCtrl | RIGHT_ARROW_KEY; } static char32_t leftArrowKeyRoutine(char32_t) { - return thisKeyMetaCtrl | LEFT_ARROW_KEY; -} -static char32_t homeKeyRoutine(char32_t) { - return thisKeyMetaCtrl | HOME_KEY; -} -static char32_t endKeyRoutine(char32_t) { - return thisKeyMetaCtrl | END_KEY; + return thisKeyMetaCtrl | LEFT_ARROW_KEY; } +static char32_t homeKeyRoutine(char32_t) { return thisKeyMetaCtrl | HOME_KEY; } +static char32_t endKeyRoutine(char32_t) { return thisKeyMetaCtrl | END_KEY; } static char32_t pageUpKeyRoutine(char32_t) { - return thisKeyMetaCtrl | PAGE_UP_KEY; + return thisKeyMetaCtrl | PAGE_UP_KEY; } static char32_t pageDownKeyRoutine(char32_t) { - return thisKeyMetaCtrl | PAGE_DOWN_KEY; + return thisKeyMetaCtrl | PAGE_DOWN_KEY; } static char32_t deleteCharRoutine(char32_t) { - return thisKeyMetaCtrl | ctrlChar('H'); + return thisKeyMetaCtrl | ctrlChar('H'); } // key labeled Backspace static char32_t deleteKeyRoutine(char32_t) { - return thisKeyMetaCtrl | DELETE_KEY; + return thisKeyMetaCtrl | DELETE_KEY; } // key labeled Delete static char32_t ctrlUpArrowKeyRoutine(char32_t) { - return thisKeyMetaCtrl | CTRL | UP_ARROW_KEY; + return thisKeyMetaCtrl | CTRL | UP_ARROW_KEY; } static char32_t ctrlDownArrowKeyRoutine(char32_t) { - return thisKeyMetaCtrl | CTRL | DOWN_ARROW_KEY; + return thisKeyMetaCtrl | CTRL | DOWN_ARROW_KEY; } static char32_t ctrlRightArrowKeyRoutine(char32_t) { - return thisKeyMetaCtrl | CTRL | RIGHT_ARROW_KEY; + return thisKeyMetaCtrl | CTRL | RIGHT_ARROW_KEY; } static char32_t ctrlLeftArrowKeyRoutine(char32_t) { - return thisKeyMetaCtrl | CTRL | LEFT_ARROW_KEY; + return thisKeyMetaCtrl | CTRL | LEFT_ARROW_KEY; } static char32_t escFailureRoutine(char32_t) { - beep(); - return -1; + beep(); + return -1; } // Handle ESC [ 1 ; 3 (or 5) escape sequences // -static CharacterDispatchRoutine escLeftBracket1Semicolon3or5Routines[] = {upArrowKeyRoutine, - downArrowKeyRoutine, - rightArrowKeyRoutine, - leftArrowKeyRoutine, - escFailureRoutine}; +static CharacterDispatchRoutine escLeftBracket1Semicolon3or5Routines[] = { + upArrowKeyRoutine, downArrowKeyRoutine, rightArrowKeyRoutine, + leftArrowKeyRoutine, escFailureRoutine}; static CharacterDispatch escLeftBracket1Semicolon3or5Dispatch = { 4, "ABCD", escLeftBracket1Semicolon3or5Routines}; // Handle ESC [ 1 ; escape sequences // static char32_t escLeftBracket1Semicolon3Routine(char32_t c) { - c = readUnicodeCharacter(); - if (c == 0) - return 0; - thisKeyMetaCtrl |= META; - return doDispatch(c, escLeftBracket1Semicolon3or5Dispatch); + c = readUnicodeCharacter(); + if (c == 0) return 0; + thisKeyMetaCtrl |= META; + return doDispatch(c, escLeftBracket1Semicolon3or5Dispatch); } static char32_t escLeftBracket1Semicolon5Routine(char32_t c) { - c = readUnicodeCharacter(); - if (c == 0) - return 0; - thisKeyMetaCtrl |= CTRL; - return doDispatch(c, escLeftBracket1Semicolon3or5Dispatch); + c = readUnicodeCharacter(); + if (c == 0) return 0; + thisKeyMetaCtrl |= CTRL; + return doDispatch(c, escLeftBracket1Semicolon3or5Dispatch); } static CharacterDispatchRoutine escLeftBracket1SemicolonRoutines[] = { - escLeftBracket1Semicolon3Routine, escLeftBracket1Semicolon5Routine, escFailureRoutine}; + escLeftBracket1Semicolon3Routine, escLeftBracket1Semicolon5Routine, + escFailureRoutine}; static CharacterDispatch escLeftBracket1SemicolonDispatch = { 2, "35", escLeftBracket1SemicolonRoutines}; // Handle ESC [ 1 escape sequences // static char32_t escLeftBracket1SemicolonRoutine(char32_t c) { - c = readUnicodeCharacter(); - if (c == 0) - return 0; - return doDispatch(c, escLeftBracket1SemicolonDispatch); + c = readUnicodeCharacter(); + if (c == 0) return 0; + return doDispatch(c, escLeftBracket1SemicolonDispatch); } static CharacterDispatchRoutine escLeftBracket1Routines[] = { homeKeyRoutine, escLeftBracket1SemicolonRoutine, escFailureRoutine}; -static CharacterDispatch escLeftBracket1Dispatch = {2, "~;", escLeftBracket1Routines}; +static CharacterDispatch escLeftBracket1Dispatch = {2, "~;", + escLeftBracket1Routines}; // Handle ESC [ 3 escape sequences // -static CharacterDispatchRoutine escLeftBracket3Routines[] = {deleteKeyRoutine, escFailureRoutine}; -static CharacterDispatch escLeftBracket3Dispatch = {1, "~", escLeftBracket3Routines}; +static CharacterDispatchRoutine escLeftBracket3Routines[] = {deleteKeyRoutine, + escFailureRoutine}; +static CharacterDispatch escLeftBracket3Dispatch = {1, "~", + escLeftBracket3Routines}; // Handle ESC [ 4 escape sequences // -static CharacterDispatchRoutine escLeftBracket4Routines[] = {endKeyRoutine, escFailureRoutine}; -static CharacterDispatch escLeftBracket4Dispatch = {1, "~", escLeftBracket4Routines}; +static CharacterDispatchRoutine escLeftBracket4Routines[] = {endKeyRoutine, + escFailureRoutine}; +static CharacterDispatch escLeftBracket4Dispatch = {1, "~", + escLeftBracket4Routines}; // Handle ESC [ 5 escape sequences // -static CharacterDispatchRoutine escLeftBracket5Routines[] = {pageUpKeyRoutine, escFailureRoutine}; -static CharacterDispatch escLeftBracket5Dispatch = {1, "~", escLeftBracket5Routines}; +static CharacterDispatchRoutine escLeftBracket5Routines[] = {pageUpKeyRoutine, + escFailureRoutine}; +static CharacterDispatch escLeftBracket5Dispatch = {1, "~", + escLeftBracket5Routines}; // Handle ESC [ 6 escape sequences // -static CharacterDispatchRoutine escLeftBracket6Routines[] = {pageDownKeyRoutine, escFailureRoutine}; -static CharacterDispatch escLeftBracket6Dispatch = {1, "~", escLeftBracket6Routines}; +static CharacterDispatchRoutine escLeftBracket6Routines[] = {pageDownKeyRoutine, + escFailureRoutine}; +static CharacterDispatch escLeftBracket6Dispatch = {1, "~", + escLeftBracket6Routines}; // Handle ESC [ 7 escape sequences // -static CharacterDispatchRoutine escLeftBracket7Routines[] = {homeKeyRoutine, escFailureRoutine}; -static CharacterDispatch escLeftBracket7Dispatch = {1, "~", escLeftBracket7Routines}; +static CharacterDispatchRoutine escLeftBracket7Routines[] = {homeKeyRoutine, + escFailureRoutine}; +static CharacterDispatch escLeftBracket7Dispatch = {1, "~", + escLeftBracket7Routines}; // Handle ESC [ 8 escape sequences // -static CharacterDispatchRoutine escLeftBracket8Routines[] = {endKeyRoutine, escFailureRoutine}; -static CharacterDispatch escLeftBracket8Dispatch = {1, "~", escLeftBracket8Routines}; +static CharacterDispatchRoutine escLeftBracket8Routines[] = {endKeyRoutine, + escFailureRoutine}; +static CharacterDispatch escLeftBracket8Dispatch = {1, "~", + escLeftBracket8Routines}; // Handle ESC [ escape sequences // static char32_t escLeftBracket0Routine(char32_t c) { - return escFailureRoutine(c); + return escFailureRoutine(c); } static char32_t escLeftBracket1Routine(char32_t c) { - c = readUnicodeCharacter(); - if (c == 0) - return 0; - return doDispatch(c, escLeftBracket1Dispatch); + c = readUnicodeCharacter(); + if (c == 0) return 0; + return doDispatch(c, escLeftBracket1Dispatch); } static char32_t escLeftBracket2Routine(char32_t c) { - return escFailureRoutine(c); // Insert key, unused + return escFailureRoutine(c); // Insert key, unused } static char32_t escLeftBracket3Routine(char32_t c) { - c = readUnicodeCharacter(); - if (c == 0) - return 0; - return doDispatch(c, escLeftBracket3Dispatch); + c = readUnicodeCharacter(); + if (c == 0) return 0; + return doDispatch(c, escLeftBracket3Dispatch); } static char32_t escLeftBracket4Routine(char32_t c) { - c = readUnicodeCharacter(); - if (c == 0) - return 0; - return doDispatch(c, escLeftBracket4Dispatch); + c = readUnicodeCharacter(); + if (c == 0) return 0; + return doDispatch(c, escLeftBracket4Dispatch); } static char32_t escLeftBracket5Routine(char32_t c) { - c = readUnicodeCharacter(); - if (c == 0) - return 0; - return doDispatch(c, escLeftBracket5Dispatch); + c = readUnicodeCharacter(); + if (c == 0) return 0; + return doDispatch(c, escLeftBracket5Dispatch); } static char32_t escLeftBracket6Routine(char32_t c) { - c = readUnicodeCharacter(); - if (c == 0) - return 0; - return doDispatch(c, escLeftBracket6Dispatch); + c = readUnicodeCharacter(); + if (c == 0) return 0; + return doDispatch(c, escLeftBracket6Dispatch); } static char32_t escLeftBracket7Routine(char32_t c) { - c = readUnicodeCharacter(); - if (c == 0) - return 0; - return doDispatch(c, escLeftBracket7Dispatch); + c = readUnicodeCharacter(); + if (c == 0) return 0; + return doDispatch(c, escLeftBracket7Dispatch); } static char32_t escLeftBracket8Routine(char32_t c) { - c = readUnicodeCharacter(); - if (c == 0) - return 0; - return doDispatch(c, escLeftBracket8Dispatch); + c = readUnicodeCharacter(); + if (c == 0) return 0; + return doDispatch(c, escLeftBracket8Dispatch); } static char32_t escLeftBracket9Routine(char32_t c) { - return escFailureRoutine(c); + return escFailureRoutine(c); } // Handle ESC [ escape sequences // -static CharacterDispatchRoutine escLeftBracketRoutines[] = {upArrowKeyRoutine, - downArrowKeyRoutine, - rightArrowKeyRoutine, - leftArrowKeyRoutine, - homeKeyRoutine, - endKeyRoutine, - escLeftBracket0Routine, - escLeftBracket1Routine, - escLeftBracket2Routine, - escLeftBracket3Routine, - escLeftBracket4Routine, - escLeftBracket5Routine, - escLeftBracket6Routine, - escLeftBracket7Routine, - escLeftBracket8Routine, - escLeftBracket9Routine, - escFailureRoutine}; -static CharacterDispatch escLeftBracketDispatch = {16, "ABCDHF0123456789", escLeftBracketRoutines}; +static CharacterDispatchRoutine escLeftBracketRoutines[] = { + upArrowKeyRoutine, downArrowKeyRoutine, rightArrowKeyRoutine, + leftArrowKeyRoutine, homeKeyRoutine, endKeyRoutine, + escLeftBracket0Routine, escLeftBracket1Routine, escLeftBracket2Routine, + escLeftBracket3Routine, escLeftBracket4Routine, escLeftBracket5Routine, + escLeftBracket6Routine, escLeftBracket7Routine, escLeftBracket8Routine, + escLeftBracket9Routine, escFailureRoutine}; +static CharacterDispatch escLeftBracketDispatch = {16, "ABCDHF0123456789", + escLeftBracketRoutines}; // Handle ESC O escape sequences // -static CharacterDispatchRoutine escORoutines[] = {upArrowKeyRoutine, - downArrowKeyRoutine, - rightArrowKeyRoutine, - leftArrowKeyRoutine, - homeKeyRoutine, - endKeyRoutine, - ctrlUpArrowKeyRoutine, - ctrlDownArrowKeyRoutine, - ctrlRightArrowKeyRoutine, - ctrlLeftArrowKeyRoutine, - escFailureRoutine}; +static CharacterDispatchRoutine escORoutines[] = { + upArrowKeyRoutine, downArrowKeyRoutine, rightArrowKeyRoutine, + leftArrowKeyRoutine, homeKeyRoutine, endKeyRoutine, + ctrlUpArrowKeyRoutine, ctrlDownArrowKeyRoutine, ctrlRightArrowKeyRoutine, + ctrlLeftArrowKeyRoutine, escFailureRoutine}; static CharacterDispatch escODispatch = {10, "ABCDHFabcd", escORoutines}; -// Initial ESC dispatch -- could be a Meta prefix or the start of an escape sequence +// Initial ESC dispatch -- could be a Meta prefix or the start of an escape +// sequence // static char32_t escLeftBracketRoutine(char32_t c) { - c = readUnicodeCharacter(); - if (c == 0) - return 0; - return doDispatch(c, escLeftBracketDispatch); + c = readUnicodeCharacter(); + if (c == 0) return 0; + return doDispatch(c, escLeftBracketDispatch); } static char32_t escORoutine(char32_t c) { - c = readUnicodeCharacter(); - if (c == 0) - return 0; - return doDispatch(c, escODispatch); + c = readUnicodeCharacter(); + if (c == 0) return 0; + return doDispatch(c, escODispatch); } static char32_t setMetaRoutine(char32_t c); // need forward reference -static CharacterDispatchRoutine escRoutines[] = { - escLeftBracketRoutine, escORoutine, setMetaRoutine}; +static CharacterDispatchRoutine escRoutines[] = {escLeftBracketRoutine, + escORoutine, setMetaRoutine}; static CharacterDispatch escDispatch = {2, "[O", escRoutines}; // Initial dispatch -- we are not in the middle of anything yet // static char32_t escRoutine(char32_t c) { - c = readUnicodeCharacter(); - if (c == 0) - return 0; - return doDispatch(c, escDispatch); + c = readUnicodeCharacter(); + if (c == 0) return 0; + return doDispatch(c, escDispatch); } static CharacterDispatchRoutine initialRoutines[] = { escRoutine, deleteCharRoutine, normalKeyRoutine}; @@ -1530,36 +1643,39 @@ static CharacterDispatch initialDispatch = {2, "\x1B\x7F", initialRoutines}; // Special handling for the ESC key because it does double duty // static char32_t setMetaRoutine(char32_t c) { - thisKeyMetaCtrl = META; - if (c == 0x1B) { // another ESC, stay in ESC processing mode - c = readUnicodeCharacter(); - if (c == 0) - return 0; - return doDispatch(c, escDispatch); - } - return doDispatch(c, initialDispatch); + thisKeyMetaCtrl = META; + if (c == 0x1B) { // another ESC, stay in ESC processing mode + c = readUnicodeCharacter(); + if (c == 0) return 0; + return doDispatch(c, escDispatch); + } + return doDispatch(c, initialDispatch); } } // namespace EscapeSequenceProcessing // move these out of global namespace #endif // #ifndef _WIN32 -// linenoiseReadChar -- read a keystroke or keychord from the keyboard, and translate it -// into an encoded "keystroke". When convenient, extended keys are translated into their +// linenoiseReadChar -- read a keystroke or keychord from the keyboard, and +// translate it +// into an encoded "keystroke". When convenient, extended keys are translated +// into their // simpler Emacs keystrokes, so an unmodified "left arrow" becomes Ctrl-B. // -// A return value of zero means "no input available", and a return value of -1 means "invalid key". +// A return value of zero means "no input available", and a return value of -1 +// means "invalid key". // static char32_t linenoiseReadChar(void) { #ifdef _WIN32 - INPUT_RECORD rec; - DWORD count; - int modifierKeys = 0; - bool escSeen = false; - while (true) { - ReadConsoleInputW(console_in, &rec, 1, &count); -#if 0 // helper for debugging keystrokes, display info in the debug "Output" window in the debugger + INPUT_RECORD rec; + DWORD count; + int modifierKeys = 0; + bool escSeen = false; + while (true) { + ReadConsoleInputW(console_in, &rec, 1, &count); +#if 0 // helper for debugging keystrokes, display info in the debug "Output" + // window in the debugger { if ( rec.EventType == KEY_EVENT ) { //if ( rec.Event.KeyEvent.uChar.UnicodeChar ) { @@ -1587,129 +1703,145 @@ static char32_t linenoiseReadChar(void) { } } #endif - if (rec.EventType != KEY_EVENT) { - continue; - } - // Windows provides for entry of characters that are not on your keyboard by sending the - // Unicode characters as a "key up" with virtual keycode 0x12 (VK_MENU == Alt key) ... - // accept these characters, otherwise only process characters on "key down" - if (!rec.Event.KeyEvent.bKeyDown && rec.Event.KeyEvent.wVirtualKeyCode != VK_MENU) { - continue; - } - modifierKeys = 0; - // AltGr is encoded as ( LEFT_CTRL_PRESSED | RIGHT_ALT_PRESSED ), so don't treat this - // combination as either CTRL or META we just turn off those two bits, so it is still - // possible to combine CTRL and/or META with an AltGr key by using right-Ctrl and/or - // left-Alt - if ((rec.Event.KeyEvent.dwControlKeyState & (LEFT_CTRL_PRESSED | RIGHT_ALT_PRESSED)) == - (LEFT_CTRL_PRESSED | RIGHT_ALT_PRESSED)) { - rec.Event.KeyEvent.dwControlKeyState &= ~(LEFT_CTRL_PRESSED | RIGHT_ALT_PRESSED); - } - if (rec.Event.KeyEvent.dwControlKeyState & (RIGHT_CTRL_PRESSED | LEFT_CTRL_PRESSED)) { - modifierKeys |= CTRL; - } - if (rec.Event.KeyEvent.dwControlKeyState & (RIGHT_ALT_PRESSED | LEFT_ALT_PRESSED)) { - modifierKeys |= META; - } - if (escSeen) { - modifierKeys |= META; - } - if (rec.Event.KeyEvent.uChar.UnicodeChar == 0) { - switch (rec.Event.KeyEvent.wVirtualKeyCode) { - case VK_LEFT: - return modifierKeys | LEFT_ARROW_KEY; - case VK_RIGHT: - return modifierKeys | RIGHT_ARROW_KEY; - case VK_UP: - return modifierKeys | UP_ARROW_KEY; - case VK_DOWN: - return modifierKeys | DOWN_ARROW_KEY; - case VK_DELETE: - return modifierKeys | DELETE_KEY; - case VK_HOME: - return modifierKeys | HOME_KEY; - case VK_END: - return modifierKeys | END_KEY; - case VK_PRIOR: - return modifierKeys | PAGE_UP_KEY; - case VK_NEXT: - return modifierKeys | PAGE_DOWN_KEY; - default: - continue; // in raw mode, ReadConsoleInput shows shift, ctrl ... - } // ... ignore them - } else if (rec.Event.KeyEvent.uChar.UnicodeChar == - ctrlChar('[')) { // ESC, set flag for later - escSeen = true; - continue; - } else { - // we got a real character, return it - return modifierKeys | rec.Event.KeyEvent.uChar.UnicodeChar; - } + if (rec.EventType != KEY_EVENT) { + continue; } + // Windows provides for entry of characters that are not on your keyboard by + // sending the + // Unicode characters as a "key up" with virtual keycode 0x12 (VK_MENU == + // Alt key) ... + // accept these characters, otherwise only process characters on "key down" + if (!rec.Event.KeyEvent.bKeyDown && + rec.Event.KeyEvent.wVirtualKeyCode != VK_MENU) { + continue; + } + modifierKeys = 0; + // AltGr is encoded as ( LEFT_CTRL_PRESSED | RIGHT_ALT_PRESSED ), so don't + // treat this + // combination as either CTRL or META we just turn off those two bits, so it + // is still + // possible to combine CTRL and/or META with an AltGr key by using + // right-Ctrl and/or + // left-Alt + if ((rec.Event.KeyEvent.dwControlKeyState & + (LEFT_CTRL_PRESSED | RIGHT_ALT_PRESSED)) == + (LEFT_CTRL_PRESSED | RIGHT_ALT_PRESSED)) { + rec.Event.KeyEvent.dwControlKeyState &= + ~(LEFT_CTRL_PRESSED | RIGHT_ALT_PRESSED); + } + if (rec.Event.KeyEvent.dwControlKeyState & + (RIGHT_CTRL_PRESSED | LEFT_CTRL_PRESSED)) { + modifierKeys |= CTRL; + } + if (rec.Event.KeyEvent.dwControlKeyState & + (RIGHT_ALT_PRESSED | LEFT_ALT_PRESSED)) { + modifierKeys |= META; + } + if (escSeen) { + modifierKeys |= META; + } + if (rec.Event.KeyEvent.uChar.UnicodeChar == 0) { + switch (rec.Event.KeyEvent.wVirtualKeyCode) { + case VK_LEFT: + return modifierKeys | LEFT_ARROW_KEY; + case VK_RIGHT: + return modifierKeys | RIGHT_ARROW_KEY; + case VK_UP: + return modifierKeys | UP_ARROW_KEY; + case VK_DOWN: + return modifierKeys | DOWN_ARROW_KEY; + case VK_DELETE: + return modifierKeys | DELETE_KEY; + case VK_HOME: + return modifierKeys | HOME_KEY; + case VK_END: + return modifierKeys | END_KEY; + case VK_PRIOR: + return modifierKeys | PAGE_UP_KEY; + case VK_NEXT: + return modifierKeys | PAGE_DOWN_KEY; + default: + continue; // in raw mode, ReadConsoleInput shows shift, ctrl ... + } // ... ignore them + } else if (rec.Event.KeyEvent.uChar.UnicodeChar == + ctrlChar('[')) { // ESC, set flag for later + escSeen = true; + continue; + } else { + // we got a real character, return it + return modifierKeys | rec.Event.KeyEvent.uChar.UnicodeChar; + } + } #else - char32_t c; - c = readUnicodeCharacter(); - if (c == 0) - return 0; + char32_t c; + c = readUnicodeCharacter(); + if (c == 0) return 0; -// If _DEBUG_LINUX_KEYBOARD is set, then ctrl-^ puts us into a keyboard debugging mode -// where we print out decimal and decoded values for whatever the "terminal" program +// If _DEBUG_LINUX_KEYBOARD is set, then ctrl-^ puts us into a keyboard +// debugging mode +// where we print out decimal and decoded values for whatever the "terminal" +// program // gives us on different keystrokes. Hit ctrl-C to exit this mode. // #define _DEBUG_LINUX_KEYBOARD #if defined(_DEBUG_LINUX_KEYBOARD) - if (c == ctrlChar('^')) { // ctrl-^, special debug mode, prints all keys hit, ctrl-C to get out - printf("\nEntering keyboard debugging mode (on ctrl-^), press ctrl-C to exit this mode\n"); - while (true) { - unsigned char keys[10]; - int ret = read(0, keys, 10); + if (c == ctrlChar('^')) { // ctrl-^, special debug mode, prints all keys hit, + // ctrl-C to get out + printf( + "\nEntering keyboard debugging mode (on ctrl-^), press ctrl-C to exit " + "this mode\n"); + while (true) { + unsigned char keys[10]; + int ret = read(0, keys, 10); - if (ret <= 0) { - printf("\nret: %d\n", ret); - } - for (int i = 0; i < ret; ++i) { - char32_t key = static_cast(keys[i]); - char* friendlyTextPtr; - char friendlyTextBuf[10]; - const char* prefixText = (key < 0x80) ? "" : "0x80+"; - char32_t keyCopy = (key < 0x80) ? key : key - 0x80; - if (keyCopy >= '!' && keyCopy <= '~') { // printable - friendlyTextBuf[0] = '\''; - friendlyTextBuf[1] = keyCopy; - friendlyTextBuf[2] = '\''; - friendlyTextBuf[3] = 0; - friendlyTextPtr = friendlyTextBuf; - } else if (keyCopy == ' ') { - friendlyTextPtr = const_cast("space"); - } else if (keyCopy == 27) { - friendlyTextPtr = const_cast("ESC"); - } else if (keyCopy == 0) { - friendlyTextPtr = const_cast("NUL"); - } else if (keyCopy == 127) { - friendlyTextPtr = const_cast("DEL"); - } else { - friendlyTextBuf[0] = '^'; - friendlyTextBuf[1] = keyCopy + 0x40; - friendlyTextBuf[2] = 0; - friendlyTextPtr = friendlyTextBuf; - } - printf("%d x%02X (%s%s) ", key, key, prefixText, friendlyTextPtr); - } - printf("\x1b[1G\n"); // go to first column of new line - - // drop out of this loop on ctrl-C - if (keys[0] == ctrlChar('C')) { - printf("Leaving keyboard debugging mode (on ctrl-C)\n"); - fflush(stdout); - return -2; - } + if (ret <= 0) { + printf("\nret: %d\n", ret); + } + for (int i = 0; i < ret; ++i) { + char32_t key = static_cast(keys[i]); + char* friendlyTextPtr; + char friendlyTextBuf[10]; + const char* prefixText = (key < 0x80) ? "" : "0x80+"; + char32_t keyCopy = (key < 0x80) ? key : key - 0x80; + if (keyCopy >= '!' && keyCopy <= '~') { // printable + friendlyTextBuf[0] = '\''; + friendlyTextBuf[1] = keyCopy; + friendlyTextBuf[2] = '\''; + friendlyTextBuf[3] = 0; + friendlyTextPtr = friendlyTextBuf; + } else if (keyCopy == ' ') { + friendlyTextPtr = const_cast("space"); + } else if (keyCopy == 27) { + friendlyTextPtr = const_cast("ESC"); + } else if (keyCopy == 0) { + friendlyTextPtr = const_cast("NUL"); + } else if (keyCopy == 127) { + friendlyTextPtr = const_cast("DEL"); + } else { + friendlyTextBuf[0] = '^'; + friendlyTextBuf[1] = keyCopy + 0x40; + friendlyTextBuf[2] = 0; + friendlyTextPtr = friendlyTextBuf; } + printf("%d x%02X (%s%s) ", key, key, prefixText, friendlyTextPtr); + } + printf("\x1b[1G\n"); // go to first column of new line + + // drop out of this loop on ctrl-C + if (keys[0] == ctrlChar('C')) { + printf("Leaving keyboard debugging mode (on ctrl-C)\n"); + fflush(stdout); + return -2; + } } + } #endif // _DEBUG_LINUX_KEYBOARD - EscapeSequenceProcessing::thisKeyMetaCtrl = 0; // no modifiers yet at initialDispatch - return EscapeSequenceProcessing::doDispatch(c, EscapeSequenceProcessing::initialDispatch); + EscapeSequenceProcessing::thisKeyMetaCtrl = + 0; // no modifiers yet at initialDispatch + return EscapeSequenceProcessing::doDispatch( + c, EscapeSequenceProcessing::initialDispatch); #endif // #_WIN32 } @@ -1719,30 +1851,31 @@ static char32_t linenoiseReadChar(void) { * @param lc pointer to a linenoiseCompletions struct */ static void freeCompletions(linenoiseCompletions* lc) { - lc->completionStrings.clear(); + lc->completionStrings.clear(); } /** - * convert {CTRL + 'A'}, {CTRL + 'a'} and {CTRL + ctrlChar( 'A' )} into ctrlChar( 'A' ) + * convert {CTRL + 'A'}, {CTRL + 'a'} and {CTRL + ctrlChar( 'A' )} into + * ctrlChar( 'A' ) * leave META alone * * @param c character to clean up * @return cleaned-up character */ static int cleanupCtrl(int c) { - if (c & CTRL) { - int d = c & 0x1FF; - if (d >= 'a' && d <= 'z') { - c = (c + ('a' - ctrlChar('A'))) & ~CTRL; - } - if (d >= 'A' && d <= 'Z') { - c = (c + ('A' - ctrlChar('A'))) & ~CTRL; - } - if (d >= ctrlChar('A') && d <= ctrlChar('Z')) { - c = c & ~CTRL; - } + if (c & CTRL) { + int d = c & 0x1FF; + if (d >= 'a' && d <= 'z') { + c = (c + ('a' - ctrlChar('A'))) & ~CTRL; } - return c; + if (d >= 'A' && d <= 'Z') { + c = (c + ('A' - ctrlChar('A'))) & ~CTRL; + } + if (d >= ctrlChar('A') && d <= ctrlChar('Z')) { + c = c & ~CTRL; + } + } + return c; } // break characters that may precede items to be completed @@ -1752,239 +1885,242 @@ static const char breakChars[] = " =+-/\\*?\"'`&<>;|@{([])}"; static const size_t completionCountCutoff = 100; /** - * Handle command completion, using a completionCallback() routine to provide possible substitutions - * This routine handles the mechanics of updating the user's input buffer with possible replacement - * of text as the user selects a proposed completion string, or cancels the completion attempt. - * @param pi PromptBase struct holding information about the prompt and our screen position + * Handle command completion, using a completionCallback() routine to provide + * possible substitutions + * This routine handles the mechanics of updating the user's input buffer with + * possible replacement + * of text as the user selects a proposed completion string, or cancels the + * completion attempt. + * @param pi PromptBase struct holding information about the prompt and our + * screen position */ int InputBuffer::completeLine(PromptBase& pi) { - linenoiseCompletions lc; - char32_t c = 0; + linenoiseCompletions lc; + char32_t c = 0; - // completionCallback() expects a parsable entity, so find the previous break character and - // extract a copy to parse. we also handle the case where tab is hit while not at end-of-line. - int startIndex = pos; - while (--startIndex >= 0) { - if (strchr(breakChars, buf32[startIndex])) { - break; - } + // completionCallback() expects a parsable entity, so find the previous break + // character and + // extract a copy to parse. we also handle the case where tab is hit while + // not at end-of-line. + int startIndex = pos; + while (--startIndex >= 0) { + if (strchr(breakChars, buf32[startIndex])) { + break; } - ++startIndex; - int itemLength = pos - startIndex; - Utf32String unicodeCopy(&buf32[startIndex], itemLength); - Utf8String parseItem(unicodeCopy); + } + ++startIndex; + int itemLength = pos - startIndex; + Utf32String unicodeCopy(&buf32[startIndex], itemLength); + Utf8String parseItem(unicodeCopy); - // get a list of completions - completionCallback(parseItem.get(), &lc); + // get a list of completions + completionCallback(parseItem.get(), &lc); - // if no completions, we are done - if (lc.completionStrings.size() == 0) { - beep(); - freeCompletions(&lc); - return 0; - } + // if no completions, we are done + if (lc.completionStrings.size() == 0) { + beep(); + freeCompletions(&lc); + return 0; + } - // at least one completion - int longestCommonPrefix = 0; - int displayLength = 0; - if (lc.completionStrings.size() == 1) { - longestCommonPrefix = static_cast(lc.completionStrings[0].length()); - } else { - bool keepGoing = true; - while (keepGoing) { - for (size_t j = 0; j < lc.completionStrings.size() - 1; ++j) { - char32_t c1 = lc.completionStrings[j][longestCommonPrefix]; - char32_t c2 = lc.completionStrings[j + 1][longestCommonPrefix]; - if ((0 == c1) || (0 == c2) || (c1 != c2)) { - keepGoing = false; - break; - } - } - if (keepGoing) { - ++longestCommonPrefix; - } + // at least one completion + int longestCommonPrefix = 0; + int displayLength = 0; + if (lc.completionStrings.size() == 1) { + longestCommonPrefix = static_cast(lc.completionStrings[0].length()); + } else { + bool keepGoing = true; + while (keepGoing) { + for (size_t j = 0; j < lc.completionStrings.size() - 1; ++j) { + char32_t c1 = lc.completionStrings[j][longestCommonPrefix]; + char32_t c2 = lc.completionStrings[j + 1][longestCommonPrefix]; + if ((0 == c1) || (0 == c2) || (c1 != c2)) { + keepGoing = false; + break; } + } + if (keepGoing) { + ++longestCommonPrefix; + } } - if (lc.completionStrings.size() != 1) { // beep if ambiguous - beep(); - } + } + if (lc.completionStrings.size() != 1) { // beep if ambiguous + beep(); + } - // if we can extend the item, extend it and return to main loop - if (longestCommonPrefix > itemLength) { - displayLength = len + longestCommonPrefix - itemLength; - if (displayLength > buflen) { - longestCommonPrefix -= displayLength - buflen; // don't overflow buffer - displayLength = buflen; // truncate the insertion - beep(); // and make a noise - } - Utf32String displayText(displayLength + 1); - memcpy(displayText.get(), buf32, sizeof(char32_t) * startIndex); - memcpy(&displayText[startIndex], - &lc.completionStrings[0][0], - sizeof(char32_t) * longestCommonPrefix); - int tailIndex = startIndex + longestCommonPrefix; - memcpy(&displayText[tailIndex], - &buf32[pos], - sizeof(char32_t) * (displayLength - tailIndex + 1)); - copyString32(buf32, displayText.get(), buflen + 1); - pos = startIndex + longestCommonPrefix; - len = displayLength; - refreshLine(pi); - return 0; + // if we can extend the item, extend it and return to main loop + if (longestCommonPrefix > itemLength) { + displayLength = len + longestCommonPrefix - itemLength; + if (displayLength > buflen) { + longestCommonPrefix -= displayLength - buflen; // don't overflow buffer + displayLength = buflen; // truncate the insertion + beep(); // and make a noise } - - // we can't complete any further, wait for second tab - do { - c = linenoiseReadChar(); - c = cleanupCtrl(c); - } while (c == static_cast(-1)); - - // if any character other than tab, pass it to the main loop - if (c != ctrlChar('I')) { - freeCompletions(&lc); - return c; - } - - // we got a second tab, maybe show list of possible completions - bool showCompletions = true; - bool onNewLine = false; - if (lc.completionStrings.size() > completionCountCutoff) { - int savePos = pos; // move cursor to EOL to avoid overwriting the command line - pos = len; - refreshLine(pi); - pos = savePos; - printf("\nDisplay all %u possibilities? (y or n)", - static_cast(lc.completionStrings.size())); - fflush(stdout); - onNewLine = true; - while (c != 'y' && c != 'Y' && c != 'n' && c != 'N' && c != ctrlChar('C')) { - do { - c = linenoiseReadChar(); - c = cleanupCtrl(c); - } while (c == static_cast(-1)); - } - switch (c) { - case 'n': - case 'N': - showCompletions = false; - freeCompletions(&lc); - break; - case ctrlChar('C'): - showCompletions = false; - freeCompletions(&lc); - if (write(1, "^C", 2) == -1) - return -1; // Display the ^C we got - c = 0; - break; - } - } - - // if showing the list, do it the way readline does it - bool stopList = false; - if (showCompletions) { - int longestCompletion = 0; - for (size_t j = 0; j < lc.completionStrings.size(); ++j) { - itemLength = static_cast(lc.completionStrings[j].length()); - if (itemLength > longestCompletion) { - longestCompletion = itemLength; - } - } - longestCompletion += 2; - int columnCount = pi.promptScreenColumns / longestCompletion; - if (columnCount < 1) { - columnCount = 1; - } - if (!onNewLine) { // skip this if we showed "Display all %d possibilities?" - int savePos = pos; // move cursor to EOL to avoid overwriting the command line - pos = len; - refreshLine(pi); - pos = savePos; - } - size_t pauseRow = getScreenRows() - 1; - size_t rowCount = (lc.completionStrings.size() + columnCount - 1) / columnCount; - for (size_t row = 0; row < rowCount; ++row) { - if (row == pauseRow) { - printf("\n--More--"); - fflush(stdout); - c = 0; - bool doBeep = false; - while (c != ' ' && c != '\r' && c != '\n' && c != 'y' && c != 'Y' && c != 'n' && - c != 'N' && c != 'q' && c != 'Q' && c != ctrlChar('C')) { - if (doBeep) { - beep(); - } - doBeep = true; - do { - c = linenoiseReadChar(); - c = cleanupCtrl(c); - } while (c == static_cast(-1)); - } - switch (c) { - case ' ': - case 'y': - case 'Y': - printf("\r \r"); - pauseRow += getScreenRows() - 1; - break; - case '\r': - case '\n': - printf("\r \r"); - ++pauseRow; - break; - case 'n': - case 'N': - case 'q': - case 'Q': - printf("\r \r"); - stopList = true; - break; - case ctrlChar('C'): - if (write(1, "^C", 2) == -1) - return -1; // Display the ^C we got - stopList = true; - break; - } - } else { - printf("\n"); - } - if (stopList) { - break; - } - for (int column = 0; column < columnCount; ++column) { - size_t index = (column * rowCount) + row; - if (index < lc.completionStrings.size()) { - itemLength = static_cast(lc.completionStrings[index].length()); - fflush(stdout); - if (write32(1, lc.completionStrings[index].get(), itemLength) == -1) - return -1; - if (((column + 1) * rowCount) + row < lc.completionStrings.size()) { - for (int k = itemLength; k < longestCompletion; ++k) { - printf(" "); - } - } - } - } - } - fflush(stdout); - freeCompletions(&lc); - } - - // display the prompt on a new line, then redisplay the input buffer - if (!stopList || c == ctrlChar('C')) { - if (write(1, "\n", 1) == -1) - return 0; - } - if (! pi.write()) - return 0; -#ifndef _WIN32 - // we have to generate our own newline on line wrap on Linux - if (pi.promptIndentation == 0 && pi.promptExtraLines > 0) - if (write(1, "\n", 1) == -1) - return 0; -#endif - pi.promptCursorRowOffset = pi.promptExtraLines; + Utf32String displayText(displayLength + 1); + memcpy(displayText.get(), buf32, sizeof(char32_t) * startIndex); + memcpy(&displayText[startIndex], &lc.completionStrings[0][0], + sizeof(char32_t) * longestCommonPrefix); + int tailIndex = startIndex + longestCommonPrefix; + memcpy(&displayText[tailIndex], &buf32[pos], + sizeof(char32_t) * (displayLength - tailIndex + 1)); + copyString32(buf32, displayText.get(), buflen + 1); + pos = startIndex + longestCommonPrefix; + len = displayLength; refreshLine(pi); return 0; + } + + // we can't complete any further, wait for second tab + do { + c = linenoiseReadChar(); + c = cleanupCtrl(c); + } while (c == static_cast(-1)); + + // if any character other than tab, pass it to the main loop + if (c != ctrlChar('I')) { + freeCompletions(&lc); + return c; + } + + // we got a second tab, maybe show list of possible completions + bool showCompletions = true; + bool onNewLine = false; + if (lc.completionStrings.size() > completionCountCutoff) { + int savePos = + pos; // move cursor to EOL to avoid overwriting the command line + pos = len; + refreshLine(pi); + pos = savePos; + printf("\nDisplay all %u possibilities? (y or n)", + static_cast(lc.completionStrings.size())); + fflush(stdout); + onNewLine = true; + while (c != 'y' && c != 'Y' && c != 'n' && c != 'N' && c != ctrlChar('C')) { + do { + c = linenoiseReadChar(); + c = cleanupCtrl(c); + } while (c == static_cast(-1)); + } + switch (c) { + case 'n': + case 'N': + showCompletions = false; + freeCompletions(&lc); + break; + case ctrlChar('C'): + showCompletions = false; + freeCompletions(&lc); + if (write(1, "^C", 2) == -1) return -1; // Display the ^C we got + c = 0; + break; + } + } + + // if showing the list, do it the way readline does it + bool stopList = false; + if (showCompletions) { + int longestCompletion = 0; + for (size_t j = 0; j < lc.completionStrings.size(); ++j) { + itemLength = static_cast(lc.completionStrings[j].length()); + if (itemLength > longestCompletion) { + longestCompletion = itemLength; + } + } + longestCompletion += 2; + int columnCount = pi.promptScreenColumns / longestCompletion; + if (columnCount < 1) { + columnCount = 1; + } + if (!onNewLine) { // skip this if we showed "Display all %d possibilities?" + int savePos = + pos; // move cursor to EOL to avoid overwriting the command line + pos = len; + refreshLine(pi); + pos = savePos; + } + size_t pauseRow = getScreenRows() - 1; + size_t rowCount = + (lc.completionStrings.size() + columnCount - 1) / columnCount; + for (size_t row = 0; row < rowCount; ++row) { + if (row == pauseRow) { + printf("\n--More--"); + fflush(stdout); + c = 0; + bool doBeep = false; + while (c != ' ' && c != '\r' && c != '\n' && c != 'y' && c != 'Y' && + c != 'n' && c != 'N' && c != 'q' && c != 'Q' && + c != ctrlChar('C')) { + if (doBeep) { + beep(); + } + doBeep = true; + do { + c = linenoiseReadChar(); + c = cleanupCtrl(c); + } while (c == static_cast(-1)); + } + switch (c) { + case ' ': + case 'y': + case 'Y': + printf("\r \r"); + pauseRow += getScreenRows() - 1; + break; + case '\r': + case '\n': + printf("\r \r"); + ++pauseRow; + break; + case 'n': + case 'N': + case 'q': + case 'Q': + printf("\r \r"); + stopList = true; + break; + case ctrlChar('C'): + if (write(1, "^C", 2) == -1) return -1; // Display the ^C we got + stopList = true; + break; + } + } else { + printf("\n"); + } + if (stopList) { + break; + } + for (int column = 0; column < columnCount; ++column) { + size_t index = (column * rowCount) + row; + if (index < lc.completionStrings.size()) { + itemLength = static_cast(lc.completionStrings[index].length()); + fflush(stdout); + if (write32(1, lc.completionStrings[index].get(), itemLength) == -1) + return -1; + if (((column + 1) * rowCount) + row < lc.completionStrings.size()) { + for (int k = itemLength; k < longestCompletion; ++k) { + printf(" "); + } + } + } + } + } + fflush(stdout); + freeCompletions(&lc); + } + + // display the prompt on a new line, then redisplay the input buffer + if (!stopList || c == ctrlChar('C')) { + if (write(1, "\n", 1) == -1) return 0; + } + if (!pi.write()) return 0; +#ifndef _WIN32 + // we have to generate our own newline on line wrap on Linux + if (pi.promptIndentation == 0 && pi.promptExtraLines > 0) + if (write(1, "\n", 1) == -1) return 0; +#endif + pi.promptCursorRowOffset = pi.promptExtraLines; + refreshLine(pi); + return 0; } /** @@ -1992,293 +2128,306 @@ int InputBuffer::completeLine(PromptBase& pi) { */ void linenoiseClearScreen(void) { #ifdef _WIN32 - COORD coord = {0, 0}; - CONSOLE_SCREEN_BUFFER_INFO inf; - HANDLE screenHandle = GetStdHandle(STD_OUTPUT_HANDLE); - GetConsoleScreenBufferInfo(screenHandle, &inf); - SetConsoleCursorPosition(screenHandle, coord); - DWORD count; - FillConsoleOutputCharacterA(screenHandle, ' ', inf.dwSize.X * inf.dwSize.Y, coord, &count); + COORD coord = {0, 0}; + CONSOLE_SCREEN_BUFFER_INFO inf; + HANDLE screenHandle = GetStdHandle(STD_OUTPUT_HANDLE); + GetConsoleScreenBufferInfo(screenHandle, &inf); + SetConsoleCursorPosition(screenHandle, coord); + DWORD count; + FillConsoleOutputCharacterA(screenHandle, ' ', inf.dwSize.X * inf.dwSize.Y, + coord, &count); #else - if (write(1, "\x1b[H\x1b[2J", 7) <= 0) - return; + if (write(1, "\x1b[H\x1b[2J", 7) <= 0) return; #endif } void InputBuffer::clearScreen(PromptBase& pi) { - linenoiseClearScreen(); - if (! pi.write()) - return; + linenoiseClearScreen(); + if (!pi.write()) return; #ifndef _WIN32 - // we have to generate our own newline on line wrap on Linux - if (pi.promptIndentation == 0 && pi.promptExtraLines > 0) - if (write(1, "\n", 1) == -1) - return; + // we have to generate our own newline on line wrap on Linux + if (pi.promptIndentation == 0 && pi.promptExtraLines > 0) + if (write(1, "\n", 1) == -1) return; #endif - pi.promptCursorRowOffset = pi.promptExtraLines; - refreshLine(pi); + pi.promptCursorRowOffset = pi.promptExtraLines; + refreshLine(pi); } /** - * Incremental history search -- take over the prompt and keyboard as the user types a search - * string, deletes characters from it, changes direction, and either accepts the found line (for + * Incremental history search -- take over the prompt and keyboard as the user + * types a search + * string, deletes characters from it, changes direction, and either accepts the + * found line (for * execution orediting) or cancels. - * @param pi PromptBase struct holding information about the (old, static) prompt and our + * @param pi PromptBase struct holding information about the (old, + * static) prompt and our * screen position - * @param startChar the character that began the search, used to set the initial direction + * @param startChar the character that began the search, used to set the initial + * direction */ int InputBuffer::incrementalHistorySearch(PromptBase& pi, int startChar) { - size_t bufferSize; - size_t ucharCount = 0; + size_t bufferSize; + size_t ucharCount = 0; - // if not already recalling, add the current line to the history list so we don't have to - // special case it - if (historyIndex == historyLen - 1) { - free(history[historyLen - 1]); - bufferSize = sizeof(char32_t) * len + 1; - unique_ptr tempBuffer(new char[bufferSize]); - copyString32to8(tempBuffer.get(), bufferSize, buf32); - history[historyLen - 1] = strdup8(tempBuffer.get()); - } - int historyLineLength = len; - int historyLinePosition = pos; - char32_t emptyBuffer[1]; - char emptyWidths[1]; - InputBuffer empty(emptyBuffer, emptyWidths, 1); - empty.refreshLine(pi); // erase the old input first - DynamicPrompt dp(pi, (startChar == ctrlChar('R')) ? -1 : 1); + // if not already recalling, add the current line to the history list so we + // don't have to + // special case it + if (historyIndex == historyLen - 1) { + free(history[historyLen - 1]); + bufferSize = sizeof(char32_t) * len + 1; + unique_ptr tempBuffer(new char[bufferSize]); + copyString32to8(tempBuffer.get(), bufferSize, buf32); + history[historyLen - 1] = strdup8(tempBuffer.get()); + } + int historyLineLength = len; + int historyLinePosition = pos; + char32_t emptyBuffer[1]; + char emptyWidths[1]; + InputBuffer empty(emptyBuffer, emptyWidths, 1); + empty.refreshLine(pi); // erase the old input first + DynamicPrompt dp(pi, (startChar == ctrlChar('R')) ? -1 : 1); - dp.promptPreviousLen = pi.promptPreviousLen; - dp.promptPreviousInputLen = pi.promptPreviousInputLen; - dynamicRefresh( - dp, buf32, historyLineLength, historyLinePosition); // draw user's text with our prompt + dp.promptPreviousLen = pi.promptPreviousLen; + dp.promptPreviousInputLen = pi.promptPreviousInputLen; + dynamicRefresh(dp, buf32, historyLineLength, + historyLinePosition); // draw user's text with our prompt - // loop until we get an exit character - int c; - bool keepLooping = true; - bool useSearchedLine = true; - bool searchAgain = false; - char32_t* activeHistoryLine = 0; - while (keepLooping) { - c = linenoiseReadChar(); - c = cleanupCtrl(c); // convert CTRL + into normal ctrl + // loop until we get an exit character + int c; + bool keepLooping = true; + bool useSearchedLine = true; + bool searchAgain = false; + char32_t* activeHistoryLine = 0; + while (keepLooping) { + c = linenoiseReadChar(); + c = cleanupCtrl(c); // convert CTRL + into normal ctrl - switch (c) { - // these characters keep the selected text but do not execute it - case ctrlChar('A'): // ctrl-A, move cursor to start of line - case HOME_KEY: - case ctrlChar('B'): // ctrl-B, move cursor left by one character - case LEFT_ARROW_KEY: - case META + 'b': // meta-B, move cursor left by one word - case META + 'B': - case CTRL + LEFT_ARROW_KEY: - case META + LEFT_ARROW_KEY: // Emacs allows Meta, bash & readline don't - case ctrlChar('D'): - case META + 'd': // meta-D, kill word to right of cursor - case META + 'D': - case ctrlChar('E'): // ctrl-E, move cursor to end of line - case END_KEY: - case ctrlChar('F'): // ctrl-F, move cursor right by one character - case RIGHT_ARROW_KEY: - case META + 'f': // meta-F, move cursor right by one word - case META + 'F': - case CTRL + RIGHT_ARROW_KEY: - case META + RIGHT_ARROW_KEY: // Emacs allows Meta, bash & readline don't - case META + ctrlChar('H'): - case ctrlChar('J'): - case ctrlChar('K'): // ctrl-K, kill from cursor to end of line - case ctrlChar('M'): - case ctrlChar('N'): // ctrl-N, recall next line in history - case ctrlChar('P'): // ctrl-P, recall previous line in history - case DOWN_ARROW_KEY: - case UP_ARROW_KEY: - case ctrlChar('T'): // ctrl-T, transpose characters - case ctrlChar('U'): // ctrl-U, kill all characters to the left of the cursor - case ctrlChar('W'): - case META + 'y': // meta-Y, "yank-pop", rotate popped text - case META + 'Y': - case 127: - case DELETE_KEY: - case META + '<': // start of history - case PAGE_UP_KEY: - case META + '>': // end of history - case PAGE_DOWN_KEY: - keepLooping = false; - break; + switch (c) { + // these characters keep the selected text but do not execute it + case ctrlChar('A'): // ctrl-A, move cursor to start of line + case HOME_KEY: + case ctrlChar('B'): // ctrl-B, move cursor left by one character + case LEFT_ARROW_KEY: + case META + 'b': // meta-B, move cursor left by one word + case META + 'B': + case CTRL + LEFT_ARROW_KEY: + case META + LEFT_ARROW_KEY: // Emacs allows Meta, bash & readline don't + case ctrlChar('D'): + case META + 'd': // meta-D, kill word to right of cursor + case META + 'D': + case ctrlChar('E'): // ctrl-E, move cursor to end of line + case END_KEY: + case ctrlChar('F'): // ctrl-F, move cursor right by one character + case RIGHT_ARROW_KEY: + case META + 'f': // meta-F, move cursor right by one word + case META + 'F': + case CTRL + RIGHT_ARROW_KEY: + case META + RIGHT_ARROW_KEY: // Emacs allows Meta, bash & readline don't + case META + ctrlChar('H'): + case ctrlChar('J'): + case ctrlChar('K'): // ctrl-K, kill from cursor to end of line + case ctrlChar('M'): + case ctrlChar('N'): // ctrl-N, recall next line in history + case ctrlChar('P'): // ctrl-P, recall previous line in history + case DOWN_ARROW_KEY: + case UP_ARROW_KEY: + case ctrlChar('T'): // ctrl-T, transpose characters + case ctrlChar( + 'U'): // ctrl-U, kill all characters to the left of the cursor + case ctrlChar('W'): + case META + 'y': // meta-Y, "yank-pop", rotate popped text + case META + 'Y': + case 127: + case DELETE_KEY: + case META + '<': // start of history + case PAGE_UP_KEY: + case META + '>': // end of history + case PAGE_DOWN_KEY: + keepLooping = false; + break; - // these characters revert the input line to its previous state - case ctrlChar('C'): // ctrl-C, abort this line - case ctrlChar('G'): - case ctrlChar('L'): // ctrl-L, clear screen and redisplay line - keepLooping = false; - useSearchedLine = false; - if (c != ctrlChar('L')) { - c = -1; // ctrl-C and ctrl-G just abort the search and do nothing else - } - break; + // these characters revert the input line to its previous state + case ctrlChar('C'): // ctrl-C, abort this line + case ctrlChar('G'): + case ctrlChar('L'): // ctrl-L, clear screen and redisplay line + keepLooping = false; + useSearchedLine = false; + if (c != ctrlChar('L')) { + c = -1; // ctrl-C and ctrl-G just abort the search and do nothing + // else + } + break; - // these characters stay in search mode and update the display - case ctrlChar('S'): - case ctrlChar('R'): - if (dp.searchTextLen == 0) { // if no current search text, recall previous text - if (previousSearchText.length()) { - dp.updateSearchText(previousSearchText.get()); - } - } - if ((dp.direction == 1 && c == ctrlChar('R')) || - (dp.direction == -1 && c == ctrlChar('S'))) { - dp.direction = 0 - dp.direction; // reverse direction - dp.updateSearchPrompt(); // change the prompt - } else { - searchAgain = true; // same direction, search again - } - break; + // these characters stay in search mode and update the display + case ctrlChar('S'): + case ctrlChar('R'): + if (dp.searchTextLen == + 0) { // if no current search text, recall previous text + if (previousSearchText.length()) { + dp.updateSearchText(previousSearchText.get()); + } + } + if ((dp.direction == 1 && c == ctrlChar('R')) || + (dp.direction == -1 && c == ctrlChar('S'))) { + dp.direction = 0 - dp.direction; // reverse direction + dp.updateSearchPrompt(); // change the prompt + } else { + searchAgain = true; // same direction, search again + } + break; // job control is its own thing #ifndef _WIN32 - case ctrlChar('Z'): // ctrl-Z, job control - disableRawMode(); // Returning to Linux (whatever) shell, leave raw mode - raise(SIGSTOP); // Break out in mid-line - enableRawMode(); // Back from Linux shell, re-enter raw mode - { - bufferSize = historyLineLength + 1; - unique_ptr tempUnicode(new char32_t[bufferSize]); - copyString8to32(tempUnicode.get(), - bufferSize, - ucharCount, - history[historyIndex]); - dynamicRefresh(dp, tempUnicode.get(), historyLineLength, historyLinePosition); - } - continue; - break; + case ctrlChar('Z'): // ctrl-Z, job control + disableRawMode(); // Returning to Linux (whatever) shell, leave raw + // mode + raise(SIGSTOP); // Break out in mid-line + enableRawMode(); // Back from Linux shell, re-enter raw mode + { + bufferSize = historyLineLength + 1; + unique_ptr tempUnicode(new char32_t[bufferSize]); + copyString8to32(tempUnicode.get(), bufferSize, ucharCount, + history[historyIndex]); + dynamicRefresh(dp, tempUnicode.get(), historyLineLength, + historyLinePosition); + } + continue; + break; #endif - // these characters update the search string, and hence the selected input line - case ctrlChar('H'): // backspace/ctrl-H, delete char to left of cursor - if (dp.searchTextLen > 0) { - unique_ptr tempUnicode(new char32_t[dp.searchTextLen]); - --dp.searchTextLen; - dp.searchText[dp.searchTextLen] = 0; - copyString32(tempUnicode.get(), dp.searchText.get(), dp.searchTextLen + 1); - dp.updateSearchText(tempUnicode.get()); - } else { - beep(); - } - break; - - case ctrlChar('Y'): // ctrl-Y, yank killed text - break; - - default: - if (!isControlChar(c) && c <= 0x0010FFFF) { // not an action character - unique_ptr tempUnicode(new char32_t[dp.searchTextLen + 2]); - copyString32(tempUnicode.get(), dp.searchText.get(), dp.searchTextLen + 2); - tempUnicode[dp.searchTextLen] = c; - tempUnicode[dp.searchTextLen + 1] = 0; - dp.updateSearchText(tempUnicode.get()); - } else { - beep(); - } - } // switch - - // if we are staying in search mode, search now - if (keepLooping) { - bufferSize = historyLineLength + 1; - activeHistoryLine = new char32_t[bufferSize]; - copyString8to32(activeHistoryLine, bufferSize, ucharCount, history[historyIndex]); - if (dp.searchTextLen > 0) { - bool found = false; - int historySearchIndex = historyIndex; - int lineLength = static_cast(ucharCount); - int lineSearchPos = historyLinePosition; - if (searchAgain) { - lineSearchPos += dp.direction; - } - searchAgain = false; - while (true) { - while ((dp.direction > 0) ? (lineSearchPos < lineLength) - : (lineSearchPos >= 0)) { - if (strncmp32(dp.searchText.get(), - &activeHistoryLine[lineSearchPos], - dp.searchTextLen) == 0) { - found = true; - break; - } - lineSearchPos += dp.direction; - } - if (found) { - historyIndex = historySearchIndex; - historyLineLength = lineLength; - historyLinePosition = lineSearchPos; - break; - } else if ((dp.direction > 0) ? (historySearchIndex < historyLen - 1) - : (historySearchIndex > 0)) { - historySearchIndex += dp.direction; - bufferSize = strlen8(history[historySearchIndex]) + 1; - delete[] activeHistoryLine; - activeHistoryLine = new char32_t[bufferSize]; - copyString8to32(activeHistoryLine, - bufferSize, - ucharCount, - history[historySearchIndex]); - lineLength = static_cast(ucharCount); - lineSearchPos = (dp.direction > 0) ? 0 : (lineLength - dp.searchTextLen); - } else { - beep(); - break; - } - }; // while - } - if (activeHistoryLine) { - delete[] activeHistoryLine; - } - bufferSize = historyLineLength + 1; - activeHistoryLine = new char32_t[bufferSize]; - copyString8to32(activeHistoryLine, bufferSize, ucharCount, history[historyIndex]); - dynamicRefresh(dp, - activeHistoryLine, - historyLineLength, - historyLinePosition); // draw user's text with our prompt + // these characters update the search string, and hence the selected input + // line + case ctrlChar('H'): // backspace/ctrl-H, delete char to left of cursor + if (dp.searchTextLen > 0) { + unique_ptr tempUnicode(new char32_t[dp.searchTextLen]); + --dp.searchTextLen; + dp.searchText[dp.searchTextLen] = 0; + copyString32(tempUnicode.get(), dp.searchText.get(), + dp.searchTextLen + 1); + dp.updateSearchText(tempUnicode.get()); + } else { + beep(); } - } // while + break; - // leaving history search, restore previous prompt, maybe make searched line current - PromptBase pb; - pb.promptChars = pi.promptIndentation; - pb.promptBytes = pi.promptBytes; - Utf32String tempUnicode(pb.promptBytes + 1); + case ctrlChar('Y'): // ctrl-Y, yank killed text + break; - copyString32(tempUnicode.get(), &pi.promptText[pi.promptLastLinePosition], pb.promptBytes + 1); - tempUnicode.initFromBuffer(); - pb.promptText = tempUnicode; - pb.promptExtraLines = 0; - pb.promptIndentation = pi.promptIndentation; - pb.promptLastLinePosition = 0; - pb.promptPreviousInputLen = historyLineLength; - pb.promptCursorRowOffset = dp.promptCursorRowOffset; - pb.promptScreenColumns = pi.promptScreenColumns; - pb.promptPreviousLen = dp.promptChars; - if (useSearchedLine && activeHistoryLine) { - historyRecallMostRecent = true; - copyString32(buf32, activeHistoryLine, buflen + 1); - len = historyLineLength; - pos = historyLinePosition; - } - if (activeHistoryLine) { + default: + if (!isControlChar(c) && c <= 0x0010FFFF) { // not an action character + unique_ptr tempUnicode( + new char32_t[dp.searchTextLen + 2]); + copyString32(tempUnicode.get(), dp.searchText.get(), + dp.searchTextLen + 2); + tempUnicode[dp.searchTextLen] = c; + tempUnicode[dp.searchTextLen + 1] = 0; + dp.updateSearchText(tempUnicode.get()); + } else { + beep(); + } + } // switch + + // if we are staying in search mode, search now + if (keepLooping) { + bufferSize = historyLineLength + 1; + activeHistoryLine = new char32_t[bufferSize]; + copyString8to32(activeHistoryLine, bufferSize, ucharCount, + history[historyIndex]); + if (dp.searchTextLen > 0) { + bool found = false; + int historySearchIndex = historyIndex; + int lineLength = static_cast(ucharCount); + int lineSearchPos = historyLinePosition; + if (searchAgain) { + lineSearchPos += dp.direction; + } + searchAgain = false; + while (true) { + while ((dp.direction > 0) ? (lineSearchPos < lineLength) + : (lineSearchPos >= 0)) { + if (strncmp32(dp.searchText.get(), + &activeHistoryLine[lineSearchPos], + dp.searchTextLen) == 0) { + found = true; + break; + } + lineSearchPos += dp.direction; + } + if (found) { + historyIndex = historySearchIndex; + historyLineLength = lineLength; + historyLinePosition = lineSearchPos; + break; + } else if ((dp.direction > 0) ? (historySearchIndex < historyLen - 1) + : (historySearchIndex > 0)) { + historySearchIndex += dp.direction; + bufferSize = strlen8(history[historySearchIndex]) + 1; + delete[] activeHistoryLine; + activeHistoryLine = new char32_t[bufferSize]; + copyString8to32(activeHistoryLine, bufferSize, ucharCount, + history[historySearchIndex]); + lineLength = static_cast(ucharCount); + lineSearchPos = + (dp.direction > 0) ? 0 : (lineLength - dp.searchTextLen); + } else { + beep(); + break; + } + }; // while + } + if (activeHistoryLine) { delete[] activeHistoryLine; + } + bufferSize = historyLineLength + 1; + activeHistoryLine = new char32_t[bufferSize]; + copyString8to32(activeHistoryLine, bufferSize, ucharCount, + history[historyIndex]); + dynamicRefresh(dp, activeHistoryLine, historyLineLength, + historyLinePosition); // draw user's text with our prompt } - dynamicRefresh(pb, buf32, len, pos); // redraw the original prompt with current input - pi.promptPreviousInputLen = len; - pi.promptCursorRowOffset = pi.promptExtraLines + pb.promptCursorRowOffset; - previousSearchText = dp.searchText; // save search text for possible reuse on ctrl-R ctrl-R - return c; // pass a character or -1 back to main loop + } // while + + // leaving history search, restore previous prompt, maybe make searched line + // current + PromptBase pb; + pb.promptChars = pi.promptIndentation; + pb.promptBytes = pi.promptBytes; + Utf32String tempUnicode(pb.promptBytes + 1); + + copyString32(tempUnicode.get(), &pi.promptText[pi.promptLastLinePosition], + pb.promptBytes + 1); + tempUnicode.initFromBuffer(); + pb.promptText = tempUnicode; + pb.promptExtraLines = 0; + pb.promptIndentation = pi.promptIndentation; + pb.promptLastLinePosition = 0; + pb.promptPreviousInputLen = historyLineLength; + pb.promptCursorRowOffset = dp.promptCursorRowOffset; + pb.promptScreenColumns = pi.promptScreenColumns; + pb.promptPreviousLen = dp.promptChars; + if (useSearchedLine && activeHistoryLine) { + historyRecallMostRecent = true; + copyString32(buf32, activeHistoryLine, buflen + 1); + len = historyLineLength; + pos = historyLinePosition; + } + if (activeHistoryLine) { + delete[] activeHistoryLine; + } + dynamicRefresh(pb, buf32, len, + pos); // redraw the original prompt with current input + pi.promptPreviousInputLen = len; + pi.promptCursorRowOffset = pi.promptExtraLines + pb.promptCursorRowOffset; + previousSearchText = + dp.searchText; // save search text for possible reuse on ctrl-R ctrl-R + return c; // pass a character or -1 back to main loop } static bool isCharacterAlphanumeric(char32_t testChar) { #ifdef _WIN32 - return (iswalnum((wint_t) testChar) != 0 ? true : false); + return (iswalnum((wint_t)testChar) != 0 ? true : false); #else - return (iswalnum(testChar) != 0 ? true : false); + return (iswalnum(testChar) != 0 ? true : false); #endif } @@ -2287,589 +2436,591 @@ static bool gotResize = false; #endif int InputBuffer::getInputLine(PromptBase& pi) { - // The latest history entry is always our current buffer - if (len > 0) { - size_t bufferSize = sizeof(char32_t) * len + 1; - unique_ptr tempBuffer(new char[bufferSize]); - copyString32to8(tempBuffer.get(), bufferSize, buf32); - linenoiseHistoryAdd(tempBuffer.get()); - } else { - linenoiseHistoryAdd(""); - } - historyIndex = historyLen - 1; - historyRecallMostRecent = false; + // The latest history entry is always our current buffer + if (len > 0) { + size_t bufferSize = sizeof(char32_t) * len + 1; + unique_ptr tempBuffer(new char[bufferSize]); + copyString32to8(tempBuffer.get(), bufferSize, buf32); + linenoiseHistoryAdd(tempBuffer.get()); + } else { + linenoiseHistoryAdd(""); + } + historyIndex = historyLen - 1; + historyRecallMostRecent = false; - // display the prompt - if (! pi.write()) + // display the prompt + if (!pi.write()) return -1; + +#ifndef _WIN32 + // we have to generate our own newline on line wrap on Linux + if (pi.promptIndentation == 0 && pi.promptExtraLines > 0) + if (write(1, "\n", 1) == -1) return -1; +#endif + + // the cursor starts out at the end of the prompt + pi.promptCursorRowOffset = pi.promptExtraLines; + + // kill and yank start in "other" mode + killRing.lastAction = KillRing::actionOther; + + // when history search returns control to us, we execute its terminating + // keystroke + int terminatingKeystroke = -1; + + // if there is already text in the buffer, display it first + if (len > 0) { + refreshLine(pi); + } + + // loop collecting characters, respond to line editing characters + while (true) { + int c; + if (terminatingKeystroke == -1) { + c = linenoiseReadChar(); // get a new keystroke + +#ifndef _WIN32 + if (c == 0 && gotResize) { + // caught a window resize event + // now redraw the prompt and line + gotResize = false; + pi.promptScreenColumns = getScreenColumns(); + dynamicRefresh(pi, buf32, len, + pos); // redraw the original prompt with current input + continue; + } +#endif + } else { + c = terminatingKeystroke; // use the terminating keystroke from search + terminatingKeystroke = -1; // clear it once we've used it + } + + c = cleanupCtrl(c); // convert CTRL + into normal ctrl + + if (c == 0) { + return len; + } + + if (c == -1) { + refreshLine(pi); + continue; + } + + if (c == -2) { + if (!pi.write()) return -1; + refreshLine(pi); + continue; + } + + // ctrl-I/tab, command completion, needs to be before switch statement + if (c == ctrlChar('I') && completionCallback) { + if (pos == 0) // SERVER-4967 -- in earlier versions, you could paste + // previous output + continue; // back into the shell ... this output may have leading + // tabs. + // This hack (i.e. what the old code did) prevents command completion + // on an empty line but lets users paste text with leading tabs. + + killRing.lastAction = KillRing::actionOther; + historyRecallMostRecent = false; + + // completeLine does the actual completion and replacement + c = completeLine(pi); + + if (c < 0) // return on error + return len; + + if (c == 0) // read next character when 0 + continue; + + // deliberate fall-through here, so we use the terminating character + } + + switch (c) { + case ctrlChar('A'): // ctrl-A, move cursor to start of line + case HOME_KEY: + killRing.lastAction = KillRing::actionOther; + pos = 0; + refreshLine(pi); + break; + + case ctrlChar('B'): // ctrl-B, move cursor left by one character + case LEFT_ARROW_KEY: + killRing.lastAction = KillRing::actionOther; + if (pos > 0) { + --pos; + refreshLine(pi); + } + break; + + case META + 'b': // meta-B, move cursor left by one word + case META + 'B': + case CTRL + LEFT_ARROW_KEY: + case META + LEFT_ARROW_KEY: // Emacs allows Meta, bash & readline don't + killRing.lastAction = KillRing::actionOther; + if (pos > 0) { + while (pos > 0 && !isCharacterAlphanumeric(buf32[pos - 1])) { + --pos; + } + while (pos > 0 && isCharacterAlphanumeric(buf32[pos - 1])) { + --pos; + } + refreshLine(pi); + } + break; + + case ctrlChar('C'): // ctrl-C, abort this line + killRing.lastAction = KillRing::actionOther; + historyRecallMostRecent = false; + errno = EAGAIN; + --historyLen; + free(history[historyLen]); + // we need one last refresh with the cursor at the end of the line + // so we don't display the next prompt over the previous input line + pos = len; // pass len as pos for EOL + refreshLine(pi); + if (write(1, "^C", 2) == -1) return -1; // Display the ^C we got return -1; -#ifndef _WIN32 - // we have to generate our own newline on line wrap on Linux - if (pi.promptIndentation == 0 && pi.promptExtraLines > 0) - if (write(1, "\n", 1) == -1) - return -1; -#endif - - // the cursor starts out at the end of the prompt - pi.promptCursorRowOffset = pi.promptExtraLines; - - // kill and yank start in "other" mode - killRing.lastAction = KillRing::actionOther; - - // when history search returns control to us, we execute its terminating keystroke - int terminatingKeystroke = -1; - - // if there is already text in the buffer, display it first - if (len > 0) { - refreshLine(pi); - } - - // loop collecting characters, respond to line editing characters - while (true) { - int c; - if (terminatingKeystroke == -1) { - c = linenoiseReadChar(); // get a new keystroke - -#ifndef _WIN32 - if (c == 0 && gotResize) { - // caught a window resize event - // now redraw the prompt and line - gotResize = false; - pi.promptScreenColumns = getScreenColumns(); - dynamicRefresh(pi, buf32, len, pos); // redraw the original prompt with current input - continue; + case META + 'c': // meta-C, give word initial Cap + case META + 'C': + killRing.lastAction = KillRing::actionOther; + historyRecallMostRecent = false; + if (pos < len) { + while (pos < len && !isCharacterAlphanumeric(buf32[pos])) { + ++pos; + } + if (pos < len && isCharacterAlphanumeric(buf32[pos])) { + if (buf32[pos] >= 'a' && buf32[pos] <= 'z') { + buf32[pos] += 'A' - 'a'; } -#endif - } else { - c = terminatingKeystroke; // use the terminating keystroke from search - terminatingKeystroke = -1; // clear it once we've used it - } - - c = cleanupCtrl(c); // convert CTRL + into normal ctrl - - if (c == 0) { - return len; + ++pos; + } + while (pos < len && isCharacterAlphanumeric(buf32[pos])) { + if (buf32[pos] >= 'A' && buf32[pos] <= 'Z') { + buf32[pos] += 'a' - 'A'; + } + ++pos; + } + refreshLine(pi); } + break; - if (c == -1) { + // ctrl-D, delete the character under the cursor + // on an empty line, exit the shell + case ctrlChar('D'): + killRing.lastAction = KillRing::actionOther; + if (len > 0 && pos < len) { + historyRecallMostRecent = false; + memmove(buf32 + pos, buf32 + pos + 1, sizeof(char32_t) * (len - pos)); + --len; + refreshLine(pi); + } else if (len == 0) { + --historyLen; + free(history[historyLen]); + return -1; + } + break; + + case META + 'd': // meta-D, kill word to right of cursor + case META + 'D': + if (pos < len) { + historyRecallMostRecent = false; + int endingPos = pos; + while (endingPos < len && + !isCharacterAlphanumeric(buf32[endingPos])) { + ++endingPos; + } + while (endingPos < len && isCharacterAlphanumeric(buf32[endingPos])) { + ++endingPos; + } + killRing.kill(&buf32[pos], endingPos - pos, true); + memmove(buf32 + pos, buf32 + endingPos, + sizeof(char32_t) * (len - endingPos + 1)); + len -= endingPos - pos; + refreshLine(pi); + } + killRing.lastAction = KillRing::actionKill; + break; + + case ctrlChar('E'): // ctrl-E, move cursor to end of line + case END_KEY: + killRing.lastAction = KillRing::actionOther; + pos = len; + refreshLine(pi); + break; + + case ctrlChar('F'): // ctrl-F, move cursor right by one character + case RIGHT_ARROW_KEY: + killRing.lastAction = KillRing::actionOther; + if (pos < len) { + ++pos; + refreshLine(pi); + } + break; + + case META + 'f': // meta-F, move cursor right by one word + case META + 'F': + case CTRL + RIGHT_ARROW_KEY: + case META + RIGHT_ARROW_KEY: // Emacs allows Meta, bash & readline don't + killRing.lastAction = KillRing::actionOther; + if (pos < len) { + while (pos < len && !isCharacterAlphanumeric(buf32[pos])) { + ++pos; + } + while (pos < len && isCharacterAlphanumeric(buf32[pos])) { + ++pos; + } + refreshLine(pi); + } + break; + + case ctrlChar('H'): // backspace/ctrl-H, delete char to left of cursor + killRing.lastAction = KillRing::actionOther; + if (pos > 0) { + historyRecallMostRecent = false; + memmove(buf32 + pos - 1, buf32 + pos, + sizeof(char32_t) * (1 + len - pos)); + --pos; + --len; + refreshLine(pi); + } + break; + + // meta-Backspace, kill word to left of cursor + case META + ctrlChar('H'): + if (pos > 0) { + historyRecallMostRecent = false; + int startingPos = pos; + while (pos > 0 && !isCharacterAlphanumeric(buf32[pos - 1])) { + --pos; + } + while (pos > 0 && isCharacterAlphanumeric(buf32[pos - 1])) { + --pos; + } + killRing.kill(&buf32[pos], startingPos - pos, false); + memmove(buf32 + pos, buf32 + startingPos, + sizeof(char32_t) * (len - startingPos + 1)); + len -= startingPos - pos; + refreshLine(pi); + } + killRing.lastAction = KillRing::actionKill; + break; + + case ctrlChar('J'): // ctrl-J/linefeed/newline, accept line + case ctrlChar('M'): // ctrl-M/return/enter + killRing.lastAction = KillRing::actionOther; + // we need one last refresh with the cursor at the end of the line + // so we don't display the next prompt over the previous input line + pos = len; // pass len as pos for EOL + refreshLine(pi); + historyPreviousIndex = historyRecallMostRecent ? historyIndex : -2; + --historyLen; + free(history[historyLen]); + return len; + + case ctrlChar('K'): // ctrl-K, kill from cursor to end of line + killRing.kill(&buf32[pos], len - pos, true); + buf32[pos] = '\0'; + len = pos; + refreshLine(pi); + killRing.lastAction = KillRing::actionKill; + historyRecallMostRecent = false; + break; + + case ctrlChar('L'): // ctrl-L, clear screen and redisplay line + clearScreen(pi); + break; + + case META + 'l': // meta-L, lowercase word + case META + 'L': + killRing.lastAction = KillRing::actionOther; + if (pos < len) { + historyRecallMostRecent = false; + while (pos < len && !isCharacterAlphanumeric(buf32[pos])) { + ++pos; + } + while (pos < len && isCharacterAlphanumeric(buf32[pos])) { + if (buf32[pos] >= 'A' && buf32[pos] <= 'Z') { + buf32[pos] += 'a' - 'A'; + } + ++pos; + } + refreshLine(pi); + } + break; + + case ctrlChar('N'): // ctrl-N, recall next line in history + case ctrlChar('P'): // ctrl-P, recall previous line in history + case DOWN_ARROW_KEY: + case UP_ARROW_KEY: + killRing.lastAction = KillRing::actionOther; + // if not already recalling, add the current line to the history list so + // we don't + // have to special case it + if (historyIndex == historyLen - 1) { + free(history[historyLen - 1]); + size_t tempBufferSize = sizeof(char32_t) * len + 1; + unique_ptr tempBuffer(new char[tempBufferSize]); + copyString32to8(tempBuffer.get(), tempBufferSize, buf32); + history[historyLen - 1] = strdup8(tempBuffer.get()); + } + if (historyLen > 1) { + if (c == UP_ARROW_KEY) { + c = ctrlChar('P'); + } + if (historyPreviousIndex != -2 && c != ctrlChar('P')) { + historyIndex = + 1 + historyPreviousIndex; // emulate Windows down-arrow + } else { + historyIndex += (c == ctrlChar('P')) ? -1 : 1; + } + historyPreviousIndex = -2; + if (historyIndex < 0) { + historyIndex = 0; + break; + } else if (historyIndex >= historyLen) { + historyIndex = historyLen - 1; + break; + } + historyRecallMostRecent = true; + size_t ucharCount = 0; + copyString8to32(buf32, buflen, ucharCount, history[historyIndex]); + len = pos = static_cast(ucharCount); + refreshLine(pi); + } + break; + + case ctrlChar('R'): // ctrl-R, reverse history search + case ctrlChar('S'): // ctrl-S, forward history search + terminatingKeystroke = incrementalHistorySearch(pi, c); + break; + + case ctrlChar('T'): // ctrl-T, transpose characters + killRing.lastAction = KillRing::actionOther; + if (pos > 0 && len > 1) { + historyRecallMostRecent = false; + size_t leftCharPos = (pos == len) ? pos - 2 : pos - 1; + char32_t aux = buf32[leftCharPos]; + buf32[leftCharPos] = buf32[leftCharPos + 1]; + buf32[leftCharPos + 1] = aux; + if (pos != len) ++pos; + refreshLine(pi); + } + break; + + case ctrlChar( + 'U'): // ctrl-U, kill all characters to the left of the cursor + if (pos > 0) { + historyRecallMostRecent = false; + killRing.kill(&buf32[0], pos, false); + len -= pos; + memmove(buf32, buf32 + pos, sizeof(char32_t) * (len + 1)); + pos = 0; + refreshLine(pi); + } + killRing.lastAction = KillRing::actionKill; + break; + + case META + 'u': // meta-U, uppercase word + case META + 'U': + killRing.lastAction = KillRing::actionOther; + if (pos < len) { + historyRecallMostRecent = false; + while (pos < len && !isCharacterAlphanumeric(buf32[pos])) { + ++pos; + } + while (pos < len && isCharacterAlphanumeric(buf32[pos])) { + if (buf32[pos] >= 'a' && buf32[pos] <= 'z') { + buf32[pos] += 'A' - 'a'; + } + ++pos; + } + refreshLine(pi); + } + break; + + // ctrl-W, kill to whitespace (not word) to left of cursor + case ctrlChar('W'): + if (pos > 0) { + historyRecallMostRecent = false; + int startingPos = pos; + while (pos > 0 && buf32[pos - 1] == ' ') { + --pos; + } + while (pos > 0 && buf32[pos - 1] != ' ') { + --pos; + } + killRing.kill(&buf32[pos], startingPos - pos, false); + memmove(buf32 + pos, buf32 + startingPos, + sizeof(char32_t) * (len - startingPos + 1)); + len -= startingPos - pos; + refreshLine(pi); + } + killRing.lastAction = KillRing::actionKill; + break; + + case ctrlChar('Y'): // ctrl-Y, yank killed text + historyRecallMostRecent = false; + { + Utf32String* restoredText = killRing.yank(); + if (restoredText) { + bool truncated = false; + size_t ucharCount = restoredText->length(); + if (ucharCount > static_cast(buflen - len)) { + ucharCount = buflen - len; + truncated = true; + } + memmove(buf32 + pos + ucharCount, buf32 + pos, + sizeof(char32_t) * (len - pos + 1)); + memmove(buf32 + pos, restoredText->get(), + sizeof(char32_t) * ucharCount); + pos += static_cast(ucharCount); + len += static_cast(ucharCount); refreshLine(pi); - continue; + killRing.lastAction = KillRing::actionYank; + killRing.lastYankSize = ucharCount; + if (truncated) { + beep(); + } + } else { + beep(); + } } + break; - if (c == -2) { - if (! pi.write()) - return -1; + case META + 'y': // meta-Y, "yank-pop", rotate popped text + case META + 'Y': + if (killRing.lastAction == KillRing::actionYank) { + historyRecallMostRecent = false; + Utf32String* restoredText = killRing.yankPop(); + if (restoredText) { + bool truncated = false; + size_t ucharCount = restoredText->length(); + if (ucharCount > + static_cast(killRing.lastYankSize + buflen - len)) { + ucharCount = killRing.lastYankSize + buflen - len; + truncated = true; + } + if (ucharCount > killRing.lastYankSize) { + memmove(buf32 + pos + ucharCount - killRing.lastYankSize, + buf32 + pos, sizeof(char32_t) * (len - pos + 1)); + memmove(buf32 + pos - killRing.lastYankSize, restoredText->get(), + sizeof(char32_t) * ucharCount); + } else { + memmove(buf32 + pos - killRing.lastYankSize, restoredText->get(), + sizeof(char32_t) * ucharCount); + memmove(buf32 + pos + ucharCount - killRing.lastYankSize, + buf32 + pos, sizeof(char32_t) * (len - pos + 1)); + } + pos += static_cast(ucharCount - killRing.lastYankSize); + len += static_cast(ucharCount - killRing.lastYankSize); + killRing.lastYankSize = ucharCount; refreshLine(pi); - continue; + if (truncated) { + beep(); + } + break; + } } - - // ctrl-I/tab, command completion, needs to be before switch statement - if (c == ctrlChar('I') && completionCallback) { - if (pos == 0) // SERVER-4967 -- in earlier versions, you could paste previous output - continue; // back into the shell ... this output may have leading tabs. - // This hack (i.e. what the old code did) prevents command completion - // on an empty line but lets users paste text with leading tabs. - - killRing.lastAction = KillRing::actionOther; - historyRecallMostRecent = false; - - // completeLine does the actual completion and replacement - c = completeLine(pi); - - if (c < 0) // return on error - return len; - - if (c == 0) // read next character when 0 - continue; - - // deliberate fall-through here, so we use the terminating character - } - - switch (c) { - case ctrlChar('A'): // ctrl-A, move cursor to start of line - case HOME_KEY: - killRing.lastAction = KillRing::actionOther; - pos = 0; - refreshLine(pi); - break; - - case ctrlChar('B'): // ctrl-B, move cursor left by one character - case LEFT_ARROW_KEY: - killRing.lastAction = KillRing::actionOther; - if (pos > 0) { - --pos; - refreshLine(pi); - } - break; - - case META + 'b': // meta-B, move cursor left by one word - case META + 'B': - case CTRL + LEFT_ARROW_KEY: - case META + LEFT_ARROW_KEY: // Emacs allows Meta, bash & readline don't - killRing.lastAction = KillRing::actionOther; - if (pos > 0) { - while (pos > 0 && !isCharacterAlphanumeric(buf32[pos - 1])) { - --pos; - } - while (pos > 0 && isCharacterAlphanumeric(buf32[pos - 1])) { - --pos; - } - refreshLine(pi); - } - break; - - case ctrlChar('C'): // ctrl-C, abort this line - killRing.lastAction = KillRing::actionOther; - historyRecallMostRecent = false; - errno = EAGAIN; - --historyLen; - free(history[historyLen]); - // we need one last refresh with the cursor at the end of the line - // so we don't display the next prompt over the previous input line - pos = len; // pass len as pos for EOL - refreshLine(pi); - if (write(1, "^C", 2) == -1) - return -1; // Display the ^C we got - return -1; - - case META + 'c': // meta-C, give word initial Cap - case META + 'C': - killRing.lastAction = KillRing::actionOther; - historyRecallMostRecent = false; - if (pos < len) { - while (pos < len && !isCharacterAlphanumeric(buf32[pos])) { - ++pos; - } - if (pos < len && isCharacterAlphanumeric(buf32[pos])) { - if (buf32[pos] >= 'a' && buf32[pos] <= 'z') { - buf32[pos] += 'A' - 'a'; - } - ++pos; - } - while (pos < len && isCharacterAlphanumeric(buf32[pos])) { - if (buf32[pos] >= 'A' && buf32[pos] <= 'Z') { - buf32[pos] += 'a' - 'A'; - } - ++pos; - } - refreshLine(pi); - } - break; - - // ctrl-D, delete the character under the cursor - // on an empty line, exit the shell - case ctrlChar('D'): - killRing.lastAction = KillRing::actionOther; - if (len > 0 && pos < len) { - historyRecallMostRecent = false; - memmove(buf32 + pos, buf32 + pos + 1, sizeof(char32_t) * (len - pos)); - --len; - refreshLine(pi); - } else if (len == 0) { - --historyLen; - free(history[historyLen]); - return -1; - } - break; - - case META + 'd': // meta-D, kill word to right of cursor - case META + 'D': - if (pos < len) { - historyRecallMostRecent = false; - int endingPos = pos; - while (endingPos < len && !isCharacterAlphanumeric(buf32[endingPos])) { - ++endingPos; - } - while (endingPos < len && isCharacterAlphanumeric(buf32[endingPos])) { - ++endingPos; - } - killRing.kill(&buf32[pos], endingPos - pos, true); - memmove( - buf32 + pos, buf32 + endingPos, sizeof(char32_t) * (len - endingPos + 1)); - len -= endingPos - pos; - refreshLine(pi); - } - killRing.lastAction = KillRing::actionKill; - break; - - case ctrlChar('E'): // ctrl-E, move cursor to end of line - case END_KEY: - killRing.lastAction = KillRing::actionOther; - pos = len; - refreshLine(pi); - break; - - case ctrlChar('F'): // ctrl-F, move cursor right by one character - case RIGHT_ARROW_KEY: - killRing.lastAction = KillRing::actionOther; - if (pos < len) { - ++pos; - refreshLine(pi); - } - break; - - case META + 'f': // meta-F, move cursor right by one word - case META + 'F': - case CTRL + RIGHT_ARROW_KEY: - case META + RIGHT_ARROW_KEY: // Emacs allows Meta, bash & readline don't - killRing.lastAction = KillRing::actionOther; - if (pos < len) { - while (pos < len && !isCharacterAlphanumeric(buf32[pos])) { - ++pos; - } - while (pos < len && isCharacterAlphanumeric(buf32[pos])) { - ++pos; - } - refreshLine(pi); - } - break; - - case ctrlChar('H'): // backspace/ctrl-H, delete char to left of cursor - killRing.lastAction = KillRing::actionOther; - if (pos > 0) { - historyRecallMostRecent = false; - memmove(buf32 + pos - 1, buf32 + pos, sizeof(char32_t) * (1 + len - pos)); - --pos; - --len; - refreshLine(pi); - } - break; - - // meta-Backspace, kill word to left of cursor - case META + ctrlChar('H'): - if (pos > 0) { - historyRecallMostRecent = false; - int startingPos = pos; - while (pos > 0 && !isCharacterAlphanumeric(buf32[pos - 1])) { - --pos; - } - while (pos > 0 && isCharacterAlphanumeric(buf32[pos - 1])) { - --pos; - } - killRing.kill(&buf32[pos], startingPos - pos, false); - memmove(buf32 + pos, - buf32 + startingPos, - sizeof(char32_t) * (len - startingPos + 1)); - len -= startingPos - pos; - refreshLine(pi); - } - killRing.lastAction = KillRing::actionKill; - break; - - case ctrlChar('J'): // ctrl-J/linefeed/newline, accept line - case ctrlChar('M'): // ctrl-M/return/enter - killRing.lastAction = KillRing::actionOther; - // we need one last refresh with the cursor at the end of the line - // so we don't display the next prompt over the previous input line - pos = len; // pass len as pos for EOL - refreshLine(pi); - historyPreviousIndex = historyRecallMostRecent ? historyIndex : -2; - --historyLen; - free(history[historyLen]); - return len; - - case ctrlChar('K'): // ctrl-K, kill from cursor to end of line - killRing.kill(&buf32[pos], len - pos, true); - buf32[pos] = '\0'; - len = pos; - refreshLine(pi); - killRing.lastAction = KillRing::actionKill; - historyRecallMostRecent = false; - break; - - case ctrlChar('L'): // ctrl-L, clear screen and redisplay line - clearScreen(pi); - break; - - case META + 'l': // meta-L, lowercase word - case META + 'L': - killRing.lastAction = KillRing::actionOther; - if (pos < len) { - historyRecallMostRecent = false; - while (pos < len && !isCharacterAlphanumeric(buf32[pos])) { - ++pos; - } - while (pos < len && isCharacterAlphanumeric(buf32[pos])) { - if (buf32[pos] >= 'A' && buf32[pos] <= 'Z') { - buf32[pos] += 'a' - 'A'; - } - ++pos; - } - refreshLine(pi); - } - break; - - case ctrlChar('N'): // ctrl-N, recall next line in history - case ctrlChar('P'): // ctrl-P, recall previous line in history - case DOWN_ARROW_KEY: - case UP_ARROW_KEY: - killRing.lastAction = KillRing::actionOther; - // if not already recalling, add the current line to the history list so we don't - // have to special case it - if (historyIndex == historyLen - 1) { - free(history[historyLen - 1]); - size_t tempBufferSize = sizeof(char32_t) * len + 1; - unique_ptr tempBuffer(new char[tempBufferSize]); - copyString32to8(tempBuffer.get(), tempBufferSize, buf32); - history[historyLen - 1] = strdup8(tempBuffer.get()); - } - if (historyLen > 1) { - if (c == UP_ARROW_KEY) { - c = ctrlChar('P'); - } - if (historyPreviousIndex != -2 && c != ctrlChar('P')) { - historyIndex = 1 + historyPreviousIndex; // emulate Windows down-arrow - } else { - historyIndex += (c == ctrlChar('P')) ? -1 : 1; - } - historyPreviousIndex = -2; - if (historyIndex < 0) { - historyIndex = 0; - break; - } else if (historyIndex >= historyLen) { - historyIndex = historyLen - 1; - break; - } - historyRecallMostRecent = true; - size_t ucharCount = 0; - copyString8to32(buf32, buflen, ucharCount, history[historyIndex]); - len = pos = static_cast(ucharCount); - refreshLine(pi); - } - break; - - case ctrlChar('R'): // ctrl-R, reverse history search - case ctrlChar('S'): // ctrl-S, forward history search - terminatingKeystroke = incrementalHistorySearch(pi, c); - break; - - case ctrlChar('T'): // ctrl-T, transpose characters - killRing.lastAction = KillRing::actionOther; - if (pos > 0 && len > 1) { - historyRecallMostRecent = false; - size_t leftCharPos = (pos == len) ? pos - 2 : pos - 1; - char32_t aux = buf32[leftCharPos]; - buf32[leftCharPos] = buf32[leftCharPos + 1]; - buf32[leftCharPos + 1] = aux; - if (pos != len) - ++pos; - refreshLine(pi); - } - break; - - case ctrlChar('U'): // ctrl-U, kill all characters to the left of the cursor - if (pos > 0) { - historyRecallMostRecent = false; - killRing.kill(&buf32[0], pos, false); - len -= pos; - memmove(buf32, buf32 + pos, sizeof(char32_t) * (len + 1)); - pos = 0; - refreshLine(pi); - } - killRing.lastAction = KillRing::actionKill; - break; - - case META + 'u': // meta-U, uppercase word - case META + 'U': - killRing.lastAction = KillRing::actionOther; - if (pos < len) { - historyRecallMostRecent = false; - while (pos < len && !isCharacterAlphanumeric(buf32[pos])) { - ++pos; - } - while (pos < len && isCharacterAlphanumeric(buf32[pos])) { - if (buf32[pos] >= 'a' && buf32[pos] <= 'z') { - buf32[pos] += 'A' - 'a'; - } - ++pos; - } - refreshLine(pi); - } - break; - - // ctrl-W, kill to whitespace (not word) to left of cursor - case ctrlChar('W'): - if (pos > 0) { - historyRecallMostRecent = false; - int startingPos = pos; - while (pos > 0 && buf32[pos - 1] == ' ') { - --pos; - } - while (pos > 0 && buf32[pos - 1] != ' ') { - --pos; - } - killRing.kill(&buf32[pos], startingPos - pos, false); - memmove(buf32 + pos, - buf32 + startingPos, - sizeof(char32_t) * (len - startingPos + 1)); - len -= startingPos - pos; - refreshLine(pi); - } - killRing.lastAction = KillRing::actionKill; - break; - - case ctrlChar('Y'): // ctrl-Y, yank killed text - historyRecallMostRecent = false; - { - Utf32String* restoredText = killRing.yank(); - if (restoredText) { - bool truncated = false; - size_t ucharCount = restoredText->length(); - if (ucharCount > static_cast(buflen - len)) { - ucharCount = buflen - len; - truncated = true; - } - memmove(buf32 + pos + ucharCount, - buf32 + pos, - sizeof(char32_t) * (len - pos + 1)); - memmove(buf32 + pos, restoredText->get(), sizeof(char32_t) * ucharCount); - pos += static_cast(ucharCount); - len += static_cast(ucharCount); - refreshLine(pi); - killRing.lastAction = KillRing::actionYank; - killRing.lastYankSize = ucharCount; - if (truncated) { - beep(); - } - } else { - beep(); - } - } - break; - - case META + 'y': // meta-Y, "yank-pop", rotate popped text - case META + 'Y': - if (killRing.lastAction == KillRing::actionYank) { - historyRecallMostRecent = false; - Utf32String* restoredText = killRing.yankPop(); - if (restoredText) { - bool truncated = false; - size_t ucharCount = restoredText->length(); - if (ucharCount > - static_cast(killRing.lastYankSize + buflen - len)) { - ucharCount = killRing.lastYankSize + buflen - len; - truncated = true; - } - if (ucharCount > killRing.lastYankSize) { - memmove(buf32 + pos + ucharCount - killRing.lastYankSize, - buf32 + pos, - sizeof(char32_t) * (len - pos + 1)); - memmove(buf32 + pos - killRing.lastYankSize, - restoredText->get(), - sizeof(char32_t) * ucharCount); - } else { - memmove(buf32 + pos - killRing.lastYankSize, - restoredText->get(), - sizeof(char32_t) * ucharCount); - memmove(buf32 + pos + ucharCount - killRing.lastYankSize, - buf32 + pos, - sizeof(char32_t) * (len - pos + 1)); - } - pos += static_cast(ucharCount - killRing.lastYankSize); - len += static_cast(ucharCount - killRing.lastYankSize); - killRing.lastYankSize = ucharCount; - refreshLine(pi); - if (truncated) { - beep(); - } - break; - } - } - beep(); - break; + beep(); + break; #ifndef _WIN32 - case ctrlChar('Z'): // ctrl-Z, job control - disableRawMode(); // Returning to Linux (whatever) shell, leave raw mode - raise(SIGSTOP); // Break out in mid-line - enableRawMode(); // Back from Linux shell, re-enter raw mode - if (! pi.write()) - break; // Redraw prompt - refreshLine(pi); // Refresh the line - break; + case ctrlChar('Z'): // ctrl-Z, job control + disableRawMode(); // Returning to Linux (whatever) shell, leave raw + // mode + raise(SIGSTOP); // Break out in mid-line + enableRawMode(); // Back from Linux shell, re-enter raw mode + if (!pi.write()) break; // Redraw prompt + refreshLine(pi); // Refresh the line + break; #endif - // DEL, delete the character under the cursor - case 127: - case DELETE_KEY: - killRing.lastAction = KillRing::actionOther; - if (len > 0 && pos < len) { - historyRecallMostRecent = false; - memmove(buf32 + pos, buf32 + pos + 1, sizeof(char32_t) * (len - pos)); - --len; - refreshLine(pi); - } - break; - - case META + '<': // meta-<, beginning of history - case PAGE_UP_KEY: // Page Up, beginning of history - case META + '>': // meta->, end of history - case PAGE_DOWN_KEY: // Page Down, end of history - killRing.lastAction = KillRing::actionOther; - // if not already recalling, add the current line to the history list so we don't - // have to special case it - if (historyIndex == historyLen - 1) { - free(history[historyLen - 1]); - size_t tempBufferSize = sizeof(char32_t) * len + 1; - unique_ptr tempBuffer(new char[tempBufferSize]); - copyString32to8(tempBuffer.get(), tempBufferSize, buf32); - history[historyLen - 1] = strdup8(tempBuffer.get()); - } - if (historyLen > 1) { - historyIndex = (c == META + '<' || c == PAGE_UP_KEY) ? 0 : historyLen - 1; - historyPreviousIndex = -2; - historyRecallMostRecent = true; - size_t ucharCount = 0; - copyString8to32(buf32, buflen, ucharCount, history[historyIndex]); - len = pos = static_cast(ucharCount); - refreshLine(pi); - } - break; - - // not one of our special characters, maybe insert it in the buffer - default: - killRing.lastAction = KillRing::actionOther; - historyRecallMostRecent = false; - if (c & (META | CTRL)) { // beep on unknown Ctrl and/or Meta keys - beep(); - break; - } - if (len < buflen) { - if (isControlChar(c)) { // don't insert control characters - beep(); - break; - } - if (len == pos) { // at end of buffer - buf32[pos] = c; - ++pos; - ++len; - buf32[len] = '\0'; - int inputLen = calculateColumnPosition(buf32, len); - if (pi.promptIndentation + inputLen < pi.promptScreenColumns) { - if (inputLen > pi.promptPreviousInputLen) - pi.promptPreviousInputLen = inputLen; - /* Avoid a full update of the line in the - * trivial case. */ - if (write32(1, reinterpret_cast(&c), 1) == -1) - return -1; - } else { - refreshLine(pi); - } - } else { // not at end of buffer, have to move characters to our right - memmove(buf32 + pos + 1, buf32 + pos, sizeof(char32_t) * (len - pos)); - buf32[pos] = c; - ++len; - ++pos; - buf32[len] = '\0'; - refreshLine(pi); - } - } else { - beep(); // buffer is full, beep on new characters - } - break; + // DEL, delete the character under the cursor + case 127: + case DELETE_KEY: + killRing.lastAction = KillRing::actionOther; + if (len > 0 && pos < len) { + historyRecallMostRecent = false; + memmove(buf32 + pos, buf32 + pos + 1, sizeof(char32_t) * (len - pos)); + --len; + refreshLine(pi); } + break; + + case META + '<': // meta-<, beginning of history + case PAGE_UP_KEY: // Page Up, beginning of history + case META + '>': // meta->, end of history + case PAGE_DOWN_KEY: // Page Down, end of history + killRing.lastAction = KillRing::actionOther; + // if not already recalling, add the current line to the history list so + // we don't + // have to special case it + if (historyIndex == historyLen - 1) { + free(history[historyLen - 1]); + size_t tempBufferSize = sizeof(char32_t) * len + 1; + unique_ptr tempBuffer(new char[tempBufferSize]); + copyString32to8(tempBuffer.get(), tempBufferSize, buf32); + history[historyLen - 1] = strdup8(tempBuffer.get()); + } + if (historyLen > 1) { + historyIndex = + (c == META + '<' || c == PAGE_UP_KEY) ? 0 : historyLen - 1; + historyPreviousIndex = -2; + historyRecallMostRecent = true; + size_t ucharCount = 0; + copyString8to32(buf32, buflen, ucharCount, history[historyIndex]); + len = pos = static_cast(ucharCount); + refreshLine(pi); + } + break; + + // not one of our special characters, maybe insert it in the buffer + default: + killRing.lastAction = KillRing::actionOther; + historyRecallMostRecent = false; + if (c & (META | CTRL)) { // beep on unknown Ctrl and/or Meta keys + beep(); + break; + } + if (len < buflen) { + if (isControlChar(c)) { // don't insert control characters + beep(); + break; + } + if (len == pos) { // at end of buffer + buf32[pos] = c; + ++pos; + ++len; + buf32[len] = '\0'; + int inputLen = calculateColumnPosition(buf32, len); + if (pi.promptIndentation + inputLen < pi.promptScreenColumns) { + if (inputLen > pi.promptPreviousInputLen) + pi.promptPreviousInputLen = inputLen; + /* Avoid a full update of the line in the + * trivial case. */ + if (write32(1, reinterpret_cast(&c), 1) == -1) + return -1; + } else { + refreshLine(pi); + } + } else { // not at end of buffer, have to move characters to our + // right + memmove(buf32 + pos + 1, buf32 + pos, + sizeof(char32_t) * (len - pos)); + buf32[pos] = c; + ++len; + ++pos; + buf32[len] = '\0'; + refreshLine(pi); + } + } else { + beep(); // buffer is full, beep on new characters + } + break; } - return len; + } + return len; } static string preloadedBufferContents; // used with linenoisePreloadBuffer @@ -2884,229 +3035,248 @@ static string preloadErrorMessage; * @param preloadText text to begin with on the next call to linenoise() */ void linenoisePreloadBuffer(const char* preloadText) { - if (!preloadText) { - return; - } - int bufferSize = static_cast(strlen(preloadText) + 1); - unique_ptr tempBuffer(new char[bufferSize]); - strncpy(&tempBuffer[0], preloadText, bufferSize); + if (!preloadText) { + return; + } + int bufferSize = static_cast(strlen(preloadText) + 1); + unique_ptr tempBuffer(new char[bufferSize]); + strncpy(&tempBuffer[0], preloadText, bufferSize); - // remove characters that won't display correctly - char* pIn = &tempBuffer[0]; - char* pOut = pIn; - bool controlsStripped = false; - bool whitespaceSeen = false; - while (*pIn) { - unsigned char c = *pIn++; // we need unsigned so chars 0x80 and above are allowed - if ('\r' == c) { // silently skip CR - continue; - } - if ('\n' == c || '\t' == c) { // note newline or tab - whitespaceSeen = true; - continue; - } - if (isControlChar(c)) { // remove other control characters, flag for message - controlsStripped = true; - *pOut++ = ' '; - continue; - } - if (whitespaceSeen) { // convert whitespace to a single space - *pOut++ = ' '; - whitespaceSeen = false; - } - *pOut++ = c; + // remove characters that won't display correctly + char* pIn = &tempBuffer[0]; + char* pOut = pIn; + bool controlsStripped = false; + bool whitespaceSeen = false; + while (*pIn) { + unsigned char c = + *pIn++; // we need unsigned so chars 0x80 and above are allowed + if ('\r' == c) { // silently skip CR + continue; } - *pOut = 0; - int processedLength = static_cast(pOut - tempBuffer.get()); - bool lineTruncated = false; - if (processedLength > (LINENOISE_MAX_LINE - 1)) { - lineTruncated = true; - tempBuffer[LINENOISE_MAX_LINE - 1] = 0; + if ('\n' == c || '\t' == c) { // note newline or tab + whitespaceSeen = true; + continue; } - preloadedBufferContents = tempBuffer.get(); - if (controlsStripped) { - preloadErrorMessage += " [Edited line: control characters were converted to spaces]\n"; + if (isControlChar( + c)) { // remove other control characters, flag for message + controlsStripped = true; + *pOut++ = ' '; + continue; } - if (lineTruncated) { - preloadErrorMessage += " [Edited line: the line length was reduced from "; - char buf[128]; - snprintf(buf, sizeof(buf), "%d to %d]\n", processedLength, (LINENOISE_MAX_LINE - 1)); - preloadErrorMessage += buf; + if (whitespaceSeen) { // convert whitespace to a single space + *pOut++ = ' '; + whitespaceSeen = false; } + *pOut++ = c; + } + *pOut = 0; + int processedLength = static_cast(pOut - tempBuffer.get()); + bool lineTruncated = false; + if (processedLength > (LINENOISE_MAX_LINE - 1)) { + lineTruncated = true; + tempBuffer[LINENOISE_MAX_LINE - 1] = 0; + } + preloadedBufferContents = tempBuffer.get(); + if (controlsStripped) { + preloadErrorMessage += + " [Edited line: control characters were converted to spaces]\n"; + } + if (lineTruncated) { + preloadErrorMessage += " [Edited line: the line length was reduced from "; + char buf[128]; + snprintf(buf, sizeof(buf), "%d to %d]\n", processedLength, + (LINENOISE_MAX_LINE - 1)); + preloadErrorMessage += buf; + } } /** * linenoise is a readline replacement. * - * call it with a prompt to display and it will return a line of input from the user + * call it with a prompt to display and it will return a line of input from the + * user * * @param prompt text of prompt to display to the user - * @return the returned string belongs to the caller on return and must be freed to prevent + * @return the returned string belongs to the caller on return and must be + * freed to prevent * memory leaks */ char* linenoise(const char* prompt) { #ifndef _WIN32 - gotResize = false; + gotResize = false; #endif - if (isatty(STDIN_FILENO)) { // input is from a terminal - char32_t buf32[LINENOISE_MAX_LINE]; - char charWidths[LINENOISE_MAX_LINE]; - if (!preloadErrorMessage.empty()) { - printf("%s", preloadErrorMessage.c_str()); - fflush(stdout); - preloadErrorMessage.clear(); - } - PromptInfo pi(prompt, getScreenColumns()); - if (isUnsupportedTerm()) { - if (! pi.write()) - return 0; - fflush(stdout); - if (preloadedBufferContents.empty()) { - unique_ptr buf8(new char[LINENOISE_MAX_LINE]); - if (fgets(buf8.get(), LINENOISE_MAX_LINE, stdin) == NULL) { - return NULL; - } - size_t len = strlen(buf8.get()); - while (len && (buf8[len - 1] == '\n' || buf8[len - 1] == '\r')) { - --len; - buf8[len] = '\0'; - } - return strdup(buf8.get()); // caller must free buffer - } else { - char* buf8 = strdup(preloadedBufferContents.c_str()); - preloadedBufferContents.clear(); - return buf8; // caller must free buffer - } - } else { - if (enableRawMode() == -1) { - return NULL; - } - InputBuffer ib(buf32, charWidths, LINENOISE_MAX_LINE); - if (!preloadedBufferContents.empty()) { - ib.preloadBuffer(preloadedBufferContents.c_str()); - preloadedBufferContents.clear(); - } - int count = ib.getInputLine(pi); - disableRawMode(); - printf("\n"); - if (count == -1) { - return NULL; - } - size_t bufferSize = sizeof(char32_t) * ib.length() + 1; - unique_ptr buf8(new char[bufferSize]); - copyString32to8(buf8.get(), bufferSize, buf32); - return strdup(buf8.get()); // caller must free buffer - } - } else { // input not from a terminal, we should work with piped input, i.e. redirected stdin + if (isatty(STDIN_FILENO)) { // input is from a terminal + char32_t buf32[LINENOISE_MAX_LINE]; + char charWidths[LINENOISE_MAX_LINE]; + if (!preloadErrorMessage.empty()) { + printf("%s", preloadErrorMessage.c_str()); + fflush(stdout); + preloadErrorMessage.clear(); + } + PromptInfo pi(prompt, getScreenColumns()); + if (isUnsupportedTerm()) { + if (!pi.write()) return 0; + fflush(stdout); + if (preloadedBufferContents.empty()) { unique_ptr buf8(new char[LINENOISE_MAX_LINE]); if (fgets(buf8.get(), LINENOISE_MAX_LINE, stdin) == NULL) { - return NULL; + return NULL; } - - // if fgets() gave us the newline, remove it - int count = static_cast(strlen(buf8.get())); - if (count > 0 && buf8[count - 1] == '\n') { - --count; - buf8[count] = '\0'; + size_t len = strlen(buf8.get()); + while (len && (buf8[len - 1] == '\n' || buf8[len - 1] == '\r')) { + --len; + buf8[len] = '\0'; } return strdup(buf8.get()); // caller must free buffer + } else { + char* buf8 = strdup(preloadedBufferContents.c_str()); + preloadedBufferContents.clear(); + return buf8; // caller must free buffer + } + } else { + if (enableRawMode() == -1) { + return NULL; + } + InputBuffer ib(buf32, charWidths, LINENOISE_MAX_LINE); + if (!preloadedBufferContents.empty()) { + ib.preloadBuffer(preloadedBufferContents.c_str()); + preloadedBufferContents.clear(); + } + int count = ib.getInputLine(pi); + disableRawMode(); + printf("\n"); + if (count == -1) { + return NULL; + } + size_t bufferSize = sizeof(char32_t) * ib.length() + 1; + unique_ptr buf8(new char[bufferSize]); + copyString32to8(buf8.get(), bufferSize, buf32); + return strdup(buf8.get()); // caller must free buffer } + } else { // input not from a terminal, we should work with piped input, i.e. + // redirected stdin + unique_ptr buf8(new char[LINENOISE_MAX_LINE]); + if (fgets(buf8.get(), LINENOISE_MAX_LINE, stdin) == NULL) { + return NULL; + } + + // if fgets() gave us the newline, remove it + int count = static_cast(strlen(buf8.get())); + if (count > 0 && buf8[count - 1] == '\n') { + --count; + buf8[count] = '\0'; + } + return strdup(buf8.get()); // caller must free buffer + } } /* Register a callback function to be called for tab-completion. */ void linenoiseSetCompletionCallback(linenoiseCompletionCallback* fn) { - completionCallback = fn; + completionCallback = fn; } void linenoiseAddCompletion(linenoiseCompletions* lc, const char* str) { - lc->completionStrings.push_back(Utf32String(str)); + lc->completionStrings.push_back(Utf32String(str)); } int linenoiseHistoryAdd(const char* line) { - if (historyMaxLen == 0) { - return 0; - } + if (historyMaxLen == 0) { + return 0; + } + if (history == NULL) { + history = + reinterpret_cast(malloc(sizeof(char8_t*) * historyMaxLen)); if (history == NULL) { - history = reinterpret_cast(malloc(sizeof(char8_t*) * historyMaxLen)); - if (history == NULL) { - return 0; - } - memset(history, 0, (sizeof(char*) * historyMaxLen)); - } - char8_t* linecopy = strdup8(line); - if (!linecopy) { - return 0; - } - - // convert newlines in multi-line code to spaces before storing - char8_t* p = linecopy; - while (*p) { - if (*p == '\n') { - *p = ' '; - } - ++p; - } - - // prevent duplicate history entries - if (historyLen > 0 && history[historyLen - 1] != nullptr && - strcmp(reinterpret_cast(history[historyLen - 1]), reinterpret_cast(linecopy)) == 0) { - free(linecopy); - return 0; + return 0; } + memset(history, 0, (sizeof(char*) * historyMaxLen)); + } + char8_t* linecopy = strdup8(line); + if (!linecopy) { + return 0; + } - if (historyLen == historyMaxLen) { - free(history[0]); - memmove(history, history + 1, sizeof(char*) * (historyMaxLen - 1)); - --historyLen; - if (--historyPreviousIndex < -1) { - historyPreviousIndex = -2; - } + // convert newlines in multi-line code to spaces before storing + char8_t* p = linecopy; + while (*p) { + if (*p == '\n') { + *p = ' '; } + ++p; + } - history[historyLen] = linecopy; - ++historyLen; - return 1; + // prevent duplicate history entries + if (historyLen > 0 && history[historyLen - 1] != nullptr && + strcmp(reinterpret_cast(history[historyLen - 1]), + reinterpret_cast(linecopy)) == 0) { + free(linecopy); + return 0; + } + + if (historyLen == historyMaxLen) { + free(history[0]); + memmove(history, history + 1, sizeof(char*) * (historyMaxLen - 1)); + --historyLen; + if (--historyPreviousIndex < -1) { + historyPreviousIndex = -2; + } + } + + history[historyLen] = linecopy; + ++historyLen; + return 1; } int linenoiseHistorySetMaxLen(int len) { - if (len < 1) { - return 0; + if (len < 1) { + return 0; + } + if (history) { + int tocopy = historyLen; + char8_t** newHistory = + reinterpret_cast(malloc(sizeof(char8_t*) * len)); + if (newHistory == NULL) { + return 0; } - if (history) { - int tocopy = historyLen; - char8_t** newHistory = reinterpret_cast(malloc(sizeof(char8_t*) * len)); - if (newHistory == NULL) { - return 0; - } - if (len < tocopy) { - tocopy = len; - } - memcpy(newHistory, history + historyMaxLen - tocopy, sizeof(char8_t*) * tocopy); - free(history); - history = newHistory; + if (len < tocopy) { + tocopy = len; } - historyMaxLen = len; - if (historyLen > historyMaxLen) { - historyLen = historyMaxLen; - } - return 1; + memcpy(newHistory, history + historyMaxLen - tocopy, + sizeof(char8_t*) * tocopy); + free(history); + history = newHistory; + } + historyMaxLen = len; + if (historyLen > historyMaxLen) { + historyLen = historyMaxLen; + } + return 1; +} + +/* Fetch a line of the history by (zero-based) index. If the requested + * line does not exist, NULL is returned. The return value is a heap-allocated + * copy of the line, and the caller is responsible for de-allocating it. */ +char* linenoiseHistoryLine(int index) { + if (index < 0 || index >= historyLen) return NULL; + + return strdup(reinterpret_cast(history[index])); } /* Save the history in the specified file. On success 0 is returned * otherwise -1 is returned. */ int linenoiseHistorySave(const char* filename) { - FILE* fp = fopen(filename, "wt"); - if (fp == NULL) { - return -1; - } + FILE* fp = fopen(filename, "wt"); + if (fp == NULL) { + return -1; + } - for (int j = 0; j < historyLen; ++j) { - if (history[j][0] != '\0') { - fprintf(fp, "%s\n", history[j]); - } + for (int j = 0; j < historyLen; ++j) { + if (history[j][0] != '\0') { + fprintf(fp, "%s\n", history[j]); } - fclose(fp); - return 0; + } + fclose(fp); + return 0; } /* Load the history from the specified file. If the file does not exist @@ -3115,45 +3285,82 @@ int linenoiseHistorySave(const char* filename) { * If the file exists and the operation succeeded 0 is returned, otherwise * on error -1 is returned. */ int linenoiseHistoryLoad(const char* filename) { - FILE* fp = fopen(filename, "rt"); - if (fp == NULL) { - return -1; - } + FILE* fp = fopen(filename, "rt"); + if (fp == NULL) { + return -1; + } - char buf[LINENOISE_MAX_LINE]; - while (fgets(buf, LINENOISE_MAX_LINE, fp) != NULL) { - char* p = strchr(buf, '\r'); - if (!p) { - p = strchr(buf, '\n'); - } - if (p) { - *p = '\0'; - } - if (p != buf) { - linenoiseHistoryAdd(buf); - } + char buf[LINENOISE_MAX_LINE]; + while (fgets(buf, LINENOISE_MAX_LINE, fp) != NULL) { + char* p = strchr(buf, '\r'); + if (!p) { + p = strchr(buf, '\n'); } - fclose(fp); - return 0; + if (p) { + *p = '\0'; + } + if (p != buf) { + linenoiseHistoryAdd(buf); + } + } + fclose(fp); + return 0; +} + +/* Set if to use or not the multi line mode. */ +/* note that this is a stub only, as linenoise-ng always multi-line */ +void linenoiseSetMultiLine(int) {} + +/* This special mode is used by linenoise in order to print scan codes + * on screen for debugging / development purposes. It is implemented + * by the linenoise_example program using the --keycodes option. */ +void linenoisePrintKeyCodes(void) { + char quit[4]; + + printf( + "Linenoise key codes debugging mode.\n" + "Press keys to see scan codes. Type 'quit' at any time to exit.\n"); + if (enableRawMode() == -1) return; + memset(quit, ' ', 4); + while (1) { + char c; + int nread; + +#if _WIN32 + nread = _read(STDIN_FILENO, &c, 1); +#else + nread = read(STDIN_FILENO, &c, 1); +#endif + if (nread <= 0) continue; + memmove(quit, quit + 1, sizeof(quit) - 1); /* shift string to left. */ + quit[sizeof(quit) - 1] = c; /* Insert current char on the right. */ + if (memcmp(quit, "quit", sizeof(quit)) == 0) break; + + printf("'%c' %02x (%d) (type quit to exit)\n", isprint(c) ? c : '?', (int)c, + (int)c); + printf("\r"); /* Go left edge manually, we are in raw mode. */ + fflush(stdout); + } + disableRawMode(); } #ifndef _WIN32 static void WindowSizeChanged(int) { - // do nothing here but setting this flag - gotResize = true; -} + // do nothing here but setting this flag + gotResize = true; +} #endif int linenoiseInstallWindowChangeHandler(void) { #ifndef _WIN32 - struct sigaction sa; - sigemptyset(&sa.sa_mask); - sa.sa_flags = 0; - sa.sa_handler = &WindowSizeChanged; + struct sigaction sa; + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; + sa.sa_handler = &WindowSizeChanged; - if (sigaction(SIGWINCH, &sa, nullptr) == -1) { - return errno; - } + if (sigaction(SIGWINCH, &sa, nullptr) == -1) { + return errno; + } #endif - return 0; + return 0; } diff --git a/3rdParty/linenoise-ng/tst/example.c b/3rdParty/linenoise-ng/tst/example.c index 66bed7b720..59792f893a 100644 --- a/3rdParty/linenoise-ng/tst/example.c +++ b/3rdParty/linenoise-ng/tst/example.c @@ -18,7 +18,18 @@ void completionHook (char const* prefix, linenoiseCompletions* lc) { } } -int main () { +int main (int argc, char** argv) { + linenoiseInstallWindowChangeHandler(); + + while(argc > 1) { + argc--; + argv++; + if (!strcmp(*argv, "--keycodes")) { + linenoisePrintKeyCodes(); + exit(0); + } + } + const char* file = "./history"; linenoiseHistoryLoad(file); @@ -26,13 +37,21 @@ int main () { printf("starting...\n"); - char const* prompt = "linenoise> "; + char const* prompt = "\x1b[1;32mlinenoise\x1b[0m> "; while (1) { char* result = linenoise(prompt); if (result == NULL) { break; + } else if (!strncmp(result, "/history", 8)) { + /* Display the current history. */ + for (int index = 0; ; ++index) { + char* hist = linenoiseHistoryLine(index); + if (hist == NULL) break; + printf("%4d: %s\n", index, hist); + free(hist); + } } if (*result == '\0') { free(result); @@ -43,7 +62,7 @@ int main () { linenoiseHistoryAdd(result); free(result); } - + linenoiseHistorySave(file); linenoiseHistoryFree(); } From 53749a8e6d6062dcaef84e16840427a5b959a47d Mon Sep 17 00:00:00 2001 From: Frank Celler Date: Tue, 15 Mar 2016 22:15:25 +0100 Subject: [PATCH 10/20] renamed feature --- .../{ArangobFeature.cpp => BenchFeature.cpp} | 32 ++++++++--------- .../{ArangobFeature.h => BenchFeature.h} | 4 +-- arangosh/Benchmark/arangob.cpp | 6 ++-- arangosh/CMakeLists.txt | 10 +++--- ...{ArangodumpFeature.cpp => DumpFeature.cpp} | 36 +++++++++---------- .../{ArangodumpFeature.h => DumpFeature.h} | 4 +-- arangosh/Dump/arangodump.cpp | 6 ++-- ...ArangoimpFeature.cpp => ImportFeature.cpp} | 20 +++++------ .../{ArangoimpFeature.h => ImportFeature.h} | 4 +-- arangosh/Import/arangoimp.cpp | 6 ++-- ...orestoreFeature.cpp => RestoreFeature.cpp} | 30 ++++++++-------- ...rangorestoreFeature.h => RestoreFeature.h} | 4 +-- arangosh/Restore/arangorestore.cpp | 6 ++-- .../{ArangoshFeature.cpp => ShellFeature.cpp} | 26 +++++++------- .../{ArangoshFeature.h => ShellFeature.h} | 4 +-- arangosh/Shell/V8ClientConnection.cpp | 2 +- arangosh/Shell/V8ShellFeature.cpp | 34 +++++++++--------- arangosh/Shell/V8ShellFeature.h | 6 ++-- arangosh/Shell/arangosh.cpp | 6 ++-- lib/ApplicationFeatures/ClientFeature.cpp | 6 ++-- lib/ApplicationFeatures/ConfigFeature.cpp | 4 +-- lib/ApplicationFeatures/ConsoleFeature.cpp | 4 +-- lib/ApplicationFeatures/DatabaseFeature.cpp | 4 +-- lib/ApplicationFeatures/LanguageFeature.cpp | 4 +-- lib/ApplicationFeatures/LoggerFeature.cpp | 2 +- lib/ApplicationFeatures/ShutdownFeature.cpp | 4 +-- lib/ApplicationFeatures/SslFeature.cpp | 4 +-- lib/ApplicationFeatures/TempFeature.cpp | 4 +-- lib/ApplicationFeatures/V8PlatformFeature.cpp | 4 +-- 29 files changed, 143 insertions(+), 143 deletions(-) rename arangosh/Benchmark/{ArangobFeature.cpp => BenchFeature.cpp} (92%) rename arangosh/Benchmark/{ArangobFeature.h => BenchFeature.h} (93%) rename arangosh/Dump/{ArangodumpFeature.cpp => DumpFeature.cpp} (97%) rename arangosh/Dump/{ArangodumpFeature.h => DumpFeature.h} (94%) rename arangosh/Import/{ArangoimpFeature.cpp => ImportFeature.cpp} (96%) rename arangosh/Import/{ArangoimpFeature.h => ImportFeature.h} (92%) rename arangosh/Restore/{ArangorestoreFeature.cpp => RestoreFeature.cpp} (96%) rename arangosh/Restore/{ArangorestoreFeature.h => RestoreFeature.h} (96%) rename arangosh/Shell/{ArangoshFeature.cpp => ShellFeature.cpp} (89%) rename arangosh/Shell/{ArangoshFeature.h => ShellFeature.h} (91%) diff --git a/arangosh/Benchmark/ArangobFeature.cpp b/arangosh/Benchmark/BenchFeature.cpp similarity index 92% rename from arangosh/Benchmark/ArangobFeature.cpp rename to arangosh/Benchmark/BenchFeature.cpp index 69ea33334c..27561807af 100644 --- a/arangosh/Benchmark/ArangobFeature.cpp +++ b/arangosh/Benchmark/BenchFeature.cpp @@ -20,7 +20,7 @@ /// @author Jan Steemann //////////////////////////////////////////////////////////////////////////////// -#include "ArangobFeature.h" +#include "BenchFeature.h" #include @@ -48,12 +48,12 @@ using namespace arangodb::rest; /// We use an evil global pointer here. //////////////////////////////////////////////////////////////////////////////// -ArangobFeature* ARANGOB; +BenchFeature* ARANGOB; #include "Benchmark/test-cases.h" -ArangobFeature::ArangobFeature(application_features::ApplicationServer* server, +BenchFeature::BenchFeature(application_features::ApplicationServer* server, int* result) - : ApplicationFeature(server, "ArangobFeature"), + : ApplicationFeature(server, "Bench"), _async(false), _concurreny(1), _operations(1000), @@ -69,12 +69,12 @@ ArangobFeature::ArangobFeature(application_features::ApplicationServer* server, _result(result) { requiresElevatedPrivileges(false); setOptional(false); - startsAfter("ClientFeature"); - startsAfter("ConfigFeature"); - startsAfter("LoggerFeature"); + startsAfter("Client"); + startsAfter("Config"); + startsAfter("Logger"); } -void ArangobFeature::collectOptions(std::shared_ptr options) { +void BenchFeature::collectOptions(std::shared_ptr options) { LOG_TOPIC(TRACE, Logger::STARTUP) << name() << "::collectOptions"; options->addSection( @@ -143,23 +143,23 @@ void ArangobFeature::collectOptions(std::shared_ptr options) { new BooleanParameter(&_quiet, false)); } -void ArangobFeature::status(std::string const& value) { +void BenchFeature::status(std::string const& value) { if (!_quiet) { std::cout << value << std::endl; } } -std::atomic ArangobFeature::_started; +std::atomic BenchFeature::_started; -void ArangobFeature::updateStartCounter() { ++_started; } +void BenchFeature::updateStartCounter() { ++_started; } -int ArangobFeature::getStartCounter() { return _started; } +int BenchFeature::getStartCounter() { return _started; } -void ArangobFeature::start() { +void BenchFeature::start() { LOG_TOPIC(TRACE, Logger::STARTUP) << name() << "::start"; ClientFeature* client = - dynamic_cast(server()->feature("ClientFeature")); + dynamic_cast(server()->feature("Client")); client->setRetries(3); client->setWarn(true); @@ -206,7 +206,7 @@ void ArangobFeature::start() { endpoints.push_back(endpoint); BenchmarkThread* thread = new BenchmarkThread( - benchmark.get(), &startCondition, &ArangobFeature::updateStartCounter, + benchmark.get(), &startCondition, &BenchFeature::updateStartCounter, i, (unsigned long)_batchSize, &operationsCounter, client, _keepAlive, _async, _verbose); @@ -321,7 +321,7 @@ void ArangobFeature::start() { *_result = ret; } -void ArangobFeature::stop() { +void BenchFeature::stop() { LOG_TOPIC(TRACE, Logger::STARTUP) << name() << "::stop"; ARANGOB = nullptr; diff --git a/arangosh/Benchmark/ArangobFeature.h b/arangosh/Benchmark/BenchFeature.h similarity index 93% rename from arangosh/Benchmark/ArangobFeature.h rename to arangosh/Benchmark/BenchFeature.h index 273cb6f224..0280ffe11c 100644 --- a/arangosh/Benchmark/ArangobFeature.h +++ b/arangosh/Benchmark/BenchFeature.h @@ -26,9 +26,9 @@ #include "ApplicationFeatures/ApplicationFeature.h" namespace arangodb { -class ArangobFeature final : public application_features::ApplicationFeature { +class BenchFeature final : public application_features::ApplicationFeature { public: - ArangobFeature(application_features::ApplicationServer* server, int* result); + BenchFeature(application_features::ApplicationServer* server, int* result); public: void collectOptions(std::shared_ptr) override; diff --git a/arangosh/Benchmark/arangob.cpp b/arangosh/Benchmark/arangob.cpp index 2123b05d19..fdfd5cfd95 100644 --- a/arangosh/Benchmark/arangob.cpp +++ b/arangosh/Benchmark/arangob.cpp @@ -28,7 +28,7 @@ #include "ApplicationFeatures/LoggerFeature.h" #include "ApplicationFeatures/ShutdownFeature.h" #include "ApplicationFeatures/TempFeature.h" -#include "Benchmark/ArangobFeature.h" +#include "Benchmark/BenchFeature.h" #include "ProgramOptions2/ProgramOptions.h" #include "Rest/InitializeRest.h" @@ -56,8 +56,8 @@ int main(int argc, char* argv[]) { server.addFeature(new TempFeature(&server, "arangob")); server.addFeature(new ConfigFeature(&server, "arangob")); server.addFeature(new ClientFeature(&server)); - server.addFeature(new ArangobFeature(&server, &ret)); - server.addFeature(new ShutdownFeature(&server, "ArangobFeature")); + server.addFeature(new BenchFeature(&server, &ret)); + server.addFeature(new ShutdownFeature(&server, "Bench")); server.run(argc, argv); diff --git a/arangosh/CMakeLists.txt b/arangosh/CMakeLists.txt index adf12ab426..b9c480542b 100644 --- a/arangosh/CMakeLists.txt +++ b/arangosh/CMakeLists.txt @@ -23,7 +23,7 @@ endif () add_executable(${BIN_ARANGOB} ${ProductVersionFiles_arangob} ${PROJECT_SOURCE_DIR}/lib/Basics/WorkMonitorDummy.cpp - Benchmark/ArangobFeature.cpp + Benchmark/BenchFeature.cpp Benchmark/arangob.cpp ) @@ -65,7 +65,7 @@ endif () add_executable(${BIN_ARANGODUMP} ${ProductVersionFiles_arangodump} ${PROJECT_SOURCE_DIR}/lib/Basics/WorkMonitorDummy.cpp - Dump/ArangodumpFeature.cpp + Dump/DumpFeature.cpp Dump/arangodump.cpp V8Client/ArangoClientHelper.cpp ) @@ -108,7 +108,7 @@ endif () add_executable(${BIN_ARANGOIMP} ${ProductVersionFiles_arangoimp} ${PROJECT_SOURCE_DIR}/lib/Basics/WorkMonitorDummy.cpp - Import/ArangoimpFeature.cpp + Import/ImportFeature.cpp Import/ImportHelper.cpp Import/arangoimp.cpp V8Client/ArangoClientHelper.cpp @@ -152,7 +152,7 @@ endif () add_executable(${BIN_ARANGORESTORE} ${ProductVersionFiles_arangorestore} ${PROJECT_SOURCE_DIR}/lib/Basics/WorkMonitorDummy.cpp - Restore/ArangorestoreFeature.cpp + Restore/RestoreFeature.cpp Restore/arangorestore.cpp V8Client/ArangoClientHelper.cpp ) @@ -196,7 +196,7 @@ add_executable(${BIN_ARANGOSH} ${ProductVersionFiles_arangosh} ${PROJECT_SOURCE_DIR}/lib/Basics/WorkMonitorDummy.cpp Import/ImportHelper.cpp - Shell/ArangoshFeature.cpp + Shell/ShellFeature.cpp Shell/V8ClientConnection.cpp Shell/V8ShellFeature.cpp Shell/arangosh.cpp diff --git a/arangosh/Dump/ArangodumpFeature.cpp b/arangosh/Dump/DumpFeature.cpp similarity index 97% rename from arangosh/Dump/ArangodumpFeature.cpp rename to arangosh/Dump/DumpFeature.cpp index 5b923301fe..0e17d7eb77 100644 --- a/arangosh/Dump/ArangodumpFeature.cpp +++ b/arangosh/Dump/DumpFeature.cpp @@ -20,7 +20,7 @@ /// @author Jan Steemann //////////////////////////////////////////////////////////////////////////////// -#include "ArangodumpFeature.h" +#include "DumpFeature.h" #include @@ -47,9 +47,9 @@ using namespace arangodb::httpclient; using namespace arangodb::options; using namespace arangodb::rest; -ArangodumpFeature::ArangodumpFeature( +DumpFeature::DumpFeature( application_features::ApplicationServer* server, int* result) - : ApplicationFeature(server, "ArangodumpFeature"), + : ApplicationFeature(server, "Dump"), _collections(), _chunkSize(1024 * 1024 * 2), _maxChunkSize(1024 * 1024 * 12), @@ -66,14 +66,14 @@ ArangodumpFeature::ArangodumpFeature( _clusterMode(false) { requiresElevatedPrivileges(false); setOptional(false); - startsAfter("ClientFeature"); - startsAfter("LoggerFeature"); + startsAfter("Client"); + startsAfter("Logger"); _outputDirectory = FileUtils::buildFilename(FileUtils::currentDirectory(), "dump"); } -void ArangodumpFeature::collectOptions( +void DumpFeature::collectOptions( std::shared_ptr options) { LOG_TOPIC(TRACE, Logger::STARTUP) << name() << "::collectOptions"; @@ -120,7 +120,7 @@ void ArangodumpFeature::collectOptions( new UInt64Parameter(&_tickEnd)); } -void ArangodumpFeature::validateOptions( +void DumpFeature::validateOptions( std::shared_ptr options) { LOG_TOPIC(TRACE, Logger::STARTUP) << name() << "::validateOptions"; @@ -157,7 +157,7 @@ void ArangodumpFeature::validateOptions( } } -void ArangodumpFeature::prepare() { +void DumpFeature::prepare() { LOG_TOPIC(TRACE, Logger::STARTUP) << name() << "::prepare"; bool isDirectory = false; @@ -204,7 +204,7 @@ void ArangodumpFeature::prepare() { } // start a batch -int ArangodumpFeature::startBatch(std::string DBserver, std::string& errorMsg) { +int DumpFeature::startBatch(std::string DBserver, std::string& errorMsg) { std::string const url = "/_api/replication/batch"; std::string const body = "{\"ttl\":300}"; @@ -254,7 +254,7 @@ int ArangodumpFeature::startBatch(std::string DBserver, std::string& errorMsg) { } // prolongs a batch -void ArangodumpFeature::extendBatch(std::string DBserver) { +void DumpFeature::extendBatch(std::string DBserver) { TRI_ASSERT(_batchId > 0); std::string const url = @@ -272,7 +272,7 @@ void ArangodumpFeature::extendBatch(std::string DBserver) { } // end a batch -void ArangodumpFeature::endBatch(std::string DBserver) { +void DumpFeature::endBatch(std::string DBserver) { TRI_ASSERT(_batchId > 0); std::string const url = @@ -291,7 +291,7 @@ void ArangodumpFeature::endBatch(std::string DBserver) { } /// @brief dump a single collection -int ArangodumpFeature::dumpCollection(int fd, std::string const& cid, +int DumpFeature::dumpCollection(int fd, std::string const& cid, std::string const& name, uint64_t maxTick, std::string& errorMsg) { uint64_t chunkSize = _chunkSize; @@ -402,7 +402,7 @@ int ArangodumpFeature::dumpCollection(int fd, std::string const& cid, } // execute a WAL flush request -void ArangodumpFeature::flushWal() { +void DumpFeature::flushWal() { std::string const url = "/_admin/wal/flush?waitForSync=true&waitForCollector=true"; @@ -418,7 +418,7 @@ void ArangodumpFeature::flushWal() { } // dump data from server -int ArangodumpFeature::runDump(std::string& dbName, std::string& errorMsg) { +int DumpFeature::runDump(std::string& dbName, std::string& errorMsg) { std::string const url = "/_api/replication/inventory?includeSystem=" + std::string(_includeSystemCollections ? "true" : "false"); @@ -665,7 +665,7 @@ int ArangodumpFeature::runDump(std::string& dbName, std::string& errorMsg) { } /// @brief dump a single shard, that is a collection on a DBserver -int ArangodumpFeature::dumpShard(int fd, std::string const& DBserver, +int DumpFeature::dumpShard(int fd, std::string const& DBserver, std::string const& name, std::string& errorMsg) { std::string const baseUrl = "/_api/replication/dump?DBserver=" + DBserver + @@ -761,7 +761,7 @@ int ArangodumpFeature::dumpShard(int fd, std::string const& DBserver, } // dump data from cluster via a coordinator -int ArangodumpFeature::runClusterDump(std::string& errorMsg) { +int DumpFeature::runClusterDump(std::string& errorMsg) { int res; std::string const url = @@ -973,11 +973,11 @@ int ArangodumpFeature::runClusterDump(std::string& errorMsg) { return TRI_ERROR_NO_ERROR; } -void ArangodumpFeature::start() { +void DumpFeature::start() { LOG_TOPIC(TRACE, Logger::STARTUP) << name() << "::start"; ClientFeature* client = - dynamic_cast(server()->feature("ClientFeature")); + dynamic_cast(server()->feature("Client")); int ret = EXIT_SUCCESS; *_result = ret; diff --git a/arangosh/Dump/ArangodumpFeature.h b/arangosh/Dump/DumpFeature.h similarity index 94% rename from arangosh/Dump/ArangodumpFeature.h rename to arangosh/Dump/DumpFeature.h index e3533cd969..50a9d81e86 100644 --- a/arangosh/Dump/ArangodumpFeature.h +++ b/arangosh/Dump/DumpFeature.h @@ -31,10 +31,10 @@ namespace httpclient { class SimpleHttpResult; } -class ArangodumpFeature final : public application_features::ApplicationFeature, +class DumpFeature final : public application_features::ApplicationFeature, public ArangoClientHelper { public: - ArangodumpFeature(application_features::ApplicationServer* server, + DumpFeature(application_features::ApplicationServer* server, int* result); public: diff --git a/arangosh/Dump/arangodump.cpp b/arangosh/Dump/arangodump.cpp index 9182ab39a7..a8a3227d9f 100644 --- a/arangosh/Dump/arangodump.cpp +++ b/arangosh/Dump/arangodump.cpp @@ -27,7 +27,7 @@ #include "ApplicationFeatures/ConfigFeature.h" #include "ApplicationFeatures/LoggerFeature.h" #include "ApplicationFeatures/ShutdownFeature.h" -#include "Dump/ArangodumpFeature.h" +#include "Dump/DumpFeature.h" #include "ProgramOptions2/ProgramOptions.h" #include "Rest/InitializeRest.h" @@ -52,8 +52,8 @@ int main(int argc, char* argv[]) { server.addFeature(new LoggerFeature(&server)); server.addFeature(new ConfigFeature(&server, "arangodump")); server.addFeature(new ClientFeature(&server)); - server.addFeature(new ArangodumpFeature(&server, &ret)); - server.addFeature(new ShutdownFeature(&server, "ArangodumpFeature")); + server.addFeature(new DumpFeature(&server, &ret)); + server.addFeature(new ShutdownFeature(&server, "Dump")); server.run(argc, argv); diff --git a/arangosh/Import/ArangoimpFeature.cpp b/arangosh/Import/ImportFeature.cpp similarity index 96% rename from arangosh/Import/ArangoimpFeature.cpp rename to arangosh/Import/ImportFeature.cpp index aba55c2cc1..aa6005c141 100644 --- a/arangosh/Import/ArangoimpFeature.cpp +++ b/arangosh/Import/ImportFeature.cpp @@ -20,7 +20,7 @@ /// @author Jan Steemann //////////////////////////////////////////////////////////////////////////////// -#include "ArangoimpFeature.h" +#include "ImportFeature.h" #include "ApplicationFeatures/ClientFeature.h" #include "Basics/StringUtils.h" @@ -36,9 +36,9 @@ using namespace arangodb::basics; using namespace arangodb::httpclient; using namespace arangodb::options; -ArangoimpFeature::ArangoimpFeature( +ImportFeature::ImportFeature( application_features::ApplicationServer* server, int* result) - : ApplicationFeature(server, "ArangoimpFeature"), + : ApplicationFeature(server, "Import"), _filename(""), _useBackslash(false), _chunkSize(1024 * 1024 * 16), @@ -54,12 +54,12 @@ ArangoimpFeature::ArangoimpFeature( _result(result) { requiresElevatedPrivileges(false); setOptional(false); - startsAfter("ClientFeature"); - startsAfter("ConfigFeature"); - startsAfter("LoggerFeature"); + startsAfter("Client"); + startsAfter("Config"); + startsAfter("Logger"); } -void ArangoimpFeature::collectOptions( +void ImportFeature::collectOptions( std::shared_ptr options) { LOG_TOPIC(TRACE, Logger::STARTUP) << name() << "::collectOptions"; @@ -131,7 +131,7 @@ void ArangoimpFeature::collectOptions( new DiscreteValuesParameter(&_typeImport, actions)); } -void ArangoimpFeature::validateOptions( +void ImportFeature::validateOptions( std::shared_ptr options) { LOG_TOPIC(TRACE, Logger::STARTUP) << name() << "::validateOptions"; @@ -147,11 +147,11 @@ void ArangoimpFeature::validateOptions( } } -void ArangoimpFeature::start() { +void ImportFeature::start() { LOG_TOPIC(TRACE, Logger::STARTUP) << name() << "::start"; ClientFeature* client = - dynamic_cast(server()->feature("ClientFeature")); + dynamic_cast(server()->feature("Client")); int ret = EXIT_SUCCESS; *_result = ret; diff --git a/arangosh/Import/ArangoimpFeature.h b/arangosh/Import/ImportFeature.h similarity index 92% rename from arangosh/Import/ArangoimpFeature.h rename to arangosh/Import/ImportFeature.h index b8bd5c93ed..8f860edf47 100644 --- a/arangosh/Import/ArangoimpFeature.h +++ b/arangosh/Import/ImportFeature.h @@ -33,10 +33,10 @@ class SimpleHttpClient; class SimpleHttpResult; } -class ArangoimpFeature final : public application_features::ApplicationFeature, +class ImportFeature final : public application_features::ApplicationFeature, public ArangoClientHelper { public: - ArangoimpFeature(application_features::ApplicationServer* server, + ImportFeature(application_features::ApplicationServer* server, int* result); public: diff --git a/arangosh/Import/arangoimp.cpp b/arangosh/Import/arangoimp.cpp index a772f55576..d3bb7f55e7 100644 --- a/arangosh/Import/arangoimp.cpp +++ b/arangosh/Import/arangoimp.cpp @@ -28,7 +28,7 @@ #include "ApplicationFeatures/LoggerFeature.h" #include "ApplicationFeatures/ShutdownFeature.h" #include "ApplicationFeatures/TempFeature.h" -#include "Import/ArangoimpFeature.h" +#include "Import/ImportFeature.h" #include "ProgramOptions2/ProgramOptions.h" #include "Rest/InitializeRest.h" @@ -54,8 +54,8 @@ int main(int argc, char* argv[]) { server.addFeature(new TempFeature(&server, "arangoimp")); server.addFeature(new ConfigFeature(&server, "arangoimp")); server.addFeature(new ClientFeature(&server)); - server.addFeature(new ArangoimpFeature(&server, &ret)); - server.addFeature(new ShutdownFeature(&server, "ArangoimpFeature")); + server.addFeature(new ImportFeature(&server, &ret)); + server.addFeature(new ShutdownFeature(&server, "Import")); server.run(argc, argv); diff --git a/arangosh/Restore/ArangorestoreFeature.cpp b/arangosh/Restore/RestoreFeature.cpp similarity index 96% rename from arangosh/Restore/ArangorestoreFeature.cpp rename to arangosh/Restore/RestoreFeature.cpp index e4c2b2e5c5..d12c8ce0e0 100644 --- a/arangosh/Restore/ArangorestoreFeature.cpp +++ b/arangosh/Restore/RestoreFeature.cpp @@ -20,7 +20,7 @@ /// @author Jan Steemann //////////////////////////////////////////////////////////////////////////////// -#include "ArangorestoreFeature.h" +#include "RestoreFeature.h" #include @@ -48,9 +48,9 @@ using namespace arangodb::httpclient; using namespace arangodb::options; using namespace arangodb::rest; -ArangorestoreFeature::ArangorestoreFeature( +RestoreFeature::RestoreFeature( application_features::ApplicationServer* server, int* result) - : ApplicationFeature(server, "ArangorestoreFeature"), + : ApplicationFeature(server, "Restore"), _collections(), _chunkSize(1024 * 1024 * 8), _includeSystemCollections(false), @@ -67,14 +67,14 @@ ArangorestoreFeature::ArangorestoreFeature( _result(result) { requiresElevatedPrivileges(false); setOptional(false); - startsAfter("ClientFeature"); - startsAfter("LoggerFeature"); + startsAfter("Client"); + startsAfter("Logger"); _inputDirectory = FileUtils::buildFilename(FileUtils::currentDirectory(), "dump"); } -void ArangorestoreFeature::collectOptions( +void RestoreFeature::collectOptions( std::shared_ptr options) { LOG_TOPIC(TRACE, Logger::STARTUP) << name() << "::collectOptions"; @@ -126,7 +126,7 @@ void ArangorestoreFeature::collectOptions( new BooleanParameter(&_force, false)); } -void ArangorestoreFeature::validateOptions( +void RestoreFeature::validateOptions( std::shared_ptr options) { LOG_TOPIC(TRACE, Logger::STARTUP) << name() << "::validateOptions"; @@ -147,7 +147,7 @@ void ArangorestoreFeature::validateOptions( } } -void ArangorestoreFeature::prepare() { +void RestoreFeature::prepare() { LOG_TOPIC(TRACE, Logger::STARTUP) << name() << "::prepare"; if (!_inputDirectory.empty() && @@ -174,7 +174,7 @@ void ArangorestoreFeature::prepare() { } } -int ArangorestoreFeature::tryCreateDatabase(ClientFeature* client, +int RestoreFeature::tryCreateDatabase(ClientFeature* client, std::string const& name) { arangodb::basics::Json json(arangodb::basics::Json::Object); json("name", arangodb::basics::Json(name)); @@ -216,7 +216,7 @@ int ArangorestoreFeature::tryCreateDatabase(ClientFeature* client, return TRI_ERROR_INTERNAL; } -int ArangorestoreFeature::sendRestoreCollection(VPackSlice const& slice, +int RestoreFeature::sendRestoreCollection(VPackSlice const& slice, std::string const& name, std::string& errorMsg) { std::string url = @@ -264,7 +264,7 @@ int ArangorestoreFeature::sendRestoreCollection(VPackSlice const& slice, return TRI_ERROR_NO_ERROR; } -int ArangorestoreFeature::sendRestoreIndexes(VPackSlice const& slice, +int RestoreFeature::sendRestoreIndexes(VPackSlice const& slice, std::string& errorMsg) { std::string const url = "/_api/replication/restore-indexes?force=" + std::string(_force ? "true" : "false"); @@ -294,7 +294,7 @@ int ArangorestoreFeature::sendRestoreIndexes(VPackSlice const& slice, return TRI_ERROR_NO_ERROR; } -int ArangorestoreFeature::sendRestoreData(std::string const& cname, +int RestoreFeature::sendRestoreData(std::string const& cname, char const* buffer, size_t bufferSize, std::string& errorMsg) { std::string const url = "/_api/replication/restore-data?collection=" + @@ -347,7 +347,7 @@ static bool SortCollections(VPackSlice const& l, VPackSlice const& r) { return strcasecmp(leftName.c_str(), rightName.c_str()) < 0; } -int ArangorestoreFeature::processInputDirectory(std::string& errorMsg) { +int RestoreFeature::processInputDirectory(std::string& errorMsg) { // create a lookup table for collections std::map restrictList; for (size_t i = 0; i < _collections.size(); ++i) { @@ -630,11 +630,11 @@ int ArangorestoreFeature::processInputDirectory(std::string& errorMsg) { return TRI_ERROR_NO_ERROR; } -void ArangorestoreFeature::start() { +void RestoreFeature::start() { LOG_TOPIC(TRACE, Logger::STARTUP) << name() << "::start"; ClientFeature* client = - dynamic_cast(server()->feature("ClientFeature")); + dynamic_cast(server()->feature("Client")); int ret = EXIT_SUCCESS; *_result = ret; diff --git a/arangosh/Restore/ArangorestoreFeature.h b/arangosh/Restore/RestoreFeature.h similarity index 96% rename from arangosh/Restore/ArangorestoreFeature.h rename to arangosh/Restore/RestoreFeature.h index f41da10ae3..8a92e43e6f 100644 --- a/arangosh/Restore/ArangorestoreFeature.h +++ b/arangosh/Restore/RestoreFeature.h @@ -34,11 +34,11 @@ namespace httpclient { class SimpleHttpResult; } -class ArangorestoreFeature final +class RestoreFeature final : public application_features::ApplicationFeature, public ArangoClientHelper { public: - ArangorestoreFeature(application_features::ApplicationServer* server, + RestoreFeature(application_features::ApplicationServer* server, int* result); public: diff --git a/arangosh/Restore/arangorestore.cpp b/arangosh/Restore/arangorestore.cpp index fea559d32e..099363ee6f 100644 --- a/arangosh/Restore/arangorestore.cpp +++ b/arangosh/Restore/arangorestore.cpp @@ -28,7 +28,7 @@ #include "ApplicationFeatures/LoggerFeature.h" #include "ApplicationFeatures/ShutdownFeature.h" #include "ApplicationFeatures/TempFeature.h" -#include "Restore/ArangorestoreFeature.h" +#include "Restore/RestoreFeature.h" #include "ProgramOptions2/ProgramOptions.h" #include "Rest/InitializeRest.h" @@ -54,8 +54,8 @@ int main(int argc, char* argv[]) { server.addFeature(new TempFeature(&server, "arangorestore")); server.addFeature(new ConfigFeature(&server, "arangorestore")); server.addFeature(new ClientFeature(&server)); - server.addFeature(new ArangorestoreFeature(&server, &ret)); - server.addFeature(new ShutdownFeature(&server, "ArangorestoreFeature")); + server.addFeature(new RestoreFeature(&server, &ret)); + server.addFeature(new ShutdownFeature(&server, "Restore")); server.run(argc, argv); diff --git a/arangosh/Shell/ArangoshFeature.cpp b/arangosh/Shell/ShellFeature.cpp similarity index 89% rename from arangosh/Shell/ArangoshFeature.cpp rename to arangosh/Shell/ShellFeature.cpp index 2796370ddf..5c1ab161ba 100644 --- a/arangosh/Shell/ArangoshFeature.cpp +++ b/arangosh/Shell/ShellFeature.cpp @@ -20,7 +20,7 @@ /// @author Dr. Frank Celler //////////////////////////////////////////////////////////////////////////////// -#include "ArangoshFeature.h" +#include "ShellFeature.h" #include "ApplicationFeatures/ClientFeature.h" #include "Logger/Logger.h" @@ -31,21 +31,21 @@ using namespace arangodb; using namespace arangodb::basics; using namespace arangodb::options; -ArangoshFeature::ArangoshFeature( +ShellFeature::ShellFeature( application_features::ApplicationServer* server, int* result) - : ApplicationFeature(server, "ArangoshFeature"), + : ApplicationFeature(server, "Shell"), _jslint(), _result(result), _runMode(RunMode::INTERACTIVE) { requiresElevatedPrivileges(false); setOptional(false); - startsAfter("ConfigFeature"); - startsAfter("LanguageFeature"); - startsAfter("LoggerFeature"); - startsAfter("V8ShellFeature"); + startsAfter("Config"); + startsAfter("Language"); + startsAfter("Logger"); + startsAfter("V8Shell"); } -void ArangoshFeature::collectOptions( +void ShellFeature::collectOptions( std::shared_ptr options) { LOG_TOPIC(TRACE, Logger::STARTUP) << name() << "::collectOptions"; @@ -74,17 +74,17 @@ void ArangoshFeature::collectOptions( new VectorParameter(&_unitTests)); } -void ArangoshFeature::validateOptions( +void ShellFeature::validateOptions( std::shared_ptr options) { LOG_TOPIC(TRACE, Logger::STARTUP) << name() << "::validateOptions"; _positionals = options->processingResult()._positionals; ClientFeature* client = - dynamic_cast(server()->feature("ClientFeature")); + dynamic_cast(server()->feature("Client")); ConsoleFeature* console = - dynamic_cast(server()->feature("ConsoleFeature")); + dynamic_cast(server()->feature("Console")); if (client->endpoint() == "none") { client->disable(); @@ -134,13 +134,13 @@ void ArangoshFeature::validateOptions( } } -void ArangoshFeature::start() { +void ShellFeature::start() { LOG_TOPIC(TRACE, Logger::STARTUP) << name() << "::start"; *_result = EXIT_FAILURE; V8ShellFeature* shell = - dynamic_cast(server()->feature("V8ShellFeature")); + dynamic_cast(server()->feature("V8Shell")); bool ok = false; diff --git a/arangosh/Shell/ArangoshFeature.h b/arangosh/Shell/ShellFeature.h similarity index 91% rename from arangosh/Shell/ArangoshFeature.h rename to arangosh/Shell/ShellFeature.h index 3d47612406..d876387c6d 100644 --- a/arangosh/Shell/ArangoshFeature.h +++ b/arangosh/Shell/ShellFeature.h @@ -26,9 +26,9 @@ #include "ApplicationFeatures/ApplicationFeature.h" namespace arangodb { -class ArangoshFeature final : public application_features::ApplicationFeature { +class ShellFeature final : public application_features::ApplicationFeature { public: - ArangoshFeature(application_features::ApplicationServer* server, int* result); + ShellFeature(application_features::ApplicationServer* server, int* result); public: void collectOptions(std::shared_ptr) override; diff --git a/arangosh/Shell/V8ClientConnection.cpp b/arangosh/Shell/V8ClientConnection.cpp index dec359fbe7..880c9eeee2 100644 --- a/arangosh/Shell/V8ClientConnection.cpp +++ b/arangosh/Shell/V8ClientConnection.cpp @@ -374,7 +374,7 @@ static void ClientConnection_reconnect( if (args.Length() < 4) { ConsoleFeature* console = dynamic_cast( - ApplicationServer::lookupFeature("ConsoleFeature")); + ApplicationServer::lookupFeature("Console")); if (console == nullptr || !console->isEnabled()) { std::cout << "Please specify a password: " << std::flush; diff --git a/arangosh/Shell/V8ShellFeature.cpp b/arangosh/Shell/V8ShellFeature.cpp index d426bb1888..8708eab5cf 100644 --- a/arangosh/Shell/V8ShellFeature.cpp +++ b/arangosh/Shell/V8ShellFeature.cpp @@ -48,7 +48,7 @@ using namespace arangodb::rest; V8ShellFeature::V8ShellFeature(application_features::ApplicationServer* server, std::string const& name) - : ApplicationFeature(server, "V8ShellFeature"), + : ApplicationFeature(server, "V8Shell"), _startupDirectory("js"), _currentModuleDirectory(true), _gcInterval(10), @@ -58,9 +58,9 @@ V8ShellFeature::V8ShellFeature(application_features::ApplicationServer* server, requiresElevatedPrivileges(false); setOptional(false); - startsAfter("LoggerFeature"); - startsAfter("ConsoleFeature"); - startsAfter("V8PlatformFeature"); + startsAfter("Logger"); + startsAfter("Console"); + startsAfter("V8Platform"); } void V8ShellFeature::collectOptions(std::shared_ptr options) { @@ -98,7 +98,7 @@ void V8ShellFeature::validateOptions( void V8ShellFeature::start() { LOG_TOPIC(TRACE, Logger::STARTUP) << name() << "::start"; - _console = dynamic_cast(server()->feature("ConsoleFeature")); + _console = dynamic_cast(server()->feature("Console")); _isolate = v8::Isolate::New(); @@ -239,7 +239,7 @@ V8ClientConnection* V8ShellFeature::setup( ClientFeature* client = nullptr; if (createConnection) { - client = dynamic_cast(server()->feature("ClientFeature")); + client = dynamic_cast(server()->feature("Client")); if (client != nullptr && client->isEnabled()) { auto connection = client->createConnection(); @@ -252,14 +252,14 @@ V8ClientConnection* V8ShellFeature::setup( } } - initMode(ArangoshFeature::RunMode::INTERACTIVE, positionals); + initMode(ShellFeature::RunMode::INTERACTIVE, positionals); if (createConnection && client != nullptr) { v8connection->initServer(_isolate, context, client); } bool pe = printHello(v8connection.get()); - loadModules(ArangoshFeature::RunMode::INTERACTIVE); + loadModules(ShellFeature::RunMode::INTERACTIVE); if (promptError != nullptr) { *promptError = pe; @@ -296,7 +296,7 @@ int V8ShellFeature::runShell(std::vector const& positionals) { uint64_t nrCommands = 0; ClientFeature* client = - dynamic_cast(server()->feature("ClientFeature")); + dynamic_cast(server()->feature("Client")); if (!client->isEnabled()) { client = nullptr; @@ -823,7 +823,7 @@ void V8ShellFeature::initGlobals() { } } -void V8ShellFeature::initMode(ArangoshFeature::RunMode runMode, +void V8ShellFeature::initMode(ShellFeature::RunMode runMode, std::vector const& positionals) { auto context = _isolate->GetCurrentContext(); @@ -841,29 +841,29 @@ void V8ShellFeature::initMode(ArangoshFeature::RunMode runMode, TRI_AddGlobalVariableVocbase( _isolate, context, TRI_V8_ASCII_STRING2(_isolate, "IS_EXECUTE_SCRIPT"), v8::Boolean::New(_isolate, - runMode == ArangoshFeature::RunMode::EXECUTE_SCRIPT)); + runMode == ShellFeature::RunMode::EXECUTE_SCRIPT)); TRI_AddGlobalVariableVocbase( _isolate, context, TRI_V8_ASCII_STRING2(_isolate, "IS_EXECUTE_STRING"), v8::Boolean::New(_isolate, - runMode == ArangoshFeature::RunMode::EXECUTE_STRING)); + runMode == ShellFeature::RunMode::EXECUTE_STRING)); TRI_AddGlobalVariableVocbase( _isolate, context, TRI_V8_ASCII_STRING2(_isolate, "IS_CHECK_SCRIPT"), v8::Boolean::New(_isolate, - runMode == ArangoshFeature::RunMode::CHECK_SYNTAX)); + runMode == ShellFeature::RunMode::CHECK_SYNTAX)); TRI_AddGlobalVariableVocbase( _isolate, context, TRI_V8_ASCII_STRING2(_isolate, "IS_UNIT_TESTS"), v8::Boolean::New(_isolate, - runMode == ArangoshFeature::RunMode::UNIT_TESTS)); + runMode == ShellFeature::RunMode::UNIT_TESTS)); TRI_AddGlobalVariableVocbase( _isolate, context, TRI_V8_ASCII_STRING2(_isolate, "IS_JS_LINT"), - v8::Boolean::New(_isolate, runMode == ArangoshFeature::RunMode::JSLINT)); + v8::Boolean::New(_isolate, runMode == ShellFeature::RunMode::JSLINT)); } -void V8ShellFeature::loadModules(ArangoshFeature::RunMode runMode) { +void V8ShellFeature::loadModules(ShellFeature::RunMode runMode) { auto context = _isolate->GetCurrentContext(); JSLoader loader; @@ -890,7 +890,7 @@ void V8ShellFeature::loadModules(ArangoshFeature::RunMode runMode) { files.push_back( "common/bootstrap/modules.js"); // must come last before patches - if (runMode != ArangoshFeature::RunMode::JSLINT) { + if (runMode != ShellFeature::RunMode::JSLINT) { files.push_back("common/bootstrap/monkeypatches.js"); } diff --git a/arangosh/Shell/V8ShellFeature.h b/arangosh/Shell/V8ShellFeature.h index 4a0a30f782..48cf889f29 100644 --- a/arangosh/Shell/V8ShellFeature.h +++ b/arangosh/Shell/V8ShellFeature.h @@ -29,7 +29,7 @@ #include #include "ApplicationFeatures/ConsoleFeature.h" -#include "Shell/ArangoshFeature.h" +#include "Shell/ShellFeature.h" namespace arangodb { class ConsoleFeature; @@ -65,8 +65,8 @@ class V8ShellFeature final : public application_features::ApplicationFeature { private: bool printHello(V8ClientConnection*); void initGlobals(); - void initMode(ArangoshFeature::RunMode, std::vector const&); - void loadModules(ArangoshFeature::RunMode); + void initMode(ShellFeature::RunMode, std::vector const&); + void loadModules(ShellFeature::RunMode); V8ClientConnection* setup(v8::Local& context, bool, std::vector const&, bool* promptError = nullptr); diff --git a/arangosh/Shell/arangosh.cpp b/arangosh/Shell/arangosh.cpp index d2b9498787..d30eae7776 100644 --- a/arangosh/Shell/arangosh.cpp +++ b/arangosh/Shell/arangosh.cpp @@ -34,7 +34,7 @@ #include "Basics/files.h" #include "ProgramOptions2/ProgramOptions.h" #include "Rest/InitializeRest.h" -#include "Shell/ArangoshFeature.h" +#include "Shell/ShellFeature.h" #include "Shell/V8ShellFeature.h" using namespace arangodb; @@ -65,8 +65,8 @@ int main(int argc, char* argv[]) { server.addFeature(new ConsoleFeature(&server)); server.addFeature(new V8PlatformFeature(&server)); server.addFeature(new V8ShellFeature(&server, name)); - server.addFeature(new ArangoshFeature(&server, &ret)); - server.addFeature(new ShutdownFeature(&server, "ArangoshFeature")); + server.addFeature(new ShellFeature(&server, &ret)); + server.addFeature(new ShutdownFeature(&server, "Shell")); server.run(argc, argv); diff --git a/lib/ApplicationFeatures/ClientFeature.cpp b/lib/ApplicationFeatures/ClientFeature.cpp index 5f1789d633..731ed71f25 100644 --- a/lib/ApplicationFeatures/ClientFeature.cpp +++ b/lib/ApplicationFeatures/ClientFeature.cpp @@ -39,7 +39,7 @@ using namespace arangodb::rest; ClientFeature::ClientFeature(application_features::ApplicationServer* server, double connectionTimeout, double requestTimeout) - : ApplicationFeature(server, "ClientFeature"), + : ApplicationFeature(server, "Client"), _databaseName("_system"), _authentication(true), _endpoint(Endpoint::getDefaultEndpoint()), @@ -53,7 +53,7 @@ ClientFeature::ClientFeature(application_features::ApplicationServer* server, _warn(false) { setOptional(true); requiresElevatedPrivileges(false); - startsAfter("LoggerFeature"); + startsAfter("Logger"); } void ClientFeature::collectOptions(std::shared_ptr options) { @@ -137,7 +137,7 @@ void ClientFeature::validateOptions(std::shared_ptr options) { !options->processingResult().touched(_section + ".password")) { usleep(10 * 1000); - ConsoleFeature* console = dynamic_cast(ApplicationServer::lookupFeature("ConsoleFeature")); + ConsoleFeature* console = dynamic_cast(ApplicationServer::lookupFeature("Console")); if (console != nullptr) { _password = console->readPassword("Please specify a password: "); diff --git a/lib/ApplicationFeatures/ConfigFeature.cpp b/lib/ApplicationFeatures/ConfigFeature.cpp index 3cdb4eb9a5..680d520243 100644 --- a/lib/ApplicationFeatures/ConfigFeature.cpp +++ b/lib/ApplicationFeatures/ConfigFeature.cpp @@ -35,12 +35,12 @@ using namespace arangodb::options; ConfigFeature::ConfigFeature(application_features::ApplicationServer* server, std::string const& progname) - : ApplicationFeature(server, "ConfigFeature"), + : ApplicationFeature(server, "Config"), _file(""), _progname(progname) { setOptional(false); requiresElevatedPrivileges(false); - startsAfter("LoggerFeature"); + startsAfter("Logger"); } void ConfigFeature::collectOptions(std::shared_ptr options) { diff --git a/lib/ApplicationFeatures/ConsoleFeature.cpp b/lib/ApplicationFeatures/ConsoleFeature.cpp index 64c092f7a5..4577802073 100644 --- a/lib/ApplicationFeatures/ConsoleFeature.cpp +++ b/lib/ApplicationFeatures/ConsoleFeature.cpp @@ -42,7 +42,7 @@ static const int INTENSITY = FOREGROUND_INTENSITY | BACKGROUND_INTENSITY; #endif ConsoleFeature::ConsoleFeature(application_features::ApplicationServer* server) - : ApplicationFeature(server, "ConsoleFeature"), + : ApplicationFeature(server, "Console"), #ifdef _WIN32 _codePage(-1), _cygwinShell(false), @@ -61,7 +61,7 @@ ConsoleFeature::ConsoleFeature(application_features::ApplicationServer* server) _toAuditFile(nullptr) { setOptional(false); requiresElevatedPrivileges(false); - startsAfter("LoggerFeature"); + startsAfter("Logger"); if (!_supportsColors) { _colors = false; diff --git a/lib/ApplicationFeatures/DatabaseFeature.cpp b/lib/ApplicationFeatures/DatabaseFeature.cpp index b1f51d708f..8200f4c092 100644 --- a/lib/ApplicationFeatures/DatabaseFeature.cpp +++ b/lib/ApplicationFeatures/DatabaseFeature.cpp @@ -32,7 +32,7 @@ using namespace arangodb::options; DatabaseFeature::DatabaseFeature( application_features::ApplicationServer* server, uint64_t maximalJournalSize) - : ApplicationFeature(server, "DatabaseFeature"), + : ApplicationFeature(server, "Database"), _directory(""), _maximalJournalSize(maximalJournalSize), _queryTracking(true), @@ -40,7 +40,7 @@ DatabaseFeature::DatabaseFeature( _queryCacheEntries(128) { setOptional(false); requiresElevatedPrivileges(false); - startsAfter("LoggerFeature"); + startsAfter("Logger"); } void DatabaseFeature::collectOptions(std::shared_ptr options) { diff --git a/lib/ApplicationFeatures/LanguageFeature.cpp b/lib/ApplicationFeatures/LanguageFeature.cpp index 7babe00e04..bd0776e5f9 100644 --- a/lib/ApplicationFeatures/LanguageFeature.cpp +++ b/lib/ApplicationFeatures/LanguageFeature.cpp @@ -33,10 +33,10 @@ using namespace arangodb::options; LanguageFeature::LanguageFeature( application_features::ApplicationServer* server) - : ApplicationFeature(server, "LanguageFeature") { + : ApplicationFeature(server, "Language") { setOptional(false); requiresElevatedPrivileges(false); - startsAfter("LoggerFeature"); + startsAfter("Logger"); } void LanguageFeature::collectOptions( diff --git a/lib/ApplicationFeatures/LoggerFeature.cpp b/lib/ApplicationFeatures/LoggerFeature.cpp index e75f6c8932..760de62ac0 100644 --- a/lib/ApplicationFeatures/LoggerFeature.cpp +++ b/lib/ApplicationFeatures/LoggerFeature.cpp @@ -30,7 +30,7 @@ using namespace arangodb; using namespace arangodb::options; LoggerFeature::LoggerFeature(application_features::ApplicationServer* server) - : ApplicationFeature(server, "LoggerFeature"), + : ApplicationFeature(server, "Logger"), _output(), _levels(), _useLocalTime(false), diff --git a/lib/ApplicationFeatures/ShutdownFeature.cpp b/lib/ApplicationFeatures/ShutdownFeature.cpp index 3ee01bcc56..7696bf3815 100644 --- a/lib/ApplicationFeatures/ShutdownFeature.cpp +++ b/lib/ApplicationFeatures/ShutdownFeature.cpp @@ -31,10 +31,10 @@ using namespace arangodb::options; ShutdownFeature::ShutdownFeature( application_features::ApplicationServer* server, std::string const& feature) - : ApplicationFeature(server, "ShutdownFeature") { + : ApplicationFeature(server, "Shutdown") { setOptional(false); requiresElevatedPrivileges(false); - startsAfter("LoggerFeature"); + startsAfter("Logger"); startsAfter(feature); } diff --git a/lib/ApplicationFeatures/SslFeature.cpp b/lib/ApplicationFeatures/SslFeature.cpp index 32e527efdf..9526ed7d4f 100644 --- a/lib/ApplicationFeatures/SslFeature.cpp +++ b/lib/ApplicationFeatures/SslFeature.cpp @@ -31,7 +31,7 @@ using namespace arangodb; using namespace arangodb::options; SslFeature::SslFeature(application_features::ApplicationServer* server) - : ApplicationFeature(server, "SslFeature"), + : ApplicationFeature(server, "Ssl"), _cafile(), _keyfile(), _sessionCache(false), @@ -41,7 +41,7 @@ SslFeature::SslFeature(application_features::ApplicationServer* server) (long)(SSL_OP_TLS_ROLLBACK_BUG | SSL_OP_CIPHER_SERVER_PREFERENCE)) { setOptional(true); requiresElevatedPrivileges(true); - startsAfter("LoggerFeature"); + startsAfter("Logger"); } void SslFeature::collectOptions(std::shared_ptr options) { diff --git a/lib/ApplicationFeatures/TempFeature.cpp b/lib/ApplicationFeatures/TempFeature.cpp index 5044c09ffc..b51c8444bc 100644 --- a/lib/ApplicationFeatures/TempFeature.cpp +++ b/lib/ApplicationFeatures/TempFeature.cpp @@ -32,10 +32,10 @@ using namespace arangodb::options; TempFeature::TempFeature(application_features::ApplicationServer* server, std::string const& appname) - : ApplicationFeature(server, "TempFeature"), _path(), _appname(appname) { + : ApplicationFeature(server, "Temp"), _path(), _appname(appname) { setOptional(false); requiresElevatedPrivileges(false); - startsAfter("LoggerFeature"); + startsAfter("Logger"); } void TempFeature::collectOptions(std::shared_ptr options) { diff --git a/lib/ApplicationFeatures/V8PlatformFeature.cpp b/lib/ApplicationFeatures/V8PlatformFeature.cpp index 0a2241aafe..ece326de40 100644 --- a/lib/ApplicationFeatures/V8PlatformFeature.cpp +++ b/lib/ApplicationFeatures/V8PlatformFeature.cpp @@ -48,10 +48,10 @@ class BufferAllocator : public v8::ArrayBuffer::Allocator { V8PlatformFeature::V8PlatformFeature( application_features::ApplicationServer* server) - : ApplicationFeature(server, "V8PlatformFeature") { + : ApplicationFeature(server, "V8Platform") { setOptional(false); requiresElevatedPrivileges(false); - startsAfter("LoggerFeature"); + startsAfter("Logger"); } void V8PlatformFeature::collectOptions( From d64bf2dc950f675ab4ad8f7803b4bba6191cca0a Mon Sep 17 00:00:00 2001 From: Frank Celler Date: Tue, 8 Mar 2016 10:51:25 +0100 Subject: [PATCH 11/20] added srv:// endpoint --- CHANGELOG | 1508 +++++++++++++++++++------------------- CMakeLists.txt | 13 + lib/CMakeLists.txt | 1 + lib/Rest/Endpoint.cpp | 37 +- lib/Rest/Endpoint.h | 188 +---- lib/Rest/EndpointIp.cpp | 1 + lib/Rest/EndpointSrv.cpp | 255 +++++++ lib/Rest/EndpointSrv.h | 53 ++ lib/V8/v8-utils.cpp | 16 +- 9 files changed, 1132 insertions(+), 940 deletions(-) create mode 100644 lib/Rest/EndpointSrv.cpp create mode 100644 lib/Rest/EndpointSrv.h diff --git a/CHANGELOG b/CHANGELOG index fcc793ecc4..26b0972d01 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,8 @@ v3.0.0 (XXXX-XX-XX) ------------------- +* added new endpoint "srv://" for DNS service records + * The result order of the AQL functions VALUES and KEYS has never been guaranteed and it only had the "correct" ordering by accident when iterating over objects that were not loaded from the database. This behaviour is now changed by @@ -9,12 +11,12 @@ the sort parameter. * removed configure option `--enable-logger` -* added AQL array comparison operators +* added AQL array comparison operators - All AQL comparison operators now also exist in an array variant. In the + All AQL comparison operators now also exist in an array variant. In the array variant, the operator is preceded with one of the keywords *ALL*, *ANY* or *NONE*. Using one of these keywords changes the operator behavior to - execute the comparison operation for all, any, or none of its left hand + execute the comparison operation for all, any, or none of its left hand argument values. It is therefore expected that the left hand argument of an array operator is an array. @@ -44,7 +46,7 @@ the sort parameter. * allow enclosing AQL identifiers in forward ticks in addition to using backward ticks - This allows for convenient writing of AQL queries in JavaScript template strings + This allows for convenient writing of AQL queries in JavaScript template strings (which are delimited with backticks themselves), e.g. var q = `FOR doc IN ´collection´ RETURN doc.´name´`; @@ -138,11 +140,13 @@ v2.8.5 (2016-03-XX) and #1770 (Display ACTUAL query time in aardvark's AQL editor) * Windows: the unhandled exception handler now calls the windows logging - facilities directly without locks. + facilities directly without locks. This fixes lockups on crashes from the logging framework. * improve nullptr handling in logger. +* added new endpoint "srv://" for DNS service records + v2.8.4 (2016-03-01) ------------------- @@ -185,7 +189,7 @@ v2.8.2 (2016-02-09) requests are made to the master. Initial synchronization hands over its handle for blocking logfile removal to the continuous replication when started via the *setupReplication* function. In this case, continuous replication will - extend the logfile removal blocking period for the required WAL logfiles when + extend the logfile removal blocking period for the required WAL logfiles when the slave makes additional requests. All handles that block logfile removal will time out automatically after at @@ -193,22 +197,22 @@ v2.8.2 (2016-02-09) case the slave's replication is turned off, the slaves loses the connection to the master or the slave goes down). -* added all-in-one function *setupReplication* to synchronize data from master +* added all-in-one function *setupReplication* to synchronize data from master to slave and start the continuous replication: require("@arangodb/replication").setupReplication(configuration); - The command will return when the initial synchronization is finished and the - continuous replication has been started, or in case the initial synchronization - has failed. + The command will return when the initial synchronization is finished and the + continuous replication has been started, or in case the initial synchronization + has failed. - If the initial synchronization is successful, the command will store the given - configuration on the slave. It also configures the continuous replication to start + If the initial synchronization is successful, the command will store the given + configuration on the slave. It also configures the continuous replication to start automatically if the slave is restarted, i.e. *autoStart* is set to *true*. - If the command is run while the slave's replication applier is already running, - it will first stop the running applier, drop its configuration and do a - resynchronization of data with the master. It will then use the provided configration, + If the command is run while the slave's replication applier is already running, + it will first stop the running applier, drop its configuration and do a + resynchronization of data with the master. It will then use the provided configration, overwriting any previously existing replication configuration on the slave. The following example demonstrates how to use the command for setting up replication @@ -232,7 +236,7 @@ v2.8.2 (2016-02-09) will automatically poll the slave periodically for status updates. The main benefit is that the connection to the slave does not need to stay open - permanently and is thus not affected by timeout issues. Additionally the caller does + permanently and is thus not affected by timeout issues. Additionally the caller does not need to query the synchronization status from the slave manually as this is now performed automatically by these functions. @@ -257,7 +261,7 @@ v2.8.1 (2016-01-29) * detect more types of transaction deadlocks early -* fixed display of relational operators in traversal explain output +* fixed display of relational operators in traversal explain output * fixed undefined behavior in AQL function `PARSE_IDENTIFIER` @@ -278,7 +282,7 @@ v2.8.1 (2016-01-29) IS_SAME_COLLECTION('_users', '_users/my-user') IS_SAME_COLLECTION('_users', { _id: '_users/my-user' }) - /* false */ + /* false */ IS_SAME_COLLECTION('_users', 'foobar/baz') IS_SAME_COLLECTION('_users', { _id: 'something/else' }) @@ -322,18 +326,18 @@ v2.8.0-beta7 (2016-01-06) RETURN { gender, minAge, maxAge } or, if no grouping keys are used, it can follow the `COLLECT` keyword: - + FOR doc IN collection COLLECT AGGREGATE minAge = MIN(doc.age), maxAge = MAX(doc.age) RETURN { minAge, maxAge } - Only specific expressions are allowed on the right-hand side of each `AGGREGATE` + Only specific expressions are allowed on the right-hand side of each `AGGREGATE` assignment: - - on the top level the expression must be a call to one of the supported aggregation - functions `LENGTH`, `MIN`, `MAX`, `SUM`, `AVERAGE`, `STDDEV_POPULATION`, `STDDEV_SAMPLE`, + - on the top level the expression must be a call to one of the supported aggregation + functions `LENGTH`, `MIN`, `MAX`, `SUM`, `AVERAGE`, `STDDEV_POPULATION`, `STDDEV_SAMPLE`, `VARIANCE_POPULATION`, or `VARIANCE_SAMPLE` - the expression must not refer to variables introduced in the `COLLECT` itself @@ -348,20 +352,20 @@ v2.8.0-beta7 (2016-01-06) * web interface: included several bugfixes: #1597, #1611, #1623 -* AQL query optimizer now converts `LENGTH(collection-name)` to an optimized +* AQL query optimizer now converts `LENGTH(collection-name)` to an optimized expression that returns the number of documents in a collection * adjusted the behavior of the expansion (`[*]`) operator in AQL for non-array values In ArangoDB 2.8, calling the expansion operator on a non-array value will always return an empty array. Previous versions of ArangoDB expanded non-array values by - calling the `TO_ARRAY()` function for the value, which for example returned an + calling the `TO_ARRAY()` function for the value, which for example returned an array with a single value for boolean, numeric and string input values, and an array with the object's values for an object input value. This behavior was inconsistent with how the expansion operator works for the array indexes in 2.8, so the behavior is now unified: - - if the left-hand side operand of `[*]` is an array, the array will be returned as + - if the left-hand side operand of `[*]` is an array, the array will be returned as is when calling `[*]` on it - if the left-hand side operand of `[*]` is not an array, an empty array will be returned by `[*]` @@ -371,7 +375,7 @@ v2.8.0-beta7 (2016-01-06) The following example query will change its result in 2.8 compared to 2.7: - LET values = "foo" RETURN values[*] + LET values = "foo" RETURN values[*] In 2.7 the query has returned the array `[ "foo" ]`, but in 2.8 it will return an empty array `[ ]`. To make it return the array `[ "foo" ]` again, an explicit @@ -385,17 +389,17 @@ v2.8.0-beta7 (2016-01-06) LET values = [ { name: "foo" }, { name: "bar" } ] RETURN values[*].name[*] - The above returned `[ [ "foo" ], [ "bar" ] ] in 2.7. In 2.8 it will return + The above returned `[ [ "foo" ], [ "bar" ] ] in 2.7. In 2.8 it will return `[ [ ], [ ] ]`, because the value of `name` is not an array. To change the results to the 2.7 style, the query can be changed to LET values = [ { name: "foo" }, { name: "bar" } ] RETURN values[* RETURN TO_ARRAY(CURRENT.name)] - The above also works in 2.7. + The above also works in 2.7. The following types of queries won't change: - LET values = [ 1, 2, 3 ] RETURN values[*] + LET values = [ 1, 2, 3 ] RETURN values[*] LET values = [ { name: "foo" }, { name: "bar" } ] RETURN values[*].name LET values = [ { names: [ "foo", "bar" ] }, { names: [ "baz" ] } ] RETURN values[*].names[*] LET values = [ { names: [ "foo", "bar" ] }, { names: [ "baz" ] } ] RETURN values[*].names[**] @@ -410,9 +414,9 @@ v2.8.0-beta7 (2016-01-06) * fixes for AQL optimizer and traversal * added `--create-collection-type` option to arangoimp - - This allows specifying the type of the collection to be created when - `--create-collection` is set to `true`. + + This allows specifying the type of the collection to be created when + `--create-collection` is set to `true`. v2.8.0-beta2 (2015-12-16) @@ -423,12 +427,12 @@ v2.8.0-beta2 (2015-12-16) This rule pre-sorts the right-hand side operand of the `IN` and `NOT IN` operators so the operation can use a binary search with logarithmic complexity instead of a linear search. The rule is applied when the right-hand side - operand of an `IN` or `NOT IN` operator in a filter condition is a variable that + operand of an `IN` or `NOT IN` operator in a filter condition is a variable that is defined in a different loop/scope than the operator itself. Additionally, - the filter condition must consist of solely the `IN` or `NOT IN` operation - in order to avoid any side-effects. + the filter condition must consist of solely the `IN` or `NOT IN` operation + in order to avoid any side-effects. -* changed collection status terminology in web interface for collections for +* changed collection status terminology in web interface for collections for which an unload request has been issued from `in the process of being unloaded` to `will be unloaded`. @@ -443,7 +447,7 @@ v2.8.0-beta2 (2015-12-16) that JavaScript code currently holds. This information can be used for debugging compaction and unload issues. - `waitingFor`: An optional string value that contains information about - which object type is at the head of the collection's cleanup queue. This + which object type is at the head of the collection's cleanup queue. This information can be used for debugging compaction and unload issues. - `compactionStatus.time`: The point in time the compaction for the collection was last executed. This information can be used for debugging compaction @@ -461,7 +465,7 @@ v2.8.0-beta2 (2015-12-16) * better error reporting for arangodump and arangorestore * arangodump will now fail by default when trying to dump edges that - refer to already dropped collections. This can be circumvented by + refer to already dropped collections. This can be circumvented by specifying the option `--force true` when invoking arangodump * fixed cluster upgrade procedure @@ -491,16 +495,16 @@ v2.8.0-beta1 (2015-12-06) * added AQL function `IS_DATESTRING(value)` - Returns true if *value* is a string that can be used in a date function. + Returns true if *value* is a string that can be used in a date function. This includes partial dates such as *2015* or *2015-10* and strings containing - invalid dates such as *2015-02-31*. The function will return false for all + invalid dates such as *2015-02-31*. The function will return false for all non-string values, even if some of them may be usable in date functions. v2.8.0-alpha1 (2015-12-03) -------------------------- -* added AQL keywords `GRAPH`, `OUTBOUND`, `INBOUND` and `ANY` for use in graph +* added AQL keywords `GRAPH`, `OUTBOUND`, `INBOUND` and `ANY` for use in graph traversals, reserved AQL keyword `ALL` for future use Usage of these keywords as collection names, variable names or attribute names @@ -517,25 +521,25 @@ v2.8.0-alpha1 (2015-12-03) * replication improvements: - - added `autoResync` configuration parameter for continuous replication. - - When set to `true`, a replication slave will automatically trigger a full data - re-synchronization with the master when the master cannot provide the log data + - added `autoResync` configuration parameter for continuous replication. + + When set to `true`, a replication slave will automatically trigger a full data + re-synchronization with the master when the master cannot provide the log data the slave had asked for. Note that `autoResync` will only work when the option `requireFromPresent` is also set to `true` for the continuous replication, or when the continuous syncer is started and detects that no start tick is present. - + Automatic re-synchronization may transfer a lot of data from the master to the - slave and may be expensive. It is therefore turned off by default. + slave and may be expensive. It is therefore turned off by default. When turned off, the slave will never perform an automatic re-synchronization with the master. - added `idleMinWaitTime` and `idleMaxWaitTime` configuration parameters for - continuous replication. + continuous replication. - These parameters can be used to control the minimum and maximum wait time the - slave will (intentionally) idle and not poll for master log changes in case the - master had sent the full logs already. + These parameters can be used to control the minimum and maximum wait time the + slave will (intentionally) idle and not poll for master log changes in case the + master had sent the full logs already. The `idleMaxWaitTime` value will only be used when `adapativePolling` is set to `true`. When `adaptivePolling` is disable, only `idleMinWaitTime` will be used as a constant time span in which the slave will not poll the master for @@ -546,24 +550,24 @@ v2.8.0-alpha1 (2015-12-03) - added `initialSyncMaxWaitTime` configuration parameter for initial and continuous replication - This option controls the maximum wait time (in seconds) that the initial - synchronization will wait for a response from the master when fetching initial + This option controls the maximum wait time (in seconds) that the initial + synchronization will wait for a response from the master when fetching initial collection data. If no response is received within this time period, the initial - synchronization will give up and fail. This option is also relevant for + synchronization will give up and fail. This option is also relevant for continuous replication in case *autoResync* is set to *true*, as then the continuous replication may trigger a full data re-synchronization in case the master cannot the log data the slave had asked for. - HTTP requests sent from the slave to the master during initial synchronization will now be retried if they fail with connection problems. - + - the initial synchronization now logs its progress so it can be queried using the regular replication status check APIs. - added `async` attribute for `sync` and `syncCollection` operations called from - the ArangoShell. Setthing this attribute to `true` will make the synchronization + the ArangoShell. Setthing this attribute to `true` will make the synchronization job on the server go into the background, so that the shell does not block. The - status of the started asynchronous synchronization job can be queried from the + status of the started asynchronous synchronization job can be queried from the ArangoShell like this: /* starts initial synchronization */ @@ -604,11 +608,11 @@ v2.8.0-alpha1 (2015-12-03) the HTTP response header `"Server: ArangoDB"` in its HTTP responses. By default, the option is turned off so the header is still sent as usual. -* added new AQL function `UNSET_RECURSIVE` to recursively unset attritutes from +* added new AQL function `UNSET_RECURSIVE` to recursively unset attritutes from objects/documents * switched command-line editor in ArangoShell and arangod to linenoise-ng - + * added automatic deadlock detection for transactions In case a deadlock is detected, a multi-collection operation may be rolled back @@ -616,7 +620,7 @@ v2.8.0-alpha1 (2015-12-03) operations containing more than one collection should be aware of this potential error and handle it accordingly, either by giving up or retrying the transaction. -* Added C++ implementations for the AQL arithmetic operations and the following +* Added C++ implementations for the AQL arithmetic operations and the following AQL functions: - ABS - APPEND @@ -656,7 +660,7 @@ v2.8.0-alpha1 (2015-12-03) - VARIANCE_POPULATION - VARIANCE_SAMPLE - WITHIN - - ZIP + - ZIP * improved performance of skipping over many documents in an AQL query when no indexes and no filters are used, e.g. @@ -667,25 +671,25 @@ v2.8.0-alpha1 (2015-12-03) * Added array indexes - Hash indexes and skiplist indexes can now optionally be defined for array values + Hash indexes and skiplist indexes can now optionally be defined for array values so they index individual array members. - + To define an index for array values, the attribute name is extended with the expansion operator `[*]` in the index definition: - + arangosh> db.colName.ensureHashIndex("tags[*]"); - + When given the following document - + { tags: [ "AQL", "ArangoDB", "Index" ] } the index will now contain the individual values `"AQL"`, `"ArangoDB"` and `"Index"`. - + Now the index can be used for finding all documents having `"ArangoDB"` somewhere in their tags array using the following AQL query: - - FOR doc IN colName - FILTER "ArangoDB" IN doc.tags[*] + + FOR doc IN colName + FILTER "ArangoDB" IN doc.tags[*] RETURN doc * rewrote AQL query optimizer rule `use-index-range` and renamed it to `use-indexes`. @@ -746,7 +750,7 @@ v2.7.4 (2015-12-21) that JavaScript code currently holds. This information can be used for debugging compaction and unload issues. - `waitingFor`: An optional string value that contains information about - which object type is at the head of the collection's cleanup queue. This + which object type is at the head of the collection's cleanup queue. This information can be used for debugging compaction and unload issues. - `compactionStatus.time`: The point in time the compaction for the collection was last executed. This information can be used for debugging compaction @@ -769,7 +773,7 @@ v2.7.3 (2015-12-17) were set via ArangoShell * fixed disappearing of documents for collections transferred via `sync` or - `syncCollection` if the collection was dropped right before synchronization + `syncCollection` if the collection was dropped right before synchronization and drop and (re-)create collection markers were located in the same WAL file @@ -781,25 +785,25 @@ v2.7.2 (2015-12-01) * replication improvements: - - added `autoResync` configuration parameter for continuous replication. - - When set to `true`, a replication slave will automatically trigger a full data - re-synchronization with the master when the master cannot provide the log data + - added `autoResync` configuration parameter for continuous replication. + + When set to `true`, a replication slave will automatically trigger a full data + re-synchronization with the master when the master cannot provide the log data the slave had asked for. Note that `autoResync` will only work when the option `requireFromPresent` is also set to `true` for the continuous replication, or when the continuous syncer is started and detects that no start tick is present. - + Automatic re-synchronization may transfer a lot of data from the master to the - slave and may be expensive. It is therefore turned off by default. + slave and may be expensive. It is therefore turned off by default. When turned off, the slave will never perform an automatic re-synchronization with the master. - added `idleMinWaitTime` and `idleMaxWaitTime` configuration parameters for - continuous replication. + continuous replication. - These parameters can be used to control the minimum and maximum wait time the - slave will (intentionally) idle and not poll for master log changes in case the - master had sent the full logs already. + These parameters can be used to control the minimum and maximum wait time the + slave will (intentionally) idle and not poll for master log changes in case the + master had sent the full logs already. The `idleMaxWaitTime` value will only be used when `adapativePolling` is set to `true`. When `adaptivePolling` is disable, only `idleMinWaitTime` will be used as a constant time span in which the slave will not poll the master for @@ -810,17 +814,17 @@ v2.7.2 (2015-12-01) - added `initialSyncMaxWaitTime` configuration parameter for initial and continuous replication - This option controls the maximum wait time (in seconds) that the initial - synchronization will wait for a response from the master when fetching initial + This option controls the maximum wait time (in seconds) that the initial + synchronization will wait for a response from the master when fetching initial collection data. If no response is received within this time period, the initial - synchronization will give up and fail. This option is also relevant for + synchronization will give up and fail. This option is also relevant for continuous replication in case *autoResync* is set to *true*, as then the continuous replication may trigger a full data re-synchronization in case the master cannot the log data the slave had asked for. - HTTP requests sent from the slave to the master during initial synchronization will now be retried if they fail with connection problems. - + - the initial synchronization now logs its progress so it can be queried using the regular replication status check APIs. @@ -863,12 +867,12 @@ v2.7.1 (2015-11-07) * more detailed output in arango-dfdb * fixed "no start tick" issue in replication applier - + This error could occur after restarting a slave server after a shutdown when no data was ever transferred from the master to the slave via the continuous replication -* fixed problem during SSL client connection abort that led to scheduler thread +* fixed problem during SSL client connection abort that led to scheduler thread staying at 100% CPU saturation * fixed potential segfault in AQL `NEIGHBORS` function implementation when C++ function @@ -880,9 +884,9 @@ v2.7.1 (2015-11-07) This allows combining the attributes of multiple objects from an array into a single object, e.g. - RETURN MERGE([ - { foo: 'bar' }, - { quux: 'quetzalcoatl', ruled: true }, + RETURN MERGE([ + { foo: 'bar' }, + { quux: 'quetzalcoatl', ruled: true }, { bar: 'baz', foo: 'done' } ]) @@ -895,7 +899,7 @@ v2.7.1 (2015-11-07) "bar": "baz" } -* fixed potential deadlock in collection status changing on Windows +* fixed potential deadlock in collection status changing on Windows * fixed hard-coded `incremental` parameter in shell implementation of `syncCollection` function in replication module @@ -919,11 +923,11 @@ v2.7.0 (2015-10-09) * raised default value of `--server.descriptors-minimum` to 1024 * allow Foxx apps to be installed underneath URL path `/_open/`, so they can be - (intentionally) accessed without authentication. + (intentionally) accessed without authentication. * added *allowImplicit* sub-attribute in collections declaration of transactions. The *allowImplicit* attributes allows making transactions fail should they - read-access a collection that was not explicitly declared in the *collections* + read-access a collection that was not explicitly declared in the *collections* array of the transaction. * added "special" password ARANGODB_DEFAULT_ROOT_PASSWORD. If you pass @@ -979,22 +983,22 @@ v2.7.0-rc1 (2015-09-17) * collection.NTH3() * upgraded Swagger to version 2.0 for the Documentation - + This gives the user better prepared test request structures. More conversions will follow so finally client libraries can be auto-generated. -* added extra AQL functions for date and time calculation and manipulation. - These functions were contributed by GitHub users @CoDEmanX and @friday. - A big thanks for their work! +* added extra AQL functions for date and time calculation and manipulation. + These functions were contributed by GitHub users @CoDEmanX and @friday. + A big thanks for their work! The following extra date functions are available from 2.7 on: * `DATE_DAYOFYEAR(date)`: Returns the day of year number of *date*. The return values range from 1 to 365, or 366 in a leap year respectively. - * `DATE_ISOWEEK(date)`: Returns the ISO week date of *date*. - The return values range from 1 to 53. Monday is considered the first day of the week. - There are no fractional weeks, thus the last days in December may belong to the first + * `DATE_ISOWEEK(date)`: Returns the ISO week date of *date*. + The return values range from 1 to 53. Monday is considered the first day of the week. + There are no fractional weeks, thus the last days in December may belong to the first week of the next year, and the first days in January may be part of the previous year's last week. @@ -1007,7 +1011,7 @@ v2.7.0-rc1 (2015-09-17) * 4: October, November, December - *DATE_DAYS_IN_MONTH(date)*: Returns the number of days in *date*'s month (28..31). - + * `DATE_ADD(date, amount, unit)`: Adds *amount* given in *unit* to *date* and returns the calculated date. @@ -1027,7 +1031,7 @@ v2.7.0-rc1 (2015-09-17) * `DATE_SUBTRACT(date, amount, unit)`: Subtracts *amount* given in *unit* from *date* and returns the calculated date. - + It works the same as `DATE_ADD()`, except that it subtracts. It is equivalent to calling `DATE_ADD()` with a negative amount, except that `DATE_SUBTRACT()` can also subtract ISO durations. Note that negative ISO durations are not @@ -1040,7 +1044,7 @@ v2.7.0-rc1 (2015-09-17) * `DATE_COMPARE(date1, date2, unitRangeStart, unitRangeEnd)`: Compare two partial dates and return true if they match, false otherwise. The parts to compare are defined by a range of time units. - + The full range is: years, months, days, hours, minutes, seconds, milliseconds. Pass the unit to start from as *unitRangeStart*, and the unit to end with as *unitRangeEnd*. All units in between will be compared. Leave out *unitRangeEnd* @@ -1083,23 +1087,23 @@ v2.7.0-rc1 (2015-09-17) - %: ignored * new WAL logfiles and datafiles are now created non-sparse - - This prevents SIGBUS signals being raised when memory of a sparse datafile is accessed + + This prevents SIGBUS signals being raised when memory of a sparse datafile is accessed and the disk is full and the accessed file part is not actually disk-backed. In this case the mapped memory region is not necessarily backed by physical memory, and accessing the memory may raise SIGBUS and crash arangod. -* the `internal.download()` function and the module `org/arangodb/request` used some +* the `internal.download()` function and the module `org/arangodb/request` used some internal library function that handled the sending of HTTP requests from inside of ArangoDB. This library unconditionally set an HTTP header `Accept-Encoding: gzip` - in all outgoing HTTP requests. + in all outgoing HTTP requests. This has been fixed in 2.7, so `Accept-Encoding: gzip` is not set automatically anymore. Additionally, the header `User-Agent: ArangoDB` is not set automatically either. If client applications desire to send these headers, they are free to add it when constructing the requests using the `download` function or the request module. - -* fixed issue #1436: org/arangodb/request advertises deflate without supporting it + +* fixed issue #1436: org/arangodb/request advertises deflate without supporting it * added template string generator function `aqlQuery` for generating AQL queries @@ -1121,16 +1125,16 @@ v2.7.0-rc1 (2015-09-17) not only the name of the dumped collection, but also an additional 32-digit hash value. This is done to prevent overwriting dump files in case-insensitive file systems when there exist multiple collections with the same name (but with - different cases). - + different cases). + For example, if a database has two collections: `test` and `Test`, previous - versions of ArangoDB created the files - + versions of ArangoDB created the files + * `test.structure.json` and `test.data.json` for collection `test` * `Test.structure.json` and `Test.data.json` for collection `Test` This did not work for case-insensitive filesystems, because the files for the - second collection would have overwritten the files of the first. arangodump in + second collection would have overwritten the files of the first. arangodump in 2.7 will create the following filenames instead: * `test_098f6bcd4621d373cade4e832627b4f6.structure.json` and `test_098f6bcd4621d373cade4e832627b4f6.data.json` @@ -1138,30 +1142,30 @@ v2.7.0-rc1 (2015-09-17) These filenames will be unambiguous even in case-insensitive filesystems. -* IMPORTANT CHANGE: make arangod actually close lingering client connections - when idle for at least the duration specified via `--server.keep-alive-timeout`. - In previous versions of ArangoDB, connections were not closed by the server - when the timeout was reached and the client was still connected. Now the +* IMPORTANT CHANGE: make arangod actually close lingering client connections + when idle for at least the duration specified via `--server.keep-alive-timeout`. + In previous versions of ArangoDB, connections were not closed by the server + when the timeout was reached and the client was still connected. Now the connection is properly closed by the server in case of timeout. Client applications relying on the old behavior may now need to reconnect to the - server when their idle connections time out and get closed (note: connections - being idle for a long time may be closed by the OS or firewalls anyway - + server when their idle connections time out and get closed (note: connections + being idle for a long time may be closed by the OS or firewalls anyway - client applications should be aware of that and try to reconnect). -* IMPORTANT CHANGE: when starting arangod, the server will drop the process - privileges to the specified values in options `--server.uid` and `--server.gid` +* IMPORTANT CHANGE: when starting arangod, the server will drop the process + privileges to the specified values in options `--server.uid` and `--server.gid` instantly after parsing the startup options. That means when either `--server.uid` or `--server.gid` are set, the privilege - change will happen earlier. This may prevent binding the server to an endpoint + change will happen earlier. This may prevent binding the server to an endpoint with a port number lower than 1024 if the arangodb user has no privileges for that. Previous versions of ArangoDB changed the privileges later, so some - startup actions were still carried out under the invoking user (i.e. likely + startup actions were still carried out under the invoking user (i.e. likely *root* when started via init.d or system scripts) and especially binding to low port numbers was still possible there. The default privileges for user *arangodb* will not be sufficient for binding - to port numbers lower than 1024. To have an ArangoDB 2.7 bind to a port number + to port numbers lower than 1024. To have an ArangoDB 2.7 bind to a port number lower than 1024, it needs to be started with either a different privileged user, or the privileges of the *arangodb* user have to raised manually beforehand. @@ -1169,14 +1173,14 @@ v2.7.0-rc1 (2015-09-17) * Linux startup scripts and systemd configuration for arangod now try to adjust the NOFILE (number of open files) limits for the process. The limit - value is set to 131072 (128k) when ArangoDB is started via start/stop + value is set to 131072 (128k) when ArangoDB is started via start/stop commands * When ArangoDB is started/stopped manually via the start/stop commands, the main process will wait for up to 10 seconds after it forks the supervisor and arangod child processes. If the startup fails within that period, the start/stop script will fail with an exit code other than zero. If the - startup of the supervisor or arangod is still ongoing after 10 seconds, + startup of the supervisor or arangod is still ongoing after 10 seconds, the main program will still return with exit code 0. The limit of 10 seconds is arbitrary because the time required for a startup is not known in advance. @@ -1187,9 +1191,9 @@ v2.7.0-rc1 (2015-09-17) would need to wait for another thread to finalize loading a collection. If set to *true*, then the first operation that accesses an unloaded collection will load it. Further threads that try to access the same collection while - it is still loading immediately fail with an error (1238, *collection not loaded*). + it is still loading immediately fail with an error (1238, *collection not loaded*). This is to prevent all server threads from being blocked while waiting on the - same collection to finish loading. When the first thread has completed loading + same collection to finish loading. When the first thread has completed loading the collection, the collection becomes regularly available, and all operations from that point on can be carried out normally, and error 1238 will not be thrown anymore for that collection. @@ -1200,7 +1204,7 @@ v2.7.0-rc1 (2015-09-17) is fully loaded. This configuration might lead to all server threads being blocked because they are all waiting for the same collection to complete loading. Setting the option to *true* will prevent this from happening, but - requires clients to catch error 1238 and react on it (maybe by scheduling + requires clients to catch error 1238 and react on it (maybe by scheduling a retry for later). The default value is *false*. @@ -1210,8 +1214,8 @@ v2.7.0-rc1 (2015-09-17) When CTRL-C is pressed in arangosh, it will now print a `^C` first. Pressing CTRL-C again will reset the prompt if something was entered before, or quit arangosh if no command was entered directly before. - - This affects the arangosh version build with Readline-support only (Linux + + This affects the arangosh version build with Readline-support only (Linux and MacOS). The MacOS version of ArangoDB for Homebrew now depends on Readline, too. The @@ -1221,8 +1225,8 @@ v2.7.0-rc1 (2015-09-17) * increased default value for collection-specific `indexBuckets` value from 1 to 8 - Collections created from 2.7 on will use the new default value of `8` if not - overridden on collection creation or later using + Collections created from 2.7 on will use the new default value of `8` if not + overridden on collection creation or later using `collection.properties({ indexBuckets: ... })`. The `indexBuckets` value determines the number of buckets to use for indexes of @@ -1232,18 +1236,18 @@ v2.7.0-rc1 (2015-09-17) less intrusive if the index uses multiple buckets, because resize and reallocation will affect only data in a single bucket instead of all index values. - The index buckets will be filled in parallel when loading a collection if the collection - has an `indexBuckets` value greater than 1 and the collection contains a significant + The index buckets will be filled in parallel when loading a collection if the collection + has an `indexBuckets` value greater than 1 and the collection contains a significant amount of documents/edges (the current threshold is 256K documents but this value may change in future versions of ArangoDB). * changed HTTP client to use poll instead of select on Linux and MacOS - This affects the ArangoShell and user-defined JavaScript code running inside + This affects the ArangoShell and user-defined JavaScript code running inside arangod that initiates its own HTTP calls. Using poll instead of select allows using arbitrary high file descriptors - (bigger than the compiled in FD_SETSIZE). Server connections are still handled using + (bigger than the compiled in FD_SETSIZE). Server connections are still handled using epoll, which has never been affected by FD_SETSIZE. * implemented AQL `LIKE` function using ICU regexes @@ -1252,13 +1256,13 @@ v2.7.0-rc1 (2015-09-17) FOR doc IN collection RETURN DISTINCT doc.status - - This change also introduces `DISTINCT` as an AQL keyword. + + This change also introduces `DISTINCT` as an AQL keyword. * removed `createNamedQueue()` and `addJob()` functions from org/arangodb/tasks * use less locks and more atomic variables in the internal dispatcher - and V8 context handling implementations. This leads to improved throughput in + and V8 context handling implementations. This leads to improved throughput in some ArangoDB internals and allows for higher HTTP request throughput for many operations. @@ -1273,7 +1277,7 @@ v2.7.0-rc1 (2015-09-17) RETURN { name, age } The above is the shorthand equivalent of the generic form - + LET name = "Peter" LET age = 42 RETURN { name : name, age : age } @@ -1285,15 +1289,15 @@ v2.7.0-rc1 (2015-09-17) * removed configure option `--enable-figures` This option previously controlled whether HTTP request statistics code was - compiled into ArangoDB or not. The previous default value was `true` so - statistics code was available in official packages. Setting the option to + compiled into ArangoDB or not. The previous default value was `true` so + statistics code was available in official packages. Setting the option to `false` led to compile errors so it is doubtful the default value was ever changed. By removing the option some internal statistics code was also simplified. * removed run-time manipulation methods for server endpoints: - * `db._removeEndpoint()` + * `db._removeEndpoint()` * `db._configureEndpoint()` * HTTP POST `/_api/endpoint` * HTTP DELETE `/_api/endpoint` @@ -1317,7 +1321,7 @@ v2.7.0-rc1 (2015-09-17) * HTTP GET `/_api/query-cache/properties`: returns the global query cache configuration * HTTP PUT `/_api/query-cache/properties`: modifies the global query cache configuration * HTTP DELETE `/_api/query-cache`: invalidates all results in the query cache - + The following JavaScript functions have been added for controlling the query cache: * `require("org/arangodb/aql/cache").properties()`: returns the global query cache configuration @@ -1340,23 +1344,23 @@ v2.7.0-rc1 (2015-09-17) retrieve data from a skiplist index using a `LIMIT` clause. It was marked as deprecated in ArangoDB 2.6. - Since ArangoDB 2.3 the behavior of the `SKIPLIST` function can be emulated using regular AQL - constructs, e.g. + Since ArangoDB 2.3 the behavior of the `SKIPLIST` function can be emulated using regular AQL + constructs, e.g. - FOR doc IN @@collection - FILTER doc.value >= @value - SORT doc.value DESC - LIMIT 1 + FOR doc IN @@collection + FILTER doc.value >= @value + SORT doc.value DESC + LIMIT 1 RETURN doc -* the `skip()` function for simple queries does not accept negative input any longer. +* the `skip()` function for simple queries does not accept negative input any longer. This feature was deprecated in 2.6.0. * fix exception handling In some cases JavaScript exceptions would re-throw without information of the original problem. Now the original exception is logged for failure analysis. - + * based REST API method PUT `/_api/simple/all` on the cursor API and make it use AQL internally. The change speeds up this REST API method and will lead to additional query information being @@ -1384,7 +1388,7 @@ v2.7.0-rc1 (2015-09-17) at load time but also for random accesses later. * Overhauled web interface - + The web interface now has a new design. The API documentation for ArangoDB has been moved from "Tools" to "Links" in the web interface. @@ -1396,7 +1400,7 @@ v2.6.12 (2015-12-02) -------------------- * fixed disappearing of documents for collections transferred via `sync` if the - the collection was dropped right before synchronization and drop and (re-)create + the collection was dropped right before synchronization and drop and (re-)create collection markers were located in the same WAL file * added missing lock instruction for primary index in compactor size calculation @@ -1424,7 +1428,7 @@ v2.6.10 (2015-11-10) * more detailed output in arango-dfdb -* fixed potential deadlock in collection status changing on Windows +* fixed potential deadlock in collection status changing on Windows * issue #1521: Can't dump/restore with user and password @@ -1440,7 +1444,7 @@ v2.6.9 (2015-09-29) When using a Skiplist index on an attribute (say "a") and then using sort and skip on this attribute caused the result to be empty e.g.: - + require("internal").db.test.ensureSkiplist("a"); require("internal").db._query("FOR x IN test SORT x.a LIMIT 10, 10"); @@ -1470,7 +1474,7 @@ v2.6.8 (2015-09-09) so it will be lost after a restart of the system. In order to make the setting permanent, it should be executed during system startup or before starting arangod. - The ArangoDB start/stop scripts do not adjust the alignment setting, but rely on + The ArangoDB start/stop scripts do not adjust the alignment setting, but rely on the environment to have the correct alignment setting already. The reason for this is that the alignment settings also affect all other user processes (which ArangoDB is not aware of) and thus may have side-effects outside of ArangoDB. It is therefore @@ -1480,7 +1484,7 @@ v2.6.8 (2015-09-09) v2.6.7 (2015-08-25) ------------------- -* improved AssocMulti index performance when resizing. +* improved AssocMulti index performance when resizing. This makes the edge index perform less I/O when under memory pressure. @@ -1502,9 +1506,9 @@ v2.6.5 (2015-08-17) would need to wait for another thread to finalize loading a collection. If set to *true*, then the first operation that accesses an unloaded collection will load it. Further threads that try to access the same collection while - it is still loading immediately fail with an error (1238, *collection not loaded*). + it is still loading immediately fail with an error (1238, *collection not loaded*). This is to prevent all server threads from being blocked while waiting on the - same collection to finish loading. When the first thread has completed loading + same collection to finish loading. When the first thread has completed loading the collection, the collection becomes regularly available, and all operations from that point on can be carried out normally, and error 1238 will not be thrown anymore for that collection. @@ -1515,7 +1519,7 @@ v2.6.5 (2015-08-17) is fully loaded. This configuration might lead to all server threads being blocked because they are all waiting for the same collection to complete loading. Setting the option to *true* will prevent this from happening, but - requires clients to catch error 1238 and react on it (maybe by scheduling + requires clients to catch error 1238 and react on it (maybe by scheduling a retry for later). The default value is *false*. @@ -1523,7 +1527,7 @@ v2.6.5 (2015-08-17) * fixed busy wait loop in scheduler threads that sometimes consumed 100% CPU while waiting for events on connections closed unexpectedly by the client side -* handle attribute `indexBuckets` when restoring collections via arangorestore. +* handle attribute `indexBuckets` when restoring collections via arangorestore. Previously the `indexBuckets` attribute value from the dump was ignored, and the server default value for `indexBuckets` was used when restoring a collection. @@ -1568,17 +1572,17 @@ v2.6.1 (2015-06-24) v2.6.0 (2015-06-20) ------------------- -* using negative values for `SimpleQuery.skip()` is deprecated. +* using negative values for `SimpleQuery.skip()` is deprecated. This functionality will be removed in future versions of ArangoDB. * The following simple query functions are now deprecated: * collection.near - * collection.within - * collection.geo + * collection.within + * collection.geo * collection.fulltext - * collection.range - * collection.closedRange + * collection.range + * collection.closedRange This also lead to the following REST API methods being deprecated from now on: @@ -1587,24 +1591,24 @@ v2.6.0 (2015-06-20) * PUT /_api/simple/fulltext * PUT /_api/simple/range - It is recommended to replace calls to these functions or APIs with equivalent AQL queries, + It is recommended to replace calls to these functions or APIs with equivalent AQL queries, which are more flexible because they can be combined with other operations: - FOR doc IN NEAR(@@collection, @latitude, @longitude, @limit) + FOR doc IN NEAR(@@collection, @latitude, @longitude, @limit) RETURN doc FOR doc IN WITHIN(@@collection, @latitude, @longitude, @radius, @distanceAttributeName) RETURN doc - FOR doc IN FULLTEXT(@@collection, @attributeName, @queryString, @limit) + FOR doc IN FULLTEXT(@@collection, @attributeName, @queryString, @limit) RETURN doc - - FOR doc IN @@collection - FILTER doc.value >= @left && doc.value < @right - LIMIT @skip, @limit + + FOR doc IN @@collection + FILTER doc.value >= @left && doc.value < @right + LIMIT @skip, @limit RETURN doc` - - The above simple query functions and REST API methods may be removed in future versions + + The above simple query functions and REST API methods may be removed in future versions of ArangoDB. * deprecated now-obsolete AQL `SKIPLIST` function @@ -1612,11 +1616,11 @@ v2.6.0 (2015-06-20) The function was introduced in older versions of ArangoDB with a less powerful query optimizer to retrieve data from a skiplist index using a `LIMIT` clause. - Since 2.3 the same goal can be achieved by using regular AQL constructs, e.g. + Since 2.3 the same goal can be achieved by using regular AQL constructs, e.g. FOR doc IN collection FILTER doc.value >= @value SORT doc.value DESC LIMIT 1 RETURN doc -* fixed issues when switching the database inside tasks and during shutdown of database cursors +* fixed issues when switching the database inside tasks and during shutdown of database cursors These features were added during 2.6 alpha stage so the fixes affect devel/2.6-alpha builds only @@ -1626,32 +1630,32 @@ v2.6.0 (2015-06-20) When this option is set, arangod and the client tools will be linked against tcmalloc, which replaces the system allocator. When the option is set, a tcmalloc library must be present on the system under - one of the names `libtcmalloc`, `libtcmalloc_minimal` or `libtcmalloc_debug`. + one of the names `libtcmalloc`, `libtcmalloc_minimal` or `libtcmalloc_debug`. As this is a configure option, it is supported for manual builds on Linux-like systems only. tcmalloc support is currently experimental. * issue #1353: Windows: HTTP API - incorrect path in errorMessage -* issue #1347: added option `--create-database` for arangorestore. - +* issue #1347: added option `--create-database` for arangorestore. + Setting this option to `true` will now create the target database if it does not exist. When creating - the target database, the username and passwords passed to arangorestore will be used to create an + the target database, the username and passwords passed to arangorestore will be used to create an initial user for the new database. * issue #1345: advanced debug information for User Functions -* issue #1341: Can't use bindvars in UPSERT +* issue #1341: Can't use bindvars in UPSERT * fixed vulnerability in JWT implementation. * changed default value of option `--database.ignore-datafile-errors` from `true` to `false` If the new default value of `false` is used, then arangod will refuse loading collections that contain - datafiles with CRC mismatches or other errors. A collection with datafile errors will then become + datafiles with CRC mismatches or other errors. A collection with datafile errors will then become unavailable. This prevents follow up errors from happening. - - The only way to access such collection is to use the datafile debugger (arango-dfdb) and try to repair + + The only way to access such collection is to use the datafile debugger (arango-dfdb) and try to repair or truncate the datafile with it. If `--database.ignore-datafile-errors` is set to `true`, then collections will become available @@ -1688,10 +1692,10 @@ v2.6.0 (2015-06-20) removed startup option `--log.severity` - The docs for `--log.severity` mentioned lots of severities (e.g. `exception`, `technical`, `functional`, `development`) - but only a few severities (e.g. `all`, `human`) were actually used, with `human` being the default and `all` enabling the + The docs for `--log.severity` mentioned lots of severities (e.g. `exception`, `technical`, `functional`, `development`) + but only a few severities (e.g. `all`, `human`) were actually used, with `human` being the default and `all` enabling the additional logging of requests. So the option pretended to control a lot of things which it actually didn't. Additionally, - the option `--log.requests-file` was around for a long time already, also controlling request logging. + the option `--log.requests-file` was around for a long time already, also controlling request logging. Because the `--log.severity` option effectively did not control that much, it was removed. A side effect of removing the option is that 2.5 installations which used `--log.severity all` will not log requests after the upgrade to 2.6. This can @@ -1701,7 +1705,7 @@ v2.6.0 (2015-06-20) * added optional `limit` parameter for AQL function `FULLTEXT` -* make fulltext index also index text values contained in direct sub-objects of the indexed +* make fulltext index also index text values contained in direct sub-objects of the indexed attribute. Previous versions of ArangoDB only indexed the attribute value if it was a string. Sub-attributes @@ -1738,10 +1742,10 @@ v2.6.0 (2015-06-20) * PUT /_api/simple/remove-by-keys * properly prefix document address URLs with the current database name for calls to the REST - API method GET `/_api/document?collection=...` (that method will return partial URLs to all - documents in the collection). + API method GET `/_api/document?collection=...` (that method will return partial URLs to all + documents in the collection). - Previous versions of ArangoDB returned the URLs starting with `/_api/` but without the current + Previous versions of ArangoDB returned the URLs starting with `/_api/` but without the current database name, e.g. `/_api/document/mycollection/mykey`. Starting with 2.6, the response URLs will include the database name as well, e.g. `/_db/_system/_api/document/mycollection/mykey`. @@ -1751,9 +1755,9 @@ v2.6.0 (2015-06-20) collections more efficiently than the general-purpose cursor API. The export API is useful to transfer the contents of an entire collection to a client application. It provides optional filtering on specific attributes. - + The export API is available at endpoint `POST /_api/export?collection=...`. The API has the - same return value structure as the already established cursor API (`POST /_api/cursor`). + same return value structure as the already established cursor API (`POST /_api/cursor`). An introduction to the export API is given in this blog post: http://jsteemann.github.io/blog/2015/04/04/more-efficient-data-exports/ @@ -1767,7 +1771,7 @@ v2.6.0 (2015-06-20) http://jsteemann.github.io/blog/2015/05/04/subquery-optimizations/ * return value optimization for AQL queries - + This optimization avoids copying the final query result inside the query's main `ReturnNode`. A brief description can be found here: @@ -1784,32 +1788,32 @@ v2.6.0 (2015-06-20) * allow `@` and `.` characters in document keys, too - This change also leads to document keys being URL-encoded when returned in HTTP `location` + This change also leads to document keys being URL-encoded when returned in HTTP `location` response headers. * added alternative implementation for AQL COLLECT The alternative method uses a hash table for grouping and does not require its input elements to be sorted. It will be taken into account by the optimizer for `COLLECT` statements that do - not use an `INTO` clause. - - In case a `COLLECT` statement can use the hash table variant, the optimizer will create an extra + not use an `INTO` clause. + + In case a `COLLECT` statement can use the hash table variant, the optimizer will create an extra plan for it at the beginning of the planning phase. In this plan, no extra `SORT` node will be added in front of the `COLLECT` because the hash table variant of `COLLECT` does not require sorted input. Instead, a `SORT` node will be added after it to sort its output. This `SORT` node may be optimized away again in later stages. If the sort order of the result is irrelevant to the user, adding an extra `SORT null` after a hash `COLLECT` operation will allow the optimizer to remove the sorts altogether. - + In addition to the hash table variant of `COLLECT`, the optimizer will modify the original plan to use the regular `COLLECT` implementation. As this implementation requires sorted input, the optimizer will insert a `SORT` node in front of the `COLLECT`. This `SORT` node may be optimized away in later stages. - + The created plans will then be shipped through the regular optimization pipeline. In the end, the optimizer will pick the plan with the lowest estimated total cost as usual. The hash table variant does not require an up-front sort of the input, and will thus be preferred over the - regular `COLLECT` if the optimizer estimates many input elements for the `COLLECT` node and + regular `COLLECT` if the optimizer estimates many input elements for the `COLLECT` node and cannot use an index to sort them. The optimizer can be explicitly told to use the regular *sorted* variant of `COLLECT` by @@ -1819,7 +1823,7 @@ v2.6.0 (2015-06-20) A blog post on the new `COLLECT` implementation can be found here: http://jsteemann.github.io/blog/2015/04/22/collecting-with-a-hash-table/ -* refactored HTTP REST API for cursors +* refactored HTTP REST API for cursors The HTTP REST API for cursors (`/_api/cursor`) has been refactored to improve its performance and use less memory. @@ -1831,19 +1835,19 @@ v2.6.0 (2015-06-20) ArangoDB 2.4 since version allows to return results from data-modification AQL queries. The syntax for this was quite limited and verbose: - + FOR i IN 1..10 INSERT { value: i } IN test LET inserted = NEW RETURN inserted - The `LET inserted = NEW RETURN inserted` was required literally to return the inserted - documents. No calculations could be made using the inserted documents. + The `LET inserted = NEW RETURN inserted` was required literally to return the inserted + documents. No calculations could be made using the inserted documents. This is now more flexible. After a data-modification clause (e.g. `INSERT`, `UPDATE`, `REPLACE`, - `REMOVE`, `UPSERT`) there can follow any number of `LET` calculations. These calculations can + `REMOVE`, `UPSERT`) there can follow any number of `LET` calculations. These calculations can refer to the pseudo-values `OLD` and `NEW` that are created by the data-modification statements. - + This allows returning projections of inserted or updated documents, e.g.: FOR i IN 1..10 @@ -1877,7 +1881,7 @@ v2.6.0 (2015-06-20) attribute `found` will return the found document before the `UPDATE` was applied. If no document was found, `found` will contain a value of `null`. The `updated` result attribute will contain the inserted / updated document: - + UPSERT { page: 'index.html' } /* search example */ INSERT { page: 'index.html', pageViews: 1 } /* insert part */ UPDATE { pageViews: OLD.pageViews + 1 } /* update part */ @@ -1889,7 +1893,7 @@ v2.6.0 (2015-06-20) * adjusted default configuration value for `--server.backlog-size` from 10 to 64. -* issue #1231: bug xor feature in AQL: LENGTH(null) == 4 +* issue #1231: bug xor feature in AQL: LENGTH(null) == 4 This changes the behavior of the AQL `LENGTH` function as follows: @@ -1914,13 +1918,13 @@ v2.6.0 (2015-06-20) with a *unique key constraint violated* error. The above behavior is still the default. However, the API now allows controlling the behavior - in case of a unique key constraint error via the optional URL parameter `onDuplicate`. - + in case of a unique key constraint error via the optional URL parameter `onDuplicate`. + This parameter can have one of the following values: - + - `error`: when a unique key constraint error occurs, do not import or update the document but report an error. This is the default. - + - `update`: when a unique key constraint error occurs, try to (partially) update the existing document with the data specified in the import. This may still fail if the document would violate secondary unique indexes. Only the attributes present in the import data will be @@ -1929,7 +1933,7 @@ v2.6.0 (2015-06-20) - `replace`: when a unique key constraint error occurs, try to fully replace the existing document with the data specified in the import. This may still fail if the document would - violate secondary unique indexes. The number of replaced documents will be reported in the + violate secondary unique indexes. The number of replaced documents will be reported in the `updated` attribute of the HTTP API result. - `ignore`: when a unique key constraint error occurs, ignore this error. There will be no @@ -1953,24 +1957,24 @@ v2.6.0 (2015-06-20) if the command's result is printed in the shell, the first 10 results will be printed. Previously only a basic description of the underlying query result cursor was printed. Additionally, if the cursor result contains more than 10 results, the cursor is assigned to a global variable `more`, - which can be used to iterate over the cursor result. + which can be used to iterate over the cursor result. Example: arangosh [_system]> db._query("FOR i IN 1..15 RETURN i") [object ArangoQueryCursor, count: 15, hasMore: true] - [ - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10 + [ + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10 ] type 'more' to show more documents @@ -1979,17 +1983,17 @@ v2.6.0 (2015-06-20) arangosh [_system]> more [object ArangoQueryCursor, count: 15, hasMore: false] - [ - 11, - 12, - 13, - 14, - 15 + [ + 11, + 12, + 13, + 14, + 15 ] * Disallow batchSize value 0 in HTTP `POST /_api/cursor`: - The HTTP REST API `POST /_api/cursor` does not accept a `batchSize` parameter value of + The HTTP REST API `POST /_api/cursor` does not accept a `batchSize` parameter value of `0` any longer. A batch size of 0 never made much sense, but previous versions of ArangoDB did not check for this value. Now creating a cursor using a `batchSize` value 0 will result in an HTTP 400 error response @@ -2020,11 +2024,11 @@ v2.6.0 (2015-06-20) In order to get the old result format prior to ArangoDB 2.6, please use the function EDGES instead. Edges allows for a new option 'includeVertices' which, set to true, returns exactly the format of NEIGHBORS. Example: - + NEIGHBORS(, , , , ) This can now be achieved by: - + EDGES(, , , , {includeVertices: true}) If you are nesting several NEIGHBORS steps you can speed up their performance in the following way: @@ -2048,15 +2052,15 @@ v2.6.0 (2015-06-20) If provided, `includeData` is set to `true`, all vertices in the result will be returned with all their attributes. The default value of `includeData` is `false`. - This makes the default function results incompatible with previous versions of ArangoDB. + This makes the default function results incompatible with previous versions of ArangoDB. To get the old result style in ArangoDB 2.6, please set the options as follows in calls to `GRAPH_NEIGHBORS`: - + GRAPH_NEIGHBORS(, , { includeData: true }) * INCOMPATIBLE CHANGE: - + The AQL function `GRAPH_COMMON_NEIGHBORS` now provides an additional option `includeData`. This option allows controlling whether the function should return the complete vertices or just their IDs. Returning only the IDs instead of the full vertices can lead to @@ -2064,11 +2068,11 @@ v2.6.0 (2015-06-20) If provided, `includeData` is set to `true`, all vertices in the result will be returned with all their attributes. The default value of `includeData` is `false`. - This makes the default function results incompatible with previous versions of ArangoDB. + This makes the default function results incompatible with previous versions of ArangoDB. To get the old result style in ArangoDB 2.6, please set the options as follows in calls to `GRAPH_COMMON_NEIGHBORS`: - + GRAPH_COMMON_NEIGHBORS(, , , { includeData: true }, { includeData: true }) * INCOMPATIBLE CHANGE: @@ -2078,19 +2082,19 @@ v2.6.0 (2015-06-20) and edges or just their IDs. Returning only the IDs instead of full vertices and edges can lead to improved performance . - If provided, `includeData` is set to `true`, all vertices and edges in the result will - be returned with all their attributes. There is also an optional parameter `includePath` of + If provided, `includeData` is set to `true`, all vertices and edges in the result will + be returned with all their attributes. There is also an optional parameter `includePath` of type object. It has two optional sub-attributes `vertices` and `edges`, both of type boolean. Both can be set individually and the result will include all vertices on the path if `includePath.vertices == true` and all edges if `includePath.edges == true` respectively. - + The default value of `includeData` is `false`, and paths are now excluded by default. - This makes the default function results incompatible with previous versions of ArangoDB. + This makes the default function results incompatible with previous versions of ArangoDB. To get the old result style in ArangoDB 2.6, please set the options as follows in calls to `GRAPH_SHORTEST_PATH`: - + GRAPH_SHORTEST_PATH(, , , { includeData: true, includePath: { edges: true, vertices: true } }) The attributes `startVertex` and `vertex` that were present in the results of `GRAPH_SHORTEST_PATH` @@ -2099,13 +2103,13 @@ v2.6.0 (2015-06-20) * INCOMPATIBLE CHANGE: - The AQL function `GRAPH_DISTANCE_TO` will now return only the id the destination vertex + The AQL function `GRAPH_DISTANCE_TO` will now return only the id the destination vertex in the `vertex` attribute, and not the full vertex data with all vertex attributes. * INCOMPATIBLE CHANGE: - All graph measurements functions in JavaScript module `general-graph` that calculated a - single figure previously returned an array containing just the figure. Now these functions + All graph measurements functions in JavaScript module `general-graph` that calculated a + single figure previously returned an array containing just the figure. Now these functions will return the figure directly and not put it inside an array. The affected functions are: @@ -2138,7 +2142,7 @@ v2.6.0 (2015-06-20) * issue #1051: add info whether server is running in service or user mode? This will add a "mode" attribute to the result of the result of HTTP GET `/_api/version?details=true` - + "mode" can have the following values: - `standalone`: server was started manually (e.g. on command-line) @@ -2158,7 +2162,7 @@ v2.6.0 (2015-06-20) the queue (or queues) for jobs to be executed. The default value is `1` second. Lowering this value will result in the queue manager waking - up and checking the queues more frequently, which may increase CPU usage of the server. + up and checking the queues more frequently, which may increase CPU usage of the server. When not using Foxx queues, this value can be raised to save some CPU time. * added startup option `--server.foxx-queues` @@ -2215,13 +2219,13 @@ v2.6.0 (2015-06-20) When a collection contains more than one secondary index, they can be built in memory in parallel when the collection is loaded. How many threads are used for parallel index creation is determined by the new configuration parameter `--database.index-threads`. If this is set - to 0, indexes are built by the opening thread only and sequentially. This is equivalent to + to 0, indexes are built by the opening thread only and sequentially. This is equivalent to the behavior in 2.5 and before. * speed up building up primary index when loading collections -* added `count` attribute to `parameters.json` files of collections. This attribute indicates - the number of live documents in the collection on unload. It is read when the collection is +* added `count` attribute to `parameters.json` files of collections. This attribute indicates + the number of live documents in the collection on unload. It is read when the collection is (re)loaded to determine the initial size for the collection's primary index * removed remainders of MRuby integration, removed arangoirb @@ -2273,10 +2277,10 @@ v2.5.6 (2015-07-21) * potentially fixed issue #1313: Wrong metric calculation at dashboard Escape whitespace in process name when scanning /proc/pid/stats - + This fixes statistics values read from that file -* Fixed variable naming in AQL `COLLECT INTO` results in case the COLLECT is placed +* Fixed variable naming in AQL `COLLECT INTO` results in case the COLLECT is placed in a subquery which itself is followed by other constructs that require variables @@ -2305,7 +2309,7 @@ v2.5.3 (2015-04-29) ------------------- * Fix fs.move to work across filesystem borders; Fixes Foxx app installation problems; - issue #1292. + issue #1292. * Fix Foxx app install when installed on a different drive on Windows @@ -2313,19 +2317,19 @@ v2.5.3 (2015-04-29) * issue #1318: Inconsistent db._create() syntax -* issue #1315: queries to a collection fail with an empty response if the +* issue #1315: queries to a collection fail with an empty response if the collection contains specific JSON data * issue #1300: Make arangodump not fail if target directory exists but is empty * allow specifying higher values than SOMAXCONN for `--server.backlog-size` - Previously, arangod would not start when a `--server.backlog-size` value was + Previously, arangod would not start when a `--server.backlog-size` value was specified that was higher than the platform's SOMAXCONN header value. Now, arangod will use the user-provided value for `--server.backlog-size` and - pass it to the listen system call even if the value is higher than SOMAXCONN. - If the user-provided value is higher than SOMAXCONN, arangod will log a warning + pass it to the listen system call even if the value is higher than SOMAXCONN. + If the user-provided value is higher than SOMAXCONN, arangod will log a warning on startup. * Fixed a cluster deadlock bug. Mark a thread that is in a RemoteBlock as @@ -2380,7 +2384,7 @@ v2.5.1 (2015-03-19) ------------------- * fixed bug that caused undefined behavior when an AQL query was killed inside - a calculation block + a calculation block * fixed memleaks in AQL query cleanup in case out-of-memory errors are thrown @@ -2401,7 +2405,7 @@ v2.5.1 (2015-03-19) the datafile being partially loaded. All data up to until the mismatch will be loaded. This will enable users to continue with collection datafiles that are corrupted, but will result in only a partial load of the data. - The WAL recovery will still abort when encountering a collection with a + The WAL recovery will still abort when encountering a collection with a corrupted datafile, at least if `--wal.ignore-recovery-errors` is not set to `true`. @@ -2421,8 +2425,8 @@ v2.5.1 (2015-03-19) * datafiles and `parameter.json` files written by arangod are now created with read and write privileges for the arangod process user, and with read and write privileges for the arangod - process group. - + process group. + Previously, these files were created with user read and write permissions only. * INCOMPATIBLE CHANGE: @@ -2463,7 +2467,7 @@ v2.5.0 (2015-03-09) * fix for downloading Foxx -* fixed issue #1258: http pipelining not working? +* fixed issue #1258: http pipelining not working? v2.5.0-beta4 (2015-03-05) @@ -2485,7 +2489,7 @@ v2.5.0-beta3 (2015-02-27) v2.5.0-beta2 (2015-02-23) ------------------------- -* fixed issue #1256: agency binary not found #1256 +* fixed issue #1256: agency binary not found #1256 * fixed issue #1230: API: document/col-name/_key and cursor return different floats @@ -2495,7 +2499,7 @@ v2.5.0-beta2 (2015-02-23) * etcd: Upgrade to version 2.0 - This requires go 1.3 to compile at least. -* refuse to startup if ICU wasn't initialized, this will i.e. prevent errors from being printed, +* refuse to startup if ICU wasn't initialized, this will i.e. prevent errors from being printed, and libraries from being loaded. * front-end: unwanted removal of index table header after creating new index @@ -2528,13 +2532,13 @@ v2.5.0-beta2 (2015-02-23) The development mode used until 2.4 is gone. It has been replaced by a much more mature version. This includes the deprecation of the javascript.dev-app-path parameter, which is useless since 2.5. - Instead of having two separate app directories for production and development, apps now reside in + Instead of having two separate app directories for production and development, apps now reside in one place, which is used for production as well as for development. Apps can still be put into development mode, changing their behavior compared to production mode. - Development mode apps are still reread from disk at every request, and still they ship more debug + Development mode apps are still reread from disk at every request, and still they ship more debug output. - This change has also made the startup options `--javascript.frontend-development-mode` and + This change has also made the startup options `--javascript.frontend-development-mode` and `--javascript.dev-app-path` obsolete. The former option will not have any effect when set, and the latter option is only read and used during the upgrade to 2.5 and does not have any effects later. @@ -2542,7 +2546,7 @@ v2.5.0-beta2 (2015-02-23) Installing Foxx apps has been a two step process: import them into ArangoDB and mount them at a specific mountpoint. These operations have been joined together. You can install an app at one - mountpoint, that's it. No fetch, mount, unmount, purge cycle anymore. The commands have been + mountpoint, that's it. No fetch, mount, unmount, purge cycle anymore. The commands have been simplified to just: * install: get your Foxx app up and running @@ -2553,7 +2557,7 @@ v2.5.0-beta2 (2015-02-23) Until 2.4 the errors produced by Foxx were not optimal. Often, the error message was just `unable to parse manifest` and contained only an internal stack trace. In 2.5 we made major improvements there, including a much more fine-grained error output that - helps you debug your Foxx apps. The error message printed is now much closer to its source and + helps you debug your Foxx apps. The error message printed is now much closer to its source and should help you track it down. Also we added the default handlers for unhandled errors in Foxx apps: @@ -2576,7 +2580,7 @@ v2.5.0-beta2 (2015-02-23) * added optimizer rule `propagate-constant-attributes` - This rule will look inside `FILTER` conditions for constant value equality comparisons, + This rule will look inside `FILTER` conditions for constant value equality comparisons, and insert the constant values in other places in `FILTER`s. For example, the rule will insert `42` instead of `i.value` in the second `FILTER` of the following query: @@ -2586,32 +2590,32 @@ v2.5.0-beta2 (2015-02-23) This value indicates how many documents were filtered by `FilterNode`s in the AQL query. Note that `IndexRangeNode`s can also filter documents by selecting only the required ranges - from the index. The `filtered` value will not include the work done by `IndexRangeNode`s, + from the index. The `filtered` value will not include the work done by `IndexRangeNode`s, but only the work performed by `FilterNode`s. * added support for sparse hash and skiplist indexes Hash and skiplist indexes can optionally be made sparse. Sparse indexes exclude documents in which at least one of the index attributes is either not set or has a value of `null`. - + As such documents are excluded from sparse indexes, they may contain fewer documents than their non-sparse counterparts. This enables faster indexing and can lead to reduced memory - usage in case the indexed attribute does occur only in some, but not all documents of the + usage in case the indexed attribute does occur only in some, but not all documents of the collection. Sparse indexes will also reduce the number of collisions in non-unique hash indexes in case non-existing or optional attributes are indexed. In order to create a sparse index, an object with the attribute `sparse` can be added to the index creation commands: - db.collection.ensureHashIndex(attributeName, { sparse: true }); - db.collection.ensureHashIndex(attributeName1, attributeName2, { sparse: true }); - db.collection.ensureUniqueConstraint(attributeName, { sparse: true }); - db.collection.ensureUniqueConstraint(attributeName1, attributeName2, { sparse: true }); + db.collection.ensureHashIndex(attributeName, { sparse: true }); + db.collection.ensureHashIndex(attributeName1, attributeName2, { sparse: true }); + db.collection.ensureUniqueConstraint(attributeName, { sparse: true }); + db.collection.ensureUniqueConstraint(attributeName1, attributeName2, { sparse: true }); - db.collection.ensureSkiplist(attributeName, { sparse: true }); - db.collection.ensureSkiplist(attributeName1, attributeName2, { sparse: true }); - db.collection.ensureUniqueSkiplist(attributeName, { sparse: true }); - db.collection.ensureUniqueSkiplist(attributeName1, attributeName2, { sparse: true }); + db.collection.ensureSkiplist(attributeName, { sparse: true }); + db.collection.ensureSkiplist(attributeName1, attributeName2, { sparse: true }); + db.collection.ensureUniqueSkiplist(attributeName, { sparse: true }); + db.collection.ensureUniqueSkiplist(attributeName1, attributeName2, { sparse: true }); Note that in place of the above specialized index creation commands, it is recommended to use the more general index creation command `ensureIndex`: @@ -2621,70 +2625,70 @@ v2.5.0-beta2 (2015-02-23) db.collection.ensureIndex({ type: "skiplist", sparse: false, unique: false, fields: [ "a", "b" ] }); ``` - When not explicitly set, the `sparse` attribute defaults to `false` for new indexes. - - This causes a change in behavior when creating a unique hash index without specifying the - sparse flag: in 2.4, unique hash indexes were implicitly sparse, always excluding `null` values. + When not explicitly set, the `sparse` attribute defaults to `false` for new indexes. + + This causes a change in behavior when creating a unique hash index without specifying the + sparse flag: in 2.4, unique hash indexes were implicitly sparse, always excluding `null` values. There was no option to control this behavior, and sparsity was neither supported for non-unique hash indexes nor skiplists in 2.4. This implicit sparsity of unique hash indexes was considered an inconsistency, and therefore the behavior was cleaned up in 2.5. As of 2.5, indexes will - only be created sparse if sparsity is explicitly requested. Existing unique hash indexes from 2.4 + only be created sparse if sparsity is explicitly requested. Existing unique hash indexes from 2.4 or before will automatically be migrated so they are still sparse after the upgrade to 2.5. - + Geo indexes are implicitly sparse, meaning documents without the indexed location attribute or containing invalid location coordinate values will be excluded from the index automatically. This is also a change when compared to pre-2.5 behavior, when documents with missing or invalid coordinate values may have caused errors on insertion when the geo index' `unique` flag was set - and its `ignoreNull` flag was not. - - This was confusing and has been rectified in 2.5. The method `ensureGeoConstaint()` now does the - same as `ensureGeoIndex()`. Furthermore, the attributes `constraint`, `unique`, `ignoreNull` and + and its `ignoreNull` flag was not. + + This was confusing and has been rectified in 2.5. The method `ensureGeoConstaint()` now does the + same as `ensureGeoIndex()`. Furthermore, the attributes `constraint`, `unique`, `ignoreNull` and `sparse` flags are now completely ignored when creating geo indexes. - The same is true for fulltext indexes. There is no need to specify non-uniqueness or sparsity for + The same is true for fulltext indexes. There is no need to specify non-uniqueness or sparsity for geo or fulltext indexes. They will always be non-unique and sparse. - As sparse indexes may exclude some documents, they cannot be used for every type of query. - Sparse hash indexes cannot be used to find documents for which at least one of the indexed - attributes has a value of `null`. For example, the following AQL query cannot use a sparse + As sparse indexes may exclude some documents, they cannot be used for every type of query. + Sparse hash indexes cannot be used to find documents for which at least one of the indexed + attributes has a value of `null`. For example, the following AQL query cannot use a sparse index, even if one was created on attribute `attr`: - FOR doc In collection - FILTER doc.attr == null + FOR doc In collection + FILTER doc.attr == null RETURN doc If the lookup value is non-constant, a sparse index may or may not be used, depending on the other types of conditions in the query. If the optimizer can safely determine that the lookup value cannot be `null`, a sparse index may be used. When uncertain, the optimizer will not make use of a sparse index in a query in order to produce correct results. - + For example, the following queries cannot use a sparse index on `attr` because the optimizer will not know beforehand whether the comparison values for `doc.attr` will include `null`: - FOR doc In collection - FILTER doc.attr == SOME_FUNCTION(...) + FOR doc In collection + FILTER doc.attr == SOME_FUNCTION(...) RETURN doc - FOR other IN otherCollection - FOR doc In collection - FILTER doc.attr == other.attr + FOR other IN otherCollection + FOR doc In collection + FILTER doc.attr == other.attr RETURN doc - Sparse skiplist indexes can be used for sorting if the optimizer can safely detect that the - index range does not include `null` for any of the index attributes. + Sparse skiplist indexes can be used for sorting if the optimizer can safely detect that the + index range does not include `null` for any of the index attributes. * inspection of AQL data-modification queries will now detect if the data-modification part of the query can run in lockstep with the data retrieval part of the query, or if the data retrieval part must be executed before the data modification can start. - Executing the two in lockstep allows using much smaller buffers for intermediate results + Executing the two in lockstep allows using much smaller buffers for intermediate results and starts the actual data-modification operations much earlier than if the two phases were executed separately. * Allow dynamic attribute names in AQL object literals - + This allows using arbitrary expressions to construct attribute names in object - literals specified in AQL queries. To disambiguate expressions and other unquoted + literals specified in AQL queries. To disambiguate expressions and other unquoted attribute names, dynamic attribute names need to be enclosed in brackets (`[` and `]`). Example: @@ -2719,7 +2723,7 @@ v2.5.0-beta2 (2015-02-23) If provided, the selectivity estimate will be a numeric value between 0 and 1. Selectivity estimates will also be reported in the result of `collection.getIndexes()` - for all indexes that support this. If no selectivity estimate can be determined for + for all indexes that support this. If no selectivity estimate can be determined for an index, the attribute `selectivityEstimate` will be omitted here, too. The web interface also shows selectivity estimates for each index that supports this. @@ -2750,7 +2754,7 @@ v2.5.0-beta2 (2015-02-23) * enforce that AQL user functions are wrapped inside JavaScript function () declarations - AQL user functions were always expected to be wrapped inside a JavaScript function, but previously + AQL user functions were always expected to be wrapped inside a JavaScript function, but previously this was not enforced when registering a user function. Enforcing the AQL user functions to be contained inside functions prevents functions from doing some unexpected things that may have led to undefined behavior. @@ -2783,7 +2787,7 @@ v2.5.0-beta2 (2015-02-23) v2.4.7 (XXXX-XX-XX) ------------------- -* fixed issue #1282: Geo WITHIN_RECTANGLE for nested lat/lng +* fixed issue #1282: Geo WITHIN_RECTANGLE for nested lat/lng v2.4.6 (2015-03-18) @@ -2804,7 +2808,7 @@ v2.4.6 (2015-03-18) the datafile being partially loaded. All data up to until the mismatch will be loaded. This will enable users to continue with a collection datafiles that are corrupted, but will result in only a partial load of the data. - The WAL recovery will still abort when encountering a collection with a + The WAL recovery will still abort when encountering a collection with a corrupted datafile, at least if `--wal.ignore-recovery-errors` is not set to `true`. @@ -2824,8 +2828,8 @@ v2.4.6 (2015-03-18) * datafiles and `parameter.json` files written by arangod are now created with read and write privileges for the arangod process user, and with read and write privileges for the arangod - process group. - + process group. + Previously, these files were created with user read and write permissions only. * INCOMPATIBLE CHANGE: @@ -2899,14 +2903,14 @@ v2.4.3 (2015-02-06) * fix timeout on socket operations when running under Windows * Fixed an error in Foxx routing which caused some apps that worked in 2.4.1 to fail with status 500: `undefined is not a function` errors in 2.4.2 - This error was occurring due to seldom internal rerouting introduced by the malformed application handler. + This error was occurring due to seldom internal rerouting introduced by the malformed application handler. v2.4.2 (2015-01-30) ------------------- * added custom visitor functionality for AQL traversals - + This allows more complex result processing in traversals triggered by AQL. A few examples are shown in [this article](http://jsteemann.github.io/blog/2015/01/28/using-custom-visitors-in-aql-graph-traversals/). @@ -2920,7 +2924,7 @@ v2.4.2 (2015-01-30) require("org/arangodb/aql/explainer").explain(query); -* enable use of indexes for certain AQL conditions with non-equality predicates, in +* enable use of indexes for certain AQL conditions with non-equality predicates, in case the condition(s) also refer to indexed attributes The following queries will now be able to use indexes: @@ -2948,13 +2952,13 @@ v2.4.2 (2015-01-30) In Development Mode the html page also contains information about the error occurred. * Unhandled errors thrown in Foxx routes are now handled by the Foxx framework itself. - + In Production the route will return a status 500 with a body {error: "Error statement"}. In Development the route will return a status 500 with a body {error: "Error statement", stack: "..."} Before, it was status 500 with a plain text stack including ArangoDB internal routing information. - -* The Applications tab in web interface will now request development apps more often. + +* The Applications tab in web interface will now request development apps more often. So if you have a fixed a syntax error in your app it should always be visible after reload. @@ -2969,7 +2973,7 @@ v2.4.1 (2015-01-19) * fixed invalid result of HTTP REST API method `/_admin/foxx/rescan` -* fixed possible segmentation fault when passing a Buffer object into a V8 function +* fixed possible segmentation fault when passing a Buffer object into a V8 function as a parameter * updated AQB module to 1.8.0. @@ -2983,7 +2987,7 @@ v2.4.0 (2015-01-13) * fixed V8 integration-related crashes * make `fs.move(src, dest)` also fail when both `src` and `dest` are - existing directories. This ensures the same behavior of the move operation + existing directories. This ensures the same behavior of the move operation on different platforms. * fixed AQL insert operation for multi-shard collections in cluster @@ -3041,12 +3045,12 @@ v2.4.0-beta1 (2014-12-26) * constants * enhanced object and numeric literals - To activate all these ES6 features in arangod or arangosh, start it with + To activate all these ES6 features in arangod or arangosh, start it with the following options: arangosh --javascript.v8-options="--harmony --harmony_generators" - More details on the available ES6 features can be found in + More details on the available ES6 features can be found in [this blog](https://jsteemann.github.io/blog/2014/12/19/using-es6-features-in-arangodb/). * Added Foxx generator for building Hypermedia APIs @@ -3055,10 +3059,10 @@ v2.4.0-beta1 (2014-12-26) * New `Applications` tab in web interface: - The `applications` tab got a complete redesign. - It will now only show applications that are currently running on ArangoDB. + The `applications` tab got a complete redesign. + It will now only show applications that are currently running on ArangoDB. For a selected application, a new detailed view has been created. - This view provides a better overview of the app: + This view provides a better overview of the app: * author * license * version @@ -3066,19 +3070,19 @@ v2.4.0-beta1 (2014-12-26) * download links * API documentation - To install a new application, a new dialog is now available. - It provides the features already available in the console application `foxx-manager` plus some more: + To install a new application, a new dialog is now available. + It provides the features already available in the console application `foxx-manager` plus some more: * install an application from Github * install an application from a zip file * install an application from ArangoDB's application store * create a new application from scratch: this feature uses a generator to create a Foxx application with pre-defined CRUD methods for a given list - of collections. The generated Foxx app can either be downloaded as a zip file or + of collections. The generated Foxx app can either be downloaded as a zip file or be installed on the server. Starting with a new Foxx app has never been easier. -* fixed issue #1102: Aardvark: Layout bug in documents overview +* fixed issue #1102: Aardvark: Layout bug in documents overview - The documents overview was entirely destroyed in some situations on Firefox. + The documents overview was entirely destroyed in some situations on Firefox. We replaced the plugin we used there. * fixed issue #1168: Aardvark: pagination buttons jumping @@ -3089,30 +3093,30 @@ v2.4.0-beta1 (2014-12-26) and `--enable-all-in-one-libev`. * global internal rename to fix naming incompatibilities with JSON: - - Internal functions with names containing `array` have been renamed to `object`, + + Internal functions with names containing `array` have been renamed to `object`, internal functions with names containing `list` have been renamed to `array`. The renaming was mainly done in the C++ parts. The documentation has also been adjusted so that the correct JSON type names are used in most places. - + The change also led to the addition of a few function aliases in AQL: * `TO_LIST` now is an alias of the new `TO_ARRAY` * `IS_LIST` now is an alias of the new `IS_ARRAY` * `IS_DOCUMENT` now is an alias of the new `IS_OBJECT` - The changed also renamed the option `mergeArrays` to `mergeObjects` for AQL + The changed also renamed the option `mergeArrays` to `mergeObjects` for AQL data-modification query options and HTTP document modification API * AQL: added optimizer rule "remove-filter-covered-by-index" - + This rule removes FilterNodes and CalculationNodes from an execution plan if the filter is already covered by a previous IndexRangeNode. Removing the CalculationNode and the FilterNode will speed up query execution because the query requires less computation. * AQL: added optimizer rule "remove-sort-rand" - + This rule removes a `SORT RAND()` expression from a query and moves the random iteration into the appropriate `EnumerateCollectionNode`. This is more efficient than individually enumerating and then sorting randomly. @@ -3129,7 +3133,7 @@ v2.4.0-beta1 (2014-12-26) FOR doc IN collection FILTER doc.indexedAttribute == 1 || doc.indexedAttribute > 99 RETURN doc - + FOR doc IN collection FILTER doc.indexedAttribute IN [ 3, 42 ] || doc.indexedAttribute > 99 RETURN doc @@ -3149,13 +3153,13 @@ v2.4.0-beta1 (2014-12-26) This allows more efficient group count calculation queries, e.g. FOR doc IN collection - COLLECT age = doc.age WITH COUNT INTO length + COLLECT age = doc.age WITH COUNT INTO length RETURN { age: age, count: length } A count-only query is also possible: - + FOR doc IN collection - COLLECT WITH COUNT INTO length + COLLECT WITH COUNT INTO length RETURN length * fixed missing makeDirectory when fetching a Foxx application from a zip file @@ -3164,9 +3168,9 @@ v2.4.0-beta1 (2014-12-26) This change will modify the IP address ArangoDB listens on to 127.0.0.1 by default. This will make new ArangoDB installations unaccessible from clients other than - localhost unless changed. This is a security feature. + localhost unless changed. This is a security feature. - To make ArangoDB accessible from any client, change the server's configuration + To make ArangoDB accessible from any client, change the server's configuration (`--server.endpoint`) to either `tcp://0.0.0.0:8529` or the server's publicly visible IP address. @@ -3174,7 +3178,7 @@ v2.4.0-beta1 (2014-12-26) * IMPORTANT CHANGE: by default, system collections are included in replication and all replication API return values. This will lead to user accounts and credentials - data being replicated from master to slave servers. This may overwrite + data being replicated from master to slave servers. This may overwrite slave-specific database users. If this is undesired, the `_users` collection can be excluded from replication @@ -3189,11 +3193,11 @@ v2.4.0-beta1 (2014-12-26) If this is also undesired, it is also possible to specify a list of collections to exclude from the initial synchronization and the continuous replication using the `restrictCollections` attribute, e.g.: - - replication.applier.properties({ + + replication.applier.properties({ includeSystem: true, restrictType: "exclude", - restrictCollections: [ "_users", "_graphs", "foo" ] + restrictCollections: [ "_users", "_graphs", "foo" ] }); The HTTP API methods for fetching the replication inventory and for dumping collections @@ -3203,10 +3207,10 @@ v2.4.0-beta1 (2014-12-26) * `replication.logger.start()` * `replication.logger.stop()` * `replication.logger.properties()` - * HTTP PUT `/_api/replication/logger-start` - * HTTP PUT `/_api/replication/logger-stop` - * HTTP GET `/_api/replication/logger-config` - * HTTP PUT `/_api/replication/logger-config` + * HTTP PUT `/_api/replication/logger-start` + * HTTP PUT `/_api/replication/logger-stop` + * HTTP GET `/_api/replication/logger-config` + * HTTP PUT `/_api/replication/logger-config` * fixed issue #1174, which was due to locking problems in distributed AQL execution @@ -3220,7 +3224,7 @@ v2.4.0-beta1 (2014-12-26) v2.3.6 (2015-XX-XX) ------------------- -* fixed AQL subquery optimization that produced wrong result when multiple subqueries +* fixed AQL subquery optimization that produced wrong result when multiple subqueries directly followed each other and and a directly following `LET` statement did refer to any but the first subquery. @@ -3244,7 +3248,7 @@ v2.3.4 (2014-12-23) v2.3.3 (2014-12-17) ------------------- -* fixed error handling in instantiation of distributed AQL queries, this +* fixed error handling in instantiation of distributed AQL queries, this also fixes a bug in cluster startup with many servers * issue #1185: parse non-fractional JSON numbers with exponent (e.g. `4e-261`) @@ -3265,7 +3269,7 @@ v2.3.2 (2014-12-09) * fixed issue #1163: invalid fullCount value returned from AQL -* fixed range operator precedence +* fixed range operator precedence * limit default maximum number of plans created by AQL optimizer to 256 (from 1024) @@ -3286,7 +3290,7 @@ v2.3.2 (2014-12-09) This change provides the `KEEP` clause for `COLLECT ... INTO`. The `KEEP` clause allows controlling which variables will be kept in the variable created by `INTO`. - + * fixed issue #1147, must protect dispatcher ID for etcd v2.3.1 (2014-11-28) @@ -3320,11 +3324,11 @@ v2.3.1 (2014-11-28) server is idle * fixed AQL COLLECT statement with INTO clause, which copied more variables - than v2.2 and thus lead to too much memory consumption. + than v2.2 and thus lead to too much memory consumption. This deals with #1107. -* fixed AQL COLLECT statement, this concerned every COLLECT statement, - only the first group had access to the values of the variables before +* fixed AQL COLLECT statement, this concerned every COLLECT statement, + only the first group had access to the values of the variables before the COLLECT statement. This deals with #1127. * fixed some AQL internals, where sometimes too many items were @@ -3335,24 +3339,24 @@ v2.3.1 (2014-11-28) v2.3.0 (2014-11-18) ------------------- -* fixed syslog flags. `--log.syslog` is deprecated and setting it has no effect, +* fixed syslog flags. `--log.syslog` is deprecated and setting it has no effect, `--log.facility` now works as described. Application name has been changed from `triagens` to `arangod`. It can be changed using `--log.application`. The syslog will only contain the actual log message. The datetime prefix is omitted. * fixed deflate in SimpleHttpClient -* fixed issue #1104: edgeExamples broken or changed +* fixed issue #1104: edgeExamples broken or changed * fixed issue #1103: Error while importing user queries -* fixed issue #1100: AQL: HAS() fails on doc[attribute_name] +* fixed issue #1100: AQL: HAS() fails on doc[attribute_name] * fixed issue #1098: runtime error when creating graph vertex * hide system applications in **Applications** tab by default - Display of system applications can be toggled by using the *system applications* + Display of system applications can be toggled by using the *system applications* toggle in the UI. * added HTTP REST API for managing tasks (`/_api/tasks`) @@ -3366,7 +3370,7 @@ v2.3.0 (2014-11-18) TRIM(" foobar\t \r\n ") // "foobar" TRIM(";foo;bar;baz, ", "; ") // "foo;bar;baz" -* added AQL string functions `LTRIM`, `RTRIM`, `FIND_FIRST`, `FIND_LAST`, `SPLIT`, +* added AQL string functions `LTRIM`, `RTRIM`, `FIND_FIRST`, `FIND_LAST`, `SPLIT`, `SUBSTITUTE` * added AQL functions `ZIP`, `VALUES` and `PERCENTILE` @@ -3389,7 +3393,7 @@ v2.3.0-beta2 (2014-11-08) * front-end: fixed missing event for documents filter function -* front-end: jsoneditor: added CMD+Return (Mac) CTRL+Return (Linux/Win) shortkey for +* front-end: jsoneditor: added CMD+Return (Mac) CTRL+Return (Linux/Win) shortkey for saving a document * front-end: added information tooltip for uploading json documents. @@ -3409,13 +3413,13 @@ v2.3.0-beta2 (2014-11-08) * removed debug print message in AQL editor query export routine -* fixed issue #1075: Aardvark: user name required even if auth is off #1075 +* fixed issue #1075: Aardvark: user name required even if auth is off #1075 The fix for this prefills the username input field with the current user's account name if any and `root` (the default username) otherwise. Additionally, the tooltip text has been slightly adjusted. -* fixed issue #1069: Add 'raw' link to swagger ui so that the raw swagger +* fixed issue #1069: Add 'raw' link to swagger ui so that the raw swagger json can easily be retrieved This adds a link to the Swagger API docs to an application's detail view in @@ -3432,7 +3436,7 @@ v2.3.0-beta1 (2014-11-01) * added dedicated `NOT IN` operator for AQL Previously, a `NOT IN` was only achievable by writing a negated `IN` condition: - + FOR i IN ... FILTER ! (i IN [ 23, 42 ]) ... This can now alternatively be expressed more intuitively as follows: @@ -3451,12 +3455,12 @@ v2.3.0-beta1 (2014-11-01) - `OR`: logical or - `NOT`: negation - The new syntax is just an alternative to the old syntax, allowing easier + The new syntax is just an alternative to the old syntax, allowing easier migration from SQL. The old syntax is still fully supported and will be. * improved output of `ArangoStatement.parse()` and POST `/_api/query` - If an AQL query can be parsed without problems, The return value of + If an AQL query can be parsed without problems, The return value of `ArangoStatement.parse()` now contains an attribute `ast` with the abstract syntax tree of the query (before optimizations). Though this is an internal representation of the query and is subject to change, it can be used to inspect @@ -3466,17 +3470,17 @@ v2.3.0-beta1 (2014-11-01) The commands for explaining AQL queries have been improved. -* added command-line option `--javascript.v8-contexts` to control the number of +* added command-line option `--javascript.v8-contexts` to control the number of V8 contexts created in arangod. - + Previously, the number of V8 contexts was equal to the number of server threads - (as specified by option `--server.threads`). + (as specified by option `--server.threads`). However, it may be sensible to create different amounts of threads and V8 contexts. If the option is not specified, the number of V8 contexts created will be equal to the number of server threads. Thus no change in configuration is required to keep the old behavior. - + If you are using the default config files or merge them with your local config files, please review if the default number of server threads is okay in your environment. Additionally you should verify that the number of V8 contexts @@ -3489,16 +3493,16 @@ v2.3.0-beta1 (2014-11-01) * removed index type "bitarray" - Bitarray indexes were only half-way documented and integrated in previous versions + Bitarray indexes were only half-way documented and integrated in previous versions of ArangoDB so their benefit was limited. The support for bitarray indexes has thus been removed in ArangoDB 2.3. It is not possible to create indexes of type "bitarray" with ArangoDB 2.3. - When a collection is opened that contains a bitarray index definition created + When a collection is opened that contains a bitarray index definition created with a previous version of ArangoDB, ArangoDB will ignore it and log the following warning: - index type 'bitarray' is not supported in this version of ArangoDB and is ignored + index type 'bitarray' is not supported in this version of ArangoDB and is ignored Future versions of ArangoDB may automatically remove such index definitions so the warnings will eventually disappear. @@ -3515,7 +3519,7 @@ v2.3.0-beta1 (2014-11-01) the individual components of a multipart HTTP request. Buffer objects can now be used when setting the response body of any Foxx action. - Additionally, `res.send()` has been added as a convenience method for returning + Additionally, `res.send()` has been added as a convenience method for returning strings, JSON objects or buffers from a Foxx action: res.send("

some HTML

"); @@ -3545,12 +3549,12 @@ v2.3.0-beta1 (2014-11-01) * command-line options that require a boolean value now validate the value given on the command-line - This prevents issues if no value is specified for an option that - requires a boolean value. For example, the following command-line would - have caused trouble in 2.2, because `--server.endpoint` would have been + This prevents issues if no value is specified for an option that + requires a boolean value. For example, the following command-line would + have caused trouble in 2.2, because `--server.endpoint` would have been used as the value for the `--server.disable-authentication` options (which requires a boolean value): - + arangod --server.disable-authentication --server.endpoint tcp://127.0.0.1:8529 data In 2.3, running this command will fail with an error and requires to @@ -3562,13 +3566,13 @@ v2.3.0-beta1 (2014-11-01) * fixed issue #1027: Stack traces are off-by-one -* fixed issue #1026: Modules loaded in different files within the same app +* fixed issue #1026: Modules loaded in different files within the same app should refer to the same module * fixed issue #1025: Traversal not as expected in undirected graph * added a _relation function in the general-graph module. - + This deprecated _directedRelation and _undirectedRelation. ArangoDB does not offer any constraints for undirected edges which caused some confusion of users how undirected relations @@ -3681,7 +3685,7 @@ v2.2.7 (2014-11-19) * fixed issue #1063: Docs: measuring unit of --wal.logfile-size? -* fixed issue #1062: Docs: typo in 14.2 Example data +* fixed issue #1062: Docs: typo in 14.2 Example data v2.2.6 (2014-10-20) @@ -3719,13 +3723,13 @@ v2.2.5 (2014-10-09) v2.2.4 (2014-10-01) ------------------- -* fixed accessing `_from` and `_to` attributes in `collection.byExample` and +* fixed accessing `_from` and `_to` attributes in `collection.byExample` and `collection.firstExample` - These internal attributes were not handled properly in the mentioned functions, so + These internal attributes were not handled properly in the mentioned functions, so searching for them did not always produce documents -* fixed issue #1030: arangoimp 2.2.3 crashing, not logging on large Windows CSV file +* fixed issue #1030: arangoimp 2.2.3 crashing, not logging on large Windows CSV file * fixed issue #1025: Traversal not as expected in undirected graph @@ -3733,17 +3737,17 @@ v2.2.4 (2014-10-01) This requires re-introducing the startup option `--database.force-sync-properties`. - This option can again be used to force fsyncs of collection, index and database properties + This option can again be used to force fsyncs of collection, index and database properties stored as JSON strings on disk in files named `parameter.json`. Syncing these files after a write may be necessary if the underlying storage does not sync file contents by itself in a "sensible" amount of time after a file has been written and closed. The default value is `true` so collection, index and database properties will always be - synced to disk immediately. This affects creating, renaming and dropping collections as + synced to disk immediately. This affects creating, renaming and dropping collections as well as creating and dropping databases and indexes. Each of these operations will perform - an additional fsync on the `parameter.json` file if the option is set to `true`. + an additional fsync on the `parameter.json` file if the option is set to `true`. - It might be sensible to set this option to `false` for workloads that create and drop a + It might be sensible to set this option to `false` for workloads that create and drop a lot of collections (e.g. test runs). Document operations such as creating, updating and dropping documents are not affected @@ -3756,13 +3760,13 @@ v2.2.4 (2014-10-01) * fixed AQL shortest path calculation in function `GRAPH_SHORTEST_PATH` to return complete vertex objects instead of just vertex ids -* allow changing of attributes of documents stored in server-side JavaScript variables +* allow changing of attributes of documents stored in server-side JavaScript variables Previously, the following did not work: var doc = db.collection.document(key); doc._key = "abc"; // overwriting internal attributes not supported - doc.value = 123; // overwriting existing attributes not supported + doc.value = 123; // overwriting existing attributes not supported Now, modifying documents stored in server-side variables (e.g. `doc` in the above case) is supported. Modifying the variables will not update the documents in the database, @@ -3771,7 +3775,7 @@ v2.2.4 (2014-10-01) * fixed issue #997: arangoimp apparently doesn't support files >2gig on Windows - large file support (requires using `_stat64` instead of `stat`) is now supported on + large file support (requires using `_stat64` instead of `stat`) is now supported on Windows @@ -3783,7 +3787,7 @@ v2.2.3 (2014-09-02) * added `type` option for HTTP API `GET /_api/document?collection=...` This allows controlling the type of results to be returned. By default, paths to - documents will be returned, e.g. + documents will be returned, e.g. [ `/_api/document/test/mykey1`, @@ -3791,7 +3795,7 @@ v2.2.3 (2014-09-02) ... ] - To return a list of document ids instead of paths, the `type` URL parameter can be + To return a list of document ids instead of paths, the `type` URL parameter can be set to `id`: [ @@ -3823,7 +3827,7 @@ v2.2.2 (2014-08-08) * allow storing non-reserved attribute names starting with an underscore - Previous versions of ArangoDB parsed away all attribute names that started with an + Previous versions of ArangoDB parsed away all attribute names that started with an underscore (e.g. `_test', '_foo', `_bar`) on all levels of a document (root level and sub-attribute levels). While this behavior was documented, it was unintuitive and prevented storing documents inside other documents, e.g.: @@ -3837,7 +3841,7 @@ v2.2.2 (2014-08-08) "_rev" : "...", "value" : 1 }, - { + { "_key" : "something else", "_rev" : "...", "value" : 2 @@ -3890,21 +3894,21 @@ v2.2.1 (2014-07-24) write-ahead log for replication, so it should be set to `false` for any replication master servers. - The default value for this option is `false`. + The default value for this option is `false`. -* added optional `ttl` attribute to specify result cursor expiration for HTTP API method - `POST /_api/cursor` +* added optional `ttl` attribute to specify result cursor expiration for HTTP API method + `POST /_api/cursor` The `ttl` attribute can be used to prevent cursor results from timing out too early. * issue #947: Foxx applicationContext missing some properties -* (reported by Christian Neubauer): +* (reported by Christian Neubauer): The problem was that in Google's V8, signed and unsigned chars are not always declared cleanly. so we need to force v8 to compile with forced signed chars which is done by the Flag: -fsigned-char - at least it is enough to follow the instructions of compiling arango on rasperry + at least it is enough to follow the instructions of compiling arango on rasperry and add "CFLAGS='-fsigned-char'" to the make command of V8 and remove the armv7=0 * Fixed a bug with the replication client. In the case of single document @@ -3916,34 +3920,34 @@ v2.2.0 (2014-07-10) * The replication methods `logger.start`, `logger.stop` and `logger.properties` are no-ops in ArangoDB 2.2 as there is no separate replication logger anymore. Data changes - are logged into the write-ahead log in ArangoDB 2.2, and not separately by the + are logged into the write-ahead log in ArangoDB 2.2, and not separately by the replication logger. The replication logger object is still there in ArangoDB 2.2 to - ensure backwards-compatibility, however, logging cannot be started, stopped or + ensure backwards-compatibility, however, logging cannot be started, stopped or configured anymore. Using any of these methods will do nothing. This also affects the following HTTP API methods: - `PUT /_api/replication/logger-start` - `PUT /_api/replication/logger-stop` - - `GET /_api/replication/logger-config` + - `GET /_api/replication/logger-config` - `PUT /_api/replication/logger-config` - Using any of these methods is discouraged from now on as they will be removed in + Using any of these methods is discouraged from now on as they will be removed in future versions of ArangoDB. * INCOMPATIBLE CHANGE: replication of transactions has changed. Previously, transactions were logged on a master in one big block and shipped to a slave in one block, too. Now transactions will be logged and replicated as separate entries, allowing transactions to be bigger and also ensure replication progress. - + This change also affects the behavior of the `stop` method of the replication applier. - If the replication applier is now stopped manually using the `stop` method and later + If the replication applier is now stopped manually using the `stop` method and later restarted using the `start` method, any transactions that were unfinished at the point of stopping will be aborted on a slave, even if they later commit on the master. In ArangoDB 2.2, stopping the replication applier manually should be avoided unless the goal is to stop replication permanently or to do a full resync with the master anyway. If the replication applier still must be stopped, it should be made sure that the - slave has fetched and applied all pending operations from a master, and that no + slave has fetched and applied all pending operations from a master, and that no extra transactions are started on the master before the `stop` command on the slave is executed. @@ -3964,24 +3968,24 @@ v2.2.0 (2014-07-10) resource usage of a collection. Additionally, the attributes `lastTick` and `uncollectedLogfileEntries` have been - added to the result of the `figures` operation and the HTTP API method + added to the result of the `figures` operation and the HTTP API method `PUT /_api/collection/figures` * added `insert` method as an alias for `save`. Documents can now be inserted into a collection using either method: - - db.test.save({ foo: "bar" }); - db.test.insert({ foo: "bar" }); + + db.test.save({ foo: "bar" }); + db.test.insert({ foo: "bar" }); * added support for data-modification AQL queries * added AQL keywords `INSERT`, `UPDATE`, `REPLACE` and `REMOVE` (and `WITH`) to support data-modification AQL queries. - + Unquoted usage of these keywords for attribute names in AQL queries will likely fail in ArangoDB 2.2. If any such attribute name needs to be used in a query, it should be enclosed in backticks to indicate the usage of a literal attribute - name. + name. For example, the following query will fail in ArangoDB 2.2 with a parse error: @@ -4004,7 +4008,7 @@ v2.2.0 (2014-07-10) Previously, objects of these types were silently converted into an empty object (i.e. `{ }`). - To store such objects in a collection, explicitly convert them into strings + To store such objects in a collection, explicitly convert them into strings like this: db.test.save({ foo: String(/bar/) }); @@ -4012,34 +4016,34 @@ v2.2.0 (2014-07-10) * The replication methods `logger.start`, `logger.stop` and `logger.properties` are no-ops in ArangoDB 2.2 as there is no separate replication logger anymore. Data changes - are logged into the write-ahead log in ArangoDB 2.2, and not separately by the + are logged into the write-ahead log in ArangoDB 2.2, and not separately by the replication logger. The replication logger object is still there in ArangoDB 2.2 to - ensure backwards-compatibility, however, logging cannot be started, stopped or + ensure backwards-compatibility, however, logging cannot be started, stopped or configured anymore. Using any of these methods will do nothing. This also affects the following HTTP API methods: - `PUT /_api/replication/logger-start` - `PUT /_api/replication/logger-stop` - - `GET /_api/replication/logger-config` + - `GET /_api/replication/logger-config` - `PUT /_api/replication/logger-config` - Using any of these methods is discouraged from now on as they will be removed in + Using any of these methods is discouraged from now on as they will be removed in future versions of ArangoDB. * INCOMPATIBLE CHANGE: replication of transactions has changed. Previously, transactions were logged on a master in one big block and shipped to a slave in one block, too. Now transactions will be logged and replicated as separate entries, allowing transactions to be bigger and also ensure replication progress. - + This change also affects the behavior of the `stop` method of the replication applier. - If the replication applier is now stopped manually using the `stop` method and later + If the replication applier is now stopped manually using the `stop` method and later restarted using the `start` method, any transactions that were unfinished at the point of stopping will be aborted on a slave, even if they later commit on the master. In ArangoDB 2.2, stopping the replication applier manually should be avoided unless the goal is to stop replication permanently or to do a full resync with the master anyway. If the replication applier still must be stopped, it should be made sure that the - slave has fetched and applied all pending operations from a master, and that no + slave has fetched and applied all pending operations from a master, and that no extra transactions are started on the master before the `stop` command on the slave is executed. @@ -4060,24 +4064,24 @@ v2.2.0 (2014-07-10) resource usage of a collection. Additionally, the attributes `lastTick` and `uncollectedLogfileEntries` have been - added to the result of the `figures` operation and the HTTP API method + added to the result of the `figures` operation and the HTTP API method `PUT /_api/collection/figures` * added `insert` method as an alias for `save`. Documents can now be inserted into a collection using either method: - - db.test.save({ foo: "bar" }); - db.test.insert({ foo: "bar" }); + + db.test.save({ foo: "bar" }); + db.test.insert({ foo: "bar" }); * added support for data-modification AQL queries * added AQL keywords `INSERT`, `UPDATE`, `REPLACE` and `REMOVE` (and `WITH`) to support data-modification AQL queries. - + Unquoted usage of these keywords for attribute names in AQL queries will likely fail in ArangoDB 2.2. If any such attribute name needs to be used in a query, it should be enclosed in backticks to indicate the usage of a literal attribute - name. + name. For example, the following query will fail in ArangoDB 2.2 with a parse error: @@ -4100,7 +4104,7 @@ v2.2.0 (2014-07-10) Previously, objects of these types were silently converted into an empty object (i.e. `{ }`). - To store such objects in a collection, explicitly convert them into strings + To store such objects in a collection, explicitly convert them into strings like this: db.test.save({ foo: String(/bar/) }); @@ -4122,36 +4126,36 @@ v2.2.0 (2014-07-10) * removed startup option `--database.force-sync-properties` - This option is now superfluous as collection properties are now stored in the + This option is now superfluous as collection properties are now stored in the write-ahead log. * introduced write-ahead log All write operations in an ArangoDB server instance are automatically logged - to the server's write-ahead log. The write-ahead log is a set of append-only + to the server's write-ahead log. The write-ahead log is a set of append-only logfiles, and it is used in case of a crash recovery and for replication. Data from the write-ahead log will eventually be moved into the journals or - datafiles of collections, allowing the server to remove older write-ahead log + datafiles of collections, allowing the server to remove older write-ahead log logfiles. Figures of collections will be updated when data are moved from the write-ahead log into the journals or datafiles of collections. Cross-collection transactions in ArangoDB should benefit considerably by this - change, as less writes than in previous versions are required to ensure the data - of multiple collections are atomically and durably committed. All data-modifying - operations inside transactions (insert, update, remove) will write their - operations into the write-ahead log directly, making transactions with multiple + change, as less writes than in previous versions are required to ensure the data + of multiple collections are atomically and durably committed. All data-modifying + operations inside transactions (insert, update, remove) will write their + operations into the write-ahead log directly, making transactions with multiple operations also require less physical memory than in previous versions of ArangoDB, that required all transaction data to fit into RAM. - + The `_trx` system collection is not used anymore in ArangoDB 2.2 and its usage is discouraged. The data in the write-ahead log can also be used in the replication context. - The `_replication` collection that was used in previous versions of ArangoDB to - store all changes on the server is not used anymore in ArangoDB 2.2. Instead, + The `_replication` collection that was used in previous versions of ArangoDB to + store all changes on the server is not used anymore in ArangoDB 2.2. Instead, slaves can read from a master's write-ahead log to get informed about most - recent changes. This removes the need to store data-modifying operations in - both the actual place and the `_replication` collection. + recent changes. This removes the need to store data-modifying operations in + both the actual place and the `_replication` collection. * removed startup option `--server.disable-replication-logger` @@ -4163,12 +4167,12 @@ v2.2.0 (2014-07-10) * changed behavior of replication logger - There is no dedicated replication logger in ArangoDB 2.2 as there is the + There is no dedicated replication logger in ArangoDB 2.2 as there is the write-ahead log now. The existing APIs for starting and stopping the replication logger still exist in ArangoDB 2.2 for downwards-compatibility, but calling the start or stop operations are no-ops in ArangoDB 2.2. When querying the replication logger status via the API, the server will always report that the - replication logger is running. Configuring the replication logger is a no-op + replication logger is running. Configuring the replication logger is a no-op in ArangoDB 2.2, too. Changing the replication logger configuration has no effect. Instead, the write-ahead log configuration can be changed. @@ -4184,7 +4188,7 @@ v2.2.0 (2014-07-10) - `--ruby.modules-path` - `--ruby.startup-directory` - Specifying these startup options will do nothing in ArangoDB 2.2, but the + Specifying these startup options will do nothing in ArangoDB 2.2, but the options should be avoided from now on as they might be removed in future versions. * reclaim index memory when last document in collection is deleted @@ -4195,7 +4199,7 @@ v2.2.0 (2014-07-10) Now, index memory for primary indexes and hash indexes is reclaimed instantly when the last document from a collection is removed. - + * inlined and optimized functions in hash indexes * added AQL TRANSLATE function @@ -4203,7 +4207,7 @@ v2.2.0 (2014-07-10) This function can be used to perform lookups from static lists, e.g. LET countryNames = { US: "United States", UK: "United Kingdom", FR: "France" } - RETURN TRANSLATE("FR", countryNames) + RETURN TRANSLATE("FR", countryNames) * fixed datafile debugger @@ -4213,7 +4217,7 @@ v2.2.0 (2014-07-10) * added mountedApp function for foxx-manager -* fixed issue #883: arango 2.1 - when starting multi-machine cluster, UI web +* fixed issue #883: arango 2.1 - when starting multi-machine cluster, UI web does not change to cluster overview * fixed dfdb: should not start any other V8 threads @@ -4221,10 +4225,10 @@ v2.2.0 (2014-07-10) * cleanup of version-check, added module org/arangodb/database-version, added --check-version option -* fixed issue #881: [2.1.0] Bombarded (every 10 sec or so) with +* fixed issue #881: [2.1.0] Bombarded (every 10 sec or so) with "WARNING format string is corrupt" when in non-system DB Dashboard -* specialized primary index implementation to allow faster hash table +* specialized primary index implementation to allow faster hash table rebuilding and reduce lookups in datafiles for the actual value of `_key`. * issue #862: added `--overwrite` option to arangoimp @@ -4241,10 +4245,10 @@ v2.2.0 (2014-07-10) * removed sorting of attribute names for use in a collection's shaper - sorting attribute names was done on document insert to keep attributes + sorting attribute names was done on document insert to keep attributes of a collection in sorted order for faster comparisons. The sort order of attributes was only used in one particular and unlikely case, so it - was removed. Collections with many different attribute names should + was removed. Collections with many different attribute names should benefit from this change by faster inserts and slightly less memory usage. * fixed a bug in arangodump which got the collection name in _from and _to @@ -4273,8 +4277,8 @@ v2.1.1 (2014-06-06) replaceBySample update updateBySample - - Old signature is yet supported but it will be removed in future versions + + Old signature is yet supported but it will be removed in future versions v2.1.0 (2014-05-29) ------------------- @@ -4305,7 +4309,7 @@ v2.1.0-rc1 (XXXX-XX-XX) * added server-side periodic task management functions: - require("org/arangodb/tasks").register(): registers a periodic task - - require("org/arangodb/tasks").unregister(): unregisters and removes a + - require("org/arangodb/tasks").unregister(): unregisters and removes a periodic task - require("org/arangodb/tasks").get(): retrieves a specific tasks or all existing tasks @@ -4313,7 +4317,7 @@ v2.1.0-rc1 (XXXX-XX-XX) the previous undocumented function `internal.definePeriodic` is now deprecated and will be removed in a future release. -* decrease the size of some seldom used system collections on creation. +* decrease the size of some seldom used system collections on creation. This will make these collections use less disk space and mapped memory. @@ -4341,7 +4345,7 @@ v2.1.0-rc1 (XXXX-XX-XX) * fixed issue #796: Searching with newline chars broken? fixed slightly different handling of backslash escape characters in a few - AQL functions. Now handling of escape sequences should be consistent, and + AQL functions. Now handling of escape sequences should be consistent, and searching for newline characters should work the same everywhere * added OpenSSL version check for configure @@ -4355,7 +4359,7 @@ v2.1.0-rc1 (XXXX-XX-XX) HTTP GET `/_api/document//` * issue #798: Lower case http headers from arango - + This change allows returning capitalized HTTP headers, e.g. `Content-Length` instead of `content-length`. The HTTP spec says that headers are case-insensitive, but @@ -4369,48 +4373,48 @@ v2.1.0-rc1 (XXXX-XX-XX) * simplified usage of `db._createStatement()` - Previously, the function could not be called with a query string parameter as + Previously, the function could not be called with a query string parameter as follows: db._createStatement(queryString); Calling it as above resulted in an error because the function expected an - object as its parameter. From now on, it's possible to call the function with + object as its parameter. From now on, it's possible to call the function with just the query string. * make ArangoDB not send back a `WWW-Authenticate` header to a client in case the client sends the `X-Omit-WWW-Authenticate` HTTP header. - This is done to prevent browsers from showing their built-in HTTP authentication + This is done to prevent browsers from showing their built-in HTTP authentication dialog for AJAX requests that require authentication. ArangoDB will still return an HTTP 401 (Unauthorized) if the request doesn't - contain valid credentials, but it will omit the `WWW-Authenticate` header, + contain valid credentials, but it will omit the `WWW-Authenticate` header, allowing clients to bypass the browser's authentication dialog. * added REST API method HTTP GET `/_api/job/job-id` to query the status of an async job without potentially fetching it from the list of done jobs * fixed non-intuitive behavior in jobs API: previously, querying the status - of an async job via the API HTTP PUT `/_api/job/job-id` removed a currently + of an async job via the API HTTP PUT `/_api/job/job-id` removed a currently executing async job from the list of queryable jobs on the server. - Now, when querying the result of an async job that is still executing, + Now, when querying the result of an async job that is still executing, the job is kept in the list of queryable jobs so its result can be fetched by a subsequent request. * use a new data structure for the edge index of an edge collection. This - improves the performance for the creation of the edge index and in + improves the performance for the creation of the edge index and in particular speeds up removal of edges in graphs. Note however that - this change might change the order in which edges starting at + this change might change the order in which edges starting at or ending in a vertex are returned. However, this order was never - guaranteed anyway and it is not sensible to guarantee any particular + guaranteed anyway and it is not sensible to guarantee any particular order. * provide a size hint to edge and hash indexes when initially filling them this will lead to less re-allocations when populating these indexes - + this may speed up building indexes when opening an existing collection -* don't requeue identical context methods in V8 threads in case a method is +* don't requeue identical context methods in V8 threads in case a method is already registered * removed arangod command line option `--database.remove-on-compacted` @@ -4442,9 +4446,9 @@ v2.0.8 (XXXX-XX-XX) ETCD was non-functional on 32 bit systems at all. The first call to the watch API crashed it. This was because atomic operations worked on data - structures that were not properly aligned on 32 bit systems. + structures that were not properly aligned on 32 bit systems. -* fixed issue #848: db.someEdgeCollection.inEdge does not return correct +* fixed issue #848: db.someEdgeCollection.inEdge does not return correct value when called the 2nd time after a .save to the edge collection @@ -4455,9 +4459,9 @@ v2.0.7 (2014-05-05) * fixed a race condition at startup - this fixes undefined behavior in case the logger was involved directly at + this fixes undefined behavior in case the logger was involved directly at startup, before the logger initialization code was called. This should have - occurred only for code that was executed before the invocation of main(), + occurred only for code that was executed before the invocation of main(), e.g. during ctor calls of statically defined objects. @@ -4505,7 +4509,7 @@ v2.0.2 (2014-04-06) * fixed dashboard modals -* fixed connection check for cluster planning front end: firefox does +* fixed connection check for cluster planning front end: firefox does not support async:false * document how to persist a cluster plan in order to relaunch an existing @@ -4518,10 +4522,10 @@ v2.0.1 (2014-03-31) * make ArangoDB not send back a `WWW-Authenticate` header to a client in case the client sends the `X-Omit-WWW-Authenticate` HTTP header. - This is done to prevent browsers from showing their built-in HTTP authentication + This is done to prevent browsers from showing their built-in HTTP authentication dialog for AJAX requests that require authentication. ArangoDB will still return an HTTP 401 (Unauthorized) if the request doesn't - contain valid credentials, but it will omit the `WWW-Authenticate` header, + contain valid credentials, but it will omit the `WWW-Authenticate` header, allowing clients to bypass the browser's authentication dialog. * fixed isses in arango-dfdb: @@ -4556,7 +4560,7 @@ v2.0.1 (2014-03-31) * fixed display of missing error messages and codes in arangosh -* when creating a collection via the web interface, the collection type was always +* when creating a collection via the web interface, the collection type was always "document", regardless of the user's choice @@ -4579,27 +4583,27 @@ v2.0.0-rc1 (2014-02-28) * added collection._dbName attribute to query the name of the database from a collection - more detailed documentation on the sharding and cluster features can be found in the user + more detailed documentation on the sharding and cluster features can be found in the user manual, section **Sharding** * INCOMPATIBLE CHANGE: using complex values in AQL filter conditions with operators other - than equality (e.g. >=, >, <=, <) will disable usage of skiplist indexes for filter + than equality (e.g. >=, >, <=, <) will disable usage of skiplist indexes for filter evaluation. - + For example, the following queries will be affected by change: FOR doc IN docs FILTER doc.value < { foo: "bar" } RETURN doc FOR doc IN docs FILTER doc.value >= [ 1, 2, 3 ] RETURN doc The following queries will not be affected by the change: - + FOR doc IN docs FILTER doc.value == 1 RETURN doc FOR doc IN docs FILTER doc.value == "foo" RETURN doc FOR doc IN docs FILTER doc.value == [ 1, 2, 3 ] RETURN doc FOR doc IN docs FILTER doc.value == { foo: "bar" } RETURN doc * INCOMPATIBLE CHANGE: removed undocumented method `collection.saveOrReplace` - + this feature was never advertised nor documented nor tested. * INCOMPATIBLE CHANGE: removed undocumented REST API method `/_api/simple/BY-EXAMPLE-HASH` @@ -4608,9 +4612,9 @@ v2.0.0-rc1 (2014-02-28) * added explicit startup parameter `--server.reuse-address` - This flag can be used to control whether sockets should be acquired with the SO_REUSEADDR + This flag can be used to control whether sockets should be acquired with the SO_REUSEADDR flag. - + Regardless of this setting, sockets on Windows are always acquired using the SO_EXCLUSIVEADDRUSE flag. @@ -4620,12 +4624,12 @@ v2.0.0-rc1 (2014-02-28) * slightly improved users management API in `/_api/user`: - Previously, when creating a new user via HTTP POST, the username needed to be + Previously, when creating a new user via HTTP POST, the username needed to be passed in an attribute `username`. When users were returned via this API, the usernames were returned in an attribute named `user`. This was slightly confusing and was changed in 2.0 as follows: - - when adding a user via HTTP POST, the username can be specified in an attribute + - when adding a user via HTTP POST, the username can be specified in an attribute `user`. If this attribute is not used, the API will look into the attribute `username` as before and use that value. - when users are returned via HTTP GET, the usernames are still returned in an @@ -4646,7 +4650,7 @@ v2.0.0-rc1 (2014-02-28) * Exchanged icons in the graphviewer toolbar -* always start networking and HTTP listeners when starting the server (even in +* always start networking and HTTP listeners when starting the server (even in console mode) * allow vertex and edge filtering with user-defined functions in TRAVERSAL, @@ -4660,11 +4664,11 @@ v2.0.0-rc1 (2014-02-28) // using the following custom filter functions var aqlfunctions = require("org/arangodb/aql/functions"); - aqlfunctions.register("myfunctions::checkedge", function (config, vertex, edge, path) { + aqlfunctions.register("myfunctions::checkedge", function (config, vertex, edge, path) { return (edge.type !== 'dislikes'); // don't follow these edges }, false); - aqlfunctions.register("myfunctions::checkvertex", function (config, vertex, path) { + aqlfunctions.register("myfunctions::checkvertex", function (config, vertex, path) { if (vertex.isDeleted || ! vertex.isActive) { return [ "prune", "exclude" ]; // exclude these and don't follow them } @@ -4672,7 +4676,7 @@ v2.0.0-rc1 (2014-02-28) }, false); * fail if invalid `strategy`, `order` or `itemOrder` attribute values - are passed to the AQL TRAVERSAL function. Omitting these attributes + are passed to the AQL TRAVERSAL function. Omitting these attributes is not considered an error, but specifying an invalid value for any of these attributes will make an AQL query fail. @@ -4682,10 +4686,10 @@ v2.0.0-rc1 (2014-02-28) database successfully. To keep compatibility with older ArangoDB versions, the startup parameter `--server.default-api-compatibility` can be set to a value of `10400` to indicate API compatibility with ArangoDB 1.4. The compatibility - can also be enforced by setting the `X-Arango-Version` HTTP header in a + can also be enforced by setting the `X-Arango-Version` HTTP header in a client request to this API on a per-request basis. -* allow direct access from the `db` object to collections whose names start +* allow direct access from the `db` object to collections whose names start with an underscore (e.g. db._users). Previously, access to such collections via the `db` object was possible from @@ -4693,7 +4697,7 @@ v2.0.0-rc1 (2014-02-28) to access such collections from these places was via the `db._collection()` workaround. -* allow `\n` (as well as `\r\n`) as line terminator in batch requests sent to +* allow `\n` (as well as `\r\n`) as line terminator in batch requests sent to `/_api/batch` HTTP API. * use `--data-binary` instead of `--data` parameter in generated cURL examples @@ -4714,7 +4718,7 @@ v2.0.0-rc1 (2014-02-28) following options have been changed: * `--javascript.modules-path`: this option has been removed. The modules paths - are determined by arangod and arangosh automatically based on the value of + are determined by arangod and arangosh automatically based on the value of `--javascript.startup-directory`. If the option is set on startup, it is ignored so startup will not abort with @@ -4757,8 +4761,8 @@ v2.0.0-rc1 (2014-02-28) be written to a new datafile in the collection directory, and the SHAPES directory will be removed afterwards. - This saves up to 2 MB of memory and disk space for each collection - (savings are higher, the less different shapes there are in a collection). + This saves up to 2 MB of memory and disk space for each collection + (savings are higher, the less different shapes there are in a collection). Additionally, one less file descriptor per opened collection will be used. When creating a new collection, the amount of sync calls may be reduced. The same @@ -4773,7 +4777,7 @@ v2.0.0-rc1 (2014-02-28) - `%e`: current endpoint - `%u`: current user -* added arangosh option `--javascript.gc-interval` to control amount of +* added arangosh option `--javascript.gc-interval` to control amount of garbage collection performed by arangosh * fixed issue #651: Allow addEdge() to take vertex ids in the JS library @@ -4807,11 +4811,11 @@ v1.4.15 (2014-04-19) -------------------- * bugfix for AQL query optimizer - + the following type of query was too eagerly optimized, leading to errors in code-generation: - + LET a = (FOR i IN [] RETURN i) LET b = (FOR i IN [] RETURN i) RETURN 1 - + the problem occurred when both lists in the subqueries were empty. In this case invalid code was generated and the query couldn't be executed. @@ -4822,8 +4826,8 @@ v1.4.14 (2014-04-05) * fixed race conditions during shape / attribute insertion A race condition could have led to spurious `cannot find attribute #xx` or - `cannot find shape #xx` (where xx is a number) warning messages being logged - by the server. This happened when a new attribute was inserted and at the same + `cannot find shape #xx` (where xx is a number) warning messages being logged + by the server. This happened when a new attribute was inserted and at the same time was queried by another thread. Also fixed a race condition that may have occurred when a thread tried to @@ -4832,7 +4836,7 @@ v1.4.14 (2014-04-05) * fixed a memory barrier / cpu synchronization problem with libev, affecting Windows with Visual Studio 2013 (probably earlier versions are affected, too) - + The issue is described in detail here: http://lists.schmorp.de/pipermail/libev/2014q1/002318.html @@ -4848,10 +4852,10 @@ v1.4.13 (2014-03-14) configuration file and not only from the command line * fixed too eager compaction - + The compaction will now wait for several seconds before trying to re-compact the same collection. Additionally, some other limits have been introduced for the compaction. - + v1.4.12 (2014-03-05) -------------------- @@ -4861,7 +4865,7 @@ v1.4.12 (2014-03-05) - document attributes view displayed many attributes with content "undefined" - document source view displayed many attributes with name "TYPEOF" and value "undefined" - an alert popping up in the browser with message "Datatables warning..." - + * re-introduced old-style read-write locks to supports Windows versions older than Windows 2008R2 and Windows 7. This should re-enable support for Windows Vista and Windows 2008. @@ -4870,24 +4874,24 @@ v1.4.12 (2014-03-05) v1.4.11 (2014-02-27) -------------------- -* added SHORTEST_PATH AQL function +* added SHORTEST_PATH AQL function this calculates the shortest paths between two vertices, using the Dijkstra algorithm, employing a min-heap By default, ArangoDB does not know the distance between any two vertices and will use a default distance of 1. A custom distance function can be registered - as an AQL user function to make the distance calculation use any document + as an AQL user function to make the distance calculation use any document attributes or custom logic: - + RETURN SHORTEST_PATH(cities, motorways, "cities/CGN", "cities/MUC", "outbound", { paths: true, distance: "myfunctions::citydistance" - }) + }) // using the following custom distance function var aqlfunctions = require("org/arangodb/aql/functions"); - aqlfunctions.register("myfunctions::distance", function (config, vertex1, vertex2, edge) { + aqlfunctions.register("myfunctions::distance", function (config, vertex1, vertex2, edge) { return Math.sqrt(Math.pow(vertex1.x - vertex2.x) + Math.pow(vertex1.y - vertex2.y)); }, false); @@ -4935,8 +4939,8 @@ v1.4.9 (2014-02-07) retrieving the document's current revision easily. * added AQL function `SKIPLIST` to directly access skiplist indexes from AQL - - This is a shortcut method to use a skiplist index for retrieving specific documents in + + This is a shortcut method to use a skiplist index for retrieving specific documents in indexed order. The function capability is rather limited, but it may be used for several cases to speed up queries. The documents are returned in index order if only one condition is used. @@ -4957,19 +4961,19 @@ v1.4.9 (2014-02-07) FOR doc IN SKIPLIST(mycollection, { a: [[ '==', 1 ]], b: [[ '==', 2 ]] }) RETURN doc - The function requires a skiplist index with the exact same attributes to + The function requires a skiplist index with the exact same attributes to be present on the specified collection. All attributes present in the skiplist index must be specified in the conditions specified for the `SKIPLIST` function. - Attribute declaration order is important, too: attributes must be specified in the + Attribute declaration order is important, too: attributes must be specified in the same order in the condition as they have been declared in the skiplist index. * added command-line option `--server.disable-authentication-unix-sockets` - + with this option, authentication can be disabled for all requests coming in via UNIX domain sockets, enabling clients located on the same host as the ArangoDB server to connect without authentication. Other connections (e.g. TCP/IP) are not affected by this option. - + The default value for this option is `false`. Note: this option is only supported on platforms that support Unix domain sockets. @@ -4992,11 +4996,11 @@ v1.4.9 (2014-02-07) // using the following custom filter functions var aqlfunctions = require("org/arangodb/aql/functions"); - aqlfunctions.register("myfunctions::checkedge", function (config, vertex, edge, path) { + aqlfunctions.register("myfunctions::checkedge", function (config, vertex, edge, path) { return (edge.type !== 'dislikes'); // don't follow these edges }, false); - aqlfunctions.register("myfunctions::checkvertex", function (config, vertex, path) { + aqlfunctions.register("myfunctions::checkvertex", function (config, vertex, path) { if (vertex.isDeleted || ! vertex.isActive) { return [ "prune", "exclude" ]; // exclude these and don't follow them } @@ -5048,7 +5052,7 @@ v1.4.5 (2014-01-15) * fixed bugs in description of HTTP API `_api/index` * fixed issue #732: Rest API GET revision number - + * added missing documentation for several methods in HTTP API `/_api/edge/...` * fixed typos in description of HTTP API `_api/document` @@ -5067,7 +5071,7 @@ v1.4.5 (2014-01-15) v1.4.4 (2013-12-24) ------------------- -* uid and gid are now set in the scripts, there is no longer a separate config file for +* uid and gid are now set in the scripts, there is no longer a separate config file for arangod when started from a script * foxx-manager is now an alias for arangosh @@ -5093,7 +5097,7 @@ v1.4.4 (2013-12-24) value for the SSL protocol is 4 (TLSv1). If the server is configured to use a different protocol, it was not possible to connect to it with the client tools. -* added more detailed request statistics +* added more detailed request statistics This adds the number of async-executed HTTP requests plus the number of HTTP requests per individual HTTP method type. @@ -5130,7 +5134,7 @@ v1.4.3 (2013-11-25) left-hand side * issue #662: - + Fixed access violation errors (crashes) in the Windows version, occurring under some circumstances when accessing databases with multiple clients in parallel @@ -5142,7 +5146,7 @@ v1.4.2 (2013-11-20) * fixed issue #669: Tiny documentation update -* ported Windows version to use native Windows API SRWLocks (slim read-write locks) +* ported Windows version to use native Windows API SRWLocks (slim read-write locks) and condition variables instead of homemade versions MSDN states the following about the compatibility of SRWLocks and Condition Variables: @@ -5152,14 +5156,14 @@ v1.4.2 (2013-11-20) Minimum supported server: Windows Vista [desktop apps | Windows Store apps] - + * fixed issue #662: ArangoDB on Windows hanging This fixes a deadlock issue that occurred on Windows when documents were written to a collection at the same time when some other thread tried to drop the collection. * fixed file-based logging in Windows - + the logger complained on startup if the specified log file already existed * fixed startup of server in daemon mode (`--daemon` startup option) @@ -5170,10 +5174,10 @@ v1.4.2 (2013-11-20) * changed Windows condition variable implementation to use Windows native condition variables - + This is an attempt to fix spurious Windows hangs as described in issue #662. -* added documentation for JavaScript traversals +* added documentation for JavaScript traversals * added --code-page command-line option for Windows version of arangosh @@ -5202,11 +5206,11 @@ v1.4.2 (2013-11-20) The Windows readline port was not able to handle characters that are built using CTRL or ALT keys. Regular characters entered using the CTRL or ALT keys were silently swallowed and not passed to the terminal input handler. - + This did not seem to cause problems for the US keyboard layout, but was a - severe issue for keyboard layouts that require the ALT (or ALT-GR) key to + severe issue for keyboard layouts that require the ALT (or ALT-GR) key to construct characters. For example, entering the character `{` with a German - keyboard layout requires pressing ALT-GR + 9. + keyboard layout requires pressing ALT-GR + 9. * fixed issue #665: Hash/skiplist combo madness bit my ass @@ -5253,8 +5257,8 @@ v1.4.1-rc1 (2013-11-07) This allows using the `DOCUMENT` function like this: - DOCUMENT('users/john') - DOCUMENT([ 'users/john', 'users/amy' ]) + DOCUMENT('users/john') + DOCUMENT([ 'users/john', 'users/amy' ]) in addition to the existing use cases: @@ -5269,14 +5273,14 @@ v1.4.1-rc1 (2013-11-07) It is not necessary anymore to send the batch boundary in the HTTP `Content-Type` header. Previously, the batch API expected the client to send a Content-Type header of`multipart/form-data; boundary=`. This is still supported in - ArangoDB 2.0, but clients can now also omit this header. If the header is not + ArangoDB 2.0, but clients can now also omit this header. If the header is not present in a client request, ArangoDB will ignore the request content type and read the MIME boundary from the beginning of the request body. This also allows using the batch API with the Swagger "Try it out" feature (which is not too good at sending a different or even dynamic content-type request header). -* added API method GET `/_api/database/user` +* added API method GET `/_api/database/user` This returns the list of databases a specific user can see without changing the username/passwd. @@ -5317,7 +5321,7 @@ v1.4.0 (2013-10-29) the server might respond with old-style results to "old" clients, increasing compatibility with "old" (non-up-to-date) clients. - - the server will on each incoming request check for an HTTP header + - the server will on each incoming request check for an HTTP header `x-arango-version`. Clients can optionally set this header to the API version number they support. For example, if a client sends the HTTP header `x-arango-version: 10300`, the server will pick this up and might send ArangoDB @@ -5327,7 +5331,7 @@ v1.4.0 (2013-10-29) running "old" clients with newer versions of ArangoDB, without having to adjust the clients too much. - - the `location` headers returned by the server for the APIs `/_api/document/...` + - the `location` headers returned by the server for the APIs `/_api/document/...` and `/_api/collection/...` will have different values depending on the used API version. If the API compatibility is `10300`, the `location` headers returned will look like this: @@ -5339,15 +5343,15 @@ v1.4.0 (2013-10-29) location: /_db//_api/document/... - Please note that even in the presence of this, old API versions still may not - be supported forever by the server. - + Please note that even in the presence of this, old API versions still may not + be supported forever by the server. + * fixed issue #643: Some minor corrections and a link to "Downloads" by @frankmayer * started issue #642: Completion of error handling -* fixed issue #639: compiling v1.4 on maverick produces warnings on - -Wstrict-null-sentinel +* fixed issue #639: compiling v1.4 on maverick produces warnings on + -Wstrict-null-sentinel * fixed issue #621: Standard Config needs to be fixed @@ -5358,7 +5362,7 @@ v1.4.0 (2013-10-29) * added foxx-manager `replace` command -* added foxx-manager `installed` command (a more intuitive alias for `list`) +* added foxx-manager `installed` command (a more intuitive alias for `list`) * fixed issue #617: Swagger API is missing '/_api/version' @@ -5366,13 +5370,13 @@ v1.4.0 (2013-10-29) * fixed issue #614: API : Typo in : Request URL /_api/database/current -* fixed issue #609: Graph viz tool - different background color +* fixed issue #609: Graph viz tool - different background color * fixed issue #608: arangosh config files - eventually missing in the manual * fixed issue #607: Admin interface: no core documentation -* fixed issue #603: Aardvark Foxx App Manager +* fixed issue #603: Aardvark Foxx App Manager * fixed a bug in type-mapping between AQL user functions and the AQL layer @@ -5392,7 +5396,7 @@ v1.4.0 (2013-10-29) v1.4.0-beta2 (2013-10-14) ------------------------- -* fixed compaction on Windows +* fixed compaction on Windows The compaction on Windows did not ftruncate the cleaned datafiles to a smaller size. This has been fixed so not only the content of the files is cleaned but also files @@ -5410,13 +5414,13 @@ v1.4.0-beta2 (2013-10-14) Especially the following system collections will now be included in replication: - `_aqlfunctions` - `_graphs` - - In previous versions of ArangoDB, all system collections were excluded from the + + In previous versions of ArangoDB, all system collections were excluded from the replication. The change also caused a change in the replication logger and applier: in previous versions of ArangoDB, only a collection's id was logged for an operation. - This has not caused problems for non-system collections but for system collections + This has not caused problems for non-system collections but for system collections there ids might differ. In addition to a collection id ArangoDB will now also log the name of a collection for each replication event. @@ -5445,14 +5449,14 @@ v1.4.0-beta2 (2013-10-14) The default value for this option is `false` (no method overriding allowed). * added "details" URL parameter for bulk import API - + Setting the `details` URL parameter to `true` in a call to POST `/_api/import` will make the import return details about non-imported documents in the `details` attribute. If `details` is `false` or omitted, no `details` attribute will be present in the response. This is the same behavior that previous ArangoDB versions exposed. * added "complete" option for bulk import API - + Setting the `complete` URL parameter to `true` in a call to POST `/_api/import` will make the import completely fail if at least one of documents cannot be imported successfully. @@ -5463,17 +5467,17 @@ v1.4.0-beta2 (2013-10-14) * added missing swagger documentation for `/_api/log` * calling `/_api/logs` (or `/_admin/logs`) is only permitted from the `_system` database now. - + Calling this API method for/from other database will result in an HTTP 400. ' ported fix from https://github.com/novus/nvd3/commit/0894152def263b8dee60192f75f66700cea532cc - - This prevents JavaScript errors from occurring in Chrome when in the admin interface, + + This prevents JavaScript errors from occurring in Chrome when in the admin interface, section "Dashboard". * show current database name in web interface (bottom right corner) -* added missing documentation for /_api/import in swagger API docs +* added missing documentation for /_api/import in swagger API docs * allow specification of database name for replication sync command replication applier @@ -5482,7 +5486,7 @@ v1.4.0-beta2 (2013-10-14) * issue #601: Show DB in prompt arangosh now displays the database name as part of the prompt by default. - + Can change the prompt by using the `--prompt` option, e.g. > arangosh --prompt "my db is named \"%d\"> " @@ -5504,7 +5508,7 @@ v1.4.0-beta1 (2013-10-01) This prevents ArangoDB from reloading all Foxx applications when it is not actually necessary. -* changed error code from 10 (bad parameter) to 1232 (invalid key generator) for +* changed error code from 10 (bad parameter) to 1232 (invalid key generator) for errors that are due to an invalid key generator specification when creating a new collection @@ -5518,11 +5522,11 @@ v1.4.0-beta1 (2013-10-01) * issue #360: added support for asynchronous requests - Incoming HTTP requests with the headers `x-arango-async: true` or - `x-arango-async: store` will be answered by the server instantly with a generic - HTTP 202 (Accepted) response. - - The actual requests will be queued and processed by the server asynchronously, + Incoming HTTP requests with the headers `x-arango-async: true` or + `x-arango-async: store` will be answered by the server instantly with a generic + HTTP 202 (Accepted) response. + + The actual requests will be queued and processed by the server asynchronously, allowing the client to continue sending other requests without waiting for the server to process the actually requested operation. @@ -5534,7 +5538,7 @@ v1.4.0-beta1 (2013-10-01) option `--scheduler.maximal-queue-size`. If the queue contains this many number of tasks and a new asynchronous request comes in, the server will reject it with an HTTP 500 (internal server error) response. - + Results of incoming requests marked with header `x-arango-async: true` will be discarded by the server immediately. Clients have no way of accessing the result of such asynchronously executed request. This is just _fire and forget_. @@ -5560,7 +5564,7 @@ v1.4.0-beta1 (2013-10-01) arangorestore currently does not re-create any indexes, and doesn't yet handle referenced documents in edges properly when doing just partial restores. This will be fixed until 1.4 stable. - + * introduced `--server.database` option for arangosh, arangoimp, and arangob. The option allows these client tools to use a certain database for their actions. @@ -5587,10 +5591,10 @@ v1.4.0-beta1 (2013-10-01) Deleting databases is still unstable in ArangoDB 1.4 alpha and might crash the server. This will be fixed until 1.4 stable. - To access a specific database via the HTTP REST API, the `/_db//` prefix + To access a specific database via the HTTP REST API, the `/_db//` prefix can be used in all URLs. ArangoDB will check if an incoming request starts with this prefix, and will automatically pick the database name from it. If the prefix - is not there, ArangoDB will assume the request is made for the default database + is not there, ArangoDB will assume the request is made for the default database (`_system`). This is done for downwards-compatibility reasons. That means, the following URL pathnames are logically identical: @@ -5608,13 +5612,13 @@ v1.4.0-beta1 (2013-10-01) Cross-database operations are unintended and unsupported. The intention of the multi-database feature is to have the possibility to have a few databases managed - by ArangoDB in parallel, but to only access one database at a time from a connection + by ArangoDB in parallel, but to only access one database at a time from a connection or a request. When accessing the web interface via the URL pathname `/_admin/html/` or `/_admin/aardvark`, the web interface for the default database (`_system`) will be displayed. To access the web interface for a different database, the database name can be - put into the URLs as a prefix, e.g. `/_db/test/_admin/html` or + put into the URLs as a prefix, e.g. `/_db/test/_admin/html` or `/_db/test/_admin/aardvark`. All internal request handlers and also all user-defined request handlers and actions @@ -5627,10 +5631,10 @@ v1.4.0-beta1 (2013-10-01) For example, when calling the URL `/myapp/myaction`, the content of `req.database` will be `_system` (the default database because no database got specified) and the content of `req.url` will be `/myapp/myaction`. - - When calling the URL `/_db/test/myapp/myaction`, the content of `req.database` will be + + When calling the URL `/_db/test/myapp/myaction`, the content of `req.database` will be `test`, and the content of `req.url` will still be `/myapp/myaction`. - + * Foxx now excludes files starting with . (dot) when bundling assets This mitigates problems with editor swap files etc. @@ -5643,10 +5647,10 @@ v1.4.0-beta1 (2013-10-01) The base URL for the admin interface changed from `_admin/html/index.html` to `_admin/aardvark/index.html`. - The "old" redirection to `_admin/html/index.html` will now produce a 404 error. - - When starting ArangoDB with the `--upgrade` option, this will automatically be remedied - by putting in a redirection from `/` to `/_admin/aardvark/index.html`, and from + The "old" redirection to `_admin/html/index.html` will now produce a 404 error. + + When starting ArangoDB with the `--upgrade` option, this will automatically be remedied + by putting in a redirection from `/` to `/_admin/aardvark/index.html`, and from `/_admin/html/index.html` to `/_admin/aardvark/index.html`. This also obsoletes the following configuration (command-line) options: @@ -5656,7 +5660,7 @@ v1.4.0-beta1 (2013-10-01) when using these now obsolete options when the server is started, no error is produced for downwards-compatibility. -* changed User-Agent value sent by arangoimp, arangosh, and arangod from "VOC-Agent" to +* changed User-Agent value sent by arangoimp, arangosh, and arangod from "VOC-Agent" to "ArangoDB" * changed journal file creation behavior as follows: @@ -5674,14 +5678,14 @@ v1.4.0-beta1 (2013-10-01) be created automatically From the end user perspective, nothing should have changed, except that there is now - less disk usage for empty collections. Disk usage of infrequently updated collections - might also be reduced significantly by running the `rotate()` method of a collection, + less disk usage for empty collections. Disk usage of infrequently updated collections + might also be reduced significantly by running the `rotate()` method of a collection, and not writing into a collection subsequently. * added method `collection.rotate()` This allows premature rotation of a collection's current journal file into a (read-only) - datafile. The purpose of using `rotate()` is to prematurely allow compaction (which is + datafile. The purpose of using `rotate()` is to prematurely allow compaction (which is performed on datafiles only) on data, even if the journal was not filled up completely. Using `rotate()` may make sense in the following scenario: @@ -5698,7 +5702,7 @@ v1.4.0-beta1 (2013-10-01) // calling rotate will make the current journal a datafile, and thus make it // eligible for compaction - c.rotate(); + c.rotate(); Using `rotate()` may also be useful when data in a collection is known to not change in the immediate future. After having completed all write operations on a collection, @@ -5710,7 +5714,7 @@ v1.4.0-beta1 (2013-10-01) Note: rotating the journal is asynchronous, so that the actual rotation may be executed after `rotate()` returns to the caller. -* changed compaction to merge small datafiles together (up to 3 datafiles are merged in +* changed compaction to merge small datafiles together (up to 3 datafiles are merged in a compaction run) In the regular case, this should leave less small datafiles stay around on disk and allow @@ -5724,7 +5728,7 @@ v1.4.0-beta1 (2013-10-01) * issue #587: Add db._create() in help for startup arangosh -* issue #586: Share a link on installation instructions in the User Manual +* issue #586: Share a link on installation instructions in the User Manual * issue #585: Bison 2.4 missing on Mac for custom build @@ -5734,7 +5738,7 @@ v1.4.0-beta1 (2013-10-01) * issue #581: Parameter binding for attributes -* issue #580: Small improvements (by @guidoreina) +* issue #580: Small improvements (by @guidoreina) * issue #577: Missing documentation for collection figures in implementor manual @@ -5756,7 +5760,7 @@ v1.4.0-alpha1 (2013-08-02) * added replication. check online manual for details. -* added server startup options `--server.disable-replication-logger` and +* added server startup options `--server.disable-replication-logger` and `--server.disable-replication-applier` * removed action deployment tool, this now handled with Foxx and its manager or @@ -5803,7 +5807,7 @@ v1.4.0-alpha1 (2013-08-02) * added AQL range operator `..` The `..` operator can be used to easily iterate over a sequence of numeric - values. It will produce a list of values in the defined range, with both bounding + values. It will produce a list of values in the defined range, with both bounding values included. Example: @@ -5817,7 +5821,7 @@ v1.4.0-alpha1 (2013-08-02) * added AQL RANGE function * added collection.first(count) and collection.last(count) document access functions - + These functions allow accessing the first or last n documents in a collection. The order is determined by document insertion/update time. @@ -5851,7 +5855,7 @@ v1.4.0-alpha1 (2013-08-02) message will give a hint on the collection name * changed return value for AQL `DOCUMENT` function in case document is not found - + Previously, when the AQL `DOCUMENT` function was called with the id of a document and the document could not be found, it returned `undefined`. This value is not part of the JSON type system and this has caused some problems. @@ -5871,7 +5875,7 @@ v1.4.0-alpha1 (2013-08-02) * added collection.checksum() method to calculate CRC checksums for collections - This can be used to + This can be used to - check if data in a collection has changed - compare the contents of two collections on different ArangoDB instances @@ -5888,11 +5892,11 @@ v1.4.0-alpha1 (2013-08-02) * renamed command-line option `--log.filter` to `--log.source-filter` to avoid misunderstandings -* introduced new command-line option `--log.content-filter` to optionally restrict +* introduced new command-line option `--log.content-filter` to optionally restrict logging to just specific log messages (containing the filter string, case-sensitive). - + For example, to filter on just log entries which contain `ArangoDB`, use: - + --log.content-filter "ArangoDB" * added optional command-line option `--log.requests-file` to log incoming HTTP @@ -5902,7 +5906,7 @@ v1.4.0-alpha1 (2013-08-02) client IP address, HTTP method, requests URL, HTTP response code, and size of the response body. -* added a signal handler for SIGUSR1 signal: +* added a signal handler for SIGUSR1 signal: when ArangoDB receives this signal, it will respond all further incoming requests with an HTTP 503 (Service Unavailable) error. This will be the case until another @@ -5915,11 +5919,11 @@ v1.4.0-alpha1 (2013-08-02) 414 (Request-URI Too Long) error. * require version 1.0 or 1.1 in HTTP version signature of requests sent by clients: - + Clients sending requests with a non-HTTP 1.0 or non-HTTP 1.1 version number will be served with an HTTP 505 (HTTP Version Not Supported) error. -* updated manual on indexes: +* updated manual on indexes: using system attributes such as `_id`, `_key`, `_from`, `_to`, `_rev` in indexes is disallowed and will be rejected by the server. This was the case since ArangoDB 1.3, @@ -5933,16 +5937,16 @@ v1.4.0-alpha1 (2013-08-02) Which restrictions there are for which system collections may vary from release to release, but users should in general not try to modify system collections directly - anyway. - + anyway. + Note: there are no such restrictions for user-created collections. * issue #559: added Foxx documentation to user manual * added server startup option `--server.authenticate-system-only`. This option can be used to restrict the need for HTTP authentication to internal functionality and APIs, - such as `/_api/*` and `/_admin/*`. - Setting this option to `true` will thus force authentication for the ArangoDB APIs + such as `/_api/*` and `/_admin/*`. + Setting this option to `true` will thus force authentication for the ArangoDB APIs and the web interface, but allow unauthenticated requests for other URLs (including user defined actions and Foxx applications). The default value of this option is `false`, meaning that if authentication is turned @@ -5951,7 +5955,7 @@ v1.4.0-alpha1 (2013-08-02) URLs starting with `/_` only. Please note that authentication still needs to be enabled regularly by setting the - `--server.disable-authentication` parameter to `false`. Otherwise no authentication + `--server.disable-authentication` parameter to `false`. Otherwise no authentication will be required for any URLs as before. * protect collections against unloading when there are still document barriers around. @@ -5962,7 +5966,7 @@ v1.4.0-alpha1 (2013-08-02) The arguments for creating a cap constraint are now: `collection.ensureCapConstraint(, );` - It is supported to specify just a count as in ArangoDB 1.3 and before, to specify + It is supported to specify just a count as in ArangoDB 1.3 and before, to specify just a fileSize, or both. The first met constraint will trigger the automated document removal. @@ -5970,7 +5974,7 @@ v1.4.0-alpha1 (2013-08-02) * added API `/_api/current-database` to retrieve information about the database the client is currently connected to (note: the API `/_api/current-database` has been - removed in the meantime. The functionality is accessible via `/_api/database/current` + removed in the meantime. The functionality is accessible via `/_api/database/current` now). * ensure a proper order of tick values in datafiles/journals/compactors. @@ -5978,21 +5982,21 @@ v1.4.0-alpha1 (2013-08-02) older files, there are edge cases at the beginning and end of the datafiles when _tick values are not properly in order. -* prevent caching of static pages in PathHandler. +* prevent caching of static pages in PathHandler. whenever a static page is requested that is served by the general PathHandler, the server will respond to HTTP GET requests with a "Cache-Control: max-age=86400" header. * added "doCompact" attribute when creating collections and to collection.properties(). The attribute controls whether collection datafiles are compacted. -* changed the HTTP return code from 400 to 404 for some cases when there is a referral +* changed the HTTP return code from 400 to 404 for some cases when there is a referral to a non-existing collection or document. * introduced error code 1909 `too many iterations` that is thrown when graph traversals hit the `maxIterations` threshold. * optionally limit traversals to a certain number of iterations - the limitation can be achieved via the traversal API by setting the `maxIterations` + the limitation can be achieved via the traversal API by setting the `maxIterations` attribute, and also via the AQL `TRAVERSAL` and `TRAVERSAL_TREE` functions by setting the same attribute. If traversals are not limited by the end user, a server-defined limit for `maxIterations` may be used to prevent server-side traversals from running @@ -6018,7 +6022,7 @@ v1.4.0-alpha1 (2013-08-02) * issue #526: Unable to escape when an errorneous command is entered into the js shell * issue #523: Graph and vertex methods for the javascript api - + * issue #517: Foxx: Route parameters with capital letters fail * issue #512: Binded Parameters for LIMIT @@ -6043,7 +6047,7 @@ v1.3.2 (2013-06-21) * made the shape-collection journal size adaptive: if too big shapes come in, a shape journal will be created with a big-enough size - automatically. the maximum size of a shape journal is still restricted, but to a + automatically. the maximum size of a shape journal is still restricted, but to a very big value that should never be reached in practice. * fixed a segfault that occurred when inserting documents with a shape size bigger @@ -6109,10 +6113,10 @@ v1.3.0-rc1 (2013-04-24) * changed compaction to only compact datafiles with more at least 10% of dead documents (byte size-wise) -* issue #498: fixed reload of authentication info when using +* issue #498: fixed reload of authentication info when using `require("org/arangodb/users").reload()` -* issue #495: Passing an empty array to create a document results in a +* issue #495: Passing an empty array to create a document results in a "phantom" document * added more precision for requests statistics figures @@ -6136,10 +6140,10 @@ v1.3.0-rc1 (2013-04-24) * issue #475: A better error message for deleting a non-existent graph * issue #474: Web interface problems with the JS Shell - + * added missing documentation for AQL UNION function -* added transaction support. +* added transaction support. This provides ACID transactions for ArangoDB. Transactions can be invoked using the `db._executeTransaction()` function, or the `/_api/transaction` REST API. @@ -6172,15 +6176,15 @@ v1.3.alpha1 (2013-04-05) but without any known meaning, for example `_test`, `_foo`, ... Previously, these attributes were saved with the document regularly in some cases, - but were discarded in other cases. + but were discarded in other cases. Now these attributes are discarded consistently. "Real" system attributes such as `_key`, `_from`, `_to` are not affected and will work as before. - + Additionally, attributes with an empty name (``) are discarded when documents are saved. - Though using reserved or empty attribute names in documents was not really and - consistently supported in previous versions of ArangoDB, this change might cause + Though using reserved or empty attribute names in documents was not really and + consistently supported in previous versions of ArangoDB, this change might cause an incompatibility for clients that rely on this feature. * added server startup flag `--database.force-sync-properties` to force syncing of @@ -6207,7 +6211,7 @@ v1.3.alpha1 (2013-04-05) startup option `--server.descriptors-minimum 0` * fixed shapedjson to json conversion for special numeric values (NaN, +inf, -inf). - Before, "NaN", "inf", or "-inf" were written into the JSONified output, but these + Before, "NaN", "inf", or "-inf" were written into the JSONified output, but these values are not allowed in JSON. Now, "null" is written to the JSONified output as required. @@ -6219,7 +6223,7 @@ v1.3.alpha1 (2013-04-05) * added AQL TRIM(), LEFT() and RIGHT() string functions * fixed issue #436: GET /_api/document on edge - + * make AQL REVERSE() and LENGTH() functions work on strings, too * disabled DOT generation in `make doxygen`. this speeds up docs generation @@ -6248,12 +6252,12 @@ v1.2.3 (XXXX-XX-XX) * added optional parameter `edgexamples` for AQL function EDGES() and NEIGHBORS() * added AQL function NEIGHBORS() - + * added freebsd support * fixed firstExample() query with `_id` and `_key` attributes -* issue triAGENS/ArangoDB-PHP#55: AQL optimizer may have mis-optimized duplicate +* issue triAGENS/ArangoDB-PHP#55: AQL optimizer may have mis-optimized duplicate filter statements with limit @@ -6273,14 +6277,14 @@ v1.2.2 (2013-03-26) db._create("mycollection", { keyOptions: { type: "autoincrement", offset: 1, increment: 10, allowUserKeys: true } }); - The `type` attribute will make sure the keys will be auto-generated if no + The `type` attribute will make sure the keys will be auto-generated if no `_key` attribute is specified for a document. - The `allowUserKeys` attribute determines whether users might still supply own + The `allowUserKeys` attribute determines whether users might still supply own `_key` values with documents or if this is considered an error. - The `increment` value determines the actual increment value, whereas the `offset` - value can be used to seed to value sequence with a specific starting value. + The `increment` value determines the actual increment value, whereas the `offset` + value can be used to seed to value sequence with a specific starting value. This will be useful later in a multi-master setup, when multiple servers can use different auto-increment seed values and thus generate non-conflicting auto-increment values. @@ -6290,12 +6294,12 @@ v1.2.2 (2013-03-26) - `offset`: `0` - `increment`: `1` - The only other available key generator type currently is `traditional`. - The `traditional` key generator will auto-generate keys in a fashion as ArangoDB + The only other available key generator type currently is `traditional`. + The `traditional` key generator will auto-generate keys in a fashion as ArangoDB always did (some increasing integer value, with a more or less unpredictable increment value). - Note that for the `traditional` key generator there is only the option to disallow + Note that for the `traditional` key generator there is only the option to disallow user-supplied keys and give the server the sole responsibility for key generation. This can be achieved by setting the `allowUserKeys` property to `false`. @@ -6317,10 +6321,10 @@ v1.2.2 (2013-03-26) collections. This change may also affect the following REST APIs: - - POST `/_api/collection`: the server does now accept the optional `keyOptions` + - POST `/_api/collection`: the server does now accept the optional `keyOptions` attribute in the second parameter - GET `/_api/collection/properties`: will return the `keyOptions` attribute as part - of the collection's properties. The previous optional attribute `createOptions` + of the collection's properties. The previous optional attribute `createOptions` is now gone. * fixed `ArangoStatement.explain()` method with bind variables @@ -6342,7 +6346,7 @@ v1.2.1 (2013-03-14) * issue #442: pls update post install info on osx -* fixed conversion of special double values (NaN, -inf, +inf) when converting from +* fixed conversion of special double values (NaN, -inf, +inf) when converting from shapedjson to JSON * fixed compaction of markers (location of _key was not updated correctly in memory, @@ -6351,11 +6355,11 @@ v1.2.1 (2013-03-14) * fixed edge index key pointers to use document master pointer plus offset instead of direct _key address -* fixed case when server could not create any more journal or compactor files. +* fixed case when server could not create any more journal or compactor files. Previously a wrong status code may have been returned, and not being able to create a new compactor file may have led to an infinite loop with error message "could not create compactor". - + * fixed value truncation for numeric filename parts when renaming datafiles/journals @@ -6374,14 +6378,14 @@ v1.2.0 (2013-03-01) * fixed dropping of indexes without collection name, e.g. `db.xxx.dropIndex("123456");` Dropping an index like this failed with an assertion error. - + * fixed issue #426: arangoimp should be able to import edges into edge collections -* fixed issue #425: In case of conflict ArangoDB returns HTTP 400 Bad request +* fixed issue #425: In case of conflict ArangoDB returns HTTP 400 Bad request (with 1207 Error) instead of HTTP 409 Conflict * fixed too greedy token consumption in AQL for negative values: - e.g. in the statement `RETURN { a: 1 -2 }` the minus token was consumed as part + e.g. in the statement `RETURN { a: 1 -2 }` the minus token was consumed as part of the value `-2`, and not interpreted as the binary arithmetic operator @@ -6428,19 +6432,19 @@ v1.2.beta2 (2013-02-15) * added AQL functions KEEP() and UNSET() -* fixed issue #348: "HTTP Interface for Administration and Monitoring" +* fixed issue #348: "HTTP Interface for Administration and Monitoring" documentation errors. * fix stringification of specific positive int64 values. Stringification of int64 values with the upper 32 bits cleared and the 33rd bit set were broken. -* issue #395: Collection properties() function should return 'isSystem' for +* issue #395: Collection properties() function should return 'isSystem' for Javascript and REST API * make server stop after upgrade procedure when invoked with `--upgrade option`. When started with the `--upgrade` option, the server will perfom the upgrade, and then exit with a status code indicating the result of the - upgrade (0 = success, 1 = failure). To start the server regularly in either + upgrade (0 = success, 1 = failure). To start the server regularly in either daemon or console mode, the `--upgrade` option must not be specified. This change was introduced to allow init.d scripts check the result of the upgrade procedure, even in case an upgrade was successful. @@ -6457,14 +6461,14 @@ v1.2.beta2 (2013-02-15) * replaced redundant error return code 1520 (Unable to open collection) with error code 1203 (Collection not found). These error codes have the same meanings, but one of them was returned from AQL queries only, the other got thrown by other parts of - ArangoDB. Now, error 1203 (Collection not found) is used in AQL too in case a + ArangoDB. Now, error 1203 (Collection not found) is used in AQL too in case a non-existing collection is used. v1.2.beta1 (2013-02-01) ----------------------- * fixed issue #382: [Documentation error] Maschine... should be Machine... - + * unified history file locations for arangod, arangosh, and arangoirb. - The readline history for arangod (emergency console) is now stored in file $HOME/.arangod. It was stored in $HOME/.arango before. @@ -6477,48 +6481,48 @@ v1.2.beta1 (2013-02-01) * allow negative list indexes in AQL to access elements from the end of a list, e.g. ```RETURN values[-1]``` will return the last element of the `values` list. -* collection ids, index ids, cursor ids, and document revision ids created and - returned by ArangoDB are now returned as strings with numeric content inside. +* collection ids, index ids, cursor ids, and document revision ids created and + returned by ArangoDB are now returned as strings with numeric content inside. This is done to prevent some value overrun/truncation in any part of the - complete client/server workflow. - In ArangoDB 1.1 and before, these values were previously returned as + complete client/server workflow. + In ArangoDB 1.1 and before, these values were previously returned as (potentially very big) integer values. This may cause problems (clipping, overrun, - precision loss) for clients that do not support big integers natively and store + precision loss) for clients that do not support big integers natively and store such values in IEEE754 doubles internally. This type loses precision after about 52 bits and is thus not safe to hold an id. - Javascript and 32 bit-PHP are examples for clients that may cause such problems. + Javascript and 32 bit-PHP are examples for clients that may cause such problems. Therefore, ids are now returned by ArangoDB as strings, with the string - content being the integer value as before. + content being the integer value as before. Example for documents ("_rev" attribute): - - Document returned by ArangoDB 1.1: { "_rev": 1234, ... } - - Document returned by ArangoDB 1.2: { "_rev": "1234", ... } - + - Document returned by ArangoDB 1.1: { "_rev": 1234, ... } + - Document returned by ArangoDB 1.2: { "_rev": "1234", ... } + Example for collections ("id" attribute / "_id" property): - - Collection returned by ArangoDB 1.1: { "id": 9327643, "name": "test", ... } + - Collection returned by ArangoDB 1.1: { "id": 9327643, "name": "test", ... } - Collection returned by ArangoDB 1.2: { "id": "9327643", "name": "test", ... } Example for cursors ("id" attribute): - - Collection returned by ArangoDB 1.1: { "id": 11734292, "hasMore": true, ... } + - Collection returned by ArangoDB 1.1: { "id": 11734292, "hasMore": true, ... } - Collection returned by ArangoDB 1.2: { "id": "11734292", "hasMore": true, ... } -* global variables are not automatically available anymore when starting the - arangod Javascript emergency console (i.e. ```arangod --console```). - - Especially, the variables `db`, `edges`, and `internal` are not available +* global variables are not automatically available anymore when starting the + arangod Javascript emergency console (i.e. ```arangod --console```). + + Especially, the variables `db`, `edges`, and `internal` are not available anymore. `db` and `internal` can be made available in 1.2 by ```var db = require("org/arangodb").db;``` and ```var internal = require("internal");```, respectively. The reason for this change is to get rid of global variables in the server because this will allow more specific inclusion of functionality. - For convenience, the global variable `db` is still available by default in + For convenience, the global variable `db` is still available by default in arangosh. The global variable `edges`, which since ArangoDB 1.1 was kind of a redundant wrapper of `db`, has been removed in 1.2 completely. Please use `db` instead, and if creating an edge collection, use the explicit ```db._createEdgeCollection()``` command. -* issue #374: prevent endless redirects when calling admin interface with +* issue #374: prevent endless redirects when calling admin interface with unexpected URLs * issue #373: TRAVERSAL() `trackPaths` option does not work. Instead `paths` does work @@ -6530,7 +6534,7 @@ v1.2.beta1 (2013-02-01) was previously honored by the REST API and on the server-side, but not when the waitForSync parameter was specified for a document operation in arangosh. -* calls to db.collection.figures() and /_api/collection//figures now +* calls to db.collection.figures() and /_api/collection//figures now additionally return the number of shapes used in the collection in the extra attribute "shapes.count" @@ -6540,15 +6544,15 @@ v1.2.beta1 (2013-02-01) * added AQL function ATTRIBUTES() to return the attribute names of a document -* removed internal server-side AQL functions from global scope. +* removed internal server-side AQL functions from global scope. - Now the AQL internal functions can only be accessed via the exports of the + Now the AQL internal functions can only be accessed via the exports of the ahuacatl module, which can be included via ```require("org/arangodb/ahuacatl")```. - It shouldn't be necessary for clients to access this module at all, but + It shouldn't be necessary for clients to access this module at all, but internal code may use this module. - The previously global AQL-related server-side functions were moved to the - internal namespace. This produced the following function name changes on + The previously global AQL-related server-side functions were moved to the + internal namespace. This produced the following function name changes on the server: old name new name @@ -6585,15 +6589,15 @@ v1.2.beta1 (2013-02-01) * Added in-memory only collections - Added collection creation parameter "isVolatile": - if set to true, the collection is created as an in-memory only collection, - meaning that all document data of that collection will reside in memory only, - and will not be stored permanently to disk. - This means that all collection data will be lost when the collection is unloaded + Added collection creation parameter "isVolatile": + if set to true, the collection is created as an in-memory only collection, + meaning that all document data of that collection will reside in memory only, + and will not be stored permanently to disk. + This means that all collection data will be lost when the collection is unloaded or the server is shut down. - As this collection type does not have datafile disk overhead for the regular + As this collection type does not have datafile disk overhead for the regular document operations, it may be faster than normal disk-backed collections. The - actual performance gains strongly depend on the underlying OS, filesystem, and + actual performance gains strongly depend on the underlying OS, filesystem, and settings though. This collection type should be used for caches only and not for any sensible data that cannot be re-created otherwise. @@ -6610,17 +6614,17 @@ v1.2.beta1 (2013-02-01) This was achieved by adding the dump() function for the "internal" object * reduced insertion time for edges index - Inserting into the edges index now avoids costly comparisons in case of a hash + Inserting into the edges index now avoids costly comparisons in case of a hash collision, reducing the prefilling/loading timer for bigger edge collections -* added fulltext queries to AQL via FULLTEXT() function. This allows search +* added fulltext queries to AQL via FULLTEXT() function. This allows search fulltext indexes from an AQL query to find matching documents * added fulltext index type. This index type allows indexing words and prefixes of words from a specific document attribute. The index can be queries using a SimpleQueryFull object, the HTTP REST API at /_api/simple/fulltext, or via AQL -* added collection.revision() method to determine whether a collection has changed. +* added collection.revision() method to determine whether a collection has changed. The revision method returns a revision string that can be used by client programs for equality/inequality comparisons. The value returned by the revision method should be treated by clients as an opaque string and clients should not try to @@ -6654,7 +6658,7 @@ v1.2.beta1 (2013-02-01) JSON being produced. Now, arangosh will produce valid JSON that can be used to send it back to ArangoDB or use it with arangoimp etc. -* fixed issue #300: allow importing documents via the REST /_api/import API +* fixed issue #300: allow importing documents via the REST /_api/import API from a JSON list, too. So far, the API only supported importing from a format that had one JSON object on each line. This is sometimes inconvenient, e.g. when the result of an AQL @@ -6701,17 +6705,17 @@ v1.2.beta1 (2013-02-01) * looking up a document: `db.users.document("fred")` * referencing other documents: `edges.relations.save("users/fred", "users/john", ...)` - This change is downwards-compatible to ArangoDB 1.1 because in ArangoDB 1.1 + This change is downwards-compatible to ArangoDB 1.1 because in ArangoDB 1.1 users were not able to define their own keys. If the user does not supply a _key attribute when creating a document, ArangoDB 1.2 will still generate a key of - its own as ArangoDB 1.1 did. However, all documents returned by ArangoDB 1.2 will + its own as ArangoDB 1.1 did. However, all documents returned by ArangoDB 1.2 will include a _key attribute and clients should be able to handle that (e.g. by ignoring it if not needed). Documents returned will still include the _id attribute as in ArangoDB 1.1. -* require collection names everywhere where a collection id was allowed in +* require collection names everywhere where a collection id was allowed in ArangoDB 1.1 & 1.0 - This change requires clients to use a collection name in place of a collection id + This change requires clients to use a collection name in place of a collection id at all places the client deals with collections. Examples: * creating edges: the _from and _to attributes must now contain collection names instead @@ -6719,14 +6723,14 @@ v1.2.beta1 (2013-02-01) * retrieving edges: the returned _from and _to attributes now will contain collection names instead of ids, too: _from: `test/fred` instead of `1234/3455` * looking up documents: db.users.document("fred") or db._document("users/fred") - + Collection names must be used in REST API calls instead of collection ids, too. This change is thus not completely downwards-compatible to ArangoDB 1.1. ArangoDB 1.1 required users to use collection ids in many places instead of collection names. This was unintuitive and caused overhead in cases when just the collection name was known on client-side but not its id. This overhead can now be avoided so clients can work with the collection names directly. There is no need to work with collection ids - on the client side anymore. + on the client side anymore. This change will likely require adjustments to API calls issued by clients, and also requires a change in how clients handle the _id value of returned documents. Previously, the _id value of returned documents contained the collection id, a slash separator and @@ -6755,7 +6759,7 @@ v1.1.3 (2013-XX-XX) * fixed issue #381: db._collection("_users").getIndexes(); -* fixed issue #379: arango-password fatal issue javscript.startup-directory +* fixed issue #379: arango-password fatal issue javscript.startup-directory * fixed issue #372: Command-Line Options for the Authentication and Authorization @@ -6768,8 +6772,8 @@ v1.1.2 (2013-01-20) * fixed issue #357: Some spelling and grammar errors * fixed issue #355: fix quotes in pdf manual - -* fixed issue #351: Strange arangosh error message for long running query + +* fixed issue #351: Strange arangosh error message for long running query * fixed randomly hanging connections in arangosh on MacOS @@ -6793,14 +6797,14 @@ v1.1.2 (2013-01-20) * started with issue #317: Feature Request (from Google Groups): DATE handling -* backported issue #300: Extend arangoImp to Allow importing resultset-like +* backported issue #300: Extend arangoImp to Allow importing resultset-like (list of documents) formatted files * fixed issue #337: "WaitForSync" on new collection does not work on Win/X64 * fixed issue #336: Collections REST API docs -* fixed issue #335: mmap errors due to wrong memory address calculation +* fixed issue #335: mmap errors due to wrong memory address calculation * fixed issue #332: arangoimp --use-ids parameter seems to have no impact @@ -6847,24 +6851,24 @@ v1.1.0 (2012-12-05) perform an automatic upgrade of the database directory. This should be the normal case when upgrading from ArangoDB 1.0 to ArangoDB 1.1. - If the VERSION file is present but is from an older version of ArangoDB, arangod + If the VERSION file is present but is from an older version of ArangoDB, arangod will refuse to start and ask the user to run a manual upgrade first. A manual upgrade - can be performed by starting arangod with the option `--upgrade`. + can be performed by starting arangod with the option `--upgrade`. - This upgrade procedure shall ensure that users have full control over when they + This upgrade procedure shall ensure that users have full control over when they perform any updates/upgrades of their data, and can plan backups accordingly. The procedure also guarantees that the server is not run without any required system collections or with in incompatible data state. * added AQL function DOCUMENT() to retrieve a document by its _id value -* fixed issue #311: fixed segfault on unload +* fixed issue #311: fixed segfault on unload * fixed issue #309: renamed stub "import" button from web interface -* fixed issue #307: added WaitForSync column in collections list in in web interface +* fixed issue #307: added WaitForSync column in collections list in in web interface -* fixed issue #306: naming in web interface +* fixed issue #306: naming in web interface * fixed issue #304: do not clear AQL query text input when switching tabs in web interface @@ -6894,11 +6898,11 @@ v1.1.0 (2012-12-05) compatible. * changed misleading AQL function name NOT_LIST() to FIRST_LIST() and slightly changed - the behavior. The function will now return its first argument that is a list, or null + the behavior. The function will now return its first argument that is a list, or null if none of the arguments are lists. This is mostly downwards-compatible. The only change to the previous implementation in - 1.1-beta will happen if two arguments were passed and the 1st and 2nd arguments were - both no lists. In previous 1.1, the 2nd argument was returned as is, but now null + 1.1-beta will happen if two arguments were passed and the 1st and 2nd arguments were + both no lists. In previous 1.1, the 2nd argument was returned as is, but now null will be returned. * add AQL function FIRST_DOCUMENT(), with same behavior as FIRST_LIST(), but working @@ -6941,15 +6945,15 @@ v1.1.beta1 (2012-10-24) "/var/lib/arangodb" to be compliant with various Linux policies - In 1.1, we have introduced types for collections: regular documents go into document - collections, and edges go into edge collections. The prefixing (db.xxx vs. edges.xxx) - works slightly different in 1.1: edges.xxx can still be used to access collections, - however, it will not determine the type of existing collections anymore. To create an - edge collection 1.1, you can use db._createEdgeCollection() or edges._create(). - And there's of course also db._createDocumentCollection(). - db._create() is also still there and will create a document collection by default, + collections, and edges go into edge collections. The prefixing (db.xxx vs. edges.xxx) + works slightly different in 1.1: edges.xxx can still be used to access collections, + however, it will not determine the type of existing collections anymore. To create an + edge collection 1.1, you can use db._createEdgeCollection() or edges._create(). + And there's of course also db._createDocumentCollection(). + db._create() is also still there and will create a document collection by default, whereas edges._create() will create an edge collection. - - the admin web interface that was previously available via the simple URL suffix / + - the admin web interface that was previously available via the simple URL suffix / is now available via a dedicated URL suffix only: /_admin/html The reason for this is that routing and URLs are now subject to changes by the end user, and only URLs parts prefixed with underscores (e.g. /_admin or /_api) are reserved @@ -6966,7 +6970,7 @@ v1.1.beta1 (2012-10-24) wait for the client to send the missing bytes. The server allows 90 seconds for this and will close the connection if the client does not send the remaining data - - if Content-Length is bigger than the maximum allowed size (512 MB), the server will + - if Content-Length is bigger than the maximum allowed size (512 MB), the server will fail with HTTP 413 (request entity too large). - if the length of the HTTP headers is greater than the maximum allowed size (1 MB), @@ -6992,14 +6996,14 @@ v1.1.beta1 (2012-10-24) * issue #245: Documentation: Central place for naming rules/limits inside ArangoDB -* reduced size of hash index elements by 50 %, allowing more index elements to fit in +* reduced size of hash index elements by 50 %, allowing more index elements to fit in memory * issue #235: GUI Shell throws Error:ReferenceError: db is not defined -* issue #229: methods marked as "under construction" +* issue #229: methods marked as "under construction" -* issue #228: remove unfinished APIs (/_admin/config/*) +* issue #228: remove unfinished APIs (/_admin/config/*) * having the OpenSSL library installed is now a prerequisite to compiling ArangoDB Also removed the --enable-ssl configure option because ssl is always required. @@ -7019,14 +7023,14 @@ v1.1.beta1 (2012-10-24) * issue #213: make waitForSync overridable on specific actions * changed AQL optimizer to use indexes in more cases. Previously, indexes might - not have been used when in a reference expression the inner collection was + not have been used when in a reference expression the inner collection was specified last. Example: FOR u1 IN users FOR u2 IN users FILTER u1._id == u2._id Previously, this only checked whether an index could be used for u2._id (not possible). It was not checked whether an index on u1._id could be used (possible). Now, for expressions that have references/attribute names on both sides of the above as above, indexes are checked for both sides. - -* issue #204: extend the CSV import by TSV and by user configurable + +* issue #204: extend the CSV import by TSV and by user configurable separator character(s) * issue #180: added support for batch operations @@ -7052,7 +7056,7 @@ v1.1.beta1 (2012-10-24) * fixed issue #188: intermittent issues with 1.0.0 (server-side cursors not cleaned up in all cases, pthreads deadlock issue) -* issue #189: key store should use ISO datetime format bug +* issue #189: key store should use ISO datetime format bug * issue #187: run arango-upgrade on server start (note: arango-upgrade was finally replaced by the `--upgrade` option for arangod)n @@ -7063,7 +7067,7 @@ v1.1.beta1 (2012-10-24) * fixed issue #181: use getaddrinfo -* moved default database directory to "/var/lib/arangodb" in accordance with +* moved default database directory to "/var/lib/arangodb" in accordance with http://www.pathname.com/fhs/pub/fhs-2.3.html * fixed issue #179: strange text in import manual @@ -7075,7 +7079,7 @@ v1.1.beta1 (2012-10-24) * fixed issue #176: explain how to use AQL from the arangosh -* issue #175: re-added hidden (and deprecated) option --server.http-port. This +* issue #175: re-added hidden (and deprecated) option --server.http-port. This option is only there to be downwards-compatible to Arango 1.0. * fixed issue #174: missing Documentation for `within` @@ -7091,13 +7095,13 @@ v1.1.beta1 (2012-10-24) Note: arango-upgrade was finally replaced by the `--upgrade` option for arangod. * issue #153: edge collection should be a flag for a collection - collections now have a type so that the distinction between document and edge + collections now have a type so that the distinction between document and edge collections can now be done at runtime using a collection's type value. - A collection's type can be queried in Javascript using the .type() method. - - When new collections are created using db._create(), they will be document + A collection's type can be queried in Javascript using the .type() method. + + When new collections are created using db._create(), they will be document collections by default. When edge._create() is called, an edge collection will be created. - To explicitly create a collection of a specific/different type, use the methods + To explicitly create a collection of a specific/different type, use the methods _createDocumentCollection() or _createEdgeCollection(), which are available for both the db and the edges object. The Javascript objects ArangoEdges and ArangoEdgesCollection have been removed @@ -7105,7 +7109,7 @@ v1.1.beta1 (2012-10-24) All internal and test code has been adjusted for this, and client code that uses edges.* should also still work because edges is still there and creates edge collections when _create() is called. - + INCOMPATIBLE CHANGE: Client code might still need to be changed in the following aspect: Previously, collections did not have a type so documents and edges could be inserted in the same collection. This is now disallowed. Edges can only be inserted into @@ -7138,7 +7142,7 @@ v1.0.3 (2012-11-08) * fixed AQL optimizer bug -* issue #273: fixed segfault in arangosh on HTTP 40x +* issue #273: fixed segfault in arangosh on HTTP 40x * issue #265: allow optional base64 encoding/decoding of action response data @@ -7174,11 +7178,11 @@ v1.0.1 (2012-09-30) * fix for issue #193: REST API HEAD request returns a message body on 404 -* fix for issue #188: intermittent issues with 1.0.0 +* fix for issue #188: intermittent issues with 1.0.0 * fix for issue #163: server cannot create collection because of abandoned files - -* fix for issue #150: call V8 garbage collection on server periodically + +* fix for issue #150: call V8 garbage collection on server periodically v1.0.0 (2012-08-17) @@ -7213,10 +7217,10 @@ v1.0.beta3 (2012-08-10) * fix for issue #59: added tests for /_api/import -* modified return value for calls to /_api/import: now, the attribute "empty" is +* modified return value for calls to /_api/import: now, the attribute "empty" is returned as well, stating the number of empty lines in the input. Also changed the return value of the error code attribute ("errorNum") from 1100 ("corrupted datafile") - to 400 ("bad request") in case invalid/unexpected JSON data was sent to the server. + to 400 ("bad request") in case invalid/unexpected JSON data was sent to the server. This error code is more appropriate as no datafile is broken but just input data is incorrect. @@ -7226,32 +7230,32 @@ v1.0.beta3 (2012-08-10) * value of --database.maximal-journal-size parameter is now validated on startup. If value is smaller than the minimum value (currently 1048576), an error is thrown and - the server will not start. Before this change, the global value of maximal journal + the server will not start. Before this change, the global value of maximal journal size was not validated at server start, but only on collection level * increased sleep value in statistics creation loop from 10 to 500 microseconds. This reduces accuracy of statistics values somewhere after the decimal points but saves CPU time. -* avoid additional sync() calls when writing partial shape data (attribute name data) +* avoid additional sync() calls when writing partial shape data (attribute name data) to disk. sync() will still be called when the shape marker (will be written after - the attributes) is written to disk + the attributes) is written to disk * issue #147: added flag --database.force-sync-shapes to force synching of shape data to disk. The default value is true so it is the same behavior as in version 1.0. if set to false, shape data is synched to disk if waitForSync for the collection is set to true, otherwise, shape data is not synched. - + * fix for issue #145: strange issue on Travis: added epsilon for numeric comparison in geo index * fix for issue #136: adjusted message during indexing -* issue #131: added timeout for HTTP keep-alive connections. The default value is 300 - seconds. There is a startup parameter server.keep-alive-timeout to configure the value. +* issue #131: added timeout for HTTP keep-alive connections. The default value is 300 + seconds. There is a startup parameter server.keep-alive-timeout to configure the value. Setting it to 0 will disable keep-alive entirely on the server. -* fix for issue #137: AQL optimizer should use indexes for ref accesses with +* fix for issue #137: AQL optimizer should use indexes for ref accesses with 2 named attributes @@ -7270,7 +7274,7 @@ v1.0.beta1 (2012-07-29) * fixed issue #126: Access-Shaper must be cached -* INCOMPATIBLE CHANGE: renamed parameters "connect-timeout" and "request-timeout" +* INCOMPATIBLE CHANGE: renamed parameters "connect-timeout" and "request-timeout" for arangosh and arangoimp to "--server.connect-timeout" and "--server.request-timeout" * INCOMPATIBLE CHANGE: authorization is now required on the server side @@ -7286,7 +7290,7 @@ v1.0.beta1 (2012-07-29) used. * added startup option "--server.ssl-cipher-list" to determine which ciphers to - use in SSL context. also added SSL_OP_CIPHER_SERVER_PREFERENCE to SSL default + use in SSL context. also added SSL_OP_CIPHER_SERVER_PREFERENCE to SSL default options so ciphers are tried in server and not in client order * changed default SSL protocol to TLSv1 instead of SSLv2 @@ -7295,15 +7299,15 @@ v1.0.beta1 (2012-07-29) * added SSL connections if server is compiled with OpenSSL support. Use --help-ssl -* INCOMPATIBLE CHANGE: removed startup option "--server.admin-port". - The new endpoints feature (see --server.endpoint) allows opening multiple endpoints - anyway, and the distinction between admin and "other" endpoints can be emulated +* INCOMPATIBLE CHANGE: removed startup option "--server.admin-port". + The new endpoints feature (see --server.endpoint) allows opening multiple endpoints + anyway, and the distinction between admin and "other" endpoints can be emulated later using privileges. -* INCOMPATIBLE CHANGE: removed startup options "--port", "--server.port", and - "--server.http-port" for arangod. +* INCOMPATIBLE CHANGE: removed startup options "--port", "--server.port", and + "--server.http-port" for arangod. These options have been replaced by the new "--server.endpoint" parameter - + * INCOMPATIBLE CHANGE: removed startup option "--server" for arangosh and arangoimp. These options have been replaced by the new "--server.endpoint" parameter @@ -7320,9 +7324,9 @@ v1.0.beta1 (2012-07-29) If no port is specified, the default port of 8529 will be used. -* INCOMPATIBLE CHANGE: removed startup options "--server.require-keep-alive" and - "--server.secure-require-keep-alive". - The server will now behave as follows which should be more conforming to the +* INCOMPATIBLE CHANGE: removed startup options "--server.require-keep-alive" and + "--server.secure-require-keep-alive". + The server will now behave as follows which should be more conforming to the HTTP standard: * if a client sends a "Connection: close" header, the server will close the connection @@ -7332,7 +7336,7 @@ v1.0.beta1 (2012-07-29) "keep-alive" if the request was an HTTP/1.1 request, and "close" if the request was an HTTP/1.0 request -* (minimal) internal optimizations for HTTP request parsing and response header +* (minimal) internal optimizations for HTTP request parsing and response header handling * fixed Unicode unescaping bugs for \f and surrogate pairs in BasicsC/strings.c @@ -7390,7 +7394,7 @@ v1.0.alpha2 (2012-06-24) * fixed issue #100: "count" attribute exists in cursor response with "count: false" -* fixed issue #84 explain command +* fixed issue #84 explain command * added new MRuby version (2012-06-02) diff --git a/CMakeLists.txt b/CMakeLists.txt index 34e29a1a17..21f03f1460 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -357,6 +357,18 @@ if (USE_TCMALLOC) find_package(tcmalloc) endif () +################################################################################ +## LIBRARY RESOLV +################################################################################ + +if (NOT WINDOWS) + check_library_exists(resolv dn_expand "" RESOLV_LIB_REQ) + + if (RESOLV_LIB_REQ) + set(NET_LIBS ${NET_LIBS} resolv) + endif () +endif () + ################################################################################ ## FLAGS ################################################################################ @@ -703,6 +715,7 @@ list(INSERT SYSTEM_LIBRARIES 0 ${BASE_LIBS} ${CMAKE_THREAD_LIBS_INIT} ${CMAKE_DL_LIBS} + ${NET_LIBS} ) add_subdirectory(lib) diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 4bd7f77229..9472723472 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -96,6 +96,7 @@ else () Basics/terminal-utils-posix.cpp Basics/threads-posix.cpp Rest/EndpointUnixDomain.cpp + Rest/EndpointSrv.cpp ) endif () diff --git a/lib/Rest/Endpoint.cpp b/lib/Rest/Endpoint.cpp index e4feb7e3db..f583162dc0 100644 --- a/lib/Rest/Endpoint.cpp +++ b/lib/Rest/Endpoint.cpp @@ -32,6 +32,7 @@ #endif #include "Rest/EndpointIpV4.h" #include "Rest/EndpointIpV6.h" +#include "Rest/EndpointSrv.h" using namespace arangodb::basics; using namespace arangodb::rest; @@ -40,8 +41,7 @@ using namespace arangodb::rest; /// @brief create an endpoint //////////////////////////////////////////////////////////////////////////////// -Endpoint::Endpoint(Endpoint::EndpointType type, - Endpoint::DomainType domainType, +Endpoint::Endpoint(Endpoint::EndpointType type, Endpoint::DomainType domainType, Endpoint::EncryptionType encryption, std::string const& specification, int listenBacklog) : _connected(false), @@ -94,25 +94,18 @@ std::string Endpoint::getUnifiedForm(std::string const& specification) { return ""; } #endif - else if (!StringUtils::isPrefix(copy, "ssl://") && - !StringUtils::isPrefix(copy, "tcp://")) { + else if (StringUtils::isPrefix(copy, "srv://")) { + return copy; + } else if (!StringUtils::isPrefix(copy, "ssl://") && + !StringUtils::isPrefix(copy, "tcp://")) { // invalid type return ""; } - size_t found; - /* - // turn "localhost" into "127.0.0.1" - // technically this is not always correct, but circumvents obvious problems - // when the configuration contains both "127.0.0.1" and "localhost" endpoints - found = copy.find("localhost"); - if (found != string::npos && found >= 6 && found < 10) { - copy = copy.replace(found, strlen("localhost"), "127.0.0.1"); - } - */ - // tcp/ip or ssl + size_t found; std::string temp = copy.substr(6, copy.length()); // strip tcp:// or ssl:// + if (temp[0] == '[') { // ipv6 found = temp.find("]:", 1); @@ -221,6 +214,14 @@ Endpoint* Endpoint::factory(const Endpoint::EndpointType type, } #endif + else if (StringUtils::isPrefix(domainType, "srv://")) { + if (type != ENDPOINT_CLIENT) { + return nullptr; + } + + return new EndpointSrv(specification.substr(6)); + } + else if (!StringUtils::isPrefix(domainType, "tcp://")) { // invalid type return nullptr; @@ -307,7 +308,8 @@ bool Endpoint::setSocketFlags(TRI_socket_t s) { bool ok = TRI_SetNonBlockingSocket(s); if (!ok) { - LOG(ERR) << "cannot switch to non-blocking: " << errno << " (" << strerror(errno) << ")"; + LOG(ERR) << "cannot switch to non-blocking: " << errno << " (" + << strerror(errno) << ")"; return false; } @@ -316,7 +318,8 @@ bool Endpoint::setSocketFlags(TRI_socket_t s) { ok = TRI_SetCloseOnExecSocket(s); if (!ok) { - LOG(ERR) << "cannot set close-on-exit: " << errno << " (" << strerror(errno) << ")"; + LOG(ERR) << "cannot set close-on-exit: " << errno << " (" << strerror(errno) + << ")"; return false; } diff --git a/lib/Rest/Endpoint.h b/lib/Rest/Endpoint.h index 09009e6b41..f5d872bb4b 100644 --- a/lib/Rest/Endpoint.h +++ b/lib/Rest/Endpoint.h @@ -44,216 +44,64 @@ namespace arangodb { namespace rest { - -//////////////////////////////////////////////////////////////////////////////// -/// @brief endpoint specification -//////////////////////////////////////////////////////////////////////////////// - class Endpoint { public: - ////////////////////////////////////////////////////////////////////////////// - /// @brief endpoint types - ////////////////////////////////////////////////////////////////////////////// - enum EndpointType { ENDPOINT_SERVER, ENDPOINT_CLIENT }; - ////////////////////////////////////////////////////////////////////////////// - /// @brief endpoint types - ////////////////////////////////////////////////////////////////////////////// - - enum DomainType { DOMAIN_UNKNOWN = 0, DOMAIN_UNIX, DOMAIN_IPV4, DOMAIN_IPV6 }; - - ////////////////////////////////////////////////////////////////////////////// - /// @brief encryption used when talking to endpoint - ////////////////////////////////////////////////////////////////////////////// + enum DomainType { + DOMAIN_UNKNOWN = 0, + DOMAIN_UNIX, + DOMAIN_IPV4, + DOMAIN_IPV6, + DOMAIN_SRV + }; enum EncryptionType { ENCRYPTION_NONE = 0, ENCRYPTION_SSL }; protected: - ////////////////////////////////////////////////////////////////////////////// - /// @brief creates an endpoint - ////////////////////////////////////////////////////////////////////////////// - - Endpoint(EndpointType, DomainType, EncryptionType, - std::string const&, int); + Endpoint(EndpointType, DomainType, EncryptionType, std::string const&, int); public: - ////////////////////////////////////////////////////////////////////////////// - /// @brief destroys an endpoint - ////////////////////////////////////////////////////////////////////////////// - virtual ~Endpoint(); public: - ////////////////////////////////////////////////////////////////////////////// - /// @brief return the endpoint specification in a unified form - ////////////////////////////////////////////////////////////////////////////// - static std::string getUnifiedForm(std::string const&); - - ////////////////////////////////////////////////////////////////////////////// - /// @brief creates a server endpoint from a string value - ////////////////////////////////////////////////////////////////////////////// - static Endpoint* serverFactory(std::string const&, int, bool reuseAddress); - - ////////////////////////////////////////////////////////////////////////////// - /// @brief creates a client endpoint from a string value - ////////////////////////////////////////////////////////////////////////////// - static Endpoint* clientFactory(std::string const&); - - ////////////////////////////////////////////////////////////////////////////// - /// @brief creates an endpoint from a string value - ////////////////////////////////////////////////////////////////////////////// - static Endpoint* factory(const EndpointType type, std::string const&, int, bool); - - ////////////////////////////////////////////////////////////////////////////// - /// @brief compare two endpoints - ////////////////////////////////////////////////////////////////////////////// - - bool operator==(Endpoint const&) const; - - ////////////////////////////////////////////////////////////////////////////// - /// @brief return the default endpoint - ////////////////////////////////////////////////////////////////////////////// - static std::string const getDefaultEndpoint(); - ////////////////////////////////////////////////////////////////////////////// - /// @brief connect the endpoint - ////////////////////////////////////////////////////////////////////////////// - - virtual TRI_socket_t connect(double, double) = 0; - - ////////////////////////////////////////////////////////////////////////////// - /// @brief disconnect the endpoint - ////////////////////////////////////////////////////////////////////////////// - - virtual void disconnect() = 0; - - ////////////////////////////////////////////////////////////////////////////// - /// @brief init an incoming connection - ////////////////////////////////////////////////////////////////////////////// - - virtual bool initIncoming(TRI_socket_t) = 0; - - ////////////////////////////////////////////////////////////////////////////// - /// @brief set socket timeout - ////////////////////////////////////////////////////////////////////////////// - - virtual bool setTimeout(TRI_socket_t, double); - - ////////////////////////////////////////////////////////////////////////////// - /// @brief initialize socket flags - ////////////////////////////////////////////////////////////////////////////// - - virtual bool setSocketFlags(TRI_socket_t); - - ////////////////////////////////////////////////////////////////////////////// - /// @brief return whether the endpoint is connected - ////////////////////////////////////////////////////////////////////////////// - - bool isConnected() const { return _connected; } - - ////////////////////////////////////////////////////////////////////////////// - /// @brief get the type of an endpoint - ////////////////////////////////////////////////////////////////////////////// - + public: + bool operator==(Endpoint const&) const; EndpointType getType() const { return _type; } - - ////////////////////////////////////////////////////////////////////////////// - /// @brief get the domain type of an endpoint - ////////////////////////////////////////////////////////////////////////////// - - DomainType getDomainType() const { return _domainType; } - - ////////////////////////////////////////////////////////////////////////////// - /// @brief get the encryption used - ////////////////////////////////////////////////////////////////////////////// - EncryptionType getEncryption() const { return _encryption; } - - ////////////////////////////////////////////////////////////////////////////// - /// @brief get the original endpoint specification - ////////////////////////////////////////////////////////////////////////////// - std::string getSpecification() const { return _specification; } - ////////////////////////////////////////////////////////////////////////////// - /// @brief get endpoint domain - ////////////////////////////////////////////////////////////////////////////// + public: + virtual TRI_socket_t connect(double, double) = 0; + virtual void disconnect() = 0; + virtual bool initIncoming(TRI_socket_t) = 0; + virtual bool setTimeout(TRI_socket_t, double); + virtual bool isConnected() const { return _connected; } + virtual bool setSocketFlags(TRI_socket_t); + virtual DomainType getDomainType() const { return _domainType; } virtual int getDomain() const = 0; - - ////////////////////////////////////////////////////////////////////////////// - /// @brief get port number - ////////////////////////////////////////////////////////////////////////////// - virtual int getPort() const = 0; - - ////////////////////////////////////////////////////////////////////////////// - /// @brief get host name - ////////////////////////////////////////////////////////////////////////////// - virtual std::string getHost() const = 0; - - ////////////////////////////////////////////////////////////////////////////// - /// @brief get address - ////////////////////////////////////////////////////////////////////////////// - virtual std::string getHostString() const = 0; public: - ////////////////////////////////////////////////////////////////////////////// - /// @brief error message if failure occurred - ////////////////////////////////////////////////////////////////////////////// - std::string _errorMessage; protected: - ////////////////////////////////////////////////////////////////////////////// - /// @brief whether or not the endpoint is connected - ////////////////////////////////////////////////////////////////////////////// - bool _connected; - - ////////////////////////////////////////////////////////////////////////////// - /// @brief the actual socket - ////////////////////////////////////////////////////////////////////////////// - TRI_socket_t _socket; - - ////////////////////////////////////////////////////////////////////////////// - /// @brief endpoint type - ////////////////////////////////////////////////////////////////////////////// - EndpointType _type; - - ////////////////////////////////////////////////////////////////////////////// - /// @brief endpoint domain type - ////////////////////////////////////////////////////////////////////////////// - DomainType _domainType; - - ////////////////////////////////////////////////////////////////////////////// - /// @brief encryption used - ////////////////////////////////////////////////////////////////////////////// - EncryptionType _encryption; - - ////////////////////////////////////////////////////////////////////////////// - /// @brief original endpoint specification - ////////////////////////////////////////////////////////////////////////////// - std::string _specification; - - ////////////////////////////////////////////////////////////////////////////// - /// @brief listen backlog size - ////////////////////////////////////////////////////////////////////////////// - int _listenBacklog; }; } diff --git a/lib/Rest/EndpointIp.cpp b/lib/Rest/EndpointIp.cpp index 80ed29f3cd..077c946f61 100644 --- a/lib/Rest/EndpointIp.cpp +++ b/lib/Rest/EndpointIp.cpp @@ -93,6 +93,7 @@ TRI_socket_t EndpointIp::connectSocket(const struct addrinfo* aip, #ifdef _WIN32 char windowsErrorBuf[256]; #endif + // set address and port char host[NI_MAXHOST]; char serv[NI_MAXSERV]; diff --git a/lib/Rest/EndpointSrv.cpp b/lib/Rest/EndpointSrv.cpp new file mode 100644 index 0000000000..f2566c5530 --- /dev/null +++ b/lib/Rest/EndpointSrv.cpp @@ -0,0 +1,255 @@ +//////////////////////////////////////////////////////////////////////////////// +/// DISCLAIMER +/// +/// Copyright 2016 ArangoDB 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 ArangoDB GmbH, Cologne, Germany +/// +/// @author Dr. Frank Celler +/// @author Copyright 2016, ArangoDB GmbH, Cologne, Germany +//////////////////////////////////////////////////////////////////////////////// + +#include "EndpointSrv.h" + +#ifndef _WIN32 + +#define BIND_4_COMPAT 1 // LINUX +#define BIND_8_COMPAT 1 // MACOSX + +#include +#include +#include +#include + +#include "Basics/StringUtils.h" +#include "Logger/Logger.h" +#include "Rest/EndpointIp.h" + +using namespace arangodb; +using namespace arangodb::basics; +using namespace arangodb::rest; + +#if PACKETSZ > 1024 +#define MAXPACKET PACKETSZ +#else +#define MAXPACKET 1024 +#endif + +union QueryBuffer { + ::HEADER header; + unsigned char buffer[MAXPACKET]; +}; + +struct SrvRecord { + int priority; + int weight; + int port; + std::string name; +}; + +std::vector srvRecords(std::string specification) { + res_init(); + + char const* dname = specification.c_str(); + int nclass = ns_c_in; + int type = ns_t_srv; + + QueryBuffer answer; + int anslen = sizeof(answer); + + int n = res_search(dname, nclass, type, answer.buffer, anslen); + + std::vector services; + + if (n != -1) { + HEADER* hp = &answer.header; + + int qdcount = ntohs(hp->qdcount); + int ancount = ntohs(hp->ancount); + + unsigned char* msg = answer.buffer; + unsigned char* eom = msg + n; + unsigned char* cp = msg + sizeof(HEADER); + + unsigned char hostbuf[256]; + + while (0 < qdcount-- && cp < eom) { + n = dn_expand(msg, eom, cp, (char*)hostbuf, 256); + + if (n < 0) { + LOG(WARN) << "DNS record for '" << specification << "' is corrupt"; + return {}; + } + + cp += n + QFIXEDSZ; + } + + // loop through the answer buffer and extract SRV records + while (0 < ancount-- && cp < eom) { + n = dn_expand(msg, eom, cp, (char*)hostbuf, 256); + + if (n < 0) { + LOG(WARN) << "DNS record for '" << specification << "' is corrupt"; + return {}; + } + + cp += n; + + int type = _getshort(cp); + cp += 2; + + int nclass = _getshort(cp); + cp += 2; + + int ttl = _getlong(cp); + cp += 4; + + int dlen = _getshort(cp); + cp += 2; + + int priority = _getshort(cp); + cp += 2; + + int weight = _getshort(cp); + cp += 2; + + int port = _getshort(cp); + cp += 2; + + n = dn_expand(msg, eom, cp, (char*)hostbuf, 256); + + if (n < 0) { + LOG(WARN) << "DNS record for '" << specification << "' is corrupt"; + break; + } + + cp += n; + + LOG(TRACE) << "DNS record for '" << specification << "': type " << type + << ", class " << nclass << ", ttl " << ttl << ", len " << dlen + << ", prio " << priority << ", weight " << weight << ", port " + << port << ", host '" << hostbuf << "'"; + + if (type != T_SRV) { + continue; + } + + SrvRecord srv; + + srv.weight = weight; + srv.priority = priority; + srv.port = port; + srv.name = (char*)hostbuf; + + services.push_back(srv); + } + } else { + LOG(WARN) << "DNS record for '" << specification << "' not found"; + } + + std::sort(services.begin(), services.end(), + [](SrvRecord const& lhs, SrvRecord const& rhs) { + if (lhs.priority != rhs.priority) { + return lhs.priority < rhs.priority; + } + + return lhs.weight > rhs.weight; + }); + + return services; +} + +#else + +std::vector srvRecords(std::string specification) { return {}; } + +#endif + +EndpointSrv::EndpointSrv(std::string const& specification) + : Endpoint(ENDPOINT_CLIENT, DOMAIN_SRV, ENCRYPTION_NONE, specification, 0) { +} + +EndpointSrv::~EndpointSrv() {} + +bool EndpointSrv::isConnected() const { + if (_endpoint != nullptr) { + return _endpoint->isConnected(); + } + + return false; +} + +TRI_socket_t EndpointSrv::connect(double connectTimeout, + double requestTimeout) { + auto services = srvRecords(_specification); + + TRI_socket_t res; + + for (auto service : services) { + std::string spec = + "tcp://" + service.name + ":" + StringUtils::itoa(service.port); + + _endpoint.reset(Endpoint::clientFactory(spec)); + + res = _endpoint->connect(connectTimeout, requestTimeout); + + if (_endpoint->isConnected()) { + return res; + } + } + + TRI_invalidatesocket(&res); + return res; +} + +void EndpointSrv::disconnect() { + if (_endpoint != nullptr) { + _endpoint->disconnect(); + } +} + +bool EndpointSrv::initIncoming(TRI_socket_t) { return false; } + +int EndpointSrv::getDomain() const { + if (_endpoint != nullptr) { + return _endpoint->getDomain(); + } + + return -1; +} + +int EndpointSrv::getPort() const { + if (_endpoint != nullptr) { + return _endpoint->getPort(); + } + + return -1; +} + +std::string EndpointSrv::getHost() const { + if (_endpoint != nullptr) { + return _endpoint->getHost(); + } + + return ""; +} + +std::string EndpointSrv::getHostString() const { + if (_endpoint != nullptr) { + return _endpoint->getHostString(); + } + + return ""; +} diff --git a/lib/Rest/EndpointSrv.h b/lib/Rest/EndpointSrv.h new file mode 100644 index 0000000000..482174c07f --- /dev/null +++ b/lib/Rest/EndpointSrv.h @@ -0,0 +1,53 @@ +//////////////////////////////////////////////////////////////////////////////// +/// DISCLAIMER +/// +/// Copyright 2016 ArangoDB 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 ArangoDB GmbH, Cologne, Germany +/// +/// @author Dr. Frank Celler +/// @author Copyright 2016, ArangoDB GmbH, Cologne, Germany +//////////////////////////////////////////////////////////////////////////////// + +#ifndef ARANGODB_REST_ENDPOINT_SRV_H +#define ARANGODB_REST_ENDPOINT_SRV_H 1 + +#include "Rest/Endpoint.h" + +namespace arangodb { +namespace rest { +class EndpointSrv final : public Endpoint { + public: + explicit EndpointSrv(std::string const&); + + ~EndpointSrv(); + + public: + bool isConnected() const override; + TRI_socket_t connect(double, double) override; + void disconnect() override; + bool initIncoming(TRI_socket_t) override; + int getDomain() const override; + int getPort() const override; + std::string getHost() const override; + std::string getHostString() const override; + + private: + std::unique_ptr _endpoint; +}; +} +} + +#endif diff --git a/lib/V8/v8-utils.cpp b/lib/V8/v8-utils.cpp index 73e1ff3ca6..8db4b2face 100644 --- a/lib/V8/v8-utils.cpp +++ b/lib/V8/v8-utils.cpp @@ -744,7 +744,21 @@ static void JS_Download(v8::FunctionCallbackInfo const& args) { endpoint = endpoint + ":443"; } endpoint = "ssl://" + endpoint; - } else if (!url.empty() && url[0] == '/') { + } + else if (url.substr(0, 6) == "srv://") { + size_t found = url.find('/', 6); + + relative = "/"; + if (found != std::string::npos) { + relative.append(url.substr(found + 1)); + endpoint = url.substr(6, found - 6); + } + else { + endpoint = url.substr(6); + } + endpoint = "srv://" + endpoint; + } + else if (! url.empty() && url[0] == '/') { // relative URL. prefix it with last endpoint relative = url; url = lastEndpoint + url; From ee7ad9efd127c15d9549f6b7f4363a6aa861178a Mon Sep 17 00:00:00 2001 From: Frank Celler Date: Wed, 9 Mar 2016 13:51:15 +0100 Subject: [PATCH 12/20] switch to GETLONG --- lib/Rest/EndpointSrv.cpp | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/lib/Rest/EndpointSrv.cpp b/lib/Rest/EndpointSrv.cpp index f2566c5530..d8ec3a8c60 100644 --- a/lib/Rest/EndpointSrv.cpp +++ b/lib/Rest/EndpointSrv.cpp @@ -107,26 +107,26 @@ std::vector srvRecords(std::string specification) { cp += n; - int type = _getshort(cp); - cp += 2; + int type; + GETSHORT(type, cp); - int nclass = _getshort(cp); - cp += 2; + int nclass; + GETSHORT(nclass, cp); - int ttl = _getlong(cp); - cp += 4; + int ttl; + GETLONG(ttl, cp); - int dlen = _getshort(cp); - cp += 2; + int dlen; + GETSHORT(dlen, cp); - int priority = _getshort(cp); - cp += 2; + int priority; + GETSHORT(priority, cp); - int weight = _getshort(cp); - cp += 2; + int weight; + GETSHORT(weight, cp); - int port = _getshort(cp); - cp += 2; + int port; + GETSHORT(port, cp); n = dn_expand(msg, eom, cp, (char*)hostbuf, 256); From 6204de9ed968dbffe25cc0599cc696bf867f192c Mon Sep 17 00:00:00 2001 From: jsteemann Date: Wed, 16 Mar 2016 00:13:30 +0100 Subject: [PATCH 13/20] always link against libresolv --- CMakeLists.txt | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 21f03f1460..a7aaaa2652 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -362,11 +362,7 @@ endif () ################################################################################ if (NOT WINDOWS) - check_library_exists(resolv dn_expand "" RESOLV_LIB_REQ) - - if (RESOLV_LIB_REQ) - set(NET_LIBS ${NET_LIBS} resolv) - endif () + set(NET_LIBS ${NET_LIBS} resolv) endif () ################################################################################ From abc7d6b84b5e1c794b72b029cf33f5f32a8d60b2 Mon Sep 17 00:00:00 2001 From: Frank Celler Date: Wed, 16 Mar 2016 09:26:03 +0100 Subject: [PATCH 14/20] added missing global section --- lib/ApplicationFeatures/ConsoleFeature.cpp | 7 +++++-- lib/ApplicationFeatures/LanguageFeature.cpp | 3 +++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/ApplicationFeatures/ConsoleFeature.cpp b/lib/ApplicationFeatures/ConsoleFeature.cpp index 4577802073..95454cf394 100644 --- a/lib/ApplicationFeatures/ConsoleFeature.cpp +++ b/lib/ApplicationFeatures/ConsoleFeature.cpp @@ -86,12 +86,15 @@ ConsoleFeature::ConsoleFeature(application_features::ApplicationServer* server) void ConsoleFeature::collectOptions(std::shared_ptr options) { LOG_TOPIC(TRACE, Logger::STARTUP) << name() << "::collectOptions"; - options->addSection(Section("console", "Configure the console", - "console options", false, false)); + options->addSection( + Section("", "Global configuration", "global options", false, false)); options->addOption("--quiet", "silent startup", new BooleanParameter(&_quiet, false)); + options->addSection(Section("console", "Configure the console", + "console options", false, false)); + options->addOption("--console.colors", "enable color support", new BooleanParameter(&_colors)); diff --git a/lib/ApplicationFeatures/LanguageFeature.cpp b/lib/ApplicationFeatures/LanguageFeature.cpp index bd0776e5f9..39f9812df9 100644 --- a/lib/ApplicationFeatures/LanguageFeature.cpp +++ b/lib/ApplicationFeatures/LanguageFeature.cpp @@ -43,6 +43,9 @@ void LanguageFeature::collectOptions( std::shared_ptr options) { LOG_TOPIC(TRACE, Logger::STARTUP) << name() << "::collectOptions"; + options->addSection( + Section("", "Global configuration", "global options", false, false)); + options->addHiddenOption("--default-language", "ISO-639 language code", new StringParameter(&_language)); } From 174b0d661c2ddee7ea1876197a47981c9034f712 Mon Sep 17 00:00:00 2001 From: Frank Celler Date: Wed, 16 Mar 2016 10:49:35 +0100 Subject: [PATCH 15/20] changed to 3.0.x-devel --- CMakeLists.txt | 2 +- VERSION | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a7aaaa2652..0d02b6da46 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -33,7 +33,7 @@ set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake) set(ARANGODB_VERSION_MAJOR "3") set(ARANGODB_VERSION_MINOR "0") -set(ARANGODB_VERSION_REVISION "0-devel") +set(ARANGODB_VERSION_REVISION "x-devel") set(ARANGODB_VERSION "${ARANGODB_VERSION_MAJOR}.${ARANGODB_VERSION_MINOR}.${ARANGODB_VERSION_REVISION}") diff --git a/VERSION b/VERSION index cbd6733503..486e7ac1a1 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -3.0.0-devel +3.0.x-devel From d8746aaf8848f7daebdc88d5246376ba9503fe39 Mon Sep 17 00:00:00 2001 From: Frank Celler Date: Wed, 16 Mar 2016 11:56:50 +0100 Subject: [PATCH 16/20] moved swagger --- Documentation/CMakeLists.txt | 10 ++++++++++ cmake/ArangoDBMacros.cmake | 9 --------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/Documentation/CMakeLists.txt b/Documentation/CMakeLists.txt index ced47fdb5a..8935aa5c4c 100644 --- a/Documentation/CMakeLists.txt +++ b/Documentation/CMakeLists.txt @@ -1,5 +1,15 @@ # -*- mode: CMAKE; -*- +# swagger +add_custom_target (swagger + COMMAND ${PYTHON_EXECUTABLE} + ${PROJECT_SOURCE_DIR}/Documentation/Scripts/generateSwagger.py + ${PROJECT_SOURCE_DIR} + ${PROJECT_SOURCE_DIR}/js/apps/system/_admin/aardvark/APP/api-docs api-docs + ${PROJECT_SOURCE_DIR}/Documentation/DocuBlocks/Rest/ + > ${PROJECT_SOURCE_DIR}/js/apps/system/_admin/aardvark/APP/api-docs.json) + +# manual pages if (USE_MAINTAINER_MODE) set(MAN_NAMES man1/arangob.1 diff --git a/cmake/ArangoDBMacros.cmake b/cmake/ArangoDBMacros.cmake index b460e60a39..003f9ad8e2 100644 --- a/cmake/ArangoDBMacros.cmake +++ b/cmake/ArangoDBMacros.cmake @@ -404,15 +404,6 @@ install( # Custom targets ---------------------------------------------------------------- -# swagger -add_custom_target (swagger - COMMAND ${PYTHON_EXECUTABLE} - ${PROJECT_SOURCE_DIR}/Documentation/Scripts/generateSwagger.py - ${PROJECT_SOURCE_DIR} - ${PROJECT_SOURCE_DIR}/js/apps/system/_admin/aardvark/APP/api-docs api-docs - ${PROJECT_SOURCE_DIR}/Documentation/DocuBlocks/Rest/ - > ${PROJECT_SOURCE_DIR}/js/apps/system/_admin/aardvark/APP/api-docs.json) - # love add_custom_target (love COMMENT "ArangoDB loves you." From aede7c5eb19817e78ec16eaff835e24da20d8d69 Mon Sep 17 00:00:00 2001 From: Frank Celler Date: Wed, 16 Mar 2016 12:30:09 +0100 Subject: [PATCH 17/20] removed arangod from document path --- scripts/generateExamples.js | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/scripts/generateExamples.js b/scripts/generateExamples.js index 285a16bdf1..2531d75833 100644 --- a/scripts/generateExamples.js +++ b/scripts/generateExamples.js @@ -18,17 +18,12 @@ var PortFinder = require("@arangodb/cluster").PortFinder; var documentationSourceDirs = [ fs.join(fs.makeAbsolute(''), "Documentation/Examples/setup-arangosh.js"), + fs.join(fs.makeAbsolute(''), "Documentation/Books/Users"), fs.join(fs.makeAbsolute(''), "js/actions"), fs.join(fs.makeAbsolute(''), "js/client"), fs.join(fs.makeAbsolute(''), "js/common"), fs.join(fs.makeAbsolute(''), "js/server"), - fs.join(fs.makeAbsolute(''), "js/apps/system/_api/gharial/APP"), - fs.join(fs.makeAbsolute(''), "Documentation/Books/Users"), - fs.join(fs.makeAbsolute(''), "arangod/RestHandler"), - fs.join(fs.makeAbsolute(''), "arangod/V8Server")]; - - - + fs.join(fs.makeAbsolute(''), "js/apps/system/_api/gharial/APP")]; var theScript = 'Documentation/Scripts/generateExamples.py'; From d9f9b8b65d336cd2d8e85580e0a9f9a2d66fa2bc Mon Sep 17 00:00:00 2001 From: Frank Celler Date: Wed, 16 Mar 2016 13:01:18 +0100 Subject: [PATCH 18/20] fixed server version --- js/client/modules/@arangodb/index.js | 29 ++++++++++++-------------- js/server/modules/@arangodb/index.js | 23 ++++++++++---------- {scripts => utils}/generateExamples | 0 {scripts => utils}/generateExamples.js | 0 4 files changed, 24 insertions(+), 28 deletions(-) rename {scripts => utils}/generateExamples (100%) rename {scripts => utils}/generateExamples.js (100%) diff --git a/js/client/modules/@arangodb/index.js b/js/client/modules/@arangodb/index.js index 578abe55d6..8f9fa2516f 100644 --- a/js/client/modules/@arangodb/index.js +++ b/js/client/modules/@arangodb/index.js @@ -1,12 +1,9 @@ 'use strict'; //////////////////////////////////////////////////////////////////////////////// -/// @brief JavaScript base module -/// -/// @file -/// /// DISCLAIMER /// +/// Copyright 2016 ArangoDB GmbH, Cologne, Germany /// Copyright 2012 triagens GmbH, Cologne, Germany /// /// Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,20 +18,18 @@ /// See the License for the specific language governing permissions and /// limitations under the License. /// -/// Copyright holder is triAGENS GmbH, Cologne, Germany +/// Copyright holder is ArangoDB GmbH, Cologne, Germany /// /// @author Dr. Frank Celler -/// @author Copyright 2012, triAGENS GmbH, Cologne, Germany //////////////////////////////////////////////////////////////////////////////// var internal = require("internal"); var common = require("@arangodb/common"); -Object.keys(common).forEach(function (key) { +Object.keys(common).forEach(function(key) { exports[key] = common[key]; }); - //////////////////////////////////////////////////////////////////////////////// /// @brief isServer //////////////////////////////////////////////////////////////////////////////// @@ -90,8 +85,7 @@ if (typeof internal.arango !== 'undefined') { exports.arango = internal.arango; exports.db = new exports.ArangoDatabase(internal.arango); internal.db = exports.db; // TODO remove - } - catch (err) { + } catch (err) { internal.print("cannot connect to server: " + String(err)); } } @@ -103,17 +97,20 @@ if (typeof internal.arango !== 'undefined') { exports.plainServerVersion = function() { if (internal.arango) { let version = internal.arango.getVersion(); - let devel = version.match(/(.*)-((alpha|beta|devel|rc)[0-9]*)$/); + let devel = version.match(/(.*)\.x-devel/); if (devel !== null) { - version = devel[1]; + version = devel[1] + ".0"; + } else { + devel = version.match(/(.*)-((alpha|beta|devel|rc)[0-9]*)$/); + + if (devel !== null) { + version = devel[1]; + } } return version; - } - else { + } else { return undefined; } }; - - diff --git a/js/server/modules/@arangodb/index.js b/js/server/modules/@arangodb/index.js index bd460c14a1..e5159b43d1 100644 --- a/js/server/modules/@arangodb/index.js +++ b/js/server/modules/@arangodb/index.js @@ -1,12 +1,9 @@ 'use strict'; //////////////////////////////////////////////////////////////////////////////// -/// @brief JavaScript base module -/// -/// @file -/// /// DISCLAIMER /// +/// Copyright 2016 ArangoDB GmbH, Cologne, Germany /// Copyright 2012 triagens GmbH, Cologne, Germany /// /// Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,17 +18,16 @@ /// See the License for the specific language governing permissions and /// limitations under the License. /// -/// Copyright holder is triAGENS GmbH, Cologne, Germany +/// Copyright holder is ArangoDB GmbH, Cologne, Germany /// /// @author Dr. Frank Celler -/// @author Copyright 2012, triAGENS GmbH, Cologne, Germany //////////////////////////////////////////////////////////////////////////////// module.isSystem = true; var common = require("@arangodb/common"); -Object.keys(common).forEach(function (key) { +Object.keys(common).forEach(function(key) { exports[key] = common[key]; }); @@ -39,7 +35,6 @@ var internal = require("internal"); // OK: db var ShapedJson = require("@arangodb/shaped-json").ShapedJson; - //////////////////////////////////////////////////////////////////////////////// /// @brief isServer //////////////////////////////////////////////////////////////////////////////// @@ -91,13 +86,17 @@ exports.db = internal.db; exports.plainServerVersion = function() { let version = internal.version; - let devel = version.match(/(.*)-((alpha|beta|devel|rc)[0-9]*)$/); + let devel = version.match(/(.*)\.x-devel/); if (devel !== null) { - version = devel[1]; + version = devel[1] + ".0"; + } else { + devel = version.match(/(.*)-((alpha|beta|devel|rc)[0-9]*)$/); + + if (devel !== null) { + version = devel[1]; + } } return version; }; - - diff --git a/scripts/generateExamples b/utils/generateExamples similarity index 100% rename from scripts/generateExamples rename to utils/generateExamples diff --git a/scripts/generateExamples.js b/utils/generateExamples.js similarity index 100% rename from scripts/generateExamples.js rename to utils/generateExamples.js From cd1b2b6fc36a3ad9b1efb351bb0bc66d016b4dda Mon Sep 17 00:00:00 2001 From: Frank Celler Date: Wed, 16 Mar 2016 13:01:41 +0100 Subject: [PATCH 19/20] moved script --- js/server/modules/@arangodb/foxx/manager.js | 2 - utils/generateExamples | 61 ++++++++++++++++++++- 2 files changed, 60 insertions(+), 3 deletions(-) mode change 120000 => 100755 utils/generateExamples diff --git a/js/server/modules/@arangodb/foxx/manager.js b/js/server/modules/@arangodb/foxx/manager.js index 1d207b1d66..7bc88acfa0 100644 --- a/js/server/modules/@arangodb/foxx/manager.js +++ b/js/server/modules/@arangodb/foxx/manager.js @@ -30,8 +30,6 @@ /// @author Copyright 2013, triAGENS GmbH, Cologne, Germany //////////////////////////////////////////////////////////////////////////////// - - const _ = require('lodash'); const fs = require('fs'); const joi = require('joi'); diff --git a/utils/generateExamples b/utils/generateExamples deleted file mode 120000 index e5224d533e..0000000000 --- a/utils/generateExamples +++ /dev/null @@ -1 +0,0 @@ -run \ No newline at end of file diff --git a/utils/generateExamples b/utils/generateExamples new file mode 100755 index 0000000000..fe0aa414b1 --- /dev/null +++ b/utils/generateExamples @@ -0,0 +1,60 @@ +#!/bin/bash +export PID=$$ + +if test -n "$ORIGINAL_PATH"; then + # running in cygwin... + PS='\' + export EXT=".exe" +else + export EXT="" + PS='/' +fi; + +SCRIPT="utils${PS}generateExamples.js" +LOGFILE="out${PS}log-$PID" +DBDIR="out${PS}data-$PID" + +mkdir -p ${DBDIR} + +echo Database has its data in ${DBDIR} +echo Logfile is in ${LOGFILE} + +if [ -z "${ARANGOD}" ]; then + if [ -x build/bin/arangod ]; then + ARANGOD=build/bin/arangod + elif [ -x bin/arangosh ]; then + ARANGOD=bin/arangod + else + echo "$0: cannot locate arangod" + fi +fi + +${ARANGOD} \ + --configuration none \ + --cluster.agent-path bin${PS}etcd-arango${EXT} \ + --cluster.arangod-path bin${PS}arangod \ + --cluster.coordinator-config etc${PS}relative${PS}arangod-coordinator.conf \ + --cluster.dbserver-config etc${PS}relative${PS}arangod-dbserver.conf \ + --cluster.disable-dispatcher-frontend false \ + --cluster.disable-dispatcher-kickstarter false \ + --cluster.data-path cluster \ + --cluster.log-path cluster \ + --database.directory ${DBDIR} \ + --log.file ${LOGFILE} \ + --server.endpoint tcp://127.0.0.1:$PORT \ + --javascript.startup-directory js \ + --javascript.app-path js${PS}apps \ + --javascript.script $SCRIPT \ + --no-server \ + --temp-path ${PS}var${PS}tmp \ + "${ARGS[@]}" \ + +if test $? -eq 0; then + echo "removing ${LOGFILE} ${DBDIR}" + rm -rf ${LOGFILE} ${DBDIR} +else + echo "failed - don't remove ${LOGFILE} ${DBDIR} - here's the logfile:" + cat ${LOGFILE} +fi + +echo Server has terminated. From edf378053eb58339217a94d086669a2814c0614a Mon Sep 17 00:00:00 2001 From: Andreas Streichardt Date: Wed, 16 Mar 2016 14:22:29 +0000 Subject: [PATCH 20/20] Do not build etcd in source --- 3rdParty/CMakeLists.txt | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/3rdParty/CMakeLists.txt b/3rdParty/CMakeLists.txt index d4e9925f36..7e3bb47c52 100644 --- a/3rdParty/CMakeLists.txt +++ b/3rdParty/CMakeLists.txt @@ -17,19 +17,17 @@ if (GO_FOUND) set (ETCD_BUILD_COMMAND ./build) endif () - ExternalProject_Add(etcd_build - SOURCE_DIR - ${CMAKE_CURRENT_SOURCE_DIR}/etcd + ExternalProject_Add(etcd CONFIGURE_COMMAND "" + URL + "${CMAKE_CURRENT_SOURCE_DIR}/etcd" BUILD_IN_SOURCE - TRUE + 1 BUILD_COMMAND "${ETCD_BUILD_COMMAND}" INSTALL_COMMAND - "cp" - ${CMAKE_CURRENT_SOURCE_DIR}/etcd/bin/etcd - ${CMAKE_BINARY_DIR}/bin/etcd-arango + cp bin/etcd ${CMAKE_BINARY_DIR}/bin/etcd-arango ) endif ()