Skip to content

Commit

Permalink
sql: implement COMMENT ON TABLE
Browse files Browse the repository at this point in the history
See cockroachdb#19472

Release note (sql change): support COMMENT ON TABLE syntax
  • Loading branch information
hueypark committed Nov 17, 2018
1 parent 02dcaf5 commit b26e64f
Show file tree
Hide file tree
Showing 16 changed files with 453 additions and 200 deletions.
8 changes: 8 additions & 0 deletions docs/generated/sql/bnf/stmt_block.bnf
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ stmt ::=
'HELPTOKEN'
| preparable_stmt
| copy_from_stmt
| comment_stmt
| execute_stmt
| deallocate_stmt
| discard_stmt
Expand Down Expand Up @@ -46,6 +47,9 @@ preparable_stmt ::=
copy_from_stmt ::=
'COPY' table_name opt_column_list 'FROM' 'STDIN'

comment_stmt ::=
'COMMENT' 'ON' 'TABLE' table_name 'IS' comment_text

execute_stmt ::=
'EXECUTE' table_alias_name execute_param_clause

Expand Down Expand Up @@ -196,6 +200,10 @@ opt_column_list ::=
'(' name_list ')'
|

comment_text ::=
'SCONST'
| 'NULL'

table_alias_name ::=
name

Expand Down
87 changes: 87 additions & 0 deletions pkg/sql/comment_on_table.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// Copyright 2018 The Cockroach Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
// implied. See the License for the specific language governing
// permissions and limitations under the License.

package sql

import (
"context"
"github.com/cockroachdb/cockroach/pkg/sql/privilege"

"github.com/cockroachdb/cockroach/pkg/sql/sem/tree"
)

type commentOnTableNode struct {
n *tree.CommentOnTable
tableDesc *MutableTableDescriptor
}

// CommentOnTable add comment on a table.
// Privileges: CREATE on table.
// notes: postgres requires CREATE on the table.
// mysql requires ALTER, CREATE, INSERT on the table.
func (p *planner) CommentOnTable(ctx context.Context, n *tree.CommentOnTable) (planNode, error) {
tableDesc, err := p.ResolveMutableTableDescriptor(ctx, &n.Table, true, requireTableDesc)
if err != nil {
return nil, err
}
if tableDesc == nil {
return newZeroNode(nil /* columns */), nil
}

if err := p.CheckPrivilege(ctx, tableDesc, privilege.CREATE); err != nil {
return nil, err
}

return &commentOnTableNode{n: n, tableDesc: tableDesc}, nil
}

func (n *commentOnTableNode) startExec(params runParams) error {
n.tableDesc.Comment = n.n.Comment

mutationID, err := params.p.createSchemaChangeJob(
params.ctx,
n.tableDesc,
tree.AsStringWithFlags(n.n, tree.FmtAlwaysQualifyTableNames))
if err != nil {
return err
}

if err := params.p.writeSchemaChange(params.ctx, n.tableDesc, mutationID); err != nil {
return err
}

return MakeEventLogger(params.extendedEvalCtx.ExecCfg).InsertEventRecord(
params.ctx,
params.p.txn,
EventLogCommentOnTable,
int32(n.tableDesc.ID),
int32(params.extendedEvalCtx.NodeID),
struct {
TableName string
Statement string
User string
MutationID uint32
Comment *string
}{
n.n.Table.FQString(),
n.n.String(),
params.SessionData().User,
uint32(mutationID),
n.n.Comment},
)
}

