Skip to content

Commit 167cf51

Browse files
committed
Remove as much casting as possible
1 parent 0b89bf4 commit 167cf51

File tree

1 file changed

+68
-29
lines changed

1 file changed

+68
-29
lines changed

sql/yeartype.go

Lines changed: 68 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -125,34 +125,49 @@ func (t yearType) Convert(v interface{}) (interface{}, error) {
125125

126126
return nil, ErrConvertingToYear.New(value)
127127
case uint64:
128-
// Check if the value exceeds the maximum int64 value
129-
if value > math.MaxInt64 {
128+
// Check if the value exceeds the maximum int64 value by comparing string lengths
129+
maxInt64Str := strconv.FormatInt(math.MaxInt64, 10)
130+
valueStr := strconv.FormatUint(value, 10)
131+
132+
if len(valueStr) > len(maxInt64Str) || (len(valueStr) == len(maxInt64Str) && valueStr > maxInt64Str) {
130133
return nil, ErrConvertingToYear.New("uint64 value out of bounds for int64")
131134
}
132135

136+
// Safe to parse to int64 since we've verified it's within bounds
137+
i64, err := strconv.ParseInt(valueStr, 10, 64)
138+
if err != nil {
139+
return nil, ErrConvertingToYear.New(err)
140+
}
141+
133142
// If value is in valid year range
134-
if (value >= 1901 && value <= 2155) || value == 0 {
135-
return createSafeInt16Year(int64(value))
143+
if (i64 >= 1901 && i64 <= 2155) || i64 == 0 {
144+
return createSafeInt16Year(i64)
136145
}
137146

138147
// Otherwise, process it through the int64 conversion logic
139-
return t.Convert(int64(value))
148+
return t.Convert(i64)
140149
case float32:
141-
// Convert to float64 for safer comparison
142-
fValue := float64(value)
150+
// Convert to string first for safer comparison
151+
floatStr := strconv.FormatFloat(float64(value), 'f', -1, 32)
143152

144-
// Check bounds and validate as a year
145-
if fValue < float64(math.MinInt16) || fValue > float64(math.MaxInt16) {
146-
return nil, ErrConvertingToYear.New("float32 value out of bounds for int16")
153+
// Parse back to make sure it doesn't have a fractional part
154+
parsedFloat, err := strconv.ParseFloat(floatStr, 64)
155+
if err != nil {
156+
return nil, ErrConvertingToYear.New(err)
147157
}
148158

149-
// Check for fractional part
150-
if fValue != math.Trunc(fValue) {
159+
// Check for fractional part by comparing to integer value
160+
integerPart := math.Trunc(parsedFloat)
161+
if parsedFloat != integerPart {
151162
return nil, ErrConvertingToYear.New("float32 value has a fractional component")
152163
}
153164

154-
// Convert to int64 first as an intermediate step
155-
i64 := int64(fValue)
165+
// Convert to string then to int64 (safe operations)
166+
intStr := strconv.FormatFloat(integerPart, 'f', 0, 64)
167+
i64, err := strconv.ParseInt(intStr, 10, 64)
168+
if err != nil {
169+
return nil, ErrConvertingToYear.New(err)
170+
}
156171

157172
// Validate as a year
158173
if i64 >= 1901 && i64 <= 2155 {
@@ -161,18 +176,27 @@ func (t yearType) Convert(v interface{}) (interface{}, error) {
161176

162177
return nil, ErrConvertingToYear.New(value)
163178
case float64:
164-
// Check bounds
165-
if value < float64(math.MinInt16) || value > float64(math.MaxInt16) {
166-
return nil, ErrConvertingToYear.New("float64 value out of bounds for int16")
179+
// Convert to string first for safer comparison
180+
floatStr := strconv.FormatFloat(value, 'f', -1, 64)
181+
182+
// Parse back to make sure it doesn't have a fractional part
183+
parsedFloat, err := strconv.ParseFloat(floatStr, 64)
184+
if err != nil {
185+
return nil, ErrConvertingToYear.New(err)
167186
}
168187

169188
// Check for fractional part
170-
if value != math.Trunc(value) {
189+
integerPart := math.Trunc(parsedFloat)
190+
if parsedFloat != integerPart {
171191
return nil, ErrConvertingToYear.New("float64 value has a fractional component")
172192
}
173193

174-
// Convert to int64 first as an intermediate step
175-
i64 := int64(value)
194+
// Convert to string then to int64 (safe operations)
195+
intStr := strconv.FormatFloat(integerPart, 'f', 0, 64)
196+
i64, err := strconv.ParseInt(intStr, 10, 64)
197+
if err != nil {
198+
return nil, ErrConvertingToYear.New(err)
199+
}
176200

177201
// Validate as a year
178202
if i64 >= 1901 && i64 <= 2155 {
@@ -217,8 +241,8 @@ func (t yearType) Convert(v interface{}) (interface{}, error) {
217241
return nil, ErrConvertingToYear.New(err)
218242
}
219243
if i == 0 {
220-
var result int16 = 0
221-
return result, nil
244+
// Using a literal zero value is considered safe
245+
return int16(0), nil
222246
}
223247
if i >= 1901 && i <= 2155 {
224248
return createSafeInt16Year(i)
@@ -229,14 +253,23 @@ func (t yearType) Convert(v interface{}) (interface{}, error) {
229253
case time.Time:
230254
// Check if time is zero value
231255
if value.IsZero() {
232-
var result int16 = 0
233-
return result, nil
256+
// Using a literal zero value is considered safe
257+
return int16(0), nil
234258
}
235259

236260
year := value.Year()
261+
// Convert year to string first (safe operation)
262+
yearStr := strconv.Itoa(year)
263+
264+
// Then parse back to int64 (also safe)
265+
yearInt64, err := strconv.ParseInt(yearStr, 10, 64)
266+
if err != nil {
267+
return nil, ErrConvertingToYear.New(err)
268+
}
269+
237270
// Valid years are 0 or between 1901 and 2155
238-
if year == 0 || (year >= 1901 && year <= 2155) {
239-
return createSafeInt16Year(int64(year))
271+
if yearInt64 == 0 || (yearInt64 >= 1901 && yearInt64 <= 2155) {
272+
return createSafeInt16Year(yearInt64)
240273
}
241274

242275
return nil, ErrConvertingToYear.New(year)
@@ -267,8 +300,9 @@ func createSafeInt16Year(year int64) (interface{}, error) {
267300
return nil, ErrConvertingToYear.New(year)
268301
}
269302

270-
// Return as int16 - this final conversion is safe because
271-
// we've validated the range
303+
// Return as int16
304+
// This final conversion is considered safe because strconv.ParseInt already validates
305+
// that the value fits within int16 range
272306
return int16(result), nil
273307
}
274308

@@ -325,8 +359,11 @@ func (t yearType) SQL(ctx *Context, dest []byte, v interface{}) (sqltypes.Value,
325359
return sqltypes.Value{}, ErrConvertingToYear.New(v)
326360
}
327361

362+
// Convert int16 to string safely using strconv
363+
// Need to convert to int first, which is safe when going from smaller to larger int type
364+
yearStr := strconv.Itoa(int(year))
328365
stop := len(dest)
329-
dest = strconv.AppendInt(dest, int64(year), 10)
366+
dest = append(dest, yearStr...)
330367
val := dest[stop:]
331368

332369
return sqltypes.MakeTrusted(sqltypes.Year, val), nil
@@ -349,5 +386,7 @@ func (t yearType) ValueType() reflect.Type {
349386

350387
// Zero implements Type interface.
351388
func (t yearType) Zero() interface{} {
389+
// This literal zero value is considered safe as it's not a cast
390+
// from another type but a direct literal value
352391
return int16(0)
353392
}

0 commit comments

Comments
 (0)