Repository: tableauio/tableau
Pull request #248 (2025-05-22 13:51:15)

wellknown: add new wellknown type `version`
- close #247 

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/tableauio/tableau/pull/248?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=tableauio) Report
Attention: Patch coverage is `63.23529%` with `25 lines` in your changes missing coverage. Please review.
> Project coverage is 70.94%. Comparing base [(`180291d`)](https://app.codecov.io/gh/tableauio/tableau/commit/180291da18532e006c4295a679cbedd16db24692?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=tableauio) to head [(`39528d3`)](https://app.codecov.io/gh/tableauio/tableau/commit/39528d39733357dcd2a0719e17916e7b12ef18aa?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=tableauio).

| [Files with missing lines](https://app.codecov.io/gh/tableauio/tableau/pull/248?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=tableauio) | Patch % | Lines |
|---|---|---|
| [internal/x/xproto/value.go](https://app.codecov.io/gh/tableauio/tableau/pull/248?src=pr&el=tree&filepath=internal%2Fx%2Fxproto%2Fvalue.go&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=tableauio#diff-aW50ZXJuYWwveC94cHJvdG8vdmFsdWUuZ28=) | 68.08% | [10 Missing and 5 partials :warning: ](https://app.codecov.io/gh/tableauio/tableau/pull/248?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=tableauio) |
| [xerrors/ecode.go](https://app.codecov.io/gh/tableauio/tableau/pull/248?src=pr&el=tree&filepath=xerrors%2Fecode.go&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=tableauio#diff-eGVycm9ycy9lY29kZS5nbw==) | 0.00% | [10 Missing :warning: ](https://app.codecov.io/gh/tableauio/tableau/pull/248?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=tableauio) |

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##           master     #248      +/-   ##
==========================================
- Coverage   71.00%   70.94%   -0.06%     
==========================================
  Files          80       80              
  Lines       10184    10250      +66     
==========================================
+ Hits         7231     7272      +41     
- Misses       2388     2408      +20     
- Partials      565      570       +5     
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/tableauio/tableau/pull/248?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=tableauio).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=tableauio).

<details><summary> :rocket: New features to boost your workflow: </summary>

- :snowflake: [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
</details>

Patch:
diff --git a/internal/confgen/parser.go b/internal/confgen/parser.go
index ba8cfbe..cc9ad67 100644
--- a/internal/confgen/parser.go
+++ b/internal/confgen/parser.go
@@ -859,7 +859,7 @@ func (sp *sheetParser) parseIncellUnion(structValue protoreflect.Value, cellData
 //   - Enum types
 //   - Well-known types
 func (sp *sheetParser) parseFieldValue(fd protoreflect.FieldDescriptor, rawValue string, fprop *tableaupb.FieldProp) (v protoreflect.Value, present bool, err error) {
-	v, present, err = xproto.ParseFieldValue(fd, rawValue, sp.LocationName)
+	v, present, err = xproto.ParseFieldValue(fd, rawValue, sp.LocationName, fprop)
 	if err != nil {
 		return v, present, err
 	}
diff --git a/internal/localizer/i18n/config/ecode/en.yaml b/internal/localizer/i18n/config/ecode/en.yaml
index 662f64a..00829a0 100644
--- a/internal/localizer/i18n/config/ecode/en.yaml
+++ b/internal/localizer/i18n/config/ecode/en.yaml
@@ -108,6 +108,14 @@ E2023:
   desc: KeyedList key not unique
   text: KeyedList key "{{.Key}}" duplicates
   help: ensure KeyedList's key is unique
+E2024:
+  desc: invalid version pattern
+  text: '"{{.Value}}" cannot be parsed into dotted-decimal format, {{.Error}}'
+  help: "ensure version pattern is: <MAJOR>.<MINOR>.<PATCH>[.<OTHER>]..."
+E2025:
+  desc: version value mismatches pattern
+  text: 'version "{{.Value}}" mismatches dotted-decimal pattern "{{.Pattern}}"'
+  help: "ensure the version value match the pattern and all decimal values are valid"
 # [3000, 3999]: importer error
 E3000:
   desc: no workbook file found about sheet specifier
diff --git a/internal/localizer/i18n/config/ecode/zh.yaml b/internal/localizer/i18n/config/ecode/zh.yaml
index 020dc0c..1ebb82b 100644
--- a/internal/localizer/i18n/config/ecode/zh.yaml
+++ b/internal/localizer/i18n/config/ecode/zh.yaml
@@ -108,6 +108,14 @@ E2023:
   desc: KeyedList key not unique
   text: KeyedList 的 key "{{.Key}}" 已存在
   help: 确保 KeyedList 的 key 唯一, 不允许重复配置相同 key
+E2024:
+  desc: invalid version pattern
+  text: '"{{.Value}}" 无法以点分十进制解析, {{.Error}}'
+  help: "确保版本号格式为: <MAJOR>.<MINOR>.<PATCH>[.<OTHER>]..."
+E2025:
+  desc: version value mismatches pattern
+  text: '版本号 "{{.Value}}" 与点分十进制格式 "{{.Pattern}}" 不匹配'
+  help: "确保输入的版本号与版本号格式匹配, 且每个十进制数值均合法"
 # [3000, 3999]: importer error
 E3000:
   desc: no workbook file found about sheet specifier
diff --git a/internal/protogen/field_prop.go b/internal/protogen/field_prop.go
index 7368bbe..360cd85 100644
--- a/internal/protogen/field_prop.go
+++ b/internal/protogen/field_prop.go
@@ -97,6 +97,7 @@ func ExtractScalarFieldProp(prop *tableaupb.FieldProp) *tableaupb.FieldProp {
 		Present:  prop.Present,
 		Optional: prop.Optional,
 		Patch:    prop.Patch,
+		Pattern:  prop.Pattern,
 	}
 	if IsEmptyFieldProp(p) {
 		return nil
diff --git a/internal/types/types.go b/internal/types/types.go
index bd101d5..694720f 100644
--- a/internal/types/types.go
+++ b/internal/types/types.go
@@ -361,6 +361,7 @@ func init() {
 		WellKnownMessageDuration:   ScalarKind,
 		WellKnownMessageFraction:   ScalarKind,
 		WellKnownMessageComparator: ScalarKind,
+		WellKnownMessageVersion:    ScalarKind,
 
 		// "enum":     EnumKind,
 		// "repeated": ListKind,
@@ -413,6 +414,13 @@ func ParseTypeDescriptor(rawType string) *Descriptor {
 			Predefined: true,
 			Kind:       ScalarKind,
 		}
+	case "version":
+		return &Descriptor{
+			Name:       WellKnownMessageVersion,
+			FullName:   WellKnownMessageVersion,
+			Predefined: true,
+			Kind:       ScalarKind,
+		}
 	default:
 		desc := &Descriptor{
 			Name:       rawType,
diff --git a/internal/types/wellknown.go b/internal/types/wellknown.go
index a251a1d..83d042a 100644
--- a/internal/types/wellknown.go
+++ b/internal/types/wellknown.go
@@ -7,6 +7,7 @@ const (
 	WellKnownMessageDuration   = "google.protobuf.Duration"
 	WellKnownMessageFraction   = "tableau.Fraction"
 	WellKnownMessageComparator = "tableau.Comparator"
+	WellKnownMessageVersion    = "tableau.Version"
 )
 
 var wellKnownMessages map[string]string
@@ -17,6 +18,7 @@ func init() {
 		WellKnownMessageDuration:   "google/protobuf/duration.proto",
 		WellKnownMessageFraction:   "tableau/protobuf/wellknown.proto",
 		WellKnownMessageComparator: "tableau/protobuf/wellknown.proto",
+		WellKnownMessageVersion:    "tableau/protobuf/wellknown.proto",
 	}
 }
 
diff --git a/internal/x/xproto/value.go b/internal/x/xproto/value.go
index d6b7528..c550204 100644
--- a/internal/x/xproto/value.go
+++ b/internal/x/xproto/value.go
@@ -5,10 +5,12 @@ import (
 	"math"
 	"strconv"
 	"strings"
+	"sync"
 	"time"
 	"unicode"
 
 	"github.com/tableauio/tableau/internal/types"
+	"github.com/tableauio/tableau/options"
 	"github.com/tableauio/tableau/proto/tableaupb"
 	"github.com/tableauio/tableau/xerrors"
 	"google.golang.org/protobuf/proto"
@@ -34,6 +36,7 @@ var DefaultTimestampValue pref.Value
 var DefaultDurationValue pref.Value
 var DefaultFractionValue pref.Value
 var DefaultComparatorValue pref.Value
+var DefaultVersionValue pref.Value
 
 func init() {
 	DefaultBoolValue = pref.ValueOfBool(false)
@@ -69,7 +72,7 @@ func init() {
 // # Well-known types
 //
 // Well-known types are message types defined by [types.IsWellKnownMessage].
-func ParseFieldValue(fd pref.FieldDescriptor, rawValue string, locationName string) (v pref.Value, present bool, err error) {
+func ParseFieldValue(fd pref.FieldDescriptor, rawValue string, locationName string, fprop *tableaupb.FieldProp) (v pref.Value, present bool, err error) {
 	purifyInteger := func(s string) string {
 		// trim integer boring suffix matched by regexp `.0*$`
 		if matches := types.MatchBoringInteger(s); matches != nil {
@@ -260,6 +263,11 @@ func ParseFieldValue(fd pref.FieldDescriptor, rawValue string, locationName stri
 				return DefaultComparatorValue, false, nil
 			}
 			return parseComparator(fd.Message(), value)
+		case types.WellKnownMessageVersion:
+			if value == "" {
+				return DefaultVersionValue, false, nil
+			}
+			return parseVersion(fd.Message(), value, fprop.GetPattern())
 		default:
 			return pref.Value{}, false, xerrors.Errorf("not supported message type: %s", msgName)
 		}
@@ -460,6 +468,106 @@ func parseComparator(md pref.MessageDescriptor, value string) (v pref.Value, pre
 	return pref.ValueOfMessage(msg.ProtoReflect()), true, nil
 }
 
+func parseVersion(md pref.MessageDescriptor, value string, pattern string) (v pref.Value, present bool, err error) {
+	if pattern == "" {
+		pattern = options.DefaultVersionPattern
+	}
+	// pattern
+	patternSlice, err := patternToSlice(pattern)
+	if err != nil {
+		return DefaultVersionValue, false, err
+	}
+	// value
+	versionStrSlice := strings.Split(value, ".")
+	if len(versionStrSlice) != len(patternSlice) {
+		return DefaultVersionValue, false, xerrors.E2025(value, pattern)
+	}
+	versionSlice := make([]uint32, 0, len(versionStrSlice))
+	for i, s := range versionStrSlice {
+		d, err := strconv.ParseUint(s, 10, 32)
+		if err != nil {
+			return DefaultVersionValue, false, xerrors.E2024(value, err)
+		}
+		if uint32(d) > patternSlice[i] {
+			return DefaultVersionValue, false, xerrors.E2025(value, pattern)
+		}
+		versionSlice = append(versionSlice, uint32(d))
+	}
+
+	msg := dynamicpb.NewMessage(md)
+	// string form
+	strFD := md.Fields().ByName("str")
+	msg.Set(strFD, pref.ValueOfString(value))
+	// integer form
+	valFD := md.Fields().ByName("val")
+	versionVal := uint64(0)
+	patternSlice = append(patternSlice, 0)
+	for i, v := range versionSlice {
+		versionVal += uint64(v)
+		if i != len(versionSlice)-1 {
+			versionVal *= uint64(patternSlice[i+1]) + 1
+		}
+	}
+	msg.Set(valFD, pref.ValueOfUint64(versionVal))
+	// major.minor.patch.others
+	majorFD := md.Fields().ByName("major")
+	msg.Set(majorFD, pref.ValueOfUint32(versionSlice[0]))
+	if len(versionSlice) > 1 {
+		minorFD := md.Fields().ByName("minor")
+		msg.Set(minorFD, pref.ValueOfUint32(versionSlice[1]))
+	}
+	if len(versionSlice) > 2 {
+		patchFD := md.Fields().ByName("patch")
+		msg.Set(patchFD, pref.ValueOfUint32(versionSlice[2]))
+	}
+	if len(versionSlice) > 3 {
+		patchFD := md.Fields().ByName("others")
+		for i := 3; i < len(versionSlice); i++ {
+			msg.Mutable(patchFD).List().Append(pref.ValueOfUint32(versionSlice[i]))
+		}
+	}
+	return pref.ValueOfMessage(msg.ProtoReflect()), true, nil
+}
+
+var versionPatterns = &versionPatternCache{
+	cache: map[string][]uint32{},
+}
+
+type versionPatternCache struct {
+	sync.RWMutex
+	cache map[string][]uint32 // pattern str -> pattern slice
+}
+
+func patternToSlice(pattern string) ([]uint32, error) {
+	versionPatterns.RLock()
+	v, ok := versionPatterns.cache[pattern]
+	versionPatterns.RUnlock()
+	if ok {
+		return v, nil
+	}
+	versionPatterns.Lock()
+	defer versionPatterns.Unlock()
+	patternStrSlice := strings.Split(pattern, ".")
+	patternSlice := make([]uint32, 0, len(patternStrSlice))
+	for _, s := range patternStrSlice {
+		d, err := strconv.ParseUint(s, 10, 32)
+		if err != nil {
+			return nil, xerrors.E2024(pattern, err)
+		}
+		patternSlice = append(patternSlice, uint32(d))
+	}
+	product := uint64(1)
+	for _, v := range patternSlice {
+		multiplier := uint64(v) + 1
+		if product > math.MaxUint64/multiplier {
+			return nil, xerrors.E2024(pattern, xerrors.Errorf("product of all pattern decimals overflow uint64"))
+		}
+		product *= multiplier
+	}
+	versionPatterns.cache[pattern] = patternSlice
+	return patternSlice, nil
+}
+
 func findFirstDigitOrSignIndex(s string) (int, error) {
 	for i, char := range s {
 		if unicode.IsDigit(char) || char == '-' || char == '+' {
diff --git a/options/options.go b/options/options.go
index 5b575c3..9ef352b 100644
--- a/options/options.go
+++ b/options/options.go
@@ -341,6 +341,10 @@ const (
 	DefaultSubsep = ":"
 )
 
+const (
+	DefaultVersionPattern = "255.255.255"
+)
+
 // Option is the functional option type.
 type Option func(*Options)
 
diff --git a/proto/tableau/protobuf/tableau.proto b/proto/tableau/protobuf/tableau.proto
index 72344b9..04a6288 100644
--- a/proto/tableau/protobuf/tableau.proto
+++ b/proto/tableau/protobuf/tableau.proto
@@ -334,6 +334,10 @@ message FieldProp {
   //
   // TODO: use cases for more composite types.
   int32 cross = 15;
+  // Specify the dotted-decimal pattern of current cell. Each decimal
+  // number ranges from 0 to the corresponding part (MAX) of pattern.
+  // Default pattern: "255.255.255".
+  string pattern = 16;
 }
 
 // Layout of list and map.
diff --git a/proto/tableau/protobuf/wellknown.proto b/proto/tableau/protobuf/wellknown.proto
index f637398..bea1a4d 100644
--- a/proto/tableau/protobuf/wellknown.proto
+++ b/proto/tableau/protobuf/wellknown.proto
@@ -94,3 +94,22 @@ message Vector4i {
   int32 z = 3 [(tableau.field) = { name: "Z" }];
   int32 w = 4 [(tableau.field) = { name: "W" }];
 }
+
+// Version number in [dot-decimal notation](https://en.wikipedia.org/wiki/Dot-decimal_notation).
+// The version form is "<MAJOR>.<MINOR>.<PATCH>[.<OTHER>]...".
+//
+// You can specify the version pattern as "<MAJOR_MAX>.<MINOR_MAX>.<PATCH_MAX>[.<OTHER_MAX>]...".
+//  - Each part with suffix "MAX" represents the max decimal value of each part in the dot-decimal notation.
+//  - Each part "XXX_MAX+1" represents the part's value occupying in an integer.
+//  - For example, the formula for general pattern "<MAJOR_MAX>.<MINOR_MAX>.<PATCH_MAX>" is: 
+//    MAJOR*(MINOR_MAX+1)*(PATCH_MAX+1) + MINOR*(PATCH_MAX+1) + PATCH
+// 
+// Default pattern: "255.255.255".
+message Version {
+  string str = 1; // Version in string form.
+  uint64 val = 2; // Version in integer form.
+  uint32 major = 3; // Major version number.
+  uint32 minor = 4; // Minor version number.
+  uint32 patch = 5; // Patch version number.
+  repeated uint32 others = 6; // Other version numbers, such as build number, resource version, and so on.
+}
\ No newline at end of file
diff --git a/proto/tableaupb/tableau.pb.go b/proto/tableaupb/tableau.pb.go
index 45c77ae..47c0546 100644
--- a/proto/tableaupb/tableau.pb.go
+++ b/proto/tableaupb/tableau.pb.go
@@ -1280,6 +1280,10 @@ type FieldProp struct {
 	//
 	// TODO: use cases for more composite types.
 	Cross int32 `protobuf:"varint,15,opt,name=cross,proto3" json:"cross,omitempty"`
+	// Specify the dotted-decimal pattern of current cell. Each decimal
+	// number ranges from 0 to the corresponding part (MAX) of pattern.
+	// Default pattern: "255.255.255".
+	Pattern string `protobuf:"bytes,16,opt,name=pattern,proto3" json:"pattern,omitempty"`
 }
 
 func (x *FieldProp) Reset() {
@@ -1419,6 +1423,13 @@ func (x *FieldProp) GetCross() int32 {
 	return 0
 }
 
+func (x *FieldProp) GetPattern() string {
+	if x != nil {
+		return x.Pattern
+	}
+	return ""
+}
+
 var file_tableau_protobuf_tableau_proto_extTypes = []protoimpl.ExtensionInfo{
 	{
 		ExtendedType:  (*descriptorpb.FileOptions)(nil),
@@ -1645,7 +1656,7 @@ var file_tableau_protobuf_tableau_proto_rawDesc = []byte{
 	0x61, 0x75, 0x2e, 0x53, 0x70, 0x61, 0x6e, 0x52, 0x04, 0x73, 0x70, 0x61, 0x6e, 0x12, 0x26, 0x0a,
 	0x04, 0x70, 0x72, 0x6f, 0x70, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x74, 0x61,
 	0x62, 0x6c, 0x65, 0x61, 0x75, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x50, 0x72, 0x6f, 0x70, 0x52,
-	0x04, 0x70, 0x72, 0x6f, 0x70, 0x22, 0xad, 0x03, 0x0a, 0x09, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x50,
+	0x04, 0x70, 0x72, 0x6f, 0x70, 0x22, 0xc7, 0x03, 0x0a, 0x09, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x50,
 	0x72, 0x6f, 0x70, 0x12, 0x14, 0x0a, 0x05, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01,
 	0x28, 0x09, 0x52, 0x05, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x1b, 0x0a, 0x06, 0x75, 0x6e, 0x69,
 	0x71, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x48, 0x00, 0x52, 0x06, 0x75, 0x6e, 0x69,
@@ -1670,88 +1681,90 @@ var file_tableau_protobuf_tableau_proto_rawDesc = []byte{
 	0x03, 0x73, 0x65, 0x70, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x73, 0x65, 0x70, 0x12,
 	0x16, 0x0a, 0x06, 0x73, 0x75, 0x62, 0x73, 0x65, 0x70, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x09, 0x52,
 	0x06, 0x73, 0x75, 0x62, 0x73, 0x65, 0x70, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x72, 0x6f, 0x73, 0x73,
-	0x18, 0x0f, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x63, 0x72, 0x6f, 0x73, 0x73, 0x42, 0x09, 0x0a,
-	0x07, 0x5f, 0x75, 0x6e, 0x69, 0x71, 0x75, 0x65, 0x42, 0x0b, 0x0a, 0x09, 0x5f, 0x73, 0x65, 0x71,
-	0x75, 0x65, 0x6e, 0x63, 0x65, 0x2a, 0x5b, 0x0a, 0x06, 0x4c, 0x61, 0x79, 0x6f, 0x75, 0x74, 0x12,
-	0x12, 0x0a, 0x0e, 0x4c, 0x41, 0x59, 0x4f, 0x55, 0x54, 0x5f, 0x44, 0x45, 0x46, 0x41, 0x55, 0x4c,
-	0x54, 0x10, 0x00, 0x12, 0x13, 0x0a, 0x0f, 0x4c, 0x41, 0x59, 0x4f, 0x55, 0x54, 0x5f, 0x56, 0x45,
-	0x52, 0x54, 0x49, 0x43, 0x41, 0x4c, 0x10, 0x01, 0x12, 0x15, 0x0a, 0x11, 0x4c, 0x41, 0x59, 0x4f,
-	0x55, 0x54, 0x5f, 0x48, 0x4f, 0x52, 0x49, 0x5a, 0x4f, 0x4e, 0x54, 0x41, 0x4c, 0x10, 0x02, 0x12,
-	0x11, 0x0a, 0x0d, 0x4c, 0x41, 0x59, 0x4f, 0x55, 0x54, 0x5f, 0x49, 0x4e, 0x43, 0x45, 0x4c, 0x4c,
-	0x10, 0x03, 0x2a, 0x42, 0x0a, 0x04, 0x53, 0x70, 0x61, 0x6e, 0x12, 0x10, 0x0a, 0x0c, 0x53, 0x50,
-	0x41, 0x4e, 0x5f, 0x44, 0x45, 0x46, 0x41, 0x55, 0x4c, 0x54, 0x10, 0x00, 0x12, 0x13, 0x0a, 0x0f,
-	0x53, 0x50, 0x41, 0x4e, 0x5f, 0x43, 0x52, 0x4f, 0x53, 0x53, 0x5f, 0x43, 0x45, 0x4c, 0x4c, 0x10,
-	0x01, 0x12, 0x13, 0x0a, 0x0f, 0x53, 0x50, 0x41, 0x4e, 0x5f, 0x49, 0x4e, 0x4e, 0x45, 0x52, 0x5f,
-	0x43, 0x45, 0x4c, 0x4c, 0x10, 0x02, 0x2a, 0xcb, 0x01, 0x0a, 0x04, 0x4d, 0x6f, 0x64, 0x65, 0x12,
-	0x10, 0x0a, 0x0c, 0x4d, 0x4f, 0x44, 0x45, 0x5f, 0x44, 0x45, 0x46, 0x41, 0x55, 0x4c, 0x54, 0x10,
-	0x00, 0x12, 0x12, 0x0a, 0x0e, 0x4d, 0x4f, 0x44, 0x45, 0x5f, 0x45, 0x4e, 0x55, 0x4d, 0x5f, 0x54,
-	0x59, 0x50, 0x45, 0x10, 0x01, 0x12, 0x18, 0x0a, 0x14, 0x4d, 0x4f, 0x44, 0x45, 0x5f, 0x45, 0x4e,
-	0x55, 0x4d, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x4d, 0x55, 0x4c, 0x54, 0x49, 0x10, 0x02, 0x12,
-	0x14, 0x0a, 0x10, 0x4d, 0x4f, 0x44, 0x45, 0x5f, 0x53, 0x54, 0x52, 0x55, 0x43, 0x54, 0x5f, 0x54,
-	0x59, 0x50, 0x45, 0x10, 0x03, 0x12, 0x1a, 0x0a, 0x16, 0x4d, 0x4f, 0x44, 0x45, 0x5f, 0x53, 0x54,
-	0x52, 0x55, 0x43, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x4d, 0x55, 0x4c, 0x54, 0x49, 0x10,
-	0x04, 0x12, 0x13, 0x0a, 0x0f, 0x4d, 0x4f, 0x44, 0x45, 0x5f, 0x55, 0x4e, 0x49, 0x4f, 0x4e, 0x5f,
-	0x54, 0x59, 0x50, 0x45, 0x10, 0x05, 0x12, 0x19, 0x0a, 0x15, 0x4d, 0x4f, 0x44, 0x45, 0x5f, 0x55,
-	0x4e, 0x49, 0x4f, 0x4e, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x4d, 0x55, 0x4c, 0x54, 0x49, 0x10,
-	0x06, 0x12, 0x0f, 0x0a, 0x0b, 0x4d, 0x4f, 0x44, 0x45, 0x5f, 0x55, 0x45, 0x5f, 0x43, 0x53, 0x56,
-	0x10, 0x0a, 0x12, 0x10, 0x0a, 0x0c, 0x4d, 0x4f, 0x44, 0x45, 0x5f, 0x55, 0x45, 0x5f, 0x4a, 0x53,
-	0x4f, 0x4e, 0x10, 0x0b, 0x2a, 0x36, 0x0a, 0x04, 0x46, 0x6f, 0x72, 0x6d, 0x12, 0x10, 0x0a, 0x0c,
-	0x46, 0x4f, 0x52, 0x4d, 0x5f, 0x44, 0x45, 0x46, 0x41, 0x55, 0x4c, 0x54, 0x10, 0x00, 0x12, 0x0d,
-	0x0a, 0x09, 0x46, 0x4f, 0x52, 0x4d, 0x5f, 0x54, 0x45, 0x58, 0x54, 0x10, 0x01, 0x12, 0x0d, 0x0a,
-	0x09, 0x46, 0x4f, 0x52, 0x4d, 0x5f, 0x4a, 0x53, 0x4f, 0x4e, 0x10, 0x02, 0x2a, 0x3b, 0x0a, 0x05,
-	0x50, 0x61, 0x74, 0x63, 0x68, 0x12, 0x0e, 0x0a, 0x0a, 0x50, 0x41, 0x54, 0x43, 0x48, 0x5f, 0x4e,
-	0x4f, 0x4e, 0x45, 0x10, 0x00, 0x12, 0x11, 0x0a, 0x0d, 0x50, 0x41, 0x54, 0x43, 0x48, 0x5f, 0x52,
-	0x45, 0x50, 0x4c, 0x41, 0x43, 0x45, 0x10, 0x01, 0x12, 0x0f, 0x0a, 0x0b, 0x50, 0x41, 0x54, 0x43,
-	0x48, 0x5f, 0x4d, 0x45, 0x52, 0x47, 0x45, 0x10, 0x02, 0x3a, 0x54, 0x0a, 0x08, 0x77, 0x6f, 0x72,
-	0x6b, 0x62, 0x6f, 0x6f, 0x6b, 0x12, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70,
-	0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, 0x6c, 0x65, 0x4f, 0x70, 0x74, 0x69,
-	0x6f, 0x6e, 0x73, 0x18, 0xd0, 0x86, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x74, 0x61,
-	0x62, 0x6c, 0x65, 0x61, 0x75, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x62, 0x6f, 0x6f, 0x6b, 0x4f, 0x70,
-	0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x08, 0x77, 0x6f, 0x72, 0x6b, 0x62, 0x6f, 0x6f, 0x6b, 0x3a,
-	0x5a, 0x0a, 0x09, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x68, 0x65, 0x65, 0x74, 0x12, 0x1f, 0x2e, 0x67,
-	0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x4d,
-	0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xd0, 0x86,
-	0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x61, 0x75, 0x2e,
-	0x57, 0x6f, 0x72, 0x6b, 0x73, 0x68, 0x65, 0x65, 0x74, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73,
-	0x52, 0x09, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x68, 0x65, 0x65, 0x74, 0x3a, 0x51, 0x0a, 0x06, 0x73,
-	0x74, 0x72, 0x75, 0x63, 0x74, 0x12, 0x1f, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70,
-	0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x4f,
-	0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xd1, 0x86, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16,
-	0x2e, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x61, 0x75, 0x2e, 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, 0x4f,
-	0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x06, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x3a, 0x4e,
-	0x0a, 0x05, 0x75, 0x6e, 0x69, 0x6f, 0x6e, 0x12, 0x1f, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65,
-	0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67,
-	0x65, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xd2, 0x86, 0x03, 0x20, 0x01, 0x28, 0x0b,
-	0x32, 0x15, 0x2e, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x61, 0x75, 0x2e, 0x55, 0x6e, 0x69, 0x6f, 0x6e,
-	0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x05, 0x75, 0x6e, 0x69, 0x6f, 0x6e, 0x3a, 0x4c,
-	0x0a, 0x05, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x12, 0x1d, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65,
-	0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x4f,
-	0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xd0, 0x86, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15,
-	0x2e, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x61, 0x75, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x4f, 0x70,
-	0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x05, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x3a, 0x4a, 0x0a, 0x05,
-	0x65, 0x74, 0x79, 0x70, 0x65, 0x12, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70,
-	0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6e, 0x75, 0x6d, 0x4f, 0x70, 0x74, 0x69,
-	0x6f, 0x6e, 0x73, 0x18, 0xd0, 0x86, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x74, 0x61,
-	0x62, 0x6c, 0x65, 0x61, 0x75, 0x2e, 0x45, 0x6e, 0x75, 0x6d, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e,
-	0x73, 0x52, 0x05, 0x65, 0x74, 0x79, 0x70, 0x65, 0x3a, 0x56, 0x0a, 0x06, 0x65, 0x76, 0x61, 0x6c,
-	0x75, 0x65, 0x12, 0x21, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74,
-	0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6e, 0x75, 0x6d, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x4f, 0x70,
-	0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xd0, 0x86, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e,
-	0x74, 0x61, 0x62, 0x6c, 0x65, 0x61, 0x75, 0x2e, 0x45, 0x6e, 0x75, 0x6d, 0x56, 0x61, 0x6c, 0x75,
-	0x65, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x06, 0x65, 0x76, 0x61, 0x6c, 0x75, 0x65,
-	0x3a, 0x4f, 0x0a, 0x05, 0x6f, 0x6e, 0x65, 0x6f, 0x66, 0x12, 0x1d, 0x2e, 0x67, 0x6f, 0x6f, 0x67,
-	0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x4f, 0x6e, 0x65, 0x6f,
-	0x66, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xd0, 0x86, 0x03, 0x20, 0x01, 0x28, 0x0b,
-	0x32, 0x15, 0x2e, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x61, 0x75, 0x2e, 0x4f, 0x6e, 0x65, 0x6f, 0x66,
-	0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x05, 0x6f, 0x6e, 0x65, 0x6f, 0x66, 0x88, 0x01,
-	0x01, 0x42, 0x75, 0x0a, 0x14, 0x63, 0x6f, 0x6d, 0x2e, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x61, 0x75,
-	0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x42, 0x0c, 0x54, 0x61, 0x62, 0x6c, 0x65,
-	0x61, 0x75, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x2c, 0x67, 0x69, 0x74, 0x68, 0x75,
-	0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x61, 0x75, 0x69, 0x6f, 0x2f,
-	0x74, 0x61, 0x62, 0x6c, 0x65, 0x61, 0x75, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x74, 0x61,
-	0x62, 0x6c, 0x65, 0x61, 0x75, 0x70, 0x62, 0xa2, 0x02, 0x03, 0x54, 0x50, 0x42, 0xaa, 0x02, 0x18,
-	0x54, 0x61, 0x62, 0x6c, 0x65, 0x61, 0x75, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66,
-	0x2e, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x61, 0x75, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
+	0x18, 0x0f, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x63, 0x72, 0x6f, 0x73, 0x73, 0x12, 0x18, 0x0a,
+	0x07, 0x70, 0x61, 0x74, 0x74, 0x65, 0x72, 0x6e, 0x18, 0x10, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07,
+	0x70, 0x61, 0x74, 0x74, 0x65, 0x72, 0x6e, 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x75, 0x6e, 0x69, 0x71,
+	0x75, 0x65, 0x42, 0x0b, 0x0a, 0x09, 0x5f, 0x73, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x65, 0x2a,
+	0x5b, 0x0a, 0x06, 0x4c, 0x61, 0x79, 0x6f, 0x75, 0x74, 0x12, 0x12, 0x0a, 0x0e, 0x4c, 0x41, 0x59,
+	0x4f, 0x55, 0x54, 0x5f, 0x44, 0x45, 0x46, 0x41, 0x55, 0x4c, 0x54, 0x10, 0x00, 0x12, 0x13, 0x0a,
+	0x0f, 0x4c, 0x41, 0x59, 0x4f, 0x55, 0x54, 0x5f, 0x56, 0x45, 0x52, 0x54, 0x49, 0x43, 0x41, 0x4c,
+	0x10, 0x01, 0x12, 0x15, 0x0a, 0x11, 0x4c, 0x41, 0x59, 0x4f, 0x55, 0x54, 0x5f, 0x48, 0x4f, 0x52,
+	0x49, 0x5a, 0x4f, 0x4e, 0x54, 0x41, 0x4c, 0x10, 0x02, 0x12, 0x11, 0x0a, 0x0d, 0x4c, 0x41, 0x59,
+	0x4f, 0x55, 0x54, 0x5f, 0x49, 0x4e, 0x43, 0x45, 0x4c, 0x4c, 0x10, 0x03, 0x2a, 0x42, 0x0a, 0x04,
+	0x53, 0x70, 0x61, 0x6e, 0x12, 0x10, 0x0a, 0x0c, 0x53, 0x50, 0x41, 0x4e, 0x5f, 0x44, 0x45, 0x46,
+	0x41, 0x55, 0x4c, 0x54, 0x10, 0x00, 0x12, 0x13, 0x0a, 0x0f, 0x53, 0x50, 0x41, 0x4e, 0x5f, 0x43,
+	0x52, 0x4f, 0x53, 0x53, 0x5f, 0x43, 0x45, 0x4c, 0x4c, 0x10, 0x01, 0x12, 0x13, 0x0a, 0x0f, 0x53,
+	0x50, 0x41, 0x4e, 0x5f, 0x49, 0x4e, 0x4e, 0x45, 0x52, 0x5f, 0x43, 0x45, 0x4c, 0x4c, 0x10, 0x02,
+	0x2a, 0xcb, 0x01, 0x0a, 0x04, 0x4d, 0x6f, 0x64, 0x65, 0x12, 0x10, 0x0a, 0x0c, 0x4d, 0x4f, 0x44,
+	0x45, 0x5f, 0x44, 0x45, 0x46, 0x41, 0x55, 0x4c, 0x54, 0x10, 0x00, 0x12, 0x12, 0x0a, 0x0e, 0x4d,
+	0x4f, 0x44, 0x45, 0x5f, 0x45, 0x4e, 0x55, 0x4d, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x10, 0x01, 0x12,
+	0x18, 0x0a, 0x14, 0x4d, 0x4f, 0x44, 0x45, 0x5f, 0x45, 0x4e, 0x55, 0x4d, 0x5f, 0x54, 0x59, 0x50,
+	0x45, 0x5f, 0x4d, 0x55, 0x4c, 0x54, 0x49, 0x10, 0x02, 0x12, 0x14, 0x0a, 0x10, 0x4d, 0x4f, 0x44,
+	0x45, 0x5f, 0x53, 0x54, 0x52, 0x55, 0x43, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x10, 0x03, 0x12,
+	0x1a, 0x0a, 0x16, 0x4d, 0x4f, 0x44, 0x45, 0x5f, 0x53, 0x54, 0x52, 0x55, 0x43, 0x54, 0x5f, 0x54,
+	0x59, 0x50, 0x45, 0x5f, 0x4d, 0x55, 0x4c, 0x54, 0x49, 0x10, 0x04, 0x12, 0x13, 0x0a, 0x0f, 0x4d,
+	0x4f, 0x44, 0x45, 0x5f, 0x55, 0x4e, 0x49, 0x4f, 0x4e, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x10, 0x05,
+	0x12, 0x19, 0x0a, 0x15, 0x4d, 0x4f, 0x44, 0x45, 0x5f, 0x55, 0x4e, 0x49, 0x4f, 0x4e, 0x5f, 0x54,
+	0x59, 0x50, 0x45, 0x5f, 0x4d, 0x55, 0x4c, 0x54, 0x49, 0x10, 0x06, 0x12, 0x0f, 0x0a, 0x0b, 0x4d,
+	0x4f, 0x44, 0x45, 0x5f, 0x55, 0x45, 0x5f, 0x43, 0x53, 0x56, 0x10, 0x0a, 0x12, 0x10, 0x0a, 0x0c,
+	0x4d, 0x4f, 0x44, 0x45, 0x5f, 0x55, 0x45, 0x5f, 0x4a, 0x53, 0x4f, 0x4e, 0x10, 0x0b, 0x2a, 0x36,
+	0x0a, 0x04, 0x46, 0x6f, 0x72, 0x6d, 0x12, 0x10, 0x0a, 0x0c, 0x46, 0x4f, 0x52, 0x4d, 0x5f, 0x44,
+	0x45, 0x46, 0x41, 0x55, 0x4c, 0x54, 0x10, 0x00, 0x12, 0x0d, 0x0a, 0x09, 0x46, 0x4f, 0x52, 0x4d,
+	0x5f, 0x54, 0x45, 0x58, 0x54, 0x10, 0x01, 0x12, 0x0d, 0x0a, 0x09, 0x46, 0x4f, 0x52, 0x4d, 0x5f,
+	0x4a, 0x53, 0x4f, 0x4e, 0x10, 0x02, 0x2a, 0x3b, 0x0a, 0x05, 0x50, 0x61, 0x74, 0x63, 0x68, 0x12,
+	0x0e, 0x0a, 0x0a, 0x50, 0x41, 0x54, 0x43, 0x48, 0x5f, 0x4e, 0x4f, 0x4e, 0x45, 0x10, 0x00, 0x12,
+	0x11, 0x0a, 0x0d, 0x50, 0x41, 0x54, 0x43, 0x48, 0x5f, 0x52, 0x45, 0x50, 0x4c, 0x41, 0x43, 0x45,
+	0x10, 0x01, 0x12, 0x0f, 0x0a, 0x0b, 0x50, 0x41, 0x54, 0x43, 0x48, 0x5f, 0x4d, 0x45, 0x52, 0x47,
+	0x45, 0x10, 0x02, 0x3a, 0x54, 0x0a, 0x08, 0x77, 0x6f, 0x72, 0x6b, 0x62, 0x6f, 0x6f, 0x6b, 0x12,
+	0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75,
+	0x66, 0x2e, 0x46, 0x69, 0x6c, 0x65, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xd0, 0x86,
+	0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x61, 0x75, 0x2e,
+	0x57, 0x6f, 0x72, 0x6b, 0x62, 0x6f, 0x6f, 0x6b, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52,
+	0x08, 0x77, 0x6f, 0x72, 0x6b, 0x62, 0x6f, 0x6f, 0x6b, 0x3a, 0x5a, 0x0a, 0x09, 0x77, 0x6f, 0x72,
+	0x6b, 0x73, 0x68, 0x65, 0x65, 0x74, 0x12, 0x1f, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e,
+	0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65,
+	0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xd0, 0x86, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32,
+	0x19, 0x2e, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x61, 0x75, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x68,
+	0x65, 0x65, 0x74, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x09, 0x77, 0x6f, 0x72, 0x6b,
+	0x73, 0x68, 0x65, 0x65, 0x74, 0x3a, 0x51, 0x0a, 0x06, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x12,
+	0x1f, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75,
+	0x66, 0x2e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73,
+	0x18, 0xd1, 0x86, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x74, 0x61, 0x62, 0x6c, 0x65,
+	0x61, 0x75, 0x2e, 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73,
+	0x52, 0x06, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x3a, 0x4e, 0x0a, 0x05, 0x75, 0x6e, 0x69, 0x6f,
+	0x6e, 0x12, 0x1f, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
+	0x62, 0x75, 0x66, 0x2e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x4f, 0x70, 0x74, 0x69, 0x6f,
+	0x6e, 0x73, 0x18, 0xd2, 0x86, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x74, 0x61, 0x62,
+	0x6c, 0x65, 0x61, 0x75, 0x2e, 0x55, 0x6e, 0x69, 0x6f, 0x6e, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e,
+	0x73, 0x52, 0x05, 0x75, 0x6e, 0x69, 0x6f, 0x6e, 0x3a, 0x4c, 0x0a, 0x05, 0x66, 0x69, 0x65, 0x6c,
+	0x64, 0x12, 0x1d, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
+	0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73,
+	0x18, 0xd0, 0x86, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x74, 0x61, 0x62, 0x6c, 0x65,
+	0x61, 0x75, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52,
+	0x05, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x3a, 0x4a, 0x0a, 0x05, 0x65, 0x74, 0x79, 0x70, 0x65, 0x12,
+	0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75,
+	0x66, 0x2e, 0x45, 0x6e, 0x75, 0x6d, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xd0, 0x86,
+	0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x61, 0x75, 0x2e,
+	0x45, 0x6e, 0x75, 0x6d, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x05, 0x65, 0x74, 0x79,
+	0x70, 0x65, 0x3a, 0x56, 0x0a, 0x06, 0x65, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x21, 0x2e, 0x67,
+	0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45,
+	0x6e, 0x75, 0x6d, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18,
+	0xd0, 0x86, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x61,
+	0x75, 0x2e, 0x45, 0x6e, 0x75, 0x6d, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x4f, 0x70, 0x74, 0x69, 0x6f,
+	0x6e, 0x73, 0x52, 0x06, 0x65, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x4f, 0x0a, 0x05, 0x6f, 0x6e,
+	0x65, 0x6f, 0x66, 0x12, 0x1d, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f,
+	0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x4f, 0x6e, 0x65, 0x6f, 0x66, 0x4f, 0x70, 0x74, 0x69, 0x6f,
+	0x6e, 0x73, 0x18, 0xd0, 0x86, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x74, 0x61, 0x62,
+	0x6c, 0x65, 0x61, 0x75, 0x2e, 0x4f, 0x6e, 0x65, 0x6f, 0x66, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e,
+	0x73, 0x52, 0x05, 0x6f, 0x6e, 0x65, 0x6f, 0x66, 0x88, 0x01, 0x01, 0x42, 0x75, 0x0a, 0x14, 0x63,
+	0x6f, 0x6d, 0x2e, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x61, 0x75, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
+	0x62, 0x75, 0x66, 0x42, 0x0c, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x61, 0x75, 0x50, 0x72, 0x6f, 0x74,
+	0x6f, 0x50, 0x01, 0x5a, 0x2c, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f,
+	0x74, 0x61, 0x62, 0x6c, 0x65, 0x61, 0x75, 0x69, 0x6f, 0x2f, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x61,
+	0x75, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x61, 0x75, 0x70,
+	0x62, 0xa2, 0x02, 0x03, 0x54, 0x50, 0x42, 0xaa, 0x02, 0x18, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x61,
+	0x75, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x61, 0x62, 0x6c, 0x65,
+	0x61, 0x75, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
 }
 
 var (
diff --git a/proto/tableaupb/wellknown.pb.go b/proto/tableaupb/wellknown.pb.go
index 23877ce..4fc2221 100644
--- a/proto/tableaupb/wellknown.pb.go
+++ b/proto/tableaupb/wellknown.pb.go
@@ -589,6 +589,103 @@ func (x *Vector4I) GetW() int32 {
 	return 0
 }
 
+// Version number in [dot-decimal notation](https://en.wikipedia.org/wiki/Dot-decimal_notation).
+// The version form is "<MAJOR>.<MINOR>.<PATCH>[.<OTHER>]...".
+//
+// You can specify the version pattern as "<MAJOR_MAX>.<MINOR_MAX>.<PATCH_MAX>[.<OTHER_MAX>]...".
+//   - Each part with suffix "MAX" represents the max decimal value of each part in the dot-decimal notation.
+//   - Each part "XXX_MAX+1" represents the part's value occupying in an integer.
+//   - For example, the formula for general pattern "<MAJOR_MAX>.<MINOR_MAX>.<PATCH_MAX>" is:
+//     MAJOR*(MINOR_MAX+1)*(PATCH_MAX+1) + MINOR*(PATCH_MAX+1) + PATCH
+//
+// Default pattern: "255.255.255".
+type Version struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	Str    string   `protobuf:"bytes,1,opt,name=str,proto3" json:"str,omitempty"`               // Version in string form.
+	Val    uint64   `protobuf:"varint,2,opt,name=val,proto3" json:"val,omitempty"`              // Version in integer form.
+	Major  uint32   `protobuf:"varint,3,opt,name=major,proto3" json:"major,omitempty"`          // Major version number.
+	Minor  uint32   `protobuf:"varint,4,opt,name=minor,proto3" json:"minor,omitempty"`          // Minor version number.
+	Patch  uint32   `protobuf:"varint,5,opt,name=patch,proto3" json:"patch,omitempty"`          // Patch version number.
+	Others []uint32 `protobuf:"varint,6,rep,packed,name=others,proto3" json:"others,omitempty"` // Other version numbers, such as build number, resource version, and so on.
+}
+
+func (x *Version) Reset() {
+	*x = Version{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_tableau_protobuf_wellknown_proto_msgTypes[8]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *Version) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Version) ProtoMessage() {}
+
+func (x *Version) ProtoReflect() protoreflect.Message {
+	mi := &file_tableau_protobuf_wellknown_proto_msgTypes[8]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use Version.ProtoReflect.Descriptor instead.
+func (*Version) Descriptor() ([]byte, []int) {
+	return file_tableau_protobuf_wellknown_proto_rawDescGZIP(), []int{8}
+}
+
+func (x *Version) GetStr() string {
+	if x != nil {
+		return x.Str
+	}
+	return ""
+}
+
+func (x *Version) GetVal() uint64 {
+	if x != nil {
+		return x.Val
+	}
+	return 0
+}
+
+func (x *Version) GetMajor() uint32 {
+	if x != nil {
+		return x.Major
+	}
+	return 0
+}
+
+func (x *Version) GetMinor() uint32 {
+	if x != nil {
+		return x.Minor
+	}
+	return 0
+}
+
+func (x *Version) GetPatch() uint32 {
+	if x != nil {
+		return x.Patch
+	}
+	return 0
+}
+
+func (x *Version) GetOthers() []uint32 {
+	if x != nil {
+		return x.Others
+	}
+	return nil
+}
+
 var File_tableau_protobuf_wellknown_proto protoreflect.FileDescriptor
 
 var file_tableau_protobuf_wellknown_proto_rawDesc = []byte{
@@ -644,15 +741,23 @@ var file_tableau_protobuf_wellknown_proto_rawDesc = []byte{
 	0x0a, 0x01, 0x59, 0x52, 0x01, 0x79, 0x12, 0x15, 0x0a, 0x01, 0x7a, 0x18, 0x03, 0x20, 0x01, 0x28,
 	0x05, 0x42, 0x07, 0x82, 0xb5, 0x18, 0x03, 0x0a, 0x01, 0x5a, 0x52, 0x01, 0x7a, 0x12, 0x15, 0x0a,
 	0x01, 0x77, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x42, 0x07, 0x82, 0xb5, 0x18, 0x03, 0x0a, 0x01,
-	0x57, 0x52, 0x01, 0x77, 0x42, 0x78, 0x0a, 0x14, 0x63, 0x6f, 0x6d, 0x2e, 0x74, 0x61, 0x62, 0x6c,
-	0x65, 0x61, 0x75, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x42, 0x0c, 0x54, 0x61,
-	0x62, 0x6c, 0x65, 0x61, 0x75, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x2c, 0x67, 0x69,
-	0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x61, 0x75,
-	0x69, 0x6f, 0x2f, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x61, 0x75, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f,
-	0x2f, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x61, 0x75, 0x70, 0x62, 0xf8, 0x01, 0x01, 0xa2, 0x02, 0x03,
-	0x54, 0x50, 0x42, 0xaa, 0x02, 0x18, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x61, 0x75, 0x2e, 0x50, 0x72,
-	0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x61, 0x75, 0x62, 0x06,
-	0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
+	0x57, 0x52, 0x01, 0x77, 0x22, 0x87, 0x01, 0x0a, 0x07, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e,
+	0x12, 0x10, 0x0a, 0x03, 0x73, 0x74, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x73,
+	0x74, 0x72, 0x12, 0x10, 0x0a, 0x03, 0x76, 0x61, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52,
+	0x03, 0x76, 0x61, 0x6c, 0x12, 0x14, 0x0a, 0x05, 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x18, 0x03, 0x20,
+	0x01, 0x28, 0x0d, 0x52, 0x05, 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x12, 0x14, 0x0a, 0x05, 0x6d, 0x69,
+	0x6e, 0x6f, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x6d, 0x69, 0x6e, 0x6f, 0x72,
+	0x12, 0x14, 0x0a, 0x05, 0x70, 0x61, 0x74, 0x63, 0x68, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52,
+	0x05, 0x70, 0x61, 0x74, 0x63, 0x68, 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x73,
+	0x18, 0x06, 0x20, 0x03, 0x28, 0x0d, 0x52, 0x06, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x73, 0x42, 0x78,
+	0x0a, 0x14, 0x63, 0x6f, 0x6d, 0x2e, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x61, 0x75, 0x2e, 0x70, 0x72,
+	0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x42, 0x0c, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x61, 0x75, 0x50,
+	0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x2c, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63,
+	0x6f, 0x6d, 0x2f, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x61, 0x75, 0x69, 0x6f, 0x2f, 0x74, 0x61, 0x62,
+	0x6c, 0x65, 0x61, 0x75, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x74, 0x61, 0x62, 0x6c, 0x65,
+	0x61, 0x75, 0x70, 0x62, 0xf8, 0x01, 0x01, 0xa2, 0x02, 0x03, 0x54, 0x50, 0x42, 0xaa, 0x02, 0x18,
+	0x54, 0x61, 0x62, 0x6c, 0x65, 0x61, 0x75, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66,
+	0x2e, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x61, 0x75, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
 }
 
 var (
@@ -668,7 +773,7 @@ func file_tableau_protobuf_wellknown_proto_rawDescGZIP() []byte {
 }
 
 var file_tableau_protobuf_wellknown_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
-var file_tableau_protobuf_wellknown_proto_msgTypes = make([]protoimpl.MessageInfo, 8)
+var file_tableau_protobuf_wellknown_proto_msgTypes = make([]protoimpl.MessageInfo, 9)
 var file_tableau_protobuf_wellknown_proto_goTypes = []interface{}{
 	(Comparator_Sign)(0), // 0: tableau.Comparator.Sign
 	(*Fraction)(nil),     // 1: tableau.Fraction
@@ -679,6 +784,7 @@ var file_tableau_protobuf_wellknown_proto_goTypes = []interface{}{
 	(*Vector2I)(nil),     // 6: tableau.Vector2i
 	(*Vector3I)(nil),     // 7: tableau.Vector3i
 	(*Vector4I)(nil),     // 8: tableau.Vector4i
+	(*Version)(nil),      // 9: tableau.Version
 }
 var file_tableau_protobuf_wellknown_proto_depIdxs = []int32{
 	0, // 0: tableau.Comparator.sign:type_name -> tableau.Comparator.Sign
@@ -793,6 +899,18 @@ func file_tableau_protobuf_wellknown_proto_init() {
 				return nil
 			}
 		}
+		file_tableau_protobuf_wellknown_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*Version); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
 	}
 	type x struct{}
 	out := protoimpl.TypeBuilder{
@@ -800,7 +918,7 @@ func file_tableau_protobuf_wellknown_proto_init() {
 			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
 			RawDescriptor: file_tableau_protobuf_wellknown_proto_rawDesc,
 			NumEnums:      1,
-			NumMessages:   8,
+			NumMessages:   9,
 			NumExtensions: 0,
 			NumServices:   0,
 		},
diff --git a/xerrors/ecode.go b/xerrors/ecode.go
index 25891be..06f6f5c 100644
--- a/xerrors/ecode.go
+++ b/xerrors/ecode.go
@@ -258,6 +258,22 @@ func E2023(key any) error {
 	})
 }
 
+// E2024: invalid version pattern.
+func E2024(value string, err error) error {
+	return renderEcode("E2024", map[string]any{
+		"Value": value,
+		"Error": err,
+	})
+}
+
+// E2025: version value mismatches pattern.
+func E2025(value, pattern string) error {
+	return renderEcode("E2025", map[string]any{
+		"Value":   value,
+		"Pattern": pattern,
+	})
+}
+
 // E3000: no workbook file found about sheet specifier.
 func E3000(sheetSpecifier, pattern string) error {
 	return renderEcode("E3000", map[string]any{


Test patch:
diff --git a/internal/types/types_test.go b/internal/types/types_test.go
index c8eb299..9eef308 100644
--- a/internal/types/types_test.go
+++ b/internal/types/types_test.go
@@ -512,6 +512,11 @@ func TestIsScalarType(t *testing.T) {
 			args: args{t: WellKnownMessageComparator},
 			want: true,
 		},
+		{
+			name: WellKnownMessageVersion,
+			args: args{t: WellKnownMessageVersion},
+			want: true,
+		},
 		{
 			name: "MessageType",
 			args: args{t: "MessageType"},
diff --git a/internal/x/xproto/value_test.go b/internal/x/xproto/value_test.go
index c351c0a..56a82f9 100644
--- a/internal/x/xproto/value_test.go
+++ b/internal/x/xproto/value_test.go
@@ -5,7 +5,9 @@ import (
 	"math"
 	"testing"
 
+	"github.com/stretchr/testify/require"
 	"github.com/tableauio/tableau/proto/tableaupb"
+	"github.com/tableauio/tableau/xerrors"
 	pref "google.golang.org/protobuf/reflect/protoreflect"
 	"google.golang.org/protobuf/types/known/wrapperspb"
 )
@@ -346,7 +348,7 @@ func TestParseFieldValue(t *testing.T) {
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			gotV, gotPresent, err := ParseFieldValue(tt.args.fd, tt.args.rawValue, tt.args.locationName)
+			gotV, gotPresent, err := ParseFieldValue(tt.args.fd, tt.args.rawValue, tt.args.locationName, nil)
 			if (err != nil) != tt.wantErr {
 				t.Errorf("ParseFieldValue() error = %v, wantErr %v", err, tt.wantErr)
 				return
@@ -595,3 +597,135 @@ func Test_parseComparator(t *testing.T) {
 		})
 	}
 }
+
+func Test_parseVersion(t *testing.T) {
+	msg := &tableaupb.Version{}
+	md := msg.ProtoReflect().Descriptor()
+	type args struct {
+		value   string
+		pattern string
+	}
+	tests := []struct {
+		name    string
+		args    args
+		wantV   *tableaupb.Version
+		wantErr bool
+		errcode string
+	}{
+		{
+			name: "default pattern",
+			args: args{
+				value: "1.2.3",
+			},
+			wantV: &tableaupb.Version{
+				Str:   "1.2.3",
+				Val:   1<<16 | 2<<8 | 3, // 66051
+				Major: 1,
+				Minor: 2,
+				Patch: 3,
+			},
+		},
+		{
+			name: "custom pattern",
+			args: args{
+				pattern: "99.999.99.999",
+				value:   "12.345.67.890",
+			},
+			wantV: &tableaupb.Version{
+				Str:    "12.345.67.890",
+				Val:    1234567890,
+				Major:  12,
+				Minor:  345,
+				Patch:  67,
+				Others: []uint32{890},
+			},
+		},
+		{
+			name: "no dot in pattern",
+			args: args{
+				pattern: "65535",
+				value:   "1024",
+			},
+			wantV: &tableaupb.Version{
+				Str:   "1024",
+				Val:   1024,
+				Major: 1024,
+			},
+		},
+		{
+			name: "version mismatches pattern",
+			args: args{
+				pattern: "255.255.255",
+				value:   "1.0.0.0",
+			},
+			wantErr: true,
+			errcode: "E2025",
+		},
+		{
+			name: "negative pattern decimal",
+			args: args{
+				pattern: "255.-255.255",
+				value:   "1.0.0",
+			},
+			wantErr: true,
+			errcode: "E2024",
+		},
+		{
+			name: "negative version decimal",
+			args: args{
+				pattern: "255.255.255",
+				value:   "1.-1.0",
+			},
+			wantErr: true,
+			errcode: "E2024",
+		},
+		{
+			name: "pattern decimal max uint32",
+			args: args{
+				pattern: fmt.Sprintf("255.255.%d", math.MaxUint32),
+				value:   "0.2.0",
+			},
+			wantV: &tableaupb.Version{
+				Str:   "0.2.0",
+				Val:   2 * (math.MaxUint32 + 1),
+				Minor: 2,
+			},
+		},
+		{
+			name: "pattern decimal exceeds max uint32",
+			args: args{
+				pattern: fmt.Sprintf("255.255.%d", math.MaxUint32+1),
+				value:   "1.0.0",
+			},
+			wantErr: true,
+			errcode: "E2024",
+		},
+		{
+			name: "pattern decimal product exceeds max uint64",
+			args: args{
+				pattern: fmt.Sprintf("%d.%d.%d", math.MaxUint32, math.MaxUint32, math.MaxUint32),
+				value:   "1.0.0",
+			},
+			wantErr: true,
+			errcode: "E2024",
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			gotV, _, err := parseVersion(md, tt.args.value, tt.args.pattern)
+			if (err != nil) != tt.wantErr {
+				t.Errorf("parseVersion() error = %v, wantErr %v", err, tt.wantErr)
+				return
+			}
+			if err == nil && !gotV.Equal(pref.ValueOfMessage(tt.wantV.ProtoReflect())) {
+				t.Errorf("parseVersion() = %v, want %v", gotV, tt.wantV)
+			}
+			if err != nil {
+				if tt.errcode != "" {
+					desc := xerrors.NewDesc(err)
+					require.Equal(t, tt.errcode, desc.ErrCode())
+				}
+			}
+		})
+	}
+}
diff --git a/test/functest/conf/default/WellKnownTypeVersion.json b/test/functest/conf/default/WellKnownTypeVersion.json
new file mode 100644
index 0000000..7abc8b2
--- /dev/null
+++ b/test/functest/conf/default/WellKnownTypeVersion.json
@@ -0,0 +1,56 @@
+{
+    "minVersion": {
+        "str": "1.2.3.4.5.6",
+        "val": "10020300405006",
+        "major": 1,
+        "minor": 2,
+        "patch": 3,
+        "others": [
+            4,
+            5,
+            6
+        ]
+    },
+    "versionList": [
+        {
+            "str": "1.0.0",
+            "val": "65536",
+            "major": 1,
+            "minor": 0,
+            "patch": 0,
+            "others": []
+        },
+        {
+            "str": "1.0.1",
+            "val": "65537",
+            "major": 1,
+            "minor": 0,
+            "patch": 1,
+            "others": []
+        },
+        {
+            "str": "1.0.2",
+            "val": "65538",
+            "major": 1,
+            "minor": 0,
+            "patch": 2,
+            "others": []
+        },
+        {
+            "str": "1.1.0",
+            "val": "65792",
+            "major": 1,
+            "minor": 1,
+            "patch": 0,
+            "others": []
+        },
+        {
+            "str": "2.0.0",
+            "val": "131072",
+            "major": 2,
+            "minor": 0,
+            "patch": 0,
+            "others": []
+        }
+    ]
+}
\ No newline at end of file
diff --git a/test/functest/proto/default/excel__wellknown__well_known.proto b/test/functest/proto/default/excel__wellknown__well_known.proto
index 21b20f1..fb5a700 100644
--- a/test/functest/proto/default/excel__wellknown__well_known.proto
+++ b/test/functest/proto/default/excel__wellknown__well_known.proto
@@ -58,3 +58,10 @@ message WellKnownTypeTime {
   google.protobuf.Duration end_time = 2 [(tableau.field) = {name:"EndTime"}]; // End time
   repeated google.protobuf.Duration time_list = 3 [(tableau.field) = {name:"Time" layout:LAYOUT_INCELL}]; // Time
 }
+
+message WellKnownTypeVersion {
+  option (tableau.worksheet) = {name:"WellKnownTypeVersion"};
+
+  tableau.Version min_version = 1 [(tableau.field) = {name:"MinVersion" prop:{pattern:"99.999.99.999.99.999"}}]; // min version
+  repeated tableau.Version version_list = 2 [(tableau.field) = {name:"Version" layout:LAYOUT_HORIZONTAL}]; // version
+}
diff --git a/test/functest/testdata/default/excel/wellknown/WellKnown#WellKnownTypeVersion.csv b/test/functest/testdata/default/excel/wellknown/WellKnown#WellKnownTypeVersion.csv
new file mode 100644
index 0000000..301c7f9
--- /dev/null
+++ b/test/functest/testdata/default/excel/wellknown/WellKnown#WellKnownTypeVersion.csv
@@ -0,0 +1,4 @@
+MinVersion,Version1,Version2,Version3,Version4,Version5
+"version|{pattern:""99.999.99.999.99.999""}",[]version,version,version,version,version
+min version,version1,version 2,version 3,version 4,version 5
+1.2.3.4.5.6,1.0.0,1.0.1,1.0.2,1.1.0,2.0.0