├── CONTRIBUTING.md ├── LICENSE ├── Makefile ├── README.md ├── cache.go ├── column.go ├── doc.go ├── docs ├── README.md └── _config.yml ├── go.mod ├── go.sum ├── load.go ├── mapper.go ├── mapper_test.go ├── resolver.go ├── setter.go ├── testdata ├── blog_sql.go ├── initdb │ ├── initdb.pb.go │ ├── initdb.proto │ ├── server.go │ └── sql │ │ └── init_db.go ├── mapper.golden ├── null_sql.go ├── pg_dtypes.go ├── relations_sql.go └── testdata.go └── value ├── cell.go └── sql_types.go /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Thank you for contributing to carta. 4 | 5 | Please follow the code of conduct when interacting with the carta project. 6 | 7 | ## Pull Request Process 8 | 9 | The recommended PR process is as follows. 10 | 11 | 1. Please first discuss the change you wish to make via issue. 12 | 2. Upgrade with `go get -u github.com/jackskj/carta` 13 | 3. cd `$GOPATH/src/github.com/jackskj/carta` 14 | 4. Reflect your changes in example files as well as the README. 15 | 5. You can use gazelle functions (gazelle, repos, and fix) from the Makefile. 16 | 6. Make sure that the build succedes with `go build` or `make build` 17 | and all tests pass with `make test` 18 | 7. Make sure to follow [Effective Go](https://golang.org/doc/effective_go.html) 19 | as well as [Go Code Review Comments](https://golang.org/wiki/CodeReviewComments) 20 | 21 | ## Code of Conduct 22 | 23 | 24 | Please do not make aggressive, harassing or condescending comments based on someone's 25 | age, body size, disability, ethnicity, gender identity and expression, level of experience, 26 | nationality, personal appearance, race, religion, or sexual identity and 27 | orientation. 28 | 29 | Please do not troll, or make insulting/derogatory comments pr personal/political attacks 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PROTO_DIRS= "testdata/initdb" 2 | DBS= "mapper" "plugin" "templates" 3 | 4 | testdbs: 5 | docker run --name carta-postgres-test --env POSTGRES_HOST_AUTH_METHOD=trust -d -p 5432:5432 postgres 6 | docker run --name carta-mysql-test -d --env MYSQL_ALLOW_EMPTY_PASSWORD=yes --env MYSQL_DATABASE=mysql -p 3306:3306 mysql 7 | 8 | gen: 9 | go install . 10 | for i in $(PROTO_DIRS); do \ 11 | protoc --go_out="plugins=grpc:$(GOPATH)/src" \ 12 | -I=. \ 13 | $$i/*.proto ; \ 14 | done 15 | 16 | 17 | install: 18 | # generating map binary in $$GOPATH/bin 19 | go install . 20 | 21 | test: 22 | go test -v 23 | 24 | testu: 25 | go test -v --update 26 | 27 | 28 | .PHONY: gen install testdbs test testu 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # Carta 3 | Dead simple SQL data mapper for complex Go structs. 4 | 5 | Load SQL data onto Go structs while keeping track of has-one and has-many relationships 6 | 7 | ## Examples 8 | Using carta is very simple. All you need to do is: 9 | ``` 10 | // 1) Run your query 11 | if rows, err = sqlDB.Query(blogQuery); err != nil { 12 | // error 13 | } 14 | 15 | // 2) Instantiate a slice(or struct) which you want to populate 16 | blogs := []Blog{} 17 | 18 | // 3) Map the SQL rows to your slice 19 | carta.Map(rows, &blogs) 20 | ``` 21 | 22 | Assume that in above exmple, we are using a schema containing has-one and has-many relationships: 23 | 24 | ![schema](https://i.ibb.co/SPH3zhQ/Schema.png) 25 | 26 | And here is our SQL query along with the corresponging Go struct: 27 | ``` 28 | select 29 | id as blog_id, 30 | title as blog_title, 31 | P.id as posts_id, 32 | P.name as posts_name, 33 | A.id as author_id, 34 | A.username as author_username 35 | from blog 36 | left outer join author A on blog.author_id = A.id 37 | left outer join post P on blog.id = P.blog_id 38 | ``` 39 | 40 | ``` 41 | type Blog struct { 42 | Id int `db:"blog_id"` 43 | Title string `db:"blog_title"` 44 | Posts []Post 45 | Author Author 46 | } 47 | type Post struct { 48 | Id int `db:"posts_id"` 49 | Name string `db:"posts_name"` 50 | } 51 | type Author struct { 52 | Id int `db:"author_id"` 53 | Username string `db:"author_username"` 54 | } 55 | ``` 56 | Carta will map the SQL rows while keeping track of those relationships. 57 | 58 | Results: 59 | ``` 60 | rows: 61 | blog_id | blog_title | posts_id | posts_name | author_id | author_username 62 | 1 | Foo | 1 | Bar | 1 | John 63 | 1 | Foo | 2 | Baz | 1 | John 64 | 2 | Egg | 3 | Beacon | 2 | Ed 65 | 66 | blogs: 67 | [{ 68 | "blog_id": 1, 69 | "blog_title": "Foo", 70 | "author": { 71 | "author_id": 1, 72 | "author_username": "John" 73 | }, 74 | "posts": [{ 75 | "post_id": 1, 76 | "posts_name": "Bar" 77 | }, { 78 | "post_id": 2, 79 | "posts_name": "Baz" 80 | }] 81 | }, { 82 | "blog_id": 2, 83 | "blog_title": "Egg", 84 | "author": { 85 | "author_id": 2, 86 | "author_username": "Ed" 87 | }, 88 | "posts": [{ 89 | "post_id": 3, 90 | "posts_name": "Beacon" 91 | }] 92 | }] 93 | ``` 94 | 95 | 96 | ## Comparison to Related Projects 97 | 98 | #### GORM 99 | Carta is NOT an an object-relational mapper(ORM). Read more in [Approach](#Approach) 100 | 101 | #### sqlx 102 | Sqlx does not track has-many relationships when mapping SQL data. This works fine when all your relationships are at most has-one (Blog has one Author) ie, each SQL row corresponds to one struct. However, handling has-many relationships (Blog has many Posts), requires running many queries or running manual post-processing of the result. Carta handles these complexities automatically. 103 | 104 | ## Guide 105 | 106 | ### Column and Field Names 107 | 108 | Carta will match your SQL columns with corresponding fields. You can use a "db" tag to represent a specific column name. 109 | Example: 110 | 111 | ``` 112 | type Blog struct { 113 | // When tag is not used, the snake case of the fiels is used 114 | BlogId int // expected column name : "blog_id" 115 | 116 | // When tag is specified, it takes priority 117 | Abc string `db:"blog_title"` // expected column name: "blog_title" 118 | 119 | // If you define multiple fiels with the same struct, 120 | // you can use a tag to identify a column prefix 121 | // (with underscore concatination) 122 | 123 | // possible column names: "writer_author_id", "author_id" 124 | Writer Author `db: "writer"` 125 | 126 | // possible column names: "rewiewer_author_id", "author_id", 127 | Reviewer Author `db: "reviewer"` 128 | } 129 | 130 | type Author struct { 131 | AuthorId int `db:"author_id"` 132 | } 133 | ``` 134 | 135 | ### Data Types and Relationships 136 | 137 | Any primative types, time.Time, protobuf Timestamp, and sql.NullX can be loaded with Carta. 138 | These types are one-to-one mapped with your SQL columns 139 | 140 | To define more complex SQL relationships use slices and structs as in example below: 141 | 142 | ``` 143 | type Blog struct { 144 | BlogId int // Will map directly with "blog_id" column 145 | 146 | // If your SQL data can be "null", use pointers or sql.NullX 147 | AuthorId *int 148 | CreatedOn *timestamp.Timestamp // protobuf timestamp 149 | UpdatedOn *time.Time 150 | SonsorId sql.NullInt64 151 | 152 | // To define has-one relationship, use nested structs 153 | // or pointer to a struct 154 | Author *Author 155 | 156 | // To define has-many relationship, use slices 157 | // options include: *[]*Post, []*Post, *[]Post, []Post 158 | Posts []*Post 159 | 160 | // If your has-many relationship corresponds to one column, 161 | // you can use a slice of a settable type 162 | TagIds []int `db:"tag_id"` 163 | CommentIds []sql.NullInt64 `db:"comment_id"` 164 | } 165 | ``` 166 | 167 | ### Drivers 168 | 169 | Recommended driver for Postgres is [lib/pg](https://github.com/lib/pq), for MySql use [go-sql-driver/mysql](https://github.com/go-sql-driver/mysql). 170 | 171 | When using MySql, carta expects time data to arrive in time.Time format. Therefore, make sure to add "parseTime=true" in your connection string, when using DATE and DATETIME types. 172 | 173 | Other types, such as TIME, will will be converted from plain text in future versions of Carta. 174 | 175 | ## Installation 176 | ``` 177 | go get -u github.com/jackskj/carta 178 | ``` 179 | 180 | 181 | ## Important Notes 182 | 183 | Carta removes any duplicate rows. This is a side effect of the data mapping as it is unclear which object to instantiate if the same data arrives more than once. 184 | If this is not a desired outcome, you should include a uniquely identifiable columns in your query and the corresponding fields in your structs. 185 | 186 | To prevent relatively expensive reflect operations, carta caches the structure of your struct using the column mames of your query response as well as the type of your struct. 187 | 188 | ## Approach 189 | Carta adopts the "database mapping" approach (described in Martin Fowler's [book](https://books.google.com/books?id=FyWZt5DdvFkC&lpg=PA1&dq=Patterns%20of%20Enterprise%20Application%20Architecture%20by%20Martin%20Fowler&pg=PT187#v=onepage&q=active%20record&f=false)) which is useful among organizations with strict code review processes. 190 | 191 | Carta is not an object-relational mapper(ORM). With large and complex datasets, using ORMs becomes restrictive and reduces performance when working with complex queries. 192 | 193 | ### License 194 | Apache License 195 | -------------------------------------------------------------------------------- /cache.go: -------------------------------------------------------------------------------- 1 | package carta 2 | 3 | import ( 4 | "reflect" 5 | "strings" 6 | "sync" 7 | ) 8 | 9 | var mapperCache = newCache() 10 | 11 | type cache struct { 12 | mapCache sync.Map 13 | } 14 | 15 | func newCache() *cache { 16 | return &cache{} 17 | } 18 | 19 | type mapperEntry struct { 20 | columns []string 21 | dst reflect.Type 22 | } 23 | 24 | func (m *mapperEntry) raw() string { 25 | // TODO: test how this works with unexported types 26 | // TODO: add a way to provide fully qualified name for the type, since m.typ is always a pointer to a struct or slice 27 | // return strings.Join(m.columns, ",") + "|" + m.dst.PkgPath() + "." + m.dst.String() 28 | return strings.Join(m.columns, ",") + "|" + m.dst.String() 29 | } 30 | 31 | func (c *cache) loadMap(columns []string, dst reflect.Type) (mapper *Mapper, ok bool) { 32 | entry := mapperEntry{columns, dst} 33 | vmap, ok := c.mapCache.Load(entry.raw()) 34 | if ok { 35 | mapper = vmap.(*Mapper) 36 | } 37 | return 38 | } 39 | 40 | func (c *cache) storeMap(columns []string, dst reflect.Type, mapper *Mapper) { 41 | entry := mapperEntry{columns, dst} 42 | c.mapCache.Store(entry.raw(), mapper) 43 | } 44 | -------------------------------------------------------------------------------- /column.go: -------------------------------------------------------------------------------- 1 | package carta 2 | 3 | import ( 4 | "database/sql" 5 | "sort" 6 | "strings" 7 | ) 8 | 9 | // column represents the ith struct field of this mapper where the column is to be mapped 10 | type column struct { 11 | typ *sql.ColumnType 12 | name string 13 | columnIndex int 14 | i fieldIndex 15 | } 16 | 17 | func allocateColumns(m *Mapper, columns map[string]column) error { 18 | var ( 19 | candidates map[string]bool 20 | ) 21 | presentColumns := map[string]column{} 22 | for cName, c := range columns { 23 | if m.IsBasic { 24 | candidates = getColumnNameCandidates("", m.AncestorNames) 25 | if _, ok := candidates[cName]; ok { 26 | presentColumns[cName] = column{ 27 | typ: c.typ, 28 | name: cName, 29 | columnIndex: c.columnIndex, 30 | } 31 | delete(columns, cName) // dealocate claimed column 32 | } 33 | } else { 34 | for i, field := range m.Fields { 35 | candidates = getColumnNameCandidates(field.Name, m.AncestorNames) 36 | // can only allocate columns to basic fields 37 | if isBasicType(field.Typ) { 38 | if _, ok := candidates[cName]; ok { 39 | presentColumns[cName] = column{ 40 | typ: c.typ, 41 | name: cName, 42 | columnIndex: c.columnIndex, 43 | i: i, 44 | } 45 | delete(columns, cName) // dealocate claimed column 46 | } 47 | } 48 | } 49 | } 50 | } 51 | m.PresentColumns = presentColumns 52 | 53 | columnIds := []int{} 54 | for _, column := range m.PresentColumns { 55 | if _, ok := m.SubMaps[column.i]; ok { 56 | continue 57 | } 58 | columnIds = append(columnIds, column.columnIndex) 59 | } 60 | sort.Ints(columnIds) 61 | m.SortedColumnIndexes = columnIds 62 | 63 | ancestorNames := []string{} 64 | if len(m.AncestorNames) != 0 { 65 | ancestorNames = m.AncestorNames 66 | } 67 | 68 | for i, subMap := range m.SubMaps { 69 | subMap.AncestorNames = append(ancestorNames, m.Fields[i].Name) 70 | if err := allocateColumns(subMap, columns); err != nil { 71 | return err 72 | } 73 | } 74 | return nil 75 | } 76 | 77 | func getColumnNameCandidates(fieldName string, ancestorNames []string) map[string]bool { 78 | // empty field name means that the mapper is basic, since there is no struct assiciated with this slice, there is no field name 79 | candidates := map[string]bool{} 80 | if fieldName != "" { 81 | candidates[fieldName] = true 82 | candidates[toSnakeCase(fieldName)] = true 83 | candidates[strings.ToLower(fieldName)] = true 84 | } 85 | if len(ancestorNames) == 0 { 86 | return candidates 87 | } 88 | nameConcat := fieldName 89 | for i := len(ancestorNames) - 1; i >= 0; i-- { 90 | if nameConcat == "" { 91 | nameConcat = ancestorNames[i] 92 | } else { 93 | nameConcat = ancestorNames[i] + "_" + nameConcat 94 | } 95 | candidates[nameConcat] = true 96 | candidates[strings.ToLower(nameConcat)] = true 97 | candidates[toSnakeCase(nameConcat)] = true 98 | } 99 | return candidates 100 | } 101 | 102 | func toSnakeCase(s string) string { 103 | delimiter := "_" 104 | s = strings.Trim(s, " ") 105 | n := "" 106 | for i, v := range s { 107 | nextCaseIsChanged := false 108 | if i+1 < len(s) { 109 | next := s[i+1] 110 | vIsCap := v >= 'A' && v <= 'Z' 111 | vIsLow := v >= 'a' && v <= 'z' 112 | nextIsCap := next >= 'A' && next <= 'Z' 113 | nextIsLow := next >= 'a' && next <= 'z' 114 | if (vIsCap && nextIsLow) || (vIsLow && nextIsCap) { 115 | nextCaseIsChanged = true 116 | } 117 | } 118 | 119 | if i > 0 && n[len(n)-1] != uint8(delimiter[0]) && nextCaseIsChanged { 120 | if v >= 'A' && v <= 'Z' { 121 | n += string(delimiter) + string(v) 122 | } else if v >= 'a' && v <= 'z' { 123 | n += string(v) + string(delimiter) 124 | } 125 | } else if v == ' ' || v == '-' { 126 | n += string(delimiter) 127 | } else { 128 | n = n + string(v) 129 | } 130 | } 131 | return strings.ToLower(n) 132 | } 133 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Carta is a simple SQL data mapper for complex Go structs. 4 | Loads SQL data onto Go structs while keeping track of has-one and has-many relationships 5 | 6 | To use carta: 7 | 8 | 1) Run your query 9 | if rows, err = sqlDB.Query(blogQuery); err != nil { 10 | // error 11 | } 12 | 13 | 2) Instantiate a slice(or struct) which you want to populate 14 | blogs := []Blog{} 15 | 16 | 3) Map the SQL rows to your slice 17 | if err := carta.Map(rows, &blogs); err != nil { 18 | // error 19 | } 20 | 21 | For more examples and guides go to: https://jackskj.github.io/carta/ 22 | 23 | */ 24 | 25 | package carta 26 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | 2 | # Carta 3 | Dead simple SQL data mapper for complex Go structs. 4 | 5 | Load SQL data onto Go structs while keeping track of has-one and has-many relationships 6 | 7 | ## Examples 8 | Using carta is very simple. All you need to do is: 9 | ``` 10 | // 1) Run your query 11 | if rows, err = sqlDB.Query(blogQuery); err != nil { 12 | // error 13 | } 14 | 15 | // 2) Instantiate a slice(or struct) which you want to populate 16 | blogs := []Blog{} 17 | 18 | // 3) Map the SQL rows to your slice 19 | carta.Map(rows, &blogs) 20 | ``` 21 | 22 | Assume that in above exmple, we are using a schema containing has-one and has-many relationships: 23 | 24 | ![schema](https://i.ibb.co/SPH3zhQ/Schema.png) 25 | 26 | And here is our SQL query along with the corresponging Go struct: 27 | ``` 28 | select 29 | id as blog_id, 30 | title as blog_title, 31 | P.id as posts_id, 32 | P.name as posts_name, 33 | A.id as author_id, 34 | A.username as author_username 35 | from blog 36 | left outer join author A on blog.author_id = A.id 37 | left outer join post P on blog.id = P.blog_id 38 | ``` 39 | 40 | ``` 41 | type Blog struct { 42 | Id int `db:"blog_id"` 43 | Title string `db:"blog_title"` 44 | Posts []Post 45 | Author Author 46 | } 47 | type Post struct { 48 | Id int `db:"posts_id"` 49 | Name string `db:"posts_name"` 50 | } 51 | type Author struct { 52 | Id int `db:"author_id"` 53 | Username string `db:"author_username"` 54 | } 55 | ``` 56 | Carta will map the SQL rows while keeping track of those relationships. 57 | 58 | Results: 59 | ``` 60 | rows: 61 | blog_id | blog_title | posts_id | posts_name | author_id | author_username 62 | 1 | Foo | 1 | Bar | 1 | John 63 | 1 | Foo | 2 | Baz | 1 | John 64 | 2 | Egg | 3 | Beacon | 2 | Ed 65 | 66 | blogs: 67 | [{ 68 | "blog_id": 1, 69 | "blog_title": "Foo", 70 | "author": { 71 | "author_id": 1, 72 | "author_username": "John" 73 | }, 74 | "posts": [{ 75 | "post_id": 1, 76 | "posts_name": "Bar" 77 | }, { 78 | "post_id": 2, 79 | "posts_name": "Baz" 80 | }] 81 | }, { 82 | "blog_id": 2, 83 | "blog_title": "Egg", 84 | "author": { 85 | "author_id": 2, 86 | "author_username": "Ed" 87 | }, 88 | "posts": [{ 89 | "post_id": 3, 90 | "posts_name": "Beacon" 91 | }] 92 | }] 93 | ``` 94 | 95 | 96 | ## Comparison to Related Projects 97 | 98 | #### GORM 99 | Carta is NOT an an object-relational mapper(ORM). Read more in [Approach](#Approach) 100 | 101 | #### sqlx 102 | Sqlx does not track has-many relationships when mapping SQL data. This works fine when all your relationships are at most has-one (Blog has one Author) ie, each SQL row corresponds to one struct. However, handling has-many relationships (Blog has many Posts), requires running many queries or running manual post-processing of the result. Carta handles these complexities automatically. 103 | 104 | ## Guide 105 | 106 | ### Column and Field Names 107 | 108 | Carta will match your SQL columns with corresponding fields. You can use a "db" tag to represent a specific column name. 109 | Example: 110 | 111 | ``` 112 | type Blog struct { 113 | // When tag is not used, the snake case of the fiels is used 114 | BlogId int // expected column name : "blog_id" 115 | 116 | // When tag is specified, it takes priority 117 | Abc string `db:"blog_title"` // expected column name: "blog_title" 118 | 119 | // If you define multiple fiels with the same struct, 120 | // you can use a tag to identify a column prefix 121 | // (with underscore concatination) 122 | 123 | // possible column names: "writer_author_id", "author_id" 124 | Writer Author `db: "writer"` 125 | 126 | // possible column names: "rewiewer_author_id", "author_id", 127 | Reviewer Author `db: "reviewer"` 128 | } 129 | 130 | type Author struct { 131 | AuthorId int `db:"author_id"` 132 | } 133 | ``` 134 | 135 | ### Data Types and Relationships 136 | 137 | Any primative types, time.Time, protobuf Timestamp, and sql.NullX can be loaded with Carta. 138 | These types are one-to-one mapped with your SQL columns 139 | 140 | To define more complex SQL relationships use slices and structs as in example below: 141 | 142 | ``` 143 | type Blog struct { 144 | BlogId int // Will map directly with "blog_id" column 145 | 146 | // If your SQL data can be "null", use pointers or sql.NullX 147 | AuthorId *int 148 | CreatedOn *timestamp.Timestamp // protobuf timestamp 149 | UpdatedOn *time.Time 150 | SonsorId sql.NullInt64 151 | 152 | // To define has-one relationship, use nested structs 153 | // or pointer to a struct 154 | Author *Author 155 | 156 | // To define has-many relationship, use slices 157 | // options include: *[]*Post, []*Post, *[]Post, []Post 158 | Posts []*Post 159 | 160 | // If your has-many relationship corresponds to one column, 161 | // you can use a slice of a settable type 162 | TagIds []int `db:"tag_id"` 163 | CommentIds []sql.NullInt64 `db:"comment_id"` 164 | } 165 | ``` 166 | 167 | ### Drivers 168 | 169 | Recommended driver for Postgres is [lib/pg](https://github.com/lib/pq), for MySql use [go-sql-driver/mysql](https://github.com/go-sql-driver/mysql). 170 | 171 | When using MySql, carta expects time data to arrive in time.Time format. Therefore, make sure to add "parseTime=true" in your connection string, when using DATE and DATETIME types. 172 | 173 | Other types, such as TIME, will will be converted from plain text in future versions of Carta. 174 | 175 | ## Installation 176 | ``` 177 | go get -u github.com/jackskj/carta 178 | ``` 179 | 180 | 181 | ## Important Notes 182 | 183 | Carta removes any duplicate rows. This is a side effect of the data mapping as it is unclear which object to instantiate if the same data arrives more than once. 184 | If this is not a desired outcome, you should include a uniquely identifiable columns in your query and the corresponding fields in your structs. 185 | 186 | To prevent relatively expensive reflect operations, carta caches the structure of your struct using the column mames of your query response as well as the type of your struct. 187 | 188 | 189 | 190 | ## Approach 191 | Carta adopts the "database mapping" approach (described in Martin Fowler's [book](https://books.google.com/books?id=FyWZt5DdvFkC&lpg=PA1&dq=Patterns%20of%20Enterprise%20Application%20Architecture%20by%20Martin%20Fowler&pg=PT187#v=onepage&q=active%20record&f=false)) which is useful among organizations with strict code review processes. 192 | 193 | Carta is not an object-relational mapper(ORM). With large and complex datasets, using ORMs becomes restrictive and reduces performance when working with complex queries. 194 | 195 | ### License 196 | Apache License 197 | -------------------------------------------------------------------------------- /docs/_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-slate -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/jackskj/carta 2 | 3 | go 1.14 4 | 5 | require ( 6 | github.com/Masterminds/goutils v1.1.0 // indirect 7 | github.com/Masterminds/semver v1.5.0 // indirect 8 | github.com/Masterminds/sprig v2.22.0+incompatible 9 | github.com/go-sql-driver/mysql v1.5.0 10 | github.com/golang/protobuf v1.4.2 11 | github.com/google/uuid v1.1.1 // indirect 12 | github.com/huandu/xstrings v1.3.1 // indirect 13 | github.com/imdario/mergo v0.3.9 // indirect 14 | github.com/jackskj/protoc-gen-map v0.4.1 15 | github.com/lib/pq v1.6.0 16 | github.com/mitchellh/copystructure v1.0.0 // indirect 17 | github.com/nsf/jsondiff v0.0.0-20200515183724-f29ed568f4ce 18 | github.com/sergi/go-diff v1.1.0 // indirect 19 | github.com/yudai/gojsondiff v1.0.0 20 | github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 // indirect 21 | google.golang.org/appengine v1.4.0 22 | google.golang.org/grpc v1.29.1 23 | google.golang.org/protobuf v1.23.0 24 | ) 25 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 3 | github.com/Masterminds/goutils v1.1.0 h1:zukEsf/1JZwCMgHiK3GZftabmxiCw4apj3a28RPBiVg= 4 | github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= 5 | github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= 6 | github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= 7 | github.com/Masterminds/sprig v2.22.0+incompatible h1:z4yfnGrZ7netVz+0EDJ0Wi+5VZCSYp4Z0m2dk6cEM60= 8 | github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o= 9 | github.com/alexbrainman/sspi v0.0.0-20180613141037-e580b900e9f5/go.mod h1:976q2ETgjT2snVCf2ZaBnyBbVoPERGjUz+0sofzEfro= 10 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= 11 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 12 | github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= 13 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 14 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 15 | github.com/elgris/jsondiff v0.0.0-20160530203242-765b5c24c302/go.mod h1:qBlWZqWeVx9BjvqBsnC/8RUlAYpIFmPvgROcw0n1scE= 16 | github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 17 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 18 | github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= 19 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= 20 | github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= 21 | github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= 22 | github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= 23 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 24 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 25 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 26 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 27 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 28 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 29 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 30 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 31 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 32 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 33 | github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= 34 | github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= 35 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 36 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 37 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 38 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 39 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 40 | github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= 41 | github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= 42 | github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 43 | github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= 44 | github.com/gorilla/sessions v1.2.0/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= 45 | github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE= 46 | github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= 47 | github.com/huandu/xstrings v1.2.0/go.mod h1:DvyZB1rfVYsBIigL8HwpZgxHwXozlTgGqn63UyNX5k4= 48 | github.com/huandu/xstrings v1.3.1 h1:4jgBlKK6tLKFvO8u5pmYjG91cqytmDCDvGh7ECVFfFs= 49 | github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= 50 | github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= 51 | github.com/imdario/mergo v0.3.9 h1:UauaLniWCFHWd+Jp9oCEkTBj8VO/9DKg3PV3VCNMDIg= 52 | github.com/imdario/mergo v0.3.9/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= 53 | github.com/jackskj/protoc-gen-map v0.4.1 h1:7rRH8F3eOjnXxmcRtkd+tqCU/vlV7mYagFbkOZzAno4= 54 | github.com/jackskj/protoc-gen-map v0.4.1/go.mod h1:wSj4EIJEvPhk6NBaoFfMV/HP8basJIIpW3tnBahkfds= 55 | github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8= 56 | github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs= 57 | github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo= 58 | github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM= 59 | github.com/jcmturner/gofork v1.0.0 h1:J7uCkflzTEhUZ64xqKnkDxq3kzc96ajM1Gli5ktUem8= 60 | github.com/jcmturner/gofork v1.0.0/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o= 61 | github.com/jcmturner/goidentity/v6 v6.0.1 h1:VKnZd2oEIMorCTsFBnJWbExfNN7yZr3EhJAxwOkZg6o= 62 | github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg= 63 | github.com/jcmturner/gokrb5/v8 v8.2.0 h1:lzPl/30ZLkTveYsYZPKMcgXc8MbnE6RsTd4F9KgiLtk= 64 | github.com/jcmturner/gokrb5/v8 v8.2.0/go.mod h1:T1hnNppQsBtxW0tCHMHTkAt8n/sABdzZgZdoFrZaZNM= 65 | github.com/jcmturner/rpc/v2 v2.0.2 h1:gMB4IwRXYsWw4Bc6o/az2HJgFUA1ffSh90i26ZJ6Xl0= 66 | github.com/jcmturner/rpc/v2 v2.0.2/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc= 67 | github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= 68 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 69 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 70 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 71 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 72 | github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= 73 | github.com/lib/pq v1.6.0 h1:I5DPxhYJChW9KYc66se+oKFFQX6VuQrKiprsX6ivRZc= 74 | github.com/lib/pq v1.6.0/go.mod h1:4vXEAYvW1fRQ2/FhZ78H73A60MHw1geSm145z2mdY1g= 75 | github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= 76 | github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= 77 | github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= 78 | github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= 79 | github.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ= 80 | github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= 81 | github.com/mitchellh/reflectwalk v1.0.0 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/IfikLNY= 82 | github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= 83 | github.com/mitchellh/reflectwalk v1.0.1 h1:FVzMWA5RllMAKIdUSC8mdWo3XtwoecrH79BY70sEEpE= 84 | github.com/mitchellh/reflectwalk v1.0.1/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= 85 | github.com/nsf/jsondiff v0.0.0-20200515183724-f29ed568f4ce h1:RPclfga2SEJmgMmz2k+Mg7cowZ8yv4Trqw9UsJby758= 86 | github.com/nsf/jsondiff v0.0.0-20200515183724-f29ed568f4ce/go.mod h1:uFMI8w+ref4v2r9jz+c9i1IfIttS/OkmLfrk1jne5hs= 87 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 88 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 89 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= 90 | github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= 91 | github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= 92 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 93 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 94 | github.com/yudai/gojsondiff v1.0.0 h1:27cbfqXLVEJ1o8I6v3y9lg8Ydm53EKqHXAOMxEGlCOA= 95 | github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg= 96 | github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 h1:BHyfKlQyqbsFN5p3IfnEUduWvb9is428/nNb5L3U01M= 97 | github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM= 98 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 99 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 100 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 101 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 102 | golang.org/x/crypto v0.0.0-20191029031824-8986dd9e96cf/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 103 | golang.org/x/crypto v0.0.0-20200117160349-530e935923ad/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 104 | golang.org/x/crypto v0.0.0-20200311171314-f7b00557c8c4 h1:QmwruyY+bKbDDL0BaglrbZABEali68eoMFhTZpCjYVA= 105 | golang.org/x/crypto v0.0.0-20200311171314-f7b00557c8c4/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 106 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 107 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 108 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 109 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 110 | golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= 111 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 112 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 113 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 114 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 115 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 116 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 117 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 118 | golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa h1:F+8P+gmewFQYRk6JoLQLwjBCTu3mcIURZfNkVweuRKA= 119 | golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 120 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 121 | golang.org/x/net v0.0.0-20200506145744-7e3656a0809f h1:QBjCr1Fz5kw158VqdE9JfI9cJnl/ymnJWAdMuinqL7Y= 122 | golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 123 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 124 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 125 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 126 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 127 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 128 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 129 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 130 | golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 131 | golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI= 132 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 133 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 134 | golang.org/x/sys v0.0.0-20200509044756-6aff5f38e54f h1:mOhmO9WsBaJCNmaZHPtHs9wOcdqdKCjF6OPJlmDM3KI= 135 | golang.org/x/sys v0.0.0-20200509044756-6aff5f38e54f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 136 | golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= 137 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 138 | golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= 139 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 140 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 141 | golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 142 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 143 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 144 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 145 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 146 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 147 | golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 148 | golang.org/x/tools v0.0.0-20200407041343-bf15fae40dea/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 149 | golang.org/x/tools/gopls v0.4.0/go.mod h1:fdOZ8zb6nqlePvfek79JCskQXI4W+i2e1xT+xOPKMcY= 150 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 151 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 152 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 153 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 154 | google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508= 155 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 156 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 157 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55 h1:gSJIx1SDwno+2ElGhA4+qG2zF97qiUzTM+rQ0klBOcE= 158 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 159 | google.golang.org/genproto v0.0.0-20200507105951-43844f6eee31 h1:Bz1qTn2YRWV+9OKJtxHJiQKCiXIdf+kwuKXdt9cBxyU= 160 | google.golang.org/genproto v0.0.0-20200507105951-43844f6eee31/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 161 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 162 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 163 | google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= 164 | google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 165 | google.golang.org/grpc v1.29.1 h1:EC2SB8S04d2r73uptxphDSUG+kTKVgjRPF+N3xpxRB4= 166 | google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= 167 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 168 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 169 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 170 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 171 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 172 | google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 173 | google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM= 174 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 175 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 176 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 177 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 178 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 179 | gopkg.in/jcmturner/aescts.v1 v1.0.1/go.mod h1:nsR8qBOg+OucoIW+WMhB3GspUQXq9XorLnQb9XtvcOo= 180 | gopkg.in/jcmturner/dnsutils.v1 v1.0.1/go.mod h1:m3v+5svpVOhtFAP/wSz+yzh4Mc0Fg7eRhxkJMWSIz9Q= 181 | gopkg.in/jcmturner/goidentity.v3 v3.0.0/go.mod h1:oG2kH0IvSYNIu80dVAyu/yoefjq1mNfM5bm88whjWx4= 182 | gopkg.in/jcmturner/gokrb5.v7 v7.5.0/go.mod h1:l8VISx+WGYp+Fp7KRbsiUuXTTOnxIc3Tuvyavf11/WM= 183 | gopkg.in/jcmturner/rpc.v1 v1.1.0/go.mod h1:YIdkC4XfD6GXbzje11McwsDuOlZQSb9W4vfLvuNnlv8= 184 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 185 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 186 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 187 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 188 | honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= 189 | mvdan.cc/xurls/v2 v2.1.0/go.mod h1:5GrSd9rOnKOpZaji1OZLYL/yeAAtGDlo/cFe+8K5n8E= 190 | -------------------------------------------------------------------------------- /load.go: -------------------------------------------------------------------------------- 1 | package carta 2 | 3 | import ( 4 | "database/sql" 5 | "errors" 6 | "fmt" 7 | "reflect" 8 | 9 | "github.com/jackskj/carta/value" 10 | ) 11 | 12 | func (m *Mapper) loadRows(rows *sql.Rows, colTyps []*sql.ColumnType) (*resolver, error) { 13 | defer rows.Close() // may not need 14 | var err error 15 | row := make([]interface{}, len(colTyps)) 16 | colTypNames := make([]string, len(colTyps)) 17 | for i := 0; i < len(colTyps); i++ { 18 | colTypNames[i] = colTyps[i].DatabaseTypeName() 19 | } 20 | rsv := newResolver() 21 | for rows.Next() { 22 | for i := 0; i < len(colTyps); i++ { 23 | row[i] = value.NewCell(colTypNames[i]) 24 | } 25 | if err = rows.Scan(row...); err != nil { 26 | return nil, err 27 | } 28 | if err = loadRow(m, row, rsv); err != nil { 29 | return nil, err 30 | } 31 | } 32 | return rsv, nil 33 | } 34 | 35 | // load row maps a single sql row onto a structure that resembles the users struct 36 | // that mapping is stored in the resolver as a pointer reference to an instance of the struct 37 | // 38 | // if new object is foind, create a new instance of a struct that 39 | // maps onto that struct, 40 | // for example, if a user maps onto: 41 | // type Blog struct { 42 | // BlogId string 43 | // } 44 | // blogs := []Blog: 45 | // carta.Map(rows, &blogs) 46 | // if a new blog_id column value is found, I instantiatiate a new instance of Blog, 47 | // set BlogId, then store the pointer referenct to this instance in the resolver 48 | // nothins is done when the object has been already mapped in previous rows, however, 49 | // the function contunous to recursivelly map rows for all sub mappings inside Blog 50 | // for example, if a blog has many Authors 51 | // rows are actually []*Cell, theu are passed here as interface since sql scan requires []interface{} 52 | func loadRow(m *Mapper, row []interface{}, rsv *resolver) error { 53 | var ( 54 | err error 55 | dstField reflect.Value // destination field to be set with 56 | cell *value.Cell 57 | elem *element 58 | found bool 59 | ) 60 | 61 | uid := getUniqueId(row, m) 62 | 63 | if elem, found = rsv.elements[uid]; !found { 64 | // unique row mapping found, new object 65 | loadElem := reflect.New(m.Typ).Elem() 66 | 67 | for _, col := range m.PresentColumns { 68 | var ( 69 | kind reflect.Kind // kind of destination 70 | dst reflect.Value // destination to set 71 | typ reflect.Type // underlying type of the destination 72 | isDstPtr bool //is the destination a pointer 73 | ) 74 | 75 | cell = row[col.columnIndex].(*value.Cell) 76 | 77 | if m.IsBasic { 78 | dst = loadElem 79 | kind = m.Kind 80 | typ = m.Typ 81 | isDstPtr = m.IsTypePtr 82 | } else { 83 | dstField = loadElem.Field(int(col.i)) 84 | if m.Fields[col.i].IsPtr { 85 | dst = reflect.New(m.Fields[col.i].ElemTyp).Elem() 86 | kind = m.Fields[col.i].ElemKind 87 | typ = m.Fields[col.i].ElemTyp 88 | isDstPtr = true 89 | } else { 90 | dst = dstField 91 | kind = m.Fields[col.i].Kind 92 | typ = m.Fields[col.i].Typ 93 | isDstPtr = false 94 | } 95 | } 96 | if cell.IsNull() { 97 | _, nullable := value.NullableTypes[typ] 98 | if !(isDstPtr || nullable) { 99 | return errors.New(fmt.Sprintf("carta: cannot load null value to type %s for column %s", typ, col.name)) 100 | } 101 | // no need to set destination if cell is null 102 | } else { 103 | switch kind { 104 | case reflect.Bool: 105 | if d, err := cell.Bool(); err != nil { 106 | return value.ConvertsionError(err, typ) 107 | } else { 108 | dst.SetBool(d) 109 | } 110 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: 111 | if d, err := cell.Uint64(); err != nil { 112 | return value.ConvertsionError(err, typ) 113 | } else { 114 | dst.SetUint(d) 115 | } 116 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 117 | if d, err := cell.Int64(); err != nil { 118 | return value.ConvertsionError(err, typ) 119 | } else { 120 | dst.SetInt(d) 121 | } 122 | case reflect.String: 123 | if d, err := cell.String(); err != nil { 124 | return value.ConvertsionError(err, typ) 125 | } else { 126 | dst.SetString(d) 127 | } 128 | case reflect.Float32, reflect.Float64: 129 | if d, err := cell.Float64(); err != nil { 130 | return value.ConvertsionError(err, typ) 131 | } else { 132 | dst.SetFloat(d) 133 | } 134 | case reflect.Struct: 135 | if strTyp, ok := value.BasicTypes[typ]; ok { 136 | // TODO: Type asserion, prevent from calling ValueOf 137 | // TODO: make these stupid error checks more concise 138 | // this swich statement should be optimized 139 | 140 | switch strTyp { 141 | case value.Time: 142 | if d, err := cell.Time(); err != nil { 143 | return value.ConvertsionError(err, typ) 144 | } else { 145 | dst.Set(reflect.ValueOf(d)) 146 | } 147 | case value.Timestamp: 148 | if d, err := cell.Timestamp(); err != nil { 149 | return value.ConvertsionError(err, typ) 150 | } else { 151 | dst.Set(reflect.ValueOf(d)) 152 | } 153 | case value.NullBool: 154 | if d, err := cell.NullBool(); err != nil { 155 | return value.ConvertsionError(err, typ) 156 | } else { 157 | dst.Set(reflect.ValueOf(d)) 158 | } 159 | case value.NullFloat64: 160 | if d, err := cell.NullFloat64(); err != nil { 161 | return value.ConvertsionError(err, typ) 162 | } else { 163 | dst.Set(reflect.ValueOf(d)) 164 | } 165 | case value.NullInt32: 166 | if d, err := cell.NullInt32(); err != nil { 167 | return value.ConvertsionError(err, typ) 168 | } else { 169 | dst.Set(reflect.ValueOf(d)) 170 | } 171 | case value.NullInt64: 172 | if d, err := cell.NullInt64(); err != nil { 173 | return value.ConvertsionError(err, typ) 174 | } else { 175 | dst.Set(reflect.ValueOf(d)) 176 | } 177 | case value.NullString: 178 | if d, err := cell.NullString(); err != nil { 179 | return value.ConvertsionError(err, typ) 180 | } else { 181 | dst.Set(reflect.ValueOf(d)) 182 | } 183 | case value.NullTime: 184 | if d, err := cell.NullTime(); err != nil { 185 | return value.ConvertsionError(err, typ) 186 | } else { 187 | dst.Set(reflect.ValueOf(d)) 188 | } 189 | } 190 | } 191 | } 192 | if !m.IsBasic && m.Fields[col.i].IsPtr { 193 | dstField.Set(dst.Addr()) 194 | } 195 | } 196 | } 197 | elem = &element{v: loadElem} 198 | if len(m.SubMaps) != 0 { 199 | elem.subMaps = map[fieldIndex]*resolver{} 200 | for i, _ := range m.SubMaps { 201 | elem.subMaps[i] = newResolver() 202 | } 203 | } 204 | rsv.elements[uid] = elem 205 | rsv.elementOrder = append(rsv.elementOrder, uid) 206 | } 207 | 208 | for i, subMap := range m.SubMaps { 209 | if err = loadRow(subMap, row, elem.subMaps[i]); err != nil { 210 | return err 211 | } 212 | } 213 | 214 | return nil 215 | } 216 | 217 | // Generates unique id based on the ancestors of the struct as well as currently considered colum values 218 | func getUniqueId(row []interface{}, m *Mapper) uniqueValId { 219 | // TODO: set capacity of the uid slice, using bytes.buffer 220 | uid := "" 221 | for _, i := range m.SortedColumnIndexes { 222 | uid = uid + row[i].(*value.Cell).Uid() 223 | } 224 | return uniqueValId(uid) 225 | } 226 | -------------------------------------------------------------------------------- /mapper.go: -------------------------------------------------------------------------------- 1 | package carta 2 | 3 | import ( 4 | "database/sql" 5 | "errors" 6 | "fmt" 7 | "reflect" 8 | 9 | "github.com/jackskj/carta/value" 10 | ) 11 | 12 | const ( 13 | CartaTagKey string = "db" 14 | ) 15 | 16 | // SQL Map cardinality can either be: 17 | // Association: has-one relationship, must be nested structs in the response 18 | // Collection: had-many relationship, repeated (slice, array) nested struct or pointer to it 19 | type Cardinality int 20 | 21 | const ( 22 | Unknown Cardinality = iota 23 | Association 24 | Collection 25 | ) 26 | 27 | type Field struct { 28 | Name string 29 | Typ reflect.Type 30 | Kind reflect.Kind 31 | 32 | //If the field is a pointer, fields below represent the underlying type, 33 | // these fields are here to prevent reflect.PtrTo, or reflect.elem calls when setting primatives and basic types 34 | IsPtr bool 35 | ElemTyp reflect.Type // if Typ is *int, elemTyp is int 36 | ElemKind reflect.Kind // if kind is ptr and typ is *int, elem kind is int 37 | } 38 | 39 | type Mapper struct { 40 | Crd Cardinality // 41 | 42 | IsListPtr bool // true if destination is *[], false if destination is [], used only if cardinality is a collection 43 | 44 | // Basic mapper is used for collections where underlying type is basic (any field that is able to be set, look at isBasicType for more deatils ) 45 | // for example 46 | // type User struct { 47 | // UserId int 48 | // UserAddr []sql.NullString // collection submap where mapper is basic 49 | // UserPhone []string // also basic mapper 50 | // UserStuff *[]*string // also basic mapper 51 | // UserBlog []*Blog // this is NOT a basic mapper 52 | // } 53 | // basic can only be true if cardinality is collection 54 | IsBasic bool 55 | 56 | Typ reflect.Type // Underlying type to be mapped 57 | Kind reflect.Kind // Underlying Kind to be mapped 58 | 59 | IsTypePtr bool // is the underlying type pointed to 60 | 61 | // present columns are columns that were found to map onto a particular fild of a struct. 62 | // those fiels must either be basic (primative, time or sql.NullXX) 63 | PresentColumns map[string]column 64 | // Sorted columns are present columns in consistant order, 65 | SortedColumnIndexes []int 66 | 67 | // when reusing the same struct multiple times, you are able to specify the colimn prefix using parent structs 68 | // example 69 | // type Employee struct { 70 | // Id int 71 | // } 72 | // type Manager struct { 73 | // Employee 74 | // Employees []Employee 75 | // } 76 | // the following querry would correctly map if we were mapping to *[]Manager 77 | // "select id, employees_id from employees join managers" 78 | // employees_ is the prefix of the parent (lower case of the parent with "_") 79 | Fields map[fieldIndex]Field 80 | AncestorNames []string // Field.Name of ancestors 81 | 82 | // Nested structs which correspond to any has-one has-many relationships 83 | // int is the ith element of this struct where the submap exists 84 | SubMaps map[fieldIndex]*Mapper 85 | } 86 | 87 | // Maps db rows onto the complex struct, 88 | // Response must be a struct, pointer to a struct for our response, a slice of structs or slice of pointers to a struct 89 | func Map(rows *sql.Rows, dst interface{}) error { 90 | var ( 91 | mapper *Mapper 92 | err error 93 | rsv *resolver 94 | ) 95 | columns, err := rows.Columns() 96 | if err != nil { 97 | return err 98 | } 99 | columnTypes, err := rows.ColumnTypes() 100 | if err != nil { 101 | return err 102 | } 103 | dstTyp := reflect.TypeOf(dst) 104 | mapper, ok := mapperCache.loadMap(columns, dstTyp) 105 | if !ok { 106 | if !(isSlicePtr(dstTyp) || isStructPtr(dstTyp)) { 107 | return fmt.Errorf("carta: cannot map rows onto %s, destination must be pointer to a slice(*[]) or pointer to a struct", dstTyp) 108 | } 109 | 110 | // generate new mapper 111 | if mapper, err = newMapper(dstTyp); err != nil { 112 | return err 113 | } 114 | 115 | // determine field names 116 | if err = determineFieldsNames(mapper); err != nil { 117 | return err 118 | } 119 | 120 | // Allocate columns 121 | columnsByName := map[string]column{} 122 | for i, columnName := range columns { 123 | columnsByName[columnName] = column{ 124 | name: columnName, 125 | typ: columnTypes[i], 126 | columnIndex: i, 127 | } 128 | } 129 | if err = allocateColumns(mapper, columnsByName); err != nil { 130 | return err 131 | } 132 | 133 | mapperCache.storeMap(columns, dstTyp, mapper) 134 | 135 | } 136 | 137 | if rsv, err = mapper.loadRows(rows, columnTypes); err != nil { 138 | return err 139 | } 140 | 141 | return setDst(mapper, reflect.ValueOf(dst), rsv) 142 | 143 | } 144 | 145 | func newMapper(t reflect.Type) (*Mapper, error) { 146 | var ( 147 | crd Cardinality 148 | elemTyp reflect.Type 149 | mapper *Mapper 150 | subMaps map[fieldIndex]*Mapper 151 | err error 152 | ) 153 | 154 | isListPtr := false 155 | isBasic := false 156 | isTypePtr := false 157 | 158 | if isSlicePtr(t) { 159 | crd = Collection 160 | elemTyp = t.Elem().Elem() // *[]interface{} to intetrface{} 161 | isListPtr = true 162 | } else if t.Kind() == reflect.Slice { 163 | crd = Association 164 | crd = Collection 165 | elemTyp = t.Elem() // []interface{} to intetrface{} 166 | } 167 | 168 | if crd == Collection { 169 | isBasic = isBasicType(elemTyp) 170 | if elemTyp.Kind() == reflect.Ptr { 171 | elemTyp = elemTyp.Elem() 172 | isTypePtr = true 173 | } 174 | } 175 | 176 | if isStructPtr(t) { 177 | crd = Association 178 | elemTyp = t.Elem() 179 | isTypePtr = true 180 | } else if t.Kind() == reflect.Struct { 181 | crd = Association 182 | elemTyp = t 183 | } 184 | 185 | if crd == Unknown { 186 | return nil, errors.New("carts: unknown mapping") 187 | } 188 | 189 | mapper = &Mapper{ 190 | Crd: crd, 191 | IsListPtr: isListPtr, 192 | IsBasic: isBasic, 193 | Typ: elemTyp, 194 | Kind: elemTyp.Kind(), 195 | IsTypePtr: isTypePtr, 196 | } 197 | if subMaps, err = findSubMaps(mapper.Typ); err != nil { 198 | return nil, err 199 | } 200 | mapper.SubMaps = subMaps 201 | return mapper, nil 202 | } 203 | 204 | func findSubMaps(t reflect.Type) (map[fieldIndex]*Mapper, error) { 205 | var ( 206 | subMap *Mapper 207 | err error 208 | ) 209 | subMaps := map[fieldIndex]*Mapper{} 210 | if t.Kind() != reflect.Struct { 211 | return nil, nil 212 | } 213 | for i := 0; i < t.NumField(); i++ { 214 | field := t.Field(i) 215 | if isExported(field) && isSubMap(field.Type) { 216 | if subMap, err = newMapper(field.Type); err != nil { 217 | return nil, err 218 | } 219 | subMaps[fieldIndex(i)] = subMap 220 | } 221 | } 222 | return subMaps, nil 223 | } 224 | 225 | func determineFieldsNames(m *Mapper) error { 226 | var ( 227 | name string 228 | ) 229 | fields := map[fieldIndex]Field{} 230 | 231 | if m.IsBasic { 232 | return nil 233 | } 234 | 235 | for i := 0; i < m.Typ.NumField(); i++ { 236 | field := m.Typ.Field(i) 237 | if isExported(field) { 238 | if tag := nameFromTag(field.Tag); tag != "" { 239 | name = tag 240 | } else { 241 | name = field.Name 242 | } 243 | f := Field{ 244 | Name: name, 245 | Typ: field.Type, 246 | Kind: field.Type.Kind(), 247 | IsPtr: (field.Type.Kind() == reflect.Ptr), 248 | } 249 | if f.IsPtr { 250 | f.ElemKind = field.Type.Elem().Kind() 251 | f.ElemTyp = field.Type.Elem() 252 | } 253 | fields[fieldIndex(i)] = f 254 | } 255 | } 256 | m.Fields = fields 257 | for _, subMap := range m.SubMaps { 258 | if err := determineFieldsNames(subMap); err != nil { 259 | return err 260 | } 261 | } 262 | return nil 263 | } 264 | 265 | func isExported(f reflect.StructField) bool { 266 | return (f.PkgPath == "") 267 | } 268 | 269 | func nameFromTag(t reflect.StructTag) string { 270 | return t.Get(CartaTagKey) 271 | 272 | } 273 | 274 | func isSubMap(t reflect.Type) bool { 275 | if t.Kind() == reflect.Ptr { 276 | t = t.Elem() 277 | } 278 | return (!isBasicType(t) && (t.Kind() == reflect.Struct || t.Kind() == reflect.Slice)) 279 | } 280 | 281 | // Basic types are any types that are intended to be set from sql row data 282 | // Primative fields, sql.NullXXX, time.Time, proto timestamp qualify as basic 283 | func isBasicType(t reflect.Type) bool { 284 | if t.Kind() == reflect.Ptr { 285 | t = t.Elem() 286 | } 287 | if _, ok := value.BasicKinds[t.Kind()]; ok { 288 | return true 289 | } 290 | if _, ok := value.BasicTypes[t]; ok { 291 | return true 292 | } 293 | return false 294 | } 295 | 296 | // test wether the type to be set is a pointer to a struct, courtesy of BQ api 297 | func isStructPtr(t reflect.Type) bool { 298 | return t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Struct 299 | } 300 | 301 | func isSlicePtr(t reflect.Type) bool { 302 | return t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Slice 303 | } 304 | -------------------------------------------------------------------------------- /mapper_test.go: -------------------------------------------------------------------------------- 1 | package carta_test 2 | 3 | import ( 4 | "context" 5 | "database/sql" 6 | "encoding/json" 7 | "errors" 8 | "flag" 9 | "io/ioutil" 10 | "log" 11 | "net" 12 | "os" 13 | "testing" 14 | "time" 15 | 16 | "github.com/jackskj/carta" 17 | td "github.com/jackskj/carta/testdata" 18 | "github.com/jackskj/carta/testdata/initdb" 19 | diff "github.com/yudai/gojsondiff" 20 | "github.com/yudai/gojsondiff/formatter" 21 | "google.golang.org/grpc" 22 | "google.golang.org/grpc/test/bufconn" 23 | // "github.com/golang/protobuf/proto" 24 | ) 25 | 26 | const ( 27 | bufSize = 1024 * 1024 28 | pg = "postgres" 29 | mysql = "mysql" 30 | ) 31 | 32 | var ( 33 | update = false 34 | initDB = true 35 | ) 36 | 37 | var ( 38 | conn *grpc.ClientConn 39 | ctx context.Context 40 | dbs map[string]*sql.DB 41 | grpcServer *grpc.Server 42 | lis *bufconn.Listener 43 | requests *td.Requests 44 | testResults map[string]interface{} 45 | ) 46 | 47 | // Generate test data before running tests 48 | // Start local server with bufconn 49 | func setup() { 50 | testResults = make(map[string]interface{}) 51 | ctx = context.Background() 52 | lis = bufconn.Listen(bufSize) 53 | grpcServer = grpc.NewServer() 54 | dbs = map[string]*sql.DB{ 55 | pg: td.GetPG(), 56 | mysql: td.GetMySql(), 57 | } 58 | initdb.RegisterInitServiceServer(grpcServer, &initdb.InitServiceMapServer{DBs: dbs}) 59 | go func() { 60 | if err := grpcServer.Serve(lis); err != nil { 61 | log.Fatalf("Server exited with error: %v", err) 62 | } 63 | }() 64 | if connection, err := grpc.DialContext(ctx, "bufnet", grpc.WithDialer(bufDialer), grpc.WithInsecure()); err != nil { 65 | log.Fatalf("bufnet dial fail: %v", err) 66 | } else { 67 | conn = connection 68 | } 69 | if initDB { 70 | createDatabase(dbs) 71 | } 72 | } 73 | 74 | func TestMain(m *testing.M) { 75 | updatePtr := flag.Bool("update", false, "update the golden file, results are always considered correct") 76 | initdbPtr := flag.Bool("initdb", false, "initialize and populate testing database") 77 | flag.Parse() 78 | update = *updatePtr 79 | initDB = *initdbPtr 80 | setup() 81 | code := m.Run() 82 | goldenFile := "testdata/mapper.golden" 83 | if update { 84 | // update golden file 85 | updateGoldenFile(goldenFile) 86 | } else { 87 | // compare existing results 88 | compareResults(goldenFile) 89 | } 90 | teardown() 91 | os.Exit(code) 92 | } 93 | 94 | func createDatabase(dbs map[string]*sql.DB) { 95 | requests = td.GenerateRequests() 96 | for dbName, _ := range dbs { 97 | meta := &initdb.Meta{Db: dbName} 98 | initService := initdb.NewInitServiceClient(conn) 99 | initService.InitDB(ctx, &initdb.InitRequest{Meta: &initdb.Meta{Db: dbName}}) 100 | for i := 0; i < len(requests.InsertAuthorRequests); i++ { 101 | requests.InsertAuthorRequests[i].Meta = meta 102 | if _, err := initService.InsertAuthor(ctx, requests.InsertAuthorRequests[i]); err != nil { 103 | log.Fatalf("InsertAuthor: %s", err) 104 | } 105 | } 106 | for i := 0; i < len(requests.InsertBlogRequests); i++ { 107 | requests.InsertBlogRequests[i].Meta = meta 108 | if _, err := initService.InsertBlog(ctx, requests.InsertBlogRequests[i]); err != nil { 109 | log.Fatalf("InsertBlog: %s", err) 110 | } 111 | } 112 | for i := 0; i < len(requests.InsertCommentRequests); i++ { 113 | requests.InsertCommentRequests[i].Meta = meta 114 | if _, err := initService.InsertComment(ctx, requests.InsertCommentRequests[i]); err != nil { 115 | log.Fatalf("InsertComment: %s", err) 116 | } 117 | } 118 | for i := 0; i < len(requests.InsertPostRequests); i++ { 119 | requests.InsertPostRequests[i].Meta = meta 120 | if _, err := initService.InsertPost(ctx, requests.InsertPostRequests[i]); err != nil { 121 | log.Fatalf("InsertPost: %s", err) 122 | } 123 | } 124 | for i := 0; i < len(requests.InsertPostTagRequests); i++ { 125 | requests.InsertPostTagRequests[i].Meta = meta 126 | if _, err := initService.InsertPostTag(ctx, requests.InsertPostTagRequests[i]); err != nil { 127 | log.Fatalf("InsertPostTag: %s", err) 128 | } 129 | } 130 | for i := 0; i < len(requests.InsertTagRequests); i++ { 131 | requests.InsertTagRequests[i].Meta = meta 132 | if _, err := initService.InsertTag(ctx, requests.InsertTagRequests[i]); err != nil { 133 | log.Fatalf("InsertTag: %s", err) 134 | } 135 | } 136 | } 137 | } 138 | 139 | func updateGoldenFile(goldenFile string) { 140 | jsonResult := generateResultBytes() 141 | if err := ioutil.WriteFile(goldenFile, jsonResult, 0644); err != nil { 142 | log.Fatalln(err) 143 | } 144 | } 145 | 146 | func compareResults(goldenFile string) { 147 | goldenFileJson, err := ioutil.ReadFile(goldenFile) 148 | if err != nil { 149 | log.Fatalln(err) 150 | } 151 | 152 | jsonResult := generateResultBytes() 153 | 154 | resultDiff := diff.New() 155 | d, err := resultDiff.Compare(goldenFileJson, jsonResult) 156 | if err != nil { 157 | log.Fatalln(err) 158 | } 159 | formatter := formatter.NewDeltaFormatter() 160 | diffString, err := formatter.Format(d) 161 | if diffString != "{}\n" { 162 | log.Println("Results Do Not Match Golden File, " + 163 | "if this is expecred result with go test with --update") 164 | log.Fatalln(diffString) 165 | } 166 | } 167 | 168 | func generateResultBytes() []byte { 169 | var jsonResult []byte 170 | if r, err := json.MarshalIndent(testResults, "", " "); err != nil { 171 | log.Fatalln(err) 172 | } else { 173 | jsonResult = r 174 | } 175 | return jsonResult 176 | } 177 | 178 | func teardown() { 179 | defer conn.Close() 180 | } 181 | 182 | func bufDialer(string, time.Duration) (net.Conn, error) { 183 | return lis.Dial() 184 | } 185 | 186 | func query(rawSql string) map[string]*sql.Rows { 187 | resp := map[string]*sql.Rows{} 188 | for dbName, db := range dbs { 189 | stmt, err := db.Prepare(rawSql) 190 | if err != nil { 191 | log.Fatal(err) 192 | } 193 | defer stmt.Close() 194 | if rows, err := stmt.Query(); err != nil { 195 | log.Fatal(err) 196 | } else { 197 | resp[dbName] = rows 198 | } 199 | } 200 | return resp 201 | } 202 | 203 | func queryPG(rawSql string) (rows *sql.Rows) { 204 | var err error 205 | if rows, err = dbs[pg].Query(rawSql); err != nil { 206 | log.Fatal(err) 207 | } 208 | return 209 | } 210 | 211 | func queryMysql(rawSql string) (rows *sql.Rows) { 212 | var err error 213 | stmt, err := dbs[mysql].Prepare(rawSql) 214 | if err != nil { 215 | log.Fatal(err) 216 | } 217 | defer stmt.Close() 218 | if rows, err = stmt.Query(); err != nil { 219 | log.Fatal(err) 220 | } 221 | return 222 | } 223 | 224 | func TestBlog(m *testing.T) { 225 | ans := []byte{} 226 | for _, rows := range query(td.BlogQuery) { 227 | resp := []td.Blog{} 228 | if err := carta.Map(rows, &resp); err != nil { 229 | log.Fatal(err.Error()) 230 | } 231 | e, _ := json.Marshal(resp) 232 | if len(ans) == 0 { 233 | ans = e 234 | } else if string(ans) != string(e) { 235 | log.Fatal(errors.New("Test Blog Produced Inconsistent Results")) 236 | } 237 | testResults["TestBlog"] = resp 238 | } 239 | } 240 | 241 | func TestNull(m *testing.T) { 242 | respPG := []td.NullTest{} 243 | if err := carta.Map(queryPG(td.NullQueryPG), &respPG); err != nil { 244 | log.Fatal(err.Error()) 245 | } 246 | respMySQL := []td.NullTest{} 247 | if err := carta.Map(queryMysql(td.NullQueryMySql), &respMySQL); err != nil { 248 | log.Fatal(err.Error()) 249 | } 250 | ansPG, _ := json.Marshal(respPG) 251 | ansMySQL, _ := json.Marshal(respMySQL) 252 | if string(ansPG) != string(ansMySQL) { 253 | log.Fatal(errors.New("Test Null Produced Inconsistent Results")) 254 | } 255 | testResults["TestNull"] = respPG 256 | } 257 | 258 | func TestNotNull(m *testing.T) { 259 | respPG := []td.NullTest{} 260 | if err := carta.Map(queryPG(td.NotNullQueryPG), &respPG); err != nil { 261 | log.Fatal(err.Error()) 262 | } 263 | respMySQL := []td.NullTest{} 264 | if err := carta.Map(queryMysql(td.NotNullQueryMySQL), &respMySQL); err != nil { 265 | log.Fatal(err.Error()) 266 | } 267 | ansPG, _ := json.Marshal(respPG) 268 | ansMySQL, _ := json.Marshal(respMySQL) 269 | if string(ansPG) != string(ansMySQL) { 270 | log.Println(string(ansMySQL)) 271 | log.Fatal(errors.New("Test Not Null Produced Inconsistent Results")) 272 | } 273 | testResults["TestNotNull"] = respPG 274 | } 275 | 276 | func TestPGTypes(m *testing.T) { 277 | resp := []td.PGDTypes{} 278 | if err := carta.Map(queryPG(td.PGDTypesQuery), &resp); err != nil { 279 | log.Fatal(err.Error()) 280 | } 281 | testResults["TestPGTypes"] = resp 282 | } 283 | 284 | func TestRelation(m *testing.T) { 285 | ans := []byte{} 286 | for _, rows := range query(td.RelationTestQuery) { 287 | resp := []td.RelationTest{} 288 | if err := carta.Map(rows, &resp); err != nil { 289 | log.Fatal(err.Error()) 290 | } 291 | e, _ := json.Marshal(resp) 292 | if len(ans) == 0 { 293 | ans = e 294 | } else if string(ans) != string(e) { 295 | log.Fatal(errors.New("Test Blog Produced Inconsistent Results")) 296 | } 297 | testResults["TestRelation"] = resp 298 | } 299 | } 300 | -------------------------------------------------------------------------------- /resolver.go: -------------------------------------------------------------------------------- 1 | package carta 2 | 3 | import ( 4 | "reflect" 5 | ) 6 | 7 | // Resolver determines whether an object has already appeared in past rows. 8 | // ie, if a set of column values was previously returned by SQL, 9 | // this is nececaty to determine whether a new instantiation of a type is necesarry 10 | // Carta uses all present columns in a particular message to generate a unique id, 11 | // if successive rows have the same id, it identifies the same element 12 | // always include a uniquely identifiable column in your query 13 | // resolver cannot be stored in pointer reciver, this would result in concurrency bugs, 14 | // 15 | // for example, if user requests mapping to *[]*User where 16 | // type User struct { 17 | // userId int 18 | // Addresses []*Address 19 | // } 20 | // if sql query returns multiple rows with the same userId, resolver will return 21 | // a pointer to the *User with that id so that furhter mapping can continue, in this case, mapping of address 22 | // 23 | // TODO: consider passing resover in context value 24 | 25 | type ( 26 | uniqueValId string 27 | fieldIndex int 28 | ) 29 | 30 | type element struct { 31 | v reflect.Value // value of a struct that is mapped, this is never a pointer, its either a primative or struct 32 | subMaps map[fieldIndex]*resolver 33 | } 34 | 35 | type resolver struct { 36 | elements map[uniqueValId]*element 37 | elementOrder []uniqueValId // all elements stored in an order, important for the " order by " clause, earlier rows that map onto elements will be earlies in this slice 38 | } 39 | 40 | func newResolver() *resolver { 41 | return &resolver{ 42 | elementOrder: []uniqueValId{}, 43 | elements: map[uniqueValId]*element{}, 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /setter.go: -------------------------------------------------------------------------------- 1 | package carta 2 | 3 | import ( 4 | "errors" 5 | "reflect" 6 | ) 7 | 8 | func setDst(m *Mapper, dst reflect.Value, rsv *resolver) error { 9 | // dst is always a pointer 10 | dstIndirect := reflect.Indirect(dst) 11 | 12 | // post order traversal, first set all submap structs, then the struct itself 13 | for _, uid := range rsv.elementOrder { 14 | elem := rsv.elements[uid] 15 | 16 | //set childeren first 17 | for fieldIndex, subMapRsv := range elem.subMaps { 18 | var ( 19 | subMap *Mapper 20 | childTyp reflect.Type 21 | childDst reflect.Value 22 | newChildElem reflect.Value 23 | ok bool 24 | ) 25 | 26 | if subMap, ok = m.SubMaps[fieldIndex]; !ok { 27 | // this should never happen 28 | return errors.New("carta: sub map not found") 29 | } 30 | if f, ok := m.SubMaps[fieldIndex]; ok { 31 | childTyp = f.Typ 32 | } else { 33 | // this should never happen 34 | return errors.New("carta: field not found") 35 | } 36 | 37 | if subMap.Crd == Collection { 38 | capacity := len(subMapRsv.elements) 39 | if subMap.IsTypePtr { 40 | newChildElem = reflect.New(reflect.SliceOf(reflect.PtrTo(childTyp))).Elem() 41 | newChildElem.Set(reflect.MakeSlice(reflect.SliceOf(reflect.PtrTo(childTyp)), 0, capacity)) 42 | } else { 43 | newChildElem = reflect.New(reflect.SliceOf(childTyp)).Elem() 44 | newChildElem.Set(reflect.MakeSlice(reflect.SliceOf(childTyp), 0, capacity)) 45 | } 46 | if subMap.IsListPtr { 47 | elem.v.Field(int(fieldIndex)).Set(newChildElem.Addr()) 48 | childDst = elem.v.Field(int(fieldIndex)) 49 | } else { 50 | elem.v.Field(int(fieldIndex)).Set(newChildElem) 51 | childDst = elem.v.Field(int(fieldIndex)).Addr() 52 | } 53 | } else if subMap.Crd == Association { 54 | newChildElem = reflect.New(childTyp).Elem() 55 | if subMap.IsTypePtr { 56 | elem.v.Field(int(fieldIndex)).Set(newChildElem.Addr()) 57 | childDst = elem.v.Field(int(fieldIndex)) 58 | } else { 59 | elem.v.Field(int(fieldIndex)).Set(newChildElem) 60 | childDst = elem.v.Field(int(fieldIndex)).Addr() 61 | } 62 | } 63 | 64 | // setting the child 65 | setDst(subMap, childDst, subMapRsv) 66 | } 67 | } 68 | 69 | for _, uid := range rsv.elementOrder { 70 | elem := rsv.elements[uid] 71 | if m.Crd == Collection { 72 | if m.IsTypePtr { 73 | dstIndirect.Set(reflect.Append(dstIndirect, elem.v.Addr())) 74 | } else { 75 | dstIndirect.Set(reflect.Append(dstIndirect, elem.v)) 76 | } 77 | } else if m.Crd == Association { 78 | dstIndirect.Set(elem.v) 79 | } 80 | } 81 | return nil 82 | } 83 | -------------------------------------------------------------------------------- /testdata/blog_sql.go: -------------------------------------------------------------------------------- 1 | package testdata 2 | 3 | import ( 4 | "database/sql" 5 | 6 | "github.com/golang/protobuf/ptypes/timestamp" 7 | ) 8 | 9 | type Blog struct { 10 | BlogId int `db:"blog_id" json:"blog_id,omitempty"` 11 | BlogTitle string `db:"blog_title" json:"blog_title,omitempty"` 12 | Author *Author `db:"author" json:"author,omitempty"` 13 | Posts *[]Post `db:"posts" json:"posts,omitempty"` 14 | } 15 | 16 | type Author struct { 17 | AuthorId int `db:"author_id" json:"author_id,omitempty"` 18 | AuthorUsername string `db:"author_username" json:"author_username,omitempty"` 19 | AuthorPassword string `db:"author_password" json:"author_password,omitempty"` 20 | AuthorEmail string `db:"author_email" json:"author_email,omitempty"` 21 | AuthorBio string `db:"author_bio" json:"author_bio,omitempty"` 22 | AuthorFavouriteSection string `db:"author_favourite_section" json:"author_favourite_section,omitempty"` 23 | } 24 | 25 | type Post struct { 26 | PostId int `db:"post_id" json:"post_id,omitempty"` 27 | PostBlogId int `db:"post_blog_id" json:"post_blog_id,omitempty"` 28 | PostAuthorId int `db:"post_author_id" json:"post_author_id,omitempty"` 29 | PostCreatedOn *timestamp.Timestamp `db:"post_created_on" json:"post_created_on,omitempty"` 30 | PostSection string `db:"post_section" json:"post_section,omitempty"` 31 | PostSubject string `db:"post_subject" json:"post_subject,omitempty"` 32 | Draft string `db:"draft" json:"draft,omitempty"` 33 | PostBody string `db:"post_body" json:"post_body,omitempty"` 34 | Comments []*Comment `db:"comments" json:"comments,omitempty"` 35 | Tags []*Tag `db:"tags" json:"tags,omitempty"` 36 | } 37 | 38 | type Comment struct { 39 | CommentId *int `db:"comment_id" json:"comment_id,omitempty"` 40 | CommentPostId *int `db:"comment_post_id" json:"comment_post_id,omitempty"` 41 | CommentText sql.NullString `db:"comment_text" json:"comment_text,omitempty"` 42 | } 43 | 44 | type Tag struct { 45 | TagId int `db:"tag_id" json:"tag_id,omitempty"` 46 | TagName string `db:"tag_name" json:"tag_name,omitempty"` 47 | } 48 | 49 | var BlogQuery = ` 50 | select 51 | B.id as blog_id, 52 | B.title as blog_title, 53 | A.id as author_id, 54 | A.username as author_username, 55 | A.password as author_password, 56 | A.email as author_email, 57 | A.bio as author_bio, 58 | A.favourite_section as author_favourite_section, 59 | P.id as post_id, 60 | P.blog_id as post_blog_id, 61 | P.author_id as post_author_id, 62 | P.created_on as post_created_on, 63 | P.section as post_section, 64 | P.subject as post_subject, 65 | P.draft as draft, 66 | P.body as post_body, 67 | C.id as comment_id, 68 | C.post_id as comment_post_id, 69 | C.comment as comment_text, 70 | T.id as tag_id, 71 | T.name as tag_name 72 | from blog B 73 | left outer join author A on B.author_id = A.id 74 | left outer join post P on B.id = P.blog_id 75 | left outer join comment C on P.id = C.post_id 76 | left outer join post_tag PT on PT.post_id = P.id 77 | left outer join tag T on PT.tag_id = T.id 78 | where B.id in (1,2,3) 79 | order by 80 | B.id, A.id, P.id, P.Id, C.id, T.id 81 | ` 82 | -------------------------------------------------------------------------------- /testdata/initdb/initdb.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. DO NOT EDIT. 2 | // versions: 3 | // protoc-gen-go v1.22.0 4 | // protoc v3.11.4 5 | // source: testdata/initdb/initdb.proto 6 | 7 | package initdb 8 | 9 | import ( 10 | context "context" 11 | proto "github.com/golang/protobuf/proto" 12 | timestamp "github.com/golang/protobuf/ptypes/timestamp" 13 | grpc "google.golang.org/grpc" 14 | codes "google.golang.org/grpc/codes" 15 | status "google.golang.org/grpc/status" 16 | protoreflect "google.golang.org/protobuf/reflect/protoreflect" 17 | protoimpl "google.golang.org/protobuf/runtime/protoimpl" 18 | reflect "reflect" 19 | sync "sync" 20 | ) 21 | 22 | const ( 23 | // Verify that this generated code is sufficiently up-to-date. 24 | _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) 25 | // Verify that runtime/protoimpl is sufficiently up-to-date. 26 | _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) 27 | ) 28 | 29 | // This is a compile-time assertion that a sufficiently up-to-date version 30 | // of the legacy proto package is being used. 31 | const _ = proto.ProtoPackageIsVersion4 32 | 33 | type Meta struct { 34 | state protoimpl.MessageState 35 | sizeCache protoimpl.SizeCache 36 | unknownFields protoimpl.UnknownFields 37 | 38 | Db string `protobuf:"bytes,1,opt,name=db,proto3" json:"db,omitempty"` 39 | } 40 | 41 | func (x *Meta) Reset() { 42 | *x = Meta{} 43 | if protoimpl.UnsafeEnabled { 44 | mi := &file_testdata_initdb_initdb_proto_msgTypes[0] 45 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 46 | ms.StoreMessageInfo(mi) 47 | } 48 | } 49 | 50 | func (x *Meta) String() string { 51 | return protoimpl.X.MessageStringOf(x) 52 | } 53 | 54 | func (*Meta) ProtoMessage() {} 55 | 56 | func (x *Meta) ProtoReflect() protoreflect.Message { 57 | mi := &file_testdata_initdb_initdb_proto_msgTypes[0] 58 | if protoimpl.UnsafeEnabled && x != nil { 59 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 60 | if ms.LoadMessageInfo() == nil { 61 | ms.StoreMessageInfo(mi) 62 | } 63 | return ms 64 | } 65 | return mi.MessageOf(x) 66 | } 67 | 68 | // Deprecated: Use Meta.ProtoReflect.Descriptor instead. 69 | func (*Meta) Descriptor() ([]byte, []int) { 70 | return file_testdata_initdb_initdb_proto_rawDescGZIP(), []int{0} 71 | } 72 | 73 | func (x *Meta) GetDb() string { 74 | if x != nil { 75 | return x.Db 76 | } 77 | return "" 78 | } 79 | 80 | type InsertAuthorRequest struct { 81 | state protoimpl.MessageState 82 | sizeCache protoimpl.SizeCache 83 | unknownFields protoimpl.UnknownFields 84 | 85 | Meta *Meta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` 86 | Id uint32 `protobuf:"varint,2,opt,name=id,proto3" json:"id,omitempty"` 87 | Username string `protobuf:"bytes,3,opt,name=username,proto3" json:"username,omitempty"` 88 | Password string `protobuf:"bytes,4,opt,name=password,proto3" json:"password,omitempty"` 89 | Email string `protobuf:"bytes,5,opt,name=email,proto3" json:"email,omitempty"` 90 | Bio string `protobuf:"bytes,6,opt,name=bio,proto3" json:"bio,omitempty"` 91 | FavouriteSection string `protobuf:"bytes,7,opt,name=favourite_section,json=favouriteSection,proto3" json:"favourite_section,omitempty"` 92 | } 93 | 94 | func (x *InsertAuthorRequest) Reset() { 95 | *x = InsertAuthorRequest{} 96 | if protoimpl.UnsafeEnabled { 97 | mi := &file_testdata_initdb_initdb_proto_msgTypes[1] 98 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 99 | ms.StoreMessageInfo(mi) 100 | } 101 | } 102 | 103 | func (x *InsertAuthorRequest) String() string { 104 | return protoimpl.X.MessageStringOf(x) 105 | } 106 | 107 | func (*InsertAuthorRequest) ProtoMessage() {} 108 | 109 | func (x *InsertAuthorRequest) ProtoReflect() protoreflect.Message { 110 | mi := &file_testdata_initdb_initdb_proto_msgTypes[1] 111 | if protoimpl.UnsafeEnabled && x != nil { 112 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 113 | if ms.LoadMessageInfo() == nil { 114 | ms.StoreMessageInfo(mi) 115 | } 116 | return ms 117 | } 118 | return mi.MessageOf(x) 119 | } 120 | 121 | // Deprecated: Use InsertAuthorRequest.ProtoReflect.Descriptor instead. 122 | func (*InsertAuthorRequest) Descriptor() ([]byte, []int) { 123 | return file_testdata_initdb_initdb_proto_rawDescGZIP(), []int{1} 124 | } 125 | 126 | func (x *InsertAuthorRequest) GetMeta() *Meta { 127 | if x != nil { 128 | return x.Meta 129 | } 130 | return nil 131 | } 132 | 133 | func (x *InsertAuthorRequest) GetId() uint32 { 134 | if x != nil { 135 | return x.Id 136 | } 137 | return 0 138 | } 139 | 140 | func (x *InsertAuthorRequest) GetUsername() string { 141 | if x != nil { 142 | return x.Username 143 | } 144 | return "" 145 | } 146 | 147 | func (x *InsertAuthorRequest) GetPassword() string { 148 | if x != nil { 149 | return x.Password 150 | } 151 | return "" 152 | } 153 | 154 | func (x *InsertAuthorRequest) GetEmail() string { 155 | if x != nil { 156 | return x.Email 157 | } 158 | return "" 159 | } 160 | 161 | func (x *InsertAuthorRequest) GetBio() string { 162 | if x != nil { 163 | return x.Bio 164 | } 165 | return "" 166 | } 167 | 168 | func (x *InsertAuthorRequest) GetFavouriteSection() string { 169 | if x != nil { 170 | return x.FavouriteSection 171 | } 172 | return "" 173 | } 174 | 175 | type InsertBlogRequest struct { 176 | state protoimpl.MessageState 177 | sizeCache protoimpl.SizeCache 178 | unknownFields protoimpl.UnknownFields 179 | 180 | Meta *Meta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` 181 | Id uint32 `protobuf:"varint,2,opt,name=id,proto3" json:"id,omitempty"` 182 | Title string `protobuf:"bytes,3,opt,name=title,proto3" json:"title,omitempty"` 183 | AuthorId uint32 `protobuf:"varint,4,opt,name=author_id,json=authorId,proto3" json:"author_id,omitempty"` 184 | } 185 | 186 | func (x *InsertBlogRequest) Reset() { 187 | *x = InsertBlogRequest{} 188 | if protoimpl.UnsafeEnabled { 189 | mi := &file_testdata_initdb_initdb_proto_msgTypes[2] 190 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 191 | ms.StoreMessageInfo(mi) 192 | } 193 | } 194 | 195 | func (x *InsertBlogRequest) String() string { 196 | return protoimpl.X.MessageStringOf(x) 197 | } 198 | 199 | func (*InsertBlogRequest) ProtoMessage() {} 200 | 201 | func (x *InsertBlogRequest) ProtoReflect() protoreflect.Message { 202 | mi := &file_testdata_initdb_initdb_proto_msgTypes[2] 203 | if protoimpl.UnsafeEnabled && x != nil { 204 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 205 | if ms.LoadMessageInfo() == nil { 206 | ms.StoreMessageInfo(mi) 207 | } 208 | return ms 209 | } 210 | return mi.MessageOf(x) 211 | } 212 | 213 | // Deprecated: Use InsertBlogRequest.ProtoReflect.Descriptor instead. 214 | func (*InsertBlogRequest) Descriptor() ([]byte, []int) { 215 | return file_testdata_initdb_initdb_proto_rawDescGZIP(), []int{2} 216 | } 217 | 218 | func (x *InsertBlogRequest) GetMeta() *Meta { 219 | if x != nil { 220 | return x.Meta 221 | } 222 | return nil 223 | } 224 | 225 | func (x *InsertBlogRequest) GetId() uint32 { 226 | if x != nil { 227 | return x.Id 228 | } 229 | return 0 230 | } 231 | 232 | func (x *InsertBlogRequest) GetTitle() string { 233 | if x != nil { 234 | return x.Title 235 | } 236 | return "" 237 | } 238 | 239 | func (x *InsertBlogRequest) GetAuthorId() uint32 { 240 | if x != nil { 241 | return x.AuthorId 242 | } 243 | return 0 244 | } 245 | 246 | type InsertCommentRequest struct { 247 | state protoimpl.MessageState 248 | sizeCache protoimpl.SizeCache 249 | unknownFields protoimpl.UnknownFields 250 | 251 | Meta *Meta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` 252 | Id uint32 `protobuf:"varint,2,opt,name=id,proto3" json:"id,omitempty"` 253 | PostId uint32 `protobuf:"varint,3,opt,name=post_id,json=postId,proto3" json:"post_id,omitempty"` 254 | Name string `protobuf:"bytes,4,opt,name=name,proto3" json:"name,omitempty"` 255 | Comment string `protobuf:"bytes,5,opt,name=comment,proto3" json:"comment,omitempty"` 256 | } 257 | 258 | func (x *InsertCommentRequest) Reset() { 259 | *x = InsertCommentRequest{} 260 | if protoimpl.UnsafeEnabled { 261 | mi := &file_testdata_initdb_initdb_proto_msgTypes[3] 262 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 263 | ms.StoreMessageInfo(mi) 264 | } 265 | } 266 | 267 | func (x *InsertCommentRequest) String() string { 268 | return protoimpl.X.MessageStringOf(x) 269 | } 270 | 271 | func (*InsertCommentRequest) ProtoMessage() {} 272 | 273 | func (x *InsertCommentRequest) ProtoReflect() protoreflect.Message { 274 | mi := &file_testdata_initdb_initdb_proto_msgTypes[3] 275 | if protoimpl.UnsafeEnabled && x != nil { 276 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 277 | if ms.LoadMessageInfo() == nil { 278 | ms.StoreMessageInfo(mi) 279 | } 280 | return ms 281 | } 282 | return mi.MessageOf(x) 283 | } 284 | 285 | // Deprecated: Use InsertCommentRequest.ProtoReflect.Descriptor instead. 286 | func (*InsertCommentRequest) Descriptor() ([]byte, []int) { 287 | return file_testdata_initdb_initdb_proto_rawDescGZIP(), []int{3} 288 | } 289 | 290 | func (x *InsertCommentRequest) GetMeta() *Meta { 291 | if x != nil { 292 | return x.Meta 293 | } 294 | return nil 295 | } 296 | 297 | func (x *InsertCommentRequest) GetId() uint32 { 298 | if x != nil { 299 | return x.Id 300 | } 301 | return 0 302 | } 303 | 304 | func (x *InsertCommentRequest) GetPostId() uint32 { 305 | if x != nil { 306 | return x.PostId 307 | } 308 | return 0 309 | } 310 | 311 | func (x *InsertCommentRequest) GetName() string { 312 | if x != nil { 313 | return x.Name 314 | } 315 | return "" 316 | } 317 | 318 | func (x *InsertCommentRequest) GetComment() string { 319 | if x != nil { 320 | return x.Comment 321 | } 322 | return "" 323 | } 324 | 325 | type InsertPostRequest struct { 326 | state protoimpl.MessageState 327 | sizeCache protoimpl.SizeCache 328 | unknownFields protoimpl.UnknownFields 329 | 330 | Meta *Meta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` 331 | Id uint32 `protobuf:"varint,2,opt,name=id,proto3" json:"id,omitempty"` 332 | AuthorId uint32 `protobuf:"varint,3,opt,name=author_id,json=authorId,proto3" json:"author_id,omitempty"` 333 | BlogId uint32 `protobuf:"varint,4,opt,name=blog_id,json=blogId,proto3" json:"blog_id,omitempty"` 334 | CreatedOn *timestamp.Timestamp `protobuf:"bytes,5,opt,name=created_on,json=createdOn,proto3" json:"created_on,omitempty"` 335 | Section string `protobuf:"bytes,6,opt,name=section,proto3" json:"section,omitempty"` 336 | Subject string `protobuf:"bytes,7,opt,name=subject,proto3" json:"subject,omitempty"` 337 | Draft string `protobuf:"bytes,8,opt,name=draft,proto3" json:"draft,omitempty"` 338 | Body string `protobuf:"bytes,9,opt,name=body,proto3" json:"body,omitempty"` 339 | } 340 | 341 | func (x *InsertPostRequest) Reset() { 342 | *x = InsertPostRequest{} 343 | if protoimpl.UnsafeEnabled { 344 | mi := &file_testdata_initdb_initdb_proto_msgTypes[4] 345 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 346 | ms.StoreMessageInfo(mi) 347 | } 348 | } 349 | 350 | func (x *InsertPostRequest) String() string { 351 | return protoimpl.X.MessageStringOf(x) 352 | } 353 | 354 | func (*InsertPostRequest) ProtoMessage() {} 355 | 356 | func (x *InsertPostRequest) ProtoReflect() protoreflect.Message { 357 | mi := &file_testdata_initdb_initdb_proto_msgTypes[4] 358 | if protoimpl.UnsafeEnabled && x != nil { 359 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 360 | if ms.LoadMessageInfo() == nil { 361 | ms.StoreMessageInfo(mi) 362 | } 363 | return ms 364 | } 365 | return mi.MessageOf(x) 366 | } 367 | 368 | // Deprecated: Use InsertPostRequest.ProtoReflect.Descriptor instead. 369 | func (*InsertPostRequest) Descriptor() ([]byte, []int) { 370 | return file_testdata_initdb_initdb_proto_rawDescGZIP(), []int{4} 371 | } 372 | 373 | func (x *InsertPostRequest) GetMeta() *Meta { 374 | if x != nil { 375 | return x.Meta 376 | } 377 | return nil 378 | } 379 | 380 | func (x *InsertPostRequest) GetId() uint32 { 381 | if x != nil { 382 | return x.Id 383 | } 384 | return 0 385 | } 386 | 387 | func (x *InsertPostRequest) GetAuthorId() uint32 { 388 | if x != nil { 389 | return x.AuthorId 390 | } 391 | return 0 392 | } 393 | 394 | func (x *InsertPostRequest) GetBlogId() uint32 { 395 | if x != nil { 396 | return x.BlogId 397 | } 398 | return 0 399 | } 400 | 401 | func (x *InsertPostRequest) GetCreatedOn() *timestamp.Timestamp { 402 | if x != nil { 403 | return x.CreatedOn 404 | } 405 | return nil 406 | } 407 | 408 | func (x *InsertPostRequest) GetSection() string { 409 | if x != nil { 410 | return x.Section 411 | } 412 | return "" 413 | } 414 | 415 | func (x *InsertPostRequest) GetSubject() string { 416 | if x != nil { 417 | return x.Subject 418 | } 419 | return "" 420 | } 421 | 422 | func (x *InsertPostRequest) GetDraft() string { 423 | if x != nil { 424 | return x.Draft 425 | } 426 | return "" 427 | } 428 | 429 | func (x *InsertPostRequest) GetBody() string { 430 | if x != nil { 431 | return x.Body 432 | } 433 | return "" 434 | } 435 | 436 | type InsertPostTagRequest struct { 437 | state protoimpl.MessageState 438 | sizeCache protoimpl.SizeCache 439 | unknownFields protoimpl.UnknownFields 440 | 441 | Meta *Meta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` 442 | PostId uint32 `protobuf:"varint,2,opt,name=post_id,json=postId,proto3" json:"post_id,omitempty"` 443 | TagId uint32 `protobuf:"varint,3,opt,name=tag_id,json=tagId,proto3" json:"tag_id,omitempty"` 444 | } 445 | 446 | func (x *InsertPostTagRequest) Reset() { 447 | *x = InsertPostTagRequest{} 448 | if protoimpl.UnsafeEnabled { 449 | mi := &file_testdata_initdb_initdb_proto_msgTypes[5] 450 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 451 | ms.StoreMessageInfo(mi) 452 | } 453 | } 454 | 455 | func (x *InsertPostTagRequest) String() string { 456 | return protoimpl.X.MessageStringOf(x) 457 | } 458 | 459 | func (*InsertPostTagRequest) ProtoMessage() {} 460 | 461 | func (x *InsertPostTagRequest) ProtoReflect() protoreflect.Message { 462 | mi := &file_testdata_initdb_initdb_proto_msgTypes[5] 463 | if protoimpl.UnsafeEnabled && x != nil { 464 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 465 | if ms.LoadMessageInfo() == nil { 466 | ms.StoreMessageInfo(mi) 467 | } 468 | return ms 469 | } 470 | return mi.MessageOf(x) 471 | } 472 | 473 | // Deprecated: Use InsertPostTagRequest.ProtoReflect.Descriptor instead. 474 | func (*InsertPostTagRequest) Descriptor() ([]byte, []int) { 475 | return file_testdata_initdb_initdb_proto_rawDescGZIP(), []int{5} 476 | } 477 | 478 | func (x *InsertPostTagRequest) GetMeta() *Meta { 479 | if x != nil { 480 | return x.Meta 481 | } 482 | return nil 483 | } 484 | 485 | func (x *InsertPostTagRequest) GetPostId() uint32 { 486 | if x != nil { 487 | return x.PostId 488 | } 489 | return 0 490 | } 491 | 492 | func (x *InsertPostTagRequest) GetTagId() uint32 { 493 | if x != nil { 494 | return x.TagId 495 | } 496 | return 0 497 | } 498 | 499 | type InsertTagRequest struct { 500 | state protoimpl.MessageState 501 | sizeCache protoimpl.SizeCache 502 | unknownFields protoimpl.UnknownFields 503 | 504 | Meta *Meta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` 505 | Id uint32 `protobuf:"varint,2,opt,name=id,proto3" json:"id,omitempty"` 506 | Name string `protobuf:"bytes,3,opt,name=name,proto3" json:"name,omitempty"` 507 | } 508 | 509 | func (x *InsertTagRequest) Reset() { 510 | *x = InsertTagRequest{} 511 | if protoimpl.UnsafeEnabled { 512 | mi := &file_testdata_initdb_initdb_proto_msgTypes[6] 513 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 514 | ms.StoreMessageInfo(mi) 515 | } 516 | } 517 | 518 | func (x *InsertTagRequest) String() string { 519 | return protoimpl.X.MessageStringOf(x) 520 | } 521 | 522 | func (*InsertTagRequest) ProtoMessage() {} 523 | 524 | func (x *InsertTagRequest) ProtoReflect() protoreflect.Message { 525 | mi := &file_testdata_initdb_initdb_proto_msgTypes[6] 526 | if protoimpl.UnsafeEnabled && x != nil { 527 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 528 | if ms.LoadMessageInfo() == nil { 529 | ms.StoreMessageInfo(mi) 530 | } 531 | return ms 532 | } 533 | return mi.MessageOf(x) 534 | } 535 | 536 | // Deprecated: Use InsertTagRequest.ProtoReflect.Descriptor instead. 537 | func (*InsertTagRequest) Descriptor() ([]byte, []int) { 538 | return file_testdata_initdb_initdb_proto_rawDescGZIP(), []int{6} 539 | } 540 | 541 | func (x *InsertTagRequest) GetMeta() *Meta { 542 | if x != nil { 543 | return x.Meta 544 | } 545 | return nil 546 | } 547 | 548 | func (x *InsertTagRequest) GetId() uint32 { 549 | if x != nil { 550 | return x.Id 551 | } 552 | return 0 553 | } 554 | 555 | func (x *InsertTagRequest) GetName() string { 556 | if x != nil { 557 | return x.Name 558 | } 559 | return "" 560 | } 561 | 562 | type EmptyResponse struct { 563 | state protoimpl.MessageState 564 | sizeCache protoimpl.SizeCache 565 | unknownFields protoimpl.UnknownFields 566 | } 567 | 568 | func (x *EmptyResponse) Reset() { 569 | *x = EmptyResponse{} 570 | if protoimpl.UnsafeEnabled { 571 | mi := &file_testdata_initdb_initdb_proto_msgTypes[7] 572 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 573 | ms.StoreMessageInfo(mi) 574 | } 575 | } 576 | 577 | func (x *EmptyResponse) String() string { 578 | return protoimpl.X.MessageStringOf(x) 579 | } 580 | 581 | func (*EmptyResponse) ProtoMessage() {} 582 | 583 | func (x *EmptyResponse) ProtoReflect() protoreflect.Message { 584 | mi := &file_testdata_initdb_initdb_proto_msgTypes[7] 585 | if protoimpl.UnsafeEnabled && x != nil { 586 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 587 | if ms.LoadMessageInfo() == nil { 588 | ms.StoreMessageInfo(mi) 589 | } 590 | return ms 591 | } 592 | return mi.MessageOf(x) 593 | } 594 | 595 | // Deprecated: Use EmptyResponse.ProtoReflect.Descriptor instead. 596 | func (*EmptyResponse) Descriptor() ([]byte, []int) { 597 | return file_testdata_initdb_initdb_proto_rawDescGZIP(), []int{7} 598 | } 599 | 600 | type InitRequest struct { 601 | state protoimpl.MessageState 602 | sizeCache protoimpl.SizeCache 603 | unknownFields protoimpl.UnknownFields 604 | 605 | Meta *Meta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` 606 | } 607 | 608 | func (x *InitRequest) Reset() { 609 | *x = InitRequest{} 610 | if protoimpl.UnsafeEnabled { 611 | mi := &file_testdata_initdb_initdb_proto_msgTypes[8] 612 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 613 | ms.StoreMessageInfo(mi) 614 | } 615 | } 616 | 617 | func (x *InitRequest) String() string { 618 | return protoimpl.X.MessageStringOf(x) 619 | } 620 | 621 | func (*InitRequest) ProtoMessage() {} 622 | 623 | func (x *InitRequest) ProtoReflect() protoreflect.Message { 624 | mi := &file_testdata_initdb_initdb_proto_msgTypes[8] 625 | if protoimpl.UnsafeEnabled && x != nil { 626 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 627 | if ms.LoadMessageInfo() == nil { 628 | ms.StoreMessageInfo(mi) 629 | } 630 | return ms 631 | } 632 | return mi.MessageOf(x) 633 | } 634 | 635 | // Deprecated: Use InitRequest.ProtoReflect.Descriptor instead. 636 | func (*InitRequest) Descriptor() ([]byte, []int) { 637 | return file_testdata_initdb_initdb_proto_rawDescGZIP(), []int{8} 638 | } 639 | 640 | func (x *InitRequest) GetMeta() *Meta { 641 | if x != nil { 642 | return x.Meta 643 | } 644 | return nil 645 | } 646 | 647 | var File_testdata_initdb_initdb_proto protoreflect.FileDescriptor 648 | 649 | var file_testdata_initdb_initdb_proto_rawDesc = []byte{ 650 | 0x0a, 0x1c, 0x74, 0x65, 0x73, 0x74, 0x64, 0x61, 0x74, 0x61, 0x2f, 0x69, 0x6e, 0x69, 0x74, 0x64, 651 | 0x62, 0x2f, 0x69, 0x6e, 0x69, 0x74, 0x64, 0x62, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x06, 652 | 0x69, 0x6e, 0x69, 0x74, 0x64, 0x62, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 653 | 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 654 | 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x16, 0x0a, 0x04, 0x4d, 0x65, 0x74, 0x61, 0x12, 655 | 0x0e, 0x0a, 0x02, 0x64, 0x62, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x64, 0x62, 0x22, 656 | 0xd4, 0x01, 0x0a, 0x13, 0x49, 0x6e, 0x73, 0x65, 0x72, 0x74, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 657 | 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x20, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 658 | 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x69, 0x6e, 0x69, 0x74, 0x64, 0x62, 0x2e, 0x4d, 659 | 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 660 | 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x02, 0x69, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73, 0x65, 661 | 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 662 | 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 663 | 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 664 | 0x64, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 665 | 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x12, 0x10, 0x0a, 0x03, 0x62, 0x69, 0x6f, 0x18, 0x06, 666 | 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x62, 0x69, 0x6f, 0x12, 0x2b, 0x0a, 0x11, 0x66, 0x61, 0x76, 667 | 0x6f, 0x75, 0x72, 0x69, 0x74, 0x65, 0x5f, 0x73, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x07, 668 | 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x66, 0x61, 0x76, 0x6f, 0x75, 0x72, 0x69, 0x74, 0x65, 0x53, 669 | 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x78, 0x0a, 0x11, 0x49, 0x6e, 0x73, 0x65, 0x72, 0x74, 670 | 0x42, 0x6c, 0x6f, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x20, 0x0a, 0x04, 0x6d, 671 | 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x69, 0x6e, 0x69, 0x74, 672 | 0x64, 0x62, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x0e, 0x0a, 673 | 0x02, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x02, 0x69, 0x64, 0x12, 0x14, 0x0a, 674 | 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x69, 675 | 0x74, 0x6c, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x5f, 0x69, 0x64, 676 | 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x49, 0x64, 677 | 0x22, 0x8f, 0x01, 0x0a, 0x14, 0x49, 0x6e, 0x73, 0x65, 0x72, 0x74, 0x43, 0x6f, 0x6d, 0x6d, 0x65, 678 | 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x20, 0x0a, 0x04, 0x6d, 0x65, 0x74, 679 | 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x69, 0x6e, 0x69, 0x74, 0x64, 0x62, 680 | 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x0e, 0x0a, 0x02, 0x69, 681 | 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x02, 0x69, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x70, 682 | 0x6f, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x70, 0x6f, 683 | 0x73, 0x74, 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 684 | 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 685 | 0x65, 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 686 | 0x6e, 0x74, 0x22, 0x94, 0x02, 0x0a, 0x11, 0x49, 0x6e, 0x73, 0x65, 0x72, 0x74, 0x50, 0x6f, 0x73, 687 | 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x20, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 688 | 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x69, 0x6e, 0x69, 0x74, 0x64, 0x62, 0x2e, 689 | 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 690 | 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x02, 0x69, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x61, 0x75, 691 | 0x74, 0x68, 0x6f, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08, 0x61, 692 | 0x75, 0x74, 0x68, 0x6f, 0x72, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x62, 0x6c, 0x6f, 0x67, 0x5f, 693 | 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x62, 0x6c, 0x6f, 0x67, 0x49, 0x64, 694 | 0x12, 0x39, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x6f, 0x6e, 0x18, 0x05, 695 | 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 696 | 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 697 | 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x4f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x73, 698 | 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x65, 699 | 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 700 | 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 701 | 0x14, 0x0a, 0x05, 0x64, 0x72, 0x61, 0x66, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 702 | 0x64, 0x72, 0x61, 0x66, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x18, 0x09, 0x20, 703 | 0x01, 0x28, 0x09, 0x52, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x22, 0x68, 0x0a, 0x14, 0x49, 0x6e, 0x73, 704 | 0x65, 0x72, 0x74, 0x50, 0x6f, 0x73, 0x74, 0x54, 0x61, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 705 | 0x74, 0x12, 0x20, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 706 | 0x0c, 0x2e, 0x69, 0x6e, 0x69, 0x74, 0x64, 0x62, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 707 | 0x65, 0x74, 0x61, 0x12, 0x17, 0x0a, 0x07, 0x70, 0x6f, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 708 | 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x70, 0x6f, 0x73, 0x74, 0x49, 0x64, 0x12, 0x15, 0x0a, 0x06, 709 | 0x74, 0x61, 0x67, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x74, 0x61, 710 | 0x67, 0x49, 0x64, 0x22, 0x58, 0x0a, 0x10, 0x49, 0x6e, 0x73, 0x65, 0x72, 0x74, 0x54, 0x61, 0x67, 711 | 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x20, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 712 | 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x69, 0x6e, 0x69, 0x74, 0x64, 0x62, 0x2e, 0x4d, 713 | 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 714 | 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 715 | 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x0f, 0x0a, 716 | 0x0d, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x2f, 717 | 0x0a, 0x0b, 0x49, 0x6e, 0x69, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x20, 0x0a, 718 | 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x69, 0x6e, 719 | 0x69, 0x74, 0x64, 0x62, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x32, 720 | 0xdf, 0x03, 0x0a, 0x0b, 0x49, 0x6e, 0x69, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 721 | 0x36, 0x0a, 0x06, 0x49, 0x6e, 0x69, 0x74, 0x44, 0x42, 0x12, 0x13, 0x2e, 0x69, 0x6e, 0x69, 0x74, 722 | 0x64, 0x62, 0x2e, 0x49, 0x6e, 0x69, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x15, 723 | 0x2e, 0x69, 0x6e, 0x69, 0x74, 0x64, 0x62, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x52, 0x65, 0x73, 724 | 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x44, 0x0a, 0x0c, 0x49, 0x6e, 0x73, 0x65, 0x72, 725 | 0x74, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x12, 0x1b, 0x2e, 0x69, 0x6e, 0x69, 0x74, 0x64, 0x62, 726 | 0x2e, 0x49, 0x6e, 0x73, 0x65, 0x72, 0x74, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x52, 0x65, 0x71, 727 | 0x75, 0x65, 0x73, 0x74, 0x1a, 0x15, 0x2e, 0x69, 0x6e, 0x69, 0x74, 0x64, 0x62, 0x2e, 0x45, 0x6d, 728 | 0x70, 0x74, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x40, 0x0a, 729 | 0x0a, 0x49, 0x6e, 0x73, 0x65, 0x72, 0x74, 0x42, 0x6c, 0x6f, 0x67, 0x12, 0x19, 0x2e, 0x69, 0x6e, 730 | 0x69, 0x74, 0x64, 0x62, 0x2e, 0x49, 0x6e, 0x73, 0x65, 0x72, 0x74, 0x42, 0x6c, 0x6f, 0x67, 0x52, 731 | 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x15, 0x2e, 0x69, 0x6e, 0x69, 0x74, 0x64, 0x62, 0x2e, 732 | 0x45, 0x6d, 0x70, 0x74, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 733 | 0x46, 0x0a, 0x0d, 0x49, 0x6e, 0x73, 0x65, 0x72, 0x74, 0x43, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, 734 | 0x12, 0x1c, 0x2e, 0x69, 0x6e, 0x69, 0x74, 0x64, 0x62, 0x2e, 0x49, 0x6e, 0x73, 0x65, 0x72, 0x74, 735 | 0x43, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x15, 736 | 0x2e, 0x69, 0x6e, 0x69, 0x74, 0x64, 0x62, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x52, 0x65, 0x73, 737 | 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x40, 0x0a, 0x0a, 0x49, 0x6e, 0x73, 0x65, 0x72, 738 | 0x74, 0x50, 0x6f, 0x73, 0x74, 0x12, 0x19, 0x2e, 0x69, 0x6e, 0x69, 0x74, 0x64, 0x62, 0x2e, 0x49, 739 | 0x6e, 0x73, 0x65, 0x72, 0x74, 0x50, 0x6f, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 740 | 0x1a, 0x15, 0x2e, 0x69, 0x6e, 0x69, 0x74, 0x64, 0x62, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x52, 741 | 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x46, 0x0a, 0x0d, 0x49, 0x6e, 0x73, 742 | 0x65, 0x72, 0x74, 0x50, 0x6f, 0x73, 0x74, 0x54, 0x61, 0x67, 0x12, 0x1c, 0x2e, 0x69, 0x6e, 0x69, 743 | 0x74, 0x64, 0x62, 0x2e, 0x49, 0x6e, 0x73, 0x65, 0x72, 0x74, 0x50, 0x6f, 0x73, 0x74, 0x54, 0x61, 744 | 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x15, 0x2e, 0x69, 0x6e, 0x69, 0x74, 0x64, 745 | 0x62, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 746 | 0x00, 0x12, 0x3e, 0x0a, 0x09, 0x49, 0x6e, 0x73, 0x65, 0x72, 0x74, 0x54, 0x61, 0x67, 0x12, 0x18, 747 | 0x2e, 0x69, 0x6e, 0x69, 0x74, 0x64, 0x62, 0x2e, 0x49, 0x6e, 0x73, 0x65, 0x72, 0x74, 0x54, 0x61, 748 | 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x15, 0x2e, 0x69, 0x6e, 0x69, 0x74, 0x64, 749 | 0x62, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 750 | 0x00, 0x42, 0x31, 0x5a, 0x2f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 751 | 0x6a, 0x61, 0x63, 0x6b, 0x73, 0x6b, 0x6a, 0x2f, 0x63, 0x61, 0x72, 0x74, 0x61, 0x2f, 0x74, 0x65, 752 | 0x73, 0x74, 0x64, 0x61, 0x74, 0x61, 0x2f, 0x69, 0x6e, 0x69, 0x74, 0x64, 0x62, 0x3b, 0x69, 0x6e, 753 | 0x69, 0x74, 0x64, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, 754 | } 755 | 756 | var ( 757 | file_testdata_initdb_initdb_proto_rawDescOnce sync.Once 758 | file_testdata_initdb_initdb_proto_rawDescData = file_testdata_initdb_initdb_proto_rawDesc 759 | ) 760 | 761 | func file_testdata_initdb_initdb_proto_rawDescGZIP() []byte { 762 | file_testdata_initdb_initdb_proto_rawDescOnce.Do(func() { 763 | file_testdata_initdb_initdb_proto_rawDescData = protoimpl.X.CompressGZIP(file_testdata_initdb_initdb_proto_rawDescData) 764 | }) 765 | return file_testdata_initdb_initdb_proto_rawDescData 766 | } 767 | 768 | var file_testdata_initdb_initdb_proto_msgTypes = make([]protoimpl.MessageInfo, 9) 769 | var file_testdata_initdb_initdb_proto_goTypes = []interface{}{ 770 | (*Meta)(nil), // 0: initdb.Meta 771 | (*InsertAuthorRequest)(nil), // 1: initdb.InsertAuthorRequest 772 | (*InsertBlogRequest)(nil), // 2: initdb.InsertBlogRequest 773 | (*InsertCommentRequest)(nil), // 3: initdb.InsertCommentRequest 774 | (*InsertPostRequest)(nil), // 4: initdb.InsertPostRequest 775 | (*InsertPostTagRequest)(nil), // 5: initdb.InsertPostTagRequest 776 | (*InsertTagRequest)(nil), // 6: initdb.InsertTagRequest 777 | (*EmptyResponse)(nil), // 7: initdb.EmptyResponse 778 | (*InitRequest)(nil), // 8: initdb.InitRequest 779 | (*timestamp.Timestamp)(nil), // 9: google.protobuf.Timestamp 780 | } 781 | var file_testdata_initdb_initdb_proto_depIdxs = []int32{ 782 | 0, // 0: initdb.InsertAuthorRequest.meta:type_name -> initdb.Meta 783 | 0, // 1: initdb.InsertBlogRequest.meta:type_name -> initdb.Meta 784 | 0, // 2: initdb.InsertCommentRequest.meta:type_name -> initdb.Meta 785 | 0, // 3: initdb.InsertPostRequest.meta:type_name -> initdb.Meta 786 | 9, // 4: initdb.InsertPostRequest.created_on:type_name -> google.protobuf.Timestamp 787 | 0, // 5: initdb.InsertPostTagRequest.meta:type_name -> initdb.Meta 788 | 0, // 6: initdb.InsertTagRequest.meta:type_name -> initdb.Meta 789 | 0, // 7: initdb.InitRequest.meta:type_name -> initdb.Meta 790 | 8, // 8: initdb.InitService.InitDB:input_type -> initdb.InitRequest 791 | 1, // 9: initdb.InitService.InsertAuthor:input_type -> initdb.InsertAuthorRequest 792 | 2, // 10: initdb.InitService.InsertBlog:input_type -> initdb.InsertBlogRequest 793 | 3, // 11: initdb.InitService.InsertComment:input_type -> initdb.InsertCommentRequest 794 | 4, // 12: initdb.InitService.InsertPost:input_type -> initdb.InsertPostRequest 795 | 5, // 13: initdb.InitService.InsertPostTag:input_type -> initdb.InsertPostTagRequest 796 | 6, // 14: initdb.InitService.InsertTag:input_type -> initdb.InsertTagRequest 797 | 7, // 15: initdb.InitService.InitDB:output_type -> initdb.EmptyResponse 798 | 7, // 16: initdb.InitService.InsertAuthor:output_type -> initdb.EmptyResponse 799 | 7, // 17: initdb.InitService.InsertBlog:output_type -> initdb.EmptyResponse 800 | 7, // 18: initdb.InitService.InsertComment:output_type -> initdb.EmptyResponse 801 | 7, // 19: initdb.InitService.InsertPost:output_type -> initdb.EmptyResponse 802 | 7, // 20: initdb.InitService.InsertPostTag:output_type -> initdb.EmptyResponse 803 | 7, // 21: initdb.InitService.InsertTag:output_type -> initdb.EmptyResponse 804 | 15, // [15:22] is the sub-list for method output_type 805 | 8, // [8:15] is the sub-list for method input_type 806 | 8, // [8:8] is the sub-list for extension type_name 807 | 8, // [8:8] is the sub-list for extension extendee 808 | 0, // [0:8] is the sub-list for field type_name 809 | } 810 | 811 | func init() { file_testdata_initdb_initdb_proto_init() } 812 | func file_testdata_initdb_initdb_proto_init() { 813 | if File_testdata_initdb_initdb_proto != nil { 814 | return 815 | } 816 | if !protoimpl.UnsafeEnabled { 817 | file_testdata_initdb_initdb_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { 818 | switch v := v.(*Meta); i { 819 | case 0: 820 | return &v.state 821 | case 1: 822 | return &v.sizeCache 823 | case 2: 824 | return &v.unknownFields 825 | default: 826 | return nil 827 | } 828 | } 829 | file_testdata_initdb_initdb_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { 830 | switch v := v.(*InsertAuthorRequest); i { 831 | case 0: 832 | return &v.state 833 | case 1: 834 | return &v.sizeCache 835 | case 2: 836 | return &v.unknownFields 837 | default: 838 | return nil 839 | } 840 | } 841 | file_testdata_initdb_initdb_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { 842 | switch v := v.(*InsertBlogRequest); i { 843 | case 0: 844 | return &v.state 845 | case 1: 846 | return &v.sizeCache 847 | case 2: 848 | return &v.unknownFields 849 | default: 850 | return nil 851 | } 852 | } 853 | file_testdata_initdb_initdb_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { 854 | switch v := v.(*InsertCommentRequest); i { 855 | case 0: 856 | return &v.state 857 | case 1: 858 | return &v.sizeCache 859 | case 2: 860 | return &v.unknownFields 861 | default: 862 | return nil 863 | } 864 | } 865 | file_testdata_initdb_initdb_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { 866 | switch v := v.(*InsertPostRequest); i { 867 | case 0: 868 | return &v.state 869 | case 1: 870 | return &v.sizeCache 871 | case 2: 872 | return &v.unknownFields 873 | default: 874 | return nil 875 | } 876 | } 877 | file_testdata_initdb_initdb_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { 878 | switch v := v.(*InsertPostTagRequest); i { 879 | case 0: 880 | return &v.state 881 | case 1: 882 | return &v.sizeCache 883 | case 2: 884 | return &v.unknownFields 885 | default: 886 | return nil 887 | } 888 | } 889 | file_testdata_initdb_initdb_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { 890 | switch v := v.(*InsertTagRequest); i { 891 | case 0: 892 | return &v.state 893 | case 1: 894 | return &v.sizeCache 895 | case 2: 896 | return &v.unknownFields 897 | default: 898 | return nil 899 | } 900 | } 901 | file_testdata_initdb_initdb_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { 902 | switch v := v.(*EmptyResponse); i { 903 | case 0: 904 | return &v.state 905 | case 1: 906 | return &v.sizeCache 907 | case 2: 908 | return &v.unknownFields 909 | default: 910 | return nil 911 | } 912 | } 913 | file_testdata_initdb_initdb_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { 914 | switch v := v.(*InitRequest); i { 915 | case 0: 916 | return &v.state 917 | case 1: 918 | return &v.sizeCache 919 | case 2: 920 | return &v.unknownFields 921 | default: 922 | return nil 923 | } 924 | } 925 | } 926 | type x struct{} 927 | out := protoimpl.TypeBuilder{ 928 | File: protoimpl.DescBuilder{ 929 | GoPackagePath: reflect.TypeOf(x{}).PkgPath(), 930 | RawDescriptor: file_testdata_initdb_initdb_proto_rawDesc, 931 | NumEnums: 0, 932 | NumMessages: 9, 933 | NumExtensions: 0, 934 | NumServices: 1, 935 | }, 936 | GoTypes: file_testdata_initdb_initdb_proto_goTypes, 937 | DependencyIndexes: file_testdata_initdb_initdb_proto_depIdxs, 938 | MessageInfos: file_testdata_initdb_initdb_proto_msgTypes, 939 | }.Build() 940 | File_testdata_initdb_initdb_proto = out.File 941 | file_testdata_initdb_initdb_proto_rawDesc = nil 942 | file_testdata_initdb_initdb_proto_goTypes = nil 943 | file_testdata_initdb_initdb_proto_depIdxs = nil 944 | } 945 | 946 | // Reference imports to suppress errors if they are not otherwise used. 947 | var _ context.Context 948 | var _ grpc.ClientConnInterface 949 | 950 | // This is a compile-time assertion to ensure that this generated file 951 | // is compatible with the grpc package it is being compiled against. 952 | const _ = grpc.SupportPackageIsVersion6 953 | 954 | // InitServiceClient is the client API for InitService service. 955 | // 956 | // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. 957 | type InitServiceClient interface { 958 | InitDB(ctx context.Context, in *InitRequest, opts ...grpc.CallOption) (*EmptyResponse, error) 959 | InsertAuthor(ctx context.Context, in *InsertAuthorRequest, opts ...grpc.CallOption) (*EmptyResponse, error) 960 | InsertBlog(ctx context.Context, in *InsertBlogRequest, opts ...grpc.CallOption) (*EmptyResponse, error) 961 | InsertComment(ctx context.Context, in *InsertCommentRequest, opts ...grpc.CallOption) (*EmptyResponse, error) 962 | InsertPost(ctx context.Context, in *InsertPostRequest, opts ...grpc.CallOption) (*EmptyResponse, error) 963 | InsertPostTag(ctx context.Context, in *InsertPostTagRequest, opts ...grpc.CallOption) (*EmptyResponse, error) 964 | InsertTag(ctx context.Context, in *InsertTagRequest, opts ...grpc.CallOption) (*EmptyResponse, error) 965 | } 966 | 967 | type initServiceClient struct { 968 | cc grpc.ClientConnInterface 969 | } 970 | 971 | func NewInitServiceClient(cc grpc.ClientConnInterface) InitServiceClient { 972 | return &initServiceClient{cc} 973 | } 974 | 975 | func (c *initServiceClient) InitDB(ctx context.Context, in *InitRequest, opts ...grpc.CallOption) (*EmptyResponse, error) { 976 | out := new(EmptyResponse) 977 | err := c.cc.Invoke(ctx, "/initdb.InitService/InitDB", in, out, opts...) 978 | if err != nil { 979 | return nil, err 980 | } 981 | return out, nil 982 | } 983 | 984 | func (c *initServiceClient) InsertAuthor(ctx context.Context, in *InsertAuthorRequest, opts ...grpc.CallOption) (*EmptyResponse, error) { 985 | out := new(EmptyResponse) 986 | err := c.cc.Invoke(ctx, "/initdb.InitService/InsertAuthor", in, out, opts...) 987 | if err != nil { 988 | return nil, err 989 | } 990 | return out, nil 991 | } 992 | 993 | func (c *initServiceClient) InsertBlog(ctx context.Context, in *InsertBlogRequest, opts ...grpc.CallOption) (*EmptyResponse, error) { 994 | out := new(EmptyResponse) 995 | err := c.cc.Invoke(ctx, "/initdb.InitService/InsertBlog", in, out, opts...) 996 | if err != nil { 997 | return nil, err 998 | } 999 | return out, nil 1000 | } 1001 | 1002 | func (c *initServiceClient) InsertComment(ctx context.Context, in *InsertCommentRequest, opts ...grpc.CallOption) (*EmptyResponse, error) { 1003 | out := new(EmptyResponse) 1004 | err := c.cc.Invoke(ctx, "/initdb.InitService/InsertComment", in, out, opts...) 1005 | if err != nil { 1006 | return nil, err 1007 | } 1008 | return out, nil 1009 | } 1010 | 1011 | func (c *initServiceClient) InsertPost(ctx context.Context, in *InsertPostRequest, opts ...grpc.CallOption) (*EmptyResponse, error) { 1012 | out := new(EmptyResponse) 1013 | err := c.cc.Invoke(ctx, "/initdb.InitService/InsertPost", in, out, opts...) 1014 | if err != nil { 1015 | return nil, err 1016 | } 1017 | return out, nil 1018 | } 1019 | 1020 | func (c *initServiceClient) InsertPostTag(ctx context.Context, in *InsertPostTagRequest, opts ...grpc.CallOption) (*EmptyResponse, error) { 1021 | out := new(EmptyResponse) 1022 | err := c.cc.Invoke(ctx, "/initdb.InitService/InsertPostTag", in, out, opts...) 1023 | if err != nil { 1024 | return nil, err 1025 | } 1026 | return out, nil 1027 | } 1028 | 1029 | func (c *initServiceClient) InsertTag(ctx context.Context, in *InsertTagRequest, opts ...grpc.CallOption) (*EmptyResponse, error) { 1030 | out := new(EmptyResponse) 1031 | err := c.cc.Invoke(ctx, "/initdb.InitService/InsertTag", in, out, opts...) 1032 | if err != nil { 1033 | return nil, err 1034 | } 1035 | return out, nil 1036 | } 1037 | 1038 | // InitServiceServer is the server API for InitService service. 1039 | type InitServiceServer interface { 1040 | InitDB(context.Context, *InitRequest) (*EmptyResponse, error) 1041 | InsertAuthor(context.Context, *InsertAuthorRequest) (*EmptyResponse, error) 1042 | InsertBlog(context.Context, *InsertBlogRequest) (*EmptyResponse, error) 1043 | InsertComment(context.Context, *InsertCommentRequest) (*EmptyResponse, error) 1044 | InsertPost(context.Context, *InsertPostRequest) (*EmptyResponse, error) 1045 | InsertPostTag(context.Context, *InsertPostTagRequest) (*EmptyResponse, error) 1046 | InsertTag(context.Context, *InsertTagRequest) (*EmptyResponse, error) 1047 | } 1048 | 1049 | // UnimplementedInitServiceServer can be embedded to have forward compatible implementations. 1050 | type UnimplementedInitServiceServer struct { 1051 | } 1052 | 1053 | func (*UnimplementedInitServiceServer) InitDB(context.Context, *InitRequest) (*EmptyResponse, error) { 1054 | return nil, status.Errorf(codes.Unimplemented, "method InitDB not implemented") 1055 | } 1056 | func (*UnimplementedInitServiceServer) InsertAuthor(context.Context, *InsertAuthorRequest) (*EmptyResponse, error) { 1057 | return nil, status.Errorf(codes.Unimplemented, "method InsertAuthor not implemented") 1058 | } 1059 | func (*UnimplementedInitServiceServer) InsertBlog(context.Context, *InsertBlogRequest) (*EmptyResponse, error) { 1060 | return nil, status.Errorf(codes.Unimplemented, "method InsertBlog not implemented") 1061 | } 1062 | func (*UnimplementedInitServiceServer) InsertComment(context.Context, *InsertCommentRequest) (*EmptyResponse, error) { 1063 | return nil, status.Errorf(codes.Unimplemented, "method InsertComment not implemented") 1064 | } 1065 | func (*UnimplementedInitServiceServer) InsertPost(context.Context, *InsertPostRequest) (*EmptyResponse, error) { 1066 | return nil, status.Errorf(codes.Unimplemented, "method InsertPost not implemented") 1067 | } 1068 | func (*UnimplementedInitServiceServer) InsertPostTag(context.Context, *InsertPostTagRequest) (*EmptyResponse, error) { 1069 | return nil, status.Errorf(codes.Unimplemented, "method InsertPostTag not implemented") 1070 | } 1071 | func (*UnimplementedInitServiceServer) InsertTag(context.Context, *InsertTagRequest) (*EmptyResponse, error) { 1072 | return nil, status.Errorf(codes.Unimplemented, "method InsertTag not implemented") 1073 | } 1074 | 1075 | func RegisterInitServiceServer(s *grpc.Server, srv InitServiceServer) { 1076 | s.RegisterService(&_InitService_serviceDesc, srv) 1077 | } 1078 | 1079 | func _InitService_InitDB_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 1080 | in := new(InitRequest) 1081 | if err := dec(in); err != nil { 1082 | return nil, err 1083 | } 1084 | if interceptor == nil { 1085 | return srv.(InitServiceServer).InitDB(ctx, in) 1086 | } 1087 | info := &grpc.UnaryServerInfo{ 1088 | Server: srv, 1089 | FullMethod: "/initdb.InitService/InitDB", 1090 | } 1091 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 1092 | return srv.(InitServiceServer).InitDB(ctx, req.(*InitRequest)) 1093 | } 1094 | return interceptor(ctx, in, info, handler) 1095 | } 1096 | 1097 | func _InitService_InsertAuthor_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 1098 | in := new(InsertAuthorRequest) 1099 | if err := dec(in); err != nil { 1100 | return nil, err 1101 | } 1102 | if interceptor == nil { 1103 | return srv.(InitServiceServer).InsertAuthor(ctx, in) 1104 | } 1105 | info := &grpc.UnaryServerInfo{ 1106 | Server: srv, 1107 | FullMethod: "/initdb.InitService/InsertAuthor", 1108 | } 1109 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 1110 | return srv.(InitServiceServer).InsertAuthor(ctx, req.(*InsertAuthorRequest)) 1111 | } 1112 | return interceptor(ctx, in, info, handler) 1113 | } 1114 | 1115 | func _InitService_InsertBlog_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 1116 | in := new(InsertBlogRequest) 1117 | if err := dec(in); err != nil { 1118 | return nil, err 1119 | } 1120 | if interceptor == nil { 1121 | return srv.(InitServiceServer).InsertBlog(ctx, in) 1122 | } 1123 | info := &grpc.UnaryServerInfo{ 1124 | Server: srv, 1125 | FullMethod: "/initdb.InitService/InsertBlog", 1126 | } 1127 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 1128 | return srv.(InitServiceServer).InsertBlog(ctx, req.(*InsertBlogRequest)) 1129 | } 1130 | return interceptor(ctx, in, info, handler) 1131 | } 1132 | 1133 | func _InitService_InsertComment_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 1134 | in := new(InsertCommentRequest) 1135 | if err := dec(in); err != nil { 1136 | return nil, err 1137 | } 1138 | if interceptor == nil { 1139 | return srv.(InitServiceServer).InsertComment(ctx, in) 1140 | } 1141 | info := &grpc.UnaryServerInfo{ 1142 | Server: srv, 1143 | FullMethod: "/initdb.InitService/InsertComment", 1144 | } 1145 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 1146 | return srv.(InitServiceServer).InsertComment(ctx, req.(*InsertCommentRequest)) 1147 | } 1148 | return interceptor(ctx, in, info, handler) 1149 | } 1150 | 1151 | func _InitService_InsertPost_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 1152 | in := new(InsertPostRequest) 1153 | if err := dec(in); err != nil { 1154 | return nil, err 1155 | } 1156 | if interceptor == nil { 1157 | return srv.(InitServiceServer).InsertPost(ctx, in) 1158 | } 1159 | info := &grpc.UnaryServerInfo{ 1160 | Server: srv, 1161 | FullMethod: "/initdb.InitService/InsertPost", 1162 | } 1163 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 1164 | return srv.(InitServiceServer).InsertPost(ctx, req.(*InsertPostRequest)) 1165 | } 1166 | return interceptor(ctx, in, info, handler) 1167 | } 1168 | 1169 | func _InitService_InsertPostTag_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 1170 | in := new(InsertPostTagRequest) 1171 | if err := dec(in); err != nil { 1172 | return nil, err 1173 | } 1174 | if interceptor == nil { 1175 | return srv.(InitServiceServer).InsertPostTag(ctx, in) 1176 | } 1177 | info := &grpc.UnaryServerInfo{ 1178 | Server: srv, 1179 | FullMethod: "/initdb.InitService/InsertPostTag", 1180 | } 1181 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 1182 | return srv.(InitServiceServer).InsertPostTag(ctx, req.(*InsertPostTagRequest)) 1183 | } 1184 | return interceptor(ctx, in, info, handler) 1185 | } 1186 | 1187 | func _InitService_InsertTag_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 1188 | in := new(InsertTagRequest) 1189 | if err := dec(in); err != nil { 1190 | return nil, err 1191 | } 1192 | if interceptor == nil { 1193 | return srv.(InitServiceServer).InsertTag(ctx, in) 1194 | } 1195 | info := &grpc.UnaryServerInfo{ 1196 | Server: srv, 1197 | FullMethod: "/initdb.InitService/InsertTag", 1198 | } 1199 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 1200 | return srv.(InitServiceServer).InsertTag(ctx, req.(*InsertTagRequest)) 1201 | } 1202 | return interceptor(ctx, in, info, handler) 1203 | } 1204 | 1205 | var _InitService_serviceDesc = grpc.ServiceDesc{ 1206 | ServiceName: "initdb.InitService", 1207 | HandlerType: (*InitServiceServer)(nil), 1208 | Methods: []grpc.MethodDesc{ 1209 | { 1210 | MethodName: "InitDB", 1211 | Handler: _InitService_InitDB_Handler, 1212 | }, 1213 | { 1214 | MethodName: "InsertAuthor", 1215 | Handler: _InitService_InsertAuthor_Handler, 1216 | }, 1217 | { 1218 | MethodName: "InsertBlog", 1219 | Handler: _InitService_InsertBlog_Handler, 1220 | }, 1221 | { 1222 | MethodName: "InsertComment", 1223 | Handler: _InitService_InsertComment_Handler, 1224 | }, 1225 | { 1226 | MethodName: "InsertPost", 1227 | Handler: _InitService_InsertPost_Handler, 1228 | }, 1229 | { 1230 | MethodName: "InsertPostTag", 1231 | Handler: _InitService_InsertPostTag_Handler, 1232 | }, 1233 | { 1234 | MethodName: "InsertTag", 1235 | Handler: _InitService_InsertTag_Handler, 1236 | }, 1237 | }, 1238 | Streams: []grpc.StreamDesc{}, 1239 | Metadata: "testdata/initdb/initdb.proto", 1240 | } 1241 | -------------------------------------------------------------------------------- /testdata/initdb/initdb.proto: -------------------------------------------------------------------------------- 1 | syntax="proto3"; 2 | 3 | package initdb; 4 | 5 | import "google/protobuf/timestamp.proto"; 6 | 7 | option go_package = "github.com/jackskj/carta/testdata/initdb;initdb"; 8 | 9 | service InitService { 10 | rpc InitDB (InitRequest) returns (EmptyResponse) {} 11 | rpc InsertAuthor (InsertAuthorRequest) returns (EmptyResponse) {} 12 | rpc InsertBlog (InsertBlogRequest) returns (EmptyResponse) {} 13 | rpc InsertComment (InsertCommentRequest) returns (EmptyResponse) {} 14 | rpc InsertPost (InsertPostRequest) returns (EmptyResponse) {} 15 | rpc InsertPostTag (InsertPostTagRequest) returns (EmptyResponse) {} 16 | rpc InsertTag (InsertTagRequest) returns (EmptyResponse) {} 17 | } 18 | message Meta { 19 | string db = 1; 20 | } 21 | 22 | message InsertAuthorRequest { 23 | Meta meta = 1; 24 | uint32 id = 2; 25 | string username = 3; 26 | string password = 4; 27 | string email = 5; 28 | string bio = 6; 29 | string favourite_section = 7; 30 | } 31 | 32 | message InsertBlogRequest { 33 | Meta meta = 1; 34 | uint32 id = 2; 35 | string title = 3; 36 | uint32 author_id= 4; 37 | } 38 | 39 | message InsertCommentRequest { 40 | Meta meta = 1; 41 | uint32 id = 2; 42 | uint32 post_id = 3; 43 | string name = 4; 44 | string comment = 5; 45 | } 46 | 47 | message InsertPostRequest { 48 | Meta meta = 1; 49 | uint32 id = 2; 50 | uint32 author_id = 3; 51 | uint32 blog_id = 4; 52 | google.protobuf.Timestamp created_on = 5; 53 | string section = 6; 54 | string subject = 7; 55 | string draft = 8; 56 | string body = 9; 57 | } 58 | 59 | message InsertPostTagRequest { 60 | Meta meta = 1; 61 | uint32 post_id = 2; 62 | uint32 tag_id = 3; 63 | } 64 | 65 | message InsertTagRequest { 66 | Meta meta = 1; 67 | uint32 id = 2; 68 | string name = 3; 69 | } 70 | 71 | message EmptyResponse {} 72 | 73 | message InitRequest { 74 | Meta meta = 1; 75 | } 76 | -------------------------------------------------------------------------------- /testdata/initdb/server.go: -------------------------------------------------------------------------------- 1 | package initdb 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "database/sql" 7 | "log" 8 | "text/template" 9 | 10 | "github.com/Masterminds/sprig" 11 | _ "github.com/golang/protobuf/ptypes/timestamp" 12 | sqlTpl "github.com/jackskj/carta/testdata/initdb/sql" 13 | tpl "github.com/jackskj/protoc-gen-map/templates" 14 | "google.golang.org/grpc/codes" 15 | "google.golang.org/grpc/status" 16 | ) 17 | 18 | type InitServiceMapServer struct { 19 | DBs map[string]*sql.DB 20 | } 21 | 22 | var InitTemplate, _ = template.New("InitTemplate").Funcs(sprig.TxtFuncMap()).Funcs(tpl.Funcs()).Parse(sqlTpl.Init) 23 | 24 | func (m *InitServiceMapServer) InitDB(ctx context.Context, r *InitRequest) (*EmptyResponse, error) { 25 | sqlBuffer := &bytes.Buffer{} 26 | if err := InitTemplate.ExecuteTemplate(sqlBuffer, "InitDB", r); err != nil { 27 | return nil, status.Error(codes.InvalidArgument, err.Error()) 28 | } 29 | rawSql := sqlBuffer.String() 30 | if _, err := m.DBs[r.GetMeta().Db].Exec(rawSql); err != nil { 31 | return nil, status.Error(codes.InvalidArgument, "error: executing query") 32 | } 33 | return &EmptyResponse{}, nil 34 | } 35 | 36 | func (m *InitServiceMapServer) InsertAuthor(ctx context.Context, r *InsertAuthorRequest) (*EmptyResponse, error) { 37 | sqlBuffer := &bytes.Buffer{} 38 | if err := InitTemplate.ExecuteTemplate(sqlBuffer, "InsertAuthor", r); err != nil { 39 | return nil, status.Error(codes.InvalidArgument, err.Error()) 40 | } 41 | rawSql := sqlBuffer.String() 42 | if _, err := m.DBs[r.GetMeta().Db].Exec(rawSql); err != nil { 43 | return nil, status.Error(codes.InvalidArgument, "error: executing query") 44 | } 45 | return &EmptyResponse{}, nil 46 | } 47 | 48 | func (m *InitServiceMapServer) InsertBlog(ctx context.Context, r *InsertBlogRequest) (*EmptyResponse, error) { 49 | sqlBuffer := &bytes.Buffer{} 50 | if err := InitTemplate.ExecuteTemplate(sqlBuffer, "InsertBlog", r); err != nil { 51 | return nil, status.Error(codes.InvalidArgument, err.Error()) 52 | } 53 | rawSql := sqlBuffer.String() 54 | if _, err := m.DBs[r.GetMeta().Db].Exec(rawSql); err != nil { 55 | return nil, status.Error(codes.InvalidArgument, "error: executing query") 56 | } 57 | return &EmptyResponse{}, nil 58 | } 59 | 60 | func (m *InitServiceMapServer) InsertComment(ctx context.Context, r *InsertCommentRequest) (*EmptyResponse, error) { 61 | sqlBuffer := &bytes.Buffer{} 62 | if err := InitTemplate.ExecuteTemplate(sqlBuffer, "InsertComment", r); err != nil { 63 | return nil, status.Error(codes.InvalidArgument, err.Error()) 64 | } 65 | rawSql := sqlBuffer.String() 66 | if _, err := m.DBs[r.GetMeta().Db].Exec(rawSql); err != nil { 67 | return nil, status.Error(codes.InvalidArgument, "error: executing query") 68 | } 69 | return &EmptyResponse{}, nil 70 | } 71 | 72 | func (m *InitServiceMapServer) InsertPost(ctx context.Context, r *InsertPostRequest) (*EmptyResponse, error) { 73 | sqlBuffer := &bytes.Buffer{} 74 | if err := InitTemplate.ExecuteTemplate(sqlBuffer, "InsertPost", r); err != nil { 75 | return nil, status.Error(codes.InvalidArgument, err.Error()) 76 | } 77 | rawSql := sqlBuffer.String() 78 | if _, err := m.DBs[r.GetMeta().Db].Exec(rawSql); err != nil { 79 | return nil, status.Error(codes.InvalidArgument, "error: executing query") 80 | } 81 | return &EmptyResponse{}, nil 82 | 83 | } 84 | 85 | func (m *InitServiceMapServer) InsertPostTag(ctx context.Context, r *InsertPostTagRequest) (*EmptyResponse, error) { 86 | sqlBuffer := &bytes.Buffer{} 87 | if err := InitTemplate.ExecuteTemplate(sqlBuffer, "InsertPostTag", r); err != nil { 88 | return nil, status.Error(codes.InvalidArgument, err.Error()) 89 | } 90 | rawSql := sqlBuffer.String() 91 | if _, err := m.DBs[r.GetMeta().Db].Exec(rawSql); err != nil { 92 | log.Fatal(err.Error()) 93 | return nil, status.Error(codes.InvalidArgument, "error: executing query") 94 | } 95 | return &EmptyResponse{}, nil 96 | 97 | } 98 | 99 | func (m *InitServiceMapServer) InsertTag(ctx context.Context, r *InsertTagRequest) (*EmptyResponse, error) { 100 | sqlBuffer := &bytes.Buffer{} 101 | if err := InitTemplate.ExecuteTemplate(sqlBuffer, "InsertTag", r); err != nil { 102 | return nil, status.Error(codes.InvalidArgument, err.Error()) 103 | } 104 | rawSql := sqlBuffer.String() 105 | if _, err := m.DBs[r.GetMeta().Db].Exec(rawSql); err != nil { 106 | return nil, status.Error(codes.InvalidArgument, "error: executing query") 107 | } 108 | return &EmptyResponse{}, nil 109 | } 110 | -------------------------------------------------------------------------------- /testdata/initdb/sql/init_db.go: -------------------------------------------------------------------------------- 1 | package sql 2 | 3 | const Init = ` 4 | {{define "InitDB" }} 5 | 6 | drop table if exists author; 7 | create table author ( 8 | id int 9 | primary key, 10 | username VARCHAR(255), 11 | password VARCHAR(255), 12 | email VARCHAR(255), 13 | bio VARCHAR(255), 14 | favourite_section VARCHAR(255) 15 | ); 16 | 17 | drop table if exists blog; 18 | create table blog ( 19 | id int 20 | primary key, 21 | title VARCHAR(255), 22 | author_id int 23 | ); 24 | 25 | drop table if exists comment; 26 | create table comment ( 27 | id int 28 | primary key, 29 | post_id int, 30 | name VARCHAR(255), 31 | comment VARCHAR(255) 32 | ); 33 | 34 | drop table if exists post; 35 | create table post ( 36 | id int 37 | primary key, 38 | author_id int, 39 | blog_id int, 40 | created_on DATE, 41 | section VARCHAR(255), 42 | subject VARCHAR(255), 43 | draft VARCHAR(255), 44 | body VARCHAR(255) 45 | ); 46 | 47 | drop table if exists post_tag; 48 | create table post_tag ( 49 | post_id int, 50 | tag_id int, 51 | constraint post_tag_pk 52 | primary key (post_id, tag_id) 53 | ); 54 | 55 | drop table if exists tag; 56 | create table tag ( 57 | id int 58 | primary key, 59 | name varchar(255) 60 | ); 61 | 62 | drop table if exists relations; 63 | create table relations ( 64 | id int , 65 | basic_submap int , 66 | basic_submap_ptr int , 67 | basic_submap_ptr_ptr int , 68 | submap_sample_submap int , 69 | submap_ptr_sample_submap int , 70 | submap_ptr_ptr_sample_submap int 71 | ); 72 | 73 | insert into relations values ( 1, 2, 3, 4, 5, 6, 7); 74 | insert into relations values ( 1, 2, 3, 4, 5, 6, 7); 75 | insert into relations values ( 1, 3, 4, 5, 6, 7, 8); 76 | insert into relations values ( 1, 4, 5, 6, 7, 8, 9); 77 | 78 | insert into relations values ( 2, 2, 3, 4, 5, 6, 7); 79 | insert into relations values ( 2, 2, 3, 4, 5, 6, 7); 80 | insert into relations values ( 2, 3, 4, 5, 6, 7, 8); 81 | insert into relations values ( 2, 4, 5, 6, 7, 8, 9); 82 | 83 | drop table if exists nullbool; 84 | create table nullbool ( 85 | nullbool bool 86 | ); 87 | insert into nullbool values (null); 88 | {{end}} 89 | 90 | 91 | {{define "InsertAuthor" }} 92 | INSERT INTO author 93 | VALUES ( 94 | {{ .Id }}, 95 | {{ .Username | squote }}, 96 | {{ .Password | squote }}, 97 | {{ .Email | squote }}, 98 | {{ .Bio | squote }}, 99 | {{ .FavouriteSection | squote }} 100 | ); 101 | {{end}} 102 | 103 | {{define "InsertBlog" }} 104 | INSERT INTO blog 105 | VALUES ( 106 | {{ .Id }}, 107 | {{ .Title | squote }}, 108 | {{ .AuthorId }} 109 | ); 110 | {{end}} 111 | 112 | {{define "InsertComment" }} 113 | INSERT INTO comment 114 | VALUES ( 115 | {{ .Id }}, 116 | {{ .PostId }}, 117 | {{ .Name | squote }}, 118 | {{ .Comment | squote }} 119 | ); 120 | {{end}} 121 | 122 | {{define "InsertPost" }} 123 | INSERT INTO post 124 | VALUES ( 125 | {{ .Id }}, 126 | {{ .AuthorId }}, 127 | {{ .BlogId }}, 128 | {{ .CreatedOn | timestamp | squote }}, 129 | {{ .Section | squote }}, 130 | {{ .Subject | squote }}, 131 | {{ .Draft | squote }}, 132 | {{ .Body | squote }} 133 | ); 134 | {{end}} 135 | 136 | {{define "InsertPostTag" }} 137 | INSERT INTO post_tag 138 | VALUES ( 139 | {{ .PostId }}, 140 | {{ .TagId }} 141 | ); 142 | {{end}} 143 | 144 | {{define "InsertTag" }} 145 | INSERT INTO tag 146 | VALUES ( 147 | {{ .Id }}, 148 | {{ .Name | squote }} 149 | ); 150 | {{end}} 151 | ` 152 | -------------------------------------------------------------------------------- /testdata/mapper.golden: -------------------------------------------------------------------------------- 1 | { 2 | "TestBlog": [ 3 | { 4 | "blog_id": 1, 5 | "blog_title": "oJj4PSn4Oe", 6 | "author": { 7 | "author_id": 4, 8 | "author_username": "X7tLFJ84qY", 9 | "author_password": "U6UrN8ctec", 10 | "author_email": "wZt5S4zjhD", 11 | "author_bio": "0tXRTmkYKQ", 12 | "author_favourite_section": "woodworking" 13 | }, 14 | "posts": [ 15 | { 16 | "post_id": 5, 17 | "post_blog_id": 1, 18 | "post_author_id": 4, 19 | "post_created_on": { 20 | "seconds": 1257811200 21 | }, 22 | "post_section": "cooking", 23 | "post_subject": "tOcco2IIaS", 24 | "draft": "ZVYYbxMJM6", 25 | "post_body": "lW3NffpMlo", 26 | "comments": [ 27 | { 28 | "comment_id": 4, 29 | "comment_post_id": 5, 30 | "comment_text": { 31 | "String": "pz8kb9iAGV", 32 | "Valid": true 33 | } 34 | }, 35 | { 36 | "comment_id": 28, 37 | "comment_post_id": 5, 38 | "comment_text": { 39 | "String": "H3DQ6P1YVa", 40 | "Valid": true 41 | } 42 | }, 43 | { 44 | "comment_id": 30, 45 | "comment_post_id": 5, 46 | "comment_text": { 47 | "String": "EE39h4J8wu", 48 | "Valid": true 49 | } 50 | }, 51 | { 52 | "comment_id": 36, 53 | "comment_post_id": 5, 54 | "comment_text": { 55 | "String": "ZpdAaKIYj0", 56 | "Valid": true 57 | } 58 | }, 59 | { 60 | "comment_id": 64, 61 | "comment_post_id": 5, 62 | "comment_text": { 63 | "String": "eh0VOrNomk", 64 | "Valid": true 65 | } 66 | }, 67 | { 68 | "comment_id": 90, 69 | "comment_post_id": 5, 70 | "comment_text": { 71 | "String": "gLFNGASTmA", 72 | "Valid": true 73 | } 74 | }, 75 | { 76 | "comment_id": 134, 77 | "comment_post_id": 5, 78 | "comment_text": { 79 | "String": "rdV888clme", 80 | "Valid": true 81 | } 82 | }, 83 | { 84 | "comment_id": 141, 85 | "comment_post_id": 5, 86 | "comment_text": { 87 | "String": "LIYh7R1zV9", 88 | "Valid": true 89 | } 90 | } 91 | ], 92 | "tags": [ 93 | { 94 | "tag_id": 1, 95 | "tag_name": "VuS9jZ8uVb" 96 | }, 97 | { 98 | "tag_id": 8, 99 | "tag_name": "hV3vC5AWX3" 100 | }, 101 | { 102 | "tag_id": 9, 103 | "tag_name": "9IVUWSP2Nc" 104 | } 105 | ] 106 | }, 107 | { 108 | "post_id": 19, 109 | "post_blog_id": 1, 110 | "post_author_id": 4, 111 | "post_created_on": { 112 | "seconds": 1257811200 113 | }, 114 | "post_section": "painting", 115 | "post_subject": "0LFFfcqNvG", 116 | "draft": "RvmSDvrMdS", 117 | "post_body": "kNPsgkvIId", 118 | "comments": [ 119 | { 120 | "comment_id": 0, 121 | "comment_post_id": 19, 122 | "comment_text": { 123 | "String": "HGPGn50NkO", 124 | "Valid": true 125 | } 126 | }, 127 | { 128 | "comment_id": 48, 129 | "comment_post_id": 19, 130 | "comment_text": { 131 | "String": "T6dDoHNSPE", 132 | "Valid": true 133 | } 134 | }, 135 | { 136 | "comment_id": 106, 137 | "comment_post_id": 19, 138 | "comment_text": { 139 | "String": "gmKwUMoa5S", 140 | "Valid": true 141 | } 142 | }, 143 | { 144 | "comment_id": 191, 145 | "comment_post_id": 19, 146 | "comment_text": { 147 | "String": "x4j9HlWQyk", 148 | "Valid": true 149 | } 150 | } 151 | ], 152 | "tags": [ 153 | { 154 | "tag_id": 6, 155 | "tag_name": "BpLnfgDsc2" 156 | }, 157 | { 158 | "tag_id": 7, 159 | "tag_name": "N95RxRTZHW" 160 | }, 161 | { 162 | "tag_id": 8, 163 | "tag_name": "hV3vC5AWX3" 164 | } 165 | ] 166 | }, 167 | { 168 | "post_id": 22, 169 | "post_blog_id": 1, 170 | "post_author_id": 4, 171 | "post_created_on": { 172 | "seconds": 1257811200 173 | }, 174 | "post_section": "cooking", 175 | "post_subject": "VwYAScjtQb", 176 | "draft": "Ks5ss7WOCv", 177 | "post_body": "dQpuP45ZdI", 178 | "comments": [ 179 | { 180 | "comment_id": 27, 181 | "comment_post_id": 22, 182 | "comment_text": { 183 | "String": "ZFqFTbV2W4", 184 | "Valid": true 185 | } 186 | }, 187 | { 188 | "comment_id": 119, 189 | "comment_post_id": 22, 190 | "comment_text": { 191 | "String": "HRxVmRFa3s", 192 | "Valid": true 193 | } 194 | } 195 | ], 196 | "tags": [ 197 | { 198 | "tag_name": "HciWvqZTa2" 199 | }, 200 | { 201 | "tag_id": 6, 202 | "tag_name": "BpLnfgDsc2" 203 | }, 204 | { 205 | "tag_id": 9, 206 | "tag_name": "9IVUWSP2Nc" 207 | } 208 | ] 209 | }, 210 | { 211 | "post_id": 34, 212 | "post_blog_id": 1, 213 | "post_author_id": 4, 214 | "post_created_on": { 215 | "seconds": 1257811200 216 | }, 217 | "post_section": "woodworking", 218 | "post_subject": "IaXEZyJ9Hb", 219 | "draft": "6Y8eu54YDr", 220 | "post_body": "5HLn3pWki1", 221 | "comments": [ 222 | { 223 | "comment_id": 11, 224 | "comment_post_id": 34, 225 | "comment_text": { 226 | "String": "B27FtqAA9w", 227 | "Valid": true 228 | } 229 | }, 230 | { 231 | "comment_id": 25, 232 | "comment_post_id": 34, 233 | "comment_text": { 234 | "String": "BhwjV7Gc9u", 235 | "Valid": true 236 | } 237 | }, 238 | { 239 | "comment_id": 67, 240 | "comment_post_id": 34, 241 | "comment_text": { 242 | "String": "58ONeDbgGb", 243 | "Valid": true 244 | } 245 | }, 246 | { 247 | "comment_id": 73, 248 | "comment_post_id": 34, 249 | "comment_text": { 250 | "String": "zjyfJXUUQD", 251 | "Valid": true 252 | } 253 | }, 254 | { 255 | "comment_id": 81, 256 | "comment_post_id": 34, 257 | "comment_text": { 258 | "String": "SllRkTm2hH", 259 | "Valid": true 260 | } 261 | }, 262 | { 263 | "comment_id": 88, 264 | "comment_post_id": 34, 265 | "comment_text": { 266 | "String": "oGMDCfLXVQ", 267 | "Valid": true 268 | } 269 | }, 270 | { 271 | "comment_id": 179, 272 | "comment_post_id": 34, 273 | "comment_text": { 274 | "String": "z5iF8zq1lM", 275 | "Valid": true 276 | } 277 | } 278 | ], 279 | "tags": [ 280 | { 281 | "tag_id": 3, 282 | "tag_name": "Dkh9h2fhfU" 283 | }, 284 | { 285 | "tag_id": 5, 286 | "tag_name": "UsaD6HEdz0" 287 | }, 288 | { 289 | "tag_id": 8, 290 | "tag_name": "hV3vC5AWX3" 291 | } 292 | ] 293 | }, 294 | { 295 | "post_id": 38, 296 | "post_blog_id": 1, 297 | "post_author_id": 4, 298 | "post_created_on": { 299 | "seconds": 1257811200 300 | }, 301 | "post_section": "woodworking", 302 | "post_subject": "GuidBS4Ya7", 303 | "draft": "08XNtldEp3", 304 | "post_body": "GJ2RU98Jas", 305 | "comments": [ 306 | { 307 | "comment_id": 52, 308 | "comment_post_id": 38, 309 | "comment_text": { 310 | "String": "PU4kzjuFy8", 311 | "Valid": true 312 | } 313 | }, 314 | { 315 | "comment_id": 76, 316 | "comment_post_id": 38, 317 | "comment_text": { 318 | "String": "cZfckdrUax", 319 | "Valid": true 320 | } 321 | }, 322 | { 323 | "comment_id": 104, 324 | "comment_post_id": 38, 325 | "comment_text": { 326 | "String": "AqOtcOjeS1", 327 | "Valid": true 328 | } 329 | }, 330 | { 331 | "comment_id": 163, 332 | "comment_post_id": 38, 333 | "comment_text": { 334 | "String": "Zf8TxfzIoz", 335 | "Valid": true 336 | } 337 | }, 338 | { 339 | "comment_id": 165, 340 | "comment_post_id": 38, 341 | "comment_text": { 342 | "String": "7ErECITW8C", 343 | "Valid": true 344 | } 345 | } 346 | ], 347 | "tags": [ 348 | { 349 | "tag_id": 1, 350 | "tag_name": "VuS9jZ8uVb" 351 | }, 352 | { 353 | "tag_id": 5, 354 | "tag_name": "UsaD6HEdz0" 355 | }, 356 | { 357 | "tag_id": 8, 358 | "tag_name": "hV3vC5AWX3" 359 | } 360 | ] 361 | } 362 | ] 363 | }, 364 | { 365 | "blog_id": 2, 366 | "blog_title": "s7fC7cqHmM", 367 | "author": { 368 | "author_id": 2, 369 | "author_username": "mxHsyzzBby", 370 | "author_password": "OonqVUSDK0", 371 | "author_email": "V6fFMTuPCd", 372 | "author_bio": "Wp9QfuWWig", 373 | "author_favourite_section": "painting" 374 | }, 375 | "posts": [ 376 | { 377 | "post_id": 6, 378 | "post_blog_id": 2, 379 | "post_author_id": 2, 380 | "post_created_on": { 381 | "seconds": 1257811200 382 | }, 383 | "post_section": "painting", 384 | "post_subject": "0foJgo85SO", 385 | "draft": "yfmQRChnyY", 386 | "post_body": "36eMXXI3TE", 387 | "comments": [ 388 | { 389 | "comment_id": 51, 390 | "comment_post_id": 6, 391 | "comment_text": { 392 | "String": "2pGsPo1YJF", 393 | "Valid": true 394 | } 395 | }, 396 | { 397 | "comment_id": 63, 398 | "comment_post_id": 6, 399 | "comment_text": { 400 | "String": "ChTHlOIgmu", 401 | "Valid": true 402 | } 403 | }, 404 | { 405 | "comment_id": 79, 406 | "comment_post_id": 6, 407 | "comment_text": { 408 | "String": "OPeSo21N1Z", 409 | "Valid": true 410 | } 411 | }, 412 | { 413 | "comment_id": 86, 414 | "comment_post_id": 6, 415 | "comment_text": { 416 | "String": "IAOoNW7pzZ", 417 | "Valid": true 418 | } 419 | }, 420 | { 421 | "comment_id": 127, 422 | "comment_post_id": 6, 423 | "comment_text": { 424 | "String": "gtpRYXuPoZ", 425 | "Valid": true 426 | } 427 | }, 428 | { 429 | "comment_id": 152, 430 | "comment_post_id": 6, 431 | "comment_text": { 432 | "String": "ti7KayVLUJ", 433 | "Valid": true 434 | } 435 | }, 436 | { 437 | "comment_id": 199, 438 | "comment_post_id": 6, 439 | "comment_text": { 440 | "String": "NElCnqsGwV", 441 | "Valid": true 442 | } 443 | } 444 | ], 445 | "tags": [ 446 | { 447 | "tag_id": 1, 448 | "tag_name": "VuS9jZ8uVb" 449 | }, 450 | { 451 | "tag_id": 5, 452 | "tag_name": "UsaD6HEdz0" 453 | }, 454 | { 455 | "tag_id": 6, 456 | "tag_name": "BpLnfgDsc2" 457 | } 458 | ] 459 | }, 460 | { 461 | "post_id": 7, 462 | "post_blog_id": 2, 463 | "post_author_id": 2, 464 | "post_created_on": { 465 | "seconds": 1257811200 466 | }, 467 | "post_section": "snowboarding", 468 | "post_subject": "jw5PTzmcl4", 469 | "draft": "mbM2jL9l1m", 470 | "post_body": "xRnPi6lgQR", 471 | "comments": [ 472 | { 473 | "comment_id": 50, 474 | "comment_post_id": 7, 475 | "comment_text": { 476 | "String": "cxYVH87sQw", 477 | "Valid": true 478 | } 479 | }, 480 | { 481 | "comment_id": 82, 482 | "comment_post_id": 7, 483 | "comment_text": { 484 | "String": "MKPOUNwak3", 485 | "Valid": true 486 | } 487 | }, 488 | { 489 | "comment_id": 132, 490 | "comment_post_id": 7, 491 | "comment_text": { 492 | "String": "XUnTUXERVA", 493 | "Valid": true 494 | } 495 | }, 496 | { 497 | "comment_id": 193, 498 | "comment_post_id": 7, 499 | "comment_text": { 500 | "String": "njpNGUDmRI", 501 | "Valid": true 502 | } 503 | }, 504 | { 505 | "comment_id": 197, 506 | "comment_post_id": 7, 507 | "comment_text": { 508 | "String": "vqFbEZsXci", 509 | "Valid": true 510 | } 511 | } 512 | ], 513 | "tags": [ 514 | { 515 | "tag_id": 1, 516 | "tag_name": "VuS9jZ8uVb" 517 | }, 518 | { 519 | "tag_id": 2, 520 | "tag_name": "5a84jjJkwz" 521 | }, 522 | { 523 | "tag_id": 7, 524 | "tag_name": "N95RxRTZHW" 525 | } 526 | ] 527 | }, 528 | { 529 | "post_id": 8, 530 | "post_blog_id": 2, 531 | "post_author_id": 2, 532 | "post_created_on": { 533 | "seconds": 1257811200 534 | }, 535 | "post_section": "woodworking", 536 | "post_subject": "9h6RCoNcOY", 537 | "draft": "oNU8FRDClA", 538 | "post_body": "8IR9hFKz7k", 539 | "comments": [ 540 | { 541 | "comment_id": 6, 542 | "comment_post_id": 8, 543 | "comment_text": { 544 | "String": "cMH6mf0aDd", 545 | "Valid": true 546 | } 547 | }, 548 | { 549 | "comment_id": 37, 550 | "comment_post_id": 8, 551 | "comment_text": { 552 | "String": "Ou92jYHQZL", 553 | "Valid": true 554 | } 555 | }, 556 | { 557 | "comment_id": 59, 558 | "comment_post_id": 8, 559 | "comment_text": { 560 | "String": "fN864O3ok5", 561 | "Valid": true 562 | } 563 | }, 564 | { 565 | "comment_id": 84, 566 | "comment_post_id": 8, 567 | "comment_text": { 568 | "String": "NNbekUZiSX", 569 | "Valid": true 570 | } 571 | }, 572 | { 573 | "comment_id": 108, 574 | "comment_post_id": 8, 575 | "comment_text": { 576 | "String": "uaHYToYjSq", 577 | "Valid": true 578 | } 579 | }, 580 | { 581 | "comment_id": 109, 582 | "comment_post_id": 8, 583 | "comment_text": { 584 | "String": "qm2fPPYAtC", 585 | "Valid": true 586 | } 587 | }, 588 | { 589 | "comment_id": 118, 590 | "comment_post_id": 8, 591 | "comment_text": { 592 | "String": "2Uzd3QDNZp", 593 | "Valid": true 594 | } 595 | } 596 | ], 597 | "tags": [ 598 | { 599 | "tag_id": 1, 600 | "tag_name": "VuS9jZ8uVb" 601 | }, 602 | { 603 | "tag_id": 3, 604 | "tag_name": "Dkh9h2fhfU" 605 | }, 606 | { 607 | "tag_id": 6, 608 | "tag_name": "BpLnfgDsc2" 609 | } 610 | ] 611 | }, 612 | { 613 | "post_id": 15, 614 | "post_blog_id": 2, 615 | "post_author_id": 2, 616 | "post_created_on": { 617 | "seconds": 1257811200 618 | }, 619 | "post_section": "woodworking", 620 | "post_subject": "fA3qYprioa", 621 | "draft": "VHOOn8HG6n", 622 | "post_body": "n5XJQNaQ0r", 623 | "comments": [ 624 | { 625 | "comment_id": 72, 626 | "comment_post_id": 15, 627 | "comment_text": { 628 | "String": "tV1hXuUp1D", 629 | "Valid": true 630 | } 631 | }, 632 | { 633 | "comment_id": 78, 634 | "comment_post_id": 15, 635 | "comment_text": { 636 | "String": "5Gh3UWXAZ8", 637 | "Valid": true 638 | } 639 | }, 640 | { 641 | "comment_id": 133, 642 | "comment_post_id": 15, 643 | "comment_text": { 644 | "String": "j2f8nAULf1", 645 | "Valid": true 646 | } 647 | } 648 | ], 649 | "tags": [ 650 | { 651 | "tag_name": "HciWvqZTa2" 652 | }, 653 | { 654 | "tag_id": 6, 655 | "tag_name": "BpLnfgDsc2" 656 | }, 657 | { 658 | "tag_id": 9, 659 | "tag_name": "9IVUWSP2Nc" 660 | } 661 | ] 662 | }, 663 | { 664 | "post_id": 25, 665 | "post_blog_id": 2, 666 | "post_author_id": 2, 667 | "post_created_on": { 668 | "seconds": 1257811200 669 | }, 670 | "post_section": "woodworking", 671 | "post_subject": "8xf3HZNWnW", 672 | "draft": "1Af4rjzAyB", 673 | "post_body": "Zl96c8YxYT", 674 | "comments": [ 675 | { 676 | "comment_id": 65, 677 | "comment_post_id": 25, 678 | "comment_text": { 679 | "String": "eHA3ZCgvXK", 680 | "Valid": true 681 | } 682 | }, 683 | { 684 | "comment_id": 122, 685 | "comment_post_id": 25, 686 | "comment_text": { 687 | "String": "8d63ycPwFJ", 688 | "Valid": true 689 | } 690 | }, 691 | { 692 | "comment_id": 138, 693 | "comment_post_id": 25, 694 | "comment_text": { 695 | "String": "uV9fHjxGUz", 696 | "Valid": true 697 | } 698 | }, 699 | { 700 | "comment_id": 155, 701 | "comment_post_id": 25, 702 | "comment_text": { 703 | "String": "bO6oWHkNug", 704 | "Valid": true 705 | } 706 | }, 707 | { 708 | "comment_id": 185, 709 | "comment_post_id": 25, 710 | "comment_text": { 711 | "String": "wcZZf2dUM9", 712 | "Valid": true 713 | } 714 | } 715 | ], 716 | "tags": [ 717 | { 718 | "tag_name": "HciWvqZTa2" 719 | }, 720 | { 721 | "tag_id": 3, 722 | "tag_name": "Dkh9h2fhfU" 723 | }, 724 | { 725 | "tag_id": 8, 726 | "tag_name": "hV3vC5AWX3" 727 | } 728 | ] 729 | } 730 | ] 731 | }, 732 | { 733 | "blog_id": 3, 734 | "blog_title": "Z6EQejXurS", 735 | "author": { 736 | "author_id": 6, 737 | "author_username": "ThbXfQ6pYS", 738 | "author_password": "Q3n267l1VQ", 739 | "author_email": "KGNbSuJE9f", 740 | "author_bio": "QbzONJAAwd", 741 | "author_favourite_section": "cooking" 742 | }, 743 | "posts": [ 744 | { 745 | "post_id": 3, 746 | "post_blog_id": 3, 747 | "post_author_id": 6, 748 | "post_created_on": { 749 | "seconds": 1257811200 750 | }, 751 | "post_section": "snowboarding", 752 | "post_subject": "luTrdPGr0I", 753 | "draft": "LCxY3PuTTP", 754 | "post_body": "0u5GDp25DG", 755 | "comments": [ 756 | { 757 | "comment_id": 15, 758 | "comment_post_id": 3, 759 | "comment_text": { 760 | "String": "0YSyNBiOW4", 761 | "Valid": true 762 | } 763 | }, 764 | { 765 | "comment_id": 46, 766 | "comment_post_id": 3, 767 | "comment_text": { 768 | "String": "tVA9ILnhud", 769 | "Valid": true 770 | } 771 | }, 772 | { 773 | "comment_id": 71, 774 | "comment_post_id": 3, 775 | "comment_text": { 776 | "String": "GeUMYPs1pp", 777 | "Valid": true 778 | } 779 | }, 780 | { 781 | "comment_id": 87, 782 | "comment_post_id": 3, 783 | "comment_text": { 784 | "String": "TUOHjEb8tz", 785 | "Valid": true 786 | } 787 | }, 788 | { 789 | "comment_id": 120, 790 | "comment_post_id": 3, 791 | "comment_text": { 792 | "String": "6YHEYJs7xV", 793 | "Valid": true 794 | } 795 | } 796 | ], 797 | "tags": [ 798 | { 799 | "tag_name": "HciWvqZTa2" 800 | }, 801 | { 802 | "tag_id": 3, 803 | "tag_name": "Dkh9h2fhfU" 804 | }, 805 | { 806 | "tag_id": 4, 807 | "tag_name": "WD8F2qNfHK" 808 | } 809 | ] 810 | }, 811 | { 812 | "post_id": 28, 813 | "post_blog_id": 3, 814 | "post_author_id": 6, 815 | "post_created_on": { 816 | "seconds": 1257811200 817 | }, 818 | "post_section": "snowboarding", 819 | "post_subject": "uGNUcNPovq", 820 | "draft": "xhUEe5qh4H", 821 | "post_body": "rUGhjFmkDY", 822 | "comments": [ 823 | { 824 | "comment_text": { 825 | "String": "", 826 | "Valid": false 827 | } 828 | } 829 | ], 830 | "tags": [ 831 | { 832 | "tag_id": 1, 833 | "tag_name": "VuS9jZ8uVb" 834 | }, 835 | { 836 | "tag_id": 5, 837 | "tag_name": "UsaD6HEdz0" 838 | }, 839 | { 840 | "tag_id": 6, 841 | "tag_name": "BpLnfgDsc2" 842 | } 843 | ] 844 | }, 845 | { 846 | "post_id": 30, 847 | "post_blog_id": 3, 848 | "post_author_id": 6, 849 | "post_created_on": { 850 | "seconds": 1257811200 851 | }, 852 | "post_section": "cooking", 853 | "post_subject": "D8aK5963nw", 854 | "draft": "Nn7L1XilVD", 855 | "post_body": "e57p6GnKO7", 856 | "comments": [ 857 | { 858 | "comment_id": 26, 859 | "comment_post_id": 30, 860 | "comment_text": { 861 | "String": "ngjpmispzD", 862 | "Valid": true 863 | } 864 | }, 865 | { 866 | "comment_id": 62, 867 | "comment_post_id": 30, 868 | "comment_text": { 869 | "String": "L1wiM6LRmA", 870 | "Valid": true 871 | } 872 | }, 873 | { 874 | "comment_id": 66, 875 | "comment_post_id": 30, 876 | "comment_text": { 877 | "String": "7AL9uROLSy", 878 | "Valid": true 879 | } 880 | } 881 | ], 882 | "tags": [ 883 | { 884 | "tag_name": "HciWvqZTa2" 885 | }, 886 | { 887 | "tag_id": 1, 888 | "tag_name": "VuS9jZ8uVb" 889 | }, 890 | { 891 | "tag_id": 3, 892 | "tag_name": "Dkh9h2fhfU" 893 | } 894 | ] 895 | }, 896 | { 897 | "post_id": 36, 898 | "post_blog_id": 3, 899 | "post_author_id": 6, 900 | "post_created_on": { 901 | "seconds": 1257811200 902 | }, 903 | "post_section": "woodworking", 904 | "post_subject": "Zjb0uDSal9", 905 | "draft": "u7OkPmE7Kd", 906 | "post_body": "hZp0lA3Bav", 907 | "comments": [ 908 | { 909 | "comment_id": 3, 910 | "comment_post_id": 36, 911 | "comment_text": { 912 | "String": "LwyTM7D4Uf", 913 | "Valid": true 914 | } 915 | }, 916 | { 917 | "comment_id": 18, 918 | "comment_post_id": 36, 919 | "comment_text": { 920 | "String": "aBUibyJ1O0", 921 | "Valid": true 922 | } 923 | }, 924 | { 925 | "comment_id": 35, 926 | "comment_post_id": 36, 927 | "comment_text": { 928 | "String": "EsVg6gZqzY", 929 | "Valid": true 930 | } 931 | }, 932 | { 933 | "comment_id": 139, 934 | "comment_post_id": 36, 935 | "comment_text": { 936 | "String": "R9KTZ604oo", 937 | "Valid": true 938 | } 939 | }, 940 | { 941 | "comment_id": 153, 942 | "comment_post_id": 36, 943 | "comment_text": { 944 | "String": "y5K5pHxs5M", 945 | "Valid": true 946 | } 947 | }, 948 | { 949 | "comment_id": 168, 950 | "comment_post_id": 36, 951 | "comment_text": { 952 | "String": "Yaty4uqMgN", 953 | "Valid": true 954 | } 955 | }, 956 | { 957 | "comment_id": 182, 958 | "comment_post_id": 36, 959 | "comment_text": { 960 | "String": "KGBwl1bBQ9", 961 | "Valid": true 962 | } 963 | } 964 | ], 965 | "tags": [ 966 | { 967 | "tag_id": 2, 968 | "tag_name": "5a84jjJkwz" 969 | }, 970 | { 971 | "tag_id": 5, 972 | "tag_name": "UsaD6HEdz0" 973 | }, 974 | { 975 | "tag_id": 6, 976 | "tag_name": "BpLnfgDsc2" 977 | } 978 | ] 979 | } 980 | ] 981 | } 982 | ], 983 | "TestNotNull": [ 984 | { 985 | "bool": true, 986 | "bool2": { 987 | "Bool": true, 988 | "Valid": true 989 | }, 990 | "time": "2006-01-02T15:04:05Z", 991 | "time2": { 992 | "Time": "2006-01-02T15:04:05Z", 993 | "Valid": true 994 | }, 995 | "timestamp": { 996 | "seconds": 1136160000 997 | }, 998 | "string": "1", 999 | "string2": { 1000 | "String": "1", 1001 | "Valid": true 1002 | }, 1003 | "float32": 1, 1004 | "float64": 1, 1005 | "float642": { 1006 | "Float64": 1, 1007 | "Valid": true 1008 | }, 1009 | "int": 1, 1010 | "int32": 1, 1011 | "int322": { 1012 | "Int32": 1, 1013 | "Valid": true 1014 | }, 1015 | "int64": 1, 1016 | "int642": { 1017 | "Int64": 1, 1018 | "Valid": true 1019 | }, 1020 | "uint": 1, 1021 | "uint32": 1, 1022 | "uint64": 1 1023 | } 1024 | ], 1025 | "TestNull": [ 1026 | { 1027 | "bool2": { 1028 | "Bool": false, 1029 | "Valid": false 1030 | }, 1031 | "time2": { 1032 | "Time": "0001-01-01T00:00:00Z", 1033 | "Valid": false 1034 | }, 1035 | "string2": { 1036 | "String": "", 1037 | "Valid": false 1038 | }, 1039 | "float642": { 1040 | "Float64": 0, 1041 | "Valid": false 1042 | }, 1043 | "int322": { 1044 | "Int32": 0, 1045 | "Valid": false 1046 | }, 1047 | "int642": { 1048 | "Int64": 0, 1049 | "Valid": false 1050 | } 1051 | } 1052 | ], 1053 | "TestPGTypes": [ 1054 | { 1055 | "bigint": 1, 1056 | "bit": "1", 1057 | "boolean": true, 1058 | "character": "a", 1059 | "character_varying": "a", 1060 | "cidr": "1.1.1.1/32", 1061 | "date": "2004-10-19T00:00:00Z", 1062 | "double_precision": 1, 1063 | "integer": 1, 1064 | "numeric": 1, 1065 | "oid": 1, 1066 | "real": 1, 1067 | "smallint": 1, 1068 | "text": "a", 1069 | "timestamp_without_time_zone": "2004-10-19T10:23:54Z", 1070 | "timestamp_with_time_zone": "2004-10-19T10:23:54Z", 1071 | "time_without_time_zone": "0000-01-01T04:05:06Z", 1072 | "time_with_time_zone": "0000-01-01T04:05:06-08:00", 1073 | "uuid": "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11", 1074 | "xml": "a" 1075 | } 1076 | ], 1077 | "TestRelation": [ 1078 | { 1079 | "id": 1, 1080 | "basic_submap": [ 1081 | { 1082 | "Int64": 2, 1083 | "Valid": true 1084 | }, 1085 | { 1086 | "Int64": 3, 1087 | "Valid": true 1088 | }, 1089 | { 1090 | "Int64": 4, 1091 | "Valid": true 1092 | } 1093 | ], 1094 | "basic_submap_ptr": [ 1095 | { 1096 | "Int64": 3, 1097 | "Valid": true 1098 | }, 1099 | { 1100 | "Int64": 4, 1101 | "Valid": true 1102 | }, 1103 | { 1104 | "Int64": 5, 1105 | "Valid": true 1106 | } 1107 | ], 1108 | "basic_submap_ptr_ptr": [ 1109 | 4, 1110 | 5, 1111 | 6 1112 | ], 1113 | "submap": [ 1114 | { 1115 | "sample_submap": 5 1116 | }, 1117 | { 1118 | "sample_submap": 6 1119 | }, 1120 | { 1121 | "sample_submap": 7 1122 | } 1123 | ], 1124 | "submap_ptr": [ 1125 | { 1126 | "sample_submap": 6 1127 | }, 1128 | { 1129 | "sample_submap": 7 1130 | }, 1131 | { 1132 | "sample_submap": 8 1133 | } 1134 | ], 1135 | "submap_ptr_ptr": [ 1136 | { 1137 | "sample_submap": 7 1138 | }, 1139 | { 1140 | "sample_submap": 8 1141 | }, 1142 | { 1143 | "sample_submap": 9 1144 | } 1145 | ] 1146 | }, 1147 | { 1148 | "id": 2, 1149 | "basic_submap": [ 1150 | { 1151 | "Int64": 2, 1152 | "Valid": true 1153 | }, 1154 | { 1155 | "Int64": 3, 1156 | "Valid": true 1157 | }, 1158 | { 1159 | "Int64": 4, 1160 | "Valid": true 1161 | } 1162 | ], 1163 | "basic_submap_ptr": [ 1164 | { 1165 | "Int64": 3, 1166 | "Valid": true 1167 | }, 1168 | { 1169 | "Int64": 4, 1170 | "Valid": true 1171 | }, 1172 | { 1173 | "Int64": 5, 1174 | "Valid": true 1175 | } 1176 | ], 1177 | "basic_submap_ptr_ptr": [ 1178 | 4, 1179 | 5, 1180 | 6 1181 | ], 1182 | "submap": [ 1183 | { 1184 | "sample_submap": 5 1185 | }, 1186 | { 1187 | "sample_submap": 6 1188 | }, 1189 | { 1190 | "sample_submap": 7 1191 | } 1192 | ], 1193 | "submap_ptr": [ 1194 | { 1195 | "sample_submap": 6 1196 | }, 1197 | { 1198 | "sample_submap": 7 1199 | }, 1200 | { 1201 | "sample_submap": 8 1202 | } 1203 | ], 1204 | "submap_ptr_ptr": [ 1205 | { 1206 | "sample_submap": 7 1207 | }, 1208 | { 1209 | "sample_submap": 8 1210 | }, 1211 | { 1212 | "sample_submap": 9 1213 | } 1214 | ] 1215 | } 1216 | ] 1217 | } -------------------------------------------------------------------------------- /testdata/null_sql.go: -------------------------------------------------------------------------------- 1 | package testdata 2 | 3 | import ( 4 | "database/sql" 5 | "time" 6 | 7 | "github.com/golang/protobuf/ptypes/timestamp" 8 | ) 9 | 10 | type NullTest struct { 11 | Bool *bool `db:"bool" json:"bool,omitempty"` 12 | Bool2 sql.NullBool `db:"bool2" json:"bool2,omitempty"` 13 | Time *time.Time `db:"time" json:"time,omitempty"` 14 | Time2 sql.NullTime `db:"time2" json:"time2,omitempty"` 15 | Timestamp *timestamp.Timestamp `db:"timestamp" json:"timestamp,omitempty"` 16 | String *string `db:"string" json:"string,omitempty"` 17 | String2 sql.NullString `db:"string2" json:"string2,omitempty"` 18 | Float32 *float32 `db:"float32" json:"float32,omitempty"` 19 | Float64 *float64 `db:"float64" json:"float64,omitempty"` 20 | Float642 sql.NullFloat64 `db:"float642" json:"float642,omitempty"` 21 | Int *int `db:"int" json:"int,omitempty"` 22 | Int32 *int32 `db:"int32" json:"int32,omitempty"` 23 | Int322 sql.NullInt32 `db:"int322" json:"int322,omitempty"` 24 | Int64 *int64 `db:"int64" json:"int64,omitempty"` 25 | Int642 sql.NullInt64 `db:"int642" json:"int642,omitempty"` 26 | Uint *uint `db:"uint" json:"uint,omitempty"` 27 | Uint32 *uint32 `db:"uint32" json:"uint32,omitempty"` 28 | Uint64 *uint64 `db:"uint64" json:"uint64,omitempty"` 29 | } 30 | 31 | var NullQueryPG = ` 32 | select 33 | CAST ( null AS bool ) as "bool", 34 | CAST ( null AS bool ) as "bool2", 35 | CAST ( null AS time ) as "time", 36 | CAST ( null AS time ) as "time2", 37 | CAST ( null AS time ) as "timestamp", 38 | CAST ( null AS text ) as "string", 39 | CAST ( null AS text ) as "string2", 40 | CAST ( null AS real) as "float32", 41 | CAST ( null AS double precision ) as "float64", 42 | CAST ( null AS double precision ) as "float642", 43 | CAST ( null AS integer ) as "int", 44 | CAST ( null AS integer ) as "int32", 45 | CAST ( null AS integer ) as "int322", 46 | CAST ( null AS bigint ) as "int64", 47 | CAST ( null AS bigint ) as "int642", 48 | CAST ( null AS integer ) as "uint", 49 | CAST ( null AS integer ) as "uint32", 50 | CAST ( null AS bigint ) as "uint64" 51 | ` 52 | 53 | var NotNullQueryPG = ` 54 | select 55 | CAST ( 1 AS bool ) as "bool", 56 | CAST ( 1 AS bool ) as "bool2", 57 | CAST ( '2006-01-02T15:04:05Z07:00' AS timestamp ) as "time", 58 | CAST ( '2006-01-02T15:04:05Z07:00' AS timestamp ) as "time2", 59 | CAST ( '2006-01-02T15:04:05Z07:00' AS date ) as "timestamp", 60 | CAST ( 1 AS text ) as "string", 61 | CAST ( 1 AS text ) as "string2", 62 | CAST ( 1 AS real) as "float32", 63 | CAST ( 1 AS double precision ) as "float64", 64 | CAST ( 1 AS double precision ) as "float642", 65 | CAST ( 1 AS integer ) as "int", 66 | CAST ( 1 AS integer ) as "int32", 67 | CAST ( 1 AS integer ) as "int322", 68 | CAST ( 1 AS bigint ) as "int64", 69 | CAST ( 1 AS bigint ) as "int642", 70 | CAST ( 1 AS integer ) as "uint", 71 | CAST ( 1 AS integer ) as "uint32", 72 | CAST ( 1 AS bigint ) as "uint64" 73 | ` 74 | 75 | var NullQueryMySql = ` 76 | select 77 | nullbool as "bool", 78 | nullbool as "bool2", 79 | CAST( null AS DATETIME ) as "time", 80 | CAST( null AS DATETIME ) as "time2", 81 | CAST( null AS DATE ) as "timestamp", 82 | CAST( null AS CHAR ) as "string", 83 | CAST( null AS CHAR ) as "string2", 84 | CAST( null AS FLOAT(32)) as "float32", 85 | CAST( null AS DECIMAL(64)) as "float64", 86 | CAST( null AS DECIMAL(64)) as "float642", 87 | CAST( null AS UNSIGNED ) as "int", 88 | CAST( null AS UNSIGNED ) as "int32", 89 | CAST( null AS UNSIGNED ) as "int322", 90 | CAST( null AS UNSIGNED ) as "int64", 91 | CAST( null AS UNSIGNED ) as "int642", 92 | CAST( null AS UNSIGNED ) as "uint", 93 | CAST( null AS UNSIGNED ) as "uint32", 94 | CAST( null AS UNSIGNED ) as "uint64" 95 | from nullbool 96 | limit 1 97 | ` 98 | 99 | var NotNullQueryMySQL = ` 100 | select 101 | true as "bool", 102 | true as "bool2", 103 | CAST( '2006-01-02T15:04:05Z07:00' AS DATETIME ) as "time", 104 | CAST( '2006-01-02T15:04:05Z07:00' AS DATETIME ) as "time2", 105 | CAST( '2006-01-02T15:04:05Z07:00' AS DATE ) as "timestamp", 106 | CAST( 1 AS CHAR ) as "string", 107 | CAST( 1 AS CHAR ) as "string2", 108 | CAST( 1 AS float(32) ) as "float32", 109 | CAST( 1 AS decimal(64) ) as "float64", 110 | CAST( 1 AS decimal(64) ) as "float642", 111 | CAST( 1 AS UNSIGNED ) as "int", 112 | CAST( 1 AS UNSIGNED ) as "int32", 113 | CAST( 1 AS UNSIGNED ) as "int322", 114 | CAST( 1 AS UNSIGNED ) as "int64", 115 | CAST( 1 AS UNSIGNED ) as "int642", 116 | CAST( 1 AS UNSIGNED ) as "uint", 117 | CAST( 1 AS UNSIGNED ) as "uint32", 118 | CAST( 1 AS UNSIGNED ) as "uint64" 119 | ` 120 | -------------------------------------------------------------------------------- /testdata/pg_dtypes.go: -------------------------------------------------------------------------------- 1 | package testdata 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | var PGDTypesQuery = ` 8 | select 9 | cast ( 1 as bigint ) as "bigint", --- "~18 digit integer, 8-byte storage" 10 | cast ( 1 as bit ) as "bit", --- fixed-length bit string 11 | cast ( 1 as boolean ) as "boolean", --- "boolean, 'true'/'false'" 12 | cast ( 'a' as character ) as "character", --- "char(length ) , blank-padded string, fixed storage length" 13 | cast ( 'a' as character varying ) as "character_varying", --- "varchar(length ) , non-blank-padded string, variable storage length" 14 | cast ( '1.1.1.1' as cidr ) as "cidr", --- "network IP address/netmask, network address" 15 | cast ( '2004-10-19' as date ) as "date", --- date 16 | cast (1 as double precision ) as "double_precision", --- "double-precision floating point number, 8-byte storage" 17 | cast ( 1 as integer ) as "integer", --- "-2 billion to 2 billion integer, 4-byte storage" 18 | cast ( 1 as numeric ) as "numeric", --- "numeric(precision, decimal ) , arbitrary precision number" 19 | cast ( 1 as oid ) as "oid", --- "object identifier(oid ) , maximum 4 billion" 20 | cast ( 1 as real ) as "real", --- "single-precision floating point number, 4-byte storage" 21 | cast ( 1 as smallint ) as "smallint", --- "-32 thousand to 32 thousand, 2-byte storage" 22 | cast ( 'a' as text ) as "text", --- "variable-length string, no limit specified" 23 | cast (TIMESTAMP '2004-10-19 10:23:54' as timestamp without time zone ) as "timestamp_without_time_zone", --- date and time 24 | cast ( TIMESTAMP '2004-10-19 10:23:54+02' as timestamp with time zone ) as "timestamp_with_time_zone", --- date and time with time zone 25 | cast ( '04:05:06' as time without time zone ) as "time_without_time_zone", --- time of day 26 | cast ( '04:05:06 PST' as time with time zone ) as "time_with_time_zone", --- time of day with time zone 27 | cast ( 'A0EEBC99-9C0B-4EF8-BB6D-6BB9BD380A11' as uuid ) as "uuid", --- UUID datatype 28 | cast ( 'a' as xml ) as "xml" --- XML content 29 | --- cast ( 1 as bit varying ) as "bit_varying", --- variable-length bit string 30 | --- cast ( as tstzrange ) as "tstzrange", --- range of timestamps with time zone 31 | --- cast ( as numrange ) as "numrange", --- range of numerics 32 | --- cast ( as inet ) as "inet", --- "IP address/netmask, host address, netmask optional" 33 | --- cast ( as int4range ) as "int4range", --- range of integers 34 | --- cast ( as int8range ) as "int8range", --- range of bigints 35 | --- cast ( 1 as cid ) as "cid", --- "command identifier type, sequence in transaction id" 36 | --- cast ( as daterange ) as "daterange", --- range of dates 37 | ` 38 | 39 | type PGDTypes struct { 40 | Bigint int `db:"bigint" json:"bigint,omitempty"` 41 | Bit string `db:"bit" json:"bit,omitempty"` 42 | Boolean bool `db:"boolean" json:"boolean,omitempty"` 43 | Character string `db:"character" json:"character,omitempty"` 44 | CharacterVarying string `db:"character_varying" json:"character_varying,omitempty"` 45 | Cidr string `db:"cidr" json:"cidr,omitempty"` 46 | Date time.Time `db:"date" json:"date,omitempty"` 47 | DoublePrecision float64 `db:"double_precision" json:"double_precision,omitempty"` 48 | Integer int `db:"integer" json:"integer,omitempty"` 49 | Numeric int `db:"numeric" json:"numeric,omitempty"` 50 | Oid int `db:"oid" json:"oid,omitempty"` 51 | Real float32 `db:"real" json:"real,omitempty"` 52 | Smallint int `db:"smallint" json:"smallint,omitempty"` 53 | Text string `db:"text" json:"text,omitempty"` 54 | TimestampWithoutTimeZone time.Time `db:"timestamp_without_time_zone" json:"timestamp_without_time_zone,omitempty"` 55 | TimestampWithTimeZone time.Time `db:"timestamp_with_time_zone" json:"timestamp_with_time_zone,omitempty"` 56 | TimeWithoutTimeZone time.Time `db:"time_without_time_zone" json:"time_without_time_zone,omitempty"` 57 | TimeWithTimeZone time.Time `db:"time_with_time_zone" json:"time_with_time_zone,omitempty"` 58 | Uuid string `db:"uuid" json:"uuid,omitempty"` 59 | Xml string `db:"xml" json:"xml,omitempty"` 60 | } 61 | -------------------------------------------------------------------------------- /testdata/relations_sql.go: -------------------------------------------------------------------------------- 1 | package testdata 2 | 3 | import ( 4 | "database/sql" 5 | ) 6 | 7 | type RelationTest struct { 8 | Id int `db:"id" json:"id,omitempty"` 9 | BasicSubMap []sql.NullInt64 `db:"basic_submap" json:"basic_submap,omitempty"` 10 | BasicSubMapPtr *[]sql.NullInt64 `db:"basic_submap_ptr" json:"basic_submap_ptr,omitempty"` 11 | BasicSubMapPtrPtr *[]*int64 `db:"basic_submap_ptr_ptr" json:"basic_submap_ptr_ptr,omitempty"` 12 | 13 | SubMap []SampleSubmap `db:"submap" json:"submap,omitempty"` 14 | SubMapPtr *[]SampleSubmap `db:"submap_ptr" json:"submap_ptr,omitempty"` 15 | SubMapPtrPtr *[]*SampleSubmap `db:"submap_ptr_ptr" json:"submap_ptr_ptr,omitempty"` 16 | } 17 | 18 | type SampleSubmap struct { 19 | SubId int `db:"sample_submap" json:"sample_submap,omitempty"` 20 | } 21 | 22 | var RelationTestQuery = ` 23 | select * from relations order by id, basic_submap, basic_submap_ptr, basic_submap_ptr_ptr, submap_sample_submap, submap_ptr_sample_submap, submap_ptr_ptr_sample_submap; 24 | ` 25 | -------------------------------------------------------------------------------- /testdata/testdata.go: -------------------------------------------------------------------------------- 1 | package testdata 2 | 3 | import ( 4 | "database/sql" 5 | "log" 6 | "math/rand" 7 | "time" 8 | 9 | "github.com/golang/protobuf/ptypes" 10 | timestamppb "github.com/golang/protobuf/ptypes/timestamp" 11 | "github.com/jackskj/carta/testdata/initdb" 12 | 13 | // default deivers 14 | _ "github.com/go-sql-driver/mysql" 15 | _ "github.com/lib/pq" 16 | ) 17 | 18 | const ( 19 | chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" 20 | iLength = 10 21 | ) 22 | 23 | var ( 24 | seed = rand.New(rand.NewSource(1)) 25 | tsSample, _ = ptypes.TimestampProto(time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC)) 26 | blogs = rand.Perm(iLength) 27 | authors = rand.Perm(iLength) 28 | posts = rand.Perm(40) 29 | comments = rand.Perm(200) 30 | tags = rand.Perm(iLength) 31 | blogAuthor map[int]int 32 | blogPost map[int][]int 33 | postAuthor map[int]int 34 | postBlog map[int]int 35 | postComment map[int][]int 36 | commentPost map[int]int 37 | postTag [][2]int 38 | ) 39 | 40 | type Requests struct { 41 | InsertAuthorRequests []*initdb.InsertAuthorRequest 42 | InsertBlogRequests []*initdb.InsertBlogRequest 43 | InsertCommentRequests []*initdb.InsertCommentRequest 44 | InsertPostRequests []*initdb.InsertPostRequest 45 | InsertPostTagRequests []*initdb.InsertPostTagRequest 46 | InsertTagRequests []*initdb.InsertTagRequest 47 | } 48 | 49 | func lorem() string { 50 | length := 10 51 | bytes := make([]byte, length) 52 | for i := range bytes { 53 | bytes[i] = chars[seed.Intn(len(chars))] 54 | } 55 | return string(bytes) 56 | } 57 | 58 | func GenerateRequests() *Requests { 59 | blogAuthor = make(map[int]int) 60 | for i, b := range blogs { 61 | blogAuthor[i] = b 62 | } 63 | blogPost = make(map[int][]int) 64 | postAuthor = make(map[int]int) 65 | postBlog = make(map[int]int) 66 | for _, post := range posts { 67 | randN := rand.Intn(iLength) 68 | blogPost[randN] = append(blogPost[randN], post) 69 | postBlog[post] = randN 70 | postAuthor[post] = blogAuthor[randN] 71 | } 72 | postComment = make(map[int][]int) 73 | commentPost = make(map[int]int) 74 | for _, comment := range comments { 75 | randN := rand.Intn(len(posts)) 76 | postComment[randN] = append(postComment[randN], comment) 77 | commentPost[comment] = randN 78 | } 79 | for i := 0; i < len(posts); i++ { 80 | t := rand.Perm(len(tags)) 81 | for j := 0; j < 3; j++ { 82 | postTag = append(postTag, [2]int{i, t[j]}) 83 | } 84 | } 85 | 86 | requests := Requests{ 87 | InsertAuthorRequests: []*initdb.InsertAuthorRequest{}, 88 | InsertBlogRequests: []*initdb.InsertBlogRequest{}, 89 | InsertCommentRequests: []*initdb.InsertCommentRequest{}, 90 | InsertPostRequests: []*initdb.InsertPostRequest{}, 91 | InsertPostTagRequests: []*initdb.InsertPostTagRequest{}, 92 | InsertTagRequests: []*initdb.InsertTagRequest{}, 93 | } 94 | for _, i := range tags { 95 | requests.InsertTagRequests = append( 96 | requests.InsertTagRequests, 97 | &initdb.InsertTagRequest{ 98 | Id: uint32(i), 99 | Name: lorem(), 100 | }, 101 | ) 102 | } 103 | for _, i := range authors { 104 | requests.InsertAuthorRequests = append( 105 | requests.InsertAuthorRequests, 106 | &initdb.InsertAuthorRequest{ 107 | Id: uint32(i), 108 | Username: lorem(), 109 | Password: lorem(), 110 | Email: lorem(), 111 | Bio: lorem(), 112 | FavouriteSection: getSection(), 113 | }, 114 | ) 115 | } 116 | for _, i := range postTag { 117 | requests.InsertPostTagRequests = append( 118 | requests.InsertPostTagRequests, 119 | &initdb.InsertPostTagRequest{ 120 | PostId: uint32(i[0]), 121 | TagId: uint32(i[1]), 122 | }, 123 | ) 124 | } 125 | for _, i := range comments { 126 | requests.InsertCommentRequests = append( 127 | requests.InsertCommentRequests, 128 | &initdb.InsertCommentRequest{ 129 | Id: uint32(i), 130 | PostId: uint32(commentPost[i]), 131 | Name: lorem(), 132 | Comment: lorem(), 133 | }, 134 | ) 135 | } 136 | 137 | for _, i := range posts { 138 | requests.InsertPostRequests = append( 139 | requests.InsertPostRequests, 140 | &initdb.InsertPostRequest{ 141 | Id: uint32(i), 142 | AuthorId: uint32(postAuthor[i]), 143 | BlogId: uint32(postBlog[i]), 144 | CreatedOn: tsSample, 145 | Section: getSection(), 146 | Subject: lorem(), 147 | Draft: lorem(), 148 | Body: lorem(), 149 | }, 150 | ) 151 | } 152 | 153 | for _, i := range blogs { 154 | requests.InsertBlogRequests = append( 155 | requests.InsertBlogRequests, 156 | &initdb.InsertBlogRequest{ 157 | Id: uint32(i), 158 | Title: lorem(), 159 | AuthorId: uint32(blogAuthor[i]), 160 | }, 161 | ) 162 | } 163 | return &requests 164 | } 165 | 166 | func getSection() string { 167 | sections := []string{ 168 | "cooking", 169 | "painting", 170 | "woodworking", 171 | "snowboarding", 172 | } 173 | return sections[rand.Intn(len(sections))] 174 | } 175 | 176 | func GetPG() *sql.DB { 177 | connStr := "postgres://postgres@localhost:5432/postgres?sslmode=disable" 178 | db, err := sql.Open("postgres", connStr) 179 | if err != nil || db.Ping() != nil { 180 | log.Println("Cannot connect to testing database, \n" + 181 | "to run local postgres testing DB, run \"docker run --env POSTGRES_HOST_AUTH_METHOD=trust -d -p 5432:5432 postgres\"") 182 | log.Fatal(err) 183 | } 184 | return db 185 | } 186 | 187 | func GetMySql() *sql.DB { 188 | connStr := "root@/mysql?multiStatements=true&parseTime=true" 189 | db, err := sql.Open("mysql", connStr) 190 | if err != nil || db.Ping() != nil { 191 | log.Println("Cannot connect to testing database, \n" + 192 | "to run local postgres testing DB, run \"docker run --name carta-mysql-test -d --env MYSQL_ALLOW_EMPTY_PASSWORD=yes --env MYSQL_DATABASE=mysql -p 3306:3306 mysql\"") 193 | log.Fatal(err) 194 | } 195 | return db 196 | } 197 | 198 | func GetSampleTS() timestamppb.Timestamp { 199 | return *tsSample 200 | } 201 | -------------------------------------------------------------------------------- /value/cell.go: -------------------------------------------------------------------------------- 1 | package value 2 | 3 | import ( 4 | "database/sql" 5 | "errors" 6 | "fmt" 7 | "log" 8 | "math" 9 | "reflect" 10 | "strconv" 11 | "time" 12 | 13 | "github.com/golang/protobuf/ptypes" 14 | "github.com/golang/protobuf/ptypes/timestamp" 15 | ) 16 | 17 | // TODO: timestamp/time/ from string 18 | // TODO: int/float/uint/bool from string 19 | 20 | type Cell struct { 21 | kind reflect.Kind // data type with which Cell will be instantiated 22 | bits uint64 //IEEE 754 binary representation of numeric value 23 | text string // non-numeric data as bytes for data which arrives as string or []byte 24 | time time.Time // any data that arrives as time, that includes timestame w/ or w/o zone 25 | colTypName string // Used for parting if some data arrices in plain text format, ex, if time arrives as string 26 | valid bool 27 | } 28 | 29 | func OverflowErr(i interface{}, typ reflect.Type) error { 30 | return fmt.Errorf("carta: value %v overflows %v", i, typ) 31 | } 32 | 33 | func ConvertsionError(convErr error, typ reflect.Type) error { 34 | return fmt.Errorf("carta: errors converting to %v: "+convErr.Error(), typ) 35 | } 36 | 37 | func NewCell(colTypName string) *Cell { 38 | return &Cell{colTypName: colTypName} 39 | } 40 | 41 | // implements database/sql scan interface 42 | func (c *Cell) Scan(src interface{}) error { 43 | switch src.(type) { 44 | case int64: 45 | c.SetInt64(src.(int64)) 46 | case float64: 47 | c.SetFloat64(src.(float64)) 48 | case bool: 49 | c.SetBool(src.(bool)) 50 | case []byte: 51 | c.SetString(string(src.([]byte))) 52 | case string: 53 | c.SetString(src.(string)) 54 | case time.Time: 55 | c.SetTime(src.(time.Time)) 56 | default: 57 | // src is nil 58 | c.SetNull() 59 | } 60 | return nil 61 | } 62 | 63 | func (c *Cell) SetBool(d bool) { 64 | c.kind = reflect.Bool 65 | c.valid = true 66 | if d { 67 | c.bits = 1 68 | } else { 69 | c.bits = 0 70 | } 71 | } 72 | 73 | func (c *Cell) SetFloat64(d float64) { 74 | c.kind = reflect.Float64 75 | c.valid = true 76 | c.bits = math.Float64bits(d) 77 | } 78 | 79 | func (c *Cell) SetInt64(d int64) { 80 | c.kind = reflect.Float64 81 | c.valid = true 82 | c.bits = uint64(d) 83 | } 84 | 85 | func (c *Cell) SetString(d string) { 86 | c.kind = reflect.String 87 | c.valid = true 88 | c.text = d 89 | } 90 | 91 | func (c *Cell) SetTime(d time.Time) { 92 | c.kind = reflect.Struct 93 | c.valid = true 94 | c.time = d 95 | } 96 | 97 | func (c *Cell) SetNull() { 98 | c.valid = false 99 | } 100 | 101 | func (c Cell) Kind() reflect.Kind { 102 | return c.kind 103 | } 104 | 105 | func (c Cell) IsNull() bool { 106 | return !c.valid 107 | } 108 | 109 | func (c Cell) IsValid() bool { 110 | return c.valid 111 | } 112 | 113 | func (c Cell) Bool() (bool, error) { 114 | return (c.bits != 0), nil 115 | } 116 | 117 | func (c Cell) Int32() (int32, error) { 118 | if c.kind == reflect.String { 119 | if num, err := strconv.ParseInt(c.text, 10, 32); err != nil { 120 | return 0, err 121 | } else { 122 | return int32(num), nil 123 | } 124 | } 125 | return int32(c.bits), nil 126 | } 127 | 128 | func (c Cell) Int64() (int64, error) { 129 | if c.kind == reflect.String { 130 | if num, err := strconv.ParseInt(c.text, 10, 64); err != nil { 131 | return 0, err 132 | } else { 133 | return int64(num), nil 134 | } 135 | } 136 | return int64(c.bits), nil 137 | } 138 | 139 | func (c Cell) Uint32() (uint32, error) { 140 | if c.kind == reflect.String { 141 | if num, err := strconv.ParseUint(c.text, 10, 32); err != nil { 142 | return 0, err 143 | } else { 144 | return uint32(num), nil 145 | } 146 | } 147 | return uint32(c.bits), nil 148 | } 149 | 150 | func (c Cell) Uint64() (uint64, error) { 151 | if c.kind == reflect.String { 152 | if num, err := strconv.ParseUint(c.text, 10, 64); err != nil { 153 | return 0, err 154 | } else { 155 | return uint64(num), nil 156 | } 157 | } 158 | return c.bits, nil 159 | } 160 | 161 | func (c Cell) Float32() (float32, error) { 162 | if c.kind == reflect.String { 163 | if num, err := strconv.ParseFloat(c.text, 32); err != nil { 164 | return 0, err 165 | } else { 166 | return float32(num), nil 167 | } 168 | } 169 | return math.Float32frombits(uint32(c.bits)), nil 170 | } 171 | 172 | func (c Cell) Float64() (float64, error) { 173 | if c.kind == reflect.String { 174 | if num, err := strconv.ParseFloat(c.text, 64); err != nil { 175 | return 0, err 176 | } else { 177 | return num, nil 178 | } 179 | } 180 | return math.Float64frombits(c.bits), nil 181 | } 182 | 183 | func (c Cell) String() (string, error) { 184 | return c.text, nil 185 | } 186 | 187 | func (c Cell) Time() (time.Time, error) { 188 | if c.kind == reflect.String { 189 | // TODO: Parse from string 190 | // switch c.colTypName { 191 | // } 192 | log.Println(c.text, " >aslkdfjaslkfj") 193 | return time.Time{}, errors.New("cannot convert time data which arrived as string or []uint8 from sql") 194 | } 195 | return c.time, nil 196 | } 197 | 198 | func (c Cell) Timestamp() (timestamp.Timestamp, error) { 199 | var t time.Time 200 | var err error 201 | if t, err = c.Time(); err != nil { 202 | return timestamp.Timestamp{}, err 203 | } 204 | if ts, err := ptypes.TimestampProto(t); err != nil { 205 | return timestamp.Timestamp{}, err 206 | } else { 207 | return *ts, nil 208 | } 209 | } 210 | 211 | func (c Cell) NullBool() (sql.NullBool, error) { 212 | if !c.valid { 213 | return sql.NullBool{}, nil 214 | } 215 | d, err := c.Bool() 216 | return sql.NullBool{ 217 | Bool: d, 218 | Valid: true, 219 | }, err 220 | } 221 | 222 | func (c Cell) NullFloat64() (sql.NullFloat64, error) { 223 | if !c.valid { 224 | return sql.NullFloat64{}, nil 225 | } 226 | d, err := c.Float64() 227 | return sql.NullFloat64{ 228 | Float64: d, 229 | Valid: true, 230 | }, err 231 | } 232 | 233 | func (c Cell) NullInt32() (sql.NullInt32, error) { 234 | if !c.valid { 235 | return sql.NullInt32{}, nil 236 | } 237 | d, err := c.Int32() 238 | return sql.NullInt32{ 239 | Int32: d, 240 | Valid: true, 241 | }, err 242 | } 243 | 244 | func (c Cell) NullInt64() (sql.NullInt64, error) { 245 | if !c.valid { 246 | return sql.NullInt64{}, nil 247 | } 248 | d, err := c.Int64() 249 | return sql.NullInt64{ 250 | Int64: d, 251 | Valid: true, 252 | }, err 253 | } 254 | 255 | func (c Cell) NullString() (sql.NullString, error) { 256 | if !c.valid { 257 | return sql.NullString{}, nil 258 | } 259 | d, err := c.String() 260 | return sql.NullString{ 261 | String: d, 262 | Valid: true, 263 | }, err 264 | } 265 | 266 | func (c Cell) NullTime() (sql.NullTime, error) { 267 | if !c.valid { 268 | return sql.NullTime{}, nil 269 | } 270 | d, err := c.Time() 271 | return sql.NullTime{ 272 | Time: d, 273 | Valid: true, 274 | }, err 275 | } 276 | 277 | func (c Cell) AsInterface() (interface{}, error) { 278 | var i interface{} 279 | var err error 280 | switch c.kind { 281 | case reflect.Bool: 282 | i, err = c.Bool() 283 | case reflect.Int32: 284 | i, err = c.Int32() 285 | case reflect.Int64: 286 | i, err = c.Int64() 287 | case reflect.Uint32: 288 | i, err = c.Uint32() 289 | case reflect.Uint64: 290 | i, err = c.Uint64() 291 | case reflect.Float32: 292 | i, err = c.Float32() 293 | case reflect.Float64: 294 | i, err = c.Float64() 295 | case reflect.String: 296 | i, err = c.String() 297 | } 298 | return i, err 299 | } 300 | 301 | func (c Cell) Uid() string { 302 | if c.IsNull() { 303 | //TODO: safely represent null and bool values as string 304 | return "cnull" 305 | } else { 306 | switch c.kind { 307 | case reflect.Int64, reflect.Float64: 308 | return strconv.FormatUint(c.bits, 36) 309 | case reflect.String: 310 | return c.text 311 | case reflect.Bool: 312 | if c.bits != 0 { 313 | return "ctrue" 314 | } else { 315 | return "cfalse" 316 | } 317 | case reflect.Struct: 318 | return strconv.FormatInt(c.time.Unix(), 36) 319 | } 320 | } 321 | return "" 322 | } 323 | 324 | // func (c Cell) BitsAsString() string { 325 | // switch c.kind { 326 | // case reflect.Bool: 327 | // return strconv.FormatBool(c.Bool()) 328 | // case reflect.Int32: 329 | // return strconv.FormatInt(int64(c.Int32()), 10) 330 | // case reflect.Int64: 331 | // return strconv.FormatInt(c.Int64(), 10) 332 | // case reflect.Uint32: 333 | // return strconv.FormatUint(uint64(c.Uint32()), 10) 334 | // case reflect.Uint64: 335 | // return strconv.FormatUint(c.Uint64(), 10) 336 | // case reflect.Float32: 337 | // return fmt.Sprint(c.Float32()) 338 | // case reflect.Float64: 339 | // return fmt.Sprint(c.Float64()) 340 | // default: 341 | // return "" 342 | // } 343 | // } 344 | -------------------------------------------------------------------------------- /value/sql_types.go: -------------------------------------------------------------------------------- 1 | package value 2 | 3 | import ( 4 | "database/sql" 5 | "reflect" 6 | "time" 7 | 8 | "github.com/golang/protobuf/ptypes/timestamp" 9 | ) 10 | 11 | // Value represents go data types which carta supports for loading as well as what data types arrive from the sql driver 12 | type Value int 13 | 14 | // NOTE, carta does NOT support loading []uint8, any data that arrives from sql database as []uint8 15 | //is converted to bytes and expected field type is a string or *string 16 | const ( 17 | Invalid Value = iota 18 | Time 19 | Timestamp 20 | NullBool 21 | NullFloat64 22 | NullInt32 23 | NullInt64 24 | NullString 25 | NullTime 26 | Float64 27 | Float32 28 | Int 29 | Int8 30 | Int16 31 | Int32 32 | Uint 33 | Uint8 34 | Uint16 35 | Uint32 36 | Int64 37 | Uint64 38 | Bool 39 | String // note, []uint8get converted to string, this is because mysql returns []uint8 for varchar while pg returns string 40 | ) 41 | 42 | var BasicKinds = map[reflect.Kind]Value{ 43 | reflect.Bool: Bool, 44 | reflect.Int: Int, 45 | reflect.Int8: Int8, 46 | reflect.Int16: Int16, 47 | reflect.Int32: Int32, 48 | reflect.Int64: Int64, 49 | reflect.Uint: Uint, 50 | reflect.Uint8: Uint8, 51 | reflect.Uint16: Uint16, 52 | reflect.Uint32: Uint32, 53 | reflect.Uint64: Uint64, 54 | reflect.Float32: Float32, 55 | reflect.Float64: Float64, 56 | reflect.String: String, 57 | } 58 | 59 | var BasicTypes = map[reflect.Type]Value{ 60 | reflect.TypeOf(time.Time{}): Time, 61 | reflect.TypeOf(timestamp.Timestamp{}): Timestamp, 62 | reflect.TypeOf(sql.NullBool{}): NullBool, 63 | reflect.TypeOf(sql.NullFloat64{}): NullFloat64, 64 | reflect.TypeOf(sql.NullInt32{}): NullInt32, 65 | reflect.TypeOf(sql.NullInt64{}): NullInt64, 66 | reflect.TypeOf(sql.NullString{}): NullString, 67 | reflect.TypeOf(sql.NullTime{}): NullTime, 68 | } 69 | 70 | var NullableTypes = map[reflect.Type]Value{ 71 | reflect.TypeOf(sql.NullBool{}): NullBool, 72 | reflect.TypeOf(sql.NullFloat64{}): NullFloat64, 73 | reflect.TypeOf(sql.NullInt32{}): NullInt32, 74 | reflect.TypeOf(sql.NullInt64{}): NullInt64, 75 | reflect.TypeOf(sql.NullString{}): NullString, 76 | reflect.TypeOf(sql.NullTime{}): NullTime, 77 | } 78 | 79 | // Map of database data types to go types 80 | // var SQLTypes = map[string]Value{ 81 | // "VARCHAR": String, 82 | // "TEXT": String, 83 | // "NVARCHAR": String, 84 | // "NUMERIC": String, 85 | // "UUID": String, 86 | // "BPCHAR": String, 87 | // "BIT": String, 88 | // "CIDR": String, 89 | // "XML": String, 90 | // "OID": String, 91 | // 92 | // "DECIMAL": Float64, 93 | // "FLOAT8": Float64, 94 | // "FLOAT4": Float64, 95 | // 96 | // "BOOL": Bool, 97 | // 98 | // "INT": Int64, 99 | // "INT2": Int64, 100 | // "INT4": Int64, 101 | // "INT8": Int64, 102 | // 103 | // "TIME": Time, 104 | // "DATE": Time, 105 | // "TIMESTAMP": Time, 106 | // "TIMESTAMPZ": Time, 107 | // "TIMETZ": Time, 108 | // "TIMESTAMPTZ": Time, 109 | // } 110 | --------------------------------------------------------------------------------