diff --git a/spanner/spansql/parser.go b/spanner/spansql/parser.go index 69ab94dca36c..a68e5b1f6f0a 100644 --- a/spanner/spansql/parser.go +++ b/spanner/spansql/parser.go @@ -3041,6 +3041,9 @@ func (p *parser) parseLit() (Expr, *parseError) { case tok.caseEqual("IF"): p.back() return p.parseIfExpr() + case tok.caseEqual("IFNULL"): + p.back() + return p.parseIfNullExpr() } // Handle typed literals. @@ -3183,6 +3186,30 @@ func (p *parser) parseIfExpr() (If, *parseError) { return If{Expr: expr, TrueResult: trueResult, ElseResult: elseResult}, nil } +func (p *parser) parseIfNullExpr() (IfNull, *parseError) { + if err := p.expect("IFNULL", "("); err != nil { + return IfNull{}, err + } + + expr, err := p.parseExpr() + if err != nil { + return IfNull{}, err + } + if err := p.expect(","); err != nil { + return IfNull{}, err + } + + nullResult, err := p.parseExpr() + if err != nil { + return IfNull{}, err + } + if err := p.expect(")"); err != nil { + return IfNull{}, err + } + + return IfNull{Expr: expr, NullResult: nullResult}, nil +} + func (p *parser) parseArrayLit() (Array, *parseError) { // ARRAY keyword is optional. // TODO: If it is present, consume any after it. diff --git a/spanner/spansql/parser_test.go b/spanner/spansql/parser_test.go index b7c6630023e9..4837df40f22c 100644 --- a/spanner/spansql/parser_test.go +++ b/spanner/spansql/parser_test.go @@ -440,6 +440,12 @@ func TestParseExpr(t *testing.T) { ElseResult: False, }, }, + {`IFNULL(NULL, TRUE)`, + IfNull{ + Expr: Null, + NullResult: True, + }, + }, // String literal: // Accept double quote and single quote. diff --git a/spanner/spansql/sql.go b/spanner/spansql/sql.go index a45cda4bdb32..23573ee2f5fb 100644 --- a/spanner/spansql/sql.go +++ b/spanner/spansql/sql.go @@ -744,6 +744,15 @@ func (i If) addSQL(sb *strings.Builder) { sb.WriteString(")") } +func (in IfNull) SQL() string { return buildSQL(in) } +func (in IfNull) addSQL(sb *strings.Builder) { + sb.WriteString("IFNULL(") + in.Expr.addSQL(sb) + sb.WriteString(", ") + in.NullResult.addSQL(sb) + sb.WriteString(")") +} + func (b BoolLiteral) SQL() string { return buildSQL(b) } func (b BoolLiteral) addSQL(sb *strings.Builder) { if b { diff --git a/spanner/spansql/sql_test.go b/spanner/spansql/sql_test.go index 6e51263b65b3..8e09e47cd15c 100644 --- a/spanner/spansql/sql_test.go +++ b/spanner/spansql/sql_test.go @@ -671,6 +671,20 @@ func TestSQL(t *testing.T) { `SELECT IF(1 < 2, TRUE, FALSE)`, reparseQuery, }, + { + Query{ + Select: Select{ + List: []Expr{ + IfNull{ + Expr: IntegerLiteral(10), + NullResult: IntegerLiteral(0), + }, + }, + }, + }, + `SELECT IFNULL(10, 0)`, + reparseQuery, + }, } for _, test := range tests { sql := test.data.SQL() diff --git a/spanner/spansql/types.go b/spanner/spansql/types.go index badd87c5add0..7df108c63202 100644 --- a/spanner/spansql/types.go +++ b/spanner/spansql/types.go @@ -752,6 +752,14 @@ type If struct { func (If) isBoolExpr() {} // possibly bool func (If) isExpr() {} +type IfNull struct { + Expr Expr + NullResult Expr +} + +func (IfNull) isBoolExpr() {} // possibly bool +func (IfNull) isExpr() {} + type BoolLiteral bool const (