mirror of
https://github.com/akvorado/akvorado.git
synced 2025-12-12 06:24:10 +01:00
common/schema: turns into a component
This is a first step to make it accept configuration. Most of the changes are quite trivial, but I also ran into some difficulties with query columns and filters. They need the schema for parsing, but parsing happens before dependencies are instantiated (and even if it was not the case, parsing is stateless). Therefore, I have added a `Validate()` method that must be called after instantiation. Various bits `panic()` if not validated to ensure we catch all cases. The alternative to make the component manages a global state would have been simpler but it would break once we add the ability to add or disable columns.
This commit is contained in:
121
console/query/column.go
Normal file
121
console/query/column.go
Normal file
@@ -0,0 +1,121 @@
|
||||
// SPDX-FileCopyrightText: 2023 Free Mobile
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
// Package query provides query columns and query filters. These
|
||||
// types are special as they need a schema to be validated.
|
||||
package query
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"akvorado/common/helpers"
|
||||
"akvorado/common/schema"
|
||||
)
|
||||
|
||||
// Column represents a query column. It should be instantiated with NewColumn() or
|
||||
// Unmarshal(), then call Validate().
|
||||
type Column struct {
|
||||
validated bool
|
||||
name string
|
||||
key schema.ColumnKey
|
||||
}
|
||||
|
||||
// Columns is a set of query columns.
|
||||
type Columns []Column
|
||||
|
||||
// NewColumn creates a new column. Validate() should be called before using it.
|
||||
func NewColumn(name string) Column {
|
||||
return Column{name: name}
|
||||
}
|
||||
|
||||
func (qc Column) check() {
|
||||
if !qc.validated {
|
||||
panic("query column not validated")
|
||||
}
|
||||
}
|
||||
|
||||
func (qc Column) String() string {
|
||||
return qc.name
|
||||
}
|
||||
|
||||
// MarshalText turns a column into a string.
|
||||
func (qc Column) MarshalText() ([]byte, error) {
|
||||
return []byte(qc.name), nil
|
||||
}
|
||||
|
||||
// UnmarshalText parses a column. Validate() should be called before use.
|
||||
func (qc *Column) UnmarshalText(input []byte) error {
|
||||
name := string(input)
|
||||
*qc = Column{name: name}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Key returns the key for the column.
|
||||
func (qc *Column) Key() schema.ColumnKey {
|
||||
qc.check()
|
||||
return qc.key
|
||||
}
|
||||
|
||||
// Validate should be called before using the column. We need a schema component
|
||||
// for that.
|
||||
func (qc *Column) Validate(schema *schema.Component) error {
|
||||
if column, ok := schema.LookupColumnByName(qc.name); ok && !column.ConsoleNotDimension {
|
||||
qc.key = column.Key
|
||||
qc.validated = true
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("unknown column name %s", qc.name)
|
||||
}
|
||||
|
||||
// Reverse reverses the column direction
|
||||
func (qc *Column) Reverse(schema *schema.Component) {
|
||||
name := schema.ReverseColumnDirection(qc.Key()).String()
|
||||
reverted := Column{name: name}
|
||||
if reverted.Validate(schema) == nil {
|
||||
*qc = reverted
|
||||
}
|
||||
// No modification otherwise
|
||||
}
|
||||
|
||||
// Reverse reverses the direction of all columns
|
||||
func (qcs Columns) Reverse(schema *schema.Component) {
|
||||
for i := range qcs {
|
||||
qcs[i].Reverse(schema)
|
||||
}
|
||||
}
|
||||
|
||||
// Validate call Validate on each column.
|
||||
func (qcs Columns) Validate(schema *schema.Component) error {
|
||||
for i := range qcs {
|
||||
if err := qcs[i].Validate(schema); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ToSQLSelect transforms a column into an expression to use in SELECT
|
||||
func (qc Column) ToSQLSelect() string {
|
||||
var strValue string
|
||||
switch qc.Key() {
|
||||
case schema.ColumnExporterAddress, schema.ColumnSrcAddr, schema.ColumnDstAddr:
|
||||
strValue = fmt.Sprintf("replaceRegexpOne(IPv6NumToString(%s), '^::ffff:', '')", qc)
|
||||
case schema.ColumnSrcAS, schema.ColumnDstAS, schema.ColumnDst1stAS, schema.ColumnDst2ndAS, schema.ColumnDst3rdAS:
|
||||
strValue = fmt.Sprintf(`concat(toString(%s), ': ', dictGetOrDefault('asns', 'name', %s, '???'))`,
|
||||
qc, qc)
|
||||
case schema.ColumnEType:
|
||||
strValue = fmt.Sprintf(`if(EType = %d, 'IPv4', if(EType = %d, 'IPv6', '???'))`,
|
||||
helpers.ETypeIPv4, helpers.ETypeIPv6)
|
||||
case schema.ColumnProto:
|
||||
strValue = `dictGetOrDefault('protocols', 'name', Proto, '???')`
|
||||
case schema.ColumnInIfSpeed, schema.ColumnOutIfSpeed, schema.ColumnSrcPort, schema.ColumnDstPort, schema.ColumnForwardingStatus, schema.ColumnInIfBoundary, schema.ColumnOutIfBoundary:
|
||||
strValue = fmt.Sprintf("toString(%s)", qc)
|
||||
case schema.ColumnDstASPath:
|
||||
strValue = `arrayStringConcat(DstASPath, ' ')`
|
||||
case schema.ColumnDstCommunities:
|
||||
strValue = `arrayStringConcat(arrayConcat(arrayMap(c -> concat(toString(bitShiftRight(c, 16)), ':', toString(bitAnd(c, 0xffff))), DstCommunities), arrayMap(c -> concat(toString(bitAnd(bitShiftRight(c, 64), 0xffffffff)), ':', toString(bitAnd(bitShiftRight(c, 32), 0xffffffff)), ':', toString(bitAnd(c, 0xffffffff))), DstLargeCommunities)), ' ')`
|
||||
default:
|
||||
strValue = qc.String()
|
||||
}
|
||||
return strValue
|
||||
}
|
||||
Reference in New Issue
Block a user