File: System\Data\Odbc\OdbcUtils.cs
Project: ndp\fx\src\data\System.Data.csproj (System.Data)
//------------------------------------------------------------------------------
// <copyright file="OdbcUtils.cs" company="Microsoft">
//      Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
// <owner current="true" primary="true">Microsoft</owner>
// <owner current="true" primary="false">Microsoft</owner>
//------------------------------------------------------------------------------
 
using System;
using System.Data;
using System.Data.Common;
using System.Diagnostics;                   // Debug services
using System.Runtime.InteropServices;
using System.Text;
 
namespace System.Data.Odbc
{
    sealed internal class CNativeBuffer : System.Data.ProviderBase.DbBuffer {
 
        internal CNativeBuffer (int initialSize) : base(initialSize) {
        }
 
        internal short ShortLength {
            get {
                return checked((short)Length);
            }
        }
 
        internal  object MarshalToManaged(int offset, ODBC32.SQL_C sqlctype, int cb) {
            object value;
             switch(sqlctype)
            {
                case ODBC32.SQL_C.WCHAR:
                    //Note: We always bind as unicode
                    if(cb == ODBC32.SQL_NTS) {
                        value = PtrToStringUni(offset);
                        break;
                    }
                    Debug.Assert((cb > 0), "Character count negative ");
                    Debug.Assert((Length >= cb), "Native buffer too small ");
                    cb = Math.Min(cb/2, (Length-2)/2);
                    value= PtrToStringUni(offset, cb);
                    break;
 
                case ODBC32.SQL_C.CHAR:
                case ODBC32.SQL_C.BINARY:
                    Debug.Assert((cb > 0), "Character count negative ");
                    Debug.Assert((Length >= cb), "Native buffer too small ");
                    cb = Math.Min(cb, Length);
                    value = ReadBytes(offset, cb);
                    break;
 
                case ODBC32.SQL_C.SSHORT:
                     value = ReadInt16(offset);
                    break;
 
                case ODBC32.SQL_C.SLONG:
                     value = ReadInt32(offset);
                    break;
 
                case ODBC32.SQL_C.SBIGINT:
                     value = ReadInt64(offset);
                    break;
 
                case ODBC32.SQL_C.BIT:
                     Byte b = ReadByte(offset);
                    value = (b != 0x00);
                    break;
 
                case ODBC32.SQL_C.REAL:
                     value = ReadSingle(offset);
                    break;
 
                case ODBC32.SQL_C.DOUBLE:
                     value = ReadDouble(offset);
                    break;
 
                case ODBC32.SQL_C.UTINYINT:
                     value = ReadByte(offset);
                    break;
 
                case ODBC32.SQL_C.GUID:
                     value = ReadGuid(offset);
                    break;
 
                case ODBC32.SQL_C.TYPE_TIMESTAMP:
                    //So we are mapping this ourselves.
                    //typedef struct tagTIMESTAMP_STRUCT
                    //{
                    //      SQLSMALLINT    year;
                    //      SQLUSMALLINT   month;
                    //      SQLUSMALLINT   day;
                    //      SQLUSMALLINT   hour;
                    //      SQLUSMALLINT   minute;
                    //      SQLUSMALLINT   second;
                    //      SQLUINTEGER    fraction;    (billoniths of a second)
                    //}
 
                    value = ReadDateTime(offset);
                    break;
 
                // Note: System does not provide a date-only type
                case ODBC32.SQL_C.TYPE_DATE:
                    //  typedef struct tagDATE_STRUCT
                    //  {
                    //      SQLSMALLINT    year;
                    //      SQLUSMALLINT   month;
                    //      SQLUSMALLINT   day;
                    //  } DATE_STRUCT;
 
                    value = ReadDate(offset);
                    break;
 
                // Note: System does not provide a date-only type
                case ODBC32.SQL_C.TYPE_TIME:
                    //  typedef struct tagTIME_STRUCT
                    //  {
                    //      SQLUSMALLINT   hour;
                    //      SQLUSMALLINT   minute;
                    //      SQLUSMALLINT   second;
                    //  } TIME_STRUCT;
 
                    value = ReadTime(offset);
                    break;
 
                case ODBC32.SQL_C.NUMERIC:
                    //Note: Unfortunatly the ODBC NUMERIC structure and the URT DECIMAL structure do not
                    //align, so we can't so do the typical "PtrToStructure" call (below) like other types
                    //We actually have to go through the pain of pulling our raw bytes and building the decimal
                    //  Marshal.PtrToStructure(buffer, typeof(decimal));
 
                    //So we are mapping this ourselves
                    //typedef struct tagSQL_NUMERIC_STRUCT
                    //{
                    //  SQLCHAR     precision;
                    //  SQLSCHAR    scale;
                    //  SQLCHAR     sign;   /* 1 if positive, 0 if negative */
                    //  SQLCHAR     val[SQL_MAX_NUMERIC_LEN];
                    //} SQL_NUMERIC_STRUCT;
 
                    value = ReadNumeric(offset);
                    break;
 
                default:
                    Debug.Assert (false, "UnknownSQLCType");
                    value = null;
                    break;
            };
             return value;
        }
 
