go.temporal.io/server@v1.23.0/common/persistence/sql/sqlplugin/tests/history_node.go (about)

     1  // The MIT License
     2  //
     3  // Copyright (c) 2020 Temporal Technologies Inc.  All rights reserved.
     4  //
     5  // Copyright (c) 2020 Uber Technologies, Inc.
     6  //
     7  // Permission is hereby granted, free of charge, to any person obtaining a copy
     8  // of this software and associated documentation files (the "Software"), to deal
     9  // in the Software without restriction, including without limitation the rights
    10  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    11  // copies of the Software, and to permit persons to whom the Software is
    12  // furnished to do so, subject to the following conditions:
    13  //
    14  // The above copyright notice and this permission notice shall be included in
    15  // all copies or substantial portions of the Software.
    16  //
    17  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    18  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    19  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    20  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    21  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    22  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    23  // THE SOFTWARE.
    24  
    25  package tests
    26  
    27  import (
    28  	"math"
    29  	"math/rand"
    30  	"sort"
    31  	"testing"
    32  
    33  	"github.com/stretchr/testify/require"
    34  	"github.com/stretchr/testify/suite"
    35  
    36  	"go.temporal.io/server/common/persistence/sql"
    37  	"go.temporal.io/server/common/persistence/sql/sqlplugin"
    38  	"go.temporal.io/server/common/primitives"
    39  	"go.temporal.io/server/common/shuffle"
    40  )
    41  
    42  type (
    43  	historyNodeSuite struct {
    44  		suite.Suite
    45  		*require.Assertions
    46  
    47  		store sqlplugin.HistoryNode
    48  	}
    49  )
    50  
    51  const (
    52  	testHistoryNodeEncoding = "random encoding"
    53  )
    54  
    55  var (
    56  	testHistoryNodeData = []byte("random history node data")
    57  )
    58  
    59  func NewHistoryNodeSuite(
    60  	t *testing.T,
    61  	store sqlplugin.HistoryNode,
    62  ) *historyNodeSuite {
    63  	return &historyNodeSuite{
    64  		Assertions: require.New(t),
    65  		store:      store,
    66  	}
    67  }
    68  
    69  func (s *historyNodeSuite) SetupSuite() {
    70  
    71  }
    72  
    73  func (s *historyNodeSuite) TearDownSuite() {
    74  
    75  }
    76  
    77  func (s *historyNodeSuite) SetupTest() {
    78  	s.Assertions = require.New(s.T())
    79  }
    80  
    81  func (s *historyNodeSuite) TearDownTest() {
    82  
    83  }
    84  
    85  func (s *historyNodeSuite) TestInsert_Success() {
    86  	shardID := rand.Int31()
    87  	treeID := primitives.NewUUID()
    88  	branchID := primitives.NewUUID()
    89  	nodeID := rand.Int63()
    90  	prevTransactionID := rand.Int63()
    91  	transactionID := rand.Int63()
    92  
    93  	node := s.newRandomNodeRow(shardID, treeID, branchID, nodeID, prevTransactionID, transactionID)
    94  	result, err := s.store.InsertIntoHistoryNode(newExecutionContext(), &node)
    95  	s.NoError(err)
    96  	rowsAffected, err := result.RowsAffected()
    97  	s.NoError(err)
    98  	s.Equal(1, int(rowsAffected))
    99  }
   100  
   101  func (s *historyNodeSuite) TestInsert_Fail_Duplicate() {
   102  	shardID := rand.Int31()
   103  	treeID := primitives.NewUUID()
   104  	branchID := primitives.NewUUID()
   105  	nodeID := rand.Int63()
   106  	prevTransactionID := rand.Int63()
   107  	transactionID := rand.Int63()
   108  
   109  	node := s.newRandomNodeRow(shardID, treeID, branchID, nodeID, prevTransactionID, transactionID)
   110  	result, err := s.store.InsertIntoHistoryNode(newExecutionContext(), &node)
   111  	s.NoError(err)
   112  	rowsAffected, err := result.RowsAffected()
   113  	s.NoError(err)
   114  	s.Equal(1, int(rowsAffected))
   115  
   116  	node = s.newRandomNodeRow(shardID, treeID, branchID, nodeID, prevTransactionID, transactionID)
   117  	_, err = s.store.InsertIntoHistoryNode(newExecutionContext(), &node)
   118  	s.NoError(err) // TODO persistence layer should do proper error translation
   119  }
   120  
   121  func (s *historyNodeSuite) TestInsertSelect_Single() {
   122  	pageSize := 100
   123  
   124  	shardID := rand.Int31()
   125  	treeID := primitives.NewUUID()
   126  	branchID := primitives.NewUUID()
   127  	nodeID := int64(1)
   128  	prevTransactionID := rand.Int63()
   129  	transactionID := rand.Int63()
   130  
   131  	node := s.newRandomNodeRow(shardID, treeID, branchID, nodeID, prevTransactionID, transactionID)
   132  	result, err := s.store.InsertIntoHistoryNode(newExecutionContext(), &node)
   133  	s.NoError(err)
   134  	rowsAffected, err := result.RowsAffected()
   135  	s.NoError(err)
   136  	s.Equal(1, int(rowsAffected))
   137  
   138  	selectFilter := sqlplugin.HistoryNodeSelectFilter{
   139  		ShardID:   shardID,
   140  		TreeID:    treeID,
   141  		BranchID:  branchID,
   142  		MinNodeID: nodeID,
   143  		MinTxnID:  sql.MinTxnID,
   144  		MaxNodeID: math.MaxInt64,
   145  		PageSize:  pageSize,
   146  	}
   147  	rows, err := s.store.RangeSelectFromHistoryNode(newExecutionContext(), selectFilter)
   148  	s.NoError(err)
   149  	// NOTE: TxnID is *= -1 within InsertIntoHistoryNode
   150  	node.TxnID = -node.TxnID
   151  	for index := range rows {
   152  		rows[index].ShardID = shardID
   153  		rows[index].TreeID = treeID
   154  		rows[index].BranchID = branchID
   155  	}
   156  	s.Equal([]sqlplugin.HistoryNodeRow{node}, rows)
   157  }
   158  
   159  func (s *historyNodeSuite) TestInsertSelect_Multiple() {
   160  	numNodeIDs := 100
   161  	nodePerNodeID := 2 + rand.Intn(8)
   162  	pageSize := 10 + rand.Intn(10)
   163  
   164  	shardID := rand.Int31()
   165  	treeID := primitives.NewUUID()
   166  	branchID := primitives.NewUUID()
   167  
   168  	nodeID := int64(1)
   169  	minNodeID := nodeID
   170  	maxNodeID := minNodeID + int64(numNodeIDs)
   171  
   172  	var nodes []sqlplugin.HistoryNodeRow
   173  	for i := 0; i < numNodeIDs; i++ {
   174  		for j := 0; j < nodePerNodeID; j++ {
   175  			node := s.newRandomNodeRow(shardID, treeID, branchID, nodeID, rand.Int63(), rand.Int63())
   176  			result, err := s.store.InsertIntoHistoryNode(newExecutionContext(), &node)
   177  			s.NoError(err)
   178  			rowsAffected, err := result.RowsAffected()
   179  			s.NoError(err)
   180  			s.Equal(1, int(rowsAffected))
   181  			nodes = append(nodes, node)
   182  		}
   183  		nodeID++
   184  	}
   185  
   186  	selectFilter := sqlplugin.HistoryNodeSelectFilter{
   187  		ShardID:   shardID,
   188  		TreeID:    treeID,
   189  		BranchID:  branchID,
   190  		MinNodeID: minNodeID,
   191  		MinTxnID:  sql.MinTxnID,
   192  		MaxNodeID: maxNodeID,
   193  		PageSize:  pageSize,
   194  	}
   195  	var rows []sqlplugin.HistoryNodeRow
   196  	for {
   197  		rowsPerPage, err := s.store.RangeSelectFromHistoryNode(newExecutionContext(), selectFilter)
   198  		s.NoError(err)
   199  		rows = append(rows, rowsPerPage...)
   200  
   201  		if len(rowsPerPage) > 0 {
   202  			lastNode := rowsPerPage[len(rowsPerPage)-1]
   203  			selectFilter.MinNodeID = lastNode.NodeID
   204  			selectFilter.MinTxnID = lastNode.TxnID
   205  		} else {
   206  			break
   207  		}
   208  	}
   209  
   210  	// NOTE: TxnID is *= -1 within InsertIntoHistoryNode
   211  	for index := range nodes {
   212  		nodes[index].TxnID = -nodes[index].TxnID
   213  	}
   214  	sort.Slice(nodes, func(i, j int) bool {
   215  		this := nodes[i]
   216  		that := nodes[j]
   217  
   218  		if this.NodeID < that.NodeID {
   219  			return true
   220  		} else if this.NodeID > that.NodeID {
   221  			return false
   222  		}
   223  
   224  		// larger transaction ID means newer
   225  		if this.TxnID < that.TxnID {
   226  			return false
   227  		} else if this.TxnID > that.TxnID {
   228  			return true
   229  		}
   230  
   231  		// same
   232  		return true
   233  	})
   234  	for index := range rows {
   235  		rows[index].ShardID = shardID
   236  		rows[index].TreeID = treeID
   237  		rows[index].BranchID = branchID
   238  	}
   239  	s.Equal(nodes, rows)
   240  }
   241  
   242  func (s *historyNodeSuite) TestDeleteSelect() {
   243  	pageSize := 100
   244  
   245  	shardID := rand.Int31()
   246  	treeID := primitives.NewUUID()
   247  	branchID := primitives.NewUUID()
   248  	nodeID := int64(1)
   249  
   250  	deleteFilter := sqlplugin.HistoryNodeDeleteFilter{
   251  		ShardID:   shardID,
   252  		TreeID:    treeID,
   253  		BranchID:  branchID,
   254  		MinNodeID: nodeID,
   255  	}
   256  	result, err := s.store.RangeDeleteFromHistoryNode(newExecutionContext(), deleteFilter)
   257  	s.NoError(err)
   258  	rowsAffected, err := result.RowsAffected()
   259  	s.NoError(err)
   260  	s.Equal(0, int(rowsAffected))
   261  
   262  	selectFilter := sqlplugin.HistoryNodeSelectFilter{
   263  		ShardID:   shardID,
   264  		TreeID:    treeID,
   265  		BranchID:  branchID,
   266  		MinNodeID: nodeID,
   267  		MinTxnID:  sql.MinTxnID,
   268  		MaxNodeID: math.MaxInt64,
   269  		PageSize:  pageSize,
   270  	}
   271  	rows, err := s.store.RangeSelectFromHistoryNode(newExecutionContext(), selectFilter)
   272  	s.NoError(err)
   273  	for index := range rows {
   274  		rows[index].ShardID = shardID
   275  		rows[index].TreeID = treeID
   276  		rows[index].BranchID = branchID
   277  	}
   278  	s.Equal([]sqlplugin.HistoryNodeRow(nil), rows)
   279  }
   280  
   281  func (s *historyNodeSuite) TestInsertDeleteSelect_Single() {
   282  	pageSize := 100
   283  
   284  	shardID := rand.Int31()
   285  	treeID := primitives.NewUUID()
   286  	branchID := primitives.NewUUID()
   287  	nodeID := int64(1)
   288  	prevTransactionID := rand.Int63()
   289  	transactionID := rand.Int63()
   290  
   291  	node := s.newRandomNodeRow(shardID, treeID, branchID, nodeID, prevTransactionID, transactionID)
   292  	result, err := s.store.InsertIntoHistoryNode(newExecutionContext(), &node)
   293  	s.NoError(err)
   294  	rowsAffected, err := result.RowsAffected()
   295  	s.NoError(err)
   296  	s.Equal(1, int(rowsAffected))
   297  	// transaction ID is *= -1 within InsertIntoHistoryNode
   298  	node.TxnID = -node.TxnID
   299  
   300  	result, err = s.store.DeleteFromHistoryNode(newExecutionContext(), &node)
   301  	s.NoError(err)
   302  	rowsAffected, err = result.RowsAffected()
   303  	s.NoError(err)
   304  	s.Equal(1, int(rowsAffected))
   305  
   306  	selectFilter := sqlplugin.HistoryNodeSelectFilter{
   307  		ShardID:   shardID,
   308  		TreeID:    treeID,
   309  		BranchID:  branchID,
   310  		MinNodeID: nodeID,
   311  		MinTxnID:  sql.MinTxnID,
   312  		MaxNodeID: math.MaxInt64,
   313  		PageSize:  pageSize,
   314  	}
   315  	rows, err := s.store.RangeSelectFromHistoryNode(newExecutionContext(), selectFilter)
   316  	s.NoError(err)
   317  	for index := range rows {
   318  		rows[index].ShardID = shardID
   319  		rows[index].TreeID = treeID
   320  		rows[index].BranchID = branchID
   321  	}
   322  	s.Equal([]sqlplugin.HistoryNodeRow(nil), rows)
   323  }
   324  
   325  func (s *historyNodeSuite) TestInsertDeleteSelect_Multiple() {
   326  	numNodeIDs := 50
   327  	nodePerNodeID := 2
   328  	pageSize := 100
   329  
   330  	shardID := rand.Int31()
   331  	treeID := primitives.NewUUID()
   332  	branchID := primitives.NewUUID()
   333  
   334  	nodeID := int64(1)
   335  	minNodeID := nodeID
   336  
   337  	for i := 0; i < numNodeIDs; i++ {
   338  		for j := 0; j < nodePerNodeID; j++ {
   339  			node := s.newRandomNodeRow(shardID, treeID, branchID, nodeID, rand.Int63(), rand.Int63())
   340  			result, err := s.store.InsertIntoHistoryNode(newExecutionContext(), &node)
   341  			s.NoError(err)
   342  			rowsAffected, err := result.RowsAffected()
   343  			s.NoError(err)
   344  			s.Equal(1, int(rowsAffected))
   345  		}
   346  		nodeID++
   347  	}
   348  
   349  	deleteFilter := sqlplugin.HistoryNodeDeleteFilter{
   350  		ShardID:   shardID,
   351  		TreeID:    treeID,
   352  		BranchID:  branchID,
   353  		MinNodeID: minNodeID,
   354  	}
   355  	result, err := s.store.RangeDeleteFromHistoryNode(newExecutionContext(), deleteFilter)
   356  	s.NoError(err)
   357  	rowsAffected, err := result.RowsAffected()
   358  	s.NoError(err)
   359  	s.Equal(numNodeIDs*nodePerNodeID, int(rowsAffected))
   360  
   361  	selectFilter := sqlplugin.HistoryNodeSelectFilter{
   362  		ShardID:   shardID,
   363  		TreeID:    treeID,
   364  		BranchID:  branchID,
   365  		MinNodeID: nodeID,
   366  		MinTxnID:  sql.MinTxnID,
   367  		MaxNodeID: math.MaxInt64,
   368  		PageSize:  pageSize,
   369  	}
   370  	rows, err := s.store.RangeSelectFromHistoryNode(newExecutionContext(), selectFilter)
   371  	s.NoError(err)
   372  	for index := range rows {
   373  		rows[index].ShardID = shardID
   374  		rows[index].TreeID = treeID
   375  		rows[index].BranchID = branchID
   376  	}
   377  	s.Equal([]sqlplugin.HistoryNodeRow(nil), rows)
   378  }
   379  
   380  func (s *historyNodeSuite) newRandomNodeRow(
   381  	shardID int32,
   382  	treeID primitives.UUID,
   383  	branchID primitives.UUID,
   384  	nodeID int64,
   385  	prevTransactionID int64,
   386  	transactionID int64,
   387  ) sqlplugin.HistoryNodeRow {
   388  	return sqlplugin.HistoryNodeRow{
   389  		ShardID:      shardID,
   390  		TreeID:       treeID,
   391  		BranchID:     branchID,
   392  		NodeID:       nodeID,
   393  		PrevTxnID:    prevTransactionID,
   394  		TxnID:        transactionID,
   395  		Data:         shuffle.Bytes(testHistoryNodeData),
   396  		DataEncoding: testHistoryNodeEncoding,
   397  	}
   398  }