Merge pull request #1773 from ztzg/sql-server-datetime2
[mono.git] / mcs / class / Mono.Data.Tds / Mono.Data.Tds / TdsMetaParameter.cs
1 //
2 // Mono.Data.Tds.TdsMetaParameter.cs
3 //
4 // Author:
5 //   Tim Coleman (tim@timcoleman.com)
6 //
7 // Copyright (C) Tim Coleman, 2002
8 //
9
10 //
11 // Permission is hereby granted, free of charge, to any person obtaining
12 // a copy of this software and associated documentation files (the
13 // "Software"), to deal in the Software without restriction, including
14 // without limitation the rights to use, copy, modify, merge, publish,
15 // distribute, sublicense, and/or sell copies of the Software, and to
16 // permit persons to whom the Software is furnished to do so, subject to
17 // the following conditions:
18 // 
19 // The above copyright notice and this permission notice shall be
20 // included in all copies or substantial portions of the Software.
21 // 
22 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
23 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
24 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
25 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
26 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
27 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
28 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
29 //
30
31 using Mono.Data.Tds.Protocol;
32 using System;
33 using System.Text;
34
35 namespace Mono.Data.Tds {
36         public delegate object FrameworkValueGetter (object rawValue, ref bool updated);
37
38         public class TdsMetaParameter
39         {
40                 #region Static 
41                 public const int maxVarCharCharacters =  2147483647; // According to MS, max size is 2GB, 1 Byte Characters
42                 public const int maxNVarCharCharacters = 1073741823; // According to MS, max size is 2GB, 2 Byte Characters
43                 #endregion
44
45                 #region Fields
46
47                 TdsParameterDirection direction = TdsParameterDirection.Input;
48                 byte precision;
49                 byte scale;
50                 int size;
51                 string typeName;
52                 string name;
53                 bool isSizeSet = false;
54                 bool isNullable;
55                 object value;
56                 bool isVariableSizeType;
57                 FrameworkValueGetter frameworkValueGetter;
58                 object rawValue;
59                 bool isUpdated;
60
61                 #endregion // Fields
62
63                 public TdsMetaParameter (string name, object value)
64                         : this (name, String.Empty, value)
65                 {
66                 }
67
68                 public TdsMetaParameter (string name, FrameworkValueGetter valueGetter)
69                         : this (name, String.Empty, null)
70                 {
71                         frameworkValueGetter = valueGetter;
72                 }
73
74                 public TdsMetaParameter (string name, string typeName, object value)
75                 {
76                         ParameterName = name;
77                         Value = value;
78                         TypeName = typeName;
79                         IsNullable = false;
80                 }
81
82                 public TdsMetaParameter (string name, int size, bool isNullable, byte precision, byte scale, object value)
83                 {
84                         ParameterName = name;
85                         Size = size;
86                         IsNullable = isNullable;
87                         Precision = precision;
88                         Scale = scale;
89                         Value = value;
90                 }
91
92                 public TdsMetaParameter (string name, int size, bool isNullable, byte precision, byte scale, FrameworkValueGetter valueGetter)
93                 {
94                         ParameterName = name;
95                         Size = size;
96                         IsNullable = isNullable;
97                         Precision = precision;
98                         Scale = scale;
99                         frameworkValueGetter = valueGetter;
100                 }
101
102                 #region Properties
103
104                 public TdsParameterDirection Direction {
105                         get { return direction; }
106                         set { direction = value; }
107                 }
108
109                 public string TypeName {
110                         get { return typeName; }
111                         set { typeName = value; }
112                 }
113
114                 public string ParameterName {
115                         get { return name; }
116                         set { name = value; }
117                 }
118
119                 public bool IsNullable {
120                         get { return isNullable; }
121                         set { isNullable = value; }
122                 }
123
124                 public object Value {
125                         get {
126                                 if (frameworkValueGetter != null) {
127                                         object newValue = frameworkValueGetter (rawValue, ref isUpdated);
128                                         if (isUpdated)
129                                                 value = newValue;
130                                 }
131
132                                 if (isUpdated) {
133                                         value = ResizeValue (value);
134                                         isUpdated = false;
135                                 }
136                                 return value;
137                         }
138                         set {
139                                 rawValue = this.value = value;
140                                 isUpdated = true;
141                         }
142                 }
143
144                 public object RawValue {
145                         get { return rawValue; }
146                         set { Value = value; }
147                 }
148
149                 public byte Precision {
150                         get { return precision; }
151                         set { precision = value; }
152                 }
153
154                 public byte Scale {
155                         get { 
156                                 if (TypeName == "decimal" || TypeName == "numeric") {
157                                         if (scale == 0 && !Convert.IsDBNull(Value)) {
158                                                 int[] arr = Decimal.GetBits (
159                                                                 Convert.ToDecimal(Value));
160                                                 scale = (byte)((arr[3]>>16) & (int)0xFF);
161                                         }
162                                 }
163                                 return scale;
164                         }
165                         set { scale = value; }
166                 }
167
168                 public int Size {
169                         get { return GetSize (); }
170                         set {
171                                 size = value;
172                                 isUpdated = true;
173                                 isSizeSet = true;
174                         }
175                 }
176
177                 public bool IsVariableSizeType
178                 {
179                         get { return isVariableSizeType; }
180                         set { isVariableSizeType = value; }
181                 }
182
183                 public bool IsVarNVarCharMax
184                 {
185                         get { return (TypeName == "ntext" && size >= maxNVarCharCharacters); }
186                 }
187
188                 public bool IsVarCharMax
189                 {
190                         get { return (TypeName == "text" && size >= maxVarCharCharacters); }
191                 }
192
193                 public bool IsAnyVarCharMax
194                 {
195                         get { return IsVarNVarCharMax || IsVarCharMax; }
196                 }
197
198                 public bool IsNonUnicodeText
199                 {
200                         get {
201                                 TdsColumnType colType = GetMetaType();
202                                 return (colType == TdsColumnType.VarChar ||
203                                         colType == TdsColumnType.BigVarChar ||
204                                         colType == TdsColumnType.Text ||
205                                         colType == TdsColumnType.Char ||
206                                         colType == TdsColumnType.BigChar);
207                         }
208                 }
209
210                 public bool IsMoneyType
211                 {
212                         get {
213                                 TdsColumnType colType = GetMetaType();
214                                 return (colType == TdsColumnType.Money ||
215                                         colType == TdsColumnType.MoneyN ||
216                                         colType == TdsColumnType.Money4 ||
217                                         colType == TdsColumnType.SmallMoney);
218                         }
219                 }
220
221                 public bool IsDateTimeType
222                 {
223                         get {
224                                 TdsColumnType colType = GetMetaType();
225                                 return (colType == TdsColumnType.DateTime ||
226                                         colType == TdsColumnType.DateTime4 ||
227                                         colType == TdsColumnType.DateTimeN);
228                         }
229                 }
230
231                 public bool IsTextType
232                 {
233                         get {
234                                 TdsColumnType colType = GetMetaType();
235                                 return (colType == TdsColumnType.VarChar ||
236                                         colType == TdsColumnType.BigVarChar ||
237                                         colType == TdsColumnType.BigChar ||
238                                         colType == TdsColumnType.Char ||
239                                         colType == TdsColumnType.BigNVarChar || 
240                                         colType == TdsColumnType.NChar ||
241                                         colType == TdsColumnType.Text ||
242                                         colType == TdsColumnType.NText);
243                         }
244                 }
245
246                 public bool IsDecimalType
247                 {
248                         get {
249                                 TdsColumnType colType = GetMetaType();
250                                 return (colType == TdsColumnType.Decimal ||
251                                         colType == TdsColumnType.Numeric);
252                         }
253                 }
254
255                 #endregion // Properties
256
257                 #region Methods
258
259                 object ResizeValue (object newValue)
260                 {
261                         if (newValue == DBNull.Value || newValue == null)
262                                 return newValue;
263
264                         if (!isSizeSet || size <= 0)
265                                 return newValue;
266
267                         // if size is set, truncate the value to specified size
268                         string text = newValue as string;
269                         if (text != null) {
270                                 if (TypeName == "nvarchar" || 
271                                     TypeName == "nchar" ||
272                                     TypeName == "xml") {
273                                         if (text.Length > size)
274                                                 return text.Substring (0, size);
275                                 }
276                         } else if (newValue.GetType () == typeof (byte [])) {
277                                 byte [] buffer = (byte []) newValue;
278                                 if (buffer.Length > size) {
279                                         byte [] tmpVal = new byte [size];
280                                         Array.Copy (buffer, tmpVal, size);
281                                         return tmpVal;
282                                 }
283                         }
284                         return newValue;
285                 }
286
287                 internal string Prepare ()
288                 {
289                         string typeName = TypeName;
290                         // Cf. GetDateTimeString
291                         TdsColumnType actualType = TdsColumnType.Char;
292                         int size;
293
294                         switch (typeName) {
295                         case "varbinary":
296                                 size = Size;
297                                 if (size <= 0) {
298                                         size = GetActualSize ();
299                                 }
300
301                                 if (size > 8000) {
302                                         typeName = "varbinary(max)";
303                                 }
304                                 break;
305                         case "datetime2":
306                                 actualType = TdsColumnType.DateTime2;
307                                 typeName = "char";
308                                 break;
309                         case "datetimeoffset":
310                                 actualType = TdsColumnType.DateTimeOffset;
311                                 typeName = "char";
312                                 break;
313                         }
314
315                         string includeAt = "@";
316                         if (ParameterName [0] == '@')
317                                 includeAt = "";
318                         StringBuilder result = new StringBuilder (String.Format ("{0}{1} {2}", includeAt, ParameterName, typeName));
319                         switch (typeName) {
320                         case "decimal":
321                         case "numeric":
322                                 // msdotnet sends a default precision of 29
323                                 result.Append (String.Format ("({0},{1})",
324                                          (Precision == (byte)0 ? (byte)38 : Precision), Scale));
325                                 break;
326                         case "varchar":
327                         case "varbinary":
328                                 //A size of 0 is not allowed in declarations.
329                                 size = Size;
330                                 if (size <= 0) {
331                                         size = GetActualSize ();
332                                         if (size <= 0)
333                                                 size = 1;
334                                 }
335                                 result.Append (size > 8000 ? "(max)" : String.Format ("({0})", size));
336                                 break;
337                         case "nvarchar":
338                                 int paramSize = Size < 0 ? GetActualSize () / 2 : Size;
339                                 result.Append (paramSize > 0 ? (paramSize > 4000 ? "(max)" : String.Format ("({0})", paramSize)) : "(4000)");
340                                 break;
341                         case "char":
342                                 size = -1;
343                                 if (actualType != TdsColumnType.Char)
344                                         size = GetDateTimeStringLength (actualType);
345                                 else if (isSizeSet)
346                                         size = Size;
347                                 if (size > 0)
348                                         result.Append (String.Format ("({0})", size));
349                                 break;
350                         case "nchar":
351                         case "binary":
352                                 if (isSizeSet && Size > 0)
353                                         result.Append (String.Format ("({0})", Size));
354                                 break;
355                         }
356                         return result.ToString ();
357                 }
358
359                 internal int GetActualSize ()
360                 {
361                         if (Value == DBNull.Value || Value == null)
362                                 return 0;
363
364                         switch (Value.GetType ().ToString ()) {
365                         case "System.String":
366                                 int len = ((string)value).Length;
367                                 if (TypeName == "nvarchar" || TypeName == "nchar" 
368                                     || TypeName == "ntext"
369                                     || TypeName == "xml")
370                                         len *= 2;
371                                 return len ;    
372                         case "System.Byte[]":
373                                 return ((byte[]) value).Length;
374                         }
375                         return GetSize ();
376                 }
377
378                 private int GetSize ()
379                 {
380                         switch (TypeName) {
381                         case "decimal":
382                                 return 17;
383                         case "uniqueidentifier":
384                                 return 16;
385                         case "bigint":
386                         case "datetime":
387                         case "float":
388                         case "money":
389                                 return 8;
390                         case "datetime2":
391                                 return GetDateTimeStringLength (TdsColumnType.DateTime2);
392                         case "datetimeoffset":
393                                 return GetDateTimeStringLength (TdsColumnType.DateTimeOffset);
394                         case "int":
395                         case "real":
396                         case "smalldatetime":
397                         case "smallmoney":
398                                 return 4;
399                         case "smallint":
400                                 return 2;
401                         case "tinyint":
402                         case "bit":
403                                 return 1;
404                         /*
405                         case "nvarchar" :
406                         */
407                         case "nchar" :
408                         case "ntext" :
409                                 return size*2 ;
410                         }
411                         return size;
412                 }
413
414                 private int GetDateTimePrecision ()
415                 {
416                         int precision = Precision;
417
418                         // http://msdn.microsoft.com/en-us/library/bb677335.aspx
419                         // says that default precision is 7.  How do
420                         // we distinguish that from zero?
421                         if (precision == 0 || precision > 7)
422                                 precision = 7;
423
424                         return precision;
425                 }
426
427                 private int GetDateTimeStringLength (TdsColumnType type)
428                 {
429                         int precision = GetDateTimePrecision ();
430                         int len = precision == 0 ? 19 : 20 + precision;
431
432                         if (type == TdsColumnType.DateTimeOffset)
433                                 len += 6;
434
435                         return len;
436                 }
437
438                 // HACK: Wire-level DateTime{2,Offset} require TDS
439                 // 7.3, which this driver does not implement
440                 // correctly--so we serialize to ASCII instead.
441                 private string GetDateTimeString (TdsColumnType type)
442                 {
443                         int precision = GetDateTimePrecision ();
444                         string fmt = "yyyy-MM-dd'T'HH':'mm':'ss";
445
446                         if (precision > 0)
447                                 fmt += ".fffffff".Substring(0, precision + 1);
448
449                         switch (type) {
450                         case TdsColumnType.DateTime2:
451                                 DateTime dt = (DateTime)Value;
452                                 return dt.ToString(fmt);
453                         case TdsColumnType.DateTimeOffset:
454                                 DateTimeOffset dto = (DateTimeOffset)Value;
455                                 return dto.ToString(fmt + "zzz");
456                         }
457
458                         throw new ApplicationException("Should be unreachable");
459                 }
460
461                 internal byte[] GetBytes ()
462                 {
463                         byte[] result = {};
464                         if (Value == DBNull.Value || Value == null)
465                                 return result;
466
467                         switch (TypeName)
468                         {
469                                 case "nvarchar" :
470                                 case "nchar" :
471                                 case "ntext" :
472                                 case "xml" :
473                                         return Encoding.Unicode.GetBytes ((string)Value);
474                                 case "varchar" :
475                                 case "char" :
476                                 case "text" :
477                                         return Encoding.Default.GetBytes ((string)Value);
478                                 case "datetime2":
479                                         return Encoding.Default.GetBytes (GetDateTimeString (TdsColumnType.DateTime2));
480                                 case "datetimeoffset":
481                                         return Encoding.Default.GetBytes (GetDateTimeString (TdsColumnType.DateTimeOffset));
482                                 default :
483                                         return ((byte[]) Value);
484                         }
485                 }
486
487                 internal TdsColumnType GetMetaType ()
488                 {
489                         switch (TypeName) {
490                         case "binary":
491                                 return TdsColumnType.BigBinary;
492                         case "bit":
493                                 if (IsNullable)
494                                         return TdsColumnType.BitN;
495                                 return TdsColumnType.Bit;
496                         case "bigint":
497                                 if (IsNullable)
498                                         return TdsColumnType.IntN ;
499                                 return TdsColumnType.BigInt;
500                         case "char":
501                                 return TdsColumnType.BigChar;
502                         case "money":
503                                 if (IsNullable)
504                                         return TdsColumnType.MoneyN;
505                                 return TdsColumnType.Money;
506                         case "smallmoney":
507                                 if (IsNullable)
508                                         return TdsColumnType.MoneyN ;
509                                 return TdsColumnType.SmallMoney;
510                         case "decimal":
511                                 return TdsColumnType.Decimal;
512                         case "datetime":
513                                 if (IsNullable)
514                                         return TdsColumnType.DateTimeN;
515                                 return TdsColumnType.DateTime;
516                         case "smalldatetime":
517                                 if (IsNullable)
518                                         return TdsColumnType.DateTimeN;
519                                 return TdsColumnType.DateTime4;
520                         case "datetime2":
521                                 return TdsColumnType.DateTime2;
522                         case "datetimeoffset":
523                                 return TdsColumnType.DateTimeOffset;
524                         case "float":
525                                 if (IsNullable)
526                                         return TdsColumnType.FloatN ;
527                                 return TdsColumnType.Float8;
528                         case "image":
529                                 return TdsColumnType.Image;
530                         case "int":
531                                 if (IsNullable)
532                                         return TdsColumnType.IntN;
533                                 return TdsColumnType.Int4;
534                         case "numeric":
535                                 return TdsColumnType.Numeric;
536                         case "nchar":
537                                 return TdsColumnType.NChar;
538                         case "ntext":
539                                 return TdsColumnType.NText;
540                         case "xml":
541                         case "nvarchar":
542                                 return TdsColumnType.BigNVarChar;
543                         case "real":
544                                 if (IsNullable)
545                                         return TdsColumnType.FloatN ;
546                                 return TdsColumnType.Real;
547                         case "smallint":
548                                 if (IsNullable)
549                                         return TdsColumnType.IntN;
550                                 return TdsColumnType.Int2;
551                         case "text":
552                                 return TdsColumnType.Text;
553                         case "tinyint":
554                                 if (IsNullable)
555                                         return TdsColumnType.IntN;
556                                 return TdsColumnType.Int1;
557                         case "uniqueidentifier":
558                                 return TdsColumnType.UniqueIdentifier;
559                         case "varbinary":
560                                 return TdsColumnType.BigVarBinary;
561                         case "varchar":
562                                 return TdsColumnType.BigVarChar;
563                         case "sql_variant":
564                                 return TdsColumnType.Variant;
565                         default:
566                                 throw new NotSupportedException ("Unknown Type : " + TypeName);
567                         }
568                 }
569
570                 public void CalculateIsVariableType()
571                 {
572                         switch (GetMetaType ()) {
573                                 case TdsColumnType.UniqueIdentifier:
574                                 case TdsColumnType.BigVarChar:
575                                 case TdsColumnType.BigVarBinary:
576                                 case TdsColumnType.IntN:
577                                 case TdsColumnType.Text:
578                                 case TdsColumnType.FloatN:
579                                 case TdsColumnType.BigNVarChar:
580                                 case TdsColumnType.NText:
581                                 case TdsColumnType.Image:
582                                 case TdsColumnType.Decimal:
583                                 case TdsColumnType.BigBinary:
584                                 case TdsColumnType.DateTimeN:
585                                 case TdsColumnType.MoneyN:
586                                 case TdsColumnType.BitN:
587                                 case TdsColumnType.Char:
588                                 case TdsColumnType.BigChar:
589                                 case TdsColumnType.NChar:
590                                         IsVariableSizeType = true;
591                                         break;
592                                 default:
593                                         IsVariableSizeType = false;
594                                 break;
595                         }
596                 }
597
598                 public void Validate (int index)
599                 {
600                         if ((this.direction == TdsParameterDirection.InputOutput || this.direction == TdsParameterDirection.Output) &&
601                                  this.isVariableSizeType && (Value == DBNull.Value || Value == null) && Size == 0
602                                 ) 
603                         {
604                                 throw new InvalidOperationException (String.Format ("{0}[{1}]: the Size property should " +
605                                                                                                 "not be of size 0",
606                                                                                                 this.typeName,
607                                                                                                 index));
608                         }
609                 }
610
611                 #endregion // Methods
612         }
613 }