10
10
using System . IO ;
11
11
using System . Linq ;
12
12
using System . Reflection ;
13
+ using System . Runtime . Remoting . Messaging ;
13
14
using System . Text ;
14
15
using System . Text . RegularExpressions ;
15
16
using System . Threading . Tasks ;
@@ -18,7 +19,16 @@ namespace DbUp.Support.SqlServer.Scripting
18
19
{
19
20
public class DbObjectScripter
20
21
{
21
- private readonly string m_scrptingObjectRegEx = @"(CREATE|ALTER|DROP)\s*(TABLE|VIEW|PROCEDURE|PROC|FUNCTION|SYNONYM|TYPE) ([\w\[\]\-]+)?\.?([\w\[\]\-]*)" ;
22
+ private const string SCRIPTING_OBJECT_REGEX = @"((CREATE|ALTER|DROP|CREATE\s*OR\s*ALTER)\s*(TABLE|VIEW|PROCEDURE|PROC|FUNCTION|SYNONYM|TYPE)\s*I?F?\s*E?X?I?S?T?S?\s*([\w\[\]\-]+)?\.?([\w\[\]\-]*))|(sp_rename{1,1}\s*'([\w\[\]\-]+)?\.?([\w\[\]\-]*)'\s*,\s*'([\w\[\]\-]*)')" ;
23
+ private const int REGEX_INDEX_ACTION_TYPE = 2 ;
24
+ private const int REGEX_INDEX_OBJECT_TYPE = 3 ;
25
+ private const int REGEX_INDEX_SCHEMA_NAME = 4 ;
26
+ private const int REGEX_INDEX_OBJECT_NAME = 5 ;
27
+ private const int REGEX_INDEX_OBJECT_RENAME_SCHEMA = 7 ;
28
+ private const int REGEX_INDEX_OBJECT_RENAME_OLD_NAME = 8 ;
29
+ private const int REGEX_INDEX_OBJECT_RENAME_NEW_NAME = 9 ;
30
+ private readonly Regex m_targetDbObjectRegex = new Regex ( SCRIPTING_OBJECT_REGEX , RegexOptions . IgnoreCase | RegexOptions . Multiline | RegexOptions . Compiled ) ;
31
+
22
32
private Options m_options ;
23
33
private string m_definitionDirectory ;
24
34
private SqlConnectionStringBuilder m_connectionBuilder ;
@@ -74,12 +84,19 @@ public ScripterResult ScriptAll()
74
84
ScriptAllUserDefinedTypes ( context ) ;
75
85
} ) ;
76
86
87
+ var functionsScriptTask = Task . Run ( ( ) =>
88
+ {
89
+ var context = GetDatabaseContext ( true ) ;
90
+ this . ScriptAllFunctions ( context ) ;
91
+ } ) ;
92
+
77
93
Task . WaitAll (
78
94
tablesScriptTask ,
79
95
viewsScriptTask ,
80
96
storedProceduresScriptTask ,
81
97
synonymsScriptTask ,
82
- udtScriptTask
98
+ udtScriptTask ,
99
+ functionsScriptTask
83
100
) ;
84
101
}
85
102
catch ( Exception ex )
@@ -93,45 +110,139 @@ public ScripterResult ScriptAll()
93
110
94
111
public ScripterResult ScriptMigrationTargets ( IEnumerable < SqlScript > migrationScripts )
95
112
{
96
- Regex targetDbObjectRegex = new Regex ( m_scrptingObjectRegEx ,
97
- RegexOptions . IgnoreCase | RegexOptions . Multiline ) ;
113
+ List < ScriptObject > scriptObjects = new List < ScriptObject > ( migrationScripts . SelectMany ( this . GetObjectsFromMigrationScripts ) ) ;
114
+ scriptObjects = CleanupScriptObjects ( scriptObjects ) ;
115
+
116
+ return ScriptObjects ( scriptObjects ) ;
117
+ }
98
118
99
- List < ScriptObject > scriptObjects = new List < ScriptObject > ( ) ;
100
- foreach ( SqlScript script in migrationScripts )
119
+ private IEnumerable < ScriptObject > GetObjectsFromMigrationScripts ( SqlScript script )
120
+ {
121
+ //extract db object target(s) from scripts
122
+ MatchCollection matches = this . m_targetDbObjectRegex . Matches ( script . Contents ) ;
123
+ foreach ( Match m in matches )
101
124
{
102
- //extract db object target(s) from scripts
103
- MatchCollection matches = targetDbObjectRegex . Matches ( script . Contents ) ;
104
- foreach ( Match m in matches )
125
+ //if this group is empty, it means the second part of the regex matched (sp_rename)
126
+ if ( ! string . IsNullOrEmpty ( m . Groups [ REGEX_INDEX_ACTION_TYPE ] . Value ) )
105
127
{
106
- string objectType = m . Groups [ 2 ] . Value ;
107
-
108
- ObjectTypeEnum type ;
109
- if ( Enum . TryParse < ObjectTypeEnum > ( objectType , true , out type ) )
128
+
129
+ if ( Enum . TryParse < ObjectTypeEnum > ( m . Groups [ REGEX_INDEX_OBJECT_TYPE ] . Value , true , out var type ) )
110
130
{
111
- ObjectActionEnum action = ( ObjectActionEnum ) Enum . Parse ( typeof ( ObjectActionEnum ) , m . Groups [ 1 ] . Value , true ) ;
131
+ //replace CREATE OR ALTER by CREATE
132
+ var actionString = m . Groups [ REGEX_INDEX_ACTION_TYPE ] . Value . StartsWith ( ObjectActionEnum . Create . ToString ( ) , StringComparison . OrdinalIgnoreCase )
133
+ ? ObjectActionEnum . Create . ToString ( )
134
+ : m . Groups [ REGEX_INDEX_ACTION_TYPE ] . Value ;
135
+
136
+ ObjectActionEnum action = ( ObjectActionEnum ) Enum . Parse ( typeof ( ObjectActionEnum ) , actionString , true ) ;
112
137
var scriptObject = new ScriptObject ( type , action ) ;
113
138
114
- if ( string . IsNullOrEmpty ( m . Groups [ 4 ] . Value ) && ! string . IsNullOrEmpty ( m . Groups [ 3 ] . Value ) )
139
+ if ( string . IsNullOrEmpty ( m . Groups [ REGEX_INDEX_OBJECT_NAME ] . Value ) && ! string . IsNullOrEmpty ( m . Groups [ REGEX_INDEX_SCHEMA_NAME ] . Value ) )
115
140
{
116
- //no schema specified
117
- scriptObject . ObjectName = m . Groups [ 3 ] . Value ;
141
+ //no schema specified. in that case, object name is in the schema group
142
+ scriptObject . ObjectName = RemoveBrackets ( m . Groups [ REGEX_INDEX_SCHEMA_NAME ] . Value ) ;
118
143
}
119
144
else
120
145
{
121
- scriptObject . ObjectSchema = m . Groups [ 3 ] . Value ;
122
- scriptObject . ObjectName = m . Groups [ 4 ] . Value ;
146
+ scriptObject . ObjectSchema = RemoveBrackets ( m . Groups [ REGEX_INDEX_SCHEMA_NAME ] . Value ) ;
147
+ scriptObject . ObjectName = RemoveBrackets ( m . Groups [ REGEX_INDEX_OBJECT_NAME ] . Value ) ;
123
148
}
124
149
125
- char [ ] removeCharacters = new char [ ] { '[' , ']' } ;
126
- scriptObject . ObjectSchema = removeCharacters . Aggregate ( scriptObject . ObjectSchema , ( c1 , c2 ) => c1 . Replace ( c2 . ToString ( ) , "" ) ) ;
127
- scriptObject . ObjectName = removeCharacters . Aggregate ( scriptObject . ObjectName , ( c1 , c2 ) => c1 . Replace ( c2 . ToString ( ) , "" ) ) ;
128
-
129
- scriptObjects . Add ( scriptObject ) ;
150
+ yield return scriptObject ;
151
+ }
152
+ }
153
+ else
154
+ {
155
+ string schemaName ;
156
+ string oldObjectName ;
157
+ string newObjectName ;
158
+ if ( string . IsNullOrEmpty ( m . Groups [ REGEX_INDEX_OBJECT_RENAME_OLD_NAME ] . Value ) && ! string . IsNullOrEmpty ( m . Groups [ REGEX_INDEX_OBJECT_RENAME_SCHEMA ] . Value ) )
159
+ {
160
+ //no schema specified. in that case, object name is in the schema group
161
+ schemaName = "dbo" ;
162
+ oldObjectName = RemoveBrackets ( m . Groups [ REGEX_INDEX_OBJECT_RENAME_SCHEMA ] . Value ) ;
163
+ newObjectName = RemoveBrackets ( m . Groups [ REGEX_INDEX_OBJECT_RENAME_OLD_NAME ] . Value ) ;
130
164
}
165
+ else
166
+ {
167
+ schemaName = m . Groups [ REGEX_INDEX_OBJECT_RENAME_SCHEMA ] . Value ;
168
+ oldObjectName = RemoveBrackets ( m . Groups [ REGEX_INDEX_OBJECT_RENAME_OLD_NAME ] . Value ) ;
169
+ newObjectName = RemoveBrackets ( m . Groups [ REGEX_INDEX_OBJECT_RENAME_NEW_NAME ] . Value ) ;
170
+ }
171
+
172
+ var type = GetObjectTypeFromDb ( schemaName , newObjectName ) ;
173
+
174
+ var scriptObjectDrop = new ScriptObject ( type , ObjectActionEnum . Drop ) ;
175
+ scriptObjectDrop . ObjectSchema = schemaName ;
176
+ scriptObjectDrop . ObjectName = oldObjectName ;
177
+
178
+ yield return scriptObjectDrop ;
179
+
180
+ var scriptObjectCreate = new ScriptObject ( type , ObjectActionEnum . Create ) ;
181
+ scriptObjectCreate . ObjectSchema = schemaName ;
182
+ scriptObjectCreate . ObjectName = newObjectName ;
183
+
184
+ yield return scriptObjectCreate ;
131
185
}
132
186
}
187
+ }
133
188
134
- return ScriptObjects ( scriptObjects ) ;
189
+ private static string RemoveBrackets ( string @string )
190
+ {
191
+ char [ ] removeCharacters = { '[' , ']' } ;
192
+ return removeCharacters . Aggregate ( @string , ( c1 , c2 ) => c1 . Replace ( c2 . ToString ( ) , "" ) ) ;
193
+ }
194
+
195
+ private ObjectTypeEnum GetObjectTypeFromDb ( string schemaName , string objectName )
196
+ {
197
+ var context = this . GetDatabaseContext ( false ) ;
198
+ if ( context . Database . Tables [ objectName , schemaName ] != null )
199
+ {
200
+ return ObjectTypeEnum . Table ;
201
+ }
202
+ if ( context . Database . Views [ objectName , schemaName ] != null )
203
+ {
204
+ return ObjectTypeEnum . View ;
205
+ }
206
+ if ( context . Database . Synonyms [ objectName , schemaName ] != null )
207
+ {
208
+ return ObjectTypeEnum . Synonym ;
209
+ }
210
+ if ( context . Database . StoredProcedures [ objectName , schemaName ] != null || context . Database . ExtendedStoredProcedures [ objectName , schemaName ] != null )
211
+ {
212
+ return ObjectTypeEnum . Procedure ;
213
+ }
214
+ if ( context . Database . UserDefinedFunctions [ objectName , schemaName ] != null )
215
+ {
216
+ return ObjectTypeEnum . Function ;
217
+ }
218
+ if ( context . Database . UserDefinedDataTypes [ objectName , schemaName ] != null || context . Database . UserDefinedTableTypes [ objectName , schemaName ] != null || context . Database . UserDefinedTypes [ objectName , schemaName ] != null )
219
+ {
220
+ return ObjectTypeEnum . Type ;
221
+ }
222
+
223
+ return ObjectTypeEnum . Undefined ;
224
+ }
225
+
226
+ /// <summary>
227
+ /// Remove duplicates from a list of ScriptObjects to avoid double sripting of files and not run into errors with later droped objects
228
+ /// </summary>
229
+ /// <param name="scriptObjects"></param>
230
+ /// <returns></returns>
231
+ private static List < ScriptObject > CleanupScriptObjects ( List < ScriptObject > scriptObjects )
232
+ {
233
+ var preCleanUpScripts = new List < ScriptObject > ( scriptObjects ) ;
234
+ preCleanUpScripts . Reverse ( ) ;
235
+
236
+ var cleanedUpScripts = new List < ScriptObject > ( ) ;
237
+ foreach ( var script in preCleanUpScripts )
238
+ {
239
+ if ( ! cleanedUpScripts . Any ( s => s . FullName . Equals ( script . FullName , StringComparison . OrdinalIgnoreCase ) ) )
240
+ {
241
+ cleanedUpScripts . Add ( script ) ;
242
+ }
243
+ }
244
+
245
+ return cleanedUpScripts ;
135
246
}
136
247
137
248
public ScripterResult ScriptObjects ( IEnumerable < ScriptObject > objects )
@@ -148,6 +259,8 @@ public ScripterResult ScriptObjects(IEnumerable<ScriptObject> objects)
148
259
ScriptFunctions ( context , objects . Where ( o => o . ObjectType == ObjectTypeEnum . Function ) ) ;
149
260
ScriptSynonyms ( context , objects . Where ( o => o . ObjectType == ObjectTypeEnum . Synonym ) ) ;
150
261
ScriptUserDefinedTypes ( context , objects . Where ( o => o . ObjectType == ObjectTypeEnum . Type ) ) ;
262
+
263
+ WarnUndefinedObjects ( objects . Where ( o => o . ObjectType == ObjectTypeEnum . Undefined ) ) ;
151
264
}
152
265
catch ( Exception ex )
153
266
{
@@ -467,7 +580,15 @@ private void ScriptDefinition(ScriptObject dbObject, string outputDirectory, Fun
467
580
}
468
581
catch ( Exception ex )
469
582
{
470
- m_log . WriteError ( string . Format ( "Error when scripting definition for {0}: {1}" , dbObject . ObjectName , ex . Message ) ) ;
583
+ m_log . WriteError ( string . Format ( "Error when scripting definition for {0}.{1}: {2}" , dbObject . ObjectSchema , dbObject . ObjectName , ex . Message ) ) ;
584
+ }
585
+ }
586
+
587
+ private void WarnUndefinedObjects ( IEnumerable < ScriptObject > dbObjects )
588
+ {
589
+ foreach ( var dbObject in dbObjects )
590
+ {
591
+ m_log . WriteWarning ( string . Format ( "The object {0}.{1} could not be scripted, since the object type was not identifyable. Normally this means, that the object has been dropped in the meantime. If necessary delete the file manually." , dbObject . ObjectSchema , dbObject . ObjectName ) ) ;
471
592
}
472
593
}
473
594
@@ -482,6 +603,13 @@ private void SaveScript(ScriptObject scriptObject, StringCollection script, stri
482
603
{
483
604
sb . Append ( str ) ;
484
605
sb . Append ( Environment . NewLine ) ;
606
+
607
+ if ( this . m_options . ScriptBatchTerminator )
608
+ {
609
+ sb . Append ( "GO" ) ;
610
+ sb . Append ( Environment . NewLine ) ;
611
+ sb . Append ( Environment . NewLine ) ;
612
+ }
485
613
}
486
614
487
615
m_log . WriteInformation ( string . Format ( "Saving object definition: {0}" , Path . Combine ( outputDirectory , scriptObject . FileName ) ) ) ;
0 commit comments