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

     1  // Copyright 2022 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 information_schema
    16  
    17  import (
    18  	"bytes"
    19  	"fmt"
    20  
    21  	. "github.com/dolthub/go-mysql-server/sql"
    22  	"github.com/dolthub/go-mysql-server/sql/mysql_db"
    23  	"github.com/dolthub/go-mysql-server/sql/plan"
    24  	"github.com/dolthub/go-mysql-server/sql/planbuilder"
    25  	"github.com/dolthub/go-mysql-server/sql/types"
    26  )
    27  
    28  const defaultRoutinesTableRowCount = 10
    29  
    30  type routineTable struct {
    31  	name       string
    32  	schema     Schema
    33  	catalog    Catalog
    34  	procedures map[string][]*plan.Procedure
    35  	// functions
    36  	rowIter func(*Context, Catalog, map[string][]*plan.Procedure) (RowIter, error)
    37  }
    38  
    39  var (
    40  	_ Table           = (*routineTable)(nil)
    41  	_ Databaseable    = (*ColumnsTable)(nil)
    42  	_ StatisticsTable = (*ColumnsTable)(nil)
    43  )
    44  
    45  func (r *routineTable) AssignCatalog(cat Catalog) Table {
    46  	r.catalog = cat
    47  	return r
    48  }
    49  
    50  func (r *routineTable) AssignProcedures(p map[string][]*plan.Procedure) Table {
    51  	// TODO: should also assign functions
    52  	r.procedures = p
    53  	return r
    54  }
    55  
    56  // Database implements the sql.Databaseable interface.
    57  func (r *routineTable) Database() string {
    58  	return InformationSchemaDatabaseName
    59  }
    60  
    61  func (r *routineTable) DataLength(_ *Context) (uint64, error) {
    62  	return uint64(len(r.Schema()) * int(types.Text.MaxByteLength()) * defaultRoutinesTableRowCount), nil
    63  }
    64  
    65  func (r *routineTable) RowCount(ctx *Context) (uint64, bool, error) {
    66  	return defaultRoutinesTableRowCount, false, nil
    67  }
    68  
    69  // Name implements the sql.Table interface.
    70  func (r *routineTable) Name() string {
    71  	return r.name
    72  }
    73  
    74  // Schema implements the sql.Table interface.
    75  func (r *routineTable) Schema() Schema {
    76  	return r.schema
    77  }
    78  
    79  // Collation implements the sql.Table interface.
    80  func (r *routineTable) Collation() CollationID {
    81  	return Collation_Information_Schema_Default
    82  }
    83  
    84  func (r *routineTable) String() string {
    85  	return printTable(r.Name(), r.Schema())
    86  }
    87  
    88  func (r *routineTable) Partitions(context *Context) (PartitionIter, error) {
    89  	return &informationSchemaPartitionIter{informationSchemaPartition: informationSchemaPartition{partitionKey(r.Name())}}, nil
    90  }
    91  
    92  func (r *routineTable) PartitionRows(context *Context, partition Partition) (RowIter, error) {
    93  	if !bytes.Equal(partition.Key(), partitionKey(r.Name())) {
    94  		return nil, ErrPartitionNotFound.New(partition.Key())
    95  	}
    96  	if r.rowIter == nil {
    97  		return RowsToRowIter(), nil
    98  	}
    99  	if r.catalog == nil {
   100  		return nil, fmt.Errorf("nil catalog for info schema table %s", r.name)
   101  	}
   102  
   103  	return r.rowIter(context, r.catalog, r.procedures)
   104  }
   105  
   106  // routinesRowIter implements the sql.RowIter for the information_schema.ROUTINES table.
   107  func routinesRowIter(ctx *Context, c Catalog, p map[string][]*plan.Procedure) (RowIter, error) {
   108  	var rows []Row
   109  	var (
   110  		securityType    string
   111  		isDeterministic string
   112  		sqlDataAccess   string
   113  	)
   114  
   115  	characterSetClient, err := ctx.GetSessionVariable(ctx, "character_set_client")
   116  	if err != nil {
   117  		return nil, err
   118  	}
   119  	collationConnection, err := ctx.GetSessionVariable(ctx, "collation_connection")
   120  	if err != nil {
   121  		return nil, err
   122  	}
   123  
   124  	sysVal, err := ctx.Session.GetSessionVariable(ctx, "sql_mode")
   125  	if err != nil {
   126  		return nil, err
   127  	}
   128  	sqlMode, ok := sysVal.(string)
   129  	if !ok {
   130  		return nil, ErrSystemVariableCodeFail.New("sql_mode", sysVal)
   131  	}
   132  
   133  	showExternalProcedures, err := ctx.GetSessionVariable(ctx, "show_external_procedures")
   134  	if err != nil {
   135  		return nil, err
   136  	}
   137  	privSet, _ := ctx.GetPrivilegeSet()
   138  	if privSet == nil {
   139  		privSet = mysql_db.NewPrivilegeSet()
   140  	}
   141  	for dbName, procedures := range p {
   142  		if !hasRoutinePrivsOnDB(privSet, dbName) {
   143  			continue
   144  		}
   145  		db, err := c.Database(ctx, dbName)
   146  		if err != nil {
   147  			return nil, err
   148  		}
   149  		dbCollation := plan.GetDatabaseCollation(ctx, db)
   150  		for _, procedure := range procedures {
   151  			// We skip external procedures if the variable to show them is set to false
   152  			if showExternalProcedures.(int8) == 0 && procedure.IsExternal() {
   153  				continue
   154  			}
   155  
   156  			// todo shortcircuit routineDef->procedure.CreateProcedureString?
   157  			parsedProcedure, err := planbuilder.Parse(ctx, c, procedure.CreateProcedureString)
   158  			if err != nil {
   159  				continue
   160  			}
   161  			procedurePlan, ok := parsedProcedure.(*plan.CreateProcedure)
   162  			if !ok {
   163  				return nil, ErrProcedureCreateStatementInvalid.New(procedure.CreateProcedureString)
   164  			}
   165  			routineDef := procedurePlan.BodyString
   166  			definer := removeBackticks(procedure.Definer)
   167  
   168  			securityType = "DEFINER"
   169  			isDeterministic = "NO" // YES or NO
   170  			sqlDataAccess = "CONTAINS SQL"
   171  			for _, ch := range procedure.Characteristics {
   172  				if ch == plan.Characteristic_LanguageSql {
   173  
   174  				}
   175  
   176  				if ch == plan.Characteristic_Deterministic {
   177  					isDeterministic = "YES"
   178  				} else if ch == plan.Characteristic_NotDeterministic {
   179  					isDeterministic = "NO"
   180  				}
   181  
   182  				if ch == plan.Characteristic_ContainsSql {
   183  					sqlDataAccess = "CONTAINS SQL"
   184  				} else if ch == plan.Characteristic_NoSql {
   185  					sqlDataAccess = "NO SQL"
   186  				} else if ch == plan.Characteristic_ReadsSqlData {
   187  					sqlDataAccess = "READS SQL DATA"
   188  				} else if ch == plan.Characteristic_ModifiesSqlData {
   189  					sqlDataAccess = "MODIFIES SQL DATA"
   190  				}
   191  			}
   192  
   193  			if procedure.SecurityContext == plan.ProcedureSecurityContext_Invoker {
   194  				securityType = "INVOKER"
   195  			}
   196  			rows = append(rows, Row{
   197  				procedure.Name,             // specific_name NOT NULL
   198  				"def",                      // routine_catalog
   199  				dbName,                     // routine_schema
   200  				procedure.Name,             // routine_name NOT NULL
   201  				"PROCEDURE",                // routine_type NOT NULL
   202  				"",                         // data_type
   203  				nil,                        // character_maximum_length
   204  				nil,                        // character_octet_length
   205  				nil,                        // numeric_precision
   206  				nil,                        // numeric_scale
   207  				nil,                        // datetime_precision
   208  				nil,                        // character_set_name
   209  				nil,                        // collation_name
   210  				nil,                        // dtd_identifier
   211  				"SQL",                      // routine_body NOT NULL
   212  				routineDef,                 // routine_definition
   213  				nil,                        // external_name
   214  				"SQL",                      // external_language NOT NULL
   215  				"SQL",                      // parameter_style NOT NULL
   216  				isDeterministic,            // is_deterministic NOT NULL
   217  				sqlDataAccess,              // sql_data_access NOT NULL
   218  				nil,                        // sql_path
   219  				securityType,               // security_type NOT NULL
   220  				procedure.CreatedAt.UTC(),  // created NOT NULL
   221  				procedure.ModifiedAt.UTC(), // last_altered NOT NULL
   222  				sqlMode,                    // sql_mode NOT NULL
   223  				procedure.Comment,          // routine_comment NOT NULL
   224  				definer,                    // definer NOT NULL
   225  				characterSetClient,         // character_set_client NOT NULL
   226  				collationConnection,        // collation_connection NOT NULL
   227  				dbCollation.String(),       // database_collation NOT NULL
   228  			})
   229  		}
   230  	}
   231  
   232  	// TODO: need to add FUNCTIONS routine_type
   233  
   234  	return RowsToRowIter(rows...), nil
   235  }
   236  
   237  // parametersRowIter implements the sql.RowIter for the information_schema.PARAMETERS table.
   238  func parametersRowIter(ctx *Context, c Catalog, p map[string][]*plan.Procedure) (RowIter, error) {
   239  	var rows []Row
   240  
   241  	showExternalProcedures, err := ctx.GetSessionVariable(ctx, "show_external_procedures")
   242  	if err != nil {
   243  		return nil, err
   244  	}
   245  	privSet, _ := ctx.GetPrivilegeSet()
   246  	if privSet == nil {
   247  		privSet = mysql_db.NewPrivilegeSet()
   248  	}
   249  	for dbName, procedures := range p {
   250  		if !hasRoutinePrivsOnDB(privSet, dbName) {
   251  			continue
   252  		}
   253  		for _, procedure := range procedures {
   254  			// We skip external procedures if the variable to show them is set to false
   255  			if showExternalProcedures.(int8) == 0 && procedure.IsExternal() {
   256  				continue
   257  			}
   258  
   259  			for i, param := range procedure.Params {
   260  				var (
   261  					ordinalPos        = uint64(i + 1)
   262  					datetimePrecision interface{}
   263  					parameterMode     interface{}
   264  				)
   265  
   266  				dtdId, dataType := getDtdIdAndDataType(param.Type)
   267  
   268  				if param.Direction == plan.ProcedureParamDirection_In {
   269  					parameterMode = "IN"
   270  				} else if param.Direction == plan.ProcedureParamDirection_Inout {
   271  					parameterMode = "INOUT"
   272  				} else if param.Direction == plan.ProcedureParamDirection_Out {
   273  					parameterMode = "OUT"
   274  				}
   275  
   276  				charName, collName, charMaxLen, charOctetLen := getCharAndCollNamesAndCharMaxAndOctetLens(ctx, param.Type)
   277  				numericPrecision, numericScale := getColumnPrecisionAndScale(param.Type)
   278  				// float types get nil for numericScale, but it gets 0 for this table
   279  				if _, ok := param.Type.(NumberType); ok {
   280  					numericScale = 0
   281  				}
   282  
   283  				if types.IsDatetimeType(param.Type) || types.IsTimestampType(param.Type) {
   284  					datetimePrecision = 0
   285  				} else if types.IsTimespan(param.Type) {
   286  					// TODO: TIME length not yet supported
   287  					datetimePrecision = 6
   288  				}
   289  
   290  				rows = append(rows, Row{
   291  					"def",             // specific_catalog
   292  					dbName,            // specific_schema
   293  					procedure.Name,    // specific_name
   294  					ordinalPos,        // ordinal_position - 0 for FUNCTIONS
   295  					parameterMode,     // parameter_mode   - NULL for FUNCTIONS
   296  					param.Name,        // parameter_name   - NULL for FUNCTIONS
   297  					dataType,          // data_type
   298  					charMaxLen,        // character_maximum_length
   299  					charOctetLen,      // character_octet_length
   300  					numericPrecision,  // numeric_precision
   301  					numericScale,      // numeric_scale
   302  					datetimePrecision, // datetime_precision
   303  					charName,          // character_set_name
   304  					collName,          // collation_name
   305  					dtdId,             // dtd_identifier
   306  					"PROCEDURE",       // routine_type
   307  				})
   308  			}
   309  		}
   310  	}
   311  	// TODO: need to add FUNCTIONS routine_type
   312  
   313  	return RowsToRowIter(rows...), nil
   314  }
   315  
   316  // hasRoutinePrivsOnDB returns bool value whether privilegeSet has either global or database level `CREATE ROUTINE` or `ALTER ROUTINE` or `EXECUTE` privileges.
   317  func hasRoutinePrivsOnDB(privSet PrivilegeSet, dbName string) bool {
   318  	return privSet.Has(PrivilegeType_CreateRoutine) || privSet.Has(PrivilegeType_AlterRoutine) || privSet.Has(PrivilegeType_Execute) ||
   319  		privSet.Database(dbName).Has(PrivilegeType_CreateRoutine) || privSet.Database(dbName).Has(PrivilegeType_AlterRoutine) || privSet.Database(dbName).Has(PrivilegeType_Execute)
   320  }