package sqltypes import ( "fmt" "strconv" "time" ) var ( // NULL represents the NULL value. NULL = Value{} // DontEscape tells you if a character should not be escaped. DontEscape = byte(255) nullstr = []byte("null") ) type Value struct { typ Type val []byte } // NewValue builds a Value using typ and val. If the value and typ // don't match, it returns an error. func NewValue(typ Type, val []byte) (v Value, err error) { switch { case IsSigned(typ): if _, err := strconv.ParseInt(string(val), 0, 64); err != nil { return NULL, err } return MakeTrusted(typ, val), nil case IsUnsigned(typ): if _, err := strconv.ParseUint(string(val), 0, 64); err != nil { return NULL, err } return MakeTrusted(typ, val), nil case IsFloat(typ) || typ == Decimal: if _, err := strconv.ParseFloat(string(val), 64); err != nil { return NULL, err } return MakeTrusted(typ, val), nil case IsQuoted(typ) || typ == Bit || typ == Null: return MakeTrusted(typ, val), nil } // All other types are unsafe or invalid. return NULL, fmt.Errorf("invalid type specified for MakeValue: %v", typ) } // MakeTrusted makes a new Value based on the type. // This function should only be used if you know the value // and type conform to the rules. Every place this function is // called, a comment is needed that explains why it's justified. // Exceptions: The current package and mysql package do not need // comments. Other packages can also use the function to create // VarBinary or VarChar values. func MakeTrusted(typ Type, val []byte) Value { if typ == Null { return NULL } return Value{typ: typ, val: val} } // NewInt64 builds an Int64 Value. func NewInt64(v int64) Value { return MakeTrusted(Int64, strconv.AppendInt(nil, v, 10)) } // NewInt32 builds an Int64 Value. func NewInt32(v int32) Value { return MakeTrusted(Int32, strconv.AppendInt(nil, int64(v), 10)) } // NewUint64 builds an Uint64 Value. func NewUint64(v uint64) Value { return MakeTrusted(Uint64, strconv.AppendUint(nil, v, 10)) } // NewFloat32 builds an Float64 Value. func NewFloat32(v float32) Value { return MakeTrusted(Float32, strconv.AppendFloat(nil, float64(v), 'f', -1, 64)) } // NewFloat64 builds an Float64 Value. func NewFloat64(v float64) Value { return MakeTrusted(Float64, strconv.AppendFloat(nil, v, 'g', -1, 64)) } // NewVarChar builds a VarChar Value. func NewVarChar(v string) Value { return MakeTrusted(VarChar, []byte(v)) } // NewVarBinary builds a VarBinary Value. // The input is a string because it's the most common use case. func NewVarBinary(v string) Value { return MakeTrusted(VarBinary, []byte(v)) } // NewIntegral builds an integral type from a string representation. // The type will be Int64 or Uint64. Int64 will be preferred where possible. func NewIntegral(val string) (n Value, err error) { signed, err := strconv.ParseInt(val, 0, 64) if err == nil { return MakeTrusted(Int64, strconv.AppendInt(nil, signed, 10)), nil } unsigned, err := strconv.ParseUint(val, 0, 64) if err != nil { return Value{}, err } return MakeTrusted(Uint64, strconv.AppendUint(nil, unsigned, 10)), nil } // MakeString makes a VarBinary Value. func MakeString(val []byte) Value { return MakeTrusted(VarBinary, val) } // BuildValue builds a value from any go type. sqltype.Value is // also allowed. func BuildValue(goval interface{}) (v Value, err error) { // Look for the most common types first. switch goval := goval.(type) { case nil: // no op case []byte: v = MakeTrusted(VarBinary, goval) case int64: v = MakeTrusted(Int64, strconv.AppendInt(nil, int64(goval), 10)) case uint64: v = MakeTrusted(Uint64, strconv.AppendUint(nil, uint64(goval), 10)) case float64: v = MakeTrusted(Float64, strconv.AppendFloat(nil, goval, 'f', -1, 64)) case int: v = MakeTrusted(Int64, strconv.AppendInt(nil, int64(goval), 10)) case int8: v = MakeTrusted(Int8, strconv.AppendInt(nil, int64(goval), 10)) case int16: v = MakeTrusted(Int16, strconv.AppendInt(nil, int64(goval), 10)) case int32: v = MakeTrusted(Int32, strconv.AppendInt(nil, int64(goval), 10)) case uint: v = MakeTrusted(Uint64, strconv.AppendUint(nil, uint64(goval), 10)) case uint8: v = MakeTrusted(Uint8, strconv.AppendUint(nil, uint64(goval), 10)) case uint16: v = MakeTrusted(Uint16, strconv.AppendUint(nil, uint64(goval), 10)) case uint32: v = MakeTrusted(Uint32, strconv.AppendUint(nil, uint64(goval), 10)) case float32: v = MakeTrusted(Float32, strconv.AppendFloat(nil, float64(goval), 'f', -1, 64)) case string: v = MakeTrusted(VarBinary, []byte(goval)) case time.Time: v = MakeTrusted(Datetime, []byte(goval.Format("2006-01-02 15:04:05"))) case Value: v = goval case *BindVariable: return ValueFromBytes(goval.Type, goval.Value) default: return v, fmt.Errorf("unexpected type %T: %v", goval, goval) } return v, nil } // BuildConverted is like BuildValue except that it tries to // convert a string or []byte to an integral if the target type // is an integral. We don't perform other implicit conversions // because they're unsafe. func BuildConverted(typ Type, goval interface{}) (v Value, err error) { if IsIntegral(typ) { switch goval := goval.(type) { case []byte: return ValueFromBytes(typ, goval) case string: return ValueFromBytes(typ, []byte(goval)) case Value: if goval.IsQuoted() { return ValueFromBytes(typ, goval.Raw()) } } } return BuildValue(goval) } // ValueFromBytes builds a Value using typ and val. It ensures that val // matches the requested type. If type is an integral it's converted to // a canonical form. Otherwise, the original representation is preserved. func ValueFromBytes(typ Type, val []byte) (v Value, err error) { switch { case IsSigned(typ): signed, err := strconv.ParseInt(string(val), 0, 64) if err != nil { return NULL, err } v = MakeTrusted(typ, strconv.AppendInt(nil, signed, 10)) case IsUnsigned(typ): unsigned, err := strconv.ParseUint(string(val), 0, 64) if err != nil { return NULL, err } v = MakeTrusted(typ, strconv.AppendUint(nil, unsigned, 10)) case IsFloat(typ) || typ == Decimal: _, err := strconv.ParseFloat(string(val), 64) if err != nil { return NULL, err } // After verification, we preserve the original representation. fallthrough default: v = MakeTrusted(typ, val) } return v, nil } // BuildIntegral builds an integral type from a string representation. // The type will be Int64 or Uint64. Int64 will be preferred where possible. func BuildIntegral(val string) (n Value, err error) { signed, err := strconv.ParseInt(val, 0, 64) if err == nil { return MakeTrusted(Int64, strconv.AppendInt(nil, signed, 10)), nil } unsigned, err := strconv.ParseUint(val, 0, 64) if err != nil { return Value{}, err } return MakeTrusted(Uint64, strconv.AppendUint(nil, unsigned, 10)), nil } // Type returns the type of Value. func (v Value) Type() Type { return v.typ } // Raw returns the raw bytes. All types are currently implemented as []byte. // You should avoid using this function. If you do, you should treat the // bytes as read-only. func (v Value) Raw() []byte { return v.val } // Len returns the length. func (v Value) Len() int { return len(v.val) } // Values represents the array of Value. type Values []Value // Len implements the interface. func (vs Values) Len() int { len := 0 for _, v := range vs { len += v.Len() } return len } // String returns the raw value as a string. func (v Value) String() string { return BytesToString(v.val) } // ToNative converts Value to a native go type. // This does not work for sqltypes.Tuple. The function // panics if there are inconsistencies. func (v Value) ToNative() interface{} { var out interface{} var err error switch { case v.typ == Null: // no-op case IsSigned(v.typ): out, err = v.ParseInt64() case IsUnsigned(v.typ): out, err = v.ParseUint64() case IsFloat(v.typ): out, err = v.ParseFloat64() default: out = v.val } if err != nil { panic(err) } return out } // ParseInt64 will parse a Value into an int64. It does // not check the type. func (v Value) ParseInt64() (val int64, err error) { return strconv.ParseInt(v.String(), 10, 64) } // ParseUint64 will parse a Value into a uint64. It does // not check the type. func (v Value) ParseUint64() (val uint64, err error) { return strconv.ParseUint(v.String(), 10, 64) } // ParseFloat64 will parse a Value into an float64. It does // not check the type. func (v Value) ParseFloat64() (val float64, err error) { return strconv.ParseFloat(v.String(), 64) } // IsNull returns true if Value is null. func (v Value) IsNull() bool { return v.typ == Null } // IsIntegral returns true if Value is an integral. func (v Value) IsIntegral() bool { return IsIntegral(v.typ) } // IsSigned returns true if Value is a signed integral. func (v Value) IsSigned() bool { return IsSigned(v.typ) } // IsUnsigned returns true if Value is an unsigned integral. func (v Value) IsUnsigned() bool { return IsUnsigned(v.typ) } // IsFloat returns true if Value is a float. func (v Value) IsFloat() bool { return IsFloat(v.typ) } // IsQuoted returns true if Value must be SQL-quoted. func (v Value) IsQuoted() bool { return IsQuoted(v.typ) } // IsText returns true if Value is a collatable text. func (v Value) IsText() bool { return IsText(v.typ) } // IsBinary returns true if Value is binary. func (v Value) IsBinary() bool { return IsBinary(v.typ) } // IsTemporal returns true if Value is time type. func (v Value) IsTemporal() bool { return IsTemporal(v.typ) } // ToString returns the value as MySQL would return it as string. // If the value is not convertible like in the case of Expression, it returns nil. func (v Value) ToString() string { return BytesToString(v.val) }