func (n *commentOnTableNode) Next(runParams) (bool, error) { return false, nil }
func (n *commentOnTableNode) Values() tree.Datums { return tree.Datums{} }
func (n *commentOnTableNode) Close(context.Context) {}
2 changes: 2 additions & 0 deletions pkg/sql/event_log.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ const (
EventLogTruncateTable EventLogType = "truncate_table"
// EventLogAlterTable is recorded when a table is altered.
EventLogAlterTable EventLogType = "alter_table"
// EventLogCommentOnTable is recorded when a table is commented.
EventLogCommentOnTable EventLogType = "comment_on_table"

// EventLogCreateIndex is recorded when an index is created.
EventLogCreateIndex EventLogType = "create_index"
Expand Down
2 changes: 2 additions & 0 deletions pkg/sql/expand_plan.go
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,7 @@ func doExpandPlan(
case *alterTableNode:
case *alterSequenceNode:
case *alterUserSetPasswordNode:
case *commentOnTableNode:
case *renameColumnNode:
case *renameDatabaseNode:
case *renameIndexNode:
Expand Down Expand Up @@ -854,6 +855,7 @@ func (p *planner) simplifyOrderings(plan planNode, usefulOrdering sqlbase.Column
case *alterTableNode:
case *alterSequenceNode:
case *alterUserSetPasswordNode:
case *commentOnTableNode:
case *renameColumnNode:
case *renameDatabaseNode:
case *renameIndexNode:
Expand Down
1 change: 1 addition & 0 deletions pkg/sql/opt_filters.go
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,7 @@ func (p *planner) propagateFilters(
case *renameTableNode:
case *scrubNode:
case *truncateNode:
case *commentOnTableNode:
case *createDatabaseNode:
case *createIndexNode:
case *CreateUserNode:
Expand Down
1 change: 1 addition & 0 deletions pkg/sql/opt_limits.go
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,7 @@ func (p *planner) applyLimit(plan planNode, numRows int64, soft bool) {
case *renameTableNode:
case *scrubNode:
case *truncateNode:
case *commentOnTableNode:
case *createDatabaseNode:
case *createIndexNode:
case *CreateUserNode:
Expand Down
1 change: 1 addition & 0 deletions pkg/sql/opt_needed.go
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,7 @@ func setNeededColumns(plan planNode, needed []bool) {
case *renameTableNode:
case *scrubNode:
case *truncateNode:
case *commentOnTableNode:
case *createDatabaseNode:
case *createIndexNode:
case *CreateUserNode:
Expand Down
4 changes: 3 additions & 1 deletion pkg/sql/parser/parse_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1126,6 +1126,9 @@ func TestParse(t *testing.T) {
{`EXPLAIN ALTER TABLE t EXPERIMENTAL_AUDIT SET READ WRITE`},
{`ALTER TABLE t EXPERIMENTAL_AUDIT SET OFF`},

{`COMMENT ON TABLE foo IS 'a'`},
{`COMMENT ON TABLE foo IS NULL`},

{`ALTER SEQUENCE a RENAME TO b`},
{`EXPLAIN ALTER SEQUENCE a RENAME TO b`},
{`ALTER SEQUENCE IF EXISTS a RENAME TO b`},
Expand Down Expand Up @@ -2574,7 +2577,6 @@ func TestUnimplementedSyntax(t *testing.T) {

{`COMMENT ON COLUMN a.b IS 'a'`, 19472, `column`},
{`COMMENT ON DATABASE a IS 'b'`, 19472, ``},
{`COMMENT ON TABLE foo IS 'a'`, 19472, `table`},

{`CREATE AGGREGATE a`, 0, `create aggregate`},
{`CREATE CAST a`, 0, `create cast`},
Expand Down
20 changes: 16 additions & 4 deletions pkg/sql/parser/sql.y
Original file line number Diff line number Diff line change
Expand Up @@ -722,7 +722,7 @@ func newNameFromStr(s string) *tree.Name {
%type <tree.Statement> show_zone_stmt

%type <str> session_var
%type <str> comment_text
%type <*string> comment_text

%type <tree.Statement> transaction_stmt
%type <tree.Statement> truncate_stmt
Expand Down Expand Up @@ -2007,7 +2007,12 @@ cancel_sessions_stmt:
comment_stmt:
COMMENT ON TABLE table_name IS comment_text
{
return unimplementedWithIssueDetail(sqllex, 19472, "table")
name, err := tree.NormalizeTableName($4.unresolvedName())
if err != nil {
sqllex.Error(err.Error())
return 1
}
$$.val = &tree.CommentOnTable{Table: name, Comment: $6.strPtr()}
}
| COMMENT ON COLUMN column_path IS comment_text
{
Expand All @@ -2019,8 +2024,15 @@ comment_stmt:
}
comment_text:
SCONST { $$ = $1 }
| NULL { $$ = "" }
SCONST
{
$$.val = &$1
}
| NULL
{
var str *string
$$.val = str
}
// %Help: CREATE
// %Category: Group
Expand Down
33 changes: 30 additions & 3 deletions pkg/sql/pg_catalog.go
Original file line number Diff line number Diff line change
Expand Up @@ -941,9 +941,36 @@ CREATE TABLE pg_catalog.pg_description (
description STRING
);
`,
populate: func(_ context.Context, p *planner, _ *DatabaseDescriptor, addRow func(...tree.Datum) error) error {
// Comments on database objects are not currently supported.
return nil
populate: func(
ctx context.Context,
p *planner,
dbContext *DatabaseDescriptor,
addRow func(...tree.Datum) error) error {
h := makeOidHasher()
return forEachTableDesc(
ctx,
p,
dbContext,
virtualMany,
func(db *sqlbase.DatabaseDescriptor, scName string, table *sqlbase.TableDescriptor) error {
var comment tree.Datum
if table.Comment != nil {
comment = tree.NewDName(*table.Comment)
} else {
comment = tree.DNull
}

if err := addRow(
h.TableOid(db, scName, table), // oid
oidZero, // classoid
zeroVal, // objsubid
comment, //description
); err != nil {
return err
}

return nil
})
},
}

Expand Down
2 changes: 2 additions & 0 deletions pkg/sql/plan.go
Original file line number Diff line number Diff line change
Expand Up @@ -772,6 +772,8 @@ func (p *planner) newPlan(
return p.CancelQueries(ctx, n)
case *tree.CancelSessions:
return p.CancelSessions(ctx, n)
case *tree.CommentOnTable:
return p.CommentOnTable(ctx, n)
case *tree.ControlJobs:
return p.ControlJobs(ctx, n)
case *tree.Scrub:
Expand Down
15 changes: 13 additions & 2 deletions pkg/sql/sem/builtins/pg_builtins.go
Original file line number Diff line number Diff line change
Expand Up @@ -724,8 +724,19 @@ var pgBuiltins = map[string]builtinDefinition{
tree.Overload{
Types: tree.ArgTypes{{"object_oid", types.Oid}},
ReturnType: tree.FixedReturnType(types.String),
Fn: func(_ *tree.EvalContext, _ tree.Datums) (tree.Datum, error) {
return tree.DNull, nil
Fn: func(ctx *tree.EvalContext, args tree.Datums) (tree.Datum, error) {
oid := args[0]
r, err := ctx.InternalExecutor.QueryRow(
ctx.Ctx(), "pg_get_objdesc",
ctx.Txn,
"SELECT description FROM pg_catalog.pg_description WHERE objoid=$1 LIMIT 1", oid)
if err != nil {
return nil, err
}
if len(r) == 0 {
return tree.DNull, nil
}
return r[0], nil
},
Info: notUsableInfo,
},
Expand Down
41 changes: 41 additions & 0 deletions pkg/sql/sem/tree/comment_on_table.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Copyright 2018 The Cockroach Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
// implied. See the License for the specific language governing
// permissions and limitations under the License.

package tree

import "github.com/cockroachdb/cockroach/pkg/sql/lex"

// CommentOnTable represents an COMMENT ON TABLE statement.
type CommentOnTable struct {
Table TableName
Comment *string
}

func (node *CommentOnTable) String() string {
return "Comment"
}

func (node *CommentOnTable) StatementType() StatementType { return DDL }
func (node *CommentOnTable) StatementTag() string { return "COMMENT ON TABLE" }

func (node *CommentOnTable) Format(ctx *FmtCtx) {
ctx.WriteString("COMMENT ON TABLE ")
ctx.FormatNode(&node.Table)
ctx.WriteString(" IS ")
if node.Comment != nil {
lex.EncodeSQLStringWithFlags(ctx.Buffer, *node.Comment, ctx.flags.EncodeFlags())
} else {
ctx.WriteString(" NULL ")
}
}
Loading

0 comments on commit b26e64f

Please sign in to comment.