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.

435 lines
14 KiB

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