// Copyright ?2006, 2019, Oracle and/or its affiliates. All rights reserved. // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License, version 2.0, as // published by the Free Software Foundation. // // This program is also distributed with certain software (including // but not limited to OpenSSL) that is licensed under separate terms, // as designated in a particular file or component or in included license // documentation. The authors of MySQL hereby grant you an // additional permission to link the program and your derivative works // with the separately licensed software that they have included with // MySQL. // // Without limiting anything contained in the foregoing, this file, // which is part of MySQL Connector/NET, is also subject to the // Universal FOSS Exception, version 1.0, a copy of which can be found at // http://oss.oracle.com/licenses/universal-foss-exception. // // This program is distributed in the hope that it will be useful, but // WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. // See the GNU General Public License, version 2.0, for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software Foundation, Inc., // 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA using System; using System.Collections.Generic; using System.Data; using System.Text; using System.Threading.Tasks; using System.Threading; using System.IO; using Sog.Properties; namespace MySql.Data.MySqlClient { /// /// Allows importing large amounts of data into a database with bulk loading. /// public class MySqlBulkLoader { // constant values private const string defaultFieldTerminator = "\t"; private const string defaultLineTerminator = "\n"; private const char defaultEscapeCharacter = '\\'; // fields public MySqlBulkLoader(MySqlConnection connection) { this.Connection = connection; this.Local = false; this.FieldTerminator = defaultFieldTerminator; this.LineTerminator = defaultLineTerminator; this.FieldQuotationCharacter = Char.MinValue; this.ConflictOption = MySqlBulkLoaderConflictOption.None; this.Columns = new List(); this.Expressions = new List(); } #region Properties /// /// Gets or sets the connection. /// /// The connection. public MySqlConnection Connection { get; set; } /// /// Gets or sets the field terminator. /// /// The field terminator. public string FieldTerminator { get; set; } /// /// Gets or sets the line terminator. /// /// The line terminator. public string LineTerminator { get; set; } /// /// Gets or sets the name of the table. /// /// The name of the table. public string TableName { get; set; } /// /// Gets or sets the character set. /// /// The character set. public string CharacterSet { get; set; } /// /// Gets or sets the name of the file. /// /// The name of the file. public string FileName { get; set; } /// /// Gets or sets the timeout. /// /// The timeout. public int Timeout { get; set; } /// /// Gets or sets a value indicating whether the file name that is to be loaded /// is local to the client or not. The default value is false. /// /// true if local; otherwise, false. public bool Local { get; set; } /// /// Gets or sets the number of lines to skip. /// /// The number of lines to skip. public int NumberOfLinesToSkip { get; set; } /// /// Gets or sets the line prefix. /// /// The line prefix. public string LinePrefix { get; set; } /// /// Gets or sets the field quotation character. /// /// The field quotation character. public char FieldQuotationCharacter { get; set; } /// /// Gets or sets a value indicating whether [field quotation optional]. /// /// /// true if [field quotation optional]; otherwise, false. /// public bool FieldQuotationOptional { get; set; } /// /// Gets or sets the escape character. /// /// The escape character. public char EscapeCharacter { get; set; } /// /// Gets or sets the conflict option. /// /// The conflict option. public MySqlBulkLoaderConflictOption ConflictOption { get; set; } /// /// Gets or sets the priority. /// /// The priority. public MySqlBulkLoaderPriority Priority { get; set; } /// /// Gets the columns. /// /// The columns. public List Columns { get; } /// /// Gets the expressions. /// /// The expressions. public List Expressions { get; } #endregion /// /// Executes the load operation. /// /// The number of rows inserted. public int Load() { bool openedConnection = false; if (this.Connection == null) { throw new InvalidOperationException(Resources.ConnectionNotSet); } // next we open up the connetion if it is not already open if (this.Connection.State != ConnectionState.Open) { openedConnection = true; this.Connection.Open(); } try { string sql = this.BuildSqlCommand(); MySqlCommand cmd = new MySqlCommand(sql, this.Connection) { CommandTimeout = this.Timeout }; return cmd.ExecuteNonQuery(); } finally { if (openedConnection) { this.Connection.Close(); } } } #region Async /// /// Asynchronous version of the load operation. /// /// The number of rows inserted. public Task LoadAsync() { return this.LoadAsync(CancellationToken.None); } /// /// Executes the load operation asynchronously while the cancellation isn't requested. /// /// The cancellation token. /// The number of rows inserted. public Task LoadAsync(CancellationToken cancellationToken) { var result = new TaskCompletionSource(); if (cancellationToken == CancellationToken.None || !cancellationToken.IsCancellationRequested) { try { int loadResult = this.Load(); result.SetResult(loadResult); } catch (Exception ex) { result.SetException(ex); } } else { result.SetCanceled(); } return result.Task; } #endregion private string BuildSqlCommand() { StringBuilder sql = new StringBuilder("LOAD DATA "); if (this.Priority == MySqlBulkLoaderPriority.Low) { sql.Append("LOW_PRIORITY "); } else if (this.Priority == MySqlBulkLoaderPriority.Concurrent) { sql.Append("CONCURRENT "); } if (this.Local) { sql.Append("LOCAL "); } sql.Append("INFILE "); if (Path.DirectorySeparatorChar == '\\') { sql.AppendFormat("'{0}' ", this.FileName.Replace(@"\", @"\\")); } else { sql.AppendFormat("'{0}' ", this.FileName); } if (this.ConflictOption == MySqlBulkLoaderConflictOption.Ignore) { sql.Append("IGNORE "); } else if (this.ConflictOption == MySqlBulkLoaderConflictOption.Replace) { sql.Append("REPLACE "); } sql.AppendFormat("INTO TABLE {0} ", this.TableName); if (this.CharacterSet != null) { sql.AppendFormat("CHARACTER SET {0} ", this.CharacterSet); } StringBuilder optionSql = new StringBuilder(String.Empty); if (this.FieldTerminator != defaultFieldTerminator) { optionSql.AppendFormat("TERMINATED BY '{0}' ", this.FieldTerminator); } if (this.FieldQuotationCharacter != Char.MinValue) { optionSql.AppendFormat( "{0} ENCLOSED BY '{1}' ", this.FieldQuotationOptional ? "OPTIONALLY" : "", this.FieldQuotationCharacter); } if (this.EscapeCharacter != defaultEscapeCharacter && this.EscapeCharacter != Char.MinValue) { optionSql.AppendFormat("ESCAPED BY '{0}' ", this.EscapeCharacter); } if (optionSql.Length > 0) { sql.AppendFormat("FIELDS {0}", optionSql.ToString()); } optionSql = new StringBuilder(String.Empty); if (!string.IsNullOrEmpty(this.LinePrefix)) { optionSql.AppendFormat("STARTING BY '{0}' ", this.LinePrefix); } if (this.LineTerminator != defaultLineTerminator) { optionSql.AppendFormat("TERMINATED BY '{0}' ", this.LineTerminator); } if (optionSql.Length > 0) { sql.AppendFormat("LINES {0}", optionSql.ToString()); } if (this.NumberOfLinesToSkip > 0) { sql.AppendFormat("IGNORE {0} LINES ", this.NumberOfLinesToSkip); } if (this.Columns.Count > 0) { sql.Append("("); sql.Append(this.Columns[0]); for (int i = 1; i < this.Columns.Count; i++) { sql.AppendFormat(",{0}", this.Columns[i]); } sql.Append(") "); } if (this.Expressions.Count > 0) { sql.Append("SET "); sql.Append(this.Expressions[0]); for (int i = 1; i < this.Expressions.Count; i++) { sql.AppendFormat(",{0}", this.Expressions[i]); } } return sql.ToString(); } } /// /// Represents the priority set for bulk loading operations. /// public enum MySqlBulkLoaderPriority { /// /// This is the default and indicates normal priority /// None, /// /// Low priority will cause the load operation to wait until all readers of the table /// have finished. This only affects storage engines that use only table-level locking /// such as MyISAM, Memory, and Merge. /// Low, /// /// Concurrent priority is only relevant for MyISAM tables and signals that if the table /// has no free blocks in the middle that other readers can retrieve data from the table /// while the load operation is happening. /// Concurrent } /// /// Represents the behavior when conflicts arise during bulk loading operations. /// public enum MySqlBulkLoaderConflictOption { /// /// This is the default and indicates normal operation. In the event of a LOCAL load, this /// is the same as ignore. When the data file is on the server, then a key conflict will /// cause an error to be thrown and the rest of the data file ignored. /// None, /// /// Replace column values when a key conflict occurs. /// Replace, /// /// Ignore any rows where the primary key conflicts. /// Ignore } }