        // if sizeorprecision applies only for wchar and numeric values
        // for wchar the a value of null means take the value's size
        //
        internal void MarshalToNative(int offset, object value, ODBC32.SQL_C sqlctype, int sizeorprecision, int valueOffset) {
            switch(sqlctype) {
                case ODBC32.SQL_C.WCHAR:
                {
                    //Note: We always bind as unicode
                    //Note: StructureToPtr fails indicating string it a non-blittable type
                    //and there is no MarshalStringTo* that moves to an existing buffer,
                    //they all alloc and return a new one, not at all what we want...
 
                    //So we have to copy the raw bytes of the string ourself?!
 
                    Char[] rgChars;
                    int length;
                    Debug.Assert(value is string || value is char[],"Only string or char[] can be marshaled to WCHAR");
 
                    if (value is string) {
                        length = Math.Max(0, ((string)value).Length - valueOffset);
 
                        if ((sizeorprecision > 0)  && (sizeorprecision < length)) {
                            length = sizeorprecision;
                        }
 
                        rgChars = ((string)value).ToCharArray(valueOffset, length);
                        Debug.Assert(rgChars.Length < (base.Length - valueOffset), "attempting to extend parameter buffer!");
 
                        WriteCharArray(offset, rgChars, 0, rgChars.Length);
                        WriteInt16(offset+(rgChars.Length * 2), 0); // Add the null terminator
                    }
                    else {
                        length = Math.Max(0, ((char[])value).Length - valueOffset);
                        if ((sizeorprecision > 0)  && (sizeorprecision < length)) {
                            length = sizeorprecision;
                        }
                        rgChars = (char[])value;
                        Debug.Assert(rgChars.Length < (base.Length - valueOffset), "attempting to extend parameter buffer!");
 
                        WriteCharArray(offset, rgChars, valueOffset, length);
                        WriteInt16(offset+(rgChars.Length * 2), 0); // Add the null terminator
                    }
                    break;
                }
 
                case ODBC32.SQL_C.BINARY:
                case ODBC32.SQL_C.CHAR:
                {
                    Byte[] rgBytes = (Byte[])value;
                    int length = rgBytes.Length;
 
                    Debug.Assert ((valueOffset <= length), "Offset out of Range" );
 
                    // reduce length by the valueOffset
                    //
                    length -= valueOffset;
 
                    // reduce length to be no more than size (if size is given)
                    //
                    if ((sizeorprecision > 0)  && (sizeorprecision < length)) {
                        length = sizeorprecision;
                    }
 
                    //AdjustSize(rgBytes.Length+1);
                    //buffer = DangerousAllocateAndGetHandle();      // Realloc may have changed buffer address
                    Debug.Assert(length < (base.Length - valueOffset), "attempting to extend parameter buffer!");
 
                    WriteBytes(offset, rgBytes, valueOffset, length);
                    break;
                }
 
                case ODBC32.SQL_C.UTINYINT:
                    WriteByte(offset, (Byte)value);
                    break;
 
                case ODBC32.SQL_C.SSHORT:   //Int16
                    WriteInt16(offset, (Int16)value);
                    break;
 
                case ODBC32.SQL_C.SLONG:    //Int32
                    WriteInt32(offset, (Int32)value);
                    break;
 
                case ODBC32.SQL_C.REAL:     //float
                    WriteSingle(offset, (Single)value);
                    break;
 
                case ODBC32.SQL_C.SBIGINT:  //Int64
                    WriteInt64(offset, (Int64)value);
                    break;
 
                case ODBC32.SQL_C.DOUBLE:   //Double
                    WriteDouble(offset, (Double)value);
                    break;
 
                case ODBC32.SQL_C.GUID:     //Guid
                    WriteGuid(offset, (Guid)value);
                    break;
 
                case ODBC32.SQL_C.BIT:
                    WriteByte(offset, (Byte)(((bool)value) ? 1 : 0));
                    break;
 
                case ODBC32.SQL_C.TYPE_TIMESTAMP:
                {
                    //typedef struct tagTIMESTAMP_STRUCT
                    //{
                    //      SQLSMALLINT    year;
                    //      SQLUSMALLINT   month;
                    //      SQLUSMALLINT   day;
                    //      SQLUSMALLINT   hour;
                    //      SQLUSMALLINT   minute;
                    //      SQLUSMALLINT   second;
                    //      SQLUINTEGER    fraction;    (billoniths of a second)
                    //}
 
                    //We have to map this ourselves, due to the different structures between
                    //ODBC TIMESTAMP and URT DateTime, (ie: can't use StructureToPtr)
 
                    WriteODBCDateTime(offset, (DateTime)value);
                    break;
                }
 
                // Note: System does not provide a date-only type
                case ODBC32.SQL_C.TYPE_DATE:
                {
                    //  typedef struct tagDATE_STRUCT
                    //  {
                    //      SQLSMALLINT    year;
                    //      SQLUSMALLINT   month;
                    //      SQLUSMALLINT   day;
                    //  } DATE_STRUCT;
                    
                    WriteDate(offset, (DateTime)value);
                    break;
                }
 
                // Note: System does not provide a date-only type
                case ODBC32.SQL_C.TYPE_TIME:
                {
                    //  typedef struct tagTIME_STRUCT
                    //  {
                    //      SQLUSMALLINT   hour;
                    //      SQLUSMALLINT   minute;
                    //      SQLUSMALLINT   second;
                    //  } TIME_STRUCT;
 
                    WriteTime(offset, (TimeSpan)value);
                    break;
                }
 
                case ODBC32.SQL_C.NUMERIC:
                {
                    WriteNumeric(offset, (Decimal)value, checked((byte)sizeorprecision));
                    break;
                }
 
                default:
                    Debug.Assert (false, "UnknownSQLCType");
                    break;
            }
        }
 
