using System; using System.IO; using System.Data.SqlTypes; using System.Collections.Generic; using Microsoft.SqlServer.Server; /* This defines the type WeightedValue which consists of two parts: one is a value and one is a weight. * This type is used for the weighted average aggregation function. */ [Serializable] [Microsoft.SqlServer.Server.SqlUserDefinedType(Format.UserDefined, MaxByteSize = 8000)] public class WeightedValue : INullable, IBinarySerialize { private bool isNull; private double value; private double weight; /* All user defined types for SQL require a constructor that takes no arguments */ public WeightedValue () { this.isNull = true; } /* However, other constructors are also useful */ public WeightedValue (SqlDouble val, SqlDouble wgt) { this.value = val.Value; this.weight = wgt.Value; this.isNull = false; } // WeightedValue() /* This defines the IsNull property of the class. * Note that "get" makes a variable act like a function. So, this is accessed * as whatever.IsNull rather than whatever.IsNull(). */ public bool IsNull { get { return isNull; } } // IsNull /* This returns a new instance of Weighted Average with a NULL value. */ public static WeightedValue Null { get { return new WeightedValue(); } } /* These are accessors for value and weight */ public double Value { get { return this.value; } } // Value public double Weight { get { return this.weight; } } // Weight /* Every SQL Type requires a ToString() function to convert the value to a string. * For this function, the string has the form "value=;weight=;" or * "". */ public override string ToString () { if (this.isNull) { return (""); } return "value=" + value.ToString("G") + ";weight=" + weight.ToString("G") + ";"; } // ToString() /* Parse is another required function. It needs to create a new WeightedValue from * a string representation, preferably the one created by ToString(). */ public static WeightedValue Parse (SqlString s) { if (s.IsNull || ("" == s)) { return (Null); } try { string[] values = s.Value.Split(';'); WeightedValue wv = new WeightedValue(); wv.isNull = false; for (int i = 0; i < values.GetLength(0); i++) { string[] parts = values[i].Split('='); switch (parts[0]) { case "value": wv.value = Double.Parse(parts[1]); break; case "weight": wv.weight = Double.Parse(parts[1]); break; } } return (wv); } catch (Exception ex) { throw new ArgumentException("The expected format is 'value=;weight='", ex); } } // Parse() /* Write() is another required function. This is the binary equivalent of ToString() */ public void Write (BinaryWriter w) { w.Write(this.isNull); if (! this.isNull) { w.Write(this.value); w.Write(this.weight); } } // Write() /* Read() is another required function. This is the binary equivalent of Parse(). */ public void Read (BinaryReader r) { this.isNull = r.ReadBoolean(); if (!this.isNull) { this.value = r.ReadDouble(); this.weight = r.ReadDouble(); } } // Read() } // WeightedValue /* This is the weighted value aggregation function. */ [Serializable] [SqlUserDefinedAggregate(Format.UserDefined, IsInvariantToNulls = true, IsInvariantToDuplicates = false, IsInvariantToOrder = false, MaxByteSize = 8000)] public class WAvg : IBinarySerialize { private double sum; private double cnt; public void Init () { sum = 0; cnt = 0; } // Init() public void Accumulate (WeightedValue value) { if (!value.IsNull) { this.sum += value.Value * value.Weight; this.cnt += value.Weight; } } // Accumulate() public void Merge (WAvg group) { this.sum += group.sum; this.cnt += group.cnt; } // Merge() public SqlDouble Terminate () { return new SqlDouble(this.sum / this.cnt); } // Terminate() public void Read (BinaryReader r) { this.sum = r.ReadDouble(); this.cnt = r.ReadDouble(); } // Read() public void Write (BinaryWriter w) { w.Write(this.sum); w.Write(this.cnt); } // Write() } // Wavg /* This defines the type AnyType which can contain any simple SQL type. * This type is needed for the MinOf() and MaxOf() aggregation functions. */ [Serializable] [Microsoft.SqlServer.Server.SqlUserDefinedType(Format.UserDefined, MaxByteSize = 8000)] public class AnyType : INullable, IBinarySerialize { /* This enumerated type defines the data types that can be stored * in AnyType. Notice that this is private and local to AnyType. */ private enum datatype { dt_int8, dt_int16, dt_int32, dt_int64, dt_single, dt_double, dt_decimal, dt_string, dt_datetime }; /* This structure is where the values are stored. There is a separate * field for each data type. I would like to store this as a union (or * the C# equivalent thereof). However, such code is considered unsafe, * and cannot be loaded into SQL Server without proper privileges. * * By the way, although the structure is private the members are public. * This is because various AnyType methods need to access the members. */ private struct union_values { public Byte value_int8; public Int16 value_int16; public Int32 value_int32; public Int64 value_int64; public float value_single; public double value_double; public String value_string; public Decimal value_decimal; public DateTime value_datetime; }; private bool isNull; private union_values value; private datatype type; /* This section contains constructors for AnyType for all sorts of * various types. The first one takes no arguments and simply returns * a NULL instance. */ public AnyType () { this.isNull = true; } // AnyType() public AnyType (SqlByte val) { type = datatype.dt_int8; value.value_int16 = val.Value; isNull = false; } // AnyType() public AnyType (SqlInt16 val) { type = datatype.dt_int16; value.value_int16 = val.Value; isNull = false; } // AnyType() public AnyType (SqlInt32 val) { type = datatype.dt_int32; value.value_int32 = val.Value; isNull = false; } // AnyType() public AnyType (SqlInt64 val) { type = datatype.dt_int64; value.value_int64 = val.Value; isNull = false; } // AnyType() public AnyType (SqlSingle val) { type = datatype.dt_single; value.value_single = val.Value; isNull = false; } // AnyType() public AnyType (SqlDouble val) { type = datatype.dt_double; value.value_double = val.Value; isNull = false; } // AnyType() public AnyType (SqlDecimal val) { type = datatype.dt_decimal; value.value_decimal = val.Value; isNull = false; } // AnyType() public AnyType (SqlMoney val) { type = datatype.dt_decimal; value.value_decimal = val.Value; isNull = false; } // AnyType() public AnyType (SqlDateTime val) { type = datatype.dt_datetime; value.value_datetime = val.Value; isNull = false; } // AnyType() public AnyType (SqlString val) { type = datatype.dt_string; value.value_string = val.Value; isNull = false; } // AnyType() public AnyType (AnyType val) { this.isNull = val.isNull; if (val.isNull) return; this.type = val.type; switch (val.type) { case datatype.dt_int8: this.value.value_int8 = val.value.value_int8; break; case datatype.dt_int16: this.value.value_int16 = val.value.value_int16; break; case datatype.dt_int32: this.value.value_int32 = val.value.value_int32; break; case datatype.dt_int64: this.value.value_int64 = val.value.value_int64; break; case datatype.dt_single: this.value.value_single = val.value.value_single; break; case datatype.dt_double: this.value.value_double = val.value.value_double; break; case datatype.dt_datetime: this.value.value_datetime = val.value.value_datetime; break; case datatype.dt_string: this.value.value_string = val.value.value_string; break; } } // AnyType() /* The following member provide NULL handlers. * Note that these are not functions. */ public bool IsNull { get { return isNull; } } // IsNull public static AnyType Null { get { AnyType at = new AnyType(); at.isNull = true; return at; } } // Null /* ToString() prints out "" for a NULL value or : for a value. */ public override string ToString () { if (this.isNull) { return (""); } switch (type) { case datatype.dt_int8: return "int8:"+value.value_int8.ToString(); case datatype.dt_int16: return "int16:"+value.value_int16.ToString(); case datatype.dt_int32: return "int32:"+value.value_int32.ToString(); case datatype.dt_int64: return "int64:"+value.value_int64.ToString(); case datatype.dt_single: return "single:"+value.value_single.ToString("G"); case datatype.dt_double: return "double:"+value.value_double.ToString("G"); case datatype.dt_decimal: return "decimal:"+value.value_decimal.ToString("G"); case datatype.dt_datetime: return "datetime:"+value.value_datetime.ToString(); case datatype.dt_string: return "string:"+value.value_string; } return ("UNKNOWN VALUE"); } // ToString() /* Parse() does the opposite of ToString(). */ public static AnyType Parse (SqlString s) { if (s.IsNull || "" == s.Value) { return (AnyType.Null); } try { string type = s.Value.Substring(0, s.Value.IndexOf(':') - 1); string value = s.Value.Substring(s.Value.IndexOf(':')+1); AnyType retval = new AnyType(); retval.isNull = false; switch (type) { case "int8" : retval.type = datatype.dt_int8; retval.value.value_int8 = Byte.Parse(value); break; case "int16" : retval.type = datatype.dt_int16; retval.value.value_int16 = Int16.Parse(value); break; case "int32": retval.type = datatype.dt_int32; retval.value.value_int32 = Int32.Parse(value); break; case "int64": retval.type = datatype.dt_int64; retval.value.value_int64 = Int64.Parse(value); break; case "single": retval.type = datatype.dt_single; retval.value.value_single = float.Parse(value); break; case "double": retval.type = datatype.dt_double; retval.value.value_double = double.Parse(value); break; case "decimal": retval.type = datatype.dt_decimal; retval.value.value_decimal = decimal.Parse(value); break; case "datetime" : retval.type = datatype.dt_datetime; retval.value.value_datetime = DateTime.Parse(value); break; case "string": retval.type = datatype.dt_string; retval.value.value_string = value; break; default: retval.isNull = true; break; } // switch return (retval); } catch (Exception ex) { throw new ArgumentException("The expected format is ':'", ex); } } // Parse() /* The following functions are accessors ala ToString(), except they return * all the various other types. Note that these functions return NULL if the * value cannot be readily converted to the desired type without overflow. * (Alternatively, the functions could raise an exception.) */ public SqlByte ToTinyInt () { if (this.isNull || (this.type != datatype.dt_int8)) { return SqlByte.Null; } return new SqlByte(this.value.value_int8); } // TinyInt() public SqlInt16 ToSmallInt () { if (this.isNull || (this.type != datatype.dt_int8 && this.type != datatype.dt_int16)) { return SqlInt16.Null; } return new SqlInt16(this.value.value_int16); } // SmallInt() public SqlInt32 ToInt () { if (this.isNull || (this.type != datatype.dt_int8 && this.type != datatype.dt_int16 && this.type != datatype.dt_int32)) { return SqlInt32.Null; } return new SqlInt32(this.value.value_int32); } // Int() public SqlInt64 ToBigInt () { if (this.isNull || (this.type != datatype.dt_int8 && this.type != datatype.dt_int16 && this.type != datatype.dt_int32 && this.type != datatype.dt_int64)) { return SqlInt64.Null; } return new SqlInt64(this.value.value_int64); } // BigInt() public SqlSingle ToReal () { if (this.isNull || (this.type != datatype.dt_single && this.type != datatype.dt_decimal && this.type != datatype.dt_int8 && this.type != datatype.dt_int16 && this.type != datatype.dt_int32 && this.type != datatype.dt_int64)) { return SqlSingle.Null; } return new SqlSingle(this.value.value_single); } // Real() public SqlDouble ToDouble () { if (this.isNull || (this.type != datatype.dt_double && this.type != datatype.dt_single && this.type != datatype.dt_decimal && this.type != datatype.dt_int8 && this.type != datatype.dt_int16 && this.type != datatype.dt_int32 && this.type != datatype.dt_int64)) { return SqlDouble.Null; } return new SqlDouble(this.value.value_double); } // Double() public SqlDouble ToDecimal () { if (this.isNull || (this.type != datatype.dt_double && this.type != datatype.dt_single && this.type != datatype.dt_decimal && this.type != datatype.dt_int8 && this.type != datatype.dt_int16 && this.type != datatype.dt_int32 && this.type != datatype.dt_int64)) { return SqlDecimal.Null; } return new SqlDecimal(this.value.value_double); } // Decimal() public SqlDateTime ToDateTime () { if (this.isNull || (this.type != datatype.dt_datetime)) { return SqlDateTime.Null; } return new SqlDateTime(this.value.value_datetime); } // DateTime() /* Next are the Write() and Read() functions needed for the API. * The binary representation is the NULL flag, the type, and then * the value. */ public void Write (BinaryWriter w) { w.Write(this.isNull); if (!this.isNull) { w.Write((byte)this.type); switch (this.type) { case datatype.dt_int8: w.Write(this.value.value_int8); break; case datatype.dt_int16: w.Write(this.value.value_int16); break; case datatype.dt_int32: w.Write(this.value.value_int32); break; case datatype.dt_int64: w.Write(this.value.value_int64); break; case datatype.dt_single: w.Write(this.value.value_single); break; case datatype.dt_double: w.Write(this.value.value_double); break; case datatype.dt_decimal: w.Write(this.value.value_decimal); break; case datatype.dt_datetime: w.Write((long)this.value.value_datetime.Ticks); break; case datatype.dt_string: w.Write(this.value.value_string); break; } } } // Write() public void Read (BinaryReader r) { this.isNull = r.ReadBoolean(); if (!this.isNull) { this.type = (datatype)r.ReadByte(); switch (this.type) { case datatype.dt_int8: this.value.value_int8 = r.ReadByte(); break; case datatype.dt_int16: this.value.value_int16 = r.ReadInt16(); break; case datatype.dt_int32: this.value.value_int32 = r.ReadInt32(); break; case datatype.dt_int64: this.value.value_int64 = r.ReadInt64(); break; case datatype.dt_single: this.value.value_single = r.ReadSingle(); break; case datatype.dt_double: this.value.value_double = r.ReadDouble(); break; case datatype.dt_decimal: this.value.value_decimal = r.ReadDecimal(); break; case datatype.dt_datetime: this.value.value_datetime = new DateTime((long)r.ReadInt64()); break; case datatype.dt_string: this.value.value_string = r.ReadString(); break; } } } // Read() /* The comparison operators are needed to implement the MinOf() and MaxOf() * aggregation functions. C# requires that both "<" and ">" be defined, or * neither. */ public static bool operator <(AnyType at1, AnyType at2) { // NULL comparisons always return false. if (at1.isNull || at2.isNull) { return (false); } switch (at1.type) { case datatype.dt_int8: switch (at2.type) { case datatype.dt_int8: return (at1.value.value_int8 < at2.value.value_int8); case datatype.dt_int16: return (at1.value.value_int8 < at2.value.value_int16); case datatype.dt_int32: return (at1.value.value_int8 < at2.value.value_int32); case datatype.dt_int64: return (at1.value.value_int8 < at2.value.value_int64); case datatype.dt_single: return (at1.value.value_int8 < at2.value.value_single); case datatype.dt_double: return (at1.value.value_int8 < at2.value.value_double); } return (false); case datatype.dt_int16: switch (at2.type) { case datatype.dt_int8: return (at1.value.value_int16 < at2.value.value_int8); case datatype.dt_int16: return (at1.value.value_int16 < at2.value.value_int16); case datatype.dt_int32: return (at1.value.value_int16 < at2.value.value_int32); case datatype.dt_int64: return (at1.value.value_int16 < at2.value.value_int64); case datatype.dt_single: return (at1.value.value_int16 < at2.value.value_single); case datatype.dt_double: return (at1.value.value_int16 < at2.value.value_double); } return (false); case datatype.dt_int32: switch (at2.type) { case datatype.dt_int8: return (at1.value.value_int32 < at2.value.value_int8); case datatype.dt_int16: return (at1.value.value_int32 < at2.value.value_int16); case datatype.dt_int32: return (at1.value.value_int32 < at2.value.value_int32); case datatype.dt_int64: return (at1.value.value_int32 < at2.value.value_int64); case datatype.dt_single: return (at1.value.value_int32 < at2.value.value_single); case datatype.dt_double: return (at1.value.value_int32 < at2.value.value_double); } return (false); case datatype.dt_int64: switch (at2.type) { case datatype.dt_int8: return (at1.value.value_int64 < at2.value.value_int8); case datatype.dt_int16: return (at1.value.value_int64 < at2.value.value_int16); case datatype.dt_int32: return (at1.value.value_int64 < at2.value.value_int32); case datatype.dt_int64: return (at1.value.value_int64 < at2.value.value_int64); case datatype.dt_single: return (at1.value.value_int64 < at2.value.value_single); case datatype.dt_double: return (at1.value.value_int64 < at2.value.value_double); } return (false); case datatype.dt_single: switch (at2.type) { case datatype.dt_int8: return (at1.value.value_single < at2.value.value_int8); case datatype.dt_int16: return (at1.value.value_single < at2.value.value_int16); case datatype.dt_int32: return (at1.value.value_single < at2.value.value_int32); case datatype.dt_int64: return (at1.value.value_single < at2.value.value_int64); case datatype.dt_single: return (at1.value.value_single < at2.value.value_single); case datatype.dt_double: return (at1.value.value_single < at2.value.value_double); } return (false); case datatype.dt_double: switch (at2.type) { case datatype.dt_int8: return (at1.value.value_double < at2.value.value_int8); case datatype.dt_int16: return (at1.value.value_double < at2.value.value_int16); case datatype.dt_int32: return (at1.value.value_double < at2.value.value_int32); case datatype.dt_int64: return (at1.value.value_double < at2.value.value_int64); case datatype.dt_single: return (at1.value.value_double < at2.value.value_single); case datatype.dt_double: return (at1.value.value_double < at2.value.value_double); } return (false); case datatype.dt_datetime: switch (at2.type) { case datatype.dt_datetime: return (at1.value.value_datetime < at2.value.value_datetime); } return (false); case datatype.dt_string: switch (at2.type) { case datatype.dt_string: return (at1.value.value_string.CompareTo(at2.value.value_string) < 0); } return (false); } return (false); } // operator < public static bool operator >(AnyType at1, AnyType at2) { return (at2 < at1); } // operator > } // AnyType /* The following type stores two instances of AnyType as a pair. This * is needed as the argument to the MinOf() and MaxOf() aggregation * functions, since the API only allows one argument to aggregation * functions. */ [Serializable] [Microsoft.SqlServer.Server.SqlUserDefinedType(Format.UserDefined, MaxByteSize = 8000)] public class ValuePair : INullable, IBinarySerialize { private bool isNull; private AnyType value1; private AnyType value2; /* First the constructors. */ public ValuePair () { this.isNull = true; } // ValuePair() public ValuePair (AnyType val1, AnyType val2) { this.value1 = new AnyType(val1); this.value2 = new AnyType(val2); this.isNull = false; } // ValuePair() public ValuePair (ValuePair vp) { this.isNull = vp.isNull; if (!this.isNull) { value1 = new AnyType(vp.value1); value2 = new AnyType(vp.value2); } } // ValuePair() /* Then the NULL members. */ public bool IsNull { get { return isNull; } } // IsNull public static ValuePair Null { get { ValuePair vp = new ValuePair(); vp.isNull = true; return vp; } } // Null /* Then accessors for value1and value2. */ public AnyType Value1 { get { return this.isNull ? AnyType.Null : this.value1; } } // Value1 public AnyType Value2 { get { return this.isNull ? AnyType.Null : this.value2; } } // Value2 /* The string format is "||". This can pose problems * when either value is a string and contains two double bars. */ public override string ToString () { if (this.isNull) { return (""); } return "value1.ToString()" + "||" + value2.ToString(); } // ToString() public static ValuePair Parse (SqlString s) { if (s.IsNull || ("" == s)) { return (ValuePair.Null); } try { string value1 = s.Value.Substring(0, s.Value.IndexOf("||")); string value2 = s.Value.Substring(s.Value.IndexOf("||") + 1); return new ValuePair(AnyType.Parse(value1), AnyType.Parse(value2)); } catch (Exception ex) { throw new ArgumentException("The expected format is '||'", ex); } } // Parse() /* Write() and Read() simply write the isNull flag and the two AnyType values. */ public void Write (BinaryWriter w) { w.Write(this.isNull); if (!this.isNull) { this.value1.Write(w); this.value2.Write(w); } } // Write() public void Read (BinaryReader r) { this.isNull = r.ReadBoolean(); if (!this.isNull) { this.value1 = new AnyType(); this.value2 = new AnyType(); this.value1.Read(r); this.value2.Read(r); } } // Read() } // ValuePair [Serializable] [SqlUserDefinedAggregate(Format.UserDefined, IsInvariantToNulls = true, IsInvariantToDuplicates = false, IsInvariantToOrder = false, MaxByteSize = 8000)] public class MinOf : IBinarySerialize { /* This aggregation returns the value of minvalue where minwhere is * minimized. */ private AnyType minvalue; private AnyType minwhere; public void Init () { this.minvalue = AnyType.Null; this.minwhere = AnyType.Null; } // Init() public void Accumulate (ValuePair value) { if (value.IsNull || value.Value1.IsNull || value.Value2.IsNull) { return; } if (this.minvalue.IsNull || (value.Value2 < minwhere)) { this.minvalue = new AnyType(value.Value1); this.minwhere = new AnyType(value.Value2); } } // Accumulate() public void Merge (MinOf group) { Accumulate(new ValuePair(group.minvalue, group.minwhere)); } // Merge() public AnyType Terminate () { return minvalue; } // Terminate() public void Write (BinaryWriter w) { minvalue.Write(w); minwhere.Write(w); } // Write() public void Read (BinaryReader r) { minvalue = new AnyType(); minvalue.Read(r); minwhere = new AnyType(); minwhere.Read(r); } // Read() } // ValueAtMin [Serializable] [SqlUserDefinedAggregate(Format.UserDefined, IsInvariantToNulls = true, IsInvariantToDuplicates = false, IsInvariantToOrder = false, MaxByteSize = 8000)] public class MaxOf : IBinarySerialize { /* This aggregation returns the value of minvalue where minwhere is * minimized. */ private AnyType maxvalue; private AnyType maxwhere; public void Init () { this.maxvalue = AnyType.Null; this.maxwhere = AnyType.Null; } // Init() public void Accumulate (ValuePair value) { if (value.IsNull || value.Value1.IsNull || value.Value2.IsNull) { return; } if (this.maxvalue.IsNull || (value.Value2 < maxwhere)) { this.maxvalue = new AnyType(value.Value1); this.maxwhere = new AnyType(value.Value2); } } // Accumulate() public void Merge (MaxOf group) { Accumulate(new ValuePair(group.maxvalue, group.maxwhere)); } // Merge() public AnyType Terminate () { return maxvalue; } // Terminate() public void Write (BinaryWriter w) { maxvalue.Write(w); maxwhere.Write(w); } // Write() public void Read (BinaryReader r) { maxvalue = new AnyType(); maxvalue.Read(r); maxwhere = new AnyType(); maxwhere.Read(r); } // Read() } // MaxOf [Serializable] [Microsoft.SqlServer.Server.SqlUserDefinedType(Format.UserDefined, MaxByteSize = 8000)] public class MarginalValueModelArgs : INullable, IBinarySerialize { private bool isNull; public int numdimensions; public System.Collections.Generic.List dimensions; public double value; public bool IsNull { get { return isNull; } } // IsNull public static MarginalValueModelArgs Null { get { MarginalValueModelArgs evma = new MarginalValueModelArgs(); return evma; } } // Null public MarginalValueModelArgs() { isNull = true; dimensions = new System.Collections.Generic.List(); value = 1; this.numdimensions = 0; } // MarginalValueModelArgs() public MarginalValueModelArgs(string s, double value) : this() { this.isNull = false; this.value = value; this.dimensions.Add(s); this.numdimensions = 1; } // MarginalValueModelArgs () public MarginalValueModelArgs(string s1, string s2, double value) : this() { this.isNull = false; this.value = value; this.dimensions.Add(s1); this.dimensions.Add(s2); this.numdimensions = 2; } // MarginalValueModelArgs () public MarginalValueModelArgs AddDim(string s) { this.dimensions.Add(s); this.numdimensions++; return (this); } // AddDim() public override string ToString() { if (this.isNull) { return (""); } string retval = "value=" + value.ToString() + ";"; retval += "numdimensions=" + numdimensions.ToString() + ";"; foreach (string s in this.dimensions) { retval += "dimension=" + s + ";"; } return (retval); } // ToString() public static MarginalValueModelArgs Parse(SqlString s) { if (s.IsNull) { return (Null); } if ("" == s.Value) { return (Null); } try { string[] values = s.Value.Split(';'); MarginalValueModelArgs evma = new MarginalValueModelArgs(); evma.value = 1; for (int i = 0; i < values.GetLength(0); i++) { string[] parts = values[i].Split('='); switch (parts[0]) { case "numdimensions": evma.numdimensions = int.Parse(parts[1]); break; case "value": evma.value = double.Parse(parts[1]); break; case "dimension": evma.dimensions.Add(parts[1]); evma.isNull = false; break; } } return (evma); } catch (Exception ex) { throw new ArgumentException("The expected format is 'value=;dimension=;. . .'", ex); } } // Parse() public void Write(BinaryWriter w) { w.Write(this.isNull); if (!this.isNull) { w.Write(this.value); w.Write(this.numdimensions); foreach (string s in this.dimensions) { w.Write(s); } } } // Write() public void Read(BinaryReader r) { this.isNull = r.ReadBoolean(); this.value = 1; this.dimensions = new List(); if (!this.isNull) { this.value = r.ReadDouble(); this.numdimensions = r.ReadInt32(); for (int i = 0; i < this.numdimensions; i++) { this.dimensions.Add(r.ReadString()); } } } // Read() } // MarginalValueModelArgs [Serializable] [Microsoft.SqlServer.Server.SqlUserDefinedType(Format.UserDefined, MaxByteSize = 8000)] public class MarginalValueModelElement : INullable, IBinarySerialize { private bool isNull; public string key; public double value; public bool IsNull { get { return isNull; } } // IsNull public static MarginalValueModelElement Null { get { MarginalValueModelElement evme = new MarginalValueModelElement(); return evme; } } // Null public MarginalValueModelElement() { isNull = true; } // MarginalValueModelElement() public MarginalValueModelElement(string s, double v) : this() { this.isNull = false; this.key = s; this.value = v; } // MarginalValueModelElement() public SqlString Key { get { return new SqlString(this.key); } } // Key public SqlDouble Value { get { return new SqlDouble(this.value); } } // Value public override string ToString() { if (this.isNull) { return (""); } string retval = "key=" + key + ";"; retval += "value=" + this.value.ToString() + ";"; return (retval); } // ToString() public static MarginalValueModelElement Parse(SqlString s) { if (s.IsNull) { return (Null); } if ("" == s.Value) { return (Null); } try { string[] values = s.Value.Split(';'); MarginalValueModelElement evme = new MarginalValueModelElement(); for (int i = 0; i < values.GetLength(0); i++) { string[] parts = values[i].Split('='); switch (parts[0]) { case "key": evme.key = parts[1]; break; case "value": evme.value = double.Parse(parts[1]); evme.isNull = false; break; } } return (evme); } catch (Exception ex) { throw new ArgumentException("The expected format is 'key=;value=;'", ex); } } // Parse() public void Write(BinaryWriter w) { w.Write(this.isNull); if (!this.isNull) { w.Write(this.key); w.Write(this.value); } } // Write() public void Read(BinaryReader r) { this.isNull = r.ReadBoolean(); if (!this.isNull) { this.key = r.ReadString(); this.value = r.ReadDouble(); } } // Read() } // MarginalValueModelElement [Serializable] [Microsoft.SqlServer.Server.SqlUserDefinedType(Format.UserDefined, MaxByteSize = 8000)] public class BasicMarginalValueModel : INullable, IBinarySerialize, System.Collections.IEnumerable { public bool isNull; public int numdimensions; public System.Collections.Generic.Dictionary marginals; static public string make_key(int dim, string val) { return (dim.ToString() + ":" + val); } // make_key() static public string make_key(int dim, MarginalValueModelArgs val) { if (val.IsNull) { return (dim.ToString() + ":"); } return make_key(dim, val.dimensions[dim]); } // make_key() public bool IsNull { get { return isNull; } } // IsNull public static BasicMarginalValueModel Null { get { BasicMarginalValueModel bmvm = new BasicMarginalValueModel(); return bmvm; } } // Null public BasicMarginalValueModel() { isNull = true; marginals = new System.Collections.Generic.Dictionary(); numdimensions = 0; marginals.Add("total:", 0.0); } // MarginalValueModel() public SqlDouble Lookup(SqlInt32 dim, SqlString value) { return new SqlDouble(this.marginals[make_key(dim.Value, value.Value)]); } // Lookup() public static double _Score (BasicMarginalValueModel mv, MarginalValueModelArgs mvma) { double expected = 1; for (int i = 0; i < mv.numdimensions; i++) { string dimension_name = make_key(i, mvma.dimensions[i]); expected *= mv.marginals[dimension_name]; } return (expected / Math.Pow(mv.marginals["total:"], mv.numdimensions - 1)); } // Score() public SqlDouble Score (MarginalValueModelArgs mvma) { if (mvma.numdimensions != this.numdimensions) { throw new ArgumentException("Number of dimensions to score does not match number of dimensions"); } return new SqlDouble(_Score(this, mvma)); } // Score() public SqlDouble ChiSquared (MarginalValueModelArgs mvma) { if (mvma.numdimensions != this.numdimensions) { throw new ArgumentException("Number of dimensions to score does not match number of dimensions"); } double expected = _Score(this, mvma); return (Math.Pow(mvma.value - expected, 2) / expected); } // CellChiSquared() public class AllCellsEnumerator : System.Collections.IEnumerator { private System.Collections.IEnumerator[] enumerators; private int numdimensions; private BasicMarginalValueModel bmvm; private bool[] need_to_move_next; public AllCellsEnumerator(BasicMarginalValueModel bmvm) { enumerators = new System.Collections.IEnumerator[bmvm.numdimensions]; this.need_to_move_next = new bool[bmvm.numdimensions]; this.numdimensions = bmvm.numdimensions; for (int i = 0; i < bmvm.numdimensions; i++) { enumerators[i] = bmvm.marginals.GetEnumerator(); this.need_to_move_next[i] = true; } this.bmvm = bmvm; } // MarginalValueModelEnumerator public bool MoveNext() { for (int i = 0; i < this.numdimensions; i++) { if (this.need_to_move_next[i]) { bool found = false; while (enumerators[i].MoveNext()) { KeyValuePair kvp = (KeyValuePair)enumerators[i].Current; if (kvp.Key.StartsWith(i.ToString() + ":")) { found = true; break; } } if (found) { this.need_to_move_next[i] = false; } else { if (0 == i) { return (false); } else { for (int j = i; j < this.numdimensions; j++) { enumerators[j].Reset(); this.need_to_move_next[j] = true; } this.need_to_move_next[i - 1] = true; return (MoveNext()); } } } } this.need_to_move_next[this.numdimensions - 1] = true; return (true); } // MoveNext() public object Current { get { MarginalValueModelArgs mvma = null; for (int i = 0; i < this.numdimensions; i++) { KeyValuePair kvp = (KeyValuePair)enumerators[i].Current; string[] pairs = kvp.Key.Split(':'); if (0 == i) { mvma = new MarginalValueModelArgs(pairs[1], 1.0); } else { mvma.AddDim(pairs[1]); } } if (null != mvma) { mvma.value = this.bmvm.Score(mvma).Value; } return mvma; } } public void Reset() { for (int i = 0; i < this.numdimensions; i++) { enumerators[i].Reset(); } } // Reset() } // AllCellsEnumerator /* I am hypothesizing that GetEnumerator() is used by C# foreach, and not by the CLR */ public System.Collections.IEnumerator GetEnumerator() { return new AllCellsEnumerator(this); //return new Dictionary.Enumerator(); } // GetEnumerator() [SqlFunction(FillRowMethodName = "AllCellsEnumeratorFillRow")] public static AllCellsEnumerator InitAllCellsEnumerator (BasicMarginalValueModel bmvm) { return new AllCellsEnumerator(bmvm); } public static void AllCellsEnumeratorFillRow (Object row, out MarginalValueModelArgs evma) { evma = (MarginalValueModelArgs)row; } public class BasicMarginalValueModelElementEnumerator : System.Collections.IEnumerator { private System.Collections.IEnumerator enumerator; public BasicMarginalValueModelElementEnumerator (BasicMarginalValueModel csm) { this.enumerator = csm.marginals.GetEnumerator(); } // MarginalValueModelElementEnumerator public bool MoveNext() { return (this.enumerator.MoveNext()); } // MoveNext() public object Current { get { KeyValuePair kvp = (KeyValuePair)enumerator.Current; MarginalValueModelElement evme; evme = new MarginalValueModelElement(kvp.Key, kvp.Value); return evme; } } public void Reset() { enumerator.Reset(); } // Reset() } // MarginalValueMethodELementEnumerator [SqlFunction(FillRowMethodName = "BasicMarginalValueModelEnumeratorFillRow")] public static BasicMarginalValueModelElementEnumerator InitMarginalValueEnumerator (BasicMarginalValueModel csm) //public static AllCellsEnumerator InitMarginalValueEnumerator (MarginalValueModel csm) { //return new AllCellsEnumerator(csm); return new BasicMarginalValueModelElementEnumerator(csm); } public static void BasicMarginalValueModelEnumeratorFillRow (Object row, out MarginalValueModelElement evme) { evme = (MarginalValueModelElement)row; } // BasicMarginalValueModelEnumeratorFillRow() public override string ToString () { if (this.isNull) { return (""); } string retval = "numdimensions=" + this.numdimensions.ToString() + ";"; foreach (KeyValuePair kvp in marginals) { retval += "entry="+kvp.Key + ',' + kvp.Value+ ";"; } return (retval); } // ToString() public static BasicMarginalValueModel Parse (SqlString s) { if (s.IsNull) { return (Null); } if ("" == s.Value) { return (Null); } try { string[] values = s.Value.Split(';'); BasicMarginalValueModel bmvm = new BasicMarginalValueModel(); bmvm.isNull = false; for (int i = 0; i < values.GetLength(0); i++) { string[] parts = values[i].Split('='); switch (parts[0]) { case "numdimensions": bmvm.numdimensions = Int32.Parse(parts[1]); break; case "entry": string[] keyvalue = parts[1].Split(','); double val = Double.Parse(keyvalue[1]); bmvm.marginals.Add(keyvalue[0], val); break; } } return (bmvm); } catch (Exception ex) { throw new ArgumentException("The expected format is 'numdimensions=;=,;...'", ex); } } // Parse() public void Write(BinaryWriter w) { w.Write(this.isNull); if (!this.isNull) { w.Write(this.numdimensions); w.Write(this.marginals.Count); foreach (KeyValuePair kvp in marginals) { w.Write(kvp.Key); w.Write(kvp.Value); } } } // Write() [Microsoft.SqlServer.Server.SqlMethod(DataAccess = DataAccessKind.Read)] public void Read(BinaryReader r) { this.isNull = r.ReadBoolean(); this.marginals = new Dictionary(); this.numdimensions = 0; marginals.Remove("total:"); if (!this.isNull) { this.numdimensions = r.ReadInt32(); int cnt = r.ReadInt32(); for (int i = 0; i < cnt; i++) { this.marginals.Add(r.ReadString(), r.ReadDouble()); } } } // Read() } // BasicMarginalValueModel [Serializable] [SqlUserDefinedAggregate(Format.UserDefined, IsInvariantToNulls = true, IsInvariantToDuplicates = false, IsInvariantToOrder = false, MaxByteSize = 8000)] public class CreateBasicMarginalValueModel : IBinarySerialize { public BasicMarginalValueModel csm; public void Init() { try { csm = new BasicMarginalValueModel(); } catch (Exception ex) { throw new ArgumentException("Init encountered exception", ex); } } // Init() public void Accumulate(MarginalValueModelArgs value) { if (!value.IsNull) { try { csm.isNull = false; if (0 == csm.numdimensions) { csm.numdimensions = value.numdimensions; } for (int i = 0; i < value.numdimensions; i++) { string key = BasicMarginalValueModel.make_key(i, value); if (!csm.marginals.ContainsKey(key)) { csm.marginals.Add(key, value.value); } else { csm.marginals[key] += value.value; } } csm.marginals["total:"] += value.value; } catch (Exception ex) { throw new ArgumentException("Accumulate encountered exception", ex); } } } // Accumulate() public void Merge(CreateBasicMarginalValueModel group) { try { foreach (KeyValuePair kvp in group.csm.marginals) { if (!csm.marginals.ContainsKey(kvp.Key)) { csm.marginals.Add(kvp.Key, kvp.Value); } else { csm.marginals[kvp.Key] += kvp.Value; } } this.csm.marginals["total:"] += group.csm.marginals["total:"]; } catch (Exception ex) { throw new ArgumentException("Merge encountered exception", ex); } } // Merge() public BasicMarginalValueModel Terminate () { return this.csm; } // Terminate() public void Write(BinaryWriter w) { this.csm.Write(w); } //Write() public void Read(BinaryReader r) { csm = new BasicMarginalValueModel(); this.csm.Read(r); } // Read() } // CreateMarginalValueModel public partial class UserDefinedFunctions { [Microsoft.SqlServer.Server.SqlFunction] public static WeightedValue CreateWeightedValue (SqlDouble val, SqlDouble wgt) { if (val.IsNull || wgt.IsNull) { return WeightedValue.Null; } return new WeightedValue(val, wgt); } // CreateWeightedValue() [Microsoft.SqlServer.Server.SqlFunction] public static AnyType AnyTypeNull() { return AnyType.Null; } /* The following user defined functions return an AnyType value * given various SQL types. */ [Microsoft.SqlServer.Server.SqlFunction] public static AnyType AnyTinyInt (SqlByte val) { return new AnyType(val); } // AnyTinyInt() [Microsoft.SqlServer.Server.SqlFunction] public static AnyType AnySmallInt (SqlInt16 val) { return new AnyType(val); } // AnySmallInt() [Microsoft.SqlServer.Server.SqlFunction] public static AnyType AnyInt (SqlInt32 val) { return new AnyType(val); } // AnyInt() [Microsoft.SqlServer.Server.SqlFunction] public static AnyType AnyBigInt (SqlInt64 val) { return new AnyType(val); } // AnyBigInt() [Microsoft.SqlServer.Server.SqlFunction] public static AnyType AnyReal(SqlSingle val) { return new AnyType(val); } // AnyReal() [Microsoft.SqlServer.Server.SqlFunction] public static AnyType AnyDouble (SqlDouble val) { return new AnyType(val); } // AnyDouble() [Microsoft.SqlServer.Server.SqlFunction] public static AnyType AnyDecimal (SqlDecimal val) { return new AnyType(val); } // AnyDecimal() [Microsoft.SqlServer.Server.SqlFunction] public static AnyType AnyDateTime (SqlDateTime val) { return new AnyType(val); } // AnyDateTime() [Microsoft.SqlServer.Server.SqlFunction] public static AnyType AnyString (SqlString val) { return new AnyType(val); } // AnyString() /* This final function generates a ValuePair from two AnyType arguments. */ [Microsoft.SqlServer.Server.SqlFunction] public static ValuePair ValuePair (AnyType val1, AnyType val2) { return new ValuePair(val1, val2); } // ValuePair() [Microsoft.SqlServer.Server.SqlFunction] public static MarginalValueModelArgs MarginalValueArgs1 (SqlString dim, SqlDouble weight) { return new MarginalValueModelArgs(dim.IsNull ? "" : dim.Value, weight.Value); } // MarginalValueArgs1() public static MarginalValueModelArgs MarginalValueArgs2 (SqlString dim1, SqlString dim2, SqlDouble weight) { return new MarginalValueModelArgs(dim1.IsNull ? "" : dim1.Value, dim2.IsNull ? "" : dim2.Value, weight.Value); } // MarginalValueArgs2() } // UserDefinedFunctions