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