-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
2 changed files
with
234 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
package norm | ||
|
||
import "context" | ||
|
||
type Merge[M1, M2 any] struct { | ||
L M1 | ||
R []M2 | ||
} | ||
|
||
type Keyer[K comparable] interface { | ||
Key() K | ||
} | ||
|
||
// Lookup performs left outer join. | ||
func Lookup[ | ||
M1 ~[]T1, | ||
M2 ~[]T2, | ||
T1, T2 Keyer[K], | ||
A1, A2 any, | ||
K comparable, | ||
]( | ||
ctx context.Context, | ||
lhs Reader[M1, A1], lhsArgs A1, | ||
rhs Reader[M2, A2], rhsArgs A2, | ||
) ( | ||
merge []Merge[T1, T2], | ||
err error, | ||
) { | ||
l, err := lhs.Read(ctx, lhsArgs) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
r, err := rhs.Read(ctx, rhsArgs) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return _lookup[M1, M2, T1, T2, K](l, r), nil | ||
} | ||
|
||
func _lookup[ | ||
M1 ~[]T1, | ||
M2 ~[]T2, | ||
T1 Keyer[K], | ||
T2 Keyer[K], | ||
K comparable, | ||
]( | ||
lhs M1, | ||
rhs M2, | ||
) ( | ||
out []Merge[T1, T2], | ||
) { | ||
out = make([]Merge[T1, T2], 0, len(lhs)) | ||
m := make(map[K][]int, len(rhs)) | ||
|
||
for i, v := range rhs { | ||
k := v.Key() | ||
m[k] = append(m[k], i) | ||
} | ||
|
||
for _, l := range lhs { | ||
merge := Merge[T1, T2]{ | ||
L: l, | ||
R: nil, | ||
} | ||
|
||
rii, ok := m[l.Key()] | ||
if ok { | ||
for _, i := range rii { | ||
merge.R = append(merge.R, rhs[i]) | ||
} | ||
} | ||
|
||
out = append(out, merge) | ||
} | ||
|
||
return out | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,155 @@ | ||
package norm | ||
|
||
import ( | ||
"context" | ||
"reflect" | ||
"testing" | ||
) | ||
|
||
type kint int | ||
|
||
func (v kint) Key() int { | ||
return int(v) | ||
} | ||
|
||
type mem struct { | ||
v []kint | ||
} | ||
|
||
func (m mem) Read(ctx context.Context, args struct{}) (value []kint, err error) { | ||
return []kint(m.v), nil | ||
} | ||
|
||
func TestLookup(t *testing.T) { | ||
type args struct { | ||
ctx context.Context | ||
lhs Reader[[]kint, struct{}] | ||
lhsArgs struct{} | ||
rhs Reader[[]kint, struct{}] | ||
rhsArgs struct{} | ||
} | ||
tests := []struct { | ||
name string | ||
args args | ||
wantMerge []Merge[kint, kint] | ||
wantErr bool | ||
}{ | ||
{ | ||
name: "with mathes", | ||
args: args{ | ||
ctx: context.Background(), | ||
lhs: mem{[]kint{1, 2, 3, 4, 5}}, | ||
lhsArgs: struct{}{}, | ||
rhs: mem{[]kint{5, 5, 2, 2, 4}}, | ||
rhsArgs: struct{}{}, | ||
}, | ||
wantMerge: []Merge[kint, kint]{ | ||
{1, nil}, | ||
{2, []kint{2, 2}}, | ||
{3, nil}, | ||
{4, []kint{4}}, | ||
{5, []kint{5, 5}}, | ||
}, | ||
wantErr: false, | ||
}, | ||
{ | ||
name: "no match", | ||
args: args{ | ||
ctx: context.Background(), | ||
lhs: mem{[]kint{1, 2, 3, 4, 5}}, | ||
lhsArgs: struct{}{}, | ||
rhs: mem{[]kint{10, 11, 12, 13, 14}}, | ||
rhsArgs: struct{}{}, | ||
}, | ||
wantMerge: []Merge[kint, kint]{ | ||
{1, nil}, | ||
{2, nil}, | ||
{3, nil}, | ||
{4, nil}, | ||
{5, nil}, | ||
}, | ||
wantErr: false, | ||
}, | ||
{ | ||
name: "right is empty", | ||
args: args{ | ||
ctx: context.Background(), | ||
lhs: mem{[]kint{1, 2, 3, 4, 5}}, | ||
lhsArgs: struct{}{}, | ||
rhs: mem{[]kint{}}, | ||
rhsArgs: struct{}{}, | ||
}, | ||
wantMerge: []Merge[kint, kint]{ | ||
{1, nil}, | ||
{2, nil}, | ||
{3, nil}, | ||
{4, nil}, | ||
{5, nil}, | ||
}, | ||
wantErr: false, | ||
}, | ||
{ | ||
name: "right is nil", | ||
args: args{ | ||
ctx: context.Background(), | ||
lhs: mem{[]kint{1, 2, 3, 4, 5}}, | ||
lhsArgs: struct{}{}, | ||
rhs: mem{}, | ||
rhsArgs: struct{}{}, | ||
}, | ||
wantMerge: []Merge[kint, kint]{ | ||
{1, nil}, | ||
{2, nil}, | ||
{3, nil}, | ||
{4, nil}, | ||
{5, nil}, | ||
}, | ||
wantErr: false, | ||
}, | ||
{ | ||
name: "left is empty", | ||
args: args{ | ||
ctx: context.Background(), | ||
lhs: mem{[]kint{}}, | ||
lhsArgs: struct{}{}, | ||
rhs: mem{[]kint{1, 2, 3, 4, 5}}, | ||
rhsArgs: struct{}{}, | ||
}, | ||
wantMerge: []Merge[kint, kint]{}, | ||
wantErr: false, | ||
}, | ||
{ | ||
name: "left is nil", | ||
args: args{ | ||
ctx: context.Background(), | ||
lhs: mem{}, | ||
lhsArgs: struct{}{}, | ||
rhs: mem{[]kint{1, 2, 3, 4, 5}}, | ||
rhsArgs: struct{}{}, | ||
}, | ||
wantMerge: []Merge[kint, kint]{}, | ||
wantErr: false, | ||
}, | ||
} | ||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
gotMerge, err := Lookup[ | ||
[]kint, []kint, | ||
kint, kint, | ||
struct{}, struct{}, | ||
int, | ||
]( | ||
tt.args.ctx, | ||
tt.args.lhs, tt.args.lhsArgs, | ||
tt.args.rhs, tt.args.rhsArgs, | ||
) | ||
if (err != nil) != tt.wantErr { | ||
t.Errorf("Lookup() error = %v, wantErr %v", err, tt.wantErr) | ||
return | ||
} | ||
if !reflect.DeepEqual(gotMerge, tt.wantMerge) { | ||
t.Errorf("Lookup() = %v, want %v", gotMerge, tt.wantMerge) | ||
} | ||
}) | ||
} | ||
} |