**** Merged from MCS ****
[mono-project.git] / mcs / class / System.Data / System.Data / MergeManager.cs
blob720653e32c0374611f072c7ff1c053b6850327fa
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.
24 using System;
26 namespace System.Data
28 /// <summary>
29 /// Summary description for MergeManager.
30 /// </summary>
31 internal class MergeManager
33 internal static void Merge(DataSet targetSet, DataSet sourceSet, bool preserveChanges, MissingSchemaAction missingSchemaAction)
35 if(targetSet == null)
36 throw new ArgumentNullException("targetSet");
37 if(sourceSet == null)
38 throw new ArgumentNullException("sourceSet");
40 foreach (DataTable t in sourceSet.Tables)
41 MergeManager.Merge(targetSet, t, preserveChanges, missingSchemaAction);
43 AdjustSchema(targetSet,sourceSet,missingSchemaAction);
47 internal static void Merge(DataSet targetSet, DataTable sourceTable, bool preserveChanges, MissingSchemaAction missingSchemaAction)
49 if(targetSet == null)
50 throw new ArgumentNullException("targetSet");
51 if(sourceTable == null)
52 throw new ArgumentNullException("sourceTable");
54 DataTable targetTable = null;
55 if (!AdjustSchema(targetSet, sourceTable, missingSchemaAction,ref targetTable)) {
56 return;
58 if (targetTable != null) {
59 checkColumnTypes(targetTable, sourceTable); // check that the colums datatype is the same
60 fillData(targetTable, sourceTable, preserveChanges);
64 internal static void Merge(DataSet targetSet, DataRow[] sourceRows, bool preserveChanges, MissingSchemaAction missingSchemaAction)
66 if(targetSet == null)
67 throw new ArgumentNullException("targetSet");
68 if(sourceRows == null)
69 throw new ArgumentNullException("sourceRows");
71 for (int i = 0; i < sourceRows.Length; i++) {
72 DataRow row = sourceRows[i];
73 DataTable sourceTable = row.Table;
74 DataTable targetTable = null;
75 if (!AdjustSchema(targetSet, sourceTable, missingSchemaAction,ref targetTable)) {
76 return;
78 if (targetTable != null) {
79 checkColumnTypes(targetTable, row.Table);
80 MergeRow(targetTable, row, preserveChanges);
85 // merge a row into a target table.
86 private static void MergeRow(DataTable targetTable, DataRow row, bool preserveChanges)
88 DataColumnCollection columns = row.Table.Columns;
89 DataColumn[] primaryKeys = targetTable.PrimaryKey;
90 DataRow targetRow = null;
91 DataRowVersion version = DataRowVersion.Default;
92 if (row.RowState == DataRowState.Deleted)
93 version = DataRowVersion.Original;
95 if (primaryKeys != null && primaryKeys.Length > 0) // if there are any primary key.
97 // initiate an array that has the values of the primary keys.
98 object[] keyValues = new object[primaryKeys.Length];
100 for (int j = 0; j < keyValues.Length; j++)
102 keyValues[j] = row[primaryKeys[j].ColumnName, version];
105 // find the row in the target table.
106 targetRow = targetTable.Rows.Find(keyValues);
108 // row doesn't exist in target table, or there are no primary keys.
109 // create new row and copy values from source row to the new row.
110 if (targetRow == null)
112 targetRow = targetTable.NewRow();
113 row.CopyValuesToRow(targetRow);
114 targetTable.Rows.Add(targetRow);
116 // row exists in target table, and presere changes is false -
117 // change the values of the target row to the values of the source row.
118 else if (!preserveChanges)
120 row.CopyValuesToRow(targetRow);
126 // adjust the dataset schema according to the missingschemaaction param
127 // (relations).
128 // return false if adjusting fails.
129 private static bool AdjustSchema(DataSet targetSet, DataSet sourceSet, MissingSchemaAction missingSchemaAction)
131 if (missingSchemaAction == MissingSchemaAction.Add || missingSchemaAction == MissingSchemaAction.AddWithKey) {
132 foreach (DataRelation relation in sourceSet.Relations) {
133 // TODO : add more precise condition (columns)
134 if (!targetSet.Relations.Contains(relation.RelationName)) {
135 DataTable targetTable = targetSet.Tables[relation.ParentColumns[0].Table.TableName];
136 DataColumn[] parentColumns = ResolveColumns(sourceSet,targetTable,relation.ParentColumns);
137 targetTable = targetSet.Tables[relation.ChildColumns[0].Table.TableName];
138 DataColumn[] childColumns = ResolveColumns(sourceSet,targetTable,relation.ChildColumns);
139 if (parentColumns != null && childColumns != null) {
140 DataRelation newRelation = new DataRelation(relation.RelationName,parentColumns,childColumns);
141 targetSet.Relations.Add(newRelation);
144 else {
145 // TODO : should we throw an exeption ?
149 foreach(DataTable sourceTable in sourceSet.Tables) {
150 DataTable targetTable = targetSet.Tables[sourceTable.TableName];
152 if (targetTable != null) {
153 foreach(UniqueConstraint uc in sourceTable.Constraints.UniqueConstraints) {
154 // TODO : add more precise condition (columns)
155 if ( !targetTable.Constraints.Contains(uc.ConstraintName) ) {
156 DataColumn[] columns = ResolveColumns(sourceSet,targetTable,uc.Columns);
157 if (columns != null) {
158 UniqueConstraint newConstraint = new UniqueConstraint(uc.ConstraintName,columns,uc.IsPrimaryKey);
159 targetTable.Constraints.Add(newConstraint);
162 else {
163 // TODO : should we throw an exeption ?
167 foreach(ForeignKeyConstraint fc in sourceTable.Constraints.ForeignKeyConstraints) {
168 // TODO : add more precise condition (columns)
169 if (!targetTable.Constraints.Contains(fc.ConstraintName)) {
170 DataColumn[] columns = ResolveColumns(sourceSet,targetTable,fc.Columns);
171 DataTable relatedTable = targetSet.Tables[fc.RelatedTable.TableName];
172 DataColumn[] relatedColumns = ResolveColumns(sourceSet,relatedTable,fc.RelatedColumns);
173 if (columns != null && relatedColumns != null) {
174 ForeignKeyConstraint newConstraint = new ForeignKeyConstraint(fc.ConstraintName,relatedColumns,columns);
175 targetTable.Constraints.Add(newConstraint);
178 else {
179 // TODO : should we throw an exeption ?
186 return true;
189 private static DataColumn[] ResolveColumns(DataSet targetSet,DataTable targetTable,DataColumn[] sourceColumns)
191 if (sourceColumns != null && sourceColumns.Length > 0) {
192 // TODO : worth to check that all source colums come from the same table
193 if (targetTable != null) {
194 int i=0;
195 DataColumn[] targetColumns = new DataColumn[sourceColumns.Length];
196 foreach(DataColumn sourceColumn in sourceColumns) {
197 targetColumns[i++] = targetTable.Columns[sourceColumn.ColumnName];
199 return targetColumns;
202 return null;
206 // adjust the table schema according to the missingschemaaction param.
207 // return false if adjusting fails.
208 private static bool AdjustSchema(DataSet targetSet, DataTable sourceTable, MissingSchemaAction missingSchemaAction, ref DataTable newTable)
210 string tableName = sourceTable.TableName;
212 // if the source table not exists in the target dataset
213 // we act according to the missingschemaaction param.
214 int tmp = targetSet.Tables.IndexOf(tableName);
215 // we need to check if it is equals names
216 if (tmp != -1 && !targetSet.Tables[tmp].TableName.Equals(tableName))
217 tmp = -1;
218 if (tmp == -1) {
219 if (missingSchemaAction == MissingSchemaAction.Ignore) {
220 return true;
222 if (missingSchemaAction == MissingSchemaAction.Error) {
223 throw new ArgumentException("Target DataSet missing definition for "+ tableName + ".");
226 DataTable cloneTable = (DataTable)sourceTable.Clone();
227 targetSet.Tables.Add(cloneTable);
228 tableName = cloneTable.TableName;
231 DataTable table = targetSet.Tables[tableName];
233 for (int i = 0; i < sourceTable.Columns.Count; i++) {
234 DataColumn sourceColumn = sourceTable.Columns[i];
235 // if a column from the source table doesn't exists in the target table
236 // we act according to the missingschemaaction param.
237 DataColumn targetColumn = table.Columns[sourceColumn.ColumnName];
238 if(targetColumn == null) {
239 if (missingSchemaAction == MissingSchemaAction.Ignore) {
240 continue;
242 if (missingSchemaAction == MissingSchemaAction.Error) {
243 throw new ArgumentException(("Column '" + sourceColumn.ColumnName + "' does not belong to table Items."));
246 targetColumn = new DataColumn(sourceColumn.ColumnName, sourceColumn.DataType, sourceColumn.Expression, sourceColumn.ColumnMapping);
247 table.Columns.Add(targetColumn);
250 if (sourceColumn.Unique) {
251 try {
252 targetColumn.Unique = sourceColumn.Unique;
254 catch(Exception e){
255 // Console.WriteLine("targetColumn : {0} targetTable : {1} ",targetColumn.ColumnName,table.TableName);
256 foreach(DataRow row in table.Rows) {
257 // Console.WriteLine(row[targetColumn]);
259 throw e;
263 if(sourceColumn.AutoIncrement) {
264 targetColumn.AutoIncrement = sourceColumn.AutoIncrement;
265 targetColumn.AutoIncrementSeed = sourceColumn.AutoIncrementSeed;
266 targetColumn.AutoIncrementStep = sourceColumn.AutoIncrementStep;
270 if (!AdjustPrimaryKeys(table, sourceTable)) {
271 return false;
274 newTable = table;
275 return true;
278 // find if there is a valid matching between the targetTable PrimaryKey and the
279 // sourceTable primatyKey.
280 // return true if there is a match, else return false and raise a MergeFailedEvent.
281 private static bool AdjustPrimaryKeys(DataTable targetTable, DataTable sourceTable)
283 // if the length of one of the tables primarykey if 0 - there is nothing to check.
284 if (sourceTable.PrimaryKey.Length != 0) {
285 if (targetTable.PrimaryKey.Length == 0) {
286 // if target table has no primary key at all -
287 // import primary key from source table
288 DataColumn[] targetColumns = new DataColumn[sourceTable.PrimaryKey.Length];
290 for(int i=0; i < sourceTable.PrimaryKey.Length; i++){
291 DataColumn sourceColumn = sourceTable.PrimaryKey[i];
292 DataColumn targetColumn = targetTable.Columns[sourceColumn.ColumnName];
294 if (targetColumn == null) {
295 // is target table has no column corresponding
296 // to source table PK column - merge fails
297 string message = "Column " + sourceColumn.ColumnName + " does not belongs to table " + targetTable.TableName;
298 MergeFailedEventArgs e = new MergeFailedEventArgs(sourceTable, message);
299 targetTable.DataSet.OnMergeFailed(e);
300 return false;
302 else {
303 targetColumns[i] = targetColumn;
306 targetTable.PrimaryKey = targetColumns;
308 else {
309 // if target table already has primary key and
310 // if the length of primarykey is not equal - merge fails
311 if (targetTable.PrimaryKey.Length != sourceTable.PrimaryKey.Length) {
312 string message = "<target>.PrimaryKey and <source>.PrimaryKey have different Length.";
313 MergeFailedEventArgs e = new MergeFailedEventArgs(sourceTable, message);
314 targetTable.DataSet.OnMergeFailed(e);
315 return false;
317 else {
318 // we have to see that each primary column in the target table
319 // has a column with the same name in the sourcetable primarykey columns.
320 bool foundMatch;
321 DataColumn[] targetDataColumns = targetTable.PrimaryKey;
322 DataColumn[] srcDataColumns = sourceTable.PrimaryKey;
324 // loop on all primary key columns in the targetTable.
325 for (int i = 0; i < targetDataColumns.Length; i++) {
326 foundMatch = false;
327 DataColumn col = targetDataColumns[i];
329 // find if there is a column with the same name in the
330 // sourceTable primary key columns.
331 for (int j = 0; j < srcDataColumns.Length; j++) {
332 if (srcDataColumns[j].ColumnName == col.ColumnName) {
333 foundMatch = true;
334 break;
337 if (!foundMatch) {
338 string message = "Mismatch columns in the PrimaryKey : <target>." + col.ColumnName + " versus <source>." + srcDataColumns[i].ColumnName + ".";
339 MergeFailedEventArgs e = new MergeFailedEventArgs(sourceTable, message);
340 targetTable.DataSet.OnMergeFailed(e);
341 return false;
349 return true;
352 // fill the data from the source table to the target table
353 private static void fillData(DataTable targetTable, DataTable sourceTable, bool preserveChanges)
355 for (int i = 0; i < sourceTable.Rows.Count; i++)
357 DataRow row = sourceTable.Rows[i];
358 MergeRow(targetTable, row, preserveChanges);
362 // check tha column from 2 tables that has the same name also has the same datatype.
363 private static void checkColumnTypes(DataTable targetTable, DataTable sourceTable)
365 for (int i = 0; i < sourceTable.Columns.Count; i++)
367 DataColumn fromCol = sourceTable.Columns[i];
368 DataColumn toCol = targetTable.Columns[fromCol.ColumnName];
369 if((toCol != null) && (toCol.DataType != fromCol.DataType))
370 throw new DataException("<target>." + fromCol.ColumnName + " and <source>." + fromCol.ColumnName + " have conflicting properties: DataType property mismatch.");