Datei: NDODLL/SchemaTransitionGenerator.cs

Last Commit (f4c9ea2)
1 //
2 // Copyright (c) 2002-2024 Mirko Matytschak
3 // (www.netdataobjects.de)
4 //
5 // Author: Mirko Matytschak
6 //
7 // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
8 // documentation files (the "Software"), to deal in the Software without restriction, including without limitation
9 // the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
10 // Software, and to permit persons to whom the Software is furnished to do so, subject to the following
11 // conditions:
12
13 // The above copyright notice and this permission notice shall be included in all copies or substantial portions
14 // of the Software.
15 //
16 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
17 // TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
18 // THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
19 // CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
20 // DEALINGS IN THE SOFTWARE.
21
22
23 using System;
24 using System.Text;
25 using System.Collections.Generic;
26 using System.Data;
27 using System.Linq;
28 using System.Xml.Linq;
29 using NDO.Mapping;
30 using NDOInterfaces;
31 using NDO.ProviderFactory;
32
33 namespace NDO
34 {
35 ····/// <summary>
36 ····/// The SchemaTransitionGenerator class transforms an Xml description to SQL DDL statements for a concrete Database.
37 ····/// </summary>
38 ····public class SchemaTransitionGenerator
39 ····{
40 ········private readonly IProvider provider;
41 ········private readonly ISqlGenerator concreteGenerator;
42 ········private readonly NDOMapping mappings;
43
44 ········/// <summary>
45 ········/// SchemaTransitionGenerator constructor
46 ········/// </summary>
47 ········/// <param name="providerFactory">Factory to get the concrete provider for the database.</param>
48 ········/// <param name="providerName">The name of the concrete provider.</param>
49 ········/// <param name="mappings">The mapping information of the current application.</param>
50 ········public SchemaTransitionGenerator( INDOProviderFactory providerFactory, string providerName, NDOMapping mappings )
51 ········{
52 ············this.provider = providerFactory[providerName];
53 ············this.concreteGenerator = providerFactory.Generators[providerName];
54 ············this.mappings = mappings;
55 ········}
56
57 ········/// <summary>
58 ········/// Transforms an Xml description to SQL DDL statements for a concrete Database.
59 ········/// </summary>
60 ········/// <param name="transElement"></param>
61 ········/// <returns></returns>
62 ········public string Generate(XElement transElement)
63 ········{
64 ············StringBuilder sb = new StringBuilder();
65 ············foreach(XElement actionElement in transElement.Elements())
66 ············{
67 ················if (actionElement.Name == "DropTable")
68 ················{
69 ····················sb.Append( DropTable( actionElement ) );
70 ················}
71 ················else if (actionElement.Name == "CreateTable")
72 ················{
73 ····················sb.Append( CreateTable( actionElement ) );
74 ················}
75 ················else if (actionElement.Name == "AlterTable")
76 ················{
77 ····················sb.Append( ChangeTable( actionElement ) );
78 ················}
79 ················else if (actionElement.Name == "CreateIndex")
80 ················{
81 ····················sb.Append( CreateIndex( actionElement ) );
82 ················}
83 ············}
84 ············return sb.ToString();
85 ········}
86
87 ········string ChangeTable(XElement actionElement)
88 ········{
89 ············List<XElement> addedColumns = actionElement.Elements("AddColumn").ToList();
90 ············List<XElement> removedColumns = actionElement.Elements("DropColumn").ToList();
91 ············List<XElement> changedColumns = actionElement.Elements("AlterColumn").ToList();
92
93 ············if (addedColumns.Count == 0 && removedColumns.Count == 0 && changedColumns.Count == 0)
94 ················return String.Empty;
95
96 ············StringBuilder sb = new StringBuilder();
97
98 ············string rawTableName = actionElement.Attribute( "name" ).Value;
99 ············string tableName = this.provider.GetQualifiedTableName(rawTableName);
100 ············string alterString = "ALTER TABLE " + tableName + ' ';
101
102 ············foreach(XElement columnElement in addedColumns)
103 ············{
104 ················sb.Append(alterString);
105 ················sb.Append(concreteGenerator.AddColumn());
106 ················sb.Append(' ');
107 ················sb.Append(CreateColumn(columnElement, FindClass(rawTableName, this.mappings), this.provider, false));
108 ················sb.Append(";\n");
109 ············}
110
111 ············foreach(XElement columnElement in removedColumns)
112 ············{
113 ················sb.Append(alterString);
114 ················sb.Append( concreteGenerator.RemoveColumn( provider.GetQuotedName( columnElement.Attribute( "name" ).Value ) ) );
115 ················sb.Append(";\n");
116 ············}
117
118 ············foreach(XElement columnElement in changedColumns)
119 ············{
120 ················sb.Append(alterString);
121 ················sb.Append(concreteGenerator.AlterColumnType());
122 ················sb.Append(' ');
123 ················sb.Append(CreateColumn(columnElement, FindClass(rawTableName, this.mappings), this.provider, false));
124 ················sb.Append(";\n");
125 ············}
126
127 ············return sb.ToString();
128 ········}
129
130 ········string RenameColumn(string tableName, string oldColumn, string newColumn, string typeString)
131 ········{
132 ············string s = concreteGenerator.RenameColumn(tableName, oldColumn, newColumn, typeString);
133 ············
134 ············if (s != string.Empty)
135 ············return s;
136 ········
137 ············string alterString = "ALTER TABLE " + tableName + ' ';
138
139 ············StringBuilder sb = new StringBuilder(alterString);
140 ············sb.Append(concreteGenerator.AddColumn());
141 ············sb.Append(' ');
142 ············sb.Append(newColumn);
143 ············sb.Append(' ');
144 ············sb.Append(typeString);
145 ············sb.Append(";\n");
146
147 ············sb.Append("UPDATE ");
148 ············sb.Append(tableName);
149 ············sb.Append(" SET ");············
150 ············sb.Append(newColumn);
151 ············sb.Append(" = ");
152 ············sb.Append(oldColumn);
153 ············sb.Append(";\n");
154
155 ············sb.Append(alterString);
156 ············sb.Append(concreteGenerator.RemoveColumn(oldColumn));
157 ············sb.Append(';');
158 ············return sb.ToString();
159 ········}
160
161 ········/// <summary>
162 ········/// Drops a table
163 ········/// </summary>
164 ········/// <param name="actionElement"></param>
165 ········/// <returns></returns>
166 ········protected string DropTable(XElement actionElement)
167 ········{
168 ············string tableName = this.provider .GetQualifiedTableName( actionElement.Attribute( "name" ).Value );
169 ············return concreteGenerator.DropTable( tableName );
170 ········}
171
172 ········/// <summary>
173 ········/// Creates a table
174 ········/// </summary>
175 ········/// <param name="actionElement"></param>
176 ········/// <returns></returns>
177 ········/// <exception cref="Exception"></exception>
178 ········protected string CreateTable(XElement actionElement)
179 ········{
180 ············StringBuilder sb = new StringBuilder();
181 ············IProvider provider = NDOProviderFactory.Instance[concreteGenerator.ProviderName];
182 ············if (provider == null)
183 ················throw new Exception("Can't find NDO provider '" + concreteGenerator.ProviderName + "'.");
184
185 ············string dtTableName = actionElement.Attribute( "name" ).Value;
186
187 ············string tableName = this.provider.GetQualifiedTableName( dtTableName );
188
189 ············Class cl = FindClass(dtTableName, mappings);
190
191 ············if (cl != null)
192 ············{
193 ················Connection conn = mappings.FindConnection(cl);
194 ················if (conn != null)
195 ····················concreteGenerator.ConnectToDatabase(conn.Name);
196 ············}
197
198 ············sb.Append(concreteGenerator.BeginnTable(tableName));
199 ············sb.Append('\n');
200
201 ············List<XElement> columnElements = actionElement.Elements( "CreateColumn" ).ToList();
202
203 ············int vorletzterIndex = columnElements.Count - 1;
204 ············List<XElement> primaryKeyColumns = columnElements.Where( e =>
205 ············{
206 ················XAttribute attr;
207 ················return (attr = e.Attribute( "isPrimary" )) != null && String.Compare( attr.Value, "true", true ) == 0;
208 ············} ).ToList();
209
210 ············bool hasPrimaryKeyColumns = primaryKeyColumns.Count > 0;
211
212 ············for (int i = 0; i < columnElements.Count; i++)
213 ············{
214 ················XElement columnElement = columnElements[i];
215 ················string columnName = columnElement.Attribute("name").Value;
216
217 ················bool isPrimary = primaryKeyColumns.Any( e => e.Attribute( "name" ).Value == columnName );
218
219 ················sb.Append(CreateColumn(columnElement, cl, provider, isPrimary));
220 ················if (i < vorletzterIndex)
221 ················{
222 ····················sb.Append(",");
223 ····················sb.Append('\n');
224 ················}
225 ············}
226
227 ············if(concreteGenerator.PrimaryConstraintPlacement == PrimaryConstraintPlacement.InTable
228 ················&& hasPrimaryKeyColumns)
229 ············{
230 ················sb.Append(",");
231 ················sb.Append('\n');
232 ············}
233
234 ············if (hasPrimaryKeyColumns && concreteGenerator.PrimaryConstraintPlacement == PrimaryConstraintPlacement.InTable)
235 ············{
236 ················sb.Append(CreatePrimaryKeyConstraint(primaryKeyColumns, dtTableName, provider));
237 ············}
238 ············else
239 ············{
240 ················sb.Append('\n');
241 ············}
242 ············sb.Append(concreteGenerator.EndTable(tableName));
243 ············sb.Append('\n');
244
245 ············//············CreateIndex(primaryKeyColumns, sb, dt, provider);
246
247 ············if (hasPrimaryKeyColumns && concreteGenerator.PrimaryConstraintPlacement == PrimaryConstraintPlacement.AfterTable)
248 ············{
249 ················sb.Append(CreatePrimaryKeyConstraint(primaryKeyColumns, dtTableName, provider));
250 ············}
251
252 ············sb.Append('\n');
253
254 ············return sb.ToString();
255 ········}
256
257 ········string CreateIndex(XElement actionElement)
258 ········{
259 ············//<CreateIndex name="xxx" unique="True|False" fulltext="True|False" onTable="TableName">
260 ············//··<Column name="xxx" desc="True|False">
261 ············//</CreateIndex>
262 ············StringBuilder sb = new StringBuilder();
263 ············IProvider provider = NDOProviderFactory.Instance[concreteGenerator.ProviderName];
264 ············if (provider == null)
265 ················throw new Exception( "Can't find NDO provider '" + concreteGenerator.ProviderName + "'." );
266
267 ············string tableName··= this.provider.GetQualifiedTableName( actionElement.Attribute( "onTable" ).Value );
268 ············string indexName = this.provider.GetQualifiedTableName( actionElement.Attribute( "name" ).Value );
269
270 ············sb.Append( "CREATE " );
271
272 ············// NDO doesn't check, if these keywords are supported by the database.
273 ············if (String.Compare( actionElement.Attribute( "unique" )?.Value, "true", true ) == 0)
274 ················sb.Append( "UNIQUE " );
275 ············if (String.Compare( actionElement.Attribute( "fulltext" )?.Value, "true", true ) == 0)
276 ················sb.Append( "FULLTEXT " );
277
278 ············sb.Append( "INDEX " );
279 ············sb.Append( indexName );
280 ············sb.Append( " ON " );
281 ············sb.Append( tableName );
282 ············sb.Append( " (" );
283
284 ············var columns = actionElement.Elements( "Column" ).ToList();
285
286 ············var lastIndex = columns.Count - 1;
287 ············for (int i = 0; i <= lastIndex; i++)
288 ············{
289 ················var columnElement = columns[i];
290 ················var columnName = provider.GetQuotedName( columnElement.Attribute( "name" )?.Value );
291 ················
292 ················if (columnName == null)
293 ····················throw new Exception( "Column element of CreateIndex needs a name attribute" );
294
295 ················sb.Append( columnName );
296 ················
297 ················// NDO doesn't check, if DESC is supported by the database.
298 ················// We also assume, that ASC is the standard case, so we don't emit the ASC keyword.
299 ················var desc = String.Compare( columnElement.Attribute( "desc" )?.Value, "true", true ) == 0;
300 ················if (desc)
301 ····················sb.Append( " DESC" );
302
303 ················if (i < lastIndex)
304 ····················sb.Append( ", " );
305 ············}
306
307 sb. Append( ") " ) ;
308
309 ············return sb.ToString();
310 ········}
311
312 ········/// <summary>
313 ········/// Finds a Class mapping object, if the mapped table name matches the given table name.
314 ········/// </summary>
315 ········/// <param name="tableName"></param>
316 ········/// <param name="mappings"></param>
317 ········/// <returns></returns>
318 ········protected Class FindClass(string tableName, NDOMapping mappings)
319 ········{
320 ············Class result = null;
321 ············foreach(Class cl in mappings.Classes)
322 ············{
323 ················if (cl.TableName == tableName)
324 ················{
325 ····················result = cl;
326 ····················break;
327 ················}
328 ············}
329 ············return result;
330 ········}
331
332 ········static bool IsNumeric( Type type )
333 ········{
334 ············switch (Type.GetTypeCode( type ))
335 ············{
336 ················case TypeCode.Byte:
337 ················case TypeCode.SByte:
338 ················case TypeCode.UInt16:
339 ················case TypeCode.UInt32:
340 ················case TypeCode.UInt64:
341 ················case TypeCode.Int16:
342 ················case TypeCode.Int32:
343 ················case TypeCode.Int64:
344 ················case TypeCode.Decimal:
345 ················case TypeCode.Double:
346 ················case TypeCode.Single:
347 ····················return true;
348 ················case TypeCode.Object:
349 ····················if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof( Nullable<> ))
350 ····················{
351 ························return IsNumeric(Nullable.GetUnderlyingType( type ));
352 ····················}
353 ····················return false;
354 ················default:
355 ····················return false;
356 ············}
357 ········}
358
359 ········/// <summary>
360 ········/// Creates a column of an existing table
361 ········/// </summary>
362 ········/// <param name="columnElement"></param>
363 ········/// <param name="cl"></param>
364 ········/// <param name="provider"></param>
365 ········/// <param name="isPrimary"></param>
366 ········/// <returns></returns>
367 ········protected string CreateColumn(XElement columnElement, Class cl, IProvider provider, bool isPrimary)
368 ········{
369 ············string rawName = columnElement.Attribute( "name" ).Value;
370 ············Type dcDataType = Type.GetType( columnElement.Attribute( "type" ).Value );
371 ············string name = provider.GetQuotedName(rawName);
372 ············string columnType = columnElement.Attribute( "dbtype" )?.Value;
373 ············if (columnType == String.Empty)··// Make sure the column type will be infered, if it is an empty string
374 ················columnType = null;
375 ············string width = columnElement.Attribute("size") == null ? null : columnElement.Attribute("size").Value;
376 ············string precision = null;
377 ············bool autoIncrement = false;
378 ············StringBuilder sb = new StringBuilder();
379 ············bool allowNull = true;
380 ············string defaultValue = columnElement.Attribute( "default" )?.Value;
381
382 ············if (cl != null)
383 ············{
384 ················Field field = FindField(rawName, cl);
385
386 ················if (null != field)
387 ················{
388 ····················if ( !String.IsNullOrEmpty(columnType) && null != field.Column.DbType)
389 ························columnType = field.Column.DbType;
390 ····················if (0 != field.Column.Size && !field.Column.IgnoreColumnSizeInDDL)
391 ····················{
392 ························int dl = field.Column.Size;
393 ························if (dl == -1)
394 ····························width = "max";
395 ························else
396 ····························width = dl.ToString();
397 ····················}
398 ····················if (0 != field.Column.Precision && !field.Column.IgnoreColumnSizeInDDL)
399 ························precision = field.Column.Precision.ToString();
400 ····················allowNull = field.Column.AllowDbNull;
401 ················}
402 ················else if (cl.TimeStampColumn == rawName)
403 ················{
404 ····················if (!provider.SupportsNativeGuidType)
405 ························width = "36";
406 ················}
407 ················else if (isPrimary && columnElement.Attribute( "autoIncrement" ) != null && String.Compare(columnElement.Attribute( "autoIncrement" ).Value, "true", true) == 0)
408 ················{
409 ····················autoIncrement = true;
410 ················}
411 ············}
412 ············if (null == columnType)
413 ············{
414 ················try
415 ················{
416 ····················columnType = concreteGenerator.DbTypeFromType(dcDataType);
417 ················}
418 ················catch
419 ················{
420 ····················System.Diagnostics.Debug.Write("");
421 ················}
422 ············}
423
424 ············if (null == width)
425 ············{
426 ················int dl = provider.GetDefaultLength(dcDataType);
427 ················if (dl != 0)
428 ················{
429 ····················if (dl == -1)
430 ························width = "max";
431 ····················else
432 ························width = dl.ToString();
433 ················}
434 ············}
435 ············
436 ············// Because there is no GetDefaultPrecision in the provider...
437 ············// We assume the field to represent currency data
438 ············if (precision == null && dcDataType == typeof(decimal))
439 ············{
440 ················precision = "2";
441 ············}
442
443 ············if (columnType != null)
444 ············{
445 ················if (!concreteGenerator.LengthAllowed(columnType))
446 ····················width = null;
447 ············}
448 ············else
449 ············{
450 ················if (!concreteGenerator.LengthAllowed(dcDataType))
451 ····················width = null;
452 ············}
453
454
455 ············if (autoIncrement && concreteGenerator.HasSpecialAutoIncrementColumnFormat)
456 ················sb.Append(concreteGenerator.AutoIncrementColumn(name, dcDataType, columnType, width, isPrimary));
457 ············else if(isPrimary && concreteGenerator.PrimaryConstraintPlacement == PrimaryConstraintPlacement.InColumn)
458 ················sb.Append(concreteGenerator.PrimaryKeyColumn(name, dcDataType, columnType, width));
459 ············else if (width != null && precision != null)
460 ················sb.Append(name + " " + columnType + "(" + width + "," + precision + ")");
461 ············else if (width != null)
462 ················sb.Append(name + " " + columnType + "(" + width + ")");············
463 ············else
464 ················sb.Append(name + " " + columnType);
465
466 ············sb.Append(" ");
467
468 ············if (defaultValue != null)
469 ············{
470 ················sb.Append( "DEFAULT " );
471 ················if (IsNumeric( dcDataType ))
472 ····················sb.Append( defaultValue);
473 ················else
474 ················{
475 ····················sb.Append( '\'' );
476 ····················sb.Append( defaultValue.Replace( "'", "''" ) );
477 ····················sb.Append( '\'' );
478 ················}
479 ················sb.Append( ' ' );
480 ············}
481
482 ············sb.Append( concreteGenerator.NullExpression( allowNull && columnElement.Attribute( "allowNull" ) != null && String.Compare( columnElement.Attribute( "allowNull" ).Value, "true", true ) == 0 ) );
483 ············return sb.ToString();
484 ········}
485
486 ········/// <summary>
487 ········/// Generates SQL code for creating a PK Constraint
488 ········/// </summary>
489 ········/// <param name="primaryKeyColumns"></param>
490 ········/// <param name="tableName"></param>
491 ········/// <param name="provider"></param>
492 ········/// <returns></returns>
493 ········protected string CreatePrimaryKeyConstraint(List<XElement> primaryKeyColumns, string tableName, IProvider provider)
494 ········{
495 ············if (primaryKeyColumns.Count == 0)
496 ················return string.Empty;
497 ············string[] strArr = tableName.Split('.');
498 ············string constraintName = provider.GetQuotedName("PK_" + strArr[strArr.Length - 1]);
499 ············DataColumn[] pkColumns = (from pk in primaryKeyColumns select new DataColumn( pk.Attribute( "name" ).Value )).ToArray();
500 ············return concreteGenerator.CreatePrimaryKeyConstraint(pkColumns, constraintName, provider.GetQualifiedTableName(tableName)) + '\n';
501 ········}
502
503 ········/// <summary>
504 ········/// Finds a field mapping for the given column name
505 ········/// </summary>
506 ········/// <param name="columnName"></param>
507 ········/// <param name="cl"></param>
508 ········/// <returns></returns>
509 ········protected Field FindField (string columnName, Class cl)
510 ········{
511 ············Field result = null;
512 ············foreach (Field field in cl.Fields)
513 ············{
514 ················if (field.Column.Name == columnName)
515 ················{
516 ····················result = field;
517 ····················break;
518 ················}
519 ············}
520 ············return result;
521 ········}
522 ····}
523 }
524
New Commit (01941c7)
1 //
2 // Copyright (c) 2002-2024 Mirko Matytschak
3 // (www.netdataobjects.de)
4 //
5 // Author: Mirko Matytschak
6 //
7 // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
8 // documentation files (the "Software"), to deal in the Software without restriction, including without limitation
9 // the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
10 // Software, and to permit persons to whom the Software is furnished to do so, subject to the following
11 // conditions:
12
13 // The above copyright notice and this permission notice shall be included in all copies or substantial portions
14 // of the Software.
15 //
16 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
17 // TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
18 // THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
19 // CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
20 // DEALINGS IN THE SOFTWARE.
21
22
23 using System;
24 using System.Text;
25 using System.Collections.Generic;
26 using System.Data;
27 using System.Linq;
28 using System.Xml.Linq;
29 using NDO.Mapping;
30 using NDOInterfaces;
31 using NDO.ProviderFactory;
32
33 namespace NDO
34 {
35 ····/// <summary>
36 ····/// The SchemaTransitionGenerator class transforms an Xml description to SQL DDL statements for a concrete Database.
37 ····/// </summary>
38 ····public class SchemaTransitionGenerator
39 ····{
40 ········private readonly IProvider provider;
41 ········private readonly ISqlGenerator concreteGenerator;
42 ········private readonly NDOMapping mappings;
43
44 ········/// <summary>
45 ········/// SchemaTransitionGenerator constructor
46 ········/// </summary>
47 ········/// <param name="providerFactory">Factory to get the concrete provider for the database.</param>
48 ········/// <param name="providerName">The name of the concrete provider.</param>
49 ········/// <param name="mappings">The mapping information of the current application.</param>
50 ········public SchemaTransitionGenerator( INDOProviderFactory providerFactory, string providerName, NDOMapping mappings )
51 ········{
52 ············this.provider = providerFactory[providerName];
53 ············this.concreteGenerator = providerFactory.Generators[providerName];
54 ············this.mappings = mappings;
55 ········}
56
57 ········/// <summary>
58 ········/// Transforms an Xml description to SQL DDL statements for a concrete Database.
59 ········/// </summary>
60 ········/// <param name="transElement"></param>
61 ········/// <returns></returns>
62 ········public string Generate(XElement transElement)
63 ········{
64 ············StringBuilder sb = new StringBuilder();
65 ············foreach(XElement actionElement in transElement.Elements())
66 ············{
67 ················if (actionElement.Name == "DropTable")
68 ················{
69 ····················sb.Append( DropTable( actionElement ) );
70 ················}
71 ················else if (actionElement.Name == "CreateTable")
72 ················{
73 ····················sb.Append( CreateTable( actionElement ) );
74 ················}
75 ················else if (actionElement.Name == "AlterTable")
76 ················{
77 ····················sb.Append( ChangeTable( actionElement ) );
78 ················}
79 ················else if (actionElement.Name == "CreateIndex")
80 ················{
81 ····················sb.Append( CreateIndex( actionElement ) );
82 ················}
83 ············}
84 ············return sb.ToString();
85 ········}
86
87 ········string ChangeTable(XElement actionElement)
88 ········{
89 ············List<XElement> addedColumns = actionElement.Elements("AddColumn").ToList();
90 ············List<XElement> removedColumns = actionElement.Elements("DropColumn").ToList();
91 ············List<XElement> changedColumns = actionElement.Elements("AlterColumn").ToList();
92
93 ············if (addedColumns.Count == 0 && removedColumns.Count == 0 && changedColumns.Count == 0)
94 ················return String.Empty;
95
96 ············StringBuilder sb = new StringBuilder();
97
98 ············string rawTableName = actionElement.Attribute( "name" ).Value;
99 ············string tableName = this.provider.GetQualifiedTableName(rawTableName);
100 ············string alterString = "ALTER TABLE " + tableName + ' ';
101
102 ············foreach(XElement columnElement in addedColumns)
103 ············{
104 ················sb.Append(alterString);
105 ················sb.Append(concreteGenerator.AddColumn());
106 ················sb.Append(' ');
107 ················sb.Append(CreateColumn(columnElement, FindClass(rawTableName, this.mappings), this.provider, false));
108 ················sb.Append(";\n");
109 ············}
110
111 ············foreach(XElement columnElement in removedColumns)
112 ············{
113 ················sb.Append(alterString);
114 ················sb.Append( concreteGenerator.RemoveColumn( provider.GetQuotedName( columnElement.Attribute( "name" ).Value ) ) );
115 ················sb.Append(";\n");
116 ············}
117
118 ············foreach(XElement columnElement in changedColumns)
119 ············{
120 ················sb.Append(alterString);
121 ················sb.Append(concreteGenerator.AlterColumnType());
122 ················sb.Append(' ');
123 ················sb.Append(CreateColumn(columnElement, FindClass(rawTableName, this.mappings), this.provider, false));
124 ················sb.Append(";\n");
125 ············}
126
127 ············return sb.ToString();
128 ········}
129
130 ········string RenameColumn(string tableName, string oldColumn, string newColumn, string typeString)
131 ········{
132 ············string s = concreteGenerator.RenameColumn(tableName, oldColumn, newColumn, typeString);
133 ············
134 ············if (s != string.Empty)
135 ············return s;
136 ········
137 ············string alterString = "ALTER TABLE " + tableName + ' ';
138
139 ············StringBuilder sb = new StringBuilder(alterString);
140 ············sb.Append(concreteGenerator.AddColumn());
141 ············sb.Append(' ');
142 ············sb.Append(newColumn);
143 ············sb.Append(' ');
144 ············sb.Append(typeString);
145 ············sb.Append(";\n");
146
147 ············sb.Append("UPDATE ");
148 ············sb.Append(tableName);
149 ············sb.Append(" SET ");············
150 ············sb.Append(newColumn);
151 ············sb.Append(" = ");
152 ············sb.Append(oldColumn);
153 ············sb.Append(";\n");
154
155 ············sb.Append(alterString);
156 ············sb.Append(concreteGenerator.RemoveColumn(oldColumn));
157 ············sb.Append(';');
158 ············return sb.ToString();
159 ········}
160
161 ········/// <summary>
162 ········/// Drops a table
163 ········/// </summary>
164 ········/// <param name="actionElement"></param>
165 ········/// <returns></returns>
166 ········protected string DropTable(XElement actionElement)
167 ········{
168 ············string tableName = this.provider .GetQualifiedTableName( actionElement.Attribute( "name" ).Value );
169 ············return concreteGenerator.DropTable( tableName );
170 ········}
171
172 ········/// <summary>
173 ········/// Creates a table
174 ········/// </summary>
175 ········/// <param name="actionElement"></param>
176 ········/// <returns></returns>
177 ········/// <exception cref="Exception"></exception>
178 ········protected string CreateTable(XElement actionElement)
179 ········{
180 ············StringBuilder sb = new StringBuilder();
181 ············IProvider provider = NDOProviderFactory.Instance[concreteGenerator.ProviderName];
182 ············if (provider == null)
183 ················throw new Exception("Can't find NDO provider '" + concreteGenerator.ProviderName + "'.");
184
185 ············string dtTableName = actionElement.Attribute( "name" ).Value;
186
187 ············string tableName = this.provider.GetQualifiedTableName( dtTableName );
188
189 ············Class cl = FindClass(dtTableName, mappings);
190
191 ············if (cl != null)
192 ············{
193 ················Connection conn = mappings.FindConnection(cl);
194 ················if (conn != null)
195 ····················concreteGenerator.ConnectToDatabase(conn.Name);
196 ············}
197
198 ············sb.Append(concreteGenerator.BeginnTable(tableName));
199 ············sb.Append('\n');
200
201 ············List<XElement> columnElements = actionElement.Elements( "CreateColumn" ).ToList();
202
203 ············int vorletzterIndex = columnElements.Count - 1;
204 ············List<XElement> primaryKeyColumns = columnElements.Where( e =>
205 ············{
206 ················XAttribute attr;
207 ················return (attr = e.Attribute( "isPrimary" )) != null && String.Compare( attr.Value, "true", true ) == 0;
208 ············} ).ToList();
209
210 ············bool hasPrimaryKeyColumns = primaryKeyColumns.Count > 0;
211
212 ············for (int i = 0; i < columnElements.Count; i++)
213 ············{
214 ················XElement columnElement = columnElements[i];
215 ················string columnName = columnElement.Attribute("name").Value;
216
217 ················bool isPrimary = primaryKeyColumns.Any( e => e.Attribute( "name" ).Value == columnName );
218
219 ················sb.Append(CreateColumn(columnElement, cl, provider, isPrimary));
220 ················if (i < vorletzterIndex)
221 ················{
222 ····················sb.Append(",");
223 ····················sb.Append('\n');
224 ················}
225 ············}
226
227 ············if(concreteGenerator.PrimaryConstraintPlacement == PrimaryConstraintPlacement.InTable
228 ················&& hasPrimaryKeyColumns)
229 ············{
230 ················sb.Append(",");
231 ················sb.Append('\n');
232 ············}
233
234 ············if (hasPrimaryKeyColumns && concreteGenerator.PrimaryConstraintPlacement == PrimaryConstraintPlacement.InTable)
235 ············{
236 ················sb.Append(CreatePrimaryKeyConstraint(primaryKeyColumns, dtTableName, provider));
237 ············}
238 ············else
239 ············{
240 ················sb.Append('\n');
241 ············}
242 ············sb.Append(concreteGenerator.EndTable(tableName));
243 ············sb.Append('\n');
244
245 ············//············CreateIndex(primaryKeyColumns, sb, dt, provider);
246
247 ············if (hasPrimaryKeyColumns && concreteGenerator.PrimaryConstraintPlacement == PrimaryConstraintPlacement.AfterTable)
248 ············{
249 ················sb.Append(CreatePrimaryKeyConstraint(primaryKeyColumns, dtTableName, provider));
250 ············}
251
252 ············sb.Append('\n');
253
254 ············return sb.ToString();
255 ········}
256
257 ········string CreateIndex(XElement actionElement)
258 ········{
259 ············//<CreateIndex name="xxx" unique="True|False" fulltext="True|False" onTable="TableName">
260 ············//··<Column name="xxx" desc="True|False">
261 ············//</CreateIndex>
262 ············StringBuilder sb = new StringBuilder();
263 ············IProvider provider = NDOProviderFactory.Instance[concreteGenerator.ProviderName];
264 ············if (provider == null)
265 ················throw new Exception( "Can't find NDO provider '" + concreteGenerator.ProviderName + "'." );
266
267 ············string tableName··= this.provider.GetQualifiedTableName( actionElement.Attribute( "onTable" ).Value );
268 ············string indexName = this.provider.GetQualifiedTableName( actionElement.Attribute( "name" ).Value );
269
270 ············sb.Append( "CREATE " );
271
272 ············// NDO doesn't check, if these keywords are supported by the database.
273 ············if (String.Compare( actionElement.Attribute( "unique" )?.Value, "true", true ) == 0)
274 ················sb.Append( "UNIQUE " );
275 ············if (String.Compare( actionElement.Attribute( "fulltext" )?.Value, "true", true ) == 0)
276 ················sb.Append( "FULLTEXT " );
277
278 ············sb.Append( "INDEX " );
279 ············sb.Append( indexName );
280 ············sb.Append( " ON " );
281 ············sb.Append( tableName );
282 ············sb.Append( " (" );
283
284 ············var columns = actionElement.Elements( "Column" ).ToList();
285
286 ············var lastIndex = columns.Count - 1;
287 ············for (int i = 0; i <= lastIndex; i++)
288 ············{
289 ················var columnElement = columns[i];
290 ················var columnName = provider.GetQuotedName( columnElement.Attribute( "name" )?.Value );
291 ················
292 ················if (columnName == null)
293 ····················throw new Exception( "Column element of CreateIndex needs a name attribute" );
294
295 ················sb.Append( columnName );
296 ················
297 ················// NDO doesn't check, if DESC is supported by the database.
298 ················// We also assume, that ASC is the standard case, so we don't emit the ASC keyword.
299 ················var desc = String.Compare( columnElement.Attribute( "desc" )?.Value, "true", true ) == 0;
300 ················if (desc)
301 ····················sb.Append( " DESC" );
302
303 ················if (i < lastIndex)
304 ····················sb.Append( ", " );
305 ············}
306
307 sb. Append( ") ;" ) ;
308
309 ············return sb.ToString();
310 ········}
311
312 ········/// <summary>
313 ········/// Finds a Class mapping object, if the mapped table name matches the given table name.
314 ········/// </summary>
315 ········/// <param name="tableName"></param>
316 ········/// <param name="mappings"></param>
317 ········/// <returns></returns>
318 ········protected Class FindClass(string tableName, NDOMapping mappings)
319 ········{
320 ············Class result = null;
321 ············foreach(Class cl in mappings.Classes)
322 ············{
323 ················if (cl.TableName == tableName)
324 ················{
325 ····················result = cl;
326 ····················break;
327 ················}
328 ············}
329 ············return result;
330 ········}
331
332 ········static bool IsNumeric( Type type )
333 ········{
334 ············switch (Type.GetTypeCode( type ))
335 ············{
336 ················case TypeCode.Byte:
337 ················case TypeCode.SByte:
338 ················case TypeCode.UInt16:
339 ················case TypeCode.UInt32:
340 ················case TypeCode.UInt64:
341 ················case TypeCode.Int16:
342 ················case TypeCode.Int32:
343 ················case TypeCode.Int64:
344 ················case TypeCode.Decimal:
345 ················case TypeCode.Double:
346 ················case TypeCode.Single:
347 ····················return true;
348 ················case TypeCode.Object:
349 ····················if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof( Nullable<> ))
350 ····················{
351 ························return IsNumeric(Nullable.GetUnderlyingType( type ));
352 ····················}
353 ····················return false;
354 ················default:
355 ····················return false;
356 ············}
357 ········}
358
359 ········/// <summary>
360 ········/// Creates a column of an existing table
361 ········/// </summary>
362 ········/// <param name="columnElement"></param>
363 ········/// <param name="cl"></param>
364 ········/// <param name="provider"></param>
365 ········/// <param name="isPrimary"></param>
366 ········/// <returns></returns>
367 ········protected string CreateColumn(XElement columnElement, Class cl, IProvider provider, bool isPrimary)
368 ········{
369 ············string rawName = columnElement.Attribute( "name" ).Value;
370 ············Type dcDataType = Type.GetType( columnElement.Attribute( "type" ).Value );
371 ············string name = provider.GetQuotedName(rawName);
372 ············string columnType = columnElement.Attribute( "dbtype" )?.Value;
373 ············if (columnType == String.Empty)··// Make sure the column type will be infered, if it is an empty string
374 ················columnType = null;
375 ············string width = columnElement.Attribute("size") == null ? null : columnElement.Attribute("size").Value;
376 ············string precision = null;
377 ············bool autoIncrement = false;
378 ············StringBuilder sb = new StringBuilder();
379 ············bool allowNull = true;
380 ············string defaultValue = columnElement.Attribute( "default" )?.Value;
381
382 ············if (cl != null)
383 ············{
384 ················Field field = FindField(rawName, cl);
385
386 ················if (null != field)
387 ················{
388 ····················if ( !String.IsNullOrEmpty(columnType) && null != field.Column.DbType)
389 ························columnType = field.Column.DbType;
390 ····················if (0 != field.Column.Size && !field.Column.IgnoreColumnSizeInDDL)
391 ····················{
392 ························int dl = field.Column.Size;
393 ························if (dl == -1)
394 ····························width = "max";
395 ························else
396 ····························width = dl.ToString();
397 ····················}
398 ····················if (0 != field.Column.Precision && !field.Column.IgnoreColumnSizeInDDL)
399 ························precision = field.Column.Precision.ToString();
400 ····················allowNull = field.Column.AllowDbNull;
401 ················}
402 ················else if (cl.TimeStampColumn == rawName)
403 ················{
404 ····················if (!provider.SupportsNativeGuidType)
405 ························width = "36";
406 ················}
407 ················else if (isPrimary && columnElement.Attribute( "autoIncrement" ) != null && String.Compare(columnElement.Attribute( "autoIncrement" ).Value, "true", true) == 0)
408 ················{
409 ····················autoIncrement = true;
410 ················}
411 ············}
412 ············if (null == columnType)
413 ············{
414 ················try
415 ················{
416 ····················columnType = concreteGenerator.DbTypeFromType(dcDataType);
417 ················}
418 ················catch
419 ················{
420 ····················System.Diagnostics.Debug.Write("");
421 ················}
422 ············}
423
424 ············if (null == width)
425 ············{
426 ················int dl = provider.GetDefaultLength(dcDataType);
427 ················if (dl != 0)
428 ················{
429 ····················if (dl == -1)
430 ························width = "max";
431 ····················else
432 ························width = dl.ToString();
433 ················}
434 ············}
435 ············
436 ············// Because there is no GetDefaultPrecision in the provider...
437 ············// We assume the field to represent currency data
438 ············if (precision == null && dcDataType == typeof(decimal))
439 ············{
440 ················precision = "2";
441 ············}
442
443 ············if (columnType != null)
444 ············{
445 ················if (!concreteGenerator.LengthAllowed(columnType))
446 ····················width = null;
447 ············}
448 ············else
449 ············{
450 ················if (!concreteGenerator.LengthAllowed(dcDataType))
451 ····················width = null;
452 ············}
453
454
455 ············if (autoIncrement && concreteGenerator.HasSpecialAutoIncrementColumnFormat)
456 ················sb.Append(concreteGenerator.AutoIncrementColumn(name, dcDataType, columnType, width, isPrimary));
457 ············else if(isPrimary && concreteGenerator.PrimaryConstraintPlacement == PrimaryConstraintPlacement.InColumn)
458 ················sb.Append(concreteGenerator.PrimaryKeyColumn(name, dcDataType, columnType, width));
459 ············else if (width != null && precision != null)
460 ················sb.Append(name + " " + columnType + "(" + width + "," + precision + ")");
461 ············else if (width != null)
462 ················sb.Append(name + " " + columnType + "(" + width + ")");············
463 ············else
464 ················sb.Append(name + " " + columnType);
465
466 ············sb.Append(" ");
467
468 ············if (defaultValue != null)
469 ············{
470 ················sb.Append( "DEFAULT " );
471 ················if (IsNumeric( dcDataType ))
472 ····················sb.Append( defaultValue);
473 ················else
474 ················{
475 ····················sb.Append( '\'' );
476 ····················sb.Append( defaultValue.Replace( "'", "''" ) );
477 ····················sb.Append( '\'' );
478 ················}
479 ················sb.Append( ' ' );
480 ············}
481
482 ············sb.Append( concreteGenerator.NullExpression( allowNull && columnElement.Attribute( "allowNull" ) != null && String.Compare( columnElement.Attribute( "allowNull" ).Value, "true", true ) == 0 ) );
483 ············return sb.ToString();
484 ········}
485
486 ········/// <summary>
487 ········/// Generates SQL code for creating a PK Constraint
488 ········/// </summary>
489 ········/// <param name="primaryKeyColumns"></param>
490 ········/// <param name="tableName"></param>
491 ········/// <param name="provider"></param>
492 ········/// <returns></returns>
493 ········protected string CreatePrimaryKeyConstraint(List<XElement> primaryKeyColumns, string tableName, IProvider provider)
494 ········{
495 ············if (primaryKeyColumns.Count == 0)
496 ················return string.Empty;
497 ············string[] strArr = tableName.Split('.');
498 ············string constraintName = provider.GetQuotedName("PK_" + strArr[strArr.Length - 1]);
499 ············DataColumn[] pkColumns = (from pk in primaryKeyColumns select new DataColumn( pk.Attribute( "name" ).Value )).ToArray();
500 ············return concreteGenerator.CreatePrimaryKeyConstraint(pkColumns, constraintName, provider.GetQualifiedTableName(tableName)) + '\n';
501 ········}
502
503 ········/// <summary>
504 ········/// Finds a field mapping for the given column name
505 ········/// </summary>
506 ········/// <param name="columnName"></param>
507 ········/// <param name="cl"></param>
508 ········/// <returns></returns>
509 ········protected Field FindField (string columnName, Class cl)
510 ········{
511 ············Field result = null;
512 ············foreach (Field field in cl.Fields)
513 ············{
514 ················if (field.Column.Name == columnName)
515 ················{
516 ····················result = field;
517 ····················break;
518 ················}
519 ············}
520 ············return result;
521 ········}
522 ····}
523 }
524