Repository: apache/datafusion-sqlparser-rs (Rust)

Issue:
Add support for Postgres OVERLAY syntax
See https://www.postgresql.org/docs/current/functions-string.html

```
overlay ( string text PLACING newsubstring text FROM start integer [ FOR count integer ] ) → text

Replaces the substring of string that starts at the start'th character and extends for count characters with newsubstring. If count is omitted, it defaults to the length of newsubstring.

overlay('Txxxxas' placing 'hom' from 2 for 4) → Thomas
```


Discussion:

Patch:
diff --git a/src/ast/mod.rs b/src/ast/mod.rs
index 838594da7..4f5fdb2eb 100644
--- a/src/ast/mod.rs
+++ b/src/ast/mod.rs
@@ -348,6 +348,13 @@ pub enum Expr {
         trim_where: Option<TrimWhereField>,
         trim_what: Option<Box<Expr>>,
     },
+    /// OVERLAY(<expr> PLACING <expr> FROM <expr>[ FOR <expr> ]
+    Overlay {
+        expr: Box<Expr>,
+        overlay_what: Box<Expr>,
+        overlay_from: Box<Expr>,
+        overlay_for: Option<Box<Expr>>,
+    },
     /// `expr COLLATE collation`
     Collate {
         expr: Box<Expr>,
@@ -639,8 +646,25 @@ impl fmt::Display for Expr {
                 if let Some(from_part) = substring_from {
                     write!(f, " FROM {}", from_part)?;
                 }
-                if let Some(from_part) = substring_for {
-                    write!(f, " FOR {}", from_part)?;
+                if let Some(for_part) = substring_for {
+                    write!(f, " FOR {}", for_part)?;
+                }
+
+                write!(f, ")")
+            }
+            Expr::Overlay {
+                expr,
+                overlay_what,
+                overlay_from,
+                overlay_for,
+            } => {
+                write!(
+                    f,
+                    "OVERLAY({} PLACING {} FROM {}",
+                    expr, overlay_what, overlay_from
+                )?;
+                if let Some(for_part) = overlay_for {
+                    write!(f, " FOR {}", for_part)?;
                 }
 
                 write!(f, ")")
diff --git a/src/keywords.rs b/src/keywords.rs
index a06ddc48d..2d7cca374 100644
--- a/src/keywords.rs
+++ b/src/keywords.rs
@@ -382,6 +382,7 @@ define_keywords!(
     PERCENTILE_DISC,
     PERCENT_RANK,
     PERIOD,
+    PLACING,
     PLANS,
     PORTION,
     POSITION,
diff --git a/src/parser.rs b/src/parser.rs
index 4cfbdd23b..22ce8418e 100644
--- a/src/parser.rs
+++ b/src/parser.rs
@@ -449,6 +449,7 @@ impl<'a> Parser<'a> {
                 Keyword::EXTRACT => self.parse_extract_expr(),
                 Keyword::POSITION => self.parse_position_expr(),
                 Keyword::SUBSTRING => self.parse_substring_expr(),
+                Keyword::OVERLAY => self.parse_overlay_expr(),
                 Keyword::TRIM => self.parse_trim_expr(),
                 Keyword::INTERVAL => self.parse_literal_interval(),
                 Keyword::LISTAGG => self.parse_listagg_expr(),
@@ -884,6 +885,28 @@ impl<'a> Parser<'a> {
         })
     }
 
+    pub fn parse_overlay_expr(&mut self) -> Result<Expr, ParserError> {
+        // PARSE OVERLAY (EXPR PLACING EXPR FROM 1 [FOR 3])
+        self.expect_token(&Token::LParen)?;
+        let expr = self.parse_expr()?;
+        self.expect_keyword(Keyword::PLACING)?;
+        let what_expr = self.parse_expr()?;
+        self.expect_keyword(Keyword::FROM)?;
+        let from_expr = self.parse_expr()?;
+        let mut for_expr = None;
+        if self.parse_keyword(Keyword::FOR) {
+            for_expr = Some(self.parse_expr()?);
+        }
+        self.expect_token(&Token::RParen)?;
+
+        Ok(Expr::Overlay {
+            expr: Box::new(expr),
+            overlay_what: Box::new(what_expr),
+            overlay_from: Box::new(from_expr),
+            overlay_for: for_expr.map(Box::new),
+        })
+    }
+
     /// TRIM ([WHERE] ['text' FROM] 'text')\
     /// TRIM ('text')
     pub fn parse_trim_expr(&mut self) -> Result<Expr, ParserError> {


Test patch:
diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs
index 65d699b3f..d579fcb7d 100644
--- a/tests/sqlparser_common.rs
+++ b/tests/sqlparser_common.rs
@@ -3950,6 +3950,38 @@ fn parse_substring() {
     one_statement_parses_to("SELECT SUBSTRING('1' FOR 3)", "SELECT SUBSTRING('1' FOR 3)");
 }
 
+#[test]
+fn parse_overlay() {
+    one_statement_parses_to(
+        "SELECT OVERLAY('abccccde' PLACING 'abc' FROM 3)",
+        "SELECT OVERLAY('abccccde' PLACING 'abc' FROM 3)",
+    );
+    one_statement_parses_to(
+        "SELECT OVERLAY('abccccde' PLACING 'abc' FROM 3 FOR 12)",
+        "SELECT OVERLAY('abccccde' PLACING 'abc' FROM 3 FOR 12)",
+    );
+    assert_eq!(
+        ParserError::ParserError("Expected PLACING, found: FROM".to_owned()),
+        parse_sql_statements("SELECT OVERLAY('abccccde' FROM 3)").unwrap_err(),
+    );
+
+    let sql = "SELECT OVERLAY('abcdef' PLACING name FROM 3 FOR id + 1) FROM CUSTOMERS";
+    let select = verified_only_select(sql);
+    assert_eq!(
+        &Expr::Overlay {
+            expr: Box::new(Expr::Value(Value::SingleQuotedString("abcdef".to_string()))),
+            overlay_what: Box::new(Expr::Identifier(Ident::new("name"))),
+            overlay_from: Box::new(Expr::Value(number("3"))),
+            overlay_for: Some(Box::new(Expr::BinaryOp {
+                left: Box::new(Expr::Identifier(Ident::new("id"))),
+                op: BinaryOperator::Plus,
+                right: Box::new(Expr::Value(number("1"))),
+            }))
+        },
+        expr_from_projection(only(&select.projection))
+    );
+}
+
 #[test]
 fn parse_trim() {
     one_statement_parses_to(
@@ -5334,21 +5366,50 @@ fn parse_position_negative() {
 fn parse_is_boolean() {
     use self::Expr::*;
 
+    let sql = "a IS TRUE";
+    assert_eq!(
+        IsTrue(Box::new(Identifier(Ident::new("a")))),
+        verified_expr(sql)
+    );
+
+    let sql = "a IS NOT TRUE";
+    assert_eq!(
+        IsNotTrue(Box::new(Identifier(Ident::new("a")))),
+        verified_expr(sql)
+    );
+
     let sql = "a IS FALSE";
     assert_eq!(
         IsFalse(Box::new(Identifier(Ident::new("a")))),
         verified_expr(sql)
     );
 
-    let sql = "a IS TRUE";
+    let sql = "a IS NOT FALSE";
     assert_eq!(
-        IsTrue(Box::new(Identifier(Ident::new("a")))),
+        IsNotFalse(Box::new(Identifier(Ident::new("a")))),
+        verified_expr(sql)
+    );
+
+    let sql = "a IS UNKNOWN";
+    assert_eq!(
+        IsUnknown(Box::new(Identifier(Ident::new("a")))),
+        verified_expr(sql)
+    );
+
+    let sql = "a IS NOT UNKNOWN";
+    assert_eq!(
+        IsNotUnknown(Box::new(Identifier(Ident::new("a")))),
         verified_expr(sql)
     );
 
     verified_stmt("SELECT f FROM foo WHERE field IS TRUE");
+    verified_stmt("SELECT f FROM foo WHERE field IS NOT TRUE");
 
     verified_stmt("SELECT f FROM foo WHERE field IS FALSE");
+    verified_stmt("SELECT f FROM foo WHERE field IS NOT FALSE");
+
+    verified_stmt("SELECT f FROM foo WHERE field IS UNKNOWN");
+    verified_stmt("SELECT f FROM foo WHERE field IS NOT UNKNOWN");
 
     let sql = "SELECT f from foo where field is 0";
     let res = parse_sql_statements(sql);