Skip to content

Commit 75ab17b

Browse files
authored
Merge pull request #17 from erizocosmico/feature/refs-udf
internal/function: implement ref_name functions
2 parents d3d4e0c + 1848576 commit 75ab17b

File tree

7 files changed

+227
-1
lines changed

7 files changed

+227
-1
lines changed

cmd/gitquery/query_base.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66

77
"github.com/src-d/gitquery"
88
"github.com/src-d/gitquery/internal/format"
9+
"github.com/src-d/gitquery/internal/function"
910

1011
"gopkg.in/src-d/go-git.v4/utils/ioutil"
1112
sqle "gopkg.in/src-d/go-mysql-server.v0"
@@ -38,7 +39,8 @@ func (c *cmdQueryBase) buildDatabase() error {
3839
}
3940

4041
c.engine.AddDatabase(gitquery.NewDatabase(c.name, &pool))
41-
return err
42+
function.Register(c.engine.Catalog)
43+
return nil
4244
}
4345

4446
func (c *cmdQueryBase) executeQuery(sql string) (sql.Schema, sql.RowIter, error) {

internal/function/is_remote.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package function
2+
3+
import (
4+
"reflect"
5+
6+
"gopkg.in/src-d/go-git.v4/plumbing"
7+
"gopkg.in/src-d/go-mysql-server.v0/sql"
8+
"gopkg.in/src-d/go-mysql-server.v0/sql/expression"
9+
)
10+
11+
// IsRemote checks the given string is a tag name.
12+
type IsRemote struct {
13+
expression.UnaryExpression
14+
}
15+
16+
// NewIsRemote creates a new IsRemote function.
17+
func NewIsRemote(e sql.Expression) sql.Expression {
18+
return &IsRemote{expression.UnaryExpression{Child: e}}
19+
}
20+
21+
var _ sql.Expression = (*IsRemote)(nil)
22+
23+
// Eval implements the expression interface.
24+
func (f *IsRemote) Eval(row sql.Row) (interface{}, error) {
25+
val, err := f.Child.Eval(row)
26+
if err != nil {
27+
return nil, err
28+
}
29+
30+
if val == nil {
31+
return false, nil
32+
}
33+
34+
name, ok := val.(string)
35+
if !ok {
36+
return nil, sql.ErrInvalidType.New(reflect.TypeOf(val).String())
37+
}
38+
39+
return plumbing.ReferenceName(name).IsRemote(), nil
40+
}
41+
42+
// Name implements the Expression interface.
43+
func (IsRemote) Name() string {
44+
return "is_remote"
45+
}
46+
47+
// TransformUp implements the Expression interface.
48+
func (f IsRemote) TransformUp(fn func(sql.Expression) sql.Expression) sql.Expression {
49+
return NewIsRemote(fn(f.Child))
50+
}
51+
52+
// Type implements the Expression interface.
53+
func (IsRemote) Type() sql.Type {
54+
return sql.Boolean
55+
}

internal/function/is_remote_test.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package function
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/require"
7+
"gopkg.in/src-d/go-mysql-server.v0/sql"
8+
"gopkg.in/src-d/go-mysql-server.v0/sql/expression"
9+
)
10+
11+
func TestIsRemote(t *testing.T) {
12+
f := NewIsRemote(expression.NewGetField(0, sql.Text, "name", true))
13+
14+
testCases := []struct {
15+
name string
16+
row sql.Row
17+
expected bool
18+
err bool
19+
}{
20+
{"null", sql.NewRow(nil), false, false},
21+
{"not a branch", sql.NewRow("foo bar"), false, false},
22+
{"not remote branch", sql.NewRow("refs/heads/foo"), false, false},
23+
{"remote branch", sql.NewRow("refs/remotes/foo/bar"), true, false},
24+
{"mismatched type", sql.NewRow(1), false, true},
25+
}
26+
27+
for _, tt := range testCases {
28+
t.Run(tt.name, func(t *testing.T) {
29+
require := require.New(t)
30+
val, err := f.Eval(tt.row)
31+
if tt.err {
32+
require.Error(err)
33+
require.True(sql.ErrInvalidType.Is(err))
34+
} else {
35+
require.NoError(err)
36+
require.Equal(tt.expected, val)
37+
}
38+
})
39+
}
40+
}

