github.com/siglens/siglens@v0.0.0-20240328180423-f7ce9ae441ed/pkg/segment/aggregations/evalaggs.go (about) 1 /* 2 Copyright 2023. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package aggregations 18 19 import ( 20 "fmt" 21 "math" 22 "sort" 23 "strings" 24 25 "github.com/siglens/siglens/pkg/segment/structs" 26 "github.com/siglens/siglens/pkg/segment/utils" 27 ) 28 29 func ComputeAggEvalForMinOrMax(measureAgg *structs.MeasureAggregator, sstMap map[string]*structs.SegStats, measureResults map[string]utils.CValueEnclosure, isMin bool) error { 30 fields := measureAgg.ValueColRequest.GetFields() 31 if len(fields) != 1 { 32 return fmt.Errorf("ComputeAggEvalForMinOrMax: Incorrect number of fields for aggCol: %v", measureAgg.String()) 33 } 34 35 sst, ok := sstMap[fields[0]] 36 if !ok { 37 return fmt.Errorf("ComputeAggEvalForMinOrMax: applyAggOpOnSegments sstMap was nil for aggCol %v", measureAgg.MeasureCol) 38 } 39 fieldToValue := make(map[string]utils.CValueEnclosure) 40 41 edgeValue := -1.7976931348623157e+308 42 if isMin { 43 edgeValue = math.MaxFloat64 44 } 45 46 for _, eVal := range sst.Records { 47 fieldToValue[fields[0]] = *eVal 48 boolResult, err := measureAgg.ValueColRequest.BooleanExpr.Evaluate(fieldToValue) 49 if err != nil { 50 return fmt.Errorf("ComputeAggEvalForMinOrMax: there are some errors in the eval function that is inside the min/max function: %v", err) 51 } 52 53 if boolResult { 54 eValFloat, err := eVal.GetFloatValue() 55 if err != nil { 56 return fmt.Errorf("ComputeAggEvalForMinOrMax: can not get the float value: %v", err) 57 } 58 // Keep maximum and minimum values 59 if (isMin && eValFloat < edgeValue) || (!isMin && eValFloat > edgeValue) { 60 edgeValue = eValFloat 61 } 62 } 63 } 64 enclosure, exists := measureResults[measureAgg.String()] 65 if !exists { 66 67 cVal := -1.7976931348623157e+308 68 if isMin { 69 cVal = math.MaxFloat64 70 } 71 72 enclosure = utils.CValueEnclosure{ 73 Dtype: utils.SS_DT_FLOAT, 74 CVal: cVal, 75 } 76 measureResults[measureAgg.String()] = enclosure 77 } 78 79 eValFloat, err := enclosure.GetFloatValue() 80 if err != nil { 81 return fmt.Errorf("ComputeAggEvalForMinOrMax: Attempted to perform aggregate min(), but the column %s is not a float value", fields[0]) 82 } 83 84 if (isMin && eValFloat > edgeValue) || (!isMin && eValFloat < edgeValue) { 85 enclosure.CVal = edgeValue 86 measureResults[measureAgg.String()] = enclosure 87 } 88 return nil 89 } 90 91 func ComputeAggEvalForRange(measureAgg *structs.MeasureAggregator, sstMap map[string]*structs.SegStats, measureResults map[string]utils.CValueEnclosure, runningEvalStats map[string]interface{}) error { 92 fields := measureAgg.ValueColRequest.GetFields() 93 if len(fields) != 1 { 94 return fmt.Errorf("ComputeAggEvalForRange: Incorrect number of fields for aggCol: %v", measureAgg.String()) 95 } 96 97 sst, ok := sstMap[fields[0]] 98 if !ok { 99 return fmt.Errorf("ComputeAggEvalForRange: applyAggOpOnSegments sstMap was nil for aggCol %v", measureAgg.MeasureCol) 100 } 101 fieldToValue := make(map[string]utils.CValueEnclosure) 102 103 maxVal := -1.7976931348623157e+308 104 minVal := math.MaxFloat64 105 106 for _, eVal := range sst.Records { 107 fieldToValue[fields[0]] = *eVal 108 boolResult, err := measureAgg.ValueColRequest.BooleanExpr.Evaluate(fieldToValue) 109 if err != nil { 110 return fmt.Errorf("ComputeAggEvalForRange: there are some errors in the eval function that is inside the range function: %v", err) 111 } 112 113 if boolResult { 114 eValFloat, err := eVal.GetFloatValue() 115 if err != nil { 116 return fmt.Errorf("ComputeAggEvalForRange: can not get the float value: %v", err) 117 } 118 // Keep maximum and minimum values 119 if eValFloat < minVal { 120 minVal = eValFloat 121 } 122 if eValFloat > maxVal { 123 maxVal = eValFloat 124 } 125 } 126 } 127 128 rangeStat := &structs.RangeStat{} 129 rangeStatVal, exists := runningEvalStats[measureAgg.String()] 130 if !exists { 131 rangeStat.Min = minVal 132 rangeStat.Max = maxVal 133 } else { 134 rangeStat = rangeStatVal.(*structs.RangeStat) 135 if rangeStat.Min > minVal { 136 rangeStat.Min = minVal 137 } 138 if rangeStat.Max < maxVal { 139 rangeStat.Max = maxVal 140 } 141 } 142 runningEvalStats[measureAgg.String()] = rangeStat 143 rangeVal := rangeStat.Max - rangeStat.Min 144 145 enclosure, exists := measureResults[measureAgg.String()] 146 if !exists { 147 enclosure = utils.CValueEnclosure{ 148 Dtype: utils.SS_DT_FLOAT, 149 CVal: rangeVal, 150 } 151 measureResults[measureAgg.String()] = enclosure 152 } 153 154 eValFloat, err := enclosure.GetFloatValue() 155 if err != nil { 156 return fmt.Errorf("ComputeAggEvalForRange: Attempted to perform aggregate min(), but the column %s is not a float value", fields[0]) 157 } 158 159 if eValFloat < rangeVal { 160 enclosure.CVal = rangeVal 161 measureResults[measureAgg.String()] = enclosure 162 } 163 return nil 164 } 165 166 func ComputeAggEvalForSum(measureAgg *structs.MeasureAggregator, sstMap map[string]*structs.SegStats, measureResults map[string]utils.CValueEnclosure) error { 167 fields := measureAgg.ValueColRequest.GetFields() 168 if len(fields) != 1 { 169 return fmt.Errorf("ComputeAggEvalForSum: Incorrect number of fields for aggCol: %v", measureAgg.String()) 170 } 171 172 sst, ok := sstMap[fields[0]] 173 if !ok { 174 return fmt.Errorf("ComputeAggEvalForSum: applyAggOpOnSegments sstMap was nil for aggCol %v", measureAgg.MeasureCol) 175 } 176 fieldToValue := make(map[string]utils.CValueEnclosure) 177 178 sumVal := float64(0) 179 180 for _, eVal := range sst.Records { 181 fieldToValue[fields[0]] = *eVal 182 boolResult, err := measureAgg.ValueColRequest.BooleanExpr.Evaluate(fieldToValue) 183 if err != nil { 184 return fmt.Errorf("ComputeAggEvalForSum: there are some errors in the eval function that is inside the sum function: %v", err) 185 } 186 187 if boolResult { 188 eValFloat, err := eVal.GetFloatValue() 189 if err != nil { 190 return fmt.Errorf("ComputeAggEvalForSum: can not get the float value: %v", err) 191 } 192 sumVal += eValFloat 193 } 194 } 195 enclosure, exists := measureResults[measureAgg.String()] 196 if !exists { 197 enclosure = utils.CValueEnclosure{ 198 Dtype: utils.SS_DT_FLOAT, 199 CVal: float64(0), 200 } 201 measureResults[measureAgg.String()] = enclosure 202 } 203 204 eValFloat, err := enclosure.GetFloatValue() 205 if err != nil { 206 return fmt.Errorf("ComputeAggEvalForSum: Attempted to perform aggregate min(), but the column %s is not a float value", fields[0]) 207 } 208 209 enclosure.CVal = eValFloat + sumVal 210 measureResults[measureAgg.String()] = enclosure 211 212 return nil 213 } 214 215 func ComputeAggEvalForCount(measureAgg *structs.MeasureAggregator, sstMap map[string]*structs.SegStats, measureResults map[string]utils.CValueEnclosure) error { 216 217 countVal := int64(0) 218 fields := measureAgg.ValueColRequest.GetFields() 219 if len(fields) == 0 { 220 return fmt.Errorf("ComputeAggEvalForCount: Incorrect number of fields for aggCol: %v", measureAgg.String()) 221 } 222 223 sst, ok := sstMap[fields[0]] 224 if !ok { 225 return fmt.Errorf("ComputeAggEvalForCount: applyAggOpOnSegments sstMap was nil for aggCol %v", measureAgg.MeasureCol) 226 } 227 228 length := len(sst.Records) 229 for i := 0; i < length; i++ { 230 fieldToValue := make(map[string]utils.CValueEnclosure) 231 // Initialize fieldToValue 232 for _, field := range fields { 233 sst, ok := sstMap[field] 234 if !ok { 235 return fmt.Errorf("ComputeAggEvalForCount: applyAggOpOnSegments sstMap was nil for aggCol %v", measureAgg.MeasureCol) 236 } 237 238 if i >= len(sst.Records) { 239 return fmt.Errorf("ComputeAggEvalForCount: Incorrect length of field: %v for aggCol: %v", field, measureAgg.String()) 240 } 241 fieldToValue[field] = *sst.Records[i] 242 } 243 244 boolResult, err := measureAgg.ValueColRequest.BooleanExpr.Evaluate(fieldToValue) 245 if err != nil { 246 return fmt.Errorf("ComputeAggEvalForCount: there are some errors in the eval function that is inside the count function: %v", err) 247 } 248 249 if boolResult { 250 countVal++ 251 } 252 } 253 254 enclosure, exists := measureResults[measureAgg.String()] 255 if !exists { 256 enclosure = utils.CValueEnclosure{ 257 Dtype: utils.SS_DT_SIGNED_NUM, 258 CVal: int64(0), 259 } 260 measureResults[measureAgg.String()] = enclosure 261 } 262 263 eVal, err := enclosure.GetValue() 264 if err != nil { 265 return fmt.Errorf("ComputeAggEvalForCount: Attempted to perform aggregate min(), but the column %s is not a float value", fields[0]) 266 } 267 268 enclosure.CVal = eVal.(int64) + countVal 269 measureResults[measureAgg.String()] = enclosure 270 271 return nil 272 } 273 274 func ComputeAggEvalForAvg(measureAgg *structs.MeasureAggregator, sstMap map[string]*structs.SegStats, measureResults map[string]utils.CValueEnclosure, runningEvalStats map[string]interface{}) error { 275 fields := measureAgg.ValueColRequest.GetFields() 276 if len(fields) != 1 { 277 return fmt.Errorf("ComputeAggEvalForAvg: Incorrect number of fields for aggCol: %v", measureAgg.String()) 278 } 279 280 sst, ok := sstMap[fields[0]] 281 if !ok { 282 return fmt.Errorf("ComputeAggEvalForAvg: applyAggOpOnSegments sstMap was nil for aggCol %v", measureAgg.MeasureCol) 283 } 284 fieldToValue := make(map[string]utils.CValueEnclosure) 285 286 sumVal := float64(0) 287 countVal := int64(0) 288 for _, eVal := range sst.Records { 289 fieldToValue[fields[0]] = *eVal 290 boolResult, err := measureAgg.ValueColRequest.BooleanExpr.Evaluate(fieldToValue) 291 if err != nil { 292 return fmt.Errorf("ComputeAggEvalForAvg: there are some errors in the eval function that is inside the avg function: %v", err) 293 } 294 295 if boolResult { 296 eValFloat, err := eVal.GetFloatValue() 297 if err != nil { 298 return fmt.Errorf("ComputeAggEvalForAvg: can not get the float value: %v", err) 299 } 300 sumVal += eValFloat 301 countVal++ 302 } 303 } 304 305 avgStat := &structs.AvgStat{} 306 avgStatVal, exists := runningEvalStats[measureAgg.String()] 307 if !exists { 308 avgStat.Sum = sumVal 309 avgStat.Count = countVal 310 } else { 311 avgStat = avgStatVal.(*structs.AvgStat) 312 avgStat.Sum += sumVal 313 avgStat.Count += countVal 314 } 315 runningEvalStats[measureAgg.String()] = avgStat 316 317 measureResults[measureAgg.String()] = utils.CValueEnclosure{ 318 Dtype: utils.SS_DT_FLOAT, 319 CVal: avgStat.Sum / float64(avgStat.Count), 320 } 321 322 return nil 323 } 324 325 func ComputeAggEvalForCardinality(measureAgg *structs.MeasureAggregator, sstMap map[string]*structs.SegStats, measureResults map[string]utils.CValueEnclosure, runningEvalStats map[string]interface{}) error { 326 fields := measureAgg.ValueColRequest.GetFields() 327 if len(fields) == 0 { 328 return fmt.Errorf("ComputeAggEvalForCount: Incorrect number of fields for aggCol: %v", measureAgg.String()) 329 } 330 331 sst, ok := sstMap[fields[0]] 332 if !ok { 333 return fmt.Errorf("ComputeAggEvalForCount: applyAggOpOnSegments sstMap was nil for aggCol %v", measureAgg.MeasureCol) 334 } 335 336 strSet := make(map[string]struct{}, 0) 337 valuesStrSetVal, exists := runningEvalStats[measureAgg.String()] 338 if !exists { 339 runningEvalStats[measureAgg.String()] = make(map[string]struct{}, 0) 340 } else { 341 strSet, ok = valuesStrSetVal.(map[string]struct{}) 342 if !ok { 343 return fmt.Errorf("ComputeAggEvalForCardinality: can not convert strSet for aggCol: %v", measureAgg.String()) 344 } 345 } 346 347 length := len(sst.Records) 348 for i := 0; i < length; i++ { 349 fieldToValue := make(map[string]utils.CValueEnclosure) 350 // Initialize fieldToValue 351 for _, field := range fields { 352 sst, ok := sstMap[field] 353 if !ok { 354 return fmt.Errorf("ComputeAggEvalForCount: applyAggOpOnSegments sstMap was nil for aggCol %v", measureAgg.MeasureCol) 355 } 356 357 if i >= len(sst.Records) { 358 return fmt.Errorf("ComputeAggEvalForCount: Incorrect length of field: %v for aggCol: %v", field, measureAgg.String()) 359 } 360 fieldToValue[field] = *sst.Records[i] 361 } 362 363 cellValueStr, err := measureAgg.ValueColRequest.EvaluateToString(fieldToValue) 364 if err != nil { 365 return fmt.Errorf("ComputeAggEvalForCount: there are some errors in the eval function that is inside the cardinality function: %v", err) 366 } 367 368 strSet[cellValueStr] = struct{}{} 369 } 370 371 measureResults[measureAgg.String()] = utils.CValueEnclosure{ 372 Dtype: utils.SS_DT_SIGNED_NUM, 373 CVal: int64(len(strSet)), 374 } 375 376 return nil 377 } 378 379 func ComputeAggEvalForValues(measureAgg *structs.MeasureAggregator, sstMap map[string]*structs.SegStats, measureResults map[string]utils.CValueEnclosure, strSet map[string]struct{}) error { 380 fields := measureAgg.ValueColRequest.GetFields() 381 if len(fields) == 0 { 382 return fmt.Errorf("ComputeAggEvalForValues: Incorrect number of fields for aggCol: %v", measureAgg.String()) 383 } 384 385 sst, ok := sstMap[fields[0]] 386 if !ok { 387 return fmt.Errorf("ComputeAggEvalForValues: applyAggOpOnSegments sstMap was nil for aggCol %v", measureAgg.MeasureCol) 388 } 389 390 length := len(sst.Records) 391 for i := 0; i < length; i++ { 392 fieldToValue := make(map[string]utils.CValueEnclosure) 393 // Initialize fieldToValue 394 for _, field := range fields { 395 sst, ok := sstMap[field] 396 if !ok { 397 return fmt.Errorf("ComputeAggEvalForValues: applyAggOpOnSegments sstMap was nil for aggCol %v", measureAgg.MeasureCol) 398 } 399 400 if i >= len(sst.Records) { 401 return fmt.Errorf("ComputeAggEvalForValues: Incorrect length of field: %v for aggCol: %v", field, measureAgg.String()) 402 } 403 fieldToValue[field] = *sst.Records[i] 404 } 405 406 cellValueStr, err := measureAgg.ValueColRequest.EvaluateToString(fieldToValue) 407 if err != nil { 408 return fmt.Errorf("ComputeAggEvalForValues: there are some errors in the eval function that is inside the values function: %v", err) 409 } 410 411 strSet[cellValueStr] = struct{}{} 412 } 413 414 uniqueStrings := make([]string, 0) 415 for str := range strSet { 416 uniqueStrings = append(uniqueStrings, str) 417 } 418 sort.Strings(uniqueStrings) 419 420 strVal := strings.Join(uniqueStrings, " ") 421 measureResults[measureAgg.String()] = utils.CValueEnclosure{ 422 Dtype: utils.SS_DT_STRING, 423 CVal: strVal, 424 } 425 426 return nil 427 } 428 429 func AddMeasureAggInRunningStatsForCount(m *structs.MeasureAggregator, allConvertedMeasureOps *[]*structs.MeasureAggregator, allReverseIndex *[]int, colToIdx map[string][]int, idx int) (int, error) { 430 431 fields := m.ValueColRequest.GetFields() 432 if len(fields) == 0 { 433 return idx, fmt.Errorf("AddMeasureAggInRunningStatsForCount: Incorrect number of fields for aggCol: %v", m.String()) 434 } 435 436 // Use the index of agg to map to the corresponding index of the runningStats result, so that we can determine which index of the result set contains the result we need. 437 *allReverseIndex = append(*allReverseIndex, idx) 438 for _, field := range fields { 439 if _, ok := colToIdx[field]; !ok { 440 colToIdx[field] = make([]int, 0) 441 } 442 colToIdx[field] = append(colToIdx[field], idx) 443 *allConvertedMeasureOps = append(*allConvertedMeasureOps, &structs.MeasureAggregator{ 444 MeasureCol: field, 445 MeasureFunc: utils.Count, 446 ValueColRequest: m.ValueColRequest, 447 StrEnc: m.StrEnc, 448 }) 449 idx++ 450 } 451 return idx, nil 452 } 453 454 func AddMeasureAggInRunningStatsForAvg(m *structs.MeasureAggregator, allConvertedMeasureOps *[]*structs.MeasureAggregator, allReverseIndex *[]int, colToIdx map[string][]int, idx int) (int, error) { 455 456 fields := m.ValueColRequest.GetFields() 457 if len(fields) != 1 { 458 return idx, fmt.Errorf("AddMeasureAggInRunningStatsForAvg: Incorrect number of fields for aggCol: %v", m.String()) 459 } 460 field := fields[0] 461 462 if _, ok := colToIdx[field]; !ok { 463 colToIdx[field] = make([]int, 0) 464 } 465 466 // We need to use sum() and count() to calculate the avg() 467 *allReverseIndex = append(*allReverseIndex, idx) 468 colToIdx[field] = append(colToIdx[field], idx) 469 *allConvertedMeasureOps = append(*allConvertedMeasureOps, &structs.MeasureAggregator{ 470 MeasureCol: field, 471 MeasureFunc: utils.Sum, 472 ValueColRequest: m.ValueColRequest, 473 StrEnc: m.StrEnc, 474 }) 475 idx++ 476 477 *allReverseIndex = append(*allReverseIndex, idx) 478 colToIdx[field] = append(colToIdx[field], idx) 479 *allConvertedMeasureOps = append(*allConvertedMeasureOps, &structs.MeasureAggregator{ 480 MeasureCol: field, 481 MeasureFunc: utils.Count, 482 ValueColRequest: m.ValueColRequest, 483 StrEnc: m.StrEnc, 484 }) 485 idx++ 486 return idx, nil 487 } 488 489 // Record the index of range() in runningStats; the index is idx 490 // To calculate the range(), we need both the min() and max(), which require two columns to store them 491 // Since it is the runningStats not the stats for results, we can use one extra col to store the min/max 492 // idx stores the result of min, and idx+1 stores the result of max. 493 func AddMeasureAggInRunningStatsForRange(m *structs.MeasureAggregator, allConvertedMeasureOps *[]*structs.MeasureAggregator, allReverseIndex *[]int, colToIdx map[string][]int, idx int) (int, error) { 494 495 measureCol := m.MeasureCol 496 if m.ValueColRequest != nil { 497 fields := m.ValueColRequest.GetFields() 498 if len(fields) != 1 { 499 return idx, fmt.Errorf("AddMeasureAggInRunningStatsForRange: Incorrect number of fields for aggCol: %v", m.String()) 500 } 501 measureCol = fields[0] 502 } 503 504 if _, ok := colToIdx[measureCol]; !ok { 505 colToIdx[measureCol] = make([]int, 0) 506 } 507 *allReverseIndex = append(*allReverseIndex, idx) 508 colToIdx[measureCol] = append(colToIdx[measureCol], idx) 509 *allConvertedMeasureOps = append(*allConvertedMeasureOps, &structs.MeasureAggregator{ 510 MeasureCol: measureCol, 511 MeasureFunc: utils.Min, 512 ValueColRequest: m.ValueColRequest, 513 StrEnc: m.StrEnc, 514 }) 515 idx++ 516 517 *allReverseIndex = append(*allReverseIndex, idx) 518 colToIdx[measureCol] = append(colToIdx[measureCol], idx) 519 *allConvertedMeasureOps = append(*allConvertedMeasureOps, &structs.MeasureAggregator{ 520 MeasureCol: measureCol, 521 MeasureFunc: utils.Max, 522 ValueColRequest: m.ValueColRequest, 523 StrEnc: m.StrEnc, 524 }) 525 idx++ 526 527 return idx, nil 528 } 529 530 func AddMeasureAggInRunningStatsForValuesOrCardinality(m *structs.MeasureAggregator, allConvertedMeasureOps *[]*structs.MeasureAggregator, allReverseIndex *[]int, colToIdx map[string][]int, idx int) (int, error) { 531 532 fields := m.ValueColRequest.GetFields() 533 if len(fields) == 0 { 534 return idx, fmt.Errorf("AddMeasureAggInRunningStatsForValuesOrCardinality: Incorrect number of fields for aggCol: %v", m.String()) 535 } 536 537 // Use the index of agg to map to the corresponding index of the runningStats result, so that we can determine which index of the result set contains the result we need. 538 *allReverseIndex = append(*allReverseIndex, idx) 539 for _, field := range fields { 540 if _, ok := colToIdx[field]; !ok { 541 colToIdx[field] = make([]int, 0) 542 } 543 colToIdx[field] = append(colToIdx[field], idx) 544 *allConvertedMeasureOps = append(*allConvertedMeasureOps, &structs.MeasureAggregator{ 545 MeasureCol: field, 546 MeasureFunc: utils.Values, 547 ValueColRequest: m.ValueColRequest, 548 StrEnc: m.StrEnc, 549 }) 550 idx++ 551 } 552 return idx, nil 553 } 554 555 // Determine if cols used by eval statements or not 556 func DetermineAggColUsage(measureAgg *structs.MeasureAggregator, aggCols map[string]bool, aggColUsage map[string]utils.AggColUsageMode, valuesUsage map[string]bool) { 557 if measureAgg.ValueColRequest != nil { 558 for _, field := range measureAgg.ValueColRequest.GetFields() { 559 aggCols[field] = true 560 colUsage, exists := aggColUsage[field] 561 if exists { 562 if colUsage == utils.NoEvalUsage { 563 aggColUsage[field] = utils.BothUsage 564 } 565 } else { 566 aggColUsage[field] = utils.WithEvalUsage 567 } 568 } 569 measureAgg.MeasureCol = measureAgg.StrEnc 570 } else { 571 aggCols[measureAgg.MeasureCol] = true 572 if measureAgg.MeasureFunc == utils.Values { 573 valuesUsage[measureAgg.MeasureCol] = true 574 } 575 576 colUsage, exists := aggColUsage[measureAgg.MeasureCol] 577 if exists { 578 if colUsage == utils.WithEvalUsage { 579 aggColUsage[measureAgg.MeasureCol] = utils.BothUsage 580 } 581 } else { 582 aggColUsage[measureAgg.MeasureCol] = utils.NoEvalUsage 583 } 584 } 585 }