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 }