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.

737 lines
27 KiB

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