Skip to content

GH-50627: [C++] Migrate from_string.cc to simdjson - #50653

Open
taepper wants to merge 8 commits into
apache:mainfrom
taepper:GH-35460
Open

GH-50627: [C++] Migrate from_string.cc to simdjson#50653
taepper wants to merge 8 commits into
apache:mainfrom
taepper:GH-35460

Conversation

@taepper

@taepper taepper commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Rationale for this change

This is part of the work for #35460. Back in January I started working on this change-set, but after discussions came up regarding the use of the simdjson::dom vs simdjson::ondemand front-end the development went stale. I saw that @Reranko05 started the work on #35460 and already made great progress, well done!

I still wanted to put up this change-set as a possible view-point, but feel free to disregard @Reranko05 if you already started your own work.

What changes are included in this PR?

This changes from_string.cc to use simdjson instead of RapidJSON.

Are there any user-facing changes?

Yes, the API has changed to no longer allow non-utf8 compatible strings. simdjson does not support non-compliant inputs and does not intent to support it in the future [1] [2] [3]

All call-sites in tests only used a transformation to non-utf8 json as an intermediate result to easily go from string -> Array, so I changed these to use different helper methods instead.

Another difficult problem arises in the handling of NaN and Inf literals in inputs. simdjson follows the json standard, whereas RapidJSON implemented an extension where the following is a valid input:

{"x": Inf, "y": -Inf, "z": NaN, "n": Infinity, "m": -Infinity}

We need to decide whether to be breaking here or not. This PR now contains a non-breaking version of the change. This can be quite elegantly added by using the public ondemand api's function to retrieve the raw token string in the ConvertNumber function.

Alternatively, we can wait for the next simdjson major release, which will ship with support for a SIMDJSON_ENABLE_NAN_INF compilation flag as per this pull request. (this was only merged to simdjson main)

Are these changes tested?

Yes

@taepper
taepper requested review from pitrou and wgtmac as code owners July 27, 2026 08:48
@github-actions github-actions Bot added the awaiting review Awaiting review label Jul 27, 2026
@Reranko05

Copy link
Copy Markdown
Contributor

Thanks for working on this! This part of the migration (from_string.cc) was planned for a later PR in my migration series, so I hadn't reached it yet. I appreciate you picking it up :)

Comment thread cpp/src/arrow/json/from_string.cc Outdated
Comment thread cpp/src/arrow/json/from_string.cc Outdated
Comment thread cpp/src/arrow/json/from_string.cc Outdated
Comment thread cpp/src/arrow/json/from_string.cc Outdated
@github-actions github-actions Bot added awaiting committer review Awaiting committer review and removed awaiting review Awaiting review labels Jul 27, 2026
@taepper

taepper commented Jul 27, 2026

Copy link
Copy Markdown
Contributor Author

Putting

template <typename BuilderType>
std::shared_ptr<Array> BinaryArrayFromStrings(
    const std::shared_ptr<DataType>& type,
    const std::vector<std::optional<std::string>>& values) {
  std::unique_ptr<ArrayBuilder> builder;
  ARROW_CHECK_OK(MakeBuilder(default_memory_pool(), type, &builder));
  auto& concrete_builder = checked_cast<BuilderType&>(*builder);
  for (const auto& value : values) {
    if (value.has_value()) {
      ARROW_CHECK_OK(concrete_builder.Append(*value));
    } else {
      ARROW_CHECK_OK(concrete_builder.AppendNull());
    }
  }
  std::shared_ptr<Array> array;
  ARROW_CHECK_OK(concrete_builder.Finish(&array));
  return array;
}

into gdb.cc felt a little bit ugly, but as the file only contains tests and the helper method is quite short it might be fine? I did not see a good other place, as changing the library interface for a test-helper-method seems excessive, and including from .../testing/... in gdb.cc would break boundaries..

Comment thread cpp/src/arrow/json/from_string.cc Outdated
@pitrou

pitrou commented Jul 27, 2026

Copy link
Copy Markdown
Member

into gdb.cc felt a little bit ugly, but as the file only contains tests and the helper method is quite short it might be fine?

Yes, I think it's fine.

Comment thread cpp/src/arrow/json/from_string.cc Outdated
}

template <typename SimdjsonValueType>
Result<SimdjsonValueType> GetAs(sj::value& value) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment thread cpp/src/arrow/json/from_string.cc Outdated
return "string";
} else if constexpr (std::is_same_v<SimdjsonClass, bool>) {
return "boolean";
} else if constexpr (std::is_same_v<SimdjsonClass, std::monostate>) {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

std::monostate

We probably want to add a typedef for this (if we want to keep this at all?). Alternatively, we could check the null case also always by hand

Comment thread cpp/src/arrow/json/from_string.cc Outdated
Comment thread cpp/src/arrow/json/from_string.cc Outdated
};

// Use fold expression to process all handlers in order
(process_one(std::forward<Handlers>(handlers)) && ...);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that Status supports the & operator, which might allow simplifying this a bit.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did now use it, but am unsure about readability improvements. Is this what you meant?

It made the body a bit cleaner, but it is now more difficult to reason about the error message, but maybe that is just me ^^

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that's what I had in mind. I understand what you mean with "difficult to reason about the error message", but normally the first should be kept :)

@pitrou

pitrou commented Jul 28, 2026

Copy link
Copy Markdown
Member

Hmm, this CI error looks related: https://github.com/apache/arrow/actions/runs/30342146109/job/90219928526?pr=50653

(perhaps my suggestion re Status::operator& was wrong? sorry)

Comment thread cpp/src/arrow/json/from_string.cc Outdated
return "string";
} else if constexpr (std::is_same_v<SimdjsonClass, bool>) {
return "boolean";
} else if constexpr (std::is_same_v<SimdjsonClass, std::monostate>) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is std::monostate somehow imposed by simdjson? Otherwise can we use something more distinctive?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I replaced it with a custom empty class

Comment thread cpp/src/arrow/json/from_string.cc Outdated
Comment on lines +86 to +87
template <typename>
inline constexpr bool kAlwaysFalse = false;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't seem used, which errors out on some compilers (see CI :-)).

@taepper taepper Jul 28, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see. Was able to replace with static_assert(false,..) inline. Not sure why this produced an error for me before. Might had missed a constexpr in one of the elif branches and thought this indirection was necessary

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see in the CI run that the way I solved it is indeed not portable:

/Users/runner/work/arrow/arrow/cpp/src/arrow/json/from_string.cc:109:5: error: static assertion failed: unmapped simdjson value type
    static_assert(false, "unmapped simdjson value type");
    ^             ~~~~~

@taepper

taepper commented Jul 28, 2026

Copy link
Copy Markdown
Contributor Author

Hmm, this CI error looks related: https://github.com/apache/arrow/actions/runs/30342146109/job/90219928526?pr=50653

(perhaps my suggestion re Status::operator& was wrong? sorry)

Yes, it is related. Nono, the mistake was on my part. But yes, consuming more elements from the iterator after an error is UB in simdjson. Sloppy by me, sorry. I refactored it and found a much cleaner solution to only template the expected_size to the function. Then we do not need to fold and can just have a loop. (Also, we can use RETURN_NOT_OK)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants