* MergeManager.cs : AdjustSchema now updates by reference new created table, so in...
[mono.git] / mcs / class / System.Data / System.Data / MergeManager.cs
1 using System;
2
3 namespace System.Data
4 {
5         /// <summary>
6         /// Summary description for MergeManager.
7         /// </summary>
8         internal class MergeManager
9         {
10                 internal static void Merge(DataSet targetSet, DataSet sourceSet, bool preserveChanges, MissingSchemaAction missingSchemaAction)
11                 {
12                         if(targetSet == null)
13                                 throw new ArgumentNullException("targetSet");
14                         if(sourceSet == null)
15                                 throw new ArgumentNullException("sourceSet");
16
17                         foreach (DataTable t in sourceSet.Tables)
18                                 MergeManager.Merge(targetSet, t, preserveChanges, missingSchemaAction);
19
20                         AdjustSchema(targetSet,sourceSet,missingSchemaAction);
21
22                 }
23
24                 internal static void Merge(DataSet targetSet, DataTable sourceTable, bool preserveChanges, MissingSchemaAction missingSchemaAction)
25                 {
26                         if(targetSet == null)
27                                 throw new ArgumentNullException("targetSet");
28                         if(sourceTable == null)
29                                 throw new ArgumentNullException("sourceTable");
30
31                         DataTable targetTable = null;
32                         if (!AdjustSchema(targetSet, sourceTable, missingSchemaAction,ref targetTable)) {
33                                 return;
34                         }
35                         if (targetTable != null) {
36                                 checkColumnTypes(targetTable, sourceTable); // check that the colums datatype is the same
37                                 fillData(targetTable, sourceTable, preserveChanges);
38                         }                       
39                 }
40
41                 internal static void Merge(DataSet targetSet, DataRow[] sourceRows, bool preserveChanges, MissingSchemaAction missingSchemaAction)
42                 {
43                         if(targetSet == null)
44                                 throw new ArgumentNullException("targetSet");
45                         if(sourceRows == null)
46                                 throw new ArgumentNullException("sourceRows");
47
48                         for (int i = 0; i < sourceRows.Length; i++) {
49                                 DataRow row = sourceRows[i];
50                                 DataTable sourceTable = row.Table;
51                                 DataTable targetTable = null;
52                                 if (!AdjustSchema(targetSet, sourceTable, missingSchemaAction,ref targetTable)) {
53                                         return;
54                                 }
55                                 if (targetTable != null) {
56                                         checkColumnTypes(targetTable, row.Table);
57                                         MergeRow(targetTable, row, preserveChanges);
58                                 }
59                         }
60                 }
61
62                 // merge a row into a target table.
63                 private static void MergeRow(DataTable targetTable, DataRow row, bool preserveChanges)
64                 {
65                         DataColumnCollection columns = row.Table.Columns;
66                         DataColumn[] primaryKeys = targetTable.PrimaryKey;
67                         DataRow targetRow = null;
68                         DataRowVersion version = DataRowVersion.Default;
69                         if (row.RowState == DataRowState.Deleted)
70                                 version = DataRowVersion.Original;
71
72                         if (primaryKeys != null && primaryKeys.Length > 0) // if there are any primary key.
73                         {
74                                 // initiate an array that has the values of the primary keys.
75                                 object[] keyValues = new object[primaryKeys.Length];
76                                 
77                                 for (int j = 0; j < keyValues.Length; j++)
78                                 {
79                                         keyValues[j] = row[primaryKeys[j].ColumnName, version];
80                                 }
81                         
82                                 // find the row in the target table.
83                                 targetRow = targetTable.Rows.Find(keyValues);
84                         }
85                         // row doesn't exist in target table, or there are no primary keys.
86                         // create new row and copy values from source row to the new row.
87                         if (targetRow == null)
88                         { 
89                                 targetRow = targetTable.NewRow();
90                                 row.CopyValuesToRow(targetRow);
91                                 targetTable.Rows.Add(targetRow);
92                         }
93                         // row exists in target table, and presere changes is false - 
94                         // change the values of the target row to the values of the source row.
95                         else if (!preserveChanges)
96                         {
97                                 row.CopyValuesToRow(targetRow);
98                         }
99
100                 }
101                         
102
103                 // adjust the dataset schema according to the missingschemaaction param
104                 // (relations).
105                 // return false if adjusting fails.
106                 private static bool AdjustSchema(DataSet targetSet, DataSet sourceSet, MissingSchemaAction missingSchemaAction)
107                 {
108                         if (missingSchemaAction == MissingSchemaAction.Add || missingSchemaAction == MissingSchemaAction.AddWithKey) {
109                                 foreach (DataRelation relation in sourceSet.Relations) {
110                                         // TODO : add more precise condition (columns)
111                                         if (!targetSet.Relations.Contains(relation.RelationName)) {
112                                                 DataTable targetTable = targetSet.Tables[relation.ParentColumns[0].Table.TableName];
113                                                 DataColumn[] parentColumns = ResolveColumns(sourceSet,targetTable,relation.ParentColumns);
114                                                 targetTable = targetSet.Tables[relation.ChildColumns[0].Table.TableName];
115                                                 DataColumn[] childColumns = ResolveColumns(sourceSet,targetTable,relation.ChildColumns);
116                                                 if (parentColumns != null && childColumns != null) {
117                                                         DataRelation newRelation = new DataRelation(relation.RelationName,parentColumns,childColumns);
118                                                         targetSet.Relations.Add(newRelation);
119                                                 }
120                                         }
121                                         else {
122                                                 // TODO : should we throw an exeption ?
123                                         }
124                                 }                       
125
126                                 foreach(DataTable sourceTable in sourceSet.Tables) {                            
127                                         DataTable targetTable = targetSet.Tables[sourceTable.TableName];
128
129                                         if (targetTable != null) {
130                                                 foreach(UniqueConstraint uc in sourceTable.Constraints.UniqueConstraints) {
131                                                         // TODO : add more precise condition (columns)
132                                                         if ( !targetTable.Constraints.Contains(uc.ConstraintName) ) {           
133                                                                 DataColumn[] columns = ResolveColumns(sourceSet,targetTable,uc.Columns);
134                                                                 if (columns != null) {
135                                                                         UniqueConstraint newConstraint = new UniqueConstraint(uc.ConstraintName,columns,uc.IsPrimaryKey);
136                                                                         targetTable.Constraints.Add(newConstraint);
137                                                                 }
138                                                         }
139                                                         else {
140                                                                 // TODO : should we throw an exeption ?
141                                                         }
142                                                 }
143
144                                                 foreach(ForeignKeyConstraint fc in sourceTable.Constraints.ForeignKeyConstraints) {
145                                                         // TODO : add more precise condition (columns)
146                                                         if (!targetTable.Constraints.Contains(fc.ConstraintName)) {
147                                                                 DataColumn[] columns = ResolveColumns(sourceSet,targetTable,fc.Columns);
148                                                                 DataTable relatedTable = targetSet.Tables[fc.RelatedTable.TableName];
149                                                                 DataColumn[] relatedColumns = ResolveColumns(sourceSet,relatedTable,fc.RelatedColumns);
150                                                                 if (columns != null && relatedColumns != null) {
151                                                                         ForeignKeyConstraint newConstraint = new ForeignKeyConstraint(fc.ConstraintName,relatedColumns,columns);
152                                                                         targetTable.Constraints.Add(newConstraint);
153                                                                 }
154                                                         }
155                                                         else {
156                                                                 // TODO : should we throw an exeption ?
157                                                         }
158                                                 }
159                                         }
160                                 }
161                         }
162
163                         return true;
164                 }
165
166                 private static DataColumn[] ResolveColumns(DataSet targetSet,DataTable targetTable,DataColumn[] sourceColumns)
167                 {
168                         if (sourceColumns != null && sourceColumns.Length > 0) {
169                                 // TODO : worth to check that all source colums come from the same table
170                                 if (targetTable != null) {
171                                         int i=0;
172                                         DataColumn[] targetColumns = new DataColumn[sourceColumns.Length];
173                                         foreach(DataColumn sourceColumn in sourceColumns) {
174                                                 targetColumns[i++] = targetTable.Columns[sourceColumn.ColumnName];
175                                         }
176                                         return targetColumns;
177                                 }
178                         }
179                         return null;
180                 }
181
182                 
183                 // adjust the table schema according to the missingschemaaction param.
184                 // return false if adjusting fails.
185                 private static bool AdjustSchema(DataSet targetSet, DataTable sourceTable, MissingSchemaAction missingSchemaAction, ref DataTable newTable)
186                 {
187                         string tableName = sourceTable.TableName;
188                         
189                         // if the source table not exists in the target dataset
190                         // we act according to the missingschemaaction param.
191                         int tmp = targetSet.Tables.IndexOf(tableName);
192                         // we need to check if it is equals names
193                         if (tmp != -1 && !targetSet.Tables[tmp].TableName.Equals(tableName))
194                                 tmp = -1;
195                         if (tmp == -1) {
196                                 if (missingSchemaAction == MissingSchemaAction.Ignore) {
197                                         return true;
198                                 }
199                                 if (missingSchemaAction == MissingSchemaAction.Error) {
200                                         throw new ArgumentException("Target DataSet missing definition for "+ tableName + ".");
201                                 }
202                                 
203                                 DataTable cloneTable = (DataTable)sourceTable.Clone();
204                                 targetSet.Tables.Add(cloneTable);
205                                 tableName = cloneTable.TableName;
206                         }                                                               
207                         
208                         DataTable table = targetSet.Tables[tableName];
209                         
210                         for (int i = 0; i < sourceTable.Columns.Count; i++) {
211                                 DataColumn sourceColumn = sourceTable.Columns[i];
212                                 // if a column from the source table doesn't exists in the target table
213                                 // we act according to the missingschemaaction param.
214                                 DataColumn targetColumn = table.Columns[sourceColumn.ColumnName];
215                                 if(targetColumn == null) {
216                                         if (missingSchemaAction == MissingSchemaAction.Ignore) {
217                                                 continue;
218                                         }
219                                         if (missingSchemaAction == MissingSchemaAction.Error) {
220                                                 throw new ArgumentException(("Column '" + sourceColumn.ColumnName + "' does not belong to table Items."));
221                                         }
222                                         
223                                         targetColumn = new DataColumn(sourceColumn.ColumnName, sourceColumn.DataType, sourceColumn.Expression, sourceColumn.ColumnMapping);
224                                         table.Columns.Add(targetColumn);
225                                 }
226
227                                 if (sourceColumn.Unique) {
228                                         try {
229                                                 targetColumn.Unique = sourceColumn.Unique;
230                                         }
231                                         catch(Exception e){
232                                                 Console.WriteLine("targetColumn : {0}   targetTable : {1} ",targetColumn.ColumnName,table.TableName);
233                                                 foreach(DataRow row in table.Rows) {
234                                                         Console.WriteLine(row[targetColumn]);
235                                                 }
236                                                 throw e;
237                                         }
238                                 }
239
240                                 if(sourceColumn.AutoIncrement) {
241                                         targetColumn.AutoIncrement = sourceColumn.AutoIncrement;
242                                         targetColumn.AutoIncrementSeed = sourceColumn.AutoIncrementSeed;
243                                         targetColumn.AutoIncrementStep = sourceColumn.AutoIncrementStep;
244                                 }
245                         }
246
247                         if (!AdjustPrimaryKeys(table, sourceTable)) {
248                                 return false;
249                         }
250
251                         newTable = table;
252                         return true;
253                 }
254                 
255                 // find if there is a valid matching between the targetTable PrimaryKey and the
256                 // sourceTable primatyKey.
257                 // return true if there is a match, else return false and raise a MergeFailedEvent.
258                 private static bool AdjustPrimaryKeys(DataTable targetTable, DataTable sourceTable)
259                 {
260                         // if the length of one of the tables primarykey if 0 - there is nothing to check.
261                         if (sourceTable.PrimaryKey.Length != 0) {
262                                 if (targetTable.PrimaryKey.Length == 0) {
263                                         // if target table has no primary key at all - 
264                                         // import primary key from source table
265                                         DataColumn[] targetColumns = new DataColumn[sourceTable.PrimaryKey.Length];
266                                         
267                                         for(int i=0; i < sourceTable.PrimaryKey.Length; i++){
268                                             DataColumn sourceColumn = sourceTable.PrimaryKey[i];
269                                                 DataColumn targetColumn = targetTable.Columns[sourceColumn.ColumnName];
270
271                                                 if (targetColumn == null) {
272                                                         // is target table has no column corresponding
273                                                         // to source table PK column - merge fails
274                                                         string message = "Column " + sourceColumn.ColumnName + " does not belongs to table " + targetTable.TableName;
275                                                         MergeFailedEventArgs e = new MergeFailedEventArgs(sourceTable, message);
276                                                         targetTable.DataSet.OnMergeFailed(e);
277                                                         return false;
278                                                 }
279                                                 else {
280                                                         targetColumns[i] = targetColumn;
281                                                 }
282                                         }
283                                         targetTable.PrimaryKey = targetColumns;
284                                 }
285                                 else {
286                                         // if target table already has primary key and
287                                         // if the length of primarykey is not equal - merge fails
288                                         if (targetTable.PrimaryKey.Length != sourceTable.PrimaryKey.Length) {
289                                                 string message = "<target>.PrimaryKey and <source>.PrimaryKey have different Length.";
290                                                 MergeFailedEventArgs e = new MergeFailedEventArgs(sourceTable, message);
291                                                 targetTable.DataSet.OnMergeFailed(e);
292                                                 return false;
293                                         }
294                                         else {
295                                                 // we have to see that each primary column in the target table
296                                                 // has a column with the same name in the sourcetable primarykey columns. 
297                                                 bool foundMatch;
298                                                 DataColumn[] targetDataColumns = targetTable.PrimaryKey;
299                                                 DataColumn[] srcDataColumns = sourceTable.PrimaryKey;
300
301                                                 // loop on all primary key columns in the targetTable.
302                                                 for (int i = 0; i < targetDataColumns.Length; i++) {
303                                                         foundMatch = false;
304                                                         DataColumn col = targetDataColumns[i];
305
306                                                         // find if there is a column with the same name in the 
307                                                         // sourceTable primary key columns.
308                                                         for (int j = 0; j < srcDataColumns.Length; j++) {
309                                                                 if (srcDataColumns[j].ColumnName == col.ColumnName) {
310                                                                         foundMatch = true;
311                                                                         break;
312                                                                 }
313                                                         }
314                                                         if (!foundMatch) {
315                                                                 string message = "Mismatch columns in the PrimaryKey : <target>." + col.ColumnName + " versus <source>." + srcDataColumns[i].ColumnName + ".";
316                                                                 MergeFailedEventArgs e = new MergeFailedEventArgs(sourceTable, message);
317                                                                 targetTable.DataSet.OnMergeFailed(e);
318                                                                 return false;
319                                                         }
320                                                         
321                                                 }
322                                         }
323                                 }
324                         }
325
326                         return true;
327                 }
328                 
329                 // fill the data from the source table to the target table
330                 private static void fillData(DataTable targetTable, DataTable sourceTable, bool preserveChanges)
331                 {
332                         for (int i = 0; i < sourceTable.Rows.Count; i++)
333                         {
334                                 DataRow row = sourceTable.Rows[i];
335                                 MergeRow(targetTable, row, preserveChanges);
336                         }
337                 }
338                 
339                 // check tha column from 2 tables that has the same name also has the same datatype.
340                 private static void checkColumnTypes(DataTable targetTable, DataTable sourceTable)
341                 {
342                         for (int i = 0; i < sourceTable.Columns.Count; i++)
343                         {
344                                 DataColumn fromCol = sourceTable.Columns[i];
345                                 DataColumn toCol = targetTable.Columns[fromCol.ColumnName];
346                                 if((toCol != null) && (toCol.DataType != fromCol.DataType))
347                                         throw new DataException("<target>." + fromCol.ColumnName + " and <source>." + fromCol.ColumnName + " have conflicting properties: DataType property mismatch.");
348                         }
349                 }
350         }
351 }