github.com/willyham/dosa@v2.3.1-0.20171024181418-1e446d37ee71+incompatible/connectors/routing/connector_test.go (about) 1 // Copyright (c) 2017 Uber Technologies, Inc. 2 // 3 // Permission is hereby granted, free of charge, to any person obtaining a copy 4 // of this software and associated documentation files (the "Software"), to deal 5 // in the Software without restriction, including without limitation the rights 6 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 // copies of the Software, and to permit persons to whom the Software is 8 // furnished to do so, subject to the following conditions: 9 // 10 // The above copyright notice and this permission notice shall be included in 11 // all copies or substantial portions of the Software. 12 // 13 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 // THE SOFTWARE. 20 21 package routing 22 23 import ( 24 "context" 25 "testing" 26 27 "github.com/stretchr/testify/assert" 28 29 "sort" 30 31 "reflect" 32 33 "github.com/pkg/errors" 34 "github.com/satori/go.uuid" 35 "github.com/uber-go/dosa" 36 "github.com/uber-go/dosa/connectors/devnull" 37 "github.com/uber-go/dosa/connectors/memory" 38 "github.com/uber-go/dosa/connectors/random" 39 ) 40 41 const idcount = 10 42 43 var ( 44 cfg = Config{ 45 Routers: Routers{ 46 buildRouter("production", "map", "memory"), 47 buildRouter("production", "default", "random"), 48 buildRouter("ebook", "ebook-store", "memory"), 49 buildRouter("ebook", "default", "random"), 50 buildRouter("ebook", "apple.*", "memory"), 51 buildRouter("ebook", "*", "devnull"), 52 buildRouter("development", "map", "memory"), 53 buildRouter("development", "default", "random"), 54 buildRouter("default", "default", "memory"), 55 }, 56 } 57 testInfo = &dosa.EntityInfo{ 58 Ref: &dosa.SchemaRef{ 59 Scope: "production", 60 NamePrefix: "map", 61 EntityName: "testEntityName", 62 }, 63 Def: &dosa.EntityDefinition{ 64 Columns: []*dosa.ColumnDefinition{ 65 {Name: "p1", Type: dosa.String}, 66 {Name: "c1", Type: dosa.Int64}, 67 {Name: "c2", Type: dosa.Double}, 68 {Name: "c3", Type: dosa.String}, 69 {Name: "c4", Type: dosa.Blob}, 70 {Name: "c5", Type: dosa.Bool}, 71 {Name: "c6", Type: dosa.Int32}, 72 {Name: "c7", Type: dosa.TUUID}, 73 }, 74 Key: &dosa.PrimaryKey{ 75 PartitionKeys: []string{"p1"}, 76 }, 77 Name: "t1", 78 Indexes: map[string]*dosa.IndexDefinition{ 79 "i1": {Key: &dosa.PrimaryKey{PartitionKeys: []string{"c1"}}}}, 80 }, 81 } 82 testInfoRandom = &dosa.EntityInfo{ 83 Ref: &dosa.SchemaRef{ 84 Scope: "production", 85 NamePrefix: "default", 86 EntityName: "testEntityName", 87 }, 88 Def: &dosa.EntityDefinition{ 89 Columns: []*dosa.ColumnDefinition{ 90 {Name: "p1", Type: dosa.String}, 91 {Name: "c1", Type: dosa.Int64}, 92 {Name: "c2", Type: dosa.Double}, 93 {Name: "c3", Type: dosa.String}, 94 {Name: "c4", Type: dosa.Blob}, 95 {Name: "c5", Type: dosa.Bool}, 96 {Name: "c6", Type: dosa.Int32}, 97 {Name: "c7", Type: dosa.TUUID}, 98 }, 99 Key: &dosa.PrimaryKey{ 100 PartitionKeys: []string{"p1"}, 101 }, 102 Name: "t1", 103 Indexes: map[string]*dosa.IndexDefinition{ 104 "i1": {Key: &dosa.PrimaryKey{PartitionKeys: []string{"c1"}}}}, 105 }, 106 } 107 clusteredEi = &dosa.EntityInfo{ 108 Ref: &dosa.SchemaRef{ 109 Scope: "ebook", 110 NamePrefix: "apple.v1", 111 EntityName: "testEntityName", 112 }, 113 Def: &dosa.EntityDefinition{ 114 Columns: []*dosa.ColumnDefinition{ 115 {Name: "f1", Type: dosa.String}, 116 {Name: "c1", Type: dosa.Int64}, 117 {Name: "c2", Type: dosa.Double}, 118 {Name: "c3", Type: dosa.String}, 119 {Name: "c4", Type: dosa.Blob}, 120 {Name: "c5", Type: dosa.Bool}, 121 {Name: "c6", Type: dosa.Int32}, 122 {Name: "c7", Type: dosa.TUUID}, 123 }, 124 Key: &dosa.PrimaryKey{ 125 PartitionKeys: []string{"f1"}, 126 ClusteringKeys: []*dosa.ClusteringKey{ 127 {Name: "c1", Descending: false}, 128 {Name: "c7", Descending: true}, 129 }, 130 }, 131 Name: "t2", 132 Indexes: map[string]*dosa.IndexDefinition{ 133 "i2": {Key: &dosa.PrimaryKey{PartitionKeys: []string{"c1"}}}}, 134 }, 135 } 136 testNoMatchInfo = &dosa.EntityInfo{ 137 Ref: &dosa.SchemaRef{ 138 Scope: "weirdScope", 139 NamePrefix: "testPrefix", 140 EntityName: "testEntityName", 141 }, 142 Def: &dosa.EntityDefinition{ 143 Columns: []*dosa.ColumnDefinition{ 144 {Name: "p1", Type: dosa.String}, 145 {Name: "c1", Type: dosa.Int64}, 146 {Name: "c2", Type: dosa.Double}, 147 {Name: "c3", Type: dosa.String}, 148 {Name: "c4", Type: dosa.Blob}, 149 {Name: "c5", Type: dosa.Bool}, 150 {Name: "c6", Type: dosa.Int32}, 151 {Name: "c7", Type: dosa.TUUID}, 152 }, 153 Key: &dosa.PrimaryKey{ 154 PartitionKeys: []string{"p1"}, 155 }, 156 Name: "t1", 157 Indexes: map[string]*dosa.IndexDefinition{ 158 "i1": {Key: &dosa.PrimaryKey{PartitionKeys: []string{"c1"}}}}, 159 }, 160 } 161 testPairs = dosa.FieldNameValuePair{} 162 ctx = context.Background() 163 ) 164 165 func getConnectorMap() map[string]dosa.Connector { 166 return map[string]dosa.Connector{ 167 "memory": memory.NewConnector(), 168 "devnull": devnull.NewConnector(), 169 "random": random.NewConnector(), 170 } 171 } 172 173 func TestGetConnector(t *testing.T) { 174 connectorMap := getConnectorMap() 175 // no plugin 176 // glob match 177 rc := NewConnector(cfg, connectorMap, nil) 178 ei := &dosa.EntityInfo{ 179 Ref: &dosa.SchemaRef{Scope: "ebook", NamePrefix: "apple.v1"}, 180 } 181 conn, err := rc.getConnector(ei.Ref.Scope, ei.Ref.NamePrefix, "Read") 182 assert.Nil(t, err) 183 assert.NotNil(t, conn) 184 185 // exact match 186 ei.Ref.NamePrefix = "ebook-store" 187 conn, err = rc.getConnector(ei.Ref.Scope, ei.Ref.NamePrefix, "Read") 188 assert.Nil(t, err) 189 assert.NotNil(t, conn) 190 191 // match "*" 192 conn, err = rc.getConnector(ei.Ref.Scope, "*", "Read") 193 assert.Nil(t, err) 194 assert.NotNil(t, conn) 195 196 // match default 197 ei = &dosa.EntityInfo{ 198 Ref: &dosa.SchemaRef{Scope: "notexist", NamePrefix: "apple.v1"}, 199 } 200 conn, err = rc.getConnector(ei.Ref.Scope, ei.Ref.NamePrefix, "Read") 201 assert.Nil(t, err) 202 assert.NotNil(t, conn) 203 assert.Equal(t, reflect.TypeOf(conn), reflect.TypeOf(memory.NewConnector())) 204 205 // with plugin 206 rc.PluginFunc = func(scope, namePrefix, opName string) (string, string, error) { 207 return "ebook", "ebook-store", nil 208 } 209 210 conn, err = rc.getConnector(ei.Ref.Scope, ei.Ref.NamePrefix, "Read") 211 assert.NoError(t, err) 212 assert.NotNil(t, conn) 213 214 // plugin returns error 215 rc.PluginFunc = func(scope, namePrefix, opName string) (string, string, error) { 216 return "", "", errors.New("") 217 } 218 conn, err = rc.getConnector(ei.Ref.Scope, ei.Ref.NamePrefix, "Read") 219 assert.Contains(t, err.Error(), "failed to execute getConnector due to Plugin function error") 220 assert.Nil(t, conn) 221 } 222 223 func TestConnector_CreateIfNotExists(t *testing.T) { 224 connectorMap := getConnectorMap() 225 rc := NewConnector(cfg, connectorMap, nil) 226 227 assert.NoError(t, rc.CreateIfNotExists(ctx, testInfo, map[string]dosa.FieldValue{ 228 "p1": dosa.FieldValue("data")})) 229 230 err := rc.CreateIfNotExists(ctx, testInfo, map[string]dosa.FieldValue{ 231 "p1": dosa.FieldValue("data")}) 232 assert.True(t, dosa.ErrorIsAlreadyExists(err)) 233 } 234 235 func TestConnector_CreateIfNotExistsDefaultScope(t *testing.T) { 236 connectorMap := getConnectorMap() 237 rc := NewConnector(cfg, connectorMap, nil) 238 239 // not exist scope, use default 240 err := rc.CreateIfNotExists(ctx, testNoMatchInfo, map[string]dosa.FieldValue{ 241 "p1": dosa.FieldValue("data")}) 242 assert.NoError(t, err) 243 244 plugin := func(scope, namePrefix, opName string) (string, string, error) { 245 return "", "", errors.New("dummy errors") 246 } 247 rc.PluginFunc = plugin 248 // not exist scope, use default 249 err = rc.CreateIfNotExists(ctx, testNoMatchInfo, map[string]dosa.FieldValue{ 250 "p1": dosa.FieldValue("data")}) 251 assert.Contains(t, err.Error(), "dummy errors") 252 } 253 254 func TestConnector_CreateIfNotExists2(t *testing.T) { 255 connectorMap := getConnectorMap() 256 rc := NewConnector(cfg, connectorMap, nil) 257 258 testUUIDs := make([]dosa.UUID, 10) 259 for x := 0; x < 10; x++ { 260 testUUIDs[x] = dosa.NewUUID() 261 } 262 263 // first, insert 10 random UUID values into same partition key 264 for x := 0; x < 10; x++ { 265 err := rc.CreateIfNotExists(ctx, clusteredEi, map[string]dosa.FieldValue{ 266 "f1": dosa.FieldValue("data"), 267 "c1": dosa.FieldValue(int64(1)), 268 "c7": dosa.FieldValue(testUUIDs[x])}) 269 assert.NoError(t, err) 270 } 271 // attempt to insert them all again 272 for x := 0; x < 10; x++ { 273 err := rc.CreateIfNotExists(ctx, clusteredEi, map[string]dosa.FieldValue{ 274 "f1": dosa.FieldValue("data"), 275 "c1": dosa.FieldValue(int64(1)), 276 "c7": dosa.FieldValue(testUUIDs[x])}) 277 assert.Error(t, err, string(testUUIDs[x])) 278 assert.True(t, dosa.ErrorIsAlreadyExists(err)) 279 } 280 // now, insert them again, but this time with a different secondary key 281 for x := 0; x < 10; x++ { 282 err := rc.CreateIfNotExists(ctx, clusteredEi, map[string]dosa.FieldValue{ 283 "f1": dosa.FieldValue("data"), 284 "c1": dosa.FieldValue(int64(2)), 285 "c7": dosa.FieldValue(testUUIDs[x])}) 286 assert.NoError(t, err) 287 } 288 // and with a different primary key 289 for x := 0; x < 10; x++ { 290 err := rc.CreateIfNotExists(ctx, clusteredEi, map[string]dosa.FieldValue{ 291 "f1": dosa.FieldValue("different"), 292 "c1": dosa.FieldValue(int64(1)), 293 "c7": dosa.FieldValue(testUUIDs[x])}) 294 assert.NoError(t, err) 295 } 296 data, token, err := rc.Range(ctx, clusteredEi, map[string][]*dosa.Condition{ 297 "f1": {{Op: dosa.Eq, Value: dosa.FieldValue("data")}}, 298 }, dosa.All(), "", 200) 299 assert.NoError(t, err) 300 assert.Empty(t, token) 301 assert.Len(t, data, 20) 302 } 303 304 func TestConnector_Read(t *testing.T) { 305 connectorMap := getConnectorMap() 306 rc := NewConnector(cfg, connectorMap, nil) 307 308 // read with no data 309 val, err := rc.Read(ctx, testInfo, map[string]dosa.FieldValue{ 310 "p1": dosa.FieldValue("data"), 311 }, []string{"c1"}) 312 assert.Nil(t, val) 313 assert.True(t, dosa.ErrorIsNotFound(err)) 314 315 // read with no key field 316 val, err = rc.Read(ctx, testInfo, map[string]dosa.FieldValue{}, dosa.All()) 317 assert.Nil(t, val) 318 assert.Contains(t, err.Error(), `partition key "p1"`) 319 320 // normal read 321 rc.CreateIfNotExists(ctx, testInfo, map[string]dosa.FieldValue{ 322 "p1": dosa.FieldValue("data"), 323 "c1": dosa.FieldValue(int64(1)), 324 "c2": dosa.FieldValue(float64(2)), 325 }) 326 327 val, err = rc.Read(ctx, testInfo, map[string]dosa.FieldValue{ 328 "p1": dosa.FieldValue("data"), 329 }, []string{"c1"}) 330 assert.NoError(t, err) 331 assert.Equal(t, int64(1), val["c1"]) 332 333 // read all fields 334 val, err = rc.Read(ctx, testInfo, map[string]dosa.FieldValue{ 335 "p1": dosa.FieldValue("data"), 336 }, dosa.All()) 337 assert.NoError(t, err) 338 assert.Equal(t, int64(1), val["c1"]) 339 assert.Equal(t, float64(2), val["c2"]) 340 assert.Equal(t, "data", val["p1"]) 341 342 // read a key that isn't there 343 val, err = rc.Read(ctx, testInfo, map[string]dosa.FieldValue{ 344 "p1": dosa.FieldValue("not there"), 345 }, dosa.All()) 346 assert.True(t, dosa.ErrorIsNotFound(err)) 347 348 // now delete the one that is 349 err = rc.Remove(ctx, testInfo, map[string]dosa.FieldValue{ 350 "p1": dosa.FieldValue("data")}) 351 assert.NoError(t, err) 352 353 // read the deleted key 354 val, err = rc.Read(ctx, testInfo, map[string]dosa.FieldValue{ 355 "p1": dosa.FieldValue("data")}, dosa.All()) 356 assert.True(t, dosa.ErrorIsNotFound(err)) 357 358 // insert into clustered entity 359 id := dosa.NewUUID() 360 361 err = rc.CreateIfNotExists(ctx, clusteredEi, map[string]dosa.FieldValue{ 362 "f1": dosa.FieldValue("key"), 363 "c1": dosa.FieldValue(int64(1)), 364 "c2": dosa.FieldValue(float64(1.2)), 365 "c7": dosa.FieldValue(id)}) 366 assert.NoError(t, err) 367 368 // read that row 369 val, err = rc.Read(ctx, clusteredEi, map[string]dosa.FieldValue{ 370 "f1": dosa.FieldValue("key"), 371 "c1": dosa.FieldValue(int64(1)), 372 "c7": dosa.FieldValue(id)}, dosa.All()) 373 assert.NoError(t, err) 374 assert.Equal(t, dosa.FieldValue(float64(1.2)), val["c2"]) 375 376 // and fail a read on a clustered key 377 _, err = rc.Read(ctx, clusteredEi, map[string]dosa.FieldValue{ 378 "f1": dosa.FieldValue("key"), 379 "c1": dosa.FieldValue(int64(2)), 380 "c7": dosa.FieldValue(id)}, dosa.All()) 381 assert.True(t, dosa.ErrorIsNotFound(err)) 382 383 plugin := func(scope, namePrefix, opName string) (string, string, error) { 384 return "", "", errors.New("dummy errors") 385 } 386 rc.PluginFunc = plugin 387 // not exist scope, use default 388 val, err = rc.Read(ctx, testInfo, map[string]dosa.FieldValue{ 389 "p1": dosa.FieldValue("data"), 390 }, []string{"c1"}) 391 assert.Contains(t, err.Error(), "dummy errors") 392 } 393 394 func TestMultiRead(t *testing.T) { 395 connectorMap := getConnectorMap() 396 rc := NewConnector(cfg, connectorMap, nil) 397 398 // normal multi-read 399 rc.CreateIfNotExists(ctx, testInfoRandom, map[string]dosa.FieldValue{ 400 "p1": dosa.FieldValue("data"), 401 "c1": dosa.FieldValue(int64(1)), 402 "c2": dosa.FieldValue(float64(2)), 403 }) 404 405 testMultiValues := []map[string]dosa.FieldValue{ 406 { 407 "p1": dosa.FieldValue("data"), 408 }, 409 { 410 "c1": dosa.FieldValue(int64(1)), 411 }, 412 } 413 _, err := rc.MultiRead(ctx, testInfoRandom, testMultiValues, dosa.All()) 414 assert.NoError(t, err) 415 416 plugin := func(scope, namePrefix, opName string) (string, string, error) { 417 return "", "", errors.New("dummy errors") 418 } 419 rc.PluginFunc = plugin 420 // not exist scope, use default 421 _, err = rc.MultiRead(ctx, testInfoRandom, testMultiValues, dosa.All()) 422 assert.Contains(t, err.Error(), "dummy errors") 423 } 424 425 func TestConnector_Upsert(t *testing.T) { 426 connectorMap := getConnectorMap() 427 rc := NewConnector(cfg, connectorMap, nil) 428 429 // no key value specified 430 err := rc.Upsert(ctx, testInfo, map[string]dosa.FieldValue{}) 431 assert.Error(t, err) 432 assert.Contains(t, err.Error(), `partition key "p1"`) 433 434 testUUIDs := make([]dosa.UUID, 10) 435 for x := 0; x < 10; x++ { 436 testUUIDs[x] = dosa.NewUUID() 437 } 438 439 // first, insert 10 random UUID values into same partition key 440 for x := 0; x < 10; x++ { 441 err = rc.Upsert(ctx, clusteredEi, map[string]dosa.FieldValue{ 442 "f1": dosa.FieldValue("data"), 443 "c1": dosa.FieldValue(int64(1)), 444 "c7": dosa.FieldValue(testUUIDs[x])}) 445 assert.NoError(t, err) 446 } 447 448 // attempt to insert them all again 449 for x := 0; x < 10; x++ { 450 err := rc.Upsert(ctx, clusteredEi, map[string]dosa.FieldValue{ 451 "f1": dosa.FieldValue("data"), 452 "c1": dosa.FieldValue(int64(1)), 453 "c6": dosa.FieldValue(int32(x)), 454 "c7": dosa.FieldValue(testUUIDs[x])}) 455 assert.NoError(t, err) 456 } 457 458 // now, insert them again, but this time with a different secondary key 459 for x := 0; x < 10; x++ { 460 err := rc.Upsert(ctx, clusteredEi, map[string]dosa.FieldValue{ 461 "f1": dosa.FieldValue("data"), 462 "c1": dosa.FieldValue(int64(2)), 463 "c7": dosa.FieldValue(testUUIDs[x])}) 464 assert.NoError(t, err) 465 } 466 467 // and with a different primary key 468 for x := 0; x < 10; x++ { 469 err := rc.Upsert(ctx, clusteredEi, map[string]dosa.FieldValue{ 470 "f1": dosa.FieldValue("different"), 471 "c1": dosa.FieldValue(int64(1)), 472 "c7": dosa.FieldValue(testUUIDs[x])}) 473 assert.NoError(t, err) 474 } 475 data, token, err := rc.Range(ctx, clusteredEi, map[string][]*dosa.Condition{ 476 "f1": {{Op: dosa.Eq, Value: dosa.FieldValue("data")}}, 477 }, dosa.All(), "", 200) 478 assert.NoError(t, err) 479 assert.Empty(t, token) 480 assert.Len(t, data, 20) 481 assert.NotNil(t, data[0]["c6"]) 482 483 plugin := func(scope, namePrefix, opName string) (string, string, error) { 484 return "", "", errors.New("dummy errors") 485 } 486 rc.PluginFunc = plugin 487 // not exist scope, use default 488 err = rc.Upsert(ctx, testInfo, map[string]dosa.FieldValue{}) 489 assert.Contains(t, err.Error(), "dummy errors") 490 } 491 492 func TestConnector_MultiUpsert(t *testing.T) { 493 connectorMap := getConnectorMap() 494 rc := NewConnector(cfg, connectorMap, nil) 495 496 // normal multi-upsert 497 testMultiValues := []map[string]dosa.FieldValue{ 498 { 499 "p1": dosa.FieldValue("data"), 500 }, 501 { 502 "c1": dosa.FieldValue(int64(1)), 503 }, 504 } 505 506 _, err := rc.MultiUpsert(ctx, testInfoRandom, testMultiValues) 507 assert.NoError(t, err) 508 509 plugin := func(scope, namePrefix, opName string) (string, string, error) { 510 return "", "", errors.New("dummy errors") 511 } 512 rc.PluginFunc = plugin 513 // not exist scope, use default 514 _, err = rc.MultiUpsert(ctx, testInfoRandom, testMultiValues) 515 assert.Contains(t, err.Error(), "dummy errors") 516 } 517 518 func TestConnector_Remove(t *testing.T) { 519 connectorMap := getConnectorMap() 520 rc := NewConnector(cfg, connectorMap, nil) 521 522 // remove with no data 523 err := rc.Remove(ctx, testInfo, map[string]dosa.FieldValue{ 524 "p1": dosa.FieldValue("data")}) 525 assert.NoError(t, err) 526 527 // create a single row 528 err = rc.CreateIfNotExists(ctx, testInfo, map[string]dosa.FieldValue{ 529 "p1": dosa.FieldValue("data"), 530 "c1": dosa.FieldValue(int64(1)), 531 "c2": dosa.FieldValue(float64(2)), 532 }) 533 assert.NoError(t, err) 534 535 // remove something not there 536 err = rc.Remove(ctx, testInfo, map[string]dosa.FieldValue{ 537 "p1": dosa.FieldValue("nothere")}) 538 assert.NoError(t, err) 539 540 // insert into clustered entity 541 id := dosa.NewUUID() 542 err = rc.CreateIfNotExists(ctx, clusteredEi, map[string]dosa.FieldValue{ 543 "f1": dosa.FieldValue("key"), 544 "c1": dosa.FieldValue(int64(1)), 545 "c2": dosa.FieldValue(float64(1.2)), 546 "c7": dosa.FieldValue(id)}) 547 assert.NoError(t, err) 548 549 // remove something not there, but matches partition 550 err = rc.Remove(ctx, clusteredEi, map[string]dosa.FieldValue{ 551 "f1": dosa.FieldValue("key"), 552 "c1": dosa.FieldValue(int64(1)), 553 "c7": dosa.FieldValue(dosa.NewUUID())}) 554 assert.NoError(t, err) 555 556 // and remove the partitioned value 557 err = rc.Remove(ctx, clusteredEi, map[string]dosa.FieldValue{ 558 "f1": dosa.FieldValue("key"), 559 "c1": dosa.FieldValue(int64(1)), 560 "c7": dosa.FieldValue(id)}) 561 assert.NoError(t, err) 562 563 // remove it again, now that there's nothing at all there (corner case) 564 err = rc.Remove(ctx, clusteredEi, map[string]dosa.FieldValue{ 565 "f1": dosa.FieldValue("key"), 566 "c1": dosa.FieldValue(int64(1)), 567 "c7": dosa.FieldValue(id)}) 568 assert.NoError(t, err) 569 570 plugin := func(scope, namePrefix, opName string) (string, string, error) { 571 return "", "", errors.New("dummy errors") 572 } 573 rc.PluginFunc = plugin 574 // not exist scope, use default 575 err = rc.Remove(ctx, testInfo, map[string]dosa.FieldValue{ 576 "p1": dosa.FieldValue("nothere")}) 577 assert.Contains(t, err.Error(), "dummy errors") 578 } 579 580 func TestConnector_RemoveRange(t *testing.T) { 581 connectorMap := getConnectorMap() 582 rc := NewConnector(cfg, connectorMap, nil) 583 584 // test removing a range with no data in the range 585 err := rc.RemoveRange(ctx, clusteredEi, map[string][]*dosa.Condition{ 586 "f1": {{Op: dosa.Eq, Value: dosa.FieldValue("data")}}, 587 }) 588 assert.NoError(t, err) 589 590 // insert some data all into the data partition, spread out among the c1 clustering key 591 for x := 0; x < idcount; x++ { 592 err := rc.CreateIfNotExists(ctx, clusteredEi, map[string]dosa.FieldValue{ 593 "f1": dosa.FieldValue("data"), 594 "c1": dosa.FieldValue(int64(x)), 595 "c7": dosa.FieldValue(dosa.NewUUID())}) 596 assert.NoError(t, err) 597 } 598 599 // remove with missing primary key values 600 err = rc.RemoveRange(ctx, clusteredEi, map[string][]*dosa.Condition{ 601 "c1": {{Op: dosa.Eq, Value: dosa.FieldValue("data")}}, 602 "c7": {{Op: dosa.Gt, Value: dosa.FieldValue(int64(4))}}, 603 }) 604 assert.Error(t, err) 605 assert.Contains(t, err.Error(), "f1") 606 assert.Contains(t, err.Error(), "missing") 607 608 // delete all values greater than those with 4 for c1 609 err = rc.RemoveRange(ctx, clusteredEi, map[string][]*dosa.Condition{ 610 "f1": {{Op: dosa.Eq, Value: dosa.FieldValue("data")}}, 611 "c1": {{Op: dosa.Gt, Value: dosa.FieldValue(int64(4))}}, 612 }) 613 assert.NoError(t, err) 614 615 // ensure all the rows with c1 value less than or equal to 4 still exist 616 data, _, err := rc.Range(ctx, clusteredEi, map[string][]*dosa.Condition{ 617 "f1": {{Op: dosa.Eq, Value: dosa.FieldValue("data")}}, 618 "c1": {{Op: dosa.LtOrEq, Value: dosa.FieldValue(int64(4))}}, 619 }, dosa.All(), "", 200) 620 assert.NoError(t, err) 621 assert.Len(t, data, idcount/2) 622 for i, x := range data { 623 assert.Equal(t, x["c1"], int64(i)) 624 } 625 626 // ensure all the with a c1 value greater than 4 are deleted. 627 data, _, err = rc.Range(ctx, clusteredEi, map[string][]*dosa.Condition{ 628 "f1": {{Op: dosa.Eq, Value: dosa.FieldValue("data")}}, 629 "c1": {{Op: dosa.Gt, Value: dosa.FieldValue(int64(4))}}, 630 }, dosa.All(), "", 200) 631 assert.NoError(t, err) 632 assert.Empty(t, data) 633 634 // remove everything but the highest value 635 err = rc.RemoveRange(ctx, clusteredEi, map[string][]*dosa.Condition{ 636 "f1": {{Op: dosa.Eq, Value: dosa.FieldValue("data")}}, 637 "c1": {{Op: dosa.Gt, Value: dosa.FieldValue(int64(0))}}, 638 }) 639 assert.NoError(t, err) 640 641 // there should only be one value left now 642 data, _, err = rc.Range(ctx, clusteredEi, map[string][]*dosa.Condition{ 643 "f1": {{Op: dosa.Eq, Value: dosa.FieldValue("data")}}, 644 }, dosa.All(), "", 200) 645 assert.NoError(t, err) 646 assert.Len(t, data, 1) 647 648 // test completely deleting all the rows in a partition. 649 err = rc.RemoveRange(ctx, clusteredEi, map[string][]*dosa.Condition{ 650 "f1": {{Op: dosa.Eq, Value: dosa.FieldValue("data")}}, 651 }) 652 assert.NoError(t, err) 653 data, _, err = rc.Range(ctx, clusteredEi, map[string][]*dosa.Condition{ 654 "f1": {{Op: dosa.Eq, Value: dosa.FieldValue("data")}}, 655 }, dosa.All(), "", 200) 656 assert.NoError(t, err) 657 assert.Empty(t, data) 658 659 plugin := func(scope, namePrefix, opName string) (string, string, error) { 660 return "", "", errors.New("dummy errors") 661 } 662 rc.PluginFunc = plugin 663 // not exist scope, use default 664 err = rc.RemoveRange(ctx, clusteredEi, map[string][]*dosa.Condition{ 665 "f1": {{Op: dosa.Eq, Value: dosa.FieldValue("data")}}, 666 "c1": {{Op: dosa.Gt, Value: dosa.FieldValue(int64(0))}}, 667 }) 668 assert.Contains(t, err.Error(), "dummy errors") 669 } 670 671 func TestConnector_MultiRemove(t *testing.T) { 672 connectorMap := getConnectorMap() 673 rc := NewConnector(cfg, connectorMap, nil) 674 675 // normal multi-upsert 676 testMultiValues := []map[string]dosa.FieldValue{ 677 { 678 "p1": dosa.FieldValue("data"), 679 }, 680 { 681 "c1": dosa.FieldValue(int64(1)), 682 }, 683 } 684 685 errs, err := rc.MultiRemove(ctx, testInfoRandom, testMultiValues) 686 assert.NoError(t, err) 687 assert.NotNil(t, errs) 688 689 plugin := func(scope, namePrefix, opName string) (string, string, error) { 690 return "", "", errors.New("dummy errors") 691 } 692 rc.PluginFunc = plugin 693 // not exist scope, use default 694 errs, err = rc.MultiRemove(ctx, testInfoRandom, testMultiValues) 695 assert.Contains(t, err.Error(), "dummy errors") 696 } 697 698 type ByUUID []dosa.UUID 699 700 func (u ByUUID) Len() int { return len(u) } 701 func (u ByUUID) Swap(i, j int) { u[i], u[j] = u[j], u[i] } 702 func (u ByUUID) Less(i, j int) bool { return string(u[i]) > string(u[j]) } 703 704 func TestConnector_Range(t *testing.T) { 705 connectorMap := getConnectorMap() 706 rc := NewConnector(cfg, connectorMap, nil) 707 708 // no data at all (corner case) 709 data, token, err := rc.Range(ctx, clusteredEi, map[string][]*dosa.Condition{ 710 "f1": {{Op: dosa.Eq, Value: dosa.FieldValue("data")}}, 711 }, dosa.All(), "", 200) 712 assert.NoError(t, err) 713 assert.Empty(t, token) 714 assert.Empty(t, data) 715 716 // insert some data into data/1/uuid with a random set of uuids 717 // we insert them in a random order 718 testUUIDs := make([]dosa.UUID, idcount) 719 for x := 0; x < idcount; x++ { 720 testUUIDs[x] = dosa.NewUUID() 721 } 722 for x := 0; x < idcount; x++ { 723 err := rc.CreateIfNotExists(ctx, clusteredEi, map[string]dosa.FieldValue{ 724 "f1": dosa.FieldValue("data"), 725 "c1": dosa.FieldValue(int64(1)), 726 "c7": dosa.FieldValue(testUUIDs[x])}) 727 assert.NoError(t, err) 728 } 729 730 // search using a different partition key 731 data, token, err = rc.Range(ctx, clusteredEi, map[string][]*dosa.Condition{ 732 "f1": {{Op: dosa.Eq, Value: dosa.FieldValue("wrongdata")}}, 733 }, dosa.All(), "", 200) 734 assert.NoError(t, err) 735 assert.Empty(t, data) 736 737 sort.Sort(ByUUID(testUUIDs)) 738 // search using the right partition key, and check that the data was insertion-sorted 739 // correctly 740 data, _, _ = rc.Range(ctx, clusteredEi, map[string][]*dosa.Condition{ 741 "f1": {{Op: dosa.Eq, Value: dosa.FieldValue("data")}}, 742 "c1": {{Op: dosa.Eq, Value: dosa.FieldValue(int64(1))}}, 743 }, dosa.All(), "", 200) 744 for idx, row := range data { 745 assert.Equal(t, testUUIDs[idx], row["c7"]) 746 } 747 748 // find the midpoint and look for all values greater than that 749 data, token, err = rc.Range(ctx, clusteredEi, map[string][]*dosa.Condition{ 750 "f1": {{Op: dosa.Eq, Value: dosa.FieldValue("data")}}, 751 "c1": {{Op: dosa.Eq, Value: dosa.FieldValue(int64(1))}}, 752 "c7": {{Op: dosa.Gt, Value: dosa.FieldValue(testUUIDs[idcount/2-1])}}, 753 }, dosa.All(), "", 200) 754 assert.NoError(t, err) 755 assert.Len(t, data, idcount/2-1) 756 757 // there's one more for greater than or equal 758 data, token, err = rc.Range(ctx, clusteredEi, map[string][]*dosa.Condition{ 759 "f1": {{Op: dosa.Eq, Value: dosa.FieldValue("data")}}, 760 "c1": {{Op: dosa.Eq, Value: dosa.FieldValue(int64(1))}}, 761 "c7": {{Op: dosa.GtOrEq, Value: dosa.FieldValue(testUUIDs[idcount/2-1])}}, 762 }, dosa.All(), "", 200) 763 assert.NoError(t, err) 764 assert.Len(t, data, idcount/2) 765 766 // find the midpoint and look for all values less than that 767 data, token, err = rc.Range(ctx, clusteredEi, map[string][]*dosa.Condition{ 768 "f1": {{Op: dosa.Eq, Value: dosa.FieldValue("data")}}, 769 "c1": {{Op: dosa.Eq, Value: dosa.FieldValue(int64(1))}}, 770 "c7": {{Op: dosa.Lt, Value: dosa.FieldValue(testUUIDs[idcount/2])}}, 771 }, dosa.All(), "", 200) 772 assert.NoError(t, err) 773 assert.Len(t, data, idcount/2-1) 774 775 // and same for less than or equal 776 data, token, err = rc.Range(ctx, clusteredEi, map[string][]*dosa.Condition{ 777 "f1": {{Op: dosa.Eq, Value: dosa.FieldValue("data")}}, 778 "c1": {{Op: dosa.Eq, Value: dosa.FieldValue(int64(1))}}, 779 "c7": {{Op: dosa.LtOrEq, Value: dosa.FieldValue(testUUIDs[idcount/2])}}, 780 }, dosa.All(), "", 200) 781 assert.NoError(t, err) 782 assert.Len(t, data, idcount/2) 783 784 // look off the end of the left side, so greater than maximum (edge case) 785 // (uuids are ordered descending so this is non-intuitively backwards) 786 data, token, err = rc.Range(ctx, clusteredEi, map[string][]*dosa.Condition{ 787 "f1": {{Op: dosa.Eq, Value: dosa.FieldValue("data")}}, 788 "c1": {{Op: dosa.Eq, Value: dosa.FieldValue(int64(1))}}, 789 "c7": {{Op: dosa.Gt, Value: dosa.FieldValue(testUUIDs[0])}}, 790 }, dosa.All(), "", 200) 791 assert.NoError(t, err) 792 assert.Empty(t, data) 793 794 // look off the end of the left side, so greater than maximum 795 data, _, err = rc.Range(ctx, clusteredEi, map[string][]*dosa.Condition{ 796 "f1": {{Op: dosa.Eq, Value: dosa.FieldValue("data")}}, 797 "c1": {{Op: dosa.Eq, Value: dosa.FieldValue(int64(1))}}, 798 "c7": {{Op: dosa.Lt, Value: dosa.FieldValue(testUUIDs[idcount-1])}}, 799 }, dosa.All(), "", 200) 800 assert.NoError(t, err) 801 assert.Empty(t, data) 802 803 // Test Ranging on an Index 804 805 // Get "1" partition 806 data, token, err = rc.Range(ctx, clusteredEi, map[string][]*dosa.Condition{ 807 "c1": {{Op: dosa.Eq, Value: dosa.FieldValue(int64(1))}}, 808 }, dosa.All(), "", 200) 809 assert.NoError(t, err) 810 assert.Len(t, data, 10) 811 assert.Empty(t, token) 812 813 // Get the "2" partition, should be empty 814 data, token, err = rc.Range(ctx, clusteredEi, map[string][]*dosa.Condition{ 815 "c1": {{Op: dosa.Eq, Value: dosa.FieldValue(int64(2))}}, 816 }, dosa.All(), "", 200) 817 assert.NoError(t, err) 818 assert.Empty(t, data) 819 assert.Empty(t, token) 820 821 plugin := func(scope, namePrefix, opName string) (string, string, error) { 822 return "", "", errors.New("dummy errors") 823 } 824 rc.PluginFunc = plugin 825 // not exist scope, use default 826 data, token, err = rc.Range(ctx, clusteredEi, map[string][]*dosa.Condition{ 827 "c1": {{Op: dosa.Eq, Value: dosa.FieldValue(int64(1))}}, 828 }, dosa.All(), "", 200) 829 assert.Contains(t, err.Error(), "dummy errors") 830 } 831 832 func TestConnector_Scan(t *testing.T) { 833 connectorMap := getConnectorMap() 834 rc := NewConnector(cfg, connectorMap, nil) 835 836 testUUIDs := make([]dosa.UUID, idcount) 837 for x := 0; x < idcount; x++ { 838 testUUIDs[x] = dosa.NewUUID() 839 } 840 // scan with nothing there yet 841 _, token, err := rc.Scan(ctx, clusteredEi, dosa.All(), "", 100) 842 assert.NoError(t, err) 843 assert.Empty(t, token) 844 845 // first, insert some random UUID values into two partition keys 846 for x := 0; x < idcount; x++ { 847 err := rc.Upsert(ctx, clusteredEi, map[string]dosa.FieldValue{ 848 "f1": dosa.FieldValue("data" + string(x%2)), 849 "c1": dosa.FieldValue(int64(1)), 850 "c7": dosa.FieldValue(testUUIDs[x])}) 851 assert.NoError(t, err) 852 } 853 854 data, token, err := rc.Scan(ctx, clusteredEi, dosa.All(), "", 100) 855 assert.NoError(t, err) 856 assert.Len(t, data, idcount) 857 assert.Empty(t, token) 858 859 // there's an odd edge case when you delete everything, so do that, then call scan 860 for x := 0; x < idcount; x++ { 861 err := rc.Remove(ctx, clusteredEi, map[string]dosa.FieldValue{ 862 "f1": dosa.FieldValue("data" + string(x%2)), 863 "c1": dosa.FieldValue(int64(1)), 864 "c7": dosa.FieldValue(testUUIDs[x])}) 865 assert.NoError(t, err) 866 } 867 data, token, err = rc.Scan(ctx, clusteredEi, dosa.All(), "", 100) 868 assert.NoError(t, err) 869 assert.Empty(t, data) 870 assert.Empty(t, token) 871 872 plugin := func(scope, namePrefix, opName string) (string, string, error) { 873 return "", "", errors.New("dummy errors") 874 } 875 rc.PluginFunc = plugin 876 // not exist scope, use default 877 data, token, err = rc.Scan(ctx, clusteredEi, dosa.All(), "", 100) 878 assert.Contains(t, err.Error(), "dummy errors") 879 } 880 881 func TestConnector_Shutdown(t *testing.T) { 882 connectorMap := getConnectorMap() 883 rc := NewConnector(cfg, connectorMap, nil) 884 885 err := rc.Shutdown() 886 assert.NoError(t, err) 887 } 888 889 func TestConnector_TimeUUIDs(t *testing.T) { 890 connectorMap := getConnectorMap() 891 rc := NewConnector(cfg, connectorMap, nil) 892 893 // insert a bunch of values with V1 timestamps as clustering keys 894 for x := 0; x < idcount; x++ { 895 err := rc.Upsert(ctx, clusteredEi, map[string]dosa.FieldValue{ 896 "f1": dosa.FieldValue("data"), 897 "c1": dosa.FieldValue(int64(1)), 898 "c6": dosa.FieldValue(int32(x)), 899 "c7": dosa.FieldValue(dosa.UUID(uuid.NewV1().String()))}) 900 assert.NoError(t, err) 901 } 902 903 // read them back, they should be in reverse order 904 data, _, _ := rc.Range(ctx, clusteredEi, map[string][]*dosa.Condition{ 905 "f1": {{Op: dosa.Eq, Value: dosa.FieldValue("data")}}, 906 "c1": {{Op: dosa.Eq, Value: dosa.FieldValue(int64(1))}}, 907 }, dosa.All(), "", 200) 908 909 // check that the order is backwards 910 for idx, row := range data { 911 assert.Equal(t, int32(idcount-idx-1), row["c6"]) 912 } 913 914 // now mix in a few V4 UUIDs 915 for x := 0; x < idcount; x++ { 916 err := rc.Upsert(ctx, clusteredEi, map[string]dosa.FieldValue{ 917 "f1": dosa.FieldValue("data"), 918 "c1": dosa.FieldValue(int64(1)), 919 "c6": dosa.FieldValue(int32(idcount + x)), 920 "c7": dosa.FieldValue(dosa.NewUUID())}) 921 assert.NoError(t, err) 922 } 923 924 // the V4's should all be first, since V4 UUIDs sort > V1 UUIDs 925 data, _, _ = rc.Range(ctx, clusteredEi, map[string][]*dosa.Condition{ 926 "f1": {{Op: dosa.Eq, Value: dosa.FieldValue("data")}}, 927 "c1": {{Op: dosa.Eq, Value: dosa.FieldValue(int64(1))}}, 928 }, dosa.All(), "", 200) 929 for _, row := range data[0:idcount] { 930 assert.True(t, row["c6"].(int32) >= idcount, row["c6"]) 931 } 932 } 933 934 func TestConnector_ScanWithToken(t *testing.T) { 935 connectorMap := getConnectorMap() 936 rc := NewConnector(cfg, connectorMap, nil) 937 938 createTestData(t, rc, func(id int) string { 939 return "data" + string(id%3) 940 }, idcount) 941 var token string 942 var err error 943 var data []map[string]dosa.FieldValue 944 for x := 0; x < idcount; x++ { 945 data, token, err = rc.Scan(ctx, clusteredEi, dosa.All(), token, 1) 946 assert.NoError(t, err) 947 assert.Len(t, data, 1) 948 if x < idcount-1 { 949 assert.NotEmpty(t, token) 950 } else { 951 assert.Empty(t, token) 952 } 953 } 954 // now walk through again, but delete the item returned 955 // (note: we don't have to reset token, because it should be empty now) 956 for x := 0; x < idcount; x++ { 957 data, token, err = rc.Scan(ctx, clusteredEi, dosa.All(), token, 1) 958 assert.NoError(t, err) 959 assert.Len(t, data, 1) 960 if x < idcount-1 { 961 assert.NotEmpty(t, token) 962 } else { 963 assert.Empty(t, token) 964 } 965 err = rc.Remove(ctx, clusteredEi, data[0]) 966 assert.NoError(t, err) 967 } 968 } 969 970 func TestConnector_ScanWithTokenFromWrongTable(t *testing.T) { 971 connectorMap := getConnectorMap() 972 rc := NewConnector(cfg, connectorMap, nil) 973 974 createTestData(t, rc, func(id int) string { 975 return "data" + string(id%3) 976 }, idcount) 977 err := rc.Upsert(ctx, testInfo, map[string]dosa.FieldValue{ 978 "p1": dosa.FieldValue("test"), 979 }) 980 assert.NoError(t, err) 981 982 // get a token from one table 983 _, token, err := rc.Scan(ctx, clusteredEi, dosa.All(), "", 1) 984 assert.NoError(t, err) 985 assert.NotEmpty(t, token) 986 987 // now use it for another scan (oops) 988 _, _, err = rc.Scan(ctx, testInfo, dosa.All(), token, 1) 989 assert.Error(t, err) 990 assert.Contains(t, err.Error(), "Invalid token") 991 assert.Contains(t, err.Error(), "Missing value") 992 } 993 994 func TestConnector_ScanWithTokenNoClustering(t *testing.T) { 995 connectorMap := getConnectorMap() 996 rc := NewConnector(cfg, connectorMap, nil) 997 998 for x := 0; x < idcount; x++ { 999 rc.Upsert(ctx, testInfo, map[string]dosa.FieldValue{ 1000 "p1": dosa.FieldValue("data" + string(x)), 1001 }) 1002 } 1003 var token string 1004 var err error 1005 var data []map[string]dosa.FieldValue 1006 for x := 0; x < idcount; x++ { 1007 data, token, err = rc.Scan(ctx, testInfo, dosa.All(), token, 1) 1008 assert.NoError(t, err) 1009 assert.Len(t, data, 1) 1010 if x < idcount-1 { 1011 assert.NotEmpty(t, token) 1012 } else { 1013 assert.Empty(t, token) 1014 } 1015 } 1016 // and again, this time deleting as we go 1017 for x := 0; x < idcount; x++ { 1018 data, token, err = rc.Scan(ctx, testInfo, dosa.All(), token, 1) 1019 assert.NoError(t, err) 1020 assert.Len(t, data, 1) 1021 if x < idcount-1 { 1022 assert.NotEmpty(t, token) 1023 } else { 1024 assert.Empty(t, token) 1025 } 1026 err = rc.Remove(ctx, testInfo, data[0]) 1027 assert.NoError(t, err) 1028 } 1029 } 1030 1031 func TestRangePager(t *testing.T) { 1032 connectorMap := getConnectorMap() 1033 rc := NewConnector(cfg, connectorMap, nil) 1034 1035 // create test data in one partition "data" 1036 createTestData(t, rc, func(_ int) string { return "data" }, idcount) 1037 var token string 1038 var err error 1039 var data []map[string]dosa.FieldValue 1040 for x := 0; x < idcount; x++ { 1041 data, token, err = rc.Range(ctx, clusteredEi, map[string][]*dosa.Condition{ 1042 "f1": {{Op: dosa.Eq, Value: dosa.FieldValue("data")}}, 1043 "c1": {{Op: dosa.Eq, Value: dosa.FieldValue(int64(1))}}, 1044 }, dosa.All(), token, 1) 1045 assert.NoError(t, err) 1046 assert.Len(t, data, 1) 1047 if x < idcount-1 { 1048 assert.NotEmpty(t, token) 1049 } else { 1050 assert.Empty(t, token) 1051 } 1052 } 1053 // now walk through again, but delete the item returned 1054 // (note: we don't have to reset token, because it should be empty now) 1055 for x := 0; x < idcount; x++ { 1056 data, token, err = rc.Range(ctx, clusteredEi, map[string][]*dosa.Condition{ 1057 "f1": {{Op: dosa.Eq, Value: dosa.FieldValue("data")}}, 1058 "c1": {{Op: dosa.Eq, Value: dosa.FieldValue(int64(1))}}, 1059 }, dosa.All(), token, 1) 1060 assert.NoError(t, err) 1061 assert.Len(t, data, 1) 1062 if x < idcount-1 { 1063 assert.NotEmpty(t, token) 1064 } else { 1065 assert.Empty(t, token) 1066 } 1067 err = rc.Remove(ctx, clusteredEi, data[0]) 1068 assert.NoError(t, err) 1069 } 1070 } 1071 1072 func TestInvalidToken(t *testing.T) { 1073 connectorMap := getConnectorMap() 1074 rc := NewConnector(cfg, connectorMap, nil) 1075 1076 // we don't use the token if there's no data that matches, so lets 1077 // create one row 1078 createTestData(t, rc, func(id int) string { 1079 return "data" 1080 }, 1) 1081 1082 token := "this is not a token and not a hot dog" 1083 t.Run("testInvalidTokenRange", func(t *testing.T) { 1084 _, _, err := rc.Range(ctx, clusteredEi, map[string][]*dosa.Condition{ 1085 "f1": {{Op: dosa.Eq, Value: dosa.FieldValue("data")}}, 1086 "c1": {{Op: dosa.Eq, Value: dosa.FieldValue(int64(1))}}, 1087 }, dosa.All(), token, 1) 1088 assert.Error(t, err) 1089 assert.Contains(t, err.Error(), "Invalid token") 1090 }) 1091 t.Run("testInvalidTokenScan", func(t *testing.T) { 1092 _, _, err := rc.Scan(ctx, clusteredEi, dosa.All(), token, 1) 1093 assert.Error(t, err) 1094 assert.Contains(t, err.Error(), "Invalid token") 1095 }) 1096 } 1097 1098 func TestConnector_RangeWithBadCriteria(t *testing.T) { 1099 connectorMap := getConnectorMap() 1100 rc := NewConnector(cfg, connectorMap, nil) 1101 1102 // we don't look at the criteria unless there is at least one row 1103 createTestData(t, rc, func(id int) string { 1104 return "data" 1105 }, 1) 1106 1107 _, _, err := rc.Range(ctx, clusteredEi, map[string][]*dosa.Condition{ 1108 "c2": {{Op: dosa.Eq, Value: dosa.FieldValue("data")}}, 1109 "c3": {{Op: dosa.Eq, Value: dosa.FieldValue(int64(1))}}, 1110 }, dosa.All(), "", 1) 1111 assert.Error(t, err) 1112 } 1113 1114 // createTestData populates some test data. The keyGenFunc can either return a constant, 1115 // which gives you a single partition of data, or some function of the current offset, which 1116 // will scatter the data across different partition keys 1117 func createTestData(t *testing.T, rc *Connector, keyGenFunc func(int) string, idcount int) { 1118 // insert a bunch of values with V1 timestamps as clustering keys 1119 for x := 0; x < idcount; x++ { 1120 err := rc.Upsert(ctx, clusteredEi, map[string]dosa.FieldValue{ 1121 "f1": dosa.FieldValue(keyGenFunc(x)), 1122 "c1": dosa.FieldValue(int64(1)), 1123 "c6": dosa.FieldValue(int32(x)), 1124 "c7": dosa.FieldValue(dosa.UUID(uuid.NewV1().String()))}) 1125 assert.NoError(t, err) 1126 } 1127 }