Skip to content

Commit

Permalink
feat: support multiple tag options join:left_col1=right_col1,join:lef…
Browse files Browse the repository at this point in the history
…t_col2=right_col2
  • Loading branch information
vmihailenco committed Nov 7, 2021
1 parent 526ab0b commit 78cd5aa
Show file tree
Hide file tree
Showing 6 changed files with 71 additions and 39 deletions.
6 changes: 2 additions & 4 deletions dialect/pgdialect/sqltype.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"encoding/json"
"net"
"reflect"
"time"

"github.com/uptrace/bun/dialect/sqltype"
"github.com/uptrace/bun/schema"
Expand Down Expand Up @@ -41,7 +40,6 @@ const (
)

var (
timeType = reflect.TypeOf((*time.Time)(nil)).Elem()
ipType = reflect.TypeOf((*net.IP)(nil)).Elem()
ipNetType = reflect.TypeOf((*net.IPNet)(nil)).Elem()
jsonRawMessageType = reflect.TypeOf((*json.RawMessage)(nil)).Elem()
Expand All @@ -52,11 +50,11 @@ func fieldSQLType(field *schema.Field) string {
return field.UserSQLType
}

if v, ok := field.Tag.Options["composite"]; ok {
if v, ok := field.Tag.Option("composite"); ok {
return v
}

if _, ok := field.Tag.Options["hstore"]; ok {
if _, ok := field.Tag.Option("hstore"); ok {
return "hstore"
}

Expand Down
2 changes: 1 addition & 1 deletion example/opentelemetry/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ OTEL_EXPORTER_JAEGER_ENDPOINT=http://localhost:14268/api/traces go run .
**Uptrace** exporter:

```shell
UPTRACE_DSN="https://<token>@api.uptrace.dev/<project_id>" go run .
UPTRACE_DSN="https://<token>@uptrace.dev/<project_id>" go run .
```

## Links
Expand Down
4 changes: 2 additions & 2 deletions internal/dbtest/orm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -485,7 +485,7 @@ type Book struct {

Genres []Genre `bun:"m2m:book_genres"` // many to many relation
Translations []Translation `bun:"rel:has-many"`
Comments []Comment `bun:"rel:has-many,join:\"id=trackable_id,type=trackable_type\",polymorphic"`
Comments []Comment `bun:"rel:has-many,join:id=trackable_id,join:type=trackable_type,polymorphic"`
}

func (b Book) String() string {
Expand All @@ -509,7 +509,7 @@ type Translation struct {
Book *Book `bun:"rel:belongs-to"`
Lang string `bun:"unique:book_id_lang"`

Comments []Comment `bun:"rel:has-many,join:\"id=trackable_id,type=trackable_type\",polymorphic"`
Comments []Comment `bun:"rel:has-many,join:id=trackable_id,join:type=trackable_type,polymorphic"`
}

type Comment struct {
Expand Down
24 changes: 21 additions & 3 deletions internal/tagparser/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,29 @@ import (

type Tag struct {
Name string
Options map[string]string
Options map[string][]string
}

func (t Tag) IsZero() bool {
return t.Name == "" && t.Options == nil
}

func (t Tag) HasOption(name string) bool {
_, ok := t.Options[name]
return ok
}

func (t Tag) Option(name string) (string, bool) {
if vs, ok := t.Options[name]; ok {
return vs[len(vs)-1], true
}
return "", false
}

func Parse(s string) Tag {
if s == "" {
return Tag{}
}
p := parser{
s: s,
}
Expand Down Expand Up @@ -45,9 +59,13 @@ func (p *parser) addOption(key, value string) {
return
}
if p.tag.Options == nil {
p.tag.Options = make(map[string]string)
p.tag.Options = make(map[string][]string)
}
if vs, ok := p.tag.Options[key]; ok {
p.tag.Options[key] = append(vs, value)
} else {
p.tag.Options[key] = []string{value}
}
p.tag.Options[key] = value
}

func (p *parser) parse() {
Expand Down
33 changes: 18 additions & 15 deletions internal/tagparser/parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,26 +11,29 @@ import (
var tagTests = []struct {
tag string
name string
options map[string]string
options map[string][]string
}{
{"", "", nil},
{"hello", "hello", nil},
{"hello,world", "hello", map[string]string{"world": ""}},
{"hello,world", "hello", map[string][]string{"world": {""}}},
{`"hello,world'`, "", nil},
{`"hello:world"`, `hello:world`, nil},
{",hello", "", map[string]string{"hello": ""}},
{",hello,world", "", map[string]string{"hello": "", "world": ""}},
{"hello:", "", map[string]string{"hello": ""}},
{"hello:world", "", map[string]string{"hello": "world"}},
{"hello:world,foo", "", map[string]string{"hello": "world", "foo": ""}},
{"hello:world,foo:bar", "", map[string]string{"hello": "world", "foo": "bar"}},
{"hello:\"world1,world2\"", "", map[string]string{"hello": "world1,world2"}},
{`hello:"world1,world2",world3`, "", map[string]string{"hello": "world1,world2", "world3": ""}},
{`hello:"world1:world2",world3`, "", map[string]string{"hello": "world1:world2", "world3": ""}},
{`hello:"D'Angelo, esquire",foo:bar`, "", map[string]string{"hello": "D'Angelo, esquire", "foo": "bar"}},
{`hello:"world('foo', 'bar')"`, "", map[string]string{"hello": "world('foo', 'bar')"}},
{" hello,foo: bar ", " hello", map[string]string{"foo": " bar "}},
{"type:geometry(POINT, 4326)", "", map[string]string{"type": "geometry(POINT, 4326)"}},
{",hello", "", map[string][]string{"hello": {""}}},
{",hello,world", "", map[string][]string{"hello": {""}, "world": {""}}},
{"hello:", "", map[string][]string{"hello": {""}}},
{"hello:world", "", map[string][]string{"hello": {"world"}}},
{"hello:world,foo", "", map[string][]string{"hello": {"world"}, "foo": {""}}},
{"hello:world,foo:bar", "", map[string][]string{"hello": {"world"}, "foo": {"bar"}}},
{"hello:\"world1,world2\"", "", map[string][]string{"hello": {"world1,world2"}}},
{`hello:"world1,world2",world3`, "", map[string][]string{"hello": {"world1,world2"}, "world3": {""}}},
{`hello:"world1:world2",world3`, "", map[string][]string{"hello": {"world1:world2"}, "world3": {""}}},
{`hello:"D'Angelo, esquire",foo:bar`, "", map[string][]string{"hello": {"D'Angelo, esquire"}, "foo": {"bar"}}},
{`hello:"world('foo', 'bar')"`, "", map[string][]string{"hello": {"world('foo', 'bar')"}}},
{" hello,foo: bar ", " hello", map[string][]string{"foo": {" bar "}}},
{"foo:bar(hello, world)", "", map[string][]string{"foo": {"bar(hello, world)"}}},
{"foo:bar(hello(), world)", "", map[string][]string{"foo": {"bar(hello(), world)"}}},
{"type:geometry(POINT, 4326)", "", map[string][]string{"type": {"geometry(POINT, 4326)"}}},
{"foo:bar,foo:baz", "", map[string][]string{"foo": []string{"bar", "baz"}}},
}

func TestTagParser(t *testing.T) {
Expand Down
41 changes: 27 additions & 14 deletions schema/table.go
Original file line number Diff line number Diff line change
Expand Up @@ -300,11 +300,11 @@ func (t *Table) processBaseModelField(f reflect.StructField) {
t.setName(tag.Name)
}

if s, ok := tag.Options["select"]; ok {
if s, ok := tag.Option("select"); ok {
t.SQLNameForSelects = t.quoteTableName(s)
}

if s, ok := tag.Options["alias"]; ok {
if s, ok := tag.Option("alias"); ok {
t.Alias = s
t.SQLAlias = t.quoteIdent(s)
}
Expand Down Expand Up @@ -359,28 +359,35 @@ func (t *Table) newField(f reflect.StructField, index []int) *Field {
}

if v, ok := tag.Options["unique"]; ok {
// Split the value by comma, this will allow multiple names to be specified.
// We can use this to create multiple named unique constraints where a single column
// might be included in multiple constraints.
for _, uniqueName := range strings.Split(v, ",") {
var names []string
if len(v) == 1 {
// Split the value by comma, this will allow multiple names to be specified.
// We can use this to create multiple named unique constraints where a single column
// might be included in multiple constraints.
names = strings.Split(v[0], ",")
} else {
names = v
}

for _, uniqueName := range names {
if t.Unique == nil {
t.Unique = make(map[string][]*Field)
}
t.Unique[uniqueName] = append(t.Unique[uniqueName], field)
}
}
if s, ok := tag.Options["default"]; ok {
if s, ok := tag.Option("default"); ok {
field.SQLDefault = s
}
if s, ok := field.Tag.Options["type"]; ok {
if s, ok := field.Tag.Option("type"); ok {
field.UserSQLType = s
}
field.DiscoveredSQLType = DiscoverSQLType(field.IndirectType)
field.Append = FieldAppender(t.dialect, field)
field.Scan = FieldScanner(t.dialect, field)
field.IsZero = zeroChecker(field.StructField.Type)

if v, ok := tag.Options["alt"]; ok {
if v, ok := tag.Option("alt"); ok {
t.FieldMap[v] = field
}

Expand Down Expand Up @@ -432,7 +439,7 @@ func (t *Table) initRelations() {
}

func (t *Table) tryRelation(field *Field) bool {
if rel, ok := field.Tag.Options["rel"]; ok {
if rel, ok := field.Tag.Option("rel"); ok {
t.initRelation(field, rel)
return true
}
Expand Down Expand Up @@ -608,7 +615,7 @@ func (t *Table) hasManyRelation(field *Field) *Relation {
}

joinTable := t.dialect.Tables().Ref(indirectType(field.IndirectType.Elem()))
polymorphicValue, isPolymorphic := field.Tag.Options["polymorphic"]
polymorphicValue, isPolymorphic := field.Tag.Option("polymorphic")
rel := &Relation{
Type: HasManyRelation,
Field: field,
Expand Down Expand Up @@ -705,7 +712,7 @@ func (t *Table) m2mRelation(field *Field) *Relation {
panic(err)
}

m2mTableName, ok := field.Tag.Options["m2m"]
m2mTableName, ok := field.Tag.Option("m2m")
if !ok {
panic(fmt.Errorf("bun: %s must have m2m tag option", field.GoName))
}
Expand Down Expand Up @@ -890,8 +897,14 @@ func removeField(fields []*Field, field *Field) []*Field {
return fields
}

func parseRelationJoin(join string) ([]string, []string) {
ss := strings.Split(join, ",")
func parseRelationJoin(join []string) ([]string, []string) {
var ss []string
if len(join) == 1 {
ss = strings.Split(join[0], ",")
} else {
ss = join
}

baseColumns := make([]string, len(ss))
joinColumns := make([]string, len(ss))
for i, s := range ss {
Expand Down

0 comments on commit 78cd5aa

Please sign in to comment.