diff --git a/.github/workflows/release-cpp.yml b/.github/workflows/release-cpp.yml index 08cd71ed..34792cca 100644 --- a/.github/workflows/release-cpp.yml +++ b/.github/workflows/release-cpp.yml @@ -27,7 +27,7 @@ jobs: - name: Install Go uses: actions/setup-go@v5 with: - go-version: "1.20.x" + go-version: "1.21.x" - name: Download dependencies run: | @@ -59,4 +59,4 @@ jobs: upload_url: ${{ github.event.release.upload_url }} asset_path: ./${{ steps.package.outputs.name }} asset_name: ${{ steps.package.outputs.name }} - asset_content_type: application/gzip \ No newline at end of file + asset_content_type: application/gzip diff --git a/.github/workflows/release-go.yml b/.github/workflows/release-go.yml index 137b55a2..68da1559 100644 --- a/.github/workflows/release-go.yml +++ b/.github/workflows/release-go.yml @@ -26,7 +26,7 @@ jobs: - name: Install Go uses: actions/setup-go@v5 with: - go-version: "1.20.x" + go-version: "1.21.x" - name: Download dependencies run: | @@ -58,4 +58,4 @@ jobs: upload_url: ${{ github.event.release.upload_url }} asset_path: ./${{ steps.package.outputs.name }} asset_name: ${{ steps.package.outputs.name }} - asset_content_type: application/gzip \ No newline at end of file + asset_content_type: application/gzip diff --git a/.github/workflows/testing-cpp.yml b/.github/workflows/testing-cpp.yml new file mode 100644 index 00000000..13ba5d61 --- /dev/null +++ b/.github/workflows/testing-cpp.yml @@ -0,0 +1,72 @@ +name: Testing C++ + +# Trigger on pushes, PRs (excluding documentation changes), and nightly. +on: + push: + branches: [master, main] + pull_request: + schedule: + - cron: 0 0 * * * # daily at 00:00 + workflow_dispatch: + +permissions: + contents: read + +jobs: + test: + strategy: + matrix: + include: + - os: ubuntu-latest + gen_script: bash gen.sh + cmake_config: cmake -S . -B build -G "Ninja" -DCMAKE_BUILD_TYPE=Debug + run_loader: ./bin/loader + - os: windows-latest + gen_script: cmd /c gen.bat + cmake_config: cmake -S . -B build -G "Ninja" -DCMAKE_BUILD_TYPE=Debug + run_loader: .\bin\loader.exe + + runs-on: ${{ matrix.os }} + timeout-minutes: 30 + + steps: + - name: Checkout Code + uses: actions/checkout@v4 + + - name: Install Go + uses: actions/setup-go@v5 + with: + go-version: 1.21.x + cache: true + + - name: Install dependencies (Ubuntu) + if: runner.os == 'Linux' + run: sudo apt-get update && sudo apt-get install -y cmake ninja-build + + - name: Install dependencies (Windows) + if: runner.os == 'Windows' + run: choco install cmake ninja -y + + - name: Setup MSVC (Windows) + if: runner.os == 'Windows' + uses: ilammy/msvc-dev-cmd@v1 + + - name: Init submodules and build protobuf + shell: bash + run: bash init.sh + + - name: Generate protoconf + working-directory: test/cpp-tableau-loader + run: ${{ matrix.gen_script }} + + - name: CMake Configure + working-directory: test/cpp-tableau-loader + run: ${{ matrix.cmake_config }} + + - name: CMake Build + working-directory: test/cpp-tableau-loader + run: cmake --build build --parallel + + - name: Run loader + working-directory: test/cpp-tableau-loader + run: ${{ matrix.run_loader }} diff --git a/.github/workflows/testing-go.yml b/.github/workflows/testing-go.yml new file mode 100644 index 00000000..9b661952 --- /dev/null +++ b/.github/workflows/testing-go.yml @@ -0,0 +1,64 @@ +name: Testing Go + +# Trigger on pushes, PRs (excluding documentation changes), and nightly. +on: + push: + branches: [master, main] + pull_request: + schedule: + - cron: 0 0 * * * # daily at 00:00 + workflow_dispatch: + +permissions: + contents: read + +jobs: + test: + strategy: + matrix: + go-version: [1.21.x] + os: [ubuntu-latest, windows-latest] + + runs-on: ${{ matrix.os }} + + steps: + - name: Checkout Code + uses: actions/checkout@v6 + + - name: Install Go + uses: actions/setup-go@v6 + with: + go-version: ${{ matrix.go-version }} + cache: true + + - name: Install Protoc + uses: arduino/setup-protoc@v3 + with: + version: "23.x" + repo-token: ${{ secrets.GITHUB_TOKEN }} + + - name: Install protoc-gen-go + run: go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.34.2 + + - name: Init Git submodules + run: git submodule update --init --recursive + + - name: Generate protoconf + shell: bash + run: PROTOC=protoc bash test/go-tableau-loader/gen.sh + + - name: Vet + run: go vet ./... + + - name: Unittest + run: go test -v -timeout 30m -race ./... -coverprofile=coverage.txt -covermode=atomic + + - name: Run loader + working-directory: test/go-tableau-loader + run: go run . + + - name: Upload coverage reports to Codecov + uses: codecov/codecov-action@v5 + with: + token: ${{ secrets.CODECOV_TOKEN }} + slug: tableauio/loader diff --git a/.gitignore b/.gitignore index f686f554..9c0a1db2 100644 --- a/.gitignore +++ b/.gitignore @@ -42,3 +42,4 @@ cmd/protoc-gen-go-tableau-loader/protoc-gen-go-tableau-loader test/go-tableau-loader/go-tableau-loader _lab/ts/src/protoconf +coverage.txt diff --git a/README.md b/README.md index d64ca672..4ff8e00d 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ The official config loader for [Tableau](https://github.com/tableauio/tableau). - C++17: `cmake -S . -B build` - C++20: `cmake -S . -B build -DCMAKE_CXX_STANDARD=20` - clang: `cmake -S . -B build -DCMAKE_CXX_COMPILER=clang++` -- Build: `cmake --build build -j16` +- Build: `cmake --build build --parallel` - Run: `./bin/loader` ### Dev at Windows @@ -44,7 +44,7 @@ The official config loader for [Tableau](https://github.com/tableauio/tableau). - CMake: - C++17: `cmake -S . -B build -G "NMake Makefiles"` - C++20: `cmake -S . -B build -G "NMake Makefiles" -DCMAKE_CXX_STANDARD=20` -- Build: `cmake --build build` +- Build: `cmake --build build --parallel` - Run: `.\bin\loader.exe` ### References @@ -58,7 +58,7 @@ The official config loader for [Tableau](https://github.com/tableauio/tableau). - Install protoc-gen-go: `go install google.golang.org/protobuf/cmd/protoc-gen-go@latest` - Change dir: `cd test/go-tableau-loader` - Generate protoconf: `bash ./gen.sh` -- Build: `go build` +- Run: `go run .` ### References diff --git a/cmd/protoc-gen-cpp-tableau-loader/helper/helper.go b/cmd/protoc-gen-cpp-tableau-loader/helper/helper.go index 88d98056..ce3dc6b9 100644 --- a/cmd/protoc-gen-cpp-tableau-loader/helper/helper.go +++ b/cmd/protoc-gen-cpp-tableau-loader/helper/helper.go @@ -176,6 +176,7 @@ 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 MapKeySlice []MapKey diff --git a/cmd/protoc-gen-cpp-tableau-loader/indexes/generator.go b/cmd/protoc-gen-cpp-tableau-loader/indexes/generator.go index b6f821fe..c5031c33 100644 --- a/cmd/protoc-gen-cpp-tableau-loader/indexes/generator.go +++ b/cmd/protoc-gen-cpp-tableau-loader/indexes/generator.go @@ -15,9 +15,7 @@ type Generator struct { message *protogen.Message // level message - maxDepth int - keys helper.MapKeySlice - mapFds []protoreflect.FieldDescriptor + keys helper.MapKeySlice } func NewGenerator(g *protogen.GeneratedFile, descriptor *index.IndexDescriptor, message *protogen.Message) *Generator { @@ -31,16 +29,18 @@ func NewGenerator(g *protogen.GeneratedFile, descriptor *index.IndexDescriptor, } func (x *Generator) initLevelMessage() { - for levelMessage := x.descriptor.LevelMessage; levelMessage != nil; levelMessage = levelMessage.NextLevel { - if fd := levelMessage.FD; fd != nil && fd.IsMap() { + for lm := x.descriptor.LevelMessage; lm != nil; lm = lm.NextLevel { + if fd := lm.FD; fd != nil && fd.IsMap() { + // Only collect map keys/fds when a deeper level has an index or ordered index, + // because these keys are used solely for building upper-level (leveled) containers. + if !lm.NextLevel.NeedGenAnyIndex() { + break + } x.keys = x.keys.AddMapKey(helper.MapKey{ Type: helper.ParseMapKeyType(fd.MapKey()), Name: helper.ParseMapFieldName(fd), + Fd: fd, }) - x.mapFds = append(x.mapFds, fd) - } - if len(levelMessage.Indexes) != 0 || len(levelMessage.OrderedIndexes) != 0 { - x.maxDepth = levelMessage.MapDepth } } } @@ -84,16 +84,32 @@ func (x *Generator) GenHppIndexFinders() { if !x.NeedGenerate() { return } - for i := 1; i <= x.maxDepth-3 && i <= len(x.mapFds)-1; i++ { - if i == 1 { + // Generate LevelIndex key structs for intermediate map levels. + // + // x.keys holds one entry per map level whose next level still needs an + // index (populated by initLevelMessage). For a 3-level map keyed by + // (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. 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} + // keys = [k1, k2, k3, k4] → struct for depth 2: {k1, k2} + // struct for depth 3: {k1, k2, k3} + // + // 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++ { + if i == 2 { x.g.P() x.g.P(helper.Indent(1), "// LevelIndex keys.") x.g.P(" public:") } - fd := x.mapFds[i] + fd := x.keys[i-1].Fd keyType := x.levelKeyType(fd) x.g.P(helper.Indent(1), "struct ", keyType, " {") - keys := x.keys[:i+1] + keys := x.keys[:i] for _, key := range keys { x.g.P(helper.Indent(2), key.Type, " ", key.Name, ";") } diff --git a/cmd/protoc-gen-cpp-tableau-loader/indexes/index.go b/cmd/protoc-gen-cpp-tableau-loader/indexes/index.go index 62835c99..eee1b66e 100644 --- a/cmd/protoc-gen-cpp-tableau-loader/indexes/index.go +++ b/cmd/protoc-gen-cpp-tableau-loader/indexes/index.go @@ -58,8 +58,8 @@ func (x *Generator) genHppIndexFinders() { return } var once sync.Once - for levelMessage := x.descriptor.LevelMessage; levelMessage != nil; levelMessage = levelMessage.NextLevel { - for _, index := range levelMessage.Indexes { + for lm := x.descriptor.LevelMessage; lm != nil; lm = lm.NextLevel { + for _, index := range lm.Indexes { x.g.P() once.Do(func() { x.g.P(helper.Indent(1), "// Index accessers.") }) x.g.P(helper.Indent(1), "// Index: ", index.Index) @@ -104,10 +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 <= levelMessage.MapDepth-2; i++ { - if i > len(x.keys) { - break - } + for i := 1; i < lm.MapDepth; 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(), ").") @@ -122,14 +119,11 @@ func (x *Generator) genHppIndexFinders() { x.g.P(" private:") x.g.P(helper.Indent(1), mapType, " ", x.indexContainerName(index, 0), ";") - for i := 1; i <= levelMessage.MapDepth-2; i++ { - if i > len(x.keys) { - break - } + for i := 1; i < lm.MapDepth; i++ { if i == 1 { x.g.P(helper.Indent(1), "std::unordered_map<", x.keys[0].Type, ", ", mapType, "> ", x.indexContainerName(index, i), ";") } else { - levelIndexKeyType := x.levelKeyType(x.mapFds[i-1]) + levelIndexKeyType := x.levelKeyType(x.keys[i-1].Fd) x.g.P(helper.Indent(1), "std::unordered_map<", levelIndexKeyType, ", ", mapType, ", ", levelIndexKeyType, "Hasher> ", x.indexContainerName(index, i), ";") } } @@ -143,40 +137,37 @@ func (x *Generator) genIndexLoader() { } defer x.genIndexSorter() x.g.P(helper.Indent(1), "// Index init.") - for levelMessage := x.descriptor.LevelMessage; levelMessage != nil; levelMessage = levelMessage.NextLevel { - for _, index := range levelMessage.Indexes { + 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 <= levelMessage.MapDepth-2; i++ { - if i > len(x.keys) { - break - } + for i := 1; i < lm.MapDepth; i++ { x.g.P(helper.Indent(1), x.indexContainerName(index, i), ".clear();") } } } parentDataName := "data_" - for levelMessage := x.descriptor.LevelMessage; levelMessage != nil; levelMessage = levelMessage.NextLevel { - for _, index := range levelMessage.Indexes { - x.genOneCppIndexLoader(levelMessage.MapDepth, levelMessage.Depth, index, parentDataName) - } - itemName := fmt.Sprintf("item%d", levelMessage.Depth) - if levelMessage.FD == nil { - break - } - if !levelMessage.NextLevel.NeedGenIndex() { + for lm := x.descriptor.LevelMessage; lm != nil; lm = lm.NextLevel { + itemName := fmt.Sprintf("item%d", lm.Depth) + if !lm.NeedGenIndex() { break } - x.g.P(helper.Indent(levelMessage.Depth), "for (auto&& ", itemName, " : ", parentDataName, x.fieldGetter(levelMessage.FD), ") {") + x.g.P(helper.Indent(lm.Depth), "for (auto&& ", itemName, " : ", parentDataName, x.fieldGetter(lm.FD), ") {") parentDataName = itemName - if levelMessage.FD.IsMap() { - x.g.P(helper.Indent(levelMessage.Depth+1), "auto k", levelMessage.MapDepth, " = ", itemName, ".first;") + if lm.FD.IsMap() { + if lm.NeedMapKeyForIndex() { + x.g.P(helper.Indent(lm.Depth+1), "auto k", lm.MapDepth, " = ", itemName, ".first;") + } parentDataName = itemName + ".second" } - defer x.g.P(helper.Indent(levelMessage.Depth), "}") + defer x.g.P(helper.Indent(lm.Depth), "}") + for _, index := range lm.Indexes { + x.genOneCppIndexLoader(lm, index, parentDataName) + } } } -func (x *Generator) genOneCppIndexLoader(depth int, ident int, index *index.LevelIndex, parentDataName string) { +func (x *Generator) genOneCppIndexLoader(lm *index.LevelMessage, index *index.LevelIndex, parentDataName string) { + ident := lm.Depth + 1 x.g.P(helper.Indent(ident), "{") x.g.P(helper.Indent(ident+1), "// Index: ", index.Index) if len(index.ColFields) == 1 { @@ -184,31 +175,31 @@ func (x *Generator) genOneCppIndexLoader(depth int, ident int, index *index.Leve field := index.ColFields[0] // just take the first field fieldName, _ := x.parseKeyFieldNameAndSuffix(field) if field.FD.IsList() { - itemName := fmt.Sprintf("item%d", depth) + itemName := fmt.Sprintf("item%d", lm.MapDepth+1) x.g.P(helper.Indent(ident+1), "for (auto&& ", itemName, " : ", parentDataName, fieldName, ") {") key := itemName if field.FD.Enum() != nil { key = "static_cast<" + helper.ParseCppType(field.FD) + ">(" + key + ")" } - x.genLoader(depth, ident+2, index, key, parentDataName) + x.genLoader(lm, index, ident+2, key, parentDataName) x.g.P(helper.Indent(ident+1), "}") } else { key := parentDataName + fieldName - x.genLoader(depth, ident+1, index, key, parentDataName) + x.genLoader(lm, index, ident+1, key, parentDataName) } } else { // multi-column index - x.generateOneCppMulticolumnIndex(depth, ident, index, parentDataName, nil) + x.generateOneCppMulticolumnIndex(lm, index, ident, parentDataName, nil) } x.g.P(helper.Indent(ident), "}") } -func (x *Generator) generateOneCppMulticolumnIndex(depth, ident int, index *index.LevelIndex, parentDataName string, keys helper.MapKeySlice) { +func (x *Generator) generateOneCppMulticolumnIndex(lm *index.LevelMessage, index *index.LevelIndex, ident int, parentDataName string, keys helper.MapKeySlice) { cursor := len(keys) if cursor >= len(index.ColFields) { keyType := x.indexMapKeyType(index) x.g.P(helper.Indent(ident+1), keyType, " key{", keys.GenGetArguments(), "};") - x.genLoader(depth, ident+1, index, "key", parentDataName) + x.genLoader(lm, index, ident+1, "key", parentDataName) return } field := index.ColFields[cursor] @@ -221,21 +212,18 @@ func (x *Generator) generateOneCppMulticolumnIndex(depth, ident int, index *inde key = "static_cast<" + helper.ParseCppType(field.FD) + ">(" + key + ")" } keys = keys.AddMapKey(helper.MapKey{Name: key}) - x.generateOneCppMulticolumnIndex(depth, ident+1, index, parentDataName, keys) + x.generateOneCppMulticolumnIndex(lm, index, ident+1, parentDataName, keys) x.g.P(helper.Indent(ident+1), "}") } else { key := parentDataName + fieldName keys = keys.AddMapKey(helper.MapKey{Name: key}) - x.generateOneCppMulticolumnIndex(depth, ident, index, parentDataName, keys) + x.generateOneCppMulticolumnIndex(lm, index, ident, parentDataName, keys) } } -func (x *Generator) genLoader(depth, ident int, index *index.LevelIndex, key, parentDataName string) { +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 <= depth-2; i++ { - if i > len(x.keys) { - break - } + for i := 1; i < lm.MapDepth; i++ { if i == 1 { x.g.P(helper.Indent(ident), x.indexContainerName(index, i), "[k1][", key, "].push_back(&", parentDataName, ");") } else { @@ -249,8 +237,8 @@ func (x *Generator) genLoader(depth, ident int, index *index.LevelIndex, key, pa } func (x *Generator) genIndexSorter() { - for levelMessage := x.descriptor.LevelMessage; levelMessage != nil; levelMessage = levelMessage.NextLevel { - for _, index := range levelMessage.Indexes { + for lm := x.descriptor.LevelMessage; lm != nil; lm = lm.NextLevel { + for _, index := range lm.Indexes { if len(index.SortedColFields) != 0 { valueType := x.mapValueType(index) x.g.P(helper.Indent(1), "// Index(sort): ", index.Index) @@ -273,10 +261,8 @@ func (x *Generator) genIndexSorter() { x.g.P(helper.Indent(1), "for (auto&& item : ", indexContainerName, ") {") x.g.P(helper.Indent(2), "std::sort(item.second.begin(), item.second.end(), ", indexContainerName, "sorter);") x.g.P(helper.Indent(1), "}") - for i := 1; i <= levelMessage.MapDepth-2; i++ { - if i > len(x.keys) { - break - } + // Iterate all leveled containers. + for i := 1; i < lm.MapDepth; 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);") @@ -292,8 +278,8 @@ func (x *Generator) genCppIndexFinders() { if !x.needGenerateIndex() { return } - for levelMessage := x.descriptor.LevelMessage; levelMessage != nil; levelMessage = levelMessage.NextLevel { - for _, index := range levelMessage.Indexes { + for lm := x.descriptor.LevelMessage; lm != nil; lm = lm.NextLevel { + for _, index := range lm.Indexes { vectorType := x.indexMapValueVectorType(index) mapType := x.indexMapType(index) indexContainerName := x.indexContainerName(index, 0) @@ -328,10 +314,7 @@ func (x *Generator) genCppIndexFinders() { x.g.P("}") x.g.P() - for i := 1; i <= levelMessage.MapDepth-2; i++ { - if i > len(x.keys) { - break - } + for i := 1; i < lm.MapDepth; 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 78caca55..3142c875 100644 --- a/cmd/protoc-gen-cpp-tableau-loader/indexes/ordered_index.go +++ b/cmd/protoc-gen-cpp-tableau-loader/indexes/ordered_index.go @@ -58,8 +58,8 @@ func (x *Generator) genHppOrderedIndexFinders() { return } var once sync.Once - for levelMessage := x.descriptor.LevelMessage; levelMessage != nil; levelMessage = levelMessage.NextLevel { - for _, index := range levelMessage.OrderedIndexes { + for lm := x.descriptor.LevelMessage; lm != nil; lm = lm.NextLevel { + for _, index := range lm.OrderedIndexes { x.g.P() once.Do(func() { x.g.P(helper.Indent(1), "// OrderedIndex accessers.") }) x.g.P(helper.Indent(1), "// OrderedIndex: ", index.Index) @@ -94,10 +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 <= levelMessage.MapDepth-2; i++ { - if i > len(x.keys) { - break - } + for i := 1; i < lm.MapDepth; 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(), ").") @@ -112,14 +109,11 @@ func (x *Generator) genHppOrderedIndexFinders() { x.g.P(" private:") x.g.P(helper.Indent(1), mapType, " ", x.orderedIndexContainerName(index, 0), ";") - for i := 1; i <= levelMessage.MapDepth-2; i++ { - if i > len(x.keys) { - break - } + for i := 1; i < lm.MapDepth; i++ { if i == 1 { x.g.P(helper.Indent(1), "std::unordered_map<", x.keys[0].Type, ", ", mapType, "> ", x.orderedIndexContainerName(index, i), ";") } else { - levelIndexKeyType := x.levelKeyType(x.mapFds[i-1]) + levelIndexKeyType := x.levelKeyType(x.keys[i-1].Fd) x.g.P(helper.Indent(1), "std::unordered_map<", levelIndexKeyType, ", ", mapType, ", ", levelIndexKeyType, "Hasher> ", x.orderedIndexContainerName(index, i), ";") } } @@ -133,40 +127,37 @@ func (x *Generator) genOrderedIndexLoader() { } defer x.genOrderedIndexSorter() x.g.P(helper.Indent(1), "// OrderedIndex init.") - for levelMessage := x.descriptor.LevelMessage; levelMessage != nil; levelMessage = levelMessage.NextLevel { - for _, index := range levelMessage.OrderedIndexes { + 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 <= levelMessage.MapDepth-2; i++ { - if i > len(x.keys) { - break - } + for i := 1; i < lm.MapDepth; i++ { x.g.P(helper.Indent(1), x.orderedIndexContainerName(index, i), ".clear();") } } } parentDataName := "data_" - for levelMessage := x.descriptor.LevelMessage; levelMessage != nil; levelMessage = levelMessage.NextLevel { - for _, index := range levelMessage.OrderedIndexes { - x.genOneCppOrderedIndexLoader(levelMessage.MapDepth, levelMessage.Depth, index, parentDataName) - } - itemName := fmt.Sprintf("item%d", levelMessage.Depth) - if levelMessage.FD == nil { - break - } - if !levelMessage.NextLevel.NeedGenOrderedIndex() { + for lm := x.descriptor.LevelMessage; lm != nil; lm = lm.NextLevel { + itemName := fmt.Sprintf("item%d", lm.Depth) + if !lm.NeedGenOrderedIndex() { break } - x.g.P(helper.Indent(levelMessage.Depth), "for (auto&& ", itemName, " : ", parentDataName, x.fieldGetter(levelMessage.FD), ") {") + x.g.P(helper.Indent(lm.Depth), "for (auto&& ", itemName, " : ", parentDataName, x.fieldGetter(lm.FD), ") {") parentDataName = itemName - if levelMessage.FD.IsMap() { - x.g.P(helper.Indent(levelMessage.Depth+1), "auto k", levelMessage.MapDepth, " = ", itemName, ".first;") + if lm.FD.IsMap() { + if lm.NeedMapKeyForOrderedIndex() { + x.g.P(helper.Indent(lm.Depth+1), "auto k", lm.MapDepth, " = ", itemName, ".first;") + } parentDataName = itemName + ".second" } - defer x.g.P(helper.Indent(levelMessage.Depth), "}") + defer x.g.P(helper.Indent(lm.Depth), "}") + for _, index := range lm.OrderedIndexes { + x.genOneCppOrderedIndexLoader(lm, index, parentDataName) + } } } -func (x *Generator) genOneCppOrderedIndexLoader(depth int, ident int, index *index.LevelIndex, parentDataName string) { +func (x *Generator) genOneCppOrderedIndexLoader(lm *index.LevelMessage, index *index.LevelIndex, parentDataName string) { + ident := lm.Depth + 1 x.g.P(helper.Indent(ident), "{") x.g.P(helper.Indent(ident+1), "// OrderedIndex: ", index.Index) if len(index.ColFields) == 1 { @@ -174,31 +165,31 @@ func (x *Generator) genOneCppOrderedIndexLoader(depth int, ident int, index *ind field := index.ColFields[0] // just take the first field fieldName, suffix := x.parseKeyFieldNameAndSuffix(field) if field.FD.IsList() { - itemName := fmt.Sprintf("item%d", depth) + itemName := fmt.Sprintf("item%d", lm.MapDepth+1) x.g.P(helper.Indent(ident+1), "for (auto&& ", itemName, " : ", parentDataName, fieldName, ") {") key := itemName + suffix if field.FD.Enum() != nil { key = "static_cast<" + helper.ParseCppType(field.FD) + ">(" + key + ")" } - x.genOrderedLoader(depth, ident+2, index, key, parentDataName) + x.genOrderedLoader(lm, index, ident+2, key, parentDataName) x.g.P(helper.Indent(ident+1), "}") } else { key := parentDataName + fieldName + suffix - x.genOrderedLoader(depth, ident+1, index, key, parentDataName) + x.genOrderedLoader(lm, index, ident+1, key, parentDataName) } } else { // multi-column index - x.generateOneCppMulticolumnOrderedIndex(depth, ident, index, parentDataName, nil) + x.generateOneCppMulticolumnOrderedIndex(lm, index, ident, parentDataName, nil) } x.g.P(helper.Indent(ident), "}") } -func (x *Generator) generateOneCppMulticolumnOrderedIndex(depth, ident int, index *index.LevelIndex, parentDataName string, keys helper.MapKeySlice) { +func (x *Generator) generateOneCppMulticolumnOrderedIndex(lm *index.LevelMessage, index *index.LevelIndex, ident int, parentDataName string, keys helper.MapKeySlice) { cursor := len(keys) if cursor >= len(index.ColFields) { keyType := x.orderedIndexMapKeyType(index) x.g.P(helper.Indent(ident+1), keyType, " key{", keys.GenGetArguments(), "};") - x.genOrderedLoader(depth, ident+1, index, "key", parentDataName) + x.genOrderedLoader(lm, index, ident+1, "key", parentDataName) return } field := index.ColFields[cursor] @@ -211,21 +202,18 @@ func (x *Generator) generateOneCppMulticolumnOrderedIndex(depth, ident int, inde key = "static_cast<" + helper.ParseCppType(field.FD) + ">(" + key + ")" } keys = keys.AddMapKey(helper.MapKey{Name: key}) - x.generateOneCppMulticolumnOrderedIndex(depth, ident+1, index, parentDataName, keys) + x.generateOneCppMulticolumnOrderedIndex(lm, index, ident+1, parentDataName, keys) x.g.P(helper.Indent(ident+1), "}") } else { key := parentDataName + fieldName + suffix keys = keys.AddMapKey(helper.MapKey{Name: key}) - x.generateOneCppMulticolumnOrderedIndex(depth, ident, index, parentDataName, keys) + x.generateOneCppMulticolumnOrderedIndex(lm, index, ident, parentDataName, keys) } } -func (x *Generator) genOrderedLoader(depth, ident int, index *index.LevelIndex, key, parentDataName string) { +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 <= depth-2; i++ { - if i > len(x.keys) { - break - } + for i := 1; i < lm.MapDepth; i++ { if i == 1 { x.g.P(helper.Indent(ident), x.orderedIndexContainerName(index, i), "[k1][", key, "].push_back(&", parentDataName, ");") } else { @@ -239,8 +227,8 @@ func (x *Generator) genOrderedLoader(depth, ident int, index *index.LevelIndex, } func (x *Generator) genOrderedIndexSorter() { - for levelMessage := x.descriptor.LevelMessage; levelMessage != nil; levelMessage = levelMessage.NextLevel { - for _, index := range levelMessage.OrderedIndexes { + for lm := x.descriptor.LevelMessage; lm != nil; lm = lm.NextLevel { + for _, index := range lm.OrderedIndexes { if len(index.SortedColFields) != 0 { valueType := x.mapValueType(index) x.g.P(helper.Indent(1), "// OrderedIndex(sort): ", index.Index) @@ -263,10 +251,8 @@ func (x *Generator) genOrderedIndexSorter() { x.g.P(helper.Indent(1), "for (auto&& item : ", indexContainerName, ") {") x.g.P(helper.Indent(2), "std::sort(item.second.begin(), item.second.end(), ", indexContainerName, "sorter);") x.g.P(helper.Indent(1), "}") - for i := 1; i <= levelMessage.MapDepth-2; i++ { - if i > len(x.keys) { - break - } + // Iterate all leveled containers. + for i := 1; i < lm.MapDepth; 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);") @@ -282,8 +268,8 @@ func (x *Generator) genCppOrderedIndexFinders() { if !x.needGenerateOrderedIndex() { return } - for levelMessage := x.descriptor.LevelMessage; levelMessage != nil; levelMessage = levelMessage.NextLevel { - for _, index := range levelMessage.OrderedIndexes { + for lm := x.descriptor.LevelMessage; lm != nil; lm = lm.NextLevel { + for _, index := range lm.OrderedIndexes { vectorType := x.orderedIndexMapValueVectorType(index) mapType := x.orderedIndexMapType(index) indexContainerName := x.orderedIndexContainerName(index, 0) @@ -318,10 +304,7 @@ func (x *Generator) genCppOrderedIndexFinders() { x.g.P("}") x.g.P() - for i := 1; i <= levelMessage.MapDepth-2; i++ { - if i > len(x.keys) { - break - } + for i := 1; i < lm.MapDepth; i++ { indexContainerNameI := 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 79fc79ad..99ebcd69 100644 --- a/cmd/protoc-gen-go-tableau-loader/helper/helper.go +++ b/cmd/protoc-gen-go-tableau-loader/helper/helper.go @@ -261,7 +261,8 @@ func ParseLeveledMapPrefix(md protoreflect.MessageDescriptor, mapFd protoreflect type MapKey struct { Type string Name string - FieldName string // multi-colunm index only + FieldName string // multi-colunm index only + Fd protoreflect.FieldDescriptor // the map field descriptor this key belongs to } type MapKeySlice []MapKey diff --git a/cmd/protoc-gen-go-tableau-loader/indexes/generator.go b/cmd/protoc-gen-go-tableau-loader/indexes/generator.go index 6ce60493..a09671ea 100644 --- a/cmd/protoc-gen-go-tableau-loader/indexes/generator.go +++ b/cmd/protoc-gen-go-tableau-loader/indexes/generator.go @@ -16,9 +16,7 @@ type Generator struct { message *protogen.Message // level message - maxDepth int - keys helper.MapKeySlice - mapFds []protoreflect.FieldDescriptor + keys helper.MapKeySlice } func NewGenerator(gen *protogen.Plugin, g *protogen.GeneratedFile, descriptor *index.IndexDescriptor, message *protogen.Message) *Generator { @@ -33,17 +31,19 @@ func NewGenerator(gen *protogen.Plugin, g *protogen.GeneratedFile, descriptor *i } func (x *Generator) initLevelMessage() { - for levelMessage := x.descriptor.LevelMessage; levelMessage != nil; levelMessage = levelMessage.NextLevel { - if fd := levelMessage.FD; fd != nil && fd.IsMap() { + for lm := x.descriptor.LevelMessage; lm != nil; lm = lm.NextLevel { + if fd := lm.FD; fd != nil && fd.IsMap() { + // Only collect map keys/fds when a deeper level has an index or ordered index, + // because these keys are used solely for building upper-level (leveled) containers. + if !lm.NextLevel.NeedGenAnyIndex() { + break + } x.keys = x.keys.AddMapKey(helper.MapKey{ Type: helper.ParseMapKeyType(fd.MapKey()), Name: helper.ParseMapFieldNameAsFuncParam(fd), FieldName: helper.ParseMapFieldNameAsKeyStructFieldName(fd), + Fd: fd, }) - x.mapFds = append(x.mapFds, fd) - } - if len(levelMessage.Indexes) != 0 || len(levelMessage.OrderedIndexes) != 0 { - x.maxDepth = levelMessage.MapDepth } } } @@ -87,14 +87,30 @@ func (x *Generator) GenIndexTypeDef() { if !x.NeedGenerate() { return } - for i := 1; i <= x.maxDepth-3 && i <= len(x.mapFds)-1; i++ { - if i == 1 { + // Generate LevelIndex key structs for intermediate map levels. + // + // x.keys holds one entry per map level whose next level still needs an + // index (populated by initLevelMessage). For a 3-level map keyed by + // (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. 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} + // keys = [k1, k2, k3, k4] → struct for depth 2: {k1, k2} + // struct for depth 3: {k1, k2, k3} + // + // 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++ { + if i == 2 { x.g.P() x.g.P("// LevelIndex keys.") } - fd := x.mapFds[i] + fd := x.keys[i-1].Fd keyType := x.levelKeyType(fd) - keys := x.keys[:i+1] + keys := x.keys[:i] x.g.P("type ", keyType, " struct {") for _, key := range keys { x.g.P(key.FieldName, " ", key.Type) diff --git a/cmd/protoc-gen-go-tableau-loader/indexes/index.go b/cmd/protoc-gen-go-tableau-loader/indexes/index.go index c464ff60..56420e5a 100644 --- a/cmd/protoc-gen-go-tableau-loader/indexes/index.go +++ b/cmd/protoc-gen-go-tableau-loader/indexes/index.go @@ -54,8 +54,8 @@ func (x *Generator) genIndexTypeDef() { return } x.g.P("// Index types.") - for levelMessage := x.descriptor.LevelMessage; levelMessage != nil; levelMessage = levelMessage.NextLevel { - for _, index := range levelMessage.Indexes { + for lm := x.descriptor.LevelMessage; lm != nil; lm = lm.NextLevel { + for _, index := range lm.Indexes { x.g.P("// Index: ", index.Index) if len(index.ColFields) != 1 { // multi-column index @@ -80,17 +80,14 @@ func (x *Generator) genIndexField() { if !x.needGenerateIndex() { return } - for levelMessage := x.descriptor.LevelMessage; levelMessage != nil; levelMessage = levelMessage.NextLevel { - for _, index := range levelMessage.Indexes { + 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 <= levelMessage.MapDepth-2; i++ { - if i > len(x.keys) { - break - } + for i := 1; i < lm.MapDepth; i++ { if i == 1 { x.g.P(x.indexContainerName(index, i), " map[", x.keys[0].Type, "]", x.indexMapType(index)) } else { - levelIndexKeyType := x.levelKeyType(x.mapFds[i-1]) + levelIndexKeyType := x.levelKeyType(x.keys[i-1].Fd) x.g.P(x.indexContainerName(index, i), " map[", levelIndexKeyType, "]", x.indexMapType(index)) } } @@ -104,47 +101,44 @@ func (x *Generator) genIndexLoader() { } defer x.genIndexSorter() x.g.P("// Index init.") - for levelMessage := x.descriptor.LevelMessage; levelMessage != nil; levelMessage = levelMessage.NextLevel { - for _, index := range levelMessage.Indexes { + 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 <= levelMessage.MapDepth-2; i++ { - if i > len(x.keys) { - break - } + for i := 1; i < lm.MapDepth; i++ { if i == 1 { x.g.P("x.", x.indexContainerName(index, i), " = make(map[", x.keys[0].Type, "]", x.indexMapType(index), ")") } else { - levelIndexKeyType := x.levelKeyType(x.mapFds[i-1]) + levelIndexKeyType := x.levelKeyType(x.keys[i-1].Fd) x.g.P("x.", x.indexContainerName(index, i), " = make(map[", levelIndexKeyType, "]", x.indexMapType(index), ")") } } } } parentDataName := "x.data" - for levelMessage := x.descriptor.LevelMessage; levelMessage != nil; levelMessage = levelMessage.NextLevel { - for _, index := range levelMessage.Indexes { - x.genOneIndexLoader(index, levelMessage.MapDepth, parentDataName) - } - keyName := fmt.Sprintf("k%d", levelMessage.MapDepth) - valueName := fmt.Sprintf("v%d", levelMessage.Depth) - if levelMessage.FD == nil { + for lm := x.descriptor.LevelMessage; lm != nil; lm = lm.NextLevel { + keyName := fmt.Sprintf("k%d", lm.MapDepth) + valueName := fmt.Sprintf("v%d", lm.Depth) + if !lm.NeedGenIndex() { break } - if !levelMessage.NextLevel.NeedGenIndex() { - break - } - if levelMessage.FD.IsMap() { - x.g.P("for ", keyName, ", ", valueName, " := range ", parentDataName, x.fieldGetter(levelMessage.FD), " {") - x.g.P("_ = ", keyName) + if lm.FD.IsMap() { + if lm.NeedMapKeyForIndex() { + x.g.P("for ", keyName, ", ", valueName, " := range ", parentDataName, x.fieldGetter(lm.FD), " {") + } else { + x.g.P("for _, ", valueName, " := range ", parentDataName, x.fieldGetter(lm.FD), " {") + } } else { - x.g.P("for _ , ", valueName, " := range ", parentDataName, x.fieldGetter(levelMessage.FD), " {") + x.g.P("for _ , ", valueName, " := range ", parentDataName, x.fieldGetter(lm.FD), " {") } parentDataName = valueName defer x.g.P("}") + for _, index := range lm.Indexes { + x.genOneIndexLoader(lm, index, parentDataName) + } } } -func (x *Generator) genOneIndexLoader(index *index.LevelIndex, depth int, parentDataName string) { +func (x *Generator) genOneIndexLoader(lm *index.LevelMessage, index *index.LevelIndex, parentDataName string) { x.g.P("{") x.g.P("// Index: ", index.Index) if len(index.ColFields) == 1 { @@ -152,28 +146,28 @@ func (x *Generator) genOneIndexLoader(index *index.LevelIndex, depth int, parent field := index.ColFields[0] // just take the first field fieldName, _ := x.parseKeyFieldNameAndSuffix(field) if field.FD.IsList() { - valueName := fmt.Sprintf("v%d", depth) + valueName := fmt.Sprintf("v%d", lm.MapDepth+1) x.g.P("for _ , ", valueName, " := range ", parentDataName, fieldName, " {") x.g.P("key := ", valueName) - x.genIndexLoaderCommon(depth, index, parentDataName) + x.genIndexLoaderCommon(lm, index, parentDataName) x.g.P("}") } else { x.g.P("key := ", parentDataName, fieldName) - x.genIndexLoaderCommon(depth, index, parentDataName) + x.genIndexLoaderCommon(lm, index, parentDataName) } } else { // multi-column index - x.generateOneMulticolumnIndex(depth, index, parentDataName, nil) + x.generateOneMulticolumnIndex(lm, index, parentDataName, nil) } x.g.P("}") } -func (x *Generator) generateOneMulticolumnIndex(depth int, index *index.LevelIndex, parentDataName string, keys helper.MapKeySlice) { +func (x *Generator) generateOneMulticolumnIndex(lm *index.LevelMessage, index *index.LevelIndex, parentDataName string, keys helper.MapKeySlice) { cursor := len(keys) if cursor >= len(index.ColFields) { keyType := x.indexMapKeyType(index) x.g.P("key := ", keyType, " {", keys.GenGetArguments(), "}") - x.genIndexLoaderCommon(depth, index, parentDataName) + x.genIndexLoaderCommon(lm, index, parentDataName) return } field := index.ColFields[cursor] @@ -182,22 +176,19 @@ func (x *Generator) generateOneMulticolumnIndex(depth int, index *index.LevelInd itemName := fmt.Sprintf("indexItem%d", cursor) x.g.P("for _, ", itemName, " := range ", parentDataName, fieldName, " {") keys = keys.AddMapKey(helper.MapKey{Name: itemName}) - x.generateOneMulticolumnIndex(depth, index, parentDataName, keys) + x.generateOneMulticolumnIndex(lm, index, parentDataName, keys) x.g.P("}") } else { key := parentDataName + fieldName keys = keys.AddMapKey(helper.MapKey{Name: key}) - x.generateOneMulticolumnIndex(depth, index, parentDataName, keys) + x.generateOneMulticolumnIndex(lm, index, parentDataName, keys) } } -func (x *Generator) genIndexLoaderCommon(depth int, index *index.LevelIndex, parentDataName string) { +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 <= depth-2; i++ { - if i > len(x.keys) { - break - } + for i := 1; i < lm.MapDepth; i++ { indexContainerName := x.indexContainerName(index, i) if i == 1 { x.g.P("if x.", indexContainerName, "[k1] == nil {") @@ -209,7 +200,7 @@ func (x *Generator) genIndexLoaderCommon(depth int, index *index.LevelIndex, par for j := 1; j <= i; j++ { fields = append(fields, fmt.Sprintf("k%d", j)) } - levelIndexKeyType := x.levelKeyType(x.mapFds[i-1]) + levelIndexKeyType := x.levelKeyType(x.keys[i-1].Fd) keyName := indexContainerName + "Keys" x.g.P(keyName, " := ", levelIndexKeyType, "{", strings.Join(fields, ", "), "}") x.g.P("if x.", indexContainerName, "[", keyName, "] == nil {") @@ -221,8 +212,8 @@ func (x *Generator) genIndexLoaderCommon(depth int, index *index.LevelIndex, par } func (x *Generator) genIndexSorter() { - for levelMessage := x.descriptor.LevelMessage; levelMessage != nil; levelMessage = levelMessage.NextLevel { - for _, index := range levelMessage.Indexes { + for lm := x.descriptor.LevelMessage; lm != nil; lm = lm.NextLevel { + for _, index := range lm.Indexes { if len(index.SortedColFields) != 0 { x.g.P("// Index(sort): ", index.Index) indexContainerName := x.indexContainerName(index, 0) @@ -243,10 +234,8 @@ func (x *Generator) genIndexSorter() { x.g.P("for _, itemList := range x.", x.indexContainerName(index, 0), " {") x.g.P(helper.SortPackage.Ident("Slice"), "(itemList, ", indexContainerName, "Sorter(itemList))") x.g.P("}") - for i := 1; i <= levelMessage.MapDepth-2; i++ { - if i > len(x.keys) { - break - } + // Iterate all leveled containers. + for i := 1; i < lm.MapDepth; 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))") @@ -262,8 +251,8 @@ func (x *Generator) genIndexFinders() { if !x.needGenerateIndex() { return } - for levelMessage := x.descriptor.LevelMessage; levelMessage != nil; levelMessage = levelMessage.NextLevel { - for _, index := range levelMessage.Indexes { + for lm := x.descriptor.LevelMessage; lm != nil; lm = lm.NextLevel { + for _, index := range lm.Indexes { indexContainerName := x.indexContainerName(index, 0) messagerName := x.messagerName() x.g.P("// Index: ", index.Index) @@ -300,10 +289,7 @@ func (x *Generator) genIndexFinders() { x.g.P("}") x.g.P() - for i := 1; i <= levelMessage.MapDepth-2; i++ { - if i > len(x.keys) { - break - } + for i := 1; i < lm.MapDepth; i++ { indexContainerName := x.indexContainerName(index, i) partKeys := x.keys[:i] partParams := partKeys.GenGetParams() @@ -316,7 +302,7 @@ func (x *Generator) genIndexFinders() { if len(partKeys) == 1 { x.g.P("return x.", indexContainerName, "[", partArgs, "]") } else { - levelIndexKeyType := x.levelKeyType(x.mapFds[i-1]) + levelIndexKeyType := x.levelKeyType(x.keys[i-1].Fd) x.g.P("return x.", indexContainerName, "[", levelIndexKeyType, "{", partArgs, "}]") } x.g.P("}") diff --git a/cmd/protoc-gen-go-tableau-loader/indexes/orderedindex.go b/cmd/protoc-gen-go-tableau-loader/indexes/ordered_index.go similarity index 80% rename from cmd/protoc-gen-go-tableau-loader/indexes/orderedindex.go rename to cmd/protoc-gen-go-tableau-loader/indexes/ordered_index.go index 5ab40268..49e765e8 100644 --- a/cmd/protoc-gen-go-tableau-loader/indexes/orderedindex.go +++ b/cmd/protoc-gen-go-tableau-loader/indexes/ordered_index.go @@ -64,8 +64,8 @@ func (x *Generator) genOrderedIndexTypeDef() { return } x.g.P("// OrderedIndex types.") - for levelMessage := x.descriptor.LevelMessage; levelMessage != nil; levelMessage = levelMessage.NextLevel { - for _, index := range levelMessage.OrderedIndexes { + for lm := x.descriptor.LevelMessage; lm != nil; lm = lm.NextLevel { + for _, index := range lm.OrderedIndexes { x.g.P("// OrderedIndex: ", index.Index) if len(index.ColFields) != 1 { // multi-column index @@ -104,17 +104,14 @@ func (x *Generator) genOrderedIndexField() { if !x.needGenerateOrderedIndex() { return } - for levelMessage := x.descriptor.LevelMessage; levelMessage != nil; levelMessage = levelMessage.NextLevel { - for _, index := range levelMessage.OrderedIndexes { + 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 <= levelMessage.MapDepth-2; i++ { - if i > len(x.keys) { - break - } + for i := 1; i < lm.MapDepth; i++ { if i == 1 { x.g.P(x.orderedIndexContainerName(index, i), " map[", x.keys[0].Type, "]*", x.orderedIndexMapType(index)) } else { - levelIndexKeyType := x.levelKeyType(x.mapFds[i-1]) + levelIndexKeyType := x.levelKeyType(x.keys[i-1].Fd) x.g.P(x.orderedIndexContainerName(index, i), " map[", levelIndexKeyType, "]*", x.orderedIndexMapType(index)) } } @@ -128,47 +125,44 @@ func (x *Generator) genOrderedIndexLoader() { } defer x.genOrderedIndexSorter() x.g.P("// OrderedIndex init.") - for levelMessage := x.descriptor.LevelMessage; levelMessage != nil; levelMessage = levelMessage.NextLevel { - for _, index := range levelMessage.OrderedIndexes { + 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 <= levelMessage.MapDepth-2; i++ { - if i > len(x.keys) { - break - } + for i := 1; i < lm.MapDepth; i++ { if i == 1 { x.g.P("x.", x.orderedIndexContainerName(index, i), " = make(map[", x.keys[0].Type, "]*", x.orderedIndexMapType(index), ")") } else { - levelIndexKeyType := x.levelKeyType(x.mapFds[i-1]) + levelIndexKeyType := x.levelKeyType(x.keys[i-1].Fd) x.g.P("x.", x.orderedIndexContainerName(index, i), " = make(map[", levelIndexKeyType, "]*", x.orderedIndexMapType(index), ")") } } } } parentDataName := "x.data" - for levelMessage := x.descriptor.LevelMessage; levelMessage != nil; levelMessage = levelMessage.NextLevel { - for _, index := range levelMessage.OrderedIndexes { - x.genOneOrderedIndexLoader(index, levelMessage.MapDepth, parentDataName) - } - keyName := fmt.Sprintf("k%d", levelMessage.MapDepth) - valueName := fmt.Sprintf("v%d", levelMessage.Depth) - if levelMessage.FD == nil { + for lm := x.descriptor.LevelMessage; lm != nil; lm = lm.NextLevel { + keyName := fmt.Sprintf("k%d", lm.MapDepth) + valueName := fmt.Sprintf("v%d", lm.Depth) + if !lm.NeedGenOrderedIndex() { break } - if !levelMessage.NextLevel.NeedGenOrderedIndex() { - break - } - if levelMessage.FD.IsMap() { - x.g.P("for ", keyName, ", ", valueName, " := range ", parentDataName, x.fieldGetter(levelMessage.FD), " {") - x.g.P("_ = ", keyName) + if lm.FD.IsMap() { + if lm.NeedMapKeyForOrderedIndex() { + x.g.P("for ", keyName, ", ", valueName, " := range ", parentDataName, x.fieldGetter(lm.FD), " {") + } else { + x.g.P("for _, ", valueName, " := range ", parentDataName, x.fieldGetter(lm.FD), " {") + } } else { - x.g.P("for _ , ", valueName, " := range ", parentDataName, x.fieldGetter(levelMessage.FD), " {") + x.g.P("for _ , ", valueName, " := range ", parentDataName, x.fieldGetter(lm.FD), " {") } parentDataName = valueName defer x.g.P("}") + for _, index := range lm.OrderedIndexes { + x.genOneOrderedIndexLoader(lm, index, parentDataName) + } } } -func (x *Generator) genOneOrderedIndexLoader(index *index.LevelIndex, depth int, parentDataName string) { +func (x *Generator) genOneOrderedIndexLoader(lm *index.LevelMessage, index *index.LevelIndex, parentDataName string) { x.g.P("{") x.g.P("// OrderedIndex: ", index.Index) if len(index.ColFields) == 1 { @@ -176,28 +170,28 @@ func (x *Generator) genOneOrderedIndexLoader(index *index.LevelIndex, depth int, field := index.ColFields[0] // just take the first field fieldName, suffix := x.parseKeyFieldNameAndSuffix(field) if field.FD.IsList() { - valueName := fmt.Sprintf("v%d", depth) + valueName := fmt.Sprintf("v%d", lm.MapDepth+1) x.g.P("for _ , ", valueName, " := range ", parentDataName, fieldName, " {") x.g.P("key := ", valueName, suffix) - x.genOrderedIndexLoaderCommon(depth, index, parentDataName) + x.genOrderedIndexLoaderCommon(lm, index, parentDataName) x.g.P("}") } else { x.g.P("key := ", parentDataName, fieldName, suffix) - x.genOrderedIndexLoaderCommon(depth, index, parentDataName) + x.genOrderedIndexLoaderCommon(lm, index, parentDataName) } } else { // multi-column index - x.generateOneMulticolumnOrderedIndex(depth, index, parentDataName, nil) + x.generateOneMulticolumnOrderedIndex(lm, index, parentDataName, nil) } x.g.P("}") } -func (x *Generator) generateOneMulticolumnOrderedIndex(depth int, index *index.LevelIndex, parentDataName string, keys helper.MapKeySlice) { +func (x *Generator) generateOneMulticolumnOrderedIndex(lm *index.LevelMessage, index *index.LevelIndex, parentDataName string, keys helper.MapKeySlice) { cursor := len(keys) if cursor >= len(index.ColFields) { keyType := x.orderedIndexMapKeyType(index) x.g.P("key := ", keyType, " {", keys.GenGetArguments(), "}") - x.genOrderedIndexLoaderCommon(depth, index, parentDataName) + x.genOrderedIndexLoaderCommon(lm, index, parentDataName) return } field := index.ColFields[cursor] @@ -207,23 +201,20 @@ func (x *Generator) generateOneMulticolumnOrderedIndex(depth int, index *index.L x.g.P("for _, ", itemName, " := range ", parentDataName, fieldName, " {") key := itemName + suffix keys = keys.AddMapKey(helper.MapKey{Name: key}) - x.generateOneMulticolumnOrderedIndex(depth, index, parentDataName, keys) + x.generateOneMulticolumnOrderedIndex(lm, index, parentDataName, keys) x.g.P("}") } else { key := parentDataName + fieldName + suffix keys = keys.AddMapKey(helper.MapKey{Name: key}) - x.generateOneMulticolumnOrderedIndex(depth, index, parentDataName, keys) + x.generateOneMulticolumnOrderedIndex(lm, index, parentDataName, keys) } } -func (x *Generator) genOrderedIndexLoaderCommon(depth int, index *index.LevelIndex, parentDataName string) { +func (x *Generator) genOrderedIndexLoaderCommon(lm *index.LevelMessage, index *index.LevelIndex, parentDataName string) { 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 <= depth-2; i++ { - if i > len(x.keys) { - break - } + for i := 1; i < lm.MapDepth; i++ { orderedIndexContainerName := x.orderedIndexContainerName(index, i) valueName := orderedIndexContainerName + "Value" if i == 1 { @@ -237,7 +228,7 @@ func (x *Generator) genOrderedIndexLoaderCommon(depth int, index *index.LevelInd for j := 1; j <= i; j++ { fields = append(fields, fmt.Sprintf("k%d", j)) } - levelIndexKeyType := x.levelKeyType(x.mapFds[i-1]) + levelIndexKeyType := x.levelKeyType(x.keys[i-1].Fd) keyName := orderedIndexContainerName + "Keys" x.g.P(keyName, " := ", levelIndexKeyType, "{", strings.Join(fields, ", "), "}") x.g.P("if x.", orderedIndexContainerName, "[", keyName, "] == nil {") @@ -250,8 +241,8 @@ func (x *Generator) genOrderedIndexLoaderCommon(depth int, index *index.LevelInd } func (x *Generator) genOrderedIndexSorter() { - for levelMessage := x.descriptor.LevelMessage; levelMessage != nil; levelMessage = levelMessage.NextLevel { - for _, index := range levelMessage.OrderedIndexes { + for lm := x.descriptor.LevelMessage; lm != nil; lm = lm.NextLevel { + for _, index := range lm.OrderedIndexes { if len(index.SortedColFields) != 0 { x.g.P("// OrderedIndex(sort): ", index.Index) indexContainerName := x.orderedIndexContainerName(index, 0) @@ -273,10 +264,8 @@ func (x *Generator) genOrderedIndexSorter() { x.g.P(helper.SortPackage.Ident("Slice"), "(itemList, ", indexContainerName, "Sorter(itemList))") x.g.P("return true") x.g.P("})") - for i := 1; i <= levelMessage.MapDepth-2; i++ { - if i > len(x.keys) { - break - } + // Iterate all leveled containers. + for i := 1; i < lm.MapDepth; 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))") @@ -293,8 +282,8 @@ func (x *Generator) genOrderedIndexFinders() { if !x.needGenerateOrderedIndex() { return } - for levelMessage := x.descriptor.LevelMessage; levelMessage != nil; levelMessage = levelMessage.NextLevel { - for _, index := range levelMessage.OrderedIndexes { + for lm := x.descriptor.LevelMessage; lm != nil; lm = lm.NextLevel { + for _, index := range lm.OrderedIndexes { indexContainerName := x.orderedIndexContainerName(index, 0) messagerName := x.messagerName() x.g.P("// OrderedIndex: ", index.Index) @@ -332,10 +321,7 @@ func (x *Generator) genOrderedIndexFinders() { x.g.P("}") x.g.P() - for i := 1; i <= levelMessage.MapDepth-2; i++ { - if i > len(x.keys) { - break - } + for i := 1; i < lm.MapDepth; i++ { orderedIndexContainerName := x.orderedIndexContainerName(index, i) partKeys := x.keys[:i] partParams := partKeys.GenGetParams() @@ -348,7 +334,7 @@ func (x *Generator) genOrderedIndexFinders() { if len(partKeys) == 1 { x.g.P("return x.", orderedIndexContainerName, "[", partArgs, "]") } else { - levelIndexKeyType := x.levelKeyType(x.mapFds[i-1]) + levelIndexKeyType := x.levelKeyType(x.keys[i-1].Fd) x.g.P("return x.", orderedIndexContainerName, "[", levelIndexKeyType, "{", partArgs, "}]") } x.g.P("}") diff --git a/init.bat b/init.bat index d97e1ab8..6d8d421b 100644 --- a/init.bat +++ b/init.bat @@ -15,12 +15,14 @@ REM Build and install the C++ Protocol Buffer runtime and the Protocol Buffer co REM Refer: https://github.com/protocolbuffers/protobuf/blob/3.19.x/cmake/README.md#cmake-configuration cd cmake REM use Debug version -cmake -S . -B build ^ --G "NMake Makefiles" ^ --DCMAKE_BUILD_TYPE=Debug ^ --DCMAKE_POLICY_VERSION_MINIMUM="3.5" +REM - protobuf_MSVC_STATIC_RUNTIME defaults to ON, which uses static CRT (/MTd for Debug). +REM Our project's CMakeLists.txt also sets static CRT to match. +REM - protobuf_WITH_ZLIB=OFF: disable ZLIB dependency to avoid ZLIB::ZLIB link requirement +REM in protobuf's exported CMake targets, which simplifies cross-platform builds. +REM - protobuf_BUILD_SHARED_LIBS=OFF: build static libraries explicitly. +cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Debug -DCMAKE_POLICY_VERSION_MINIMUM=3.5 -Dprotobuf_BUILD_TESTS=OFF -Dprotobuf_WITH_ZLIB=OFF -Dprotobuf_BUILD_SHARED_LIBS=OFF REM Compile the code -cmake --build build +cmake --build build --parallel endlocal diff --git a/init.sh b/init.sh index 5e78d447..7b4ee9d1 100755 --- a/init.sh +++ b/init.sh @@ -22,8 +22,12 @@ git submodule update --init --recursive # Refer: https://github.com/protocolbuffers/protobuf/blob/3.19.x/cmake/README.md#cmake-configuration cd cmake # use Debug version -cmake -S . -B build \ - -DCMAKE_BUILD_TYPE=Debug +# - protobuf_MSVC_STATIC_RUNTIME defaults to ON, which uses static CRT (/MTd for Debug). +# Our project's CMakeLists.txt also sets static CRT to match. +# - protobuf_WITH_ZLIB=OFF: disable ZLIB dependency to avoid ZLIB::ZLIB link requirement +# in protobuf's exported CMake targets, which simplifies cross-platform builds. +# - protobuf_BUILD_SHARED_LIBS=OFF: build static libraries explicitly. +cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Debug -DCMAKE_POLICY_VERSION_MINIMUM=3.5 -Dprotobuf_BUILD_TESTS=OFF -Dprotobuf_WITH_ZLIB=OFF -Dprotobuf_BUILD_SHARED_LIBS=OFF # Compile the code -cmake --build build -j16 +cmake --build build --parallel diff --git a/internal/index/descriptor.go b/internal/index/descriptor.go index e68308ec..93202064 100644 --- a/internal/index/descriptor.go +++ b/internal/index/descriptor.go @@ -46,15 +46,20 @@ type LevelField struct { type LevelMessage struct { NextLevel *LevelMessage - // Current level message's field which contains index fields. - // Only valid when NextLevel is not nil. + // FD is the container field (map or list) through which this level is entered + // from its parent. It is nil for the top-level (root) LevelMessage. FD protoreflect.FieldDescriptor // Current level message's all index fields Indexes, OrderedIndexes []*LevelIndex - // depth of message hierarchy - Depth, MapDepth int + // 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 int } func (l *LevelMessage) NeedGenIndex() bool { @@ -71,6 +76,37 @@ func (l *LevelMessage) NeedGenOrderedIndex() bool { return len(l.OrderedIndexes) != 0 || l.NextLevel.NeedGenOrderedIndex() } +// NeedGenAnyIndex reports whether this level or any deeper level has +// at least one index (regular or ordered). +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. +func (l *LevelMessage) NeedMapKeyForIndex() bool { + for lm := l.NextLevel; lm != nil; lm = lm.NextLevel { + if lm.MapDepth > 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. +func (l *LevelMessage) NeedMapKeyForOrderedIndex() bool { + for lm := l.NextLevel; lm != nil; lm = lm.NextLevel { + if lm.MapDepth > l.MapDepth { + return lm.NeedGenOrderedIndex() + } + } + return false +} + type LevelIndex struct { *Index MD protoreflect.MessageDescriptor @@ -87,20 +123,19 @@ func (l *LevelIndex) Name() string { return name } -func parseLevelMessage(md protoreflect.MessageDescriptor, depth, mapDepth int) *LevelMessage { +func parseLevelMessage(fd protoreflect.FieldDescriptor, md protoreflect.MessageDescriptor, depth, mapDepth int) *LevelMessage { levelMsg := &LevelMessage{ + FD: fd, Depth: depth, MapDepth: mapDepth, } for i := 0; i < md.Fields().Len(); i++ { - fd := md.Fields().Get(i) - if fd.IsMap() && fd.MapValue().Kind() == protoreflect.MessageKind { - levelMsg.NextLevel = parseLevelMessage(fd.MapValue().Message(), depth+1, mapDepth+1) - levelMsg.FD = fd + childFD := md.Fields().Get(i) + if childFD.IsMap() && childFD.MapValue().Kind() == protoreflect.MessageKind { + levelMsg.NextLevel = parseLevelMessage(childFD, childFD.MapValue().Message(), depth+1, mapDepth+1) return levelMsg - } else if fd.IsList() && fd.Kind() == protoreflect.MessageKind { - levelMsg.NextLevel = parseLevelMessage(fd.Message(), depth+1, mapDepth) - levelMsg.FD = fd + } else if childFD.IsList() && childFD.Kind() == protoreflect.MessageKind { + levelMsg.NextLevel = parseLevelMessage(childFD, childFD.Message(), depth+1, mapDepth) return levelMsg } } @@ -108,7 +143,7 @@ func parseLevelMessage(md protoreflect.MessageDescriptor, depth, mapDepth int) * } // parseRecursively parses multi-column index related info. -func parseRecursively(index *Index, prefix string, md protoreflect.MessageDescriptor, levelMessage *LevelMessage, ordered bool) { +func parseRecursively(index *Index, prefix string, md protoreflect.MessageDescriptor, lm *LevelMessage, ordered bool) { colFields := parseInSameLevel(index.Cols, prefix, md, nil) sortedColFields := parseInSameLevel(index.SortedCols, prefix, md, nil) if len(colFields) != 0 { @@ -120,20 +155,20 @@ func parseRecursively(index *Index, prefix string, md protoreflect.MessageDescri SortedColFields: sortedColFields, } if ordered { - levelMessage.OrderedIndexes = append(levelMessage.OrderedIndexes, levelIndex) + lm.OrderedIndexes = append(lm.OrderedIndexes, levelIndex) } else { - levelMessage.Indexes = append(levelMessage.Indexes, levelIndex) + lm.Indexes = append(lm.Indexes, levelIndex) } - } else if levelMessage != nil && levelMessage.NextLevel != nil { + } else if lm != nil && lm.NextLevel != nil { // index invalid or belongs to deeper level - fd := levelMessage.FD + fd := lm.NextLevel.FD opts := fd.Options().(*descriptorpb.FieldOptions) fdOpts := proto.GetExtension(opts, tableaupb.E_Field).(*tableaupb.FieldOptions) fieldOptName := fdOpts.GetName() if fd.IsMap() { - parseRecursively(index, prefix+fieldOptName, fd.MapValue().Message(), levelMessage.NextLevel, ordered) + parseRecursively(index, prefix+fieldOptName, fd.MapValue().Message(), lm.NextLevel, ordered) } else { - parseRecursively(index, prefix+fieldOptName, fd.Message(), levelMessage.NextLevel, ordered) + parseRecursively(index, prefix+fieldOptName, fd.Message(), lm.NextLevel, ordered) } } } @@ -181,27 +216,25 @@ func parseCols(cols []string, prefix string, md protoreflect.MessageDescriptor, } func ParseIndexDescriptor(md protoreflect.MessageDescriptor) *IndexDescriptor { - descriptor := &IndexDescriptor{ - LevelMessage: parseLevelMessage(md, 1, 1), - } + levelMessage := parseLevelMessage(nil, md, 0, 0) indexes, orderedIndexes := ParseWSOptionIndex(md) // parse indexes into level message for _, index := range indexes { if len(index.Cols) == 0 { continue } - parseRecursively(index, "", md, descriptor.LevelMessage, false) + parseRecursively(index, "", md, levelMessage, false) } for _, index := range orderedIndexes { if len(index.Cols) == 0 { continue } - parseRecursively(index, "", md, descriptor.LevelMessage, true) + parseRecursively(index, "", md, levelMessage, true) } // check duplicate index name indexNameMap := map[string]*Index{} - for levelMessage := descriptor.LevelMessage; levelMessage != nil; levelMessage = levelMessage.NextLevel { - allIndexes := append(levelMessage.Indexes, levelMessage.OrderedIndexes...) + for lm := levelMessage; lm != nil; lm = lm.NextLevel { + allIndexes := append(lm.Indexes, lm.OrderedIndexes...) for _, index := range allIndexes { name := index.Name() if existingIndex, ok := indexNameMap[name]; ok { @@ -211,5 +244,7 @@ func ParseIndexDescriptor(md protoreflect.MessageDescriptor) *IndexDescriptor { } } } - return descriptor + return &IndexDescriptor{ + LevelMessage: levelMessage.NextLevel, + } } diff --git a/internal/index/descriptor_test.go b/internal/index/descriptor_test.go index aa5e0e29..df7d4f54 100644 --- a/internal/index/descriptor_test.go +++ b/internal/index/descriptor_test.go @@ -34,263 +34,402 @@ func Test_ParseIndexDescriptor(t *testing.T) { }, want: &IndexDescriptor{ LevelMessage: &LevelMessage{ + FD: fd[*protoconf.ItemConf]("item_map"), Depth: 1, MapDepth: 1, - FD: fd[*protoconf.ItemConf]("item_map"), - NextLevel: &LevelMessage{ - Depth: 2, - MapDepth: 2, - Indexes: []*LevelIndex{ - { - Index: &Index{ - Cols: []string{"Type"}, - Name: "", + Indexes: []*LevelIndex{ + { + Index: &Index{ + Cols: []string{"Type"}, + Name: "", + }, + MD: md[*protoconf.ItemConf_Item](), + ColFields: []*LevelField{ + { + FD: fd[*protoconf.ItemConf_Item]("type"), + LeveledFDList: []protoreflect.FieldDescriptor{ + fd[*protoconf.ItemConf_Item]("type"), + }, }, - MD: md[*protoconf.ItemConf_Item](), - ColFields: []*LevelField{ - { - FD: fd[*protoconf.ItemConf_Item]("type"), - LeveledFDList: []protoreflect.FieldDescriptor{ - fd[*protoconf.ItemConf_Item]("type"), - }, + }, + }, + { + Index: &Index{ + Cols: []string{"Param"}, + SortedCols: []string{"ID"}, + Name: "ItemInfo", + }, + MD: md[*protoconf.ItemConf_Item](), + ColFields: []*LevelField{ + { + FD: fd[*protoconf.ItemConf_Item]("param_list"), + LeveledFDList: []protoreflect.FieldDescriptor{ + fd[*protoconf.ItemConf_Item]("param_list"), }, }, }, - { - Index: &Index{ - Cols: []string{"Param"}, - SortedCols: []string{"ID"}, - Name: "ItemInfo", + SortedColFields: []*LevelField{ + { + FD: fd[*protoconf.ItemConf_Item]("id"), + LeveledFDList: []protoreflect.FieldDescriptor{ + fd[*protoconf.ItemConf_Item]("id"), + }, }, - MD: md[*protoconf.ItemConf_Item](), - ColFields: []*LevelField{ - { - FD: fd[*protoconf.ItemConf_Item]("param_list"), - LeveledFDList: []protoreflect.FieldDescriptor{ - fd[*protoconf.ItemConf_Item]("param_list"), - }, + }, + }, + { + Index: &Index{ + Cols: []string{"Default"}, + Name: "ItemDefaultInfo", + }, + MD: md[*protoconf.ItemConf_Item](), + ColFields: []*LevelField{ + { + FD: fd[*protoconf.ItemConf_Item]("default"), + LeveledFDList: []protoreflect.FieldDescriptor{ + fd[*protoconf.ItemConf_Item]("default"), }, }, - SortedColFields: []*LevelField{ - { - FD: fd[*protoconf.ItemConf_Item]("id"), - LeveledFDList: []protoreflect.FieldDescriptor{ - fd[*protoconf.ItemConf_Item]("id"), - }, + }, + }, + { + Index: &Index{ + Cols: []string{"ExtType"}, + Name: "ItemExtInfo", + }, + MD: md[*protoconf.ItemConf_Item](), + ColFields: []*LevelField{ + { + FD: fd[*protoconf.ItemConf_Item]("ext_type_list"), + LeveledFDList: []protoreflect.FieldDescriptor{ + fd[*protoconf.ItemConf_Item]("ext_type_list"), }, }, }, - { - Index: &Index{ - Cols: []string{"Default"}, - Name: "ItemDefaultInfo", + }, + { + Index: &Index{ + Cols: []string{"ID", "Name"}, + SortedCols: []string{"Type", "UseEffectType"}, + Name: "AwardItem", + }, + MD: md[*protoconf.ItemConf_Item](), + ColFields: []*LevelField{ + { + FD: fd[*protoconf.ItemConf_Item]("id"), + LeveledFDList: []protoreflect.FieldDescriptor{ + fd[*protoconf.ItemConf_Item]("id"), + }, }, - MD: md[*protoconf.ItemConf_Item](), - ColFields: []*LevelField{ - { - FD: fd[*protoconf.ItemConf_Item]("default"), - LeveledFDList: []protoreflect.FieldDescriptor{ - fd[*protoconf.ItemConf_Item]("default"), - }, + { + FD: fd[*protoconf.ItemConf_Item]("name"), + LeveledFDList: []protoreflect.FieldDescriptor{ + fd[*protoconf.ItemConf_Item]("name"), }, }, }, - { - Index: &Index{ - Cols: []string{"ExtType"}, - Name: "ItemExtInfo", + SortedColFields: []*LevelField{ + { + FD: fd[*protoconf.ItemConf_Item]("type"), + LeveledFDList: []protoreflect.FieldDescriptor{ + fd[*protoconf.ItemConf_Item]("type"), + }, }, - MD: md[*protoconf.ItemConf_Item](), - ColFields: []*LevelField{ - { - FD: fd[*protoconf.ItemConf_Item]("ext_type_list"), - LeveledFDList: []protoreflect.FieldDescriptor{ - fd[*protoconf.ItemConf_Item]("ext_type_list"), - }, + { + FD: fd[*protoconf.UseEffect]("type"), + LeveledFDList: []protoreflect.FieldDescriptor{ + fd[*protoconf.ItemConf_Item]("use_effect"), + fd[*protoconf.UseEffect]("type"), }, }, }, - { - Index: &Index{ - Cols: []string{"ID", "Name"}, - SortedCols: []string{"Type", "UseEffectType"}, - Name: "AwardItem", - }, - MD: md[*protoconf.ItemConf_Item](), - ColFields: []*LevelField{ - { - FD: fd[*protoconf.ItemConf_Item]("id"), - LeveledFDList: []protoreflect.FieldDescriptor{ - fd[*protoconf.ItemConf_Item]("id"), - }, + }, + { + Index: &Index{ + Cols: []string{"ID", "Type", "Param", "ExtType"}, + Name: "SpecialItem", + }, + MD: md[*protoconf.ItemConf_Item](), + ColFields: []*LevelField{ + { + FD: fd[*protoconf.ItemConf_Item]("id"), + LeveledFDList: []protoreflect.FieldDescriptor{ + fd[*protoconf.ItemConf_Item]("id"), }, - { - FD: fd[*protoconf.ItemConf_Item]("name"), - LeveledFDList: []protoreflect.FieldDescriptor{ - fd[*protoconf.ItemConf_Item]("name"), - }, + }, + { + FD: fd[*protoconf.ItemConf_Item]("type"), + LeveledFDList: []protoreflect.FieldDescriptor{ + fd[*protoconf.ItemConf_Item]("type"), }, }, - SortedColFields: []*LevelField{ - { - FD: fd[*protoconf.ItemConf_Item]("type"), - LeveledFDList: []protoreflect.FieldDescriptor{ - fd[*protoconf.ItemConf_Item]("type"), - }, + { + FD: fd[*protoconf.ItemConf_Item]("param_list"), + LeveledFDList: []protoreflect.FieldDescriptor{ + fd[*protoconf.ItemConf_Item]("param_list"), }, - { - FD: fd[*protoconf.UseEffect]("type"), - LeveledFDList: []protoreflect.FieldDescriptor{ - fd[*protoconf.ItemConf_Item]("use_effect"), - fd[*protoconf.UseEffect]("type"), - }, + }, + { + FD: fd[*protoconf.ItemConf_Item]("ext_type_list"), + LeveledFDList: []protoreflect.FieldDescriptor{ + fd[*protoconf.ItemConf_Item]("ext_type_list"), }, }, }, - { - Index: &Index{ - Cols: []string{"ID", "Type", "Param", "ExtType"}, - Name: "SpecialItem", + }, + { + Index: &Index{ + Cols: []string{"PathDir"}, + Name: "ItemPathDir", + }, + MD: md[*protoconf.ItemConf_Item](), + ColFields: []*LevelField{ + { + FD: fd[*protoconf.Path]("dir"), + LeveledFDList: []protoreflect.FieldDescriptor{ + fd[*protoconf.ItemConf_Item]("path"), + fd[*protoconf.Path]("dir"), + }, }, - MD: md[*protoconf.ItemConf_Item](), - ColFields: []*LevelField{ - { - FD: fd[*protoconf.ItemConf_Item]("id"), - LeveledFDList: []protoreflect.FieldDescriptor{ - fd[*protoconf.ItemConf_Item]("id"), - }, + }, + }, + { + Index: &Index{ + Cols: []string{"PathName"}, + Name: "ItemPathName", + }, + MD: md[*protoconf.ItemConf_Item](), + ColFields: []*LevelField{ + { + FD: fd[*protoconf.Path]("name_list"), + LeveledFDList: []protoreflect.FieldDescriptor{ + fd[*protoconf.ItemConf_Item]("path"), + fd[*protoconf.Path]("name_list"), }, - { - FD: fd[*protoconf.ItemConf_Item]("type"), - LeveledFDList: []protoreflect.FieldDescriptor{ - fd[*protoconf.ItemConf_Item]("type"), - }, + }, + }, + }, + { + Index: &Index{ + Cols: []string{"PathFriendID"}, + Name: "ItemPathFriendID", + }, + MD: md[*protoconf.ItemConf_Item](), + ColFields: []*LevelField{ + { + FD: fd[*protoconf.Path_Friend]("id"), + LeveledFDList: []protoreflect.FieldDescriptor{ + fd[*protoconf.ItemConf_Item]("path"), + fd[*protoconf.Path]("friend"), + fd[*protoconf.Path_Friend]("id"), }, - { - FD: fd[*protoconf.ItemConf_Item]("param_list"), - LeveledFDList: []protoreflect.FieldDescriptor{ - fd[*protoconf.ItemConf_Item]("param_list"), - }, + }, + }, + }, + { + Index: &Index{ + Cols: []string{"UseEffectType"}, + Name: "UseEffectType", + }, + MD: md[*protoconf.ItemConf_Item](), + ColFields: []*LevelField{ + { + FD: fd[*protoconf.UseEffect]("type"), + LeveledFDList: []protoreflect.FieldDescriptor{ + fd[*protoconf.ItemConf_Item]("use_effect"), + fd[*protoconf.UseEffect]("type"), }, - { - FD: fd[*protoconf.ItemConf_Item]("ext_type_list"), - LeveledFDList: []protoreflect.FieldDescriptor{ - fd[*protoconf.ItemConf_Item]("ext_type_list"), - }, + }, + }, + }, + }, + OrderedIndexes: []*LevelIndex{ + { + Index: &Index{ + Cols: []string{"ExtType"}, + Name: "ExtType", + }, + MD: md[*protoconf.ItemConf_Item](), + ColFields: []*LevelField{ + { + FD: fd[*protoconf.ItemConf_Item]("ext_type_list"), + LeveledFDList: []protoreflect.FieldDescriptor{ + fd[*protoconf.ItemConf_Item]("ext_type_list"), }, }, }, - { - Index: &Index{ - Cols: []string{"PathDir"}, - Name: "ItemPathDir", + }, + { + Index: &Index{ + Cols: []string{"Param", "ExtType"}, + SortedCols: []string{"ID"}, + Name: "ParamExtType", + }, + MD: md[*protoconf.ItemConf_Item](), + ColFields: []*LevelField{ + { + FD: fd[*protoconf.ItemConf_Item]("param_list"), + LeveledFDList: []protoreflect.FieldDescriptor{ + fd[*protoconf.ItemConf_Item]("param_list"), + }, }, - MD: md[*protoconf.ItemConf_Item](), - ColFields: []*LevelField{ - { - FD: fd[*protoconf.Path]("dir"), - LeveledFDList: []protoreflect.FieldDescriptor{ - fd[*protoconf.ItemConf_Item]("path"), - fd[*protoconf.Path]("dir"), - }, + { + FD: fd[*protoconf.ItemConf_Item]("ext_type_list"), + LeveledFDList: []protoreflect.FieldDescriptor{ + fd[*protoconf.ItemConf_Item]("ext_type_list"), }, }, }, - { - Index: &Index{ - Cols: []string{"PathName"}, - Name: "ItemPathName", - }, - MD: md[*protoconf.ItemConf_Item](), - ColFields: []*LevelField{ - { - FD: fd[*protoconf.Path]("name_list"), - LeveledFDList: []protoreflect.FieldDescriptor{ - fd[*protoconf.ItemConf_Item]("path"), - fd[*protoconf.Path]("name_list"), - }, + SortedColFields: []*LevelField{ + { + FD: fd[*protoconf.ItemConf_Item]("id"), + LeveledFDList: []protoreflect.FieldDescriptor{ + fd[*protoconf.ItemConf_Item]("id"), }, }, }, + }, + }, + }, + }, + }, + { + name: "HeroConf", + args: args{ + md: md[*protoconf.HeroConf](), + }, + want: &IndexDescriptor{ + LevelMessage: &LevelMessage{ + FD: fd[*protoconf.HeroConf]("hero_map"), + Depth: 1, + MapDepth: 1, + NextLevel: &LevelMessage{ + FD: fd[*protoconf.HeroConf_Hero]("attr_map"), + Depth: 2, + MapDepth: 2, + Indexes: []*LevelIndex{ { Index: &Index{ - Cols: []string{"PathFriendID"}, - Name: "ItemPathFriendID", + Cols: []string{"Title"}, + Name: "", }, - MD: md[*protoconf.ItemConf_Item](), + MD: md[*protoconf.HeroConf_Hero_Attr](), ColFields: []*LevelField{ { - FD: fd[*protoconf.Path_Friend]("id"), + FD: fd[*protoconf.HeroConf_Hero_Attr]("title"), LeveledFDList: []protoreflect.FieldDescriptor{ - fd[*protoconf.ItemConf_Item]("path"), - fd[*protoconf.Path]("friend"), - fd[*protoconf.Path_Friend]("id"), + fd[*protoconf.HeroConf_Hero_Attr]("title"), }, }, }, }, - { - Index: &Index{ - Cols: []string{"UseEffectType"}, - Name: "UseEffectType", - }, - MD: md[*protoconf.ItemConf_Item](), - ColFields: []*LevelField{ - { - FD: fd[*protoconf.UseEffect]("type"), - LeveledFDList: []protoreflect.FieldDescriptor{ - fd[*protoconf.ItemConf_Item]("use_effect"), - fd[*protoconf.UseEffect]("type"), - }, + }, + }, + }, + }, + }, + { + name: "ActivityConf", + args: args{ + md: md[*protoconf.ActivityConf](), + }, + want: &IndexDescriptor{ + LevelMessage: &LevelMessage{ + FD: fd[*protoconf.ActivityConf]("activity_map"), + Depth: 1, + MapDepth: 1, + Indexes: []*LevelIndex{ + { + Index: &Index{ + Cols: []string{"ActivityName"}, + Name: "", + }, + MD: md[*protoconf.ActivityConf_Activity](), + ColFields: []*LevelField{ + { + FD: fd[*protoconf.ActivityConf_Activity]("activity_name"), + LeveledFDList: []protoreflect.FieldDescriptor{ + fd[*protoconf.ActivityConf_Activity]("activity_name"), }, }, }, }, - OrderedIndexes: []*LevelIndex{ + }, + NextLevel: &LevelMessage{ + FD: fd[*protoconf.ActivityConf_Activity]("chapter_map"), + Depth: 2, + MapDepth: 2, + Indexes: []*LevelIndex{ { Index: &Index{ - Cols: []string{"ExtType"}, - Name: "ExtType", + Cols: []string{"ChapterID"}, + Name: "", }, - MD: md[*protoconf.ItemConf_Item](), + MD: md[*protoconf.ActivityConf_Activity_Chapter](), ColFields: []*LevelField{ { - FD: fd[*protoconf.ItemConf_Item]("ext_type_list"), + FD: fd[*protoconf.ActivityConf_Activity_Chapter]("chapter_id"), LeveledFDList: []protoreflect.FieldDescriptor{ - fd[*protoconf.ItemConf_Item]("ext_type_list"), + fd[*protoconf.ActivityConf_Activity_Chapter]("chapter_id"), }, }, }, }, { Index: &Index{ - Cols: []string{"Param", "ExtType"}, - SortedCols: []string{"ID"}, - Name: "ParamExtType", + Cols: []string{"ChapterName"}, + SortedCols: []string{"AwardID"}, + Name: "NamedChapter", }, - MD: md[*protoconf.ItemConf_Item](), + MD: md[*protoconf.ActivityConf_Activity_Chapter](), ColFields: []*LevelField{ { - FD: fd[*protoconf.ItemConf_Item]("param_list"), + FD: fd[*protoconf.ActivityConf_Activity_Chapter]("chapter_name"), LeveledFDList: []protoreflect.FieldDescriptor{ - fd[*protoconf.ItemConf_Item]("param_list"), + fd[*protoconf.ActivityConf_Activity_Chapter]("chapter_name"), }, }, + }, + SortedColFields: []*LevelField{ { - FD: fd[*protoconf.ItemConf_Item]("ext_type_list"), + FD: fd[*protoconf.ActivityConf_Activity_Chapter]("award_id"), LeveledFDList: []protoreflect.FieldDescriptor{ - fd[*protoconf.ItemConf_Item]("ext_type_list"), + fd[*protoconf.ActivityConf_Activity_Chapter]("award_id"), }, }, }, - SortedColFields: []*LevelField{ - { - FD: fd[*protoconf.ItemConf_Item]("id"), - LeveledFDList: []protoreflect.FieldDescriptor{ - fd[*protoconf.ItemConf_Item]("id"), + }, + }, + NextLevel: &LevelMessage{ + FD: fd[*protoconf.ActivityConf_Activity_Chapter]("section_map"), + Depth: 3, + MapDepth: 3, + NextLevel: &LevelMessage{ + FD: fd[*protoconf.Section]("section_item_list"), + Depth: 4, + MapDepth: 3, + Indexes: []*LevelIndex{ + { + Index: &Index{ + Cols: []string{"SectionItemID"}, + Name: "Award", + }, + MD: md[*protoconf.Section_SectionItem](), + ColFields: []*LevelField{ + { + FD: fd[*protoconf.Section_SectionItem]("id"), + LeveledFDList: []protoreflect.FieldDescriptor{ + fd[*protoconf.Section_SectionItem]("id"), + }, + }, }, }, }, + NextLevel: &LevelMessage{ + FD: fd[*protoconf.Section_SectionItem]("decompose_item_list"), + Depth: 5, + MapDepth: 3, + }, }, }, }, @@ -298,36 +437,134 @@ func Test_ParseIndexDescriptor(t *testing.T) { }, }, { - name: "HeroConf", + name: "TaskConf", args: args{ - md: md[*protoconf.HeroConf](), + md: md[*protoconf.TaskConf](), }, want: &IndexDescriptor{ LevelMessage: &LevelMessage{ + FD: fd[*protoconf.TaskConf]("task_map"), Depth: 1, MapDepth: 1, - FD: fd[*protoconf.HeroConf]("hero_map"), - NextLevel: &LevelMessage{ - Depth: 2, - MapDepth: 2, - FD: fd[*protoconf.HeroConf_Hero]("attr_map"), - NextLevel: &LevelMessage{ - Depth: 3, - MapDepth: 3, - Indexes: []*LevelIndex{ + Indexes: []*LevelIndex{ + { + Index: &Index{ + Cols: []string{"ActivityID"}, + SortedCols: []string{"Goal", "ID"}, + Name: "", + }, + MD: md[*protoconf.TaskConf_Task](), + ColFields: []*LevelField{ { - Index: &Index{ - Cols: []string{"Title"}, - Name: "", + FD: fd[*protoconf.TaskConf_Task]("activity_id"), + LeveledFDList: []protoreflect.FieldDescriptor{ + fd[*protoconf.TaskConf_Task]("activity_id"), }, - MD: md[*protoconf.HeroConf_Hero_Attr](), - ColFields: []*LevelField{ - { - FD: fd[*protoconf.HeroConf_Hero_Attr]("title"), - LeveledFDList: []protoreflect.FieldDescriptor{ - fd[*protoconf.HeroConf_Hero_Attr]("title"), - }, - }, + }, + }, + SortedColFields: []*LevelField{ + { + FD: fd[*protoconf.TaskConf_Task]("goal"), + LeveledFDList: []protoreflect.FieldDescriptor{ + fd[*protoconf.TaskConf_Task]("goal"), + }, + }, + { + FD: fd[*protoconf.TaskConf_Task]("id"), + LeveledFDList: []protoreflect.FieldDescriptor{ + fd[*protoconf.TaskConf_Task]("id"), + }, + }, + }, + }, + }, + OrderedIndexes: []*LevelIndex{ + { + Index: &Index{ + Cols: []string{"Goal"}, + SortedCols: []string{"ID"}, + Name: "OrderedTask", + }, + MD: md[*protoconf.TaskConf_Task](), + ColFields: []*LevelField{ + { + FD: fd[*protoconf.TaskConf_Task]("goal"), + LeveledFDList: []protoreflect.FieldDescriptor{ + fd[*protoconf.TaskConf_Task]("goal"), + }, + }, + }, + SortedColFields: []*LevelField{ + { + FD: fd[*protoconf.TaskConf_Task]("id"), + LeveledFDList: []protoreflect.FieldDescriptor{ + fd[*protoconf.TaskConf_Task]("id"), + }, + }, + }, + }, + { + Index: &Index{ + Cols: []string{"Expiry"}, + Name: "TaskExpiry", + }, + MD: md[*protoconf.TaskConf_Task](), + ColFields: []*LevelField{ + { + FD: fd[*protoconf.TaskConf_Task]("expiry"), + LeveledFDList: []protoreflect.FieldDescriptor{ + fd[*protoconf.TaskConf_Task]("expiry"), + }, + }, + }, + }, + { + Index: &Index{ + Cols: []string{"Expiry"}, + SortedCols: []string{"Goal", "ID"}, + Name: "SortedTaskExpiry", + }, + MD: md[*protoconf.TaskConf_Task](), + ColFields: []*LevelField{ + { + FD: fd[*protoconf.TaskConf_Task]("expiry"), + LeveledFDList: []protoreflect.FieldDescriptor{ + fd[*protoconf.TaskConf_Task]("expiry"), + }, + }, + }, + SortedColFields: []*LevelField{ + { + FD: fd[*protoconf.TaskConf_Task]("goal"), + LeveledFDList: []protoreflect.FieldDescriptor{ + fd[*protoconf.TaskConf_Task]("goal"), + }, + }, + { + FD: fd[*protoconf.TaskConf_Task]("id"), + LeveledFDList: []protoreflect.FieldDescriptor{ + fd[*protoconf.TaskConf_Task]("id"), + }, + }, + }, + }, + { + Index: &Index{ + Cols: []string{"Expiry", "ActivityID"}, + Name: "ActivityExpiry", + }, + MD: md[*protoconf.TaskConf_Task](), + ColFields: []*LevelField{ + { + FD: fd[*protoconf.TaskConf_Task]("expiry"), + LeveledFDList: []protoreflect.FieldDescriptor{ + fd[*protoconf.TaskConf_Task]("expiry"), + }, + }, + { + FD: fd[*protoconf.TaskConf_Task]("activity_id"), + LeveledFDList: []protoreflect.FieldDescriptor{ + fd[*protoconf.TaskConf_Task]("activity_id"), }, }, }, @@ -337,110 +574,89 @@ func Test_ParseIndexDescriptor(t *testing.T) { }, }, { - name: "ActivityConf", + // Fruit3Conf: list -> list -> map -> list + // indexes at Country level (L2) and Attr level (L4), + // ordered index at Item level (L3). + name: "Fruit3Conf", args: args{ - md: md[*protoconf.ActivityConf](), + md: md[*protoconf.Fruit3Conf](), }, want: &IndexDescriptor{ LevelMessage: &LevelMessage{ + FD: fd[*protoconf.Fruit3Conf]("fruit_list"), Depth: 1, - MapDepth: 1, - FD: fd[*protoconf.ActivityConf]("activity_map"), + MapDepth: 0, NextLevel: &LevelMessage{ + FD: fd[*protoconf.Fruit3Conf_Fruit]("country_list"), Depth: 2, - MapDepth: 2, - FD: fd[*protoconf.ActivityConf_Activity]("chapter_map"), + MapDepth: 0, Indexes: []*LevelIndex{ { Index: &Index{ - Cols: []string{"ActivityName"}, + Cols: []string{"CountryName"}, Name: "", }, - MD: md[*protoconf.ActivityConf_Activity](), + MD: md[*protoconf.Fruit3Conf_Fruit_Country](), ColFields: []*LevelField{ { - FD: fd[*protoconf.ActivityConf_Activity]("activity_name"), + FD: fd[*protoconf.Fruit3Conf_Fruit_Country]("name"), LeveledFDList: []protoreflect.FieldDescriptor{ - fd[*protoconf.ActivityConf_Activity]("activity_name"), + fd[*protoconf.Fruit3Conf_Fruit_Country]("name"), }, }, }, }, }, NextLevel: &LevelMessage{ + FD: fd[*protoconf.Fruit3Conf_Fruit_Country]("item_map"), Depth: 3, - MapDepth: 3, - FD: fd[*protoconf.ActivityConf_Activity_Chapter]("section_map"), - Indexes: []*LevelIndex{ - { - Index: &Index{ - Cols: []string{"ChapterID"}, - Name: "", - }, - MD: md[*protoconf.ActivityConf_Activity_Chapter](), - ColFields: []*LevelField{ - { - FD: fd[*protoconf.ActivityConf_Activity_Chapter]("chapter_id"), - LeveledFDList: []protoreflect.FieldDescriptor{ - fd[*protoconf.ActivityConf_Activity_Chapter]("chapter_id"), - }, - }, - }, - }, + MapDepth: 1, + OrderedIndexes: []*LevelIndex{ { Index: &Index{ - Cols: []string{"ChapterName"}, - SortedCols: []string{"AwardID"}, - Name: "NamedChapter", + Cols: []string{"CountryItemPrice"}, + SortedCols: []string{"CountryItemID"}, + Name: "", }, - MD: md[*protoconf.ActivityConf_Activity_Chapter](), + MD: md[*protoconf.Fruit3Conf_Fruit_Country_Item](), ColFields: []*LevelField{ { - FD: fd[*protoconf.ActivityConf_Activity_Chapter]("chapter_name"), + FD: fd[*protoconf.Fruit3Conf_Fruit_Country_Item]("price"), LeveledFDList: []protoreflect.FieldDescriptor{ - fd[*protoconf.ActivityConf_Activity_Chapter]("chapter_name"), + fd[*protoconf.Fruit3Conf_Fruit_Country_Item]("price"), }, }, }, SortedColFields: []*LevelField{ { - FD: fd[*protoconf.ActivityConf_Activity_Chapter]("award_id"), + FD: fd[*protoconf.Fruit3Conf_Fruit_Country_Item]("id"), LeveledFDList: []protoreflect.FieldDescriptor{ - fd[*protoconf.ActivityConf_Activity_Chapter]("award_id"), + fd[*protoconf.Fruit3Conf_Fruit_Country_Item]("id"), }, }, }, }, }, NextLevel: &LevelMessage{ + FD: fd[*protoconf.Fruit3Conf_Fruit_Country_Item]("attr_list"), Depth: 4, - MapDepth: 4, - FD: fd[*protoconf.Section]("section_item_list"), - NextLevel: &LevelMessage{ - Depth: 5, - MapDepth: 4, - FD: fd[*protoconf.Section_SectionItem]("decompose_item_list"), - Indexes: []*LevelIndex{ - { - Index: &Index{ - Cols: []string{"SectionItemID"}, - Name: "Award", - }, - MD: md[*protoconf.Section_SectionItem](), - ColFields: []*LevelField{ - { - FD: fd[*protoconf.Section_SectionItem]("id"), - LeveledFDList: []protoreflect.FieldDescriptor{ - fd[*protoconf.Section_SectionItem]("id"), - }, + MapDepth: 1, + Indexes: []*LevelIndex{ + { + Index: &Index{ + Cols: []string{"CountryItemAttrName"}, + Name: "", + }, + MD: md[*protoconf.Fruit3Conf_Fruit_Country_Item_Attr](), + ColFields: []*LevelField{ + { + FD: fd[*protoconf.Fruit3Conf_Fruit_Country_Item_Attr]("name"), + LeveledFDList: []protoreflect.FieldDescriptor{ + fd[*protoconf.Fruit3Conf_Fruit_Country_Item_Attr]("name"), }, }, }, }, - NextLevel: &LevelMessage{ - Depth: 6, - MapDepth: 4, - }, }, }, }, @@ -449,141 +665,46 @@ func Test_ParseIndexDescriptor(t *testing.T) { }, }, { - name: "TaskConf", + // Fruit5Conf: 3-level map (fruit_map -> country_map -> item_map), + // but indexes only at MapDepth=2 (Country level). + // The 3rd level map (MapDepth=3, Item) has no index. + // This validates that initLevelMessage only collects map keys + // for levels whose deeper levels have indexes, so len(keys) == 2 + // (not 3), preventing generation of extra LevelIndex key structs. + name: "Fruit5Conf", args: args{ - md: md[*protoconf.TaskConf](), + md: md[*protoconf.Fruit5Conf](), }, want: &IndexDescriptor{ LevelMessage: &LevelMessage{ + FD: fd[*protoconf.Fruit5Conf]("fruit_map"), Depth: 1, MapDepth: 1, - FD: fd[*protoconf.TaskConf]("task_map"), NextLevel: &LevelMessage{ + FD: fd[*protoconf.Fruit5Conf_Fruit]("country_map"), Depth: 2, MapDepth: 2, Indexes: []*LevelIndex{ { Index: &Index{ - Cols: []string{"ActivityID"}, - SortedCols: []string{"Goal", "ID"}, - Name: "", + Cols: []string{"CountryName"}, + Name: "", }, - MD: md[*protoconf.TaskConf_Task](), + MD: md[*protoconf.Fruit5Conf_Fruit_Country](), ColFields: []*LevelField{ { - FD: fd[*protoconf.TaskConf_Task]("activity_id"), - LeveledFDList: []protoreflect.FieldDescriptor{ - fd[*protoconf.TaskConf_Task]("activity_id"), - }, - }, - }, - SortedColFields: []*LevelField{ - { - FD: fd[*protoconf.TaskConf_Task]("goal"), - LeveledFDList: []protoreflect.FieldDescriptor{ - fd[*protoconf.TaskConf_Task]("goal"), - }, - }, - { - FD: fd[*protoconf.TaskConf_Task]("id"), + FD: fd[*protoconf.Fruit5Conf_Fruit_Country]("name"), LeveledFDList: []protoreflect.FieldDescriptor{ - fd[*protoconf.TaskConf_Task]("id"), + fd[*protoconf.Fruit5Conf_Fruit_Country]("name"), }, }, }, }, }, - OrderedIndexes: []*LevelIndex{ - { - Index: &Index{ - Cols: []string{"Goal"}, - SortedCols: []string{"ID"}, - Name: "OrderedTask", - }, - MD: md[*protoconf.TaskConf_Task](), - ColFields: []*LevelField{ - { - FD: fd[*protoconf.TaskConf_Task]("goal"), - LeveledFDList: []protoreflect.FieldDescriptor{ - fd[*protoconf.TaskConf_Task]("goal"), - }, - }, - }, - SortedColFields: []*LevelField{ - { - FD: fd[*protoconf.TaskConf_Task]("id"), - LeveledFDList: []protoreflect.FieldDescriptor{ - fd[*protoconf.TaskConf_Task]("id"), - }, - }, - }, - }, - { - Index: &Index{ - Cols: []string{"Expiry"}, - Name: "TaskExpiry", - }, - MD: md[*protoconf.TaskConf_Task](), - ColFields: []*LevelField{ - { - FD: fd[*protoconf.TaskConf_Task]("expiry"), - LeveledFDList: []protoreflect.FieldDescriptor{ - fd[*protoconf.TaskConf_Task]("expiry"), - }, - }, - }, - }, - { - Index: &Index{ - Cols: []string{"Expiry"}, - SortedCols: []string{"Goal", "ID"}, - Name: "SortedTaskExpiry", - }, - MD: md[*protoconf.TaskConf_Task](), - ColFields: []*LevelField{ - { - FD: fd[*protoconf.TaskConf_Task]("expiry"), - LeveledFDList: []protoreflect.FieldDescriptor{ - fd[*protoconf.TaskConf_Task]("expiry"), - }, - }, - }, - SortedColFields: []*LevelField{ - { - FD: fd[*protoconf.TaskConf_Task]("goal"), - LeveledFDList: []protoreflect.FieldDescriptor{ - fd[*protoconf.TaskConf_Task]("goal"), - }, - }, - { - FD: fd[*protoconf.TaskConf_Task]("id"), - LeveledFDList: []protoreflect.FieldDescriptor{ - fd[*protoconf.TaskConf_Task]("id"), - }, - }, - }, - }, - { - Index: &Index{ - Cols: []string{"Expiry", "ActivityID"}, - Name: "ActivityExpiry", - }, - MD: md[*protoconf.TaskConf_Task](), - ColFields: []*LevelField{ - { - FD: fd[*protoconf.TaskConf_Task]("expiry"), - LeveledFDList: []protoreflect.FieldDescriptor{ - fd[*protoconf.TaskConf_Task]("expiry"), - }, - }, - { - FD: fd[*protoconf.TaskConf_Task]("activity_id"), - LeveledFDList: []protoreflect.FieldDescriptor{ - fd[*protoconf.TaskConf_Task]("activity_id"), - }, - }, - }, - }, + NextLevel: &LevelMessage{ + FD: fd[*protoconf.Fruit5Conf_Fruit_Country]("item_map"), + Depth: 3, + MapDepth: 3, }, }, }, diff --git a/internal/index/index_test.go b/internal/index/index_test.go index f0f58a07..e6c3ce8e 100644 --- a/internal/index/index_test.go +++ b/internal/index/index_test.go @@ -15,18 +15,18 @@ func Test_parseIndex(t *testing.T) { name: "Single column with single sorted column and name", input: "Column1@IndexName", want: &Index{ - Cols: []string{"Column1"}, + Cols: []string{"Column1"}, SortedCols: []string{"SortedCol1"}, - Name: "IndexName", + Name: "IndexName", }, }, { name: "Multi-column with multi sorted column and name", input: "( Column1 , Column2 )< SortedCol1 , SortedCol2 >@IndexName", want: &Index{ - Cols: []string{"Column1", "Column2"}, + Cols: []string{"Column1", "Column2"}, SortedCols: []string{"SortedCol1", "SortedCol2"}, - Name: "IndexName", + Name: "IndexName", }, }, { @@ -48,15 +48,15 @@ func Test_parseIndex(t *testing.T) { name: "Single column with single sorted column only", input: "Column6", want: &Index{ - Cols: []string{"Column6"}, + Cols: []string{"Column6"}, SortedCols: []string{"SortedCol"}, }, }, { - name: "zinotest", + name: "Single-column with multi sorted columns", input: "ActivityID", want: &Index{ - Cols: []string{"ActivityID"}, + Cols: []string{"ActivityID"}, SortedCols: []string{"Goal", "ID"}, }, }, @@ -64,9 +64,9 @@ func Test_parseIndex(t *testing.T) { name: "Multi-column with spaces around commas", input: "(Column7, Column8, Column9)@IndexName", want: &Index{ - Cols: []string{"Column7", "Column8", "Column9"}, + Cols: []string{"Column7", "Column8", "Column9"}, SortedCols: []string{"SortedCol7", "SortedCol8", "SortedCol9"}, - Name: "IndexName", + Name: "IndexName", }, }, { diff --git a/test/cpp-tableau-loader/CMakeLists.txt b/test/cpp-tableau-loader/CMakeLists.txt index 7787aed9..0297c3e1 100644 --- a/test/cpp-tableau-loader/CMakeLists.txt +++ b/test/cpp-tableau-loader/CMakeLists.txt @@ -19,49 +19,45 @@ message(STATUS "Using C++${CMAKE_CXX_STANDARD} standard") if (MSVC) SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W4 /DNDEBUG") + # Use static CRT (/MT or /MTd) to match protobuf's default static runtime build. + set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") else() SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -g -fPIC -Wno-deprecated -Wno-unused-variable -Wno-sign-compare -Wno-strict-aliasing -fno-strict-aliasing -DNDEBUG") endif() -# root dir define -SET(CMAKE_ROOT_DIR ${CMAKE_CURRENT_SOURCE_DIR}) -SET(THRID_PARTY_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../../third_party) - # google protobuf -SET(PROTOBUF_ROOT_DIR ${THRID_PARTY_DIR}/_submodules/protobuf/) -SET(PROTOBUF_SRC_DIR ${PROTOBUF_ROOT_DIR}/src/) -SET(PROTOBUF_BUILD_DIR ${PROTOBUF_ROOT_DIR}/cmake/build/) -# loader -SET(LOADER_SRC_DIR ${PROJECT_SOURCE_DIR}/src/) - -# protoconf -SET(PROTOCONF_DIR ${TOOLS_DIR}/protoconf) -SET(PROTOCONF_TOOL ${PROTOCONF_DIR}/protoconf) +# Try to find protobuf from local submodule first, then fallback to system. +# Protobuf's install.cmake generates config files under: +# - Linux: build/lib64/cmake/protobuf/ (via CMAKE_INSTALL_LIBDIR) +# - Windows: build/cmake/ (MSVC uses "cmake" directly) +# Setting CMAKE_PREFIX_PATH to the build dir allows CMake to search both layouts. +set(LOCAL_PROTOBUF_BUILD_DIR "${PROJECT_SOURCE_DIR}/../../third_party/_submodules/protobuf/cmake/build") +set(LOCAL_PROTOBUF_SRC_DIR "${PROJECT_SOURCE_DIR}/../../third_party/_submodules/protobuf/src") +if(EXISTS "${LOCAL_PROTOBUF_BUILD_DIR}" AND EXISTS "${LOCAL_PROTOBUF_SRC_DIR}") + message(STATUS "Found local protobuf submodule, using it preferentially.") + list(PREPEND CMAKE_PREFIX_PATH "${LOCAL_PROTOBUF_BUILD_DIR}") +endif() -# common include -SET(COMMON_INCLUDE_DIR - ${PROTOBUF_SRC_DIR} - ${LOADER_SRC_DIR} - ) +# Use CONFIG mode explicitly to pick up protobuf's own protobuf-config.cmake +# instead of CMake's built-in FindProtobuf.cmake module, which may not handle +# the local build directory layout correctly. +find_package(Protobuf CONFIG REQUIRED) +message(STATUS "Using protobuf ${Protobuf_VERSION}") -# common lib -if(MSVC) - SET(COMMON_LIB - ${PROTOBUF_BUILD_DIR}/libprotobufd.lib - ) - SET(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreadedDebug") -else() - SET(COMMON_LIB - ${PROTOBUF_BUILD_DIR}/libprotobufd.a - pthread - stdc++fs - ) -endif() +# loader +SET(LOADER_SRC_DIR ${PROJECT_SOURCE_DIR}/src/) # include -include_directories(${COMMON_INCLUDE_DIR} ${LOADER_SRC_DIR}/protoconf) +# protobuf::libprotobuf target already provides its include directories via +# INTERFACE_INCLUDE_DIRECTORIES, so we only need to add our own source dirs. +include_directories(${LOADER_SRC_DIR} ${LOADER_SRC_DIR}/protoconf) # add the executable add_executable(${PROJECT_NAME} ${PROTO_SOURCE} ${SOURCE}) set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin) -target_link_libraries(${PROJECT_NAME} ${COMMON_LIB}) + +# link libraries +target_link_libraries(${PROJECT_NAME} protobuf::libprotobuf) +if(NOT MSVC) + target_link_libraries(${PROJECT_NAME} pthread stdc++fs) +endif() diff --git a/test/cpp-tableau-loader/gen.bat b/test/cpp-tableau-loader/gen.bat index c1a4ac96..1751c14c 100644 --- a/test/cpp-tableau-loader/gen.bat +++ b/test/cpp-tableau-loader/gen.bat @@ -5,8 +5,35 @@ setlocal enabledelayedexpansion for /f "delims=" %%i in ('git rev-parse --show-toplevel') do set repoRoot=%%i cd /d "%repoRoot%" -set "PROTOC=%repoRoot%\third_party\_submodules\protobuf\cmake\build\protoc.exe" -set "PROTOBUF_PROTO=%repoRoot%\third_party\_submodules\protobuf\src" +REM Allow overriding protoc via environment variable. +REM Default to locally compiled protoc for local development; fallback to system protoc. +if not defined PROTOC ( + if exist "%repoRoot%\third_party\_submodules\protobuf\cmake\build\protoc.exe" ( + set "PROTOC=%repoRoot%\third_party\_submodules\protobuf\cmake\build\protoc.exe" + ) else ( + where protoc >nul 2>nul + if !errorlevel! equ 0 ( + for /f "delims=" %%p in ('where protoc') do set "PROTOC=%%p" + ) else ( + echo Error: protoc not found. Please build protobuf submodule or install protoc. >&2 + exit /b 1 + ) + ) +) +REM Allow overriding protobuf include path via environment variable. +REM Default to local submodule source; fallback to system include path. +if not defined PROTOBUF_PROTO ( + if exist "%repoRoot%\third_party\_submodules\protobuf\src\google\protobuf" ( + set "PROTOBUF_PROTO=%repoRoot%\third_party\_submodules\protobuf\src" + ) else ( + for /f "delims=" %%p in ('where protoc 2^>nul') do set "_PROTOC_DIR=%%~dpp" + if defined _PROTOC_DIR ( + set "PROTOBUF_PROTO=!_PROTOC_DIR!..\include" + ) else ( + set "PROTOBUF_PROTO=%repoRoot%\third_party\_submodules\protobuf\src" + ) + ) +) set "TABLEAU_PROTO=%repoRoot%\third_party\_submodules\tableau\proto" set "ROOTDIR=%repoRoot%\test\cpp-tableau-loader" set "PLGUIN_DIR=%repoRoot%\cmd\protoc-gen-cpp-tableau-loader" diff --git a/test/cpp-tableau-loader/gen.sh b/test/cpp-tableau-loader/gen.sh index 32756cb3..017757e3 100755 --- a/test/cpp-tableau-loader/gen.sh +++ b/test/cpp-tableau-loader/gen.sh @@ -7,8 +7,31 @@ set -o pipefail shopt -s globstar cd "$(git rev-parse --show-toplevel)" -PROTOC="./third_party/_submodules/protobuf/cmake/build/protoc" -PROTOBUF_PROTO="./third_party/_submodules/protobuf/src" + +# Allow overriding protoc via environment variable. +# Default to locally compiled protoc for local development; fallback to system protoc. +LOCAL_PROTOC="./third_party/_submodules/protobuf/cmake/build/protoc" +if [ -z "$PROTOC" ]; then + if [ -x "$LOCAL_PROTOC" ]; then + PROTOC="$LOCAL_PROTOC" + else + PROTOC="$(which protoc 2>/dev/null || true)" + fi +fi +if [ -z "$PROTOC" ]; then + echo "Error: protoc not found. Please build protobuf submodule or install protoc." >&2 + exit 1 +fi +# Allow overriding protobuf include path via environment variable. +# Default to local submodule source; fallback to system include path. +LOCAL_PROTOBUF_PROTO="./third_party/_submodules/protobuf/src" +if [ -z "$PROTOBUF_PROTO" ]; then + if [ -d "$LOCAL_PROTOBUF_PROTO/google/protobuf" ]; then + PROTOBUF_PROTO="$LOCAL_PROTOBUF_PROTO" + else + PROTOBUF_PROTO="$(pkg-config --variable=includedir protobuf 2>/dev/null || echo /usr/include)" + fi +fi TABLEAU_PROTO="./third_party/_submodules/tableau/proto" ROOTDIR="./test/cpp-tableau-loader" PLGUIN_DIR="./cmd/protoc-gen-cpp-tableau-loader" diff --git a/test/cpp-tableau-loader/src/protoconf/hub.pc.h b/test/cpp-tableau-loader/src/protoconf/hub.pc.h index cd7c8d69..14f7ca08 100644 --- a/test/cpp-tableau-loader/src/protoconf/hub.pc.h +++ b/test/cpp-tableau-loader/src/protoconf/hub.pc.h @@ -138,6 +138,10 @@ class Fruit4Conf; template <> const std::shared_ptr Hub::Get() const; +class Fruit5Conf; +template <> +const std::shared_ptr Hub::Get() const; + class FruitConf; template <> const std::shared_ptr Hub::Get() const; @@ -193,6 +197,7 @@ class MessagerContainer { std::shared_ptr fruit_2_conf_; std::shared_ptr fruit_3_conf_; std::shared_ptr fruit_4_conf_; + std::shared_ptr fruit_5_conf_; std::shared_ptr fruit_conf_; std::shared_ptr item_conf_; std::shared_ptr patch_merge_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 d82400ff..16ec170f 100644 --- a/test/cpp-tableau-loader/src/protoconf/hub_shard0.pc.cc +++ b/test/cpp-tableau-loader/src/protoconf/hub_shard0.pc.cc @@ -35,6 +35,11 @@ const std::shared_ptr Hub::Get() const { return GetMessagerContainerWithProvider()->fruit_4_conf_; } +template <> +const std::shared_ptr Hub::Get() const { + return GetMessagerContainerWithProvider()->fruit_5_conf_; +} + template <> const std::shared_ptr Hub::Get() const { return GetMessagerContainerWithProvider()->fruit_conf_; @@ -51,6 +56,7 @@ void MessagerContainer::InitShard0() { 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())); + fruit_5_conf_ = std::dynamic_pointer_cast(GetMessager(Fruit5Conf::Name())); fruit_conf_ = std::dynamic_pointer_cast(GetMessager(FruitConf::Name())); item_conf_ = std::dynamic_pointer_cast(GetMessager(ItemConf::Name())); } @@ -61,6 +67,7 @@ void Registry::InitShard0() { 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 d1607380..00527afc 100644 --- a/test/cpp-tableau-loader/src/protoconf/index_conf.pc.cc +++ b/test/cpp-tableau-loader/src/protoconf/index_conf.pc.cc @@ -27,7 +27,6 @@ bool FruitConf::ProcessAfterLoad() { for (auto&& item1 : data_.fruit_map()) { auto k1 = item1.first; for (auto&& item2 : item1.second.item_map()) { - auto k2 = item2.first; { // OrderedIndex: Price ordered_index_item_map_[item2.second.price()].push_back(&item2.second); @@ -141,7 +140,6 @@ bool Fruit2Conf::ProcessAfterLoad() { index_country_map_[item2.name()].push_back(&item2); } for (auto&& item3 : item2.item_map()) { - auto k2 = item3.first; for (auto&& item4 : item3.second.attr_list()) { { // Index: CountryItemAttrName @@ -159,7 +157,6 @@ bool Fruit2Conf::ProcessAfterLoad() { auto k1 = item1.first; for (auto&& item2 : item1.second.country_list()) { for (auto&& item3 : item2.item_map()) { - auto k2 = item3.first; { // OrderedIndex: CountryItemPrice ordered_index_item_map_[item3.second.price()].push_back(&item3.second); @@ -326,7 +323,6 @@ 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 @@ -341,7 +337,6 @@ bool Fruit3Conf::ProcessAfterLoad() { for (auto&& item1 : data_.fruit_list()) { for (auto&& item2 : item1.country_list()) { for (auto&& item3 : item2.item_map()) { - auto k1 = item3.first; { // OrderedIndex: CountryItemPrice ordered_index_item_map_[item3.second.price()].push_back(&item3.second); @@ -444,7 +439,6 @@ 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 @@ -465,7 +459,6 @@ bool Fruit4Conf::ProcessAfterLoad() { for (auto&& item2 : item1.second.country_map()) { auto k2 = item2.first; for (auto&& item3 : item2.second.item_map()) { - auto k3 = item3.first; { // OrderedIndex: CountryItemPrice ordered_index_item_map_[item3.second.price()].push_back(&item3.second); @@ -725,4 +718,110 @@ const protoconf::Fruit4Conf::Fruit::Country::Item* Fruit4Conf::FindFirstItem(int return conf->front(); } +const std::string Fruit5Conf::kProtoName = protoconf::Fruit5Conf::GetDescriptor()->name(); + +bool Fruit5Conf::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 Fruit5Conf::ProcessAfterLoad() { + // Index init. + index_country_map_.clear(); + index_country_map1_.clear(); + for (auto&& item1 : data_.fruit_map()) { + auto k1 = item1.first; + for (auto&& item2 : item1.second.country_map()) { + { + // Index: CountryName + index_country_map_[item2.second.name()].push_back(&item2.second); + index_country_map1_[k1][item2.second.name()].push_back(&item2.second); + } + } + } + return true; +} + +const protoconf::Fruit5Conf::Fruit* Fruit5Conf::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; +} + +const protoconf::Fruit5Conf::Fruit::Country* Fruit5Conf::Get(int32_t fruit_type, int32_t id) const { + const auto* conf = Get(fruit_type); + if (conf == nullptr) { + return nullptr; + } + auto iter = conf->country_map().find(id); + if (iter == conf->country_map().end()) { + return nullptr; + } + return &iter->second; +} + +const protoconf::Fruit5Conf::Fruit::Country::Item* Fruit5Conf::Get(int32_t fruit_type, int32_t id, int32_t id3) const { + const auto* conf = Get(fruit_type, id); + if (conf == nullptr) { + return nullptr; + } + auto iter = conf->item_map().find(id3); + if (iter == conf->item_map().end()) { + return nullptr; + } + return &iter->second; +} + +// Index: CountryName +const Fruit5Conf::Index_CountryMap& Fruit5Conf::FindCountryMap() const { return index_country_map_; } + +const Fruit5Conf::Index_CountryVector* Fruit5Conf::FindCountry(const std::string& name) const { + auto iter = index_country_map_.find(name); + if (iter == index_country_map_.end()) { + return nullptr; + } + return &iter->second; +} + +const protoconf::Fruit5Conf::Fruit::Country* Fruit5Conf::FindFirstCountry(const std::string& name) const { + auto conf = FindCountry(name); + if (conf == nullptr || conf->empty()) { + return nullptr; + } + return conf->front(); +} + +const Fruit5Conf::Index_CountryMap* Fruit5Conf::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 Fruit5Conf::Index_CountryVector* Fruit5Conf::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::Fruit5Conf::Fruit::Country* Fruit5Conf::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(); +} + } // namespace tableau 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 ac4040ae..76e6431e 100644 --- a/test/cpp-tableau-loader/src/protoconf/index_conf.pc.h +++ b/test/cpp-tableau-loader/src/protoconf/index_conf.pc.h @@ -334,6 +334,51 @@ class Fruit4Conf final : public Messager { std::unordered_map ordered_index_item_map2_; }; +class Fruit5Conf 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::Fruit5Conf& Data() const { return data_; } + const google::protobuf::Message* Message() const override { return &data_; } + + private: + virtual bool ProcessAfterLoad() override; + + public: + const protoconf::Fruit5Conf::Fruit* Get(int32_t fruit_type) const; + const protoconf::Fruit5Conf::Fruit::Country* Get(int32_t fruit_type, int32_t id) const; + const protoconf::Fruit5Conf::Fruit::Country::Item* Get(int32_t fruit_type, int32_t id, int32_t id3) const; + + private: + static const std::string kProtoName; + protoconf::Fruit5Conf data_; + + // Index accessers. + // Index: CountryName + public: + using Index_CountryVector = std::vector; + using Index_CountryMap = std::unordered_map; + // Finds the index: key(CountryName) to value(Index_CountryVector) hashmap. + // One key may correspond to multiple values, which are represented by a vector. + const Index_CountryMap& FindCountryMap() const; + // Finds a vector of all values of the given key(s). + const Index_CountryVector* FindCountry(const std::string& name) const; + // Finds the first value of the given key(s). + const protoconf::Fruit5Conf::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::Fruit5Conf::Fruit::Country* FindFirstCountry(int32_t fruit_type, const std::string& name) const; + + private: + Index_CountryMap index_country_map_; + std::unordered_map index_country_map1_; +}; + } // namespace tableau namespace protoconf { @@ -342,4 +387,5 @@ using FruitConfMgr = tableau::FruitConf; using Fruit2ConfMgr = tableau::Fruit2Conf; using Fruit3ConfMgr = tableau::Fruit3Conf; using Fruit4ConfMgr = tableau::Fruit4Conf; +using Fruit5ConfMgr = tableau::Fruit5Conf; } // namespace protoconf diff --git a/test/cpp-tableau-loader/src/protoconf/item_conf.pc.cc b/test/cpp-tableau-loader/src/protoconf/item_conf.pc.cc index 156cc9b2..67a91675 100644 --- a/test/cpp-tableau-loader/src/protoconf/item_conf.pc.cc +++ b/test/cpp-tableau-loader/src/protoconf/item_conf.pc.cc @@ -38,7 +38,6 @@ bool ItemConf::ProcessAfterLoad() { index_item_path_friend_id_map_.clear(); index_use_effect_type_map_.clear(); for (auto&& item1 : data_.item_map()) { - auto k1 = item1.first; { // Index: Type index_item_map_[item1.second.type()].push_back(&item1.second); @@ -115,7 +114,6 @@ bool ItemConf::ProcessAfterLoad() { ordered_index_ext_type_map_.clear(); ordered_index_param_ext_type_map_.clear(); for (auto&& item1 : data_.item_map()) { - auto k1 = item1.first; { // OrderedIndex: ExtType@ExtType for (auto&& item2 : item1.second.ext_type_list()) { 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 6388e121..ca4bd15d 100644 --- a/test/cpp-tableau-loader/src/protoconf/test_conf.pc.cc +++ b/test/cpp-tableau-loader/src/protoconf/test_conf.pc.cc @@ -66,7 +66,6 @@ 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 @@ -428,7 +427,6 @@ bool TaskConf::ProcessAfterLoad() { // Index init. index_task_map_.clear(); for (auto&& item1 : data_.task_map()) { - auto k1 = item1.first; { // Index: ActivityID index_task_map_[item1.second.activity_id()].push_back(&item1.second); @@ -451,7 +449,6 @@ bool TaskConf::ProcessAfterLoad() { ordered_index_sorted_task_expiry_map_.clear(); ordered_index_activity_expiry_map_.clear(); for (auto&& item1 : data_.task_map()) { - auto k1 = item1.first; { // OrderedIndex: Goal@OrderedTask ordered_index_ordered_task_map_[item1.second.goal()].push_back(&item1.second); diff --git a/test/go-tableau-loader/gen.bat b/test/go-tableau-loader/gen.bat index 7a9c2fb3..2520760e 100644 --- a/test/go-tableau-loader/gen.bat +++ b/test/go-tableau-loader/gen.bat @@ -5,8 +5,35 @@ setlocal enabledelayedexpansion for /f "delims=" %%i in ('git rev-parse --show-toplevel') do set repoRoot=%%i cd /d "%repoRoot%" -set "PROTOC=%repoRoot%\third_party\_submodules\protobuf\cmake\build\protoc.exe" -set "PROTOBUF_PROTO=%repoRoot%\third_party\_submodules\protobuf\src" +REM Allow overriding protoc via environment variable. +REM Default to locally compiled protoc for local development; fallback to system protoc. +if not defined PROTOC ( + if exist "%repoRoot%\third_party\_submodules\protobuf\cmake\build\protoc.exe" ( + set "PROTOC=%repoRoot%\third_party\_submodules\protobuf\cmake\build\protoc.exe" + ) else ( + where protoc >nul 2>nul + if !errorlevel! equ 0 ( + for /f "delims=" %%p in ('where protoc') do set "PROTOC=%%p" + ) else ( + echo Error: protoc not found. Please build protobuf submodule or install protoc. >&2 + exit /b 1 + ) + ) +) +REM Allow overriding protobuf include path via environment variable. +REM Default to local submodule source; fallback to system include path. +if not defined PROTOBUF_PROTO ( + if exist "%repoRoot%\third_party\_submodules\protobuf\src\google\protobuf" ( + set "PROTOBUF_PROTO=%repoRoot%\third_party\_submodules\protobuf\src" + ) else ( + for /f "delims=" %%p in ('where protoc 2^>nul') do set "_PROTOC_DIR=%%~dpp" + if defined _PROTOC_DIR ( + set "PROTOBUF_PROTO=!_PROTOC_DIR!..\include" + ) else ( + set "PROTOBUF_PROTO=%repoRoot%\third_party\_submodules\protobuf\src" + ) + ) +) set "TABLEAU_PROTO=%repoRoot%\third_party\_submodules\tableau\proto" set "PLGUIN_DIR=%repoRoot%\cmd\protoc-gen-go-tableau-loader" set "PROTOCONF_IN=%repoRoot%\test\proto" diff --git a/test/go-tableau-loader/gen.sh b/test/go-tableau-loader/gen.sh index e547ff5e..0c74e757 100755 --- a/test/go-tableau-loader/gen.sh +++ b/test/go-tableau-loader/gen.sh @@ -4,13 +4,34 @@ set -e set -o pipefail -shopt -s globstar - cd "$(git rev-parse --show-toplevel)" -PROTOC="./third_party/_submodules/protobuf/cmake/build/protoc" -PROTOBUF_PROTO="./third_party/_submodules/protobuf/src" + +# Allow overriding protoc via environment variable. +# Default to locally compiled protoc for local development; fallback to system protoc. +LOCAL_PROTOC="./third_party/_submodules/protobuf/cmake/build/protoc" +if [ -z "$PROTOC" ]; then + if [ -x "$LOCAL_PROTOC" ]; then + PROTOC="$LOCAL_PROTOC" + else + PROTOC="$(which protoc 2>/dev/null || true)" + fi +fi +if [ -z "$PROTOC" ]; then + echo "Error: protoc not found. Please build protobuf submodule or install protoc." >&2 + exit 1 +fi +# Allow overriding protobuf include path via environment variable. +# Default to local submodule source; fallback to system include path. +LOCAL_PROTOBUF_PROTO="./third_party/_submodules/protobuf/src" +if [ -z "$PROTOBUF_PROTO" ]; then + if [ -d "$LOCAL_PROTOBUF_PROTO/google/protobuf" ]; then + PROTOBUF_PROTO="$LOCAL_PROTOBUF_PROTO" + else + PROTOBUF_PROTO="$(pkg-config --variable=includedir protobuf 2>/dev/null || echo /usr/include)" + fi +fi TABLEAU_PROTO="./third_party/_submodules/tableau/proto" -PLGUIN_DIR="./cmd/protoc-gen-go-tableau-loader" +PLUGIN_DIR="./cmd/protoc-gen-go-tableau-loader" PROTOCONF_IN="./test/proto" PROTOCONF_OUT="./test/go-tableau-loader/protoconf" LOADER_OUT="$PROTOCONF_OUT/loader" @@ -20,9 +41,12 @@ rm -rfv "$PROTOCONF_OUT" "$LOADER_OUT" mkdir -p "$PROTOCONF_OUT" "$LOADER_OUT" # build -cd "${PLGUIN_DIR}" && go build && cd - +cd "${PLUGIN_DIR}" && go build && cd - + +export PATH="$(pwd)/${PLUGIN_DIR}:${PATH}" -export PATH="${PLGUIN_DIR}:${PATH}" +# Collect all .proto files (use `find` for cross-platform compatibility). +PROTO_FILES=$(find "$PROTOCONF_IN" -name "*.proto") ${PROTOC} \ --go-tableau-loader_out="$LOADER_OUT" \ @@ -32,4 +56,4 @@ ${PROTOC} \ --proto_path="$PROTOBUF_PROTO" \ --proto_path="$TABLEAU_PROTO" \ --proto_path="$PROTOCONF_IN" \ - "$PROTOCONF_IN"/**/*.proto + $PROTO_FILES diff --git a/test/go-tableau-loader/protoconf/loader/hero_conf.pc.go b/test/go-tableau-loader/protoconf/loader/hero_conf.pc.go index 188558b6..a47a64cc 100644 --- a/test/go-tableau-loader/protoconf/loader/hero_conf.pc.go +++ b/test/go-tableau-loader/protoconf/loader/hero_conf.pc.go @@ -97,9 +97,7 @@ func (x *HeroConf) processAfterLoad() error { x.indexAttrMap = make(HeroConf_Index_AttrMap) x.indexAttrMap1 = make(map[string]HeroConf_Index_AttrMap) for k1, v1 := range x.data.GetHeroMap() { - _ = k1 - for k2, v2 := range v1.GetAttrMap() { - _ = k2 + for _, v2 := range v1.GetAttrMap() { { // Index: Title key := v2.GetTitle() diff --git a/test/go-tableau-loader/protoconf/loader/hub.pc.go b/test/go-tableau-loader/protoconf/loader/hub.pc.go index ca63f9be..e3b5db11 100644 --- a/test/go-tableau-loader/protoconf/loader/hub.pc.go +++ b/test/go-tableau-loader/protoconf/loader/hub.pc.go @@ -211,12 +211,24 @@ func (h *Hub) GetLastLoadedTime() time.Time { return h.mc.Load().GetLastLoadedTime() } -func (h *Hub) GetHeroConf() *HeroConf { - return h.mc.Load().GetHeroConf() +func (h *Hub) GetItemConf() *ItemConf { + return h.mc.Load().GetItemConf() } -func (h *Hub) GetHeroBaseConf() *HeroBaseConf { - return h.mc.Load().GetHeroBaseConf() +func (h *Hub) GetActivityConf() *ActivityConf { + return h.mc.Load().GetActivityConf() +} + +func (h *Hub) GetChapterConf() *ChapterConf { + return h.mc.Load().GetChapterConf() +} + +func (h *Hub) GetThemeConf() *ThemeConf { + return h.mc.Load().GetThemeConf() +} + +func (h *Hub) GetTaskConf() *TaskConf { + return h.mc.Load().GetTaskConf() } func (h *Hub) GetFruitConf() *FruitConf { @@ -235,8 +247,16 @@ func (h *Hub) GetFruit4Conf() *Fruit4Conf { return h.mc.Load().GetFruit4Conf() } -func (h *Hub) GetItemConf() *ItemConf { - return h.mc.Load().GetItemConf() +func (h *Hub) GetFruit5Conf() *Fruit5Conf { + return h.mc.Load().GetFruit5Conf() +} + +func (h *Hub) GetHeroConf() *HeroConf { + return h.mc.Load().GetHeroConf() +} + +func (h *Hub) GetHeroBaseConf() *HeroBaseConf { + return h.mc.Load().GetHeroBaseConf() } func (h *Hub) GetPatchReplaceConf() *PatchReplaceConf { @@ -250,19 +270,3 @@ func (h *Hub) GetPatchMergeConf() *PatchMergeConf { func (h *Hub) GetRecursivePatchConf() *RecursivePatchConf { return h.mc.Load().GetRecursivePatchConf() } - -func (h *Hub) GetActivityConf() *ActivityConf { - return h.mc.Load().GetActivityConf() -} - -func (h *Hub) GetChapterConf() *ChapterConf { - return h.mc.Load().GetChapterConf() -} - -func (h *Hub) GetThemeConf() *ThemeConf { - return h.mc.Load().GetThemeConf() -} - -func (h *Hub) GetTaskConf() *TaskConf { - return h.mc.Load().GetTaskConf() -} 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 03ecab0a..0ea56050 100644 --- a/test/go-tableau-loader/protoconf/loader/index_conf.pc.go +++ b/test/go-tableau-loader/protoconf/loader/index_conf.pc.go @@ -96,9 +96,7 @@ func (x *FruitConf) processAfterLoad() error { x.orderedIndexItemMap = treemap.New[int32, []*protoconf.FruitConf_Fruit_Item]() x.orderedIndexItemMap1 = make(map[int32]*FruitConf_OrderedIndex_ItemMap) for k1, v1 := range x.data.GetFruitMap() { - _ = k1 - for k2, v2 := range v1.GetItemMap() { - _ = k2 + for _, v2 := range v1.GetItemMap() { { // OrderedIndex: Price key := v2.GetPrice() @@ -298,15 +296,13 @@ func (x *Fruit2Conf) processAfterLoad() error { x.indexAttrMap = make(Fruit2Conf_Index_AttrMap) x.indexAttrMap1 = make(map[int32]Fruit2Conf_Index_AttrMap) for k1, v1 := range x.data.GetFruitMap() { - _ = k1 for _, v2 := range v1.GetCountryList() { { // Index: CountryName key := v2.GetName() x.indexCountryMap[key] = append(x.indexCountryMap[key], v2) } - for k2, v3 := range v2.GetItemMap() { - _ = k2 + for _, v3 := range v2.GetItemMap() { for _, v4 := range v3.GetAttrList() { { // Index: CountryItemAttrName @@ -325,10 +321,8 @@ func (x *Fruit2Conf) processAfterLoad() error { x.orderedIndexItemMap = treemap.New[int32, []*protoconf.Fruit2Conf_Fruit_Country_Item]() x.orderedIndexItemMap1 = make(map[int32]*Fruit2Conf_OrderedIndex_ItemMap) for k1, v1 := range x.data.GetFruitMap() { - _ = k1 for _, v2 := range v1.GetCountryList() { - for k2, v3 := range v2.GetItemMap() { - _ = k2 + for _, v3 := range v2.GetItemMap() { { // OrderedIndex: CountryItemPrice key := v3.GetPrice() @@ -586,8 +580,7 @@ func (x *Fruit3Conf) processAfterLoad() error { key := v2.GetName() x.indexCountryMap[key] = append(x.indexCountryMap[key], v2) } - for k1, v3 := range v2.GetItemMap() { - _ = k1 + for _, v3 := range v2.GetItemMap() { for _, v4 := range v3.GetAttrList() { { // Index: CountryItemAttrName @@ -602,8 +595,7 @@ func (x *Fruit3Conf) processAfterLoad() error { x.orderedIndexItemMap = treemap.New[int32, []*protoconf.Fruit3Conf_Fruit_Country_Item]() for _, v1 := range x.data.GetFruitList() { for _, v2 := range v1.GetCountryList() { - for k1, v3 := range v2.GetItemMap() { - _ = k1 + for _, v3 := range v2.GetItemMap() { { // OrderedIndex: CountryItemPrice key := v3.GetPrice() @@ -796,9 +788,7 @@ func (x *Fruit4Conf) processAfterLoad() error { x.indexAttrMap1 = make(map[int32]Fruit4Conf_Index_AttrMap) x.indexAttrMap2 = make(map[Fruit4Conf_LevelIndex_Fruit_CountryKey]Fruit4Conf_Index_AttrMap) for k1, v1 := range x.data.GetFruitMap() { - _ = k1 for k2, v2 := range v1.GetCountryMap() { - _ = k2 { // Index: CountryName key := v2.GetName() @@ -808,8 +798,7 @@ func (x *Fruit4Conf) processAfterLoad() error { } x.indexCountryMap1[k1][key] = append(x.indexCountryMap1[k1][key], v2) } - for k3, v3 := range v2.GetItemMap() { - _ = k3 + for _, v3 := range v2.GetItemMap() { for _, v4 := range v3.GetAttrList() { { // Index: CountryItemAttrName @@ -834,11 +823,8 @@ func (x *Fruit4Conf) processAfterLoad() error { x.orderedIndexItemMap1 = make(map[int32]*Fruit4Conf_OrderedIndex_ItemMap) x.orderedIndexItemMap2 = make(map[Fruit4Conf_LevelIndex_Fruit_CountryKey]*Fruit4Conf_OrderedIndex_ItemMap) for k1, v1 := range x.data.GetFruitMap() { - _ = k1 for k2, v2 := range v1.GetCountryMap() { - _ = k2 - for k3, v3 := range v2.GetItemMap() { - _ = k3 + for _, v3 := range v2.GetItemMap() { { // OrderedIndex: CountryItemPrice key := v3.GetPrice() @@ -1120,6 +1106,186 @@ func (x *Fruit4Conf) FindFirstItem2(fruitType int32, id int32, price int32) *pro return nil } +// Index types. +// Index: CountryName +type Fruit5Conf_Index_CountryMap = map[string][]*protoconf.Fruit5Conf_Fruit_Country + +// Fruit5Conf is a wrapper around protobuf message: protoconf.Fruit5Conf. +// +// 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 Fruit5Conf struct { + UnimplementedMessager + data, originalData *protoconf.Fruit5Conf + indexCountryMap Fruit5Conf_Index_CountryMap + indexCountryMap1 map[int32]Fruit5Conf_Index_CountryMap +} + +// Name returns the Fruit5Conf's message name. +func (x *Fruit5Conf) Name() string { + return string((*protoconf.Fruit5Conf)(nil).ProtoReflect().Descriptor().Name()) +} + +// Data returns the Fruit5Conf's inner message data. +func (x *Fruit5Conf) Data() *protoconf.Fruit5Conf { + if x != nil { + return x.data + } + return nil +} + +// Load loads Fruit5Conf's content in the given dir, based on format and messager options. +func (x *Fruit5Conf) Load(dir string, format format.Format, opts *load.MessagerOptions) error { + start := time.Now() + defer func() { + x.Stats.Duration = time.Since(start) + }() + x.data = &protoconf.Fruit5Conf{} + err := load.LoadMessagerInDir(x.data, dir, format, opts) + if err != nil { + return err + } + if x.backup { + x.originalData = proto.Clone(x.data).(*protoconf.Fruit5Conf) + } + return x.processAfterLoad() +} + +// Store stores Fruit5Conf's content to file in the specified directory and format. +// Available formats: JSON, Bin, and Text. +func (x *Fruit5Conf) Store(dir string, format format.Format, options ...store.Option) error { + return store.Store(x.Data(), dir, format, options...) +} + +// Message returns the Fruit5Conf's inner message data. +func (x *Fruit5Conf) Message() proto.Message { + return x.Data() +} + +// Messager returns the current messager. +func (x *Fruit5Conf) Messager() Messager { + return x +} + +// originalMessage returns the Fruit5Conf's original inner message. +func (x *Fruit5Conf) originalMessage() proto.Message { + if x != nil { + return x.originalData + } + return nil +} + +// processAfterLoad runs after this messager is loaded. +func (x *Fruit5Conf) processAfterLoad() error { + // Index init. + x.indexCountryMap = make(Fruit5Conf_Index_CountryMap) + x.indexCountryMap1 = make(map[int32]Fruit5Conf_Index_CountryMap) + for k1, v1 := range x.data.GetFruitMap() { + for _, v2 := range v1.GetCountryMap() { + { + // Index: CountryName + key := v2.GetName() + x.indexCountryMap[key] = append(x.indexCountryMap[key], v2) + if x.indexCountryMap1[k1] == nil { + x.indexCountryMap1[k1] = make(Fruit5Conf_Index_CountryMap) + } + x.indexCountryMap1[k1][key] = append(x.indexCountryMap1[k1][key], v2) + } + } + } + return nil +} + +// Get1 finds value in the 1st-level map. It will return +// NotFound error if the key is not found. +func (x *Fruit5Conf) Get1(fruitType int32) (*protoconf.Fruit5Conf_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 + } +} + +// Get2 finds value in the 2nd-level map. It will return +// NotFound error if the key is not found. +func (x *Fruit5Conf) Get2(fruitType int32, id int32) (*protoconf.Fruit5Conf_Fruit_Country, error) { + conf, err := x.Get1(fruitType) + if err != nil { + return nil, err + } + d := conf.GetCountryMap() + if val, ok := d[id]; !ok { + return nil, fmt.Errorf("id(%v) %w", id, ErrNotFound) + } else { + return val, nil + } +} + +// Get3 finds value in the 3rd-level map. It will return +// NotFound error if the key is not found. +func (x *Fruit5Conf) Get3(fruitType int32, id int32, id3 int32) (*protoconf.Fruit5Conf_Fruit_Country_Item, error) { + conf, err := x.Get2(fruitType, id) + if err != nil { + return nil, err + } + d := conf.GetItemMap() + if val, ok := d[id3]; !ok { + return nil, fmt.Errorf("id3(%v) %w", id3, ErrNotFound) + } else { + return val, nil + } +} + +// Index: CountryName + +// FindCountryMap finds the index: key(CountryName) to value(protoconf.Fruit5Conf_Fruit_Country) map. +// One key may correspond to multiple values, which are represented by a slice. +func (x *Fruit5Conf) FindCountryMap() Fruit5Conf_Index_CountryMap { + return x.indexCountryMap +} + +// FindCountry finds a slice of all values of the given key(s). +func (x *Fruit5Conf) FindCountry(name string) []*protoconf.Fruit5Conf_Fruit_Country { + return x.indexCountryMap[name] +} + +// FindFirstCountry finds the first value of the given key(s), +// or nil if no value found. +func (x *Fruit5Conf) FindFirstCountry(name string) *protoconf.Fruit5Conf_Fruit_Country { + val := x.FindCountry(name) + if len(val) > 0 { + return val[0] + } + return nil +} + +// FindCountryMap1 finds the index: key(CountryName) to value(protoconf.Fruit5Conf_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 *Fruit5Conf) FindCountryMap1(fruitType int32) Fruit5Conf_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 *Fruit5Conf) FindCountry1(fruitType int32, name string) []*protoconf.Fruit5Conf_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 *Fruit5Conf) FindFirstCountry1(fruitType int32, name string) *protoconf.Fruit5Conf_Fruit_Country { + val := x.FindCountry1(fruitType, name) + if len(val) > 0 { + return val[0] + } + return nil +} + func init() { Register(func() Messager { return new(FruitConf) @@ -1133,4 +1299,7 @@ func init() { Register(func() Messager { return new(Fruit4Conf) }) + Register(func() Messager { + return new(Fruit5Conf) + }) } diff --git a/test/go-tableau-loader/protoconf/loader/item_conf.pc.go b/test/go-tableau-loader/protoconf/loader/item_conf.pc.go index d63807dc..86b117d5 100644 --- a/test/go-tableau-loader/protoconf/loader/item_conf.pc.go +++ b/test/go-tableau-loader/protoconf/loader/item_conf.pc.go @@ -179,8 +179,7 @@ func (x *ItemConf) processAfterLoad() error { x.indexItemPathNameMap = make(ItemConf_Index_ItemPathNameMap) x.indexItemPathFriendIdMap = make(ItemConf_Index_ItemPathFriendIDMap) x.indexUseEffectTypeMap = make(ItemConf_Index_UseEffectTypeMap) - for k1, v1 := range x.data.GetItemMap() { - _ = k1 + for _, v1 := range x.data.GetItemMap() { { // Index: Type key := v1.GetType() @@ -266,8 +265,7 @@ func (x *ItemConf) processAfterLoad() error { // OrderedIndex init. x.orderedIndexExtTypeMap = treemap.New[protoconf.FruitType, []*protoconf.ItemConf_Item]() x.orderedIndexParamExtTypeMap = treemap.New2[ItemConf_OrderedIndex_ParamExtTypeKey, []*protoconf.ItemConf_Item]() - for k1, v1 := range x.data.GetItemMap() { - _ = k1 + for _, v1 := range x.data.GetItemMap() { { // OrderedIndex: ExtType@ExtType for _, v2 := range v1.GetExtTypeList() { 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 e483a662..8ba64d0a 100644 --- a/test/go-tableau-loader/protoconf/loader/messager_container.pc.go +++ b/test/go-tableau-loader/protoconf/loader/messager_container.pc.go @@ -13,40 +13,42 @@ type MessagerContainer struct { messagerMap MessagerMap loadedTime time.Time // all messagers as fields for fast access - heroConf *HeroConf - heroBaseConf *HeroBaseConf + itemConf *ItemConf + activityConf *ActivityConf + chapterConf *ChapterConf + themeConf *ThemeConf + taskConf *TaskConf fruitConf *FruitConf fruit2Conf *Fruit2Conf fruit3Conf *Fruit3Conf fruit4Conf *Fruit4Conf - itemConf *ItemConf + fruit5Conf *Fruit5Conf + heroConf *HeroConf + heroBaseConf *HeroBaseConf patchReplaceConf *PatchReplaceConf patchMergeConf *PatchMergeConf recursivePatchConf *RecursivePatchConf - activityConf *ActivityConf - chapterConf *ChapterConf - themeConf *ThemeConf - taskConf *TaskConf } func newMessagerContainer(messagerMap MessagerMap) *MessagerContainer { return &MessagerContainer{ messagerMap: messagerMap, loadedTime: time.Now(), - heroConf: GetMessager[*HeroConf](messagerMap), - heroBaseConf: GetMessager[*HeroBaseConf](messagerMap), + itemConf: GetMessager[*ItemConf](messagerMap), + activityConf: GetMessager[*ActivityConf](messagerMap), + chapterConf: GetMessager[*ChapterConf](messagerMap), + themeConf: GetMessager[*ThemeConf](messagerMap), + taskConf: GetMessager[*TaskConf](messagerMap), fruitConf: GetMessager[*FruitConf](messagerMap), fruit2Conf: GetMessager[*Fruit2Conf](messagerMap), fruit3Conf: GetMessager[*Fruit3Conf](messagerMap), fruit4Conf: GetMessager[*Fruit4Conf](messagerMap), - itemConf: GetMessager[*ItemConf](messagerMap), + fruit5Conf: GetMessager[*Fruit5Conf](messagerMap), + heroConf: GetMessager[*HeroConf](messagerMap), + heroBaseConf: GetMessager[*HeroBaseConf](messagerMap), patchReplaceConf: GetMessager[*PatchReplaceConf](messagerMap), patchMergeConf: GetMessager[*PatchMergeConf](messagerMap), recursivePatchConf: GetMessager[*RecursivePatchConf](messagerMap), - activityConf: GetMessager[*ActivityConf](messagerMap), - chapterConf: GetMessager[*ChapterConf](messagerMap), - themeConf: GetMessager[*ThemeConf](messagerMap), - taskConf: GetMessager[*TaskConf](messagerMap), } } @@ -64,12 +66,24 @@ func (mc *MessagerContainer) GetLastLoadedTime() time.Time { // Auto-generated getters below -func (mc *MessagerContainer) GetHeroConf() *HeroConf { - return mc.heroConf +func (mc *MessagerContainer) GetItemConf() *ItemConf { + return mc.itemConf } -func (mc *MessagerContainer) GetHeroBaseConf() *HeroBaseConf { - return mc.heroBaseConf +func (mc *MessagerContainer) GetActivityConf() *ActivityConf { + return mc.activityConf +} + +func (mc *MessagerContainer) GetChapterConf() *ChapterConf { + return mc.chapterConf +} + +func (mc *MessagerContainer) GetThemeConf() *ThemeConf { + return mc.themeConf +} + +func (mc *MessagerContainer) GetTaskConf() *TaskConf { + return mc.taskConf } func (mc *MessagerContainer) GetFruitConf() *FruitConf { @@ -88,8 +102,16 @@ func (mc *MessagerContainer) GetFruit4Conf() *Fruit4Conf { return mc.fruit4Conf } -func (mc *MessagerContainer) GetItemConf() *ItemConf { - return mc.itemConf +func (mc *MessagerContainer) GetFruit5Conf() *Fruit5Conf { + return mc.fruit5Conf +} + +func (mc *MessagerContainer) GetHeroConf() *HeroConf { + return mc.heroConf +} + +func (mc *MessagerContainer) GetHeroBaseConf() *HeroBaseConf { + return mc.heroBaseConf } func (mc *MessagerContainer) GetPatchReplaceConf() *PatchReplaceConf { @@ -103,19 +125,3 @@ func (mc *MessagerContainer) GetPatchMergeConf() *PatchMergeConf { func (mc *MessagerContainer) GetRecursivePatchConf() *RecursivePatchConf { return mc.recursivePatchConf } - -func (mc *MessagerContainer) GetActivityConf() *ActivityConf { - return mc.activityConf -} - -func (mc *MessagerContainer) GetChapterConf() *ChapterConf { - return mc.chapterConf -} - -func (mc *MessagerContainer) GetThemeConf() *ThemeConf { - return mc.themeConf -} - -func (mc *MessagerContainer) GetTaskConf() *TaskConf { - return mc.taskConf -} 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 671f0ccd..9bcec3bd 100644 --- a/test/go-tableau-loader/protoconf/loader/test_conf.pc.go +++ b/test/go-tableau-loader/protoconf/loader/test_conf.pc.go @@ -167,14 +167,12 @@ func (x *ActivityConf) processAfterLoad() error { x.indexAwardMap1 = make(map[uint64]ActivityConf_Index_AwardMap) x.indexAwardMap2 = make(map[ActivityConf_LevelIndex_Activity_ChapterKey]ActivityConf_Index_AwardMap) for k1, v1 := range x.data.GetActivityMap() { - _ = k1 { // Index: ActivityName key := v1.GetActivityName() x.indexActivityMap[key] = append(x.indexActivityMap[key], v1) } for k2, v2 := range v1.GetChapterMap() { - _ = k2 { // Index: ChapterID key := v2.GetChapterId() @@ -193,8 +191,7 @@ func (x *ActivityConf) processAfterLoad() error { } x.indexNamedChapterMap1[k1][key] = append(x.indexNamedChapterMap1[k1][key], v2) } - for k3, v3 := range v2.GetSectionMap() { - _ = k3 + for _, v3 := range v2.GetSectionMap() { for _, v4 := range v3.GetSectionItemList() { { // Index: SectionItemID@Award @@ -788,8 +785,7 @@ func (x *TaskConf) originalMessage() proto.Message { func (x *TaskConf) processAfterLoad() error { // Index init. x.indexTaskMap = make(TaskConf_Index_TaskMap) - for k1, v1 := range x.data.GetTaskMap() { - _ = k1 + for _, v1 := range x.data.GetTaskMap() { { // Index: ActivityID key := v1.GetActivityId() @@ -813,8 +809,7 @@ func (x *TaskConf) processAfterLoad() error { x.orderedIndexTaskExpiryMap = treemap.New[int64, []*protoconf.TaskConf_Task]() x.orderedIndexSortedTaskExpiryMap = treemap.New[int64, []*protoconf.TaskConf_Task]() x.orderedIndexActivityExpiryMap = treemap.New2[TaskConf_OrderedIndex_ActivityExpiryKey, []*protoconf.TaskConf_Task]() - for k1, v1 := range x.data.GetTaskMap() { - _ = k1 + for _, v1 := range x.data.GetTaskMap() { { // OrderedIndex: Goal@OrderedTask key := v1.GetGoal() diff --git a/test/proto/index_conf.proto b/test/proto/index_conf.proto index 2ac16a4a..802364f8 100644 --- a/test/proto/index_conf.proto +++ b/test/proto/index_conf.proto @@ -116,4 +116,31 @@ message Fruit4Conf { } } } +} + +// Nesting: map -> map -> map +// 3-level map but indexes only at the 2nd level (MapDepth=2). +// The 3rd level map (MapDepth=3) has no index. +// This tests that when indexes exist only at the 2nd level (not the 3rd), +// no extra LevelIndex key structs are generated. +message Fruit5Conf { + option (tableau.worksheet) = { + name: "Fruit5Conf" + index: "CountryName" + }; + + map fruit_map = 1 [(tableau.field) = { key: "FruitType" layout: LAYOUT_VERTICAL }]; + message Fruit { + protoconf.FruitType fruit_type = 1 [(tableau.field) = { name: "FruitType" }]; + map country_map = 2 [(tableau.field) = { name: "Country" key: "ID" layout: LAYOUT_HORIZONTAL }]; + message Country { + int32 id = 1 [(tableau.field) = { name: "ID" }]; + string name = 2 [(tableau.field) = { name: "Name" }]; + map item_map = 3 [(tableau.field) = { name: "Item" key: "ID" layout: LAYOUT_HORIZONTAL }]; + message Item { + int32 id = 1 [(tableau.field) = { name: "ID" }]; + int32 price = 2 [(tableau.field) = { name: "Price" }]; + } + } + } } \ No newline at end of file diff --git a/test/testdata/conf/Fruit5Conf.json b/test/testdata/conf/Fruit5Conf.json new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/test/testdata/conf/Fruit5Conf.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/third_party/_submodules/tableau b/third_party/_submodules/tableau index fe374083..0d308e52 160000 --- a/third_party/_submodules/tableau +++ b/third_party/_submodules/tableau @@ -1 +1 @@ -Subproject commit fe374083bb58a917c1937f3d602585ad3787da4d +Subproject commit 0d308e52f7ca68ae770d7da40598ef84c7136fb3