From 0f70253cc068dc7214cfc642fa92885644a7215b Mon Sep 17 00:00:00 2001 From: Ben Johnson Date: Mon, 28 Feb 2022 12:17:07 -0700 Subject: [PATCH] Add SQL SELECT mapping test --- sql/handler_test.go | 52 +++++++++++++++++++++++++++++++++++++++++++++ sql/query.go | 18 +++++++++------- sql/select.go | 4 ++-- 3 files changed, 64 insertions(+), 10 deletions(-) diff --git a/sql/handler_test.go b/sql/handler_test.go index 6eaba476f..26ffce0ce 100644 --- a/sql/handler_test.go +++ b/sql/handler_test.go @@ -3,10 +3,13 @@ package sql_test import ( "context" + "math" "testing" + "github.com/molecula/featurebase/v3" "github.com/molecula/featurebase/v3/sql" "github.com/molecula/featurebase/v3/test" + "vitess.io/vitess/go/vt/sqlparser" ) func TestHandler(t *testing.T) { @@ -28,3 +31,52 @@ func TestHandler(t *testing.T) { } } + +func TestSelectHandler_MapSelect(t *testing.T) { + cluster := test.MustRunCluster(t, 1) + defer cluster.Close() + api := cluster.GetNode(0).API + + if _, err := api.CreateIndex(context.Background(), "i", pilosa.IndexOptions{}); err != nil { + t.Fatal(err) + } else if _, err = api.CreateField(context.Background(), "i", "bytes", pilosa.OptFieldTypeInt(math.MinInt64, math.MaxInt64)); err != nil { + t.Fatal(err) + } else if _, err = api.CreateField(context.Background(), "i", "duration_time", pilosa.OptFieldTypeInt(math.MinInt64, math.MaxInt64)); err != nil { + t.Fatal(err) + } else if _, err = api.CreateField(context.Background(), "i", "timestamp", pilosa.OptFieldTypeTimestamp(pilosa.DefaultEpoch, pilosa.TimeUnitSeconds)); err != nil { + t.Fatal(err) + } + + for _, tt := range []struct { + name string + input string + output string + }{ + { + name: "WhereTimestamp", + input: `SELECT * FROM i WHERE timestamp>"2000-01-01T00:00:00Z"`, + output: `Extract(Row(timestamp>"2000-01-01T00:00:00Z"),Rows(bytes),Rows(duration_time),Rows(timestamp))`, + }, + + { + name: "WhereTimestampWithSpaces", + input: `SELECT * FROM i WHERE timestamp > "2000-01-01T00:00:00Z"`, + output: `Extract(Row(timestamp>"2000-01-01T00:00:00Z"),Rows(bytes),Rows(duration_time),Rows(timestamp))`, + }, + } { + t.Run(tt.name, func(t *testing.T) { + query, err := sql.NewMapper().MapSQL(tt.input) + if err != nil { + t.Fatal(err) + } + + h := sql.NewSelectHandler(api) + mr, err := h.MapSelect(context.Background(), query.Statement.(*sqlparser.Select), query.Mask) + if err != nil { + t.Fatal(err) + } else if got, want := mr.Query, tt.output; got != want { + t.Fatalf("unexpected pql\npql: %s\nwant: %s", got, want) + } + }) + } +} diff --git a/sql/query.go b/sql/query.go index 0f4db98b7..23eaa28fb 100644 --- a/sql/query.go +++ b/sql/query.go @@ -15,32 +15,32 @@ const timeFormat = "2006-01-02T15:04" // LT creates a less than query. func LT(fieldName string, value interface{}) string { - return fmt.Sprintf("Row(%s<%s)", fieldName, intOrFloat(value)) + return fmt.Sprintf("Row(%s<%s)", fieldName, formatValue(value)) } // LTE creates a less than or equal query. func LTE(fieldName string, value interface{}) string { - return fmt.Sprintf("Row(%s<=%s)", fieldName, intOrFloat(value)) + return fmt.Sprintf("Row(%s<=%s)", fieldName, formatValue(value)) } // GT creates a greater than query. func GT(fieldName string, value interface{}) string { - return fmt.Sprintf("Row(%s>%s)", fieldName, intOrFloat(value)) + return fmt.Sprintf("Row(%s>%s)", fieldName, formatValue(value)) } // GTE creates a greater than or equal query. func GTE(fieldName string, value interface{}) string { - return fmt.Sprintf("Row(%s>=%s)", fieldName, intOrFloat(value)) + return fmt.Sprintf("Row(%s>=%s)", fieldName, formatValue(value)) } // Equals creates an equals query. func Equals(fieldName string, value interface{}) string { - return fmt.Sprintf("Row(%s=%s)", fieldName, intOrFloat(value)) + return fmt.Sprintf("Row(%s=%s)", fieldName, formatValue(value)) } // NotEquals creates a not equals query. func NotEquals(fieldName string, value interface{}) string { - return fmt.Sprintf("Row(%s!=%s)", fieldName, intOrFloat(value)) + return fmt.Sprintf("Row(%s!=%s)", fieldName, formatValue(value)) } // NotNull creates a not equal to null query. @@ -94,7 +94,7 @@ func Like(fieldName string, pattern string) string { // Between creates a between query. func Between(fieldName string, a interface{}, b interface{}) string { - return fmt.Sprintf("Row(%s >< [%s,%s])", fieldName, intOrFloat(a), intOrFloat(b)) + return fmt.Sprintf("Row(%s >< [%s,%s])", fieldName, formatValue(a), formatValue(b)) } // Distinct creates a Distinct query. @@ -269,8 +269,10 @@ func formatIDKey(idKey interface{}) (string, error) { } } -func intOrFloat(value interface{}) string { +func formatValue(value interface{}) string { switch value.(type) { + case string: + return fmt.Sprintf("%q", value) case float64, float32: // In order to test expected values, we set the precision // to 8. TODO: It's likely we'll need to address this diff --git a/sql/select.go b/sql/select.go index 3297ee0fc..d7afc2eec 100644 --- a/sql/select.go +++ b/sql/select.go @@ -34,14 +34,14 @@ func (s *SelectHandler) Handle(ctx context.Context, mapped *MappedSQL) (pproto.T if !ok { return nil, fmt.Errorf("statement is not type select: %T", mapped.Statement) } - mr, err := s.mapSelect(ctx, stmt, mapped.Mask) + mr, err := s.MapSelect(ctx, stmt, mapped.Mask) if err != nil { return nil, errors.Wrap(err, "mapping select") } return s.execMappingResult(ctx, mr, mapped.SQL) } -func (s *SelectHandler) mapSelect(ctx context.Context, selectStmt *sqlparser.Select, qm QueryMask) (*MappingResult, error) { +func (s *SelectHandler) MapSelect(ctx context.Context, selectStmt *sqlparser.Select, qm QueryMask) (*MappingResult, error) { // Get the handler for this query mask. hndlr := s.router.handler(qm) if hndlr == nil {