Compare commits

...

4 Commits

Author SHA1 Message Date
Daniel Lemire 076f41ae4b Patch release candidate. 2021-05-15 15:45:02 -04:00
Daniel Lemire 6a37fc1871 Disabling perf testing on version 0.9 2021-05-14 09:22:48 -04:00
Daniel Lemire c6c29c2827 Better definition for fallthrough. 2021-03-31 14:38:52 -04:00
Daniel Lemire 941e903f28 Prerelease commit. 2021-03-31 13:48:43 -04:00
9 changed files with 353 additions and 44 deletions
+2 -2
View File
@@ -9,8 +9,8 @@ project(simdjson
set(PROJECT_VERSION_MAJOR 0) set(PROJECT_VERSION_MAJOR 0)
set(PROJECT_VERSION_MINOR 9) set(PROJECT_VERSION_MINOR 9)
set(PROJECT_VERSION_PATCH 1) set(PROJECT_VERSION_PATCH 4)
set(SIMDJSON_SEMANTIC_VERSION "0.9.1" CACHE STRING "simdjson semantic version") set(SIMDJSON_SEMANTIC_VERSION "0.9.4" CACHE STRING "simdjson semantic version")
set(SIMDJSON_LIB_VERSION "8.0.0" CACHE STRING "simdjson library version") set(SIMDJSON_LIB_VERSION "8.0.0" CACHE STRING "simdjson library version")
set(SIMDJSON_LIB_SOVERSION "8" CACHE STRING "simdjson library soversion") set(SIMDJSON_LIB_SOVERSION "8" CACHE STRING "simdjson library soversion")
set(SIMDJSON_GITHUB_REPOSITORY https://github.com/simdjson/simdjson) set(SIMDJSON_GITHUB_REPOSITORY https://github.com/simdjson/simdjson)
+1 -1
View File
@@ -38,7 +38,7 @@ PROJECT_NAME = simdjson
# could be handy for archiving the generated documentation or if some version # could be handy for archiving the generated documentation or if some version
# control system is used. # control system is used.
PROJECT_NUMBER = "0.9.1" PROJECT_NUMBER = "0.9.4"
# Using the PROJECT_BRIEF tag one can provide an optional one line description # Using the PROJECT_BRIEF tag one can provide an optional one line description
# for a project that appears at the top of each page and should give viewer a # for a project that appears at the top of each page and should give viewer a
+2 -1
View File
@@ -37,4 +37,5 @@ if (TARGET benchmark::benchmark)
endif() endif()
endif() endif()
include(checkperf.cmake) # deliberately disabling.
# include(checkperf.cmake)
+19
View File
@@ -251,4 +251,23 @@ namespace std {
#endif #endif
#endif #endif
#if SIMDJSON_CPLUSPLUS17
// if we have C++, then fallthrough is a default attribute
# define simdjson_fallthrough [[fallthrough]]
// check if we have __attribute__ support
#elif defined(__has_attribute)
// check if we have the __fallthrough__ attribute
#if __has_attribute(__fallthrough__)
// we are good to go:
# define simdjson_fallthrough __attribute__((__fallthrough__))
#endif
#endif
// on some systems, we simply do not have support for fallthrough, so use a default:
#ifndef simdjson_fallthrough
# define simdjson_fallthrough do {} while (0) /* fallthrough */
#endif
#endif // SIMDJSON_COMMON_DEFS_H #endif // SIMDJSON_COMMON_DEFS_H
@@ -139,21 +139,35 @@ simdjson_warn_unused simdjson_really_inline simdjson_result<bool> value_iterator
return false; return false;
} }
simdjson_warn_unused simdjson_really_inline simdjson_result<bool> value_iterator::find_field_unordered_raw(const std::string_view key) noexcept { simdjson_warn_unused simdjson_really_inline simdjson_result<bool> value_iterator::find_field_unordered_raw(const std::string_view key) noexcept {
/**
* When find_field_unordered_raw is called, we can either be pointing at the
* first key, pointing outside (at the closing brace) or if a key was matched
* we can be either pointing right afterthe ':' right before the value (that we need skip),
* or we may have consumed the value and we might be at a comma or at the
* final brace (ready for a call to has_next_field()).
*/
error_code error; error_code error;
bool has_value; bool has_value;
//
// First, we scan from that point to the end.
// If we don't find a match, we may loop back around, and scan from the beginning to that point.
token_position search_start = _json_iter->position();
// We want to know whether we need to go back to the beginning.
bool at_first = at_first_field();
///////////////
// Initially, the object can be in one of a few different places: // Initially, the object can be in one of a few different places:
// //
// 1. The start of the object, at the first field: // 1. At the first key:
// //
// ``` // ```
// { "a": [ 1, 2 ], "b": [ 3, 4 ] } // { "a": [ 1, 2 ], "b": [ 3, 4 ] }
// ^ (depth 2, index 1) // ^ (depth 2, index 1)
// ``` // ```
// //
if (at_first_field()) { if (at_first) {
// If we're at the beginning of the object, we definitely have a field
has_value = true; has_value = true;
// 2. When a previous search did not yield a value or the object is empty: // 2. When a previous search did not yield a value or the object is empty:
@@ -166,14 +180,16 @@ simdjson_warn_unused simdjson_really_inline simdjson_result<bool> value_iterator
// ``` // ```
// //
} else if (!is_open()) { } else if (!is_open()) {
#ifdef SIMDJSON_DEVELOPMENT_CHECKS #ifdef SIMDJSON_DEVELOPMENT_CHECKS
// If we're past the end of the object, we're being iterated out of order. // If we're past the end of the object, we're being iterated out of order.
// Note: this isn't perfect detection. It's possible the user is inside some other object; if so, // Note: this isn't perfect detection. It's possible the user is inside some other object; if so,
// this object iterator will blithely scan that object for fields. // this object iterator will blithely scan that object for fields.
if (_json_iter->depth() < depth() - 1) { return OUT_OF_ORDER_ITERATION; } if (_json_iter->depth() < depth() - 1) { return OUT_OF_ORDER_ITERATION; }
#endif #endif
has_value = false; _json_iter->reenter_child(_start_position + 1, _depth);
at_first = true;
has_value = started_object();
// 3. When a previous search found a field or an iterator yielded a value: // 3. When a previous search found a field or an iterator yielded a value:
// //
// ``` // ```
@@ -189,8 +205,13 @@ simdjson_warn_unused simdjson_really_inline simdjson_result<bool> value_iterator
// ``` // ```
// //
} else { } else {
// Finish the previous value and see if , or } is next // If someone queried a key but they did access the value, then we are left pointing
// at the ':' and we need to move forward through the value... If the value was
// processed then skip_child() does not move the iterator (but may adjust the depth).
if ((error = skip_child() )) { abandon(); return error; } if ((error = skip_child() )) { abandon(); return error; }
// The has_next_field() advances the pointer and check that either ',' or '}' is found.
// It returns true if ',' is found, false otherwise. If anything other than ',' or '}' is found,
// then we are in error and we abort.
if ((error = has_next_field().get(has_value) )) { abandon(); return error; } if ((error = has_next_field().get(has_value) )) { abandon(); return error; }
#ifdef SIMDJSON_DEVELOPMENT_CHECKS #ifdef SIMDJSON_DEVELOPMENT_CHECKS
if (_json_iter->start_position(_depth) != _start_position) { return OUT_OF_ORDER_ITERATION; } if (_json_iter->start_position(_depth) != _start_position) { return OUT_OF_ORDER_ITERATION; }
@@ -211,10 +232,6 @@ simdjson_warn_unused simdjson_really_inline simdjson_result<bool> value_iterator
// ``` // ```
// //
// First, we scan from that point to the end.
// If we don't find a match, we loop back around, and scan from the beginning to that point.
token_position search_start = _json_iter->position();
// Next, we find a match starting from the current position. // Next, we find a match starting from the current position.
while (has_value) { while (has_value) {
SIMDJSON_ASSUME( _json_iter->_depth == _depth ); // We must be at the start of a field SIMDJSON_ASSUME( _json_iter->_depth == _depth ); // We must be at the start of a field
@@ -222,8 +239,11 @@ simdjson_warn_unused simdjson_really_inline simdjson_result<bool> value_iterator
// Get the key and colon, stopping at the value. // Get the key and colon, stopping at the value.
raw_json_string actual_key; raw_json_string actual_key;
// size_t max_key_length = _json_iter->peek_length() - 2; // -2 for the two quotes // size_t max_key_length = _json_iter->peek_length() - 2; // -2 for the two quotes
// field_key() advances the pointer and checks that '"' is found (corresponding to a key).
// The depth is left unchanged by field_key().
if ((error = field_key().get(actual_key) )) { abandon(); return error; }; if ((error = field_key().get(actual_key) )) { abandon(); return error; };
// field_value() will advance and check that we find a ':' separating the
// key and the value. It will also increment the depth by one.
if ((error = field_value() )) { abandon(); return error; } if ((error = field_value() )) { abandon(); return error; }
// If it matches, stop and return // If it matches, stop and return
@@ -236,31 +256,44 @@ simdjson_warn_unused simdjson_really_inline simdjson_result<bool> value_iterator
// input). // input).
if (actual_key.unsafe_is_equal(key)) { if (actual_key.unsafe_is_equal(key)) {
logger::log_event(*this, "match", key, -2); logger::log_event(*this, "match", key, -2);
// If we return here, then we return while pointing at the ':' that we just checked.
return true; return true;
} }
// No match: skip the value and see if , or } is next // No match: skip the value and see if , or } is next
logger::log_event(*this, "no match", key, -2); logger::log_event(*this, "no match", key, -2);
// The call to skip_child is meant to skip over the value corresponding to the key.
// After skip_child(), we are right before the next comma (',') or the final brace ('}').
SIMDJSON_TRY( skip_child() ); SIMDJSON_TRY( skip_child() );
// The has_next_field() advances the pointer and check that either ',' or '}' is found.
// It returns true if ',' is found, false otherwise. If anything other than ',' or '}' is found,
// then we are in error and we abort.
if ((error = has_next_field().get(has_value) )) { abandon(); return error; } if ((error = has_next_field().get(has_value) )) { abandon(); return error; }
} }
// Performance note: it maybe wasteful to rewind to the beginning when there might be
// no other query following. Indeed, it would require reskipping the whole object.
// Instead, you can just stay where you are. If there is a new query, there is always time
// to rewind.
if(at_first) { return false; }
// If we reach the end without finding a match, search the rest of the fields starting at the // If we reach the end without finding a match, search the rest of the fields starting at the
// beginning of the object. // beginning of the object.
// (We have already run through the object before, so we've already validated its structure. We // (We have already run through the object before, so we've already validated its structure. We
// don't check errors in this bit.) // don't check errors in this bit.)
_json_iter->reenter_child(_start_position + 1, _depth); _json_iter->reenter_child(_start_position + 1, _depth);
has_value = started_object(); has_value = started_object();
while (_json_iter->position() < search_start) { while (true) {
SIMDJSON_ASSUME(has_value); // we should reach search_start before ever reaching the end of the object SIMDJSON_ASSUME(has_value); // we should reach search_start before ever reaching the end of the object
SIMDJSON_ASSUME( _json_iter->_depth == _depth ); // We must be at the start of a field SIMDJSON_ASSUME( _json_iter->_depth == _depth ); // We must be at the start of a field
// Get the key and colon, stopping at the value. // Get the key and colon, stopping at the value.
raw_json_string actual_key; raw_json_string actual_key;
// size_t max_key_length = _json_iter->peek_length() - 2; // -2 for the two quotes // size_t max_key_length = _json_iter->peek_length() - 2; // -2 for the two quotes
// field_key() advances the pointer and checks that '"' is found (corresponding to a key).
// The depth is left unchanged by field_key().
error = field_key().get(actual_key); SIMDJSON_ASSUME(!error); error = field_key().get(actual_key); SIMDJSON_ASSUME(!error);
// field_value() will advance and check that we find a ':' separating the
// key and the value. It will also increment the depth by one.
error = field_value(); SIMDJSON_ASSUME(!error); error = field_value(); SIMDJSON_ASSUME(!error);
// If it matches, stop and return // If it matches, stop and return
@@ -273,16 +306,28 @@ simdjson_warn_unused simdjson_really_inline simdjson_result<bool> value_iterator
// input). // input).
if (actual_key.unsafe_is_equal(key)) { if (actual_key.unsafe_is_equal(key)) {
logger::log_event(*this, "match", key, -2); logger::log_event(*this, "match", key, -2);
// If we return here, then we return while pointing at the ':' that we just checked.
return true; return true;
} }
// No match: skip the value and see if , or } is next // No match: skip the value and see if , or } is next
logger::log_event(*this, "no match", key, -2); logger::log_event(*this, "no match", key, -2);
// The call to skip_child is meant to skip over the value corresponding to the key.
// After skip_child(), we are right before the next comma (',') or the final brace ('}').
SIMDJSON_TRY( skip_child() ); SIMDJSON_TRY( skip_child() );
// If we reached the end of the key-value pair we started from, then we know
// that the key is not there so we return false. We are either right before
// the next comma or the final brace.
if(_json_iter->position() == search_start) { return false; }
// The has_next_field() advances the pointer and check that either ',' or '}' is found.
// It returns true if ',' is found, false otherwise. If anything other than ',' or '}' is found,
// then we are in error and we abort.
error = has_next_field().get(has_value); SIMDJSON_ASSUME(!error); error = has_next_field().get(has_value); SIMDJSON_ASSUME(!error);
// If we make the mistake of exiting here, then we could be left pointing at a key
// in the middle of an object. That's not an allowable state.
} }
// If the loop ended, we're out of fields to look at. The program should
// If the loop ended, we're out of fields to look at. // never reach this point.
return false; return false;
} }
+2 -2
View File
@@ -4,7 +4,7 @@
#define SIMDJSON_SIMDJSON_VERSION_H #define SIMDJSON_SIMDJSON_VERSION_H
/** The version of simdjson being used (major.minor.revision) */ /** The version of simdjson being used (major.minor.revision) */
#define SIMDJSON_VERSION 0.9.1 #define SIMDJSON_VERSION 0.9.4
namespace simdjson { namespace simdjson {
enum { enum {
@@ -19,7 +19,7 @@ enum {
/** /**
* The revision (major.minor.REVISION) of simdjson being used. * The revision (major.minor.REVISION) of simdjson being used.
*/ */
SIMDJSON_VERSION_REVISION = 1 SIMDJSON_VERSION_REVISION = 4
}; };
} // namespace simdjson } // namespace simdjson
+1 -1
View File
@@ -1,4 +1,4 @@
/* auto-generated on 2021-03-18 11:30:40 -0400. Do not edit! */ /* auto-generated on 2021-05-14 09:22:48 -0400. Do not edit! */
/* begin file src/simdjson.cpp */ /* begin file src/simdjson.cpp */
#include "simdjson.h" #include "simdjson.h"
+84 -20
View File
@@ -1,4 +1,4 @@
/* auto-generated on 2021-03-18 11:30:40 -0400. Do not edit! */ /* auto-generated on 2021-05-14 09:22:48 -0400. Do not edit! */
/* begin file include/simdjson.h */ /* begin file include/simdjson.h */
#ifndef SIMDJSON_H #ifndef SIMDJSON_H
#define SIMDJSON_H #define SIMDJSON_H
@@ -2045,6 +2045,25 @@ namespace std {
#endif #endif
#endif #endif
#if SIMDJSON_CPLUSPLUS17
// if we have C++, then fallthrough is a default attribute
# define simdjson_fallthrough [[fallthrough]]
// check if we have __attribute__ support
#elif defined(__has_attribute)
// check if we have the __fallthrough__ attribute
#if __has_attribute(__fallthrough__)
// we are good to go:
# define simdjson_fallthrough __attribute__((__fallthrough__))
#endif
#endif
// on some systems, we simply do not have support for fallthrough, so use a default:
#ifndef simdjson_fallthrough
# define simdjson_fallthrough do {} while (0) /* fallthrough */
#endif
#endif // SIMDJSON_COMMON_DEFS_H #endif // SIMDJSON_COMMON_DEFS_H
/* end file include/simdjson/common_defs.h */ /* end file include/simdjson/common_defs.h */
@@ -2059,7 +2078,7 @@ SIMDJSON_DISABLE_UNDESIRED_WARNINGS
#define SIMDJSON_SIMDJSON_VERSION_H #define SIMDJSON_SIMDJSON_VERSION_H
/** The version of simdjson being used (major.minor.revision) */ /** The version of simdjson being used (major.minor.revision) */
#define SIMDJSON_VERSION 0.9.1 #define SIMDJSON_VERSION 0.9.4
namespace simdjson { namespace simdjson {
enum { enum {
@@ -2074,7 +2093,7 @@ enum {
/** /**
* The revision (major.minor.REVISION) of simdjson being used. * The revision (major.minor.REVISION) of simdjson being used.
*/ */
SIMDJSON_VERSION_REVISION = 1 SIMDJSON_VERSION_REVISION = 4
}; };
} // namespace simdjson } // namespace simdjson
@@ -22088,21 +22107,35 @@ simdjson_warn_unused simdjson_really_inline simdjson_result<bool> value_iterator
return false; return false;
} }
simdjson_warn_unused simdjson_really_inline simdjson_result<bool> value_iterator::find_field_unordered_raw(const std::string_view key) noexcept { simdjson_warn_unused simdjson_really_inline simdjson_result<bool> value_iterator::find_field_unordered_raw(const std::string_view key) noexcept {
/**
* When find_field_unordered_raw is called, we can either be pointing at the
* first key, pointing outside (at the closing brace) or if a key was matched
* we can be either pointing right afterthe ':' right before the value (that we need skip),
* or we may have consumed the value and we might be at a comma or at the
* final brace (ready for a call to has_next_field()).
*/
error_code error; error_code error;
bool has_value; bool has_value;
//
// First, we scan from that point to the end.
// If we don't find a match, we may loop back around, and scan from the beginning to that point.
token_position search_start = _json_iter->position();
// We want to know whether we need to go back to the beginning.
bool at_first = at_first_field();
///////////////
// Initially, the object can be in one of a few different places: // Initially, the object can be in one of a few different places:
// //
// 1. The start of the object, at the first field: // 1. At the first key:
// //
// ``` // ```
// { "a": [ 1, 2 ], "b": [ 3, 4 ] } // { "a": [ 1, 2 ], "b": [ 3, 4 ] }
// ^ (depth 2, index 1) // ^ (depth 2, index 1)
// ``` // ```
// //
if (at_first_field()) { if (at_first) {
// If we're at the beginning of the object, we definitely have a field
has_value = true; has_value = true;
// 2. When a previous search did not yield a value or the object is empty: // 2. When a previous search did not yield a value or the object is empty:
@@ -22115,14 +22148,16 @@ simdjson_warn_unused simdjson_really_inline simdjson_result<bool> value_iterator
// ``` // ```
// //
} else if (!is_open()) { } else if (!is_open()) {
#ifdef SIMDJSON_DEVELOPMENT_CHECKS #ifdef SIMDJSON_DEVELOPMENT_CHECKS
// If we're past the end of the object, we're being iterated out of order. // If we're past the end of the object, we're being iterated out of order.
// Note: this isn't perfect detection. It's possible the user is inside some other object; if so, // Note: this isn't perfect detection. It's possible the user is inside some other object; if so,
// this object iterator will blithely scan that object for fields. // this object iterator will blithely scan that object for fields.
if (_json_iter->depth() < depth() - 1) { return OUT_OF_ORDER_ITERATION; } if (_json_iter->depth() < depth() - 1) { return OUT_OF_ORDER_ITERATION; }
#endif #endif
has_value = false; _json_iter->reenter_child(_start_position + 1, _depth);
at_first = true;
has_value = started_object();
// 3. When a previous search found a field or an iterator yielded a value: // 3. When a previous search found a field or an iterator yielded a value:
// //
// ``` // ```
@@ -22138,8 +22173,13 @@ simdjson_warn_unused simdjson_really_inline simdjson_result<bool> value_iterator
// ``` // ```
// //
} else { } else {
// Finish the previous value and see if , or } is next // If someone queried a key but they did access the value, then we are left pointing
// at the ':' and we need to move forward through the value... If the value was
// processed then skip_child() does not move the iterator (but may adjust the depth).
if ((error = skip_child() )) { abandon(); return error; } if ((error = skip_child() )) { abandon(); return error; }
// The has_next_field() advances the pointer and check that either ',' or '}' is found.
// It returns true if ',' is found, false otherwise. If anything other than ',' or '}' is found,
// then we are in error and we abort.
if ((error = has_next_field().get(has_value) )) { abandon(); return error; } if ((error = has_next_field().get(has_value) )) { abandon(); return error; }
#ifdef SIMDJSON_DEVELOPMENT_CHECKS #ifdef SIMDJSON_DEVELOPMENT_CHECKS
if (_json_iter->start_position(_depth) != _start_position) { return OUT_OF_ORDER_ITERATION; } if (_json_iter->start_position(_depth) != _start_position) { return OUT_OF_ORDER_ITERATION; }
@@ -22160,10 +22200,6 @@ simdjson_warn_unused simdjson_really_inline simdjson_result<bool> value_iterator
// ``` // ```
// //
// First, we scan from that point to the end.
// If we don't find a match, we loop back around, and scan from the beginning to that point.
token_position search_start = _json_iter->position();
// Next, we find a match starting from the current position. // Next, we find a match starting from the current position.
while (has_value) { while (has_value) {
SIMDJSON_ASSUME( _json_iter->_depth == _depth ); // We must be at the start of a field SIMDJSON_ASSUME( _json_iter->_depth == _depth ); // We must be at the start of a field
@@ -22171,8 +22207,11 @@ simdjson_warn_unused simdjson_really_inline simdjson_result<bool> value_iterator
// Get the key and colon, stopping at the value. // Get the key and colon, stopping at the value.
raw_json_string actual_key; raw_json_string actual_key;
// size_t max_key_length = _json_iter->peek_length() - 2; // -2 for the two quotes // size_t max_key_length = _json_iter->peek_length() - 2; // -2 for the two quotes
// field_key() advances the pointer and checks that '"' is found (corresponding to a key).
// The depth is left unchanged by field_key().
if ((error = field_key().get(actual_key) )) { abandon(); return error; }; if ((error = field_key().get(actual_key) )) { abandon(); return error; };
// field_value() will advance and check that we find a ':' separating the
// key and the value. It will also increment the depth by one.
if ((error = field_value() )) { abandon(); return error; } if ((error = field_value() )) { abandon(); return error; }
// If it matches, stop and return // If it matches, stop and return
@@ -22185,31 +22224,44 @@ simdjson_warn_unused simdjson_really_inline simdjson_result<bool> value_iterator
// input). // input).
if (actual_key.unsafe_is_equal(key)) { if (actual_key.unsafe_is_equal(key)) {
logger::log_event(*this, "match", key, -2); logger::log_event(*this, "match", key, -2);
// If we return here, then we return while pointing at the ':' that we just checked.
return true; return true;
} }
// No match: skip the value and see if , or } is next // No match: skip the value and see if , or } is next
logger::log_event(*this, "no match", key, -2); logger::log_event(*this, "no match", key, -2);
// The call to skip_child is meant to skip over the value corresponding to the key.
// After skip_child(), we are right before the next comma (',') or the final brace ('}').
SIMDJSON_TRY( skip_child() ); SIMDJSON_TRY( skip_child() );
// The has_next_field() advances the pointer and check that either ',' or '}' is found.
// It returns true if ',' is found, false otherwise. If anything other than ',' or '}' is found,
// then we are in error and we abort.
if ((error = has_next_field().get(has_value) )) { abandon(); return error; } if ((error = has_next_field().get(has_value) )) { abandon(); return error; }
} }
// Performance note: it maybe wasteful to rewind to the beginning when there might be
// no other query following. Indeed, it would require reskipping the whole object.
// Instead, you can just stay where you are. If there is a new query, there is always time
// to rewind.
if(at_first) { return false; }
// If we reach the end without finding a match, search the rest of the fields starting at the // If we reach the end without finding a match, search the rest of the fields starting at the
// beginning of the object. // beginning of the object.
// (We have already run through the object before, so we've already validated its structure. We // (We have already run through the object before, so we've already validated its structure. We
// don't check errors in this bit.) // don't check errors in this bit.)
_json_iter->reenter_child(_start_position + 1, _depth); _json_iter->reenter_child(_start_position + 1, _depth);
has_value = started_object(); has_value = started_object();
while (_json_iter->position() < search_start) { while (true) {
SIMDJSON_ASSUME(has_value); // we should reach search_start before ever reaching the end of the object SIMDJSON_ASSUME(has_value); // we should reach search_start before ever reaching the end of the object
SIMDJSON_ASSUME( _json_iter->_depth == _depth ); // We must be at the start of a field SIMDJSON_ASSUME( _json_iter->_depth == _depth ); // We must be at the start of a field
// Get the key and colon, stopping at the value. // Get the key and colon, stopping at the value.
raw_json_string actual_key; raw_json_string actual_key;
// size_t max_key_length = _json_iter->peek_length() - 2; // -2 for the two quotes // size_t max_key_length = _json_iter->peek_length() - 2; // -2 for the two quotes
// field_key() advances the pointer and checks that '"' is found (corresponding to a key).
// The depth is left unchanged by field_key().
error = field_key().get(actual_key); SIMDJSON_ASSUME(!error); error = field_key().get(actual_key); SIMDJSON_ASSUME(!error);
// field_value() will advance and check that we find a ':' separating the
// key and the value. It will also increment the depth by one.
error = field_value(); SIMDJSON_ASSUME(!error); error = field_value(); SIMDJSON_ASSUME(!error);
// If it matches, stop and return // If it matches, stop and return
@@ -22222,16 +22274,28 @@ simdjson_warn_unused simdjson_really_inline simdjson_result<bool> value_iterator
// input). // input).
if (actual_key.unsafe_is_equal(key)) { if (actual_key.unsafe_is_equal(key)) {
logger::log_event(*this, "match", key, -2); logger::log_event(*this, "match", key, -2);
// If we return here, then we return while pointing at the ':' that we just checked.
return true; return true;
} }
// No match: skip the value and see if , or } is next // No match: skip the value and see if , or } is next
logger::log_event(*this, "no match", key, -2); logger::log_event(*this, "no match", key, -2);
// The call to skip_child is meant to skip over the value corresponding to the key.
// After skip_child(), we are right before the next comma (',') or the final brace ('}').
SIMDJSON_TRY( skip_child() ); SIMDJSON_TRY( skip_child() );
// If we reached the end of the key-value pair we started from, then we know
// that the key is not there so we return false. We are either right before
// the next comma or the final brace.
if(_json_iter->position() == search_start) { return false; }
// The has_next_field() advances the pointer and check that either ',' or '}' is found.
// It returns true if ',' is found, false otherwise. If anything other than ',' or '}' is found,
// then we are in error and we abort.
error = has_next_field().get(has_value); SIMDJSON_ASSUME(!error); error = has_next_field().get(has_value); SIMDJSON_ASSUME(!error);
// If we make the mistake of exiting here, then we could be left pointing at a key
// in the middle of an object. That's not an allowable state.
} }
// If the loop ended, we're out of fields to look at. The program should
// If the loop ended, we're out of fields to look at. // never reach this point.
return false; return false;
} }
+180
View File
@@ -7,6 +7,178 @@ namespace object_tests {
using namespace std; using namespace std;
using simdjson::ondemand::json_type; using simdjson::ondemand::json_type;
// In this test, no non-trivial object in an array have a missing key
bool no_missing_keys() {
TEST_START();
simdjson::ondemand::parser parser;
simdjson::padded_string docdata = R"([{"a":"a"},{}])"_padded;
simdjson::ondemand::document doc;
auto error = parser.iterate(docdata).get(doc);
if(error != simdjson::SUCCESS) { return false; }
simdjson::ondemand::array a;
error = doc.get_array().get(a);
if(error != simdjson::SUCCESS) { return false; }
size_t counter{0};
for(auto elem : a) {
error = elem.find_field_unordered("a").error();
if(counter == 0) {
ASSERT_EQUAL( error, simdjson::SUCCESS);
} else {
ASSERT_EQUAL( error, simdjson::NO_SUCH_FIELD);
}
counter++;
}
return true;
}
bool missing_key_continue() {
TEST_START();
simdjson::ondemand::parser parser;
simdjson::padded_string docdata = R"({"a":0, "b":1, "c":2})"_padded;
simdjson::ondemand::document doc;
ASSERT_SUCCESS(parser.iterate(docdata).get(doc));
int64_t num;
ASSERT_SUCCESS(doc["a"].get(num));
ASSERT_EQUAL(num, 0);
ASSERT_SUCCESS(doc["b"].get(num));
ASSERT_EQUAL(num, 1);
ASSERT_SUCCESS(doc["c"].get(num));
ASSERT_EQUAL(num, 2);
// Start again, but omit a key
ASSERT_SUCCESS(parser.iterate(docdata).get(doc));
ASSERT_SUCCESS(doc["a"].get(num));
ASSERT_EQUAL(num, 0);
ASSERT_SUCCESS(doc["c"].get(num));
ASSERT_EQUAL(num, 2);
// Start again, but request a missing key
ASSERT_SUCCESS(parser.iterate(docdata).get(doc));
simdjson::ondemand::object obj;
ASSERT_SUCCESS(doc.get_object().get(obj));
ASSERT_SUCCESS(obj["a"].get(num));
ASSERT_EQUAL(num, 0);
assert_error(obj["d"].get(num), NO_SUCH_FIELD);
ASSERT_SUCCESS(obj["c"].get(num));
ASSERT_EQUAL(num, 2);
// Start again, but request a missing key first
ASSERT_SUCCESS(parser.iterate(docdata).get(doc));
ASSERT_SUCCESS(doc.get_object().get(obj));
assert_error(obj["d"].get(num), NO_SUCH_FIELD);
ASSERT_SUCCESS(obj["a"].get(num));
ASSERT_EQUAL(num, 0);
// Start again, but request a missing key twice
ASSERT_SUCCESS(parser.iterate(docdata).get(doc));
ASSERT_SUCCESS(doc.get_object().get(obj));
ASSERT_SUCCESS(obj["a"].get(num));
ASSERT_EQUAL(num, 0);
assert_error(obj["d"].get(num), NO_SUCH_FIELD);
assert_error(obj["z"].get(num), NO_SUCH_FIELD);
// Because we do a full circle, you can query the same
// key twice!!!
ASSERT_SUCCESS(obj["a"].get(num));
ASSERT_EQUAL(num, 0);
ASSERT_SUCCESS(obj["b"].get(num));
ASSERT_EQUAL(num, 1);
ASSERT_SUCCESS(obj["b"].get(num));
ASSERT_EQUAL(num, 1);
ASSERT_SUCCESS(obj["a"].get(num));
ASSERT_EQUAL(num, 0);
assert_error(obj["d"].get(num), NO_SUCH_FIELD);
ASSERT_SUCCESS(obj["a"].get(num));
ASSERT_EQUAL(num, 0);
TEST_SUCCEED();
}
bool missing_keys() {
TEST_START();
simdjson::ondemand::parser parser;
simdjson::padded_string docdata = R"([{"a":"a"},{}])"_padded;
simdjson::ondemand::document doc;
auto error = parser.iterate(docdata).get(doc);
if(error != simdjson::SUCCESS) { return false; }
simdjson::ondemand::array a;
error = doc.get_array().get(a);
if(error != simdjson::SUCCESS) { return false; }
for(auto elem : a) {
error = elem.find_field_unordered("keynotfound").error();
if(error != simdjson::NO_SUCH_FIELD) {
std::cout << error << std::endl;
return false;
}
}
return true;
}
#if SIMDJSON_EXCEPTIONS
// used in issue_1521
// difficult to use as a lambda because it is recursive.
void broken_descend(ondemand::object node) {
if(auto type = node.find_field_unordered("type"); type.error() == SUCCESS && type == "child") {
auto n = node.find_field_unordered("name");
if(n.error() == simdjson::SUCCESS) {
std::cout << std::string_view(n) << std::endl;
}
} else {
for (ondemand::object child_node : node["nodes"]) { broken_descend(child_node); }
}
}
bool broken_issue_1521() {
TEST_START();
ondemand::parser parser;
padded_string json = R"({"type":"root","nodes":[{"type":"child","nodes":[]},{"type":"child","name":"child-name","nodes":[]}]})"_padded;
ondemand::document file_tree = parser.iterate(json);
try {
broken_descend(file_tree);
} catch(simdjson::simdjson_error& e) {
std::cout << "The document is valid JSON: " << json << std::endl;
TEST_FAIL(e.error());
}
TEST_SUCCEED();
}
bool fixed_broken_issue_1521() {
TEST_START();
ondemand::parser parser;
// We omit the ',"nodes":[]'
padded_string json = R"({"type":"root","nodes":[{"type":"child"},{"type":"child","name":"child-name","nodes":[]}]})"_padded;
ondemand::document file_tree = parser.iterate(json);
try {
broken_descend(file_tree);
} catch(simdjson::simdjson_error& e) {
std::cout << "The document is valid JSON: " << json << std::endl;
TEST_FAIL(e.error());
}
TEST_SUCCEED();
}
// used in issue_1521
// difficult to use as a lambda because it is recursive.
void descend(ondemand::object node) {
auto n = node.find_field_unordered("name");
if(auto type = node.find_field_unordered("type"); type.error() == SUCCESS && type == "child") {
if(n.error() == simdjson::SUCCESS) {
std::cout << std::string_view(n) << std::endl;
}
} else {
for (ondemand::object child_node : node["nodes"]) { descend(child_node); }
}
}
bool issue_1521() {
TEST_START();
ondemand::parser parser;
padded_string json = R"({"type":"root","nodes":[{"type":"child","nodes":[]},{"type":"child","name":"child-name","nodes":[]}]})"_padded;
ondemand::document file_tree = parser.iterate(json);
try {
descend(file_tree);
} catch(simdjson::simdjson_error& e) {
std::cout << "The document is valid JSON: " << json << std::endl;
TEST_FAIL(e.error());
}
TEST_SUCCEED();
}
#endif
bool iterate_object() { bool iterate_object() {
TEST_START(); TEST_START();
auto json = R"({ "a": 1, "b": 2, "c": 3 })"_padded; auto json = R"({ "a": 1, "b": 2, "c": 3 })"_padded;
@@ -893,6 +1065,14 @@ namespace object_tests {
bool run() { bool run() {
return return
no_missing_keys() &&
missing_key_continue() &&
missing_keys() &&
#if SIMDJSON_EXCEPTIONS
fixed_broken_issue_1521() &&
issue_1521() &&
broken_issue_1521() &&
#endif
iterate_object() && iterate_object() &&
iterate_empty_object() && iterate_empty_object() &&
object_index() && object_index() &&