From 8cbe568167504f827cf5d926d98198f681bf0f60 Mon Sep 17 00:00:00 2001 From: xiangjinwu <17769960+xiangjinwu@users.noreply.github.com> Date: Sun, 29 Oct 2023 23:51:32 +0800 Subject: [PATCH 1/9] fix(expr): include data type in parse error message (#13121) --- src/expr/impl/src/scalar/cast.rs | 14 +++++++++----- .../tests/testdata/output/range_scan.yaml | 6 +++--- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/expr/impl/src/scalar/cast.rs b/src/expr/impl/src/scalar/cast.rs index f22b643bb9a09..ae2ed50472b86 100644 --- a/src/expr/impl/src/scalar/cast.rs +++ b/src/expr/impl/src/scalar/cast.rs @@ -37,14 +37,14 @@ use risingwave_pb::expr::expr_node::PbType; #[function("cast(varchar) -> timestamp")] #[function("cast(varchar) -> interval")] #[function("cast(varchar) -> jsonb")] -pub fn str_parse(elem: &str) -> Result +pub fn str_parse(elem: &str, ctx: &Context) -> Result where T: FromStr, ::Err: std::fmt::Display, { - elem.trim() - .parse() - .map_err(|err: ::Err| ExprError::Parse(err.to_string().into())) + elem.trim().parse().map_err(|err: ::Err| { + ExprError::Parse(format!("{} {}", ctx.return_type, err).into()) + }) } // TODO: introduce `FromBinary` and support all types @@ -521,7 +521,11 @@ mod tests { async fn test_unary() { test_unary_bool::(|x| !x, PbType::Not).await; test_unary_date::(|x| try_cast(x).unwrap(), PbType::Cast).await; - test_str_to_int16::(|x| str_parse(x).unwrap()).await; + let ctx_str_to_int16 = Context { + arg_types: vec![DataType::Varchar], + return_type: DataType::Int16, + }; + test_str_to_int16::(|x| str_parse(x, &ctx_str_to_int16).unwrap()).await; } #[tokio::test] diff --git a/src/frontend/planner_test/tests/testdata/output/range_scan.yaml b/src/frontend/planner_test/tests/testdata/output/range_scan.yaml index d16b98f101d3b..f95075c8823a8 100644 --- a/src/frontend/planner_test/tests/testdata/output/range_scan.yaml +++ b/src/frontend/planner_test/tests/testdata/output/range_scan.yaml @@ -42,12 +42,12 @@ - create_table_and_mv sql: | SELECT * FROM orders_count_by_user WHERE user_id = 'a' - batch_error: 'Expr error: Parse error: invalid digit found in string' + batch_error: 'Expr error: Parse error: bigint invalid digit found in string' - before: - create_table_and_mv sql: | SELECT * FROM orders_count_by_user WHERE user_id > 'a' - batch_error: 'Expr error: Parse error: invalid digit found in string' + batch_error: 'Expr error: Parse error: bigint invalid digit found in string' - before: - create_table_and_mv sql: | @@ -129,7 +129,7 @@ - create_table_and_mv sql: | SELECT * FROM orders_count_by_user WHERE user_id in ('42', '43.0') - batch_error: 'Expr error: Parse error: invalid digit found in string' + batch_error: 'Expr error: Parse error: bigint invalid digit found in string' - before: - create_table_and_mv sql: | From b0166944d335f16d7ffd5294fea706a3db6ce2b0 Mon Sep 17 00:00:00 2001 From: Yingjun Wu Date: Sun, 29 Oct 2023 18:27:25 -0700 Subject: [PATCH 2/9] chore: Update README.md (#13120) Co-authored-by: hengm3467 <100685635+hengm3467@users.noreply.github.com> --- README.md | 60 ++++++++++++++++++++++++++++++------------------------- 1 file changed, 33 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index 29a7d7e51888a..091bccc0de9b4 100644 --- a/README.md +++ b/README.md @@ -54,52 +54,62 @@ -RisingWave is a distributed SQL streaming database that enables simple, efficient, and reliable processing of streaming data. +RisingWave is a distributed SQL streaming database that enables cost-efficient and reliable processing of streaming data. ![RisingWave](https://github.com/risingwavelabs/risingwave-docs/blob/0f7e1302b22493ba3c1c48e78810750ce9a5ff42/docs/images/archi_simple.png) -## How to install -**Ubuntu** -``` -wget https://github.com/risingwavelabs/risingwave/releases/download/v1.3.0/risingwave-v1.3.0-x86_64-unknown-linux.tar.gz -tar xvf risingwave-v1.3.0-x86_64-unknown-linux.tar.gz -./risingwave playground -``` +## Try it out in 5 minutes **Mac** ``` brew tap risingwavelabs/risingwave brew install risingwave risingwave playground ``` +**Ubuntu** +``` +wget https://github.com/risingwavelabs/risingwave/releases/download/v1.3.0/risingwave-v1.3.0-x86_64-unknown-linux.tar.gz +tar xvf risingwave-v1.3.0-x86_64-unknown-linux.tar.gz +./risingwave playground +``` Now connect to RisingWave using `psql`: ``` psql -h localhost -p 4566 -d dev -U root ``` - Learn more at [Quick Start](https://docs.risingwave.com/docs/current/get-started/). +## Production deployments +For **single-node Docker deployments**, please refer to [Docker Compose](https://docs.risingwave.com/docs/current/risingwave-trial/?method=docker-compose). + +For **Kubernetes deployments**, please refer to [Kubernetes with Helm](https://docs.risingwave.com/docs/current/risingwave-k8s-helm/) or [Kubernetes with Operator](https://docs.risingwave.com/docs/current/risingwave-kubernetes/). + +**RisingWave Cloud** the easiest way to run a fully-fledged RisingWave cluster. Try it out for free at: [cloud.risingwave.com](https://cloud.risingwave.com). + + ## Why RisingWave for stream processing? -RisingWave adaptly tackles some of the most challenging problems in stream processing. Compared to existing stream processing systems, RisingWave shines through with the following key features: -* **Easy to learn** +RisingWave adaptly addresses some of the most challenging problems in stream processing. Compared to existing stream processing systems like [Apache Flink](https://flink.apache.org/), [Apache Spark Streaming](https://spark.apache.org/docs/latest/streaming-programming-guide.html), and [KsqlDB](https://ksqldb.io/), RisingWave stands out in two primary dimensions: **Ease-of-use** and **efficiency**, thanks to its **[PostgreSQL](https://www.postgresql.org/)-style interaction experience** and **[Snowflake](https://snowflake.com/)-like architectural design** (i.e., compute-storage decoupling). +### Ease-of-use +* **Simple to learn** * RisingWave speaks PostgreSQL-style SQL, enabling users to dive into stream processing in much the same way as operating a PostgreSQL database. -* **Highly efficient in multi-stream joins** - * RisingWave has made significant optimizations for multiple stream join scenarios. Users can easily join 10-20 streams (or more) efficiently in a production environment. +* **Simple to verify correctness** + * RisingWave persists results in materialized views and allow users to break down complex stream computation programs into stacked materialized views, simplifying program development and result verification. +* **Simple to maintain and operate** + * RisingWave abstracts away unnecessary low-level details, allowing users to concentrate solely on SQL code-level issues. +* **Simple to integrate** + * With integrations to a diverse range of cloud systems and the PostgreSQL ecosystem, RisingWave boasts a rich and expansive ecosystem, making it straightforward to incorporate into existing infrastructures. + +### Efficiency * **High resource utilization** * Queries in RisingWave leverage shared computational resources, eliminating the need for users to manually allocate resources for each query. * **No compromise on large state management** * The decoupled compute-storage architecture of RisingWave ensures remote persistence of internal states, and users never need to worry about the size of internal states when handling complex queries. +* **Highly efficient in multi-stream joins** + * RisingWave has made significant optimizations for multiple stream join scenarios. Users can easily join 10-20 streams (or more) efficiently in a production environment. * **Transparent dynamic scaling** * RisingWave supports near-instantaneous dynamic scaling without any service interruptions. * **Instant failure recovery** * RisingWave's state management mechanism allows it to recover from failure in seconds, not minutes or hours. -* **Easy to verify correctness** - * RisingWave persists results in materialized views and allow users to break down complex stream computation programs into stacked materialized views, simplifying program development and result verification. * **Simplified data stack** * RisingWave's ability to store data and serve queries eliminates the need for separate maintenance of stream processors and databases. Users can effortlessly connect RisingWave to their preferred BI tools or through client libraries. -* **Simple to maintain and operate** - * RisingWave abstracts away unnecessary low-level details, allowing users to concentrate solely on SQL code-level issues. -* **Rich ecosystem** - * With integrations to a diverse range of cloud systems and the PostgreSQL ecosystem, RisingWave boasts a rich and expansive ecosystem. ## RisingWave's limitations RisingWave isn’t a panacea for all data engineering hurdles. It has its own set of limitations: @@ -111,14 +121,6 @@ RisingWave isn’t a panacea for all data engineering hurdles. It has its own se * RisingWave's row store design is tailored for optimal stream processing performance rather than interactive analytical workloads. Hence, it's not a suitable replacement for OLAP databases. Yet, a reliable integration with many OLAP databases exists, and a collaborative use of RisingWave and OLAP databases is a common practice among many users. -## RisingWave Cloud - -RisingWave Cloud is a fully-managed and scalable stream processing platform powered by the open-source RisingWave project. Try it out for free at: [cloud.risingwave.com](https://cloud.risingwave.com). - -## Notes on telemetry - -RisingWave collects anonymous usage statistics to better understand how the community is using RisingWave. The sole intention of this exercise is to help improve the product. Users may opt out easily at any time. Please refer to the [user documentation](https://docs.risingwave.com/docs/current/telemetry/) for more details. - ## In-production use cases Like other stream processing systems, the primary use cases of RisingWave include monitoring, alerting, real-time dashboard reporting, streaming ETL (Extract, Transform, Load), machine learning feature engineering, and more. It has already been adopted in fields such as financial trading, manufacturing, new media, logistics, gaming, and more. Check out [customer stories](https://www.risingwave.com/use-cases/). @@ -126,6 +128,10 @@ Like other stream processing systems, the primary use cases of RisingWave includ Looking for help, discussions, collaboration opportunities, or a casual afternoon chat with our fellow engineers and community members? Join our [Slack workspace](https://risingwave.com/slack)! +## Notes on telemetry + +RisingWave collects anonymous usage statistics to better understand how the community is using RisingWave. The sole intention of this exercise is to help improve the product. Users may opt out easily at any time. Please refer to the [user documentation](https://docs.risingwave.com/docs/current/telemetry/) for more details. + ## License RisingWave is distributed under the Apache License (Version 2.0). Please refer to [LICENSE](LICENSE) for more information. From 4b7d19453152de7d474c5b5cbc336f5a282f0034 Mon Sep 17 00:00:00 2001 From: Renjie Liu Date: Mon, 30 Oct 2023 10:53:51 +0800 Subject: [PATCH 3/9] fix: iceberg.enabled in pulsar connector should be optional (#13124) --- src/connector/src/source/pulsar/mod.rs | 4 ++-- src/connector/src/source/pulsar/source/reader.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/connector/src/source/pulsar/mod.rs b/src/connector/src/source/pulsar/mod.rs index c9db87fd3bb94..5a1d73963bd23 100644 --- a/src/connector/src/source/pulsar/mod.rs +++ b/src/connector/src/source/pulsar/mod.rs @@ -49,8 +49,8 @@ pub struct PulsarProperties { pub common: PulsarCommon, #[serde(rename = "iceberg.enabled")] - #[serde_as(as = "Option")] - pub iceberg_loader_enabled: bool, + #[serde_as(as = "DisplayFromStr")] + pub iceberg_loader_enabled: Option, #[serde(rename = "iceberg.bucket", default)] pub iceberg_bucket: Option, diff --git a/src/connector/src/source/pulsar/source/reader.rs b/src/connector/src/source/pulsar/source/reader.rs index 0568a8935932e..04fee26f42aa7 100644 --- a/src/connector/src/source/pulsar/source/reader.rs +++ b/src/connector/src/source/pulsar/source/reader.rs @@ -65,7 +65,7 @@ impl SplitReader for PulsarSplitReader { tracing::debug!("creating consumer for pulsar split topic {}", topic,); - if props.iceberg_loader_enabled + if props.iceberg_loader_enabled.unwrap_or(false) && matches!(split.start_offset, PulsarEnumeratorOffset::Earliest) && !topic.starts_with("non-persistent://") { From 8b47f9f2b59c00c8e9bf305ee5a60042236528ba Mon Sep 17 00:00:00 2001 From: congyi wang <58715567+wcy-fdu@users.noreply.github.com> Date: Mon, 30 Oct 2023 11:10:33 +0800 Subject: [PATCH 4/9] refactor(object store): bump OpenDAL to v0.41 (#13101) --- Cargo.lock | 24 ++- src/object_store/Cargo.toml | 2 +- src/object_store/src/object/mod.rs | 7 +- .../src/object/opendal_engine/gcs.rs | 8 - .../opendal_engine/opendal_object_store.rs | 150 ++++++------------ 5 files changed, 64 insertions(+), 127 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0b7d0573bf22a..498002620379d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5269,9 +5269,9 @@ checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" [[package]] name = "opendal" -version = "0.39.0" +version = "0.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ad95e460e5976ab1b74f398ab856c59f8417b3dd32202329e3491dcbe3a6b84" +checksum = "ddba7299bab261d3ae2f37617fb7f45b19ed872752bb4e22cf93a69d979366c5" dependencies = [ "anyhow", "async-compat", @@ -5290,6 +5290,7 @@ dependencies = [ "parking_lot 0.12.1", "percent-encoding", "pin-project", + "prometheus", "quick-xml 0.29.0", "reqsign", "reqwest", @@ -5302,9 +5303,9 @@ dependencies = [ [[package]] name = "opendal" -version = "0.40.0" +version = "0.41.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddba7299bab261d3ae2f37617fb7f45b19ed872752bb4e22cf93a69d979366c5" +checksum = "e31b48f0af6de5b3b344c1acc1e06c4581dca3e13cd5ba05269927fc2abf953a" dependencies = [ "anyhow", "async-compat", @@ -5323,8 +5324,7 @@ dependencies = [ "parking_lot 0.12.1", "percent-encoding", "pin-project", - "prometheus", - "quick-xml 0.29.0", + "quick-xml 0.30.0", "reqsign", "reqwest", "serde", @@ -6564,6 +6564,16 @@ dependencies = [ "serde", ] +[[package]] +name = "quick-xml" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eff6510e86862b57b210fd8cbe8ed3f0d7d600b9c2863cd4549a2e033c66e956" +dependencies = [ + "memchr", + "serde", +] + [[package]] name = "quote" version = "1.0.33" @@ -7841,7 +7851,7 @@ dependencies = [ "itertools 0.11.0", "madsim-aws-sdk-s3", "madsim-tokio", - "opendal 0.39.0", + "opendal 0.41.0", "prometheus", "risingwave_common", "spin 0.9.8", diff --git a/src/object_store/Cargo.toml b/src/object_store/Cargo.toml index f117c272a9afc..2459bf83b5af6 100644 --- a/src/object_store/Cargo.toml +++ b/src/object_store/Cargo.toml @@ -24,7 +24,7 @@ futures = { version = "0.3", default-features = false, features = ["alloc"] } hyper = "0.14" hyper-tls = "0.5.0" itertools = "0.11" -opendal = "0.39" +opendal = "0.41" prometheus = { version = "0.13", features = ["process"] } risingwave_common = { workspace = true } spin = "0.9" diff --git a/src/object_store/src/object/mod.rs b/src/object_store/src/object/mod.rs index 96e58397dfa82..fe80794756246 100644 --- a/src/object_store/src/object/mod.rs +++ b/src/object_store/src/object/mod.rs @@ -242,12 +242,7 @@ impl ObjectStoreImpl { match self { ObjectStoreImpl::InMem(_) => true, ObjectStoreImpl::Opendal(store) => { - store - .inner - .op - .info() - .capability() - .write_without_content_length + store.inner.op.info().native_capability().write_can_multi } ObjectStoreImpl::S3(_) => true, } diff --git a/src/object_store/src/object/opendal_engine/gcs.rs b/src/object_store/src/object/opendal_engine/gcs.rs index bb6ef8eee0446..b497f85c263a5 100644 --- a/src/object_store/src/object/opendal_engine/gcs.rs +++ b/src/object_store/src/object/opendal_engine/gcs.rs @@ -19,12 +19,6 @@ use opendal::Operator; use super::{EngineType, OpendalObjectStore}; use crate::object::ObjectResult; -/// The fixed number of bytes that is buffered before they are uploaded as a part, will be used in -/// streaing upload. -/// -/// Reference: -const GCS_PART_SIZE: usize = 16 * 1024 * 1024; - impl OpendalObjectStore { /// create opendal gcs engine. pub fn new_gcs_engine(bucket: String, root: String) -> ObjectResult { @@ -35,8 +29,6 @@ impl OpendalObjectStore { builder.root(&root); - builder.write_fixed_size(GCS_PART_SIZE); - // if credential env is set, use it. Otherwise, ADC will be used. let cred = std::env::var("GOOGLE_APPLICATION_CREDENTIALS"); if let Ok(cred) = cred { diff --git a/src/object_store/src/object/opendal_engine/opendal_object_store.rs b/src/object_store/src/object/opendal_engine/opendal_object_store.rs index b829dbd544abf..ff682946b0651 100644 --- a/src/object_store/src/object/opendal_engine/opendal_object_store.rs +++ b/src/object_store/src/object/opendal_engine/opendal_object_store.rs @@ -12,15 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::pin::Pin; -use std::task::{ready, Context, Poll}; - use bytes::Bytes; use fail::fail_point; -use futures::future::BoxFuture; -use futures::{FutureExt, Stream, StreamExt}; +use futures::{stream, StreamExt}; use opendal::services::Memory; -use opendal::{Entry, Error, Lister, Metakey, Operator, Writer}; +use opendal::{Metakey, Operator, Writer}; use risingwave_common::range::RangeBoundsExt; use tokio::io::AsyncRead; @@ -84,7 +80,10 @@ impl ObjectStore for OpendalObjectStore { let data = if range.is_full() { self.op.read(path).await? } else { - self.op.range_read(path, range.map(|v| *v as u64)).await? + self.op + .read_with(path) + .range(range.map(|v| *v as u64)) + .await? }; if let Some(len) = range.len() && len != data.len() { @@ -112,7 +111,12 @@ impl ObjectStore for OpendalObjectStore { ObjectError::internal("opendal streaming read error") )); let reader = match start_pos { - Some(start_position) => self.op.range_reader(path, start_position as u64..).await?, + Some(start_position) => { + self.op + .reader_with(path) + .range(start_position as u64..) + .await? + } None => self.op.reader(path).await?, }; @@ -149,8 +153,36 @@ impl ObjectStore for OpendalObjectStore { } async fn list(&self, prefix: &str) -> ObjectResult { - let lister = self.op.scan(prefix).await?; - Ok(Box::pin(OpenDalObjectIter::new(lister, self.op.clone()))) + let object_lister = self + .op + .lister_with(prefix) + .delimiter("") + .metakey(Metakey::ContentLength | Metakey::ContentType) + .await?; + + let stream = stream::unfold(object_lister, |mut object_lister| async move { + match object_lister.next().await { + Some(Ok(object)) => { + let key = object.path().to_string(); + let om = object.metadata(); + let last_modified = match om.last_modified() { + Some(t) => t.timestamp() as f64, + None => 0_f64, + }; + let total_size = om.content_length() as usize; + let metadata = ObjectMetadata { + key, + last_modified, + total_size, + }; + Some((Ok(metadata), object_lister)) + } + Some(Err(err)) => Some((Err(err.into()), object_lister)), + None => None, + } + }); + + Ok(stream.boxed()) } fn store_media_type(&self) -> &'static str { @@ -172,12 +204,12 @@ pub struct OpenDalStreamingUploader { } impl OpenDalStreamingUploader { pub async fn new(op: Operator, path: String) -> ObjectResult { - let writer = op.writer(&path).await?; + let writer = op.writer_with(&path).buffer(OPENDAL_BUFFER_SIZE).await?; Ok(Self { writer }) } } -const OPENDAL_BUFFER_SIZE: u64 = 8 * 1024 * 1024; +const OPENDAL_BUFFER_SIZE: usize = 16 * 1024 * 1024; #[async_trait::async_trait] impl StreamingUploader for OpenDalStreamingUploader { @@ -199,99 +231,7 @@ impl StreamingUploader for OpenDalStreamingUploader { } fn get_memory_usage(&self) -> u64 { - OPENDAL_BUFFER_SIZE - } -} - -struct OpenDalObjectIter { - lister: Option, - op: Option, - #[allow(clippy::type_complexity)] - next_future: Option>, Lister)>>, - #[allow(clippy::type_complexity)] - metadata_future: Option, Operator)>>, -} - -impl OpenDalObjectIter { - fn new(lister: Lister, op: Operator) -> Self { - Self { - lister: Some(lister), - op: Some(op), - next_future: None, - metadata_future: None, - } - } -} - -impl Stream for OpenDalObjectIter { - type Item = ObjectResult; - - fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - if let Some(metadata_future) = self.metadata_future.as_mut() { - let (result, op) = ready!(metadata_future.poll_unpin(cx)); - self.op = Some(op); - return match result { - Ok(m) => { - self.metadata_future = None; - Poll::Ready(Some(Ok(m))) - } - Err(e) => { - self.metadata_future = None; - Poll::Ready(Some(Err(e.into()))) - } - }; - } - if let Some(next_future) = self.next_future.as_mut() { - let (option, lister) = ready!(next_future.poll_unpin(cx)); - self.lister = Some(lister); - return match option { - None => { - self.next_future = None; - Poll::Ready(None) - } - Some(result) => { - self.next_future = None; - match result { - Ok(object) => { - let op = self.op.take().expect("op should not be None"); - let f = async move { - let key = object.path().to_string(); - // FIXME: How does opendal metadata cache work? - // Will below line result in one IO per object? - let om = match op - .metadata( - &object, - Metakey::LastModified | Metakey::ContentLength, - ) - .await - { - Ok(om) => om, - Err(e) => return (Err(e), op), - }; - let last_modified = match om.last_modified() { - Some(t) => t.timestamp() as f64, - None => 0_f64, - }; - let total_size = om.content_length() as usize; - let metadata = ObjectMetadata { - key, - last_modified, - total_size, - }; - (Ok(metadata), op) - }; - self.metadata_future = Some(Box::pin(f)); - self.poll_next(cx) - } - Err(e) => Poll::Ready(Some(Err(e.into()))), - } - } - }; - } - let mut lister = self.lister.take().expect("list should not be None"); - let f = async move { (lister.next().await, lister) }; - self.next_future = Some(Box::pin(f)); - self.poll_next(cx) + OPENDAL_BUFFER_SIZE as u64 } } From af6b4c6a4f7a6354a607088c2bcdf5d51473b18a Mon Sep 17 00:00:00 2001 From: Noel Kwan <47273164+kwannoel@users.noreply.github.com> Date: Mon, 30 Oct 2023 11:17:04 +0800 Subject: [PATCH 5/9] chore(stream): change `FlowControlExecutor` to print WARN logs in `madsim` (#13125) --- src/stream/src/executor/flow_control.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/stream/src/executor/flow_control.rs b/src/stream/src/executor/flow_control.rs index 230359109fca0..320d93b9810f1 100644 --- a/src/stream/src/executor/flow_control.rs +++ b/src/stream/src/executor/flow_control.rs @@ -39,7 +39,7 @@ impl FlowControlExecutor { #[allow(clippy::too_many_arguments)] pub fn new(input: Box, rate_limit: Option) -> Self { #[cfg(madsim)] - println!("FlowControlExecutor rate limiter is disabled in madsim as it will spawn system threads"); + tracing::warn!("FlowControlExecutor rate limiter is disabled in madsim as it will spawn system threads"); Self { input, rate_limit } } From 9a5e2b886e823e038edd6f42a632112236d5b8d1 Mon Sep 17 00:00:00 2001 From: Runji Wang Date: Mon, 30 Oct 2023 11:28:50 +0800 Subject: [PATCH 6/9] feat(parser): support "jsonb" as typed string (#13087) Signed-off-by: Runji Wang --- e2e_test/batch/types/jsonb.slt.part | 6 ++++++ src/frontend/src/binder/expr/mod.rs | 2 +- src/frontend/src/binder/select.rs | 1 + src/sqlparser/src/ast/data_type.rs | 3 +++ src/sqlparser/src/parser.rs | 7 ++++++- 5 files changed, 17 insertions(+), 2 deletions(-) diff --git a/e2e_test/batch/types/jsonb.slt.part b/e2e_test/batch/types/jsonb.slt.part index fc2f60d48db35..9812f254ff0cc 100644 --- a/e2e_test/batch/types/jsonb.slt.part +++ b/e2e_test/batch/types/jsonb.slt.part @@ -33,6 +33,12 @@ null true NULL +# typed string +query TT +select jsonb 'true', JsonB '{}'; +---- +true {} + query T select 'true'::jsonb::bool; ---- diff --git a/src/frontend/src/binder/expr/mod.rs b/src/frontend/src/binder/expr/mod.rs index 221056f3a4822..4433605b03a54 100644 --- a/src/frontend/src/binder/expr/mod.rs +++ b/src/frontend/src/binder/expr/mod.rs @@ -642,7 +642,6 @@ pub fn bind_data_type(data_type: &AstDataType) -> Result { "float4" => DataType::Float32, "float8" => DataType::Float64, "timestamptz" => DataType::Timestamptz, - "jsonb" => DataType::Jsonb, "serial" => { return Err(ErrorCode::NotSupported( "Column type SERIAL is not supported".into(), @@ -654,6 +653,7 @@ pub fn bind_data_type(data_type: &AstDataType) -> Result { } } AstDataType::Bytea => DataType::Bytea, + AstDataType::Jsonb => DataType::Jsonb, AstDataType::Regclass | AstDataType::Regproc | AstDataType::Uuid diff --git a/src/frontend/src/binder/select.rs b/src/frontend/src/binder/select.rs index 48c4290ee7e05..ac1a53e75f63f 100644 --- a/src/frontend/src/binder/select.rs +++ b/src/frontend/src/binder/select.rs @@ -905,6 +905,7 @@ fn data_type_to_alias(data_type: &AstDataType) -> Option { AstDataType::Regproc => "regproc".to_string(), AstDataType::Text => "text".to_string(), AstDataType::Bytea => "bytea".to_string(), + AstDataType::Jsonb => "jsonb".to_string(), AstDataType::Array(ty) => return data_type_to_alias(ty), AstDataType::Custom(ty) => format!("{}", ty), AstDataType::Struct(_) => { diff --git a/src/sqlparser/src/ast/data_type.rs b/src/sqlparser/src/ast/data_type.rs index e8ad404d4d7d6..1e588955f093b 100644 --- a/src/sqlparser/src/ast/data_type.rs +++ b/src/sqlparser/src/ast/data_type.rs @@ -62,6 +62,8 @@ pub enum DataType { Text, /// Bytea Bytea, + /// JSONB + Jsonb, /// Custom type such as enums Custom(ObjectName), /// Arrays @@ -102,6 +104,7 @@ impl fmt::Display for DataType { DataType::Regproc => write!(f, "REGPROC"), DataType::Text => write!(f, "TEXT"), DataType::Bytea => write!(f, "BYTEA"), + DataType::Jsonb => write!(f, "JSONB"), DataType::Array(ty) => write!(f, "{}[]", ty), DataType::Custom(ty) => write!(f, "{}", ty), DataType::Struct(defs) => { diff --git a/src/sqlparser/src/parser.rs b/src/sqlparser/src/parser.rs index 5cc094a204268..d87488ac30648 100644 --- a/src/sqlparser/src/parser.rs +++ b/src/sqlparser/src/parser.rs @@ -3254,7 +3254,12 @@ impl Parser { _ => { self.prev_token(); let type_name = self.parse_object_name()?; - Ok(DataType::Custom(type_name)) + // JSONB is not a keyword + if type_name.to_string().eq_ignore_ascii_case("jsonb") { + Ok(DataType::Jsonb) + } else { + Ok(DataType::Custom(type_name)) + } } }, unexpected => { From 7db34d516356d11ed2562e798cbb28f66108f715 Mon Sep 17 00:00:00 2001 From: Runji Wang Date: Mon, 30 Oct 2023 12:32:01 +0800 Subject: [PATCH 7/9] feat(expr): support jsonb `@>`, `<@`, `?`, `?|` and `?&` operator (#13056) Signed-off-by: Runji Wang --- proto/expr.proto | 10 + src/expr/impl/src/scalar/jsonb_contains.rs | 273 +++++++++++++++++++++ src/expr/impl/src/scalar/mod.rs | 1 + src/frontend/src/binder/expr/binary_op.rs | 5 + src/frontend/src/binder/expr/function.rs | 5 + src/frontend/src/expr/pure.rs | 5 + src/sqlparser/src/ast/operator.rs | 10 + src/sqlparser/src/parser.rs | 12 +- src/sqlparser/src/tokenizer.rs | 34 ++- src/tests/regress/data/sql/jsonb.sql | 206 ++++++++-------- 10 files changed, 456 insertions(+), 105 deletions(-) create mode 100644 src/expr/impl/src/scalar/jsonb_contains.rs diff --git a/proto/expr.proto b/proto/expr.proto index 7998f2fe8128a..cab83e0ea45ce 100644 --- a/proto/expr.proto +++ b/proto/expr.proto @@ -222,6 +222,16 @@ message ExprNode { JSONB_CAT = 605; JSONB_OBJECT = 606; JSONB_PRETTY = 607; + // jsonb @> jsonb + JSONB_CONTAINS = 608; + // jsonb <@ jsonb + JSONB_CONTAINED = 609; + // jsonb ? text + JSONB_EXISTS = 610; + // jsonb ?| text[] + JSONB_EXISTS_ANY = 611; + // jsonb ?& text[] + JSONB_EXISTS_ALL = 612; // Non-pure functions below (> 1000) // ------------------------ diff --git a/src/expr/impl/src/scalar/jsonb_contains.rs b/src/expr/impl/src/scalar/jsonb_contains.rs new file mode 100644 index 0000000000000..02dfffe9d2d18 --- /dev/null +++ b/src/expr/impl/src/scalar/jsonb_contains.rs @@ -0,0 +1,273 @@ +// Copyright 2023 RisingWave Labs +// +// Licensed 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. + +use jsonbb::ValueRef; +use risingwave_common::types::{JsonbRef, ListRef}; +use risingwave_expr::function; + +/// Does the first JSON value contain the second? +/// +/// Examples: +/// +/// ```slt +/// # Simple scalar/primitive values contain only the identical value: +/// query B +/// SELECT '"foo"'::jsonb @> '"foo"'::jsonb; +/// ---- +/// t +/// +/// # The array on the right side is contained within the one on the left: +/// query B +/// SELECT '[1, 2, 3]'::jsonb @> '[1, 3]'::jsonb; +/// ---- +/// t +/// +/// # Order of array elements is not significant, so this is also true: +/// query B +/// SELECT '[1, 2, 3]'::jsonb @> '[3, 1]'::jsonb; +/// ---- +/// t +/// +/// # Duplicate array elements don't matter either: +/// query B +/// SELECT '[1, 2, 3]'::jsonb @> '[1, 2, 2]'::jsonb; +/// ---- +/// t +/// +/// # The object with a single pair on the right side is contained +/// # within the object on the left side: +/// query B +/// SELECT '{"product": "PostgreSQL", "version": 9.4, "jsonb": true}'::jsonb @> '{"version": 9.4}'::jsonb; +/// ---- +/// t +/// +/// # The array on the right side is not considered contained within the +/// # array on the left, even though a similar array is nested within it: +/// query B +/// SELECT '[1, 2, [1, 3]]'::jsonb @> '[1, 3]'::jsonb; +/// ---- +/// f +/// +/// # But with a layer of nesting, it is contained: +/// query B +/// SELECT '[1, 2, [1, 3]]'::jsonb @> '[[1, 3]]'::jsonb; +/// ---- +/// t +/// +/// # Similarly, containment is not reported here: +/// query B +/// SELECT '{"foo": {"bar": "baz"}}'::jsonb @> '{"bar": "baz"}'::jsonb; +/// ---- +/// f +/// +/// # A top-level key and an empty object is contained: +/// query B +/// SELECT '{"foo": {"bar": "baz"}}'::jsonb @> '{"foo": {}}'::jsonb; +/// ---- +/// t +/// +/// # This array contains the primitive string value: +/// query B +/// SELECT '["foo", "bar"]'::jsonb @> '"bar"'::jsonb; +/// ---- +/// t +/// +/// # This exception is not reciprocal -- non-containment is reported here: +/// query B +/// SELECT '"bar"'::jsonb @> '["bar"]'::jsonb; +/// ---- +/// f +/// +/// # Object is not primitive: +/// query B +/// SELECT '[1, {"a":2}]'::jsonb @> '{"a":2}'; +/// ---- +/// f +/// +/// # Array can be nested: +/// query B +/// SELECT '[1, [3, 4]]'::jsonb @> '[[3]]'; +/// ---- +/// t +/// +/// # Recursion shall not include the special rule of array containing primitive: +/// query B +/// SELECT '{"a": [3, 4]}'::jsonb @> '{"a": 3}'; +/// ---- +/// f +/// ``` +#[function("jsonb_contains(jsonb, jsonb) -> boolean")] +fn jsonb_contains(left: JsonbRef<'_>, right: JsonbRef<'_>) -> bool { + jsonbb_contains(left.into(), right.into(), true) +} + +/// Performs `jsonb_contains` on `jsonbb::ValueRef`. +/// `root` indicates whether the current recursion is at the root level. +fn jsonbb_contains(left: ValueRef<'_>, right: ValueRef<'_>, root: bool) -> bool { + match (left, right) { + // Both left and right are objects. + (ValueRef::Object(left_obj), ValueRef::Object(right_obj)) => { + // Every key-value pair in right should be present in left. + right_obj.iter().all(|(key, value)| { + left_obj + .get(key) + .map_or(false, |left_val| jsonbb_contains(left_val, value, false)) + }) + } + + // Both left and right are arrays. + (ValueRef::Array(left_arr), ValueRef::Array(right_arr)) => { + // For every value in right, there should be an equivalent in left. + right_arr.iter().all(|right_val| { + left_arr + .iter() + .any(|left_val| jsonbb_contains(left_val, right_val, false)) + }) + } + + // Left is an array and right is an object. + (ValueRef::Array(_), ValueRef::Object(_)) => false, + + // Left is an array and right is a primitive value. only at root level. + (ValueRef::Array(left_arr), right_val) if root => { + // The right should be present in left. + left_arr.iter().any(|left_val| left_val == right_val) + } + + // Both left and right are primitive values. + (left_val, right_val) => left_val == right_val, + } +} + +/// Is the first JSON value contained in the second? +/// +/// Examples: +/// +/// ```slt +/// query B +/// select '{"b":2}'::jsonb <@ '{"a":1, "b":2}'::jsonb; +/// ---- +/// t +/// ``` +#[function("jsonb_contained(jsonb, jsonb) -> boolean")] +fn jsonb_contained(left: JsonbRef<'_>, right: JsonbRef<'_>) -> bool { + jsonb_contains(right, left) +} + +/// Does the text string exist as a top-level key or array element within the JSON value? +/// +/// Examples: +/// +/// ```slt +/// # String exists as array element: +/// query B +/// SELECT '["foo", "bar", "baz"]'::jsonb ? 'bar'; +/// ---- +/// t +/// +/// # String exists as object key: +/// query B +/// SELECT '{"foo": "bar"}'::jsonb ? 'foo'; +/// ---- +/// t +/// +/// # Object values are not considered: +/// query B +/// SELECT '{"foo": "bar"}'::jsonb ? 'bar'; +/// ---- +/// f +/// +/// # As with containment, existence must match at the top level: +/// query B +/// SELECT '{"foo": {"bar": "baz"}}'::jsonb ? 'bar'; +/// ---- +/// f +/// +/// # A string is considered to exist if it matches a primitive JSON string: +/// query B +/// SELECT '"foo"'::jsonb ? 'foo'; +/// ---- +/// t +/// ``` +#[function("jsonb_exists(jsonb, varchar) -> boolean")] +fn jsonb_exists(left: JsonbRef<'_>, key: &str) -> bool { + match left.into() { + ValueRef::Object(object) => object.get(key).is_some(), + ValueRef::Array(array) => array.iter().any(|val| val.as_str() == Some(key)), + ValueRef::String(str) => str == key, + _ => false, + } +} + +/// Do any of the strings in the text array exist as top-level keys or array elements? +/// +/// Examples: +/// +/// ```slt +/// query B +/// select '{"a":1, "b":2, "c":3}'::jsonb ?| array['b', 'd']; +/// ---- +/// t +/// +/// query B +/// select '["a", "b", "c"]'::jsonb ?| array['b', 'd']; +/// ---- +/// t +/// +/// query B +/// select '"b"'::jsonb ?| array['b', 'd']; +/// ---- +/// t +/// ``` +#[function("jsonb_exists_any(jsonb, varchar[]) -> boolean")] +fn jsonb_exists_any(left: JsonbRef<'_>, keys: ListRef<'_>) -> bool { + let mut keys = keys.iter().flatten().map(|val| val.into_utf8()); + match left.into() { + ValueRef::Object(object) => keys.any(|key| object.get(key).is_some()), + ValueRef::Array(array) => keys.any(|key| array.iter().any(|val| val.as_str() == Some(key))), + ValueRef::String(str) => keys.any(|key| str == key), + _ => false, + } +} + +/// Do all of the strings in the text array exist as top-level keys or array elements? +/// +/// Examples: +/// +/// ```slt +/// query B +/// select '{"a":1, "b":2, "c":3}'::jsonb ?& array['a', 'b']; +/// ---- +/// t +/// +/// query B +/// select '["a", "b", "c"]'::jsonb ?& array['a', 'b']; +/// ---- +/// t +/// +/// query B +/// select '"b"'::jsonb ?& array['b']; +/// ---- +/// t +/// ``` +#[function("jsonb_exists_all(jsonb, varchar[]) -> boolean")] +fn jsonb_exists_all(left: JsonbRef<'_>, keys: ListRef<'_>) -> bool { + let mut keys = keys.iter().flatten().map(|val| val.into_utf8()); + match left.into() { + ValueRef::Object(object) => keys.all(|key| object.get(key).is_some()), + ValueRef::Array(array) => keys.all(|key| array.iter().any(|val| val.as_str() == Some(key))), + ValueRef::String(str) => keys.all(|key| str == key), + _ => false, + } +} diff --git a/src/expr/impl/src/scalar/mod.rs b/src/expr/impl/src/scalar/mod.rs index d9d10e4548aee..d1b89d3c75d6c 100644 --- a/src/expr/impl/src/scalar/mod.rs +++ b/src/expr/impl/src/scalar/mod.rs @@ -44,6 +44,7 @@ mod format_type; mod int256; mod jsonb_access; mod jsonb_concat; +mod jsonb_contains; mod jsonb_info; mod jsonb_object; mod length; diff --git a/src/frontend/src/binder/expr/binary_op.rs b/src/frontend/src/binder/expr/binary_op.rs index f7c8a86144fc9..bd85089b18cce 100644 --- a/src/frontend/src/binder/expr/binary_op.rs +++ b/src/frontend/src/binder/expr/binary_op.rs @@ -92,6 +92,11 @@ impl Binder { BinaryOperator::Arrow => ExprType::JsonbAccessInner, BinaryOperator::LongArrow => ExprType::JsonbAccessStr, BinaryOperator::Prefix => ExprType::StartsWith, + BinaryOperator::Contains => ExprType::JsonbContains, + BinaryOperator::Contained => ExprType::JsonbContained, + BinaryOperator::Exists => ExprType::JsonbExists, + BinaryOperator::ExistsAny => ExprType::JsonbExistsAny, + BinaryOperator::ExistsAll => ExprType::JsonbExistsAll, BinaryOperator::Concat => { let left_type = (!bound_left.is_untyped()).then(|| bound_left.return_type()); let right_type = (!bound_right.is_untyped()).then(|| bound_right.return_type()); diff --git a/src/frontend/src/binder/expr/function.rs b/src/frontend/src/binder/expr/function.rs index 50a621c0879d5..7ca509e6e3af9 100644 --- a/src/frontend/src/binder/expr/function.rs +++ b/src/frontend/src/binder/expr/function.rs @@ -879,6 +879,11 @@ impl Binder { ("jsonb_array_length", raw_call(ExprType::JsonbArrayLength)), ("jsonb_object", raw_call(ExprType::JsonbObject)), ("jsonb_pretty", raw_call(ExprType::JsonbPretty)), + ("jsonb_contains", raw_call(ExprType::JsonbContains)), + ("jsonb_contained", raw_call(ExprType::JsonbContained)), + ("jsonb_exists", raw_call(ExprType::JsonbExists)), + ("jsonb_exists_any", raw_call(ExprType::JsonbExistsAny)), + ("jsonb_exists_all", raw_call(ExprType::JsonbExistsAll)), // Functions that return a constant value ("pi", pi()), // greatest and least diff --git a/src/frontend/src/expr/pure.rs b/src/frontend/src/expr/pure.rs index a3c7abf1ef482..038bd94ffcf07 100644 --- a/src/frontend/src/expr/pure.rs +++ b/src/frontend/src/expr/pure.rs @@ -179,6 +179,11 @@ impl ExprVisitor for ImpureAnalyzer { | expr_node::Type::JsonbArrayLength | expr_node::Type::JsonbObject | expr_node::Type::JsonbPretty + | expr_node::Type::JsonbContains + | expr_node::Type::JsonbContained + | expr_node::Type::JsonbExists + | expr_node::Type::JsonbExistsAny + | expr_node::Type::JsonbExistsAll | expr_node::Type::IsJson | expr_node::Type::Sind | expr_node::Type::Cosd diff --git a/src/sqlparser/src/ast/operator.rs b/src/sqlparser/src/ast/operator.rs index ad084a59425b0..147a78d0b7174 100644 --- a/src/sqlparser/src/ast/operator.rs +++ b/src/sqlparser/src/ast/operator.rs @@ -99,6 +99,11 @@ pub enum BinaryOperator { LongArrow, HashArrow, HashLongArrow, + Contains, + Contained, + Exists, + ExistsAny, + ExistsAll, PGQualified(Box), } @@ -143,6 +148,11 @@ impl fmt::Display for BinaryOperator { BinaryOperator::LongArrow => "->>", BinaryOperator::HashArrow => "#>", BinaryOperator::HashLongArrow => "#>>", + BinaryOperator::Contains => "@>", + BinaryOperator::Contained => "<@", + BinaryOperator::Exists => "?", + BinaryOperator::ExistsAny => "?|", + BinaryOperator::ExistsAll => "?&", BinaryOperator::PGQualified(_) => unreachable!(), }) } diff --git a/src/sqlparser/src/parser.rs b/src/sqlparser/src/parser.rs index d87488ac30648..d73ec295b0f90 100644 --- a/src/sqlparser/src/parser.rs +++ b/src/sqlparser/src/parser.rs @@ -1397,6 +1397,11 @@ impl Parser { Token::LongArrow => Some(BinaryOperator::LongArrow), Token::HashArrow => Some(BinaryOperator::HashArrow), Token::HashLongArrow => Some(BinaryOperator::HashLongArrow), + Token::AtArrow => Some(BinaryOperator::Contains), + Token::ArrowAt => Some(BinaryOperator::Contained), + Token::QuestionMark => Some(BinaryOperator::Exists), + Token::QuestionMarkPipe => Some(BinaryOperator::ExistsAny), + Token::QuestionMarkAmpersand => Some(BinaryOperator::ExistsAll), Token::Word(w) => match w.keyword { Keyword::AND => Some(BinaryOperator::And), Keyword::OR => Some(BinaryOperator::Or), @@ -1735,7 +1740,12 @@ impl Parser { | Token::Arrow | Token::LongArrow | Token::HashArrow - | Token::HashLongArrow => Ok(P::Other), + | Token::HashLongArrow + | Token::AtArrow + | Token::ArrowAt + | Token::QuestionMark + | Token::QuestionMarkPipe + | Token::QuestionMarkAmpersand => Ok(P::Other), Token::Word(w) if w.keyword == Keyword::OPERATOR && self.peek_nth_token(1) == Token::LParen => { diff --git a/src/sqlparser/src/tokenizer.rs b/src/sqlparser/src/tokenizer.rs index d0d1e096f8f73..914f0e5f0f8d4 100644 --- a/src/sqlparser/src/tokenizer.rs +++ b/src/sqlparser/src/tokenizer.rs @@ -164,6 +164,16 @@ pub enum Token { HashArrow, /// `#>>`, extract JSON sub-object at the specified path as text in PostgreSQL HashLongArrow, + /// `@>`, does the left JSON value contain the right JSON path/value entries at the top level + AtArrow, + /// `<@`, does the right JSON value contain the left JSON path/value entries at the top level + ArrowAt, + /// `?`, does the string exist as a top-level key within the JSON value + QuestionMark, + /// `?|`, do any of the strings exist as top-level keys or array elements? + QuestionMarkPipe, + /// `?&`, do all of the strings exist as top-level keys or array elements? + QuestionMarkAmpersand, } impl fmt::Display for Token { @@ -231,6 +241,11 @@ impl fmt::Display for Token { Token::LongArrow => f.write_str("->>"), Token::HashArrow => f.write_str("#>"), Token::HashLongArrow => f.write_str("#>>"), + Token::AtArrow => f.write_str("@>"), + Token::ArrowAt => f.write_str("<@"), + Token::QuestionMark => f.write_str("?"), + Token::QuestionMarkPipe => f.write_str("?|"), + Token::QuestionMarkAmpersand => f.write_str("?&"), } } } @@ -693,6 +708,7 @@ impl<'a> Tokenizer<'a> { } Some('>') => self.consume_and_return(chars, Token::Neq), Some('<') => self.consume_and_return(chars, Token::ShiftLeft), + Some('@') => self.consume_and_return(chars, Token::ArrowAt), _ => Ok(Some(Token::Lt)), } } @@ -759,7 +775,23 @@ impl<'a> Tokenizer<'a> { _ => Ok(Some(Token::Sharp)), } } - '@' => self.consume_and_return(chars, Token::AtSign), + '@' => { + chars.next(); // consume the '@' + match chars.peek() { + Some('>') => self.consume_and_return(chars, Token::AtArrow), + // a regular '@' operator + _ => Ok(Some(Token::AtSign)), + } + } + '?' => { + chars.next(); // consume the '?' + match chars.peek() { + Some('|') => self.consume_and_return(chars, Token::QuestionMarkPipe), + Some('&') => self.consume_and_return(chars, Token::QuestionMarkAmpersand), + // a regular '?' operator + _ => Ok(Some(Token::QuestionMark)), + } + } other => self.consume_and_return(chars, Token::Char(other)), }, None => Ok(None), diff --git a/src/tests/regress/data/sql/jsonb.sql b/src/tests/regress/data/sql/jsonb.sql index 59b00932db189..26c25af897bf4 100644 --- a/src/tests/regress/data/sql/jsonb.sql +++ b/src/tests/regress/data/sql/jsonb.sql @@ -211,53 +211,53 @@ select '"foo"'::jsonb ->> 'z'; --@ SELECT '{"x":"y"}'::jsonb <> '{"x":"z"}'::jsonb; -- containment ---@ SELECT jsonb_contains('{"a":"b", "b":1, "c":null}', '{"a":"b"}'); ---@ SELECT jsonb_contains('{"a":"b", "b":1, "c":null}', '{"a":"b", "c":null}'); ---@ SELECT jsonb_contains('{"a":"b", "b":1, "c":null}', '{"a":"b", "g":null}'); ---@ SELECT jsonb_contains('{"a":"b", "b":1, "c":null}', '{"g":null}'); ---@ SELECT jsonb_contains('{"a":"b", "b":1, "c":null}', '{"a":"c"}'); ---@ SELECT jsonb_contains('{"a":"b", "b":1, "c":null}', '{"a":"b"}'); ---@ SELECT jsonb_contains('{"a":"b", "b":1, "c":null}', '{"a":"b", "c":"q"}'); ---@ SELECT '{"a":"b", "b":1, "c":null}'::jsonb @> '{"a":"b"}'; ---@ SELECT '{"a":"b", "b":1, "c":null}'::jsonb @> '{"a":"b", "c":null}'; ---@ SELECT '{"a":"b", "b":1, "c":null}'::jsonb @> '{"a":"b", "g":null}'; ---@ SELECT '{"a":"b", "b":1, "c":null}'::jsonb @> '{"g":null}'; ---@ SELECT '{"a":"b", "b":1, "c":null}'::jsonb @> '{"a":"c"}'; ---@ SELECT '{"a":"b", "b":1, "c":null}'::jsonb @> '{"a":"b"}'; ---@ SELECT '{"a":"b", "b":1, "c":null}'::jsonb @> '{"a":"b", "c":"q"}'; ---@ ---@ SELECT '[1,2]'::jsonb @> '[1,2,2]'::jsonb; ---@ SELECT '[1,1,2]'::jsonb @> '[1,2,2]'::jsonb; ---@ SELECT '[[1,2]]'::jsonb @> '[[1,2,2]]'::jsonb; ---@ SELECT '[1,2,2]'::jsonb <@ '[1,2]'::jsonb; ---@ SELECT '[1,2,2]'::jsonb <@ '[1,1,2]'::jsonb; ---@ SELECT '[[1,2,2]]'::jsonb <@ '[[1,2]]'::jsonb; ---@ ---@ SELECT jsonb_contained('{"a":"b"}', '{"a":"b", "b":1, "c":null}'); ---@ SELECT jsonb_contained('{"a":"b", "c":null}', '{"a":"b", "b":1, "c":null}'); ---@ SELECT jsonb_contained('{"a":"b", "g":null}', '{"a":"b", "b":1, "c":null}'); ---@ SELECT jsonb_contained('{"g":null}', '{"a":"b", "b":1, "c":null}'); ---@ SELECT jsonb_contained('{"a":"c"}', '{"a":"b", "b":1, "c":null}'); ---@ SELECT jsonb_contained('{"a":"b"}', '{"a":"b", "b":1, "c":null}'); ---@ SELECT jsonb_contained('{"a":"b", "c":"q"}', '{"a":"b", "b":1, "c":null}'); ---@ SELECT '{"a":"b"}'::jsonb <@ '{"a":"b", "b":1, "c":null}'; ---@ SELECT '{"a":"b", "c":null}'::jsonb <@ '{"a":"b", "b":1, "c":null}'; ---@ SELECT '{"a":"b", "g":null}'::jsonb <@ '{"a":"b", "b":1, "c":null}'; ---@ SELECT '{"g":null}'::jsonb <@ '{"a":"b", "b":1, "c":null}'; ---@ SELECT '{"a":"c"}'::jsonb <@ '{"a":"b", "b":1, "c":null}'; ---@ SELECT '{"a":"b"}'::jsonb <@ '{"a":"b", "b":1, "c":null}'; ---@ SELECT '{"a":"b", "c":"q"}'::jsonb <@ '{"a":"b", "b":1, "c":null}'; ---@ -- Raw scalar may contain another raw scalar, array may contain a raw scalar ---@ SELECT '[5]'::jsonb @> '[5]'; ---@ SELECT '5'::jsonb @> '5'; ---@ SELECT '[5]'::jsonb @> '5'; ---@ -- But a raw scalar cannot contain an array ---@ SELECT '5'::jsonb @> '[5]'; ---@ -- In general, one thing should always contain itself. Test array containment: ---@ SELECT '["9", ["7", "3"], 1]'::jsonb @> '["9", ["7", "3"], 1]'::jsonb; ---@ SELECT '["9", ["7", "3"], ["1"]]'::jsonb @> '["9", ["7", "3"], ["1"]]'::jsonb; ---@ -- array containment string matching confusion bug ---@ SELECT '{ "name": "Bob", "tags": [ "enim", "qui"]}'::jsonb @> '{"tags":["qu"]}'; +SELECT jsonb_contains('{"a":"b", "b":1, "c":null}', '{"a":"b"}'); +SELECT jsonb_contains('{"a":"b", "b":1, "c":null}', '{"a":"b", "c":null}'); +SELECT jsonb_contains('{"a":"b", "b":1, "c":null}', '{"a":"b", "g":null}'); +SELECT jsonb_contains('{"a":"b", "b":1, "c":null}', '{"g":null}'); +SELECT jsonb_contains('{"a":"b", "b":1, "c":null}', '{"a":"c"}'); +SELECT jsonb_contains('{"a":"b", "b":1, "c":null}', '{"a":"b"}'); +SELECT jsonb_contains('{"a":"b", "b":1, "c":null}', '{"a":"b", "c":"q"}'); +SELECT '{"a":"b", "b":1, "c":null}'::jsonb @> '{"a":"b"}'; +SELECT '{"a":"b", "b":1, "c":null}'::jsonb @> '{"a":"b", "c":null}'; +SELECT '{"a":"b", "b":1, "c":null}'::jsonb @> '{"a":"b", "g":null}'; +SELECT '{"a":"b", "b":1, "c":null}'::jsonb @> '{"g":null}'; +SELECT '{"a":"b", "b":1, "c":null}'::jsonb @> '{"a":"c"}'; +SELECT '{"a":"b", "b":1, "c":null}'::jsonb @> '{"a":"b"}'; +SELECT '{"a":"b", "b":1, "c":null}'::jsonb @> '{"a":"b", "c":"q"}'; + +SELECT '[1,2]'::jsonb @> '[1,2,2]'::jsonb; +SELECT '[1,1,2]'::jsonb @> '[1,2,2]'::jsonb; +SELECT '[[1,2]]'::jsonb @> '[[1,2,2]]'::jsonb; +SELECT '[1,2,2]'::jsonb <@ '[1,2]'::jsonb; +SELECT '[1,2,2]'::jsonb <@ '[1,1,2]'::jsonb; +SELECT '[[1,2,2]]'::jsonb <@ '[[1,2]]'::jsonb; + +SELECT jsonb_contained('{"a":"b"}', '{"a":"b", "b":1, "c":null}'); +SELECT jsonb_contained('{"a":"b", "c":null}', '{"a":"b", "b":1, "c":null}'); +SELECT jsonb_contained('{"a":"b", "g":null}', '{"a":"b", "b":1, "c":null}'); +SELECT jsonb_contained('{"g":null}', '{"a":"b", "b":1, "c":null}'); +SELECT jsonb_contained('{"a":"c"}', '{"a":"b", "b":1, "c":null}'); +SELECT jsonb_contained('{"a":"b"}', '{"a":"b", "b":1, "c":null}'); +SELECT jsonb_contained('{"a":"b", "c":"q"}', '{"a":"b", "b":1, "c":null}'); +SELECT '{"a":"b"}'::jsonb <@ '{"a":"b", "b":1, "c":null}'; +SELECT '{"a":"b", "c":null}'::jsonb <@ '{"a":"b", "b":1, "c":null}'; +SELECT '{"a":"b", "g":null}'::jsonb <@ '{"a":"b", "b":1, "c":null}'; +SELECT '{"g":null}'::jsonb <@ '{"a":"b", "b":1, "c":null}'; +SELECT '{"a":"c"}'::jsonb <@ '{"a":"b", "b":1, "c":null}'; +SELECT '{"a":"b"}'::jsonb <@ '{"a":"b", "b":1, "c":null}'; +SELECT '{"a":"b", "c":"q"}'::jsonb <@ '{"a":"b", "b":1, "c":null}'; +-- Raw scalar may contain another raw scalar, array may contain a raw scalar +SELECT '[5]'::jsonb @> '[5]'; +SELECT '5'::jsonb @> '5'; +SELECT '[5]'::jsonb @> '5'; +-- But a raw scalar cannot contain an array +SELECT '5'::jsonb @> '[5]'; +-- In general, one thing should always contain itself. Test array containment: +SELECT '["9", ["7", "3"], 1]'::jsonb @> '["9", ["7", "3"], 1]'::jsonb; +SELECT '["9", ["7", "3"], ["1"]]'::jsonb @> '["9", ["7", "3"], ["1"]]'::jsonb; +-- array containment string matching confusion bug +SELECT '{ "name": "Bob", "tags": [ "enim", "qui"]}'::jsonb @> '{"tags":["qu"]}'; -- array length SELECT jsonb_array_length('[1,2,3,{"f1":1,"f2":[5,6]},4]'); @@ -277,14 +277,14 @@ SELECT jsonb_array_length('4'); --@ SELECT * FROM jsonb_each_text('{"a":{"b":"c","c":"b","1":"first"},"b":[1,2],"c":"cc","1":"first","n":null}'::jsonb) AS q; -- exists ---@ SELECT jsonb_exists('{"a":null, "b":"qq"}', 'a'); ---@ SELECT jsonb_exists('{"a":null, "b":"qq"}', 'b'); ---@ SELECT jsonb_exists('{"a":null, "b":"qq"}', 'c'); ---@ SELECT jsonb_exists('{"a":"null", "b":"qq"}', 'a'); ---@ SELECT jsonb '{"a":null, "b":"qq"}' ? 'a'; ---@ SELECT jsonb '{"a":null, "b":"qq"}' ? 'b'; ---@ SELECT jsonb '{"a":null, "b":"qq"}' ? 'c'; ---@ SELECT jsonb '{"a":"null", "b":"qq"}' ? 'a'; +SELECT jsonb_exists('{"a":null, "b":"qq"}', 'a'); +SELECT jsonb_exists('{"a":null, "b":"qq"}', 'b'); +SELECT jsonb_exists('{"a":null, "b":"qq"}', 'c'); +SELECT jsonb_exists('{"a":"null", "b":"qq"}', 'a'); +SELECT jsonb '{"a":null, "b":"qq"}' ? 'a'; +SELECT jsonb '{"a":null, "b":"qq"}' ? 'b'; +SELECT jsonb '{"a":null, "b":"qq"}' ? 'c'; +SELECT jsonb '{"a":"null", "b":"qq"}' ? 'a'; --@ -- array exists - array elements should behave as keys --@ SELECT count(*) from testjsonb WHERE j->'array' ? 'bar'; --@ -- type sensitive array exists - should return no rows (since "exists" only @@ -292,29 +292,29 @@ SELECT jsonb_array_length('4'); --@ SELECT count(*) from testjsonb WHERE j->'array' ? '5'::text; --@ -- However, a raw scalar is *contained* within the array --@ SELECT count(*) from testjsonb WHERE j->'array' @> '5'::jsonb; ---@ ---@ SELECT jsonb_exists_any('{"a":null, "b":"qq"}', ARRAY['a','b']); ---@ SELECT jsonb_exists_any('{"a":null, "b":"qq"}', ARRAY['b','a']); ---@ SELECT jsonb_exists_any('{"a":null, "b":"qq"}', ARRAY['c','a']); ---@ SELECT jsonb_exists_any('{"a":null, "b":"qq"}', ARRAY['c','d']); ---@ SELECT jsonb_exists_any('{"a":null, "b":"qq"}', '{}'::text[]); ---@ SELECT jsonb '{"a":null, "b":"qq"}' ?| ARRAY['a','b']; ---@ SELECT jsonb '{"a":null, "b":"qq"}' ?| ARRAY['b','a']; ---@ SELECT jsonb '{"a":null, "b":"qq"}' ?| ARRAY['c','a']; ---@ SELECT jsonb '{"a":null, "b":"qq"}' ?| ARRAY['c','d']; ---@ SELECT jsonb '{"a":null, "b":"qq"}' ?| '{}'::text[]; ---@ ---@ SELECT jsonb_exists_all('{"a":null, "b":"qq"}', ARRAY['a','b']); ---@ SELECT jsonb_exists_all('{"a":null, "b":"qq"}', ARRAY['b','a']); ---@ SELECT jsonb_exists_all('{"a":null, "b":"qq"}', ARRAY['c','a']); ---@ SELECT jsonb_exists_all('{"a":null, "b":"qq"}', ARRAY['c','d']); ---@ SELECT jsonb_exists_all('{"a":null, "b":"qq"}', '{}'::text[]); ---@ SELECT jsonb '{"a":null, "b":"qq"}' ?& ARRAY['a','b']; ---@ SELECT jsonb '{"a":null, "b":"qq"}' ?& ARRAY['b','a']; ---@ SELECT jsonb '{"a":null, "b":"qq"}' ?& ARRAY['c','a']; ---@ SELECT jsonb '{"a":null, "b":"qq"}' ?& ARRAY['c','d']; ---@ SELECT jsonb '{"a":null, "b":"qq"}' ?& ARRAY['a','a', 'b', 'b', 'b']; ---@ SELECT jsonb '{"a":null, "b":"qq"}' ?& '{}'::text[]; + +SELECT jsonb_exists_any('{"a":null, "b":"qq"}', ARRAY['a','b']); +SELECT jsonb_exists_any('{"a":null, "b":"qq"}', ARRAY['b','a']); +SELECT jsonb_exists_any('{"a":null, "b":"qq"}', ARRAY['c','a']); +SELECT jsonb_exists_any('{"a":null, "b":"qq"}', ARRAY['c','d']); +SELECT jsonb_exists_any('{"a":null, "b":"qq"}', '{}'::text[]); +SELECT jsonb '{"a":null, "b":"qq"}' ?| ARRAY['a','b']; +SELECT jsonb '{"a":null, "b":"qq"}' ?| ARRAY['b','a']; +SELECT jsonb '{"a":null, "b":"qq"}' ?| ARRAY['c','a']; +SELECT jsonb '{"a":null, "b":"qq"}' ?| ARRAY['c','d']; +SELECT jsonb '{"a":null, "b":"qq"}' ?| '{}'::text[]; + +SELECT jsonb_exists_all('{"a":null, "b":"qq"}', ARRAY['a','b']); +SELECT jsonb_exists_all('{"a":null, "b":"qq"}', ARRAY['b','a']); +SELECT jsonb_exists_all('{"a":null, "b":"qq"}', ARRAY['c','a']); +SELECT jsonb_exists_all('{"a":null, "b":"qq"}', ARRAY['c','d']); +SELECT jsonb_exists_all('{"a":null, "b":"qq"}', '{}'::text[]); +SELECT jsonb '{"a":null, "b":"qq"}' ?& ARRAY['a','b']; +SELECT jsonb '{"a":null, "b":"qq"}' ?& ARRAY['b','a']; +SELECT jsonb '{"a":null, "b":"qq"}' ?& ARRAY['c','a']; +SELECT jsonb '{"a":null, "b":"qq"}' ?& ARRAY['c','d']; +SELECT jsonb '{"a":null, "b":"qq"}' ?& ARRAY['a','a', 'b', 'b', 'b']; +SELECT jsonb '{"a":null, "b":"qq"}' ?& '{}'::text[]; -- typeof SELECT jsonb_typeof('{}') AS object; @@ -941,25 +941,25 @@ SELECT '{"ff":{"a":12,"b":16},"qq":123,"x":[1,2],"Y":null}'::jsonb -> 'x'; -- nested containment ---@ SELECT '{"a":[1,2],"c":"b"}'::jsonb @> '{"a":[1,2]}'; ---@ SELECT '{"a":[2,1],"c":"b"}'::jsonb @> '{"a":[1,2]}'; ---@ SELECT '{"a":{"1":2},"c":"b"}'::jsonb @> '{"a":[1,2]}'; ---@ SELECT '{"a":{"2":1},"c":"b"}'::jsonb @> '{"a":[1,2]}'; ---@ SELECT '{"a":{"1":2},"c":"b"}'::jsonb @> '{"a":{"1":2}}'; ---@ SELECT '{"a":{"2":1},"c":"b"}'::jsonb @> '{"a":{"1":2}}'; ---@ SELECT '["a","b"]'::jsonb @> '["a","b","c","b"]'; ---@ SELECT '["a","b","c","b"]'::jsonb @> '["a","b"]'; ---@ SELECT '["a","b","c",[1,2]]'::jsonb @> '["a",[1,2]]'; ---@ SELECT '["a","b","c",[1,2]]'::jsonb @> '["b",[1,2]]'; ---@ ---@ SELECT '{"a":[1,2],"c":"b"}'::jsonb @> '{"a":[1]}'; ---@ SELECT '{"a":[1,2],"c":"b"}'::jsonb @> '{"a":[2]}'; ---@ SELECT '{"a":[1,2],"c":"b"}'::jsonb @> '{"a":[3]}'; ---@ ---@ SELECT '{"a":[1,2,{"c":3,"x":4}],"c":"b"}'::jsonb @> '{"a":[{"c":3}]}'; ---@ SELECT '{"a":[1,2,{"c":3,"x":4}],"c":"b"}'::jsonb @> '{"a":[{"x":4}]}'; ---@ SELECT '{"a":[1,2,{"c":3,"x":4}],"c":"b"}'::jsonb @> '{"a":[{"x":4},3]}'; ---@ SELECT '{"a":[1,2,{"c":3,"x":4}],"c":"b"}'::jsonb @> '{"a":[{"x":4},1]}'; +SELECT '{"a":[1,2],"c":"b"}'::jsonb @> '{"a":[1,2]}'; +SELECT '{"a":[2,1],"c":"b"}'::jsonb @> '{"a":[1,2]}'; +SELECT '{"a":{"1":2},"c":"b"}'::jsonb @> '{"a":[1,2]}'; +SELECT '{"a":{"2":1},"c":"b"}'::jsonb @> '{"a":[1,2]}'; +SELECT '{"a":{"1":2},"c":"b"}'::jsonb @> '{"a":{"1":2}}'; +SELECT '{"a":{"2":1},"c":"b"}'::jsonb @> '{"a":{"1":2}}'; +SELECT '["a","b"]'::jsonb @> '["a","b","c","b"]'; +SELECT '["a","b","c","b"]'::jsonb @> '["a","b"]'; +SELECT '["a","b","c",[1,2]]'::jsonb @> '["a",[1,2]]'; +SELECT '["a","b","c",[1,2]]'::jsonb @> '["b",[1,2]]'; + +SELECT '{"a":[1,2],"c":"b"}'::jsonb @> '{"a":[1]}'; +SELECT '{"a":[1,2],"c":"b"}'::jsonb @> '{"a":[2]}'; +SELECT '{"a":[1,2],"c":"b"}'::jsonb @> '{"a":[3]}'; + +SELECT '{"a":[1,2,{"c":3,"x":4}],"c":"b"}'::jsonb @> '{"a":[{"c":3}]}'; +SELECT '{"a":[1,2,{"c":3,"x":4}],"c":"b"}'::jsonb @> '{"a":[{"x":4}]}'; +SELECT '{"a":[1,2,{"c":3,"x":4}],"c":"b"}'::jsonb @> '{"a":[{"x":4},3]}'; +SELECT '{"a":[1,2,{"c":3,"x":4}],"c":"b"}'::jsonb @> '{"a":[{"x":4},1]}'; -- check some corner cases for indexed nested containment (bug #13756) --@ create temp table nestjsonb (j jsonb); @@ -1020,12 +1020,12 @@ SELECT '["a","b","c",[1,2],null]'::jsonb -> -6; --@ SELECT '[0,1,2,[3,4],{"5":"five"}]'::jsonb #> '{4,5}'; --nested exists ---@ SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::jsonb ? 'n'; ---@ SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::jsonb ? 'a'; ---@ SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::jsonb ? 'b'; ---@ SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::jsonb ? 'c'; ---@ SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::jsonb ? 'd'; ---@ SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::jsonb ? 'e'; +SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::jsonb ? 'n'; +SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::jsonb ? 'a'; +SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::jsonb ? 'b'; +SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::jsonb ? 'c'; +SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::jsonb ? 'd'; +SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::jsonb ? 'e'; -- jsonb_strip_nulls From 98edea69b37499c697935c3e9aab3327458b9b63 Mon Sep 17 00:00:00 2001 From: Xinhao Xu <84456268+xxhZs@users.noreply.github.com> Date: Mon, 30 Oct 2023 13:02:35 +0800 Subject: [PATCH 8/9] fix(psql_conn): Add row desc with 'SHOW PARAMETERS' (#13099) Co-authored-by: xiangjinwu <17769960+xiangjinwu@users.noreply.github.com> --- src/frontend/src/handler/variable.rs | 81 ++++--------------- src/frontend/src/session.rs | 28 +------ src/frontend/src/utils/infer_stmt_row_desc.rs | 46 +++++++++++ 3 files changed, 63 insertions(+), 92 deletions(-) diff --git a/src/frontend/src/handler/variable.rs b/src/frontend/src/handler/variable.rs index 9060ff2209413..7d108ae35128e 100644 --- a/src/frontend/src/handler/variable.rs +++ b/src/frontend/src/handler/variable.rs @@ -13,7 +13,6 @@ // limitations under the License. use itertools::Itertools; -use pgwire::pg_field_descriptor::PgFieldDescriptor; use pgwire::pg_protocol::ParameterStatus; use pgwire::pg_response::{PgResponse, StatementType}; use pgwire::types::Row; @@ -25,6 +24,7 @@ use risingwave_sqlparser::ast::{Ident, SetTimeZoneValue, SetVariableValue, Value use super::RwPgResponse; use crate::handler::HandlerArgs; +use crate::utils::infer_stmt_row_desc::infer_show_variable; pub fn handle_set( handler_args: HandlerArgs, @@ -96,29 +96,22 @@ pub(super) async fn handle_show( ) -> Result { // TODO: Verify that the name used in `show` command is indeed always case-insensitive. let name = variable.iter().map(|e| e.real_value()).join(" "); - if name.eq_ignore_ascii_case("PARAMETERS") { - return handle_show_system_params(handler_args).await; - } - // Show session config. - let config_reader = handler_args.session.config(); - if name.eq_ignore_ascii_case("ALL") { - return handle_show_all(handler_args.clone()); - } - let row = Row::new(vec![Some(config_reader.get(&name)?.into())]); + let row_desc = infer_show_variable(&name); + let rows = if name.eq_ignore_ascii_case("PARAMETERS") { + handle_show_system_params(handler_args).await? + } else if name.eq_ignore_ascii_case("ALL") { + handle_show_all(handler_args.clone())? + } else { + let config_reader = handler_args.session.config(); + vec![Row::new(vec![Some(config_reader.get(&name)?.into())])] + }; Ok(PgResponse::builder(StatementType::SHOW_VARIABLE) - .values( - vec![row].into(), - vec![PgFieldDescriptor::new( - name.to_ascii_lowercase(), - DataType::Varchar.to_oid(), - DataType::Varchar.type_len(), - )], - ) + .values(rows.into(), row_desc) .into()) } -fn handle_show_all(handler_args: HandlerArgs) -> Result { +fn handle_show_all(handler_args: HandlerArgs) -> Result> { let config_reader = handler_args.session.config(); let all_variables = config_reader.get_all(); @@ -133,32 +126,10 @@ fn handle_show_all(handler_args: HandlerArgs) -> Result { ]) }) .collect_vec(); - - Ok(RwPgResponse::builder(StatementType::SHOW_VARIABLE) - .values( - rows.into(), - vec![ - PgFieldDescriptor::new( - "Name".to_string(), - DataType::Varchar.to_oid(), - DataType::Varchar.type_len(), - ), - PgFieldDescriptor::new( - "Setting".to_string(), - DataType::Varchar.to_oid(), - DataType::Varchar.type_len(), - ), - PgFieldDescriptor::new( - "Description".to_string(), - DataType::Varchar.to_oid(), - DataType::Varchar.type_len(), - ), - ], - ) - .into()) + Ok(rows) } -async fn handle_show_system_params(handler_args: HandlerArgs) -> Result { +async fn handle_show_system_params(handler_args: HandlerArgs) -> Result> { let params = handler_args .session .env() @@ -175,27 +146,5 @@ async fn handle_show_system_params(handler_args: HandlerArgs) -> Result, stmt: Statement) -> Result { let name = &variable[0].real_value().to_lowercase(); - if name.eq_ignore_ascii_case("ALL") { - Ok(vec![ - PgFieldDescriptor::new( - "Name".to_string(), - DataType::Varchar.to_oid(), - DataType::Varchar.type_len(), - ), - PgFieldDescriptor::new( - "Setting".to_string(), - DataType::Varchar.to_oid(), - DataType::Varchar.type_len(), - ), - PgFieldDescriptor::new( - "Description".to_string(), - DataType::Varchar.to_oid(), - DataType::Varchar.type_len(), - ), - ]) - } else { - Ok(vec![PgFieldDescriptor::new( - name.to_ascii_lowercase(), - DataType::Varchar.to_oid(), - DataType::Varchar.type_len(), - )]) - } + Ok(infer_show_variable(name)) } Statement::Describe { name: _ } => Ok(vec![ PgFieldDescriptor::new( diff --git a/src/frontend/src/utils/infer_stmt_row_desc.rs b/src/frontend/src/utils/infer_stmt_row_desc.rs index dbe8968f0a293..8ebb7ac5c7d7a 100644 --- a/src/frontend/src/utils/infer_stmt_row_desc.rs +++ b/src/frontend/src/utils/infer_stmt_row_desc.rs @@ -168,3 +168,49 @@ pub fn infer_show_object(objects: &ShowObject) -> Vec { )], } } + +pub fn infer_show_variable(name: &str) -> Vec { + if name.eq_ignore_ascii_case("ALL") { + vec![ + PgFieldDescriptor::new( + "Name".to_string(), + DataType::Varchar.to_oid(), + DataType::Varchar.type_len(), + ), + PgFieldDescriptor::new( + "Setting".to_string(), + DataType::Varchar.to_oid(), + DataType::Varchar.type_len(), + ), + PgFieldDescriptor::new( + "Description".to_string(), + DataType::Varchar.to_oid(), + DataType::Varchar.type_len(), + ), + ] + } else if name.eq_ignore_ascii_case("PARAMETERS") { + vec![ + PgFieldDescriptor::new( + "Name".to_string(), + DataType::Varchar.to_oid(), + DataType::Varchar.type_len(), + ), + PgFieldDescriptor::new( + "Value".to_string(), + DataType::Varchar.to_oid(), + DataType::Varchar.type_len(), + ), + PgFieldDescriptor::new( + "Mutable".to_string(), + DataType::Boolean.to_oid(), + DataType::Boolean.type_len(), + ), + ] + } else { + vec![PgFieldDescriptor::new( + name.to_ascii_lowercase(), + DataType::Varchar.to_oid(), + DataType::Varchar.type_len(), + )] + } +} From e392db0c6f468caf89597294a94d4e4134c61eaf Mon Sep 17 00:00:00 2001 From: Runji Wang Date: Mon, 30 Oct 2023 13:31:35 +0800 Subject: [PATCH 9/9] feat(expr): support `#>` and `#>>` operator for extracting jsonb at a path (#13110) Signed-off-by: Runji Wang --- proto/expr.proto | 10 +- src/common/src/types/jsonb.rs | 10 ++ src/expr/impl/src/scalar/jsonb_access.rs | 142 +++++++++++++++++- .../tests/testdata/output/cse_expr.yaml | 12 +- src/frontend/src/binder/expr/binary_op.rs | 4 +- src/frontend/src/binder/expr/function.rs | 4 +- src/frontend/src/expr/pure.rs | 4 +- src/tests/regress/data/sql/jsonb.sql | 132 ++++++++-------- 8 files changed, 236 insertions(+), 82 deletions(-) diff --git a/proto/expr.proto b/proto/expr.proto index cab83e0ea45ce..fecefc12b4ee7 100644 --- a/proto/expr.proto +++ b/proto/expr.proto @@ -212,10 +212,14 @@ message ExprNode { // Jsonb functions - // jsonb -> int, jsonb -> text, jsonb #> text[] that returns jsonb - JSONB_ACCESS_INNER = 600; - // jsonb ->> int, jsonb ->> text, jsonb #>> text[] that returns text + // jsonb -> int, jsonb -> text that returns jsonb + JSONB_ACCESS = 600; + // jsonb ->> int, jsonb ->> text that returns text JSONB_ACCESS_STR = 601; + // jsonb #> text[] -> jsonb + JSONB_ACCESS_MULTI = 613; + // jsonb #>> text[] -> text + JSONB_ACCESS_MULTI_STR = 614; JSONB_TYPEOF = 602; JSONB_ARRAY_LENGTH = 603; IS_JSON = 604; diff --git a/src/common/src/types/jsonb.rs b/src/common/src/types/jsonb.rs index be708ac9013a8..664af6c0b1921 100644 --- a/src/common/src/types/jsonb.rs +++ b/src/common/src/types/jsonb.rs @@ -293,6 +293,16 @@ impl<'a> JsonbRef<'a> { self.0.as_null().is_some() } + /// Returns true if this is a jsonb array. + pub fn is_array(&self) -> bool { + matches!(self.0, ValueRef::Array(_)) + } + + /// Returns true if this is a jsonb object. + pub fn is_object(&self) -> bool { + matches!(self.0, ValueRef::Object(_)) + } + /// Returns the type name of this jsonb. /// /// Possible values are: `null`, `boolean`, `number`, `string`, `array`, `object`. diff --git a/src/expr/impl/src/scalar/jsonb_access.rs b/src/expr/impl/src/scalar/jsonb_access.rs index 8115c1d7214ab..08e36bedf83cc 100644 --- a/src/expr/impl/src/scalar/jsonb_access.rs +++ b/src/expr/impl/src/scalar/jsonb_access.rs @@ -14,15 +14,45 @@ use std::fmt::Write; -use risingwave_common::types::JsonbRef; +use risingwave_common::types::{JsonbRef, ListRef}; use risingwave_expr::function; -#[function("jsonb_access_inner(jsonb, varchar) -> jsonb")] +/// Extracts JSON object field with the given key. +/// +/// `jsonb -> text → jsonb` +/// +/// # Examples +/// +/// ```slt +/// query T +/// select '{"a": {"b":"foo"}}'::jsonb -> 'a'; +/// ---- +/// {"b": "foo"} +/// ``` +#[function("jsonb_access(jsonb, varchar) -> jsonb")] pub fn jsonb_object_field<'a>(v: JsonbRef<'a>, p: &str) -> Option> { v.access_object_field(p) } -#[function("jsonb_access_inner(jsonb, int4) -> jsonb")] +/// Extracts n'th element of JSON array (array elements are indexed from zero, +/// but negative integers count from the end). +/// +/// `jsonb -> integer → jsonb` +/// +/// # Examples +/// +/// ```slt +/// query T +/// select '[{"a":"foo"},{"b":"bar"},{"c":"baz"}]'::jsonb -> 2; +/// ---- +/// {"c": "baz"} +/// +/// query T +/// select '[{"a":"foo"},{"b":"bar"},{"c":"baz"}]'::jsonb -> -3; +/// ---- +/// {"a": "foo"} +/// ``` +#[function("jsonb_access(jsonb, int4) -> jsonb")] pub fn jsonb_array_element(v: JsonbRef<'_>, p: i32) -> Option> { let idx = if p < 0 { let Ok(len) = v.array_len() else { @@ -39,6 +69,59 @@ pub fn jsonb_array_element(v: JsonbRef<'_>, p: i32) -> Option> { v.access_array_element(idx) } +/// Extracts JSON sub-object at the specified path, where path elements can be either field keys or array indexes. +/// +/// `jsonb #> text[] → jsonb` +/// +/// # Examples +/// +/// ```slt +/// query T +/// select '{"a": {"b": ["foo","bar"]}}'::jsonb #> '{a,b,1}'::text[]; +/// ---- +/// "bar" +/// +/// query T +/// select '{"a": {"b": ["foo","bar"]}}'::jsonb #> '{a,b,null}'::text[]; +/// ---- +/// NULL +/// ``` +#[function("jsonb_access_multi(jsonb, varchar[]) -> jsonb")] +pub fn jsonb_access_multi<'a>(v: JsonbRef<'a>, path: ListRef<'_>) -> Option> { + let mut jsonb = v; + for key in path.iter() { + // return null if any element is null + let key = key?.into_utf8(); + if jsonb.is_array() { + // return null if the key is not an integer + let idx = key.parse().ok()?; + jsonb = jsonb_array_element(jsonb, idx)?; + } else if jsonb.is_object() { + jsonb = jsonb_object_field(jsonb, key)?; + } else { + return None; + } + } + Some(jsonb) +} + +/// Extracts JSON object field with the given key, as text. +/// +/// `jsonb ->> text → text` +/// +/// # Examples +/// +/// ```slt +/// query T +/// select '{"a":1,"b":2}'::jsonb ->> 'b'; +/// ---- +/// 2 +/// +/// query T +/// select '{"a":1,"b":null}'::jsonb ->> 'b'; +/// ---- +/// NULL +/// ``` #[function("jsonb_access_str(jsonb, varchar) -> varchar")] pub fn jsonb_object_field_str(v: JsonbRef<'_>, p: &str, writer: &mut impl Write) -> Option<()> { let jsonb = jsonb_object_field(v, p)?; @@ -49,6 +132,23 @@ pub fn jsonb_object_field_str(v: JsonbRef<'_>, p: &str, writer: &mut impl Write) Some(()) } +/// Extracts n'th element of JSON array, as text. +/// +/// `jsonb ->> integer → text` +/// +/// # Examples +/// +/// ```slt +/// query T +/// select '[1,2,3]'::jsonb ->> 2; +/// ---- +/// 3 +/// +/// query T +/// select '[1,2,null]'::jsonb ->> 2; +/// ---- +/// NULL +/// ``` #[function("jsonb_access_str(jsonb, int4) -> varchar")] pub fn jsonb_array_element_str(v: JsonbRef<'_>, p: i32, writer: &mut impl Write) -> Option<()> { let jsonb = jsonb_array_element(v, p)?; @@ -58,3 +158,39 @@ pub fn jsonb_array_element_str(v: JsonbRef<'_>, p: i32, writer: &mut impl Write) jsonb.force_str(writer).unwrap(); Some(()) } + +/// Extracts JSON sub-object at the specified path as text. +/// +/// `jsonb #>> text[] → text` +/// +/// # Examples +/// +/// ```slt +/// query T +/// select '{"a": {"b": ["foo","bar"]}}'::jsonb #>> '{a,b,1}'::text[]; +/// ---- +/// bar +/// +/// query T +/// select '{"a": {"b": ["foo",null]}}'::jsonb #>> '{a,b,1}'::text[]; +/// ---- +/// NULL +/// +/// query T +/// select '{"a": {"b": ["foo","bar"]}}'::jsonb #>> '{a,b,null}'::text[]; +/// ---- +/// NULL +/// ``` +#[function("jsonb_access_multi_str(jsonb, varchar[]) -> varchar")] +pub fn jsonb_access_multi_str( + v: JsonbRef<'_>, + path: ListRef<'_>, + writer: &mut impl Write, +) -> Option<()> { + let jsonb = jsonb_access_multi(v, path)?; + if jsonb.is_jsonb_null() { + return None; + } + jsonb.force_str(writer).unwrap(); + Some(()) +} diff --git a/src/frontend/planner_test/tests/testdata/output/cse_expr.yaml b/src/frontend/planner_test/tests/testdata/output/cse_expr.yaml index f54f6a837343f..09e0e7872e7c7 100644 --- a/src/frontend/planner_test/tests/testdata/output/cse_expr.yaml +++ b/src/frontend/planner_test/tests/testdata/output/cse_expr.yaml @@ -5,13 +5,13 @@ select v1->'a'->'c' x, v1->'a'->'b' y from t; batch_plan: |- BatchExchange { order: [], dist: Single } - └─BatchProject { exprs: [JsonbAccessInner($expr1, 'c':Varchar) as $expr2, JsonbAccessInner($expr1, 'b':Varchar) as $expr3] } - └─BatchProject { exprs: [t.v1, JsonbAccessInner(t.v1, 'a':Varchar) as $expr1] } + └─BatchProject { exprs: [JsonbAccess($expr1, 'c':Varchar) as $expr2, JsonbAccess($expr1, 'b':Varchar) as $expr3] } + └─BatchProject { exprs: [t.v1, JsonbAccess(t.v1, 'a':Varchar) as $expr1] } └─BatchScan { table: t, columns: [t.v1], distribution: SomeShard } stream_plan: |- StreamMaterialize { columns: [x, y, t._row_id(hidden)], stream_key: [t._row_id], pk_columns: [t._row_id], pk_conflict: NoCheck } - └─StreamProject { exprs: [JsonbAccessInner($expr1, 'c':Varchar) as $expr2, JsonbAccessInner($expr1, 'b':Varchar) as $expr3, t._row_id] } - └─StreamProject { exprs: [t.v1, JsonbAccessInner(t.v1, 'a':Varchar) as $expr1, t._row_id] } + └─StreamProject { exprs: [JsonbAccess($expr1, 'c':Varchar) as $expr2, JsonbAccess($expr1, 'b':Varchar) as $expr3, t._row_id] } + └─StreamProject { exprs: [t.v1, JsonbAccess(t.v1, 'a':Varchar) as $expr1, t._row_id] } └─StreamTableScan { table: t, columns: [t.v1, t._row_id], pk: [t._row_id], dist: UpstreamHashShard(t._row_id) } - name: Common sub expression extract2 sql: | @@ -20,12 +20,12 @@ batch_plan: |- BatchExchange { order: [], dist: Single } └─BatchProject { exprs: [$expr1, $expr1] } - └─BatchProject { exprs: [t.v1, JsonbAccessInner(JsonbAccessInner(t.v1, 'a':Varchar), 'c':Varchar) as $expr1] } + └─BatchProject { exprs: [t.v1, JsonbAccess(JsonbAccess(t.v1, 'a':Varchar), 'c':Varchar) as $expr1] } └─BatchScan { table: t, columns: [t.v1], distribution: SomeShard } stream_plan: |- StreamMaterialize { columns: [x, y, t._row_id(hidden)], stream_key: [t._row_id], pk_columns: [t._row_id], pk_conflict: NoCheck } └─StreamProject { exprs: [$expr1, $expr1, t._row_id] } - └─StreamProject { exprs: [t.v1, JsonbAccessInner(JsonbAccessInner(t.v1, 'a':Varchar), 'c':Varchar) as $expr1, t._row_id] } + └─StreamProject { exprs: [t.v1, JsonbAccess(JsonbAccess(t.v1, 'a':Varchar), 'c':Varchar) as $expr1, t._row_id] } └─StreamTableScan { table: t, columns: [t.v1, t._row_id], pk: [t._row_id], dist: UpstreamHashShard(t._row_id) } - name: Common sub expression shouldn't extract impure function sql: | diff --git a/src/frontend/src/binder/expr/binary_op.rs b/src/frontend/src/binder/expr/binary_op.rs index bd85089b18cce..352d2bfbfd246 100644 --- a/src/frontend/src/binder/expr/binary_op.rs +++ b/src/frontend/src/binder/expr/binary_op.rs @@ -89,8 +89,10 @@ impl Binder { BinaryOperator::PGBitwiseXor => ExprType::BitwiseXor, BinaryOperator::PGBitwiseShiftLeft => ExprType::BitwiseShiftLeft, BinaryOperator::PGBitwiseShiftRight => ExprType::BitwiseShiftRight, - BinaryOperator::Arrow => ExprType::JsonbAccessInner, + BinaryOperator::Arrow => ExprType::JsonbAccess, BinaryOperator::LongArrow => ExprType::JsonbAccessStr, + BinaryOperator::HashArrow => ExprType::JsonbAccessMulti, + BinaryOperator::HashLongArrow => ExprType::JsonbAccessMultiStr, 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 7ca509e6e3af9..6dfab2c3bc283 100644 --- a/src/frontend/src/binder/expr/function.rs +++ b/src/frontend/src/binder/expr/function.rs @@ -871,8 +871,8 @@ impl Binder { // int256 ("hex_to_int256", raw_call(ExprType::HexToInt256)), // jsonb - ("jsonb_object_field", raw_call(ExprType::JsonbAccessInner)), - ("jsonb_array_element", raw_call(ExprType::JsonbAccessInner)), + ("jsonb_object_field", raw_call(ExprType::JsonbAccess)), + ("jsonb_array_element", raw_call(ExprType::JsonbAccess)), ("jsonb_object_field_text", raw_call(ExprType::JsonbAccessStr)), ("jsonb_array_element_text", raw_call(ExprType::JsonbAccessStr)), ("jsonb_typeof", raw_call(ExprType::JsonbTypeof)), diff --git a/src/frontend/src/expr/pure.rs b/src/frontend/src/expr/pure.rs index 038bd94ffcf07..7a63c7f95ae99 100644 --- a/src/frontend/src/expr/pure.rs +++ b/src/frontend/src/expr/pure.rs @@ -173,8 +173,10 @@ impl ExprVisitor for ImpureAnalyzer { | expr_node::Type::ArrayPosition | expr_node::Type::HexToInt256 | expr_node::Type::JsonbCat - | expr_node::Type::JsonbAccessInner + | expr_node::Type::JsonbAccess | expr_node::Type::JsonbAccessStr + | expr_node::Type::JsonbAccessMulti + | expr_node::Type::JsonbAccessMultiStr | expr_node::Type::JsonbTypeof | expr_node::Type::JsonbArrayLength | expr_node::Type::JsonbObject diff --git a/src/tests/regress/data/sql/jsonb.sql b/src/tests/regress/data/sql/jsonb.sql index 26c25af897bf4..69bd9a928a0f5 100644 --- a/src/tests/regress/data/sql/jsonb.sql +++ b/src/tests/regress/data/sql/jsonb.sql @@ -465,58 +465,58 @@ SELECT jsonb_typeof('"1.0"') AS string; --@ 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']; ---@ SELECT '{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}'::jsonb#>array['f2']; ---@ SELECT '{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}'::jsonb#>array['f2','0']; ---@ SELECT '{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}'::jsonb#>array['f2','1']; ---@ ---@ SELECT '{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}'::jsonb#>>array['f4','f6']; ---@ SELECT '{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}'::jsonb#>>array['f2']; ---@ SELECT '{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}'::jsonb#>>array['f2','0']; ---@ SELECT '{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}'::jsonb#>>array['f2','1']; ---@ +SELECT '{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}'::jsonb#>array['f4','f6']; +SELECT '{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}'::jsonb#>array['f2']; +SELECT '{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}'::jsonb#>array['f2','0']; +SELECT '{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}'::jsonb#>array['f2','1']; + +SELECT '{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}'::jsonb#>>array['f4','f6']; +SELECT '{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}'::jsonb#>>array['f2']; +SELECT '{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}'::jsonb#>>array['f2','0']; +SELECT '{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}'::jsonb#>>array['f2','1']; + --@ -- corner cases for same ---@ select '{"a": {"b":{"c": "foo"}}}'::jsonb #> '{}'; ---@ select '[1,2,3]'::jsonb #> '{}'; ---@ select '"foo"'::jsonb #> '{}'; ---@ select '42'::jsonb #> '{}'; ---@ select 'null'::jsonb #> '{}'; ---@ select '{"a": {"b":{"c": "foo"}}}'::jsonb #> array['a']; ---@ select '{"a": {"b":{"c": "foo"}}}'::jsonb #> array['a', null]; ---@ select '{"a": {"b":{"c": "foo"}}}'::jsonb #> array['a', '']; ---@ select '{"a": {"b":{"c": "foo"}}}'::jsonb #> array['a','b']; ---@ select '{"a": {"b":{"c": "foo"}}}'::jsonb #> array['a','b','c']; ---@ select '{"a": {"b":{"c": "foo"}}}'::jsonb #> array['a','b','c','d']; ---@ select '{"a": {"b":{"c": "foo"}}}'::jsonb #> array['a','z','c']; ---@ select '{"a": [{"b": "c"}, {"b": "cc"}]}'::jsonb #> array['a','1','b']; ---@ select '{"a": [{"b": "c"}, {"b": "cc"}]}'::jsonb #> array['a','z','b']; ---@ select '[{"b": "c"}, {"b": "cc"}]'::jsonb #> array['1','b']; ---@ select '[{"b": "c"}, {"b": "cc"}]'::jsonb #> array['z','b']; ---@ select '[{"b": "c"}, {"b": null}]'::jsonb #> array['1','b']; ---@ select '"foo"'::jsonb #> array['z']; ---@ select '42'::jsonb #> array['f2']; ---@ select '42'::jsonb #> array['0']; ---@ ---@ select '{"a": {"b":{"c": "foo"}}}'::jsonb #>> '{}'; ---@ select '[1,2,3]'::jsonb #>> '{}'; ---@ select '"foo"'::jsonb #>> '{}'; ---@ select '42'::jsonb #>> '{}'; ---@ select 'null'::jsonb #>> '{}'; ---@ select '{"a": {"b":{"c": "foo"}}}'::jsonb #>> array['a']; ---@ select '{"a": {"b":{"c": "foo"}}}'::jsonb #>> array['a', null]; ---@ select '{"a": {"b":{"c": "foo"}}}'::jsonb #>> array['a', '']; ---@ select '{"a": {"b":{"c": "foo"}}}'::jsonb #>> array['a','b']; ---@ select '{"a": {"b":{"c": "foo"}}}'::jsonb #>> array['a','b','c']; ---@ select '{"a": {"b":{"c": "foo"}}}'::jsonb #>> array['a','b','c','d']; ---@ select '{"a": {"b":{"c": "foo"}}}'::jsonb #>> array['a','z','c']; ---@ select '{"a": [{"b": "c"}, {"b": "cc"}]}'::jsonb #>> array['a','1','b']; ---@ select '{"a": [{"b": "c"}, {"b": "cc"}]}'::jsonb #>> array['a','z','b']; ---@ select '[{"b": "c"}, {"b": "cc"}]'::jsonb #>> array['1','b']; ---@ select '[{"b": "c"}, {"b": "cc"}]'::jsonb #>> array['z','b']; ---@ select '[{"b": "c"}, {"b": null}]'::jsonb #>> array['1','b']; ---@ select '"foo"'::jsonb #>> array['z']; ---@ select '42'::jsonb #>> array['f2']; ---@ select '42'::jsonb #>> array['0']; +select '{"a": {"b":{"c": "foo"}}}'::jsonb #> '{}'; +select '[1,2,3]'::jsonb #> '{}'; +select '"foo"'::jsonb #> '{}'; +select '42'::jsonb #> '{}'; +select 'null'::jsonb #> '{}'; +select '{"a": {"b":{"c": "foo"}}}'::jsonb #> array['a']; +select '{"a": {"b":{"c": "foo"}}}'::jsonb #> array['a', null]; +select '{"a": {"b":{"c": "foo"}}}'::jsonb #> array['a', '']; +select '{"a": {"b":{"c": "foo"}}}'::jsonb #> array['a','b']; +select '{"a": {"b":{"c": "foo"}}}'::jsonb #> array['a','b','c']; +select '{"a": {"b":{"c": "foo"}}}'::jsonb #> array['a','b','c','d']; +select '{"a": {"b":{"c": "foo"}}}'::jsonb #> array['a','z','c']; +select '{"a": [{"b": "c"}, {"b": "cc"}]}'::jsonb #> array['a','1','b']; +select '{"a": [{"b": "c"}, {"b": "cc"}]}'::jsonb #> array['a','z','b']; +select '[{"b": "c"}, {"b": "cc"}]'::jsonb #> array['1','b']; +select '[{"b": "c"}, {"b": "cc"}]'::jsonb #> array['z','b']; +select '[{"b": "c"}, {"b": null}]'::jsonb #> array['1','b']; +select '"foo"'::jsonb #> array['z']; +select '42'::jsonb #> array['f2']; +select '42'::jsonb #> array['0']; + +select '{"a": {"b":{"c": "foo"}}}'::jsonb #>> '{}'; +select '[1,2,3]'::jsonb #>> '{}'; +select '"foo"'::jsonb #>> '{}'; +select '42'::jsonb #>> '{}'; +select 'null'::jsonb #>> '{}'; +select '{"a": {"b":{"c": "foo"}}}'::jsonb #>> array['a']; +select '{"a": {"b":{"c": "foo"}}}'::jsonb #>> array['a', null]; +select '{"a": {"b":{"c": "foo"}}}'::jsonb #>> array['a', '']; +select '{"a": {"b":{"c": "foo"}}}'::jsonb #>> array['a','b']; +select '{"a": {"b":{"c": "foo"}}}'::jsonb #>> array['a','b','c']; +select '{"a": {"b":{"c": "foo"}}}'::jsonb #>> array['a','b','c','d']; +select '{"a": {"b":{"c": "foo"}}}'::jsonb #>> array['a','z','c']; +select '{"a": [{"b": "c"}, {"b": "cc"}]}'::jsonb #>> array['a','1','b']; +select '{"a": [{"b": "c"}, {"b": "cc"}]}'::jsonb #>> array['a','z','b']; +select '[{"b": "c"}, {"b": "cc"}]'::jsonb #>> array['1','b']; +select '[{"b": "c"}, {"b": "cc"}]'::jsonb #>> array['z','b']; +select '[{"b": "c"}, {"b": null}]'::jsonb #>> array['1','b']; +select '"foo"'::jsonb #>> array['z']; +select '42'::jsonb #>> array['f2']; +select '42'::jsonb #>> array['0']; -- array_elements --@ SELECT jsonb_array_elements('[1,true,[1,[2,3]],null,{"f1":1,"f2":[7,8,9]},false]'); @@ -1003,21 +1003,21 @@ SELECT '["a","b","c",[1,2],null]'::jsonb -> -5; SELECT '["a","b","c",[1,2],null]'::jsonb -> -6; --nested path extraction ---@ SELECT '{"a":"b","c":[1,2,3]}'::jsonb #> '{0}'; ---@ SELECT '{"a":"b","c":[1,2,3]}'::jsonb #> '{a}'; ---@ SELECT '{"a":"b","c":[1,2,3]}'::jsonb #> '{c}'; ---@ SELECT '{"a":"b","c":[1,2,3]}'::jsonb #> '{c,0}'; ---@ SELECT '{"a":"b","c":[1,2,3]}'::jsonb #> '{c,1}'; ---@ SELECT '{"a":"b","c":[1,2,3]}'::jsonb #> '{c,2}'; ---@ SELECT '{"a":"b","c":[1,2,3]}'::jsonb #> '{c,3}'; ---@ SELECT '{"a":"b","c":[1,2,3]}'::jsonb #> '{c,-1}'; ---@ SELECT '{"a":"b","c":[1,2,3]}'::jsonb #> '{c,-3}'; ---@ SELECT '{"a":"b","c":[1,2,3]}'::jsonb #> '{c,-4}'; ---@ ---@ SELECT '[0,1,2,[3,4],{"5":"five"}]'::jsonb #> '{0}'; ---@ SELECT '[0,1,2,[3,4],{"5":"five"}]'::jsonb #> '{3}'; ---@ SELECT '[0,1,2,[3,4],{"5":"five"}]'::jsonb #> '{4}'; ---@ SELECT '[0,1,2,[3,4],{"5":"five"}]'::jsonb #> '{4,5}'; +SELECT '{"a":"b","c":[1,2,3]}'::jsonb #> '{0}'; +SELECT '{"a":"b","c":[1,2,3]}'::jsonb #> '{a}'; +SELECT '{"a":"b","c":[1,2,3]}'::jsonb #> '{c}'; +SELECT '{"a":"b","c":[1,2,3]}'::jsonb #> '{c,0}'; +SELECT '{"a":"b","c":[1,2,3]}'::jsonb #> '{c,1}'; +SELECT '{"a":"b","c":[1,2,3]}'::jsonb #> '{c,2}'; +SELECT '{"a":"b","c":[1,2,3]}'::jsonb #> '{c,3}'; +SELECT '{"a":"b","c":[1,2,3]}'::jsonb #> '{c,-1}'; +SELECT '{"a":"b","c":[1,2,3]}'::jsonb #> '{c,-3}'; +SELECT '{"a":"b","c":[1,2,3]}'::jsonb #> '{c,-4}'; + +SELECT '[0,1,2,[3,4],{"5":"five"}]'::jsonb #> '{0}'; +SELECT '[0,1,2,[3,4],{"5":"five"}]'::jsonb #> '{3}'; +SELECT '[0,1,2,[3,4],{"5":"five"}]'::jsonb #> '{4}'; +SELECT '[0,1,2,[3,4],{"5":"five"}]'::jsonb #> '{4,5}'; --nested exists SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::jsonb ? 'n';