        internal HandleRef PtrOffset(int offset, int length) {
            //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
            // NOTE: You must have called DangerousAddRef before calling this
            //       method, or you run the risk of allowing Handle Recycling
            //       to occur!
            //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
            Validate(offset, length);
 
            IntPtr ptr = ADP.IntPtrOffset(DangerousGetHandle(), offset);
            return new HandleRef(this, ptr);
        }
        
        internal void WriteODBCDateTime(int offset, DateTime value) {
            short[] buffer = new short[6] {
                unchecked((short)value.Year),
                unchecked((short)value.Month),
                unchecked((short)value.Day),
                unchecked((short)value.Hour),
                unchecked((short)value.Minute),
                unchecked((short)value.Second),
            };
            WriteInt16Array(offset, buffer, 0, 6);
            WriteInt32(offset + 12, value.Millisecond*1000000); //fraction
        }
    }
 
 
    sealed internal class CStringTokenizer {
        readonly StringBuilder _token;
        readonly string _sqlstatement;
        readonly char _quote;         // typically the semicolon '"'
        readonly char _escape;        // typically the same char as the quote
        int _len = 0;
        int _idx = 0;
 
        internal CStringTokenizer(string text, char quote, char escape) {
            _token = new StringBuilder();
            _quote = quote;
            _escape = escape;
            _sqlstatement = text;
            if (text != null) {
                int iNul = text.IndexOf('\0');
                _len = (0>iNul)?text.Length:iNul;
            }
            else {
                _len = 0;
            }
        }
 
        internal int CurrentPosition {
              get{ return _idx; }
        }
 
