github.com/square/finch@v0.0.0-20240412205204-6530c03e2b96/trx/trx_test.go (about)

     1  // Copyright 2024 Block, Inc.
     2  
     3  package trx_test
     4  
     5  import (
     6  	"testing"
     7  
     8  	"github.com/go-test/deep"
     9  
    10  	"github.com/square/finch"
    11  	"github.com/square/finch/config"
    12  	"github.com/square/finch/data"
    13  	"github.com/square/finch/trx"
    14  )
    15  
    16  var p = map[string]string{}
    17  
    18  func TestLoad_001(t *testing.T) {
    19  	// The most basic test: 1 query, 1 @d, nothing fancy.
    20  	trxList := []config.Trx{
    21  		{
    22  			Name: "001.sql", // must set because we don't call Validate
    23  			File: "../test/trx/001.sql",
    24  			Data: map[string]config.Data{
    25  				"id": {
    26  					Generator: "int",
    27  				},
    28  			},
    29  		},
    30  	}
    31  
    32  	scope := data.NewScope()
    33  	got, err := trx.Load(trxList, scope, p)
    34  	if err != nil {
    35  		t.Fatal(err)
    36  	}
    37  
    38  	expect := &trx.Set{
    39  		Order: []string{"001.sql"},
    40  		Statements: map[string][]*trx.Statement{
    41  			"001.sql": []*trx.Statement{
    42  				{
    43  					Trx:       "001.sql",
    44  					Query:     "select c from t where id=%d",
    45  					Inputs:    []string{"@id"},
    46  					ResultSet: true,
    47  					Calls:     []byte{0},
    48  				},
    49  			},
    50  		},
    51  		Data: &data.Scope{
    52  			Keys: map[string]data.Key{
    53  				"@id": data.Key{
    54  					Name:      "@id",
    55  					Trx:       "001.sql",
    56  					Line:      1,
    57  					Statement: 1,
    58  					Column:    -1,
    59  					Scope:     finch.SCOPE_STATEMENT,
    60  				},
    61  			},
    62  			CopiedAt: map[string]finch.RunLevel{},
    63  		},
    64  		Meta: map[string]trx.Meta{
    65  			"001.sql": {DDL: false},
    66  		},
    67  	}
    68  
    69  	if diff := deep.Equal(got, expect); diff != nil {
    70  		t.Error(diff)
    71  		t.Logf("got: %#v", got)
    72  	}
    73  }
    74  
    75  func TestLoad_002(t *testing.T) {
    76  	// Basic "@d AND @PREV" test: @PREV causes no additional data gen, but it
    77  	// should be interpolated into query like "%d AND %d", and then the 1 data gen
    78  	// should return 2 values./
    79  	trxList := []config.Trx{
    80  		{
    81  			Name: "002.sql", // must set because we don't call Validate
    82  			File: "../test/trx/002.sql",
    83  			Data: map[string]config.Data{
    84  				"d": {
    85  					Generator: "int",
    86  				},
    87  			},
    88  		},
    89  	}
    90  
    91  	scope := data.NewScope()
    92  	got, err := trx.Load(trxList, scope, p)
    93  	if err != nil {
    94  		t.Fatal(err)
    95  	}
    96  
    97  	expect := &trx.Set{
    98  		Order: []string{"002.sql"},
    99  		Statements: map[string][]*trx.Statement{
   100  			"002.sql": []*trx.Statement{
   101  				{
   102  					Trx:       "002.sql",
   103  					Query:     "SELECT c FROM t WHERE id BETWEEN %d AND %d",
   104  					Inputs:    []string{"@d", "@PREV"},
   105  					ResultSet: true,
   106  					Calls:     []byte{0, 0},
   107  				},
   108  			},
   109  		},
   110  		Data: &data.Scope{
   111  			Keys: map[string]data.Key{
   112  				"@d": data.Key{
   113  					Name:      "@d",
   114  					Trx:       "002.sql",
   115  					Line:      2,
   116  					Statement: 1,
   117  					Column:    -1,
   118  					Scope:     finch.SCOPE_STATEMENT,
   119  				},
   120  				// No key for @PREV, but it is in Inputs ^
   121  			},
   122  			CopiedAt: map[string]finch.RunLevel{},
   123  		},
   124  		Meta: map[string]trx.Meta{
   125  			"002.sql": {DDL: false},
   126  		},
   127  	}
   128  
   129  	if diff := deep.Equal(got, expect); diff != nil {
   130  		t.Error(diff)
   131  		t.Logf("got: %#v", got)
   132  	}
   133  }
   134  
   135  func TestLoad_003(t *testing.T) {
   136  	// Basic column ref: one stmt saves a col (output), the other uses it as input
   137  	trxList := []config.Trx{
   138  		{
   139  			Name: "003.sql", // must set because we don't call Validate
   140  			File: "../test/trx/003.sql",
   141  			Data: map[string]config.Data{
   142  				"c": {
   143  					Generator: "str-not-null",
   144  					Params: map[string]string{
   145  						"quote-value": "yes",
   146  					},
   147  				},
   148  			},
   149  		},
   150  	}
   151  
   152  	scope := data.NewScope()
   153  	got, err := trx.Load(trxList, scope, p)
   154  	if err != nil {
   155  		t.Fatal(err)
   156  	}
   157  
   158  	expect := &trx.Set{
   159  		Order: []string{"003.sql"},
   160  		Statements: map[string][]*trx.Statement{
   161  			"003.sql": []*trx.Statement{
   162  				{
   163  					Trx:       "003.sql",
   164  					Query:     "select c from t1 where id=1",
   165  					Inputs:    nil,
   166  					Outputs:   []string{"@c"},
   167  					ResultSet: true,
   168  					// no Calls because this is an output column
   169  				},
   170  				{
   171  					Trx:     "003.sql",
   172  					Query:   "insert into t2 values ('%v')",
   173  					Inputs:  []string{"@c"},
   174  					Outputs: nil,
   175  					Write:   true,
   176  					Calls:   []byte{0},
   177  				},
   178  			},
   179  		},
   180  		Data: &data.Scope{
   181  			Keys: map[string]data.Key{
   182  				"@c": data.Key{
   183  					Name:      "@c",
   184  					Trx:       "003.sql",
   185  					Line:      4,
   186  					Statement: 1,
   187  					Column:    0,
   188  					Scope:     "",
   189  				},
   190  			},
   191  			CopiedAt: map[string]finch.RunLevel{},
   192  		},
   193  		Meta: map[string]trx.Meta{
   194  			"003.sql": {DDL: false},
   195  		},
   196  	}
   197  
   198  	if diff := deep.Equal(got, expect); diff != nil {
   199  		t.Error(diff)
   200  		t.Logf("got: %#v", got)
   201  	}
   202  }
   203  
   204  func TestLoad_copy3(t *testing.T) {
   205  	// -- copy: 3 should yield 3x the same query. copy3-1.sql has the copy: 3
   206  	// mod first, then a prepare mode. copy3-2.sql has the reverse. This is to
   207  	// test that copy works with other mods in any order. Either way, same result:
   208  	expect := &trx.Set{
   209  		Order: []string{"copy3"},
   210  		Statements: map[string][]*trx.Statement{
   211  			"copy3": []*trx.Statement{
   212  				{
   213  					Trx:          "copy3",
   214  					Query:        "select c from t where id=?",
   215  					Inputs:       []string{"@id"},
   216  					ResultSet:    true,
   217  					Prepare:      true,
   218  					PrepareMulti: 3,
   219  					Calls:        []byte{0},
   220  				},
   221  				{
   222  					Trx:          "copy3",
   223  					Query:        "select c from t where id=?",
   224  					Inputs:       []string{"@id"},
   225  					ResultSet:    true,
   226  					Prepare:      true,
   227  					PrepareMulti: 0,
   228  					Calls:        []byte{0},
   229  				},
   230  
   231  				{
   232  					Trx:          "copy3",
   233  					Query:        "select c from t where id=?",
   234  					Inputs:       []string{"@id"},
   235  					ResultSet:    true,
   236  					Prepare:      true,
   237  					PrepareMulti: 0,
   238  					Calls:        []byte{0},
   239  				},
   240  			},
   241  		},
   242  		Data: &data.Scope{
   243  			Keys: map[string]data.Key{
   244  				"@id": data.Key{
   245  					Name:      "@id",
   246  					Trx:       "copy3",
   247  					Line:      4,
   248  					Statement: 1,
   249  					Column:    -1,
   250  					Scope:     finch.SCOPE_TRX,
   251  				},
   252  			},
   253  			CopiedAt: map[string]finch.RunLevel{},
   254  		},
   255  		Meta: map[string]trx.Meta{
   256  			"copy3": {DDL: false},
   257  		},
   258  	}
   259  
   260  	trxList := []config.Trx{
   261  		{
   262  			Name: "copy3", // must set because we don't call Validate
   263  			File: "../test/trx/copy3-1.sql",
   264  			Data: map[string]config.Data{
   265  				"id": {
   266  					Generator: "int",
   267  					Scope:     "trx",
   268  				},
   269  			},
   270  		},
   271  	}
   272  
   273  	scope := data.NewScope()
   274  	got, err := trx.Load(trxList, scope, p)
   275  	if err != nil {
   276  		t.Fatal(err)
   277  	}
   278  	if diff := deep.Equal(got, expect); diff != nil {
   279  		t.Error(diff)
   280  		t.Logf("got: %#v", got)
   281  	}
   282  
   283  	// ----------------------------------------------------------------------
   284  
   285  	trxList = []config.Trx{
   286  		{
   287  			Name: "copy3",
   288  			File: "../test/trx/copy3-2.sql",
   289  			Data: map[string]config.Data{
   290  				"id": {
   291  					Generator: "int",
   292  					Scope:     "trx",
   293  				},
   294  			},
   295  		},
   296  	}
   297  
   298  	scope = data.NewScope()
   299  	got, err = trx.Load(trxList, scope, p)
   300  	if err != nil {
   301  		t.Fatal(err)
   302  	}
   303  	if diff := deep.Equal(got, expect); diff != nil {
   304  		t.Error(diff)
   305  		t.Logf("got: %#v", got)
   306  	}
   307  }
   308  
   309  func TestLoad_COPY_NUMBER(t *testing.T) {
   310  	// /*!copy-number*/ should be replaced with the copy number
   311  	expect := &trx.Set{
   312  		Order: []string{"copyNo"},
   313  		Statements: map[string][]*trx.Statement{
   314  			"copyNo": []*trx.Statement{
   315  				{
   316  					Trx:       "copyNo",
   317  					Query:     "select c from t1 where id=1",
   318  					ResultSet: true,
   319  				},
   320  				{
   321  					Trx:       "copyNo",
   322  					Query:     "select c from t2 where id=1",
   323  					ResultSet: true,
   324  				},
   325  			},
   326  		},
   327  		Data: &data.Scope{
   328  			Keys:     map[string]data.Key{}, // no @d
   329  			CopiedAt: map[string]finch.RunLevel{},
   330  		},
   331  		Meta: map[string]trx.Meta{
   332  			"copyNo": {DDL: false},
   333  		},
   334  	}
   335  
   336  	trxList := []config.Trx{
   337  		{
   338  			Name: "copyNo", // must set because we don't call Validate
   339  			File: "../test/trx/copy-no.sql",
   340  			Data: map[string]config.Data{
   341  				"id": {
   342  					Generator: "int",
   343  					Scope:     "trx",
   344  				},
   345  			},
   346  		},
   347  	}
   348  
   349  	scope := data.NewScope()
   350  	got, err := trx.Load(trxList, scope, p)
   351  	if err != nil {
   352  		t.Fatal(err)
   353  	}
   354  	if diff := deep.Equal(got, expect); diff != nil {
   355  		t.Error(diff)
   356  		t.Logf("got: %#v", got)
   357  	}
   358  }
   359  
   360  func TestLoad_RowScopeCSV(t *testing.T) {
   361  	file := "rowscope-csv.sql"
   362  	trxList := []config.Trx{
   363  		{
   364  			Name: file, // must set because we don't call Validate
   365  			File: "../test/trx/" + file,
   366  			Data: map[string]config.Data{
   367  				"d": {
   368  					Generator: "auto-inc",
   369  				},
   370  			},
   371  		},
   372  	}
   373  
   374  	scope := data.NewScope()
   375  	got, err := trx.Load(trxList, scope, p)
   376  	if err != nil {
   377  		t.Fatal(err)
   378  	}
   379  
   380  	expect := &trx.Set{
   381  		Order: []string{file},
   382  		Statements: map[string][]*trx.Statement{
   383  			file: []*trx.Statement{
   384  				{
   385  					Trx:       file,
   386  					Query:     "SELECT 1 -- (%d, %d %% 1000, '%d', '%d'), (%d, %d %% 1000, '%d', '%d')",
   387  					Inputs:    []string{"@d", "@d", "@d", "@d", "@d", "@d", "@d", "@d"},
   388  					Calls:     []byte{1, 0, 0, 0, 1, 0, 0, 0},
   389  					ResultSet: true,
   390  				},
   391  
   392  				{
   393  					Trx:       file,
   394  					Query:     "SELECT 1 -- (%d, %d %% 1000, '%d', '%d'), (%d, %d %% 1000, '%d', '%d')",
   395  					Inputs:    []string{"@d", "@d", "@d", "@d", "@d", "@d", "@d", "@d"},
   396  					Calls:     []byte{1, 0, 0, 0, 1, 0, 0, 0},
   397  					ResultSet: true,
   398  				},
   399  			},
   400  		},
   401  		Data: &data.Scope{
   402  			Keys: map[string]data.Key{
   403  				"@d": data.Key{
   404  					Name:      "@d",
   405  					Trx:       file,
   406  					Line:      1,
   407  					Statement: 1,
   408  					Column:    -1,
   409  					Scope:     finch.SCOPE_ROW,
   410  				},
   411  			},
   412  			CopiedAt: map[string]finch.RunLevel{},
   413  		},
   414  		Meta: map[string]trx.Meta{
   415  			file: {},
   416  		},
   417  	}
   418  
   419  	if diff := deep.Equal(got, expect); diff != nil {
   420  		t.Error(diff)
   421  		t.Logf("got: %#v", got)
   422  	}
   423  }
   424  
   425  func TestCalls(t *testing.T) {
   426  	type test struct {
   427  		inputs []string
   428  		expect []byte
   429  	}
   430  	callTests := []test{
   431  		{[]string{"@d"}, []byte{0}},
   432  		{[]string{"@d", "@d"}, []byte{0, 0}},
   433  		{[]string{"@d()"}, []byte{1}},
   434  		{[]string{"@d()", "@d"}, []byte{1, 0}},
   435  		{[]string{"@d", "@x", "@d()"}, []byte{0, 0, 1}},
   436  		{[]string{"@d()", "@d()", "@d()"}, []byte{1, 1, 1}},
   437  	}
   438  	for _, c := range callTests {
   439  		got := trx.Calls(c.inputs)
   440  		if diff := deep.Equal(got, c.expect); diff != nil {
   441  			t.Logf("%s -> %v", c.inputs, got)
   442  			t.Error(diff)
   443  		}
   444  	}
   445  }
   446  
   447  func TestRowScope(t *testing.T) {
   448  	s := map[string]bool{
   449  		"@d": true,
   450  		"@x": true,
   451  	}
   452  	type test struct {
   453  		keys   map[string]bool
   454  		in     string
   455  		expect string
   456  	}
   457  	callTests := []test{
   458  		{s, "(@d)", "(@d())"},
   459  		{s, "(@d())", "(@d())"},
   460  		{s, "(@d(), @d)", "(@d(), @d)"},
   461  		{s, "(@d, @d)", "(@d(), @d)"},
   462  		{s, "(@d, @x, @d)", "(@d(), @x(), @d)"},
   463  		{s, "(@d, @x(), @d)", "(@d(), @x(), @d)"},
   464  	}
   465  	for _, c := range callTests {
   466  		got := trx.RowScope(c.keys, c.in)
   467  		if diff := deep.Equal(got, c.expect); diff != nil {
   468  			t.Error(diff)
   469  			t.Logf("%s -> %v", c.in, got)
   470  		}
   471  	}
   472  }