github.com/hyperledger/burrow@v0.34.5-0.20220512172541-77f09336001d/vent/sqlsol/projection_test.go (about)

     1  package sqlsol_test
     2  
     3  import (
     4  	"testing"
     5  
     6  	"github.com/hyperledger/burrow/vent/sqlsol"
     7  	"github.com/hyperledger/burrow/vent/test"
     8  	"github.com/hyperledger/burrow/vent/types"
     9  	"github.com/stretchr/testify/require"
    10  )
    11  
    12  var columns = types.DefaultSQLColumnNames
    13  
    14  func TestNewProjection(t *testing.T) {
    15  	t.Run("returns an error if the json is malformed", func(t *testing.T) {
    16  		badJSON := test.BadJSONConfFile(t)
    17  
    18  		byteValue := []byte(badJSON)
    19  		_, err := sqlsol.NewProjectionFromBytes(byteValue)
    20  		require.Error(t, err)
    21  	})
    22  
    23  	t.Run("returns an error if needed json fields are missing", func(t *testing.T) {
    24  		missingFields := test.MissingFieldsJSONConfFile(t)
    25  
    26  		byteValue := []byte(missingFields)
    27  		_, err := sqlsol.NewProjectionFromBytes(byteValue)
    28  		require.Error(t, err)
    29  	})
    30  
    31  	t.Run("successfully builds table structure based on json events config", func(t *testing.T) {
    32  		tableStruct, err := sqlsol.NewProjectionFromBytes([]byte(test.GoodJSONConfFile(t)))
    33  		require.NoError(t, err)
    34  
    35  		// columns map
    36  		tableName := "UserAccounts"
    37  		col, err := tableStruct.GetColumn(tableName, "username")
    38  		require.NoError(t, err)
    39  		require.Equal(t, false, col.Primary)
    40  		require.Equal(t, types.SQLColumnTypeText, col.Type)
    41  		require.Equal(t, "username", col.Name)
    42  
    43  		col, err = tableStruct.GetColumn(tableName, "address")
    44  		require.NoError(t, err)
    45  		require.Equal(t, true, col.Primary)
    46  		require.Equal(t, types.SQLColumnTypeVarchar, col.Type)
    47  		require.Equal(t, "address", col.Name)
    48  
    49  		col, err = tableStruct.GetColumn(tableName, columns.TxHash)
    50  		require.NoError(t, err)
    51  		require.Equal(t, false, col.Primary)
    52  		require.Equal(t, types.SQLColumnTypeText, col.Type)
    53  
    54  		col, err = tableStruct.GetColumn(tableName, columns.EventName)
    55  		require.NoError(t, err)
    56  		require.Equal(t, false, col.Primary)
    57  		require.Equal(t, types.SQLColumnTypeText, col.Type)
    58  	})
    59  
    60  	t.Run("returns an error if the event type of a given column is unknown", func(t *testing.T) {
    61  		typeUnknownJSON := test.UnknownTypeJSONConfFile(t)
    62  
    63  		byteValue := []byte(typeUnknownJSON)
    64  		_, err := sqlsol.NewProjectionFromBytes(byteValue)
    65  		require.Error(t, err)
    66  	})
    67  
    68  	t.Run("returns an error if there are duplicated column names for a given table in json file", func(t *testing.T) {
    69  		duplicatedColNameJSON := test.DuplicatedColNameJSONConfFile(t)
    70  
    71  		byteValue := []byte(duplicatedColNameJSON)
    72  		_, err := sqlsol.NewProjectionFromBytes(byteValue)
    73  		require.Error(t, err)
    74  	})
    75  
    76  }
    77  
    78  func TestGetColumn(t *testing.T) {
    79  	projection, err := sqlsol.NewProjectionFromBytes([]byte(test.GoodJSONConfFile(t)))
    80  	require.NoError(t, err)
    81  
    82  	t.Run("successfully gets the mapping column info for a given table & column name", func(t *testing.T) {
    83  		column, err := projection.GetColumn("TEST_TABLE", "Block")
    84  		require.NoError(t, err)
    85  		require.Equal(t, "Block", column.Name)
    86  		require.Equal(t, types.SQLColumnTypeNumeric, column.Type)
    87  		require.Equal(t, false, column.Primary)
    88  
    89  		column, err = projection.GetColumn("TEST_TABLE", "Instance")
    90  		require.NoError(t, err)
    91  		require.Equal(t, "Instance", column.Name)
    92  		require.Equal(t, types.SQLColumnTypeNumeric, column.Type)
    93  		require.Equal(t, false, column.Primary)
    94  
    95  	})
    96  
    97  	t.Run("unsuccessfully gets the mapping column info for a non existent table name", func(t *testing.T) {
    98  		_, err := projection.GetColumn("NOT_EXISTS", "userName")
    99  		require.Error(t, err)
   100  	})
   101  
   102  	t.Run("unsuccessfully gets the mapping column info for a non existent column name", func(t *testing.T) {
   103  		_, err := projection.GetColumn("UpdateUserAccount", "NOT_EXISTS")
   104  		require.Error(t, err)
   105  	})
   106  }
   107  
   108  func TestGetTables(t *testing.T) {
   109  	goodJSON := test.GoodJSONConfFile(t)
   110  
   111  	byteValue := []byte(goodJSON)
   112  	tableStruct, _ := sqlsol.NewProjectionFromBytes(byteValue)
   113  
   114  	t.Run("successfully returns event tables structures", func(t *testing.T) {
   115  		tables := tableStruct.Tables
   116  		require.Equal(t, 2, len(tables))
   117  		require.Equal(t, "UserAccounts", tables["UserAccounts"].Name)
   118  
   119  	})
   120  }
   121  
   122  func TestNewProjectionFromBytes(t *testing.T) {
   123  	goodJSON := test.GoodJSONConfFile(t)
   124  
   125  	byteValue := []byte(goodJSON)
   126  	tableStruct, _ := sqlsol.NewProjectionFromBytes(byteValue)
   127  
   128  	t.Run("successfully returns event specification structures", func(t *testing.T) {
   129  		spec := tableStruct.Spec
   130  		require.Equal(t, 2, len(spec))
   131  		require.Equal(t, "LOG0 = 'UserAccounts'", spec[0].Filter)
   132  		require.Equal(t, "UserAccounts", spec[0].TableName)
   133  
   134  		require.Equal(t, "Log1Text = 'EVENT_TEST'", spec[1].Filter)
   135  		require.Equal(t, "TEST_TABLE", spec[1].TableName)
   136  	})
   137  }
   138  
   139  func TestProjectionSpec(t *testing.T) {
   140  	tableName := "BurnNotices"
   141  	spec := types.ProjectionSpec{
   142  		{
   143  			TableName: tableName,
   144  			Filter:    "LOG1Text = 'CIA/burn'",
   145  			FieldMappings: []*types.EventFieldMapping{
   146  				{
   147  					Field:      "codename",
   148  					Type:       types.EventFieldTypeString,
   149  					ColumnName: "name",
   150  					Notify:     []string{"burn"},
   151  					Primary:    true,
   152  				},
   153  				{
   154  					Field:      "burn",
   155  					Type:       types.EventFieldTypeBool,
   156  					ColumnName: "burnt",
   157  					Notify:     []string{"burn"},
   158  				},
   159  				{
   160  					Field:      "dairy",
   161  					Type:       types.EventFieldTypeString,
   162  					ColumnName: "coffee_milk",
   163  					Notify:     []string{"mrs_doyle"},
   164  				},
   165  				{
   166  					Field:      "datetime",
   167  					Type:       types.EventFieldTypeInt,
   168  					ColumnName: "time_changed",
   169  					Notify:     []string{"last_heard", "mrs_doyle"},
   170  				},
   171  			},
   172  		},
   173  		{
   174  			TableName: tableName,
   175  			Filter:    "LOG1Text = 'MI5/burn'",
   176  			FieldMappings: []*types.EventFieldMapping{
   177  				{
   178  					Field:      "codename",
   179  					Type:       types.EventFieldTypeString,
   180  					ColumnName: "name",
   181  					Notify:     []string{"burn"},
   182  					Primary:    true,
   183  				},
   184  				{
   185  					Field:      "unreliable",
   186  					Type:       types.EventFieldTypeBool,
   187  					ColumnName: "burnt",
   188  					Notify:     []string{"burn"},
   189  				},
   190  				{
   191  					Field:      "sugars",
   192  					Type:       types.EventFieldTypeInt,
   193  					ColumnName: "tea_sugars",
   194  					Notify:     []string{"mrs_doyle"},
   195  				},
   196  				{
   197  					Field:      "milk",
   198  					Type:       types.EventFieldTypeBool,
   199  					ColumnName: "tea_milk",
   200  					Notify:     []string{"mrs_doyle"},
   201  				},
   202  				{
   203  					Field:      "datetime",
   204  					Type:       types.EventFieldTypeInt,
   205  					ColumnName: "time_changed",
   206  					Notify:     []string{"last_heard", "mrs_doyle"},
   207  				},
   208  			},
   209  		},
   210  	}
   211  	projection, err := sqlsol.NewProjection(spec)
   212  	require.NoError(t, err, "burn and unreliable field mappings should unify to single column")
   213  
   214  	require.Equal(t, []string{"burnt", "name"}, projection.Tables[tableName].NotifyChannels["burn"])
   215  
   216  	// Notify sugars on the burn channel
   217  	field := spec[1].GetFieldMapping("sugars")
   218  	field.Notify = append(field.Notify, "burn")
   219  
   220  	projection, err = sqlsol.NewProjection(spec)
   221  	require.NoError(t, err)
   222  	require.Equal(t, []string{"burnt", "name", "tea_sugars"}, projection.Tables[tableName].NotifyChannels["burn"])
   223  
   224  	// Create a column conflict between burn and unreliable fields (both map to burnt so the SQL column def must be identical)
   225  	field = spec[1].GetFieldMapping("unreliable")
   226  	field.Primary = !field.Primary
   227  	_, err = sqlsol.NewProjection(spec)
   228  	require.Error(t, err)
   229  }
   230  
   231  func TestWithNoPrimaryKey(t *testing.T) {
   232  	tableName := "BurnNotices"
   233  	spec := types.ProjectionSpec{
   234  		{
   235  			TableName:         tableName,
   236  			Filter:            "LOG1Text = 'CIA/burn'",
   237  			DeleteMarkerField: "__DELETE__",
   238  			FieldMappings: []*types.EventFieldMapping{
   239  				{
   240  					Field:      "codename",
   241  					Type:       types.EventFieldTypeString,
   242  					ColumnName: "name",
   243  					Notify:     []string{"burn"},
   244  				},
   245  				{
   246  					Field:      "burn",
   247  					Type:       types.EventFieldTypeBool,
   248  					ColumnName: "burnt",
   249  					Notify:     []string{"burn"},
   250  				},
   251  				{
   252  					Field:      "dairy",
   253  					Type:       types.EventFieldTypeString,
   254  					ColumnName: "coffee_milk",
   255  					Notify:     []string{"mrs_doyle"},
   256  				},
   257  				{
   258  					Field:      "datetime",
   259  					Type:       types.EventFieldTypeInt,
   260  					ColumnName: "time_changed",
   261  					Notify:     []string{"last_heard", "mrs_doyle"},
   262  				},
   263  			},
   264  		},
   265  	}
   266  
   267  	_, err := sqlsol.NewProjection(spec)
   268  	require.Error(t, err, "no DeleteMarkerField allowed if no primary key on")
   269  
   270  	// Try again and now check that the right fields are primary
   271  	spec[0].DeleteMarkerField = ""
   272  
   273  	projection, err := sqlsol.NewProjection(spec)
   274  	require.NoError(t, err, "projection with no primary key should be allowed")
   275  
   276  	for _, c := range projection.Tables[tableName].Columns {
   277  		switch c.Name {
   278  		case columns.ChainID:
   279  			require.Equal(t, true, c.Primary)
   280  		case columns.Height:
   281  			require.Equal(t, true, c.Primary)
   282  		case columns.TxIndex:
   283  			require.Equal(t, true, c.Primary)
   284  		case columns.EventIndex:
   285  			require.Equal(t, true, c.Primary)
   286  		default:
   287  			require.Equal(t, false, c.Primary)
   288  		}
   289  	}
   290  
   291  	spec = types.ProjectionSpec{
   292  		{
   293  			TableName: tableName,
   294  			Filter:    "LOG1Text = 'CIA/burn'",
   295  			FieldMappings: []*types.EventFieldMapping{
   296  				{
   297  					Field:      "codename",
   298  					Type:       types.EventFieldTypeString,
   299  					ColumnName: "name",
   300  					Notify:     []string{"burn"},
   301  					Primary:    true,
   302  				},
   303  				{
   304  					Field:      "burn",
   305  					Type:       types.EventFieldTypeBool,
   306  					ColumnName: "burnt",
   307  					Notify:     []string{"burn"},
   308  				},
   309  				{
   310  					Field:      "dairy",
   311  					Type:       types.EventFieldTypeString,
   312  					ColumnName: "coffee_milk",
   313  					Notify:     []string{"mrs_doyle"},
   314  				},
   315  				{
   316  					Field:      "datetime",
   317  					Type:       types.EventFieldTypeInt,
   318  					ColumnName: "time_changed",
   319  					Notify:     []string{"last_heard", "mrs_doyle"},
   320  				},
   321  			},
   322  		},
   323  	}
   324  
   325  	projection, err = sqlsol.NewProjection(spec)
   326  	require.NoError(t, err, "projection with primary key should be allowed")
   327  
   328  	for _, c := range projection.Tables[tableName].Columns {
   329  		require.Equal(t, c.Name == "name", c.Primary)
   330  	}
   331  }