|
| 1 | +# Set Returning Functions |
| 2 | + |
| 3 | +PL/Rust supports both set returning function styles, `RETURNS SETOF $type` and `RETURNS TABLE (...)`. In both cases, |
| 4 | +the function returns a specialized `Iterator` for the specific style. |
| 5 | + |
| 6 | +It's useful to think of set returning functions as returning something that resembles a table, either with one unnamed |
| 7 | +column (`RETURNS SETOF`) or multiple, named columns (`RETURNS TABLE`). |
| 8 | + |
| 9 | +In both cases, the Iterator Item type is an `Option<T>`, where `T` is the [return type](return-type.md). The reason |
| 10 | +for this is that PL/Rust needs to allow a returned row/tuple to be NULL (`Option::None`). |
| 11 | + |
| 12 | +## `RETURNS SETOF $type` |
| 13 | + |
| 14 | +`RETURNS SETOF $type` returns a "table" with one, unnamed column. Each returned row must be an `Option` of the return |
| 15 | +type, either `Some(T)` or `None`, indicating NULL. |
| 16 | + |
| 17 | +A simple example of splitting a text string on whitespace, following Rust's rules: |
| 18 | + |
| 19 | +```sql |
| 20 | +CREATE OR REPLACE FUNCTION split_whitespace(s text) RETURNS SETOF text STRICT LANGUAGE plrust AS $$ |
| 21 | + let by_whitespace = s.split_whitespace(); // borrows from `s` which is a `&str` |
| 22 | + let mapped = by_whitespace.map(|token| { |
| 23 | + if token == "this" { None } // just to demonstrate returning a NULL |
| 24 | + else { Some(token.to_string()) } |
| 25 | + }); |
| 26 | + let iter = SetOfIterator::new(mapped); |
| 27 | + Ok(Some(iter)) |
| 28 | +$$; |
| 29 | +``` |
| 30 | + |
| 31 | +PL/Rust generates the following method signature for the above function: |
| 32 | + |
| 33 | +```rust |
| 34 | +fn plrust_fn_oid_19691_336344<'a>( |
| 35 | + s: &'a str, |
| 36 | +) -> ::std::result::Result< // the function itself can return a `Result::Err` |
| 37 | + Option< // `Option::None` will return zero rows |
| 38 | + ::pgrx::iter::SetOfIterator< // indicates returning a set of values |
| 39 | + 'a, // allows borrowing from `s` |
| 40 | + Option<String> // and the type is an optional, owned string |
| 41 | + > |
| 42 | + >, |
| 43 | + Box<dyn std::error::Error + Send + Sync + 'static>, // boilerplate error type |
| 44 | +> { |
| 45 | + // <your code here> |
| 46 | +} |
| 47 | +``` |
| 48 | + |
| 49 | +And finally, its result: |
| 50 | + |
| 51 | +```sql |
| 52 | +SELECT * FROM split_whitespace('hello world, this is a plrust set returning function'); |
| 53 | +split_whitespace |
| 54 | +------------------ |
| 55 | + hello |
| 56 | + world, |
| 57 | + -- remember we returned `None` for the token "this" |
| 58 | + is |
| 59 | + a |
| 60 | + plrust |
| 61 | + set |
| 62 | + returning |
| 63 | + function |
| 64 | + (9 rows) |
| 65 | +``` |
| 66 | + |
| 67 | +## `RETURNS TABLE (...)` |
| 68 | + |
| 69 | +Returning a table with multiple named (and typed) columns is similar to returning a set. Instead of `SetOfIterator`, |
| 70 | +PL/Rust uses `TableIterator`. `TableIterator` is a Rust `Iterator` whose Item is a tuple where its field types match |
| 71 | +those of the UDF being created: |
| 72 | + |
| 73 | +```sql |
| 74 | +CREATE OR REPLACE FUNCTION count_words(s text) RETURNS TABLE (count int, word text) STRICT LANGUAGE plrust AS $$ |
| 75 | + use std::collections::HashMap; |
| 76 | + let mut buckets: HashMap<&str, i32> = Default::default(); |
| 77 | + |
| 78 | + for word in s.split_whitespace() { |
| 79 | + buckets.entry(word).and_modify(|cnt| *cnt += 1).or_insert(1); |
| 80 | + } |
| 81 | + |
| 82 | + let as_tuples = buckets.into_iter().map(|(word, cnt)| { |
| 83 | + ( Some(cnt), Some(word.to_string()) ) |
| 84 | + }); |
| 85 | + Ok(Some(TableIterator::new(as_tuples))) |
| 86 | +$$; |
| 87 | +``` |
| 88 | + |
| 89 | +PL/Rust generates this function signature: |
| 90 | + |
| 91 | +```rust |
| 92 | +fn plrust_fn_oid_19691_336349<'a>( |
| 93 | + s: &'a str, |
| 94 | +) -> ::std::result::Result::< // the function itself can return a `Result::Err` |
| 95 | + Option< // `Option::None` will return zero rows |
| 96 | + ::pgrx::iter::TableIterator< // indicates returning a "table" of tuples |
| 97 | + 'a, // allows borrowing from `s` |
| 98 | + ( // a Rust tuple |
| 99 | + ::pgrx::name!(count, Option < i32 >), // the "count" column, can be "NULL" with `Option::None` |
| 100 | + ::pgrx::name!(word, Option < String >), // the "word" column, can be "NULL" with `Option::None` |
| 101 | + ), |
| 102 | + >, |
| 103 | + >, |
| 104 | + Box<dyn std::error::Error + Send + Sync + 'static>, |
| 105 | +> { |
| 106 | + // <your code here> |
| 107 | +} |
| 108 | +``` |
| 109 | + |
| 110 | +And the results from this function are: |
| 111 | + |
| 112 | +```sql |
| 113 | +# SELECT * FROM count_words('this is a test that is testing plrust''s SRF support'); |
| 114 | + count | word |
| 115 | +-------+---------- |
| 116 | + 1 | a |
| 117 | + 1 | test |
| 118 | + 1 | that |
| 119 | + 2 | is |
| 120 | + 1 | this |
| 121 | + 1 | testing |
| 122 | + 1 | SRF |
| 123 | + 1 | support |
| 124 | + 1 | plrust's |
| 125 | +(9 rows) |
| 126 | +``` |
| 127 | +
|
| 128 | +The important thing to keep in mind when writing PL/Rust functions that `RETURNS TABLE` is that the structure being |
| 129 | +returned is a Rust tuple of `Option<T>`s where each field's `T` is the [return type](return-type.md) as specified in |
| 130 | +the `RETURNS TABLE (...)` clause. |
0 commit comments