├── .gitignore ├── .idea └── vcs.xml ├── .travis.yml ├── LICENSE ├── Makefile ├── README.md ├── action.go ├── ensure_dir.go ├── go.mod ├── internal ├── generator │ ├── array_field.go │ ├── chstuff │ │ ├── ch2generic.go │ │ ├── chstuff.go │ │ └── decomposeEnum.go │ ├── date_field.go │ ├── datetime_field.go │ ├── decimal128_field.go │ ├── decimal32_field.go │ ├── decimal64_field.go │ ├── enum16_field.go │ ├── enum8_field.go │ ├── field.go │ ├── field_set.go │ ├── fixed_string_field.go │ ├── float32_field.go │ ├── float64_field.go │ ├── generation.go │ ├── generator.go │ ├── gogen │ │ ├── encoder.go │ │ ├── encoding.go │ │ ├── fieldtypes.go │ │ ├── filterencoder.go │ │ ├── generator.go │ │ ├── helpers.go │ │ ├── native-typenames.go │ │ ├── regular-typenames.go │ │ ├── testing-encoding.go │ │ ├── testing-typenames.go │ │ ├── testingencoder.go │ │ ├── util.go │ │ └── voidencoder.go │ ├── int16_field.go │ ├── int32_field.go │ ├── int64_field.go │ ├── int8_field.go │ ├── nullable_array_field.go │ ├── nullable_field.go │ ├── nullable_string_field.go │ ├── string_field.go │ ├── uint16_field.go │ ├── uint32_field.go │ ├── uint64_field.go │ ├── uint8_field.go │ └── uuid_field.go └── util │ ├── components_parser.go │ ├── envch_params.go │ ├── extractor_lde.go │ ├── testingch.go │ ├── testingch_test.go │ └── userpass_parser.go ├── main.go └── screenshot.png /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.dll 4 | *.so 5 | *.dylib 6 | 7 | # Test binary, build with `go test -c` 8 | *.test 9 | 10 | # Output of the go coverage tool, specifically when used with LiteIDE 11 | *.out 12 | 13 | # Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 14 | .glide/ 15 | 16 | # Hide JetBrains stuff 17 | .idea/ 18 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: go 3 | go: 4 | - 1.9.x 5 | 6 | install: 7 | - go get -v -d -t github.com/sirkon/ch-encode 8 | 9 | script: 10 | - echo done -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Denis 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | set-version: 2 | git tag | tail -1 | xargs printf 'package main\n\nvar version="%s"\n' > version.go -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ch-encode 2 | [![Build Status](https://travis-ci.org/sirkon/ch-encode.svg?branch=master)](https://travis-ci.org/sirkon/ch-encode) 3 | 4 | Clickhouse typesafe RowBinary insert data encoder generator for Go. Supported types: 5 | 6 | String | FixedString(N) | UUID* | UIntX | IntX |Decimal(X,Y)| EnumX | Array(T)|FloatX|Nested**|Nullable(T)| 7 | -------|----------------|------|-------|------|-------------|-------|---------|------|------|--------| 8 | 9 | > * UUID type rely on `github.com/google/uuid` 10 | 11 | 12 | > \*\* Nested types are supported in the same sense they are used in the Clickhouse itself: despite having clearly 13 | nested declaration they are represented as ordinary fields with dotted names (`[nested name].[subfield name]`) with DB level 14 | constraint on these fields (what are ordinary arrays of subfield's types) having the same length. We did the same: there's encoder level control implemented for Raw and Testing encoders for these arrays' length. There's no syntactic level grouping for them. 15 | 16 | This piece is for record data generation only. See https://github.com/sirkon/ch-insert for actual data insert 17 | 18 | ## The problem 19 | * Tables can be wide and thus generating proper INSERT statements is a source of errors itself. 20 | * Proper data buffering on INSERT statements is a bit tricky 21 | 22 | ## Solution 23 | * Take Clickhouse table. Map it into the function call (column → function parameter) 24 | * Function generates binary data in Clickhouse RowBinary format. Buffering became trivial (special bufferer is needed anyway - 25 | function call output which represents a table record must not be splitted) 26 | 27 | ## Problems raised 28 | * Scheme desync. Basically solved as we check current table' schema when creating an encoder. 29 | 30 | ## Bonuses 31 | * Testing mock objects can be generated as well. 32 | * It is fast as it avoids any allocation in a process and RawBinary should be the easiest format (I don't know exactly though) after Native for the Clickhouse. 33 | 34 | 35 | # How to use 36 | ```bash 37 | go get github.com/sirkon/ch-encode 38 | ``` 39 | then 40 | ```bash 41 | ch-encode --date-field date table1 table2 … tableN 42 | ``` 43 | will generated N packages in the current folder with autogenerated encoders and desync tests. Use `--date-field ''` in case if the table doesn't have any `Date` field. 44 | 45 | Example. 46 | 47 | 1. Let's create clickhouse table 48 | ```sql 49 | CREATE TABLE test 50 | ( 51 | date Date, 52 | uid String, 53 | hidden UInt8 54 | ) ENGINE = MergeTree(date, (date, uid, hidden), 8192); 55 | ``` 56 | 2. Let we have translation dictionary translation.yaml 57 | ```yaml 58 | uid: UID 59 | ``` 60 | we use translation in order uid to be translated into UID in generated code. Something like 61 | `first_uid` or `firstUid` will be translated into `firstUID` as well. 62 | 3. Now generate encoder 63 | ```bash 64 | bin/ch-encode --yaml-dict=translation.yaml test 65 | ``` 66 | test directory will appear in current directory, it will have two go files, test.go and test_test.go. 67 | this is **test** package. Move it into src to be seen by **go install** 68 | 4. Get [ch-insert](https://github.com/sirkon/ch-insert) package: 69 | ``` 70 | go get -u github.com/sirkon/ch-insert 71 | ``` 72 | 5. Now, some code 73 | ```go 74 | // file main.go 75 | package main 76 | 77 | import ( 78 | "net/http" 79 | "test" 80 | "time" 81 | 82 | chinsert "github.com/sirkon/ch-insert" 83 | ) 84 | 85 | func main() { 86 | inserter, err := chinsert.Open("localhost:8123/default", "test", 10*1024*1024, 1024*1024*1024) 87 | if err != nil { 88 | panic(err) 89 | } 90 | defer inserter.Close() 91 | 92 | encoder, err := test.NewTestRawEncoder(inserter) 93 | if err != nil { 94 | panic(err) // must be scheme desync 95 | } 96 | if err := encoder.Encode(test.Date.FromTime(time.Now()), test.UID("123"), test.Hidden(1)); err != nil { 97 | panic(err) 98 | } 99 | if err := encoder.Encode(test.Date.FromTime(time.Now()), test.UID("123"), test.Hidden(0)); err != nil { 100 | panic(err) 101 | } 102 | } 103 | 104 | ``` 105 | 6. See test table now 106 | 107 | ![Screenshot](screenshot.png) 108 | 109 | # Details 110 | Let's see into the generated test.go file. 111 | 112 | There's test encoder 113 | ```go 114 | type TestEncoder interface { 115 | Encode(date DateType, uid UID, hidden Hidden) error 116 | InsertCount() int 117 | ErrorCount() int 118 | } 119 | ``` 120 | 121 | And there are 4 types that implements TestEncoder: 122 | ```go 123 | type TestRawEncoder {} // Regular RowBinary data generator 124 | type TestRawEncoderDateFilter{} // Will only generate data for a given date 125 | type TestRawEncoderVoid{} // Will not generate anything 126 | type TestingTestEncoder{} // For testing purposes 127 | ``` 128 | 129 | We saw how the TestRawEncoder works. Bother TestRawEncoderDateFilter and TestRawEncoderVoid work the same way. Let's see how to use TestingTestEncoder: 130 | ```go 131 | package main 132 | 133 | import ( 134 | "test" 135 | "time" 136 | 137 | "github.com/sanity-io/litter" 138 | ) 139 | 140 | func main() { 141 | e := test.NewTestingTestEncoder() 142 | date := time.Date(2006, 1, 2, 3, 4, 5, 0, time.UTC) 143 | err := e.Encode( 144 | test.Date.FromTime(date), 145 | test.UID("123123"), 146 | test.Hidden(1)) 147 | if err != nil { 148 | panic(err) 149 | } 150 | err = e.Encode( 151 | test.Date.FromTime(date), 152 | test.UID("321321"), 153 | test.Hidden(0)) 154 | if err != nil { 155 | panic(err) 156 | } 157 | 158 | litter.Dump(e.Result) 159 | } 160 | 161 | ``` 162 | this program will output 163 | ```go 164 | []test.TestingTestResult{ 165 | test.TestingTestResult{ 166 | Date: "2006-01-02", 167 | UID: "123123", 168 | Hidden: 1, 169 | }, 170 | test.TestingTestResult{ 171 | Date: "2006-01-02", 172 | UID: "321321", 173 | Hidden: 0, 174 | }, 175 | } 176 | ``` 177 | Good for testing, you see. 178 | DateTime type will be represented as `%Y-%m-%dT%H:%M:%S` string, enums will be represented as their text values. Other clickhouse types match directly into Golang equivalents (Int16 -> int16, Float64 -> float64, UInt32 -> uint32, nullables as pointers to types except `Nullable(Array(T))` which are represented as regular `[]τ` slices, etc) 179 | -------------------------------------------------------------------------------- /action.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "database/sql" 6 | "encoding/json" 7 | "io" 8 | "io/ioutil" 9 | "os" 10 | 11 | "github.com/go-yaml/yaml" 12 | 13 | "github.com/sirkon/gosrcfmt" 14 | "github.com/sirkon/gotify" 15 | "github.com/sirkon/message" 16 | 17 | "github.com/sirkon/ch-encode/internal/generator" 18 | "github.com/sirkon/ch-encode/internal/generator/chstuff" 19 | "github.com/sirkon/ch-encode/internal/generator/gogen" 20 | "github.com/sirkon/ch-encode/internal/util" 21 | ) 22 | 23 | func yamlSource(path string) map[string]string { 24 | res := map[string]string{} 25 | data, err := ioutil.ReadFile(path) 26 | if err != nil { 27 | message.Criticalf("Cannot read `%s`: %s", path, err) 28 | } 29 | if err := yaml.Unmarshal(data, &res); err != nil { 30 | message.Criticalf("Cannot parse `%s` as YAML file: %s", path, err) 31 | } 32 | return res 33 | } 34 | 35 | func jsonSource(path string) map[string]string { 36 | res := map[string]string{} 37 | data, err := ioutil.ReadFile(path) 38 | if err != nil { 39 | message.Criticalf("Cannot read `%s`: %s", path, err) 40 | } 41 | if err := json.Unmarshal(data, &res); err != nil { 42 | message.Criticalf("Cannot parse `%s` as JSON file: %s", path, err) 43 | } 44 | return res 45 | } 46 | 47 | func action(isTesting bool, yamlDict string, jsonDict string, dateField string, tables []string, connParams string) error { 48 | var dict map[string]string 49 | if len(yamlDict) > 0 { 50 | dict = yamlSource(yamlDict) 51 | } 52 | if len(jsonDict) > 0 { 53 | dict = jsonSource(jsonDict) 54 | } 55 | 56 | goish := gotify.New(dict) 57 | 58 | prms := util.EnvCHParams(connParams) 59 | connect, err := sql.Open("clickhouse", prms.DBURL()) 60 | if err != nil { 61 | panic(err) 62 | } 63 | 64 | for _, table := range tables { 65 | metas, err := chstuff.RetrieveTableMeta(connect, table) 66 | if err != nil { 67 | message.Critical(err) 68 | } 69 | 70 | partWriter := &bytes.Buffer{} 71 | gen := gogen.New(table, goish, partWriter) 72 | fields := generator.NewFieldSet(gen) 73 | dateArg := func(gen generator.Generator) string { return "" } 74 | for _, meta := range metas { 75 | fieldInfo := chstuff.Meta2Field(meta) 76 | if meta.Name == dateField { 77 | dateArg = func(gen generator.Generator) string { return fieldInfo.ArgName(gen) } 78 | } 79 | fields.Add(fieldInfo) 80 | } 81 | 82 | if err = generator.Generate(gen, dateArg, fields); err != nil { 83 | message.Critical(err) 84 | } 85 | writer := &bytes.Buffer{} 86 | var output io.WriteCloser 87 | if isTesting { 88 | output = os.Stdout 89 | } else { 90 | output, err = GoModule(goish, table) 91 | if err != nil { 92 | message.Critical(err) 93 | } 94 | } 95 | gen.Header(writer) 96 | io.Copy(writer, partWriter) 97 | gosrcfmt.FormatReader(output, writer) 98 | if err = output.Close(); err != nil { 99 | message.Critical(err) 100 | } 101 | 102 | message.Noticef("Table `\033[1m%s\033[0m` encoder generated", table) 103 | } 104 | return nil 105 | } 106 | -------------------------------------------------------------------------------- /ensure_dir.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "log" 7 | "os" 8 | "path/filepath" 9 | 10 | "github.com/sirkon/gotify" 11 | ) 12 | 13 | // GoModule creates 14 | func GoModule(goish *gotify.Gotify, table string) (writer io.WriteCloser, err error) { 15 | packagepath := filepath.Join(goish.Goimports(table)) 16 | fi, err := os.Stat(packagepath) 17 | if err != nil { 18 | err = os.MkdirAll(packagepath, 0777) 19 | if err != nil { 20 | return 21 | } 22 | } else { 23 | if !fi.IsDir() { 24 | err = fmt.Errorf("%s exists and is not a folder", packagepath) 25 | return 26 | } 27 | } 28 | path := filepath.Join(packagepath, goish.Goimports(table)+".go") 29 | writer, err = os.Create(path) 30 | return 31 | } 32 | 33 | // GoModuleTest creates 34 | func GoModuleTest(goish *gotify.Gotify, table string) (path string) { 35 | packagepath := filepath.Join(goish.Goimports(table)) 36 | fi, err := os.Stat(packagepath) 37 | if err != nil { 38 | err = os.MkdirAll(packagepath, 0777) 39 | if err != nil { 40 | log.Fatal(err) 41 | } 42 | } else { 43 | if !fi.IsDir() { 44 | log.Fatalf("%s exists and is not a folder", packagepath) 45 | } 46 | } 47 | path = filepath.Join(packagepath, goish.Goimports(table)+"_test.go") 48 | return 49 | } 50 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/sirkon/ch-encode 2 | 3 | require ( 4 | github.com/go-yaml/yaml v0.0.0-20170812160011-eb3733d160e7 5 | github.com/jawher/mow.cli v1.1.0 6 | github.com/kr/pretty v0.1.0 // indirect 7 | github.com/mailru/go-clickhouse v1.1.0 8 | github.com/sirkon/binenc v1.1.0 9 | github.com/sirkon/ch-insert v1.1.0 // indirect 10 | github.com/sirkon/go-diff v1.0.0 11 | github.com/sirkon/gosrcfmt v1.5.0 12 | github.com/sirkon/gotify v0.6.0 13 | github.com/sirkon/message v1.5.1 14 | github.com/stretchr/testify v1.3.0 15 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect 16 | gopkg.in/yaml.v2 v2.2.2 // indirect 17 | ) 18 | -------------------------------------------------------------------------------- /internal/generator/array_field.go: -------------------------------------------------------------------------------- 1 | package generator 2 | 3 | // Array Type implementation 4 | type Array struct { 5 | field string 6 | fieldType string 7 | meta Field 8 | } 9 | 10 | // NewArray constructor 11 | func NewArray(field, fieldType string, meta Field) *Array { 12 | return &Array{ 13 | field: field, 14 | fieldType: fieldType, 15 | meta: meta, 16 | } 17 | } 18 | 19 | // FieldName ... 20 | func (a *Array) FieldName(gen Generator) string { 21 | return a.field 22 | } 23 | 24 | // FieldTypeName ... 25 | func (a *Array) FieldTypeName(gen Generator) string { 26 | return a.fieldType 27 | } 28 | 29 | // TypeName ... 30 | func (a *Array) TypeName(gen Generator) string { 31 | return gen.EasyTypeName(a.field) 32 | } 33 | 34 | // ArgName ... 35 | func (a *Array) ArgName(gen Generator) string { 36 | return gen.VarName(a.field) 37 | } 38 | 39 | // AccessName ... 40 | func (a *Array) AccessName(gen Generator) string { 41 | return gen.HelperName(a.field) 42 | } 43 | 44 | // NativeTypeName Array implementation 45 | func (a *Array) NativeTypeName(gen Generator) string { 46 | return gen.ArrayNativeTypeName(a.meta) 47 | } 48 | 49 | // Encoding Array implementation 50 | func (a *Array) Encoding(source string, gen Generator) error { 51 | return gen.ArrayEncoding(source, a.meta) 52 | } 53 | 54 | // Helper ... 55 | func (a *Array) Helper(gen Generator) error { 56 | return nil 57 | } 58 | 59 | // TestingTypeName ... 60 | func (a *Array) TestingTypeName(gen Generator) string { 61 | return gen.ArrayTestingTypeName(a.meta) 62 | } 63 | 64 | // TestEncoding ... 65 | func (a *Array) TestEncoding(source string, gen Generator) error { 66 | return gen.ArrayTestEncoding(source, a.meta) 67 | } 68 | -------------------------------------------------------------------------------- /internal/generator/chstuff/ch2generic.go: -------------------------------------------------------------------------------- 1 | package chstuff 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/sirkon/ch-encode/internal/generator" 7 | ) 8 | 9 | // Meta2Field translates clickhouse field metainfo into generator Field object 10 | func Meta2Field(meta FieldMeta) (field generator.Field) { 11 | switch meta.Type { 12 | case "Int8": 13 | field = generator.NewInt8(meta.Name, meta.RawType) 14 | case "Int16": 15 | field = generator.NewInt16(meta.Name, meta.RawType) 16 | case "Int32": 17 | field = generator.NewInt32(meta.Name, meta.RawType) 18 | case "Int64": 19 | field = generator.NewInt64(meta.Name, meta.RawType) 20 | case "UInt8": 21 | field = generator.NewUint8(meta.Name, meta.RawType) 22 | case "UInt16": 23 | field = generator.NewUint16(meta.Name, meta.RawType) 24 | case "UInt32": 25 | field = generator.NewUint32(meta.Name, meta.RawType) 26 | case "UInt64": 27 | field = generator.NewUint64(meta.Name, meta.RawType) 28 | case "Float32": 29 | field = generator.NewFloat32(meta.Name, meta.RawType) 30 | case "Float64": 31 | field = generator.NewFloat64(meta.Name, meta.RawType) 32 | case "String": 33 | field = generator.NewString(meta.Name, meta.RawType) 34 | case "FixedString": 35 | field = generator.NewFixedString(meta.Name, meta.RawType, meta.FixedStringLen) 36 | case "UUID": 37 | field = generator.NewUUIDField(meta.Name, meta.RawType) 38 | case "Date": 39 | field = generator.NewDate(meta.Name, meta.RawType) 40 | case "DateTime": 41 | field = generator.NewDateTime(meta.Name, meta.RawType) 42 | case "Enum8": 43 | field = generator.NewEnum8(meta.Name, meta.RawType, meta.EnumData) 44 | case "Enum16": 45 | field = generator.NewEnum16(meta.Name, meta.RawType, meta.EnumData) 46 | case "Array": 47 | field = generator.NewArray(meta.Name, meta.RawType, Meta2Field(*meta.Subtype)) 48 | case "Nullable": 49 | if meta.Subtype == nil { 50 | panic(fmt.Errorf("integrity error, nullable type must be nullable type of T, not just nullable")) 51 | } 52 | switch meta.Subtype.Type { 53 | case "String": 54 | field = generator.NewNullableString(meta.Name, meta.RawType) 55 | case "Array": 56 | field = generator.NewNullableArray(meta.Name, meta.RawType, Meta2Field(*meta.Subtype)) 57 | default: 58 | field = generator.NewNullable(meta.Name, meta.RawType, Meta2Field(*meta.Subtype)) 59 | } 60 | case "Decimal32": 61 | field = generator.NewDecimal32(meta.Name, meta.RawType, meta.Decimal.Precision, meta.Decimal.Scale) 62 | case "Decimal64": 63 | field = generator.NewDecimal64(meta.Name, meta.RawType, meta.Decimal.Precision, meta.Decimal.Scale) 64 | case "Decimal128": 65 | field = generator.NewDecimal128(meta.Name, meta.RawType, meta.Decimal.Precision, meta.Decimal.Scale) 66 | default: 67 | panic(fmt.Errorf("unsupported clickhouse type %s for field %s", meta.RawType, meta.Name)) 68 | } 69 | return 70 | } 71 | -------------------------------------------------------------------------------- /internal/generator/chstuff/chstuff.go: -------------------------------------------------------------------------------- 1 | package chstuff 2 | 3 | import ( 4 | "database/sql" 5 | "strconv" 6 | "strings" 7 | 8 | "github.com/sirkon/ch-encode/internal/util" 9 | ) 10 | 11 | // FieldMeta field description 12 | type FieldMeta struct { 13 | Name string 14 | Type string 15 | RawType string 16 | EnumData map[string]int 17 | FixedStringLen int 18 | Decimal struct { 19 | Bits int // разрядность в битах для данного Decimal 20 | Precision int // количество цифр в записи числа 21 | Scale int // количество цифр в дробной части 22 | } 23 | Subtype *FieldMeta 24 | } 25 | 26 | func retreiveField(name, ftype string) (meta FieldMeta, err error) { 27 | decExtractor := util.Extractor{} 28 | meta.Name = name 29 | if strings.HasPrefix(ftype, "Enum8(") && strings.HasSuffix(ftype, ")") { 30 | meta.Type = "Enum8" 31 | meta.RawType = ftype 32 | meta.EnumData, err = decomposeEnumArgs(ftype) 33 | if err != nil { 34 | return 35 | } 36 | } else if strings.HasPrefix(ftype, "Enum16(") && strings.HasSuffix(ftype, ")") { 37 | meta.Type = "Enum16" 38 | meta.RawType = ftype 39 | meta.EnumData, err = decomposeEnumArgs(ftype) 40 | if err != nil { 41 | return 42 | } 43 | } else if strings.HasPrefix(ftype, "Array(") && strings.HasSuffix(ftype, ")") { 44 | meta.Type = "Array" 45 | meta.RawType = ftype 46 | submeta, err := retreiveField("", ftype[6:len(ftype)-1]) 47 | if err != nil { 48 | return meta, err 49 | } 50 | meta.Subtype = &submeta 51 | } else if strings.HasPrefix(ftype, "FixedString(") && strings.HasSuffix(ftype, ")") { 52 | meta.Type = "FixedString" 53 | meta.RawType = ftype 54 | submeta := ftype[len("FixedString(") : len(ftype)-1] 55 | length, err := strconv.ParseInt(submeta, 10, 64) 56 | if err != nil { 57 | return meta, err 58 | } 59 | meta.FixedStringLen = int(length) 60 | } else if strings.HasPrefix(ftype, "Nullable(") && strings.HasSuffix(ftype, ")") { 61 | meta.Type = "Nullable" 62 | meta.RawType = ftype 63 | submeta, err := retreiveField(name, ftype[9:len(ftype)-1]) 64 | if err != nil { 65 | return meta, err 66 | } 67 | meta.Subtype = &submeta 68 | } else if ok, _ := decExtractor.Extract(ftype); ok { 69 | meta.Type = "Decimal" 70 | meta.RawType = ftype 71 | meta.Decimal.Precision = decExtractor.Precision 72 | meta.Decimal.Scale = decExtractor.Scale 73 | switch { 74 | case decExtractor.Precision <= 9: 75 | meta.Decimal.Bits = 32 76 | meta.Type += "32" 77 | case decExtractor.Precision <= 18: 78 | meta.Decimal.Bits = 64 79 | meta.Type += "64" 80 | case decExtractor.Precision <= 38: 81 | meta.Decimal.Bits = 128 82 | meta.Type += "128" 83 | } 84 | } else { 85 | meta.Type = ftype 86 | meta.RawType = ftype 87 | } 88 | return 89 | } 90 | 91 | // RetrieveTableMeta retrieves clickhouse table metainformation 92 | func RetrieveTableMeta(conn *sql.DB, table string) (res []FieldMeta, err error) { 93 | rows, err := conn.Query("SELECT name, type FROM system.columns WHERE table = ?", table) 94 | if err != nil { 95 | return 96 | } 97 | 98 | var fieldName string 99 | var fieldType string 100 | 101 | for rows.Next() { 102 | if err := rows.Scan(&fieldName, &fieldType); err != nil { 103 | return res, err 104 | } 105 | meta, err := retreiveField(fieldName, fieldType) 106 | if err != nil { 107 | return nil, err 108 | } 109 | res = append(res, meta) 110 | } 111 | if rows.Err() != nil { 112 | return res, rows.Err() 113 | } 114 | return 115 | } 116 | -------------------------------------------------------------------------------- /internal/generator/chstuff/decomposeEnum.go: -------------------------------------------------------------------------------- 1 | package chstuff 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | "strings" 7 | ) 8 | 9 | // Enum{8,16}('arg1'=val1,....,'argN'=valN) arguments decompositor 10 | func decomposeEnumArgs(enum string) (res map[string]int, err error) { 11 | res = map[string]int{} 12 | makeError := func(appendix string) { 13 | err = fmt.Errorf("%s is not an enum"+appendix, enum) 14 | } 15 | 16 | // Must be either Enum8 or Enum16 17 | if !(strings.HasPrefix(enum, "Enum8") || strings.HasPrefix(enum, "Enum16")) { 18 | makeError("") 19 | return 20 | } 21 | 22 | // Locate data between ( and ) 23 | pos := strings.IndexByte(enum, '(') 24 | if pos < 0 { 25 | makeError("") 26 | return 27 | } 28 | data := enum[pos+1:] 29 | pos = strings.IndexByte(data, ')') 30 | if pos < 0 { 31 | makeError("") 32 | return 33 | } 34 | data = data[:pos] 35 | 36 | // Extract arguments 37 | for _, litCouple := range strings.Split(data, ",") { 38 | couple := strings.Split(litCouple, "=") 39 | if len(couple) != 2 { 40 | makeError(". The most probable reason is enum key value having unnacceptable characters. Try to use alnums only") 41 | return 42 | } 43 | key := strings.Trim(couple[0], " ") 44 | key = strings.TrimPrefix(key, "'") 45 | key = strings.TrimSuffix(key, "'") 46 | num, err := strconv.Atoi(strings.TrimSpace(couple[1])) 47 | if err != nil { 48 | return res, fmt.Errorf("Cannot parse numeric value `%s` for %s", couple[1], key) 49 | } 50 | res[key] = int(num) 51 | } 52 | return 53 | } 54 | -------------------------------------------------------------------------------- /internal/generator/date_field.go: -------------------------------------------------------------------------------- 1 | package generator 2 | 3 | // Date Type implementation 4 | type Date struct { 5 | field string 6 | fieldType string 7 | } 8 | 9 | // NewDate constructor 10 | func NewDate(field, fieldType string) *Date { 11 | return &Date{ 12 | field: field, 13 | fieldType: fieldType, 14 | } 15 | } 16 | 17 | // FieldName ... 18 | func (d *Date) FieldName(gen Generator) string { 19 | return d.field 20 | } 21 | 22 | // FieldTypeName ... 23 | func (d *Date) FieldTypeName(gen Generator) string { 24 | return d.fieldType 25 | } 26 | 27 | // TypeName ... 28 | func (d *Date) TypeName(gen Generator) string { 29 | return gen.UneasyTypeName(d.field) 30 | } 31 | 32 | // ArgName ... 33 | func (d *Date) ArgName(gen Generator) string { 34 | return gen.VarName(d.field) 35 | } 36 | 37 | // AccessName ... 38 | func (d *Date) AccessName(gen Generator) string { 39 | return gen.HelperName(d.field) 40 | } 41 | 42 | // NativeTypeName Date implementation 43 | func (d *Date) NativeTypeName(gen Generator) string { 44 | return gen.Uint16NativeTypeName() 45 | } 46 | 47 | // Encoding ... 48 | func (d *Date) Encoding(source string, gen Generator) error { 49 | return gen.Uint16Encoding(source) 50 | } 51 | 52 | // Helper ... 53 | func (d *Date) Helper(gen Generator) error { 54 | return gen.DateHelpers(d) 55 | } 56 | 57 | // TestingTypeName ... 58 | func (d *Date) TestingTypeName(gen Generator) string { 59 | return gen.DateTestingTypeName() 60 | } 61 | 62 | // TestEncoding ... 63 | func (d *Date) TestEncoding(source string, gen Generator) error { 64 | return gen.DateTestEncoding(source) 65 | } 66 | -------------------------------------------------------------------------------- /internal/generator/datetime_field.go: -------------------------------------------------------------------------------- 1 | package generator 2 | 3 | // DateTime Type implementation 4 | type DateTime struct { 5 | field string 6 | fieldType string 7 | } 8 | 9 | // NewDateTime constructor 10 | func NewDateTime(field, fieldType string) *DateTime { 11 | return &DateTime{ 12 | field: field, 13 | fieldType: fieldType, 14 | } 15 | } 16 | 17 | // FieldName ... 18 | func (dt *DateTime) FieldName(gen Generator) string { 19 | return dt.field 20 | } 21 | 22 | // FieldTypeName ... 23 | func (dt *DateTime) FieldTypeName(gen Generator) string { 24 | return dt.fieldType 25 | } 26 | 27 | // TypeName ... 28 | func (dt *DateTime) TypeName(gen Generator) string { 29 | return gen.UneasyTypeName(dt.field) 30 | } 31 | 32 | // ArgName ... 33 | func (dt *DateTime) ArgName(gen Generator) string { 34 | return gen.VarName(dt.field) 35 | } 36 | 37 | // AccessName ... 38 | func (dt *DateTime) AccessName(gen Generator) string { 39 | return gen.HelperName(dt.field) 40 | } 41 | 42 | // NativeTypeName ... 43 | func (dt *DateTime) NativeTypeName(gen Generator) string { 44 | return gen.Uint32NativeTypeName() 45 | } 46 | 47 | // Encoding ... 48 | func (dt *DateTime) Encoding(source string, gen Generator) error { 49 | return gen.Uint32Encoding(source) 50 | } 51 | 52 | // Helper ... 53 | func (dt *DateTime) Helper(gen Generator) error { 54 | return gen.DateTimeHelpers(dt) 55 | } 56 | 57 | // TestingTypeName ... 58 | func (dt *DateTime) TestingTypeName(gen Generator) string { 59 | return gen.DateTimeTestingTypeName() 60 | } 61 | 62 | // TestEncoding ... 63 | func (dt *DateTime) TestEncoding(source string, gen Generator) error { 64 | return gen.DateTimeTestEncoding(source) 65 | } 66 | -------------------------------------------------------------------------------- /internal/generator/decimal128_field.go: -------------------------------------------------------------------------------- 1 | package generator 2 | 3 | // Decimal128 type implementation 4 | type Decimal128 struct { 5 | field string 6 | fieldType string 7 | precision int 8 | scale int 9 | } 10 | 11 | func NewDecimal128(field string, fieldType string, precision int, scale int) *Decimal128 { 12 | return &Decimal128{field: field, fieldType: fieldType, precision: precision, scale: scale} 13 | } 14 | 15 | func (d *Decimal128) FieldName(gen Generator) string { 16 | return d.field 17 | } 18 | 19 | func (d *Decimal128) FieldTypeName(gen Generator) string { 20 | return d.fieldType 21 | } 22 | 23 | func (d *Decimal128) TypeName(gen Generator) string { 24 | return gen.UneasyTypeName(d.field) 25 | } 26 | 27 | func (d *Decimal128) ArgName(gen Generator) string { 28 | return gen.VarName(d.field) 29 | } 30 | 31 | func (d *Decimal128) AccessName(gen Generator) string { 32 | return gen.HelperName(d.field) 33 | } 34 | 35 | func (d *Decimal128) NativeTypeName(gen Generator) string { 36 | return gen.Dec128NativeTypeName() 37 | } 38 | 39 | func (d *Decimal128) Encoding(source string, gen Generator) error { 40 | return gen.Dec128Encoding(source) 41 | } 42 | 43 | func (d *Decimal128) Helper(gen Generator) error { 44 | return gen.Dec128Helpers(d) 45 | } 46 | 47 | func (d *Decimal128) TestingTypeName(gen Generator) string { 48 | return gen.Dec128TestingTypeName() 49 | } 50 | 51 | func (d *Decimal128) TestEncoding(source string, gen Generator) error { 52 | return gen.Dec128TestEncoding(d.scale, source) 53 | } 54 | -------------------------------------------------------------------------------- /internal/generator/decimal32_field.go: -------------------------------------------------------------------------------- 1 | package generator 2 | 3 | // Decimal32 type implementation 4 | type Decimal32 struct { 5 | field string 6 | fieldType string 7 | precision int 8 | scale int 9 | } 10 | 11 | func NewDecimal32(field string, fieldType string, precision int, scale int) *Decimal32 { 12 | return &Decimal32{field: field, fieldType: fieldType, precision: precision, scale: scale} 13 | } 14 | 15 | func (d *Decimal32) FieldName(gen Generator) string { 16 | return d.field 17 | } 18 | 19 | func (d *Decimal32) FieldTypeName(gen Generator) string { 20 | return d.fieldType 21 | } 22 | 23 | func (d *Decimal32) TypeName(gen Generator) string { 24 | return gen.EasyTypeName(d.field) 25 | } 26 | 27 | func (d *Decimal32) ArgName(gen Generator) string { 28 | return gen.VarName(d.field) 29 | } 30 | 31 | func (d *Decimal32) AccessName(gen Generator) string { 32 | return gen.HelperName(d.field) 33 | } 34 | 35 | func (d *Decimal32) NativeTypeName(gen Generator) string { 36 | return gen.Uint32NativeTypeName() 37 | } 38 | 39 | func (d *Decimal32) Encoding(source string, gen Generator) error { 40 | return gen.Uint32Encoding(source) 41 | } 42 | 43 | func (d *Decimal32) Helper(gen Generator) error { 44 | return nil 45 | } 46 | 47 | func (d *Decimal32) TestingTypeName(gen Generator) string { 48 | return gen.Dec32TestingTypeName() 49 | } 50 | 51 | func (d *Decimal32) TestEncoding(source string, gen Generator) error { 52 | return gen.Dec32TestEncoding(d.scale, source) 53 | } 54 | -------------------------------------------------------------------------------- /internal/generator/decimal64_field.go: -------------------------------------------------------------------------------- 1 | package generator 2 | 3 | // Decimal64 type implementation 4 | type Decimal64 struct { 5 | field string 6 | fieldType string 7 | precision int 8 | scale int 9 | } 10 | 11 | func NewDecimal64(field string, fieldType string, precision int, scale int) *Decimal64 { 12 | return &Decimal64{field: field, fieldType: fieldType, precision: precision, scale: scale} 13 | } 14 | 15 | func (d *Decimal64) FieldName(gen Generator) string { 16 | return d.field 17 | } 18 | 19 | func (d *Decimal64) FieldTypeName(gen Generator) string { 20 | return d.fieldType 21 | } 22 | 23 | func (d *Decimal64) TypeName(gen Generator) string { 24 | return gen.EasyTypeName(d.field) 25 | } 26 | 27 | func (d *Decimal64) ArgName(gen Generator) string { 28 | return gen.VarName(d.field) 29 | } 30 | 31 | func (d *Decimal64) AccessName(gen Generator) string { 32 | return gen.HelperName(d.field) 33 | } 34 | 35 | func (d *Decimal64) NativeTypeName(gen Generator) string { 36 | return gen.Uint64NativeTypeName() 37 | } 38 | 39 | func (d *Decimal64) Encoding(source string, gen Generator) error { 40 | return gen.Uint64Encoding(source) 41 | } 42 | 43 | func (d *Decimal64) Helper(gen Generator) error { 44 | return nil 45 | } 46 | 47 | func (d *Decimal64) TestingTypeName(gen Generator) string { 48 | return gen.Dec64TestingTypeName() 49 | } 50 | 51 | func (d *Decimal64) TestEncoding(source string, gen Generator) error { 52 | return gen.Dec64TestEncoding(d.scale, source) 53 | } 54 | -------------------------------------------------------------------------------- /internal/generator/enum16_field.go: -------------------------------------------------------------------------------- 1 | package generator 2 | 3 | // Enum16 Type implementation 4 | type Enum16 struct { 5 | field string 6 | fieldType string 7 | choices map[string]int 8 | } 9 | 10 | // NewEnum16 constructor 11 | func NewEnum16(field, fieldType string, choices map[string]int) *Enum16 { 12 | return &Enum16{ 13 | field: field, 14 | fieldType: fieldType, 15 | choices: choices, 16 | } 17 | } 18 | 19 | // FieldName ... 20 | func (e16 *Enum16) FieldName(gen Generator) string { 21 | return e16.field 22 | } 23 | 24 | // FieldTypeName ... 25 | func (e16 *Enum16) FieldTypeName(gen Generator) string { 26 | return e16.fieldType 27 | } 28 | 29 | // TypeName ... 30 | func (e16 *Enum16) TypeName(gen Generator) string { 31 | return gen.UneasyTypeName(e16.field) 32 | } 33 | 34 | // ArgName ... 35 | func (e16 *Enum16) ArgName(gen Generator) string { 36 | return gen.VarName(e16.field) 37 | } 38 | 39 | // AccessName ... 40 | func (e16 *Enum16) AccessName(gen Generator) string { 41 | return gen.HelperName(e16.field) 42 | } 43 | 44 | // NativeTypeName ... 45 | func (e16 *Enum16) NativeTypeName(gen Generator) string { 46 | return gen.Int16NativeTypeName() 47 | } 48 | 49 | // Encoding *Enum16 implementation 50 | func (e16 *Enum16) Encoding(source string, gen Generator) error { 51 | return gen.Int16Encoding(source) 52 | } 53 | 54 | // Helper ... 55 | func (e16 *Enum16) Helper(gen Generator) error { 56 | return gen.EnumHelpers(e16, e16.choices) 57 | } 58 | 59 | // TestingTypeName ... 60 | func (e16 *Enum16) TestingTypeName(gen Generator) string { 61 | return gen.EnumTestingTypeName() 62 | } 63 | 64 | // TestEncoding ... 65 | func (e16 *Enum16) TestEncoding(source string, gen Generator) error { 66 | return gen.EnumTestEncoding(source, e16.choices) 67 | } 68 | -------------------------------------------------------------------------------- /internal/generator/enum8_field.go: -------------------------------------------------------------------------------- 1 | package generator 2 | 3 | // Enum8 Type implementation 4 | type Enum8 struct { 5 | field string 6 | fieldType string 7 | choices map[string]int 8 | } 9 | 10 | // NewEnum8 constructor 11 | func NewEnum8(field, fieldType string, choices map[string]int) *Enum8 { 12 | return &Enum8{ 13 | field: field, 14 | fieldType: fieldType, 15 | choices: choices, 16 | } 17 | } 18 | 19 | // FieldName ... 20 | func (e8 *Enum8) FieldName(gen Generator) string { 21 | return e8.field 22 | } 23 | 24 | // FieldTypeName ... 25 | func (e8 *Enum8) FieldTypeName(gen Generator) string { 26 | return e8.fieldType 27 | } 28 | 29 | // TypeName ... 30 | func (e8 *Enum8) TypeName(gen Generator) string { 31 | return gen.UneasyTypeName(e8.field) 32 | } 33 | 34 | // ArgName ... 35 | func (e8 *Enum8) ArgName(gen Generator) string { 36 | return gen.VarName(e8.field) 37 | } 38 | 39 | // AccessName ... 40 | func (e8 *Enum8) AccessName(gen Generator) string { 41 | return gen.HelperName(e8.field) 42 | } 43 | 44 | // NativeTypeName ... 45 | func (e8 *Enum8) NativeTypeName(gen Generator) string { 46 | return gen.Int8NativeTypeName() 47 | } 48 | 49 | // Encoding *Enum8 implementation 50 | func (e8 *Enum8) Encoding(source string, gen Generator) error { 51 | return gen.Int8Encoding(source) 52 | } 53 | 54 | // Helper ... 55 | func (e8 *Enum8) Helper(gen Generator) error { 56 | return gen.EnumHelpers(e8, e8.choices) 57 | } 58 | 59 | // TestingTypeName ... 60 | func (e8 *Enum8) TestingTypeName(gen Generator) string { 61 | return gen.EnumTestingTypeName() 62 | } 63 | 64 | // TestEncoding ... 65 | func (e8 *Enum8) TestEncoding(source string, gen Generator) error { 66 | return gen.EnumTestEncoding(source, e8.choices) 67 | } 68 | -------------------------------------------------------------------------------- /internal/generator/field.go: -------------------------------------------------------------------------------- 1 | package generator 2 | 3 | // Field is a generic field representation 4 | type Field interface { 5 | // FieldName returns raw field name generation as it was in a clickhouse 6 | FieldName(gen Generator) string 7 | 8 | // FieldTypeName causes a raw field type generation as it was in a clickhouse 9 | FieldTypeName(gen Generator) string 10 | 11 | // TypeName returns safe type name 12 | // It will be (Golang) 13 | // type Access int32 14 | // for access Int32 clickhouse field 15 | // Or (C++) 16 | // typedef Access int32_t; 17 | TypeName(gen Generator) string 18 | 19 | // ArgName returns argument name to use as a TypeName parameter 20 | // It will be (Golang) 21 | // … access Access … 22 | // for access clickhouse field 23 | // Or (C++) 24 | // … Access access … 25 | ArgName(gen Generator) string 26 | 27 | // AccessName returns "namespace" name that keeps helpers. 28 | // For instance we have 29 | // direction Enum8('to'=0, 'from'=1, 'blocked'=2) 30 | // field in a clickhouse instance table `table` 31 | // It obviously a bad idea to use codes (0, 1, 2) for encoding direction 32 | // In this case (C++) the TypeName will be table::DirectionType instead of table::Direction 33 | // and the access name will be table::Direction. 34 | // Then the code like 35 | // namespace table { 36 | // … 37 | // typedef char DirectionType; 38 | // namespace Direction { 39 | // const DirectionType to = 0; 40 | // const DirectionType from = 1; 41 | // const DirectionType blocked = 2; 42 | // } 43 | // … 44 | // } 45 | // The Go code is a bit more sophisticated for this piece due to its lack of nested namespaces 46 | // (will use special public object instead and a bit of boilerplate code to fill the data) 47 | AccessName(gen Generator) string 48 | 49 | // NativeTypeName returns native type name generation 50 | NativeTypeName(gen Generator) string 51 | 52 | // Encoding causes an encoding code generation 53 | Encoding(source string, gen Generator) error 54 | 55 | // Helper generation for the field 56 | Helper(gen Generator) error 57 | 58 | // Testing stuff 59 | 60 | // TestingTypeName returns a type name used for testing 61 | TestingTypeName(gen Generator) string 62 | 63 | // TestEncoding causes an encoding code generation used for testing purposes 64 | TestEncoding(source string, gen Generator) error 65 | } 66 | -------------------------------------------------------------------------------- /internal/generator/field_set.go: -------------------------------------------------------------------------------- 1 | package generator 2 | 3 | import "strings" 4 | 5 | // FieldSet represents whole set of table fields 6 | type FieldSet struct { 7 | g Generator 8 | fields []Field 9 | nests map[string][]Field 10 | } 11 | 12 | // NewFieldSet constructor 13 | func NewFieldSet(g Generator) *FieldSet { 14 | return &FieldSet{ 15 | g: g, 16 | nests: map[string][]Field{}, 17 | } 18 | } 19 | 20 | // Add adds field to the field set 21 | func (fs *FieldSet) Add(field Field) { 22 | data := strings.Split(field.FieldName(fs.g), ".") 23 | fs.fields = append(fs.fields, field) 24 | if len(data) > 1 { 25 | fieldName := data[0] 26 | fs.nests[fieldName] = append(fs.nests[fieldName], field) 27 | } 28 | } 29 | 30 | // List returns list of fields 31 | func (fs *FieldSet) List() []Field { 32 | return fs.fields 33 | } 34 | 35 | // Nests returns dictionary of { nested field → list of its subfields } 36 | func (fs *FieldSet) Nests() map[string][]Field { 37 | return fs.nests 38 | } 39 | -------------------------------------------------------------------------------- /internal/generator/fixed_string_field.go: -------------------------------------------------------------------------------- 1 | package generator 2 | 3 | // FixedString Type implementation 4 | type FixedString struct { 5 | field string 6 | fieldType string 7 | stringLength int 8 | } 9 | 10 | // NewFixedString constructor 11 | func NewFixedString(field, fieldType string, length int) *FixedString { 12 | return &FixedString{ 13 | field: field, 14 | fieldType: fieldType, 15 | stringLength: length, 16 | } 17 | } 18 | 19 | // FieldName ... 20 | func (fs *FixedString) FieldName(gen Generator) string { 21 | return fs.field 22 | } 23 | 24 | // FieldTypeName ... 25 | func (fs *FixedString) FieldTypeName(gen Generator) string { 26 | return fs.fieldType 27 | } 28 | 29 | // TypeName ... 30 | func (fs *FixedString) TypeName(gen Generator) string { 31 | return gen.EasyTypeName(fs.field) 32 | } 33 | 34 | // ArgName ... 35 | func (fs *FixedString) ArgName(gen Generator) string { 36 | return gen.VarName(fs.field) 37 | } 38 | 39 | // AccessName ... 40 | func (fs *FixedString) AccessName(gen Generator) string { 41 | return gen.HelperName(fs.field) 42 | } 43 | 44 | // NativeTypeName ... 45 | func (fs *FixedString) NativeTypeName(gen Generator) string { 46 | return gen.FixedStringNativeTypeName() 47 | } 48 | 49 | // Encoding ... 50 | func (fs *FixedString) Encoding(source string, gen Generator) error { 51 | return gen.FixedStringEncoding(source, fs.stringLength) 52 | } 53 | 54 | // Helper ... 55 | func (fs *FixedString) Helper(gen Generator) error { 56 | return nil 57 | } 58 | 59 | // TestingTypeName ... 60 | func (fs *FixedString) TestingTypeName(gen Generator) string { 61 | return gen.FixedStringTestingTypeName() 62 | } 63 | 64 | // TestEncoding ... 65 | func (fs *FixedString) TestEncoding(source string, gen Generator) error { 66 | return gen.FixedStringTestEncoding(source, fs.stringLength) 67 | } 68 | -------------------------------------------------------------------------------- /internal/generator/float32_field.go: -------------------------------------------------------------------------------- 1 | package generator 2 | 3 | // Float32 Type implementation 4 | type Float32 struct { 5 | field string 6 | fieldType string 7 | } 8 | 9 | // NewFloat32 constructor 10 | func NewFloat32(field string, fieldType string) *Float32 { 11 | return &Float32{ 12 | field: field, 13 | fieldType: fieldType, 14 | } 15 | } 16 | 17 | // FieldName ... 18 | func (f32 *Float32) FieldName(gen Generator) string { 19 | return f32.field 20 | } 21 | 22 | // FieldTypeName ... 23 | func (f32 *Float32) FieldTypeName(gen Generator) string { 24 | return f32.fieldType 25 | } 26 | 27 | // TypeName ... 28 | func (f32 *Float32) TypeName(gen Generator) string { 29 | return gen.EasyTypeName(f32.field) 30 | } 31 | 32 | // ArgName ... 33 | func (f32 *Float32) ArgName(gen Generator) string { 34 | return gen.VarName(f32.field) 35 | } 36 | 37 | // AccessName ... 38 | func (f32 *Float32) AccessName(gen Generator) string { 39 | return gen.HelperName(f32.field) 40 | } 41 | 42 | // NativeTypeName ... 43 | func (f32 Float32) NativeTypeName(gen Generator) string { 44 | return gen.Float32NativeTypeName() 45 | } 46 | 47 | // Encoding ... 48 | func (f32 Float32) Encoding(source string, gen Generator) error { 49 | return gen.Float32Encoding(source) 50 | } 51 | 52 | // Helper ... 53 | func (f32 *Float32) Helper(gen Generator) error { 54 | return nil 55 | } 56 | 57 | // TestingTypeName ... 58 | func (f32 Float32) TestingTypeName(gen Generator) string { 59 | return gen.Float32TestingTypeName() 60 | } 61 | 62 | // TestEncoding ... 63 | func (f32 Float32) TestEncoding(source string, gen Generator) error { 64 | return gen.Float32TestEncoding(source) 65 | } 66 | -------------------------------------------------------------------------------- /internal/generator/float64_field.go: -------------------------------------------------------------------------------- 1 | package generator 2 | 3 | // Float64 Type implementation 4 | type Float64 struct { 5 | field string 6 | fieldType string 7 | } 8 | 9 | // NewFloat64 constructor 10 | func NewFloat64(field string, fieldType string) *Float64 { 11 | return &Float64{ 12 | field: field, 13 | fieldType: fieldType, 14 | } 15 | } 16 | 17 | // FieldName ... 18 | func (f64 *Float64) FieldName(gen Generator) string { 19 | return f64.field 20 | } 21 | 22 | // FieldTypeName ... 23 | func (f64 *Float64) FieldTypeName(gen Generator) string { 24 | return f64.fieldType 25 | } 26 | 27 | // TypeName ... 28 | func (f64 *Float64) TypeName(gen Generator) string { 29 | return gen.EasyTypeName(f64.field) 30 | } 31 | 32 | // ArgName ... 33 | func (f64 *Float64) ArgName(gen Generator) string { 34 | return gen.VarName(f64.field) 35 | } 36 | 37 | // AccessName ... 38 | func (f64 *Float64) AccessName(gen Generator) string { 39 | return gen.HelperName(f64.field) 40 | } 41 | 42 | // NativeTypeName ... 43 | func (f64 Float64) NativeTypeName(gen Generator) string { 44 | return gen.Float64NativeTypeName() 45 | } 46 | 47 | // Encoding ... 48 | func (f64 Float64) Encoding(source string, gen Generator) error { 49 | return gen.Float64Encoding(source) 50 | } 51 | 52 | // Helper ... 53 | func (f64 *Float64) Helper(gen Generator) error { 54 | return nil 55 | } 56 | 57 | // TestingTypeName ... 58 | func (f64 Float64) TestingTypeName(gen Generator) string { 59 | return gen.Float64TestingTypeName() 60 | } 61 | 62 | // TestEncoding ... 63 | func (f64 Float64) TestEncoding(source string, gen Generator) error { 64 | return gen.Float64TestEncoding(source) 65 | } 66 | -------------------------------------------------------------------------------- /internal/generator/generation.go: -------------------------------------------------------------------------------- 1 | package generator 2 | 3 | // Generate generates encoding stuff 4 | func Generate(gen Generator, dateField func(gen Generator) string, fields *FieldSet) (err error) { 5 | dateFieldName := dateField(gen) 6 | pieceGens := []func(*FieldSet) error{ 7 | gen.Types, 8 | gen.EncoderInterface, 9 | gen.EncoderDef, 10 | gen.EncodingMethod, 11 | } 12 | if dateFieldName != "" { 13 | pieceGens = append(pieceGens, 14 | gen.DateFilterEncoderDef, 15 | func(f *FieldSet) error { return gen.DateFilterEncodingMethod(dateField(gen), f) }, 16 | ) 17 | } 18 | pieceGens = append(pieceGens, 19 | gen.VoidEncoderDef, 20 | gen.VoidEncodingMethod, 21 | gen.TestDef, 22 | gen.TestEncoderDef, 23 | gen.TestEncodingMethod, 24 | ) 25 | for _, piece := range pieceGens { 26 | if err = piece(fields); err != nil { 27 | return 28 | } 29 | } 30 | 31 | return 32 | } 33 | -------------------------------------------------------------------------------- /internal/generator/generator.go: -------------------------------------------------------------------------------- 1 | package generator 2 | 3 | import "io" 4 | 5 | // Generator abstraction 6 | type Generator interface { 7 | RawData(string) error 8 | 9 | // Custom dest is to workaround strict Golang policies unused imports 10 | Header(dest io.Writer) error 11 | 12 | //////////////////////////////////// 13 | ////////////// Production purposes 14 | 15 | // Types generates type definitions for all fields 16 | Types(*FieldSet) error 17 | 18 | // TypeDef generates type definition for the field 19 | TypeDef(Field) error 20 | 21 | // HelperDef generates type helpers for the field 22 | HelperDef(Field) error 23 | 24 | // EncoderInterface generates encoding interface 25 | EncoderInterface(*FieldSet) error 26 | 27 | // EncoderDef generates production encoder 28 | EncoderDef(*FieldSet) error 29 | 30 | // EncodeMethod generates 31 | EncodingMethod(*FieldSet) error 32 | 33 | //////////////////////////////////// 34 | ////////////// Production date filter 35 | 36 | // FilterEncoderDef generates encoder with filter 37 | DateFilterEncoderDef(*FieldSet) error 38 | 39 | // FilterEncodingMethod generates 40 | DateFilterEncodingMethod(dateField string, fset *FieldSet) error 41 | 42 | //////////////////////////////////// 43 | ////////////// Void purposes 44 | 45 | // VoidEncoderDef generates encoder that does nothing 46 | VoidEncoderDef(*FieldSet) error 47 | 48 | //VoidEncodingMethod 49 | VoidEncodingMethod(*FieldSet) error 50 | 51 | //////////////////////////////////// 52 | ////////////// Testing purposes 53 | 54 | // TestDef generates record representation for testing purposes 55 | TestDef(*FieldSet) error 56 | 57 | // TestEncoderDef generates encoder aimed for testing 58 | TestEncoderDef(*FieldSet) error 59 | 60 | // TestEncodingMethod Encode method generator 61 | TestEncodingMethod(*FieldSet) error 62 | 63 | EasyTypeName(string) string 64 | UneasyTypeName(string) string 65 | HelperName(string) string 66 | VarName(string) string 67 | 68 | Int8NativeTypeName() string 69 | Int16NativeTypeName() string 70 | Int32NativeTypeName() string 71 | Int64NativeTypeName() string 72 | Uint8NativeTypeName() string 73 | Uint16NativeTypeName() string 74 | Uint32NativeTypeName() string 75 | Uint64NativeTypeName() string 76 | Dec128NativeTypeName() string 77 | Float32NativeTypeName() string 78 | Float64NativeTypeName() string 79 | StringNativeTypeName() string 80 | FixedStringNativeTypeName() string 81 | UUIDNativeTypeName() string 82 | ArrayNativeTypeName(itemType Field) string 83 | NullableNativeTypeName(itemType Field) string 84 | NullableStringNativeTypeName() string 85 | NullableArrayNativeTypeName(itemType Field) string 86 | 87 | Int8TestingTypeName() string 88 | Int16TestingTypeName() string 89 | Int32TestingTypeName() string 90 | Int64TestingTypeName() string 91 | Uint8TestingTypeName() string 92 | Uint16TestingTypeName() string 93 | Uint32TestingTypeName() string 94 | Uint64TestingTypeName() string 95 | Dec32TestingTypeName() string 96 | Dec64TestingTypeName() string 97 | Dec128TestingTypeName() string 98 | Float32TestingTypeName() string 99 | Float64TestingTypeName() string 100 | EnumTestingTypeName() string 101 | DateTestingTypeName() string 102 | DateTimeTestingTypeName() string 103 | StringTestingTypeName() string 104 | FixedStringTestingTypeName() string 105 | UUIDTestingTypeName() string 106 | ArrayTestingTypeName(itemType Field) string 107 | NullableTestingTypeName(itemType Field) string 108 | NullableStringTestingTypeName() string 109 | NullableArrayTestingTypeName(itemType Field) string 110 | 111 | Int8Encoding(string) error 112 | Int16Encoding(string) error 113 | Int32Encoding(string) error 114 | Int64Encoding(string) error 115 | Uint8Encoding(string) error 116 | Uint16Encoding(string) error 117 | Uint32Encoding(string) error 118 | Uint64Encoding(string) error 119 | Dec128Encoding(string) error 120 | Float32Encoding(string) error 121 | Float64Encoding(string) error 122 | DateEncoding(string) error 123 | DateTimeEncoding(string) error 124 | StringEncoding(string) error 125 | FixedStringEncoding(string, int) error 126 | UUIDEncoding(string) error 127 | ArrayEncoding(string, Field) error 128 | NullableEncoding(string, Field) error 129 | NullableArrayEncoding(string, Field) error 130 | NullableStringEncoding(string) error 131 | 132 | Int8TestEncoding(string) error 133 | Int16TestEncoding(string) error 134 | Int32TestEncoding(string) error 135 | Int64TestEncoding(string) error 136 | Uint8TestEncoding(string) error 137 | Uint16TestEncoding(string) error 138 | Uint32TestEncoding(string) error 139 | Uint64TestEncoding(string) error 140 | Dec32TestEncoding(int, string) error 141 | Dec64TestEncoding(int, string) error 142 | Dec128TestEncoding(int, string) error 143 | Float32TestEncoding(string) error 144 | Float64TestEncoding(string) error 145 | EnumTestEncoding(string, map[string]int) error 146 | DateTestEncoding(string) error 147 | DateTimeTestEncoding(string) error 148 | StringTestEncoding(string) error 149 | FixedStringTestEncoding(string, int) error 150 | UUIDTestEncoding(string) error 151 | ArrayTestEncoding(string, Field) error 152 | NullableTestEncoding(string, Field) error 153 | NullableStringTestEncoding(string) error 154 | NullableArrayTestEncoding(string, Field) error 155 | 156 | EnumHelpers(Field, map[string]int) error 157 | DateHelpers(Field) error 158 | DateTimeHelpers(Field) error 159 | Dec128Helpers(Field) error 160 | } 161 | -------------------------------------------------------------------------------- /internal/generator/gogen/encoder.go: -------------------------------------------------------------------------------- 1 | package gogen 2 | 3 | import ( 4 | "fmt" 5 | "text/template" 6 | 7 | "github.com/sirkon/ch-encode/internal/generator" 8 | ) 9 | 10 | // EncoderInterface ... 11 | func (gg *GoGen) EncoderInterface(field *generator.FieldSet) error { 12 | text := 13 | ` 14 | type {{.encoderName}} interface { 15 | Encode({{.args}}) error 16 | InsertCount() int 17 | ErrorCount() int 18 | } 19 | ` 20 | tmpl, err := template.New("interface").Parse(text) 21 | if err != nil { 22 | return err 23 | } 24 | return tmpl.Execute(gg.dest, map[string]string{ 25 | "encoderName": gg.interfaceEncoderName(), 26 | "args": gg.argList(field)}, 27 | ) 28 | } 29 | 30 | // EncoderDef ... 31 | func (gg *GoGen) EncoderDef(fs *generator.FieldSet) error { 32 | text := 33 | ` 34 | type {{.encoderName}} struct { 35 | insertCounter int 36 | buffer *bytes.Buffer 37 | helper *binenc.Encoder 38 | zeroes []byte 39 | dest io.Writer 40 | } 41 | 42 | func checkSchema(curCols []chinsert.Column) error { 43 | sample := []chinsert.Column{ 44 | {{ range .columns }}{Name: {{.Name}}, Type: {{.Type}}}, 45 | {{ end }}} 46 | for i, col := range sample { 47 | if i >= len(curCols) { 48 | return fmt.Errorf("column %s(%s) is missing in the current schema for table {{.table}}, outdated encoder?", col.Name, col.Type) 49 | } 50 | var colNum string 51 | switch i { 52 | case 0: 53 | colNum = "1st" 54 | case 1: 55 | colNum = "2nd" 56 | case 2: 57 | colNum = "3rd" 58 | default: 59 | colNum = fmt.Sprintf("%dth", i+1) 60 | } 61 | if col.Name != curCols[i].Name || col.Type != curCols[i].Type { 62 | return fmt.Errorf("table {{.table}} encoder expects for a %s column to be %s(%s), while it is %s(%s) in actual schema", colNum, col.Name, col.Type, curCols[i].Name, curCols[i].Type) 63 | } 64 | } 65 | if len(curCols) > len(sample) { 66 | mismatch := curCols[len(sample)] 67 | return fmt.Errorf("unexpected extra column %s(%s) in the current schema for table {{.table}}, outdated encoder?", mismatch.Name, mismatch.Type) 68 | } 69 | return nil 70 | } 71 | 72 | func New{{.encoderName}}(w chinsert.WriterWithSchemaCheck) (*{{.encoderName}},error) { 73 | columns, err := w.Schema() 74 | if err != nil { 75 | return nil,err 76 | } 77 | if err := checkSchema(columns); err != nil { 78 | return nil, err 79 | } 80 | buffer := &bytes.Buffer{} 81 | buffer.Grow(4096) 82 | return &{{.encoderName}}{ 83 | buffer: buffer, 84 | helper: binenc.New(), 85 | dest: w, 86 | zeroes: make([]byte, 64), 87 | }, nil 88 | } 89 | 90 | func (enc *{{.encoderName}}) InsertCount() int { 91 | return enc.insertCounter 92 | } 93 | ` 94 | gg.regImport("", "github.com/sirkon/ch-insert") 95 | gg.regImport("", "fmt") 96 | tmpl, err := template.New("encoder").Parse(text) 97 | if err != nil { 98 | return err 99 | } 100 | 101 | var fields []struct { 102 | Name string 103 | Type string 104 | } 105 | for _, f := range fs.List() { 106 | fields = append(fields, struct { 107 | Name string 108 | Type string 109 | }{Name: "`" + f.FieldName(gg) + "`", Type: "`" + f.FieldTypeName(gg) + "`"}) 110 | } 111 | 112 | return tmpl.Execute(gg.dest, map[string]interface{}{ 113 | "encoderName": gg.encoderName(), 114 | "columns": fields, 115 | "table": gg.table, 116 | }) 117 | } 118 | 119 | // EncodingMethod ... 120 | func (gg *GoGen) EncodingMethod(fields *generator.FieldSet) (err error) { 121 | text := "func (enc *%s) Encode(%s) error {\nenc.buffer.Reset();\n" 122 | if err = gg.RawData(fmt.Sprintf(text, gg.encoderName(), gg.argList(fields))); err != nil { 123 | return 124 | } 125 | if err = gg.constraints(fields); err != nil { 126 | return 127 | } 128 | for _, field := range fields.List() { 129 | if err = field.Encoding(field.ArgName(gg), gg); err != nil { 130 | return 131 | } 132 | if err = gg.RawData("\n"); err != nil { 133 | return 134 | } 135 | } 136 | err = gg.RawData(` 137 | enc.insertCounter++; 138 | _, err := enc.dest.Write(enc.buffer.Bytes()); 139 | return err}`) 140 | return 141 | } 142 | -------------------------------------------------------------------------------- /internal/generator/gogen/encoding.go: -------------------------------------------------------------------------------- 1 | package gogen 2 | 3 | import ( 4 | "fmt" 5 | "text/template" 6 | 7 | "io" 8 | 9 | "github.com/sirkon/ch-encode/internal/generator" 10 | ) 11 | 12 | // Int8Encoding ... 13 | func (gg *GoGen) Int8Encoding(source string) error { 14 | _, err := fmt.Fprintf(gg.dest, "enc.buffer.Write(enc.helper.Byte(byte(%s)));\n", source) 15 | return err 16 | } 17 | 18 | // Int16Encoding ... 19 | func (gg *GoGen) Int16Encoding(source string) error { 20 | _, err := fmt.Fprintf(gg.dest, "enc.buffer.Write(enc.helper.Int16(int16(%s)));\n", source) 21 | return err 22 | } 23 | 24 | // Int32Encoding ... 25 | func (gg *GoGen) Int32Encoding(source string) error { 26 | _, err := fmt.Fprintf(gg.dest, "enc.buffer.Write(enc.helper.Int32(int32(%s)));\n", source) 27 | return err 28 | } 29 | 30 | // Int64Encoding ... 31 | func (gg *GoGen) Int64Encoding(source string) error { 32 | _, err := fmt.Fprintf(gg.dest, "enc.buffer.Write(enc.helper.Int64(int64(%s)));\n", source) 33 | return err 34 | } 35 | 36 | // Uint8Encoding ... 37 | func (gg *GoGen) Uint8Encoding(source string) error { 38 | _, err := fmt.Fprintf(gg.dest, "enc.buffer.Write(enc.helper.Byte(byte(%s)));\n", source) 39 | return err 40 | } 41 | 42 | // Uint16Encoding ... 43 | func (gg *GoGen) Uint16Encoding(source string) error { 44 | _, err := fmt.Fprintf(gg.dest, "enc.buffer.Write(enc.helper.Uint16(uint16(%s)));\n", source) 45 | return err 46 | } 47 | 48 | // Uint32Encoding ... 49 | func (gg *GoGen) Uint32Encoding(source string) error { 50 | _, err := fmt.Fprintf(gg.dest, "enc.buffer.Write(enc.helper.Uint32(uint32(%s)));\n", source) 51 | return err 52 | } 53 | 54 | // Uint64Encoding ... 55 | func (gg *GoGen) Uint64Encoding(source string) error { 56 | _, err := fmt.Fprintf(gg.dest, "enc.buffer.Write(enc.helper.Uint64(uint64(%s)));\n", source) 57 | return err 58 | } 59 | 60 | func (gg *GoGen) Dec128Encoding(source string) error { 61 | _, err := fmt.Fprintf(gg.dest, 62 | ""+ 63 | "enc.buffer.Write(enc.helper.Uint64(uint64(%s.Lo)));\n"+ 64 | "enc.buffer.Write(enc.helper.Uint64(uint64(%s.Hi)));\n", 65 | source, source) 66 | return err 67 | } 68 | 69 | // Float32Encoding ... 70 | func (gg *GoGen) Float32Encoding(source string) error { 71 | gg.useUnsafe() 72 | _, err := fmt.Fprintf(gg.dest, "enc.buffer.Write(enc.helper.Uint32(*(*uint32)(unsafe.Pointer(&%s))));\n", source) 73 | return err 74 | } 75 | 76 | // Float64Encoding ... 77 | func (gg *GoGen) Float64Encoding(source string) error { 78 | gg.useUnsafe() 79 | _, err := fmt.Fprintf(gg.dest, "enc.buffer.Write(enc.helper.Uint64(*(*uint64)(unsafe.Pointer(&%s))));\n", source) 80 | return err 81 | } 82 | 83 | // DateEncoding ... 84 | func (gg *GoGen) DateEncoding(source string) error { 85 | gg.useTime() 86 | _, err := fmt.Fprintf(gg.dest, "enc.buffer.Write(enc.helper.Uint16(uint16(%s)));\n", source) 87 | return err 88 | } 89 | 90 | // DateTimeEncoding ... 91 | func (gg *GoGen) DateTimeEncoding(source string) error { 92 | gg.useTime() 93 | _, err := fmt.Fprintf(gg.dest, "enc.buffer.Write(enc.helper.Uint32(uint32(%s)));\n", source) 94 | return err 95 | } 96 | 97 | // StringEncoding ... 98 | func (gg *GoGen) StringEncoding(source string) error { 99 | _, err := fmt.Fprintf(gg.dest, 100 | ""+ 101 | "enc.buffer.Write(enc.helper.Uleb128(uint32(len([]byte(%s)))));\n"+ 102 | "enc.buffer.Write([]byte(%s));\n", 103 | source, source) 104 | return err 105 | } 106 | 107 | // FixedStringEncoding ... 108 | func (gg *GoGen) FixedStringEncoding(source string, length int) error { 109 | gg.useFmt() 110 | text := ` 111 | if len({{.var}}) != {{.length}} { 112 | return fmt.Errorf("string {{.var}} must be {{.length}} bytes long, got %d bytes intsead (\"\033[1m%s\033[0m\", %v)", len({{.var}}), string({{.var}}), {{.var}}) 113 | } 114 | enc.buffer.Write([]byte({{.var}})) 115 | ` 116 | t, err := template.New("fixed_string_encoding").Parse(text) 117 | if err != nil { 118 | return err 119 | } 120 | err = t.Execute(gg.dest, map[string]interface{}{ 121 | "var": source, 122 | "length": length, 123 | }) 124 | return err 125 | } 126 | 127 | // UUIDEncoding ... 128 | func (gg *GoGen) UUIDEncoding(source string) error { 129 | _, err := fmt.Fprintf(gg.dest, "enc.buffer.Write(%s[:]);\n", source) 130 | return err 131 | } 132 | 133 | // ArrayEncoding ... 134 | func (gg *GoGen) ArrayEncoding(source string, field generator.Field) error { 135 | text := ` 136 | enc.buffer.Write(enc.helper.Uleb128(uint32(len({{.var}})))); 137 | for _, arrayItem := range {{.var}} { 138 | ` 139 | tmpl, err := template.New("array_encoding").Parse(text) 140 | if err != nil { 141 | return err 142 | } 143 | err = tmpl.Execute(gg.dest, map[string]string{ 144 | "var": source, 145 | }) 146 | if err != nil { 147 | return err 148 | } 149 | if err = field.Encoding("arrayItem", gg); err != nil { 150 | return err 151 | } 152 | return gg.RawData("}") 153 | } 154 | 155 | // NullableEncoding ... 156 | func (gg *GoGen) NullableEncoding(source string, field generator.Field) error { 157 | if _, err := fmt.Fprintf(gg.dest, ";if %s != nil {\n", source); err != nil { 158 | return err 159 | } 160 | if _, err := io.WriteString(gg.dest, ";enc.buffer.WriteByte(byte(0));"); err != nil { 161 | return err 162 | } 163 | if err := field.Encoding("*"+source, gg); err != nil { 164 | return err 165 | } 166 | return gg.RawData("} else { enc.buffer.WriteByte(byte(1)) }") 167 | } 168 | 169 | // NullableArrayEncoding ... 170 | func (gg *GoGen) NullableArrayEncoding(source string, field generator.Field) error { 171 | return gg.NullableEncoding(source, field) 172 | } 173 | 174 | // NullableStringEncoding 175 | func (gg *GoGen) NullableStringEncoding(source string) error { 176 | if _, err := fmt.Fprintf(gg.dest, ";if %s != nil {\n", source); err != nil { 177 | return err 178 | } 179 | _, err := fmt.Fprintf(gg.dest, ""+"enc.buffer.WriteByte(byte(0));\n") 180 | if err != nil { 181 | return err 182 | } 183 | if err := gg.StringEncoding(source); err != nil { 184 | return err 185 | } 186 | if _, err := fmt.Fprintf(gg.dest, "; } else { enc.buffer.WriteByte(byte(1)) }"); err != nil { 187 | return err 188 | } 189 | return nil 190 | } 191 | -------------------------------------------------------------------------------- /internal/generator/gogen/fieldtypes.go: -------------------------------------------------------------------------------- 1 | package gogen 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/sirkon/ch-encode/internal/generator" 7 | ) 8 | 9 | // Types ... 10 | func (gg *GoGen) Types(fields *generator.FieldSet) (err error) { 11 | for _, field := range fields.List() { 12 | if err = gg.TypeDef(field); err != nil { 13 | return 14 | } 15 | if err = gg.HelperDef(field); err != nil { 16 | return 17 | } 18 | } 19 | return gg.RawData("\n") 20 | } 21 | 22 | // TypeDef ... 23 | func (gg *GoGen) TypeDef(field generator.Field) (err error) { 24 | _, err = fmt.Fprintf(gg.dest, "type %s %s;\n", field.TypeName(gg), field.NativeTypeName(gg)) 25 | return 26 | } 27 | 28 | // HelperDef ... 29 | func (gg *GoGen) HelperDef(field generator.Field) (err error) { 30 | if err = field.Helper(gg); err != nil { 31 | return 32 | } 33 | return gg.RawData("\n") 34 | } 35 | -------------------------------------------------------------------------------- /internal/generator/gogen/filterencoder.go: -------------------------------------------------------------------------------- 1 | package gogen 2 | 3 | import ( 4 | "strings" 5 | "text/template" 6 | 7 | "github.com/sirkon/ch-encode/internal/generator" 8 | ) 9 | 10 | // DateFilterEncoderDef ... 11 | func (gg *GoGen) DateFilterEncoderDef(field *generator.FieldSet) error { 12 | text := 13 | ` 14 | type {{.encoder}} struct { 15 | dayNo uint16 16 | encoder {{.interface}} 17 | } 18 | 19 | func New{{.encoder}}(dayNo uint16, enc {{.interface}}) *{{.encoder}} { 20 | return &{{.encoder}}{ 21 | dayNo: dayNo, 22 | encoder: enc, 23 | } 24 | } 25 | 26 | func (enc *{{.encoder}}) InsertCount() int { 27 | return enc.encoder.InsertCount(); 28 | } 29 | func (enc *{{.encoder}}) ErrorCount() int { 30 | return enc.encoder.ErrorCount(); 31 | } 32 | ` 33 | tmpl, err := template.New("filterer").Parse(text) 34 | if err != nil { 35 | return err 36 | } 37 | return tmpl.Execute(gg.dest, map[string]string{ 38 | "encoder": gg.filterEncoderName(), 39 | "interface": gg.interfaceEncoderName(), 40 | }) 41 | } 42 | 43 | // DateFilterEncodingMethod ... 44 | func (gg *GoGen) DateFilterEncodingMethod(dateArg string, fields *generator.FieldSet) error { 45 | text := 46 | ` 47 | func (enc *{{.encoder}}) Encode({{.args}}) error { 48 | if enc.dayNo != uint16({{.dateArg}}) { 49 | return nil 50 | } 51 | return enc.encoder.Encode({{.app}}) 52 | } 53 | ` 54 | tmpl, err := template.New("filterer").Parse(text) 55 | if err != nil { 56 | return err 57 | } 58 | app := []string{} 59 | for _, field := range fields.List() { 60 | app = append(app, field.ArgName(gg)) 61 | } 62 | return tmpl.Execute(gg.dest, map[string]string{ 63 | "encoder": gg.filterEncoderName(), 64 | "args": gg.argList(fields), 65 | "app": strings.Join(app, ", "), 66 | "dateArg": dateArg, 67 | }) 68 | } 69 | -------------------------------------------------------------------------------- /internal/generator/gogen/generator.go: -------------------------------------------------------------------------------- 1 | package gogen 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "text/template" 7 | 8 | "github.com/sirkon/ch-encode/internal/generator" 9 | "github.com/sirkon/gotify" 10 | ) 11 | 12 | var _ generator.Generator = &GoGen{} 13 | 14 | // GoGen encoder code generation for Go language 15 | type GoGen struct { 16 | goish *gotify.Gotify 17 | table string 18 | dest io.Writer 19 | imports map[string]string // path -> access name 20 | } 21 | 22 | // New Go generator constructor 23 | func New(table string, g *gotify.Gotify, dest io.Writer) *GoGen { 24 | res := &GoGen{ 25 | table: table, 26 | dest: dest, 27 | goish: g, 28 | imports: map[string]string{}, 29 | } 30 | res.useBytes() 31 | res.useBinEnc() 32 | res.useIO() 33 | return res 34 | } 35 | 36 | // RawData ... 37 | func (gg *GoGen) RawData(v string) error { 38 | _, err := gg.dest.Write([]byte(v)) 39 | return err 40 | } 41 | 42 | // regImport registers new import path 43 | func (gg *GoGen) regImport(importAs string, path string) { 44 | if prev, ok := gg.imports[path]; ok { 45 | if prev != importAs { 46 | panic(fmt.Errorf(`attempt to import path "%s" as %s, when it was registered before as %s`, path, importAs, prev)) 47 | } 48 | } 49 | gg.imports[path] = importAs 50 | } 51 | 52 | func (gg *GoGen) useTime() { 53 | gg.regImport("stdtime", "time") 54 | } 55 | 56 | func (gg *GoGen) useBinEnc() { 57 | gg.regImport("", "github.com/sirkon/binenc") 58 | } 59 | 60 | func (gg *GoGen) useBytes() { 61 | gg.regImport("", "bytes") 62 | } 63 | 64 | func (gg *GoGen) useIO() { 65 | gg.regImport("", "io") 66 | } 67 | 68 | func (gg *GoGen) useUnsafe() { 69 | gg.regImport("", "unsafe") 70 | } 71 | 72 | func (gg *GoGen) useFmt() { 73 | gg.regImport("", "fmt") 74 | } 75 | 76 | func (gg *GoGen) useGoogleUUID() { 77 | gg.regImport("googleUUID", "github.com/google/uuid") 78 | } 79 | 80 | const headerTemplate = ` 81 | package {{ .Package }} 82 | 83 | 84 | import ( 85 | {{ range $path, $access := .Imports }}{{ $access }} "{{ $path }}" {{ printf "\n" }}{{ end }}) 86 | ` 87 | 88 | // Header ... 89 | func (gg *GoGen) Header(dest io.Writer) error { 90 | pkgname := gg.goish.Package(gg.table) 91 | t := template.New("encoder header") 92 | tmpl, err := t.Parse(headerTemplate) 93 | if err != nil { 94 | return err 95 | } 96 | var ctx struct { 97 | Package string 98 | Imports map[string]string 99 | } 100 | ctx.Package = pkgname 101 | ctx.Imports = gg.imports 102 | if err := tmpl.Execute(dest, ctx); err != nil { 103 | return err 104 | } 105 | return nil 106 | } 107 | 108 | const constraintCheck = ` 109 | if len({{.First}}) != len({{.Current}}) { 110 | return fmt.Errorf("length mismatch between {{.First}} and {{.Current}} (%d != %d), lengths must be the same being subfields of {{.Nest}}", 111 | len({{.First}}), len({{.Current}})) 112 | } 113 | ` 114 | 115 | // Constraints ... 116 | func (gg *GoGen) constraints(fields *generator.FieldSet) error { 117 | nests := fields.Nests() 118 | t := template.New("constraint if") 119 | tmpl, err := t.Parse(constraintCheck) 120 | if err != nil { 121 | return err 122 | } 123 | var ctx struct { 124 | First string 125 | Current string 126 | Nest string 127 | } 128 | for nested, subfields := range nests { 129 | ctx.Nest = nested 130 | if len(subfields) <= 1 { 131 | continue 132 | } 133 | gg.useFmt() 134 | gg.dest.Write([]byte(fmt.Sprintf("\n// constraints on %s nested field", nested))) 135 | ctx.First = subfields[0].ArgName(gg) 136 | for _, restItem := range subfields[1:] { 137 | ctx.Current = restItem.ArgName(gg) 138 | if err = tmpl.Execute(gg.dest, ctx); err != nil { 139 | return err 140 | } 141 | } 142 | } 143 | return nil 144 | } 145 | -------------------------------------------------------------------------------- /internal/generator/gogen/helpers.go: -------------------------------------------------------------------------------- 1 | package gogen 2 | 3 | import ( 4 | "fmt" 5 | "sort" 6 | "strings" 7 | "text/template" 8 | 9 | "github.com/sirkon/ch-encode/internal/generator" 10 | ) 11 | 12 | // EnumHelpers ... 13 | func (gg *GoGen) EnumHelpers(field generator.Field, safeValues map[string]int) error { 14 | lines := []string{ 15 | fmt.Sprintf("type compl%s struct {", field.TypeName(gg)), 16 | } 17 | enumItems := NewEnumItemSlice(safeValues) 18 | sort.Sort(enumItems) 19 | for _, item := range enumItems { 20 | lines = append(lines, fmt.Sprintf("%s %s", gg.goish.Public(item.Key), field.TypeName(gg))) 21 | } 22 | lines = append(lines, "}") 23 | lines = append(lines, 24 | fmt.Sprintf("var %s compl%s = compl%s {", 25 | field.AccessName(gg), field.TypeName(gg), field.TypeName(gg))) 26 | for _, item := range enumItems { 27 | lines = append(lines, fmt.Sprintf("%s: %s(%d),", gg.goish.Public(item.Key), field.TypeName(gg), item.Value)) 28 | } 29 | lines = append(lines, "}") 30 | lines = append(lines, fmt.Sprintf("var %sEnumMapping = map[string]%s {", field.FieldName(gg), field.TypeName(gg))) 31 | for _, item := range enumItems { 32 | lines = append(lines, fmt.Sprintf(`"%s": %s.%s,`, item.Key, field.AccessName(gg), gg.goish.Public(item.Key))) 33 | } 34 | lines = append(lines, "}") 35 | lines = append( 36 | lines, 37 | fmt.Sprintf( 38 | "func (ct *compl%s) FromString(key string) (res %s, ok bool) {", 39 | field.TypeName(gg), field.TypeName(gg)), 40 | ) 41 | lines = append(lines, fmt.Sprintf("res, ok = %sEnumMapping[key]", field.FieldName(gg))) 42 | lines = append(lines, "return") 43 | lines = append(lines, "}") 44 | 45 | return gg.RawData(strings.Join(lines, "\n")) 46 | } 47 | 48 | // DateHelpers ... 49 | func (gg *GoGen) DateHelpers(field generator.Field) error { 50 | gg.useTime() 51 | lines := []string{ 52 | fmt.Sprintf("type compl%s struct{}", field.TypeName(gg)), 53 | fmt.Sprintf(` 54 | func (c compl%s) FromTime(t stdtime.Time) %s { 55 | return %s(t.Unix()/86400) 56 | } 57 | func (c compl%s) FromTimestamp(t int64) %s { 58 | return %s(t/86400) 59 | } 60 | `, 61 | field.TypeName(gg), field.TypeName(gg), field.TypeName(gg), 62 | field.TypeName(gg), field.TypeName(gg), field.TypeName(gg)), 63 | fmt.Sprintf("var %s compl%s", field.AccessName(gg), field.TypeName(gg)), 64 | } 65 | return gg.RawData(strings.Join(lines, "\n")) 66 | } 67 | 68 | // DateTimeHelpers ... 69 | func (gg *GoGen) DateTimeHelpers(field generator.Field) error { 70 | gg.useTime() 71 | lines := []string{ 72 | fmt.Sprintf("type compl%s struct{}", field.TypeName(gg)), 73 | fmt.Sprintf(` 74 | func (c compl%s) FromTime(t stdtime.Time) %s { 75 | return %s(t.Unix()) 76 | } 77 | func (c compl%s) FromTimestamp(t int64) %s { 78 | return %s(t) 79 | } 80 | `, 81 | field.TypeName(gg), field.TypeName(gg), field.TypeName(gg), 82 | field.TypeName(gg), field.TypeName(gg), field.TypeName(gg)), 83 | fmt.Sprintf("var %s compl%s", field.AccessName(gg), field.TypeName(gg)), 84 | } 85 | return gg.RawData(strings.Join(lines, "\n")) 86 | } 87 | 88 | // Dec128Helpers ... 89 | func (gg *GoGen) Dec128Helpers(field generator.Field) error { 90 | format := ` 91 | func {{.FuncName}}(lo, hi uint64) {{.TypeName}} { 92 | return {{.TypeName}}{ 93 | Lo: lo, 94 | Hi: hi, 95 | } 96 | } 97 | 98 | func {{.FuncName}}Struct(item struct {Lo uint64; Hi uint64}) {{.TypeName}} { 99 | return {{.TypeName}}{ 100 | Lo: item.Lo, 101 | Hi: item.Hi, 102 | } 103 | } 104 | ` 105 | 106 | gen := template.New("test") 107 | gen, err := gen.Parse(format) 108 | if err != nil { 109 | panic("failed to parse template: " + err.Error()) 110 | } 111 | err = gen.Execute(gg.dest, map[string]string{ 112 | "FuncName": gg.EasyTypeName(field.AccessName(gg)), 113 | "TypeName": gg.UneasyTypeName(field.AccessName(gg)), 114 | }) 115 | return err 116 | } 117 | -------------------------------------------------------------------------------- /internal/generator/gogen/native-typenames.go: -------------------------------------------------------------------------------- 1 | package gogen 2 | 3 | import ( 4 | "github.com/sirkon/ch-encode/internal/generator" 5 | ) 6 | 7 | // Int8NativeTypeName ... 8 | func (gg *GoGen) Int8NativeTypeName() string { 9 | return "int8" 10 | } 11 | 12 | // Int16NativeTypeName ... 13 | func (gg *GoGen) Int16NativeTypeName() string { 14 | return "int16" 15 | } 16 | 17 | // Int32NativeTypeName ... 18 | func (gg *GoGen) Int32NativeTypeName() string { 19 | return "int32" 20 | } 21 | 22 | // Int64NativeTypeName ... 23 | func (gg *GoGen) Int64NativeTypeName() string { 24 | return "int64" 25 | } 26 | 27 | // Uint8NativeTypeName ... 28 | func (gg *GoGen) Uint8NativeTypeName() string { 29 | return "byte" 30 | } 31 | 32 | // Uint16NativeTypeName ... 33 | func (gg *GoGen) Uint16NativeTypeName() string { 34 | return "uint16" 35 | } 36 | 37 | // Uint32NativeTypeName ... 38 | func (gg *GoGen) Uint32NativeTypeName() string { 39 | return "uint32" 40 | } 41 | 42 | // Uint64NativeTypeName ... 43 | func (gg *GoGen) Uint64NativeTypeName() string { 44 | return "uint64" 45 | } 46 | 47 | // Dec128NativeTypeName ... 48 | func (gg *GoGen) Dec128NativeTypeName() string { 49 | return `struct { 50 | Lo uint64 51 | Hi uint64 52 | }` 53 | } 54 | 55 | // Float32NativeTypeName ... 56 | func (gg *GoGen) Float32NativeTypeName() string { 57 | return "float32" 58 | } 59 | 60 | // Float64NativeTypeName ... 61 | func (gg *GoGen) Float64NativeTypeName() string { 62 | return "float64" 63 | } 64 | 65 | // StringNativeTypeName ... 66 | func (gg *GoGen) StringNativeTypeName() string { 67 | return "[]byte" 68 | } 69 | 70 | // FixedStringNativeTypeName ... 71 | func (gg *GoGen) FixedStringNativeTypeName() string { 72 | return "[]byte" 73 | } 74 | 75 | // UUIDNativeTypeName ... 76 | func (gg *GoGen) UUIDNativeTypeName() string { 77 | gg.useGoogleUUID() 78 | return "googleUUID.UUID" 79 | } 80 | 81 | // ArrayNativeTypeName ... 82 | func (gg *GoGen) ArrayNativeTypeName(itemType generator.Field) string { 83 | return "[]" + itemType.NativeTypeName(gg) 84 | } 85 | 86 | // NullableNativeTypeName ... 87 | func (gg *GoGen) NullableNativeTypeName(itemType generator.Field) string { 88 | return "*" + itemType.NativeTypeName(gg) 89 | } 90 | 91 | // NullableStringNativeTypeName ... 92 | func (gg *GoGen) NullableStringNativeTypeName() string { 93 | return gg.StringNativeTypeName() 94 | } 95 | 96 | // NullableArrayNativeTypeName ... 97 | func (gg *GoGen) NullableArrayNativeTypeName(itemType generator.Field) string { 98 | return gg.ArrayNativeTypeName(itemType) 99 | } 100 | -------------------------------------------------------------------------------- /internal/generator/gogen/regular-typenames.go: -------------------------------------------------------------------------------- 1 | package gogen 2 | 3 | // EasyTypeName ... 4 | func (gg *GoGen) EasyTypeName(name string) string { 5 | return gg.goish.Public(name) 6 | } 7 | 8 | // UneasyTypeName ... 9 | func (gg *GoGen) UneasyTypeName(name string) string { 10 | return gg.goish.Public(name + "_type") 11 | } 12 | 13 | // HelperName ... 14 | func (gg *GoGen) HelperName(name string) string { 15 | return gg.goish.Public(name) 16 | } 17 | 18 | var reservedKeywords = map[string]struct{}{ 19 | "break": {}, "case": {}, "chan": {}, "const": {}, "continue": {}, "default": {}, "defer": {}, 20 | "else": {}, "fallthrough": {}, "for": {}, "func": {}, "go": {}, "goto": {}, "if": {}, 21 | "import": {}, "interface": {}, "map": {}, "package": {}, "range": {}, "return": {}, 22 | "select": {}, "struct": {}, "switch": {}, "type": {}, "var": {}, 23 | } 24 | 25 | // VarName ... 26 | func (gg *GoGen) VarName(name string) string { 27 | if _, ok := reservedKeywords[name]; ok { 28 | return gg.goish.Private("thisIsReallyStrangeIfYouTookSuchANameForAField" + name) 29 | } 30 | return gg.goish.Private(name) 31 | } 32 | -------------------------------------------------------------------------------- /internal/generator/gogen/testing-encoding.go: -------------------------------------------------------------------------------- 1 | package gogen 2 | 3 | import ( 4 | "fmt" 5 | "sort" 6 | "text/template" 7 | 8 | "github.com/sirkon/ch-encode/internal/generator" 9 | ) 10 | 11 | // Int8TestEncoding ... 12 | func (gg *GoGen) Int8TestEncoding(source string) error { 13 | _, err := fmt.Fprintf(gg.dest, "byte(%s),", source) 14 | return err 15 | } 16 | 17 | // Int16TestEncoding ... 18 | func (gg *GoGen) Int16TestEncoding(source string) error { 19 | _, err := fmt.Fprintf(gg.dest, "int16(%s),", source) 20 | return err 21 | } 22 | 23 | // Int32TestEncoding ... 24 | func (gg *GoGen) Int32TestEncoding(source string) error { 25 | _, err := fmt.Fprintf(gg.dest, "int32(%s),", source) 26 | return err 27 | } 28 | 29 | // Int64TestEncoding ... 30 | func (gg *GoGen) Int64TestEncoding(source string) error { 31 | _, err := fmt.Fprintf(gg.dest, "int64(%s),", source) 32 | return err 33 | } 34 | 35 | // Uint8TestEncoding ... 36 | func (gg *GoGen) Uint8TestEncoding(source string) error { 37 | _, err := fmt.Fprintf(gg.dest, "byte(%s),", source) 38 | return err 39 | } 40 | 41 | // Uint16TestEncoding ... 42 | func (gg *GoGen) Uint16TestEncoding(source string) error { 43 | _, err := fmt.Fprintf(gg.dest, "uint16(%s),", source) 44 | return err 45 | } 46 | 47 | // Uint32TestEncoding ... 48 | func (gg *GoGen) Uint32TestEncoding(source string) error { 49 | _, err := fmt.Fprintf(gg.dest, "uint32(%s),", source) 50 | return err 51 | } 52 | 53 | // Uint64TestEncoding ... 54 | func (gg *GoGen) Uint64TestEncoding(source string) error { 55 | _, err := fmt.Fprintf(gg.dest, "uint64(%s),", source) 56 | return err 57 | } 58 | 59 | // Dec32TestEncoding ... 60 | func (gg *GoGen) Dec32TestEncoding(scale int, source string) error { 61 | gg.regImport("", "github.com/sirkon/decconv") 62 | text := "decconv.Encode32(%d, int32(%s))," 63 | _, err := fmt.Fprintf(gg.dest, text, scale, source) 64 | return err 65 | } 66 | 67 | // Dec64TestEncoding ... 68 | func (gg *GoGen) Dec64TestEncoding(scale int, source string) error { 69 | gg.regImport("", "github.com/sirkon/decconv") 70 | text := "decconv.Encode64(%d, int64(%s))," 71 | _, err := fmt.Fprintf(gg.dest, text, scale, source) 72 | return err 73 | } 74 | 75 | // Dec128TestEncoding ... 76 | func (gg *GoGen) Dec128TestEncoding(scale int, source string) error { 77 | gg.regImport("", "github.com/sirkon/decconv") 78 | text := "decconv.Encode128(%d, %s.Lo, %s.Hi)," 79 | _, err := fmt.Fprintf(gg.dest, text, scale, source, source) 80 | return err 81 | } 82 | 83 | // Float32TestEncoding ... 84 | func (gg *GoGen) Float32TestEncoding(source string) error { 85 | _, err := fmt.Fprintf(gg.dest, "float32(%s),", source) 86 | return err 87 | } 88 | 89 | // Float64TestEncoding ... 90 | func (gg *GoGen) Float64TestEncoding(source string) error { 91 | _, err := fmt.Fprintf(gg.dest, "float64(%s),", source) 92 | return err 93 | } 94 | 95 | // EnumTestEncoding ... 96 | func (gg *GoGen) EnumTestEncoding(source string, safeMapping map[string]int) error { 97 | enumItems := NewEnumItemSlice(safeMapping) 98 | sort.Sort(enumItems) 99 | text := 100 | ` 101 | func() string { 102 | revMapping := map[int]string{ 103 | {{ range .mapping }} {{ .Value }}: "{{ .Key }}", 104 | {{ end }} 105 | } 106 | key, ok := revMapping[int({{ .source }})] 107 | if !ok { 108 | panic(fmt.Errorf("Value %d has no key mapped to", {{ .source }})); 109 | } 110 | return key 111 | }(), 112 | ` 113 | tmpl, err := template.New("enum_test_encoding").Parse(text) 114 | if err != nil { 115 | return err 116 | } 117 | gg.regImport("", "fmt") 118 | err = tmpl.Execute(gg.dest, map[string]interface{}{ 119 | "source": source, 120 | "mapping": enumItems, 121 | }) 122 | return err 123 | } 124 | 125 | // DateTestEncoding ... 126 | func (gg *GoGen) DateTestEncoding(source string) error { 127 | text := 128 | ` 129 | func() string { 130 | timestamp := int64(%s)*3600*24 131 | tm := stdtime.Unix(timestamp, 0) 132 | moscowZone, _ := stdtime.LoadLocation("Europe/Moscow") 133 | moscow := tm.In(moscowZone) 134 | return moscow.Format("2006-01-02") 135 | }(), 136 | ` 137 | _, err := fmt.Fprintf(gg.dest, text, source) 138 | return err 139 | } 140 | 141 | // DateTimeTestEncoding ... 142 | func (gg *GoGen) DateTimeTestEncoding(source string) error { 143 | text := 144 | ` 145 | func() string { 146 | timestamp := int64(%s) 147 | tm := stdtime.Unix(timestamp, 0) 148 | moscowZone, _ := stdtime.LoadLocation("Europe/Moscow") 149 | moscow := tm.In(moscowZone) 150 | return moscow.Format("2006-01-02T15:04:05") 151 | }(), 152 | ` 153 | _, err := fmt.Fprintf(gg.dest, text, source) 154 | return err 155 | 156 | } 157 | 158 | // StringTestEncoding ... 159 | func (gg *GoGen) StringTestEncoding(source string) error { 160 | _, err := fmt.Fprintf(gg.dest, "string(%s),", source) 161 | return err 162 | } 163 | 164 | // FixedStringTestEncoding ... 165 | func (gg *GoGen) FixedStringTestEncoding(source string, length int) error { 166 | _, err := fmt.Fprintf(gg.dest, "string(%s),", source) 167 | return err 168 | } 169 | 170 | // UUIDTestEncoding ... 171 | func (gg *GoGen) UUIDTestEncoding(source string) error { 172 | gg.useGoogleUUID() 173 | _, err := fmt.Fprintf(gg.dest, "googleUUID.UUID(%s).String(),", source) 174 | return err 175 | } 176 | 177 | // ArrayTestEncoding ... 178 | func (gg *GoGen) ArrayTestEncoding(source string, field generator.Field) error { 179 | text := 180 | ` 181 | func() (res []%s) { 182 | for _, arrayItem := range %s { 183 | res = append(res, ` 184 | _, err := fmt.Fprintf(gg.dest, text, field.TestingTypeName(gg), source) 185 | if err != nil { 186 | return err 187 | } 188 | if err = field.TestEncoding("arrayItem", gg); err != nil { 189 | return err 190 | } 191 | if err = gg.RawData(");\n};\nreturn res;\n}(),\n"); err != nil { 192 | return err 193 | } 194 | return nil 195 | } 196 | 197 | // NullableTestEncoding ... 198 | func (gg *GoGen) NullableTestEncoding(source string, field generator.Field) error { 199 | _, err := fmt.Fprintf(gg.dest, "(*%s)(%s),\n", field.TestingTypeName(gg), source) 200 | return err 201 | } 202 | 203 | // NullableStringTestEncoding 204 | func (gg *GoGen) NullableStringTestEncoding(source string) error { 205 | text := 206 | ` 207 | func() *string { 208 | if %s == nil { return nil} 209 | res := string(%s) 210 | return &res 211 | }(),` 212 | _, err := fmt.Fprintf(gg.dest, text, source, source) 213 | return err 214 | } 215 | 216 | // NullableArrayTestEncoding 217 | func (gg *GoGen) NullableArrayTestEncoding(source string, field generator.Field) error { 218 | return gg.NullableTestEncoding(source, field) 219 | } 220 | -------------------------------------------------------------------------------- /internal/generator/gogen/testing-typenames.go: -------------------------------------------------------------------------------- 1 | package gogen 2 | 3 | import ( 4 | "github.com/sirkon/ch-encode/internal/generator" 5 | ) 6 | 7 | // Int8TestingTypeName ... 8 | func (gg *GoGen) Int8TestingTypeName() string { 9 | return "byte" 10 | } 11 | 12 | // Int16TestingTypeName ... 13 | func (gg *GoGen) Int16TestingTypeName() string { 14 | return "int16" 15 | } 16 | 17 | // Int32TestingTypeName ... 18 | func (gg *GoGen) Int32TestingTypeName() string { 19 | return "int32" 20 | } 21 | 22 | // Int64TestingTypeName ... 23 | func (gg *GoGen) Int64TestingTypeName() string { 24 | return "int64" 25 | } 26 | 27 | // Uint8TestingTypeName ... 28 | func (gg *GoGen) Uint8TestingTypeName() string { 29 | return "byte" 30 | } 31 | 32 | // Uint16TestingTypeName ... 33 | func (gg *GoGen) Uint16TestingTypeName() string { 34 | return "uint16" 35 | } 36 | 37 | // Uint32TestingTypeName ... 38 | func (gg *GoGen) Uint32TestingTypeName() string { 39 | return "uint32" 40 | } 41 | 42 | // Uint64TestingTypeName ... 43 | func (gg *GoGen) Uint64TestingTypeName() string { 44 | return "uint64" 45 | } 46 | 47 | // Dec32TestingTypeName ... 48 | func (gg *GoGen) Dec32TestingTypeName() string { 49 | return "string" 50 | } 51 | 52 | // Dec64TestingTypeName ... 53 | func (gg *GoGen) Dec64TestingTypeName() string { 54 | return "string" 55 | } 56 | 57 | // Dec128TestingTypeName ... 58 | func (gg *GoGen) Dec128TestingTypeName() string { 59 | return "string" 60 | } 61 | 62 | // Float32TestingTypeName ... 63 | func (gg *GoGen) Float32TestingTypeName() string { 64 | return "float32" 65 | } 66 | 67 | // Float64TestingTypeName ... 68 | func (gg *GoGen) Float64TestingTypeName() string { 69 | return "float64" 70 | } 71 | 72 | // EnumTestingTypeName ... 73 | func (gg *GoGen) EnumTestingTypeName() string { 74 | return "string" 75 | } 76 | 77 | // DateTestingTypeName ... 78 | func (gg *GoGen) DateTestingTypeName() string { 79 | return "string" 80 | } 81 | 82 | // DateTimeTestingTypeName ... 83 | func (gg *GoGen) DateTimeTestingTypeName() string { 84 | return "string" 85 | } 86 | 87 | // StringTestingTypeName ... 88 | func (gg *GoGen) StringTestingTypeName() string { 89 | return "string" 90 | } 91 | 92 | // FixedStringTestingTypeName ... 93 | func (gg *GoGen) FixedStringTestingTypeName() string { 94 | return "string" 95 | } 96 | 97 | // UUIDTestingTypeName ... 98 | func (gg *GoGen) UUIDTestingTypeName() string { 99 | return "string" 100 | } 101 | 102 | // ArrayTestingTypeName ... 103 | func (gg *GoGen) ArrayTestingTypeName(itemType generator.Field) string { 104 | return "[]" + itemType.TestingTypeName(gg) 105 | } 106 | 107 | // NullableTestingTypeName ... 108 | func (gg *GoGen) NullableTestingTypeName(itemType generator.Field) string { 109 | return "*" + itemType.TestingTypeName(gg) 110 | } 111 | 112 | // NullableStringTestingTypeName ... 113 | func (gg *GoGen) NullableStringTestingTypeName() string { 114 | return "*string" 115 | } 116 | 117 | // NullableArrayTestingTypeName ... 118 | func (gg *GoGen) NullableArrayTestingTypeName(itemType generator.Field) string { 119 | return gg.ArrayTestingTypeName(itemType) 120 | } 121 | -------------------------------------------------------------------------------- /internal/generator/gogen/testingencoder.go: -------------------------------------------------------------------------------- 1 | package gogen 2 | 3 | import ( 4 | "fmt" 5 | "text/template" 6 | 7 | "github.com/sirkon/ch-encode/internal/generator" 8 | ) 9 | 10 | // TestDef ... 11 | func (gg *GoGen) TestDef(fields *generator.FieldSet) error { 12 | text := 13 | ` 14 | type {{.record}} struct { 15 | {{ range .fields }} {{.Name}} {{.Type}} 16 | {{ end}} 17 | } 18 | ` 19 | type item struct { 20 | Name string 21 | Type string 22 | } 23 | items := make([]item, len(fields.List())) 24 | for i, field := range fields.List() { 25 | items[i].Name = field.AccessName(gg) 26 | items[i].Type = field.TestingTypeName(gg) 27 | } 28 | tmpl, err := template.New("testing").Parse(text) 29 | if err != nil { 30 | return err 31 | } 32 | return tmpl.Execute(gg.dest, map[string]interface{}{ 33 | "record": gg.testingResultName(), 34 | "fields": items, 35 | }) 36 | } 37 | 38 | // TestEncoderDef ... 39 | func (gg *GoGen) TestEncoderDef(*generator.FieldSet) error { 40 | text := 41 | ` 42 | type {{.encoder}} struct { 43 | Result []{{.record}} 44 | } 45 | 46 | func New{{.encoder}}() *{{.encoder}} { 47 | return &{{.encoder}}{ 48 | Result: make([]{{.record}}, 0, 3), 49 | } 50 | } 51 | 52 | func (enc *{{.encoder}}) InsertCount() int { 53 | return len(enc.Result); 54 | } 55 | func (enc *{{.encoder}}) ErrorCount() int { 56 | return 0; 57 | } 58 | ` 59 | tmpl, err := template.New("testingEncoder").Parse(text) 60 | if err != nil { 61 | return err 62 | } 63 | return tmpl.Execute(gg.dest, map[string]string{ 64 | "record": gg.testingResultName(), 65 | "encoder": gg.testingEncoderName(), 66 | }) 67 | } 68 | 69 | // TestEncodingMethod ... 70 | func (gg *GoGen) TestEncodingMethod(fields *generator.FieldSet) (err error) { 71 | if err = gg.RawData(fmt.Sprintf("func (enc *%s)Encode(%s) error{", gg.testingEncoderName(), gg.argList(fields))); err != nil { 72 | return 73 | } 74 | if err = gg.constraints(fields); err != nil { 75 | return 76 | } 77 | if err = gg.RawData(fmt.Sprintf("enc.Result = append(enc.Result, %s{\n", gg.testingResultName())); err != nil { 78 | return 79 | } 80 | for _, field := range fields.List() { 81 | if _, err = fmt.Fprintf(gg.dest, "%s:", field.AccessName(gg)); err != nil { 82 | return 83 | } 84 | if err = field.TestEncoding(field.ArgName(gg), gg); err != nil { 85 | return 86 | } 87 | if err = gg.RawData("\n"); err != nil { 88 | return 89 | } 90 | } 91 | return gg.RawData("\n}); return nil}\n") 92 | } 93 | -------------------------------------------------------------------------------- /internal/generator/gogen/util.go: -------------------------------------------------------------------------------- 1 | package gogen 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | "github.com/sirkon/ch-encode/internal/generator" 8 | ) 9 | 10 | func (gg *GoGen) interfaceEncoderName() string { 11 | return fmt.Sprintf("%sEncoder", gg.goish.Public(gg.table)) 12 | } 13 | 14 | func (gg *GoGen) encoderName() string { 15 | return fmt.Sprintf("%sRawEncoder", gg.goish.Public(gg.table)) 16 | } 17 | 18 | func (gg *GoGen) filterEncoderName() string { 19 | return fmt.Sprintf("%sRawEncoderDateFilter", gg.goish.Public(gg.table)) 20 | } 21 | 22 | func (gg *GoGen) voidEncoderName() string { 23 | return fmt.Sprintf("New%sRawEncoderVoid", gg.goish.Public(gg.table)) 24 | } 25 | 26 | func (gg *GoGen) testingEncoderName() string { 27 | return fmt.Sprintf("Testing%sEncoder", gg.goish.Public(gg.table)) 28 | } 29 | 30 | func (gg *GoGen) testingResultName() string { 31 | return fmt.Sprintf("Testing%sResult", gg.goish.Public(gg.table)) 32 | } 33 | 34 | func (gg *GoGen) argList(fields *generator.FieldSet) string { 35 | res := []string{} 36 | for _, field := range fields.List() { 37 | res = append(res, fmt.Sprintf("%s %s", field.ArgName(gg), field.TypeName(gg))) 38 | } 39 | return strings.Join(res, ", ") 40 | } 41 | 42 | // EnumItem represents enumX key→value mapping 43 | type EnumItem struct { 44 | Key string 45 | Value int 46 | } 47 | 48 | // EnumItemSlice represents 49 | type EnumItemSlice []EnumItem 50 | 51 | // NewEnumItemSlice constructor 52 | func NewEnumItemSlice(input map[string]int) (res EnumItemSlice) { 53 | for key, value := range input { 54 | res = append(res, EnumItem{Key: key, Value: value}) 55 | } 56 | return res 57 | } 58 | 59 | //// Now implement sort.Interface for EnumItemSlice 60 | 61 | // Len for sort.Interface 62 | func (eis EnumItemSlice) Len() int { return len(eis) } 63 | 64 | // Less for sort.Interface 65 | func (eis EnumItemSlice) Less(i, j int) bool { return eis[i].Value < eis[j].Value } 66 | 67 | // Swap for sort.Interface 68 | func (eis EnumItemSlice) Swap(i, j int) { 69 | eis[i], eis[j] = eis[j], eis[i] 70 | } 71 | -------------------------------------------------------------------------------- /internal/generator/gogen/voidencoder.go: -------------------------------------------------------------------------------- 1 | package gogen 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/sirkon/ch-encode/internal/generator" 7 | ) 8 | 9 | // VoidEncoderDef ... 10 | func (gg *GoGen) VoidEncoderDef(*generator.FieldSet) error { 11 | _, err := fmt.Fprintf(gg.dest, "\ntype %s bool\n", gg.voidEncoderName()) 12 | return err 13 | } 14 | 15 | // VoidEncodingMethod ... 16 | func (gg *GoGen) VoidEncodingMethod(fields *generator.FieldSet) error { 17 | _, err := fmt.Fprintf( 18 | gg.dest, 19 | ` 20 | func (enc %s) Encode(%s) error {return nil;} 21 | func (enc %s) InsertCount() int {return 0;} 22 | func (enc %s) ErrorCount() int {return 0;} 23 | `, 24 | gg.voidEncoderName(), gg.argList(fields), gg.voidEncoderName(), gg.voidEncoderName()) 25 | return err 26 | } 27 | -------------------------------------------------------------------------------- /internal/generator/int16_field.go: -------------------------------------------------------------------------------- 1 | package generator 2 | 3 | // Int16 Type implementation 4 | type Int16 struct { 5 | field string 6 | fieldType string 7 | } 8 | 9 | // NewInt16 constructor 10 | func NewInt16(field string, fieldType string) *Int16 { 11 | return &Int16{ 12 | field: field, 13 | fieldType: fieldType, 14 | } 15 | } 16 | 17 | // FieldName ... 18 | func (i16 *Int16) FieldName(gen Generator) string { 19 | return i16.field 20 | } 21 | 22 | // FieldTypeName ... 23 | func (i16 *Int16) FieldTypeName(gen Generator) string { 24 | return i16.fieldType 25 | } 26 | 27 | // TypeName ... 28 | func (i16 *Int16) TypeName(gen Generator) string { 29 | return gen.EasyTypeName(i16.field) 30 | } 31 | 32 | // ArgName ... 33 | func (i16 *Int16) ArgName(gen Generator) string { 34 | return gen.VarName(i16.field) 35 | } 36 | 37 | // AccessName ... 38 | func (i16 *Int16) AccessName(gen Generator) string { 39 | return gen.HelperName(i16.field) 40 | } 41 | 42 | // NativeTypeName ... 43 | func (i16 Int16) NativeTypeName(gen Generator) string { 44 | return gen.Int16NativeTypeName() 45 | } 46 | 47 | // Encoding ... 48 | func (i16 Int16) Encoding(source string, gen Generator) error { 49 | return gen.Int16Encoding(source) 50 | } 51 | 52 | // Helper ... 53 | func (i16 *Int16) Helper(gen Generator) error { 54 | return nil 55 | } 56 | 57 | // TestingTypeName ... 58 | func (i16 Int16) TestingTypeName(gen Generator) string { 59 | return gen.Int16TestingTypeName() 60 | } 61 | 62 | // TestEncoding ... 63 | func (i16 Int16) TestEncoding(source string, gen Generator) error { 64 | return gen.Int16TestEncoding(source) 65 | } 66 | -------------------------------------------------------------------------------- /internal/generator/int32_field.go: -------------------------------------------------------------------------------- 1 | package generator 2 | 3 | // Int32 Type implementation 4 | type Int32 struct { 5 | field string 6 | fieldType string 7 | } 8 | 9 | // NewInt32 constructor 10 | func NewInt32(field string, fieldType string) *Int32 { 11 | return &Int32{ 12 | field: field, 13 | fieldType: fieldType, 14 | } 15 | } 16 | 17 | // FieldName ... 18 | func (i32 *Int32) FieldName(gen Generator) string { 19 | return i32.field 20 | } 21 | 22 | // FieldTypeName ... 23 | func (i32 *Int32) FieldTypeName(gen Generator) string { 24 | return i32.fieldType 25 | } 26 | 27 | // TypeName ... 28 | func (i32 *Int32) TypeName(gen Generator) string { 29 | return gen.EasyTypeName(i32.field) 30 | } 31 | 32 | // ArgName ... 33 | func (i32 *Int32) ArgName(gen Generator) string { 34 | return gen.VarName(i32.field) 35 | } 36 | 37 | // AccessName ... 38 | func (i32 *Int32) AccessName(gen Generator) string { 39 | return gen.HelperName(i32.field) 40 | } 41 | 42 | // NativeTypeName ... 43 | func (i32 Int32) NativeTypeName(gen Generator) string { 44 | return gen.Int32NativeTypeName() 45 | } 46 | 47 | // Encoding ... 48 | func (i32 Int32) Encoding(source string, gen Generator) error { 49 | return gen.Int32Encoding(source) 50 | } 51 | 52 | // Helper ... 53 | func (i32 *Int32) Helper(gen Generator) error { 54 | return nil 55 | } 56 | 57 | // TestingTypeName ... 58 | func (i32 Int32) TestingTypeName(gen Generator) string { 59 | return gen.Int32TestingTypeName() 60 | } 61 | 62 | // TestEncoding ... 63 | func (i32 Int32) TestEncoding(source string, gen Generator) error { 64 | return gen.Int32TestEncoding(source) 65 | } 66 | -------------------------------------------------------------------------------- /internal/generator/int64_field.go: -------------------------------------------------------------------------------- 1 | package generator 2 | 3 | // Int64 Type implementation 4 | type Int64 struct { 5 | field string 6 | fieldType string 7 | } 8 | 9 | // NewInt64 constructor 10 | func NewInt64(field string, fieldType string) *Int64 { 11 | return &Int64{ 12 | field: field, 13 | fieldType: fieldType, 14 | } 15 | } 16 | 17 | // FieldName ... 18 | func (i64 *Int64) FieldName(gen Generator) string { 19 | return i64.field 20 | } 21 | 22 | // FieldTypeName ... 23 | func (i64 *Int64) FieldTypeName(gen Generator) string { 24 | return i64.fieldType 25 | } 26 | 27 | // TypeName ... 28 | func (i64 *Int64) TypeName(gen Generator) string { 29 | return gen.EasyTypeName(i64.field) 30 | } 31 | 32 | // ArgName ... 33 | func (i64 *Int64) ArgName(gen Generator) string { 34 | return gen.VarName(i64.field) 35 | } 36 | 37 | // AccessName ... 38 | func (i64 *Int64) AccessName(gen Generator) string { 39 | return gen.HelperName(i64.field) 40 | } 41 | 42 | // NativeTypeName ... 43 | func (i64 Int64) NativeTypeName(gen Generator) string { 44 | return gen.Int64NativeTypeName() 45 | } 46 | 47 | // Encoding ... 48 | func (i64 Int64) Encoding(source string, gen Generator) error { 49 | return gen.Int64Encoding(source) 50 | } 51 | 52 | // Helper ... 53 | func (i64 *Int64) Helper(gen Generator) error { 54 | return nil 55 | } 56 | 57 | // TestingTypeName ... 58 | func (i64 Int64) TestingTypeName(gen Generator) string { 59 | return gen.Int64TestingTypeName() 60 | } 61 | 62 | // TestEncoding ... 63 | func (i64 Int64) TestEncoding(source string, gen Generator) error { 64 | return gen.Int64TestEncoding(source) 65 | } 66 | -------------------------------------------------------------------------------- /internal/generator/int8_field.go: -------------------------------------------------------------------------------- 1 | package generator 2 | 3 | // Int8 Type implementation 4 | type Int8 struct { 5 | field string 6 | fieldType string 7 | } 8 | 9 | // NewInt8 constructor 10 | func NewInt8(field string, fieldType string) *Int8 { 11 | return &Int8{ 12 | field: field, 13 | fieldType: fieldType, 14 | } 15 | } 16 | 17 | // FieldName ... 18 | func (i8 *Int8) FieldName(gen Generator) string { 19 | return i8.field 20 | } 21 | 22 | // FieldTypeName ... 23 | func (i8 *Int8) FieldTypeName(gen Generator) string { 24 | return i8.fieldType 25 | } 26 | 27 | // TypeName ... 28 | func (i8 *Int8) TypeName(gen Generator) string { 29 | return gen.EasyTypeName(i8.field) 30 | } 31 | 32 | // ArgName ... 33 | func (i8 *Int8) ArgName(gen Generator) string { 34 | return gen.VarName(i8.field) 35 | } 36 | 37 | // AccessName ... 38 | func (i8 *Int8) AccessName(gen Generator) string { 39 | return gen.HelperName(i8.field) 40 | } 41 | 42 | // NativeTypeName ... 43 | func (i8 Int8) NativeTypeName(gen Generator) string { 44 | return gen.Int8NativeTypeName() 45 | } 46 | 47 | // Encoding ... 48 | func (i8 Int8) Encoding(source string, gen Generator) error { 49 | return gen.Int8Encoding(source) 50 | } 51 | 52 | // Helper ... 53 | func (i8 *Int8) Helper(gen Generator) error { 54 | return nil 55 | } 56 | 57 | // TestingTypeName ... 58 | func (i8 Int8) TestingTypeName(gen Generator) string { 59 | return gen.Int8TestingTypeName() 60 | } 61 | 62 | // TestEncoding ... 63 | func (i8 Int8) TestEncoding(source string, gen Generator) error { 64 | return gen.Int8TestEncoding(source) 65 | } 66 | -------------------------------------------------------------------------------- /internal/generator/nullable_array_field.go: -------------------------------------------------------------------------------- 1 | package generator 2 | 3 | // NullableArray Type implementation 4 | type NullableArray struct { 5 | field string 6 | fieldType string 7 | meta Field 8 | } 9 | 10 | // NewNullableArray constructor 11 | func NewNullableArray(field, fieldType string, meta Field) *NullableArray { 12 | return &NullableArray{ 13 | field: field, 14 | fieldType: fieldType, 15 | meta: meta, 16 | } 17 | } 18 | 19 | // FieldName ... 20 | func (a *NullableArray) FieldName(gen Generator) string { 21 | return a.field 22 | } 23 | 24 | // FieldTypeName ... 25 | func (a *NullableArray) FieldTypeName(gen Generator) string { 26 | return a.fieldType 27 | } 28 | 29 | // TypeName ... 30 | func (a *NullableArray) TypeName(gen Generator) string { 31 | return gen.EasyTypeName(a.field) 32 | } 33 | 34 | // ArgName ... 35 | func (a *NullableArray) ArgName(gen Generator) string { 36 | return gen.VarName(a.field) 37 | } 38 | 39 | // AccessName ... 40 | func (a *NullableArray) AccessName(gen Generator) string { 41 | return gen.HelperName(a.field) 42 | } 43 | 44 | // NativeTypeName NullableArray implementation 45 | func (a *NullableArray) NativeTypeName(gen Generator) string { 46 | return gen.NullableArrayNativeTypeName(a.meta) 47 | } 48 | 49 | // Encoding NullableArray implementation 50 | func (a *NullableArray) Encoding(source string, gen Generator) error { 51 | return gen.NullableArrayEncoding(source, a.meta) 52 | } 53 | 54 | // Helper ... 55 | func (a *NullableArray) Helper(gen Generator) error { 56 | return nil 57 | } 58 | 59 | // TestingTypeName ... 60 | func (a *NullableArray) TestingTypeName(gen Generator) string { 61 | return gen.NullableArrayTestingTypeName(a.meta) 62 | } 63 | 64 | // TestEncoding ... 65 | func (a *NullableArray) TestEncoding(source string, gen Generator) error { 66 | return gen.NullableArrayTestEncoding(source, a.meta) 67 | } 68 | -------------------------------------------------------------------------------- /internal/generator/nullable_field.go: -------------------------------------------------------------------------------- 1 | package generator 2 | 3 | // Nullable Type implementation 4 | type Nullable struct { 5 | field string 6 | fieldType string 7 | meta Field 8 | } 9 | 10 | // NewNullable constructor 11 | func NewNullable(field, fieldType string, meta Field) *Nullable { 12 | return &Nullable{ 13 | field: field, 14 | fieldType: fieldType, 15 | meta: meta, 16 | } 17 | } 18 | 19 | // FieldName ... 20 | func (n *Nullable) FieldName(gen Generator) string { 21 | return n.field 22 | } 23 | 24 | // FieldTypeName ... 25 | func (n *Nullable) FieldTypeName(gen Generator) string { 26 | return n.fieldType 27 | } 28 | 29 | // TypeName ... 30 | func (n *Nullable) TypeName(gen Generator) string { 31 | return gen.EasyTypeName(n.field) 32 | } 33 | 34 | // ArgName ... 35 | func (n *Nullable) ArgName(gen Generator) string { 36 | return gen.VarName(n.field) 37 | } 38 | 39 | // AccessName ... 40 | func (n *Nullable) AccessName(gen Generator) string { 41 | return gen.HelperName(n.field) 42 | } 43 | 44 | // NativeTypeName Array implementation 45 | func (n *Nullable) NativeTypeName(gen Generator) string { 46 | return gen.NullableNativeTypeName(n.meta) 47 | } 48 | 49 | // Encoding Array implementation 50 | func (n *Nullable) Encoding(source string, gen Generator) error { 51 | return gen.NullableEncoding(source, n.meta) 52 | } 53 | 54 | // Helper ... 55 | func (n *Nullable) Helper(gen Generator) error { 56 | return nil 57 | } 58 | 59 | // TestingTypeName ... 60 | func (n *Nullable) TestingTypeName(gen Generator) string { 61 | return gen.NullableTestingTypeName(n.meta) 62 | } 63 | 64 | // TestEncoding ... 65 | func (n *Nullable) TestEncoding(source string, gen Generator) error { 66 | return gen.NullableTestEncoding(source, n.meta) 67 | } 68 | -------------------------------------------------------------------------------- /internal/generator/nullable_string_field.go: -------------------------------------------------------------------------------- 1 | package generator 2 | 3 | // NullableString Type implementation 4 | type NullableString struct { 5 | field string 6 | fieldType string 7 | } 8 | 9 | // NewNullableString constructor 10 | func NewNullableString(field, fieldType string) *NullableString { 11 | return &NullableString{ 12 | field: field, 13 | fieldType: fieldType, 14 | } 15 | } 16 | 17 | // FieldName ... 18 | func (s *NullableString) FieldName(gen Generator) string { 19 | return s.field 20 | } 21 | 22 | // FieldTypeName ... 23 | func (s *NullableString) FieldTypeName(gen Generator) string { 24 | return s.fieldType 25 | } 26 | 27 | // TypeName ... 28 | func (s *NullableString) TypeName(gen Generator) string { 29 | return gen.EasyTypeName(s.field) 30 | } 31 | 32 | // ArgName ... 33 | func (s *NullableString) ArgName(gen Generator) string { 34 | return gen.VarName(s.field) 35 | } 36 | 37 | // AccessName ... 38 | func (s *NullableString) AccessName(gen Generator) string { 39 | return gen.HelperName(s.field) 40 | } 41 | 42 | // NativeTypeName NullableString implementation 43 | func (s *NullableString) NativeTypeName(gen Generator) string { 44 | return gen.NullableStringNativeTypeName() 45 | } 46 | 47 | // Encoding NullableString implementation 48 | func (s *NullableString) Encoding(source string, gen Generator) error { 49 | return gen.NullableStringEncoding(source) 50 | } 51 | 52 | // Helper ... 53 | func (s *NullableString) Helper(gen Generator) error { 54 | return nil 55 | } 56 | 57 | // TestingTypeName ... 58 | func (s *NullableString) TestingTypeName(gen Generator) string { 59 | return gen.NullableStringTestingTypeName() 60 | } 61 | 62 | // TestEncoding ... 63 | func (s *NullableString) TestEncoding(source string, gen Generator) error { 64 | return gen.NullableStringTestEncoding(source) 65 | } 66 | -------------------------------------------------------------------------------- /internal/generator/string_field.go: -------------------------------------------------------------------------------- 1 | package generator 2 | 3 | // String Type implementation 4 | type String struct { 5 | field string 6 | fieldType string 7 | } 8 | 9 | // NewString constructor 10 | func NewString(field, fieldType string) *String { 11 | return &String{ 12 | field: field, 13 | fieldType: fieldType, 14 | } 15 | } 16 | 17 | // FieldName ... 18 | func (s *String) FieldName(gen Generator) string { 19 | return s.field 20 | } 21 | 22 | // FieldTypeName ... 23 | func (s *String) FieldTypeName(gen Generator) string { 24 | return s.fieldType 25 | } 26 | 27 | // TypeName ... 28 | func (s *String) TypeName(gen Generator) string { 29 | return gen.EasyTypeName(s.field) 30 | } 31 | 32 | // ArgName ... 33 | func (s *String) ArgName(gen Generator) string { 34 | return gen.VarName(s.field) 35 | } 36 | 37 | // AccessName ... 38 | func (s *String) AccessName(gen Generator) string { 39 | return gen.HelperName(s.field) 40 | } 41 | 42 | // NativeTypeName String implementation 43 | func (s *String) NativeTypeName(gen Generator) string { 44 | return gen.StringNativeTypeName() 45 | } 46 | 47 | // Encoding String implementation 48 | func (s *String) Encoding(source string, gen Generator) error { 49 | return gen.StringEncoding(source) 50 | } 51 | 52 | // Helper ... 53 | func (s *String) Helper(gen Generator) error { 54 | return nil 55 | } 56 | 57 | // TestingTypeName ... 58 | func (s *String) TestingTypeName(gen Generator) string { 59 | return gen.StringTestingTypeName() 60 | } 61 | 62 | // TestEncoding ... 63 | func (s *String) TestEncoding(source string, gen Generator) error { 64 | return gen.StringTestEncoding(source) 65 | } 66 | -------------------------------------------------------------------------------- /internal/generator/uint16_field.go: -------------------------------------------------------------------------------- 1 | package generator 2 | 3 | // Uint16 Type implementation 4 | type Uint16 struct { 5 | field string 6 | fieldType string 7 | } 8 | 9 | // NewUint16 constructor 10 | func NewUint16(field string, fieldType string) *Uint16 { 11 | return &Uint16{ 12 | field: field, 13 | fieldType: fieldType, 14 | } 15 | } 16 | 17 | // FieldName ... 18 | func (ui16 *Uint16) FieldName(gen Generator) string { 19 | return ui16.field 20 | } 21 | 22 | // FieldTypeName ... 23 | func (ui16 *Uint16) FieldTypeName(gen Generator) string { 24 | return ui16.fieldType 25 | } 26 | 27 | // TypeName ... 28 | func (ui16 *Uint16) TypeName(gen Generator) string { 29 | return gen.EasyTypeName(ui16.field) 30 | } 31 | 32 | // ArgName ... 33 | func (ui16 *Uint16) ArgName(gen Generator) string { 34 | return gen.VarName(ui16.field) 35 | } 36 | 37 | // AccessName ... 38 | func (ui16 *Uint16) AccessName(gen Generator) string { 39 | return gen.HelperName(ui16.field) 40 | } 41 | 42 | // NativeTypeName ... 43 | func (ui16 Uint16) NativeTypeName(gen Generator) string { 44 | return gen.Uint16NativeTypeName() 45 | } 46 | 47 | // Encoding ... 48 | func (ui16 Uint16) Encoding(source string, gen Generator) error { 49 | return gen.Uint16Encoding(source) 50 | } 51 | 52 | // Helper ... 53 | func (ui16 *Uint16) Helper(gen Generator) error { 54 | return nil 55 | } 56 | 57 | // TestingTypeName ... 58 | func (ui16 Uint16) TestingTypeName(gen Generator) string { 59 | return gen.Uint16TestingTypeName() 60 | } 61 | 62 | // TestEncoding ... 63 | func (ui16 Uint16) TestEncoding(source string, gen Generator) error { 64 | return gen.Uint16TestEncoding(source) 65 | } 66 | -------------------------------------------------------------------------------- /internal/generator/uint32_field.go: -------------------------------------------------------------------------------- 1 | package generator 2 | 3 | // Uint32 Type implementation 4 | type Uint32 struct { 5 | field string 6 | fieldType string 7 | } 8 | 9 | // NewUint32 constructor 10 | func NewUint32(field string, fieldType string) *Uint32 { 11 | return &Uint32{ 12 | field: field, 13 | fieldType: fieldType, 14 | } 15 | } 16 | 17 | // FieldName ... 18 | func (ui32 *Uint32) FieldName(gen Generator) string { 19 | return ui32.field 20 | } 21 | 22 | // FieldTypeName ... 23 | func (ui32 *Uint32) FieldTypeName(gen Generator) string { 24 | return ui32.fieldType 25 | } 26 | 27 | // TypeName ... 28 | func (ui32 *Uint32) TypeName(gen Generator) string { 29 | return gen.EasyTypeName(ui32.field) 30 | } 31 | 32 | // ArgName ... 33 | func (ui32 *Uint32) ArgName(gen Generator) string { 34 | return gen.VarName(ui32.field) 35 | } 36 | 37 | // AccessName ... 38 | func (ui32 *Uint32) AccessName(gen Generator) string { 39 | return gen.HelperName(ui32.field) 40 | } 41 | 42 | // NativeTypeName ... 43 | func (ui32 Uint32) NativeTypeName(gen Generator) string { 44 | return gen.Uint32NativeTypeName() 45 | } 46 | 47 | // Encoding ... 48 | func (ui32 Uint32) Encoding(source string, gen Generator) error { 49 | return gen.Uint32Encoding(source) 50 | } 51 | 52 | // Helper ... 53 | func (ui32 *Uint32) Helper(gen Generator) error { 54 | return nil 55 | } 56 | 57 | // TestingTypeName ... 58 | func (ui32 Uint32) TestingTypeName(gen Generator) string { 59 | return gen.Uint32TestingTypeName() 60 | } 61 | 62 | // TestEncoding ... 63 | func (ui32 Uint32) TestEncoding(source string, gen Generator) error { 64 | return gen.Uint32TestEncoding(source) 65 | } 66 | -------------------------------------------------------------------------------- /internal/generator/uint64_field.go: -------------------------------------------------------------------------------- 1 | package generator 2 | 3 | // Uint64 Type implementation 4 | type Uint64 struct { 5 | field string 6 | fieldType string 7 | } 8 | 9 | // NewUint64 constructor 10 | func NewUint64(field string, fieldType string) *Uint64 { 11 | return &Uint64{ 12 | field: field, 13 | fieldType: fieldType, 14 | } 15 | } 16 | 17 | // FieldName ... 18 | func (ui64 *Uint64) FieldName(gen Generator) string { 19 | return ui64.field 20 | } 21 | 22 | // FieldTypeName ... 23 | func (ui64 *Uint64) FieldTypeName(gen Generator) string { 24 | return ui64.fieldType 25 | } 26 | 27 | // TypeName ... 28 | func (ui64 *Uint64) TypeName(gen Generator) string { 29 | return gen.EasyTypeName(ui64.field) 30 | } 31 | 32 | // ArgName ... 33 | func (ui64 *Uint64) ArgName(gen Generator) string { 34 | return gen.VarName(ui64.field) 35 | } 36 | 37 | // AccessName ... 38 | func (ui64 *Uint64) AccessName(gen Generator) string { 39 | return gen.HelperName(ui64.field) 40 | } 41 | 42 | // NativeTypeName ... 43 | func (ui64 Uint64) NativeTypeName(gen Generator) string { 44 | return gen.Uint64NativeTypeName() 45 | } 46 | 47 | // Encoding ... 48 | func (ui64 Uint64) Encoding(source string, gen Generator) error { 49 | return gen.Uint64Encoding(source) 50 | } 51 | 52 | // Helper ... 53 | func (ui64 *Uint64) Helper(gen Generator) error { 54 | return nil 55 | } 56 | 57 | // TestingTypeName ... 58 | func (ui64 Uint64) TestingTypeName(gen Generator) string { 59 | return gen.Uint64TestingTypeName() 60 | } 61 | 62 | // TestEncoding ... 63 | func (ui64 Uint64) TestEncoding(source string, gen Generator) error { 64 | return gen.Uint64TestEncoding(source) 65 | } 66 | -------------------------------------------------------------------------------- /internal/generator/uint8_field.go: -------------------------------------------------------------------------------- 1 | package generator 2 | 3 | // Uint8 Type implementation 4 | type Uint8 struct { 5 | field string 6 | fieldType string 7 | } 8 | 9 | // NewUint8 constructor 10 | func NewUint8(field string, fieldType string) *Uint8 { 11 | return &Uint8{ 12 | field: field, 13 | fieldType: fieldType, 14 | } 15 | } 16 | 17 | // FieldName ... 18 | func (ui8 *Uint8) FieldName(gen Generator) string { 19 | return ui8.field 20 | } 21 | 22 | // FieldTypeName ... 23 | func (ui8 *Uint8) FieldTypeName(gen Generator) string { 24 | return ui8.fieldType 25 | } 26 | 27 | // TypeName ... 28 | func (ui8 *Uint8) TypeName(gen Generator) string { 29 | return gen.EasyTypeName(ui8.field) 30 | } 31 | 32 | // ArgName ... 33 | func (ui8 *Uint8) ArgName(gen Generator) string { 34 | return gen.VarName(ui8.field) 35 | } 36 | 37 | // AccessName ... 38 | func (ui8 *Uint8) AccessName(gen Generator) string { 39 | return gen.HelperName(ui8.field) 40 | } 41 | 42 | // NativeTypeName ... 43 | func (ui8 Uint8) NativeTypeName(gen Generator) string { 44 | return gen.Uint8NativeTypeName() 45 | } 46 | 47 | // Encoding ... 48 | func (ui8 Uint8) Encoding(source string, gen Generator) error { 49 | return gen.Uint8Encoding(source) 50 | } 51 | 52 | // Helper ... 53 | func (ui8 *Uint8) Helper(gen Generator) error { 54 | return nil 55 | } 56 | 57 | // TestingTypeName ... 58 | func (ui8 Uint8) TestingTypeName(gen Generator) string { 59 | return gen.Uint8TestingTypeName() 60 | } 61 | 62 | // TestEncoding ... 63 | func (ui8 Uint8) TestEncoding(source string, gen Generator) error { 64 | return gen.Uint8TestEncoding(source) 65 | } 66 | -------------------------------------------------------------------------------- /internal/generator/uuid_field.go: -------------------------------------------------------------------------------- 1 | package generator 2 | 3 | // NewUUIDField генерация поля с типом UUID 4 | func NewUUIDField(field string, fieldType string) *UUIDField { 5 | return &UUIDField{field: field, fieldType: fieldType} 6 | } 7 | 8 | var _ Field = &UUIDField{} 9 | 10 | // UUIDField ... 11 | type UUIDField struct { 12 | field string 13 | fieldType string 14 | } 15 | 16 | // FieldName ... 17 | func (f *UUIDField) FieldName(gen Generator) string { 18 | return f.field 19 | } 20 | 21 | // FieldTypeName ... 22 | func (f *UUIDField) FieldTypeName(gen Generator) string { 23 | return f.fieldType 24 | } 25 | 26 | // TypeName ... 27 | func (f *UUIDField) TypeName(gen Generator) string { 28 | return gen.UneasyTypeName(f.field) 29 | } 30 | 31 | // ArgName ... 32 | func (f *UUIDField) ArgName(gen Generator) string { 33 | return gen.VarName(f.field) 34 | } 35 | 36 | // AccessName ... 37 | func (f *UUIDField) AccessName(gen Generator) string { 38 | return gen.HelperName(f.field) 39 | } 40 | 41 | // NativeTypeName ... 42 | func (f *UUIDField) NativeTypeName(gen Generator) string { 43 | return gen.UUIDNativeTypeName() 44 | } 45 | 46 | // Encoding ... 47 | func (f *UUIDField) Encoding(source string, gen Generator) error { 48 | return gen.UUIDEncoding(source) 49 | } 50 | 51 | // Helper ... 52 | func (f *UUIDField) Helper(gen Generator) error { 53 | return nil 54 | } 55 | 56 | // TestingTypeName ... 57 | func (f *UUIDField) TestingTypeName(gen Generator) string { 58 | return gen.UUIDTestingTypeName() 59 | } 60 | 61 | // TestEncoding ... 62 | func (f *UUIDField) TestEncoding(source string, gen Generator) error { 63 | return gen.UUIDTestEncoding(source) 64 | } 65 | -------------------------------------------------------------------------------- /internal/util/components_parser.go: -------------------------------------------------------------------------------- 1 | /* 2 | * THE FILE WAS GENERATED WITH logparsergen --source=testingch.script --package=util 3 | * DO NOT TOUCH IT! 4 | */ 5 | 6 | package util 7 | 8 | import ( 9 | "bytes" 10 | "fmt" 11 | "strconv" 12 | "unsafe" 13 | ) 14 | 15 | type components struct { 16 | rest []byte 17 | Auth struct { 18 | Valid bool 19 | Data []byte 20 | } 21 | Host []byte 22 | Port uint16 23 | DBName []byte 24 | } 25 | 26 | func (p *components) Parse(line []byte) (bool, error) { 27 | p.rest = line 28 | var pos int 29 | var tmp []byte 30 | var restAuth []byte 31 | restAuth = p.rest 32 | if pos = bytes.IndexByte(p.rest, '@'); pos < 0 { 33 | p.Auth.Valid = false 34 | p.rest = restAuth 35 | goto outAuth 36 | } 37 | p.Auth.Data = p.rest[:pos] 38 | p.rest = p.rest[pos+1:] 39 | p.Auth.Valid = true 40 | outAuth: 41 | if pos = bytes.IndexByte(p.rest, ':'); pos < 0 { 42 | return false, fmt.Errorf("`%s` is not a prefix of \033[1m%s\033[0m", string(':'), string(p.rest)) 43 | } 44 | p.Host = p.rest[:pos] 45 | p.rest = p.rest[pos+1:] 46 | if pos = bytes.IndexByte(p.rest, '/'); pos < 0 { 47 | if value, err := strconv.ParseUint(*(*string)(unsafe.Pointer(&p.rest)), 10, 16); err == nil { 48 | p.Port = uint16(value) 49 | } else { 50 | return false, fmt.Errorf("Cannot convert `%s` into uint16 (field Port)", string(p.rest)) 51 | } 52 | p.rest = p.rest[len(p.rest):] 53 | } else { 54 | tmp = p.rest[:pos] 55 | if value, err := strconv.ParseUint(*(*string)(unsafe.Pointer(&tmp)), 10, 16); err == nil { 56 | p.Port = uint16(value) 57 | } else { 58 | return false, fmt.Errorf("Cannot convert `%s` into uint16 (field Port)", string(p.rest[:pos])) 59 | } 60 | p.rest = p.rest[pos+1:] 61 | } 62 | p.DBName = p.rest 63 | p.rest = p.rest[len(p.rest):] 64 | return true, nil 65 | } 66 | 67 | func (p *components) GetAuthData() (res []byte) { 68 | if p.Auth.Valid { 69 | res = p.Auth.Data 70 | } 71 | return 72 | } 73 | -------------------------------------------------------------------------------- /internal/util/envch_params.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | // EnvCHParams extracts clickhouse connection parameters from CLICKHOUSE environment variable 4 | func EnvCHParams(connParams string) CHParams { 5 | return ExtractCHParams(connParams) 6 | } 7 | -------------------------------------------------------------------------------- /internal/util/extractor_lde.go: -------------------------------------------------------------------------------- 1 | /* 2 | This file was autogenerated via 3 | ------------------------------------------ 4 | ldetool generate --go-string extractor.lde 5 | ------------------------------------------ 6 | do not touch it with bare hands! 7 | */ 8 | 9 | package util 10 | 11 | import ( 12 | "fmt" 13 | "strconv" 14 | "strings" 15 | ) 16 | 17 | var commaSpace = ", " 18 | var decimalLbrack = "Decimal(" 19 | 20 | // Extractor ... 21 | type Extractor struct { 22 | Rest string 23 | Precision int 24 | Scale int 25 | } 26 | 27 | // Extract ... 28 | func (p *Extractor) Extract(line string) (bool, error) { 29 | p.Rest = line 30 | var err error 31 | var pos int 32 | var tmp string 33 | var tmpInt int64 34 | 35 | // Checks if the rest starts with `"Decimal("` and pass it 36 | if strings.HasPrefix(p.Rest, decimalLbrack) { 37 | p.Rest = p.Rest[len(decimalLbrack):] 38 | } else { 39 | return false, nil 40 | } 41 | 42 | // Take until ", " as Precision(int) 43 | pos = strings.Index(p.Rest, commaSpace) 44 | if pos >= 0 { 45 | tmp = p.Rest[:pos] 46 | p.Rest = p.Rest[pos+len(commaSpace):] 47 | } else { 48 | return false, nil 49 | } 50 | if tmpInt, err = strconv.ParseInt(tmp, 10, 64); err != nil { 51 | return false, fmt.Errorf("Cannot parse `%s`: %s", string(tmp), err) 52 | } 53 | p.Precision = int(tmpInt) 54 | 55 | // Take until ')' as Scale(int) 56 | pos = strings.IndexByte(p.Rest, ')') 57 | if pos >= 0 { 58 | tmp = p.Rest[:pos] 59 | p.Rest = p.Rest[pos+1:] 60 | } else { 61 | return false, nil 62 | } 63 | if tmpInt, err = strconv.ParseInt(tmp, 10, 64); err != nil { 64 | return false, fmt.Errorf("Cannot parse `%s`: %s", string(tmp), err) 65 | } 66 | p.Scale = int(tmpInt) 67 | 68 | return true, nil 69 | } 70 | -------------------------------------------------------------------------------- /internal/util/testingch.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | ) 7 | 8 | // CHParams clickhouse connection coordinates 9 | type CHParams struct { 10 | Host string 11 | Port int 12 | User string 13 | Password string 14 | DBName string 15 | } 16 | 17 | // URL for clickhouse connection 18 | func (ch CHParams) URL() string { 19 | req, err := http.NewRequest("GET", fmt.Sprintf("http://%s:%d", ch.Host, ch.Port), nil) 20 | if err != nil { 21 | panic(err) 22 | } 23 | q := req.URL.Query() 24 | if len(ch.User) > 0 { 25 | q.Set("user", ch.User) 26 | } 27 | if len(ch.Password) > 0 { 28 | q.Set("password", ch.Password) 29 | } 30 | if len(ch.DBName) > 0 { 31 | q.Set("database", ch.DBName) 32 | } 33 | req.URL.RawQuery = q.Encode() 34 | return req.URL.String() 35 | } 36 | 37 | // DBURL is a URL generator for Mail.RU's clickhouse connector 38 | func (ch CHParams) DBURL() string { 39 | req, err := http.NewRequest("GET", fmt.Sprintf("http://%s:%d/%s", ch.Host, ch.Port, ch.DBName), nil) 40 | if err != nil { 41 | panic(err) 42 | } 43 | q := req.URL.Query() 44 | if len(ch.User) > 0 { 45 | q.Set("user", ch.User) 46 | } 47 | if len(ch.Password) > 0 { 48 | q.Set("password", ch.Password) 49 | } 50 | req.URL.RawQuery = q.Encode() 51 | return req.URL.String() 52 | } 53 | 54 | // ExtractCHParams extracts CH connection components from string 55 | // [user[:password]@]host:port[/dbname] 56 | func ExtractCHParams(data string) (res CHParams) { 57 | c := &components{} 58 | up := &userpass{} 59 | 60 | if ok, err := c.Parse([]byte(data)); !ok || err != nil { 61 | panic(err) 62 | } 63 | 64 | if ok, err := up.Parse(c.GetAuthData()); !ok || err != nil { 65 | panic(err) 66 | } 67 | 68 | res.Host = string(c.Host) 69 | res.Port = int(c.Port) 70 | res.DBName = string(c.DBName) 71 | res.User = string(up.User) 72 | res.Password = string(up.Password) 73 | return 74 | } 75 | -------------------------------------------------------------------------------- /internal/util/testingch_test.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | ) 8 | 9 | func TestExtractCHParams(t *testing.T) { 10 | require.Equal(t, 11 | CHParams{ 12 | Host: "localhost", 13 | Port: 8123, 14 | DBName: "default", 15 | User: "default", 16 | }, 17 | ExtractCHParams("default@localhost:8123/default"), 18 | ) 19 | require.Equal(t, 20 | CHParams{ 21 | Host: "localhost", 22 | Port: 8123, 23 | }, 24 | ExtractCHParams("localhost:8123"), 25 | ) 26 | require.Equal(t, 27 | CHParams{ 28 | Host: "localhost", 29 | Port: 8123, 30 | DBName: "default", 31 | User: "default", 32 | Password: "bugaga", 33 | }, 34 | ExtractCHParams("default:bugaga@localhost:8123/default"), 35 | ) 36 | } 37 | -------------------------------------------------------------------------------- /internal/util/userpass_parser.go: -------------------------------------------------------------------------------- 1 | /* 2 | * THE FILE WAS GENERATED WITH logparsergen --source=testingch.script --package=util 3 | * DO NOT TOUCH IT! 4 | */ 5 | package util 6 | 7 | import ( 8 | "bytes" 9 | ) 10 | 11 | type userpass struct { 12 | rest []byte 13 | User []byte 14 | Password []byte 15 | } 16 | 17 | func (p *userpass) Parse(line []byte) (bool, error) { 18 | p.rest = line 19 | var pos int 20 | if pos = bytes.IndexByte(p.rest, ':'); pos < 0 { 21 | p.User = p.rest 22 | p.rest = p.rest[len(p.rest):] 23 | } else { 24 | p.User = p.rest[:pos] 25 | p.rest = p.rest[pos+1:] 26 | } 27 | p.Password = p.rest 28 | p.rest = p.rest[len(p.rest):] 29 | return true, nil 30 | } 31 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | 6 | cli "github.com/jawher/mow.cli" 7 | _ "github.com/mailru/go-clickhouse" // Mail.RU's clickhouse connector 8 | _ "github.com/sirkon/binenc" // Binary encoding library go get's "dependency", generated package will need it 9 | _ "github.com/sirkon/go-diff" // Diff for testing 10 | "github.com/sirkon/message" 11 | ) 12 | 13 | const ( 14 | version = "0.1.0" 15 | ) 16 | 17 | func chDefaultParams() string { 18 | res := os.Getenv("CLICKHOUSE") 19 | if len(res) > 0 { 20 | return res 21 | } 22 | return "default@localhost:8123/default" 23 | } 24 | 25 | func main() { 26 | app := cli.App("ch-encode", "Go code generator for Clickhouse data insert") 27 | var ( 28 | test = app.BoolOpt("test", false, "Don't save generated code, just write it into the stdout") 29 | yamlDict = app.StringOpt("yaml-dict", "", "Use this YAML formatted dictionary to generate Goish names") 30 | jsonDict = app.StringOpt("json-dict", "", "User this JSON formatted dictionary to generate Goish names") 31 | dateField = app.StringOpt("date-field", "", "Use this field as a date") 32 | chConn = app.StringOpt("clickhouse", chDefaultParams(), "Clickhouse connection params") 33 | tables = app.StringsArg("TABLES", nil, "List of clickhouse tables") 34 | ) 35 | app.Spec = `[--test] [--yaml-dict|--json-dict] [--date-field] [--clickhouse] TABLES...` 36 | app.Action = func() { 37 | if err := action(*test, *yamlDict, *jsonDict, *dateField, *tables, *chConn); err != nil { 38 | message.Fatal(err) 39 | } 40 | 41 | } 42 | 43 | if err := app.Run(os.Args); err != nil { 44 | message.Fatal(err) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sirkon/ch-encode/c438692c28c4b7e50e557bdb00b281c08ef258c0/screenshot.png --------------------------------------------------------------------------------