github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/sql/sqlbase/roundtrip_format_test.go (about) 1 // Copyright 2020 The Cockroach Authors. 2 // 3 // Use of this software is governed by the Business Source License 4 // included in the file licenses/BSL.txt. 5 // 6 // As of the Change Date specified in that file, in accordance with 7 // the Business Source License, use of this software will be governed 8 // by the Apache License, Version 2.0, included in the file 9 // licenses/APL.txt. 10 11 package sqlbase 12 13 import ( 14 "fmt" 15 "math" 16 "strconv" 17 "testing" 18 "time" 19 20 "github.com/cockroachdb/cockroach/pkg/sql/sem/tree" 21 "github.com/cockroachdb/cockroach/pkg/sql/types" 22 "github.com/cockroachdb/cockroach/pkg/util/leaktest" 23 "github.com/cockroachdb/cockroach/pkg/util/randutil" 24 "github.com/cockroachdb/cockroach/pkg/util/uuid" 25 ) 26 27 // TestParseDatumStringAs tests that datums are roundtrippable between 28 // printing with FmtExport and ParseDatumStringAs, but with random datums. 29 // This test lives in sqlbase to avoid dependency cycles when trying to move 30 // RandDatumWithNullChance into tree. 31 func TestRandParseDatumStringAs(t *testing.T) { 32 defer leaktest.AfterTest(t)() 33 tests := append([]*types.T{ 34 types.MakeTimestamp(0), 35 types.MakeTimestamp(3), 36 types.MakeTimestamp(6), 37 types.MakeTimestampTZ(0), 38 types.MakeTimestampTZ(3), 39 types.MakeTimestampTZ(6), 40 types.MakeTime(0), 41 types.MakeTime(3), 42 types.MakeTime(6), 43 types.MakeTimeTZ(0), 44 types.MakeTimeTZ(3), 45 types.MakeTimeTZ(6), 46 types.MakeCollatedString(types.String, "en"), 47 types.MakeCollatedString(types.String, "de"), 48 }, 49 types.Scalar...) 50 for _, ty := range types.Scalar { 51 if ty != types.Jsonb { 52 tests = append(tests, types.MakeArray(ty)) 53 } 54 } 55 evalCtx := tree.NewTestingEvalContext(nil) 56 rng, _ := randutil.NewPseudoRand() 57 for _, typ := range tests { 58 const testsForTyp = 100 59 t.Run(typ.String(), func(t *testing.T) { 60 for i := 0; i < testsForTyp; i++ { 61 datum := RandDatumWithNullChance(rng, typ, 0) 62 ds := tree.AsStringWithFlags(datum, tree.FmtExport) 63 64 // Because of how RandDatumWithNullChanceWorks, we might 65 // get an interesting datum for a time related type that 66 // doesn't have the precision that we requested. In these 67 // cases, manually correct the type ourselves. 68 var err error 69 switch d := datum.(type) { 70 case *tree.DTimestampTZ: 71 roundTo := tree.TimeFamilyPrecisionToRoundDuration(typ.Precision()) 72 // We can't round the max time, as it exceeds bounds. 73 if roundTo > time.Microsecond && d.Time.Round(roundTo).Equal(tree.MaxSupportedTime.Round(roundTo)) { 74 continue 75 } 76 datum, err = d.Round(roundTo) 77 case *tree.DTimestamp: 78 roundTo := tree.TimeFamilyPrecisionToRoundDuration(typ.Precision()) 79 // We can't round the max time, as it exceeds bounds. 80 if roundTo > time.Microsecond && d.Time.Round(roundTo).Equal(tree.MaxSupportedTime.Round(roundTo)) { 81 continue 82 } 83 datum, err = d.Round(roundTo) 84 case *tree.DTime: 85 datum = d.Round(tree.TimeFamilyPrecisionToRoundDuration(typ.Precision())) 86 case *tree.DTimeTZ: 87 datum = d.Round(tree.TimeFamilyPrecisionToRoundDuration(typ.Precision())) 88 } 89 90 if err != nil { 91 t.Fatal(ds, err) 92 } 93 94 parsed, err := ParseDatumStringAs(typ, ds, evalCtx) 95 if err != nil { 96 t.Fatal(ds, err) 97 } 98 if parsed.Compare(evalCtx, datum) != 0 { 99 t.Fatal(ds, "expected", datum, "found", parsed) 100 } 101 } 102 }) 103 } 104 } 105 106 // TestParseDatumStringAs tests that datums are roundtrippable between 107 // printing with FmtExport and ParseDatumStringAs. 108 func TestParseDatumStringAs(t *testing.T) { 109 defer leaktest.AfterTest(t)() 110 tests := map[*types.T][]string{ 111 types.Bool: { 112 "true", 113 "false", 114 }, 115 types.Bytes: { 116 `\x`, 117 `\x00`, 118 `\xff`, 119 `\xffff`, 120 fmt.Sprintf(`\x%x`, "abc"), 121 }, 122 types.Date: { 123 "2001-01-01", 124 }, 125 types.Decimal: { 126 "0.0", 127 "1.0", 128 "-1.0", 129 strconv.FormatFloat(math.MaxFloat64, 'G', -1, 64), 130 strconv.FormatFloat(math.SmallestNonzeroFloat64, 'G', -1, 64), 131 strconv.FormatFloat(-math.MaxFloat64, 'G', -1, 64), 132 strconv.FormatFloat(-math.SmallestNonzeroFloat64, 'G', -1, 64), 133 "1E+1000", 134 "1E-1000", 135 "Infinity", 136 "-Infinity", 137 "NaN", 138 }, 139 types.IntArray: { 140 "ARRAY[]", 141 "ARRAY[1, 2]", 142 }, 143 types.StringArray: { 144 `ARRAY[NULL, 'NULL']`, 145 `ARRAY['hello', 'there']`, 146 `ARRAY['hel,lo']`, 147 }, 148 types.Float: { 149 "0.0", 150 "-0.0", 151 "1.0", 152 "-1.0", 153 strconv.FormatFloat(math.MaxFloat64, 'g', -1, 64), 154 strconv.FormatFloat(math.SmallestNonzeroFloat64, 'g', -1, 64), 155 strconv.FormatFloat(-math.MaxFloat64, 'g', -1, 64), 156 strconv.FormatFloat(-math.SmallestNonzeroFloat64, 'g', -1, 64), 157 "+Inf", 158 "-Inf", 159 "NaN", 160 }, 161 types.INet: { 162 "127.0.0.1", 163 }, 164 types.Int: { 165 "1", 166 "0", 167 "-1", 168 strconv.Itoa(math.MaxInt64), 169 strconv.Itoa(math.MinInt64), 170 }, 171 types.Interval: { 172 "01:00:00", 173 "-00:01:00", 174 "2 years 3 mons", 175 }, 176 types.MakeInterval(types.IntervalTypeMetadata{}): { 177 "01:02:03", 178 "02:03:04", 179 "-00:01:00", 180 "2 years 3 mons", 181 }, 182 types.MakeInterval(types.IntervalTypeMetadata{Precision: 3, PrecisionIsSet: true}): { 183 "01:02:03", 184 "02:03:04.123", 185 }, 186 types.MakeInterval(types.IntervalTypeMetadata{Precision: 6, PrecisionIsSet: true}): { 187 "01:02:03", 188 "02:03:04.123456", 189 }, 190 types.Jsonb: { 191 "{}", 192 "[]", 193 "null", 194 "1", 195 "1.0", 196 `""`, 197 `"abc"`, 198 `"ab\u0000c"`, 199 `"ab\u0001c"`, 200 `"ab⚣ cd"`, 201 }, 202 types.String: { 203 "", 204 "abc", 205 "abc\x00", 206 "ab⚣ cd", 207 }, 208 types.Geography: { 209 "0101000020E6100000000000000000F03F000000000000F03F", 210 }, 211 types.Geometry: { 212 "0101000000000000000000F03F000000000000F03F", 213 }, 214 types.Timestamp: { 215 "2001-01-01 01:02:03+00:00", 216 "2001-01-01 02:03:04.123456+00:00", 217 }, 218 types.MakeTimestamp(0): { 219 "2001-01-01 01:02:03+00:00", 220 "2001-01-01 02:03:04+00:00", 221 }, 222 types.MakeTimestamp(3): { 223 "2001-01-01 01:02:03+00:00", 224 "2001-01-01 02:03:04.123+00:00", 225 }, 226 types.MakeTimestamp(6): { 227 "2001-01-01 01:02:03+00:00", 228 "2001-01-01 02:03:04.123456+00:00", 229 }, 230 types.TimestampTZ: { 231 "2001-01-01 01:02:03+00:00", 232 "2001-01-01 02:03:04.123456+00:00", 233 }, 234 types.MakeTimestampTZ(0): { 235 "2001-01-01 01:02:03+00:00", 236 "2001-01-01 02:03:04+00:00", 237 }, 238 types.MakeTimestampTZ(3): { 239 "2001-01-01 01:02:03+00:00", 240 "2001-01-01 02:03:04.123+00:00", 241 }, 242 types.MakeTimestampTZ(6): { 243 "2001-01-01 01:02:03+00:00", 244 "2001-01-01 02:03:04.123456+00:00", 245 }, 246 types.Time: { 247 "01:02:03", 248 "02:03:04.123456", 249 }, 250 types.MakeTime(0): { 251 "01:02:03", 252 "02:03:04", 253 }, 254 types.MakeTime(3): { 255 "01:02:03", 256 "02:03:04.123", 257 }, 258 types.MakeTime(6): { 259 "01:02:03", 260 "02:03:04.123456", 261 }, 262 types.TimeTZ: { 263 "01:02:03+00:00:00", 264 "01:02:03+11:00:00", 265 "01:02:03+11:00:00", 266 "01:02:03-11:00:00", 267 "02:03:04.123456+11:00:00", 268 }, 269 types.MakeTimeTZ(0): { 270 "01:02:03+03:30:00", 271 }, 272 types.MakeTimeTZ(3): { 273 "01:02:03+03:30:00", 274 "02:03:04.123+03:30:00", 275 }, 276 types.MakeTimeTZ(6): { 277 "01:02:03+03:30:00", 278 "02:03:04.123456+03:30:00", 279 }, 280 types.Uuid: { 281 uuid.MakeV4().String(), 282 }, 283 } 284 evalCtx := tree.NewTestingEvalContext(nil) 285 for typ, exprs := range tests { 286 t.Run(typ.String(), func(t *testing.T) { 287 for _, s := range exprs { 288 t.Run(fmt.Sprintf("%q", s), func(t *testing.T) { 289 d, err := ParseDatumStringAs(typ, s, evalCtx) 290 if err != nil { 291 t.Fatal(err) 292 } 293 if d.ResolvedType().Family() != typ.Family() { 294 t.Fatalf("unexpected type: %s", d.ResolvedType()) 295 } 296 ds := tree.AsStringWithFlags(d, tree.FmtExport) 297 parsed, err := ParseDatumStringAs(typ, ds, evalCtx) 298 if err != nil { 299 t.Fatal(err) 300 } 301 if parsed.Compare(evalCtx, d) != 0 { 302 t.Fatal("expected", d, "found", parsed) 303 } 304 }) 305 } 306 }) 307 } 308 }