Merge pull request #3992 from svanharmelen/f-change-sets
core: change set internals and make (extreme) performance improvements
This commit is contained in:
commit
f1e7cec566
|
@ -18,10 +18,12 @@ type ConfigFieldReader struct {
|
||||||
Config *terraform.ResourceConfig
|
Config *terraform.ResourceConfig
|
||||||
Schema map[string]*Schema
|
Schema map[string]*Schema
|
||||||
|
|
||||||
lock sync.Mutex
|
indexMaps map[string]map[string]int
|
||||||
|
once sync.Once
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *ConfigFieldReader) ReadField(address []string) (FieldReadResult, error) {
|
func (r *ConfigFieldReader) ReadField(address []string) (FieldReadResult, error) {
|
||||||
|
r.once.Do(func() { r.indexMaps = make(map[string]map[string]int) })
|
||||||
return r.readField(address, false)
|
return r.readField(address, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,20 +57,18 @@ func (r *ConfigFieldReader) readField(
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the code
|
indexMap, ok := r.indexMaps[strings.Join(address[:i+1], ".")]
|
||||||
code, err := strconv.ParseInt(address[i+1], 0, 0)
|
if !ok {
|
||||||
if err != nil {
|
|
||||||
return FieldReadResult{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the set so we can get the index map that tells us the
|
// Get the set so we can get the index map that tells us the
|
||||||
// mapping of the hash code to the list index
|
// mapping of the hash code to the list index
|
||||||
_, indexMap, err := r.readSet(address[:i+1], v)
|
_, err := r.readSet(address[:i+1], v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return FieldReadResult{}, err
|
return FieldReadResult{}, err
|
||||||
}
|
}
|
||||||
|
indexMap = r.indexMaps[strings.Join(address[:i+1], ".")]
|
||||||
|
}
|
||||||
|
|
||||||
index, ok := indexMap[int(code)]
|
index, ok := indexMap[address[i+1]]
|
||||||
if !ok {
|
if !ok {
|
||||||
return FieldReadResult{}, nil
|
return FieldReadResult{}, nil
|
||||||
}
|
}
|
||||||
|
@ -87,8 +87,7 @@ func (r *ConfigFieldReader) readField(
|
||||||
case TypeMap:
|
case TypeMap:
|
||||||
return r.readMap(k)
|
return r.readMap(k)
|
||||||
case TypeSet:
|
case TypeSet:
|
||||||
result, _, err := r.readSet(address, schema)
|
return r.readSet(address, schema)
|
||||||
return result, err
|
|
||||||
case typeObject:
|
case typeObject:
|
||||||
return readObjectField(
|
return readObjectField(
|
||||||
&nestedConfigFieldReader{r},
|
&nestedConfigFieldReader{r},
|
||||||
|
@ -112,7 +111,7 @@ func (r *ConfigFieldReader) readMap(k string) (FieldReadResult, error) {
|
||||||
switch m := mraw.(type) {
|
switch m := mraw.(type) {
|
||||||
case []interface{}:
|
case []interface{}:
|
||||||
for i, innerRaw := range m {
|
for i, innerRaw := range m {
|
||||||
for ik, _ := range innerRaw.(map[string]interface{}) {
|
for ik := range innerRaw.(map[string]interface{}) {
|
||||||
key := fmt.Sprintf("%s.%d.%s", k, i, ik)
|
key := fmt.Sprintf("%s.%d.%s", k, i, ik)
|
||||||
if r.Config.IsComputed(key) {
|
if r.Config.IsComputed(key) {
|
||||||
computed = true
|
computed = true
|
||||||
|
@ -125,7 +124,7 @@ func (r *ConfigFieldReader) readMap(k string) (FieldReadResult, error) {
|
||||||
}
|
}
|
||||||
case []map[string]interface{}:
|
case []map[string]interface{}:
|
||||||
for i, innerRaw := range m {
|
for i, innerRaw := range m {
|
||||||
for ik, _ := range innerRaw {
|
for ik := range innerRaw {
|
||||||
key := fmt.Sprintf("%s.%d.%s", k, i, ik)
|
key := fmt.Sprintf("%s.%d.%s", k, i, ik)
|
||||||
if r.Config.IsComputed(key) {
|
if r.Config.IsComputed(key) {
|
||||||
computed = true
|
computed = true
|
||||||
|
@ -137,7 +136,7 @@ func (r *ConfigFieldReader) readMap(k string) (FieldReadResult, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case map[string]interface{}:
|
case map[string]interface{}:
|
||||||
for ik, _ := range m {
|
for ik := range m {
|
||||||
key := fmt.Sprintf("%s.%s", k, ik)
|
key := fmt.Sprintf("%s.%s", k, ik)
|
||||||
if r.Config.IsComputed(key) {
|
if r.Config.IsComputed(key) {
|
||||||
computed = true
|
computed = true
|
||||||
|
@ -198,17 +197,17 @@ func (r *ConfigFieldReader) readPrimitive(
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *ConfigFieldReader) readSet(
|
func (r *ConfigFieldReader) readSet(
|
||||||
address []string, schema *Schema) (FieldReadResult, map[int]int, error) {
|
address []string, schema *Schema) (FieldReadResult, error) {
|
||||||
indexMap := make(map[int]int)
|
indexMap := make(map[string]int)
|
||||||
// Create the set that will be our result
|
// Create the set that will be our result
|
||||||
set := schema.ZeroValue().(*Set)
|
set := schema.ZeroValue().(*Set)
|
||||||
|
|
||||||
raw, err := readListField(&nestedConfigFieldReader{r}, address, schema)
|
raw, err := readListField(&nestedConfigFieldReader{r}, address, schema)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return FieldReadResult{}, indexMap, err
|
return FieldReadResult{}, err
|
||||||
}
|
}
|
||||||
if !raw.Exists {
|
if !raw.Exists {
|
||||||
return FieldReadResult{Value: set}, indexMap, nil
|
return FieldReadResult{Value: set}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the list is computed, the set is necessarilly computed
|
// If the list is computed, the set is necessarilly computed
|
||||||
|
@ -217,7 +216,7 @@ func (r *ConfigFieldReader) readSet(
|
||||||
Value: set,
|
Value: set,
|
||||||
Exists: true,
|
Exists: true,
|
||||||
Computed: raw.Computed,
|
Computed: raw.Computed,
|
||||||
}, indexMap, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build up the set from the list elements
|
// Build up the set from the list elements
|
||||||
|
@ -226,19 +225,16 @@ func (r *ConfigFieldReader) readSet(
|
||||||
computed := r.hasComputedSubKeys(
|
computed := r.hasComputedSubKeys(
|
||||||
fmt.Sprintf("%s.%d", strings.Join(address, "."), i), schema)
|
fmt.Sprintf("%s.%d", strings.Join(address, "."), i), schema)
|
||||||
|
|
||||||
code := set.add(v)
|
code := set.add(v, computed)
|
||||||
indexMap[code] = i
|
indexMap[code] = i
|
||||||
if computed {
|
|
||||||
set.m[-code] = set.m[code]
|
|
||||||
delete(set.m, code)
|
|
||||||
code = -code
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
r.indexMaps[strings.Join(address, ".")] = indexMap
|
||||||
|
|
||||||
return FieldReadResult{
|
return FieldReadResult{
|
||||||
Value: set,
|
Value: set,
|
||||||
Exists: true,
|
Exists: true,
|
||||||
}, indexMap, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// hasComputedSubKeys walks through a schema and returns whether or not the
|
// hasComputedSubKeys walks through a schema and returns whether or not the
|
||||||
|
|
|
@ -228,8 +228,8 @@ func TestConfigFieldReader_ComputedSet(t *testing.T) {
|
||||||
"set, normal": {
|
"set, normal": {
|
||||||
[]string{"strSet"},
|
[]string{"strSet"},
|
||||||
FieldReadResult{
|
FieldReadResult{
|
||||||
Value: map[int]interface{}{
|
Value: map[string]interface{}{
|
||||||
2356372769: "foo",
|
"2356372769": "foo",
|
||||||
},
|
},
|
||||||
Exists: true,
|
Exists: true,
|
||||||
Computed: false,
|
Computed: false,
|
||||||
|
|
|
@ -298,8 +298,7 @@ func (w *MapFieldWriter) setSet(
|
||||||
}
|
}
|
||||||
|
|
||||||
for code, elem := range value.(*Set).m {
|
for code, elem := range value.(*Set).m {
|
||||||
codeStr := strconv.FormatInt(int64(code), 10)
|
if err := w.set(append(addrCopy, code), elem); err != nil {
|
||||||
if err := w.set(append(addrCopy, codeStr), elem); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -228,7 +228,7 @@ func (d *ResourceData) State() *terraform.InstanceState {
|
||||||
// attribute set as a map[string]interface{}, write it to a MapFieldWriter,
|
// attribute set as a map[string]interface{}, write it to a MapFieldWriter,
|
||||||
// and then use that map.
|
// and then use that map.
|
||||||
rawMap := make(map[string]interface{})
|
rawMap := make(map[string]interface{})
|
||||||
for k, _ := range d.schema {
|
for k := range d.schema {
|
||||||
source := getSourceSet
|
source := getSourceSet
|
||||||
if d.partial {
|
if d.partial {
|
||||||
source = getSourceState
|
source = getSourceState
|
||||||
|
@ -343,13 +343,13 @@ func (d *ResourceData) diffChange(
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *ResourceData) getChange(
|
func (d *ResourceData) getChange(
|
||||||
key string,
|
k string,
|
||||||
oldLevel getSource,
|
oldLevel getSource,
|
||||||
newLevel getSource) (getResult, getResult) {
|
newLevel getSource) (getResult, getResult) {
|
||||||
var parts, parts2 []string
|
var parts, parts2 []string
|
||||||
if key != "" {
|
if k != "" {
|
||||||
parts = strings.Split(key, ".")
|
parts = strings.Split(k, ".")
|
||||||
parts2 = strings.Split(key, ".")
|
parts2 = strings.Split(k, ".")
|
||||||
}
|
}
|
||||||
|
|
||||||
o := d.get(parts, oldLevel)
|
o := d.get(parts, oldLevel)
|
||||||
|
@ -374,13 +374,6 @@ func (d *ResourceData) get(addr []string, source getSource) getResult {
|
||||||
level = "state"
|
level = "state"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build the address of the key we're looking for and ask the FieldReader
|
|
||||||
for i, v := range addr {
|
|
||||||
if v[0] == '~' {
|
|
||||||
addr[i] = v[1:]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var result FieldReadResult
|
var result FieldReadResult
|
||||||
var err error
|
var err error
|
||||||
if exact {
|
if exact {
|
||||||
|
|
|
@ -1509,9 +1509,9 @@ func TestResourceDataSet(t *testing.T) {
|
||||||
|
|
||||||
Key: "ports",
|
Key: "ports",
|
||||||
Value: &Set{
|
Value: &Set{
|
||||||
m: map[int]interface{}{
|
m: map[string]interface{}{
|
||||||
1: 1,
|
"1": 1,
|
||||||
2: 2,
|
"2": 2,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -1546,7 +1546,7 @@ func TestResourceDataSet(t *testing.T) {
|
||||||
Err: true,
|
Err: true,
|
||||||
|
|
||||||
GetKey: "ports",
|
GetKey: "ports",
|
||||||
GetValue: []interface{}{80, 100},
|
GetValue: []interface{}{100, 80},
|
||||||
},
|
},
|
||||||
|
|
||||||
// #11: Set with nested set
|
// #11: Set with nested set
|
||||||
|
|
|
@ -866,23 +866,16 @@ func (m schemaMap) diffSet(
|
||||||
|
|
||||||
// Build the list of codes that will make up our set. This is the
|
// Build the list of codes that will make up our set. This is the
|
||||||
// removed codes as well as all the codes in the new codes.
|
// removed codes as well as all the codes in the new codes.
|
||||||
codes := make([][]int, 2)
|
codes := make([][]string, 2)
|
||||||
codes[0] = os.Difference(ns).listCode()
|
codes[0] = os.Difference(ns).listCode()
|
||||||
codes[1] = ns.listCode()
|
codes[1] = ns.listCode()
|
||||||
for _, list := range codes {
|
for _, list := range codes {
|
||||||
for _, code := range list {
|
for _, code := range list {
|
||||||
// If the code is negative (first character is -) then
|
|
||||||
// replace it with "~" for our computed set stuff.
|
|
||||||
codeStr := strconv.Itoa(code)
|
|
||||||
if codeStr[0] == '-' {
|
|
||||||
codeStr = string('~') + codeStr[1:]
|
|
||||||
}
|
|
||||||
|
|
||||||
switch t := schema.Elem.(type) {
|
switch t := schema.Elem.(type) {
|
||||||
case *Resource:
|
case *Resource:
|
||||||
// This is a complex resource
|
// This is a complex resource
|
||||||
for k2, schema := range t.Schema {
|
for k2, schema := range t.Schema {
|
||||||
subK := fmt.Sprintf("%s.%s.%s", k, codeStr, k2)
|
subK := fmt.Sprintf("%s.%s.%s", k, code, k2)
|
||||||
err := m.diff(subK, schema, diff, d, true)
|
err := m.diff(subK, schema, diff, d, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -896,7 +889,7 @@ func (m schemaMap) diffSet(
|
||||||
|
|
||||||
// This is just a primitive element, so go through each and
|
// This is just a primitive element, so go through each and
|
||||||
// just diff each.
|
// just diff each.
|
||||||
subK := fmt.Sprintf("%s.%s", k, codeStr)
|
subK := fmt.Sprintf("%s.%s", k, code)
|
||||||
err := m.diff(subK, &t2, diff, d, true)
|
err := m.diff(subK, &t2, diff, d, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
"sort"
|
"sort"
|
||||||
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/helper/hashcode"
|
"github.com/hashicorp/terraform/helper/hashcode"
|
||||||
|
@ -43,7 +44,7 @@ func HashSchema(schema *Schema) SchemaSetFunc {
|
||||||
type Set struct {
|
type Set struct {
|
||||||
F SchemaSetFunc
|
F SchemaSetFunc
|
||||||
|
|
||||||
m map[int]interface{}
|
m map[string]interface{}
|
||||||
once sync.Once
|
once sync.Once
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -65,7 +66,7 @@ func CopySet(otherSet *Set) *Set {
|
||||||
|
|
||||||
// Add adds an item to the set if it isn't already in the set.
|
// Add adds an item to the set if it isn't already in the set.
|
||||||
func (s *Set) Add(item interface{}) {
|
func (s *Set) Add(item interface{}) {
|
||||||
s.add(item)
|
s.add(item, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove removes an item if it's already in the set. Idempotent.
|
// Remove removes an item if it's already in the set. Idempotent.
|
||||||
|
@ -157,13 +158,17 @@ func (s *Set) GoString() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Set) init() {
|
func (s *Set) init() {
|
||||||
s.m = make(map[int]interface{})
|
s.m = make(map[string]interface{})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Set) add(item interface{}) int {
|
func (s *Set) add(item interface{}, computed bool) string {
|
||||||
s.once.Do(s.init)
|
s.once.Do(s.init)
|
||||||
|
|
||||||
code := s.hash(item)
|
code := s.hash(item)
|
||||||
|
if computed {
|
||||||
|
code = "~" + code
|
||||||
|
}
|
||||||
|
|
||||||
if _, ok := s.m[code]; !ok {
|
if _, ok := s.m[code]; !ok {
|
||||||
s.m[code] = item
|
s.m[code] = item
|
||||||
}
|
}
|
||||||
|
@ -171,34 +176,34 @@ func (s *Set) add(item interface{}) int {
|
||||||
return code
|
return code
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Set) hash(item interface{}) int {
|
func (s *Set) hash(item interface{}) string {
|
||||||
code := s.F(item)
|
code := s.F(item)
|
||||||
// Always return a nonnegative hashcode.
|
// Always return a nonnegative hashcode.
|
||||||
if code < 0 {
|
if code < 0 {
|
||||||
return -code
|
code = -code
|
||||||
}
|
}
|
||||||
return code
|
return strconv.Itoa(code)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Set) remove(item interface{}) int {
|
func (s *Set) remove(item interface{}) string {
|
||||||
s.once.Do(s.init)
|
s.once.Do(s.init)
|
||||||
|
|
||||||
code := s.F(item)
|
code := s.hash(item)
|
||||||
delete(s.m, code)
|
delete(s.m, code)
|
||||||
|
|
||||||
return code
|
return code
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Set) index(item interface{}) int {
|
func (s *Set) index(item interface{}) int {
|
||||||
return sort.SearchInts(s.listCode(), s.hash(item))
|
return sort.SearchStrings(s.listCode(), s.hash(item))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Set) listCode() []int {
|
func (s *Set) listCode() []string {
|
||||||
// Sort the hash codes so the order of the list is deterministic
|
// Sort the hash codes so the order of the list is deterministic
|
||||||
keys := make([]int, 0, len(s.m))
|
keys := make([]string, 0, len(s.m))
|
||||||
for k, _ := range s.m {
|
for k := range s.m {
|
||||||
keys = append(keys, k)
|
keys = append(keys, k)
|
||||||
}
|
}
|
||||||
sort.Sort(sort.IntSlice(keys))
|
sort.Sort(sort.StringSlice(keys))
|
||||||
return keys
|
return keys
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,7 @@ func TestSetAdd(t *testing.T) {
|
||||||
s.Add(5)
|
s.Add(5)
|
||||||
s.Add(25)
|
s.Add(25)
|
||||||
|
|
||||||
expected := []interface{}{1, 5, 25}
|
expected := []interface{}{1, 25, 5}
|
||||||
actual := s.List()
|
actual := s.List()
|
||||||
if !reflect.DeepEqual(actual, expected) {
|
if !reflect.DeepEqual(actual, expected) {
|
||||||
t.Fatalf("bad: %#v", actual)
|
t.Fatalf("bad: %#v", actual)
|
||||||
|
@ -101,7 +101,7 @@ func TestSetUnion(t *testing.T) {
|
||||||
union := s1.Union(s2)
|
union := s1.Union(s2)
|
||||||
union.Add(2)
|
union.Add(2)
|
||||||
|
|
||||||
expected := []interface{}{1, 2, 5, 25}
|
expected := []interface{}{1, 2, 25, 5}
|
||||||
actual := union.List()
|
actual := union.List()
|
||||||
if !reflect.DeepEqual(actual, expected) {
|
if !reflect.DeepEqual(actual, expected) {
|
||||||
t.Fatalf("bad: %#v", actual)
|
t.Fatalf("bad: %#v", actual)
|
||||||
|
|
Loading…
Reference in New Issue