merge r67228-r67235, r67237, r67251 and r67256-67259 to trunk (they are
[mono.git] / mcs / class / System.Data / System.Data / MergeManager.cs
1
2 //
3 // Copyright (C) 2004 Novell, Inc (http://www.novell.com)
4 //
5 // Permission is hereby granted, free of charge, to any person obtaining
6 // a copy of this software and associated documentation files (the
7 // "Software"), to deal in the Software without restriction, including
8 // without limitation the rights to use, copy, modify, merge, publish,
9 // distribute, sublicense, and/or sell copies of the Software, and to
10 // permit persons to whom the Software is furnished to do so, subject to
11 // the following conditions:
12 // 
13 // The above copyright notice and this permission notice shall be
14 // included in all copies or substantial portions of the Software.
15 // 
16 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23 //
24 using System;
25 using System.Collections;
26
27 namespace System.Data
28 {
29         /// <summary>
30         /// Summary description for MergeManager.
31         /// </summary>
32         internal class MergeManager
33         {
34                 internal static void Merge(DataSet targetSet, DataSet sourceSet, bool preserveChanges, MissingSchemaAction missingSchemaAction)
35                 {
36                         if(targetSet == null)
37                                 throw new ArgumentNullException("targetSet");
38                         if(sourceSet == null)
39                                 throw new ArgumentNullException("sourceSet");
40
41                         bool prevEC = targetSet.EnforceConstraints;
42                         targetSet.EnforceConstraints = false;
43
44                         foreach (DataTable t in sourceSet.Tables)
45                                 MergeManager.Merge(targetSet, t, preserveChanges, missingSchemaAction);
46
47                         AdjustSchemaRelations (targetSet, sourceSet, missingSchemaAction);
48                         targetSet.EnforceConstraints = prevEC;
49                 }
50
51                 internal static void Merge(DataSet targetSet, DataTable sourceTable, bool preserveChanges, MissingSchemaAction missingSchemaAction)
52                 {
53                         if(targetSet == null)
54                                 throw new ArgumentNullException("targetSet");
55                         if(sourceTable == null)
56                                 throw new ArgumentNullException("sourceTable");
57
58                         bool savedEnfoceConstraints = targetSet.EnforceConstraints;
59                         targetSet.EnforceConstraints = false;
60
61                         DataTable targetTable = null;
62                         if (!AdjustSchema(targetSet, sourceTable, missingSchemaAction,ref targetTable))
63                                 return;
64                         if (targetTable != null)
65                                 fillData(targetTable, sourceTable, preserveChanges);
66                         targetSet.EnforceConstraints = savedEnfoceConstraints;
67                 }
68
69                 internal static void Merge (DataTable targetTable, 
70                                             DataTable sourceTable, 
71                                             bool preserveChanges, 
72                                             MissingSchemaAction missingSchemaAction)
73                 {
74                         if(targetTable== null)
75                                 throw new ArgumentNullException("targetTable");
76                         if(sourceTable == null)
77                                 throw new ArgumentNullException("sourceTable");
78
79                         bool savedEnforceConstraints = targetTable.EnforceConstraints;
80                         targetTable.EnforceConstraints = false;
81
82                         if (!AdjustSchema(targetTable, sourceTable, missingSchemaAction))
83                                 return;
84
85                         fillData(targetTable, sourceTable, preserveChanges);
86                         targetTable.EnforceConstraints = savedEnforceConstraints;
87                 }
88
89                 internal static void Merge(DataSet targetSet, DataRow[] sourceRows, bool preserveChanges, MissingSchemaAction missingSchemaAction)
90                 {
91                         if(targetSet == null)
92                                 throw new ArgumentNullException("targetSet");
93                         if(sourceRows == null)
94                                 throw new ArgumentNullException("sourceRows");
95
96                         bool savedEnfoceConstraints = targetSet.EnforceConstraints;
97                         targetSet.EnforceConstraints = false;
98
99                         ArrayList targetTables = new ArrayList();
100                         for (int i = 0; i < sourceRows.Length; i++) {
101                                 DataRow row = sourceRows[i];
102                                 DataTable sourceTable = row.Table;
103                                 DataTable targetTable = null;
104                                 if (!AdjustSchema(targetSet, sourceTable, missingSchemaAction,ref targetTable))
105                                         return;
106                                 if (targetTable != null) {
107                                         MergeRow(targetTable, row, preserveChanges);
108                                         if (!(targetTables.IndexOf(targetTable) >= 0))
109                                                 targetTables.Add(targetTable);
110                                 }
111                         }
112
113                         targetSet.EnforceConstraints = savedEnfoceConstraints;
114                 }
115
116                 // merge a row into a target table.
117                 private static void MergeRow(DataTable targetTable, DataRow row, bool preserveChanges)
118                 {
119                         DataColumnCollection columns = row.Table.Columns;
120                         DataColumn[] primaryKeys = targetTable.PrimaryKey;
121                         DataRow targetRow = null;
122                         DataRowVersion version = DataRowVersion.Default;
123                         if (row.RowState == DataRowState.Deleted)
124                                 version = DataRowVersion.Original;
125
126                         if (primaryKeys != null && primaryKeys.Length > 0) // if there are any primary key.
127                         {
128                                 // initiate an array that has the values of the primary keys.
129                                 object[] keyValues = new object[primaryKeys.Length];
130                                 
131                                 for (int j = 0; j < keyValues.Length; j++)
132                                 {
133                                         keyValues[j] = row[primaryKeys[j].ColumnName, version];
134                                 }
135                         
136                                 // find the row in the target table.
137                                 targetRow = targetTable.Rows.Find(keyValues, DataViewRowState.OriginalRows);
138                                 if (targetRow == null)
139                                         targetRow = targetTable.Rows.Find(keyValues);
140                         }
141                         // row doesn't exist in target table, or there are no primary keys.
142                         // create new row and copy values from source row to the new row.
143                         if (targetRow == null)
144                         { 
145                                 DataRow newRow = targetTable.NewNotInitializedRow();
146                                 row.CopyValuesToRow(newRow);
147                                 targetTable.Rows.AddInternal (newRow);
148                         }
149                         // row exists in target table, and presere changes is false - 
150                         // change the values of the target row to the values of the source row.
151                         else
152                         {
153                                 row.MergeValuesToRow(targetRow, preserveChanges);
154                         }
155                 }
156                         
157                 private static bool AdjustSchemaRelations (DataSet targetSet, DataSet sourceSet, MissingSchemaAction missingSchemaAction)
158                 {
159                         if (missingSchemaAction == MissingSchemaAction.Ignore)
160                                 return true;
161
162                         foreach(DataTable sourceTable in sourceSet.Tables) {
163
164                                 DataTable targetTable = targetSet.Tables[sourceTable.TableName];
165                                 if (targetTable == null)
166                                         continue;
167
168                                 foreach (Constraint constraint in sourceTable.Constraints) {
169
170                                         Constraint targetConstraint = null;
171
172                                         string constraintName = constraint.ConstraintName;
173                                         if (targetTable.Constraints.Contains (constraintName))
174                                                 constraintName = "";
175
176                                         UniqueConstraint uc = constraint as UniqueConstraint;
177                                         // PrimaryKey is already taken care of while merging the table
178                                         // ForeignKey constraint takes care of Parent Unique Constraints
179                                         if (uc != null) {
180                                                 if (uc.IsPrimaryKey || uc.ChildConstraint != null)
181                                                         continue;
182                                                 DataColumn[] columns = ResolveColumns (targetTable, uc.Columns);
183                                                 targetConstraint = new UniqueConstraint (constraintName, columns, false);
184                                         }
185
186                                         ForeignKeyConstraint fc = constraint as ForeignKeyConstraint;
187                                         if (fc != null) {
188                                                 DataColumn[] columns = ResolveColumns (targetTable, fc.Columns);
189                                                 DataColumn[] relatedColumns = ResolveColumns (targetSet.Tables [fc.RelatedTable.TableName],
190                                                                                         fc.RelatedColumns);
191                                                 targetConstraint = new ForeignKeyConstraint (constraintName, relatedColumns, columns);
192                                         }
193
194                                         bool dupConstraintFound = false;
195                                         foreach (Constraint cons in targetTable.Constraints) {
196                                         if (!targetConstraint.Equals (cons))
197                                                 continue;
198                                         dupConstraintFound = true;
199                                         break;
200                                         }
201
202                                         // If equivalent-constraint already exists, then just do nothing
203                                         if (dupConstraintFound)
204                                                 continue;
205
206                                         if (missingSchemaAction == MissingSchemaAction.Error)
207                                                 throw new DataException ("Target DataSet missing " + targetConstraint.GetType () +
208                                                                 targetConstraint.ConstraintName);
209                                         else
210                                                 targetTable.Constraints.Add (targetConstraint);
211                                 }
212                         }
213
214                         foreach (DataRelation relation in sourceSet.Relations) {
215                                 DataRelation targetRelation = targetSet.Relations [relation.RelationName];
216                                 if (targetRelation == null) {
217                                         if (missingSchemaAction == MissingSchemaAction.Error)
218                                                 throw new ArgumentException ("Target DataSet mising definition for " +
219                                                                 relation.RelationName);
220
221                                         DataColumn[] parentColumns = ResolveColumns (targetSet.Tables [relation.ParentTable.TableName],
222                                                         relation.ParentColumns);
223                                         DataColumn[] childColumns = ResolveColumns (targetSet.Tables [relation.ChildTable.TableName],
224                                                         relation.ChildColumns);
225                                         targetRelation = targetSet.Relations.Add (relation.RelationName, parentColumns,
226                                                         childColumns, false);
227                                         targetRelation.Nested = relation.Nested;
228                                 } else if (!CompareColumnArrays (relation.ParentColumns, targetRelation.ParentColumns) ||
229                                                 !CompareColumnArrays (relation.ChildColumns, targetRelation.ChildColumns)) {
230                                         RaiseMergeFailedEvent (null, "Relation " + relation.RelationName +
231                                                 " cannot be merged, because keys have mismatch columns.");
232                                 }
233                         }
234
235                         return true;
236                 }
237
238                 private static DataColumn[] ResolveColumns(DataTable targetTable, DataColumn[] sourceColumns)
239                 {
240                         if (sourceColumns != null && sourceColumns.Length > 0) {
241                                 // lets just assume that all columns are from the Same table
242                                 if (targetTable != null) {
243                                         int i=0;
244                                         DataColumn[] targetColumns = new DataColumn[sourceColumns.Length];
245
246                                         DataColumn tmpCol ;
247                                         foreach(DataColumn sourceColumn in sourceColumns) {
248                                                 tmpCol = targetTable.Columns[sourceColumn.ColumnName];
249                                                 if (tmpCol == null)
250                                                         throw new DataException ("Column " + sourceColumn.ColumnName  + 
251                                                                         " does not belong to table " + targetTable.TableName);
252                                                 targetColumns [i++] = tmpCol;
253                                         }
254                                         return targetColumns;
255                                 }
256                         }
257                         return null;
258                 }
259
260
261                 // adjust the table schema according to the missingschemaaction param.
262                 // return false if adjusting fails.
263                 private static bool AdjustSchema(DataSet targetSet, DataTable sourceTable, MissingSchemaAction missingSchemaAction, ref DataTable newTable)
264                 {
265                         // if the source table not exists in the target dataset
266                         // we act according to the missingschemaaction param.
267                         DataTable targetTable = targetSet.Tables [sourceTable.TableName];
268                         if (targetTable == null) {
269                                 if (missingSchemaAction == MissingSchemaAction.Ignore)
270                                         return true;
271
272                                 if (missingSchemaAction == MissingSchemaAction.Error)
273                                         throw new ArgumentException ("Target DataSet missing definition for " +
274                                                         sourceTable.TableName + ".");
275
276                                 targetTable = (DataTable)sourceTable.Clone();
277                                 targetSet.Tables.Add(targetTable);
278                         }
279
280                         AdjustSchema (targetTable, sourceTable, missingSchemaAction);
281
282                         newTable = targetTable;
283                         return true;
284                 }
285
286
287                 private static bool AdjustSchema(DataTable targetTable, DataTable sourceTable, MissingSchemaAction missingSchemaAction)
288                 {
289                         if (missingSchemaAction == MissingSchemaAction.Ignore)
290                                 return true;
291
292                         for (int i = 0; i < sourceTable.Columns.Count; i++) {
293                                 DataColumn sourceColumn = sourceTable.Columns[i];
294                                 // if a column from the source table doesn't exists in the target table
295                                 // we act according to the missingschemaaction param.
296                                 DataColumn targetColumn = targetTable.Columns [sourceColumn.ColumnName];
297                                 if(targetColumn == null) {
298                                         if (missingSchemaAction == MissingSchemaAction.Error)
299                                                 throw new DataException ("Target table " + targetTable.TableName +
300                                                                 " missing definition for column " + sourceColumn.ColumnName);
301                                         
302                                         targetColumn = new DataColumn(sourceColumn.ColumnName, sourceColumn.DataType, 
303                                                                 sourceColumn.Expression, sourceColumn.ColumnMapping);
304                                         targetTable.Columns.Add(targetColumn);
305                                 }
306
307                                 if(sourceColumn.AutoIncrement) {
308                                         targetColumn.AutoIncrement = sourceColumn.AutoIncrement;
309                                         targetColumn.AutoIncrementSeed = sourceColumn.AutoIncrementSeed;
310                                         targetColumn.AutoIncrementStep = sourceColumn.AutoIncrementStep;
311                                 }
312                         }
313
314                         if (!AdjustPrimaryKeys(targetTable, sourceTable))
315                                 return false;
316
317                         checkColumnTypes (targetTable, sourceTable);
318
319                         return true;
320                 }
321         
322         
323                 // find if there is a valid matching between the targetTable PrimaryKey and the
324                 // sourceTable primatyKey.
325                 // return true if there is a match, else return false and raise a MergeFailedEvent.
326                 private static bool AdjustPrimaryKeys(DataTable targetTable, DataTable sourceTable)
327                 {
328                         if (sourceTable.PrimaryKey.Length == 0)
329                                 return true;
330
331                         // If targetTable does not have a PrimaryKey, just import the sourceTable PrimaryKey
332                         if (targetTable.PrimaryKey.Length == 0) {
333                                 DataColumn[] targetColumns = ResolveColumns (targetTable, sourceTable.PrimaryKey);
334                                 targetTable.PrimaryKey = targetColumns;
335                                 return true;
336                         }
337                         
338                         // If both the tables have a primary key, verify that they are equivalent.
339                         // raise a MergeFailedEvent if the keys are not equivalent
340                         if (targetTable.PrimaryKey.Length != sourceTable.PrimaryKey.Length) {
341                                 RaiseMergeFailedEvent (targetTable, "<target>.PrimaryKey and <source>.PrimaryKey have different Length.");
342                                 return false;
343                         }
344
345                         for (int i=0; i < targetTable.PrimaryKey.Length; ++i) {
346                                 if (targetTable.PrimaryKey [i].ColumnName.Equals (sourceTable.PrimaryKey [i].ColumnName))
347                                         continue;
348                                 RaiseMergeFailedEvent (targetTable, "Mismatch columns in the PrimaryKey : <target>." + 
349                                         targetTable.PrimaryKey [i].ColumnName + " versus <source>." + sourceTable.PrimaryKey [i].ColumnName);
350                                 return false;
351                         }
352                         return true;
353                 }
354
355                 // fill the data from the source table to the target table
356                 private static void fillData(DataTable targetTable, DataTable sourceTable, bool preserveChanges)
357                 {
358                         for (int i = 0; i < sourceTable.Rows.Count; i++)
359                         {
360                                 DataRow row = sourceTable.Rows[i];
361                                 MergeRow(targetTable, row, preserveChanges);
362                         }
363                 }
364
365                 // check tha column from 2 tables that has the same name also has the same datatype.
366                 private static void checkColumnTypes(DataTable targetTable, DataTable sourceTable)
367                 {
368                         for (int i = 0; i < sourceTable.Columns.Count; i++)
369                         {
370                                 DataColumn fromCol = sourceTable.Columns[i];
371                                 DataColumn toCol = targetTable.Columns[fromCol.ColumnName];
372                                 if (toCol == null)
373                                         continue;
374                                 if (toCol.DataTypeMatches (fromCol))
375                                         continue;
376                                 throw new DataException("<target>." + fromCol.ColumnName + " and <source>." + 
377                                                 fromCol.ColumnName + " have conflicting properties: DataType " + 
378                                                 " property mismatch.");
379                         }
380                 }
381
382                 private static bool CompareColumnArrays (DataColumn[] arr1, DataColumn[] arr2)
383                 {
384                         if (arr1.Length != arr2.Length)
385                                 return false;
386
387                         for (int i=0; i < arr1.Length; ++i)
388                                 if (!arr1 [i].ColumnName.Equals (arr2 [i].ColumnName))
389                                         return false;
390                         return true;
391                 }
392
393                 private static void RaiseMergeFailedEvent (DataTable targetTable, string errMsg)
394                 {
395                         MergeFailedEventArgs args = new MergeFailedEventArgs (targetTable, errMsg);
396                         if (targetTable.DataSet != null)
397                                 targetTable.DataSet.OnMergeFailed (args);
398                 }
399         }
400 }