DeveelDB  20151217
complete SQL database system, primarly developed for .NET/Mono frameworks
StringType.cs
Go to the documentation of this file.
1 //
2 // Copyright 2010-2015 Deveel
3 //
4 // Licensed under the Apache License, Version 2.0 (the "License");
5 // you may not use this file except in compliance with the License.
6 // You may obtain a copy of the License at
7 //
8 // http://www.apache.org/licenses/LICENSE-2.0
9 //
10 // Unless required by applicable law or agreed to in writing, software
11 // distributed under the License is distributed on an "AS IS" BASIS,
12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 // See the License for the specific language governing permissions and
14 // limitations under the License.
15 //
16 
17 using System;
18 using System.Globalization;
19 using System.IO;
20 using System.Text;
21 
23 using Deveel.Data.Sql.Objects;
24 using Deveel.Data.Store;
25 using Deveel.Data.Text;
26 
27 namespace Deveel.Data.Types {
28  [Serializable]
29  public sealed class StringType : SqlType, ISizeableType {
30  private CompareInfo collator;
31 
32  public const int DefaultMaxSize = Int16.MaxValue;
33 
34  public StringType(SqlTypeCode typeCode, int maxSize, Encoding encoding, CultureInfo locale)
35  : base("STRING", typeCode) {
36  if (encoding == null)
37  throw new ArgumentNullException("encoding");
38 
39  AssertIsString(typeCode);
40  MaxSize = maxSize;
41  Locale = locale;
42  Encoding = encoding;
43  }
44 
45  private StringType(ObjectData data)
46  : base(data) {
47  MaxSize = data.GetInt32("MaxSize");
48 
49  if (data.HasValue("Locale")) {
50  var locale = data.GetString("Locale");
51  Locale = new CultureInfo(locale);
52  }
53 
54  if (data.HasValue("Encoding")) {
55  var encoding = data.GetString("Encoding");
56  Encoding = Encoding.GetEncoding(encoding);
57  }
58  }
59 
60  private static void AssertIsString(SqlTypeCode sqlType) {
61  if (!IsStringType(sqlType))
62  throw new ArgumentException(String.Format("The type {0} is not a valid STRING type.", sqlType), "sqlType");
63  }
64 
69  public int MaxSize { get; private set; }
70 
72  get { return MaxSize; }
73  }
74 
75  public override bool IsStorable {
76  get { return true; }
77  }
78 
86  public CultureInfo Locale { get; private set; }
87 
88  private CompareInfo Collator {
89  get {
90  lock (this) {
91  if (collator != null) {
92  return collator;
93  } else {
94  //TODO:
95  collator = Locale.CompareInfo;
96  return collator;
97  }
98  }
99  }
100  }
101 
102  public Encoding Encoding { get; private set; }
103 
104  protected override void GetData(SerializeData data) {
105  data.SetValue("MaxSize", MaxSize);
106  if (Locale != null)
107  data.SetValue("Locale", Locale.Name);
108  if (Encoding != null)
109  data.SetValue("Encoding", Encoding.WebName);
110  }
111 
112  public override bool IsCacheable(ISqlObject value) {
113  return value is SqlString || value is SqlNull || value == null;
114  }
115 
117  public override string ToString() {
118  var sb = new StringBuilder(Name);
119  if (MaxSize >= 0)
120  sb.AppendFormat("({0})", MaxSize);
121 
122  return sb.ToString();
123  }
124 
125  public override Type GetObjectType() {
126  if (TypeCode == SqlTypeCode.Char ||
127  TypeCode == SqlTypeCode.VarChar ||
128  TypeCode == SqlTypeCode.String)
129  return typeof (SqlString);
130 
131  if (TypeCode == SqlTypeCode.LongVarChar ||
132  TypeCode == SqlTypeCode.Clob)
133  return typeof (SqlLongString);
134 
135  return base.GetObjectType();
136  }
137 
138  public override Type GetRuntimeType() {
139  if (TypeCode == SqlTypeCode.Char ||
140  TypeCode == SqlTypeCode.VarChar ||
141  TypeCode == SqlTypeCode.String)
142  return typeof (string);
143 
144  if (TypeCode == SqlTypeCode.LongVarChar ||
145  TypeCode == SqlTypeCode.Clob)
146  return typeof (Stream);
147 
148  return base.GetRuntimeType();
149  }
150 
152  public override bool Equals(SqlType other) {
153  if (!base.Equals(other))
154  return false;
155 
156  var stringType = (StringType) other;
157  if (stringType.MaxSize != MaxSize)
158  return false;
159 
160  if (Locale == null && stringType.Locale == null)
161  return true;
162  if (Locale == null && stringType.Locale != null)
163  return false;
164  if (Locale != null && stringType.Locale == null)
165  return false;
166 
167  if (Locale != null && stringType.Locale != null)
168  return Locale.NativeName.Equals(stringType.Locale.NativeName);
169 
170  return true;
171  }
172 
174  public override int GetHashCode() {
175  return TypeCode.GetHashCode() ^ MaxSize.GetHashCode();
176  }
177 
179  public override bool IsComparable(SqlType type) {
180  // Are we comparing with another string type?
181  if (type is StringType) {
182  var stringType = (StringType) type;
183  // If either locale is null return true
184  if (Locale == null || stringType.Locale == null)
185  return true;
186 
187  //TODO: Check batter on the locale comparison: we could compare
188  // neutral cultures
189 
190  // If the locales are the same return true
191  return Locale.Equals(stringType.Locale);
192  }
193 
194  // Only string types can be comparable
195  return false;
196  }
197 
198  public SqlBoolean IsLike(ISqlString value, ISqlString pattern) {
199  if (value == null && IsNull)
200  return true;
201  if (!IsNull && value == null)
202  return false;
203  if (value == null)
204  return false;
205 
206  var s1 = value.ToString();
207  var s2 = pattern.ToString();
208  return PatternSearch.FullPatternMatch(s1, s2, '\\');
209  }
210 
211  public SqlBoolean IsNotLike(ISqlString value, ISqlString pattern) {
212  var likeResult = IsLike(value, pattern);
213  if (likeResult.IsNull)
214  return likeResult;
215 
216  return likeResult.Not();
217  }
218 
219  public override object ConvertTo(ISqlObject obj, Type destType) {
220  if (!(obj is ISqlString))
221  throw new ArgumentException();
222 
223  var s = (ISqlString) obj;
224  if (s.IsNull)
225  return null;
226 
227  if (destType == typeof (string))
228  return s.ToString();
229 
230  throw new InvalidCastException();
231  }
232 
233  #region Operators
234 
236  public override ISqlObject Add(ISqlObject a, ISqlObject b) {
237  if (!(a is ISqlString))
238  throw new ArgumentException();
239 
240  if (!(b is ISqlString)) {
241  //TODO: convert to a ISqlString
242  throw new NotSupportedException();
243  }
244 
245  if (a is SqlString) {
246  var x = (SqlString) a;
247  var y = (ISqlString) b;
248  return x.Concat(y);
249  }
250 
251  return base.Add(a, b);
252  }
253 
254  #endregion
255 
256  private static SqlNumber ToNumber(String str) {
257  SqlNumber value;
258  if (!SqlNumber.TryParse(str, out value))
259  value = SqlNumber.Null;
260 
261  return value;
262  }
263 
264 
265  private SqlDateTime ToDate(string str) {
266  SqlDateTime result;
267  if (!SqlDateTime.TryParseDate(str, out result))
268  throw new InvalidCastException(DateErrorMessage(str, SqlTypeCode.Date, DateType.DateFormatSql));
269 
270  return result;
271  }
272 
273  private SqlDateTime ToTime(String str) {
274  SqlDateTime result;
275  if (!SqlDateTime.TryParseTime(str, out result))
276  throw new InvalidCastException(DateErrorMessage(str, SqlTypeCode.Time, DateType.TimeFormatSql));
277 
278  return result;
279 
280  }
281 
282  private SqlDateTime ToTimeStamp(String str) {
283  SqlDateTime result;
284  if (!SqlDateTime.TryParseTimeStamp(str, out result))
285  throw new InvalidCastException(DateErrorMessage(str, SqlTypeCode.TimeStamp, DateType.TsFormatSql));
286 
287  return result;
288  }
289 
290  private SqlDateTime ToDateTime(string str) {
291  SqlDateTime result;
292  if (!SqlDateTime.TryParse(str, out result))
293  throw new InvalidCastException(DateErrorMessage(str, SqlTypeCode.TimeStamp, DateType.TsFormatSql));
294 
295  return result;
296  }
297 
298  private string DateErrorMessage(string str, SqlTypeCode sqlType, string[] formats) {
299  return String.Format("The input string {0} of type {1} is not compatible with any of the formats for SQL Type {2} ( {3} )",
300  str,
301  TypeCode.ToString().ToUpperInvariant(),
302  sqlType.ToString().ToUpperInvariant(),
303  String.Join(", ", formats));
304  }
305 
306  private SqlBoolean ToBoolean(string s) {
307  SqlBoolean value;
308  if (SqlBoolean.TryParse(s, out value))
309  return value;
310 
311  if (String.Equals(s, "1"))
312  return SqlBoolean.True;
313  if (String.Equals(s, "2"))
314  return SqlBoolean.False;
315 
316  throw new InvalidCastException(String.Format("Could not convert string '{0}' of type '{1}' to BOOLEAN", s, TypeCode.ToString().ToUpperInvariant()));
317  }
318 
320  public override bool CanCastTo(SqlType destType) {
321  return destType.TypeCode != SqlTypeCode.Array &&
322  destType.TypeCode != SqlTypeCode.ColumnType &&
323  destType.TypeCode != SqlTypeCode.RowType &&
324  destType.TypeCode != SqlTypeCode.Object;
325  }
326 
328  public override DataObject CastTo(DataObject value, SqlType destType) {
329  string str = value.Value.ToString();
330  var sqlType = destType.TypeCode;
331  ISqlObject castedValue;
332 
333  if (value.IsNull)
334  return DataObject.Null(destType);
335 
336  switch (sqlType) {
337  case (SqlTypeCode.Bit):
338  case (SqlTypeCode.Boolean):
339  castedValue = ToBoolean(str);
340  break;
341  case (SqlTypeCode.TinyInt):
342  case (SqlTypeCode.SmallInt):
343  case (SqlTypeCode.Integer): {
344  var num = ToNumber(str);
345  if (num.IsNull) {
346  castedValue = num;
347  } else {
348  castedValue = new SqlNumber(num.ToInt32());
349  }
350 
351  break;
352  }
353  case (SqlTypeCode.BigInt): {
354  var num = ToNumber(str);
355  if (num.IsNull) {
356  castedValue = num;
357  } else {
358  castedValue = new SqlNumber(num.ToInt64());
359  }
360 
361  break;
362  }
363  case (SqlTypeCode.Float):
364  case (SqlTypeCode.Double): {
365  var num = ToNumber(str);
366  if (num.IsNull) {
367  castedValue = num;
368  } else {
369  castedValue = new SqlNumber(num.ToDouble());
370  }
371 
372  break;
373  }
374  case (SqlTypeCode.Real):
375  case (SqlTypeCode.Numeric):
376  case (SqlTypeCode.Decimal):
377  castedValue = ToNumber(str);
378  break;
379  case (SqlTypeCode.Char):
380  castedValue = new SqlString(str.PadRight(((StringType)destType).MaxSize));
381  break;
382  case (SqlTypeCode.VarChar):
383  case (SqlTypeCode.LongVarChar):
384  case (SqlTypeCode.String):
385  castedValue = new SqlString(str);
386  break;
387  case (SqlTypeCode.Date):
388  castedValue = ToDate(str);
389  break;
390  case (SqlTypeCode.Time):
391  castedValue = ToTime(str);
392  break;
393  case (SqlTypeCode.TimeStamp):
394  castedValue = ToTimeStamp(str);
395  break;
396  case (SqlTypeCode.DateTime):
397  castedValue = ToDateTime(str);
398  break;
399  case (SqlTypeCode.Blob):
400  case (SqlTypeCode.Binary):
401  case (SqlTypeCode.VarBinary):
402  case (SqlTypeCode.LongVarBinary):
403  castedValue = new SqlBinary(Encoding.Unicode.GetBytes(str));
404  break;
405  case (SqlTypeCode.Null):
406  castedValue = SqlNull.Value;
407  break;
408  case (SqlTypeCode.Clob):
409  // TODO: have a context where to get a new CLOB
410  castedValue = new SqlString(str);
411  break;
412  default:
413  throw new InvalidCastException();
414  }
415 
416  return new DataObject(destType, castedValue);
417  }
418 
419  public override int Compare(ISqlObject x, ISqlObject y) {
420  if (x == null)
421  throw new ArgumentNullException("x");
422 
423  if (!(x is ISqlString) ||
424  !(y is ISqlString))
425  throw new ArgumentException("Cannot compare objects that are not strings.");
426 
427  if (x.IsNull && y.IsNull)
428  return 0;
429  if (x.IsNull && !y.IsNull)
430  return 1;
431  if (!x.IsNull && y.IsNull)
432  return -1;
433 
434  // If lexicographical ordering,
435  if (Locale == null)
436  return LexicographicalOrder((ISqlString)x, (ISqlString)y);
437 
438  return Collator.Compare(x.ToString(), y.ToString());
439  }
440 
441  private static int LexicographicalOrder(ISqlString str1, ISqlString str2) {
442  // If both strings are small use the 'toString' method to compare the
443  // strings. This saves the overhead of having to store very large string
444  // objects in memory for all comparisons.
445  long str1Size = str1.Length;
446  long str2Size = str2.Length;
447  if (str1Size < 32 * 1024 &&
448  str2Size < 32 * 1024) {
449  return String.Compare(str1.ToString(), str2.ToString(), StringComparison.Ordinal);
450  }
451 
452  // TODO: pick one of the two encodings?
453 
454  // The minimum size
455  long size = System.Math.Min(str1Size, str2Size);
456  TextReader r1 = str1.GetInput(str1.Encoding);
457  TextReader r2 = str2.GetInput(str2.Encoding);
458  try {
459  try {
460  while (size > 0) {
461  int c1 = r1.Read();
462  int c2 = r2.Read();
463  if (c1 != c2) {
464  return c1 - c2;
465  }
466  --size;
467  }
468  // They compare equally up to the limit, so now compare sizes,
469  if (str1Size > str2Size) {
470  // If str1 is larger
471  return 1;
472  } else if (str1Size < str2Size) {
473  // If str1 is smaller
474  return -1;
475  }
476  // Must be equal
477  return 0;
478  } finally {
479  r1.Dispose();
480  r2.Dispose();
481  }
482  } catch (IOException e) {
483  throw new Exception("IO Error: " + e.Message);
484  }
485  }
486 
487  public override void SerializeObject(Stream stream, ISqlObject obj) {
488  var writer = new BinaryWriter(stream);
489 
490  if (obj.IsNull) {
491  if (TypeCode == SqlTypeCode.Clob ||
492  TypeCode == SqlTypeCode.LongVarChar) {
493  writer.Write((byte)3);
494  } else {
495  writer.Write((byte)1);
496  }
497 
498  return;
499  }
500 
501  var sqlString = (ISqlString)obj;
502 
503  if (obj is SqlString) {
504  var bytes = ((SqlString) sqlString).ToByteArray(Encoding);
505  writer.Write((byte) 2);
506  writer.Write(bytes.Length);
507  writer.Write(bytes);
508  } else if (obj is SqlLongString) {
509  var longString = (SqlLongString) sqlString;
510  writer.Write((byte) 4);
511  writer.Write(longString.ObjectId.StoreId);
512  writer.Write(longString.ObjectId.Id);
513  } else {
514  throw new FormatException(String.Format("The object of type '{0}' is not handled by {1}", obj.GetType(), ToString()));
515  }
516  }
517 
518  public override ISqlObject DeserializeObject(Stream stream) {
519  var reader = new BinaryReader(stream);
520  var type = reader.ReadByte();
521 
522  if (type == 1)
523  return SqlString.Null;
524  if (type == 3)
525  return SqlLongString.Null;
526 
527  if (type == 2) {
528  var length = reader.ReadInt32();
529  var bytes = reader.ReadBytes(length);
530 
531  var chars = Encoding.GetChars(bytes);
532 
533  return new SqlString(chars);
534  }
535 
536  if (type == 4) {
537  var storeId = reader.ReadInt32();
538  var objId = reader.ReadInt64();
539  var refObjId = new ObjectId(storeId, objId);
540 
541  // TODO: find the store and get the object
542  throw new NotImplementedException();
543  }
544 
545  throw new FormatException("Invalid type code in deserialization.");
546  }
547 
548  internal override int ColumnSizeOf(ISqlObject obj) {
549  if (obj.IsNull)
550  return 1;
551  if (obj is SqlString) {
552  var s = (SqlString) obj;
553  var length = s.GetByteCount(Encoding);
554 
555  // Type + Byte Length + Bytes
556  return 1 + 4 + length;
557  }
558  if (obj is SqlLongString) {
559  // Type + Store ID + Object ID
560  return 1 + 4 + 8;
561  }
562 
563  throw new ArgumentException();
564  }
565 
566  internal static bool IsStringType(SqlTypeCode typeCode) {
567  return typeCode == SqlTypeCode.String ||
568  typeCode == SqlTypeCode.VarChar ||
569  typeCode == SqlTypeCode.Char ||
570  typeCode == SqlTypeCode.LongVarChar ||
571  typeCode == SqlTypeCode.Clob;
572  }
573  }
574 }
SqlBoolean IsNotLike(ISqlString value, ISqlString pattern)
Definition: StringType.cs:211
bool IsNull
Gets a value that indicates if this object is materialized as null.
Definition: DataObject.cs:91
override bool IsComparable(SqlType type)
Verifies if a given SqlType is comparable to this data-type.
Definition: StringType.cs:179
override int Compare(ISqlObject x, ISqlObject y)
Definition: StringType.cs:419
static SqlNumber ToNumber(String str)
Definition: StringType.cs:256
A long string in the system.
override DataObject CastTo(DataObject value, SqlType destType)
Converts the given object value to a SqlType specified.
Definition: StringType.cs:328
static bool TryParseTimeStamp(string s, out SqlDateTime value)
Definition: SqlDateTime.cs:537
override ISqlObject Add(ISqlObject a, ISqlObject b)
Definition: StringType.cs:236
static bool TryParse(string s, out SqlNumber value)
Definition: SqlNumber.cs:702
SqlDateTime ToTime(String str)
Definition: StringType.cs:273
Implements a BINARY object that handles a limited number of bytes, not exceding MaxLength.
Definition: SqlBinary.cs:27
static bool TryParseTime(string s, out SqlDateTime value)
Definition: SqlDateTime.cs:523
override ISqlObject DeserializeObject(Stream stream)
Definition: StringType.cs:518
static DataObject Null(SqlType type)
Definition: DataObject.cs:630
void SetValue(string key, Type type, object value)
static void AssertIsString(SqlTypeCode sqlType)
Definition: StringType.cs:60
static readonly SqlLongString Null
override void SerializeObject(Stream stream, ISqlObject obj)
Definition: StringType.cs:487
ISqlObject Value
Gets the underlined value that is handled.
Definition: DataObject.cs:84
SqlTypeCode TypeCode
Gets the kind of SQL type this data-type handles.
Definition: SqlType.cs:91
bool IsNull
Gets a boolean value indicating if the object is NULL.
Definition: ISqlObject.cs:28
override Type GetRuntimeType()
Definition: StringType.cs:138
static bool IsStringType(SqlTypeCode typeCode)
Definition: StringType.cs:566
static readonly SqlNull Value
Definition: SqlNull.cs:24
Defines the contract for a valid SQL Object
Definition: ISqlObject.cs:23
override int ColumnSizeOf(ISqlObject obj)
Definition: StringType.cs:548
SqlDateTime ToDate(string str)
Definition: StringType.cs:265
static readonly string[] DateFormatSql
Definition: DateType.cs:29
override bool IsCacheable(ISqlObject value)
Definition: StringType.cs:112
Represents a dynamic object that encapsulates a defined SqlType and a compatible constant ISqlObject ...
Definition: DataObject.cs:35
override string ToString()
Definition: StringType.cs:117
override bool Equals(SqlType other)
Definition: StringType.cs:152
static bool TryParseDate(string s, out SqlDateTime value)
Definition: SqlDateTime.cs:510
Defines the properties of a specific SQL Type and handles the values compatible.
Definition: SqlType.cs:33
TextReader GetInput(Encoding encoding)
string DateErrorMessage(string str, SqlTypeCode sqlType, string[] formats)
Definition: StringType.cs:298
A unique identifier of an object within a database system, that is composed by a reference to the sto...
Definition: ObjectId.cs:31
static readonly SqlNumber Null
Definition: SqlNumber.cs:31
SqlTypeCode
Enumerates the codes of all SQL types handled by the system.
Definition: SqlTypeCode.cs:23
SqlBoolean ToBoolean(string s)
Definition: StringType.cs:306
override Type GetObjectType()
Definition: StringType.cs:125
static readonly string[] TimeFormatSql
Definition: DateType.cs:34
SqlBoolean IsLike(ISqlString value, ISqlString pattern)
Definition: StringType.cs:198
SqlDateTime ToDateTime(string str)
Definition: StringType.cs:290
SqlDateTime ToTimeStamp(String str)
Definition: StringType.cs:282
override void GetData(SerializeData data)
Definition: StringType.cs:104
override object ConvertTo(ISqlObject obj, Type destType)
Definition: StringType.cs:219
static int LexicographicalOrder(ISqlString str1, ISqlString str2)
Definition: StringType.cs:441
StringType(SqlTypeCode typeCode, int maxSize, Encoding encoding, CultureInfo locale)
Definition: StringType.cs:34
static bool TryParse(string s, out SqlDateTime value)
Definition: SqlDateTime.cs:495
StringType(ObjectData data)
Definition: StringType.cs:45
This is a static class that performs the operations to do a pattern search on a given column of a tab...
static bool FullPatternMatch(string pattern, string str, char escapeChar)
Matches a pattern against a string and returns true if it matches or false otherwise.
Deveel.Data.Sql.Objects.SqlString SqlString
Definition: DataObject.cs:27
override bool CanCastTo(SqlType destType)
Verifies if this type can cast any value to the given SqlType.
Definition: StringType.cs:320
static readonly string[] TsFormatSql
Definition: DateType.cs:45
override bool Equals(object obj)
Definition: SqlType.cs:373
override int GetHashCode()
Definition: StringType.cs:174