1// This file and its contents are licensed under the Apache License 2.0.
2// Please see the included NOTICE for copyright information and
3// LICENSE for a copy of the license.
4
5package model
6
7import (
8	"fmt"
9	"strconv"
10	"strings"
11	"sync"
12	"unsafe"
13
14	"github.com/timescale/promscale/pkg/prompb"
15)
16
17// SeriesID represents a globally unique id for the series. This should be equivalent
18// to the PostgreSQL type in the series table (currently BIGINT).
19type SeriesID int64
20
21const (
22	invalidSeriesID    = -1
23	InvalidSeriesEpoch = -1
24)
25
26func (s SeriesID) String() string {
27	return strconv.FormatInt(int64(s), 10)
28}
29
30//Epoch represents the series epoch
31type SeriesEpoch int64
32
33// Series stores a labels.Series in its canonical string representation
34type Series struct {
35	//protects names, values, seriesID, epoch
36	//str and metricName are immutable and doesn't need a lock
37	lock       sync.RWMutex
38	names      []string
39	values     []string
40	metricName string
41	str        string
42	seriesID   SeriesID
43	epoch      SeriesEpoch
44}
45
46func NewSeries(key string, labelPairs []prompb.Label) *Series {
47	series := &Series{
48		names:    make([]string, len(labelPairs)),
49		values:   make([]string, len(labelPairs)),
50		str:      key,
51		seriesID: invalidSeriesID,
52		epoch:    InvalidSeriesEpoch,
53	}
54	for i, l := range labelPairs {
55		series.names[i] = l.Name
56		series.values[i] = l.Value
57		if l.Name == MetricNameLabelName {
58			series.metricName = l.Value
59		}
60	}
61	return series
62}
63
64//NameValues returns the names and values, only valid if the seriesIDIsNotSet
65func (l *Series) NameValues() (names []string, values []string, ok bool) {
66	l.lock.RLock()
67	defer l.lock.RUnlock()
68	return l.names, l.values, !l.isSeriesIDSetNoLock()
69}
70
71func (l *Series) MetricName() string {
72	return l.metricName
73}
74
75// Get a string representation for hashing and comparison
76// This representation is guaranteed to uniquely represent the underlying label
77// set, though need not human-readable, or indeed, valid utf-8
78func (l *Series) String() string {
79	return l.str
80}
81
82// Compare returns a comparison int between two Labels
83func (l *Series) Compare(b *Series) int {
84	return strings.Compare(l.str, b.str)
85}
86
87// Equal returns true if two Labels are equal
88func (l *Series) Equal(b *Series) bool {
89	return l.str == b.str
90}
91
92func (l *Series) isSeriesIDSetNoLock() bool {
93	return l.seriesID != invalidSeriesID
94}
95
96func (l *Series) IsSeriesIDSet() bool {
97	l.lock.RLock()
98	defer l.lock.RUnlock()
99
100	return l.isSeriesIDSetNoLock()
101}
102
103//FinalSizeBytes returns the size in bytes /after/ the seriesID is set
104func (l *Series) FinalSizeBytes() uint64 {
105	//size is the base size of the struct + the str and metricName strings
106	//names and values are not counted since they will be nilled out
107	return uint64(unsafe.Sizeof(*l)) + uint64(len(l.str)+len(l.metricName)) // #nosec
108}
109
110func (l *Series) GetSeriesID() (SeriesID, SeriesEpoch, error) {
111	l.lock.RLock()
112	defer l.lock.RUnlock()
113
114	switch l.seriesID {
115	case invalidSeriesID:
116		return 0, 0, fmt.Errorf("Series id not set")
117	case 0:
118		return 0, 0, fmt.Errorf("Series id invalid")
119	default:
120		return l.seriesID, l.epoch, nil
121	}
122}
123
124//note this has to be idempotent
125func (l *Series) SetSeriesID(sid SeriesID, eid SeriesEpoch) {
126	l.lock.Lock()
127	defer l.lock.Unlock()
128	l.seriesID = sid
129	l.epoch = eid
130	l.names = nil
131	l.values = nil
132}
133