Fixes for broken RO check
[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                         if (sourceSet == targetSet)
41                                 return;
42
43                         bool prevEC = targetSet.EnforceConstraints;
44                         targetSet.EnforceConstraints = false;
45
46                         foreach (DataTable t in sourceSet.Tables)
47                                 MergeManager.Merge(targetSet, t, preserveChanges, missingSchemaAction);
48
49                         AdjustSchemaRelations (targetSet, sourceSet, missingSchemaAction);
50                         targetSet.EnforceConstraints = prevEC;
51                 }
52
53                 internal static void Merge(DataSet targetSet, DataTable sourceTable, bool preserveChanges, MissingSchemaAction missingSchemaAction)
54                 {
55                         if(targetSet == null)
56                                 throw new ArgumentNullException("targetSet");
57                         if(sourceTable == null)
58                                 throw new ArgumentNullException("sourceTable");
59                         if (sourceTable.DataSet == targetSet)
60                                 return;
61
62                         bool savedEnfoceConstraints = targetSet.EnforceConstraints;
63                         targetSet.EnforceConstraints = false;
64
65                         DataTable targetTable = null;
66                         if (!AdjustSchema(targetSet, sourceTable, missingSchemaAction,ref targetTable))
67                                 return;
68                         if (targetTable != null)
69                                 fillData(targetTable, sourceTable, preserveChanges);
70                         targetSet.EnforceConstraints = savedEnfoceConstraints;
71                 }
72
73                 internal static void Merge (DataTable targetTable, 
74                                             DataTable sourceTable, 
75                                             bool preserveChanges, 
76                                             MissingSchemaAction missingSchemaAction)
77                 {
78                         if(targetTable== null)
79                                 throw new ArgumentNullException("targetTable");
80                         if(sourceTable == null)
81                                 throw new ArgumentNullException("sourceTable");
82                         if (sourceTable == targetTable)
83                                 return;
84
85                         bool savedEnforceConstraints = targetTable.EnforceConstraints;
86                         targetTable.EnforceConstraints = false;
87
88                         if (!AdjustSchema(targetTable, sourceTable, missingSchemaAction))
89                                 return;
90
91                         fillData(targetTable, sourceTable, preserveChanges);
92                         targetTable.EnforceConstraints = savedEnforceConstraints;
93                 }
94
95                 internal static void Merge(DataSet targetSet, DataRow[] sourceRows, bool preserveChanges, MissingSchemaAction missingSchemaAction)
96                 {
97                         if(targetSet == null)
98                                 throw new ArgumentNullException("targetSet");
99                         if(sourceRows == null)
100                                 throw new ArgumentNullException("sourceRows");
101
102                         bool savedEnfoceConstraints = targetSet.EnforceConstraints;
103                         targetSet.EnforceConstraints = false;
104
105                         ArrayList targetTables = new ArrayList();
106                         for (int i = 0; i < sourceRows.Length; i++) {
107                                 DataRow row = sourceRows[i];
108                                 DataTable sourceTable = row.Table;
109                                 DataTable targetTable = null;
110                                 if (!AdjustSchema(targetSet, sourceTable, missingSchemaAction,ref targetTable))
111                                         return;
112                                 if (targetTable != null) {
113                                         MergeRow(targetTable, row, preserveChanges);
114                                         if (!(targetTables.IndexOf(targetTable) >= 0))
115                                                 targetTables.Add(targetTable);
116                                 }
117                         }
118
119                         targetSet.EnforceConstraints = savedEnfoceConstraints;
120                 }
121
122                 // merge a row into a target table.
123                 private static void MergeRow(DataTable targetTable, DataRow row, bool preserveChanges)
124                 {
125                         DataColumn[] primaryKeys = targetTable.PrimaryKey;
126                         DataRow targetRow = null;
127                         DataRowVersion version = DataRowVersion.Default;
128                         if (row.RowState == DataRowState.Deleted)
129                                 version = DataRowVersion.Original;
130
131                         if (primaryKeys != null && primaryKeys.Length > 0) // if there are any primary key.
132                         {
133                                 // initiate an array that has the values of the primary keys.
134                                 object[] keyValues = new object[primaryKeys.Length];
135                                 
136                                 for (int j = 0; j < keyValues.Length; j++)
137                                 {
138                                         keyValues[j] = row[primaryKeys[j].ColumnName, version];
139                                 }
140                         
141                                 // find the row in the target table.
142                                 targetRow = targetTable.Rows.Find(keyValues, DataViewRowState.OriginalRows);
143                                 if (targetRow == null)
144                                         targetRow = targetTable.Rows.Find(keyValues);
145                         }
146                         // row doesn't exist in target table, or there are no primary keys.
147                         // create new row and copy values from source row to the new row.
148                         if (targetRow == null)
149                         { 
150                                 DataRow newRow = targetTable.NewNotInitializedRow();
151                                 // Don't check for ReadOnly, when cloning data to new uninitialized row.
152                                 row.CopyValuesToRow(newRow, false);
153                                 targetTable.Rows.AddInternal (newRow);
154                         }
155                         // row exists in target table, and presere changes is false - 
156                         // change the values of the target row to the values of the source row.
157                         else
158                         {
159                                 row.MergeValuesToRow(targetRow, preserveChanges);
160                         }
161                 }
162                         
163                 private static bool AdjustSchemaRelations (DataSet targetSet, DataSet sourceSet, MissingSchemaAction missingSchemaAction)
164                 {
165                         if (missingSchemaAction == MissingSchemaAction.Ignore)
166                                 return true;
167
168                         foreach(DataTable sourceTable in sourceSet.Tables) {
169
170                                 DataTable targetTable = targetSet.Tables[sourceTable.TableName];
171                                 if (targetTable == null)
172                                         continue;
173
174                                 foreach (Constraint constraint in sourceTable.Constraints) {
175
176                                         Constraint targetConstraint = null;
177
178                                         string constraintName = constraint.ConstraintName;
179                                         if (targetTable.Constraints.Contains (constraintName))
180                                                 constraintName = "";
181
182                                         UniqueConstraint uc = constraint as UniqueConstraint;
183                                         // PrimaryKey is already taken care of while merging the table
184                                         // ForeignKey constraint takes care of Parent Unique Constraints
185                                         if (uc != null) {
186                                                 if (uc.IsPrimaryKey || uc.ChildConstraint != null)
187                                                         continue;
188                                                 DataColumn[] columns = ResolveColumns (targetTable, uc.Columns);
189                                                 targetConstraint = new UniqueConstraint (constraintName, columns, false);
190                                         }
191
192                                         ForeignKeyConstraint fc = constraint as ForeignKeyConstraint;
193                                         if (fc != null) {
194                                                 DataColumn[] columns = ResolveColumns (targetTable, fc.Columns);
195                                                 DataColumn[] relatedColumns = ResolveColumns (targetSet.Tables [fc.RelatedTable.TableName],
196                                                                                         fc.RelatedColumns);
197                                                 targetConstraint = new ForeignKeyConstraint (constraintName, relatedColumns, columns);
198                                         }
199
200                                         bool dupConstraintFound = false;
201                                         foreach (Constraint cons in targetTable.Constraints) {
202                                         if (!targetConstraint.Equals (cons))
203                                                 continue;
204                                         dupConstraintFound = true;
205                                         break;
206                                         }
207
208                                         // If equivalent-constraint already exists, then just do nothing
209                                         if (dupConstraintFound)
210                                                 continue;
211
212                                         if (missingSchemaAction == MissingSchemaAction.Error)
213                                                 throw new DataException ("Target DataSet missing " + targetConstraint.GetType () +
214                                                                 targetConstraint.ConstraintName);
215                                         else
216                                                 targetTable.Constraints.Add (targetConstraint);
217                                 }
218                         }
219
220                         foreach (DataRelation relation in sourceSet.Relations) {
221                                 DataRelation targetRelation = targetSet.Relations [relation.RelationName];
222                                 if (targetRelation == null) {
223                                         if (missingSchemaAction == MissingSchemaAction.Error)
224                                                 throw new ArgumentException ("Target DataSet mising definition for " +
225                                                                 relation.RelationName);
226
227                                         DataColumn[] parentColumns = ResolveColumns (targetSet.Tables [relation.ParentTable.TableName],
228                                                         relation.ParentColumns);
229                                         DataColumn[] childColumns = ResolveColumns (targetSet.Tables [relation.ChildTable.TableName],
230                                                         relation.ChildColumns);
231                                         targetRelation = targetSet.Relations.Add (relation.RelationName, parentColumns,
232                                                         childColumns, relation.createConstraints);
233                                         targetRelation.Nested = relation.Nested;
234                                 } else if (!CompareColumnArrays (relation.ParentColumns, targetRelation.ParentColumns) ||
235                                                 !CompareColumnArrays (relation.ChildColumns, targetRelation.ChildColumns)) {
236                                         RaiseMergeFailedEvent (null, "Relation " + relation.RelationName +
237                                                 " cannot be merged, because keys have mismatch columns.");
238                                 }
239                         }
240
241                         return true;
242                 }
243
244                 private static DataColumn[] ResolveColumns(DataTable targetTable, DataColumn[] sourceColumns)
245                 {
246                         if (sourceColumns != null && sourceColumns.Length > 0) {
247                                 // lets just assume that all columns are from the Same table
248                                 if (targetTable != null) {
249                                         int i=0;
250                                         DataColumn[] targetColumns = new DataColumn[sourceColumns.Length];
251
252                                         DataColumn tmpCol ;
253                                         foreach(DataColumn sourceColumn in sourceColumns) {
254                                                 tmpCol = targetTable.Columns[sourceColumn.ColumnName];
255                                                 if (tmpCol == null)
256                                                         throw new DataException ("Column " + sourceColumn.ColumnName  + 
257                                                                         " does not belong to table " + targetTable.TableName);
258                                                 targetColumns [i++] = tmpCol;
259                                         }
260                                         return targetColumns;
261                                 }
262                         }
263                         return null;
264                 }
265
266
267                 // adjust the table schema according to the missingschemaaction param.
268                 // return false if adjusting fails.
269                 private static bool AdjustSchema(DataSet targetSet, DataTable sourceTable, MissingSchemaAction missingSchemaAction, ref DataTable newTable)
270                 {
271                         // if the source table not exists in the target dataset
272                         // we act according to the missingschemaaction param.
273                         DataTable targetTable = targetSet.Tables [sourceTable.TableName];
274                         if (targetTable == null) {
275                                 if (missingSchemaAction == MissingSchemaAction.Ignore)
276                                         return true;
277
278                                 if (missingSchemaAction == MissingSchemaAction.Error)
279                                         throw new ArgumentException ("Target DataSet missing definition for " +
280                                                         sourceTable.TableName + ".");
281
282                                 targetTable = (DataTable)sourceTable.Clone();
283                                 targetSet.Tables.Add(targetTable);
284                         }
285
286                         AdjustSchema (targetTable, sourceTable, missingSchemaAction);
287
288                         newTable = targetTable;
289                         return true;
290                 }
291
292
293                 private static bool AdjustSchema(DataTable targetTable, DataTable sourceTable, MissingSchemaAction missingSchemaAction)
294                 {
295                         if (missingSchemaAction == MissingSchemaAction.Ignore)
296                                 return true;
297
298                         for (int i = 0; i < sourceTable.Columns.Count; i++) {
299                                 DataColumn sourceColumn = sourceTable.Columns[i];
300                                 // if a column from the source table doesn't exists in the target table
301                                 // we act according to the missingschemaaction param.
302                                 DataColumn targetColumn = targetTable.Columns [sourceColumn.ColumnName];
303                                 if(targetColumn == null) {
304                                         if (missingSchemaAction == MissingSchemaAction.Error)
305                                                 throw new DataException ("Target table " + targetTable.TableName +
306                                                                 " missing definition for column " + sourceColumn.ColumnName);
307                                         
308                                         targetColumn = new DataColumn(sourceColumn.ColumnName, sourceColumn.DataType, 
309                                                                 sourceColumn.Expression, sourceColumn.ColumnMapping);
310                                         targetTable.Columns.Add(targetColumn);
311                                 }
312
313                                 if(sourceColumn.AutoIncrement) {
314                                         targetColumn.AutoIncrement = sourceColumn.AutoIncrement;
315                                         targetColumn.AutoIncrementSeed = sourceColumn.AutoIncrementSeed;
316                                         targetColumn.AutoIncrementStep = sourceColumn.AutoIncrementStep;
317                                 }
318                         }
319
320                         if (!AdjustPrimaryKeys(targetTable, sourceTable))
321                                 return false;
322
323                         checkColumnTypes (targetTable, sourceTable);
324
325                         return true;
326                 }
327         
328         
329                 // find if there is a valid matching between the targetTable PrimaryKey and the
330                 // sourceTable primatyKey.
331                 // return true if there is a match, else return false and raise a MergeFailedEvent.
332                 private static bool AdjustPrimaryKeys(DataTable targetTable, DataTable sourceTable)
333                 {
334                         if (sourceTable.PrimaryKey.Length == 0)
335                                 return true;
336
337                         // If targetTable does not have a PrimaryKey, just import the sourceTable PrimaryKey
338                         if (targetTable.PrimaryKey.Length == 0) {
339                                 DataColumn[] targetColumns = ResolveColumns (targetTable, sourceTable.PrimaryKey);
340                                 targetTable.PrimaryKey = targetColumns;
341                                 return true;
342                         }
343                         
344                         // If both the tables have a primary key, verify that they are equivalent.
345                         // raise a MergeFailedEvent if the keys are not equivalent
346                         if (targetTable.PrimaryKey.Length != sourceTable.PrimaryKey.Length) {
347                                 RaiseMergeFailedEvent (targetTable, "<target>.PrimaryKey and <source>.PrimaryKey have different Length.");
348                                 return false;
349                         }
350
351                         for (int i=0; i < targetTable.PrimaryKey.Length; ++i) {
352                                 if (targetTable.PrimaryKey [i].ColumnName.Equals (sourceTable.PrimaryKey [i].ColumnName))
353                                         continue;
354                                 RaiseMergeFailedEvent (targetTable, "Mismatch columns in the PrimaryKey : <target>." + 
355                                         targetTable.PrimaryKey [i].ColumnName + " versus <source>." + sourceTable.PrimaryKey [i].ColumnName);
356                                 return false;
357                         }
358                         return true;
359                 }
360
361                 // fill the data from the source table to the target table
362                 private static void fillData(DataTable targetTable, DataTable sourceTable, bool preserveChanges)
363                 {
364                         for (int i = 0; i < sourceTable.Rows.Count; i++)
365                         {
366                                 DataRow row = sourceTable.Rows[i];
367                                 MergeRow(targetTable, row, preserveChanges);
368                         }
369                 }
370
371                 // check tha column from 2 tables that has the same name also has the same datatype.
372                 private static void checkColumnTypes(DataTable targetTable, DataTable sourceTable)
373                 {
374                         for (int i = 0; i < sourceTable.Columns.Count; i++)
375                         {
376                                 DataColumn fromCol = sourceTable.Columns[i];
377                                 DataColumn toCol = targetTable.Columns[fromCol.ColumnName];
378                                 if (toCol == null)
379                                         continue;
380                                 if (toCol.DataTypeMatches (fromCol))
381                                         continue;
382                                 throw new DataException("<target>." + fromCol.ColumnName + " and <source>." + 
383                                                 fromCol.ColumnName + " have conflicting properties: DataType " + 
384                                                 " property mismatch.");
385                         }
386                 }
387
388                 private static bool CompareColumnArrays (DataColumn[] arr1, DataColumn[] arr2)
389                 {
390                         if (arr1.Length != arr2.Length)
391                                 return false;
392
393                         for (int i=0; i < arr1.Length; ++i)
394                                 if (!arr1 [i].ColumnName.Equals (arr2 [i].ColumnName))
395                                         return false;
396                         return true;
397                 }
398
399                 private static void RaiseMergeFailedEvent (DataTable targetTable, string errMsg)
400                 {
401                         MergeFailedEventArgs args = new MergeFailedEventArgs (targetTable, errMsg);
402                         if (targetTable.DataSet != null)
403                                 targetTable.DataSet.OnMergeFailed (args);
404                 }
405         }
406 }