mirror of
https://github.com/featurebasedb/featurebase.git
synced 2026-09-07 17:15:56 +00:00
.
This commit is contained in:
parent
4dfd713189
commit
5b43a84a3b
10 changed files with 3513 additions and 0 deletions
199
cmd/sauron/design.org
Normal file
199
cmd/sauron/design.org
Normal file
|
|
@ -0,0 +1,199 @@
|
|||
|
||||
* Oracle Design
|
||||
- setup server that intersepts PQL queries and proxies to live pilosa server
|
||||
- implement all the pilosa functions using SQL, specifically the sqlite flavor. This will
|
||||
provide validation against pilosa results
|
||||
- provide timings for each query path, PQL and SQL and compare
|
||||
- this is intended to be a correctness check and not a performance check. the
|
||||
sql solution is likely to be pretty slow with realitively small numbers ~1millions
|
||||
#+BEGIN_SRC
|
||||
.─────.
|
||||
,' `. ┌────────────┐ ┌────────┐
|
||||
;pilosa json: │ Sauron │ ┌──▶│ pilosa │
|
||||
: client ;──────────▶ │ │───┤ └────────┘
|
||||
╲ ╱ │ │ │ ┌────────┐
|
||||
`. ,' └────────────┘ └──▶│ sqlite │
|
||||
`───' └────────┘
|
||||
#+END_SRC
|
||||
* Setup Sql (sqlite)
|
||||
#+name: setup test
|
||||
#+header: :results silent
|
||||
#+header: :db poc.db
|
||||
#+BEGIN_SRC sqlite
|
||||
create table bits (field string,row string, column bigint);
|
||||
create table bsi (field string,column bigint , val bigint);
|
||||
CREATE VIEW columns as
|
||||
select distinct column from bits
|
||||
UNION
|
||||
select distinct column from bsi;
|
||||
|
||||
INSERT INTO bits VALUES( 'color','red',1);
|
||||
INSERT INTO bits VALUES( 'color','red',2);
|
||||
INSERT INTO bits VALUES( 'color','red',3);
|
||||
INSERT INTO bits VALUES( 'color','red',4);
|
||||
INSERT INTO bits VALUES( 'color','red',5);
|
||||
INSERT INTO bits VALUES( 'color','green',2);
|
||||
INSERT INTO bits VALUES( 'color','green',4);
|
||||
INSERT INTO bits VALUES( 'color','green',6);
|
||||
INSERT INTO bits VALUES( 'color','green',8);
|
||||
INSERT INTO bits VALUES( 'color','yellow',1);
|
||||
INSERT INTO bits VALUES( 'color','yellow',3);
|
||||
INSERT INTO bits VALUES( 'color','yellow',5);
|
||||
INSERT INTO bits VALUES( 'color','yellow',7);
|
||||
INSERT INTO bits VALUES( 'color','orange',5);
|
||||
INSERT INTO bits VALUES( 'color','orange',6);
|
||||
INSERT INTO bits VALUES( 'color','orange',7);
|
||||
INSERT INTO bits VALUES( 'color','orange',8);
|
||||
|
||||
INSERT INTO bsi VALUES( 'luminosity',1, 1);
|
||||
INSERT INTO bsi VALUES( 'luminosity',2, 2);
|
||||
INSERT INTO bsi VALUES( 'luminosity',3, 4);
|
||||
INSERT INTO bsi VALUES( 'luminosity',4, 5);
|
||||
INSERT INTO bsi VALUES( 'luminosity',5, 4);
|
||||
INSERT INTO bsi VALUES( 'luminosity',6, 3);
|
||||
INSERT INTO bsi VALUES( 'luminosity',7, 2);
|
||||
INSERT INTO bsi VALUES( 'luminosity',8, 1);
|
||||
#+END_SRC
|
||||
|
||||
* TODO PQL to SQL TODO [10/11]
|
||||
- [X] Row(color=red)
|
||||
#+begin_src go
|
||||
(*pql.Query)(&pql.Query{
|
||||
Calls: ([]*pql.Call)([]*pql.Call{
|
||||
(*pql.Call)(&pql.Call{
|
||||
Name: (string)("Row"),
|
||||
Args: (map[string]interface{})(map[string]interface{}{
|
||||
(string)("color"): (string)("red"),
|
||||
}),
|
||||
Children: ([]*pql.Call)(nil),
|
||||
Type: (pql.CallType)(0),
|
||||
Precomputed: (map[uint64]interface{})(nil),
|
||||
}),
|
||||
}),
|
||||
callStack: ([]*pql.callStackElem)([]*pql.callStackElem{}),
|
||||
conditional: ([]string)(nil),
|
||||
})
|
||||
#+end_src
|
||||
#+BEGIN_SRC sqlite
|
||||
select column from bits where field="color" AND row="red"
|
||||
#+END_SRC
|
||||
- [X] Count(Row(color=red))
|
||||
#+begin_src go
|
||||
(*pql.Query)(&pql.Query{
|
||||
Calls: ([]*pql.Call)([]*pql.Call{
|
||||
(*pql.Call)(&pql.Call{
|
||||
Name: (string)("Count"),
|
||||
Args: (map[string]interface{})(nil),
|
||||
Children: ([]*pql.Call)([]*pql.Call{
|
||||
(*pql.Call)(&pql.Call{
|
||||
Name: (string)("Row"),
|
||||
Args: (map[string]interface{})(map[string]interface{}{
|
||||
(string)("color"): (string)("red"),
|
||||
}),
|
||||
Children: ([]*pql.Call)(nil),
|
||||
Type: (pql.CallType)(0),
|
||||
Precomputed: (map[uint64]interface{})(nil),
|
||||
}),
|
||||
}),
|
||||
Type: (pql.CallType)(0),
|
||||
Precomputed: (map[uint64]interface{})(nil),
|
||||
}),
|
||||
}),
|
||||
callStack: ([]*pql.callStackElem)([]*pql.callStackElem{}),
|
||||
conditional: ([]string)(nil),
|
||||
})
|
||||
|
||||
#+end_src
|
||||
#+BEGIN_SRC sql
|
||||
select count(*) from(
|
||||
select column from bits where field="color" AND row="red"
|
||||
)
|
||||
#+END_SRC
|
||||
- [X] Intersect(Row(color=red),Row(color=green))
|
||||
#+BEGIN_SRC sql
|
||||
select column from bits where field="color" AND row="red"
|
||||
intersect
|
||||
select column from bits where field="color" AND row="green"
|
||||
#+END_SRC
|
||||
- [X] Union(Row(color=red),Row(color=green))
|
||||
#+BEGIN_SRC sql
|
||||
select column from bits where field="color" AND row="red"
|
||||
union
|
||||
select column from bits where field="color" AND row="green"
|
||||
#+END_SRC
|
||||
- [X] Difference(Row(color=red),Row(color=green))
|
||||
#+BEGIN_SRC sql
|
||||
select column from bits where field="color" AND row="red"
|
||||
except
|
||||
select column from bits where field="color" AND row="green"
|
||||
#+END_SRC
|
||||
- [X] Not(Row(color=red))
|
||||
#+BEGIN_SRC sql
|
||||
select column from columns
|
||||
except
|
||||
select column from bits where field="color" AND row="red"
|
||||
#+END_SRC
|
||||
- [X] Xor(Row(color=red),Row(color=green))
|
||||
#+BEGIN_SRC sql
|
||||
select column from (
|
||||
select column from bits where field="color" AND row="green"
|
||||
union
|
||||
select column from bits where field="color" AND row="red"
|
||||
)
|
||||
except
|
||||
select column from (
|
||||
select column from bits where field="color" AND row="green"
|
||||
intersect
|
||||
select column from bits where field="color" AND row="red"
|
||||
)
|
||||
#+END_SRC
|
||||
- [X] Distinct(field=luminosity)
|
||||
#+BEGIN_SRC sql
|
||||
select distinct val from bsi where field="lumin"
|
||||
#+END_SRC
|
||||
- [X] Distinct(Row(color="red"),field=luminosity)
|
||||
#+BEGIN_SRC sql
|
||||
select distinct val from bsi
|
||||
where field="luminosity" ANDcolumn in (
|
||||
select column from bits where row="color" AND row="red"
|
||||
)
|
||||
#+END_SRC
|
||||
- [X] Distinct(Intersect(Row(color="red"),Row(color="green")),luminosity)
|
||||
#+BEGIN_SRC sql
|
||||
select distinct val from bsi
|
||||
where column in (
|
||||
select column from bits where field="color" AND row="red"
|
||||
intersect
|
||||
select column from bits where field="color" AND row="green"
|
||||
)
|
||||
AND field="luminosity"
|
||||
#+END_SRC
|
||||
|
||||
- [X] Count(Union(Intersect(Row(color=red),Row(color=green)),Intersect(Row(color=yellow),Row(color=orange))))
|
||||
#+begin_src sqlite
|
||||
select count(*) from (
|
||||
select column from (
|
||||
select column from bits where field="color" AND row="green"
|
||||
intersect
|
||||
select column from bits where field="color" AND row="red"
|
||||
)
|
||||
union
|
||||
select column from (
|
||||
select column from bits where field="color" AND row="yellow"
|
||||
intersect
|
||||
select column from bits where field="color" AND row="orange"
|
||||
))
|
||||
|
||||
- [ ] Rows(color)
|
||||
#+begin_src sqlite
|
||||
select distinct row from bits where field="color"
|
||||
#+end_src
|
||||
|
||||
- [ ] GroupBy(Rows(color))
|
||||
#+BEGIN_SRC sql
|
||||
select row,count(*) from bits where field="color" group by row;
|
||||
#+END_SRC
|
||||
- [ ] GroupBy(Rows(color), Rows(other), limit=7)
|
||||
#+BEGIN_SRC sql
|
||||
select row,count(*) from bits where field="color" group by row;
|
||||
#+END_SRC
|
||||
274
cmd/sauron/httputil.go
Normal file
274
cmd/sauron/httputil.go
Normal file
|
|
@ -0,0 +1,274 @@
|
|||
// Copyright 2017 Pilosa Corp.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"log"
|
||||
"mime"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/pilosa/pilosa/v2"
|
||||
"github.com/pilosa/pilosa/v2/pql"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// validHeaderAcceptJSON returns false if one or more Accept
|
||||
// headers are present, but none of them are "application/json"
|
||||
// (or any matching wildcard). Otherwise returns true.
|
||||
func validHeaderAcceptJSON(header http.Header) bool {
|
||||
return validHeaderAcceptType(header, "application", "json")
|
||||
}
|
||||
|
||||
func validHeaderAcceptType(header http.Header, typ, subtyp string) bool {
|
||||
if v, found := header["Accept"]; found {
|
||||
for _, v := range v {
|
||||
t, _, err := mime.ParseMediaType(v)
|
||||
if err != nil {
|
||||
switch err {
|
||||
case mime.ErrInvalidMediaParameter:
|
||||
// This is an optional feature, so we can keep going anyway.
|
||||
default:
|
||||
continue
|
||||
}
|
||||
}
|
||||
spl := strings.SplitN(t, "/", 2)
|
||||
if len(spl) < 2 {
|
||||
continue
|
||||
}
|
||||
switch {
|
||||
case spl[0] == typ && spl[1] == subtyp:
|
||||
return true
|
||||
case spl[0] == "*" && spl[1] == subtyp:
|
||||
return true
|
||||
case spl[0] == typ && spl[1] == "*":
|
||||
return true
|
||||
case spl[0] == "*" && spl[1] == "*":
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// successResponse is a general success/error struct for http responses.
|
||||
type successResponse struct {
|
||||
//h *Handler
|
||||
Success bool `json:"success"`
|
||||
Name string `json:"name,omitempty"`
|
||||
CreatedAt int64 `json:"createdAt,omitempty"`
|
||||
//Error *Error `json:"error,omitempty"`
|
||||
Error error
|
||||
}
|
||||
|
||||
// check determines success or failure based on the error.
|
||||
// It also returns the corresponding http status code.
|
||||
func (r *successResponse) check(err error) (statusCode int) {
|
||||
if err == nil {
|
||||
r.Success = true
|
||||
return 0
|
||||
}
|
||||
|
||||
cause := errors.Cause(err)
|
||||
|
||||
// Determine HTTP status code based on the error type.
|
||||
switch cause.(type) {
|
||||
case pilosa.BadRequestError:
|
||||
statusCode = http.StatusBadRequest
|
||||
case pilosa.ConflictError:
|
||||
statusCode = http.StatusConflict
|
||||
case pilosa.NotFoundError:
|
||||
statusCode = http.StatusNotFound
|
||||
default:
|
||||
statusCode = http.StatusInternalServerError
|
||||
}
|
||||
|
||||
r.Success = false
|
||||
r.Error = err // = &Error{Message: err.Error()}
|
||||
|
||||
return statusCode
|
||||
}
|
||||
|
||||
// write sends a response to the http.ResponseWriter based on the success
|
||||
// status and the error.
|
||||
func (r *successResponse) write(w http.ResponseWriter, err error) {
|
||||
// Apply the error and get the status code.
|
||||
statusCode := r.check(err)
|
||||
|
||||
// Marshal the json response.
|
||||
msg, err := json.Marshal(r)
|
||||
if err != nil {
|
||||
http.Error(w, string(msg), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// Write the response.
|
||||
if statusCode == 0 {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_, err := w.Write(msg)
|
||||
if err != nil {
|
||||
log.Printf("error writing response: %v", err)
|
||||
return
|
||||
}
|
||||
_, err = w.Write([]byte("\n"))
|
||||
if err != nil {
|
||||
log.Printf("error writing newline after response: %v", err)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
http.Error(w, string(msg), statusCode)
|
||||
}
|
||||
}
|
||||
|
||||
type postFieldRequest struct {
|
||||
Options fieldOptions `json:"options"`
|
||||
}
|
||||
|
||||
// fieldOptions tracks pilosa.FieldOptions. It is made up of pointers to values,
|
||||
// and used for input validation.
|
||||
type fieldOptions struct {
|
||||
Type string `json:"type,omitempty"`
|
||||
CacheType *string `json:"cacheType,omitempty"`
|
||||
CacheSize *uint32 `json:"cacheSize,omitempty"`
|
||||
Min *pql.Decimal `json:"min,omitempty"`
|
||||
Max *pql.Decimal `json:"max,omitempty"`
|
||||
Scale *int64 `json:"scale,omitempty"`
|
||||
TimeQuantum *pilosa.TimeQuantum `json:"timeQuantum,omitempty"`
|
||||
Keys *bool `json:"keys,omitempty"`
|
||||
NoStandardView bool `json:"noStandardView,omitempty"`
|
||||
ForeignIndex *string `json:"foreignIndex,omitempty"`
|
||||
}
|
||||
|
||||
func (o *fieldOptions) ToPilosaFieldOptions() *pilosa.FieldOptions {
|
||||
|
||||
fo := &pilosa.FieldOptions{
|
||||
Type: o.Type,
|
||||
|
||||
/*
|
||||
//Base int64 `json:"base,omitempty"`
|
||||
//BitDepth uint `json:"bitDepth,omitempty"`
|
||||
Min: *o.Min,
|
||||
Max: *o.Max,
|
||||
Scale: *o.Scale,
|
||||
NoStandardView: o.NoStandardView,
|
||||
CacheSize: *o.CacheSize,
|
||||
CacheType: *o.CacheType,
|
||||
TimeQuantum: *o.TimeQuantum,
|
||||
ForeignIndex: *o.ForeignIndex,
|
||||
*/
|
||||
}
|
||||
if o.Keys != nil {
|
||||
fo.Keys = *o.Keys
|
||||
}
|
||||
return fo
|
||||
}
|
||||
|
||||
func (o *fieldOptions) validate() error {
|
||||
// Pointers to default values.
|
||||
defaultCacheType := pilosa.DefaultCacheType
|
||||
defaultCacheSize := uint32(pilosa.DefaultCacheSize)
|
||||
|
||||
switch o.Type {
|
||||
case pilosa.FieldTypeSet, "":
|
||||
// Because FieldTypeSet is the default, its arguments are
|
||||
// not required. Instead, the defaults are applied whenever
|
||||
// a value does not exist.
|
||||
if o.Type == "" {
|
||||
o.Type = pilosa.FieldTypeSet
|
||||
}
|
||||
if o.CacheType == nil {
|
||||
o.CacheType = &defaultCacheType
|
||||
}
|
||||
if o.CacheSize == nil {
|
||||
o.CacheSize = &defaultCacheSize
|
||||
}
|
||||
if o.Min != nil {
|
||||
return pilosa.NewBadRequestError(errors.New("min does not apply to field type set"))
|
||||
} else if o.Max != nil {
|
||||
return pilosa.NewBadRequestError(errors.New("max does not apply to field type set"))
|
||||
} else if o.TimeQuantum != nil {
|
||||
return pilosa.NewBadRequestError(errors.New("timeQuantum does not apply to field type set"))
|
||||
}
|
||||
case pilosa.FieldTypeInt:
|
||||
if o.CacheType != nil {
|
||||
return pilosa.NewBadRequestError(errors.New("cacheType does not apply to field type int"))
|
||||
} else if o.CacheSize != nil {
|
||||
return pilosa.NewBadRequestError(errors.New("cacheSize does not apply to field type int"))
|
||||
} else if o.TimeQuantum != nil {
|
||||
return pilosa.NewBadRequestError(errors.New("timeQuantum does not apply to field type int"))
|
||||
} else if o.ForeignIndex != nil && o.Type == pilosa.FieldTypeDecimal {
|
||||
return pilosa.NewBadRequestError(errors.New("decimal field cannot be a foreign key"))
|
||||
}
|
||||
case pilosa.FieldTypeDecimal:
|
||||
if o.Scale == nil {
|
||||
return pilosa.NewBadRequestError(errors.New("decimal field requires a scale argument"))
|
||||
} else if o.CacheType != nil {
|
||||
return pilosa.NewBadRequestError(errors.New("cacheType does not apply to field type int"))
|
||||
} else if o.CacheSize != nil {
|
||||
return pilosa.NewBadRequestError(errors.New("cacheSize does not apply to field type int"))
|
||||
} else if o.TimeQuantum != nil {
|
||||
return pilosa.NewBadRequestError(errors.New("timeQuantum does not apply to field type int"))
|
||||
} else if o.ForeignIndex != nil && o.Type == pilosa.FieldTypeDecimal {
|
||||
return pilosa.NewBadRequestError(errors.New("decimal field cannot be a foreign key"))
|
||||
}
|
||||
case pilosa.FieldTypeTime:
|
||||
if o.CacheType != nil {
|
||||
return pilosa.NewBadRequestError(errors.New("cacheType does not apply to field type time"))
|
||||
} else if o.CacheSize != nil {
|
||||
return pilosa.NewBadRequestError(errors.New("cacheSize does not apply to field type time"))
|
||||
} else if o.Min != nil {
|
||||
return pilosa.NewBadRequestError(errors.New("min does not apply to field type time"))
|
||||
} else if o.Max != nil {
|
||||
return pilosa.NewBadRequestError(errors.New("max does not apply to field type time"))
|
||||
} else if o.TimeQuantum == nil {
|
||||
return pilosa.NewBadRequestError(errors.New("timeQuantum is required for field type time"))
|
||||
}
|
||||
case pilosa.FieldTypeMutex:
|
||||
if o.CacheType == nil {
|
||||
o.CacheType = &defaultCacheType
|
||||
}
|
||||
if o.CacheSize == nil {
|
||||
o.CacheSize = &defaultCacheSize
|
||||
}
|
||||
if o.Min != nil {
|
||||
return pilosa.NewBadRequestError(errors.New("min does not apply to field type mutex"))
|
||||
} else if o.Max != nil {
|
||||
return pilosa.NewBadRequestError(errors.New("max does not apply to field type mutex"))
|
||||
} else if o.TimeQuantum != nil {
|
||||
return pilosa.NewBadRequestError(errors.New("timeQuantum does not apply to field type mutex"))
|
||||
}
|
||||
case pilosa.FieldTypeBool:
|
||||
if o.CacheType != nil {
|
||||
return pilosa.NewBadRequestError(errors.New("cacheType does not apply to field type bool"))
|
||||
} else if o.CacheSize != nil {
|
||||
return pilosa.NewBadRequestError(errors.New("cacheSize does not apply to field type bool"))
|
||||
} else if o.Min != nil {
|
||||
return pilosa.NewBadRequestError(errors.New("min does not apply to field type bool"))
|
||||
} else if o.Max != nil {
|
||||
return pilosa.NewBadRequestError(errors.New("max does not apply to field type bool"))
|
||||
} else if o.TimeQuantum != nil {
|
||||
return pilosa.NewBadRequestError(errors.New("timeQuantum does not apply to field type bool"))
|
||||
} else if o.Keys != nil {
|
||||
return pilosa.NewBadRequestError(errors.New("keys does not apply to field type bool"))
|
||||
} else if o.ForeignIndex != nil {
|
||||
return pilosa.NewBadRequestError(errors.New("bool field cannot be a foreign key"))
|
||||
}
|
||||
default:
|
||||
return errors.Errorf("invalid field type: %s", o.Type)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
1802
cmd/sauron/main.go
Normal file
1802
cmd/sauron/main.go
Normal file
File diff suppressed because it is too large
Load diff
10
cmd/sauron/populate.sh
Normal file
10
cmd/sauron/populate.sh
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
#!/bin/bash
|
||||
mod=20
|
||||
for i in `seq 100 1`;do
|
||||
b=$(dc -e "$i $mod %p")
|
||||
[[ $b == 0 ]] && mod=$(($mod+1))
|
||||
r=$(($b+30))
|
||||
payload="Set($i, j=$r)"
|
||||
echo $payload
|
||||
curl -XPOST "localhost:10101/index/i/query" -d "$payload"
|
||||
done
|
||||
1
cmd/sauron/sample_schema.json
Normal file
1
cmd/sauron/sample_schema.json
Normal file
File diff suppressed because one or more lines are too long
396
cmd/sauron/sauron_test.go
Normal file
396
cmd/sauron/sauron_test.go
Normal file
|
|
@ -0,0 +1,396 @@
|
|||
// Copyright 2017 Pilosa Corp.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
pb "github.com/golang/protobuf/proto" //nolint:staticcheck
|
||||
pbuf "github.com/molecula/go-pilosa/v2/gopilosa_pbuf"
|
||||
"github.com/pilosa/pilosa/v2"
|
||||
"github.com/pilosa/pilosa/v2/encoding/proto"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func TestSauron(t *testing.T) {
|
||||
|
||||
cfg := NewSauronTestConfig(t)
|
||||
proxy := NewSauron(cfg)
|
||||
proxy.sql.MustRemove() //remove the existing db file if present
|
||||
url, err := proxy.Start()
|
||||
panicOn(err)
|
||||
defer proxy.Stop()
|
||||
|
||||
schemaJson := `{"indexes":[{"name":"scratch","createdAt":1611185870149882000,"options":{"keys":false,"trackExistence":true}, "fields":[{"name":"luminosity","createdAt":1595896639332730413,"options":{"type":"int","base":0,"bitDepth":31,"min":-9223372036854775808,"max":9223372036854775807,"keys":false,"foreignIndex":""}}]}]}`
|
||||
|
||||
client := &http.Client{}
|
||||
t.Run("Schema", func(t *testing.T) {
|
||||
_, err := client.Post(url+"/schema", "application/json", bytes.NewBufferString(schemaJson))
|
||||
panicOn(err)
|
||||
// verify that we can fetch it back
|
||||
resp, err := client.Get(url + "/schema")
|
||||
panicOn(err)
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
panicOn(err)
|
||||
sbody := string(body)
|
||||
if sbody[:62] != schemaJson[:62] {
|
||||
fmt.Printf("did not get posted schema back: observed:\n\n%v\n\nexpected:\n\n%v\n\n", sbody, schemaJson)
|
||||
panic("unexpected schema back")
|
||||
}
|
||||
})
|
||||
// add a set field "color"
|
||||
//POST localhost:10101/index/scratch/field/color
|
||||
t.Run("Create Non-Keyed SetField", func(t *testing.T) {
|
||||
resp, err := client.Post(url+"/index/scratch/field/rating", "application/text", bytes.NewBuffer(nil))
|
||||
panicOn(err)
|
||||
if resp.StatusCode != 200 {
|
||||
panic(fmt.Sprintf("expected 200 status, got '%v'", resp))
|
||||
}
|
||||
})
|
||||
t.Run("Create Keyed SetField", func(t *testing.T) {
|
||||
resp, err := client.Post(url+"/index/scratch/field/color", "application/text", bytes.NewBuffer([]byte(`{"options": {"keys": true}}`)))
|
||||
panicOn(err)
|
||||
if resp.StatusCode != 200 {
|
||||
panic(fmt.Sprintf("expected 200 status, got '%v'", resp))
|
||||
}
|
||||
})
|
||||
t.Run("Create Time Field", func(t *testing.T) {
|
||||
resp, err := client.Post(url+"/index/scratch/field/event", "application/text", bytes.NewBuffer([]byte(`{"options": { "type": "time", "timeQuantum": "YMDH" }}`)))
|
||||
panicOn(err)
|
||||
if resp.StatusCode != 200 {
|
||||
panic(fmt.Sprintf("expected 200 status, got '%v'", resp))
|
||||
}
|
||||
})
|
||||
|
||||
// set some bits
|
||||
|
||||
// set some bits
|
||||
|
||||
t.Run("Set", func(t *testing.T) {
|
||||
_, err = client.Post(url+"/index/scratch/query", "application/text", bytes.NewBufferString(`Set(0, color=red)`))
|
||||
panicOn(err)
|
||||
// test for dups
|
||||
_, err = client.Post(url+"/index/scratch/query", "application/text", bytes.NewBufferString(`Set(0, color=red)`))
|
||||
panicOn(err)
|
||||
//
|
||||
_, err = client.Post(url+"/index/scratch/query", "application/text", bytes.NewBufferString(`Set(1, color=red)`))
|
||||
panicOn(err)
|
||||
_, err = client.Post(url+"/index/scratch/query", "application/text", bytes.NewBufferString(`Set(2, color=red)`))
|
||||
panicOn(err)
|
||||
_, err = client.Post(url+"/index/scratch/query", "application/text", bytes.NewBufferString(`Set(3, color=red)`))
|
||||
panicOn(err)
|
||||
_, err = client.Post(url+"/index/scratch/query", "application/text", bytes.NewBufferString(`Set(4, color=red)`))
|
||||
panicOn(err)
|
||||
_, err = client.Post(url+"/index/scratch/query", "application/text", bytes.NewBufferString(`Set(5, color=red)`))
|
||||
panicOn(err)
|
||||
|
||||
_, err = client.Post(url+"/index/scratch/query", "application/text", bytes.NewBufferString(`Set(2, color=green)`))
|
||||
panicOn(err)
|
||||
_, err = client.Post(url+"/index/scratch/query", "application/text", bytes.NewBufferString(`Set(4, color=green)`))
|
||||
panicOn(err)
|
||||
_, err = client.Post(url+"/index/scratch/query", "application/text", bytes.NewBufferString(`Set(6, color=green)`))
|
||||
panicOn(err)
|
||||
_, err = client.Post(url+"/index/scratch/query", "application/text", bytes.NewBufferString(`Set(8, color=green)`))
|
||||
panicOn(err)
|
||||
|
||||
_, err = client.Post(url+"/index/scratch/query", "application/text", bytes.NewBufferString(`Set(1, color=yellow)`))
|
||||
panicOn(err)
|
||||
_, err = client.Post(url+"/index/scratch/query", "application/text", bytes.NewBufferString(`Set(3, color=yellow)`))
|
||||
panicOn(err)
|
||||
_, err = client.Post(url+"/index/scratch/query", "application/text", bytes.NewBufferString(`Set(5, color=yellow)`))
|
||||
panicOn(err)
|
||||
_, err = client.Post(url+"/index/scratch/query", "application/text", bytes.NewBufferString(`Set(7, color=yellow)`))
|
||||
panicOn(err)
|
||||
|
||||
_, err = client.Post(url+"/index/scratch/query", "application/text", bytes.NewBufferString(`Set(5, color=orange)`))
|
||||
panicOn(err)
|
||||
_, err = client.Post(url+"/index/scratch/query", "application/text", bytes.NewBufferString(`Set(6, color=orange)`))
|
||||
panicOn(err)
|
||||
_, err = client.Post(url+"/index/scratch/query", "application/text", bytes.NewBufferString(`Set(7, color=orange)`))
|
||||
panicOn(err)
|
||||
_, err = client.Post(url+"/index/scratch/query", "application/text", bytes.NewBufferString(`Set(8, color=orange)`))
|
||||
panicOn(err)
|
||||
|
||||
_, err = client.Post(url+"/index/scratch/query", "application/text", bytes.NewBufferString(`Set(1, luminosity=1)`))
|
||||
panicOn(err)
|
||||
_, err = client.Post(url+"/index/scratch/query", "application/text", bytes.NewBufferString(`Set(2, luminosity=2)`))
|
||||
panicOn(err)
|
||||
_, err = client.Post(url+"/index/scratch/query", "application/text", bytes.NewBufferString(`Set(3, luminosity=4)`))
|
||||
panicOn(err)
|
||||
_, err = client.Post(url+"/index/scratch/query", "application/text", bytes.NewBufferString(`Set(4, luminosity=5)`))
|
||||
panicOn(err)
|
||||
_, err = client.Post(url+"/index/scratch/query", "application/text", bytes.NewBufferString(`Set(5, luminosity=4)`))
|
||||
panicOn(err)
|
||||
_, err = client.Post(url+"/index/scratch/query", "application/text", bytes.NewBufferString(`Set(6, luminosity=3)`))
|
||||
panicOn(err)
|
||||
_, err = client.Post(url+"/index/scratch/query", "application/text", bytes.NewBufferString(`Set(7, luminosity=2)`))
|
||||
panicOn(err)
|
||||
_, err = client.Post(url+"/index/scratch/query", "application/text", bytes.NewBufferString(`Set(8, luminosity=1)`))
|
||||
panicOn(err)
|
||||
|
||||
_, err = client.Post(url+"/index/scratch/query", "application/text", bytes.NewBufferString(`Set(7, rating=1)`))
|
||||
panicOn(err)
|
||||
_, err = client.Post(url+"/index/scratch/query", "application/text", bytes.NewBufferString(`Set(10, rating=1)`))
|
||||
panicOn(err)
|
||||
_, err = client.Post(url+"/index/scratch/query", "application/text", bytes.NewBufferString(`Set(6, rating=2)`))
|
||||
panicOn(err)
|
||||
_, err = client.Post(url+"/index/scratch/query", "application/text", bytes.NewBufferString(`Set(5, rating=2)`))
|
||||
panicOn(err)
|
||||
_, err = client.Post(url+"/index/scratch/query", "application/text", bytes.NewBufferString(`Set(4, rating=3)`))
|
||||
panicOn(err)
|
||||
_, err = client.Post(url+"/index/scratch/query", "application/text", bytes.NewBufferString(`Set(3, rating=4)`))
|
||||
panicOn(err)
|
||||
_, err = client.Post(url+"/index/scratch/query", "application/text", bytes.NewBufferString(`Set(2, rating=4)`))
|
||||
panicOn(err)
|
||||
|
||||
//time fields
|
||||
_, err = client.Post(url+"/index/scratch/query", "application/text", bytes.NewBufferString(`Set(5, event=1,2021-02-05T01:00)`))
|
||||
panicOn(err)
|
||||
_, err = client.Post(url+"/index/scratch/query", "application/text", bytes.NewBufferString(`Set(5, event=1,2021-02-05T02:00)`))
|
||||
panicOn(err)
|
||||
_, err = client.Post(url+"/index/scratch/query", "application/text", bytes.NewBufferString(`Set(4, event=1,2021-02-05T02:05)`))
|
||||
panicOn(err)
|
||||
_, err = client.Post(url+"/index/scratch/query", "application/text", bytes.NewBufferString(`Set(3, event=1,2021-02-05T03:00)`))
|
||||
panicOn(err)
|
||||
_, err = client.Post(url+"/index/scratch/query", "application/text", bytes.NewBufferString(`Set(2, event=1,2021-02-05T04:00)`))
|
||||
panicOn(err)
|
||||
})
|
||||
t.Run("Clear", func(t *testing.T) {
|
||||
_, err = client.Post(url+"/index/scratch/query", "application/text", bytes.NewBufferString(`Clear(1, color=purple)`))
|
||||
panicOn(err)
|
||||
_, err = client.Post(url+"/index/scratch/query", "application/text", bytes.NewBufferString(`ClearRow(color=purple)`))
|
||||
panicOn(err)
|
||||
})
|
||||
t.Run("Store", func(t *testing.T) {
|
||||
_, err = client.Post(url+"/index/scratch/query", "application/text", bytes.NewBufferString(`Store(Row(color=red), color=purple)`))
|
||||
panicOn(err)
|
||||
})
|
||||
// verify that we can fetch it back
|
||||
var schema pilosa.Schema
|
||||
t.Run("Fetch Schema", func(t *testing.T) {
|
||||
resp, err := client.Get(url + "/schema")
|
||||
panicOn(err)
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
panicOn(err)
|
||||
err = json.Unmarshal(body, &schema)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("decoding request as JSON Pilosa schema: %v", err))
|
||||
}
|
||||
})
|
||||
t.Run("Import Roaring", func(t *testing.T) {
|
||||
//sets 12 bits in shard 0
|
||||
roaringData, _ := hex.DecodeString("3B3001000100000900010000000100010009000100")
|
||||
idx := schema.Indexes[0]
|
||||
var fld *pilosa.FieldInfo
|
||||
for _, f := range idx.Fields {
|
||||
if f.Name == "rating" {
|
||||
fld = f
|
||||
}
|
||||
}
|
||||
if fld == nil {
|
||||
panic("field color not found")
|
||||
}
|
||||
msg := pilosa.ImportRoaringRequest{
|
||||
IndexCreatedAt: idx.CreatedAt,
|
||||
FieldCreatedAt: fld.CreatedAt,
|
||||
Clear: false,
|
||||
Views: map[string][]byte{
|
||||
"": roaringData,
|
||||
},
|
||||
UpdateExistence: true,
|
||||
}
|
||||
ser := proto.Serializer{}
|
||||
data, err := ser.Marshal(&msg)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
furl := url + "/index/scratch/field/rating/import-roaring/0"
|
||||
req, err := http.NewRequest("POST", furl, bytes.NewBuffer(data))
|
||||
panicOn(err)
|
||||
req.Header.Set("Content-Type", "application/x-protobuf")
|
||||
req.Header.Set("Accept", "application/x-protobuf")
|
||||
|
||||
// maybe need to Accept: "application/x-protobuf"
|
||||
resp, err := client.Do(req)
|
||||
panicOn(err)
|
||||
_ = resp
|
||||
})
|
||||
t.Run("Read Query", func(t *testing.T) {
|
||||
for _, tst := range GetTests() {
|
||||
_, err = client.Post(url+"/index/scratch/query", "application/text", bytes.NewBufferString(tst.Pql))
|
||||
panicOn(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func makeImportRequest(field *FieldInfo2, shard uint64, rows, columns []uint64, clear bool) (path string, data []byte, err error) {
|
||||
msg := &pbuf.ImportRequest{
|
||||
Index: field.GetIndexName(),
|
||||
IndexCreatedAt: field.GetIndexCreatedAt(),
|
||||
Field: field.GetName(),
|
||||
FieldCreatedAt: field.GetCreatedAt(),
|
||||
Shard: shard,
|
||||
RowIDs: rows,
|
||||
ColumnIDs: columns,
|
||||
}
|
||||
data, err = pb.Marshal(msg)
|
||||
if err != nil {
|
||||
return "", nil, errors.Wrap(err, "marshaling Import to protobuf")
|
||||
}
|
||||
path = fmt.Sprintf("/index/%s/field/%s/import?clear=%s&ignoreKeyCheck=true", field.GetIndexName(), field.GetName(), strconv.FormatBool(clear))
|
||||
return path, data, nil
|
||||
}
|
||||
func makeImportKeyRequest(field *FieldInfo2, shard uint64, rows []string, columns []uint64, clear bool) (path string, data []byte, err error) {
|
||||
msg := &pbuf.ImportRequest{
|
||||
Index: field.GetIndexName(),
|
||||
IndexCreatedAt: field.GetIndexCreatedAt(),
|
||||
Field: field.GetName(),
|
||||
FieldCreatedAt: field.GetCreatedAt(),
|
||||
Shard: shard,
|
||||
RowKeys: rows,
|
||||
ColumnIDs: columns,
|
||||
}
|
||||
data, err = pb.Marshal(msg)
|
||||
if err != nil {
|
||||
return "", nil, errors.Wrap(err, "marshaling Import to protobuf")
|
||||
}
|
||||
path = fmt.Sprintf("/index/%s/field/%s/import?clear=%s&ignoreKeyCheck=true", field.GetIndexName(), field.GetName(), strconv.FormatBool(clear))
|
||||
return path, data, nil
|
||||
}
|
||||
|
||||
func makeImportValueRequest(field *FieldInfo2, shard uint64, values []int64, columns []uint64, clear bool) (path string, data []byte, err error) {
|
||||
msg := &pbuf.ImportValueRequest{
|
||||
Index: field.GetIndexName(),
|
||||
IndexCreatedAt: field.GetIndexCreatedAt(),
|
||||
Field: field.GetName(),
|
||||
FieldCreatedAt: field.GetCreatedAt(),
|
||||
Shard: shard,
|
||||
Values: values,
|
||||
ColumnIDs: columns,
|
||||
}
|
||||
data, err = pb.Marshal(msg)
|
||||
if err != nil {
|
||||
return "", nil, errors.Wrap(err, "marshaling Import to protobuf")
|
||||
}
|
||||
path = fmt.Sprintf("/index/%s/field/%s/import?clear=%s&ignoreKeyCheck=true", field.GetIndexName(), field.GetName(), strconv.FormatBool(clear))
|
||||
return path, data, nil
|
||||
}
|
||||
func TestSauronImport(t *testing.T) {
|
||||
|
||||
cfg := NewSauronTestConfig(t)
|
||||
proxy := NewSauron(cfg)
|
||||
proxy.sql.MustRemove() //remove the existing db file if present
|
||||
url, err := proxy.Start()
|
||||
panicOn(err)
|
||||
defer proxy.Stop()
|
||||
schemaJson := `{
|
||||
"indexes": [{
|
||||
"name": "scratch",
|
||||
"createdAt": 1611185870149882000,
|
||||
"options": {
|
||||
"keys": false,
|
||||
"trackExistence": true
|
||||
},
|
||||
"fields": [{
|
||||
"name": "bsi",
|
||||
"createdAt": 1595896639332730413,
|
||||
"options": {
|
||||
"type": "int",
|
||||
"base": 0,
|
||||
"bitDepth": 31,
|
||||
"min": -9223372036854775808,
|
||||
"max": 9223372036854775807,
|
||||
"keys": false,
|
||||
"foreignIndex": ""
|
||||
}
|
||||
}, {
|
||||
"name": "decimal",
|
||||
"createdAt": 1613177295654228860,
|
||||
"options": {
|
||||
"type": "decimal",
|
||||
"base": 0,
|
||||
"scale": 1,
|
||||
"bitDepth": 0,
|
||||
"min": -922337203685477580.8,
|
||||
"max": 922337203685477580.7,
|
||||
"keys": false
|
||||
}
|
||||
},{
|
||||
"name": "setkeyfield",
|
||||
"createdAt": 1613345793598357200,
|
||||
"options": {
|
||||
"type": "set",
|
||||
"cacheType": "ranked",
|
||||
"cacheSize": 50000,
|
||||
"keys": true
|
||||
}
|
||||
},{
|
||||
"name": "setfield",
|
||||
"createdAt": 1613345793598357200,
|
||||
"options": {
|
||||
"type": "set",
|
||||
"cacheType": "ranked",
|
||||
"cacheSize": 50000,
|
||||
"keys": false
|
||||
}
|
||||
}]
|
||||
}]
|
||||
}`
|
||||
client := &http.Client{}
|
||||
_, err = client.Post(url+"/schema", "application/json", bytes.NewBufferString(schemaJson))
|
||||
panicOn(err)
|
||||
|
||||
t.Run("basic row/column", func(t *testing.T) {
|
||||
f, err := proxy.sql.GetField("scratch", "setfield")
|
||||
panicOn(err)
|
||||
clear := false
|
||||
cols := []uint64{0, 1, 2, 2, 3, 4, 1, 2, 3}
|
||||
rows := []uint64{0, 0, 0, 1, 1, 1, 2, 2, 2}
|
||||
shard := uint64(0)
|
||||
path, payload, err := makeImportRequest(f, shard, rows, cols, clear)
|
||||
_, err = client.Post(url+path, "application/json", bytes.NewBuffer(payload))
|
||||
panicOn(err)
|
||||
})
|
||||
t.Run("basic rowkey/column", func(t *testing.T) {
|
||||
f, err := proxy.sql.GetField("scratch", "setkeyfield")
|
||||
panicOn(err)
|
||||
clear := false
|
||||
cols := []uint64{0, 1, 2, 2, 3, 4, 1, 2, 3}
|
||||
rows := []string{"red", "red", "red", "blue", "blue", "blue", "green", "green", "green"}
|
||||
shard := uint64(0)
|
||||
path, payload, err := makeImportKeyRequest(f, shard, rows, cols, clear)
|
||||
_, err = client.Post(url+path, "application/json", bytes.NewBuffer(payload))
|
||||
panicOn(err)
|
||||
})
|
||||
t.Run("basic import values", func(t *testing.T) {
|
||||
f, err := proxy.sql.GetField("scratch", "bsi")
|
||||
panicOn(err)
|
||||
clear := false
|
||||
cols := []uint64{0, 1, 2, 3, 4}
|
||||
values := []int64{40, 30, 20, 10, 5}
|
||||
shard := uint64(0)
|
||||
path, payload, err := makeImportValueRequest(f, shard, values, cols, clear)
|
||||
_, err = client.Post(url+path, "application/json", bytes.NewBuffer(payload))
|
||||
panicOn(err)
|
||||
})
|
||||
|
||||
}
|
||||
353
cmd/sauron/schema_test.go
Normal file
353
cmd/sauron/schema_test.go
Normal file
|
|
@ -0,0 +1,353 @@
|
|||
// Copyright 2017 Pilosa Corp.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/pilosa/pilosa/v2"
|
||||
)
|
||||
|
||||
func NewSauronTestConfig(t *testing.T) *SauronConfig {
|
||||
|
||||
bind := fmt.Sprintf("127.0.0.1:%d", GetAvailPort())
|
||||
bindGRPC := fmt.Sprintf("127.0.0.1:%d", GetAvailPort())
|
||||
gossipPort := fmt.Sprintf("%d", GetAvailPort())
|
||||
dataDir, err := ioutil.TempDir("", "pilosa-sauron-target-*")
|
||||
panicOn(err)
|
||||
|
||||
return &SauronConfig{
|
||||
DatabaseName: strings.ToLower(t.Name()),
|
||||
Bind: bind,
|
||||
BindGRPC: bindGRPC,
|
||||
GossipPort: gossipPort,
|
||||
DataDir: dataDir,
|
||||
}
|
||||
}
|
||||
|
||||
func TestProxyCanHandlePostSchema(t *testing.T) {
|
||||
|
||||
cfg := NewSauronTestConfig(t)
|
||||
proxy := NewSauron(cfg)
|
||||
proxy.sql.MustRemove() //start with
|
||||
url, err := proxy.Start()
|
||||
panicOn(err)
|
||||
defer proxy.Stop()
|
||||
|
||||
// prep for test by cleaning up leftovers from any old run.
|
||||
//panicOn(proxy.sql.DropTablesWithPrefix(t.Name()))
|
||||
|
||||
//schemaJson := `{ "indexes": [{ "name": "simple", "options": { "trackExistence": true }, "fields": [{ "name": "luminosity", "options": { "type": "int" } }, { "name ": "color", "options ": {} } ] }] }`
|
||||
/*
|
||||
schemaJson := `{ "indexes": [{ "name": "simple", "options": { "trackExistence": true }, "fields": [{ "name": "luminosity", "options": { "type": "int" } } ] }] }`
|
||||
|
||||
|
||||
vv("Posting err = '%v'", schemaJson)
|
||||
resp, err := client.Post(url+"/schema", "application/json", bytes.NewBufferString(schemaJson))
|
||||
if resp.StatusCode != http.StatusNoContent {
|
||||
panic(fmt.Sprintf("expecting %v got %v", http.StatusNoContent, resp.StatusCode))
|
||||
}
|
||||
// verify that we can fetch it back
|
||||
resp2, err := client.Get(url + "/schema")
|
||||
panicOn(err)
|
||||
body, err := ioutil.ReadAll(resp2.Body)
|
||||
panicOn(err)
|
||||
sbody := string(body)
|
||||
vv("body = '%v'", sbody)
|
||||
if sbody[:62] != schemaJson[:62] {
|
||||
fmt.Printf("did not get posted schema back: observed:\n\n%v\n\nexpected:\n\n%v\n\n", sbody, schemaJson)
|
||||
panic("unxpected schema back")
|
||||
}
|
||||
*/
|
||||
// load large schema too
|
||||
client := &http.Client{}
|
||||
schm, err := ioutil.ReadFile("sample_schema.json")
|
||||
panicOn(err)
|
||||
|
||||
resp, err := client.Post(url+"/schema", "application/json", bytes.NewBuffer(schm))
|
||||
panicOn(err)
|
||||
if resp.StatusCode != http.StatusNoContent {
|
||||
panic(fmt.Sprintf("expecting %v got %v", http.StatusNoContent, resp.StatusCode))
|
||||
}
|
||||
|
||||
// verify all tables expected are present
|
||||
obs := make(map[string]bool)
|
||||
for i, table := range proxy.sql.ListIndexFields() {
|
||||
_ = i
|
||||
obs[table] = true
|
||||
if !expectedTables[table] {
|
||||
panic(fmt.Sprintf("observed table '%v' but was not expected", table))
|
||||
}
|
||||
}
|
||||
for table := range expectedTables {
|
||||
if !obs[table] {
|
||||
panic(fmt.Sprintf("expected table '%v' but was not observed", table))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestProxyCanCreateIndexFieldViaPost(t *testing.T) {
|
||||
|
||||
cfg := NewSauronTestConfig(t)
|
||||
proxy := NewSauron(cfg)
|
||||
proxy.sql.MustRemove() //start with
|
||||
url, err := proxy.Start()
|
||||
panicOn(err)
|
||||
defer proxy.Stop()
|
||||
client := &http.Client{}
|
||||
//create non-keyed or "simple" index
|
||||
resp, err := client.Post(url+"/index/simple", "application/json", bytes.NewBuffer(nil))
|
||||
panicOn(err)
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
panic(fmt.Sprintf("expecting %v got %v", http.StatusOK, resp.StatusCode))
|
||||
}
|
||||
|
||||
fields := []struct {
|
||||
name string
|
||||
options []byte
|
||||
}{
|
||||
{name: "set", options: []byte{}},
|
||||
{name: "keyset", options: []byte{}},
|
||||
{name: "bsi", options: []byte{}},
|
||||
}
|
||||
for _, field := range fields {
|
||||
//create set field
|
||||
resp, err = client.Post(fmt.Sprintf("%v/index/simple/field/%v", url, field.name), "application/json", bytes.NewBuffer(field.options))
|
||||
panicOn(err)
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
panic(fmt.Sprintf("expecting %v got %v", http.StatusOK, resp.StatusCode))
|
||||
}
|
||||
}
|
||||
// verify that we can fetch it back
|
||||
resp2, err := client.Get(url + "/schema")
|
||||
panicOn(err)
|
||||
body, err := ioutil.ReadAll(resp2.Body)
|
||||
panicOn(err)
|
||||
var schema pilosa.Schema
|
||||
err = json.Unmarshal(body, &schema)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("decoding request as JSON Pilosa schema: %v", err))
|
||||
}
|
||||
|
||||
expected := map[string]map[string]bool{
|
||||
"simple": {"bsi": true, "set": true, "keyset": true},
|
||||
}
|
||||
for _, i := range schema.Indexes {
|
||||
ll, ok := expected[i.Name]
|
||||
if !ok {
|
||||
panic(fmt.Sprintf("expected index not present: %v", i.Name))
|
||||
}
|
||||
for _, f := range i.Fields {
|
||||
_, ok := ll[f.Name]
|
||||
if !ok {
|
||||
panic(fmt.Sprintf("expected field '%v' not present in index'%v'", f.Name, i.Name))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var expectedTables = map[string]bool{
|
||||
"trait_store/aba": true,
|
||||
"trait_store/ach_batch_recency": true,
|
||||
"trait_store/ach_pass_thru_recency": true,
|
||||
"trait_store/ach_payment_recency": true,
|
||||
"trait_store/bools": true,
|
||||
"trait_store/bools-exists": true,
|
||||
"trait_store/central_group": true,
|
||||
"trait_store/custom_audiences": true,
|
||||
"trait_store/days_since_last_logon": true,
|
||||
"trait_store/db": true,
|
||||
"trait_store/desktop_recency": true,
|
||||
"trait_store/domestic_wire_recency": true,
|
||||
"trait_store/external_transfer_recency": true,
|
||||
"trait_store/fields": true,
|
||||
"trait_store/funds_transfer_recency": true,
|
||||
"trait_store/international_wire_recency": true,
|
||||
"trait_store/mobile_remote_deposit_recency": true,
|
||||
"trait_store/payroll_recency": true,
|
||||
"trait_store/pfm_category_total_current_balance__401k_investment": true,
|
||||
"trait_store/pfm_category_total_current_balance__403b_investment": true,
|
||||
"trait_store/pfm_category_total_current_balance__529_investment": true,
|
||||
"trait_store/pfm_category_total_current_balance__any": true,
|
||||
"trait_store/pfm_category_total_current_balance__auto_loan": true,
|
||||
"trait_store/pfm_category_total_current_balance__brokerage_account": true,
|
||||
"trait_store/pfm_category_total_current_balance__certificate_of_deposit": true,
|
||||
"trait_store/pfm_category_total_current_balance__checking": true,
|
||||
"trait_store/pfm_category_total_current_balance__credit_card": true,
|
||||
"trait_store/pfm_category_total_current_balance__home_equity_loan": true,
|
||||
"trait_store/pfm_category_total_current_balance__ira_investment": true,
|
||||
"trait_store/pfm_category_total_current_balance__line_of_credit": true,
|
||||
"trait_store/pfm_category_total_current_balance__loan": true,
|
||||
"trait_store/pfm_category_total_current_balance__money_market": true,
|
||||
"trait_store/pfm_category_total_current_balance__mortgage": true,
|
||||
"trait_store/pfm_category_total_current_balance__personal_loan": true,
|
||||
"trait_store/pfm_category_total_current_balance__roth_ira_investment": true,
|
||||
"trait_store/pfm_category_total_current_balance__savings": true,
|
||||
"trait_store/pfm_category_total_current_balance__simple_ira": true,
|
||||
"trait_store/pfm_category_total_current_balance__student_loan": true,
|
||||
"trait_store/pfm_category_total_current_balance__taxable_investment": true,
|
||||
"trait_store/phone_recency": true,
|
||||
"trait_store/product_count__brokerage_account": true,
|
||||
"trait_store/product_count__commercial_cd_or_share_certificate": true,
|
||||
"trait_store/product_count__commercial_checking_or_share_draft": true,
|
||||
"trait_store/product_count__commercial_credit_card": true,
|
||||
"trait_store/product_count__commercial_indirect_auto_loan": true,
|
||||
"trait_store/product_count__commercial_line_of_credit": true,
|
||||
"trait_store/product_count__commercial_loan": true,
|
||||
"trait_store/product_count__commercial_money_market": true,
|
||||
"trait_store/product_count__commercial_new_auto_loan": true,
|
||||
"trait_store/product_count__commercial_real_estate_loan": true,
|
||||
"trait_store/product_count__commercial_savings_or_share": true,
|
||||
"trait_store/product_count__commercial_secured_credit_card": true,
|
||||
"trait_store/product_count__commercial_secured_loan": true,
|
||||
"trait_store/product_count__commercial_special_checking_or_share_draft": true,
|
||||
"trait_store/product_count__commercial_special_credit_card": true,
|
||||
"trait_store/product_count__commercial_special_savings_or_share": true,
|
||||
"trait_store/product_count__commercial_special_vehicle_loan": true,
|
||||
"trait_store/product_count__commercial_used_auto_loan": true,
|
||||
"trait_store/product_count__consumer_basic_checking_or_share_draft": true,
|
||||
"trait_store/product_count__consumer_cd_or_share_certificate": true,
|
||||
"trait_store/product_count__consumer_credit_card": true,
|
||||
"trait_store/product_count__consumer_indirect_auto_loan": true,
|
||||
"trait_store/product_count__consumer_interest_bearing_checking_or_share_draft": true,
|
||||
"trait_store/product_count__consumer_line_of_credit": true,
|
||||
"trait_store/product_count__consumer_loan": true,
|
||||
"trait_store/product_count__consumer_money_market": true,
|
||||
"trait_store/product_count__consumer_new_auto_loan": true,
|
||||
"trait_store/product_count__consumer_premium_checking_or_share_draft": true,
|
||||
"trait_store/product_count__consumer_premium_savings_or_share": true,
|
||||
"trait_store/product_count__consumer_real_estate_loan": true,
|
||||
"trait_store/product_count__consumer_restricted_checking_or_share_draft": true,
|
||||
"trait_store/product_count__consumer_restricted_savings_or_share": true,
|
||||
"trait_store/product_count__consumer_savings_or_share": true,
|
||||
"trait_store/product_count__consumer_secured_credit_card": true,
|
||||
"trait_store/product_count__consumer_secured_loan": true,
|
||||
"trait_store/product_count__consumer_special_checking_or_share_draft": true,
|
||||
"trait_store/product_count__consumer_special_credit_card": true,
|
||||
"trait_store/product_count__consumer_special_savings_or_share": true,
|
||||
"trait_store/product_count__consumer_special_vehicle_loan": true,
|
||||
"trait_store/product_count__consumer_used_auto_loan": true,
|
||||
"trait_store/product_count__debit_card": true,
|
||||
"trait_store/product_count__education_savings_or_share": true,
|
||||
"trait_store/product_count__escrow_account": true,
|
||||
"trait_store/product_count__health_savings_account": true,
|
||||
"trait_store/product_count__home_equity_line_of_credit": true,
|
||||
"trait_store/product_count__home_equity_loan": true,
|
||||
"trait_store/product_count__insurance_product": true,
|
||||
"trait_store/product_count__interest_only_legal_trust_account": true,
|
||||
"trait_store/product_count__membership_share": true,
|
||||
"trait_store/product_count__other_service": true,
|
||||
"trait_store/product_count__plan_401k": true,
|
||||
"trait_store/product_count__plan_403b": true,
|
||||
"trait_store/product_count__plan_529": true,
|
||||
"trait_store/product_count__roth_ira": true,
|
||||
"trait_store/product_count__roth_ira_cd_or_share_certificate": true,
|
||||
"trait_store/product_count__small_business_checking_or_share_draft": true,
|
||||
"trait_store/product_count__special_indirect_vehicle_loan": true,
|
||||
"trait_store/product_count__student_education_loan": true,
|
||||
"trait_store/product_count__traditional_ira": true,
|
||||
"trait_store/product_count__traditional_ira_cd_or_share_certificate": true,
|
||||
"trait_store/product_count__trust": true,
|
||||
"trait_store/product_count__vacation_club_holiday_savings": true,
|
||||
"trait_store/product_recency__brokerage_account": true,
|
||||
"trait_store/product_recency__commercial_cd_or_share_certificate": true,
|
||||
"trait_store/product_recency__commercial_checking_or_share_draft": true,
|
||||
"trait_store/product_recency__commercial_credit_card": true,
|
||||
"trait_store/product_recency__commercial_indirect_auto_loan": true,
|
||||
"trait_store/product_recency__commercial_line_of_credit": true,
|
||||
"trait_store/product_recency__commercial_loan": true,
|
||||
"trait_store/product_recency__commercial_money_market": true,
|
||||
"trait_store/product_recency__commercial_new_auto_loan": true,
|
||||
"trait_store/product_recency__commercial_real_estate_loan": true,
|
||||
"trait_store/product_recency__commercial_savings_or_share": true,
|
||||
"trait_store/product_recency__commercial_secured_credit_card": true,
|
||||
"trait_store/product_recency__commercial_secured_loan": true,
|
||||
"trait_store/product_recency__commercial_special_checking_or_share_draft": true,
|
||||
"trait_store/product_recency__commercial_special_credit_card": true,
|
||||
"trait_store/product_recency__commercial_special_savings_or_share": true,
|
||||
"trait_store/product_recency__commercial_special_vehicle_loan": true,
|
||||
"trait_store/product_recency__commercial_used_auto_loan": true,
|
||||
"trait_store/product_recency__consumer_basic_checking_or_share_draft": true,
|
||||
"trait_store/product_recency__consumer_cd_or_share_certificate": true,
|
||||
"trait_store/product_recency__consumer_credit_card": true,
|
||||
"trait_store/product_recency__consumer_indirect_auto_loan": true,
|
||||
"trait_store/product_recency__consumer_interest_bearing_checking_or_share_draft": true,
|
||||
"trait_store/product_recency__consumer_line_of_credit": true,
|
||||
"trait_store/product_recency__consumer_loan": true,
|
||||
"trait_store/product_recency__consumer_money_market": true,
|
||||
"trait_store/product_recency__consumer_new_auto_loan": true,
|
||||
"trait_store/product_recency__consumer_premium_checking_or_share_draft": true,
|
||||
"trait_store/product_recency__consumer_premium_savings_or_share": true,
|
||||
"trait_store/product_recency__consumer_real_estate_loan": true,
|
||||
"trait_store/product_recency__consumer_restricted_checking_or_share_draft": true,
|
||||
"trait_store/product_recency__consumer_restricted_savings_or_share": true,
|
||||
"trait_store/product_recency__consumer_savings_or_share": true,
|
||||
"trait_store/product_recency__consumer_secured_credit_card": true,
|
||||
"trait_store/product_recency__consumer_secured_loan": true,
|
||||
"trait_store/product_recency__consumer_special_checking_or_share_draft": true,
|
||||
"trait_store/product_recency__consumer_special_credit_card": true,
|
||||
"trait_store/product_recency__consumer_special_savings_or_share": true,
|
||||
"trait_store/product_recency__consumer_special_vehicle_loan": true,
|
||||
"trait_store/product_recency__consumer_used_auto_loan": true,
|
||||
"trait_store/product_recency__debit_card": true,
|
||||
"trait_store/product_recency__education_savings_or_share": true,
|
||||
"trait_store/product_recency__escrow_account": true,
|
||||
"trait_store/product_recency__health_savings_account": true,
|
||||
"trait_store/product_recency__home_equity_line_of_credit": true,
|
||||
"trait_store/product_recency__home_equity_loan": true,
|
||||
"trait_store/product_recency__insurance_product": true,
|
||||
"trait_store/product_recency__interest_only_legal_trust_account": true,
|
||||
"trait_store/product_recency__membership_share": true,
|
||||
"trait_store/product_recency__other_service": true,
|
||||
"trait_store/product_recency__plan_401k": true,
|
||||
"trait_store/product_recency__plan_403b": true,
|
||||
"trait_store/product_recency__plan_529": true,
|
||||
"trait_store/product_recency__roth_ira": true,
|
||||
"trait_store/product_recency__roth_ira_cd_or_share_certificate": true,
|
||||
"trait_store/product_recency__small_business_checking_or_share_draft": true,
|
||||
"trait_store/product_recency__special_indirect_vehicle_loan": true,
|
||||
"trait_store/product_recency__student_education_loan": true,
|
||||
"trait_store/product_recency__traditional_ira": true,
|
||||
"trait_store/product_recency__traditional_ira_cd_or_share_certificate": true,
|
||||
"trait_store/product_recency__trust": true,
|
||||
"trait_store/product_recency__vacation_club_holiday_savings": true,
|
||||
"trait_store/stop_payment_recency": true,
|
||||
"trait_store/survey_2053c210_adfc_4e14_afd3_9abc68a70719": true,
|
||||
"trait_store/survey_2679bd7e_cdb8_4c24_a1da_e904799382eb": true,
|
||||
"trait_store/survey_2f336120_7130_492b_b116_d93d560baa0a": true,
|
||||
"trait_store/survey_36e229ff_c0a6_48ad_928e_19b5abf69f11": true,
|
||||
"trait_store/survey_3773950d_a3a6_419a_b9e8_7ed9e33f93aa": true,
|
||||
"trait_store/survey_38aeaf3f_35ab_4df5_b213_5bd7dc29b796": true,
|
||||
"trait_store/survey_587ae933_b8e3_49a2_a23d_989df9ad119f": true,
|
||||
"trait_store/survey_5dfe1a89_29fa_4505_ac00_4cd055b758ab": true,
|
||||
"trait_store/survey_7bc3b9f0_fb8c_4a79_9a22_14be4ed71949": true,
|
||||
"trait_store/survey_9cebb459_162e_4518_88f6_bc1cdd630cd0": true,
|
||||
"trait_store/survey_a05de6d3_6620_4eb2_b9a2_511936680b57": true,
|
||||
"trait_store/survey_b570363c_ba94_4a7a_9891_141a1befc42b": true,
|
||||
"trait_store/survey_c358d6e0_4116_4230_84ed_869b315e89a1": true,
|
||||
"trait_store/survey_cce773ce_14e8_4fae_90cd_213a821a3bc0": true,
|
||||
"trait_store/survey_e49df858_4903_40ac_bd9f_96f764940f2c": true,
|
||||
"trait_store/survey_eab7aa49_26e0_4bef_a25f_ddabc9ee2bd9": true,
|
||||
"trait_store/tablet_recency": true,
|
||||
"trait_store/user_id": true,
|
||||
"trait_store/zip_code": true,
|
||||
}
|
||||
260
cmd/sauron/sqlgen_test.go
Normal file
260
cmd/sauron/sqlgen_test.go
Normal file
|
|
@ -0,0 +1,260 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/pilosa/pilosa/v2/pql"
|
||||
"github.com/shurcooL/go-goon"
|
||||
)
|
||||
|
||||
/*
|
||||
sqlite db this test validated this against
|
||||
create table bits (field ,row , column , timestamp);
|
||||
create table λbsi (field string,column bigint , val bigint);
|
||||
CREATE VIEW columns as
|
||||
select distinct column from λbits
|
||||
UNION
|
||||
select distinct column from λbsi;
|
||||
|
||||
INSERT INTO λbits VALUES( 'color','red',1);
|
||||
INSERT INTO λbits VALUES( 'color','red',2);
|
||||
INSERT INTO λbits VALUES( 'color','red',3);
|
||||
INSERT INTO λbits VALUES( 'color','red',4);
|
||||
INSERT INTO λbits VALUES( 'color','red',5);
|
||||
INSERT INTO λbits VALUES( 'color','green',2);
|
||||
INSERT INTO λbits VALUES( 'color','green',4);
|
||||
INSERT INTO λbits VALUES( 'color','green',6);
|
||||
INSERT INTO λbits VALUES( 'color','green',8);
|
||||
INSERT INTO λbits VALUES( 'color','yellow',1);
|
||||
INSERT INTO λbits VALUES( 'color','yellow',3);
|
||||
INSERT INTO λbits VALUES( 'color','yellow',5);
|
||||
INSERT INTO λbits VALUES( 'color','yellow',7);
|
||||
INSERT INTO λbits VALUES( 'color','orange',5);
|
||||
INSERT INTO λbits VALUES( 'color','orange',6);
|
||||
INSERT INTO λbits VALUES( 'color','orange',7);
|
||||
INSERT INTO λbits VALUES( 'color','orange',8);
|
||||
|
||||
INSERT INTO λbits VALUES( 'rating',1,8);
|
||||
INSERT INTO λbits VALUES( 'rating',1,7);
|
||||
INSERT INTO λbits VALUES( 'rating',1,10);
|
||||
INSERT INTO λbits VALUES( 'rating',2,6);
|
||||
INSERT INTO λbits VALUES( 'rating',2,5);
|
||||
INSERT INTO λbits VALUES( 'rating',3,4);
|
||||
INSERT INTO λbits VALUES( 'rating',4,3);
|
||||
INSERT INTO λbits VALUES( 'rating',4,2);
|
||||
|
||||
INSERT INTO λbsi VALUES( 'luminosity',1, 1);
|
||||
INSERT INTO λbsi VALUES( 'luminosity',2, 2);
|
||||
INSERT INTO λbsi VALUES( 'luminosity',3, 4);
|
||||
INSERT INTO λbsi VALUES( 'luminosity',4, 5);
|
||||
INSERT INTO λbsi VALUES( 'luminosity',5, 4);
|
||||
INSERT INTO λbsi VALUES( 'luminosity',6, 3);
|
||||
INSERT INTO λbsi VALUES( 'luminosity',7, 2);
|
||||
INSERT INTO λbsi VALUES( 'luminosity',8, 1);
|
||||
*/
|
||||
func GetTests() []struct {
|
||||
Pql string
|
||||
Sql string
|
||||
} {
|
||||
return []struct {
|
||||
Pql string
|
||||
Sql string
|
||||
}{
|
||||
{
|
||||
Pql: "Row(rating=0)",
|
||||
Sql: `select distinct column from λbits where field="rating" AND row="0"`,
|
||||
},
|
||||
{
|
||||
Pql: "Row(color=red)",
|
||||
Sql: `select distinct column from λbits where field="color" AND row="red"`,
|
||||
},
|
||||
{
|
||||
Pql: "Row(luminosity>2)",
|
||||
Sql: `select distinct column from λbsi where field="luminosity" and val>2`,
|
||||
},
|
||||
{
|
||||
Pql: "Row(luminosity<2)",
|
||||
Sql: `select distinct column from λbsi where field="luminosity" and val<2`,
|
||||
},
|
||||
{
|
||||
Pql: "Row(luminosity==4)",
|
||||
Sql: `select distinct column from λbsi where field="luminosity" and val=4`,
|
||||
},
|
||||
{
|
||||
Pql: "Row(luminosity<=4)",
|
||||
Sql: `select distinct column from λbsi where field="luminosity" and val<=4`,
|
||||
},
|
||||
{
|
||||
Pql: "Row(luminosity>=4)",
|
||||
Sql: `select distinct column from λbsi where field="luminosity" and val>=4`,
|
||||
},
|
||||
{
|
||||
Pql: "Row(luminosity!=4)",
|
||||
Sql: `select distinct column from λbsi where field="luminosity" and val!=4`,
|
||||
},
|
||||
{
|
||||
Pql: "Row(2<=luminosity<=4)",
|
||||
Sql: `select distinct column from λbsi where field="luminosity" and val>=2 and val<=4`,
|
||||
},
|
||||
{
|
||||
Pql: "Row(2<luminosity<=4)",
|
||||
Sql: `select distinct column from λbsi where field="luminosity" and val>2 and val<=4`,
|
||||
},
|
||||
{
|
||||
Pql: "Row(2<luminosity<4)",
|
||||
Sql: `select distinct column from λbsi where field="luminosity" and val>2 and val<4`,
|
||||
},
|
||||
{
|
||||
Pql: `Row(event=1, from='2021-02-01T00:00', to='2021-02-05T03:00')`,
|
||||
Sql: `select distinct column from λbits where field="event" AND row="1" AND timestamp >= "2021-02-01 00:00" AND timestamp < "2021-02-05 03:00"`,
|
||||
},
|
||||
{
|
||||
Pql: "Count(row(color=red))",
|
||||
Sql: `select count(*) from( select distinct column from λbits where field="color" AND row="red" )`,
|
||||
},
|
||||
{
|
||||
Pql: `Intersect(Row(color=red),Row(color=green))`,
|
||||
Sql: `select column from(select distinct column from λbits where field="color" AND row="red" intersect select distinct column from λbits where field="color" AND row="green")`,
|
||||
},
|
||||
{
|
||||
Pql: "Count(Union(Row(color=red),Row(color=green)))",
|
||||
Sql: `select count(*) from( select column from(select distinct column from λbits where field="color" AND row="red" union select distinct column from λbits where field="color" AND row="green") )`,
|
||||
},
|
||||
{
|
||||
Pql: "Union(Row(color=red),Row(color=green))",
|
||||
Sql: `select column from(select distinct column from λbits where field="color" AND row="red" union select distinct column from λbits where field="color" AND row="green")`,
|
||||
},
|
||||
{
|
||||
Pql: "Difference(Row(color=red),Row(color=green))",
|
||||
Sql: `select column from(select distinct column from λbits where field="color" AND row="red" except select distinct column from λbits where field="color" AND row="green")`,
|
||||
},
|
||||
{
|
||||
Pql: `All()`,
|
||||
Sql: `select column from λcolumns`,
|
||||
},
|
||||
{
|
||||
Pql: "Not(Row(color=red))",
|
||||
Sql: `select column from λcolumns except select distinct column from λbits where field="color" AND row="red"`,
|
||||
},
|
||||
{
|
||||
Pql: "Xor(Row(color=red),Row(color=green))",
|
||||
Sql: `select column from(select distinct column from λbits where field="color" AND row="red" union select distinct column from λbits where field="color" AND row="green") except select column from(select distinct column from λbits where field="color" AND row="red" intersect select distinct column from λbits where field="color" AND row="green")`,
|
||||
},
|
||||
{
|
||||
Pql: "Distinct(field=luminosity)",
|
||||
Sql: `select distinct val from λbsi where field="luminosity"`,
|
||||
},
|
||||
{
|
||||
Pql: "Distinct(Row(color=red),field=luminosity)",
|
||||
Sql: `select distinct val from λbsi where field="luminosity" AND column in (select distinct column from λbits where field="color" AND row="red")`,
|
||||
},
|
||||
{
|
||||
Pql: "Distinct(Intersect(Row(color=red),Row(color=green)),field=luminosity)",
|
||||
Sql: `select distinct val from λbsi where field="luminosity" AND column in (select column from(select distinct column from λbits where field="color" AND row="red" intersect select distinct column from λbits where field="color" AND row="green"))`,
|
||||
},
|
||||
{
|
||||
Pql: "Rows(color)",
|
||||
Sql: `select distinct field, row from λbits where field="color" order by row`,
|
||||
},
|
||||
{
|
||||
Pql: "Rows(color, limit=10)",
|
||||
Sql: `select distinct field, row from λbits where field="color" order by row limit 10`,
|
||||
},
|
||||
{
|
||||
Pql: "Rows(color, column=1)",
|
||||
Sql: `select distinct field, row from λbits where field="color" and column='1' order by row`,
|
||||
},
|
||||
// { // TODO (twg) Not supported by this test, can't reliably get key ordering
|
||||
// Pql: "Rows(color, previous=green )",
|
||||
// Sql: `select distinct field, row from λbits where field="color" and row>'green' order by row`,
|
||||
// },
|
||||
{
|
||||
Pql: "GroupBy(Rows(color))",
|
||||
Sql: `select f1.field, f1.row,count(*) as cnt from λbits f1 where f1.field='color' group by f1.field,f1.row order by cnt desc`,
|
||||
},
|
||||
{
|
||||
Pql: "GroupBy(Rows(color),Rows(rating))",
|
||||
Sql: `select f1.field, f1.row,f2.field, f2.row,count(*) as cnt from λbits f1 inner join λbits f2 on f1.column = f2.column where f1.field='color'and f2.field='rating' group by f1.field,f1.row,f2.field,f2.row order by cnt desc`,
|
||||
},
|
||||
{
|
||||
Pql: "TopN(color)",
|
||||
Sql: `select row,count(*) as cnt from λbits where field="color" group by row order by cnt desc`,
|
||||
},
|
||||
{
|
||||
Pql: "TopN(color,n=2)",
|
||||
Sql: `select row,count(*) as cnt from λbits where field="color" group by row order by cnt desc limit 2`,
|
||||
},
|
||||
{
|
||||
Pql: "TopN(color,Row(color=red),n=2)",
|
||||
Sql: `select row,count(*) as cnt from λbits where field="color" and column in (select distinct column from λbits where field="color" AND row="red") group by row order by cnt desc limit 2`,
|
||||
},
|
||||
{
|
||||
Pql: "Topk(color)",
|
||||
Sql: `select row,count(*) as cnt from λbits where field="color" group by row order by cnt desc`,
|
||||
},
|
||||
{
|
||||
Pql: "TopK(color, k=2, filter=Row(rating=1))",
|
||||
Sql: `select row,count(*) as cnt from λbits where field="color" and column in (select distinct column from λbits where field="rating" AND row="1") group by row order by cnt desc limit 2`,
|
||||
},
|
||||
{
|
||||
Pql: `Min(field="luminosity")`,
|
||||
Sql: `select val,count(*) from λbsi where val=(select min(val) from λbsi where field="luminosity")`,
|
||||
},
|
||||
{
|
||||
Pql: `Max(field="luminosity")`,
|
||||
Sql: `select val,count(*) from λbsi where val=(select max(val) from λbsi where field="luminosity")`,
|
||||
},
|
||||
{
|
||||
Pql: `Min(Row(color=orange),field="luminosity")`,
|
||||
Sql: `select val,count(*) from λbsi where val=(select min(val) from λbsi where field="luminosity" and column in (select distinct column from λbits where field="color" AND row="orange")) and column in (select distinct column from λbits where field="color" AND row="orange")`,
|
||||
},
|
||||
{
|
||||
Pql: `Min(Intersect(Row(color=red),Row(color=orange)),field="luminosity")`,
|
||||
Sql: `select val,count(*) from λbsi where val=(select min(val) from λbsi where field="luminosity" and column in (select column from(select distinct column from λbits where field="color" AND row="red" intersect select distinct column from λbits where field="color" AND row="orange"))) and column in (select column from(select distinct column from λbits where field="color" AND row="red" intersect select distinct column from λbits where field="color" AND row="orange"))`,
|
||||
},
|
||||
{
|
||||
Pql: `Sum(field="luminosity")`,
|
||||
Sql: `select sum(val),count(*) from λbsi where field="luminosity"`,
|
||||
},
|
||||
{
|
||||
Pql: `ConstRow(columns=[1,3,5])`,
|
||||
Sql: `select column1 as column from (values (1),(3),(5))`,
|
||||
},
|
||||
{
|
||||
Pql: `UnionRows(Rows(color))`,
|
||||
Sql: `select distinct b.column from (select field,row from(select distinct field, row from λbits where field="color" order by row)) sq inner join λbits b on sq.field = b.field and sq.row = b.row`,
|
||||
},
|
||||
{
|
||||
Pql: `UnionRows(Rows(color),Rows(rating))`,
|
||||
Sql: `select distinct b.column from (select field,row from(select distinct field, row from λbits where field="color" order by row)union all select field,row from (select distinct field, row from λbits where field="rating" order by row)) sq inner join λbits b on sq.field = b.field and sq.row = b.row`,
|
||||
},
|
||||
{
|
||||
Pql: `IncludesColumn(Row(color=red), column=1)`,
|
||||
Sql: `select count(*)>0 from (select column from(select distinct column from λbits where field="color" AND row="red") where column='1')`,
|
||||
},
|
||||
{
|
||||
Pql: `Extract(Row(color=red),Rows(color),Rows(rating))`,
|
||||
Sql: `select sq.field, sq.row, b.column from (select field,row from(select distinct field, row from λbits where field="color" order by row)union all select field,row from (select distinct field, row from λbits where field="rating" order by row)) sq inner join λbits b on sq.field = b.field and sq.row = b.row where b.column in (select distinct column from λbits where field="color" AND row="red")`,
|
||||
},
|
||||
{
|
||||
Pql: `Extract(ConstRow(columns=[1,2,3]))`,
|
||||
Sql: `select '' as field, '' as row,column from (select column1 as column from (values (1),(2),(3)))`,
|
||||
},
|
||||
}
|
||||
}
|
||||
func TestParse(t *testing.T) {
|
||||
|
||||
for _, tst := range GetTests() {
|
||||
|
||||
q, err := pql.NewParser(strings.NewReader(tst.Pql)).Parse()
|
||||
panicOn(err)
|
||||
sql, _, _, err := ToSql(q.Calls, "")
|
||||
panicOn(err)
|
||||
if sql != tst.Sql {
|
||||
vv("\npql:%v\ngenerated:\n%v\nexpected:\n%v\n", tst.Pql, sql, tst.Sql)
|
||||
goon.Dump(q)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
51
cmd/sauron/test_sqlite.sh
Executable file
51
cmd/sauron/test_sqlite.sh
Executable file
|
|
@ -0,0 +1,51 @@
|
|||
#!/bin/bash
|
||||
db="poc2.db"
|
||||
sqlite3 ${db} <<EOF
|
||||
create table λbits (field ,row , column , timestamp);
|
||||
create table λbsi (field string,column bigint , val bigint);
|
||||
|
||||
CREATE VIEW λcolumns as
|
||||
select distinct column from λbits
|
||||
UNION
|
||||
select distinct column from λbsi;
|
||||
INSERT INTO λbsi VALUES( 'luminosity',1, 1);
|
||||
INSERT INTO λbsi VALUES( 'luminosity',2, 2);
|
||||
INSERT INTO λbsi VALUES( 'luminosity',3, 4);
|
||||
INSERT INTO λbsi VALUES( 'luminosity',4, 5);
|
||||
INSERT INTO λbsi VALUES( 'luminosity',5, 4);
|
||||
INSERT INTO λbsi VALUES( 'luminosity',6, 3);
|
||||
INSERT INTO λbsi VALUES( 'luminosity',7, 2);
|
||||
INSERT INTO λbsi VALUES( 'luminosity',8, 1);
|
||||
|
||||
INSERT INTO λbits (field,row,column)VALUES( 'color','red',1);
|
||||
INSERT INTO λbits (field,row,column)VALUES( 'color','red',2);
|
||||
INSERT INTO λbits (field,row,column)VALUES( 'color','red',3);
|
||||
INSERT INTO λbits (field,row,column)VALUES( 'color','red',4);
|
||||
INSERT INTO λbits (field,row,column)VALUES( 'color','red',5);
|
||||
INSERT INTO λbits (field,row,column)VALUES( 'color','green',2);
|
||||
INSERT INTO λbits (field,row,column)VALUES( 'color','green',4);
|
||||
INSERT INTO λbits (field,row,column)VALUES( 'color','green',6);
|
||||
INSERT INTO λbits (field,row,column)VALUES( 'color','green',8);
|
||||
INSERT INTO λbits (field,row,column)VALUES( 'color','yellow',1);
|
||||
INSERT INTO λbits (field,row,column)VALUES( 'color','yellow',3);
|
||||
INSERT INTO λbits (field,row,column)VALUES( 'color','yellow',5);
|
||||
INSERT INTO λbits (field,row,column)VALUES( 'color','yellow',7);
|
||||
INSERT INTO λbits (field,row,column)VALUES( 'color','orange',5);
|
||||
INSERT INTO λbits (field,row,column)VALUES( 'color','orange',6);
|
||||
INSERT INTO λbits (field,row,column)VALUES( 'color','orange',7);
|
||||
INSERT INTO λbits (field,row,column)VALUES( 'color','orange',8);
|
||||
INSERT INTO λbits (field,row,column)VALUES( 'rating','1',8);
|
||||
INSERT INTO λbits (field,row,column)VALUES( 'rating','1',7);
|
||||
INSERT INTO λbits (field,row,column)VALUES( 'rating','1',10);
|
||||
INSERT INTO λbits (field,row,column)VALUES( 'rating','2',6);
|
||||
INSERT INTO λbits (field,row,column)VALUES( 'rating','2',5);
|
||||
INSERT INTO λbits (field,row,column)VALUES( 'rating','3',4);
|
||||
INSERT INTO λbits (field,row,column)VALUES( 'rating','4',3);
|
||||
INSERT INTO λbits (field,row,column)VALUES( 'rating','4',2);
|
||||
|
||||
INSERT INTO λbits (field,row,column,timestamp)VALUES( 'event','1',5,'2021-02-05 01:00');
|
||||
INSERT INTO λbits (field,row,column,timestamp)VALUES( 'event','1',5,'2021-02-05 02:00');
|
||||
INSERT INTO λbits (field,row,column,timestamp)VALUES( 'event','1',4,'2021-02-05 02:05');
|
||||
INSERT INTO λbits (field,row,column,timestamp)VALUES( 'event','1',3,'2021-02-05 03:00');
|
||||
INSERT INTO λbits (field,row,column,timestamp)VALUES( 'event','1',2,'2021-02-05 04:00');
|
||||
EOF
|
||||
167
cmd/sauron/vprint.go
Normal file
167
cmd/sauron/vprint.go
Normal file
|
|
@ -0,0 +1,167 @@
|
|||
// home: https://github.com/glycerine/vprint
|
||||
// Copyright 2019 Jason E. Aten, Ph.D. All rights reserved.
|
||||
// License: MIT
|
||||
//
|
||||
// MIT License
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
"runtime"
|
||||
"runtime/debug"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
const RFC3339MsecTz0 = "2006-01-02T15:04:05.000Z07:00"
|
||||
const RFC3339UsecTz0 = "2006-01-02T15:04:05.000000Z07:00"
|
||||
|
||||
// for tons of debug output
|
||||
var VerboseVerbose bool = false
|
||||
|
||||
// convience functions for . import
|
||||
var pp = PP
|
||||
var vv = VV
|
||||
|
||||
var panicOn = PanicOn
|
||||
|
||||
func init() {
|
||||
// keeper linter happy
|
||||
_ = pp
|
||||
_ = vv
|
||||
}
|
||||
|
||||
func PanicOn(err error) {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func PP(format string, a ...interface{}) {
|
||||
if VerboseVerbose {
|
||||
TSPrintf(format, a...)
|
||||
}
|
||||
}
|
||||
|
||||
func VV(format string, a ...interface{}) {
|
||||
TSPrintf(format, a...)
|
||||
}
|
||||
|
||||
func AlwaysPrintf(format string, a ...interface{}) {
|
||||
TSPrintf(format, a...)
|
||||
}
|
||||
|
||||
var tsPrintfMut sync.Mutex
|
||||
|
||||
// time-stamped printf
|
||||
func TSPrintf(format string, a ...interface{}) {
|
||||
tsPrintfMut.Lock()
|
||||
Printf("\n%s %s ", FileLine(3), ts())
|
||||
Printf(format+"\n", a...)
|
||||
tsPrintfMut.Unlock()
|
||||
}
|
||||
|
||||
// get timestamp for logging purposes
|
||||
func ts() string {
|
||||
return time.Now().Format(RFC3339UsecTz0)
|
||||
}
|
||||
|
||||
// so we can multi write easily, use our own printf
|
||||
var OurStdout io.Writer = os.Stdout
|
||||
|
||||
// Printf formats according to a format specifier and writes to standard output.
|
||||
// It returns the number of bytes written and any write error encountered.
|
||||
func Printf(format string, a ...interface{}) (n int, err error) {
|
||||
return fmt.Fprintf(OurStdout, format, a...)
|
||||
}
|
||||
|
||||
func FileLine(depth int) string {
|
||||
_, fileName, fileLine, ok := runtime.Caller(depth)
|
||||
var s string
|
||||
if ok {
|
||||
s = fmt.Sprintf("%s:%d", path.Base(fileName), fileLine)
|
||||
} else {
|
||||
s = ""
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func stack() string {
|
||||
return string(debug.Stack())
|
||||
}
|
||||
|
||||
func FileExists(name string) bool {
|
||||
fi, err := os.Stat(name)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
if fi.IsDir() {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func DirExists(name string) bool {
|
||||
fi, err := os.Stat(name)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
if fi.IsDir() {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func FileSize(name string) (int64, error) {
|
||||
fi, err := os.Stat(name)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
return fi.Size(), nil
|
||||
}
|
||||
|
||||
// Caller returns the name of the calling function.
|
||||
func Caller(upStack int) string {
|
||||
// elide ourself and runtime.Callers
|
||||
target := upStack + 2
|
||||
|
||||
pc := make([]uintptr, target+2)
|
||||
n := runtime.Callers(0, pc)
|
||||
|
||||
f := runtime.Frame{Function: "unknown"}
|
||||
if n > 0 {
|
||||
frames := runtime.CallersFrames(pc[:n])
|
||||
for i := 0; i <= target; i++ {
|
||||
contender, more := frames.Next()
|
||||
if i == target {
|
||||
f = contender
|
||||
}
|
||||
if !more {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return f.Function
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue