Skip to content

Commit 6f0f20a

Browse files
committed
Add SETOF support for PostgreSQL function return types
1 parent dcd5db0 commit 6f0f20a

File tree

5 files changed

+35
-215
lines changed

5 files changed

+35
-215
lines changed

src/ast/data_type.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,11 @@ pub enum DataType {
6161
/// Table columns.
6262
columns: Vec<ColumnDef>,
6363
},
64+
/// SETOF type modifier for [PostgreSQL] function return types,
65+
/// e.g. `CREATE FUNCTION ... RETURNS SETOF text`.
66+
///
67+
/// [PostgreSQL]: https://www.postgresql.org/docs/current/sql-createfunction.html
68+
SetOf(Box<DataType>),
6469
/// Fixed-length character type, e.g. CHARACTER(10).
6570
Character(Option<CharacterLength>),
6671
/// Fixed-length char type, e.g. CHAR(10).
@@ -808,6 +813,7 @@ impl fmt::Display for DataType {
808813
DataType::NamedTable { name, columns } => {
809814
write!(f, "{} TABLE ({})", name, display_comma_separated(columns))
810815
}
816+
DataType::SetOf(inner) => write!(f, "SETOF {inner}"),
811817
DataType::GeometricType(kind) => write!(f, "{kind}"),
812818
DataType::TsVector => write!(f, "TSVECTOR"),
813819
DataType::TsQuery => write!(f, "TSQUERY"),

src/ast/spans.rs

Lines changed: 0 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -654,33 +654,6 @@ impl Spanned for TableConstraint {
654654
}
655655
}
656656

657-
impl Spanned for PartitionBoundValue {
658-
fn span(&self) -> Span {
659-
match self {
660-
PartitionBoundValue::Expr(expr) => expr.span(),
661-
// MINVALUE and MAXVALUE are keywords without tracked spans
662-
PartitionBoundValue::MinValue => Span::empty(),
663-
PartitionBoundValue::MaxValue => Span::empty(),
664-
}
665-
}
666-
}
667-
668-
impl Spanned for ForValues {
669-
fn span(&self) -> Span {
670-
match self {
671-
ForValues::In(exprs) => union_spans(exprs.iter().map(|e| e.span())),
672-
ForValues::From { from, to } => union_spans(
673-
from.iter()
674-
.map(|v| v.span())
675-
.chain(to.iter().map(|v| v.span())),
676-
),
677-
// WITH (MODULUS n, REMAINDER r) - u64 values have no spans
678-
ForValues::With { .. } => Span::empty(),
679-
ForValues::Default => Span::empty(),
680-
}
681-
}
682-
}
683-
684657
impl Spanned for CreateIndex {
685658
fn span(&self) -> Span {
686659
let CreateIndex {

src/keywords.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -932,6 +932,7 @@ define_keywords!(
932932
SESSION_USER,
933933
SET,
934934
SETERROR,
935+
SETOF,
935936
SETS,
936937
SETTINGS,
937938
SHARE,

src/parser/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11976,6 +11976,10 @@ impl<'a> Parser<'a> {
1197611976
let field_defs = self.parse_click_house_tuple_def()?;
1197711977
Ok(DataType::Tuple(field_defs))
1197811978
}
11979+
Keyword::SETOF => {
11980+
let inner = self.parse_data_type()?;
11981+
Ok(DataType::SetOf(Box::new(inner)))
11982+
}
1197911983
Keyword::TRIGGER => Ok(DataType::Trigger),
1198011984
Keyword::ANY if self.peek_keyword(Keyword::TYPE) => {
1198111985
let _ = self.parse_keyword(Keyword::TYPE);

tests/sqlparser_postgres.rs

Lines changed: 24 additions & 188 deletions
Original file line numberDiff line numberDiff line change
@@ -4564,6 +4564,30 @@ fn parse_create_function_detailed() {
45644564
);
45654565
}
45664566

4567+
#[test]
4568+
fn parse_create_function_returns_setof() {
4569+
pg_and_generic().verified_stmt(
4570+
"CREATE FUNCTION get_users() RETURNS SETOF TEXT LANGUAGE sql AS 'SELECT name FROM users'",
4571+
);
4572+
pg_and_generic().verified_stmt(
4573+
"CREATE FUNCTION get_ids() RETURNS SETOF INTEGER LANGUAGE sql AS 'SELECT id FROM users'",
4574+
);
4575+
pg_and_generic().verified_stmt(
4576+
r#"CREATE FUNCTION get_all() RETURNS SETOF my_schema."MyType" LANGUAGE sql AS 'SELECT * FROM t'"#,
4577+
);
4578+
pg_and_generic().verified_stmt(
4579+
"CREATE FUNCTION get_rows() RETURNS SETOF RECORD LANGUAGE sql AS 'SELECT * FROM t'",
4580+
);
4581+
4582+
let sql = "CREATE FUNCTION get_names() RETURNS SETOF TEXT LANGUAGE sql AS 'SELECT name FROM t'";
4583+
match pg_and_generic().verified_stmt(sql) {
4584+
Statement::CreateFunction(CreateFunction { return_type, .. }) => {
4585+
assert_eq!(return_type, Some(DataType::SetOf(Box::new(DataType::Text))));
4586+
}
4587+
_ => panic!("Expected CreateFunction"),
4588+
}
4589+
}
4590+
45674591
#[test]
45684592
fn parse_create_function_with_security() {
45694593
let sql =
@@ -8262,194 +8286,6 @@ fn parse_create_operator_class() {
82628286
.is_err());
82638287
}
82648288

8265-
#[test]
8266-
fn parse_create_table_partition_of_range() {
8267-
// RANGE partition with FROM ... TO
8268-
let sql = "CREATE TABLE measurement_y2006m02 PARTITION OF measurement FOR VALUES FROM ('2006-02-01') TO ('2006-03-01')";
8269-
match pg_and_generic().verified_stmt(sql) {
8270-
Statement::CreateTable(create_table) => {
8271-
assert_eq!("measurement_y2006m02", create_table.name.to_string());
8272-
assert_eq!(
8273-
Some(ObjectName::from(vec![Ident::new("measurement")])),
8274-
create_table.partition_of
8275-
);
8276-
match create_table.for_values {
8277-
Some(ForValues::From { from, to }) => {
8278-
assert_eq!(1, from.len());
8279-
assert_eq!(1, to.len());
8280-
match &from[0] {
8281-
PartitionBoundValue::Expr(Expr::Value(v)) => {
8282-
assert_eq!("'2006-02-01'", v.to_string());
8283-
}
8284-
_ => panic!("Expected Expr value in from"),
8285-
}
8286-
match &to[0] {
8287-
PartitionBoundValue::Expr(Expr::Value(v)) => {
8288-
assert_eq!("'2006-03-01'", v.to_string());
8289-
}
8290-
_ => panic!("Expected Expr value in to"),
8291-
}
8292-
}
8293-
_ => panic!("Expected ForValues::From"),
8294-
}
8295-
}
8296-
_ => panic!("Expected CreateTable"),
8297-
}
8298-
}
8299-
8300-
#[test]
8301-
fn parse_create_table_partition_of_range_with_minvalue_maxvalue() {
8302-
// RANGE partition with MINVALUE/MAXVALUE
8303-
let sql =
8304-
"CREATE TABLE orders_old PARTITION OF orders FOR VALUES FROM (MINVALUE) TO ('2020-01-01')";
8305-
match pg_and_generic().verified_stmt(sql) {
8306-
Statement::CreateTable(create_table) => {
8307-
assert_eq!("orders_old", create_table.name.to_string());
8308-
assert_eq!(
8309-
Some(ObjectName::from(vec![Ident::new("orders")])),
8310-
create_table.partition_of
8311-
);
8312-
match create_table.for_values {
8313-
Some(ForValues::From { from, to }) => {
8314-
assert_eq!(PartitionBoundValue::MinValue, from[0]);
8315-
match &to[0] {
8316-
PartitionBoundValue::Expr(Expr::Value(v)) => {
8317-
assert_eq!("'2020-01-01'", v.to_string());
8318-
}
8319-
_ => panic!("Expected Expr value in to"),
8320-
}
8321-
}
8322-
_ => panic!("Expected ForValues::From"),
8323-
}
8324-
}
8325-
_ => panic!("Expected CreateTable"),
8326-
}
8327-
8328-
// With MAXVALUE
8329-
let sql =
8330-
"CREATE TABLE orders_new PARTITION OF orders FOR VALUES FROM ('2024-01-01') TO (MAXVALUE)";
8331-
match pg_and_generic().verified_stmt(sql) {
8332-
Statement::CreateTable(create_table) => match create_table.for_values {
8333-
Some(ForValues::From { from, to }) => {
8334-
match &from[0] {
8335-
PartitionBoundValue::Expr(Expr::Value(v)) => {
8336-
assert_eq!("'2024-01-01'", v.to_string());
8337-
}
8338-
_ => panic!("Expected Expr value in from"),
8339-
}
8340-
assert_eq!(PartitionBoundValue::MaxValue, to[0]);
8341-
}
8342-
_ => panic!("Expected ForValues::From"),
8343-
},
8344-
_ => panic!("Expected CreateTable"),
8345-
}
8346-
}
8347-
8348-
#[test]
8349-
fn parse_create_table_partition_of_list() {
8350-
// LIST partition
8351-
let sql = "CREATE TABLE orders_us PARTITION OF orders FOR VALUES IN ('US', 'CA', 'MX')";
8352-
match pg_and_generic().verified_stmt(sql) {
8353-
Statement::CreateTable(create_table) => {
8354-
assert_eq!("orders_us", create_table.name.to_string());
8355-
assert_eq!(
8356-
Some(ObjectName::from(vec![Ident::new("orders")])),
8357-
create_table.partition_of
8358-
);
8359-
match create_table.for_values {
8360-
Some(ForValues::In(values)) => {
8361-
assert_eq!(3, values.len());
8362-
}
8363-
_ => panic!("Expected ForValues::In"),
8364-
}
8365-
}
8366-
_ => panic!("Expected CreateTable"),
8367-
}
8368-
}
8369-
8370-
#[test]
8371-
fn parse_create_table_partition_of_hash() {
8372-
// HASH partition
8373-
let sql = "CREATE TABLE orders_p0 PARTITION OF orders FOR VALUES WITH (MODULUS 4, REMAINDER 0)";
8374-
match pg_and_generic().verified_stmt(sql) {
8375-
Statement::CreateTable(create_table) => {
8376-
assert_eq!("orders_p0", create_table.name.to_string());
8377-
assert_eq!(
8378-
Some(ObjectName::from(vec![Ident::new("orders")])),
8379-
create_table.partition_of
8380-
);
8381-
match create_table.for_values {
8382-
Some(ForValues::With { modulus, remainder }) => {
8383-
assert_eq!(4, modulus);
8384-
assert_eq!(0, remainder);
8385-
}
8386-
_ => panic!("Expected ForValues::With"),
8387-
}
8388-
}
8389-
_ => panic!("Expected CreateTable"),
8390-
}
8391-
}
8392-
8393-
#[test]
8394-
fn parse_create_table_partition_of_default() {
8395-
// DEFAULT partition
8396-
let sql = "CREATE TABLE orders_default PARTITION OF orders DEFAULT";
8397-
match pg_and_generic().verified_stmt(sql) {
8398-
Statement::CreateTable(create_table) => {
8399-
assert_eq!("orders_default", create_table.name.to_string());
8400-
assert_eq!(
8401-
Some(ObjectName::from(vec![Ident::new("orders")])),
8402-
create_table.partition_of
8403-
);
8404-
assert_eq!(Some(ForValues::Default), create_table.for_values);
8405-
}
8406-
_ => panic!("Expected CreateTable"),
8407-
}
8408-
}
8409-
8410-
#[test]
8411-
fn parse_create_table_partition_of_multicolumn_range() {
8412-
// Multi-column RANGE partition
8413-
let sql = "CREATE TABLE sales_2023_q1 PARTITION OF sales FOR VALUES FROM ('2023-01-01', 1) TO ('2023-04-01', 1)";
8414-
match pg_and_generic().verified_stmt(sql) {
8415-
Statement::CreateTable(create_table) => {
8416-
assert_eq!("sales_2023_q1", create_table.name.to_string());
8417-
match create_table.for_values {
8418-
Some(ForValues::From { from, to }) => {
8419-
assert_eq!(2, from.len());
8420-
assert_eq!(2, to.len());
8421-
}
8422-
_ => panic!("Expected ForValues::From"),
8423-
}
8424-
}
8425-
_ => panic!("Expected CreateTable"),
8426-
}
8427-
}
8428-
8429-
#[test]
8430-
fn parse_create_table_partition_of_with_constraint() {
8431-
// With table constraint (not column constraint which has different syntax in PARTITION OF)
8432-
let sql = "CREATE TABLE orders_2023 PARTITION OF orders (\
8433-
CONSTRAINT check_date CHECK (order_date >= '2023-01-01')\
8434-
) FOR VALUES FROM ('2023-01-01') TO ('2024-01-01')";
8435-
match pg_and_generic().verified_stmt(sql) {
8436-
Statement::CreateTable(create_table) => {
8437-
assert_eq!("orders_2023", create_table.name.to_string());
8438-
assert_eq!(
8439-
Some(ObjectName::from(vec![Ident::new("orders")])),
8440-
create_table.partition_of
8441-
);
8442-
// Check that table constraint was parsed
8443-
assert_eq!(1, create_table.constraints.len());
8444-
match create_table.for_values {
8445-
Some(ForValues::From { .. }) => {}
8446-
_ => panic!("Expected ForValues::From"),
8447-
}
8448-
}
8449-
_ => panic!("Expected CreateTable"),
8450-
}
8451-
}
8452-
84538289
#[test]
84548290
fn parse_identifiers_semicolon_handling() {
84558291
let statement = "SHOW search_path; SELECT 1";

0 commit comments

Comments
 (0)