From 5f156e8c88f4729f569ee5b4ac9378dda3907997 Mon Sep 17 00:00:00 2001 From: TOGASHI Tomoki Date: Tue, 30 Jan 2024 13:49:08 +0900 Subject: [PATCH] feat(spanner/spansql): add support for CREATE VIEW with SQL SECURITY DEFINER (#8754) Co-authored-by: rahul2393 --- spanner/spansql/parser.go | 27 ++++++++++++++++++++++----- spanner/spansql/parser_test.go | 32 ++++++++++++++++++++++++++++---- spanner/spansql/sql.go | 12 +++++++++++- spanner/spansql/sql_test.go | 23 +++++++++++++++++++++-- spanner/spansql/types.go | 14 +++++++++++--- 5 files changed, 93 insertions(+), 15 deletions(-) diff --git a/spanner/spansql/parser.go b/spanner/spansql/parser.go index 81eebb3aeae3..b2feb3fe1452 100644 --- a/spanner/spansql/parser.go +++ b/spanner/spansql/parser.go @@ -1311,7 +1311,7 @@ func (p *parser) parseCreateView() (*CreateView, *parseError) { /* { CREATE VIEW | CREATE OR REPLACE VIEW } view_name - SQL SECURITY INVOKER + SQL SECURITY {INVOKER | DEFINER} AS query */ @@ -1328,7 +1328,23 @@ func (p *parser) parseCreateView() (*CreateView, *parseError) { return nil, err } vname, err := p.parseTableOrIndexOrColumnName() - if err := p.expect("SQL", "SECURITY", "INVOKER", "AS"); err != nil { + if err := p.expect("SQL", "SECURITY"); err != nil { + return nil, err + } + tok := p.next() + if tok.err != nil { + return nil, tok.err + } + var securityType SecurityType + switch { + case tok.caseEqual("INVOKER"): + securityType = Invoker + case tok.caseEqual("DEFINER"): + securityType = Definer + default: + return nil, p.errorf("got %q, want INVOKER or DEFINER", tok.value) + } + if err := p.expect("AS"); err != nil { return nil, err } query, err := p.parseQuery() @@ -1337,9 +1353,10 @@ func (p *parser) parseCreateView() (*CreateView, *parseError) { } return &CreateView{ - Name: vname, - OrReplace: orReplace, - Query: query, + Name: vname, + OrReplace: orReplace, + SecurityType: securityType, + Query: query, Position: pos, }, nil diff --git a/spanner/spansql/parser_test.go b/spanner/spansql/parser_test.go index c185f4e86965..6d534abcb071 100644 --- a/spanner/spansql/parser_test.go +++ b/spanner/spansql/parser_test.go @@ -904,8 +904,9 @@ func TestParseDDL(t *testing.T) { Position: line(58), }, &CreateView{ - Name: "SingersView", - OrReplace: false, + Name: "SingersView", + OrReplace: false, + SecurityType: Invoker, Query: Query{ Select: Select{ List: []Expr{ID("SingerId"), ID("FullName")}, @@ -1298,8 +1299,9 @@ func TestParseDDL(t *testing.T) { &DDL{ Filename: "filename", List: []DDLStmt{ &CreateView{ - Name: "SingersView", - OrReplace: true, + Name: "SingersView", + OrReplace: true, + SecurityType: Invoker, Query: Query{ Select: Select{ List: []Expr{ID("SingerId"), ID("FullName"), ID("Picture")}, @@ -1686,6 +1688,28 @@ func TestParseDDL(t *testing.T) { }, }, }, + { + `CREATE VIEW vname SQL SECURITY DEFINER AS SELECT cname FROM tname;`, + &DDL{ + Filename: "filename", + List: []DDLStmt{ + &CreateView{ + Name: "vname", + OrReplace: false, + SecurityType: Definer, + Query: Query{ + Select: Select{ + List: []Expr{ID("cname")}, + From: []SelectFrom{SelectFromTable{ + Table: "tname", + }}, + }, + }, + Position: line(1), + }, + }, + }, + }, } for _, test := range tests { got, err := ParseDDL("filename", test.in) diff --git a/spanner/spansql/sql.go b/spanner/spansql/sql.go index 2b853f1bd650..687a18f58b42 100644 --- a/spanner/spansql/sql.go +++ b/spanner/spansql/sql.go @@ -100,10 +100,20 @@ func (cv CreateView) SQL() string { if cv.OrReplace { str += " OR REPLACE" } - str += " VIEW " + cv.Name.SQL() + " SQL SECURITY INVOKER AS " + cv.Query.SQL() + str += " VIEW " + cv.Name.SQL() + " SQL SECURITY " + cv.SecurityType.SQL() + " AS " + cv.Query.SQL() return str } +func (st SecurityType) SQL() string { + switch st { + case Invoker: + return "INVOKER" + case Definer: + return "DEFINER" + } + panic("unknown SecurityType") +} + func (cr CreateRole) SQL() string { return "CREATE ROLE " + cr.Name.SQL() } diff --git a/spanner/spansql/sql_test.go b/spanner/spansql/sql_test.go index 06a74947672c..645190b316bc 100644 --- a/spanner/spansql/sql_test.go +++ b/spanner/spansql/sql_test.go @@ -199,8 +199,9 @@ func TestSQL(t *testing.T) { }, { &CreateView{ - Name: "SingersView", - OrReplace: true, + Name: "SingersView", + OrReplace: true, + SecurityType: Invoker, Query: Query{ Select: Select{ List: []Expr{ID("SingerId"), ID("FullName"), ID("Picture")}, @@ -218,6 +219,24 @@ func TestSQL(t *testing.T) { "CREATE OR REPLACE VIEW SingersView SQL SECURITY INVOKER AS SELECT SingerId, FullName, Picture FROM Singers ORDER BY LastName, FirstName", reparseDDL, }, + { + &CreateView{ + Name: "vname", + OrReplace: false, + SecurityType: Definer, + Query: Query{ + Select: Select{ + List: []Expr{ID("cname")}, + From: []SelectFrom{SelectFromTable{ + Table: "tname", + }}, + }, + }, + Position: line(1), + }, + "CREATE VIEW vname SQL SECURITY DEFINER AS SELECT cname FROM tname", + reparseDDL, + }, { &DropView{ Name: "SingersView", diff --git a/spanner/spansql/types.go b/spanner/spansql/types.go index ad089a82f83b..ea4016ae0f72 100644 --- a/spanner/spansql/types.go +++ b/spanner/spansql/types.go @@ -123,9 +123,10 @@ func (ci *CreateIndex) clearOffset() { ci.Position.Offset = 0 } // CreateView represents a CREATE [OR REPLACE] VIEW statement. // https://cloud.google.com/spanner/docs/data-definition-language#view_statements type CreateView struct { - Name ID - OrReplace bool - Query Query + Name ID + OrReplace bool + SecurityType SecurityType + Query Query Position Position // position of the "CREATE" token } @@ -135,6 +136,13 @@ func (*CreateView) isDDLStmt() {} func (cv *CreateView) Pos() Position { return cv.Position } func (cv *CreateView) clearOffset() { cv.Position.Offset = 0 } +type SecurityType int + +const ( + Invoker SecurityType = iota + Definer +) + // CreateRole represents a CREATE Role statement. // https://cloud.google.com/spanner/docs/reference/standard-sql/data-definition-language#create_role type CreateRole struct {