Skip to content

Commit

Permalink
Merge pull request #13 from varfrog/index-prefixes
Browse files Browse the repository at this point in the history
Add support for index_prefixes
  • Loading branch information
varfrog authored Apr 10, 2023
2 parents e694521 + cdc79bc commit 3cac33a
Show file tree
Hide file tree
Showing 8 changed files with 160 additions and 53 deletions.
70 changes: 37 additions & 33 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,44 +13,44 @@ Utilities for working with OpenSearch.
package main

import (
_ "embed"
"fmt"
"github.com/varfrog/opensearchutil"
"os"
_ "embed"
"fmt"
"github.com/varfrog/opensearchutil"
"os"
)

func main() {
type location struct {
FullAddress string
Confirmed bool
}
type person struct {
Name string
Email string `opensearch:"type:keyword"`
DOB opensearchutil.TimeBasicDateTimeNoMillis
Age uint8
AccountBalance float64
IsDead bool
HomeLoc location
WorkLoc *location
SocialSecurity *string
}
type location struct {
FullAddress string
Confirmed bool
}
type person struct {
Name string
Email string `opensearch:"type:keyword"`
DOB opensearchutil.TimeBasicDateTimeNoMillis
Age uint8
AccountBalance float64
IsDead bool
HomeLoc location
WorkLoc *location
SocialSecurity *string `opensearch:"index_prefixes:min_chars=3;max_chars=5"`
}

builder := opensearchutil.NewMappingPropertiesBuilder()
jsonGenerator := opensearchutil.NewIndexGenerator()
builder := opensearchutil.NewMappingPropertiesBuilder()
jsonGenerator := opensearchutil.NewIndexGenerator()

mappingProperties, err := builder.BuildMappingProperties(person{})
if err != nil {
fmt.Printf("BuildMappingProperties: %v", err)
os.Exit(1)
}
mappingProperties, err := builder.BuildMappingProperties(person{})
if err != nil {
fmt.Printf("BuildMappingProperties: %v", err)
os.Exit(1)
}

indexJson, err := jsonGenerator.GenerateIndexJson(mappingProperties)
if err != nil {
fmt.Printf("GenerateIndexJson: %v", err)
os.Exit(1)
}
fmt.Printf("%s\n", string(indexJson))
indexJson, err := jsonGenerator.GenerateIndexJson(mappingProperties, nil)
if err != nil {
fmt.Printf("GenerateIndexJson: %v", err)
os.Exit(1)
}
fmt.Printf("%s\n", string(indexJson))
}
```

Expand Down Expand Up @@ -89,6 +89,10 @@ Output:
"type": "text"
},
"social_security": {
"index_prefixes": {
"max_chars": "5",
"min_chars": "3"
},
"type": "text"
},
"work_loc": {
Expand Down Expand Up @@ -143,7 +147,7 @@ func main() {
fmt.Printf("BuildMappingProperties: %v", err)
os.Exit(1)
}
jsonBytes, err := opensearchutil.NewIndexGenerator().GenerateIndexJson(mappingProperties)
jsonBytes, err := opensearchutil.NewIndexGenerator().GenerateIndexJson(mappingProperties, nil)
if err != nil {
fmt.Printf("GenerateIndexJson: %v", err)
os.Exit(1)
Expand Down
13 changes: 8 additions & 5 deletions index_generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,9 @@ type (
Properties map[string]interface{} `json:"properties"`
}
leafNode struct {
Type string `json:"type"`
Format *string `json:"format,omitempty"`
Type string `json:"type"`
Format *string `json:"format,omitempty"`
IndexPrefixes *map[string]string `json:"index_prefixes,omitempty"`
}
)

Expand Down Expand Up @@ -77,10 +78,12 @@ func (g *IndexGenerator) buildProperties(mappingProperties []MappingProperty) ma
m := make(map[string]interface{}, len(mappingProperties))
for _, mp := range mappingProperties {
if mp.Children == nil {
m[mp.FieldName] = leafNode{
Type: mp.FieldType,
Format: mp.FieldFormat,
node := leafNode{
Type: mp.FieldType,
Format: mp.FieldFormat,
IndexPrefixes: mp.IndexPrefixes,
}
m[mp.FieldName] = node
} else {
m[mp.FieldName] = parentNode{Properties: g.buildProperties(mp.Children)}
}
Expand Down
31 changes: 31 additions & 0 deletions index_generator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,37 @@ func TestIndexGenerator_GenerateIndexJson_addsDynamic(t *testing.T) {
}`))
}

