Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(expr): implement pg_get_keywords() and pg_keywords #17033

Merged
merged 4 commits into from
May 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions proto/expr.proto
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,7 @@ message TableFunction {
GENERATE_SUBSCRIPTS = 5;
// buf:lint:ignore ENUM_VALUE_UPPER_SNAKE_CASE
_PG_EXPANDARRAY = 6;
PG_GET_KEYWORDS = 18;
// Jsonb functions
JSONB_ARRAY_ELEMENTS = 10;
JSONB_ARRAY_ELEMENTS_TEXT = 11;
Expand Down
3 changes: 2 additions & 1 deletion src/expr/core/src/codegen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@
// See the License for the specific language governing permissions and
// limitations under the License.

//! This module contains imports that are used in the generated code for the `#[function]` macro.

pub use async_trait::async_trait;
pub use futures_async_stream::try_stream;
pub use futures_util::stream::BoxStream;
pub use itertools::multizip;
pub use linkme;
1 change: 1 addition & 0 deletions src/expr/impl/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ risingwave_common = { workspace = true }
risingwave_common_estimate_size = { workspace = true }
risingwave_expr = { workspace = true }
risingwave_pb = { workspace = true }
risingwave_sqlparser = { workspace = true }
rust_decimal = { version = "1", features = ["db-postgres", "maths"] }
self_cell = "1.0.1"
serde = { version = "1", features = ["derive"] }
Expand Down
1 change: 1 addition & 0 deletions src/expr/impl/src/table_function/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,6 @@ mod generate_series;
mod generate_subscripts;
mod jsonb;
mod pg_expandarray;
mod pg_get_keywords;
mod regexp_matches;
mod unnest;
58 changes: 58 additions & 0 deletions src/expr/impl/src/table_function/pg_get_keywords.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// Copyright 2024 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 risingwave_expr::function;
use risingwave_sqlparser::keywords::{
ALL_KEYWORDS_INDEX, RESERVED_FOR_COLUMN_ALIAS, RESERVED_FOR_COLUMN_OR_TABLE_NAME,
};

/// Returns a set of records describing the SQL keywords recognized by the server.
///
/// The word column contains the keyword.
///
/// The catcode column contains a category code:
/// - U for an unreserved keyword
/// - C for a keyword that can be a column name
/// - T for a keyword that can be a type or function name
/// - R for a fully reserved keyword.
///
/// The catdesc column contains a possibly-localized string describing the keyword's category.
///
/// ```slt
/// query TTT
/// select * from pg_get_keywords() where word = 'add';
/// ----
/// add U unreserved
/// ```
#[function("pg_get_keywords() -> setof struct<word varchar, catcode varchar, catdesc varchar>")]
fn pg_get_keywords() -> impl Iterator<Item = (Box<str>, &'static str, &'static str)> {
ALL_KEYWORDS_INDEX.iter().map(|keyword| {
// FIXME: The current category is not correct. Many are different from the PostgreSQL.
let catcode = if !RESERVED_FOR_COLUMN_OR_TABLE_NAME.contains(keyword) {
"U"
} else if !RESERVED_FOR_COLUMN_ALIAS.contains(keyword) {
"C"
} else {
"R"
};
let catdesc = match catcode {
"U" => "unreserved",
"C" => "unreserved (cannot be function or type name)",
"T" => "reserved (can be function or type name)",
"R" => "reserved",
_ => unreachable!(),
};
(keyword.to_string().to_lowercase().into(), catcode, catdesc)
})
}
19 changes: 8 additions & 11 deletions src/expr/macro/src/gen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -484,10 +484,6 @@ impl FunctionAttr {
}
} else {
// no optimization
let array_zip = match children_indices.len() {
0 => quote! { std::iter::repeat(()).take(input.capacity()) },
_ => quote! { multizip((#(#arrays.iter(),)*)) },
};
let let_variadic = variadic.then(|| {
quote! {
let variadic_row = variadic_input.row_at_unchecked_vis(i);
Expand All @@ -497,18 +493,18 @@ impl FunctionAttr {
let mut builder = #builder_type::with_type(input.capacity(), self.context.return_type.clone());

if input.is_compacted() {
for (i, (#(#inputs,)*)) in #array_zip.enumerate() {
for i in 0..input.capacity() {
#(let #inputs = unsafe { #arrays.value_at_unchecked(i) };)*
#let_variadic
#append_output
}
} else {
// allow using `zip` for performance
#[allow(clippy::disallowed_methods)]
for (i, ((#(#inputs,)*), visible)) in #array_zip.zip(input.visibility().iter()).enumerate() {
if !visible {
for i in 0..input.capacity() {
if unsafe { !input.visibility().is_set_unchecked(i) } {
builder.append_null();
continue;
}
#(let #inputs = unsafe { #arrays.value_at_unchecked(i) };)*
#let_variadic
#append_output
}
Expand Down Expand Up @@ -1179,10 +1175,11 @@ impl FunctionAttr {
let mut index_builder = I32ArrayBuilder::new(self.chunk_size);
#(let mut #builders = #builder_types::with_type(self.chunk_size, #return_types);)*

for (i, ((#(#inputs,)*), visible)) in multizip((#(#arrays.iter(),)*)).zip_eq_fast(input.visibility().iter()).enumerate() {
if !visible {
for i in 0..input.capacity() {
if unsafe { !input.visibility().is_set_unchecked(i) } {
continue;
}
#(let #inputs = unsafe { #arrays.value_at_unchecked(i) };)*
for output in #iter {
index_builder.append(Some(i as i32));
match #output {
Expand Down
25 changes: 1 addition & 24 deletions src/frontend/src/binder/relation/table_function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,13 @@ use std::str::FromStr;

use itertools::Itertools;
use risingwave_common::bail_not_implemented;
use risingwave_common::catalog::{
Field, Schema, PG_CATALOG_SCHEMA_NAME, RW_INTERNAL_TABLE_FUNCTION_NAME,
};
use risingwave_common::catalog::{Field, Schema, RW_INTERNAL_TABLE_FUNCTION_NAME};
use risingwave_common::types::DataType;
use risingwave_sqlparser::ast::{Function, FunctionArg, ObjectName, TableAlias};

use super::watermark::is_watermark_func;
use super::{Binder, Relation, Result, WindowTableFunctionKind};
use crate::binder::bind_context::Clause;
use crate::catalog::system_catalog::pg_catalog::{
PG_GET_KEYWORDS_FUNC_NAME, PG_KEYWORDS_TABLE_NAME,
};
use crate::error::ErrorCode;
use crate::expr::{Expr, ExprImpl};

Expand Down Expand Up @@ -57,24 +52,6 @@ impl Binder {
}
return self.bind_internal_table(args, alias);
}
if func_name.eq_ignore_ascii_case(PG_GET_KEYWORDS_FUNC_NAME)
|| name.real_value().eq_ignore_ascii_case(
format!("{}.{}", PG_CATALOG_SCHEMA_NAME, PG_GET_KEYWORDS_FUNC_NAME).as_str(),
)
{
if with_ordinality {
bail_not_implemented!(
"WITH ORDINALITY for internal/system table function {}",
func_name
);
}
return self.bind_relation_by_name_inner(
Some(PG_CATALOG_SCHEMA_NAME),
PG_KEYWORDS_TABLE_NAME,
alias,
None,
);
}
}
// window table functions (tumble/hop)
if let Ok(kind) = WindowTableFunctionKind::from_str(func_name) {
Expand Down
2 changes: 0 additions & 2 deletions src/frontend/src/catalog/system_catalog/pg_catalog/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,5 +52,3 @@ mod pg_trigger;
mod pg_type;
mod pg_user;
mod pg_views;

pub use pg_keywords::*;
53 changes: 47 additions & 6 deletions src/frontend/src/catalog/system_catalog/pg_catalog/pg_keywords.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,60 @@
// See the License for the specific language governing permissions and
// limitations under the License.

// The code is same as `expr/impl/src/table_function/pg_get_keywords.rs`.

use risingwave_common::types::Fields;
use risingwave_frontend_macro::system_catalog;
use risingwave_sqlparser::keywords::{
ALL_KEYWORDS_INDEX, RESERVED_FOR_COLUMN_ALIAS, RESERVED_FOR_COLUMN_OR_TABLE_NAME,
};

pub const PG_KEYWORDS_TABLE_NAME: &str = "pg_keywords";
pub const PG_GET_KEYWORDS_FUNC_NAME: &str = "pg_get_keywords";
use crate::catalog::system_catalog::SysCatalogReaderImpl;

/// The catalog `pg_keywords` stores keywords. `pg_get_keywords` returns the content of this table.
/// Ref: [`https://www.postgresql.org/docs/15/functions-info.html`]
// TODO: change to read reserved keywords here
#[system_catalog(view, "pg_catalog.pg_keywords")]
///
/// # Example
///
/// ```slt
/// query TTT
/// select * from pg_keywords where word = 'add';
/// ----
/// add U unreserved
/// ```
#[derive(Fields)]
struct PgKeywords {
#[primary_key]
word: String,
catcode: String,
catdesc: String,
catcode: char,
catdesc: &'static str,
}

#[system_catalog(table, "pg_catalog.pg_keywords")]
fn read_pg_keywords(_reader: &SysCatalogReaderImpl) -> Vec<PgKeywords> {
ALL_KEYWORDS_INDEX
.iter()
.map(|keyword| {
// FIXME: The current category is not correct. Many are different from the PostgreSQL.
let catcode = if !RESERVED_FOR_COLUMN_OR_TABLE_NAME.contains(keyword) {
'U'
} else if !RESERVED_FOR_COLUMN_ALIAS.contains(keyword) {
'C'
} else {
'R'
};
let catdesc = match catcode {
'U' => "unreserved",
'C' => "unreserved (cannot be function or type name)",
'T' => "reserved (can be function or type name)",
'R' => "reserved",
_ => unreachable!(),
};
PgKeywords {
word: keyword.to_string().to_lowercase(),
catcode,
catdesc,
}
})
.collect()
}
4 changes: 2 additions & 2 deletions src/sqlparser/src/keywords.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ macro_rules! define_keywords {
];

$(kw_def!($ident $(= $string_keyword)?);)*
pub const ALL_KEYWORDS: &[&str] = &[
pub const ALL_KEYWORDS: &[&'static str] = &[
$($ident),*
];
};
Expand Down Expand Up @@ -651,7 +651,7 @@ pub const RESERVED_FOR_COLUMN_ALIAS: &[Keyword] = &[
/// Can't be used as a column or table name in PostgreSQL.
///
/// This list is taken from the following table, for all "reserved" words in the PostgreSQL column,
/// includinhg "can be function or type" and "requires AS". <https://www.postgresql.org/docs/14/sql-keywords-appendix.html#KEYWORDS-TABLE>
/// including "can be function or type" and "requires AS". <https://www.postgresql.org/docs/14/sql-keywords-appendix.html#KEYWORDS-TABLE>
///
/// `SELECT` and `WITH` were commented out because the following won't parse:
/// `SELECT (SELECT 1)` or `SELECT (WITH a AS (SELECT 1) SELECT 1)`
Expand Down
Loading