87281ad3bf74ab14660de59771d8a8e75eb597a5
[mono.git] / mcs / class / referencesource / System.Data / System / Data / Odbc / odbcmetadatafactory.cs
1 //------------------------------------------------------------------------------
2 // <copyright file="OdbcMetaDataFactory.cs" company="Microsoft">
3 //     Copyright (c) Microsoft Corporation.  All rights reserved.
4 // </copyright>
5 //
6 // <owner current="true" primary="true">[....]</owner>
7 // <owner current="true" primary="false">[....]</owner>
8 //
9 //------------------------------------------------------------------------------
10
11 namespace System.Data.Odbc{
12
13     using System;
14     using System.Data;
15     using System.IO;
16     using System.Collections;
17     using System.Data.Common;
18     using System.Xml;
19     using System.Xml.Schema;
20     using System.Diagnostics;
21     using System.Data.ProviderBase;
22     using System.Text;
23
24
25     internal class OdbcMetaDataFactory : DbMetaDataFactory{
26
27         private struct SchemaFunctionName {
28             internal SchemaFunctionName(string schemaName, ODBC32.SQL_API odbcFunction) {
29                 _schemaName = schemaName;
30                 _odbcFunction = odbcFunction;
31             }
32             internal readonly string          _schemaName;
33             internal readonly ODBC32.SQL_API  _odbcFunction;
34         }
35
36         private const string _collectionName = "CollectionName";
37         private const string _populationMechanism = "PopulationMechanism";
38         private const string _prepareCollection = "PrepareCollection";
39
40         private readonly SchemaFunctionName[] _schemaMapping;
41
42         static internal readonly char[] KeywordSeparatorChar = new char[1] { ',' };
43
44
45         internal OdbcMetaDataFactory(Stream XMLStream,
46                                    string serverVersion,
47                                    string serverVersionNormalized,
48                                    OdbcConnection connection):
49             base(XMLStream, serverVersion, serverVersionNormalized) {
50
51             // set up the colletion name ODBC function mapping guid mapping
52             _schemaMapping = new SchemaFunctionName[] {
53
54                 new SchemaFunctionName(DbMetaDataCollectionNames.DataTypes,ODBC32.SQL_API.SQLGETTYPEINFO),
55                 new SchemaFunctionName(OdbcMetaDataCollectionNames.Columns,ODBC32.SQL_API.SQLCOLUMNS),
56                 new SchemaFunctionName(OdbcMetaDataCollectionNames.Indexes,ODBC32.SQL_API.SQLSTATISTICS),
57                 new SchemaFunctionName(OdbcMetaDataCollectionNames.Procedures,ODBC32.SQL_API.SQLPROCEDURES),
58                 new SchemaFunctionName(OdbcMetaDataCollectionNames.ProcedureColumns,ODBC32.SQL_API.SQLPROCEDURECOLUMNS),
59                 new SchemaFunctionName(OdbcMetaDataCollectionNames.ProcedureParameters,ODBC32.SQL_API.SQLPROCEDURECOLUMNS),
60                 new SchemaFunctionName(OdbcMetaDataCollectionNames.Tables,ODBC32.SQL_API.SQLTABLES),
61                 new SchemaFunctionName(OdbcMetaDataCollectionNames.Views,ODBC32.SQL_API.SQLTABLES)};
62
63             // verify the existance of the table in the data set
64             DataTable metaDataCollectionsTable = CollectionDataSet.Tables[DbMetaDataCollectionNames.MetaDataCollections];
65             if (metaDataCollectionsTable == null){
66                 throw ADP.UnableToBuildCollection(DbMetaDataCollectionNames.MetaDataCollections);
67             }
68
69             // copy the table filtering out any rows that don't apply to the current version of the provider
70             metaDataCollectionsTable = CloneAndFilterCollection(DbMetaDataCollectionNames.MetaDataCollections, null);
71
72             // verify the existance of the table in the data set
73             DataTable restrictionsTable = CollectionDataSet.Tables[DbMetaDataCollectionNames.Restrictions];
74             if (restrictionsTable != null){
75                 // copy the table filtering out any rows that don't apply to the current version of the provider
76                 restrictionsTable= CloneAndFilterCollection(DbMetaDataCollectionNames.Restrictions, null);
77             }
78
79             // need to filter out any of the collections where
80             // 1) it is populated using prepare collection
81             // 2) it is in the collection to odbc function mapping above
82             // 3) the provider does not support the necessary odbc function
83
84
85             DataColumn populationMechanism = metaDataCollectionsTable.Columns[_populationMechanism];
86             DataColumn collectionName = metaDataCollectionsTable.Columns[_collectionName];
87             DataColumn restrictionCollectionName = null;
88             if (restrictionsTable != null){
89                 restrictionCollectionName = restrictionsTable.Columns[_collectionName];
90             }
91
92             foreach (DataRow collection in metaDataCollectionsTable.Rows){
93                 if ((string)collection[populationMechanism] == _prepareCollection) {
94                     // is the collection in the mapping
95                     int mapping = -1;
96                     for (int i = 0; i < _schemaMapping.Length; i++){
97                         if (_schemaMapping[i]._schemaName == (string)collection[collectionName]){
98                            mapping = i;
99                            break;
100                         }
101                     }
102                     // no go on to the next collection
103                     if (mapping == -1) {
104                         continue;
105                     }
106
107                     // does the provider support the necessary odbc function
108                     // if not delete the row from the table
109                     if (connection.SQLGetFunctions(_schemaMapping[mapping]._odbcFunction) == false){
110                         // but first delete any related restrictions
111                         if (restrictionsTable != null) {
112                             foreach (DataRow restriction in restrictionsTable.Rows) {
113                                 if ((string)collection[collectionName]==(string)restriction[restrictionCollectionName]) {
114                                     restriction.Delete();
115                                 }
116                             }
117                             restrictionsTable.AcceptChanges();
118                         }
119                         collection.Delete();
120                     }
121
122                 }
123             }
124
125             // replace the original table with the updated one
126             metaDataCollectionsTable.AcceptChanges();
127             CollectionDataSet.Tables.Remove(CollectionDataSet.Tables[DbMetaDataCollectionNames.MetaDataCollections]);
128             CollectionDataSet.Tables.Add(metaDataCollectionsTable);
129
130             if (restrictionsTable != null) {
131                 CollectionDataSet.Tables.Remove(CollectionDataSet.Tables[DbMetaDataCollectionNames.Restrictions]);
132                 CollectionDataSet.Tables.Add(restrictionsTable);
133             }
134
135         }
136
137         private object BooleanFromODBC(object odbcSource) {
138
139             if (odbcSource != DBNull.Value){
140                 //convert to Int32 before doing the comparison
141                 //some odbc drivers report the odbcSource value as unsigned, in which case we will
142                 //have upgraded the type to Int32, and thus can't cast directly to short
143                 if (Convert.ToInt32(odbcSource, null) == 0) 
144                 {
145                     return false;
146                 }
147                 else {
148                     return true;
149                 }
150             }
151
152             return DBNull.Value;
153         }
154
155         private OdbcCommand GetCommand(OdbcConnection connection){
156
157             OdbcCommand command = connection.CreateCommand();
158
159             // You need to make sure you pick up the transaction from the connection,
160             // or odd things can happen...
161             command.Transaction = connection.LocalTransaction;
162             return command;
163
164         }
165
166         private DataTable DataTableFromDataReader(IDataReader reader, string tableName) {
167
168             // set up the column structure of the data table from the reader
169             object[] values;
170             DataTable resultTable = NewDataTableFromReader(reader, out values, tableName);
171
172             // populate the data table from the data reader
173             while (reader.Read()) {
174                 reader.GetValues(values);
175                 resultTable.Rows.Add(values);
176             }
177             return resultTable;
178         }
179
180         private void DataTableFromDataReaderDataTypes(DataTable dataTypesTable, OdbcDataReader dataReader, OdbcConnection connection) {
181
182             DataTable       schemaTable = null;
183             // 
184
185             // Build a DataTable from the reader
186             schemaTable = dataReader.GetSchemaTable();
187
188             // vstfdevdiv:479715 Handle cases where reader is empty
189             if (null == schemaTable) {
190                 throw ADP.OdbcNoTypesFromProvider();
191             }
192             
193             object[] getTypeInfoValues = new object[schemaTable.Rows.Count];
194             DataRow dataTypesRow;
195
196             DataColumn typeNameColumn = dataTypesTable.Columns[DbMetaDataColumnNames.TypeName];
197             DataColumn providerDbTypeColumn = dataTypesTable.Columns[DbMetaDataColumnNames.ProviderDbType];
198             DataColumn columnSizeColumn = dataTypesTable.Columns[DbMetaDataColumnNames.ColumnSize];
199             DataColumn createParametersColumn = dataTypesTable.Columns[DbMetaDataColumnNames.CreateParameters];
200             DataColumn dataTypeColumn = dataTypesTable.Columns[DbMetaDataColumnNames.DataType];
201             DataColumn isAutoIncermentableColumn = dataTypesTable.Columns[DbMetaDataColumnNames.IsAutoIncrementable];
202             DataColumn isCaseSensitiveColumn = dataTypesTable.Columns[DbMetaDataColumnNames.IsCaseSensitive];
203             DataColumn isFixedLengthColumn = dataTypesTable.Columns[DbMetaDataColumnNames.IsFixedLength];
204             DataColumn isFixedPrecisionScaleColumn = dataTypesTable.Columns[DbMetaDataColumnNames.IsFixedPrecisionScale];
205             DataColumn isLongColumn = dataTypesTable.Columns[DbMetaDataColumnNames.IsLong];
206             DataColumn isNullableColumn = dataTypesTable.Columns[DbMetaDataColumnNames.IsNullable];
207             DataColumn isSearchableColumn = dataTypesTable.Columns[DbMetaDataColumnNames.IsSearchable];
208             DataColumn isSearchableWithLikeColumn = dataTypesTable.Columns[DbMetaDataColumnNames.IsSearchableWithLike];
209             DataColumn isUnsignedColumn = dataTypesTable.Columns[DbMetaDataColumnNames.IsUnsigned];
210             DataColumn maximumScaleColumn = dataTypesTable.Columns[DbMetaDataColumnNames.MaximumScale];
211             DataColumn minimumScaleColumn = dataTypesTable.Columns[DbMetaDataColumnNames.MinimumScale];
212             DataColumn literalPrefixColumn = dataTypesTable.Columns[DbMetaDataColumnNames.LiteralPrefix];
213             DataColumn literalSuffixColumn = dataTypesTable.Columns[DbMetaDataColumnNames.LiteralSuffix];
214             DataColumn SQLTypeNameColumn = dataTypesTable.Columns[OdbcMetaDataColumnNames.SQLType];
215
216
217             const int indexTYPE_NAME = 0;
218             const int indexDATA_TYPE = 1;
219             const int indexCOLUMN_SIZE = 2;
220             const int indexCREATE_PARAMS = 5;
221             const int indexAUTO_UNIQUE_VALUE = 11;
222             const int indexCASE_SENSITIVE = 7;
223             const int indexFIXED_PREC_SCALE = 10;
224             const int indexNULLABLE = 6;
225             const int indexSEARCHABLE = 8;
226             const int indexUNSIGNED_ATTRIBUTE = 9;
227             const int indexMAXIMUM_SCALE = 14;
228             const int indexMINIMUM_SCALE = 13;
229             const int indexLITERAL_PREFIX = 3;
230             const int indexLITERAL_SUFFIX = 4;
231
232             const int SQL_DATE_V2 = 9;
233             const int SQL_TIME_V2 = 10;
234
235             TypeMap typeMap ;
236
237
238             while (dataReader.Read()) {
239                 dataReader.GetValues(getTypeInfoValues);
240                 dataTypesRow = dataTypesTable.NewRow();
241
242                 ODBC32.SQL_TYPE sqlType;
243
244                 dataTypesRow[typeNameColumn] = getTypeInfoValues[indexTYPE_NAME];
245                 dataTypesRow[SQLTypeNameColumn] = getTypeInfoValues[indexDATA_TYPE];
246
247                 sqlType = (ODBC32.SQL_TYPE)(Int32) Convert.ChangeType(getTypeInfoValues[indexDATA_TYPE],
248                                                                       typeof(Int32), 
249                                                                       (System.IFormatProvider)null);
250                 // if the driver is pre version 3 and it returned the v2 SQL_DATE or SQL_TIME types they need
251                 // to be mapped to thier v3 equlivants
252                 if (connection.IsV3Driver == false) {
253                     if ((int)sqlType == SQL_DATE_V2) {
254                         sqlType = ODBC32.SQL_TYPE.TYPE_DATE;
255                     }
256                     else if ((int)sqlType == SQL_TIME_V2) {
257                         sqlType = ODBC32.SQL_TYPE.TYPE_TIME;
258                     }
259                 }
260                 try {
261                    typeMap = TypeMap.FromSqlType(sqlType);
262                 }
263                 // FromSqlType will throw an argument exception if it does not recognize the SqlType.
264                 // This is not an error since the GetTypeInfo DATA_TYPE may be a SQL data type or a driver specific
265                 // type. If there is no TypeMap for the type its not an error but it will degrade our level of
266                 // understanding of/ support for the type.
267                 catch (ArgumentException) {
268                     typeMap = null;
269                 }
270
271                 // if we have a type map we can determine the dbType and the CLR type if not leave them null
272                 if (typeMap != null) {
273                     dataTypesRow[providerDbTypeColumn] = typeMap._odbcType;
274                     dataTypesRow[dataTypeColumn] = typeMap._type.FullName;
275                     // setting isLong and isFixedLength only if we have a type map because for provider
276                     // specific types we have no idea what the types attributes are if GetTypeInfo did not
277                     // tell us
278                     switch (sqlType) {
279
280                         case ODBC32.SQL_TYPE.LONGVARCHAR:
281                         case ODBC32.SQL_TYPE.WLONGVARCHAR:
282                         case ODBC32.SQL_TYPE.LONGVARBINARY:
283                         case ODBC32.SQL_TYPE.SS_XML:
284                             dataTypesRow[isLongColumn] = true;
285                             dataTypesRow[isFixedLengthColumn] = false;
286                             break;
287
288                         case ODBC32.SQL_TYPE.VARCHAR:
289                         case ODBC32.SQL_TYPE.WVARCHAR:
290                         case ODBC32.SQL_TYPE.VARBINARY:
291                             dataTypesRow[isLongColumn] = false;
292                             dataTypesRow[isFixedLengthColumn] = false;
293                             break;
294
295                         case ODBC32.SQL_TYPE.CHAR:
296                         case ODBC32.SQL_TYPE.WCHAR:
297                         case ODBC32.SQL_TYPE.DECIMAL:
298                         case ODBC32.SQL_TYPE.NUMERIC:
299                         case ODBC32.SQL_TYPE.SMALLINT:
300                         case ODBC32.SQL_TYPE.INTEGER:
301                         case ODBC32.SQL_TYPE.REAL:
302                         case ODBC32.SQL_TYPE.FLOAT:
303                         case ODBC32.SQL_TYPE.DOUBLE:
304                         case ODBC32.SQL_TYPE.BIT:
305                         case ODBC32.SQL_TYPE.TINYINT:
306                         case ODBC32.SQL_TYPE.BIGINT:
307                         case ODBC32.SQL_TYPE.TYPE_DATE:
308                         case ODBC32.SQL_TYPE.TYPE_TIME:
309                         case ODBC32.SQL_TYPE.TIMESTAMP:
310                         case ODBC32.SQL_TYPE.TYPE_TIMESTAMP:
311                         case ODBC32.SQL_TYPE.GUID:
312                         case ODBC32.SQL_TYPE.SS_VARIANT:
313                         case ODBC32.SQL_TYPE.SS_UTCDATETIME:
314                         case ODBC32.SQL_TYPE.SS_TIME_EX:
315                         case ODBC32.SQL_TYPE.BINARY:    
316                             dataTypesRow[isLongColumn] = false;
317                             dataTypesRow[isFixedLengthColumn] = true;
318                             break;
319
320                         case ODBC32.SQL_TYPE.SS_UDT:
321                         default:
322                             // for User defined types don't know if its long or or if it is
323                             // varaible length or not so leave the fields null
324                             break;
325                     }
326                 }
327
328
329
330                 dataTypesRow[columnSizeColumn] = getTypeInfoValues[indexCOLUMN_SIZE];
331                 dataTypesRow[createParametersColumn] = getTypeInfoValues[indexCREATE_PARAMS];
332
333                 if ((getTypeInfoValues[indexAUTO_UNIQUE_VALUE] == DBNull.Value) ||
334                     (Convert.ToInt16(getTypeInfoValues[indexAUTO_UNIQUE_VALUE], null) == 0)) {
335                     dataTypesRow[isAutoIncermentableColumn] = false;
336                 }
337                 else {
338                     dataTypesRow[isAutoIncermentableColumn] = true;
339                 }
340
341                 dataTypesRow[isCaseSensitiveColumn] = BooleanFromODBC(getTypeInfoValues[indexCASE_SENSITIVE]);
342                 dataTypesRow[isFixedPrecisionScaleColumn] = BooleanFromODBC(getTypeInfoValues[indexFIXED_PREC_SCALE]);
343
344                 if (getTypeInfoValues[indexNULLABLE] != DBNull.Value) {
345                     //Use Convert.ToInt16 instead of direct cast to short because the value will be Int32 in some cases
346                     switch ((ODBC32.SQL_NULLABILITY)Convert.ToInt16(getTypeInfoValues[indexNULLABLE], null)) { 
347
348                         case ODBC32.SQL_NULLABILITY.NO_NULLS:
349                             dataTypesRow[isNullableColumn] = false;
350                             break;
351
352                         case ODBC32.SQL_NULLABILITY.NULLABLE:
353                             dataTypesRow[isNullableColumn] = true;
354                             break;
355
356                         case ODBC32.SQL_NULLABILITY.UNKNOWN:
357                             dataTypesRow[isNullableColumn] = DBNull.Value;
358                             break;
359                     }
360                 }
361
362                 if ( DBNull.Value != getTypeInfoValues[indexSEARCHABLE]){
363
364                     //Use Convert.ToInt16 instead of direct cast to short because the value will be Int32 in some cases
365                     Int16 searchableValue = Convert.ToInt16(getTypeInfoValues[indexSEARCHABLE], null);
366                     switch (searchableValue){
367
368                         case (Int16)ODBC32.SQL_SEARCHABLE.UNSEARCHABLE:
369                             dataTypesRow[isSearchableColumn] = false;
370                             dataTypesRow[isSearchableWithLikeColumn] = false;
371                             break;
372
373                         case (Int16)ODBC32.SQL_SEARCHABLE.LIKE_ONLY:
374                              dataTypesRow[isSearchableColumn] = false;
375                              dataTypesRow[isSearchableWithLikeColumn] = true;
376                              break;
377
378                         case (Int16)ODBC32.SQL_SEARCHABLE.ALL_EXCEPT_LIKE:
379                             dataTypesRow[isSearchableColumn] = true;
380                             dataTypesRow[isSearchableWithLikeColumn] = false;
381                             break;
382
383                         case (Int16)ODBC32.SQL_SEARCHABLE.SEARCHABLE:
384                             dataTypesRow[isSearchableColumn] = true;
385                             dataTypesRow[isSearchableWithLikeColumn] = true;
386                             break;
387                     }
388                 }
389
390                 dataTypesRow[isUnsignedColumn] = BooleanFromODBC(getTypeInfoValues[indexUNSIGNED_ATTRIBUTE]);
391
392                 //For assignment to the DataSet, don't cast the data types -- let the DataSet take care of any conversion
393                 if (getTypeInfoValues[indexMAXIMUM_SCALE] != DBNull.Value ) {
394                     dataTypesRow[maximumScaleColumn] = getTypeInfoValues[indexMAXIMUM_SCALE];
395                 }
396
397                 if (getTypeInfoValues[indexMINIMUM_SCALE] != DBNull.Value ) {
398                     dataTypesRow[minimumScaleColumn] = getTypeInfoValues[indexMINIMUM_SCALE];
399                 }
400
401                 if (getTypeInfoValues[indexLITERAL_PREFIX] != DBNull.Value ) {
402                     dataTypesRow[literalPrefixColumn] = getTypeInfoValues[indexLITERAL_PREFIX];
403                 }
404
405                 if (getTypeInfoValues[indexLITERAL_SUFFIX] != DBNull.Value ) {
406                     dataTypesRow[literalSuffixColumn] = getTypeInfoValues[indexLITERAL_SUFFIX];
407                 }
408
409                 dataTypesTable.Rows.Add(dataTypesRow);
410             }
411
412         }
413
414         private DataTable DataTableFromDataReaderIndex(IDataReader reader,
415                                                        string tableName,
416                                                        string restrictionIndexName) {
417
418             // set up the column structure of the data table from the reader
419             object[] values;
420             DataTable resultTable = NewDataTableFromReader(reader, out values, tableName);
421
422             // populate the data table from the data reader
423             int positionOfType = 6;
424             int positionOfIndexName = 5;
425             while (reader.Read()) {
426                 reader.GetValues(values);
427                 if (IncludeIndexRow(values[positionOfIndexName],
428                                     restrictionIndexName,
429                                     Convert.ToInt16(values[positionOfType], null)) == true){
430                     resultTable.Rows.Add(values);
431                 }
432             }
433             return resultTable;
434         }
435
436         private DataTable DataTableFromDataReaderProcedureColumns(IDataReader reader, string tableName,Boolean isColumn) {
437
438             // set up the column structure of the data table from the reader
439             object[] values;
440             DataTable resultTable = NewDataTableFromReader(reader, out values, tableName);
441
442             // populate the data table from the data reader
443             int positionOfColumnType = 4;
444             while (reader.Read()) {
445                 reader.GetValues(values);
446                 // the column type should always be short but need to check just in case
447                 if (values[positionOfColumnType].GetType() == typeof(short)) {
448                     if ((((short)values[positionOfColumnType] == ODBC32.SQL_RESULT_COL) && (isColumn == true)) ||
449                         (((short)values[positionOfColumnType] != ODBC32.SQL_RESULT_COL) && (isColumn == false))) {
450                         resultTable.Rows.Add(values);
451                     }
452                 }
453             }
454             return resultTable;
455         }
456
457         private DataTable DataTableFromDataReaderProcedures(IDataReader reader, string tableName,Int16 procedureType) {
458
459             // Build a DataTable from the reader
460
461             // set up the column structure of the data table from the reader
462             object[] values;
463             DataTable resultTable = NewDataTableFromReader(reader, out values, tableName);
464
465             // populate the data table from the data reader
466             int positionOfProcedureType = 7;
467             while (reader.Read()) {
468                 reader.GetValues(values);
469                 // the column type should always be short but need to check just in case its null
470                 if (values[positionOfProcedureType].GetType() == typeof(short)) {
471                     if ((short)values[positionOfProcedureType] == procedureType) {
472                         resultTable.Rows.Add(values);
473                     }
474                 }
475             }
476             return resultTable;
477         }
478
479         private void FillOutRestrictions(int restrictionsCount, string[] restrictions, object[] allRestrictions, string collectionName) {
480
481             Debug.Assert(allRestrictions.Length >= restrictionsCount);
482
483             int i = 0;
484
485             // if we have restrictions put them in the restrictions array
486             if (restrictions != null) {
487
488                 if (restrictions.Length > restrictionsCount) {
489                     throw ADP.TooManyRestrictions(collectionName);
490                 }
491
492                 for (i=0; i < restrictions.Length; i++) {
493                     if (restrictions[i] != null) {
494                         allRestrictions[i] = restrictions[i];
495                     }
496                 }
497             }
498
499             // initalize the rest to no restrictions
500             for (; i < restrictionsCount; i++) {
501                 allRestrictions[i] = null;
502             }
503         }
504
505
506         private DataTable GetColumnsCollection(String[] restrictions, OdbcConnection connection){
507
508             OdbcCommand command = null;
509             OdbcDataReader dataReader =null;
510             DataTable resultTable = null;
511             const int  columnsRestrictionsCount = 4;
512
513             try {
514                 command = GetCommand(connection);
515                 String[] allRestrictions = new string[columnsRestrictionsCount];
516                 FillOutRestrictions(columnsRestrictionsCount,restrictions,allRestrictions,OdbcMetaDataCollectionNames.Columns);
517
518                 dataReader = command.ExecuteReaderFromSQLMethod(allRestrictions, ODBC32.SQL_API.SQLCOLUMNS);
519
520                 resultTable = DataTableFromDataReader(dataReader, OdbcMetaDataCollectionNames.Columns);
521             }
522
523             finally {
524                 if (dataReader != null) {
525                     dataReader.Dispose();
526                 };
527                 if (command != null) {
528                     command.Dispose();
529                 };
530             }
531             return resultTable;
532         }
533
534
535         private DataTable GetDataSourceInformationCollection(string [] restrictions,
536                                                              OdbcConnection connection){
537
538             if (ADP.IsEmptyArray(restrictions) == false) {
539                throw ADP.TooManyRestrictions(DbMetaDataCollectionNames.DataSourceInformation);
540             }
541
542             // verify that the data source information table is in the data set
543             DataTable dataSourceInformationTable = CollectionDataSet.Tables[DbMetaDataCollectionNames.DataSourceInformation];
544             if (dataSourceInformationTable == null){
545                 throw ADP.UnableToBuildCollection(DbMetaDataCollectionNames.DataSourceInformation);
546             }
547
548             // copy the table filtering out any rows that don't apply to the current version of the provider
549             dataSourceInformationTable = CloneAndFilterCollection(DbMetaDataCollectionNames.DataSourceInformation, null);
550
551             // after filtering there better be just one row
552             if (dataSourceInformationTable.Rows.Count != 1){
553                 throw ADP.IncorrectNumberOfDataSourceInformationRows();
554             }
555             DataRow dataSourceInformation = dataSourceInformationTable.Rows[0];
556
557             string stringValue;
558             Int16 int16Value;
559             Int32 int32Value;
560             ODBC32.RetCode retcode;
561
562             // update the catalog separator
563             stringValue = connection.GetInfoStringUnhandled(ODBC32.SQL_INFO.CATALOG_NAME_SEPARATOR);
564             if (!ADP.IsEmpty(stringValue)) {
565                 StringBuilder patternEscaped = new StringBuilder();
566                 ADP.EscapeSpecialCharacters(stringValue,patternEscaped);
567                 dataSourceInformation[DbMetaDataColumnNames.CompositeIdentifierSeparatorPattern] = patternEscaped.ToString();
568             }
569
570             // get the DBMS Name
571             stringValue = connection.GetInfoStringUnhandled(ODBC32.SQL_INFO.DBMS_NAME);
572             if (stringValue != null) {
573                 dataSourceInformation[DbMetaDataColumnNames.DataSourceProductName] = stringValue;
574             }
575
576
577             // update the server version strings
578             dataSourceInformation[DbMetaDataColumnNames.DataSourceProductVersion] = ServerVersion;
579             dataSourceInformation[DbMetaDataColumnNames.DataSourceProductVersionNormalized] = ServerVersionNormalized;
580
581             
582             // values that are the same for all ODBC drivers. See bug 105333
583             dataSourceInformation[DbMetaDataColumnNames.ParameterMarkerFormat] =  "?";
584             dataSourceInformation[DbMetaDataColumnNames.ParameterMarkerPattern] =  "\\?";
585             dataSourceInformation[DbMetaDataColumnNames.ParameterNameMaxLength] = 0;
586
587             // determine the supportedJoinOperators
588             // leave the column null if the GetInfo fails. There is no explicit value for
589             // unknown.
590             if (connection.IsV3Driver) {
591                     
592                 retcode = connection.GetInfoInt32Unhandled(ODBC32.SQL_INFO.SQL_OJ_CAPABILITIES_30,out int32Value);
593             }
594             else {
595                 retcode = connection.GetInfoInt32Unhandled(ODBC32.SQL_INFO.SQL_OJ_CAPABILITIES_20, out int32Value);
596             }
597                  
598             if ((retcode == ODBC32.RetCode.SUCCESS) || (retcode == ODBC32.RetCode.SUCCESS_WITH_INFO)){
599
600                 Common. SupportedJoinOperators supportedJoinOperators = Common.SupportedJoinOperators.None;
601                 if ((int32Value & (Int32)ODBC32.SQL_OJ_CAPABILITIES.LEFT) != 0){
602                     supportedJoinOperators = supportedJoinOperators | Common.SupportedJoinOperators.LeftOuter;
603                 }
604                 if ((int32Value & (Int32)ODBC32.SQL_OJ_CAPABILITIES.RIGHT) != 0){
605                     supportedJoinOperators = supportedJoinOperators | Common.SupportedJoinOperators.RightOuter;
606                 } 
607                 if ((int32Value & (Int32)ODBC32.SQL_OJ_CAPABILITIES.FULL) != 0){
608                     supportedJoinOperators = supportedJoinOperators | Common.SupportedJoinOperators.FullOuter;
609                 }
610                 if ((int32Value & (Int32)ODBC32.SQL_OJ_CAPABILITIES.INNER) != 0){
611                     supportedJoinOperators = supportedJoinOperators | Common.SupportedJoinOperators.Inner;
612                 }
613                         
614                 dataSourceInformation[DbMetaDataColumnNames.SupportedJoinOperators] = supportedJoinOperators;
615             }
616
617             // determine the GroupByBehavior
618             retcode = connection.GetInfoInt16Unhandled(ODBC32.SQL_INFO.GROUP_BY, out int16Value);
619             Common.GroupByBehavior groupByBehavior = Common.GroupByBehavior.Unknown;
620
621             if ((retcode == ODBC32.RetCode.SUCCESS) || (retcode == ODBC32.RetCode.SUCCESS_WITH_INFO)){
622
623                 switch (int16Value) {
624
625                     case (Int16)ODBC32.SQL_GROUP_BY.NOT_SUPPORTED:
626                         groupByBehavior = Common.GroupByBehavior.NotSupported;
627                         break;
628
629                     case (Int16)ODBC32.SQL_GROUP_BY.GROUP_BY_EQUALS_SELECT:
630                         groupByBehavior = Common.GroupByBehavior.ExactMatch;
631                         break;
632
633                     case (Int16)ODBC32.SQL_GROUP_BY.GROUP_BY_CONTAINS_SELECT:
634                         groupByBehavior = Common.GroupByBehavior.MustContainAll;
635                         break;
636
637                     case (Int16)ODBC32.SQL_GROUP_BY.NO_RELATION:
638                         groupByBehavior = Common.GroupByBehavior.Unrelated;
639                         break;
640 /* COLLATE is new in ODBC 3.0 and GroupByBehavior does not have a value for it.
641                     case ODBC32.SQL_GROUP_BY.COLLATE:
642                         groupByBehavior = Common.GroupByBehavior.Unknown;
643                         break;
644 */
645                 }
646             }
647
648             dataSourceInformation[DbMetaDataColumnNames.GroupByBehavior] = groupByBehavior;
649
650             // determine the identifier case
651             retcode = connection.GetInfoInt16Unhandled(ODBC32.SQL_INFO.IDENTIFIER_CASE, out int16Value);
652             Common.IdentifierCase identifierCase = Common.IdentifierCase.Unknown;
653
654             if ((retcode == ODBC32.RetCode.SUCCESS) || (retcode == ODBC32.RetCode.SUCCESS_WITH_INFO)){
655
656                 switch (int16Value) {
657
658                     case (Int16)ODBC32.SQL_IDENTIFIER_CASE.SENSITIVE:
659                         identifierCase = Common.IdentifierCase.Sensitive;
660                         break;
661
662                     case (Int16)ODBC32.SQL_IDENTIFIER_CASE.UPPER:
663                     case (Int16)ODBC32.SQL_IDENTIFIER_CASE.LOWER:
664                     case (Int16)ODBC32.SQL_IDENTIFIER_CASE.MIXED:
665                         identifierCase = Common.IdentifierCase.Insensitive;
666                         break;
667
668                 }
669             }
670             dataSourceInformation[DbMetaDataColumnNames.IdentifierCase] = identifierCase;
671
672             // OrderByColumnsInSelect
673             stringValue = connection.GetInfoStringUnhandled(ODBC32.SQL_INFO.ORDER_BY_COLUMNS_IN_SELECT);
674             if (stringValue != null) {
675                 if (stringValue == "Y"){
676                     dataSourceInformation[DbMetaDataColumnNames.OrderByColumnsInSelect] = true;
677                 }
678                 else if (stringValue == "N") {
679                     dataSourceInformation[DbMetaDataColumnNames.OrderByColumnsInSelect] = false;
680                 }
681             }
682
683             // build the QuotedIdentifierPattern using the quote prefix and suffix from the provider and
684             // assuming that the quote suffix is escaped via repetion (i.e " becomes "")
685             stringValue = connection.QuoteChar(ADP.GetSchema);
686
687             if (stringValue != null){
688
689                 // by spec a blank identifier quote char indicates that the provider does not suppport
690                 // quoted identifiers
691                 if (stringValue != " ") {
692
693                     // only know how to build the parttern if the quote characters is 1 character
694                     // in all other cases just leave the field null
695                     if (stringValue.Length == 1) {
696                         StringBuilder scratchStringBuilder = new StringBuilder();
697                         ADP.EscapeSpecialCharacters(stringValue,scratchStringBuilder );
698                         string escapedQuoteSuffixString = scratchStringBuilder.ToString();
699                         scratchStringBuilder.Length = 0;
700
701                         ADP.EscapeSpecialCharacters(stringValue, scratchStringBuilder);
702                         scratchStringBuilder.Append("(([^");
703                         scratchStringBuilder.Append(escapedQuoteSuffixString);
704                         scratchStringBuilder.Append("]|");
705                         scratchStringBuilder.Append(escapedQuoteSuffixString);
706                         scratchStringBuilder.Append(escapedQuoteSuffixString);
707                         scratchStringBuilder.Append(")*)");
708                         scratchStringBuilder.Append(escapedQuoteSuffixString);
709                         dataSourceInformation[DbMetaDataColumnNames.QuotedIdentifierPattern] = scratchStringBuilder.ToString();
710                     }
711                 }
712             }
713
714             // determine the quoted identifier case
715             retcode = connection.GetInfoInt16Unhandled(ODBC32.SQL_INFO.QUOTED_IDENTIFIER_CASE, out int16Value);
716             Common.IdentifierCase quotedIdentifierCase = Common.IdentifierCase.Unknown;
717
718             if ((retcode == ODBC32.RetCode.SUCCESS) || (retcode == ODBC32.RetCode.SUCCESS_WITH_INFO)){
719
720                 switch (int16Value) {
721
722                     case (Int16)ODBC32.SQL_IDENTIFIER_CASE.SENSITIVE:
723                         quotedIdentifierCase = Common.IdentifierCase.Sensitive;
724                         break;
725
726                     case (Int16)ODBC32.SQL_IDENTIFIER_CASE.UPPER:
727                     case (Int16)ODBC32.SQL_IDENTIFIER_CASE.LOWER:
728                     case (Int16)ODBC32.SQL_IDENTIFIER_CASE.MIXED:
729                         quotedIdentifierCase = Common.IdentifierCase.Insensitive;
730                         break;
731
732                 }
733             }
734             dataSourceInformation[DbMetaDataColumnNames.QuotedIdentifierCase] = quotedIdentifierCase;
735
736             dataSourceInformationTable.AcceptChanges();
737
738             return dataSourceInformationTable;
739         }
740
741
742         private DataTable GetDataTypesCollection(String[] restrictions, OdbcConnection connection){
743             
744             if (ADP.IsEmptyArray(restrictions) == false){
745                 throw ADP.TooManyRestrictions(DbMetaDataCollectionNames.DataTypes);
746             }
747
748             
749
750             // verify the existance of the table in the data set
751             DataTable dataTypesTable = CollectionDataSet.Tables[DbMetaDataCollectionNames.DataTypes];
752             if (dataTypesTable == null){
753                 throw ADP.UnableToBuildCollection(DbMetaDataCollectionNames.DataTypes);
754             }
755
756             // copy the data table it
757             dataTypesTable = CloneAndFilterCollection(DbMetaDataCollectionNames.DataTypes, null);
758
759             OdbcCommand command = null;
760             OdbcDataReader dataReader =null;
761             object[] allArguments = new object[1];
762             allArguments[0] = ODBC32.SQL_ALL_TYPES;
763
764             try {
765                 command = GetCommand(connection);
766
767
768                 dataReader = command.ExecuteReaderFromSQLMethod(allArguments, ODBC32.SQL_API.SQLGETTYPEINFO);
769
770                 DataTableFromDataReaderDataTypes(dataTypesTable,dataReader,connection);
771             }
772
773             finally {
774                 if (dataReader != null) {
775                     dataReader.Dispose();
776                 };
777                 if (command != null) {
778                     command.Dispose();
779                 };
780             }
781             dataTypesTable.AcceptChanges();
782             return  dataTypesTable;
783         }
784
785         private DataTable GetIndexCollection(String[] restrictions, OdbcConnection connection){
786
787             OdbcCommand command = null;
788             OdbcDataReader dataReader =null;
789             DataTable resultTable = null;
790             const int nativeRestrictionsCount = 5;
791             const int indexRestrictionsCount = 4;
792             const int indexOfTableName = 2;
793             const int indexOfIndexName = 3;
794             
795             try {
796                 command = GetCommand(connection);
797                 object[] allRestrictions = new object[nativeRestrictionsCount];
798                 FillOutRestrictions(indexRestrictionsCount,restrictions,allRestrictions,OdbcMetaDataCollectionNames.Indexes);
799
800                 if (allRestrictions[indexOfTableName] == null) {
801                     throw ODBC.GetSchemaRestrictionRequired();
802                 }
803
804                 allRestrictions[3] = (Int16)ODBC32.SQL_INDEX.ALL;
805                 allRestrictions[4] = (Int16)ODBC32.SQL_STATISTICS_RESERVED.ENSURE;
806
807                 dataReader = command.ExecuteReaderFromSQLMethod(allRestrictions, ODBC32.SQL_API.SQLSTATISTICS);
808
809                 string indexName = null;
810                 if (restrictions != null) {
811
812                     if (restrictions.Length >= indexOfIndexName+1) {
813                        indexName = restrictions[indexOfIndexName];
814                     }
815                 }
816
817                 resultTable = DataTableFromDataReaderIndex(dataReader,
818                                                            OdbcMetaDataCollectionNames.Indexes,
819                                                            indexName);
820             }
821
822             finally {
823                 if (dataReader != null) {
824                     dataReader.Dispose();
825                 };
826                 if (command != null) {
827                     command.Dispose();
828                 };
829             }
830             return resultTable;
831         }
832
833         private DataTable GetProcedureColumnsCollection(String[] restrictions, OdbcConnection connection,Boolean isColumns){
834
835             OdbcCommand command = null;
836             OdbcDataReader dataReader =null;
837             DataTable resultTable = null;
838             const int  procedureColumnsRestrictionsCount = 4;
839
840             try {
841                 command = GetCommand(connection);
842                 String[] allRestrictions = new string[procedureColumnsRestrictionsCount];
843                 FillOutRestrictions(procedureColumnsRestrictionsCount,restrictions, allRestrictions,OdbcMetaDataCollectionNames.Columns);
844
845                 dataReader = command.ExecuteReaderFromSQLMethod(allRestrictions,ODBC32.SQL_API.SQLPROCEDURECOLUMNS);
846
847                 string collectionName;
848                 if (isColumns == true) {
849                    collectionName = OdbcMetaDataCollectionNames.ProcedureColumns;
850                 }
851                 else {
852                     collectionName = OdbcMetaDataCollectionNames.ProcedureParameters;
853                 }
854                 resultTable = DataTableFromDataReaderProcedureColumns(dataReader,
855                                                                       collectionName,
856                                                                       isColumns);
857             }
858
859             finally {
860                 if (dataReader != null) {
861                     dataReader.Dispose();
862                 };
863                 if (command != null) {
864                     command.Dispose();
865                 };
866             }
867             return resultTable;
868         }
869
870         private DataTable GetProceduresCollection(String[] restrictions, OdbcConnection connection){
871
872             OdbcCommand command = null;
873             OdbcDataReader dataReader =null;
874             DataTable resultTable = null;
875             const int  columnsRestrictionsCount = 4;
876             const int indexOfProcedureType = 3;
877
878             try {
879                 command = GetCommand(connection);
880                 String[] allRestrictions = new string[columnsRestrictionsCount];
881                 FillOutRestrictions(columnsRestrictionsCount,restrictions, allRestrictions,OdbcMetaDataCollectionNames.Procedures);
882
883
884                 dataReader = command.ExecuteReaderFromSQLMethod(allRestrictions,ODBC32.SQL_API.SQLPROCEDURES);
885
886                 if (allRestrictions[indexOfProcedureType] == null) {
887                     resultTable = DataTableFromDataReader(dataReader, OdbcMetaDataCollectionNames.Procedures);
888                 }
889                 else {
890                     Int16 procedureType;
891                     if ((restrictions[indexOfProcedureType] == "SQL_PT_UNKNOWN") ||
892                             (restrictions[indexOfProcedureType] == "0" /*ODBC32.SQL_PROCEDURETYPE.UNKNOWN*/)) {
893                             procedureType = (Int16)ODBC32.SQL_PROCEDURETYPE.UNKNOWN;
894                         }
895                     else if ((restrictions[indexOfProcedureType] == "SQL_PT_PROCEDURE") ||
896                              (restrictions[indexOfProcedureType] == "1" /*ODBC32.SQL_PROCEDURETYPE.PROCEDURE*/)) {
897                         procedureType = (Int16)ODBC32.SQL_PROCEDURETYPE.PROCEDURE;
898                     }
899                     else if ((restrictions[indexOfProcedureType] == "SQL_PT_FUNCTION") ||
900                              (restrictions[indexOfProcedureType] == "2" /*ODBC32.SQL_PROCEDURETYPE.FUNCTION*/)) {
901                         procedureType = (Int16)ODBC32.SQL_PROCEDURETYPE.FUNCTION;
902                     }
903                     else{
904                         throw ADP.InvalidRestrictionValue(OdbcMetaDataCollectionNames.Procedures,"PROCEDURE_TYPE",restrictions[indexOfProcedureType]);
905                     }
906
907                     resultTable = DataTableFromDataReaderProcedures(dataReader, OdbcMetaDataCollectionNames.Procedures,procedureType);
908
909                 }
910
911             }
912
913             finally {
914                 if (dataReader != null) {
915                     dataReader.Dispose();
916                 };
917                 if (command != null) {
918                     command.Dispose();
919                 };
920             }
921             return resultTable;
922         }
923
924         private DataTable GetReservedWordsCollection(string[] restrictions, OdbcConnection connection){
925
926             if (ADP.IsEmptyArray(restrictions) == false){
927                throw ADP.TooManyRestrictions(DbMetaDataCollectionNames.ReservedWords);
928             }
929
930             // verify the existance of the table in the data set
931             DataTable reservedWordsTable = CollectionDataSet.Tables[DbMetaDataCollectionNames.ReservedWords];
932             if (reservedWordsTable == null){
933                 throw ADP.UnableToBuildCollection(DbMetaDataCollectionNames.ReservedWords);
934             }
935
936             // copy the table filtering out any rows that don't apply to tho current version of the prrovider
937             reservedWordsTable = CloneAndFilterCollection(DbMetaDataCollectionNames.ReservedWords, null);
938
939             DataColumn reservedWordColumn = reservedWordsTable.Columns[DbMetaDataColumnNames.ReservedWord];
940             if (reservedWordColumn == null){
941                 throw ADP.UnableToBuildCollection(DbMetaDataCollectionNames.ReservedWords);
942             }
943
944             string keywords = connection.GetInfoStringUnhandled(ODBC32.SQL_INFO.KEYWORDS);
945
946             if (null != keywords) {
947                 string[] values = keywords.Split(KeywordSeparatorChar);
948                 for (int i = 0; i < values.Length; ++i) {
949                     DataRow row = reservedWordsTable.NewRow();
950                     row[reservedWordColumn] = values[i];
951
952                     reservedWordsTable.Rows.Add(row);
953                     row.AcceptChanges();
954                 }
955             }
956
957             return  reservedWordsTable;
958         }
959
960         private DataTable GetTablesCollection(String[] restrictions, OdbcConnection connection, Boolean isTables){
961
962             OdbcCommand command = null;
963             OdbcDataReader dataReader =null;
964             DataTable resultTable = null;
965             const int  tablesRestrictionsCount = 3;
966             const string includedTableTypesTables =  "TABLE,SYSTEM TABLE";
967             const string includedTableTypesViews = "VIEW";
968             string includedTableTypes;
969             string dataTableName;
970
971             try {
972                 //command = (OdbcCommand) connection.CreateCommand();
973                 command = GetCommand(connection);
974                 string [] allArguments = new string[tablesRestrictionsCount+1];
975                 if (isTables == true) {
976                     includedTableTypes = includedTableTypesTables;
977                     dataTableName = OdbcMetaDataCollectionNames.Tables;
978                 }
979                 else {
980                     includedTableTypes = includedTableTypesViews;
981                     dataTableName = OdbcMetaDataCollectionNames.Views;
982                 }
983                 FillOutRestrictions(tablesRestrictionsCount,restrictions,allArguments,dataTableName);
984
985                 allArguments[tablesRestrictionsCount] = includedTableTypes;
986
987                 dataReader = command.ExecuteReaderFromSQLMethod(allArguments, ODBC32.SQL_API.SQLTABLES);
988
989                 resultTable = DataTableFromDataReader(dataReader,dataTableName);
990             }
991
992             finally {
993                 if (dataReader != null) {
994                     dataReader.Dispose();
995                 };
996                 if (command != null) {
997                     command.Dispose();
998                 };
999             }
1000             return resultTable;
1001         }
1002
1003         private Boolean IncludeIndexRow(object rowIndexName,
1004                                         string restrictionIndexName,
1005                                         Int16 rowIndexType) {
1006             // never include table statictics rows
1007             if (rowIndexType == (Int16)ODBC32.SQL_STATISTICSTYPE.TABLE_STAT) {
1008                 return false;
1009             }
1010
1011             if ((restrictionIndexName != null) && (restrictionIndexName != (string)rowIndexName)) {
1012                 return false;
1013             }
1014
1015            return true;
1016         }
1017
1018         private DataTable NewDataTableFromReader(IDataReader reader, out object[] values, string tableName){
1019
1020             DataTable resultTable = new DataTable(tableName);
1021             resultTable.Locale = System.Globalization.CultureInfo.InvariantCulture;
1022             DataTable schemaTable = reader.GetSchemaTable();
1023             foreach (DataRow row in schemaTable.Rows){
1024                 resultTable.Columns.Add(row["ColumnName"] as string, (Type)row["DataType"] as Type);
1025             }
1026
1027             values = new object[resultTable.Columns.Count];
1028             return resultTable;
1029         }
1030
1031         protected override DataTable PrepareCollection(String collectionName, String[] restrictions, DbConnection connection){
1032
1033             DataTable resultTable = null;
1034             OdbcConnection odbcConnection = (OdbcConnection) connection;
1035
1036             if (collectionName == OdbcMetaDataCollectionNames.Tables) {
1037                 resultTable = GetTablesCollection(restrictions, odbcConnection, true);
1038             }
1039             else if (collectionName == OdbcMetaDataCollectionNames.Views) {
1040                 resultTable = GetTablesCollection(restrictions, odbcConnection, false);
1041             }
1042             else if (collectionName == OdbcMetaDataCollectionNames.Columns) {
1043                 resultTable = GetColumnsCollection(restrictions, odbcConnection);
1044             }
1045             else if (collectionName == OdbcMetaDataCollectionNames.Procedures) {
1046                 resultTable = GetProceduresCollection(restrictions, odbcConnection);
1047             }
1048             else if (collectionName == OdbcMetaDataCollectionNames.ProcedureColumns) {
1049                 resultTable = GetProcedureColumnsCollection(restrictions, odbcConnection, true);
1050             }
1051             else if (collectionName == OdbcMetaDataCollectionNames.ProcedureParameters) {
1052                 resultTable = GetProcedureColumnsCollection(restrictions, odbcConnection, false);
1053             }
1054             else if (collectionName == OdbcMetaDataCollectionNames.Indexes) {
1055                 resultTable = GetIndexCollection(restrictions, odbcConnection);
1056             }
1057             else if (collectionName == DbMetaDataCollectionNames.DataTypes) {
1058                 resultTable = GetDataTypesCollection(restrictions, odbcConnection);
1059             }
1060             else if (collectionName == DbMetaDataCollectionNames.DataSourceInformation) {
1061                 resultTable = GetDataSourceInformationCollection(restrictions, odbcConnection);
1062             }
1063             else if (collectionName == DbMetaDataCollectionNames.ReservedWords) {
1064                 resultTable = GetReservedWordsCollection(restrictions, odbcConnection);
1065             }
1066
1067             if (resultTable == null){
1068                throw ADP.UnableToBuildCollection(collectionName);
1069             }
1070
1071             return resultTable;
1072         }
1073
1074
1075
1076    }
1077 }
1078
1079
1080
1081