Make field lookup order-insensitive.

This commit is contained in:
John Keiser
2020-12-19 10:16:22 -08:00
parent 85001c55fb
commit b8426584fc
6 changed files with 125 additions and 24 deletions
@@ -156,6 +156,14 @@ simdjson_really_inline error_code json_iterator::report_error(error_code _error,
return error;
}
simdjson_really_inline const uint32_t *json_iterator::checkpoint() const noexcept {
return token.checkpoint();
}
simdjson_really_inline void json_iterator::restore_checkpoint(const uint32_t *target_checkpoint) noexcept {
token.restore_checkpoint(target_checkpoint);
}
simdjson_really_inline error_code json_iterator::optional_error(error_code _error, const char *message) noexcept {
SIMDJSON_ASSUME(_error == INCORRECT_TYPE || _error == NO_SUCH_FIELD);
logger::log_error(*this, message);
@@ -163,6 +163,9 @@ public:
template<int N> simdjson_warn_unused simdjson_really_inline bool peek_to_buffer(uint8_t (&tmpbuf)[N]) noexcept;
template<int N> simdjson_warn_unused simdjson_really_inline bool advance_to_buffer(uint8_t (&tmpbuf)[N]) noexcept;
simdjson_really_inline const uint32_t *checkpoint() const noexcept;
simdjson_really_inline void restore_checkpoint(const uint32_t *target_checkpoint) noexcept;
protected:
simdjson_really_inline json_iterator(ondemand::parser *parser) noexcept;
@@ -43,6 +43,10 @@ simdjson_really_inline const uint32_t *token_iterator::checkpoint() const noexce
return index;
}
simdjson_really_inline void token_iterator::restore_checkpoint(const uint32_t *target_checkpoint) noexcept {
index = target_checkpoint;
}
} // namespace ondemand
} // namespace SIMDJSON_IMPLEMENTATION
} // namespace simdjson
@@ -54,6 +54,11 @@ public:
*/
simdjson_really_inline const uint32_t *checkpoint() const noexcept;
/**
* Reset to a previously saved index.
*/
simdjson_really_inline void restore_checkpoint(const uint32_t *target_checkpoint) noexcept;
// NOTE: we don't support a full C++ iterator interface, because we expect people to make
// different calls to advance the iterator based on *their own* state.
@@ -51,37 +51,124 @@ simdjson_warn_unused simdjson_really_inline simdjson_result<bool> value_iterator
}
}
/**
* Find the field with the given key. May be used in place of ++.
*/
simdjson_warn_unused simdjson_really_inline simdjson_result<bool> value_iterator::find_field_raw(const std::string_view key) noexcept {
if (!is_open()) { return false; }
// Unless this is the first field, we need to advance past the , and check for }
error_code error;
bool has_value;
//
// Initially, the object can be in one of a few different places:
//
// 1. The start of the object, at the first field:
//
// ```
// { "a": [ 1, 2 ], "b": [ 3, 4 ] }
// ^ (depth 2, index 1)
// ```
//
if (at_first_field()) {
// If we're at the beginning of the object, we definitely have a field
has_value = true;
// 2. When a previous search did not yield a value or the object is empty:
//
// ```
// { "a": [ 1, 2 ], "b": [ 3, 4 ] }
// ^ (depth 0)
// { }
// ^ (depth 0, index 2)
// ```
//
} else if (!is_open()) {
has_value = false;
// 3. When a previous search found a field or an iterator yielded a value:
//
// ```
// // When a field was not fully consumed (or not even touched at all)
// { "a": [ 1, 2 ], "b": [ 3, 4 ] }
// ^ (depth 2)
// // When a field was fully consumed
// { "a": [ 1, 2 ], "b": [ 3, 4 ] }
// ^ (depth 1)
// // When the last field was fully consumed
// { "a": [ 1, 2 ], "b": [ 3, 4 ] }
// ^ (depth 1)
// ```
//
} else {
// Finish the previous value and see if , or } is next
if ((error = skip_child() )) { abandon(); return error; }
if ((error = has_next_field().get(has_value) )) { abandon(); return error; }
}
// After initial processing, we will be in one of two states:
//
// ```
// // At the beginning of a field
// { "a": [ 1, 2 ], "b": [ 3, 4 ] }
// ^ (depth 1)
// { "a": [ 1, 2 ], "b": [ 3, 4 ] }
// ^ (depth 1)
// // At the end of the object
// { "a": [ 1, 2 ], "b": [ 3, 4 ] }
// ^ (depth 0)
// ```
//
// 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.
const uint32_t *search_start = _json_iter->checkpoint();
// Next, we find a match starting from the current position.
while (has_value) {
// Get the key
SIMDJSON_ASSUME( _json_iter->_depth == _depth + 1 ); // We must be at the start of a field
// Get the key and colon, stopping at the value.
raw_json_string actual_key;
if ((error = field_key().get(actual_key) )) { abandon(); return error; };
if ((error = field_value() )) { abandon(); return error; }
// Check if it matches
// If it matches, stop and return
if (actual_key == key) {
logger::log_event(*this, "match", key, -2);
return true;
}
// No match: skip the value and see if , or } is next
logger::log_event(*this, "no match", key, -2);
SIMDJSON_TRY( skip_child() ); // Skip the value entirely
SIMDJSON_TRY( skip_child() );
if ((error = has_next_field().get(has_value) )) { abandon(); return error; }
}
// If we reach the end without finding a match, search the rest of the fields starting at the
// beginning of the object.
// (We have already run through the object before, so we've already validated its structure. We
// don't check errors in this bit.)
_json_iter->restore_checkpoint(_start_index + 1);
_json_iter->descend_to(_depth);
has_value = started_object();
while (_json_iter->checkpoint() < search_start) {
SIMDJSON_ASSUME(has_value); // we should reach search_start before ever reaching the end of the object
SIMDJSON_ASSUME( _json_iter->_depth == _depth + 1 ); // We must be at the start of a field
// Get the key and colon, stopping at the value.
raw_json_string actual_key;
error = field_key().get(actual_key); SIMDJSON_ASSUME(!error);
error = field_value(); SIMDJSON_ASSUME(!error);
// If it matches, stop and return
if (actual_key == key) {
logger::log_event(*this, "match", key, -2);
return true;
}
// No match: skip the value and see if , or } is next
logger::log_event(*this, "no match", key, -2);
SIMDJSON_TRY( skip_child() );
error = has_next_field().get(has_value); SIMDJSON_ASSUME(!error);
}
// If the loop ended, we're out of fields to look at.
return false;
}
+9 -15
View File
@@ -1166,7 +1166,7 @@ namespace dom_api_tests {
ASSERT_EQUAL( object["b"].get_uint64().first, 2 );
ASSERT_EQUAL( object["c/d"].get_uint64().first, 3 );
ASSERT_ERROR( object["a"], NO_SUCH_FIELD );
ASSERT_EQUAL( object["a"].get_uint64().first, 1 );
ASSERT_ERROR( object["d"], NO_SUCH_FIELD );
return true;
}));
@@ -1178,7 +1178,7 @@ namespace dom_api_tests {
ASSERT_EQUAL( object["b"].get_uint64().first, 2 );
ASSERT_EQUAL( object["c/d"].get_uint64().first, 3 );
ASSERT_ERROR( object["a"], NO_SUCH_FIELD );
ASSERT_EQUAL( object["a"].get_uint64().first, 1 );
ASSERT_ERROR( object["d"], NO_SUCH_FIELD );
return true;
}));
@@ -1189,7 +1189,7 @@ namespace dom_api_tests {
ASSERT_EQUAL( doc["b"].get_uint64().first, 2 );
ASSERT_EQUAL( doc["c/d"].get_uint64().first, 3 );
ASSERT_ERROR( doc["a"], NO_SUCH_FIELD );
ASSERT_EQUAL( doc["a"].get_uint64().first, 1 );
ASSERT_ERROR( doc["d"], NO_SUCH_FIELD );
return true;
}));
@@ -1198,7 +1198,7 @@ namespace dom_api_tests {
ASSERT_EQUAL( doc_result["b"].get_uint64().first, 2 );
ASSERT_EQUAL( doc_result["c/d"].get_uint64().first, 3 );
ASSERT_ERROR( doc_result["a"], NO_SUCH_FIELD );
ASSERT_EQUAL( doc_result["a"].get_uint64().first, 1 );
ASSERT_ERROR( doc_result["d"], NO_SUCH_FIELD );
return true;
}));
@@ -1437,19 +1437,13 @@ namespace ordering_tests {
double z{0};
for (ondemand::object point_object : doc["coordinates"]) {
z += double(point_object["z"]);
try {
x += double(point_object["x"]);
return false;
} catch(simdjson_error&) {}
try {
y += double(point_object["y"]);
return false;
} catch(simdjson_error&) {}
x += double(point_object["x"]);
y += double(point_object["y"]);
}
return (x == 0) && (y == 0) && (z == 3.3);
return (x == 1.1) && (y == 2.2) && (z == 3.3);
}
bool robust_order() {
bool foreach_lookup() {
TEST_START();
ondemand::parser parser{};
auto doc = parser.iterate(json);
@@ -1472,7 +1466,7 @@ namespace ordering_tests {
#if SIMDJSON_EXCEPTIONS
in_order() &&
out_of_order() &&
robust_order() &&
foreach_lookup() &&
#endif
true;
}