Skip to content

Commit

Permalink
Add support to count commits (#80)
Browse files Browse the repository at this point in the history
* count(*) works like '*'

* count(*) kind of works, some tests are still missing

* Added more tests

* Fix typo

* Don't apply limit when counting records (rembmer limit=0 is forbidden)
  • Loading branch information
budden authored and filhodanuvem committed Feb 18, 2019
1 parent 0f264eb commit 4767869
Show file tree
Hide file tree
Showing 16 changed files with 187 additions and 27 deletions.
1 change: 1 addition & 0 deletions lexical/lexemes.go
Expand Up @@ -13,3 +13,4 @@ const L_ASC = "asc"
const L_DESC = "desc"
const L_LIKE = "like"
const L_NOT = "not"
const L_COUNT = "count"
2 changes: 2 additions & 0 deletions lexical/lexical.go
Expand Up @@ -222,6 +222,8 @@ func lexemeToToken(lexeme string) uint8 {
return T_LIKE
case L_NOT:
return T_NOT
case L_COUNT:
return T_COUNT
}
return T_ID
}
Expand Down
10 changes: 8 additions & 2 deletions lexical/lexical_test.go
Expand Up @@ -86,7 +86,7 @@ func TestRecognizeTokensWithLexemesOfTwoChars(t *testing.T) {

func TestRecognizeTokensWithSourceManySpaced(t *testing.T) {
setUp()
source = "= < >= != cloudson"
source = "= < >= != cloudson count"
char = nextChar()

var token uint8
Expand All @@ -105,6 +105,9 @@ func TestRecognizeTokensWithSourceManySpaced(t *testing.T) {

token, _ = Token()
assertToken(t, token, T_ID)

token, _ = Token()
assertToken(t, token, T_COUNT)
}

func TestErrorUnrecognizeChar(t *testing.T) {
Expand All @@ -127,7 +130,7 @@ func TestErrorUnrecognizeChar(t *testing.T) {

func TestReservedWords(t *testing.T) {
setUp()
source = "SELECT from WHEre in not"
source = "SELECT from WHEre in not cOuNt"
char = nextChar()

var token uint8
Expand All @@ -147,6 +150,9 @@ func TestReservedWords(t *testing.T) {
token, _ = Token()
assertToken(t, token, T_NOT)

token, _ = Token()
assertToken(t, token, T_COUNT)

token, _ = Token()
assertToken(t, token, T_EOF)
}
Expand Down
4 changes: 3 additions & 1 deletion lexical/tokens.go
Expand Up @@ -27,6 +27,7 @@ const T_IN = 24
const T_ASC = 25
const T_LIKE = 26
const T_NOT = 27
const T_COUNT = 28
const T_EOF = 0
const T_FUCK = 66

Expand Down Expand Up @@ -59,7 +60,8 @@ func allocMapTokenNames() {
T_IN: "T_IN",
T_EOF: "T_EOF",
T_ASC: "T_ASC",
T_NOT: "T_NOT",
T_NOT: "T_NOT",
T_COUNT: "T_COUNT",
}
}
}
Expand Down
1 change: 1 addition & 0 deletions parser/ast.go
Expand Up @@ -23,6 +23,7 @@ type NodeProgram struct {

type NodeSelect struct {
WildCard bool
Count bool
Fields []string
Tables []string
Where NodeExpr
Expand Down
47 changes: 45 additions & 2 deletions parser/parser.go
Expand Up @@ -74,6 +74,7 @@ func gSelect() (*NodeSelect, error) {
return nil, throwSyntaxError(lexical.T_SELECT, look_ahead)
}
token, tokenError := lexical.Token()

look_ahead = token
if tokenError != nil {
return nil, tokenError
Expand All @@ -86,8 +87,13 @@ func gSelect() (*NodeSelect, error) {
return nil, err
}

if len(fields) == 1 && fields[0] == "*" {
s.WildCard = true
if len(fields) == 1 {
f0 := fields[0]
if f0 == "*" {
s.WildCard = true
} else if f0 == "#" {
s.Count = true
}
}
s.Fields = fields

Expand Down Expand Up @@ -159,6 +165,9 @@ func gTableParams() ([]string, error) {
}
look_ahead = token
return []string{"*"}, nil
} else if look_ahead == lexical.T_COUNT {
result, err := gCount()
return result, err
}
var fields = []string{}
if look_ahead == lexical.T_ID {
Expand All @@ -173,7 +182,41 @@ func gTableParams() ([]string, error) {
return fields, errorSyntax
}
return nil, throwSyntaxError(lexical.T_ID, look_ahead)
}

// consume count(*)
func gCount() ([]string, error) {
// by construction, T_COUNT is consumed and stored
// in the look_ahead
err := gExactlyASpecificToken(lexical.T_COUNT)
if err != nil {
return nil, err
}
err = gExactlyASpecificToken(lexical.T_PARENTH_L)
if err != nil {
return nil, err
}
err = gExactlyASpecificToken(lexical.T_WILD_CARD)
if err != nil {
return nil, err
}
err = gExactlyASpecificToken(lexical.T_PARENTH_R)
if err != nil {
return nil, err
}
return []string{"#"}, nil
}

func gExactlyASpecificToken(expected uint8) error {
if look_ahead != expected {
return throwSyntaxError(expected, look_ahead)
}
token, err := lexical.Token()
if err != nil {
return err
}
look_ahead = token
return nil
}

func gTableParamsRest(fields *[]string, count int) ([]string, error) {
Expand Down
30 changes: 30 additions & 0 deletions parser/parser_test.go
Expand Up @@ -55,6 +55,25 @@ func TestUsingWildCard(t *testing.T) {
}
}

func TestUsingCount(t *testing.T) {
New("select count(*) from users")
ast, error := AST()

if error != nil {
t.Errorf(error.Error())
}

if ast.Child == nil {
t.Errorf("Program is empty")
}

selectNode := ast.Child.(*NodeSelect)
if !selectNode.Count {
t.Errorf("Expected count setted")
}
}


func TestUsingOneFieldName(t *testing.T) {
New("select name from files")

Expand Down Expand Up @@ -119,6 +138,17 @@ func TestErrorWithUnexpectedComma(t *testing.T) {
}
}

func TestErrorWithMalformedCount(t *testing.T) {
New("select count(*]")

_, error := AST()

if error == nil {
t.Errorf("Expected error 'Expected )'")
}
}


func TestErrorWithInvalidRootNode(t *testing.T) {
New("name from files")

Expand Down
25 changes: 17 additions & 8 deletions runtime/commits.go
Expand Up @@ -2,6 +2,7 @@ package runtime

import (
"fmt"
"strconv"
"log"
"strings"

Expand All @@ -26,7 +27,7 @@ func walkCommits(n *parser.NodeProgram, visitor *RuntimeVisitor) (*TableData, er
resultFields := fields // These are the fields in output with wildcards expanded
rows := make([]tableRow, s.Limit)
usingOrder := false
if s.Order != nil {
if s.Order != nil && !s.Count {
usingOrder = true
// Check if the order by field is in the selected fields. If not, add them to selected fields list
if !utilities.IsFieldPresentInArray(fields, s.Order.Field) {
Expand All @@ -38,15 +39,16 @@ func walkCommits(n *parser.NodeProgram, visitor *RuntimeVisitor) (*TableData, er
boolRegister = true
visitor.VisitExpr(where)
if boolRegister {
newRow := make(tableRow)
for _, f := range fields {
newRow[f] = metadataCommit(f, object)
if !s.Count {
newRow := make(tableRow)
for _, f := range fields {
newRow[f] = metadataCommit(f, object)
}
rows = append(rows, newRow)
}
rows = append(rows, newRow)

counter = counter + 1
}
if !usingOrder && counter > s.Limit {
if !usingOrder && !s.Count && counter > s.Limit {
return false
}
return true
Expand All @@ -56,13 +58,20 @@ func walkCommits(n *parser.NodeProgram, visitor *RuntimeVisitor) (*TableData, er
if err != nil {
fmt.Printf(err.Error())
}
if s.Count {
newRow := make(tableRow)
// counter was started from 1!
newRow[COUNT_FIELD_NAME] = strconv.Itoa(counter-1)
counter = 2
rows = append(rows, newRow)
}
rowsSliced := rows[len(rows)-counter+1:]
rowsSliced, err = orderTable(rowsSliced, s.Order)
if err != nil {
return nil, err
}

if usingOrder && counter > s.Limit {
if usingOrder && !s.Count && counter > s.Limit {
counter = s.Limit
rowsSliced = rowsSliced[0:counter]
}
Expand Down
20 changes: 15 additions & 5 deletions runtime/reference.go
@@ -1,6 +1,7 @@
package runtime

import (
"strconv"
"log"

"github.com/cloudson/git2go"
Expand All @@ -23,7 +24,7 @@ func walkReferences(n *parser.NodeProgram, visitor *RuntimeVisitor) (*TableData,
}
rows := make([]tableRow, s.Limit)
usingOrder := false
if s.Order != nil {
if s.Order != nil && !s.Count {
usingOrder = true
}
for object, inTheEnd := iterator.Next(); inTheEnd == nil; object, inTheEnd = iterator.Next() {
Expand All @@ -36,17 +37,26 @@ func walkReferences(n *parser.NodeProgram, visitor *RuntimeVisitor) (*TableData,
if s.WildCard {
fields = builder.possibleTables[s.Tables[0]]
}
newRow := make(tableRow)
for _, f := range fields {
newRow[f] = metadataReference(f, object)
if !s.Count {
newRow := make(tableRow)
for _, f := range fields {
newRow[f] = metadataReference(f, object)
}
rows = append(rows, newRow)
}
rows = append(rows, newRow)
counter = counter + 1
if !usingOrder && counter > s.Limit {
break
}
}
}
if s.Count {
newRow := make(tableRow)
// counter was started from 1!
newRow[COUNT_FIELD_NAME] = strconv.Itoa(counter-1)
counter = 2
rows = append(rows, newRow)
}
rowsSliced := rows[len(rows)-counter+1:]
rowsSliced, err = orderTable(rowsSliced, s.Order)
if err != nil {
Expand Down
20 changes: 15 additions & 5 deletions runtime/remotes.go
@@ -1,6 +1,7 @@
package runtime

import (
"strconv"
"log"

"github.com/cloudson/git2go"
Expand All @@ -24,7 +25,7 @@ func walkRemotes(n *parser.NodeProgram, visitor *RuntimeVisitor) (*TableData, er
}
rows := make([]tableRow, s.Limit)
usingOrder := false
if s.Order != nil {
if s.Order != nil && !s.Count {
usingOrder = true
}
for _, remoteName := range remoteNames {
Expand All @@ -37,18 +38,27 @@ func walkRemotes(n *parser.NodeProgram, visitor *RuntimeVisitor) (*TableData, er
boolRegister = true
visitor.VisitExpr(where)
if boolRegister {
newRow := make(map[string]interface{})
for _, f := range fields {
newRow[f] = metadataRemote(f, object)
if !s.Count {
newRow := make(map[string]interface{})
for _, f := range fields {
newRow[f] = metadataRemote(f, object)
}
rows = append(rows, newRow)
}
rows = append(rows, newRow)

counter = counter + 1
if !usingOrder && counter > s.Limit {
break
}
}
}
if s.Count {
newRow := make(tableRow)
// counter was started from 1!
newRow[COUNT_FIELD_NAME] = strconv.Itoa(counter-1)
counter = 2
rows = append(rows, newRow)
}
rowsSliced := rows[len(rows)-counter+1:]
rowsSliced, err = orderTable(rowsSliced, s.Order)
if err != nil {
Expand Down
4 changes: 4 additions & 0 deletions runtime/runtime.go
Expand Up @@ -24,6 +24,10 @@ const (
REFERENCE_TYPE_TAG = "tag"
)

const (
COUNT_FIELD_NAME = "count"
)

var repo *git.Repository
var builder *GitBuilder
var boolRegister bool
Expand Down
26 changes: 26 additions & 0 deletions runtime/runtime_test.go
Expand Up @@ -130,3 +130,29 @@ func TestNotFoundCommitWithInStatementAndSorting(t *testing.T) {
t.Errorf(errGit.Error())
}
}

func TestFoundCommitsWithSevenInHash(t *testing.T) {
folder, errFile := filepath.Abs("../")

if errFile != nil {
t.Errorf(errFile.Error())
}

query := "select count(*) from commits where '7' in hash order by date desc limit 1000"

parser.New(query)
ast, errGit := parser.AST()
if errGit != nil {
t.Errorf(errGit.Error())
}
ast.Path = &folder
errGit = semantical.Analysis(ast)
if errGit != nil {
t.Errorf(errGit.Error())
}

typeFormat := "table"
if errGit = Run(ast, &typeFormat); errGit != nil {
t.Errorf(errGit.Error())
}
}

0 comments on commit 4767869

Please sign in to comment.