From 15cf22d497fe8b99c216d8e8901832f0e5ce3a75 Mon Sep 17 00:00:00 2001 From: Runji Wang Date: Mon, 30 Oct 2023 15:12:24 +0800 Subject: [PATCH 1/4] support `jsonb_extract_path` and `jsonb_extract_path_text` Signed-off-by: Runji Wang --- proto/expr.proto | 2 ++ src/common/src/array/list_array.rs | 18 +++++++++++++++ src/expr/impl/src/scalar/jsonb_access.rs | 9 +++++--- src/frontend/src/binder/expr/function.rs | 2 ++ src/frontend/src/expr/pure.rs | 2 ++ src/frontend/src/expr/type_inference/func.rs | 16 +++++++++++++ src/tests/regress/data/sql/jsonb.sql | 24 ++++++++++---------- 7 files changed, 58 insertions(+), 15 deletions(-) diff --git a/proto/expr.proto b/proto/expr.proto index fecefc12b4ee7..06bfecc66d4d0 100644 --- a/proto/expr.proto +++ b/proto/expr.proto @@ -236,6 +236,8 @@ message ExprNode { JSONB_EXISTS_ANY = 611; // jsonb ?& text[] JSONB_EXISTS_ALL = 612; + JSONB_EXTRACT_PATH = 616; + JSONB_EXTRACT_PATH_TEXT = 617; // Non-pure functions below (> 1000) // ------------------------ diff --git a/src/common/src/array/list_array.rs b/src/common/src/array/list_array.rs index 7eaaffff98534..d6a8aba766da4 100644 --- a/src/common/src/array/list_array.rs +++ b/src/common/src/array/list_array.rs @@ -538,6 +538,24 @@ impl<'a> ListRef<'a> { } } +impl Row for ListRef<'_> { + fn len(&self) -> usize { + self.len() + } + + fn datum_at(&self, index: usize) -> DatumRef<'_> { + self.get(index).unwrap() + } + + unsafe fn datum_at_unchecked(&self, index: usize) -> DatumRef<'_> { + self.get(index).unwrap() + } + + fn iter(&self) -> impl Iterator> { + self.clone().iter() + } +} + impl PartialEq for ListRef<'_> { fn eq(&self, other: &Self) -> bool { iter_elems_ref!(*self, lhs, { diff --git a/src/expr/impl/src/scalar/jsonb_access.rs b/src/expr/impl/src/scalar/jsonb_access.rs index 08e36bedf83cc..12d517ec31f4d 100644 --- a/src/expr/impl/src/scalar/jsonb_access.rs +++ b/src/expr/impl/src/scalar/jsonb_access.rs @@ -14,7 +14,8 @@ use std::fmt::Write; -use risingwave_common::types::{JsonbRef, ListRef}; +use risingwave_common::row::Row; +use risingwave_common::types::JsonbRef; use risingwave_expr::function; /// Extracts JSON object field with the given key. @@ -87,7 +88,8 @@ pub fn jsonb_array_element(v: JsonbRef<'_>, p: i32) -> Option> { /// NULL /// ``` #[function("jsonb_access_multi(jsonb, varchar[]) -> jsonb")] -pub fn jsonb_access_multi<'a>(v: JsonbRef<'a>, path: ListRef<'_>) -> Option> { +#[function("jsonb_extract_path(jsonb, ...) -> jsonb")] +pub fn jsonb_access_multi<'a>(v: JsonbRef<'a>, path: impl Row) -> Option> { let mut jsonb = v; for key in path.iter() { // return null if any element is null @@ -182,9 +184,10 @@ pub fn jsonb_array_element_str(v: JsonbRef<'_>, p: i32, writer: &mut impl Write) /// NULL /// ``` #[function("jsonb_access_multi_str(jsonb, varchar[]) -> varchar")] +#[function("jsonb_extract_path_text(jsonb, ...) -> varchar")] pub fn jsonb_access_multi_str( v: JsonbRef<'_>, - path: ListRef<'_>, + path: impl Row, writer: &mut impl Write, ) -> Option<()> { let jsonb = jsonb_access_multi(v, path)?; diff --git a/src/frontend/src/binder/expr/function.rs b/src/frontend/src/binder/expr/function.rs index 6dfab2c3bc283..39da7540795a5 100644 --- a/src/frontend/src/binder/expr/function.rs +++ b/src/frontend/src/binder/expr/function.rs @@ -875,6 +875,8 @@ impl Binder { ("jsonb_array_element", raw_call(ExprType::JsonbAccess)), ("jsonb_object_field_text", raw_call(ExprType::JsonbAccessStr)), ("jsonb_array_element_text", raw_call(ExprType::JsonbAccessStr)), + ("jsonb_extract_path", raw_call(ExprType::JsonbExtractPath)), + ("jsonb_extract_path_text", raw_call(ExprType::JsonbExtractPathText)), ("jsonb_typeof", raw_call(ExprType::JsonbTypeof)), ("jsonb_array_length", raw_call(ExprType::JsonbArrayLength)), ("jsonb_object", raw_call(ExprType::JsonbObject)), diff --git a/src/frontend/src/expr/pure.rs b/src/frontend/src/expr/pure.rs index 7a63c7f95ae99..ffba850eb7874 100644 --- a/src/frontend/src/expr/pure.rs +++ b/src/frontend/src/expr/pure.rs @@ -177,6 +177,8 @@ impl ExprVisitor for ImpureAnalyzer { | expr_node::Type::JsonbAccessStr | expr_node::Type::JsonbAccessMulti | expr_node::Type::JsonbAccessMultiStr + | expr_node::Type::JsonbExtractPath + | expr_node::Type::JsonbExtractPathText | expr_node::Type::JsonbTypeof | expr_node::Type::JsonbArrayLength | expr_node::Type::JsonbObject diff --git a/src/frontend/src/expr/type_inference/func.rs b/src/frontend/src/expr/type_inference/func.rs index 84e315dacae45..b3fe1e8dd4766 100644 --- a/src/frontend/src/expr/type_inference/func.rs +++ b/src/frontend/src/expr/type_inference/func.rs @@ -541,6 +541,22 @@ fn infer_type_for_special( ensure_arity!("greatest/least", 1 <= | inputs |); Ok(Some(align_types(inputs.iter_mut())?)) } + ExprType::JsonbExtractPath => { + ensure_arity!("jsonb_extract_path", 2 <= | inputs |); + inputs[0].cast_implicit_mut(DataType::Jsonb)?; + for input in inputs.iter_mut().skip(1) { + input.cast_explicit_mut(DataType::Varchar)?; + } + Ok(Some(DataType::Jsonb)) + } + ExprType::JsonbExtractPathText => { + ensure_arity!("jsonb_extract_path_text", 2 <= | inputs |); + inputs[0].cast_implicit_mut(DataType::Jsonb)?; + for input in inputs.iter_mut().skip(1) { + input.cast_explicit_mut(DataType::Varchar)?; + } + Ok(Some(DataType::Varchar)) + } _ => Ok(None), } } diff --git a/src/tests/regress/data/sql/jsonb.sql b/src/tests/regress/data/sql/jsonb.sql index 69bd9a928a0f5..bf44f328c8ebb 100644 --- a/src/tests/regress/data/sql/jsonb.sql +++ b/src/tests/regress/data/sql/jsonb.sql @@ -449,20 +449,20 @@ SELECT jsonb_typeof('"1.0"') AS string; -- extract_path, extract_path_as_text ---@ SELECT jsonb_extract_path('{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}','f4','f6'); ---@ SELECT jsonb_extract_path('{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}','f2'); ---@ SELECT jsonb_extract_path('{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}','f2',0::text); ---@ SELECT jsonb_extract_path('{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}','f2',1::text); ---@ SELECT jsonb_extract_path_text('{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}','f4','f6'); ---@ SELECT jsonb_extract_path_text('{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}','f2'); ---@ SELECT jsonb_extract_path_text('{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}','f2',0::text); ---@ SELECT jsonb_extract_path_text('{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}','f2',1::text); +SELECT jsonb_extract_path('{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}','f4','f6'); +SELECT jsonb_extract_path('{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}','f2'); +SELECT jsonb_extract_path('{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}','f2',0::text); +SELECT jsonb_extract_path('{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}','f2',1::text); +SELECT jsonb_extract_path_text('{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}','f4','f6'); +SELECT jsonb_extract_path_text('{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}','f2'); +SELECT jsonb_extract_path_text('{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}','f2',0::text); +SELECT jsonb_extract_path_text('{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}','f2',1::text); -- extract_path nulls ---@ SELECT jsonb_extract_path('{"f2":{"f3":1},"f4":{"f5":null,"f6":"stringy"}}','f4','f5') IS NULL AS expect_false; ---@ SELECT jsonb_extract_path_text('{"f2":{"f3":1},"f4":{"f5":null,"f6":"stringy"}}','f4','f5') IS NULL AS expect_true; ---@ SELECT jsonb_extract_path('{"f2":{"f3":1},"f4":[0,1,2,null]}','f4','3') IS NULL AS expect_false; ---@ SELECT jsonb_extract_path_text('{"f2":{"f3":1},"f4":[0,1,2,null]}','f4','3') IS NULL AS expect_true; +SELECT jsonb_extract_path('{"f2":{"f3":1},"f4":{"f5":null,"f6":"stringy"}}','f4','f5') IS NULL AS expect_false; +SELECT jsonb_extract_path_text('{"f2":{"f3":1},"f4":{"f5":null,"f6":"stringy"}}','f4','f5') IS NULL AS expect_true; +SELECT jsonb_extract_path('{"f2":{"f3":1},"f4":[0,1,2,null]}','f4','3') IS NULL AS expect_false; +SELECT jsonb_extract_path_text('{"f2":{"f3":1},"f4":[0,1,2,null]}','f4','3') IS NULL AS expect_true; -- extract_path operators SELECT '{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}'::jsonb#>array['f4','f6']; From 7bab36f2b4c7c06296b753cfbcf9e521bff8bf58 Mon Sep 17 00:00:00 2001 From: Runji Wang Date: Mon, 30 Oct 2023 15:58:12 +0800 Subject: [PATCH 2/4] fix clippy Signed-off-by: Runji Wang --- src/common/src/array/list_array.rs | 2 +- src/expr/impl/src/scalar/jsonb_access.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/common/src/array/list_array.rs b/src/common/src/array/list_array.rs index d6a8aba766da4..f3ecf2c5f68ed 100644 --- a/src/common/src/array/list_array.rs +++ b/src/common/src/array/list_array.rs @@ -552,7 +552,7 @@ impl Row for ListRef<'_> { } fn iter(&self) -> impl Iterator> { - self.clone().iter() + (*self).iter() } } diff --git a/src/expr/impl/src/scalar/jsonb_access.rs b/src/expr/impl/src/scalar/jsonb_access.rs index 12d517ec31f4d..ade563e178f6b 100644 --- a/src/expr/impl/src/scalar/jsonb_access.rs +++ b/src/expr/impl/src/scalar/jsonb_access.rs @@ -89,7 +89,7 @@ pub fn jsonb_array_element(v: JsonbRef<'_>, p: i32) -> Option> { /// ``` #[function("jsonb_access_multi(jsonb, varchar[]) -> jsonb")] #[function("jsonb_extract_path(jsonb, ...) -> jsonb")] -pub fn jsonb_access_multi<'a>(v: JsonbRef<'a>, path: impl Row) -> Option> { +pub fn jsonb_access_multi(v: JsonbRef<'_>, path: impl Row) -> Option> { let mut jsonb = v; for key in path.iter() { // return null if any element is null From 2bc0faa08a0e39b172871341b92cf1615ee66571 Mon Sep 17 00:00:00 2001 From: Runji Wang Date: Tue, 31 Oct 2023 16:54:27 +0800 Subject: [PATCH 3/4] merge backend functions and rewrite variadic on frontend Signed-off-by: Runji Wang --- proto/expr.proto | 6 ++-- src/expr/impl/src/scalar/jsonb_access.rs | 33 +++++++++++++------- src/frontend/src/binder/expr/binary_op.rs | 4 +-- src/frontend/src/binder/expr/function.rs | 32 +++++++++++++++++-- src/frontend/src/expr/pure.rs | 2 -- src/frontend/src/expr/type_inference/func.rs | 16 ---------- 6 files changed, 55 insertions(+), 38 deletions(-) diff --git a/proto/expr.proto b/proto/expr.proto index 1eb8a4ca190b5..01200ec975498 100644 --- a/proto/expr.proto +++ b/proto/expr.proto @@ -218,9 +218,9 @@ message ExprNode { // jsonb ->> int, jsonb ->> text that returns text JSONB_ACCESS_STR = 601; // jsonb #> text[] -> jsonb - JSONB_ACCESS_MULTI = 613; + JSONB_EXTRACT_PATH = 613; // jsonb #>> text[] -> text - JSONB_ACCESS_MULTI_STR = 614; + JSONB_EXTRACT_PATH_TEXT = 614; JSONB_TYPEOF = 602; JSONB_ARRAY_LENGTH = 603; IS_JSON = 604; @@ -244,8 +244,6 @@ message ExprNode { // // jsonb #- text[] -> jsonb JSONB_DELETE_PATH = 615; - JSONB_EXTRACT_PATH = 616; - JSONB_EXTRACT_PATH_TEXT = 617; // Non-pure functions below (> 1000) // ------------------------ diff --git a/src/expr/impl/src/scalar/jsonb_access.rs b/src/expr/impl/src/scalar/jsonb_access.rs index ade563e178f6b..ccfedb5518a7e 100644 --- a/src/expr/impl/src/scalar/jsonb_access.rs +++ b/src/expr/impl/src/scalar/jsonb_access.rs @@ -14,8 +14,7 @@ use std::fmt::Write; -use risingwave_common::row::Row; -use risingwave_common::types::JsonbRef; +use risingwave_common::types::{JsonbRef, ListRef}; use risingwave_expr::function; /// Extracts JSON object field with the given key. @@ -72,7 +71,8 @@ pub fn jsonb_array_element(v: JsonbRef<'_>, p: i32) -> Option> { /// Extracts JSON sub-object at the specified path, where path elements can be either field keys or array indexes. /// -/// `jsonb #> text[] → jsonb` +/// - `jsonb #> text[] → jsonb` +/// - `jsonb_extract_path ( from_json jsonb, VARIADIC path_elems text[] ) → jsonb` /// /// # Examples /// @@ -86,10 +86,14 @@ pub fn jsonb_array_element(v: JsonbRef<'_>, p: i32) -> Option> { /// select '{"a": {"b": ["foo","bar"]}}'::jsonb #> '{a,b,null}'::text[]; /// ---- /// NULL +/// +/// query T +/// select jsonb_extract_path('{"a": {"b": ["foo","bar"]}}', 'a', 'b', '1'); +/// ---- +/// "bar" /// ``` -#[function("jsonb_access_multi(jsonb, varchar[]) -> jsonb")] -#[function("jsonb_extract_path(jsonb, ...) -> jsonb")] -pub fn jsonb_access_multi(v: JsonbRef<'_>, path: impl Row) -> Option> { +#[function("jsonb_extract_path(jsonb, varchar[]) -> jsonb")] +pub fn jsonb_extract_path<'a>(v: JsonbRef<'a>, path: ListRef<'_>) -> Option> { let mut jsonb = v; for key in path.iter() { // return null if any element is null @@ -163,7 +167,8 @@ pub fn jsonb_array_element_str(v: JsonbRef<'_>, p: i32, writer: &mut impl Write) /// Extracts JSON sub-object at the specified path as text. /// -/// `jsonb #>> text[] → text` +/// - `jsonb #>> text[] → text` +/// - `jsonb_extract_path_text ( from_json jsonb, VARIADIC path_elems text[] ) → text` /// /// # Examples /// @@ -182,15 +187,19 @@ pub fn jsonb_array_element_str(v: JsonbRef<'_>, p: i32, writer: &mut impl Write) /// select '{"a": {"b": ["foo","bar"]}}'::jsonb #>> '{a,b,null}'::text[]; /// ---- /// NULL +/// +/// query T +/// select jsonb_extract_path_text('{"a": {"b": ["foo","bar"]}}', 'a', 'b', '1'); +/// ---- +/// bar /// ``` -#[function("jsonb_access_multi_str(jsonb, varchar[]) -> varchar")] -#[function("jsonb_extract_path_text(jsonb, ...) -> varchar")] -pub fn jsonb_access_multi_str( +#[function("jsonb_extract_path_text(jsonb, varchar[]) -> varchar")] +pub fn jsonb_extract_path_text( v: JsonbRef<'_>, - path: impl Row, + path: ListRef<'_>, writer: &mut impl Write, ) -> Option<()> { - let jsonb = jsonb_access_multi(v, path)?; + let jsonb = jsonb_extract_path(v, path)?; if jsonb.is_jsonb_null() { return None; } diff --git a/src/frontend/src/binder/expr/binary_op.rs b/src/frontend/src/binder/expr/binary_op.rs index 8718ea74e5b5d..9ad6ccdb7c825 100644 --- a/src/frontend/src/binder/expr/binary_op.rs +++ b/src/frontend/src/binder/expr/binary_op.rs @@ -92,8 +92,8 @@ impl Binder { BinaryOperator::Arrow => ExprType::JsonbAccess, BinaryOperator::LongArrow => ExprType::JsonbAccessStr, BinaryOperator::HashMinus => ExprType::JsonbDeletePath, - BinaryOperator::HashArrow => ExprType::JsonbAccessMulti, - BinaryOperator::HashLongArrow => ExprType::JsonbAccessMultiStr, + BinaryOperator::HashArrow => ExprType::JsonbExtractPath, + BinaryOperator::HashLongArrow => ExprType::JsonbExtractPathText, BinaryOperator::Prefix => ExprType::StartsWith, BinaryOperator::Contains => ExprType::JsonbContains, BinaryOperator::Contained => ExprType::JsonbContained, diff --git a/src/frontend/src/binder/expr/function.rs b/src/frontend/src/binder/expr/function.rs index b60b44480e7d1..fd289cdbb05a4 100644 --- a/src/frontend/src/binder/expr/function.rs +++ b/src/frontend/src/binder/expr/function.rs @@ -875,8 +875,36 @@ impl Binder { ("jsonb_array_element", raw_call(ExprType::JsonbAccess)), ("jsonb_object_field_text", raw_call(ExprType::JsonbAccessStr)), ("jsonb_array_element_text", raw_call(ExprType::JsonbAccessStr)), - ("jsonb_extract_path", raw_call(ExprType::JsonbExtractPath)), - ("jsonb_extract_path_text", raw_call(ExprType::JsonbExtractPathText)), + ("jsonb_extract_path", raw(|_binder, mut inputs| { + // rewrite: jsonb_extract_path(jsonb, s1, s2...) + // to: jsonb_extract_path(jsonb, array[s1, s2...]) + if inputs.len() < 2 { + return Err(ErrorCode::ExprError("unexpected arguments number".into()).into()); + } + inputs[0].cast_implicit_mut(DataType::Jsonb)?; + let mut variadic_inputs = inputs.split_off(1); + for input in &mut variadic_inputs { + input.cast_implicit_mut(DataType::Varchar)?; + } + let array = FunctionCall::new_unchecked(ExprType::Array, variadic_inputs, DataType::List(Box::new(DataType::Varchar))); + inputs.push(array.into()); + Ok(FunctionCall::new_unchecked(ExprType::JsonbExtractPath, inputs, DataType::Jsonb).into()) + })), + ("jsonb_extract_path_text", raw(|_binder, mut inputs| { + // rewrite: jsonb_extract_path_text(jsonb, s1, s2...) + // to: jsonb_extract_path_text(jsonb, array[s1, s2...]) + if inputs.len() < 2 { + return Err(ErrorCode::ExprError("unexpected arguments number".into()).into()); + } + inputs[0].cast_implicit_mut(DataType::Jsonb)?; + let mut variadic_inputs = inputs.split_off(1); + for input in &mut variadic_inputs { + input.cast_implicit_mut(DataType::Varchar)?; + } + let array = FunctionCall::new_unchecked(ExprType::Array, variadic_inputs, DataType::List(Box::new(DataType::Varchar))); + inputs.push(array.into()); + Ok(FunctionCall::new_unchecked(ExprType::JsonbExtractPathText, inputs, DataType::Varchar).into()) + })), ("jsonb_typeof", raw_call(ExprType::JsonbTypeof)), ("jsonb_array_length", raw_call(ExprType::JsonbArrayLength)), ("jsonb_object", raw_call(ExprType::JsonbObject)), diff --git a/src/frontend/src/expr/pure.rs b/src/frontend/src/expr/pure.rs index b6cb7079d8bfc..96b001ebda23c 100644 --- a/src/frontend/src/expr/pure.rs +++ b/src/frontend/src/expr/pure.rs @@ -176,8 +176,6 @@ impl ExprVisitor for ImpureAnalyzer { | expr_node::Type::JsonbCat | expr_node::Type::JsonbAccess | expr_node::Type::JsonbAccessStr - | expr_node::Type::JsonbAccessMulti - | expr_node::Type::JsonbAccessMultiStr | expr_node::Type::JsonbExtractPath | expr_node::Type::JsonbExtractPathText | expr_node::Type::JsonbTypeof diff --git a/src/frontend/src/expr/type_inference/func.rs b/src/frontend/src/expr/type_inference/func.rs index b3fe1e8dd4766..84e315dacae45 100644 --- a/src/frontend/src/expr/type_inference/func.rs +++ b/src/frontend/src/expr/type_inference/func.rs @@ -541,22 +541,6 @@ fn infer_type_for_special( ensure_arity!("greatest/least", 1 <= | inputs |); Ok(Some(align_types(inputs.iter_mut())?)) } - ExprType::JsonbExtractPath => { - ensure_arity!("jsonb_extract_path", 2 <= | inputs |); - inputs[0].cast_implicit_mut(DataType::Jsonb)?; - for input in inputs.iter_mut().skip(1) { - input.cast_explicit_mut(DataType::Varchar)?; - } - Ok(Some(DataType::Jsonb)) - } - ExprType::JsonbExtractPathText => { - ensure_arity!("jsonb_extract_path_text", 2 <= | inputs |); - inputs[0].cast_implicit_mut(DataType::Jsonb)?; - for input in inputs.iter_mut().skip(1) { - input.cast_explicit_mut(DataType::Varchar)?; - } - Ok(Some(DataType::Varchar)) - } _ => Ok(None), } } From b3b045dc03c6f472e89541f7b4f58e979c6cb96a Mon Sep 17 00:00:00 2001 From: Runji Wang Date: Tue, 31 Oct 2023 16:57:06 +0800 Subject: [PATCH 4/4] revert `impl Row for ListRef` Signed-off-by: Runji Wang --- src/common/src/array/list_array.rs | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/src/common/src/array/list_array.rs b/src/common/src/array/list_array.rs index f3ecf2c5f68ed..7eaaffff98534 100644 --- a/src/common/src/array/list_array.rs +++ b/src/common/src/array/list_array.rs @@ -538,24 +538,6 @@ impl<'a> ListRef<'a> { } } -impl Row for ListRef<'_> { - fn len(&self) -> usize { - self.len() - } - - fn datum_at(&self, index: usize) -> DatumRef<'_> { - self.get(index).unwrap() - } - - unsafe fn datum_at_unchecked(&self, index: usize) -> DatumRef<'_> { - self.get(index).unwrap() - } - - fn iter(&self) -> impl Iterator> { - (*self).iter() - } -} - impl PartialEq for ListRef<'_> { fn eq(&self, other: &Self) -> bool { iter_elems_ref!(*self, lhs, {