From 767685cc787e756504e0ebacf412bf980e37a68f Mon Sep 17 00:00:00 2001 From: Alex Date: Sun, 19 Nov 2023 22:55:44 +0100 Subject: [PATCH] compare dates and renamed --- eval.go | 169 ++++++++++++++++++++++++++++++++++++++---------- eval_test.go | 12 ++-- gorage.go | 19 ++---- gorage_table.go | 44 +++++++------ utils.go | 2 +- 5 files changed, 172 insertions(+), 74 deletions(-) diff --git a/eval.go b/eval.go index ac4ff1e..bbc93cb 100644 --- a/eval.go +++ b/eval.go @@ -3,6 +3,7 @@ package Gorage import ( "strconv" "strings" + "time" ) const ( @@ -11,6 +12,7 @@ const ( tokenTypeFloat = 3 tokenTypeBoolean = 4 tokenTypeChar = 5 + tokenTypeDate = 6 ) type token struct { @@ -25,6 +27,14 @@ var ( strongSplit = []string{"&&", "!&", "||", "!|"} ) +func validateDate(d string) bool { + _, err := time.Parse("2006-01-02", d) + if err != nil { + return false + } + return true +} + func compareByteArray(b1, b2 []byte) bool { if len(b1) != len(b2) { return false @@ -50,6 +60,37 @@ func convertBytesToFloat(v []byte) float64 { return r } +// -2 d1 is greater +// -1 d1 is greater than +// 0 equal +// 1 d2 is greater than +// 2 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 -2 + case td1 == td2: + return 0 + case td1 >= td2: + return -1 + case td2 >= td1: + return 1 + case td2 > td1: + return 2 + } + return 0 +} + func evaluate(f *token) *token { if f.left == nil && f.right == nil { return f @@ -62,74 +103,130 @@ func evaluate(f *token) *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) { - panic("<= is only supported for int and float") + l.tokenType == tokenTypeFloat && r.tokenType == tokenTypeInt || + l.tokenType == tokenTypeDate && r.tokenType == tokenTypeDate) { + panic("< is only supported for int, float and date") } - lv := convertBytesToFloat(l.value) - rv := convertBytesToFloat(r.value) - if lv < rv { - return &token{value: []byte("t"), tokenType: tokenTypeBoolean} + if l.tokenType == tokenTypeDate && r.tokenType == tokenTypeDate { + i := compareDates(string(l.value), string(r.value)) + if i == -2 { + 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} } - return &token{value: []byte("f"), tokenType: tokenTypeBoolean} + case ">": 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) { - panic("<= is only supported for int and float") + l.tokenType == tokenTypeFloat && r.tokenType == tokenTypeInt || + l.tokenType == tokenTypeDate && r.tokenType == tokenTypeDate) { + panic("<= is only supported for int, float and date") } - lv := convertBytesToFloat(l.value) - rv := convertBytesToFloat(r.value) - if lv > rv { - return &token{value: []byte("t"), tokenType: tokenTypeBoolean} + if l.tokenType == tokenTypeDate && r.tokenType == tokenTypeDate { + i := compareDates(string(l.value), string(r.value)) + if i == 2 { + 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} } - return &token{value: []byte("f"), tokenType: tokenTypeBoolean} + case ">=": 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) { - panic("<= is only supported for int and float") + l.tokenType == tokenTypeFloat && r.tokenType == tokenTypeInt || + l.tokenType == tokenTypeDate && r.tokenType == tokenTypeDate) { + panic("<= is only supported for int, float and date") } - lv := convertBytesToFloat(l.value) - rv := convertBytesToFloat(r.value) - if lv >= rv { - return &token{value: []byte("t"), tokenType: tokenTypeBoolean} + 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} } - return &token{value: []byte("f"), tokenType: tokenTypeBoolean} + case "<=": 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) { - panic("<= is only supported for int and float") + l.tokenType == tokenTypeFloat && r.tokenType == tokenTypeInt || + l.tokenType == tokenTypeDate && r.tokenType == tokenTypeDate) { + panic("<= is only supported for int, float and date") } - lv := convertBytesToFloat(l.value) - rv := convertBytesToFloat(r.value) - if lv <= rv { - return &token{value: []byte("t"), tokenType: tokenTypeBoolean} + 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} } - 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 compareByteArray(l.value, r.value) { - return &token{value: []byte("t"), tokenType: tokenTypeBoolean} + 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} } - 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 !compareByteArray(l.value, r.value) { - return &token{value: []byte("t"), tokenType: tokenTypeBoolean} + 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} } - return &token{value: []byte("f"), tokenType: tokenTypeBoolean} case "&&": if !(l.tokenType == tokenTypeBoolean && r.tokenType == tokenTypeBoolean) { panic("&& expects both sides to be a boolean") @@ -311,7 +408,9 @@ func parse(f string) []*token { tokenType = tokenTypeBoolean break default: - if _, err := strconv.Atoi(k); err == nil { + 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 diff --git a/eval_test.go b/eval_test.go index b4371a4..7668e3d 100644 --- a/eval_test.go +++ b/eval_test.go @@ -17,8 +17,6 @@ import ( // func TestEval(t *testing.T) { - //println(float64(int(rune('a')))) - //f := fmt.Sprintf("'%s' = 'hallo welt' & %d = 3 & %d = 4", "hallo welt", 3, 4) if eval("( 'William' == 'William' && 2 == 2 ) || 85.5 >= 90.0") != "t" { t.Fatalf("Should return true") } @@ -28,8 +26,10 @@ func TestEval(t *testing.T) { if eval("( 'Hi' == 'hi' ) || ( 1 == 1 && ( 5 != 5 !& ( t == f ) ) ) && 1.0 < 1.1") != "t" { t.Fatalf("Should be true") } - //traverseTree(tr[0]) - - //_printTree(&tr[0]) - //_printTree(&tr[0]) + if eval("2023-11-19 == 2023-11-19") != "t" { + t.Fatalf("Should be true") + } + if eval("2023-11-19 == 2023-11-19") != "t" { + t.Fatalf("Should be true") + } } diff --git a/gorage.go b/gorage.go index 5130d35..09a5fd4 100644 --- a/gorage.go +++ b/gorage.go @@ -12,14 +12,14 @@ type Gorage struct { AllowDuplicated bool Log bool Path string - Tables []GorageTable + Tables []Table } /* Select a table from the loaded Gorage */ -func (g *Gorage) FromTable(name string) *GorageTable { +func (g *Gorage) FromTable(name string) *Table { k := -1 for i, v := range g.Tables { if v.Name == name { @@ -60,17 +60,17 @@ Create a table. Two tables with the same name in the same gorage are NOT possible */ -func (g *Gorage) CreateTable(name string) *GorageTable { +func (g *Gorage) CreateTable(name string) *Table { if g.TableExists(name) { if g.Log { gprint("CreateTable", "Table already exists") } return nil } - t := GorageTable{ + t := Table{ Name: name, host: g, - Columns: []GorageColumn{}, + Columns: []Column{}, Rows: [][]interface{}{}, } g.Tables = append(g.Tables, t) @@ -111,13 +111,6 @@ func Open(path string) *Gorage { for i, _ := range g.Tables { g.Tables[i].host = &g } - go func() { - for _, v := range g.Tables { - if c := v.getColByType(TIMEOUT); c != nil { - - } - } - }() return &g } @@ -144,7 +137,7 @@ func Create(path string, allowDuplicates, log bool) *Gorage { Log: log, AllowDuplicated: allowDuplicates, Path: path, - Tables: []GorageTable{}, + Tables: []Table{}, } file, _ := json.MarshalIndent(g, "", " ") err = os.WriteFile(path, file, 0644) diff --git a/gorage_table.go b/gorage_table.go index d17ccdb..eeef42b 100644 --- a/gorage_table.go +++ b/gorage_table.go @@ -12,23 +12,23 @@ const ( STRING = 1 BOOLEAN = 2 FLOAT = 3 - TIMEOUT = 4 + DATE = 4 ) -type GorageColumn struct { +type Column struct { Name string Datatype int } -type GorageTable struct { +type Table struct { sync.Mutex Name string - Columns []GorageColumn + Columns []Column Rows [][]interface{} host *Gorage } -func (g *GorageTable) getColByType(t int) *GorageColumn { +func (g *Table) getColByType(t int) *Column { if len(g.Columns) == 0 { return nil } @@ -40,7 +40,7 @@ func (g *GorageTable) getColByType(t int) *GorageColumn { return nil } -func (g *GorageTable) getColAndIndexByName(name string) (*GorageColumn, int) { +func (g *Table) getColAndIndexByName(name string) (*Column, int) { if len(g.Columns) == 0 { return nil, -1 } @@ -61,7 +61,7 @@ func (g *GorageTable) getColAndIndexByName(name string) (*GorageColumn, int) { /* the name is the column name */ -func (g *GorageTable) RemoveColumn(name string) *GorageTable { +func (g *Table) RemoveColumn(name string) *Table { c, idx := g.getColAndIndexByName(name) if c == nil { @@ -83,9 +83,9 @@ func (g *GorageTable) RemoveColumn(name string) *GorageTable { /* name is the name of the column. The datatype can be choosen from the provieded and implemented datatypes (f.e. INT,STRING) */ -func (g *GorageTable) AddColumn(name string, datatype int) *GorageTable { +func (g *Table) AddColumn(name string, datatype int) *Table { if v, _ := g.getColAndIndexByName(name); v == nil { - g.Columns = append(g.Columns, GorageColumn{ + g.Columns = append(g.Columns, Column{ name, datatype, }) @@ -115,9 +115,9 @@ func (g *GorageTable) AddColumn(name string, datatype int) *GorageTable { /* f is the evaluate string. See github README.md for examples */ -func (g *GorageTable) Where(f string) *GorageTable { +func (g *Table) Where(f string) *Table { g.Lock() - res := &GorageTable{ + res := &Table{ Name: g.Name, Columns: g.Columns, host: g.host, @@ -134,13 +134,19 @@ func (g *GorageTable) Where(f string) *GorageTable { panic("Column not found") } switch col.Datatype { + case DATE: + if v[colIdx] == nil { + k = fmt.Sprintf("f") + } + k = fmt.Sprintf("%s", v[colIdx]) + break case STRING: if v[colIdx] == nil { k = fmt.Sprintf("f") } k = fmt.Sprintf("'%s'", v[colIdx]) break - case FLOAT, INT, TIMEOUT: + case FLOAT, INT: if v[colIdx] == nil { k = fmt.Sprintf("f") } else { @@ -176,7 +182,7 @@ func (g *GorageTable) Where(f string) *GorageTable { data is a map, where the key is the column and the interace is the value. the datatype of the interface needs to match the datatype, which the column represents */ -func (g *GorageTable) Update(data map[string]interface{}) *GorageTable { +func (g *Table) Update(data map[string]interface{}) *Table { //g.Lock() rt := g.host.FromTable(g.Name) // we need to get the table again to do persistent changes to it in memory rt.Lock() @@ -210,7 +216,7 @@ func (g *GorageTable) Update(data map[string]interface{}) *GorageTable { /* Deletes Rows */ -func (g *GorageTable) Delete() { +func (g *Table) Delete() { realTable := g.host.FromTable(g.Name) // we need to get the table again to do persistent changes to it in memory if realTable == nil { @@ -239,12 +245,12 @@ func (g *GorageTable) Delete() { /* columns is a string array, in which the wanted columns are stored */ -func (g *GorageTable) Select(columns []string) *GorageTable { +func (g *Table) Select(columns []string) *Table { g.Lock() var columnIdx []int - tmp := &GorageTable{ + tmp := &Table{ Name: g.Name, - Columns: []GorageColumn{}, + Columns: []Column{}, host: g.host, Rows: [][]interface{}{}, } @@ -271,7 +277,7 @@ func (g *GorageTable) Select(columns []string) *GorageTable { return tmp } -func (g *GorageTable) isDuplicate(hash uint32) bool { +func (g *Table) isDuplicate(hash uint32) bool { for _, v := range g.Rows { if hash == computeHash(v) { return true @@ -286,7 +292,7 @@ If a cell shall be empty you can use nil. *Remember*: You can not compare in nil value, when using the column in a where condition */ -func (g *GorageTable) Insert(data []interface{}) { +func (g *Table) Insert(data []interface{}) { g.Lock() if len(data) != len(g.Columns) { panic(fmt.Errorf("column count and data count are different")) diff --git a/utils.go b/utils.go index 1abb01c..e56d894 100644 --- a/utils.go +++ b/utils.go @@ -51,7 +51,7 @@ func compareRows(a, b []interface{}) bool { return computeHash(a) == computeHash(b) } -func validateDatatype(is interface{}, c GorageColumn) bool { +func validateDatatype(is interface{}, c Column) bool { switch is.(type) { case int: if c.Datatype != INT {