You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

735 lines
31 KiB

4 years ago
  1. #if MYSQL_6_10
  2. // Copyright (c) 2004, 2019, Oracle and/or its affiliates. All rights reserved.
  3. //
  4. // MySQL Connector/NET is licensed under the terms of the GPLv2
  5. // <http://www.gnu.org/licenses/old-licenses/gpl-2.0.html>, like most
  6. // MySQL Connectors. There are special exceptions to the terms and
  7. // conditions of the GPLv2 as it is applied to this software, see the
  8. // FLOSS License Exception
  9. // <http://www.mysql.com/about/legal/licensing/foss-exception.html>.
  10. //
  11. // This program is free software; you can redistribute it and/or modify
  12. // it under the terms of the GNU General Public License as published
  13. // by the Free Software Foundation; version 2 of the License.
  14. //
  15. // This program is distributed in the hope that it will be useful, but
  16. // WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
  17. // or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
  18. // for more details.
  19. //
  20. // You should have received a copy of the GNU General Public License along
  21. // with this program; if not, write to the Free Software Foundation, Inc.,
  22. // 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
  23. using Externals.MySql.Data.Common;
  24. using Externals.MySql.Data.Types;
  25. using System;
  26. using System.Collections.Generic;
  27. using System.Globalization;
  28. using System.Text;
  29. namespace Externals.MySql.Data.MySqlClient
  30. {
  31. internal class ISSchemaProvider : SchemaProvider
  32. {
  33. public ISSchemaProvider(MySqlConnection connection)
  34. : base(connection)
  35. {
  36. }
  37. protected override MySqlSchemaCollection GetCollections()
  38. {
  39. MySqlSchemaCollection dt = base.GetCollections();
  40. object[][] collections = {
  41. new object[] {"Views", 2, 3},
  42. new object[] {"ViewColumns", 3, 4},
  43. new object[] {"Procedure Parameters", 5, 1},
  44. new object[] {"Procedures", 4, 3},
  45. new object[] {"Triggers", 2, 4}
  46. };
  47. FillTable(dt, collections);
  48. return dt;
  49. }
  50. protected override MySqlSchemaCollection GetRestrictions()
  51. {
  52. MySqlSchemaCollection dt = base.GetRestrictions();
  53. object[][] restrictions = new object[][]
  54. {
  55. new object[] {"Procedure Parameters", "Database", "", 0},
  56. new object[] {"Procedure Parameters", "Schema", "", 1},
  57. new object[] {"Procedure Parameters", "Name", "", 2},
  58. new object[] {"Procedure Parameters", "Type", "", 3},
  59. new object[] {"Procedure Parameters", "Parameter", "", 4},
  60. new object[] {"Procedures", "Database", "", 0},
  61. new object[] {"Procedures", "Schema", "", 1},
  62. new object[] {"Procedures", "Name", "", 2},
  63. new object[] {"Procedures", "Type", "", 3},
  64. new object[] {"Views", "Database", "", 0},
  65. new object[] {"Views", "Schema", "", 1},
  66. new object[] {"Views", "Table", "", 2},
  67. new object[] {"ViewColumns", "Database", "", 0},
  68. new object[] {"ViewColumns", "Schema", "", 1},
  69. new object[] {"ViewColumns", "Table", "", 2},
  70. new object[] {"ViewColumns", "Column", "", 3},
  71. new object[] {"Triggers", "Database", "", 0},
  72. new object[] {"Triggers", "Schema", "", 1},
  73. new object[] {"Triggers", "Name", "", 2},
  74. new object[] {"Triggers", "EventObjectTable", "", 3},
  75. };
  76. FillTable(dt, restrictions);
  77. return dt;
  78. }
  79. public override MySqlSchemaCollection GetDatabases(string[] restrictions)
  80. {
  81. string[] keys = new string[1];
  82. keys[0] = "SCHEMA_NAME";
  83. MySqlSchemaCollection dt = Query("SCHEMATA", "", keys, restrictions);
  84. dt.Columns[1].Name = "database_name";
  85. dt.Name = "Databases";
  86. return dt;
  87. }
  88. public override MySqlSchemaCollection GetTables(string[] restrictions)
  89. {
  90. string[] keys = new string[4];
  91. keys[0] = "TABLE_CATALOG";
  92. keys[1] = "TABLE_SCHEMA";
  93. keys[2] = "TABLE_NAME";
  94. keys[3] = "TABLE_TYPE";
  95. MySqlSchemaCollection dt = Query("TABLES", "TABLE_TYPE != 'VIEW'", keys, restrictions);
  96. dt.Name = "Tables";
  97. return dt;
  98. }
  99. public override MySqlSchemaCollection GetColumns(string[] restrictions)
  100. {
  101. string[] keys = new string[4];
  102. keys[0] = "TABLE_CATALOG";
  103. keys[1] = "TABLE_SCHEMA";
  104. keys[2] = "TABLE_NAME";
  105. keys[3] = "COLUMN_NAME";
  106. MySqlSchemaCollection dt = Query("COLUMNS", null, keys, restrictions);
  107. dt.RemoveColumn("CHARACTER_OCTET_LENGTH");
  108. dt.Name = "Columns";
  109. QuoteDefaultValues(dt);
  110. return dt;
  111. }
  112. private MySqlSchemaCollection GetViews(string[] restrictions)
  113. {
  114. string[] keys = new string[3];
  115. keys[0] = "TABLE_CATALOG";
  116. keys[1] = "TABLE_SCHEMA";
  117. keys[2] = "TABLE_NAME";
  118. MySqlSchemaCollection dt = Query("VIEWS", null, keys, restrictions);
  119. dt.Name = "Views";
  120. return dt;
  121. }
  122. private MySqlSchemaCollection GetViewColumns(string[] restrictions)
  123. {
  124. StringBuilder where = new StringBuilder();
  125. StringBuilder sql = new StringBuilder(
  126. "SELECT C.* FROM information_schema.columns C");
  127. sql.Append(" JOIN information_schema.views V ");
  128. sql.Append("ON C.table_schema=V.table_schema AND C.table_name=V.table_name ");
  129. if (restrictions != null && restrictions.Length >= 2 &&
  130. restrictions[1] != null)
  131. where.AppendFormat(CultureInfo.InvariantCulture, "C.table_schema='{0}' ", restrictions[1]);
  132. if (restrictions != null && restrictions.Length >= 3 &&
  133. restrictions[2] != null)
  134. {
  135. if (where.Length > 0)
  136. where.Append("AND ");
  137. where.AppendFormat(CultureInfo.InvariantCulture, "C.table_name='{0}' ", restrictions[2]);
  138. }
  139. if (restrictions != null && restrictions.Length == 4 &&
  140. restrictions[3] != null)
  141. {
  142. if (where.Length > 0)
  143. where.Append("AND ");
  144. where.AppendFormat(CultureInfo.InvariantCulture, "C.column_name='{0}' ", restrictions[3]);
  145. }
  146. if (where.Length > 0)
  147. sql.AppendFormat(CultureInfo.InvariantCulture, " WHERE {0}", where);
  148. MySqlSchemaCollection dt = GetTable(sql.ToString());
  149. dt.Name = "ViewColumns";
  150. dt.Columns[0].Name = "VIEW_CATALOG";
  151. dt.Columns[1].Name = "VIEW_SCHEMA";
  152. dt.Columns[2].Name = "VIEW_NAME";
  153. QuoteDefaultValues(dt);
  154. return dt;
  155. }
  156. private MySqlSchemaCollection GetTriggers(string[] restrictions)
  157. {
  158. string[] keys = new string[4];
  159. keys[0] = "TRIGGER_CATALOG";
  160. keys[1] = "TRIGGER_SCHEMA";
  161. keys[2] = "EVENT_OBJECT_TABLE";
  162. keys[3] = "TRIGGER_NAME";
  163. MySqlSchemaCollection dt = Query("TRIGGERS", null, keys, restrictions);
  164. dt.Name = "Triggers";
  165. return dt;
  166. }
  167. /// <summary>
  168. /// Return schema information about procedures and functions
  169. /// Restrictions supported are:
  170. /// schema, name, type
  171. /// </summary>
  172. /// <param name="restrictions"></param>
  173. /// <returns></returns>
  174. public override MySqlSchemaCollection GetProcedures(string[] restrictions)
  175. {
  176. try
  177. {
  178. if (connection.Settings.HasProcAccess)
  179. return base.GetProcedures(restrictions);
  180. }
  181. catch (MySqlException ex)
  182. {
  183. if (ex.Number == (int)MySqlErrorCode.TableAccessDenied)
  184. connection.Settings.HasProcAccess = false;
  185. else
  186. throw;
  187. }
  188. string[] keys = new string[4];
  189. keys[0] = "ROUTINE_CATALOG";
  190. keys[1] = "ROUTINE_SCHEMA";
  191. keys[2] = "ROUTINE_NAME";
  192. keys[3] = "ROUTINE_TYPE";
  193. MySqlSchemaCollection dt = Query("ROUTINES", null, keys, restrictions);
  194. dt.Name = "Procedures";
  195. return dt;
  196. }
  197. private MySqlSchemaCollection GetProceduresWithParameters(string[] restrictions)
  198. {
  199. MySqlSchemaCollection dt = GetProcedures(restrictions);
  200. dt.AddColumn("ParameterList", typeof(string));
  201. foreach (MySqlSchemaRow row in dt.Rows)
  202. {
  203. row["ParameterList"] = GetProcedureParameterLine(row);
  204. }
  205. return dt;
  206. }
  207. private string GetProcedureParameterLine(MySqlSchemaRow isRow)
  208. {
  209. string sql = "SHOW CREATE {0} `{1}`.`{2}`";
  210. sql = String.Format(sql, isRow["ROUTINE_TYPE"], isRow["ROUTINE_SCHEMA"],
  211. isRow["ROUTINE_NAME"]);
  212. MySqlCommand cmd = new MySqlCommand(sql, connection);
  213. using (MySqlDataReader reader = cmd.ExecuteReader())
  214. {
  215. reader.Read();
  216. // if we are not the owner of this proc or have permissions
  217. // then we will get null for the body
  218. if (reader.IsDBNull(2)) return null;
  219. string sqlMode = reader.GetString(1);
  220. string body = reader.GetString(2);
  221. MySqlTokenizer tokenizer = new MySqlTokenizer(body);
  222. tokenizer.AnsiQuotes = sqlMode.IndexOf("ANSI_QUOTES") != -1;
  223. tokenizer.BackslashEscapes = sqlMode.IndexOf("NO_BACKSLASH_ESCAPES") == -1;
  224. string token = tokenizer.NextToken();
  225. while (token != "(")
  226. token = tokenizer.NextToken();
  227. int start = tokenizer.StartIndex + 1;
  228. token = tokenizer.NextToken();
  229. while (token != ")" || tokenizer.Quoted)
  230. {
  231. token = tokenizer.NextToken();
  232. // if we see another ( and we are not quoted then we
  233. // are in a size element and we need to look for the closing paren
  234. if (token == "(" && !tokenizer.Quoted)
  235. {
  236. while (token != ")" || tokenizer.Quoted)
  237. token = tokenizer.NextToken();
  238. token = tokenizer.NextToken();
  239. }
  240. }
  241. return body.Substring(start, tokenizer.StartIndex - start);
  242. }
  243. }
  244. private MySqlSchemaCollection GetParametersForRoutineFromIS(string[] restrictions)
  245. {
  246. string[] keys = new string[5];
  247. keys[0] = "SPECIFIC_CATALOG";
  248. keys[1] = "SPECIFIC_SCHEMA";
  249. keys[2] = "SPECIFIC_NAME";
  250. keys[3] = "ROUTINE_TYPE";
  251. keys[4] = "PARAMETER_NAME";
  252. StringBuilder sql = new StringBuilder(@"SELECT * FROM INFORMATION_SCHEMA.PARAMETERS");
  253. // now get our where clause and append it if there is one
  254. string where = GetWhereClause(null, keys, restrictions);
  255. if (!String.IsNullOrEmpty(where))
  256. sql.AppendFormat(CultureInfo.InvariantCulture, " WHERE {0}", where);
  257. MySqlSchemaCollection coll = QueryCollection("parameters", sql.ToString());
  258. if ((coll.Rows.Count != 0) && ((string)coll.Rows[0]["routine_type"] == "FUNCTION"))
  259. {
  260. // update missing data for the first row (function return value).
  261. // (using sames valus than GetParametersFromShowCreate).
  262. coll.Rows[0]["parameter_mode"] = "IN";
  263. coll.Rows[0]["parameter_name"] = "return_value"; // "FUNCTION";
  264. }
  265. return coll;
  266. }
  267. private MySqlSchemaCollection GetParametersFromIS(string[] restrictions, MySqlSchemaCollection routines)
  268. {
  269. MySqlSchemaCollection parms = null;
  270. if (routines == null || routines.Rows.Count == 0)
  271. {
  272. if (restrictions == null)
  273. {
  274. parms = QueryCollection("parameters", "SELECT * FROM INFORMATION_SCHEMA.PARAMETERS WHERE 1=2");
  275. }
  276. else
  277. parms = GetParametersForRoutineFromIS(restrictions);
  278. }
  279. else foreach (MySqlSchemaRow routine in routines.Rows)
  280. {
  281. if (restrictions != null && restrictions.Length >= 3)
  282. restrictions[2] = routine["ROUTINE_NAME"].ToString();
  283. parms = GetParametersForRoutineFromIS(restrictions);
  284. }
  285. parms.Name = "Procedure Parameters";
  286. return parms;
  287. }
  288. internal MySqlSchemaCollection CreateParametersTable()
  289. {
  290. MySqlSchemaCollection dt = new MySqlSchemaCollection("Procedure Parameters");
  291. dt.AddColumn("SPECIFIC_CATALOG", typeof(string));
  292. dt.AddColumn("SPECIFIC_SCHEMA", typeof(string));
  293. dt.AddColumn("SPECIFIC_NAME", typeof(string));
  294. dt.AddColumn("ORDINAL_POSITION", typeof(Int32));
  295. dt.AddColumn("PARAMETER_MODE", typeof(string));
  296. dt.AddColumn("PARAMETER_NAME", typeof(string));
  297. dt.AddColumn("DATA_TYPE", typeof(string));
  298. dt.AddColumn("CHARACTER_MAXIMUM_LENGTH", typeof(Int32));
  299. dt.AddColumn("CHARACTER_OCTET_LENGTH", typeof(Int32));
  300. dt.AddColumn("NUMERIC_PRECISION", typeof(byte));
  301. dt.AddColumn("NUMERIC_SCALE", typeof(Int32));
  302. dt.AddColumn("CHARACTER_SET_NAME", typeof(string));
  303. dt.AddColumn("COLLATION_NAME", typeof(string));
  304. dt.AddColumn("DTD_IDENTIFIER", typeof(string));
  305. dt.AddColumn("ROUTINE_TYPE", typeof(string));
  306. return dt;
  307. }
  308. /// <summary>
  309. /// Return schema information about parameters for procedures and functions
  310. /// Restrictions supported are:
  311. /// schema, name, type, parameter name
  312. /// </summary>
  313. public virtual MySqlSchemaCollection GetProcedureParameters(string[] restrictions,
  314. MySqlSchemaCollection routines)
  315. {
  316. bool is55 = connection.driver.Version.isAtLeast(5, 5, 3);
  317. try
  318. {
  319. // we want to avoid using IS if we can as it is painfully slow
  320. MySqlSchemaCollection dt = CreateParametersTable();
  321. GetParametersFromShowCreate(dt, restrictions, routines);
  322. return dt;
  323. }
  324. catch (Exception)
  325. {
  326. if (!is55) throw;
  327. // we get here by not having access and we are on 5.5 or later so just use IS
  328. return GetParametersFromIS(restrictions, routines);
  329. }
  330. }
  331. protected override MySqlSchemaCollection GetSchemaInternal(string collection, string[] restrictions)
  332. {
  333. MySqlSchemaCollection dt = base.GetSchemaInternal(collection, restrictions);
  334. if (dt != null)
  335. return dt;
  336. switch (collection)
  337. {
  338. case "VIEWS":
  339. return GetViews(restrictions);
  340. case "PROCEDURES":
  341. return GetProcedures(restrictions);
  342. case "PROCEDURES WITH PARAMETERS":
  343. return GetProceduresWithParameters(restrictions);
  344. case "PROCEDURE PARAMETERS":
  345. return GetProcedureParameters(restrictions, null);
  346. case "TRIGGERS":
  347. return GetTriggers(restrictions);
  348. case "VIEWCOLUMNS":
  349. return GetViewColumns(restrictions);
  350. }
  351. return null;
  352. }
  353. private static string GetWhereClause(string initial_where, string[] keys, string[] values)
  354. {
  355. StringBuilder where = new StringBuilder(initial_where);
  356. if (values != null)
  357. {
  358. for (int i = 0; i < keys.Length; i++)
  359. {
  360. if (i >= values.Length) break;
  361. if (values[i] == null || values[i] == String.Empty) continue;
  362. if (where.Length > 0)
  363. where.Append(" AND ");
  364. where.AppendFormat(CultureInfo.InvariantCulture,
  365. "{0} LIKE '{1}'", keys[i], values[i]);
  366. }
  367. }
  368. return where.ToString();
  369. }
  370. private MySqlSchemaCollection Query(string tableName, string initialWhere,
  371. string[] keys, string[] values)
  372. {
  373. StringBuilder query = new StringBuilder("SELECT * FROM INFORMATION_SCHEMA.");
  374. query.Append(tableName);
  375. string where = GetWhereClause(initialWhere, keys, values);
  376. if (where.Length > 0)
  377. query.AppendFormat(CultureInfo.InvariantCulture, " WHERE {0}", where);
  378. if (tableName.Equals("COLUMNS", StringComparison.OrdinalIgnoreCase))
  379. query.Append(" ORDER BY ORDINAL_POSITION");
  380. return GetTable(query.ToString());
  381. }
  382. private MySqlSchemaCollection GetTable(string sql)
  383. {
  384. MySqlSchemaCollection c = new MySqlSchemaCollection();
  385. MySqlCommand cmd = new MySqlCommand(sql, connection);
  386. MySqlDataReader reader = cmd.ExecuteReader();
  387. // add columns
  388. for (int i = 0; i < reader.FieldCount; i++)
  389. c.AddColumn(reader.GetName(i), reader.GetFieldType(i));
  390. using (reader)
  391. {
  392. while (reader.Read())
  393. {
  394. MySqlSchemaRow row = c.AddRow();
  395. for (int i = 0; i < reader.FieldCount; i++)
  396. row[i] = reader.GetValue(i);
  397. }
  398. }
  399. return c;
  400. }
  401. public override MySqlSchemaCollection GetForeignKeys(string[] restrictions)
  402. {
  403. if (!connection.driver.Version.isAtLeast(5, 1, 16))
  404. return base.GetForeignKeys(restrictions);
  405. string sql = @"SELECT rc.constraint_catalog, rc.constraint_schema,
  406. rc.constraint_name, kcu.table_catalog, kcu.table_schema, rc.table_name,
  407. rc.match_option, rc.update_rule, rc.delete_rule,
  408. NULL as referenced_table_catalog,
  409. kcu.referenced_table_schema, rc.referenced_table_name
  410. FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS rc
  411. LEFT JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE kcu ON
  412. kcu.constraint_catalog <=> rc.constraint_catalog AND
  413. kcu.constraint_schema <=> rc.constraint_schema AND
  414. kcu.constraint_name <=> rc.constraint_name AND
  415. kcu.ORDINAL_POSITION=1 WHERE 1=1";
  416. StringBuilder where = new StringBuilder();
  417. if (restrictions.Length >= 2 && !String.IsNullOrEmpty(restrictions[1]))
  418. where.AppendFormat(CultureInfo.InvariantCulture,
  419. " AND rc.constraint_schema LIKE '{0}'", restrictions[1]);
  420. if (restrictions.Length >= 3 && !String.IsNullOrEmpty(restrictions[2]))
  421. where.AppendFormat(CultureInfo.InvariantCulture,
  422. " AND rc.table_name LIKE '{0}'", restrictions[2]);
  423. if (restrictions.Length >= 4 && !String.IsNullOrEmpty(restrictions[3]))
  424. where.AppendFormat(CultureInfo.InvariantCulture,
  425. " AND rc.constraint_name LIKE '{0}'", restrictions[2]);
  426. sql += where.ToString();
  427. return GetTable(sql);
  428. }
  429. public override MySqlSchemaCollection GetForeignKeyColumns(string[] restrictions)
  430. {
  431. if (!connection.driver.Version.isAtLeast(5, 0, 6))
  432. return base.GetForeignKeyColumns(restrictions);
  433. string sql = @"SELECT kcu.* FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE kcu
  434. WHERE kcu.referenced_table_name IS NOT NULL";
  435. StringBuilder where = new StringBuilder();
  436. if (restrictions.Length >= 2 && !String.IsNullOrEmpty(restrictions[1]))
  437. where.AppendFormat(CultureInfo.InvariantCulture,
  438. " AND kcu.constraint_schema LIKE '{0}'", restrictions[1]);
  439. if (restrictions.Length >= 3 && !String.IsNullOrEmpty(restrictions[2]))
  440. where.AppendFormat(CultureInfo.InvariantCulture,
  441. " AND kcu.table_name LIKE '{0}'", restrictions[2]);
  442. if (restrictions.Length >= 4 && !String.IsNullOrEmpty(restrictions[3]))
  443. where.AppendFormat(CultureInfo.InvariantCulture,
  444. " AND kcu.constraint_name LIKE '{0}'", restrictions[3]);
  445. sql += where.ToString();
  446. return GetTable(sql);
  447. }
  448. #region Procedures Support Rouines
  449. internal void GetParametersFromShowCreate(MySqlSchemaCollection parametersTable,
  450. string[] restrictions, MySqlSchemaCollection routines)
  451. {
  452. // this allows us to pass in a pre-populated routines table
  453. // and avoid the querying for them again.
  454. // we use this when calling a procedure or function
  455. if (routines == null)
  456. routines = GetSchema("procedures", restrictions);
  457. MySqlCommand cmd = connection.CreateCommand();
  458. foreach (MySqlSchemaRow routine in routines.Rows)
  459. {
  460. string showCreateSql = String.Format("SHOW CREATE {0} `{1}`.`{2}`",
  461. routine["ROUTINE_TYPE"], routine["ROUTINE_SCHEMA"],
  462. routine["ROUTINE_NAME"]);
  463. cmd.CommandText = showCreateSql;
  464. try
  465. {
  466. string nameToRestrict = null;
  467. if (restrictions != null && restrictions.Length == 5 &&
  468. restrictions[4] != null)
  469. nameToRestrict = restrictions[4];
  470. using (MySqlDataReader reader = cmd.ExecuteReader())
  471. {
  472. reader.Read();
  473. string body = reader.GetString(2);
  474. reader.Close();
  475. ParseProcedureBody(parametersTable, body, routine, nameToRestrict);
  476. }
  477. }
  478. catch (System.Data.SqlTypes.SqlNullValueException snex)
  479. {
  480. throw new InvalidOperationException(
  481. String.Format(Resources.UnableToRetrieveParameters, routine["ROUTINE_NAME"]), snex);
  482. }
  483. }
  484. }
  485. private void ParseProcedureBody(MySqlSchemaCollection parametersTable, string body,
  486. MySqlSchemaRow row, string nameToRestrict)
  487. {
  488. List<string> modes = new List<string>(new string[3] { "IN", "OUT", "INOUT" });
  489. string sqlMode = row["SQL_MODE"].ToString();
  490. int pos = 1;
  491. MySqlTokenizer tokenizer = new MySqlTokenizer(body);
  492. tokenizer.AnsiQuotes = sqlMode.IndexOf("ANSI_QUOTES") != -1;
  493. tokenizer.BackslashEscapes = sqlMode.IndexOf("NO_BACKSLASH_ESCAPES") == -1;
  494. tokenizer.ReturnComments = false;
  495. string token = tokenizer.NextToken();
  496. // this block will scan for the opening paren while also determining
  497. // if this routine is a function. If so, then we need to add a
  498. // parameter row for the return parameter since it is ordinal position
  499. // 0 and should appear first.
  500. while (token != "(")
  501. {
  502. if (String.Compare(token, "FUNCTION", StringComparison.OrdinalIgnoreCase) == 0 &&
  503. nameToRestrict == null)
  504. {
  505. parametersTable.AddRow();
  506. InitParameterRow(row, parametersTable.Rows[0]);
  507. }
  508. token = tokenizer.NextToken();
  509. }
  510. token = tokenizer.NextToken(); // now move to the next token past the (
  511. while (token != ")")
  512. {
  513. MySqlSchemaRow parmRow = parametersTable.NewRow();
  514. InitParameterRow(row, parmRow);
  515. parmRow["ORDINAL_POSITION"] = pos++;
  516. // handle mode and name for the parameter
  517. string mode = StringUtility.ToUpperInvariant(token);
  518. if (!tokenizer.Quoted && modes.Contains(mode))
  519. {
  520. parmRow["PARAMETER_MODE"] = mode;
  521. token = tokenizer.NextToken();
  522. }
  523. if (tokenizer.Quoted)
  524. token = token.Substring(1, token.Length - 2);
  525. parmRow["PARAMETER_NAME"] = token;
  526. // now parse data type
  527. token = ParseDataType(parmRow, tokenizer);
  528. if (token == ",")
  529. token = tokenizer.NextToken();
  530. // now determine if we should include this row after all
  531. // we need to parse it before this check so we are correctly
  532. // positioned for the next parameter
  533. if (nameToRestrict == null ||
  534. String.Compare(parmRow["PARAMETER_NAME"].ToString(), nameToRestrict, StringComparison.OrdinalIgnoreCase) == 0)
  535. parametersTable.Rows.Add(parmRow);
  536. }
  537. // now parse out the return parameter if there is one.
  538. token = StringUtility.ToUpperInvariant(tokenizer.NextToken());
  539. if (String.Compare(token, "RETURNS", StringComparison.OrdinalIgnoreCase) == 0)
  540. {
  541. MySqlSchemaRow parameterRow = parametersTable.Rows[0];
  542. parameterRow["PARAMETER_NAME"] = "RETURN_VALUE";
  543. ParseDataType(parameterRow, tokenizer);
  544. }
  545. }
  546. /// <summary>
  547. /// Initializes a new row for the procedure parameters table.
  548. /// </summary>
  549. private static void InitParameterRow(MySqlSchemaRow procedure, MySqlSchemaRow parameter)
  550. {
  551. parameter["SPECIFIC_CATALOG"] = null;
  552. parameter["SPECIFIC_SCHEMA"] = procedure["ROUTINE_SCHEMA"];
  553. parameter["SPECIFIC_NAME"] = procedure["ROUTINE_NAME"];
  554. parameter["PARAMETER_MODE"] = "IN";
  555. parameter["ORDINAL_POSITION"] = 0;
  556. parameter["ROUTINE_TYPE"] = procedure["ROUTINE_TYPE"];
  557. }
  558. /// <summary>
  559. /// Parses out the elements of a procedure parameter data type.
  560. /// </summary>
  561. private string ParseDataType(MySqlSchemaRow row, MySqlTokenizer tokenizer)
  562. {
  563. StringBuilder dtd = new StringBuilder(
  564. StringUtility.ToUpperInvariant(tokenizer.NextToken()));
  565. row["DATA_TYPE"] = dtd.ToString();
  566. string type = row["DATA_TYPE"].ToString();
  567. string token = tokenizer.NextToken();
  568. if (token == "(")
  569. {
  570. token = tokenizer.ReadParenthesis();
  571. dtd.AppendFormat(CultureInfo.InvariantCulture, "{0}", token);
  572. if (type != "ENUM" && type != "SET")
  573. ParseDataTypeSize(row, token);
  574. token = tokenizer.NextToken();
  575. }
  576. else
  577. dtd.Append(GetDataTypeDefaults(type, row));
  578. while (token != ")" &&
  579. token != "," &&
  580. String.Compare(token, "begin", StringComparison.OrdinalIgnoreCase) != 0 &&
  581. String.Compare(token, "return", StringComparison.OrdinalIgnoreCase) != 0)
  582. {
  583. if (String.Compare(token, "CHARACTER", StringComparison.OrdinalIgnoreCase) == 0 ||
  584. String.Compare(token, "BINARY", StringComparison.OrdinalIgnoreCase) == 0)
  585. { } // we don't need to do anything with this
  586. else if (String.Compare(token, "SET", StringComparison.OrdinalIgnoreCase) == 0 ||
  587. String.Compare(token, "CHARSET", StringComparison.OrdinalIgnoreCase) == 0)
  588. row["CHARACTER_SET_NAME"] = tokenizer.NextToken();
  589. else if (String.Compare(token, "ASCII", StringComparison.OrdinalIgnoreCase) == 0)
  590. row["CHARACTER_SET_NAME"] = "latin1";
  591. else if (String.Compare(token, "UNICODE", StringComparison.OrdinalIgnoreCase) == 0)
  592. row["CHARACTER_SET_NAME"] = "ucs2";
  593. else if (String.Compare(token, "COLLATE", StringComparison.OrdinalIgnoreCase) == 0)
  594. row["COLLATION_NAME"] = tokenizer.NextToken();
  595. else
  596. dtd.AppendFormat(CultureInfo.InvariantCulture, " {0}", token);
  597. token = tokenizer.NextToken();
  598. }
  599. if (dtd.Length > 0)
  600. row["DTD_IDENTIFIER"] = dtd.ToString();
  601. // now default the collation if one wasn't given
  602. if (string.IsNullOrEmpty((string)row["COLLATION_NAME"]) &&
  603. !string.IsNullOrEmpty((string)row["CHARACTER_SET_NAME"]))
  604. row["COLLATION_NAME"] = CharSetMap.GetDefaultCollation(
  605. row["CHARACTER_SET_NAME"].ToString(), connection);
  606. // now set the octet length
  607. if (row["CHARACTER_MAXIMUM_LENGTH"] != null)
  608. {
  609. if (row["CHARACTER_SET_NAME"] == null)
  610. row["CHARACTER_SET_NAME"] = "";
  611. row["CHARACTER_OCTET_LENGTH"] =
  612. CharSetMap.GetMaxLength((string)row["CHARACTER_SET_NAME"], connection) *
  613. (int)row["CHARACTER_MAXIMUM_LENGTH"];
  614. }
  615. return token;
  616. }
  617. private static string GetDataTypeDefaults(string type, MySqlSchemaRow row)
  618. {
  619. string format = "({0},{1})";
  620. object precision = row["NUMERIC_PRECISION"];
  621. if (MetaData.IsNumericType(type) &&
  622. string.IsNullOrEmpty((string)row["NUMERIC_PRECISION"]))
  623. {
  624. row["NUMERIC_PRECISION"] = 10;
  625. row["NUMERIC_SCALE"] = 0;
  626. if (!MetaData.SupportScale(type))
  627. format = "({0})";
  628. return String.Format(format, row["NUMERIC_PRECISION"],
  629. row["NUMERIC_SCALE"]);
  630. }
  631. return String.Empty;
  632. }
  633. private static void ParseDataTypeSize(MySqlSchemaRow row, string size)
  634. {
  635. size = size.Trim('(', ')');
  636. string[] parts = size.Split(',');
  637. if (!MetaData.IsNumericType(row["DATA_TYPE"].ToString()))
  638. {
  639. row["CHARACTER_MAXIMUM_LENGTH"] = Int32.Parse(parts[0], CultureInfo.InvariantCulture);
  640. // will set octet length in a minute
  641. }
  642. else
  643. {
  644. row["NUMERIC_PRECISION"] = Int32.Parse(parts[0], CultureInfo.InvariantCulture);
  645. if (parts.Length == 2)
  646. row["NUMERIC_SCALE"] = Int32.Parse(parts[1], CultureInfo.InvariantCulture);
  647. }
  648. }
  649. #endregion
  650. }
  651. }
  652. #endif