github.com/Jeffail/benthos/v3@v3.65.0/lib/processor/sql_test.go (about)

     1  package processor
     2  
     3  import (
     4  	"database/sql"
     5  	"flag"
     6  	"fmt"
     7  	"reflect"
     8  	"regexp"
     9  	"strings"
    10  	"testing"
    11  
    12  	"github.com/Jeffail/benthos/v3/lib/log"
    13  	"github.com/Jeffail/benthos/v3/lib/message"
    14  	"github.com/Jeffail/benthos/v3/lib/metrics"
    15  	"github.com/ory/dockertest/v3"
    16  	"github.com/stretchr/testify/assert"
    17  	"github.com/stretchr/testify/require"
    18  )
    19  
    20  func TestSQLIntegration(t *testing.T) {
    21  	if m := flag.Lookup("test.run").Value.String(); m == "" || regexp.MustCompile(strings.Split(m, "/")[0]).FindString(t.Name()) == "" {
    22  		t.Skip("Skipping as execution was not requested explicitly using go test -run ^TestIntegration$")
    23  	}
    24  
    25  	if testing.Short() {
    26  		t.Skip("Skipping integration test in short mode")
    27  	}
    28  
    29  	t.Run("TestSQLClickhouseIntegration", SQLClickhouseIntegration)
    30  	t.Run("TestSQLPostgresIntegration", SQLPostgresIntegration)
    31  	t.Run("TestSQLMySQLIntegration", SQLMySQLIntegration)
    32  	t.Run("TestSQLMSSQLIntegration", SQLMSSQLIntegration)
    33  }
    34  
    35  func SQLClickhouseIntegration(t *testing.T) {
    36  	t.Parallel()
    37  
    38  	pool, err := dockertest.NewPool("")
    39  	if err != nil {
    40  		t.Skipf("Could not connect to docker: %s", err)
    41  	}
    42  
    43  	resource, err := pool.RunWithOptions(&dockertest.RunOptions{
    44  		Repository:   "yandex/clickhouse-server",
    45  		ExposedPorts: []string{"9000/tcp"},
    46  	})
    47  	require.NoError(t, err)
    48  
    49  	t.Cleanup(func() {
    50  		if err = pool.Purge(resource); err != nil {
    51  			t.Logf("Failed to clean up docker resource: %v", err)
    52  		}
    53  	})
    54  
    55  	dsn := fmt.Sprintf("tcp://localhost:%v/", resource.GetPort("9000/tcp"))
    56  	if err = pool.Retry(func() error {
    57  		db, dberr := sql.Open("clickhouse", dsn)
    58  		if dberr != nil {
    59  			return dberr
    60  		}
    61  		if dberr = db.Ping(); err != nil {
    62  			return dberr
    63  		}
    64  		if _, dberr = db.Exec(`create table footable (
    65    foo String,
    66    bar Int64,
    67    baz String
    68  ) engine=Memory;`); dberr != nil {
    69  			return dberr
    70  		}
    71  		return nil
    72  	}); err != nil {
    73  		t.Fatalf("Could not connect to docker resource: %s", err)
    74  	}
    75  
    76  	t.Run("testSQLClickhouse", func(t *testing.T) {
    77  		testSQLClickhouse(t, dsn)
    78  	})
    79  }
    80  
    81  func testSQLClickhouse(t *testing.T, dsn string) {
    82  	conf := NewConfig()
    83  	conf.Type = TypeSQL
    84  	conf.SQL.Driver = "clickhouse"
    85  	conf.SQL.DataSourceName = dsn
    86  	conf.SQL.Query = "INSERT INTO footable (foo, bar, baz) VALUES (?, ?, ?);"
    87  	conf.SQL.ArgsMapping = `root = [ this.foo, this.bar.floor(), this.baz ]`
    88  
    89  	s, err := NewSQL(conf, nil, log.Noop(), metrics.Noop())
    90  	require.NoError(t, err)
    91  
    92  	parts := [][]byte{
    93  		[]byte(`{"foo":"foo1","bar":11,"baz":"baz1"}`),
    94  		[]byte(`{"foo":"foo2","bar":12,"baz":"baz2"}`),
    95  	}
    96  
    97  	resMsgs, response := s.ProcessMessage(message.New(parts))
    98  	require.Nil(t, response)
    99  	require.Len(t, resMsgs, 1)
   100  	assert.Equal(t, parts, message.GetAllBytes(resMsgs[0]))
   101  	require.Empty(t, GetFail(resMsgs[0].Get(0)))
   102  	require.Empty(t, GetFail(resMsgs[0].Get(1)))
   103  
   104  	conf.SQL.Query = "SELECT * FROM footable WHERE foo = ?;"
   105  	conf.SQL.ArgsMapping = `[ this.foo ]`
   106  	conf.SQL.ResultCodec = "json_array"
   107  	s, err = NewSQL(conf, nil, log.Noop(), metrics.Noop())
   108  	require.NoError(t, err)
   109  
   110  	parts = [][]byte{
   111  		[]byte(`{"foo":"foo1"}`),
   112  		[]byte(`{"foo":"foo2"}`),
   113  	}
   114  
   115  	resMsgs, response = s.ProcessMessage(message.New(parts))
   116  	require.Nil(t, response)
   117  	require.Len(t, resMsgs, 1)
   118  
   119  	expParts := [][]byte{
   120  		[]byte(`[{"bar":11,"baz":"baz1","foo":"foo1"}]`),
   121  		[]byte(`[{"bar":12,"baz":"baz2","foo":"foo2"}]`),
   122  	}
   123  	assert.Equal(t, expParts, message.GetAllBytes(resMsgs[0]))
   124  }
   125  
   126  func SQLPostgresIntegration(t *testing.T) {
   127  	t.Parallel()
   128  
   129  	pool, err := dockertest.NewPool("")
   130  	if err != nil {
   131  		t.Skipf("Could not connect to docker: %s", err)
   132  	}
   133  
   134  	resource, err := pool.RunWithOptions(&dockertest.RunOptions{
   135  		Repository:   "postgres",
   136  		ExposedPorts: []string{"5432/tcp"},
   137  		Env: []string{
   138  			"POSTGRES_USER=testuser",
   139  			"POSTGRES_PASSWORD=testpass",
   140  			"POSTGRES_DB=testdb",
   141  		},
   142  	})
   143  	if err != nil {
   144  		t.Fatalf("Could not start resource: %s", err)
   145  	}
   146  
   147  	t.Cleanup(func() {
   148  		if err = pool.Purge(resource); err != nil {
   149  			t.Logf("Failed to clean up docker resource: %v", err)
   150  		}
   151  	})
   152  
   153  	dsn := fmt.Sprintf("postgres://testuser:testpass@localhost:%v/testdb?sslmode=disable", resource.GetPort("5432/tcp"))
   154  	if err = pool.Retry(func() error {
   155  		db, dberr := sql.Open("postgres", dsn)
   156  		if dberr != nil {
   157  			return dberr
   158  		}
   159  		if dberr = db.Ping(); err != nil {
   160  			return dberr
   161  		}
   162  		if _, dberr = db.Exec(`create table footable (
   163    foo varchar(50) not null,
   164    bar integer not null,
   165    baz varchar(50) not null,
   166    primary key (foo)
   167  );`); dberr != nil {
   168  			return dberr
   169  		}
   170  		return nil
   171  	}); err != nil {
   172  		t.Fatalf("Could not connect to docker resource: %s", err)
   173  	}
   174  
   175  	t.Run("testSQLPostgresArgsMapping", func(t *testing.T) {
   176  		testSQLPostgresArgsMapping(t, dsn)
   177  	})
   178  	t.Run("testSQLPostgresArgs", func(t *testing.T) {
   179  		testSQLPostgresArgs(t, dsn)
   180  	})
   181  	t.Run("testSQLPostgresDeprecated", func(t *testing.T) {
   182  		testSQLPostgresDeprecated(t, dsn)
   183  	})
   184  }
   185  
   186  func testSQLPostgresArgsMapping(t *testing.T, dsn string) {
   187  	conf := NewConfig()
   188  	conf.Type = TypeSQL
   189  	conf.SQL.Driver = "postgres"
   190  	conf.SQL.DataSourceName = dsn
   191  	conf.SQL.Query = "INSERT INTO footable (foo, bar, baz) VALUES ($1, $2, $3);"
   192  	conf.SQL.ArgsMapping = `[ this.foo, this.bar, this.baz ]`
   193  
   194  	s, err := NewSQL(conf, nil, log.Noop(), metrics.Noop())
   195  	require.NoError(t, err)
   196  
   197  	parts := [][]byte{
   198  		[]byte(`{"foo":"foo1","bar":11,"baz":"baz1"}`),
   199  		[]byte(`{"foo":"foo2","bar":12,"baz":"baz2"}`),
   200  	}
   201  
   202  	resMsgs, response := s.ProcessMessage(message.New(parts))
   203  	require.Nil(t, response)
   204  	require.Len(t, resMsgs, 1)
   205  	assert.Equal(t, parts, message.GetAllBytes(resMsgs[0]))
   206  
   207  	conf.SQL.Query = "SELECT * FROM footable WHERE foo = $1;"
   208  	conf.SQL.ArgsMapping = `[ this.foo ]`
   209  	conf.SQL.ResultCodec = "json_array"
   210  	s, err = NewSQL(conf, nil, log.Noop(), metrics.Noop())
   211  	require.NoError(t, err)
   212  
   213  	parts = [][]byte{
   214  		[]byte(`{"foo":"foo1"}`),
   215  		[]byte(`{"foo":"foo2"}`),
   216  	}
   217  
   218  	resMsgs, response = s.ProcessMessage(message.New(parts))
   219  	require.Nil(t, response)
   220  	require.Len(t, resMsgs, 1)
   221  
   222  	expParts := [][]byte{
   223  		[]byte(`[{"bar":11,"baz":"baz1","foo":"foo1"}]`),
   224  		[]byte(`[{"bar":12,"baz":"baz2","foo":"foo2"}]`),
   225  	}
   226  	assert.Equal(t, expParts, message.GetAllBytes(resMsgs[0]))
   227  }
   228  
   229  func testSQLPostgresArgs(t *testing.T, dsn string) {
   230  	conf := NewConfig()
   231  	conf.Type = TypeSQL
   232  	conf.SQL.Driver = "postgres"
   233  	conf.SQL.DataSourceName = dsn
   234  	conf.SQL.Query = "INSERT INTO footable (foo, bar, baz) VALUES ($1, $2, $3);"
   235  	conf.SQL.Args = []string{
   236  		"${! json(\"foo\") }",
   237  		"${! json(\"bar\") }",
   238  		"${! json(\"baz\") }",
   239  	}
   240  
   241  	s, err := NewSQL(conf, nil, log.Noop(), metrics.Noop())
   242  	require.NoError(t, err)
   243  
   244  	parts := [][]byte{
   245  		[]byte(`{"foo":"foo1","bar":11,"baz":"baz1"}`),
   246  		[]byte(`{"foo":"foo2","bar":12,"baz":"baz2"}`),
   247  	}
   248  
   249  	resMsgs, response := s.ProcessMessage(message.New(parts))
   250  	require.Nil(t, response)
   251  	require.Len(t, resMsgs, 1)
   252  	assert.Equal(t, parts, message.GetAllBytes(resMsgs[0]))
   253  
   254  	conf.SQL.Query = "SELECT * FROM footable WHERE foo = $1;"
   255  	conf.SQL.Args = []string{
   256  		"${! json(\"foo\") }",
   257  	}
   258  	conf.SQL.ResultCodec = "json_array"
   259  	s, err = NewSQL(conf, nil, log.Noop(), metrics.Noop())
   260  	require.NoError(t, err)
   261  
   262  	parts = [][]byte{
   263  		[]byte(`{"foo":"foo1"}`),
   264  		[]byte(`{"foo":"foo2"}`),
   265  	}
   266  
   267  	resMsgs, response = s.ProcessMessage(message.New(parts))
   268  	require.Nil(t, response)
   269  	require.Len(t, resMsgs, 1)
   270  
   271  	expParts := [][]byte{
   272  		[]byte(`[{"bar":11,"baz":"baz1","foo":"foo1"}]`),
   273  		[]byte(`[{"bar":12,"baz":"baz2","foo":"foo2"}]`),
   274  	}
   275  	assert.Equal(t, expParts, message.GetAllBytes(resMsgs[0]))
   276  }
   277  
   278  func testSQLPostgresDeprecated(t *testing.T, dsn string) {
   279  	conf := NewConfig()
   280  	conf.Type = TypeSQL
   281  	conf.SQL.Driver = "postgres"
   282  	conf.SQL.DSN = dsn
   283  	conf.SQL.Query = "INSERT INTO footable (foo, bar, baz) VALUES ($1, $2, $3);"
   284  	conf.SQL.Args = []string{
   285  		"${! json(\"foo\").from(1) }",
   286  		"${! json(\"bar\").from(1) }",
   287  		"${! json(\"baz\").from(1) }",
   288  	}
   289  
   290  	s, err := NewSQL(conf, nil, log.Noop(), metrics.Noop())
   291  	if err != nil {
   292  		t.Fatal(err)
   293  	}
   294  
   295  	parts := [][]byte{
   296  		[]byte(`{"foo":"foo3","bar":13,"baz":"baz3"}`),
   297  		[]byte(`{"foo":"foo4","bar":14,"baz":"baz4"}`),
   298  	}
   299  
   300  	resMsgs, response := s.ProcessMessage(message.New(parts))
   301  	if response != nil {
   302  		if response.Error() != nil {
   303  			t.Fatal(response.Error())
   304  		}
   305  		t.Fatal("Expected nil response")
   306  	}
   307  	if len(resMsgs) != 1 {
   308  		t.Fatalf("Wrong resulting msgs: %v != %v", len(resMsgs), 1)
   309  	}
   310  	if act, exp := message.GetAllBytes(resMsgs[0]), parts; !reflect.DeepEqual(exp, act) {
   311  		t.Fatalf("Wrong result: %s != %s", act, exp)
   312  	}
   313  
   314  	conf.SQL.Query = "SELECT * FROM footable WHERE foo = $1;"
   315  	conf.SQL.Args = []string{
   316  		"${! json(\"foo\").from(1) }",
   317  	}
   318  	conf.SQL.ResultCodec = "json_array"
   319  	s, err = NewSQL(conf, nil, log.Noop(), metrics.Noop())
   320  	if err != nil {
   321  		t.Fatal(err)
   322  	}
   323  
   324  	resMsgs, response = s.ProcessMessage(message.New(parts))
   325  	if response != nil {
   326  		if response.Error() != nil {
   327  			t.Fatal(response.Error())
   328  		}
   329  		t.Fatal("Expected nil response")
   330  	}
   331  	if len(resMsgs) != 1 {
   332  		t.Fatalf("Wrong resulting msgs: %v != %v", len(resMsgs), 1)
   333  	}
   334  	expParts := [][]byte{
   335  		[]byte(`[{"bar":14,"baz":"baz4","foo":"foo4"}]`),
   336  	}
   337  	if act, exp := message.GetAllBytes(resMsgs[0]), expParts; !reflect.DeepEqual(exp, act) {
   338  		t.Fatalf("Wrong result: %s != %s", act, exp)
   339  	}
   340  }
   341  
   342  func SQLMySQLIntegration(t *testing.T) {
   343  	t.Parallel()
   344  
   345  	pool, err := dockertest.NewPool("")
   346  	if err != nil {
   347  		t.Skipf("Could not connect to docker: %s", err)
   348  	}
   349  
   350  	resource, err := pool.RunWithOptions(&dockertest.RunOptions{
   351  		Repository:   "mysql",
   352  		ExposedPorts: []string{"3306/tcp"},
   353  		Env: []string{
   354  			"MYSQL_USER=testuser",
   355  			"MYSQL_PASSWORD=testpass",
   356  			"MYSQL_DATABASE=testdb",
   357  			"MYSQL_RANDOM_ROOT_PASSWORD=yes",
   358  		},
   359  	})
   360  	if err != nil {
   361  		t.Fatalf("Could not start resource: %s", err)
   362  	}
   363  
   364  	t.Cleanup(func() {
   365  		if err = pool.Purge(resource); err != nil {
   366  			t.Logf("Failed to clean up docker resource: %v", err)
   367  		}
   368  	})
   369  
   370  	var db *sql.DB
   371  	t.Cleanup(func() {
   372  		if db != nil {
   373  			db.Close()
   374  		}
   375  	})
   376  
   377  	dsn := fmt.Sprintf("testuser:testpass@tcp(localhost:%v)/testdb", resource.GetPort("3306/tcp"))
   378  	if err = pool.Retry(func() error {
   379  		var dberr error
   380  		if db, dberr = sql.Open("mysql", dsn); dberr != nil {
   381  			return dberr
   382  		}
   383  		if dberr = db.Ping(); dberr != nil {
   384  			db.Close()
   385  			db = nil
   386  			return dberr
   387  		}
   388  		return nil
   389  	}); err != nil {
   390  		t.Fatalf("Could not connect to docker resource: %s", err)
   391  	}
   392  
   393  	t.Run("testSQLMySQLArgsMapping", func(t *testing.T) {
   394  		testSQLMySQLArgsMapping(t, db, dsn)
   395  	})
   396  	t.Run("testSQLMySQLDynamicQueries", func(t *testing.T) {
   397  		testSQLMySQLDynamicQueries(t, db, dsn)
   398  	})
   399  	t.Run("testSQLMySQLArgs", func(t *testing.T) {
   400  		testSQLMySQLArgs(t, db, dsn)
   401  	})
   402  	t.Run("testSQLMySQLDeprecated", func(t *testing.T) {
   403  		testSQLMySQLDeprecated(t, db, dsn)
   404  	})
   405  }
   406  
   407  func testSQLMySQLArgsMapping(t *testing.T, db *sql.DB, dsn string) {
   408  	_, err := db.Exec(`create table footable (
   409    foo varchar(50) not null,
   410    bar integer not null,
   411    baz varchar(50) not null,
   412    primary key (foo)
   413  );`)
   414  	require.NoError(t, err)
   415  
   416  	conf := NewConfig()
   417  	conf.Type = TypeSQL
   418  	conf.SQL.Driver = "mysql"
   419  	conf.SQL.DataSourceName = dsn
   420  	conf.SQL.Query = "INSERT INTO footable (foo, bar, baz) VALUES (?, ?, ?);"
   421  	conf.SQL.ArgsMapping = `[ this.foo, this.bar, this.baz ]`
   422  
   423  	s, err := NewSQL(conf, nil, log.Noop(), metrics.Noop())
   424  	require.NoError(t, err)
   425  
   426  	parts := [][]byte{
   427  		[]byte(`{"foo":"foo1","bar":11,"baz":"baz1"}`),
   428  		[]byte(`{"foo":"foo2","bar":12,"baz":"baz2"}`),
   429  	}
   430  
   431  	resMsgs, response := s.ProcessMessage(message.New(parts))
   432  	require.Nil(t, response)
   433  	require.Len(t, resMsgs, 1)
   434  	assert.Equal(t, parts, message.GetAllBytes(resMsgs[0]))
   435  
   436  	conf.SQL.Query = "SELECT * FROM footable WHERE foo = ?;"
   437  	conf.SQL.ArgsMapping = `[ this.foo ]`
   438  	conf.SQL.ResultCodec = "json_array"
   439  	s, err = NewSQL(conf, nil, log.Noop(), metrics.Noop())
   440  	require.NoError(t, err)
   441  
   442  	parts = [][]byte{
   443  		[]byte(`{"foo":"foo1"}`),
   444  		[]byte(`{"foo":"foo2"}`),
   445  	}
   446  
   447  	resMsgs, response = s.ProcessMessage(message.New(parts))
   448  	require.Nil(t, response)
   449  	require.Len(t, resMsgs, 1)
   450  
   451  	expParts := [][]byte{
   452  		[]byte(`[{"bar":11,"baz":"baz1","foo":"foo1"}]`),
   453  		[]byte(`[{"bar":12,"baz":"baz2","foo":"foo2"}]`),
   454  	}
   455  	assert.Equal(t, expParts, message.GetAllBytes(resMsgs[0]))
   456  }
   457  
   458  func testSQLMySQLDynamicQueries(t *testing.T, db *sql.DB, dsn string) {
   459  	_, err := db.Exec(`create table bartable (
   460    foo varchar(50) not null,
   461    bar integer not null,
   462    baz varchar(50) not null,
   463    primary key (foo)
   464  );`)
   465  	require.NoError(t, err)
   466  
   467  	conf := NewConfig()
   468  	conf.Type = TypeSQL
   469  	conf.SQL.Driver = "mysql"
   470  	conf.SQL.DataSourceName = dsn
   471  	conf.SQL.Query = `${! json("query") }`
   472  	conf.SQL.UnsafeDynamicQuery = true
   473  	conf.SQL.ArgsMapping = `[ this.foo, this.bar, this.baz ]`
   474  
   475  	s, err := NewSQL(conf, nil, log.Noop(), metrics.Noop())
   476  	require.NoError(t, err)
   477  
   478  	parts := [][]byte{
   479  		[]byte(`{"query":"INSERT INTO bartable (foo, bar, baz) VALUES (?, ?, ?);","foo":"foo1","bar":11,"baz":"baz1"}`),
   480  		[]byte(`{"query":"INSERT INTO bartable (foo, bar, baz) VALUES (?, ?, ?);","foo":"foo2","bar":12,"baz":"baz2"}`),
   481  	}
   482  
   483  	resMsgs, response := s.ProcessMessage(message.New(parts))
   484  	require.Nil(t, response)
   485  	require.Len(t, resMsgs, 1)
   486  	assert.Equal(t, parts, message.GetAllBytes(resMsgs[0]))
   487  
   488  	conf.SQL.Query = `${! json("query") }`
   489  	conf.SQL.ArgsMapping = `[ this.foo ]`
   490  	conf.SQL.UnsafeDynamicQuery = true
   491  	conf.SQL.ResultCodec = "json_array"
   492  	s, err = NewSQL(conf, nil, log.Noop(), metrics.Noop())
   493  	require.NoError(t, err)
   494  
   495  	parts = [][]byte{
   496  		[]byte(`{"query":"SELECT * FROM bartable WHERE foo = ?;","foo":"foo1"}`),
   497  		[]byte(`{"query":"SELECT * FROM bartable WHERE foo = ?;","foo":"foo2"}`),
   498  	}
   499  
   500  	resMsgs, response = s.ProcessMessage(message.New(parts))
   501  	require.Nil(t, response)
   502  	require.Len(t, resMsgs, 1)
   503  
   504  	expParts := [][]byte{
   505  		[]byte(`[{"bar":11,"baz":"baz1","foo":"foo1"}]`),
   506  		[]byte(`[{"bar":12,"baz":"baz2","foo":"foo2"}]`),
   507  	}
   508  	assert.Equal(t, expParts, message.GetAllBytes(resMsgs[0]))
   509  }
   510  
   511  func testSQLMySQLArgs(t *testing.T, db *sql.DB, dsn string) {
   512  	_, err := db.Exec(`create table baztable (
   513    foo varchar(50) not null,
   514    bar integer not null,
   515    baz varchar(50) not null,
   516    primary key (foo)
   517  );`)
   518  	require.NoError(t, err)
   519  
   520  	conf := NewConfig()
   521  	conf.Type = TypeSQL
   522  	conf.SQL.Driver = "mysql"
   523  	conf.SQL.DataSourceName = dsn
   524  	conf.SQL.Query = "INSERT INTO baztable (foo, bar, baz) VALUES (?, ?, ?);"
   525  	conf.SQL.Args = []string{
   526  		"${! json(\"foo\") }",
   527  		"${! json(\"bar\") }",
   528  		"${! json(\"baz\") }",
   529  	}
   530  
   531  	s, err := NewSQL(conf, nil, log.Noop(), metrics.Noop())
   532  	require.NoError(t, err)
   533  
   534  	parts := [][]byte{
   535  		[]byte(`{"foo":"foo1","bar":11,"baz":"baz1"}`),
   536  		[]byte(`{"foo":"foo2","bar":12,"baz":"baz2"}`),
   537  	}
   538  
   539  	resMsgs, response := s.ProcessMessage(message.New(parts))
   540  	require.Nil(t, response)
   541  	require.Len(t, resMsgs, 1)
   542  	assert.Equal(t, parts, message.GetAllBytes(resMsgs[0]))
   543  
   544  	conf.SQL.Query = "SELECT * FROM baztable WHERE foo = ?;"
   545  	conf.SQL.Args = []string{
   546  		"${! json(\"foo\") }",
   547  	}
   548  	conf.SQL.ResultCodec = "json_array"
   549  	s, err = NewSQL(conf, nil, log.Noop(), metrics.Noop())
   550  	require.NoError(t, err)
   551  
   552  	parts = [][]byte{
   553  		[]byte(`{"foo":"foo1"}`),
   554  		[]byte(`{"foo":"foo2"}`),
   555  	}
   556  
   557  	resMsgs, response = s.ProcessMessage(message.New(parts))
   558  	require.Nil(t, response)
   559  	require.Len(t, resMsgs, 1)
   560  
   561  	expParts := [][]byte{
   562  		[]byte(`[{"bar":11,"baz":"baz1","foo":"foo1"}]`),
   563  		[]byte(`[{"bar":12,"baz":"baz2","foo":"foo2"}]`),
   564  	}
   565  	assert.Equal(t, expParts, message.GetAllBytes(resMsgs[0]))
   566  }
   567  
   568  func testSQLMySQLDeprecated(t *testing.T, db *sql.DB, dsn string) {
   569  	_, err := db.Exec(`create table buztable (
   570    foo varchar(50) not null,
   571    bar integer not null,
   572    baz varchar(50) not null,
   573    primary key (foo)
   574  );`)
   575  	require.NoError(t, err)
   576  
   577  	conf := NewConfig()
   578  	conf.Type = TypeSQL
   579  	conf.SQL.Driver = "mysql"
   580  	conf.SQL.DSN = dsn
   581  	conf.SQL.Query = "INSERT INTO buztable (foo, bar, baz) VALUES (?, ?, ?);"
   582  	conf.SQL.Args = []string{
   583  		"${! json(\"foo\").from(1) }",
   584  		"${! json(\"bar\").from(1) }",
   585  		"${! json(\"baz\").from(1) }",
   586  	}
   587  
   588  	s, err := NewSQL(conf, nil, log.Noop(), metrics.Noop())
   589  	if err != nil {
   590  		t.Fatal(err)
   591  	}
   592  
   593  	parts := [][]byte{
   594  		[]byte(`{"foo":"foo3","bar":13,"baz":"baz3"}`),
   595  		[]byte(`{"foo":"foo4","bar":14,"baz":"baz4"}`),
   596  	}
   597  
   598  	resMsgs, response := s.ProcessMessage(message.New(parts))
   599  	if response != nil {
   600  		if response.Error() != nil {
   601  			t.Fatal(response.Error())
   602  		}
   603  		t.Fatal("Expected nil response")
   604  	}
   605  	if len(resMsgs) != 1 {
   606  		t.Fatalf("Wrong resulting msgs: %v != %v", len(resMsgs), 1)
   607  	}
   608  	if act, exp := message.GetAllBytes(resMsgs[0]), parts; !reflect.DeepEqual(exp, act) {
   609  		t.Fatalf("Wrong result: %s != %s", act, exp)
   610  	}
   611  
   612  	conf.SQL.Query = "SELECT * FROM buztable WHERE foo = ?;"
   613  	conf.SQL.Args = []string{
   614  		"${! json(\"foo\").from(1) }",
   615  	}
   616  	conf.SQL.ResultCodec = "json_array"
   617  	s, err = NewSQL(conf, nil, log.Noop(), metrics.Noop())
   618  	if err != nil {
   619  		t.Fatal(err)
   620  	}
   621  
   622  	resMsgs, response = s.ProcessMessage(message.New(parts))
   623  	if response != nil {
   624  		if response.Error() != nil {
   625  			t.Fatal(response.Error())
   626  		}
   627  		t.Fatal("Expected nil response")
   628  	}
   629  	if len(resMsgs) != 1 {
   630  		t.Fatalf("Wrong resulting msgs: %v != %v", len(resMsgs), 1)
   631  	}
   632  	expParts := [][]byte{
   633  		[]byte(`[{"bar":14,"baz":"baz4","foo":"foo4"}]`),
   634  	}
   635  	if act, exp := message.GetAllBytes(resMsgs[0]), expParts; !reflect.DeepEqual(exp, act) {
   636  		t.Fatalf("Wrong result: %s != %s", act, exp)
   637  	}
   638  }
   639  
   640  func SQLMSSQLIntegration(t *testing.T) {
   641  	t.Parallel()
   642  
   643  	pool, err := dockertest.NewPool("")
   644  	if err != nil {
   645  		t.Skipf("Could not connect to docker: %s", err)
   646  	}
   647  
   648  	testPassword := "ins4n3lyStrongP4ssword"
   649  	resource, err := pool.RunWithOptions(&dockertest.RunOptions{
   650  		Repository:   "mcr.microsoft.com/mssql/server",
   651  		ExposedPorts: []string{"1433/tcp"},
   652  		Env: []string{
   653  			"ACCEPT_EULA=Y",
   654  			"SA_PASSWORD=" + testPassword,
   655  		},
   656  	})
   657  	if err != nil {
   658  		t.Fatalf("Could not start resource: %s", err)
   659  	}
   660  
   661  	t.Cleanup(func() {
   662  		if err = pool.Purge(resource); err != nil {
   663  			t.Logf("Failed to clean up docker resource: %v", err)
   664  		}
   665  	})
   666  	dsn := fmt.Sprintf("sqlserver://sa:"+testPassword+"@localhost:%v?database=master", resource.GetPort("1433/tcp"))
   667  	if err = pool.Retry(func() error {
   668  		db, dberr := sql.Open("mssql", dsn)
   669  		if dberr != nil {
   670  			return dberr
   671  		}
   672  		if dberr = db.Ping(); err != nil {
   673  			return dberr
   674  		}
   675  		if _, dberr = db.Exec(`create table footable (
   676    foo varchar(50) not null,
   677    bar integer not null,
   678    baz varchar(50) not null,
   679    primary key (foo)
   680  		);`); dberr != nil {
   681  			return dberr
   682  		}
   683  		return nil
   684  	}); err != nil {
   685  		t.Fatalf("Could not connect to docker resource: %s", err)
   686  	}
   687  
   688  	t.Run("testSQLMSSQLArgsMapping", func(t *testing.T) {
   689  		testSQLMSSQLArgsMapping(t, dsn)
   690  	})
   691  	t.Run("testSQLMSSQLArgs", func(t *testing.T) {
   692  		testSQLMSSQLArgs(t, dsn)
   693  	})
   694  	t.Run("testSQLMSSQLDeprecated", func(t *testing.T) {
   695  		testSQLMSSQLDeprecated(t, dsn)
   696  	})
   697  }
   698  
   699  func testSQLMSSQLArgsMapping(t *testing.T, dsn string) {
   700  	conf := NewConfig()
   701  	conf.Type = TypeSQL
   702  	conf.SQL.Driver = "mssql"
   703  	conf.SQL.DataSourceName = dsn
   704  	conf.SQL.Query = "INSERT INTO footable (foo, bar, baz) VALUES ($1, $2, $3);"
   705  	conf.SQL.ArgsMapping = `[ this.foo, this.bar, this.baz ]`
   706  
   707  	s, err := NewSQL(conf, nil, log.Noop(), metrics.Noop())
   708  	require.NoError(t, err)
   709  
   710  	parts := [][]byte{
   711  		[]byte(`{"foo":"foo1","bar":11,"baz":"baz1"}`),
   712  		[]byte(`{"foo":"foo2","bar":12,"baz":"baz2"}`),
   713  	}
   714  
   715  	resMsgs, response := s.ProcessMessage(message.New(parts))
   716  	require.Nil(t, response)
   717  	require.Len(t, resMsgs, 1)
   718  	assert.Equal(t, parts, message.GetAllBytes(resMsgs[0]))
   719  
   720  	conf.SQL.Query = "SELECT * FROM footable WHERE foo = $1;"
   721  	conf.SQL.ArgsMapping = `[ this.foo ]`
   722  	conf.SQL.ResultCodec = "json_array"
   723  	s, err = NewSQL(conf, nil, log.Noop(), metrics.Noop())
   724  	require.NoError(t, err)
   725  
   726  	parts = [][]byte{
   727  		[]byte(`{"foo":"foo1"}`),
   728  		[]byte(`{"foo":"foo2"}`),
   729  	}
   730  
   731  	resMsgs, response = s.ProcessMessage(message.New(parts))
   732  	require.Nil(t, response)
   733  	require.Len(t, resMsgs, 1)
   734  
   735  	expParts := [][]byte{
   736  		[]byte(`[{"bar":11,"baz":"baz1","foo":"foo1"}]`),
   737  		[]byte(`[{"bar":12,"baz":"baz2","foo":"foo2"}]`),
   738  	}
   739  	assert.Equal(t, expParts, message.GetAllBytes(resMsgs[0]))
   740  }
   741  
   742  func testSQLMSSQLArgs(t *testing.T, dsn string) {
   743  	conf := NewConfig()
   744  	conf.Type = TypeSQL
   745  	conf.SQL.Driver = "mssql"
   746  	conf.SQL.DataSourceName = dsn
   747  	conf.SQL.Query = "INSERT INTO footable (foo, bar, baz) VALUES ($1, $2, $3);"
   748  	conf.SQL.Args = []string{
   749  		"${! json(\"foo\") }",
   750  		"${! json(\"bar\") }",
   751  		"${! json(\"baz\") }",
   752  	}
   753  
   754  	s, err := NewSQL(conf, nil, log.Noop(), metrics.Noop())
   755  	require.NoError(t, err)
   756  
   757  	parts := [][]byte{
   758  		[]byte(`{"foo":"foo1","bar":11,"baz":"baz1"}`),
   759  		[]byte(`{"foo":"foo2","bar":12,"baz":"baz2"}`),
   760  	}
   761  
   762  	resMsgs, response := s.ProcessMessage(message.New(parts))
   763  	require.Nil(t, response)
   764  	require.Len(t, resMsgs, 1)
   765  	assert.Equal(t, parts, message.GetAllBytes(resMsgs[0]))
   766  
   767  	conf.SQL.Query = "SELECT * FROM footable WHERE foo = $1;"
   768  	conf.SQL.Args = []string{
   769  		"${! json(\"foo\") }",
   770  	}
   771  	conf.SQL.ResultCodec = "json_array"
   772  	s, err = NewSQL(conf, nil, log.Noop(), metrics.Noop())
   773  	require.NoError(t, err)
   774  
   775  	parts = [][]byte{
   776  		[]byte(`{"foo":"foo1"}`),
   777  		[]byte(`{"foo":"foo2"}`),
   778  	}
   779  
   780  	resMsgs, response = s.ProcessMessage(message.New(parts))
   781  	require.Nil(t, response)
   782  	require.Len(t, resMsgs, 1)
   783  
   784  	expParts := [][]byte{
   785  		[]byte(`[{"bar":11,"baz":"baz1","foo":"foo1"}]`),
   786  		[]byte(`[{"bar":12,"baz":"baz2","foo":"foo2"}]`),
   787  	}
   788  	assert.Equal(t, expParts, message.GetAllBytes(resMsgs[0]))
   789  }
   790  
   791  func testSQLMSSQLDeprecated(t *testing.T, dsn string) {
   792  	conf := NewConfig()
   793  	conf.Type = TypeSQL
   794  	conf.SQL.Driver = "mssql"
   795  	conf.SQL.DSN = dsn
   796  	conf.SQL.Query = "INSERT INTO footable (foo, bar, baz) VALUES ($1, $2, $3);"
   797  	conf.SQL.Args = []string{
   798  		"${! json(\"foo\").from(1) }",
   799  		"${! json(\"bar\").from(1) }",
   800  		"${! json(\"baz\").from(1) }",
   801  	}
   802  
   803  	s, err := NewSQL(conf, nil, log.Noop(), metrics.Noop())
   804  	if err != nil {
   805  		t.Fatal(err)
   806  	}
   807  
   808  	parts := [][]byte{
   809  		[]byte(`{"foo":"foo3","bar":13,"baz":"baz3"}`),
   810  		[]byte(`{"foo":"foo4","bar":14,"baz":"baz4"}`),
   811  	}
   812  
   813  	resMsgs, response := s.ProcessMessage(message.New(parts))
   814  	if response != nil {
   815  		if response.Error() != nil {
   816  			t.Fatal(response.Error())
   817  		}
   818  		t.Fatal("Expected nil response")
   819  	}
   820  	if len(resMsgs) != 1 {
   821  		t.Fatalf("Wrong resulting msgs: %v != %v", len(resMsgs), 1)
   822  	}
   823  	if act, exp := message.GetAllBytes(resMsgs[0]), parts; !reflect.DeepEqual(exp, act) {
   824  		t.Fatalf("Wrong result: %s != %s", act, exp)
   825  	}
   826  
   827  	conf.SQL.Query = "SELECT * FROM footable WHERE foo = $1;"
   828  	conf.SQL.Args = []string{
   829  		"${! json(\"foo\").from(1) }",
   830  	}
   831  	conf.SQL.ResultCodec = "json_array"
   832  	s, err = NewSQL(conf, nil, log.Noop(), metrics.Noop())
   833  	if err != nil {
   834  		t.Fatal(err)
   835  	}
   836  
   837  	resMsgs, response = s.ProcessMessage(message.New(parts))
   838  	if response != nil {
   839  		if response.Error() != nil {
   840  			t.Fatal(response.Error())
   841  		}
   842  		t.Fatal("Expected nil response")
   843  	}
   844  	if len(resMsgs) != 1 {
   845  		t.Fatalf("Wrong resulting msgs: %v != %v", len(resMsgs), 1)
   846  	}
   847  	expParts := [][]byte{
   848  		[]byte(`[{"bar":14,"baz":"baz4","foo":"foo4"}]`),
   849  	}
   850  	if act, exp := message.GetAllBytes(resMsgs[0]), expParts; !reflect.DeepEqual(exp, act) {
   851  		t.Fatalf("Wrong result: %s != %s", act, exp)
   852  	}
   853  }