diff --git a/cmd/protoc-gen-cpp-tableau-loader/helper/helper.go b/cmd/protoc-gen-cpp-tableau-loader/helper/helper.go index 843e57a5..d309115e 100644 --- a/cmd/protoc-gen-cpp-tableau-loader/helper/helper.go +++ b/cmd/protoc-gen-cpp-tableau-loader/helper/helper.go @@ -174,21 +174,69 @@ func ParseLeveledMapPrefix(md protoreflect.MessageDescriptor, mapFd protoreflect } type MapKey struct { - Type string - Name string - Fd protoreflect.FieldDescriptor // the map field descriptor this key belongs to + Type string + Name string + FieldName string // multi-column index only (may be deduplicated, e.g., "Id" → "Id3") + OrigFieldName string // original FieldName before deduplication (empty if not renamed) + Fd protoreflect.FieldDescriptor // the map field descriptor this key belongs to } type MapKeySlice []MapKey +// AddMapKey appends a new map key to the slice, automatically deduplicating +// both Name (used as function parameter names) and FieldName (used as struct +// field names in LevelIndex key structs). +// +// Deduplication is needed because different map levels may share the same key +// name. For example, given the following nested proto maps where country_map +// and item_map both use "ID" as their key name: +// +// message Fruit4Conf { +// map fruit_map = 1; // key field: "FruitType" +// message Fruit { +// map country_map = 2; // key field: "ID" +// message Country { +// map item_map = 3; // key field: "ID" ← same name! +// } +// } +// } +// +// Without dedup, the generated LevelIndex key struct would have duplicate +// field names, causing a compile error: +// +// struct LevelIndex_Fruit_Country_ItemKey { +// int32_t id; // key of protoconf.Fruit4Conf.fruit_map +// int32_t id; // key of protoconf.Fruit4Conf.Fruit.country_map +// int32_t id; // key of protoconf.Fruit4Conf.Fruit.Country.item_map — COMPILE ERROR! +// }; +// +// With dedup, the conflicting name gets a numeric suffix (the 1-based position +// of the new key in the slice), producing valid C++ code: +// +// struct LevelIndex_Fruit_Country_ItemKey { +// int32_t fruit_type; // key of protoconf.Fruit4Conf.fruit_map +// int32_t id; // key of protoconf.Fruit4Conf.Fruit.country_map +// int32_t id3; // key of protoconf.Fruit4Conf.Fruit.Country.item_map (renamed from id) +// }; func (s MapKeySlice) AddMapKey(newKey MapKey) MapKeySlice { if newKey.Name == "" { newKey.Name = fmt.Sprintf("key%d", len(s)+1) - } else { + } + // Deduplicate Name (used as function parameter, e.g., "id" → "id3"). + for _, key := range s { + if key.Name == newKey.Name { + newKey.Name = fmt.Sprintf("%s%d", newKey.Name, len(s)+1) + break + } + } + // Deduplicate FieldName (used as struct field, e.g., "Id" → "Id3"). + // This is only relevant for multi-column indexes that generate LevelIndex + // key structs; single-column indexes leave FieldName empty. + if newKey.FieldName != "" { for _, key := range s { - if key.Name == newKey.Name { - // rewrite to avoid name confict - newKey.Name = fmt.Sprintf("%s%d", newKey.Name, len(s)+1) + if key.FieldName == newKey.FieldName { + newKey.OrigFieldName = newKey.FieldName + newKey.FieldName = fmt.Sprintf("%s%d", newKey.FieldName, len(s)+1) break } } diff --git a/cmd/protoc-gen-cpp-tableau-loader/indexes/generator.go b/cmd/protoc-gen-cpp-tableau-loader/indexes/generator.go index cbc4ad68..6c2a2228 100644 --- a/cmd/protoc-gen-cpp-tableau-loader/indexes/generator.go +++ b/cmd/protoc-gen-cpp-tableau-loader/indexes/generator.go @@ -37,9 +37,10 @@ func (x *Generator) initLevelMessage() { break } x.keys = x.keys.AddMapKey(helper.MapKey{ - Type: helper.ParseMapKeyType(fd.MapKey()), - Name: helper.ParseMapFieldName(fd), - Fd: fd, + Type: helper.ParseMapKeyType(fd.MapKey()), + Name: helper.ParseMapFieldName(fd), + FieldName: helper.ParseMapFieldName(fd), + Fd: fd, }) } } @@ -95,12 +96,19 @@ func (x *Generator) GenHppIndexFinders() { // struct that bundles all ancestor keys up to that depth: // // keys = [k1, k2, k3] → struct for depth 2: {k1, k2} + // struct for depth 3: {k1, k2, k3} // keys = [k1, k2, k3, k4] → struct for depth 2: {k1, k2} // struct for depth 3: {k1, k2, k3} + // struct for depth 4: {k1, k2, k3, k4} // // The loop starts at i=2 (depth 2) and creates a struct from keys[:i]. - // It runs len(x.keys)-2 times (0 times when len ≤ 2). - for i := 2; i < len(x.keys); i++ { + // It runs len(x.keys)-1 times (0 times when len ≤ 1). + // + // NOTE: When multiple map levels share the same key name (e.g., two maps + // both keyed by "ID"), the FieldName in x.keys is automatically + // deduplicated by AddMapKey (e.g., "Id" → "Id3"). This ensures the + // generated struct has unique field names. See AddMapKey for details. + for i := 2; i <= len(x.keys); i++ { if i == 2 { x.g.P() x.g.P(helper.Indent(1), "// LevelIndex keys.") @@ -111,7 +119,11 @@ func (x *Generator) GenHppIndexFinders() { x.g.P(helper.Indent(1), "struct ", keyType, " {") keys := x.keys[:i] for _, key := range keys { - x.g.P(helper.Indent(2), key.Type, " ", key.Name, ";") + comment := fmt.Sprintf("// key of %s", key.Fd.FullName()) + if key.OrigFieldName != "" { + comment += fmt.Sprintf(" (renamed from %s)", key.OrigFieldName) + } + x.g.P(helper.Indent(2), key.Type, " ", key.Name, "; ", comment) } x.g.P("#if __cplusplus >= 202002L") x.g.P(helper.Indent(2), "bool operator==(const ", keyType, "& other) const = default;") diff --git a/cmd/protoc-gen-cpp-tableau-loader/indexes/index.go b/cmd/protoc-gen-cpp-tableau-loader/indexes/index.go index eee1b66e..6cc33774 100644 --- a/cmd/protoc-gen-cpp-tableau-loader/indexes/index.go +++ b/cmd/protoc-gen-cpp-tableau-loader/indexes/index.go @@ -104,7 +104,7 @@ func (x *Generator) genHppIndexFinders() { x.g.P(helper.Indent(1), "const ", vectorType, "* Find", index.Name(), "(", keys.GenGetParams(), ") const;") x.g.P(helper.Indent(1), "// Finds the first value of the given key(s).") x.g.P(helper.Indent(1), "const ", valueType, "* FindFirst", index.Name(), "(", keys.GenGetParams(), ") const;") - for i := 1; i < lm.MapDepth; i++ { + for i := 1; i < lm.LeveledContainerDepth(); i++ { partKeys := x.keys[:i] x.g.P(helper.Indent(1), "// Finds the index: key(", index.Index, ") to value(", vectorType, "),") x.g.P(helper.Indent(1), "// which is the upper ", loadutil.Ordinal(i), "-level hashmap specified by (", partKeys.GenGetArguments(), ").") @@ -119,7 +119,7 @@ func (x *Generator) genHppIndexFinders() { x.g.P(" private:") x.g.P(helper.Indent(1), mapType, " ", x.indexContainerName(index, 0), ";") - for i := 1; i < lm.MapDepth; i++ { + for i := 1; i < lm.LeveledContainerDepth(); i++ { if i == 1 { x.g.P(helper.Indent(1), "std::unordered_map<", x.keys[0].Type, ", ", mapType, "> ", x.indexContainerName(index, i), ";") } else { @@ -140,7 +140,7 @@ func (x *Generator) genIndexLoader() { for lm := x.descriptor.LevelMessage; lm != nil; lm = lm.NextLevel { for _, index := range lm.Indexes { x.g.P(helper.Indent(1), x.indexContainerName(index, 0), ".clear();") - for i := 1; i < lm.MapDepth; i++ { + for i := 1; i < lm.LeveledContainerDepth(); i++ { x.g.P(helper.Indent(1), x.indexContainerName(index, i), ".clear();") } } @@ -223,7 +223,7 @@ func (x *Generator) generateOneCppMulticolumnIndex(lm *index.LevelMessage, index func (x *Generator) genLoader(lm *index.LevelMessage, index *index.LevelIndex, ident int, key, parentDataName string) { x.g.P(helper.Indent(ident), x.indexContainerName(index, 0), "[", key, "].push_back(&", parentDataName, ");") - for i := 1; i < lm.MapDepth; i++ { + for i := 1; i < lm.LeveledContainerDepth(); i++ { if i == 1 { x.g.P(helper.Indent(ident), x.indexContainerName(index, i), "[k1][", key, "].push_back(&", parentDataName, ");") } else { @@ -262,7 +262,7 @@ func (x *Generator) genIndexSorter() { x.g.P(helper.Indent(2), "std::sort(item.second.begin(), item.second.end(), ", indexContainerName, "sorter);") x.g.P(helper.Indent(1), "}") // Iterate all leveled containers. - for i := 1; i < lm.MapDepth; i++ { + for i := 1; i < lm.LeveledContainerDepth(); i++ { x.g.P(helper.Indent(1), "for (auto&& item : ", x.indexContainerName(index, i), ") {") x.g.P(helper.Indent(2), "for (auto&& item1 : item.second) {") x.g.P(helper.Indent(3), "std::sort(item1.second.begin(), item1.second.end(), ", indexContainerName, "sorter);") @@ -314,7 +314,7 @@ func (x *Generator) genCppIndexFinders() { x.g.P("}") x.g.P() - for i := 1; i < lm.MapDepth; i++ { + for i := 1; i < lm.LeveledContainerDepth(); i++ { indexContainerName := x.indexContainerName(index, i) partKeys := x.keys[:i] partParams := partKeys.GenGetParams() diff --git a/cmd/protoc-gen-cpp-tableau-loader/indexes/ordered_index.go b/cmd/protoc-gen-cpp-tableau-loader/indexes/ordered_index.go index 3142c875..960ef793 100644 --- a/cmd/protoc-gen-cpp-tableau-loader/indexes/ordered_index.go +++ b/cmd/protoc-gen-cpp-tableau-loader/indexes/ordered_index.go @@ -94,7 +94,7 @@ func (x *Generator) genHppOrderedIndexFinders() { x.g.P(helper.Indent(1), "const ", vectorType, "* Find", index.Name(), "(", keys.GenGetParams(), ") const;") x.g.P(helper.Indent(1), "// Finds the first value of the given key(s).") x.g.P(helper.Indent(1), "const ", helper.ParseCppClassType(index.MD), "* FindFirst", index.Name(), "(", keys.GenGetParams(), ") const;") - for i := 1; i < lm.MapDepth; i++ { + for i := 1; i < lm.LeveledContainerDepth(); i++ { partKeys := x.keys[:i] x.g.P(helper.Indent(1), "// Finds the ordered index: key(", index.Index, ") to value(", vectorType, "),") x.g.P(helper.Indent(1), "// which is the upper ", loadutil.Ordinal(i), "-level map specified by (", partKeys.GenGetArguments(), ").") @@ -109,7 +109,7 @@ func (x *Generator) genHppOrderedIndexFinders() { x.g.P(" private:") x.g.P(helper.Indent(1), mapType, " ", x.orderedIndexContainerName(index, 0), ";") - for i := 1; i < lm.MapDepth; i++ { + for i := 1; i < lm.LeveledContainerDepth(); i++ { if i == 1 { x.g.P(helper.Indent(1), "std::unordered_map<", x.keys[0].Type, ", ", mapType, "> ", x.orderedIndexContainerName(index, i), ";") } else { @@ -130,7 +130,7 @@ func (x *Generator) genOrderedIndexLoader() { for lm := x.descriptor.LevelMessage; lm != nil; lm = lm.NextLevel { for _, index := range lm.OrderedIndexes { x.g.P(helper.Indent(1), x.orderedIndexContainerName(index, 0), ".clear();") - for i := 1; i < lm.MapDepth; i++ { + for i := 1; i < lm.LeveledContainerDepth(); i++ { x.g.P(helper.Indent(1), x.orderedIndexContainerName(index, i), ".clear();") } } @@ -213,7 +213,7 @@ func (x *Generator) generateOneCppMulticolumnOrderedIndex(lm *index.LevelMessage func (x *Generator) genOrderedLoader(lm *index.LevelMessage, index *index.LevelIndex, ident int, key, parentDataName string) { x.g.P(helper.Indent(ident), x.orderedIndexContainerName(index, 0), "[", key, "].push_back(&", parentDataName, ");") - for i := 1; i < lm.MapDepth; i++ { + for i := 1; i < lm.LeveledContainerDepth(); i++ { if i == 1 { x.g.P(helper.Indent(ident), x.orderedIndexContainerName(index, i), "[k1][", key, "].push_back(&", parentDataName, ");") } else { @@ -252,7 +252,7 @@ func (x *Generator) genOrderedIndexSorter() { x.g.P(helper.Indent(2), "std::sort(item.second.begin(), item.second.end(), ", indexContainerName, "sorter);") x.g.P(helper.Indent(1), "}") // Iterate all leveled containers. - for i := 1; i < lm.MapDepth; i++ { + for i := 1; i < lm.LeveledContainerDepth(); i++ { x.g.P(helper.Indent(1), "for (auto&& item : ", x.orderedIndexContainerName(index, i), ") {") x.g.P(helper.Indent(2), "for (auto&& item1 : item.second) {") x.g.P(helper.Indent(3), "std::sort(item1.second.begin(), item1.second.end(), ", indexContainerName, "sorter);") @@ -304,7 +304,7 @@ func (x *Generator) genCppOrderedIndexFinders() { x.g.P("}") x.g.P() - for i := 1; i < lm.MapDepth; i++ { + for i := 1; i < lm.LeveledContainerDepth(); i++ { indexContainerNameI := x.orderedIndexContainerName(index, i) partKeys := x.keys[:i] partParams := partKeys.GenGetParams() diff --git a/cmd/protoc-gen-csharp-tableau-loader/helper/helper.go b/cmd/protoc-gen-csharp-tableau-loader/helper/helper.go index 86b136f1..70921485 100644 --- a/cmd/protoc-gen-csharp-tableau-loader/helper/helper.go +++ b/cmd/protoc-gen-csharp-tableau-loader/helper/helper.go @@ -325,10 +325,11 @@ func ParseLeveledMapPrefix(md protoreflect.MessageDescriptor, mapFd protoreflect // holding the C# type, parameter name, original field name, and the // associated protobuf field descriptor. type MapKey struct { - Type string // C# type string (e.g. "int", "string") - Name string // parameter/variable name in generated code - FieldName string // original field name (multi-column index only) - Fd protoreflect.FieldDescriptor // the map field descriptor this key belongs to + Type string // C# type string (e.g. "int", "string") + Name string // parameter/variable name in generated code + FieldName string // multi-column index only (may be deduplicated, e.g., "Id" → "Id3") + OrigFieldName string // original FieldName before deduplication (empty if not renamed) + Fd protoreflect.FieldDescriptor // the map field descriptor this key belongs to } // MapKeySlice is an ordered collection of MapKey entries, providing methods @@ -336,20 +337,62 @@ type MapKey struct { // for code generation. type MapKeySlice []MapKey -// AddMapKey appends a new MapKey to the slice. If the new key has no name, -// a default name "keyN" is assigned (where N is the new length). If the name -// conflicts with an existing key, a numeric suffix is appended to resolve it. +// AddMapKey appends a new map key to the slice, automatically deduplicating +// both Name (used as function parameter names) and FieldName (used as struct +// field names in LevelIndex key structs). +// +// Deduplication is needed because different map levels may share the same key +// name. For example, given the following nested proto maps where country_map +// and item_map both use "ID" as their key name: +// +// message Fruit4Conf { +// map fruit_map = 1; // key field: "FruitType" +// message Fruit { +// map country_map = 2; // key field: "ID" +// message Country { +// map item_map = 3; // key field: "ID" ← same name! +// } +// } +// } +// +// Without dedup, the generated LevelIndex key struct would have duplicate +// field names, causing a compile error: +// +// public readonly struct LevelIndex_Fruit_Country_ItemKey { +// public int Id { get; } // key of protoconf.Fruit4Conf.Fruit.country_map +// public int Id { get; } // key of protoconf.Fruit4Conf.Fruit.Country.item_map — COMPILE ERROR! +// } +// +// With dedup, the conflicting name gets a numeric suffix (the 1-based position +// of the new key in the slice), producing valid C# code: +// +// public readonly struct LevelIndex_Fruit_Country_ItemKey { +// public int Id { get; } // key of protoconf.Fruit4Conf.Fruit.country_map +// public int Id3 { get; } // key of protoconf.Fruit4Conf.Fruit.Country.item_map (renamed from Id) +// } func (s MapKeySlice) AddMapKey(newKey MapKey) MapKeySlice { if newKey.Name == "" { newKey.Name = fmt.Sprintf("key%d", len(s)+1) } + // Deduplicate Name (used as function parameter, e.g., "id" → "id3"). for _, key := range s { if key.Name == newKey.Name { - // rewrite to avoid name confict newKey.Name = fmt.Sprintf("%s%d", newKey.Name, len(s)+1) break } } + // Deduplicate FieldName (used as struct field, e.g., "Id" → "Id3"). + // This is only relevant for multi-column indexes that generate LevelIndex + // key structs; single-column indexes leave FieldName empty. + if newKey.FieldName != "" { + for _, key := range s { + if key.FieldName == newKey.FieldName { + newKey.OrigFieldName = newKey.FieldName + newKey.FieldName = fmt.Sprintf("%s%d", newKey.FieldName, len(s)+1) + break + } + } + } return append(s, newKey) } diff --git a/cmd/protoc-gen-csharp-tableau-loader/indexes/generator.go b/cmd/protoc-gen-csharp-tableau-loader/indexes/generator.go index 29f84787..a76ebbd9 100644 --- a/cmd/protoc-gen-csharp-tableau-loader/indexes/generator.go +++ b/cmd/protoc-gen-csharp-tableau-loader/indexes/generator.go @@ -98,19 +98,23 @@ func (x *Generator) GenIndexTypeDef() { // (k1, k2, k3) with an index at the deepest level, x.keys = [k1, k2, k3]. // // Level containers at depth 1 are keyed by a single scalar (k1), so no - // composite key struct is needed. The deepest level (depth = len(keys)) - // also does not need one, because its full key combination (all keys) - // is already represented by the index's own key struct generated - // separately. Only intermediate depths (2 ≤ depth < len(keys)) require - // a LevelIndex struct that bundles all ancestor keys up to that depth: + // composite key struct is needed. Only depths ≥ 2 require a LevelIndex + // struct that bundles all ancestor keys up to that depth: // // keys = [k1, k2, k3] → struct for depth 2: {k1, k2} + // struct for depth 3: {k1, k2, k3} // keys = [k1, k2, k3, k4] → struct for depth 2: {k1, k2} - // struct for depth 3: {k1, k2, k3} + // struct for depth 3: {k1, k2, k3} + // struct for depth 4: {k1, k2, k3, k4} // // The loop starts at i=2 (depth 2) and creates a struct from keys[:i]. - // It runs len(x.keys)-2 times (0 times when len ≤ 2). - for i := 2; i < len(x.keys); i++ { + // It runs len(x.keys)-1 times (0 times when len ≤ 1). + // + // NOTE: When multiple map levels share the same key name (e.g., two maps + // both keyed by "ID"), the FieldName in x.keys is automatically + // deduplicated by AddMapKey (e.g., "Id" → "Id3"). This ensures the + // generated struct has unique field names. See AddMapKey for details. + for i := 2; i <= len(x.keys); i++ { if i == 2 { x.g.P() x.g.P(helper.Indent(2), "// LevelIndex keys.") @@ -121,7 +125,11 @@ func (x *Generator) GenIndexTypeDef() { x.g.P(helper.Indent(2), "{") keys := x.keys[:i] for _, key := range keys { - x.g.P(helper.Indent(3), "public ", key.Type, " ", key.FieldName, " { get; }") + comment := fmt.Sprintf("// key of %s", key.Fd.FullName()) + if key.OrigFieldName != "" { + comment += fmt.Sprintf(" (renamed from %s)", key.OrigFieldName) + } + x.g.P(helper.Indent(3), "public ", key.Type, " ", key.FieldName, " { get; } ", comment) } x.g.P() x.g.P(helper.Indent(3), "public ", keyType, "(", keys.GenGetParams(), ")") diff --git a/cmd/protoc-gen-csharp-tableau-loader/indexes/index.go b/cmd/protoc-gen-csharp-tableau-loader/indexes/index.go index d5fb3a7f..5a73cdcc 100644 --- a/cmd/protoc-gen-csharp-tableau-loader/indexes/index.go +++ b/cmd/protoc-gen-csharp-tableau-loader/indexes/index.go @@ -91,7 +91,7 @@ func (x *Generator) genIndexTypeDef() { x.g.P(helper.Indent(2), "private ", mapType, " ", x.indexContainerName(index, 0), " = new ", mapType, "();") x.g.P() - for i := 1; i < lm.MapDepth; i++ { + for i := 1; i < lm.LeveledContainerDepth(); i++ { if i == 1 { x.g.P(helper.Indent(2), "private Dictionary<", x.keys[0].Type, ", ", mapType, "> ", x.indexContainerName(index, i), " = new Dictionary<", x.keys[0].Type, ", ", mapType, ">();") } else { @@ -113,7 +113,7 @@ func (x *Generator) genIndexLoader() { for lm := x.descriptor.LevelMessage; lm != nil; lm = lm.NextLevel { for _, index := range lm.Indexes { x.g.P(helper.Indent(3), x.indexContainerName(index, 0), ".Clear();") - for i := 1; i < lm.MapDepth; i++ { + for i := 1; i < lm.LeveledContainerDepth(); i++ { x.g.P(helper.Indent(3), x.indexContainerName(index, i), ".Clear();") } } @@ -199,7 +199,7 @@ func (x *Generator) genLoader(lm *index.LevelMessage, index *index.LevelIndex, i x.g.P(helper.Indent(ident+1), "existingList : ", x.indexContainerName(index, 0), "[", key, "] = new List<", valueType, ">();") x.g.P(helper.Indent(ident+1), "list.Add(", parentDataName, ");") x.g.P(helper.Indent(ident), "}") - for i := 1; i < lm.MapDepth; i++ { + for i := 1; i < lm.LeveledContainerDepth(); i++ { x.g.P(helper.Indent(ident), "{") if i == 1 { x.g.P(helper.Indent(ident+1), "var map = ", x.indexContainerName(index, i), ".TryGetValue(k1, out var existingMap) ?") @@ -244,7 +244,7 @@ func (x *Generator) genIndexSorter() { x.g.P(helper.Indent(4), "itemList.Sort(", sorter, ");") x.g.P(helper.Indent(3), "}") // Iterate all leveled containers. - for i := 1; i < lm.MapDepth; i++ { + for i := 1; i < lm.LeveledContainerDepth(); i++ { x.g.P(helper.Indent(3), "foreach (var itemDict in ", x.indexContainerName(index, i), ".Values)") x.g.P(helper.Indent(3), "{") x.g.P(helper.Indent(4), "foreach (var itemList in itemDict.Values)") @@ -300,7 +300,7 @@ func (x *Generator) genIndexFinders() { x.g.P(helper.Indent(2), "public ", mapValueType, "? FindFirst", index.Name(), "(", params, ") =>") x.g.P(helper.Indent(3), "Find", index.Name(), "(", args, ")?.FirstOrDefault();") - for i := 1; i < lm.MapDepth; i++ { + for i := 1; i < lm.LeveledContainerDepth(); i++ { indexContainerName := x.indexContainerName(index, i) partKeys := x.keys[:i] partParams := partKeys.GenGetParams() diff --git a/cmd/protoc-gen-csharp-tableau-loader/indexes/ordered_index.go b/cmd/protoc-gen-csharp-tableau-loader/indexes/ordered_index.go index d139a283..3f060465 100644 --- a/cmd/protoc-gen-csharp-tableau-loader/indexes/ordered_index.go +++ b/cmd/protoc-gen-csharp-tableau-loader/indexes/ordered_index.go @@ -88,7 +88,7 @@ func (x *Generator) genOrderedIndexTypeDef() { x.g.P(helper.Indent(2), "private ", mapType, " ", x.orderedIndexContainerName(index, 0), " = new ", mapType, "();") x.g.P() - for i := 1; i < lm.MapDepth; i++ { + for i := 1; i < lm.LeveledContainerDepth(); i++ { if i == 1 { x.g.P(helper.Indent(2), "private Dictionary<", x.keys[0].Type, ", ", mapType, "> ", x.orderedIndexContainerName(index, i), " = new Dictionary<", x.keys[0].Type, ", ", mapType, ">();") } else { @@ -109,8 +109,8 @@ func (x *Generator) genOrderedIndexLoader() { x.g.P(helper.Indent(3), "// OrderedIndex init.") for lm := x.descriptor.LevelMessage; lm != nil; lm = lm.NextLevel { for _, index := range lm.OrderedIndexes { - x.g.P(helper.Indent(3), x.orderedIndexContainerName(index, 0), ".Clear();") - for i := 1; i < lm.MapDepth; i++ { + x.g.P(helper.Indent(3), x.orderedIndexContainerName(index, 0), ".Clear();") + for i := 1; i < lm.LeveledContainerDepth(); i++ { x.g.P(helper.Indent(3), x.orderedIndexContainerName(index, i), ".Clear();") } } @@ -196,7 +196,7 @@ func (x *Generator) genOrderedIndexLoaderCommon(lm *index.LevelMessage, index *i x.g.P(helper.Indent(ident+1), "existingList : ", x.orderedIndexContainerName(index, 0), "[", key, "] = new List<", valueType, ">();") x.g.P(helper.Indent(ident+1), "list.Add(", parentDataName, ");") x.g.P(helper.Indent(ident), "}") - for i := 1; i < lm.MapDepth; i++ { + for i := 1; i < lm.LeveledContainerDepth(); i++ { x.g.P(helper.Indent(ident), "{") if i == 1 { x.g.P(helper.Indent(ident+1), "var map = ", x.orderedIndexContainerName(index, i), ".TryGetValue(k1, out var existingMap) ?") @@ -240,8 +240,8 @@ func (x *Generator) genOrderedIndexSorter() { x.g.P(helper.Indent(3), "{") x.g.P(helper.Indent(4), "itemList.Sort(", sorter, ");") x.g.P(helper.Indent(3), "}") - // Iterate all leveled containers. - for i := 1; i < lm.MapDepth; i++ { + // Iterate all leveled containers. + for i := 1; i < lm.LeveledContainerDepth(); i++ { x.g.P(helper.Indent(3), "foreach (var itemDict in ", x.orderedIndexContainerName(index, i), ".Values)") x.g.P(helper.Indent(3), "{") x.g.P(helper.Indent(4), "foreach (var itemList in itemDict.Values)") @@ -297,7 +297,7 @@ func (x *Generator) genOrderedIndexFinders() { x.g.P(helper.Indent(2), "public ", mapValueType, "? FindFirst", index.Name(), "(", params, ") =>") x.g.P(helper.Indent(3), "Find", index.Name(), "(", args, ")?.FirstOrDefault();") - for i := 1; i < lm.MapDepth; i++ { + for i := 1; i < lm.LeveledContainerDepth(); i++ { indexContainerName := x.orderedIndexContainerName(index, i) partKeys := x.keys[:i] partParams := partKeys.GenGetParams() diff --git a/cmd/protoc-gen-go-tableau-loader/helper/helper.go b/cmd/protoc-gen-go-tableau-loader/helper/helper.go index 99ebcd69..e84d1cef 100644 --- a/cmd/protoc-gen-go-tableau-loader/helper/helper.go +++ b/cmd/protoc-gen-go-tableau-loader/helper/helper.go @@ -259,25 +259,73 @@ func ParseLeveledMapPrefix(md protoreflect.MessageDescriptor, mapFd protoreflect } type MapKey struct { - Type string - Name string - FieldName string // multi-colunm index only - Fd protoreflect.FieldDescriptor // the map field descriptor this key belongs to + Type string + Name string + FieldName string // multi-column index only (may be deduplicated, e.g., "Id" → "Id3") + OrigFieldName string // original FieldName before deduplication (empty if not renamed) + Fd protoreflect.FieldDescriptor // the map field descriptor this key belongs to } type MapKeySlice []MapKey +// AddMapKey appends a new map key to the slice, automatically deduplicating +// both Name (used as function parameter names) and FieldName (used as struct +// field names in LevelIndex key structs). +// +// Deduplication is needed because different map levels may share the same key +// name. For example, given the following nested proto maps where country_map +// and item_map both use "ID" as their key name: +// +// message Fruit4Conf { +// map fruit_map = 1; // key field: "FruitType" +// message Fruit { +// map country_map = 2; // key field: "ID" +// message Country { +// map item_map = 3; // key field: "ID" ← same name! +// } +// } +// } +// +// Without dedup, the generated LevelIndex key struct would have duplicate +// field names, causing a compile error: +// +// type Fruit4Conf_LevelIndex_Fruit_Country_ItemKey struct { +// FruitType int32 // key of protoconf.Fruit4Conf.fruit_map +// Id int32 // key of protoconf.Fruit4Conf.Fruit.country_map +// Id int32 // key of protoconf.Fruit4Conf.Fruit.Country.item_map — COMPILE ERROR! +// } +// +// With dedup, the conflicting name gets a numeric suffix (the 1-based position +// of the new key in the slice), producing valid Go code: +// +// type Fruit4Conf_LevelIndex_Fruit_Country_ItemKey struct { +// FruitType int32 // key of protoconf.Fruit4Conf.fruit_map +// Id int32 // key of protoconf.Fruit4Conf.Fruit.country_map +// Id3 int32 // key of protoconf.Fruit4Conf.Fruit.Country.item_map (renamed from Id) +// } func (s MapKeySlice) AddMapKey(newKey MapKey) MapKeySlice { if newKey.Name == "" { newKey.Name = fmt.Sprintf("key%d", len(s)+1) } + // Deduplicate Name (used as function parameter, e.g., "id" → "id3"). for _, key := range s { if key.Name == newKey.Name { - // rewrite to avoid name confict newKey.Name = fmt.Sprintf("%s%d", newKey.Name, len(s)+1) break } } + // Deduplicate FieldName (used as struct field, e.g., "Id" → "Id3"). + // This is only relevant for multi-column indexes that generate LevelIndex + // key structs; single-column indexes leave FieldName empty. + if newKey.FieldName != "" { + for _, key := range s { + if key.FieldName == newKey.FieldName { + newKey.OrigFieldName = newKey.FieldName + newKey.FieldName = fmt.Sprintf("%s%d", newKey.FieldName, len(s)+1) + break + } + } + } return append(s, newKey) } diff --git a/cmd/protoc-gen-go-tableau-loader/indexes/generator.go b/cmd/protoc-gen-go-tableau-loader/indexes/generator.go index a09671ea..b7431e0b 100644 --- a/cmd/protoc-gen-go-tableau-loader/indexes/generator.go +++ b/cmd/protoc-gen-go-tableau-loader/indexes/generator.go @@ -98,12 +98,19 @@ func (x *Generator) GenIndexTypeDef() { // struct that bundles all ancestor keys up to that depth: // // keys = [k1, k2, k3] → struct for depth 2: {k1, k2} + // struct for depth 3: {k1, k2, k3} // keys = [k1, k2, k3, k4] → struct for depth 2: {k1, k2} // struct for depth 3: {k1, k2, k3} + // struct for depth 4: {k1, k2, k3, k4} // // The loop starts at i=2 (depth 2) and creates a struct from keys[:i]. - // It runs len(x.keys)-2 times (0 times when len ≤ 2). - for i := 2; i < len(x.keys); i++ { + // It runs len(x.keys)-1 times (0 times when len ≤ 1). + // + // NOTE: When multiple map levels share the same key name (e.g., two maps + // both keyed by "ID"), the FieldName in x.keys is automatically + // deduplicated by AddMapKey (e.g., "Id" → "Id3"). This ensures the + // generated struct has unique field names. See AddMapKey for details. + for i := 2; i <= len(x.keys); i++ { if i == 2 { x.g.P() x.g.P("// LevelIndex keys.") @@ -113,7 +120,11 @@ func (x *Generator) GenIndexTypeDef() { keys := x.keys[:i] x.g.P("type ", keyType, " struct {") for _, key := range keys { - x.g.P(key.FieldName, " ", key.Type) + comment := fmt.Sprintf("// key of %s", key.Fd.FullName()) + if key.OrigFieldName != "" { + comment += fmt.Sprintf(" (renamed from %s)", key.OrigFieldName) + } + x.g.P(key.FieldName, " ", key.Type, " ", comment) } x.g.P("}") } diff --git a/cmd/protoc-gen-go-tableau-loader/indexes/index.go b/cmd/protoc-gen-go-tableau-loader/indexes/index.go index 56420e5a..2251791d 100644 --- a/cmd/protoc-gen-go-tableau-loader/indexes/index.go +++ b/cmd/protoc-gen-go-tableau-loader/indexes/index.go @@ -83,7 +83,7 @@ func (x *Generator) genIndexField() { for lm := x.descriptor.LevelMessage; lm != nil; lm = lm.NextLevel { for _, index := range lm.Indexes { x.g.P(x.indexContainerName(index, 0), " ", x.indexMapType(index)) - for i := 1; i < lm.MapDepth; i++ { + for i := 1; i < lm.LeveledContainerDepth(); i++ { if i == 1 { x.g.P(x.indexContainerName(index, i), " map[", x.keys[0].Type, "]", x.indexMapType(index)) } else { @@ -104,7 +104,7 @@ func (x *Generator) genIndexLoader() { for lm := x.descriptor.LevelMessage; lm != nil; lm = lm.NextLevel { for _, index := range lm.Indexes { x.g.P("x.", x.indexContainerName(index, 0), " = make(", x.indexMapType(index), ")") - for i := 1; i < lm.MapDepth; i++ { + for i := 1; i < lm.LeveledContainerDepth(); i++ { if i == 1 { x.g.P("x.", x.indexContainerName(index, i), " = make(map[", x.keys[0].Type, "]", x.indexMapType(index), ")") } else { @@ -188,7 +188,7 @@ func (x *Generator) generateOneMulticolumnIndex(lm *index.LevelMessage, index *i func (x *Generator) genIndexLoaderCommon(lm *index.LevelMessage, index *index.LevelIndex, parentDataName string) { indexContainerName := x.indexContainerName(index, 0) x.g.P("x.", indexContainerName, "[key] = append(x.", indexContainerName, "[key], ", parentDataName, ")") - for i := 1; i < lm.MapDepth; i++ { + for i := 1; i < lm.LeveledContainerDepth(); i++ { indexContainerName := x.indexContainerName(index, i) if i == 1 { x.g.P("if x.", indexContainerName, "[k1] == nil {") @@ -235,7 +235,7 @@ func (x *Generator) genIndexSorter() { x.g.P(helper.SortPackage.Ident("Slice"), "(itemList, ", indexContainerName, "Sorter(itemList))") x.g.P("}") // Iterate all leveled containers. - for i := 1; i < lm.MapDepth; i++ { + for i := 1; i < lm.LeveledContainerDepth(); i++ { x.g.P("for _, itemMap := range x.", x.indexContainerName(index, i), " {") x.g.P("for _, itemList := range itemMap {") x.g.P(helper.SortPackage.Ident("Slice"), "(itemList, ", indexContainerName, "Sorter(itemList))") @@ -289,7 +289,7 @@ func (x *Generator) genIndexFinders() { x.g.P("}") x.g.P() - for i := 1; i < lm.MapDepth; i++ { + for i := 1; i < lm.LeveledContainerDepth(); i++ { indexContainerName := x.indexContainerName(index, i) partKeys := x.keys[:i] partParams := partKeys.GenGetParams() diff --git a/cmd/protoc-gen-go-tableau-loader/indexes/ordered_index.go b/cmd/protoc-gen-go-tableau-loader/indexes/ordered_index.go index 49e765e8..b022632a 100644 --- a/cmd/protoc-gen-go-tableau-loader/indexes/ordered_index.go +++ b/cmd/protoc-gen-go-tableau-loader/indexes/ordered_index.go @@ -107,7 +107,7 @@ func (x *Generator) genOrderedIndexField() { for lm := x.descriptor.LevelMessage; lm != nil; lm = lm.NextLevel { for _, index := range lm.OrderedIndexes { x.g.P(x.orderedIndexContainerName(index, 0), " *", x.orderedIndexMapType(index)) - for i := 1; i < lm.MapDepth; i++ { + for i := 1; i < lm.LeveledContainerDepth(); i++ { if i == 1 { x.g.P(x.orderedIndexContainerName(index, i), " map[", x.keys[0].Type, "]*", x.orderedIndexMapType(index)) } else { @@ -127,8 +127,8 @@ func (x *Generator) genOrderedIndexLoader() { x.g.P("// OrderedIndex init.") for lm := x.descriptor.LevelMessage; lm != nil; lm = lm.NextLevel { for _, index := range lm.OrderedIndexes { - x.g.P("x.", x.orderedIndexContainerName(index, 0), " = ", helper.TreeMapPackage.Ident(x.mapCtor(index)), "[", x.orderedIndexMapKeyType(index), ", []*", x.mapValueType(index), "]()") - for i := 1; i < lm.MapDepth; i++ { + x.g.P("x.", x.orderedIndexContainerName(index, 0), " = ", helper.TreeMapPackage.Ident(x.mapCtor(index)), "[", x.orderedIndexMapKeyType(index), ", []*", x.mapValueType(index), "]()") + for i := 1; i < lm.LeveledContainerDepth(); i++ { if i == 1 { x.g.P("x.", x.orderedIndexContainerName(index, i), " = make(map[", x.keys[0].Type, "]*", x.orderedIndexMapType(index), ")") } else { @@ -214,7 +214,7 @@ func (x *Generator) genOrderedIndexLoaderCommon(lm *index.LevelMessage, index *i indexContainerName := x.orderedIndexContainerName(index, 0) x.g.P("value, _ := x.", indexContainerName, ".Get(key)") x.g.P("x.", indexContainerName, ".Put(key, append(value, ", parentDataName, "))") - for i := 1; i < lm.MapDepth; i++ { + for i := 1; i < lm.LeveledContainerDepth(); i++ { orderedIndexContainerName := x.orderedIndexContainerName(index, i) valueName := orderedIndexContainerName + "Value" if i == 1 { @@ -265,7 +265,7 @@ func (x *Generator) genOrderedIndexSorter() { x.g.P("return true") x.g.P("})") // Iterate all leveled containers. - for i := 1; i < lm.MapDepth; i++ { + for i := 1; i < lm.LeveledContainerDepth(); i++ { x.g.P("for _, itemMap := range x.", x.orderedIndexContainerName(index, i), " {") x.g.P("itemMap.Range(func(key ", x.orderedIndexMapKeyType(index), ", itemList []*", x.mapValueType(index), ") bool {") x.g.P(helper.SortPackage.Ident("Slice"), "(itemList, ", indexContainerName, "Sorter(itemList))") @@ -321,7 +321,7 @@ func (x *Generator) genOrderedIndexFinders() { x.g.P("}") x.g.P() - for i := 1; i < lm.MapDepth; i++ { + for i := 1; i < lm.LeveledContainerDepth(); i++ { orderedIndexContainerName := x.orderedIndexContainerName(index, i) partKeys := x.keys[:i] partParams := partKeys.GenGetParams() diff --git a/internal/index/descriptor.go b/internal/index/descriptor.go index 208b731b..38d60fd6 100644 --- a/internal/index/descriptor.go +++ b/internal/index/descriptor.go @@ -56,9 +56,14 @@ type LevelMessage struct { // Depth is the 0-based depth of message hierarchy. // For example, the top-level message has Depth=0, the next level has Depth=1, and so on. Depth int - // MapDepth is the 0-based map depth of message hierarchy. - // It only increments when the current level is entered via a map field (i.e., FD.IsMap()). - // For example, the top-level message has MapDepth=0, the next level (if entered via map) has MapDepth=1, and so on. + // MapDepth is the number of map fields from the root to this level (inclusive). + // It only increments when the current level is entered via a map field (FD.IsMap()), + // so list fields do NOT increase MapDepth. + // + // Examples (root has MapDepth=0): + // map -> map -> map : MapDepth = 1, 2, 3 + // map -> list -> map : MapDepth = 1, 1, 2 + // map -> list : MapDepth = 1, 1 MapDepth int } @@ -82,25 +87,53 @@ func (l *LevelMessage) NeedGenAnyIndex() bool { return l.NeedGenIndex() || l.NeedGenOrderedIndex() } -// NeedMapKeyForIndex checks if the map key variable at this level is needed -// by any deeper level's regular index's leveled containers. -// It finds the first level whose MapDepth > l.MapDepth+1 (i.e., at least 2 map -// levels deeper), then delegates to NeedGenIndex which recursively checks that -// level and all deeper levels for indexes. +// LeveledContainerDepth returns the depth used for generating leveled index +// containers and finders. Leveled containers allow querying indexes scoped to +// a specific upper map key (e.g., FindItem1(mapKey, indexKey)). +// +// The returned value N means the codegen loop "for i := 1; i < N; i++" will +// produce N-1 leveled containers. +// +// For map levels, MapDepth already includes the current map, so N = MapDepth. +// For non-map levels (e.g., list), MapDepth does NOT include the current level, +// but all upper maps still need leveled containers, so N = MapDepth + 1. +// +// Examples: +// +// map(1) -> map(2) : map(2).LCD = 2 → 1 leveled container +// map(1) -> list(1) : list(1).LCD = 2 → 1 leveled container +// map(1) -> map(2) -> map(3) -> list(3) : list(3).LCD = 4 → 3 leveled containers +func (l *LevelMessage) LeveledContainerDepth() int { + if l.FD != nil && l.FD.IsMap() { + return l.MapDepth + } + if l.MapDepth > 0 { + return l.MapDepth + 1 + } + return 0 +} + +// NeedMapKeyForIndex checks whether the map key variable declared at this +// level is referenced by any deeper level's leveled index containers. +// +// It walks forward through the level chain and finds the first level whose +// LeveledContainerDepth exceeds l.MapDepth — meaning that level requires at +// least one more leveled container than what the current map provides. If that +// level (or any of its descendants) has a regular index, the map key is needed. func (l *LevelMessage) NeedMapKeyForIndex() bool { for lm := l.NextLevel; lm != nil; lm = lm.NextLevel { - if lm.MapDepth > l.MapDepth { + if lm.LeveledContainerDepth() > l.MapDepth { return lm.NeedGenIndex() } } return false } -// NeedMapKeyForOrderedIndex checks if the map key variable at this level is -// needed by any deeper level's ordered index's leveled containers. +// NeedMapKeyForOrderedIndex is the ordered-index counterpart of +// NeedMapKeyForIndex. See NeedMapKeyForIndex for the algorithm description. func (l *LevelMessage) NeedMapKeyForOrderedIndex() bool { for lm := l.NextLevel; lm != nil; lm = lm.NextLevel { - if lm.MapDepth > l.MapDepth { + if lm.LeveledContainerDepth() > l.MapDepth { return lm.NeedGenOrderedIndex() } } diff --git a/test/cpp-tableau-loader/src/protoconf/hub.pc.h b/test/cpp-tableau-loader/src/protoconf/hub.pc.h index 72861076..d5a8d55b 100644 --- a/test/cpp-tableau-loader/src/protoconf/hub.pc.h +++ b/test/cpp-tableau-loader/src/protoconf/hub.pc.h @@ -130,6 +130,10 @@ class FruitConf; template <> const std::shared_ptr Hub::Get() const; +class Fruit6Conf; +template <> +const std::shared_ptr Hub::Get() const; + class Fruit2Conf; template <> const std::shared_ptr Hub::Get() const; @@ -199,6 +203,7 @@ class MessagerContainer { std::shared_ptr hero_conf_; std::shared_ptr hero_base_conf_; std::shared_ptr fruit_conf_; + std::shared_ptr fruit_6_conf_; std::shared_ptr fruit_2_conf_; std::shared_ptr fruit_3_conf_; std::shared_ptr fruit_4_conf_; diff --git a/test/cpp-tableau-loader/src/protoconf/hub_shard0.pc.cc b/test/cpp-tableau-loader/src/protoconf/hub_shard0.pc.cc index 29405e2f..c2efd03c 100644 --- a/test/cpp-tableau-loader/src/protoconf/hub_shard0.pc.cc +++ b/test/cpp-tableau-loader/src/protoconf/hub_shard0.pc.cc @@ -25,6 +25,11 @@ const std::shared_ptr Hub::Get() const { return GetMessagerContainerWithProvider()->fruit_conf_; } +template <> +const std::shared_ptr Hub::Get() const { + return GetMessagerContainerWithProvider()->fruit_6_conf_; +} + template <> const std::shared_ptr Hub::Get() const { return GetMessagerContainerWithProvider()->fruit_2_conf_; @@ -54,6 +59,7 @@ void MessagerContainer::InitShard0() { hero_conf_ = std::dynamic_pointer_cast(GetMessager(HeroConf::Name())); hero_base_conf_ = std::dynamic_pointer_cast(GetMessager(HeroBaseConf::Name())); fruit_conf_ = std::dynamic_pointer_cast(GetMessager(FruitConf::Name())); + fruit_6_conf_ = std::dynamic_pointer_cast(GetMessager(Fruit6Conf::Name())); fruit_2_conf_ = std::dynamic_pointer_cast(GetMessager(Fruit2Conf::Name())); fruit_3_conf_ = std::dynamic_pointer_cast(GetMessager(Fruit3Conf::Name())); fruit_4_conf_ = std::dynamic_pointer_cast(GetMessager(Fruit4Conf::Name())); @@ -65,6 +71,7 @@ void Registry::InitShard0() { Register(); Register(); Register(); + Register(); Register(); Register(); Register(); diff --git a/test/cpp-tableau-loader/src/protoconf/index_conf.pc.cc b/test/cpp-tableau-loader/src/protoconf/index_conf.pc.cc index 00527afc..00b8f0f5 100644 --- a/test/cpp-tableau-loader/src/protoconf/index_conf.pc.cc +++ b/test/cpp-tableau-loader/src/protoconf/index_conf.pc.cc @@ -21,30 +21,56 @@ bool FruitConf::Load(const std::filesystem::path& dir, Format fmt, std::shared_p } bool FruitConf::ProcessAfterLoad() { + // Index init. + index_item_map_.clear(); + index_item_map1_.clear(); + for (auto&& item1 : data_.fruit_map()) { + auto k1 = item1.first; + for (auto&& item2 : item1.second.item_map()) { + { + // Index: Price + index_item_map_[item2.second.price()].push_back(&item2.second); + index_item_map1_[k1][item2.second.price()].push_back(&item2.second); + } + } + } + // Index(sort): Price + auto index_item_map_sorter = [](const protoconf::FruitConf::Fruit::Item* a, + const protoconf::FruitConf::Fruit::Item* b) { + return a->id() < b->id(); + }; + for (auto&& item : index_item_map_) { + std::sort(item.second.begin(), item.second.end(), index_item_map_sorter); + } + for (auto&& item : index_item_map1_) { + for (auto&& item1 : item.second) { + std::sort(item1.second.begin(), item1.second.end(), index_item_map_sorter); + } + } // OrderedIndex init. - ordered_index_item_map_.clear(); - ordered_index_item_map1_.clear(); + ordered_index_ordered_fruit_map_.clear(); + ordered_index_ordered_fruit_map1_.clear(); for (auto&& item1 : data_.fruit_map()) { auto k1 = item1.first; for (auto&& item2 : item1.second.item_map()) { { - // OrderedIndex: Price - ordered_index_item_map_[item2.second.price()].push_back(&item2.second); - ordered_index_item_map1_[k1][item2.second.price()].push_back(&item2.second); + // OrderedIndex: Price@OrderedFruit + ordered_index_ordered_fruit_map_[item2.second.price()].push_back(&item2.second); + ordered_index_ordered_fruit_map1_[k1][item2.second.price()].push_back(&item2.second); } } } - // OrderedIndex(sort): Price - auto ordered_index_item_map_sorter = [](const protoconf::FruitConf::Fruit::Item* a, - const protoconf::FruitConf::Fruit::Item* b) { + // OrderedIndex(sort): Price@OrderedFruit + auto ordered_index_ordered_fruit_map_sorter = [](const protoconf::FruitConf::Fruit::Item* a, + const protoconf::FruitConf::Fruit::Item* b) { return a->id() < b->id(); }; - for (auto&& item : ordered_index_item_map_) { - std::sort(item.second.begin(), item.second.end(), ordered_index_item_map_sorter); + for (auto&& item : ordered_index_ordered_fruit_map_) { + std::sort(item.second.begin(), item.second.end(), ordered_index_ordered_fruit_map_sorter); } - for (auto&& item : ordered_index_item_map1_) { + for (auto&& item : ordered_index_ordered_fruit_map1_) { for (auto&& item1 : item.second) { - std::sort(item1.second.begin(), item1.second.end(), ordered_index_item_map_sorter); + std::sort(item1.second.begin(), item1.second.end(), ordered_index_ordered_fruit_map_sorter); } } return true; @@ -70,12 +96,12 @@ const protoconf::FruitConf::Fruit::Item* FruitConf::Get(int32_t fruit_type, int3 return &iter->second; } -// OrderedIndex: Price -const FruitConf::OrderedIndex_ItemMap& FruitConf::FindItemMap() const { return ordered_index_item_map_; } +// Index: Price +const FruitConf::Index_ItemMap& FruitConf::FindItemMap() const { return index_item_map_; } -const FruitConf::OrderedIndex_ItemVector* FruitConf::FindItem(int32_t price) const { - auto iter = ordered_index_item_map_.find(price); - if (iter == ordered_index_item_map_.end()) { +const FruitConf::Index_ItemVector* FruitConf::FindItem(int32_t price) const { + auto iter = index_item_map_.find(price); + if (iter == index_item_map_.end()) { return nullptr; } return &iter->second; @@ -89,15 +115,15 @@ const protoconf::FruitConf::Fruit::Item* FruitConf::FindFirstItem(int32_t price) return conf->front(); } -const FruitConf::OrderedIndex_ItemMap* FruitConf::FindItemMap(int32_t fruit_type) const { - auto iter = ordered_index_item_map1_.find(fruit_type); - if (iter == ordered_index_item_map1_.end()) { +const FruitConf::Index_ItemMap* FruitConf::FindItemMap(int32_t fruit_type) const { + auto iter = index_item_map1_.find(fruit_type); + if (iter == index_item_map1_.end()) { return nullptr; } return &iter->second; } -const FruitConf::OrderedIndex_ItemVector* FruitConf::FindItem(int32_t fruit_type, int32_t price) const { +const FruitConf::Index_ItemVector* FruitConf::FindItem(int32_t fruit_type, int32_t price) const { auto map = FindItemMap(fruit_type); if (map == nullptr) { return nullptr; @@ -117,6 +143,221 @@ const protoconf::FruitConf::Fruit::Item* FruitConf::FindFirstItem(int32_t fruit_ return conf->front(); } +// OrderedIndex: Price@OrderedFruit +const FruitConf::OrderedIndex_OrderedFruitMap& FruitConf::FindOrderedFruitMap() const { return ordered_index_ordered_fruit_map_; } + +const FruitConf::OrderedIndex_OrderedFruitVector* FruitConf::FindOrderedFruit(int32_t price) const { + auto iter = ordered_index_ordered_fruit_map_.find(price); + if (iter == ordered_index_ordered_fruit_map_.end()) { + return nullptr; + } + return &iter->second; +} + +const protoconf::FruitConf::Fruit::Item* FruitConf::FindFirstOrderedFruit(int32_t price) const { + auto conf = FindOrderedFruit(price); + if (conf == nullptr || conf->empty()) { + return nullptr; + } + return conf->front(); +} + +const FruitConf::OrderedIndex_OrderedFruitMap* FruitConf::FindOrderedFruitMap(int32_t fruit_type) const { + auto iter = ordered_index_ordered_fruit_map1_.find(fruit_type); + if (iter == ordered_index_ordered_fruit_map1_.end()) { + return nullptr; + } + return &iter->second; +} + +const FruitConf::OrderedIndex_OrderedFruitVector* FruitConf::FindOrderedFruit(int32_t fruit_type, int32_t price) const { + auto map = FindOrderedFruitMap(fruit_type); + if (map == nullptr) { + return nullptr; + } + auto iter = map->find(price); + if (iter == map->end()) { + return nullptr; + } + return &iter->second; +} + +const protoconf::FruitConf::Fruit::Item* FruitConf::FindFirstOrderedFruit(int32_t fruit_type, int32_t price) const { + auto conf = FindOrderedFruit(fruit_type, price); + if (conf == nullptr || conf->empty()) { + return nullptr; + } + return conf->front(); +} + +const std::string Fruit6Conf::kProtoName = protoconf::Fruit6Conf::GetDescriptor()->name(); + +bool Fruit6Conf::Load(const std::filesystem::path& dir, Format fmt, std::shared_ptr options /* = nullptr */) { + tableau::util::TimeProfiler profiler; + bool loaded = LoadMessagerInDir(data_, dir, fmt, options); + bool ok = loaded ? ProcessAfterLoad() : false; + stats_.duration = profiler.Elapse(); + return ok; +} + +bool Fruit6Conf::ProcessAfterLoad() { + // Index init. + index_item_map_.clear(); + index_item_map1_.clear(); + for (auto&& item1 : data_.fruit_map()) { + auto k1 = item1.first; + for (auto&& item2 : item1.second.item_list()) { + { + // Index: Price + index_item_map_[item2.price()].push_back(&item2); + index_item_map1_[k1][item2.price()].push_back(&item2); + } + } + } + // Index(sort): Price + auto index_item_map_sorter = [](const protoconf::Fruit6Conf::Fruit::Item* a, + const protoconf::Fruit6Conf::Fruit::Item* b) { + return a->id() < b->id(); + }; + for (auto&& item : index_item_map_) { + std::sort(item.second.begin(), item.second.end(), index_item_map_sorter); + } + for (auto&& item : index_item_map1_) { + for (auto&& item1 : item.second) { + std::sort(item1.second.begin(), item1.second.end(), index_item_map_sorter); + } + } + // OrderedIndex init. + ordered_index_ordered_fruit_map_.clear(); + ordered_index_ordered_fruit_map1_.clear(); + for (auto&& item1 : data_.fruit_map()) { + auto k1 = item1.first; + for (auto&& item2 : item1.second.item_list()) { + { + // OrderedIndex: Price@OrderedFruit + ordered_index_ordered_fruit_map_[item2.price()].push_back(&item2); + ordered_index_ordered_fruit_map1_[k1][item2.price()].push_back(&item2); + } + } + } + // OrderedIndex(sort): Price@OrderedFruit + auto ordered_index_ordered_fruit_map_sorter = [](const protoconf::Fruit6Conf::Fruit::Item* a, + const protoconf::Fruit6Conf::Fruit::Item* b) { + return a->id() < b->id(); + }; + for (auto&& item : ordered_index_ordered_fruit_map_) { + std::sort(item.second.begin(), item.second.end(), ordered_index_ordered_fruit_map_sorter); + } + for (auto&& item : ordered_index_ordered_fruit_map1_) { + for (auto&& item1 : item.second) { + std::sort(item1.second.begin(), item1.second.end(), ordered_index_ordered_fruit_map_sorter); + } + } + return true; +} + +const protoconf::Fruit6Conf::Fruit* Fruit6Conf::Get(int32_t fruit_type) const { + auto iter = data_.fruit_map().find(fruit_type); + if (iter == data_.fruit_map().end()) { + return nullptr; + } + return &iter->second; +} + +// Index: Price +const Fruit6Conf::Index_ItemMap& Fruit6Conf::FindItemMap() const { return index_item_map_; } + +const Fruit6Conf::Index_ItemVector* Fruit6Conf::FindItem(int32_t price) const { + auto iter = index_item_map_.find(price); + if (iter == index_item_map_.end()) { + return nullptr; + } + return &iter->second; +} + +const protoconf::Fruit6Conf::Fruit::Item* Fruit6Conf::FindFirstItem(int32_t price) const { + auto conf = FindItem(price); + if (conf == nullptr || conf->empty()) { + return nullptr; + } + return conf->front(); +} + +const Fruit6Conf::Index_ItemMap* Fruit6Conf::FindItemMap(int32_t fruit_type) const { + auto iter = index_item_map1_.find(fruit_type); + if (iter == index_item_map1_.end()) { + return nullptr; + } + return &iter->second; +} + +const Fruit6Conf::Index_ItemVector* Fruit6Conf::FindItem(int32_t fruit_type, int32_t price) const { + auto map = FindItemMap(fruit_type); + if (map == nullptr) { + return nullptr; + } + auto iter = map->find(price); + if (iter == map->end()) { + return nullptr; + } + return &iter->second; +} + +const protoconf::Fruit6Conf::Fruit::Item* Fruit6Conf::FindFirstItem(int32_t fruit_type, int32_t price) const { + auto conf = FindItem(fruit_type, price); + if (conf == nullptr || conf->empty()) { + return nullptr; + } + return conf->front(); +} + +// OrderedIndex: Price@OrderedFruit +const Fruit6Conf::OrderedIndex_OrderedFruitMap& Fruit6Conf::FindOrderedFruitMap() const { return ordered_index_ordered_fruit_map_; } + +const Fruit6Conf::OrderedIndex_OrderedFruitVector* Fruit6Conf::FindOrderedFruit(int32_t price) const { + auto iter = ordered_index_ordered_fruit_map_.find(price); + if (iter == ordered_index_ordered_fruit_map_.end()) { + return nullptr; + } + return &iter->second; +} + +const protoconf::Fruit6Conf::Fruit::Item* Fruit6Conf::FindFirstOrderedFruit(int32_t price) const { + auto conf = FindOrderedFruit(price); + if (conf == nullptr || conf->empty()) { + return nullptr; + } + return conf->front(); +} + +const Fruit6Conf::OrderedIndex_OrderedFruitMap* Fruit6Conf::FindOrderedFruitMap(int32_t fruit_type) const { + auto iter = ordered_index_ordered_fruit_map1_.find(fruit_type); + if (iter == ordered_index_ordered_fruit_map1_.end()) { + return nullptr; + } + return &iter->second; +} + +const Fruit6Conf::OrderedIndex_OrderedFruitVector* Fruit6Conf::FindOrderedFruit(int32_t fruit_type, int32_t price) const { + auto map = FindOrderedFruitMap(fruit_type); + if (map == nullptr) { + return nullptr; + } + auto iter = map->find(price); + if (iter == map->end()) { + return nullptr; + } + return &iter->second; +} + +const protoconf::Fruit6Conf::Fruit::Item* Fruit6Conf::FindFirstOrderedFruit(int32_t fruit_type, int32_t price) const { + auto conf = FindOrderedFruit(fruit_type, price); + if (conf == nullptr || conf->empty()) { + return nullptr; + } + return conf->front(); +} + const std::string Fruit2Conf::kProtoName = protoconf::Fruit2Conf::GetDescriptor()->name(); bool Fruit2Conf::Load(const std::filesystem::path& dir, Format fmt, std::shared_ptr options /* = nullptr */) { @@ -130,21 +371,26 @@ bool Fruit2Conf::Load(const std::filesystem::path& dir, Format fmt, std::shared_ bool Fruit2Conf::ProcessAfterLoad() { // Index init. index_country_map_.clear(); + index_country_map1_.clear(); index_attr_map_.clear(); index_attr_map1_.clear(); + index_attr_map2_.clear(); for (auto&& item1 : data_.fruit_map()) { auto k1 = item1.first; for (auto&& item2 : item1.second.country_list()) { { // Index: CountryName index_country_map_[item2.name()].push_back(&item2); + index_country_map1_[k1][item2.name()].push_back(&item2); } for (auto&& item3 : item2.item_map()) { + auto k2 = item3.first; for (auto&& item4 : item3.second.attr_list()) { { // Index: CountryItemAttrName index_attr_map_[item4.name()].push_back(&item4); index_attr_map1_[k1][item4.name()].push_back(&item4); + index_attr_map2_[{k1, k2}][item4.name()].push_back(&item4); } } } @@ -208,6 +454,34 @@ const protoconf::Fruit2Conf::Fruit::Country* Fruit2Conf::FindFirstCountry(const return conf->front(); } +const Fruit2Conf::Index_CountryMap* Fruit2Conf::FindCountryMap(int32_t fruit_type) const { + auto iter = index_country_map1_.find(fruit_type); + if (iter == index_country_map1_.end()) { + return nullptr; + } + return &iter->second; +} + +const Fruit2Conf::Index_CountryVector* Fruit2Conf::FindCountry(int32_t fruit_type, const std::string& name) const { + auto map = FindCountryMap(fruit_type); + if (map == nullptr) { + return nullptr; + } + auto iter = map->find(name); + if (iter == map->end()) { + return nullptr; + } + return &iter->second; +} + +const protoconf::Fruit2Conf::Fruit::Country* Fruit2Conf::FindFirstCountry(int32_t fruit_type, const std::string& name) const { + auto conf = FindCountry(fruit_type, name); + if (conf == nullptr || conf->empty()) { + return nullptr; + } + return conf->front(); +} + // Index: CountryItemAttrName const Fruit2Conf::Index_AttrMap& Fruit2Conf::FindAttrMap() const { return index_attr_map_; } @@ -255,6 +529,34 @@ const protoconf::Fruit2Conf::Fruit::Country::Item::Attr* Fruit2Conf::FindFirstAt return conf->front(); } +const Fruit2Conf::Index_AttrMap* Fruit2Conf::FindAttrMap(int32_t fruit_type, int32_t id) const { + auto iter = index_attr_map2_.find({fruit_type, id}); + if (iter == index_attr_map2_.end()) { + return nullptr; + } + return &iter->second; +} + +const Fruit2Conf::Index_AttrVector* Fruit2Conf::FindAttr(int32_t fruit_type, int32_t id, const std::string& name) const { + auto map = FindAttrMap(fruit_type, id); + if (map == nullptr) { + return nullptr; + } + auto iter = map->find(name); + if (iter == map->end()) { + return nullptr; + } + return &iter->second; +} + +const protoconf::Fruit2Conf::Fruit::Country::Item::Attr* Fruit2Conf::FindFirstAttr(int32_t fruit_type, int32_t id, const std::string& name) const { + auto conf = FindAttr(fruit_type, id, name); + if (conf == nullptr || conf->empty()) { + return nullptr; + } + return conf->front(); +} + // OrderedIndex: CountryItemPrice const Fruit2Conf::OrderedIndex_ItemMap& Fruit2Conf::FindItemMap() const { return ordered_index_item_map_; } @@ -316,6 +618,7 @@ bool Fruit3Conf::ProcessAfterLoad() { // Index init. index_country_map_.clear(); index_attr_map_.clear(); + index_attr_map1_.clear(); for (auto&& item1 : data_.fruit_list()) { for (auto&& item2 : item1.country_list()) { { @@ -323,10 +626,12 @@ bool Fruit3Conf::ProcessAfterLoad() { index_country_map_[item2.name()].push_back(&item2); } for (auto&& item3 : item2.item_map()) { + auto k1 = item3.first; for (auto&& item4 : item3.second.attr_list()) { { // Index: CountryItemAttrName index_attr_map_[item4.name()].push_back(&item4); + index_attr_map1_[k1][item4.name()].push_back(&item4); } } } @@ -393,6 +698,34 @@ const protoconf::Fruit3Conf::Fruit::Country::Item::Attr* Fruit3Conf::FindFirstAt return conf->front(); } +const Fruit3Conf::Index_AttrMap* Fruit3Conf::FindAttrMap(int32_t id) const { + auto iter = index_attr_map1_.find(id); + if (iter == index_attr_map1_.end()) { + return nullptr; + } + return &iter->second; +} + +const Fruit3Conf::Index_AttrVector* Fruit3Conf::FindAttr(int32_t id, const std::string& name) const { + auto map = FindAttrMap(id); + if (map == nullptr) { + return nullptr; + } + auto iter = map->find(name); + if (iter == map->end()) { + return nullptr; + } + return &iter->second; +} + +const protoconf::Fruit3Conf::Fruit::Country::Item::Attr* Fruit3Conf::FindFirstAttr(int32_t id, const std::string& name) const { + auto conf = FindAttr(id, name); + if (conf == nullptr || conf->empty()) { + return nullptr; + } + return conf->front(); +} + // OrderedIndex: CountryItemPrice const Fruit3Conf::OrderedIndex_ItemMap& Fruit3Conf::FindItemMap() const { return ordered_index_item_map_; } @@ -429,6 +762,7 @@ bool Fruit4Conf::ProcessAfterLoad() { index_attr_map_.clear(); index_attr_map1_.clear(); index_attr_map2_.clear(); + index_attr_map3_.clear(); for (auto&& item1 : data_.fruit_map()) { auto k1 = item1.first; for (auto&& item2 : item1.second.country_map()) { @@ -439,12 +773,14 @@ bool Fruit4Conf::ProcessAfterLoad() { index_country_map1_[k1][item2.second.name()].push_back(&item2.second); } for (auto&& item3 : item2.second.item_map()) { + auto k3 = item3.first; for (auto&& item4 : item3.second.attr_list()) { { // Index: CountryItemAttrName index_attr_map_[item4.name()].push_back(&item4); index_attr_map1_[k1][item4.name()].push_back(&item4); index_attr_map2_[{k1, k2}][item4.name()].push_back(&item4); + index_attr_map3_[{k1, k2, k3}][item4.name()].push_back(&item4); } } } @@ -643,6 +979,34 @@ const protoconf::Fruit4Conf::Fruit::Country::Item::Attr* Fruit4Conf::FindFirstAt return conf->front(); } +const Fruit4Conf::Index_AttrMap* Fruit4Conf::FindAttrMap(int32_t fruit_type, int32_t id, int32_t id3) const { + auto iter = index_attr_map3_.find({fruit_type, id, id3}); + if (iter == index_attr_map3_.end()) { + return nullptr; + } + return &iter->second; +} + +const Fruit4Conf::Index_AttrVector* Fruit4Conf::FindAttr(int32_t fruit_type, int32_t id, int32_t id3, const std::string& name) const { + auto map = FindAttrMap(fruit_type, id, id3); + if (map == nullptr) { + return nullptr; + } + auto iter = map->find(name); + if (iter == map->end()) { + return nullptr; + } + return &iter->second; +} + +const protoconf::Fruit4Conf::Fruit::Country::Item::Attr* Fruit4Conf::FindFirstAttr(int32_t fruit_type, int32_t id, int32_t id3, const std::string& name) const { + auto conf = FindAttr(fruit_type, id, id3, name); + if (conf == nullptr || conf->empty()) { + return nullptr; + } + return conf->front(); +} + // OrderedIndex: CountryItemPrice const Fruit4Conf::OrderedIndex_ItemMap& Fruit4Conf::FindItemMap() const { return ordered_index_item_map_; } diff --git a/test/cpp-tableau-loader/src/protoconf/index_conf.pc.h b/test/cpp-tableau-loader/src/protoconf/index_conf.pc.h index 76e6431e..09b22539 100644 --- a/test/cpp-tableau-loader/src/protoconf/index_conf.pc.h +++ b/test/cpp-tableau-loader/src/protoconf/index_conf.pc.h @@ -31,30 +31,123 @@ class FruitConf final : public Messager { static const std::string kProtoName; protoconf::FruitConf data_; - // OrderedIndex accessers. - // OrderedIndex: Price + // Index accessers. + // Index: Price public: - using OrderedIndex_ItemVector = std::vector; - using OrderedIndex_ItemMap = std::map; - // Finds the ordered index: key(Price) to value(OrderedIndex_ItemVector) map. + using Index_ItemVector = std::vector; + using Index_ItemMap = std::unordered_map; + // Finds the index: key(Price) to value(Index_ItemVector) hashmap. // One key may correspond to multiple values, which are represented by a vector. - const OrderedIndex_ItemMap& FindItemMap() const; + const Index_ItemMap& FindItemMap() const; // Finds a vector of all values of the given key(s). - const OrderedIndex_ItemVector* FindItem(int32_t price) const; + const Index_ItemVector* FindItem(int32_t price) const; // Finds the first value of the given key(s). const protoconf::FruitConf::Fruit::Item* FindFirstItem(int32_t price) const; - // Finds the ordered index: key(Price) to value(OrderedIndex_ItemVector), + // Finds the index: key(Price) to value(Index_ItemVector), + // which is the upper 1st-level hashmap specified by (fruit_type). + // One key may correspond to multiple values, which are represented by a vector. + const Index_ItemMap* FindItemMap(int32_t fruit_type) const; + // Finds a vector of all values of the given key(s) in the upper 1st-level hashmap specified by (fruit_type). + const Index_ItemVector* FindItem(int32_t fruit_type, int32_t price) const; + // Finds the first value of the given key(s) in the upper 1st-level hashmap specified by (fruit_type). + const protoconf::FruitConf::Fruit::Item* FindFirstItem(int32_t fruit_type, int32_t price) const; + + private: + Index_ItemMap index_item_map_; + std::unordered_map index_item_map1_; + + // OrderedIndex accessers. + // OrderedIndex: Price@OrderedFruit + public: + using OrderedIndex_OrderedFruitVector = std::vector; + using OrderedIndex_OrderedFruitMap = std::map; + // Finds the ordered index: key(Price@OrderedFruit) to value(OrderedIndex_OrderedFruitVector) map. + // One key may correspond to multiple values, which are represented by a vector. + const OrderedIndex_OrderedFruitMap& FindOrderedFruitMap() const; + // Finds a vector of all values of the given key(s). + const OrderedIndex_OrderedFruitVector* FindOrderedFruit(int32_t price) const; + // Finds the first value of the given key(s). + const protoconf::FruitConf::Fruit::Item* FindFirstOrderedFruit(int32_t price) const; + // Finds the ordered index: key(Price@OrderedFruit) to value(OrderedIndex_OrderedFruitVector), // which is the upper 1st-level map specified by (fruit_type). // One key may correspond to multiple values, which are represented by a vector. - const OrderedIndex_ItemMap* FindItemMap(int32_t fruit_type) const; + const OrderedIndex_OrderedFruitMap* FindOrderedFruitMap(int32_t fruit_type) const; // Finds a vector of all values of the given key(s) in the upper 1st-level map specified by (fruit_type). - const OrderedIndex_ItemVector* FindItem(int32_t fruit_type, int32_t price) const; + const OrderedIndex_OrderedFruitVector* FindOrderedFruit(int32_t fruit_type, int32_t price) const; // Finds the first value of the given key(s) in the upper 1st-level map specified by (fruit_type). - const protoconf::FruitConf::Fruit::Item* FindFirstItem(int32_t fruit_type, int32_t price) const; + const protoconf::FruitConf::Fruit::Item* FindFirstOrderedFruit(int32_t fruit_type, int32_t price) const; private: - OrderedIndex_ItemMap ordered_index_item_map_; - std::unordered_map ordered_index_item_map1_; + OrderedIndex_OrderedFruitMap ordered_index_ordered_fruit_map_; + std::unordered_map ordered_index_ordered_fruit_map1_; +}; + +class Fruit6Conf final : public Messager { + public: + static const std::string& Name() { return kProtoName; } + virtual bool Load(const std::filesystem::path& dir, Format fmt, std::shared_ptr options = nullptr) override; + const protoconf::Fruit6Conf& Data() const { return data_; } + const google::protobuf::Message* Message() const override { return &data_; } + + private: + virtual bool ProcessAfterLoad() override; + + public: + const protoconf::Fruit6Conf::Fruit* Get(int32_t fruit_type) const; + + private: + static const std::string kProtoName; + protoconf::Fruit6Conf data_; + + // Index accessers. + // Index: Price + public: + using Index_ItemVector = std::vector; + using Index_ItemMap = std::unordered_map; + // Finds the index: key(Price) to value(Index_ItemVector) hashmap. + // One key may correspond to multiple values, which are represented by a vector. + const Index_ItemMap& FindItemMap() const; + // Finds a vector of all values of the given key(s). + const Index_ItemVector* FindItem(int32_t price) const; + // Finds the first value of the given key(s). + const protoconf::Fruit6Conf::Fruit::Item* FindFirstItem(int32_t price) const; + // Finds the index: key(Price) to value(Index_ItemVector), + // which is the upper 1st-level hashmap specified by (fruit_type). + // One key may correspond to multiple values, which are represented by a vector. + const Index_ItemMap* FindItemMap(int32_t fruit_type) const; + // Finds a vector of all values of the given key(s) in the upper 1st-level hashmap specified by (fruit_type). + const Index_ItemVector* FindItem(int32_t fruit_type, int32_t price) const; + // Finds the first value of the given key(s) in the upper 1st-level hashmap specified by (fruit_type). + const protoconf::Fruit6Conf::Fruit::Item* FindFirstItem(int32_t fruit_type, int32_t price) const; + + private: + Index_ItemMap index_item_map_; + std::unordered_map index_item_map1_; + + // OrderedIndex accessers. + // OrderedIndex: Price@OrderedFruit + public: + using OrderedIndex_OrderedFruitVector = std::vector; + using OrderedIndex_OrderedFruitMap = std::map; + // Finds the ordered index: key(Price@OrderedFruit) to value(OrderedIndex_OrderedFruitVector) map. + // One key may correspond to multiple values, which are represented by a vector. + const OrderedIndex_OrderedFruitMap& FindOrderedFruitMap() const; + // Finds a vector of all values of the given key(s). + const OrderedIndex_OrderedFruitVector* FindOrderedFruit(int32_t price) const; + // Finds the first value of the given key(s). + const protoconf::Fruit6Conf::Fruit::Item* FindFirstOrderedFruit(int32_t price) const; + // Finds the ordered index: key(Price@OrderedFruit) to value(OrderedIndex_OrderedFruitVector), + // which is the upper 1st-level map specified by (fruit_type). + // One key may correspond to multiple values, which are represented by a vector. + const OrderedIndex_OrderedFruitMap* FindOrderedFruitMap(int32_t fruit_type) const; + // Finds a vector of all values of the given key(s) in the upper 1st-level map specified by (fruit_type). + const OrderedIndex_OrderedFruitVector* FindOrderedFruit(int32_t fruit_type, int32_t price) const; + // Finds the first value of the given key(s) in the upper 1st-level map specified by (fruit_type). + const protoconf::Fruit6Conf::Fruit::Item* FindFirstOrderedFruit(int32_t fruit_type, int32_t price) const; + + private: + OrderedIndex_OrderedFruitMap ordered_index_ordered_fruit_map_; + std::unordered_map ordered_index_ordered_fruit_map1_; }; class Fruit2Conf final : public Messager { @@ -74,6 +167,25 @@ class Fruit2Conf final : public Messager { static const std::string kProtoName; protoconf::Fruit2Conf data_; + // LevelIndex keys. + public: + struct LevelIndex_Fruit_Country_ItemKey { + int32_t fruit_type; // key of protoconf.Fruit2Conf.fruit_map + int32_t id; // key of protoconf.Fruit2Conf.Fruit.Country.item_map +#if __cplusplus >= 202002L + bool operator==(const LevelIndex_Fruit_Country_ItemKey& other) const = default; +#else + bool operator==(const LevelIndex_Fruit_Country_ItemKey& other) const { + return std::tie(fruit_type, id) == std::tie(other.fruit_type, other.id); + } +#endif + }; + struct LevelIndex_Fruit_Country_ItemKeyHasher { + std::size_t operator()(const LevelIndex_Fruit_Country_ItemKey& key) const { + return util::SugaredHashCombine(key.fruit_type, key.id); + } + }; + // Index accessers. // Index: CountryName public: @@ -86,9 +198,18 @@ class Fruit2Conf final : public Messager { const Index_CountryVector* FindCountry(const std::string& name) const; // Finds the first value of the given key(s). const protoconf::Fruit2Conf::Fruit::Country* FindFirstCountry(const std::string& name) const; + // Finds the index: key(CountryName) to value(Index_CountryVector), + // which is the upper 1st-level hashmap specified by (fruit_type). + // One key may correspond to multiple values, which are represented by a vector. + const Index_CountryMap* FindCountryMap(int32_t fruit_type) const; + // Finds a vector of all values of the given key(s) in the upper 1st-level hashmap specified by (fruit_type). + const Index_CountryVector* FindCountry(int32_t fruit_type, const std::string& name) const; + // Finds the first value of the given key(s) in the upper 1st-level hashmap specified by (fruit_type). + const protoconf::Fruit2Conf::Fruit::Country* FindFirstCountry(int32_t fruit_type, const std::string& name) const; private: Index_CountryMap index_country_map_; + std::unordered_map index_country_map1_; // Index: CountryItemAttrName public: @@ -109,10 +230,19 @@ class Fruit2Conf final : public Messager { const Index_AttrVector* FindAttr(int32_t fruit_type, const std::string& name) const; // Finds the first value of the given key(s) in the upper 1st-level hashmap specified by (fruit_type). const protoconf::Fruit2Conf::Fruit::Country::Item::Attr* FindFirstAttr(int32_t fruit_type, const std::string& name) const; + // Finds the index: key(CountryItemAttrName) to value(Index_AttrVector), + // which is the upper 2nd-level hashmap specified by (fruit_type, id). + // One key may correspond to multiple values, which are represented by a vector. + const Index_AttrMap* FindAttrMap(int32_t fruit_type, int32_t id) const; + // Finds a vector of all values of the given key(s) in the upper 2nd-level hashmap specified by (fruit_type, id). + const Index_AttrVector* FindAttr(int32_t fruit_type, int32_t id, const std::string& name) const; + // Finds the first value of the given key(s) in the upper 2nd-level hashmap specified by (fruit_type, id). + const protoconf::Fruit2Conf::Fruit::Country::Item::Attr* FindFirstAttr(int32_t fruit_type, int32_t id, const std::string& name) const; private: Index_AttrMap index_attr_map_; std::unordered_map index_attr_map1_; + std::unordered_map index_attr_map2_; // OrderedIndex accessers. // OrderedIndex: CountryItemPrice @@ -182,9 +312,18 @@ class Fruit3Conf final : public Messager { const Index_AttrVector* FindAttr(const std::string& name) const; // Finds the first value of the given key(s). const protoconf::Fruit3Conf::Fruit::Country::Item::Attr* FindFirstAttr(const std::string& name) const; + // Finds the index: key(CountryItemAttrName) to value(Index_AttrVector), + // which is the upper 1st-level hashmap specified by (id). + // One key may correspond to multiple values, which are represented by a vector. + const Index_AttrMap* FindAttrMap(int32_t id) const; + // Finds a vector of all values of the given key(s) in the upper 1st-level hashmap specified by (id). + const Index_AttrVector* FindAttr(int32_t id, const std::string& name) const; + // Finds the first value of the given key(s) in the upper 1st-level hashmap specified by (id). + const protoconf::Fruit3Conf::Fruit::Country::Item::Attr* FindFirstAttr(int32_t id, const std::string& name) const; private: Index_AttrMap index_attr_map_; + std::unordered_map index_attr_map1_; // OrderedIndex accessers. // OrderedIndex: CountryItemPrice @@ -225,8 +364,8 @@ class Fruit4Conf final : public Messager { // LevelIndex keys. public: struct LevelIndex_Fruit_CountryKey { - int32_t fruit_type; - int32_t id; + int32_t fruit_type; // key of protoconf.Fruit4Conf.fruit_map + int32_t id; // key of protoconf.Fruit4Conf.Fruit.country_map #if __cplusplus >= 202002L bool operator==(const LevelIndex_Fruit_CountryKey& other) const = default; #else @@ -240,6 +379,23 @@ class Fruit4Conf final : public Messager { return util::SugaredHashCombine(key.fruit_type, key.id); } }; + struct LevelIndex_Fruit_Country_ItemKey { + int32_t fruit_type; // key of protoconf.Fruit4Conf.fruit_map + int32_t id; // key of protoconf.Fruit4Conf.Fruit.country_map + int32_t id3; // key of protoconf.Fruit4Conf.Fruit.Country.item_map (renamed from id) +#if __cplusplus >= 202002L + bool operator==(const LevelIndex_Fruit_Country_ItemKey& other) const = default; +#else + bool operator==(const LevelIndex_Fruit_Country_ItemKey& other) const { + return std::tie(fruit_type, id, id3) == std::tie(other.fruit_type, other.id, other.id3); + } +#endif + }; + struct LevelIndex_Fruit_Country_ItemKeyHasher { + std::size_t operator()(const LevelIndex_Fruit_Country_ItemKey& key) const { + return util::SugaredHashCombine(key.fruit_type, key.id, key.id3); + } + }; // Index accessers. // Index: CountryName @@ -293,11 +449,20 @@ class Fruit4Conf final : public Messager { const Index_AttrVector* FindAttr(int32_t fruit_type, int32_t id, const std::string& name) const; // Finds the first value of the given key(s) in the upper 2nd-level hashmap specified by (fruit_type, id). const protoconf::Fruit4Conf::Fruit::Country::Item::Attr* FindFirstAttr(int32_t fruit_type, int32_t id, const std::string& name) const; + // Finds the index: key(CountryItemAttrName) to value(Index_AttrVector), + // which is the upper 3rd-level hashmap specified by (fruit_type, id, id3). + // One key may correspond to multiple values, which are represented by a vector. + const Index_AttrMap* FindAttrMap(int32_t fruit_type, int32_t id, int32_t id3) const; + // Finds a vector of all values of the given key(s) in the upper 3rd-level hashmap specified by (fruit_type, id, id3). + const Index_AttrVector* FindAttr(int32_t fruit_type, int32_t id, int32_t id3, const std::string& name) const; + // Finds the first value of the given key(s) in the upper 3rd-level hashmap specified by (fruit_type, id, id3). + const protoconf::Fruit4Conf::Fruit::Country::Item::Attr* FindFirstAttr(int32_t fruit_type, int32_t id, int32_t id3, const std::string& name) const; private: Index_AttrMap index_attr_map_; std::unordered_map index_attr_map1_; std::unordered_map index_attr_map2_; + std::unordered_map index_attr_map3_; // OrderedIndex accessers. // OrderedIndex: CountryItemPrice @@ -384,6 +549,7 @@ class Fruit5Conf final : public Messager { namespace protoconf { // Here are some type aliases for easy use. using FruitConfMgr = tableau::FruitConf; +using Fruit6ConfMgr = tableau::Fruit6Conf; using Fruit2ConfMgr = tableau::Fruit2Conf; using Fruit3ConfMgr = tableau::Fruit3Conf; using Fruit4ConfMgr = tableau::Fruit4Conf; diff --git a/test/cpp-tableau-loader/src/protoconf/test_conf.pc.cc b/test/cpp-tableau-loader/src/protoconf/test_conf.pc.cc index 646d806a..41590b4e 100644 --- a/test/cpp-tableau-loader/src/protoconf/test_conf.pc.cc +++ b/test/cpp-tableau-loader/src/protoconf/test_conf.pc.cc @@ -47,6 +47,7 @@ bool ActivityConf::ProcessAfterLoad() { index_award_map_.clear(); index_award_map1_.clear(); index_award_map2_.clear(); + index_award_map3_.clear(); for (auto&& item1 : data_.activity_map()) { auto k1 = item1.first; { @@ -66,12 +67,14 @@ bool ActivityConf::ProcessAfterLoad() { index_named_chapter_map1_[k1][item2.second.chapter_name()].push_back(&item2.second); } for (auto&& item3 : item2.second.section_map()) { + auto k3 = item3.first; for (auto&& item4 : item3.second.section_item_list()) { { // Index: SectionItemID@Award index_award_map_[item4.id()].push_back(&item4); index_award_map1_[k1][item4.id()].push_back(&item4); index_award_map2_[{k1, k2}][item4.id()].push_back(&item4); + index_award_map3_[{k1, k2, k3}][item4.id()].push_back(&item4); } } } @@ -365,6 +368,34 @@ const protoconf::Section::SectionItem* ActivityConf::FindFirstAward(uint64_t act return conf->front(); } +const ActivityConf::Index_AwardMap* ActivityConf::FindAwardMap(uint64_t activity_id, uint32_t chapter_id, uint32_t section_id) const { + auto iter = index_award_map3_.find({activity_id, chapter_id, section_id}); + if (iter == index_award_map3_.end()) { + return nullptr; + } + return &iter->second; +} + +const ActivityConf::Index_AwardVector* ActivityConf::FindAward(uint64_t activity_id, uint32_t chapter_id, uint32_t section_id, uint32_t id) const { + auto map = FindAwardMap(activity_id, chapter_id, section_id); + if (map == nullptr) { + return nullptr; + } + auto iter = map->find(id); + if (iter == map->end()) { + return nullptr; + } + return &iter->second; +} + +const protoconf::Section::SectionItem* ActivityConf::FindFirstAward(uint64_t activity_id, uint32_t chapter_id, uint32_t section_id, uint32_t id) const { + auto conf = FindAward(activity_id, chapter_id, section_id, id); + if (conf == nullptr || conf->empty()) { + return nullptr; + } + return conf->front(); +} + const std::string ChapterConf::kProtoName = protoconf::ChapterConf::GetDescriptor()->name(); bool ChapterConf::Load(const std::filesystem::path& dir, Format fmt, std::shared_ptr options /* = nullptr */) { diff --git a/test/cpp-tableau-loader/src/protoconf/test_conf.pc.h b/test/cpp-tableau-loader/src/protoconf/test_conf.pc.h index ee6c9d49..c8c91e2e 100644 --- a/test/cpp-tableau-loader/src/protoconf/test_conf.pc.h +++ b/test/cpp-tableau-loader/src/protoconf/test_conf.pc.h @@ -56,8 +56,8 @@ class ActivityConf final : public Messager { // LevelIndex keys. public: struct LevelIndex_Activity_ChapterKey { - uint64_t activity_id; - uint32_t chapter_id; + uint64_t activity_id; // key of protoconf.ActivityConf.activity_map + uint32_t chapter_id; // key of protoconf.ActivityConf.Activity.chapter_map #if __cplusplus >= 202002L bool operator==(const LevelIndex_Activity_ChapterKey& other) const = default; #else @@ -71,6 +71,23 @@ class ActivityConf final : public Messager { return util::SugaredHashCombine(key.activity_id, key.chapter_id); } }; + struct LevelIndex_protoconf_SectionKey { + uint64_t activity_id; // key of protoconf.ActivityConf.activity_map + uint32_t chapter_id; // key of protoconf.ActivityConf.Activity.chapter_map + uint32_t section_id; // key of protoconf.ActivityConf.Activity.Chapter.section_map +#if __cplusplus >= 202002L + bool operator==(const LevelIndex_protoconf_SectionKey& other) const = default; +#else + bool operator==(const LevelIndex_protoconf_SectionKey& other) const { + return std::tie(activity_id, chapter_id, section_id) == std::tie(other.activity_id, other.chapter_id, other.section_id); + } +#endif + }; + struct LevelIndex_protoconf_SectionKeyHasher { + std::size_t operator()(const LevelIndex_protoconf_SectionKey& key) const { + return util::SugaredHashCombine(key.activity_id, key.chapter_id, key.section_id); + } + }; // Index accessers. // Index: ActivityName @@ -163,11 +180,20 @@ class ActivityConf final : public Messager { const Index_AwardVector* FindAward(uint64_t activity_id, uint32_t chapter_id, uint32_t id) const; // Finds the first value of the given key(s) in the upper 2nd-level hashmap specified by (activity_id, chapter_id). const protoconf::Section::SectionItem* FindFirstAward(uint64_t activity_id, uint32_t chapter_id, uint32_t id) const; + // Finds the index: key(SectionItemID@Award) to value(Index_AwardVector), + // which is the upper 3rd-level hashmap specified by (activity_id, chapter_id, section_id). + // One key may correspond to multiple values, which are represented by a vector. + const Index_AwardMap* FindAwardMap(uint64_t activity_id, uint32_t chapter_id, uint32_t section_id) const; + // Finds a vector of all values of the given key(s) in the upper 3rd-level hashmap specified by (activity_id, chapter_id, section_id). + const Index_AwardVector* FindAward(uint64_t activity_id, uint32_t chapter_id, uint32_t section_id, uint32_t id) const; + // Finds the first value of the given key(s) in the upper 3rd-level hashmap specified by (activity_id, chapter_id, section_id). + const protoconf::Section::SectionItem* FindFirstAward(uint64_t activity_id, uint32_t chapter_id, uint32_t section_id, uint32_t id) const; private: Index_AwardMap index_award_map_; std::unordered_map index_award_map1_; std::unordered_map index_award_map2_; + std::unordered_map index_award_map3_; }; class ChapterConf final : public Messager { diff --git a/test/csharp-tableau-loader/Program.cs b/test/csharp-tableau-loader/Program.cs index 63f85cdd..6cd17fe7 100644 --- a/test/csharp-tableau-loader/Program.cs +++ b/test/csharp-tableau-loader/Program.cs @@ -139,7 +139,7 @@ static void LoadBin() } if (!heroConf.Load("../testdata/notexist", Tableau.Format.Bin)) { - Console.WriteLine("HeroConf not exist"); + Console.WriteLine("expected: HeroConf not exist"); } } } \ No newline at end of file diff --git a/test/csharp-tableau-loader/tableau/Hub.pc.cs b/test/csharp-tableau-loader/tableau/Hub.pc.cs index 9eff0da9..a076b694 100644 --- a/test/csharp-tableau-loader/tableau/Hub.pc.cs +++ b/test/csharp-tableau-loader/tableau/Hub.pc.cs @@ -21,6 +21,7 @@ internal class MessagerContainer public HeroConf? HeroConf; public HeroBaseConf? HeroBaseConf; public FruitConf? FruitConf; + public Fruit6Conf? Fruit6Conf; public Fruit2Conf? Fruit2Conf; public Fruit3Conf? Fruit3Conf; public Fruit4Conf? Fruit4Conf; @@ -42,6 +43,7 @@ public MessagerContainer(Dictionary? messagerMap = null) HeroConf = InternalGet(messagerMap); HeroBaseConf = InternalGet(messagerMap); FruitConf = InternalGet(messagerMap); + Fruit6Conf = InternalGet(messagerMap); Fruit2Conf = InternalGet(messagerMap); Fruit3Conf = InternalGet(messagerMap); Fruit4Conf = InternalGet(messagerMap); @@ -161,6 +163,8 @@ public bool Load(string dir, Format fmt, in Load.Options? options = null) public FruitConf? GetFruitConf() => _messagerContainer.Value?.FruitConf; + public Fruit6Conf? GetFruit6Conf() => _messagerContainer.Value?.Fruit6Conf; + public Fruit2Conf? GetFruit2Conf() => _messagerContainer.Value?.Fruit2Conf; public Fruit3Conf? GetFruit3Conf() => _messagerContainer.Value?.Fruit3Conf; @@ -229,6 +233,7 @@ public static void Init() Register(); Register(); Register(); + Register(); Register(); Register(); Register(); diff --git a/test/csharp-tableau-loader/tableau/IndexConf.pc.cs b/test/csharp-tableau-loader/tableau/IndexConf.pc.cs index e647e830..fab40217 100644 --- a/test/csharp-tableau-loader/tableau/IndexConf.pc.cs +++ b/test/csharp-tableau-loader/tableau/IndexConf.pc.cs @@ -17,13 +17,21 @@ namespace Tableau /// public class FruitConf : Messager, IMessagerName { + // Index types. + // Index: Price + public class Index_ItemMap : Dictionary> { } + + private Index_ItemMap _indexItemMap = new Index_ItemMap(); + + private Dictionary _indexItemMap1 = new Dictionary(); + // OrderedIndex types. - // OrderedIndex: Price - public class OrderedIndex_ItemMap : SortedDictionary> { } + // OrderedIndex: Price@OrderedFruit + public class OrderedIndex_OrderedFruitMap : SortedDictionary> { } - private OrderedIndex_ItemMap _orderedIndexItemMap = new OrderedIndex_ItemMap(); + private OrderedIndex_OrderedFruitMap _orderedIndexOrderedFruitMap = new OrderedIndex_OrderedFruitMap(); - private Dictionary _orderedIndexItemMap1 = new Dictionary(); + private Dictionary _orderedIndexOrderedFruitMap1 = new Dictionary(); private Protoconf.FruitConf _data = new(); @@ -72,25 +80,65 @@ public override bool Load(string dir, Format fmt, in Load.MessagerOptions? optio /// protected override bool ProcessAfterLoad() { + // Index init. + _indexItemMap.Clear(); + _indexItemMap1.Clear(); + foreach (var item1 in _data.FruitMap) + { + var k1 = item1.Key; + foreach (var item2 in item1.Value.ItemMap) + { + { + // Index: Price + var key = item2.Value.Price; + { + var list = _indexItemMap.TryGetValue(key, out var existingList) ? + existingList : _indexItemMap[key] = new List(); + list.Add(item2.Value); + } + { + var map = _indexItemMap1.TryGetValue(k1, out var existingMap) ? + existingMap : _indexItemMap1[k1] = new Index_ItemMap(); + var list = map.TryGetValue(key, out var existingList) ? + existingList : map[key] = new List(); + list.Add(item2.Value); + } + } + } + } + // Index(sort): Price + Comparison indexItemMapComparison = (a, b) => + (a.Id).CompareTo((b.Id)); + foreach (var itemList in _indexItemMap.Values) + { + itemList.Sort(indexItemMapComparison); + } + foreach (var itemDict in _indexItemMap1.Values) + { + foreach (var itemList in itemDict.Values) + { + itemList.Sort(indexItemMapComparison); + } + } // OrderedIndex init. - _orderedIndexItemMap.Clear(); - _orderedIndexItemMap1.Clear(); + _orderedIndexOrderedFruitMap.Clear(); + _orderedIndexOrderedFruitMap1.Clear(); foreach (var item1 in _data.FruitMap) { var k1 = item1.Key; foreach (var item2 in item1.Value.ItemMap) { { - // OrderedIndex: Price + // OrderedIndex: Price@OrderedFruit var key = item2.Value.Price; { - var list = _orderedIndexItemMap.TryGetValue(key, out var existingList) ? - existingList : _orderedIndexItemMap[key] = new List(); + var list = _orderedIndexOrderedFruitMap.TryGetValue(key, out var existingList) ? + existingList : _orderedIndexOrderedFruitMap[key] = new List(); list.Add(item2.Value); } { - var map = _orderedIndexItemMap1.TryGetValue(k1, out var existingMap) ? - existingMap : _orderedIndexItemMap1[k1] = new OrderedIndex_ItemMap(); + var map = _orderedIndexOrderedFruitMap1.TryGetValue(k1, out var existingMap) ? + existingMap : _orderedIndexOrderedFruitMap1[k1] = new OrderedIndex_OrderedFruitMap(); var list = map.TryGetValue(key, out var existingList) ? existingList : map[key] = new List(); list.Add(item2.Value); @@ -98,18 +146,18 @@ protected override bool ProcessAfterLoad() } } } - // OrderedIndex(sort): Price - Comparison orderedIndexItemMapComparison = (a, b) => + // OrderedIndex(sort): Price@OrderedFruit + Comparison orderedIndexOrderedFruitMapComparison = (a, b) => (a.Id).CompareTo((b.Id)); - foreach (var itemList in _orderedIndexItemMap.Values) + foreach (var itemList in _orderedIndexOrderedFruitMap.Values) { - itemList.Sort(orderedIndexItemMapComparison); + itemList.Sort(orderedIndexOrderedFruitMapComparison); } - foreach (var itemDict in _orderedIndexItemMap1.Values) + foreach (var itemDict in _orderedIndexOrderedFruitMap1.Values) { foreach (var itemList in itemDict.Values) { - itemList.Sort(orderedIndexItemMapComparison); + itemList.Sort(orderedIndexOrderedFruitMapComparison); } } return true; @@ -129,19 +177,19 @@ protected override bool ProcessAfterLoad() public Protoconf.FruitConf.Types.Fruit.Types.Item? Get2(int fruitType, int id) => Get1(fruitType)?.ItemMap?.TryGetValue(id, out var val) == true ? val : null; - // OrderedIndex: Price + // Index: Price /// - /// FindItemMap finds the ordered index: key(Price) to value(Protoconf.FruitConf.Types.Fruit.Types.Item) sorted map. + /// FindItemMap finds the index: key(Price) to value(Protoconf.FruitConf.Types.Fruit.Types.Item) map. /// One key may correspond to multiple values, which are represented by a list. /// - public ref readonly OrderedIndex_ItemMap FindItemMap() => ref _orderedIndexItemMap; + public ref readonly Index_ItemMap FindItemMap() => ref _indexItemMap; /// /// FindItem finds a list of all values of the given key(s). /// public List? FindItem(int price) => - _orderedIndexItemMap.TryGetValue(price, out var value) ? value : null; + _indexItemMap.TryGetValue(price, out var value) ? value : null; /// /// FindFirstItem finds the first value of the given key(s), @@ -151,26 +199,314 @@ protected override bool ProcessAfterLoad() FindItem(price)?.FirstOrDefault(); /// - /// FindItemMap1 finds the ordered index: key(Price) to value(Protoconf.FruitConf.Types.Fruit.Types.Item), - /// which is the upper 1st-level sorted map specified by (fruitType). + /// FindItemMap1 finds the index: key(Price) to value(Protoconf.FruitConf.Types.Fruit.Types.Item), + /// which is the upper 1st-level map specified by (fruitType). /// One key may correspond to multiple values, which are represented by a list. /// - public OrderedIndex_ItemMap? FindItemMap1(int fruitType) => - _orderedIndexItemMap1.TryGetValue(fruitType, out var value) ? value : null; + public Index_ItemMap? FindItemMap1(int fruitType) => + _indexItemMap1.TryGetValue(fruitType, out var value) ? value : null; /// - /// FindItem1 finds a list of all values of the given key(s) in the upper 1st-level sorted map + /// FindItem1 finds a list of all values of the given key(s) in the upper 1st-level map /// specified by (fruitType). /// public List? FindItem1(int fruitType, int price) => FindItemMap1(fruitType)?.TryGetValue(price, out var value) == true ? value : null; /// - /// FindFirstItem1 finds the first value of the given key(s) in the upper 1st-level sorted map + /// FindFirstItem1 finds the first value of the given key(s) in the upper 1st-level map /// specified by (fruitType), or null if no value found. /// public Protoconf.FruitConf.Types.Fruit.Types.Item? FindFirstItem1(int fruitType, int price) => FindItem1(fruitType, price)?.FirstOrDefault(); + + // OrderedIndex: Price@OrderedFruit + + /// + /// FindOrderedFruitMap finds the ordered index: key(Price@OrderedFruit) to value(Protoconf.FruitConf.Types.Fruit.Types.Item) sorted map. + /// One key may correspond to multiple values, which are represented by a list. + /// + public ref readonly OrderedIndex_OrderedFruitMap FindOrderedFruitMap() => ref _orderedIndexOrderedFruitMap; + + /// + /// FindOrderedFruit finds a list of all values of the given key(s). + /// + public List? FindOrderedFruit(int price) => + _orderedIndexOrderedFruitMap.TryGetValue(price, out var value) ? value : null; + + /// + /// FindFirstOrderedFruit finds the first value of the given key(s), + /// or null if no value found. + /// + public Protoconf.FruitConf.Types.Fruit.Types.Item? FindFirstOrderedFruit(int price) => + FindOrderedFruit(price)?.FirstOrDefault(); + + /// + /// FindOrderedFruitMap1 finds the ordered index: key(Price@OrderedFruit) to value(Protoconf.FruitConf.Types.Fruit.Types.Item), + /// which is the upper 1st-level sorted map specified by (fruitType). + /// One key may correspond to multiple values, which are represented by a list. + /// + public OrderedIndex_OrderedFruitMap? FindOrderedFruitMap1(int fruitType) => + _orderedIndexOrderedFruitMap1.TryGetValue(fruitType, out var value) ? value : null; + + /// + /// FindOrderedFruit1 finds a list of all values of the given key(s) in the upper 1st-level sorted map + /// specified by (fruitType). + /// + public List? FindOrderedFruit1(int fruitType, int price) => + FindOrderedFruitMap1(fruitType)?.TryGetValue(price, out var value) == true ? value : null; + + /// + /// FindFirstOrderedFruit1 finds the first value of the given key(s) in the upper 1st-level sorted map + /// specified by (fruitType), or null if no value found. + /// + public Protoconf.FruitConf.Types.Fruit.Types.Item? FindFirstOrderedFruit1(int fruitType, int price) => + FindOrderedFruit1(fruitType, price)?.FirstOrDefault(); + } + + /// + /// Fruit6Conf is a wrapper around protobuf message Protoconf.Fruit6Conf. + /// + public class Fruit6Conf : Messager, IMessagerName + { + // Index types. + // Index: Price + public class Index_ItemMap : Dictionary> { } + + private Index_ItemMap _indexItemMap = new Index_ItemMap(); + + private Dictionary _indexItemMap1 = new Dictionary(); + + // OrderedIndex types. + // OrderedIndex: Price@OrderedFruit + public class OrderedIndex_OrderedFruitMap : SortedDictionary> { } + + private OrderedIndex_OrderedFruitMap _orderedIndexOrderedFruitMap = new OrderedIndex_OrderedFruitMap(); + + private Dictionary _orderedIndexOrderedFruitMap1 = new Dictionary(); + + private Protoconf.Fruit6Conf _data = new(); + + /// + /// Name returns the Fruit6Conf's message name. + /// + public string Name() => Protoconf.Fruit6Conf.Descriptor.Name; + + /// + /// Load loads Fruit6Conf's content in the given dir, based on format and messager options. + /// + public override bool Load(string dir, Format fmt, in Load.MessagerOptions? options = null) + { + var start = DateTime.Now; + try + { + _data = (Protoconf.Fruit6Conf)( + Tableau.Load.LoadMessagerInDir(Protoconf.Fruit6Conf.Descriptor, dir, fmt, options) + ?? throw new InvalidOperationException() + ); + } + catch (Exception ex) + { + if (string.IsNullOrEmpty(Util.GetErrMsg())) + { + Util.SetErrMsg($"failed to load Fruit6Conf: {ex.Message}"); + } + return false; + } + LoadStats.Duration = DateTime.Now - start; + return ProcessAfterLoad(); + } + + /// + /// Data returns the Fruit6Conf's inner message data. + /// + public ref readonly Protoconf.Fruit6Conf Data() => ref _data; + + /// + /// Message returns the Fruit6Conf's inner message data. + /// + public override pb::IMessage? Message() => _data; + + /// + /// ProcessAfterLoad runs after this messager is loaded. + /// + protected override bool ProcessAfterLoad() + { + // Index init. + _indexItemMap.Clear(); + _indexItemMap1.Clear(); + foreach (var item1 in _data.FruitMap) + { + var k1 = item1.Key; + foreach (var item2 in item1.Value.ItemList) + { + { + // Index: Price + var key = item2.Price; + { + var list = _indexItemMap.TryGetValue(key, out var existingList) ? + existingList : _indexItemMap[key] = new List(); + list.Add(item2); + } + { + var map = _indexItemMap1.TryGetValue(k1, out var existingMap) ? + existingMap : _indexItemMap1[k1] = new Index_ItemMap(); + var list = map.TryGetValue(key, out var existingList) ? + existingList : map[key] = new List(); + list.Add(item2); + } + } + } + } + // Index(sort): Price + Comparison indexItemMapComparison = (a, b) => + (a.Id).CompareTo((b.Id)); + foreach (var itemList in _indexItemMap.Values) + { + itemList.Sort(indexItemMapComparison); + } + foreach (var itemDict in _indexItemMap1.Values) + { + foreach (var itemList in itemDict.Values) + { + itemList.Sort(indexItemMapComparison); + } + } + // OrderedIndex init. + _orderedIndexOrderedFruitMap.Clear(); + _orderedIndexOrderedFruitMap1.Clear(); + foreach (var item1 in _data.FruitMap) + { + var k1 = item1.Key; + foreach (var item2 in item1.Value.ItemList) + { + { + // OrderedIndex: Price@OrderedFruit + var key = item2.Price; + { + var list = _orderedIndexOrderedFruitMap.TryGetValue(key, out var existingList) ? + existingList : _orderedIndexOrderedFruitMap[key] = new List(); + list.Add(item2); + } + { + var map = _orderedIndexOrderedFruitMap1.TryGetValue(k1, out var existingMap) ? + existingMap : _orderedIndexOrderedFruitMap1[k1] = new OrderedIndex_OrderedFruitMap(); + var list = map.TryGetValue(key, out var existingList) ? + existingList : map[key] = new List(); + list.Add(item2); + } + } + } + } + // OrderedIndex(sort): Price@OrderedFruit + Comparison orderedIndexOrderedFruitMapComparison = (a, b) => + (a.Id).CompareTo((b.Id)); + foreach (var itemList in _orderedIndexOrderedFruitMap.Values) + { + itemList.Sort(orderedIndexOrderedFruitMapComparison); + } + foreach (var itemDict in _orderedIndexOrderedFruitMap1.Values) + { + foreach (var itemList in itemDict.Values) + { + itemList.Sort(orderedIndexOrderedFruitMapComparison); + } + } + return true; + } + + /// + /// Get1 finds value in the 1st-level map. + /// It will return null if the key is not found. + /// + public Protoconf.Fruit6Conf.Types.Fruit? Get1(int fruitType) => + _data.FruitMap?.TryGetValue(fruitType, out var val) == true ? val : null; + + // Index: Price + + /// + /// FindItemMap finds the index: key(Price) to value(Protoconf.Fruit6Conf.Types.Fruit.Types.Item) map. + /// One key may correspond to multiple values, which are represented by a list. + /// + public ref readonly Index_ItemMap FindItemMap() => ref _indexItemMap; + + /// + /// FindItem finds a list of all values of the given key(s). + /// + public List? FindItem(int price) => + _indexItemMap.TryGetValue(price, out var value) ? value : null; + + /// + /// FindFirstItem finds the first value of the given key(s), + /// or null if no value found. + /// + public Protoconf.Fruit6Conf.Types.Fruit.Types.Item? FindFirstItem(int price) => + FindItem(price)?.FirstOrDefault(); + + /// + /// FindItemMap1 finds the index: key(Price) to value(Protoconf.Fruit6Conf.Types.Fruit.Types.Item), + /// which is the upper 1st-level map specified by (fruitType). + /// One key may correspond to multiple values, which are represented by a list. + /// + public Index_ItemMap? FindItemMap1(int fruitType) => + _indexItemMap1.TryGetValue(fruitType, out var value) ? value : null; + + /// + /// FindItem1 finds a list of all values of the given key(s) in the upper 1st-level map + /// specified by (fruitType). + /// + public List? FindItem1(int fruitType, int price) => + FindItemMap1(fruitType)?.TryGetValue(price, out var value) == true ? value : null; + + /// + /// FindFirstItem1 finds the first value of the given key(s) in the upper 1st-level map + /// specified by (fruitType), or null if no value found. + /// + public Protoconf.Fruit6Conf.Types.Fruit.Types.Item? FindFirstItem1(int fruitType, int price) => + FindItem1(fruitType, price)?.FirstOrDefault(); + + // OrderedIndex: Price@OrderedFruit + + /// + /// FindOrderedFruitMap finds the ordered index: key(Price@OrderedFruit) to value(Protoconf.Fruit6Conf.Types.Fruit.Types.Item) sorted map. + /// One key may correspond to multiple values, which are represented by a list. + /// + public ref readonly OrderedIndex_OrderedFruitMap FindOrderedFruitMap() => ref _orderedIndexOrderedFruitMap; + + /// + /// FindOrderedFruit finds a list of all values of the given key(s). + /// + public List? FindOrderedFruit(int price) => + _orderedIndexOrderedFruitMap.TryGetValue(price, out var value) ? value : null; + + /// + /// FindFirstOrderedFruit finds the first value of the given key(s), + /// or null if no value found. + /// + public Protoconf.Fruit6Conf.Types.Fruit.Types.Item? FindFirstOrderedFruit(int price) => + FindOrderedFruit(price)?.FirstOrDefault(); + + /// + /// FindOrderedFruitMap1 finds the ordered index: key(Price@OrderedFruit) to value(Protoconf.Fruit6Conf.Types.Fruit.Types.Item), + /// which is the upper 1st-level sorted map specified by (fruitType). + /// One key may correspond to multiple values, which are represented by a list. + /// + public OrderedIndex_OrderedFruitMap? FindOrderedFruitMap1(int fruitType) => + _orderedIndexOrderedFruitMap1.TryGetValue(fruitType, out var value) ? value : null; + + /// + /// FindOrderedFruit1 finds a list of all values of the given key(s) in the upper 1st-level sorted map + /// specified by (fruitType). + /// + public List? FindOrderedFruit1(int fruitType, int price) => + FindOrderedFruitMap1(fruitType)?.TryGetValue(price, out var value) == true ? value : null; + + /// + /// FindFirstOrderedFruit1 finds the first value of the given key(s) in the upper 1st-level sorted map + /// specified by (fruitType), or null if no value found. + /// + public Protoconf.Fruit6Conf.Types.Fruit.Types.Item? FindFirstOrderedFruit1(int fruitType, int price) => + FindOrderedFruit1(fruitType, price)?.FirstOrDefault(); } /// @@ -178,12 +514,34 @@ protected override bool ProcessAfterLoad() /// public class Fruit2Conf : Messager, IMessagerName { + + // LevelIndex keys. + public readonly struct LevelIndex_Fruit_Country_ItemKey : IEquatable + { + public int FruitType { get; } // key of protoconf.Fruit2Conf.fruit_map + public int Id { get; } // key of protoconf.Fruit2Conf.Fruit.Country.item_map + + public LevelIndex_Fruit_Country_ItemKey(int fruitType, int id) + { + FruitType = fruitType; + Id = id; + } + + public bool Equals(LevelIndex_Fruit_Country_ItemKey other) => + (FruitType, Id).Equals((other.FruitType, other.Id)); + + public override int GetHashCode() => + (FruitType, Id).GetHashCode(); + } + // Index types. // Index: CountryName public class Index_CountryMap : Dictionary> { } private Index_CountryMap _indexCountryMap = new Index_CountryMap(); + private Dictionary _indexCountryMap1 = new Dictionary(); + // Index: CountryItemAttrName public class Index_AttrMap : Dictionary> { } @@ -191,6 +549,8 @@ public class Index_AttrMap : Dictionary _indexAttrMap1 = new Dictionary(); + private Dictionary _indexAttrMap2 = new Dictionary(); + // OrderedIndex types. // OrderedIndex: CountryItemPrice public class OrderedIndex_ItemMap : SortedDictionary> { } @@ -248,8 +608,10 @@ protected override bool ProcessAfterLoad() { // Index init. _indexCountryMap.Clear(); + _indexCountryMap1.Clear(); _indexAttrMap.Clear(); _indexAttrMap1.Clear(); + _indexAttrMap2.Clear(); foreach (var item1 in _data.FruitMap) { var k1 = item1.Key; @@ -263,9 +625,17 @@ protected override bool ProcessAfterLoad() existingList : _indexCountryMap[key] = new List(); list.Add(item2); } + { + var map = _indexCountryMap1.TryGetValue(k1, out var existingMap) ? + existingMap : _indexCountryMap1[k1] = new Index_CountryMap(); + var list = map.TryGetValue(key, out var existingList) ? + existingList : map[key] = new List(); + list.Add(item2); + } } foreach (var item3 in item2.ItemMap) { + var k2 = item3.Key; foreach (var item4 in item3.Value.AttrList) { { @@ -283,6 +653,14 @@ protected override bool ProcessAfterLoad() existingList : map[key] = new List(); list.Add(item4); } + { + var mapKey = new LevelIndex_Fruit_Country_ItemKey(k1, k2); + var map = _indexAttrMap2.TryGetValue(mapKey, out var existingMap) ? + existingMap : _indexAttrMap2[mapKey] = new Index_AttrMap(); + var list = map.TryGetValue(key, out var existingList) ? + existingList : map[key] = new List(); + list.Add(item4); + } } } } @@ -362,6 +740,28 @@ protected override bool ProcessAfterLoad() public Protoconf.Fruit2Conf.Types.Fruit.Types.Country? FindFirstCountry(string name) => FindCountry(name)?.FirstOrDefault(); + /// + /// FindCountryMap1 finds the index: key(CountryName) to value(Protoconf.Fruit2Conf.Types.Fruit.Types.Country), + /// which is the upper 1st-level map specified by (fruitType). + /// One key may correspond to multiple values, which are represented by a list. + /// + public Index_CountryMap? FindCountryMap1(int fruitType) => + _indexCountryMap1.TryGetValue(fruitType, out var value) ? value : null; + + /// + /// FindCountry1 finds a list of all values of the given key(s) in the upper 1st-level map + /// specified by (fruitType). + /// + public List? FindCountry1(int fruitType, string name) => + FindCountryMap1(fruitType)?.TryGetValue(name, out var value) == true ? value : null; + + /// + /// FindFirstCountry1 finds the first value of the given key(s) in the upper 1st-level map + /// specified by (fruitType), or null if no value found. + /// + public Protoconf.Fruit2Conf.Types.Fruit.Types.Country? FindFirstCountry1(int fruitType, string name) => + FindCountry1(fruitType, name)?.FirstOrDefault(); + // Index: CountryItemAttrName /// @@ -405,6 +805,28 @@ protected override bool ProcessAfterLoad() public Protoconf.Fruit2Conf.Types.Fruit.Types.Country.Types.Item.Types.Attr? FindFirstAttr1(int fruitType, string name) => FindAttr1(fruitType, name)?.FirstOrDefault(); + /// + /// FindAttrMap2 finds the index: key(CountryItemAttrName) to value(Protoconf.Fruit2Conf.Types.Fruit.Types.Country.Types.Item.Types.Attr), + /// which is the upper 2nd-level map specified by (fruitType, id). + /// One key may correspond to multiple values, which are represented by a list. + /// + public Index_AttrMap? FindAttrMap2(int fruitType, int id) => + _indexAttrMap2.TryGetValue(new LevelIndex_Fruit_Country_ItemKey(fruitType, id), out var value) ? value : null; + + /// + /// FindAttr2 finds a list of all values of the given key(s) in the upper 2nd-level map + /// specified by (fruitType, id). + /// + public List? FindAttr2(int fruitType, int id, string name) => + FindAttrMap2(fruitType, id)?.TryGetValue(name, out var value) == true ? value : null; + + /// + /// FindFirstAttr2 finds the first value of the given key(s) in the upper 2nd-level map + /// specified by (fruitType, id), or null if no value found. + /// + public Protoconf.Fruit2Conf.Types.Fruit.Types.Country.Types.Item.Types.Attr? FindFirstAttr2(int fruitType, int id, string name) => + FindAttr2(fruitType, id, name)?.FirstOrDefault(); + // OrderedIndex: CountryItemPrice /// @@ -465,6 +887,8 @@ public class Index_AttrMap : Dictionary _indexAttrMap1 = new Dictionary(); + // OrderedIndex types. // OrderedIndex: CountryItemPrice public class OrderedIndex_ItemMap : SortedDictionary> { } @@ -521,6 +945,7 @@ protected override bool ProcessAfterLoad() // Index init. _indexCountryMap.Clear(); _indexAttrMap.Clear(); + _indexAttrMap1.Clear(); foreach (var item1 in _data.FruitList) { foreach (var item2 in item1.CountryList) @@ -536,6 +961,7 @@ protected override bool ProcessAfterLoad() } foreach (var item3 in item2.ItemMap) { + var k1 = item3.Key; foreach (var item4 in item3.Value.AttrList) { { @@ -546,6 +972,13 @@ protected override bool ProcessAfterLoad() existingList : _indexAttrMap[key] = new List(); list.Add(item4); } + { + var map = _indexAttrMap1.TryGetValue(k1, out var existingMap) ? + existingMap : _indexAttrMap1[k1] = new Index_AttrMap(); + var list = map.TryGetValue(key, out var existingList) ? + existingList : map[key] = new List(); + list.Add(item4); + } } } } @@ -623,6 +1056,28 @@ protected override bool ProcessAfterLoad() public Protoconf.Fruit3Conf.Types.Fruit.Types.Country.Types.Item.Types.Attr? FindFirstAttr(string name) => FindAttr(name)?.FirstOrDefault(); + /// + /// FindAttrMap1 finds the index: key(CountryItemAttrName) to value(Protoconf.Fruit3Conf.Types.Fruit.Types.Country.Types.Item.Types.Attr), + /// which is the upper 1st-level map specified by (id). + /// One key may correspond to multiple values, which are represented by a list. + /// + public Index_AttrMap? FindAttrMap1(int id) => + _indexAttrMap1.TryGetValue(id, out var value) ? value : null; + + /// + /// FindAttr1 finds a list of all values of the given key(s) in the upper 1st-level map + /// specified by (id). + /// + public List? FindAttr1(int id, string name) => + FindAttrMap1(id)?.TryGetValue(name, out var value) == true ? value : null; + + /// + /// FindFirstAttr1 finds the first value of the given key(s) in the upper 1st-level map + /// specified by (id), or null if no value found. + /// + public Protoconf.Fruit3Conf.Types.Fruit.Types.Country.Types.Item.Types.Attr? FindFirstAttr1(int id, string name) => + FindAttr1(id, name)?.FirstOrDefault(); + // OrderedIndex: CountryItemPrice /// @@ -654,8 +1109,8 @@ public class Fruit4Conf : Messager, IMessagerName // LevelIndex keys. public readonly struct LevelIndex_Fruit_CountryKey : IEquatable { - public int FruitType { get; } - public int Id { get; } + public int FruitType { get; } // key of protoconf.Fruit4Conf.fruit_map + public int Id { get; } // key of protoconf.Fruit4Conf.Fruit.country_map public LevelIndex_Fruit_CountryKey(int fruitType, int id) { @@ -670,6 +1125,26 @@ public override int GetHashCode() => (FruitType, Id).GetHashCode(); } + public readonly struct LevelIndex_Fruit_Country_ItemKey : IEquatable + { + public int FruitType { get; } // key of protoconf.Fruit4Conf.fruit_map + public int Id { get; } // key of protoconf.Fruit4Conf.Fruit.country_map + public int Id3 { get; } // key of protoconf.Fruit4Conf.Fruit.Country.item_map (renamed from Id) + + public LevelIndex_Fruit_Country_ItemKey(int fruitType, int id, int id3) + { + FruitType = fruitType; + Id = id; + Id3 = id3; + } + + public bool Equals(LevelIndex_Fruit_Country_ItemKey other) => + (FruitType, Id, Id3).Equals((other.FruitType, other.Id, other.Id3)); + + public override int GetHashCode() => + (FruitType, Id, Id3).GetHashCode(); + } + // Index types. // Index: CountryName public class Index_CountryMap : Dictionary> { } @@ -687,6 +1162,8 @@ public class Index_AttrMap : Dictionary _indexAttrMap2 = new Dictionary(); + private Dictionary _indexAttrMap3 = new Dictionary(); + // OrderedIndex types. // OrderedIndex: CountryItemPrice public class OrderedIndex_ItemMap : SortedDictionary> { } @@ -750,6 +1227,7 @@ protected override bool ProcessAfterLoad() _indexAttrMap.Clear(); _indexAttrMap1.Clear(); _indexAttrMap2.Clear(); + _indexAttrMap3.Clear(); foreach (var item1 in _data.FruitMap) { var k1 = item1.Key; @@ -774,6 +1252,7 @@ protected override bool ProcessAfterLoad() } foreach (var item3 in item2.Value.ItemMap) { + var k3 = item3.Key; foreach (var item4 in item3.Value.AttrList) { { @@ -799,6 +1278,14 @@ protected override bool ProcessAfterLoad() existingList : map[key] = new List(); list.Add(item4); } + { + var mapKey = new LevelIndex_Fruit_Country_ItemKey(k1, k2, k3); + var map = _indexAttrMap3.TryGetValue(mapKey, out var existingMap) ? + existingMap : _indexAttrMap3[mapKey] = new Index_AttrMap(); + var list = map.TryGetValue(key, out var existingList) ? + existingList : map[key] = new List(); + list.Add(item4); + } } } } @@ -996,6 +1483,28 @@ protected override bool ProcessAfterLoad() public Protoconf.Fruit4Conf.Types.Fruit.Types.Country.Types.Item.Types.Attr? FindFirstAttr2(int fruitType, int id, string name) => FindAttr2(fruitType, id, name)?.FirstOrDefault(); + /// + /// FindAttrMap3 finds the index: key(CountryItemAttrName) to value(Protoconf.Fruit4Conf.Types.Fruit.Types.Country.Types.Item.Types.Attr), + /// which is the upper 3rd-level map specified by (fruitType, id, id3). + /// One key may correspond to multiple values, which are represented by a list. + /// + public Index_AttrMap? FindAttrMap3(int fruitType, int id, int id3) => + _indexAttrMap3.TryGetValue(new LevelIndex_Fruit_Country_ItemKey(fruitType, id, id3), out var value) ? value : null; + + /// + /// FindAttr3 finds a list of all values of the given key(s) in the upper 3rd-level map + /// specified by (fruitType, id, id3). + /// + public List? FindAttr3(int fruitType, int id, int id3, string name) => + FindAttrMap3(fruitType, id, id3)?.TryGetValue(name, out var value) == true ? value : null; + + /// + /// FindFirstAttr3 finds the first value of the given key(s) in the upper 3rd-level map + /// specified by (fruitType, id, id3), or null if no value found. + /// + public Protoconf.Fruit4Conf.Types.Fruit.Types.Country.Types.Item.Types.Attr? FindFirstAttr3(int fruitType, int id, int id3, string name) => + FindAttr3(fruitType, id, id3, name)?.FirstOrDefault(); + // OrderedIndex: CountryItemPrice /// diff --git a/test/csharp-tableau-loader/tableau/TestConf.pc.cs b/test/csharp-tableau-loader/tableau/TestConf.pc.cs index ccb31f38..577bf334 100644 --- a/test/csharp-tableau-loader/tableau/TestConf.pc.cs +++ b/test/csharp-tableau-loader/tableau/TestConf.pc.cs @@ -44,8 +44,8 @@ public class OrderedMap_ActivityMap : SortedDictionary { - public ulong ActivityId { get; } - public uint ChapterId { get; } + public ulong ActivityId { get; } // key of protoconf.ActivityConf.activity_map + public uint ChapterId { get; } // key of protoconf.ActivityConf.Activity.chapter_map public LevelIndex_Activity_ChapterKey(ulong activityId, uint chapterId) { @@ -60,6 +60,26 @@ public override int GetHashCode() => (ActivityId, ChapterId).GetHashCode(); } + public readonly struct LevelIndex_protoconf_SectionKey : IEquatable + { + public ulong ActivityId { get; } // key of protoconf.ActivityConf.activity_map + public uint ChapterId { get; } // key of protoconf.ActivityConf.Activity.chapter_map + public uint SectionId { get; } // key of protoconf.ActivityConf.Activity.Chapter.section_map + + public LevelIndex_protoconf_SectionKey(ulong activityId, uint chapterId, uint sectionId) + { + ActivityId = activityId; + ChapterId = chapterId; + SectionId = sectionId; + } + + public bool Equals(LevelIndex_protoconf_SectionKey other) => + (ActivityId, ChapterId, SectionId).Equals((other.ActivityId, other.ChapterId, other.SectionId)); + + public override int GetHashCode() => + (ActivityId, ChapterId, SectionId).GetHashCode(); + } + // Index types. // Index: ActivityName public class Index_ActivityMap : Dictionary> { } @@ -89,6 +109,8 @@ public class Index_AwardMap : Dictionary _indexAwardMap2 = new Dictionary(); + private Dictionary _indexAwardMap3 = new Dictionary(); + private Protoconf.ActivityConf _data = new(); /// @@ -166,6 +188,7 @@ protected override bool ProcessAfterLoad() _indexAwardMap.Clear(); _indexAwardMap1.Clear(); _indexAwardMap2.Clear(); + _indexAwardMap3.Clear(); foreach (var item1 in _data.ActivityMap) { var k1 = item1.Key; @@ -215,6 +238,7 @@ protected override bool ProcessAfterLoad() } foreach (var item3 in item2.Value.SectionMap) { + var k3 = item3.Key; foreach (var item4 in item3.Value.SectionItemList) { { @@ -240,6 +264,14 @@ protected override bool ProcessAfterLoad() existingList : map[key] = new List(); list.Add(item4); } + { + var mapKey = new LevelIndex_protoconf_SectionKey(k1, k2, k3); + var map = _indexAwardMap3.TryGetValue(mapKey, out var existingMap) ? + existingMap : _indexAwardMap3[mapKey] = new Index_AwardMap(); + var list = map.TryGetValue(key, out var existingList) ? + existingList : map[key] = new List(); + list.Add(item4); + } } } } @@ -488,6 +520,28 @@ protected override bool ProcessAfterLoad() /// public Protoconf.Section.Types.SectionItem? FindFirstAward2(ulong activityId, uint chapterId, uint id) => FindAward2(activityId, chapterId, id)?.FirstOrDefault(); + + /// + /// FindAwardMap3 finds the index: key(SectionItemID@Award) to value(Protoconf.Section.Types.SectionItem), + /// which is the upper 3rd-level map specified by (activityId, chapterId, sectionId). + /// One key may correspond to multiple values, which are represented by a list. + /// + public Index_AwardMap? FindAwardMap3(ulong activityId, uint chapterId, uint sectionId) => + _indexAwardMap3.TryGetValue(new LevelIndex_protoconf_SectionKey(activityId, chapterId, sectionId), out var value) ? value : null; + + /// + /// FindAward3 finds a list of all values of the given key(s) in the upper 3rd-level map + /// specified by (activityId, chapterId, sectionId). + /// + public List? FindAward3(ulong activityId, uint chapterId, uint sectionId, uint id) => + FindAwardMap3(activityId, chapterId, sectionId)?.TryGetValue(id, out var value) == true ? value : null; + + /// + /// FindFirstAward3 finds the first value of the given key(s) in the upper 3rd-level map + /// specified by (activityId, chapterId, sectionId), or null if no value found. + /// + public Protoconf.Section.Types.SectionItem? FindFirstAward3(ulong activityId, uint chapterId, uint sectionId, uint id) => + FindAward3(activityId, chapterId, sectionId, id)?.FirstOrDefault(); } /// diff --git a/test/go-tableau-loader/hub/hub.go b/test/go-tableau-loader/hub/hub.go index db049f47..a9b8d496 100644 --- a/test/go-tableau-loader/hub/hub.go +++ b/test/go-tableau-loader/hub/hub.go @@ -15,6 +15,13 @@ type MyHub struct { var hubSingleton *MyHub var once sync.Once +// NewMyHub creates a new MyHub instance (useful for testing). +func NewMyHub() *MyHub { + return &MyHub{ + Hub: tableau.NewHub(), + } +} + // GetHub return the singleton of MyHub func GetHub() *MyHub { once.Do(func() { diff --git a/test/go-tableau-loader/index_test.go b/test/go-tableau-loader/index_test.go new file mode 100644 index 00000000..d710a100 --- /dev/null +++ b/test/go-tableau-loader/index_test.go @@ -0,0 +1,543 @@ +package main + +import ( + "errors" + "testing" + + "github.com/tableauio/loader/test/go-tableau-loader/protoconf" + "github.com/tableauio/loader/test/go-tableau-loader/protoconf/loader" +) + +// fruitType constants matching FruitConf.json / Fruit6Conf.json +var ( + fruitTypeApple = int32(protoconf.FruitType_FRUIT_TYPE_APPLE) + fruitTypeOrange = int32(protoconf.FruitType_FRUIT_TYPE_ORANGE) + fruitTypeBanana = int32(protoconf.FruitType_FRUIT_TYPE_BANANA) +) + +// ---- FruitConf ---- + +func Test_FruitConf_Get1(t *testing.T) { + h := prepareHub(t) + conf := h.GetFruitConf() + + // found + fruit, err := conf.Get1(fruitTypeApple) + if err != nil { + t.Fatalf("Get1(%d) unexpected error: %v", fruitTypeApple, err) + } + if fruit == nil { + t.Fatal("Get1: returned nil fruit") + } + + // not found + _, err = conf.Get1(999) + if err == nil { + t.Fatal("Get1(999): expected ErrNotFound, got nil") + } + if !errors.Is(err, loader.ErrNotFound) { + t.Fatalf("Get1(999): expected ErrNotFound, got: %v", err) + } +} + +func Test_FruitConf_Get2(t *testing.T) { + h := prepareHub(t) + conf := h.GetFruitConf() + + // found: APPLE -> item 1001 + item, err := conf.Get2(fruitTypeApple, 1001) + if err != nil { + t.Fatalf("Get2(%d, 1001) unexpected error: %v", fruitTypeApple, err) + } + if item.GetId() != 1001 { + t.Errorf("Get2: expected id=1001, got %d", item.GetId()) + } + if item.GetPrice() != 10 { + t.Errorf("Get2: expected price=10, got %d", item.GetPrice()) + } + + // not found: wrong item id + _, err = conf.Get2(fruitTypeApple, 9999) + if err == nil { + t.Fatal("Get2(apple, 9999): expected ErrNotFound, got nil") + } + if !errors.Is(err, loader.ErrNotFound) { + t.Fatalf("Get2(apple, 9999): expected ErrNotFound, got: %v", err) + } + + // not found: wrong fruitType + _, err = conf.Get2(999, 1001) + if !errors.Is(err, loader.ErrNotFound) { + t.Fatalf("Get2(999, 1001): expected ErrNotFound, got: %v", err) + } +} + +func Test_FruitConf_FindItem(t *testing.T) { + h := prepareHub(t) + conf := h.GetFruitConf() + + // price=10 belongs to APPLE item 1001 + items := conf.FindItem(10) + if len(items) != 1 { + t.Fatalf("FindItem(10): expected 1 item, got %d", len(items)) + } + if items[0].GetId() != 1001 { + t.Errorf("FindItem(10): expected id=1001, got %d", items[0].GetId()) + } + + // price not present + items = conf.FindItem(999) + if len(items) != 0 { + t.Errorf("FindItem(999): expected 0 items, got %d", len(items)) + } +} + +func Test_FruitConf_FindFirstItem(t *testing.T) { + h := prepareHub(t) + conf := h.GetFruitConf() + + // price=20 belongs to APPLE item 1002 + item := conf.FindFirstItem(20) + if item == nil { + t.Fatal("FindFirstItem(20): expected non-nil, got nil") + } + if item.GetId() != 1002 { + t.Errorf("FindFirstItem(20): expected id=1002, got %d", item.GetId()) + } + + // price not present + item = conf.FindFirstItem(999) + if item != nil { + t.Errorf("FindFirstItem(999): expected nil, got %v", item) + } +} + +func Test_FruitConf_FindItemMap(t *testing.T) { + h := prepareHub(t) + conf := h.GetFruitConf() + + m := conf.FindItemMap() + if m == nil { + t.Fatal("FindItemMap: returned nil") + } + // 6 items total, each with a unique price → 6 entries + if len(m) != 6 { + t.Errorf("FindItemMap: expected 6 entries, got %d", len(m)) + } +} + +func Test_FruitConf_FindItem1(t *testing.T) { + h := prepareHub(t) + conf := h.GetFruitConf() + + // ORANGE(2) -> price=15 -> item 2001 + items := conf.FindItem1(fruitTypeOrange, 15) + if len(items) != 1 { + t.Fatalf("FindItem1(orange, 15): expected 1 item, got %d", len(items)) + } + if items[0].GetId() != 2001 { + t.Errorf("FindItem1(orange, 15): expected id=2001, got %d", items[0].GetId()) + } + + // wrong fruitType → nil slice + items = conf.FindItem1(999, 15) + if len(items) != 0 { + t.Errorf("FindItem1(999, 15): expected 0 items, got %d", len(items)) + } + + // correct fruitType, wrong price → nil slice + items = conf.FindItem1(fruitTypeOrange, 999) + if len(items) != 0 { + t.Errorf("FindItem1(orange, 999): expected 0 items, got %d", len(items)) + } +} + +func Test_FruitConf_FindFirstItem1(t *testing.T) { + h := prepareHub(t) + conf := h.GetFruitConf() + + // BANANA(3) -> price=8 -> item 3001 + item := conf.FindFirstItem1(fruitTypeBanana, 8) + if item == nil { + t.Fatal("FindFirstItem1(banana, 8): expected non-nil, got nil") + } + if item.GetId() != 3001 { + t.Errorf("FindFirstItem1(banana, 8): expected id=3001, got %d", item.GetId()) + } + + // not found + item = conf.FindFirstItem1(fruitTypeBanana, 999) + if item != nil { + t.Errorf("FindFirstItem1(banana, 999): expected nil, got %v", item) + } +} + +func Test_FruitConf_FindItemMap1(t *testing.T) { + h := prepareHub(t) + conf := h.GetFruitConf() + + // APPLE(1) has 2 items → 2 price entries + m := conf.FindItemMap1(fruitTypeApple) + if len(m) != 2 { + t.Errorf("FindItemMap1(apple): expected 2 entries, got %d", len(m)) + } + + // non-existent fruitType → nil map + m = conf.FindItemMap1(999) + if m != nil { + t.Errorf("FindItemMap1(999): expected nil, got %v", m) + } +} + +func Test_FruitConf_FindOrderedFruit(t *testing.T) { + h := prepareHub(t) + conf := h.GetFruitConf() + + // price=10 → APPLE item 1001 + items := conf.FindOrderedFruit(10) + if len(items) != 1 { + t.Fatalf("FindOrderedFruit(10): expected 1 item, got %d", len(items)) + } + if items[0].GetId() != 1001 { + t.Errorf("FindOrderedFruit(10): expected id=1001, got %d", items[0].GetId()) + } + + // price not present + items = conf.FindOrderedFruit(999) + if len(items) != 0 { + t.Errorf("FindOrderedFruit(999): expected 0 items, got %d", len(items)) + } +} + +func Test_FruitConf_FindFirstOrderedFruit(t *testing.T) { + h := prepareHub(t) + conf := h.GetFruitConf() + + item := conf.FindFirstOrderedFruit(25) + if item == nil { + t.Fatal("FindFirstOrderedFruit(25): expected non-nil, got nil") + } + if item.GetId() != 2002 { + t.Errorf("FindFirstOrderedFruit(25): expected id=2002, got %d", item.GetId()) + } + + item = conf.FindFirstOrderedFruit(999) + if item != nil { + t.Errorf("FindFirstOrderedFruit(999): expected nil, got %v", item) + } +} + +func Test_FruitConf_FindOrderedFruitMap(t *testing.T) { + h := prepareHub(t) + conf := h.GetFruitConf() + + m := conf.FindOrderedFruitMap() + if m == nil { + t.Fatal("FindOrderedFruitMap: returned nil") + } + if m.Size() != 6 { + t.Errorf("FindOrderedFruitMap: expected size=6, got %d", m.Size()) + } + // verify ascending order of keys + prev := int32(-1) + m.Range(func(key int32, _ []*protoconf.FruitConf_Fruit_Item) bool { + if key < prev { + t.Errorf("FindOrderedFruitMap: keys not in ascending order: %d after %d", key, prev) + } + prev = key + return true + }) +} + +func Test_FruitConf_FindOrderedFruit1(t *testing.T) { + h := prepareHub(t) + conf := h.GetFruitConf() + + // ORANGE(2) -> price=25 -> item 2002 + items := conf.FindOrderedFruit1(fruitTypeOrange, 25) + if len(items) != 1 { + t.Fatalf("FindOrderedFruit1(orange, 25): expected 1 item, got %d", len(items)) + } + if items[0].GetId() != 2002 { + t.Errorf("FindOrderedFruit1(orange, 25): expected id=2002, got %d", items[0].GetId()) + } + + // ORANGE(2) -> price=15 -> items [2001, 2002], verify IDs are in ascending order + items = conf.FindOrderedFruit1(fruitTypeOrange, 15) + if len(items) != 1 { + t.Fatalf("FindOrderedFruit1(orange, 15): expected 1 item, got %d", len(items)) + } + for i := 1; i < len(items); i++ { + if items[i].GetId() < items[i-1].GetId() { + t.Errorf("FindOrderedFruit1(orange, 15): items not in ascending id order at index %d: id=%d after id=%d", + i, items[i].GetId(), items[i-1].GetId()) + } + } + + // wrong fruitType + items = conf.FindOrderedFruit1(999, 25) + if len(items) != 0 { + t.Errorf("FindOrderedFruit1(999, 25): expected 0 items, got %d", len(items)) + } +} + +func Test_FruitConf_FindFirstOrderedFruit1(t *testing.T) { + h := prepareHub(t) + conf := h.GetFruitConf() + + // BANANA(3) -> price=12 -> item 3002 + item := conf.FindFirstOrderedFruit1(fruitTypeBanana, 12) + if item == nil { + t.Fatal("FindFirstOrderedFruit1(banana, 12): expected non-nil, got nil") + } + if item.GetId() != 3002 { + t.Errorf("FindFirstOrderedFruit1(banana, 12): expected id=3002, got %d", item.GetId()) + } + + // not found + item = conf.FindFirstOrderedFruit1(fruitTypeBanana, 999) + if item != nil { + t.Errorf("FindFirstOrderedFruit1(banana, 999): expected nil, got %v", item) + } +} + +// ---- Fruit6Conf ---- + +func Test_Fruit6Conf_Get1(t *testing.T) { + h := prepareHub(t) + conf := h.GetFruit6Conf() + + // found + fruit, err := conf.Get1(fruitTypeApple) + if err != nil { + t.Fatalf("Get1(%d) unexpected error: %v", fruitTypeApple, err) + } + if fruit == nil { + t.Fatal("Get1: returned nil fruit") + } + + // not found + _, err = conf.Get1(999) + if !errors.Is(err, loader.ErrNotFound) { + t.Fatalf("Get1(999): expected ErrNotFound, got: %v", err) + } +} + +func Test_Fruit6Conf_FindItem(t *testing.T) { + h := prepareHub(t) + conf := h.GetFruit6Conf() + + // price=10 → APPLE item 1001 + items := conf.FindItem(10) + if len(items) != 1 { + t.Fatalf("FindItem(10): expected 1 item, got %d", len(items)) + } + if items[0].GetId() != 1001 { + t.Errorf("FindItem(10): expected id=1001, got %d", items[0].GetId()) + } + + // price not present + items = conf.FindItem(999) + if len(items) != 0 { + t.Errorf("FindItem(999): expected 0 items, got %d", len(items)) + } +} + +func Test_Fruit6Conf_FindFirstItem(t *testing.T) { + h := prepareHub(t) + conf := h.GetFruit6Conf() + + item := conf.FindFirstItem(20) + if item == nil { + t.Fatal("FindFirstItem(20): expected non-nil, got nil") + } + if item.GetId() != 1002 { + t.Errorf("FindFirstItem(20): expected id=1002, got %d", item.GetId()) + } + + item = conf.FindFirstItem(999) + if item != nil { + t.Errorf("FindFirstItem(999): expected nil, got %v", item) + } +} + +func Test_Fruit6Conf_FindItemMap(t *testing.T) { + h := prepareHub(t) + conf := h.GetFruit6Conf() + + m := conf.FindItemMap() + if m == nil { + t.Fatal("FindItemMap: returned nil") + } + if len(m) != 6 { + t.Errorf("FindItemMap: expected 6 entries, got %d", len(m)) + } +} + +func Test_Fruit6Conf_FindItem1(t *testing.T) { + h := prepareHub(t) + conf := h.GetFruit6Conf() + + // ORANGE(2) -> price=15 -> item 2001 + items := conf.FindItem1(fruitTypeOrange, 15) + if len(items) != 3 { + t.Fatalf("FindItem1(orange, 15): expected 1 item, got %d", len(items)) + } + if items[0].GetId() != 2000 { + t.Errorf("FindItem1(orange, 15): expected id=2001, got %d", items[0].GetId()) + } + + // wrong fruitType + items = conf.FindItem1(999, 15) + if len(items) != 0 { + t.Errorf("FindItem1(999, 15): expected 0 items, got %d", len(items)) + } + + // correct fruitType, wrong price + items = conf.FindItem1(fruitTypeOrange, 999) + if len(items) != 0 { + t.Errorf("FindItem1(orange, 999): expected 0 items, got %d", len(items)) + } +} + +func Test_Fruit6Conf_FindFirstItem1(t *testing.T) { + h := prepareHub(t) + conf := h.GetFruit6Conf() + + // BANANA(3) -> price=8 -> item 3001 + item := conf.FindFirstItem1(fruitTypeBanana, 8) + if item == nil { + t.Fatal("FindFirstItem1(banana, 8): expected non-nil, got nil") + } + if item.GetId() != 3001 { + t.Errorf("FindFirstItem1(banana, 8): expected id=3001, got %d", item.GetId()) + } + + item = conf.FindFirstItem1(fruitTypeBanana, 999) + if item != nil { + t.Errorf("FindFirstItem1(banana, 999): expected nil, got %v", item) + } +} + +func Test_Fruit6Conf_FindItemMap1(t *testing.T) { + h := prepareHub(t) + conf := h.GetFruit6Conf() + + m := conf.FindItemMap1(fruitTypeApple) + if len(m) != 2 { + t.Errorf("FindItemMap1(apple): expected 2 entries, got %d", len(m)) + } + + m = conf.FindItemMap1(999) + if m != nil { + t.Errorf("FindItemMap1(999): expected nil, got %v", m) + } +} + +func Test_Fruit6Conf_FindOrderedFruit(t *testing.T) { + h := prepareHub(t) + conf := h.GetFruit6Conf() + + items := conf.FindOrderedFruit(10) + if len(items) != 1 { + t.Fatalf("FindOrderedFruit(10): expected 1 item, got %d", len(items)) + } + if items[0].GetId() != 1001 { + t.Errorf("FindOrderedFruit(10): expected id=1001, got %d", items[0].GetId()) + } + + items = conf.FindOrderedFruit(999) + if len(items) != 0 { + t.Errorf("FindOrderedFruit(999): expected 0 items, got %d", len(items)) + } +} + +func Test_Fruit6Conf_FindFirstOrderedFruit(t *testing.T) { + h := prepareHub(t) + conf := h.GetFruit6Conf() + + item := conf.FindFirstOrderedFruit(25) + if item == nil { + t.Fatal("FindFirstOrderedFruit(25): expected non-nil, got nil") + } + if item.GetId() != 2002 { + t.Errorf("FindFirstOrderedFruit(25): expected id=2002, got %d", item.GetId()) + } + + item = conf.FindFirstOrderedFruit(999) + if item != nil { + t.Errorf("FindFirstOrderedFruit(999): expected nil, got %v", item) + } +} + +func Test_Fruit6Conf_FindOrderedFruitMap(t *testing.T) { + h := prepareHub(t) + conf := h.GetFruit6Conf() + + m := conf.FindOrderedFruitMap() + if m == nil { + t.Fatal("FindOrderedFruitMap: returned nil") + } + if m.Size() != 6 { + t.Errorf("FindOrderedFruitMap: expected size=6, got %d", m.Size()) + } + // verify ascending order of keys + prev := int32(-1) + m.Range(func(key int32, _ []*protoconf.Fruit6Conf_Fruit_Item) bool { + if key < prev { + t.Errorf("FindOrderedFruitMap: keys not in ascending order: %d after %d", key, prev) + } + prev = key + return true + }) +} + +func Test_Fruit6Conf_FindOrderedFruit1(t *testing.T) { + h := prepareHub(t) + conf := h.GetFruit6Conf() + + // ORANGE(2) -> price=25 -> item 2002 + items := conf.FindOrderedFruit1(fruitTypeOrange, 25) + if len(items) != 1 { + t.Fatalf("FindOrderedFruit1(orange, 25): expected 1 item, got %d", len(items)) + } + if items[0].GetId() != 2002 { + t.Errorf("FindOrderedFruit1(orange, 25): expected id=2002, got %d", items[0].GetId()) + } + + // ORANGE(2) -> price=15 -> items [2001, 2002], verify IDs are in ascending order + items = conf.FindOrderedFruit1(fruitTypeOrange, 15) + if len(items) != 3 { + t.Fatalf("FindOrderedFruit1(orange, 15): expected 2 items, got %d", len(items)) + } + for i := 1; i < len(items); i++ { + if items[i].GetId() < items[i-1].GetId() { + t.Errorf("FindOrderedFruit1(orange, 15): items not in ascending id order at index %d: id=%d after id=%d", + i, items[i].GetId(), items[i-1].GetId()) + } + } + + items = conf.FindOrderedFruit1(999, 25) + if len(items) != 0 { + t.Errorf("FindOrderedFruit1(999, 25): expected 0 items, got %d", len(items)) + } +} + +func Test_Fruit6Conf_FindFirstOrderedFruit1(t *testing.T) { + h := prepareHub(t) + conf := h.GetFruit6Conf() + + // BANANA(3) -> price=12 -> item 3002 + item := conf.FindFirstOrderedFruit1(fruitTypeBanana, 12) + if item == nil { + t.Fatal("FindFirstOrderedFruit1(banana, 12): expected non-nil, got nil") + } + if item.GetId() != 3002 { + t.Errorf("FindFirstOrderedFruit1(banana, 12): expected id=3002, got %d", item.GetId()) + } + + item = conf.FindFirstOrderedFruit1(fruitTypeBanana, 999) + if item != nil { + t.Errorf("FindFirstOrderedFruit1(banana, 999): expected nil, got %v", item) + } +} diff --git a/test/go-tableau-loader/main.go b/test/go-tableau-loader/main.go index 04cf54b5..5a63bf58 100644 --- a/test/go-tableau-loader/main.go +++ b/test/go-tableau-loader/main.go @@ -1,15 +1,9 @@ package main import ( - "context" - "errors" - "fmt" - "github.com/tableauio/loader/test/go-tableau-loader/hub" - "github.com/tableauio/loader/test/go-tableau-loader/protoconf/loader" "github.com/tableauio/tableau/format" "github.com/tableauio/tableau/load" - "github.com/tableauio/tableau/store" ) func main() { @@ -25,76 +19,6 @@ func main() { panic(err) } - for name, msger := range hub.GetHub().GetMessagerMap() { - fmt.Printf("%s: duration: %v\n", name, msger.GetStats().Duration) - } - - conf := hub.GetHub().GetActivityConf() - if conf == nil { - panic("ActivityConf is nil") - } - - // error: not found - if _, err := conf.Get3(100001, 1, 999); err != nil { - if errors.Is(err, loader.ErrNotFound) { - fmt.Println("error: not found:", err) - } - } - - // update and store - chapter, err := conf.Get3(100001, 1, 2) - if err != nil { - panic(err) - } - chapter.SectionName = "updated section 2" - err = hub.GetHub().Store("_out/", format.JSON, - store.Pretty(true), - ) - if err != nil { - panic(err) - } - - // OrderedMap - orderedMap := conf.GetOrderedMap() - for iter := orderedMap.Iterator(); iter.Next(); { - key := iter.Key() - value := iter.Value().Second - fmt.Println("key:", key) - fmt.Println("value:", value) - fmt.Println() - subOrderedMap := iter.Value().First - for iter2 := subOrderedMap.Iterator(); iter2.Next(); { - key2 := iter2.Key() - value2 := iter2.Value().Second - fmt.Println("key2:", key2) - fmt.Println("value2:", value2) - fmt.Println() - } - } - fmt.Printf("specialItemName: %v\n", hub.GetHub().GetCustomItemConf().GetSpecialItemName()) - fmt.Printf("HeroBaseConf: %v\n", hub.GetHub().GetHeroBaseConf().Data().GetHeroMap()) - - // save current messager container to ctx - ctx := hub.GetHub().NewContext(context.Background()) - - // load again with patch - err = hub.GetHub().Load("../testdata/conf/", format.JSON, - load.IgnoreUnknownFields(), - load.PatchDirs("../testdata/patchconf/"), - ) - if err != nil { - panic(err) - } - // print recursive patch conf - fmt.Printf("RecursivePatchConf: %v\n", hub.GetHub().GetRecursivePatchConf().Data()) - - // print patch replace conf - fmt.Printf("PatchReplaceConf: %v\n", hub.GetHub().GetPatchReplaceConf().Data()) - // print patch replace conf from ctx - fmt.Printf("PatchReplaceConf(from ctx): %v\n", hub.GetHub().FromContext(ctx).GetPatchReplaceConf().Data()) - // print patch replace conf from background context - fmt.Printf("PatchReplaceConf(from background): %v\n", hub.GetHub().FromContext(context.Background()).GetPatchReplaceConf().Data()) - // // test mutable check // delete(hub.GetHub().GetActivityConf().Data().ActivityMap, 100001) // hub.GetHub().GetActivityConf().Data().ThemeName = "theme2" diff --git a/test/go-tableau-loader/main_test.go b/test/go-tableau-loader/main_test.go new file mode 100644 index 00000000..58fe301e --- /dev/null +++ b/test/go-tableau-loader/main_test.go @@ -0,0 +1,130 @@ +package main + +import ( + "context" + "errors" + "testing" + + "github.com/tableauio/loader/test/go-tableau-loader/hub" + "github.com/tableauio/loader/test/go-tableau-loader/protoconf/loader" + "github.com/tableauio/tableau/format" + "github.com/tableauio/tableau/load" + "github.com/tableauio/tableau/store" +) + +func prepareHub(t *testing.T) *hub.MyHub { + t.Helper() + h := hub.NewMyHub() + err := h.Load("../testdata/conf/", format.JSON, + load.IgnoreUnknownFields(), + load.WithMessagerOptions(map[string]*load.MessagerOptions{ + "ItemConf": { + Path: "../testdata/conf/ItemConf.json", + }, + }), + ) + if err != nil { + t.Fatalf("failed to load hub: %v", err) + } + return h +} + +func Test_Load(t *testing.T) { + h := prepareHub(t) + for name, msger := range h.GetMessagerMap() { + t.Logf("%s: duration: %v", name, msger.GetStats().Duration) + } +} + +func Test_ActivityConf_NotFound(t *testing.T) { + h := prepareHub(t) + conf := h.GetActivityConf() + if conf == nil { + t.Fatal("ActivityConf is nil") + } + _, err := conf.Get3(100001, 1, 999) + if err == nil { + t.Fatal("expected ErrNotFound, got nil") + } + if !errors.Is(err, loader.ErrNotFound) { + t.Fatalf("expected ErrNotFound, got: %v", err) + } +} + +func Test_ActivityConf_UpdateAndStore(t *testing.T) { + h := prepareHub(t) + conf := h.GetActivityConf() + if conf == nil { + t.Fatal("ActivityConf is nil") + } + chapter, err := conf.Get3(100001, 1, 2) + if err != nil { + t.Fatalf("Get3 failed: %v", err) + } + chapter.SectionName = "updated section 2" + err = h.Store(t.TempDir(), format.JSON, + store.Pretty(true), + ) + if err != nil { + t.Fatalf("Store failed: %v", err) + } +} + +func Test_ActivityConf_OrderedMap(t *testing.T) { + h := prepareHub(t) + conf := h.GetActivityConf() + if conf == nil { + t.Fatal("ActivityConf is nil") + } + orderedMap := conf.GetOrderedMap() + for iter := orderedMap.Iterator(); iter.Next(); { + key := iter.Key() + value := iter.Value().Second + t.Logf("key: %v, value: %v", key, value) + subOrderedMap := iter.Value().First + for iter2 := subOrderedMap.Iterator(); iter2.Next(); { + key2 := iter2.Key() + value2 := iter2.Value().Second + t.Logf(" key2: %v, value2: %v", key2, value2) + } + } +} + +func Test_CustomItemConf(t *testing.T) { + h := prepareHub(t) + customConf := h.GetCustomItemConf() + if customConf == nil { + t.Fatal("CustomItemConf is nil") + } + t.Logf("specialItemName: %v", customConf.GetSpecialItemName()) +} + +func Test_HeroBaseConf(t *testing.T) { + h := prepareHub(t) + heroConf := h.GetHeroBaseConf() + if heroConf == nil { + t.Fatal("HeroBaseConf is nil") + } + t.Logf("HeroBaseConf: %v", heroConf.Data().GetHeroMap()) +} + +func Test_Context(t *testing.T) { + h := prepareHub(t) + + // Save current messager container to ctx. + ctx := h.NewContext(context.Background()) + + // Load again with patch. + err := h.Load("../testdata/conf/", format.JSON, + load.IgnoreUnknownFields(), + load.PatchDirs("../testdata/patchconf/"), + ) + if err != nil { + t.Fatalf("failed to load with patch: %v", err) + } + + t.Logf("RecursivePatchConf: %v", h.GetRecursivePatchConf().Data()) + t.Logf("PatchReplaceConf: %v", h.GetPatchReplaceConf().Data()) + t.Logf("PatchReplaceConf(from ctx): %v", h.FromContext(ctx).GetPatchReplaceConf().Data()) + t.Logf("PatchReplaceConf(from background): %v", h.FromContext(context.Background()).GetPatchReplaceConf().Data()) +} diff --git a/test/go-tableau-loader/protoconf/loader/hub.pc.go b/test/go-tableau-loader/protoconf/loader/hub.pc.go index df150799..a4e2e86c 100644 --- a/test/go-tableau-loader/protoconf/loader/hub.pc.go +++ b/test/go-tableau-loader/protoconf/loader/hub.pc.go @@ -223,6 +223,10 @@ func (h *Hub) GetFruitConf() *FruitConf { return h.mc.Load().GetFruitConf() } +func (h *Hub) GetFruit6Conf() *Fruit6Conf { + return h.mc.Load().GetFruit6Conf() +} + func (h *Hub) GetFruit2Conf() *Fruit2Conf { return h.mc.Load().GetFruit2Conf() } diff --git a/test/go-tableau-loader/protoconf/loader/index_conf.pc.go b/test/go-tableau-loader/protoconf/loader/index_conf.pc.go index 0ea56050..6ec07e40 100644 --- a/test/go-tableau-loader/protoconf/loader/index_conf.pc.go +++ b/test/go-tableau-loader/protoconf/loader/index_conf.pc.go @@ -18,9 +18,13 @@ import ( time "time" ) +// Index types. +// Index: Price +type FruitConf_Index_ItemMap = map[int32][]*protoconf.FruitConf_Fruit_Item + // OrderedIndex types. -// OrderedIndex: Price -type FruitConf_OrderedIndex_ItemMap = treemap.TreeMap[int32, []*protoconf.FruitConf_Fruit_Item] +// OrderedIndex: Price@OrderedFruit +type FruitConf_OrderedIndex_OrderedFruitMap = treemap.TreeMap[int32, []*protoconf.FruitConf_Fruit_Item] // FruitConf is a wrapper around protobuf message: protoconf.FruitConf. // @@ -31,9 +35,11 @@ type FruitConf_OrderedIndex_ItemMap = treemap.TreeMap[int32, []*protoconf.FruitC // 3. Extensibility: Map, OrdererdMap, Index, OrderedIndex... type FruitConf struct { UnimplementedMessager - data, originalData *protoconf.FruitConf - orderedIndexItemMap *FruitConf_OrderedIndex_ItemMap - orderedIndexItemMap1 map[int32]*FruitConf_OrderedIndex_ItemMap + data, originalData *protoconf.FruitConf + indexItemMap FruitConf_Index_ItemMap + indexItemMap1 map[int32]FruitConf_Index_ItemMap + orderedIndexOrderedFruitMap *FruitConf_OrderedIndex_OrderedFruitMap + orderedIndexOrderedFruitMap1 map[int32]*FruitConf_OrderedIndex_OrderedFruitMap } // Name returns the FruitConf's message name. @@ -92,37 +98,67 @@ func (x *FruitConf) originalMessage() proto.Message { // processAfterLoad runs after this messager is loaded. func (x *FruitConf) processAfterLoad() error { + // Index init. + x.indexItemMap = make(FruitConf_Index_ItemMap) + x.indexItemMap1 = make(map[int32]FruitConf_Index_ItemMap) + for k1, v1 := range x.data.GetFruitMap() { + for _, v2 := range v1.GetItemMap() { + { + // Index: Price + key := v2.GetPrice() + x.indexItemMap[key] = append(x.indexItemMap[key], v2) + if x.indexItemMap1[k1] == nil { + x.indexItemMap1[k1] = make(FruitConf_Index_ItemMap) + } + x.indexItemMap1[k1][key] = append(x.indexItemMap1[k1][key], v2) + } + } + } + // Index(sort): Price + indexItemMapSorter := func(itemList []*protoconf.FruitConf_Fruit_Item) func(i, j int) bool { + return func(i, j int) bool { + return itemList[i].GetId() < itemList[j].GetId() + } + } + for _, itemList := range x.indexItemMap { + sort.Slice(itemList, indexItemMapSorter(itemList)) + } + for _, itemMap := range x.indexItemMap1 { + for _, itemList := range itemMap { + sort.Slice(itemList, indexItemMapSorter(itemList)) + } + } // OrderedIndex init. - x.orderedIndexItemMap = treemap.New[int32, []*protoconf.FruitConf_Fruit_Item]() - x.orderedIndexItemMap1 = make(map[int32]*FruitConf_OrderedIndex_ItemMap) + x.orderedIndexOrderedFruitMap = treemap.New[int32, []*protoconf.FruitConf_Fruit_Item]() + x.orderedIndexOrderedFruitMap1 = make(map[int32]*FruitConf_OrderedIndex_OrderedFruitMap) for k1, v1 := range x.data.GetFruitMap() { for _, v2 := range v1.GetItemMap() { { - // OrderedIndex: Price + // OrderedIndex: Price@OrderedFruit key := v2.GetPrice() - value, _ := x.orderedIndexItemMap.Get(key) - x.orderedIndexItemMap.Put(key, append(value, v2)) - if x.orderedIndexItemMap1[k1] == nil { - x.orderedIndexItemMap1[k1] = treemap.New[int32, []*protoconf.FruitConf_Fruit_Item]() + value, _ := x.orderedIndexOrderedFruitMap.Get(key) + x.orderedIndexOrderedFruitMap.Put(key, append(value, v2)) + if x.orderedIndexOrderedFruitMap1[k1] == nil { + x.orderedIndexOrderedFruitMap1[k1] = treemap.New[int32, []*protoconf.FruitConf_Fruit_Item]() } - orderedIndexItemMap1Value, _ := x.orderedIndexItemMap1[k1].Get(key) - x.orderedIndexItemMap1[k1].Put(key, append(orderedIndexItemMap1Value, v2)) + orderedIndexOrderedFruitMap1Value, _ := x.orderedIndexOrderedFruitMap1[k1].Get(key) + x.orderedIndexOrderedFruitMap1[k1].Put(key, append(orderedIndexOrderedFruitMap1Value, v2)) } } } - // OrderedIndex(sort): Price - orderedIndexItemMapSorter := func(itemList []*protoconf.FruitConf_Fruit_Item) func(i, j int) bool { + // OrderedIndex(sort): Price@OrderedFruit + orderedIndexOrderedFruitMapSorter := func(itemList []*protoconf.FruitConf_Fruit_Item) func(i, j int) bool { return func(i, j int) bool { return itemList[i].GetId() < itemList[j].GetId() } } - x.orderedIndexItemMap.Range(func(key int32, itemList []*protoconf.FruitConf_Fruit_Item) bool { - sort.Slice(itemList, orderedIndexItemMapSorter(itemList)) + x.orderedIndexOrderedFruitMap.Range(func(key int32, itemList []*protoconf.FruitConf_Fruit_Item) bool { + sort.Slice(itemList, orderedIndexOrderedFruitMapSorter(itemList)) return true }) - for _, itemMap := range x.orderedIndexItemMap1 { + for _, itemMap := range x.orderedIndexOrderedFruitMap1 { itemMap.Range(func(key int32, itemList []*protoconf.FruitConf_Fruit_Item) bool { - sort.Slice(itemList, orderedIndexItemMapSorter(itemList)) + sort.Slice(itemList, orderedIndexOrderedFruitMapSorter(itemList)) return true }) } @@ -155,18 +191,17 @@ func (x *FruitConf) Get2(fruitType int32, id int32) (*protoconf.FruitConf_Fruit_ } } -// OrderedIndex: Price +// Index: Price -// FindItemMap finds the ordered index: key(Price) to value(protoconf.FruitConf_Fruit_Item) treemap. +// FindItemMap finds the index: key(Price) to value(protoconf.FruitConf_Fruit_Item) map. // One key may correspond to multiple values, which are represented by a slice. -func (x *FruitConf) FindItemMap() *FruitConf_OrderedIndex_ItemMap { - return x.orderedIndexItemMap +func (x *FruitConf) FindItemMap() FruitConf_Index_ItemMap { + return x.indexItemMap } // FindItem finds a slice of all values of the given key(s). func (x *FruitConf) FindItem(price int32) []*protoconf.FruitConf_Fruit_Item { - val, _ := x.orderedIndexItemMap.Get(price) - return val + return x.indexItemMap[price] } // FindFirstItem finds the first value of the given key(s), @@ -180,16 +215,63 @@ func (x *FruitConf) FindFirstItem(price int32) *protoconf.FruitConf_Fruit_Item { } // FindItemMap1 finds the index: key(Price) to value(protoconf.FruitConf_Fruit_Item), -// which is the upper 1st-level treemap specified by (fruitType). +// which is the upper 1st-level map specified by (fruitType). // One key may correspond to multiple values, which are represented by a slice. -func (x *FruitConf) FindItemMap1(fruitType int32) *FruitConf_OrderedIndex_ItemMap { - return x.orderedIndexItemMap1[fruitType] +func (x *FruitConf) FindItemMap1(fruitType int32) FruitConf_Index_ItemMap { + return x.indexItemMap1[fruitType] } -// FindItem1 finds a slice of all values of the given key(s) in the upper 1st-level treemap +// FindItem1 finds a slice of all values of the given key(s) in the upper 1st-level map // specified by (fruitType). func (x *FruitConf) FindItem1(fruitType int32, price int32) []*protoconf.FruitConf_Fruit_Item { - m := x.FindItemMap1(fruitType) + return x.FindItemMap1(fruitType)[price] +} + +// FindFirstItem1 finds the first value of the given key(s) in the upper 1st-level map +// specified by (fruitType), or nil if no value found. +func (x *FruitConf) FindFirstItem1(fruitType int32, price int32) *protoconf.FruitConf_Fruit_Item { + val := x.FindItem1(fruitType, price) + if len(val) > 0 { + return val[0] + } + return nil +} + +// OrderedIndex: Price@OrderedFruit + +// FindOrderedFruitMap finds the ordered index: key(Price@OrderedFruit) to value(protoconf.FruitConf_Fruit_Item) treemap. +// One key may correspond to multiple values, which are represented by a slice. +func (x *FruitConf) FindOrderedFruitMap() *FruitConf_OrderedIndex_OrderedFruitMap { + return x.orderedIndexOrderedFruitMap +} + +// FindOrderedFruit finds a slice of all values of the given key(s). +func (x *FruitConf) FindOrderedFruit(price int32) []*protoconf.FruitConf_Fruit_Item { + val, _ := x.orderedIndexOrderedFruitMap.Get(price) + return val +} + +// FindFirstOrderedFruit finds the first value of the given key(s), +// or nil if no value found. +func (x *FruitConf) FindFirstOrderedFruit(price int32) *protoconf.FruitConf_Fruit_Item { + val := x.FindOrderedFruit(price) + if len(val) > 0 { + return val[0] + } + return nil +} + +// FindOrderedFruitMap1 finds the index: key(Price@OrderedFruit) to value(protoconf.FruitConf_Fruit_Item), +// which is the upper 1st-level treemap specified by (fruitType). +// One key may correspond to multiple values, which are represented by a slice. +func (x *FruitConf) FindOrderedFruitMap1(fruitType int32) *FruitConf_OrderedIndex_OrderedFruitMap { + return x.orderedIndexOrderedFruitMap1[fruitType] +} + +// FindOrderedFruit1 finds a slice of all values of the given key(s) in the upper 1st-level treemap +// specified by (fruitType). +func (x *FruitConf) FindOrderedFruit1(fruitType int32, price int32) []*protoconf.FruitConf_Fruit_Item { + m := x.FindOrderedFruitMap1(fruitType) if m == nil { return nil } @@ -197,9 +279,213 @@ func (x *FruitConf) FindItem1(fruitType int32, price int32) []*protoconf.FruitCo return val } -// FindFirstItem1 finds the first value of the given key(s) in the upper 1st-level treemap +// FindFirstOrderedFruit1 finds the first value of the given key(s) in the upper 1st-level treemap // specified by (fruitType), or nil if no value found. -func (x *FruitConf) FindFirstItem1(fruitType int32, price int32) *protoconf.FruitConf_Fruit_Item { +func (x *FruitConf) FindFirstOrderedFruit1(fruitType int32, price int32) *protoconf.FruitConf_Fruit_Item { + val := x.FindOrderedFruit1(fruitType, price) + if len(val) > 0 { + return val[0] + } + return nil +} + +// Index types. +// Index: Price +type Fruit6Conf_Index_ItemMap = map[int32][]*protoconf.Fruit6Conf_Fruit_Item + +// OrderedIndex types. +// OrderedIndex: Price@OrderedFruit +type Fruit6Conf_OrderedIndex_OrderedFruitMap = treemap.TreeMap[int32, []*protoconf.Fruit6Conf_Fruit_Item] + +// Fruit6Conf is a wrapper around protobuf message: protoconf.Fruit6Conf. +// +// It is designed for three goals: +// +// 1. Easy use: simple yet powerful accessers. +// 2. Elegant API: concise and clean functions. +// 3. Extensibility: Map, OrdererdMap, Index, OrderedIndex... +type Fruit6Conf struct { + UnimplementedMessager + data, originalData *protoconf.Fruit6Conf + indexItemMap Fruit6Conf_Index_ItemMap + indexItemMap1 map[int32]Fruit6Conf_Index_ItemMap + orderedIndexOrderedFruitMap *Fruit6Conf_OrderedIndex_OrderedFruitMap + orderedIndexOrderedFruitMap1 map[int32]*Fruit6Conf_OrderedIndex_OrderedFruitMap +} + +// Name returns the Fruit6Conf's message name. +func (x *Fruit6Conf) Name() string { + return string((*protoconf.Fruit6Conf)(nil).ProtoReflect().Descriptor().Name()) +} + +// Data returns the Fruit6Conf's inner message data. +func (x *Fruit6Conf) Data() *protoconf.Fruit6Conf { + if x != nil { + return x.data + } + return nil +} + +// Load loads Fruit6Conf's content in the given dir, based on format and messager options. +func (x *Fruit6Conf) Load(dir string, format format.Format, opts *load.MessagerOptions) error { + start := time.Now() + defer func() { + x.Stats.Duration = time.Since(start) + }() + x.data = &protoconf.Fruit6Conf{} + err := load.LoadMessagerInDir(x.data, dir, format, opts) + if err != nil { + return err + } + if x.backup { + x.originalData = proto.Clone(x.data).(*protoconf.Fruit6Conf) + } + return x.processAfterLoad() +} + +// Store stores Fruit6Conf's content to file in the specified directory and format. +// Available formats: JSON, Bin, and Text. +func (x *Fruit6Conf) Store(dir string, format format.Format, options ...store.Option) error { + return store.Store(x.Data(), dir, format, options...) +} + +// Message returns the Fruit6Conf's inner message data. +func (x *Fruit6Conf) Message() proto.Message { + return x.Data() +} + +// Messager returns the current messager. +func (x *Fruit6Conf) Messager() Messager { + return x +} + +// originalMessage returns the Fruit6Conf's original inner message. +func (x *Fruit6Conf) originalMessage() proto.Message { + if x != nil { + return x.originalData + } + return nil +} + +// processAfterLoad runs after this messager is loaded. +func (x *Fruit6Conf) processAfterLoad() error { + // Index init. + x.indexItemMap = make(Fruit6Conf_Index_ItemMap) + x.indexItemMap1 = make(map[int32]Fruit6Conf_Index_ItemMap) + for k1, v1 := range x.data.GetFruitMap() { + for _, v2 := range v1.GetItemList() { + { + // Index: Price + key := v2.GetPrice() + x.indexItemMap[key] = append(x.indexItemMap[key], v2) + if x.indexItemMap1[k1] == nil { + x.indexItemMap1[k1] = make(Fruit6Conf_Index_ItemMap) + } + x.indexItemMap1[k1][key] = append(x.indexItemMap1[k1][key], v2) + } + } + } + // Index(sort): Price + indexItemMapSorter := func(itemList []*protoconf.Fruit6Conf_Fruit_Item) func(i, j int) bool { + return func(i, j int) bool { + return itemList[i].GetId() < itemList[j].GetId() + } + } + for _, itemList := range x.indexItemMap { + sort.Slice(itemList, indexItemMapSorter(itemList)) + } + for _, itemMap := range x.indexItemMap1 { + for _, itemList := range itemMap { + sort.Slice(itemList, indexItemMapSorter(itemList)) + } + } + // OrderedIndex init. + x.orderedIndexOrderedFruitMap = treemap.New[int32, []*protoconf.Fruit6Conf_Fruit_Item]() + x.orderedIndexOrderedFruitMap1 = make(map[int32]*Fruit6Conf_OrderedIndex_OrderedFruitMap) + for k1, v1 := range x.data.GetFruitMap() { + for _, v2 := range v1.GetItemList() { + { + // OrderedIndex: Price@OrderedFruit + key := v2.GetPrice() + value, _ := x.orderedIndexOrderedFruitMap.Get(key) + x.orderedIndexOrderedFruitMap.Put(key, append(value, v2)) + if x.orderedIndexOrderedFruitMap1[k1] == nil { + x.orderedIndexOrderedFruitMap1[k1] = treemap.New[int32, []*protoconf.Fruit6Conf_Fruit_Item]() + } + orderedIndexOrderedFruitMap1Value, _ := x.orderedIndexOrderedFruitMap1[k1].Get(key) + x.orderedIndexOrderedFruitMap1[k1].Put(key, append(orderedIndexOrderedFruitMap1Value, v2)) + } + } + } + // OrderedIndex(sort): Price@OrderedFruit + orderedIndexOrderedFruitMapSorter := func(itemList []*protoconf.Fruit6Conf_Fruit_Item) func(i, j int) bool { + return func(i, j int) bool { + return itemList[i].GetId() < itemList[j].GetId() + } + } + x.orderedIndexOrderedFruitMap.Range(func(key int32, itemList []*protoconf.Fruit6Conf_Fruit_Item) bool { + sort.Slice(itemList, orderedIndexOrderedFruitMapSorter(itemList)) + return true + }) + for _, itemMap := range x.orderedIndexOrderedFruitMap1 { + itemMap.Range(func(key int32, itemList []*protoconf.Fruit6Conf_Fruit_Item) bool { + sort.Slice(itemList, orderedIndexOrderedFruitMapSorter(itemList)) + return true + }) + } + return nil +} + +// Get1 finds value in the 1st-level map. It will return +// NotFound error if the key is not found. +func (x *Fruit6Conf) Get1(fruitType int32) (*protoconf.Fruit6Conf_Fruit, error) { + d := x.Data().GetFruitMap() + if val, ok := d[fruitType]; !ok { + return nil, fmt.Errorf("fruitType(%v) %w", fruitType, ErrNotFound) + } else { + return val, nil + } +} + +// Index: Price + +// FindItemMap finds the index: key(Price) to value(protoconf.Fruit6Conf_Fruit_Item) map. +// One key may correspond to multiple values, which are represented by a slice. +func (x *Fruit6Conf) FindItemMap() Fruit6Conf_Index_ItemMap { + return x.indexItemMap +} + +// FindItem finds a slice of all values of the given key(s). +func (x *Fruit6Conf) FindItem(price int32) []*protoconf.Fruit6Conf_Fruit_Item { + return x.indexItemMap[price] +} + +// FindFirstItem finds the first value of the given key(s), +// or nil if no value found. +func (x *Fruit6Conf) FindFirstItem(price int32) *protoconf.Fruit6Conf_Fruit_Item { + val := x.FindItem(price) + if len(val) > 0 { + return val[0] + } + return nil +} + +// FindItemMap1 finds the index: key(Price) to value(protoconf.Fruit6Conf_Fruit_Item), +// which is the upper 1st-level map specified by (fruitType). +// One key may correspond to multiple values, which are represented by a slice. +func (x *Fruit6Conf) FindItemMap1(fruitType int32) Fruit6Conf_Index_ItemMap { + return x.indexItemMap1[fruitType] +} + +// FindItem1 finds a slice of all values of the given key(s) in the upper 1st-level map +// specified by (fruitType). +func (x *Fruit6Conf) FindItem1(fruitType int32, price int32) []*protoconf.Fruit6Conf_Fruit_Item { + return x.FindItemMap1(fruitType)[price] +} + +// FindFirstItem1 finds the first value of the given key(s) in the upper 1st-level map +// specified by (fruitType), or nil if no value found. +func (x *Fruit6Conf) FindFirstItem1(fruitType int32, price int32) *protoconf.Fruit6Conf_Fruit_Item { val := x.FindItem1(fruitType, price) if len(val) > 0 { return val[0] @@ -207,6 +493,64 @@ func (x *FruitConf) FindFirstItem1(fruitType int32, price int32) *protoconf.Frui return nil } +// OrderedIndex: Price@OrderedFruit + +// FindOrderedFruitMap finds the ordered index: key(Price@OrderedFruit) to value(protoconf.Fruit6Conf_Fruit_Item) treemap. +// One key may correspond to multiple values, which are represented by a slice. +func (x *Fruit6Conf) FindOrderedFruitMap() *Fruit6Conf_OrderedIndex_OrderedFruitMap { + return x.orderedIndexOrderedFruitMap +} + +// FindOrderedFruit finds a slice of all values of the given key(s). +func (x *Fruit6Conf) FindOrderedFruit(price int32) []*protoconf.Fruit6Conf_Fruit_Item { + val, _ := x.orderedIndexOrderedFruitMap.Get(price) + return val +} + +// FindFirstOrderedFruit finds the first value of the given key(s), +// or nil if no value found. +func (x *Fruit6Conf) FindFirstOrderedFruit(price int32) *protoconf.Fruit6Conf_Fruit_Item { + val := x.FindOrderedFruit(price) + if len(val) > 0 { + return val[0] + } + return nil +} + +// FindOrderedFruitMap1 finds the index: key(Price@OrderedFruit) to value(protoconf.Fruit6Conf_Fruit_Item), +// which is the upper 1st-level treemap specified by (fruitType). +// One key may correspond to multiple values, which are represented by a slice. +func (x *Fruit6Conf) FindOrderedFruitMap1(fruitType int32) *Fruit6Conf_OrderedIndex_OrderedFruitMap { + return x.orderedIndexOrderedFruitMap1[fruitType] +} + +// FindOrderedFruit1 finds a slice of all values of the given key(s) in the upper 1st-level treemap +// specified by (fruitType). +func (x *Fruit6Conf) FindOrderedFruit1(fruitType int32, price int32) []*protoconf.Fruit6Conf_Fruit_Item { + m := x.FindOrderedFruitMap1(fruitType) + if m == nil { + return nil + } + val, _ := m.Get(price) + return val +} + +// FindFirstOrderedFruit1 finds the first value of the given key(s) in the upper 1st-level treemap +// specified by (fruitType), or nil if no value found. +func (x *Fruit6Conf) FindFirstOrderedFruit1(fruitType int32, price int32) *protoconf.Fruit6Conf_Fruit_Item { + val := x.FindOrderedFruit1(fruitType, price) + if len(val) > 0 { + return val[0] + } + return nil +} + +// LevelIndex keys. +type Fruit2Conf_LevelIndex_Fruit_Country_ItemKey struct { + FruitType int32 // key of protoconf.Fruit2Conf.fruit_map + Id int32 // key of protoconf.Fruit2Conf.Fruit.Country.item_map +} + // Index types. // Index: CountryName type Fruit2Conf_Index_CountryMap = map[string][]*protoconf.Fruit2Conf_Fruit_Country @@ -229,8 +573,10 @@ type Fruit2Conf struct { UnimplementedMessager data, originalData *protoconf.Fruit2Conf indexCountryMap Fruit2Conf_Index_CountryMap + indexCountryMap1 map[int32]Fruit2Conf_Index_CountryMap indexAttrMap Fruit2Conf_Index_AttrMap indexAttrMap1 map[int32]Fruit2Conf_Index_AttrMap + indexAttrMap2 map[Fruit2Conf_LevelIndex_Fruit_Country_ItemKey]Fruit2Conf_Index_AttrMap orderedIndexItemMap *Fruit2Conf_OrderedIndex_ItemMap orderedIndexItemMap1 map[int32]*Fruit2Conf_OrderedIndex_ItemMap } @@ -293,16 +639,22 @@ func (x *Fruit2Conf) originalMessage() proto.Message { func (x *Fruit2Conf) processAfterLoad() error { // Index init. x.indexCountryMap = make(Fruit2Conf_Index_CountryMap) + x.indexCountryMap1 = make(map[int32]Fruit2Conf_Index_CountryMap) x.indexAttrMap = make(Fruit2Conf_Index_AttrMap) x.indexAttrMap1 = make(map[int32]Fruit2Conf_Index_AttrMap) + x.indexAttrMap2 = make(map[Fruit2Conf_LevelIndex_Fruit_Country_ItemKey]Fruit2Conf_Index_AttrMap) for k1, v1 := range x.data.GetFruitMap() { for _, v2 := range v1.GetCountryList() { { // Index: CountryName key := v2.GetName() x.indexCountryMap[key] = append(x.indexCountryMap[key], v2) + if x.indexCountryMap1[k1] == nil { + x.indexCountryMap1[k1] = make(Fruit2Conf_Index_CountryMap) + } + x.indexCountryMap1[k1][key] = append(x.indexCountryMap1[k1][key], v2) } - for _, v3 := range v2.GetItemMap() { + for k2, v3 := range v2.GetItemMap() { for _, v4 := range v3.GetAttrList() { { // Index: CountryItemAttrName @@ -312,6 +664,11 @@ func (x *Fruit2Conf) processAfterLoad() error { x.indexAttrMap1[k1] = make(Fruit2Conf_Index_AttrMap) } x.indexAttrMap1[k1][key] = append(x.indexAttrMap1[k1][key], v4) + indexAttrMap2Keys := Fruit2Conf_LevelIndex_Fruit_Country_ItemKey{k1, k2} + if x.indexAttrMap2[indexAttrMap2Keys] == nil { + x.indexAttrMap2[indexAttrMap2Keys] = make(Fruit2Conf_Index_AttrMap) + } + x.indexAttrMap2[indexAttrMap2Keys][key] = append(x.indexAttrMap2[indexAttrMap2Keys][key], v4) } } } @@ -390,6 +747,29 @@ func (x *Fruit2Conf) FindFirstCountry(name string) *protoconf.Fruit2Conf_Fruit_C return nil } +// FindCountryMap1 finds the index: key(CountryName) to value(protoconf.Fruit2Conf_Fruit_Country), +// which is the upper 1st-level map specified by (fruitType). +// One key may correspond to multiple values, which are represented by a slice. +func (x *Fruit2Conf) FindCountryMap1(fruitType int32) Fruit2Conf_Index_CountryMap { + return x.indexCountryMap1[fruitType] +} + +// FindCountry1 finds a slice of all values of the given key(s) in the upper 1st-level map +// specified by (fruitType). +func (x *Fruit2Conf) FindCountry1(fruitType int32, name string) []*protoconf.Fruit2Conf_Fruit_Country { + return x.FindCountryMap1(fruitType)[name] +} + +// FindFirstCountry1 finds the first value of the given key(s) in the upper 1st-level map +// specified by (fruitType), or nil if no value found. +func (x *Fruit2Conf) FindFirstCountry1(fruitType int32, name string) *protoconf.Fruit2Conf_Fruit_Country { + val := x.FindCountry1(fruitType, name) + if len(val) > 0 { + return val[0] + } + return nil +} + // Index: CountryItemAttrName // FindAttrMap finds the index: key(CountryItemAttrName) to value(protoconf.Fruit2Conf_Fruit_Country_Item_Attr) map. @@ -436,6 +816,29 @@ func (x *Fruit2Conf) FindFirstAttr1(fruitType int32, name string) *protoconf.Fru return nil } +// FindAttrMap2 finds the index: key(CountryItemAttrName) to value(protoconf.Fruit2Conf_Fruit_Country_Item_Attr), +// which is the upper 2nd-level map specified by (fruitType, id). +// One key may correspond to multiple values, which are represented by a slice. +func (x *Fruit2Conf) FindAttrMap2(fruitType int32, id int32) Fruit2Conf_Index_AttrMap { + return x.indexAttrMap2[Fruit2Conf_LevelIndex_Fruit_Country_ItemKey{fruitType, id}] +} + +// FindAttr2 finds a slice of all values of the given key(s) in the upper 2nd-level map +// specified by (fruitType, id). +func (x *Fruit2Conf) FindAttr2(fruitType int32, id int32, name string) []*protoconf.Fruit2Conf_Fruit_Country_Item_Attr { + return x.FindAttrMap2(fruitType, id)[name] +} + +// FindFirstAttr2 finds the first value of the given key(s) in the upper 2nd-level map +// specified by (fruitType, id), or nil if no value found. +func (x *Fruit2Conf) FindFirstAttr2(fruitType int32, id int32, name string) *protoconf.Fruit2Conf_Fruit_Country_Item_Attr { + val := x.FindAttr2(fruitType, id, name) + if len(val) > 0 { + return val[0] + } + return nil +} + // OrderedIndex: CountryItemPrice // FindItemMap finds the ordered index: key(CountryItemPrice) to value(protoconf.Fruit2Conf_Fruit_Country_Item) treemap. @@ -511,6 +914,7 @@ type Fruit3Conf struct { data, originalData *protoconf.Fruit3Conf indexCountryMap Fruit3Conf_Index_CountryMap indexAttrMap Fruit3Conf_Index_AttrMap + indexAttrMap1 map[int32]Fruit3Conf_Index_AttrMap orderedIndexItemMap *Fruit3Conf_OrderedIndex_ItemMap } @@ -573,6 +977,7 @@ func (x *Fruit3Conf) processAfterLoad() error { // Index init. x.indexCountryMap = make(Fruit3Conf_Index_CountryMap) x.indexAttrMap = make(Fruit3Conf_Index_AttrMap) + x.indexAttrMap1 = make(map[int32]Fruit3Conf_Index_AttrMap) for _, v1 := range x.data.GetFruitList() { for _, v2 := range v1.GetCountryList() { { @@ -580,12 +985,16 @@ func (x *Fruit3Conf) processAfterLoad() error { key := v2.GetName() x.indexCountryMap[key] = append(x.indexCountryMap[key], v2) } - for _, v3 := range v2.GetItemMap() { + for k1, v3 := range v2.GetItemMap() { for _, v4 := range v3.GetAttrList() { { // Index: CountryItemAttrName key := v4.GetName() x.indexAttrMap[key] = append(x.indexAttrMap[key], v4) + if x.indexAttrMap1[k1] == nil { + x.indexAttrMap1[k1] = make(Fruit3Conf_Index_AttrMap) + } + x.indexAttrMap1[k1][key] = append(x.indexAttrMap1[k1][key], v4) } } } @@ -664,6 +1073,29 @@ func (x *Fruit3Conf) FindFirstAttr(name string) *protoconf.Fruit3Conf_Fruit_Coun return nil } +// FindAttrMap1 finds the index: key(CountryItemAttrName) to value(protoconf.Fruit3Conf_Fruit_Country_Item_Attr), +// which is the upper 1st-level map specified by (id). +// One key may correspond to multiple values, which are represented by a slice. +func (x *Fruit3Conf) FindAttrMap1(id int32) Fruit3Conf_Index_AttrMap { + return x.indexAttrMap1[id] +} + +// FindAttr1 finds a slice of all values of the given key(s) in the upper 1st-level map +// specified by (id). +func (x *Fruit3Conf) FindAttr1(id int32, name string) []*protoconf.Fruit3Conf_Fruit_Country_Item_Attr { + return x.FindAttrMap1(id)[name] +} + +// FindFirstAttr1 finds the first value of the given key(s) in the upper 1st-level map +// specified by (id), or nil if no value found. +func (x *Fruit3Conf) FindFirstAttr1(id int32, name string) *protoconf.Fruit3Conf_Fruit_Country_Item_Attr { + val := x.FindAttr1(id, name) + if len(val) > 0 { + return val[0] + } + return nil +} + // OrderedIndex: CountryItemPrice // FindItemMap finds the ordered index: key(CountryItemPrice) to value(protoconf.Fruit3Conf_Fruit_Country_Item) treemap. @@ -690,8 +1122,13 @@ func (x *Fruit3Conf) FindFirstItem(price int32) *protoconf.Fruit3Conf_Fruit_Coun // LevelIndex keys. type Fruit4Conf_LevelIndex_Fruit_CountryKey struct { - FruitType int32 - Id int32 + FruitType int32 // key of protoconf.Fruit4Conf.fruit_map + Id int32 // key of protoconf.Fruit4Conf.Fruit.country_map +} +type Fruit4Conf_LevelIndex_Fruit_Country_ItemKey struct { + FruitType int32 // key of protoconf.Fruit4Conf.fruit_map + Id int32 // key of protoconf.Fruit4Conf.Fruit.country_map + Id3 int32 // key of protoconf.Fruit4Conf.Fruit.Country.item_map (renamed from Id) } // Index types. @@ -720,6 +1157,7 @@ type Fruit4Conf struct { indexAttrMap Fruit4Conf_Index_AttrMap indexAttrMap1 map[int32]Fruit4Conf_Index_AttrMap indexAttrMap2 map[Fruit4Conf_LevelIndex_Fruit_CountryKey]Fruit4Conf_Index_AttrMap + indexAttrMap3 map[Fruit4Conf_LevelIndex_Fruit_Country_ItemKey]Fruit4Conf_Index_AttrMap orderedIndexItemMap *Fruit4Conf_OrderedIndex_ItemMap orderedIndexItemMap1 map[int32]*Fruit4Conf_OrderedIndex_ItemMap orderedIndexItemMap2 map[Fruit4Conf_LevelIndex_Fruit_CountryKey]*Fruit4Conf_OrderedIndex_ItemMap @@ -787,6 +1225,7 @@ func (x *Fruit4Conf) processAfterLoad() error { x.indexAttrMap = make(Fruit4Conf_Index_AttrMap) x.indexAttrMap1 = make(map[int32]Fruit4Conf_Index_AttrMap) x.indexAttrMap2 = make(map[Fruit4Conf_LevelIndex_Fruit_CountryKey]Fruit4Conf_Index_AttrMap) + x.indexAttrMap3 = make(map[Fruit4Conf_LevelIndex_Fruit_Country_ItemKey]Fruit4Conf_Index_AttrMap) for k1, v1 := range x.data.GetFruitMap() { for k2, v2 := range v1.GetCountryMap() { { @@ -798,7 +1237,7 @@ func (x *Fruit4Conf) processAfterLoad() error { } x.indexCountryMap1[k1][key] = append(x.indexCountryMap1[k1][key], v2) } - for _, v3 := range v2.GetItemMap() { + for k3, v3 := range v2.GetItemMap() { for _, v4 := range v3.GetAttrList() { { // Index: CountryItemAttrName @@ -813,6 +1252,11 @@ func (x *Fruit4Conf) processAfterLoad() error { x.indexAttrMap2[indexAttrMap2Keys] = make(Fruit4Conf_Index_AttrMap) } x.indexAttrMap2[indexAttrMap2Keys][key] = append(x.indexAttrMap2[indexAttrMap2Keys][key], v4) + indexAttrMap3Keys := Fruit4Conf_LevelIndex_Fruit_Country_ItemKey{k1, k2, k3} + if x.indexAttrMap3[indexAttrMap3Keys] == nil { + x.indexAttrMap3[indexAttrMap3Keys] = make(Fruit4Conf_Index_AttrMap) + } + x.indexAttrMap3[indexAttrMap3Keys][key] = append(x.indexAttrMap3[indexAttrMap3Keys][key], v4) } } } @@ -1026,6 +1470,29 @@ func (x *Fruit4Conf) FindFirstAttr2(fruitType int32, id int32, name string) *pro return nil } +// FindAttrMap3 finds the index: key(CountryItemAttrName) to value(protoconf.Fruit4Conf_Fruit_Country_Item_Attr), +// which is the upper 3rd-level map specified by (fruitType, id, id3). +// One key may correspond to multiple values, which are represented by a slice. +func (x *Fruit4Conf) FindAttrMap3(fruitType int32, id int32, id3 int32) Fruit4Conf_Index_AttrMap { + return x.indexAttrMap3[Fruit4Conf_LevelIndex_Fruit_Country_ItemKey{fruitType, id, id3}] +} + +// FindAttr3 finds a slice of all values of the given key(s) in the upper 3rd-level map +// specified by (fruitType, id, id3). +func (x *Fruit4Conf) FindAttr3(fruitType int32, id int32, id3 int32, name string) []*protoconf.Fruit4Conf_Fruit_Country_Item_Attr { + return x.FindAttrMap3(fruitType, id, id3)[name] +} + +// FindFirstAttr3 finds the first value of the given key(s) in the upper 3rd-level map +// specified by (fruitType, id, id3), or nil if no value found. +func (x *Fruit4Conf) FindFirstAttr3(fruitType int32, id int32, id3 int32, name string) *protoconf.Fruit4Conf_Fruit_Country_Item_Attr { + val := x.FindAttr3(fruitType, id, id3, name) + if len(val) > 0 { + return val[0] + } + return nil +} + // OrderedIndex: CountryItemPrice // FindItemMap finds the ordered index: key(CountryItemPrice) to value(protoconf.Fruit4Conf_Fruit_Country_Item) treemap. @@ -1290,6 +1757,9 @@ func init() { Register(func() Messager { return new(FruitConf) }) + Register(func() Messager { + return new(Fruit6Conf) + }) Register(func() Messager { return new(Fruit2Conf) }) diff --git a/test/go-tableau-loader/protoconf/loader/messager_container.pc.go b/test/go-tableau-loader/protoconf/loader/messager_container.pc.go index 4a68a631..a6e08fbe 100644 --- a/test/go-tableau-loader/protoconf/loader/messager_container.pc.go +++ b/test/go-tableau-loader/protoconf/loader/messager_container.pc.go @@ -16,6 +16,7 @@ type MessagerContainer struct { heroConf *HeroConf heroBaseConf *HeroBaseConf fruitConf *FruitConf + fruit6Conf *Fruit6Conf fruit2Conf *Fruit2Conf fruit3Conf *Fruit3Conf fruit4Conf *Fruit4Conf @@ -38,6 +39,7 @@ func newMessagerContainer(messagerMap MessagerMap) *MessagerContainer { heroConf: GetMessager[*HeroConf](messagerMap), heroBaseConf: GetMessager[*HeroBaseConf](messagerMap), fruitConf: GetMessager[*FruitConf](messagerMap), + fruit6Conf: GetMessager[*Fruit6Conf](messagerMap), fruit2Conf: GetMessager[*Fruit2Conf](messagerMap), fruit3Conf: GetMessager[*Fruit3Conf](messagerMap), fruit4Conf: GetMessager[*Fruit4Conf](messagerMap), @@ -80,6 +82,10 @@ func (mc *MessagerContainer) GetFruitConf() *FruitConf { return mc.fruitConf } +func (mc *MessagerContainer) GetFruit6Conf() *Fruit6Conf { + return mc.fruit6Conf +} + func (mc *MessagerContainer) GetFruit2Conf() *Fruit2Conf { return mc.fruit2Conf } diff --git a/test/go-tableau-loader/protoconf/loader/test_conf.pc.go b/test/go-tableau-loader/protoconf/loader/test_conf.pc.go index 72fe645e..f6e78a8f 100644 --- a/test/go-tableau-loader/protoconf/loader/test_conf.pc.go +++ b/test/go-tableau-loader/protoconf/loader/test_conf.pc.go @@ -33,8 +33,13 @@ type ActivityConf_OrderedMap_ActivityMap = treemap.TreeMap[uint64, *ActivityConf // LevelIndex keys. type ActivityConf_LevelIndex_Activity_ChapterKey struct { - ActivityId uint64 - ChapterId uint32 + ActivityId uint64 // key of protoconf.ActivityConf.activity_map + ChapterId uint32 // key of protoconf.ActivityConf.Activity.chapter_map +} +type ActivityConf_LevelIndex_protoconf_SectionKey struct { + ActivityId uint64 // key of protoconf.ActivityConf.activity_map + ChapterId uint32 // key of protoconf.ActivityConf.Activity.chapter_map + SectionId uint32 // key of protoconf.ActivityConf.Activity.Chapter.section_map } // Index types. @@ -69,6 +74,7 @@ type ActivityConf struct { indexAwardMap ActivityConf_Index_AwardMap indexAwardMap1 map[uint64]ActivityConf_Index_AwardMap indexAwardMap2 map[ActivityConf_LevelIndex_Activity_ChapterKey]ActivityConf_Index_AwardMap + indexAwardMap3 map[ActivityConf_LevelIndex_protoconf_SectionKey]ActivityConf_Index_AwardMap } // Name returns the ActivityConf's message name. @@ -166,6 +172,7 @@ func (x *ActivityConf) processAfterLoad() error { x.indexAwardMap = make(ActivityConf_Index_AwardMap) x.indexAwardMap1 = make(map[uint64]ActivityConf_Index_AwardMap) x.indexAwardMap2 = make(map[ActivityConf_LevelIndex_Activity_ChapterKey]ActivityConf_Index_AwardMap) + x.indexAwardMap3 = make(map[ActivityConf_LevelIndex_protoconf_SectionKey]ActivityConf_Index_AwardMap) for k1, v1 := range x.data.GetActivityMap() { { // Index: ActivityName @@ -191,7 +198,7 @@ func (x *ActivityConf) processAfterLoad() error { } x.indexNamedChapterMap1[k1][key] = append(x.indexNamedChapterMap1[k1][key], v2) } - for _, v3 := range v2.GetSectionMap() { + for k3, v3 := range v2.GetSectionMap() { for _, v4 := range v3.GetSectionItemList() { { // Index: SectionItemID@Award @@ -206,6 +213,11 @@ func (x *ActivityConf) processAfterLoad() error { x.indexAwardMap2[indexAwardMap2Keys] = make(ActivityConf_Index_AwardMap) } x.indexAwardMap2[indexAwardMap2Keys][key] = append(x.indexAwardMap2[indexAwardMap2Keys][key], v4) + indexAwardMap3Keys := ActivityConf_LevelIndex_protoconf_SectionKey{k1, k2, k3} + if x.indexAwardMap3[indexAwardMap3Keys] == nil { + x.indexAwardMap3[indexAwardMap3Keys] = make(ActivityConf_Index_AwardMap) + } + x.indexAwardMap3[indexAwardMap3Keys][key] = append(x.indexAwardMap3[indexAwardMap3Keys][key], v4) } } } @@ -512,6 +524,29 @@ func (x *ActivityConf) FindFirstAward2(activityId uint64, chapterId uint32, id u return nil } +// FindAwardMap3 finds the index: key(SectionItemID@Award) to value(protoconf.Section_SectionItem), +// which is the upper 3rd-level map specified by (activityId, chapterId, sectionId). +// One key may correspond to multiple values, which are represented by a slice. +func (x *ActivityConf) FindAwardMap3(activityId uint64, chapterId uint32, sectionId uint32) ActivityConf_Index_AwardMap { + return x.indexAwardMap3[ActivityConf_LevelIndex_protoconf_SectionKey{activityId, chapterId, sectionId}] +} + +// FindAward3 finds a slice of all values of the given key(s) in the upper 3rd-level map +// specified by (activityId, chapterId, sectionId). +func (x *ActivityConf) FindAward3(activityId uint64, chapterId uint32, sectionId uint32, id uint32) []*protoconf.Section_SectionItem { + return x.FindAwardMap3(activityId, chapterId, sectionId)[id] +} + +// FindFirstAward3 finds the first value of the given key(s) in the upper 3rd-level map +// specified by (activityId, chapterId, sectionId), or nil if no value found. +func (x *ActivityConf) FindFirstAward3(activityId uint64, chapterId uint32, sectionId uint32, id uint32) *protoconf.Section_SectionItem { + val := x.FindAward3(activityId, chapterId, sectionId, id) + if len(val) > 0 { + return val[0] + } + return nil +} + // ChapterConf is a wrapper around protobuf message: protoconf.ChapterConf. // // It is designed for three goals: diff --git a/test/proto/index_conf.proto b/test/proto/index_conf.proto index 802364f8..bac8a495 100644 --- a/test/proto/index_conf.proto +++ b/test/proto/index_conf.proto @@ -10,11 +10,12 @@ option (tableau.workbook) = { name: "Index.xlsx" }; -// Nesting: map -> map +// Nesting: map (vertical) -> map (vertical) message FruitConf { option (tableau.worksheet) = { name: "FruitConf" - ordered_index: "Price" + index: "Price" + ordered_index: "Price@OrderedFruit" }; map fruit_map = 1 [(tableau.field) = { key: "FruitType" layout: LAYOUT_VERTICAL }]; @@ -28,6 +29,25 @@ message FruitConf { } } +// Nesting: map (vertical) -> list (vertical)) +message Fruit6Conf { + option (tableau.worksheet) = { + name: "FruitConf" + index: "Price" + ordered_index: "Price@OrderedFruit" + }; + + map fruit_map = 1 [(tableau.field) = { key: "FruitType" layout: LAYOUT_VERTICAL }]; + message Fruit { + protoconf.FruitType fruit_type = 1 [(tableau.field) = { name: "FruitType" }]; + repeated Item item_list = 2 [(tableau.field) = { key: "ID" layout: LAYOUT_VERTICAL }]; + message Item { + int32 id = 1 [(tableau.field) = { name: "ID" }]; + int32 price = 2 [(tableau.field) = { name: "Price" }]; + } + } +} + // Nesting: map -> list -> map -> list message Fruit2Conf { option (tableau.worksheet) = { diff --git a/test/testdata/conf/Fruit6Conf.json b/test/testdata/conf/Fruit6Conf.json new file mode 100644 index 00000000..6f4bd7fa --- /dev/null +++ b/test/testdata/conf/Fruit6Conf.json @@ -0,0 +1,51 @@ +{ + "fruitMap": { + "1": { + "fruitType": "FRUIT_TYPE_APPLE", + "itemList": [ + { + "id": 1001, + "price": 10 + }, + { + "id": 1002, + "price": 20 + } + ] + }, + "2": { + "fruitType": "FRUIT_TYPE_ORANGE", + "itemList": [ + { + "id": 2001, + "price": 15 + }, + { + "id": 2009, + "price": 15 + }, + { + "id": 2000, + "price": 15 + }, + { + "id": 2002, + "price": 25 + } + ] + }, + "3": { + "fruitType": "FRUIT_TYPE_BANANA", + "itemList": [ + { + "id": 3001, + "price": 8 + }, + { + "id": 3002, + "price": 12 + } + ] + } + } +} \ No newline at end of file diff --git a/test/testdata/conf/FruitConf.json b/test/testdata/conf/FruitConf.json index 9e26dfee..6852bdad 100644 --- a/test/testdata/conf/FruitConf.json +++ b/test/testdata/conf/FruitConf.json @@ -1 +1,43 @@ -{} \ No newline at end of file +{ + "fruitMap": { + "1": { + "fruitType": "FRUIT_TYPE_APPLE", + "itemMap": { + "1001": { + "id": 1001, + "price": 10 + }, + "1002": { + "id": 1002, + "price": 20 + } + } + }, + "2": { + "fruitType": "FRUIT_TYPE_ORANGE", + "itemMap": { + "2001": { + "id": 2001, + "price": 15 + }, + "2002": { + "id": 2002, + "price": 25 + } + } + }, + "3": { + "fruitType": "FRUIT_TYPE_BANANA", + "itemMap": { + "3001": { + "id": 3001, + "price": 8 + }, + "3002": { + "id": 3002, + "price": 12 + } + } + } + } +} \ No newline at end of file