1 //------------------------------------------------------------------------------
2 // <copyright file="OdbcUtils.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
5 // <owner current="true" primary="true">Microsoft</owner>
6 // <owner current="true" primary="false">Microsoft</owner>
7 //------------------------------------------------------------------------------
11 using System
.Data
.Common
;
12 using System
.Diagnostics
; // Debug services
13 using System
.Runtime
.InteropServices
;
16 namespace System
.Data
.Odbc
18 sealed internal class CNativeBuffer
: System
.Data
.ProviderBase
.DbBuffer
{
20 internal CNativeBuffer (int initialSize
) : base(initialSize
) {
23 internal short ShortLength
{
25 return checked((short)Length
);
29 internal object MarshalToManaged(int offset
, ODBC32
.SQL_C sqlctype
, int cb
) {
33 case ODBC32
.SQL_C
.WCHAR
:
34 //Note: We always bind as unicode
35 if(cb
== ODBC32
.SQL_NTS
) {
36 value = PtrToStringUni(offset
);
39 Debug
.Assert((cb
> 0), "Character count negative ");
40 Debug
.Assert((Length
>= cb
), "Native buffer too small ");
41 cb
= Math
.Min(cb
/2, (Length
-2)/2);
42 value= PtrToStringUni(offset
, cb
);
45 case ODBC32
.SQL_C
.CHAR
:
46 case ODBC32
.SQL_C
.BINARY
:
47 Debug
.Assert((cb
> 0), "Character count negative ");
48 Debug
.Assert((Length
>= cb
), "Native buffer too small ");
49 cb
= Math
.Min(cb
, Length
);
50 value = ReadBytes(offset
, cb
);
53 case ODBC32
.SQL_C
.SSHORT
:
54 value = ReadInt16(offset
);
57 case ODBC32
.SQL_C
.SLONG
:
58 value = ReadInt32(offset
);
61 case ODBC32
.SQL_C
.SBIGINT
:
62 value = ReadInt64(offset
);
65 case ODBC32
.SQL_C
.BIT
:
66 Byte b
= ReadByte(offset
);
70 case ODBC32
.SQL_C
.REAL
:
71 value = ReadSingle(offset
);
74 case ODBC32
.SQL_C
.DOUBLE
:
75 value = ReadDouble(offset
);
78 case ODBC32
.SQL_C
.UTINYINT
:
79 value = ReadByte(offset
);
82 case ODBC32
.SQL_C
.GUID
:
83 value = ReadGuid(offset
);
86 case ODBC32
.SQL_C
.TYPE_TIMESTAMP
:
87 //So we are mapping this ourselves.
88 //typedef struct tagTIMESTAMP_STRUCT
91 // SQLUSMALLINT month;
94 // SQLUSMALLINT minute;
95 // SQLUSMALLINT second;
96 // SQLUINTEGER fraction; (billoniths of a second)
99 value = ReadDateTime(offset
);
102 // Note: System does not provide a date-only type
103 case ODBC32
.SQL_C
.TYPE_DATE
:
104 // typedef struct tagDATE_STRUCT
107 // SQLUSMALLINT month;
111 value = ReadDate(offset
);
114 // Note: System does not provide a date-only type
115 case ODBC32
.SQL_C
.TYPE_TIME
:
116 // typedef struct tagTIME_STRUCT
118 // SQLUSMALLINT hour;
119 // SQLUSMALLINT minute;
120 // SQLUSMALLINT second;
123 value = ReadTime(offset
);
126 case ODBC32
.SQL_C
.NUMERIC
:
127 //Note: Unfortunatly the ODBC NUMERIC structure and the URT DECIMAL structure do not
128 //align, so we can't so do the typical "PtrToStructure" call (below) like other types
129 //We actually have to go through the pain of pulling our raw bytes and building the decimal
130 // Marshal.PtrToStructure(buffer, typeof(decimal));
132 //So we are mapping this ourselves
133 //typedef struct tagSQL_NUMERIC_STRUCT
135 // SQLCHAR precision;
137 // SQLCHAR sign; /* 1 if positive, 0 if negative */
138 // SQLCHAR val[SQL_MAX_NUMERIC_LEN];
139 //} SQL_NUMERIC_STRUCT;
141 value = ReadNumeric(offset
);
145 Debug
.Assert (false, "UnknownSQLCType");
152 // if sizeorprecision applies only for wchar and numeric values
153 // for wchar the a value of null means take the value's size
155 internal void MarshalToNative(int offset
, object value, ODBC32
.SQL_C sqlctype
, int sizeorprecision
, int valueOffset
) {
157 case ODBC32
.SQL_C
.WCHAR
:
159 //Note: We always bind as unicode
160 //Note: StructureToPtr fails indicating string it a non-blittable type
161 //and there is no MarshalStringTo* that moves to an existing buffer,
162 //they all alloc and return a new one, not at all what we want...
164 //So we have to copy the raw bytes of the string ourself?!
168 Debug
.Assert(value is string || value is char[],"Only string or char[] can be marshaled to WCHAR");
170 if (value is string) {
171 length
= Math
.Max(0, ((string)value).Length
- valueOffset
);
173 if ((sizeorprecision
> 0) && (sizeorprecision
< length
)) {
174 length
= sizeorprecision
;
177 rgChars
= ((string)value).ToCharArray(valueOffset
, length
);
178 Debug
.Assert(rgChars
.Length
< (base.Length
- valueOffset
), "attempting to extend parameter buffer!");
180 WriteCharArray(offset
, rgChars
, 0, rgChars
.Length
);
181 WriteInt16(offset
+(rgChars
.Length
* 2), 0); // Add the null terminator
184 length
= Math
.Max(0, ((char[])value).Length
- valueOffset
);
185 if ((sizeorprecision
> 0) && (sizeorprecision
< length
)) {
186 length
= sizeorprecision
;
188 rgChars
= (char[])value;
189 Debug
.Assert(rgChars
.Length
< (base.Length
- valueOffset
), "attempting to extend parameter buffer!");
191 WriteCharArray(offset
, rgChars
, valueOffset
, length
);
192 WriteInt16(offset
+(rgChars
.Length
* 2), 0); // Add the null terminator
197 case ODBC32
.SQL_C
.BINARY
:
198 case ODBC32
.SQL_C
.CHAR
:
200 Byte
[] rgBytes
= (Byte
[])value;
201 int length
= rgBytes
.Length
;
203 Debug
.Assert ((valueOffset
<= length
), "Offset out of Range" );
205 // reduce length by the valueOffset
207 length
-= valueOffset
;
209 // reduce length to be no more than size (if size is given)
211 if ((sizeorprecision
> 0) && (sizeorprecision
< length
)) {
212 length
= sizeorprecision
;
215 //AdjustSize(rgBytes.Length+1);
216 //buffer = DangerousAllocateAndGetHandle(); // Realloc may have changed buffer address
217 Debug
.Assert(length
< (base.Length
- valueOffset
), "attempting to extend parameter buffer!");
219 WriteBytes(offset
, rgBytes
, valueOffset
, length
);
223 case ODBC32
.SQL_C
.UTINYINT
:
224 WriteByte(offset
, (Byte
)value);
227 case ODBC32
.SQL_C
.SSHORT
: //Int16
228 WriteInt16(offset
, (Int16
)value);
231 case ODBC32
.SQL_C
.SLONG
: //Int32
232 WriteInt32(offset
, (Int32
)value);
235 case ODBC32
.SQL_C
.REAL
: //float
236 WriteSingle(offset
, (Single
)value);
239 case ODBC32
.SQL_C
.SBIGINT
: //Int64
240 WriteInt64(offset
, (Int64
)value);
243 case ODBC32
.SQL_C
.DOUBLE
: //Double
244 WriteDouble(offset
, (Double
)value);
247 case ODBC32
.SQL_C
.GUID
: //Guid
248 WriteGuid(offset
, (Guid
)value);
251 case ODBC32
.SQL_C
.BIT
:
252 WriteByte(offset
, (Byte
)(((bool)value) ? 1 : 0));
255 case ODBC32
.SQL_C
.TYPE_TIMESTAMP
:
257 //typedef struct tagTIMESTAMP_STRUCT
260 // SQLUSMALLINT month;
262 // SQLUSMALLINT hour;
263 // SQLUSMALLINT minute;
264 // SQLUSMALLINT second;
265 // SQLUINTEGER fraction; (billoniths of a second)
268 //We have to map this ourselves, due to the different structures between
269 //ODBC TIMESTAMP and URT DateTime, (ie: can't use StructureToPtr)
271 WriteODBCDateTime(offset
, (DateTime
)value);
275 // Note: System does not provide a date-only type
276 case ODBC32
.SQL_C
.TYPE_DATE
:
278 // typedef struct tagDATE_STRUCT
281 // SQLUSMALLINT month;
285 WriteDate(offset
, (DateTime
)value);
289 // Note: System does not provide a date-only type
290 case ODBC32
.SQL_C
.TYPE_TIME
:
292 // typedef struct tagTIME_STRUCT
294 // SQLUSMALLINT hour;
295 // SQLUSMALLINT minute;
296 // SQLUSMALLINT second;
299 WriteTime(offset
, (TimeSpan
)value);
303 case ODBC32
.SQL_C
.NUMERIC
:
305 WriteNumeric(offset
, (Decimal
)value, checked((byte)sizeorprecision
));
310 Debug
.Assert (false, "UnknownSQLCType");
315 internal HandleRef
PtrOffset(int offset
, int length
) {
316 //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
317 // NOTE: You must have called DangerousAddRef before calling this
318 // method, or you run the risk of allowing Handle Recycling
320 //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
321 Validate(offset
, length
);
323 IntPtr ptr
= ADP
.IntPtrOffset(DangerousGetHandle(), offset
);
324 return new HandleRef(this, ptr
);
327 internal void WriteODBCDateTime(int offset
, DateTime
value) {
328 short[] buffer
= new short[6] {
329 unchecked((short)value.Year
),
330 unchecked((short)value.Month
),
331 unchecked((short)value.Day
),
332 unchecked((short)value.Hour
),
333 unchecked((short)value.Minute
),
334 unchecked((short)value.Second
),
336 WriteInt16Array(offset
, buffer
, 0, 6);
337 WriteInt32(offset
+ 12, value.Millisecond
*1000000); //fraction
342 sealed internal class CStringTokenizer
{
343 readonly StringBuilder _token
;
344 readonly string _sqlstatement
;
345 readonly char _quote
; // typically the semicolon '"'
346 readonly char _escape
; // typically the same char as the quote
350 internal CStringTokenizer(string text
, char quote
, char escape
) {
351 _token
= new StringBuilder();
354 _sqlstatement
= text
;
356 int iNul
= text
.IndexOf('\0');
357 _len
= (0>iNul
)?text
.Length
:iNul
;
364 internal int CurrentPosition
{
368 // Returns the next token in the statement, advancing the current index to
369 // the start of the token
370 internal String
NextToken() {
371 if (_token
.Length
!= 0) { // if we've read a token before
372 _idx
+= _token
.Length
; // proceed the internal marker (_idx) behind the token
373 _token
.Remove(0, _token
.Length
); // and start over with a fresh token
376 while((_idx
< _len
) && Char
.IsWhiteSpace(_sqlstatement
[_idx
])) {
382 // return if string is empty
386 int curidx
= _idx
; // start with internal index at current index
387 bool endtoken
= false; //
389 // process characters until we reache the end of the token or the end of the string
391 while (!endtoken
&& curidx
< _len
) {
392 if (IsValidNameChar(_sqlstatement
[curidx
])) {
393 while ((curidx
< _len
) && IsValidNameChar(_sqlstatement
[curidx
])) {
394 _token
.Append(_sqlstatement
[curidx
]);
399 char currentchar
= _sqlstatement
[curidx
];
400 if (currentchar
== '[') {
401 curidx
= GetTokenFromBracket(curidx
);
403 else if (' ' != _quote
&& currentchar
== _quote
) { // if the ODBC driver does not support quoted identifiers it returns a single blank character
404 curidx
= GetTokenFromQuote(curidx
);
407 // Some other marker like , ; ( or )
408 // could also be * or ?
409 if (!Char
.IsWhiteSpace(currentchar
)) {
410 switch (currentchar
) {
412 // commas are not part of a token so we'll only append them if they are at the beginning
414 _token
.Append(currentchar
);
417 _token
.Append(currentchar
);
427 return (_token
.Length
> 0) ? _token
.ToString() : String
.Empty
;
431 private int GetTokenFromBracket(int curidx
) {
432 Debug
.Assert((_sqlstatement
[curidx
] == '['), "GetTokenFromQuote: character at starting position must be same as quotechar");
433 while (curidx
< _len
) {
434 _token
.Append(_sqlstatement
[curidx
]);
436 if (_sqlstatement
[curidx
-1] == ']')
442 // attempts to complete an encapsulated token (e.g. "scott")
443 // double quotes are valid part of the token (e.g. "foo""bar")
445 private int GetTokenFromQuote(int curidx
) {
446 Debug
.Assert(_quote
!= ' ', "ODBC driver doesn't support quoted identifiers -- GetTokenFromQuote should not be used in this case");
447 Debug
.Assert((_sqlstatement
[curidx
] == _quote
), "GetTokenFromQuote: character at starting position must be same as quotechar");
449 int localidx
= curidx
; // start with local index at current index
450 while (localidx
< _len
) { // run to the end of the statement
451 _token
.Append(_sqlstatement
[localidx
]); // append current character to token
452 if (_sqlstatement
[localidx
] == _quote
) {
453 if(localidx
> curidx
) { // don't care for the first char
454 if (_sqlstatement
[localidx
-1] != _escape
) { // if it's not escape we look at the following char
455 if (localidx
+1 < _len
) { // do not overrun the end of the string
456 if (_sqlstatement
[localidx
+1] != _quote
) {
457 return localidx
+1; // We've reached the end of the quoted text
468 private bool IsValidNameChar(char ch
) {
469 return (Char
.IsLetterOrDigit(ch
) ||
470 (ch
== '_') || (ch
== '-') ||(ch
== '.') ||
471 (ch
== '$') || (ch
== '#') || (ch
== '@') ||
472 (ch
== '~') || (ch
== '`') || (ch
== '%') ||
473 (ch
== '^') || (ch
== '&') || (ch
== '|') ) ;
476 // Searches for the token given, starting from the current position
477 // If found, positions the currentindex at the
478 // beginning of the token if found.
479 internal int FindTokenIndex(String tokenString
) {
482 nextToken
= NextToken();
483 if ((_idx
== _len
) || ADP
.IsEmpty(nextToken
)) { // fxcop
486 if (String
.Compare(tokenString
, nextToken
, StringComparison
.OrdinalIgnoreCase
) == 0) {
493 // Skips the white space found in the beginning of the string.
494 internal bool StartsWith(String tokenString
) {
496 while((tempidx
< _len
) && Char
.IsWhiteSpace(_sqlstatement
[tempidx
])) {
499 if ((_len
- tempidx
) < tokenString
.Length
) {
503 if (0 == String
.Compare(_sqlstatement
, tempidx
, tokenString
, 0, tokenString
.Length
, StringComparison
.OrdinalIgnoreCase
)) {
504 // Reset current position and token