github.com/dolthub/go-mysql-server@v0.18.0/sql/plan/show_create_procedure.go (about)

     1  // Copyright 2020-2021 Dolthub, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package plan
    16  
    17  import (
    18  	"fmt"
    19  	"strings"
    20  
    21  	"github.com/dolthub/go-mysql-server/sql"
    22  	"github.com/dolthub/go-mysql-server/sql/types"
    23  )
    24  
    25  type ShowCreateProcedure struct {
    26  	db                      sql.Database
    27  	ProcedureName           string
    28  	ExternalStoredProcedure *sql.ExternalStoredProcedureDetails
    29  }
    30  
    31  var _ sql.Databaser = (*ShowCreateProcedure)(nil)
    32  var _ sql.Node = (*ShowCreateProcedure)(nil)
    33  var _ sql.CollationCoercible = (*ShowCreateProcedure)(nil)
    34  
    35  var showCreateProcedureSchema = sql.Schema{
    36  	&sql.Column{Name: "Procedure", Type: types.LongText, Nullable: false},
    37  	&sql.Column{Name: "sql_mode", Type: types.LongText, Nullable: false},
    38  	&sql.Column{Name: "Create Procedure", Type: types.LongText, Nullable: false},
    39  	&sql.Column{Name: "character_set_client", Type: types.LongText, Nullable: false},
    40  	&sql.Column{Name: "collation_connection", Type: types.LongText, Nullable: false},
    41  	&sql.Column{Name: "Database Collation", Type: types.LongText, Nullable: false},
    42  }
    43  
    44  // NewShowCreateProcedure creates a new ShowCreateProcedure node for SHOW CREATE PROCEDURE statements.
    45  func NewShowCreateProcedure(db sql.Database, procedure string) *ShowCreateProcedure {
    46  	return &ShowCreateProcedure{
    47  		db:            db,
    48  		ProcedureName: strings.ToLower(procedure),
    49  	}
    50  }
    51  
    52  // String implements the sql.Node interface.
    53  func (s *ShowCreateProcedure) String() string {
    54  	return fmt.Sprintf("SHOW CREATE PROCEDURE %s", s.ProcedureName)
    55  }
    56  
    57  // Resolved implements the sql.Node interface.
    58  func (s *ShowCreateProcedure) Resolved() bool {
    59  	_, ok := s.db.(sql.UnresolvedDatabase)
    60  	return !ok
    61  }
    62  
    63  func (s *ShowCreateProcedure) IsReadOnly() bool {
    64  	return true
    65  }
    66  
    67  // Children implements the sql.Node interface.
    68  func (s *ShowCreateProcedure) Children() []sql.Node {
    69  	return nil
    70  }
    71  
    72  // Schema implements the sql.Node interface.
    73  func (s *ShowCreateProcedure) Schema() sql.Schema {
    74  	return showCreateProcedureSchema
    75  }
    76  
    77  // RowIter implements the sql.Node interface.
    78  func (s *ShowCreateProcedure) RowIter(ctx *sql.Context, _ sql.Row) (sql.RowIter, error) {
    79  	characterSetClient, err := ctx.GetSessionVariable(ctx, "character_set_client")
    80  	if err != nil {
    81  		return nil, err
    82  	}
    83  	collationConnection, err := ctx.GetSessionVariable(ctx, "collation_connection")
    84  	if err != nil {
    85  		return nil, err
    86  	}
    87  	collationServer, err := ctx.GetSessionVariable(ctx, "collation_server")
    88  	if err != nil {
    89  		return nil, err
    90  	}
    91  
    92  	if s.ExternalStoredProcedure != nil {
    93  		// If an external stored procedure has been plugged in by the analyzer, use that
    94  		fakeCreateProcedureStmt := s.ExternalStoredProcedure.FakeCreateProcedureStmt()
    95  		return sql.RowsToRowIter(sql.Row{
    96  			s.ExternalStoredProcedure.Name, // Procedure
    97  			"",                             // sql_mode
    98  			fakeCreateProcedureStmt,        // Create Procedure
    99  			characterSetClient,             // character_set_client
   100  			collationConnection,            // collation_connection
   101  			collationServer,                // Database Collation
   102  		}), nil
   103  	} else {
   104  		// Otherwise, search the StoredProcedureDatabase for a user-created stored procedure
   105  		procedureDb, ok := s.db.(sql.StoredProcedureDatabase)
   106  		if !ok {
   107  			return nil, sql.ErrStoredProceduresNotSupported.New(s.db.Name())
   108  		}
   109  		procedures, err := procedureDb.GetStoredProcedures(ctx)
   110  		if err != nil {
   111  			return nil, err
   112  		}
   113  		for _, procedure := range procedures {
   114  			if strings.ToLower(procedure.Name) == s.ProcedureName {
   115  				return sql.RowsToRowIter(sql.Row{
   116  					procedure.Name,            // Procedure
   117  					"",                        // sql_mode
   118  					procedure.CreateStatement, // Create Procedure
   119  					characterSetClient,        // character_set_client
   120  					collationConnection,       // collation_connection
   121  					collationServer,           // Database Collation
   122  				}), nil
   123  			}
   124  		}
   125  		return nil, sql.ErrStoredProcedureDoesNotExist.New(s.ProcedureName)
   126  	}
   127  }
   128  
   129  // WithChildren implements the sql.Node interface.
   130  func (s *ShowCreateProcedure) WithChildren(children ...sql.Node) (sql.Node, error) {
   131  	return NillaryWithChildren(s, children...)
   132  }
   133  
   134  // CheckPrivileges implements the interface sql.Node.
   135  func (s *ShowCreateProcedure) CheckPrivileges(ctx *sql.Context, opChecker sql.PrivilegedOperationChecker) bool {
   136  	// TODO: set definer
   137  	// TODO: dynamic privilege SHOW ROUTINE
   138  	// According to: https://dev.mysql.com/doc/refman/8.0/en/show-create-procedure.html
   139  	// Must have Global SELECT, SHOW_ROUTINE, CREATE_ROUTINE, ALTER_ROUTINE, or EXECUTE privileges.
   140  
   141  	dbSubject := sql.PrivilegeCheckSubject{
   142  		Database: s.db.Name(),
   143  	}
   144  
   145  	return opChecker.UserHasPrivileges(ctx, sql.NewPrivilegedOperation(sql.PrivilegeCheckSubject{}, sql.PrivilegeType_Select)) ||
   146  		opChecker.UserHasPrivileges(ctx, sql.NewPrivilegedOperation(dbSubject, sql.PrivilegeType_CreateRoutine)) ||
   147  		opChecker.UserHasPrivileges(ctx, sql.NewPrivilegedOperation(dbSubject, sql.PrivilegeType_AlterRoutine)) ||
   148  		opChecker.UserHasPrivileges(ctx, sql.NewPrivilegedOperation(dbSubject, sql.PrivilegeType_Execute))
   149  }
   150  
   151  // CollationCoercibility implements the interface sql.CollationCoercible.
   152  func (*ShowCreateProcedure) CollationCoercibility(ctx *sql.Context) (collation sql.CollationID, coercibility byte) {
   153  	return sql.Collation_binary, 7
   154  }
   155  
   156  // Database implements the sql.Databaser interface.
   157  func (s *ShowCreateProcedure) Database() sql.Database {
   158  	return s.db
   159  }
   160  
   161  // WithDatabase implements the sql.Databaser interface.
   162  func (s *ShowCreateProcedure) WithDatabase(db sql.Database) (sql.Node, error) {
   163  	ns := *s
   164  	ns.db = db
   165  	return &ns, nil
   166  }
   167  
   168  // WithExternalStoredProcedure returns a new ShowCreateProcedure node with the specified external stored procedure set
   169  // as the procedure to be shown.
   170  func (s *ShowCreateProcedure) WithExternalStoredProcedure(procedure sql.ExternalStoredProcedureDetails) sql.Node {
   171  	ns := *s
   172  	ns.ExternalStoredProcedure = &procedure
   173  	return &ns
   174  }