1 //------------------------------------------------------------------------------
2 // <copyright file="OdbcMetaDataFactory.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
6 // <owner current="true" primary="true">[....]</owner>
7 // <owner current="true" primary="false">[....]</owner>
9 //------------------------------------------------------------------------------
11 namespace System.Data.Odbc{
16 using System.Collections;
17 using System.Data.Common;
19 using System.Xml.Schema;
20 using System.Diagnostics;
21 using System.Data.ProviderBase;
25 internal class OdbcMetaDataFactory : DbMetaDataFactory{
27 private struct SchemaFunctionName {
28 internal SchemaFunctionName(string schemaName, ODBC32.SQL_API odbcFunction) {
29 _schemaName = schemaName;
30 _odbcFunction = odbcFunction;
32 internal readonly string _schemaName;
33 internal readonly ODBC32.SQL_API _odbcFunction;
36 private const string _collectionName = "CollectionName";
37 private const string _populationMechanism = "PopulationMechanism";
38 private const string _prepareCollection = "PrepareCollection";
40 private readonly SchemaFunctionName[] _schemaMapping;
42 static internal readonly char[] KeywordSeparatorChar = new char[1] { ',' };
45 internal OdbcMetaDataFactory(Stream XMLStream,
47 string serverVersionNormalized,
48 OdbcConnection connection):
49 base(XMLStream, serverVersion, serverVersionNormalized) {
51 // set up the colletion name ODBC function mapping guid mapping
52 _schemaMapping = new SchemaFunctionName[] {
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)};
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);
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);
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);
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
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];
92 foreach (DataRow collection in metaDataCollectionsTable.Rows){
93 if ((string)collection[populationMechanism] == _prepareCollection) {
94 // is the collection in the mapping
96 for (int i = 0; i < _schemaMapping.Length; i++){
97 if (_schemaMapping[i]._schemaName == (string)collection[collectionName]){
102 // no go on to the next collection
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();
117 restrictionsTable.AcceptChanges();
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);
130 if (restrictionsTable != null) {
131 CollectionDataSet.Tables.Remove(CollectionDataSet.Tables[DbMetaDataCollectionNames.Restrictions]);
132 CollectionDataSet.Tables.Add(restrictionsTable);
137 private object BooleanFromODBC(object odbcSource) {
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)
155 private OdbcCommand GetCommand(OdbcConnection connection){
157 OdbcCommand command = connection.CreateCommand();
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;
166 private DataTable DataTableFromDataReader(IDataReader reader, string tableName) {
168 // set up the column structure of the data table from the reader
170 DataTable resultTable = NewDataTableFromReader(reader, out values, tableName);
172 // populate the data table from the data reader
173 while (reader.Read()) {
174 reader.GetValues(values);
175 resultTable.Rows.Add(values);
180 private void DataTableFromDataReaderDataTypes(DataTable dataTypesTable, OdbcDataReader dataReader, OdbcConnection connection) {
182 DataTable schemaTable = null;
185 // Build a DataTable from the reader
186 schemaTable = dataReader.GetSchemaTable();
188 // vstfdevdiv:479715 Handle cases where reader is empty
189 if (null == schemaTable) {
190 throw ADP.OdbcNoTypesFromProvider();
193 object[] getTypeInfoValues = new object[schemaTable.Rows.Count];
194 DataRow dataTypesRow;
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];
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;
232 const int SQL_DATE_V2 = 9;
233 const int SQL_TIME_V2 = 10;
238 while (dataReader.Read()) {
239 dataReader.GetValues(getTypeInfoValues);
240 dataTypesRow = dataTypesTable.NewRow();
242 ODBC32.SQL_TYPE sqlType;
244 dataTypesRow[typeNameColumn] = getTypeInfoValues[indexTYPE_NAME];
245 dataTypesRow[SQLTypeNameColumn] = getTypeInfoValues[indexDATA_TYPE];
247 sqlType = (ODBC32.SQL_TYPE)(Int32) Convert.ChangeType(getTypeInfoValues[indexDATA_TYPE],
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;
256 else if ((int)sqlType == SQL_TIME_V2) {
257 sqlType = ODBC32.SQL_TYPE.TYPE_TIME;
261 typeMap = TypeMap.FromSqlType(sqlType);
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) {
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
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;
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;
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;
320 case ODBC32.SQL_TYPE.SS_UDT:
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
330 dataTypesRow[columnSizeColumn] = getTypeInfoValues[indexCOLUMN_SIZE];
331 dataTypesRow[createParametersColumn] = getTypeInfoValues[indexCREATE_PARAMS];
333 if ((getTypeInfoValues[indexAUTO_UNIQUE_VALUE] == DBNull.Value) ||
334 (Convert.ToInt16(getTypeInfoValues[indexAUTO_UNIQUE_VALUE], null) == 0)) {
335 dataTypesRow[isAutoIncermentableColumn] = false;
338 dataTypesRow[isAutoIncermentableColumn] = true;
341 dataTypesRow[isCaseSensitiveColumn] = BooleanFromODBC(getTypeInfoValues[indexCASE_SENSITIVE]);
342 dataTypesRow[isFixedPrecisionScaleColumn] = BooleanFromODBC(getTypeInfoValues[indexFIXED_PREC_SCALE]);
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)) {
348 case ODBC32.SQL_NULLABILITY.NO_NULLS:
349 dataTypesRow[isNullableColumn] = false;
352 case ODBC32.SQL_NULLABILITY.NULLABLE:
353 dataTypesRow[isNullableColumn] = true;
356 case ODBC32.SQL_NULLABILITY.UNKNOWN:
357 dataTypesRow[isNullableColumn] = DBNull.Value;
362 if ( DBNull.Value != getTypeInfoValues[indexSEARCHABLE]){
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){
368 case (Int16)ODBC32.SQL_SEARCHABLE.UNSEARCHABLE:
369 dataTypesRow[isSearchableColumn] = false;
370 dataTypesRow[isSearchableWithLikeColumn] = false;
373 case (Int16)ODBC32.SQL_SEARCHABLE.LIKE_ONLY:
374 dataTypesRow[isSearchableColumn] = false;
375 dataTypesRow[isSearchableWithLikeColumn] = true;
378 case (Int16)ODBC32.SQL_SEARCHABLE.ALL_EXCEPT_LIKE:
379 dataTypesRow[isSearchableColumn] = true;
380 dataTypesRow[isSearchableWithLikeColumn] = false;
383 case (Int16)ODBC32.SQL_SEARCHABLE.SEARCHABLE:
384 dataTypesRow[isSearchableColumn] = true;
385 dataTypesRow[isSearchableWithLikeColumn] = true;
390 dataTypesRow[isUnsignedColumn] = BooleanFromODBC(getTypeInfoValues[indexUNSIGNED_ATTRIBUTE]);
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];
397 if (getTypeInfoValues[indexMINIMUM_SCALE] != DBNull.Value ) {
398 dataTypesRow[minimumScaleColumn] = getTypeInfoValues[indexMINIMUM_SCALE];
401 if (getTypeInfoValues[indexLITERAL_PREFIX] != DBNull.Value ) {
402 dataTypesRow[literalPrefixColumn] = getTypeInfoValues[indexLITERAL_PREFIX];
405 if (getTypeInfoValues[indexLITERAL_SUFFIX] != DBNull.Value ) {
406 dataTypesRow[literalSuffixColumn] = getTypeInfoValues[indexLITERAL_SUFFIX];
409 dataTypesTable.Rows.Add(dataTypesRow);
414 private DataTable DataTableFromDataReaderIndex(IDataReader reader,
416 string restrictionIndexName) {
418 // set up the column structure of the data table from the reader
420 DataTable resultTable = NewDataTableFromReader(reader, out values, tableName);
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);
436 private DataTable DataTableFromDataReaderProcedureColumns(IDataReader reader, string tableName,Boolean isColumn) {
438 // set up the column structure of the data table from the reader
440 DataTable resultTable = NewDataTableFromReader(reader, out values, tableName);
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);
457 private DataTable DataTableFromDataReaderProcedures(IDataReader reader, string tableName,Int16 procedureType) {
459 // Build a DataTable from the reader
461 // set up the column structure of the data table from the reader
463 DataTable resultTable = NewDataTableFromReader(reader, out values, tableName);
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);
479 private void FillOutRestrictions(int restrictionsCount, string[] restrictions, object[] allRestrictions, string collectionName) {
481 Debug.Assert(allRestrictions.Length >= restrictionsCount);
485 // if we have restrictions put them in the restrictions array
486 if (restrictions != null) {
488 if (restrictions.Length > restrictionsCount) {
489 throw ADP.TooManyRestrictions(collectionName);
492 for (i=0; i < restrictions.Length; i++) {
493 if (restrictions[i] != null) {
494 allRestrictions[i] = restrictions[i];
499 // initalize the rest to no restrictions
500 for (; i < restrictionsCount; i++) {
501 allRestrictions[i] = null;
506 private DataTable GetColumnsCollection(String[] restrictions, OdbcConnection connection){
508 OdbcCommand command = null;
509 OdbcDataReader dataReader =null;
510 DataTable resultTable = null;
511 const int columnsRestrictionsCount = 4;
514 command = GetCommand(connection);
515 String[] allRestrictions = new string[columnsRestrictionsCount];
516 FillOutRestrictions(columnsRestrictionsCount,restrictions,allRestrictions,OdbcMetaDataCollectionNames.Columns);
518 dataReader = command.ExecuteReaderFromSQLMethod(allRestrictions, ODBC32.SQL_API.SQLCOLUMNS);
520 resultTable = DataTableFromDataReader(dataReader, OdbcMetaDataCollectionNames.Columns);
524 if (dataReader != null) {
525 dataReader.Dispose();
527 if (command != null) {
535 private DataTable GetDataSourceInformationCollection(string [] restrictions,
536 OdbcConnection connection){
538 if (ADP.IsEmptyArray(restrictions) == false) {
539 throw ADP.TooManyRestrictions(DbMetaDataCollectionNames.DataSourceInformation);
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);
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);
551 // after filtering there better be just one row
552 if (dataSourceInformationTable.Rows.Count != 1){
553 throw ADP.IncorrectNumberOfDataSourceInformationRows();
555 DataRow dataSourceInformation = dataSourceInformationTable.Rows[0];
560 ODBC32.RetCode retcode;
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();
571 stringValue = connection.GetInfoStringUnhandled(ODBC32.SQL_INFO.DBMS_NAME);
572 if (stringValue != null) {
573 dataSourceInformation[DbMetaDataColumnNames.DataSourceProductName] = stringValue;
577 // update the server version strings
578 dataSourceInformation[DbMetaDataColumnNames.DataSourceProductVersion] = ServerVersion;
579 dataSourceInformation[DbMetaDataColumnNames.DataSourceProductVersionNormalized] = ServerVersionNormalized;
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;
587 // determine the supportedJoinOperators
588 // leave the column null if the GetInfo fails. There is no explicit value for
590 if (connection.IsV3Driver) {
592 retcode = connection.GetInfoInt32Unhandled(ODBC32.SQL_INFO.SQL_OJ_CAPABILITIES_30,out int32Value);
595 retcode = connection.GetInfoInt32Unhandled(ODBC32.SQL_INFO.SQL_OJ_CAPABILITIES_20, out int32Value);
598 if ((retcode == ODBC32.RetCode.SUCCESS) || (retcode == ODBC32.RetCode.SUCCESS_WITH_INFO)){
600 Common. SupportedJoinOperators supportedJoinOperators = Common.SupportedJoinOperators.None;
601 if ((int32Value & (Int32)ODBC32.SQL_OJ_CAPABILITIES.LEFT) != 0){
602 supportedJoinOperators = supportedJoinOperators | Common.SupportedJoinOperators.LeftOuter;
604 if ((int32Value & (Int32)ODBC32.SQL_OJ_CAPABILITIES.RIGHT) != 0){
605 supportedJoinOperators = supportedJoinOperators | Common.SupportedJoinOperators.RightOuter;
607 if ((int32Value & (Int32)ODBC32.SQL_OJ_CAPABILITIES.FULL) != 0){
608 supportedJoinOperators = supportedJoinOperators | Common.SupportedJoinOperators.FullOuter;
610 if ((int32Value & (Int32)ODBC32.SQL_OJ_CAPABILITIES.INNER) != 0){
611 supportedJoinOperators = supportedJoinOperators | Common.SupportedJoinOperators.Inner;
614 dataSourceInformation[DbMetaDataColumnNames.SupportedJoinOperators] = supportedJoinOperators;
617 // determine the GroupByBehavior
618 retcode = connection.GetInfoInt16Unhandled(ODBC32.SQL_INFO.GROUP_BY, out int16Value);
619 Common.GroupByBehavior groupByBehavior = Common.GroupByBehavior.Unknown;
621 if ((retcode == ODBC32.RetCode.SUCCESS) || (retcode == ODBC32.RetCode.SUCCESS_WITH_INFO)){
623 switch (int16Value) {
625 case (Int16)ODBC32.SQL_GROUP_BY.NOT_SUPPORTED:
626 groupByBehavior = Common.GroupByBehavior.NotSupported;
629 case (Int16)ODBC32.SQL_GROUP_BY.GROUP_BY_EQUALS_SELECT:
630 groupByBehavior = Common.GroupByBehavior.ExactMatch;
633 case (Int16)ODBC32.SQL_GROUP_BY.GROUP_BY_CONTAINS_SELECT:
634 groupByBehavior = Common.GroupByBehavior.MustContainAll;
637 case (Int16)ODBC32.SQL_GROUP_BY.NO_RELATION:
638 groupByBehavior = Common.GroupByBehavior.Unrelated;
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;
648 dataSourceInformation[DbMetaDataColumnNames.GroupByBehavior] = groupByBehavior;
650 // determine the identifier case
651 retcode = connection.GetInfoInt16Unhandled(ODBC32.SQL_INFO.IDENTIFIER_CASE, out int16Value);
652 Common.IdentifierCase identifierCase = Common.IdentifierCase.Unknown;
654 if ((retcode == ODBC32.RetCode.SUCCESS) || (retcode == ODBC32.RetCode.SUCCESS_WITH_INFO)){
656 switch (int16Value) {
658 case (Int16)ODBC32.SQL_IDENTIFIER_CASE.SENSITIVE:
659 identifierCase = Common.IdentifierCase.Sensitive;
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;
670 dataSourceInformation[DbMetaDataColumnNames.IdentifierCase] = identifierCase;
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;
678 else if (stringValue == "N") {
679 dataSourceInformation[DbMetaDataColumnNames.OrderByColumnsInSelect] = false;
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);
687 if (stringValue != null){
689 // by spec a blank identifier quote char indicates that the provider does not suppport
690 // quoted identifiers
691 if (stringValue != " ") {
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;
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();
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;
718 if ((retcode == ODBC32.RetCode.SUCCESS) || (retcode == ODBC32.RetCode.SUCCESS_WITH_INFO)){
720 switch (int16Value) {
722 case (Int16)ODBC32.SQL_IDENTIFIER_CASE.SENSITIVE:
723 quotedIdentifierCase = Common.IdentifierCase.Sensitive;
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;
734 dataSourceInformation[DbMetaDataColumnNames.QuotedIdentifierCase] = quotedIdentifierCase;
736 dataSourceInformationTable.AcceptChanges();
738 return dataSourceInformationTable;
742 private DataTable GetDataTypesCollection(String[] restrictions, OdbcConnection connection){
744 if (ADP.IsEmptyArray(restrictions) == false){
745 throw ADP.TooManyRestrictions(DbMetaDataCollectionNames.DataTypes);
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);
756 // copy the data table it
757 dataTypesTable = CloneAndFilterCollection(DbMetaDataCollectionNames.DataTypes, null);
759 OdbcCommand command = null;
760 OdbcDataReader dataReader =null;
761 object[] allArguments = new object[1];
762 allArguments[0] = ODBC32.SQL_ALL_TYPES;
765 command = GetCommand(connection);
768 dataReader = command.ExecuteReaderFromSQLMethod(allArguments, ODBC32.SQL_API.SQLGETTYPEINFO);
770 DataTableFromDataReaderDataTypes(dataTypesTable,dataReader,connection);
774 if (dataReader != null) {
775 dataReader.Dispose();
777 if (command != null) {
781 dataTypesTable.AcceptChanges();
782 return dataTypesTable;
785 private DataTable GetIndexCollection(String[] restrictions, OdbcConnection connection){
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;
796 command = GetCommand(connection);
797 object[] allRestrictions = new object[nativeRestrictionsCount];
798 FillOutRestrictions(indexRestrictionsCount,restrictions,allRestrictions,OdbcMetaDataCollectionNames.Indexes);
800 if (allRestrictions[indexOfTableName] == null) {
801 throw ODBC.GetSchemaRestrictionRequired();
804 allRestrictions[3] = (Int16)ODBC32.SQL_INDEX.ALL;
805 allRestrictions[4] = (Int16)ODBC32.SQL_STATISTICS_RESERVED.ENSURE;
807 dataReader = command.ExecuteReaderFromSQLMethod(allRestrictions, ODBC32.SQL_API.SQLSTATISTICS);
809 string indexName = null;
810 if (restrictions != null) {
812 if (restrictions.Length >= indexOfIndexName+1) {
813 indexName = restrictions[indexOfIndexName];
817 resultTable = DataTableFromDataReaderIndex(dataReader,
818 OdbcMetaDataCollectionNames.Indexes,
823 if (dataReader != null) {
824 dataReader.Dispose();
826 if (command != null) {
833 private DataTable GetProcedureColumnsCollection(String[] restrictions, OdbcConnection connection,Boolean isColumns){
835 OdbcCommand command = null;
836 OdbcDataReader dataReader =null;
837 DataTable resultTable = null;
838 const int procedureColumnsRestrictionsCount = 4;
841 command = GetCommand(connection);
842 String[] allRestrictions = new string[procedureColumnsRestrictionsCount];
843 FillOutRestrictions(procedureColumnsRestrictionsCount,restrictions, allRestrictions,OdbcMetaDataCollectionNames.Columns);
845 dataReader = command.ExecuteReaderFromSQLMethod(allRestrictions,ODBC32.SQL_API.SQLPROCEDURECOLUMNS);
847 string collectionName;
848 if (isColumns == true) {
849 collectionName = OdbcMetaDataCollectionNames.ProcedureColumns;
852 collectionName = OdbcMetaDataCollectionNames.ProcedureParameters;
854 resultTable = DataTableFromDataReaderProcedureColumns(dataReader,
860 if (dataReader != null) {
861 dataReader.Dispose();
863 if (command != null) {
870 private DataTable GetProceduresCollection(String[] restrictions, OdbcConnection connection){
872 OdbcCommand command = null;
873 OdbcDataReader dataReader =null;
874 DataTable resultTable = null;
875 const int columnsRestrictionsCount = 4;
876 const int indexOfProcedureType = 3;
879 command = GetCommand(connection);
880 String[] allRestrictions = new string[columnsRestrictionsCount];
881 FillOutRestrictions(columnsRestrictionsCount,restrictions, allRestrictions,OdbcMetaDataCollectionNames.Procedures);
884 dataReader = command.ExecuteReaderFromSQLMethod(allRestrictions,ODBC32.SQL_API.SQLPROCEDURES);
886 if (allRestrictions[indexOfProcedureType] == null) {
887 resultTable = DataTableFromDataReader(dataReader, OdbcMetaDataCollectionNames.Procedures);
891 if ((restrictions[indexOfProcedureType] == "SQL_PT_UNKNOWN") ||
892 (restrictions[indexOfProcedureType] == "0" /*ODBC32.SQL_PROCEDURETYPE.UNKNOWN*/)) {
893 procedureType = (Int16)ODBC32.SQL_PROCEDURETYPE.UNKNOWN;
895 else if ((restrictions[indexOfProcedureType] == "SQL_PT_PROCEDURE") ||
896 (restrictions[indexOfProcedureType] == "1" /*ODBC32.SQL_PROCEDURETYPE.PROCEDURE*/)) {
897 procedureType = (Int16)ODBC32.SQL_PROCEDURETYPE.PROCEDURE;
899 else if ((restrictions[indexOfProcedureType] == "SQL_PT_FUNCTION") ||
900 (restrictions[indexOfProcedureType] == "2" /*ODBC32.SQL_PROCEDURETYPE.FUNCTION*/)) {
901 procedureType = (Int16)ODBC32.SQL_PROCEDURETYPE.FUNCTION;
904 throw ADP.InvalidRestrictionValue(OdbcMetaDataCollectionNames.Procedures,"PROCEDURE_TYPE",restrictions[indexOfProcedureType]);
907 resultTable = DataTableFromDataReaderProcedures(dataReader, OdbcMetaDataCollectionNames.Procedures,procedureType);
914 if (dataReader != null) {
915 dataReader.Dispose();
917 if (command != null) {
924 private DataTable GetReservedWordsCollection(string[] restrictions, OdbcConnection connection){
926 if (ADP.IsEmptyArray(restrictions) == false){
927 throw ADP.TooManyRestrictions(DbMetaDataCollectionNames.ReservedWords);
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);
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);
939 DataColumn reservedWordColumn = reservedWordsTable.Columns[DbMetaDataColumnNames.ReservedWord];
940 if (reservedWordColumn == null){
941 throw ADP.UnableToBuildCollection(DbMetaDataCollectionNames.ReservedWords);
944 string keywords = connection.GetInfoStringUnhandled(ODBC32.SQL_INFO.KEYWORDS);
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];
952 reservedWordsTable.Rows.Add(row);
957 return reservedWordsTable;
960 private DataTable GetTablesCollection(String[] restrictions, OdbcConnection connection, Boolean isTables){
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;
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;
980 includedTableTypes = includedTableTypesViews;
981 dataTableName = OdbcMetaDataCollectionNames.Views;
983 FillOutRestrictions(tablesRestrictionsCount,restrictions,allArguments,dataTableName);
985 allArguments[tablesRestrictionsCount] = includedTableTypes;
987 dataReader = command.ExecuteReaderFromSQLMethod(allArguments, ODBC32.SQL_API.SQLTABLES);
989 resultTable = DataTableFromDataReader(dataReader,dataTableName);
993 if (dataReader != null) {
994 dataReader.Dispose();
996 if (command != null) {
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) {
1011 if ((restrictionIndexName != null) && (restrictionIndexName != (string)rowIndexName)) {
1018 private DataTable NewDataTableFromReader(IDataReader reader, out object[] values, string tableName){
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);
1027 values = new object[resultTable.Columns.Count];
1031 protected override DataTable PrepareCollection(String collectionName, String[] restrictions, DbConnection connection){
1033 DataTable resultTable = null;
1034 OdbcConnection odbcConnection = (OdbcConnection) connection;
1036 if (collectionName == OdbcMetaDataCollectionNames.Tables) {
1037 resultTable = GetTablesCollection(restrictions, odbcConnection, true);
1039 else if (collectionName == OdbcMetaDataCollectionNames.Views) {
1040 resultTable = GetTablesCollection(restrictions, odbcConnection, false);
1042 else if (collectionName == OdbcMetaDataCollectionNames.Columns) {
1043 resultTable = GetColumnsCollection(restrictions, odbcConnection);
1045 else if (collectionName == OdbcMetaDataCollectionNames.Procedures) {
1046 resultTable = GetProceduresCollection(restrictions, odbcConnection);
1048 else if (collectionName == OdbcMetaDataCollectionNames.ProcedureColumns) {
1049 resultTable = GetProcedureColumnsCollection(restrictions, odbcConnection, true);
1051 else if (collectionName == OdbcMetaDataCollectionNames.ProcedureParameters) {
1052 resultTable = GetProcedureColumnsCollection(restrictions, odbcConnection, false);
1054 else if (collectionName == OdbcMetaDataCollectionNames.Indexes) {
1055 resultTable = GetIndexCollection(restrictions, odbcConnection);
1057 else if (collectionName == DbMetaDataCollectionNames.DataTypes) {
1058 resultTable = GetDataTypesCollection(restrictions, odbcConnection);
1060 else if (collectionName == DbMetaDataCollectionNames.DataSourceInformation) {
1061 resultTable = GetDataSourceInformationCollection(restrictions, odbcConnection);
1063 else if (collectionName == DbMetaDataCollectionNames.ReservedWords) {
1064 resultTable = GetReservedWordsCollection(restrictions, odbcConnection);
1067 if (resultTable == null){
1068 throw ADP.UnableToBuildCollection(collectionName);