github.com/tickoalcantara12/micro/v3@v3.0.0-20221007104245-9d75b9bcbab9/service/model/README.md (about) 1 # Model 2 3 Package model is a convenience wrapper around what the `Store` provides. 4 It's main responsibility is to maintain indexes that would otherwise be maintaned by the users to enable different queries on the same data. 5 6 ## Usage 7 8 The following snippets will this piece of code prepends them. 9 10 ```go 11 import( 12 model "github.com/tickoalcantara12/micro/v3/service/model" 13 fs "github.com/tickoalcantara12/micro/v3/service/store/file" 14 ) 15 16 type User struct { 17 ID string `json:"id"` 18 Name string `json:"name"` 19 Age int `json:"age"` 20 HasPet bool `json:"hasPet"` 21 Created int64 `json:"created"` 22 Tag string `json:"tag"` 23 Updated int64 `json:"updated"` 24 } 25 ``` 26 27 ## Query by field equality 28 29 For each field we want to query on we have to create an index. Index by `id` is provided by default to each `DB`, there is no need to specify it. 30 31 ```go 32 ageIndex := model.ByEquality("age") 33 34 db := model.New(User{}, []model.Index{(ageIndex}) 35 36 err := db.Create(User{ 37 ID: "1", 38 Name: "Alice", 39 Age: 20, 40 }) 41 if err != nil { 42 // handle save error 43 } 44 err := db.Create(User{ 45 ID: "2", 46 Name: "Jane", 47 Age: 22 48 }) 49 if err != nil { 50 // handle save error 51 } 52 53 err = db.Read(model.Equals("age", 22), &users) 54 if err != nil { 55 // handle list error 56 } 57 fmt.Println(users) 58 59 // will print 60 // [{"id":"2","name":"Jane","age":22}] 61 ``` 62 63 ## Reading all records in an index 64 65 Reading can be done without specifying a value: 66 67 ```go 68 db.Read(Equals("age", nil), &users) 69 ``` 70 71 Readings will be unordered, ascending ordered or descending ordered depending on the ordering settings of the index. 72 73 ## Ordering 74 75 Indexes by default are ordered. If we want to turn this behaviour off: 76 77 ```go 78 ageIndex.Order.Type = OrderTypeUnordered 79 80 ageQuery := model.Equals("age", 22) 81 ageQuery.Order.Type = OrderTypeUnordered 82 ``` 83 84 ### Filtering by one field, ordering by other 85 86 ```go 87 typeIndex := ByEquality("type") 88 typeIndex.Order = Order{ 89 Type: OrderTypeDesc, 90 FieldName: "age", 91 } 92 93 // Results will be ordered by age 94 db.Read(typeIndex.ToQuery("a-certain-type-value")) 95 ``` 96 97 By default the ordering field is the same as the filtering field. 98 99 ### Reverse order 100 101 ```go 102 ageQuery.Desc = true 103 ``` 104 105 ### Queries must match indexes 106 107 It is important to note that queries must match indexes. The following index-query pairs match (separated by an empty line) 108 109 ```go 110 // Ascending ordered index by age 111 index := model.Equality("age") 112 // Read ascending ordered by age 113 query := model.Equals("age", nil) 114 // Read ascending ordered by age where age = 20 115 query2 := model.Equals("age", 20) 116 117 // Descending ordered index by age 118 index := model.Equality("age") 119 index.Order.Type = OrderTypeDesc 120 // Read descending ordered by age 121 query := model.Equals("age", nil) 122 query.Order.Type = OrderTypeDesc 123 // Read descending ordered by age where age = 20 124 query2 := model.Equals("age", 20) 125 query2.Order.Type = OrderTypeDesc 126 127 // Unordered index by age 128 index := model.Equality("age") 129 index.Order.Type = OrderTypeUnordered 130 // Read unordered by age 131 query := model.Equals("age", nil) 132 query.Order.Type = OrderTypeUnordered 133 // Read unordered by age where age = 20 134 query2 := model.Equals("age", 20) 135 query2.Order.Type = OrderTypeUnordered 136 ``` 137 138 Of course, maintaining this might be inconvenient, for this reason the `ToQuery` method was introduced, see below. 139 140 #### Creating a query out of an Index 141 142 ```go 143 144 index := model.Equality("age") 145 index.Order.Type = OrderTypeUnordered 146 147 db.Read(index.ToQuery(25)) 148 ``` 149 150 ### Unordered listing without value 151 152 It's easy to see how listing things by unordered indexes on different fields should result in the same output: a randomly ordered list, ie: 153 154 ```go 155 ageIndex := model.Equality("age") 156 ageIndex.Order.Type = OrderTypeUnordered 157 158 emailIndex := model.Equality("email") 159 emailIndex.Order.Type = OrderTypeUnordered 160 161 result1 := []User{} 162 result2 := []User{} 163 164 db.Read(model.Equals("age"), &result1) 165 db.Read(model.Equals("email"), &result2) 166 167 // Both result1 and result2 will be an unordered listing without 168 // filtering on either the age or email fields. 169 // Could be thought of as a noop query despite not having an explicit "no query" listing. 170 ``` 171 172 ### Ordering by string fields 173 174 Ordering comes for "free" when dealing with numeric or boolean fields, but it involves in padding, inversing and order preserving base32 encoding of values to work for strings. 175 176 This can sometimes result in large keys saved, as the inverse of a small 1 byte character in a string is a 4 byte rune. Optionally adding base32 encoding on top to prevent exotic runes appearing in keys, strings blow up in size even more. If saving space is a requirement and ordering is not, ordering for strings should be turned off. 177 178 The matter is further complicated by the fact that the padding size must be specified ahead of time. 179 180 ```go 181 nameIndex := model.ByEquality("name") 182 nameIndex.StringOrderPadLength = 10 183 184 nameQuery := model.Equals("age", 22) 185 // `StringOrderPadLength` is not needed to be specified for the query 186 ``` 187 188 To turn off base32 encoding and keep the runes: 189 190 ```go 191 nameIndex.Base32Encode = false 192 ``` 193 194 ## Unique indexes 195 196 ```go 197 emailIndex := model.ByEquality("email") 198 emailIndex.Unique = true 199 ``` 200 201 ## Design 202 203 ### Restrictions 204 205 To maintain all indexes properly, all fields must be filled out when saving. 206 This sometimes requires a `Read, Modify, Write` pattern. In other words, partial updates will break indexes. 207 208 This could be avoided later if model does the loading itself. 209 210 ## TODO 211 212 - Implement deletes 213 - Implement counters, for pattern inspiration see the [tags service](https://github.com/micro/services/tree/master/blog/tags) 214 - Test boolean indexes and its ordering 215 - There is a stuttering in the way `id` fields are being saved twice. ID fields since they are unique do not need `id` appended after them in the record keys.