mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-10-13 00:03:00 +00:00
feat: support array arguments in snowflake scripts (#11702)
* feat: support array arguments in snowflake scripts Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs: cast the parsed snowflake list to array in the guidance Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: parse a json array default of a sql list argument as a list Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
a92b29dae6
commit
14519884e4
@@ -235,7 +235,7 @@ lazy_static::lazy_static! {
|
||||
// -- $name (type) = default
|
||||
static ref RE_ARG_DUCKDB: Regex = Regex::new(r#"(?m)^-- \$(\w+) \(([A-Za-z0-9_\[\]]+)\)(?: ?\= ?(.+))? *(?:\r|\n|$)"#).unwrap();
|
||||
|
||||
static ref RE_ARG_SNOWFLAKE: Regex = Regex::new(r#"(?m)^-- \? (\w+) \((\w+)\)(?: ?\= ?(.+))? *(?:\r|\n|$)"#).unwrap();
|
||||
static ref RE_ARG_SNOWFLAKE: Regex = Regex::new(r#"(?m)^-- \? (\w+) \((\w+(?:\[\])?)\)(?: ?\= ?(.+))? *(?:\r|\n|$)"#).unwrap();
|
||||
|
||||
|
||||
static ref RE_ARG_MSSQL: Regex = Regex::new(r#"(?m)^-- @(?:P|p)\d+ (\w+) \((\w+)\)(?: ?\= ?(.+))? *(?:\r|\n|$)"#).unwrap();
|
||||
@@ -254,6 +254,12 @@ fn parsed_default(parsed_typ: &Typ, default: String) -> Option<serde_json::Value
|
||||
Typ::Str(_) if default.len() >= 2 && default.starts_with("'") && default.ends_with("'") => {
|
||||
Some(json!(&default[1..default.len() - 1]))
|
||||
}
|
||||
Typ::List(_) => Some(
|
||||
serde_json::from_str::<serde_json::Value>(&default)
|
||||
.ok()
|
||||
.filter(|v| v.is_array())
|
||||
.unwrap_or_else(|| json!(default)),
|
||||
),
|
||||
_ => Some(json!(default)),
|
||||
}
|
||||
}
|
||||
@@ -1039,7 +1045,11 @@ pub fn parse_duckdb_typ(typ: &str) -> Typ {
|
||||
}
|
||||
|
||||
pub fn parse_snowflake_typ(typ: &str) -> Typ {
|
||||
if let Some(base_typ) = typ.strip_suffix("[]") {
|
||||
return Typ::List(Box::new(parse_snowflake_typ(base_typ)));
|
||||
}
|
||||
match typ {
|
||||
"array" => Typ::List(Box::new(Typ::Unknown)),
|
||||
"varchar" | "text" | "string" | "char" | "character" => Typ::Str(None),
|
||||
"binary" | "varbinary" => Typ::Bytes,
|
||||
"date" | "time" | "datetime" | "timestamp" | "timestamp_ntz" | "timestamp_ltz"
|
||||
@@ -1591,6 +1601,39 @@ SELECT ?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_snowflake_array_types() -> anyhow::Result<()> {
|
||||
let code = r#"
|
||||
-- ? groups (varchar[])
|
||||
-- ? ids (int[]) = [1, 2]
|
||||
-- ? anything (ARRAY)
|
||||
SELECT 1;
|
||||
"#;
|
||||
let args = parse_snowflake_sig(code)?.args;
|
||||
assert_eq!(args[1].default, Some(json!([1, 2])));
|
||||
let got = args
|
||||
.iter()
|
||||
.map(|a| (a.name.as_str(), a.otyp.as_deref(), &a.typ))
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(
|
||||
got,
|
||||
vec![
|
||||
(
|
||||
"groups",
|
||||
Some("varchar[]"),
|
||||
&Typ::List(Box::new(Typ::Str(None)))
|
||||
),
|
||||
("ids", Some("int[]"), &Typ::List(Box::new(Typ::Int))),
|
||||
(
|
||||
"anything",
|
||||
Some("array"),
|
||||
&Typ::List(Box::new(Typ::Unknown))
|
||||
),
|
||||
]
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_mssql_sig() -> anyhow::Result<()> {
|
||||
let code = r#"
|
||||
|
||||
@@ -873,7 +873,8 @@ fn convert_typ_val(arg_t: String, arg_v: Value) -> Value {
|
||||
let mut v = arg_v;
|
||||
|
||||
if !v.is_string() {
|
||||
// if not string, convert to string for api request
|
||||
// The SQL API has no ARRAY/VARIANT binding type: a list or object
|
||||
// is bound as its JSON text, which the query reads with PARSE_JSON(?).
|
||||
v = json!(v.to_string());
|
||||
}
|
||||
|
||||
@@ -935,3 +936,16 @@ fn parse_val(value: &Value, typ: &str) -> Value {
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn list_arg_binds_as_json_text() {
|
||||
assert_eq!(
|
||||
convert_typ_val("varchar[]".to_string(), json!(["sales", "g/ops"])),
|
||||
json!({ "type": "TEXT", "value": r#"["sales","g/ops"]"# })
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Generated
+21
@@ -5625,6 +5625,27 @@ Name the parameters by adding comments before the statement:
|
||||
SELECT * FROM users WHERE name = ? AND age > ?;
|
||||
\`\`\`
|
||||
|
||||
## Array parameters
|
||||
|
||||
Declare a list with a \`[]\` suffix on the element type (\`(varchar[])\`,
|
||||
\`(int[])\`), or \`(array)\` for an untyped one. Snowflake cannot bind an array
|
||||
directly, so Windmill binds the list as JSON text: wrap the placeholder with
|
||||
\`PARSE_JSON(?)\`, and cast to \`ARRAY\` where a function expects one.
|
||||
|
||||
\`\`\`sql
|
||||
-- ? groups (varchar[])
|
||||
SELECT o.* FROM orders o
|
||||
WHERE ARRAY_CONTAINS(o.owner_group::VARIANT, PARSE_JSON(?)::ARRAY);
|
||||
\`\`\`
|
||||
|
||||
Or join on the elements with \`FLATTEN\`:
|
||||
|
||||
\`\`\`sql
|
||||
-- ? groups (varchar[])
|
||||
SELECT o.* FROM orders o
|
||||
JOIN TABLE(FLATTEN(input => PARSE_JSON(?))) g ON g.value::STRING = o.owner_group;
|
||||
\`\`\`
|
||||
|
||||
## Receiving an S3Object as a script parameter
|
||||
|
||||
Declare the arg with type \`(s3object)\`. Windmill renders an S3 file picker for
|
||||
|
||||
@@ -5815,6 +5815,27 @@ Name the parameters by adding comments before the statement:
|
||||
SELECT * FROM users WHERE name = ? AND age > ?;
|
||||
\`\`\`
|
||||
|
||||
## Array parameters
|
||||
|
||||
Declare a list with a \`[]\` suffix on the element type (\`(varchar[])\`,
|
||||
\`(int[])\`), or \`(array)\` for an untyped one. Snowflake cannot bind an array
|
||||
directly, so Windmill binds the list as JSON text: wrap the placeholder with
|
||||
\`PARSE_JSON(?)\`, and cast to \`ARRAY\` where a function expects one.
|
||||
|
||||
\`\`\`sql
|
||||
-- ? groups (varchar[])
|
||||
SELECT o.* FROM orders o
|
||||
WHERE ARRAY_CONTAINS(o.owner_group::VARIANT, PARSE_JSON(?)::ARRAY);
|
||||
\`\`\`
|
||||
|
||||
Or join on the elements with \`FLATTEN\`:
|
||||
|
||||
\`\`\`sql
|
||||
-- ? groups (varchar[])
|
||||
SELECT o.* FROM orders o
|
||||
JOIN TABLE(FLATTEN(input => PARSE_JSON(?))) g ON g.value::STRING = o.owner_group;
|
||||
\`\`\`
|
||||
|
||||
## Receiving an S3Object as a script parameter
|
||||
|
||||
Declare the arg with type \`(s3object)\`. Windmill renders an S3 file picker for
|
||||
|
||||
@@ -1409,6 +1409,27 @@ Name the parameters by adding comments before the statement:
|
||||
SELECT * FROM users WHERE name = ? AND age > ?;
|
||||
```
|
||||
|
||||
## Array parameters
|
||||
|
||||
Declare a list with a `[]` suffix on the element type (`(varchar[])`,
|
||||
`(int[])`), or `(array)` for an untyped one. Snowflake cannot bind an array
|
||||
directly, so Windmill binds the list as JSON text: wrap the placeholder with
|
||||
`PARSE_JSON(?)`, and cast to `ARRAY` where a function expects one.
|
||||
|
||||
```sql
|
||||
-- ? groups (varchar[])
|
||||
SELECT o.* FROM orders o
|
||||
WHERE ARRAY_CONTAINS(o.owner_group::VARIANT, PARSE_JSON(?)::ARRAY);
|
||||
```
|
||||
|
||||
Or join on the elements with `FLATTEN`:
|
||||
|
||||
```sql
|
||||
-- ? groups (varchar[])
|
||||
SELECT o.* FROM orders o
|
||||
JOIN TABLE(FLATTEN(input => PARSE_JSON(?))) g ON g.value::STRING = o.owner_group;
|
||||
```
|
||||
|
||||
## Receiving an S3Object as a script parameter
|
||||
|
||||
Declare the arg with type `(s3object)`. Windmill renders an S3 file picker for
|
||||
|
||||
@@ -84,6 +84,27 @@ Name the parameters by adding comments before the statement:
|
||||
SELECT * FROM users WHERE name = ? AND age > ?;
|
||||
```
|
||||
|
||||
## Array parameters
|
||||
|
||||
Declare a list with a `[]` suffix on the element type (`(varchar[])`,
|
||||
`(int[])`), or `(array)` for an untyped one. Snowflake cannot bind an array
|
||||
directly, so Windmill binds the list as JSON text: wrap the placeholder with
|
||||
`PARSE_JSON(?)`, and cast to `ARRAY` where a function expects one.
|
||||
|
||||
```sql
|
||||
-- ? groups (varchar[])
|
||||
SELECT o.* FROM orders o
|
||||
WHERE ARRAY_CONTAINS(o.owner_group::VARIANT, PARSE_JSON(?)::ARRAY);
|
||||
```
|
||||
|
||||
Or join on the elements with `FLATTEN`:
|
||||
|
||||
```sql
|
||||
-- ? groups (varchar[])
|
||||
SELECT o.* FROM orders o
|
||||
JOIN TABLE(FLATTEN(input => PARSE_JSON(?))) g ON g.value::STRING = o.owner_group;
|
||||
```
|
||||
|
||||
## Receiving an S3Object as a script parameter
|
||||
|
||||
Declare the arg with type `(s3object)`. Windmill renders an S3 file picker for
|
||||
|
||||
@@ -10,6 +10,27 @@ Name the parameters by adding comments before the statement:
|
||||
SELECT * FROM users WHERE name = ? AND age > ?;
|
||||
```
|
||||
|
||||
## Array parameters
|
||||
|
||||
Declare a list with a `[]` suffix on the element type (`(varchar[])`,
|
||||
`(int[])`), or `(array)` for an untyped one. Snowflake cannot bind an array
|
||||
directly, so Windmill binds the list as JSON text: wrap the placeholder with
|
||||
`PARSE_JSON(?)`, and cast to `ARRAY` where a function expects one.
|
||||
|
||||
```sql
|
||||
-- ? groups (varchar[])
|
||||
SELECT o.* FROM orders o
|
||||
WHERE ARRAY_CONTAINS(o.owner_group::VARIANT, PARSE_JSON(?)::ARRAY);
|
||||
```
|
||||
|
||||
Or join on the elements with `FLATTEN`:
|
||||
|
||||
```sql
|
||||
-- ? groups (varchar[])
|
||||
SELECT o.* FROM orders o
|
||||
JOIN TABLE(FLATTEN(input => PARSE_JSON(?))) g ON g.value::STRING = o.owner_group;
|
||||
```
|
||||
|
||||
## Receiving an S3Object as a script parameter
|
||||
|
||||
Declare the arg with type `(s3object)`. Windmill renders an S3 file picker for
|
||||
|
||||
Reference in New Issue
Block a user