Skip to content

Commit ee26d3f

Browse files
authored
Merge pull request #13 from team-gonyo/feature/chapter13
Feature/chapter13
2 parents 277619e + a090d12 commit ee26d3f

25 files changed

Lines changed: 1657 additions & 14 deletions

parse/lexer.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ func (l *Lexer) EatDelim(d rune) error {
8383

8484
func (l *Lexer) EatIntConstant() (int32, error) {
8585
if ok := l.MatchIntConstant(); !ok {
86-
return 0, fmt.Errorf("tried to eat int token(%v), but got other token type(%v)", tokenInt, l.currentTokenType)
86+
return 0, fmt.Errorf("tried to eat int token(%v), but got other token type(%v) with input '%v'", tokenInt, l.currentTokenType, l.input)
8787
}
8888
retval, err := strconv.Atoi(l.currentToken)
8989
if err != nil {
@@ -100,7 +100,7 @@ func (l *Lexer) EatIntConstant() (int32, error) {
100100

101101
func (l *Lexer) EatStringConstant() (string, error) {
102102
if ok := l.MatchStringConstant(); !ok {
103-
return "", fmt.Errorf("tried to eat string token(%v), but got other token type(%v)", tokenString, l.currentTokenType)
103+
return "", fmt.Errorf("tried to eat string token(%v), but got other token type(%v) with input '%v'", tokenString, l.currentTokenType, l.input)
104104
}
105105
retval := l.currentToken
106106
err := l.extractToken()
@@ -116,7 +116,7 @@ func (l *Lexer) EatStringConstant() (string, error) {
116116
func (l *Lexer) EatKeyword(keyword string) error {
117117
keyword = strings.ToLower(keyword)
118118
if ok := l.MatchKeyword(keyword); !ok {
119-
return fmt.Errorf("tried to eat string token(%v), but got other token type(%v)", tokenKeyword, l.currentTokenType)
119+
return fmt.Errorf("tried to eat string token(%v), but got other token type(%v) with input '%v'", tokenKeyword, l.currentTokenType, l.input)
120120
}
121121
if keyword != strings.ToLower(l.currentToken) {
122122
return fmt.Errorf("expected keyword is %v, but got %v", keyword, l.currentToken)
@@ -132,7 +132,7 @@ func (l *Lexer) EatKeyword(keyword string) error {
132132

133133
func (l *Lexer) EatId() (string, error) {
134134
if ok := l.MatchId(); !ok {
135-
return "", fmt.Errorf("tried to eat string token(%v), but got other token type(%v)", tokenIdentifier, l.currentTokenType)
135+
return "", fmt.Errorf("tried to eat string token(%v), but got other token type(%v) with input '%v'", tokenIdentifier, l.currentTokenType, l.input)
136136
}
137137
retval := l.currentToken
138138
err := l.extractToken()
@@ -291,7 +291,7 @@ func isAllowedSymbol(ch rune) bool {
291291
func isIdentChar(ch rune) bool {
292292
isDigit := isDigitChar(ch)
293293
isChar := isChar(ch)
294-
return isDigit || isChar
294+
return isDigit || isChar || ch == '_'
295295
}
296296

297297
func isIdentStart(ch rune) bool {

parse/lexer_test.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,16 @@ func TestLexerToken(t *testing.T) {
3636
},
3737
},
3838
},
39+
{
40+
desc: "parse hoge_fuga",
41+
input: "hoge_fuga",
42+
expected: []expects{
43+
{
44+
"hoge_fuga",
45+
tokenIdentifier,
46+
},
47+
},
48+
},
3949
{
4050
desc: "parse 123",
4151
input: "123",

parse/parser_test.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,20 @@ func TestQueryParser(t *testing.T) {
4646
)),
4747
},
4848
},
49+
{
50+
desc: "parse SELECT hoge, piyo FROM fuga WHERE hoge = 1",
51+
input: "SELECT hoge, piyo FROM fuga WHERE hoge = 1",
52+
expected: &QueryData{
53+
fields: []string{"hoge", "piyo"},
54+
tables: map[string]interface{}{"fuga": struct{}{}},
55+
pred: query.NewPredicateWithTerm(query.NewTerm(
56+
query.NewFieldNameExpression("hoge"),
57+
query.NewConstantExpression(
58+
scan.NewIntConstant(1),
59+
),
60+
)),
61+
},
62+
},
4963
{
5064
desc: "case insensitive",
5165
input: "SELECT hoge, fuga FROM piyo WHERE hoge = 1",

plan/group_by_plan.go

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package plan
2+
3+
import (
4+
"github.com/team-gonyo/gonyo-db/query"
5+
"github.com/team-gonyo/gonyo-db/record"
6+
"github.com/team-gonyo/gonyo-db/scan"
7+
"github.com/team-gonyo/gonyo-db/tx"
8+
)
9+
10+
type GroupByPlan struct {
11+
plan Plan
12+
groupFields []string
13+
aggregationFunc []query.AggregationFn
14+
schema *record.Schema
15+
}
16+
17+
var _ Plan = (*GroupByPlan)(nil)
18+
19+
func NewGroupByPlan(
20+
tx *tx.Transaction,
21+
plan Plan,
22+
groupFields []string,
23+
aggregationFuncs []query.AggregationFn,
24+
) *GroupByPlan {
25+
schema := record.NewSchema()
26+
for _, fieldName := range groupFields {
27+
schema.Add(fieldName, plan.Schema())
28+
}
29+
for _, fn := range aggregationFuncs {
30+
schema.AddIntField(fn.FieldName())
31+
}
32+
33+
return &GroupByPlan{
34+
plan: NewSortPlan(plan, groupFields, tx),
35+
groupFields: groupFields,
36+
aggregationFunc: aggregationFuncs,
37+
schema: schema,
38+
}
39+
}
40+
41+
func (g *GroupByPlan) Open() (scan.Scan, error) {
42+
s, err := g.plan.Open()
43+
if err != nil {
44+
return nil, err
45+
}
46+
47+
return query.NewGroupByScan(s, g.groupFields, g.aggregationFunc), nil
48+
}
49+
50+
func (g *GroupByPlan) BlocksAccessed() (int, error) {
51+
return g.plan.BlocksAccessed()
52+
}
53+
54+
func (g *GroupByPlan) RecordsOutput() int {
55+
groups := 1
56+
for _, fieldName := range g.groupFields {
57+
groups *= g.plan.DistinctValues(fieldName)
58+
}
59+
return groups
60+
}
61+
62+
func (g *GroupByPlan) DistinctValues(fldName string) int {
63+
if g.plan.Schema().HasField(fldName) {
64+
return g.plan.DistinctValues(fldName)
65+
}
66+
67+
return g.RecordsOutput()
68+
}
69+
70+
func (g *GroupByPlan) Schema() *record.Schema {
71+
return g.schema
72+
}

plan/group_by_plan_test.go

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package plan
2+
3+
import (
4+
"testing"
5+
6+
"github.com/team-gonyo/gonyo-db/query"
7+
)
8+
9+
func TestGroupBy(t *testing.T) {
10+
db, _, _ := createTester(t)
11+
tx, err := db.NewTx()
12+
if err != nil {
13+
t.Fatalf("Failed to create transaction: %v", err)
14+
}
15+
defer tx.Commit()
16+
17+
todoDetailPlan, err := NewTablePlan(tx, "todo_detail", db.MetadataManager())
18+
if err != nil {
19+
t.Fatalf("Failed to create todo_detail plan: %v", err)
20+
}
21+
groupByPlan := NewGroupByPlan(tx, todoDetailPlan, []string{"todo_id"}, []query.AggregationFn{NewMaxFunc("some_score")})
22+
groupBySchema := groupByPlan.Schema()
23+
for _, field := range groupBySchema.Fields() {
24+
t.Logf("Field: %s", field)
25+
}
26+
27+
// スキャンを開く
28+
groupByScan, err := groupByPlan.Open()
29+
if err != nil {
30+
t.Fatalf("Failed to open group by scan: %v", err)
31+
}
32+
defer groupByScan.Close()
33+
34+
// スキャンを実行して、結果を確認
35+
count := 0
36+
for {
37+
hasNext, err := groupByScan.Next()
38+
if err != nil {
39+
t.Fatalf("Failed to get next record: %v", err)
40+
}
41+
if !hasNext {
42+
break
43+
}
44+
45+
todoId, err := groupByScan.GetInt("todo_id")
46+
if err != nil {
47+
t.Fatalf("Failed to get todo_id: %v", err)
48+
}
49+
someScore, err := groupByScan.GetInt("maxofsome_score")
50+
if err != nil {
51+
t.Fatalf("Failed to get some_score: %v", err)
52+
}
53+
54+
t.Logf("todo_id: %d, some_score: %d", todoId, someScore)
55+
56+
count++
57+
}
58+
if count != 2 {
59+
t.Fatalf("Expected 2 records, got %d", count)
60+
}
61+
}

plan/index_join_plan.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,12 @@ func (ijp *IndexJoinPlan) Open() (*query.IndexJoinScan, error) {
5151
return query.NewIndexJoinScan(scan1, idx, ijp.joinField, ts)
5252
}
5353

54-
func (ijp *IndexJoinPlan) BlocksAccessed() int {
55-
return ijp.plan1.BlocksAccessed() + (ijp.plan1.RecordsOutput() * ijp.ii.BlocksAccessed()) + ijp.RecordsOutput()
54+
func (ijp *IndexJoinPlan) BlocksAccessed() (int, error) {
55+
plan1BlockAccesses, err := ijp.plan1.BlocksAccessed()
56+
if err != nil {
57+
return 0, err
58+
}
59+
return plan1BlockAccesses + (ijp.plan1.RecordsOutput() * ijp.ii.BlocksAccessed()) + ijp.RecordsOutput(), nil
5660
}
5761

5862
func (ijp *IndexJoinPlan) RecordsOutput() int {

plan/materialize_plan.go

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
package plan
2+
3+
import (
4+
"math"
5+
6+
"github.com/team-gonyo/gonyo-db/query"
7+
"github.com/team-gonyo/gonyo-db/record"
8+
"github.com/team-gonyo/gonyo-db/scan"
9+
"github.com/team-gonyo/gonyo-db/tx"
10+
)
11+
12+
var _ Plan = (*MaterializePlan)(nil)
13+
14+
type MaterializePlan struct {
15+
srcPlan Plan
16+
tx *tx.Transaction
17+
}
18+
19+
func NewMaterializePlan(tx *tx.Transaction, srcPlan Plan) *MaterializePlan {
20+
return &MaterializePlan{
21+
srcPlan: srcPlan,
22+
tx: tx,
23+
}
24+
}
25+
26+
// これを呼ぶと仮想テーブルが作成される
27+
func (m *MaterializePlan) Open() (scan.Scan, error) {
28+
schema := m.srcPlan.Schema()
29+
tempTable, err := query.NewTempTable(m.tx, schema)
30+
if err != nil {
31+
return nil, err
32+
}
33+
34+
src, err := m.srcPlan.Open()
35+
if err != nil {
36+
return nil, err
37+
}
38+
defer src.Close()
39+
40+
dest, err := tempTable.Open()
41+
if err != nil {
42+
return nil, err
43+
}
44+
45+
// srcを進めるループ
46+
for {
47+
ok, err := src.Next()
48+
if !ok {
49+
break
50+
}
51+
if err != nil {
52+
return nil, err
53+
}
54+
55+
// destを空きスロットまで移動
56+
err = dest.Insert()
57+
if err != nil {
58+
return nil, err
59+
}
60+
// schemaの各フィールドを舐めるループ
61+
for _, fieldName := range schema.Fields() {
62+
val, err := src.GetVal(fieldName)
63+
if err != nil {
64+
return nil, err
65+
}
66+
67+
err = dest.SetVal(fieldName, val)
68+
if err != nil {
69+
return nil, err
70+
}
71+
}
72+
}
73+
74+
dest.BeforeFirst()
75+
return dest, nil
76+
}
77+
78+
func (m *MaterializePlan) BlocksAccessed() (int, error) {
79+
// create a dummy layout object to calculate slot size
80+
// FIXME: エラー握りつぶし!!
81+
// Plan#BlocksAccessed()のシグネチャを変えないとエラーをエスカレーションできない
82+
// だが、12章のIndexPlanもPlanを実装していて、そっちがマージされてからじゃないとコンフリクトしそう
83+
// 溜まったPRが落ち着いて、直せるようになった時点で対処すること
84+
layout, err := record.NewLayoutFromSchema(m.Schema())
85+
if err != nil {
86+
return 0, err
87+
}
88+
89+
recordPerBlock := (float64(m.tx.BlockSize())) / float64(layout.SlotSize())
90+
return int(math.Ceil(float64(m.srcPlan.RecordsOutput()) / recordPerBlock)), nil
91+
}
92+
93+
func (m *MaterializePlan) RecordsOutput() int {
94+
return m.srcPlan.RecordsOutput()
95+
}
96+
97+
func (m *MaterializePlan) DistinctValues(fldName string) int {
98+
return m.srcPlan.DistinctValues(fldName)
99+
}
100+
101+
func (m *MaterializePlan) Schema() *record.Schema {
102+
return m.srcPlan.Schema()
103+
}

0 commit comments

Comments
 (0)