gotidb/pkg/engine/file/query.go

341 lines
7.6 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package file
import (
"context"
"encoding/binary"
"fmt"
"io"
"sort"
"time"
"git.pyer.club/kingecg/gotidb/pkg/engine"
)
// queryRaw 实现原始数据查询
func (e *FileEngine) queryRaw(ctx context.Context, query engine.Query) (engine.QueryResult, error) {
result := &engine.TimeSeriesResult{
SeriesID: query.SeriesID,
Points: make([]engine.DataPoint, 0),
}
// 如果指定了SeriesID只查询特定序列
if query.SeriesID != "" {
points, err := e.querySeriesPoints(query.SeriesID, query.StartTime, query.EndTime)
if err != nil {
return nil, err
}
result.Points = points
return result, nil
}
// 否则查询所有匹配的序列
matchedSeries := e.findMatchingSeries(query)
for _, seriesID := range matchedSeries {
points, err := e.querySeriesPoints(seriesID, query.StartTime, query.EndTime)
if err != nil {
return nil, err
}
result.Points = append(result.Points, points...)
}
// 按时间戳排序
sort.Slice(result.Points, func(i, j int) bool {
return result.Points[i].Timestamp < result.Points[j].Timestamp
})
return result, nil
}
// queryLatest 实现最新数据查询
func (e *FileEngine) queryLatest(ctx context.Context, query engine.Query) (engine.QueryResult, error) {
result := &engine.TimeSeriesResult{
SeriesID: query.SeriesID,
Points: make([]engine.DataPoint, 0),
}
if query.SeriesID != "" {
point, err := e.queryLatestPoint(query.SeriesID)
if err != nil {
return nil, err
}
if point != nil {
result.Points = append(result.Points, *point)
}
return result, nil
}
matchedSeries := e.findMatchingSeries(query)
for _, seriesID := range matchedSeries {
point, err := e.queryLatestPoint(seriesID)
if err != nil {
return nil, err
}
if point != nil {
result.Points = append(result.Points, *point)
}
}
return result, nil
}
// queryAggregate 实现聚合查询
func (e *FileEngine) queryAggregate(ctx context.Context, query engine.Query) (engine.QueryResult, error) {
result := &engine.AggregateResult{
SeriesID: query.SeriesID,
Groups: make([]engine.AggregateGroup, 0),
}
// 创建时间窗口
windows := splitTimeRange(query.StartTime, query.EndTime, query.AggInterval)
// 如果指定了SeriesID只聚合特定序列
if query.SeriesID != "" {
groups, err := e.aggregateSeriesInWindows(query.SeriesID, windows, query.Aggregation)
if err != nil {
return nil, err
}
result.Groups = groups
return result, nil
}
// 否则聚合所有匹配的序列
matchedSeries := e.findMatchingSeries(query)
for _, window := range windows {
group := engine.AggregateGroup{
StartTime: window.start,
EndTime: window.end,
}
var totalSum float64
var totalCount int
for _, seriesID := range matchedSeries {
points, err := e.querySeriesPoints(seriesID, window.start, window.end)
if err != nil {
return nil, err
}
windowSum, windowCount := aggregatePoints(points, query.Aggregation)
totalSum += windowSum
totalCount += windowCount
}
if totalCount > 0 {
group.Value = calculateAggregateValue(totalSum, totalCount, query.Aggregation)
group.Count = totalCount
result.Groups = append(result.Groups, group)
}
}
return result, nil
}
// 辅助方法
func (e *FileEngine) querySeriesPoints(seriesID string, startTime, endTime int64) ([]engine.DataPoint, error) {
e.timeIndex.mu.RLock()
windows := e.timeIndex.windows[seriesID]
e.timeIndex.mu.RUnlock()
var points []engine.DataPoint
for _, window := range windows {
if window.endTime < startTime || window.startTime > endTime {
continue
}
segment, ok := e.segments[window.segmentID]
if !ok {
continue
}
segmentPoints, err := e.readSegmentPoints(segment, window.offset, startTime, endTime)
if err != nil {
return nil, err
}
points = append(points, segmentPoints...)
}
return points, nil
}
func (e *FileEngine) queryLatestPoint(seriesID string) (*engine.DataPoint, error) {
e.timeIndex.mu.RLock()
windows := e.timeIndex.windows[seriesID]
e.timeIndex.mu.RUnlock()
if len(windows) == 0 {
return nil, nil
}
// 找到最新的时间窗口
var latestWindow timeWindow
for _, window := range windows {
if window.endTime > latestWindow.endTime {
latestWindow = window
}
}
segment, ok := e.segments[latestWindow.segmentID]
if !ok {
return nil, nil
}
points, err := e.readSegmentPoints(segment, latestWindow.offset, 0, time.Now().UnixNano())
if err != nil {
return nil, err
}
if len(points) == 0 {
return nil, nil
}
// 返回最新的点
latestPoint := points[len(points)-1]
return &latestPoint, nil
}
func (e *FileEngine) readSegmentPoints(segment *Segment, offset int64, startTime, endTime int64) ([]engine.DataPoint, error) {
segment.mu.RLock()
defer segment.mu.RUnlock()
var points []engine.DataPoint
// 移动到指定偏移
if _, err := segment.file.Seek(offset, io.SeekStart); err != nil {
return nil, err
}
// 读取数据点
for {
var timestamp int64
var value float64
err := binary.Read(segment.file, binary.BigEndian, &timestamp)
if err == io.EOF {
break
}
if err != nil {
return nil, err
}
err = binary.Read(segment.file, binary.BigEndian, &value)
if err != nil {
return nil, err
}
if timestamp >= startTime && timestamp <= endTime {
points = append(points, engine.DataPoint{
Timestamp: timestamp,
Value: value,
})
}
}
return points, nil
}
func (e *FileEngine) findMatchingSeries(query engine.Query) []string {
e.tagIndex.mu.RLock()
defer e.tagIndex.mu.RUnlock()
var matchedSeries []string
seriesMap := make(map[string]bool)
// 首先根据标签过滤器找到匹配的序列
for _, filter := range query.TagFilters {
if values, ok := e.tagIndex.index[filter.Key]; ok {
if series, ok := values[filter.Value]; ok {
for _, seriesID := range series {
seriesMap[seriesID] = true
}
}
}
}
// 转换为切片
for seriesID := range seriesMap {
matchedSeries = append(matchedSeries, seriesID)
}
return matchedSeries
}
func (e *FileEngine) aggregateSeriesInWindows(seriesID string, windows []timeWindow, aggType engine.AggregationType) ([]engine.AggregateGroup, error) {
var groups []engine.AggregateGroup
for _, window := range windows {
points, err := e.querySeriesPoints(seriesID, window.start, window.end)
if err != nil {
return nil, err
}
if len(points) > 0 {
sum, count := aggregatePoints(points, aggType)
groups = append(groups, engine.AggregateGroup{
StartTime: window.start,
EndTime: window.end,
Value: calculateAggregateValue(sum, count, aggType),
Count: count,
})
}
}
return groups, nil
}
func aggregatePoints(points []engine.DataPoint, aggType engine.AggregationType) (float64, int) {
if len(points) == 0 {
return 0, 0
}
var sum float64
for _, p := range points {
sum += p.Value
}
return sum, len(points)
}
func calculateAggregateValue(sum float64, count int, aggType engine.AggregationType) float64 {
if count == 0 {
return 0
}
switch aggType {
case engine.AggSum:
return sum
case engine.AggAvg:
return sum / float64(count)
case engine.AggCount:
return float64(count)
default:
return sum
}
}
func splitTimeRange(start, end int64, interval time.Duration) []timeWindow {
var windows []timeWindow
intervalNanos := interval.Nanoseconds()
for windowStart := start; windowStart < end; windowStart += intervalNanos {
windowEnd := windowStart + intervalNanos
if windowEnd > end {
windowEnd = end
}
windows = append(windows, timeWindow{
start: windowStart,
end: windowEnd,
})
}
return windows
}
// 错误处理辅助函数
func wrapError(op string, err error) error {
if err == nil {
return nil
}
return fmt.Errorf("%s: %v", op, err)
}