Skip to content
This repository has been archived by the owner on Dec 7, 2022. It is now read-only.

Commit

Permalink
Merge branch 'working'
Browse files Browse the repository at this point in the history
Conflicts:
	TeslaSQL/Agents/Notifier.cs
	TeslaSQL/DataCopy/MySQLToMSSQLDataCopy.cs
	TeslaSQL/DataType.cs
	TeslaSQL/DataUtils/MSSQLDataUtils.cs
	TeslaSQL/DataUtils/MySQLDataUtils.cs
	TeslaSQL/TeslaSQL.csproj
  • Loading branch information
Clayton Pence committed Feb 3, 2014
2 parents e615d40 + 2828056 commit 0d94f28
Show file tree
Hide file tree
Showing 9 changed files with 529 additions and 16 deletions.
5 changes: 4 additions & 1 deletion TeslaSQL/Agents/Slave.cs
Expand Up @@ -139,9 +139,12 @@ public Slave(IDataUtils sourceDataUtils, IDataUtils destDataUtils, Logger logger
IList<ChangeTrackingBatch> batches = new List<ChangeTrackingBatch>();

DataRow lastBatch = sourceDataUtils.GetLastCTBatch(Config.RelayDB, AgentType.Slave, Config.Slave);
//apparently we shouldn't hit the code block below except for unit tests?
//in the db init we're supposed to write the first row, so it (in theory) shouldn't return null
if (lastBatch == null) {
ctb = new ChangeTrackingBatch(1, 0, 0, 0);
batches.Add(ctb);
logger.Log("Couldn't find any records for the last change tracking batch! Returning the default new CTB.", LogLevel.Warn);
return batches;
}

Expand Down Expand Up @@ -179,7 +182,7 @@ public Slave(IDataUtils sourceDataUtils, IDataUtils destDataUtils, Logger logger
/// <summary>
/// Runs a single change tracking batch
/// </summary>
/// <param name="CTID">Change tracking batch object to work on</param>
/// <param name="ctb">Change tracking batch object to work on</param>
private void RunSingleBatch(ChangeTrackingBatch ctb) {
Stopwatch sw;
logger.Log("Applying schema changes ", LogLevel.Info);
Expand Down
14 changes: 11 additions & 3 deletions TeslaSQL/Config.cs
Expand Up @@ -555,7 +555,7 @@ public class RefreshView {
public class TColumn : IEquatable<TColumn> {
public readonly string name;
public bool isPk;
public readonly DataType dataType;
public DataType dataType {get;set;}
public readonly bool isNullable;

public TColumn(string name, bool isPk, DataType dataType, bool isNullable) {
Expand All @@ -571,8 +571,16 @@ public class TColumn : IEquatable<TColumn> {
/// <summary>
/// Returns a string representation of the column for use in CREATE TABLE statements
/// </summary>
public string ToExpression() {
return string.Format("[{0}] {1} {2}", name, dataType.ToString(), isNullable ? "NULL" : "NOT NULL");
public string ToExpression(SqlFlavor flavor = SqlFlavor.MSSQL) {
switch (flavor)
{
case SqlFlavor.MSSQL:
return string.Format("[{0}] {1} {2}", name, dataType.ToString(), isNullable ? "NULL" : "NOT NULL");
case SqlFlavor.MySQL:
return string.Format("{0} {1} {2}", name, dataType.ToString(), isNullable ? "NULL" : "NOT NULL");
default:
throw new NotImplementedException("No defined ToExpression for sql flavor: " + flavor.ToString());
}
}

public bool Equals(TColumn other) {
Expand Down
17 changes: 15 additions & 2 deletions TeslaSQL/DataCopy/DataCopyFactory.cs
Expand Up @@ -14,11 +14,24 @@ public static class DataCopyFactory {
}
switch (sourceSqlFlavor) {
case SqlFlavor.MSSQL:
if (destSqlFlavor == SqlFlavor.MSSQL) {
if (destSqlFlavor == SqlFlavor.MSSQL)
{
return new MSSQLToMSSQLDataCopy((MSSQLDataUtils)sourceDataUtils, (MSSQLDataUtils)destDataUtils, logger);
} else if (destSqlFlavor == SqlFlavor.Netezza) {
}
else if (destSqlFlavor == SqlFlavor.Netezza)
{
return new MSSQLToNetezzaDataCopy((MSSQLDataUtils)sourceDataUtils, (NetezzaDataUtils)destDataUtils, logger, Config.Slave, Config.NetezzaUser, Config.NetezzaPrivateKeyPath);
}
else if (destSqlFlavor == SqlFlavor.MySQL)
{
return new MSSQLToMySQLDataCopy((MSSQLDataUtils)sourceDataUtils, (MySQLDataUtils)destDataUtils, logger);
}
break;
case SqlFlavor.MySQL:
if (destSqlFlavor == SqlFlavor.MSSQL)
{
return new MySQLToMSSQLDataCopy((MySQLDataUtils)sourceDataUtils, (MSSQLDataUtils)destDataUtils, logger);
}
break;
case SqlFlavor.MySQL:
if (destSqlFlavor == SqlFlavor.MSSQL)
Expand Down
207 changes: 207 additions & 0 deletions TeslaSQL/DataCopy/MSSQLToMySQLDataCopy.cs
@@ -0,0 +1,207 @@
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Text;
using System.Data.SqlClient;
using TeslaSQL.DataUtils;
using MySql.Data.MySqlClient;
using System.Diagnostics;
using System.Text.RegularExpressions;
using System.IO;

namespace TeslaSQL.DataCopy {
public class MSSQLToMySQLDataCopy : IDataCopy {

private MSSQLDataUtils sourceDataUtils;
private MySQLDataUtils destDataUtils;
private Logger logger;

public MSSQLToMySQLDataCopy(MSSQLDataUtils sourceDataUtils, MySQLDataUtils destDataUtils, Logger logger) {
this.sourceDataUtils = sourceDataUtils;
this.destDataUtils = destDataUtils;
this.logger = logger;
}

private static void CreateDirectoryIfNotExists(string directory)
{
DirectoryInfo dir = new DirectoryInfo(directory);
if (!dir.Exists)
{
dir.Create();
}
}

public void CopyTable(string sourceDB, string sourceTableName, string schema, string destDB, int timeout, string destTableName = null, string originalTableName = null) {
//by default the dest table will have the same name as the source table
destTableName = (destTableName == null) ? sourceTableName : destTableName;
originalTableName = originalTableName ?? sourceTableName;

//drop table at destination and create from source schema
CopyTableDefinition(sourceDB, sourceTableName, schema, destDB, destTableName, originalTableName);

var cols = GetColumns(sourceDB, sourceTableName, schema, originalTableName);
var bcpSelect = string.Format("SELECT {0} FROM {1}..{2};",
string.Join(",", cols.Select(col => col.ColExpression())),
sourceDB, sourceTableName);
if (bcpSelect.Length > 3800)
{
//BCP commands fail if their text length is over 4000 characters, and we need some padding
//drop view CTVWtablename if exists
//create view CTVWtablename AS $bcpSelect
string viewName = "CTVW" + sourceTableName;
sourceDataUtils.RecreateView(sourceDB, viewName, bcpSelect);
bcpSelect = string.Format("SELECT * FROM {0}..{1}", sourceDB, viewName);
}
string directory = Config.BcpPath.TrimEnd('\\') + @"\" + sourceDB.ToLower();
CreateDirectoryIfNotExists(directory);
string password = new cTripleDes().Decrypt(Config.RelayPassword);
var bcpArgs = string.Format(@"""{0}"" queryout {1}\{2}.txt -c -S{3} -U {4} -P {5} -t""|"" -r""&_+-!/=/=""",
bcpSelect,
directory,
destTableName,
Config.RelayServer,
Config.RelayUser,
password
);
logger.Log("BCP command: bcp " + bcpArgs.Replace(password, "********"), LogLevel.Trace);
var outputBuilder = new StringBuilder();
var errorBuilder = new StringBuilder();
var bcp = new Process();
bcp.StartInfo.FileName = "bcp";
bcp.StartInfo.Arguments = bcpArgs;
bcp.StartInfo.UseShellExecute = false;
bcp.StartInfo.RedirectStandardError = true;
bcp.StartInfo.RedirectStandardOutput = true;
bcp.OutputDataReceived += delegate(object sender, DataReceivedEventArgs e)
{
lock (outputBuilder)
{
outputBuilder.AppendLine(e.Data);
}
};
bcp.ErrorDataReceived += delegate(object sender, DataReceivedEventArgs e)
{
lock (errorBuilder)
{
errorBuilder.AppendLine(e.Data);
}
};
bcp.Start();
bcp.BeginOutputReadLine();
bcp.BeginErrorReadLine();
bool status = bcp.WaitForExit(Config.DataCopyTimeout * 1000);
if (!status)
{
bcp.Kill();
throw new Exception("BCP timed out for table " + sourceTableName);
}
if (bcp.ExitCode != 0)
{
string err = outputBuilder + "\r\n" + errorBuilder;
logger.Log(err, LogLevel.Critical);
throw new Exception("BCP error: " + err);
}
logger.Log("BCP successful for " + sourceTableName, LogLevel.Trace);
string filename = sourceDB.ToLower() + "/" + destTableName + ".txt";
destDataUtils.BulkCopy(filename, destDB, destTableName, 60 * 10, cols);

}


/// <summary>
/// Copies the schema of a table from one server to another, dropping it first if it exists at the destination.
/// </summary>
/// <param name="sourceDB">Source database name</param>
/// <param name="sourceTableName">Table name</param>
/// <param name="schema">Table's schema</param>
/// <param name="destDB">Destination database name</param>
public void CopyTableDefinition(string sourceDB, string sourceTableName, string schema, string destDB, string destTableName, string originalTableName = null) {
//script out the table at the source
string createScript = sourceDataUtils.ScriptTable(sourceDB, sourceTableName, schema, originalTableName, SqlFlavor.MySQL);
createScript = createScript.Replace(sourceTableName, destTableName);
MySqlCommand cmd = new MySqlCommand(createScript);

//drop it if it exists at the destination
destDataUtils.DropTableIfExists(destDB, destTableName, schema);

//create it at the destination
destDataUtils.MySqlNonQuery(destDB, cmd);
}


public struct Col
{
public string name;
public string typeName;
public DataType dataType;

public Col(string name, string typeName, DataType dataType)
{
this.name = name;
this.typeName = typeName;
this.dataType = dataType;
}
/// <summary>
/// Return an expression for use in BCP command
/// </summary>
public string ColExpression()
{
return name;
}
public override string ToString()
{
return name + " " + typeName;
}
}

private List<Col> GetColumns(string sourceDB, string sourceTableName, string schema, string originalTableName)
{
//get actual field list on the source table
var includeColumns = new List<string>() { "SYS_CHANGE_VERSION", "SYS_CHANGE_OPERATION" };
var columns = sourceDataUtils.GetFieldList(sourceDB, sourceTableName, schema, originalTableName, includeColumns);
//get the table config object
var table = Config.TableByName(originalTableName);

var cols = new List<Col>();
foreach (TColumn col in columns)
{
string typeName = col.dataType.BaseType;

ColumnModifier mod = null;
//see if there are any column modifiers which override our length defaults
ColumnModifier[] modifiers = table.ColumnModifiers;
if (modifiers != null)
{
IEnumerable<ColumnModifier> mods = modifiers.Where(c => ((c.columnName == col.name) && (c.type == "ShortenField")));
mod = mods.FirstOrDefault();
}

string modDataType = DataType.MapDataType(SqlFlavor.MSSQL, SqlFlavor.MySQL, typeName);
if (typeName != modDataType)
{
if (mod != null && Regex.IsMatch(modDataType, @".*\(\d+\)$"))
{
modDataType = Regex.Replace(modDataType, @"\d+", mod.length.ToString());
}
cols.Add(new Col(col.name, modDataType, col.dataType));
continue;
}

if (col.dataType.UsesMaxLength())
{
if (mod != null)
{
typeName += "(" + mod.length + ")";
}
}
else if (col.dataType.UsesPrecisionScale())
{
typeName += "(" + col.dataType.NumericPrecision + "," + col.dataType.NumericScale + ")";
}
cols.Add(new Col(col.name, typeName, col.dataType));
}
return cols;
}
}
}
5 changes: 5 additions & 0 deletions TeslaSQL/DataCopy/MySQLToMSSQLDataCopy.cs
Expand Up @@ -97,6 +97,11 @@ public void CopyTableDefinition(string sourceDB, string sourceTableName, string
case "nvarchar":
case "char":
case "nchar":
<<<<<<< HEAD
=======
case "text":
case "ntext":
>>>>>>> working
script.Append('(');
script.Append((column.dataType.CharacterMaximumLength != null && column.dataType.CharacterMaximumLength < 8000) ? column.dataType.CharacterMaximumLength.ToString() : "MAX");
script.Append(')');
Expand Down
26 changes: 22 additions & 4 deletions TeslaSQL/DataType.cs
Expand Up @@ -16,6 +16,7 @@ public class DataType {
public long? CharacterMaximumLength { get; private set; }
public int? NumericPrecision { get; private set; }
public int? NumericScale { get; private set; }
public bool? Signed { get; private set; }

public static void LoadDataMappingsFromFile(string filePath) {
string s;
Expand Down Expand Up @@ -58,11 +59,12 @@ public class DataType {
}


public DataType(string baseType, long? characterMaximumLength = null, int? numericPrecision = null, int? numericScale = null) {
public DataType(string baseType, long? characterMaximumLength = null, int? numericPrecision = null, int? numericScale = null, bool? signed = null) {
this.BaseType = baseType;
this.CharacterMaximumLength = characterMaximumLength;
this.NumericPrecision = numericPrecision;
this.NumericScale = numericScale;
this.Signed = signed;
}

/// <summary>
Expand Down Expand Up @@ -94,7 +96,7 @@ public class DataType {
/// <returns>String expression representing the data type</returns>
public override string ToString() {
var typesUsingMaxLen = new string[6] { "varchar", "nvarchar", "char", "nchar", "varbinary", "binary" };
var typesUsingScale = new string[2] { "numeric", "decimal" };
var typesUsingScale = new string[4] { "numeric", "decimal", "real", "float" };

string suffix = "";
if (typesUsingMaxLen.Contains(BaseType) && CharacterMaximumLength != null) {
Expand Down Expand Up @@ -129,7 +131,10 @@ public class DataType {
/// </summary>
public bool UsesPrecisionScale() {
return BaseType.ToLower().Contains("decimal")
|| BaseType.ToLower().Contains("numeric");
|| BaseType.ToLower().Contains("numeric")
|| BaseType.ToLower().Contains("money")
|| BaseType.ToLower().Contains("real")
|| BaseType.ToLower().Contains("float");
}

/// <summary>
Expand All @@ -139,6 +144,19 @@ public class DataType {
/// <returns>A TeslaSQL.DataType object</returns>
public static DataType ParseDataType(DataRow row) {
string dataType = row.Field<string>("DATA_TYPE");
bool? signed;
if (dataType.ToUpper().Contains("INT"))
{
signed = !dataType.ToUpper().Contains("UNSIGNED");
}
else if (dataType.ToUpper().Contains("BIT"))
{
signed = false;
}
else
{
signed = null;
}
Nullable<long> characterMaximumLength;
if (row["CHARACTER_MAXIMUM_LENGTH"].GetType() == typeof(System.DBNull))
{
Expand All @@ -156,7 +174,7 @@ public class DataType {
var numericPrecision = precision;
Nullable<int> numericScale = row["NUMERIC_SCALE"].GetType() == typeof(UInt64) ? Convert.ToByte(row.Field<UInt64>("NUMERIC_SCALE")) : row.Field<int?>("NUMERIC_SCALE");
return new DataType(
dataType, characterMaximumLength, numericPrecision, numericScale
dataType, characterMaximumLength, numericPrecision, numericScale, signed
);
}
}
Expand Down

0 comments on commit 0d94f28

Please sign in to comment.