//------------------------------------------------------------------------------ // // Copyright (c) Microsoft Corporation. All rights reserved. // // Microsoft // Microsoft //------------------------------------------------------------------------------ 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; } } }