Skip to content
Merged
340 changes: 315 additions & 25 deletions src/build/mod.rs

Large diffs are not rendered by default.

51 changes: 49 additions & 2 deletions src/ddl/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,32 @@ pub(crate) fn create_function(
.child_of_kind("NonReservedWord_or_Sconst")
.map(|n| unquote(n.text(src)));
} else if option.has("kw_as") {
if let Some(body) = option.find("Sconst") {
function.definition = Some(string_value(&body, src));
if let Some(func_as) = option.child_of_kind("func_as") {
let sconsts = func_as.find_all("Sconst");
match sconsts.as_slice() {
[object_file, link_symbol] => {
function.object_file =
Some(string_value(object_file, src));
function.link_symbol =
Some(string_value(link_symbol, src));
}
[body] => {
function.definition = Some(string_value(body, src));
}
_ => {}
}
}
} else if option.has("kw_window") {
function.window = Some(true);
} else if option.has("kw_transform") {
let transform_types: Vec<String> = option
.find_all("Typename")
.iter()
.map(|n| n.text(src).to_string())
.collect();
if !transform_types.is_empty() {
function.transform_types = Some(transform_types);
}
} else if let Some(common) =
option.child_of_kind("common_func_opt_item")
{
Expand Down Expand Up @@ -264,6 +285,32 @@ mod tests {
);
}

#[test]
fn parses_c_language_function() {
let Statement::CreateFunction(function) = parse_one(
"CREATE FUNCTION test.c_fn(a integer) RETURNS integer \
LANGUAGE c AS 'ext_module', 'c_fn';",
) else {
panic!("expected CreateFunction")
};
assert_eq!(function.language, Some("c".into()));
assert_eq!(function.object_file, Some("ext_module".into()));
assert_eq!(function.link_symbol, Some("c_fn".into()));
assert_eq!(function.definition, None);
}

#[test]
fn parses_transform_types() {
let Statement::CreateFunction(function) = parse_one(
"CREATE FUNCTION test.fn(a hstore) RETURNS integer \
LANGUAGE plpython3u TRANSFORM FOR TYPE hstore \
AS $$ return 1 $$;",
) else {
panic!("expected CreateFunction")
};
assert_eq!(function.transform_types, Some(vec!["hstore".into()]));
}

#[test]
fn parses_unqualified_function() {
let Statement::CreateFunction(function) = parse_one(
Expand Down
7 changes: 7 additions & 0 deletions src/ddl/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,13 @@ use crate::models;
#[derive(Clone, Debug, PartialEq)]
pub enum Statement {
CreateTable(Box<models::Table>),
/// CREATE TABLE ... PARTITION OF `parent` FOR VALUES ... — folded
/// into the parent table's `partitions` list during pull assembly
/// rather than modeled as a standalone table
CreateTablePartition {
parent: QualifiedName,
partition: models::TablePartition,
},
/// CREATE INDEX — `table` is the qualified relation name
CreateIndex {
table: QualifiedName,
Expand Down
42 changes: 42 additions & 0 deletions src/ddl/object.rs
Original file line number Diff line number Diff line change
Expand Up @@ -415,6 +415,48 @@ pub(crate) fn unstring(text: &str) -> String {
text.to_string()
}

/// Parse a `reloptions` node (`key = value, ...` inside `WITH (...)`)
/// into a string map; values keep their raw text (dequoted if a
/// string literal) so they round-trip unchanged through
/// `utils::raw_value`
pub(crate) fn reloptions(
node: &Node,
src: &str,
) -> Option<serde_json::Map<String, serde_json::Value>> {
let elems = node.find_all("reloption_elem");
if elems.is_empty() {
return None;
}
let mut map = serde_json::Map::new();
for elem in elems {
let labels = elem.find_all("ColLabel");
let Some(first) = labels.first() else {
continue;
};
let key = match labels.get(1) {
Some(second) => {
format!(
"{}.{}",
unquote(first.text(src)),
unquote(second.text(src))
)
}
None => unquote(first.text(src)),
};
// A bare option (`WITH (security_barrier)`) has no def_arg;
// PostgreSQL treats it as shorthand for `= true`.
let value = match elem.child_of_kind("def_arg") {
Some(value) => value
.child_of_kind("Sconst")
.map(|s| string_value(&s, src))
.unwrap_or_else(|| value.text(src).to_string()),
None => "true".to_string(),
};
map.insert(key, serde_json::Value::String(value));
}
(!map.is_empty()).then_some(map)
}

Comment thread
coderabbitai[bot] marked this conversation as resolved.
#[cfg(test)]
mod tests {
use super::*;
Expand Down
Loading