1/*
2Copyright 2015 Google LLC
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8    http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17package bigtable
18
19import (
20	"fmt"
21	"strings"
22	"time"
23
24	durpb "github.com/golang/protobuf/ptypes/duration"
25	bttdpb "google.golang.org/genproto/googleapis/bigtable/admin/v2"
26)
27
28// A GCPolicy represents a rule that determines which cells are eligible for garbage collection.
29type GCPolicy interface {
30	String() string
31	proto() *bttdpb.GcRule
32}
33
34// IntersectionPolicy returns a GC policy that only applies when all its sub-policies apply.
35func IntersectionPolicy(sub ...GCPolicy) GCPolicy { return intersectionPolicy{sub} }
36
37type intersectionPolicy struct {
38	sub []GCPolicy
39}
40
41func (ip intersectionPolicy) String() string {
42	var ss []string
43	for _, sp := range ip.sub {
44		ss = append(ss, sp.String())
45	}
46	return "(" + strings.Join(ss, " && ") + ")"
47}
48
49func (ip intersectionPolicy) proto() *bttdpb.GcRule {
50	inter := &bttdpb.GcRule_Intersection{}
51	for _, sp := range ip.sub {
52		inter.Rules = append(inter.Rules, sp.proto())
53	}
54	return &bttdpb.GcRule{
55		Rule: &bttdpb.GcRule_Intersection_{Intersection: inter},
56	}
57}
58
59// UnionPolicy returns a GC policy that applies when any of its sub-policies apply.
60func UnionPolicy(sub ...GCPolicy) GCPolicy { return unionPolicy{sub} }
61
62type unionPolicy struct {
63	sub []GCPolicy
64}
65
66func (up unionPolicy) String() string {
67	var ss []string
68	for _, sp := range up.sub {
69		ss = append(ss, sp.String())
70	}
71	return "(" + strings.Join(ss, " || ") + ")"
72}
73
74func (up unionPolicy) proto() *bttdpb.GcRule {
75	union := &bttdpb.GcRule_Union{}
76	for _, sp := range up.sub {
77		union.Rules = append(union.Rules, sp.proto())
78	}
79	return &bttdpb.GcRule{
80		Rule: &bttdpb.GcRule_Union_{Union: union},
81	}
82}
83
84// MaxVersionsPolicy returns a GC policy that applies to all versions of a cell
85// except for the most recent n.
86func MaxVersionsPolicy(n int) GCPolicy { return maxVersionsPolicy(n) }
87
88type maxVersionsPolicy int
89
90func (mvp maxVersionsPolicy) String() string { return fmt.Sprintf("versions() > %d", int(mvp)) }
91
92func (mvp maxVersionsPolicy) proto() *bttdpb.GcRule {
93	return &bttdpb.GcRule{Rule: &bttdpb.GcRule_MaxNumVersions{MaxNumVersions: int32(mvp)}}
94}
95
96// MaxAgePolicy returns a GC policy that applies to all cells
97// older than the given age.
98func MaxAgePolicy(d time.Duration) GCPolicy { return maxAgePolicy(d) }
99
100type maxAgePolicy time.Duration
101
102var units = []struct {
103	d      time.Duration
104	suffix string
105}{
106	{24 * time.Hour, "d"},
107	{time.Hour, "h"},
108	{time.Minute, "m"},
109}
110
111func (ma maxAgePolicy) String() string {
112	d := time.Duration(ma)
113	for _, u := range units {
114		if d%u.d == 0 {
115			return fmt.Sprintf("age() > %d%s", d/u.d, u.suffix)
116		}
117	}
118	return fmt.Sprintf("age() > %d", d/time.Microsecond)
119}
120
121func (ma maxAgePolicy) proto() *bttdpb.GcRule {
122	// This doesn't handle overflows, etc.
123	// Fix this if people care about GC policies over 290 years.
124	ns := time.Duration(ma).Nanoseconds()
125	return &bttdpb.GcRule{
126		Rule: &bttdpb.GcRule_MaxAge{MaxAge: &durpb.Duration{
127			Seconds: ns / 1e9,
128			Nanos:   int32(ns % 1e9),
129		}},
130	}
131}
132
133type noGCPolicy struct{}
134
135func (n noGCPolicy) String() string { return "" }
136
137func (n noGCPolicy) proto() *bttdpb.GcRule { return &bttdpb.GcRule{Rule: nil} }
138
139// NoGcPolicy applies to all cells setting maxage and maxversions to nil implies no gc policies
140func NoGcPolicy() GCPolicy { return noGCPolicy{} }
141
142// GCRuleToString converts the given GcRule proto to a user-visible string.
143func GCRuleToString(rule *bttdpb.GcRule) string {
144	if rule == nil {
145		return "<never>"
146	}
147	switch r := rule.Rule.(type) {
148	case *bttdpb.GcRule_MaxNumVersions:
149		return MaxVersionsPolicy(int(r.MaxNumVersions)).String()
150	case *bttdpb.GcRule_MaxAge:
151		return MaxAgePolicy(time.Duration(r.MaxAge.Seconds) * time.Second).String()
152	case *bttdpb.GcRule_Intersection_:
153		return joinRules(r.Intersection.Rules, " && ")
154	case *bttdpb.GcRule_Union_:
155		return joinRules(r.Union.Rules, " || ")
156	default:
157		return ""
158	}
159}
160
161func joinRules(rules []*bttdpb.GcRule, sep string) string {
162	var chunks []string
163	for _, r := range rules {
164		chunks = append(chunks, GCRuleToString(r))
165	}
166	return "(" + strings.Join(chunks, sep) + ")"
167}
168