Skip to content

Commit dcb8c3f

Browse files
committed
Added some basic extensions to support generating joining tables + FK's.
Added support for Encoding Guid value as a string to the transformation provider (to deal with Oracle requiring a different format of Guid to other databases). Added small utility for the transformation provider, with help methods for generating "standard" foreign key names, formatting a table name to include a schema and adjusting a name to a specific size with some (currently pretty terrible/domain specic) rules.
1 parent 2d560a4 commit dcb8c3f

11 files changed

+364
-41
lines changed

src/Migrator.Framework/ITransformationProvider.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -496,5 +496,12 @@ void GenerateForeignKey(string foreignTable, string foreignColumn, string primar
496496
/// <param name="name"></param>
497497
/// <returns></returns>
498498
string QuoteTableNameIfRequired(string name);
499+
500+
/// <summary>
501+
/// Encodes a guid value as a string, suitable for inclusion in sql statement
502+
/// </summary>
503+
/// <param name="guid"></param>
504+
/// <returns></returns>
505+
string Encode(Guid guid);
499506
}
500507
}
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
using System.Collections.Generic;
2+
using System.Data;
3+
using System.Linq;
4+
using System.Text;
5+
using Migrator.Framework.Support;
6+
7+
namespace Migrator.Framework
8+
{
9+
/// <summary>
10+
/// A set of extension methods for the transformation provider to make it easier to
11+
/// build many-to-many joining tables (takes care of adding the joining table and foreign
12+
/// key constraints as necessary.
13+
/// <remarks>This functionality was useful when bootstrapping a number of projects a few years ago, but
14+
/// now that most changes are brown-field I'm thinking of removing these methods as it's easier to maintain
15+
/// code that creates the tables etc. directly within migration.</remarks>
16+
/// </summary>
17+
public static class JoiningTableTransformationProviderExtensions
18+
{
19+
public static ITransformationProvider AddManyToManyJoiningTable(this ITransformationProvider database, string schema, string lhsTableName, string lhsKey, string rhsTableName, string rhsKey)
20+
{
21+
string joiningTable = GetNameOfJoiningTable(lhsTableName, rhsTableName);
22+
23+
return AddManyToManyJoiningTable(database, schema, lhsTableName, lhsKey, rhsTableName, rhsKey, joiningTable);
24+
}
25+
26+
static string GetNameOfJoiningTable(string lhsTableName, string rhsTableName)
27+
{
28+
return (Inflector.Singularize(lhsTableName) ?? lhsTableName) + (Inflector.Pluralize(rhsTableName) ?? rhsTableName);
29+
}
30+
31+
public static ITransformationProvider AddManyToManyJoiningTable(this ITransformationProvider database, string schema, string lhsTableName, string lhsKey, string rhsTableName, string rhsKey,
32+
string joiningTableName)
33+
{
34+
string joiningTableWithSchema = TransformationProviderUtility.FormatTableName(schema, joiningTableName);
35+
36+
string joinLhsKey = Inflector.Singularize(lhsTableName) + "Id";
37+
string joinRhsKey = Inflector.Singularize(rhsTableName) + "Id";
38+
39+
database.AddTable(joiningTableWithSchema,
40+
new Column(joinLhsKey, DbType.Guid, ColumnProperty.NotNull),
41+
new Column(joinRhsKey, DbType.Guid, ColumnProperty.NotNull));
42+
43+
string pkName = "PK_" + joiningTableName;
44+
45+
pkName = ShortenKeyNameToBeSuitableForOracle(pkName);
46+
47+
database.AddPrimaryKey(pkName, joiningTableWithSchema, joinLhsKey, joinRhsKey);
48+
49+
string lhsTableNameWithSchema = TransformationProviderUtility.FormatTableName(schema, lhsTableName);
50+
string rhsTableNameWithSchema = TransformationProviderUtility.FormatTableName(schema, rhsTableName);
51+
52+
string lhsFkName = TransformationProviderUtility.CreateForeignKeyName(lhsTableName, joiningTableName);
53+
database.AddForeignKey(lhsFkName, joiningTableWithSchema, joinLhsKey, lhsTableNameWithSchema, lhsKey, ForeignKeyConstraint.NoAction);
54+
55+
string rhsFkName = TransformationProviderUtility.CreateForeignKeyName(rhsTableName, joiningTableName);
56+
database.AddForeignKey(rhsFkName, joiningTableWithSchema, joinRhsKey, rhsTableNameWithSchema, rhsKey, ForeignKeyConstraint.NoAction);
57+
58+
return database;
59+
}
60+
61+
static string ShortenKeyNameToBeSuitableForOracle(string pkName)
62+
{
63+
return TransformationProviderUtility.AdjustNameToSize(pkName, TransformationProviderUtility.MaxLengthForForeignKeyInOracle);
64+
}
65+
66+
public static ITransformationProvider RemoveManyToManyJoiningTable(this ITransformationProvider database, string schema, string lhsTableName, string rhsTableName)
67+
{
68+
string joiningTable = GetNameOfJoiningTable(lhsTableName, rhsTableName);
69+
return RemoveManyToManyJoiningTable(database, schema, lhsTableName, rhsTableName, joiningTable);
70+
}
71+
72+
public static ITransformationProvider RemoveManyToManyJoiningTable(this ITransformationProvider database, string schema, string lhsTableName, string rhsTableName, string joiningTableName)
73+
{
74+
string joiningTableNameWithSchema = TransformationProviderUtility.FormatTableName(schema, joiningTableName);
75+
string lhsFkName = TransformationProviderUtility.CreateForeignKeyName(lhsTableName, joiningTableName);
76+
string rhsFkName = TransformationProviderUtility.CreateForeignKeyName(rhsTableName, joiningTableName);
77+
78+
database.RemoveForeignKey(joiningTableNameWithSchema, lhsFkName);
79+
database.RemoveForeignKey(joiningTableNameWithSchema, rhsFkName);
80+
database.RemoveTable(joiningTableNameWithSchema);
81+
82+
return database;
83+
}
84+
}
85+
86+
}

