diff --git a/README.md b/README.md index 184b79468..1bbf575dd 100644 --- a/README.md +++ b/README.md @@ -58,6 +58,7 @@ gitql supports a subset of the SQL standard, currently including: * `ORDER BY` (with `ASC` and `DESC`) * `LIMIT` * `SHOW TABLES` +* `DESCRIBE TABLE` ## License diff --git a/sql/analyzer/analyzer_test.go b/sql/analyzer/analyzer_test.go index 58263ed13..687e80b38 100644 --- a/sql/analyzer/analyzer_test.go +++ b/sql/analyzer/analyzer_test.go @@ -45,13 +45,21 @@ func TestAnalyzer_Analyze(t *testing.T) { plan.NewUnresolvedTable("mytable"), ) analyzed, err = a.Analyze(notAnalyzed) - expected := plan.NewProject( + var expected sql.Node = plan.NewProject( []sql.Expression{expression.NewGetField(0, sql.Integer, "i")}, table, ) assert.Nil(err) assert.Equal(expected, analyzed) + notAnalyzed = plan.NewDescribe( + plan.NewUnresolvedTable("mytable"), + ) + analyzed, err = a.Analyze(notAnalyzed) + expected = plan.NewDescribe(table) + assert.Nil(err) + assert.Equal(expected, analyzed) + notAnalyzed = plan.NewProject( []sql.Expression{expression.NewStar()}, plan.NewUnresolvedTable("mytable"), diff --git a/sql/parse/parse.go b/sql/parse/parse.go index d25ba0f96..c952fceb0 100644 --- a/sql/parse/parse.go +++ b/sql/parse/parse.go @@ -2,6 +2,7 @@ package parse import ( "fmt" + "regexp" "strconv" "strings" @@ -34,6 +35,11 @@ func Parse(s string) (sql.Node, error) { return plan.NewShowTables(&sql.UnresolvedDatabase{}), nil } + t := regexp.MustCompile(`^describe\s+table\s+(.*)`).FindStringSubmatch(strings.ToLower(s)) + if len(t) == 2 && t[1] != "" { + return plan.NewDescribe(plan.NewUnresolvedTable(t[1])), nil + } + stmt, err := sqlparser.Parse(s) if err != nil { return nil, err diff --git a/sql/parse/parse_test.go b/sql/parse/parse_test.go index 52a8035a1..2dd82b968 100644 --- a/sql/parse/parse_test.go +++ b/sql/parse/parse_test.go @@ -11,6 +11,9 @@ import ( ) var fixtures = map[string]sql.Node{ + `DESCRIBE TABLE foo;`: plan.NewDescribe( + plan.NewUnresolvedTable("foo"), + ), `SELECT foo, bar FROM foo;`: plan.NewProject( []sql.Expression{ expression.NewUnresolvedColumn("foo"), diff --git a/sql/plan/common.go b/sql/plan/common.go index a2316b70b..972ec903a 100644 --- a/sql/plan/common.go +++ b/sql/plan/common.go @@ -6,6 +6,10 @@ type UnaryNode struct { Child sql.Node } +func (n UnaryNode) Resolved() bool { + return n.Child.Resolved() +} + func (n UnaryNode) Children() []sql.Node { return []sql.Node{n.Child} } diff --git a/sql/plan/describe.go b/sql/plan/describe.go new file mode 100644 index 000000000..fb86dacba --- /dev/null +++ b/sql/plan/describe.go @@ -0,0 +1,61 @@ +package plan + +import ( + "io" + + "github.com/gitql/gitql/sql" +) + +type Describe struct { + UnaryNode +} + +func NewDescribe(child sql.Node) *Describe { + return &Describe{UnaryNode{child}} +} + +func (d *Describe) Schema() sql.Schema { + return sql.Schema{ + sql.Field{ + Name: "name", + Type: sql.String, + }, + sql.Field{ + Name: "type", + Type: sql.String, + }, + } +} + +func (d *Describe) RowIter() (sql.RowIter, error) { + return &describeIter{schema: d.Child.Schema()}, nil +} + +func (d *Describe) TransformUp(f func(sql.Node) sql.Node) sql.Node { + c := d.UnaryNode.Child.TransformUp(f) + n := NewDescribe(c) + + return f(n) +} + +func (d *Describe) TransformExpressionsUp(f func(sql.Expression) sql.Expression) sql.Node { + c := d.UnaryNode.Child.TransformExpressionsUp(f) + n := NewDescribe(c) + + return n +} + +type describeIter struct { + schema sql.Schema + i int +} + +func (i *describeIter) Next() (sql.Row, error) { + if i.i >= len(i.schema) { + return nil, io.EOF + } + + f := i.schema[i.i] + i.i++ + return sql.NewMemoryRow(f.Name, f.Type.Name()), nil +} diff --git a/sql/plan/describe_test.go b/sql/plan/describe_test.go new file mode 100644 index 000000000..3d0910d2a --- /dev/null +++ b/sql/plan/describe_test.go @@ -0,0 +1,50 @@ +package plan + +import ( + "io" + "testing" + + "github.com/gitql/gitql/mem" + "github.com/gitql/gitql/sql" + "github.com/stretchr/testify/assert" +) + +func TestDescribe(t *testing.T) { + assert := assert.New(t) + + table := mem.NewTable("test", sql.Schema{ + sql.Field{Name: "c1", Type: sql.String}, + sql.Field{Name: "c2", Type: sql.Integer}, + }) + + d := NewDescribe(table) + iter, err := d.RowIter() + assert.Nil(err) + assert.NotNil(iter) + + n, err := iter.Next() + assert.Nil(err) + assert.Equal(sql.NewMemoryRow("c1", "string"), n) + + n, err = iter.Next() + assert.Nil(err) + assert.Equal(sql.NewMemoryRow("c2", "integer"), n) + + n, err = iter.Next() + assert.Equal(io.EOF, err) + assert.Nil(n) +} + +func TestDescribe_Empty(t *testing.T) { + assert := assert.New(t) + + d := NewDescribe(NewUnresolvedTable("test_table")) + + iter, err := d.RowIter() + assert.Nil(err) + assert.NotNil(iter) + + n, err := iter.Next() + assert.Equal(io.EOF, err) + assert.Nil(n) +}