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.

353 lines
12 KiB

4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
  1. #if MYSQL_6_10
  2. // Copyright ?2006, 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 System;
  24. using System.Collections.Generic;
  25. using System.Data;
  26. using System.Text;
  27. using System.Threading.Tasks;
  28. using System.Threading;
  29. using System.IO;
  30. namespace Externals.MySql.Data.MySqlClient
  31. {
  32. /// <summary>
  33. /// Allows importing large amounts of data into a database with bulk loading.
  34. /// </summary>
  35. internal class MySqlBulkLoader
  36. {
  37. // constant values
  38. private const string defaultFieldTerminator = "\t";
  39. private const string defaultLineTerminator = "\n";
  40. private const char defaultEscapeCharacter = '\\';
  41. // fields
  42. public MySqlBulkLoader(MySqlConnection connection)
  43. {
  44. Connection = connection;
  45. Local = false;
  46. FieldTerminator = defaultFieldTerminator;
  47. LineTerminator = defaultLineTerminator;
  48. FieldQuotationCharacter = Char.MinValue;
  49. ConflictOption = MySqlBulkLoaderConflictOption.None;
  50. Columns = new List<string>();
  51. Expressions = new List<string>();
  52. }
  53. #region Properties
  54. /// <summary>
  55. /// Gets or sets the connection.
  56. /// </summary>
  57. /// <value>The connection.</value>
  58. public MySqlConnection Connection { get; set; }
  59. /// <summary>
  60. /// Gets or sets the field terminator.
  61. /// </summary>
  62. /// <value>The field terminator.</value>
  63. public string FieldTerminator { get; set; }
  64. /// <summary>
  65. /// Gets or sets the line terminator.
  66. /// </summary>
  67. /// <value>The line terminator.</value>
  68. public string LineTerminator { get; set; }
  69. /// <summary>
  70. /// Gets or sets the name of the table.
  71. /// </summary>
  72. /// <value>The name of the table.</value>
  73. public string TableName { get; set; }
  74. /// <summary>
  75. /// Gets or sets the character set.
  76. /// </summary>
  77. /// <value>The character set.</value>
  78. public string CharacterSet { get; set; }
  79. /// <summary>
  80. /// Gets or sets the name of the file.
  81. /// </summary>
  82. /// <value>The name of the file.</value>
  83. public string FileName { get; set; }
  84. /// <summary>
  85. /// Gets or sets the timeout.
  86. /// </summary>
  87. /// <value>The timeout.</value>
  88. public int Timeout { get; set; }
  89. /// <summary>
  90. /// Gets or sets a value indicating whether the file name that is to be loaded
  91. /// is local to the client or not. The default value is false.
  92. /// </summary>
  93. /// <value><c>true</c> if local; otherwise, <c>false</c>.</value>
  94. public bool Local { get; set; }
  95. /// <summary>
  96. /// Gets or sets the number of lines to skip.
  97. /// </summary>
  98. /// <value>The number of lines to skip.</value>
  99. public int NumberOfLinesToSkip { get; set; }
  100. /// <summary>
  101. /// Gets or sets the line prefix.
  102. /// </summary>
  103. /// <value>The line prefix.</value>
  104. public string LinePrefix { get; set; }
  105. /// <summary>
  106. /// Gets or sets the field quotation character.
  107. /// </summary>
  108. /// <value>The field quotation character.</value>
  109. public char FieldQuotationCharacter { get; set; }
  110. /// <summary>
  111. /// Gets or sets a value indicating whether [field quotation optional].
  112. /// </summary>
  113. /// <value>
  114. /// <c>true</c> if [field quotation optional]; otherwise, <c>false</c>.
  115. /// </value>
  116. public bool FieldQuotationOptional { get; set; }
  117. /// <summary>
  118. /// Gets or sets the escape character.
  119. /// </summary>
  120. /// <value>The escape character.</value>
  121. public char EscapeCharacter { get; set; }
  122. /// <summary>
  123. /// Gets or sets the conflict option.
  124. /// </summary>
  125. /// <value>The conflict option.</value>
  126. public MySqlBulkLoaderConflictOption ConflictOption { get; set; }
  127. /// <summary>
  128. /// Gets or sets the priority.
  129. /// </summary>
  130. /// <value>The priority.</value>
  131. public MySqlBulkLoaderPriority Priority { get; set; }
  132. /// <summary>
  133. /// Gets the columns.
  134. /// </summary>
  135. /// <value>The columns.</value>
  136. public List<string> Columns { get; }
  137. /// <summary>
  138. /// Gets the expressions.
  139. /// </summary>
  140. /// <value>The expressions.</value>
  141. public List<string> Expressions { get; }
  142. #endregion
  143. /// <summary>
  144. /// Executes the load operation.
  145. /// </summary>
  146. /// <returns>The number of rows inserted.</returns>
  147. public int Load()
  148. {
  149. bool openedConnection = false;
  150. if (Connection == null)
  151. throw new InvalidOperationException(Resources.ConnectionNotSet);
  152. // next we open up the connetion if it is not already open
  153. if (Connection.State != ConnectionState.Open)
  154. {
  155. openedConnection = true;
  156. Connection.Open();
  157. }
  158. try
  159. {
  160. string sql = BuildSqlCommand();
  161. MySqlCommand cmd = new MySqlCommand(sql, Connection) { CommandTimeout = Timeout };
  162. return cmd.ExecuteNonQuery();
  163. }
  164. finally
  165. {
  166. if (openedConnection)
  167. Connection.Close();
  168. }
  169. }
  170. #region Async
  171. /// <summary>
  172. /// Asynchronous version of the load operation.
  173. /// </summary>
  174. /// <returns>The number of rows inserted.</returns>
  175. public Task<int> LoadAsync()
  176. {
  177. return LoadAsync(CancellationToken.None);
  178. }
  179. /// <summary>
  180. /// Executes the load operation asynchronously while the cancellation isn't requested.
  181. /// </summary>
  182. /// <param name="cancellationToken">The cancellation token.</param>
  183. /// <returns>The number of rows inserted.</returns>
  184. public Task<int> LoadAsync(CancellationToken cancellationToken)
  185. {
  186. var result = new TaskCompletionSource<int>();
  187. if (cancellationToken == CancellationToken.None || !cancellationToken.IsCancellationRequested)
  188. {
  189. try
  190. {
  191. int loadResult = Load();
  192. result.SetResult(loadResult);
  193. }
  194. catch (Exception ex)
  195. {
  196. result.SetException(ex);
  197. }
  198. }
  199. else
  200. {
  201. result.SetCanceled();
  202. }
  203. return result.Task;
  204. }
  205. #endregion
  206. private string BuildSqlCommand()
  207. {
  208. StringBuilder sql = new StringBuilder("LOAD DATA ");
  209. if (Priority == MySqlBulkLoaderPriority.Low)
  210. sql.Append("LOW_PRIORITY ");
  211. else if (Priority == MySqlBulkLoaderPriority.Concurrent)
  212. sql.Append("CONCURRENT ");
  213. if (Local)
  214. sql.Append("LOCAL ");
  215. sql.Append("INFILE ");
  216. if (Path.DirectorySeparatorChar == '\\')
  217. sql.AppendFormat("'{0}' ", FileName.Replace(@"\", @"\\"));
  218. else
  219. sql.AppendFormat("'{0}' ", FileName);
  220. if (ConflictOption == MySqlBulkLoaderConflictOption.Ignore)
  221. sql.Append("IGNORE ");
  222. else if (ConflictOption == MySqlBulkLoaderConflictOption.Replace)
  223. sql.Append("REPLACE ");
  224. sql.AppendFormat("INTO TABLE {0} ", TableName);
  225. if (CharacterSet != null)
  226. sql.AppendFormat("CHARACTER SET {0} ", CharacterSet);
  227. StringBuilder optionSql = new StringBuilder(String.Empty);
  228. if (FieldTerminator != defaultFieldTerminator)
  229. optionSql.AppendFormat("TERMINATED BY '{0}' ", FieldTerminator);
  230. if (FieldQuotationCharacter != Char.MinValue)
  231. optionSql.AppendFormat("{0} ENCLOSED BY '{1}' ",
  232. FieldQuotationOptional ? "OPTIONALLY" : "", FieldQuotationCharacter);
  233. if (EscapeCharacter != defaultEscapeCharacter &&
  234. EscapeCharacter != Char.MinValue)
  235. optionSql.AppendFormat("ESCAPED BY '{0}' ", EscapeCharacter);
  236. if (optionSql.Length > 0)
  237. sql.AppendFormat("FIELDS {0}", optionSql.ToString());
  238. optionSql = new StringBuilder(String.Empty);
  239. if (!string.IsNullOrEmpty(LinePrefix))
  240. optionSql.AppendFormat("STARTING BY '{0}' ", LinePrefix);
  241. if (LineTerminator != defaultLineTerminator)
  242. optionSql.AppendFormat("TERMINATED BY '{0}' ", LineTerminator);
  243. if (optionSql.Length > 0)
  244. sql.AppendFormat("LINES {0}", optionSql.ToString());
  245. if (NumberOfLinesToSkip > 0)
  246. sql.AppendFormat("IGNORE {0} LINES ", NumberOfLinesToSkip);
  247. if (Columns.Count > 0)
  248. {
  249. sql.Append("(");
  250. sql.Append(Columns[0]);
  251. for (int i = 1; i < Columns.Count; i++)
  252. sql.AppendFormat(",{0}", Columns[i]);
  253. sql.Append(") ");
  254. }
  255. if (Expressions.Count > 0)
  256. {
  257. sql.Append("SET ");
  258. sql.Append(Expressions[0]);
  259. for (int i = 1; i < Expressions.Count; i++)
  260. sql.AppendFormat(",{0}", Expressions[i]);
  261. }
  262. return sql.ToString();
  263. }
  264. }
  265. /// <summary>
  266. /// Represents the priority set for bulk loading operations.
  267. /// </summary>
  268. internal enum MySqlBulkLoaderPriority
  269. {
  270. /// <summary>
  271. /// This is the default and indicates normal priority
  272. /// </summary>
  273. None,
  274. /// <summary>
  275. /// Low priority will cause the load operation to wait until all readers of the table
  276. /// have finished. This only affects storage engines that use only table-level locking
  277. /// such as MyISAM, Memory, and Merge.
  278. /// </summary>
  279. Low,
  280. /// <summary>
  281. /// Concurrent priority is only relevant for MyISAM tables and signals that if the table
  282. /// has no free blocks in the middle that other readers can retrieve data from the table
  283. /// while the load operation is happening.
  284. /// </summary>
  285. Concurrent
  286. }
  287. /// <summary>
  288. /// Represents the behavior when conflicts arise during bulk loading operations.
  289. /// </summary>
  290. internal enum MySqlBulkLoaderConflictOption
  291. {
  292. /// <summary>
  293. /// This is the default and indicates normal operation. In the event of a LOCAL load, this
  294. /// is the same as ignore. When the data file is on the server, then a key conflict will
  295. /// cause an error to be thrown and the rest of the data file ignored.
  296. /// </summary>
  297. None,
  298. /// <summary>
  299. /// Replace column values when a key conflict occurs.
  300. /// </summary>
  301. Replace,
  302. /// <summary>
  303. /// Ignore any rows where the primary key conflicts.
  304. /// </summary>
  305. Ignore
  306. }
  307. }
  308. #endif