From 8de04c5f54bc3d76e19d6e0713b3aacc61bbf6b3 Mon Sep 17 00:00:00 2001 From: TOGASHI Tomoki Date: Sun, 22 Oct 2023 10:42:45 +0900 Subject: [PATCH] Add support for CREATE VIEW with SQL SECURITY DEFINER (#75) --- ast/ast.go | 9 +- ast/const.go | 7 + ast/sql.go | 2 +- parser.go | 22 +++- .../ddl/create_view_sql_security_definer.sql | 6 + .../result/ddl/create_or_replace_view.sql.txt | 5 +- testdata/result/ddl/create_view.sql.txt | 5 +- .../create_view_sql_security_definer.sql.txt | 120 ++++++++++++++++++ .../statement/create_or_replace_view.sql.txt | 5 +- testdata/result/statement/create_view.sql.txt | 5 +- .../create_view_sql_security_definer.sql.txt | 120 ++++++++++++++++++ 11 files changed, 288 insertions(+), 18 deletions(-) create mode 100644 testdata/input/ddl/create_view_sql_security_definer.sql create mode 100644 testdata/result/ddl/create_view_sql_security_definer.sql.txt create mode 100644 testdata/result/statement/create_view_sql_security_definer.sql.txt diff --git a/ast/ast.go b/ast/ast.go index 39b68572..445eb898 100644 --- a/ast/ast.go +++ b/ast/ast.go @@ -1599,7 +1599,7 @@ type RowDeletionPolicy struct { // CreateView is CREATE VIEW statement node. // // CREATE {{if .OrReplace}}OR REPLACE{{end}} VIEW {{.Name | sql}} -// SQL SECURITY INVOKER AS +// SQL SECURITY {{.SecurityType}} AS // {{.Query | sql}} type CreateView struct { // pos = Create @@ -1607,9 +1607,10 @@ type CreateView struct { Create token.Pos - Name *Ident - OrReplace bool - Query QueryExpr + Name *Ident + OrReplace bool + SecurityType SecurityType + Query QueryExpr } // AlterTable is ALTER TABLE statement node. diff --git a/ast/const.go b/ast/const.go index 70315835..57840e25 100644 --- a/ast/const.go +++ b/ast/const.go @@ -115,3 +115,10 @@ const ( OnDeleteCascade OnDeleteAction = "ON DELETE CASCADE" OnDeleteNoAction OnDeleteAction = "ON DELETE NO ACTION" ) + +type SecurityType string + +const ( + SecurityTypeInvoker SecurityType = "INVOKER" + SecurityTypeDefiner SecurityType = "DEFINER" +) diff --git a/ast/sql.go b/ast/sql.go index 1750522d..02b7df27 100644 --- a/ast/sql.go +++ b/ast/sql.go @@ -771,7 +771,7 @@ func (c *CreateView) SQL() string { if c.OrReplace { sql += " OR REPLACE" } - sql += " VIEW " + c.Name.SQL() + " SQL SECURITY INVOKER AS " + c.Query.SQL() + sql += " VIEW " + c.Name.SQL() + " SQL SECURITY " + string(c.SecurityType) + " AS " + c.Query.SQL() return sql } diff --git a/parser.go b/parser.go index 62e19c81..42757cce 100644 --- a/parser.go +++ b/parser.go @@ -2164,16 +2164,28 @@ func (p *Parser) parseCreateView(pos token.Pos) *ast.CreateView { p.expectKeywordLike("SQL") p.expectKeywordLike("SECURITY") - p.expectKeywordLike("INVOKER") + + id := p.expect(token.TokenIdent) + var securityType ast.SecurityType + switch { + case id.IsIdent("INVOKER"): + securityType = ast.SecurityTypeInvoker + case id.IsIdent("DEFINER"): + securityType = ast.SecurityTypeDefiner + default: + p.panicfAtToken(id, "expected identifier: INVOKER, DEFINER, but: %s", id.Raw) + } + p.expect("AS") query := p.parseQueryExpr() return &ast.CreateView{ - Create: pos, - Name: name, - OrReplace: orReplace, - Query: query, + Create: pos, + Name: name, + OrReplace: orReplace, + SecurityType: securityType, + Query: query, } } diff --git a/testdata/input/ddl/create_view_sql_security_definer.sql b/testdata/input/ddl/create_view_sql_security_definer.sql new file mode 100644 index 00000000..b9b89231 --- /dev/null +++ b/testdata/input/ddl/create_view_sql_security_definer.sql @@ -0,0 +1,6 @@ +create view singernames +sql security definer +as select + singers.singerid as singerid, + singers.firstname || ' ' || singers.lastname as name +from singers diff --git a/testdata/result/ddl/create_or_replace_view.sql.txt b/testdata/result/ddl/create_or_replace_view.sql.txt index a23a1adf..e2ffed9e 100644 --- a/testdata/result/ddl/create_or_replace_view.sql.txt +++ b/testdata/result/ddl/create_or_replace_view.sql.txt @@ -14,8 +14,9 @@ from singers NameEnd: 34, Name: "singernames", }, - OrReplace: true, - Query: &ast.Select{ + OrReplace: true, + SecurityType: "INVOKER", + Query: &ast.Select{ Select: 59, Distinct: false, AsStruct: false, diff --git a/testdata/result/ddl/create_view.sql.txt b/testdata/result/ddl/create_view.sql.txt index 3e93fcd8..ecd9b7cb 100644 --- a/testdata/result/ddl/create_view.sql.txt +++ b/testdata/result/ddl/create_view.sql.txt @@ -14,8 +14,9 @@ from singers NameEnd: 23, Name: "singernames", }, - OrReplace: false, - Query: &ast.Select{ + OrReplace: false, + SecurityType: "INVOKER", + Query: &ast.Select{ Select: 48, Distinct: false, AsStruct: false, diff --git a/testdata/result/ddl/create_view_sql_security_definer.sql.txt b/testdata/result/ddl/create_view_sql_security_definer.sql.txt new file mode 100644 index 00000000..11c5a8a4 --- /dev/null +++ b/testdata/result/ddl/create_view_sql_security_definer.sql.txt @@ -0,0 +1,120 @@ +--- create_view_sql_security_definer.sql +create view singernames +sql security definer +as select + singers.singerid as singerid, + singers.firstname || ' ' || singers.lastname as name +from singers + +--- AST +&ast.CreateView{ + Create: 0, + Name: &ast.Ident{ + NamePos: 12, + NameEnd: 23, + Name: "singernames", + }, + OrReplace: false, + SecurityType: "DEFINER", + Query: &ast.Select{ + Select: 48, + Distinct: false, + AsStruct: false, + Results: []ast.SelectItem{ + &ast.Alias{ + Expr: &ast.Path{ + Idents: []*ast.Ident{ + &ast.Ident{ + NamePos: 59, + NameEnd: 66, + Name: "singers", + }, + &ast.Ident{ + NamePos: 67, + NameEnd: 75, + Name: "singerid", + }, + }, + }, + As: &ast.AsAlias{ + As: -1, + Alias: &ast.Ident{ + NamePos: 79, + NameEnd: 87, + Name: "singerid", + }, + }, + }, + &ast.Alias{ + Expr: &ast.BinaryExpr{ + Op: "||", + Left: &ast.BinaryExpr{ + Op: "||", + Left: &ast.Path{ + Idents: []*ast.Ident{ + &ast.Ident{ + NamePos: 93, + NameEnd: 100, + Name: "singers", + }, + &ast.Ident{ + NamePos: 101, + NameEnd: 110, + Name: "firstname", + }, + }, + }, + Right: &ast.StringLiteral{ + ValuePos: 114, + ValueEnd: 117, + Value: " ", + }, + }, + Right: &ast.Path{ + Idents: []*ast.Ident{ + &ast.Ident{ + NamePos: 121, + NameEnd: 128, + Name: "singers", + }, + &ast.Ident{ + NamePos: 129, + NameEnd: 137, + Name: "lastname", + }, + }, + }, + }, + As: &ast.AsAlias{ + As: -1, + Alias: &ast.Ident{ + NamePos: 141, + NameEnd: 145, + Name: "name", + }, + }, + }, + }, + From: &ast.From{ + From: 146, + Source: &ast.TableName{ + Table: &ast.Ident{ + NamePos: 151, + NameEnd: 158, + Name: "singers", + }, + Hint: (*ast.Hint)(nil), + As: (*ast.AsAlias)(nil), + Sample: (*ast.TableSample)(nil), + }, + }, + Where: (*ast.Where)(nil), + GroupBy: (*ast.GroupBy)(nil), + Having: (*ast.Having)(nil), + OrderBy: (*ast.OrderBy)(nil), + Limit: (*ast.Limit)(nil), + }, +} + +--- SQL +CREATE VIEW singernames SQL SECURITY DEFINER AS SELECT singers.singerid AS singerid, singers.firstname || " " || singers.lastname AS name FROM singers diff --git a/testdata/result/statement/create_or_replace_view.sql.txt b/testdata/result/statement/create_or_replace_view.sql.txt index a23a1adf..e2ffed9e 100644 --- a/testdata/result/statement/create_or_replace_view.sql.txt +++ b/testdata/result/statement/create_or_replace_view.sql.txt @@ -14,8 +14,9 @@ from singers NameEnd: 34, Name: "singernames", }, - OrReplace: true, - Query: &ast.Select{ + OrReplace: true, + SecurityType: "INVOKER", + Query: &ast.Select{ Select: 59, Distinct: false, AsStruct: false, diff --git a/testdata/result/statement/create_view.sql.txt b/testdata/result/statement/create_view.sql.txt index 3e93fcd8..ecd9b7cb 100644 --- a/testdata/result/statement/create_view.sql.txt +++ b/testdata/result/statement/create_view.sql.txt @@ -14,8 +14,9 @@ from singers NameEnd: 23, Name: "singernames", }, - OrReplace: false, - Query: &ast.Select{ + OrReplace: false, + SecurityType: "INVOKER", + Query: &ast.Select{ Select: 48, Distinct: false, AsStruct: false, diff --git a/testdata/result/statement/create_view_sql_security_definer.sql.txt b/testdata/result/statement/create_view_sql_security_definer.sql.txt new file mode 100644 index 00000000..11c5a8a4 --- /dev/null +++ b/testdata/result/statement/create_view_sql_security_definer.sql.txt @@ -0,0 +1,120 @@ +--- create_view_sql_security_definer.sql +create view singernames +sql security definer +as select + singers.singerid as singerid, + singers.firstname || ' ' || singers.lastname as name +from singers + +--- AST +&ast.CreateView{ + Create: 0, + Name: &ast.Ident{ + NamePos: 12, + NameEnd: 23, + Name: "singernames", + }, + OrReplace: false, + SecurityType: "DEFINER", + Query: &ast.Select{ + Select: 48, + Distinct: false, + AsStruct: false, + Results: []ast.SelectItem{ + &ast.Alias{ + Expr: &ast.Path{ + Idents: []*ast.Ident{ + &ast.Ident{ + NamePos: 59, + NameEnd: 66, + Name: "singers", + }, + &ast.Ident{ + NamePos: 67, + NameEnd: 75, + Name: "singerid", + }, + }, + }, + As: &ast.AsAlias{ + As: -1, + Alias: &ast.Ident{ + NamePos: 79, + NameEnd: 87, + Name: "singerid", + }, + }, + }, + &ast.Alias{ + Expr: &ast.BinaryExpr{ + Op: "||", + Left: &ast.BinaryExpr{ + Op: "||", + Left: &ast.Path{ + Idents: []*ast.Ident{ + &ast.Ident{ + NamePos: 93, + NameEnd: 100, + Name: "singers", + }, + &ast.Ident{ + NamePos: 101, + NameEnd: 110, + Name: "firstname", + }, + }, + }, + Right: &ast.StringLiteral{ + ValuePos: 114, + ValueEnd: 117, + Value: " ", + }, + }, + Right: &ast.Path{ + Idents: []*ast.Ident{ + &ast.Ident{ + NamePos: 121, + NameEnd: 128, + Name: "singers", + }, + &ast.Ident{ + NamePos: 129, + NameEnd: 137, + Name: "lastname", + }, + }, + }, + }, + As: &ast.AsAlias{ + As: -1, + Alias: &ast.Ident{ + NamePos: 141, + NameEnd: 145, + Name: "name", + }, + }, + }, + }, + From: &ast.From{ + From: 146, + Source: &ast.TableName{ + Table: &ast.Ident{ + NamePos: 151, + NameEnd: 158, + Name: "singers", + }, + Hint: (*ast.Hint)(nil), + As: (*ast.AsAlias)(nil), + Sample: (*ast.TableSample)(nil), + }, + }, + Where: (*ast.Where)(nil), + GroupBy: (*ast.GroupBy)(nil), + Having: (*ast.Having)(nil), + OrderBy: (*ast.OrderBy)(nil), + Limit: (*ast.Limit)(nil), + }, +} + +--- SQL +CREATE VIEW singernames SQL SECURITY DEFINER AS SELECT singers.singerid AS singerid, singers.firstname || " " || singers.lastname AS name FROM singers