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.

1042 lines
46 KiB

4 years ago
  1. #if MYSQL_6_9
  2. // Copyright ?2004, 2015, 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 System;
  24. using System.Globalization;
  25. using System.IO;
  26. using System.Reflection;
  27. using System.Text;
  28. using Externals.MySql.Data.Common;
  29. using Externals.MySql.Data.Types;
  30. using System.Collections.Specialized;
  31. using System.Collections;
  32. using System.Text.RegularExpressions;
  33. using Externals.MySql.Data.MySqlClient.Properties;
  34. using System.Collections.Generic;
  35. using System.Data;
  36. using System.Data.Common;
  37. namespace Externals.MySql.Data.MySqlClient
  38. {
  39. internal class SchemaProvider
  40. {
  41. protected MySqlConnection connection;
  42. public static string MetaCollection = "MetaDataCollections";
  43. public SchemaProvider(MySqlConnection connectionToUse)
  44. {
  45. connection = connectionToUse;
  46. }
  47. public virtual MySqlSchemaCollection GetSchema(string collection, String[] restrictions)
  48. {
  49. if (connection.State != ConnectionState.Open)
  50. throw new MySqlException("GetSchema can only be called on an open connection.");
  51. collection = StringUtility.ToUpperInvariant(collection);
  52. MySqlSchemaCollection c = GetSchemaInternal(collection, restrictions);
  53. if (c == null)
  54. throw new ArgumentException("Invalid collection name");
  55. return c;
  56. }
  57. public virtual MySqlSchemaCollection GetDatabases(string[] restrictions)
  58. {
  59. Regex regex = null;
  60. int caseSetting = Int32.Parse(connection.driver.Property("lower_case_table_names"));
  61. string sql = "SHOW DATABASES";
  62. // if lower_case_table_names is zero, then case lookup should be sensitive
  63. // so we can use LIKE to do the matching.
  64. if (caseSetting == 0)
  65. {
  66. if (restrictions != null && restrictions.Length >= 1)
  67. sql = sql + " LIKE '" + restrictions[0] + "'";
  68. }
  69. MySqlSchemaCollection c = QueryCollection("Databases", sql);
  70. if (caseSetting != 0 && restrictions != null && restrictions.Length >= 1 && restrictions[0] != null)
  71. regex = new Regex(restrictions[0], RegexOptions.IgnoreCase);
  72. MySqlSchemaCollection c2 = new MySqlSchemaCollection("Databases");
  73. c2.AddColumn("CATALOG_NAME", typeof(string));
  74. c2.AddColumn("SCHEMA_NAME", typeof(string));
  75. foreach (MySqlSchemaRow row in c.Rows)
  76. {
  77. if (regex != null && !regex.Match(row[0].ToString()).Success) continue;
  78. MySqlSchemaRow newRow = c2.AddRow();
  79. newRow[1] = row[0];
  80. }
  81. return c2;
  82. }
  83. public virtual MySqlSchemaCollection GetTables(string[] restrictions)
  84. {
  85. MySqlSchemaCollection c = new MySqlSchemaCollection("Tables");
  86. c.AddColumn("TABLE_CATALOG", typeof(string));
  87. c.AddColumn("TABLE_SCHEMA", typeof(string));
  88. c.AddColumn("TABLE_NAME", typeof(string));
  89. c.AddColumn("TABLE_TYPE", typeof(string));
  90. c.AddColumn("ENGINE", typeof(string));
  91. c.AddColumn("VERSION", typeof(ulong));
  92. c.AddColumn("ROW_FORMAT", typeof(string));
  93. c.AddColumn("TABLE_ROWS", typeof(ulong));
  94. c.AddColumn("AVG_ROW_LENGTH", typeof(ulong));
  95. c.AddColumn("DATA_LENGTH", typeof(ulong));
  96. c.AddColumn("MAX_DATA_LENGTH", typeof(ulong));
  97. c.AddColumn("INDEX_LENGTH", typeof(ulong));
  98. c.AddColumn("DATA_FREE", typeof(ulong));
  99. c.AddColumn("AUTO_INCREMENT", typeof(ulong));
  100. c.AddColumn("CREATE_TIME", typeof(DateTime));
  101. c.AddColumn("UPDATE_TIME", typeof(DateTime));
  102. c.AddColumn("CHECK_TIME", typeof(DateTime));
  103. c.AddColumn("TABLE_COLLATION", typeof(string));
  104. c.AddColumn("CHECKSUM", typeof(ulong));
  105. c.AddColumn("CREATE_OPTIONS", typeof(string));
  106. c.AddColumn("TABLE_COMMENT", typeof(string));
  107. // we have to new up a new restriction array here since
  108. // GetDatabases takes the database in the first slot
  109. string[] dbRestriction = new string[4];
  110. if (restrictions != null && restrictions.Length >= 2)
  111. dbRestriction[0] = restrictions[1];
  112. MySqlSchemaCollection databases = GetDatabases(dbRestriction);
  113. if (restrictions != null)
  114. Array.Copy(restrictions, dbRestriction,
  115. Math.Min(dbRestriction.Length, restrictions.Length));
  116. foreach (MySqlSchemaRow row in databases.Rows)
  117. {
  118. dbRestriction[1] = row["SCHEMA_NAME"].ToString();
  119. FindTables(c, dbRestriction);
  120. }
  121. return c;
  122. }
  123. protected void QuoteDefaultValues(MySqlSchemaCollection schemaCollection)
  124. {
  125. if (schemaCollection == null) return;
  126. if (!schemaCollection.ContainsColumn("COLUMN_DEFAULT")) return;
  127. foreach (MySqlSchemaRow row in schemaCollection.Rows)
  128. {
  129. object defaultValue = row["COLUMN_DEFAULT"];
  130. if (MetaData.IsTextType(row["DATA_TYPE"].ToString()))
  131. row["COLUMN_DEFAULT"] = String.Format("{0}", defaultValue);
  132. }
  133. }
  134. public virtual MySqlSchemaCollection GetColumns(string[] restrictions)
  135. {
  136. MySqlSchemaCollection c = new MySqlSchemaCollection("Columns");
  137. c.AddColumn("TABLE_CATALOG", typeof(string));
  138. c.AddColumn("TABLE_SCHEMA", typeof(string));
  139. c.AddColumn("TABLE_NAME", typeof(string));
  140. c.AddColumn("COLUMN_NAME", typeof(string));
  141. c.AddColumn("ORDINAL_POSITION", typeof(ulong));
  142. c.AddColumn("COLUMN_DEFAULT", typeof(string));
  143. c.AddColumn("IS_NULLABLE", typeof(string));
  144. c.AddColumn("DATA_TYPE", typeof(string));
  145. c.AddColumn("CHARACTER_MAXIMUM_LENGTH", typeof(ulong));
  146. c.AddColumn("CHARACTER_OCTET_LENGTH", typeof(ulong));
  147. c.AddColumn("NUMERIC_PRECISION", typeof(ulong));
  148. c.AddColumn("NUMERIC_SCALE", typeof(ulong));
  149. c.AddColumn("CHARACTER_SET_NAME", typeof(string));
  150. c.AddColumn("COLLATION_NAME", typeof(string));
  151. c.AddColumn("COLUMN_TYPE", typeof(string));
  152. c.AddColumn("COLUMN_KEY", typeof(string));
  153. c.AddColumn("EXTRA", typeof(string));
  154. c.AddColumn("PRIVILEGES", typeof(string));
  155. c.AddColumn("COLUMN_COMMENT", typeof(string));
  156. c.AddColumn("GENERATION_EXPRESSION", typeof(string));
  157. // we don't allow restricting on table type here
  158. string columnName = null;
  159. if (restrictions != null && restrictions.Length == 4)
  160. {
  161. columnName = restrictions[3];
  162. restrictions[3] = null;
  163. }
  164. MySqlSchemaCollection tables = GetTables(restrictions);
  165. foreach (MySqlSchemaRow row in tables.Rows)
  166. LoadTableColumns(c, row["TABLE_SCHEMA"].ToString(),
  167. row["TABLE_NAME"].ToString(), columnName);
  168. QuoteDefaultValues(c);
  169. return c;
  170. }
  171. private void LoadTableColumns(MySqlSchemaCollection schemaCollection, string schema,
  172. string tableName, string columnRestriction)
  173. {
  174. string sql = String.Format("SHOW FULL COLUMNS FROM `{0}`.`{1}`",
  175. schema, tableName);
  176. MySqlCommand cmd = new MySqlCommand(sql, connection);
  177. int pos = 1;
  178. using (MySqlDataReader reader = cmd.ExecuteReader())
  179. {
  180. while (reader.Read())
  181. {
  182. string colName = reader.GetString(0);
  183. if (columnRestriction != null && colName != columnRestriction)
  184. continue;
  185. MySqlSchemaRow row = schemaCollection.AddRow();
  186. row["TABLE_CATALOG"] = DBNull.Value;
  187. row["TABLE_SCHEMA"] = schema;
  188. row["TABLE_NAME"] = tableName;
  189. row["COLUMN_NAME"] = colName;
  190. row["ORDINAL_POSITION"] = pos++;
  191. row["COLUMN_DEFAULT"] = reader.GetValue(5);
  192. row["IS_NULLABLE"] = reader.GetString(3);
  193. row["DATA_TYPE"] = reader.GetString(1);
  194. row["CHARACTER_MAXIMUM_LENGTH"] = DBNull.Value;
  195. row["CHARACTER_OCTET_LENGTH"] = DBNull.Value;
  196. row["NUMERIC_PRECISION"] = DBNull.Value;
  197. row["NUMERIC_SCALE"] = DBNull.Value;
  198. row["CHARACTER_SET_NAME"] = reader.GetValue(2);
  199. row["COLLATION_NAME"] = row["CHARACTER_SET_NAME"];
  200. row["COLUMN_TYPE"] = reader.GetString(1);
  201. row["COLUMN_KEY"] = reader.GetString(4);
  202. row["EXTRA"] = reader.GetString(6);
  203. row["PRIVILEGES"] = reader.GetString(7);
  204. row["COLUMN_COMMENT"] = reader.GetString(8);
  205. row["GENERATION_EXPRESION"] = reader.GetString(6).Contains("VIRTUAL") ? reader.GetString(9) : string.Empty;
  206. ParseColumnRow(row);
  207. }
  208. }
  209. }
  210. private static void ParseColumnRow(MySqlSchemaRow row)
  211. {
  212. // first parse the character set name
  213. string charset = row["CHARACTER_SET_NAME"].ToString();
  214. int index = charset.IndexOf('_');
  215. if (index != -1)
  216. row["CHARACTER_SET_NAME"] = charset.Substring(0, index);
  217. // now parse the data type
  218. string dataType = row["DATA_TYPE"].ToString();
  219. index = dataType.IndexOf('(');
  220. if (index == -1)
  221. return;
  222. row["DATA_TYPE"] = dataType.Substring(0, index);
  223. int stop = dataType.IndexOf(')', index);
  224. string dataLen = dataType.Substring(index + 1, stop - (index + 1));
  225. string lowerType = row["DATA_TYPE"].ToString().ToLower();
  226. if (lowerType == "char" || lowerType == "varchar")
  227. row["CHARACTER_MAXIMUM_LENGTH"] = dataLen;
  228. else if (lowerType == "real" || lowerType == "decimal")
  229. {
  230. string[] lenparts = dataLen.Split(new char[] { ',' });
  231. row["NUMERIC_PRECISION"] = lenparts[0];
  232. if (lenparts.Length == 2)
  233. row["NUMERIC_SCALE"] = lenparts[1];
  234. }
  235. }
  236. public virtual MySqlSchemaCollection GetIndexes(string[] restrictions)
  237. {
  238. MySqlSchemaCollection dt = new MySqlSchemaCollection("Indexes");
  239. dt.AddColumn("INDEX_CATALOG", typeof(string));
  240. dt.AddColumn("INDEX_SCHEMA", typeof(string));
  241. dt.AddColumn("INDEX_NAME", typeof(string));
  242. dt.AddColumn("TABLE_NAME", typeof(string));
  243. dt.AddColumn("UNIQUE", typeof(bool));
  244. dt.AddColumn("PRIMARY", typeof(bool));
  245. dt.AddColumn("TYPE", typeof(string));
  246. dt.AddColumn("COMMENT", typeof(string));
  247. // Get the list of tables first
  248. int max = restrictions == null ? 4 : restrictions.Length;
  249. string[] tableRestrictions = new string[Math.Max(max, 4)];
  250. if (restrictions != null)
  251. restrictions.CopyTo(tableRestrictions, 0);
  252. tableRestrictions[3] = "BASE TABLE";
  253. MySqlSchemaCollection tables = GetTables(tableRestrictions);
  254. foreach (MySqlSchemaRow table in tables.Rows)
  255. {
  256. string sql = String.Format("SHOW INDEX FROM `{0}`.`{1}`",
  257. MySqlHelper.DoubleQuoteString((string)table["TABLE_SCHEMA"]),
  258. MySqlHelper.DoubleQuoteString((string)table["TABLE_NAME"]));
  259. MySqlSchemaCollection indexes = QueryCollection("indexes", sql);
  260. foreach (MySqlSchemaRow index in indexes.Rows)
  261. {
  262. long seq_index = (long)index["SEQ_IN_INDEX"];
  263. if (seq_index != 1) continue;
  264. if (restrictions != null && restrictions.Length == 4 &&
  265. restrictions[3] != null &&
  266. !index["KEY_NAME"].Equals(restrictions[3])) continue;
  267. MySqlSchemaRow row = dt.AddRow();
  268. row["INDEX_CATALOG"] = null;
  269. row["INDEX_SCHEMA"] = table["TABLE_SCHEMA"];
  270. row["INDEX_NAME"] = index["KEY_NAME"];
  271. row["TABLE_NAME"] = index["TABLE"];
  272. row["UNIQUE"] = (long)index["NON_UNIQUE"] == 0;
  273. row["PRIMARY"] = index["KEY_NAME"].Equals("PRIMARY");
  274. row["TYPE"] = index["INDEX_TYPE"];
  275. row["COMMENT"] = index["COMMENT"];
  276. }
  277. }
  278. return dt;
  279. }
  280. public virtual MySqlSchemaCollection GetIndexColumns(string[] restrictions)
  281. {
  282. MySqlSchemaCollection dt = new MySqlSchemaCollection("IndexColumns");
  283. dt.AddColumn("INDEX_CATALOG", typeof(string));
  284. dt.AddColumn("INDEX_SCHEMA", typeof(string));
  285. dt.AddColumn("INDEX_NAME", typeof(string));
  286. dt.AddColumn("TABLE_NAME", typeof(string));
  287. dt.AddColumn("COLUMN_NAME", typeof(string));
  288. dt.AddColumn("ORDINAL_POSITION", typeof(int));
  289. dt.AddColumn("SORT_ORDER", typeof(string));
  290. int max = restrictions == null ? 4 : restrictions.Length;
  291. string[] tableRestrictions = new string[Math.Max(max, 4)];
  292. if (restrictions != null)
  293. restrictions.CopyTo(tableRestrictions, 0);
  294. tableRestrictions[3] = "BASE TABLE";
  295. MySqlSchemaCollection tables = GetTables(tableRestrictions);
  296. foreach (MySqlSchemaRow table in tables.Rows)
  297. {
  298. string sql = String.Format("SHOW INDEX FROM `{0}`.`{1}`",
  299. table["TABLE_SCHEMA"], table["TABLE_NAME"]);
  300. MySqlCommand cmd = new MySqlCommand(sql, connection);
  301. using (MySqlDataReader reader = cmd.ExecuteReader())
  302. {
  303. while (reader.Read())
  304. {
  305. string key_name = GetString(reader, reader.GetOrdinal("KEY_NAME"));
  306. string col_name = GetString(reader, reader.GetOrdinal("COLUMN_NAME"));
  307. if (restrictions != null)
  308. {
  309. if (restrictions.Length >= 4 && restrictions[3] != null &&
  310. key_name != restrictions[3]) continue;
  311. if (restrictions.Length >= 5 && restrictions[4] != null &&
  312. col_name != restrictions[4]) continue;
  313. }
  314. MySqlSchemaRow row = dt.AddRow();
  315. row["INDEX_CATALOG"] = null;
  316. row["INDEX_SCHEMA"] = table["TABLE_SCHEMA"];
  317. row["INDEX_NAME"] = key_name;
  318. row["TABLE_NAME"] = GetString(reader, reader.GetOrdinal("TABLE"));
  319. row["COLUMN_NAME"] = col_name;
  320. row["ORDINAL_POSITION"] = reader.GetValue(reader.GetOrdinal("SEQ_IN_INDEX"));
  321. row["SORT_ORDER"] = reader.GetString("COLLATION");
  322. }
  323. }
  324. }
  325. return dt;
  326. }
  327. public virtual MySqlSchemaCollection GetForeignKeys(string[] restrictions)
  328. {
  329. MySqlSchemaCollection dt = new MySqlSchemaCollection("Foreign Keys");
  330. dt.AddColumn("CONSTRAINT_CATALOG", typeof(string));
  331. dt.AddColumn("CONSTRAINT_SCHEMA", typeof(string));
  332. dt.AddColumn("CONSTRAINT_NAME", typeof(string));
  333. dt.AddColumn("TABLE_CATALOG", typeof(string));
  334. dt.AddColumn("TABLE_SCHEMA", typeof(string));
  335. dt.AddColumn("TABLE_NAME", typeof(string));
  336. dt.AddColumn("MATCH_OPTION", typeof(string));
  337. dt.AddColumn("UPDATE_RULE", typeof(string));
  338. dt.AddColumn("DELETE_RULE", typeof(string));
  339. dt.AddColumn("REFERENCED_TABLE_CATALOG", typeof(string));
  340. dt.AddColumn("REFERENCED_TABLE_SCHEMA", typeof(string));
  341. dt.AddColumn("REFERENCED_TABLE_NAME", typeof(string));
  342. // first we use our restrictions to get a list of tables that should be
  343. // consulted. We save the keyname restriction since GetTables doesn't
  344. // understand that.
  345. string keyName = null;
  346. if (restrictions != null && restrictions.Length >= 4)
  347. {
  348. keyName = restrictions[3];
  349. restrictions[3] = null;
  350. }
  351. MySqlSchemaCollection tables = GetTables(restrictions);
  352. // now for each table retrieved, we call our helper function to
  353. // parse it's foreign keys
  354. foreach (MySqlSchemaRow table in tables.Rows)
  355. GetForeignKeysOnTable(dt, table, keyName, false);
  356. return dt;
  357. }
  358. public virtual MySqlSchemaCollection GetForeignKeyColumns(string[] restrictions)
  359. {
  360. MySqlSchemaCollection dt = new MySqlSchemaCollection("Foreign Keys");
  361. dt.AddColumn("CONSTRAINT_CATALOG", typeof(string));
  362. dt.AddColumn("CONSTRAINT_SCHEMA", typeof(string));
  363. dt.AddColumn("CONSTRAINT_NAME", typeof(string));
  364. dt.AddColumn("TABLE_CATALOG", typeof(string));
  365. dt.AddColumn("TABLE_SCHEMA", typeof(string));
  366. dt.AddColumn("TABLE_NAME", typeof(string));
  367. dt.AddColumn("COLUMN_NAME", typeof(string));
  368. dt.AddColumn("ORDINAL_POSITION", typeof(int));
  369. dt.AddColumn("REFERENCED_TABLE_CATALOG", typeof(string));
  370. dt.AddColumn("REFERENCED_TABLE_SCHEMA", typeof(string));
  371. dt.AddColumn("REFERENCED_TABLE_NAME", typeof(string));
  372. dt.AddColumn("REFERENCED_COLUMN_NAME", typeof(string));
  373. // first we use our restrictions to get a list of tables that should be
  374. // consulted. We save the keyname restriction since GetTables doesn't
  375. // understand that.
  376. string keyName = null;
  377. if (restrictions != null && restrictions.Length >= 4)
  378. {
  379. keyName = restrictions[3];
  380. restrictions[3] = null;
  381. }
  382. MySqlSchemaCollection tables = GetTables(restrictions);
  383. // now for each table retrieved, we call our helper function to
  384. // parse it's foreign keys
  385. foreach (MySqlSchemaRow table in tables.Rows)
  386. GetForeignKeysOnTable(dt, table, keyName, true);
  387. return dt;
  388. }
  389. private string GetSqlMode()
  390. {
  391. MySqlCommand cmd = new MySqlCommand("SELECT @@SQL_MODE", connection);
  392. return cmd.ExecuteScalar().ToString();
  393. }
  394. #region Foreign Key routines
  395. /// <summary>
  396. /// GetForeignKeysOnTable retrieves the foreign keys on the given table.
  397. /// Since MySQL supports foreign keys on versions prior to 5.0, we can't use
  398. /// information schema. MySQL also does not include any type of SHOW command
  399. /// for foreign keys so we have to resort to use SHOW CREATE TABLE and parsing
  400. /// the output.
  401. /// </summary>
  402. /// <param name="fkTable">The table to store the key info in.</param>
  403. /// <param name="tableToParse">The table to get the foeign key info for.</param>
  404. /// <param name="filterName">Only get foreign keys that match this name.</param>
  405. /// <param name="includeColumns">Should column information be included in the table.</param>
  406. private void GetForeignKeysOnTable(MySqlSchemaCollection fkTable, MySqlSchemaRow tableToParse,
  407. string filterName, bool includeColumns)
  408. {
  409. string sqlMode = GetSqlMode();
  410. if (filterName != null)
  411. filterName = StringUtility.ToLowerInvariant(filterName);
  412. string sql = string.Format("SHOW CREATE TABLE `{0}`.`{1}`",
  413. tableToParse["TABLE_SCHEMA"], tableToParse["TABLE_NAME"]);
  414. string lowerBody = null, body = null;
  415. MySqlCommand cmd = new MySqlCommand(sql, connection);
  416. using (MySqlDataReader reader = cmd.ExecuteReader())
  417. {
  418. reader.Read();
  419. body = reader.GetString(1);
  420. lowerBody = StringUtility.ToLowerInvariant(body);
  421. }
  422. MySqlTokenizer tokenizer = new MySqlTokenizer(lowerBody);
  423. tokenizer.AnsiQuotes = sqlMode.IndexOf("ANSI_QUOTES") != -1;
  424. tokenizer.BackslashEscapes = sqlMode.IndexOf("NO_BACKSLASH_ESCAPES") != -1;
  425. while (true)
  426. {
  427. string token = tokenizer.NextToken();
  428. // look for a starting contraint
  429. while (token != null && (token != "constraint" || tokenizer.Quoted))
  430. token = tokenizer.NextToken();
  431. if (token == null) break;
  432. ParseConstraint(fkTable, tableToParse, tokenizer, includeColumns);
  433. }
  434. }
  435. private static void ParseConstraint(MySqlSchemaCollection fkTable, MySqlSchemaRow table,
  436. MySqlTokenizer tokenizer, bool includeColumns)
  437. {
  438. string name = tokenizer.NextToken();
  439. MySqlSchemaRow row = fkTable.AddRow();
  440. // make sure this constraint is a FK
  441. string token = tokenizer.NextToken();
  442. if (token != "foreign" || tokenizer.Quoted)
  443. return;
  444. tokenizer.NextToken(); // read off the 'KEY' symbol
  445. tokenizer.NextToken(); // read off the '(' symbol
  446. row["CONSTRAINT_CATALOG"] = table["TABLE_CATALOG"];
  447. row["CONSTRAINT_SCHEMA"] = table["TABLE_SCHEMA"];
  448. row["TABLE_CATALOG"] = table["TABLE_CATALOG"];
  449. row["TABLE_SCHEMA"] = table["TABLE_SCHEMA"];
  450. row["TABLE_NAME"] = table["TABLE_NAME"];
  451. row["REFERENCED_TABLE_CATALOG"] = null;
  452. row["CONSTRAINT_NAME"] = name.Trim(new char[] { '\'', '`' });
  453. List<string> srcColumns = includeColumns ? ParseColumns(tokenizer) : null;
  454. // now look for the references section
  455. while (token != "references" || tokenizer.Quoted)
  456. token = tokenizer.NextToken();
  457. string target1 = tokenizer.NextToken();
  458. string target2 = tokenizer.NextToken();
  459. if (target2.StartsWith(".", StringComparison.Ordinal))
  460. {
  461. row["REFERENCED_TABLE_SCHEMA"] = target1;
  462. row["REFERENCED_TABLE_NAME"] = target2.Substring(1).Trim(new char[] { '\'', '`' });
  463. tokenizer.NextToken(); // read off the '('
  464. }
  465. else
  466. {
  467. row["REFERENCED_TABLE_SCHEMA"] = table["TABLE_SCHEMA"];
  468. row["REFERENCED_TABLE_NAME"] = target1.Substring(1).Trim(new char[] { '\'', '`' }); ;
  469. }
  470. // if we are supposed to include columns, read the target columns
  471. List<string> targetColumns = includeColumns ? ParseColumns(tokenizer) : null;
  472. if (includeColumns)
  473. ProcessColumns(fkTable, row, srcColumns, targetColumns);
  474. else
  475. fkTable.Rows.Add(row);
  476. }
  477. private static List<string> ParseColumns(MySqlTokenizer tokenizer)
  478. {
  479. List<string> sc = new List<string>();
  480. string token = tokenizer.NextToken();
  481. while (token != ")")
  482. {
  483. if (token != ",")
  484. sc.Add(token);
  485. token = tokenizer.NextToken();
  486. }
  487. return sc;
  488. }
  489. private static void ProcessColumns(MySqlSchemaCollection fkTable, MySqlSchemaRow row, List<string> srcColumns, List<string> targetColumns)
  490. {
  491. for (int i = 0; i < srcColumns.Count; i++)
  492. {
  493. MySqlSchemaRow newRow = fkTable.AddRow();
  494. row.CopyRow(newRow);
  495. newRow["COLUMN_NAME"] = srcColumns[i];
  496. newRow["ORDINAL_POSITION"] = i;
  497. newRow["REFERENCED_COLUMN_NAME"] = targetColumns[i];
  498. fkTable.Rows.Add(newRow);
  499. }
  500. }
  501. #endregion
  502. public virtual MySqlSchemaCollection GetUsers(string[] restrictions)
  503. {
  504. StringBuilder sb = new StringBuilder("SELECT Host, User FROM mysql.user");
  505. if (restrictions != null && restrictions.Length > 0)
  506. sb.AppendFormat(CultureInfo.InvariantCulture, " WHERE User LIKE '{0}'", restrictions[0]);
  507. MySqlSchemaCollection c = QueryCollection("Users", sb.ToString());
  508. c.Columns[0].Name = "HOST";
  509. c.Columns[1].Name = "USERNAME";
  510. return c;
  511. }
  512. public virtual MySqlSchemaCollection GetProcedures(string[] restrictions)
  513. {
  514. MySqlSchemaCollection dt = new MySqlSchemaCollection("Procedures");
  515. dt.AddColumn("SPECIFIC_NAME", typeof(string));
  516. dt.AddColumn("ROUTINE_CATALOG", typeof(string));
  517. dt.AddColumn("ROUTINE_SCHEMA", typeof(string));
  518. dt.AddColumn("ROUTINE_NAME", typeof(string));
  519. dt.AddColumn("ROUTINE_TYPE", typeof(string));
  520. dt.AddColumn("DTD_IDENTIFIER", typeof(string));
  521. dt.AddColumn("ROUTINE_BODY", typeof(string));
  522. dt.AddColumn("ROUTINE_DEFINITION", typeof(string));
  523. dt.AddColumn("EXTERNAL_NAME", typeof(string));
  524. dt.AddColumn("EXTERNAL_LANGUAGE", typeof(string));
  525. dt.AddColumn("PARAMETER_STYLE", typeof(string));
  526. dt.AddColumn("IS_DETERMINISTIC", typeof(string));
  527. dt.AddColumn("SQL_DATA_ACCESS", typeof(string));
  528. dt.AddColumn("SQL_PATH", typeof(string));
  529. dt.AddColumn("SECURITY_TYPE", typeof(string));
  530. dt.AddColumn("CREATED", typeof(DateTime));
  531. dt.AddColumn("LAST_ALTERED", typeof(DateTime));
  532. dt.AddColumn("SQL_MODE", typeof(string));
  533. dt.AddColumn("ROUTINE_COMMENT", typeof(string));
  534. dt.AddColumn("DEFINER", typeof(string));
  535. StringBuilder sql = new StringBuilder("SELECT * FROM mysql.proc WHERE 1=1");
  536. if (restrictions != null)
  537. {
  538. if (restrictions.Length >= 2 && restrictions[1] != null)
  539. sql.AppendFormat(CultureInfo.InvariantCulture,
  540. " AND db LIKE '{0}'", restrictions[1]);
  541. if (restrictions.Length >= 3 && restrictions[2] != null)
  542. sql.AppendFormat(CultureInfo.InvariantCulture,
  543. " AND name LIKE '{0}'", restrictions[2]);
  544. if (restrictions.Length >= 4 && restrictions[3] != null)
  545. sql.AppendFormat(CultureInfo.InvariantCulture,
  546. " AND type LIKE '{0}'", restrictions[3]);
  547. }
  548. MySqlCommand cmd = new MySqlCommand(sql.ToString(), connection);
  549. using (MySqlDataReader reader = cmd.ExecuteReader())
  550. {
  551. while (reader.Read())
  552. {
  553. MySqlSchemaRow row = dt.AddRow();
  554. row["SPECIFIC_NAME"] = reader.GetString("specific_name");
  555. row["ROUTINE_CATALOG"] = DBNull.Value;
  556. row["ROUTINE_SCHEMA"] = reader.GetString("db");
  557. row["ROUTINE_NAME"] = reader.GetString("name");
  558. string routineType = reader.GetString("type");
  559. row["ROUTINE_TYPE"] = routineType;
  560. row["DTD_IDENTIFIER"] = StringUtility.ToLowerInvariant(routineType) == "function" ?
  561. (object)reader.GetString("returns") : DBNull.Value;
  562. row["ROUTINE_BODY"] = "SQL";
  563. row["ROUTINE_DEFINITION"] = reader.GetString("body");
  564. row["EXTERNAL_NAME"] = DBNull.Value;
  565. row["EXTERNAL_LANGUAGE"] = DBNull.Value;
  566. row["PARAMETER_STYLE"] = "SQL";
  567. row["IS_DETERMINISTIC"] = reader.GetString("is_deterministic");
  568. row["SQL_DATA_ACCESS"] = reader.GetString("sql_data_access");
  569. row["SQL_PATH"] = DBNull.Value;
  570. row["SECURITY_TYPE"] = reader.GetString("security_type");
  571. row["CREATED"] = reader.GetDateTime("created");
  572. row["LAST_ALTERED"] = reader.GetDateTime("modified");
  573. row["SQL_MODE"] = reader.GetString("sql_mode");
  574. row["ROUTINE_COMMENT"] = reader.GetString("comment");
  575. row["DEFINER"] = reader.GetString("definer");
  576. }
  577. }
  578. return dt;
  579. }
  580. protected virtual MySqlSchemaCollection GetCollections()
  581. {
  582. object[][] collections = new object[][]
  583. {
  584. new object[] {"MetaDataCollections", 0, 0},
  585. new object[] {"DataSourceInformation", 0, 0},
  586. new object[] {"DataTypes", 0, 0},
  587. new object[] {"Restrictions", 0, 0},
  588. new object[] {"ReservedWords", 0, 0},
  589. new object[] {"Databases", 1, 1},
  590. new object[] {"Tables", 4, 2},
  591. new object[] {"Columns", 4, 4},
  592. new object[] {"Users", 1, 1},
  593. new object[] {"Foreign Keys", 4, 3},
  594. new object[] {"IndexColumns", 5, 4},
  595. new object[] {"Indexes", 4, 3},
  596. new object[] {"Foreign Key Columns", 4, 3},
  597. new object[] {"UDF", 1, 1}
  598. };
  599. MySqlSchemaCollection dt = new MySqlSchemaCollection("MetaDataCollections");
  600. dt.AddColumn("CollectionName", typeof(string));
  601. dt.AddColumn("NumberOfRestrictions", typeof(int));
  602. dt.AddColumn("NumberOfIdentifierParts", typeof(int));
  603. FillTable(dt, collections);
  604. return dt;
  605. }
  606. private MySqlSchemaCollection GetDataSourceInformation()
  607. {
  608. MySqlSchemaCollection dt = new MySqlSchemaCollection("DataSourceInformation");
  609. dt.AddColumn("CompositeIdentifierSeparatorPattern", typeof(string));
  610. dt.AddColumn("DataSourceProductName", typeof(string));
  611. dt.AddColumn("DataSourceProductVersion", typeof(string));
  612. dt.AddColumn("DataSourceProductVersionNormalized", typeof(string));
  613. dt.AddColumn("GroupByBehavior", typeof(GroupByBehavior));
  614. dt.AddColumn("IdentifierPattern", typeof(string));
  615. dt.AddColumn("IdentifierCase", typeof(IdentifierCase));
  616. dt.AddColumn("OrderByColumnsInSelect", typeof(bool));
  617. dt.AddColumn("ParameterMarkerFormat", typeof(string));
  618. dt.AddColumn("ParameterMarkerPattern", typeof(string));
  619. dt.AddColumn("ParameterNameMaxLength", typeof(int));
  620. dt.AddColumn("ParameterNamePattern", typeof(string));
  621. dt.AddColumn("QuotedIdentifierPattern", typeof(string));
  622. dt.AddColumn("QuotedIdentifierCase", typeof(IdentifierCase));
  623. dt.AddColumn("StatementSeparatorPattern", typeof(string));
  624. dt.AddColumn("StringLiteralPattern", typeof(string));
  625. dt.AddColumn("SupportedJoinOperators", typeof(SupportedJoinOperators));
  626. DBVersion v = connection.driver.Version;
  627. string ver = String.Format("{0:0}.{1:0}.{2:0}",
  628. v.Major, v.Minor, v.Build);
  629. MySqlSchemaRow row = dt.AddRow();
  630. row["CompositeIdentifierSeparatorPattern"] = "\\.";
  631. row["DataSourceProductName"] = "MySQL";
  632. row["DataSourceProductVersion"] = connection.ServerVersion;
  633. row["DataSourceProductVersionNormalized"] = ver;
  634. row["GroupByBehavior"] = GroupByBehavior.Unrelated;
  635. row["IdentifierPattern"] =
  636. @"(^\`\p{Lo}\p{Lu}\p{Ll}_@#][\p{Lo}\p{Lu}\p{Ll}\p{Nd}@$#_]*$)|(^\`[^\`\0]|\`\`+\`$)|(^\"" + [^\""\0]|\""\""+\""$)";
  637. row["IdentifierCase"] = IdentifierCase.Insensitive;
  638. row["OrderByColumnsInSelect"] = false;
  639. row["ParameterMarkerFormat"] = "{0}";
  640. row["ParameterMarkerPattern"] = "(@[A-Za-z0-9_$#]*)";
  641. row["ParameterNameMaxLength"] = 128;
  642. row["ParameterNamePattern"] =
  643. @"^[\p{Lo}\p{Lu}\p{Ll}\p{Lm}_@#][\p{Lo}\p{Lu}\p{Ll}\p{Lm}\p{Nd}\uff3f_@#\$]*(?=\s+|$)";
  644. row["QuotedIdentifierPattern"] = @"(([^\`]|\`\`)*)";
  645. row["QuotedIdentifierCase"] = IdentifierCase.Sensitive;
  646. row["StatementSeparatorPattern"] = ";";
  647. row["StringLiteralPattern"] = "'(([^']|'')*)'";
  648. row["SupportedJoinOperators"] = 15;
  649. dt.Rows.Add(row);
  650. return dt;
  651. }
  652. private static MySqlSchemaCollection GetDataTypes()
  653. {
  654. MySqlSchemaCollection dt = new MySqlSchemaCollection("DataTypes");
  655. dt.AddColumn("TypeName", typeof(string));
  656. dt.AddColumn("ProviderDbType", typeof(int));
  657. dt.AddColumn("ColumnSize", typeof(long));
  658. dt.AddColumn("CreateFormat", typeof(string));
  659. dt.AddColumn("CreateParameters", typeof(string));
  660. dt.AddColumn("DataType", typeof(string));
  661. dt.AddColumn("IsAutoincrementable", typeof(bool));
  662. dt.AddColumn("IsBestMatch", typeof(bool));
  663. dt.AddColumn("IsCaseSensitive", typeof(bool));
  664. dt.AddColumn("IsFixedLength", typeof(bool));
  665. dt.AddColumn("IsFixedPrecisionScale", typeof(bool));
  666. dt.AddColumn("IsLong", typeof(bool));
  667. dt.AddColumn("IsNullable", typeof(bool));
  668. dt.AddColumn("IsSearchable", typeof(bool));
  669. dt.AddColumn("IsSearchableWithLike", typeof(bool));
  670. dt.AddColumn("IsUnsigned", typeof(bool));
  671. dt.AddColumn("MaximumScale", typeof(short));
  672. dt.AddColumn("MinimumScale", typeof(short));
  673. dt.AddColumn("IsConcurrencyType", typeof(bool));
  674. dt.AddColumn("IsLiteralSupported", typeof(bool));
  675. dt.AddColumn("LiteralPrefix", typeof(string));
  676. dt.AddColumn("LiteralSuffix", typeof(string));
  677. dt.AddColumn("NativeDataType", typeof(string));
  678. // have each one of the types contribute to the datatypes collection
  679. MySqlBit.SetDSInfo(dt);
  680. MySqlBinary.SetDSInfo(dt);
  681. MySqlDateTime.SetDSInfo(dt);
  682. MySqlTimeSpan.SetDSInfo(dt);
  683. MySqlString.SetDSInfo(dt);
  684. MySqlDouble.SetDSInfo(dt);
  685. MySqlSingle.SetDSInfo(dt);
  686. MySqlByte.SetDSInfo(dt);
  687. MySqlInt16.SetDSInfo(dt);
  688. MySqlInt32.SetDSInfo(dt);
  689. MySqlInt64.SetDSInfo(dt);
  690. MySqlDecimal.SetDSInfo(dt);
  691. MySqlUByte.SetDSInfo(dt);
  692. MySqlUInt16.SetDSInfo(dt);
  693. MySqlUInt32.SetDSInfo(dt);
  694. MySqlUInt64.SetDSInfo(dt);
  695. return dt;
  696. }
  697. protected virtual MySqlSchemaCollection GetRestrictions()
  698. {
  699. object[][] restrictions = new object[][]
  700. {
  701. new object[] {"Users", "Name", "", 0},
  702. new object[] {"Databases", "Name", "", 0},
  703. new object[] {"Tables", "Database", "", 0},
  704. new object[] {"Tables", "Schema", "", 1},
  705. new object[] {"Tables", "Table", "", 2},
  706. new object[] {"Tables", "TableType", "", 3},
  707. new object[] {"Columns", "Database", "", 0},
  708. new object[] {"Columns", "Schema", "", 1},
  709. new object[] {"Columns", "Table", "", 2},
  710. new object[] {"Columns", "Column", "", 3},
  711. new object[] {"Indexes", "Database", "", 0},
  712. new object[] {"Indexes", "Schema", "", 1},
  713. new object[] {"Indexes", "Table", "", 2},
  714. new object[] {"Indexes", "Name", "", 3},
  715. new object[] {"IndexColumns", "Database", "", 0},
  716. new object[] {"IndexColumns", "Schema", "", 1},
  717. new object[] {"IndexColumns", "Table", "", 2},
  718. new object[] {"IndexColumns", "ConstraintName", "", 3},
  719. new object[] {"IndexColumns", "Column", "", 4},
  720. new object[] {"Foreign Keys", "Database", "", 0},
  721. new object[] {"Foreign Keys", "Schema", "", 1},
  722. new object[] {"Foreign Keys", "Table", "", 2},
  723. new object[] {"Foreign Keys", "Constraint Name", "", 3},
  724. new object[] {"Foreign Key Columns", "Catalog", "", 0},
  725. new object[] {"Foreign Key Columns", "Schema", "", 1},
  726. new object[] {"Foreign Key Columns", "Table", "", 2},
  727. new object[] {"Foreign Key Columns", "Constraint Name", "", 3},
  728. new object[] {"UDF", "Name", "", 0}
  729. };
  730. MySqlSchemaCollection dt = new MySqlSchemaCollection("Restrictions");
  731. dt.AddColumn("CollectionName", typeof(string));
  732. dt.AddColumn("RestrictionName", typeof(string));
  733. dt.AddColumn("RestrictionDefault", typeof(string));
  734. dt.AddColumn("RestrictionNumber", typeof(int));
  735. FillTable(dt, restrictions);
  736. return dt;
  737. }
  738. private static MySqlSchemaCollection GetReservedWords()
  739. {
  740. MySqlSchemaCollection dt = new MySqlSchemaCollection("ReservedWords");
  741. dt.AddColumn(DbMetaDataColumnNames.ReservedWord, typeof(string));
  742. var array = Resources.ReservedWords;
  743. foreach (var s in Resources.ReservedWords)
  744. {
  745. if (string.IsNullOrEmpty(s)) continue;
  746. MySqlSchemaRow row = dt.AddRow();
  747. row[0] = s;
  748. }
  749. return dt;
  750. }
  751. protected static void FillTable(MySqlSchemaCollection dt, object[][] data)
  752. {
  753. foreach (object[] dataItem in data)
  754. {
  755. MySqlSchemaRow row = dt.AddRow();
  756. for (int i = 0; i < dataItem.Length; i++)
  757. row[i] = dataItem[i];
  758. }
  759. }
  760. private void FindTables(MySqlSchemaCollection schema, string[] restrictions)
  761. {
  762. StringBuilder sql = new StringBuilder();
  763. StringBuilder where = new StringBuilder();
  764. sql.AppendFormat(CultureInfo.InvariantCulture,
  765. "SHOW TABLE STATUS FROM `{0}`", restrictions[1]);
  766. if (restrictions != null && restrictions.Length >= 3 &&
  767. restrictions[2] != null)
  768. where.AppendFormat(CultureInfo.InvariantCulture,
  769. " LIKE '{0}'", restrictions[2]);
  770. sql.Append(where.ToString());
  771. string table_type = restrictions[1].ToLower() == "information_schema"
  772. ?
  773. "SYSTEM VIEW"
  774. : "BASE TABLE";
  775. MySqlCommand cmd = new MySqlCommand(sql.ToString(), connection);
  776. using (MySqlDataReader reader = cmd.ExecuteReader())
  777. {
  778. while (reader.Read())
  779. {
  780. MySqlSchemaRow row = schema.AddRow();
  781. row["TABLE_CATALOG"] = null;
  782. row["TABLE_SCHEMA"] = restrictions[1];
  783. row["TABLE_NAME"] = reader.GetString(0);
  784. row["TABLE_TYPE"] = table_type;
  785. row["ENGINE"] = GetString(reader, 1);
  786. row["VERSION"] = reader.GetValue(2);
  787. row["ROW_FORMAT"] = GetString(reader, 3);
  788. row["TABLE_ROWS"] = reader.GetValue(4);
  789. row["AVG_ROW_LENGTH"] = reader.GetValue(5);
  790. row["DATA_LENGTH"] = reader.GetValue(6);
  791. row["MAX_DATA_LENGTH"] = reader.GetValue(7);
  792. row["INDEX_LENGTH"] = reader.GetValue(8);
  793. row["DATA_FREE"] = reader.GetValue(9);
  794. row["AUTO_INCREMENT"] = reader.GetValue(10);
  795. row["CREATE_TIME"] = reader.GetValue(11);
  796. row["UPDATE_TIME"] = reader.GetValue(12);
  797. row["CHECK_TIME"] = reader.GetValue(13);
  798. row["TABLE_COLLATION"] = GetString(reader, 14);
  799. row["CHECKSUM"] = reader.GetValue(15);
  800. row["CREATE_OPTIONS"] = GetString(reader, 16);
  801. row["TABLE_COMMENT"] = GetString(reader, 17);
  802. }
  803. }
  804. }
  805. private static string GetString(MySqlDataReader reader, int index)
  806. {
  807. if (reader.IsDBNull(index))
  808. return null;
  809. return reader.GetString(index);
  810. }
  811. public virtual MySqlSchemaCollection GetUDF(string[] restrictions)
  812. {
  813. string sql = "SELECT name,ret,dl FROM mysql.func";
  814. if (restrictions != null)
  815. {
  816. if (restrictions.Length >= 1 && !String.IsNullOrEmpty(restrictions[0]))
  817. sql += String.Format(" WHERE name LIKE '{0}'", restrictions[0]);
  818. }
  819. MySqlSchemaCollection dt = new MySqlSchemaCollection("User-defined Functions");
  820. dt.AddColumn("NAME", typeof(string));
  821. dt.AddColumn("RETURN_TYPE", typeof(int));
  822. dt.AddColumn("LIBRARY_NAME", typeof(string));
  823. MySqlCommand cmd = new MySqlCommand(sql, connection);
  824. try
  825. {
  826. using (MySqlDataReader reader = cmd.ExecuteReader())
  827. {
  828. while (reader.Read())
  829. {
  830. MySqlSchemaRow row = dt.AddRow();
  831. row[0] = reader.GetString(0);
  832. row[1] = reader.GetInt32(1);
  833. row[2] = reader.GetString(2);
  834. }
  835. }
  836. }
  837. catch (MySqlException ex)
  838. {
  839. if (ex.Number != (int)MySqlErrorCode.TableAccessDenied)
  840. throw;
  841. throw new MySqlException(Resources.UnableToEnumerateUDF, ex);
  842. }
  843. return dt;
  844. }
  845. protected virtual MySqlSchemaCollection GetSchemaInternal(string collection, string[] restrictions)
  846. {
  847. switch (collection)
  848. {
  849. // common collections
  850. case "METADATACOLLECTIONS":
  851. return GetCollections();
  852. case "DATASOURCEINFORMATION":
  853. return GetDataSourceInformation();
  854. case "DATATYPES":
  855. return GetDataTypes();
  856. case "RESTRICTIONS":
  857. return GetRestrictions();
  858. case "RESERVEDWORDS":
  859. return GetReservedWords();
  860. // collections specific to our provider
  861. case "USERS":
  862. return GetUsers(restrictions);
  863. case "DATABASES":
  864. return GetDatabases(restrictions);
  865. case "UDF":
  866. return GetUDF(restrictions);
  867. }
  868. // if we have a current database and our users have
  869. // not specified a database, then default to the currently
  870. // selected one.
  871. if (restrictions == null)
  872. restrictions = new string[2];
  873. if (connection != null &&
  874. connection.Database != null &&
  875. connection.Database.Length > 0 &&
  876. restrictions.Length > 1 &&
  877. restrictions[1] == null)
  878. restrictions[1] = connection.Database;
  879. switch (collection)
  880. {
  881. case "TABLES":
  882. return GetTables(restrictions);
  883. case "COLUMNS":
  884. return GetColumns(restrictions);
  885. case "INDEXES":
  886. return GetIndexes(restrictions);
  887. case "INDEXCOLUMNS":
  888. return GetIndexColumns(restrictions);
  889. case "FOREIGN KEYS":
  890. return GetForeignKeys(restrictions);
  891. case "FOREIGN KEY COLUMNS":
  892. return GetForeignKeyColumns(restrictions);
  893. }
  894. return null;
  895. }
  896. internal string[] CleanRestrictions(string[] restrictionValues)
  897. {
  898. string[] restrictions = null;
  899. if (restrictionValues != null)
  900. {
  901. restrictions = (string[])restrictionValues.Clone();
  902. for (int x = 0; x < restrictions.Length; x++)
  903. {
  904. string s = restrictions[x];
  905. if (s == null) continue;
  906. restrictions[x] = s.Trim('`');
  907. }
  908. }
  909. return restrictions;
  910. }
  911. protected MySqlSchemaCollection QueryCollection(string name, string sql)
  912. {
  913. MySqlSchemaCollection c = new MySqlSchemaCollection(name);
  914. MySqlCommand cmd = new MySqlCommand(sql, connection);
  915. MySqlDataReader reader = cmd.ExecuteReader();
  916. for (int i = 0; i < reader.FieldCount; i++)
  917. c.AddColumn(reader.GetName(i), reader.GetFieldType(i));
  918. using (reader)
  919. {
  920. while (reader.Read())
  921. {
  922. MySqlSchemaRow row = c.AddRow();
  923. for (int i = 0; i < reader.FieldCount; i++)
  924. row[i] = reader.GetValue(i);
  925. }
  926. }
  927. return c;
  928. }
  929. }
  930. }
  931. #endif