Files
2026-07-13 12:32:25 +08:00

239 lines
7.6 KiB
Go

// Copyright 2023 Dolthub, Inc.
//
// 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 ast
import (
"strings"
"github.com/cockroachdb/errors"
"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
"github.com/dolthub/doltgresql/postgres/parser/privilege"
"github.com/dolthub/doltgresql/postgres/parser/sem/tree"
"github.com/dolthub/doltgresql/server/auth"
pgnodes "github.com/dolthub/doltgresql/server/node"
)
// nodeGrant handles *tree.Grant nodes.
func nodeGrant(ctx *Context, node *tree.Grant) (vitess.Statement, error) {
if node == nil {
return nil, nil
}
var grantTable *pgnodes.GrantTable
var grantSchema *pgnodes.GrantSchema
var grantDatabase *pgnodes.GrantDatabase
var grantSequence *pgnodes.GrantSequence
var grantRoutine *pgnodes.GrantRoutine
switch node.Targets.TargetType {
case privilege.Table:
tables := make([]doltdb.TableName, 0, len(node.Targets.Tables)+len(node.Targets.InSchema))
for _, table := range node.Targets.Tables {
normalizedTable, err := table.NormalizeTablePattern()
if err != nil {
return nil, err
}
switch normalizedTable := normalizedTable.(type) {
case *tree.TableName:
if normalizedTable.ExplicitCatalog {
return nil, errors.Errorf("granting privileges to other databases is not yet supported")
}
tables = append(tables, doltdb.TableName{
Name: string(normalizedTable.ObjectName),
Schema: string(normalizedTable.SchemaName),
})
case *tree.AllTablesSelector:
tables = append(tables, doltdb.TableName{
Name: "",
Schema: string(normalizedTable.SchemaName),
})
default:
return nil, errors.Errorf(`unexpected table type in GRANT: %T`, normalizedTable)
}
}
for _, schema := range node.Targets.InSchema {
tables = append(tables, doltdb.TableName{
Name: "",
Schema: schema,
})
}
privileges, err := convertPrivilegeKinds(auth.PrivilegeObject_TABLE, node.Privileges)
if err != nil {
return nil, err
}
grantTable = &pgnodes.GrantTable{
Privileges: privileges,
Tables: tables,
}
case privilege.Schema:
privileges, err := convertPrivilegeKinds(auth.PrivilegeObject_SCHEMA, node.Privileges)
if err != nil {
return nil, err
}
grantSchema = &pgnodes.GrantSchema{
Privileges: privileges,
Schemas: node.Targets.Names,
}
case privilege.Database:
privileges, err := convertPrivilegeKinds(auth.PrivilegeObject_DATABASE, node.Privileges)
if err != nil {
return nil, err
}
grantDatabase = &pgnodes.GrantDatabase{
Privileges: privileges,
Databases: node.Targets.Databases.ToStrings(),
}
case privilege.Sequence:
sequences := make([]auth.SequencePrivilegeKey, 0, len(node.Targets.Sequences)+len(node.Targets.InSchema))
for _, seq := range node.Targets.Sequences {
sequences = append(sequences, auth.SequencePrivilegeKey{
Schema: sequenceSchema(seq),
Name: seq.Parts[0],
})
}
for _, schema := range node.Targets.InSchema {
sequences = append(sequences, auth.SequencePrivilegeKey{
Schema: schema,
Name: "",
})
}
privileges, err := convertPrivilegeKinds(auth.PrivilegeObject_SEQUENCE, node.Privileges)
if err != nil {
return nil, err
}
grantSequence = &pgnodes.GrantSequence{
Privileges: privileges,
Sequences: sequences,
}
case privilege.Function, privilege.Procedure, privilege.Routine:
routines := make([]auth.RoutinePrivilegeKey, 0, len(node.Targets.Routines)+len(node.Targets.InSchema))
for _, r := range node.Targets.Routines {
routines = append(routines, auth.RoutinePrivilegeKey{
Schema: routineSchema(r.Name),
Name: r.Name.Parts[0],
// TODO: there can be 2 routines with the same name but different argument types
// need a fix for getting argument types from parsing CALL statement
//ArgTypes: routineArgTypesKey(r.Args),
})
}
for _, schema := range node.Targets.InSchema {
routines = append(routines, auth.RoutinePrivilegeKey{
Schema: schema,
Name: "",
})
}
privileges, err := convertPrivilegeKinds(auth.PrivilegeObject_FUNCTION, node.Privileges)
if err != nil {
return nil, err
}
grantRoutine = &pgnodes.GrantRoutine{
Privileges: privileges,
Routines: routines,
}
default:
return nil, errors.Errorf("this form of GRANT is not yet supported")
}
return vitess.InjectedStatement{
Statement: &pgnodes.Grant{
GrantTable: grantTable,
GrantSchema: grantSchema,
GrantDatabase: grantDatabase,
GrantSequence: grantSequence,
GrantRoutine: grantRoutine,
GrantRole: nil,
ToRoles: node.Grantees,
WithGrantOption: node.WithGrantOption,
GrantedBy: node.GrantedBy,
},
Children: nil,
}, nil
}
// sequenceSchema returns the schema portion of an UnresolvedObjectName for a sequence.
func sequenceSchema(name *tree.UnresolvedObjectName) string {
if name.NumParts >= 2 {
return name.Parts[1]
}
return ""
}
// routineSchema returns the schema portion of an UnresolvedObjectName for a routine.
func routineSchema(name *tree.UnresolvedObjectName) string {
if name.NumParts >= 2 {
return name.Parts[1]
}
return ""
}
// routineArgTypesKey builds a canonical string key from a RoutineArgs list using only the argument types.
func routineArgTypesKey(args tree.RoutineArgs) string {
parts := make([]string, len(args))
for i, arg := range args {
parts[i] = arg.Type.SQLString()
}
return strings.Join(parts, ",")
}
// convertPrivilegeKind converts a privilege from its parser representation to the server representation.
func convertPrivilegeKinds(object auth.PrivilegeObject, kinds []privilege.Kind) ([]auth.Privilege, error) {
privileges := make([]auth.Privilege, len(kinds))
for i, kind := range kinds {
switch kind {
case privilege.ALL:
// If we encounter ALL, then we know to return all privileges for this object
return object.AllPrivileges(), nil
case privilege.ALTERSYSTEM:
privileges[i] = auth.Privilege_ALTER_SYSTEM
case privilege.CONNECT:
privileges[i] = auth.Privilege_CONNECT
case privilege.CREATE:
privileges[i] = auth.Privilege_CREATE
case privilege.DELETE:
privileges[i] = auth.Privilege_DELETE
case privilege.DROP:
privileges[i] = auth.Privilege_DROP
case privilege.EXECUTE:
privileges[i] = auth.Privilege_EXECUTE
case privilege.INSERT:
privileges[i] = auth.Privilege_INSERT
case privilege.REFERENCES:
privileges[i] = auth.Privilege_REFERENCES
case privilege.SELECT:
privileges[i] = auth.Privilege_SELECT
case privilege.SET:
privileges[i] = auth.Privilege_SET
case privilege.TEMPORARY:
privileges[i] = auth.Privilege_TEMPORARY
case privilege.TRIGGER:
privileges[i] = auth.Privilege_TRIGGER
case privilege.TRUNCATE:
privileges[i] = auth.Privilege_TRUNCATE
case privilege.UPDATE:
privileges[i] = auth.Privilege_UPDATE
case privilege.USAGE:
privileges[i] = auth.Privilege_USAGE
default:
// This shouldn't be possible unless we update our list of supported privileges
return nil, errors.Errorf("unknown privilege kind: %v", kind)
}
}
for _, p := range privileges {
if !object.IsValid(p) {
return nil, errors.Errorf("invalid privilege type %s for relation", p.String())
}
}
return privileges, nil
}