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 }