STET

agent_patch

runs/2026-02-21__03-36-16__gpt-5-1-codex-mini/flux-pr-1534/flux-pr-1534.1-of-1.2026-03-05__17-48-34__gpt-5-1-codex-mini/agent-logs/agent.patch

28030 bytes

Back to adjudication
diff --git a/tmp/agent-patch-flux-pr-1534.1-of-1.2026-03-05__17-48-34__gpt-5-1-codex-mini/app/src/ast/mod.rs b/app/src/ast/mod.rs
index 9185c9d..14c5b58 100644
--- a/tmp/agent-patch-flux-pr-1534.1-of-1.2026-03-05__17-48-34__gpt-5-1-codex-mini/app/src/ast/mod.rs
+++ b/app/src/ast/mod.rs
@@ -447,16 +447,33 @@ pub enum JsonPathElem {
 pub struct JsonPath {
     pub path: Vec<JsonPathElem>,
 }
-
-impl fmt::Display for JsonPath {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+/// The style used to format a [`JsonPath`].
+#[derive(Debug, Copy, Clone)]
+pub enum JsonPathStyle {
+    /// Snowflake-style path with a leading colon on the first identifier.
+    Snowflake,
+    /// PartiQL-style path with dot notation for all identifiers.
+    Partiql,
+}
+
+impl JsonPath {
+    pub fn fmt_with_style(
+        &self,
+        f: &mut fmt::Formatter<'_>,
+        style: JsonPathStyle,
+    ) -> fmt::Result {
         for (i, elem) in self.path.iter().enumerate() {
             match elem {
                 JsonPathElem::Dot { key, quoted } => {
-                    if i == 0 {
-                        write!(f, ":")?;
-                    } else {
-                        write!(f, ".")?;
+                    match style {
+                        JsonPathStyle::Snowflake => {
+                            if i == 0 {
+                                write!(f, ":")?;
+                            } else {
+                                write!(f, ".")?;
+                            }
+                        }
+                        JsonPathStyle::Partiql => write!(f, ".")?,
                     }
 
                     if *quoted {
@@ -474,6 +491,14 @@ impl fmt::Display for JsonPath {
     }
 }
 
+impl fmt::Display for JsonPath {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        self.fmt_with_style(f, JsonPathStyle::Snowflake)
+    }
+        Ok(())
+    }
+}
+
 /// The syntax used for in a cast expression.
 #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
 #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
diff --git a/tmp/agent-patch-flux-pr-1534.1-of-1.2026-03-05__17-48-34__gpt-5-1-codex-mini/app/src/ast/query.rs b/app/src/ast/query.rs
index 078bbc8..5f5b677 100644
--- a/tmp/agent-patch-flux-pr-1534.1-of-1.2026-03-05__17-48-34__gpt-5-1-codex-mini/app/src/ast/query.rs
+++ b/app/src/ast/query.rs
@@ -975,6 +975,20 @@ pub enum TableFactor {
         /// [Partition selection](https://dev.mysql.com/doc/refman/8.0/en/partitioning-selection.html), supported by MySQL.
         partitions: Vec<Ident>,
     },
+    /// Represents the PartiQL syntax for iterating over a JSON path expression.
+    ///
+    /// For example, `c.c_orders o` iterates over the `c_orders` array exposed via the
+    /// `c` alias from a previous table reference.
+    Partiql {
+        /// The alias or identifier preceding the JSON path.
+        base: Ident,
+        /// The JSON path expressions accessed via dot/bracket notation.
+        path: JsonPath,
+        /// Optional alias for the derived table expression.
+        alias: Option<TableAlias>,
+        /// Optional `AT <alias>` clause that exposes the current index.
+        at: Option<Ident>,
+    },
     Derived {
         lateral: bool,
         subquery: Box<Query>,
@@ -1405,6 +1419,17 @@ impl fmt::Display for TableFactor {
                 }
                 Ok(())
             }
+            TableFactor::Partiql { base, path, alias, at } => {
+                write!(f, "{base}")?;
+                path.fmt_with_style(f, JsonPathStyle::Partiql)?;
+                if let Some(alias) = alias {
+                    write!(f, " AS {alias}")?;
+                }
+                if let Some(at) = at {
+                    write!(f, " AT {at}")?;
+                }
+                Ok(())
+            }
             TableFactor::Derived {
                 lateral,
                 subquery,
diff --git a/tmp/agent-patch-flux-pr-1534.1-of-1.2026-03-05__17-48-34__gpt-5-1-codex-mini/app/src/dialect/mod.rs b/app/src/dialect/mod.rs
index 985cad7..caab7f9 100644
--- a/tmp/agent-patch-flux-pr-1534.1-of-1.2026-03-05__17-48-34__gpt-5-1-codex-mini/app/src/dialect/mod.rs
+++ b/app/src/dialect/mod.rs
@@ -221,6 +221,11 @@ pub trait Dialect: Debug + Any {
         false
     }
 
+    /// Returns true if the dialect supports PartiQL-style JSON path navigation.
+    fn supports_partiql_json_path(&self) -> bool {
+        false
+    }
+
     /// Returns true if the dialect supports `(NOT) IN ()` expressions
     fn supports_in_empty_list(&self) -> bool {
         false
diff --git a/tmp/agent-patch-flux-pr-1534.1-of-1.2026-03-05__17-48-34__gpt-5-1-codex-mini/app/src/dialect/redshift.rs b/app/src/dialect/redshift.rs
index 4d07738..f1da496 100644
--- a/tmp/agent-patch-flux-pr-1534.1-of-1.2026-03-05__17-48-34__gpt-5-1-codex-mini/app/src/dialect/redshift.rs
+++ b/app/src/dialect/redshift.rs
@@ -69,6 +69,10 @@ impl Dialect for RedshiftSqlDialect {
         true
     }
 
+    fn supports_partiql_json_path(&self) -> bool {
+        true
+    }
+
     /// Redshift expects the `TOP` option before the `ALL/DISTINCT` option:
     /// <https://docs.aws.amazon.com/redshift/latest/dg/r_SELECT_list.html#r_SELECT_list-parameters>
     fn supports_top_before_distinct(&self) -> bool {
diff --git a/tmp/agent-patch-flux-pr-1534.1-of-1.2026-03-05__17-48-34__gpt-5-1-codex-mini/app/src/parser/mod.rs b/app/src/parser/mod.rs
index c835876..5d722c1 100644
--- a/tmp/agent-patch-flux-pr-1534.1-of-1.2026-03-05__17-48-34__gpt-5-1-codex-mini/app/src/parser/mod.rs
+++ b/app/src/parser/mod.rs
@@ -277,6 +277,7 @@ pub struct Parser<'a> {
     options: ParserOptions,
     /// Ensure the stack does not overflow by limiting recursion depth.
     recursion_counter: RecursionCounter,
+    partiql_alias_stack: Vec<Vec<Ident>>,
 }
 
 impl<'a> Parser<'a> {
@@ -303,6 +304,7 @@ impl<'a> Parser<'a> {
             dialect,
             recursion_counter: RecursionCounter::new(DEFAULT_REMAINING_DEPTH),
             options: ParserOptions::new().with_trailing_commas(dialect.supports_trailing_commas()),
+            partiql_alias_stack: Vec::new(),
         }
     }
 
@@ -2934,7 +2936,10 @@ impl<'a> Parser<'a> {
                 expr: Box::new(expr),
             })
         } else if Token::LBracket == tok {
-            if dialect_of!(self is PostgreSqlDialect | DuckDbDialect | GenericDialect) {
+            if self.dialect.supports_partiql_json_path() {
+                self.prev_token();
+                self.parse_json_access(expr)
+            } else if dialect_of!(self is PostgreSqlDialect | DuckDbDialect | GenericDialect) {
                 self.parse_subscript(expr)
             } else if dialect_of!(self is SnowflakeDialect) {
                 self.prev_token();
@@ -9967,145 +9972,264 @@ impl<'a> Parser<'a> {
     }
 
     pub fn parse_table_and_joins(&mut self) -> Result<TableWithJoins, ParserError> {
-        let relation = self.parse_table_factor()?;
-        // Note that for keywords to be properly handled here, they need to be
-        // added to `RESERVED_FOR_TABLE_ALIAS`, otherwise they may be parsed as
-        // a table alias.
-        let mut joins = vec![];
-        loop {
-            let global = self.parse_keyword(Keyword::GLOBAL);
-            let join = if self.parse_keyword(Keyword::CROSS) {
-                let join_operator = if self.parse_keyword(Keyword::JOIN) {
-                    JoinOperator::CrossJoin
-                } else if self.parse_keyword(Keyword::APPLY) {
-                    // MSSQL extension, similar to CROSS JOIN LATERAL
-                    JoinOperator::CrossApply
-                } else {
-                    return self.expected("JOIN or APPLY after CROSS", self.peek_token());
-                };
-                Join {
-                    relation: self.parse_table_factor()?,
-                    global,
-                    join_operator,
-                }
-            } else if self.parse_keyword(Keyword::OUTER) {
-                // MSSQL extension, similar to LEFT JOIN LATERAL .. ON 1=1
-                self.expect_keyword(Keyword::APPLY)?;
-                Join {
-                    relation: self.parse_table_factor()?,
-                    global,
-                    join_operator: JoinOperator::OuterApply,
-                }
-            } else if self.parse_keyword(Keyword::ASOF) {
-                self.expect_keyword(Keyword::JOIN)?;
-                let relation = self.parse_table_factor()?;
-                self.expect_keyword(Keyword::MATCH_CONDITION)?;
-                let match_condition = self.parse_parenthesized(Self::parse_expr)?;
-                Join {
-                    relation,
-                    global,
-                    join_operator: JoinOperator::AsOf {
-                        match_condition,
-                        constraint: self.parse_join_constraint(false)?,
-                    },
-                }
-            } else {
-                let natural = self.parse_keyword(Keyword::NATURAL);
-                let peek_keyword = if let Token::Word(w) = self.peek_token().token {
-                    w.keyword
+        self.push_partiql_alias_context();
+        let result = (|| {
+            let relation = self.parse_table_factor()?;
+            self.register_partiql_alias_from_table_factor(&relation);
+            // Note that for keywords to be properly handled here, they need to be
+            // added to `RESERVED_FOR_TABLE_ALIAS`, otherwise they may be parsed as
+            // a table alias.
+            let mut joins = vec![];
+            loop {
+                let global = self.parse_keyword(Keyword::GLOBAL);
+                let join = if self.parse_keyword(Keyword::CROSS) {
+                    let join_operator = if self.parse_keyword(Keyword::JOIN) {
+                        JoinOperator::CrossJoin
+                    } else if self.parse_keyword(Keyword::APPLY) {
+                        // MSSQL extension, similar to CROSS JOIN LATERAL
+                        JoinOperator::CrossApply
+                    } else {
+                        return self.expected("JOIN or APPLY after CROSS", self.peek_token());
+                    };
+                    let relation = self.parse_table_factor()?;
+                    self.register_partiql_alias_from_table_factor(&relation);
+                    Join {
+                        relation,
+                        global,
+                        join_operator,
+                    }
+                } else if self.parse_keyword(Keyword::OUTER) {
+                    // MSSQL extension, similar to LEFT JOIN LATERAL .. ON 1=1
+                    self.expect_keyword(Keyword::APPLY)?;
+                    let relation = self.parse_table_factor()?;
+                    self.register_partiql_alias_from_table_factor(&relation);
+                    Join {
+                        relation,
+                        global,
+                        join_operator: JoinOperator::OuterApply,
+                    }
+                } else if self.parse_keyword(Keyword::ASOF) {
+                    self.expect_keyword(Keyword::JOIN)?;
+                    let relation = self.parse_table_factor()?;
+                    self.register_partiql_alias_from_table_factor(&relation);
+                    self.expect_keyword(Keyword::MATCH_CONDITION)?;
+                    let match_condition = self.parse_parenthesized(Self::parse_expr)?;
+                    Join {
+                        relation,
+                        global,
+                        join_operator: JoinOperator::AsOf {
+                            match_condition,
+                            constraint: self.parse_join_constraint(false)?,
+                        },
+                    }
                 } else {
-                    Keyword::NoKeyword
-                };
+                    let natural = self.parse_keyword(Keyword::NATURAL);
+                    let peek_keyword = if let Token::Word(w) = self.peek_token().token {
+                        w.keyword
+                    } else {
+                        Keyword::NoKeyword
+                    };
 
-                let join_operator_type = match peek_keyword {
-                    Keyword::INNER | Keyword::JOIN => {
-                        let _ = self.parse_keyword(Keyword::INNER); // [ INNER ]
-                        self.expect_keyword(Keyword::JOIN)?;
-                        JoinOperator::Inner
-                    }
-                    kw @ Keyword::LEFT | kw @ Keyword::RIGHT => {
-                        let _ = self.next_token(); // consume LEFT/RIGHT
-                        let is_left = kw == Keyword::LEFT;
-                        let join_type = self.parse_one_of_keywords(&[
-                            Keyword::OUTER,
-                            Keyword::SEMI,
-                            Keyword::ANTI,
-                            Keyword::JOIN,
-                        ]);
-                        match join_type {
-                            Some(Keyword::OUTER) => {
-                                self.expect_keyword(Keyword::JOIN)?;
-                                if is_left {
-                                    JoinOperator::LeftOuter
-                                } else {
-                                    JoinOperator::RightOuter
+                    let join_operator_type = match peek_keyword {
+                        Keyword::INNER | Keyword::JOIN => {
+                            let _ = self.parse_keyword(Keyword::INNER); // [ INNER ]
+                            self.expect_keyword(Keyword::JOIN)?;
+                            JoinOperator::Inner
+                        }
+                        kw @ Keyword::LEFT | kw @ Keyword::RIGHT => {
+                            let _ = self.next_token(); // consume LEFT/RIGHT
+                            let is_left = kw == Keyword::LEFT;
+                            let join_type = self.parse_one_of_keywords(&[
+                                Keyword::OUTER,
+                                Keyword::SEMI,
+                                Keyword::ANTI,
+                                Keyword::JOIN,
+                            ]);
+                            match join_type {
+                                Some(Keyword::OUTER) => {
+                                    self.expect_keyword(Keyword::JOIN)?;
+                                    if is_left {
+                                        JoinOperator::LeftOuter
+                                    } else {
+                                        JoinOperator::RightOuter
+                                    }
                                 }
-                            }
-                            Some(Keyword::SEMI) => {
-                                self.expect_keyword(Keyword::JOIN)?;
-                                if is_left {
-                                    JoinOperator::LeftSemi
-                                } else {
-                                    JoinOperator::RightSemi
+                                Some(Keyword::SEMI) => {
+                                    self.expect_keyword(Keyword::JOIN)?;
+                                    if is_left {
+                                        JoinOperator::LeftSemi
+                                    } else {
+                                        JoinOperator::RightSemi
+                                    }
                                 }
-                            }
-                            Some(Keyword::ANTI) => {
-                                self.expect_keyword(Keyword::JOIN)?;
-                                if is_left {
-                                    JoinOperator::LeftAnti
-                                } else {
-                                    JoinOperator::RightAnti
+                                Some(Keyword::ANTI) => {
+                                    self.expect_keyword(Keyword::JOIN)?;
+                                    if is_left {
+                                        JoinOperator::LeftAnti
+                                    } else {
+                                        JoinOperator::RightAnti
+                                    }
                                 }
-                            }
-                            Some(Keyword::JOIN) => {
-                                if is_left {
-                                    JoinOperator::LeftOuter
-                                } else {
-                                    JoinOperator::RightOuter
+                                Some(Keyword::JOIN) => {
+                                    if is_left {
+                                        JoinOperator::LeftOuter
+                                    } else {
+                                        JoinOperator::RightOuter
+                                    }
+                                }
+                                _ => {
+                                    return Err(ParserError::ParserError(format!(
+                                        "expected OUTER, SEMI, ANTI or JOIN after {kw:?}"
+                                    )))
                                 }
-                            }
-                            _ => {
-                                return Err(ParserError::ParserError(format!(
-                                    "expected OUTER, SEMI, ANTI or JOIN after {kw:?}"
-                                )))
                             }
                         }
+                        Keyword::ANTI => {
+                            let _ = self.next_token(); // consume ANTI
+                            self.expect_keyword(Keyword::JOIN)?;
+                            JoinOperator::Anti
+                        }
+                        Keyword::SEMI => {
+                            let _ = self.next_token(); // consume SEMI
+                            self.expect_keyword(Keyword::JOIN)?;
+                            JoinOperator::Semi
+                        }
+                        Keyword::FULL => {
+                            let _ = self.next_token(); // consume FULL
+                            let _ = self.parse_keyword(Keyword::OUTER); // [ OUTER ]
+                            self.expect_keyword(Keyword::JOIN)?;
+                            JoinOperator::FullOuter
+                        }
+                        Keyword::OUTER => {
+                            return self.expected("LEFT, RIGHT, or FULL", self.peek_token());
+                        }
+                        _ if natural => {
+                            return self.expected("a join type after NATURAL", self.peek_token());
+                        }
+                        _ => break,
+                    };
+                    let relation = self.parse_table_factor()?;
+                    self.register_partiql_alias_from_table_factor(&relation);
+                    let join_constraint = self.parse_join_constraint(natural)?;
+                    Join {
+                        relation,
+                        global,
+                        join_operator: join_operator_type(join_constraint),
                     }
-                    Keyword::ANTI => {
-                        let _ = self.next_token(); // consume ANTI
-                        self.expect_keyword(Keyword::JOIN)?;
-                        JoinOperator::Anti
-                    }
-                    Keyword::SEMI => {
-                        let _ = self.next_token(); // consume SEMI
-                        self.expect_keyword(Keyword::JOIN)?;
-                        JoinOperator::Semi
-                    }
-                    Keyword::FULL => {
-                        let _ = self.next_token(); // consume FULL
-                        let _ = self.parse_keyword(Keyword::OUTER); // [ OUTER ]
-                        self.expect_keyword(Keyword::JOIN)?;
-                        JoinOperator::FullOuter
-                    }
-                    Keyword::OUTER => {
-                        return self.expected("LEFT, RIGHT, or FULL", self.peek_token());
-                    }
-                    _ if natural => {
-                        return self.expected("a join type after NATURAL", self.peek_token());
-                    }
-                    _ => break,
                 };
-                let relation = self.parse_table_factor()?;
-                let join_constraint = self.parse_join_constraint(natural)?;
-                Join {
-                    relation,
-                    global,
-                    join_operator: join_operator_type(join_constraint),
+                joins.push(join);
+            }
+            Ok(TableWithJoins { relation, joins })
+        })();
+        self.pop_partiql_alias_context();
+        result
+    }
+
+    fn push_partiql_alias_context(&mut self) {
+        if self.dialect.supports_partiql_json_path() {
+            self.partiql_alias_stack.push(Vec::new());
+        }
+    }
+
+    fn pop_partiql_alias_context(&mut self) {
+        if self.dialect.supports_partiql_json_path() {
+            self.partiql_alias_stack.pop();
+        }
+    }
+
+    fn register_partiql_alias_name(&mut self, alias: Ident) {
+        if let Some(stack) = self.partiql_alias_stack.last_mut() {
+            stack.push(alias);
+        }
+    }
+
+    fn register_partiql_alias_from_table_factor(&mut self, table_factor: &TableFactor) {
+        if !self.dialect.supports_partiql_json_path() {
+            return;
+        }
+        let alias = match table_factor {
+            TableFactor::Table { alias, .. } => alias.as_ref(),
+            TableFactor::Derived { alias, .. } => alias.as_ref(),
+            TableFactor::TableFunction { alias, .. } => alias.as_ref(),
+            TableFactor::Function { alias, .. } => alias.as_ref(),
+            TableFactor::UNNEST { alias, .. } => alias.as_ref(),
+            TableFactor::JsonTable { alias, .. } => alias.as_ref(),
+            TableFactor::OpenJsonTable { alias, .. } => alias.as_ref(),
+            TableFactor::NestedJoin { alias, .. } => alias.as_ref(),
+            TableFactor::Pivot { alias, .. } => alias.as_ref(),
+            TableFactor::Unpivot { alias, .. } => alias.as_ref(),
+            TableFactor::MatchRecognize { alias, .. } => alias.as_ref(),
+            TableFactor::Partiql { alias, .. } => alias.as_ref(),
+        };
+        if let Some(alias) = alias {
+            self.register_partiql_alias_name(alias.name.clone());
+        }
+    }
+
+    fn is_partiql_alias(&self, ident: &Ident) -> bool {
+        self.partiql_alias_stack
+            .last()
+            .map(|aliases| aliases.iter().any(|alias| Self::ident_eq(alias, ident)))
+            .unwrap_or(false)
+    }
+
+    fn ident_eq(a: &Ident, b: &Ident) -> bool {
+        match (a.quote_style, b.quote_style) {
+            (None, None) => a.value.eq_ignore_ascii_case(&b.value),
+            _ => a.value == b.value,
+        }
+    }
+
+    fn try_parse_partiql_table_reference(
+        &mut self,
+        name: &ObjectName,
+    ) -> Result<Option<(Ident, JsonPath)>, ParserError> {
+        if !self.dialect.supports_partiql_json_path() {
+            return Ok(None);
+        }
+        let base_ident = match name.0.first() {
+            Some(ident) => ident.clone(),
+            None => return Ok(None),
+        };
+        if !self.is_partiql_alias(&base_ident) {
+            return Ok(None);
+        }
+
+        let mut path = Vec::new();
+        for ident in name.0.iter().skip(1) {
+            path.push(JsonPathElem::Dot {
+                key: ident.value.clone(),
+                quoted: ident.quote_style.is_some(),
+            });
+        }
+        self.parse_partiql_table_json_path(&mut path)?;
+        if path.is_empty() {
+            Ok(None)
+        } else {
+            Ok(Some((base_ident, JsonPath { path })))
+        }
+    }
+
+    fn parse_partiql_table_json_path(
+        &mut self,
+        path: &mut Vec<JsonPathElem>,
+    ) -> Result<(), ParserError> {
+        loop {
+            match self.peek_token().token {
+                Token::Period => {
+                    self.next_token();
+                    path.push(self.parse_json_path_object_key()?);
                 }
-            };
-            joins.push(join);
+                Token::LBracket => {
+                    self.next_token();
+                    let key = self.parse_expr()?;
+                    self.expect_token(&Token::RBracket)?;
+                    path.push(JsonPathElem::Bracket { key });
+                }
+                _ => break,
+            }
         }
-        Ok(TableWithJoins { relation, joins })
+        Ok(())
     }
 
     /// A table name or a parenthesized subquery, followed by optional `[AS] alias`
@@ -10337,6 +10461,20 @@ impl<'a> Parser<'a> {
             self.parse_open_json_table_factor()
         } else {
             let name = self.parse_object_name(true)?;
+            if let Some((base, path)) = self.try_parse_partiql_table_reference(&name)? {
+                let alias = self.parse_optional_table_alias(keywords::RESERVED_FOR_TABLE_ALIAS)?;
+                let at = if self.parse_keyword(Keyword::AT) {
+                    Some(self.parse_identifier(false)?)
+                } else {
+                    None
+                };
+                return Ok(TableFactor::Partiql {
+                    base,
+                    path,
+                    alias,
+                    at,
+                });
+            }
 
             let partitions: Vec<Ident> = if dialect_of!(self is MySqlDialect | GenericDialect)
                 && self.parse_keyword(Keyword::PARTITION)
diff --git a/tmp/agent-patch-flux-pr-1534.1-of-1.2026-03-05__17-48-34__gpt-5-1-codex-mini/app/tests/sqlparser_redshift.rs b/app/tests/sqlparser_redshift.rs
index a25d506..36edce8 100644
--- a/tmp/agent-patch-flux-pr-1534.1-of-1.2026-03-05__17-48-34__gpt-5-1-codex-mini/app/tests/sqlparser_redshift.rs
+++ b/app/tests/sqlparser_redshift.rs
@@ -170,6 +170,60 @@ fn parse_delimited_identifiers() {
     //TODO verified_stmt(r#"UPDATE foo SET "bar" = 5"#);
 }
 
+#[test]
+fn parse_partiql_json_path_expression() {
+    let select = redshift()
+        .verified_only_select("SELECT c.c_orders[0].o_orderkey FROM customer_orders_lineitem c");
+
+    let expected = Expr::JsonAccess {
+        value: Box::new(Expr::CompoundIdentifier(vec![
+            Ident::new("c"),
+            Ident::new("c_orders"),
+        ])),
+        path: JsonPath {
+            path: vec![
+                JsonPathElem::Bracket {
+                    key: Expr::Value(Value::Number("0".to_string(), false)),
+                },
+                JsonPathElem::Dot {
+                    key: "o_orderkey".to_string(),
+                    quoted: false,
+                },
+            ],
+        },
+    };
+
+    assert_eq!(expected, expr_from_projection(&select.projection[0]));
+}
+
+#[test]
+fn parse_partiql_table_reference() {
+    let select = redshift().verified_only_select(
+        "SELECT * FROM customer_orders_lineitem c, c.c_orders o",
+    );
+
+    match &select.from[1].relation {
+        TableFactor::Partiql {
+            base,
+            path,
+            alias,
+            at,
+        } => {
+            assert_eq!(&Ident::new("c"), base);
+            assert_eq!(
+                vec![JsonPathElem::Dot {
+                    key: "c_orders".to_string(),
+                    quoted: false,
+                }],
+                path.path
+            );
+            assert_eq!(Some(Ident::new("o")), alias.as_ref().map(|n| n.name.clone()));
+            assert!(at.is_none());
+        }
+        other => panic!("expected Partiql table factor, got {other:?}"),
+    }
+}
+
 fn redshift() -> TestedDialects {
     TestedDialects::new(vec![Box::new(RedshiftSqlDialect {})])
 }