src/Migrator.Framework/Maximums.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
6+
namespace Migrator.Framework
7+
{
8+
public static class Maximums
9+
{
10+
public const int NTextLength = 1073741823;
11+
}
12+
}

src/Migrator.Framework/Migrator.Framework-vs2010.csproj

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,10 +54,15 @@
5454
<CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet>
5555
</PropertyGroup>
5656
<ItemGroup>
57+
<Reference Include="log4net">
58+
<HintPath>..\..\lib\log4net.dll</HintPath>
59+
</Reference>
5760
<Reference Include="System" />
5861
<Reference Include="System.Data" />
5962
</ItemGroup>
6063
<ItemGroup>
64+
<Compile Include="Maximums.cs" />
65+
<Compile Include="JoiningTableTransformationProviderExtensions.cs" />
6166
<Compile Include="DataRecordExtensions.cs" />
6267
<Compile Include="Column.cs" />
6368
<Compile Include="ColumnProperty.cs" />
@@ -87,6 +92,8 @@
8792
<Compile Include="SchemaBuilder\RenameTableExpression.cs" />
8893
<Compile Include="SchemaBuilder\SchemaBuilder.cs" />
8994
<Compile Include="StringUtils.cs" />
95+
<Compile Include="Support\Inflector.cs" />
96+
<Compile Include="Support\TransformationProviderUtility.cs" />
9097
</ItemGroup>
9198
<ItemGroup>
9299
<None Include="MigratorDotNet.snk" />
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
using System.Collections;
2+
using System.Text.RegularExpressions;
3+
4+
namespace Migrator.Framework.Support
5+
{
6+
public class Inflector
7+
{
8+
private static readonly ArrayList plurals = new ArrayList();
9+
private static readonly ArrayList singulars = new ArrayList();
10+
private static readonly ArrayList uncountables = new ArrayList();
11+
12+
private Inflector()
13+
{
14+
}
15+
16+
static Inflector()
17+
{
18+
AddPlural("$", "s");
19+
AddPlural("s$", "s");
20+
AddPlural("(ax|test)is$", "$1es");
21+
AddPlural("(octop|vir)us$", "$1i");
22+
AddPlural("(alias|status)$", "$1es");
23+
AddPlural("(bu)s$", "$1ses");
24+
AddPlural("(buffal|tomat)o$", "$1oes");
25+
AddPlural("([ti])um$", "$1a");
26+
AddPlural("sis$", "ses");
27+
AddPlural("(?:([^f])fe|([lr])f)$", "$1$2ves");
28+
AddPlural("(hive)$", "$1s");
29+
AddPlural("([^aeiouy]|qu)y$", "$1ies");
30+
AddPlural("(x|ch|ss|sh)$", "$1es");
31+
AddPlural("(matr|vert|ind)ix|ex$", "$1ices");
32+
AddPlural("([m|l])ouse$", "$1ice");
33+
AddPlural("^(ox)$", "$1en");
34+
AddPlural("(quiz)$", "$1zes");
35+
AddSingular("s$", "");
36+
AddSingular("(n)ews$", "$1ews");
37+
AddSingular("([ti])a$", "$1um");
38+
AddSingular("((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$", "$1$2sis");
39+
AddSingular("(^analy)ses$", "$1sis");
40+
AddSingular("([^f])ves$", "$1fe");
41+
AddSingular("(hive)s$", "$1");
42+
AddSingular("(tive)s$", "$1");
43+
AddSingular("([lr])ves$", "$1f");
44+
AddSingular("([^aeiouy]|qu)ies$", "$1y");
45+
AddSingular("(s)eries$", "$1eries");
46+
AddSingular("(m)ovies$", "$1ovie");
47+
AddSingular("(x|ch|ss|sh)es$", "$1");
48+
AddSingular("([m|l])ice$", "$1ouse");
49+
AddSingular("(bus)es$", "$1");
50+
AddSingular("(o)es$", "$1");
51+
AddSingular("(shoe)s$", "$1");
52+
AddSingular("(cris|ax|test)es$", "$1is");
53+
AddSingular("(octop|vir)i$", "$1us");
54+
AddSingular("(alias|status)es$", "$1");
55+
AddSingular("^(ox)en", "$1");
56+
AddSingular("(vert|ind)ices$", "$1ex");
57+
AddSingular("(matr)ices$", "$1ix");
58+
AddSingular("(quiz)zes$", "$1");
59+
AddIrregular("person", "people");
60+
AddIrregular("man", "men");
61+
AddIrregular("child", "children");
62+
AddIrregular("sex", "sexes");
63+
AddIrregular("move", "moves");
64+
AddUncountable("equipment");
65+
AddUncountable("information");
66+
AddUncountable("rice");
67+
AddUncountable("money");
68+
AddUncountable("species");
69+
AddUncountable("series");
70+
AddUncountable("fish");
71+
AddUncountable("sheep");
72+
}
73+
74+
private class Rule
75+
{
76+
private readonly Regex regex;
77+
private readonly string replacement;
78+
79+
public Rule(string pattern, string replacement)
80+
{
81+
regex = new Regex(pattern, RegexOptions.IgnoreCase);
82+
this.replacement = replacement;
83+
}
84+
85+
public string Apply(string word)
86+
{
87+
if (!regex.IsMatch(word))
88+
{
89+
return null;
90+
}
91+
92+
return regex.Replace(word, replacement);
93+
}
94+
}
95+
96+
/// <summary>
97+
/// Return the plural of a word.
98+
/// </summary>
99+
/// <param name="word">The singular form</param>
100+
/// <returns>The plural form of <paramref name="word"/></returns>
101+
public static string Pluralize(string word)
102+
{
103+
return ApplyRules(plurals, word);
104+
}
105+
106+
/// <summary>
107+
/// Return the singular of a word.
108+
/// </summary>
109+
/// <param name="word">The plural form</param>
110+
/// <returns>The singular form of <paramref name="word"/></returns>
111+
public static string Singularize(string word)
112+
{
113+
return ApplyRules(singulars, word);
114+
}
115+
116+
/// <summary>
117+
/// Capitalizes a word.
118+
/// </summary>
119+
/// <param name="word">The word to be capitalized.</param>
120+
/// <returns><paramref name="word"/> capitalized.</returns>
121+
public static string Capitalize(string word)
122+
{
123+
return word.Substring(0, 1).ToUpper() + word.Substring(1).ToLower();
124+
}
125+
126+
private static void AddIrregular(string singular, string plural)
127+
{
128+
AddPlural("(" + singular[0] + ")" + singular.Substring(1) + "$", "$1" + plural.Substring(1));
129+
AddSingular("(" + plural[0] + ")" + plural.Substring(1) + "$", "$1" + singular.Substring(1));
130+
}
131+
132+
private static void AddUncountable(string word)
133+
{
134+
uncountables.Add(word.ToLower());
135+
}
136+
137+
private static void AddPlural(string rule, string replacement)
138+
{
139+
plurals.Add(new Rule(rule, replacement));
140+
}
141+
142+
private static void AddSingular(string rule, string replacement)
143+
{
144+
singulars.Add(new Rule(rule, replacement));
145+
}
146+
147+
private static string ApplyRules(IList rules, string word)
148+
{
149+
string result = word;
150+
151+
if (!uncountables.Contains(word.ToLower()))
152+
{
153+
for (int i = rules.Count - 1; i >= 0; i--)
154+
{
155+
Rule rule = (Rule)rules[i];
156+
157+
if ((result = rule.Apply(word)) != null)
158+
{
159+
break;
160+
}
161+
}
162+
}
163+
164+
return result;
165+
}
166+
}
167+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
using log4net;
2+
3+
namespace Migrator.Framework.Support
4+
{
5+
public static class TransformationProviderUtility
6+
{
7+
public const int MaxLengthForForeignKeyInOracle = 30;
8+
static readonly ILog log = LogManager.GetLogger(typeof (TransformationProviderUtility));
9+
10+
public static string CreateForeignKeyName(string tableName, string foreignKeyTableName)
11+
{
12+
string fkName = string.Format("FK_{0}_{1}", tableName, foreignKeyTableName);
13+
14+
return AdjustNameToSize(fkName, MaxLengthForForeignKeyInOracle);
15+
}
16+
17+
public static string AdjustNameToSize(string name, int totalCharacters)
18+
{
19+
string adjustedName = name;
20+
21+
if (adjustedName.Length > totalCharacters)
22+
{
23+
if (adjustedName.Contains("Test"))
24+
{
25+
adjustedName = adjustedName.Replace("Test", "");
26+
}
27+
}
28+
29+
if (adjustedName.Length > totalCharacters) adjustedName = adjustedName.Substring(0, totalCharacters);
30+
31+
if (name != adjustedName)
32+
{
33+
log.WarnFormat("Name has been truncated from: {0} to: {1}", name, adjustedName);
34+
}
35+
36+
return adjustedName;
37+
}
38+
39+
public static string FormatTableName(string schema, string tableName)
40+
{
41+
return string.IsNullOrEmpty(schema) ? tableName : string.Format("{0}.{1}", schema, tableName);
42+
}
43+
}
44+
}

src/Migrator.Providers/Impl/Oracle/OracleTransformationProvider.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.Collections.Generic;
33
using System.Data;
44
using System.Linq;
5+
using System.Text;
56
using Migrator.Framework;
67
using Oracle.DataAccess.Client;
78
using ForeignKeyConstraint = Migrator.Framework.ForeignKeyConstraint;
@@ -262,6 +263,14 @@ public override void AddTable(string name, params Column[] columns)
262263
base.AddTable(name, columns);
263264
}
264265

266+
public override string Encode(Guid guid)
267+
{
268+
byte[] bytes = guid.ToByteArray();
269+
var hex = new StringBuilder(bytes.Length * 2);
270+
foreach (byte b in bytes) hex.AppendFormat("{0:X2}", b);
271+
return hex.ToString();
272+
}
273+
265274
void GuardAgainstMaximumColumnNameLengthForOracle(string name, Column[] columns)
266275
{
267276
foreach (Column column in columns)

src/Migrator.Providers/NoOpTransformationProvider.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,11 @@ public string QuoteTableNameIfRequired(string name)
332332
throw new NotImplementedException();
333333
}
334334

335+
public string Encode(Guid guid)
336+
{
337+
return guid.ToString();
338+
}
339+
335340
public void Dispose()
336341
{
337342
//No Op

0 commit comments

Comments
 (0)