Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions ci/conda_env_cpp.txt
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ pkg-config
python
rapidjson
re2
simdjson
snappy
thrift-cpp>=0.11.0
xsimd>=14.2
Expand Down
1 change: 1 addition & 0 deletions ci/docker/alpine-linux-3.22-cpp.dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ RUN apk add \
re2-dev \
rsync \
samurai \
simdjson-dev \
snappy-dev \
sqlite-dev \
thrift-dev \
Expand Down
1 change: 1 addition & 0 deletions ci/docker/debian-13-cpp.dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ RUN apt-get update -y -q && \
libprotobuf-dev \
libprotoc-dev \
libre2-dev \
libsimdjson-dev \
libsnappy-dev \
libsqlite3-dev \
libssl-dev \
Expand Down
1 change: 1 addition & 0 deletions ci/docker/debian-experimental-cpp.dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ RUN if [ -n "${gcc}" ]; then \
libpsl-dev \
libre2-dev \
librtmp-dev \
libsimdjson-dev \
libsnappy-dev \
libsqlite3-dev \
libssh-dev \
Expand Down
1 change: 1 addition & 0 deletions ci/docker/fedora-42-cpp.dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ RUN dnf update -y && \
python-pip \
rapidjson-devel \
re2-devel \
simdjson-devel \
snappy-devel \
thrift-devel \
utf8proc-devel \
Expand Down
1 change: 1 addition & 0 deletions ci/docker/ubuntu-22.04-cpp.dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ RUN apt-get update -y -q && \
libradospp-dev \
libre2-dev \
librtmp-dev \
libsimdjson-dev \
libsnappy-dev \
libsqlite3-dev \
libssh-dev \
Expand Down
1 change: 1 addition & 0 deletions ci/docker/ubuntu-24.04-cpp.dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ RUN apt-get update -y -q && \
libradospp-dev \
libre2-dev \
librtmp-dev \
libsimdjson-dev \
libsnappy-dev \
libsqlite3-dev \
libssh-dev \
Expand Down
1 change: 1 addition & 0 deletions ci/scripts/PKGBUILD
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ build() {
-DARROW_CXXFLAGS="${CPPFLAGS}" \
-DAWSSDK_SOURCE=BUNDLED \
-DCMAKE_BUILD_TYPE="release" \
-DCMAKE_DISABLE_PRECOMPILE_HEADERS=ON \
-DCMAKE_INSTALL_PREFIX=${MINGW_PREFIX} \
-DCMAKE_UNITY_BUILD=OFF \
-DCMAKE_VERBOSE_MAKEFILE=ON \
Expand Down
1 change: 1 addition & 0 deletions ci/scripts/cpp_build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,7 @@ else
-DProtobuf_SOURCE="${Protobuf_SOURCE:-}" \
-DRapidJSON_SOURCE="${RapidJSON_SOURCE:-}" \
-Dre2_SOURCE="${re2_SOURCE:-}" \
-Dsimdjson_SOURCE="${simdjson_SOURCE:-}" \
-DSnappy_SOURCE="${Snappy_SOURCE:-}" \
-DThrift_SOURCE="${Thrift_SOURCE:-}" \
-Dutf8proc_SOURCE="${utf8proc_SOURCE:-}" \
Expand Down
1 change: 1 addition & 0 deletions ci/scripts/msys2_setup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ case "${target}" in
packages+=("${MINGW_PACKAGE_PREFIX}-protobuf")
packages+=("${MINGW_PACKAGE_PREFIX}-rapidjson")
packages+=("${MINGW_PACKAGE_PREFIX}-re2")
packages+=("${MINGW_PACKAGE_PREFIX}-simdjson")
packages+=("${MINGW_PACKAGE_PREFIX}-snappy")
packages+=("${MINGW_PACKAGE_PREFIX}-sqlite3")
packages+=("${MINGW_PACKAGE_PREFIX}-thrift")
Expand Down
1 change: 1 addition & 0 deletions ci/scripts/r_revdepcheck.sh
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ apt install -y \
libprotoc-dev \
libradospp-dev \
libre2-dev \
libsimdjson-dev \
libsnappy-dev \
libssl-dev \
libthrift-dev \
Expand Down
3 changes: 2 additions & 1 deletion ci/vcpkg/vcpkg.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,8 @@
"json": {
"description": "JSON support",
"dependencies": [
"rapidjson"
"rapidjson",
"simdjson"
]
},
"gandiva": {
Expand Down
33 changes: 33 additions & 0 deletions cpp/cmake_modules/FindsimdjsonAlt.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you 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.

if(SimdjsonAlt_FOUND)
return()
endif()

# Manual search failed, try CMake config mode
set(find_package_args)
if(SimdjsonAlt_FIND_VERSION)
list(APPEND find_package_args ${SimdjsonAlt_FIND_VERSION})
endif()
if(SimdjsonAlt_FIND_QUIETLY)
list(APPEND find_package_args QUIET)
endif()
find_package(simdjson ${find_package_args} CONFIG)
if(simdjson_FOUND)
set(SimdjsonAlt_FOUND TRUE)
endif()
51 changes: 51 additions & 0 deletions cpp/cmake_modules/ThirdpartyToolchain.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ set(ARROW_THIRDPARTY_DEPENDENCIES
re2
Protobuf
RapidJSON
simdjson
Snappy
Substrait
Thrift
Expand Down Expand Up @@ -209,6 +210,8 @@ macro(build_dependency DEPENDENCY_NAME)
build_protobuf()
elseif("${DEPENDENCY_NAME}" STREQUAL "RapidJSON")
build_rapidjson()
elseif("${DEPENDENCY_NAME}" STREQUAL "simdjson")
build_simdjson()
elseif("${DEPENDENCY_NAME}" STREQUAL "re2")
build_re2()
elseif("${DEPENDENCY_NAME}" STREQUAL "Snappy")
Expand Down Expand Up @@ -411,6 +414,10 @@ if(ARROW_JSON OR ARROW_FLIGHT_SQL_ODBC)
set(ARROW_WITH_RAPIDJSON ON)
endif()

if(ARROW_JSON)
set(ARROW_WITH_SIMDJSON ON)
endif()

if(ARROW_ORC OR ARROW_FLIGHT)
set(ARROW_WITH_PROTOBUF ON)
endif()
Expand Down Expand Up @@ -763,6 +770,14 @@ else()
"${THIRDPARTY_MIRROR_URL}/rapidjson-${ARROW_RAPIDJSON_BUILD_VERSION}.tar.gz")
endif()

if(DEFINED ENV{ARROW_SIMDJSON_URL})
set(SIMDJSON_SOURCE_URL "$ENV{ARROW_SIMDJSON_URL}")
else()
set_urls(SIMDJSON_SOURCE_URL
"https://github.com/simdjson/simdjson/archive/refs/tags/${ARROW_SIMDJSON_BUILD_VERSION}.tar.gz"
"${THIRDPARTY_MIRROR_URL}/simdjson-${ARROW_SIMDJSON_BUILD_VERSION}.tar.gz")
endif()

if(DEFINED ENV{ARROW_S2N_TLS_URL})
set(S2N_TLS_SOURCE_URL "$ENV{ARROW_S2N_TLS_URL}")
else()
Expand Down Expand Up @@ -2782,6 +2797,42 @@ if(ARROW_BUILD_BENCHMARKS)
FALSE)
endif()

function(build_simdjson)
list(APPEND CMAKE_MESSAGE_INDENT "simdjson: ")
message(STATUS "Building simdjson from source")

fetchcontent_declare(simdjson
${FC_DECLARE_COMMON_OPTIONS} OVERRIDE_FIND_PACKAGE
URL ${SIMDJSON_SOURCE_URL}
URL_HASH "SHA256=${ARROW_SIMDJSON_BUILD_SHA256_CHECKSUM}")

prepare_fetchcontent()

fetchcontent_makeavailable(simdjson)

set(SIMDJSON_VENDORED
TRUE
PARENT_SCOPE)

list(APPEND ARROW_BUNDLED_STATIC_LIBS simdjson::simdjson)
set(ARROW_BUNDLED_STATIC_LIBS
"${ARROW_BUNDLED_STATIC_LIBS}"
PARENT_SCOPE)

list(POP_BACK CMAKE_MESSAGE_INDENT)
endfunction()

if(ARROW_WITH_SIMDJSON)
set(ARROW_SIMDJSON_REQUIRED_VERSION "3.0.0")
resolve_dependency(simdjson
HAVE_ALT
TRUE
REQUIRED_VERSION
${ARROW_SIMDJSON_REQUIRED_VERSION}
IS_RUNTIME_DEPENDENCY
FALSE)
endif()

function(build_rapidjson)
list(APPEND CMAKE_MESSAGE_INDENT "RapidJSON: ")
message(STATUS "Building from source")
Expand Down
2 changes: 1 addition & 1 deletion cpp/src/arrow/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1044,7 +1044,7 @@ if(ARROW_JSON)
json/parser.cc
json/reader.cc)
foreach(ARROW_JSON_TARGET ${ARROW_JSON_TARGETS})
target_link_libraries(${ARROW_JSON_TARGET} PRIVATE RapidJSON)
target_link_libraries(${ARROW_JSON_TARGET} PRIVATE RapidJSON simdjson::simdjson)
endforeach()
else()
set(ARROW_JSON_TARGET_SHARED)
Expand Down
1 change: 1 addition & 0 deletions cpp/src/arrow/json/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ add_arrow_test(test
from_string_test.cc
parser_test.cc
reader_test.cc
object_parser_test.cc
PREFIX
"arrow-json"
EXTRA_LINK_LIBS
Expand Down
121 changes: 89 additions & 32 deletions cpp/src/arrow/json/object_parser.cc
Original file line number Diff line number Diff line change
Expand Up @@ -16,72 +16,129 @@
// under the License.

#include "arrow/json/object_parser.h"
#include "arrow/json/rapidjson_defs.h" // IWYU pragma: keep

#include <rapidjson/document.h>
#include <simdjson.h>

namespace arrow {
namespace json {
namespace internal {

namespace rj = arrow::rapidjson;

class ObjectParser::Impl {
public:
Status Parse(std::string_view json) {
document_.Parse(reinterpret_cast<const rj::Document::Ch*>(json.data()),
static_cast<size_t>(json.size()));
// Copy into padded buffer
padded_json_ = simdjson::padded_string(json);

if (document_.HasParseError()) {
return Status::Invalid("Json parse error (offset ", document_.GetErrorOffset(),
"): ", document_.GetParseError());
}
if (!document_.IsObject()) {
return Status::TypeError("Not a json object");
// Store parsed document
document_ = parser_.iterate(padded_json_);

// Validate root is an object
auto object = document_.get_object();
if (object.error()) {
if (object.error() == simdjson::INCORRECT_TYPE) {
return Status::TypeError("Not a JSON object");
}
return Status::Invalid("JSON parse error: ",
simdjson::error_message(object.error()));
}

return Status::OK();
}

Result<std::string> GetString(const char* key) const {
if (!document_.HasMember(key)) {
Result<std::string> GetString(const char* key) {
document_.rewind();

auto object = document_.get_object();

auto field = object.find_field(key);

if (field.error() == simdjson::NO_SUCH_FIELD) {
return Status::KeyError("Key '", key, "' does not exist");
}
if (!document_[key].IsString()) {
if (field.error()) {
return Status::Invalid("Error accessing key '", key,
"': ", simdjson::error_message(field.error()));
}

auto str_result = field.get_string();
if (str_result.error() == simdjson::INCORRECT_TYPE) {
return Status::TypeError("Key '", key, "' is not a string");
}
return document_[key].GetString();
if (str_result.error()) {
return Status::Invalid("Error getting string for key '", key,
"': ", simdjson::error_message(str_result.error()));
}

return std::string(str_result.value());
}

Result<std::unordered_map<std::string, std::string>> GetStringMap() const {
Result<std::unordered_map<std::string, std::string>> GetStringMap() {
std::unordered_map<std::string, std::string> map;
for (auto itr = document_.MemberBegin(); itr != document_.MemberEnd(); ++itr) {
const auto& json_name = itr->name;
const auto& json_value = itr->value;
if (!json_name.IsString()) {
return Status::TypeError("Key is not a string");

document_.rewind();

auto object = document_.get_object();

for (auto field : object) {
auto key_result = field.unescaped_key();

auto key = key_result.value();

if (key_result.error()) {
return Status::Invalid("Error getting value for key '", std::string(key),
"': ", simdjson::error_message(key_result.error()));
}

auto value = field.value();

auto str_result = value.get_string();

if (str_result.error() == simdjson::INCORRECT_TYPE) {
return Status::TypeError("Key '", std::string(key),
"' does not have a string value");
}
std::string name = json_name.GetString();
if (!json_value.IsString()) {
return Status::TypeError("Key '", name, "' does not have a string value");
if (str_result.error()) {
return Status::Invalid("Error getting value for key '", std::string(key),
"': (code=", static_cast<int>(str_result.error()), ")");
}
std::string value = json_value.GetString();
map.insert({std::move(name), std::move(value)});

map.emplace(std::string(key), std::string(str_result.value()));
}

return map;
}

Result<bool> GetBool(const char* key) const {
if (!document_.HasMember(key)) {
Result<bool> GetBool(const char* key) {
document_.rewind();

auto object = document_.get_object();

auto field = object.find_field(key);

if (field.error() == simdjson::NO_SUCH_FIELD) {
return Status::KeyError("Key '", key, "' does not exist");
}
if (!document_[key].IsBool()) {
if (field.error()) {
return Status::Invalid("Error accessing key '", key,
"': ", simdjson::error_message(field.error()));
}

auto bool_result = field.get_bool();
if (bool_result.error() == simdjson::INCORRECT_TYPE) {
return Status::TypeError("Key '", key, "' is not a boolean");
}
return document_[key].GetBool();
if (bool_result.error()) {
return Status::Invalid("Error getting bool for key '", key,
"': ", simdjson::error_message(bool_result.error()));
}

return bool_result.value();
}

private:
rj::Document document_;
simdjson::ondemand::parser parser_;
simdjson::padded_string padded_json_;
simdjson::ondemand::document document_;
};

ObjectParser::ObjectParser() : impl_(new ObjectParser::Impl()) {}
Expand Down
Loading
Loading