internal/function/is_tag.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package function
2+
3+
import (
4+
"reflect"
5+
6+
"gopkg.in/src-d/go-git.v4/plumbing"
7+
"gopkg.in/src-d/go-mysql-server.v0/sql"
8+
"gopkg.in/src-d/go-mysql-server.v0/sql/expression"
9+
)
10+
11+
// IsTag checks the given string is a tag name.
12+
type IsTag struct {
13+
expression.UnaryExpression
14+
}
15+
16+
// NewIsTag creates a new IsTag function.
17+
func NewIsTag(e sql.Expression) sql.Expression {
18+
return &IsTag{expression.UnaryExpression{Child: e}}
19+
}
20+
21+
var _ sql.Expression = (*IsTag)(nil)
22+
23+
// Eval implements the expression interface.
24+
func (f *IsTag) Eval(row sql.Row) (interface{}, error) {
25+
val, err := f.Child.Eval(row)
26+
if err != nil {
27+
return nil, err
28+
}
29+
30+
if val == nil {
31+
return false, nil
32+
}
33+
34+
name, ok := val.(string)
35+
if !ok {
36+
return nil, sql.ErrInvalidType.New(reflect.TypeOf(val).String())
37+
}
38+
39+
return plumbing.ReferenceName(name).IsTag(), nil
40+
}
41+
42+
// Name implements the Expression interface.
43+
func (IsTag) Name() string {
44+
return "is_tag"
45+
}
46+
47+
// TransformUp implements the Expression interface.
48+
func (f IsTag) TransformUp(fn func(sql.Expression) sql.Expression) sql.Expression {
49+
return NewIsTag(fn(f.Child))
50+
}
51+
52+
// Type implements the Expression interface.
53+
func (IsTag) Type() sql.Type {
54+
return sql.Boolean
55+
}

internal/function/is_tag_test.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package function
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/require"
7+
"gopkg.in/src-d/go-mysql-server.v0/sql"
8+
"gopkg.in/src-d/go-mysql-server.v0/sql/expression"
9+
)
10+
11+
func TestIsTag(t *testing.T) {
12+
f := NewIsTag(expression.NewGetField(0, sql.Text, "name", true))
13+
14+
testCases := []struct {
15+
name string
16+
row sql.Row
17+
expected bool
18+
err bool
19+
}{
20+
{"null", sql.NewRow(nil), false, false},
21+
{"not a ref name", sql.NewRow("foo bar"), false, false},
22+
{"not a tag ref", sql.NewRow("refs/heads/v1.x"), false, false},
23+
{"a tag", sql.NewRow("refs/tags/v1.0.0"), true, false},
24+
{"mismatched type", sql.NewRow(1), false, true},
25+
}
26+
27+
for _, tt := range testCases {
28+
t.Run(tt.name, func(t *testing.T) {
29+
require := require.New(t)
30+
val, err := f.Eval(tt.row)
31+
if tt.err {
32+
require.Error(err)
33+
require.True(sql.ErrInvalidType.Is(err))
34+
} else {
35+
require.NoError(err)
36+
require.Equal(tt.expected, val)
37+
}
38+
})
39+
}
40+
}

internal/function/registry.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package function
2+
3+
import "gopkg.in/src-d/go-mysql-server.v0/sql"
4+
5+
var functions = map[string]sql.Function{
6+
"is_tag": sql.Function1(NewIsTag),
7+
"is_remote": sql.Function1(NewIsRemote),
8+
}
9+
10+
// Register all the gitquery functions in the SQL catalog.
11+
func Register(c *sql.Catalog) {
12+
for k, v := range functions {
13+
c.RegisterFunction(k, v)
14+
}
15+
}

internal/function/registry_test.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package function
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/require"
7+
"gopkg.in/src-d/go-mysql-server.v0/sql"
8+
)
9+
10+
func TestRegister(t *testing.T) {
11+
require := require.New(t)
12+
catalog := sql.NewCatalog()
13+
Register(catalog)
14+
15+
for fn := range functions {
16+
_, err := catalog.Function(fn)
17+
require.NoError(err, "expected to find function: %s", fn)
18+
}
19+
}

0 commit comments

Comments
 (0)