        // Returns the next token in the statement, advancing the current index to
        //  the start of the token
        internal String NextToken() {
            if (_token.Length != 0) {                   // if we've read a token before
                _idx += _token.Length;                  // proceed the internal marker (_idx) behind the token
                _token.Remove(0, _token.Length);        // and start over with a fresh token
            }
 
            while((_idx < _len) && Char.IsWhiteSpace(_sqlstatement[_idx])) {
                // skip whitespace
                _idx++;
            }
 
            if (_idx == _len) {
                // return if string is empty
                return String.Empty;
            }
 
            int curidx = _idx;                          // start with internal index at current index
            bool endtoken = false;                      //
 
            // process characters until we reache the end of the token or the end of the string
            //
            while (!endtoken && curidx < _len) {
                if (IsValidNameChar(_sqlstatement[curidx])) {
                    while ((curidx < _len) && IsValidNameChar(_sqlstatement[curidx])) {
                        _token.Append(_sqlstatement[curidx]);
                        curidx++;
                    }
                }
                else {
                    char currentchar = _sqlstatement[curidx];
                    if (currentchar == '[') {
                        curidx = GetTokenFromBracket(curidx);
                    }
                    else if (' ' != _quote && currentchar == _quote) { // if the ODBC driver does not support quoted identifiers it returns a single blank character
                        curidx = GetTokenFromQuote(curidx);
                    }
                    else {
                        // Some other marker like , ; ( or )
                        // could also be * or ?
                        if (!Char.IsWhiteSpace(currentchar)) {
                            switch (currentchar) {
                                case ',':
                                    // commas are not part of a token so we'll only append them if they are at the beginning
                                    if (curidx == _idx)
                                    _token.Append(currentchar);
                                    break;
                                default:
                                    _token.Append(currentchar);
                                    break;
                            }
                        }
                        endtoken = true;
                        break;
                    }
                }
            }
 
            return (_token.Length > 0) ? _token.ToString() : String.Empty ;
 
        }
 
        private int GetTokenFromBracket(int curidx) {
            Debug.Assert((_sqlstatement[curidx] == '['), "GetTokenFromQuote: character at starting position must be same as quotechar");
            while (curidx < _len) {
                _token.Append(_sqlstatement[curidx]);
                curidx++;
                if (_sqlstatement[curidx-1] == ']')
                    break;
            }
            return curidx;
        }
 
        // attempts to complete an encapsulated token (e.g. "scott")
        // double quotes are valid part of the token (e.g. "foo""bar")
        //        
        private int GetTokenFromQuote(int curidx) {
            Debug.Assert(_quote != ' ', "ODBC driver doesn't support quoted identifiers -- GetTokenFromQuote should not be used in this case");
            Debug.Assert((_sqlstatement[curidx] == _quote), "GetTokenFromQuote: character at starting position must be same as quotechar");
 
            int localidx = curidx;                                  // start with local index at current index
            while (localidx < _len) {                               // run to the end of the statement
                _token.Append(_sqlstatement[localidx]);             // append current character to token
                if (_sqlstatement[localidx] == _quote) {
                    if(localidx > curidx) {                         // don't care for the first char
                        if (_sqlstatement[localidx-1] != _escape) { // if it's not escape we look at the following char
                            if (localidx+1 < _len) {                // do not overrun the end of the string
                                if (_sqlstatement[localidx+1] != _quote) {
                                    return localidx+1;              // We've reached the end of the quoted text
                                }
                            }
                        }
                    }
                }
                localidx++;
            }
            return localidx;
        }
 
        private bool IsValidNameChar(char ch) {
            return (Char.IsLetterOrDigit(ch) ||
                    (ch == '_') || (ch == '-') ||(ch == '.') ||
                    (ch == '$') || (ch == '#') || (ch == '@') ||
                    (ch == '~') || (ch == '`') || (ch == '%') ||
                    (ch == '^') || (ch == '&') || (ch == '|') ) ;
        }
 
        // Searches for the token given, starting from the current position
        // If found, positions the currentindex at the
        // beginning of the token if found.
        internal int FindTokenIndex(String tokenString) {
            String nextToken;
            while (true) {
                nextToken = NextToken();
                if ((_idx == _len) || ADP.IsEmpty(nextToken)) { // fxcop
                    break;
                }
                if (String.Compare(tokenString, nextToken, StringComparison.OrdinalIgnoreCase) == 0) {
                    return _idx;
                }
            }
            return -1;
        }
 
        // Skips the white space found in the beginning of the string.
        internal bool StartsWith(String tokenString) {
            int     tempidx = 0;
            while((tempidx < _len) && Char.IsWhiteSpace(_sqlstatement[tempidx])) {
                tempidx++;
            }
            if ((_len - tempidx) < tokenString.Length) {
                return false;
            }
 
            if (0 == String.Compare(_sqlstatement, tempidx, tokenString, 0, tokenString.Length, StringComparison.OrdinalIgnoreCase)) {
                // Reset current position and token
                _idx = 0;
                NextToken();
                return true;
            }
            return false;
        }
    }
}