github.com/instana/go-sensor@v1.62.2-0.20240520081010-4919868049e1/instrumentation_sql_test.go (about) 1 // (c) Copyright IBM Corp. 2021 2 // (c) Copyright Instana Inc. 2020 3 4 package instana_test 5 6 import ( 7 "context" 8 "database/sql" 9 "database/sql/driver" 10 "errors" 11 "io" 12 "os" 13 "testing" 14 15 instana "github.com/instana/go-sensor" 16 ot "github.com/opentracing/opentracing-go" 17 "github.com/opentracing/opentracing-go/ext" 18 "github.com/stretchr/testify/assert" 19 "github.com/stretchr/testify/require" 20 ) 21 22 func TestInstrumentSQLDriver(t *testing.T) { 23 recorder := instana.NewTestRecorder() 24 s := instana.NewSensorWithTracer(instana.NewTracerWithEverything(&instana.Options{ 25 Service: "go-sensor-test", 26 AgentClient: alwaysReadyClient{}, 27 }, recorder)) 28 29 defer instana.ShutdownSensor() 30 31 instana.InstrumentSQLDriver(s, "test_register_driver", sqlDriver{}) 32 assert.NotPanics(t, func() { 33 instana.InstrumentSQLDriver(s, "test_register_driver", sqlDriver{}) 34 }) 35 } 36 37 func BenchmarkSQLOpenAndExec(b *testing.B) { 38 recorder := instana.NewTestRecorder() 39 s := instana.NewSensorWithTracer(instana.NewTracerWithEverything(&instana.Options{ 40 Service: "go-sensor-test", 41 AgentClient: alwaysReadyClient{}, 42 }, recorder)) 43 defer instana.ShutdownSensor() 44 45 instana.InstrumentSQLDriver(s, "test_driver", sqlDriver{}) 46 47 b.ResetTimer() 48 b.ReportAllocs() 49 50 for i := 0; i < b.N; i++ { 51 db, err := instana.SQLOpen("test_driver", "connection string") 52 if err != nil { 53 b.Fatal(err) 54 } 55 _, err = db.Exec("TEST QUERY") 56 if err != nil { 57 b.Fatal(err) 58 } 59 } 60 } 61 62 func TestOpenSQLDB_WithoutParentSpan(t *testing.T) { 63 64 os.Setenv("INSTANA_ALLOW_ROOT_EXIT_SPAN", "1") 65 defer os.Unsetenv("INSTANA_ALLOW_ROOT_EXIT_SPAN") 66 67 recorder := instana.NewTestRecorder() 68 s := instana.NewSensorWithTracer(instana.NewTracerWithEverything(&instana.Options{ 69 Service: "go-sensor-test", 70 AgentClient: alwaysReadyClient{}, 71 }, recorder)) 72 defer instana.ShutdownSensor() 73 74 instana.InstrumentSQLDriver(s, "test_driver_without_parent_span", sqlDriver{}) 75 require.Contains(t, sql.Drivers(), "test_driver_without_parent_span_with_instana") 76 77 db, err := instana.SQLOpen("test_driver_without_parent_span", "connection string") 78 require.NoError(t, err) 79 80 t.Run("Exec", func(t *testing.T) { 81 res, err := db.Exec("TEST QUERY") 82 require.NoError(t, err) 83 84 lastID, err := res.LastInsertId() 85 require.NoError(t, err) 86 assert.Equal(t, int64(42), lastID) 87 88 spans := recorder.GetQueuedSpans() 89 require.Len(t, spans, 1) 90 91 span := spans[0] 92 assert.Equal(t, 0, span.Ec) 93 assert.EqualValues(t, instana.ExitSpanKind, span.Kind) 94 95 require.IsType(t, instana.SDKSpanData{}, span.Data) 96 data := span.Data.(instana.SDKSpanData) 97 98 assert.Equal(t, instana.SDKSpanTags{ 99 Name: "sdk.database", 100 Type: "exit", 101 Custom: map[string]interface{}{ 102 "tags": ot.Tags{ 103 "span.kind": ext.SpanKindRPCClientEnum, 104 "db.instance": "connection string", 105 "db.statement": "TEST QUERY", 106 "db.type": "sql", 107 "peer.address": "connection string", 108 }, 109 }, 110 }, data.Tags) 111 }) 112 113 t.Run("Query", func(t *testing.T) { 114 res, err := db.Query("TEST QUERY") 115 require.NoError(t, err) 116 117 cols, err := res.Columns() 118 require.NoError(t, err) 119 assert.Equal(t, []string{"col1", "col2"}, cols) 120 121 spans := recorder.GetQueuedSpans() 122 require.Len(t, spans, 1) 123 124 span := spans[0] 125 assert.Equal(t, 0, span.Ec) 126 assert.EqualValues(t, instana.ExitSpanKind, span.Kind) 127 128 require.IsType(t, instana.SDKSpanData{}, span.Data) 129 data := span.Data.(instana.SDKSpanData) 130 131 assert.Equal(t, instana.SDKSpanTags{ 132 Name: "sdk.database", 133 Type: "exit", 134 Custom: map[string]interface{}{ 135 "tags": ot.Tags{ 136 "span.kind": ext.SpanKindRPCClientEnum, 137 "db.instance": "connection string", 138 "db.statement": "TEST QUERY", 139 "db.type": "sql", 140 "peer.address": "connection string", 141 }, 142 }, 143 }, data.Tags) 144 }) 145 } 146 147 func TestOpenSQLDB(t *testing.T) { 148 149 recorder := instana.NewTestRecorder() 150 s := instana.NewSensorWithTracer(instana.NewTracerWithEverything(&instana.Options{ 151 Service: "go-sensor-test", 152 AgentClient: alwaysReadyClient{}, 153 }, recorder)) 154 defer instana.ShutdownSensor() 155 156 span := s.Tracer().StartSpan("parent-span") 157 ctx := context.Background() 158 if span != nil { 159 ctx = instana.ContextWithSpan(ctx, span) 160 } 161 instana.InstrumentSQLDriver(s, "test_driver", sqlDriver{}) 162 require.Contains(t, sql.Drivers(), "test_driver_with_instana") 163 164 db, err := instana.SQLOpen("test_driver", "connection string") 165 require.NoError(t, err) 166 167 t.Run("Exec", func(t *testing.T) { 168 res, err := db.ExecContext(ctx, "TEST QUERY") 169 require.NoError(t, err) 170 171 lastID, err := res.LastInsertId() 172 require.NoError(t, err) 173 assert.Equal(t, int64(42), lastID) 174 175 spans := recorder.GetQueuedSpans() 176 require.Len(t, spans, 1) 177 178 span := spans[0] 179 assert.Equal(t, 0, span.Ec) 180 assert.EqualValues(t, instana.ExitSpanKind, span.Kind) 181 182 require.IsType(t, instana.SDKSpanData{}, span.Data) 183 data := span.Data.(instana.SDKSpanData) 184 185 assert.Equal(t, instana.SDKSpanTags{ 186 Name: "sdk.database", 187 Type: "exit", 188 Custom: map[string]interface{}{ 189 "tags": ot.Tags{ 190 "span.kind": ext.SpanKindRPCClientEnum, 191 "db.instance": "connection string", 192 "db.statement": "TEST QUERY", 193 "db.type": "sql", 194 "peer.address": "connection string", 195 }, 196 }, 197 }, data.Tags) 198 }) 199 200 t.Run("Query", func(t *testing.T) { 201 res, err := db.QueryContext(ctx, "TEST QUERY") 202 require.NoError(t, err) 203 204 cols, err := res.Columns() 205 require.NoError(t, err) 206 assert.Equal(t, []string{"col1", "col2"}, cols) 207 208 spans := recorder.GetQueuedSpans() 209 require.Len(t, spans, 1) 210 211 span := spans[0] 212 assert.Equal(t, 0, span.Ec) 213 assert.EqualValues(t, instana.ExitSpanKind, span.Kind) 214 215 require.IsType(t, instana.SDKSpanData{}, span.Data) 216 data := span.Data.(instana.SDKSpanData) 217 218 assert.Equal(t, instana.SDKSpanTags{ 219 Name: "sdk.database", 220 Type: "exit", 221 Custom: map[string]interface{}{ 222 "tags": ot.Tags{ 223 "span.kind": ext.SpanKindRPCClientEnum, 224 "db.instance": "connection string", 225 "db.statement": "TEST QUERY", 226 "db.type": "sql", 227 "peer.address": "connection string", 228 }, 229 }, 230 }, data.Tags) 231 }) 232 } 233 234 func TestDSNParing(t *testing.T) { 235 testcases := map[string]struct { 236 DSN string 237 ExpectedConfig instana.DbConnDetails 238 }{ 239 "URI": { 240 DSN: "db://user1:p@55w0rd@db-host:1234/test-schema?param=value", 241 ExpectedConfig: instana.DbConnDetails{ 242 Schema: "test-schema", 243 RawString: "db://user1@db-host:1234/test-schema?param=value", 244 Host: "db-host", 245 Port: "1234", 246 User: "user1", 247 }, 248 }, 249 "Postgres": { 250 DSN: "host=db-host1,db-host-2 hostaddr=1.2.3.4,2.3.4.5 connect_timeout=10 port=1234 user=user1 password=p@55w0rd dbname=test-schema", 251 ExpectedConfig: instana.DbConnDetails{ 252 RawString: "host=db-host1,db-host-2 hostaddr=1.2.3.4,2.3.4.5 connect_timeout=10 port=1234 user=user1 dbname=test-schema", 253 Host: "1.2.3.4,2.3.4.5", 254 Port: "1234", 255 Schema: "test-schema", 256 User: "user1", 257 DatabaseName: "postgres", 258 }, 259 }, 260 "MySQL": { 261 DSN: "Server=db-host1, db-host2;Database=test-schema;Port=1234;Uid=user1;Pwd=p@55w0rd;", 262 ExpectedConfig: instana.DbConnDetails{ 263 RawString: "Server=db-host1, db-host2;Database=test-schema;Port=1234;Uid=user1;", 264 Host: "db-host1, db-host2", 265 Port: "1234", 266 Schema: "test-schema", 267 User: "user1", 268 DatabaseName: "mysql", 269 }, 270 }, 271 "Redis_full_conn_string": { 272 DSN: "user:p455w0rd@127.0.0.15:3679", 273 ExpectedConfig: instana.DbConnDetails{ 274 RawString: "user:p455w0rd@127.0.0.15:3679", 275 Host: "127.0.0.15", 276 Port: "3679", 277 Schema: "", 278 User: "", 279 DatabaseName: "redis", 280 }, 281 }, 282 "Redis_no_user": { 283 DSN: ":p455w0rd@127.0.0.15:3679", 284 ExpectedConfig: instana.DbConnDetails{ 285 RawString: ":p455w0rd@127.0.0.15:3679", 286 Host: "127.0.0.15", 287 Port: "3679", 288 Schema: "", 289 User: "", 290 DatabaseName: "redis", 291 }, 292 }, 293 "SQLite": { 294 DSN: "/home/user/products.db", 295 ExpectedConfig: instana.DbConnDetails{ 296 RawString: "/home/user/products.db", 297 }, 298 }, 299 } 300 301 for name, testcase := range testcases { 302 t.Run(name, func(t *testing.T) { 303 connDetails := instana.ParseDBConnDetails(testcase.DSN) 304 assert.Equal(t, testcase.ExpectedConfig, connDetails) 305 }) 306 } 307 } 308 309 func TestOpenSQLDB_URIConnString(t *testing.T) { 310 311 recorder := instana.NewTestRecorder() 312 s := instana.NewSensorWithTracer(instana.NewTracerWithEverything(&instana.Options{ 313 Service: "go-sensor-test", 314 AgentClient: alwaysReadyClient{}, 315 }, recorder)) 316 defer instana.ShutdownSensor() 317 318 span := s.Tracer().StartSpan("parent-span") 319 ctx := context.Background() 320 if span != nil { 321 ctx = instana.ContextWithSpan(ctx, span) 322 } 323 324 instana.InstrumentSQLDriver(s, "fake_db_driver", sqlDriver{}) 325 require.Contains(t, sql.Drivers(), "test_driver_with_instana") 326 327 db, err := instana.SQLOpen("fake_db_driver", "db://user1:p@55w0rd@db-host:1234/test-schema?param=value") 328 require.NoError(t, err) 329 330 _, err = db.ExecContext(ctx, "TEST QUERY") 331 require.NoError(t, err) 332 333 spans := recorder.GetQueuedSpans() 334 require.Len(t, spans, 1) 335 336 require.IsType(t, instana.SDKSpanData{}, spans[0].Data) 337 data := spans[0].Data.(instana.SDKSpanData) 338 339 assert.Equal(t, instana.SDKSpanTags{ 340 Name: "sdk.database", 341 Type: "exit", 342 Custom: map[string]interface{}{ 343 "tags": ot.Tags{ 344 "span.kind": ext.SpanKindRPCClientEnum, 345 "db.instance": "test-schema", 346 "db.statement": "TEST QUERY", 347 "db.type": "sql", 348 "peer.address": "db://user1@db-host:1234/test-schema?param=value", 349 "peer.hostname": "db-host", 350 "peer.port": "1234", 351 }, 352 }, 353 }, data.Tags) 354 } 355 356 func TestOpenSQLDB_PostgresKVConnString(t *testing.T) { 357 358 recorder := instana.NewTestRecorder() 359 s := instana.NewSensorWithTracer(instana.NewTracerWithEverything(&instana.Options{ 360 Service: "go-sensor-test", 361 AgentClient: alwaysReadyClient{}, 362 }, recorder)) 363 defer instana.ShutdownSensor() 364 365 span := s.Tracer().StartSpan("parent-span") 366 ctx := context.Background() 367 if span != nil { 368 ctx = instana.ContextWithSpan(ctx, span) 369 } 370 371 instana.InstrumentSQLDriver(s, "fake_postgres_driver", sqlDriver{}) 372 require.Contains(t, sql.Drivers(), "fake_postgres_driver_with_instana") 373 374 db, err := instana.SQLOpen("fake_postgres_driver", "host=db-host1,db-host-2 hostaddr=1.2.3.4,2.3.4.5 connect_timeout=10 port=1234 user=user1 password=p@55w0rd dbname=test-schema") 375 require.NoError(t, err) 376 377 _, err = db.ExecContext(ctx, "TEST QUERY") 378 require.NoError(t, err) 379 380 spans := recorder.GetQueuedSpans() 381 require.Len(t, spans, 1) 382 383 require.IsType(t, instana.PostgreSQLSpanData{}, spans[0].Data) 384 data := spans[0].Data.(instana.PostgreSQLSpanData) 385 386 assert.Equal(t, instana.PostgreSQLSpanTags{ 387 Host: "1.2.3.4,2.3.4.5", 388 DB: "test-schema", 389 Port: "1234", 390 User: "user1", 391 Stmt: "TEST QUERY", 392 Error: "", 393 }, data.Tags) 394 } 395 396 func TestOpenSQLDB_MySQLKVConnString(t *testing.T) { 397 398 recorder := instana.NewTestRecorder() 399 s := instana.NewSensorWithTracer(instana.NewTracerWithEverything(&instana.Options{ 400 Service: "go-sensor-test", 401 AgentClient: alwaysReadyClient{}, 402 }, recorder)) 403 defer instana.ShutdownSensor() 404 405 span := s.Tracer().StartSpan("parent-span") 406 ctx := context.Background() 407 if span != nil { 408 ctx = instana.ContextWithSpan(ctx, span) 409 } 410 411 instana.InstrumentSQLDriver(s, "fake_mysql_driver", sqlDriver{}) 412 require.Contains(t, sql.Drivers(), "fake_mysql_driver_with_instana") 413 414 db, err := instana.SQLOpen("fake_mysql_driver", "Server=db-host1, db-host2;Database=test-schema;Port=1234;Uid=user1;Pwd=p@55w0rd;") 415 require.NoError(t, err) 416 417 _, err = db.ExecContext(ctx, "TEST QUERY") 418 require.NoError(t, err) 419 420 spans := recorder.GetQueuedSpans() 421 require.Len(t, spans, 1) 422 423 require.IsType(t, instana.MySQLSpanData{}, spans[0].Data) 424 data := spans[0].Data.(instana.MySQLSpanData) 425 426 assert.Equal(t, instana.MySQLSpanTags{ 427 Host: "db-host1, db-host2", 428 Port: "1234", 429 DB: "test-schema", 430 User: "user1", 431 Stmt: "TEST QUERY", 432 Error: "", 433 }, data.Tags) 434 } 435 436 func TestOpenSQLDB_RedisConnString(t *testing.T) { 437 438 recorder := instana.NewTestRecorder() 439 s := instana.NewSensorWithTracer(instana.NewTracerWithEverything(&instana.Options{ 440 Service: "go-sensor-test", 441 AgentClient: alwaysReadyClient{}, 442 }, recorder)) 443 defer instana.ShutdownSensor() 444 445 span := s.Tracer().StartSpan("parent-span") 446 ctx := context.Background() 447 if span != nil { 448 ctx = instana.ContextWithSpan(ctx, span) 449 } 450 451 instana.InstrumentSQLDriver(s, "fake_redis_driver", sqlDriver{}) 452 require.Contains(t, sql.Drivers(), "fake_redis_driver_with_instana") 453 454 db, err := instana.SQLOpen("fake_redis_driver", ":p455w0rd@192.168.2.10:6790") 455 require.NoError(t, err) 456 457 _, err = db.ExecContext(ctx, "SET name Instana EX 15") 458 require.NoError(t, err) 459 460 spans := recorder.GetQueuedSpans() 461 require.Len(t, spans, 1) 462 463 require.IsType(t, instana.RedisSpanData{}, spans[0].Data) 464 data := spans[0].Data.(instana.RedisSpanData) 465 466 assert.Equal(t, instana.RedisSpanTags{ 467 Connection: "192.168.2.10:6790", 468 Command: "SET", 469 Error: "", 470 }, data.Tags) 471 } 472 473 func TestConnPrepareContext(t *testing.T) { 474 475 recorder := instana.NewTestRecorder() 476 s := instana.NewSensorWithTracer(instana.NewTracerWithEverything(&instana.Options{ 477 Service: "go-sensor-test", 478 AgentClient: alwaysReadyClient{}, 479 }, recorder)) 480 defer instana.ShutdownSensor() 481 482 span := s.Tracer().StartSpan("parent-span") 483 ctx := context.Background() 484 if span != nil { 485 ctx = instana.ContextWithSpan(ctx, span) 486 } 487 488 instana.InstrumentSQLDriver(s, "fake_pc", sqlDriver{}) 489 require.Contains(t, sql.Drivers(), "fake_pc_with_instana") 490 491 db, err := instana.SQLOpen("fake_pc", "conn string") 492 require.NoError(t, err) 493 494 stmt, err := db.PrepareContext(ctx, "select 1 from table") 495 require.NoError(t, err) 496 497 _, err = stmt.QueryContext(ctx) 498 require.NoError(t, err) 499 500 spans := recorder.GetQueuedSpans() 501 502 require.Len(t, spans, 1) 503 504 require.IsType(t, instana.SDKSpanData{}, spans[0].Data) 505 data := spans[0].Data.(instana.SDKSpanData) 506 507 assert.Equal(t, instana.SDKSpanTags{ 508 Name: "sdk.database", 509 Type: "exit", 510 Custom: map[string]interface{}{ 511 "tags": ot.Tags{ 512 "span.kind": ext.SpanKindRPCClientEnum, 513 "db.instance": "conn string", 514 "db.statement": "select 1 from table", 515 "db.type": "sql", 516 "peer.address": "conn string", 517 }, 518 }, 519 }, data.Tags) 520 } 521 522 func TestConnPrepareContextWithError(t *testing.T) { 523 524 recorder := instana.NewTestRecorder() 525 s := instana.NewSensorWithTracer(instana.NewTracerWithEverything(&instana.Options{ 526 Service: "go-sensor-test", 527 AgentClient: alwaysReadyClient{}, 528 }, recorder)) 529 defer instana.ShutdownSensor() 530 531 span := s.Tracer().StartSpan("parent-span") 532 ctx := context.Background() 533 if span != nil { 534 ctx = instana.ContextWithSpan(ctx, span) 535 } 536 537 instana.InstrumentSQLDriver(s, "fake_conn_pc_error", sqlDriver{Error: errors.New("some error")}) 538 require.Contains(t, sql.Drivers(), "fake_conn_pc_error_with_instana") 539 540 db, err := instana.SQLOpen("fake_conn_pc_error", "conn string") 541 require.NoError(t, err) 542 543 stmt, err := db.PrepareContext(ctx, "select 1 from table") 544 require.NoError(t, err) 545 546 _, err = stmt.QueryContext(ctx) 547 require.Error(t, err) 548 549 spans := recorder.GetQueuedSpans() 550 551 require.Len(t, spans, 2) 552 553 assert.Equal(t, spans[0].Ec, 1) 554 555 require.IsType(t, instana.SDKSpanData{}, spans[0].Data) 556 data := spans[0].Data.(instana.SDKSpanData) 557 558 assert.Equal(t, instana.SDKSpanTags{ 559 Name: "sdk.database", 560 Type: "exit", 561 Custom: map[string]interface{}{ 562 "tags": ot.Tags{ 563 "span.kind": ext.SpanKindRPCClientEnum, 564 "db.error": "some error", 565 "db.instance": "conn string", 566 "db.statement": "select 1 from table", 567 "db.type": "sql", 568 "peer.address": "conn string", 569 }, 570 }, 571 }, data.Tags) 572 } 573 574 func TestStmtExecContext(t *testing.T) { 575 576 recorder := instana.NewTestRecorder() 577 s := instana.NewSensorWithTracer(instana.NewTracerWithEverything(&instana.Options{ 578 Service: "go-sensor-test", 579 AgentClient: alwaysReadyClient{}, 580 }, recorder)) 581 defer instana.ShutdownSensor() 582 583 instana.InstrumentSQLDriver(s, "fake_stmt_ec", sqlDriver{}) 584 require.Contains(t, sql.Drivers(), "fake_stmt_ec_with_instana") 585 586 db, err := instana.SQLOpen("fake_stmt_ec", "conn string") 587 require.NoError(t, err) 588 589 span := s.Tracer().StartSpan("parent-span") 590 ctx := context.Background() 591 if span != nil { 592 ctx = instana.ContextWithSpan(ctx, span) 593 } 594 595 stmt, err := db.PrepareContext(ctx, "select 1 from table") 596 require.NoError(t, err) 597 598 _, err = stmt.ExecContext(ctx) 599 require.NoError(t, err) 600 601 spans := recorder.GetQueuedSpans() 602 603 require.Len(t, spans, 1) 604 605 require.IsType(t, instana.SDKSpanData{}, spans[0].Data) 606 data := spans[0].Data.(instana.SDKSpanData) 607 608 assert.Equal(t, instana.SDKSpanTags{ 609 Name: "sdk.database", 610 Type: "exit", 611 Custom: map[string]interface{}{ 612 "tags": ot.Tags{ 613 "span.kind": ext.SpanKindRPCClientEnum, 614 "db.instance": "conn string", 615 "db.statement": "select 1 from table", 616 "db.type": "sql", 617 "peer.address": "conn string", 618 }, 619 }, 620 }, data.Tags) 621 } 622 623 func TestStmtExecContextWithError(t *testing.T) { 624 625 recorder := instana.NewTestRecorder() 626 s := instana.NewSensorWithTracer(instana.NewTracerWithEverything(&instana.Options{ 627 Service: "go-sensor-test", 628 AgentClient: alwaysReadyClient{}, 629 }, recorder)) 630 defer instana.ShutdownSensor() 631 632 span := s.Tracer().StartSpan("parent-span") 633 ctx := context.Background() 634 if span != nil { 635 ctx = instana.ContextWithSpan(ctx, span) 636 } 637 638 instana.InstrumentSQLDriver(s, "fake_stmt_ec_with_error", sqlDriver{Error: errors.New("oh no")}) 639 require.Contains(t, sql.Drivers(), "fake_stmt_ec_with_error_with_instana") 640 641 db, err := instana.SQLOpen("fake_stmt_ec_with_error", "conn string") 642 require.NoError(t, err) 643 644 stmt, err := db.PrepareContext(ctx, "select 1 from table") 645 require.NoError(t, err) 646 647 _, err = stmt.ExecContext(ctx) 648 require.Error(t, err) 649 650 spans := recorder.GetQueuedSpans() 651 652 require.Len(t, spans, 2) 653 654 require.IsType(t, instana.SDKSpanData{}, spans[0].Data) 655 data := spans[0].Data.(instana.SDKSpanData) 656 657 assert.Equal(t, instana.SDKSpanTags{ 658 Name: "sdk.database", 659 Type: "exit", 660 Custom: map[string]interface{}{ 661 "tags": ot.Tags{ 662 "span.kind": ext.SpanKindRPCClientEnum, 663 "db.error": "oh no", 664 "db.instance": "conn string", 665 "db.statement": "select 1 from table", 666 "db.type": "sql", 667 "peer.address": "conn string", 668 }, 669 }, 670 }, data.Tags) 671 } 672 673 func TestConnPrepareContextWithErrorOnReturn(t *testing.T) { 674 recorder := instana.NewTestRecorder() 675 s := instana.NewSensorWithTracer(instana.NewTracerWithEverything(&instana.Options{ 676 Service: "go-sensor-test", 677 AgentClient: alwaysReadyClient{}, 678 }, recorder)) 679 defer instana.ShutdownSensor() 680 681 instana.InstrumentSQLDriver(s, "fake_conn_pc_error_on_ret", sqlDriver{PrepareError: errors.New("oh no")}) 682 require.Contains(t, sql.Drivers(), "fake_conn_pc_error_on_ret_with_instana") 683 684 db, err := instana.SQLOpen("fake_conn_pc_error_on_ret", "conn string") 685 require.NoError(t, err) 686 687 ctx := context.Background() 688 689 _, err = db.PrepareContext(ctx, "select 1 from table") 690 require.Error(t, err) 691 } 692 693 func TestOpenSQLDB_RedisConnString_WithError(t *testing.T) { 694 695 recorder := instana.NewTestRecorder() 696 s := instana.NewSensorWithTracer(instana.NewTracerWithEverything(&instana.Options{ 697 Service: "go-sensor-test", 698 AgentClient: alwaysReadyClient{}, 699 }, recorder)) 700 defer instana.ShutdownSensor() 701 702 span := s.Tracer().StartSpan("parent-span") 703 ctx := context.Background() 704 if span != nil { 705 ctx = instana.ContextWithSpan(ctx, span) 706 } 707 708 instana.InstrumentSQLDriver(s, "fake_redis_driver_with_error", sqlDriver{Error: errors.New("oops")}) 709 require.Contains(t, sql.Drivers(), "fake_redis_driver_with_error_with_instana") 710 711 db, err := instana.SQLOpen("fake_redis_driver_with_error", ":p455w0rd@192.168.2.10:6790") 712 require.NoError(t, err) 713 714 _, err = db.ExecContext(ctx, "SET name Instana EX 15") 715 716 require.Error(t, err) 717 718 spans := recorder.GetQueuedSpans() 719 require.Len(t, spans, 2) 720 721 require.IsType(t, instana.RedisSpanData{}, spans[0].Data) 722 data := spans[0].Data.(instana.RedisSpanData) 723 724 assert.Equal(t, instana.RedisSpanTags{ 725 Connection: "192.168.2.10:6790", 726 Command: "SET", 727 Error: "oops", 728 }, data.Tags) 729 } 730 731 func TestOpenSQLDB_RedisKVConnString(t *testing.T) { 732 733 recorder := instana.NewTestRecorder() 734 s := instana.NewSensorWithTracer(instana.NewTracerWithEverything(&instana.Options{ 735 Service: "go-sensor-test", 736 AgentClient: alwaysReadyClient{}, 737 }, recorder)) 738 defer instana.ShutdownSensor() 739 740 span := s.Tracer().StartSpan("parent-span") 741 ctx := context.Background() 742 if span != nil { 743 ctx = instana.ContextWithSpan(ctx, span) 744 } 745 746 instana.InstrumentSQLDriver(s, "fake_redis_kv_driver", sqlDriver{}) 747 require.Contains(t, sql.Drivers(), "fake_redis_kv_driver_with_instana") 748 749 db, err := instana.SQLOpen("fake_redis_kv_driver", "192.168.2.10:6790") 750 require.NoError(t, err) 751 752 _, err = db.ExecContext(ctx, "SET name Instana EX 15") 753 require.NoError(t, err) 754 755 spans := recorder.GetQueuedSpans() 756 require.Len(t, spans, 1) 757 758 require.IsType(t, instana.RedisSpanData{}, spans[0].Data) 759 data := spans[0].Data.(instana.RedisSpanData) 760 761 assert.Equal(t, instana.RedisSpanTags{ 762 Connection: "192.168.2.10:6790", 763 Command: "SET", 764 Error: "", 765 }, data.Tags) 766 } 767 768 func TestNoPanicWithNotParsableConnectionString(t *testing.T) { 769 s := instana.NewSensorWithTracer(instana.NewTracerWithEverything(&instana.Options{ 770 Service: "go-sensor-test", 771 AgentClient: alwaysReadyClient{}, 772 }, instana.NewTestRecorder())) 773 defer instana.ShutdownSensor() 774 775 instana.InstrumentSQLDriver(s, "test_driver", sqlDriver{}) 776 require.Contains(t, sql.Drivers(), "test_driver_with_instana") 777 778 assert.NotPanics(t, func() { 779 _, _ = instana.SQLOpen("test_driver", 780 "postgres:mysecretpassword@localhost/postgres") 781 }) 782 } 783 784 func TestProcedureWithCheckerOnStmt(t *testing.T) { 785 s := instana.NewSensorWithTracer(instana.NewTracerWithEverything(&instana.Options{ 786 Service: "go-sensor-test", 787 AgentClient: alwaysReadyClient{}, 788 }, instana.NewTestRecorder())) 789 defer instana.ShutdownSensor() 790 791 var called bool 792 793 driver := &db2DriverMock{ 794 called: &called, 795 } 796 797 instana.InstrumentSQLDriver(s, "test_driver2", driver) 798 db, err := instana.SQLOpen("test_driver2", "some datasource") 799 800 assert.NoError(t, err) 801 802 var outValue string 803 _, err = db.Exec("CALL SOME_PROCEDURE(?)", sql.Out{Dest: &outValue}) 804 805 assert.True(t, called) 806 807 // Here we expect the instrumentation to look for the driver's conn.CheckNamedValue implementation. 808 // If there is none, we check stmt.CheckNamedValue, which sqlDriver2 has. 809 // If there is none, we return nil from our side, since driver.ErrSkip won't work for CheckNamedValue, as seen here: 810 // https://cs.opensource.google/go/go/+/refs/tags/go1.19.1:src/database/sql/driver/driver.go;l=143 811 // and here: https://cs.opensource.google/go/go/+/refs/tags/go1.19.1:src/database/sql/driver/driver.go;l=399 812 assert.NoError(t, err) 813 } 814 815 func TestProcedureWithNoDefaultChecker(t *testing.T) { 816 s := instana.NewSensorWithTracer(instana.NewTracerWithEverything(&instana.Options{ 817 Service: "go-sensor-test", 818 AgentClient: alwaysReadyClient{}, 819 }, instana.NewTestRecorder())) 820 defer instana.ShutdownSensor() 821 822 driver := pqDriverMock{} 823 824 instana.InstrumentSQLDriver(s, "test_driver3", driver) 825 db, err := instana.SQLOpen("test_driver3", "some datasource") 826 827 assert.NoError(t, err) 828 829 _, err = db.Exec("select $1", int32(1)) 830 831 // Here we expect the instrumentation to look for the driver's conn.CheckNamedValue implementation. 832 // If there is none, we check stmt.CheckNamedValue, which sqlDriver also doesn't have. 833 // If there is none, we return nil from our side, since driver.ErrSkip won't work for CheckNamedValue, as seen here: 834 // https://cs.opensource.google/go/go/+/refs/tags/go1.19.1:src/database/sql/driver/driver.go;l=143 835 // and here: https://cs.opensource.google/go/go/+/refs/tags/go1.19.1:src/database/sql/driver/driver.go;l=399 836 assert.NoError(t, err) 837 } 838 839 type sqlDriver struct { 840 // Error is a generic error in the SQL execution. It generates spans with errors 841 Error error 842 // StmtError will give an error when a method from Stmt returns. It does not generate spans at all 843 StmtError error 844 // PrepareError will give an error when a method from Prepare* returns. It does not generate spans at all 845 PrepareError error 846 } 847 848 func (drv sqlDriver) Open(name string) (driver.Conn, error) { 849 return sqlConn{ 850 Error: drv.Error, 851 StmtError: drv.StmtError, 852 PrepareError: drv.PrepareError, 853 }, nil 854 } //nolint:gosimple 855 856 type sqlConn struct { 857 Error error 858 StmtError error 859 PrepareError error 860 } 861 862 var _ driver.Conn = (*sqlConn)(nil) 863 var _ driver.ConnPrepareContext = (*sqlConn)(nil) 864 865 func (conn sqlConn) Prepare(query string) (driver.Stmt, error) { 866 return sqlStmt{Error: conn.Error}, nil 867 } //nolint:gosimple 868 func (conn sqlConn) Close() error { return driver.ErrSkip } 869 func (conn sqlConn) Begin() (driver.Tx, error) { return nil, driver.ErrSkip } 870 871 func (conn sqlConn) PrepareContext(ctx context.Context, query string) (driver.Stmt, error) { 872 return sqlStmt{StmtError: conn.StmtError, Error: conn.Error}, conn.PrepareError //nolint:gosimple 873 } 874 875 func (conn sqlConn) ExecContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Result, error) { 876 return sqlResult{}, conn.Error 877 } 878 879 func (conn sqlConn) QueryContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Rows, error) { 880 return sqlRows{}, conn.Error 881 } 882 883 type sqlStmt struct { 884 Error error 885 StmtError error 886 } 887 888 func (sqlStmt) Close() error { return nil } 889 func (sqlStmt) NumInput() int { return -1 } 890 func (stmt sqlStmt) Exec(args []driver.Value) (driver.Result, error) { return sqlResult{}, stmt.Error } 891 func (stmt sqlStmt) Query(args []driver.Value) (driver.Rows, error) { return sqlRows{}, stmt.Error } 892 func (stmt sqlStmt) QueryContext(ctx context.Context, args []driver.NamedValue) (driver.Rows, error) { 893 return sqlRows{}, stmt.Error 894 } 895 func (stmt sqlStmt) ExecContext(ctx context.Context, args []driver.NamedValue) (driver.Result, error) { 896 return sqlResult{}, stmt.Error 897 } 898 899 type sqlResult struct{} 900 901 func (sqlResult) LastInsertId() (int64, error) { return 42, nil } 902 func (sqlResult) RowsAffected() (int64, error) { return 100, nil } 903 904 type sqlRows struct{} 905 906 func (sqlRows) Columns() []string { return []string{"col1", "col2"} } 907 func (sqlRows) Close() error { return nil } 908 func (sqlRows) Next(dest []driver.Value) error { return io.EOF } 909 910 // Driver use case: 911 // * driver.Conn doesn't implement Exec or ExecContext 912 // * driver.Conn doesn't implement the driver.NamedValueChecker interface (CheckNamedValue method) 913 // * driver.Stmt does implement the driver.NamedValueChecker interface (CheckNamedValue method) 914 // * Our wrapper ALWAYS implements ExecContext, no matter what 915 916 type db2DriverMock struct { 917 Error error 918 called *bool 919 } 920 921 func (drv *db2DriverMock) Open(name string) (driver.Conn, error) { 922 return db2ConnMock{drv.Error, drv.called}, nil 923 } //nolint:gosimple 924 925 type db2ConnMock struct { 926 Error error 927 called *bool 928 } 929 930 func (conn db2ConnMock) Prepare(query string) (driver.Stmt, error) { 931 return db2StmtMock{conn.Error, conn.called}, nil //nolint:gosimple 932 } 933 func (s db2ConnMock) Close() error { return driver.ErrSkip } 934 935 func (s db2ConnMock) Begin() (driver.Tx, error) { return nil, driver.ErrSkip } 936 937 type db2StmtMock struct { 938 Error error 939 called *bool 940 } 941 942 func (db2StmtMock) Close() error { return nil } 943 func (db2StmtMock) NumInput() int { return -1 } 944 func (stmt db2StmtMock) Exec(args []driver.Value) (driver.Result, error) { 945 return db2ResultMock{}, stmt.Error 946 } 947 948 func (stmt db2StmtMock) Query(args []driver.Value) (driver.Rows, error) { 949 return db2RowsMock{}, stmt.Error 950 } 951 952 func (stmt db2StmtMock) CheckNamedValue(d *driver.NamedValue) error { 953 *stmt.called = true 954 return nil 955 } 956 957 type db2ResultMock struct{} 958 959 func (db2ResultMock) LastInsertId() (int64, error) { return 42, nil } 960 func (db2ResultMock) RowsAffected() (int64, error) { return 100, nil } 961 962 type db2RowsMock struct{} 963 964 func (db2RowsMock) Columns() []string { return []string{"col1", "col2"} } 965 func (db2RowsMock) Close() error { return nil } 966 func (db2RowsMock) Next(dest []driver.Value) error { return io.EOF } 967 968 // Driver use case: driver does not implement NamedValueChecker,arg type checking is internal. 969 // The idea is to mock pq: https://github.com/lib/pq/blob/8446d16b8935fdf2b5c0fe333538ac395e3e1e4b/encode.go#L31 970 971 type pqDriverMock struct{ Error error } 972 973 func (drv pqDriverMock) Open(name string) (driver.Conn, error) { return pqConnMock{drv.Error}, nil } //nolint:gosimple 974 975 type pqConnMock struct{ Error error } 976 977 func (conn pqConnMock) Prepare(query string) (driver.Stmt, error) { return pqStmtMock{conn.Error}, nil } //nolint:gosimple 978 func (s pqConnMock) Close() error { return driver.ErrSkip } 979 func (s pqConnMock) Begin() (driver.Tx, error) { return nil, driver.ErrSkip } 980 981 func (conn pqConnMock) ExecContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Result, error) { 982 var err error 983 984 if _, ok := args[0].Value.(int32); ok { 985 err = errors.New("invalid type int32") 986 } 987 988 return pqResultMock{}, err 989 } 990 991 func (conn pqConnMock) QueryContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Rows, error) { 992 return pqRowsMock{}, conn.Error 993 } 994 995 type pqStmtMock struct{ Error error } 996 997 func (pqStmtMock) Close() error { return nil } 998 func (pqStmtMock) NumInput() int { return -1 } 999 func (stmt pqStmtMock) Exec(args []driver.Value) (driver.Result, error) { 1000 return pqResultMock{}, stmt.Error 1001 } 1002 func (stmt pqStmtMock) Query(args []driver.Value) (driver.Rows, error) { 1003 return pqRowsMock{}, stmt.Error 1004 } 1005 1006 type pqResultMock struct{} 1007 1008 func (pqResultMock) LastInsertId() (int64, error) { return 42, nil } 1009 func (pqResultMock) RowsAffected() (int64, error) { return 100, nil } 1010 1011 type pqRowsMock struct{} 1012 1013 func (pqRowsMock) Columns() []string { return []string{"col1", "col2"} } 1014 func (pqRowsMock) Close() error { return nil } 1015 func (pqRowsMock) Next(dest []driver.Value) error { return io.EOF }