From 46088e51cbde24647994575760351a1fcbfbb4df Mon Sep 17 00:00:00 2001 From: Alex <90933044+0x3alex@users.noreply.github.com> Date: Wed, 27 Dec 2023 14:32:35 +0100 Subject: [PATCH] clean up, avg and sum function --- eval.go | 430 ------------------------------ eval/eval.go | 71 +++++ eval_test.go => eval/eval_test.go | 12 +- eval/operations.go | 101 +++++++ eval/parser.go | 144 ++++++++++ eval/utils.go | 106 ++++++++ go.mod | 2 + gorage_table.go | 24 +- gorage_test.go | 5 +- utils.go | 4 - 10 files changed, 456 insertions(+), 443 deletions(-) delete mode 100644 eval.go create mode 100644 eval/eval.go rename eval_test.go => eval/eval_test.go (58%) create mode 100644 eval/operations.go create mode 100644 eval/parser.go create mode 100644 eval/utils.go diff --git a/eval.go b/eval.go deleted file mode 100644 index 5401b76..0000000 --- a/eval.go +++ /dev/null @@ -1,430 +0,0 @@ -package Gorage - -import ( - "strconv" - "strings" - "time" -) - -const ( - tokenTypeInt = 1 - tokenTypeString = 2 - tokenTypeFloat = 3 - tokenTypeBoolean = 4 - tokenTypeChar = 5 - tokenTypeDate = 6 -) - -type token struct { - value []byte - left *token - right *token - tokenType int -} - -var ( - //keywords = []string{"&&", "!&", "||", "!|", "==", "!="} - strongSplit = []string{"&&", "!&", "||", "!|"} -) - -/* -* HELPER FUNCTIONS - */ - -func validateDate(d string) bool { - _, err := time.Parse("2006-01-02", d) - if err != nil { - return false - } - return true -} - -func splitForStrings(f string) (r []string) { - var tmp string - inString := false - for _, v := range f { - if string(v) != "'" { - tmp += string(v) - } else { - if inString { - tmp += string(v) - inString = false - r = append(r, tmp) - tmp = "" - continue - } - inString = true - r = append(r, tmp) - tmp = "" - tmp += string(v) - - } - } - if len(tmp) > 0 { - r = append(r, tmp) - } - return r -} - -func compareByteArray(b1, b2 []byte) bool { - if len(b1) != len(b2) { - return false - } - for i, _ := range b1 { - if b1[i] != b2[i] { - return false - } - } - return true -} - -func convertBytesToFloat(v []byte) float64 { - s := string(v) - r, err := strconv.ParseFloat(s, 64) - if err != nil { - //check if - if len(s) == 1 { //prob. a char - return float64(int(rune(s[0]))) //formatted like +9.00..e+001 - not good for comparison - } - panic("Value used in >=,<=,<,> is not a number") - } - return r -} - -// -1 d1 is greater -// 0 equal -// 1 d2 is greater -func compareDates(d1, d2 string) int { - t1, err := time.Parse("2006-01-02", d1) - if err != nil { - panic("Error parsing dates") - } - td1 := t1.Unix() - t2, err := time.Parse("2006-01-02", d2) - if err != nil { - panic("Error parsing dates") - } - td2 := t2.Unix() - switch { - case td1 > td2: - return -1 - case td1 == td2: - return 0 - case td2 > td1: - return 1 - } - return 0 -} - -func compareCheck(r, l *token) { - if !(r.tokenType == tokenTypeInt && l.tokenType == tokenTypeInt || - l.tokenType == tokenTypeFloat && r.tokenType == tokenTypeFloat || - l.tokenType == tokenTypeInt && r.tokenType == tokenTypeFloat || - l.tokenType == tokenTypeFloat && r.tokenType == tokenTypeInt || - l.tokenType == tokenTypeDate && r.tokenType == tokenTypeDate) { - panic("< is only supported for int, float and date") - } -} - -/*£ -* ----------------------------------------- - */ - -func evaluate(f *token) *token { - if f.left == nil && f.right == nil { - return f - } - l := evaluate(f.left) - m := f - r := evaluate(f.right) - switch string(m.value) { - case "<": - compareCheck(r, l) - if l.tokenType == tokenTypeDate && r.tokenType == tokenTypeDate { - i := compareDates(string(l.value), string(r.value)) - if i == 1 { - return &token{value: []byte("t"), tokenType: tokenTypeBoolean} - } - return &token{value: []byte("f"), tokenType: tokenTypeBoolean} - } else { - lv := convertBytesToFloat(l.value) - rv := convertBytesToFloat(r.value) - if lv < rv { - return &token{value: []byte("t"), tokenType: tokenTypeBoolean} - } - return &token{value: []byte("f"), tokenType: tokenTypeBoolean} - } - case ">": - compareCheck(r, l) - if l.tokenType == tokenTypeDate && r.tokenType == tokenTypeDate { - i := compareDates(string(l.value), string(r.value)) - if i == -1 { - return &token{value: []byte("t"), tokenType: tokenTypeBoolean} - } - return &token{value: []byte("f"), tokenType: tokenTypeBoolean} - } else { - lv := convertBytesToFloat(l.value) - rv := convertBytesToFloat(r.value) - if lv > rv { - return &token{value: []byte("t"), tokenType: tokenTypeBoolean} - } - return &token{value: []byte("f"), tokenType: tokenTypeBoolean} - } - case ">=": - compareCheck(r, l) - if l.tokenType == tokenTypeDate && r.tokenType == tokenTypeDate { - i := compareDates(string(l.value), string(r.value)) - if i == -1 || i == 0 { - return &token{value: []byte("t"), tokenType: tokenTypeBoolean} - } - return &token{value: []byte("f"), tokenType: tokenTypeBoolean} - } else { - lv := convertBytesToFloat(l.value) - rv := convertBytesToFloat(r.value) - if lv >= rv { - return &token{value: []byte("t"), tokenType: tokenTypeBoolean} - } - return &token{value: []byte("f"), tokenType: tokenTypeBoolean} - } - case "<=": - compareCheck(r, l) - if l.tokenType == tokenTypeDate && r.tokenType == tokenTypeDate { - i := compareDates(string(l.value), string(r.value)) - if i == 1 || i == 0 { - return &token{value: []byte("t"), tokenType: tokenTypeBoolean} - } - return &token{value: []byte("f"), tokenType: tokenTypeBoolean} - } else { - lv := convertBytesToFloat(l.value) - rv := convertBytesToFloat(r.value) - if lv <= rv { - return &token{value: []byte("t"), tokenType: tokenTypeBoolean} - } - return &token{value: []byte("f"), tokenType: tokenTypeBoolean} - } - case "==": - if !(l.tokenType == r.tokenType || - l.tokenType == tokenTypeChar && r.tokenType == tokenTypeInt || - l.tokenType == tokenTypeInt && r.tokenType == tokenTypeChar) { - panic("mismatching == types") - } - if l.tokenType == tokenTypeDate && r.tokenType == tokenTypeDate { - i := compareDates(string(l.value), string(r.value)) - if i == 0 { - return &token{value: []byte("t"), tokenType: tokenTypeBoolean} - } - return &token{value: []byte("f"), tokenType: tokenTypeBoolean} - } else { - if compareByteArray(l.value, r.value) { - return &token{value: []byte("t"), tokenType: tokenTypeBoolean} - } - return &token{value: []byte("f"), tokenType: tokenTypeBoolean} - } - case "!=": - if !(l.tokenType == r.tokenType || - l.tokenType == tokenTypeChar && r.tokenType == tokenTypeInt || - l.tokenType == tokenTypeInt && r.tokenType == tokenTypeChar) { - panic("mismatching == types") - } - if l.tokenType == tokenTypeDate && r.tokenType == tokenTypeDate { - i := compareDates(string(l.value), string(r.value)) - if i != 0 { - return &token{value: []byte("t"), tokenType: tokenTypeBoolean} - } - return &token{value: []byte("f"), tokenType: tokenTypeBoolean} - } else { - if !compareByteArray(l.value, r.value) { - return &token{value: []byte("t"), tokenType: tokenTypeBoolean} - } - return &token{value: []byte("f"), tokenType: tokenTypeBoolean} - } - case "&&": - if !(l.tokenType == tokenTypeBoolean && r.tokenType == tokenTypeBoolean) { - panic("&& expects both sides to be a boolean") - } - if compareByteArray(l.value, []byte("t")) && compareByteArray(r.value, []byte("t")) { - return &token{value: []byte("t"), tokenType: tokenTypeBoolean} - } - return &token{value: []byte("f"), tokenType: tokenTypeBoolean} - case "||": - if !(l.tokenType == tokenTypeBoolean && r.tokenType == tokenTypeBoolean) { - panic("&& expects both sides to be a boolean") - } - if compareByteArray(l.value, []byte("t")) || compareByteArray(r.value, []byte("t")) { - return &token{value: []byte("t"), tokenType: tokenTypeBoolean} - } - return &token{value: []byte("f"), tokenType: tokenTypeBoolean} - case "!&": - if !(l.tokenType == tokenTypeBoolean && r.tokenType == tokenTypeBoolean) { - panic("&& expects both sides to be a boolean") - } - if !(compareByteArray(l.value, []byte("t")) && compareByteArray(r.value, []byte("t"))) { - return &token{value: []byte("t"), tokenType: tokenTypeBoolean} - } - return &token{value: []byte("f"), tokenType: tokenTypeBoolean} - case "!|": - if !(l.tokenType == tokenTypeBoolean && r.tokenType == tokenTypeBoolean) { - panic("&& expects both sides to be a boolean") - } - if !(compareByteArray(l.value, []byte("t")) || compareByteArray(r.value, []byte("t"))) { - return &token{value: []byte("t"), tokenType: tokenTypeBoolean} - } - return &token{value: []byte("f"), tokenType: tokenTypeBoolean} - } - panic("UNREACHABLE: NOT A VALID OPERATOR " + string(m.value)) - return nil -} - -func toTree(nodes []*token) []*token { - var op string - var query []*token - var base []*token - - buildQuery := func() { - nq := &token{ - value: []byte(op), - left: query[0], - right: query[1], - } - query = []*token{} - op = "" - query = append(query, nq) - } - - for i := 0; i < len(nodes); i++ { - isOp := false - for _, k := range strongSplit { - if compareByteArray([]byte(k), nodes[i].value) { - op = k - isOp = true - } - } - if string(nodes[i].value) == "(" && !isOp { - openCount := 1 - var tmp []*token - i += 1 - for ; i < len(nodes); i++ { - //println(string(nodes[i].value)) - if string(nodes[i].value) == "(" { - openCount++ - } - if string(nodes[i].value) == ")" { - openCount-- - if openCount == 0 { - break - } - - } - tmp = append(tmp, nodes[i]) - } - if i == len(nodes) { - panic("No ) found") - } - query = append(query, toTree(tmp)[0]) - if len(query) == 2 { - buildQuery() - } - continue - } - if !isOp { - base = append(base, nodes[i]) - } - if len(base) == 3 { - ne := &token{ - value: base[1].value, - left: &token{value: base[0].value, tokenType: base[0].tokenType}, - right: &token{value: base[2].value, tokenType: base[2].tokenType}, - } - query = append(query, ne) - if len(query) == 2 { - buildQuery() - } - base = []*token{} - } - } - return query -} - -func traverseTree(t *token) { - if t == nil { - return - } - traverseTree(t.left) - println(string(t.value)) - traverseTree(t.right) -} - -func parse(f string) []*token { - var nodes []*token - split := splitForStrings(f) - if len(split) == 0 { - split = strings.Split(f, " ") //no string in f. We can split normal - } - for _, v := range split { - if strings.Contains(v, "'") { - tokenType := tokenTypeString - v = strings.ReplaceAll(v, "'", "") - v = strings.TrimSpace(v) - if len(v) == 0 { - continue - } - //strings and chars both use ' '. The length decides if it's a char or a string - if len(v) == 1 { - tokenType = tokenTypeChar - } - nodes = append(nodes, &token{ - value: []byte(v), - left: nil, - right: nil, - tokenType: tokenType, - }) - continue - } - s := strings.Split(v, " ") - for _, k := range s { - var tokenType int - k = strings.TrimSpace(k) - if len(k) == 0 { - continue - } - switch k { - case "t", "f": - tokenType = tokenTypeBoolean - break - default: - if validateDate(k) { - tokenType = tokenTypeDate - } else if _, err := strconv.Atoi(k); err == nil { - tokenType = tokenTypeInt - } else if _, err = strconv.ParseFloat(k, 64); err == nil { - tokenType = tokenTypeFloat - } - break - } - - nodes = append(nodes, &token{ - value: []byte(k), - left: nil, - right: nil, - tokenType: tokenType, - }) - } - } - return nodes -} - -func eval(f string) bool { - p := parse(f) - t := toTree(p) - if len(t) == 0 { - panic("Error while eval") - } - e := evaluate(t[0]) - if e == nil { - panic("Eval returned nil") - } - if string(e.value) == "t" { - return true - } - return false -} diff --git a/eval/eval.go b/eval/eval.go new file mode 100644 index 0000000..40a6366 --- /dev/null +++ b/eval/eval.go @@ -0,0 +1,71 @@ +package eval + +const ( + tokenTypeInt = 1 + tokenTypeString = 2 + tokenTypeFloat = 3 + tokenTypeBoolean = 4 + tokenTypeChar = 5 + tokenTypeDate = 6 +) + +type token struct { + value []byte + left *token + right *token + tokenType int +} + +var ( + //keywords = []string{"&&", "!&", "||", "!|", "==", "!="} + strongSplit = []string{"&&", "!&", "||", "!|"} +) + +func evaluate(f *token) *token { + if f.left == nil && f.right == nil { + return f + } + l := evaluate(f.left) + m := f + r := evaluate(f.right) + switch string(m.value) { + case "<": + return less(l, r) + case ">": + return greater(l, r) + case ">=": + return greaterThan(l, r) + case "<=": + return lessThan(l, r) + case "==": + return equal(l, r) + case "!=": + return notEqual(l, r) + case "&&": + return and(l, r) + case "||": + return or(l, r) + case "!&": + return nand(l, r) + case "!|": + return nor(l, r) + } + panic("UNREACHABLE: NOT A VALID OPERATOR " + string(m.value)) + return nil +} + +func Eval(f string) bool { + p := parse(f) + t := toTree(p) + if len(t) == 0 { + panic("Error while eval") + } + e := evaluate(t[0]) + if e == nil { + panic("Eval returned nil") + } + if string(e.value) == "t" { + return true + } + return false +} diff --git a/eval_test.go b/eval/eval_test.go similarity index 58% rename from eval_test.go rename to eval/eval_test.go index 1ad45ae..93491cb 100644 --- a/eval_test.go +++ b/eval/eval_test.go @@ -1,4 +1,4 @@ -package Gorage +package eval import ( "testing" @@ -17,19 +17,19 @@ import ( // func TestEval(t *testing.T) { - if !eval("( 'William' == 'William' && 2 == 2 ) || 85.5 >= 90.0") { + if !Eval("( 'William' == 'William' && 2 == 2 ) || 85.5 >= 90.0") { t.Fatalf("Should return true") } - if eval("1 != 1") { + if Eval("1 != 1") { t.Fatalf("Should be false") } - if !eval("( 'Hi' == 'hi' ) || ( 1 == 1 && ( 5 != 5 !& ( t == f ) ) ) && 1.0 < 1.1") { + if !Eval("( 'Hi' == 'hi' ) || ( 1 == 1 && ( 5 != 5 !& ( t == f ) ) ) && 1.0 < 1.1") { t.Fatalf("Should be true") } - if !eval("2023-11-19 == 2023-11-19") { + if !Eval("2023-11-19 == 2023-11-19") { t.Fatalf("Should be true") } - if !eval("2022-11-19 <= 2023-11-19") { + if !Eval("2022-11-19 <= 2023-11-19") { t.Fatalf("Should be true") } } diff --git a/eval/operations.go b/eval/operations.go new file mode 100644 index 0000000..26bc816 --- /dev/null +++ b/eval/operations.go @@ -0,0 +1,101 @@ +package eval + +func less(l, r *token) *token { + checkIfCompatible(l, r) + if l.tokenType == tokenTypeDate && compareDates(string(l.value), string(r.value)) == 1 { + return &token{value: []byte("t"), tokenType: tokenTypeBoolean} + } else if convertBytesToFloat(l.value) < convertBytesToFloat(r.value) { + return &token{value: []byte("t"), tokenType: tokenTypeBoolean} + } + return &token{value: []byte("f"), tokenType: tokenTypeBoolean} +} + +func greater(l, r *token) *token { + checkIfCompatible(l, r) + if l.tokenType == tokenTypeDate && compareDates(string(l.value), string(r.value)) == -1 { + return &token{value: []byte("t"), tokenType: tokenTypeBoolean} + } else if convertBytesToFloat(l.value) > convertBytesToFloat(r.value) { + return &token{value: []byte("t"), tokenType: tokenTypeBoolean} + } + return &token{value: []byte("f"), tokenType: tokenTypeBoolean} +} + +func greaterThan(l, r *token) *token { + checkIfCompatible(l, r) + if l.tokenType == tokenTypeDate && compareDates(string(l.value), string(r.value)) <= 0 { + return &token{value: []byte("t"), tokenType: tokenTypeBoolean} + } else if convertBytesToFloat(l.value) >= convertBytesToFloat(r.value) { + return &token{value: []byte("t"), tokenType: tokenTypeBoolean} + } + return &token{value: []byte("f"), tokenType: tokenTypeBoolean} +} + +func lessThan(l, r *token) *token { + checkIfCompatible(l, r) + if l.tokenType == tokenTypeDate && compareDates(string(l.value), string(r.value)) >= 0 { + return &token{value: []byte("t"), tokenType: tokenTypeBoolean} + } else if convertBytesToFloat(l.value) <= convertBytesToFloat(r.value) { + return &token{value: []byte("t"), tokenType: tokenTypeBoolean} + } + return &token{value: []byte("f"), tokenType: tokenTypeBoolean} +} + +func equal(l, r *token) *token { + checkIfCompatible(l, r) + if l.tokenType == tokenTypeDate && compareDates(string(l.value), string(r.value)) == 0 { + return &token{value: []byte("t"), tokenType: tokenTypeBoolean} + } else if compareByteArray(l.value, r.value) { + return &token{value: []byte("t"), tokenType: tokenTypeBoolean} + } + return &token{value: []byte("f"), tokenType: tokenTypeBoolean} +} + +func notEqual(l, r *token) *token { + checkIfCompatible(l, r) + if l.tokenType == tokenTypeDate && compareDates(string(l.value), string(r.value)) != 0 { + return &token{value: []byte("t"), tokenType: tokenTypeBoolean} + } else if !compareByteArray(l.value, r.value) { + return &token{value: []byte("t"), tokenType: tokenTypeBoolean} + } + return &token{value: []byte("f"), tokenType: tokenTypeBoolean} +} + +func and(l, r *token) *token { + if !(l.tokenType == tokenTypeBoolean && r.tokenType == tokenTypeBoolean) { + panic("&& expects both sides to be a boolean") + } + if compareByteArray(l.value, []byte("t")) && compareByteArray(r.value, []byte("t")) { + return &token{value: []byte("t"), tokenType: tokenTypeBoolean} + } + return &token{value: []byte("f"), tokenType: tokenTypeBoolean} +} + +func or(l, r *token) *token { + if !(l.tokenType == tokenTypeBoolean && r.tokenType == tokenTypeBoolean) { + panic("&& expects both sides to be a boolean") + } + if compareByteArray(l.value, []byte("t")) || compareByteArray(r.value, []byte("t")) { + return &token{value: []byte("t"), tokenType: tokenTypeBoolean} + } + return &token{value: []byte("f"), tokenType: tokenTypeBoolean} +} + +func nand(l, r *token) *token { + if !(l.tokenType == tokenTypeBoolean && r.tokenType == tokenTypeBoolean) { + panic("&& expects both sides to be a boolean") + } + if !(compareByteArray(l.value, []byte("t")) && compareByteArray(r.value, []byte("t"))) { + return &token{value: []byte("t"), tokenType: tokenTypeBoolean} + } + return &token{value: []byte("f"), tokenType: tokenTypeBoolean} +} + +func nor(l, r *token) *token { + if !(l.tokenType == tokenTypeBoolean && r.tokenType == tokenTypeBoolean) { + panic("&& expects both sides to be a boolean") + } + if !(compareByteArray(l.value, []byte("t")) || compareByteArray(r.value, []byte("t"))) { + return &token{value: []byte("t"), tokenType: tokenTypeBoolean} + } + return &token{value: []byte("f"), tokenType: tokenTypeBoolean} +} diff --git a/eval/parser.go b/eval/parser.go new file mode 100644 index 0000000..3062a80 --- /dev/null +++ b/eval/parser.go @@ -0,0 +1,144 @@ +package eval + +import ( + "strconv" + "strings" +) + +func toTree(nodes []*token) []*token { + var op string + var query []*token + var base []*token + + buildQuery := func() { + nq := &token{ + value: []byte(op), + left: query[0], + right: query[1], + } + query = []*token{} + op = "" + query = append(query, nq) + } + + for i := 0; i < len(nodes); i++ { + isOp := false + for _, k := range strongSplit { + if compareByteArray([]byte(k), nodes[i].value) { + op = k + isOp = true + } + } + //handle braces + if string(nodes[i].value) == "(" && !isOp { + openCount := 1 + var tmp []*token + i += 1 + for ; i < len(nodes); i++ { + if string(nodes[i].value) == "(" { + openCount++ + } + if string(nodes[i].value) == ")" { + openCount-- + if openCount == 0 { + break + } + + } + tmp = append(tmp, nodes[i]) + } + if i == len(nodes) { + panic("No ) found") + } + query = append(query, toTree(tmp)[0]) + if len(query) == 2 { + buildQuery() + } + continue + } + if !isOp { + base = append(base, nodes[i]) + } + if len(base) == 3 { + ne := &token{ + value: base[1].value, + left: &token{value: base[0].value, tokenType: base[0].tokenType}, + right: &token{value: base[2].value, tokenType: base[2].tokenType}, + } + query = append(query, ne) + if len(query) == 2 { + buildQuery() + } + base = []*token{} + } + } + return query +} + +func traverseTree(t *token) { + if t == nil { + return + } + traverseTree(t.left) + println(string(t.value)) + traverseTree(t.right) +} + +func parse(f string) []*token { + var nodes []*token + split := splitForStrings(f) + if len(split) == 0 { + split = strings.Split(f, " ") //no string in f. We can split normal + } + for _, v := range split { + if strings.Contains(v, "'") { + tokenType := tokenTypeString + v = strings.ReplaceAll(v, "'", "") + v = strings.TrimSpace(v) + if len(v) == 0 { + continue + } + //strings and chars both use ' '. The length decides if it's a char or a string + if len(v) == 1 { + tokenType = tokenTypeChar + } + nodes = append(nodes, &token{ + value: []byte(v), + left: nil, + right: nil, + tokenType: tokenType, + }) + continue + } + s := strings.Split(v, " ") + for _, k := range s { + var tokenType int + k = strings.TrimSpace(k) + if len(k) == 0 { + continue + } + switch k { + case "t", "f": + tokenType = tokenTypeBoolean + break + default: + if validateDate(k) { + tokenType = tokenTypeDate + } else if _, err := strconv.Atoi(k); err == nil { + tokenType = tokenTypeInt + } else if _, err = strconv.ParseFloat(k, 64); err == nil { + tokenType = tokenTypeFloat + } + break + } + + nodes = append(nodes, &token{ + value: []byte(k), + left: nil, + right: nil, + tokenType: tokenType, + }) + } + } + return nodes +} diff --git a/eval/utils.go b/eval/utils.go new file mode 100644 index 0000000..a088332 --- /dev/null +++ b/eval/utils.go @@ -0,0 +1,106 @@ +package eval + +import ( + "fmt" + "strconv" + "time" +) + +func validateDate(d string) bool { + _, err := time.Parse("2006-01-02", d) + if err != nil { + return false + } + return true +} + +func splitForStrings(f string) (r []string) { + var tmp string + inString := false + for _, v := range f { + if string(v) != "'" { + tmp += string(v) + } else { + if inString { + tmp += string(v) + inString = false + r = append(r, tmp) + tmp = "" + continue + } + inString = true + r = append(r, tmp) + tmp = "" + tmp += string(v) + + } + } + if len(tmp) > 0 { + r = append(r, tmp) + } + return r +} + +func compareByteArray(b1, b2 []byte) bool { + if len(b1) != len(b2) { + return false + } + for i, _ := range b1 { + if b1[i] != b2[i] { + return false + } + } + return true +} + +func convertBytesToFloat(v []byte) float64 { + s := string(v) + r, err := strconv.ParseFloat(s, 64) + if err != nil { + //check if + if len(s) == 1 { //prob. a char + return float64(int(rune(s[0]))) //formatted like +9.00..e+001 - not good for comparison + } + panic("Value used in >=,<=,<,> is not a number") + } + return r +} + +// -1 d1 is greater +// 0 equal +// 1 d2 is greater +func compareDates(d1, d2 string) int { + t1, err := time.Parse("2006-01-02", d1) + if err != nil { + panic("Error parsing dates") + } + t2, err := time.Parse("2006-01-02", d2) + if err != nil { + panic("Error parsing dates") + } + td2 := t2.Unix() + td1 := t1.Unix() + switch { + case td1 > td2: + return -1 + case td1 == td2: + return 0 + case td2 > td1: + return 1 + } + return 0 +} + +func checkIfCompatible(r, l *token) { + if !(r.tokenType == tokenTypeInt && l.tokenType == tokenTypeInt || + l.tokenType == tokenTypeFloat && r.tokenType == tokenTypeFloat || + l.tokenType == tokenTypeInt && r.tokenType == tokenTypeFloat || + l.tokenType == tokenTypeFloat && r.tokenType == tokenTypeInt || + l.tokenType == tokenTypeDate && r.tokenType == tokenTypeDate || + l.tokenType == tokenTypeChar && r.tokenType == tokenTypeInt || + l.tokenType == tokenTypeInt && r.tokenType == tokenTypeChar || + l.tokenType == tokenTypeString && r.tokenType == tokenTypeString || + l.tokenType == tokenTypeBoolean && r.tokenType == tokenTypeBoolean) { + panic(fmt.Sprintf("cant do operation for tokens %s , %s", l.value, r.value)) + } +} diff --git a/go.mod b/go.mod index 4f87a94..d46d960 100644 --- a/go.mod +++ b/go.mod @@ -1 +1,3 @@ module github.com/0x3alex/gorage + +go 1.18 diff --git a/gorage_table.go b/gorage_table.go index 5229cd0..c8bce19 100644 --- a/gorage_table.go +++ b/gorage_table.go @@ -2,6 +2,7 @@ package Gorage import ( "fmt" + "github.com/0x3alex/gorage/eval" "strconv" "strings" ) @@ -269,7 +270,7 @@ func (g *Table) where(f string) *Table { tmp = append(tmp, k) } q := strings.Join(tmp, " ") - if eval(q) { + if eval.Eval(q) { res.Rows = append(res.Rows, v) } } @@ -395,6 +396,27 @@ func (g *Table) delete() *Table { return g } +func (g *Table) Sum(column string) float64 { + c, idx := g.getColAndIndexByName(column) + if c == nil || (c.Datatype != INT && c.Datatype != FLOAT) { + return 0 + } + sum := 0.0 + for _, row := range g.Rows { + v := row[idx] + if i, ok := v.(int); ok { + sum += float64(i) + } else if f, ok := v.(float64); ok { + sum += f + } + } + return sum +} + +func (g *Table) Avg(column string) float64 { + return g.Sum(column) / float64(len(g.Rows)) +} + /* columns is a string array, in which the wanted columns are stored */ diff --git a/gorage_test.go b/gorage_test.go index 035d899..501c48d 100644 --- a/gorage_test.go +++ b/gorage_test.go @@ -169,8 +169,9 @@ func TestCreateMemOnly(t *testing.T) { g := CreateMemOnly(true, false) tab := g.CreateTable("Test") tab.AddColumn("Name", STRING) - tab.Insert([]interface{}{"Tom"}) - tab.Insert([]interface{}{"Tom"}) + tab.AddColumn("Age", INT) + tab.Insert([]interface{}{"Tom", 1}) + tab.Insert([]interface{}{"Tom", 3}) tab.Where(":Name == 'Tom'") tab.Update(map[string]interface{}{ "Name": "John", diff --git a/utils.go b/utils.go index e56d894..af942f5 100644 --- a/utils.go +++ b/utils.go @@ -73,10 +73,6 @@ func validateDatatype(is interface{}, c Column) bool { if c.Datatype != FLOAT { return false } - default: - if is != nil { - return false - } } return true