func TestIndexGenerator_GenerateIndexJson_addsCustomProps(t *testing.T) {
g := gomega.NewGomegaWithT(t)

resultJson, err := NewIndexGenerator().GenerateIndexJson([]MappingProperty{
{
FieldName: "name",
FieldType: "text",
IndexPrefixes: MakePtr(map[string]string{
"min_chars": "2",
"max_chars": "10",
}),
},
}, nil, WithStrictMapping(true))
g.Expect(err).To(gomega.BeNil())

assertJsonsEqual(g, resultJson, []byte(`{
"mappings": {
"dynamic": "strict",
"properties": {
"name": {
"type": "text",
"index_prefixes": {
"min_chars": "2",
"max_chars": "10"
}
}
}
}
}`))
}

func makeJsonObj(jsonBytes []byte) (map[string]interface{}, error) {
var m map[string]interface{}
if err := json.Unmarshal(jsonBytes, &m); err != nil {
Expand Down
21 changes: 19 additions & 2 deletions mapping_properties_builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,11 +74,15 @@ func (b *MappingPropertiesBuilder) doBuildMappingProperties(
}

if fieldType != "" {
mappingProperties = append(mappingProperties, MappingProperty{
mappingProperty := MappingProperty{
FieldName: transformedFieldName,
FieldType: fieldType,
FieldFormat: fieldFormat,
})
}
if err := b.addProperties(resolvedField, &mappingProperty); err != nil {
return nil, errors.Wrapf(err, "addProperties")
}
mappingProperties = append(mappingProperties, mappingProperty)
continue
} else if resolvedField.kind == reflect.Struct && nthLevel+1 <= b.optionContainer.maxDepth {
children, err := b.doBuildMappingProperties(resolvedField.value.Interface(), nthLevel+1)
Expand All @@ -96,6 +100,19 @@ func (b *MappingPropertiesBuilder) doBuildMappingProperties(
return mappingProperties, nil
}

func (b *MappingPropertiesBuilder) addProperties(resolvedField fieldWrapper, mappingProperty *MappingProperty) error {
indexPrefixes := getTagOptionValue(resolvedField.field, tagKey, tagOptionIndexPrefixes)
if indexPrefixes != "" {
opts := parseCustomPropertyValue(indexPrefixes)
mappingProperty.IndexPrefixes = MakePtr(make(map[string]string, len(opts)))
for k, v := range opts {
(*mappingProperty.IndexPrefixes)[k] = v
}
}

return nil
}

func validateField(field fieldWrapper) error {
if field.kind == reflect.Struct {
switch field.value.Interface().(type) {
Expand Down
18 changes: 18 additions & 0 deletions mapping_properties_builder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -207,3 +207,21 @@ func TestMappingPropertiesBuilder_BuildMappingProperties_DoesNotExceedGivenMaxDe
g.Expect(err).To(gomega.BeNil())
g.Expect(mps).To(gomega.Equal(expectedMappingProperties))
}

func TestMappingPropertiesBuilder_BuildMappingProperties_SetsCustomProps(t *testing.T) {
g := gomega.NewGomegaWithT(t)

type person struct {
BusinessName string `opensearch:"index_prefixes:min_chars=2;max_chars=10"`
}

builder := NewMappingPropertiesBuilder()
mps, err := builder.BuildMappingProperties(person{})
g.Expect(err).To(gomega.BeNil())
g.Expect(mps).To(gomega.HaveLen(1))
g.Expect(mps[0].IndexPrefixes).ToNot(gomega.BeNil())
g.Expect(*mps[0].IndexPrefixes).To(gomega.Equal(map[string]string{
"min_chars": "2",
"max_chars": "10",
}))
}
16 changes: 9 additions & 7 deletions opensearchutil.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,22 @@ import (
const (
DefaultMaxDepth = 2

tagKey = "opensearch"
tagOptionType = "type"
tagOptionFormat = "format"
tagKey = "opensearch"
tagOptionType = "type"
tagOptionFormat = "format"
tagOptionIndexPrefixes = "index_prefixes"
)

// MappingProperty corresponds to mappings.properties of a mapping JSON. See
// https://opensearch.org/docs/1.3/opensearch/mappings/#explicit-mapping.
// MappingProperty defines either a primitive data type, in which case FieldType != "", or an object, in which case
// len(Children) > 0.
type MappingProperty struct {
FieldName string
FieldType string
FieldFormat *string
Children []MappingProperty
FieldName string
FieldType string
FieldFormat *string
IndexPrefixes *map[string]string
Children []MappingProperty
}

// IndexSettings allows to specify settings of an index, at its creation. This struct includes both static (those
Expand Down
26 changes: 21 additions & 5 deletions util.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,31 @@ func MakePtr[V any](v V) *V {
}

func getTagOptionValue(structField reflect.StructField, tagKey string, optionKey string) string {
const tagOptionSep = ","
const keyValSep = ":"
if tag := structField.Tag.Get(tagKey); tag != "" {
for _, kvs := range strings.Split(tag, ",") {
kv := strings.Split(kvs, ":")
if len(kv) == 2 {
if kv[0] == optionKey {
return kv[1]
for _, kvs := range strings.Split(tag, tagOptionSep) {
kv := strings.Split(kvs, keyValSep)
if len(kv) > 1 {
if strings.Trim(kv[0], " ") == optionKey {
return strings.Join(kv[1:], keyValSep)
}
}
}
}
return ""
}

func parseCustomPropertyValue(str string) map[string]string {
const keyValSep = "="
pairs := strings.Split(str, ";")
m := make(map[string]string, len(pairs))
for _, kvs := range pairs {
kv := strings.Split(kvs, keyValSep)
if len(kv) > 1 {
val := strings.Join(kv[1:], keyValSep)
m[kv[0]] = val
}
}
return m
}
18 changes: 17 additions & 1 deletion util_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@ func Test_getTagOptionValue(t *testing.T) {
type foo struct {
a string // No tag
b string `opensearch:"type:keyword"`
c time.Time `opensearch:"type:date,format:basic_time"`
c time.Time `opensearch:"type:date, format:basic_time"`
d string `opensearch:"index_prefixes:min_chars=2;max_chars=10"`
e string `opensearch:"type:text, index_prefixes:min_chars=2;max_chars=10"`
}

v := reflect.TypeOf(foo{})
Expand All @@ -40,4 +42,18 @@ func Test_getTagOptionValue(t *testing.T) {

g.Expect(getTagOptionValue(v.Field(2), tagKey, "type")).To(gomega.Equal("date"))
g.Expect(getTagOptionValue(v.Field(2), tagKey, "format")).To(gomega.Equal("basic_time"))

g.Expect(getTagOptionValue(v.Field(3), tagKey, "index_prefixes")).To(gomega.Equal(`min_chars=2;max_chars=10`))

g.Expect(getTagOptionValue(v.Field(4), tagKey, "type")).To(gomega.Equal("text"))
g.Expect(getTagOptionValue(v.Field(4), tagKey, "index_prefixes")).To(gomega.Equal(`min_chars=2;max_chars=10`))
}

func Test_parseCustomPropertyValue(t *testing.T) {
g := gomega.NewGomegaWithT(t)

g.Expect(parseCustomPropertyValue("min_chars=2;foo=bar")).To(gomega.Equal(map[string]string{
"min_chars": "2",
"foo": "bar",
}))
}

0 comments on commit 3cac33a

Please sign in to comment.