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 }