Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 55 additions & 7 deletions cmd/protoc-gen-cpp-tableau-loader/helper/helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -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<int32, Fruit> fruit_map = 1; // key field: "FruitType"
// message Fruit {
// map<int32, Country> country_map = 2; // key field: "ID"
// message Country {
// map<int32, Item> 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
}
}
Expand Down
24 changes: 18 additions & 6 deletions cmd/protoc-gen-cpp-tableau-loader/indexes/generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
})
}
}
Expand Down Expand Up @@ -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.")
Expand All @@ -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;")
Expand Down
12 changes: 6 additions & 6 deletions cmd/protoc-gen-cpp-tableau-loader/indexes/index.go
Original file line number Diff line number Diff line change
Expand Up @@ -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(), ").")
Expand All @@ -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 {
Expand All @@ -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();")
}
}
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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);")
Expand Down Expand Up @@ -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()
Expand Down
12 changes: 6 additions & 6 deletions cmd/protoc-gen-cpp-tableau-loader/indexes/ordered_index.go
Original file line number Diff line number Diff line change
Expand Up @@ -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(), ").")
Expand All @@ -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 {
Expand All @@ -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();")
}
}
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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);")
Expand Down Expand Up @@ -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()
Expand Down
59 changes: 51 additions & 8 deletions cmd/protoc-gen-csharp-tableau-loader/helper/helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -325,31 +325,74 @@ 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
// to build function parameters, arguments, and custom formatted strings
// 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<int32, Fruit> fruit_map = 1; // key field: "FruitType"
// message Fruit {
// map<int32, Country> country_map = 2; // key field: "ID"
// message Country {
// map<int32, Item> 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)
}

Expand Down
26 changes: 17 additions & 9 deletions cmd/protoc-gen-csharp-tableau-loader/indexes/generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.")
Expand All @@ -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(), ")")
Expand Down
Loading
Loading