diff --git a/src/materialized/tests/pgwire.rs b/src/materialized/tests/pgwire.rs index 10a756e0f2194..14da8491a9b80 100644 --- a/src/materialized/tests/pgwire.rs +++ b/src/materialized/tests/pgwire.rs @@ -252,8 +252,8 @@ fn test_persistence() -> Result<(), Box> { &[ ("@1".into(), 1), ("@2".into(), 2), + ("@4".into(), 4), ("c".into(), 3), - ("@4".into(), 4) ], ); assert_eq!( diff --git a/src/sql/query.rs b/src/sql/query.rs index cff0a64a7bb94..476e2963f02c7 100644 --- a/src/sql/query.rs +++ b/src/sql/query.rs @@ -142,7 +142,12 @@ pub fn plan_show_where( Ok(( row_expr, RowSetFinishing { - order_by: vec![], + order_by: (0..num_cols) + .map(|c| ColumnOrder { + column: c, + desc: false, + }) + .collect(), limit: None, offset: 0, project: (0..num_cols).collect(), diff --git a/src/sql/statement.rs b/src/sql/statement.rs index 24f13c588623c..24b82f5ddbda8 100644 --- a/src/sql/statement.rs +++ b/src/sql/statement.rs @@ -50,6 +50,52 @@ use tokio::io::AsyncBufReadExt; lazy_static! { static ref SHOW_DATABASES_DESC: RelationDesc = { RelationDesc::empty().add_column("Database", ScalarType::String) }; + static ref SHOW_INDEXES_DESC: RelationDesc = RelationDesc::new( + RelationType::new(vec![ + ColumnType::new(ScalarType::String), + ColumnType::new(ScalarType::String), + ColumnType::new(ScalarType::String).nullable(true), + ColumnType::new(ScalarType::String).nullable(true), + ColumnType::new(ScalarType::Bool), + ColumnType::new(ScalarType::Int64), + ]), + vec![ + "Source_or_view", + "Key_name", + "Column_name", + "Expression", + "Null", + "Seq_in_index", + ] + .into_iter() + .map(Some), + ); + static ref SHOW_COLUMNS_DESC: RelationDesc = RelationDesc::empty() + .add_column("Field", ScalarType::String) + .add_column("Nullable", ScalarType::String) + .add_column("Type", ScalarType::String); +} + +pub fn make_show_objects_desc( + object_type: ObjectType, + materialized: bool, + full: bool, +) -> RelationDesc { + let col_name = object_type_as_plural_str(object_type); + if full { + let mut relation_desc = RelationDesc::empty() + .add_column(col_name, ScalarType::String) + .add_column("TYPE", ScalarType::String); + if ObjectType::View == object_type { + relation_desc = relation_desc.add_column("QUERYABLE", ScalarType::Bool); + } + if !materialized && (ObjectType::View == object_type || ObjectType::Source == object_type) { + relation_desc = relation_desc.add_column("MATERIALIZED", ScalarType::Bool); + } + relation_desc + } else { + RelationDesc::empty().add_column(col_name, ScalarType::String) + } } pub fn describe_statement( @@ -124,40 +170,9 @@ pub fn describe_statement( vec![], ), - Statement::ShowColumns { .. } => ( - Some( - RelationDesc::empty() - .add_column("Field", ScalarType::String) - .add_column("Nullable", ScalarType::String) - .add_column("Type", ScalarType::String), - ), - vec![], - ), + Statement::ShowColumns { .. } => (Some(SHOW_COLUMNS_DESC.clone()), vec![]), - Statement::ShowIndexes { .. } => ( - Some(RelationDesc::new( - RelationType::new(vec![ - ColumnType::new(ScalarType::String), - ColumnType::new(ScalarType::String), - ColumnType::new(ScalarType::String).nullable(true), - ColumnType::new(ScalarType::String).nullable(true), - ColumnType::new(ScalarType::Bool), - ColumnType::new(ScalarType::Int64), - ]), - vec![ - "Source_or_view", - "Key_name", - "Column_name", - "Expression", - "Null", - "Seq_in_index", - ] - .iter() - .map(|s| Some(*s)) - .collect::>(), - )), - vec![], - ), + Statement::ShowIndexes { .. } => (Some(SHOW_INDEXES_DESC.clone()), vec![]), Statement::ShowDatabases { .. } => (Some(SHOW_DATABASES_DESC.clone()), vec![]), @@ -166,28 +181,10 @@ pub fn describe_statement( full, materialized, .. - } => { - let col_name = object_type_as_plural_str(object_type); - ( - Some(if full { - let mut relation_desc = RelationDesc::empty() - .add_column(col_name, ScalarType::String) - .add_column("TYPE", ScalarType::String); - if ObjectType::View == object_type { - relation_desc = relation_desc.add_column("QUERYABLE", ScalarType::Bool); - } - if !materialized - && (ObjectType::View == object_type || ObjectType::Source == object_type) - { - relation_desc = relation_desc.add_column("MATERIALIZED", ScalarType::Bool); - } - relation_desc - } else { - RelationDesc::empty().add_column(col_name, ScalarType::String) - }), - vec![], - ) - } + } => ( + Some(make_show_objects_desc(object_type, materialized, full)), + vec![], + ), Statement::ShowVariable { variable, .. } => { if variable.value == unicase::Ascii::new("ALL") { ( @@ -281,7 +278,7 @@ pub fn handle_statement( from, materialized, filter, - } => handle_show_objects(scx, extended, full, materialized, ot, from, filter), + } => handle_show_objects(scx, extended, full, materialized, ot, from, filter.as_ref()), Statement::ShowIndexes { extended, table_name, @@ -346,17 +343,13 @@ fn handle_tail(scx: &StatementContext, from: ObjectName) -> Result, + rows: Vec>, + desc: &RelationDesc, ) -> Result { - let rows = scx - .catalog - .databases() - .map(|database| vec![Datum::from(database)]) - .collect(); - - let (r, finishing) = query::plan_show_where(scx, filter, rows, &SHOW_DATABASES_DESC)?; + let (r, finishing) = query::plan_show_where(scx, filter, rows, desc)?; Ok(Plan::Peek { source: r.decorrelate()?, @@ -366,6 +359,19 @@ fn handle_show_databases( }) } +fn handle_show_databases( + scx: &StatementContext, + filter: Option<&ShowStatementFilter>, +) -> Result { + let rows = scx + .catalog + .databases() + .map(|database| vec![Datum::from(database)]) + .collect(); + + finish_show_where(scx, filter, rows, &SHOW_DATABASES_DESC) +} + fn handle_show_objects( scx: &StatementContext, extended: bool, @@ -373,25 +379,25 @@ fn handle_show_objects( materialized: bool, object_type: ObjectType, from: Option, - filter: Option, + filter: Option<&ShowStatementFilter>, ) -> Result { let classify_id = |id| match id { GlobalId::System(_) => "SYSTEM", GlobalId::User(_) => "USER", }; - let make_row = |name: &str, class| { + let arena = RowArena::new(); + let make_row = |name: &str, class: &str| { if full { - Row::pack(&[Datum::from(name), Datum::from(class)]) + vec![ + Datum::from(arena.push_string(name.to_string())), + Datum::from(arena.push_string(class.to_string())), + ] } else { - Row::pack(&[Datum::from(name)]) + vec![Datum::from(arena.push_string(name.to_string()))] } }; if let ObjectType::Schema = object_type { - if filter.is_some() { - bail!("SHOW SCHEMAS ... {LIKE | WHERE} is not supported"); - } - let schemas = if let Some(from) = from { if from.0.len() != 1 { bail!( @@ -427,15 +433,15 @@ fn handle_show_objects( rows.push(make_row(name, "SYSTEM")); } } - rows.sort_unstable_by(move |a, b| a.unpack_first().cmp(&b.unpack_first())); - Ok(Plan::SendRows(rows)) + // TODO(justin): it's unfortunate that we call make_show_objects_desc twice, I think we + // should be able to restructure this so that it only gets called once. + finish_show_where( + scx, + filter, + rows, + &make_show_objects_desc(object_type, materialized, full), + ) } else { - let like_regex = match filter { - Some(ShowStatementFilter::Like(pattern)) => like_pattern::build_regex(&pattern)?, - Some(ShowStatementFilter::Where(_)) => bail!("SHOW ... WHERE is not supported"), - None => like_pattern::build_regex("%")?, - }; - let empty_schema = BTreeMap::new(); let items = if let Some(mut from) = from { if from.0.len() > 2 { @@ -469,12 +475,20 @@ fn handle_show_objects( let filtered_items = items .iter() .map(|(name, id)| (name, scx.catalog.get_by_id(id))) - .filter(|(_name, entry)| { - object_type_matches(object_type, entry.item()) - && like_regex.is_match(&entry.name().to_string()) - }); + .filter(|(_name, entry)| object_type_matches(object_type, entry.item())); if object_type == ObjectType::View || object_type == ObjectType::Source { + // TODO(justin): we can't handle SHOW ... WHERE here yet because the coordinator adds + // extra columns to this result that we don't have access to here yet. This could be + // fixed by passing down this extra catalog info somehow. + let like_regex = match filter { + Some(ShowStatementFilter::Like(pattern)) => like_pattern::build_regex(&pattern)?, + Some(ShowStatementFilter::Where(_)) => bail!("SHOW ... WHERE is not supported"), + None => like_pattern::build_regex("%")?, + }; + + let filtered_items = filtered_items + .filter(|(_name, entry)| like_regex.is_match(&entry.name().to_string())); Ok(Plan::ShowViews { ids: filtered_items .map(|(name, entry)| (name.clone(), entry.id())) @@ -484,11 +498,16 @@ fn handle_show_objects( limit_materialized: materialized, }) } else { - let mut rows = filtered_items + let rows = filtered_items .map(|(name, entry)| make_row(name, classify_id(entry.id()))) .collect::>(); - rows.sort_unstable_by(move |a, b| a.unpack_first().cmp(&b.unpack_first())); - Ok(Plan::SendRows(rows)) + + finish_show_where( + scx, + filter, + rows, + &make_show_objects_desc(object_type, materialized, full), + ) } } } @@ -502,9 +521,6 @@ fn handle_show_indexes( if extended { bail!("SHOW EXTENDED INDEXES is not supported") } - if filter.is_some() { - bail!("SHOW INDEXES ... WHERE is not supported"); - } let from_name = scx.resolve_name(from_name)?; let from_entry = scx.catalog.get(&from_name)?; if !object_type_matches(ObjectType::View, from_entry.item()) @@ -516,6 +532,7 @@ fn handle_show_indexes( from_entry.item().type_string() ); } + let arena = RowArena::new(); let rows = scx .catalog .iter() @@ -541,7 +558,6 @@ fn handle_show_indexes( for (i, (key_expr, key_sql)) in keys.iter().zip_eq(key_sqls).enumerate() { let desc = scx.catalog.get_by_id(&on).desc().unwrap(); let key_sql = key_sql.to_string(); - let arena = RowArena::new(); let (col_name, func) = match key_expr { expr::ScalarExpr::Column(i) => { let col_name = match desc.get_unambiguous_name(*i) { @@ -552,21 +568,22 @@ fn handle_show_indexes( } _ => (Datum::Null, Datum::String(arena.push_string(key_sql))), }; - row_subset.push(Row::pack(&vec![ - Datum::String(&from_entry.name().to_string()), - Datum::String(&entry.name().to_string()), + row_subset.push(vec![ + Datum::String(arena.push_string(from_entry.name().to_string())), + Datum::String(arena.push_string(entry.name().to_string())), col_name, func, Datum::from(key_expr.typ(desc.typ()).nullable), Datum::from((i + 1) as i64), - ])); + ]); } row_subset } _ => unreachable!(), }) .collect(); - Ok(Plan::SendRows(rows)) + + finish_show_where(scx, filter, rows, &SHOW_INDEXES_DESC) } /// Create an immediate result that describes all the columns for the given table @@ -583,27 +600,25 @@ fn handle_show_columns( if full { bail!("SHOW FULL COLUMNS is not supported"); } - if filter.is_some() { - bail!("SHOW COLUMNS ... { LIKE | WHERE } is not supported"); - } + let arena = RowArena::new(); let table_name = scx.resolve_name(table_name)?; - let column_descriptions: Vec<_> = scx + let rows: Vec<_> = scx .catalog .get(&table_name)? .desc()? .iter() .map(|(name, typ)| { let name = name.map(|n| n.to_string()); - Row::pack(&[ - Datum::String(name.as_deref().unwrap_or("?")), + vec![ + Datum::String(name.map(|n| arena.push_string(n)).unwrap_or("?")), Datum::String(if typ.nullable { "YES" } else { "NO" }), Datum::String(pgrepr::Type::from(&typ.scalar_type).name()), - ]) + ] }) .collect(); - Ok(Plan::SendRows(column_descriptions)) + finish_show_where(scx, filter, rows, &SHOW_COLUMNS_DESC) } fn handle_show_create_view( diff --git a/test/sqllogictest/index.slt b/test/sqllogictest/index.slt index 554a984613e3c..125dc06fd48c6 100644 --- a/test/sqllogictest/index.slt +++ b/test/sqllogictest/index.slt @@ -38,19 +38,19 @@ query TTTTBI colnames SHOW INDEX IN bar ---- Source_or_view Key_name Column_name Expression Null Seq_in_index -materialize.public.bar materialize.public.bar_primary_idx z NULL false 1 materialize.public.bar materialize.public.bar_idx NULL substr("z",␠3) true 1 +materialize.public.bar materialize.public.bar_primary_idx z NULL false 1 query TTTTBI colnames SHOW INDEX FROM foo ---- Source_or_view Key_name Column_name Expression Null Seq_in_index +materialize.public.foo materialize.public.edge_columns NULL floor("c") true 2 +materialize.public.foo materialize.public.edge_columns a NULL false 1 +materialize.public.foo materialize.public.foo_idx NULL "a"␠+␠"c" true 1 materialize.public.foo materialize.public.foo_primary_idx a NULL false 1 materialize.public.foo materialize.public.foo_primary_idx b NULL true 2 materialize.public.foo materialize.public.foo_primary_idx c NULL true 3 -materialize.public.foo materialize.public.foo_idx NULL "a"␠+␠"c" true 1 -materialize.public.foo materialize.public.edge_columns a NULL false 1 -materialize.public.foo materialize.public.edge_columns NULL floor("c") true 2 statement ok DROP INDEX foo_idx diff --git a/test/sqllogictest/show.slt b/test/sqllogictest/show.slt index 469db1dce5d68..2d27d4014a8ab 100644 --- a/test/sqllogictest/show.slt +++ b/test/sqllogictest/show.slt @@ -45,10 +45,7 @@ statement ok INSERT INTO xyz VALUES (1, 2, 3), (4, 5, 6) ---- -query B -SELECT (EXISTS (SELECT * FROM xyz)) ----- -true +# SHOW DATABASES query T SHOW DATABASES WHERE (EXISTS (SELECT * FROM xyz)) @@ -63,3 +60,139 @@ materialize query T SHOW DATABASES WHERE (EXISTS (SELECT * FROM xyz WHERE x = 3)) ---- + +statement ok +CREATE MATERIALIZED VIEW v AS SELECT 1 AS a, 2 AS b, 3 AS c +---- + +statement ok +CREATE INDEX idx1 ON v (b) +---- + +# SHOW INDEXES + +query TTTTTT colnames +SHOW INDEXES FROM v +---- +Source_or_view Key_name Column_name Expression Null Seq_in_index +materialize.public.v materialize.public.idx1 b NULL false 1 + +query TTTTTT colnames +SHOW INDEXES FROM v WHERE "Column_name" = 'b' +---- +Source_or_view Key_name Column_name Expression Null Seq_in_index +materialize.public.v materialize.public.idx1 b NULL false 1 + +query TTTTTT colnames +SHOW INDEXES FROM v WHERE "Column_name" = 'c' +---- +Source_or_view Key_name Column_name Expression Null Seq_in_index + +# Reference a different column + +query TTTTTT colnames +SHOW INDEXES FROM v WHERE "Key_name" = 'materialize.public.idx1' +---- +Source_or_view Key_name Column_name Expression Null Seq_in_index +materialize.public.v materialize.public.idx1 b NULL false 1 + +# TODO(justin): not handled in parser yet: +# SHOW INDEXES FROM v LIKE '%v' + +query TTT colnames +SHOW COLUMNS FROM v +---- +Field Nullable Type +a NO int4 +b NO int4 +c NO int4 + +query TTT +SHOW COLUMNS FROM v LIKE 'b' +---- +b NO int4 + +query TTT +SHOW COLUMNS FROM v WHERE "Nullable" = 'NO' +---- +a NO int4 +b NO int4 +c NO int4 + +# SHOW SCHEMAS + +query T colnames +SHOW SCHEMAS +---- +SCHEMAS +public + +query T +SHOW SCHEMAS LIKE 'public' +---- +public + +query T +SHOW SCHEMAS LIKE 'private' +---- + +query T +SHOW SCHEMAS WHERE "SCHEMAS" = 'public' +---- +public + +# SHOW VIEWS/SOURCES + +statement ok +CREATE MATERIALIZED VIEW u AS SELECT 1 AS x, 2 AS y + +query T +SHOW VIEWS +---- +u +v + +query TTBB colnames +SHOW FULL VIEWS +---- +VIEWS TYPE QUERYABLE MATERIALIZED +u USER true true +v USER true true + +query T +SHOW VIEWS LIKE '%u' +---- +u + +statement error not supported +SHOW VIEWS WHERE "VIEWS" = 'u' + +query T +SHOW SOURCES +---- +xyz + +statement error not supported +SHOW SOURCES WHERE "SOURCES" = 'u' + +# SHOW OBJECTS + +query T colnames +SHOW TABLES +---- +TABLES +xyz + +query T +SHOW TABLES LIKE 'xyz' +---- +xyz + +query T +SHOW TABLES LIKE 'abc' +---- + +query T +SHOW TABLES WHERE "TABLES" = 'xyz' +---- +xyz